[Type] ndarray typing 3: Add cook_dtype calls at C++ boundaries#413
[Type] ndarray typing 3: Add cook_dtype calls at C++ boundaries#413hughperkins wants to merge 12 commits intohp/typing-t4-2-ndarray-subscriptfrom
Conversation
89f34d8 to
72a9636
Compare
219619c to
5762be5
Compare
|
Opus 4.6 review: PR Review: hp/typing-t4-3-cook-dtypeBranch: SummaryThis PR adds Issues Found1. Bug fix (positive finding):
|
72a9636 to
9b5e1a8
Compare
5762be5 to
a402c99
Compare
9b5e1a8 to
1adb863
Compare
…refactor) Add cook_dtype() calls at all points where dtype values are passed to C++ code. Make PyQuadrants.default_fp/ip/up into properties that always store DataTypeCxx. Rename shadowed dtype var in create_field_member. All changes are behavioral no-ops with current code, preparing for a future refactor of primitive dtypes into Python classes.
1adb863 to
71dcd62
Compare
The previous code overwrote the outer `dtype` parameter in the debug checkbit block, causing x_dual to be created with the wrong dtype.
Verify that forward-mode AD produces correct results when debug=True, guarding against the previous bug where the checkbit block's local dtype variable shadowed the outer dtype parameter.
The regression test covers this; no need for a code comment.
When dtype is provided it is already cooked at the top of the function, so the per-branch cook_dtype(constant_dtype) was a no-op. Now only the fallback default_fp/default_ip paths are cooked.
Fixes pylint C0415 (import-outside-toplevel) on `inspect` in `exception.py` and `get_func_signature` in `_kernel_impl_dataclass.py`. Made-with: Cursor
The Vulkan/Metal backends on macOS lack f64 support and crash when running this test, which uses qd.f64 fields. Add require=qd.extension.data64 to skip on backends without double-precision support. Made-with: Cursor
…type Made-with: Cursor # Conflicts: # python/quadrants/lang/_perf_dispatch.py
…ook-dtype # Conflicts: # python/quadrants/lang/exception.py
The previous version used qd.f64 and was gated on qd.extension.data64, so it was skipped on backends without f64 support (Vulkan/Metal on macOS) -- meaning the regression wasn't exercised everywhere. Rewrite with f32 (universally supported) and pick values whose dual is a non-integer exactly representable in f32 (x=1.25, dual=2x=2.5). Under the original bug the dual field was created as u8 (or i32 on Vulkan), which would truncate 2.5 to 2 and fail the assertion -- so the test still catches the regression on every backend.
Run the regression test on both f32 and f64, using test_utils.skip_if_f64_unsupported to skip f64 only on backends that don't reliably support it (Metal, Vulkan). f32 still runs everywhere so the bug is exercised on every backend; f64 adds extra coverage where supported.
…refactor)
Add cook_dtype() calls at all points where dtype values are passed to C++ code. Make PyQuadrants.default_fp/ip/up into properties that always store DataTypeCxx. Rename shadowed dtype var in create_field_member. All changes are behavioral no-ops with current code, preparing for a future refactor of primitive dtypes into Python classes.
Issue: #
Brief Summary
Summary
Pushes
cook_dtype()(the conversion from Python-side dtype objects to the C++DataTypeCxxrepresentation) closer to its consumers, so that:default_fp,default_ip,default_up) onPyQuadrantsare stored already-cooked. Direct attribute access is preserved via@propertygetter/setter pairs that auto-cook on assignment, so user code likeqd.init(default_fp=qd.f64)keeps working.expr_alloca_shared_array, constant-expression makers, etc.) receive cooked dtypes at the call site rather than relyingon the C++ binding to coerce — making the Python/C++ boundary explicit and catching un-cooked types earlier.
create_field_memberwhere the local checkbit dtype was being assigned to the function parameterdtype, mutating thecaller-visible variable. Renamed to
checkbit_dtypeand added a debug-mode regression test.This PR is stacked on top of
hp/typing-t4-2-ndarray-subscript(which itself stackshp/typing-t4-1-eval-str); both are merged in.Changes
cook_dtypeboundary workpython/quadrants/lang/impl.py—PyQuadrants.default_fp/default_ip/default_upconverted from raw attributes into properties with auto-cooking setters; backingfields (
_default_fpetc.) are pre-cooked in__init__.expr_init_shared_arraynow cookselement_typebefore passing to the AST builder.python/quadrants/lang/expr.py—make_constant_exprcooksdtypeonce up-front and cooks thedefault_fp/default_ip/u1fallbacks. Adds an inline commentexplaining the strategy (cook normalised input once, then cook only the runtime defaults in each branch).
python/quadrants/lang/matrix.py—make_matrixempty-vector path now usescook_dtype(primitive_types.i32)instead of the raw Python type.python/quadrants/linalg/sparse_matrix.py,sparse_solver.py—SparseMatrix,SparseMatrixBuilder, andSparseSolverconstructors cookdtypeonce and passthe cooked value to the C++ factories.
Bug fix
python/quadrants/lang/impl.py— Increate_field_member's debug-mode branch, the local variable holding the checkbit dtype was nameddtype, shadowing the functionparameter. Renamed to
checkbit_dtype. Added documentation comment explaining the rename.Tests
tests/python/test_ad_basics_fwd.py—test_dual_field_dtype_preserved_in_debug_mode: regression test for the checkbit shadowing bug, gated ondata64since it usesqd.f64.Merged-in work
hp/typing-t4-1-eval-str(get_func_signaturehelper,eval_str=Truesupport for stringified annotations) andhp/typing-t4-2-ndarray-subscript(
NDArray[i32]single-arg subscript fix) come along via merge.Good points
dtypeshadowing increate_field_memberwas silently mutating the wrong variable — caller-visible only in subtle debug-mode AD scenarios, which isexactly why it had gone unnoticed. Now caught by an explicit regression test.
cook_dtypecalls being scattered across deep call paths makes it hard to tell where Python-side dtype objects can vs. can'tappear. Cooking at the C++ boundary means downstream code (and types — see the
DataTypeCxxannotations on the new properties) can assume cooked types from then on.default_fp/default_ip/default_upto properties preserves all existing reads and assignments(
get_runtime().default_fp,qd.init(default_fp=qd.f64), etc.) — the cooking happens transparently.cook_dtypecalls. A follow-up commit (215544f15) removes redundant cooks inmake_constant_exprnow that the up-front normalisation handlesthem.
Bad / weak points (worth reviewer attention)
default_fp/ip/upare documented attributes. Code that doesruntime.default_fp = some_already_cooked_valueisfine (cooking is idempotent), but anything that introspects via
vars(runtime)orruntime.__dict__["default_fp"]will now get nothing / find_default_fpinstead. Risk islow but not zero.
DataTypeCxx, notAny. The new properties annotate the return type asDataTypeCxxbut the setter acceptsAny. That's correct for thecook-on-write pattern, but the asymmetric annotation may confuse type checkers downstream.
receiving raw Python dtypes. This PR doesn't claim to be exhaustive, but reviewers should weigh whether to expand or leave a TODO.
Test gating.
[fixed]test_dual_field_dtype_preserved_in_debug_moderequiresqd.extension.data64(added after a CI failure on backends without f64 support — see commitdcc12164f). This means the regression won't be exercised on f32-only backends; if the bug ever recurs in a different shape there, the test won't catch it.mainshows the union of t4-1 + t4-2 + t4-3 changes (~150 lines). Reviewers should look at this branch after t4-2 lands, or usegit diff hp/typing-t4-2-ndarray-subscript...HEADto see only the t4-3-specific delta.copilot:summary
Walkthrough
copilot:walkthrough