1# This file is part of Cantera. See License.txt in the top-level directory or
2# at https://cantera.org/license.txt for license and copyright information.
3
4cdef extern from "cantera/thermo/speciesThermoTypes.h" namespace "Cantera":
5    cdef int SPECIES_THERMO_CONSTANT_CP "CONSTANT_CP"
6    cdef int SPECIES_THERMO_NASA2 "NASA2"
7    cdef int SPECIES_THERMO_SHOMATE2 "SHOMATE2"
8    cdef int SPECIES_THERMO_NASA9MULTITEMP "NASA9MULTITEMP"
9    cdef int SPECIES_THERMO_MU0_INTERP "MU0_INTERP"
10
11
12cdef class SpeciesThermo:
13    """
14    Base class for representing the reference-state thermodynamic properties of
15    a pure species. These properties are a function of temperature. Derived
16    classes implement a parameterization of this temperature dependence. This is
17    a wrapper for the C++ class :ct:`SpeciesThermoInterpType`.
18
19    :param T_low:
20        The minimum temperature [K] at which the parameterization is valid
21    :param T_high:
22        The maximum temperature [K] at which the parameterization is valid
23    :param P_ref:
24        The reference pressure [Pa] for the parameterization
25    :param coeffs:
26        An array of coefficients for the parameterization. The length of this
27        array and the meaning of each element depends on the specific
28        parameterization.
29    """
30    def __cinit__(self, T_low=None, T_high=None, P_ref=None, coeffs=None, *args,
31                  init=True, **kwargs):
32        if not init:
33            return
34
35        if not self._check_n_coeffs(len(coeffs)):
36            raise ValueError("Coefficient array has incorrect length")
37        cdef np.ndarray[np.double_t, ndim=1] data = np.ascontiguousarray(
38            coeffs, dtype=np.double)
39        self._spthermo.reset(CxxNewSpeciesThermo(self.derived_type, T_low,
40                                                 T_high, P_ref, &data[0]))
41        self.spthermo = self._spthermo.get()
42
43    cdef _assign(self, shared_ptr[CxxSpeciesThermo] other):
44        self._spthermo = other
45        self.spthermo = self._spthermo.get()
46
47    property min_temp:
48        """ Minimum temperature [K] at which the parameterization is valid."""
49        def __get__(self):
50            return self.spthermo.minTemp()
51
52    property max_temp:
53        """ Maximum temperature [K] at which the parameterization is valid."""
54        def __get__(self):
55            return self.spthermo.maxTemp()
56
57    property reference_pressure:
58        """ Reference pressure [Pa] for the parameterization."""
59        def __get__(self):
60            return self.spthermo.refPressure()
61
62    property n_coeffs:
63        """ Number of parameters for the parameterization."""
64        def __get__(self):
65            return self.spthermo.nCoeffs()
66
67    property coeffs:
68        """
69        Array of coefficients for the parameterization. The length of this
70        array and the meaning of each element depends on the specific
71        parameterization.
72        """
73        def __get__(self):
74            cdef size_t index = 0
75            cdef int thermo_type = 0
76            cdef double T_low = 0, T_high = 0, P_ref = 0
77            cdef np.ndarray[np.double_t, ndim=1] data = np.empty(self.n_coeffs)
78            self.spthermo.reportParameters(index, thermo_type, T_low,
79                                           T_high, P_ref, &data[0])
80            return data
81
82    def _check_n_coeffs(self, n):
83        """
84        Check whether number of coefficients is compatible with a given
85        parameterization prior to instantiation of the underlying C++ object.
86        """
87        raise NotImplementedError('Needs to be overloaded')
88
89    property input_data:
90        def __get__(self):
91            return anymap_to_dict(self.spthermo.parameters(True))
92
93    def update_user_data(self, data):
94        """
95        Add the contents of the provided `dict` as additional fields when generating
96        YAML phase definition files with `Solution.write_yaml` or in the data returned
97        by `input_data`. Existing keys with matching names are overwritten.
98        """
99        self.spthermo.input().update(dict_to_anymap(data), False)
100
101    def clear_user_data(self):
102        """
103        Clear all saved input data, so that the data given by `input_data` or
104        `Solution.write_yaml` will only include values generated by Cantera based on
105        the current object state.
106        """
107        self.spthermo.input().clear()
108
109    def cp(self, T):
110        """
111        Molar heat capacity at constant pressure [J/kmol/K] at temperature *T*.
112        """
113        cdef double cp_r, h_rt, s_r
114        self.spthermo.updatePropertiesTemp(T, &cp_r, &h_rt, &s_r)
115        return cp_r * gas_constant
116
117    def h(self, T):
118        """ Molar enthalpy [J/kmol] at temperature *T* """
119        cdef double cp_r, h_rt, s_r
120        self.spthermo.updatePropertiesTemp(T, &cp_r, &h_rt, &s_r)
121        return h_rt * gas_constant * T
122
123    def s(self, T):
124        """ Molar entropy [J/kmol/K] at temperature *T* """
125        cdef double cp_r, h_rt, s_r
126        self.spthermo.updatePropertiesTemp(T, &cp_r, &h_rt, &s_r)
127        return s_r * gas_constant
128
129
130cdef class ConstantCp(SpeciesThermo):
131    r"""
132    Thermodynamic properties for a species that has a constant specific heat
133    capacity. This is a wrapper for the C++ class :ct:`ConstCpPoly`.
134
135    :param coeffs:
136        An array of 4 elements:
137
138             - `coeffs[0]` = :math:`T_0` [K]
139             - `coeffs[1]` = :math:`H^o(T_0, p_{ref})` [J/kmol]
140             - `coeffs[2]` = :math:`S^o(T_0, p_{ref})` [J/kmol-K]
141             - `coeffs[3]` = :math:`c_p^o(T_0, p_{ref})` [J/kmol-K]
142    """
143    derived_type = SPECIES_THERMO_CONSTANT_CP
144
145    def _check_n_coeffs(self, n):
146        return n == 4
147
148
149cdef class Mu0Poly(SpeciesThermo):
150    """
151    Thermodynamic properties for a species which is parameterized using an
152    interpolation of the Gibbs free energy based on a piecewise constant heat
153    capacity approximation. This is a wrapper for the C++ class :ct:`Mu0Poly`.
154
155    :param coeffs:
156        An array of 2+2*npoints elements, in the following order:
157
158            - `coeffs[0]`: number of points (integer)
159            - `coeffs[1]`: h^o(298.15 K) [J/kmol]
160            - `coeffs[2]`: T_1 [Kelvin]
161            - `coeffs[3]`: \mu^o(T_1) [J/kmol]
162            - `coeffs[4]`: T_2 [Kelvin]
163            - `coeffs[5]`: \mu^o(T_2) [J/kmol]
164            - ...
165    """
166    derived_type = SPECIES_THERMO_MU0_INTERP
167
168    def _check_n_coeffs(self, n):
169        return n > 3 and n % 2 == 0
170
171
172cdef class NasaPoly2(SpeciesThermo):
173    """
174    Thermodynamic properties for a species which is parameterized using the
175    7-coefficient NASA polynomial form in two temperature ranges. This is a
176    wrapper for the C++ class :ct:`NasaPoly2`.
177
178    :param coeffs:
179        An array of 15 elements, in the following order:
180
181            - `coeffs[0]`: The mid-point temperature [K] between the two
182              parameterizations
183            - `coeffs[1:8]`: The 7 coefficients of the high-temperature
184              parameterization
185            - `coeffs[8:15]`: The 7 coefficients of the low-temperature
186              parameterization
187
188        This is the coefficient order used in the standard fixed-format NASA
189        input files.
190    """
191    derived_type = SPECIES_THERMO_NASA2
192
193    def _check_n_coeffs(self, n):
194        return n == 15
195
196
197cdef class Nasa9PolyMultiTempRegion(SpeciesThermo):
198    """
199    Thermodynamic properties for a species which is parameterized using the
200    9-coefficient NASA polynomial form encompassing multiple temperature ranges.
201    This is a wrapper for the C++ class :ct:`Nasa9PolyMultiTempRegion`.
202
203    :param coeffs:
204        An array of 1 + 11*`nzones` elements, in the following order:
205
206            - `coeffs[0]`: Number of zones (`nzones`)
207            - `coeffs[1 + 11*zone]`: minimum temperature within zone
208            - `coeffs[2 + 11*zone]`: maximum temperature within zone
209            - `coeffs[3:11 + 11*zone]`: 9 coefficients of the parameterization
210
211        where `zone` runs from zero to `nzones`-1.
212    """
213    derived_type = SPECIES_THERMO_NASA9MULTITEMP
214
215    def _check_n_coeffs(self, n):
216        return n > 11 and ((n - 1) % 11) == 0
217
218
219cdef class ShomatePoly2(SpeciesThermo):
220    """
221    Thermodynamic properties for a species which is parameterized using the
222    Shomate equation in two temperature ranges. This is a wrapper for the C++
223    class :ct:`ShomatePoly2`.
224
225    :param coeffs:
226        An array of 15 elements, in the following order:
227
228            - `coeffs[0]`: The mid-point temperature [K] between the two
229              parameterizations
230            - `coeffs[1:8]`: The 7 coefficients of the low-temperature
231              parameterization
232            - `coeffs[8:15]`: The 7 coefficients of the high-temperature
233              parameterization
234
235        These coefficients should be provided in their customary units (i.e.
236        such that :math:`c_p^o` is in J/gmol-K and :math:`H^o` is in kJ/gmol,
237        as in the NIST Chemistry WebBook).
238    """
239    derived_type = SPECIES_THERMO_SHOMATE2
240
241    def _check_n_coeffs(self, n):
242        return n == 15
243
244
245cdef wrapSpeciesThermo(shared_ptr[CxxSpeciesThermo] spthermo):
246    """
247    Wrap a C++ SpeciesThermoInterpType object with a Python object of the
248    correct derived type.
249    """
250    cdef int thermo_type = spthermo.get().reportType()
251
252    if thermo_type == SPECIES_THERMO_NASA2:
253        st = NasaPoly2(init=False)
254    elif thermo_type == SPECIES_THERMO_NASA9MULTITEMP:
255        st = Nasa9PolyMultiTempRegion(init=False)
256    elif thermo_type == SPECIES_THERMO_CONSTANT_CP:
257        st = ConstantCp(init=False)
258    elif thermo_type == SPECIES_THERMO_MU0_INTERP:
259        st = Mu0Poly(init=False)
260    elif thermo_type == SPECIES_THERMO_SHOMATE2:
261        st = ShomatePoly2(init=False)
262    else:
263        st = SpeciesThermo()
264
265    st._assign(spthermo)
266    return st
267