bloc.io.mechanisms#

Loading Cantera mechanisms: caches, and cheap phases built from them.

Everything here answers “give me a phase for this mechanism file” – which is file IO plus Cantera’s parser, not thermodynamics, hence bloc.io rather than bloc.chem. What callers do with the resulting phase (state lookups, property queries) stays in bloc.chem.state.

The cost being managed here is a large mechanism’s parse. On CRECK_2003_TOT_HT_SOOT_kinetics_transport.yaml (452 species, 24041 reactions) a full ct.Solution is 6.5 s, reading its species alone is 3.6 s, and reading only the species: section is 0.46 s – see tests.io.test_mechanism_read_paths, which compares every route on that file. Three layers of reuse sit on top: this module’s two caches, and the species_from argument of build_lite_solution() for callers that already hold a phase.

Attributes#

Classes#

BlocSolution

A ct.Solution derived from a parsed mechanism that remembers which one.

Functions#

get_cached_solution(mechanism_source)

Get or create a cached Solution object for a mechanism.

mechanism_path_of(gas)

Return the mechanism gas was built from, or None when it cannot be told.

require_mechanism_path(gas)

mechanism_path_of(), raising ValueError when it is unknown.

new_solution(mechanism_source, *[, kinetics, transport])

Return a fresh, independent ct.Solution for mechanism_source, cheaply.

build_lite_solution(mechanism_source[, transport, ...])

Construct a fresh thermo(+transport) ct.Solution with no reactions.

Module Contents#

bloc.io.mechanisms.MECHANISM_SOLUTION_CACHE: dict[str, cantera.Solution]#
bloc.io.mechanisms.get_cached_solution(mechanism_source)#

Get or create a cached Solution object for a mechanism.

This function provides centralized access to the MECHANISM_SOLUTION_CACHE, creating new Solution objects only when needed and reusing existing ones for performance optimization.

Parameters:

mechanism_source (str) – The mechanism source (e.g., ‘gri30.yaml’ or path to mechanism file)

Returns:

Cached Solution object for the mechanism

Return type:

ct.Solution

Examples

>>> gas = get_cached_solution("gri30.yaml")
>>> gas.TPX = 1000, ct.one_atm, {"H2": 1.0}
>>> print(gas.T)
1000.0
class bloc.io.mechanisms.BlocSolution#

Bases: cantera.Solution

A ct.Solution derived from a parsed mechanism that remembers which one.

Cantera’s source is the file a phase was read from – and only that: a phase assembled from species/reaction objects reports "custom parts", a reactor-cloned one "<unknown>". Bloc keys a lot of behaviour on the mechanism (cached lookups, species-basis mixing, KPI helpers), so phases it derives carry the path here; read it through mechanism_path_of().

mechanism_path: str#
bloc.io.mechanisms.mechanism_path_of(gas)#

Return the mechanism gas was built from, or None when it cannot be told.

The stamped BlocSolution.mechanism_path first (any phase built by new_solution() / build_lite_solution(), or by Boulder’s factory), else a source that names a file or a bare Cantera data name. None for a cloned or otherwise anonymous phase – callers that need the mechanism use require_mechanism_path() for a clear error instead of ct.Solution("<unknown>") failing deep inside Cantera.

bloc.io.mechanisms.require_mechanism_path(gas)#

mechanism_path_of(), raising ValueError when it is unknown.

bloc.io.mechanisms.new_solution(mechanism_source, *, kinetics=True, transport=False)#

Return a fresh, independent ct.Solution for mechanism_source, cheaply.

The file is parsed once per process (get_cached_solution()); this installs its parsed species and reactions into a new phase. On CRECK_2003_TOT_HT_SOOT_kinetics_transport.yaml (452 species, 24041 reactions) that is 0.36 s against 5-8 s for ct.Solution(file) and 2.6 s for Reactor(gas, clone=True), with identical thermo and net_production_rates (to the last digit) and an independent state.

kinetics=False leaves the reactions out (a state holder, ~1 ms) – never integrate chemistry on such a phase. transport=False (default) skips the transport fit, which is the dominant per-object cost (~3 s on that mechanism) and only matters to callers reading viscosity or thermal conductivity. The phase name is copied so cantera.Quantity mixes the result with phases loaded from the file, and mechanism_path is stamped so mechanism_path_of() still answers.

bloc.io.mechanisms.build_lite_solution(mechanism_source, transport=True, species_from=None)#

Construct a fresh thermo(+transport) ct.Solution with no reactions.

For a large mechanism, most of the memory (and load time) a ct.Solution costs is reaction-kinetics data: on CRECK_2003_TOT_HT_SOOT_kinetics_transport.yaml (452 species, 24041 reactions), a full ct.Solution is ~199 MB versus ~21 MB here – about a 90% reduction (see issue #331). Use this instead of ct.Solution(mechanism_source) wherever the object is only used to hold a thermodynamic/transport state (setting TPX/TPY and reading things like density, cp_mass, viscosity, thermal_conductivity, elemental fractions, heating values, or for ct.Quantity mixing) rather than to integrate chemistry.

The returned object has n_reactions == 0. Calling anything that requires active kinetics (net_production_rates, advancing a ReactorNet built on it, etc.) will not raise, but will silently give a physically meaningless answer (no reactions to produce a rate) – never use it for that.

Pass transport=False when the phase will only be asked for thermodynamic properties. Fitting a mixture-averaged transport model is the cost of building a phase for a large mechanism – 2149 ms against 0.7 ms on CRECK_2003_TOT_HT_SOOT_kinetics_transport.yaml (452 species), a factor of 3000 – and it buys nothing for a caller that reads density, cp, elemental fractions or heating values. The result then reports transport_model == "none", so a caller that does ask for viscosity/thermal_conductivity gets a loud NotImplementedError rather than a wrong number; the two places in Bloc that read them (bloc.chem.heat_transfer() and the tube-furnace recorder) check for "none" first anyway.

Pass species_from when the caller already holds a phase for the same mechanism – a solved reactor’s phase, say. Only its species definitions are borrowed, never its state, so the result is still a fresh, independently mutable phase, and several can be built from one such phase and given different states. They are already parsed, so the new phase costs ~0.04 ms against seconds for a file, and the mechanism file is never opened.

Borrowed only when that phase’s mechanism (mechanism_path_of()) matches mechanism_source – stages either side of a mechanism_switch legitimately carry different species, and taking the wrong set would not raise, it would answer wrongly – and, when transport is requested, only when it carries a transport model of its own: a cloned reservoir phase reports "none" and would silently strip viscosity and thermal conductivity from the result. Either check failing falls back to reading the file, so species_from is an optimisation, never a requirement.

A phase built this way mixes with one built from the file: Quantity addition accepts them and returns the same state to the last digit. It is building a phase from a file’s phase definition that is incompatible with both.

Always builds a new, independent object (new_solution() is the kinetics-carrying counterpart). The result is a BlocSolution, so mechanism_path_of() still names the mechanism although Cantera’s source does not.

Two phases from this function (same mechanism) can be added as cantera.Quantity objects and give the same mixed state as two full ct.Solution``s would. Phases built by *different* routes cannot: ``Quantity.__add__ compares phase definitions, not species lists, so a mixing chain must build every side the same way.