WIP: Cython pure Python#2135
Draft
speth wants to merge 59 commits into
Draft
Conversation
Convert jacobians.pyx to annotated jacobians.py (pure-Python Cython syntax) and fold jacobians.pyi's public type information inline. The companion jacobians.pxd is unchanged (augmenting-.pxd pattern). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Compile the pure-Python-syntax jacobians.py into its own shared object shipped next to its source, instead of folding it into _cantera. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Argument annotations are real C types in pure-Python Cython mode, so the setter must match the C++ signature setIlutFillFactor(int) rather than the looser 'float' the old stub used. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The standalone jacobians extension references pyCanteraError, a shared symbol defined in the merged _cantera extension and used by the C++ exception-translation layer. Load _cantera with RTLD_GLOBAL (POSIX) so such symbols resolve across extension boundaries, then restore the previous dlopen flags. Add a smoke script for the jacobians POC. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Demonstrates that with jacobians.pyi merged into the shipped jacobians.py, mypy resolves the public types (threshold -> float, side -> Literal) for downstream code with no separate stub. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…a#241) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Convert the scikit-build-core / CMake sdist build to the same standalone-extension architecture the SCons build already uses, so that the Cython pure-Python-syntax modules (enhancement Cantera#241) can ship their .py source beside the compiled extension. - Build cantera_lib as a SHARED library instead of STATIC. On Windows, where the public API isn't annotated with dllexport/dllimport, WINDOWS_EXPORT_ALL_SYMBOLS generates the export table (mirroring the filtered .def the SCons build produces). Link Python::Module so pythonShim.cpp's Python C API references resolve at link time on Windows. Install the library beside the extensions in the package. - Build each pure-Python Cython module (currently just jacobians) as its own extension linked against the shared libcantera, rather than merging it into _cantera. Both _cantera and the pure modules get an $ORIGIN/@loader_path RPATH so they find the shared library at runtime on Linux/macOS; on Windows the loader searches the directory containing the .pyd. - Stop excluding jacobians.py from the wheel: with a standalone jacobians extension present, CPython's import system prefers the extension while the .py serves tracebacks, IDEs, and type checkers. The deps fetched via FetchContent stay static (BUILD_SHARED_LIBS remains OFF); only cantera_lib becomes shared. Verified on Windows: the wheel ships cantera_lib.dll, jacobians.pyd, and jacobians.py; both extensions depend on cantera_lib.dll; jacobians resolves to the extension; and the full Python test suite passes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a `wheel` environment that layers the scikit-build-core / CMake toolchain (cmake, ninja, scikit-build-core, build) onto the `core` dependencies, so the sdist wheel route can be built and tested natively on the platforms (Windows, macOS) that the Linux dev box can't exercise. On Windows it must be run from a shell where the MSVC toolchain is activated (e.g. via vcvars64.bat), since cmake's Ninja generator needs cl/link on PATH. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The native scikit-build-core/CMake wheel builds libcantera as a SHARED library and each Cython pure-Python-syntax module (enhancement Cantera#241) as its own standalone extension that links it, so the .py source can ship beside the compiled .so. That layout does not work on Pyodide: each extension and libcantera become emscripten side modules, and emscripten cannot resolve C++ vague-linkage data symbols (RTTI typeinfo and vtables, which exception handling relies on) across the side-module boundary -- _cantera fails to load with "bad export type for 'typeinfo for Cantera::CanteraError'" even when libcantera defines and exports it and is loaded globally first. It fails on the exception base class, so it is not a single-symbol workaround, and it would only get worse as more modules are split out. Gate the layout on CANTERA_PYODIDE: on Pyodide, build a STATIC libcantera merged into a single _cantera extension (cantera.<module> resolved by the CythonPackageMetaPathFinder, as before), and exclude the raw .py sources from the wheel so they don't shadow the merged modules. Native platforms keep the standalone-extension + shared-libcantera layout. A developer note records the emscripten limitation so this can be revisited if its dynamic linking of data symbols improves. While here, link libcantera's third-party dependencies PRIVATE on the shared build and propagate only their compile usage requirements via $<COMPILE_ONLY>, so the extensions link just libcantera instead of each carrying a duplicate copy of the static archives (e.g. the whole SUNDIALS stack). This mirrors the SCons build and matters as more modules are split out. fmt stays PUBLIC because Cantera's public headers inline fmt calls. Add `make` to the pyodide Pixi feature, needed to initialize the emscripten cross-build environment on machines without a system make. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #2135 +/- ##
==========================================
+ Coverage 77.81% 78.18% +0.36%
==========================================
Files 452 453 +1
Lines 53698 54810 +1112
Branches 8973 8982 +9
==========================================
+ Hits 41787 42854 +1067
- Misses 8886 8928 +42
- Partials 3025 3028 +3 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Convert _utils.pyx to annotated _utils.py (pure-Python Cython syntax), keeping the companion _utils.pxd (augmenting-.pxd pattern). The .pyi stub is retained for now; merging it inline is deferred to a separate pass. Pure-Python Cython syntax cannot spell a C++ reference parameter, so the two cross-module helpers that took a reference (anymap_to_py, anyvalue_to_python) now take their argument by value in _utils.pxd. To keep this free of extra copies, the by-value entry points are thin wrappers that take the address of their argument and delegate to pointer-based workers (_anymap_to_py, _anyvalue_to_py) that recurse without copying nested children -- so deeply nested AnyMaps (e.g. a full mechanism) are converted with the same copy behavior as the original reference-based code. By value at the boundary is otherwise harmless: rvalue arguments (the common parameters()/header() temporaries) are copy-elided, and no caller relies on the previous in-place applyUnits() side effect. The private out-parameter helper setQuantity is likewise refactored into _make_quantity, which returns the AnyValue by value for the caller to assign, avoiding a reference parameter entirely. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Move _utils from the merged _cantera extension into the pure_py_modules list so it builds as its own shared object alongside its .py source. The GIT_COMMIT define (consumed by utils_utils.h, previously applied to _utils.pyx in the merged-extension loop) is carried into the pure-Python build path for _utils. The now-dead _utils.pyx special case is removed from the merged-extension loop. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Convert constants.pyx to constants.py using Cython pure-Python (annotation) syntax and build it as a standalone extension. The module only defines module-level physical constants sourced from the C++ extern doubles declared in the augmenting constants.pxd, so the explicit `from .constants cimport *` is dropped (those names are already in scope via the augmenting .pxd). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Convert units.pyx to units.py using Cython pure-Python (annotation) syntax and build it as a standalone extension. The static helper `UnitStack.copy` took a `const CxxUnitStack&` argument, which has no pure-Python Cython spelling. Since copy() always constructs a fresh CxxUnitStack from its argument, the parameter is changed to by-value in units.pxd; rvalue callers get guaranteed copy elision and the single cross-module consumer (delegator) recompiles against the updated signature with no source change. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Convert func1.pyx to func1.py using Cython pure-Python (annotation) syntax and build it as a standalone extension. This is the first converted module with a C callback passed to C++ as a function pointer (func_callback -> CxxFunc1Py). The pure-Python syntax handles it without issue: the module-level `cdef ... except? 0.0` callback becomes a `@cython.cfunc` with `@cython.exceptval(0.0, check=True)`, `void*`/`void**` parameters become `cython.p_void`/ `cython.pp_void`, and referencing the cfunc by name still coerces to the expected `callback_wrapper` function pointer. Exception propagation through the callback is verified. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Convert reactionpath.pyx to reactionpath.py using Cython pure-Python (annotation) syntax and build it as a standalone extension. The many `property` blocks become `@property`/`@x.setter` pairs; typed setter parameters keep their C types via the `cython.` prefix (`cython.double`, `cython.int`) to preserve coercion behavior under annotation_typing. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Convert yamlwriter.pyx to yamlwriter.py using Cython pure-Python (annotation) syntax and build it as a standalone extension. The write-only `property` blocks become an `@property` getter that raises AttributeError plus an `@x.setter`. The C++ reference dereference `deref(ptr)` is rewritten as pointer indexing `ptr[0]`, since the `cython.operator` dereference operator is not reliably reachable from a pure-Python `.py` body. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Convert speciesthermo.pyx to speciesthermo.py using Cython pure-Python (annotation) syntax and build it as a standalone extension. This is the first converted module using typed numpy buffers. The `cdef np.ndarray[np.double_t, ndim=1]` buffer plus `span[double](&data[0], ...)` pattern becomes: keep the numpy array as a plain object (so it is still returned as an ndarray), bind a typed memoryview `cython.double[::1]` over it, and build the span with `cython.address(view[0])`. Module-level `cdef int` constants become `cython.declare(cython.int, ...)` so they stay C ints and do not leak into the package namespace. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Convert mixture.pyx to mixture.py using Cython pure-Python (annotation) syntax and build it as a standalone extension. Uses the typed-numpy- buffer pattern for the span[double] calls (setMoles, getChemPotentials). The `isinstance(p, ThermoPhase)` check keeps working by cimporting the still-monolithic ThermoPhase via `from cython.cimports.cantera.thermo import ThermoPhase`, which binds as a usable runtime type. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Convert transport.pyx to pure-Python (annotation) syntax built as a standalone extension, following enhancement Cantera#241. This is the first converted module that inherits a cdef class (_SolutionBase) defined in another extension, and the first to require numpy's C-API: reading the inherited, ndarray-typed _selected_species attribute compiles to PyArray_SIZE, which needs import_array() run in this translation unit. Cython injects that call only when the module cimports numpy, so keep `import cython.cimports.numpy as cnp` even though the buffers themselves are now typed memoryviews. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Convert solutionbase.pyx to pure-Python (annotation) syntax built as a standalone extension, following enhancement Cantera#241. This is the first standalone module to participate in a cross-extension cyclic cimport: it defines _SolutionBase (cimported by thermo, kinetics, transport, and reaction) while itself cimporting those derived classes for isinstance checks. The cycle resolves at import time without special handling. As with transport, the inherited ndarray-typed _selected_species attribute requires numpy's C-API, so the numpy cimport is retained to trigger import_array(). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Convert kinetics.pyx to pure-Python (annotation) syntax built as a standalone extension, following enhancement Cantera#241. The free function get_from_sparse, which is cimported by the already-converted jacobians module, took a CxxSparseMatrix reference argument. Since pure-Python annotation syntax has no spelling for a C++ reference parameter, its declaration in kinetics.pxd is changed to pass the matrix by value; all callers pass rvalues returned by the C++ core, so this is copy-elided. As with the other phase modules, the numpy cimport is retained so that import_array() runs for the C-API access via the inherited _selected_species attribute. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Convert reaction.pyx to pure-Python (annotation) syntax built as a standalone extension, following enhancement Cantera#241. reaction.pxd is unchanged. The numpy cimport is retained so import_array() runs for the typed-ndarray buffers and the ndarray rate arguments. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Convert thermo.pyx to pure-Python (annotation) syntax built as a standalone extension, following enhancement Cantera#241. thermo.pxd is unchanged. The numpy cimport is retained so import_array() runs for the typed-ndarray state-vector buffers and the inherited _selected_species access. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Convert reactor.pyx to pure-Python (annotation) syntax built as a standalone extension, following enhancement Cantera#241. reactor.pxd is unchanged. The numpy cimport is retained so import_array() runs for the typed-ndarray state/sensitivity buffers. The ExtensibleReactor classes wrap a C++ span as a non-owning memoryview (the .pyx `<double[:n]> ptr` sized pointer cast); pure-Python annotation syntax has no equivalent, so this uses the cython.view.array idiom with allocate_buffer=False and a statically typed array whose data pointer is assigned directly. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Convert _onedim.pyx to pure-Python (annotation) syntax built as a standalone extension, following enhancement Cantera#241. _onedim.pxd is unchanged. The numpy cimport is retained so import_array() runs for the typed-ndarray grid/profile buffers, and the Domain1D.grid getter wraps the C++ span via the cython.view.array idiom. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Second module of the .pyi stub-merge pass (enhancement Cantera#241): fold the public type annotations from constants.pyi inline into constants.py and delete the stub, so the annotated .py is the single source of the published type surface. Each module-level constant gains an inline ``: float`` annotation matching the former stub. In Cython pure-Python mode this annotation does not turn the global into a C-only variable: the values remain ordinary Python module attributes (verified that cantera.avogadro and cantera.constants.avogadro still resolve and are of type float). Because the assignments reference the C++ ``Cxx*`` names injected by constants.pxd (undefined to a plain Python parse), cantera.constants is added to mypy's ignore_errors list, matching cantera.func1 and the other pure-Python modules. No stubtest allowlist entries are needed (constants exposes no C-level helpers). Verified: mypy clean, stubtest 0 findings, pyright --verifytypes unchanged (all constants symbols known), namespace-cleanliness and constants tests pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UPNco77PDEgwTxvtaaCHt6
Third module of the .pyi stub-merge pass (enhancement Cantera#241), and the first to establish two patterns the remaining modules need. (1) `new`→`make_shared`: `__cinit__` used the C++ `new` operator (`self._writer.reset(new CxxYamlWriter())`), which is invalid Python syntax and so blocks dropping the stub (mypy/stubtest fall back to a strict Python parse of the .py). Replaced with `self._writer = make_shared[CxxYamlWriter]()`, which is valid Python *and* Cython. No C++ factory is needed here since the return type is not polymorphic (per Ray); `make_shared` is cimported from `libcpp.memory`. As a cimported name it is absent at runtime, so the stubtest allowlist's general cimported-helper pattern gains `make_shared`. (2) Sibling cdef classes in public annotations (`_SolutionBase`, `UnitSystem`): these must be resolvable by mypy/pyright (which cannot see `cython.cimports` or the .pxd), so they are imported as ordinary Python imports; the companion .pxd cimports the same names so Cython still treats them as C-level extension types (needed for e.g. `soln.base`). `UnitSystem` is aliased to `_UnitSystem` so it is not re-exported by `from .yamlwriter import *` (test_namespace_cleanliness); `_SolutionBase` already starts with an underscore. The `output_units` setter accepts a UnitSystem *or* a units map, so its annotation uses the Python-imported `_UnitSystem` (param stays a plain object — no C typing) which keeps the body's `isinstance`/conversion branch working for the dict path (verified at runtime). cantera.yamlwriter is added to mypy's ignore_errors list and the `@cython.cfunc @staticmethod` helper `_get_unitsystem` (absent at runtime) to the stubtest allowlist. Verified: mypy clean, stubtest 0 findings, pyright --verifytypes unchanged (96.7%; cantera.YamlWriter fully known), runtime smoke covers both output_units paths and the make_shared __cinit__, and 58 yaml/output_units/namespace tests pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UPNco77PDEgwTxvtaaCHt6
Fourth module of the .pyi stub-merge pass (enhancement Cantera#241). reactionpath already used a factory (`newReactionPathDiagram`) so it needed no `new`/make_shared change; it has no internal `@cython.cfunc` helpers, so no stubtest allowlist entries. Two points of note: - Published constructor needs a typed `__init__`: the work is done in `__cinit__`, which mypy/pyright do not recognize as the constructor (a consumer doing `ReactionPathDiagram(gas, "H")` was reported as "Expected 0 positional arguments" after dropping the stub). Added a typed `__init__` carrying the published signature (the C++ diagram is still built in `__cinit__`), mirroring func1's structure. The former stub's parameter name `contents` did not match the runtime `__cinit__` parameter `phase`; the merged signature uses the runtime-truthful `phase`. - `_SolutionBase` (constructor annotation) is imported as an ordinary Python import for the checkers and is available to Cython as a C-level type via the .pxd's `from .kinetics cimport *` chain; it already starts with an underscore so it is not re-exported by `from .reactionpath import *`. Basic-type annotations use the Python spellings (`bool`, `float`, `str`, `int`) and the flow_type Literal is live, all matching the former stub. cantera.reactionpath is added to mypy's ignore_errors list. Verified: mypy clean, stubtest 0 findings, and a consumer check confirms the full surface is preserved -- `ReactionPathDiagram(gas, "H")` type-checks, a wrong first arg is rejected (`phase` must be `_SolutionBase`), `d.flow_type` is `Literal['NetFlow', 'OneWayFlow']`, `d.build` is `(verbose: bool = False) -> None`. pyright completeness is 96.6% (was 96.7): no new unknown -- the 79 unknowns remain entirely in cantera.with_units; the rounding shift is purely the known-symbol denominator shrinking because the stub listed members at module level while the .py nests them under the class (a documented counting artifact, not a surface loss). 6 reactionpath/namespace tests pass; runtime smoke builds a diagram. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UPNco77PDEgwTxvtaaCHt6
Fifth module of the .pyi stub-merge pass (enhancement Cantera#241). - Constructor: SpeciesThermo builds itself in __cinit__, so a typed __init__ is added to publish the constructor signature (T_low/T_high/P_ref/coeffs/init), as with reactionpath; the real work stays in __cinit__ and the parameters remain documented in the class docstring. - The _SpeciesThermoInput TypedDict (return type of input_data) is folded inline, and the base class gains the `derived_type: ClassVar[int]` annotation; the subclasses already assign it at runtime. - Published annotations use Python spellings (float, int, bool, dict[str, Any]) and the numpy aliases _Array/_ArrayLike from ._types. - Allowlist additions for names compiled away at runtime: the module-level C ints `SPECIES_THERMO_*` (cython.declare), the `@cython.cfunc` helpers SpeciesThermo._assign and wrapSpeciesThermo (the latter is cimported by thermo.py and unaffected), plus the cimported _utils helpers anymap_to_py/py_to_anymap added to the general cimported-helper allowlist pattern. cantera.speciesthermo is added to mypy's ignore_errors list. Verified: mypy clean, stubtest 0 findings, pyright --verifytypes has no new unknown (79 unknowns all remain in cantera.with_units; score 96.6% reflects the documented member-count denominator artifact, not a surface loss). A consumer check confirms the surface: `NasaPoly2(...)` type-checks, a wrong T_low is rejected, input_data is `_SpeciesThermoInput`, cp is `(T: float) -> float`. 22 speciesthermo/namespace tests pass and a runtime smoke covers construction, properties, input_data, and a coeffs roundtrip. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UPNco77PDEgwTxvtaaCHt6
Sixth module of the .pyi stub-merge pass (enhancement Cantera#241), and the first to carry `@overload` method signatures. - Overloads: UnitSystem.convert_to / convert_activation_energy_to / convert_rate_coeff_to each get their three `@overload` stubs prepended to the existing implementation. Cython compiles the stubs (the final undecorated `def` is the runtime method) and stubtest accepts them; a consumer confirms the published return types narrow correctly (`convert_to("3 cm", "m")` -> float, `convert_to([...], "m")` -> list[float], `convert_rate_coeff_to(..., UnitStack())` -> float). The implementation bodies and their existing arg annotations are left unchanged so runtime behavior is identical (this faithfully preserves the prior surface, including the stub's `dest: str | Units` on convert_activation_energy_to even though the body only handles a string -- a pre-existing discrepancy, not something to "fix" in a mechanical merge). - `new CxxUnitSystem()` -> `make_shared[CxxUnitSystem]()`. - Typed `__init__` added for Units and UnitSystem (their constructors take args); UnitStack needs none (no-arg constructor, default is correct). - The `_UnitDict` TypedDict is folded inline; `_Array` comes from ._types. - Allowlist additions for compiled-away names: the `@cython.cfunc` staticmethods Units.copy / UnitStack.copy and the cfunc UnitSystem._set_unitSystem, plus the cimported _utils helper python_to_anyvalue added to the general allowlist pattern. cantera.units is added to mypy's ignore_errors list. Verified: mypy clean, stubtest 0 findings, pyright --verifytypes no new unknown (79 unknowns all remain in cantera.with_units). 610 units/convert/namespace tests pass and a runtime smoke covers the scalar/list/array convert_to paths, Units, and UnitStack. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UPNco77PDEgwTxvtaaCHt6
Seventh module of the .pyi stub-merge pass (enhancement Cantera#241). - Ownership: the raw `new CxxMultiPhase()` (with a manual `del` in __dealloc__) is replaced by the codebase-standard pattern -- a `shared_ptr[CxxMultiPhase] _mix` owner constructed with `make_shared`, plus the existing raw `mix` pointer as a non-owning accessor (`self.mix = self._mix.get()`). __dealloc__ is removed; the shared_ptr member is destroyed automatically, so memory management is equivalent. Verified at runtime through construction, equilibration, and `del` with no crash. - phase_moles gets its two `@overload` stubs (p -> float; no-arg -> list[float]). - ThermoPhase (used throughout the annotations and in the phase_index isinstance check) switches from a `cython.cimports` cimport to an ordinary Python import under the `_ThermoPhase` alias: mixture needs no C-level access to it, so the single Python import both satisfies the checkers and serves the isinstance check, and avoids a phantom `mixture.ThermoPhase` that the cimport made stubtest flag. - Annotations use Python spellings and the ._types aliases (_Array/_ArrayLike/ _EquilibriumSolver/_LogLevel/_PropertyPair). element_index keeps `-> cython.int` to match its cpdef declaration; pyright resolves it to `int`. No new stubtest allowlist entries are needed (no @cython.cfunc helpers). cantera.mixture is added to mypy's ignore_errors list. Verified: mypy clean, stubtest 0 findings, pyright --verifytypes no new unknown (79 unknowns all in cantera.with_units). A consumer confirms the surface (phase_moles overloads, phase -> ThermoPhase, element_index -> int, equilibrate rejects a non-PropertyPair literal). 197 mixture/multiphase/namespace tests pass and a runtime smoke covers the full lifecycle plus deletion. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UPNco77PDEgwTxvtaaCHt6
Fold the published annotations from transport.pyi inline into transport.py and delete the stub, so the .py is the single source of the published type surface. Transport is the first merged Cython class that is a *base class* of pure-Python classes (Solution, DustyGas, _WaterWithTransport), which required more than a plain annotation merge to keep the type checkers correct: - Convert C++ `new CxxGasTransportData(...)` to `make_shared[...]`, so the .py parses as valid Python (mypy/stubtest parse the .py once the stub is gone). - Import `_SolutionBase` via a plain `from .solutionbase import ...` instead of `cython.cimports`, so mypy/pyright resolve Transport's base to the solutionbase stub. The C-level base still comes from transport.pxd, unchanged. - Give Transport.__init__ / DustyGasTransport.__init__ the explicit published constructor signature (forwarding to super()), so the precise _SolutionBase constructor is not shadowed in the MRO by an untyped *args/**kwargs forwarder. Runtime forwarding and the standalone-instantiation guard are unchanged. - Add cantera.transport to the mypy opaque list and add the @cython.cfunc helpers plus three compile-time-only / stubtest-quirk names to the stubtest allowlist. mypy and stubtest pass; the full runtime suite passes; consumer-side typing (reveal_type) of Transport/Solution is correct. pyright --verifytypes (a non-gating coverage report) drops because it cannot resolve a @cython.cclass base class for completeness purposes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UPNco77PDEgwTxvtaaCHt6
Fold the published annotations from kinetics.pyi inline into kinetics.py and delete the stub. Like transport, Kinetics is a base class of pure-Python classes (Solution, Interface, DustyGas), so this follows the same pattern: - Import _SolutionBase via a plain `from .solutionbase import ...` (the C-level base still comes from kinetics.pxd) so the checkers resolve the base class. - Give Kinetics.__init__ and InterfaceKinetics.__init__ the explicit published constructor signature, forwarding to super(); the standalone-instantiation guard and InterfaceKinetics phase-index setup are unchanged. - Annotation-only sibling types (Reaction, CustomRate, ThermoPhase, UnitSystem, ...) are imported under TYPE_CHECKING; the two methods that C-access a Reaction param (add_reaction, modify_reaction) take the alias and cast in the body. - reaction(): route the index through a `cython.int` local so a negative index wraps to size_t and reaches the C++ range check (matching prior behavior) now that the published param type is `int` rather than `cython.int`. - Add cantera.kinetics to the mypy opaque list and the @cython.cfunc helpers plus compile-time-only cimports to the stubtest allowlist. mypy and stubtest pass; full runtime suite passes (kinetics/reaction/composite/ purefluid/onedim/reactor). kinetics's own published surface is 100% known to pyright; the non-gating --verifytypes total dips only because Interface joins the already-unknown Solution/DustyGas set (a @cython.cclass base it inherits). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UPNco77PDEgwTxvtaaCHt6
Fold the published type annotations from solutionbase.pyi inline into solutionbase.py and delete the stub, so the .py is the single source of the published type surface for `_SolutionBase` and `SolutionArrayBase`. `_SolutionBase` is the root base class of the `Solution` hierarchy (ThermoPhase/Kinetics/Transport and Solution/Interface/DustyGas), so this follows the base-class merge recipe: a typed `__init__` carrying the published constructor signature (the runtime work stays in `__cinit__`/`_cinit`), sibling cdef-class annotations under TYPE_CHECKING, and the C++ `new CxxPythonHandle(...)` rewritten as make_shared + static_pointer_cast. Component/`extra` name marshalling methods receive `numpy.str_` and the private `_cxx_save`/`_cxx_restore` interfaces receive `Path`/`None`; a bare `str` annotation makes Cython's annotation_typing reject these (it is stricter than PEP 484), so those params use a `str` alias / accurate unions to keep the published types while preserving the pre-merge runtime behavior. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UPNco77PDEgwTxvtaaCHt6
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UPNco77PDEgwTxvtaaCHt6
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UPNco77PDEgwTxvtaaCHt6
The .pyi-merge `new CxxFoo(...)` -> `make_shared[CxxFoo](...)` conversion silently dropped C++ exception translation: libcpp.memory.make_shared is declared `except +`, so a CanteraError thrown by a C++ constructor surfaced as a generic RuntimeError instead of cantera.CanteraError (e.g. constructing a Reaction with a negative pre-exponential factor). The original `new CxxFoo(...)` expressions translated because the constructors are declared `except +translate_exception` in the .pxd files. Declare a translate-aware `make_shared` in ctcxx.pxd that shadows the libcpp one, and have the converted modules pick it up via `from .ctcxx cimport *` instead of importing libcpp.memory.make_shared. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UPNco77PDEgwTxvtaaCHt6
composite is plain Python (not Cython), so it is now type-checked by mypy strict rather than added to the ignore_errors list. SolutionArray's single-property accessors are read-only properties installed at runtime by the setattr loops; the inline bare annotations publish the correct type but not read-only-ness (mypy strict rejects every compact read-only spelling), so the read-only/read-write mismatch is allowlisted for stubtest. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UPNco77PDEgwTxvtaaCHt6
_onedim is a Cython pure-Python module whose classes (Domain1D, Sim1D, and the flow/boundary hierarchy) are base classes for the plain-Python onedim module. Following the base-class merge pattern, the base _SolutionBase is imported via a plain Python import (checker-visible) while the augmenting .pxd keeps the cimport for the C type; sibling cdef-class names used only for C-attr access in bodies stay bare (resolved via the .pxd cimport), which is safe because _onedim is added to the mypy ignore_errors list. Parametrized-generic return/argument annotations (dict[str, str] for Sim1D.restore, tuple[float, float] for bounds) are routed through TypeAliases to avoid Cython 3's annotation_typing coercion, which would reject an AnyMap (dict subclass) return value or a list argument at runtime. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UPNco77PDEgwTxvtaaCHt6
onedim is plain Python (not Cython), so it is now type-checked by mypy strict rather than added to the ignore_errors list. All 69 methods across the 7 flame classes are annotated; `from __future__ import annotations` keeps annotations lazy. FlameBase's ~95 gas/kinetics property pass-throughs are read-only `property` objects installed at import time by the setattr loops, so the inline bare annotations publish the correct type but not read-only-ness; that mismatch is allowlisted for stubtest, as are the two function-local exception classes that stubtest's static parse misreports. A handful of precise `# type: ignore[code]` mark genuine discrepancies: SolutionArray.TP's setter broadcasts scalars while its published type matches the getter, and two pre-existing stub bugs (to_pandas declared -> Array but returns a DataFrame; Sim1D.solve_adjoint declared -> None but returns an ndarray) are carried forward faithfully rather than widening the published surface. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UPNco77PDEgwTxvtaaCHt6
reactor is a Cython pure-Python module, so it is added to the mypy ignore_errors list; its published surface is covered by stubtest and pyright. All 26 reactor/surface/connector/network classes are annotated inline. Sibling types used only in annotations (Solution, _Func1Like, _DerivativeSettings, graphviz.Digraph) are imported under TYPE_CHECKING and string-annotated; SystemJacobian and the cdef-class types whose bodies need C-attribute access stay resolved via the .pxd cimport (safe under ignore_errors). ReactorNet.solver_stats returns an AnyMap (a dict subclass) from anymap_to_py, so its dict[str, int] return is routed through a TypeAlias to avoid Cython's annotation_typing coercion. Two stub bugs are fixed against .pxd/runtime truth: surface_production_rates is declared on ExtensibleReactorSurface (not the ReactorSurface base, where the stub mistakenly placed it), and Reactor.group_name is annotated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UPNco77PDEgwTxvtaaCHt6
Fold the published ``extension`` signature inline and delete the stub. The ``ExtensibleRate``/``ExtensibleRateData`` references are imported under ``TYPE_CHECKING`` aliases (the runtime cimports are kept for the ``issubclass`` checks) and string-annotated to avoid an import cycle, since ``delegator`` is initialized before ``reaction``. Add ``cantera.delegator`` to the mypy opaque list and allowlist its compile-time-only cimports for stubtest. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UPNco77PDEgwTxvtaaCHt6
Delete the package stub; the inline annotations now carried by every submodule plus the ``_cantera`` aggregator source let the type checkers resolve the full ``cantera`` surface through the real import chain. Re-export the version dunders with explicit ``as`` aliases (required for mypy's no-implicit-reexport) and mark the ``del`` of the star-imported ``np``/``os`` as the names the checkers cannot track. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UPNco77PDEgwTxvtaaCHt6
``composite`` imported ``ThermoPhase``/``Kinetics``/``Transport``/``PureFluid`` and friends from the ``_cantera`` aggregator. pyright does not reliably re-export a name through ``_cantera``'s ``from .X import *`` chain, so those names resolved to ``Unknown`` and ``Solution``'s inherited ``ThermoPhase`` and ``Kinetics`` members were invisible to the type checkers / IDEs. Import them directly from ``.kinetics`` / ``.thermo`` / ``.transport`` / ``.solutionbase`` instead (as the former ``composite.pyi`` stub did, and now possible at runtime since those modules are standalone extensions). The classes are still needed at runtime as base classes, and ``composite`` is imported after every submodule, so there is no import cycle. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UPNco77PDEgwTxvtaaCHt6
1bac505 to
5e214c2
Compare
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Changes proposed in this pull request
This is a proof of concept for converting the Cython module from the
.pyxsyntax to Cython's "pure Python" mode. Specific findings:.pyfile being compiled into its own extension module (.soor.pyd). This means we now break up the monolithic_canteraextension module and remove our customization of the module loading process. Both the.so(or equivalent) and the.pyare packaged in the wheel.libcanteradata symbols used by multiple submodules, so we fall back to the current approach of building a monolithic extension module. We may be able to revisit this as the emscripten platform evolves..pyfile instead of a.pyistub is a big usability improvement. At least in the case of Pylance, this means the popup on hover actually contains the docstring for the method/property, rather than just the type info (since the.pyionly has the type info) and ctrl-clicking takes you to the actual definition rather than the entry in the.pyifile.I'm inclined to go ahead and implement the rest of this conversion. There aren't any other open PRs that make any significant changes to the Python module, so this seems like an okay time for something this invasive. Update: this is now in progress.
If applicable, fill in the issue number this pull request is fixing
Closes #
If applicable, provide an example illustrating new features this pull request is introducing
AI Statement (required)
logic and implementation decisions. All generated code and documentation were reviewed and understood by the contributor. Implemented using Claude Code (Opus 4.8).
Checklist
scons build&scons test) and unit tests address code coverage