diff --git a/opm/parser/eclipse/CMakeLists.txt b/opm/parser/eclipse/CMakeLists.txt index a1fd2f76d..e8ef7d164 100644 --- a/opm/parser/eclipse/CMakeLists.txt +++ b/opm/parser/eclipse/CMakeLists.txt @@ -80,6 +80,7 @@ EclipseState/EclipseState.cpp EclipseState/EclipseConfig.cpp EclipseState/Eclipse3DProperties.cpp EclipseState/Runspec.cpp +EclipseState/EndpointScaling.cpp # EclipseState/checkDeck.cpp # diff --git a/opm/parser/eclipse/EclipseState/EndpointScaling.cpp b/opm/parser/eclipse/EclipseState/EndpointScaling.cpp new file mode 100644 index 000000000..075fad863 --- /dev/null +++ b/opm/parser/eclipse/EclipseState/EndpointScaling.cpp @@ -0,0 +1,113 @@ +#include +#include +#include + +namespace Opm { + +EndpointScaling::operator bool() const noexcept { + return this->options[ static_cast< ue >( option::any ) ]; +} + +bool EndpointScaling::directional() const noexcept { + return this->options[ static_cast< ue >( option::directional ) ]; +} + +bool EndpointScaling::nondirectional() const noexcept { + return bool( *this ) && !this->directional(); +} + +bool EndpointScaling::reversible() const noexcept { + return this->options[ static_cast< ue >( option::reversible ) ]; +} + +bool EndpointScaling::irreversible() const noexcept { + return bool( *this ) && !this->reversible(); +} + +bool EndpointScaling::twopoint() const noexcept { + return bool( *this ) && !this->threepoint(); +} + +bool EndpointScaling::threepoint() const noexcept { + return this->options[ static_cast< ue >( option::threepoint ) ]; +} + +namespace { + +bool threepoint_scaling( const Deck& deck ) { + if( !deck.hasKeyword( "SCALECRS" ) ) return false; + + /* + * the manual says that Y and N are acceptable values for "YES" and "NO", so + * it's *VERY* likely that only the first character is checked. We preserve + * this behaviour + */ + const auto value = std::toupper( + deck.getKeyword( "SCALECRS" ) + .getRecord( 0 ) + .getItem( "VALUE" ) + .get< std::string >( 0 ).front() ); + + if( value != 'Y' && value != 'N' ) + throw std::invalid_argument( "SCALECRS takes 'YES' or 'NO'" ); + + return value == 'Y'; +} + +bool endscale_nodir( const DeckKeyword& kw ) { + if( kw.getRecord( 0 ).getItem( 0 ).defaultApplied( 0 ) ) + return true; + + const auto& value = uppercase( kw.getRecord( 0 ) + .getItem( 0 ) + .get< std::string >( 0 ) ); + + if( value != "DIRECT" && value != "NODIR" ) + throw std::invalid_argument( + "ENDSCALE argument 1 must be defaulted, 'DIRECT' or 'NODIR', was " + + value + ); + + return value == "NODIR"; +} + +bool endscale_revers( const DeckKeyword& kw ) { + if( kw.getRecord( 0 ).getItem( 1 ).defaultApplied( 0 ) ) + return true; + + const auto& value = uppercase( kw.getRecord( 0 ) + .getItem( 1 ) + .get< std::string >( 0 ) ); + + if( value != "IRREVERS" && value != "REVERS" ) + throw std::invalid_argument( + "ENDSCALE argument 2 must be defaulted, 'REVERS' or 'IRREVERS', was " + + value + ); + + const auto& item0 = kw.getRecord( 0 ).getItem( 0 ); + if( value == "IRREVERS" + && uppercase( item0.get< std::string >( 0 ) ) != "DIRECT" ) + throw std::invalid_argument( "'IRREVERS' requires 'DIRECT'" ); + + return value == "REVERS"; +} + +} + +EndpointScaling::EndpointScaling( const Deck& deck ) { + if( !deck.hasKeyword( "ENDSCALE" ) ) return; + + const auto& endscale = deck.getKeyword( "ENDSCALE" ); + const bool direct = !endscale_nodir( endscale ); + const bool revers = endscale_revers( endscale ); + const bool threep = threepoint_scaling( deck ); + + this->options.set( static_cast< ue >( option::any ), true ); + this->options.set( static_cast< ue >( option::directional ), direct ); + this->options.set( static_cast< ue >( option::reversible ), revers ); + this->options.set( static_cast< ue >( option::threepoint ), threep ); +} + +} + diff --git a/opm/parser/eclipse/EclipseState/Runspec.cpp b/opm/parser/eclipse/EclipseState/Runspec.cpp index a768edbf5..af4fa5666 100644 --- a/opm/parser/eclipse/EclipseState/Runspec.cpp +++ b/opm/parser/eclipse/EclipseState/Runspec.cpp @@ -62,23 +62,26 @@ size_t Phases::size() const noexcept { return this->bits.count(); } - Runspec::Runspec( const Deck& deck ) : active_phases( Phases{ deck.hasKeyword( "OIL" ), deck.hasKeyword( "GAS" ), deck.hasKeyword( "WATER" ), deck.hasKeyword( "SOLVENT" ), } ), - m_tabdims( deck ) + m_tabdims( deck ), + endscale( deck ) {} const Phases& Runspec::phases() const noexcept { return this->active_phases; } - const Tabdims& Runspec::tabdims() const noexcept { return this->m_tabdims; } +const EndpointScaling& Runspec::endpoint_scaling() const noexcept { + return this->endscale; +} + } diff --git a/opm/parser/eclipse/EclipseState/Runspec.hpp b/opm/parser/eclipse/EclipseState/Runspec.hpp index abb7ebe2e..d74d68e46 100644 --- a/opm/parser/eclipse/EclipseState/Runspec.hpp +++ b/opm/parser/eclipse/EclipseState/Runspec.hpp @@ -51,15 +51,45 @@ class Phases { std::bitset< 4 > bits; }; +class EndpointScaling { + public: + EndpointScaling() noexcept = default; + explicit EndpointScaling( const Deck& ); + + /* true if endpoint scaling is enabled, otherwise false */ + operator bool() const noexcept; + + bool directional() const noexcept; + bool nondirectional() const noexcept; + bool reversible() const noexcept; + bool irreversible() const noexcept; + bool twopoint() const noexcept; + bool threepoint() const noexcept; + + private: + enum class option { + any = 0, + directional = 1, + reversible = 2, + threepoint = 3, + }; + + using ue = std::underlying_type< option >::type; + std::bitset< 4 > options; +}; + class Runspec { public: explicit Runspec( const Deck& ); const Phases& phases() const noexcept; const Tabdims& tabdims() const noexcept; + const EndpointScaling& endpoint_scaling() const noexcept; + private: Phases active_phases; Tabdims m_tabdims; + EndpointScaling endscale; }; } diff --git a/opm/parser/eclipse/EclipseState/tests/RunspecTests.cpp b/opm/parser/eclipse/EclipseState/tests/RunspecTests.cpp index ce2dae57a..04f9cadd7 100644 --- a/opm/parser/eclipse/EclipseState/tests/RunspecTests.cpp +++ b/opm/parser/eclipse/EclipseState/tests/RunspecTests.cpp @@ -107,3 +107,161 @@ BOOST_AUTO_TEST_CASE(TABDIMS) { BOOST_CHECK_EQUAL( tabdims.getNumRSNodes( ) , 20 ); } +BOOST_AUTO_TEST_CASE( EndpointScalingWithoutENDSCALE ) { + const std::string input = R"( + RUNSPEC + )"; + + Runspec runspec( Parser{}.parseString( input, ParseContext{} ) ); + const auto& endscale = runspec.endpoint_scaling(); + + BOOST_CHECK( !endscale ); + BOOST_CHECK( !endscale.directional() ); + BOOST_CHECK( !endscale.nondirectional() ); + BOOST_CHECK( !endscale.reversible() ); + BOOST_CHECK( !endscale.irreversible() ); +} + +BOOST_AUTO_TEST_CASE( EndpointScalingDefaulted ) { + const std::string input = R"( + RUNSPEC + ENDSCALE + / + )"; + + Runspec runspec( Parser{}.parseString( input, ParseContext{} ) ); + const auto& endscale = runspec.endpoint_scaling(); + + BOOST_CHECK( endscale ); + BOOST_CHECK( !endscale.directional() ); + BOOST_CHECK( endscale.nondirectional() ); + BOOST_CHECK( endscale.reversible() ); + BOOST_CHECK( !endscale.irreversible() ); +} + +BOOST_AUTO_TEST_CASE( EndpointScalingDIRECT ) { + const std::string input = R"( + RUNSPEC + ENDSCALE + DIRECT / + )"; + + Runspec runspec( Parser{}.parseString( input, ParseContext{} ) ); + const auto& endscale = runspec.endpoint_scaling(); + + BOOST_CHECK( endscale ); + BOOST_CHECK( endscale.directional() ); + BOOST_CHECK( !endscale.nondirectional() ); + BOOST_CHECK( endscale.reversible() ); + BOOST_CHECK( !endscale.irreversible() ); +} + +BOOST_AUTO_TEST_CASE( EndpointScalingDIRECT_IRREVERS ) { + const std::string input = R"( + RUNSPEC + ENDSCALE + direct IRREVERS / + )"; + + Runspec runspec( Parser{}.parseString( input, ParseContext{} ) ); + const auto& endscale = runspec.endpoint_scaling(); + + BOOST_CHECK( endscale ); + BOOST_CHECK( endscale.directional() ); + BOOST_CHECK( !endscale.nondirectional() ); + BOOST_CHECK( !endscale.reversible() ); + BOOST_CHECK( endscale.irreversible() ); +} + +BOOST_AUTO_TEST_CASE( SCALECRS_without_ENDSCALE ) { + const std::string input = R"( + RUNSPEC + SCALECRS + / + )"; + + Runspec runspec( Parser{}.parseString( input, ParseContext{} ) ); + const auto& endscale = runspec.endpoint_scaling(); + + BOOST_CHECK( !endscale ); + BOOST_CHECK( !endscale.twopoint() ); + BOOST_CHECK( !endscale.threepoint() ); +} + +BOOST_AUTO_TEST_CASE( SCALECRS_N ) { + const std::string N = R"( + RUNSPEC + ENDSCALE + / + SCALECRS + N / + )"; + + const std::string defaulted = R"( + RUNSPEC + ENDSCALE + / + SCALECRS + / + )"; + + for( const auto& input : { N, defaulted } ) { + Runspec runspec( Parser{}.parseString( input, ParseContext{} ) ); + const auto& endscale = runspec.endpoint_scaling(); + + BOOST_CHECK( endscale ); + BOOST_CHECK( endscale.twopoint() ); + BOOST_CHECK( !endscale.threepoint() ); + } +} + +BOOST_AUTO_TEST_CASE( SCALECRS_Y ) { + const std::string input = R"( + RUNSPEC + ENDSCALE + / + SCALECRS + Y / + )"; + + Runspec runspec( Parser{}.parseString( input, ParseContext{} ) ); + const auto& endscale = runspec.endpoint_scaling(); + + BOOST_CHECK( endscale ); + BOOST_CHECK( !endscale.twopoint() ); + BOOST_CHECK( endscale.threepoint() ); +} + +BOOST_AUTO_TEST_CASE( endpoint_scaling_throw_invalid_argument ) { + + const std::string inputs[] = { + R"( + RUNSPEC + ENDSCALE + NODIR IRREVERSIBLE / -- irreversible requires direct + )", + R"( + RUNSPEC + ENDSCALE + * IRREVERSIBLE / -- irreversible requires direct *specified* + )", + R"( + RUNSPEC + ENDSCALE -- ENDSCALE can't take arbitrary input (takes enumeration) + broken / + )", + R"( + RUNSPEC + ENDSCALE + / + SCALECRS -- SCALECRS takes YES/NO + broken / + )", + }; + + for( auto&& input : inputs ) { + auto deck = Parser{}.parseString( input, ParseContext{} ); + std::cout << input << std::endl; + BOOST_CHECK_THROW( Runspec{ deck }, std::invalid_argument ); + } +}