Skip to content

Commit

Permalink
Implement stability test (#3136)
Browse files Browse the repository at this point in the history
  • Loading branch information
dkachuma authored and Algiane committed Jul 30, 2024
1 parent fc5a129 commit 917740c
Show file tree
Hide file tree
Showing 6 changed files with 1,127 additions and 3 deletions.
1 change: 1 addition & 0 deletions src/coreComponents/constitutive/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ set( constitutive_headers
fluid/multifluid/compositional/functions/KValueInitialization.hpp
fluid/multifluid/compositional/functions/NegativeTwoPhaseFlash.hpp
fluid/multifluid/compositional/functions/RachfordRice.hpp
fluid/multifluid/compositional/functions/StabilityTest.hpp
fluid/multifluid/compositional/models/ComponentProperties.hpp
fluid/multifluid/compositional/models/CompositionalDensity.hpp
fluid/multifluid/compositional/models/ConstantViscosity.hpp
Expand Down
119 changes: 117 additions & 2 deletions src/coreComponents/constitutive/docs/CompositionalMultiphaseFluid.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,117 @@ Overview
=========================

This model represents a full composition description of a multiphase multicomponent fluid.
Phase behavior is modeled by an Equation of State (EOS) and partitioning of components into
phases is computed based on instantaneous chemical equilibrium via a two- or three-phase flash.
Phase behavior is modeled by an equation of state (EOS) and partitioning of components into
phases is computed based on instantaneous chemical equilibrium via a two-phase flash.
Each component (species) is characterized by molar weight and critical properties that
serve as input parameters for the EOS.
See `Petrowiki`_ for more information.

In this model the fluid is described by :math:`N_c` components with :math:`z_c` being the total
mole fraction of component :math:`c`. The fluid can partition into a liquid phase, denoted :math:`\ell`,
and a vapor phase denoted by :math:`v`. Therefore, by taking into account the molar phase component
fractions, (which is the fraction of the molar mass of phase :math:`p` represented by component
:math:`c`), the following partition matrix establishes the component distribution within the two
phases:

.. math::
\begin{bmatrix}
x_{1} & x_{2} & x_{3} & \cdots & x_{N_c} \\
y_{1} & y_{2} & y_{3} & \cdots & y_{N_c} \\
\end{bmatrix}
where :math:`x_c` is the mole fraction of component :math:`c` in the liquid phase and :math:`y_c`
is the mole fraction of component :math:`c` in the vapor phase.

The fluid properties are updated through the following steps:

1) The phase fractions (:math:`\nu_p`) and phase component fractions (:math:`x_c` and :math:`y_c`) are
computed as a function of pressure (:math:`p`), temperature (:math:`T`) and total component fractions
(:math:`z_c`).

2) The phase densities (:math:`\rho_p`) and phase viscosities (:math:`\mu_p`) are computed as a function
of pressure, temperature and the updated phase component fractions.

After calculating the phase fractions, phase component fractions, phase densities, phase viscosities,
and their derivatives with respect to pressure, temperature, and component fractions, the
:ref:`CompositionalMultiphaseFlow` then moves on to assembling the accumulation and flux terms.

Step 1: Computation of the phase fractions and phase component fractions (flash)
================================================================================
Stability test
-------------------------------
The first step is to determine if the provided mixture with total molar fractions :math:`z_c` is stable
as a single phase at the current pressure :math:`p` and temperature :math:`T`. However, this can only
be confirmed through stability testing.

The stability of a mixture is traditionally assessed using the Tangent Plane Distance (TPD) criterion
developed by Michelsen (1982a). This criterion states that a phase with composition :math:`z` is stable
at a specified pressure :math:`p` and temperature :math:`T` if and only if

.. math::
g(y) = \sum_{i=1}^{N_c}y_i\left(\ln y_i + \ln \phi_i(y) - \ln z_i - \ln \phi_i(z) \right) \geq 0
for any permissible trial composition :math:`y`, where :math:`\phi_i` denotes the fugacity
coefficient of component :math:`i`.

To determine stability of the mixture this testing in initiated from a basic starting point, based on
Wilson K-values, to get both a lighter and a heavier trial mixture. The two trial mixtures are
calculated as :math:`y_i = z_i/K_i` and :math:`y_i = z_iK_i` where :math:`K_i` are defined by

