Cantera  2.0
Sub.cpp
1 /*
2  * The Substance class
3  * D. Goodwin, Caltech Nov. 1996
4  */
5 #include "cantera/tpx/Sub.h"
6 #include <math.h>
7 #include <fstream>
8 #include <stdio.h>
9 
10 using std::string;
11 
12 namespace tpx
13 {
14 
15 static string fp2str(double x, string fmt="%g")
16 {
17  char buf[30];
18  sprintf(buf, fmt.c_str(), x);
19  return string(buf);
20 }
21 
22 /*
23  static string int2str(int n, string fmt="%d") {
24  char buf[30];
25  sprintf(buf, fmt.c_str(), n);
26  return string(buf);
27  }
28 */
29 
30 string TPX_Error::ErrorMessage = "";
31 string TPX_Error::ErrorProcedure = "";
32 
33 string errorMsg(int flag)
34 {
35  switch (flag) {
36  case NoConverge:
37  return "no convergence";
38  case GenError:
39  return "general error";
40  case InvalidInput:
41  return "invalid input";
42  case TempError:
43  return "temperature error";
44  case PresError:
45  return "pressure error";
46  default:
47  return "(unknown error)";
48  }
49 }
50 
51 //-------------- Public Member Functions --------------
52 
53 Substance::Substance() :
54  T(Undef),
55  Rho(Undef),
56  Tslast(Undef),
57  Rhf(Undef),
58  Rhv(Undef),
59  Pst(Undef),
60  Err(0),
61  m_energy_offset(0.0),
62  m_entropy_offset(0.0),
63  kbr(0)
64 {
65 }
66 
67 /// Pressure [Pa]. If two phases are present, return the
68 /// saturation pressure; otherwise return the pressure
69 /// computed directly from the underlying eos.
70 double Substance::P()
71 {
72  double ppp = (TwoPhase() ? Ps() : Pp());
73  return (Err ? Undef : ppp);
74 }
75 
76 const double DeltaT = 0.000001;
77 
78 /// The derivative of the saturation pressure
79 /// with respect to temperature.
80 double Substance::dPsdT()
81 {
82  double ps1, tsave, dpdt;
83  tsave = T;
84  ps1 = Ps();
85  set_T(T + DeltaT);
86  dpdt = (Ps() - ps1)/DeltaT;
87  set_T(tsave);
88  return (Err ? Undef : dpdt);
89 }
90 
91 /// true if a liquid/vapor mixture, false otherwise
92 int Substance::TwoPhase()
93 {
94  if (T >= Tcrit()) {
95  return 0;
96  }
97  update_sat();
98  return ((Rho < Rhf) && (Rho > Rhv) ? 1 : 0);
99 }
100 
101 /// Vapor fraction.
102 /// If T >= Tcrit, 0 is returned for v < Vcrit, and 1 is
103 /// returned if v > Vcrit.
104 double Substance::x()
105 {
106  double xx, vv, vl;
107  if (T >= Tcrit()) {
108  return (1.0/Rho < Vcrit() ? 0.0 : 1.0);
109  } else {
110  update_sat();
111  if (Rho <= Rhv) {
112  xx = 1.0;
113  } else if (Rho >= Rhf) {
114  xx = 0.0;
115  } else {
116  vv = 1.0/Rhv;
117  vl = 1.0/Rhf;
118  xx = (1.0/Rho - vl)/(vv - vl);
119  }
120  return (Err ? Undef : xx);
121  }
122 }
123 
124 /// Saturation temperature at pressure p.
125 double Substance::Tsat(double p)
126 {
127  double Tsave, p_here, dp, dt, dpdt, dta,
128  dtm, tsat;
129  if (Err || (p <= 0.0) || (p > Pcrit())) {
130  throw TPX_Error("Substance::Tsat","illegal pressure value");
131  set_Err(PresError);
132  return Undef;
133  }
134  int LoopCount = 0;
135  double tol = 1.e-6*p;
136  Tsave = T;
137  if (T < Tmin()) {
138  T = 0.5*(Tcrit() - Tmin());
139  }
140  if (T >= Tcrit()) {
141  T = 0.5*(Tcrit() - Tmin());
142  }
143  do {
144  if (Err) {
145  break;
146  }
147  if (T > Tcrit()) {
148  T = Tcrit() - 0.001;
149  }
150  if (T < Tmin()) {
151  T = Tmin() + 0.001;
152  }
153  p_here = Ps();
154  dpdt = dPsdT();
155  dp = p - p_here;
156  dt = dp/dpdt;
157  dta = fabs(dt);
158  dtm = 0.1*T;
159  if (dta > dtm) {
160  dt = dt*dtm/dta;
161  }
162  T = T + dt;
163  LoopCount++;
164  if (LoopCount > 100) {
165  T = Tsave;
166  set_Err(NoConverge);
167  return Undef;
168  }
169  } while (fabs(dp) > tol);
170  tsat = T;
171  T = Tsave;
172  return (Err ? Undef : tsat);
173 }
174 
175 
176 // absolute tolerances
177 
178 static const double TolAbsH = 0.0001; // J/kg
179 static const double TolAbsU = 0.0001;
180 static const double TolAbsS = 1.e-7;
181 static const double TolAbsP = 0.000; // Pa
182 static const double TolAbsV = 1.e-8;
183 static const double TolAbsT = 1.e-3;
184 static const double TolRel = 3.e-8;
185 
186 void Substance::Set(int XY, double x0, double y0)
187 {
188  double temp;
189 
190  clear_Err(); // clear error flag
191 
192  /* if inverted (PT) switch order and change sign of XY (TP = -PT) */
193  if (XY < 0) {
194  double tmp = x0;
195  x0 = y0;
196  y0 = tmp;
197  XY *= -1;
198  }
199 
200  switch (XY) {
201  case TV:
202  set_T(x0);
203  set_v(y0);
204  break;
205 
206  case HP:
207  if (Lever(Pgiven, y0, x0, EvalH)) {
208  return;
209  }
210  set_xy(EvalH, EvalP, x0, y0, TolAbsH, TolAbsP, TolRel, TolRel);
211  break;
212 
213  case SP:
214  if (Lever(Pgiven, y0, x0, EvalS)) {
215  return;
216  }
217  set_xy(EvalS, EvalP, x0, y0, TolAbsS, TolAbsP, TolRel, TolRel);
218  break;
219 
220  case PV:
221  if (Lever(Pgiven, x0, y0, EvalV)) {
222  return;
223  }
224  set_xy(EvalP, EvalV, x0, y0, TolAbsP, TolAbsV, TolRel, TolRel);
225  break;
226 
227  case TP:
228  if (x0 < Tcrit()) {
229  set_T(x0);
230  if (y0 < Ps()) {
231  Set(TX, x0, Vapor);
232  } else {
233  Set(TX, x0, Liquid);
234  }
235  } else {
236  set_T(x0);
237  }
238  set_xy(EvalT, EvalP, x0, y0, TolAbsT, TolAbsP, TolRel, TolRel);
239  break;
240 
241  case UV:
242  set_xy(EvalU, EvalV, x0, y0, TolAbsU, TolAbsV, TolRel, TolRel);
243  break;
244 
245  case ST:
246  if (Lever(Tgiven, y0, x0, EvalS)) {
247  return;
248  }
249  set_xy(EvalS, EvalT, x0, y0, TolAbsS, TolAbsT, TolRel, TolRel);
250  break;
251 
252  case SV:
253  set_xy(EvalS, EvalV, x0, y0, TolAbsS, TolAbsV, TolRel, TolRel);
254  break;
255 
256  case UP:
257  if (Lever(Pgiven, y0, x0, EvalU)) {
258  return;
259  }
260  set_xy(EvalU, EvalP, x0, y0, TolAbsU, TolAbsP, TolRel, TolRel);
261  break;
262 
263  case VH:
264  set_xy(EvalV, EvalH, x0, y0, TolAbsV, TolAbsH, TolRel, TolRel);
265  break;
266 
267  case TH:
268  set_xy(EvalT, EvalH, x0, y0, TolAbsT, TolAbsH, TolRel, TolRel);
269  break;
270 
271  case SH:
272  set_xy(EvalS, EvalH, x0, y0, TolAbsS, TolAbsH, TolRel, TolRel);
273  break;
274 
275  case PX:
276  temp = Tsat(x0);
277  if ((y0 >= 0.0) && (y0 <= 1.0) && (temp < Tcrit())) {
278  set_T(temp);
279  update_sat();
280  Rho = 1.0/((1.0 - y0)/Rhf + y0/Rhv);
281  } else {
282  set_Err(InvalidInput);
283  }
284  break;
285 
286  case TX:
287  if ((y0 >= 0.0) && (y0 <= 1.0) && (x0 < Tcrit())) {
288  set_T(x0);
289  update_sat();
290  Rho = 1.0/((1.0 - y0)/Rhf + y0/Rhv);
291  } else {
292  set_Err(InvalidInput);
293  }
294  break;
295 
296  default:
297  set_Err(InvalidInput);
298  }
299  if (Err) {
300  T = Undef;
301  Rho = Undef;
302  Tslast = Undef;
303  Rhf = Undef;
304  Rhv = Undef;
305  }
306 }
307 
308 //------------------ Protected and Private Functions -------------------
309 
310 void Substance::set_Rho(double r0)
311 {
312  if (r0 > 0.0) {
313  Rho = r0;
314  } else {
315  set_Err(InvalidInput);
316  }
317 }
318 
319 void Substance::set_T(double t0)
320 {
321  if ((t0 >= Tmin()) && (t0 <= Tmax())) {
322  T = t0;
323  } else {
324  throw TPX_Error("Substance::set_T",
325  "illegal temperature value "+fp2str(t0));
326  set_Err(TempError);
327  }
328 }
329 
330 void Substance::set_v(double v0)
331 {
332  if (v0 > 0) {
333  Rho = 1.0/v0;
334  } else {
335  throw TPX_Error("Substance::set_v",
336  "negative specific volume: "+fp2str(v0));
337  set_Err(InvalidInput);
338  }
339 }
340 
341 double Substance::Ps()
342 {
343  if (T < Tmin() || T > Tcrit()) {
344  throw TPX_Error("Substance::Ps",
345  "illegal temperature value "+fp2str(T));
346  set_Err(TempError);
347  return Undef;
348  }
349  update_sat();
350  return Pst;
351 }
352 
353 // update saturated liquid and vapor densities and saturation pressure
354 
355 void Substance::Set_meta(double phase, double pp)
356 {
357  if (phase == Liquid) {
358  Rho = ldens(); // trial value = liquid dens
359  } else {
360  Rho = pp*MolWt()/(8314.0*T); // trial value = ideal gas
361  }
362  set_TPp(T, pp);
363 }
364 
365 void Substance::update_sat()
366 {
367  if ((T != Tslast) && (T < Tcrit())) {
368  double Rho_save = Rho;
369  double gf, gv, dg, dp, dlp, psold;
370 
371  double pp = Psat();
372  double lps = log(pp);
373  // trial value = Psat from correlation
374  int i;
375 
376  for (i = 0; i<20; i++) {
377  if (i==0) {
378  Rho = ldens(); // trial value = liquid density
379  } else {
380  Rho = Rhf;
381  }
382  set_TPp(T,pp);
383  Rhf = Rho; // sat liquid density
384 
385  gf = hp() - T*sp();
386  if (i==0) {
387  Rho = pp*MolWt()/(8314.0*T); // trial value = ideal gas
388  } else {
389  Rho = Rhv;
390  }
391  set_TPp(T,pp);
392 
393  Rhv = Rho; // sat vapor density
394  gv = hp() - T*sp();
395  dg = gv - gf;
396 
397  if (Rhv > Rhf) {
398  std::swap(Rhv, Rhf);
399  dg = - dg;
400  }
401 
402  if (fabs(dg) < 0.001 && Rhf > Rhv) {
403  break;
404  }
405  dp = dg/(1.0/Rhv - 1.0/Rhf);
406  psold = pp;
407 
408  if (fabs(dp) > pp) {
409  dlp = dg/(pp*(1.0/Rhv - 1.0/Rhf));
410  lps -= dlp;
411  pp = exp(lps);
412  } else {
413  pp -= dp;
414  lps = log(pp);
415  }
416  if (pp > Pcrit()) {
417  pp = psold + 0.5*(Pcrit() - psold);
418  lps = log(pp);
419  } else if (pp < 0.0) {
420  pp = psold/2.0;
421  lps = log(pp);
422  }
423  }
424  if (Rhf <= Rhv) {
425  throw TPX_Error("Substance::update_sat",
426  "wrong root found for sat. liquid or vapor at P = "+fp2str(pp));
427  }
428 
429  if (i >= 20) {
430  Pst = Undef;
431  Rhv = Undef;
432  Rhf = Undef;
433  Tslast = Undef;
434  throw TPX_Error("substance::update_sat","no convergence");
435  set_Err(NoConverge);
436  } else {
437  Pst = pp;
438  Tslast = T;
439  }
440  Rho = Rho_save;
441  }
442 }
443 
444 double Substance::vprop(int ijob)
445 {
446  switch (ijob) {
447  case EvalH:
448  return hp();
449  case EvalS:
450  return sp();
451  case EvalU:
452  return up();
453  case EvalV:
454  return vp();
455  case EvalP:
456  return Pp();
457  default:
458  return Undef;
459  }
460 }
461 
462 int Substance::Lever(int itp, double sat, double val, int ifunc)
463 {
464  /*
465  * uses lever rule to set state in the dome. Returns 1 if in dome,
466  * 0 if not, in which case state not set.
467  */
468  double Valf, Valg, Tsave, Rhosave, xx, vv, psat;
469  Tsave = T;
470  Rhosave = Rho;
471  if (itp == Tgiven) {
472  if (sat >= Tcrit()) {
473  return 0;
474  }
475  set_T(sat);
476  psat = Ps();
477  } else if (itp == Pgiven) {
478  if (sat >= Pcrit()) {
479  return 0;
480  }
481  psat = sat;
482  T = Tsat(psat);
483  if (T == Undef) {
484  Err = 0;
485  T = Tsave;
486  Rho = Rhosave;
487  return 0;
488  }
489  } else {
490  throw TPX_Error("Substance::Lever","general error");
491  set_Err(GenError);
492  return GenError;
493  }
494  Set(TX, T, Vapor);
495  Valg = vprop(ifunc);
496  Set(TX, T, Liquid);
497  Valf = vprop(ifunc);
498  if (Err) {
499  return Err;
500  } else if ((val >= Valf) && (val <= Valg)) {
501  xx = (val - Valf)/(Valg - Valf);
502  vv = (1.0 - xx)/Rhf + xx/Rhv;
503  set_v(vv);
504  return 1;
505  } else {
506  T = Tsave;
507  Rho = Rhosave;
508  return 0;
509  }
510 }
511 
512 
513 void Substance::set_xy(int ifx, int ify, double X, double Y,
514  double atx, double aty,
515  double rtx, double rty)
516 {
517 
518  double v_here, t_here, dv, dt, dxdt, dydt, dxdv, dydv,
519  det, x_here, y_here, dvm, dtm, dva, dta;
520  double Xa, Ya, err_x, err_y;
521  double dvs1 = 2.0*Vcrit();
522  double dvs2 = 0.7*Vcrit();
523  int LoopCount = 0;
524 
525  double v_save = 1.0/Rho;
526  double t_save = T;
527 
528  if (Err) {
529  return;
530  }
531 
532  if ((T == Undef) && (Rho == Undef)) { // new object, try to pick
533  Set(TV,Tcrit()*1.1,Vcrit()*1.1); // "reasonable" starting point
534  t_here = T;
535  v_here = 1.0/Rho;
536  } else if (Rho == Undef) { // new object, try to pick
537  Set(TV,T,Vcrit()*1.1); // "reasonable" starting point
538  t_here = T;
539  v_here = 1.0/Rho;
540  } else {
541  v_here = v_save;
542  t_here = t_save;
543  }
544 
545  Xa = fabs(X);
546  Ya = fabs(Y);
547 
548 
549  // loop
550  do {
551  if (Err) {
552  break;
553  }
554  x_here = prop(ifx);
555  y_here = prop(ify);
556  err_x = fabs(X - x_here);
557  err_y = fabs(Y - y_here);
558 
559  if ((err_x < atx + rtx*Xa) && (err_y < aty + rty*Ya)) {
560  break;
561  }
562 
563  /* perturb t */
564  dt = 0.001*t_here;
565  if (t_here + dt > Tmax()) {
566  dt *= -1.0;
567  }
568 
569  /* perturb v */
570  dv = 0.001*v_here;
571  if (v_here <= Vcrit()) {
572  dv *= -1.0;
573  }
574 
575  /* derivatives with respect to T */
576  Set(TV, t_here + dt, v_here);
577  dxdt = (prop(ifx) - x_here)/dt;
578  dydt = (prop(ify) - y_here)/dt;
579 
580  /* derivatives with respect to v */
581  Set(TV, t_here, v_here + dv);
582  dxdv = (prop(ifx) - x_here)/dv;
583  dydv = (prop(ify) - y_here)/dv;
584 
585  det = dxdt*dydv - dydt*dxdv;
586  dt = ((X - x_here)*dydv - (Y - y_here)*dxdv)/det;
587  dv = ((Y - y_here)*dxdt - (X - x_here)*dydt)/det;
588 
589  dvm = 0.2*v_here;
590  if (v_here < dvs1) {
591  dvm *= 0.5;
592  }
593  if (v_here < dvs2) {
594  dvm *= 0.5;
595  }
596  dtm = 0.1*t_here;
597  dva = fabs(dv);
598  dta = fabs(dt);
599  if (dva > dvm) {
600  dv *= dvm/dva;
601  }
602  if (dta > dtm) {
603  dt *= dtm/dta;
604  }
605  v_here += dv;
606  t_here += dt;
607  if (t_here >= Tmax()) {
608  t_here = Tmax() - 0.001;
609  } else if (t_here <= Tmin()) {
610  t_here = Tmin() + 0.001;
611  }
612  if (v_here <= 0.0) {
613  v_here = 0.0001;
614  }
615  Set(TV, t_here, v_here);
616  LoopCount++;
617  if (LoopCount > 200) {
618  throw TPX_Error("Substance::set_xy","no convergence");
619  set_Err(NoConverge);
620  break;
621  }
622  } while (1);
623 }
624 
625 
626 double Substance::prop(int ijob)
627 {
628  double xx, pp, lp, vp, Rho_save;
629  if (ijob == EvalP) {
630  return P();
631  }
632  if (ijob == EvalT) {
633  return T;
634  }
635  xx = x();
636  if ((xx > 0.0) && (xx < 1.0)) {
637  Rho_save = Rho;
638  Rho = Rhv;
639  vp = vprop(ijob);
640  Rho = Rhf;
641  lp = vprop(ijob);
642  pp = (1.0 - xx)*lp + xx*vp;
643  Rho = Rho_save;
644  return pp;
645  } else {
646  return vprop(ijob);
647  }
648 }
649 
650 static const double ErrP = 1.e-7;
651 static const double Big = 1.e30;
652 
653 void Substance::BracketSlope(double Pressure)
654 {
655  if (kbr == 0) {
656  dv = (v_here < Vcrit() ? -0.05*v_here : 0.2*v_here);
657  if (Vmin > 0.0) {
658  dv = 0.2*v_here;
659  }
660  if (Vmax < Big) {
661  dv = -0.05*v_here;
662  }
663  } else {
664  double dpdv = (Pmax - Pmin)/(Vmax - Vmin);
665  v_here = Vmax;
666  P_here = Pmax;
667  dv = dvbf*(Pressure - P_here)/dpdv;
668  dvbf = 0.5*dvbf;
669  }
670 }
671 
672 void Substance::set_TPp(double Temp, double Pressure)
673 {
674  kbr = 0;
675  dvbf = 1.0;
676  Vmin = 0.0;
677  Vmax = Big;
678  Pmin = Big;
679  Pmax = 0.0;
680  double dvs1 = 2.0*Vcrit();
681  double dvs2 = 0.7*Vcrit();
682  int LoopCount = 0;
683 
684  double v_save = 1.0/Rho;
685  set_T(Temp);
686  v_here = vp();
687 
688  // loop
689  while (P_here = Pp(),
690  fabs(Pressure - P_here) >= ErrP*Pressure || LoopCount == 0) {
691  if (P_here < 0.0) {
692  BracketSlope(Pressure);
693  } else {
694  dv = 0.001*v_here;
695  if (v_here <= Vcrit()) {
696  dv *= -1.0;
697  }
698  Set(TV, Temp, v_here+dv);
699  double dpdv = (Pp() - P_here)/dv;
700  if (dpdv > 0.0) {
701  BracketSlope(Pressure);
702  } else {
703  if ((P_here > Pressure) && (v_here > Vmin)) {
704  Vmin = v_here;
705  } else if ((P_here < Pressure) && (v_here < Vmax)) {
706  Vmax = v_here;
707  }
708  if (v_here == Vmin) {
709  Pmin = P_here;
710  }
711  if (v_here == Vmax) {
712  Pmax = P_here;
713  }
714  if (Vmin >= Vmax) {
715  throw TPX_Error("Substance::set_TPp","Vmin >= Vmax");
716  set_Err(GenError);
717  } else if ((Vmin > 0.0) && (Vmax < Big)) {
718  kbr = 1;
719  }
720  dvbf = 1.0;
721  if (dpdv == 0.0) {
722  dvbf = 0.5;
723  BracketSlope(Pressure);
724  } else {
725  dv = (Pressure - P_here)/dpdv;
726  }
727  }
728  }
729  double dvm = 0.2*v_here;
730  if (v_here < dvs1) {
731  dvm *= 0.5;
732  }
733  if (v_here < dvs2) {
734  dvm *= 0.5;
735  }
736  if (kbr != 0) {
737  double vt = v_here + dv;
738  if ((vt < Vmin) || (vt > Vmax)) {
739  dv = Vmin + (Pressure - Pmin)*(Vmax - Vmin)/(Pmax - Pmin) - v_here;
740  }
741  }
742  double dva = fabs(dv);
743  if (dva > dvm) {
744  dv *= dvm/dva;
745  }
746  v_here += dv;
747  if (dv == 0.0) {
748  throw TPX_Error("Substance::set_TPp","dv = 0 and no convergence");
749  set_Err(NoConverge);
750  return;
751  }
752  Set(TV, Temp, v_here);
753  LoopCount++;
754  if (LoopCount > 100) {
755  Set(TV, Temp, v_save);
756  throw TPX_Error("Substance::set_TPp",string("no convergence for ")
757  +"P* = "+fp2str(Pressure/Pcrit())+". V* = "
758  +fp2str(v_save/Vcrit()));
759  set_Err(NoConverge);
760  return;
761  }
762  }
763  Set(TV, Temp,v_here);
764 }
765 }