From 740bfc2ef8ff8ce0a995c5ef716a0baf279aa2e4 Mon Sep 17 00:00:00 2001 From: dkachuma Date: Fri, 24 Apr 2026 15:27:58 -0500 Subject: [PATCH 01/25] Move relperm driver --- .../constitutiveDriver/relperm/relperm.ats | 20 ++ .../relperm/testRelperm_Table_2_phase.xml | 118 +++++++ .../relperm/testRelperm_Table_3_phase.xml | 41 +++ .../RelativePermeabilitySelector.hpp | 4 +- .../relativePermeability/RelpermDriver.cpp | 320 ++++++------------ .../relativePermeability/RelpermDriver.hpp | 104 ++---- .../RelpermDriverRunTest.hpp | 268 ++++----------- ...rmDriverTableRelativeHysteresisRunTest.cpp | 6 +- src/coreComponents/schema/schema.xsd | 12 +- src/coreComponents/schema/schema.xsd.other | 5 +- 10 files changed, 402 insertions(+), 496 deletions(-) create mode 100644 inputFiles/constitutiveDriver/relperm/relperm.ats create mode 100644 inputFiles/constitutiveDriver/relperm/testRelperm_Table_2_phase.xml create mode 100644 inputFiles/constitutiveDriver/relperm/testRelperm_Table_3_phase.xml diff --git a/inputFiles/constitutiveDriver/relperm/relperm.ats b/inputFiles/constitutiveDriver/relperm/relperm.ats new file mode 100644 index 00000000000..30c06d25a23 --- /dev/null +++ b/inputFiles/constitutiveDriver/relperm/relperm.ats @@ -0,0 +1,20 @@ +from geos.ats.test_builder import TestDeck, RestartcheckParameters, generate_geos_tests + +restartcheck_params = {'atol': 1e-08, 'rtol': 4e-07} + +def create_relperm_test(name, description=None): + if description is None: description = name + return TestDeck( + name=name, + description=description, + partitions=[(1, 1, 1)], + restart_step=0, + check_step=0, + restartcheck_params=RestartcheckParameters(**restartcheck_params)) + +decks = [ + create_relperm_test("testRelperm_Table_2_phase", "Relperm driver for 2 phase tables"), + create_relperm_test("testRelperm_Table_3_phase", "Relperm driver for 3 phase tables") +] + +generate_geos_tests(decks) diff --git a/inputFiles/constitutiveDriver/relperm/testRelperm_Table_2_phase.xml b/inputFiles/constitutiveDriver/relperm/testRelperm_Table_2_phase.xml new file mode 100644 index 00000000000..94574aafe28 --- /dev/null +++ b/inputFiles/constitutiveDriver/relperm/testRelperm_Table_2_phase.xml @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inputFiles/constitutiveDriver/relperm/testRelperm_Table_3_phase.xml b/inputFiles/constitutiveDriver/relperm/testRelperm_Table_3_phase.xml new file mode 100644 index 00000000000..715e5e58421 --- /dev/null +++ b/inputFiles/constitutiveDriver/relperm/testRelperm_Table_3_phase.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/coreComponents/constitutive/relativePermeability/RelativePermeabilitySelector.hpp b/src/coreComponents/constitutive/relativePermeability/RelativePermeabilitySelector.hpp index ee20a4c4f6e..fbba10578b8 100644 --- a/src/coreComponents/constitutive/relativePermeability/RelativePermeabilitySelector.hpp +++ b/src/coreComponents/constitutive/relativePermeability/RelativePermeabilitySelector.hpp @@ -51,7 +51,7 @@ void constitutiveUpdatePassThru( RelativePermeabilityBase const & relPerm, BrooksCoreyBakerRelativePermeability, BrooksCoreyStone2RelativePermeability, TableRelativePermeability, - constitutive::TableRelativePermeabilityHysteresis, + TableRelativePermeabilityHysteresis, VanGenuchtenBakerRelativePermeability, VanGenuchtenStone2RelativePermeability >::execute( relPerm, std::forward< LAMBDA >( lambda ) ); } @@ -64,7 +64,7 @@ void constitutiveUpdatePassThru( RelativePermeabilityBase & relPerm, BrooksCoreyBakerRelativePermeability, BrooksCoreyStone2RelativePermeability, TableRelativePermeability, - constitutive::TableRelativePermeabilityHysteresis, + TableRelativePermeabilityHysteresis, VanGenuchtenBakerRelativePermeability, VanGenuchtenStone2RelativePermeability >::execute( relPerm, std::forward< LAMBDA >( lambda ) ); } diff --git a/src/coreComponents/constitutiveDrivers/relativePermeability/RelpermDriver.cpp b/src/coreComponents/constitutiveDrivers/relativePermeability/RelpermDriver.cpp index ca17f97223f..4291eebf48e 100644 --- a/src/coreComponents/constitutiveDrivers/relativePermeability/RelpermDriver.cpp +++ b/src/coreComponents/constitutiveDrivers/relativePermeability/RelpermDriver.cpp @@ -13,204 +13,175 @@ * ------------------------------------------------------------------------------------------------------------ */ -#include "common/MpiWrapper.hpp" -#include "functions/FunctionManager.hpp" -#include "functions/TableFunction.hpp" +#include "RelpermDriver.hpp" + #include "constitutive/ConstitutiveManager.hpp" #include "constitutiveDrivers/LogLevelsInfo.hpp" #include "constitutive/relativePermeability/RelativePermeabilityBase.hpp" #include "constitutive/relativePermeability/RelativePermeabilitySelector.hpp" -#include "RelpermDriver.hpp" - namespace geos { using namespace dataRepository; using namespace constitutive; -RelpermDriver::RelpermDriver( const geos::string & name, - geos::dataRepository::Group * const parent ) - : - TaskBase( name, parent ) +RelpermDriver::RelpermDriver( const string & name, + Group * const parent ) + : ConstitutiveDriver( name, parent ) { registerWrapper( viewKeyStruct::relpermNameString(), &m_relpermName ). setRTTypeName( rtTypes::CustomTypes::groupNameRef ). setInputFlag( InputFlags::REQUIRED ). - setDescription( "Relperm model to test" ); + setDescription( "Relative permeability model to test" ); - registerWrapper( viewKeyStruct::numStepsString(), &m_numSteps ). - setInputFlag( InputFlags::REQUIRED ). - setDescription( "Number of saturation steps to take" ); - - registerWrapper( viewKeyStruct::outputString(), &m_outputFile ). + registerWrapper( viewKeyStruct::historicalSaturationsString(), &m_historicalSaturations ). setInputFlag( InputFlags::OPTIONAL ). - setApplyDefaultValue( "none" ). - setDescription( "Output file" ); - - registerWrapper( viewKeyStruct::baselineString(), &m_baselineFile ). - setInputFlag( InputFlags::OPTIONAL ). - setApplyDefaultValue( "none" ). - setDescription( "Baseline file" ); - - addLogLevel< logInfo::LogOutput >(); + setDescription( "Historical saturations for each phase." ); } - -void RelpermDriver::outputResults() +void RelpermDriver::postInputInitialization() { - // TODO: improve file path output to grab command line -o directory - // for the moment, we just use the specified m_outputFile directly + ConstitutiveDriver::postInputInitialization(); - FILE * fp = fopen( m_outputFile.c_str(), "w" ); + RelativePermeabilityBase const & baseRelperm = getRelperm(); - fprintf( fp, "# column 1 = time\n" ); - fprintf( fp, "# columns %d-%d = phase vol fractions\n", 2, 1 + m_numPhases ); - fprintf( fp, "# columns %d-%d = phase relperm\n", 2 + m_numPhases, 1 + 2 * m_numPhases ); + integer const numPhases = baseRelperm.numFluidPhases(); - if( ( m_numPhases == 2 && m_table.size( 1 ) > 5 ) || m_table.size( 1 ) > 7 ) - { - fprintf( fp, "# columns %d-%d = phase relperm (hyst)\n", 1 + 2 * m_numPhases, 1 + 3 * m_numPhases ); - } + // Must be 2-phase or 3-phase + GEOS_ERROR_IF( numPhases < 2 || 3 < numPhases, + "Number of phases for relative permeability model must be 2 or 3", + getWrapperDataContext( viewKeyStruct::relpermNameString() ) ); - - for( integer n = 0; n < m_table.size( 0 ); ++n ) + // Historical saturations must be the same number as the phases + if( !m_historicalSaturations.empty()) { - for( integer col = 0; col < m_table.size( 1 ); ++col ) - { - fprintf( fp, "%.4e ", m_table( n, col ) ); - } - fprintf( fp, "\n" ); + GEOS_ERROR_IF( m_historicalSaturations.size() != numPhases, + "Number of historical saturations must be the same as the number of phases", + getWrapperDataContext( viewKeyStruct::historicalSaturationsString() ) ); } - fclose( fp ); - - -} + string_array columnNames; + getColumnNames( columnNames ); + integer const numCols = static_cast< integer >(columnNames.size()); -void RelpermDriver::postInputInitialization() -{ - ConstitutiveManager - & constitutiveManager = this->getGroupByPath< ConstitutiveManager >( "/Problem/domain/Constitutive" ); - RelativePermeabilityBase - & baseRelperm = constitutiveManager.getGroup< RelativePermeabilityBase >( m_relpermName ); - - m_numPhases = baseRelperm.numFluidPhases(); - + allocateTable( numCols, numPhases ); } - -bool RelpermDriver::execute( const geos::real64 GEOS_UNUSED_PARAM( time_n ), - const geos::real64 GEOS_UNUSED_PARAM( dt ), - const geos::integer GEOS_UNUSED_PARAM( cycleNumber ), - const geos::integer GEOS_UNUSED_PARAM( eventCounter ), - const geos::real64 GEOS_UNUSED_PARAM( eventProgress ), - geos::DomainPartition & - GEOS_UNUSED_PARAM( domain ) ) +bool RelpermDriver::execute() { - // this code only makes sense in serial - - GEOS_THROW_IF( MpiWrapper::commRank() > 0, "RelpermDriver should only be run in serial", geos::RuntimeError ); + RelativePermeabilityBase & baseRelperm = getRelperm(); - - ConstitutiveManager - & constitutiveManager = this->getGroupByPath< ConstitutiveManager >( "/Problem/domain/Constitutive" ); - RelativePermeabilityBase - & baseRelperm = constitutiveManager.getGroup< RelativePermeabilityBase >( m_relpermName ); + integer const numPhases = baseRelperm.numFluidPhases(); GEOS_LOG_LEVEL_RANK_0( logInfo::LogOutput, "Launching Relperm Driver" ); - GEOS_LOG_LEVEL_RANK_0( logInfo::LogOutput, " Relperm .................. " << m_relpermName ); + GEOS_LOG_LEVEL_RANK_0( logInfo::LogOutput, " Relperm ................ " << m_relpermName ); GEOS_LOG_LEVEL_RANK_0( logInfo::LogOutput, " Type ................... " << baseRelperm.getCatalogName() ); - GEOS_LOG_LEVEL_RANK_0( logInfo::LogOutput, " No. of Phases .......... " << m_numPhases ); + GEOS_LOG_LEVEL_RANK_0( logInfo::LogOutput, " No. of Phases .......... " << numPhases ); GEOS_LOG_LEVEL_RANK_0( logInfo::LogOutput, " Steps .................. " << m_numSteps ); - GEOS_LOG_LEVEL_RANK_0( logInfo::LogOutput, " Output ................. " << m_outputFile ); - GEOS_LOG_LEVEL_RANK_0( logInfo::LogOutput, " Baseline ............... " << m_baselineFile ); + if( !m_outputFile.empty()) + { + GEOS_LOG_LEVEL_RANK_0( logInfo::LogOutput, " Output ................. " << m_outputFile ); + } + + initializeTable(); // create a dummy discretization with one quadrature point for // storing constitutive data - conduit::Node node; dataRepository::Group rootGroup( "root", node ); dataRepository::Group discretization( "discretization", &rootGroup ); - discretization.resize( 1 ); // one element + // Allocate as many elements as the number of rows + integer const numRows = m_table.size( 0 ); + discretization.resize( numRows ); // numRows elements baseRelperm.allocateConstitutiveData( discretization, 1 ); // one quadrature point constitutiveUpdatePassThru( baseRelperm, [&]( auto & selectedRelpermModel ) { using RELPERM_TYPE = TYPEOFREF( selectedRelpermModel ); - resizeTables< RELPERM_TYPE >(); runTest< RELPERM_TYPE >( selectedRelpermModel, m_table ); } ); // move table back to host for output m_table.move( LvArray::MemorySpace::host ); - if( m_outputFile != "none" ) + return false; +} + +void RelpermDriver::getColumnNames( string_array & columnNames ) const +{ + RelativePermeabilityBase const & baseRelperm = getRelperm(); + bool const has_hysteresis = (dynamic_cast< constitutive::TableRelativePermeabilityHysteresis const * >(&baseRelperm) != nullptr); + + integer const numPhases = baseRelperm.numFluidPhases(); + string_array const & phaseNames = baseRelperm.phaseNames(); + + columnNames.emplace_back( "index" ); + for( integer ip = 0; ip < numPhases; ip++ ) { - outputResults(); + columnNames.emplace_back( GEOS_FMT( "saturation,{}", phaseNames[ip] )); } - - if( m_baselineFile != "none" ) + if( has_hysteresis ) { - compareWithBaseline(); + for( integer ip = 0; ip < numPhases; ip++ ) + { + columnNames.emplace_back( GEOS_FMT( "historical saturation,{}", phaseNames[ip] )); + } + } + for( integer ip = 0; ip < numPhases; ip++ ) + { + columnNames.emplace_back( GEOS_FMT( "relperm,{}", phaseNames[ip] )); } - - return false; } +void RelpermDriver::allocateTable( integer numColumns, integer numPhases ) +{ + // For 3-phase we have m_numSteps+1 points for each of the other two phases + integer const numRows = (numPhases == 3) ? (m_numSteps+1)*(m_numSteps+1) : (m_numSteps+1); + m_table.resize( numRows, numColumns ); + for( integer index = 0; index < numRows; ++index ) + { + m_table( index, TIME ) = index; + } +} -template< typename RELPERM_TYPE > -void RelpermDriver::resizeTables() +void RelpermDriver::initializeTable() { - ConstitutiveManager - & constitutiveManager = this->getGroupByPath< ConstitutiveManager >( "/Problem/domain/Constitutive" ); - RelativePermeabilityBase - & baseRelperm = constitutiveManager.getGroup< RelativePermeabilityBase >( m_relpermName ); + RelativePermeabilityBase & baseRelperm = getRelperm(); using PT = RelativePermeabilityBase::PhaseType; integer const ipWater = baseRelperm.getPhaseOrder()[PT::WATER]; integer const ipOil = baseRelperm.getPhaseOrder()[PT::OIL]; integer const ipGas = baseRelperm.getPhaseOrder()[PT::GAS]; - real64 minSw = 0., minSnw = 0.; - if( baseRelperm.numFluidPhases() > 2 ) - { - minSw = baseRelperm.getWettingPhaseMinVolumeFraction(); - minSnw = baseRelperm.getNonWettingMinVolumeFraction(); - } - else - { - if( ipWater < 0 )// a.k.a o/g - { - minSw = 0; - minSnw = baseRelperm.getNonWettingMinVolumeFraction(); - } - else if( ipGas < 0 || ipOil < 0 )// a.k.a w/o or w/g - { - minSnw = 0; - minSw = baseRelperm.getWettingPhaseMinVolumeFraction(); - } - } + integer const numPhases = baseRelperm.numFluidPhases(); + + auto const [ipWetting, ipNonWetting] = baseRelperm.wettingAndNonWettingPhaseIndices(); + real64 const min_wetting_saturation = baseRelperm.getPhaseMinVolumeFraction()[ipWetting]; + real64 const min_non_wetting_saturation = baseRelperm.getPhaseMinVolumeFraction()[ipNonWetting]; - real64 const dSw = ( 1 - minSw - minSnw ) / m_numSteps; - // set input columns + real64 const dSw = ( 1.0 - min_wetting_saturation - min_non_wetting_saturation ) / m_numSteps; + + // Offset for saturations in table + constexpr integer SATURATION = 1; - resizeTable< RELPERM_TYPE >(); // 3-phase branch - if( m_numPhases > 2 ) + if( numPhases == 3 ) { + real64 swat = 0.0; + real64 sgas = 0.0; for( integer ni = 0; ni < m_numSteps + 1; ++ni ) { + swat = min_wetting_saturation + ni*dSw; for( integer nj = 0; nj < m_numSteps + 1; ++nj ) { + sgas = min_non_wetting_saturation + nj*dSw; integer index = ni * ( m_numSteps + 1 ) + nj; - m_table( index, TIME ) = minSw + index * dSw; - m_table( index, ipWater + 1 ) = minSw + nj * dSw; - m_table( index, ipGas + 1 ) = minSnw + ni * dSw; - m_table( index, ipOil + 1 ) = - 1. - m_table( index, ipWater + 1 ) - m_table( index, ipOil + 1 ); + m_table( index, ipWater + SATURATION ) = swat; + m_table( index, ipGas + SATURATION ) = sgas; + m_table( index, ipOil + SATURATION ) = 1.0 - swat - sgas; } } } @@ -218,118 +189,23 @@ void RelpermDriver::resizeTables() { for( integer ni = 0; ni < m_numSteps + 1; ++ni ) { - integer index = ni; - m_table( index, TIME ) = minSw + index * dSw; - if( ipWater < 0 ) - { - m_table( index, ipGas + 1 ) = minSnw + ni * dSw; - m_table( index, ipOil + 1 ) = 1. - m_table( index, ipGas + 1 ); - } - else if( ipGas < 0 ) - { - m_table( index, ipWater + 1 ) = minSw + ni * dSw; - m_table( index, ipOil + 1 ) = 1. - m_table( index, ipWater + 1 ); - } - else if( ipOil < 0 ) - { - m_table( index, ipWater + 1 ) = minSw + ni * dSw; - m_table( index, ipGas + 1 ) = 1. - m_table( index, ipWater + 1 ); - } + real64 const s_nw = min_non_wetting_saturation + ni * dSw; + m_table( ni, ipNonWetting + SATURATION ) = s_nw; + m_table( ni, ipWetting + SATURATION ) = 1.0 - s_nw; } - } - - } - -template< typename RELPERM_TYPE > -std::enable_if_t< std::is_same< TableRelativePermeabilityHysteresis, RELPERM_TYPE >::value, void > -RelpermDriver::resizeTable() +RelativePermeabilityBase & RelpermDriver::getRelperm() { - if( m_numPhases > 2 ) - { - m_table.resize( ( m_numSteps + 1 ) * ( m_numSteps + 1 ), 1 + 3 * m_numPhases ); - } - else - { - m_table.resize( m_numSteps + 1, 1 + 3 * m_numPhases ); - } - + return getConstitutiveManager().getGroup< RelativePermeabilityBase >( m_relpermName ); } -template< typename RELPERM_TYPE > -std::enable_if_t< !std::is_same< TableRelativePermeabilityHysteresis, RELPERM_TYPE >::value, void > -RelpermDriver::resizeTable() +RelativePermeabilityBase const & RelpermDriver::getRelperm() const { - if( m_numPhases > 2 ) - { - m_table.resize( ( m_numSteps + 1 ) * ( m_numSteps + 1 ), 1 + 2 * m_numPhases ); - } - else - { - m_table.resize( m_numSteps + 1, 1 + 2 * m_numPhases ); - } -} - - -//TODO refactor - duplication -void RelpermDriver::compareWithBaseline() -{ - // open baseline file - - std::ifstream file( m_baselineFile.c_str() ); - GEOS_THROW_IF( !file.is_open(), - GEOS_FMT( "Can't seem to open the baseline file {}", m_baselineFile ), - InputError ); - - // discard file header - - string line; - for( integer row = 0; row < 7; ++row ) - { - getline( file, line ); - } - - // read data block. we assume the file size is consistent with m_table, - // but check for a premature end-of-file. we then compare results value by value. - // we ignore the newton iteration and residual columns, as those may be platform - // specific. - - real64 value; - //table is redim to fit the layout of relperm so the second dimension is numGaussPt - // and always of size 1 - for( integer row = 0; row < m_table.size( 0 ); ++row ) - { - for( integer col = 0; col < m_table.size( 1 ); ++col ) - { - GEOS_THROW_IF( file.eof(), "Baseline file appears shorter than internal results", geos::RuntimeError ); - file >> value; - - real64 const error = fabs( m_table[row][col] - value ) / ( fabs( value ) + 1 ); - GEOS_THROW_IF( error > m_baselineTol, - GEOS_FMT( "Results do not match baseline at data row {} (row {} with header) and column {}", - row + 1, - row + m_numColumns, - col + 1 ), - geos::RuntimeError ); - } - } - - // check we actually reached the end of the baseline file - - file >> value; - GEOS_THROW_IF( !file.eof(), "Baseline file appears longer than internal results", geos::RuntimeError ); - - // success - - GEOS_LOG_LEVEL_RANK_0( logInfo::LogOutput, " Comparison ............. Internal results consistent with baseline." ); - - file.close(); + return getConstitutiveManager().getGroup< RelativePermeabilityBase >( m_relpermName ); } - - REGISTER_CATALOG_ENTRY( TaskBase, RelpermDriver, string const &, dataRepository::Group * const ) diff --git a/src/coreComponents/constitutiveDrivers/relativePermeability/RelpermDriver.hpp b/src/coreComponents/constitutiveDrivers/relativePermeability/RelpermDriver.hpp index 426c66bf130..89e4f8b5cce 100644 --- a/src/coreComponents/constitutiveDrivers/relativePermeability/RelpermDriver.hpp +++ b/src/coreComponents/constitutiveDrivers/relativePermeability/RelpermDriver.hpp @@ -13,111 +13,73 @@ * ------------------------------------------------------------------------------------------------------------ */ -#ifndef GEOS_RELPERMDRIVER_HPP_ -#define GEOS_RELPERMDRIVER_HPP_ +#ifndef GEOS_CONSTITUTIVEDRIVERS_RELATIVEPERMEABILITY_RELPERMDRIVER_HPP +#define GEOS_CONSTITUTIVEDRIVERS_RELATIVEPERMEABILITY_RELPERMDRIVER_HPP -#include "events/tasks/TaskBase.hpp" -#include "constitutive/relativePermeability/TableRelativePermeabilityHysteresis.hpp" +#include "constitutiveDrivers/ConstitutiveDriver.hpp" namespace geos { -class RelpermDriver : public TaskBase +namespace constitutive { +class RelativePermeabilityBase; +} +/** + * @class RelpermDriver + * + * Class to allow for testing Relative permeability models without the + * complexity of setting up a full simulation. + */ +class RelpermDriver : public ConstitutiveDriver +{ public: - RelpermDriver( const string & name, - Group * const parent ); + RelpermDriver( const string & name, Group * const parent ); - static string catalogName() - { return "RelpermDriver"; } + static string catalogName() { return "RelpermDriver"; } void postInputInitialization() override; - virtual bool execute( real64 const GEOS_UNUSED_PARAM( time_n ), - real64 const GEOS_UNUSED_PARAM( dt ), - integer const GEOS_UNUSED_PARAM( cycleNumber ), - integer const GEOS_UNUSED_PARAM( eventCounter ), - real64 const GEOS_UNUSED_PARAM( eventProgress ), - DomainPartition & - GEOS_UNUSED_PARAM( domain ) ) override; + bool execute() override; + + void getColumnNames( string_array & columnNames ) const override; /** * @brief Run test using loading protocol in table - * @param i Relperm constitutive model + * @param relperm Relperm constitutive model * @param table Table with input/output time history */ template< typename RELPERM_TYPE > - std::enable_if_t< std::is_same< constitutive::TableRelativePermeabilityHysteresis, RELPERM_TYPE >::value, void > - runTest( RELPERM_TYPE & relperm, - const arrayView2d< real64, 1 > & table ); - - template< typename RELPERM_TYPE > - std::enable_if_t< !std::is_same< constitutive::TableRelativePermeabilityHysteresis, RELPERM_TYPE >::value, void > - runTest( RELPERM_TYPE & relperm, - const arrayView2d< real64, 1 > & table ); + void runTest( RELPERM_TYPE & relperm, const arrayView2d< real64, 1 > & table ); +private: /** - * @brief Ouput table to file for easy plotting + * @brief Get the relative permeability model from the catalog */ - void outputResults(); + constitutive::RelativePermeabilityBase & getRelperm(); + constitutive::RelativePermeabilityBase const & getRelperm() const; + + void allocateTable( integer numColumns, integer numPhases ); /** - * @brief Read in a baseline table from file and compare with computed one (for unit testing purposes) + * @brief Initialises the table by filling in primary variables */ - void compareWithBaseline(); - -private: - - template< typename RELPERM_TYPE > - void resizeTables(); - - template< typename RELPERM_TYPE > - std::enable_if_t< std::is_same< constitutive::TableRelativePermeabilityHysteresis, RELPERM_TYPE >::value, void > - resizeTable(); - - template< typename RELPERM_TYPE > - std::enable_if_t< !std::is_same< constitutive::TableRelativePermeabilityHysteresis, RELPERM_TYPE >::value, void > - resizeTable(); + void initializeTable(); /** * @struct viewKeyStruct holds char strings and viewKeys for fast lookup */ struct viewKeyStruct { - constexpr static char const * relpermNameString() - { return "relperm"; } - - constexpr static char const * numStepsString() - { return "steps"; } - - constexpr static char const * outputString() - { return "output"; } - - constexpr static char const * baselineString() - { return "baseline"; } + constexpr static char const * relpermNameString() { return "relperm"; } + constexpr static char const * historicalSaturationsString() { return "historicalSaturations"; } }; - integer m_numSteps; ///< Number of load steps - integer m_numColumns; ///< Number of columns in data table (depends on number of fluid phases) - integer m_numPhases; ///< Number of fluid phases - string m_relpermName; ///< relPermType identifier - string m_outputFile; ///< Output file (optional, no output if not specified) - - array2d< real64 > m_table; ///< Table storing time-history of input/output - - Path m_baselineFile; ///< Baseline file (optional, for unit testing of solid models) - - enum columnKeys - { - TIME - }; ///< Enumeration of "input" column keys for readability - - static constexpr real64 m_baselineTol = 1e-3; ///< Comparison tolerance for baseline results + array1d< real64 > m_historicalSaturations; }; - } -#endif //GEOS_RELPERMDRIVER_HPP_ +#endif //GEOS_CONSTITUTIVEDRIVERS_RELATIVEPERMEABILITY_RELPERMDRIVER_HPP diff --git a/src/coreComponents/constitutiveDrivers/relativePermeability/RelpermDriverRunTest.hpp b/src/coreComponents/constitutiveDrivers/relativePermeability/RelpermDriverRunTest.hpp index eabe5616eee..e430e8fab3f 100644 --- a/src/coreComponents/constitutiveDrivers/relativePermeability/RelpermDriverRunTest.hpp +++ b/src/coreComponents/constitutiveDrivers/relativePermeability/RelpermDriverRunTest.hpp @@ -13,239 +13,119 @@ * ------------------------------------------------------------------------------------------------------------ */ -#ifndef GEOS_RELPERMDRIVERRUNTEST_HPP_ -#define GEOS_RELPERMDRIVERRUNTEST_HPP_ +#ifndef GEOS_CONSTITUTIVEDRIVERS_RELATIVEPERMEABILITY_RELPERMDRIVERRUNTEST_HPP +#define GEOS_CONSTITUTIVEDRIVERS_RELATIVEPERMEABILITY_RELPERMDRIVERRUNTEST_HPP #include "constitutiveDrivers/relativePermeability/RelpermDriver.hpp" +#include "constitutive/relativePermeability/RelativePermeabilityBase.hpp" #include "constitutive/relativePermeability/RelativePermeabilityFields.hpp" -#include "constitutive/relativePermeability/Layouts.hpp" #include "constitutive/relativePermeability/KilloughHysteresis.hpp" - +#include "common/DataLayouts.hpp" namespace geos { -//specific to Hysteresis +// Hysteresis traits +template< typename RELPERM_TYPE > +struct HasHysteresis : std::false_type +{}; + template< typename RELPERM_TYPE > -std::enable_if_t< std::is_same< constitutive::TableRelativePermeabilityHysteresis, - RELPERM_TYPE >::value, void > -RelpermDriver::runTest( RELPERM_TYPE & relperm, - const arrayView2d< real64 > & table ) +void +RelpermDriver::runTest( RELPERM_TYPE & relperm, const arrayView2d< real64 > & table ) { - // get number of phases and components + // Get the number of phases integer const numPhases = relperm.numFluidPhases(); - // create kernel wrapper + // Create the kernel wrapper + typename RELPERM_TYPE::KernelWrapper const kernelWrapper = relperm.createKernelWrapper(); - typename constitutive::TableRelativePermeabilityHysteresis::KernelWrapper const kernelWrapper = relperm.createKernelWrapper(); + // Offset for saturations in table + constexpr integer SATURATION = 1; - // set saturation to user specified feed - // it is more convenient to provide input in molar, so perform molar to mass conversion here + // Offset for relative permeability data + integer RELPERM = SATURATION + numPhases; - array2d< real64, compflow::LAYOUT_PHASE > saturationValues; - if( numPhases > 2 ) - { - saturationValues.resize( ( m_numSteps + 1 ) * ( m_numSteps + 1 ), numPhases ); - } - else - { - saturationValues.resize( m_numSteps + 1, numPhases ); - } - using PT = typename RELPERM_TYPE::PhaseType; - integer const ipWater = relperm.getPhaseOrder()[PT::WATER]; - integer const ipOil = relperm.getPhaseOrder()[PT::OIL]; - integer const ipGas = relperm.getPhaseOrder()[PT::GAS]; - localIndex offset = std::max( std::max( ipOil, ipWater ), std::max( ipOil, ipGas ) ) + 1; - - integer ipWetting = -1, ipNonWetting = -1; - std::tie( ipWetting, ipNonWetting ) = relperm.wettingAndNonWettingPhaseIndices(); + // Number of "cells" + integer const numRows = m_table.size( 0 ); - for( integer n = 0; n < table.size( 0 ); ++n ) + // If we have hysteresis, we need to populate the historical saturations + if constexpr (HasHysteresis< RELPERM_TYPE >::value) { - if( m_numPhases > 2 ) - { - saturationValues[n][ipWater] = table( n, ipWater + 1 ); - saturationValues[n][ipOil] = table( n, ipOil + 1 ); - saturationValues[n][ipGas] = table( n, ipGas + 1 ); - } - else//two-phase - { - if( ipWater < 0 ) - { - saturationValues[n][ipOil] = table( n, ipOil + 1 ); - saturationValues[n][ipGas] = table( n, ipGas + 1 ); - } - else if( ipGas < 0 ) - { - saturationValues[n][ipWater] = table( n, ipWater + 1 ); - saturationValues[n][ipOil] = table( n, ipOil + 1 ); - } - else if( ipOil < 0 ) - { - saturationValues[n][ipWater] = table( n, ipWater + 1 ); - saturationValues[n][ipGas] = table( n, ipGas + 1 ); - } - } - } - - - arrayView2d< real64 const, compflow::USD_PHASE > const saturation = saturationValues.toViewConst(); + // Shift the relative permeability offset + RELPERM += numPhases; - auto const & phaseHasHysteresis = relperm.template getReference< array1d< integer > >( constitutive::TableRelativePermeabilityHysteresis::viewKeyStruct::phaseHasHysteresisString()); + // Offset for the historical saturations + integer const OFFSET = SATURATION + numPhases; - arrayView2d< real64, compflow::USD_PHASE > phaseMaxHistoricalVolFraction = relperm.template getField< fields::relperm::phaseMaxHistoricalVolFraction >().reference(); - arrayView2d< real64, compflow::USD_PHASE > phaseMinHistoricalVolFraction = relperm.template getField< fields::relperm::phaseMinHistoricalVolFraction >().reference(); + using CurveType = constitutive::KilloughHysteresis::HysteresisCurve; + using KeyStruct = typename RELPERM_TYPE::viewKeyStruct; -// arrayView1d< real64 > const drainagePhaseMinVolFraction = relperm.template getReference< array1d< real64 > >( -// constitutive::TableRelativePermeabilityHysteresis::viewKeyStruct::drainagePhaseMinVolumeFractionString()); -// arrayView1d< real64 > const drainagePhaseMaxVolFraction = relperm.template getReference< array1d< real64 > >( -// constitutive::TableRelativePermeabilityHysteresis::viewKeyStruct::drainagePhaseMaxVolumeFractionString()); - constitutive::KilloughHysteresis::HysteresisCurve const wettingCurve = relperm.template getReference< constitutive::KilloughHysteresis::HysteresisCurve >( - constitutive::TableRelativePermeabilityHysteresis::viewKeyStruct::wettingCurveString()); + auto [ipWetting, ipNonWetting] = relperm.wettingAndNonWettingPhaseIndices(); - constitutive::KilloughHysteresis::HysteresisCurve const nonWettingCurve = relperm.template getReference< constitutive::KilloughHysteresis::HysteresisCurve >( - constitutive::TableRelativePermeabilityHysteresis::viewKeyStruct::nonWettingCurveString()); - //setting for drainage - { - if( phaseHasHysteresis[ipNonWetting] ) - { - phaseMaxHistoricalVolFraction[0][ipNonWetting] = nonWettingCurve.m_extremumPhaseVolFraction; - GEOS_LOG( GEOS_FMT( "New max non-wetting phase historical phase volume fraction: {}", phaseMaxHistoricalVolFraction[0][ipNonWetting] ) ); - } - if( phaseHasHysteresis[ipWetting] ) - { - phaseMinHistoricalVolFraction[0][ipWetting] = wettingCurve.m_extremumPhaseVolFraction; - GEOS_LOG( GEOS_FMT( "New min wetting phase historical phase volume fraction: {}", phaseMinHistoricalVolFraction[0][ipWetting] ) ); - } - } + stackArray1d< real64, constitutive::RelativePermeabilityBase::MAX_NUM_PHASES > historicalSaturations( numPhases ); + historicalSaturations.zero(); - - - forAll< parallelDevicePolicy<> >( saturation.size( 0 ), - [numPhases, kernelWrapper, saturation, table, - offset] GEOS_HOST_DEVICE ( integer const n ) - { - // nw phase set max to snw_max to get the imbibition bounding curve - kernelWrapper.update( 0, 0, saturation[n] ); - for( integer p = 0; p < numPhases; ++p ) + if( m_historicalSaturations.empty()) { - table( n, offset + 1 + p ) = kernelWrapper.relperm()( 0, 0, p ); + auto const & phaseHasHysteresis = relperm.template getReference< array1d< integer > >( KeyStruct::phaseHasHysteresisString()); + if( phaseHasHysteresis[ipNonWetting] ) + { + CurveType const nonWettingCurve = relperm.template getReference< CurveType >( KeyStruct::nonWettingCurveString() ); + historicalSaturations[ipNonWetting] = nonWettingCurve.m_extremumPhaseVolFraction; + } + if( phaseHasHysteresis[ipWetting] ) + { + CurveType const wettingCurve = relperm.template getReference< CurveType >( KeyStruct::wettingCurveString()); + historicalSaturations[ipWetting] = wettingCurve.m_extremumPhaseVolFraction; + } } - } ); - - //loop in charge of hysteresis values - offset += numPhases; - -//setting for imbibition - { - if( phaseHasHysteresis[ipNonWetting] ) + else { - - phaseMaxHistoricalVolFraction[0][ipNonWetting] = nonWettingCurve.m_criticalDrainagePhaseVolFraction; - GEOS_LOG( GEOS_FMT( "New max non-wetting phase historical phase volume fraction: {}", phaseMaxHistoricalVolFraction[0][ipNonWetting] ) ); + for( integer p = 0; p < numPhases; ++p ) + { + historicalSaturations[p] = m_historicalSaturations[p]; + } } - if( phaseHasHysteresis[ipWetting] ) + auto historicalView = historicalSaturations.toView(); + + arrayView2d< real64, compflow::USD_PHASE > phaseMaxHistoricalVolFraction = relperm.template getField< fields::relperm::phaseMaxHistoricalVolFraction >().reference(); + arrayView2d< real64, compflow::USD_PHASE > phaseMinHistoricalVolFraction = relperm.template getField< fields::relperm::phaseMinHistoricalVolFraction >().reference(); + forAll< parallelDevicePolicy<> >( numRows, + [numPhases, table, + ipWetting, ipNonWetting, + OFFSET, + phaseMaxHistoricalVolFraction, + phaseMinHistoricalVolFraction, + historicalView] GEOS_HOST_DEVICE ( integer const n ) { - phaseMinHistoricalVolFraction[0][ipWetting] = wettingCurve.m_criticalDrainagePhaseVolFraction; - GEOS_LOG( GEOS_FMT( "New min wetting phase historical phase volume fraction: {}", phaseMinHistoricalVolFraction[0][ipWetting] ) ); - } + phaseMaxHistoricalVolFraction( n, ipNonWetting ) = historicalView[ipNonWetting]; + phaseMinHistoricalVolFraction( n, ipWetting ) = historicalView[ipWetting]; + for( integer p = 0; p < numPhases; ++p ) + { + table( n, p + OFFSET ) = historicalView[p]; + } + } ); } - - - forAll< parallelDevicePolicy<> >( saturation.size( 0 ), - [numPhases, kernelWrapper, saturation, table, - offset] GEOS_HOST_DEVICE ( integer const n ) + forAll< parallelDevicePolicy<> >( numRows, + [numPhases, kernelWrapper, table, + RELPERM] GEOS_HOST_DEVICE ( integer const n ) { - // nw phase set max to snw_max to get the imbibition bounding curve + StackArray< real64, 2, constitutive::RelativePermeabilityBase::MAX_NUM_PHASES, compflow::LAYOUT_PHASE > saturation( 1, numPhases ); - kernelWrapper.update( 0, 0, saturation[n] ); for( integer p = 0; p < numPhases; ++p ) { - table( n, offset + 1 + p ) = kernelWrapper.relperm()( 0, 0, p ); + saturation[0][p] = table( n, SATURATION + p ); } - } ); - - -} - -template< typename RELPERM_TYPE > -std::enable_if_t< !std::is_same< constitutive::TableRelativePermeabilityHysteresis, RELPERM_TYPE >::value, void > -RelpermDriver::runTest( RELPERM_TYPE & relperm, - const arrayView2d< real64 > & table ) -{ - // get number of phases and components - - integer const numPhases = relperm.numFluidPhases(); - - // create kernel wrapper - - typename RELPERM_TYPE::KernelWrapper const kernelWrapper = relperm.createKernelWrapper(); - - // set saturation to user specified feed - // it is more convenient to provide input in molar, so perform molar to mass conversion here - - array2d< real64, compflow::LAYOUT_PHASE > saturationValues; - if( numPhases > 2 ) - { - saturationValues.resize(( m_numSteps + 1 ) * ( m_numSteps + 1 ), numPhases ); - } - else - { - saturationValues.resize( m_numSteps + 1, numPhases ); - } - using PT = typename RELPERM_TYPE::PhaseType; - integer const ipWater = relperm.getPhaseOrder()[PT::WATER]; - integer const ipOil = relperm.getPhaseOrder()[PT::OIL]; - integer const ipGas = relperm.getPhaseOrder()[PT::GAS]; - const localIndex offset = std::max( std::max( ipOil, ipWater ), std::max( ipOil, ipGas ) ) + 1; - - for( integer n = 0; n < table.size( 0 ); ++n ) - { - - - if( m_numPhases > 2 ) - { - saturationValues[n][ipWater] = table( n, ipWater + 1 ); - saturationValues[n][ipOil] = table( n, ipOil + 1 ); - saturationValues[n][ipGas] = table( n, ipGas + 1 ); - } - else//two-phase - { - if( ipWater < 0 ) - { - saturationValues[n][ipOil] = table( n, ipOil + 1 ); - saturationValues[n][ipGas] = table( n, ipGas + 1 ); - } - else if( ipGas < 0 ) - { - saturationValues[n][ipWater] = table( n, ipWater + 1 ); - saturationValues[n][ipOil] = table( n, ipOil + 1 ); - } - } - - } - - arrayView2d< real64 const, compflow::USD_PHASE > const saturation = saturationValues.toViewConst(); - - // perform relperm update using table (Swet,Snonwet) and save resulting total density, etc. - // note: column indexing should be kept consistent with output file header below. - - forAll< parallelDevicePolicy<> >( saturation.size( 0 ), - [numPhases, kernelWrapper, saturation, table, - offset] GEOS_HOST_DEVICE ( integer const n ) - { - kernelWrapper.update( 0, 0, saturation[n] ); + kernelWrapper.update( n, 0, saturation[0] ); for( integer p = 0; p < numPhases; ++p ) { - table( n, offset + 1 + p ) = kernelWrapper.relperm()( 0, 0, p ); + table( n, RELPERM + p ) = kernelWrapper.relperm()( n, 0, p ); } } ); - } - } - -#endif //GEOS_RELPERMDRIVERRUNTEST_HPP_ +#endif //GEOS_CONSTITUTIVEDRIVERS_RELATIVEPERMEABILITY_RELPERMDRIVERRUNTEST_HPP diff --git a/src/coreComponents/constitutiveDrivers/relativePermeability/RelpermDriverTableRelativeHysteresisRunTest.cpp b/src/coreComponents/constitutiveDrivers/relativePermeability/RelpermDriverTableRelativeHysteresisRunTest.cpp index 4ed6420b673..d50bd1bdf19 100644 --- a/src/coreComponents/constitutiveDrivers/relativePermeability/RelpermDriverTableRelativeHysteresisRunTest.cpp +++ b/src/coreComponents/constitutiveDrivers/relativePermeability/RelpermDriverTableRelativeHysteresisRunTest.cpp @@ -16,8 +16,12 @@ #include "RelpermDriverRunTest.hpp" #include "constitutive/relativePermeability/TableRelativePermeabilityHysteresis.hpp" - namespace geos { + +template<> +struct HasHysteresis< constitutive::TableRelativePermeabilityHysteresis > : std::true_type +{}; + template void RelpermDriver::runTest< geos::constitutive::TableRelativePermeabilityHysteresis >( geos::constitutive::TableRelativePermeabilityHysteresis &, arrayView2d< real64 > const & ); } diff --git a/src/coreComponents/schema/schema.xsd b/src/coreComponents/schema/schema.xsd index 9a9bb51d843..3449d34d4b7 100644 --- a/src/coreComponents/schema/schema.xsd +++ b/src/coreComponents/schema/schema.xsd @@ -6484,18 +6484,20 @@ Information output from lower logLevels is added with the desired log level - - + + - - + + + + - + diff --git a/src/coreComponents/schema/schema.xsd.other b/src/coreComponents/schema/schema.xsd.other index c7434b7b99c..15258cad63f 100644 --- a/src/coreComponents/schema/schema.xsd.other +++ b/src/coreComponents/schema/schema.xsd.other @@ -1554,7 +1554,10 @@ A field can represent a physical variable. (pressure, temperature, global compos - + + + + From e90b28a622f677c40aac9703b28a9095c14fb621 Mon Sep 17 00:00:00 2001 From: dkachuma Date: Fri, 22 May 2026 15:35:29 -0500 Subject: [PATCH 02/25] Add 3-phase test --- .../constitutiveDriver/constitutiveDriver.ats | 15 +++- .../constitutiveDriver/relperm/relperm.ats | 20 ----- .../testRelperm_Table_2_phase.xml | 78 ++++++++++++++++++ .../testRelperm_Table_3_phase.xml | 24 +++++- .../relperm_tables.xml} | 82 +++---------------- .../ConstitutiveDriver.cpp | 7 +- .../relativePermeability/RelpermDriver.cpp | 61 ++++++++++---- .../relativePermeability/RelpermDriver.hpp | 2 +- 8 files changed, 171 insertions(+), 118 deletions(-) delete mode 100644 inputFiles/constitutiveDriver/relperm/relperm.ats create mode 100644 inputFiles/constitutiveDriver/testRelperm_Table_2_phase.xml rename inputFiles/constitutiveDriver/{relperm => }/testRelperm_Table_3_phase.xml (50%) rename inputFiles/constitutiveDriver/{relperm/testRelperm_Table_2_phase.xml => testRelperm_data/relperm_tables.xml} (54%) diff --git a/inputFiles/constitutiveDriver/constitutiveDriver.ats b/inputFiles/constitutiveDriver/constitutiveDriver.ats index 4ba8f0cc79b..0730beab901 100644 --- a/inputFiles/constitutiveDriver/constitutiveDriver.ats +++ b/inputFiles/constitutiveDriver/constitutiveDriver.ats @@ -12,13 +12,26 @@ def create_pvt_test(name, description=None): check_step=0, restartcheck_params=RestartcheckParameters(**restartcheck_params)) +def create_relperm_test(name, description=None): + if description is None: description = name + return TestDeck( + name=name, + description=description, + partitions=[(1, 1, 1)], + restart_step=0, + check_step=0, + restartcheck_params=RestartcheckParameters(**restartcheck_params)) + decks = [ create_pvt_test("testPVT"), create_pvt_test("testPVT_CO2Brine", "PVT Driver test for CO2 fluid models"), create_pvt_test("testPVT_CO2BrineTables", "PVT Driver test for CO2 fluid models with solubility tables"), create_pvt_test("testPVT_PhaseComposition", "PVT Driver test with composition output"), create_pvt_test("testPVT_ThreePhaseCompositional", "PVT Driver test for three phase compositional models"), - create_pvt_test("testPVT_docExample", "PVT Driver documentation example") + create_pvt_test("testPVT_docExample", "PVT Driver documentation example"), + + create_relperm_test("testRelperm_Table_2_phase", "Relperm driver for 2 phase tables"), + create_relperm_test("testRelperm_Table_3_phase", "Relperm driver for 3 phase tables") ] generate_geos_tests(decks) diff --git a/inputFiles/constitutiveDriver/relperm/relperm.ats b/inputFiles/constitutiveDriver/relperm/relperm.ats deleted file mode 100644 index 30c06d25a23..00000000000 --- a/inputFiles/constitutiveDriver/relperm/relperm.ats +++ /dev/null @@ -1,20 +0,0 @@ -from geos.ats.test_builder import TestDeck, RestartcheckParameters, generate_geos_tests - -restartcheck_params = {'atol': 1e-08, 'rtol': 4e-07} - -def create_relperm_test(name, description=None): - if description is None: description = name - return TestDeck( - name=name, - description=description, - partitions=[(1, 1, 1)], - restart_step=0, - check_step=0, - restartcheck_params=RestartcheckParameters(**restartcheck_params)) - -decks = [ - create_relperm_test("testRelperm_Table_2_phase", "Relperm driver for 2 phase tables"), - create_relperm_test("testRelperm_Table_3_phase", "Relperm driver for 3 phase tables") -] - -generate_geos_tests(decks) diff --git a/inputFiles/constitutiveDriver/testRelperm_Table_2_phase.xml b/inputFiles/constitutiveDriver/testRelperm_Table_2_phase.xml new file mode 100644 index 00000000000..7a6c5721825 --- /dev/null +++ b/inputFiles/constitutiveDriver/testRelperm_Table_2_phase.xml @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inputFiles/constitutiveDriver/relperm/testRelperm_Table_3_phase.xml b/inputFiles/constitutiveDriver/testRelperm_Table_3_phase.xml similarity index 50% rename from inputFiles/constitutiveDriver/relperm/testRelperm_Table_3_phase.xml rename to inputFiles/constitutiveDriver/testRelperm_Table_3_phase.xml index 715e5e58421..b9169420275 100644 --- a/inputFiles/constitutiveDriver/relperm/testRelperm_Table_3_phase.xml +++ b/inputFiles/constitutiveDriver/testRelperm_Table_3_phase.xml @@ -2,10 +2,16 @@ + @@ -16,6 +22,9 @@ + @@ -28,14 +37,21 @@ + - - + + + diff --git a/inputFiles/constitutiveDriver/relperm/testRelperm_Table_2_phase.xml b/inputFiles/constitutiveDriver/testRelperm_data/relperm_tables.xml similarity index 54% rename from inputFiles/constitutiveDriver/relperm/testRelperm_Table_2_phase.xml rename to inputFiles/constitutiveDriver/testRelperm_data/relperm_tables.xml index 94574aafe28..fa306221455 100644 --- a/inputFiles/constitutiveDriver/relperm/testRelperm_Table_2_phase.xml +++ b/inputFiles/constitutiveDriver/testRelperm_data/relperm_tables.xml @@ -1,77 +1,5 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - + + tableRow( numColumns ); - for( integer step = 0; step <= m_numSteps; ++step ) + for( integer step = 0; step < numRows; ++step ) { for( integer col = 0; col < numColumns; ++col ) { diff --git a/src/coreComponents/constitutiveDrivers/relativePermeability/RelpermDriver.cpp b/src/coreComponents/constitutiveDrivers/relativePermeability/RelpermDriver.cpp index 4291eebf48e..3f0a1237534 100644 --- a/src/coreComponents/constitutiveDrivers/relativePermeability/RelpermDriver.cpp +++ b/src/coreComponents/constitutiveDrivers/relativePermeability/RelpermDriver.cpp @@ -60,12 +60,6 @@ void RelpermDriver::postInputInitialization() "Number of historical saturations must be the same as the number of phases", getWrapperDataContext( viewKeyStruct::historicalSaturationsString() ) ); } - - string_array columnNames; - getColumnNames( columnNames ); - integer const numCols = static_cast< integer >(columnNames.size()); - - allocateTable( numCols, numPhases ); } bool RelpermDriver::execute() @@ -135,10 +129,8 @@ void RelpermDriver::getColumnNames( string_array & columnNames ) const } } -void RelpermDriver::allocateTable( integer numColumns, integer numPhases ) +void RelpermDriver::allocateTable( integer numRows, integer numColumns ) { - // For 3-phase we have m_numSteps+1 points for each of the other two phases - integer const numRows = (numPhases == 3) ? (m_numSteps+1)*(m_numSteps+1) : (m_numSteps+1); m_table.resize( numRows, numColumns ); for( integer index = 0; index < numRows; ++index ) { @@ -166,22 +158,55 @@ void RelpermDriver::initializeTable() // Offset for saturations in table constexpr integer SATURATION = 1; + // For 3-phase we don't know apriori how many rows it will be because we don't want negative saturations + string_array columnNames; + getColumnNames( columnNames ); + integer const numCols = static_cast< integer >( columnNames.size() ); + integer const numRows = [&]( bool is3Phase ) -> integer + { + if( is3Phase ) + { + integer rowCount = 0; + for( integer ni = 0; ni < m_numSteps + 1; ++ni ) + { + real64 const swat = min_wetting_saturation + ni*dSw; + for( integer nj = 0; nj < m_numSteps + 1; ++nj ) + { + real64 const sgas = min_non_wetting_saturation + nj*dSw; + if( swat + sgas <= 1.0 ) + { + ++rowCount; + } + } + } + return rowCount; + } + else + { + return m_numSteps + 1; + } + }( numPhases == 3 ); + + // Resize the table + allocateTable( numRows, numCols ); + // 3-phase branch if( numPhases == 3 ) { - real64 swat = 0.0; - real64 sgas = 0.0; + integer index = 0; for( integer ni = 0; ni < m_numSteps + 1; ++ni ) { - swat = min_wetting_saturation + ni*dSw; + real64 const swat = min_wetting_saturation + ni*dSw; for( integer nj = 0; nj < m_numSteps + 1; ++nj ) { - sgas = min_non_wetting_saturation + nj*dSw; - - integer index = ni * ( m_numSteps + 1 ) + nj; - m_table( index, ipWater + SATURATION ) = swat; - m_table( index, ipGas + SATURATION ) = sgas; - m_table( index, ipOil + SATURATION ) = 1.0 - swat - sgas; + real64 const sgas = min_non_wetting_saturation + nj*dSw; + if( swat + sgas <= 1.0 ) + { + m_table( index, ipWater + SATURATION ) = swat; + m_table( index, ipGas + SATURATION ) = sgas; + m_table( index, ipOil + SATURATION ) = LvArray::math::max( 1.0 - swat - sgas, 0.0 ); // aesthetic + ++index; + } } } } diff --git a/src/coreComponents/constitutiveDrivers/relativePermeability/RelpermDriver.hpp b/src/coreComponents/constitutiveDrivers/relativePermeability/RelpermDriver.hpp index 89e4f8b5cce..c20afcf50af 100644 --- a/src/coreComponents/constitutiveDrivers/relativePermeability/RelpermDriver.hpp +++ b/src/coreComponents/constitutiveDrivers/relativePermeability/RelpermDriver.hpp @@ -60,7 +60,7 @@ class RelpermDriver : public ConstitutiveDriver constitutive::RelativePermeabilityBase & getRelperm(); constitutive::RelativePermeabilityBase const & getRelperm() const; - void allocateTable( integer numColumns, integer numPhases ); + void allocateTable( integer numRows, integer numColumns ); /** * @brief Initialises the table by filling in primary variables From a640350b2d9db8d320dc18cc5e17e265c8dcf9b7 Mon Sep 17 00:00:00 2001 From: dkachuma Date: Fri, 22 May 2026 15:44:11 -0500 Subject: [PATCH 03/25] Move test files --- .../testRelperm_BCBaker.xml} | 0 .../testRelperm_BCStoneII.xml} | 0 .../testRelperm_TableHyst2ph.xml} | 0 .../testRelperm_data}/drainagePhaseVolFraction_gas.txt | 0 .../testRelperm_data}/drainagePhaseVolFraction_water.txt | 0 .../testRelperm_data}/drainageRelPerm_gas.txt | 0 .../testRelperm_data}/drainageRelPerm_water.txt | 0 .../testRelperm_data}/imbibitionPhaseVolFraction_gas.txt | 0 .../testRelperm_data}/imbibitionPhaseVolFraction_water.txt | 0 .../testRelperm_data}/imbibitionRelPerm_gas.txt | 0 .../testRelperm_data}/imbibitionRelPerm_water.txt | 0 11 files changed, 0 insertions(+), 0 deletions(-) rename inputFiles/{relpermDriver/testRelpermDriverBCBaker.xml => constitutiveDriver/testRelperm_BCBaker.xml} (100%) rename inputFiles/{relpermDriver/testRelpermDriverBCStoneII.xml => constitutiveDriver/testRelperm_BCStoneII.xml} (100%) rename inputFiles/{relpermDriver/testRelpermDriverTableHyst2ph.xml => constitutiveDriver/testRelperm_TableHyst2ph.xml} (100%) rename inputFiles/{relpermDriver => constitutiveDriver/testRelperm_data}/drainagePhaseVolFraction_gas.txt (100%) rename inputFiles/{relpermDriver => constitutiveDriver/testRelperm_data}/drainagePhaseVolFraction_water.txt (100%) rename inputFiles/{relpermDriver => constitutiveDriver/testRelperm_data}/drainageRelPerm_gas.txt (100%) rename inputFiles/{relpermDriver => constitutiveDriver/testRelperm_data}/drainageRelPerm_water.txt (100%) rename inputFiles/{relpermDriver => constitutiveDriver/testRelperm_data}/imbibitionPhaseVolFraction_gas.txt (100%) rename inputFiles/{relpermDriver => constitutiveDriver/testRelperm_data}/imbibitionPhaseVolFraction_water.txt (100%) rename inputFiles/{relpermDriver => constitutiveDriver/testRelperm_data}/imbibitionRelPerm_gas.txt (100%) rename inputFiles/{relpermDriver => constitutiveDriver/testRelperm_data}/imbibitionRelPerm_water.txt (100%) diff --git a/inputFiles/relpermDriver/testRelpermDriverBCBaker.xml b/inputFiles/constitutiveDriver/testRelperm_BCBaker.xml similarity index 100% rename from inputFiles/relpermDriver/testRelpermDriverBCBaker.xml rename to inputFiles/constitutiveDriver/testRelperm_BCBaker.xml diff --git a/inputFiles/relpermDriver/testRelpermDriverBCStoneII.xml b/inputFiles/constitutiveDriver/testRelperm_BCStoneII.xml similarity index 100% rename from inputFiles/relpermDriver/testRelpermDriverBCStoneII.xml rename to inputFiles/constitutiveDriver/testRelperm_BCStoneII.xml diff --git a/inputFiles/relpermDriver/testRelpermDriverTableHyst2ph.xml b/inputFiles/constitutiveDriver/testRelperm_TableHyst2ph.xml similarity index 100% rename from inputFiles/relpermDriver/testRelpermDriverTableHyst2ph.xml rename to inputFiles/constitutiveDriver/testRelperm_TableHyst2ph.xml diff --git a/inputFiles/relpermDriver/drainagePhaseVolFraction_gas.txt b/inputFiles/constitutiveDriver/testRelperm_data/drainagePhaseVolFraction_gas.txt similarity index 100% rename from inputFiles/relpermDriver/drainagePhaseVolFraction_gas.txt rename to inputFiles/constitutiveDriver/testRelperm_data/drainagePhaseVolFraction_gas.txt diff --git a/inputFiles/relpermDriver/drainagePhaseVolFraction_water.txt b/inputFiles/constitutiveDriver/testRelperm_data/drainagePhaseVolFraction_water.txt similarity index 100% rename from inputFiles/relpermDriver/drainagePhaseVolFraction_water.txt rename to inputFiles/constitutiveDriver/testRelperm_data/drainagePhaseVolFraction_water.txt diff --git a/inputFiles/relpermDriver/drainageRelPerm_gas.txt b/inputFiles/constitutiveDriver/testRelperm_data/drainageRelPerm_gas.txt similarity index 100% rename from inputFiles/relpermDriver/drainageRelPerm_gas.txt rename to inputFiles/constitutiveDriver/testRelperm_data/drainageRelPerm_gas.txt diff --git a/inputFiles/relpermDriver/drainageRelPerm_water.txt b/inputFiles/constitutiveDriver/testRelperm_data/drainageRelPerm_water.txt similarity index 100% rename from inputFiles/relpermDriver/drainageRelPerm_water.txt rename to inputFiles/constitutiveDriver/testRelperm_data/drainageRelPerm_water.txt diff --git a/inputFiles/relpermDriver/imbibitionPhaseVolFraction_gas.txt b/inputFiles/constitutiveDriver/testRelperm_data/imbibitionPhaseVolFraction_gas.txt similarity index 100% rename from inputFiles/relpermDriver/imbibitionPhaseVolFraction_gas.txt rename to inputFiles/constitutiveDriver/testRelperm_data/imbibitionPhaseVolFraction_gas.txt diff --git a/inputFiles/relpermDriver/imbibitionPhaseVolFraction_water.txt b/inputFiles/constitutiveDriver/testRelperm_data/imbibitionPhaseVolFraction_water.txt similarity index 100% rename from inputFiles/relpermDriver/imbibitionPhaseVolFraction_water.txt rename to inputFiles/constitutiveDriver/testRelperm_data/imbibitionPhaseVolFraction_water.txt diff --git a/inputFiles/relpermDriver/imbibitionRelPerm_gas.txt b/inputFiles/constitutiveDriver/testRelperm_data/imbibitionRelPerm_gas.txt similarity index 100% rename from inputFiles/relpermDriver/imbibitionRelPerm_gas.txt rename to inputFiles/constitutiveDriver/testRelperm_data/imbibitionRelPerm_gas.txt diff --git a/inputFiles/relpermDriver/imbibitionRelPerm_water.txt b/inputFiles/constitutiveDriver/testRelperm_data/imbibitionRelPerm_water.txt similarity index 100% rename from inputFiles/relpermDriver/imbibitionRelPerm_water.txt rename to inputFiles/constitutiveDriver/testRelperm_data/imbibitionRelPerm_water.txt From e9bcd7bd860b0b56a2af6ae1993e92329bca4a80 Mon Sep 17 00:00:00 2001 From: dkachuma Date: Fri, 22 May 2026 15:48:13 -0500 Subject: [PATCH 04/25] Add 2-phase hysteresis test --- .../constitutiveDriver/constitutiveDriver.ats | 1 + .../testRelperm_TableHyst2ph.xml | 59 +++++++------------ 2 files changed, 23 insertions(+), 37 deletions(-) diff --git a/inputFiles/constitutiveDriver/constitutiveDriver.ats b/inputFiles/constitutiveDriver/constitutiveDriver.ats index 0730beab901..3af6c0e2d77 100644 --- a/inputFiles/constitutiveDriver/constitutiveDriver.ats +++ b/inputFiles/constitutiveDriver/constitutiveDriver.ats @@ -32,6 +32,7 @@ decks = [ create_relperm_test("testRelperm_Table_2_phase", "Relperm driver for 2 phase tables"), create_relperm_test("testRelperm_Table_3_phase", "Relperm driver for 3 phase tables") + create_relperm_test("testRelperm_TableHyst2ph", "Two-phase hysteresis with large tables") ] generate_geos_tests(decks) diff --git a/inputFiles/constitutiveDriver/testRelperm_TableHyst2ph.xml b/inputFiles/constitutiveDriver/testRelperm_TableHyst2ph.xml index 60614391abf..7dd7234aa38 100644 --- a/inputFiles/constitutiveDriver/testRelperm_TableHyst2ph.xml +++ b/inputFiles/constitutiveDriver/testRelperm_TableHyst2ph.xml @@ -3,20 +3,31 @@ + + + + - + + + + - - - - - - - - - - - - - + coordinateFiles="{ testRelperm_data/drainagePhaseVolFraction_water.txt }" + voxelFile="testRelperm_data/drainageRelPerm_water.txt"/> + coordinateFiles="{ testRelperm_data/drainagePhaseVolFraction_gas.txt }" + voxelFile="testRelperm_data/drainageRelPerm_gas.txt"/> + coordinateFiles="{ testRelperm_data/imbibitionPhaseVolFraction_water.txt }" + voxelFile="testRelperm_data/imbibitionRelPerm_water.txt"/> + coordinateFiles="{ testRelperm_data/imbibitionPhaseVolFraction_gas.txt }" + voxelFile="testRelperm_data/imbibitionRelPerm_gas.txt"/> - - From 9eaf1acb4c909cda399eda1b3dbb506d8859fd71 Mon Sep 17 00:00:00 2001 From: dkachuma Date: Fri, 22 May 2026 15:54:08 -0500 Subject: [PATCH 05/25] Add Brooks-Corey tests --- .../constitutiveDriver/constitutiveDriver.ats | 2 + .../testRelperm_BCBaker.xml | 43 ++++++--------- .../testRelperm_BCStoneII.xml | 54 ++++++++----------- .../testRelperm_TableHyst2ph.xml | 2 +- 4 files changed, 40 insertions(+), 61 deletions(-) diff --git a/inputFiles/constitutiveDriver/constitutiveDriver.ats b/inputFiles/constitutiveDriver/constitutiveDriver.ats index 3af6c0e2d77..361ceaea3d3 100644 --- a/inputFiles/constitutiveDriver/constitutiveDriver.ats +++ b/inputFiles/constitutiveDriver/constitutiveDriver.ats @@ -33,6 +33,8 @@ decks = [ create_relperm_test("testRelperm_Table_2_phase", "Relperm driver for 2 phase tables"), create_relperm_test("testRelperm_Table_3_phase", "Relperm driver for 3 phase tables") create_relperm_test("testRelperm_TableHyst2ph", "Two-phase hysteresis with large tables") + create_relperm_test("testRelperm_BCBaker", "Three-phase Brooks-Corey with Baker mixing rule") + create_relperm_test("testRelperm_BCStoneII", "Three-phase Brooks-Corey with Stone II mixing rule") ] generate_geos_tests(decks) diff --git a/inputFiles/constitutiveDriver/testRelperm_BCBaker.xml b/inputFiles/constitutiveDriver/testRelperm_BCBaker.xml index 0a7c0f3018e..d7045ed3ac0 100644 --- a/inputFiles/constitutiveDriver/testRelperm_BCBaker.xml +++ b/inputFiles/constitutiveDriver/testRelperm_BCBaker.xml @@ -3,21 +3,32 @@ + + + + - + + + - - - - - - - - - - diff --git a/inputFiles/constitutiveDriver/testRelperm_BCStoneII.xml b/inputFiles/constitutiveDriver/testRelperm_BCStoneII.xml index 80c5ab16abb..c33c266ccb3 100644 --- a/inputFiles/constitutiveDriver/testRelperm_BCStoneII.xml +++ b/inputFiles/constitutiveDriver/testRelperm_BCStoneII.xml @@ -3,52 +3,40 @@ + + + + - + + + - + name="relperm" + phaseNames="{ oil, gas, water }" + phaseMinVolumeFraction="{ 0.1, 0.05, 0.2}" + waterOilRelPermExponent="{ 2.0, 2.0}" + gasOilRelPermExponent="{ 2.0, 2.0 }" + waterOilRelPermMaxValue="{ 1.0, 1.0 }" + gasOilRelPermMaxValue="{ 0.8, 0.4 }"/> - - - - - - - - - - diff --git a/inputFiles/constitutiveDriver/testRelperm_TableHyst2ph.xml b/inputFiles/constitutiveDriver/testRelperm_TableHyst2ph.xml index 7dd7234aa38..e3744af64fa 100644 --- a/inputFiles/constitutiveDriver/testRelperm_TableHyst2ph.xml +++ b/inputFiles/constitutiveDriver/testRelperm_TableHyst2ph.xml @@ -6,7 +6,7 @@ name="testRelperm" relperm="relperm" steps="100" - output="out_alt.txt" + output="testRelperm_TableHyst2ph.txt" logLevel="1" /> From 1f6d5316ea8393065b1e5dce37301f8154c3bc44 Mon Sep 17 00:00:00 2001 From: dkachuma Date: Fri, 22 May 2026 16:04:05 -0500 Subject: [PATCH 06/25] Add doc example --- .../constitutiveDriver/constitutiveDriver.ats | 7 ++- .../testRelperm_docExample.xml | 59 +++++++++++++++++++ 2 files changed, 63 insertions(+), 3 deletions(-) create mode 100644 inputFiles/constitutiveDriver/testRelperm_docExample.xml diff --git a/inputFiles/constitutiveDriver/constitutiveDriver.ats b/inputFiles/constitutiveDriver/constitutiveDriver.ats index 361ceaea3d3..63043f16359 100644 --- a/inputFiles/constitutiveDriver/constitutiveDriver.ats +++ b/inputFiles/constitutiveDriver/constitutiveDriver.ats @@ -31,10 +31,11 @@ decks = [ create_pvt_test("testPVT_docExample", "PVT Driver documentation example"), create_relperm_test("testRelperm_Table_2_phase", "Relperm driver for 2 phase tables"), - create_relperm_test("testRelperm_Table_3_phase", "Relperm driver for 3 phase tables") - create_relperm_test("testRelperm_TableHyst2ph", "Two-phase hysteresis with large tables") - create_relperm_test("testRelperm_BCBaker", "Three-phase Brooks-Corey with Baker mixing rule") + create_relperm_test("testRelperm_Table_3_phase", "Relperm driver for 3 phase tables"), + create_relperm_test("testRelperm_TableHyst2ph", "Two-phase hysteresis with large tables"), + create_relperm_test("testRelperm_BCBaker", "Three-phase Brooks-Corey with Baker mixing rule"), create_relperm_test("testRelperm_BCStoneII", "Three-phase Brooks-Corey with Stone II mixing rule") + create_relperm_test("testRelperm_docExample", "Relperm Driver documentation example") ] generate_geos_tests(decks) diff --git a/inputFiles/constitutiveDriver/testRelperm_docExample.xml b/inputFiles/constitutiveDriver/testRelperm_docExample.xml new file mode 100644 index 00000000000..62f372b9345 --- /dev/null +++ b/inputFiles/constitutiveDriver/testRelperm_docExample.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 335a3826db8cb1086c8933ed62807053efe83b9b Mon Sep 17 00:00:00 2001 From: dkachuma Date: Fri, 22 May 2026 16:26:59 -0500 Subject: [PATCH 07/25] add documentation --- .../testRelperm_docExample.xml | 1 - .../docs/ConstitutiveDrivers.rst | 2 + .../docs/RelpermDriver.rst | 139 ++++++++++++++++++ 3 files changed, 141 insertions(+), 1 deletion(-) create mode 100644 src/coreComponents/constitutiveDrivers/docs/RelpermDriver.rst diff --git a/inputFiles/constitutiveDriver/testRelperm_docExample.xml b/inputFiles/constitutiveDriver/testRelperm_docExample.xml index 62f372b9345..b3d9d37c4d8 100644 --- a/inputFiles/constitutiveDriver/testRelperm_docExample.xml +++ b/inputFiles/constitutiveDriver/testRelperm_docExample.xml @@ -7,7 +7,6 @@ name="test_sandstone" relperm="sandstone" steps="20" - output="sandstone_model.txt" logLevel="1" /> diff --git a/src/coreComponents/constitutiveDrivers/docs/ConstitutiveDrivers.rst b/src/coreComponents/constitutiveDrivers/docs/ConstitutiveDrivers.rst index 8ad6a17ca83..2520aa8c568 100644 --- a/src/coreComponents/constitutiveDrivers/docs/ConstitutiveDrivers.rst +++ b/src/coreComponents/constitutiveDrivers/docs/ConstitutiveDrivers.rst @@ -11,3 +11,5 @@ These drivers are designed to facilitate the exploration of various constitutive PVTDriver TriaxialDriver + + RelpermDriver diff --git a/src/coreComponents/constitutiveDrivers/docs/RelpermDriver.rst b/src/coreComponents/constitutiveDrivers/docs/RelpermDriver.rst new file mode 100644 index 00000000000..10fb4b0eb93 --- /dev/null +++ b/src/coreComponents/constitutiveDrivers/docs/RelpermDriver.rst @@ -0,0 +1,139 @@ +Relperm Driver +=============== + +.. contents:: Table of Contents + :depth: 3 + +Introduction +------------ + +Fitting relative permeability parameters to experimental or benchmark data often should not require a full flow simulation, when the only goal is to check that the permeability curves respond correctly to changes in phase saturation. +As such, GEOS provides a ``RelpermDriver`` allowing the user to test relative permeability models for a well-defined sweep of saturation conditions. +The driver itself is launched like any other GEOS simulation, but with a particular XML structure: + +.. code-block:: sh + + ./bin/geosx -i myRelpermTest.xml + +This driver will work for any 2-phase or 3-phase relative permeability model enabled within GEOS. + +XML Structure +------------- +A typical XML file to run the driver will have several key elements. +Here, we will walk through an example file included in the source tree at + +.. code-block:: sh + + inputFiles/constitutiveDriver/testRelperm_docExample.xml + +A key point is that the XML file follows the same structure as a typical GEOS input deck. As with the other constitutive drivers, once the constitutive block has been calibrated, solver and discretization sections can be added directly to transform it into a full field simulation setup. This continuity allows for smooth transitions between calibration and simulation workflows. + +The first step is to define a parameterized relative permeability model to test. +Here, we create a particular type of table-based two-phase relative permeability for sandstone: + +.. literalinclude:: ../../../../inputFiles/constitutiveDriver/testRelperm_docExample.xml + :language: xml + :start-after: + :end-before: + +We also define the underlying table functions used by the model. + +.. literalinclude:: ../../../../inputFiles/constitutiveDriver/testRelperm_docExample.xml + :language: xml + :start-after: + :end-before: + +A ``RelpermDriver`` is then added as a ``Task``, a particular type of executable event often used for simple actions. + +.. literalinclude:: ../../../../inputFiles/constitutiveDriver/testRelperm_docExample.xml + :language: xml + :start-after: + :end-before: + +The driver itself takes as input the relative permeability model (referenced by the ``relperm`` parameter) and the ``steps`` parameter, which controls how many saturation increments are evaluated. +Results will be written in a simple ASCII table format (described below) to a specified file if an ``output`` parameter is provided. If it is not specified, output will be written to the standard log (screen). +The ``logLevel`` parameter controls the verbosity of log output during execution. + +The driver task is added as a ``SoloEvent`` to the event queue. +This leads to a trivial event queue, since all we do is launch the driver and then quit. + +.. literalinclude:: ../../../../inputFiles/constitutiveDriver/testRelperm_docExample.xml + :language: xml + :start-after: + :end-before: + +Parameters +---------- +The key XML parameters for the RelpermDriver are summarized in the following table: + +.. include:: /docs/sphinx/datastructure/RelpermDriver.rst + +Phase Count and Step Logic +^^^^^^^^^^^^^^^^^^^^^^^^^^ +The internal behavior of the driver depends on the number of phases present in the relative permeability model: + +* **2-Phase Models:** The driver iterates exactly ``steps`` + 1 times, spanning from the minimum non-wetting saturation to its maximum valid bounds. +* **3-Phase Models:** The driver iterates through combinations of the wetting and non-wetting phase saturations based on the specified number of ``steps``. Only combinations where the sum of saturations is valid (i.e., less than or equal to 1.0) are evaluated, with the third intermediate phase making up the balance. Thus, the total number of output rows for a 3-phase model depends heavily on the saturation end-points and will be generally greater than the provided ``steps`` parameter. + +Hysteresis Support +^^^^^^^^^^^^^^^^^^ +For relative permeability models featuring hysteresis (e.g., ``TableRelativePermeabilityHysteresis``), the driver requires historical phase saturations to determine the departure paths of the curves. This can be directly specified via the ``historicalSaturations`` array in the XML. If omitted, the driver will default to extracting the extremum phase volume fractions from the underlying curve definitions. + +Output Format +------------- +The ``output`` key is used to identify a file to which the results of the simulation are written. +If this key is omitted, file output will be suppressed and instead the resulting table will be output to the screen. + +When written to standard output, the data is written in a table format similar to the one below. + +.. code:: sh + + --------------------------------------------------------------------------------- + | Output for test_sandstone | + |-------------------------------------------------------------------------------| + | index | saturation | relperm | + |---------------|-------------------------------|-------------------------------| + | | gas | water | gas | water | + |---------------|---------------|---------------|---------------|---------------| + | 0.0000e+00 | 0.0000e+00 | 1.0000e+00 | 0.0000e+00 | 1.0000e+00 | + | 1.0000e+00 | 3.9525e-02 | 9.6047e-01 | 2.3250e-03 | 8.2337e-01 | + | 2.0000e+00 | 7.9050e-02 | 9.2095e-01 | 7.0157e-03 | 6.6543e-01 | + | 3.0000e+00 | 1.1857e-01 | 8.8143e-01 | 1.4124e-02 | 5.2659e-01 | + | 4.0000e+00 | 1.5810e-01 | 8.4190e-01 | 2.5831e-02 | 4.1943e-01 | + | 5.0000e+00 | 1.9762e-01 | 8.0238e-01 | 4.0028e-02 | 3.2628e-01 | + | 6.0000e+00 | 2.3715e-01 | 7.6285e-01 | 5.6826e-02 | 2.4769e-01 | + | 7.0000e+00 | 2.7668e-01 | 7.2332e-01 | 7.7434e-02 | 1.8665e-01 | + | 8.0000e+00 | 3.1620e-01 | 6.8380e-01 | 1.0046e-01 | 1.3539e-01 | + | 9.0000e+00 | 3.5572e-01 | 6.4428e-01 | 1.2703e-01 | 9.6594e-02 | + | 1.0000e+01 | 3.9525e-01 | 6.0475e-01 | 1.5710e-01 | 6.7650e-02 | + | 1.1000e+01 | 4.3477e-01 | 5.6523e-01 | 1.8949e-01 | 4.4341e-02 | + | 1.2000e+01 | 4.7430e-01 | 5.2570e-01 | 2.2543e-01 | 2.8331e-02 | + | 1.3000e+01 | 5.1382e-01 | 4.8618e-01 | 2.6486e-01 | 1.7448e-02 | + | 1.4000e+01 | 5.5335e-01 | 4.4665e-01 | 3.0663e-01 | 9.4157e-03 | + | 1.5000e+01 | 5.9287e-01 | 4.0713e-01 | 3.5200e-01 | 4.8321e-03 | + | 1.6000e+01 | 6.3240e-01 | 3.6760e-01 | 4.0105e-01 | 2.3675e-03 | + | 1.7000e+01 | 6.7192e-01 | 3.2808e-01 | 4.5150e-01 | 6.9571e-04 | + | 1.8000e+01 | 7.1145e-01 | 2.8855e-01 | 5.0696e-01 | 2.9714e-04 | + | 1.9000e+01 | 7.5097e-01 | 2.4903e-01 | 5.6478e-01 | 6.6429e-05 | + | 2.0000e+01 | 7.9050e-01 | 2.0950e-01 | 6.2490e-01 | 4.4409e-20 | + --------------------------------------------------------------------------------- + +When written to a file, the file is a simple ASCII format with a brief header followed by the actual saturation data: + +.. code:: sh + + # column 1 = index + # column 2 = saturation,gas + # column 3 = saturation,water + # column 4 = relperm,gas + # column 5 = relperm,water + 0.0000e+00 0.0000e+00 1.0000e+00 0.0000e+00 1.0000e+00 + 1.0000e+00 3.9525e-02 9.6047e-01 2.3250e-03 8.2337e-01 + 2.0000e+00 7.9050e-02 9.2095e-01 7.0157e-03 6.6543e-01 + 3.0000e+00 1.1857e-01 8.8143e-01 1.4124e-02 5.2659e-01 + 4.0000e+00 1.5810e-01 8.4190e-01 2.5831e-02 4.1943e-01 + 5.0000e+00 1.9762e-01 8.0238e-01 4.0028e-02 3.2628e-01 + ... + +The exact number of columns will depend on the phase count configured in the chosen model. In this 2-phase example, there are 5 total columns tracking the step index, saturations, and resulting relative permeabilities. If hysteresis is activated on the model, additional columns tracking historical saturations will be present between the saturations and the calculated relative permeabilities. +This file format can be easily parsed using standard tools or plotting scripts to examine the generated curves. From 9d2da80b5076c21fa9b753f04f866d11c1885536 Mon Sep 17 00:00:00 2001 From: dkachuma Date: Fri, 22 May 2026 16:38:55 -0500 Subject: [PATCH 08/25] Add van Genuchten-Mualem models --- .../constitutiveDriver/constitutiveDriver.ats | 3 +- .../testRelperm_VanGenuchten.xml | 59 +++++++++++++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 inputFiles/constitutiveDriver/testRelperm_VanGenuchten.xml diff --git a/inputFiles/constitutiveDriver/constitutiveDriver.ats b/inputFiles/constitutiveDriver/constitutiveDriver.ats index 63043f16359..6908fe23f57 100644 --- a/inputFiles/constitutiveDriver/constitutiveDriver.ats +++ b/inputFiles/constitutiveDriver/constitutiveDriver.ats @@ -34,7 +34,8 @@ decks = [ create_relperm_test("testRelperm_Table_3_phase", "Relperm driver for 3 phase tables"), create_relperm_test("testRelperm_TableHyst2ph", "Two-phase hysteresis with large tables"), create_relperm_test("testRelperm_BCBaker", "Three-phase Brooks-Corey with Baker mixing rule"), - create_relperm_test("testRelperm_BCStoneII", "Three-phase Brooks-Corey with Stone II mixing rule") + create_relperm_test("testRelperm_BCStoneII", "Three-phase Brooks-Corey with Stone II mixing rule"), + create_relperm_test("testRelperm_VanGenuchten", "Three-phase van Genuchten-Mualem models"), create_relperm_test("testRelperm_docExample", "Relperm Driver documentation example") ] diff --git a/inputFiles/constitutiveDriver/testRelperm_VanGenuchten.xml b/inputFiles/constitutiveDriver/testRelperm_VanGenuchten.xml new file mode 100644 index 00000000000..13e580c6d24 --- /dev/null +++ b/inputFiles/constitutiveDriver/testRelperm_VanGenuchten.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + From 07441d88fe180ef8a15f1552a08d7317d18549ec Mon Sep 17 00:00:00 2001 From: dkachuma Date: Fri, 22 May 2026 16:52:48 -0500 Subject: [PATCH 09/25] Simplify definition --- .../relativePermeability/RelpermDriverRunTest.hpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/coreComponents/constitutiveDrivers/relativePermeability/RelpermDriverRunTest.hpp b/src/coreComponents/constitutiveDrivers/relativePermeability/RelpermDriverRunTest.hpp index e430e8fab3f..af69cf37c23 100644 --- a/src/coreComponents/constitutiveDrivers/relativePermeability/RelpermDriverRunTest.hpp +++ b/src/coreComponents/constitutiveDrivers/relativePermeability/RelpermDriverRunTest.hpp @@ -27,8 +27,7 @@ namespace geos // Hysteresis traits template< typename RELPERM_TYPE > -struct HasHysteresis : std::false_type -{}; +struct HasHysteresis : std::false_type {}; template< typename RELPERM_TYPE > void From cd0acd747038ae761b3825be6f4d74332ad2f886 Mon Sep 17 00:00:00 2001 From: dkachuma Date: Fri, 22 May 2026 17:00:22 -0500 Subject: [PATCH 10/25] Use std::tie --- .../relativePermeability/RelpermDriverRunTest.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/coreComponents/constitutiveDrivers/relativePermeability/RelpermDriverRunTest.hpp b/src/coreComponents/constitutiveDrivers/relativePermeability/RelpermDriverRunTest.hpp index af69cf37c23..ab53d2d227f 100644 --- a/src/coreComponents/constitutiveDrivers/relativePermeability/RelpermDriverRunTest.hpp +++ b/src/coreComponents/constitutiveDrivers/relativePermeability/RelpermDriverRunTest.hpp @@ -60,7 +60,8 @@ RelpermDriver::runTest( RELPERM_TYPE & relperm, const arrayView2d< real64 > & ta using CurveType = constitutive::KilloughHysteresis::HysteresisCurve; using KeyStruct = typename RELPERM_TYPE::viewKeyStruct; - auto [ipWetting, ipNonWetting] = relperm.wettingAndNonWettingPhaseIndices(); + integer ipWetting, ipNonWetting; + std::tie( ipWetting, ipNonWetting ) = relperm.wettingAndNonWettingPhaseIndices(); stackArray1d< real64, constitutive::RelativePermeabilityBase::MAX_NUM_PHASES > historicalSaturations( numPhases ); historicalSaturations.zero(); From 284c5284121c50b6ef57078fa888852abc5a970b Mon Sep 17 00:00:00 2001 From: dkachuma Date: Fri, 22 May 2026 17:46:22 -0500 Subject: [PATCH 11/25] Fix rst heading level --- src/coreComponents/constitutiveDrivers/docs/RelpermDriver.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/coreComponents/constitutiveDrivers/docs/RelpermDriver.rst b/src/coreComponents/constitutiveDrivers/docs/RelpermDriver.rst index 10fb4b0eb93..283e453920a 100644 --- a/src/coreComponents/constitutiveDrivers/docs/RelpermDriver.rst +++ b/src/coreComponents/constitutiveDrivers/docs/RelpermDriver.rst @@ -69,14 +69,14 @@ The key XML parameters for the RelpermDriver are summarized in the following tab .. include:: /docs/sphinx/datastructure/RelpermDriver.rst Phase Count and Step Logic -^^^^^^^^^^^^^^^^^^^^^^^^^^ +-------------------------- The internal behavior of the driver depends on the number of phases present in the relative permeability model: * **2-Phase Models:** The driver iterates exactly ``steps`` + 1 times, spanning from the minimum non-wetting saturation to its maximum valid bounds. * **3-Phase Models:** The driver iterates through combinations of the wetting and non-wetting phase saturations based on the specified number of ``steps``. Only combinations where the sum of saturations is valid (i.e., less than or equal to 1.0) are evaluated, with the third intermediate phase making up the balance. Thus, the total number of output rows for a 3-phase model depends heavily on the saturation end-points and will be generally greater than the provided ``steps`` parameter. Hysteresis Support -^^^^^^^^^^^^^^^^^^ +------------------ For relative permeability models featuring hysteresis (e.g., ``TableRelativePermeabilityHysteresis``), the driver requires historical phase saturations to determine the departure paths of the curves. This can be directly specified via the ``historicalSaturations`` array in the XML. If omitted, the driver will default to extracting the extremum phase volume fractions from the underlying curve definitions. Output Format From 68a8e2f7b897e48a7e373ea65eef9cf89a9ed599 Mon Sep 17 00:00:00 2001 From: dkachuma Date: Fri, 22 May 2026 19:22:33 -0500 Subject: [PATCH 12/25] Rebaseline --- .integrated_tests.yaml | 2 +- BASELINE_NOTES.md | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.integrated_tests.yaml b/.integrated_tests.yaml index de37c7e6147..52900dd4e99 100644 --- a/.integrated_tests.yaml +++ b/.integrated_tests.yaml @@ -1,6 +1,6 @@ baselines: bucket: geosx - baseline: integratedTests/baseline_integratedTests-pr4057-16739-5dde641 + baseline: integratedTests/baseline_integratedTests-pr4040-16770-cd0acd7 allow_fail: all: '' diff --git a/BASELINE_NOTES.md b/BASELINE_NOTES.md index 5909bd5d0bd..50318c646fe 100644 --- a/BASELINE_NOTES.md +++ b/BASELINE_NOTES.md @@ -5,6 +5,9 @@ This file is designed to track changes to the integrated test baselines. Any developer who updates the baseline ID in the .integrated_tests.yaml file is expected to create an entry in this file with the pull request number, date, and their justification for rebaselining. These notes should be in reverse-chronological order, and use the following time format: (YYYY-MM-DD). +PR #4040 (2026-05-22) +Move relperm driver to use bew constitutive driver framework + PR #4057 (2026-05-21) Remove dependency on PVT package From 962fe5b1a5d888a93b28394123178bfba082b339 Mon Sep 17 00:00:00 2001 From: dkachuma Date: Sat, 23 May 2026 12:46:03 -0500 Subject: [PATCH 13/25] Add saturation functions --- .../ConstitutiveDriver.cpp | 7 +- .../relativePermeability/RelpermDriver.cpp | 178 ++++++++++-------- .../relativePermeability/RelpermDriver.hpp | 10 +- 3 files changed, 111 insertions(+), 84 deletions(-) diff --git a/src/coreComponents/constitutiveDrivers/ConstitutiveDriver.cpp b/src/coreComponents/constitutiveDrivers/ConstitutiveDriver.cpp index 5ecba0a32ea..e7d29b7582c 100644 --- a/src/coreComponents/constitutiveDrivers/ConstitutiveDriver.cpp +++ b/src/coreComponents/constitutiveDrivers/ConstitutiveDriver.cpp @@ -139,8 +139,7 @@ void ConstitutiveDriver::outputToFile() const file << std::scientific << std::setprecision( precision ); integer const width = precision+7; - integer const numRows = m_table.size( 0 ); - for( integer step = 0; step < numRows; ++step ) + for( integer step = 0; step <= m_numSteps; ++step ) { for( integer col = 0; col < numColumns; ++col ) { @@ -204,10 +203,8 @@ void ConstitutiveDriver::outputToConsole() const TableData tableData; - integer const numRows = m_table.size( 0 ); - stdVector< TableData::CellData > tableRow( numColumns ); - for( integer step = 0; step < numRows; ++step ) + for( integer step = 0; step <= m_numSteps; ++step ) { for( integer col = 0; col < numColumns; ++col ) { diff --git a/src/coreComponents/constitutiveDrivers/relativePermeability/RelpermDriver.cpp b/src/coreComponents/constitutiveDrivers/relativePermeability/RelpermDriver.cpp index 3f0a1237534..3cbbaebc4f6 100644 --- a/src/coreComponents/constitutiveDrivers/relativePermeability/RelpermDriver.cpp +++ b/src/coreComponents/constitutiveDrivers/relativePermeability/RelpermDriver.cpp @@ -19,6 +19,9 @@ #include "constitutiveDrivers/LogLevelsInfo.hpp" #include "constitutive/relativePermeability/RelativePermeabilityBase.hpp" #include "constitutive/relativePermeability/RelativePermeabilitySelector.hpp" +#include "functions/FunctionManager.hpp" +#include "functions/TableFunction.hpp" +#include "common/format/StringUtilities.hpp" namespace geos { @@ -35,6 +38,14 @@ RelpermDriver::RelpermDriver( const string & name, setInputFlag( InputFlags::REQUIRED ). setDescription( "Relative permeability model to test" ); + registerWrapper( viewKeyStruct::phaseNamesString(), &m_phaseNames ). + setInputFlag( InputFlags::REQUIRED ). + setDescription( "The names of the phases for which saturations are defined" ); + + registerWrapper( viewKeyStruct::saturationFunctionsString(), &m_saturationFunctionNames ). + setInputFlag( InputFlags::REQUIRED ). + setDescription( "Functions controlling saturation time history of the selected phases" ); + registerWrapper( viewKeyStruct::historicalSaturationsString(), &m_historicalSaturations ). setInputFlag( InputFlags::OPTIONAL ). setDescription( "Historical saturations for each phase." ); @@ -53,6 +64,42 @@ void RelpermDriver::postInputInitialization() "Number of phases for relative permeability model must be 2 or 3", getWrapperDataContext( viewKeyStruct::relpermNameString() ) ); + // Table functions should be provided for np-1 phases + integer const numSelectedPhases = m_phaseNames.size(); + GEOS_ERROR_IF( numSelectedPhases != numPhases - 1, + GEOS_FMT( "Number of selected phases should be {} not {}", numPhases - 1, numSelectedPhases ), + getWrapperDataContext( viewKeyStruct::phaseNamesString() ) ); + + integer const numFunctions = m_saturationFunctionNames.size(); + GEOS_ERROR_IF( numFunctions != numSelectedPhases, + "Number of saturations functions should match the number of selected phases", + getWrapperDataContext( viewKeyStruct::saturationFunctionsString() ) ); + + // Check that the phase names are valid + std::set< string > const relpermPhases( baseRelperm.phaseNames().begin(), baseRelperm.phaseNames().end()); + std::set< string > seenPhases; + for( auto const & phaseName : m_phaseNames ) + { + GEOS_ERROR_IF ( relpermPhases.find( phaseName ) == relpermPhases.end(), + GEOS_FMT( "Phase {} is not in the list of allowed phases for the relative permeability model", phaseName ), + getWrapperDataContext( viewKeyStruct::phaseNamesString() ) ); + + GEOS_ERROR_IF ( seenPhases.find( phaseName ) != seenPhases.end(), + GEOS_FMT( "Phase {} is repeated in the list of selected phases", phaseName ), + getWrapperDataContext( viewKeyStruct::phaseNamesString() ) ); + + seenPhases.insert( phaseName ); + } + + // Check that the functions exist + FunctionManager & functionManager = FunctionManager::getInstance(); + for( auto const & functionName : m_saturationFunctionNames ) + { + GEOS_ERROR_IF( !functionManager.hasGroup< TableFunction >( functionName ), + GEOS_FMT( "Saturation function with name '{}' not found", functionName ), + getWrapperDataContext( viewKeyStruct::saturationFunctionsString() ) ); + } + // Historical saturations must be the same number as the phases if( !m_historicalSaturations.empty()) { @@ -60,6 +107,28 @@ void RelpermDriver::postInputInitialization() "Number of historical saturations must be the same as the number of phases", getWrapperDataContext( viewKeyStruct::historicalSaturationsString() ) ); } + + string_array columnNames; + getColumnNames( columnNames ); + integer const numCols = static_cast< integer >(columnNames.size()); + + // Initialize functions and extract limits + real64 minTime = LvArray::NumericLimits< real64 >::max; + real64 maxTime = -LvArray::NumericLimits< real64 >::max; + for( auto const & functionName : m_saturationFunctionNames ) + { + TableFunction & function = functionManager.getGroup< TableFunction >( functionName ); + function.initializeFunction(); + ArrayOfArraysView< real64 > coordinates = function.getCoordinates(); + minTime = LvArray::math::min( minTime, coordinates[0][0] ); + maxTime = LvArray::math::max( maxTime, coordinates[0][coordinates.sizeOfArray( 0 )-1] ); + } + + // Allocate the data + allocateTable( numCols, minTime, maxTime ); + + // Populate the data + initializeTable( baseRelperm ); } bool RelpermDriver::execute() @@ -73,13 +142,13 @@ bool RelpermDriver::execute() GEOS_LOG_LEVEL_RANK_0( logInfo::LogOutput, " Type ................... " << baseRelperm.getCatalogName() ); GEOS_LOG_LEVEL_RANK_0( logInfo::LogOutput, " No. of Phases .......... " << numPhases ); GEOS_LOG_LEVEL_RANK_0( logInfo::LogOutput, " Steps .................. " << m_numSteps ); + GEOS_LOG_LEVEL_RANK_0( logInfo::LogOutput, " Selected phases ........ " << stringutilities::join( m_phaseNames, ',' ) ); + GEOS_LOG_LEVEL_RANK_0( logInfo::LogOutput, " Saturation functions ... " << stringutilities::join( m_saturationFunctionNames, ',' ) ); if( !m_outputFile.empty()) { GEOS_LOG_LEVEL_RANK_0( logInfo::LogOutput, " Output ................. " << m_outputFile ); } - initializeTable(); - // create a dummy discretization with one quadrature point for // storing constitutive data conduit::Node node; @@ -129,95 +198,52 @@ void RelpermDriver::getColumnNames( string_array & columnNames ) const } } -void RelpermDriver::allocateTable( integer numRows, integer numColumns ) -{ - m_table.resize( numRows, numColumns ); - for( integer index = 0; index < numRows; ++index ) - { - m_table( index, TIME ) = index; - } -} - -void RelpermDriver::initializeTable() +void RelpermDriver::initializeTable( RelativePermeabilityBase const & baseRelperm ) { - RelativePermeabilityBase & baseRelperm = getRelperm(); - - using PT = RelativePermeabilityBase::PhaseType; - integer const ipWater = baseRelperm.getPhaseOrder()[PT::WATER]; - integer const ipOil = baseRelperm.getPhaseOrder()[PT::OIL]; - integer const ipGas = baseRelperm.getPhaseOrder()[PT::GAS]; - + integer const numRows = m_table.size( 0 ); integer const numPhases = baseRelperm.numFluidPhases(); - auto const [ipWetting, ipNonWetting] = baseRelperm.wettingAndNonWettingPhaseIndices(); - real64 const min_wetting_saturation = baseRelperm.getPhaseMinVolumeFraction()[ipWetting]; - real64 const min_non_wetting_saturation = baseRelperm.getPhaseMinVolumeFraction()[ipNonWetting]; + string_array const & phaseNames = baseRelperm.phaseNames(); + array1d< integer > phaseOrder( numPhases ); + phaseOrder[0] = numPhases * (numPhases - 1) / 2; + for( integer ip = 0; ip < numPhases-1; ++ip ) + { + integer const index = static_cast< integer >(std::distance( phaseNames.begin(), std::find( phaseNames.begin(), phaseNames.end(), m_phaseNames[ip] ))); + phaseOrder[ip+1] = index; + phaseOrder[0] -= index; + } - real64 const dSw = ( 1.0 - min_wetting_saturation - min_non_wetting_saturation ) / m_numSteps; + FunctionManager const & functionManager = FunctionManager::getInstance(); + stdVector< TableFunction const * > tableFunctions; + for( auto const & functionName : m_saturationFunctionNames ) + { + TableFunction const * function = functionManager.getGroupPointer< TableFunction >( functionName ); + tableFunctions.emplace_back( function ); + } // Offset for saturations in table constexpr integer SATURATION = 1; - // For 3-phase we don't know apriori how many rows it will be because we don't want negative saturations - string_array columnNames; - getColumnNames( columnNames ); - integer const numCols = static_cast< integer >( columnNames.size() ); - integer const numRows = [&]( bool is3Phase ) -> integer + for( integer step = 0; step < numRows; ++step ) { - if( is3Phase ) - { - integer rowCount = 0; - for( integer ni = 0; ni < m_numSteps + 1; ++ni ) - { - real64 const swat = min_wetting_saturation + ni*dSw; - for( integer nj = 0; nj < m_numSteps + 1; ++nj ) - { - real64 const sgas = min_non_wetting_saturation + nj*dSw; - if( swat + sgas <= 1.0 ) - { - ++rowCount; - } - } - } - return rowCount; - } - else + real64 const time = m_table( step, TIME ); + real64 sumSaturation = 0.0; + for( integer ip = 1; ip < numPhases; ip++ ) { - return m_numSteps + 1; + real64 const saturation = LvArray::math::max( tableFunctions[ip-1]->evaluate( &time ), 0.0 ); + m_table( step, phaseOrder[ip] + SATURATION ) = saturation; + sumSaturation += saturation; } - }( numPhases == 3 ); - - // Resize the table - allocateTable( numRows, numCols ); - - // 3-phase branch - if( numPhases == 3 ) - { - integer index = 0; - for( integer ni = 0; ni < m_numSteps + 1; ++ni ) + if( 1.0 - sumSaturation < -LvArray::NumericLimits< real64 >::epsilon ) { - real64 const swat = min_wetting_saturation + ni*dSw; - for( integer nj = 0; nj < m_numSteps + 1; ++nj ) + real64 const scale = 1.0 / sumSaturation; + for( integer ip = 1; ip < numPhases; ip++ ) { - real64 const sgas = min_non_wetting_saturation + nj*dSw; - if( swat + sgas <= 1.0 ) - { - m_table( index, ipWater + SATURATION ) = swat; - m_table( index, ipGas + SATURATION ) = sgas; - m_table( index, ipOil + SATURATION ) = LvArray::math::max( 1.0 - swat - sgas, 0.0 ); // aesthetic - ++index; - } + m_table( step, phaseOrder[ip] + SATURATION ) *= scale; } + sumSaturation = 1.0; } - } - else // 2-phase branch - { - for( integer ni = 0; ni < m_numSteps + 1; ++ni ) - { - real64 const s_nw = min_non_wetting_saturation + ni * dSw; - m_table( ni, ipNonWetting + SATURATION ) = s_nw; - m_table( ni, ipWetting + SATURATION ) = 1.0 - s_nw; - } + m_table( step, phaseOrder[0] + SATURATION ) = 1.0 - sumSaturation; } } diff --git a/src/coreComponents/constitutiveDrivers/relativePermeability/RelpermDriver.hpp b/src/coreComponents/constitutiveDrivers/relativePermeability/RelpermDriver.hpp index c20afcf50af..3e8cf6b3568 100644 --- a/src/coreComponents/constitutiveDrivers/relativePermeability/RelpermDriver.hpp +++ b/src/coreComponents/constitutiveDrivers/relativePermeability/RelpermDriver.hpp @@ -41,6 +41,8 @@ class RelpermDriver : public ConstitutiveDriver void postInputInitialization() override; + using ConstitutiveDriver::execute; + bool execute() override; void getColumnNames( string_array & columnNames ) const override; @@ -60,12 +62,10 @@ class RelpermDriver : public ConstitutiveDriver constitutive::RelativePermeabilityBase & getRelperm(); constitutive::RelativePermeabilityBase const & getRelperm() const; - void allocateTable( integer numRows, integer numColumns ); - /** * @brief Initialises the table by filling in primary variables */ - void initializeTable(); + void initializeTable( constitutive::RelativePermeabilityBase const & baseRelperm ); /** * @struct viewKeyStruct holds char strings and viewKeys for fast lookup @@ -73,10 +73,14 @@ class RelpermDriver : public ConstitutiveDriver struct viewKeyStruct { constexpr static char const * relpermNameString() { return "relperm"; } + constexpr static char const * phaseNamesString() { return "phaseNames"; } + constexpr static char const * saturationFunctionsString() { return "saturationControls"; } constexpr static char const * historicalSaturationsString() { return "historicalSaturations"; } }; string m_relpermName; ///< relPermType identifier + string_array m_phaseNames; + string_array m_saturationFunctionNames; array1d< real64 > m_historicalSaturations; }; From 971c595068de44a9b56fde302974847e612cead0 Mon Sep 17 00:00:00 2001 From: dkachuma Date: Sat, 23 May 2026 13:18:59 -0500 Subject: [PATCH 14/25] Change tests --- .../constitutiveDriver/constitutiveDriver.ats | 3 +- .../testRelperm_BCBaker.xml | 41 ------------ .../testRelperm_BCStoneII.xml | 42 ------------ .../testRelperm_BrooksCorey.xml | 64 +++++++++++++++++++ .../testRelperm_TableHyst2ph.xml | 9 ++- .../testRelperm_Table_2_phase.xml | 8 +++ .../testRelperm_Table_3_phase.xml | 9 ++- .../testRelperm_VanGenuchten.xml | 12 +++- .../testRelperm_data/relperm_tables.xml | 34 ++++++++++ .../testRelperm_docExample.xml | 15 ++++- .../relativePermeability/RelpermDriver.cpp | 4 +- 11 files changed, 146 insertions(+), 95 deletions(-) delete mode 100644 inputFiles/constitutiveDriver/testRelperm_BCBaker.xml delete mode 100644 inputFiles/constitutiveDriver/testRelperm_BCStoneII.xml create mode 100644 inputFiles/constitutiveDriver/testRelperm_BrooksCorey.xml diff --git a/inputFiles/constitutiveDriver/constitutiveDriver.ats b/inputFiles/constitutiveDriver/constitutiveDriver.ats index 6908fe23f57..0cb289572ed 100644 --- a/inputFiles/constitutiveDriver/constitutiveDriver.ats +++ b/inputFiles/constitutiveDriver/constitutiveDriver.ats @@ -33,8 +33,7 @@ decks = [ create_relperm_test("testRelperm_Table_2_phase", "Relperm driver for 2 phase tables"), create_relperm_test("testRelperm_Table_3_phase", "Relperm driver for 3 phase tables"), create_relperm_test("testRelperm_TableHyst2ph", "Two-phase hysteresis with large tables"), - create_relperm_test("testRelperm_BCBaker", "Three-phase Brooks-Corey with Baker mixing rule"), - create_relperm_test("testRelperm_BCStoneII", "Three-phase Brooks-Corey with Stone II mixing rule"), + create_relperm_test("testRelperm_BrooksCorey", "Three-phase Brooks-Corey with Baker and Stone II mixing rule"), create_relperm_test("testRelperm_VanGenuchten", "Three-phase van Genuchten-Mualem models"), create_relperm_test("testRelperm_docExample", "Relperm Driver documentation example") ] diff --git a/inputFiles/constitutiveDriver/testRelperm_BCBaker.xml b/inputFiles/constitutiveDriver/testRelperm_BCBaker.xml deleted file mode 100644 index d7045ed3ac0..00000000000 --- a/inputFiles/constitutiveDriver/testRelperm_BCBaker.xml +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/inputFiles/constitutiveDriver/testRelperm_BCStoneII.xml b/inputFiles/constitutiveDriver/testRelperm_BCStoneII.xml deleted file mode 100644 index c33c266ccb3..00000000000 --- a/inputFiles/constitutiveDriver/testRelperm_BCStoneII.xml +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/inputFiles/constitutiveDriver/testRelperm_BrooksCorey.xml b/inputFiles/constitutiveDriver/testRelperm_BrooksCorey.xml new file mode 100644 index 00000000000..a6fec333c09 --- /dev/null +++ b/inputFiles/constitutiveDriver/testRelperm_BrooksCorey.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inputFiles/constitutiveDriver/testRelperm_TableHyst2ph.xml b/inputFiles/constitutiveDriver/testRelperm_TableHyst2ph.xml index e3744af64fa..db7744e3860 100644 --- a/inputFiles/constitutiveDriver/testRelperm_TableHyst2ph.xml +++ b/inputFiles/constitutiveDriver/testRelperm_TableHyst2ph.xml @@ -6,6 +6,8 @@ name="testRelperm" relperm="relperm" steps="100" + phaseNames="{ gas }" + saturationControls="{ gasSaturation }" output="testRelperm_TableHyst2ph.txt" logLevel="1" /> @@ -54,6 +56,11 @@ name="imbibitionGasRelativePermeabilityTable" coordinateFiles="{ testRelperm_data/imbibitionPhaseVolFraction_gas.txt }" voxelFile="testRelperm_data/imbibitionRelPerm_gas.txt"/> - + + diff --git a/inputFiles/constitutiveDriver/testRelperm_Table_2_phase.xml b/inputFiles/constitutiveDriver/testRelperm_Table_2_phase.xml index 7a6c5721825..b27dce823d8 100644 --- a/inputFiles/constitutiveDriver/testRelperm_Table_2_phase.xml +++ b/inputFiles/constitutiveDriver/testRelperm_Table_2_phase.xml @@ -7,24 +7,32 @@ relperm="2_phase_residual" steps="10" output="testRelperm_Table_2_phase_2_phase_residual.txt" + phaseNames="{ gas }" + saturationControls="{ LINEAR.SATURATION }" logLevel="1" /> diff --git a/inputFiles/constitutiveDriver/testRelperm_Table_3_phase.xml b/inputFiles/constitutiveDriver/testRelperm_Table_3_phase.xml index b9169420275..88784e04ac9 100644 --- a/inputFiles/constitutiveDriver/testRelperm_Table_3_phase.xml +++ b/inputFiles/constitutiveDriver/testRelperm_Table_3_phase.xml @@ -5,13 +5,18 @@ diff --git a/inputFiles/constitutiveDriver/testRelperm_VanGenuchten.xml b/inputFiles/constitutiveDriver/testRelperm_VanGenuchten.xml index 13e580c6d24..cb6183319e5 100644 --- a/inputFiles/constitutiveDriver/testRelperm_VanGenuchten.xml +++ b/inputFiles/constitutiveDriver/testRelperm_VanGenuchten.xml @@ -5,13 +5,17 @@ @@ -56,4 +60,8 @@ waterOilRelPermExponentInv="{ 0.25, 0.5 }" waterOilRelPermMaxValue="{ 0.65, 0.75 }" /> + + + + diff --git a/inputFiles/constitutiveDriver/testRelperm_data/relperm_tables.xml b/inputFiles/constitutiveDriver/testRelperm_data/relperm_tables.xml index fa306221455..20f40c23458 100644 --- a/inputFiles/constitutiveDriver/testRelperm_data/relperm_tables.xml +++ b/inputFiles/constitutiveDriver/testRelperm_data/relperm_tables.xml @@ -52,5 +52,39 @@ coordinates="{ 0,0.07,0.14,0.21,0.29,0.36,0.43,0.5,0.57,0.64,0.71,0.79,0.86,0.93,1 }" values="{ 0,0.0001,0.0008,0.0036,0.0116,0.0253,0.0479,0.0825,0.1322,0.2006,0.2914,0.428,0.581,0.7701,1 }" interpolation="linear" /> + + + + diff --git a/inputFiles/constitutiveDriver/testRelperm_docExample.xml b/inputFiles/constitutiveDriver/testRelperm_docExample.xml index b3d9d37c4d8..7ca60cc47ea 100644 --- a/inputFiles/constitutiveDriver/testRelperm_docExample.xml +++ b/inputFiles/constitutiveDriver/testRelperm_docExample.xml @@ -6,7 +6,9 @@ @@ -40,8 +42,8 @@ - + + + + + - diff --git a/src/coreComponents/constitutiveDrivers/relativePermeability/RelpermDriver.cpp b/src/coreComponents/constitutiveDrivers/relativePermeability/RelpermDriver.cpp index 3cbbaebc4f6..732c3647bee 100644 --- a/src/coreComponents/constitutiveDrivers/relativePermeability/RelpermDriver.cpp +++ b/src/coreComponents/constitutiveDrivers/relativePermeability/RelpermDriver.cpp @@ -142,8 +142,8 @@ bool RelpermDriver::execute() GEOS_LOG_LEVEL_RANK_0( logInfo::LogOutput, " Type ................... " << baseRelperm.getCatalogName() ); GEOS_LOG_LEVEL_RANK_0( logInfo::LogOutput, " No. of Phases .......... " << numPhases ); GEOS_LOG_LEVEL_RANK_0( logInfo::LogOutput, " Steps .................. " << m_numSteps ); - GEOS_LOG_LEVEL_RANK_0( logInfo::LogOutput, " Selected phases ........ " << stringutilities::join( m_phaseNames, ',' ) ); - GEOS_LOG_LEVEL_RANK_0( logInfo::LogOutput, " Saturation functions ... " << stringutilities::join( m_saturationFunctionNames, ',' ) ); + GEOS_LOG_LEVEL_RANK_0( logInfo::LogOutput, " Selected phases ........ " << stringutilities::join( m_phaseNames, ", " ) ); + GEOS_LOG_LEVEL_RANK_0( logInfo::LogOutput, " Saturation functions ... " << stringutilities::join( m_saturationFunctionNames, ", " ) ); if( !m_outputFile.empty()) { GEOS_LOG_LEVEL_RANK_0( logInfo::LogOutput, " Output ................. " << m_outputFile ); From 468e4cb19a87a4f86d70656ef529510bdf276385 Mon Sep 17 00:00:00 2001 From: dkachuma Date: Sat, 23 May 2026 14:00:03 -0500 Subject: [PATCH 15/25] Add historical saturation --- .../testRelperm_docExample.xml | 34 ++++++++++- .../relativePermeability/RelpermDriver.cpp | 12 ---- .../relativePermeability/RelpermDriver.hpp | 2 - .../RelpermDriverRunTest.hpp | 60 ++++++------------- 4 files changed, 50 insertions(+), 58 deletions(-) diff --git a/inputFiles/constitutiveDriver/testRelperm_docExample.xml b/inputFiles/constitutiveDriver/testRelperm_docExample.xml index 7ca60cc47ea..249efbacd06 100644 --- a/inputFiles/constitutiveDriver/testRelperm_docExample.xml +++ b/inputFiles/constitutiveDriver/testRelperm_docExample.xml @@ -10,6 +10,14 @@ phaseNames="{ gas }" saturationControls="{ gasSaturation }" logLevel="1" /> + @@ -24,6 +32,9 @@ + + wettingNonWettingRelPermTableNames="{ waterRelPerm, gasRelPermDrainage }" /> + + @@ -51,10 +69,16 @@ interpolation="linear" /> + + + + diff --git a/src/coreComponents/constitutiveDrivers/relativePermeability/RelpermDriver.cpp b/src/coreComponents/constitutiveDrivers/relativePermeability/RelpermDriver.cpp index 732c3647bee..8a136a5e5e2 100644 --- a/src/coreComponents/constitutiveDrivers/relativePermeability/RelpermDriver.cpp +++ b/src/coreComponents/constitutiveDrivers/relativePermeability/RelpermDriver.cpp @@ -45,10 +45,6 @@ RelpermDriver::RelpermDriver( const string & name, registerWrapper( viewKeyStruct::saturationFunctionsString(), &m_saturationFunctionNames ). setInputFlag( InputFlags::REQUIRED ). setDescription( "Functions controlling saturation time history of the selected phases" ); - - registerWrapper( viewKeyStruct::historicalSaturationsString(), &m_historicalSaturations ). - setInputFlag( InputFlags::OPTIONAL ). - setDescription( "Historical saturations for each phase." ); } void RelpermDriver::postInputInitialization() @@ -100,14 +96,6 @@ void RelpermDriver::postInputInitialization() getWrapperDataContext( viewKeyStruct::saturationFunctionsString() ) ); } - // Historical saturations must be the same number as the phases - if( !m_historicalSaturations.empty()) - { - GEOS_ERROR_IF( m_historicalSaturations.size() != numPhases, - "Number of historical saturations must be the same as the number of phases", - getWrapperDataContext( viewKeyStruct::historicalSaturationsString() ) ); - } - string_array columnNames; getColumnNames( columnNames ); integer const numCols = static_cast< integer >(columnNames.size()); diff --git a/src/coreComponents/constitutiveDrivers/relativePermeability/RelpermDriver.hpp b/src/coreComponents/constitutiveDrivers/relativePermeability/RelpermDriver.hpp index 3e8cf6b3568..c652447c109 100644 --- a/src/coreComponents/constitutiveDrivers/relativePermeability/RelpermDriver.hpp +++ b/src/coreComponents/constitutiveDrivers/relativePermeability/RelpermDriver.hpp @@ -75,13 +75,11 @@ class RelpermDriver : public ConstitutiveDriver constexpr static char const * relpermNameString() { return "relperm"; } constexpr static char const * phaseNamesString() { return "phaseNames"; } constexpr static char const * saturationFunctionsString() { return "saturationControls"; } - constexpr static char const * historicalSaturationsString() { return "historicalSaturations"; } }; string m_relpermName; ///< relPermType identifier string_array m_phaseNames; string_array m_saturationFunctionNames; - array1d< real64 > m_historicalSaturations; }; } diff --git a/src/coreComponents/constitutiveDrivers/relativePermeability/RelpermDriverRunTest.hpp b/src/coreComponents/constitutiveDrivers/relativePermeability/RelpermDriverRunTest.hpp index ab53d2d227f..04655ec956c 100644 --- a/src/coreComponents/constitutiveDrivers/relativePermeability/RelpermDriverRunTest.hpp +++ b/src/coreComponents/constitutiveDrivers/relativePermeability/RelpermDriverRunTest.hpp @@ -57,55 +57,31 @@ RelpermDriver::runTest( RELPERM_TYPE & relperm, const arrayView2d< real64 > & ta // Offset for the historical saturations integer const OFFSET = SATURATION + numPhases; - using CurveType = constitutive::KilloughHysteresis::HysteresisCurve; - using KeyStruct = typename RELPERM_TYPE::viewKeyStruct; - integer ipWetting, ipNonWetting; std::tie( ipWetting, ipNonWetting ) = relperm.wettingAndNonWettingPhaseIndices(); - stackArray1d< real64, constitutive::RelativePermeabilityBase::MAX_NUM_PHASES > historicalSaturations( numPhases ); - historicalSaturations.zero(); - - if( m_historicalSaturations.empty()) - { - auto const & phaseHasHysteresis = relperm.template getReference< array1d< integer > >( KeyStruct::phaseHasHysteresisString()); - if( phaseHasHysteresis[ipNonWetting] ) - { - CurveType const nonWettingCurve = relperm.template getReference< CurveType >( KeyStruct::nonWettingCurveString() ); - historicalSaturations[ipNonWetting] = nonWettingCurve.m_extremumPhaseVolFraction; - } - if( phaseHasHysteresis[ipWetting] ) - { - CurveType const wettingCurve = relperm.template getReference< CurveType >( KeyStruct::wettingCurveString()); - historicalSaturations[ipWetting] = wettingCurve.m_extremumPhaseVolFraction; - } - } - else - { - for( integer p = 0; p < numPhases; ++p ) - { - historicalSaturations[p] = m_historicalSaturations[p]; - } - } - auto historicalView = historicalSaturations.toView(); + // For the wetting phase, we need the minimum saturation + // For the non-wetting phase, we need the maximum saturation + real64 maxNonWetting = 0.0; + real64 minWetting = 1.0; arrayView2d< real64, compflow::USD_PHASE > phaseMaxHistoricalVolFraction = relperm.template getField< fields::relperm::phaseMaxHistoricalVolFraction >().reference(); arrayView2d< real64, compflow::USD_PHASE > phaseMinHistoricalVolFraction = relperm.template getField< fields::relperm::phaseMinHistoricalVolFraction >().reference(); - forAll< parallelDevicePolicy<> >( numRows, - [numPhases, table, - ipWetting, ipNonWetting, - OFFSET, - phaseMaxHistoricalVolFraction, - phaseMinHistoricalVolFraction, - historicalView] GEOS_HOST_DEVICE ( integer const n ) + + for( integer step = 0; step < numRows; ++step ) { - phaseMaxHistoricalVolFraction( n, ipNonWetting ) = historicalView[ipNonWetting]; - phaseMinHistoricalVolFraction( n, ipWetting ) = historicalView[ipWetting]; - for( integer p = 0; p < numPhases; ++p ) - { - table( n, p + OFFSET ) = historicalView[p]; - } - } ); + real64 const sw = table( step, SATURATION + ipWetting ); + real64 const snw = table( step, SATURATION + ipNonWetting ); + + minWetting = LvArray::math::min( minWetting, sw ); + maxNonWetting = LvArray::math::max( maxNonWetting, snw ); + + phaseMinHistoricalVolFraction( step, ipWetting ) = minWetting; + phaseMaxHistoricalVolFraction( step, ipNonWetting ) = maxNonWetting; + + table( step, OFFSET + ipWetting ) = minWetting; + table( step, OFFSET + ipNonWetting ) = maxNonWetting; + } } forAll< parallelDevicePolicy<> >( numRows, From fecc624528469f8ab6fdc7041160fd341917ca0b Mon Sep 17 00:00:00 2001 From: dkachuma Date: Sat, 23 May 2026 14:01:22 -0500 Subject: [PATCH 16/25] Adjust test --- inputFiles/constitutiveDriver/testRelperm_Table_2_phase.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/inputFiles/constitutiveDriver/testRelperm_Table_2_phase.xml b/inputFiles/constitutiveDriver/testRelperm_Table_2_phase.xml index b27dce823d8..47d1d1e4fe6 100644 --- a/inputFiles/constitutiveDriver/testRelperm_Table_2_phase.xml +++ b/inputFiles/constitutiveDriver/testRelperm_Table_2_phase.xml @@ -29,7 +29,6 @@ Date: Sat, 23 May 2026 14:21:15 -0500 Subject: [PATCH 17/25] Documentation --- .../docs/RelpermDriver.rst | 103 ++++++++---------- src/coreComponents/schema/schema.xsd | 6 +- 2 files changed, 48 insertions(+), 61 deletions(-) diff --git a/src/coreComponents/constitutiveDrivers/docs/RelpermDriver.rst b/src/coreComponents/constitutiveDrivers/docs/RelpermDriver.rst index 283e453920a..de74cd57d8c 100644 --- a/src/coreComponents/constitutiveDrivers/docs/RelpermDriver.rst +++ b/src/coreComponents/constitutiveDrivers/docs/RelpermDriver.rst @@ -7,20 +7,17 @@ Relperm Driver Introduction ------------ -Fitting relative permeability parameters to experimental or benchmark data often should not require a full flow simulation, when the only goal is to check that the permeability curves respond correctly to changes in phase saturation. -As such, GEOS provides a ``RelpermDriver`` allowing the user to test relative permeability models for a well-defined sweep of saturation conditions. -The driver itself is launched like any other GEOS simulation, but with a particular XML structure: +Fitting relative permeability parameters to experimental or benchmark data often should not require a full flow simulation, when the only goal is to check that the relative permeability curves respond correctly to changes in phase saturation. As such, GEOS provides a ``RelpermDriver`` allowing the user to test relative permeability models for a well-defined sweep of saturation conditions. The driver itself is launched like any other GEOS simulation, but with a particular XML structure: .. code-block:: sh ./bin/geosx -i myRelpermTest.xml -This driver will work for any 2-phase or 3-phase relative permeability model enabled within GEOS. +This driver will work for any 2-phase or 3-phase relative permeability model enabled within GEOS. XML Structure ------------- -A typical XML file to run the driver will have several key elements. -Here, we will walk through an example file included in the source tree at +A typical XML file to run the driver will have several key elements. Here, we will walk through an example file included in the source tree at: .. code-block:: sh @@ -28,38 +25,33 @@ Here, we will walk through an example file included in the source tree at A key point is that the XML file follows the same structure as a typical GEOS input deck. As with the other constitutive drivers, once the constitutive block has been calibrated, solver and discretization sections can be added directly to transform it into a full field simulation setup. This continuity allows for smooth transitions between calibration and simulation workflows. -The first step is to define a parameterized relative permeability model to test. -Here, we create a particular type of table-based two-phase relative permeability for sandstone: +The first step is to define a parameterized relative permeability model to test. Here, we create particular types of table-based two-phase relative permeabilities for sandstone, one standard and one with hysteresis: .. literalinclude:: ../../../../inputFiles/constitutiveDriver/testRelperm_docExample.xml :language: xml - :start-after: + :start-after: :end-before: We also define the underlying table functions used by the model. .. literalinclude:: ../../../../inputFiles/constitutiveDriver/testRelperm_docExample.xml :language: xml - :start-after: + :start-after: :end-before: -A ``RelpermDriver`` is then added as a ``Task``, a particular type of executable event often used for simple actions. +A ``RelpermDriver`` is then added as a ``Task``, a particular type of executable event often used for simple actions. .. literalinclude:: ../../../../inputFiles/constitutiveDriver/testRelperm_docExample.xml :language: xml - :start-after: + :start-after: :end-before: -The driver itself takes as input the relative permeability model (referenced by the ``relperm`` parameter) and the ``steps`` parameter, which controls how many saturation increments are evaluated. -Results will be written in a simple ASCII table format (described below) to a specified file if an ``output`` parameter is provided. If it is not specified, output will be written to the standard log (screen). -The ``logLevel`` parameter controls the verbosity of log output during execution. - -The driver task is added as a ``SoloEvent`` to the event queue. -This leads to a trivial event queue, since all we do is launch the driver and then quit. +The driver itself takes as input the relative permeability model (referenced by the ``relperm`` parameter). The model is evaluated over time increments defined by the ``steps`` parameter, while the saturations at each step are evaluated using functions provided in the ``saturationControls`` array. Results will be written in a simple ASCII table format to a specified file if an ``output`` parameter is provided; otherwise, it is written to the standard log. +The driver task is added as a ``SoloEvent`` to the event queue. .. literalinclude:: ../../../../inputFiles/constitutiveDriver/testRelperm_docExample.xml :language: xml - :start-after: + :start-after: :end-before: Parameters @@ -68,23 +60,33 @@ The key XML parameters for the RelpermDriver are summarized in the following tab .. include:: /docs/sphinx/datastructure/RelpermDriver.rst -Phase Count and Step Logic --------------------------- -The internal behavior of the driver depends on the number of phases present in the relative permeability model: +Saturation Assignment +--------------------- +The phase saturations are explicitly driven by user-defined functions over time. -* **2-Phase Models:** The driver iterates exactly ``steps`` + 1 times, spanning from the minimum non-wetting saturation to its maximum valid bounds. -* **3-Phase Models:** The driver iterates through combinations of the wetting and non-wetting phase saturations based on the specified number of ``steps``. Only combinations where the sum of saturations is valid (i.e., less than or equal to 1.0) are evaluated, with the third intermediate phase making up the balance. Thus, the total number of output rows for a 3-phase model depends heavily on the saturation end-points and will be generally greater than the provided ``steps`` parameter. +To configure the driver, the user must specify a list of target phases via ``phaseNames`` and corresponding time-history functions via ``saturationControls``. For an $N$-phase model, exactly $N-1$ phases and saturation functions must be provided. The saturation of the remaining implicit phase is calculated automatically by the driver to ensure the phase volume fractions (saturations) always sum to exactly 1.0. -Hysteresis Support ------------------- -For relative permeability models featuring hysteresis (e.g., ``TableRelativePermeabilityHysteresis``), the driver requires historical phase saturations to determine the departure paths of the curves. This can be directly specified via the ``historicalSaturations`` array in the XML. If omitted, the driver will default to extracting the extremum phase volume fractions from the underlying curve definitions. +During execution, the driver evaluates the provided functions at each step. To ensure physical validity: +* If a saturation function evaluates to a negative value at a given step, the saturation is clamped to zero. +* If the evaluated saturations sum to a value greater than 1.0, they are proportionally scaled down so that their collective sum equals 1.0, and the implicit phase is assigned a saturation of zero. +Examples and Hysteresis +----------------------- +The documentation example contains two distinct driver tests that showcase different saturation trajectories: + +1. **Standard Relperm Test (``test_sandstone``):** This test evaluates a standard relative permeability model. The gas saturation is driven by the ``gasSaturation`` function, which simply increases linearly from 0.0 to 1.0 over the evaluation time. +2. **Hysteresis Relperm Test (``test_sandstone_hysteresis``):** This test evaluates a hysteresis-enabled model and requires a non-monotonic saturation path to test historical tracking. It uses the ``gasSaturationHysteresis`` function, which dynamically varies the gas saturation: rising from 0.0 to 0.4 (primary drainage), dropping to 0.3 (imbibition), rising again to 0.6 (secondary drainage), and finally dropping to 0.4 (secondary imbibition). + +These saturation functions are explicitly defined in the XML as shown below: + +.. literalinclude:: ../../../../inputFiles/constitutiveDriver/testRelperm_docExample.xml + :language: xml + :start-after: + :end-before: + Output Format ------------- -The ``output`` key is used to identify a file to which the results of the simulation are written. -If this key is omitted, file output will be suppressed and instead the resulting table will be output to the screen. - -When written to standard output, the data is written in a table format similar to the one below. +The ``output`` key is used to identify a file to which the results of the simulation are written. If this key is omitted, file output will be suppressed and instead the resulting table will be output to the screen. When written to standard output, the data is written in a table format similar to the one below. .. code:: sh @@ -99,26 +101,9 @@ When written to standard output, the data is written in a table format similar t | 1.0000e+00 | 3.9525e-02 | 9.6047e-01 | 2.3250e-03 | 8.2337e-01 | | 2.0000e+00 | 7.9050e-02 | 9.2095e-01 | 7.0157e-03 | 6.6543e-01 | | 3.0000e+00 | 1.1857e-01 | 8.8143e-01 | 1.4124e-02 | 5.2659e-01 | - | 4.0000e+00 | 1.5810e-01 | 8.4190e-01 | 2.5831e-02 | 4.1943e-01 | - | 5.0000e+00 | 1.9762e-01 | 8.0238e-01 | 4.0028e-02 | 3.2628e-01 | - | 6.0000e+00 | 2.3715e-01 | 7.6285e-01 | 5.6826e-02 | 2.4769e-01 | - | 7.0000e+00 | 2.7668e-01 | 7.2332e-01 | 7.7434e-02 | 1.8665e-01 | - | 8.0000e+00 | 3.1620e-01 | 6.8380e-01 | 1.0046e-01 | 1.3539e-01 | - | 9.0000e+00 | 3.5572e-01 | 6.4428e-01 | 1.2703e-01 | 9.6594e-02 | - | 1.0000e+01 | 3.9525e-01 | 6.0475e-01 | 1.5710e-01 | 6.7650e-02 | - | 1.1000e+01 | 4.3477e-01 | 5.6523e-01 | 1.8949e-01 | 4.4341e-02 | - | 1.2000e+01 | 4.7430e-01 | 5.2570e-01 | 2.2543e-01 | 2.8331e-02 | - | 1.3000e+01 | 5.1382e-01 | 4.8618e-01 | 2.6486e-01 | 1.7448e-02 | - | 1.4000e+01 | 5.5335e-01 | 4.4665e-01 | 3.0663e-01 | 9.4157e-03 | - | 1.5000e+01 | 5.9287e-01 | 4.0713e-01 | 3.5200e-01 | 4.8321e-03 | - | 1.6000e+01 | 6.3240e-01 | 3.6760e-01 | 4.0105e-01 | 2.3675e-03 | - | 1.7000e+01 | 6.7192e-01 | 3.2808e-01 | 4.5150e-01 | 6.9571e-04 | - | 1.8000e+01 | 7.1145e-01 | 2.8855e-01 | 5.0696e-01 | 2.9714e-04 | - | 1.9000e+01 | 7.5097e-01 | 2.4903e-01 | 5.6478e-01 | 6.6429e-05 | - | 2.0000e+01 | 7.9050e-01 | 2.0950e-01 | 6.2490e-01 | 4.4409e-20 | --------------------------------------------------------------------------------- -When written to a file, the file is a simple ASCII format with a brief header followed by the actual saturation data: +When written to a file, the file is a simple ASCII format with a brief header followed by the actual saturation data. An example is shown below. .. code:: sh @@ -127,13 +112,13 @@ When written to a file, the file is a simple ASCII format with a brief header fo # column 3 = saturation,water # column 4 = relperm,gas # column 5 = relperm,water - 0.0000e+00 0.0000e+00 1.0000e+00 0.0000e+00 1.0000e+00 - 1.0000e+00 3.9525e-02 9.6047e-01 2.3250e-03 8.2337e-01 - 2.0000e+00 7.9050e-02 9.2095e-01 7.0157e-03 6.6543e-01 - 3.0000e+00 1.1857e-01 8.8143e-01 1.4124e-02 5.2659e-01 - 4.0000e+00 1.5810e-01 8.4190e-01 2.5831e-02 4.1943e-01 - 5.0000e+00 1.9762e-01 8.0238e-01 4.0028e-02 3.2628e-01 - ... - -The exact number of columns will depend on the phase count configured in the chosen model. In this 2-phase example, there are 5 total columns tracking the step index, saturations, and resulting relative permeabilities. If hysteresis is activated on the model, additional columns tracking historical saturations will be present between the saturations and the calculated relative permeabilities. -This file format can be easily parsed using standard tools or plotting scripts to examine the generated curves. + 0.0000e+00 1.0000e+00 0.0000e+00 1.0000e+00 0.0000e+00 + 2.0000e-01 8.0000e-01 2.0000e-01 7.0006e-01 7.1429e-04 + 4.0000e-01 6.0000e-01 4.0000e-01 4.4233e-01 1.5671e-02 + 6.0000e-01 4.0000e-01 6.0000e-01 2.3163e-01 9.8057e-02 + 8.0000e-01 2.0000e-01 8.0000e-01 7.6686e-02 3.6119e-01 + 1.0000e+00 0.0000e+00 1.0000e+00 0.0000e+00 1.0000e+00 + +The exact number of columns will depend on the phase count configured in the chosen model. If hysteresis is activated on the model, additional columns tracking historical extremum saturations will automatically be present between the instantaneous saturations and the calculated relative permeabilities. This file can be readily plotted using any number of plotting tools. Each row corresponds to one timestep of the driver, starting from initial conditions in the first row. + + diff --git a/src/coreComponents/schema/schema.xsd b/src/coreComponents/schema/schema.xsd index 479a06a4bb4..06fe5b92f7a 100644 --- a/src/coreComponents/schema/schema.xsd +++ b/src/coreComponents/schema/schema.xsd @@ -6482,8 +6482,6 @@ Information output from lower logLevels is added with the desired log level - - + + + + From be8506df7ed3a8bc5a631c84e2dacf133eac81e4 Mon Sep 17 00:00:00 2001 From: dkachuma Date: Sat, 23 May 2026 14:40:07 -0500 Subject: [PATCH 18/25] Documentation --- .../testRelperm_data/plot_relperm.py | 50 +++++++++++ .../test_relperm_sandstone_hysteresis.txt | 88 +++++++++++++++++++ .../docs/RelpermDriver.rst | 11 ++- 3 files changed, 148 insertions(+), 1 deletion(-) create mode 100644 inputFiles/constitutiveDriver/testRelperm_data/plot_relperm.py create mode 100644 inputFiles/constitutiveDriver/testRelperm_data/test_relperm_sandstone_hysteresis.txt diff --git a/inputFiles/constitutiveDriver/testRelperm_data/plot_relperm.py b/inputFiles/constitutiveDriver/testRelperm_data/plot_relperm.py new file mode 100644 index 00000000000..4a4877102a9 --- /dev/null +++ b/inputFiles/constitutiveDriver/testRelperm_data/plot_relperm.py @@ -0,0 +1,50 @@ +import numpy as np +import matplotlib.pyplot as plt +from matplotlib.collections import LineCollection +import os + +# Set global font size to 12 +plt.rcParams.update({'font.size': 12}) + +# Load data (ignore comment lines starting with '#') +script_dir = os.path.dirname(os.path.abspath(__file__)) +file_name = "test_relperm_sandstone_hysteresis.txt" +data_file = os.path.join(script_dir, "..", file_name) +if not os.path.exists(data_file): + data_file = os.path.join(script_dir, file_name) +data = np.loadtxt(data_file) + +# Columns: +# col 2 = gas saturation (Sg) +# col 4 = historical maximum gas saturation (Sghy) +# col 6 = gas relperm (krg) +Sg = data[:, 1] +krg = data[:, 5] +Sghy = data[:, 3] + +# Create segments for the colored line +points = np.array([Sg, krg]).T.reshape(-1, 1, 2) +segments = np.concatenate([points[:-1], points[1:]], axis=1) + +fig, ax = plt.subplots() + +# Create the LineCollection +norm = plt.Normalize(Sghy.min(), Sghy.max()) +lc = LineCollection(segments, cmap='jet', norm=norm) + +# Set the colors based on the average Sghy of the two points making up each segment +lc.set_array(0.5 * (Sghy[:-1] + Sghy[1:])) +lc.set_linewidth(2) # 2pt line width + +line = ax.add_collection(lc) + +# Autoscale the axes since add_collection doesn't do it automatically +ax.autoscale() + +ax.set_xlabel('Gas saturation (Sg)') +ax.set_ylabel('Gas relative permeability (krg)') +ax.set_title('Gas RelPerm vs Gas Saturation') +fig.colorbar(line, ax=ax, label='Sghy') +ax.grid(True, alpha=0.3) + +plt.show() diff --git a/inputFiles/constitutiveDriver/testRelperm_data/test_relperm_sandstone_hysteresis.txt b/inputFiles/constitutiveDriver/testRelperm_data/test_relperm_sandstone_hysteresis.txt new file mode 100644 index 00000000000..826611c60fb --- /dev/null +++ b/inputFiles/constitutiveDriver/testRelperm_data/test_relperm_sandstone_hysteresis.txt @@ -0,0 +1,88 @@ +# column 1 = index +# column 2 = saturation,gas +# column 3 = saturation,water +# column 4 = historical saturation,gas +# column 5 = historical saturation,water +# column 6 = relperm,gas +# column 7 = relperm,water + 0.0000e+00 0.0000e+00 1.0000e+00 0.0000e+00 1.0000e+00 0.0000e+00 1.0000e+00 + 5.0000e-02 2.0000e-02 9.8000e-01 2.0000e-02 9.8000e-01 1.1765e-03 9.1062e-01 + 1.0000e-01 4.0000e-02 9.6000e-01 4.0000e-02 9.6000e-01 2.3529e-03 8.2124e-01 + 1.5000e-01 6.0000e-02 9.4000e-01 6.0000e-02 9.4000e-01 3.5899e-03 7.3234e-01 + 2.0000e-01 8.0000e-02 9.2000e-01 8.0000e-02 9.2000e-01 7.1866e-03 6.6209e-01 + 2.5000e-01 1.0000e-01 9.0000e-01 1.0000e-01 9.0000e-01 1.0783e-02 5.9184e-01 + 3.0000e-01 1.2000e-01 8.8000e-01 1.2000e-01 8.8000e-01 1.4497e-02 5.2240e-01 + 3.5000e-01 1.4000e-01 8.6000e-01 1.4000e-01 8.6000e-01 2.0447e-02 4.6835e-01 + 4.0000e-01 1.6000e-01 8.4000e-01 1.6000e-01 8.4000e-01 2.6397e-02 4.1430e-01 + 4.5000e-01 1.8000e-01 8.2000e-01 1.8000e-01 8.2000e-01 3.2537e-02 3.6132e-01 + 5.0000e-01 2.0000e-01 8.0000e-01 2.0000e-01 8.0000e-01 4.1038e-02 3.2155e-01 + 5.5000e-01 2.2000e-01 7.8000e-01 2.2000e-01 7.8000e-01 4.9538e-02 2.8179e-01 + 6.0000e-01 2.4000e-01 7.6000e-01 2.4000e-01 7.6000e-01 5.8038e-02 2.4202e-01 + 6.5000e-01 2.6000e-01 7.4000e-01 2.6000e-01 7.4000e-01 6.8242e-02 2.1011e-01 + 7.0000e-01 2.8000e-01 7.2000e-01 2.8000e-01 7.2000e-01 7.9267e-02 1.8197e-01 + 7.5000e-01 3.0000e-01 7.0000e-01 3.0000e-01 7.0000e-01 9.0292e-02 1.5384e-01 + 8.0000e-01 3.2000e-01 6.8000e-01 3.2000e-01 6.8000e-01 1.0301e-01 1.3166e-01 + 8.5000e-01 3.4000e-01 6.6000e-01 3.4000e-01 6.6000e-01 1.1646e-01 1.1203e-01 + 9.0000e-01 3.6000e-01 6.4000e-01 3.6000e-01 6.4000e-01 1.2990e-01 9.2398e-02 + 9.5000e-01 3.8000e-01 6.2000e-01 3.8000e-01 6.2000e-01 1.4505e-01 7.7569e-02 + 1.0000e+00 4.0000e-01 6.0000e-01 4.0000e-01 6.0000e-01 1.6085e-01 6.4561e-02 + 1.0500e+00 3.9500e-01 6.0500e-01 4.0000e-01 6.0000e-01 1.5193e-01 6.7813e-02 + 1.1000e+00 3.9000e-01 6.1000e-01 4.0000e-01 6.0000e-01 1.4301e-01 7.1065e-02 + 1.1500e+00 3.8500e-01 6.1500e-01 4.0000e-01 6.0000e-01 1.3409e-01 7.4317e-02 + 1.2000e+00 3.8000e-01 6.2000e-01 4.0000e-01 6.0000e-01 1.2517e-01 7.7569e-02 + 1.2500e+00 3.7500e-01 6.2500e-01 4.0000e-01 6.0000e-01 1.1724e-01 8.0821e-02 + 1.3000e+00 3.7000e-01 6.3000e-01 4.0000e-01 6.0000e-01 1.0977e-01 8.4073e-02 + 1.3500e+00 3.6500e-01 6.3500e-01 4.0000e-01 6.0000e-01 1.0230e-01 8.7491e-02 + 1.4000e+00 3.6000e-01 6.4000e-01 4.0000e-01 6.0000e-01 9.4837e-02 9.2398e-02 + 1.4500e+00 3.5500e-01 6.4500e-01 4.0000e-01 6.0000e-01 8.7855e-02 9.7306e-02 + 1.5000e+00 3.5000e-01 6.5000e-01 4.0000e-01 6.0000e-01 8.1742e-02 1.0221e-01 + 1.5500e+00 3.4500e-01 6.5500e-01 4.0000e-01 6.0000e-01 7.5629e-02 1.0712e-01 + 1.6000e+00 3.4000e-01 6.6000e-01 4.0000e-01 6.0000e-01 6.9517e-02 1.1203e-01 + 1.6500e+00 3.3500e-01 6.6500e-01 4.0000e-01 6.0000e-01 6.3451e-02 1.1694e-01 + 1.7000e+00 3.3000e-01 6.7000e-01 4.0000e-01 6.0000e-01 5.8588e-02 1.2184e-01 + 1.7500e+00 3.2500e-01 6.7500e-01 4.0000e-01 6.0000e-01 5.3725e-02 1.2675e-01 + 1.8000e+00 3.2000e-01 6.8000e-01 4.0000e-01 6.0000e-01 4.8862e-02 1.3166e-01 + 1.8500e+00 3.1500e-01 6.8500e-01 4.0000e-01 6.0000e-01 4.3999e-02 1.3657e-01 + 1.9000e+00 3.1000e-01 6.9000e-01 4.0000e-01 6.0000e-01 3.9952e-02 1.4147e-01 + 1.9500e+00 3.0500e-01 6.9500e-01 4.0000e-01 6.0000e-01 3.6228e-02 1.4681e-01 + 2.0000e+00 3.0000e-01 7.0000e-01 4.0000e-01 6.0000e-01 3.2504e-02 1.5384e-01 + 2.0500e+00 3.1500e-01 6.8500e-01 4.0000e-01 6.0000e-01 4.3999e-02 1.3657e-01 + 2.1000e+00 3.3000e-01 6.7000e-01 4.0000e-01 6.0000e-01 5.8588e-02 1.2184e-01 + 2.1500e+00 3.4500e-01 6.5500e-01 4.0000e-01 6.0000e-01 7.5629e-02 1.0712e-01 + 2.2000e+00 3.6000e-01 6.4000e-01 4.0000e-01 6.0000e-01 9.4837e-02 9.2398e-02 + 2.2500e+00 3.7500e-01 6.2500e-01 4.0000e-01 6.0000e-01 1.1724e-01 8.0821e-02 + 2.3000e+00 3.9000e-01 6.1000e-01 4.0000e-01 6.0000e-01 1.4301e-01 7.1065e-02 + 2.3500e+00 4.0500e-01 5.9500e-01 4.0500e-01 5.9500e-01 1.6480e-01 6.1308e-02 + 2.4000e+00 4.2000e-01 5.8000e-01 4.2000e-01 5.8000e-01 1.7665e-01 5.1552e-02 + 2.4500e+00 4.3500e-01 5.6500e-01 4.3500e-01 5.6500e-01 1.8969e-01 4.4250e-02 + 2.5000e+00 4.5000e-01 5.5000e-01 4.5000e-01 5.5000e-01 2.0333e-01 3.8174e-02 + 2.5500e+00 4.6500e-01 5.3500e-01 4.6500e-01 5.3500e-01 2.1697e-01 3.2098e-02 + 2.6000e+00 4.8000e-01 5.2000e-01 4.8000e-01 5.2000e-01 2.3061e-01 2.6023e-02 + 2.6500e+00 4.9500e-01 5.0500e-01 4.9500e-01 5.0500e-01 2.4550e-01 2.1782e-02 + 2.7000e+00 5.1000e-01 4.9000e-01 5.1000e-01 4.9000e-01 2.6093e-01 1.8329e-02 + 2.7500e+00 5.2500e-01 4.7500e-01 5.2500e-01 4.7500e-01 2.7636e-01 1.4875e-02 + 2.8000e+00 5.4000e-01 4.6000e-01 5.4000e-01 4.6000e-01 2.9179e-01 1.1421e-02 + 2.8500e+00 5.5500e-01 4.4500e-01 5.5500e-01 4.4500e-01 3.0853e-01 9.2244e-03 + 2.9000e+00 5.7000e-01 4.3000e-01 5.7000e-01 4.3000e-01 3.2575e-01 7.4849e-03 + 2.9500e+00 5.8500e-01 4.1500e-01 5.8500e-01 4.1500e-01 3.4296e-01 5.7454e-03 + 3.0000e+00 6.0000e-01 4.0000e-01 6.0000e-01 4.0000e-01 3.6018e-01 4.0059e-03 + 3.0500e+00 5.9000e-01 4.1000e-01 6.0000e-01 4.0000e-01 3.3743e-01 5.1655e-03 + 3.1000e+00 5.8000e-01 4.2000e-01 6.0000e-01 4.0000e-01 3.1468e-01 6.3252e-03 + 3.1500e+00 5.7000e-01 4.3000e-01 6.0000e-01 4.0000e-01 2.9193e-01 7.4849e-03 + 3.2000e+00 5.6000e-01 4.4000e-01 6.0000e-01 4.0000e-01 2.6994e-01 8.6445e-03 + 3.2500e+00 5.5000e-01 4.5000e-01 6.0000e-01 4.0000e-01 2.5090e-01 9.8042e-03 + 3.3000e+00 5.4000e-01 4.6000e-01 6.0000e-01 4.0000e-01 2.3185e-01 1.1421e-02 + 3.3500e+00 5.3000e-01 4.7000e-01 6.0000e-01 4.0000e-01 2.1281e-01 1.3724e-02 + 3.4000e+00 5.2000e-01 4.8000e-01 6.0000e-01 4.0000e-01 1.9519e-01 1.6026e-02 + 3.4500e+00 5.1000e-01 4.9000e-01 6.0000e-01 4.0000e-01 1.7960e-01 1.8329e-02 + 3.5000e+00 5.0000e-01 5.0000e-01 6.0000e-01 4.0000e-01 1.6401e-01 2.0631e-02 + 3.5500e+00 4.9000e-01 5.1000e-01 6.0000e-01 4.0000e-01 1.4842e-01 2.2934e-02 + 3.6000e+00 4.8000e-01 5.2000e-01 6.0000e-01 4.0000e-01 1.3480e-01 2.6023e-02 + 3.6500e+00 4.7000e-01 5.3000e-01 6.0000e-01 4.0000e-01 1.2240e-01 3.0073e-02 + 3.7000e+00 4.6000e-01 5.4000e-01 6.0000e-01 4.0000e-01 1.0999e-01 3.4124e-02 + 3.7500e+00 4.5000e-01 5.5000e-01 6.0000e-01 4.0000e-01 9.7590e-02 3.8174e-02 + 3.8000e+00 4.4000e-01 5.6000e-01 6.0000e-01 4.0000e-01 8.7588e-02 4.2224e-02 + 3.8500e+00 4.3000e-01 5.7000e-01 6.0000e-01 4.0000e-01 7.8090e-02 4.6275e-02 + 3.9000e+00 4.2000e-01 5.8000e-01 6.0000e-01 4.0000e-01 6.8592e-02 5.1552e-02 + 3.9500e+00 4.1000e-01 5.9000e-01 6.0000e-01 4.0000e-01 5.9180e-02 5.8056e-02 + 4.0000e+00 4.0000e-01 6.0000e-01 6.0000e-01 4.0000e-01 5.2287e-02 6.4561e-02 diff --git a/src/coreComponents/constitutiveDrivers/docs/RelpermDriver.rst b/src/coreComponents/constitutiveDrivers/docs/RelpermDriver.rst index de74cd57d8c..df3b8adc0c0 100644 --- a/src/coreComponents/constitutiveDrivers/docs/RelpermDriver.rst +++ b/src/coreComponents/constitutiveDrivers/docs/RelpermDriver.rst @@ -83,7 +83,16 @@ These saturation functions are explicitly defined in the XML as shown below: :language: xml :start-after: :end-before: - + +.. plot:: inputFiles/constitutiveDriver/testRelperm_data/plot_relperm.py + +Output of the ``test_sandstone_hysteresis`` task. The plotted trajectory follows the defined ``gasSaturationHysteresis`` values ``{ 0.0, 0.4, 0.3, 0.6, 0.4 }``. The color scale indicates the internally tracked maximum historical gas saturation (Sghy), dictating the departure curves: + +* **Primary Drainage (0.0 to 0.4):** Sghy updates alongside the current saturation (dark blue to yellow). +* **Primary Imbibition (0.4 to 0.3):** The curve drops from the main envelope; Sghy remains constant at 0.4 (yellow). +* **Secondary Drainage (0.3 to 0.6):** The curve climbs back up, with Sghy resuming updates past 0.4 until it hits 0.6 (dark red). +* **Secondary Imbibition (0.6 to 0.4):** The relative permeability drops along a new, lower path, while Sghy stays fixed at 0.6 (dark red). + Output Format ------------- The ``output`` key is used to identify a file to which the results of the simulation are written. If this key is omitted, file output will be suppressed and instead the resulting table will be output to the screen. When written to standard output, the data is written in a table format similar to the one below. From b9f7bd7b90e04462cea0c38c416cf496db050527 Mon Sep 17 00:00:00 2001 From: dkachuma Date: Sat, 23 May 2026 15:06:32 -0500 Subject: [PATCH 19/25] Fix documentation --- src/coreComponents/constitutiveDrivers/docs/RelpermDriver.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/coreComponents/constitutiveDrivers/docs/RelpermDriver.rst b/src/coreComponents/constitutiveDrivers/docs/RelpermDriver.rst index df3b8adc0c0..53067ac7361 100644 --- a/src/coreComponents/constitutiveDrivers/docs/RelpermDriver.rst +++ b/src/coreComponents/constitutiveDrivers/docs/RelpermDriver.rst @@ -49,6 +49,7 @@ A ``RelpermDriver`` is then added as a ``Task``, a particular type of executable The driver itself takes as input the relative permeability model (referenced by the ``relperm`` parameter). The model is evaluated over time increments defined by the ``steps`` parameter, while the saturations at each step are evaluated using functions provided in the ``saturationControls`` array. Results will be written in a simple ASCII table format to a specified file if an ``output`` parameter is provided; otherwise, it is written to the standard log. The driver task is added as a ``SoloEvent`` to the event queue. + .. literalinclude:: ../../../../inputFiles/constitutiveDriver/testRelperm_docExample.xml :language: xml :start-after: @@ -84,7 +85,7 @@ These saturation functions are explicitly defined in the XML as shown below: :start-after: :end-before: -.. plot:: inputFiles/constitutiveDriver/testRelperm_data/plot_relperm.py +.. plot:: ../inputFiles/constitutiveDriver/testRelperm_data/plot_relperm.py Output of the ``test_sandstone_hysteresis`` task. The plotted trajectory follows the defined ``gasSaturationHysteresis`` values ``{ 0.0, 0.4, 0.3, 0.6, 0.4 }``. The color scale indicates the internally tracked maximum historical gas saturation (Sghy), dictating the departure curves: From f02ad2c1cf0210ab0b48cacb13a4bb010630ae2e Mon Sep 17 00:00:00 2001 From: dkachuma Date: Sat, 23 May 2026 15:43:43 -0500 Subject: [PATCH 20/25] Fix script --- .../constitutiveDriver/testRelperm_data/plot_relperm.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/inputFiles/constitutiveDriver/testRelperm_data/plot_relperm.py b/inputFiles/constitutiveDriver/testRelperm_data/plot_relperm.py index 4a4877102a9..8445cf6a196 100644 --- a/inputFiles/constitutiveDriver/testRelperm_data/plot_relperm.py +++ b/inputFiles/constitutiveDriver/testRelperm_data/plot_relperm.py @@ -7,11 +7,11 @@ plt.rcParams.update({'font.size': 12}) # Load data (ignore comment lines starting with '#') -script_dir = os.path.dirname(os.path.abspath(__file__)) +data_dir = "../../../../../../../inputFiles/constitutiveDriver/testRelperm_data" file_name = "test_relperm_sandstone_hysteresis.txt" -data_file = os.path.join(script_dir, "..", file_name) +data_file = os.path.join(geos_dir, "..", file_name) if not os.path.exists(data_file): - data_file = os.path.join(script_dir, file_name) + data_file = os.path.join(geos_dir, file_name) data = np.loadtxt(data_file) # Columns: From ab799db372d5503b22b3d9ddce0d0fbaa5a0d090 Mon Sep 17 00:00:00 2001 From: dkachuma Date: Sat, 23 May 2026 16:04:02 -0500 Subject: [PATCH 21/25] Fix script --- .../constitutiveDriver/testRelperm_data/plot_relperm.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/inputFiles/constitutiveDriver/testRelperm_data/plot_relperm.py b/inputFiles/constitutiveDriver/testRelperm_data/plot_relperm.py index 8445cf6a196..14e099510a5 100644 --- a/inputFiles/constitutiveDriver/testRelperm_data/plot_relperm.py +++ b/inputFiles/constitutiveDriver/testRelperm_data/plot_relperm.py @@ -9,9 +9,9 @@ # Load data (ignore comment lines starting with '#') data_dir = "../../../../../../../inputFiles/constitutiveDriver/testRelperm_data" file_name = "test_relperm_sandstone_hysteresis.txt" -data_file = os.path.join(geos_dir, "..", file_name) +data_file = os.path.join(data_dir, "..", file_name) if not os.path.exists(data_file): - data_file = os.path.join(geos_dir, file_name) + data_file = os.path.join(data_dir, file_name) data = np.loadtxt(data_file) # Columns: From 2d505f0370b351f24b708e0bb770d6fbec8a5bf2 Mon Sep 17 00:00:00 2001 From: dkachuma Date: Sat, 23 May 2026 16:26:35 -0500 Subject: [PATCH 22/25] Fix script --- inputFiles/constitutiveDriver/testRelperm_data/plot_relperm.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/inputFiles/constitutiveDriver/testRelperm_data/plot_relperm.py b/inputFiles/constitutiveDriver/testRelperm_data/plot_relperm.py index 14e099510a5..5c7c6b67e25 100644 --- a/inputFiles/constitutiveDriver/testRelperm_data/plot_relperm.py +++ b/inputFiles/constitutiveDriver/testRelperm_data/plot_relperm.py @@ -7,7 +7,8 @@ plt.rcParams.update({'font.size': 12}) # Load data (ignore comment lines starting with '#') -data_dir = "../../../../../../../inputFiles/constitutiveDriver/testRelperm_data" +data_dir = "." +data_dir = os.path.normpath(os.path.abspath(data_dir)) file_name = "test_relperm_sandstone_hysteresis.txt" data_file = os.path.join(data_dir, "..", file_name) if not os.path.exists(data_file): From 0e716f2313e287dd713fe894b8705c4a13af4810 Mon Sep 17 00:00:00 2001 From: Dickson Kachuma <81433670+dkachuma@users.noreply.github.com> Date: Sun, 24 May 2026 12:30:50 -0500 Subject: [PATCH 23/25] Update baseline entry for PR #4040 Updated the link for PR #4040 to the new baseline tar.gz file. --- BASELINE_NOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BASELINE_NOTES.md b/BASELINE_NOTES.md index 50318c646fe..64349621d11 100644 --- a/BASELINE_NOTES.md +++ b/BASELINE_NOTES.md @@ -5,7 +5,7 @@ This file is designed to track changes to the integrated test baselines. Any developer who updates the baseline ID in the .integrated_tests.yaml file is expected to create an entry in this file with the pull request number, date, and their justification for rebaselining. These notes should be in reverse-chronological order, and use the following time format: (YYYY-MM-DD). -PR #4040 (2026-05-22) +PR #4040 (2026-05-22) Move relperm driver to use bew constitutive driver framework PR #4057 (2026-05-21) From 83a1bf41ac473b660f891648618ecc107836878f Mon Sep 17 00:00:00 2001 From: Dickson Kachuma <81433670+dkachuma@users.noreply.github.com> Date: Sun, 24 May 2026 12:31:01 -0500 Subject: [PATCH 24/25] Update date for PR #4040 in baseline notes Updated the date for PR #4040 in the baseline notes. --- BASELINE_NOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BASELINE_NOTES.md b/BASELINE_NOTES.md index 64349621d11..3a895daeabb 100644 --- a/BASELINE_NOTES.md +++ b/BASELINE_NOTES.md @@ -5,7 +5,7 @@ This file is designed to track changes to the integrated test baselines. Any developer who updates the baseline ID in the .integrated_tests.yaml file is expected to create an entry in this file with the pull request number, date, and their justification for rebaselining. These notes should be in reverse-chronological order, and use the following time format: (YYYY-MM-DD). -PR #4040 (2026-05-22) +PR #4040 (2026-05-24) Move relperm driver to use bew constitutive driver framework PR #4057 (2026-05-21) From 9166050dbc8f05b45e43ad6bd1c91717a0aaee7a Mon Sep 17 00:00:00 2001 From: Dickson Kachuma <81433670+dkachuma@users.noreply.github.com> Date: Sun, 24 May 2026 12:31:14 -0500 Subject: [PATCH 25/25] Update baseline path in integrated_tests.yaml --- .integrated_tests.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.integrated_tests.yaml b/.integrated_tests.yaml index 52900dd4e99..9a2d47f3c6a 100644 --- a/.integrated_tests.yaml +++ b/.integrated_tests.yaml @@ -1,6 +1,6 @@ baselines: bucket: geosx - baseline: integratedTests/baseline_integratedTests-pr4040-16770-cd0acd7 + baseline: integratedTests/baseline_integratedTests-pr4040-16777-2d505f0 allow_fail: all: ''