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 4from ctypes import c_int 5 6# NOTE: These cdef functions cannot be members of Kinetics because they would 7# cause "layout conflicts" when creating derived classes with multiple bases, 8# e.g. class Solution. [Cython 0.16] 9cdef np.ndarray get_species_array(Kinetics kin, kineticsMethod1d method): 10 cdef np.ndarray[np.double_t, ndim=1] data = np.empty(kin.n_total_species) 11 method(kin.kinetics, &data[0]) 12 # @TODO: Fix _selected_species to work with interface kinetics 13 if kin._selected_species.size: 14 return data[kin._selected_species] 15 else: 16 return data 17 18cdef np.ndarray get_reaction_array(Kinetics kin, kineticsMethod1d method): 19 cdef np.ndarray[np.double_t, ndim=1] data = np.empty(kin.n_reactions) 20 method(kin.kinetics, &data[0]) 21 return data 22 23cdef np.ndarray get_dense(Kinetics kin, kineticsMethodSparse method): 24 cdef CxxSparseMatrix smat = method(kin.kinetics) 25 cdef size_t length = smat.nonZeros() 26 if length == 0: 27 return np.zeros((kin.n_reactions, 0)) 28 29 # index/value triplets 30 cdef np.ndarray[int, ndim=1, mode="c"] rows = np.empty(length, dtype=c_int) 31 cdef np.ndarray[int, ndim=1, mode="c"] cols = np.empty(length, dtype=c_int) 32 cdef np.ndarray[np.double_t, ndim=1] data = np.empty(length) 33 34 size = CxxSparseTriplets(smat, &rows[0], &cols[0], &data[0], length) 35 out = np.zeros((smat.rows(), smat.cols())) 36 for i in xrange(length): 37 out[rows[i], cols[i]] = data[i] 38 return out 39 40cdef tuple get_sparse(Kinetics kin, kineticsMethodSparse method): 41 # retrieve sparse matrix 42 cdef CxxSparseMatrix smat = method(kin.kinetics) 43 44 # pointers to values and inner indices of CSC storage 45 cdef size_t length = smat.nonZeros() 46 cdef np.ndarray[np.double_t, ndim=1] value = np.empty(length) 47 cdef np.ndarray[int, ndim=1, mode="c"] inner = np.empty(length, dtype=c_int) 48 49 # pointers outer indices of CSC storage 50 cdef size_t ncols = smat.outerSize() 51 cdef np.ndarray[int, ndim=1, mode="c"] outer = np.empty(ncols + 1, dtype=c_int) 52 53 CxxSparseCscData(smat, &value[0], &inner[0], &outer[0]) 54 return value, inner, outer 55 56 57cdef class Kinetics(_SolutionBase): 58 """ 59 Instances of class `Kinetics` are responsible for evaluating reaction rates 60 of progress, species production rates, and other quantities pertaining to 61 a reaction mechanism. 62 """ 63 64 property kinetics_model: 65 """ 66 Return type of kinetics. 67 """ 68 def __get__(self): 69 return pystr(self.kinetics.kineticsType()) 70 71 property n_total_species: 72 """ 73 Total number of species in all phases participating in the kinetics 74 mechanism. 75 """ 76 def __get__(self): 77 return self.kinetics.nTotalSpecies() 78 79 property n_reactions: 80 """Number of reactions in the reaction mechanism.""" 81 def __get__(self): 82 return self.kinetics.nReactions() 83 84 property n_phases: 85 """Number of phases in the reaction mechanism.""" 86 def __get__(self): 87 return self.kinetics.nPhases() 88 89 property reaction_phase_index: 90 """The index of the phase where the reactions occur.""" 91 def __get__(self): 92 return self.kinetics.reactionPhaseIndex() 93 94 def _check_phase_index(self, n): 95 if not 0 <= n < self.n_phases: 96 raise ValueError("Phase index ({0}) out of range".format(n)) 97 98 def _check_reaction_index(self, n): 99 if not 0 <= n < self.n_reactions: 100 raise ValueError("Reaction index ({0}) out of range".format(n)) 101 102 def _check_kinetics_species_index(self, n): 103 if not 0 <= n < self.n_total_species: 104 raise ValueError("Kinetics Species index ({0}) out of range".format(n)) 105 106 def kinetics_species_index(self, species, int phase=0): 107 """ 108 The index of species *species* of phase *phase* within arrays returned 109 by methods of class `Kinetics`. If *species* is a string, the *phase* 110 argument is unused. 111 """ 112 cdef int k 113 if isinstance(species, (str, bytes)): 114 return self.kinetics.kineticsSpeciesIndex(stringify(species)) 115 else: 116 k = species 117 self._check_kinetics_species_index(k) 118 self._check_phase_index(k) 119 return self.kinetics.kineticsSpeciesIndex(k, phase) 120 121 def kinetics_species_name(self, k): 122 """ 123 Name of the species with index *k* in the arrays returned by methods 124 of class `Kinetics`. 125 """ 126 return pystr(self.kinetics.kineticsSpeciesName(k)) 127 128 property kinetics_species_names: 129 """ 130 A list of all species names, corresponding to the arrays returned by 131 methods of class `Kinetics`. 132 """ 133 def __get__(self): 134 return [self.kinetics_species_name(k) 135 for k in range(self.n_total_species)] 136 137 def reaction(self, int i_reaction): 138 """ 139 Return a `Reaction` object representing the reaction with index 140 ``i_reaction``. Changes to this object do not affect the `Kinetics` or 141 `Solution` object until the `modify_reaction` function is called. 142 """ 143 return Reaction.wrap(self.kinetics.reaction(i_reaction)) 144 145 def reactions(self): 146 """ 147 Return a list of all `Reaction` objects. Changes to these objects do not 148 affect the `Kinetics` or `Solution` object until the `modify_reaction` 149 function is called. 150 """ 151 return [self.reaction(i) for i in range(self.n_reactions)] 152 153 def modify_reaction(self, int irxn, Reaction rxn): 154 """ 155 Modify the `Reaction` with index ``irxn`` to have the same rate 156 parameters as ``rxn``. ``rxn`` must have the same reactants and products 157 and be of the same type (i.e. `ElementaryReaction`, `FalloffReaction`, 158 `PlogReaction`, etc.) as the existing reaction. This method does not 159 modify the third-body efficiencies, reaction orders, or reversibility of 160 the reaction. 161 """ 162 self.kinetics.modifyReaction(irxn, rxn._reaction) 163 164 def add_reaction(self, Reaction rxn): 165 """ Add a new reaction to this phase. """ 166 self.kinetics.addReaction(rxn._reaction) 167 168 def is_reversible(self, int i_reaction): 169 """True if reaction `i_reaction` is reversible.""" 170 self._check_reaction_index(i_reaction) 171 return self.kinetics.isReversible(i_reaction) 172 173 def multiplier(self, int i_reaction): 174 """ 175 A scaling factor applied to the rate coefficient for reaction 176 *i_reaction*. Can be used to carry out sensitivity analysis or to 177 selectively disable a particular reaction. See `set_multiplier`. 178 """ 179 self._check_reaction_index(i_reaction) 180 return self.kinetics.multiplier(i_reaction) 181 182 def set_multiplier(self, double value, int i_reaction=-1): 183 """ 184 Set the multiplier for for reaction *i_reaction* to *value*. 185 If *i_reaction* is not specified, then the multiplier for all reactions 186 is set to *value*. See `multiplier`. 187 """ 188 if i_reaction == -1: 189 for i_reaction in range(self.n_reactions): 190 self.kinetics.setMultiplier(i_reaction, value) 191 else: 192 self._check_reaction_index(i_reaction) 193 self.kinetics.setMultiplier(i_reaction, value) 194 195 def reaction_type(self, int i_reaction): 196 """ 197 Type code of reaction *i_reaction*. 198 199 .. deprecated:: 2.6 200 """ 201 warnings.warn("Behavior changes after Cantera 2.6, " 202 "when function will return a type string. " 203 "Use method 'reaction_type_str' during " 204 "transition instead.", DeprecationWarning) 205 self._check_reaction_index(i_reaction) 206 return self.kinetics.reactionType(i_reaction) 207 208 def reaction_type_str(self, int i_reaction): 209 """Type of reaction *i_reaction*.""" 210 self._check_reaction_index(i_reaction) 211 return pystr(self.kinetics.reactionTypeStr(i_reaction)) 212 213 def reaction_equation(self, int i_reaction): 214 """The equation for the specified reaction. See also `reaction_equations`.""" 215 self._check_reaction_index(i_reaction) 216 return pystr(self.kinetics.reactionString(i_reaction)) 217 218 def reactants(self, int i_reaction): 219 """The reactants portion of the reaction equation""" 220 self._check_reaction_index(i_reaction) 221 return pystr(self.kinetics.reactantString(i_reaction)) 222 223 def products(self, int i_reaction): 224 """The products portion of the reaction equation""" 225 self._check_reaction_index(i_reaction) 226 return pystr(self.kinetics.productString(i_reaction)) 227 228 def reaction_equations(self, indices=None): 229 """ 230 Returns a list containing the reaction equation for all reactions in the 231 mechanism (if *indices* is unspecified) or the equations for each 232 reaction in the sequence *indices*. For example:: 233 234 >>> gas.reaction_equations() 235 ['2 O + M <=> O2 + M', 'O + H + M <=> OH + M', 'O + H2 <=> H + OH', ...] 236 >>> gas.reaction_equations([2,3]) 237 ['O + H + M <=> OH + M', 'O + H2 <=> H + OH'] 238 239 See also `reaction_equation`. 240 """ 241 if indices is None: 242 return [self.reaction_equation(i) for i in range(self.n_reactions)] 243 else: 244 return [self.reaction_equation(i) for i in indices] 245 246 def reactant_stoich_coeff(self, k_spec, int i_reaction): 247 """ 248 The stoichiometric coefficient of species ``k_spec`` as a reactant in 249 reaction ``i_reaction``. 250 """ 251 cdef int k 252 if isinstance(k_spec, (str, bytes)): 253 k = self.kinetics_species_index(k_spec) 254 else: 255 k = k_spec 256 self._check_kinetics_species_index(k_spec) 257 258 self._check_reaction_index(i_reaction) 259 return self.kinetics.reactantStoichCoeff(k, i_reaction) 260 261 def product_stoich_coeff(self, k_spec, int i_reaction): 262 """ 263 The stoichiometric coefficient of species ``k_spec`` as a product in 264 reaction ``i_reaction``. 265 """ 266 cdef int k 267 if isinstance(k_spec, (str, bytes)): 268 k = self.kinetics_species_index(k_spec) 269 else: 270 k = k_spec 271 self._check_kinetics_species_index(k_spec) 272 273 self._check_reaction_index(i_reaction) 274 return self.kinetics.productStoichCoeff(k, i_reaction) 275 276 def reactant_stoich_coeffs(self): 277 """ 278 The array of reactant stoichiometric coefficients. Element *[k,i]* of 279 this array is the reactant stoichiometric coefficient of species *k* in 280 reaction *i*. 281 282 .. deprecated:: 2.6 283 284 Behavior to change after Cantera 2.6; for new behavior, see property 285 `Kinetics.reactant_stoich_coeffs3`. 286 """ 287 warnings.warn("Behavior to change after Cantera 2.6; for new behavior, see " 288 "property 'reactant_stoich_coeffs3'.", DeprecationWarning) 289 return self.reactant_stoich_coeffs3 290 291 property reactant_stoich_coeffs3: 292 """ 293 The array of reactant stoichiometric coefficients. Element ``[k,i]`` of 294 this array is the reactant stoichiometric coefficient of species ``k`` in 295 reaction ``i``. 296 297 For sparse output, set ``ct.use_sparse(True)``. 298 """ 299 def __get__(self): 300 if __use_sparse__: 301 tup = get_sparse(self, kin_reactantStoichCoeffs) 302 shape = self.n_total_species, self.n_reactions 303 return _scipy_sparse.csc_matrix(tup, shape=shape) 304 return get_dense(self, kin_reactantStoichCoeffs) 305 306 def product_stoich_coeffs(self): 307 """ 308 The array of product stoichiometric coefficients. Element *[k,i]* of 309 this array is the product stoichiometric coefficient of species *k* in 310 reaction *i*. 311 312 .. deprecated:: 2.6 313 314 Behavior to change after Cantera 2.6; for new behavior, see property 315 `Kinetics.reactant_stoich_coeffs3`. 316 """ 317 warnings.warn("Behavior to change after Cantera 2.6; for new behavior, see " 318 "property 'product_stoich_coeffs3'.", DeprecationWarning) 319 return self.product_stoich_coeffs3 320 321 property product_stoich_coeffs3: 322 """ 323 The array of product stoichiometric coefficients. Element ``[k,i]`` of 324 this array is the product stoichiometric coefficient of species ``k`` in 325 reaction ``i``. 326 327 For sparse output, set ``ct.use_sparse(True)``. 328 """ 329 def __get__(self): 330 if __use_sparse__: 331 tup = get_sparse(self, kin_productStoichCoeffs) 332 shape = self.n_total_species, self.n_reactions 333 return _scipy_sparse.csc_matrix(tup, shape=shape) 334 return get_dense(self, kin_productStoichCoeffs) 335 336 property product_stoich_coeffs_reversible: 337 """ 338 The array of product stoichiometric coefficients of reversible reactions. 339 Element ``[k,i]`` of this array is the product stoichiometric coefficient 340 of species ``k`` in reaction ``i``. 341 342 For sparse output, set ``ct.use_sparse(True)``. 343 """ 344 def __get__(self): 345 if __use_sparse__: 346 tup = get_sparse(self, kin_revProductStoichCoeffs) 347 shape = self.n_total_species, self.n_reactions 348 return _scipy_sparse.csc_matrix(tup, shape=shape) 349 return get_dense(self, kin_revProductStoichCoeffs) 350 351 property forward_rates_of_progress: 352 """ 353 Forward rates of progress for the reactions. [kmol/m^3/s] for bulk 354 phases or [kmol/m^2/s] for surface phases. 355 """ 356 def __get__(self): 357 return get_reaction_array(self, kin_getFwdRatesOfProgress) 358 359 property reverse_rates_of_progress: 360 """ 361 Reverse rates of progress for the reactions. [kmol/m^3/s] for bulk 362 phases or [kmol/m^2/s] for surface phases. 363 """ 364 def __get__(self): 365 return get_reaction_array(self, kin_getRevRatesOfProgress) 366 367 property net_rates_of_progress: 368 """ 369 Net rates of progress for the reactions. [kmol/m^3/s] for bulk phases 370 or [kmol/m^2/s] for surface phases. 371 """ 372 def __get__(self): 373 return get_reaction_array(self, kin_getNetRatesOfProgress) 374 375 property equilibrium_constants: 376 """Equilibrium constants in concentration units for all reactions.""" 377 def __get__(self): 378 return get_reaction_array(self, kin_getEquilibriumConstants) 379 380 property forward_rate_constants: 381 """ 382 Forward rate constants for all reactions. The computed values include 383 all temperature-dependent, pressure-dependent, and third body 384 contributions. Units are a combination of kmol, m^3 and s, that depend 385 on the rate expression for the reaction. 386 387 .. deprecated:: 2.6 388 389 Behavior to change after Cantera 2.6; for Cantera 2.6, rate constants of 390 three-body reactions are multiplied with third-body concentrations 391 (no change to legacy behavior). After Cantera 2.6, results will no longer 392 include third-body concentrations and be consistent with conventional 393 definitions (see Eq. 9.75 in Kee, Coltrin and Glarborg, 'Chemically 394 Reacting Flow', Wiley Interscience, 2003). 395 To switch to new behavior, run 'cantera.use_legacy_rate_constants(False)'. 396 """ 397 def __get__(self): 398 return get_reaction_array(self, kin_getFwdRateConstants) 399 400 property reverse_rate_constants: 401 """ 402 Reverse rate constants for all reactions. The computed values include 403 all temperature-dependent, pressure-dependent, and third body 404 contributions. Units are a combination of kmol, m^3 and s, that depend 405 on the rate expression for the reaction. 406 407 .. deprecated:: 2.6 408 409 Behavior to change after Cantera 2.6; for Cantera 2.6, rate constants of 410 three-body reactions are multiplied with third-body concentrations 411 (no change to legacy behavior). After Cantera 2.6, results will no longer 412 include third-body concentrations and be consistent with conventional 413 definitions (see Eq. 9.75 in Kee, Coltrin and Glarborg, 'Chemically 414 Reacting Flow', Wiley Interscience, 2003). 415 To switch to new behavior, run 'cantera.use_legacy_rate_constants(False)'. 416 """ 417 def __get__(self): 418 return get_reaction_array(self, kin_getRevRateConstants) 419 420 property creation_rates: 421 """ 422 Creation rates for each species. [kmol/m^3/s] for bulk phases or 423 [kmol/m^2/s] for surface phases. 424 """ 425 def __get__(self): 426 return get_species_array(self, kin_getCreationRates) 427 428 property destruction_rates: 429 """ 430 Destruction rates for each species. [kmol/m^3/s] for bulk phases or 431 [kmol/m^2/s] for surface phases. 432 """ 433 def __get__(self): 434 return get_species_array(self, kin_getDestructionRates) 435 436 property net_production_rates: 437 """ 438 Net production rates for each species. [kmol/m^3/s] for bulk phases or 439 [kmol/m^2/s] for surface phases. 440 """ 441 def __get__(self): 442 return get_species_array(self, kin_getNetProductionRates) 443 444 property delta_enthalpy: 445 """Change in enthalpy for each reaction [J/kmol].""" 446 def __get__(self): 447 return get_reaction_array(self, kin_getDeltaEnthalpy) 448 449 property delta_gibbs: 450 """Change in Gibbs free energy for each reaction [J/kmol].""" 451 def __get__(self): 452 return get_reaction_array(self, kin_getDeltaGibbs) 453 454 property delta_entropy: 455 """Change in entropy for each reaction [J/kmol/K].""" 456 def __get__(self): 457 return get_reaction_array(self, kin_getDeltaEntropy) 458 459 property delta_standard_enthalpy: 460 """ 461 Change in standard-state enthalpy (independent of composition) for 462 each reaction [J/kmol]. 463 """ 464 def __get__(self): 465 return get_reaction_array(self, kin_getDeltaSSEnthalpy) 466 467 property delta_standard_gibbs: 468 """ 469 Change in standard-state Gibbs free energy (independent of composition) 470 for each reaction [J/kmol]. 471 """ 472 def __get__(self): 473 return get_reaction_array(self, kin_getDeltaSSGibbs) 474 475 property delta_standard_entropy: 476 """ 477 Change in standard-state entropy (independent of composition) for 478 each reaction [J/kmol/K]. 479 """ 480 def __get__(self): 481 return get_reaction_array(self, kin_getDeltaSSEntropy) 482 483 property heat_release_rate: 484 """ 485 Get the total volumetric heat release rate [W/m^3]. 486 """ 487 def __get__(self): 488 return - np.sum(self.partial_molar_enthalpies * 489 self.net_production_rates, 0) 490 491 property heat_production_rates: 492 """ 493 Get the volumetric heat production rates [W/m^3] on a per-reaction 494 basis. The sum over all reactions results in the total volumetric heat 495 release rate. 496 Example: C. K. Law: Combustion Physics (2006), Fig. 7.8.6 497 498 >>> gas.heat_production_rates[1] # heat production rate of the 2nd reaction 499 """ 500 def __get__(self): 501 return - self.net_rates_of_progress * self.delta_enthalpy 502 503 504cdef class InterfaceKinetics(Kinetics): 505 """ 506 A kinetics manager for heterogeneous reaction mechanisms. The 507 reactions are assumed to occur at an interface between bulk phases. 508 """ 509 def __init__(self, infile='', name='', adjacent=(), *args, **kwargs): 510 super().__init__(infile, name, adjacent, *args, **kwargs) 511 if pystr(self.kinetics.kineticsType()) not in ("Surf", "Edge"): 512 raise TypeError("Underlying Kinetics class is not of the correct type.") 513 514 self._phase_indices = {} 515 for phase in [self] + list(adjacent): 516 i = self.kinetics.phaseIndex(stringify(phase.name)) 517 self._phase_indices[phase] = i 518 self._phase_indices[phase.name] = i 519 self._phase_indices[i] = i 520 521 def advance_coverages(self, double dt, double rtol=1e-7, double atol=1e-14, 522 double max_step_size=0, int max_steps=20000, 523 int max_error_test_failures=7): 524 """ 525 This method carries out a time-accurate advancement of the surface 526 coverages for a specified amount of time. 527 """ 528 (<CxxInterfaceKinetics*>self.kinetics).advanceCoverages( 529 dt, rtol, atol, max_step_size, max_steps, max_error_test_failures) 530 531 def advance_coverages_to_steady_state(self): 532 """ 533 This method advances the surface coverages to steady state. 534 """ 535 (<CxxInterfaceKinetics*>self.kinetics).solvePseudoSteadyStateProblem() 536 537 def phase_index(self, phase): 538 """ 539 Get the index of the phase *phase*, where *phase* may specified using 540 the phase object, the name, or the index itself. 541 """ 542 return self._phase_indices[phase] 543 544 def _phase_slice(self, phase): 545 p = self.phase_index(phase) 546 k1 = self.kinetics_species_index(0, p) 547 548 if p == self.n_phases - 1: 549 k2 = self.n_total_species 550 else: 551 k2 = self.kinetics_species_index(0, p+1) 552 553 return slice(k1,k2) 554 555 def get_creation_rates(self, phase): 556 """ 557 Creation rates for each species in phase *phase*. Use the 558 `creation_rates` property to get the creation rates for species in all 559 phases. 560 """ 561 return self.creation_rates[self._phase_slice(phase)] 562 563 def get_destruction_rates(self, phase): 564 """ 565 Destruction rates for each species in phase *phase*. Use the 566 `destruction_rates` property to get the destruction rates for species 567 in all phases. 568 """ 569 return self.destruction_rates[self._phase_slice(phase)] 570 571 def get_net_production_rates(self, phase): 572 """ 573 Net production rates for each species in phase *phase*. Use the 574 `net_production_rates` property to get the net_production rates for 575 species in all phases. 576 """ 577 return self.net_production_rates[self._phase_slice(phase)] 578 579 def write_yaml(self, filename, phases=None, units=None, precision=None, 580 skip_user_defined=None): 581 """ 582 See `_SolutionBase.write_yaml`. 583 """ 584 if phases is not None: 585 phases = list(phases) 586 else: 587 phases = [] 588 589 for phase in self._phase_indices: 590 if isinstance(phase, _SolutionBase) and phase is not self: 591 phases.append(phase) 592 593 super().write_yaml(filename, phases, units, precision, skip_user_defined) 594