.. math::
K_i = \frac{P_{ci}}{p}\exp\left( 5.37( 1 + \omega_i ) \left(1-\frac{T_{ci}}{T}\right)\right)
where :math:`P_{ci}` and :math:`T_{ci}` are respectively, the critical pressure and temperature of
component :math:`i` and :math:`\omega_i` is the accentric factor of component :math:`i`.

The stability problem is solved by observing that a necessary condition is that :math:`g(y)` must
be non-negative at all its stationary points. The stationarity criterion can be expressed as

.. math::
\ln y_i + \ln \phi_i(y) - h_i = k \hspace{1cm} i=1,2,3,\ldots,N_c
where :math:`h_i = \ln z_i + \ln \phi_i(z)` is a constant parameter dependent on the feed composition
:math:`z` and :math:`k` is an undetermined constant. This constant can be further incorporated into
the equation by defining the unnormalized trial phase moles :math:`Y_i` as

.. math::
Y_i = \exp(-k)y_i
which reduces the stationarity criterion to

.. math::
\ln Y_i + \ln \phi_i(y) - h_i = 0
with the mole fractions :math:`y_i` related to the trial phase moles :math:`Y_i` by

.. math::
y_i = Y_i/\sum_jY_j
With the two starting mixtures, the stationarity condition is solved using successive substitution to
determine the stationary points. If both initial states converge to a solution which has :math:`g(y)\geq 0`
then the mixture is deemed to be stable, otherwise it is deemed unstable.

Phase labeling
----------------------------------------
Once it is confirmed that the fluid with composition :math:`z` is stable as a single phase at the current
pressure and temperature, it must be labeled as either 'liquid' or 'vapor'. This is necessary only to apply
the correct relative permeability function for calculating the phase's flow properties. The properties of the
fluid (density, viscosity) are unchanged by the assignment of the label.

Determining the mixture's true critical point is the most rigorous method for labeling. It is however expensive
and may not always be necessary. As such, a simple correlation for pseudo-critical temperature is used and this
is expected to be sufficiently accurate for correct phase labeling, except under some specific conditions.

The Li-correlation is a weighted average of the component critical temperatures and is used to determine the label
applied to the mixture. The Li pseudo-critical temperature is calcaulated as

.. math::
T_{cp} = \frac{\sum_{i=1}^{N_c}T_{ci}V_{ci}z_{i}}{\sum_{i=1}^{N_c}V_{ci}z_{i}}
where :math:`V_{ci}` and :math:`T_{ci}` are respectively the critical volume and temperature of component
:math:`i`. This is compared to the current temperature :math:`T` such that if :math:`T_{cp}<T` then the mixture
is labeled as vapor and as liquid otherwise.

Parameters
=========================

Expand Down Expand Up @@ -65,5 +170,15 @@ Example
{ 0, 0, 0, 0 } }"/>
</Constitutive>
References
==========

- M. L. Michelsen, `The Isothermal Flash Problem. Part I. Stability.
<https://doi.org/10.1016/0378-3812(82)85001-2>`__, Fluid Phase Equilibria,
vol. 9.1, pp. 1-19, 1982a.

- M. L. Michelsen, `The Isothermal Flash Problem. Part II. Phase-Split Calculation.
<https://doi.org/10.1016/0378-3812(82)85002-4>`__, Fluid Phase Equilibria,
vol. 9.1, pp. 21-40, 1982b.

.. _Petrowiki: https://petrowiki.spe.org/Phase_behavior_in_reservoir_simulation#Equation-of-state_models
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
/*
* ------------------------------------------------------------------------------------------------------------
* SPDX-License-Identifier: LGPL-2.1-only
*
* Copyright (c) 2018-2020 Lawrence Livermore National Security LLC
* Copyright (c) 2018-2020 The Board of Trustees of the Leland Stanford Junior University
* Copyright (c) 2018-2020 TotalEnergies
* Copyright (c) 2019- GEOSX Contributors
* All rights reserved
*
* See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details.
* ------------------------------------------------------------------------------------------------------------
*/

/**
* @file StabilityTest.hpp
*/

