diff --git a/.github/workflows/linters.yml b/.github/workflows/linters.yml index 902de3ba955..2bdc592fde5 100644 --- a/.github/workflows/linters.yml +++ b/.github/workflows/linters.yml @@ -59,6 +59,7 @@ jobs: echo "test/data/*.inp -diff" >> .gitattributes echo "test/data/*.xml -diff" >> .gitattributes echo "test/data/*.cti -diff" >> .gitattributes + echo "test/data/*.dat -diff" >> .gitattributes echo "test_problems/**/*blessed* -diff" >> .gitattributes git config --global core.autocrlf false diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b643fb55cd4..fbb403ec120 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -30,7 +30,8 @@ jobs: name: > ${{ matrix.os }} with Python ${{ matrix.python-version }}, Numpy ${{ matrix.numpy || 'latest' }}, Cython ${{ matrix.cython || 'latest' }}, - SCons ${{ matrix.scons || 'latest' }} + SCons ${{ matrix.scons || 'latest' }}, + setuptools ${{ matrix.setuptools || 'latest' }} runs-on: ${{ matrix.os }} timeout-minutes: 60 strategy: @@ -41,6 +42,7 @@ jobs: numpy: [''] cython: ['!=3.1.2'] # Specifier can be dropped after 3.1.3 or 3.2.0 is released scons: [''] + setuptools: [''] include: # Keep some test cases with NumPy 1.x until we drop support - python-version: '3.12' @@ -53,6 +55,7 @@ jobs: os: 'ubuntu-22.04' cython: "==0.29.31" # minimum supported version scons: '==4.0.1' # minimum supported version + setuptools: "==75.7.0" # latest version that leaves package name uppercase - python-version: '3.11' os: 'ubuntu-22.04' cython: "==3.0.8" # System version for Ubuntu 24.04 @@ -697,7 +700,7 @@ jobs: # See https://unix.stackexchange.com/a/392973 for an explanation of the -exec part. # Skip 1D_packed_bed.py due to difficulty installing scikits.odes. # Increase figure limit to handle flame_speed_convergence_analysis.py. - # Skip examples for Python releases where pandas/pyarrow or CoolProp are not available + # Skip examples for Python releases where pandas/pyarrow or CoolProp are not available. run: | echo "Running on `cat /proc/cpuinfo | grep 'model name' | head -n 1 | cut -d ':' -f 2`" ln -s libcantera_shared.so build/lib/libcantera_shared.so.3 @@ -722,9 +725,10 @@ jobs: find samples/python -type f -iname "*.py" \ -exec sh -c 'for n; do echo "$n" | tee -a results.txt && python3 "$n" >> results.txt || exit 1; done' sh {} + env: - # The pyparsing ignore setting is due to a new warning introduced in Matplotlib==3.6.0 - # Ignore NasaPoly2 warnings from n-hexane-NUIG-2015.yaml - PYTHONWARNINGS: "error,ignore:warn_name_set_on_empty_Forward::pyparsing,ignore:datetime.datetime.utcfromtimestamp:DeprecationWarning:,ignore:NasaPoly2:UserWarning:" + # Fail on warnings attributed to direct example usage while ignoring + # transitive dependency warnings. + # Ignore known Cantera NasaPoly2 warnings from n-hexane-NUIG-2015.yaml. + PYTHONWARNINGS: "ignore,error:::__main__,ignore:NasaPoly2:UserWarning:" MPLBACKEND: Agg - name: Save the results file for inspection uses: actions/upload-artifact@v5 @@ -1256,8 +1260,8 @@ jobs: # MATLAB requires a specific library name: libcantera_shared.so.X # Recreating all symlinks to replicate status after build process run: | - ln -s libcantera_shared.so libcantera_shared.so.3.2.0 - ln -s libcantera_shared.so.3.2.0 libcantera_shared.so.3 + ln -s libcantera_shared.so libcantera_shared.so.3.2.1 + ln -s libcantera_shared.so.3.2.1 libcantera_shared.so.3 if: runner.os == 'Linux' - name: Download the Cantera shared library (macOS) uses: actions/download-artifact@v5 @@ -1270,8 +1274,8 @@ jobs: # MATLAB requires a specific library name: libcantera_shared.X.Y.Z.dylib # Recreating all symlinks to replicate status after build process run: | - ln -s libcantera_shared.dylib libcantera_shared.3.2.0.dylib - ln -s libcantera_shared.3.2.0.dylib libcantera_shared.3.dylib + ln -s libcantera_shared.dylib libcantera_shared.3.2.1.dylib + ln -s libcantera_shared.3.2.1.dylib libcantera_shared.3.dylib if: runner.os == 'macOS' - name: Download the Cantera shared library (Windows) uses: actions/download-artifact@v5 diff --git a/README.rst b/README.rst index d2007085cfc..eab9b0f4f3b 100644 --- a/README.rst +++ b/README.rst @@ -91,7 +91,7 @@ possible. Development Site ================ -The current development version is 3.3.0a1. The current stable version is +The current development version is 4.0.0a1. The current stable version is 3.2.0. The `latest Cantera source code `_, the `issue tracker `_ for bugs and enhancement requests, `downloads of Cantera releases and binary installers diff --git a/SConstruct b/SConstruct index 6bbfe3e2a2e..168b0a4a277 100644 --- a/SConstruct +++ b/SConstruct @@ -158,7 +158,7 @@ logger.info( f"SCons {SCons.__version__} is using the following Python interpreter:\n" f" {sys.executable} (Python {python_version})", print_level=False) -cantera_version = "3.2.0" +cantera_version = "3.2.1b1" # For use where pre-release tags are not permitted (MSI, sonames) cantera_pure_version = re.match(r'(\d+\.\d+\.\d+)', cantera_version).group(0) cantera_short_version = re.match(r'(\d+\.\d+)', cantera_version).group(0) @@ -1000,7 +1000,10 @@ def get_processor_name(): elif platform.system() == "Darwin": os.environ['PATH'] = os.environ['PATH'] + os.pathsep + '/usr/sbin' command ="sysctl -n machdep.cpu.brand_string" - return subprocess.check_output(command, shell=True).decode().strip() + try: + return subprocess.check_output(command, shell=True).decode().strip() + except subprocess.CalledProcessError: + return "" elif platform.system() == "Linux": command = "lscpu || cat /proc/cpuinfo" all_info = subprocess.check_output(command, shell=True).decode().strip() diff --git a/doc/doxygen/Doxyfile.in b/doc/doxygen/Doxyfile.in index a70d30b9f04..ff0dca78800 100644 --- a/doc/doxygen/Doxyfile.in +++ b/doc/doxygen/Doxyfile.in @@ -48,7 +48,7 @@ PROJECT_NAME = Cantera # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 3.2.0 +PROJECT_NUMBER = 3.2.1b1 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/doc/doxygen/versions.md b/doc/doxygen/versions.md index 148a593e76a..dcc64ec88e0 100644 --- a/doc/doxygen/versions.md +++ b/doc/doxygen/versions.md @@ -9,8 +9,8 @@ | Version | Status | Release Date | |---------|--------|--------------| | [dev/latest](https://cantera.org/dev/cxx/index.html) | unstable | -- | -| [v3.2.0](https://cantera.org/stable/cxx/index.html) | stable |*November 2025* | -| [v3.1.0](https://cantera.org/3.1/cxx/index.html) | stable |*December 2024* | +| [v3.2.0](https://cantera.org/3.2/cxx/index.html) | stable |*November 2025* | +| [v3.1.0](https://cantera.org/3.1/cxx/index.html) | legacy |*December 2024* | | [v3.0.0](https://cantera.org/3.0/doxygen/html/index.html) | legacy | *August 2023* | | [v2.6.0](https://cantera.org/2.6/doxygen/html/modules.html) | archived | *May 2022* | | [v2.5.1](https://cantera.org/2.5/doxygen/html/modules.html) | archived | *February 2021* | diff --git a/doc/sphinx/reference/onedim/governing-equations.md b/doc/sphinx/reference/onedim/governing-equations.md index b6a04be8345..f48237ccf23 100644 --- a/doc/sphinx/reference/onedim/governing-equations.md +++ b/doc/sphinx/reference/onedim/governing-equations.md @@ -11,7 +11,7 @@ in Section 7.2 of {cite:t}`kee2017` and are implemented by class {ct}`Flow1D`. *Continuity*: -$$ \pxpy{u}{z} + 2 \rho V = 0 $$ +$$ \pxpy{(\rho u)}{z} + 2 \rho V = 0 $$ *Radial momentum*: diff --git a/doc/sphinx/reference/releasenotes/v3.2.md b/doc/sphinx/reference/releasenotes/v3.2.md index 452e37a24f3..42e5a6ba117 100644 --- a/doc/sphinx/reference/releasenotes/v3.2.md +++ b/doc/sphinx/reference/releasenotes/v3.2.md @@ -1,3 +1,30 @@ +# Cantera 3.2.1 + +*Unreleased* + +This is a maintenance release which fixes issues identified since the release +of Cantera 3.2.0. + +- Resolve some issues with Python type hinting related to `TypeForm` [#2064](https://github.com/Cantera/cantera/pull/2064) +- Fix net surface mass flux calculation in `FlowReactor` [#2069](https://github.com/Cantera/cantera/pull/2069) +- Fix handling of multi-site surface species in `MoleReactor` surfaces [#2093](https://github.com/Cantera/cantera/pull/2093) +- Fix reservoir synchronization in `surf_pfr_chain.py` [#2069](https://github.com/Cantera/cantera/pull/2069) +- Fix garbage collection error with `Mixture` objects [#2091](https://github.com/Cantera/cantera/pull/2091) +- Add workaround for older versions of `typing_extensions` [#2056](https://github.com/Cantera/cantera/pull/2056) +- Handle wheel renaming from specific setuptools versions [#2055](https://github.com/Cantera/cantera/pull/2055) +- Fix documentation for 1D flame continuity equation [#2058](https://github.com/Cantera/cantera/pull/2058) +- Fix species slicing error with Numpy 2.4.0 development version [#2063](https://github.com/Cantera/cantera/pull/2063) +- Add workaround for Cython coverage reporting with Python 3.12+ [#2070](https://github.com/Cantera/cantera/pull/2070) +- Fix stubtest allow list for Python type hinting [#2070] +https://github.com/Cantera/cantera/pull/2070- Correct the "basis" parameter description for mole fraction basis [#2079](https://github.com/Cantera/cantera/pull/2079) +- Fix swapped items returned by `Kinetics::getDerivativeSettings` [#2069](https://github.com/Cantera/cantera/pull/2069) +- Ensure `PlasmaPhase` EEDF array is initialized [#2068](https://github.com/Cantera/cantera/pull/2068) +- Add workaround for running macOS `sysctl` command via SCons in sandboxed environments [#2086](https://github.com/Cantera/cantera/pull/2086) +- Fix slowdowns in sandboxed environments due to repeated `locale` calls [#2086](https://github.com/Cantera/cantera/pull/2086) +- Fix errors related to handling of derived Jacobian types in Python [#2069](https://github.com/Cantera/cantera/pull/2069) +- Fix segfault when calling `ReactorNet.get_state` on networks containing `FlowReactor [#2098](https://github.com/Cantera/cantera/pull/2098) +- Handle some non-standard formatting in CK-format input files related to surface species site occupancy, extended elemental composition, and whitespace in thermo entries [#2089](https://github.com/Cantera/cantera/pull/2089) + # Cantera 3.2.0 Published on November 17, 2025 | [Full release on GitHub](https://github.com/Cantera/cantera/releases/tag/v3.2.0) diff --git a/interfaces/cython/.mypyignore b/interfaces/cython/.mypyignore index dd19d483b46..d576ef5c938 100644 --- a/interfaces/cython/.mypyignore +++ b/interfaces/cython/.mypyignore @@ -10,7 +10,7 @@ cantera._types.* # cantera\.((_|\w)+\.)+__(?!init__$)[a-zA-Z0-9_]*__ # Exclude specific dunder methods -cantera\.((_|\w)+\.)+__(.*cython|test|pyx_capi|mutable_keys|readonly_keys)__ +cantera\.((_|\w)+\.)+__(.*cython|test|pyx_capi|mutable_keys|readonly_keys|conditional_annotations)__ # Current output of stubtest --generate-allowlist cantera.Arrhenius.__init__ diff --git a/interfaces/cython/SConscript b/interfaces/cython/SConscript index 9dcd50c2658..90fb0f8646f 100644 --- a/interfaces/cython/SConscript +++ b/interfaces/cython/SConscript @@ -1,9 +1,10 @@ """Cython-based Python Module""" import re from pathlib import Path +from packaging.specifiers import SpecifierSet from packaging.version import parse as parse_version -from buildutils import (get_command_output, which, multi_glob, +from buildutils import (get_command_output, which, multi_glob, logger, get_pip_install_location, setup_python_env) Import('env', 'build', 'install') @@ -26,6 +27,7 @@ if localenv["example_data"]: install(localenv.RecursiveInstall, "$inst_sampledir/python", "#samples/python") setup_python_env(localenv) +logger.info(f"Using setuptools version {localenv['setuptools_version']}") # Python module shouldn't explicitly link to Python library (added in other cases to # support Python-based extensions), except when using MinGW if localenv["toolchain"] != "mingw": @@ -40,7 +42,11 @@ localenv.Depends(license, localenv["license_target"]) directives = {"binding": True} if env["coverage"]: directives["linetrace"] = True - localenv.Append(CPPDEFINES={"CYTHON_TRACE": 1}) + localenv.Append(CPPDEFINES={ + "CYTHON_TRACE": 1, + # TODO: Remove after https://github.com/coveragepy/coveragepy/issues/1790 is resolved. + "CYTHON_USE_SYS_MONITORING": 0} + ) # Build the Python module cython_obj = [] @@ -86,8 +92,10 @@ build_cmd = ("$python_cmd_esc -m pip wheel -v --no-build-isolation --no-deps " if parse_version(localenv["py_version_short"]) >= env["python_max_version"]: build_cmd += " --ignore-requires-python" -# Setuptools 75.3.1 and later always lowercase the package name -if localenv["setuptools_version"] >= parse_version("75.3.1"): +# Newer setuptools versions always lowercase the package name +spec1 = SpecifierSet(">=75.8.1") +spec2 = SpecifierSet("~=75.3.1") # change backported to this branch +if localenv["setuptools_version"] in spec1 or localenv["setuptools_version"] in spec2: pkg_name = "cantera" else: pkg_name = "Cantera" diff --git a/interfaces/cython/cantera/_onedim.pyx b/interfaces/cython/cantera/_onedim.pyx index 2f2bf7d3c49..432eb10cafe 100644 --- a/interfaces/cython/cantera/_onedim.pyx +++ b/interfaces/cython/cantera/_onedim.pyx @@ -1687,7 +1687,7 @@ cdef class Sim1D: :param compression: Compression level (0-9); optional (default=0; HDF only) :param basis: - Output mass (``Y``/``mass``) or mole (``Y``/``mass``) fractions; + Output mass (``Y``/``mass``) or mole (``X``/``mole``) fractions; if not specified (`None`), the native basis of the underlying `ThermoPhase` manager is used. diff --git a/interfaces/cython/cantera/_types.py b/interfaces/cython/cantera/_types.py index 42473e0a9a4..8edbf478c10 100644 --- a/interfaces/cython/cantera/_types.py +++ b/interfaces/cython/cantera/_types.py @@ -7,6 +7,7 @@ Any, Concatenate, Literal, + ParamSpec, TypeAlias, TypedDict, TypeGuard, @@ -18,7 +19,13 @@ import numpy as np from numpy.typing import ArrayLike as ArrayLike from numpy.typing import NDArray -from typing_extensions import ParamSpec, TypeForm +try: + # Requires typing_extensions >= 4.13, or possibly Python >= 3.15 + from typing_extensions import TypeForm as TypeForm +except ImportError: + # Wrong, but better than crashing with an ImportError at runtime + from typing_extensions import Type as TypeForm + Array: TypeAlias = NDArray[np.float64] Index: TypeAlias = EllipsisType | int | slice | tuple[EllipsisType | int | slice, ...] @@ -194,7 +201,7 @@ def __init__(self, d: int, e: int, /, *args: Any, **kwargs: Any) -> None: return lambda f: f -def literal_type_guard(tag: str, literal: TypeForm[_T0]) -> TypeGuard[_T0]: # type: ignore[valid-type] +def literal_type_guard(tag: str, literal: TypeForm[_T0]) -> TypeGuard[_T0]: """Utility function for narrowing strings to specified literals. Typically used to check a string against the permissible keys of a diff --git a/interfaces/cython/cantera/ck2yaml.py b/interfaces/cython/cantera/ck2yaml.py index 7a886cabcd8..a4969dc2af5 100644 --- a/interfaces/cython/cantera/ck2yaml.py +++ b/interfaces/cython/cantera/ck2yaml.py @@ -790,13 +790,14 @@ def parse_composition(elements, nElements, width): if not symbol: continue try: - # Convert to float first for cases where ``count`` is a string - # like "2.00". - count = int(float(count)) - if count: - composition[symbol.capitalize()] = count + count = int(count) except ValueError: - pass + try: + count = float(count) + except ValueError: + pass + if count: + composition[symbol.capitalize()] = count return composition @staticmethod @@ -860,7 +861,11 @@ def read_NASA7_entry(self, lines, TintDefault, comments): comp = ' '.join(line.rstrip('&') for line in complines).split() composition = {} for i in range(0, len(comp), 2): - composition[comp[i].capitalize()] = int(comp[i+1]) + try: + value = int(comp[i+1]) + except ValueError: + value = float(comp[i+1]) + composition[comp[i].capitalize()] = value # Non-standard extended elemental composition data may be located beyond # column 80 on the first line of the thermo entry @@ -1565,7 +1570,7 @@ def load_chemkin_file(self, path, surface=False): else: comment = '' - lines.append((i, None, line, comment)) + lines.append((i, None, line.rstrip(), comment.rstrip())) lines = np.array(lines, dtype=object) @@ -1718,52 +1723,63 @@ def parse_site_section(self, lines): :param lines: A list of ``(line number, section name, line content, comment)`` tuples """ - # First line contains optional parameters, followed by species declarations - tokens = lines[0,2].split() or [''] - self.current_range = [lines[0,0]] * 2 - if tokens[0].startswith('/'): - surf_name = tokens[0].strip('/') - tokens = tokens[1:] - else: - surf_name = 'surface{}'.format(len(self.surfaces)+1) - + surf_name = None site_density = None - for token in tokens[:]: - if token.upper().startswith('SDEN/'): - site_density = fortFloat(token.split('/')[1]) - tokens.remove(token) - - if site_density is None: - logger.error(self.entry("SITE section") + - "SITE section defined with no site density") - self.surfaces.append(Surface(name=surf_name, site_density=site_density)) - surf = self.surfaces[-1] - - # Get the rest of the species declarations - for line in lines[1:]: - tokens.extend(line[2].split()) - - # List of species identifiers for surface species - for token in tokens: - if token.count('/') == 2: - # species occupies a specific number of sites - token, sites, _ = token.split('/') - sites = float(sites) - else: + + for lineno, _, line, _ in lines: + self.current_range = [lineno] * 2 + tokens = line.replace('/', ' / ').split() + if surf_name is None: + if len(tokens) >= 2 and tokens[0] == '/' and tokens[2] == '/': + surf_name = tokens[1] + tokens = tokens[3:] + else: + surf_name = 'surface{}'.format(len(self.surfaces)+1) + + if site_density is None and tokens: + if tokens[0].upper() == 'SDEN' and tokens[1] == tokens[3] == '/': + site_density = fortFloat(tokens[2]) + tokens = tokens[4:] + else: + self.current_range = [lines[0,0], lineno] + logger.error(self.entry("SITE section") + + "Site density missing or incorrectly formatted") + + self.surfaces.append(Surface(name=surf_name, site_density=site_density)) + surf = self.surfaces[-1] + + i = -1 + while i < len(tokens) - 1: + i += 1 + name = tokens[i] sites = None - if token in self.species_dict: - species = self.species_dict[token] - if self.permissive: - logger.warning("Ignoring redundant declaration for " - f"species '{species}'") + if name == '/': + logger.error(self.entry("SITE section") + "Encountered site " + f"occupancy '{tokens[i+1]}' before a species name.") + i += 2 + continue + + if len(tokens) >= i+4 and tokens[i+1] == tokens[i+3] == '/': + try: + sites = fortFloat(tokens[i+2]) + except ValueError: + logger.error(self.entry("SITE section") + "Unable to parse site" + f" occupancy value '{tokens[i+2]}' for species '{name}'.") + i += 3 + + if name in self.species_dict: + species = self.species_dict[name] + if self.permissive: + logger.warning("Ignoring redundant declaration for " + f"species '{species}'") + else: + logger.error(f"Found multiple declarations for species " + f"'{species}'. Run ck2yaml again with the\n'--permissive' " + "option to ignore the extra declarations.") else: - logger.error(f"Found multiple declarations for species " - f"'{species}'. Run ck2yaml again with the\n'--permissive' " - "option to ignore the extra declarations.") - else: - species = Species(label=token, sites=sites) - self.species_dict[token] = species - surf.species_list.append(species) + species = Species(label=name, sites=sites) + self.species_dict[name] = species + surf.species_list.append(species) def parse_nasa9_section(self, lines): """ @@ -2036,7 +2052,7 @@ def write_yaml(self, name='gas', out_name='mech.yaml'): metadata = BlockMap([ ("generator", "ck2yaml"), ("input-files", FlowList(self.files)), - ("cantera-version", "3.2.0"), + ("cantera-version", "3.2.1b1"), ("date", formatdate(localtime=True)), ]) if desc.strip(): diff --git a/interfaces/cython/cantera/composite.py b/interfaces/cython/cantera/composite.py index 1e62e4c4d2f..6893bce386d 100644 --- a/interfaces/cython/cantera/composite.py +++ b/interfaces/cython/cantera/composite.py @@ -1278,7 +1278,7 @@ def save(self, fname, name=None, sub=None, description=None, *, :param compression: Compression level (0-9); optional (default=0; HDF only) :param basis: - Output mass (``Y``/``mass``) or mole (``Y``/``mass``) fractions; + Output mass (``Y``/``mass``) or mole (``X``/``mole``) fractions; if not specified (`None`), the native basis of the underlying `ThermoPhase` manager is used. diff --git a/interfaces/cython/cantera/cti2yaml.py b/interfaces/cython/cantera/cti2yaml.py index ba4505a1876..53f6bd77ab4 100644 --- a/interfaces/cython/cantera/cti2yaml.py +++ b/interfaces/cython/cantera/cti2yaml.py @@ -1649,7 +1649,7 @@ def convert(filename=None, output_name=None, text=None, encoding="latin-1"): # information regarding conversion metadata = BlockMap([ ("generator", "cti2yaml"), - ("cantera-version", "3.2.0"), + ("cantera-version", "3.2.1b1"), ("date", formatdate(localtime=True)), ]) if filename != "": diff --git a/interfaces/cython/cantera/ctml2yaml.py b/interfaces/cython/cantera/ctml2yaml.py index 8c264dbf1fa..68779836816 100644 --- a/interfaces/cython/cantera/ctml2yaml.py +++ b/interfaces/cython/cantera/ctml2yaml.py @@ -39,7 +39,7 @@ from ruamel.yaml.representer import RoundTripRepresenter, SafeRepresenter from typing_extensions import Required -from ._types import literal_type_guard +from ._types import TypeForm, literal_type_guard # yaml.version_info is a tuple with the three parts of the version yaml_version: tuple[int, int, int] = yaml.version_info @@ -1673,8 +1673,8 @@ def const_cp(self, thermo: etree.Element) -> _ConstCpThermoInput: if tag == "t0": tag = "T0" - if literal_type_guard(tag, Literal["T0", "h0", "s0", "cp0"]): - thermo_attribs[tag] = get_float_or_quantity(node) # type: ignore[literal-required] + if literal_type_guard(tag, TypeForm(Literal["T0", "h0", "s0", "cp0"])): + thermo_attribs[tag] = get_float_or_quantity(node) tmin = const_cp_node.get("Tmin") if tmin is not None and tmin != "100.0": @@ -2736,7 +2736,7 @@ def convert( metadata: CommentedMap = BlockMap( { "generator": "ctml2yaml", - "cantera-version": "3.2.0", + "cantera-version": "3.2.1b1", "date": formatdate(localtime=True), } ) diff --git a/interfaces/cython/cantera/jacobians.pxd b/interfaces/cython/cantera/jacobians.pxd index a5c6c45b18e..54786ff2c20 100644 --- a/interfaces/cython/cantera/jacobians.pxd +++ b/interfaces/cython/cantera/jacobians.pxd @@ -44,20 +44,17 @@ cdef extern from "cantera/numerics/SystemJacobianFactory.h" namespace "Cantera": cdef class SystemJacobian: @staticmethod cdef wrap(shared_ptr[CxxSystemJacobian]) - cdef set_cxx_object(self) cdef shared_ptr[CxxSystemJacobian] _base + cdef CxxSystemJacobian* jac cdef class EigenSparseJacobian(SystemJacobian): - cdef set_cxx_object(self) - cdef CxxEigenSparseJacobian* sparse_jac + pass cdef class EigenSparseDirectJacobian(EigenSparseJacobian): pass cdef class AdaptivePreconditioner(EigenSparseJacobian): - cdef set_cxx_object(self) - cdef CxxAdaptivePreconditioner* adaptive + pass cdef class BandedJacobian(SystemJacobian): - cdef set_cxx_object(self) - cdef CxxMultiJac* band_jac + pass diff --git a/interfaces/cython/cantera/jacobians.pyx b/interfaces/cython/cantera/jacobians.pyx index bc951af06f1..d7f806ee6a8 100644 --- a/interfaces/cython/cantera/jacobians.pyx +++ b/interfaces/cython/cantera/jacobians.pyx @@ -22,7 +22,7 @@ cdef class SystemJacobian: def _cinit(self, *args, **kwargs): self._base = newSystemJacobian(stringify(self._type)) - self.set_cxx_object() + self.jac = self._base.get() @staticmethod cdef wrap(shared_ptr[CxxSystemJacobian] base): @@ -45,12 +45,9 @@ cdef class SystemJacobian: cdef SystemJacobian jac jac = cls(init=False) jac._base = base - jac.set_cxx_object() + jac.jac = base.get() return jac - cdef set_cxx_object(self): - pass - property side: """ Get/Set the side of the system matrix where the preconditioner is applied. @@ -58,11 +55,10 @@ cdef class SystemJacobian: by all solver types. """ def __get__(self): - return pystr(self._base.get().preconditionerSide()) + return pystr(self.jac.preconditionerSide()) def __set__(self, side): - self._base.get().setPreconditionerSide(stringify(side)) - + self.jac.setPreconditionerSide(stringify(side)) cdef class EigenSparseJacobian(SystemJacobian): """ @@ -72,18 +68,15 @@ cdef class EigenSparseJacobian(SystemJacobian): _type = "eigen-sparse" - cdef set_cxx_object(self): - self.sparse_jac = self._base.get() - def print_contents(self): - self.sparse_jac.printPreconditioner() + (self.jac).printPreconditioner() property matrix: """ Property to retrieve the latest internal preconditioner matrix. """ def __get__(self): - cdef CxxSparseMatrix smat = self.sparse_jac.matrix() + cdef CxxSparseMatrix smat = (self.jac).matrix() return get_from_sparse(smat, smat.rows(), smat.cols()) property jacobian: @@ -91,7 +84,7 @@ cdef class EigenSparseJacobian(SystemJacobian): Property to retrieve the latest Jacobian. """ def __get__(self): - cdef CxxSparseMatrix smat = self.sparse_jac.jacobian() + cdef CxxSparseMatrix smat = (self.jac).jacobian() return get_from_sparse(smat, smat.rows(), smat.cols()) @@ -108,9 +101,6 @@ cdef class AdaptivePreconditioner(EigenSparseJacobian): _type = "Adaptive" linear_solver_type = "GMRES" - cdef set_cxx_object(self): - self.adaptive = self._base.get() - property threshold: """ The threshold of the preconditioner is used to remove or prune any off diagonal @@ -127,10 +117,10 @@ cdef class AdaptivePreconditioner(EigenSparseJacobian): Default is 0.0. """ def __get__(self): - return self.adaptive.threshold() + return (self.jac).threshold() def __set__(self, val): - self.adaptive.setThreshold(val) + (self.jac).setThreshold(val) property ilut_fill_factor: """ @@ -147,10 +137,10 @@ cdef class AdaptivePreconditioner(EigenSparseJacobian): Default is the state size divided by 4. """ def __set__(self, val): - self.adaptive.setIlutFillFactor(val) + (self.jac).setIlutFillFactor(val) def __get__(self): - return self.adaptive.ilutFillFactor() + return (self.jac).ilutFillFactor() property ilut_drop_tol: """ @@ -165,11 +155,10 @@ cdef class AdaptivePreconditioner(EigenSparseJacobian): Default is 1e-10. """ def __set__(self, val): - self.adaptive.setIlutDropTol(val) + (self.jac).setIlutDropTol(val) def __get__(self): - return self.adaptive.ilutDropTol() - + return (self.jac).ilutDropTol() cdef class BandedJacobian(SystemJacobian): """ @@ -178,6 +167,3 @@ cdef class BandedJacobian(SystemJacobian): """ _type = "banded-direct" linear_solver_type = "direct" - - cdef set_cxx_object(self): - self.band_jac = self._base.get() diff --git a/interfaces/cython/cantera/mixture.pxd b/interfaces/cython/cantera/mixture.pxd index 7e2d9c16700..c360cedf8d6 100644 --- a/interfaces/cython/cantera/mixture.pxd +++ b/interfaces/cython/cantera/mixture.pxd @@ -10,7 +10,7 @@ cdef extern from "cantera/equil/MultiPhase.h" namespace "Cantera": cdef cppclass CxxThermoPhase "Cantera::ThermoPhase" cdef cppclass CxxMultiPhase "Cantera::MultiPhase": CxxMultiPhase() - void addPhase(CxxThermoPhase*, double) except +translate_exception + void addPhase(shared_ptr[CxxThermoPhase], double) except +translate_exception void init() except +translate_exception void updatePhases() except +translate_exception diff --git a/interfaces/cython/cantera/mixture.pyx b/interfaces/cython/cantera/mixture.pyx index b6ef4175c8b..ddffd60e229 100644 --- a/interfaces/cython/cantera/mixture.pyx +++ b/interfaces/cython/cantera/mixture.pyx @@ -57,7 +57,7 @@ cdef class Mixture: for phase,moles in phases: # Block species from being added to the phase as long as this object exists - self.mix.addPhase(phase.thermo, moles) + self.mix.addPhase(phase._base.get().thermo(), moles) self._phases.append(phase) self.mix.init() diff --git a/interfaces/cython/cantera/reactor.pxd b/interfaces/cython/cantera/reactor.pxd index 17e5fa852a8..feb0b6caad2 100644 --- a/interfaces/cython/cantera/reactor.pxd +++ b/interfaces/cython/cantera/reactor.pxd @@ -192,7 +192,7 @@ cdef extern from "cantera/zerodim.h" namespace "Cantera": cbool verbose() void setVerbose(cbool) size_t neq() - void getState(double*) + void getState(double*) except +translate_exception void getDerivative(int, double *) except +translate_exception void setAdvanceLimits(double*) cbool getAdvanceLimits(double*) diff --git a/interfaces/cython/cantera/solutionbase.pyx b/interfaces/cython/cantera/solutionbase.pyx index 54b60bc9afe..2d57607beaa 100644 --- a/interfaces/cython/cantera/solutionbase.pyx +++ b/interfaces/cython/cantera/solutionbase.pyx @@ -395,7 +395,7 @@ cdef class _SolutionBase: def __set__(self, species): if isinstance(species, (str, int)): species = (species,) - self._selected_species.resize(len(species)) + self._selected_species = np.ndarray(len(species), dtype=np.uint64) for i,spec in enumerate(species): self._selected_species[i] = self.species_index(spec) diff --git a/interfaces/cython/pyproject.toml b/interfaces/cython/pyproject.toml index 10b79a8c242..670998f9805 100644 --- a/interfaces/cython/pyproject.toml +++ b/interfaces/cython/pyproject.toml @@ -14,6 +14,7 @@ enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"] warn_unreachable = true disallow_untyped_defs = true disallow_incomplete_defs = true +enable_incomplete_feature = "TypeForm" [[tool.mypy.overrides]] module = "graphviz.*" diff --git a/interfaces/sourcegen/pyproject.toml b/interfaces/sourcegen/pyproject.toml index e3ae7dde3fb..704b41c0b89 100644 --- a/interfaces/sourcegen/pyproject.toml +++ b/interfaces/sourcegen/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "sourcegen" -version = "3.2.0" +version = "3.2.1b1" license-files = ["../../License.txt"] description = "Source generator for creating Cantera interface code" authors = [{name = "Cantera Developers"}] diff --git a/samples/python/reactors/surf_pfr_chain.py b/samples/python/reactors/surf_pfr_chain.py index b5be27b821b..6fd7ddadeff 100644 --- a/samples/python/reactors/surf_pfr_chain.py +++ b/samples/python/reactors/surf_pfr_chain.py @@ -105,7 +105,7 @@ for n in range(NReactors): # Set the state of the reservoir to match that of the previous reactor - gas.TDY = r.phase.TDY + upstream.phase.TDY = r.phase.TDY upstream.syncState() sim.reinitialize() sim.advance_to_steady_state() diff --git a/src/base/stringUtils.cpp b/src/base/stringUtils.cpp index b7f4d823269..1590b1dd807 100644 --- a/src/base/stringUtils.cpp +++ b/src/base/stringUtils.cpp @@ -121,8 +121,9 @@ Composition parseCompString(const string& ss, const vector& names) double fpValue(const string& val) { double rval; + static const auto locale = std::locale("C"); std::stringstream ss(val); - ss.imbue(std::locale("C")); + ss.imbue(locale); ss >> rval; return rval; } diff --git a/src/kinetics/InterfaceKinetics.cpp b/src/kinetics/InterfaceKinetics.cpp index 9739b7ab43c..c7bde98dcda 100644 --- a/src/kinetics/InterfaceKinetics.cpp +++ b/src/kinetics/InterfaceKinetics.cpp @@ -660,8 +660,8 @@ void InterfaceKinetics::setDerivativeSettings(const AnyMap& settings) void InterfaceKinetics::getDerivativeSettings(AnyMap& settings) const { - settings["skip-coverage-dependence"] = m_jac_skip_electrochemistry; - settings["skip-electrochemistry"] = m_jac_skip_coverage_dependence; + settings["skip-coverage-dependence"] = m_jac_skip_coverage_dependence; + settings["skip-electrochemistry"] = m_jac_skip_electrochemistry; settings["rtol-delta"] = m_jac_rtol_delta; } diff --git a/src/thermo/PlasmaPhase.cpp b/src/thermo/PlasmaPhase.cpp index 6c84ed1a198..125792e0a89 100644 --- a/src/thermo/PlasmaPhase.cpp +++ b/src/thermo/PlasmaPhase.cpp @@ -37,6 +37,7 @@ PlasmaPhase::PlasmaPhase(const string& inputFile, const string& id_) m_nPoints = nGridCells + 1; m_eedfSolver->setLinearGrid(kTe_max, nGridCells); m_electronEnergyLevels = MappedVector(m_eedfSolver->getGridEdge().data(), m_nPoints); + m_electronEnergyDist.setZero(m_nPoints); } PlasmaPhase::~PlasmaPhase() diff --git a/src/zeroD/FlowReactor.cpp b/src/zeroD/FlowReactor.cpp index 045713aff82..fa8461567ab 100644 --- a/src/zeroD/FlowReactor.cpp +++ b/src/zeroD/FlowReactor.cpp @@ -251,7 +251,7 @@ void FlowReactor::evalDae(double time, double* y, double* ydot, double* residual const vector& mw = m_thermo->molecularWeights(); double sk_wk = 0; for (size_t i = 0; i < m_nsp; ++i) { - sk_wk = m_sdot[i] * mw[i]; + sk_wk += m_sdot[i] * mw[i]; } m_thermo->getPartialMolarEnthalpies(m_hk.data()); // get net production diff --git a/src/zeroD/MoleReactor.cpp b/src/zeroD/MoleReactor.cpp index ba10068b702..7013f63400a 100644 --- a/src/zeroD/MoleReactor.cpp +++ b/src/zeroD/MoleReactor.cpp @@ -70,7 +70,7 @@ void MoleReactor::evalSurfaces(double* LHS, double* RHS, double* sdot) S->restoreState(); kin->getNetProductionRates(&m_work[0]); for (size_t k = 0; k < nk; k++) { - RHS[loc + k] = m_work[k] * wallarea / surf->size(k); + RHS[loc + k] = m_work[k] * wallarea; } loc += nk; diff --git a/test/data/soot-therm.dat b/test/data/soot-therm.dat index d328bfb731e..e68ec73f66d 100644 --- a/test/data/soot-therm.dat +++ b/test/data/soot-therm.dat @@ -42,9 +42,10 @@ C 778 H 263 4.05143093E+04-1.77494305E+02-1.20603441E+01 1.59247554E-01-1.41562602E-04 3 6.26071650E-08-1.09305161E-11 5.56473533E+04 7.68451211E+01 4 BIN6 PYRENE C 0H 0 0 0G 300.000 5000.000 1401.000 01& -C 778& +C 778.0& H 264 3.65839677E+01 3.36764102E-02-1.16783938E-05 1.83077466E-09-1.06963777E-13 2 9.29809483E+03-1.81272070E+02-1.29758980E+01 1.63790064E-01-1.43851166E-04 3 6.31057915E-08-1.09568047E-11 2.48866399E+04 7.94950474E+01 4 + END diff --git a/test/data/surface-bad-occupancy.inp b/test/data/surface-bad-occupancy.inp new file mode 100644 index 00000000000..d6517778ff9 --- /dev/null +++ b/test/data/surface-bad-occupancy.inp @@ -0,0 +1,9 @@ +SITE SDEN/2.72E-9/ +_Pt_ H_Pt O2_Pt/3/ /4/H2O_Pt OH_Pt O_Pt +END + + REACTIONS JOULES/MOLE MWOFF + H2 + 2_Pt_ => 2H_Pt 4.4579E+10 0.5 0.0 + FORD/_Pt_ 1/ + O2_Pt = O2 + 3 _Pt_ 3.70E+21 0.00 11500.0 +END diff --git a/test/data/surface1b.inp b/test/data/surface1b.inp new file mode 100644 index 00000000000..35847359f5e --- /dev/null +++ b/test/data/surface1b.inp @@ -0,0 +1,63 @@ +SITE/PT_SURFACE/ + ! a case with weird spacing and interstitial comments + SDEN/ 2.72E-9/ +_Pt_ H_Pt O2_Pt /3/ H2O_Pt / 1/ +! another comment +OH_Pt / 1 / O_Pt +END +THERMO ALL + 300.0 1000.0 3000.0 +O_Pt 92491O 1PT 1 I 300.00 3000.00 1000.00 1 + 0.19454180E+01 0.91761647E-03-0.11226719E-06-0.99099624E-10 0.24307699E-13 2 +-0.14005187E+05-0.11531663E+02-0.94986904E+00 0.74042305E-02-0.10451424E-05 3 +-0.61120420E-08 0.33787992E-11-0.13209912E+05 0.36137905E+01 4 +O2_Pt 92491O 2PT 3 I 300.00 3000.00 1000.00 1 + 0.19454180E+01 0.91761647E-03-0.11226719E-06-0.99099624E-10 0.24307699E-13 2 +-0.14005187E+05-0.11531663E+02-0.94986904E+00 0.74042305E-02-0.10451424E-05 3 +-0.61120420E-08 0.33787992E-11-0.13209912E+05 0.36137905E+01 4 +H_Pt 92491H 1PT 1 I 300.00 3000.00 1000.00 1 + 0.10696996E+01 0.15432230E-02-0.15500922E-06-0.16573165E-09 0.38359347E-13 2 +-0.50546128E+04-0.71555238E+01-0.13029877E+01 0.54173199E-02 0.31277972E-06 3 +-0.32328533E-08 0.11362820E-11-0.42277075E+04 0.58743238E+01 4 +H2O_Pt 92491O 1H 2PT 1 I 300.00 3000.00 1000.00 1 + 0.25803051E+01 0.49570827E-02-0.46894056E-06-0.52633137E-09 0.11998322E-12 2 +-0.38302234E+05-0.17406322E+02-0.27651553E+01 0.13315115E-01 0.10127695E-05 3 +-0.71820083E-08 0.22813776E-11-0.36398055E+05 0.12098145E+02 4 +OH_Pt 92491O 1H 1PT 1 I 300.00 3000.00 1000.00 1 + 0.18249973E+01 0.32501565E-02-0.31197541E-06-0.34603206E-09 0.79171472E-13 2 +-0.26685492E+05-0.12280891E+02-0.20340881E+01 0.93662683E-02 0.66275214E-06 3 +-0.52074887E-08 0.17088735E-11-0.25319949E+05 0.89863186E+01 4 +_Pt_ PT 1 S 300.0 3000.0 1000.0 1 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 2 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 3 + 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 4 +END + + REACTIONS JOULES/MOLE MWON + H2 + 2_Pt_ => 2H_Pt 4.4579E+10 0.5 0.0 + FORD/_Pt_ 1/ + 2H_Pt => H2 + 2_Pt_ 3.70E+21 0.00 67400.0 + COV/H_Pt 0.0 0.0 -6000.0/ + COV/OH_Pt 0.0 1.0 -3000.0/ + H + _Pt_ => H_Pt 1.00 0.0 0.0 + STICK + O2 + 2_Pt_ => 2O_Pt 1.80E+21 -0.5 0.0 + DUPLICATE + O2 + 2_Pt_ => 2O_Pt 0.023 0.00 0.00 + DUPLICATE STICK MWOFF + 2O_Pt => O2 + 2_Pt_ 3.70E+21 0.00 213200.0 + COV/O_Pt 0.0 0.0 -60000.0/ + O + _Pt_ => O_Pt 1.00 0.0 0.0 + STICK + MWON + H2O + _Pt_ => H2O_Pt 0.75 0.0 0.0 + STICK + H2O_Pt => H2O + _Pt_ 1.0E+13 0.00 40300.0 + OH + _Pt_ => OH_Pt 1.00 0.0 0.0 + STICK + OH_Pt => OH + _Pt_ 1.0E+13 0.00 192800.0 + H_Pt + O_Pt = OH_Pt + _Pt_ 3.70E+21 0.00 11500.0 + H_Pt + OH_Pt = H2O_Pt + _Pt_ 3.70E+21 0.00 17400.0 + OH_Pt + OH_Pt = H2O_Pt + O_Pt 3.70E+21 0.00 48200.0 + O2_Pt = O2 + 3 _Pt_ 3.70E+21 0.00 11500.0 +END diff --git a/test/python/coverage.ini b/test/python/coverage.ini index 80e95de18d1..e4f99123d54 100644 --- a/test/python/coverage.ini +++ b/test/python/coverage.ini @@ -1,4 +1,7 @@ [run] +# TODO: Remove this 'core' setting after +# https://github.com/coveragepy/coveragepy/issues/1790 is resolved. +core = ctrace plugins = Cython.Coverage omit = *.cti diff --git a/test/python/test_convert.py b/test/python/test_convert.py index b091126d842..66501f8100f 100644 --- a/test/python/test_convert.py +++ b/test/python/test_convert.py @@ -648,8 +648,16 @@ def test_surface_mech3(self): assert surf.n_reactions == 15 assert surf.reaction(4).duplicate is True + def test_surface_mech4(self): + # A case with unusual formatting of the surface species section + output = self.convert('surface1-gas-noreac.inp', surface='surface1b.inp', + output='surface1b-nogasreac') + + surf = ct.Interface(output, 'PT_SURFACE') + assert surf.n_species == 6 + def test_missing_site_density(self): - with pytest.raises(ck2yaml.InputError, match="no site density"): + with pytest.raises(ck2yaml.InputError, match="Site density missing"): self.convert("surface1-gas.inp", surface="missing-site-density.inp") captured = self._capsys.readouterr() @@ -659,6 +667,12 @@ def test_bad_reaction_option(self): captured = self._capsys.readouterr() assert "Unrecognized token 'XYZ' on REACTIONS line" in captured.out + def test_bad_site_occupancy(self): + with pytest.raises(ck2yaml.InputError, match="Encountered site occupancy '4'"): + self.convert('surface2-gas.inp', thermo='surface2-thermo.dat', + surface='surface-bad-occupancy.inp') + captured = self._capsys.readouterr() + def test_third_body_plus_falloff_reactions(self): output = self.convert("third_body_plus_falloff_reaction.inp") gas = ct.Solution(output)