|
19 | 19 | #include <stdexcept> |
20 | 20 | #include <cstdint> |
21 | 21 | #include <type_traits> |
| 22 | +#include "detail/buffer_dtype.h" |
22 | 23 |
|
23 | 24 | namespace py = pybind11; |
24 | 25 |
|
@@ -263,110 +264,84 @@ DEF_SPECIAL(isfinite) |
263 | 264 | // Type conversion |
264 | 265 | // ============================================================================ |
265 | 266 |
|
266 | | -/// ndarray.astype(dtype) — unified dtype dispatch |
| 267 | +/// ndarray.astype(dtype) — unified dtype dispatch. |
| 268 | +/// 零 Python 调用: 仅通过 buf.format + itemsize (C API) 检测源类型; |
| 269 | +/// 不使用 py::dtype::is() / kind(),避免 Python↔C++ 递归。 |
| 270 | +/// 目标必须显式指定精度: "float64"/"double", "float32", 不接受模糊的 "float"。 |
267 | 271 | inline py::array astype(const py::array& arr, const std::string& dtype) { |
268 | | - auto buf = arr.request(); |
269 | | - auto dt = arr.dtype(); |
270 | | - |
271 | | - // py::dtype::of<float>() / py::dtype::of<double>() 在 Python 传入的 |
272 | | - // numpy 数组上可能不匹配(已知 pybind11 问题)。用 buffer format + itemsize |
273 | | - // 回退——buf.format 来自 C API 的 PyObject_GetBuffer,不触发 Python 属性调用, |
274 | | - // 避免 astype 内递归。 |
275 | | - char _fmt_char = buf.format.empty() ? '\0' : |
276 | | - (buf.format[0] == '<' || buf.format[0] == '>' || buf.format[0] == '=') |
277 | | - ? buf.format[1] : buf.format[0]; |
278 | | - bool _is_f32 = (_fmt_char == 'f' && buf.itemsize == 4); |
279 | | - bool _is_f64 = (_fmt_char == 'd' && buf.itemsize == 8) || |
280 | | - (_fmt_char == 'f' && buf.itemsize == 8); |
281 | | - bool _is_i32 = (_fmt_char == 'i' && buf.itemsize == 4) || |
282 | | - (_fmt_char == 'l' && buf.itemsize == 4); |
283 | | - bool _is_i64 = (_fmt_char == 'i' && buf.itemsize == 8) || |
284 | | - (_fmt_char == 'l' && buf.itemsize == 8) || |
285 | | - (_fmt_char == 'q' && buf.itemsize == 8); |
286 | | - bool _is_bool = (_fmt_char == '?' || _fmt_char == 'b'); |
287 | | - |
288 | | -#define _ASTYPE_MATCH(SrcT) \ |
289 | | - (dt.is(py::dtype::of<SrcT>()) || \ |
290 | | - (std::is_same<SrcT, float>::value && _is_f32) || \ |
291 | | - (std::is_same<SrcT, double>::value && _is_f64) || \ |
292 | | - (std::is_same<SrcT, int>::value && _is_i32) || \ |
293 | | - (std::is_same<SrcT, int64_t>::value&& _is_i64) || \ |
294 | | - (std::is_same<SrcT, bool>::value && _is_bool)) |
295 | | - |
296 | | -#define _ASTYPE_CASE(SrcT, dst_str, DstT) \ |
297 | | - if (_ASTYPE_MATCH(SrcT) && (dtype == dst_str)) { \ |
298 | | - py::array_t<DstT> r(buf.shape); \ |
299 | | - numpy::astype<DstT, SrcT>(static_cast<const SrcT*>(buf.ptr), \ |
300 | | - static_cast<DstT*>(r.request().ptr), buf.size); \ |
301 | | - return r; \ |
| 272 | + auto buf = arr.request(); // C API — 零 Python |
| 273 | + auto src = detail::analyze_buffer(buf); // 纯 C++ — 零 Python |
| 274 | + auto dst = detail::target_dtype(dtype); |
| 275 | + |
| 276 | + if (dst.kind == detail::BufKind::UNKNOWN) { |
| 277 | + auto dt = arr.dtype(); // 仅错误路径: 获取 dtype 名称用于报错 |
| 278 | + throw std::runtime_error( |
| 279 | + "astype: unsupported target dtype '" + dtype + "'. " |
| 280 | + "Available: float64/double, float32, int/int32, int64, bool. " |
| 281 | + "Source: " + std::string(py::str(dt))); |
302 | 282 | } |
303 | 283 |
|
304 | | - // ═══════════════════════════════════════════════════════════════════════ |
305 | | - // 目标字符串语义(严格对齐 numpy ndarray.astype() 行为): |
306 | | - // |
307 | | - // "float64" / "double" → C++ double (64-bit) |
308 | | - // "float32" → C++ float (32-bit) |
309 | | - // "float" → C++ double (64-bit) ← 注意!numpy 中 |
310 | | - // np.float64(1).astype(float) → float64 |
311 | | - // np.float32(1).astype(float) → float64 |
312 | | - // np.int32(1).astype(float) → float64 |
313 | | - // 即 numpy 默认 float = float64,不是 float32 |
314 | | - // "int" / "int32" → C++ int (32-bit) |
315 | | - // "int64" → C++ int64_t |
316 | | - // "bool" → C++ bool |
317 | | - // ═══════════════════════════════════════════════════════════════════════ |
318 | | - // float64 |
319 | | - _ASTYPE_CASE(double, "float64", double) |
320 | | - _ASTYPE_CASE(double, "double", double) |
321 | | - _ASTYPE_CASE(double, "float32", float) |
322 | | - _ASTYPE_CASE(double, "float", double) // numpy: float64.astype(float) → float64 |
323 | | - _ASTYPE_CASE(double, "int", int) |
324 | | - _ASTYPE_CASE(double, "int32", int) |
325 | | - _ASTYPE_CASE(double, "int64", int64_t) |
326 | | - _ASTYPE_CASE(double, "bool", bool) |
327 | | - // float32 |
328 | | - _ASTYPE_CASE(float, "float64", double) |
329 | | - _ASTYPE_CASE(float, "double", double) |
330 | | - _ASTYPE_CASE(float, "float", double) // "float" → float64 (numpy 默认) |
331 | | - _ASTYPE_CASE(float, "float32", float) |
332 | | - _ASTYPE_CASE(float, "int", int) |
333 | | - _ASTYPE_CASE(float, "int32", int) |
334 | | - _ASTYPE_CASE(float, "int64", int64_t) |
335 | | - _ASTYPE_CASE(float, "bool", bool) |
336 | | - // int32 |
337 | | - _ASTYPE_CASE(int, "int32", int) |
338 | | - _ASTYPE_CASE(int, "int", int) |
339 | | - _ASTYPE_CASE(int, "float64", double) |
340 | | - _ASTYPE_CASE(int, "double", double) |
341 | | - _ASTYPE_CASE(int, "float32", float) |
342 | | - _ASTYPE_CASE(int, "float", double) // "float" → float64 (numpy 默认) |
343 | | - _ASTYPE_CASE(int, "int64", int64_t) |
344 | | - _ASTYPE_CASE(int, "bool", bool) |
345 | | - // int64 |
346 | | - _ASTYPE_CASE(int64_t, "int64", int64_t) |
347 | | - _ASTYPE_CASE(int64_t, "float64", double) |
348 | | - _ASTYPE_CASE(int64_t, "double", double) |
349 | | - _ASTYPE_CASE(int64_t, "float32", float) |
350 | | - _ASTYPE_CASE(int64_t, "float", double) // "float" → float64 (numpy 默认) |
351 | | - _ASTYPE_CASE(int64_t, "int", int) |
352 | | - _ASTYPE_CASE(int64_t, "int32", int) |
353 | | - _ASTYPE_CASE(int64_t, "bool", bool) |
354 | | - // bool |
355 | | - _ASTYPE_CASE(bool, "bool", bool) |
356 | | - _ASTYPE_CASE(bool, "float64", double) |
357 | | - _ASTYPE_CASE(bool, "double", double) |
358 | | - _ASTYPE_CASE(bool, "float32", float) |
359 | | - _ASTYPE_CASE(bool, "float", double) // "float" → float64 (numpy 默认) |
360 | | - _ASTYPE_CASE(bool, "int", int) |
361 | | - _ASTYPE_CASE(bool, "int32", int) |
362 | | - _ASTYPE_CASE(bool, "int64", int64_t) |
363 | | -#undef _ASTYPE_CASE |
364 | | -#undef _ASTYPE_MATCH |
| 284 | + // dispatch: 源类型 × 目标类型 → 调用对应模板实例 |
| 285 | + switch (src.kind) { |
| 286 | + case detail::BufKind::FLOAT64: |
| 287 | + switch (dst.kind) { |
| 288 | + case detail::BufKind::FLOAT64: { py::array_t<double> r(buf.shape); numpy::astype<double,double>(static_cast<const double*>(buf.ptr),static_cast<double*>(r.request().ptr),buf.size); return r; } |
| 289 | + case detail::BufKind::FLOAT32: { py::array_t<float> r(buf.shape); numpy::astype<float, double>(static_cast<const double*>(buf.ptr),static_cast<float*>(r.request().ptr),buf.size); return r; } |
| 290 | + case detail::BufKind::INT32: { py::array_t<int> r(buf.shape); numpy::astype<int, double>(static_cast<const double*>(buf.ptr),static_cast<int*>(r.request().ptr),buf.size); return r; } |
| 291 | + case detail::BufKind::INT64: { py::array_t<int64_t>r(buf.shape); numpy::astype<int64_t,double>(static_cast<const double*>(buf.ptr),static_cast<int64_t*>(r.request().ptr),buf.size); return r; } |
| 292 | + case detail::BufKind::BOOL: { py::array_t<bool> r(buf.shape); numpy::astype<bool, double>(static_cast<const double*>(buf.ptr),static_cast<bool*>(r.request().ptr),buf.size); return r; } |
| 293 | + default: break; |
| 294 | + } |
| 295 | + break; |
| 296 | + case detail::BufKind::FLOAT32: |
| 297 | + switch (dst.kind) { |
| 298 | + case detail::BufKind::FLOAT64: { py::array_t<double> r(buf.shape); numpy::astype<double,float>(static_cast<const float*>(buf.ptr),static_cast<double*>(r.request().ptr),buf.size); return r; } |
| 299 | + case detail::BufKind::FLOAT32: { py::array_t<float> r(buf.shape); numpy::astype<float, float>(static_cast<const float*>(buf.ptr),static_cast<float*>(r.request().ptr),buf.size); return r; } |
| 300 | + case detail::BufKind::INT32: { py::array_t<int> r(buf.shape); numpy::astype<int, float>(static_cast<const float*>(buf.ptr),static_cast<int*>(r.request().ptr),buf.size); return r; } |
| 301 | + case detail::BufKind::INT64: { py::array_t<int64_t>r(buf.shape); numpy::astype<int64_t,float>(static_cast<const float*>(buf.ptr),static_cast<int64_t*>(r.request().ptr),buf.size); return r; } |
| 302 | + case detail::BufKind::BOOL: { py::array_t<bool> r(buf.shape); numpy::astype<bool, float>(static_cast<const float*>(buf.ptr),static_cast<bool*>(r.request().ptr),buf.size); return r; } |
| 303 | + default: break; |
| 304 | + } |
| 305 | + break; |
| 306 | + case detail::BufKind::INT32: |
| 307 | + switch (dst.kind) { |
| 308 | + case detail::BufKind::FLOAT64: { py::array_t<double> r(buf.shape); numpy::astype<double,int>(static_cast<const int*>(buf.ptr),static_cast<double*>(r.request().ptr),buf.size); return r; } |
| 309 | + case detail::BufKind::FLOAT32: { py::array_t<float> r(buf.shape); numpy::astype<float, int>(static_cast<const int*>(buf.ptr),static_cast<float*>(r.request().ptr),buf.size); return r; } |
| 310 | + case detail::BufKind::INT32: { py::array_t<int> r(buf.shape); numpy::astype<int, int>(static_cast<const int*>(buf.ptr),static_cast<int*>(r.request().ptr),buf.size); return r; } |
| 311 | + case detail::BufKind::INT64: { py::array_t<int64_t>r(buf.shape); numpy::astype<int64_t,int>(static_cast<const int*>(buf.ptr),static_cast<int64_t*>(r.request().ptr),buf.size); return r; } |
| 312 | + case detail::BufKind::BOOL: { py::array_t<bool> r(buf.shape); numpy::astype<bool, int>(static_cast<const int*>(buf.ptr),static_cast<bool*>(r.request().ptr),buf.size); return r; } |
| 313 | + default: break; |
| 314 | + } |
| 315 | + break; |
| 316 | + case detail::BufKind::INT64: |
| 317 | + switch (dst.kind) { |
| 318 | + case detail::BufKind::FLOAT64: { py::array_t<double> r(buf.shape); numpy::astype<double,int64_t>(static_cast<const int64_t*>(buf.ptr),static_cast<double*>(r.request().ptr),buf.size); return r; } |
| 319 | + case detail::BufKind::FLOAT32: { py::array_t<float> r(buf.shape); numpy::astype<float, int64_t>(static_cast<const int64_t*>(buf.ptr),static_cast<float*>(r.request().ptr),buf.size); return r; } |
| 320 | + case detail::BufKind::INT32: { py::array_t<int> r(buf.shape); numpy::astype<int, int64_t>(static_cast<const int64_t*>(buf.ptr),static_cast<int*>(r.request().ptr),buf.size); return r; } |
| 321 | + case detail::BufKind::INT64: { py::array_t<int64_t>r(buf.shape); numpy::astype<int64_t,int64_t>(static_cast<const int64_t*>(buf.ptr),static_cast<int64_t*>(r.request().ptr),buf.size); return r; } |
| 322 | + case detail::BufKind::BOOL: { py::array_t<bool> r(buf.shape); numpy::astype<bool, int64_t>(static_cast<const int64_t*>(buf.ptr),static_cast<bool*>(r.request().ptr),buf.size); return r; } |
| 323 | + default: break; |
| 324 | + } |
| 325 | + break; |
| 326 | + case detail::BufKind::BOOL: |
| 327 | + switch (dst.kind) { |
| 328 | + case detail::BufKind::FLOAT64: { py::array_t<double> r(buf.shape); numpy::astype<double,bool>(static_cast<const bool*>(buf.ptr),static_cast<double*>(r.request().ptr),buf.size); return r; } |
| 329 | + case detail::BufKind::FLOAT32: { py::array_t<float> r(buf.shape); numpy::astype<float, bool>(static_cast<const bool*>(buf.ptr),static_cast<float*>(r.request().ptr),buf.size); return r; } |
| 330 | + case detail::BufKind::INT32: { py::array_t<int> r(buf.shape); numpy::astype<int, bool>(static_cast<const bool*>(buf.ptr),static_cast<int*>(r.request().ptr),buf.size); return r; } |
| 331 | + case detail::BufKind::INT64: { py::array_t<int64_t>r(buf.shape); numpy::astype<int64_t,bool>(static_cast<const bool*>(buf.ptr),static_cast<int64_t*>(r.request().ptr),buf.size); return r; } |
| 332 | + case detail::BufKind::BOOL: { py::array_t<bool> r(buf.shape); numpy::astype<bool, bool>(static_cast<const bool*>(buf.ptr),static_cast<bool*>(r.request().ptr),buf.size); return r; } |
| 333 | + default: break; |
| 334 | + } |
| 335 | + break; |
| 336 | + default: |
| 337 | + break; |
| 338 | + } |
365 | 339 |
|
| 340 | + auto dt = arr.dtype(); // 仅错误路径 |
366 | 341 | throw std::runtime_error( |
367 | 342 | "astype: unsupported conversion " + std::string(py::str(dt)) + |
368 | | - " -> " + dtype + ". Available targets: float64/double, float32/float, " |
369 | | - "int/int32, int64, bool."); |
| 343 | + " -> " + dtype + ". Available targets: float64/double, float, " |
| 344 | + "float32, int/int32, int64, bool."); |
370 | 345 | } |
371 | 346 |
|
372 | 347 | /// float64 → float32 → float64 roundtrip |
|
0 commit comments