Cantera  2.0
MixtureFugacityTP.cpp
Go to the documentation of this file.
1 /**
2  * @file MixtureFugacityTP.cpp
3  * Methods file for a derived class of ThermoPhase that handles
4  * non-ideal mixtures based on the fugacity models (see \ref thermoprops and
5  * class \link Cantera::MixtureFugacityTP MixtureFugacityTP\endlink).
6  *
7  */
8 /*
9  * Copyright (2005) Sandia Corporation. Under the terms of
10  * Contract DE-AC04-94AL85000 with Sandia Corporation, the
11  * U.S. Government retains certain rights in this software.
12  */
13 
15 #include "cantera/thermo/VPSSMgr.h"
16 #include "cantera/thermo/PDSS.h"
18 
19 using namespace std;
20 
21 namespace Cantera
22 {
23 //====================================================================================================================
24 /*
25  * Default constructor
26  */
27 MixtureFugacityTP::MixtureFugacityTP() :
28  ThermoPhase(),
29  m_Pcurrent(-1.0),
30  moleFractions_(0),
31  iState_(FLUID_GAS),
32  forcedState_(FLUID_UNDEFINED),
33  m_Tlast_ref(-1.0),
34  m_logc0(0.0),
35  m_h0_RT(0),
36  m_cp0_R(0),
37  m_g0_RT(0),
38  m_s0_R(0)
39 {
40 }
41 //====================================================================================================================
42 /*
43  * Copy Constructor:
44  *
45  * Note this stuff will not work until the underlying phase
46  * has a working copy constructor.
47  *
48  * The copy constructor just calls the assignment operator
49  * to do the heavy lifting.
50  */
52  ThermoPhase(),
53  m_Pcurrent(-1.0),
54  moleFractions_(0),
55  iState_(FLUID_GAS),
56  forcedState_(FLUID_UNDEFINED),
57  m_Tlast_ref(-1.0),
58  m_logc0(0.0),
59  m_h0_RT(0),
60  m_cp0_R(0),
61  m_g0_RT(0),
62  m_s0_R(0)
63 {
65 }
66 //====================================================================================================================
67 /*
68  * operator=()
69  *
70  * Note this stuff will not work until the underlying phase
71  * has a working assignment operator
72  */
75 {
76  if (&b != this) {
77  /*
78  * Mostly, this is a passthrough to the underlying
79  * assignment operator for the ThermoPhase parent object.
80  */
82  /*
83  * However, we have to handle data that we own.
84  */
87  iState_ = b.iState_;
90  m_logc0 = b.m_logc0;
91  m_h0_RT = b.m_h0_RT;
92  m_cp0_R = b.m_cp0_R;
93  m_g0_RT = b.m_g0_RT;
94  m_s0_R = b.m_s0_R;
95  /*
96  * The VPSSMgr object contains shallow pointers. Whenever you have shallow
97  * pointers, they have to be fixed up to point to the correct objects referring
98  * back to this ThermoPhase's properties.
99  */
100  //m_VPSS_ptr->initAllPtrs(this, m_spthermo);
101  /*
102  * The PDSS objects contains shallow pointers. Whenever you have shallow
103  * pointers, they have to be fixed up to point to the correct objects referring
104  * back to this ThermoPhase's properties. This function also sets m_VPSS_ptr
105  * so it occurs after m_VPSS_ptr is set.
106  */
107 
108  /*
109  * Ok, the VPSSMgr object is ready for business.
110  * We need to resync the temperature and the pressure of the new standard states
111  * with what is stored in this object.
112  */
113  // m_VPSS_ptr->setState_TP(m_Tlast_ss, m_Plast_ss);
114  }
115  return *this;
116 }
117 //====================================================================================================================
118 /*
119  * ~MixtureFugacityTP(): (virtual)
120  *
121  */
123 {
124 
125 }
126 
127 /*
128  * Duplication function.
129  * This calls the copy constructor for this object.
130  */
132 {
133  MixtureFugacityTP* vptp = new MixtureFugacityTP(*this);
134  return (ThermoPhase*) vptp;
135 }
136 //====================================================================================================================
137 // This method returns the convention used in specification
138 // of the standard state, of which there are currently two,
139 // temperature based, and variable pressure based.
140 /*
141  * Currently, there are two standard state conventions:
142  * - Temperature-based activities
143  * cSS_CONVENTION_TEMPERATURE 0
144  * - default
145  *
146  * - Variable Pressure and Temperature -based activities
147  * cSS_CONVENTION_VPSS 1
148  */
150 {
152 }
153 //====================================================================================================================
154 // Set the solution branch to force the ThermoPhase to exist on one branch or another
155 /*
156  * @param solnBranch Branch that the solution is restricted to.
157  * the value -1 means gas. The value -2 means unrestricted.
158  * Values of zero or greater refer to species dominated condensed phases.
159  */
161 {
162  forcedState_ = solnBranch;
163 }
164 //====================================================================================================================
165 // Report the solution branch which the solution is restricted to
166 /*
167  * @return Branch that the solution is restricted to.
168  * the value -1 means gas. The value -2 means unrestricted.
169  * Values of zero or greater refer to species dominated condensed phases.
170  */
172 {
173  return forcedState_;
174 }
175 //====================================================================================================================
176 // Report the solution branch which the solution is actually on
177 /*
178  * @return Branch that the solution is restricted to.
179  * the value -1 means gas. The value -2 means superfluid..
180  * Values of zero or greater refer to species dominated condensed phases.
181  */
183 {
184  return iState_;
185 }
186 //====================================================================================================================
187 
188 /*
189  * ------------Molar Thermodynamic Properties -------------------------
190  */
191 //====================================================================================================================
192 
193 doublereal MixtureFugacityTP::err(std::string msg) const
194 {
195  throw CanteraError("MixtureFugacityTP","Base class method "
196  +msg+" called. Equation of state type: "+int2str(eosType()));
197  return 0;
198 }
199 //====================================================================================================================
200 /*
201  * ---- Partial Molar Properties of the Solution -----------------
202  */
203 //====================================================================================================================
204 /*
205  * Get the array of non-dimensional species chemical potentials
206  * These are partial molar Gibbs free energies.
207  * \f$ \mu_k / \hat R T \f$.
208  * Units: unitless
209  *
210  * We close the loop on this function, here, calling
211  * getChemPotentials() and then dividing by RT.
212  */
213 void MixtureFugacityTP::getChemPotentials_RT(doublereal* muRT) const
214 {
215  getChemPotentials(muRT);
216  doublereal invRT = 1.0 / _RT();
217  for (size_t k = 0; k < m_kk; k++) {
218  muRT[k] *= invRT;
219  }
220 }
221 //====================================================================================================================
222 /*
223  * ----- Thermodynamic Values for the Species Standard States States ----
224  */
226 {
228  copy(m_g0_RT.begin(), m_g0_RT.end(), g);
229  doublereal RT = _RT();
230  double tmp = log(pressure() /m_spthermo->refPressure());
231  for (size_t k = 0; k < m_kk; k++) {
232  g[k] = RT * (g[k] + tmp);
233  }
234 }
235 //====================================================================================================================
236 void MixtureFugacityTP::getEnthalpy_RT(doublereal* hrt) const
237 {
238  getEnthalpy_RT_ref(hrt);
239 }
240 //================================================================================================
241 #ifdef H298MODIFY_CAPABILITY
242 // Modify the value of the 298 K Heat of Formation of one species in the phase (J kmol-1)
243 /*
244  * The 298K heat of formation is defined as the enthalpy change to create the standard state
245  * of the species from its constituent elements in their standard states at 298 K and 1 bar.
246  *
247  * @param k Species k
248  * @param Hf298New Specify the new value of the Heat of Formation at 298K and 1 bar
249  */
250 void MixtureFugacityTP::modifyOneHf298SS(const int k, const doublereal Hf298New)
251 {
252  m_spthermo->modifyOneHf298(k, Hf298New);
253  m_Tlast_ref += 0.0001234;
254 }
255 #endif
256 //====================================================================================================================
257 /*
258  * Get the array of nondimensional entropy functions for the
259  * standard state species
260  * at the current <I>T</I> and <I>P</I> of the solution.
261  */
262 void MixtureFugacityTP::getEntropy_R(doublereal* sr) const
263 {
265  copy(m_s0_R.begin(), m_s0_R.end(), sr);
266  double tmp = log(pressure() /m_spthermo->refPressure());
267  for (size_t k = 0; k < m_kk; k++) {
268  sr[k] -= tmp;
269  }
270 }
271 //====================================================================================================================
272 /*
273  * Get the nondimensional gibbs function for the species
274  * standard states at the current T and P of the solution.
275  */
276 void MixtureFugacityTP::getGibbs_RT(doublereal* grt) const
277 {
279  copy(m_g0_RT.begin(), m_g0_RT.end(), grt);
280  double tmp = log(pressure() /m_spthermo->refPressure());
281  for (size_t k = 0; k < m_kk; k++) {
282  grt[k] += tmp;
283  }
284 }
285 //====================================================================================================================
286 /*
287  * get the pure Gibbs free energies of each species assuming
288  * it is in its standard state. This is the same as
289  * getStandardChemPotentials().
290  */
291 void MixtureFugacityTP::getPureGibbs(doublereal* g) const
292 {
294  scale(m_g0_RT.begin(), m_g0_RT.end(), g, _RT());
295  double tmp = log(pressure() /m_spthermo->refPressure());
296  tmp *= _RT();
297  for (size_t k = 0; k < m_kk; k++) {
298  g[k] += tmp;
299  }
300 }
301 //====================================================================================================================
302 /*
303  * Returns the vector of nondimensional
304  * internal Energies of the standard state at the current temperature
305  * and pressure of the solution for each species.
306  */
307 void MixtureFugacityTP::getIntEnergy_RT(doublereal* urt) const
308 {
310  copy(m_h0_RT.begin(), m_h0_RT.end(), urt);
311  doublereal p = pressure();
312  doublereal tmp = p / _RT();
313  doublereal v0 = _RT() / p;
314  for (size_t i = 0; i < m_kk; i++) {
315  urt[i] -= tmp * v0;
316  }
317 }
318 //====================================================================================================================
319 /*
320  * Get the nondimensional heat capacity at constant pressure
321  * function for the species
322  * standard states at the current T and P of the solution.
323  */
324 void MixtureFugacityTP::getCp_R(doublereal* cpr) const
325 {
327  copy(m_cp0_R.begin(), m_cp0_R.end(), cpr);
328 }
329 //====================================================================================================================
330 /*
331  * Get the molar volumes of the species standard states at the current
332  * <I>T</I> and <I>P</I> of the solution.
333  * units = m^3 / kmol
334  *
335  * @param vol Output vector containing the standard state volumes.
336  * Length: m_kk.
337  */
338 void MixtureFugacityTP::getStandardVolumes(doublereal* vol) const
339 {
341  doublereal v0 = _RT() / pressure();
342  for (size_t i = 0; i < m_kk; i++) {
343  vol[i]= v0;
344  }
345 }
346 //====================================================================================================================
347 /*
348  * ----- Thermodynamic Values for the Species Reference States ----
349  */
350 
351 /*
352  * Returns the vector of nondimensional enthalpies of the
353  * reference state at the current temperature of the solution and
354  * the reference pressure for the species.
355  */
356 void MixtureFugacityTP::getEnthalpy_RT_ref(doublereal* hrt) const
357 {
359  copy(m_h0_RT.begin(), m_h0_RT.end(), hrt);
360 }
361 //====================================================================================================================
362 /*
363  * Returns the vector of nondimensional
364  * enthalpies of the reference state at the current temperature
365  * of the solution and the reference pressure for the species.
366  */
367 void MixtureFugacityTP::getGibbs_RT_ref(doublereal* grt) const
368 {
370  copy(m_g0_RT.begin(), m_g0_RT.end(), grt);
371 }
372 //====================================================================================================================
373 /*
374  * Returns the vector of the
375  * gibbs function of the reference state at the current temperature
376  * of the solution and the reference pressure for the species.
377  * units = J/kmol
378  *
379  * This is filled in here so that derived classes don't have to
380  * take care of it.
381  */
382 void MixtureFugacityTP::getGibbs_ref(doublereal* g) const
383 {
384  const vector_fp& gibbsrt = gibbs_RT_ref();
385  scale(gibbsrt.begin(), gibbsrt.end(), g, _RT());
386 }
387 //====================================================================================================================
389 {
391  return m_g0_RT;
392 }
393 //====================================================================================================================
394 /*
395  * Returns the vector of nondimensional
396  * entropies of the reference state at the current temperature
397  * of the solution and the reference pressure for the species.
398  */
399 void MixtureFugacityTP::getEntropy_R_ref(doublereal* er) const
400 {
402  copy(m_s0_R.begin(), m_s0_R.end(), er);
403  return;
404 }
405 //====================================================================================================================
406 /*
407  * Returns the vector of nondimensional
408  * constant pressure heat capacities of the reference state
409  * at the current temperature of the solution
410  * and reference pressure for the species.
411  */
412 void MixtureFugacityTP::getCp_R_ref(doublereal* cpr) const
413 {
415  copy(m_cp0_R.begin(), m_cp0_R.end(), cpr);
416 }
417 //====================================================================================================================
418 /*
419  * Get the molar volumes of the species reference states at the current
420  * <I>T</I> and reference pressure of the solution.
421  *
422  * units = m^3 / kmol
423  */
424 void MixtureFugacityTP::getStandardVolumes_ref(doublereal* vol) const
425 {
427  double pp = refPressure();
428  doublereal v0 = _RT() / pp;
429  for (size_t i = 0; i < m_kk; i++) {
430  vol[i]= v0;
431  }
432 }
433 //====================================================================================================================
434 // Set the initial state of the phase to the conditions specified in the state XML element.
435 /*
436  *
437  * This method sets the temperature, pressure, and mole fraction vector to a set default value.
438  * We modify the default behavior here so that TP is evaluated at the same time.
439  *
440  * @param state AN XML_Node object corresponding to
441  * the "state" entry for this phase in the
442  * input file.
443  */
445 {
446  int doTP = 0;
447  string comp = ctml::getChildValue(state,"moleFractions");
448  if (comp != "") {
449  // not overloaded in current object -> phase state is not calculated.
451  doTP = 1;
452  } else {
453  comp = ctml::getChildValue(state,"massFractions");
454  if (comp != "") {
455  // not overloaded in current object -> phase state is not calculated.
457  doTP = 1;
458  }
459  }
460  double t = temperature();
461  if (state.hasChild("temperature")) {
462  t = ctml::getFloat(state, "temperature", "temperature");
463  doTP = 1;
464  }
465  if (state.hasChild("pressure")) {
466  double p = ctml::getFloat(state, "pressure", "pressure");
467  setState_TP(t, p);
468  } else if (state.hasChild("density")) {
469  double rho = ctml::getFloat(state, "density", "density");
470  setState_TR(t, rho);
471  } else if (doTP) {
472  double rho = Phase::density();
473  setState_TR(t, rho);
474  }
475 }
476 //====================================================================================================================
477 /*
478  * Perform initializations after all species have been
479  * added.
480  */
482 {
483  initLengths();
485 }
486 //====================================================================================================================
487 /*
488  * Initialize the internal lengths.
489  * (this is not a virtual function)
490  */
492 {
493  m_kk = nSpecies();
494  moleFractions_.resize(m_kk, 0.0);
495  moleFractions_[0] = 1.0;
496  m_h0_RT.resize(m_kk, 0.0);
497  m_cp0_R.resize(m_kk, 0.0);
498  m_g0_RT.resize(m_kk, 0.0);
499  m_s0_R.resize(m_kk, 0.0);
500 }
501 //====================================================================================================================
502 void MixtureFugacityTP::setTemperature(const doublereal temp)
503 {
506 }
507 //====================================================================================================================
509 {
510  setState_TP(temperature(), p);
511  // double chemPot[5], mf[5];
512  // getMoleFractions(mf);
513  // getChemPotentials(chemPot);
514  // for (int i = 0; i < m_kk; i++) {
515  // printf(" MixFug:setPres: mu(%d = %g) = %18.8g\n", i, mf[i], chemPot[i]);
516  // }
517 }
518 //====================================================================================================================
519 void MixtureFugacityTP::setMassFractions(const doublereal* const y)
520 {
523 }
524 //====================================================================================================================
525 void MixtureFugacityTP::setMassFractions_NoNorm(const doublereal* const y)
526 {
529 }
530 //====================================================================================================================
531 void MixtureFugacityTP::setMoleFractions(const doublereal* const x)
532 {
535 }
536 //====================================================================================================================
537 void MixtureFugacityTP::setMoleFractions_NoNorm(const doublereal* const x)
538 {
541 }
542 //====================================================================================================================
543 void MixtureFugacityTP::setConcentrations(const doublereal* const c)
544 {
547 }
548 //====================================================================================================================
549 void MixtureFugacityTP::setMoleFractions_NoState(const doublereal* const x)
550 {
553  updateMixingExpressions();
554 }
555 //====================================================================================================================
557 {
558  err("MixtureFugacityTP::calcDensity() called, but EOS for phase is not known");
559 }
560 //====================================================================================================================
561 
562 void MixtureFugacityTP::setState_TP(doublereal t, doublereal pres)
563 {
564  /*
565  * A pretty tricky algorithm is needed here, due to problems involving
566  * standard states of real fluids. For those cases you need
567  * to combine the T and P specification for the standard state, or else
568  * you may venture into the forbidden zone, especially when nearing the
569  * triple point.
570  * Therefore, we need to do the standard state thermo calc with the
571  * (t, pres) combo.
572  */
574 
575 
578  // Depends on the mole fractions and the temperature
579  updateMixingExpressions();
580  // setPressure(pres);
581  m_Pcurrent = pres;
582  // double mmw = meanMolecularWeight();
583 
584  if (forcedState_ == FLUID_UNDEFINED) {
585  double rhoNow = Phase::density();
586  double rho = densityCalc(t, pres, iState_, rhoNow);
587  if (rho > 0.0) {
588  Phase::setDensity(rho);
589  m_Pcurrent = pres;
590  iState_ = phaseState(true);
591  } else {
592  if (rho < -1.5) {
593  rho = densityCalc(t, pres, FLUID_UNDEFINED , rhoNow);
594  if (rho > 0.0) {
595  Phase::setDensity(rho);
596  m_Pcurrent = pres;
597  iState_ = phaseState(true);
598  } else {
599  throw CanteraError("MixtureFugacityTP::setState_TP()", "neg rho");
600  }
601  } else {
602  throw CanteraError("MixtureFugacityTP::setState_TP()", "neg rho");
603  }
604  }
605 
606 
607 
608  } else if (forcedState_ == FLUID_GAS) {
609  // Normal density calculation
610  if (iState_ < FLUID_LIQUID_0) {
611  double rhoNow = Phase::density();
612  double rho = densityCalc(t, pres, iState_, rhoNow);
613  if (rho > 0.0) {
614  Phase::setDensity(rho);
615  m_Pcurrent = pres;
616  iState_ = phaseState(true);
617  if (iState_ >= FLUID_LIQUID_0) {
618  throw CanteraError("MixtureFugacityTP::setState_TP()", "wrong state");
619  }
620  } else {
621  throw CanteraError("MixtureFugacityTP::setState_TP()", "neg rho");
622  }
623 
624  }
625 
626 
627  } else if (forcedState_ > FLUID_LIQUID_0) {
628  if (iState_ >= FLUID_LIQUID_0) {
629  double rhoNow = Phase::density();
630  double rho = densityCalc(t, pres, iState_, rhoNow);
631  if (rho > 0.0) {
632  Phase::setDensity(rho);
633  m_Pcurrent = pres;
634  iState_ = phaseState(true);
635  if (iState_ == FLUID_GAS) {
636  throw CanteraError("MixtureFugacityTP::setState_TP()", "wrong state");
637  }
638  } else {
639  throw CanteraError("MixtureFugacityTP::setState_TP()", "neg rho");
640  }
641 
642  }
643  }
644 
645 
646 
647  //setTemperature(t);
648  //setPressure(pres);
649  //calcDensity();
650 }
651 //====================================================================================================================
652 // Set the internally stored temperature (K) and density (kg/m^3)
653 /*
654  * This overrides the default behavior. In addition to just storing the state in the object, we need to do
655  * an equation of state calculation and figure out what phase state we are in.
656  *
657  * @param t Temperature in kelvin
658  * @param rho Density (kg/m^3)
659  */
660 void MixtureFugacityTP::setState_TR(doublereal T, doublereal rho)
661 {
665  Phase::setDensity(rho);
666  doublereal mv = molarVolume();
667  // depends on mole fraction and temperature
668  updateMixingExpressions();
669 
670  m_Pcurrent = pressureCalc(T, mv);
671  iState_ = phaseState(true);
672 
673  // printf("setState_TR: state at T = %g, rho = %g, mv = %g, P = %20.13g, iState = %d\n", T, rho, mv, m_Pcurrent, iState_);
674 }
675 
676 //====================================================================================================================
677 // Set the temperature (K), pressure (Pa), and mole fractions.
678 /*
679  * Note, the mole fractions are set first before the pressure is set.
680  * Setting the pressure may involve the solution of a nonlinear equation.
681  *
682  * @param t Temperature (K)
683  * @param p Pressure (Pa)
684  * @param x Vector of mole fractions.
685  * Length is equal to m_kk.
686  */
687 void MixtureFugacityTP::setState_TPX(doublereal t, doublereal p, const doublereal* x)
688 {
689  setMoleFractions_NoState(x);
690  setState_TP(t,p);
691 }
692 //====================================================================================================================
693 /*
694  * Import and initialize a ThermoPhase object
695  *
696  * param phaseNode This object must be the phase node of a
697  * complete XML tree
698  * description of the phase, including all of the
699  * species data. In other words while "phase" must
700  * point to an XML phase object, it must have
701  * sibling nodes "speciesData" that describe
702  * the species in the phase.
703  * param id ID of the phase. If nonnull, a check is done
704  * to see if phaseNode is pointing to the phase
705  * with the correct id.
706  *
707  * This routine initializes the lengths in the current object and
708  * then calls the parent routine.
709  */
710 void MixtureFugacityTP::initThermoXML(XML_Node& phaseNode, std::string id)
711 {
713 
714  //m_VPSS_ptr->initThermo();
715 
716  // m_VPSS_ptr->initThermoXML(phaseNode, id);
717  ThermoPhase::initThermoXML(phaseNode, id);
718 }
719 //====================================================================================================================
720 doublereal MixtureFugacityTP::z() const
721 {
722  doublereal p = pressure();
723  doublereal rho = density();
724  doublereal mmw = meanMolecularWeight();
725  doublereal molarV = mmw / rho;
726  doublereal rt = _RT();
727  doublereal zz = p * molarV / rt;
728  return zz;
729 }
730 //====================================================================================================================
731 doublereal MixtureFugacityTP::sresid() const
732 {
733  throw CanteraError("MixtureFugacityTP::sresid()", "Base Class: not implemented");
734  return 0.0;
735 }
736 //====================================================================================================================
737 doublereal MixtureFugacityTP::hresid() const
738 {
739  throw CanteraError("MixtureFugacityTP::hresid()", "Base Class: not implemented");
740  return 0.0;
741 }
742 //====================================================================================================================
743 doublereal MixtureFugacityTP::psatEst(doublereal TKelvin) const
744 {
745  doublereal tcrit = critTemperature();
746  doublereal pcrit = critPressure();
747  doublereal tt = tcrit/TKelvin;
748  if (tt < 1.0) {
749  return pcrit;
750  }
751  doublereal lpr = -0.8734*tt*tt - 3.4522*tt + 4.2918;
752  return pcrit*exp(lpr);
753 }
754 //====================================================================================================================
755 doublereal MixtureFugacityTP::liquidVolEst(doublereal TKelvin, doublereal& pres) const
756 {
757  throw CanteraError("MixtureFugacityTP::liquidVolEst()", "unimplemented");
758  return 0.0;
759 }
760 //====================================================================================================================
761 /*
762  * Calculates the density given the temperature and the pressure,
763  * and a guess at the density. Note, below T_c, this is a
764  * multivalued function. This function assumes that the phase is on one side of the vapor dome
765  * or the other. It does not allow for crosses of the vapor dome.
766  *
767  * parameters:
768  * temperature: Kelvin
769  * pressure : Pressure in Pascals (Newton/m**2)
770  * phase : guessed phase of water
771  * : -1: no guessed phase
772  * rhoguess : guessed density of the water
773  *
774  * -1.0 no guessed density
775  *
776  * If a problem is encountered, a negative 1 is returned.
777  *
778  * @TODO make this a const function
779  */
780 doublereal MixtureFugacityTP::densityCalc(doublereal TKelvin, doublereal presPa,
781  int phase, doublereal rhoguess)
782 {
783  double tcrit = critTemperature();
784  doublereal mmw = meanMolecularWeight();
785  // double pcrit = critPressure();
786  // doublereal deltaGuess = 0.0;
787  if (rhoguess == -1.0) {
788  if (phase != -1) {
789  if (TKelvin > tcrit) {
790  rhoguess = presPa * mmw / (GasConstant * TKelvin);
791  } else {
792  if (phase == FLUID_GAS || phase == FLUID_SUPERCRIT) {
793  rhoguess = presPa * mmw / (GasConstant * TKelvin);
794  } else if (phase >= FLUID_LIQUID_0) {
795  double lqvol = liquidVolEst(TKelvin, presPa);
796  rhoguess = mmw / lqvol;
797  }
798  }
799  } else {
800  /*
801  * Assume the Gas phase initial guess, if nothing is
802  * specified to the routine
803  */
804  rhoguess = presPa * mmw / (GasConstant * TKelvin);
805  }
806 
807  }
808 
809  double molarVolBase = mmw / rhoguess;
810  double molarVolLast = molarVolBase;
811  double vc = mmw / critDensity();
812  /*
813  * molar volume of the spinodal at the current temperature and mole fractions. this will
814  * be updated as we go.
815  */
816  double molarVolSpinodal = vc;
817  doublereal pcheck = 1.0E-30 + 1.0E-8 * presPa;
818  doublereal presBase, dpdVBase, delMV;
819  bool conv = false;
820  /*
821  * We start on one side of the vc and stick with that side
822  */
823  bool gasSide = molarVolBase > vc;
824  if (gasSide) {
825  molarVolLast = (GasConstant * TKelvin)/presPa;
826  } else {
827  molarVolLast = liquidVolEst(TKelvin, presPa);
828  }
829 
830  /*
831  * OK, now we do a small solve to calculate the molar volume given the T,P value.
832  * The algorithm is taken from dfind()
833  */
834  for (int n = 0; n < 200; n++) {
835 
836  /*
837  * Calculate the predicted reduced pressure, pred0, based on the
838  * current tau and dd.
839  */
840 
841  /*
842  * Calculate the derivative of the predicted pressure
843  * wrt the molar volume.
844  * This routine also returns the pressure, presBase
845  */
846  dpdVBase = dpdVCalc(TKelvin, molarVolBase, presBase);
847 
848  /*
849  * If dpdV is positve, then we are in the middle of the
850  * 2 phase region and beyond the spinodal stability curve. We need to adjust
851  * the initial guess outwards and start a new iteration.
852  */
853  if (dpdVBase >= 0.0) {
854  if (TKelvin > tcrit) {
855  throw CanteraError("", "confused");
856  }
857  /*
858  * TODO Spawn a calculation for the value of the spinodal point that is
859  * very accurate. Answer the question as to wethera solution is
860  * possible on the current side of the vapor dome.
861  */
862  if (gasSide) {
863  if (molarVolBase >= vc) {
864  molarVolSpinodal = molarVolBase;
865  molarVolBase = 0.5 * (molarVolLast + molarVolSpinodal);
866  } else {
867  molarVolBase = 0.5 * (molarVolLast + molarVolSpinodal);
868  }
869  } else {
870  if (molarVolBase <= vc) {
871  molarVolSpinodal = molarVolBase;
872  molarVolBase = 0.5 * (molarVolLast + molarVolSpinodal);
873  } else {
874  molarVolBase = 0.5 * (molarVolLast + molarVolSpinodal);
875  }
876  }
877  continue;
878  }
879 
880  /*
881  * Check for convergence
882  */
883  if (fabs(presBase-presPa) < pcheck) {
884  conv = true;
885  break;
886  }
887 
888  /*
889  * Dampen and crop the update
890  */
891  doublereal dpdV = dpdVBase;
892  if (n < 10) {
893  dpdV = dpdVBase * 1.5;
894  }
895  // if (dpdV > -0.001) dpdV = -0.001;
896 
897  /*
898  * Formulate the update to the molar volume by
899  * Newton's method. Then, crop it to a max value
900  * of 0.1 times the current volume
901  */
902  delMV = - (presBase - presPa) / dpdV;
903  if (!gasSide || delMV < 0.0) {
904  if (fabs(delMV) > 0.2 * molarVolBase) {
905  delMV = delMV / fabs(delMV) * 0.2 * molarVolBase;
906  }
907  }
908  /*
909  * Only go 1/10 the way towards the spinodal at any one time.
910  */
911  if (TKelvin < tcrit) {
912  if (gasSide) {
913  if (delMV < 0.0) {
914  if (-delMV > 0.5 * (molarVolBase - molarVolSpinodal)) {
915  delMV = - 0.5 * (molarVolBase - molarVolSpinodal);
916  }
917  }
918  } else {
919  if (delMV > 0.0) {
920  if (delMV > 0.5 * (molarVolSpinodal - molarVolBase)) {
921  delMV = 0.5 * (molarVolSpinodal - molarVolBase);
922  }
923  }
924  }
925  }
926  /*
927  * updated the molar volume value
928  */
929  molarVolLast = molarVolBase;
930  molarVolBase += delMV;
931 
932 
933  if (fabs(delMV/molarVolBase) < 1.0E-14) {
934  conv = true;
935  break;
936  }
937 
938  /*
939  * Check for negative molar volumes
940  */
941  if (molarVolBase <= 0.0) {
942  molarVolBase = std::min(1.0E-30, fabs(delMV*1.0E-4));
943  }
944 
945  }
946 
947 
948  /*
949  * Check for convergence, and return 0.0 if it wasn't achieved.
950  */
951  double densBase = 0.0;
952  if (! conv) {
953  molarVolBase = 0.0;
954  throw CanteraError("MixtureFugacityTP::densityCalc()", "Process didnot converge");
955  } else {
956  densBase = mmw / molarVolBase;
957  }
958  return densBase;
959 }
960 //====================================================================================================================
961 void MixtureFugacityTP::updateMixingExpressions()
962 {
963 
964 }
965 //====================================================================================================================
966 MixtureFugacityTP::spinodalFunc::spinodalFunc(MixtureFugacityTP* tp) :
967  ResidEval(),
968  m_tp(tp)
969 {
970 }
971 //====================================================================================================================
972 int MixtureFugacityTP::spinodalFunc::evalSS(const doublereal t, const doublereal* const y,
973  doublereal* const r)
974 {
975  int status = 0;
976  doublereal molarVol = y[0];
977  doublereal tt = m_tp->temperature();
978  doublereal pp;
979  doublereal val = m_tp->dpdVCalc(tt, molarVol, pp);
980  r[0] = val;
981  return status;
982 }
983 //====================================================================================================================
984 
985 int MixtureFugacityTP::corr0(doublereal TKelvin, doublereal pres, doublereal& densLiqGuess,
986  doublereal& densGasGuess, doublereal& liqGRT, doublereal& gasGRT)
987 {
988 
989  int retn = 0;
990  doublereal densLiq = densityCalc(TKelvin, pres, FLUID_LIQUID_0, densLiqGuess);
991  if (densLiq <= 0.0) {
992  // throw Cantera::CanteraError("MixtureFugacityTP::corr0",
993  // "Error occurred trying to find liquid density at (T,P) = "
994  // + Cantera::fp2str(TKelvin) + " " + Cantera::fp2str(pres));
995  retn = -1;
996  } else {
997  densLiqGuess = densLiq;
998  setState_TR(TKelvin, densLiq);
999  liqGRT = gibbs_mole() / _RT();
1000  }
1001 
1002  doublereal densGas = densityCalc(TKelvin, pres, FLUID_GAS, densGasGuess);
1003  if (densGas <= 0.0) {
1004  //throw Cantera::CanteraError("MixtureFugacityTP::corr0",
1005  // "Error occurred trying to find gas density at (T,P) = "
1006  // + Cantera::fp2str(TKelvin) + " " + Cantera::fp2str(pres));
1007  if (retn == -1) {
1008  throw Cantera::CanteraError("MixtureFugacityTP::corr0",
1009  "Error occurred trying to find gas density at (T,P) = "
1010  + Cantera::fp2str(TKelvin) + " " + Cantera::fp2str(pres));
1011  }
1012  retn = -2;
1013  } else {
1014  densGasGuess = densGas;
1015  setState_TR(TKelvin, densGas);
1016  gasGRT = gibbs_mole() / _RT();
1017  }
1018  // delGRT = gibbsLiqRT - gibbsGasRT;
1019  return retn;
1020 }
1021 //====================================================================================================================
1022 // Returns the Phase State flag for the current state of the object
1023 /*
1024  * @param checkState If true, this function does a complete check to see where
1025  * in parameter space we are
1026  *
1027  * There are three values:
1028  * WATER_GAS below the critical temperature but below the critical density
1029  * WATER_LIQUID below the critical temperature but above the critical density
1030  * WATER_SUPERCRIT above the critical temperature
1031  */
1032 int MixtureFugacityTP::phaseState(bool checkState) const
1033 {
1034  int state = iState_;
1035  if (checkState) {
1036  double t = temperature();
1037  double tcrit = critTemperature();
1038  double rhocrit = critDensity();
1039  if (t >= tcrit) {
1040  state = FLUID_SUPERCRIT;
1041  return state;
1042  }
1043  double tmid = tcrit - 100.;
1044  if (tmid < 0.0) {
1045  tmid = tcrit / 2.0;
1046  }
1047  double pp = psatEst(tmid);
1048  double mmw = meanMolecularWeight();
1049  double molVolLiqTmid = liquidVolEst(tmid, pp);
1050  double molVolGasTmid = GasConstant * tmid / (pp);
1051  double densLiqTmid = mmw / molVolLiqTmid;
1052  double densGasTmid = mmw / molVolGasTmid;
1053  double densMidTmid = 0.5 * (densLiqTmid + densGasTmid);
1054  doublereal rhoMid = rhocrit + (t - tcrit) * (rhocrit - densMidTmid) / (tcrit - tmid);
1055 
1056  double rho = density();
1057  int iStateGuess = FLUID_LIQUID_0;
1058  if (rho < rhoMid) {
1059  iStateGuess = FLUID_GAS;
1060  }
1061  double molarVol = mmw / rho;
1062  double presCalc;
1063 
1064  double dpdv = dpdVCalc(t, molarVol, presCalc);
1065  if (dpdv < 0.0) {
1066  state = iStateGuess;
1067  } else {
1068  state = FLUID_UNSTABLE;
1069  }
1070 
1071  }
1072  return state;
1073 }
1074 //====================================================================================================================
1075 // Return the value of the density at the liquid spinodal point (on the liquid side)
1076 // for the current temperature.
1077 /*
1078  * @return returns the density with units of kg m-3
1079  */
1081 {
1082  throw CanteraError("", "unimplmented");
1083  return 0.0;
1084 }
1085 //====================================================================================================================
1086 // Return the value of the density at the gas spinodal point (on the gas side)
1087 // for the current temperature.
1088 /*
1089  * @return returns the density with units of kg m-3
1090  */
1092 {
1093  throw CanteraError("", "unimplmented");
1094  return 0.0;
1095 }
1096 //====================================================================================================================
1097 // Calculate the saturation pressure at the current mixture content for the given temperature
1098 /*
1099  * This is a non-const routine that is public.
1100  *
1101  * The algorithm for this routine has undergone quite a bit of work. It probably needs more work.
1102  * However, it seems now to be fairly robust.
1103  * The key requirement is to find an initial pressure where both the liquid and the gas exist. This
1104  * is not as easy as it sounds, and it gets exceedingly hard as the critical temperature is approached
1105  * from below.
1106  * Once we have this initial state, then we seek to equilibrate the gibbs free energies of the
1107  * gas and liquid and use the formula
1108  *
1109  * dp = VdG
1110  *
1111  * to create an update condition for deltaP using
1112  *
1113  * - (Gliq - Ggas) = (Vliq - Vgas) (deltaP)
1114  *
1115  *
1116  *
1117  * @param TKelvin (input) Temperature (Kelvin)
1118  * @param molarVolGas (return) Molar volume of the gas
1119  * @param molarVolLiquid (return) Molar volume of the liquid
1120  *
1121  * @return Returns the saturation pressure at the given temperature
1122  *
1123  * @TODO Suggestions for the future would be to switch it to an algorithm that uses the gas molar volume
1124  * and the liquid molar volumes as the fundamental unknowns.
1125  *
1126  */
1127 doublereal MixtureFugacityTP::calculatePsat(doublereal TKelvin, doublereal& molarVolGas,
1128  doublereal& molarVolLiquid)
1129 {
1130  // we need this because this is a non-const routine that is public
1131  setTemperature(TKelvin);
1132  double tcrit = critTemperature();
1133  double RhoLiquid, RhoGas;
1134  double RhoLiquidGood, RhoGasGood;
1135  double densSave = density();
1136  double tempSave = temperature();
1137  double pres;
1138  doublereal mw = meanMolecularWeight();
1139  if (TKelvin < tcrit) {
1140 
1141  pres = psatEst(TKelvin);
1142  // trial value = Psat from correlation
1143  int i;
1144  doublereal volLiquid = liquidVolEst(TKelvin, pres);
1145  RhoLiquidGood = mw / volLiquid;
1146  RhoGasGood = pres * mw / (GasConstant * TKelvin);
1147  doublereal delGRT = 1.0E6;
1148  doublereal liqGRT, gasGRT;
1149  int stab;
1150  doublereal presLast = pres;
1151 
1152  /*
1153  * First part of the calculation involves finding a pressure at which the
1154  * gas and the liquid state coexists.
1155  */
1156  doublereal presLiquid;
1157  doublereal presGas;
1158  doublereal presBase = pres;
1159  bool foundLiquid = false;
1160  bool foundGas = false;
1161 
1162  doublereal densLiquid = densityCalc(TKelvin, presBase, FLUID_LIQUID_0, RhoLiquidGood);
1163  if (densLiquid > 0.0) {
1164  foundLiquid = true;
1165  presLiquid = pres;
1166  RhoLiquidGood = densLiquid;
1167  }
1168  if (!foundLiquid) {
1169  for (int i = 0; i < 50; i++) {
1170  pres = 1.1 * pres;
1171  densLiquid = densityCalc(TKelvin, pres, FLUID_LIQUID_0, RhoLiquidGood);
1172  if (densLiquid > 0.0) {
1173  foundLiquid = true;
1174  presLiquid = pres;
1175  RhoLiquidGood = densLiquid;
1176  break;
1177  }
1178  }
1179  }
1180 
1181  pres = presBase;
1182  doublereal densGas = densityCalc(TKelvin, pres, FLUID_GAS, RhoGasGood);
1183  if (densGas <= 0.0) {
1184  foundGas = false;
1185  } else {
1186  foundGas = true;
1187  presGas = pres;
1188  RhoGasGood = densGas;
1189  }
1190  if (!foundGas) {
1191  for (int i = 0; i < 50; i++) {
1192  pres = 0.9 * pres;
1193  densGas = densityCalc(TKelvin, pres, FLUID_GAS, RhoGasGood);
1194  if (densGas > 0.0) {
1195  foundGas = true;
1196  presGas = pres;
1197  RhoGasGood = densGas;
1198  break;
1199  }
1200  }
1201  }
1202 
1203  if (foundGas && foundLiquid) {
1204  if (presGas == presLiquid) {
1205  pres = presGas;
1206  goto startIteration;
1207  }
1208  pres = 0.5 * (presLiquid + presGas);
1209  bool goodLiq;
1210  bool goodGas;
1211  for (int i = 0; i < 50; i++) {
1212 
1213  doublereal densLiquid = densityCalc(TKelvin, pres, FLUID_LIQUID_0, RhoLiquidGood);
1214  if (densLiquid <= 0.0) {
1215  goodLiq = false;
1216  } else {
1217  goodLiq = true;
1218  RhoLiquidGood = densLiquid;
1219  presLiquid = pres;
1220  }
1221  doublereal densGas = densityCalc(TKelvin, pres, FLUID_GAS, RhoGasGood);
1222  if (densGas <= 0.0) {
1223  goodGas = false;
1224  } else {
1225  goodGas = true;
1226  RhoGasGood = densGas;
1227  presGas = pres;
1228  }
1229  if (goodGas && goodLiq) {
1230  break;
1231  }
1232  if (!goodLiq && !goodGas) {
1233  pres = 0.5 * (pres + presLiquid);
1234  }
1235  if (goodLiq || goodGas) {
1236  pres = 0.5 * (presLiquid + presGas);
1237  }
1238 
1239  }
1240  }
1241  if (!foundGas || !foundLiquid) {
1242  printf("error coundn't find a starting pressure\n");
1243  return (0.0);
1244  }
1245  if (presGas != presLiquid) {
1246  printf("error coundn't find a starting pressure\n");
1247  return (0.0);
1248  }
1249 
1250 startIteration:
1251  pres = presGas;
1252  presLast = pres;
1253  RhoGas = RhoGasGood;
1254  RhoLiquid = RhoLiquidGood;
1255 
1256 
1257  /*
1258  * Now that we have found a good pressure we can proceed with the algorithm.
1259  */
1260 
1261  for (i = 0; i < 20; i++) {
1262 
1263  stab = corr0(TKelvin, pres, RhoLiquid, RhoGas, liqGRT, gasGRT);
1264  if (stab == 0) {
1265  presLast = pres;
1266  delGRT = liqGRT - gasGRT;
1267  doublereal delV = mw * (1.0/RhoLiquid - 1.0/RhoGas);
1268  doublereal dp = - delGRT * GasConstant * TKelvin / delV;
1269 
1270  if (fabs(dp) > 0.1 * pres) {
1271  if (dp > 0.0) {
1272  dp = 0.1 * pres;
1273  } else {
1274  dp = -0.1 * pres;
1275  }
1276  }
1277  pres += dp;
1278 
1279  } else if (stab == -1) {
1280  delGRT = 1.0E6;
1281  if (presLast > pres) {
1282  pres = 0.5 * (presLast + pres);
1283  } else {
1284  // we are stuck here - try this
1285  pres = 1.1 * pres;
1286  }
1287  } else if (stab == -2) {
1288  if (presLast < pres) {
1289  pres = 0.5 * (presLast + pres);
1290  } else {
1291  // we are stuck here - try this
1292  pres = 0.9 * pres;
1293  }
1294  }
1295  molarVolGas = mw / RhoGas;
1296  molarVolLiquid = mw / RhoLiquid;
1297 
1298 
1299  if (fabs(delGRT) < 1.0E-8) {
1300  // converged
1301  break;
1302  }
1303  }
1304 
1305  molarVolGas = mw / RhoGas;
1306  molarVolLiquid = mw / RhoLiquid;
1307  // Put the fluid in the desired end condition
1308  setState_TR(tempSave, densSave);
1309 
1310  return pres;
1311 
1312 
1313  } else {
1314  pres = critPressure();
1315  setState_TP(TKelvin, pres);
1316  RhoGas = density();
1317  molarVolGas = mw / RhoGas;
1318  molarVolLiquid = molarVolGas;
1319  setState_TR(tempSave, densSave);
1320  }
1321  return pres;
1322 }
1323 
1324 //====================================================================================================================
1325 // Calculate the pressure given the temperature and the molar volume
1326 doublereal MixtureFugacityTP::pressureCalc(doublereal TKelvin, doublereal molarVol) const
1327 {
1328  throw CanteraError("MixtureFugacityTP::pressureCalc", "unimplemented");
1329  return 0.0;
1330 }
1331 //====================================================================================================================
1332 // Calculate the pressure given the temperature and the molar volume
1333 doublereal MixtureFugacityTP::dpdVCalc(doublereal TKelvin, doublereal molarVol, doublereal& presCalc) const
1334 {
1335  throw CanteraError("MixtureFugacityTP::dpdVCalc", "unimplemented");
1336  return 0.0;
1337 }
1338 //====================================================================================================================
1339 
1340 /*
1341  * void _updateStandardStateThermo() (protected, virtual, const)
1342  *
1343  * If m_useTmpStandardStateStorage is true,
1344  * This function must be called for every call to functions in this
1345  * class that need standard state properties.
1346  * Child classes may require that it be called even if m_useTmpStandardStateStorage
1347  * is not true.
1348  * It checks to see whether the temperature has changed and
1349  * thus the ss thermodynamics functions for all of the species
1350  * must be recalculated.
1351  *
1352  * This
1353  */
1355 {
1356  double Tnow = temperature();
1357 
1358  // If the temperature has changed since the last time these
1359  // properties were computed, recompute them.
1360  if (m_Tlast_ref != Tnow) {
1361  m_spthermo->update(Tnow, &m_cp0_R[0], &m_h0_RT[0], &m_s0_R[0]);
1362  m_Tlast_ref = Tnow;
1363 
1364  // update the species Gibbs functions
1365  for (size_t k = 0; k < m_kk; k++) {
1366  m_g0_RT[k] = m_h0_RT[k] - m_s0_R[k];
1367  }
1368  doublereal pref = refPressure();
1369  if (pref <= 0.0) {
1370  throw CanteraError("MixtureFugacityTP::_updateReferenceStateThermo()", "neg ref pressure");
1371  }
1372  m_logc0 = log(pref/(GasConstant * Tnow));
1373  }
1374 }
1375 //====================================================================================================================
1376 
1377 
1378 }
1379 
1380