[Python] Simplify Claude's verbose comments

This commit is contained in:
Ray Speth
2026-07-10 15:08:48 -04:00
committed by Ray Speth
parent dd5401038c
commit fd2f6b773f
22 changed files with 84 additions and 204 deletions
+4 -20
View File
@@ -162,29 +162,13 @@ private:
void* m_pyobj;
};
//! Create a `Func1Py` wrapping a Python callback and return it as a ``shared_ptr``.
//!
//! This factory exists so that the Cython layer can construct a `Func1Py` without the
//! C++ ``new`` operator. ``new`` is not valid Python syntax, and its presence would
//! prevent the pure-Python ``.py`` source from being parsed by the Python-based type
//! checkers (mypy/stubtest), which is what allows the module to ship its annotations
//! inline instead of in a separate ``.pyi`` stub.
inline std::shared_ptr<Cantera::Func1> newFunc1Py(callback_wrapper callback, void* pyobj)
{
return std::make_shared<Func1Py>(callback, pyobj);
}
//! Return the Python ``CanteraError`` class, used by translate_exception() to raise a
//! ``CanteraError`` from C++ code.
//!
//! The class object is fetched on first use from its single canonical definition in the
//! ``cantera._utils`` module and cached. Resolving it this way (rather than referencing a
//! shared C symbol) keeps each Python extension module self-contained: a shared symbol
//! previously required loading the main extension with ``RTLD_GLOBAL`` on POSIX and could
//! not be linked across DLL boundaries on Windows (enhancement #241). Because the lookup
//! is automatic, any extension that includes this header can raise ``CanteraError``
//! without registering anything.
inline PyObject* getCanteraError() {
//! ``cantera._utils`` module and cached. Resolving it this way keeps each Python
// extension module self-contained.
inline PyObject* getCanteraErrorClass() {
// Cached per extension module; CanteraError is a singleton class object, so the
// borrowed reference held here remains valid for the lifetime of the module.
static PyObject* cls = nullptr;
@@ -262,7 +246,7 @@ inline int translate_exception()
} catch (const Cantera::ArraySizeError& exn) {
PyErr_SetString(PyExc_ValueError, exn.what());
} catch (const Cantera::CanteraError& exn) {
PyErr_SetString(getCanteraError(), exn.what());
PyErr_SetString(getCanteraErrorClass(), exn.what());
} catch (const std::exception& exn) {
PyErr_SetString(PyExc_RuntimeError, exn.what());
} catch (...) {
+12 -10
View File
@@ -80,10 +80,10 @@ for pyxfile in multi_glob(localenv, "cantera", "pyx"):
f"#build/temp-py/{pyxfile.name.split('.')[0]}", cythonized[0])
cython_obj.append(obj)
# The aggregator module _cantera uses Cython pure-Python (.py) syntax (enhancement #241),
# but unlike the standalone pure_py_modules below it is cythonized and compiled INTO the
# merged _cantera extension here: it hosts the CythonPackageMetaPathFinder and provides
# the PyInit functions for the remaining .pyx modules merged into this extension. Its .py
# The aggregator module _cantera uses Cython pure-Python (.py) syntax, but unlike the
# standalone pure_py_modules below it is cythonized and compiled INTO the merged
# _cantera extension here: it hosts the CythonPackageMetaPathFinder and provides the
# PyInit functions for the remaining .pyx modules merged into this extension. Its .py
# source ships alongside the extension to serve tracebacks, IDEs, and type checkers.
_cantera_cpp = localenv.Command(
"cantera/_cantera.cpp", "cantera/_cantera.py",
@@ -106,12 +106,14 @@ ext = localenv.LoadableModule(f"cantera/_cantera{module_ext}",
cython_obj, LIBPREFIX="", SHLIBSUFFIX=module_ext,
SHLIBPREFIX="", LIBSUFFIXES=[module_ext])
# Cython pure-Python-syntax modules built as their own extensions (PoC, enhancement
# #241). Each ships its .py source alongside the compiled extension: the extension wins
# CPython import precedence, while the .py serves tracebacks, IDEs, and type checkers.
pure_py_modules = ["_utils", "jacobians", "constants", "units", "func1", "reactionpath",
"yamlwriter", "speciesthermo", "mixture", "transport", "solutionbase",
"kinetics", "reaction", "thermo", "reactor", "_onedim", "delegator"]
# Cython pure-Python-syntax modules built as their own extensions. Each ships its .py
# source alongside the compiled extension: the extension wins CPython import precedence,
# while the .py serves tracebacks, IDEs, and type checkers.
pure_py_modules = [
"constants", "delegator", "func1", "jacobians", "kinetics", "mixture", "_onedim",
"reaction", "reactionpath", "reactor", "solutionbase", "speciesthermo", "thermo",
"transport", "units", "_utils", "yamlwriter"
]
pure_py_ext = []
for _name in pure_py_modules:
if _name == "_utils":
+2 -10
View File
@@ -41,15 +41,12 @@ if TYPE_CHECKING:
# Parametrized generics (tuple[...]) are coerced by Cython's annotation_typing just
# like bare builtins, rejecting a list where a tuple was published; route through a
# TypeAlias (not coerced) to keep the runtime accepting any sequence, as before
# (thermo.py's _TPQSetter precedent).
# TypeAlias (not coerced) to keep the runtime accepting any sequence
_BoundsPair: _TypeAlias = tuple[float, float]
# `anymap_to_py` may return an `AnyMap` (a `dict` subclass) rather than a plain
# `dict`; an inline `dict[str, str]` return annotation is coerced by Cython 3 and
# rejects that subclass instance at runtime (regression caught by
# test_onedim.py's save/restore tests). Route through a TypeAlias (not coerced),
# matching the `_BoundsPair` precedent above.
# rejects that subclass instance at runtime. Route through a TypeAlias (not coerced).
_RestoreMetadata: _TypeAlias = dict[str, str]
_ToleranceSettings = _TypedDict(
@@ -242,8 +239,6 @@ class Domain1D:
def grid(self) -> _Array:
"""The grid for this domain."""
grid_span: span[const_double] = self.domain.grid()
# Non-owning memoryview over the C++ span data (pure-Python spelling of the
# .pyx `<double[:grid_span.size()]> grid_span.data()` sized pointer cast).
garr: view.array = view.array(shape=(grid_span.size(),),
itemsize=cython.sizeof(cython.double), format="d",
allocate_buffer=False)
@@ -376,9 +371,6 @@ class Domain1D:
"""
self.domain.setFlatProfile(stringify(component), value)
# Parametrized generics (tuple[...]) are coerced by Cython's annotation_typing
# just like bare builtins; route through a TypeAlias to avoid rejecting e.g. a
# list where a tuple was published (see thermo.py's _TPQSetter precedent).
def set_bounds(
self,
*,
-3
View File
@@ -114,9 +114,6 @@ cdef comp_map_to_dict(Composition m)
cdef Composition comp_map(X) except *
cdef CxxAnyMap py_to_anymap(data, cbool hyphenize=*) except *
# Argument passed by value rather than by reference: pure-Python Cython syntax (used in
# the paired _utils.py) cannot spell a C++ reference parameter, and these helpers only
# read or locally mutate a copy of the argument, so by-value is behavior-preserving.
cdef anymap_to_py(CxxAnyMap m)
cdef CxxAnyValue python_to_anyvalue(item, name=*) except *
+6 -7
View File
@@ -254,13 +254,12 @@ class AnyMap(dict):
self[key] = _DimensionalValue(value, src, True)
# anyvalue_to_python / anymap_to_py take their argument by value so that the cross-module
# cimport signature is expressible in pure-Python syntax (which has no C++ reference
# spelling). To avoid deep-copying nested children at every level of the recursion, the
# by-value entry points immediately take the address of their (now local) argument and
# delegate to the pointer-based workers below, which descend without further copies --
# matching the original reference-based .pyx (each vector element is still copied once into
# its loop variable, exactly as before).
# anyvalue_to_python / anymap_to_py take their argument by value so that the
# cross-module cimport signature is expressible in pure-Python syntax (which as of
# Cython 3.2.5 has no C++ reference spelling). To avoid deep-copying nested children at
# every level of the recursion, the by-value entry points immediately take the address
# of their (now local) argument and delegate to the pointer-based workers below, which
# descend without further copies.
@cython.cfunc
def anyvalue_to_python(name: string, v: CxxAnyValue):
+5 -33
View File
@@ -481,8 +481,7 @@ class Quantity:
# Synonyms for total properties. The literal properties below are type-only
# declarations: the assignments that follow the class body overwrite them at
# runtime with the *bound* property objects (verified equivalent; see also the
# dynamically-added pass-through properties below, which use the same pattern).
# runtime with the *bound* property objects.
@property
def V(self) -> float: # type: ignore[empty-body]
...
@@ -504,19 +503,7 @@ class Quantity:
...
# Dynamically-added properties/methods acting as pass-throughs to Solution
# class (assigned via the ``setattr`` loop following this class). That loop
# only assigns an attribute if it is not already present in
# ``Quantity.__dict__``, so it is NOT safe to declare these as literal
# `@property`/`def` members here (doing so puts the name in ``__dict__`` and
# silently disables the loop's installation of the real implementation --
# this was tried and reverted; see git history). A *bare* (unassigned) class
# variable annotation, however, only populates ``__annotations__`` and not
# ``__dict__``, so it is invisible to the loop's guard while still telling
# type checkers (and `stubtest`) the published type of each dynamic
# attribute. That is the technique used below. Unlike `SolutionArray` (whose
# equivalent loop unconditionally overwrites and so can use real
# `@property`/`def` declarations instead), `Quantity` requires this
# annotation-only form.
# class (assigned via the ``setattr`` loop following this class)
name: str
source: str
composite: tuple[_ThermoType | None, _KineticsType | None, _TransportModel | None]
@@ -758,8 +745,7 @@ class Quantity:
set_electron_energy_distribution_parameters: _Callable[..., None]
# Synonyms for total properties. These class-level re-assignments overwrite the
# type-only `...`-body declarations above with the *bound* property objects
# (verified equivalent at runtime; see the comment above those declarations).
# type-only `...`-body declarations above with the *bound* property objects.
Quantity.V = Quantity.volume # type: ignore[method-assign]
Quantity.U = Quantity.int_energy # type: ignore[method-assign]
Quantity.H = Quantity.enthalpy # type: ignore[method-assign]
@@ -1008,10 +994,7 @@ class SolutionArray(SolutionArrayBase, _Generic[_P]):
# Dynamically-added properties/methods acting as pass-throughs to the
# underlying phase (assigned via the ``setattr`` loops/`_make_functions()`
# following this class). Unlike `Quantity`'s equivalent loop (which only
# installs an attribute if not already present in `Quantity.__dict__`),
# these loops unconditionally overwrite, so real class-level annotations
# here are safe and do not shadow the runtime implementations.
# following this class).
TD: tuple[_Array, _Array]
TDX: tuple[_Array, _Array, _Array]
TDY: tuple[_Array, _Array, _Array]
@@ -1502,11 +1485,7 @@ class SolutionArray(SolutionArrayBase, _Generic[_P]):
""" See `ThermoPhase.equilibrate` """
for loc in range(self.size):
self._set_loc(loc)
# `XY` is published as optional for parity with `Quantity.equilibrate`,
# but unlike that method there is no substitution of a default here;
# passing `None` fails at runtime in `ThermoPhase.equilibrate`, matching
# pre-merge (and `.pyi`-published) behavior.
self._phase.equilibrate(XY, solver, rtol, max_steps, max_iter, # type: ignore[arg-type]
self._phase.equilibrate(XY, solver, rtol, max_steps, max_iter,
estimate_equil, log_level)
self._update_state(loc)
@@ -1534,9 +1513,6 @@ class SolutionArray(SolutionArrayBase, _Generic[_P]):
"""
# check arguments
# `data`'s static type is already `dict[str, Array]`, but this runtime
# check still matters because annotations are not enforced at the
# boundary for callers passing in arbitrary objects.
if not isinstance(data, dict) or len(data) == 0: # type: ignore[redundant-expr]
raise ValueError("'SolutionArray.restore_data' requires a "
"non-empty data dictionary")
@@ -1567,10 +1543,6 @@ class SolutionArray(SolutionArrayBase, _Generic[_P]):
)
# get full state information (may differ depending on ThermoPhase type).
# `states` is treated as a plain `list[str]` from here on, since the rest
# of this function manipulates its entries with generic string operations
# (slicing, `rstrip`, membership tests) that lose the narrow `Literal`
# typing published by `_full_states`/`_partial_states`.
states: list[str] = list(self._phase._full_states.values())
# add partial and/or potentially non-unique state definitions
+2 -4
View File
@@ -8,10 +8,8 @@ import sys as _sys
import numpy as np
import warnings
# External typing names are imported under "private" (underscore-prefixed) aliases so
# that they are not re-exported into the top-level ``cantera`` namespace via ``from
# .func1 import *`` (checked by test_namespace_cleanliness), matching the convention used
# in the other Cython submodules.
# External typing names are imported under "private" aliases so that they are not
# re-exported into the top-level ``cantera`` namespace via ``from .func1 import *``
from collections.abc import Callable as _Callable, Iterable as _Iterable
from typing import Any as _Any, Literal as _Literal
from typing_extensions import Never as _Never
+2 -7
View File
@@ -4,10 +4,8 @@
# distutils: language = c++
# cython: language_level=3
# External names are imported under "private" (underscore-prefixed) aliases so that they
# are not re-exported into the top-level ``cantera`` namespace via ``from .jacobians
# import *`` (checked by test_namespace_cleanliness), matching the convention used in the
# other Cython submodules.
# External names are imported under "private" aliases so that they are not re-exported
# into the top-level ``cantera`` namespace via ``from .jacobians import *``
from typing import Any as _Any, ClassVar as _ClassVar, Literal as _Literal
import cython
@@ -166,9 +164,6 @@ class AdaptivePreconditioner(EigenSparseJacobian):
@ilut_fill_factor.setter
def ilut_fill_factor(self, val: int) -> None:
# NOTE: in pure-Python Cython, argument annotations are real C types, so this
# must match the C++ signature ``setIlutFillFactor(int)`` (the old .pyi loosely
# typed it ``float``, which is fine as a stub but a compile error as source).
cython.cast(
cython.pointer(CxxAdaptivePreconditioner), self.jac
).setIlutFillFactor(val)
+1 -1
View File
@@ -147,4 +147,4 @@ cdef class InterfaceKinetics(Kinetics):
cdef np.ndarray get_species_array(Kinetics kin, kineticsMethod1d method)
cdef np.ndarray get_reaction_array(Kinetics kin, kineticsMethod1d method)
cdef get_from_sparse(CxxSparseMatrix, int, int) # by-value for pure-Python compat (no T& in annotations)
cdef get_from_sparse(CxxSparseMatrix, int, int)
+1 -5
View File
@@ -13,10 +13,6 @@ import cython
from cython.cimports.cantera._utils import stringify, pystr
from cython.cimports.cantera.solutionbase import _SolutionBase
# ThermoPhase is imported as an ordinary Python import (under an underscore alias to keep
# it out of ``from .mixture import *``) so that its use in the public annotations is
# resolvable by mypy/pyright; the runtime class object also serves the isinstance check
# in phase_index (mixture needs no C-level access to ThermoPhase).
from .thermo import ThermoPhase as _ThermoPhase
from ._types import (
Array as _Array,
@@ -92,7 +88,7 @@ class Mixture:
) -> None:
# The C++ object is constructed in __cinit__; this typed __init__ exists so that
# mypy/pyright (which do not recognize Cython's __cinit__) publish the constructor
# signature. Construction is documented in the class docstring above.
# signature.
pass
def report(self, threshold: float = 1e-14) -> str:
+1 -1
View File
@@ -512,7 +512,7 @@ class FlameBase(Sim1D):
# strict rejects every compact read-only spelling for these properties
# (`Final[...]` needs an initializer, an empty-body `@property` trips
# `[empty-body]`), so the read-write/read-only mismatch is allowlisted in
# ``.mypyignore`` instead (mirrors `composite.SolutionArray`).
# ``.mypyignore`` instead.
density: _Array
density_mass: _Array
density_mole: _Array
+16 -22
View File
@@ -416,7 +416,7 @@ class BlowersMaselRate(ArrheniusRateBase):
input_data: _ReactionRateInput[_BlowersMaselParameters] | None = None,
init: bool = True,
) -> None:
"""Published constructor signature."""
pass # Placeholder for constructor type annotations
def _from_dict(self, input_data: _ReactionRateInput[_BlowersMaselParameters]) -> None:
self._rate = make_shared[CxxBlowersMaselRate](py_to_anymap(input_data))
@@ -502,7 +502,7 @@ class TwoTempPlasmaRate(ArrheniusRateBase):
input_data: _ReactionRateInput[_TwoTempPlasmaParameters] | None = None,
init: bool = True,
) -> None:
"""Published constructor signature."""
pass # Placeholder for constructor type annotations
def __call__(self, temperature: cython.double, elec_temp: cython.double) -> float:
"""
@@ -568,7 +568,7 @@ class ElectronCollisionPlasmaRate(ReactionRate):
input_data: _ReactionRateInput[_ElectronCollisionPlasmaParameters] | None = None,
init: bool = True,
) -> None:
"""Published constructor signature."""
pass # Placeholder for constructor type annotations
def _from_dict(
self, input_data: _ReactionRateInput[_ElectronCollisionPlasmaParameters]
@@ -665,7 +665,7 @@ class FalloffRate(ReactionRate):
input_data: _ReactionRateInput[_FalloffRateInput] | None = None,
init: bool = True,
) -> None:
"""Published constructor signature."""
pass # Placeholder for constructor type annotations
def __call__(self, temperature: cython.double, concm: cython.double) -> float:
"""
@@ -882,7 +882,7 @@ class PlogRate(ReactionRate):
input_data: _PlogRateInput | None = None,
init: bool = True,
) -> None:
"""Published constructor signature."""
pass # Placeholder for constructor type annotations
def __call__(self, temperature: cython.double, pressure: cython.double) -> float:
"""
@@ -990,7 +990,7 @@ class ChebyshevRate(ReactionRate):
input_data: _ChebyshevRateInput | None = None,
init: bool = True,
) -> None:
"""Published constructor signature."""
pass # Placeholder for constructor type annotations
def __call__(self, temperature: cython.double, pressure: cython.double) -> float:
"""
@@ -1050,10 +1050,7 @@ class ChebyshevRate(ReactionRate):
2D array of Chebyshev coefficients where rows and columns correspond to
temperature and pressure dimensions over which the Chebyshev fit is computed.
"""
# Must split declaration and assignment: 'data()' returns CxxArray2D&
# and assigning it to a typed local copies the value into a CxxArray2D
cxxcoeffs: CxxArray2D
cxxcoeffs = self.cxx_object().data()
cxxcoeffs: CxxArray2D = self.cxx_object().data()
c = np.fromiter(cxxcoeffs.data(), np.double)
return c.reshape(cxxcoeffs.nRows(), cxxcoeffs.nColumns(), order="F")
@@ -1087,7 +1084,7 @@ class CustomRate(ReactionRate):
f"Cannot convert input with type '{type(k)}' to rate expression.")
def __init__(self, k: _Func1Like | None = None, init: bool = True) -> None:
"""Published constructor signature."""
pass # Placeholder for constructor type annotations
@cython.cfunc
def cxx_object(self) -> cython.pointer(CxxCustomFunc1Rate):
@@ -1359,7 +1356,7 @@ class InterfaceArrheniusRate(InterfaceRateBase):
input_data: _ReactionRateInput[_ArrheniusParameters] | None = None,
init: bool = True,
) -> None:
"""Published constructor signature."""
pass # Placeholder for constructor type annotations
def _from_dict(self, input_data: _ReactionRateInput[_ArrheniusParameters]) -> None:
self._rate = make_shared[CxxInterfaceArrheniusRate](py_to_anymap(input_data))
@@ -1402,7 +1399,7 @@ class InterfaceBlowersMaselRate(InterfaceRateBase):
input_data: _ReactionRateInput[_BlowersMaselParameters] | None = None,
init: bool = True,
) -> None:
"""Published constructor signature."""
pass # Placeholder for constructor type annotations
def _from_dict(
self, input_data: _ReactionRateInput[_BlowersMaselParameters]
@@ -1577,7 +1574,7 @@ class StickingBlowersMaselRate(StickRateBase):
input_data: _ReactionRateInput[_BlowersMaselParameters] | None = None,
init: bool = True,
) -> None:
"""Published constructor signature."""
pass # Placeholder for constructor type annotations
def _from_dict(
self, input_data: _ReactionRateInput[_BlowersMaselParameters]
@@ -1669,7 +1666,7 @@ class ThirdBody:
default_efficiency: float | None = None,
init: bool = True,
) -> None:
"""Published constructor signature."""
pass # Placeholder for constructor type annotations
@cython.cfunc
@staticmethod
@@ -1827,20 +1824,17 @@ class Reaction:
)
else:
self._reaction = make_shared[CxxReaction](
comp_map(reactants), comp_map(products),
_rate._rate
comp_map(reactants), comp_map(products), _rate._rate
)
elif equation:
# create from reaction equation
if third_body:
self._reaction = make_shared[CxxReaction](
stringify(equation),
_rate._rate, _third_body._third_body
stringify(equation), _rate._rate, _third_body._third_body
)
else:
self._reaction = make_shared[CxxReaction](
stringify(equation),
_rate._rate
stringify(equation), _rate._rate
)
else:
# create default object
@@ -1863,7 +1857,7 @@ class Reaction:
init: bool = True,
third_body: ThirdBody | _Str | None = None,
) -> None:
"""Published constructor signature."""
pass # Placeholder for constructor type annotations
@cython.cfunc
@staticmethod
@@ -32,9 +32,6 @@ class ReactionPathDiagram:
according to the net reaction rates determined by the `Kinetics` object
``phase``.
"""
# The C++ diagram is constructed in __cinit__; this typed __init__ exists so
# that mypy/pyright (which do not recognize Cython's __cinit__) publish the
# constructor signature.
@property
def show_details(self) -> bool:
+3 -10
View File
@@ -39,10 +39,7 @@ if TYPE_CHECKING:
from .drawnetwork import *
# `anymap_to_py` may return an `AnyMap` (a `dict` subclass) rather than a plain
# `dict`; an inline `dict[str, int]` return annotation is coerced by Cython 3 and
# rejects that subclass instance at runtime. Route through a TypeAlias (not coerced),
# matching the `_onedim._RestoreMetadata` precedent.
# Non-coercing alias for AnyMap used by ReactorNet.solver_stats
_SolverStats: _TypeAlias = dict[str, int]
@@ -152,7 +149,7 @@ class ReactorBase:
y = np.zeros(self.n_vars)
cy: cython.double[::1] = y
self.rbase.getState(span[double](cython.address(cy[0]),
cython.cast(cython.size_t, y.size)))
cython.cast(cython.size_t, y.size)))
return y
def get_state_dae(self) -> tuple[_Array, _Array]:
@@ -699,8 +696,6 @@ class ExtensibleReactor(Reactor):
self.accessor = dynamic_cast[CxxReactorAccessorPtr](self.rbase)
sdot: span[cython.double] = \
dynamic_cast[CxxReactorAccessorPtr](self.rbase).surfaceProductionRates()
# Non-owning memoryview over the C++ span data (pure-Python spelling of the
# .pyx `<double[:sdot.size()]> sdot.data()` sized pointer cast).
sarr: view.array = view.array(shape=(sdot.size(),),
itemsize=cython.sizeof(cython.double), format="d",
allocate_buffer=False)
@@ -1095,8 +1090,6 @@ class ExtensibleReactorSurface(ReactorSurface):
assign_delegates(self, dynamic_cast[CxxDelegatorPtr](self.rbase))
sdot: span[cython.double] = \
dynamic_cast[CxxReactorAccessorPtr](self.rbase).surfaceProductionRates()
# Non-owning memoryview over the C++ span data (pure-Python spelling of the
# .pyx `<double[:sdot.size()]> sdot.data()` sized pointer cast).
sarr: view.array = view.array(shape=(sdot.size(),),
itemsize=cython.sizeof(cython.double), format="d",
allocate_buffer=False)
@@ -2253,7 +2246,7 @@ class ReactorNet:
y = np.zeros(self.n_vars)
cy: cython.double[::1] = y
self.net.getState(span[double](cython.address(cy[0]),
cython.cast(cython.size_t, y.size)))
cython.cast(cython.size_t, y.size)))
return y
def get_state_dae(self) -> tuple[_Array, _Array]:
+3 -8
View File
@@ -41,11 +41,8 @@ from ._types import (Array as _Array, ArrayLike as _ArrayLike, Basis as _Basis,
_CxxSurfPhasePtr = cython.typedef(cython.pointer(CxxSurfPhase))
# A ``str`` alias used for component/``extra`` names: these are published as ``str``
# but flow in as ``numpy.str_`` (a ``str`` subclass) from array operations. A bare
# ``str`` annotation would make Cython's annotation_typing reject the subclass (Cython
# is deliberately stricter than PEP 484); routing through an alias keeps the published
# type ``str`` while accepting subclasses, matching the pre-merge runtime behavior.
# Alias to prevent Cython from rejecting numpy.str_ for functions that take str
# arguments.
_Str: _TypeAlias = str
_SortingType: _TypeAlias = _Literal["alphabetical", "molar-mass"] | None
@@ -637,9 +634,7 @@ class SolutionArrayBase:
meta: "dict[str, _Any]" = {},
init: bool = True,
) -> None:
"""
Construct a `SolutionArrayBase`. The C++ object is created in ``__cinit__``.
"""
pass
def _share(self, dest: SolutionArrayBase, selected):
""" Share entries with new `SolutionArrayBase` object. """
+1 -6
View File
@@ -72,10 +72,6 @@ class SpeciesThermo:
def __init__(self, T_low: float | None = None, T_high: float | None = None,
P_ref: float | None = None, coeffs: _ArrayLike | None = None,
*args: _Any, init: bool = True, **kwargs: _Any) -> None:
# The C++ object is constructed in __cinit__; this typed __init__ exists so
# that mypy/pyright (which do not recognize Cython's __cinit__) publish the
# constructor signature. Constructor parameters are documented in the class
# docstring above.
pass
@cython.cfunc
@@ -119,8 +115,7 @@ class SpeciesThermo:
cdata: cython.double[::1] = data
view: span[cython.double] = span[cython.double](
cython.address(cdata[0]), cython.cast(cython.size_t, self.n_coeffs))
self.spthermo.reportParameters(index, thermo_type, T_low,
T_high, P_ref, view)
self.spthermo.reportParameters(index, thermo_type, T_low, T_high, P_ref, view)
return data
def _check_n_coeffs(self, n: int) -> bool:
+3 -7
View File
@@ -43,9 +43,8 @@ if TYPE_CHECKING:
from .composite import Solution as _Solution
from .reaction import Reaction as _Reaction
# TypeAlias to str is NOT coerced by Cython's annotation_typing (unlike a bare `str`
# annotation, which rejects subclasses such as numpy.str_); use this for params that
# are forwarded to `stringify()` (which itself accepts any str-like object).
# Alias to prevent Cython from rejecting numpy.str_ or other types derived from str
# for functions that should allow any string-like type
_Str: _TypeAlias = str
# Avoid fixed options unless we can find a way to support custom extensions:
@@ -90,9 +89,7 @@ _PhaseOfMatter: _TypeAlias = str
_QuadratureMethod: _TypeAlias = _Literal["simpson", "trapezoidal"]
# Parametrized generics (tuple[...]) are coerced by Cython's annotation_typing just
# like bare builtins, rejecting a list where a tuple was published; route through a
# TypeAlias (not coerced) to keep the runtime accepting any sequence, as before.
# Type alias relaxes Cython's strict checking and allows any type of sequence
_TPQSetter: _TypeAlias = tuple[float | None, float | None, float | None]
_SpeciesInput = _TypedDict(
@@ -109,7 +106,6 @@ _SpeciesInput = _TypedDict(
total=False,
)
# Module-level C int constants replacing the private `cdef enum ThermoBasisType`
mass_basis = cython.declare(cython.int, 0)
molar_basis = cython.declare(cython.int, 1)
+1 -12
View File
@@ -50,9 +50,6 @@ class Units:
self.units = CxxUnits(stringify(name), True)
def __init__(self, name: str | None = None) -> None:
# The C++ object is constructed in __cinit__; this typed __init__ exists so
# that mypy/pyright (which do not recognize Cython's __cinit__) publish the
# constructor signature.
pass
def __repr__(self) -> str:
@@ -107,12 +104,7 @@ class UnitStack:
@cython.cfunc
@staticmethod
def copy(other: CxxUnitStack) -> UnitStack:
"""Copy a C++ UnitStack object to a Python object.
Note: the ``&`` (C++ reference) was dropped for pure-Python Cython compatibility
(pure-Python syntax has no spelling for reference parameters). Rvalue callers
get copy elision; ``copy()`` already copies into a new ``CxxUnitStack``.
"""
"""Copy a C++ UnitStack object to a Python object."""
stack: UnitStack = UnitStack()
stack.stack = CxxUnitStack(other)
return stack
@@ -165,9 +157,6 @@ class UnitSystem:
self.units = units
def __init__(self, units: _UnitDict | None = None) -> None:
# The C++ object is constructed in __cinit__; this typed __init__ exists so
# that mypy/pyright (which do not recognize Cython's __cinit__) publish the
# constructor signature.
pass
def __repr__(self) -> str:
-6
View File
@@ -4,12 +4,6 @@
# distutils: language = c++
# cython: language_level=3
# Sibling cdef classes used in the public annotations are imported as ordinary
# Python imports (resolvable by mypy/pyright); the companion yamlwriter.pxd cimports
# the same names so Cython still sees them as C-level extension types. Names that do
# not already start with an underscore are aliased to an underscore-prefixed name so
# they are not re-exported by ``from .yamlwriter import *`` (test_namespace_cleanliness).
# typing_extensions is a hard runtime dependency, so ``Never`` is imported directly.
from typing_extensions import Never as _Never
import cython
+11 -12
View File
@@ -21,10 +21,10 @@ foreach(CY_SOURCE IN LISTS CY_SOURCES)
VERBATIM)
endforeach()
# The aggregator module _cantera uses pure-Python (.py) Cython syntax (enhancement #241)
# but is always compiled INTO the merged _cantera extension (it hosts the metapath finder
# and the PyInit functions for the .pyx modules merged here), so cythonize it alongside the
# .pyx sources rather than as a standalone pure-Python module.
# The aggregator module _cantera uses pure-Python (.py) Cython syntax but is always
# compiled INTO the merged _cantera extension (it hosts the metapath finder and the
# PyInit functions for the .pyx modules merged here), so cythonize it alongside the .pyx
# sources rather than as a standalone pure-Python module.
set(CANTERA_AGG_CPP "${CMAKE_CURRENT_BINARY_DIR}/_cantera.cpp")
add_custom_command(
OUTPUT ${CANTERA_AGG_CPP}
@@ -34,8 +34,7 @@ add_custom_command(
VERBATIM)
list(APPEND CY_OUTPUTS ${CANTERA_AGG_CPP})
# Cython pure-Python-syntax modules use the .py extension rather than .pyx
# (enhancement #241). Cythonize each to a .cpp here; how it is compiled depends on the
# Cythonize each Cython pure-Python-syntax modules. How they are compiled depends on the
# platform (see below).
set(PURE_CY_MODULES _utils jacobians constants units func1 reactionpath
yamlwriter speciesthermo mixture transport solutionbase
@@ -54,12 +53,12 @@ foreach(MODULE IN LISTS PURE_CY_MODULES)
endforeach()
if(CANTERA_PYODIDE)
# On Pyodide the standalone-extension + shared-libcantera layout isn't usable (see
# src/CMakeLists.txt), so compile the pure-Python modules INTO the merged _cantera
# extension, just like the .pyx modules. cantera.<module> is then provided by
# PyInit_<module> within _cantera and resolved by the CythonPackageMetaPathFinder in
# _cantera.pyx; the raw .py is excluded from the Pyodide wheel (see pyproject.toml.in)
# so it doesn't shadow the merged module on import.
# On Pyodide the standalone-extension + shared-libcantera layout isn't usable,
# so compile the pure-Python modules INTO the merged _cantera extension.
# cantera.<module> is then provided by PyInit_<module> within _cantera and
# resolved by the CythonPackageMetaPathFinder in _cantera.py; the raw .py is excluded
# from the Pyodide wheel (see pyproject.toml.in) so it doesn't shadow the merged
# module on import.
list(APPEND CY_OUTPUTS ${PURE_CY_OUTPUTS})
endif()
@@ -82,11 +82,6 @@ HDF5_ROOT = { env = "HDF5_ROOT" }
exclude = ["pyproject.toml.in", "cantera/with_units/solution.py.in"]
[tool.scikit-build.wheel]
# jacobians.py and the other pure-Python-syntax modules listed in PURE_CY_MODULES
# (cantera/CMakeLists.txt) are each built as their own standalone extension linked
# against the shared libcantera. The .py source ships alongside the compiled module so
# IDEs, type checkers, and tracebacks can read it; CPython prefers the extension over
# the source on import.
exclude = ["include", "lib", "share", "**.pyx", "cantera/CMakeLists.txt"]
[[tool.scikit-build.overrides]]
+10 -12
View File
@@ -143,16 +143,15 @@ endif()
configure_file("../include/cantera/base/config.h.in" "${CMAKE_CURRENT_SOURCE_DIR}/../include/cantera/base/config.h")
# On native platforms (Linux/macOS/Windows) libcantera is SHARED, so the Cython
# pure-Python-syntax modules (enhancement #241) can be standalone extensions that link it
# and ship their .py source beside the compiled .so. On Pyodide this split layout is not
# viable: each extension and libcantera would be an emscripten "side module", and
# emscripten cannot resolve C++ vague-linkage *data* symbols (RTTI typeinfo / vtables,
# which exception handling depends on) across the side-module boundary -- loading
# _cantera fails with e.g. "bad export type for 'typeinfo for Cantera::CanteraError'".
# So on Pyodide we fall back to a STATIC libcantera merged into a single _cantera
# extension (see cantera/CMakeLists.txt; full writeup in the developer notes). Revisit if
# emscripten's dynamic linking of data symbols improves.
# On native platforms (Linux/macOS/Windows) libcantera is a SHARED library, so the
# Cython pure-Python-syntax modules can be standalone extensions that link it and ship
# their .py source beside the compiled .so. On Pyodide this split layout is not viable:
# each extension and libcantera would be an emscripten "side module", and emscripten
# cannot resolve C++ vague-linkage *data* symbols (RTTI typeinfo / vtables, which
# exception handling depends on) across the side-module boundary. So on Pyodide we fall
# back to a STATIC libcantera merged into a single _cantera extension (see
# cantera/CMakeLists.txt). Revisit if emscripten's dynamic linking of data symbols
# improves.
if(CANTERA_PYODIDE)
add_library(cantera_lib STATIC ${CT_LIB_SOURCES})
else()
@@ -163,8 +162,7 @@ target_include_directories(cantera_lib SYSTEM PRIVATE "${Python_INCLUDE_DIRS}" "
set_target_properties(cantera_lib PROPERTIES
POSITION_INDEPENDENT_CODE ON
# Cantera's headers don't annotate the public API with dllexport/dllimport, so
# have CMake generate a .def exporting all symbols from the shared library (the
# SCons build achieves the same result with a filtered dumpbin-generated .def).
# have CMake generate a .def exporting all symbols from the shared library
WINDOWS_EXPORT_ALL_SYMBOLS ON)
# pythonShim.cpp (embedded interpreter support) calls into the Python C API, so the
# shared library must resolve those symbols at link time on Windows. Python::Module