opm-common
Loading...
Searching...
No Matches
PTFlash.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 2022 NORCE.
5 Copyright 2022 SINTEF Digital, Mathematics and Cybernetics.
6
7 This file is part of the Open Porous Media project (OPM).
8
9 OPM is free software: you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation, either version 2 of the License, or
12 (at your option) any later version.
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
21 Consult the COPYING file in the top-level source directory of this
22 module for the precise wording of the license and the list of
23 copyright holders.
24*/
29#ifndef OPM_CHI_FLASH_HPP
30#define OPM_CHI_FLASH_HPP
31
40#include <opm/material/eos/CubicEOS.hpp>
41
42#include <opm/input/eclipse/EclipseState/Compositional/CompositionalConfig.hpp>
43
44#include <dune/common/fvector.hh>
45#include <dune/common/fmatrix.hh>
46#include <dune/common/classname.hh>
47
48#include <limits>
49#include <iostream>
50#include <iomanip>
51#include <stdexcept>
52#include <type_traits>
53
54#include <fmt/format.h>
55
56namespace Opm {
57
63template <class Scalar, class FluidSystem, bool isThermal = false>
65{
66 static constexpr int numPhases = FluidSystem::numPhases;
67 static constexpr int numComponents = FluidSystem::numComponents;
68 enum { oilPhaseIdx = FluidSystem::oilPhaseIdx};
69 enum { gasPhaseIdx = FluidSystem::gasPhaseIdx};
70 enum { waterPhaseIdx = FluidSystem::waterPhaseIdx};
71 static constexpr int numMiscibleComponents = FluidSystem::numMiscibleComponents;
72 static constexpr int numMisciblePhases = FluidSystem::numMisciblePhases; //oil, gas
73 static constexpr int numEq = numMisciblePhases + numMisciblePhases * numMiscibleComponents;
74
75 using EOSType = CompositionalConfig::EOSType;
76
77public:
82 template <class FluidState>
83 static bool solve(FluidState& fluid_state,
84 const std::string& twoPhaseMethod,
85 Scalar flash_tolerance,
86 const EOSType& eos_type,
87 int verbosity = 0)
88 {
89 using ScalarFluidState = CompositionalFluidState<Scalar, FluidSystem>;
90 ScalarFluidState fluid_state_scalar;
91
92 for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) {
93 fluid_state_scalar.setKvalue(compIdx, Opm::getValue(fluid_state.K(compIdx) ) );
94 fluid_state_scalar.setMoleFraction(compIdx, Opm::getValue(fluid_state.moleFraction(compIdx) ) );
95 }
96
97 fluid_state_scalar.setLvalue(Opm::getValue(fluid_state.L()));
98 // other values need to be Scalar, but I guess the fluidstate does not support it yet.
99 fluid_state_scalar.setPressure(FluidSystem::oilPhaseIdx,
100 Opm::getValue(fluid_state.pressure(FluidSystem::oilPhaseIdx)));
101 fluid_state_scalar.setPressure(FluidSystem::gasPhaseIdx,
102 Opm::getValue(fluid_state.pressure(FluidSystem::gasPhaseIdx)));
103
104 fluid_state_scalar.setTemperature(Opm::getValue(fluid_state.temperature(0)));
105
106 const auto is_single_phase = flash_solve_scalar_(fluid_state_scalar, twoPhaseMethod, flash_tolerance, eos_type, verbosity);
107
108 // the flash solution process were performed in scalar form, after the flash calculation finishes,
109 // ensure that things in fluid_state_scalar is transformed to fluid_state
110 for (int compIdx=0; compIdx<numComponents; ++compIdx){
111 const auto x_i = fluid_state_scalar.moleFraction(oilPhaseIdx, compIdx);
112 fluid_state.setMoleFraction(oilPhaseIdx, compIdx, x_i);
113 const auto y_i = fluid_state_scalar.moleFraction(gasPhaseIdx, compIdx);
114 fluid_state.setMoleFraction(gasPhaseIdx, compIdx, y_i);
115 }
116
117 // we update the derivatives in fluid_state
118 updateDerivatives_(fluid_state_scalar, fluid_state, eos_type, is_single_phase);
119
120 return is_single_phase;
121 } //end solve
122
130 template <class FluidState, class ComponentVector>
131 static void solve(FluidState& fluid_state,
132 const ComponentVector& globalMolarities,
133 Scalar tolerance = 0.0)
134 {
135 using MaterialTraits = NullMaterialTraits<Scalar, numPhases>;
136 using MaterialLaw = NullMaterial<MaterialTraits>;
137 using MaterialLawParams = typename MaterialLaw::Params;
138
139 MaterialLawParams matParams;
140 solve<MaterialLaw>(fluid_state, matParams, globalMolarities, tolerance);
141 }
142
143 template <class Vector>
144 static typename Vector::field_type solveRachfordRice_g_(const Vector& K, const Vector& z, int verbosity)
145 {
146 // Find min and max K. Have to do a laborious for loop to avoid water component (where K=0)
147 // TODO: Replace loop with Dune::min_value() and Dune::max_value() when water component is properly handled
148 using field_type = typename Vector::field_type;
149 constexpr field_type tol = 1e-12;
150 constexpr int itmax = 10000;
151 field_type Kmin = K[0];
152 field_type Kmax = K[0];
153 for (int compIdx = 1; compIdx < numComponents; ++compIdx){
154 if (K[compIdx] < Kmin)
155 Kmin = K[compIdx];
156 else if (K[compIdx] >= Kmax)
157 Kmax = K[compIdx];
158 }
159 // Lower and upper bound for solution
160 auto Vmin = 1 / (1 - Kmax);
161 auto Vmax = 1 / (1 - Kmin);
162 // Initial guess
163 auto V = (Vmin + Vmax)/2;
164 // Print initial guess and header
165 if (verbosity == 3 || verbosity == 4) {
166 std::cout << "Initial guess " << numComponents << "c : V = " << V << " and [Vmin, Vmax] = [" << Vmin << ", " << Vmax << "]" << std::endl;
167 std::cout << std::setw(10) << "Iteration" << std::setw(16) << "abs(step)" << std::setw(16) << "V" << std::endl;
168 }
169 // Newton-Raphson loop
170 for (int iteration = 1; iteration < itmax; ++iteration) {
171 // Calculate function and derivative values
172 field_type denum = 0.0;
173 field_type r = 0.0;
174 for (int compIdx = 0; compIdx < numComponents; ++compIdx){
175 auto dK = K[compIdx] - 1.0;
176 auto a = z[compIdx] * dK;
177 auto b = (1 + V * dK);
178 r += a/b;
179 denum += z[compIdx] * (dK*dK) / (b*b);
180 }
181 auto delta = r / denum;
182 V += delta;
183
184 // Check if V is within the bounds, and if not, we apply bisection method
185 if (V < Vmin || V > Vmax)
186 {
187 // Print info
188 if (verbosity == 3 || verbosity == 4) {
189 std::cout << "V = " << V << " is not within the the range [Vmin, Vmax], solve using Bisection method!" << std::endl;
190 }
191
192 // Run bisection
193 // TODO: This is required for some cases. Not clear why
194 // since the objective function should be monotone with a
195 // single zero between the Lmin/Lmax interval defined by
196 // K-values.
197 decltype(Vmax) Lmin = 1.0;
198 decltype(Vmin) Lmax = 0.0;
199 auto L = bisection_g_(K, Lmin, Lmax, z, verbosity);
200
201 // Print final result
202 if (verbosity >= 1) {
203 std::cout << "Rachford-Rice (Bisection) converged to final solution L = " << L << std::endl;
204 }
205 return L;
206 }
207
208 // Print iteration info
209 if (verbosity == 3 || verbosity == 4) {
210 std::cout << std::setw(10) << iteration << std::setw(16) << Opm::abs(delta) << std::setw(16) << V << std::endl;
211 }
212 // Check for convergence
213 if ( Opm::abs(r) < tol ) {
214 auto L = 1 - V;
215 // Should we make sure the range of L is within (0, 1)?
216
217 // Print final result
218 if (verbosity >= 1) {
219 std::cout << "Rachford-Rice converged to final solution L = " << L << std::endl;
220 }
221 return L;
222 }
223 }
224
225 // Throw error if Rachford-Rice fails
226 throw std::runtime_error(" Rachford-Rice did not converge within maximum number of iterations" );
227 }
228
229 // performing the flash calculation, which is done with Scalar without touching derivatives
230 template <typename FluidState>
231 static bool flash_solve_scalar_(FluidState& fluid_state,
232 const std::string& twoPhaseMethod,
233 const Scalar flash_tolerance,
234 const EOSType& eos_type,
235 const int verbosity = 0)
236 {
237 // Do a stability test to check if cell is is_single_phase-phase (do for all cells the first time).
238 bool is_stable = false;
239 auto L_scalar = fluid_state.L();
240 using ScalarVector = Dune::FieldVector<Scalar, numComponents>;
241 ScalarVector K_scalar, z_scalar;
242 for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) {
243 K_scalar[compIdx] = fluid_state.K(compIdx);
244 z_scalar[compIdx] = fluid_state.moleFraction(compIdx);
245 }
246
247 if ( L_scalar <= 0 || L_scalar == 1 ) {
248 if (verbosity >= 1) {
249 std::cout << "Perform stability test (L <= 0 or L == 1)!" << std::endl;
250 }
251 phaseStabilityTest_(is_stable, K_scalar, fluid_state, z_scalar, eos_type, verbosity);
252 }
253 if (verbosity >= 1) {
254 std::cout << "Inputs after stability test are K = [" << K_scalar << "], L = [" << L_scalar << "], z = [" << z_scalar << "], P = " << fluid_state.pressure(0) << ", and T = " << fluid_state.temperature(0) << std::endl;
255 }
256 // TODO: we do not need two variables is_stable and is_single_hase, while lacking a good name
257 // TODO: from the later code, is good if we knows whether single_phase_gas or single_phase_oil here
258 const bool is_single_phase = is_stable;
259
260 // Update the composition if cell is two-phase
261 if ( !is_single_phase ) {
262 // Rachford Rice equation to get initial L for composition solver
263 L_scalar = solveRachfordRice_g_(K_scalar, z_scalar, verbosity);
264 flash_2ph(z_scalar, twoPhaseMethod, K_scalar, L_scalar, fluid_state, flash_tolerance, eos_type, verbosity);
265 } else {
266 // Cell is one-phase. Use Li's phase labeling method to see if it's liquid or vapor
267 L_scalar = li_single_phase_label_(fluid_state, z_scalar, verbosity);
268 }
269 fluid_state.setLvalue(L_scalar);
270 return is_single_phase;
271 }
272
273 template <class Vector>
274 static typename Vector::field_type bisection_g_(const Vector& K, typename Vector::field_type Lmin,
275 typename Vector::field_type Lmax, const Vector& z, int verbosity)
276 {
277 // Calculate for g(Lmin) for first comparison with gMid = g(L)
278 typename Vector::field_type gLmin = rachfordRice_g_(K, Lmin, z);
279
280 // Print new header
281 if (verbosity >= 3) {
282 std::cout << std::setw(10) << "Iteration" << std::setw(16) << "g(Lmid)" << std::setw(16) << "L" << std::endl;
283 }
284
285 constexpr int max_it = 10000;
286
287 auto closeLmaxLmin = [](double max_v, double min_v) {
288 return Opm::abs(max_v - min_v) / 2. < 1e-10;
289 // what if max_v < min_v?
290 };
291
292 // Bisection loop
293 if (closeLmaxLmin(Lmax, Lmin) ){
294 throw std::runtime_error(fmt::format("Strange bisection with Lmax {} and Lmin {}?", Lmax, Lmin));
295 }
296 for (int iteration = 0; iteration < max_it; ++iteration){
297 // New midpoint
298 auto L = (Lmin + Lmax) / 2;
299 auto gMid = rachfordRice_g_(K, L, z);
300 // std::cout << ">>> Lmin = " << Lmin << "g(Lmin) = " << gLmin << " L = " << L << " g(L) = " << gMid << std::endl;
301 if (verbosity == 3 || verbosity == 4) {
302 std::cout << std::setw(10) << iteration << std::setw(16) << gMid << std::setw(16) << L << std::endl;
303 }
304
305 // Check if midpoint fulfills g=0 or L - Lmin is sufficiently small
306 if (Opm::abs(gMid) < 1e-16 || closeLmaxLmin(Lmax, Lmin)){
307 return L;
308 }
309 // Else we repeat with midpoint being either Lmin og Lmax (depending on the signs).
310 else if (Dune::sign(gMid) != Dune::sign(gLmin)) {
311 // gMid has different sign as gLmin, so we set L as the new Lmax
312 Lmax = L;
313 }
314 else {
315 // gMid and gLmin have same sign so we set L as the new Lmin
316 Lmin = L;
317 gLmin = gMid;
318 }
319 }
320 throw std::runtime_error(" Rachford-Rice bisection failed with " + std::to_string(max_it) + " iterations!");
321 }
322
323 template <class Vector, class FlashFluidState>
324 static typename Vector::field_type li_single_phase_label_(const FlashFluidState& fluid_state, const Vector& z, int verbosity)
325 {
326 // Calculate intermediate sum
327 typename Vector::field_type sumVz = 0.0;
328 for (int compIdx=0; compIdx<numComponents; ++compIdx){
329 // Get component information
330 const auto& V_crit = FluidSystem::criticalVolume(compIdx);
331
332 // Sum calculation
333 sumVz += (V_crit * z[compIdx]);
334 }
335
336 // Calculate approximate (pseudo) critical temperature using Li's method
337 typename Vector::field_type Tc_est = 0.0;
338 for (int compIdx=0; compIdx<numComponents; ++compIdx){
339 // Get component information
340 const auto& V_crit = FluidSystem::criticalVolume(compIdx);
341 const auto& T_crit = FluidSystem::criticalTemperature(compIdx);
342
343 // Sum calculation
344 Tc_est += (V_crit * T_crit * z[compIdx] / sumVz);
345 }
346
347 // Get temperature
348 const auto& T = fluid_state.temperature(0);
349
350 // If temperature is below estimated critical temperature --> phase = liquid; else vapor
351 typename Vector::field_type L;
352 if (T < Tc_est) {
353 // Liquid
354 L = 1.0;
355
356 // Print
357 if (verbosity >= 1) {
358 std::cout << "Cell is single-phase, liquid (L = 1.0) due to Li's phase labeling method giving T < Tc_est (" << T << " < " << Tc_est << ")!" << std::endl;
359 }
360 }
361 else {
362 // Vapor
363 L = 0.0;
364
365 // Print
366 if (verbosity >= 1) {
367 std::cout << "Cell is single-phase, vapor (L = 0.0) due to Li's phase labeling method giving T >= Tc_est (" << T << " >= " << Tc_est << ")!" << std::endl;
368 }
369 }
370
371
372 return L;
373 }
374
375 template <class FlashFluidState, class ComponentVector>
376 static void phaseStabilityTest_(bool& isStable, ComponentVector& K, FlashFluidState& fluid_state, const ComponentVector& z, const EOSType& eos_type, int verbosity)
377 {
378 // Declarations
379 bool isTrivialL, isTrivialV;
380 ComponentVector x, y;
381 typename FlashFluidState::ValueType S_l, S_v;
382 ComponentVector K0 = K;
383 ComponentVector K1 = K;
384
385 // Check for vapour instable phase
386 if (verbosity == 3 || verbosity == 4) {
387 std::cout << "Stability test for vapor phase:" << std::endl;
388 }
389 checkStability_(fluid_state, isTrivialV, K0, y, S_v, z, /*isGas=*/true, eos_type, verbosity);
390 bool V_unstable = (S_v < (1.0 + 1e-5)) || isTrivialV;
391
392 // Check for liquids stable phase
393 if (verbosity == 3 || verbosity == 4) {
394 std::cout << "Stability test for liquid phase:" << std::endl;
395 }
396 checkStability_(fluid_state, isTrivialL, K1, x, S_l, z, /*isGas=*/false, eos_type, verbosity);
397 bool L_stable = (S_l < (1.0 + 1e-5)) || isTrivialL;
398
399 // L-stable means success in making liquid, V-unstable means no success in making vapour
400 isStable = L_stable && V_unstable;
401 if (isStable) {
402 // Single phase, i.e. phase composition is equivalent to the global composition
403 // Update fluid_state with mole fraction
404 for (int compIdx=0; compIdx<numComponents; ++compIdx){
405 fluid_state.setMoleFraction(gasPhaseIdx, compIdx, z[compIdx]);
406 fluid_state.setMoleFraction(oilPhaseIdx, compIdx, z[compIdx]);
407 }
408 }
409 // If not stable: use the mole fractions from Michelsen's test to update K
410 else {
411 for (int compIdx = 0; compIdx<numComponents; ++compIdx) {
412 K[compIdx] = y[compIdx] / x[compIdx];
413 }
414 }
415 }
416
417protected:
418
419 template <class FlashFluidState>
420 static typename FlashFluidState::ValueType wilsonK_(const FlashFluidState& fluid_state, int compIdx)
421 {
422 const auto& acf = FluidSystem::acentricFactor(compIdx);
423 const auto& T_crit = FluidSystem::criticalTemperature(compIdx);
424 const auto& T = fluid_state.temperature(0);
425 const auto& p_crit = FluidSystem::criticalPressure(compIdx);
426 const auto& p = fluid_state.pressure(0); //for now assume no capillary pressure
427
428 const auto& tmp = Opm::exp(5.3727 * (1+acf) * (1-T_crit/T)) * (p_crit/p);
429 return tmp;
430 }
431
432 template <class Vector>
433 static typename Vector::field_type rachfordRice_g_(const Vector& K, typename Vector::field_type L, const Vector& z)
434 {
435 typename Vector::field_type g=0;
436 for (int compIdx=0; compIdx<numComponents; ++compIdx){
437 g += (z[compIdx]*(K[compIdx]-1))/(K[compIdx]-L*(K[compIdx]-1));
438 }
439 return g;
440 }
441
442 template <class Vector>
443 static typename Vector::field_type rachfordRice_dg_dL_(const Vector& K, const typename Vector::field_type L, const Vector& z)
444 {
445 typename Vector::field_type dg=0;
446 for (int compIdx=0; compIdx<numComponents; ++compIdx){
447 dg += (z[compIdx]*(K[compIdx]-1)*(K[compIdx]-1))/((K[compIdx]-L*(K[compIdx]-1))*(K[compIdx]-L*(K[compIdx]-1)));
448 }
449 return dg;
450 }
451
452 template <class FlashFluidState, class ComponentVector>
453 static void checkStability_(const FlashFluidState& fluid_state, bool& isTrivial, ComponentVector& K, ComponentVector& xy_loc,
454 typename FlashFluidState::ValueType& S_loc, const ComponentVector& z, bool isGas, const EOSType& eos_type,
455 int verbosity)
456 {
457 using FlashEval = typename FlashFluidState::ValueType;
458 using CubicEOS = typename Opm::CubicEOS<Scalar, FluidSystem>;
459
460 // Declarations
461 FlashFluidState fluid_state_fake = fluid_state;
462 FlashFluidState fluid_state_global = fluid_state;
463
464 // Setup output
465 if (verbosity >= 3) {
466 std::cout << std::setw(10) << "Iteration" << std::setw(16) << "K-Norm" << std::setw(16) << "R-Norm" << std::endl;
467 }
468
469 // Michelsens stability test.
470 // Make two fake phases "inside" one phase and check for positive volume
471 for (int i = 0; i < 20000; ++i) {
472 S_loc = 0.0;
473 if (isGas) {
474 for (int compIdx=0; compIdx<numComponents; ++compIdx){
475 xy_loc[compIdx] = K[compIdx] * z[compIdx];
476 S_loc += xy_loc[compIdx];
477 }
478 for (int compIdx=0; compIdx<numComponents; ++compIdx){
479 xy_loc[compIdx] /= S_loc;
480 fluid_state_fake.setMoleFraction(gasPhaseIdx, compIdx, xy_loc[compIdx]);
481 }
482 }
483 else {
484 for (int compIdx=0; compIdx<numComponents; ++compIdx){
485 xy_loc[compIdx] = z[compIdx]/K[compIdx];
486 S_loc += xy_loc[compIdx];
487 }
488 for (int compIdx=0; compIdx<numComponents; ++compIdx){
489 xy_loc[compIdx] /= S_loc;
490 fluid_state_fake.setMoleFraction(oilPhaseIdx, compIdx, xy_loc[compIdx]);
491 }
492 }
493
494 int phaseIdx = (isGas ? static_cast<int>(gasPhaseIdx) : static_cast<int>(oilPhaseIdx));
495 int phaseIdx2 = (isGas ? static_cast<int>(oilPhaseIdx) : static_cast<int>(gasPhaseIdx));
496 // TODO: not sure the following makes sense
497 for (int compIdx=0; compIdx<numComponents; ++compIdx){
498 fluid_state_global.setMoleFraction(phaseIdx2, compIdx, z[compIdx]);
499 }
500
501 typename FluidSystem::template ParameterCache<FlashEval> paramCache_fake(eos_type);
502 paramCache_fake.updatePhase(fluid_state_fake, phaseIdx);
503
504 typename FluidSystem::template ParameterCache<FlashEval> paramCache_global(eos_type);
505 paramCache_global.updatePhase(fluid_state_global, phaseIdx2);
506
507 //fugacity for fake phases each component
508 for (int compIdx=0; compIdx<numComponents; ++compIdx){
509 auto phiFake = CubicEOS::computeFugacityCoefficient(fluid_state_fake, paramCache_fake, phaseIdx, compIdx);
510 auto phiGlobal = CubicEOS::computeFugacityCoefficient(fluid_state_global, paramCache_global, phaseIdx2, compIdx);
511
512 fluid_state_fake.setFugacityCoefficient(phaseIdx, compIdx, phiFake);
513 fluid_state_global.setFugacityCoefficient(phaseIdx2, compIdx, phiGlobal);
514 }
515
516
517 ComponentVector R;
518 for (int compIdx=0; compIdx<numComponents; ++compIdx){
519 if (isGas){
520 auto fug_fake = fluid_state_fake.fugacity(phaseIdx, compIdx);
521 auto fug_global = fluid_state_global.fugacity(phaseIdx2, compIdx);
522 auto fug_ratio = fug_global / fug_fake;
523 R[compIdx] = fug_ratio / S_loc;
524 }
525 else{
526 auto fug_fake = fluid_state_fake.fugacity(phaseIdx, compIdx);
527 auto fug_global = fluid_state_global.fugacity(phaseIdx2, compIdx);
528 auto fug_ratio = fug_fake / fug_global;
529 R[compIdx] = fug_ratio * S_loc;
530 }
531 }
532
533 for (int compIdx=0; compIdx<numComponents; ++compIdx){
534 K[compIdx] *= R[compIdx];
535 }
536 Scalar R_norm = 0.0;
537 Scalar K_norm = 0.0;
538 for (int compIdx=0; compIdx<numComponents; ++compIdx){
539 auto a = Opm::getValue(R[compIdx]) - 1.0;
540 auto b = Opm::log(Opm::getValue(K[compIdx]));
541 R_norm += a*a;
542 K_norm += b*b;
543 }
544
545 // Print iteration info
546 if (verbosity >= 3) {
547 std::cout << std::setw(10) << i << std::setw(16) << K_norm << std::setw(16) << R_norm << std::endl;
548 }
549
550 // Check convergence
551 isTrivial = (K_norm < 1e-5);
552 if (isTrivial || R_norm < 1e-10)
553 return;
554 //todo: make sure that no mole fraction is smaller than 1e-8 ?
555 //todo: take care of water!
556 }
557 throw std::runtime_error(" Stability test did not converge");
558 }//end checkStability
559
560 // TODO: basically FlashFluidState and ComponentVector are both depending on the one Scalar type
561 template <class FlashFluidState, class ComponentVector>
562 static void computeLiquidVapor_(FlashFluidState& fluid_state, typename FlashFluidState::ValueType& L, ComponentVector& K, const ComponentVector& z)
563 {
564 // Calculate x and y, and normalize
565 ComponentVector x;
566 ComponentVector y;
567 typename FlashFluidState::ValueType sumx=0;
568 typename FlashFluidState::ValueType sumy=0;
569 for (int compIdx=0; compIdx<numComponents; ++compIdx){
570 x[compIdx] = z[compIdx]/(L + (1-L)*K[compIdx]);
571 sumx += x[compIdx];
572 y[compIdx] = (K[compIdx]*z[compIdx])/(L + (1-L)*K[compIdx]);
573 sumy += y[compIdx];
574 }
575 x /= sumx;
576 y /= sumy;
577
578 for (int compIdx=0; compIdx<numComponents; ++compIdx){
579 fluid_state.setMoleFraction(oilPhaseIdx, compIdx, x[compIdx]);
580 fluid_state.setMoleFraction(gasPhaseIdx, compIdx, y[compIdx]);
581 }
582 }
583
584 template <class FluidState, class ComponentVector>
585 static void flash_2ph(const ComponentVector& z_scalar,
586 const std::string& flash_2p_method,
587 ComponentVector& K_scalar,
588 typename FluidState::ValueType& L_scalar,
589 FluidState& fluid_state_scalar,
590 const Scalar flash_tolerance,
591 const EOSType& eos_type,
592 int verbosity = 0) {
593 if (verbosity >= 1) {
594 std::cout << "Cell is two-phase! Solve Rachford-Rice with initial K = [" << K_scalar << "]" << std::endl;
595 }
596
597 // Calculate composition using nonlinear solver
598 // Newton
599 bool converged = false;
600 if (flash_2p_method == "newton") {
601 if (verbosity >= 1) {
602 std::cout << "Calculate composition using Newton." << std::endl;
603 }
604 converged = newtonComposition_(K_scalar, L_scalar, fluid_state_scalar, z_scalar, flash_tolerance, eos_type, verbosity);
605 } else if (flash_2p_method == "ssi") {
606 // Successive substitution
607 if (verbosity >= 1) {
608 std::cout << "Calculate composition using Succcessive Substitution." << std::endl;
609 }
610 converged = successiveSubstitutionComposition_(K_scalar, L_scalar, fluid_state_scalar, z_scalar, false, flash_tolerance, eos_type, verbosity);
611 } else if (flash_2p_method == "ssi+newton") {
612 converged = successiveSubstitutionComposition_(K_scalar, L_scalar, fluid_state_scalar, z_scalar, true, flash_tolerance, eos_type, verbosity);
613 if (!converged) {
614 converged = newtonComposition_(K_scalar, L_scalar, fluid_state_scalar, z_scalar, flash_tolerance, eos_type, verbosity);
615 }
616 } else {
617 throw std::logic_error("unknown two phase flash method " + flash_2p_method + " is specified");
618 }
619
620 if (!converged) {
621 throw std::runtime_error("flash calculation did not get converged with " + flash_2p_method);
622 }
623 }
624
625 template <class FlashFluidState, class ComponentVector>
626 static bool newtonComposition_(ComponentVector& K,
627 typename FlashFluidState::ValueType& L,
628 FlashFluidState& fluid_state,
629 const ComponentVector& z,
630 const Scalar tolerance,
631 const EOSType& eos_type,
632 int verbosity)
633 {
634 // Note: due to the need for inverse flash update for derivatives, the following two can be different
635 // Looking for a good way to organize them
636 constexpr size_t num_equations = numMisciblePhases * numMiscibleComponents + 1;
637 constexpr size_t num_primary_variables = numMisciblePhases * numMiscibleComponents + 1;
638 using NewtonVector = Dune::FieldVector<Scalar, num_equations>;
639 using NewtonMatrix = Dune::FieldMatrix<Scalar, num_equations, num_primary_variables>;
640
641 NewtonVector soln(0.);
642 NewtonVector res(0.);
643 NewtonMatrix jac (0.);
644
645 // Compute x and y from K, L and Z
646 computeLiquidVapor_(fluid_state, L, K, z);
647
648 // Print initial condition
649 if (verbosity >= 1) {
650 std::cout << " the current L is " << Opm::getValue(L) << std::endl;
651 std::cout << "Initial guess: x = [";
652 for (int compIdx=0; compIdx<numComponents; ++compIdx){
653 if (compIdx < numComponents - 1)
654 std::cout << fluid_state.moleFraction(oilPhaseIdx, compIdx) << " ";
655 else
656 std::cout << fluid_state.moleFraction(oilPhaseIdx, compIdx);
657 }
658 std::cout << "], y = [";
659 for (int compIdx=0; compIdx<numComponents; ++compIdx){
660 if (compIdx < numComponents - 1)
661 std::cout << fluid_state.moleFraction(gasPhaseIdx, compIdx) << " ";
662 else
663 std::cout << fluid_state.moleFraction(gasPhaseIdx, compIdx);
664 }
665 std::cout << "], and " << "L = " << L << std::endl;
666 }
667
668 // Print header
669 if (verbosity == 2 || verbosity == 4) {
670 std::cout << std::setw(10) << "Iteration" << std::setw(16) << "Norm2(step)" << std::setw(16) << "Norm2(Residual)" << std::endl;
671 }
672
673 // AD type
674 using Eval = DenseAd::Evaluation<Scalar, num_primary_variables>;
675 // TODO: we might need to use numMiscibleComponents here
676 std::vector<Eval> x(numComponents), y(numComponents);
677 Eval l;
678
679 // TODO: I might not need to set soln anything here.
680 for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) {
681 x[compIdx] = Eval(fluid_state.moleFraction(oilPhaseIdx, compIdx), compIdx);
682 const unsigned idx = compIdx + numComponents;
683 y[compIdx] = Eval(fluid_state.moleFraction(gasPhaseIdx, compIdx), idx);
684 }
685 l = Eval(L, num_primary_variables - 1);
686
687 // it is created for the AD calculation for the flash calculation
688 CompositionalFluidState<Eval, FluidSystem> flash_fluid_state;
689 for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) {
690 flash_fluid_state.setMoleFraction(FluidSystem::oilPhaseIdx, compIdx, x[compIdx]);
691 flash_fluid_state.setMoleFraction(FluidSystem::gasPhaseIdx, compIdx, y[compIdx]);
692 // TODO: should we use wilsonK_ here?
693 flash_fluid_state.setKvalue(compIdx, y[compIdx] / x[compIdx]);
694 }
695 flash_fluid_state.setLvalue(l);
696 // other values need to be Scalar, but I guess the fluid_state does not support it yet.
697 flash_fluid_state.setPressure(FluidSystem::oilPhaseIdx,
698 fluid_state.pressure(FluidSystem::oilPhaseIdx));
699 flash_fluid_state.setPressure(FluidSystem::gasPhaseIdx,
700 fluid_state.pressure(FluidSystem::gasPhaseIdx));
701
702 // TODO: not sure whether we need to set the saturations
703 flash_fluid_state.setSaturation(FluidSystem::gasPhaseIdx,
704 fluid_state.saturation(FluidSystem::gasPhaseIdx));
705 flash_fluid_state.setSaturation(FluidSystem::oilPhaseIdx,
706 fluid_state.saturation(FluidSystem::oilPhaseIdx));
707 flash_fluid_state.setTemperature(fluid_state.temperature(0));
708
709 using ParamCache = typename FluidSystem::template ParameterCache<typename CompositionalFluidState<Eval, FluidSystem>::ValueType>;
710 ParamCache paramCache(eos_type);
711
712 for (unsigned phaseIdx = 0; phaseIdx < numMisciblePhases; ++phaseIdx) {
713 paramCache.updatePhase(flash_fluid_state, phaseIdx);
714 for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) {
715 // TODO: will phi here carry the correct derivatives?
716 Eval phi = FluidSystem::fugacityCoefficient(flash_fluid_state, paramCache, phaseIdx, compIdx);
717 flash_fluid_state.setFugacityCoefficient(phaseIdx, compIdx, phi);
718 }
719 }
720 bool converged = false;
721 unsigned iter = 0;
722 constexpr unsigned max_iter = 1000;
723 while (iter < max_iter) {
724 assembleNewton_<CompositionalFluidState<Eval, FluidSystem>, ComponentVector, num_primary_variables, num_equations>
725 (flash_fluid_state, z, jac, res);
726 if (verbosity >= 1) {
727 std::cout << " newton residual is " << res.two_norm() << std::endl;
728 }
729 converged = res.two_norm() < tolerance;
730 if (converged) {
731 break;
732 }
733
734 jac.solve(soln, res);
735 constexpr Scalar damping_factor = 1.0;
736 // updating x and y
737 for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) {
738 x[compIdx] -= soln[compIdx] * damping_factor;
739 y[compIdx] -= soln[compIdx + numComponents] * damping_factor;
740 }
741 l -= soln[num_equations - 1] * damping_factor;
742
743 for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) {
744 flash_fluid_state.setMoleFraction(FluidSystem::oilPhaseIdx, compIdx, x[compIdx]);
745 flash_fluid_state.setMoleFraction(FluidSystem::gasPhaseIdx, compIdx, y[compIdx]);
746 // TODO: should we use wilsonK_ here?
747 flash_fluid_state.setKvalue(compIdx, y[compIdx] / x[compIdx]);
748 }
749 flash_fluid_state.setLvalue(l);
750
751 for (unsigned phaseIdx = 0; phaseIdx < numMisciblePhases; ++phaseIdx) {
752 paramCache.updatePhase(flash_fluid_state, phaseIdx);
753 for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) {
754 Eval phi = FluidSystem::fugacityCoefficient(flash_fluid_state, paramCache, phaseIdx, compIdx);
755 flash_fluid_state.setFugacityCoefficient(phaseIdx, compIdx, phi);
756 }
757 }
758 ++iter;
759 }
760 if (verbosity >= 1) {
761 for (unsigned i = 0; i < num_equations; ++i) {
762 for (unsigned j = 0; j < num_primary_variables; ++j) {
763 std::cout << " " << jac[i][j];
764 }
765 std::cout << std::endl;
766 }
767 std::cout << std::endl;
768 }
769 if (!converged) {
770 throw std::runtime_error(" Newton composition update did not converge within maxIterations " + std::to_string(max_iter));
771 }
772
773 // fluid_state is scalar, we need to update all the values for fluid_state here
774 for (unsigned idx = 0; idx < numComponents; ++idx) {
775 const auto x_i = Opm::getValue(flash_fluid_state.moleFraction(oilPhaseIdx, idx));
776 fluid_state.setMoleFraction(FluidSystem::oilPhaseIdx, idx, x_i);
777 const auto y_i = Opm::getValue(flash_fluid_state.moleFraction(gasPhaseIdx, idx));
778 fluid_state.setMoleFraction(FluidSystem::gasPhaseIdx, idx, y_i);
779 const auto K_i = Opm::getValue(flash_fluid_state.K(idx));
780 fluid_state.setKvalue(idx, K_i);
781 // TODO: not sure we need K and L here, because they are in the flash_fluid_state anyway.
782 K[idx] = K_i;
783 }
784 L = Opm::getValue(l);
785 fluid_state.setLvalue(L);
786 return converged;
787 }
788
789 // TODO: the interface will need to refactor for later usage
790 template<typename FlashFluidState, typename ComponentVector, size_t num_primary, size_t num_equation >
791 static void assembleNewton_(const FlashFluidState& fluid_state,
792 const ComponentVector& global_composition,
793 Dune::FieldMatrix<double, num_equation, num_primary>& jac,
794 Dune::FieldVector<double, num_equation>& res)
795 {
796 using Eval = DenseAd::Evaluation<double, num_primary>;
797 std::vector<Eval> x(numComponents), y(numComponents);
798 for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) {
799 x[compIdx] = fluid_state.moleFraction(oilPhaseIdx, compIdx);
800 y[compIdx] = fluid_state.moleFraction(gasPhaseIdx, compIdx);
801 }
802 const Eval& l = fluid_state.L();
803 // TODO: clearing zero whether necessary?
804 jac = 0.;
805 res = 0.;
806 for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) {
807 {
808 // z - L*x - (1-L) * y
809 auto local_res = -global_composition[compIdx] + l * x[compIdx] + (1 - l) * y[compIdx];
810 res[compIdx] = Opm::getValue(local_res);
811 for (unsigned i = 0; i < num_primary; ++i) {
812 jac[compIdx][i] = local_res.derivative(i);
813 }
814 }
815
816 {
817 // f_liquid - f_vapor = 0
818 auto local_res = (fluid_state.fugacity(oilPhaseIdx, compIdx) -
819 fluid_state.fugacity(gasPhaseIdx, compIdx));
820 res[compIdx + numComponents] = Opm::getValue(local_res);
821 for (unsigned i = 0; i < num_primary; ++i) {
822 jac[compIdx + numComponents][i] = local_res.derivative(i);
823 }
824 }
825 }
826 // sum(x) - sum(y) = 0
827 Eval sumx = 0.;
828 Eval sumy = 0.;
829 for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) {
830 sumx += x[compIdx];
831 sumy += y[compIdx];
832 }
833 auto local_res = sumx - sumy;
834 res[num_equation - 1] = Opm::getValue(local_res);
835 for (unsigned i = 0; i < num_primary; ++i) {
836 jac[num_equation - 1][i] = local_res.derivative(i);
837 }
838 }
839
840 template <typename FlashFluidStateScalar, typename FluidState>
841 static void updateDerivatives_(const FlashFluidStateScalar& fluid_state_scalar,
842 FluidState& fluid_state,
843 const EOSType& eos_type,
844 bool is_single_phase)
845 {
846 if(!is_single_phase)
847 updateDerivativesTwoPhase_(fluid_state_scalar, fluid_state, eos_type);
848 else
849 updateDerivativesSinglePhase_(fluid_state_scalar, fluid_state);
850
851 }
852
853 template <typename FlashFluidStateScalar, typename FluidState>
854 static void updateDerivativesTwoPhase_(const FlashFluidStateScalar& fluid_state_scalar,
855 FluidState& fluid_state,
856 const EOSType& eos_type)
857 {
858 using InputEval = typename FluidState::ValueType;
859 using ComponentVector = Dune::FieldVector<InputEval, numComponents>;
860 ComponentVector z;
861 for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) {
862 z[compIdx] = fluid_state.moleFraction(compIdx);
863 }
864
865 // getting the secondary Jocobian matrix
866 constexpr size_t num_equations = numMisciblePhases * numMiscibleComponents + 1;
867 constexpr size_t secondary_num_pv = isThermal ? numComponents + 2 : numComponents + 1;
868 // secondary variables: pressure, [temperature if thermal], z for all the components
869 using SecondaryEval = Opm::DenseAd::Evaluation<double, secondary_num_pv>;
870 using SecondaryComponentVector = Dune::FieldVector<SecondaryEval, numComponents>;
871 using SecondaryFlashFluidState = Opm::CompositionalFluidState<SecondaryEval, FluidSystem>;
872
873 SecondaryFlashFluidState secondary_fluid_state;
874 SecondaryComponentVector secondary_z;
875 // p and z are the primary variables here
876 // pressure
877 const SecondaryEval sec_p = SecondaryEval(fluid_state_scalar.pressure(FluidSystem::oilPhaseIdx), 0);
878 secondary_fluid_state.setPressure(FluidSystem::oilPhaseIdx, sec_p);
879 secondary_fluid_state.setPressure(FluidSystem::gasPhaseIdx, sec_p);
880
881 if constexpr (isThermal) {
882 // set the temperature with derivatives
883 const SecondaryEval sec_T = SecondaryEval(fluid_state_scalar.temperature(0), 1);
884 secondary_fluid_state.setTemperature(sec_T);
885 } else {
886 // set the temperature as a scalar
887 secondary_fluid_state.setTemperature(Opm::getValue(fluid_state_scalar.temperature(0)));
888 }
889
890 // composition variable offset: 2 for thermal (pressure=0, temperature=1, z starts at 2)
891 // 1 for non-thermal (pressure=0, z starts at 1)
892 constexpr unsigned z_offset = isThermal ? 2 : 1;
893 for (unsigned idx = 0; idx < numComponents; ++idx) {
894 secondary_z[idx] = SecondaryEval(Opm::getValue(z[idx]), idx + z_offset);
895 }
896 // set up the mole fractions
897 for (unsigned idx = 0; idx < numComponents; ++idx) {
898 // TODO: double checking that fluid_state_scalar returns a scalar here
899 const auto x_i = fluid_state_scalar.moleFraction(oilPhaseIdx, idx);
900 secondary_fluid_state.setMoleFraction(FluidSystem::oilPhaseIdx, idx, x_i);
901 const auto y_i = fluid_state_scalar.moleFraction(gasPhaseIdx, idx);
902 secondary_fluid_state.setMoleFraction(FluidSystem::gasPhaseIdx, idx, y_i);
903 // TODO: double checking make sure those are consistent
904 const auto K_i = fluid_state_scalar.K(idx);
905 secondary_fluid_state.setKvalue(idx, K_i);
906 }
907 const auto L = fluid_state_scalar.L();
908 secondary_fluid_state.setLvalue(L);
909 // TODO: Do we need to update the saturations?
910 // compositions
911 // TODO: we probably can simplify SecondaryFlashFluidState::ValueType
912 using SecondaryParamCache = typename FluidSystem::template ParameterCache<typename SecondaryFlashFluidState::ValueType>;
913 SecondaryParamCache secondary_param_cache(eos_type);
914 for (unsigned phase_idx = 0; phase_idx < numMisciblePhases; ++phase_idx) {
915 secondary_param_cache.updatePhase(secondary_fluid_state, phase_idx);
916 for (unsigned comp_idx = 0; comp_idx < numComponents; ++comp_idx) {
917 SecondaryEval phi = FluidSystem::fugacityCoefficient(secondary_fluid_state, secondary_param_cache, phase_idx, comp_idx);
918 secondary_fluid_state.setFugacityCoefficient(phase_idx, comp_idx, phi);
919 }
920 }
921
922 using SecondaryNewtonVector = Dune::FieldVector<Scalar, num_equations>;
923 using SecondaryNewtonMatrix = Dune::FieldMatrix<Scalar, num_equations, secondary_num_pv>;
924 SecondaryNewtonMatrix sec_jac;
925 SecondaryNewtonVector sec_res;
926
927
928 //use the regular equations
929 assembleNewton_<SecondaryFlashFluidState, SecondaryComponentVector, secondary_num_pv, num_equations>
930 (secondary_fluid_state, secondary_z, sec_jac, sec_res);
931
932
933 // assembly the major matrix here
934 // primary variables are x, y and L
935 constexpr size_t primary_num_pv = numMisciblePhases * numMiscibleComponents + 1;
936 using PrimaryEval = Opm::DenseAd::Evaluation<double, primary_num_pv>;
937 using PrimaryComponentVector = Dune::FieldVector<double, numComponents>;
938 using PrimaryFlashFluidState = Opm::CompositionalFluidState<PrimaryEval, FluidSystem>;
939
940 PrimaryFlashFluidState primary_fluid_state;
941 // primary_z is not needed, because we use z will be okay here
942 PrimaryComponentVector primary_z;
943 for (unsigned comp_idx = 0; comp_idx < numComponents; ++comp_idx) {
944 primary_z[comp_idx] = Opm::getValue(z[comp_idx]);
945 }
946 for (unsigned comp_idx = 0; comp_idx < numComponents; ++comp_idx) {
947 const auto x_ii = PrimaryEval(fluid_state_scalar.moleFraction(oilPhaseIdx, comp_idx), comp_idx);
948 primary_fluid_state.setMoleFraction(oilPhaseIdx, comp_idx, x_ii);
949 const unsigned idx = comp_idx + numComponents;
950 const auto y_ii = PrimaryEval(fluid_state_scalar.moleFraction(gasPhaseIdx, comp_idx), idx);
951 primary_fluid_state.setMoleFraction(gasPhaseIdx, comp_idx, y_ii);
952 primary_fluid_state.setKvalue(comp_idx, y_ii / x_ii);
953 }
954 PrimaryEval l;
955 l = PrimaryEval(fluid_state_scalar.L(), primary_num_pv - 1);
956 primary_fluid_state.setLvalue(l);
957 primary_fluid_state.setPressure(oilPhaseIdx, fluid_state_scalar.pressure(oilPhaseIdx));
958 primary_fluid_state.setPressure(gasPhaseIdx, fluid_state_scalar.pressure(gasPhaseIdx));
959 primary_fluid_state.setTemperature(fluid_state_scalar.temperature(0));
960
961 // TODO: is PrimaryFlashFluidState::ValueType> PrimaryEval here?
962 using PrimaryParamCache = typename FluidSystem::template ParameterCache<typename PrimaryFlashFluidState::ValueType>;
963 PrimaryParamCache primary_param_cache(eos_type);
964 for (unsigned phase_idx = 0; phase_idx < numMisciblePhases; ++phase_idx) {
965 primary_param_cache.updatePhase(primary_fluid_state, phase_idx);
966 for (unsigned comp_idx = 0; comp_idx < numComponents; ++comp_idx) {
967 PrimaryEval phi = FluidSystem::fugacityCoefficient(primary_fluid_state, primary_param_cache, phase_idx, comp_idx);
968 primary_fluid_state.setFugacityCoefficient(phase_idx, comp_idx, phi);
969 }
970 }
971
972 using PrimaryNewtonVector = Dune::FieldVector<Scalar, num_equations>;
973 using PrimaryNewtonMatrix = Dune::FieldMatrix<Scalar, num_equations, primary_num_pv>;
974 PrimaryNewtonVector pri_res;
975 PrimaryNewtonMatrix pri_jac;
976
977 //use the regular equations
978 assembleNewton_<PrimaryFlashFluidState, PrimaryComponentVector, primary_num_pv, num_equations>
979 (primary_fluid_state, primary_z, pri_jac, pri_res);
980
981 // the following code does not compile with DUNE2.6
982 // SecondaryNewtonMatrix xx;
983 // pri_jac.solve(xx, sec_jac);
984 pri_jac.invert();
985 sec_jac.template leftmultiply<PrimaryNewtonMatrix>(pri_jac);
986
987 ComponentVector x(numComponents), y(numComponents);
988 InputEval L_eval = L;
989
990 // use the chainrule (and using partial instead of total
991 // derivatives, DF / Dp = dF / dp + dF / ds * ds/dp.
992 // where p is the primary variables and s the secondary variables. We then obtain
993 // ds / dp = -inv(dF / ds)*(DF / Dp)
994
995 const auto p_l = fluid_state.pressure(FluidSystem::oilPhaseIdx);
996 const auto p_v = fluid_state.pressure(FluidSystem::gasPhaseIdx);
997 // at the moment, the temperature is the same for both phases
998 // T_input is not used for non-thermal case
999 [[maybe_unused]] const auto& T_input = fluid_state.temperature(0);
1000
1001 for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) {
1002 x[compIdx] = fluid_state_scalar.moleFraction(FluidSystem::oilPhaseIdx,compIdx);//;z[compIdx] * 1. / (L + (1 - L) * K[compIdx]);
1003 y[compIdx] = fluid_state_scalar.moleFraction(FluidSystem::gasPhaseIdx,compIdx);//;x[compIdx] * K[compIdx];
1004 }
1005
1006 // then we try to set the derivatives for x, y and L against P, [T] and z.
1007 // p_l and p_v are the same here, in the future, there might be slightly more complicated scenarios when capillary
1008 // pressure joins
1009
1010 constexpr size_t num_deri = InputEval::numVars;
1011 for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) {
1012 std::vector<double> deri(num_deri, 0.);
1013 // derivatives from P
1014 for (unsigned idx = 0; idx < num_deri; ++idx) {
1015 deri[idx] = -sec_jac[compIdx][0] * p_l.derivative(idx);
1016 }
1017 // derivatives from T (only for thermal case)
1018 if constexpr (isThermal) {
1019 for (unsigned idx = 0; idx < num_deri; ++idx) {
1020 deri[idx] += -sec_jac[compIdx][1] * T_input.derivative(idx);
1021 }
1022 }
1023
1024 for (unsigned cIdx = 0; cIdx < numComponents; ++cIdx) {
1025 const double pz = -sec_jac[compIdx][cIdx + z_offset];
1026 const auto& zi = z[cIdx];
1027 for (unsigned idx = 0; idx < num_deri; ++idx) {
1028 deri[idx] += pz * zi.derivative(idx);
1029 }
1030 }
1031 for (unsigned idx = 0; idx < num_deri; ++idx) {
1032 x[compIdx].setDerivative(idx, deri[idx]);
1033 }
1034 // handling y
1035 for (unsigned idx = 0; idx < num_deri; ++idx) {
1036 deri[idx] = -sec_jac[compIdx + numComponents][0] * p_v.derivative(idx);
1037 }
1038 // derivatives from T (only for thermal case)
1039 if constexpr (isThermal) {
1040 for (unsigned idx = 0; idx < num_deri; ++idx) {
1041 deri[idx] += -sec_jac[compIdx + numComponents][1] * T_input.derivative(idx);
1042 }
1043 }
1044 for (unsigned cIdx = 0; cIdx < numComponents; ++cIdx) {
1045 const double pz = -sec_jac[compIdx + numComponents][cIdx + z_offset];
1046 const auto& zi = z[cIdx];
1047 for (unsigned idx = 0; idx < num_deri; ++idx) {
1048 deri[idx] += pz * zi.derivative(idx);
1049 }
1050 }
1051 for (unsigned idx = 0; idx < num_deri; ++idx) {
1052 y[compIdx].setDerivative(idx, deri[idx]);
1053 }
1054
1055 // handling derivatives of L
1056 std::vector<double> deriL(num_deri, 0.);
1057 for (unsigned idx = 0; idx < num_deri; ++idx) {
1058 deriL[idx] = -sec_jac[2 * numComponents][0] * p_v.derivative(idx);
1059 }
1060 // derivatives from T (only for thermal case)
1061 if constexpr (isThermal) {
1062 for (unsigned idx = 0; idx < num_deri; ++idx) {
1063 deriL[idx] += -sec_jac[2 * numComponents][1] * T_input.derivative(idx);
1064 }
1065 }
1066 for (unsigned cIdx = 0; cIdx < numComponents; ++cIdx) {
1067 const double pz = -sec_jac[2 * numComponents][cIdx + z_offset];
1068 const auto& zi = z[cIdx];
1069 for (unsigned idx = 0; idx < num_deri; ++idx) {
1070 deriL[idx] += pz * zi.derivative(idx);
1071 }
1072 }
1073
1074 for (unsigned idx = 0; idx < num_deri; ++idx) {
1075 L_eval.setDerivative(idx, deriL[idx]);
1076 }
1077 }
1078
1079 // set up the mole fractions
1080 for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) {
1081 fluid_state.setMoleFraction(FluidSystem::oilPhaseIdx, compIdx, x[compIdx]);
1082 fluid_state.setMoleFraction(FluidSystem::gasPhaseIdx, compIdx, y[compIdx]);
1083 }
1084 fluid_state.setLvalue(L_eval);
1085 } //end updateDerivativesTwoPhase
1086
1087 template <typename FlashFluidStateScalar, typename FluidState>
1088 static void updateDerivativesSinglePhase_(const FlashFluidStateScalar& fluid_state_scalar,
1089 FluidState& fluid_state)
1090 {
1091 using InputEval = typename FluidState::ValueType;
1092 // L_eval is converted from a scalar, so all derivatives are zero at this point
1093 InputEval L_eval = fluid_state_scalar.L();;
1094
1095 // for single phase situation, x = y = z;
1096 // and L_eval have all zero derivatives
1097 for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) {
1098 fluid_state.setMoleFraction(FluidSystem::oilPhaseIdx, compIdx, fluid_state.moleFraction(compIdx) );
1099 fluid_state.setMoleFraction(FluidSystem::gasPhaseIdx, compIdx, fluid_state.moleFraction(compIdx) );
1100 }
1101 fluid_state.setLvalue(L_eval);
1102 } //end updateDerivativesSinglePhase
1103
1104
1105 // TODO: or use typename FlashFluidState::ValueType
1106 template <class FlashFluidState, class ComponentVector>
1107 static bool successiveSubstitutionComposition_(ComponentVector& K,
1108 typename ComponentVector::field_type& L,
1109 FlashFluidState& fluid_state,
1110 const ComponentVector& z,
1111 const bool newton_afterwards,
1112 const Scalar flash_tolerance,
1113 const EOSType& eos_type,
1114 const int verbosity)
1115 {
1116 // Determine max. iterations based on if it will be used as a standalone flash or as a pre-process to Newton (or other) method.
1117 const int maxIterations = newton_afterwards ? 5 : 100;
1118
1119 // Store cout format before manipulation
1120 std::ios_base::fmtflags f(std::cout.flags());
1121
1122 // Print initial guess
1123 if (verbosity >= 1)
1124 std::cout << "Initial guess: K = [" << K << "] and L = " << L << std::endl;
1125
1126 if (verbosity == 2 || verbosity == 4) {
1127 // Print header
1128 int fugWidth = (numComponents * 12)/2;
1129 int convWidth = fugWidth + 7;
1130 std::cout << std::setw(10) << "Iteration" << std::setw(fugWidth) << "fL/fV" << std::setw(convWidth) << "norm2(fL/fv-1)" << std::endl;
1131 }
1132 //
1133 // Successive substitution loop
1134 //
1135 for (int i=0; i < maxIterations; ++i){
1136 // Compute (normalized) liquid and vapor mole fractions
1137 computeLiquidVapor_(fluid_state, L, K, z);
1138
1139 // Calculate fugacity coefficient
1140 using ParamCache = typename FluidSystem::template ParameterCache<typename FlashFluidState::ValueType>;
1141 ParamCache paramCache(eos_type);
1142 for (int phaseIdx=0; phaseIdx<numMisciblePhases; ++phaseIdx){
1143 paramCache.updatePhase(fluid_state, phaseIdx);
1144 for (int compIdx=0; compIdx<numComponents; ++compIdx){
1145 auto phi = FluidSystem::fugacityCoefficient(fluid_state, paramCache, phaseIdx, compIdx);
1146 fluid_state.setFugacityCoefficient(phaseIdx, compIdx, phi);
1147 }
1148 }
1149
1150 // Calculate fugacity ratio
1151 ComponentVector newFugRatio;
1152 ComponentVector convFugRatio;
1153 for (int compIdx=0; compIdx<numComponents; ++compIdx){
1154 newFugRatio[compIdx] = fluid_state.fugacity(oilPhaseIdx, compIdx)/fluid_state.fugacity(gasPhaseIdx, compIdx);
1155 convFugRatio[compIdx] = newFugRatio[compIdx] - 1.0;
1156 }
1157
1158 // Print iteration info
1159 if (verbosity >= 2) {
1160 int prec = 5;
1161 int fugWidth = (prec + 3);
1162 int convWidth = prec + 9;
1163 std::cout << std::defaultfloat;
1164 std::cout << std::fixed;
1165 std::cout << std::setw(5) << i;
1166 std::cout << std::setw(fugWidth);
1167 std::cout << std::setprecision(prec);
1168 std::cout << newFugRatio;
1169 std::cout << std::scientific;
1170 std::cout << std::setw(convWidth) << convFugRatio.two_norm() << std::endl;
1171 }
1172
1173 // Check convergence
1174 if (convFugRatio.two_norm() < flash_tolerance) {
1175 // Restore cout format
1176 std::cout.flags(f);
1177
1178 // Print info
1179 if (verbosity >= 1) {
1180 std::cout << "Solution converged to the following result :" << std::endl;
1181 std::cout << "x = [";
1182 for (int compIdx = 0; compIdx < numComponents; ++compIdx) {
1183 if (compIdx < numComponents - 1)
1184 std::cout << fluid_state.moleFraction(oilPhaseIdx, compIdx) << " ";
1185 else
1186 std::cout << fluid_state.moleFraction(oilPhaseIdx, compIdx);
1187 }
1188 std::cout << "]" << std::endl;
1189 std::cout << "y = [";
1190 for (int compIdx = 0; compIdx < numComponents; ++compIdx) {
1191 if (compIdx < numComponents - 1)
1192 std::cout << fluid_state.moleFraction(gasPhaseIdx, compIdx) << " ";
1193 else
1194 std::cout << fluid_state.moleFraction(gasPhaseIdx, compIdx);
1195 }
1196 std::cout << "]" << std::endl;
1197 std::cout << "K = [" << K << "]" << std::endl;
1198 std::cout << "L = " << L << std::endl;
1199 }
1200 // Restore cout format format
1201 return true;
1202 }
1203 // If convergence is not met, K is updated in a successive substitution manner
1204 else {
1205 // Update K
1206 for (int compIdx=0; compIdx<numComponents; ++compIdx){
1207 K[compIdx] *= newFugRatio[compIdx];
1208 }
1209
1210 // Solve Rachford-Rice to get L from updated K
1211 L = solveRachfordRice_g_(K, z, 0);
1212 }
1213 }
1214 // did not get converged. check whether we will do more newton later afterward
1215 {
1216 const std::string msg = fmt::format("Successive substitution composition update did not converge within maxIterations {}.", maxIterations);
1217 if (!newton_afterwards) {
1218 throw std::runtime_error(msg);
1219 } else if (verbosity > 0) {
1220 std::cout << msg << std::endl;
1221 }
1222 }
1223
1224 return false;
1225 }
1226
1227};//end PTFlash
1228
1229} // namespace Opm
1230
1231#endif
Represents all relevant thermodynamic quantities of a multi-phase, multi-component fluid system assum...
Representation of an evaluation of a function and its derivatives w.r.t.
This file contains helper classes for the material laws.
A traits class which provides basic mathematical functions for arbitrary scalar floating point values...
A number of commonly used algebraic functions for the localized OPM automatic differentiation (AD) fr...
Implements a dummy linear saturation-capillary pressure relation which just disables capillary pressu...
Some templates to wrap the valgrind client request macros.
Represents all relevant thermodynamic quantities of a multi-phase, multi-component fluid system assum...
Definition CompositionalFluidState.hpp:53
A generic traits class which does not provide any indices.
Definition MaterialTraits.hpp:45
Implements a dummy linear saturation-capillary pressure relation which just disables capillary pressu...
Definition NullMaterial.hpp:46
Determines the phase compositions, pressures and saturations given the total mass of all components f...
Definition PTFlash.hpp:65
static void solve(FluidState &fluid_state, const ComponentVector &globalMolarities, Scalar tolerance=0.0)
Calculates the chemical equilibrium from the component fugacities in a phase.
Definition PTFlash.hpp:131
static bool solve(FluidState &fluid_state, const std::string &twoPhaseMethod, Scalar flash_tolerance, const EOSType &eos_type, int verbosity=0)
Calculates the fluid state from the global mole fractions of the components and the phase pressures.
Definition PTFlash.hpp:83
This class implements a small container which holds the transmissibility mulitpliers for all the face...
Definition Exceptions.hpp:30