#ifndef GEOS_CONSTITUTIVE_FLUID_MULTIFLUID_COMPOSITIONAL_FUNCTIONS_STABILITYTEST_HPP_
#define GEOS_CONSTITUTIVE_FLUID_MULTIFLUID_COMPOSITIONAL_FUNCTIONS_STABILITYTEST_HPP_

#include "KValueInitialization.hpp"
#include "constitutive/fluid/multifluid/Layouts.hpp"
#include "constitutive/fluid/multifluid/MultiFluidConstants.hpp"
#include "constitutive/fluid/multifluid/compositional/models/ComponentProperties.hpp"

namespace geos
{

namespace constitutive
{

namespace compositional
{

struct StabilityTest
{
private:
static constexpr integer maxNumComps = MultiFluidConstants::MAX_NUM_COMPONENTS;
public:
/**
* @brief Perform a two-phase stability test
* @param[in] numComps number of components
* @param[in] pressure pressure
* @param[in] temperature temperature
* @param[in] composition composition of the mixture
* @param[in] componentProperties The compositional component properties
* @param[out] tangentPlaneDistance the minimum tangent plane distance (TPD)
* @param[out] kValues the k-values estimated from the stationary points
* @return a flag indicating that 2 stationary points have been found
*/
template< typename EOS_TYPE, integer USD1 >
GEOS_HOST_DEVICE
static bool compute( integer const numComps,
real64 const pressure,
real64 const temperature,
arraySlice1d< real64 const, USD1 > const & composition,
ComponentProperties::KernelWrapper const & componentProperties,
real64 & tangentPlaneDistance,
arraySlice1d< real64 > const & kValues )
{
constexpr integer numTrials = 2; // Trial compositions
stackArray1d< real64, maxNumComps > logFugacity( numComps );
stackArray1d< real64, maxNumComps > normalizedComposition( numComps );
stackArray2d< real64, numTrials *maxNumComps > trialComposition( numTrials, numComps );
stackArray1d< real64, maxNumComps > logTrialComposition( numComps );
stackArray1d< real64, maxNumComps > hyperplane( numComps ); // h-parameter
stackArray1d< integer, maxNumComps > availableComponents( numComps );

calculatePresentComponents( numComps, composition, availableComponents );
auto const presentComponents = availableComponents.toSliceConst();

// Calculate the hyperplane parameter
// h_i = log( z_i ) + log( phi_i )
for( integer ic = 0; ic < numComps; ++ic )
{
hyperplane[ic] = 0.0;
}
EOS_TYPE::computeLogFugacityCoefficients( numComps,
pressure,
temperature,
composition,
componentProperties,
logFugacity );
for( integer const ic : presentComponents )
{
hyperplane[ic] = LvArray::math::log( composition[ic] ) + logFugacity[ic];
}

// Initialise the trial compositions using Wilson k-Values
KValueInitialization::computeWilsonGasLiquidKvalue( numComps,
pressure,
temperature,
componentProperties,
kValues );

for( integer ic = 0; ic < numComps; ++ic )
{
trialComposition( 0, ic ) = composition[ic] / kValues[ic];
trialComposition( 1, ic ) = composition[ic] * kValues[ic];
}

integer numberOfStationaryPoints = 0;
tangentPlaneDistance = LvArray::NumericLimits< real64 >::max;
for( integer trialIndex = 0; trialIndex < numTrials; ++trialIndex )
{
for( integer ic = 0; ic < numComps; ++ic )
{
normalizedComposition[ic] = trialComposition( trialIndex, ic );
}
normalizeComposition( numComps, normalizedComposition.toSlice() );
EOS_TYPE::computeLogFugacityCoefficients( numComps,
pressure,
temperature,
normalizedComposition.toSliceConst(),
componentProperties,
logFugacity );
for( integer const ic : presentComponents )
{
logTrialComposition[ic] = LvArray::math::log( trialComposition( trialIndex, ic ) );
}
for( localIndex iterationCount = 0; iterationCount < MultiFluidConstants::maxSSIIterations; ++iterationCount )
{
for( integer const ic : presentComponents )
{
logTrialComposition[ic] = hyperplane[ic] - logFugacity[ic];
trialComposition( trialIndex, ic ) = LvArray::math::exp( logTrialComposition[ic] );
normalizedComposition[ic] = trialComposition( trialIndex, ic );
}
normalizeComposition( numComps, normalizedComposition.toSlice() );
EOS_TYPE::computeLogFugacityCoefficients( numComps,
pressure,
temperature,
normalizedComposition.toSliceConst(),
componentProperties,
logFugacity );
real64 error = 0.0;
for( integer const ic : presentComponents )
{
real64 const dG = logTrialComposition[ic] + logFugacity[ic] - hyperplane[ic];
error += (dG*dG);
}
error = LvArray::math::sqrt( error );

if( error < MultiFluidConstants::fugacityTolerance )
{
// Calculate modified tangent plane distance (Michelsen, 1982b) of trial composition relative to input composition
real64 tpd = 1.0;
for( integer const ic : presentComponents )
{
tpd += trialComposition( trialIndex, ic ) * (logTrialComposition[ic] + logFugacity[ic] - hyperplane[ic] - 1.0);
}
if( tpd < tangentPlaneDistance )
{
tangentPlaneDistance = tpd;
}
numberOfStationaryPoints++;
break;
}
}
}
if( numberOfStationaryPoints == numTrials )
{
for( integer const ic : presentComponents )
{
kValues[ic] = trialComposition( 1, ic ) / trialComposition( 0, ic );
}
}
return numberOfStationaryPoints == numTrials;
}

private:
/**
* @brief Calculate which components are present.
* @details Creates a list of indices whose components have non-zero mole fraction.
* @param[in] numComps number of components
* @param[in] composition the composition of the fluid
* @param[out] presentComponents the list of present components
* @return the number of present components
*/
GEOS_HOST_DEVICE
GEOS_FORCE_INLINE
static integer calculatePresentComponents( integer const numComps,
arraySlice1d< real64 const > const & composition,
stackArray1d< integer, maxNumComps > & presentComponents )
{
// Check for machine-zero feed values
integer presentCount = 0;
for( integer ic = 0; ic < numComps; ++ic )
{
if( MultiFluidConstants::epsilon < composition[ic] )
{
presentComponents[presentCount++] = ic;
}
}
presentComponents.resize( presentCount );
return presentCount;
}

/**
* @brief Normalise a composition in place to ensure that the components add up to unity
* @param[in] numComps number of components
* @param[in/out] composition composition to be normalized
* @return the sum of the given values
*/
template< integer USD >
GEOS_HOST_DEVICE
GEOS_FORCE_INLINE
static real64 normalizeComposition( integer const numComps,
arraySlice1d< real64, USD > const & composition )
{
real64 totalMoles = 0.0;
for( integer ic = 0; ic < numComps; ++ic )
{
totalMoles += composition[ic];
}
GEOS_ASSERT( MultiFluidConstants::epsilon < totalMoles );
real64 const oneOverTotalMoles = 1.0 / totalMoles;
for( integer ic = 0; ic < numComps; ++ic )
{
composition[ic] *= oneOverTotalMoles;
}
return totalMoles;
}
};

} // namespace compositional

} // namespace constitutive

} // namespace geos

#endif //GEOS_CONSTITUTIVE_FLUID_MULTIFLUID_COMPOSITIONAL_FUNCTIONS_STABILITYTEST_HPP_
4 changes: 3 additions & 1 deletion src/coreComponents/constitutive/unitTests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
set( gtest_geosx_tests
testCompositionalDensity.cpp
testCompositionalProperties.cpp
testCubicEOS.cpp
testDamageUtilities.cpp
testDruckerPrager.cpp
testElasticIsotropic.cpp
Expand All @@ -12,7 +13,8 @@ set( gtest_geosx_tests
testNegativeTwoPhaseFlash9Comp.cpp
testParticleFluidEnums.cpp
testPropertyConversions.cpp
testCubicEOS.cpp
testStabilityTest2Comp.cpp
testStabilityTest9Comp.cpp
testRachfordRice.cpp )

set( dependencyList gtest blas lapack constitutive ${parallelDeps} )
Expand Down
Loading

0 comments on commit 917740c

Please sign in to comment.