PiecewiseSymbolicFunction(
breakpoints : list[float],
functions : list[typing.Any]
) -> None:Compose a symbolic piecewise function with CasADi.
Parameters
breakpoints : list[float]functions : list[typing.Any]aka majordome_engineering.symbolic
This module contains utilities for working with symbolic functions, especially in the context of engineering problems treated by Majordome models. It is mostly a wrapper around CasADi, organizing symbolic operations and providing additional functionality for function manipulation in a physical context.
A major issue when working with symbolic expressions and real-world data is that they do not support flow-control constructs like if statements. This is a problem when we want to represent functions that are defined piecewise, especially when algorithmic differentiation is involved.
As an example, take the parameterization of thermodynamic properties of chemical species. For quantitative purposes, to ensure high accuracy, researchers have found that it is best to use different polynomial fits for different temperature ranges. This means that the thermodynamic properties of a species are defined as piecewise functions of temperature, which is a problem when we want to use them in a symbolic context.
To tackle this issue in certain contexts, PiecewiseSymbolicFunction provides an interpolation between different ranges of the domain of a function. Internally it makes use of Heaviside functions to ensure that the function is continuous. This is not a perfect solution, as it does not guarantee differentiability at the breakpoints, but it is a good compromise between accuracy and simplicity. It is also worth noting that this class is not meant to be used in a general context, but rather in specific contexts where the function is known to be well-behaved.
PiecewiseSymbolicFunction(
breakpoints : list[float],
functions : list[typing.Any]
) -> None:Compose a symbolic piecewise function with CasADi.
breakpoints : list[float]functions : list[typing.Any]Let’s illustrate its use with a simple example. We define two functions f and g, and we want to create a piecewise function that is equal to f for x < 0.5 and equal to g for x >= 0.5. Functions have being created such that they evaluate to the same value at the breakpoint, as the practical case that led to the class development, i.e. NASA-parameterization of thermochemical properties. Below we show how to create the piecewise function, evaluate it numerically and symbolically, and compute its derivative symbolically.
# Define two simple functions:
f = lambda x: x**2
g = lambda x: f(0.5) + (x - 0.5)
# Create the piecewise function:
F = PiecewiseSymbolicFunction([0, 0.5, 1], [f, g])
# Evaluate numerically:
x = np.linspace(-0.2, 1.2, 100)
y = F(x)
# Evaluate symbolically:
T = SX.sym("T")
Y = F(T)
# Create a function for evaluating the derivative:
ydot = Function("fdot", [T], [jacobian(Y, T)])Below we plot the piecewise function and its derivative. The function is continuous, but the derivative is not, as expected, although the transition is handled smoothly by the Heaviside functions. The red dashed line indicates the breakpoint at x = 0.5, where the function transitions from f to g.

symbolic_thermo_factory(
species : Species,
T : SX
) -> AbstractSymbolicThermo:Create an AbstractSymbolicThermo object.
species : SpeciesT : SXSuppose you need to create a symbolic representation of the NASA7 thermodynamic parameterization of molecular nitrogen, species of index 47 in the GRI-Mech 3.0 mechanism shipped with Cantera. Let’s start by loading and retrieving the thermodynamic data for this species.
gas = ct.Solution("gri30.yaml")
species = gas.species()[47]
thermo = species.thermoBelow we instantiate SymbolicThermo with the input data of the species. It is important to emphasize here that when building more complex systems, generally one should share the symbolic variables, e.g. temperature, across the different components of the system. For this reason, the constructor of symbolic thermodynamic classes expect the temperature symbol to be provided, instead of trying to create it internally; this is a design choice that will improve code robustness when handling mixtures and avoid many of the pitfalls of symbolic programming.
T = SX.sym("T")
nasa7 = symbolic_thermo_factory(species, T)You could instead directly instantiate Nasa7Thermo with the input data, but using the factory method SymbolicThermo.from_species is a better choice, as it will allow you to easily switch to a different thermodynamic model. Furthermore, it is the safe choice for loading entire databases.
nasa7 = Nasa7Thermo(T, species.thermo.input_data)
# This is just syntactic sugar for the above:
nasa7 = Nasa7Thermo.from_species(species, T)Here we illustrate the use of algorithmic differentiation to compute the derivative of the specific enthalpy with respect to temperature.
hdot = Function("hdot", [T], [jacobian(nasa7.h(T), T)])The above derivative is then confronted to the specific heat below:

Finally, we compare the deviations between symbolic and numerical (Cantera) evaluation of the thermodynamic functions. This is not for error analysis, but a proof of correctness of the symbolic implementation.
# XXX: Cantera evaluates quantities per kmol! Divide by 1000 to get
# per mol, as the symbolic implementation does.
c_ct = np.array([0.001 * species.thermo.cp(T) for T in T_num])
h_ct = np.array([0.001 * species.thermo.h(T) for T in T_num])
s_ct = np.array([0.001 * species.thermo.s(T) for T in T_num])
error_c = np.mean(np.abs(nasa7._cp(T_num) - c_ct))
error_h = np.mean(np.abs(nasa7._h(T_num) - h_ct))
error_s = np.mean(np.abs(nasa7._s(T_num) - s_ct))
error_c, error_h, error_s(np.float64(4.334310688136611e-15),
np.float64(6.311893230304122e-12),
np.float64(3.2400748750660566e-14))
Nasa7Thermo(
T : SX,
input_data : dict[str, typing.Any]
) -> None:NASA7 thermodynamic parameterization.
This class does not implement Horner polynomial evaluation, as the main use case is to create symbolic expressions that are then evaluated by CasADi, which can handle polynomial evaluation efficiently. This is intentional and allows for easy verification.
It aims at providing a similar interface as SpeciesThermo from Cantera, from which it retrieves the data. That means, molar properties are provided through cp, h, and s properties.
T : SXinput_data : dict[str, typing.Any]SpeciesThermo property input_data.
specific_heat(
T : Any,
a : list[float],
symbolic : bool = False
) -> casadi.casadi.Function | casadi.casadi.SX:Compose NASA7 specific heat parameterization.
T : Anya : list[float]symbolic : bool = Falseenthalpy(
T : Any,
a : list[float],
symbolic : bool = False
) -> casadi.casadi.Function | casadi.casadi.SX:Compose NASA7 specific enthalpy parameterization.
T : Anya : list[float]symbolic : bool = Falseentropy(
T : Any,
a : list[float],
symbolic : bool = False
) -> casadi.casadi.Function | casadi.casadi.SX:Compose NASA7 specific entropy parameterization.
T : Anya : list[float]symbolic : bool = Falsecompose(
cls : Any,
name : str,
T : casadi.casadi.SX | numpy.ndarray[tuple[typing.Any, ...], numpy.dtype[numpy.float64]] | float,
data : list[list[float]],
symbolic : bool = False
) -> list[casadi.casadi.Function | casadi.casadi.SX | numpy.ndarray[tuple[typing.Any, ...], numpy.dtype[numpy.float64]] | float]:Compose a list of NASA7 functions for given data.
name : strT : casadi.casadi.SX | numpy.ndarray[tuple[typing.Any, ...], numpy.dtype[numpy.float64]] | floatdata : list[list[float]]symbolic : bool = FalseT is symbolic.
from_species(
cls : Any,
species : Species,
T : SX
) -> typing.Self:Create a Nasa7Thermo object from a Cantera species.
species : SpeciesT : SXsymbolic_transport_factory(
species : Species,
T : SX
) -> AbstractSymbolicTransport:Create an AbstractSymbolicTransport object.
species : SpeciesT : SX