1 /** 2 * @file IdealGasPhase.h 3 * ThermoPhase object for the ideal gas equation of 4 * state - workhorse for %Cantera (see \ref thermoprops 5 * and class \link Cantera::IdealGasPhase IdealGasPhase\endlink). 6 */ 7 8 // This file is part of Cantera. See License.txt in the top-level directory or 9 // at https://cantera.org/license.txt for license and copyright information. 10 11 #ifndef CT_IDEALGASPHASE_H 12 #define CT_IDEALGASPHASE_H 13 14 #include "ThermoPhase.h" 15 16 namespace Cantera 17 { 18 19 //! Class IdealGasPhase represents low-density gases that obey the ideal gas 20 //! equation of state. 21 /*! 22 * 23 * IdealGasPhase derives from class ThermoPhase, and overloads the virtual 24 * methods defined there with ones that use expressions appropriate for ideal 25 * gas mixtures. 26 * 27 * The independent unknowns are density, mass fraction, and temperature. the 28 * #setPressure() function will calculate the density consistent with the 29 * current mass fraction vector and temperature and the desired pressure, and 30 * then set the density. 31 * 32 * ## Specification of Species Standard State Properties 33 * 34 * It is assumed that the reference state thermodynamics may be obtained by a 35 * pointer to a populated species thermodynamic property manager class in the 36 * base class, ThermoPhase::m_spthermo (see the base class \link 37 * Cantera::MultiSpeciesThermo MultiSpeciesThermo \endlink for a description of 38 * the specification of reference state species thermodynamics functions). The 39 * reference state, where the pressure is fixed at a single pressure, is a key 40 * species property calculation for the Ideal Gas Equation of state. 41 * 42 * This class is optimized for speed of execution. All calls to thermodynamic 43 * functions first call internal routines (aka #enthalpy_RT_ref()) which return 44 * references the reference state thermodynamics functions. Within these 45 * internal reference state functions, the function #_updateThermo() is called, 46 * that first checks to see whether the temperature has changed. If it has, it 47 * updates the internal reference state thermo functions by calling the 48 * MultiSpeciesThermo object. 49 * 50 * Functions for the calculation of standard state properties for species at 51 * arbitrary pressure are provided in IdealGasPhase. However, they are all 52 * derived from their reference state counterparts. 53 * 54 * The standard state enthalpy is independent of pressure: 55 * 56 * \f[ 57 * h^o_k(T,P) = h^{ref}_k(T) 58 * \f] 59 * 60 * The standard state constant-pressure heat capacity is independent of pressure: 61 * 62 * \f[ 63 * Cp^o_k(T,P) = Cp^{ref}_k(T) 64 * \f] 65 * 66 * The standard state entropy depends in the following fashion on pressure: 67 * 68 * \f[ 69 * S^o_k(T,P) = S^{ref}_k(T) - R \ln(\frac{P}{P_{ref}}) 70 * \f] 71 * The standard state Gibbs free energy is obtained from the enthalpy and entropy 72 * functions: 73 * 74 * \f[ 75 * \mu^o_k(T,P) = h^o_k(T,P) - S^o_k(T,P) T 76 * \f] 77 * 78 * \f[ 79 * \mu^o_k(T,P) = \mu^{ref}_k(T) + R T \ln( \frac{P}{P_{ref}}) 80 * \f] 81 * 82 * where 83 * \f[ 84 * \mu^{ref}_k(T) = h^{ref}_k(T) - T S^{ref}_k(T) 85 * \f] 86 * 87 * The standard state internal energy is obtained from the enthalpy function also 88 * 89 * \f[ 90 * u^o_k(T,P) = h^o_k(T) - R T 91 * \f] 92 * 93 * The molar volume of a species is given by the ideal gas law 94 * 95 * \f[ 96 * V^o_k(T,P) = \frac{R T}{P} 97 * \f] 98 * 99 * where R is the molar gas constant. For a complete list of physical constants 100 * used within %Cantera, see \ref physConstants . 101 * 102 * ## Specification of Solution Thermodynamic Properties 103 * 104 * The activity of a species defined in the phase is given by the ideal gas law: 105 * \f[ 106 * a_k = X_k 107 * \f] 108 * where \f$ X_k \f$ is the mole fraction of species *k*. The chemical potential 109 * for species *k* is equal to 110 * 111 * \f[ 112 * \mu_k(T,P) = \mu^o_k(T, P) + R T \log(X_k) 113 * \f] 114 * 115 * In terms of the reference state, the above can be rewritten 116 * 117 * \f[ 118 * \mu_k(T,P) = \mu^{ref}_k(T, P) + R T \log(\frac{P X_k}{P_{ref}}) 119 * \f] 120 * 121 * The partial molar entropy for species *k* is given by the following relation, 122 * 123 * \f[ 124 * \tilde{s}_k(T,P) = s^o_k(T,P) - R \log(X_k) = s^{ref}_k(T) - R \log(\frac{P X_k}{P_{ref}}) 125 * \f] 126 * 127 * The partial molar enthalpy for species *k* is 128 * 129 * \f[ 130 * \tilde{h}_k(T,P) = h^o_k(T,P) = h^{ref}_k(T) 131 * \f] 132 * 133 * The partial molar Internal Energy for species *k* is 134 * 135 * \f[ 136 * \tilde{u}_k(T,P) = u^o_k(T,P) = u^{ref}_k(T) 137 * \f] 138 * 139 * The partial molar Heat Capacity for species *k* is 140 * 141 * \f[ 142 * \tilde{Cp}_k(T,P) = Cp^o_k(T,P) = Cp^{ref}_k(T) 143 * \f] 144 * 145 * ## %Application within Kinetics Managers 146 * 147 * \f$ C^a_k\f$ are defined such that \f$ a_k = C^a_k / C^s_k, \f$ where \f$ 148 * C^s_k \f$ is a standard concentration defined below and \f$ a_k \f$ are 149 * activities used in the thermodynamic functions. These activity (or 150 * generalized) concentrations are used by kinetics manager classes to compute 151 * the forward and reverse rates of elementary reactions. The activity 152 * concentration,\f$ C^a_k \f$,is given by the following expression. 153 * 154 * \f[ 155 * C^a_k = C^s_k X_k = \frac{P}{R T} X_k 156 * \f] 157 * 158 * The standard concentration for species *k* is independent of *k* and equal to 159 * 160 * \f[ 161 * C^s_k = C^s = \frac{P}{R T} 162 * \f] 163 * 164 * For example, a bulk-phase binary gas reaction between species j and k, 165 * producing a new gas species l would have the following equation for its rate 166 * of progress variable, \f$ R^1 \f$, which has units of kmol m-3 s-1. 167 * 168 * \f[ 169 * R^1 = k^1 C_j^a C_k^a = k^1 (C^s a_j) (C^s a_k) 170 * \f] 171 * where 172 * \f[ 173 * C_j^a = C^s a_j \quad \mbox{and} \quad C_k^a = C^s a_k 174 * \f] 175 * 176 * \f$ C_j^a \f$ is the activity concentration of species j, and 177 * \f$ C_k^a \f$ is the activity concentration of species k. \f$ C^s \f$ is the 178 * standard concentration. \f$ a_j \f$ is the activity of species j which is 179 * equal to the mole fraction of j. 180 * 181 * The reverse rate constant can then be obtained from the law of microscopic 182 * reversibility and the equilibrium expression for the system. 183 * 184 * \f[ 185 * \frac{a_j a_k}{ a_l} = K_a^{o,1} = \exp(\frac{\mu^o_l - \mu^o_j - \mu^o_k}{R T} ) 186 * \f] 187 * 188 * \f$ K_a^{o,1} \f$ is the dimensionless form of the equilibrium constant, 189 * associated with the pressure dependent standard states \f$ \mu^o_l(T,P) \f$ 190 * and their associated activities, \f$ a_l \f$, repeated here: 191 * 192 * \f[ 193 * \mu_l(T,P) = \mu^o_l(T, P) + R T \log(a_l) 194 * \f] 195 * 196 * We can switch over to expressing the equilibrium constant in terms of the 197 * reference state chemical potentials 198 * 199 * \f[ 200 * K_a^{o,1} = \exp(\frac{\mu^{ref}_l - \mu^{ref}_j - \mu^{ref}_k}{R T} ) * \frac{P_{ref}}{P} 201 * \f] 202 * 203 * The concentration equilibrium constant, \f$ K_c \f$, may be obtained by 204 * changing over to activity concentrations. When this is done: 205 * 206 * \f[ 207 * \frac{C^a_j C^a_k}{ C^a_l} = C^o K_a^{o,1} = K_c^1 = 208 * \exp(\frac{\mu^{ref}_l - \mu^{ref}_j - \mu^{ref}_k}{R T} ) * \frac{P_{ref}}{RT} 209 * \f] 210 * 211 * %Kinetics managers will calculate the concentration equilibrium constant, 212 * \f$ K_c \f$, using the second and third part of the above expression as a 213 * definition for the concentration equilibrium constant. 214 * 215 * For completeness, the pressure equilibrium constant may be obtained as well 216 * 217 * \f[ 218 * \frac{P_j P_k}{ P_l P_{ref}} = K_p^1 = 219 * \exp\left(\frac{\mu^{ref}_l - \mu^{ref}_j - \mu^{ref}_k}{R T} \right) 220 * \f] 221 * 222 * \f$ K_p \f$ is the simplest form of the equilibrium constant for ideal gases. 223 * However, it isn't necessarily the simplest form of the equilibrium constant 224 * for other types of phases; \f$ K_c \f$ is used instead because it is 225 * completely general. 226 * 227 * The reverse rate of progress may be written down as 228 * \f[ 229 * R^{-1} = k^{-1} C_l^a = k^{-1} (C^o a_l) 230 * \f] 231 * 232 * where we can use the concept of microscopic reversibility to write the 233 * reverse rate constant in terms of the forward rate constant and the 234 * concentration equilibrium constant, \f$ K_c \f$. 235 * 236 * \f[ 237 * k^{-1} = k^1 K^1_c 238 * \f] 239 * 240 * \f$k^{-1} \f$ has units of s-1. 241 * 242 * ## Instantiation of the Class 243 * 244 * The constructor for this phase is located in the default ThermoFactory for 245 * %Cantera. A new IdealGasPhase may be created by the following code snippet: 246 * 247 * @code 248 * XML_Node *xc = get_XML_File("silane.xml"); 249 * XML_Node * const xs = xc->findNameID("phase", "silane"); 250 * ThermoPhase *silane_tp = newPhase(*xs); 251 * IdealGasPhase *silaneGas = dynamic_cast <IdealGasPhase *>(silane_tp); 252 * @endcode 253 * 254 * or by the following constructor: 255 * 256 * @code 257 * XML_Node *xc = get_XML_File("silane.xml"); 258 * XML_Node * const xs = xc->findNameID("phase", "silane"); 259 * IdealGasPhase *silaneGas = new IdealGasPhase(*xs); 260 * @endcode 261 * 262 * ## XML Example 263 * 264 * An example of an XML Element named phase setting up a IdealGasPhase 265 * object named silane is given below. 266 * 267 * @code 268 * <!-- phase silane --> 269 * <phase dim="3" id="silane"> 270 * <elementArray datasrc="elements.xml"> Si H He </elementArray> 271 * <speciesArray datasrc="#species_data"> 272 * H2 H HE SIH4 SI SIH SIH2 SIH3 H3SISIH SI2H6 273 * H2SISIH2 SI3H8 SI2 SI3 274 * </speciesArray> 275 * <reactionArray datasrc="#reaction_data"/> 276 * <thermo model="IdealGas"/> 277 * <kinetics model="GasKinetics"/> 278 * <transport model="None"/> 279 * </phase> 280 * @endcode 281 * 282 * The model attribute "IdealGas" of the thermo XML element identifies the phase 283 * as being of the type handled by the IdealGasPhase object. 284 * 285 * @ingroup thermoprops 286 */ 287 class IdealGasPhase: public ThermoPhase 288 { 289 public: 290 //! Construct and initialize an IdealGasPhase ThermoPhase object 291 //! directly from an ASCII input file 292 /*! 293 * @param inputFile Name of the input file containing the phase definition 294 * to set up the object. If blank, an empty phase will be 295 * created. 296 * @param id ID of the phase in the input file. Defaults to the 297 * empty string. 298 */ 299 explicit IdealGasPhase(const std::string& inputFile="", 300 const std::string& id=""); 301 302 //! Construct and initialize an IdealGasPhase ThermoPhase object 303 //! directly from an XML database 304 /*! 305 * @param phaseRef XML phase node containing the description of the phase 306 * @param id id attribute containing the name of the phase. 307 * (default is the empty string) 308 * 309 * @deprecated The XML input format is deprecated and will be removed in 310 * Cantera 3.0. 311 */ 312 IdealGasPhase(XML_Node& phaseRef, const std::string& id = ""); 313 type()314 virtual std::string type() const { 315 return "IdealGas"; 316 } 317 318 //! String indicating the mechanical phase of the matter in this Phase. 319 /*! 320 * For the `IdealGasPhase`, this string is always `gas`. 321 */ phaseOfMatter()322 virtual std::string phaseOfMatter() const { 323 return "gas"; 324 } 325 326 //! @name Molar Thermodynamic Properties of the Solution 327 //! @{ 328 329 //! Return the Molar enthalpy. Units: J/kmol. 330 /*! 331 * For an ideal gas mixture, 332 * \f[ 333 * \hat h(T) = \sum_k X_k \hat h^0_k(T), 334 * \f] 335 * and is a function only of temperature. The standard-state pure-species 336 * enthalpies \f$ \hat h^0_k(T) \f$ are computed by the species 337 * thermodynamic property manager. 338 * 339 * \see MultiSpeciesThermo 340 */ enthalpy_mole()341 virtual doublereal enthalpy_mole() const { 342 return RT() * mean_X(enthalpy_RT_ref()); 343 } 344 345 /** 346 * Molar entropy. Units: J/kmol/K. 347 * For an ideal gas mixture, 348 * \f[ 349 * \hat s(T, P) = \sum_k X_k \hat s^0_k(T) - \hat R \log (P/P^0). 350 * \f] 351 * The reference-state pure-species entropies \f$ \hat s^0_k(T) \f$ are 352 * computed by the species thermodynamic property manager. 353 * @see MultiSpeciesThermo 354 */ 355 virtual doublereal entropy_mole() const; 356 357 /** 358 * Molar heat capacity at constant pressure. Units: J/kmol/K. 359 * For an ideal gas mixture, 360 * \f[ 361 * \hat c_p(t) = \sum_k \hat c^0_{p,k}(T). 362 * \f] 363 * The reference-state pure-species heat capacities \f$ \hat c^0_{p,k}(T) \f$ 364 * are computed by the species thermodynamic property manager. 365 * @see MultiSpeciesThermo 366 */ 367 virtual doublereal cp_mole() const; 368 369 /** 370 * Molar heat capacity at constant volume. Units: J/kmol/K. 371 * For an ideal gas mixture, 372 * \f[ \hat c_v = \hat c_p - \hat R. \f] 373 */ 374 virtual doublereal cv_mole() const; 375 376 //! @} 377 //! @name Mechanical Equation of State 378 //! @{ 379 380 /** 381 * Pressure. Units: Pa. 382 * For an ideal gas mixture, 383 * \f[ P = n \hat R T. \f] 384 */ pressure()385 virtual doublereal pressure() const { 386 return GasConstant * molarDensity() * temperature(); 387 } 388 389 //! Set the pressure at constant temperature and composition. 390 /*! 391 * Units: Pa. 392 * This method is implemented by setting the mass density to 393 * \f[ 394 * \rho = \frac{P \overline W}{\hat R T }. 395 * \f] 396 * 397 * @param p Pressure (Pa) 398 */ setPressure(doublereal p)399 virtual void setPressure(doublereal p) { 400 setDensity(p * meanMolecularWeight() / RT()); 401 } 402 403 //! Set the density and pressure at constant composition. 404 /*! 405 * Units: kg/m^3, Pa. 406 * This method is implemented by setting the density to the input value and 407 * setting the temperature to 408 * \f[ 409 * T = \frac{P \overline W}{\hat R \rho}. 410 * \f] 411 * 412 * @param rho Density (kg/m^3) 413 * @param p Pressure (Pa) 414 */ setState_RP(doublereal rho,doublereal p)415 virtual void setState_RP(doublereal rho, doublereal p) 416 { 417 if (p <= 0) { 418 throw CanteraError("IdealGasPhase::setState_RP", 419 "pressure must be positive"); 420 } 421 setDensity(rho); 422 setTemperature(p * meanMolecularWeight() / (GasConstant * rho)); 423 } 424 425 //! Returns the isothermal compressibility. Units: 1/Pa. 426 /** 427 * The isothermal compressibility is defined as 428 * \f[ 429 * \kappa_T = -\frac{1}{v}\left(\frac{\partial v}{\partial P}\right)_T 430 * \f] 431 * For ideal gases it's equal to the inverse of the pressure 432 */ isothermalCompressibility()433 virtual doublereal isothermalCompressibility() const { 434 return 1.0 / pressure(); 435 } 436 437 //! Return the volumetric thermal expansion coefficient. Units: 1/K. 438 /*! 439 * The thermal expansion coefficient is defined as 440 * \f[ 441 * \beta = \frac{1}{v}\left(\frac{\partial v}{\partial T}\right)_P 442 * \f] 443 * For ideal gases, it's equal to the inverse of the temperature. 444 */ thermalExpansionCoeff()445 virtual doublereal thermalExpansionCoeff() const { 446 return 1.0 / temperature(); 447 } 448 449 //@} 450 451 /** 452 * @name Chemical Potentials and Activities 453 * 454 * The activity \f$a_k\f$ of a species in solution is 455 * related to the chemical potential by 456 * \f[ 457 * \mu_k(T,P,X_k) = \mu_k^0(T,P) 458 * + \hat R T \log a_k. 459 * \f] 460 * The quantity \f$\mu_k^0(T,P)\f$ is the standard state chemical potential 461 * at unit activity. It may depend on the pressure and the temperature. 462 * However, it may not depend on the mole fractions of the species in the 463 * solution. 464 * 465 * The activities are related to the generalized concentrations, \f$\tilde 466 * C_k\f$, and standard concentrations, \f$C^0_k\f$, by the following 467 * formula: 468 * 469 * \f[ 470 * a_k = \frac{\tilde C_k}{C^0_k} 471 * \f] 472 * The generalized concentrations are used in the kinetics classes to 473 * describe the rates of progress of reactions involving the species. Their 474 * formulation depends upon the specification of the rate constants for 475 * reaction, especially the units used in specifying the rate constants. The 476 * bridge between the thermodynamic equilibrium expressions that use a_k and 477 * the kinetics expressions which use the generalized concentrations is 478 * provided by the multiplicative factor of the standard concentrations. 479 * @{ 480 */ 481 482 //! This method returns the array of generalized concentrations. 483 /*! 484 * For an ideal gas mixture, these are simply the actual concentrations. 485 * 486 * @param c Output array of generalized concentrations. The units depend 487 * upon the implementation of the reaction rate expressions within 488 * the phase. 489 */ getActivityConcentrations(doublereal * c)490 virtual void getActivityConcentrations(doublereal* c) const { 491 getConcentrations(c); 492 } 493 494 //! Returns the standard concentration \f$ C^0_k \f$, which is used to 495 //! normalize the generalized concentration. 496 /*! 497 * This is defined as the concentration by which the generalized 498 * concentration is normalized to produce the activity. In many cases, this 499 * quantity will be the same for all species in a phase. Since the activity 500 * for an ideal gas mixture is simply the mole fraction, for an ideal gas 501 * \f$ C^0_k = P/\hat R T \f$. 502 * 503 * @param k Optional parameter indicating the species. The default 504 * is to assume this refers to species 0. 505 * @return 506 * Returns the standard Concentration in units of m3 kmol-1. 507 */ 508 virtual doublereal standardConcentration(size_t k = 0) const; 509 510 //! Get the array of non-dimensional activity coefficients at the current 511 //! solution temperature, pressure, and solution concentration. 512 /*! 513 * For ideal gases, the activity coefficients are all equal to one. 514 * 515 * @param ac Output vector of activity coefficients. Length: m_kk. 516 */ 517 virtual void getActivityCoefficients(doublereal* ac) const; 518 519 //@} 520 /// @name Partial Molar Properties of the Solution 521 //@{ 522 523 virtual void getChemPotentials(doublereal* mu) const; 524 virtual void getPartialMolarEnthalpies(doublereal* hbar) const; 525 virtual void getPartialMolarEntropies(doublereal* sbar) const; 526 virtual void getPartialMolarIntEnergies(doublereal* ubar) const; 527 virtual void getPartialMolarCp(doublereal* cpbar) const; 528 virtual void getPartialMolarVolumes(doublereal* vbar) const; 529 530 //@} 531 /// @name Properties of the Standard State of the Species in the Solution 532 //@{ 533 534 virtual void getStandardChemPotentials(doublereal* mu) const; 535 virtual void getEnthalpy_RT(doublereal* hrt) const; 536 virtual void getEntropy_R(doublereal* sr) const; 537 virtual void getGibbs_RT(doublereal* grt) const; 538 virtual void getPureGibbs(doublereal* gpure) const; 539 virtual void getIntEnergy_RT(doublereal* urt) const; 540 virtual void getCp_R(doublereal* cpr) const; 541 virtual void getStandardVolumes(doublereal* vol) const; 542 543 //@} 544 /// @name Thermodynamic Values for the Species Reference States 545 //@{ 546 547 virtual void getEnthalpy_RT_ref(doublereal* hrt) const; 548 virtual void getGibbs_RT_ref(doublereal* grt) const; 549 virtual void getGibbs_ref(doublereal* g) const; 550 virtual void getEntropy_R_ref(doublereal* er) const; 551 virtual void getIntEnergy_RT_ref(doublereal* urt) const; 552 virtual void getCp_R_ref(doublereal* cprt) const; 553 virtual void getStandardVolumes_ref(doublereal* vol) const; 554 555 //@} 556 /// @name NonVirtual Internal methods to Return References to Reference State Thermo 557 //@{ 558 559 //! Returns a reference to the dimensionless reference state enthalpy vector. 560 /*! 561 * This function is part of the layer that checks/recalculates the reference 562 * state thermo functions. 563 */ enthalpy_RT_ref()564 const vector_fp& enthalpy_RT_ref() const { 565 _updateThermo(); 566 return m_h0_RT; 567 } 568 569 //! Returns a reference to the dimensionless reference state Gibbs free energy vector. 570 /*! 571 * This function is part of the layer that checks/recalculates the reference 572 * state thermo functions. 573 */ gibbs_RT_ref()574 const vector_fp& gibbs_RT_ref() const { 575 _updateThermo(); 576 return m_g0_RT; 577 } 578 579 //! Returns a reference to the dimensionless reference state Entropy vector. 580 /*! 581 * This function is part of the layer that checks/recalculates the reference 582 * state thermo functions. 583 */ entropy_R_ref()584 const vector_fp& entropy_R_ref() const { 585 _updateThermo(); 586 return m_s0_R; 587 } 588 589 //! Returns a reference to the dimensionless reference state Heat Capacity vector. 590 /*! 591 * This function is part of the layer that checks/recalculates the reference 592 * state thermo functions. 593 */ cp_R_ref()594 const vector_fp& cp_R_ref() const { 595 _updateThermo(); 596 return m_cp0_R; 597 } 598 599 //@} 600 601 virtual bool addSpecies(shared_ptr<Species> spec); 602 virtual void setToEquilState(const doublereal* mu_RT); 603 604 protected: 605 //! Reference state pressure 606 /*! 607 * Value of the reference state pressure in Pascals. 608 * All species must have the same reference state pressure. 609 */ 610 doublereal m_p0; 611 612 //! Temporary storage for dimensionless reference state enthalpies 613 mutable vector_fp m_h0_RT; 614 615 //! Temporary storage for dimensionless reference state heat capacities 616 mutable vector_fp m_cp0_R; 617 618 //! Temporary storage for dimensionless reference state Gibbs energies 619 mutable vector_fp m_g0_RT; 620 621 //! Temporary storage for dimensionless reference state entropies 622 mutable vector_fp m_s0_R; 623 624 mutable vector_fp m_expg0_RT; 625 626 //! Temporary array containing internally calculated partial pressures 627 mutable vector_fp m_pp; 628 629 private: 630 //! Update the species reference state thermodynamic functions 631 /*! 632 * This method is called each time a thermodynamic property is requested, 633 * to check whether the internal species properties within the object 634 * need to be updated. Currently, this updates the species thermo 635 * polynomial values for the current value of the temperature. A check is 636 * made to see if the temperature has changed since the last evaluation. 637 * This object does not contain any persistent data that depends on the 638 * concentration, that needs to be updated. The state object modifies its 639 * concentration dependent information at the time the setMoleFractions() 640 * (or equivalent) call is made. 641 */ 642 void _updateThermo() const; 643 }; 644 645 } 646 647 #endif 648