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