version 3.10-dev
pengrobinson.hh
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// SPDX-FileCopyrightInfo: Copyright © DuMux Project contributors, see AUTHORS.md in root folder
5// SPDX-License-Identifier: GPL-3.0-or-later
6//
21#ifndef DUMUX_PENG_ROBINSON_HH
22#define DUMUX_PENG_ROBINSON_HH
23
24#include <dune/common/math.hh>
28#include <dumux/common/math.hh>
30
31#include <iostream>
32
33namespace Dumux {
34
46template <class Scalar>
48{
50 { }
51
52public:
53 static void init(Scalar aMin, Scalar aMax, int na,
54 Scalar bMin, Scalar bMax, int nb)
55 {
56 // resize the tabulation for the critical points
57 criticalTemperature_.resize(aMin, aMax, na, bMin, bMax, nb);
58 criticalPressure_.resize(aMin, aMax, na, bMin, bMax, nb);
59 criticalMolarVolume_.resize(aMin, aMax, na, bMin, bMax, nb);
60
61 Scalar VmCrit = 18e-6;
62 Scalar pCrit = 1e7;
63 Scalar TCrit = 273;
64 for (int i = 0; i < na; ++i) {
65 Scalar a = criticalTemperature_.iToX(i);
66 for (int j = 0; j < nb; ++j) {
67 Scalar b = criticalTemperature_.jToY(j);
68
69 findCriticalPoint_(TCrit, pCrit, VmCrit, a, b);
70
71 criticalTemperature_.setSamplePoint(i, j, TCrit);
72 criticalPressure_.setSamplePoint(i, j, pCrit);
73 criticalMolarVolume_.setSamplePoint(i, j, VmCrit);
74 }
75 }
76 };
77
88 template <class Params>
89 static Scalar computeVaporPressure(const Params &params, Scalar T)
90 {
91 using Component = typename Params::Component;
92 if (T >= Component::criticalTemperature())
93 return Component::criticalPressure();
94
95 // initial guess of the vapor pressure
96 Scalar Vm[3];
97 const Scalar eps = Component::criticalPressure()*1e-10;
98
99 // use the Ambrose-Walton method to get an initial guess of
100 // the vapor pressure
101 Scalar pVap = ambroseWalton_(params, T);
102
103 // Newton-Raphson method
104 for (int i = 0; i < 5; ++i) {
105 // calculate the molar densities
106 assert(molarVolumes(Vm, params, T, pVap) == 3);
107
108 Scalar f = fugacityDifference_(params, T, pVap, Vm[0], Vm[2]);
109 Scalar df_dp =
110 fugacityDifference_(params, T, pVap + eps, Vm[0], Vm[2])
111 -
112 fugacityDifference_(params, T, pVap - eps, Vm[0], Vm[2]);
113 df_dp /= 2*eps;
114
115 Scalar delta = f/df_dp;
116 pVap = pVap - delta;
117 using std::abs;
118 if (abs(delta/pVap) < 1e-10)
119 break;
120 }
121
122 return pVap;
123 }
124
143 template <class FluidState, class Params>
144 static Scalar computeMolarVolume(const FluidState &fs,
145 Params &params,
146 int phaseIdx,
147 bool isGasPhase,
148 bool handleUnphysicalPhase = true)
149 {
150 Scalar Vm = 0;
151
152 Scalar T = fs.temperature(phaseIdx);
153 Scalar p = fs.pressure(phaseIdx);
154
155 Scalar a = params.a(phaseIdx); // "attractive factor"
156 Scalar b = params.b(phaseIdx); // "co-volume"
157
158 Scalar RT = Constants<Scalar>::R*T;
159 Scalar Astar = a*p/(RT*RT);
160 Scalar Bstar = b*p/RT;
161
162 Scalar a1 = 1.0;
163 Scalar a2 = - (1 - Bstar);
164 Scalar a3 = Astar - Bstar*(3*Bstar + 2);
165 Scalar a4 = Bstar*(- Astar + Bstar*(1 + Bstar));
166
167 Scalar Z[3] = {}; // zero-initializer
168 int numSol = invertCubicPolynomial(Z, a1, a2, a3, a4);
169
170 // ignore the first two results if the smallest
171 // compressibility factor is <= 0.0. (this means that if we
172 // would get negative molar volumes for the liquid phase, we
173 // consider the liquid phase non-existent.)
174 // Note that invertCubicPolynomial sorts the roots in ascending order
175 if (numSol > 1 && Z[0] <= 0.0) {
176 Z[0] = Z[numSol - 1];
177 numSol = 1;
178 }
179
180 if (numSol > 0 && Z[0] <= 0)
181 DUNE_THROW(NumericalProblem, "No positive compressibility factors found");
182
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 has only one intersection with the pressure
194
195 Vm = Z[0]*RT/p;
196
197 // Handle single root case specially unless told otherwise
198 if (handleUnphysicalPhase)
199 Vm = handleSingleRoot_(Vm, fs, params, phaseIdx, isGasPhase);
200 }
201 else
202 DUNE_THROW(NumericalProblem, "Unexpected number of roots in the equation for compressibility factor: " << numSol);
203
204 using std::isfinite;
205
206 if (!isfinite(Vm) || Vm <= 0)
207 DUNE_THROW(NumericalProblem, "Bad molar volume");
208
209 return Vm;
210 }
211
218 template <class Params>
219 static Scalar criticalTemperature(const Params &params)
220 {
221 // Here, a() is the attractive force parameter and b()
222 // is the repulsive force parameter of the EOS
223 return criticalTemperature_(params.a(), params.b());
224 }
225
236 template <class Params>
237 static Scalar computeFugacityCoeffient(const Params &params)
238 {
239 Scalar T = params.temperature();
240 Scalar p = params.pressure();
241 Scalar Vm = params.molarVolume();
242
243 Scalar RT = Constants<Scalar>::R*T;
244 Scalar Z = p*Vm/RT;
245 Scalar Bstar = p*params.b() / RT;
246 using std::sqrt;
247 Scalar tmp =
248 (Vm + params.b()*(1 + sqrt(2))) /
249 (Vm + params.b()*(1 - sqrt(2)));
250 Scalar expo = - params.a()/(RT * 2 * params.b() * sqrt(2));
251 using std::exp;
252 using std::pow;
253 Scalar fugCoeff =
254 exp(Z - 1) / (Z - Bstar) *
255 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
274protected:
275
276 /*
277 * Handle single-root case in the equation of state. The problem here is
278 * that only one phase exists in this case, while we should also figure out
279 * something to return for the other phase. For reference, see Andreas
280 * Lauser's thesis: http://dx.doi.org/10.18419/opus-516 (page 32).
281 */
282 template <class FluidState, class Params>
283 static Scalar handleSingleRoot_(Scalar Vm,
284 const FluidState &fs,
285 const Params &params,
286 int phaseIdx,
287 bool isGasPhase)
288 {
289 Scalar T = fs.temperature(phaseIdx);
290
291 // for the other phase, we take the extremum of the EOS
292 // with the largest distance from the intersection.
293 if (T > criticalTemperature_(params.a(phaseIdx), params.b(phaseIdx))) {
294 // if the EOS does not exhibit any extrema, the fluid
295 // is critical...
296 return handleCriticalFluid_(Vm, fs, params, phaseIdx, isGasPhase);
297 }
298 else {
299 // find the extrema (if they are present)
300 Scalar Vmin, Vmax, pmin, pmax;
301 using std::min;
302 using std::max;
303 if (findExtrema_(Vmin, Vmax, pmin, pmax,
304 params.a(phaseIdx), params.b(phaseIdx), T))
305 return isGasPhase ? max(Vmax, Vm) : min(Vmin, Vm);
306 }
307 return Vm;
308 }
309
310 template <class FluidState, class Params>
311 static Scalar handleCriticalFluid_(Scalar Vm,
312 const FluidState &fs,
313 const Params &params,
314 int phaseIdx,
315 bool isGasPhase)
316 {
317 Scalar Vcrit = criticalMolarVolume_(params.a(phaseIdx), params.b(phaseIdx));
318
319 using std::max;
320 using std::min;
321 return isGasPhase ? max(Vm, Vcrit) : min(Vm, Vcrit);
322 }
323
324 static void findCriticalPoint_(Scalar &Tcrit,
325 Scalar &pcrit,
326 Scalar &Vcrit,
327 Scalar a,
328 Scalar b)
329 {
330 Scalar minVm;
331 Scalar maxVm;
332
333 Scalar minP(0);
334 Scalar maxP(1e100);
335
336 // first, we need to find an isotherm where the EOS exhibits
337 // a maximum and a minimum
338 Scalar Tmin = 250; // [K]
339 for (int i = 0; i < 30; ++i) {
340 bool hasExtrema = findExtrema_(minVm, maxVm, minP, maxP, a, b, Tmin);
341 if (hasExtrema)
342 break;
343 Tmin /= 2;
344 }
345
346 Scalar T = Tmin;
347
348 // Newton's method: Start at minimum temperature and minimize
349 // the "gap" between the extrema of the EOS
350 for (int i = 0; i < 25; ++i) {
351 // calculate function we would like to minimize
352 Scalar f = maxVm - minVm;
353
354 // check if we're converged
355 if (f < 1e-10) {
356 Tcrit = T;
357 pcrit = (minP + maxP)/2;
358 Vcrit = (maxVm + minVm)/2;
359 return;
360 }
361
362 // backward differences. Forward differences are not
363 // robust, since the isotherm could be critical if an
364 // epsilon was added to the temperature. (this is case
365 // rarely happens, though)
366 const Scalar eps = - 1e-8;
367 [[maybe_unused]] bool hasExtrema = findExtrema_(minVm, maxVm, minP, maxP, a, b, T + eps);
368 assert(hasExtrema);
369 Scalar fStar = maxVm - minVm;
370
371 // derivative of the difference between the maximum's
372 // molar volume and the minimum's molar volume regarding
373 // temperature
374 Scalar fPrime = (fStar - f)/eps;
375
376 // update value for the current iteration
377 Scalar delta = f/fPrime;
378 if (delta > 0)
379 delta = -10;
380
381 // line search (we have to make sure that both extrema
382 // still exist after the update)
383 for (int j = 0; ; ++j) {
384 if (j >= 20) {
385 DUNE_THROW(NumericalProblem,
386 "Could not determine the critical point for a=" << a << ", b=" << b);
387 }
388
389 if (findExtrema_(minVm, maxVm, minP, maxP, a, b, T - delta)) {
390 // if the isotherm for T - delta exhibits two
391 // extrema the update is finished
392 T -= delta;
393 break;
394 }
395 else
396 delta /= 2;
397 }
398 }
399 }
400
401 // find the two molar volumes where the EOS exhibits extrema and
402 // which are larger than the covolume of the phase
403 static bool findExtrema_(Scalar &Vmin,
404 Scalar &Vmax,
405 Scalar &pMin,
406 Scalar &pMax,
407 Scalar a,
408 Scalar b,
409 Scalar T)
410 {
411 Scalar u = 2;
412 Scalar w = -1;
413
414 Scalar RT = Constants<Scalar>::R*T;
415
416 // calculate coefficients of the 4th order polynominal in
417 // monomial basis
418 Scalar a1 = RT;
419 Scalar a2 = 2*RT*u*b - 2*a;
420 Scalar a3 = 2*RT*w*b*b + RT*u*u*b*b + 4*a*b - u*a*b;
421 Scalar a4 = 2*RT*u*w*b*b*b + 2*u*a*b*b - 2*a*b*b;
422 Scalar a5 = RT*w*w*b*b*b*b - u*a*b*b*b;
423
424 // Newton method to find first root
425
426 // if the values which we got on Vmin and Vmax are useful, we
427 // will reuse them as initial value, else we will start 10%
428 // above the covolume
429 Scalar V = b*1.1;
430 Scalar delta = 1.0;
431 using std::abs;
432 for (int i = 0; abs(delta) > 1e-9; ++i) {
433 Scalar f = a5 + V*(a4 + V*(a3 + V*(a2 + V*a1)));
434 Scalar fPrime = a4 + V*(2*a3 + V*(3*a2 + V*4*a1));
435
436 if (abs(fPrime) < 1e-20) {
437 // give up if the derivative is zero
438 Vmin = 0;
439 Vmax = 0;
440 return false;
441 }
442
443
444 delta = f/fPrime;
445 V -= delta;
446
447 if (i > 20) {
448 // give up after 20 iterations...
449 Vmin = 0;
450 Vmax = 0;
451 return false;
452 }
453 }
454
455 // polynomial division
456 Scalar b1 = a1;
457 Scalar b2 = a2 + V*b1;
458 Scalar b3 = a3 + V*b2;
459 Scalar b4 = a4 + V*b3;
460
461 // invert resulting cubic polynomial analytically
462 Scalar allV[4];
463 allV[0] = V;
464 int numSol = 1 + invertCubicPolynomial(allV + 1, b1, b2, b3, b4);
465
466 // sort all roots of the derivative
467 std::sort(allV + 0, allV + numSol);
468
469 // check whether the result is physical
470 if (allV[numSol - 2] < b) {
471 // the second largest extremum is smaller than the phase's
472 // covolume which is physically impossible
473 Vmin = Vmax = 0;
474 return false;
475 }
476
477 // it seems that everything is okay...
478 Vmin = allV[numSol - 2];
479 Vmax = allV[numSol - 1];
480 return true;
481 }
482
493 template <class Params>
494 static Scalar ambroseWalton_(const Params &params, Scalar T)
495 {
496 using Component = typename Params::Component;
497
498 Scalar Tr = T / Component::criticalTemperature();
499 Scalar tau = 1 - Tr;
500 Scalar omega = Component::acentricFactor();
501 using std::sqrt;
502 using Dune::power;
503 Scalar f0 = (tau*(-5.97616 + sqrt(tau)*(1.29874 - tau*0.60394)) - 1.06841*power(tau, 5))/Tr;
504 Scalar f1 = (tau*(-5.03365 + sqrt(tau)*(1.11505 - tau*5.41217)) - 7.46628*power(tau, 5))/Tr;
505 Scalar f2 = (tau*(-0.64771 + sqrt(tau)*(2.41539 - tau*4.26979)) + 3.25259*power(tau, 5))/Tr;
506 using std::exp;
507 return Component::criticalPressure()*exp(f0 + omega * (f1 + omega*f2));
508 }
509
520 template <class Params>
521 static Scalar fugacityDifference_(const Params &params,
522 Scalar T,
523 Scalar p,
524 Scalar VmLiquid,
525 Scalar VmGas)
526 { return fugacity(params, T, p, VmLiquid) - fugacity(params, T, p, VmGas); }
527
531};
532
533template <class Scalar>
535
536template <class Scalar>
538
539template <class Scalar>
541
542} // end namespace
543
544#endif
A central place for various physical constants occurring in some equations.
Definition: constants.hh:27
Exception thrown if a fixable numerical problem occurs.
Definition: exceptions.hh:27
Implements the Peng-Robinson equation of state for liquids and gases.
Definition: pengrobinson.hh:48
static Scalar criticalTemperature(const Params &params)
Returns the critical temperature for a given mix.
Definition: pengrobinson.hh:219
static Scalar handleSingleRoot_(Scalar Vm, const FluidState &fs, const Params &params, int phaseIdx, bool isGasPhase)
Definition: pengrobinson.hh:283
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.hh:521
static Scalar computeMolarVolume(const FluidState &fs, Params &params, int phaseIdx, bool isGasPhase, bool handleUnphysicalPhase=true)
Computes molar volumes where the Peng-Robinson EOS is true.
Definition: pengrobinson.hh:144
static Tabulated2DFunction< Scalar > criticalPressure_
Definition: pengrobinson.hh:529
static void init(Scalar aMin, Scalar aMax, int na, Scalar bMin, Scalar bMax, int nb)
Definition: pengrobinson.hh:53
static Tabulated2DFunction< Scalar > criticalMolarVolume_
Definition: pengrobinson.hh:530
static bool findExtrema_(Scalar &Vmin, Scalar &Vmax, Scalar &pMin, Scalar &pMax, Scalar a, Scalar b, Scalar T)
Definition: pengrobinson.hh:403
static void findCriticalPoint_(Scalar &Tcrit, Scalar &pcrit, Scalar &Vcrit, Scalar a, Scalar b)
Definition: pengrobinson.hh:324
static Scalar computeFugacity(const Params &params)
Returns the fugacity coefficient for a given pressure and molar volume.
Definition: pengrobinson.hh:271
static Scalar ambroseWalton_(const Params &params, Scalar T)
The Ambrose-Walton method to estimate the vapor pressure.
Definition: pengrobinson.hh:494
static Scalar handleCriticalFluid_(Scalar Vm, const FluidState &fs, const Params &params, int phaseIdx, bool isGasPhase)
Definition: pengrobinson.hh:311
static Tabulated2DFunction< Scalar > criticalTemperature_
Definition: pengrobinson.hh:528
static Scalar computeVaporPressure(const Params &params, Scalar T)
Predicts the vapor pressure for the temperature given in setTP().
Definition: pengrobinson.hh:89
static Scalar computeFugacityCoeffient(const Params &params)
Returns the fugacity coefficient for a given pressure and molar volume.
Definition: pengrobinson.hh:237
Implements tabulation for a two-dimensional function.
Definition: tabulated2dfunction.hh:36
Some exceptions thrown in DuMux
int invertCubicPolynomial(SolContainer *sol, Scalar a, Scalar b, Scalar c, Scalar d, std::size_t numPostProcessIterations=1)
Invert a cubic polynomial analytically.
Definition: math.hh:269
Relations valid for an ideal gas.
Define some often used mathematical functions.
Definition: adapt.hh:17
Implements tabulation for a two-dimensional function.
This is a fluid state which allows to set the fluid temperatures and takes all other quantities from ...