PengRobinson.hpp
Go to the documentation of this file.
1 // -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 // vi: set et ts=4 sw=4 sts=4:
3 /*
4  Copyright (C) 2011-2013 by Andreas Lauser
5 
6  This file is part of the Open Porous Media project (OPM).
7 
8  OPM is free software: you can redistribute it and/or modify
9  it under the terms of the GNU General Public License as published by
10  the Free Software Foundation, either version 2 of the License, or
11  (at your option) any later version.
12 
13  OPM is distributed in the hope that it will be useful,
14  but WITHOUT ANY WARRANTY; without even the implied warranty of
15  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16  GNU General Public License for more details.
17 
18  You should have received a copy of the GNU General Public License
19  along with OPM. If not, see <http://www.gnu.org/licenses/>.
20 */
26 #ifndef OPM_PENG_ROBINSON_HPP
27 #define OPM_PENG_ROBINSON_HPP
28 
32 
35 
36 #include <csignal>
37 
38 namespace Opm {
39 
53 template <class Scalar>
55 {
57  static const Scalar R;
58 
59  PengRobinson()
60  { }
61 
62 public:
63  static void init(Scalar /*aMin*/, Scalar /*aMax*/, unsigned /*na*/,
64  Scalar /*bMin*/, Scalar /*bMax*/, unsigned /*nb*/)
65  {
66 /*
67  // resize the tabulation for the critical points
68  criticalTemperature_.resize(aMin, aMax, na, bMin, bMax, nb);
69  criticalPressure_.resize(aMin, aMax, na, bMin, bMax, nb);
70  criticalMolarVolume_.resize(aMin, aMax, na, bMin, bMax, nb);
71 
72  Scalar VmCrit, pCrit, TCrit;
73  for (unsigned i = 0; i < na; ++i) {
74  Scalar a = criticalTemperature_.iToX(i);
75  assert(std::abs(criticalTemperature_.xToI(criticalTemperature_.iToX(i)) - i) < 1e-10);
76 
77  for (unsigned j = 0; j < nb; ++j) {
78  Scalar b = criticalTemperature_.jToY(j);
79  assert(std::abs(criticalTemperature_.yToJ(criticalTemperature_.jToY(j)) - j) < 1e-10);
80 
81  findCriticalPoint_(TCrit, pCrit, VmCrit, a, b);
82 
83  criticalTemperature_.setSamplePoint(i, j, TCrit);
84  criticalPressure_.setSamplePoint(i, j, pCrit);
85  criticalMolarVolume_.setSamplePoint(i, j, VmCrit);
86  }
87  }
88  */
89  }
90 
99  template <class Params>
100  static Scalar computeVaporPressure(const Params &params, Scalar T)
101  {
102  typedef typename Params::Component Component;
105 
106  // initial guess of the vapor pressure
107  Scalar Vm[3];
108  const Scalar eps = Component::criticalPressure()*1e-10;
109 
110  // use the Ambrose-Walton method to get an initial guess of
111  // the vapor pressure
112  Scalar pVap = ambroseWalton_(params, T);
113 
114  // Newton-Raphson method
115  for (unsigned i = 0; i < 5; ++i) {
116  // calculate the molar densities
117  OPM_OPTIM_UNUSED int numSol = molarVolumes(Vm, params, T, pVap);
118  assert(numSol == 3);
119 
120  Scalar f = fugacityDifference_(params, T, pVap, Vm[0], Vm[2]);
121  Scalar df_dp =
122  fugacityDifference_(params, T, pVap + eps, Vm[0], Vm[2])
123  -
124  fugacityDifference_(params, T, pVap - eps, Vm[0], Vm[2]);
125  df_dp /= 2*eps;
126 
127  Scalar delta = f/df_dp;
128  pVap = pVap - delta;
129 
130  if (std::abs(delta/pVap) < 1e-10)
131  break;
132  }
133 
134  return pVap;
135  }
136 
141  template <class FluidState, class Params>
142  static Scalar computeMolarVolume(const FluidState &fs,
143  Params &params,
144  unsigned phaseIdx,
145  bool isGasPhase)
146  {
147  Valgrind::CheckDefined(fs.temperature(phaseIdx));
148  Valgrind::CheckDefined(fs.pressure(phaseIdx));
149 
150  Scalar Vm = 0;
152 
153  Scalar T = fs.temperature(phaseIdx);
154  Scalar p = fs.pressure(phaseIdx);
155 
156  Scalar a = params.a(phaseIdx); // "attractive factor"
157  Scalar b = params.b(phaseIdx); // "co-volume"
158 
159  if (!std::isfinite(a) || std::abs(a) < 1e-30)
160  return std::numeric_limits<Scalar>::quiet_NaN();
161  if (!std::isfinite(b) || b <= 0)
162  return std::numeric_limits<Scalar>::quiet_NaN();
163 
164  Scalar RT= R*T;
165  Scalar Astar = a*p/(RT*RT);
166  Scalar Bstar = b*p/RT;
167 
168  Scalar a1 = 1.0;
169  Scalar a2 = - (1 - Bstar);
170  Scalar a3 = Astar - Bstar*(3*Bstar + 2);
171  Scalar a4 = Bstar*(- Astar + Bstar*(1 + Bstar));
172 
173  // ignore the first two results if the smallest
174  // compressibility factor is <= 0.0. (this means that if we
175  // would get negative molar volumes for the liquid phase, we
176  // consider the liquid phase non-existant.)
177  Scalar Z[3] = {0.0,0.0,0.0};
182  int numSol = invertCubicPolynomial(Z, a1, a2, a3, a4);
183  if (numSol == 3) {
184  // the EOS has three intersections with the pressure,
185  // i.e. the molar volume of gas is the largest one and the
186  // molar volume of liquid is the smallest one
187  if (isGasPhase)
188  Vm = Z[2]*RT/p;
189  else
190  Vm = Z[0]*RT/p;
191  }
192  else if (numSol == 1) {
193  // the EOS only has one intersection with the pressure,
194  // for the other phase, we take the extremum of the EOS
195  // with the largest distance from the intersection.
196  Scalar VmCubic = Z[0]*RT/p;
197  Vm = VmCubic;
198 
199  // find the extrema (if they are present)
200  Scalar Vmin, Vmax, pmin, pmax;
201  if (findExtrema_(Vmin, Vmax,
202  pmin, pmax,
203  a, b, T))
204  {
205  if (isGasPhase)
206  Vm = std::max(Vmax, VmCubic);
207  else {
208  if (Vmin > 0)
209  Vm = std::min(Vmin, VmCubic);
210  else
211  Vm = VmCubic;
212  }
213  }
214  else {
215  // the EOS does not exhibit any physically meaningful
216  // extrema, and the fluid is critical...
217  Vm = VmCubic;
218  handleCriticalFluid_(Vm, fs, params, phaseIdx, isGasPhase);
219  }
220  }
221 
223  assert(std::isfinite(Vm));
224  assert(Vm > 0);
225  return Vm;
226  }
227 
238  template <class Params>
239  static Scalar computeFugacityCoeffient(const Params &params)
240  {
241  Scalar T = params.temperature();
242  Scalar p = params.pressure();
243  Scalar Vm = params.molarVolume();
244 
245  Scalar RT = R*T;
246  Scalar Z = p*Vm/RT;
247  Scalar Bstar = p*params.b() / RT;
248 
249  Scalar tmp =
250  (Vm + params.b()*(1 + std::sqrt(2))) /
251  (Vm + params.b()*(1 - std::sqrt(2)));
252  Scalar expo = - params.a()/(RT * 2 * params.b() * std::sqrt(2));
253  Scalar fugCoeff =
254  std::exp(Z - 1) / (Z - Bstar) *
255  std::pow(tmp, expo);
256 
257  return fugCoeff;
258  }
259 
270  template <class Params>
271  static Scalar computeFugacity(const Params &params)
272  { return params.pressure()*computeFugacityCoeff(params); }
273 
274 protected:
275  template <class FluidState, class Params>
276  static void handleCriticalFluid_(Scalar &Vm,
277  const FluidState &/*fs*/,
278  const Params &params,
279  unsigned phaseIdx,
280  bool isGasPhase)
281  {
282  Scalar Tcrit, pcrit, Vcrit;
283  findCriticalPoint_(Tcrit,
284  pcrit,
285  Vcrit,
286  params.a(phaseIdx),
287  params.b(phaseIdx));
288 
289 
290  //Scalar Vcrit = criticalMolarVolume_.eval(params.a(phaseIdx), params.b(phaseIdx));
291 
292  if (isGasPhase)
293  Vm = std::max(Vm, Vcrit);
294  else
295  Vm = std::min(Vm, Vcrit);
296  }
297 
298  static void findCriticalPoint_(Scalar &Tcrit,
299  Scalar &pcrit,
300  Scalar &Vcrit,
301  Scalar a,
302  Scalar b)
303  {
304  Scalar minVm(0);
305  Scalar maxVm(1e100);
306 
307  Scalar minP(0);
308  Scalar maxP(1e100);
309 
310  // first, we need to find an isotherm where the EOS exhibits
311  // a maximum and a minimum
312  Scalar Tmin = 250; // [K]
313  for (unsigned i = 0; i < 30; ++i) {
314  bool hasExtrema = findExtrema_(minVm, maxVm, minP, maxP, a, b, Tmin);
315  if (hasExtrema)
316  break;
317  Tmin /= 2;
318  };
319 
320  Scalar T = Tmin;
321 
322  // Newton's method: Start at minimum temperature and minimize
323  // the "gap" between the extrema of the EOS
324  unsigned iMax = 100;
325  for (unsigned i = 0; i < iMax; ++i) {
326  // calculate function we would like to minimize
327  Scalar f = maxVm - minVm;
328 
329  // check if we're converged
330  if (f < 1e-10 || (i == iMax - 1 && f < 1e-8)) {
331  Tcrit = T;
332  pcrit = (minP + maxP)/2;
333  Vcrit = (maxVm + minVm)/2;
334  return;
335  }
336 
337  // backward differences. Forward differences are not
338  // robust, since the isotherm could be critical if an
339  // epsilon was added to the temperature. (this is case
340  // rarely happens, though)
341  const Scalar eps = - 1e-11;
342  bool OPM_OPTIM_UNUSED hasExtrema = findExtrema_(minVm, maxVm, minP, maxP, a, b, T + eps);
343  assert(hasExtrema);
344  assert(std::isfinite(maxVm));
345  Scalar fStar = maxVm - minVm;
346 
347  // derivative of the difference between the maximum's
348  // molar volume and the minimum's molar volume regarding
349  // temperature
350  Scalar fPrime = (fStar - f)/eps;
351  if (std::abs(fPrime) < 1e-40) {
352  Tcrit = T;
353  pcrit = (minP + maxP)/2;
354  Vcrit = (maxVm + minVm)/2;
355  return;
356  }
357 
358  // update value for the current iteration
359  Scalar delta = f/fPrime;
360  assert(std::isfinite(delta));
361  if (delta > 0)
362  delta = -10;
363 
364  // line search (we have to make sure that both extrema
365  // still exist after the update)
366  for (unsigned j = 0; ; ++j) {
367  if (j >= 20) {
368  if (f < 1e-8) {
369  Tcrit = T;
370  pcrit = (minP + maxP)/2;
371  Vcrit = (maxVm + minVm)/2;
372  return;
373  }
374  OPM_THROW(NumericalProblem,
375  "Could not determine the critical point for a=" << a << ", b=" << b);
376  }
377 
378  if (findExtrema_(minVm, maxVm, minP, maxP, a, b, T - delta)) {
379  // if the isotherm for T - delta exhibits two
380  // extrema the update is finished
381  T -= delta;
382  break;
383  }
384  else
385  delta /= 2;
386  };
387  }
388 
389  assert(false);
390  }
391 
392  // find the two molar volumes where the EOS exhibits extrema and
393  // which are larger than the covolume of the phase
394  static bool findExtrema_(Scalar &Vmin,
395  Scalar &Vmax,
396  Scalar &/*pMin*/,
397  Scalar &/*pMax*/,
398  Scalar a,
399  Scalar b,
400  Scalar T)
401  {
402  Scalar u = 2;
403  Scalar w = -1;
404 
405  Scalar RT = R*T;
406 
407  // calculate coefficients of the 4th order polynominal in
408  // monomial basis
409  Scalar a1 = RT;
410  Scalar a2 = 2*RT*u*b - 2*a;
411  Scalar a3 = 2*RT*w*b*b + RT*u*u*b*b + 4*a*b - u*a*b;
412  Scalar a4 = 2*RT*u*w*b*b*b + 2*u*a*b*b - 2*a*b*b;
413  Scalar a5 = RT*w*w*b*b*b*b - u*a*b*b*b;
414 
415  assert(std::isfinite(a1));
416  assert(std::isfinite(a2));
417  assert(std::isfinite(a3));
418  assert(std::isfinite(a4));
419  assert(std::isfinite(a5));
420 
421  // Newton method to find first root
422 
423  // if the values which we got on Vmin and Vmax are usefull, we
424  // will reuse them as initial value, else we will start 10%
425  // above the covolume
426  Scalar V = b*1.1;
427  Scalar delta = 1.0;
428  for (unsigned i = 0; std::abs(delta) > 1e-12; ++i) {
429  Scalar f = a5 + V*(a4 + V*(a3 + V*(a2 + V*a1)));
430  Scalar fPrime = a4 + V*(2*a3 + V*(3*a2 + V*4*a1));
431 
432  if (std::abs(fPrime) < 1e-20) {
433  // give up if the derivative is zero
434  return false;
435  }
436 
437 
438  delta = f/fPrime;
439  V -= delta;
440 
441  if (i > 200) {
442  // give up after 200 iterations...
443  return false;
444  }
445  }
446  assert(std::isfinite(V));
447 
448  // polynomial division
449  Scalar b1 = a1;
450  Scalar b2 = a2 + V*b1;
451  Scalar b3 = a3 + V*b2;
452  Scalar b4 = a4 + V*b3;
453 
454  // invert resulting cubic polynomial analytically
455  Scalar allV[4];
456  allV[0] = V;
457  int numSol = 1 + Opm::invertCubicPolynomial<Scalar>(allV + 1, b1, b2, b3, b4);
458 
459  // sort all roots of the derivative
460  std::sort(allV + 0, allV + numSol);
461 
462  // check whether the result is physical
463  if (numSol != 4 || allV[numSol - 2] < b) {
464  // the second largest extremum is smaller than the phase's
465  // covolume which is physically impossible
466  return false;
467  }
468 
469 
470  // it seems that everything is okay...
471  Vmin = allV[numSol - 2];
472  Vmax = allV[numSol - 1];
473  return true;
474  }
475 
488  template <class Params>
489  static Scalar ambroseWalton_(const Params &/*params*/, Scalar T)
490  {
491  typedef typename Params::Component Component;
492 
493  Scalar Tr = T / Component::criticalTemperature();
494  Scalar tau = 1 - Tr;
495  Scalar omega = Component::acentricFactor();
496 
497  Scalar f0 = (tau*(-5.97616 + std::sqrt(tau)*(1.29874 - tau*0.60394)) - 1.06841*std::pow(tau, 5))/Tr;
498  Scalar f1 = (tau*(-5.03365 + std::sqrt(tau)*(1.11505 - tau*5.41217)) - 7.46628*std::pow(tau, 5))/Tr;
499  Scalar f2 = (tau*(-0.64771 + std::sqrt(tau)*(2.41539 - tau*4.26979)) + 3.25259*std::pow(tau, 5))/Tr;
500 
501  return Component::criticalPressure()*std::exp(f0 + omega * (f1 + omega*f2));
502  }
503 
514  template <class Params>
515  static Scalar fugacityDifference_(const Params &params,
516  Scalar T,
517  Scalar p,
518  Scalar VmLiquid,
519  Scalar VmGas)
520  { return fugacity(params, T, p, VmLiquid) - fugacity(params, T, p, VmGas); }
521 
522 /*
523  static UniformTabulated2DFunction<Scalar> criticalTemperature_;
524  static UniformTabulated2DFunction<Scalar> criticalPressure_;
525  static UniformTabulated2DFunction<Scalar> criticalMolarVolume_;
526 */
527 };
528 
529 template <class Scalar>
530 const Scalar PengRobinson<Scalar>::R = Opm::Constants<Scalar>::R;
531 
532 /*
533 template <class Scalar>
534 UniformTabulated2DFunction<Scalar> PengRobinson<Scalar>::criticalTemperature_;
535 
536 template <class Scalar>
537 UniformTabulated2DFunction<Scalar> PengRobinson<Scalar>::criticalPressure_;
538 
539 template <class Scalar>
540 UniformTabulated2DFunction<Scalar> PengRobinson<Scalar>::criticalMolarVolume_;
541 */
542 
543 } // namespace Opm
544 
545 #endif
This is a fluid state which allows to set the fluid temperatures and takes all other quantities from ...
Abstract base class of a pure chemical species.
Definition: Component.hpp:42
void SetUndefined(const T &value OPM_UNUSED)
Make the memory on which an object resides undefined in valgrind runs.
Definition: Valgrind.hpp:138
bool CheckDefined(const T &value OPM_UNUSED)
Make valgrind complain if any of the memory occupied by an object is undefined.
Definition: Valgrind.hpp:74
static bool findExtrema_(Scalar &Vmin, Scalar &Vmax, Scalar &, Scalar &, Scalar a, Scalar b, Scalar T)
Definition: PengRobinson.hpp:394
Definition: Air_Mesitylene.hpp:31
static void handleCriticalFluid_(Scalar &Vm, const FluidState &, const Params &params, unsigned phaseIdx, bool isGasPhase)
Definition: PengRobinson.hpp:276
Implements a scalar function that depends on two variables and which is sampled on an uniform X-Y gri...
Evaluation< Scalar, VarSetTag, numVars > max(const Evaluation< Scalar, VarSetTag, numVars > &x1, const Evaluation< Scalar, VarSetTag, numVars > &x2)
Definition: Math.hpp:114
Evaluation< Scalar, VarSetTag, numVars > sqrt(const Evaluation< Scalar, VarSetTag, numVars > &x)
Definition: Math.hpp:278
static Scalar computeMolarVolume(const FluidState &fs, Params &params, unsigned phaseIdx, bool isGasPhase)
Computes molar volumes where the Peng-Robinson EOS is true.
Definition: PengRobinson.hpp:142
static void findCriticalPoint_(Scalar &Tcrit, Scalar &pcrit, Scalar &Vcrit, Scalar a, Scalar b)
Definition: PengRobinson.hpp:298
static Scalar ambroseWalton_(const Params &, Scalar T)
The Ambrose-Walton method to estimate the vapor pressure.
Definition: PengRobinson.hpp:489
int invertCubicPolynomial(SolContainer *sol, Scalar a, Scalar b, Scalar c, Scalar d)
Invert a cubic polynomial analytically.
Definition: PolynomialUtils.hpp:151
Evaluation< Scalar, VarSetTag, numVars > exp(const Evaluation< Scalar, VarSetTag, numVars > &x)
Definition: Math.hpp:295
Evaluation< Scalar, VarSetTag, numVars > abs(const Evaluation< Scalar, VarSetTag, numVars > &)
Definition: Math.hpp:41
Provides free functions to invert polynomials of degree 1, 2 and 3.
Evaluation< Scalar, VarSetTag, numVars > min(const Evaluation< Scalar, VarSetTag, numVars > &x1, const Evaluation< Scalar, VarSetTag, numVars > &x2)
Definition: Math.hpp:61
static Scalar fugacityDifference_(const Params &params, Scalar T, Scalar p, Scalar VmLiquid, Scalar VmGas)
Returns the difference between the liquid and the gas phase fugacities in [bar].
Definition: PengRobinson.hpp:515
#define OPM_OPTIM_UNUSED
Definition: Unused.hpp:42
Relations valid for an ideal gas.
static Scalar criticalTemperature()
Returns the critical temperature in of the component.
Definition: Component.hpp:98
static Scalar computeFugacityCoeffient(const Params &params)
Returns the fugacity coefficient for a given pressure and molar volume.
Definition: PengRobinson.hpp:239
Implements the Peng-Robinson equation of state for liquids and gases.
Definition: PengRobinson.hpp:54
static void init(Scalar, Scalar, unsigned, Scalar, Scalar, unsigned)
Definition: PengRobinson.hpp:63
Evaluation< Scalar, VarSetTag, numVars > pow(const Evaluation< Scalar, VarSetTag, numVars > &base, Scalar exp)
Definition: Math.hpp:312
static Scalar computeFugacity(const Params &params)
Returns the fugacity coefficient for a given pressure and molar volume.
Definition: PengRobinson.hpp:271
Provides the OPM_UNUSED macro.
static Scalar computeVaporPressure(const Params &params, Scalar T)
Predicts the vapor pressure for the temperature given in setTP().
Definition: PengRobinson.hpp:100
static Scalar criticalPressure()
Returns the critical pressure in of the component.
Definition: Component.hpp:104
A central place for various physical constants occuring in some equations.
Definition: Constants.hpp:39