From 7601706e7f9a0e9844c2249b1021b47bae84b358 Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Thu, 23 Mar 2023 14:17:33 +0100 Subject: [PATCH 01/85] Adding context to errors when reading wrapper from xml --- src/coreComponents/dataRepository/Wrapper.hpp | 39 +++++++++++-------- .../dataRepository/WrapperBase.cpp | 10 +++++ .../dataRepository/WrapperBase.hpp | 2 + 3 files changed, 35 insertions(+), 16 deletions(-) diff --git a/src/coreComponents/dataRepository/Wrapper.hpp b/src/coreComponents/dataRepository/Wrapper.hpp index 52a3ef90990..8fc7e4a2451 100644 --- a/src/coreComponents/dataRepository/Wrapper.hpp +++ b/src/coreComponents/dataRepository/Wrapper.hpp @@ -611,25 +611,32 @@ class Wrapper final : public WrapperBase InputFlags const inputFlag = getInputFlag(); if( inputFlag >= InputFlags::OPTIONAL ) { - if( inputFlag == InputFlags::REQUIRED || !hasDefaultValue() ) + try { - m_successfulReadFromInput = xmlWrapper::readAttributeAsType( reference(), - getName(), - targetNode, - inputFlag == InputFlags::REQUIRED ); - GEOSX_THROW_IF( !m_successfulReadFromInput, - GEOSX_FMT( "XML Node '{}' with name='{}' is missing required attribute '{}'." - "Available options are:\n{}\nFor more details, please refer to documentation at:\n" - "http://geosx-geosx.readthedocs-hosted.com/en/latest/docs/sphinx/userGuide/Index.html", - targetNode.path(), targetNode.attribute( "name" ).value(), getName(), dumpInputOptions( true ) ), - InputError ); + if( inputFlag == InputFlags::REQUIRED || !hasDefaultValue() ) + { + m_successfulReadFromInput = xmlWrapper::readAttributeAsType( reference(), + getName(), + targetNode, + inputFlag == InputFlags::REQUIRED ); + GEOSX_THROW_IF( !m_successfulReadFromInput, + GEOSX_FMT( "XML Node '{}' with name='{}' is missing required attribute '{}'." + "Available options are:\n{}\nFor more details, please refer to documentation at:\n" + "http://geosx-geosx.readthedocs-hosted.com/en/latest/docs/sphinx/userGuide/Index.html", + targetNode.path(), targetNode.attribute( "name" ).value(), getName(), dumpInputOptions( true ) ), + InputError ); + } + else + { + m_successfulReadFromInput = xmlWrapper::readAttributeAsType( reference(), + getName(), + targetNode, + getDefaultValueStruct() ); + } } - else + catch( std::exception const & ex ) { - m_successfulReadFromInput = xmlWrapper::readAttributeAsType( reference(), - getName(), - targetNode, - getDefaultValueStruct() ); + processInputException( ex, targetNode ); } return true; diff --git a/src/coreComponents/dataRepository/WrapperBase.cpp b/src/coreComponents/dataRepository/WrapperBase.cpp index 8fef4c691bb..61c3af6423a 100644 --- a/src/coreComponents/dataRepository/WrapperBase.cpp +++ b/src/coreComponents/dataRepository/WrapperBase.cpp @@ -104,6 +104,16 @@ int WrapperBase::setTotalviewDisplay() const } #endif +void WrapperBase::processInputException( std::exception const & ex, + xmlWrapper::xmlNode const & targetNode ) const +{ + string const inputStr = string( targetNode.attribute( getName().c_str() ).value() ); + string subExStr = string( "***** Error happened while reading attribute " ) + getName() + + " from " + m_parent->getName() + ".\n***** Input value: '" + inputStr + "'\n"; + + throw InputError( subExStr + ex.what() ); +} + } } /* namespace geosx */ diff --git a/src/coreComponents/dataRepository/WrapperBase.hpp b/src/coreComponents/dataRepository/WrapperBase.hpp index 9db358efc09..38f3a9e158e 100644 --- a/src/coreComponents/dataRepository/WrapperBase.hpp +++ b/src/coreComponents/dataRepository/WrapperBase.hpp @@ -612,6 +612,8 @@ class WrapperBase /// @endcond + void processInputException( std::exception const & ex, xmlWrapper::xmlNode const & targetNode ) const; + protected: /// Name of the object that is being wrapped From 622f8d678104f4505e082b148019038a17a4b958 Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Fri, 24 Mar 2023 14:12:51 +0100 Subject: [PATCH 02/85] added XML hierarchy to the wrapper parsing error --- src/coreComponents/dataRepository/WrapperBase.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/coreComponents/dataRepository/WrapperBase.cpp b/src/coreComponents/dataRepository/WrapperBase.cpp index 61c3af6423a..a636595c975 100644 --- a/src/coreComponents/dataRepository/WrapperBase.cpp +++ b/src/coreComponents/dataRepository/WrapperBase.cpp @@ -107,10 +107,12 @@ int WrapperBase::setTotalviewDisplay() const void WrapperBase::processInputException( std::exception const & ex, xmlWrapper::xmlNode const & targetNode ) const { - string const inputStr = string( targetNode.attribute( getName().c_str() ).value() ); - string subExStr = string( "***** Error happened while reading attribute " ) + getName() + - " from " + m_parent->getName() + ".\n***** Input value: '" + inputStr + "'\n"; - + xmlWrapper::xmlAttribute const & attribute = targetNode.attribute( getName().c_str() ); + string const inputStr = string( attribute.value() ); + string subExStr = string( "***** XML parsing error in " ) + targetNode.path() + + " (name=" + targetNode.attribute( "name" ).value() + ")/" + attribute.name() + + "\n***** Input value: '" + inputStr + "'\n"; + throw InputError( subExStr + ex.what() ); } From a5fb1fe004460dea5b7ab8b34c384dd042fcac17 Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Mon, 27 Mar 2023 14:05:10 +0200 Subject: [PATCH 03/85] Modified testXmlWrapper so it expect exceptions --- .../unitTests/testXmlWrapper.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/coreComponents/dataRepository/unitTests/testXmlWrapper.cpp b/src/coreComponents/dataRepository/unitTests/testXmlWrapper.cpp index ba79ddb73c5..fed9babd1f4 100644 --- a/src/coreComponents/dataRepository/unitTests/testXmlWrapper.cpp +++ b/src/coreComponents/dataRepository/unitTests/testXmlWrapper.cpp @@ -34,49 +34,49 @@ TEST( testXmlWrapper, array3d_errors ) { input = " { { {0,1,2},{3,4,5} }, { {6,7,8},{9,10,11} }, { {12,13,14},{15,16,17} } , { {18,19,20},{21,22,23} } "; array3d< int > array; - EXPECT_DEATH_IF_SUPPORTED( xmlWrapper::stringToInputVariable( array, input ), IGNORE_OUTPUT ); + ASSERT_THROW( xmlWrapper::stringToInputVariable( array, input ), std::invalid_argument ); } { input = " { { {0,1,2},{3,4,5} }, { {6,7,8},{9,10,11} }, { {12,13,14},{15,16,17} , { {18,19,20},{21,22,23} } }"; array3d< int > array; - EXPECT_DEATH_IF_SUPPORTED( xmlWrapper::stringToInputVariable( array, input ), IGNORE_OUTPUT ); + ASSERT_THROW( xmlWrapper::stringToInputVariable( array, input ), std::invalid_argument ); } { input = " { { {0,1,2},{3,4,5} }, { {6,7,8},{9,10,11} }, { {12,13,14,{15,16,17} } , { {18,19,20},{21,22,23} } }"; array3d< int > array; - EXPECT_DEATH_IF_SUPPORTED( xmlWrapper::stringToInputVariable( array, input ), IGNORE_OUTPUT ); + ASSERT_THROW( xmlWrapper::stringToInputVariable( array, input ), std::invalid_argument ); } { input = " { { {0,1,2},{3,4,5} }, { {6,7,8,{9,10,11} }, { {12,13,14},{15,16,17} } , { {18,19,20},{21,22,23} } }"; array3d< int > array; - EXPECT_DEATH_IF_SUPPORTED( xmlWrapper::stringToInputVariable( array, input ), IGNORE_OUTPUT ); + ASSERT_THROW( xmlWrapper::stringToInputVariable( array, input ), std::invalid_argument ); } { input = " { { 0,1,2},{3,4,5} }, { {6,7,8},{9,10,11} }, { {12,13,14},{15,16,17} } , { {18,19,20},{21,22,23} } }"; array3d< int > array; - EXPECT_DEATH_IF_SUPPORTED( xmlWrapper::stringToInputVariable( array, input ), IGNORE_OUTPUT ); + ASSERT_THROW( xmlWrapper::stringToInputVariable( array, input ), std::invalid_argument ); } { input = " { {0,1,2},{3,4,5} }, { {6,7,8},{9,10,11} }, { {12,13,14},{15,16,17} } , { {18,19,20},{21,22,23} } "; array3d< int > array; - EXPECT_DEATH_IF_SUPPORTED( xmlWrapper::stringToInputVariable( array, input ), IGNORE_OUTPUT ); + ASSERT_THROW( xmlWrapper::stringToInputVariable( array, input ), std::invalid_argument ); } { input = " { { {,1,2},{3,4,5} }, { {6,7,8},{9,10,11} }, { {12,13,14},{15,16,17} } , { {18,19,20},{21,22,23} } }"; array3d< int > array; - EXPECT_DEATH_IF_SUPPORTED( xmlWrapper::stringToInputVariable( array, input ), IGNORE_OUTPUT ); + ASSERT_THROW( xmlWrapper::stringToInputVariable( array, input ), std::invalid_argument ); } { input = " { { {},{3,4,5} }, { {6,7,8},{9,10,11} }, { {12,13,14},{15,16,17} } , { {18,19,20},{21,22,23} } }"; array3d< int > array; - EXPECT_DEATH_IF_SUPPORTED( xmlWrapper::stringToInputVariable( array, input ), IGNORE_OUTPUT ); + ASSERT_THROW( xmlWrapper::stringToInputVariable( array, input ), std::invalid_argument ); } { input = " { { {0,1,2}}{ } }"; array3d< int > array; - EXPECT_DEATH_IF_SUPPORTED( xmlWrapper::stringToInputVariable( array, input ), IGNORE_OUTPUT ); + ASSERT_THROW( xmlWrapper::stringToInputVariable( array, input ), std::invalid_argument ); } From 4524704fd3ab22b2e64a91b9c5acf94215e930dd Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Mon, 27 Mar 2023 14:09:14 +0200 Subject: [PATCH 04/85] Changed the regexs so the schema detects more errors --- src/coreComponents/common/DataTypes.hpp | 21 +++++++++++++++------ src/coreComponents/schema/schema.xsd | 14 +++++++------- src/coreComponents/schema/schema.xsd.other | 10 +++++----- 3 files changed, 27 insertions(+), 18 deletions(-) diff --git a/src/coreComponents/common/DataTypes.hpp b/src/coreComponents/common/DataTypes.hpp index 12209038de6..0b048c8ff00 100644 --- a/src/coreComponents/common/DataTypes.hpp +++ b/src/coreComponents/common/DataTypes.hpp @@ -643,8 +643,17 @@ class rtTypes // Note: the xsd regex implementation does not allow an empty branch, so use allow whitespace at the end string rr = "[+-]?[\\d]*([\\d]\\.?|\\.[\\d])[\\d]*([eE][-+]?[\\d]+|\\s*)"; - // Regex to match a string that does not contain the characters ,{} - string rs = "[^,\\{\\}]*"; + // Regex to match a string that can't be empty and does not contain any whitespaces nor the characters ,{} + string rs = "[^,\\{\\}\\s]+\\s*"; + + // Regex to match a string that does not contain any whitespaces nor the characters ,{} + string rse = "[^,\\{\\}\\s]*\\s*"; + + // Regex to match a path: a string that can't be empty and does not contain any space nor the characters *?<>|:", + string rp = "[^*?<>\\|:\";,\\s]+\\s*";//"[^*?\\<\\>\\|:\\",\\s]+\\s*"; + + // Regex to match a path: a string that does not contain any space nor the characters *?<>|:", + string rpe = "[^*?<>\\|:\";,\\s]*\\s*";//"[^*\\?<\\>\\|:\\",\\s]*\\s*"; // Regex to match a R1Tensor string r1 = "\\s*\\{\\s*(" + rr + ",\\s*){2}" + rr + "\\s*\\}"; @@ -679,11 +688,11 @@ class rtTypes {"real32_array3d", constructArrayRegex( rr, 3 )}, {"real64_array3d", constructArrayRegex( rr, 3 )}, {"real64_array4d", constructArrayRegex( rr, 4 )}, - {"string", rs}, - {"path", rs}, + {"string", rse}, + {"path", rpe}, {"string_array", constructArrayRegex( rs, 1 )}, - {"path_array", constructArrayRegex( rs, 1 )}, - {"mapPair", rs}, + {"path_array", constructArrayRegex( rp, 1 )}, + {"mapPair", rse}, {"geosx_dataRepository_PlotLevel", ri} }; }; diff --git a/src/coreComponents/schema/schema.xsd b/src/coreComponents/schema/schema.xsd index 314aa042fa1..fdd67f9b84c 100644 --- a/src/coreComponents/schema/schema.xsd +++ b/src/coreComponents/schema/schema.xsd @@ -85,17 +85,17 @@ - + - + - + @@ -145,12 +145,12 @@ - + - + @@ -2288,13 +2288,13 @@ Equal to 1 for surface conditions, and to 0 for reservoir conditions--> None - Add no stabilization to mass equation, Global - Add stabilization to all faces, Local - Add stabilization only to interiors of macro elements.--> - + - + diff --git a/src/coreComponents/schema/schema.xsd.other b/src/coreComponents/schema/schema.xsd.other index 01d60bc4728..61e2ada4e47 100644 --- a/src/coreComponents/schema/schema.xsd.other +++ b/src/coreComponents/schema/schema.xsd.other @@ -85,17 +85,17 @@ - + - + - + @@ -145,12 +145,12 @@ - + - + From c36a052cd7056724175a2d842cb334df31138d91 Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Mon, 27 Mar 2023 16:27:25 +0200 Subject: [PATCH 05/85] updating LvArray submodule --- src/coreComponents/LvArray | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreComponents/LvArray b/src/coreComponents/LvArray index 75b707a6994..9dd078b54a8 160000 --- a/src/coreComponents/LvArray +++ b/src/coreComponents/LvArray @@ -1 +1 @@ -Subproject commit 75b707a6994e8e36f20ba852a7bfc1ee769ca92c +Subproject commit 9dd078b54a8d3afefde70af1f9b32e503d371de7 From 081939cd9b4e11df0fa8c57809716649de8d98b3 Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Tue, 28 Mar 2023 16:06:21 +0200 Subject: [PATCH 06/85] Adding documentation --- src/coreComponents/dataRepository/WrapperBase.hpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/coreComponents/dataRepository/WrapperBase.hpp b/src/coreComponents/dataRepository/WrapperBase.hpp index 38f3a9e158e..c0c6544510f 100644 --- a/src/coreComponents/dataRepository/WrapperBase.hpp +++ b/src/coreComponents/dataRepository/WrapperBase.hpp @@ -612,6 +612,11 @@ class WrapperBase /// @endcond + /** + * @brief Helper method to process an exception that has been thrown during xml parsing. + * @param ex The caught exception. + * @param targetNode The node from which this Group is interpreted. + */ void processInputException( std::exception const & ex, xmlWrapper::xmlNode const & targetNode ) const; protected: From a123a15d677f1d070b7440cd8a77083552c23ec3 Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Thu, 6 Apr 2023 17:36:14 +0200 Subject: [PATCH 07/85] added C++17 string_view comments --- src/coreComponents/common/DataTypes.hpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/coreComponents/common/DataTypes.hpp b/src/coreComponents/common/DataTypes.hpp index 0b048c8ff00..5a1eccca9ba 100644 --- a/src/coreComponents/common/DataTypes.hpp +++ b/src/coreComponents/common/DataTypes.hpp @@ -629,9 +629,11 @@ class rtTypes // Define the component regexes: // Regex to match an unsigned int (123, etc.) + // TODO c++17: Move to static constexpr std::string_view string ru = "[\\d]+"; // Regex to match an signed int (-123, 455, +789, etc.) + // TODO c++17: Move to static constexpr std::string_view string ri = "[+-]?[\\d]+"; // Regex to match a float (1, +2.3, -.4, 5.6e7, 8E-9, etc.) @@ -641,24 +643,31 @@ class rtTypes // [\\d]* matches any number of numbers following the decimal // ([eE][-+]?[\\d]+|\\s*) matches an optional scientific notation number // Note: the xsd regex implementation does not allow an empty branch, so use allow whitespace at the end + // TODO c++17: Move to static constexpr std::string_view string rr = "[+-]?[\\d]*([\\d]\\.?|\\.[\\d])[\\d]*([eE][-+]?[\\d]+|\\s*)"; // Regex to match a string that can't be empty and does not contain any whitespaces nor the characters ,{} + // TODO c++17: Move to static constexpr std::string_view string rs = "[^,\\{\\}\\s]+\\s*"; // Regex to match a string that does not contain any whitespaces nor the characters ,{} + // TODO c++17: Move to static constexpr std::string_view string rse = "[^,\\{\\}\\s]*\\s*"; // Regex to match a path: a string that can't be empty and does not contain any space nor the characters *?<>|:", - string rp = "[^*?<>\\|:\";,\\s]+\\s*";//"[^*?\\<\\>\\|:\\",\\s]+\\s*"; + // TODO c++17: Move to static constexpr std::string_view + string rp = "[^*?<>\\|:\";,\\s]+\\s*"; // Regex to match a path: a string that does not contain any space nor the characters *?<>|:", - string rpe = "[^*?<>\\|:\";,\\s]*\\s*";//"[^*\\?<\\>\\|:\\",\\s]*\\s*"; + // TODO c++17: Move to static constexpr std::string_view + string rpe = "[^*?<>\\|:\";,\\s]*\\s*"; // Regex to match a R1Tensor + // TODO c++17: Move to static constexpr std::string_view string r1 = "\\s*\\{\\s*(" + rr + ",\\s*){2}" + rr + "\\s*\\}"; // Regex to match a R2SymTensor + // TODO c++17: Move to static constexpr std::string_view string r2s = "\\s*\\{\\s*(" + rr + ",\\s*){5}" + rr + "\\s*\\}"; // Build master list of regexes From 0b1cd21818201e3961675acbd90ad5ee808ee05a Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Thu, 20 Apr 2023 11:04:26 +0200 Subject: [PATCH 08/85] - Added the ability to know the line of xml nodes & attributes - Added SourceContext to know where Groups/Wrappers has been declared - Added a test for the node & attribute line infos --- .../unitTests/testDruckerPrager.cpp | 4 +- .../unitTests/testElasticIsotropic.cpp | 2 +- .../unitTests/testModifiedCamClay.cpp | 2 +- .../dataRepository/CMakeLists.txt | 16 +- src/coreComponents/dataRepository/Group.cpp | 50 ++-- src/coreComponents/dataRepository/Group.hpp | 28 ++- .../dataRepository/SourceContext.cpp | 113 +++++++++ .../dataRepository/SourceContext.hpp | 205 ++++++++++++++++ src/coreComponents/dataRepository/Wrapper.hpp | 6 +- .../dataRepository/WrapperBase.cpp | 13 +- .../dataRepository/WrapperBase.hpp | 31 ++- .../dataRepository/xmlWrapper.cpp | 232 +++++++++++++++++- .../dataRepository/xmlWrapper.hpp | 209 ++++++++++++++-- .../mainInterface/ProblemManager.cpp | 15 +- .../mainInterface/ProblemManager.hpp | 4 +- src/coreComponents/schema/schemaUtilities.cpp | 2 +- .../constitutiveTests/testDamage.cpp | 2 +- .../fluidFlowTests/testCompFlowUtils.hpp | 6 +- .../fluidFlowTests/testSingleFlowUtils.hpp | 6 +- .../testDofManagerUtils.hpp | 4 +- .../meshTests/testMeshGeneration.cpp | 4 +- .../unitTests/meshTests/testVTKImport.cpp | 2 +- .../testConformingVirtualElementOrder1.cpp | 8 +- .../unitTests/xmlTests/testXMLFile.cpp | 197 +++++++++++++++ 24 files changed, 1069 insertions(+), 92 deletions(-) create mode 100644 src/coreComponents/dataRepository/SourceContext.cpp create mode 100644 src/coreComponents/dataRepository/SourceContext.hpp diff --git a/src/coreComponents/constitutive/unitTests/testDruckerPrager.cpp b/src/coreComponents/constitutive/unitTests/testDruckerPrager.cpp index 5ce0c24b731..4b81ecb5bc8 100644 --- a/src/coreComponents/constitutive/unitTests/testDruckerPrager.cpp +++ b/src/coreComponents/constitutive/unitTests/testDruckerPrager.cpp @@ -82,7 +82,7 @@ void testDruckerPragerDriver() } xmlWrapper::xmlNode xmlConstitutiveNode = xmlDocument.child( "Constitutive" ); - constitutiveManager.processInputFileRecursive( xmlConstitutiveNode ); + constitutiveManager.processInputFileRecursive( xmlDocument, xmlConstitutiveNode ); constitutiveManager.postProcessInputRecursive(); localIndex constexpr numElem = 2; @@ -198,7 +198,7 @@ void testDruckerPragerExtendedDriver() } xmlWrapper::xmlNode xmlConstitutiveNode = xmlDocument.child( "Constitutive" ); - constitutiveManager.processInputFileRecursive( xmlConstitutiveNode ); + constitutiveManager.processInputFileRecursive( xmlDocument, xmlConstitutiveNode ); constitutiveManager.postProcessInputRecursive(); localIndex constexpr numElem = 2; diff --git a/src/coreComponents/constitutive/unitTests/testElasticIsotropic.cpp b/src/coreComponents/constitutive/unitTests/testElasticIsotropic.cpp index 8eef05773df..44bbd2e758b 100644 --- a/src/coreComponents/constitutive/unitTests/testElasticIsotropic.cpp +++ b/src/coreComponents/constitutive/unitTests/testElasticIsotropic.cpp @@ -79,7 +79,7 @@ TEST( ElasticIsotropicTests, testStateUpdatePoint ) } xmlWrapper::xmlNode xmlConstitutiveNode = xmlDocument.child( "Constitutive" ); - constitutiveManager.processInputFileRecursive( xmlConstitutiveNode ); + constitutiveManager.processInputFileRecursive( xmlDocument, xmlConstitutiveNode ); constitutiveManager.postProcessInputRecursive(); dataRepository::Group disc( "discretization", &rootGroup ); diff --git a/src/coreComponents/constitutive/unitTests/testModifiedCamClay.cpp b/src/coreComponents/constitutive/unitTests/testModifiedCamClay.cpp index 1df6e2a271a..36856cb9f4d 100644 --- a/src/coreComponents/constitutive/unitTests/testModifiedCamClay.cpp +++ b/src/coreComponents/constitutive/unitTests/testModifiedCamClay.cpp @@ -95,7 +95,7 @@ void testModifiedCamClayDriver() } xmlWrapper::xmlNode xmlConstitutiveNode = xmlDocument.child( "Constitutive" ); - constitutiveManager.processInputFileRecursive( xmlConstitutiveNode ); + constitutiveManager.processInputFileRecursive( xmlDocument, xmlConstitutiveNode ); constitutiveManager.postProcessInputRecursive(); localIndex constexpr numElem = 2; diff --git a/src/coreComponents/dataRepository/CMakeLists.txt b/src/coreComponents/dataRepository/CMakeLists.txt index 6a2770fea62..869ddd94751 100644 --- a/src/coreComponents/dataRepository/CMakeLists.txt +++ b/src/coreComponents/dataRepository/CMakeLists.txt @@ -22,12 +22,13 @@ set( dataRepository_headers WrapperBase.hpp wrapperHelpers.hpp xmlWrapper.hpp - ) - -# -# Specify all sources -# -set( dataRepository_sources + SourceContext.hpp + ) + + # + # Specify all sources + # + set( dataRepository_sources BufferOpsDevice.cpp ConduitRestart.cpp ExecutableGroup.cpp @@ -35,7 +36,8 @@ set( dataRepository_sources Utilities.cpp WrapperBase.cpp xmlWrapper.cpp - ) + SourceContext.cpp + ) set( dependencyList codingUtilities ) diff --git a/src/coreComponents/dataRepository/Group.cpp b/src/coreComponents/dataRepository/Group.cpp index a49340791a0..c24a6eacded 100644 --- a/src/coreComponents/dataRepository/Group.cpp +++ b/src/coreComponents/dataRepository/Group.cpp @@ -13,11 +13,13 @@ */ // Source includes +#include #include "Group.hpp" #include "ConduitRestart.hpp" #include "codingUtilities/StringUtilities.hpp" #include "codingUtilities/Utilities.hpp" #include "common/TimingMacros.hpp" +#include "SourceContext.hpp" #if defined(GEOSX_USE_PYGEOSX) #include "python/PyGroupType.hpp" #endif @@ -47,7 +49,8 @@ Group::Group( string const & name, m_logLevel( 0 ), m_restart_flags( RestartFlags::WRITE_AND_READ ), m_input_flags( InputFlags::INVALID ), - m_conduitNode( rootNode[ name ] ) + m_conduitNode( rootNode[ name ] ), + m_sourceContext( std::make_unique< GroupContext >( *this ) ) {} Group::~Group() @@ -131,9 +134,10 @@ string Group::getPath() const return noProblem.empty() ? "/" : noProblem; } -void Group::processInputFileRecursive( xmlWrapper::xmlNode & targetNode ) +void Group::processInputFileRecursive( xmlWrapper::xmlDocument & xmlDocument, + xmlWrapper::xmlNode & targetNode ) { - xmlWrapper::addIncludedXML( targetNode ); + xmlDocument.addIncludedXML( targetNode ); // Handle the case where the node was imported from a different input file // Set the path prefix to make sure all relative Path variables are interpreted correctly @@ -141,8 +145,7 @@ void Group::processInputFileRecursive( xmlWrapper::xmlNode & targetNode ) xmlWrapper::xmlAttribute filePath = targetNode.attribute( xmlWrapper::filePathString ); if( filePath ) { - Path::pathPrefix() = splitPath( filePath.value() ).first; - targetNode.remove_attribute( filePath ); + Path::pathPrefix() = getAbsolutePath( splitPath( filePath.value() ).first ); } // Loop over the child nodes of the targetNode @@ -158,9 +161,14 @@ void Group::processInputFileRecursive( xmlWrapper::xmlNode & targetNode ) else { // Make sure child names are not duplicated - GEOS_ERROR_IF( std::find( childNames.begin(), childNames.end(), childName ) != childNames.end(), - GEOS_FMT( "Error: An XML block cannot contain children with duplicated names ({}/{}). ", - getPath(), childName ) ); + if( std::find( childNames.begin(), childNames.end(), childName ) != childNames.end() ) + { + GEOS_ERROR( GEOS_FMT( "Error: An XML block cannot contain children with duplicated names.\n" + "Error detected at node {} with name = {} ({}:l.{})", + childNode.path(), childName, xmlDocument.getFilePath(), + xmlDocument.getNodePosition( childNode ).line ) ); + } + childNames.emplace_back( childName ); } @@ -172,22 +180,31 @@ void Group::processInputFileRecursive( xmlWrapper::xmlNode & targetNode ) } if( newChild != nullptr ) { - newChild->processInputFileRecursive( childNode ); + newChild->processInputFileRecursive( xmlDocument, childNode ); } } - processInputFile( targetNode ); + processInputFile( xmlDocument, targetNode ); // Restore original prefix once the node is processed Path::pathPrefix() = oldPrefix; } -void Group::processInputFile( xmlWrapper::xmlNode const & targetNode ) +void Group::processInputFile( xmlWrapper::xmlDocument const & xmlDocument, + xmlWrapper::xmlNode const & targetNode ) { + xmlWrapper::xmlNodePos nodePos = xmlDocument.getNodePosition( targetNode ); + + if( nodePos.isFound() ) + { + m_sourceContext = std::make_unique< FileContext >( *this, nodePos, targetNode.name() ); + } + + std::set< string > processedAttributes; for( std::pair< string const, WrapperBase * > & pair : m_wrappers ) { - if( pair.second->processInputFile( targetNode ) ) + if( pair.second->processInputFile( targetNode, nodePos ) ) { processedAttributes.insert( pair.first ); } @@ -196,13 +213,14 @@ void Group::processInputFile( xmlWrapper::xmlNode const & targetNode ) for( xmlWrapper::xmlAttribute attribute : targetNode.attributes() ) { string const attributeName = attribute.name(); - if( attributeName != "name" && attributeName != "xmlns:xsi" && attributeName != "xsi:noNamespaceSchemaLocation" ) + if( !xmlWrapper::isFileMetadataAttribute( attributeName ) ) { GEOS_THROW_IF( processedAttributes.count( attributeName ) == 0, - GEOS_FMT( "XML Node '{}' with name='{}' contains unused attribute '{}'.\n" + GEOS_FMT( "XML Node at '{}' with name={} contains unused attribute '{}'.\n" "Valid attributes are:\n{}\nFor more details, please refer to documentation at:\n" "http://geosx-geosx.readthedocs-hosted.com/en/latest/docs/sphinx/userGuide/Index.html", - targetNode.path(), targetNode.attribute( "name" ).value(), attributeName, dumpInputOptions() ), + targetNode.path(), m_sourceContext->toString(), attributeName, + dumpInputOptions() ), InputError ); } } @@ -354,7 +372,7 @@ localIndex Group::packImpl( buffer_unit_type * & buffer, } else { - GEOS_ERROR( "Wrapper " << wrapperName << " not found in Group " << getName() << "." ); + GEOS_ERROR( "Wrapper " << wrapperName << " not found in Group " << getSourceContext() << "." ); } } diff --git a/src/coreComponents/dataRepository/Group.hpp b/src/coreComponents/dataRepository/Group.hpp index d2dcb489b7a..bba005abeb9 100644 --- a/src/coreComponents/dataRepository/Group.hpp +++ b/src/coreComponents/dataRepository/Group.hpp @@ -26,6 +26,7 @@ #include "RestartFlags.hpp" #include "Wrapper.hpp" #include "xmlWrapper.hpp" +#include "SourceContext.hpp" #include @@ -323,7 +324,7 @@ class Group T & getGroup( KEY const & key ) { Group * const child = m_subGroups[ key ]; - GEOS_THROW_IF( child == nullptr, "Group " << getPath() << " doesn't have a child " << key, std::domain_error ); + GEOS_THROW_IF( child == nullptr, "Group " << getSourceContext() << " doesn't have a child " << key, std::domain_error ); return dynamicCast< T & >( *child ); } @@ -334,7 +335,7 @@ class Group T const & getGroup( KEY const & key ) const { Group const * const child = m_subGroups[ key ]; - GEOS_THROW_IF( child == nullptr, "Group " << getPath() << " doesn't have a child " << key, std::domain_error ); + GEOS_THROW_IF( child == nullptr, "Group " << getSourceContext() << " doesn't have a child " << key, std::domain_error ); return dynamicCast< T const & >( *child ); } @@ -725,7 +726,8 @@ class Group * file and put them into the wrapped values for this group. * @param[in] targetNode the XML node that to extract input values from. */ - void processInputFileRecursive( xmlWrapper::xmlNode & targetNode ); + void processInputFileRecursive( xmlWrapper::xmlDocument & xmlDocument, + xmlWrapper::xmlNode & targetNode ); /** * @brief Recursively call postProcessInput() to apply post processing after @@ -1047,7 +1049,7 @@ class Group WrapperBase const & getWrapperBase( KEY const & key ) const { WrapperBase const * const wrapper = m_wrappers[ key ]; - GEOS_THROW_IF( wrapper == nullptr, "Group " << getPath() << " doesn't have a child " << key, std::domain_error ); + GEOS_THROW_IF( wrapper == nullptr, "Group " << getSourceContext() << " doesn't have a child " << key, std::domain_error ); return *wrapper; } @@ -1058,7 +1060,7 @@ class Group WrapperBase & getWrapperBase( KEY const & key ) { WrapperBase * const wrapper = m_wrappers[ key ]; - GEOS_THROW_IF( wrapper == nullptr, "Group " << getPath() << " doesn't have a child " << key, std::domain_error ); + GEOS_THROW_IF( wrapper == nullptr, "Group " << getSourceContext() << " doesn't have a child " << key, std::domain_error ); return *wrapper; } @@ -1248,6 +1250,12 @@ class Group */ string getPath() const; + /** + * @return A SourceContext object that can helps to contextualize this Group. + */ + SourceContext const & getSourceContext() const + { return *m_sourceContext; } + /** * @brief Access the group's parent. * @return reference to parent Group @@ -1255,7 +1263,7 @@ class Group */ Group & getParent() { - GEOS_THROW_IF( m_parent == nullptr, "Group at " << getPath() << " does not have a parent.", std::domain_error ); + GEOS_THROW_IF( m_parent == nullptr, "Group at " << getSourceContext() << " does not have a parent.", std::domain_error ); return *m_parent; } @@ -1264,7 +1272,7 @@ class Group */ Group const & getParent() const { - GEOS_THROW_IF( m_parent == nullptr, "Group at " << getPath() << " does not have a parent.", std::domain_error ); + GEOS_THROW_IF( m_parent == nullptr, "Group at " << getSourceContext() << " does not have a parent.", std::domain_error ); return *m_parent; } @@ -1445,7 +1453,8 @@ class Group * wrapped values for this group. * @param[in] targetNode the XML node that to extract input values from. */ - virtual void processInputFile( xmlWrapper::xmlNode const & targetNode ); + virtual void processInputFile( xmlWrapper::xmlDocument const & xmlDocument, + xmlWrapper::xmlNode const & targetNode ); Group const & getBaseGroupByPath( string const & path ) const; @@ -1508,6 +1517,9 @@ class Group /// Reference to the conduit::Node that mirrors this group conduit::Node & m_conduitNode; + /// A SourceContext object that can helps to contextualize this Group. + std::unique_ptr< SourceContext > m_sourceContext; + }; /** diff --git a/src/coreComponents/dataRepository/SourceContext.cpp b/src/coreComponents/dataRepository/SourceContext.cpp new file mode 100644 index 00000000000..b9e45479e79 --- /dev/null +++ b/src/coreComponents/dataRepository/SourceContext.cpp @@ -0,0 +1,113 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2018-2020 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2020 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2018-2020 TotalEnergies + * Copyright (c) 2019- GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +/** + * @file SourceContext.cpp + */ + +//#include "codingUtilities/StringUtilities.hpp" +#include "Group.hpp" +#include "WrapperBase.hpp" +#include "../mainInterface/ProblemManager.hpp" + +namespace geos +{ +namespace dataRepository +{ + + +SourceContext::SourceContext( string const & objectName, bool const isFileContext ): + m_objectName( objectName ), + m_isFileContext( isFileContext ) +{} + +std::ostream & operator<<( std::ostream & os, SourceContext const & sc ) +{ + os << sc.toString(); + return os; +} + + +GroupContext::GroupContext( Group & group, string const & objectName ): + SourceContext( objectName, false ), + m_group( group ) +{} +GroupContext::GroupContext( Group & group ): + GroupContext( group, group.getName() ) +{} + +string GroupContext::toString() const +{ + // it would be possible to insert the FileContext::toString() of parent objects when it exists, but is it relevant ? + return m_group.getPath(); +} + + +WrapperContext::WrapperContext( WrapperBase & wrapper ): + GroupContext( wrapper.getParent(), wrapper.getName() ) +{} + +string WrapperContext::toString() const +{ + // if possible, we show the FileContext of the parent. + if( m_group.getSourceContext().isFileContext() ) + { + return m_group.getSourceContext().toString() + ", attribute " + m_objectName; + } + else + { + // it would be possible to insert the FileContext::toString() of parent objects when it exists, but is it relevant ? + return m_group.getPath() + "/" + m_objectName; + } +} + + +FileContext::FileContext( Group & group, xmlWrapper::xmlNodePos const & nodePos, + string const & nodeTagName ): + SourceContext( group.getName(), true ), + m_typeName( nodeTagName ), + m_filePath( nodePos.filePath ), + m_line( nodePos.line ), + m_offsetInLine( nodePos.offsetInLine ), + m_offset( nodePos.offset ) +{} + +FileContext::FileContext( WrapperBase & wrapper, xmlWrapper::xmlAttributePos const & attPos ): + SourceContext( wrapper.getParent().getName() + "/" + wrapper.getName(), true ), + m_typeName( wrapper.getName() ), + m_filePath( attPos.filePath ), + m_line( attPos.line ), + m_offsetInLine( attPos.offsetInLine ), + m_offset( attPos.offset ) +{} + +string FileContext::toString() const +{ + std::ostringstream oss; + oss << m_objectName << " (" << m_filePath; + if( m_line != xmlWrapper::xmlDocument::npos ) + { + oss << ": l." << m_line << ")"; + } + else + { + // line hasn't been found, filename is probably wrong too, we just output the character offset. + oss << ": offset " << m_offset << ")"; + } + return oss.str(); +} + + +} /* namespace dataRepository */ +} /* namespace geos */ diff --git a/src/coreComponents/dataRepository/SourceContext.hpp b/src/coreComponents/dataRepository/SourceContext.hpp new file mode 100644 index 00000000000..dc00e488882 --- /dev/null +++ b/src/coreComponents/dataRepository/SourceContext.hpp @@ -0,0 +1,205 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2018-2020 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2020 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2018-2020 TotalEnergies + * Copyright (c) 2019- GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +/** + * @file SourceContext.hpp + */ + +#ifndef GEOS_DATAREPOSITORY_SOURCECONTEXT_HPP_ +#define GEOS_DATAREPOSITORY_SOURCECONTEXT_HPP_ + +#include "common/DataTypes.hpp" +#include "common/Logger.hpp" +#include "xmlWrapper.hpp" + +namespace geos +{ + +namespace dataRepository +{ + +class Group; +class WrapperBase; + +/// This abstract class stores data that helps to retrieve from where a object comes from : +/// - from which position in a file (if applicable), see FileContext, +/// - where it is located in the data hierarchy, see GroupContext and WrapperContext. +/// Typically, the target object contain an unique_ptr< SourceContext > instance of this class. +class SourceContext +{ +public: + + /** + * @brief Construct a new SourceContext object. + * @param objectName the target object name + * @param isFileContext true if this Context is a FileContext (see isFileContext for more infos) + */ + SourceContext( string const & objectName, bool isFileContext ); + + /** + * @return A string that mention all the known informations to retrieve from where the target + * object comes from. + */ + virtual string toString() const = 0; + + string getObjectName() const + { return m_objectName; } + + /** + * @brief In some cases, we need to know if a SourceContext is from a file. It means that it + * is a more user-friendly information compared to other SourceContext classes. + * @return true if the context is from a file. + */ + bool isFileContext() const + { return m_isFileContext; } + + /** + * @brief Insert toString() result in a stream. + */ + friend std::ostream & operator<<( std::ostream & os, const SourceContext & dt ); + +protected: + + /// see getObjectName() + string const m_objectName; + + /// see isFileContext() + bool const m_isFileContext; + +}; + +/// Helps to know where a Group is in the hierarchy. +/// See SourceContext class for more infos. +class GroupContext : public SourceContext +{ +public: + + /** + * @brief Construct a new GroupContext object + * @param group see getGroup() + */ + GroupContext( Group & group ); + + /** + * @brief Get the target Group object (wrapper's parent in case of a WrapperContext). + */ + Group & getGroup() const; + + /** + * @copydoc SourceContext::toString() + */ + virtual string toString() const; + +protected: + + /** + * @brief Construct a new GroupContext object + * @param group see getGroup() + * @param objectName Target object name. + */ + GroupContext( Group & group, string const & objectName ); + + /// see getGroup() + Group & m_group; + +}; + +/// Helps to know the source context of a Wrapper in the hierarchy, or in the source file, if possible. +/// See SourceContext class for more infos. +class WrapperContext final : public GroupContext +{ +public: + + /** + * @brief Construct a new WrapperContext object + */ + WrapperContext( WrapperBase & wrapper ); + + /** + * @copydoc SourceContext::toString() + */ + virtual string toString() const; + +}; + +/// Helps to know from where a Group or a Wrapper has been declared in the source file. +/// See SourceContext class for more infos. +class FileContext final : public SourceContext +{ +public: + + /** + * @brief Construct the file context of a Group from an xml node. + */ + FileContext( Group & group, xmlWrapper::xmlNodePos const & nodePos, string const & nodeTagName ); + /** + * @brief Construct the file context of a Group from an xml attribute. + */ + FileContext( WrapperBase & wrapper, xmlWrapper::xmlAttributePos const & attPos ); + + virtual string toString() const; + + /** + * @brief Get the type name in the source file (XML node tag name / attribute name). + */ + string getTypeName() const + { return m_typeName; } + + /** + * @brief Get the source file path where the target object has been declared. + */ + string getFilePath() const + { return m_filePath; } + + /** + * @brief Get the line (starting from 1) where the target object has been declared in the source + * file. + */ + size_t getLine() const + { return m_line; } + + /** + * @brief Get the character offset in the line (starting from 1) where the target object has been declared in the source + * file. + */ + size_t getOffsetInLine() const + { return m_offsetInLine; } + + /** + * @brief Get the character offset (starting from 0) in the source file path where the target object has been + * declared. + */ + size_t getOffset() const + { return m_offset; } + +protected: + + /// see getTypeName() + string const m_typeName; + /// see getFilePath() + string const m_filePath; + /// see getLine() + size_t const m_line; + /// see getLineOffset() + size_t const m_offsetInLine; + /// see getOffset() + size_t const m_offset; + +}; + +} /* namespace dataRepository */ + +} /* namespace geos */ + +#endif /* GEOS_DATAREPOSITORY_INPUTFLAGS_HPP_ */ diff --git a/src/coreComponents/dataRepository/Wrapper.hpp b/src/coreComponents/dataRepository/Wrapper.hpp index 42ff5448790..e043c3817f8 100644 --- a/src/coreComponents/dataRepository/Wrapper.hpp +++ b/src/coreComponents/dataRepository/Wrapper.hpp @@ -606,7 +606,8 @@ class Wrapper final : public WrapperBase return ss.str(); } - virtual bool processInputFile( xmlWrapper::xmlNode const & targetNode ) override + virtual bool processInputFile( xmlWrapper::xmlNode const & targetNode, + xmlWrapper::xmlNodePos const & nodePos ) override { InputFlags const inputFlag = getInputFlag(); if( inputFlag >= InputFlags::OPTIONAL ) @@ -632,6 +633,9 @@ class Wrapper final : public WrapperBase getDefaultValueStruct() ); } + if( m_successfulReadFromInput ) + createSourceContext( nodePos ); + return true; } diff --git a/src/coreComponents/dataRepository/WrapperBase.cpp b/src/coreComponents/dataRepository/WrapperBase.cpp index 7c23d04a687..6b3eda44012 100644 --- a/src/coreComponents/dataRepository/WrapperBase.cpp +++ b/src/coreComponents/dataRepository/WrapperBase.cpp @@ -18,6 +18,7 @@ #include "Group.hpp" #include "RestartFlags.hpp" +#include "SourceContext.hpp" namespace geos @@ -37,7 +38,8 @@ WrapperBase::WrapperBase( string const & name, m_successfulReadFromInput( false ), m_description(), m_registeringObjects(), - m_conduitNode( parent.getConduitNode()[ name ] ) + m_conduitNode( parent.getConduitNode()[ name ] ), + m_sourceContext( std::make_unique< WrapperContext >( *this ) ) {} @@ -104,6 +106,15 @@ int WrapperBase::setTotalviewDisplay() const } #endif +void WrapperBase::createSourceContext( xmlWrapper::xmlNodePos const & nodePos ) +{ + xmlWrapper::xmlAttributePos attPos = nodePos.getAttributeLine( m_name ); + if( nodePos.isFound() && attPos.isFound() ) + { + m_sourceContext = std::make_unique< FileContext >( *this, attPos ); + } +} + } } /* namespace geos */ diff --git a/src/coreComponents/dataRepository/WrapperBase.hpp b/src/coreComponents/dataRepository/WrapperBase.hpp index a164f096730..00ad29db460 100644 --- a/src/coreComponents/dataRepository/WrapperBase.hpp +++ b/src/coreComponents/dataRepository/WrapperBase.hpp @@ -45,6 +45,7 @@ namespace dataRepository { class Group; +class SourceContext; /** * @class WrapperBase @@ -181,7 +182,8 @@ class WrapperBase * @param targetNode the xml node to initialize from. * @return True iff the wrapper initialized itself from the file. */ - virtual bool processInputFile( xmlWrapper::xmlNode const & targetNode ) = 0; + virtual bool processInputFile( xmlWrapper::xmlNode const & targetNode, + xmlWrapper::xmlNodePos const & nodePos ) = 0; /** * @brief Push the data in the wrapper into a Conduit blueprint field. @@ -412,6 +414,24 @@ class WrapperBase */ string getPath() const; + /** + * @return A SourceContext object that can helps to contextualize this Group. + */ + SourceContext const & getSourceContext() const + { return *m_sourceContext; } + + /** + * @brief Return the group that contains this Wrapper. + */ + Group & getParent() + { return *m_parent; } + + /** + * @copydoc getParent() + */ + Group const & getParent() const + { return *m_parent; } + /** * @brief Set the InputFlag of the wrapper. * @param input the new InputFlags value @@ -612,6 +632,12 @@ class WrapperBase /// @endcond + /** + * @brief Sets the m_sourceContext to a FileContext by retrieving the attribute file line. + * @param nodePos the xml node position of the node containing this wrapper source attribute. + */ + void createSourceContext( xmlWrapper::xmlNodePos const & nodePos ); + protected: /// Name of the object that is being wrapped @@ -644,6 +670,9 @@ class WrapperBase /// A reference to the corresponding conduit::Node. conduit::Node & m_conduitNode; + /// A SourceContext object that can helps to contextualize this Group. + std::unique_ptr< SourceContext > m_sourceContext; + private: /** diff --git a/src/coreComponents/dataRepository/xmlWrapper.cpp b/src/coreComponents/dataRepository/xmlWrapper.cpp index d5607ff3f08..b6dd5046e73 100644 --- a/src/coreComponents/dataRepository/xmlWrapper.cpp +++ b/src/coreComponents/dataRepository/xmlWrapper.cpp @@ -16,6 +16,8 @@ * @file xmlWrapper.cpp */ +#include + #include "xmlWrapper.hpp" #include "codingUtilities/StringUtilities.hpp" @@ -71,12 +73,36 @@ template void stringToInputVariable( Tensor< real32, 3 > & target, string const template void stringToInputVariable( Tensor< real64, 3 > & target, string const & inputValue ); template void stringToInputVariable( Tensor< real64, 6 > & target, string const & inputValue ); -void addIncludedXML( xmlNode & targetNode, int const level ) +/** + * @brief Adds the filePath and character offset infos on the node in filePathString + * and charOffsetString attributes. This function allow to keep track of the source + * filename & offset of each node. + * @param node the target node to add the informations on. + * @param filePath the absolute path of the xml file containing the node. + */ +void addNodeFileInfos( xmlNode node, string const & filePath ) +{ + // we keep the file path and the character offset on each node so we keep track of these + // informations, even if the nodes are manipulated within the xml hierarchy. + node.append_attribute( filePathString ).set_value( filePath.c_str() ); + node.append_attribute( charOffsetString ).set_value( node.offset_debug() ); + + for( xmlNode subNode : node.children() ) + { + addNodeFileInfos( subNode, filePath ); + } +} +/** + * @brief Returns true if the addNodeFileInfos() command has been called of the specified node. + */ +bool xmlDocument::hasNodeFileInfos() const +{ return !first_child().attribute( filePathString ).empty(); } + +void xmlDocument::addIncludedXML( xmlNode & targetNode, int const level ) { GEOS_THROW_IF( level > 100, "XML include level limit reached, please check input for include loops", InputError ); - xmlNode const rootNode = targetNode.root(); - string const currentFilePath = rootNode.child( filePathString ).attribute( filePathString ).value(); + string const currentFilePath = targetNode.attribute( filePathString ).value(); // Schema currently allows a single unique , but a non-validating file may include multiple for( xmlNode includedNode : targetNode.children( includedListTag ) ) @@ -98,9 +124,13 @@ void addIncludedXML( xmlNode & targetNode, int const level ) }(); xmlDocument includedXmlDocument; - xmlResult const result = includedXmlDocument.load_file( includedFilePath.c_str() ); - GEOS_THROW_IF( !result, GEOS_FMT( "Errors found while parsing included XML file {}\nDescription: {}\nOffset: {}", - includedFilePath, result.description(), result.offset ), InputError ); + xmlResult const result = includedXmlDocument.load_file( includedFilePath.c_str(), + hasNodeFileInfos() ); + GEOS_THROW_IF( !result, GEOS_FMT( "Errors found while parsing included XML file {}\n" + "Description: {}\nOffset: {}", + includedFilePath, result.description(), result.offset ), + InputError ); + // All included files must contain a root node that must match the target node. // Currently, schema only allows tags at the top level (inside ). @@ -111,20 +141,16 @@ void addIncludedXML( xmlNode & targetNode, int const level ) "Included document root does not match the including XML node", InputError ); // Process potential includes in the included file to allow nesting - includedXmlDocument.append_child( filePathString ).append_attribute( filePathString ).set_value( includedFilePath.c_str() ); addIncludedXML( includedRootNode, level + 1 ); // Add each top level tag of imported document to current // This may result in repeated XML blocks, which will be implicitly merged when processed for( xmlNode importedNode : includedRootNode.children() ) { - // Save path to current file on the node, in order to handle relative paths later - if( !importedNode.attribute( filePathString ) ) - { - importedNode.append_attribute( filePathString ).set_value( includedFilePath.c_str() ); - } targetNode.append_copy( importedNode ); } + + m_originalBuffers[includedXmlDocument.getFilePath()] = includedXmlDocument.getOriginalBuffer(); } } @@ -169,6 +195,188 @@ string buildMultipleInputXML( string_array const & inputFileList, return inputFileName; } +bool isFileMetadataAttribute( string const & name ) +{ + static const std::set< string > fileMetadataAttributes { + "name", "xmlns:xsi", "xsi:noNamespaceSchemaLocation", xmlWrapper::filePathString, xmlWrapper::charOffsetString + }; + return fileMetadataAttributes.find( name ) != fileMetadataAttributes.end(); +} + +const size_t xmlDocument::npos = string::npos; +size_t documentId=0; + +xmlDocument::xmlDocument(): + pugi::xml_document(), + m_rootFilePath( "CodeIncludedXML" + std::to_string( documentId++ ) ) +{} + +xmlResult xmlDocument::load_string( const pugi::char_t * contents, bool loadNodeFileInfos, + unsigned int options ) +{ + xmlResult result = pugi::xml_document::load_string( contents, options ); + + // keeping a copy of original buffer to allow line retrieval + if( loadNodeFileInfos ) + { + new (&m_originalBuffers) map< string, string >(); + m_originalBuffers[m_rootFilePath] = string( contents ); + + addNodeFileInfos( first_child(), m_rootFilePath ); + } + + return result; +} +xmlResult xmlDocument::load_file( const char * path, bool loadNodeFileInfos, + unsigned int options, pugi::xml_encoding encoding ) +{ + xmlResult result = pugi::xml_document::load_file( path, options, encoding ); + m_rootFilePath = getAbsolutePath( path ); + + // keeping a copy of original buffer to allow line retrieval + if( loadNodeFileInfos ) + { + std::ifstream t( path ); + std::stringstream buffer; + buffer << t.rdbuf(); + + new (&m_originalBuffers) map< string, string >(); + m_originalBuffers[m_rootFilePath] = string( buffer.str() ); + + addNodeFileInfos( first_child(), getAbsolutePath( m_rootFilePath ) ); + } + + return result; +} +xmlResult xmlDocument::load_buffer( const void * contents, size_t size, bool loadNodeFileInfos, + unsigned int options, pugi::xml_encoding encoding ) +{ + xmlResult result = pugi::xml_document::load_buffer( contents, size, options, encoding ); + + //keeping a copy of original buffer + if( loadNodeFileInfos ) + { + new (&m_originalBuffers) map< string, string >(); + m_originalBuffers[m_rootFilePath] = string( ( char const * )contents, size ); + + addNodeFileInfos( first_child(), m_rootFilePath ); + } + + return result; +} + +string const & xmlDocument::getFilePath() const +{ return m_rootFilePath; } + +string const & xmlDocument::getOriginalBuffer() const +{ return m_originalBuffers.find( m_rootFilePath )->second; } + +string const * xmlDocument::getOriginalBuffer( string const & filePath ) const +{ + map< string, string >::const_iterator it = m_originalBuffers.find( filePath ); + return it != m_originalBuffers.cend() ? &it->second : nullptr; +} + +map< string, string > const & xmlDocument::getOriginalBuffers() const +{ return m_originalBuffers; } + +xmlNodePos xmlDocument::getNodePosition( xmlNode const & node ) const +{ + size_t line = npos; + size_t offsetInLine = npos; + size_t offset = npos; + xmlAttribute filePathAtt = node.attribute( filePathString ); + xmlAttribute charOffsetAtt = node.attribute( charOffsetString ); + string filePath; + + if( filePathAtt && charOffsetAtt ) + { + filePath = string( filePathAtt.value() ); + offset = std::atoi( charOffsetAtt.value() ); + + auto sourceBuffer = m_originalBuffers.find( filePath ); + string nodeName = node.name(); + + if( sourceBuffer!=m_originalBuffers.end() && + offset > 0 && offset + nodeName.size() < sourceBuffer->second.size() && + sourceBuffer->second.substr( offset, nodeName.size() ) == nodeName ) + { + line = std::count( sourceBuffer->second.begin(), sourceBuffer->second.begin() + offset, '\n' ) + 1; + offsetInLine = offset - sourceBuffer->second.rfind( '\n', offset ); + } + else + { + std::ostringstream oss; + oss << "Node from file=" << filePath << ", path=" << node.path() << ", offset=" << offset; + oss << " could not be found: "; + oss << ( sourceBuffer == m_originalBuffers.end() ? + ( filePathAtt.empty() ? "Source file not found." : "Source file infos not loaded") : + ( offset > 0 && offset + nodeName.size() < sourceBuffer->second.size() ? + "The offset doesn't lead to this node." : + "Offset out of bounds." ) + ); + oss << "\nSource files list:"; + for( auto const & buffer : m_originalBuffers ) + oss << "\n " << buffer.first << " buffer size = " << std::to_string( buffer.second.size() ); + + throw InputError( oss.str() ); + } + } + + return xmlNodePos( *this, filePath, line, offsetInLine, offset ); +} + +xmlNodePos::xmlNodePos( xmlDocument const & document_, string const & filePath_, size_t line_, + size_t offsetInLine_, size_t offset_ ): + document( document_ ), + filePath( filePath_ ), + line( line_ ), + offsetInLine( offsetInLine_ ), + offset( offset_ ) +{} + +bool xmlNodePos::isFound() const +{ return line != xmlDocument::npos; } + +xmlAttributePos xmlNodePos::getAttributeLine( string const & attName ) const +{ + string const * buffer = document.getOriginalBuffer( filePath ); + size_t tagEnd = xmlDocument::npos; + size_t attOffset = xmlDocument::npos; + size_t attLine = xmlDocument::npos; + size_t attOffsetInLine = xmlDocument::npos; + + if( isFound() && buffer != nullptr && offset < buffer->size() ) + { + tagEnd = buffer->find( '>', offset ); + if( tagEnd != string::npos ) + { + std::smatch m; + // we search for a string which is the attribute name followed by an '=', eventually separated by spaces + if( std::regex_search( buffer->cbegin() + offset, buffer->cbegin() + tagEnd, + m, std::regex( attName + "\\s*=" ) ) ) + { + attOffset = m.position() + offset; + attLine = line + std::count( buffer->cbegin() + offset, buffer->cbegin() + attOffset, '\n' ); + attOffsetInLine = attOffset - buffer->rfind( '\n', attOffset ); + } + } + } + + return xmlAttributePos( filePath, attLine, attOffsetInLine, attOffset ); +} + +xmlAttributePos::xmlAttributePos( string const & filePath_, size_t line_, size_t offsetInLine_, + size_t offset_ ): + filePath( filePath_ ), + line( line_ ), + offsetInLine( offsetInLine_ ), + offset( offset_ ) +{} + +bool xmlAttributePos::isFound() const +{ return offset != xmlDocument::npos; } + } /* namespace xmlWrapper */ } /* namespace geos */ diff --git a/src/coreComponents/dataRepository/xmlWrapper.hpp b/src/coreComponents/dataRepository/xmlWrapper.hpp index ef4b525b627..8f24f6a7c8e 100644 --- a/src/coreComponents/dataRepository/xmlWrapper.hpp +++ b/src/coreComponents/dataRepository/xmlWrapper.hpp @@ -46,8 +46,6 @@ namespace geos */ namespace xmlWrapper { -/// Alias for the type of xml document. -using xmlDocument = pugi::xml_document; /// Alias for the type of the result from an xml parse attempt. using xmlResult = pugi::xml_parse_result; @@ -61,6 +59,186 @@ using xmlAttribute = pugi::xml_attribute; /// Alias for the type variant of an xml node. using xmlTypes = pugi::xml_node_type; +class xmlDocument; + +/// Stores the source file path, and position in the file of a xml attribute. +struct xmlAttributePos +{ + /// Path of the file containing this node + string filePath; + /// Line of this node in the file that contains it (starting from 1). + /// Equals to xmlDocument::npos if it couldn't be determined. + size_t const line; + /// Character offset of this node in the line that contains it (starting from 1) + /// Equals to xmlDocument::npos if it couldn't be determined. + size_t const offsetInLine; + /// Character offset of this node in the file that contains it (starting from 0) + /// Equals to xmlDocument::npos if it couldn't be determined. + size_t const offset; + + /** + * @brief Constructor of this struct. + */ + xmlAttributePos( string const & filePath, size_t line, size_t offsetInLine, size_t offset ); + /** + * @return false if the position is undetermined. + */ + bool isFound() const; +}; + +/// Stores the source document, file path, and position in the file of a xml node. +/// It can help to retrieve the position (xmlAttributePos) of a xml attribute. +struct xmlNodePos +{ + /// Reference of the main xmlDocument that contains all original file buffers. + xmlDocument const & document; + /// Path of the file containing this node + string const filePath; + /// Line of this node in the file that contains it (starting from 1) + /// Equals to xmlDocument::npos if it couldn't be determined. + size_t const line; + /// Character offset of this node in the line that contains it (starting from 1) + /// Equals to xmlDocument::npos if it couldn't be determined. + size_t const offsetInLine; + /// Character offset of this node in the file that contains it (starting from 0) + /// Equals to xmlDocument::npos if it couldn't be determined. + size_t const offset; + + /** + * @brief Constructor of this struct. + */ + xmlNodePos( xmlDocument const & document, string const & filePath, size_t line, size_t offsetInLine, size_t offset ); + /** + * @return false if the position is undetermined. + */ + bool isFound() const; + /** + * @brief Compute the xmlAttributePos of the attributed with the given name. + */ + xmlAttributePos getAttributeLine( string const & attName ) const; +}; + +/// Wrapper class for the type of xml document. +/// This class exists to intercept file / string loading methods, and to keep the loaded buffers, +/// in order to retrieve the source file and line of nodes and attributes. +class xmlDocument : public pugi::xml_document +{ +public: + /// Error value for when an offset / line position is undetermined. + static const size_t npos; + + /** + * @brief Construct an empty xmlDocument that waits to load something. + */ + xmlDocument(); + + /** + * @brief Get the original file buffer loaded during the last load_X() call on this object. + */ + string const & getOriginalBuffer() const; + /** + * @brief If the specified file at the "filePath" is the loaded document of the instance, + * or one of its includes, returns its original buffer as a string. + * Returns nullptr if filePath is not a loaded and available document. + */ + string const * getOriginalBuffer( string const & filePath ) const; + /** + * @brief Map containing the original buffers of the document and its includes, indexed by file path. + */ + map< string, string > const & getOriginalBuffers() const; + /** + * @brief If load_file() has been loaded, returns the path of the source file. + * If another load method has been called, it returns a generated unique value. + */ + string const & getFilePath() const; + /** + * @brief Compute the position of the given node if the node file information are loaded. + * @throws an InputError if the node position couldn't be determined despite the node source file + * informations being loaded. + */ + xmlNodePos getNodePosition( xmlWrapper::xmlNode const & node ) const; + + /** + * @brief Load document from zero-terminated string. No encoding conversions are applied. + * Wrapper of pugi::xml_document::load_buffer() method. + * @param loadNodeFileInfos Load the node source file infos, allowing getNodePosition() to work. + */ + xmlResult load_string( const pugi::char_t * contents, bool loadNodeFileInfos = false, + unsigned int options = pugi::parse_default ); + + /** + * @brief Load document from file. Wrapper of pugi::xml_document::load_buffer() method. + * @param loadNodeFileInfos Load the node source file infos, allowing getNodePosition() to work. + */ + xmlResult load_file( const char * path, bool loadNodeFileInfos = false, + unsigned int options = pugi::parse_default, + pugi::xml_encoding encoding = pugi::encoding_auto ); + + /** + * @brief Load document from buffer. Copies/converts the buffer, so it may be deleted or changed + * after the function returns. Wrapper of pugi::xml_document::load_buffer() method. + * @param loadNodeFileInfos Load the node source file infos, allowing getNodePosition() to work. + */ + xmlResult load_buffer( const void * contents, size_t size, bool loadNodeFileInfos = false, + unsigned int options = pugi::parse_default, + pugi::xml_encoding encoding = pugi::encoding_auto ); + + /** + * @name deleted methods we don't need to inherit + */ + ///@{ + /// @cond DO_NOT_DOCUMENT + xmlResult load_string( const pugi::char_t * contents, unsigned int options ) = delete; + xmlResult load_file( const char * path, unsigned int options, + pugi::xml_encoding encoding = pugi::encoding_auto ) = delete; + + xmlResult load_buffer( const void * contents, size_t size, unsigned int options, + pugi::xml_encoding encoding = pugi::encoding_auto ) = delete; + #ifndef PUGIXML_NO_STL + xmlResult load( std::basic_istream< char, std::char_traits< char > > & stream, + unsigned int options, + pugi::xml_encoding encoding = pugi::encoding_auto ) = delete; + xmlResult load( std::basic_istream< wchar_t, std::char_traits< wchar_t > > & stream, + unsigned int options ) = delete; + #endif + xmlResult load_file( const wchar_t * path, unsigned int options, + pugi::xml_encoding encoding = pugi::encoding_auto ) = delete; + xmlResult load_buffer_inplace( void * contents, size_t size, unsigned int options, + pugi::xml_encoding encoding = pugi::encoding_auto ) = delete; + xmlResult load_buffer_inplace_own( void * contents, size_t size, unsigned int options, + pugi::xml_encoding encoding = pugi::encoding_auto ) = delete; + /// @endcond + ///@} + + /** + * @brief Function to add xml nodes from included files. + * @param targetNode the node for which to look for included children specifications + * @param level include tree level used to detect circular includes + * + * This function looks for a "Included" node under the targetNode, loops over all subnodes under the "Included" + * node, and then parses the file specified in those subnodes taking all the nodes in the file and adding them to + * the targetNode. + * Each found includes are added to xmlDocument::m_originalBuffers. + */ + void addIncludedXML( xmlNode & targetNode, int level = 0 ); + + /** + * @brief True if loadNodeFileInfos was true during the last load_X call. + */ + bool hasNodeFileInfos() const; + +private: + /// Map containing the original buffers of the document and its includes, indexed by file path. + /// We're keeping these original buffer to retrieve the source file and line of nodes and + /// attributes (the pugixml buffer cannot help to determine these informations as it is private + /// and processed). + map< string, string > m_originalBuffers; + /// see getFilePath() + string m_rootFilePath; + // see hasNodeFileInfos() + bool m_hasNodeFileInfos; +}; + /** * @brief constexpr variable to hold name for inserting the file path into the xml file. * @@ -69,28 +247,25 @@ using xmlTypes = pugi::xml_node_type; */ constexpr char const filePathString[] = "__filePath__"; +/** + * @brief constexpr variable to hold node character offset from the start of the xml file. + * + * This is used because we would like the option to hold the offset in the xml structure. + * The name is uglified with underscores to avoid collisions with real attribute names. + */ +constexpr char const charOffsetString[] = "__charOffset__"; + /// XML tag name for included sections constexpr char const includedListTag[] = "Included"; /// XML tag name for included files constexpr char const includedFileTag[] = "File"; -/** - * @brief Function to add xml nodes from included files. - * @param targetNode the node for which to look for included children specifications - * @param level include tree level used to detect circular includes - * - * This function looks for a "Included" node under the targetNode, loops over all subnodes under the "Included" - * node, and then parses the file specified in those subnodes taking all the nodes in the file and adding them to - * the targetNode. - */ -void addIncludedXML( xmlNode & targetNode, int level = 0 ); - /** * @brief Function to handle multiple input xml files. * @param inputFileList the list of input xml files * @param outputDir the output directory to place the composite input file in - * @return inputFileName the input xml file name + * @return inputFilePath the input xml file name * * This function checks for multiple xml files, and will build * a new input xml file with an included block if neccesary @@ -98,6 +273,12 @@ void addIncludedXML( xmlNode & targetNode, int level = 0 ); string buildMultipleInputXML( string_array const & inputFileList, string const & outputDir = {} ); +/** + * @brief Returns if the attribute with the specified name declares metadata relative to the xml + * file (needed by the xml reader) rather than "true data" relative to geos objects. + */ +bool isFileMetadataAttribute( string const & name ); + /** * @name String to variable parsing. * diff --git a/src/coreComponents/mainInterface/ProblemManager.cpp b/src/coreComponents/mainInterface/ProblemManager.cpp index 5a57fdf4b28..423b2d61d1f 100644 --- a/src/coreComponents/mainInterface/ProblemManager.cpp +++ b/src/coreComponents/mainInterface/ProblemManager.cpp @@ -379,13 +379,10 @@ void ProblemManager::parseInputFile() // Load preprocessed xml file xmlWrapper::xmlDocument xmlDocument; - xmlWrapper::xmlResult const xmlResult = xmlDocument.load_file( inputFileName.c_str() ); + xmlWrapper::xmlResult const xmlResult = xmlDocument.load_file( inputFileName.c_str(), true ); GEOS_THROW_IF( !xmlResult, GEOS_FMT( "Errors found while parsing XML file {}\nDescription: {}\nOffset: {}", inputFileName, xmlResult.description(), xmlResult.offset ), InputError ); - // Add path information to the file - xmlDocument.append_child( xmlWrapper::filePathString ).append_attribute( xmlWrapper::filePathString ).set_value( inputFileName.c_str() ); - // Parse the results parseXMLDocument( xmlDocument ); } @@ -395,7 +392,7 @@ void ProblemManager::parseInputString( string const & xmlString ) { // Load preprocessed xml file xmlWrapper::xmlDocument xmlDocument; - xmlWrapper::xmlResult xmlResult = xmlDocument.load_buffer( xmlString.c_str(), xmlString.length() ); + xmlWrapper::xmlResult xmlResult = xmlDocument.load_buffer( xmlString.c_str(), xmlString.length(), true ); GEOS_THROW_IF( !xmlResult, GEOS_FMT( "Errors found while parsing XML string\nDescription: {}\nOffset: {}", xmlResult.description(), xmlResult.offset ), InputError ); @@ -404,18 +401,18 @@ void ProblemManager::parseInputString( string const & xmlString ) } -void ProblemManager::parseXMLDocument( xmlWrapper::xmlDocument const & xmlDocument ) +void ProblemManager::parseXMLDocument( xmlWrapper::xmlDocument & xmlDocument ) { // Extract the problem node and begin processing the user inputs xmlWrapper::xmlNode xmlProblemNode = xmlDocument.child( this->getName().c_str() ); - processInputFileRecursive( xmlProblemNode ); + processInputFileRecursive( xmlDocument, xmlProblemNode ); // The objects in domain are handled separately for now { DomainPartition & domain = getDomainPartition(); ConstitutiveManager & constitutiveManager = domain.getGroup< ConstitutiveManager >( groupKeys.constitutiveManager ); xmlWrapper::xmlNode topLevelNode = xmlProblemNode.child( constitutiveManager.getName().c_str()); - constitutiveManager.processInputFileRecursive( topLevelNode ); + constitutiveManager.processInputFileRecursive( xmlDocument, topLevelNode ); // Open mesh levels MeshManager & meshManager = this->getGroup< MeshManager >( groupKeys.meshManager ); @@ -435,7 +432,7 @@ void ProblemManager::parseXMLDocument( xmlWrapper::xmlDocument const & xmlDocume { ElementRegionManager & elementManager = meshLevel.getElemManager(); Group * newRegion = elementManager.createChild( regionNode.name(), regionName ); - newRegion->processInputFileRecursive( regionNode ); + newRegion->processInputFileRecursive( xmlDocument, regionNode ); } ); } } diff --git a/src/coreComponents/mainInterface/ProblemManager.hpp b/src/coreComponents/mainInterface/ProblemManager.hpp index bfda0156143..4076ad754d1 100644 --- a/src/coreComponents/mainInterface/ProblemManager.hpp +++ b/src/coreComponents/mainInterface/ProblemManager.hpp @@ -41,7 +41,7 @@ class CellBlockManagerABC; /** * @class ProblemManager - * @brief This is the class handling the operation flow of the problem being ran in GEOSX + * @brief This is the class handling the operation flow of the problem being ran in GEOS */ class ProblemManager : public dataRepository::Group { @@ -122,7 +122,7 @@ class ProblemManager : public dataRepository::Group * @brief Parses the input xml document * @param xmlDocument The parsed xml document handle */ - void parseXMLDocument( xmlWrapper::xmlDocument const & xmlDocument ); + void parseXMLDocument( xmlWrapper::xmlDocument & xmlDocument ); /** * @brief Generates numerical meshes used throughout the code diff --git a/src/coreComponents/schema/schemaUtilities.cpp b/src/coreComponents/schema/schemaUtilities.cpp index 0ee3adbbaf8..1a86bd3b15d 100644 --- a/src/coreComponents/schema/schemaUtilities.cpp +++ b/src/coreComponents/schema/schemaUtilities.cpp @@ -52,7 +52,7 @@ void ConvertDocumentationToSchema( string const & fname, "; xmlWrapper::xmlDocument schemaTree; - schemaTree.load_string( schemaBase.c_str()); + schemaTree.load_string( schemaBase.c_str() ); xmlWrapper::xmlNode schemaRoot = schemaTree.child( "xsd:schema" ); // Build the simple schema types diff --git a/src/coreComponents/unitTests/constitutiveTests/testDamage.cpp b/src/coreComponents/unitTests/constitutiveTests/testDamage.cpp index d5195a655f7..8c64f1990e0 100644 --- a/src/coreComponents/unitTests/constitutiveTests/testDamage.cpp +++ b/src/coreComponents/unitTests/constitutiveTests/testDamage.cpp @@ -55,7 +55,7 @@ TEST( DamageTests, testDamageSpectral ) } xmlWrapper::xmlNode xmlConstitutiveNode = xmlDocument.child( "Constitutive" ); - constitutiveManager.processInputFileRecursive( xmlConstitutiveNode ); + constitutiveManager.processInputFileRecursive( xmlDocument, xmlConstitutiveNode ); constitutiveManager.postProcessInputRecursive(); localIndex constexpr numElem = 2; diff --git a/src/coreComponents/unitTests/fluidFlowTests/testCompFlowUtils.hpp b/src/coreComponents/unitTests/fluidFlowTests/testCompFlowUtils.hpp index b2e0f97165c..451815fa2c6 100644 --- a/src/coreComponents/unitTests/fluidFlowTests/testCompFlowUtils.hpp +++ b/src/coreComponents/unitTests/fluidFlowTests/testCompFlowUtils.hpp @@ -200,20 +200,20 @@ void setupProblemFromXML( ProblemManager & problemManager, char const * const xm setApplyDefaultValue( mpiSize ); xmlWrapper::xmlNode xmlProblemNode = xmlDocument.child( dataRepository::keys::ProblemManager ); - problemManager.processInputFileRecursive( xmlProblemNode ); + problemManager.processInputFileRecursive( xmlDocument, xmlProblemNode ); DomainPartition & domain = problemManager.getDomainPartition(); constitutive::ConstitutiveManager & constitutiveManager = domain.getConstitutiveManager(); xmlWrapper::xmlNode topLevelNode = xmlProblemNode.child( constitutiveManager.getName().c_str()); - constitutiveManager.processInputFileRecursive( topLevelNode ); + constitutiveManager.processInputFileRecursive( xmlDocument, topLevelNode ); MeshManager & meshManager = problemManager.getGroup< MeshManager >( problemManager.groupKeys.meshManager ); meshManager.generateMeshLevels( domain ); ElementRegionManager & elementManager = domain.getMeshBody( 0 ).getBaseDiscretization().getElemManager(); topLevelNode = xmlProblemNode.child( elementManager.getName().c_str()); - elementManager.processInputFileRecursive( topLevelNode ); + elementManager.processInputFileRecursive( xmlDocument, topLevelNode ); problemManager.problemSetup(); problemManager.applyInitialConditions(); diff --git a/src/coreComponents/unitTests/fluidFlowTests/testSingleFlowUtils.hpp b/src/coreComponents/unitTests/fluidFlowTests/testSingleFlowUtils.hpp index 4c4e81f1f66..c05fd757ae4 100644 --- a/src/coreComponents/unitTests/fluidFlowTests/testSingleFlowUtils.hpp +++ b/src/coreComponents/unitTests/fluidFlowTests/testSingleFlowUtils.hpp @@ -92,20 +92,20 @@ void setupProblemFromXML( ProblemManager & problemManager, char const * const xm setApplyDefaultValue( mpiSize ); xmlWrapper::xmlNode xmlProblemNode = xmlDocument.child( dataRepository::keys::ProblemManager ); - problemManager.processInputFileRecursive( xmlProblemNode ); + problemManager.processInputFileRecursive( xmlDocument, xmlProblemNode ); DomainPartition & domain = problemManager.getDomainPartition(); constitutive::ConstitutiveManager & constitutiveManager = domain.getConstitutiveManager(); xmlWrapper::xmlNode topLevelNode = xmlProblemNode.child( constitutiveManager.getName().c_str()); - constitutiveManager.processInputFileRecursive( topLevelNode ); + constitutiveManager.processInputFileRecursive( xmlDocument, topLevelNode ); MeshManager & meshManager = problemManager.getGroup< MeshManager >( problemManager.groupKeys.meshManager ); meshManager.generateMeshLevels( domain ); ElementRegionManager & elementManager = domain.getMeshBody( 0 ).getBaseDiscretization().getElemManager(); topLevelNode = xmlProblemNode.child( elementManager.getName().c_str()); - elementManager.processInputFileRecursive( topLevelNode ); + elementManager.processInputFileRecursive( xmlDocument, topLevelNode ); problemManager.problemSetup(); problemManager.applyInitialConditions(); diff --git a/src/coreComponents/unitTests/linearAlgebraTests/testDofManagerUtils.hpp b/src/coreComponents/unitTests/linearAlgebraTests/testDofManagerUtils.hpp index 2f697f8a324..217de9eed93 100644 --- a/src/coreComponents/unitTests/linearAlgebraTests/testDofManagerUtils.hpp +++ b/src/coreComponents/unitTests/linearAlgebraTests/testDofManagerUtils.hpp @@ -53,7 +53,7 @@ void setupProblemFromXML( ProblemManager * const problemManager, char const * co setApplyDefaultValue( mpiSize ); xmlWrapper::xmlNode xmlProblemNode = xmlDocument.child( dataRepository::keys::ProblemManager ); - problemManager->processInputFileRecursive( xmlProblemNode ); + problemManager->processInputFileRecursive( xmlDocument, xmlProblemNode ); // Open mesh levels DomainPartition & domain = problemManager->getDomainPartition(); @@ -62,7 +62,7 @@ void setupProblemFromXML( ProblemManager * const problemManager, char const * co ElementRegionManager & elementManager = domain.getMeshBody( 0 ).getBaseDiscretization().getElemManager(); xmlWrapper::xmlNode topLevelNode = xmlProblemNode.child( elementManager.getName().c_str() ); - elementManager.processInputFileRecursive( topLevelNode ); + elementManager.processInputFileRecursive( xmlDocument, topLevelNode ); elementManager.postProcessInputRecursive(); problemManager->problemSetup(); diff --git a/src/coreComponents/unitTests/meshTests/testMeshGeneration.cpp b/src/coreComponents/unitTests/meshTests/testMeshGeneration.cpp index 84c559e49a8..e3e0cdd08dd 100644 --- a/src/coreComponents/unitTests/meshTests/testMeshGeneration.cpp +++ b/src/coreComponents/unitTests/meshTests/testMeshGeneration.cpp @@ -104,7 +104,7 @@ class MeshGenerationTest : public ::testing::Test xmlWrapper::xmlNode xmlProblemNode = xmlDocument.child( dataRepository::keys::ProblemManager ); ProblemManager & problemManager = getGlobalState().getProblemManager(); - problemManager.processInputFileRecursive( xmlProblemNode ); + problemManager.processInputFileRecursive( xmlDocument, xmlProblemNode ); // Open mesh levels DomainPartition & domain = problemManager.getDomainPartition(); @@ -113,7 +113,7 @@ class MeshGenerationTest : public ::testing::Test ElementRegionManager & elementManager = domain.getMeshBody( 0 ).getBaseDiscretization().getElemManager(); xmlWrapper::xmlNode topLevelNode = xmlProblemNode.child( elementManager.getName().c_str() ); - elementManager.processInputFileRecursive( topLevelNode ); + elementManager.processInputFileRecursive( xmlDocument, topLevelNode ); elementManager.postProcessInputRecursive(); problemManager.problemSetup(); diff --git a/src/coreComponents/unitTests/meshTests/testVTKImport.cpp b/src/coreComponents/unitTests/meshTests/testVTKImport.cpp index 5a0de657cca..7bf7ef719eb 100644 --- a/src/coreComponents/unitTests/meshTests/testVTKImport.cpp +++ b/src/coreComponents/unitTests/meshTests/testVTKImport.cpp @@ -45,7 +45,7 @@ void TestMeshImport( string const & meshFilePath, V const & validate ) Group root( "root", node ); MeshManager meshManager( "mesh", &root ); - meshManager.processInputFileRecursive( xmlMeshNode ); + meshManager.processInputFileRecursive( xmlDocument, xmlMeshNode ); meshManager.postProcessInputRecursive(); DomainPartition domain( "domain", &root ); meshManager.generateMeshes( domain ); diff --git a/src/coreComponents/unitTests/virtualElementTests/testConformingVirtualElementOrder1.cpp b/src/coreComponents/unitTests/virtualElementTests/testConformingVirtualElementOrder1.cpp index 0ffba7845a0..191ccc33ad9 100644 --- a/src/coreComponents/unitTests/virtualElementTests/testConformingVirtualElementOrder1.cpp +++ b/src/coreComponents/unitTests/virtualElementTests/testConformingVirtualElementOrder1.cpp @@ -292,7 +292,7 @@ TEST( ConformingVirtualElementOrder1, hexahedra ) GeosxState state( std::make_unique< CommandLineOptions >( g_commandLineOptions ) ); ProblemManager & problemManager = state.getProblemManager(); - problemManager.processInputFileRecursive( xmlProblemNode ); + problemManager.processInputFileRecursive( inputFile, xmlProblemNode ); // Open mesh levels DomainPartition & domain = problemManager.getDomainPartition(); @@ -302,7 +302,7 @@ TEST( ConformingVirtualElementOrder1, hexahedra ) MeshLevel & mesh = domain.getMeshBody( 0 ).getBaseDiscretization(); ElementRegionManager & elementManager = mesh.getElemManager(); xmlWrapper::xmlNode topLevelNode = xmlProblemNode.child( elementManager.getName().c_str() ); - elementManager.processInputFileRecursive( topLevelNode ); + elementManager.processInputFileRecursive( inputFile, topLevelNode ); elementManager.postProcessInputRecursive(); problemManager.problemSetup(); @@ -345,7 +345,7 @@ TEST( ConformingVirtualElementOrder1, wedges ) GeosxState state( std::make_unique< CommandLineOptions >( g_commandLineOptions ) ); ProblemManager & problemManager = state.getProblemManager(); - problemManager.processInputFileRecursive( xmlProblemNode ); + problemManager.processInputFileRecursive( inputFile, xmlProblemNode ); // Open mesh levels DomainPartition & domain = problemManager.getDomainPartition(); @@ -355,7 +355,7 @@ TEST( ConformingVirtualElementOrder1, wedges ) MeshLevel & mesh = domain.getMeshBody( 0 ).getBaseDiscretization(); ElementRegionManager & elementManager = mesh.getElemManager(); xmlWrapper::xmlNode topLevelNode = xmlProblemNode.child( elementManager.getName().c_str() ); - elementManager.processInputFileRecursive( topLevelNode ); + elementManager.processInputFileRecursive( inputFile, topLevelNode ); elementManager.postProcessInputRecursive(); problemManager.problemSetup(); diff --git a/src/coreComponents/unitTests/xmlTests/testXMLFile.cpp b/src/coreComponents/unitTests/xmlTests/testXMLFile.cpp index 47a8e30bc53..4e57021995f 100644 --- a/src/coreComponents/unitTests/xmlTests/testXMLFile.cpp +++ b/src/coreComponents/unitTests/xmlTests/testXMLFile.cpp @@ -18,13 +18,19 @@ #include "mainInterface/GeosxState.hpp" #include "fieldSpecification/FieldSpecificationManager.hpp" #include "fieldSpecification/FieldSpecificationBase.hpp" +#include "dataRepository/Group.hpp" +#include "dataRepository/SourceContext.hpp" // TPL includes #include #include +#include using namespace geos; +using namespace geos::dataRepository; +using namespace xmlWrapper; +// Tests if the xml file parsing works with one file, one file with nested includes, and multiple files TEST( testXML, testXMLFile ) { geos::ProblemManager & problemManager = geos::getGlobalState().getProblemManager(); @@ -35,6 +41,197 @@ TEST( testXML, testXMLFile ) EXPECT_TRUE( problemManager.getFieldSpecificationManager().hasGroup< FieldSpecificationBase >( "v0" ) ); } +/** + * @brief Returns the xml nodes and attribute in hierarchy that GEOS is supposed to read in + * the Group hierarchy. + * @param document root xml document (which potentially contains includes). + * @param targetNode the function will be called on this node and its children. + * @param elementNames the list of xml elements names with the form "GroupName" or + * "GroupName/WrapperName". The node name is supposed to be the name attribute value, it it exists, + * or the tag name. + */ +void getGEOSElementsRecursive( xmlDocument const & document, xmlNode const & targetNode, + std::set< string > & elementNames ) +{ + // Store here every node name that only exist in the xml (and not in the Group hierarchy). + static const std::set< string > xmlOnlyNodes { + "ElementRegions" + }; + + // The Group name will be the name attribute value, or the node tag name if the name attribute + // doesn't exist. + string groupName = [&]() { + xmlAttribute nameAtt = targetNode.attribute( "name" ); + return nameAtt ? string( nameAtt.value() ) : string( targetNode.name() ); + }(); + + if( xmlOnlyNodes.find( groupName ) == xmlOnlyNodes.end() ) + { + elementNames.emplace( groupName ); + + for( xmlAttribute att : targetNode.attributes() ) + { + if( !isFileMetadataAttribute( att.name() ) ) + { + elementNames.emplace( groupName + '/' + att.name() ); + } + } + } + + for( xmlNode subNode : targetNode.children() ) + { + getGEOSElementsRecursive( document, subNode, elementNames ); + } +} +/** + * @brief Verify if the FileContext data correctly locates from where the object comes from (in the + * correct document/include, at the correct line and at the correct character offset). + * @param document root xml document (which potentially contains includes). + * @param fileContext the FileContext to verify. + */ +void verifyFileContext( FileContext const & fileContext, + xmlDocument const & document ) +{ + string const & strToVerify = fileContext.getTypeName(); + string const & errInfos = "Verifying " + strToVerify + " in " + fileContext.toString(); + + // verifying if all FileContext data have been found + EXPECT_FALSE( fileContext.getFilePath().empty() ) << errInfos; + EXPECT_FALSE( fileContext.getObjectName().empty() ) << errInfos; + EXPECT_FALSE( fileContext.getTypeName().empty() ) << errInfos; + EXPECT_NE( fileContext.getOffset(), xmlDocument::npos ) << errInfos; + EXPECT_NE( fileContext.getLine(), xmlDocument::npos ) << errInfos; + EXPECT_NE( fileContext.getOffsetInLine(), xmlDocument::npos ) << errInfos; + + // will crash if the source buffer specified by fileContext.getFilePath() isn't available + string const * buffer = document.getOriginalBuffer( fileContext.getFilePath() ); + EXPECT_NE( buffer, nullptr ) << errInfos; + + size_t curLine = 1; + bool lineFound = false; + + // Does fileContext.getOffset() locates the object? + EXPECT_LT( fileContext.getOffset() + strToVerify.size(), buffer->size() ) << errInfos; + EXPECT_EQ( strToVerify, buffer->substr( fileContext.getOffset(), strToVerify.size() ) ) << errInfos; + + // Were trying to reach the line return by FileContext::getLine() + for( size_t offset=0; + offset < buffer->size() && curLine < fileContext.getLine(); + ++offset ) + { + if( (*buffer)[offset] == '\n' ) + { + curLine++; + } + + // the theorical line has been reach! + if( curLine == fileContext.getLine() ) + { + // Does fileContext.getLine() and fileContext.getOffsetInLine() locates the object? + EXPECT_LT( offset + fileContext.getOffsetInLine() + strToVerify.size(), + buffer->size() ) << errInfos; + EXPECT_EQ( strToVerify, + buffer->substr( offset + fileContext.getOffsetInLine(), strToVerify.size() ) ) << errInfos; + lineFound = true; + } + } + // does the fileContext line has been reached? + EXPECT_TRUE( lineFound ); +} +/** + * @brief Verifies if the specified group, its children, and the associated wrappers have a + * FileContext object that correctly locate from where the objects where declared in the + * source file. + * @param document root xml document (which potentially contains includes). + * @param group The group to (recursively) verify. + * @param verifCount a set that will be filled with the names, with the form + * "GroupName" or "GroupName/WrapperName". + */ +void verifyGroupFileContextRecursive( xmlDocument const & document, Group const & group, + std::set< string > & verifications ) +{ + // GEOS_LOG( "Verifying "<< group.getName()); + if( group.getSourceContext().isFileContext() ) + { + verifyFileContext( dynamic_cast< FileContext const & >( group.getSourceContext() ), + document ); + verifications.emplace( group.getName() ); + } + + for( auto const & wrapperIterator : group.wrappers() ) + { + WrapperBase const * wrapper = wrapperIterator.second; + if( wrapper->getSourceContext().isFileContext() ) + { + verifyFileContext( dynamic_cast< FileContext const & >( wrapper->getSourceContext() ), + document ); + verifications.emplace( group.getName() + '/' + wrapper->getName() ); + } + } + + for( auto subGroup : group.getSubGroups() ) + { + verifyGroupFileContextRecursive( document, *subGroup.second, verifications ); + } +} + +/** + * @brief Returns the element that exists in setB but not in setA. + */ +std::set< string > getDifference( std::set< string > & setA, + std::set< string > & setB ) +{ + std::vector< string > result( setA.size() + setB.size() ); + + auto it = std::set_difference( setA.begin(), setA.end(), + setB.begin(), setB.end(), + result.begin() ); + result.resize( it - result.begin() ); + + return std::set< string >( result.begin(), result.end() ); +} + +// Tests +// - if the line information of each nodes and attributes can be retrieved, +// - if the resulting Group & Wrapper hierarchy matches with the input xml documents and includes hierarchy. +TEST( testXML, testXMLFileLines ) +{ + xmlDocument xmlDocument; + ProblemManager & problemManager = getGlobalState().getProblemManager(); + + { + problemManager.parseCommandLineInput(); + Group & commandLine = problemManager.getGroup( problemManager.groupKeys.commandLine ); + string const & inputFileName = commandLine.getReference< string >( problemManager.viewKeys.inputFileName ); + xmlDocument.load_file( inputFileName.c_str(), true ); + problemManager.parseXMLDocument( xmlDocument ); + } + + GEOS_LOG( "Loaded files : " ); + for( auto const & buffer: xmlDocument.getOriginalBuffers() ) + { + GEOS_LOG( " " << buffer.first << " (" << buffer.second.size() << " chars)" ); + } + + std::set< string > expectedElements; + getGEOSElementsRecursive( xmlDocument, xmlDocument.root().child( "Problem" ), expectedElements ); + + std::set< string > verifiedElements; + verifyGroupFileContextRecursive( xmlDocument, problemManager, verifiedElements ); + + std::set< string > notFound = getDifference( expectedElements, verifiedElements ); + EXPECT_TRUE( notFound.empty() ) << "Infos : There should not exists xml element that were not in " + "the Group hierarchy.\nNot in Group hierarchy : {" + << stringutilities::join( notFound, "," ) << "}"; + + std::set< string > notExpected = getDifference( verifiedElements, expectedElements ); + EXPECT_TRUE( notExpected.empty() ) << "Infos : There should not exists an object in the Group " + "hierarchy that contains a FileContext but which were not " + "declared in the Xml.\nNot in XML hierarchy : {" + << stringutilities::join( notExpected, "," ) << "}"; +} + + int main( int argc, char * * argv ) { ::testing::InitGoogleTest( &argc, argv ); From 7a7331f872ab708de8d18da7c2abdce8a17ace1a Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Thu, 20 Apr 2023 14:14:42 +0200 Subject: [PATCH 09/85] merge branch "feature/rey/error-xml-line-2" with PR branch "bugfix/rey/2320-xml-errors-feedback" - using getSourceContext() instead of getPath() --- .../codingUtilities/StringUtilities.hpp | 19 ++ src/coreComponents/common/Logger.cpp | 32 ++++ src/coreComponents/common/Logger.hpp | 7 + src/coreComponents/dataRepository/Group.cpp | 50 ++++- src/coreComponents/dataRepository/Group.hpp | 45 ++++- .../FieldSpecificationBase.cpp | 9 +- .../timeHistory/HistoryCollectionBase.cpp | 176 ++++++++++-------- src/coreComponents/mesh/ElementRegionBase.hpp | 1 + .../mesh/ElementRegionManager.hpp | 2 + src/coreComponents/mesh/MeshObjectPath.cpp | 68 ++++--- src/coreComponents/mesh/MeshObjectPath.hpp | 1 + .../mesh/unitTests/testMeshObjectPath.cpp | 6 +- .../dataRepositoryTests/CMakeLists.txt | 3 +- .../dataRepositoryTests/testGroupPath.cpp | 129 +++++++++++++ 14 files changed, 434 insertions(+), 114 deletions(-) create mode 100644 src/coreComponents/unitTests/dataRepositoryTests/testGroupPath.cpp diff --git a/src/coreComponents/codingUtilities/StringUtilities.hpp b/src/coreComponents/codingUtilities/StringUtilities.hpp index 8bd9c805d42..39cacf9b30e 100644 --- a/src/coreComponents/codingUtilities/StringUtilities.hpp +++ b/src/coreComponents/codingUtilities/StringUtilities.hpp @@ -207,6 +207,25 @@ array1d< T > fromStringToArray( string const & str ) template< typename T > string toMetricPrefixString( T const & value ); +/** + * @brief Compute the length of a constant string at compile-time. + */ +// TODO c++17: this function is to remove in favor of std::string_view +constexpr size_t cstrlen( char const * const str ) +{ + if( str ) + { + char const * ptr = str; + for(; *ptr != '\0'; ++ptr ) + {} + return ptr - str; + } + else + { + return 0; + } +} + } // namespace stringutilities } // namespace geos diff --git a/src/coreComponents/common/Logger.cpp b/src/coreComponents/common/Logger.cpp index 024230d1af8..56c20135d56 100644 --- a/src/coreComponents/common/Logger.cpp +++ b/src/coreComponents/common/Logger.cpp @@ -19,10 +19,42 @@ // Source includes #include "Logger.hpp" #include "Path.hpp" +#include "codingUtilities/StringUtilities.hpp" namespace geos { +/** + * @brief Insert an exception message in another one. + * @param originalMsg original exception message (i.e. thrown from LVARRAY_THROW or GEOSX_THROW) + * @param msgToInsert message to insert at the top of the originalMsg + */ +std::string InsertExMsg( std::string const & originalMsg, std::string const & msgToInsert ) +{ + std::string newMsg( originalMsg ); + + size_t insertPos = 0; + // for readability purposes, we try to insert the message after the "***** Rank N: " or after "***** " instead of at the top. + static auto constexpr rankLogStart = "***** Rank "; + static auto constexpr rankLogEnd = ": "; + static auto constexpr simpleLogStart = "***** "; + if( ( insertPos = newMsg.find( rankLogStart ) ) != std::string::npos ) + { + insertPos = newMsg.find( rankLogEnd, insertPos + stringutilities::cstrlen( rankLogStart ) ) + + stringutilities::cstrlen( rankLogEnd ); + } + else if( ( insertPos = newMsg.find_last_of( simpleLogStart ) ) != std::string::npos ) + { + insertPos += stringutilities::cstrlen( simpleLogStart ); + } + newMsg.insert( insertPos, msgToInsert ); + return newMsg; +} + +InputError::InputError( std::exception const & subException, std::string const & msgToInsert ): + std::runtime_error( InsertExMsg( subException.what(), msgToInsert ) ) +{} + namespace logger { diff --git a/src/coreComponents/common/Logger.hpp b/src/coreComponents/common/Logger.hpp index d9a28f13ad2..cb69fdfb2df 100644 --- a/src/coreComponents/common/Logger.hpp +++ b/src/coreComponents/common/Logger.hpp @@ -476,6 +476,13 @@ struct InputError : public std::runtime_error InputError( char const * const what ): std::runtime_error( what ) {} + + /** + * @brief Construct an InputError from an underlying exception. + * @param subException An exception to base this new one on. + * @param msgToInsert The error message. It will be inserted into the one inside of subException. + */ + InputError( std::exception const & subException, std::string const & msgToInsert ); }; /** diff --git a/src/coreComponents/dataRepository/Group.cpp b/src/coreComponents/dataRepository/Group.cpp index c24a6eacded..e4ccd1ef291 100644 --- a/src/coreComponents/dataRepository/Group.cpp +++ b/src/coreComponents/dataRepository/Group.cpp @@ -130,7 +130,7 @@ string Group::getPath() const { // In the Conduit node heirarchy everything begins with 'Problem', we should change it so that // the ProblemManager actually uses the root Conduit Node but that will require a full rebaseline. - string const noProblem = getConduitNode().path().substr( std::strlen( dataRepository::keys::ProblemManager ) - 1 ); + string const noProblem = getConduitNode().path().substr( stringutilities::cstrlen( dataRepository::keys::ProblemManager ) ); return noProblem.empty() ? "/" : noProblem; } @@ -285,6 +285,32 @@ string Group::dumpInputOptions() const return rval; } +string Group::dumpSubGroupsNames() const +{ + if( numSubGroups() == 0 ) + { + return getName() + " has no children."; + } + else + { + return "The children of " + getName() + " are: " + + "{ " + stringutilities::join( getSubGroupsNames(), ", " ) + " }"; + } +} + +string Group::dumpWrappersNames() const +{ + if( numWrappers() == 0 ) + { + return getName() + " has no wrappers."; + } + else + { + return "The wrappers of " + getName() + " are: " + + "{ " + stringutilities::join( getWrappersNames(), ", " ) + " }"; + } +} + void Group::deregisterGroup( string const & name ) { GEOS_ERROR_IF( !hasGroup( name ), "Group " << name << " doesn't exist." ); @@ -628,8 +654,10 @@ Group const & Group::getBaseGroupByPath( string const & path ) const break; } } - GEOS_ERROR_IF( !foundTarget, - "Could not find the specified path from the starting group." ); + GEOS_THROW_IF( !foundTarget, + "Could not find the specified path start.\n"<< + "Specified path is " << path, + std::domain_error ); } string::size_type currentPosition; @@ -668,5 +696,21 @@ PyTypeObject * Group::getPythonType() const { return geos::python::getPyGroupType(); } #endif +std::vector< string > Group::getSubGroupsNames() const +{ + std::vector< string > childrenNames; + childrenNames.reserve( numSubGroups() ); + forSubGroups( [&]( Group const & subGroup ){ childrenNames.push_back( subGroup.getName() ); } ); + return childrenNames; +} + +std::vector< string > Group::getWrappersNames() const +{ + std::vector< string > wrappersNames; + wrappersNames.reserve( numWrappers() ); + forWrappers( [&]( WrapperBase const & wrapper ){ wrappersNames.push_back( wrapper.getName() ); } ); + return wrappersNames; +} + } /* end namespace dataRepository */ } /* end namespace geos */ diff --git a/src/coreComponents/dataRepository/Group.hpp b/src/coreComponents/dataRepository/Group.hpp index bba005abeb9..ca917eeadf2 100644 --- a/src/coreComponents/dataRepository/Group.hpp +++ b/src/coreComponents/dataRepository/Group.hpp @@ -166,6 +166,16 @@ class Group */ string dumpInputOptions() const; + /** + * @brief @return a comma separated string containing all sub groups name. + */ + string dumpSubGroupsNames() const; + + /** + * @brief @return a comma separated string containing all wrappers name. + */ + string dumpWrappersNames() const; + ///@} //START_SPHINX_INCLUDE_REGISTER_GROUP @@ -324,7 +334,11 @@ class Group T & getGroup( KEY const & key ) { Group * const child = m_subGroups[ key ]; - GEOS_THROW_IF( child == nullptr, "Group " << getSourceContext() << " doesn't have a child " << key, std::domain_error ); + GEOS_THROW_IF( child == nullptr, + "Group " << getSourceContext() << " has no child named " << key << std::endl + << dumpSubGroupsNames(), + std::domain_error ); + return dynamicCast< T & >( *child ); } @@ -335,7 +349,11 @@ class Group T const & getGroup( KEY const & key ) const { Group const * const child = m_subGroups[ key ]; - GEOS_THROW_IF( child == nullptr, "Group " << getSourceContext() << " doesn't have a child " << key, std::domain_error ); + GEOS_THROW_IF( child == nullptr, + "Group " << getSourceContext() << " has no child named " << key << std::endl + << dumpSubGroupsNames(), + std::domain_error ); + return dynamicCast< T const & >( *child ); } @@ -381,6 +399,11 @@ class Group */ localIndex numSubGroups() const { return m_subGroups.size(); } + /** + * @return An array containing all sub groups keys + */ + std::vector< string > getSubGroupsNames() const; + /** * @brief Check whether a sub-group exists. * @param name the name of sub-group to search for @@ -1049,7 +1072,11 @@ class Group WrapperBase const & getWrapperBase( KEY const & key ) const { WrapperBase const * const wrapper = m_wrappers[ key ]; - GEOS_THROW_IF( wrapper == nullptr, "Group " << getSourceContext() << " doesn't have a child " << key, std::domain_error ); + GEOS_THROW_IF( wrapper == nullptr, + "Group " << getSourceContext() << " has no wrapper named " << key << std::endl + << dumpWrappersNames(), + std::domain_error ); + return *wrapper; } @@ -1060,7 +1087,11 @@ class Group WrapperBase & getWrapperBase( KEY const & key ) { WrapperBase * const wrapper = m_wrappers[ key ]; - GEOS_THROW_IF( wrapper == nullptr, "Group " << getSourceContext() << " doesn't have a child " << key, std::domain_error ); + GEOS_THROW_IF( wrapper == nullptr, + "Group " << getSourceContext() << " has no wrapper named " << key << std::endl + << dumpWrappersNames(), + std::domain_error ); + return *wrapper; } @@ -1092,6 +1123,11 @@ class Group indexType numWrappers() const { return m_wrappers.size(); } + /** + * @return An array containing all wrappers keys + */ + std::vector< string > getWrappersNames() const; + ///@} /** @@ -1246,6 +1282,7 @@ class Group /** * @brief Return the path of this Group in the data repository. + * Starts with '/' followed by the hierarchy of the children of the "Problem" in which the Group is. * @return The path of this group in the data repository. */ string getPath() const; diff --git a/src/coreComponents/fieldSpecification/FieldSpecificationBase.cpp b/src/coreComponents/fieldSpecification/FieldSpecificationBase.cpp index c8eacb7d5bf..521eff2dc5e 100644 --- a/src/coreComponents/fieldSpecification/FieldSpecificationBase.cpp +++ b/src/coreComponents/fieldSpecification/FieldSpecificationBase.cpp @@ -97,7 +97,14 @@ FieldSpecificationBase::getCatalog() void FieldSpecificationBase::setMeshObjectPath( Group const & meshBodies ) { - m_meshObjectPaths = std::make_unique< MeshObjectPath >( m_objectPath, meshBodies ); + try + { + m_meshObjectPaths = std::make_unique< MeshObjectPath >( m_objectPath, meshBodies ); + } + catch( InputError const & e ) + { + throw InputError( e, getName() + " has a wrong objectPath: " + m_objectPath + "\n" ); + } } diff --git a/src/coreComponents/fileIO/timeHistory/HistoryCollectionBase.cpp b/src/coreComponents/fileIO/timeHistory/HistoryCollectionBase.cpp index e5ccb1ef02b..ba32826c9ae 100644 --- a/src/coreComponents/fileIO/timeHistory/HistoryCollectionBase.cpp +++ b/src/coreComponents/fileIO/timeHistory/HistoryCollectionBase.cpp @@ -84,108 +84,128 @@ bool HistoryCollectionBase::execute( real64 const time_n, dataRepository::Group const * HistoryCollectionBase::getTargetObject( DomainPartition const & domain, string const & objectPath ) const { - // absolute objectPaths can be used to target anything in the data repo that is packable - if( objectPath[0] == '/' ) + try { - return &Group::getGroupByPath( objectPath ); - } - else // relative objectPaths use relative lookup identical to fieldSpecification to make xml input spec easier - { - std::vector< string > targetTokens = stringutilities::tokenize( objectPath, "/" ); - localIndex targetTokenLength = LvArray::integerConversion< localIndex >( targetTokens.size() ); - - dataRepository::Group const * targetGroup = nullptr; - int const numMeshBodies = domain.getMeshBodies().numSubGroups(); - - - if( numMeshBodies==1 ) + // absolute objectPaths can be used to target anything in the data repo that is packable + if( objectPath[0] == '/' ) { - string const singleMeshBodyName = domain.getMeshBody( 0 ).getName(); - if( targetTokens[0] != singleMeshBodyName ) - { - ++targetTokenLength; - targetTokens.insert( targetTokens.begin(), singleMeshBodyName ); - } + return &Group::getGroupByPath( objectPath ); } - else + else // relative objectPaths use relative lookup identical to fieldSpecification to make xml input spec easier { - bool bodyFound = false; - domain.forMeshBodies( [&]( MeshBody const & meshBody ) - { - if( meshBody.getName()==targetTokens[0] ) - { - bodyFound=true; - } - } ); - - GEOS_ERROR_IF( !bodyFound, - GEOS_FMT( "MeshBody ({}) is specified, but not found.", - targetTokens[0] ) ); - } + std::vector< string > targetTokens = stringutilities::tokenize( objectPath, "/" ); + localIndex targetTokenLength = LvArray::integerConversion< localIndex >( targetTokens.size() ); + dataRepository::Group const * targetGroup = nullptr; + int const numMeshBodies = domain.getMeshBodies().numSubGroups(); - string const meshBodyName = targetTokens[0]; - MeshBody const & meshBody = domain.getMeshBody( meshBodyName ); - - // set mesh level in path - localIndex const numMeshLevels = meshBody.getMeshLevels().numSubGroups(); - if( numMeshLevels==1 ) - { - string const singleMeshLevelName = meshBody.getMeshLevels().getGroup< MeshLevel >( 0 ).getName(); - if( targetTokens[1] != singleMeshLevelName ) + if( numMeshBodies==1 ) { - ++targetTokenLength; - targetTokens.insert( targetTokens.begin()+1, singleMeshLevelName ); + string const singleMeshBodyName = domain.getMeshBody( 0 ).getName(); + if( targetTokens[0] != singleMeshBodyName ) + { + ++targetTokenLength; + targetTokens.insert( targetTokens.begin(), singleMeshBodyName ); + } } else { - bool levelFound = false; - meshBody.forMeshLevels( [&]( MeshLevel const & meshLevel ) + bool bodyFound = false; + domain.forMeshBodies( [&]( MeshBody const & meshBody ) { - if( meshLevel.getName()==targetTokens[1] ) + if( meshBody.getName()==targetTokens[0] ) { - levelFound=true; + bodyFound=true; } } ); - GEOS_ERROR_IF( !levelFound, - GEOS_FMT( "MeshLevel ({}) is specified, but not found.", - targetTokens[1] ) ); + GEOS_ERROR_IF( !bodyFound, + GEOS_FMT( "MeshBody ({}) is specified, but not found.", + targetTokens[0] ) ); } - } - else if( !meshBody.getMeshLevels().hasGroup< MeshLevel >( targetTokens[1] ) ) - { - //GEOS_LOG_RANK_0( "In TimeHistoryCollection.hpp, Mesh Level Discretization not specified, " - // "using baseDiscretizationString()." ); - string const baseMeshLevelName = MeshBody::groupStructKeys::baseDiscretizationString(); - ++targetTokenLength; - targetTokens.insert( targetTokens.begin()+1, baseMeshLevelName ); - } - string meshLevelName = targetTokens[1]; - MeshLevel const & meshLevel = meshBody.getMeshLevel( meshLevelName ); - targetGroup = &meshLevel; + string const meshBodyName = targetTokens[0]; + MeshBody const & meshBody = domain.getMeshBody( meshBodyName ); - if( targetTokens[2]== MeshLevel::groupStructKeys::elemManagerString() ) - { - ElementRegionManager const & elemRegionManager = meshLevel.getElemManager(); - string const elemRegionName = targetTokens[3]; - ElementRegionBase const & elemRegion = elemRegionManager.getRegion( elemRegionName ); - string const elemSubRegionName = targetTokens[4]; - ElementSubRegionBase const & elemSubRegion = elemRegion.getSubRegion( elemSubRegionName ); - targetGroup = &elemSubRegion; - } - else - { - for( localIndex pathLevel = 2; pathLevel < targetTokenLength; ++pathLevel ) + // set mesh level in path + localIndex const numMeshLevels = meshBody.getMeshLevels().numSubGroups(); + if( numMeshLevels==1 ) { - targetGroup = targetGroup->getGroupPointer( targetTokens[pathLevel] ); + string const singleMeshLevelName = meshBody.getMeshLevels().getGroup< MeshLevel >( 0 ).getName(); + if( targetTokens[1] != singleMeshLevelName ) + { + ++targetTokenLength; + targetTokens.insert( targetTokens.begin()+1, singleMeshLevelName ); + } + else + { + bool levelFound = false; + meshBody.forMeshLevels( [&]( MeshLevel const & meshLevel ) + { + if( meshLevel.getName()==targetTokens[1] ) + { + levelFound=true; + } + } ); + + GEOS_ERROR_IF( !levelFound, + GEOS_FMT( "MeshLevel ({}) is specified, but not found.", + targetTokens[1] ) ); + } + } + else if( !meshBody.getMeshLevels().hasGroup< MeshLevel >( targetTokens[1] ) ) + { + //GEOSX_LOG_RANK_0( "In TimeHistoryCollection.hpp, Mesh Level Discretization not specified, " + // "using baseDiscretizationString()." ); + + string const baseMeshLevelName = MeshBody::groupStructKeys::baseDiscretizationString(); + ++targetTokenLength; + targetTokens.insert( targetTokens.begin()+1, baseMeshLevelName ); + } + + string meshLevelName = targetTokens[1]; + MeshLevel const & meshLevel = meshBody.getMeshLevel( meshLevelName ); + targetGroup = &meshLevel; + + + if( targetTokens[2]== MeshLevel::groupStructKeys::elemManagerString() ) + { + ElementRegionManager const & elemRegionManager = meshLevel.getElemManager(); + string const elemRegionName = targetTokens[3]; + ElementRegionBase const & elemRegion = elemRegionManager.getRegion( elemRegionName ); + string const elemSubRegionName = targetTokens[4]; + ElementSubRegionBase const & elemSubRegion = elemRegion.getSubRegion( elemSubRegionName ); + targetGroup = &elemSubRegion; + } + else + { + for( localIndex pathLevel = 2; pathLevel < targetTokenLength; ++pathLevel ) + { + dataRepository::Group const * const childGroup = targetGroup->getGroupPointer( targetTokens[pathLevel] ); + if( childGroup != nullptr ) + { + targetGroup=childGroup; + } + else + { + string const targetTokensStr = stringutilities::join( targetTokens.begin(), + targetTokens.begin()+pathLevel, + '/' ); + GEOS_THROW( targetTokens[pathLevel] << " not found in path " << + objectPath << std::endl << targetGroup->dumpSubGroupsNames(), + std::domain_error ); + } + } } + return targetGroup; } - return targetGroup; + } + catch( std::domain_error const & e ) + { + throw InputError( e, getName() + " has a wrong objectPath: " + objectPath + "\n" ); } } diff --git a/src/coreComponents/mesh/ElementRegionBase.hpp b/src/coreComponents/mesh/ElementRegionBase.hpp index 673ea6fe33b..825044d7fac 100644 --- a/src/coreComponents/mesh/ElementRegionBase.hpp +++ b/src/coreComponents/mesh/ElementRegionBase.hpp @@ -135,6 +135,7 @@ class ElementRegionBase : public ObjectManagerBase * @tparam KEY_TYPE The type of the key used to lookup the subregion. * @param key The key to the subregion. * @return A reference to the subregion + * @throw std::domain_error if the the requested sub-region doesn't exist. */ template< typename SUBREGIONTYPE=ElementSubRegionBase, typename KEY_TYPE=void > SUBREGIONTYPE const & getSubRegion( KEY_TYPE const & key ) const diff --git a/src/coreComponents/mesh/ElementRegionManager.hpp b/src/coreComponents/mesh/ElementRegionManager.hpp index 4b9c7bd3663..e53e1545bb8 100644 --- a/src/coreComponents/mesh/ElementRegionManager.hpp +++ b/src/coreComponents/mesh/ElementRegionManager.hpp @@ -225,6 +225,7 @@ class ElementRegionManager : public ObjectManagerBase * @brief Get a element region. * @param key The key of element region, either name or number. * @return Reference to const T. + * @throw std::domain_error if the requested region doesn't exist. */ template< typename T=ElementRegionBase, typename KEY_TYPE=void > T const & getRegion( KEY_TYPE const & key ) const @@ -236,6 +237,7 @@ class ElementRegionManager : public ObjectManagerBase * @brief Get a element region. * @param key The key of the element region, either name or number. * @return Reference to T. + * @throw std::domain_error if the requested region doesn't exist. */ template< typename T=ElementRegionBase, typename KEY_TYPE=void > T & getRegion( KEY_TYPE const & key ) diff --git a/src/coreComponents/mesh/MeshObjectPath.cpp b/src/coreComponents/mesh/MeshObjectPath.cpp index 59886ec04ee..6cdaf6bfe98 100644 --- a/src/coreComponents/mesh/MeshObjectPath.cpp +++ b/src/coreComponents/mesh/MeshObjectPath.cpp @@ -80,14 +80,15 @@ MeshObjectPath::fillPathTokens( string const & path, int objectIndex = findObjectIndex(); - GEOS_ERROR_IF( objectIndex==-1, - GEOS_FMT( "Path ({}) does not contain a valid object type. " - "Must contain one of ({},{},{},{})", + GEOS_THROW_IF( objectIndex==-1, + GEOS_FMT( "Path {} does not contain a valid object type. " + "It must contain one of the following: {}, {}, {}, {}", path, MeshLevel::groupStructKeys::nodeManagerString(), MeshLevel::groupStructKeys::edgeManagerString(), MeshLevel::groupStructKeys::faceManagerString(), - MeshLevel::groupStructKeys::elemManagerString() ) ); + MeshLevel::groupStructKeys::elemManagerString() ), + InputError ); // No MeshBody or MeshLevels were specified. add all of them if( objectIndex==0 ) @@ -110,24 +111,36 @@ MeshObjectPath::fillPathTokens( string const & path, { pathTokens.insert( pathTokens.begin(), "{*}" ); - string existingMeshBodyAndLevel; + // searching if the mesh level exists bool levelNameFound = false; meshBodies.forSubGroups< MeshBody >( [&]( MeshBody const & meshBody ) { - existingMeshBodyAndLevel += meshBody.getName() + ": "; meshBody.forMeshLevels( [&]( MeshLevel const & meshLevel ) { - existingMeshBodyAndLevel += meshLevel.getName() + ", "; - levelNameFound = ( unidentifiedName==meshLevel.getName() ) ? true : levelNameFound; + levelNameFound |= ( unidentifiedName==meshLevel.getName() ); } ); - existingMeshBodyAndLevel += "/n"; } ); - GEOS_ERROR_IF( !levelNameFound, - GEOS_FMT( "Path ({}) specifies an invalid MeshBody or MeshLevel. ", - "existing MeshBodies: MeshLevels /n", - path, - existingMeshBodyAndLevel ) ); + if( !levelNameFound ) + { + string existingMeshBodiesAndLevels; + meshBodies.forSubGroups< MeshBody >( [&]( MeshBody const & meshBody ) + { + std::vector< string > meshLevelsNames; + existingMeshBodiesAndLevels += " MeshBody "+meshBody.getName() + ": { "; + meshBody.forMeshLevels( [&]( MeshLevel const & meshLevel ) + { + meshLevelsNames.push_back( meshLevel.getName() ); + } ); + existingMeshBodiesAndLevels += stringutilities::join( meshLevelsNames, ", " ) + " }\n"; + } ); + + GEOS_THROW( GEOS_FMT( "Path {0} specifies an invalid MeshBody or MeshLevel. ", + "existing MeshBodies: \n{1}\n", + path, + existingMeshBodiesAndLevels ), + InputError ); + } pathTokens.insert( pathTokens.begin()+1, unidentifiedName ); } } @@ -136,11 +149,13 @@ MeshObjectPath::fillPathTokens( string const & path, objectIndex = findObjectIndex(); size_t targetTokenLength = pathTokens.size(); - GEOS_ERROR_IF_NE_MSG( objectIndex, 2, - "Filling of MeshBody and/or MeshLevel in path has failed. Object Index should be 2" ); + GEOS_THROW_IF_NE_MSG( objectIndex, 2, + "Filling of MeshBody and/or MeshLevel in path has failed. Object Index should be 2", + InputError ); - GEOS_ERROR_IF( targetTokenLength < 2, - "Filling of MeshBody and/or MeshLevel in path has failed. targetTokenLength should be greater than 2" ); + GEOS_THROW_IF( targetTokenLength < 2, + "Filling of MeshBody and/or MeshLevel in path has failed. targetTokenLength should be greater than 2", + InputError ); // now we need to fill in any missing region/subregion specifications. @@ -189,12 +204,16 @@ void processTokenRecursive( dataRepository::Group const & parentGroup, NODETYPE & node, CALLBACK && cbfunc ) { - array1d< string > namesInRepository; + std::vector< string > namesInRepository; parentGroup.forSubGroups< TYPE >( [&]( TYPE const & group ) { namesInRepository.emplace_back( group.getName() ); } ); + GEOS_THROW_IF( namesInRepository.empty(), + GEOS_FMT( "{0} doesn't have any children.", parentGroup.getName()), + InputError ); + for( string const & inputEntry : stringutilities::tokenize( pathToken, " " ) ) { bool foundMatch = false; @@ -212,12 +231,13 @@ void processTokenRecursive( dataRepository::Group const & parentGroup, } } - GEOS_ERROR_IF( !foundMatch, - GEOS_FMT( "Specified name ({0}) did not find a match with a object in group ({1}). " - "Objects that are present in ({1}) are:\n{2}", - inputEntry, + GEOS_THROW_IF( !foundMatch, + GEOS_FMT( "{0} doesn't have a child named {1}.\n" + "{0} have the following children: {{ {2} }}", parentGroup.getName(), - stringutilities::join( namesInRepository, ", " ) ) ); + inputEntry, + stringutilities::join( namesInRepository, ", " ) ), + InputError ); } } diff --git a/src/coreComponents/mesh/MeshObjectPath.hpp b/src/coreComponents/mesh/MeshObjectPath.hpp index 573bfd915bd..b8124d1c9df 100644 --- a/src/coreComponents/mesh/MeshObjectPath.hpp +++ b/src/coreComponents/mesh/MeshObjectPath.hpp @@ -62,6 +62,7 @@ class MeshObjectPath * * @param path The path string * @param meshBodies The Group that contains all MeshBody objects + * @throw InputError when the input path is wrong. */ MeshObjectPath( string const path, dataRepository::Group const & meshBodies ); diff --git a/src/coreComponents/mesh/unitTests/testMeshObjectPath.cpp b/src/coreComponents/mesh/unitTests/testMeshObjectPath.cpp index d76015a3749..d5021b24c01 100644 --- a/src/coreComponents/mesh/unitTests/testMeshObjectPath.cpp +++ b/src/coreComponents/mesh/unitTests/testMeshObjectPath.cpp @@ -280,7 +280,7 @@ TEST( testMeshObjectPath, invalidMeshBody ) Group const & meshBodies = testMesh.meshBodies(); { string const path = "*/level2/ElementRegions"; - EXPECT_DEATH_IF_SUPPORTED( MeshObjectPath meshObjectPath( path, meshBodies ), ".*" ); + ASSERT_THROW( MeshObjectPath meshObjectPath( path, meshBodies ), InputError ); } } @@ -291,7 +291,7 @@ TEST( testMeshObjectPath, invalidMeshLevel ) Group const & meshBodies = testMesh.meshBodies(); { string const path = "*/*/ElementRegions/{region2}"; - EXPECT_DEATH_IF_SUPPORTED( MeshObjectPath meshObjectPath( path, meshBodies ), ".*" ); + ASSERT_THROW( MeshObjectPath meshObjectPath( path, meshBodies ), InputError ); } } @@ -301,7 +301,7 @@ TEST( testMeshObjectPath, invalidMeshRegion ) Group const & meshBodies = testMesh.meshBodies(); { string const path = "*/*/ElementRegions/*/subreg2"; - EXPECT_DEATH_IF_SUPPORTED( MeshObjectPath meshObjectPath( path, meshBodies ), ".*" ); + ASSERT_THROW( MeshObjectPath meshObjectPath( path, meshBodies ), InputError ); } } diff --git a/src/coreComponents/unitTests/dataRepositoryTests/CMakeLists.txt b/src/coreComponents/unitTests/dataRepositoryTests/CMakeLists.txt index dc372d7d1ef..3d88175d1fb 100644 --- a/src/coreComponents/unitTests/dataRepositoryTests/CMakeLists.txt +++ b/src/coreComponents/unitTests/dataRepositoryTests/CMakeLists.txt @@ -8,7 +8,8 @@ set( dataRepository_tests testRestartExtended.cpp testPacking.cpp testWrapperHelpers.cpp - ) + testGroupPath.cpp + ) set( dependencyList gtest ) diff --git a/src/coreComponents/unitTests/dataRepositoryTests/testGroupPath.cpp b/src/coreComponents/unitTests/dataRepositoryTests/testGroupPath.cpp new file mode 100644 index 00000000000..20761ea22c7 --- /dev/null +++ b/src/coreComponents/unitTests/dataRepositoryTests/testGroupPath.cpp @@ -0,0 +1,129 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2018-2020 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2020 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2018-2020 TotalEnergies + * Copyright (c) 2019- GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +// Source includes +#include "mainInterface/ProblemManager.hpp" +#include "mainInterface/initialization.hpp" +#include "mainInterface/GeosxState.hpp" + +// TPL includes +#include +#include + +// Tests the Group::getGroup() and getPath() methods +TEST( testGroupPath, testGlobalPaths ) +{ + using namespace geos; + using namespace dataRepository; + + char const * xmlInput = + "\n" + "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + ""; + + std::vector< string > const groupPaths{ + "/Mesh/mesh1", + "/domain/MeshBodies/mesh1/meshLevels/Level0/ElementRegions/elementRegionsGroup/Region2", + "/domain/Constitutive/shale", + "/Events/solverApplications", + "/NumericalMethods/FiniteElements/FE1", + "/Solvers/lagsolve", + }; + + ProblemManager & problem = getGlobalState().getProblemManager(); + problem.parseInputString( xmlInput ); + + for( string const & path : groupPaths ) + { + Group const & group = problem.getGroupByPath( path ); + ASSERT_STREQ( path.c_str(), group.getPath().c_str() ); + } + + // test for a wrong path given to getGroupByPath() + bool trowHappened = false; + try + { + problem.getGroupByPath( "/Mesh/mesh2" ); + } + catch( const std::domain_error & e ) + { + static constexpr auto expectedMsg = "***** Controlling expression (should be false): child == nullptr\n" + "***** Rank 0: Group /Mesh has no child named mesh2\n" + "The children of Mesh are: { mesh1 }"; + // checks if the exception contains the expected message + ASSERT_TRUE( string( e.what() ).find( expectedMsg ) != string::npos ); + trowHappened = true; + } + // checks if the exception has been thrown as expected + ASSERT_TRUE( trowHappened ); +} + +int main( int argc, char * * argv ) +{ + ::testing::InitGoogleTest( &argc, argv ); + + geos::GeosxState state( geos::basicSetup( argc, argv, false ) ); + + int const result = RUN_ALL_TESTS(); + + geos::basicCleanup(); + + return result; +} From fb418c8fb3926f66dc562eb667c15926c27bbaab Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Mon, 24 Apr 2023 11:06:33 +0200 Subject: [PATCH 10/85] Indentation, code style, naming --- .../dataRepository/CMakeLists.txt | 26 ++++++++++--------- src/coreComponents/dataRepository/Group.cpp | 12 ++++----- .../unitTests/xmlTests/testXMLFile.cpp | 10 +++---- 3 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/coreComponents/dataRepository/CMakeLists.txt b/src/coreComponents/dataRepository/CMakeLists.txt index 869ddd94751..f9d6c4c576e 100644 --- a/src/coreComponents/dataRepository/CMakeLists.txt +++ b/src/coreComponents/dataRepository/CMakeLists.txt @@ -24,11 +24,11 @@ set( dataRepository_headers xmlWrapper.hpp SourceContext.hpp ) - - # - # Specify all sources - # - set( dataRepository_sources + +# +# Specify all sources +# +set( dataRepository_sources BufferOpsDevice.cpp ConduitRestart.cpp ExecutableGroup.cpp @@ -47,12 +47,14 @@ endif() if( ENABLE_PYGEOSX ) list( APPEND dataRepository_headers - python/PyGroup.hpp - python/PyGroupType.hpp - python/PyWrapper.hpp ) + python/PyGroup.hpp + python/PyGroupType.hpp + python/PyWrapper.hpp + ) list( APPEND dataRepository_sources - python/PyGroup.cpp - python/PyWrapper.cpp ) + python/PyGroup.cpp + python/PyWrapper.cpp + ) list( APPEND dependencyList Python3::Python pylvarray ) endif() @@ -62,10 +64,10 @@ blt_add_library( NAME dataRepository HEADERS ${dataRepository_headers} DEPENDS_ON ${dependencyList} OBJECT ${GEOSX_BUILD_OBJ_LIBS} - ) + ) target_include_directories( dataRepository PUBLIC ${CMAKE_SOURCE_DIR}/coreComponents ) -geosx_add_code_checks(PREFIX dataRepository ) +geosx_add_code_checks( PREFIX dataRepository ) add_subdirectory( unitTests ) diff --git a/src/coreComponents/dataRepository/Group.cpp b/src/coreComponents/dataRepository/Group.cpp index e4ccd1ef291..1ebe5d5f339 100644 --- a/src/coreComponents/dataRepository/Group.cpp +++ b/src/coreComponents/dataRepository/Group.cpp @@ -161,13 +161,11 @@ void Group::processInputFileRecursive( xmlWrapper::xmlDocument & xmlDocument, else { // Make sure child names are not duplicated - if( std::find( childNames.begin(), childNames.end(), childName ) != childNames.end() ) - { - GEOS_ERROR( GEOS_FMT( "Error: An XML block cannot contain children with duplicated names.\n" - "Error detected at node {} with name = {} ({}:l.{})", - childNode.path(), childName, xmlDocument.getFilePath(), - xmlDocument.getNodePosition( childNode ).line ) ); - } + GEOS_ERROR_IF( std::find( childNames.begin(), childNames.end(), childName ) != childNames.end(), + GEOS_FMT( "Error: An XML block cannot contain children with duplicated names.\n" + "Error detected at node {} with name = {} ({}:l.{})", + childNode.path(), childName, xmlDocument.getFilePath(), + xmlDocument.getNodePosition( childNode ).line ) ); childNames.emplace_back( childName ); } diff --git a/src/coreComponents/unitTests/xmlTests/testXMLFile.cpp b/src/coreComponents/unitTests/xmlTests/testXMLFile.cpp index 4e57021995f..5f0ff40eb96 100644 --- a/src/coreComponents/unitTests/xmlTests/testXMLFile.cpp +++ b/src/coreComponents/unitTests/xmlTests/testXMLFile.cpp @@ -50,8 +50,8 @@ TEST( testXML, testXMLFile ) * "GroupName/WrapperName". The node name is supposed to be the name attribute value, it it exists, * or the tag name. */ -void getGEOSElementsRecursive( xmlDocument const & document, xmlNode const & targetNode, - std::set< string > & elementNames ) +void getElementsRecursive( xmlDocument const & document, xmlNode const & targetNode, + std::set< string > & elementNames ) { // Store here every node name that only exist in the xml (and not in the Group hierarchy). static const std::set< string > xmlOnlyNodes { @@ -80,7 +80,7 @@ void getGEOSElementsRecursive( xmlDocument const & document, xmlNode const & tar for( xmlNode subNode : targetNode.children() ) { - getGEOSElementsRecursive( document, subNode, elementNames ); + getElementsRecursive( document, subNode, elementNames ); } } /** @@ -191,7 +191,7 @@ std::set< string > getDifference( std::set< string > & setA, return std::set< string >( result.begin(), result.end() ); } -// Tests +// Tests // - if the line information of each nodes and attributes can be retrieved, // - if the resulting Group & Wrapper hierarchy matches with the input xml documents and includes hierarchy. TEST( testXML, testXMLFileLines ) @@ -214,7 +214,7 @@ TEST( testXML, testXMLFileLines ) } std::set< string > expectedElements; - getGEOSElementsRecursive( xmlDocument, xmlDocument.root().child( "Problem" ), expectedElements ); + getElementsRecursive( xmlDocument, xmlDocument.root().child( "Problem" ), expectedElements ); std::set< string > verifiedElements; verifyGroupFileContextRecursive( xmlDocument, problemManager, verifiedElements ); From e995b0c35915f21e98a2d70d4daaed8093510259 Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Mon, 24 Apr 2023 12:02:15 +0200 Subject: [PATCH 11/85] Renaming SourceContext to DataContext - also renaming FileContext to DataFileContext --- .../dataRepository/CMakeLists.txt | 4 +- .../{SourceContext.cpp => DataContext.cpp} | 30 +++++------ .../{SourceContext.hpp => DataContext.hpp} | 50 +++++++++---------- src/coreComponents/dataRepository/Group.cpp | 10 ++-- src/coreComponents/dataRepository/Group.hpp | 24 ++++----- src/coreComponents/dataRepository/Wrapper.hpp | 2 +- .../dataRepository/WrapperBase.cpp | 8 +-- .../dataRepository/WrapperBase.hpp | 16 +++--- .../unitTests/xmlTests/testXMLFile.cpp | 38 +++++++------- 9 files changed, 91 insertions(+), 91 deletions(-) rename src/coreComponents/dataRepository/{SourceContext.cpp => DataContext.cpp} (67%) rename src/coreComponents/dataRepository/{SourceContext.hpp => DataContext.hpp} (73%) diff --git a/src/coreComponents/dataRepository/CMakeLists.txt b/src/coreComponents/dataRepository/CMakeLists.txt index f9d6c4c576e..40ef78f56d3 100644 --- a/src/coreComponents/dataRepository/CMakeLists.txt +++ b/src/coreComponents/dataRepository/CMakeLists.txt @@ -22,7 +22,7 @@ set( dataRepository_headers WrapperBase.hpp wrapperHelpers.hpp xmlWrapper.hpp - SourceContext.hpp + DataContext.hpp ) # @@ -36,7 +36,7 @@ set( dataRepository_sources Utilities.cpp WrapperBase.cpp xmlWrapper.cpp - SourceContext.cpp + DataContext.cpp ) set( dependencyList codingUtilities ) diff --git a/src/coreComponents/dataRepository/SourceContext.cpp b/src/coreComponents/dataRepository/DataContext.cpp similarity index 67% rename from src/coreComponents/dataRepository/SourceContext.cpp rename to src/coreComponents/dataRepository/DataContext.cpp index b9e45479e79..e5150170334 100644 --- a/src/coreComponents/dataRepository/SourceContext.cpp +++ b/src/coreComponents/dataRepository/DataContext.cpp @@ -13,7 +13,7 @@ */ /** - * @file SourceContext.cpp + * @file DataContext.cpp */ //#include "codingUtilities/StringUtilities.hpp" @@ -27,12 +27,12 @@ namespace dataRepository { -SourceContext::SourceContext( string const & objectName, bool const isFileContext ): +DataContext::DataContext( string const & objectName, bool const isDataFileContext ): m_objectName( objectName ), - m_isFileContext( isFileContext ) + m_isDataFileContext( isDataFileContext ) {} -std::ostream & operator<<( std::ostream & os, SourceContext const & sc ) +std::ostream & operator<<( std::ostream & os, DataContext const & sc ) { os << sc.toString(); return os; @@ -40,7 +40,7 @@ std::ostream & operator<<( std::ostream & os, SourceContext const & sc ) GroupContext::GroupContext( Group & group, string const & objectName ): - SourceContext( objectName, false ), + DataContext( objectName, false ), m_group( group ) {} GroupContext::GroupContext( Group & group ): @@ -49,7 +49,7 @@ GroupContext::GroupContext( Group & group ): string GroupContext::toString() const { - // it would be possible to insert the FileContext::toString() of parent objects when it exists, but is it relevant ? + // it would be possible to insert the DataFileContext::toString() of parent objects when it exists, but is it relevant ? return m_group.getPath(); } @@ -60,22 +60,22 @@ WrapperContext::WrapperContext( WrapperBase & wrapper ): string WrapperContext::toString() const { - // if possible, we show the FileContext of the parent. - if( m_group.getSourceContext().isFileContext() ) + // if possible, we show the DataFileContext of the parent. + if( m_group.getDataContext().isDataFileContext() ) { - return m_group.getSourceContext().toString() + ", attribute " + m_objectName; + return m_group.getDataContext().toString() + ", attribute " + m_objectName; } else { - // it would be possible to insert the FileContext::toString() of parent objects when it exists, but is it relevant ? + // it would be possible to insert the DataFileContext::toString() of parent objects when it exists, but is it relevant ? return m_group.getPath() + "/" + m_objectName; } } -FileContext::FileContext( Group & group, xmlWrapper::xmlNodePos const & nodePos, +DataFileContext::DataFileContext( Group & group, xmlWrapper::xmlNodePos const & nodePos, string const & nodeTagName ): - SourceContext( group.getName(), true ), + DataContext( group.getName(), true ), m_typeName( nodeTagName ), m_filePath( nodePos.filePath ), m_line( nodePos.line ), @@ -83,8 +83,8 @@ FileContext::FileContext( Group & group, xmlWrapper::xmlNodePos const & nodePos, m_offset( nodePos.offset ) {} -FileContext::FileContext( WrapperBase & wrapper, xmlWrapper::xmlAttributePos const & attPos ): - SourceContext( wrapper.getParent().getName() + "/" + wrapper.getName(), true ), +DataFileContext::DataFileContext( WrapperBase & wrapper, xmlWrapper::xmlAttributePos const & attPos ): + DataContext( wrapper.getParent().getName() + "/" + wrapper.getName(), true ), m_typeName( wrapper.getName() ), m_filePath( attPos.filePath ), m_line( attPos.line ), @@ -92,7 +92,7 @@ FileContext::FileContext( WrapperBase & wrapper, xmlWrapper::xmlAttributePos con m_offset( attPos.offset ) {} -string FileContext::toString() const +string DataFileContext::toString() const { std::ostringstream oss; oss << m_objectName << " (" << m_filePath; diff --git a/src/coreComponents/dataRepository/SourceContext.hpp b/src/coreComponents/dataRepository/DataContext.hpp similarity index 73% rename from src/coreComponents/dataRepository/SourceContext.hpp rename to src/coreComponents/dataRepository/DataContext.hpp index dc00e488882..403b1018f6a 100644 --- a/src/coreComponents/dataRepository/SourceContext.hpp +++ b/src/coreComponents/dataRepository/DataContext.hpp @@ -13,7 +13,7 @@ */ /** - * @file SourceContext.hpp + * @file DataContext.hpp */ #ifndef GEOS_DATAREPOSITORY_SOURCECONTEXT_HPP_ @@ -32,20 +32,20 @@ namespace dataRepository class Group; class WrapperBase; -/// This abstract class stores data that helps to retrieve from where a object comes from : -/// - from which position in a file (if applicable), see FileContext, +/// This abstract class stores data that helps to retrieve an object: +/// - from which position in a file (if applicable), see DataFileContext, /// - where it is located in the data hierarchy, see GroupContext and WrapperContext. -/// Typically, the target object contain an unique_ptr< SourceContext > instance of this class. -class SourceContext +/// Typically, the target object contain an unique_ptr< DataContext > instance of this class. +class DataContext { public: /** - * @brief Construct a new SourceContext object. + * @brief Construct a new DataContext object. * @param objectName the target object name - * @param isFileContext true if this Context is a FileContext (see isFileContext for more infos) + * @param isDataFileContext true if this Context is a DataFileContext (see isDataFileContext for more infos) */ - SourceContext( string const & objectName, bool isFileContext ); + DataContext( string const & objectName, bool isDataFileContext ); /** * @return A string that mention all the known informations to retrieve from where the target @@ -57,31 +57,31 @@ class SourceContext { return m_objectName; } /** - * @brief In some cases, we need to know if a SourceContext is from a file. It means that it - * is a more user-friendly information compared to other SourceContext classes. + * @brief In some cases, we need to know if a DataContext is from a file. It means that it + * is a more user-friendly information compared to other DataContext classes. * @return true if the context is from a file. */ - bool isFileContext() const - { return m_isFileContext; } + bool isDataFileContext() const + { return m_isDataFileContext; } /** * @brief Insert toString() result in a stream. */ - friend std::ostream & operator<<( std::ostream & os, const SourceContext & dt ); + friend std::ostream & operator<<( std::ostream & os, const DataContext & dt ); protected: /// see getObjectName() string const m_objectName; - /// see isFileContext() - bool const m_isFileContext; + /// see isDataFileContext() + bool const m_isDataFileContext; }; /// Helps to know where a Group is in the hierarchy. -/// See SourceContext class for more infos. -class GroupContext : public SourceContext +/// See DataContext class for more infos. +class GroupContext : public DataContext { public: @@ -97,7 +97,7 @@ class GroupContext : public SourceContext Group & getGroup() const; /** - * @copydoc SourceContext::toString() + * @copydoc DataContext::toString() */ virtual string toString() const; @@ -116,7 +116,7 @@ class GroupContext : public SourceContext }; /// Helps to know the source context of a Wrapper in the hierarchy, or in the source file, if possible. -/// See SourceContext class for more infos. +/// See DataContext class for more infos. class WrapperContext final : public GroupContext { public: @@ -127,26 +127,26 @@ class WrapperContext final : public GroupContext WrapperContext( WrapperBase & wrapper ); /** - * @copydoc SourceContext::toString() + * @copydoc DataContext::toString() */ virtual string toString() const; }; -/// Helps to know from where a Group or a Wrapper has been declared in the source file. -/// See SourceContext class for more infos. -class FileContext final : public SourceContext +/// Helps to know from where a Group or a Wrapper has been declared in its source file (a xml typically). +/// See DataContext class for more infos. +class DataFileContext final : public DataContext { public: /** * @brief Construct the file context of a Group from an xml node. */ - FileContext( Group & group, xmlWrapper::xmlNodePos const & nodePos, string const & nodeTagName ); + DataFileContext( Group & group, xmlWrapper::xmlNodePos const & nodePos, string const & nodeTagName ); /** * @brief Construct the file context of a Group from an xml attribute. */ - FileContext( WrapperBase & wrapper, xmlWrapper::xmlAttributePos const & attPos ); + DataFileContext( WrapperBase & wrapper, xmlWrapper::xmlAttributePos const & attPos ); virtual string toString() const; diff --git a/src/coreComponents/dataRepository/Group.cpp b/src/coreComponents/dataRepository/Group.cpp index 1ebe5d5f339..bba929c1912 100644 --- a/src/coreComponents/dataRepository/Group.cpp +++ b/src/coreComponents/dataRepository/Group.cpp @@ -19,7 +19,7 @@ #include "codingUtilities/StringUtilities.hpp" #include "codingUtilities/Utilities.hpp" #include "common/TimingMacros.hpp" -#include "SourceContext.hpp" +#include "DataContext.hpp" #if defined(GEOSX_USE_PYGEOSX) #include "python/PyGroupType.hpp" #endif @@ -50,7 +50,7 @@ Group::Group( string const & name, m_restart_flags( RestartFlags::WRITE_AND_READ ), m_input_flags( InputFlags::INVALID ), m_conduitNode( rootNode[ name ] ), - m_sourceContext( std::make_unique< GroupContext >( *this ) ) + m_dataContext( std::make_unique< GroupContext >( *this ) ) {} Group::~Group() @@ -195,7 +195,7 @@ void Group::processInputFile( xmlWrapper::xmlDocument const & xmlDocument, if( nodePos.isFound() ) { - m_sourceContext = std::make_unique< FileContext >( *this, nodePos, targetNode.name() ); + m_dataContext = std::make_unique< DataFileContext >( *this, nodePos, targetNode.name() ); } @@ -217,7 +217,7 @@ void Group::processInputFile( xmlWrapper::xmlDocument const & xmlDocument, GEOS_FMT( "XML Node at '{}' with name={} contains unused attribute '{}'.\n" "Valid attributes are:\n{}\nFor more details, please refer to documentation at:\n" "http://geosx-geosx.readthedocs-hosted.com/en/latest/docs/sphinx/userGuide/Index.html", - targetNode.path(), m_sourceContext->toString(), attributeName, + targetNode.path(), m_dataContext->toString(), attributeName, dumpInputOptions() ), InputError ); } @@ -396,7 +396,7 @@ localIndex Group::packImpl( buffer_unit_type * & buffer, } else { - GEOS_ERROR( "Wrapper " << wrapperName << " not found in Group " << getSourceContext() << "." ); + GEOS_ERROR( "Wrapper " << wrapperName << " not found in Group " << getDataContext() << "." ); } } diff --git a/src/coreComponents/dataRepository/Group.hpp b/src/coreComponents/dataRepository/Group.hpp index ca917eeadf2..14799d90364 100644 --- a/src/coreComponents/dataRepository/Group.hpp +++ b/src/coreComponents/dataRepository/Group.hpp @@ -26,7 +26,7 @@ #include "RestartFlags.hpp" #include "Wrapper.hpp" #include "xmlWrapper.hpp" -#include "SourceContext.hpp" +#include "DataContext.hpp" #include @@ -335,7 +335,7 @@ class Group { Group * const child = m_subGroups[ key ]; GEOS_THROW_IF( child == nullptr, - "Group " << getSourceContext() << " has no child named " << key << std::endl + "Group " << getDataContext() << " has no child named " << key << std::endl << dumpSubGroupsNames(), std::domain_error ); @@ -350,7 +350,7 @@ class Group { Group const * const child = m_subGroups[ key ]; GEOS_THROW_IF( child == nullptr, - "Group " << getSourceContext() << " has no child named " << key << std::endl + "Group " << getDataContext() << " has no child named " << key << std::endl << dumpSubGroupsNames(), std::domain_error ); @@ -1073,7 +1073,7 @@ class Group { WrapperBase const * const wrapper = m_wrappers[ key ]; GEOS_THROW_IF( wrapper == nullptr, - "Group " << getSourceContext() << " has no wrapper named " << key << std::endl + "Group " << getDataContext() << " has no wrapper named " << key << std::endl << dumpWrappersNames(), std::domain_error ); @@ -1088,7 +1088,7 @@ class Group { WrapperBase * const wrapper = m_wrappers[ key ]; GEOS_THROW_IF( wrapper == nullptr, - "Group " << getSourceContext() << " has no wrapper named " << key << std::endl + "Group " << getDataContext() << " has no wrapper named " << key << std::endl << dumpWrappersNames(), std::domain_error ); @@ -1288,10 +1288,10 @@ class Group string getPath() const; /** - * @return A SourceContext object that can helps to contextualize this Group. + * @return A DataContext object that can helps to contextualize this Group. */ - SourceContext const & getSourceContext() const - { return *m_sourceContext; } + DataContext const & getDataContext() const + { return *m_dataContext; } /** * @brief Access the group's parent. @@ -1300,7 +1300,7 @@ class Group */ Group & getParent() { - GEOS_THROW_IF( m_parent == nullptr, "Group at " << getSourceContext() << " does not have a parent.", std::domain_error ); + GEOS_THROW_IF( m_parent == nullptr, "Group at " << getDataContext() << " does not have a parent.", std::domain_error ); return *m_parent; } @@ -1309,7 +1309,7 @@ class Group */ Group const & getParent() const { - GEOS_THROW_IF( m_parent == nullptr, "Group at " << getSourceContext() << " does not have a parent.", std::domain_error ); + GEOS_THROW_IF( m_parent == nullptr, "Group at " << getDataContext() << " does not have a parent.", std::domain_error ); return *m_parent; } @@ -1554,8 +1554,8 @@ class Group /// Reference to the conduit::Node that mirrors this group conduit::Node & m_conduitNode; - /// A SourceContext object that can helps to contextualize this Group. - std::unique_ptr< SourceContext > m_sourceContext; + /// A DataContext object that can helps to contextualize this Group. + std::unique_ptr< DataContext > m_dataContext; }; diff --git a/src/coreComponents/dataRepository/Wrapper.hpp b/src/coreComponents/dataRepository/Wrapper.hpp index e8e93feeda7..bcb43272475 100644 --- a/src/coreComponents/dataRepository/Wrapper.hpp +++ b/src/coreComponents/dataRepository/Wrapper.hpp @@ -641,7 +641,7 @@ class Wrapper final : public WrapperBase } if( m_successfulReadFromInput ) - createSourceContext( nodePos ); + createDataContext( nodePos ); return true; } diff --git a/src/coreComponents/dataRepository/WrapperBase.cpp b/src/coreComponents/dataRepository/WrapperBase.cpp index d4f2da97bf1..2923727e2a8 100644 --- a/src/coreComponents/dataRepository/WrapperBase.cpp +++ b/src/coreComponents/dataRepository/WrapperBase.cpp @@ -18,7 +18,7 @@ #include "Group.hpp" #include "RestartFlags.hpp" -#include "SourceContext.hpp" +#include "DataContext.hpp" namespace geos @@ -39,7 +39,7 @@ WrapperBase::WrapperBase( string const & name, m_description(), m_registeringObjects(), m_conduitNode( parent.getConduitNode()[ name ] ), - m_sourceContext( std::make_unique< WrapperContext >( *this ) ) + m_dataContext( std::make_unique< WrapperContext >( *this ) ) {} @@ -106,12 +106,12 @@ int WrapperBase::setTotalviewDisplay() const } #endif -void WrapperBase::createSourceContext( xmlWrapper::xmlNodePos const & nodePos ) +void WrapperBase::createDataContext( xmlWrapper::xmlNodePos const & nodePos ) { xmlWrapper::xmlAttributePos attPos = nodePos.getAttributeLine( m_name ); if( nodePos.isFound() && attPos.isFound() ) { - m_sourceContext = std::make_unique< FileContext >( *this, attPos ); + m_dataContext = std::make_unique< DataFileContext >( *this, attPos ); } } diff --git a/src/coreComponents/dataRepository/WrapperBase.hpp b/src/coreComponents/dataRepository/WrapperBase.hpp index e9eb87d38d0..eadc46902a4 100644 --- a/src/coreComponents/dataRepository/WrapperBase.hpp +++ b/src/coreComponents/dataRepository/WrapperBase.hpp @@ -45,7 +45,7 @@ namespace dataRepository { class Group; -class SourceContext; +class DataContext; /** * @class WrapperBase @@ -415,10 +415,10 @@ class WrapperBase string getPath() const; /** - * @return A SourceContext object that can helps to contextualize this Group. + * @return A DataContext object that can helps to contextualize this Group. */ - SourceContext const & getSourceContext() const - { return *m_sourceContext; } + DataContext const & getDataContext() const + { return *m_dataContext; } /** * @brief Return the group that contains this Wrapper. @@ -633,10 +633,10 @@ class WrapperBase /// @endcond /** - * @brief Sets the m_sourceContext to a FileContext by retrieving the attribute file line. + * @brief Sets the m_dataContext to a DataFileContext by retrieving the attribute file line. * @param nodePos the xml node position of the node containing this wrapper source attribute. */ - void createSourceContext( xmlWrapper::xmlNodePos const & nodePos ); + void createDataContext( xmlWrapper::xmlNodePos const & nodePos ); /** * @brief Helper method to process an exception that has been thrown during xml parsing. @@ -677,8 +677,8 @@ class WrapperBase /// A reference to the corresponding conduit::Node. conduit::Node & m_conduitNode; - /// A SourceContext object that can helps to contextualize this Group. - std::unique_ptr< SourceContext > m_sourceContext; + /// A DataContext object that can helps to contextualize this Group. + std::unique_ptr< DataContext > m_dataContext; private: diff --git a/src/coreComponents/unitTests/xmlTests/testXMLFile.cpp b/src/coreComponents/unitTests/xmlTests/testXMLFile.cpp index 5f0ff40eb96..d517d00f20c 100644 --- a/src/coreComponents/unitTests/xmlTests/testXMLFile.cpp +++ b/src/coreComponents/unitTests/xmlTests/testXMLFile.cpp @@ -19,7 +19,7 @@ #include "fieldSpecification/FieldSpecificationManager.hpp" #include "fieldSpecification/FieldSpecificationBase.hpp" #include "dataRepository/Group.hpp" -#include "dataRepository/SourceContext.hpp" +#include "dataRepository/DataContext.hpp" // TPL includes #include @@ -84,18 +84,18 @@ void getElementsRecursive( xmlDocument const & document, xmlNode const & targetN } } /** - * @brief Verify if the FileContext data correctly locates from where the object comes from (in the + * @brief Verify if the DataFileContext data correctly locates from where the object comes from (in the * correct document/include, at the correct line and at the correct character offset). * @param document root xml document (which potentially contains includes). - * @param fileContext the FileContext to verify. + * @param fileContext the DataFileContext to verify. */ -void verifyFileContext( FileContext const & fileContext, - xmlDocument const & document ) +void verifyDataFileContext( DataFileContext const & fileContext, + xmlDocument const & document ) { string const & strToVerify = fileContext.getTypeName(); string const & errInfos = "Verifying " + strToVerify + " in " + fileContext.toString(); - // verifying if all FileContext data have been found + // verifying if all DataFileContext data have been found EXPECT_FALSE( fileContext.getFilePath().empty() ) << errInfos; EXPECT_FALSE( fileContext.getObjectName().empty() ) << errInfos; EXPECT_FALSE( fileContext.getTypeName().empty() ) << errInfos; @@ -114,7 +114,7 @@ void verifyFileContext( FileContext const & fileContext, EXPECT_LT( fileContext.getOffset() + strToVerify.size(), buffer->size() ) << errInfos; EXPECT_EQ( strToVerify, buffer->substr( fileContext.getOffset(), strToVerify.size() ) ) << errInfos; - // Were trying to reach the line return by FileContext::getLine() + // Were trying to reach the line return by DataFileContext::getLine() for( size_t offset=0; offset < buffer->size() && curLine < fileContext.getLine(); ++offset ) @@ -140,38 +140,38 @@ void verifyFileContext( FileContext const & fileContext, } /** * @brief Verifies if the specified group, its children, and the associated wrappers have a - * FileContext object that correctly locate from where the objects where declared in the + * DataFileContext object that correctly locate from where the objects where declared in the * source file. * @param document root xml document (which potentially contains includes). * @param group The group to (recursively) verify. * @param verifCount a set that will be filled with the names, with the form * "GroupName" or "GroupName/WrapperName". */ -void verifyGroupFileContextRecursive( xmlDocument const & document, Group const & group, - std::set< string > & verifications ) +void verifyGroupDataFileContextRecursive( xmlDocument const & document, Group const & group, + std::set< string > & verifications ) { // GEOS_LOG( "Verifying "<< group.getName()); - if( group.getSourceContext().isFileContext() ) + if( group.getDataContext().isDataFileContext() ) { - verifyFileContext( dynamic_cast< FileContext const & >( group.getSourceContext() ), - document ); + verifyDataFileContext( dynamic_cast< DataFileContext const & >( group.getDataContext() ), + document ); verifications.emplace( group.getName() ); } for( auto const & wrapperIterator : group.wrappers() ) { WrapperBase const * wrapper = wrapperIterator.second; - if( wrapper->getSourceContext().isFileContext() ) + if( wrapper->getDataContext().isDataFileContext() ) { - verifyFileContext( dynamic_cast< FileContext const & >( wrapper->getSourceContext() ), - document ); + verifyDataFileContext( dynamic_cast< DataFileContext const & >( wrapper->getDataContext() ), + document ); verifications.emplace( group.getName() + '/' + wrapper->getName() ); } } for( auto subGroup : group.getSubGroups() ) { - verifyGroupFileContextRecursive( document, *subGroup.second, verifications ); + verifyGroupDataFileContextRecursive( document, *subGroup.second, verifications ); } } @@ -217,7 +217,7 @@ TEST( testXML, testXMLFileLines ) getElementsRecursive( xmlDocument, xmlDocument.root().child( "Problem" ), expectedElements ); std::set< string > verifiedElements; - verifyGroupFileContextRecursive( xmlDocument, problemManager, verifiedElements ); + verifyGroupDataFileContextRecursive( xmlDocument, problemManager, verifiedElements ); std::set< string > notFound = getDifference( expectedElements, verifiedElements ); EXPECT_TRUE( notFound.empty() ) << "Infos : There should not exists xml element that were not in " @@ -226,7 +226,7 @@ TEST( testXML, testXMLFileLines ) std::set< string > notExpected = getDifference( verifiedElements, expectedElements ); EXPECT_TRUE( notExpected.empty() ) << "Infos : There should not exists an object in the Group " - "hierarchy that contains a FileContext but which were not " + "hierarchy that contains a DataFileContext but which were not " "declared in the Xml.\nNot in XML hierarchy : {" << stringutilities::join( notExpected, "," ) << "}"; } From 7b793c94f2fc38e0588cefe2357f64d9c9d828d6 Mon Sep 17 00:00:00 2001 From: Jeanne Pellerin <48675268+jeannepellerin@users.noreply.github.com> Date: Mon, 24 Apr 2023 17:16:25 +0200 Subject: [PATCH 12/85] comment update in Logger.hpp --- src/coreComponents/common/Logger.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/coreComponents/common/Logger.hpp b/src/coreComponents/common/Logger.hpp index cb69fdfb2df..da7f76fd9a6 100644 --- a/src/coreComponents/common/Logger.hpp +++ b/src/coreComponents/common/Logger.hpp @@ -478,9 +478,9 @@ struct InputError : public std::runtime_error {} /** - * @brief Construct an InputError from an underlying exception. - * @param subException An exception to base this new one on. - * @param msgToInsert The error message. It will be inserted into the one inside of subException. + * @brief Constructs an InputError from an underlying exception. + * @param subException The exception on which the created one is based. + * @param msgToInsert The error message that will be inserted in the subException error message. */ InputError( std::exception const & subException, std::string const & msgToInsert ); }; From e1af0c6f54ce5be489334a05598138e05fec1729 Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Mon, 24 Apr 2023 17:31:17 +0200 Subject: [PATCH 13/85] - Added xml line to messages from PR 2341 & 2357 - code refactoring + doc edit --- .../dataRepository/DataContext.cpp | 11 +++++--- src/coreComponents/dataRepository/Wrapper.hpp | 7 +++--- .../dataRepository/WrapperBase.cpp | 23 +++++++++++++---- .../dataRepository/WrapperBase.hpp | 3 ++- .../dataRepository/xmlWrapper.cpp | 25 +++++++++++++++---- .../dataRepository/xmlWrapper.hpp | 25 +++++++------------ .../FieldSpecificationBase.cpp | 2 +- .../timeHistory/HistoryCollectionBase.cpp | 2 +- 8 files changed, 62 insertions(+), 36 deletions(-) diff --git a/src/coreComponents/dataRepository/DataContext.cpp b/src/coreComponents/dataRepository/DataContext.cpp index e5150170334..6ecdcbfd1dd 100644 --- a/src/coreComponents/dataRepository/DataContext.cpp +++ b/src/coreComponents/dataRepository/DataContext.cpp @@ -95,15 +95,18 @@ DataFileContext::DataFileContext( WrapperBase & wrapper, xmlWrapper::xmlAttribut string DataFileContext::toString() const { std::ostringstream oss; - oss << m_objectName << " (" << m_filePath; + oss << m_objectName; if( m_line != xmlWrapper::xmlDocument::npos ) { - oss << ": l." << m_line << ")"; + oss << " (" << splitPath( m_filePath ).second << ", l." << m_line << ")"; + } + else if ( m_offset != xmlWrapper::xmlDocument::npos ) + { + oss << " (" << splitPath( m_filePath ).second << ", offset " << m_offset << ")"; } else { - // line hasn't been found, filename is probably wrong too, we just output the character offset. - oss << ": offset " << m_offset << ")"; + oss << " (Source file not found)"; } return oss.str(); } diff --git a/src/coreComponents/dataRepository/Wrapper.hpp b/src/coreComponents/dataRepository/Wrapper.hpp index bcb43272475..fcb013ab7cf 100644 --- a/src/coreComponents/dataRepository/Wrapper.hpp +++ b/src/coreComponents/dataRepository/Wrapper.hpp @@ -621,10 +621,11 @@ class Wrapper final : public WrapperBase targetNode, inputFlag == InputFlags::REQUIRED ); GEOS_THROW_IF( !m_successfulReadFromInput, - GEOS_FMT( "XML Node '{}' with name='{}' is missing required attribute '{}'." + GEOS_FMT( "XML Node {} ({}) with name={} is missing required attribute {}." "Available options are:\n{}\nFor more details, please refer to documentation at:\n" "http://geosx-geosx.readthedocs-hosted.com/en/latest/docs/sphinx/userGuide/Index.html", - targetNode.path(), targetNode.attribute( "name" ).value(), getName(), dumpInputOptions( true ) ), + targetNode.name(), nodePos.toString(), targetNode.attribute( "name" ).value(), + getName(), dumpInputOptions( true ) ), InputError ); } else @@ -637,7 +638,7 @@ class Wrapper final : public WrapperBase } catch( std::exception const & ex ) { - processInputException( ex, targetNode ); + processInputException( ex, targetNode, nodePos ); } if( m_successfulReadFromInput ) diff --git a/src/coreComponents/dataRepository/WrapperBase.cpp b/src/coreComponents/dataRepository/WrapperBase.cpp index 2923727e2a8..4795b76db1f 100644 --- a/src/coreComponents/dataRepository/WrapperBase.cpp +++ b/src/coreComponents/dataRepository/WrapperBase.cpp @@ -116,15 +116,28 @@ void WrapperBase::createDataContext( xmlWrapper::xmlNodePos const & nodePos ) } void WrapperBase::processInputException( std::exception const & ex, - xmlWrapper::xmlNode const & targetNode ) const + xmlWrapper::xmlNode const & targetNode, + xmlWrapper::xmlNodePos const & nodePos ) const { xmlWrapper::xmlAttribute const & attribute = targetNode.attribute( getName().c_str() ); string const inputStr = string( attribute.value() ); - string subExStr = string( "***** XML parsing error in " ) + targetNode.path() + - " (name=" + targetNode.attribute( "name" ).value() + ")/" + attribute.name() + - "\n***** Input value: '" + inputStr + "'\n"; + xmlWrapper::xmlAttributePos const attPos = nodePos.getAttributeLine( inputStr ); + std::ostringstream oss; - throw InputError( subExStr + ex.what() ); + oss << "***** XML parsing error at node "; + if( nodePos.isFound() ) + { + oss << "named " << targetNode.attribute( "name" ).value() << ", attribute " << attribute.name() + << '(' << splitPath( attPos.isFound() ? attPos.filePath : nodePos.filePath ).second + << ", l." << ( attPos.isFound() ? attPos.line : nodePos.line ) << ")."; + } + else + { + oss << targetNode.path() << " (name=" << targetNode.attribute( "name" ).value() << ")/" << attribute.name(); + } + oss << "\n***** Input value: '" << inputStr << "'\n" << ex.what(); + + throw InputError( oss.str() ); } diff --git a/src/coreComponents/dataRepository/WrapperBase.hpp b/src/coreComponents/dataRepository/WrapperBase.hpp index eadc46902a4..d3c70e28913 100644 --- a/src/coreComponents/dataRepository/WrapperBase.hpp +++ b/src/coreComponents/dataRepository/WrapperBase.hpp @@ -643,7 +643,8 @@ class WrapperBase * @param ex The caught exception. * @param targetNode The node from which this Group is interpreted. */ - void processInputException( std::exception const & ex, xmlWrapper::xmlNode const & targetNode ) const; + void processInputException( std::exception const & ex, xmlWrapper::xmlNode const & targetNode, + xmlWrapper::xmlNodePos const & nodePos ) const; protected: diff --git a/src/coreComponents/dataRepository/xmlWrapper.cpp b/src/coreComponents/dataRepository/xmlWrapper.cpp index b6dd5046e73..b66e93598a4 100644 --- a/src/coreComponents/dataRepository/xmlWrapper.cpp +++ b/src/coreComponents/dataRepository/xmlWrapper.cpp @@ -328,11 +328,8 @@ xmlNodePos xmlDocument::getNodePosition( xmlNode const & node ) const xmlNodePos::xmlNodePos( xmlDocument const & document_, string const & filePath_, size_t line_, size_t offsetInLine_, size_t offset_ ): - document( document_ ), - filePath( filePath_ ), - line( line_ ), - offsetInLine( offsetInLine_ ), - offset( offset_ ) + xmlAttributePos( filePath_, line_, offsetInLine_, offset_ ), + document( document_ ) {} bool xmlNodePos::isFound() const @@ -377,6 +374,24 @@ xmlAttributePos::xmlAttributePos( string const & filePath_, size_t line_, size_t bool xmlAttributePos::isFound() const { return offset != xmlDocument::npos; } +string xmlAttributePos::toString() const +{ + if( line != xmlDocument::npos ) + { + return splitPath( filePath ).second + ", l." + std::to_string( line ); + } + else if( offset != xmlDocument::npos ) + { + // line hasn't been found, we output the character offset. + return splitPath( filePath ).second + ", offset " + std::to_string( offset ); + } + else + { + // offset hasn't been found, filename is probably wrong too, we just output an error. + return "Source file not found"; + } +} + } /* namespace xmlWrapper */ } /* namespace geos */ diff --git a/src/coreComponents/dataRepository/xmlWrapper.hpp b/src/coreComponents/dataRepository/xmlWrapper.hpp index 8f24f6a7c8e..fe50c7bed5d 100644 --- a/src/coreComponents/dataRepository/xmlWrapper.hpp +++ b/src/coreComponents/dataRepository/xmlWrapper.hpp @@ -64,15 +64,15 @@ class xmlDocument; /// Stores the source file path, and position in the file of a xml attribute. struct xmlAttributePos { - /// Path of the file containing this node + /// Path of the file containing this element string filePath; - /// Line of this node in the file that contains it (starting from 1). + /// Line of this element in the file that contains it (starting from 1). /// Equals to xmlDocument::npos if it couldn't be determined. size_t const line; - /// Character offset of this node in the line that contains it (starting from 1) + /// Character offset of this element in the line that contains it (starting from 1) /// Equals to xmlDocument::npos if it couldn't be determined. size_t const offsetInLine; - /// Character offset of this node in the file that contains it (starting from 0) + /// Character offset of this element in the file that contains it (starting from 0) /// Equals to xmlDocument::npos if it couldn't be determined. size_t const offset; @@ -84,25 +84,18 @@ struct xmlAttributePos * @return false if the position is undetermined. */ bool isFound() const; + /** + * @return returns a string to locate the node (typically the source file and line). + */ + string toString() const; }; /// Stores the source document, file path, and position in the file of a xml node. /// It can help to retrieve the position (xmlAttributePos) of a xml attribute. -struct xmlNodePos +struct xmlNodePos : xmlAttributePos { /// Reference of the main xmlDocument that contains all original file buffers. xmlDocument const & document; - /// Path of the file containing this node - string const filePath; - /// Line of this node in the file that contains it (starting from 1) - /// Equals to xmlDocument::npos if it couldn't be determined. - size_t const line; - /// Character offset of this node in the line that contains it (starting from 1) - /// Equals to xmlDocument::npos if it couldn't be determined. - size_t const offsetInLine; - /// Character offset of this node in the file that contains it (starting from 0) - /// Equals to xmlDocument::npos if it couldn't be determined. - size_t const offset; /** * @brief Constructor of this struct. diff --git a/src/coreComponents/fieldSpecification/FieldSpecificationBase.cpp b/src/coreComponents/fieldSpecification/FieldSpecificationBase.cpp index 521eff2dc5e..3ee4f93b348 100644 --- a/src/coreComponents/fieldSpecification/FieldSpecificationBase.cpp +++ b/src/coreComponents/fieldSpecification/FieldSpecificationBase.cpp @@ -103,7 +103,7 @@ void FieldSpecificationBase::setMeshObjectPath( Group const & meshBodies ) } catch( InputError const & e ) { - throw InputError( e, getName() + " has a wrong objectPath: " + m_objectPath + "\n" ); + throw InputError( e, getDataContext().toString() + " has a wrong objectPath: " + m_objectPath + "\n" ); } } diff --git a/src/coreComponents/fileIO/timeHistory/HistoryCollectionBase.cpp b/src/coreComponents/fileIO/timeHistory/HistoryCollectionBase.cpp index ba32826c9ae..bf6af727210 100644 --- a/src/coreComponents/fileIO/timeHistory/HistoryCollectionBase.cpp +++ b/src/coreComponents/fileIO/timeHistory/HistoryCollectionBase.cpp @@ -205,7 +205,7 @@ dataRepository::Group const * HistoryCollectionBase::getTargetObject( DomainPart } catch( std::domain_error const & e ) { - throw InputError( e, getName() + " has a wrong objectPath: " + objectPath + "\n" ); + throw InputError( e, getDataContext().toString() + " has a wrong objectPath: " + objectPath + "\n" ); } } From 3e9580390bea5c19e4e4f9a810f483b2a334b715 Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Thu, 27 Apr 2023 10:53:58 +0200 Subject: [PATCH 14/85] Added getWrapperDataContext() --- src/coreComponents/dataRepository/Group.hpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/coreComponents/dataRepository/Group.hpp b/src/coreComponents/dataRepository/Group.hpp index 14799d90364..8f4efe23315 100644 --- a/src/coreComponents/dataRepository/Group.hpp +++ b/src/coreComponents/dataRepository/Group.hpp @@ -1293,6 +1293,16 @@ class Group DataContext const & getDataContext() const { return *m_dataContext; } + /** + * @brief Get the DataContext object that can help to contextualize a wrapper stored in this Group. + * @tparam KEY The lookup type. + * @param key The value used to lookup the wrapper. + * @throw std::domain_error if the wrapper doesn't exist. + */ + template< typename KEY > + DataContext const & getWrapperDataContext( KEY key ) const + { return getWrapperBase( key ).getDataContext(); } + /** * @brief Access the group's parent. * @return reference to parent Group From cbfa5077f3a00a4963a2435e51b920abc9ac56f1 Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Thu, 27 Apr 2023 15:49:34 +0200 Subject: [PATCH 15/85] uncrustify + documentation corrections --- .../dataRepository/DataContext.cpp | 5 +- .../dataRepository/DataContext.hpp | 45 +++++++++--------- src/coreComponents/dataRepository/Group.cpp | 7 +-- src/coreComponents/dataRepository/Group.hpp | 11 +++-- .../dataRepository/WrapperBase.cpp | 2 +- .../dataRepository/WrapperBase.hpp | 3 +- .../dataRepository/xmlWrapper.cpp | 32 ++++++------- .../dataRepository/xmlWrapper.hpp | 46 ++++++++----------- src/coreComponents/mesh/MeshObjectPath.cpp | 8 ++-- .../unitTests/xmlTests/testXMLFile.cpp | 30 ++++++------ 10 files changed, 94 insertions(+), 95 deletions(-) diff --git a/src/coreComponents/dataRepository/DataContext.cpp b/src/coreComponents/dataRepository/DataContext.cpp index 6ecdcbfd1dd..fc92bccd06f 100644 --- a/src/coreComponents/dataRepository/DataContext.cpp +++ b/src/coreComponents/dataRepository/DataContext.cpp @@ -16,7 +16,6 @@ * @file DataContext.cpp */ -//#include "codingUtilities/StringUtilities.hpp" #include "Group.hpp" #include "WrapperBase.hpp" #include "../mainInterface/ProblemManager.hpp" @@ -74,7 +73,7 @@ string WrapperContext::toString() const DataFileContext::DataFileContext( Group & group, xmlWrapper::xmlNodePos const & nodePos, - string const & nodeTagName ): + string const & nodeTagName ): DataContext( group.getName(), true ), m_typeName( nodeTagName ), m_filePath( nodePos.filePath ), @@ -100,7 +99,7 @@ string DataFileContext::toString() const { oss << " (" << splitPath( m_filePath ).second << ", l." << m_line << ")"; } - else if ( m_offset != xmlWrapper::xmlDocument::npos ) + else if( m_offset != xmlWrapper::xmlDocument::npos ) { oss << " (" << splitPath( m_filePath ).second << ", offset " << m_offset << ")"; } diff --git a/src/coreComponents/dataRepository/DataContext.hpp b/src/coreComponents/dataRepository/DataContext.hpp index 403b1018f6a..0fb2918cc20 100644 --- a/src/coreComponents/dataRepository/DataContext.hpp +++ b/src/coreComponents/dataRepository/DataContext.hpp @@ -16,8 +16,8 @@ * @file DataContext.hpp */ -#ifndef GEOS_DATAREPOSITORY_SOURCECONTEXT_HPP_ -#define GEOS_DATAREPOSITORY_SOURCECONTEXT_HPP_ +#ifndef GEOS_DATAREPOSITORY_DATACONTEXT_HPP_ +#define GEOS_DATAREPOSITORY_DATACONTEXT_HPP_ #include "common/DataTypes.hpp" #include "common/Logger.hpp" @@ -32,10 +32,10 @@ namespace dataRepository class Group; class WrapperBase; -/// This abstract class stores data that helps to retrieve an object: -/// - from which position in a file (if applicable), see DataFileContext, -/// - where it is located in the data hierarchy, see GroupContext and WrapperContext. -/// Typically, the target object contain an unique_ptr< DataContext > instance of this class. +/// DataContext is an abstract class storing contextual information on an object: +/// - its line position in a file, if applicable, implementation in DataFileContext, +/// - its location in the data hierarchy, implementation in GroupContext and WrapperContext. +/// Typically, the target object contains an unique_ptr< DataContext > instance of this class. class DataContext { public: @@ -43,7 +43,7 @@ class DataContext /** * @brief Construct a new DataContext object. * @param objectName the target object name - * @param isDataFileContext true if this Context is a DataFileContext (see isDataFileContext for more infos) + * @param isDataFileContext true if this Context is a DataFileContext */ DataContext( string const & objectName, bool isDataFileContext ); @@ -57,15 +57,14 @@ class DataContext { return m_objectName; } /** - * @brief In some cases, we need to know if a DataContext is from a file. It means that it - * is a more user-friendly information compared to other DataContext classes. + * @brief Flag on availability of file information. Used to provide more user-friendly information. * @return true if the context is from a file. */ bool isDataFileContext() const { return m_isDataFileContext; } /** - * @brief Insert toString() result in a stream. + * @brief Insert contextual information in the provided stream. */ friend std::ostream & operator<<( std::ostream & os, const DataContext & dt ); @@ -80,24 +79,24 @@ class DataContext }; /// Helps to know where a Group is in the hierarchy. -/// See DataContext class for more infos. +/// See DataContext class for more info. class GroupContext : public DataContext { public: /** * @brief Construct a new GroupContext object - * @param group see getGroup() + * @param group The reference to the Group related to this GroupContext. */ GroupContext( Group & group ); /** - * @brief Get the target Group object (wrapper's parent in case of a WrapperContext). + * @brief Get the reference to the Group related to this GroupContext. */ Group & getGroup() const; /** - * @copydoc DataContext::toString() + * @return the group path. */ virtual string toString() const; @@ -105,18 +104,18 @@ class GroupContext : public DataContext /** * @brief Construct a new GroupContext object - * @param group see getGroup() + * @param group The reference to the Group related to this GroupContext. * @param objectName Target object name. */ GroupContext( Group & group, string const & objectName ); - /// see getGroup() + /// The reference to the Group related to this GroupContext. Group & m_group; }; -/// Helps to know the source context of a Wrapper in the hierarchy, or in the source file, if possible. -/// See DataContext class for more infos. +/// Dedicated implementation of GroupContext for Wrapper. +/// See DataContext class for more info. class WrapperContext final : public GroupContext { public: @@ -127,14 +126,13 @@ class WrapperContext final : public GroupContext WrapperContext( WrapperBase & wrapper ); /** - * @copydoc DataContext::toString() + * @return the parent group DataContext followed by the wrapper name. */ virtual string toString() const; }; -/// Helps to know from where a Group or a Wrapper has been declared in its source file (a xml typically). -/// See DataContext class for more infos. +/// Stores information to retrieve where a Group or Wrapper has been declared in the input source file (e.g. XML) class DataFileContext final : public DataContext { public: @@ -148,6 +146,9 @@ class DataFileContext final : public DataContext */ DataFileContext( WrapperBase & wrapper, xmlWrapper::xmlAttributePos const & attPos ); + /** + * @return the target object name followed by the the file and line declaring it. + */ virtual string toString() const; /** @@ -202,4 +203,4 @@ class DataFileContext final : public DataContext } /* namespace geos */ -#endif /* GEOS_DATAREPOSITORY_INPUTFLAGS_HPP_ */ +#endif /* GEOS_DATAREPOSITORY_DATACONTEXT_HPP_ */ diff --git a/src/coreComponents/dataRepository/Group.cpp b/src/coreComponents/dataRepository/Group.cpp index bba929c1912..15ad48e92f3 100644 --- a/src/coreComponents/dataRepository/Group.cpp +++ b/src/coreComponents/dataRepository/Group.cpp @@ -33,7 +33,7 @@ Group::Group( string const & name, Group * const parent ): Group( name, parent->getConduitNode() ) { - GEOS_ERROR_IF( parent == nullptr, "Should not be null." ); + GEOS_ERROR_IF( parent == nullptr, "Should not be null (for Group named " << name << ")." ); m_parent = parent; } @@ -74,7 +74,8 @@ WrapperBase & Group::registerWrapper( std::unique_ptr< WrapperBase > wrapper ) void Group::deregisterWrapper( string const & name ) { - GEOS_ERROR_IF( !hasWrapper( name ), "Wrapper " << name << " doesn't exist." ); + GEOS_ERROR_IF( !hasWrapper( name ), + "Wrapper " << name << " doesn't exist in Group" << getDataContext() << '.' ); m_wrappers.erase( name ); m_conduitNode.remove( name ); } @@ -128,7 +129,7 @@ void Group::reserve( indexType const newSize ) string Group::getPath() const { - // In the Conduit node heirarchy everything begins with 'Problem', we should change it so that + // In the Conduit node hierarchy everything begins with 'Problem', we should change it so that // the ProblemManager actually uses the root Conduit Node but that will require a full rebaseline. string const noProblem = getConduitNode().path().substr( stringutilities::cstrlen( dataRepository::keys::ProblemManager ) ); return noProblem.empty() ? "/" : noProblem; diff --git a/src/coreComponents/dataRepository/Group.hpp b/src/coreComponents/dataRepository/Group.hpp index 8f4efe23315..712c0299ad5 100644 --- a/src/coreComponents/dataRepository/Group.hpp +++ b/src/coreComponents/dataRepository/Group.hpp @@ -1288,20 +1288,22 @@ class Group string getPath() const; /** - * @return A DataContext object that can helps to contextualize this Group. + * @return DataContext object that that stores contextual information on this group that can be + * used in output messages. */ DataContext const & getDataContext() const { return *m_dataContext; } /** - * @brief Get the DataContext object that can help to contextualize a wrapper stored in this Group. + * @return DataContext object that that stores contextual information on a wrapper contained by + * this group that can be used in output messages. * @tparam KEY The lookup type. * @param key The value used to lookup the wrapper. * @throw std::domain_error if the wrapper doesn't exist. */ template< typename KEY > DataContext const & getWrapperDataContext( KEY key ) const - { return getWrapperBase( key ).getDataContext(); } + { return getWrapperBase< KEY >( key ).getDataContext(); } /** * @brief Access the group's parent. @@ -1564,7 +1566,8 @@ class Group /// Reference to the conduit::Node that mirrors this group conduit::Node & m_conduitNode; - /// A DataContext object that can helps to contextualize this Group. + /// A DataContext object used to provide contextual information on this Group, + /// if it is created from an input XML file, the line or offset in that file. std::unique_ptr< DataContext > m_dataContext; }; diff --git a/src/coreComponents/dataRepository/WrapperBase.cpp b/src/coreComponents/dataRepository/WrapperBase.cpp index 4795b76db1f..7c7b3d311b8 100644 --- a/src/coreComponents/dataRepository/WrapperBase.cpp +++ b/src/coreComponents/dataRepository/WrapperBase.cpp @@ -62,7 +62,7 @@ void WrapperBase::copyWrapperAttributes( WrapperBase const & source ) string WrapperBase::getPath() const { - // In the Conduit node heirarchy everything begins with 'Problem', we should change it so that + // In the Conduit node hierarchy everything begins with 'Problem', we should change it so that // the ProblemManager actually uses the root Conduit Node but that will require a full rebaseline. string const noProblem = m_conduitNode.path().substr( std::strlen( dataRepository::keys::ProblemManager ) - 1 ); return noProblem.empty() ? "/" : noProblem; diff --git a/src/coreComponents/dataRepository/WrapperBase.hpp b/src/coreComponents/dataRepository/WrapperBase.hpp index d3c70e28913..f020b3805ca 100644 --- a/src/coreComponents/dataRepository/WrapperBase.hpp +++ b/src/coreComponents/dataRepository/WrapperBase.hpp @@ -415,7 +415,8 @@ class WrapperBase string getPath() const; /** - * @return A DataContext object that can helps to contextualize this Group. + * @return DataContext object that that stores contextual information on this group that can be + * used in output messages. */ DataContext const & getDataContext() const { return *m_dataContext; } diff --git a/src/coreComponents/dataRepository/xmlWrapper.cpp b/src/coreComponents/dataRepository/xmlWrapper.cpp index b66e93598a4..1813ae571df 100644 --- a/src/coreComponents/dataRepository/xmlWrapper.cpp +++ b/src/coreComponents/dataRepository/xmlWrapper.cpp @@ -74,13 +74,13 @@ template void stringToInputVariable( Tensor< real64, 3 > & target, string const template void stringToInputVariable( Tensor< real64, 6 > & target, string const & inputValue ); /** - * @brief Adds the filePath and character offset infos on the node in filePathString + * @brief Adds the filePath and character offset info on the node in filePathString * and charOffsetString attributes. This function allow to keep track of the source * filename & offset of each node. * @param node the target node to add the informations on. * @param filePath the absolute path of the xml file containing the node. */ -void addNodeFileInfos( xmlNode node, string const & filePath ) +void addNodeFileInfo( xmlNode node, string const & filePath ) { // we keep the file path and the character offset on each node so we keep track of these // informations, even if the nodes are manipulated within the xml hierarchy. @@ -89,13 +89,13 @@ void addNodeFileInfos( xmlNode node, string const & filePath ) for( xmlNode subNode : node.children() ) { - addNodeFileInfos( subNode, filePath ); + addNodeFileInfo( subNode, filePath ); } } /** - * @brief Returns true if the addNodeFileInfos() command has been called of the specified node. + * @brief Returns true if the addNodeFileInfo() command has been called of the specified node. */ -bool xmlDocument::hasNodeFileInfos() const +bool xmlDocument::hasNodeFileInfo() const { return !first_child().attribute( filePathString ).empty(); } void xmlDocument::addIncludedXML( xmlNode & targetNode, int const level ) @@ -125,7 +125,7 @@ void xmlDocument::addIncludedXML( xmlNode & targetNode, int const level ) xmlDocument includedXmlDocument; xmlResult const result = includedXmlDocument.load_file( includedFilePath.c_str(), - hasNodeFileInfos() ); + hasNodeFileInfo() ); GEOS_THROW_IF( !result, GEOS_FMT( "Errors found while parsing included XML file {}\n" "Description: {}\nOffset: {}", includedFilePath, result.description(), result.offset ), @@ -211,30 +211,30 @@ xmlDocument::xmlDocument(): m_rootFilePath( "CodeIncludedXML" + std::to_string( documentId++ ) ) {} -xmlResult xmlDocument::load_string( const pugi::char_t * contents, bool loadNodeFileInfos, +xmlResult xmlDocument::load_string( const pugi::char_t * contents, bool loadNodeFileInfo, unsigned int options ) { xmlResult result = pugi::xml_document::load_string( contents, options ); // keeping a copy of original buffer to allow line retrieval - if( loadNodeFileInfos ) + if( loadNodeFileInfo ) { new (&m_originalBuffers) map< string, string >(); m_originalBuffers[m_rootFilePath] = string( contents ); - addNodeFileInfos( first_child(), m_rootFilePath ); + addNodeFileInfo( first_child(), m_rootFilePath ); } return result; } -xmlResult xmlDocument::load_file( const char * path, bool loadNodeFileInfos, +xmlResult xmlDocument::load_file( const char * path, bool loadNodeFileInfo, unsigned int options, pugi::xml_encoding encoding ) { xmlResult result = pugi::xml_document::load_file( path, options, encoding ); m_rootFilePath = getAbsolutePath( path ); // keeping a copy of original buffer to allow line retrieval - if( loadNodeFileInfos ) + if( loadNodeFileInfo ) { std::ifstream t( path ); std::stringstream buffer; @@ -243,23 +243,23 @@ xmlResult xmlDocument::load_file( const char * path, bool loadNodeFileInfos, new (&m_originalBuffers) map< string, string >(); m_originalBuffers[m_rootFilePath] = string( buffer.str() ); - addNodeFileInfos( first_child(), getAbsolutePath( m_rootFilePath ) ); + addNodeFileInfo( first_child(), getAbsolutePath( m_rootFilePath ) ); } return result; } -xmlResult xmlDocument::load_buffer( const void * contents, size_t size, bool loadNodeFileInfos, +xmlResult xmlDocument::load_buffer( const void * contents, size_t size, bool loadNodeFileInfo, unsigned int options, pugi::xml_encoding encoding ) { xmlResult result = pugi::xml_document::load_buffer( contents, size, options, encoding ); //keeping a copy of original buffer - if( loadNodeFileInfos ) + if( loadNodeFileInfo ) { new (&m_originalBuffers) map< string, string >(); m_originalBuffers[m_rootFilePath] = string( ( char const * )contents, size ); - addNodeFileInfos( first_child(), m_rootFilePath ); + addNodeFileInfo( first_child(), m_rootFilePath ); } return result; @@ -310,7 +310,7 @@ xmlNodePos xmlDocument::getNodePosition( xmlNode const & node ) const oss << "Node from file=" << filePath << ", path=" << node.path() << ", offset=" << offset; oss << " could not be found: "; oss << ( sourceBuffer == m_originalBuffers.end() ? - ( filePathAtt.empty() ? "Source file not found." : "Source file infos not loaded") : + ( filePathAtt.empty() ? "Source file not found." : "Source file info not loaded") : ( offset > 0 && offset + nodeName.size() < sourceBuffer->second.size() ? "The offset doesn't lead to this node." : "Offset out of bounds." ) diff --git a/src/coreComponents/dataRepository/xmlWrapper.hpp b/src/coreComponents/dataRepository/xmlWrapper.hpp index fe50c7bed5d..c4ecb4d34eb 100644 --- a/src/coreComponents/dataRepository/xmlWrapper.hpp +++ b/src/coreComponents/dataRepository/xmlWrapper.hpp @@ -66,8 +66,8 @@ struct xmlAttributePos { /// Path of the file containing this element string filePath; - /// Line of this element in the file that contains it (starting from 1). - /// Equals to xmlDocument::npos if it couldn't be determined. + /// Line where the element is defined. Start at 1. + /// Default value is xmlDocument::npos size_t const line; /// Character offset of this element in the line that contains it (starting from 1) /// Equals to xmlDocument::npos if it couldn't be determined. @@ -81,17 +81,16 @@ struct xmlAttributePos */ xmlAttributePos( string const & filePath, size_t line, size_t offsetInLine, size_t offset ); /** - * @return false if the position is undetermined. + * @return false if the position is undefined. */ bool isFound() const; /** - * @return returns a string to locate the node (typically the source file and line). + * @return a string containing the file name and position in the file. */ string toString() const; }; -/// Stores the source document, file path, and position in the file of a xml node. -/// It can help to retrieve the position (xmlAttributePos) of a xml attribute. +/// Used to retrieve the position of dataRepository::Wrapper in XML struct xmlNodePos : xmlAttributePos { /// Reference of the main xmlDocument that contains all original file buffers. @@ -102,7 +101,7 @@ struct xmlNodePos : xmlAttributePos */ xmlNodePos( xmlDocument const & document, string const & filePath, size_t line, size_t offsetInLine, size_t offset ); /** - * @return false if the position is undetermined. + * @return false if the position is undefined. */ bool isFound() const; /** @@ -117,7 +116,7 @@ struct xmlNodePos : xmlAttributePos class xmlDocument : public pugi::xml_document { public: - /// Error value for when an offset / line position is undetermined. + /// Error value for when an offset / line position is undefined. static const size_t npos; /** @@ -146,33 +145,32 @@ class xmlDocument : public pugi::xml_document string const & getFilePath() const; /** * @brief Compute the position of the given node if the node file information are loaded. - * @throws an InputError if the node position couldn't be determined despite the node source file - * informations being loaded. + * @throws an InputError if the node position is not found and source file is loaded. */ xmlNodePos getNodePosition( xmlWrapper::xmlNode const & node ) const; /** * @brief Load document from zero-terminated string. No encoding conversions are applied. * Wrapper of pugi::xml_document::load_buffer() method. - * @param loadNodeFileInfos Load the node source file infos, allowing getNodePosition() to work. + * @param loadNodeFileInfo Load the node source file info, allowing getNodePosition() to work. */ - xmlResult load_string( const pugi::char_t * contents, bool loadNodeFileInfos = false, + xmlResult load_string( const pugi::char_t * contents, bool loadNodeFileInfo = false, unsigned int options = pugi::parse_default ); /** * @brief Load document from file. Wrapper of pugi::xml_document::load_buffer() method. - * @param loadNodeFileInfos Load the node source file infos, allowing getNodePosition() to work. + * @param loadNodeFileInfo Load the node source file info, allowing getNodePosition() to work. */ - xmlResult load_file( const char * path, bool loadNodeFileInfos = false, + xmlResult load_file( const char * path, bool loadNodeFileInfo = false, unsigned int options = pugi::parse_default, pugi::xml_encoding encoding = pugi::encoding_auto ); /** * @brief Load document from buffer. Copies/converts the buffer, so it may be deleted or changed * after the function returns. Wrapper of pugi::xml_document::load_buffer() method. - * @param loadNodeFileInfos Load the node source file infos, allowing getNodePosition() to work. + * @param loadNodeFileInfo Load the node source file info, allowing getNodePosition() to work. */ - xmlResult load_buffer( const void * contents, size_t size, bool loadNodeFileInfos = false, + xmlResult load_buffer( const void * contents, size_t size, bool loadNodeFileInfo = false, unsigned int options = pugi::parse_default, pugi::xml_encoding encoding = pugi::encoding_auto ); @@ -216,20 +214,17 @@ class xmlDocument : public pugi::xml_document void addIncludedXML( xmlNode & targetNode, int level = 0 ); /** - * @brief True if loadNodeFileInfos was true during the last load_X call. + * @brief True if loadNodeFileInfo was true during the last load_X call. */ - bool hasNodeFileInfos() const; + bool hasNodeFileInfo() const; private: - /// Map containing the original buffers of the document and its includes, indexed by file path. - /// We're keeping these original buffer to retrieve the source file and line of nodes and - /// attributes (the pugixml buffer cannot help to determine these informations as it is private - /// and processed). + /// Used to retrieve node positions as pugixml buffer is private and processed. map< string, string > m_originalBuffers; /// see getFilePath() string m_rootFilePath; - // see hasNodeFileInfos() - bool m_hasNodeFileInfos; + // see hasNodeFileInfo() + bool m_hasNodeFileInfo; }; /** @@ -267,8 +262,7 @@ string buildMultipleInputXML( string_array const & inputFileList, string const & outputDir = {} ); /** - * @brief Returns if the attribute with the specified name declares metadata relative to the xml - * file (needed by the xml reader) rather than "true data" relative to geos objects. + * @return if the attribute with the specified name declares metadata relative to the xml */ bool isFileMetadataAttribute( string const & name ); diff --git a/src/coreComponents/mesh/MeshObjectPath.cpp b/src/coreComponents/mesh/MeshObjectPath.cpp index 6cdaf6bfe98..bec8fe9d6f1 100644 --- a/src/coreComponents/mesh/MeshObjectPath.cpp +++ b/src/coreComponents/mesh/MeshObjectPath.cpp @@ -211,7 +211,7 @@ void processTokenRecursive( dataRepository::Group const & parentGroup, } ); GEOS_THROW_IF( namesInRepository.empty(), - GEOS_FMT( "{0} doesn't have any children.", parentGroup.getName()), + GEOS_FMT( "{0} has no children.", parentGroup.getDataContext().toString()), InputError ); for( string const & inputEntry : stringutilities::tokenize( pathToken, " " ) ) @@ -232,9 +232,9 @@ void processTokenRecursive( dataRepository::Group const & parentGroup, } } GEOS_THROW_IF( !foundMatch, - GEOS_FMT( "{0} doesn't have a child named {1}.\n" - "{0} have the following children: {{ {2} }}", - parentGroup.getName(), + GEOS_FMT( "{0} has no child named {1}.\n" + "{0} has the following children: {{ {2} }}", + parentGroup.getDataContext().toString(), inputEntry, stringutilities::join( namesInRepository, ", " ) ), InputError ); diff --git a/src/coreComponents/unitTests/xmlTests/testXMLFile.cpp b/src/coreComponents/unitTests/xmlTests/testXMLFile.cpp index d517d00f20c..8cd68e6fac0 100644 --- a/src/coreComponents/unitTests/xmlTests/testXMLFile.cpp +++ b/src/coreComponents/unitTests/xmlTests/testXMLFile.cpp @@ -93,26 +93,26 @@ void verifyDataFileContext( DataFileContext const & fileContext, xmlDocument const & document ) { string const & strToVerify = fileContext.getTypeName(); - string const & errInfos = "Verifying " + strToVerify + " in " + fileContext.toString(); + string const & errInfo = "Verifying " + strToVerify + " in " + fileContext.toString(); // verifying if all DataFileContext data have been found - EXPECT_FALSE( fileContext.getFilePath().empty() ) << errInfos; - EXPECT_FALSE( fileContext.getObjectName().empty() ) << errInfos; - EXPECT_FALSE( fileContext.getTypeName().empty() ) << errInfos; - EXPECT_NE( fileContext.getOffset(), xmlDocument::npos ) << errInfos; - EXPECT_NE( fileContext.getLine(), xmlDocument::npos ) << errInfos; - EXPECT_NE( fileContext.getOffsetInLine(), xmlDocument::npos ) << errInfos; + EXPECT_FALSE( fileContext.getFilePath().empty() ) << errInfo; + EXPECT_FALSE( fileContext.getObjectName().empty() ) << errInfo; + EXPECT_FALSE( fileContext.getTypeName().empty() ) << errInfo; + EXPECT_NE( fileContext.getOffset(), xmlDocument::npos ) << errInfo; + EXPECT_NE( fileContext.getLine(), xmlDocument::npos ) << errInfo; + EXPECT_NE( fileContext.getOffsetInLine(), xmlDocument::npos ) << errInfo; // will crash if the source buffer specified by fileContext.getFilePath() isn't available string const * buffer = document.getOriginalBuffer( fileContext.getFilePath() ); - EXPECT_NE( buffer, nullptr ) << errInfos; + EXPECT_NE( buffer, nullptr ) << errInfo; size_t curLine = 1; bool lineFound = false; // Does fileContext.getOffset() locates the object? - EXPECT_LT( fileContext.getOffset() + strToVerify.size(), buffer->size() ) << errInfos; - EXPECT_EQ( strToVerify, buffer->substr( fileContext.getOffset(), strToVerify.size() ) ) << errInfos; + EXPECT_LT( fileContext.getOffset() + strToVerify.size(), buffer->size() ) << errInfo; + EXPECT_EQ( strToVerify, buffer->substr( fileContext.getOffset(), strToVerify.size() ) ) << errInfo; // Were trying to reach the line return by DataFileContext::getLine() for( size_t offset=0; @@ -129,9 +129,9 @@ void verifyDataFileContext( DataFileContext const & fileContext, { // Does fileContext.getLine() and fileContext.getOffsetInLine() locates the object? EXPECT_LT( offset + fileContext.getOffsetInLine() + strToVerify.size(), - buffer->size() ) << errInfos; + buffer->size() ) << errInfo; EXPECT_EQ( strToVerify, - buffer->substr( offset + fileContext.getOffsetInLine(), strToVerify.size() ) ) << errInfos; + buffer->substr( offset + fileContext.getOffsetInLine(), strToVerify.size() ) ) << errInfo; lineFound = true; } } @@ -192,7 +192,7 @@ std::set< string > getDifference( std::set< string > & setA, } // Tests -// - if the line information of each nodes and attributes can be retrieved, +// - if the line information of each nodes and attributes can be all retrieved, // - if the resulting Group & Wrapper hierarchy matches with the input xml documents and includes hierarchy. TEST( testXML, testXMLFileLines ) { @@ -220,12 +220,12 @@ TEST( testXML, testXMLFileLines ) verifyGroupDataFileContextRecursive( xmlDocument, problemManager, verifiedElements ); std::set< string > notFound = getDifference( expectedElements, verifiedElements ); - EXPECT_TRUE( notFound.empty() ) << "Infos : There should not exists xml element that were not in " + EXPECT_TRUE( notFound.empty() ) << "Info : There should not exist xml element that were not in " "the Group hierarchy.\nNot in Group hierarchy : {" << stringutilities::join( notFound, "," ) << "}"; std::set< string > notExpected = getDifference( verifiedElements, expectedElements ); - EXPECT_TRUE( notExpected.empty() ) << "Infos : There should not exists an object in the Group " + EXPECT_TRUE( notExpected.empty() ) << "Info : There should not exist an object in the Group " "hierarchy that contains a DataFileContext but which were not " "declared in the Xml.\nNot in XML hierarchy : {" << stringutilities::join( notExpected, "," ) << "}"; From c69a98d802de6bdf79d21a50a4538162983cf422 Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Fri, 28 Apr 2023 15:31:47 +0200 Subject: [PATCH 16/85] added compatibility with GEOS_FMT + virtual destructors --- .../dataRepository/DataContext.hpp | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/src/coreComponents/dataRepository/DataContext.hpp b/src/coreComponents/dataRepository/DataContext.hpp index 0fb2918cc20..fc03fb26fd2 100644 --- a/src/coreComponents/dataRepository/DataContext.hpp +++ b/src/coreComponents/dataRepository/DataContext.hpp @@ -22,6 +22,7 @@ #include "common/DataTypes.hpp" #include "common/Logger.hpp" #include "xmlWrapper.hpp" +#include "common/Format.hpp" namespace geos { @@ -47,6 +48,11 @@ class DataContext */ DataContext( string const & objectName, bool isDataFileContext ); + /** + * @brief Destroy the DataContext object + */ + virtual ~DataContext() {} + /** * @return A string that mention all the known informations to retrieve from where the target * object comes from. @@ -90,6 +96,11 @@ class GroupContext : public DataContext */ GroupContext( Group & group ); + /** + * @brief Destroy the GroupContext object + */ + virtual ~GroupContext() {} + /** * @brief Get the reference to the Group related to this GroupContext. */ @@ -125,6 +136,11 @@ class WrapperContext final : public GroupContext */ WrapperContext( WrapperBase & wrapper ); + /** + * @brief Destroy the WrapperContext object + */ + virtual ~WrapperContext() {} + /** * @return the parent group DataContext followed by the wrapper name. */ @@ -146,6 +162,11 @@ class DataFileContext final : public DataContext */ DataFileContext( WrapperBase & wrapper, xmlWrapper::xmlAttributePos const & attPos ); + /** + * @brief Destroy the DataFileContext object + */ + virtual ~DataFileContext() {} + /** * @return the target object name followed by the the file and line declaring it. */ @@ -199,8 +220,34 @@ class DataFileContext final : public DataContext }; + } /* namespace dataRepository */ } /* namespace geos */ +#ifdef GEOSX_USE_FMT +#define GEOS_FMT_NS_PREFIX fmt +#else +#define GEOS_FMT_NS_PREFIX std +#endif + +/// Formatter to be able to directly use a DataContext as a GEOS_FMT() argument. +template<> +struct GEOS_FMT_NS_PREFIX::formatter< geos::dataRepository::DataContext > +{ + /** + * @brief Format the specified dataContext to a string. + */ + auto format( geos::dataRepository::DataContext const & dataContext, format_context & ctx ) + { + return format_to( ctx.out(), dataContext.toString() ); + } + + /** + * @brief Method to parse a dataContext from a string. Not implemented! + */ + constexpr auto parse( format_parse_context & ctx ) + { return ctx.begin(); } +}; + #endif /* GEOS_DATAREPOSITORY_DATACONTEXT_HPP_ */ From 552ae0ce9d597205c5685d097c0980e557696d2c Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Fri, 28 Apr 2023 18:20:39 +0200 Subject: [PATCH 17/85] Added more context in various mesh/ error messages --- .../mainInterface/ProblemManager.cpp | 26 +++-- .../mesh/CellElementSubRegion.cpp | 4 +- src/coreComponents/mesh/ElementRegionBase.cpp | 10 +- .../mesh/ElementRegionManager.cpp | 9 +- .../mesh/ElementRegionManager.hpp | 8 +- src/coreComponents/mesh/FaceManager.cpp | 15 ++- src/coreComponents/mesh/Perforation.cpp | 3 +- src/coreComponents/mesh/PerforationData.cpp | 12 +- .../mesh/SurfaceElementRegion.hpp | 3 +- src/coreComponents/mesh/WellElementRegion.cpp | 2 +- .../mesh/WellElementSubRegion.cpp | 4 +- .../mesh/generators/CellBlock.cpp | 2 +- .../mesh/generators/CellBlockUtilities.cpp | 38 ++++--- .../generators/ExternalMeshGeneratorBase.cpp | 19 ++-- .../mesh/generators/InternalMeshGenerator.cpp | 22 +++- .../mesh/generators/InternalMeshGenerator.hpp | 6 +- .../mesh/generators/InternalWellGenerator.cpp | 104 ++++++++++-------- .../generators/InternalWellboreGenerator.cpp | 30 ++--- .../mesh/generators/MeshGeneratorBase.cpp | 3 +- .../mesh/generators/PrismUtilities.hpp | 15 ++- .../simpleGeometricObjects/BoundedPlane.cpp | 6 +- .../mesh/simpleGeometricObjects/Box.cpp | 2 +- .../simpleGeometricObjects/ThickPlane.cpp | 5 +- 23 files changed, 214 insertions(+), 134 deletions(-) diff --git a/src/coreComponents/mainInterface/ProblemManager.cpp b/src/coreComponents/mainInterface/ProblemManager.cpp index 423b2d61d1f..9b07d407ff8 100644 --- a/src/coreComponents/mainInterface/ProblemManager.cpp +++ b/src/coreComponents/mainInterface/ProblemManager.cpp @@ -423,17 +423,25 @@ void ProblemManager::parseXMLDocument( xmlWrapper::xmlDocument & xmlDocument ) for( xmlWrapper::xmlNode regionNode : elementRegionsNode.children() ) { string const regionName = regionNode.attribute( "name" ).value(); - string const - regionMeshBodyName = ElementRegionBase::verifyMeshBodyName( meshBodies, - regionNode.attribute( "meshBody" ).value() ); + try + { + string const + regionMeshBodyName = ElementRegionBase::verifyMeshBodyName( meshBodies, + regionNode.attribute( "meshBody" ).value() ); - MeshBody & meshBody = domain.getMeshBody( regionMeshBodyName ); - meshBody.forMeshLevels( [&]( MeshLevel & meshLevel ) + MeshBody & meshBody = domain.getMeshBody( regionMeshBodyName ); + meshBody.forMeshLevels( [&]( MeshLevel & meshLevel ) + { + ElementRegionManager & elementManager = meshLevel.getElemManager(); + Group * newRegion = elementManager.createChild( regionNode.name(), regionName ); + newRegion->processInputFileRecursive( xmlDocument, regionNode ); + } ); + } + catch( InputError const & e ) { - ElementRegionManager & elementManager = meshLevel.getElemManager(); - Group * newRegion = elementManager.createChild( regionNode.name(), regionName ); - newRegion->processInputFileRecursive( xmlDocument, regionNode ); - } ); + string const nodePosString = xmlDocument.getNodePosition( regionNode ).toString(); + throw InputError( e, "Error while parsing region " + regionName + " (" + nodePosString + "):\n" ); + } } } } diff --git a/src/coreComponents/mesh/CellElementSubRegion.cpp b/src/coreComponents/mesh/CellElementSubRegion.cpp index 0bcd3c0c18d..7732e137249 100644 --- a/src/coreComponents/mesh/CellElementSubRegion.cpp +++ b/src/coreComponents/mesh/CellElementSubRegion.cpp @@ -390,13 +390,13 @@ void CellElementSubRegion:: default: { GEOS_ERROR( GEOS_FMT( "Volume calculation not supported for element type {} in subregion {}", - m_elementType, getName() ) ); + m_elementType, getDataContext() ) ); } } GEOS_ERROR_IF( m_elementVolume[k] <= 0.0, GEOS_FMT( "Negative volume for element {} type {} in subregion {}", - k, m_elementType, getName() ) ); + k, m_elementType, getDataContext() ) ); } void CellElementSubRegion::calculateElementGeometricQuantities( NodeManager const & nodeManager, diff --git a/src/coreComponents/mesh/ElementRegionBase.cpp b/src/coreComponents/mesh/ElementRegionBase.cpp index 1547ba1c46e..490ff866817 100644 --- a/src/coreComponents/mesh/ElementRegionBase.cpp +++ b/src/coreComponents/mesh/ElementRegionBase.cpp @@ -64,9 +64,10 @@ string ElementRegionBase::verifyMeshBodyName( Group const & meshBodies, { meshBodyName = onlyMeshBodyName; } - GEOS_ERROR_IF_NE_MSG( onlyMeshBodyName, + GEOS_THROW_IF_NE_MSG( onlyMeshBodyName, meshBodyName, - "MeshBody specified does not match MeshBody in hierarchy." ); + "MeshBody specified does not match MeshBody in hierarchy.", + InputError ); } else { @@ -78,9 +79,10 @@ string ElementRegionBase::verifyMeshBodyName( Group const & meshBodies, meshBodyFound = true; } } ); - GEOS_ERROR_IF( !meshBodyFound, + GEOS_THROW_IF( !meshBodyFound, "There are multiple MeshBodies in this problem, but the " - "specified MeshBody name "< const faceNodes ) { localIndex const numFaceNodes = LvArray::integerConversion< localIndex >( faceNodes.size() ); - GEOS_ERROR_IF_GT_MSG( numFaceNodes, MAX_FACE_NODES, "Node per face limit exceeded" ); + GEOS_THROW_IF_GT_MSG( numFaceNodes, MAX_FACE_NODES, "Node per face limit exceeded", std::runtime_error ); localIndex const firstNodeIndex = faceNodes[0]; diff --git a/src/coreComponents/mesh/Perforation.cpp b/src/coreComponents/mesh/Perforation.cpp index 4533b1246da..64671e70ef5 100644 --- a/src/coreComponents/mesh/Perforation.cpp +++ b/src/coreComponents/mesh/Perforation.cpp @@ -45,7 +45,8 @@ Perforation::Perforation( string const & name, Group * const parent ) void Perforation::postProcessInput() { GEOS_ERROR_IF( m_distanceFromHead <= 0, - "Invalid distance well head to perforation " << getName() ); + getWrapperDataContext( viewKeyStruct::distanceFromHeadString() ) << + ": Invalid distance well head to perforation " ); } diff --git a/src/coreComponents/mesh/PerforationData.cpp b/src/coreComponents/mesh/PerforationData.cpp index 1f82bb68c87..cf7d60013b4 100644 --- a/src/coreComponents/mesh/PerforationData.cpp +++ b/src/coreComponents/mesh/PerforationData.cpp @@ -127,9 +127,11 @@ void PerforationData::computeWellTransmissibility( MeshLevel const & mesh, { WellElementRegion const & wellRegion = dynamicCast< WellElementRegion const & >( wellElemSubRegion.getParent().getParent() ); GEOS_LOG_RANK_IF( isZero( m_wellTransmissibility[iperf] ), - "\n \nWarning! A perforation is defined with a zero transmissibility in " << wellRegion.getWellGeneratorName() << "! \n" << + "\n \nWarning! Perforation " << wellRegion.getWellGeneratorName() << + " is defined with a zero transmissibility.\n" << "The simulation is going to proceed with this zero transmissibility,\n" << - "but a better strategy to shut down a perforation is to remove the block from the XML\n \n" ); + "but a better strategy to shut down a perforation is to remove the " << + " block from the XML\n \n" ); continue; } @@ -149,7 +151,8 @@ void PerforationData::computeWellTransmissibility( MeshLevel const & mesh, if( dx <= 0 || dy <= 0 || dz <= 0 ) { WellElementRegion const & wellRegion = dynamicCast< WellElementRegion const & >( wellElemSubRegion.getParent().getParent() ); - GEOS_THROW( "The reservoir element dimensions (dx, dy, and dz) should be positive in " << wellRegion.getWellGeneratorName(), + GEOS_THROW( "The reservoir element dimensions (dx, dy, and dz) should be positive in " << + wellRegion.getWellGeneratorName(), InputError ); } @@ -211,7 +214,8 @@ void PerforationData::computeWellTransmissibility( MeshLevel const & mesh, if( m_wellTransmissibility[iperf] <= 0 ) { WellElementRegion const & wellRegion = dynamicCast< WellElementRegion const & >( wellElemSubRegion.getParent().getParent() ); - GEOS_THROW( "The well index is negative or equal to zero in " << wellRegion.getWellGeneratorName(), + GEOS_THROW( "The well index is negative or equal to zero in " << + wellRegion.getWellGeneratorName(), InputError ); } } diff --git a/src/coreComponents/mesh/SurfaceElementRegion.hpp b/src/coreComponents/mesh/SurfaceElementRegion.hpp index 46338db6f97..23fd16496e5 100644 --- a/src/coreComponents/mesh/SurfaceElementRegion.hpp +++ b/src/coreComponents/mesh/SurfaceElementRegion.hpp @@ -201,7 +201,8 @@ class SurfaceElementRegion : public ElementRegionBase subRegionNames.push_back( sr.getName() ); } ); GEOS_ERROR_IF( subRegionNames.size() != 1, - "Surface region \"" << getName() << "\" should have one unique sub region. \"" << subRegionNames.size() << "\" found." ); + "Surface region \"" << getDataContext() << + "\" should have one unique sub region (" << subRegionNames.size() << " found)." ); return subRegionNames.front(); } diff --git a/src/coreComponents/mesh/WellElementRegion.cpp b/src/coreComponents/mesh/WellElementRegion.cpp index 82a5a54adf2..b36dd88b0a2 100644 --- a/src/coreComponents/mesh/WellElementRegion.cpp +++ b/src/coreComponents/mesh/WellElementRegion.cpp @@ -65,7 +65,7 @@ void WellElementRegion::generateWell( MeshLevel & mesh, globalIndex const matchedPerforations = MpiWrapper::sum( perforationData->size() ); GEOS_THROW_IF( matchedPerforations != numPerforationsGlobal, - "Invalid mapping perforation-to-element in well " << wellGeometry.getName() << "." << + "Invalid mapping perforation-to-element in InternalWell " << wellGeometry.getDataContext() << "." << " This happens when GEOSX cannot match a perforation with a reservoir element." << " There are two common reasons for this error:\n" << " 1- The most common reason for this error is that a perforation is on a section of " << diff --git a/src/coreComponents/mesh/WellElementSubRegion.cpp b/src/coreComponents/mesh/WellElementSubRegion.cpp index 03c40e0f77e..4b9df39e825 100644 --- a/src/coreComponents/mesh/WellElementSubRegion.cpp +++ b/src/coreComponents/mesh/WellElementSubRegion.cpp @@ -368,7 +368,7 @@ void WellElementSubRegion::generate( MeshLevel & mesh, // if they belong to the same well element. This is a temporary solution. // TODO: split the well elements that contain multiple perforations, so that no element is shared GEOS_THROW_IF( sharedElems.size() > 0, - "Well " << wellGeometry.getName() << " contains shared well elements", + "InternalWell " << wellGeometry.getDataContext() << " contains shared well elements", InputError ); @@ -520,7 +520,7 @@ void WellElementSubRegion::checkPartitioningValidity( InternalWellGenerator cons globalIndex const prevGlobal = prevElemIdsGlobal[iwelemGlobal][numBranches-1]; GEOS_THROW_IF( prevGlobal <= iwelemGlobal || prevGlobal < 0, - "The structure of well " << wellGeometry.getName() << " is invalid. " << + "The structure of well " << wellGeometry.getDataContext() << " is invalid. " << " The main reason for this error is that there may be no perforation" << " in the bottom well element of the well, which is required to have" << " a well-posed problem.", diff --git a/src/coreComponents/mesh/generators/CellBlock.cpp b/src/coreComponents/mesh/generators/CellBlock.cpp index 388c8b3b36a..d62a0878b16 100644 --- a/src/coreComponents/mesh/generators/CellBlock.cpp +++ b/src/coreComponents/mesh/generators/CellBlock.cpp @@ -120,7 +120,7 @@ void CellBlock::setElementType( ElementType elementType ) } default: { - GEOS_ERROR( "Invalid element type " << m_elementType << " for CellBlock " << getName() ); + GEOS_ERROR( "Invalid element type " << m_elementType << " for CellBlock " << getDataContext() ); } } diff --git a/src/coreComponents/mesh/generators/CellBlockUtilities.cpp b/src/coreComponents/mesh/generators/CellBlockUtilities.cpp index db677e77877..0f1c720d01c 100644 --- a/src/coreComponents/mesh/generators/CellBlockUtilities.cpp +++ b/src/coreComponents/mesh/generators/CellBlockUtilities.cpp @@ -27,7 +27,8 @@ static localIndex getFaceNodesHex( localIndex const faceNum, arraySlice1d< localIndex const, cells::NODE_MAP_USD-1 > const & elemNodes, Span< localIndex > const faceNodes ) { - GEOS_ERROR_IF_LT( faceNodes.size(), 4 ); + GEOS_ERROR_IF_LT_MSG( faceNodes.size(), 4, + "Invalid number of nodes for face (face index = " << faceNum << ")." ); switch( faceNum ) { case 0: @@ -94,7 +95,8 @@ static localIndex getFaceNodesWedge( localIndex const faceNum, { case 0: { - GEOS_ERROR_IF_LT( faceNodes.size(), 4 ); + GEOS_ERROR_IF_LT_MSG( faceNodes.size(), 4, + "Invalid number of nodes for face (face index = " << faceNum << ")." ); faceNodes[0] = elemNodes[0]; faceNodes[1] = elemNodes[1]; faceNodes[2] = elemNodes[5]; @@ -103,7 +105,8 @@ static localIndex getFaceNodesWedge( localIndex const faceNum, } case 1: { - GEOS_ERROR_IF_LT( faceNodes.size(), 4 ); + GEOS_ERROR_IF_LT_MSG( faceNodes.size(), 4, + "Invalid number of nodes for face (face index = " << faceNum << ")." ); faceNodes[0] = elemNodes[0]; faceNodes[1] = elemNodes[2]; faceNodes[2] = elemNodes[3]; @@ -112,7 +115,8 @@ static localIndex getFaceNodesWedge( localIndex const faceNum, } case 2: { - GEOS_ERROR_IF_LT( faceNodes.size(), 3 ); + GEOS_ERROR_IF_LT_MSG( faceNodes.size(), 3, + "Invalid number of nodes for face (face index = " << faceNum << ")." ); faceNodes[0] = elemNodes[0]; faceNodes[1] = elemNodes[4]; faceNodes[2] = elemNodes[2]; @@ -120,7 +124,8 @@ static localIndex getFaceNodesWedge( localIndex const faceNum, } case 3: { - GEOS_ERROR_IF_LT( faceNodes.size(), 3 ); + GEOS_ERROR_IF_LT_MSG( faceNodes.size(), 3, + "Invalid number of nodes for face (face index = " << faceNum << ")." ); faceNodes[0] = elemNodes[1]; faceNodes[1] = elemNodes[3]; faceNodes[2] = elemNodes[5]; @@ -128,7 +133,8 @@ static localIndex getFaceNodesWedge( localIndex const faceNum, } case 4: { - GEOS_ERROR_IF_LT( faceNodes.size(), 4 ); + GEOS_ERROR_IF_LT_MSG( faceNodes.size(), 4, + "Invalid number of nodes for face (face index = " << faceNum << ")." ); faceNodes[0] = elemNodes[2]; faceNodes[1] = elemNodes[4]; faceNodes[2] = elemNodes[5]; @@ -147,7 +153,8 @@ static localIndex getFaceNodesTet( localIndex const faceNum, arraySlice1d< localIndex const, cells::NODE_MAP_USD-1 > const & elemNodes, Span< localIndex > const faceNodes ) { - GEOS_ERROR_IF_LT( faceNodes.size(), 3 ); + GEOS_ERROR_IF_LT_MSG( faceNodes.size(), 3, + "Invalid number of nodes for face (face index = " << faceNum << ")." ); switch( faceNum ) { case 0: @@ -194,7 +201,8 @@ static localIndex getFaceNodesPyramid( localIndex const faceNum, { case 0: { - GEOS_ERROR_IF_LT( faceNodes.size(), 3 ); + GEOS_ERROR_IF_LT_MSG( faceNodes.size(), 3, + "Invalid number of nodes for face (face index = " << faceNum << ")." ); faceNodes[0] = elemNodes[0]; faceNodes[1] = elemNodes[1]; faceNodes[2] = elemNodes[4]; @@ -202,7 +210,8 @@ static localIndex getFaceNodesPyramid( localIndex const faceNum, } case 1: { - GEOS_ERROR_IF_LT( faceNodes.size(), 4 ); + GEOS_ERROR_IF_LT_MSG( faceNodes.size(), 4, + "Invalid number of nodes for face (face index = " << faceNum << ")." ); faceNodes[0] = elemNodes[0]; faceNodes[1] = elemNodes[2]; faceNodes[2] = elemNodes[3]; @@ -211,7 +220,8 @@ static localIndex getFaceNodesPyramid( localIndex const faceNum, } case 2: { - GEOS_ERROR_IF_LT( faceNodes.size(), 3 ); + GEOS_ERROR_IF_LT_MSG( faceNodes.size(), 3, + "Invalid number of nodes for face (face index = " << faceNum << ")." ); faceNodes[0] = elemNodes[0]; faceNodes[1] = elemNodes[4]; faceNodes[2] = elemNodes[2]; @@ -219,7 +229,8 @@ static localIndex getFaceNodesPyramid( localIndex const faceNum, } case 3: { - GEOS_ERROR_IF_LT( faceNodes.size(), 3 ); + GEOS_ERROR_IF_LT_MSG( faceNodes.size(), 3, + "Invalid number of nodes for face (face index = " << faceNum << ")." ); faceNodes[0] = elemNodes[1]; faceNodes[1] = elemNodes[3]; faceNodes[2] = elemNodes[4]; @@ -227,7 +238,8 @@ static localIndex getFaceNodesPyramid( localIndex const faceNum, } case 4: { - GEOS_ERROR_IF_LT( faceNodes.size(), 3 ); + GEOS_ERROR_IF_LT_MSG( faceNodes.size(), 3, + "Invalid number of nodes for face (face index = " << faceNum << ")." ); faceNodes[0] = elemNodes[2]; faceNodes[1] = elemNodes[4]; faceNodes[2] = elemNodes[3]; @@ -295,7 +307,7 @@ localIndex getFaceNodes( ElementType const elementType, } default: { - GEOS_ERROR( "Invalid element type: " << elementType ); + GEOS_ERROR( "Invalid element type " << elementType << " at face index " << faceNumber ); } } return 0; diff --git a/src/coreComponents/mesh/generators/ExternalMeshGeneratorBase.cpp b/src/coreComponents/mesh/generators/ExternalMeshGeneratorBase.cpp index 4756a4f6c74..02962bb1fd1 100644 --- a/src/coreComponents/mesh/generators/ExternalMeshGeneratorBase.cpp +++ b/src/coreComponents/mesh/generators/ExternalMeshGeneratorBase.cpp @@ -59,29 +59,30 @@ ExternalMeshGeneratorBase::ExternalMeshGeneratorBase( string const & name, void ExternalMeshGeneratorBase::postProcessInput() { - auto const checkSizes = [meshName = getName()]( arrayView1d< string const > from, - arrayView1d< string const > to, - string const & fromKey, - string const & toKey ) + auto const checkSizes = [this]( arrayView1d< string const > from, arrayView1d< string const > to, + string const & fromKey, string const & toKey ) { GEOS_THROW_IF_NE_MSG( from.size(), to.size(), - "Mesh '" << meshName << "': attributes '" << fromKey << "' and '" << toKey << "' must contain the same number of values.", + getWrapperDataContext( fromKey ) << + " and " << getWrapperDataContext( toKey ) << + " must contain the same number of values.", InputError ); }; checkSizes( m_volumicFieldsToImport, m_volumicFieldsInGEOSX, viewKeyStruct::volumicFieldsToImportString(), viewKeyStruct::volumicFieldsInGEOSXString() ); checkSizes( m_surfacicFieldsToImport, m_surfacicFieldsInGEOSX, viewKeyStruct::surfacicFieldsToImportString(), viewKeyStruct::surfacicFieldsInGEOSXString() ); - auto const checkDuplicates = [meshName = getName()]( arrayView1d< string const > v ) + auto const checkDuplicates = [this]( arrayView1d< string const > v, string const & key ) { std::set< string > const tmp{ v.begin(), v.end() }; bool const hasDuplicates = tmp.size() != LvArray::integerConversion< std::size_t >( v.size() ); GEOS_THROW_IF( hasDuplicates, - "Mesh '" << meshName << "': '" << stringutilities::join( v, ", " ) << "' already present in list of fields to import.", + getWrapperDataContext( key ) << ": '" << stringutilities::join( v, ", " ) << + "' already present in list of fields to import.", InputError ); }; - checkDuplicates( m_volumicFieldsInGEOSX ); - checkDuplicates( m_surfacicFieldsInGEOSX ); + checkDuplicates( m_volumicFieldsInGEOSX, viewKeyStruct::volumicFieldsInGEOSXString() ); + checkDuplicates( m_surfacicFieldsInGEOSX, viewKeyStruct::surfacicFieldsInGEOSXString() ); // Building the fields mapping from the two separated input/output vectors. auto const buildMapping = [&]( arrayView1d< string const > from, diff --git a/src/coreComponents/mesh/generators/InternalMeshGenerator.cpp b/src/coreComponents/mesh/generators/InternalMeshGenerator.cpp index 8a518c207ed..6e5e7024d82 100644 --- a/src/coreComponents/mesh/generators/InternalMeshGenerator.cpp +++ b/src/coreComponents/mesh/generators/InternalMeshGenerator.cpp @@ -129,7 +129,7 @@ static int getNumElemPerBox( ElementType const elementType ) case ElementType::Hexahedron: return 1; default: { - GEOS_ERROR( "InternalMeshGenerator: unsupported element type " << elementType ); + GEOS_THROW( "Unsupported element type " << elementType, InputError ); return 0; } } @@ -148,7 +148,7 @@ void InternalMeshGenerator::postProcessInput() } if( failFlag ) { - GEOS_ERROR( "vertex/element mismatch InternalMeshGenerator::ReadXMLPost()" ); + GEOS_ERROR( getDataContext() << ": vertex/element mismatch InternalMeshGenerator::ReadXMLPost()" ); } // If specified, check to make sure bias values have the correct length @@ -161,7 +161,7 @@ void InternalMeshGenerator::postProcessInput() } if( failFlag ) { - GEOS_ERROR( "element/bias mismatch InternalMeshGenerator::ReadXMLPost()" ); + GEOS_ERROR( getDataContext() << ": element/bias mismatch InternalMeshGenerator::ReadXMLPost()" ); } } @@ -176,13 +176,22 @@ void InternalMeshGenerator::postProcessInput() } else { - GEOS_ERROR( "InternalMeshGenerator: The number of element types is inconsistent with the number of total block." ); + GEOS_ERROR( getDataContext() << ": InternalMeshGenerator: The number of element types is" + " inconsistent with the number of total block." ); } } for( localIndex i = 0; i < LvArray::integerConversion< localIndex >( m_elementType.size() ); ++i ) { - m_numElePerBox[i] = getNumElemPerBox( EnumStrings< ElementType >::fromString( m_elementType[i] ) ); + try + { + m_numElePerBox[i] = getNumElemPerBox( EnumStrings< ElementType >::fromString( m_elementType[i] ) ); + } catch( InputError const & e ) + { + WrapperBase const & wrapper = getWrapperBase( viewKeyStruct::elementTypesString() ); + throw InputError( e, "InternalMesh " + wrapper.getDataContext().toString() + + ", element index = " + std::to_string( i ) + ": " ); + } } { @@ -200,7 +209,8 @@ void InternalMeshGenerator::postProcessInput() } else { - GEOS_ERROR( "Incorrect number of regionLayout entries specified in InternalMeshGenerator::ReadXML()" ); + GEOS_ERROR( getDataContext() << ": Incorrect number of regionLayout entries specified in" + " InternalMeshGenerator::ReadXML()" ); } } } diff --git a/src/coreComponents/mesh/generators/InternalMeshGenerator.hpp b/src/coreComponents/mesh/generators/InternalMeshGenerator.hpp index 81a7e31186c..03d27359f02 100644 --- a/src/coreComponents/mesh/generators/InternalMeshGenerator.hpp +++ b/src/coreComponents/mesh/generators/InternalMeshGenerator.hpp @@ -343,7 +343,11 @@ class InternalMeshGenerator : public MeshGeneratorBase // Verify that the bias is non-zero and applied to more than one block: if( ( !isZero( m_nElemBias[i][block] ) ) && (m_nElems[i][block]>1)) { - GEOS_ERROR_IF( fabs( m_nElemBias[i][block] ) >= 1, "Mesh bias must between -1 and 1!" ); + GEOS_ERROR_IF( fabs( m_nElemBias[i][block] ) >= 1, + getWrapperDataContext( i == 0 ? viewKeyStruct::xBiasString() : + i == 1 ? viewKeyStruct::yBiasString() : + viewKeyStruct::zBiasString() ) << + ", block index = " << block << " : Mesh bias must between -1 and 1!" ); real64 len = max - min; real64 xmean = len / m_nElems[i][block]; diff --git a/src/coreComponents/mesh/generators/InternalWellGenerator.cpp b/src/coreComponents/mesh/generators/InternalWellGenerator.cpp index b3e74dcc9ad..98245bf0d06 100644 --- a/src/coreComponents/mesh/generators/InternalWellGenerator.cpp +++ b/src/coreComponents/mesh/generators/InternalWellGenerator.cpp @@ -105,31 +105,38 @@ InternalWellGenerator::~InternalWellGenerator() void InternalWellGenerator::postProcessInput() { GEOS_THROW_IF( m_polyNodeCoords.size( 1 ) != m_nDims, - "Invalid number of physical coordinates in " << viewKeyStruct::polylineNodeCoordsString() << " for well " << getName(), + "InternalWell " << getWrapperDataContext( viewKeyStruct::polylineNodeCoordsString() ) << + ": Invalid number of physical coordinates.", InputError ); GEOS_THROW_IF( m_segmentToPolyNodeMap.size( 1 ) != 2, - "Invalid size in " << viewKeyStruct::polylineSegmentConnString() << " for well " << getName(), + "InternalWell " << getWrapperDataContext( viewKeyStruct::polylineSegmentConnString() ) << + ": Invalid size.", InputError ); GEOS_THROW_IF( m_polyNodeCoords.size( 0 )-1 != m_segmentToPolyNodeMap.size( 0 ), - "Incompatible sizes of " << viewKeyStruct::polylineNodeCoordsString() << " and " << viewKeyStruct::polylineSegmentConnString() << " in well " << getName(), + "Incompatible sizes of " << getWrapperDataContext( viewKeyStruct::polylineNodeCoordsString() ) << + " and " << getWrapperDataContext( viewKeyStruct::polylineSegmentConnString() ), InputError ); GEOS_THROW_IF( m_radius <= 0, - "Invalid " << viewKeyStruct::radiusString() << " in well " << getName(), + "InternalWell " << getWrapperDataContext( viewKeyStruct::radiusString() ) << + ": Value must be greater that 0.", InputError ); GEOS_THROW_IF( m_wellRegionName.empty(), - "Invalid well region name in well " << getName(), + "InternalWell " << getWrapperDataContext( viewKeyStruct::wellRegionNameString() ) << + ": Empty well region name.", InputError ); GEOS_THROW_IF( m_meshBodyName.empty(), - "Invalid mesh name in well " << getName(), + "InternalWell " << getWrapperDataContext( viewKeyStruct::meshNameString() ) << + ": Empty mesh name.", InputError ); GEOS_THROW_IF( m_wellControlsName.empty(), - "Invalid well constraint name in well " << getName(), + "InternalWell " << getWrapperDataContext( viewKeyStruct::wellControlsNameString() ) << + ": Empty well constraint name.", InputError ); // TODO: add more checks here @@ -150,7 +157,7 @@ Group * InternalWellGenerator::createChild( string const & childKey, string cons } else { - GEOS_THROW( "Unrecognized node: " << childKey, InputError ); + GEOS_THROW( getDataContext() << ": Unrecognized node with tag name " << childKey, InputError ); } return nullptr; } @@ -233,14 +240,14 @@ void InternalWellGenerator::constructPolylineNodeToSegmentMap() // various checks and warnings on the segment and element length GEOS_THROW_IF( segmentLength < m_minSegmentLength, - "Error in the topology of well '" << getName() << - "': we detected a polyline segment measuring less than " << m_minSegmentLength << "m. \n" << + getDataContext() << ": Error in the topology of the well. We detected a" << + " polyline segment measuring less than " << m_minSegmentLength << "m. \n" << "You can change the minimum segment length using the field " << viewKeyStruct::minSegmentLengthString(), InputError ); GEOS_THROW_IF( m_polyNodeCoords[ipolyNode_a][2] < m_polyNodeCoords[ipolyNode_b][2], - "Error in the topology of well '" << getName() << - "': in the polyline, each segment must be going down. \n" << + getDataContext() << ": Error in the topology of the well. Wn the polyline"<< + ", each segment must be going down. \n" << "This is not the case between polyline nodes " << m_polyNodeCoords[ipolyNode_a] << " and " << m_polyNodeCoords[ipolyNode_b], InputError ); @@ -256,9 +263,11 @@ void InternalWellGenerator::constructPolylineNodeToSegmentMap() if( foundSmallElem ) { - GEOS_LOG_RANK_0( "\nWarning: the chosen number of well elements per polyline segment (" << m_numElemsPerSegment << - ") leads to well elements measuring less than " << m_minElemLength << "m in the topology of well '" << getName() << "'.\n" << - "The simulation can proceed like that, but small well elements may cause numerical issues, so it is something to keep an eye on.\n" << + GEOS_LOG_RANK_0( "\nWarning for well " << getDataContext() << ": the chosen number of well" << + " elements per polyline segment (" << m_numElemsPerSegment << ") leads to well" << + " elements measuring less than " << m_minElemLength << "m in the topology.\n" << + "The simulation can proceed like that, but small well elements may cause" << + " numerical issues, so it is something to keep an eye on.\n" << "You can get rid of this message by changing the field " << viewKeyStruct::minElementLengthString() ); } } @@ -276,10 +285,10 @@ void InternalWellGenerator::findPolylineHeadNodeIndex() // therefore here we throw an error if the well head segment is horizontal GEOS_THROW_IF( !(m_polyNodeCoords[ipolyNode_a][2] < m_polyNodeCoords[ipolyNode_b][2]) && !(m_polyNodeCoords[ipolyNode_a][2] > m_polyNodeCoords[ipolyNode_b][2]), - "The head polyline segment cannot be horizontal in well '" << getName() - << "' since we use depth to determine which of its nodes is to head node of the well.\n" - << "If you are trying to set up a horizontal well, please simply add a non-horizontal segment at the top of the well," - << " and this error will go away", + getDataContext() << ": The head polyline segment cannot be horizontal in the well" << + " since we use depth to determine which of its nodes is to head node of the well.\n" << + "If you are trying to set up a horizontal well, please simply add a non-horizontal" << + " segment at the top of the well, and this error will go away.", InputError ); // detect the top node, assuming z oriented upwards @@ -298,8 +307,8 @@ void InternalWellGenerator::findPolylineHeadNodeIndex() real64 const currentZcoord = m_polyNodeCoords[inode][2]; GEOS_THROW_IF( !(currentZcoord < headZcoord), - "Error in the topology of well '" << getName() - << "' since we found a well node that is above the head node", + getDataContext() << ": Error in the topology since we found a well node that" << + " is above the head node", InputError ); } } @@ -324,7 +333,8 @@ void InternalWellGenerator::discretizePolyline() { GEOS_THROW_IF( isegCurrent == -1, - "Invalid segmentToNode map in well " << getName(), + getWrapperDataContext( viewKeyStruct::polylineSegmentConnString() ) << + ": Invalid map.", InputError ); globalIndex const ipolyNodeBottom = ( ipolyNodeTop == m_segmentToPolyNodeMap[isegCurrent][0] ) @@ -345,7 +355,7 @@ void InternalWellGenerator::discretizePolyline() LvArray::tensorOps::add< 3 >( m_elemCenterCoords[iwelemCurrent], m_polyNodeCoords[ipolyNodeTop] ); GEOS_THROW_IF( iwelemCurrent >= m_numElems, - "Invalid well topology in well " << getName(), + getDataContext() << ": Invalid well topology", InputError ); globalIndex const iwelemTop = iwelemCurrent - 1; @@ -412,10 +422,10 @@ void InternalWellGenerator::connectPerforationsToWellElements() real64 const wellLength = m_nodeDistFromHead[m_elemToNodesMap[iwelemBottom][NodeLocation::BOTTOM]]; GEOS_THROW_IF( m_perfDistFromHead[iperf] > wellLength, - "Distance from perforation " << perf.getName() << " to head is larger than well polyline length for well " << getName() << "\n \n" - << "Here is how the \"distanceFromHead\" keyword is used in the definition of the perforation location: \n" - << "We start from the well head (top of the well) and we measure the linear distance along the well polyline as we go down the well.\n" - << "When we reach the distanceFromHead specified by the user, we place a perforation on the well at this location of the polyline, and connect it to the reservoir element that contains this perforation", + perf.getDataContext() << ": Distance from well perforation to head is larger than well polyline length\n \n" << + "Here is how the \"distanceFromHead\" keyword is used in the definition of the perforation location: \n" << + "We start from the well head (top of the well) and we measure the linear distance along the well polyline as we go down the well.\n" << + "When we reach the distanceFromHead specified by the user, we place a perforation on the well at this location of the polyline, and connect it to the reservoir element that contains this perforation", InputError ); // start binary search @@ -438,7 +448,7 @@ void InternalWellGenerator::connectPerforationsToWellElements() } GEOS_THROW_IF( currentNumSteps > maxNumSteps, - "Perforation " << perf.getName() << " cannot be mapped to a well element in well " << getName(), + perf.getDataContext() << ": Perforation cannot be mapped to a well element.", InputError ); currentNumSteps++; @@ -447,7 +457,7 @@ void InternalWellGenerator::connectPerforationsToWellElements() // set the index of the matched element globalIndex iwelemMatched = iwelemTop; GEOS_THROW_IF( iwelemMatched >= m_numElems, - "Invalid topology in well " << getName(), + getDataContext() << ": Invalid well topology.", InputError ); m_perfElemId[iperf] = iwelemMatched; @@ -459,7 +469,7 @@ void InternalWellGenerator::connectPerforationsToWellElements() real64 const topToPerfDist = m_perfDistFromHead[iperf] - m_nodeDistFromHead[inodeTop]; GEOS_THROW_IF( (elemLength <= 0) || (topToPerfDist < 0), - "Invalid topology in well " << getName(), + getDataContext() << ": Invalid well topology.", InputError ); LvArray::tensorOps::copy< 3 >( m_perfCoords[iperf], m_nodeCoords[inodeBottom] ); @@ -515,14 +525,14 @@ void InternalWellGenerator::checkPerforationLocationsValidity() for( localIndex iwelemPrev = 0; iwelemPrev < m_prevElemId[iwelem].size(); ++iwelemPrev ) { GEOS_THROW_IF( m_prevElemId[iwelem][iwelemPrev] == -1 && elemToPerfMap[iwelem].size() == 0, - "The bottom element of well " << getName() << " does not have a perforation. " - << "This is needed to have a well-posed problem. \n\n" - << "Here are the two possible ways to solve this problem: \n\n" - << "1) Adding a perforation located close to the bottom of the well. " - << "To do that, compute the total length of the well polyline (by summing the length of the well segments defined by the keywords \"polylineNodeCoords\" and \"polylineSegmentConn\") " - << "and place a perforation whose \"distanceFromHead\" is slightly smaller than this total length. \n \n" - << "2) Shorten the well polyline. " - << "To do that, reduce the length of the well polyline by shortening the segments defined by the keywords \"polylineNodeCoords\" and \"polylineSegmentConn\", or by removing a segment.", + getDataContext() << "The bottom element of the well does not have a perforation. " << + "This is needed to have a well-posed problem. \n\n" << + "Here are the two possible ways to solve this problem: \n\n" << + "1) Adding a perforation located close to the bottom of the well. " << + "To do that, compute the total length of the well polyline (by summing the length of the well segments defined by the keywords \"polylineNodeCoords\" and \"polylineSegmentConn\") " << + "and place a perforation whose \"distanceFromHead\" is slightly smaller than this total length. \n \n" << + "2) Shorten the well polyline. " << + "To do that, reduce the length of the well polyline by shortening the segments defined by the keywords \"polylineNodeCoords\" and \"polylineSegmentConn\", or by removing a segment.", InputError ); } } @@ -556,15 +566,15 @@ void InternalWellGenerator::mergePerforations( array1d< array1d< localIndex > > continue; } - GEOS_LOG_RANK_0( "\n \nThe GEOSX wells currently have the following limitation in parallel: \n" - << "We cannot allow an element of the well mesh to have two or more perforations associated with it. \n" - << "So, in the present simulation, perforation #" << elemToPerfMap[iwelem][ip] - << " of well " << getName() - << " is moved from " << m_perfCoords[elemToPerfMap[iwelem][ip]] - << " to " << m_perfCoords[iperfMaxTransmissibility] - << " to make sure that no element of the well mesh has two perforations associated with it. \n" - << "To circumvent this issue, please increase the value of \"numElementsPerSegment\" that controls the number of (uniformly distributed) well mesh elements per segment of the well polyline. \n" - << "Our recommendation is to choose \"numElementsPerSegment\" such that each well mesh element has at most one perforation. \n\n" ); + GEOS_LOG_RANK_0( "\n \nThe GEOSX wells currently have the following limitation in parallel: \n" << + "We cannot allow an element of the well mesh to have two or more perforations associated with it. \n" << + "So, in the present simulation, perforation #" << elemToPerfMap[iwelem][ip] << + " of well " << getDataContext() << + " is moved from " << m_perfCoords[elemToPerfMap[iwelem][ip]] << + " to " << m_perfCoords[iperfMaxTransmissibility] << + " to make sure that no element of the well mesh has two perforations associated with it. \n" << + "To circumvent this issue, please increase the value of \"numElementsPerSegment\" that controls the number of (uniformly distributed) well mesh elements per segment of the well polyline. \n" << + "Our recommendation is to choose \"numElementsPerSegment\" such that each well mesh element has at most one perforation. \n\n" ); LvArray::tensorOps::copy< 3 >( m_perfCoords[elemToPerfMap[iwelem][ip]], m_perfCoords[iperfMaxTransmissibility] ); } } diff --git a/src/coreComponents/mesh/generators/InternalWellboreGenerator.cpp b/src/coreComponents/mesh/generators/InternalWellboreGenerator.cpp index bfa6177de34..aa261fb9313 100644 --- a/src/coreComponents/mesh/generators/InternalWellboreGenerator.cpp +++ b/src/coreComponents/mesh/generators/InternalWellboreGenerator.cpp @@ -116,17 +116,18 @@ void InternalWellboreGenerator::postProcessInput() { GEOS_ERROR_IF( m_nElems[1].size() > 1, - "Only one block in the theta direction is currently supported. " - "This is specified by the nt keyword in InternalWellbore" ); + getWrapperDataContext( viewKeyStruct::yElemsString() ) << + ": Only one block in the theta direction is currently supported. " ); GEOS_ERROR_IF( m_nElems[2].size() > 1, - "Only one block in the z direction is currently supported. " - "This is specified by the nz keyword in InternalWellbore" ); + getWrapperDataContext( viewKeyStruct::yElemsString() ) << + ": Only one block in the z direction is currently supported. " ); GEOS_ERROR_IF( m_trajectory.size( 0 ) != 2 || m_trajectory.size( 1 ) != 3, - "Input for trajectory should be specified in the form of " + getWrapperDataContext( viewKeyStruct::trajectoryString() ) << + ": Input for trajectory should be specified in the form of " "{ { xbottom, ybottom, zbottom }, { xtop, ytop, ztop } }." ); // Project trajectory to bottom and top of the wellbore @@ -264,16 +265,19 @@ void InternalWellboreGenerator::postProcessInput() if( m_cartesianOuterBoundary < 1000000 ) { - GEOS_ERROR_IF( m_cartesianOuterBoundary < 0, "useCartesianOuterBoundary must be > 0" ); + GEOS_ERROR_IF( m_cartesianOuterBoundary < 0, + getWrapperDataContext( viewKeyStruct::cartesianOuterBoundaryString() ) << + " must be > 0" ); real64 const innerLimit = m_vertices[0][m_cartesianOuterBoundary]; real64 const outerLimit = m_vertices[0].size(); - GEOS_ERROR_IF( m_cartesianMappingInnerRadius< 1e98 && - m_cartesianMappingInnerRadius > outerLimit, - "cartesianMappingInnerRadius must be inside the outer radius of the mesh" ); - - GEOS_ERROR_IF( m_cartesianMappingInnerRadius < innerLimit, - "cartesianMappingInnerRadius must be outside the radius " - "of the inner boundary of the region specified by useCartesianOuterBoundary" ); + GEOS_ERROR_IF( m_cartesianMappingInnerRadius > outerLimit && m_cartesianMappingInnerRadius < 1e98, + getWrapperDataContext( viewKeyStruct::cartesianMappingInnerRadiusString() ) << + " must be inside the outer radius of the mesh" ); + + GEOS_ERROR_IF_LT_MSG( m_cartesianMappingInnerRadius, innerLimit, + getWrapperDataContext( viewKeyStruct::cartesianMappingInnerRadiusString() ) << + " must be outside the radius of the inner boundary of the region specified by " << + getWrapperDataContext( viewKeyStruct::cartesianOuterBoundaryString() ) ); if( m_cartesianMappingInnerRadius > 1e98 ) { diff --git a/src/coreComponents/mesh/generators/MeshGeneratorBase.cpp b/src/coreComponents/mesh/generators/MeshGeneratorBase.cpp index 6d4148f368d..de912ce6a13 100644 --- a/src/coreComponents/mesh/generators/MeshGeneratorBase.cpp +++ b/src/coreComponents/mesh/generators/MeshGeneratorBase.cpp @@ -27,7 +27,8 @@ MeshGeneratorBase::MeshGeneratorBase( string const & name, Group * const parent Group * MeshGeneratorBase::createChild( string const & childKey, string const & childName ) { // Mesh generators generally don't have child XML nodes, must override this method to enable - GEOS_THROW( GEOS_FMT( "Mesh '{}': invalid child XML node '{}' of type {}", getName(), childName, childKey ), + GEOS_THROW( GEOS_FMT( "Mesh '{}': invalid child XML node '{}' of type {}", + getDataContext(), childName, childKey ), InputError ); } diff --git a/src/coreComponents/mesh/generators/PrismUtilities.hpp b/src/coreComponents/mesh/generators/PrismUtilities.hpp index b5cdf32d942..645e12e828a 100644 --- a/src/coreComponents/mesh/generators/PrismUtilities.hpp +++ b/src/coreComponents/mesh/generators/PrismUtilities.hpp @@ -42,7 +42,8 @@ localIndex getFaceNodesPrism( localIndex const faceNum, if( faceNum == 0 ) { - GEOS_ERROR_IF_LT( faceNodes.size(), 4 ); + GEOS_ERROR_IF_LT_MSG( faceNodes.size(), 4, + "Invalid number of nodes for face (face index = " << faceNum << ")." ); faceNodes[0] = elemNodes[0]; faceNodes[1] = elemNodes[1]; faceNodes[2] = elemNodes[N+1]; @@ -51,7 +52,8 @@ localIndex getFaceNodesPrism( localIndex const faceNum, } else if( faceNum == 1 ) { - GEOS_ERROR_IF_LT( faceNodes.size(), N ); + GEOS_ERROR_IF_LT_MSG( faceNodes.size(), N, + "Invalid number of nodes for face (face index = " << faceNum << ")." ); faceNodes[0] = elemNodes[0]; for( localIndex i = 1; i < N; ++i ) { @@ -61,7 +63,8 @@ localIndex getFaceNodesPrism( localIndex const faceNum, } else if( faceNum == 2 ) { - GEOS_ERROR_IF_LT( faceNodes.size(), 4 ); + GEOS_ERROR_IF_LT_MSG( faceNodes.size(), 4, + "Invalid number of nodes for face (face index = " << faceNum << ")." ); faceNodes[0] = elemNodes[0]; faceNodes[1] = elemNodes[N]; faceNodes[2] = elemNodes[N*2-1]; @@ -70,7 +73,8 @@ localIndex getFaceNodesPrism( localIndex const faceNum, } else if( faceNum >= 3 && faceNum <= N ) { - GEOS_ERROR_IF_LT( faceNodes.size(), 4 ); + GEOS_ERROR_IF_LT_MSG( faceNodes.size(), 4, + "Invalid number of nodes for face (face index = " << faceNum << ")." ); faceNodes[0] = elemNodes[faceNum-2]; faceNodes[1] = elemNodes[faceNum-1]; faceNodes[2] = elemNodes[N+faceNum-1]; @@ -79,7 +83,8 @@ localIndex getFaceNodesPrism( localIndex const faceNum, } else if( faceNum == N + 1 ) { - GEOS_ERROR_IF_LT( faceNodes.size(), N ); + GEOS_ERROR_IF_LT_MSG( faceNodes.size(), N, + "Invalid number of nodes for face (face index = " << faceNum << ")." ); for( localIndex i = 0; i < N; ++i ) { faceNodes[i] = elemNodes[i+N]; diff --git a/src/coreComponents/mesh/simpleGeometricObjects/BoundedPlane.cpp b/src/coreComponents/mesh/simpleGeometricObjects/BoundedPlane.cpp index 989aa1298db..f3f8998afd1 100644 --- a/src/coreComponents/mesh/simpleGeometricObjects/BoundedPlane.cpp +++ b/src/coreComponents/mesh/simpleGeometricObjects/BoundedPlane.cpp @@ -79,9 +79,11 @@ void BoundedPlane::postProcessInput() GEOS_ERROR_IF( std::fabs( std::fabs( LvArray::tensorOps::AiBi< 3 >( m_normal, vector )) - 1 ) > 1e-15 || std::fabs( LvArray::tensorOps::AiBi< 3 >( m_widthVector, m_lengthVector )) > 1e-15, - "Error: the 3 vectors provided in the BoundedPlane do not form an orthonormal basis!" ); + getDataContext() << ": the 3 vectors provided in the BoundedPlane do not form an" << + " orthonormal basis!" ); - GEOS_ERROR_IF( m_dimensions.size() != 2, "Error: Need to provide both length and width!" ); + GEOS_ERROR_IF( m_dimensions.size() != 2, + getDataContext() << ": Need to provide both length and width!" ); findRectangleLimits(); } diff --git a/src/coreComponents/mesh/simpleGeometricObjects/Box.cpp b/src/coreComponents/mesh/simpleGeometricObjects/Box.cpp index 6b9fc0a94c1..30d90ff610f 100644 --- a/src/coreComponents/mesh/simpleGeometricObjects/Box.cpp +++ b/src/coreComponents/mesh/simpleGeometricObjects/Box.cpp @@ -69,7 +69,7 @@ void Box::postProcessInput() if( std::fabs( m_strikeAngle ) > 1e-20 ) { GEOS_ERROR_IF( (m_max[0]-m_min[0]) < (m_max[1]-m_min[1]), - "Error: When a strike angle is specified, the box is supposed to represent a plane normal to the " + getDataContext() << ": When a strike angle is specified, the box is supposed to represent a plane normal to the " "y direction. This box seems to be too thick." ); m_cosStrike = std::cos( m_strikeAngle / 180 *M_PI ); diff --git a/src/coreComponents/mesh/simpleGeometricObjects/ThickPlane.cpp b/src/coreComponents/mesh/simpleGeometricObjects/ThickPlane.cpp index e27868070bd..db9820a8e93 100644 --- a/src/coreComponents/mesh/simpleGeometricObjects/ThickPlane.cpp +++ b/src/coreComponents/mesh/simpleGeometricObjects/ThickPlane.cpp @@ -49,11 +49,12 @@ ThickPlane::~ThickPlane() void ThickPlane::postProcessInput() { m_thickness *= 0.5; // actually store the half-thickness - GEOS_ERROR_IF( m_thickness <= 0, "Error: the plane appears to have zero or negative thickness" ); + GEOS_ERROR_IF( m_thickness <= 0, + getDataContext() << ": The plane appears to have zero or negative thickness" ); LvArray::tensorOps::normalize< 3 >( m_normal ); GEOS_ERROR_IF( std::fabs( LvArray::tensorOps::l2Norm< 3 >( m_normal ) - 1.0 ) > 1e-15, - "Error: could not properly normalize input normal." ); + getDataContext() << ": Could not properly normalize input normal." ); } From 703d88b6e591c6481c4deb07266ef9bbbcb835c6 Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Wed, 10 May 2023 11:19:46 +0200 Subject: [PATCH 18/85] Adapted getGroupByPath test with new error message --- .../unitTests/dataRepositoryTests/testGroupPath.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/coreComponents/unitTests/dataRepositoryTests/testGroupPath.cpp b/src/coreComponents/unitTests/dataRepositoryTests/testGroupPath.cpp index 20761ea22c7..b439783f112 100644 --- a/src/coreComponents/unitTests/dataRepositoryTests/testGroupPath.cpp +++ b/src/coreComponents/unitTests/dataRepositoryTests/testGroupPath.cpp @@ -105,10 +105,13 @@ TEST( testGroupPath, testGlobalPaths ) catch( const std::domain_error & e ) { static constexpr auto expectedMsg = "***** Controlling expression (should be false): child == nullptr\n" - "***** Rank 0: Group /Mesh has no child named mesh2\n" + "***** Rank 0: Group Mesh (CodeIncludedXML0, l.10) has no child named mesh2\n" "The children of Mesh are: { mesh1 }"; // checks if the exception contains the expected message - ASSERT_TRUE( string( e.what() ).find( expectedMsg ) != string::npos ); + GEOS_ERROR_IF_EQ_MSG( string( e.what() ).find( expectedMsg ), string::npos, + "The error message was not containing the expected sequence.\n" << + " Error message :\n" << e.what() << + " expected sequence :\n" << expectedMsg ); trowHappened = true; } // checks if the exception has been thrown as expected From 7a7e053ec6c3f09eaedd68c13549b7c790d71cba Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Wed, 10 May 2023 11:52:10 +0200 Subject: [PATCH 19/85] error message improvements + bugfixes + removed unused header --- src/coreComponents/dataRepository/Group.cpp | 1 - src/coreComponents/dataRepository/Wrapper.hpp | 4 ++-- .../dataRepository/WrapperBase.cpp | 12 +++++++----- .../dataRepository/xmlWrapper.cpp | 18 +++++++++++------- .../FieldSpecificationBase.cpp | 5 +++-- .../timeHistory/HistoryCollectionBase.cpp | 12 +++++++----- src/coreComponents/mesh/ElementRegionBase.cpp | 10 ++++++---- .../mesh/ElementRegionManager.cpp | 9 ++++++--- .../mesh/ElementRegionManager.hpp | 8 ++++++-- 9 files changed, 48 insertions(+), 31 deletions(-) diff --git a/src/coreComponents/dataRepository/Group.cpp b/src/coreComponents/dataRepository/Group.cpp index 15ad48e92f3..185e15ccd2f 100644 --- a/src/coreComponents/dataRepository/Group.cpp +++ b/src/coreComponents/dataRepository/Group.cpp @@ -13,7 +13,6 @@ */ // Source includes -#include #include "Group.hpp" #include "ConduitRestart.hpp" #include "codingUtilities/StringUtilities.hpp" diff --git a/src/coreComponents/dataRepository/Wrapper.hpp b/src/coreComponents/dataRepository/Wrapper.hpp index fcb013ab7cf..5fec3b9f564 100644 --- a/src/coreComponents/dataRepository/Wrapper.hpp +++ b/src/coreComponents/dataRepository/Wrapper.hpp @@ -621,8 +621,8 @@ class Wrapper final : public WrapperBase targetNode, inputFlag == InputFlags::REQUIRED ); GEOS_THROW_IF( !m_successfulReadFromInput, - GEOS_FMT( "XML Node {} ({}) with name={} is missing required attribute {}." - "Available options are:\n{}\nFor more details, please refer to documentation at:\n" + GEOS_FMT( "XML Node {} ({}) with name={} is missing required attribute '{}'." + "Available options are:\n {}\n For more details, please refer to documentation at:\n" "http://geosx-geosx.readthedocs-hosted.com/en/latest/docs/sphinx/userGuide/Index.html", targetNode.name(), nodePos.toString(), targetNode.attribute( "name" ).value(), getName(), dumpInputOptions( true ) ), diff --git a/src/coreComponents/dataRepository/WrapperBase.cpp b/src/coreComponents/dataRepository/WrapperBase.cpp index 7c7b3d311b8..bc61462b11c 100644 --- a/src/coreComponents/dataRepository/WrapperBase.cpp +++ b/src/coreComponents/dataRepository/WrapperBase.cpp @@ -121,21 +121,23 @@ void WrapperBase::processInputException( std::exception const & ex, { xmlWrapper::xmlAttribute const & attribute = targetNode.attribute( getName().c_str() ); string const inputStr = string( attribute.value() ); - xmlWrapper::xmlAttributePos const attPos = nodePos.getAttributeLine( inputStr ); + xmlWrapper::xmlAttributePos const attPos = nodePos.getAttributeLine( getName() ); std::ostringstream oss; + string const exStr = ex.what(); oss << "***** XML parsing error at node "; if( nodePos.isFound() ) { - oss << "named " << targetNode.attribute( "name" ).value() << ", attribute " << attribute.name() - << '(' << splitPath( attPos.isFound() ? attPos.filePath : nodePos.filePath ).second + oss << "named " << m_parent->getName() << ", attribute " << getName() + << " (" << splitPath( attPos.isFound() ? attPos.filePath : nodePos.filePath ).second << ", l." << ( attPos.isFound() ? attPos.line : nodePos.line ) << ")."; } else { - oss << targetNode.path() << " (name=" << targetNode.attribute( "name" ).value() << ")/" << attribute.name(); + oss << targetNode.path() << " (name=" << targetNode.attribute( "name" ).value() << ")/" << getName(); } - oss << "\n***** Input value: '" << inputStr << "'\n" << ex.what(); + oss << "\n***** Input value: '" << inputStr << '\''; + oss << ( exStr[0]=='\n' ? exStr : "'\n" + exStr ); throw InputError( oss.str() ); } diff --git a/src/coreComponents/dataRepository/xmlWrapper.cpp b/src/coreComponents/dataRepository/xmlWrapper.cpp index 1813ae571df..78de92a15b1 100644 --- a/src/coreComponents/dataRepository/xmlWrapper.cpp +++ b/src/coreComponents/dataRepository/xmlWrapper.cpp @@ -349,14 +349,18 @@ xmlAttributePos xmlNodePos::getAttributeLine( string const & attName ) const if( tagEnd != string::npos ) { std::smatch m; - // we search for a string which is the attribute name followed by an '=', eventually separated by spaces - if( std::regex_search( buffer->cbegin() + offset, buffer->cbegin() + tagEnd, - m, std::regex( attName + "\\s*=" ) ) ) + try { - attOffset = m.position() + offset; - attLine = line + std::count( buffer->cbegin() + offset, buffer->cbegin() + attOffset, '\n' ); - attOffsetInLine = attOffset - buffer->rfind( '\n', attOffset ); - } + // we search for a string which is the attribute name followed by an '=', eventually separated by spaces + if( std::regex_search( buffer->cbegin() + offset, buffer->cbegin() + tagEnd, + m, std::regex( attName + "\\s*=" ) ) ) + { + attOffset = m.position() + offset; + attLine = line + std::count( buffer->cbegin() + offset, buffer->cbegin() + attOffset, '\n' ); + attOffsetInLine = attOffset - buffer->rfind( '\n', attOffset ); + } + } catch( std::regex_error const & e ) + { } } } diff --git a/src/coreComponents/fieldSpecification/FieldSpecificationBase.cpp b/src/coreComponents/fieldSpecification/FieldSpecificationBase.cpp index 3ee4f93b348..75a59bf3edb 100644 --- a/src/coreComponents/fieldSpecification/FieldSpecificationBase.cpp +++ b/src/coreComponents/fieldSpecification/FieldSpecificationBase.cpp @@ -101,9 +101,10 @@ void FieldSpecificationBase::setMeshObjectPath( Group const & meshBodies ) { m_meshObjectPaths = std::make_unique< MeshObjectPath >( m_objectPath, meshBodies ); } - catch( InputError const & e ) + catch( std::exception const & e ) { - throw InputError( e, getDataContext().toString() + " has a wrong objectPath: " + m_objectPath + "\n" ); + throw InputError( e, getWrapperDataContext( viewKeyStruct::objectPathString() ).toString() + + " is a wrong objectPath: " + m_objectPath + "\n" ); } } diff --git a/src/coreComponents/fileIO/timeHistory/HistoryCollectionBase.cpp b/src/coreComponents/fileIO/timeHistory/HistoryCollectionBase.cpp index bf6af727210..10b46404afa 100644 --- a/src/coreComponents/fileIO/timeHistory/HistoryCollectionBase.cpp +++ b/src/coreComponents/fileIO/timeHistory/HistoryCollectionBase.cpp @@ -120,9 +120,10 @@ dataRepository::Group const * HistoryCollectionBase::getTargetObject( DomainPart } } ); - GEOS_ERROR_IF( !bodyFound, + GEOS_THROW_IF( !bodyFound, GEOS_FMT( "MeshBody ({}) is specified, but not found.", - targetTokens[0] ) ); + targetTokens[0] ), + std::domain_error ); } @@ -151,9 +152,10 @@ dataRepository::Group const * HistoryCollectionBase::getTargetObject( DomainPart } } ); - GEOS_ERROR_IF( !levelFound, + GEOS_THROW_IF( !levelFound, GEOS_FMT( "MeshLevel ({}) is specified, but not found.", - targetTokens[1] ) ); + targetTokens[1] ), + std::domain_error ); } } else if( !meshBody.getMeshLevels().hasGroup< MeshLevel >( targetTokens[1] ) ) @@ -203,7 +205,7 @@ dataRepository::Group const * HistoryCollectionBase::getTargetObject( DomainPart return targetGroup; } } - catch( std::domain_error const & e ) + catch( std::exception const & e ) { throw InputError( e, getDataContext().toString() + " has a wrong objectPath: " + objectPath + "\n" ); } diff --git a/src/coreComponents/mesh/ElementRegionBase.cpp b/src/coreComponents/mesh/ElementRegionBase.cpp index 1547ba1c46e..490ff866817 100644 --- a/src/coreComponents/mesh/ElementRegionBase.cpp +++ b/src/coreComponents/mesh/ElementRegionBase.cpp @@ -64,9 +64,10 @@ string ElementRegionBase::verifyMeshBodyName( Group const & meshBodies, { meshBodyName = onlyMeshBodyName; } - GEOS_ERROR_IF_NE_MSG( onlyMeshBodyName, + GEOS_THROW_IF_NE_MSG( onlyMeshBodyName, meshBodyName, - "MeshBody specified does not match MeshBody in hierarchy." ); + "MeshBody specified does not match MeshBody in hierarchy.", + InputError ); } else { @@ -78,9 +79,10 @@ string ElementRegionBase::verifyMeshBodyName( Group const & meshBodies, meshBodyFound = true; } } ); - GEOS_ERROR_IF( !meshBodyFound, + GEOS_THROW_IF( !meshBodyFound, "There are multiple MeshBodies in this problem, but the " - "specified MeshBody name "< Date: Wed, 10 May 2023 11:54:01 +0200 Subject: [PATCH 20/85] Group::printDataHierarchy : wrapper type fix + layout --- src/coreComponents/dataRepository/Group.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/coreComponents/dataRepository/Group.cpp b/src/coreComponents/dataRepository/Group.cpp index 185e15ccd2f..ad77dab8cad 100644 --- a/src/coreComponents/dataRepository/Group.cpp +++ b/src/coreComponents/dataRepository/Group.cpp @@ -257,14 +257,16 @@ Group * Group::createChild( string const & childKey, string const & childName ) void Group::printDataHierarchy( integer const indent ) { + GEOS_LOG( string( indent, '\t' ) << getName() << " : " << LvArray::system::demangleType( *this ) ); for( auto & view : wrappers() ) { - GEOS_LOG( string( indent, '\t' ) << view.second->getName() << ", " << LvArray::system::demangleType( view.second ) ); + GEOS_LOG( string( indent, '\t' ) << "-> " << view.second->getName() << " : " + << LvArray::system::demangleType( *view.second ) ); } + GEOS_LOG( string( indent, '\t' ) ); for( auto & group : m_subGroups ) { - GEOS_LOG( string( indent, '\t' ) << group.first << ':' ); group.second->printDataHierarchy( indent + 1 ); } } From 10fef506f66a021c4d387c2f18df617e6c1bd7d9 Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Wed, 10 May 2023 14:35:22 +0200 Subject: [PATCH 21/85] perforation error message improvement --- .../mesh/generators/InternalWellGenerator.cpp | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/coreComponents/mesh/generators/InternalWellGenerator.cpp b/src/coreComponents/mesh/generators/InternalWellGenerator.cpp index 98245bf0d06..c271b215816 100644 --- a/src/coreComponents/mesh/generators/InternalWellGenerator.cpp +++ b/src/coreComponents/mesh/generators/InternalWellGenerator.cpp @@ -26,6 +26,7 @@ #include "mesh/WellElementSubRegion.hpp" #include "mesh/PerforationData.hpp" #include "mesh/Perforation.hpp" +#include "physicsSolvers/fluidFlow/wells/WellControls.hpp" namespace geos { @@ -422,10 +423,15 @@ void InternalWellGenerator::connectPerforationsToWellElements() real64 const wellLength = m_nodeDistFromHead[m_elemToNodesMap[iwelemBottom][NodeLocation::BOTTOM]]; GEOS_THROW_IF( m_perfDistFromHead[iperf] > wellLength, - perf.getDataContext() << ": Distance from well perforation to head is larger than well polyline length\n \n" << - "Here is how the \"distanceFromHead\" keyword is used in the definition of the perforation location: \n" << - "We start from the well head (top of the well) and we measure the linear distance along the well polyline as we go down the well.\n" << - "When we reach the distanceFromHead specified by the user, we place a perforation on the well at this location of the polyline, and connect it to the reservoir element that contains this perforation", + GEOS_FMT( "{}: Distance from well perforation to head ({} = {}) is larger than well" + " polyline length ({})\n \n You should check the following values:" + "\n 1 - {}\n 2 - the {} of the used WellControls named {}\n 3 - {}, Z values", + perf.getWrapperDataContext( Perforation::viewKeyStruct::distanceFromHeadString() ), + Perforation::viewKeyStruct::distanceFromHeadString(), + m_perfDistFromHead[iperf], wellLength, + perf.getWrapperDataContext( Perforation::viewKeyStruct::distanceFromHeadString() ), + WellControls::viewKeyStruct::refElevString(), m_wellControlsName, + getWrapperDataContext( viewKeyStruct::polylineNodeCoordsString() ) ), InputError ); // start binary search From 197ae3805f55c53216ee4b1a549db72700ab6d7a Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Wed, 10 May 2023 14:37:59 +0200 Subject: [PATCH 22/85] made "cellBlocks" attribute a required one --- src/coreComponents/mesh/CellElementRegion.cpp | 2 +- src/coreComponents/schema/schema.xsd | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/coreComponents/mesh/CellElementRegion.cpp b/src/coreComponents/mesh/CellElementRegion.cpp index e4037110d19..54caf947302 100644 --- a/src/coreComponents/mesh/CellElementRegion.cpp +++ b/src/coreComponents/mesh/CellElementRegion.cpp @@ -23,7 +23,7 @@ CellElementRegion::CellElementRegion( string const & name, Group * const parent ElementRegionBase( name, parent ) { registerWrapper( viewKeyStruct::sourceCellBlockNamesString(), &m_cellBlockNames ). - setInputFlag( InputFlags::OPTIONAL ); + setInputFlag( InputFlags::REQUIRED ); registerWrapper( viewKeyStruct::coarseningRatioString(), &m_coarseningRatio ). setInputFlag( InputFlags::OPTIONAL ); diff --git a/src/coreComponents/schema/schema.xsd b/src/coreComponents/schema/schema.xsd index 03253850303..06af48ed542 100644 --- a/src/coreComponents/schema/schema.xsd +++ b/src/coreComponents/schema/schema.xsd @@ -4199,7 +4199,7 @@ The expected format is "{ waterMax, oilMax }", in that order--> - + From e644d86a0bf8c5b0f12ffa597e60be54f5c8ddfa Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Wed, 10 May 2023 14:39:34 +0200 Subject: [PATCH 23/85] Error messages slight improvements + uncrustify --- src/coreComponents/mesh/Perforation.cpp | 2 +- src/coreComponents/mesh/SurfaceElementRegion.hpp | 2 +- src/coreComponents/mesh/simpleGeometricObjects/Box.cpp | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/coreComponents/mesh/Perforation.cpp b/src/coreComponents/mesh/Perforation.cpp index 64671e70ef5..2099d5df04c 100644 --- a/src/coreComponents/mesh/Perforation.cpp +++ b/src/coreComponents/mesh/Perforation.cpp @@ -46,7 +46,7 @@ void Perforation::postProcessInput() { GEOS_ERROR_IF( m_distanceFromHead <= 0, getWrapperDataContext( viewKeyStruct::distanceFromHeadString() ) << - ": Invalid distance well head to perforation " ); + ": distance from well head to perforation cannot be negative." ); } diff --git a/src/coreComponents/mesh/SurfaceElementRegion.hpp b/src/coreComponents/mesh/SurfaceElementRegion.hpp index 23fd16496e5..0e86f38a54a 100644 --- a/src/coreComponents/mesh/SurfaceElementRegion.hpp +++ b/src/coreComponents/mesh/SurfaceElementRegion.hpp @@ -201,7 +201,7 @@ class SurfaceElementRegion : public ElementRegionBase subRegionNames.push_back( sr.getName() ); } ); GEOS_ERROR_IF( subRegionNames.size() != 1, - "Surface region \"" << getDataContext() << + "Surface region \"" << getDataContext() << "\" should have one unique sub region (" << subRegionNames.size() << " found)." ); return subRegionNames.front(); } diff --git a/src/coreComponents/mesh/simpleGeometricObjects/Box.cpp b/src/coreComponents/mesh/simpleGeometricObjects/Box.cpp index 30d90ff610f..25325c1d2d0 100644 --- a/src/coreComponents/mesh/simpleGeometricObjects/Box.cpp +++ b/src/coreComponents/mesh/simpleGeometricObjects/Box.cpp @@ -69,8 +69,8 @@ void Box::postProcessInput() if( std::fabs( m_strikeAngle ) > 1e-20 ) { GEOS_ERROR_IF( (m_max[0]-m_min[0]) < (m_max[1]-m_min[1]), - getDataContext() << ": When a strike angle is specified, the box is supposed to represent a plane normal to the " - "y direction. This box seems to be too thick." ); + getDataContext() << ": When a strike angle is specified, the box is supposed to" << + " represent a plane normal to the y direction. This box seems to be too thick." ); m_cosStrike = std::cos( m_strikeAngle / 180 *M_PI ); m_sinStrike = std::sin( m_strikeAngle / 180 *M_PI ); From 026786071683f23320abfe87d671241addd51a38 Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Tue, 16 May 2023 11:18:15 +0200 Subject: [PATCH 24/85] uncrustify --- src/coreComponents/dataRepository/WrapperBase.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreComponents/dataRepository/WrapperBase.cpp b/src/coreComponents/dataRepository/WrapperBase.cpp index bc61462b11c..86411dfd260 100644 --- a/src/coreComponents/dataRepository/WrapperBase.cpp +++ b/src/coreComponents/dataRepository/WrapperBase.cpp @@ -136,7 +136,7 @@ void WrapperBase::processInputException( std::exception const & ex, { oss << targetNode.path() << " (name=" << targetNode.attribute( "name" ).value() << ")/" << getName(); } - oss << "\n***** Input value: '" << inputStr << '\''; + oss << "\n***** Input value: '" << inputStr << '\''; oss << ( exStr[0]=='\n' ? exStr : "'\n" + exStr ); throw InputError( oss.str() ); From 40aae17c80b0911d33335aa10595b029090530a5 Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Tue, 16 May 2023 11:34:03 +0200 Subject: [PATCH 25/85] code style --- .../dataRepositoryTests/testGroupPath.cpp | 101 +++++++++--------- 1 file changed, 51 insertions(+), 50 deletions(-) diff --git a/src/coreComponents/unitTests/dataRepositoryTests/testGroupPath.cpp b/src/coreComponents/unitTests/dataRepositoryTests/testGroupPath.cpp index b439783f112..45f08453d63 100644 --- a/src/coreComponents/unitTests/dataRepositoryTests/testGroupPath.cpp +++ b/src/coreComponents/unitTests/dataRepositoryTests/testGroupPath.cpp @@ -27,56 +27,57 @@ TEST( testGroupPath, testGlobalPaths ) using namespace geos; using namespace dataRepository; - char const * xmlInput = - "\n" - "\n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - ""; + char const * xmlInput = + R"xml( + + + + + + + + + + + + + + + + + + + + + + + )xml"; std::vector< string > const groupPaths{ "/Mesh/mesh1", From f5f1276d3610dba8d0f572ed91f8536d8f144996 Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Tue, 16 May 2023 11:44:08 +0200 Subject: [PATCH 26/85] update LvArray version --- src/coreComponents/LvArray | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreComponents/LvArray b/src/coreComponents/LvArray index 42940840613..eaa8e43883b 160000 --- a/src/coreComponents/LvArray +++ b/src/coreComponents/LvArray @@ -1 +1 @@ -Subproject commit 429408406138a9827fdeaacdf2669fe94e9a10f0 +Subproject commit eaa8e43883b2fd3dec9879c18c367144255c9fa3 From faa73581e0ece0ebf19bade3e93f03d7814a4870 Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Tue, 16 May 2023 13:15:34 +0200 Subject: [PATCH 27/85] update LvArray version --- src/coreComponents/LvArray | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreComponents/LvArray b/src/coreComponents/LvArray index eaa8e43883b..42940840613 160000 --- a/src/coreComponents/LvArray +++ b/src/coreComponents/LvArray @@ -1 +1 @@ -Subproject commit eaa8e43883b2fd3dec9879c18c367144255c9fa3 +Subproject commit 429408406138a9827fdeaacdf2669fe94e9a10f0 From be91af38f3617e8a63da7020f7055ccc03d3ad59 Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Tue, 16 May 2023 14:09:48 +0200 Subject: [PATCH 28/85] improved messages with group/wrapper path --- .../dataRepository/DataContext.cpp | 39 +++++++++++++------ .../dataRepository/DataContext.hpp | 2 +- src/coreComponents/dataRepository/Group.hpp | 6 +++ 3 files changed, 34 insertions(+), 13 deletions(-) diff --git a/src/coreComponents/dataRepository/DataContext.cpp b/src/coreComponents/dataRepository/DataContext.cpp index fc92bccd06f..e66d033b1cc 100644 --- a/src/coreComponents/dataRepository/DataContext.cpp +++ b/src/coreComponents/dataRepository/DataContext.cpp @@ -18,7 +18,6 @@ #include "Group.hpp" #include "WrapperBase.hpp" -#include "../mainInterface/ProblemManager.hpp" namespace geos { @@ -48,8 +47,32 @@ GroupContext::GroupContext( Group & group ): string GroupContext::toString() const { - // it would be possible to insert the DataFileContext::toString() of parent objects when it exists, but is it relevant ? - return m_group.getPath(); + string path; + bool foundNearestLine = false; + for( Group const * parentGroup = &m_group; parentGroup->hasParent(); parentGroup = &parentGroup->getParent() ) + { + if( !foundNearestLine && parentGroup->getDataContext().isDataFileContext() ) + { + DataFileContext const & parentContext = + dynamic_cast< DataFileContext const & >( parentGroup->getDataContext() ); + if( parentContext.getLine() != xmlWrapper::xmlDocument::npos ) + { + path.insert( 0, '/' + parentGroup->getName() + '(' + + splitPath( parentContext.getFilePath() ).second + + ",l." + std::to_string( parentContext.getLine() ) + ')' ); + foundNearestLine=true; + } + else + { + path.insert( 0, '/' + parentGroup->getName() ); + } + } + else + { + path.insert( 0, '/' + parentGroup->getName() ); + } + } + return path; } @@ -60,15 +83,7 @@ WrapperContext::WrapperContext( WrapperBase & wrapper ): string WrapperContext::toString() const { // if possible, we show the DataFileContext of the parent. - if( m_group.getDataContext().isDataFileContext() ) - { - return m_group.getDataContext().toString() + ", attribute " + m_objectName; - } - else - { - // it would be possible to insert the DataFileContext::toString() of parent objects when it exists, but is it relevant ? - return m_group.getPath() + "/" + m_objectName; - } + return m_group.getDataContext().toString() + ", attribute " + m_objectName; } diff --git a/src/coreComponents/dataRepository/DataContext.hpp b/src/coreComponents/dataRepository/DataContext.hpp index fc03fb26fd2..57f33a45ff7 100644 --- a/src/coreComponents/dataRepository/DataContext.hpp +++ b/src/coreComponents/dataRepository/DataContext.hpp @@ -107,7 +107,7 @@ class GroupContext : public DataContext Group & getGroup() const; /** - * @return the group path. + * @return the group path with the file & line of the first parent for which this information exists. */ virtual string toString() const; diff --git a/src/coreComponents/dataRepository/Group.hpp b/src/coreComponents/dataRepository/Group.hpp index 712c0299ad5..02945bfb9e2 100644 --- a/src/coreComponents/dataRepository/Group.hpp +++ b/src/coreComponents/dataRepository/Group.hpp @@ -1325,6 +1325,12 @@ class Group return *m_parent; } + /** + * @return true if this group has a parent. + */ + bool hasParent() const + { return m_parent != nullptr; } + /** * @brief Get the group's index within its parent group * @return integral index of current group within its parent From 5fae7b61d8b16453baa17120ee226717e8f5cc4e Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Tue, 16 May 2023 15:55:12 +0200 Subject: [PATCH 29/85] rewrited WrapperContext message --- src/coreComponents/dataRepository/DataContext.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/coreComponents/dataRepository/DataContext.cpp b/src/coreComponents/dataRepository/DataContext.cpp index e66d033b1cc..d61ade520b6 100644 --- a/src/coreComponents/dataRepository/DataContext.cpp +++ b/src/coreComponents/dataRepository/DataContext.cpp @@ -82,8 +82,14 @@ WrapperContext::WrapperContext( WrapperBase & wrapper ): string WrapperContext::toString() const { - // if possible, we show the DataFileContext of the parent. - return m_group.getDataContext().toString() + ", attribute " + m_objectName; + if( m_group.getDataContext().isDataFileContext() ) + { + return m_group.getDataContext().toString() + ", attribute " + m_objectName; + } + else + { + return m_group.getDataContext().toString() + "/" + m_objectName; + } } From 585d59da4be9d531a959cd36776bf6228d5790fd Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Wed, 17 May 2023 15:29:37 +0200 Subject: [PATCH 30/85] Documentation --- .../dataRepository/DataContext.hpp | 35 +++++++++++---- src/coreComponents/dataRepository/Group.hpp | 1 + .../dataRepository/WrapperBase.hpp | 6 ++- .../dataRepository/xmlWrapper.hpp | 44 +++++++++++++++---- src/docs/doxygen/GeosxConfig.hpp | 2 +- 5 files changed, 68 insertions(+), 20 deletions(-) diff --git a/src/coreComponents/dataRepository/DataContext.hpp b/src/coreComponents/dataRepository/DataContext.hpp index 57f33a45ff7..eb6c0676882 100644 --- a/src/coreComponents/dataRepository/DataContext.hpp +++ b/src/coreComponents/dataRepository/DataContext.hpp @@ -59,6 +59,9 @@ class DataContext */ virtual string toString() const = 0; + /** + * @brief Get the target object name + */ string getObjectName() const { return m_objectName; } @@ -102,7 +105,7 @@ class GroupContext : public DataContext virtual ~GroupContext() {} /** - * @brief Get the reference to the Group related to this GroupContext. + * @return the reference to the Group related to this GroupContext. */ Group & getGroup() const; @@ -133,6 +136,7 @@ class WrapperContext final : public GroupContext /** * @brief Construct a new WrapperContext object + * @param wrapper the target Wrapper object */ WrapperContext( WrapperBase & wrapper ); @@ -155,10 +159,15 @@ class DataFileContext final : public DataContext /** * @brief Construct the file context of a Group from an xml node. + * @param group the target Group object + * @param nodePos the target object xml node position + * @param nodeTagName the xml node tag name. */ DataFileContext( Group & group, xmlWrapper::xmlNodePos const & nodePos, string const & nodeTagName ); /** * @brief Construct the file context of a Group from an xml attribute. + * @param wrapper the target Wrapper object + * @param attPos the target object xml attribute position */ DataFileContext( WrapperBase & wrapper, xmlWrapper::xmlAttributePos const & attPos ); @@ -173,33 +182,32 @@ class DataFileContext final : public DataContext virtual string toString() const; /** - * @brief Get the type name in the source file (XML node tag name / attribute name). + * @return the type name in the source file (XML node tag name / attribute name). */ string getTypeName() const { return m_typeName; } /** - * @brief Get the source file path where the target object has been declared. + * @return the source file path where the target object has been declared. */ string getFilePath() const { return m_filePath; } /** - * @brief Get the line (starting from 1) where the target object has been declared in the source - * file. + * @return the line (starting from 1) where the target object has been declared in the source file. */ size_t getLine() const { return m_line; } /** - * @brief Get the character offset in the line (starting from 1) where the target object has been declared in the source - * file. + * @return the character offset in the line (starting from 1) where the target object has been + * declared in the source file. */ size_t getOffsetInLine() const { return m_offsetInLine; } /** - * @brief Get the character offset (starting from 0) in the source file path where the target object has been + * @return the character offset (starting from 0) in the source file path where the target object has been * declared. */ size_t getOffset() const @@ -225,18 +233,25 @@ class DataFileContext final : public DataContext } /* namespace geos */ + + +/// @cond DO_NOT_DOCUMENT #ifdef GEOSX_USE_FMT #define GEOS_FMT_NS_PREFIX fmt #else #define GEOS_FMT_NS_PREFIX std #endif +/// @endcond /// Formatter to be able to directly use a DataContext as a GEOS_FMT() argument. template<> struct GEOS_FMT_NS_PREFIX::formatter< geos::dataRepository::DataContext > { /** - * @brief Format the specified dataContext to a string. + * @brief Format the specified DataContext to a string. + * @param dataContext the DataContext object to format + * @param ctx formatting state consisting of the formatting arguments and the output iterator + * @return iterator to the output buffer */ auto format( geos::dataRepository::DataContext const & dataContext, format_context & ctx ) { @@ -245,6 +260,8 @@ struct GEOS_FMT_NS_PREFIX::formatter< geos::dataRepository::DataContext > /** * @brief Method to parse a dataContext from a string. Not implemented! + * @param ctx formatting state consisting of the formatting arguments and the output iterator + * @return iterator to the output buffer (leaved unchanged) */ constexpr auto parse( format_parse_context & ctx ) { return ctx.begin(); } diff --git a/src/coreComponents/dataRepository/Group.hpp b/src/coreComponents/dataRepository/Group.hpp index 02945bfb9e2..caed4e47524 100644 --- a/src/coreComponents/dataRepository/Group.hpp +++ b/src/coreComponents/dataRepository/Group.hpp @@ -747,6 +747,7 @@ class Group /** * @brief Recursively read values using ProcessInputFile() from the input * file and put them into the wrapped values for this group. + * @param[in] xmlDocument the XML document that contains the targetNode * @param[in] targetNode the XML node that to extract input values from. */ void processInputFileRecursive( xmlWrapper::xmlDocument & xmlDocument, diff --git a/src/coreComponents/dataRepository/WrapperBase.hpp b/src/coreComponents/dataRepository/WrapperBase.hpp index f020b3805ca..7fd72b6d7db 100644 --- a/src/coreComponents/dataRepository/WrapperBase.hpp +++ b/src/coreComponents/dataRepository/WrapperBase.hpp @@ -180,7 +180,8 @@ class WrapperBase /** * @brief Initialize the wrapper from the input xml node. * @param targetNode the xml node to initialize from. - * @return True iff the wrapper initialized itself from the file. + * @param nodePos the target node position, typically obtained with xmlDocument::getNodePosition(). + * @return True if the wrapper initialized itself from the file. */ virtual bool processInputFile( xmlWrapper::xmlNode const & targetNode, xmlWrapper::xmlNodePos const & nodePos ) = 0; @@ -422,7 +423,7 @@ class WrapperBase { return *m_dataContext; } /** - * @brief Return the group that contains this Wrapper. + * @return the group that contains this Wrapper. */ Group & getParent() { return *m_parent; } @@ -643,6 +644,7 @@ class WrapperBase * @brief Helper method to process an exception that has been thrown during xml parsing. * @param ex The caught exception. * @param targetNode The node from which this Group is interpreted. + * @param nodePos the target node position. */ void processInputException( std::exception const & ex, xmlWrapper::xmlNode const & targetNode, xmlWrapper::xmlNodePos const & nodePos ) const; diff --git a/src/coreComponents/dataRepository/xmlWrapper.hpp b/src/coreComponents/dataRepository/xmlWrapper.hpp index c4ecb4d34eb..994f1e18f07 100644 --- a/src/coreComponents/dataRepository/xmlWrapper.hpp +++ b/src/coreComponents/dataRepository/xmlWrapper.hpp @@ -78,6 +78,10 @@ struct xmlAttributePos /** * @brief Constructor of this struct. + * @param filePath the path of the original xml file containing this attribute + * @param line Line where the element is defined. Start at 1. + * @param offsetInLine Character offset of this element in the line that contains it (starting from 1) + * @param offset Character offset of this element in the file that contains it (starting from 0) */ xmlAttributePos( string const & filePath, size_t line, size_t offsetInLine, size_t offset ); /** @@ -98,6 +102,11 @@ struct xmlNodePos : xmlAttributePos /** * @brief Constructor of this struct. + * @param document an xml document containing this node, or including a file which includes it + * @param filePath the path of the original xml file containing this node + * @param line Line where the node is defined. Start at 1. + * @param offsetInLine Character offset of this node in the line that contains it (starting from 1) + * @param offset Character offset of this node in the file that contains it (starting from 0) */ xmlNodePos( xmlDocument const & document, string const & filePath, size_t line, size_t offsetInLine, size_t offset ); /** @@ -105,7 +114,9 @@ struct xmlNodePos : xmlAttributePos */ bool isFound() const; /** - * @brief Compute the xmlAttributePos of the attributed with the given name. + * @brief Compute the xmlAttributePos of an xml attribute + * @param attName the name of the attribute to locate + * @return an xmlAttributePos object that represents the position of the target node. */ xmlAttributePos getAttributeLine( string const & attName ) const; }; @@ -125,26 +136,29 @@ class xmlDocument : public pugi::xml_document xmlDocument(); /** - * @brief Get the original file buffer loaded during the last load_X() call on this object. + * @return the original file buffer loaded during the last load_X() call on this object. */ string const & getOriginalBuffer() const; /** - * @brief If the specified file at the "filePath" is the loaded document of the instance, + * @return If the specified file at the "filePath" is the loaded document of the instance, * or one of its includes, returns its original buffer as a string. * Returns nullptr if filePath is not a loaded and available document. + * @param filePath the path of the file which buffer must be returned. */ string const * getOriginalBuffer( string const & filePath ) const; /** - * @brief Map containing the original buffers of the document and its includes, indexed by file path. + * @return a map containing the original buffers of the document and its includes, indexed by file path. */ map< string, string > const & getOriginalBuffers() const; /** - * @brief If load_file() has been loaded, returns the path of the source file. + * @return If load_file() has been loaded, returns the path of the source file. * If another load method has been called, it returns a generated unique value. */ string const & getFilePath() const; /** - * @brief Compute the position of the given node if the node file information are loaded. + * @brief If the node file information are loaded, compute the position of a node. + * @param node the node to locate + * @return an xmlNodePos object that represents the position of the target node. * @throws an InputError if the node position is not found and source file is loaded. */ xmlNodePos getNodePosition( xmlWrapper::xmlNode const & node ) const; @@ -152,14 +166,22 @@ class xmlDocument : public pugi::xml_document /** * @brief Load document from zero-terminated string. No encoding conversions are applied. * Wrapper of pugi::xml_document::load_buffer() method. + * @param contents the string containing the document content * @param loadNodeFileInfo Load the node source file info, allowing getNodePosition() to work. + * @param options the parsing options + * @param encoding the encoding options + * @return an xmlResult object representing the parsing resulting status. */ xmlResult load_string( const pugi::char_t * contents, bool loadNodeFileInfo = false, unsigned int options = pugi::parse_default ); /** * @brief Load document from file. Wrapper of pugi::xml_document::load_buffer() method. + * @param path the path of an xml file to load. * @param loadNodeFileInfo Load the node source file info, allowing getNodePosition() to work. + * @param options the parsing options + * @param encoding the encoding options + * @return an xmlResult object representing the parsing resulting status. */ xmlResult load_file( const char * path, bool loadNodeFileInfo = false, unsigned int options = pugi::parse_default, @@ -168,7 +190,12 @@ class xmlDocument : public pugi::xml_document /** * @brief Load document from buffer. Copies/converts the buffer, so it may be deleted or changed * after the function returns. Wrapper of pugi::xml_document::load_buffer() method. + * @param contents the buffer containing the document content + * @param size the size of the buffer in bytes * @param loadNodeFileInfo Load the node source file info, allowing getNodePosition() to work. + * @param options the parsing options + * @param encoding the encoding options + * @return an xmlResult object representing the parsing resulting status. */ xmlResult load_buffer( const void * contents, size_t size, bool loadNodeFileInfo = false, unsigned int options = pugi::parse_default, @@ -214,7 +241,7 @@ class xmlDocument : public pugi::xml_document void addIncludedXML( xmlNode & targetNode, int level = 0 ); /** - * @brief True if loadNodeFileInfo was true during the last load_X call. + * @return True if loadNodeFileInfo was true during the last load_X call. */ bool hasNodeFileInfo() const; @@ -262,7 +289,8 @@ string buildMultipleInputXML( string_array const & inputFileList, string const & outputDir = {} ); /** - * @return if the attribute with the specified name declares metadata relative to the xml + * @return true if the attribute with the specified name declares metadata relative to the xml + * @param name the name of an attribute */ bool isFileMetadataAttribute( string const & name ); diff --git a/src/docs/doxygen/GeosxConfig.hpp b/src/docs/doxygen/GeosxConfig.hpp index 53e69939fc2..6fed87bb8da 100644 --- a/src/docs/doxygen/GeosxConfig.hpp +++ b/src/docs/doxygen/GeosxConfig.hpp @@ -141,7 +141,7 @@ #define fmt_VERSION 8.0.1 /// Version information for python -#define Python3_VERSION 3.10.8 +/* #undef Python3_VERSION */ /// Version information for CUDAToolkit /* #undef CUDAToolkit_VERSION */ From e5e91b681de7b7a12aa3347d4a75b1ce998a8cec Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Wed, 17 May 2023 15:59:12 +0200 Subject: [PATCH 31/85] submodule develop sync --- src/coreComponents/LvArray | 2 +- src/coreComponents/fileIO/coupling/hdf5_interface | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/coreComponents/LvArray b/src/coreComponents/LvArray index 42940840613..9444c314b5c 160000 --- a/src/coreComponents/LvArray +++ b/src/coreComponents/LvArray @@ -1 +1 @@ -Subproject commit 429408406138a9827fdeaacdf2669fe94e9a10f0 +Subproject commit 9444c314b5ca0209788ce485bc5b3d369f89f0cf diff --git a/src/coreComponents/fileIO/coupling/hdf5_interface b/src/coreComponents/fileIO/coupling/hdf5_interface index bbc6bd0e2bf..5136554439e 160000 --- a/src/coreComponents/fileIO/coupling/hdf5_interface +++ b/src/coreComponents/fileIO/coupling/hdf5_interface @@ -1 +1 @@ -Subproject commit bbc6bd0e2bfe6057ae9913e2713688eb8c26bc09 +Subproject commit 5136554439e791dc5e948f2a74ede31c4c697ef5 From 6c392568755bce9a6dce6a95797806973b2df569 Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Mon, 22 May 2023 15:53:07 +0200 Subject: [PATCH 32/85] face error message improvement --- src/coreComponents/mesh/FaceManager.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/coreComponents/mesh/FaceManager.cpp b/src/coreComponents/mesh/FaceManager.cpp index 461d97240c4..213082d8de9 100644 --- a/src/coreComponents/mesh/FaceManager.cpp +++ b/src/coreComponents/mesh/FaceManager.cpp @@ -196,7 +196,9 @@ void FaceManager::sortAllFaceNodes( NodeManager const & nodeManager, // The face should be connected to at least one element. if( facesToElements( faceIndex, 0 ) < 0 && facesToElements( faceIndex, 1 ) < 0 ) { - GEOS_ERROR( getDataContext() << ": Face " << faceIndex << " is not connected to an element." ); + GEOS_ERROR( getDataContext() << ": Face " << faceIndex << " is not connected to an element." << + "The cellBlocks of the CellElementRegions might not have referenced this face." << + " Be sure to include every primitive of every part of the mesh in the cellBlocks." ); } // Take the first defined face-to-(elt/region/sub region) to sorting direction. From d79b0c9f71147e6c173d565aa69ecfb10867e19f Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Tue, 23 May 2023 11:48:37 +0200 Subject: [PATCH 33/85] code style / factorization + constness --- .../dataRepository/DataContext.hpp | 19 ++++---------- .../unitTests/xmlTests/testXMLFile.cpp | 25 ++++++++----------- 2 files changed, 16 insertions(+), 28 deletions(-) diff --git a/src/coreComponents/dataRepository/DataContext.hpp b/src/coreComponents/dataRepository/DataContext.hpp index eb6c0676882..8e9efcdd71d 100644 --- a/src/coreComponents/dataRepository/DataContext.hpp +++ b/src/coreComponents/dataRepository/DataContext.hpp @@ -23,6 +23,8 @@ #include "common/Logger.hpp" #include "xmlWrapper.hpp" #include "common/Format.hpp" +#include "Group.hpp" +#include "WrapperBase.hpp" namespace geos { @@ -30,12 +32,9 @@ namespace geos namespace dataRepository { -class Group; -class WrapperBase; - /// DataContext is an abstract class storing contextual information on an object: -/// - its line position in a file, if applicable, implementation in DataFileContext, -/// - its location in the data hierarchy, implementation in GroupContext and WrapperContext. +/// - Either its line position in a file (if applicable, implementation in DataFileContext), +/// - or its location in the data hierarchy (implementation in GroupContext and WrapperContext). /// Typically, the target object contains an unique_ptr< DataContext > instance of this class. class DataContext { @@ -235,17 +234,9 @@ class DataFileContext final : public DataContext -/// @cond DO_NOT_DOCUMENT -#ifdef GEOSX_USE_FMT -#define GEOS_FMT_NS_PREFIX fmt -#else -#define GEOS_FMT_NS_PREFIX std -#endif -/// @endcond - /// Formatter to be able to directly use a DataContext as a GEOS_FMT() argument. template<> -struct GEOS_FMT_NS_PREFIX::formatter< geos::dataRepository::DataContext > +struct GEOS_FMT_NS::formatter< geos::dataRepository::DataContext > { /** * @brief Format the specified DataContext to a string. diff --git a/src/coreComponents/unitTests/xmlTests/testXMLFile.cpp b/src/coreComponents/unitTests/xmlTests/testXMLFile.cpp index 8cd68e6fac0..395bbd37cf8 100644 --- a/src/coreComponents/unitTests/xmlTests/testXMLFile.cpp +++ b/src/coreComponents/unitTests/xmlTests/testXMLFile.cpp @@ -178,17 +178,14 @@ void verifyGroupDataFileContextRecursive( xmlDocument const & document, Group co /** * @brief Returns the element that exists in setB but not in setA. */ -std::set< string > getDifference( std::set< string > & setA, - std::set< string > & setB ) +std::set< string > getDifference( std::set< string > const & setA, + std::set< string > const & setB ) { - std::vector< string > result( setA.size() + setB.size() ); - - auto it = std::set_difference( setA.begin(), setA.end(), - setB.begin(), setB.end(), - result.begin() ); - result.resize( it - result.begin() ); - - return std::set< string >( result.begin(), result.end() ); + std::set< string > result; + std::set_difference( setA.cbegin(), setA.cend(), + setB.cbegin(), setB.cend(), + std::inserter( result, result.begin() ) ); + return result; } // Tests @@ -219,15 +216,15 @@ TEST( testXML, testXMLFileLines ) std::set< string > verifiedElements; verifyGroupDataFileContextRecursive( xmlDocument, problemManager, verifiedElements ); - std::set< string > notFound = getDifference( expectedElements, verifiedElements ); + std::set< string > const notFound = getDifference( expectedElements, verifiedElements ); EXPECT_TRUE( notFound.empty() ) << "Info : There should not exist xml element that were not in " - "the Group hierarchy.\nNot in Group hierarchy : {" + "the Group hierarchy.\nElements not found in Group hierarchy : {" << stringutilities::join( notFound, "," ) << "}"; - std::set< string > notExpected = getDifference( verifiedElements, expectedElements ); + std::set< string > const notExpected = getDifference( verifiedElements, expectedElements ); EXPECT_TRUE( notExpected.empty() ) << "Info : There should not exist an object in the Group " "hierarchy that contains a DataFileContext but which were not " - "declared in the Xml.\nNot in XML hierarchy : {" + "declared in the Xml.\nElements not found in XML hierarchy : {" << stringutilities::join( notExpected, "," ) << "}"; } From f7217eeaf9c79749259cc1361b947b4e49966cba Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Wed, 24 May 2023 15:05:17 +0200 Subject: [PATCH 34/85] docs fixes --- src/coreComponents/dataRepository/DataContext.hpp | 2 +- src/coreComponents/dataRepository/xmlWrapper.hpp | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/coreComponents/dataRepository/DataContext.hpp b/src/coreComponents/dataRepository/DataContext.hpp index 8e9efcdd71d..c6d20f75c46 100644 --- a/src/coreComponents/dataRepository/DataContext.hpp +++ b/src/coreComponents/dataRepository/DataContext.hpp @@ -59,7 +59,7 @@ class DataContext virtual string toString() const = 0; /** - * @brief Get the target object name + * @return Get the target object name */ string getObjectName() const { return m_objectName; } diff --git a/src/coreComponents/dataRepository/xmlWrapper.hpp b/src/coreComponents/dataRepository/xmlWrapper.hpp index 994f1e18f07..117e5482952 100644 --- a/src/coreComponents/dataRepository/xmlWrapper.hpp +++ b/src/coreComponents/dataRepository/xmlWrapper.hpp @@ -169,7 +169,6 @@ class xmlDocument : public pugi::xml_document * @param contents the string containing the document content * @param loadNodeFileInfo Load the node source file info, allowing getNodePosition() to work. * @param options the parsing options - * @param encoding the encoding options * @return an xmlResult object representing the parsing resulting status. */ xmlResult load_string( const pugi::char_t * contents, bool loadNodeFileInfo = false, From b3afd5ccbe1b5def46b998a15730423d53e554fa Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Fri, 2 Jun 2023 17:04:19 +0200 Subject: [PATCH 35/85] error message fix --- src/coreComponents/mesh/generators/InternalWellGenerator.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreComponents/mesh/generators/InternalWellGenerator.cpp b/src/coreComponents/mesh/generators/InternalWellGenerator.cpp index c271b215816..241cdc2c634 100644 --- a/src/coreComponents/mesh/generators/InternalWellGenerator.cpp +++ b/src/coreComponents/mesh/generators/InternalWellGenerator.cpp @@ -425,7 +425,7 @@ void InternalWellGenerator::connectPerforationsToWellElements() GEOS_THROW_IF( m_perfDistFromHead[iperf] > wellLength, GEOS_FMT( "{}: Distance from well perforation to head ({} = {}) is larger than well" " polyline length ({})\n \n You should check the following values:" - "\n 1 - {}\n 2 - the {} of the used WellControls named {}\n 3 - {}, Z values", + "\n 1 - {}\n 2 - {}, Z values", perf.getWrapperDataContext( Perforation::viewKeyStruct::distanceFromHeadString() ), Perforation::viewKeyStruct::distanceFromHeadString(), m_perfDistFromHead[iperf], wellLength, From 17759eb031c2c73e3469f47aa718f88479129a9f Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Tue, 20 Jun 2023 15:06:19 +0200 Subject: [PATCH 36/85] code cleaning / refactoring, docs improvements --- .../dataRepository/DataContext.cpp | 6 ++--- .../dataRepository/DataContext.hpp | 24 +++++++++---------- src/coreComponents/dataRepository/Group.hpp | 4 ++-- .../dataRepository/xmlWrapper.hpp | 15 ++++++++---- .../mainInterface/ProblemManager.hpp | 3 ++- .../unitTests/xmlTests/testXMLFile.cpp | 5 ++-- 6 files changed, 32 insertions(+), 25 deletions(-) diff --git a/src/coreComponents/dataRepository/DataContext.cpp b/src/coreComponents/dataRepository/DataContext.cpp index d61ade520b6..7bc43026fca 100644 --- a/src/coreComponents/dataRepository/DataContext.cpp +++ b/src/coreComponents/dataRepository/DataContext.cpp @@ -25,8 +25,8 @@ namespace dataRepository { -DataContext::DataContext( string const & objectName, bool const isDataFileContext ): - m_objectName( objectName ), +DataContext::DataContext( string const & targetName, bool const isDataFileContext ): + m_targetName( targetName ), m_isDataFileContext( isDataFileContext ) {} @@ -115,7 +115,7 @@ DataFileContext::DataFileContext( WrapperBase & wrapper, xmlWrapper::xmlAttribut string DataFileContext::toString() const { std::ostringstream oss; - oss << m_objectName; + oss << m_targetName; if( m_line != xmlWrapper::xmlDocument::npos ) { oss << " (" << splitPath( m_filePath ).second << ", l." << m_line << ")"; diff --git a/src/coreComponents/dataRepository/DataContext.hpp b/src/coreComponents/dataRepository/DataContext.hpp index c6d20f75c46..163d4b5f7a0 100644 --- a/src/coreComponents/dataRepository/DataContext.hpp +++ b/src/coreComponents/dataRepository/DataContext.hpp @@ -42,10 +42,10 @@ class DataContext /** * @brief Construct a new DataContext object. - * @param objectName the target object name + * @param targetName the target object name * @param isDataFileContext true if this Context is a DataFileContext */ - DataContext( string const & objectName, bool isDataFileContext ); + DataContext( string const & targetName, bool isDataFileContext ); /** * @brief Destroy the DataContext object @@ -61,8 +61,8 @@ class DataContext /** * @return Get the target object name */ - string getObjectName() const - { return m_objectName; } + string getTargetName() const + { return m_targetName; } /** * @brief Flag on availability of file information. Used to provide more user-friendly information. @@ -78,10 +78,10 @@ class DataContext protected: - /// see getObjectName() - string const m_objectName; + /// @see getObjectName() + string const m_targetName; - /// see isDataFileContext() + /// @see isDataFileContext() bool const m_isDataFileContext; }; @@ -214,15 +214,15 @@ class DataFileContext final : public DataContext protected: - /// see getTypeName() + /// @see getTypeName() string const m_typeName; - /// see getFilePath() + /// @see getFilePath() string const m_filePath; - /// see getLine() + /// @see getLine() size_t const m_line; - /// see getLineOffset() + /// @see getLineOffset() size_t const m_offsetInLine; - /// see getOffset() + /// @see getOffset() size_t const m_offset; }; diff --git a/src/coreComponents/dataRepository/Group.hpp b/src/coreComponents/dataRepository/Group.hpp index 6084176a8f5..745a8d1c31f 100644 --- a/src/coreComponents/dataRepository/Group.hpp +++ b/src/coreComponents/dataRepository/Group.hpp @@ -26,7 +26,6 @@ #include "RestartFlags.hpp" #include "Wrapper.hpp" #include "xmlWrapper.hpp" -#include "DataContext.hpp" #include @@ -761,7 +760,8 @@ class Group /** * @brief Recursively read values using ProcessInputFile() from the input - * file and put them into the wrapped values for this group. + * file and put them into the wrapped values for this group. + * Also add the includes content to the xmlDocument when `Include` nodes are encountered. * @param[in] xmlDocument the XML document that contains the targetNode * @param[in] targetNode the XML node that to extract input values from. */ diff --git a/src/coreComponents/dataRepository/xmlWrapper.hpp b/src/coreComponents/dataRepository/xmlWrapper.hpp index 117e5482952..78277b3ad3e 100644 --- a/src/coreComponents/dataRepository/xmlWrapper.hpp +++ b/src/coreComponents/dataRepository/xmlWrapper.hpp @@ -51,9 +51,13 @@ namespace xmlWrapper using xmlResult = pugi::xml_parse_result; /// Alias for the type of an xml node. +/// An xmlNode behave as a pointer object: passing it by value to a function which modify it +/// will modify the original xmlNode object. using xmlNode = pugi::xml_node; /// Alias for the type of an xml attribute. +/// An xmlAttribute behave as a pointer object: passing it by value to a function which modify it +/// will modify the original xmlAttribute object. using xmlAttribute = pugi::xml_attribute; /// Alias for the type variant of an xml node. @@ -165,6 +169,7 @@ class xmlDocument : public pugi::xml_document /** * @brief Load document from zero-terminated string. No encoding conversions are applied. + * Free any previously loaded xml tree. * Wrapper of pugi::xml_document::load_buffer() method. * @param contents the string containing the document content * @param loadNodeFileInfo Load the node source file info, allowing getNodePosition() to work. @@ -175,7 +180,8 @@ class xmlDocument : public pugi::xml_document unsigned int options = pugi::parse_default ); /** - * @brief Load document from file. Wrapper of pugi::xml_document::load_buffer() method. + * @brief Load document from file. Free any previously loaded xml tree. + * Wrapper of pugi::xml_document::load_buffer() method. * @param path the path of an xml file to load. * @param loadNodeFileInfo Load the node source file info, allowing getNodePosition() to work. * @param options the parsing options @@ -188,7 +194,8 @@ class xmlDocument : public pugi::xml_document /** * @brief Load document from buffer. Copies/converts the buffer, so it may be deleted or changed - * after the function returns. Wrapper of pugi::xml_document::load_buffer() method. + * after the function returns. Free any previously loaded xml tree. + * Wrapper of pugi::xml_document::load_buffer() method. * @param contents the buffer containing the document content * @param size the size of the buffer in bytes * @param loadNodeFileInfo Load the node source file info, allowing getNodePosition() to work. @@ -247,9 +254,9 @@ class xmlDocument : public pugi::xml_document private: /// Used to retrieve node positions as pugixml buffer is private and processed. map< string, string > m_originalBuffers; - /// see getFilePath() + /// @see getFilePath() string m_rootFilePath; - // see hasNodeFileInfo() + /// @see hasNodeFileInfo() bool m_hasNodeFileInfo; }; diff --git a/src/coreComponents/mainInterface/ProblemManager.hpp b/src/coreComponents/mainInterface/ProblemManager.hpp index 4076ad754d1..6e761609a0f 100644 --- a/src/coreComponents/mainInterface/ProblemManager.hpp +++ b/src/coreComponents/mainInterface/ProblemManager.hpp @@ -119,7 +119,8 @@ class ProblemManager : public dataRepository::Group void parseInputString( string const & xmlString ); /** - * @brief Parses the input xml document + * @brief Parses the input xml document. Also add the includes content to the xmlDocument when + * `Include` nodes are encountered. * @param xmlDocument The parsed xml document handle */ void parseXMLDocument( xmlWrapper::xmlDocument & xmlDocument ); diff --git a/src/coreComponents/unitTests/xmlTests/testXMLFile.cpp b/src/coreComponents/unitTests/xmlTests/testXMLFile.cpp index 395bbd37cf8..4ccce9bdd8a 100644 --- a/src/coreComponents/unitTests/xmlTests/testXMLFile.cpp +++ b/src/coreComponents/unitTests/xmlTests/testXMLFile.cpp @@ -60,7 +60,7 @@ void getElementsRecursive( xmlDocument const & document, xmlNode const & targetN // The Group name will be the name attribute value, or the node tag name if the name attribute // doesn't exist. - string groupName = [&]() { + string const groupName = [&]() { xmlAttribute nameAtt = targetNode.attribute( "name" ); return nameAtt ? string( nameAtt.value() ) : string( targetNode.name() ); }(); @@ -97,7 +97,7 @@ void verifyDataFileContext( DataFileContext const & fileContext, // verifying if all DataFileContext data have been found EXPECT_FALSE( fileContext.getFilePath().empty() ) << errInfo; - EXPECT_FALSE( fileContext.getObjectName().empty() ) << errInfo; + EXPECT_FALSE( fileContext.getTargetName().empty() ) << errInfo; EXPECT_FALSE( fileContext.getTypeName().empty() ) << errInfo; EXPECT_NE( fileContext.getOffset(), xmlDocument::npos ) << errInfo; EXPECT_NE( fileContext.getLine(), xmlDocument::npos ) << errInfo; @@ -150,7 +150,6 @@ void verifyDataFileContext( DataFileContext const & fileContext, void verifyGroupDataFileContextRecursive( xmlDocument const & document, Group const & group, std::set< string > & verifications ) { - // GEOS_LOG( "Verifying "<< group.getName()); if( group.getDataContext().isDataFileContext() ) { verifyDataFileContext( dynamic_cast< DataFileContext const & >( group.getDataContext() ), From 4045ce227b2883e1fc60163612043dde72f298d0 Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Tue, 20 Jun 2023 15:16:32 +0200 Subject: [PATCH 37/85] removing circular dependency: - moved GroupContext & WrapperContext in a dedicated file - removed dependency of Group&Wrapper in DataFileContext - reorganised inclusions --- .../dataRepository/CMakeLists.txt | 2 + .../dataRepository/DataContext.cpp | 76 ++++--------- .../dataRepository/DataContext.hpp | 86 ++------------- src/coreComponents/dataRepository/Group.cpp | 4 +- .../dataRepository/GroupContext.cpp | 84 +++++++++++++++ .../dataRepository/GroupContext.hpp | 101 ++++++++++++++++++ src/coreComponents/dataRepository/Wrapper.hpp | 2 +- .../dataRepository/WrapperBase.cpp | 10 +- .../dataRepository/WrapperBase.hpp | 8 +- 9 files changed, 230 insertions(+), 143 deletions(-) create mode 100644 src/coreComponents/dataRepository/GroupContext.cpp create mode 100644 src/coreComponents/dataRepository/GroupContext.hpp diff --git a/src/coreComponents/dataRepository/CMakeLists.txt b/src/coreComponents/dataRepository/CMakeLists.txt index 65e9f70bbdc..fa77df2e871 100644 --- a/src/coreComponents/dataRepository/CMakeLists.txt +++ b/src/coreComponents/dataRepository/CMakeLists.txt @@ -23,6 +23,7 @@ set( dataRepository_headers wrapperHelpers.hpp xmlWrapper.hpp DataContext.hpp + GroupContext.hpp ) # @@ -37,6 +38,7 @@ set( dataRepository_sources WrapperBase.cpp xmlWrapper.cpp DataContext.cpp + GroupContext.cpp ) set( dependencyList ${parallelDeps} codingUtilities pugixml ) diff --git a/src/coreComponents/dataRepository/DataContext.cpp b/src/coreComponents/dataRepository/DataContext.cpp index 7bc43026fca..b81bae011aa 100644 --- a/src/coreComponents/dataRepository/DataContext.cpp +++ b/src/coreComponents/dataRepository/DataContext.cpp @@ -16,8 +16,7 @@ * @file DataContext.cpp */ -#include "Group.hpp" -#include "WrapperBase.hpp" +#include "DataContext.hpp" namespace geos { @@ -37,75 +36,38 @@ std::ostream & operator<<( std::ostream & os, DataContext const & sc ) } -GroupContext::GroupContext( Group & group, string const & objectName ): - DataContext( objectName, false ), - m_group( group ) -{} -GroupContext::GroupContext( Group & group ): - GroupContext( group, group.getName() ) -{} - -string GroupContext::toString() const -{ - string path; - bool foundNearestLine = false; - for( Group const * parentGroup = &m_group; parentGroup->hasParent(); parentGroup = &parentGroup->getParent() ) - { - if( !foundNearestLine && parentGroup->getDataContext().isDataFileContext() ) - { - DataFileContext const & parentContext = - dynamic_cast< DataFileContext const & >( parentGroup->getDataContext() ); - if( parentContext.getLine() != xmlWrapper::xmlDocument::npos ) - { - path.insert( 0, '/' + parentGroup->getName() + '(' + - splitPath( parentContext.getFilePath() ).second + - ",l." + std::to_string( parentContext.getLine() ) + ')' ); - foundNearestLine=true; - } - else - { - path.insert( 0, '/' + parentGroup->getName() ); - } - } - else - { - path.insert( 0, '/' + parentGroup->getName() ); - } - } - return path; -} - - -WrapperContext::WrapperContext( WrapperBase & wrapper ): - GroupContext( wrapper.getParent(), wrapper.getName() ) -{} - -string WrapperContext::toString() const +/** + * @return the node 'name' attribute if it exists, return the node tag name otherwise. + * @param node the target node. + */ +string getNodeName( xmlWrapper::xmlNode const & node ) { - if( m_group.getDataContext().isDataFileContext() ) + xmlWrapper::xmlAttribute const nameAtt = node.attribute( "name" ); + if( !nameAtt.empty() ) { - return m_group.getDataContext().toString() + ", attribute " + m_objectName; + return string( node.attribute( "name" ).value() ); } else { - return m_group.getDataContext().toString() + "/" + m_objectName; + return string( node.name() ); } } - -DataFileContext::DataFileContext( Group & group, xmlWrapper::xmlNodePos const & nodePos, - string const & nodeTagName ): - DataContext( group.getName(), true ), - m_typeName( nodeTagName ), +DataFileContext::DataFileContext( xmlWrapper::xmlNode const & node, + xmlWrapper::xmlNodePos const & nodePos ): + DataContext( getNodeName( node ), true ), + m_typeName( node.name() ), m_filePath( nodePos.filePath ), m_line( nodePos.line ), m_offsetInLine( nodePos.offsetInLine ), m_offset( nodePos.offset ) {} -DataFileContext::DataFileContext( WrapperBase & wrapper, xmlWrapper::xmlAttributePos const & attPos ): - DataContext( wrapper.getParent().getName() + "/" + wrapper.getName(), true ), - m_typeName( wrapper.getName() ), +DataFileContext::DataFileContext( xmlWrapper::xmlNode const & node, + xmlWrapper::xmlAttribute const & att, + xmlWrapper::xmlAttributePos const & attPos ): + DataContext( getNodeName( node ) + '/' + att.name(), true ), + m_typeName( att.name() ), m_filePath( attPos.filePath ), m_line( attPos.line ), m_offsetInLine( attPos.offsetInLine ), diff --git a/src/coreComponents/dataRepository/DataContext.hpp b/src/coreComponents/dataRepository/DataContext.hpp index 163d4b5f7a0..3b3c1785585 100644 --- a/src/coreComponents/dataRepository/DataContext.hpp +++ b/src/coreComponents/dataRepository/DataContext.hpp @@ -23,15 +23,13 @@ #include "common/Logger.hpp" #include "xmlWrapper.hpp" #include "common/Format.hpp" -#include "Group.hpp" -#include "WrapperBase.hpp" namespace geos { - namespace dataRepository { + /// DataContext is an abstract class storing contextual information on an object: /// - Either its line position in a file (if applicable, implementation in DataFileContext), /// - or its location in the data hierarchy (implementation in GroupContext and WrapperContext). @@ -86,89 +84,26 @@ class DataContext }; -/// Helps to know where a Group is in the hierarchy. -/// See DataContext class for more info. -class GroupContext : public DataContext -{ -public: - - /** - * @brief Construct a new GroupContext object - * @param group The reference to the Group related to this GroupContext. - */ - GroupContext( Group & group ); - - /** - * @brief Destroy the GroupContext object - */ - virtual ~GroupContext() {} - - /** - * @return the reference to the Group related to this GroupContext. - */ - Group & getGroup() const; - - /** - * @return the group path with the file & line of the first parent for which this information exists. - */ - virtual string toString() const; - -protected: - - /** - * @brief Construct a new GroupContext object - * @param group The reference to the Group related to this GroupContext. - * @param objectName Target object name. - */ - GroupContext( Group & group, string const & objectName ); - - /// The reference to the Group related to this GroupContext. - Group & m_group; - -}; - -/// Dedicated implementation of GroupContext for Wrapper. -/// See DataContext class for more info. -class WrapperContext final : public GroupContext -{ -public: - - /** - * @brief Construct a new WrapperContext object - * @param wrapper the target Wrapper object - */ - WrapperContext( WrapperBase & wrapper ); - - /** - * @brief Destroy the WrapperContext object - */ - virtual ~WrapperContext() {} - - /** - * @return the parent group DataContext followed by the wrapper name. - */ - virtual string toString() const; - -}; - -/// Stores information to retrieve where a Group or Wrapper has been declared in the input source file (e.g. XML) +/// Stores information to retrieve where a target object has been declared in the input source +/// file (e.g. XML). class DataFileContext final : public DataContext { public: /** * @brief Construct the file context of a Group from an xml node. - * @param group the target Group object + * @param node the target object xml node * @param nodePos the target object xml node position - * @param nodeTagName the xml node tag name. */ - DataFileContext( Group & group, xmlWrapper::xmlNodePos const & nodePos, string const & nodeTagName ); + DataFileContext( xmlWrapper::xmlNode const & node, xmlWrapper::xmlNodePos const & nodePos ); /** - * @brief Construct the file context of a Group from an xml attribute. - * @param wrapper the target Wrapper object + * @brief Construct the file context of a Group from an xml node. + * @param node the xml node containing the xml attribute + * @param att the target object xml attribute * @param attPos the target object xml attribute position */ - DataFileContext( WrapperBase & wrapper, xmlWrapper::xmlAttributePos const & attPos ); + DataFileContext( xmlWrapper::xmlNode const & node, xmlWrapper::xmlAttribute const & att, + xmlWrapper::xmlAttributePos const & attPos ); /** * @brief Destroy the DataFileContext object @@ -229,7 +164,6 @@ class DataFileContext final : public DataContext } /* namespace dataRepository */ - } /* namespace geos */ diff --git a/src/coreComponents/dataRepository/Group.cpp b/src/coreComponents/dataRepository/Group.cpp index 99692cb1c76..4373abe653b 100644 --- a/src/coreComponents/dataRepository/Group.cpp +++ b/src/coreComponents/dataRepository/Group.cpp @@ -18,7 +18,7 @@ #include "codingUtilities/StringUtilities.hpp" #include "codingUtilities/Utilities.hpp" #include "common/TimingMacros.hpp" -#include "DataContext.hpp" +#include "GroupContext.hpp" #if defined(GEOSX_USE_PYGEOSX) #include "python/PyGroupType.hpp" #endif @@ -195,7 +195,7 @@ void Group::processInputFile( xmlWrapper::xmlDocument const & xmlDocument, if( nodePos.isFound() ) { - m_dataContext = std::make_unique< DataFileContext >( *this, nodePos, targetNode.name() ); + m_dataContext = std::make_unique< DataFileContext >( targetNode, nodePos ); } diff --git a/src/coreComponents/dataRepository/GroupContext.cpp b/src/coreComponents/dataRepository/GroupContext.cpp new file mode 100644 index 00000000000..de71620f111 --- /dev/null +++ b/src/coreComponents/dataRepository/GroupContext.cpp @@ -0,0 +1,84 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2018-2020 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2020 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2018-2020 TotalEnergies + * Copyright (c) 2019- GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +/** + * @file GroupContext.cpp + */ + +#include "GroupContext.hpp" + +namespace geos +{ +namespace dataRepository +{ + + +GroupContext::GroupContext( Group & group, string const & objectName ): + DataContext( objectName, false ), + m_group( group ) +{} +GroupContext::GroupContext( Group & group ): + GroupContext( group, group.getName() ) +{} + +string GroupContext::toString() const +{ + string path; + bool foundNearestLine = false; + for( Group const * parentGroup = &m_group; parentGroup->hasParent(); parentGroup = &parentGroup->getParent() ) + { + if( !foundNearestLine && parentGroup->getDataContext().isDataFileContext() ) + { + DataFileContext const & parentContext = + dynamic_cast< DataFileContext const & >( parentGroup->getDataContext() ); + if( parentContext.getLine() != xmlWrapper::xmlDocument::npos ) + { + path.insert( 0, '/' + parentGroup->getName() + '(' + + splitPath( parentContext.getFilePath() ).second + + ",l." + std::to_string( parentContext.getLine() ) + ')' ); + foundNearestLine=true; + } + else + { + path.insert( 0, '/' + parentGroup->getName() ); + } + } + else + { + path.insert( 0, '/' + parentGroup->getName() ); + } + } + return path; +} + + +WrapperContext::WrapperContext( WrapperBase & wrapper ): + GroupContext( wrapper.getParent(), wrapper.getName() ) +{} + +string WrapperContext::toString() const +{ + if( m_group.getDataContext().isDataFileContext() ) + { + return m_group.getDataContext().toString() + ", attribute " + m_targetName; + } + else + { + return m_group.getDataContext().toString() + "/" + m_targetName; + } +} + + +} /* namespace dataRepository */ +} /* namespace geos */ diff --git a/src/coreComponents/dataRepository/GroupContext.hpp b/src/coreComponents/dataRepository/GroupContext.hpp new file mode 100644 index 00000000000..b65fc851f78 --- /dev/null +++ b/src/coreComponents/dataRepository/GroupContext.hpp @@ -0,0 +1,101 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2018-2020 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2020 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2018-2020 TotalEnergies + * Copyright (c) 2019- GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +/** + * @file GroupContext.hpp + */ + +#ifndef GEOS_DATAREPOSITORY_GROUPCONTEXT_HPP_ +#define GEOS_DATAREPOSITORY_GROUPCONTEXT_HPP_ + +#include "DataContext.hpp" +#include "WrapperBase.hpp" +#include "Group.hpp" + +namespace geos +{ +namespace dataRepository +{ + + +/// Helps to know where a Group is in the hierarchy. +/// See DataContext class for more info. +class GroupContext : public DataContext +{ +public: + + /** + * @brief Construct a new GroupContext object + * @param group The reference to the Group related to this GroupContext. + */ + GroupContext( Group & group ); + + /** + * @brief Destroy the GroupContext object + */ + virtual ~GroupContext() {} + + /** + * @return the reference to the Group related to this GroupContext. + */ + Group & getGroup() const; + + /** + * @return the group path with the file & line of the first parent for which this information exists. + */ + virtual string toString() const; + +protected: + + /** + * @brief Construct a new GroupContext object + * @param group The reference to the Group related to this GroupContext. + * @param objectName Target object name. + */ + GroupContext( Group & group, string const & objectName ); + + /// The reference to the Group related to this GroupContext. + Group & m_group; + +}; + +/// Dedicated implementation of GroupContext for Wrapper. +/// See DataContext class for more info. +class WrapperContext final : public GroupContext +{ +public: + + /** + * @brief Construct a new WrapperContext object + * @param wrapper the target Wrapper object + */ + WrapperContext( WrapperBase & wrapper ); + + /** + * @brief Destroy the WrapperContext object + */ + virtual ~WrapperContext() {} + + /** + * @return the parent group DataContext followed by the wrapper name. + */ + virtual string toString() const; + +}; + + +} /* namespace dataRepository */ +} /* namespace geos */ + +#endif /* GEOS_DATAREPOSITORY_GROUPCONTEXT_HPP_ */ diff --git a/src/coreComponents/dataRepository/Wrapper.hpp b/src/coreComponents/dataRepository/Wrapper.hpp index 320cdaef10a..dcb413c6fe9 100644 --- a/src/coreComponents/dataRepository/Wrapper.hpp +++ b/src/coreComponents/dataRepository/Wrapper.hpp @@ -642,7 +642,7 @@ class Wrapper final : public WrapperBase } if( m_successfulReadFromInput ) - createDataContext( nodePos ); + createDataContext( targetNode, nodePos ); return true; } diff --git a/src/coreComponents/dataRepository/WrapperBase.cpp b/src/coreComponents/dataRepository/WrapperBase.cpp index 86411dfd260..34c4d642bd5 100644 --- a/src/coreComponents/dataRepository/WrapperBase.cpp +++ b/src/coreComponents/dataRepository/WrapperBase.cpp @@ -18,7 +18,7 @@ #include "Group.hpp" #include "RestartFlags.hpp" -#include "DataContext.hpp" +#include "GroupContext.hpp" namespace geos @@ -106,12 +106,14 @@ int WrapperBase::setTotalviewDisplay() const } #endif -void WrapperBase::createDataContext( xmlWrapper::xmlNodePos const & nodePos ) +void WrapperBase::createDataContext( xmlWrapper::xmlNode const & targetNode, + xmlWrapper::xmlNodePos const & nodePos ) { + xmlWrapper::xmlAttribute att = targetNode.attribute( m_name.c_str() ); xmlWrapper::xmlAttributePos attPos = nodePos.getAttributeLine( m_name ); - if( nodePos.isFound() && attPos.isFound() ) + if( nodePos.isFound() && attPos.isFound() && !att.empty() ) { - m_dataContext = std::make_unique< DataFileContext >( *this, attPos ); + m_dataContext = std::make_unique< DataFileContext >( targetNode, att, attPos ); } } diff --git a/src/coreComponents/dataRepository/WrapperBase.hpp b/src/coreComponents/dataRepository/WrapperBase.hpp index 7fd72b6d7db..5f16b3c872b 100644 --- a/src/coreComponents/dataRepository/WrapperBase.hpp +++ b/src/coreComponents/dataRepository/WrapperBase.hpp @@ -24,6 +24,7 @@ #include "xmlWrapper.hpp" #include "RestartFlags.hpp" #include "HistoryDataSpec.hpp" +#include "DataContext.hpp" #if defined(GEOSX_USE_PYGEOSX) #include "LvArray/src/python/python.hpp" @@ -45,7 +46,6 @@ namespace dataRepository { class Group; -class DataContext; /** * @class WrapperBase @@ -636,9 +636,11 @@ class WrapperBase /** * @brief Sets the m_dataContext to a DataFileContext by retrieving the attribute file line. - * @param nodePos the xml node position of the node containing this wrapper source attribute. + * @param node the node containing this wrapper source attribute. + * @param nodePos the xml node position of the node */ - void createDataContext( xmlWrapper::xmlNodePos const & nodePos ); + void createDataContext( xmlWrapper::xmlNode const & targetNode, + xmlWrapper::xmlNodePos const & nodePos ); /** * @brief Helper method to process an exception that has been thrown during xml parsing. From 041d4a73552a1e3448e42b3839d5452b780db42e Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Mon, 26 Jun 2023 16:02:03 +0200 Subject: [PATCH 38/85] Docs fixes & params names --- src/coreComponents/dataRepository/DataContext.cpp | 10 +++++----- src/coreComponents/dataRepository/DataContext.hpp | 8 ++++---- src/coreComponents/dataRepository/WrapperBase.hpp | 2 +- src/coreComponents/dataRepository/xmlWrapper.cpp | 10 +++++----- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/coreComponents/dataRepository/DataContext.cpp b/src/coreComponents/dataRepository/DataContext.cpp index b81bae011aa..3ec248db43c 100644 --- a/src/coreComponents/dataRepository/DataContext.cpp +++ b/src/coreComponents/dataRepository/DataContext.cpp @@ -53,20 +53,20 @@ string getNodeName( xmlWrapper::xmlNode const & node ) } } -DataFileContext::DataFileContext( xmlWrapper::xmlNode const & node, +DataFileContext::DataFileContext( xmlWrapper::xmlNode const & targetNode, xmlWrapper::xmlNodePos const & nodePos ): - DataContext( getNodeName( node ), true ), - m_typeName( node.name() ), + DataContext( getNodeName( targetNode ), true ), + m_typeName( targetNode.name() ), m_filePath( nodePos.filePath ), m_line( nodePos.line ), m_offsetInLine( nodePos.offsetInLine ), m_offset( nodePos.offset ) {} -DataFileContext::DataFileContext( xmlWrapper::xmlNode const & node, +DataFileContext::DataFileContext( xmlWrapper::xmlNode const & targetNode, xmlWrapper::xmlAttribute const & att, xmlWrapper::xmlAttributePos const & attPos ): - DataContext( getNodeName( node ) + '/' + att.name(), true ), + DataContext( getNodeName( targetNode ) + '/' + att.name(), true ), m_typeName( att.name() ), m_filePath( attPos.filePath ), m_line( attPos.line ), diff --git a/src/coreComponents/dataRepository/DataContext.hpp b/src/coreComponents/dataRepository/DataContext.hpp index 3b3c1785585..f53f7c827de 100644 --- a/src/coreComponents/dataRepository/DataContext.hpp +++ b/src/coreComponents/dataRepository/DataContext.hpp @@ -92,17 +92,17 @@ class DataFileContext final : public DataContext /** * @brief Construct the file context of a Group from an xml node. - * @param node the target object xml node + * @param targetNode the target object xml node * @param nodePos the target object xml node position */ - DataFileContext( xmlWrapper::xmlNode const & node, xmlWrapper::xmlNodePos const & nodePos ); + DataFileContext( xmlWrapper::xmlNode const & targetNode, xmlWrapper::xmlNodePos const & nodePos ); /** * @brief Construct the file context of a Group from an xml node. - * @param node the xml node containing the xml attribute + * @param targetNode the xml node containing the xml attribute * @param att the target object xml attribute * @param attPos the target object xml attribute position */ - DataFileContext( xmlWrapper::xmlNode const & node, xmlWrapper::xmlAttribute const & att, + DataFileContext( xmlWrapper::xmlNode const & targetNode, xmlWrapper::xmlAttribute const & att, xmlWrapper::xmlAttributePos const & attPos ); /** diff --git a/src/coreComponents/dataRepository/WrapperBase.hpp b/src/coreComponents/dataRepository/WrapperBase.hpp index 5f16b3c872b..540a8901b10 100644 --- a/src/coreComponents/dataRepository/WrapperBase.hpp +++ b/src/coreComponents/dataRepository/WrapperBase.hpp @@ -636,7 +636,7 @@ class WrapperBase /** * @brief Sets the m_dataContext to a DataFileContext by retrieving the attribute file line. - * @param node the node containing this wrapper source attribute. + * @param targetNode the node containing this wrapper source attribute. * @param nodePos the xml node position of the node */ void createDataContext( xmlWrapper::xmlNode const & targetNode, diff --git a/src/coreComponents/dataRepository/xmlWrapper.cpp b/src/coreComponents/dataRepository/xmlWrapper.cpp index 33d444a39a0..38a788405d5 100644 --- a/src/coreComponents/dataRepository/xmlWrapper.cpp +++ b/src/coreComponents/dataRepository/xmlWrapper.cpp @@ -77,17 +77,17 @@ template void stringToInputVariable( Tensor< real64, 6 > & target, string const * @brief Adds the filePath and character offset info on the node in filePathString * and charOffsetString attributes. This function allow to keep track of the source * filename & offset of each node. - * @param node the target node to add the informations on. + * @param targetNode the target node to add the informations on. * @param filePath the absolute path of the xml file containing the node. */ -void addNodeFileInfo( xmlNode node, string const & filePath ) +void addNodeFileInfo( xmlNode targetNode, string const & filePath ) { // we keep the file path and the character offset on each node so we keep track of these // informations, even if the nodes are manipulated within the xml hierarchy. - node.append_attribute( filePathString ).set_value( filePath.c_str() ); - node.append_attribute( charOffsetString ).set_value( node.offset_debug() ); + targetNode.append_attribute( filePathString ).set_value( filePath.c_str() ); + targetNode.append_attribute( charOffsetString ).set_value( targetNode.offset_debug() ); - for( xmlNode subNode : node.children() ) + for( xmlNode subNode : targetNode.children() ) { addNodeFileInfo( subNode, filePath ); } From d4851bd40bf00b89385c88081a3b45b4c11bfba9 Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Tue, 27 Jun 2023 11:02:42 +0200 Subject: [PATCH 39/85] Message review --- src/coreComponents/mesh/FaceManager.cpp | 10 ++++++---- .../mesh/generators/InternalMeshGenerator.cpp | 2 +- .../mesh/generators/InternalWellGenerator.cpp | 4 ++-- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/coreComponents/mesh/FaceManager.cpp b/src/coreComponents/mesh/FaceManager.cpp index c7d6d053969..3e0cdfc60ef 100644 --- a/src/coreComponents/mesh/FaceManager.cpp +++ b/src/coreComponents/mesh/FaceManager.cpp @@ -28,6 +28,7 @@ #include "mesh/NodeManager.hpp" #include "mesh/utilities/MeshMapUtilities.hpp" #include "utilities/ComputationalGeometry.hpp" +#include "CellElementRegion.hpp" namespace geos { @@ -201,9 +202,10 @@ void FaceManager::sortAllFaceNodes( NodeManager const & nodeManager, // The face should be connected to at least one element. if( facesToElements( faceIndex, 0 ) < 0 && facesToElements( faceIndex, 1 ) < 0 ) { - GEOS_ERROR( getDataContext() << ": Face " << faceIndex << " is not connected to an element." << - "The cellBlocks of the CellElementRegions might not have referenced this face." << - " Be sure to include every primitive of every part of the mesh in the cellBlocks." ); + GEOS_ERROR( getDataContext() << ": Face " << faceIndex << " is not connected to any cell." << + "You might have forgotten one cell type in the " << + elemManager.getWrapperDataContext( CellElementRegion::viewKeyStruct::sourceCellBlockNamesString() ) << + ", or your mesh might be invalid" ); } // Take the first defined face-to-(elt/region/sub region) to sorting direction. @@ -234,7 +236,7 @@ void FaceManager::sortFaceNodes( arrayView2d< real64 const, nodes::REFERENCE_POS Span< localIndex > const faceNodes ) { localIndex const numFaceNodes = LvArray::integerConversion< localIndex >( faceNodes.size() ); - GEOS_THROW_IF_GT_MSG( numFaceNodes, MAX_FACE_NODES, "Node per face limit exceeded", std::runtime_error ); + GEOS_THROW_IF_GT_MSG( numFaceNodes, MAX_FACE_NODES, "The number of maximum nodes allocated per cell face has been reached.", std::runtime_error ); localIndex const firstNodeIndex = faceNodes[0]; diff --git a/src/coreComponents/mesh/generators/InternalMeshGenerator.cpp b/src/coreComponents/mesh/generators/InternalMeshGenerator.cpp index 6e5e7024d82..d1f88d35b56 100644 --- a/src/coreComponents/mesh/generators/InternalMeshGenerator.cpp +++ b/src/coreComponents/mesh/generators/InternalMeshGenerator.cpp @@ -177,7 +177,7 @@ void InternalMeshGenerator::postProcessInput() else { GEOS_ERROR( getDataContext() << ": InternalMeshGenerator: The number of element types is" - " inconsistent with the number of total block." ); + " inconsistent with the number of total cell blocks." ); } } diff --git a/src/coreComponents/mesh/generators/InternalWellGenerator.cpp b/src/coreComponents/mesh/generators/InternalWellGenerator.cpp index 241cdc2c634..2805ad3b022 100644 --- a/src/coreComponents/mesh/generators/InternalWellGenerator.cpp +++ b/src/coreComponents/mesh/generators/InternalWellGenerator.cpp @@ -122,7 +122,7 @@ void InternalWellGenerator::postProcessInput() GEOS_THROW_IF( m_radius <= 0, "InternalWell " << getWrapperDataContext( viewKeyStruct::radiusString() ) << - ": Value must be greater that 0.", + ": Radius value must be greater that 0.", InputError ); GEOS_THROW_IF( m_wellRegionName.empty(), @@ -247,7 +247,7 @@ void InternalWellGenerator::constructPolylineNodeToSegmentMap() InputError ); GEOS_THROW_IF( m_polyNodeCoords[ipolyNode_a][2] < m_polyNodeCoords[ipolyNode_b][2], - getDataContext() << ": Error in the topology of the well. Wn the polyline"<< + getDataContext() << ": Error in the topology of the well. In the polyline"<< ", each segment must be going down. \n" << "This is not the case between polyline nodes " << m_polyNodeCoords[ipolyNode_a] << " and " << m_polyNodeCoords[ipolyNode_b], InputError ); From 1a5c02a536872b46ca94119a0c9d1d05662c48a9 Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Thu, 29 Jun 2023 17:31:31 +0200 Subject: [PATCH 40/85] Refactorization: removing "isDataFileContext()" (work in progress) --- .../dataRepository/DataContext.cpp | 20 +++++-- .../dataRepository/DataContext.hpp | 22 ++++---- src/coreComponents/dataRepository/Group.hpp | 31 +++++++++++ .../dataRepository/GroupContext.cpp | 31 +++-------- .../dataRepository/GroupContext.hpp | 4 ++ .../unitTests/xmlTests/testXMLFile.cpp | 54 +++++-------------- 6 files changed, 80 insertions(+), 82 deletions(-) diff --git a/src/coreComponents/dataRepository/DataContext.cpp b/src/coreComponents/dataRepository/DataContext.cpp index 3ec248db43c..b687de8830b 100644 --- a/src/coreComponents/dataRepository/DataContext.cpp +++ b/src/coreComponents/dataRepository/DataContext.cpp @@ -24,9 +24,8 @@ namespace dataRepository { -DataContext::DataContext( string const & targetName, bool const isDataFileContext ): - m_targetName( targetName ), - m_isDataFileContext( isDataFileContext ) +DataContext::DataContext( string const & targetName ): + m_targetName( targetName ) {} std::ostream & operator<<( std::ostream & os, DataContext const & sc ) @@ -55,7 +54,7 @@ string getNodeName( xmlWrapper::xmlNode const & node ) DataFileContext::DataFileContext( xmlWrapper::xmlNode const & targetNode, xmlWrapper::xmlNodePos const & nodePos ): - DataContext( getNodeName( targetNode ), true ), + DataContext( getNodeName( targetNode ) ), m_typeName( targetNode.name() ), m_filePath( nodePos.filePath ), m_line( nodePos.line ), @@ -66,7 +65,7 @@ DataFileContext::DataFileContext( xmlWrapper::xmlNode const & targetNode, DataFileContext::DataFileContext( xmlWrapper::xmlNode const & targetNode, xmlWrapper::xmlAttribute const & att, xmlWrapper::xmlAttributePos const & attPos ): - DataContext( getNodeName( targetNode ) + '/' + att.name(), true ), + DataContext( getNodeName( targetNode ) + '/' + att.name() ), m_typeName( att.name() ), m_filePath( attPos.filePath ), m_line( attPos.line ), @@ -93,6 +92,17 @@ string DataFileContext::toString() const return oss.str(); } +string DataFileContext::getTargetNameInPath( bool & foundNearestLine ) const +{ + std::ostringstream oss; + oss << m_targetName; + foundNearestLine = ( m_line != xmlWrapper::xmlDocument::npos ); + if( foundNearestLine ) + { + oss << "(" << splitPath( m_filePath ).second << ",l." << m_line << ")"; + } + return oss.str(); +} } /* namespace dataRepository */ } /* namespace geos */ diff --git a/src/coreComponents/dataRepository/DataContext.hpp b/src/coreComponents/dataRepository/DataContext.hpp index f53f7c827de..e75119248dc 100644 --- a/src/coreComponents/dataRepository/DataContext.hpp +++ b/src/coreComponents/dataRepository/DataContext.hpp @@ -41,9 +41,8 @@ class DataContext /** * @brief Construct a new DataContext object. * @param targetName the target object name - * @param isDataFileContext true if this Context is a DataFileContext */ - DataContext( string const & targetName, bool isDataFileContext ); + DataContext( string const & targetName ); /** * @brief Destroy the DataContext object @@ -62,12 +61,11 @@ class DataContext string getTargetName() const { return m_targetName; } - /** - * @brief Flag on availability of file information. Used to provide more user-friendly information. - * @return true if the context is from a file. - */ - bool isDataFileContext() const - { return m_isDataFileContext; } + virtual string getTargetNameInPath( bool & fileLineFound ) const + { fileLineFound = false; return m_targetName; } + + virtual string getWrapperSeparator() const + { return "/"; } /** * @brief Insert contextual information in the provided stream. @@ -79,9 +77,6 @@ class DataContext /// @see getObjectName() string const m_targetName; - /// @see isDataFileContext() - bool const m_isDataFileContext; - }; /// Stores information to retrieve where a target object has been declared in the input source @@ -115,6 +110,11 @@ class DataFileContext final : public DataContext */ virtual string toString() const; + virtual string getTargetNameInPath( bool & foundNearestLine ) const override; + + virtual string getWrapperSeparator() const override + { return ", attribute "; } + /** * @return the type name in the source file (XML node tag name / attribute name). */ diff --git a/src/coreComponents/dataRepository/Group.hpp b/src/coreComponents/dataRepository/Group.hpp index 745a8d1c31f..7c662a28413 100644 --- a/src/coreComponents/dataRepository/Group.hpp +++ b/src/coreComponents/dataRepository/Group.hpp @@ -1321,6 +1321,10 @@ class Group DataContext const & getWrapperDataContext( KEY key ) const { return getWrapperBase< KEY >( key ).getDataContext(); } + template< typename TARGET_DC_TYPE, typename ... INPUT_PARAMS, typename ... FUNC_PARAMS > + void forAllDataContextOfType( void ( &func )( TARGET_DC_TYPE const &, FUNC_PARAMS ... ), + INPUT_PARAMS && ... params ) const; + /** * @brief Access the group's parent. * @return reference to parent Group @@ -1674,6 +1678,33 @@ Wrapper< T > & Group::registerWrapper( string const & name, return rval; } +template< typename TARGET_DC_TYPE, typename ... INPUT_PARAMS, typename ... FUNC_PARAMS > +void Group::forAllDataContextOfType( void ( &func )( TARGET_DC_TYPE const &, FUNC_PARAMS ... ), + INPUT_PARAMS && ... params ) const +{ + TARGET_DC_TYPE const & groupCtx = + dynamic_cast< TARGET_DC_TYPE const & >( *m_dataContext ); + if( groupCtx ) + { + func( groupCtx, std::forward< INPUT_PARAMS >( params ) ... ); + } + + for( auto const & wrapperIterator : m_wrappers ) + { + TARGET_DC_TYPE const & wrapperCtx = + dynamic_cast< TARGET_DC_TYPE const & >( wrapperIterator.second->getDataContext() ); + if( wrapperCtx ) + { + func( wrapperCtx, std::forward< INPUT_PARAMS >( params ) ... ); + } + } + + for( auto subGroup : m_subGroups ) + { + forAllTypedDataContext( *subGroup.second, func, std::forward< INPUT_PARAMS >( params ) ... ); + } +} + } /* end namespace dataRepository */ } /* end namespace geos */ diff --git a/src/coreComponents/dataRepository/GroupContext.cpp b/src/coreComponents/dataRepository/GroupContext.cpp index de71620f111..8aea288a69e 100644 --- a/src/coreComponents/dataRepository/GroupContext.cpp +++ b/src/coreComponents/dataRepository/GroupContext.cpp @@ -25,7 +25,7 @@ namespace dataRepository GroupContext::GroupContext( Group & group, string const & objectName ): - DataContext( objectName, false ), + DataContext( objectName ), m_group( group ) {} GroupContext::GroupContext( Group & group ): @@ -38,21 +38,9 @@ string GroupContext::toString() const bool foundNearestLine = false; for( Group const * parentGroup = &m_group; parentGroup->hasParent(); parentGroup = &parentGroup->getParent() ) { - if( !foundNearestLine && parentGroup->getDataContext().isDataFileContext() ) + if( !foundNearestLine ) { - DataFileContext const & parentContext = - dynamic_cast< DataFileContext const & >( parentGroup->getDataContext() ); - if( parentContext.getLine() != xmlWrapper::xmlDocument::npos ) - { - path.insert( 0, '/' + parentGroup->getName() + '(' + - splitPath( parentContext.getFilePath() ).second + - ",l." + std::to_string( parentContext.getLine() ) + ')' ); - foundNearestLine=true; - } - else - { - path.insert( 0, '/' + parentGroup->getName() ); - } + path.insert( 0, '/' + parentGroup->getDataContext().getTargetNameInPath( foundNearestLine ) ); } else { @@ -64,19 +52,14 @@ string GroupContext::toString() const WrapperContext::WrapperContext( WrapperBase & wrapper ): - GroupContext( wrapper.getParent(), wrapper.getName() ) + GroupContext( wrapper.getParent(), wrapper.getParent().getName() + '/' + wrapper.getName() ), + m_typeName( wrapper.getName() ) {} string WrapperContext::toString() const { - if( m_group.getDataContext().isDataFileContext() ) - { - return m_group.getDataContext().toString() + ", attribute " + m_targetName; - } - else - { - return m_group.getDataContext().toString() + "/" + m_targetName; - } + DataContext const & parentDC = m_group.getDataContext(); + return parentDC.toString() + parentDC.getWrapperSeparator() + m_typeName; } diff --git a/src/coreComponents/dataRepository/GroupContext.hpp b/src/coreComponents/dataRepository/GroupContext.hpp index b65fc851f78..a5fd5199e23 100644 --- a/src/coreComponents/dataRepository/GroupContext.hpp +++ b/src/coreComponents/dataRepository/GroupContext.hpp @@ -92,6 +92,10 @@ class WrapperContext final : public GroupContext */ virtual string toString() const; +protected: + + string const m_typeName; + }; diff --git a/src/coreComponents/unitTests/xmlTests/testXMLFile.cpp b/src/coreComponents/unitTests/xmlTests/testXMLFile.cpp index 4ccce9bdd8a..cc752bdec39 100644 --- a/src/coreComponents/unitTests/xmlTests/testXMLFile.cpp +++ b/src/coreComponents/unitTests/xmlTests/testXMLFile.cpp @@ -90,8 +90,11 @@ void getElementsRecursive( xmlDocument const & document, xmlNode const & targetN * @param fileContext the DataFileContext to verify. */ void verifyDataFileContext( DataFileContext const & fileContext, - xmlDocument const & document ) + xmlDocument const & document, + std::set< string > & verifications ) { + verifications.emplace( fileContext.getTargetName() ); + string const & strToVerify = fileContext.getTypeName(); string const & errInfo = "Verifying " + strToVerify + " in " + fileContext.toString(); @@ -138,41 +141,6 @@ void verifyDataFileContext( DataFileContext const & fileContext, // does the fileContext line has been reached? EXPECT_TRUE( lineFound ); } -/** - * @brief Verifies if the specified group, its children, and the associated wrappers have a - * DataFileContext object that correctly locate from where the objects where declared in the - * source file. - * @param document root xml document (which potentially contains includes). - * @param group The group to (recursively) verify. - * @param verifCount a set that will be filled with the names, with the form - * "GroupName" or "GroupName/WrapperName". - */ -void verifyGroupDataFileContextRecursive( xmlDocument const & document, Group const & group, - std::set< string > & verifications ) -{ - if( group.getDataContext().isDataFileContext() ) - { - verifyDataFileContext( dynamic_cast< DataFileContext const & >( group.getDataContext() ), - document ); - verifications.emplace( group.getName() ); - } - - for( auto const & wrapperIterator : group.wrappers() ) - { - WrapperBase const * wrapper = wrapperIterator.second; - if( wrapper->getDataContext().isDataFileContext() ) - { - verifyDataFileContext( dynamic_cast< DataFileContext const & >( wrapper->getDataContext() ), - document ); - verifications.emplace( group.getName() + '/' + wrapper->getName() ); - } - } - - for( auto subGroup : group.getSubGroups() ) - { - verifyGroupDataFileContextRecursive( document, *subGroup.second, verifications ); - } -} /** * @brief Returns the element that exists in setB but not in setA. @@ -192,28 +160,30 @@ std::set< string > getDifference( std::set< string > const & setA, // - if the resulting Group & Wrapper hierarchy matches with the input xml documents and includes hierarchy. TEST( testXML, testXMLFileLines ) { - xmlDocument xmlDocument; + xmlDocument xmlDoc; ProblemManager & problemManager = getGlobalState().getProblemManager(); { problemManager.parseCommandLineInput(); Group & commandLine = problemManager.getGroup( problemManager.groupKeys.commandLine ); string const & inputFileName = commandLine.getReference< string >( problemManager.viewKeys.inputFileName ); - xmlDocument.load_file( inputFileName.c_str(), true ); - problemManager.parseXMLDocument( xmlDocument ); + xmlDoc.load_file( inputFileName.c_str(), true ); + problemManager.parseXMLDocument( xmlDoc ); } GEOS_LOG( "Loaded files : " ); - for( auto const & buffer: xmlDocument.getOriginalBuffers() ) + for( auto const & buffer: xmlDoc.getOriginalBuffers() ) { GEOS_LOG( " " << buffer.first << " (" << buffer.second.size() << " chars)" ); } std::set< string > expectedElements; - getElementsRecursive( xmlDocument, xmlDocument.root().child( "Problem" ), expectedElements ); + getElementsRecursive( xmlDoc, xmlDoc.root().child( "Problem" ), expectedElements ); std::set< string > verifiedElements; - verifyGroupDataFileContextRecursive( xmlDocument, problemManager, verifiedElements ); + xmlDocument const & xmlDocConst=xmlDoc; + problemManager.forAllDataContextOfType< DataFileContext >( verifyDataFileContext, + xmlDocConst, verifiedElements ); std::set< string > const notFound = getDifference( expectedElements, verifiedElements ); EXPECT_TRUE( notFound.empty() ) << "Info : There should not exist xml element that were not in " From 1508b00b4e1cc9bb5b4f4ac1249fc4f3ba9d66e1 Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Mon, 3 Jul 2023 15:06:42 +0200 Subject: [PATCH 41/85] memleak fix --- src/coreComponents/dataRepository/xmlWrapper.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/coreComponents/dataRepository/xmlWrapper.cpp b/src/coreComponents/dataRepository/xmlWrapper.cpp index 38a788405d5..ce85c5da30c 100644 --- a/src/coreComponents/dataRepository/xmlWrapper.cpp +++ b/src/coreComponents/dataRepository/xmlWrapper.cpp @@ -219,7 +219,7 @@ xmlResult xmlDocument::load_string( const pugi::char_t * contents, bool loadNode // keeping a copy of original buffer to allow line retrieval if( loadNodeFileInfo ) { - new (&m_originalBuffers) map< string, string >(); + m_originalBuffers.clear(); m_originalBuffers[m_rootFilePath] = string( contents ); addNodeFileInfo( first_child(), m_rootFilePath ); @@ -240,7 +240,7 @@ xmlResult xmlDocument::load_file( const char * path, bool loadNodeFileInfo, std::stringstream buffer; buffer << t.rdbuf(); - new (&m_originalBuffers) map< string, string >(); + m_originalBuffers.clear(); m_originalBuffers[m_rootFilePath] = string( buffer.str() ); addNodeFileInfo( first_child(), getAbsolutePath( m_rootFilePath ) ); @@ -256,7 +256,7 @@ xmlResult xmlDocument::load_buffer( const void * contents, size_t size, bool loa //keeping a copy of original buffer if( loadNodeFileInfo ) { - new (&m_originalBuffers) map< string, string >(); + m_originalBuffers.clear(); m_originalBuffers[m_rootFilePath] = string( ( char const * )contents, size ); addNodeFileInfo( first_child(), m_rootFilePath ); From 8db1cc308f90b72eb085c74f5b23966816eefaf3 Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Mon, 3 Jul 2023 16:13:56 +0200 Subject: [PATCH 42/85] Refactorization: removed getWrapperSeparator() --- .../dataRepository/DataContext.hpp | 16 ++++++++++------ .../dataRepository/GroupContext.cpp | 3 +-- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/coreComponents/dataRepository/DataContext.hpp b/src/coreComponents/dataRepository/DataContext.hpp index e75119248dc..f3927aabd02 100644 --- a/src/coreComponents/dataRepository/DataContext.hpp +++ b/src/coreComponents/dataRepository/DataContext.hpp @@ -54,6 +54,11 @@ class DataContext * object comes from. */ virtual string toString() const = 0; + /** + * @return The result of toString() properly suffixed by the name of a contained object. + */ + virtual string toString( string const & innerObjectName ) const + { return toString() + '/' + innerObjectName; } /** * @return Get the target object name @@ -64,9 +69,6 @@ class DataContext virtual string getTargetNameInPath( bool & fileLineFound ) const { fileLineFound = false; return m_targetName; } - virtual string getWrapperSeparator() const - { return "/"; } - /** * @brief Insert contextual information in the provided stream. */ @@ -109,12 +111,14 @@ class DataFileContext final : public DataContext * @return the target object name followed by the the file and line declaring it. */ virtual string toString() const; + /** + * @copydoc DataContext::toString() + */ + string toString( string const & innerObjectName ) const override + { return toString() + ", attribute " + innerObjectName; } virtual string getTargetNameInPath( bool & foundNearestLine ) const override; - virtual string getWrapperSeparator() const override - { return ", attribute "; } - /** * @return the type name in the source file (XML node tag name / attribute name). */ diff --git a/src/coreComponents/dataRepository/GroupContext.cpp b/src/coreComponents/dataRepository/GroupContext.cpp index 8aea288a69e..77a78cbe657 100644 --- a/src/coreComponents/dataRepository/GroupContext.cpp +++ b/src/coreComponents/dataRepository/GroupContext.cpp @@ -58,8 +58,7 @@ WrapperContext::WrapperContext( WrapperBase & wrapper ): string WrapperContext::toString() const { - DataContext const & parentDC = m_group.getDataContext(); - return parentDC.toString() + parentDC.getWrapperSeparator() + m_typeName; + return m_group.getDataContext().toString( m_typeName ); } From e416cdf0cdcef8dd185780537f8088baef10d72b Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Mon, 3 Jul 2023 16:30:55 +0200 Subject: [PATCH 43/85] getTargetNameInPath() docs --- src/coreComponents/dataRepository/DataContext.hpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/coreComponents/dataRepository/DataContext.hpp b/src/coreComponents/dataRepository/DataContext.hpp index f3927aabd02..a276eb3d60c 100644 --- a/src/coreComponents/dataRepository/DataContext.hpp +++ b/src/coreComponents/dataRepository/DataContext.hpp @@ -66,6 +66,11 @@ class DataContext string getTargetName() const { return m_targetName; } + /** + * @return the target name formatted in such a way that it can be inserted in a hierarchy path. + * @param foundNearestLine reference to a boolean that is set to true if the file line of the + * data context could be determined, otherwise it is set to false. + */ virtual string getTargetNameInPath( bool & fileLineFound ) const { fileLineFound = false; return m_targetName; } @@ -117,6 +122,9 @@ class DataFileContext final : public DataContext string toString( string const & innerObjectName ) const override { return toString() + ", attribute " + innerObjectName; } + /** + * @copydoc DataContext::getTargetNameInPath( bool & foundNearestLine ) const + */ virtual string getTargetNameInPath( bool & foundNearestLine ) const override; /** From 5750d125f250c38c6f63f9856aab35d45350e19f Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Mon, 3 Jul 2023 16:55:52 +0200 Subject: [PATCH 44/85] removed virtual destructors --- src/coreComponents/dataRepository/DataContext.hpp | 5 ----- src/coreComponents/dataRepository/GroupContext.hpp | 10 ---------- 2 files changed, 15 deletions(-) diff --git a/src/coreComponents/dataRepository/DataContext.hpp b/src/coreComponents/dataRepository/DataContext.hpp index a276eb3d60c..320a5e23e2e 100644 --- a/src/coreComponents/dataRepository/DataContext.hpp +++ b/src/coreComponents/dataRepository/DataContext.hpp @@ -107,11 +107,6 @@ class DataFileContext final : public DataContext DataFileContext( xmlWrapper::xmlNode const & targetNode, xmlWrapper::xmlAttribute const & att, xmlWrapper::xmlAttributePos const & attPos ); - /** - * @brief Destroy the DataFileContext object - */ - virtual ~DataFileContext() {} - /** * @return the target object name followed by the the file and line declaring it. */ diff --git a/src/coreComponents/dataRepository/GroupContext.hpp b/src/coreComponents/dataRepository/GroupContext.hpp index a5fd5199e23..b63e2588081 100644 --- a/src/coreComponents/dataRepository/GroupContext.hpp +++ b/src/coreComponents/dataRepository/GroupContext.hpp @@ -41,11 +41,6 @@ class GroupContext : public DataContext */ GroupContext( Group & group ); - /** - * @brief Destroy the GroupContext object - */ - virtual ~GroupContext() {} - /** * @return the reference to the Group related to this GroupContext. */ @@ -82,11 +77,6 @@ class WrapperContext final : public GroupContext */ WrapperContext( WrapperBase & wrapper ); - /** - * @brief Destroy the WrapperContext object - */ - virtual ~WrapperContext() {} - /** * @return the parent group DataContext followed by the wrapper name. */ From 8f357602cdbc74b3cf0f7850e9bc9e6bfac4a0a6 Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Tue, 4 Jul 2023 14:54:18 +0200 Subject: [PATCH 45/85] Refactorings --- src/coreComponents/dataRepository/Group.hpp | 33 ++++++++++--------- .../dataRepository/xmlWrapper.cpp | 2 +- .../dataRepository/xmlWrapper.hpp | 2 +- .../unitTests/xmlTests/testXMLFile.cpp | 7 ++-- 4 files changed, 23 insertions(+), 21 deletions(-) diff --git a/src/coreComponents/dataRepository/Group.hpp b/src/coreComponents/dataRepository/Group.hpp index 7c662a28413..1a6d8146bda 100644 --- a/src/coreComponents/dataRepository/Group.hpp +++ b/src/coreComponents/dataRepository/Group.hpp @@ -1321,9 +1321,16 @@ class Group DataContext const & getWrapperDataContext( KEY key ) const { return getWrapperBase< KEY >( key ).getDataContext(); } - template< typename TARGET_DC_TYPE, typename ... INPUT_PARAMS, typename ... FUNC_PARAMS > - void forAllDataContextOfType( void ( &func )( TARGET_DC_TYPE const &, FUNC_PARAMS ... ), - INPUT_PARAMS && ... params ) const; + /** + * @brief Calls a lambda for the DataContext of this Group and the ones of its children Group and + * Wrapper if they can be casted to the requested class type. + * @tparam TARGET_DC the requested DataContext class or parent class type. + * @tparam LAMBDA the type of functor to call. The functor takes in parameter a DataContext of + * TARGET_DC type. + * @param func + */ + template< typename TARGET_DC = DataContext, typename FUNC > + void forAllDataContext( FUNC func ) const; /** * @brief Access the group's parent. @@ -1678,30 +1685,24 @@ Wrapper< T > & Group::registerWrapper( string const & name, return rval; } -template< typename TARGET_DC_TYPE, typename ... INPUT_PARAMS, typename ... FUNC_PARAMS > -void Group::forAllDataContextOfType( void ( &func )( TARGET_DC_TYPE const &, FUNC_PARAMS ... ), - INPUT_PARAMS && ... params ) const +template< typename TARGET_DC = DataContext, typename FUNC > +void Group::forAllDataContext( FUNC func ) const { - TARGET_DC_TYPE const & groupCtx = - dynamic_cast< TARGET_DC_TYPE const & >( *m_dataContext ); - if( groupCtx ) + if( auto * groupCtx = dynamic_cast< TARGET_DC const * >( &getDataContext() ) ) { - func( groupCtx, std::forward< INPUT_PARAMS >( params ) ... ); + func( *groupCtx ); } - for( auto const & wrapperIterator : m_wrappers ) { - TARGET_DC_TYPE const & wrapperCtx = - dynamic_cast< TARGET_DC_TYPE const & >( wrapperIterator.second->getDataContext() ); + auto * wrapperCtx = dynamic_cast< TARGET_DC const * >( &wrapperIterator.second->getDataContext() ); if( wrapperCtx ) { - func( wrapperCtx, std::forward< INPUT_PARAMS >( params ) ... ); + func( *wrapperCtx ); } } - for( auto subGroup : m_subGroups ) { - forAllTypedDataContext( *subGroup.second, func, std::forward< INPUT_PARAMS >( params ) ... ); + subGroup.second->forAllDataContext< TARGET_DC >( func ); } } diff --git a/src/coreComponents/dataRepository/xmlWrapper.cpp b/src/coreComponents/dataRepository/xmlWrapper.cpp index ce85c5da30c..44ff98efabd 100644 --- a/src/coreComponents/dataRepository/xmlWrapper.cpp +++ b/src/coreComponents/dataRepository/xmlWrapper.cpp @@ -203,7 +203,7 @@ bool isFileMetadataAttribute( string const & name ) return fileMetadataAttributes.find( name ) != fileMetadataAttributes.end(); } -const size_t xmlDocument::npos = string::npos; +constexpr size_t xmlDocument::npos; size_t documentId=0; xmlDocument::xmlDocument(): diff --git a/src/coreComponents/dataRepository/xmlWrapper.hpp b/src/coreComponents/dataRepository/xmlWrapper.hpp index 78277b3ad3e..c53a7c25843 100644 --- a/src/coreComponents/dataRepository/xmlWrapper.hpp +++ b/src/coreComponents/dataRepository/xmlWrapper.hpp @@ -132,7 +132,7 @@ class xmlDocument : public pugi::xml_document { public: /// Error value for when an offset / line position is undefined. - static const size_t npos; + static constexpr size_t npos = string::npos; /** * @brief Construct an empty xmlDocument that waits to load something. diff --git a/src/coreComponents/unitTests/xmlTests/testXMLFile.cpp b/src/coreComponents/unitTests/xmlTests/testXMLFile.cpp index cc752bdec39..d056ee5d675 100644 --- a/src/coreComponents/unitTests/xmlTests/testXMLFile.cpp +++ b/src/coreComponents/unitTests/xmlTests/testXMLFile.cpp @@ -181,9 +181,10 @@ TEST( testXML, testXMLFileLines ) getElementsRecursive( xmlDoc, xmlDoc.root().child( "Problem" ), expectedElements ); std::set< string > verifiedElements; - xmlDocument const & xmlDocConst=xmlDoc; - problemManager.forAllDataContextOfType< DataFileContext >( verifyDataFileContext, - xmlDocConst, verifiedElements ); + problemManager.forAllDataContext< DataFileContext >( [&]( DataFileContext const & ctx ) + { + verifyDataFileContext( ctx, xmlDoc, verifiedElements ); + } ); std::set< string > const notFound = getDifference( expectedElements, verifiedElements ); EXPECT_TRUE( notFound.empty() ) << "Info : There should not exist xml element that were not in " From b96630b03f3e22dc7ed4c447fb93582a10d27f05 Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Tue, 4 Jul 2023 14:55:09 +0200 Subject: [PATCH 46/85] Added a test for DataContext string formatting --- .../dataRepositoryTests/testGroupPath.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/coreComponents/unitTests/dataRepositoryTests/testGroupPath.cpp b/src/coreComponents/unitTests/dataRepositoryTests/testGroupPath.cpp index 45f08453d63..1451308b7fd 100644 --- a/src/coreComponents/unitTests/dataRepositoryTests/testGroupPath.cpp +++ b/src/coreComponents/unitTests/dataRepositoryTests/testGroupPath.cpp @@ -117,6 +117,22 @@ TEST( testGroupPath, testGlobalPaths ) } // checks if the exception has been thrown as expected ASSERT_TRUE( trowHappened ); + + auto const testDataContextString = [&]( string const & groupPath, string const & ctxString ) + { + Group const * const groupToTest = &problem.getGroupByPath( groupPath ); + ASSERT_NE( groupToTest, nullptr ); + ASSERT_STREQ( groupToTest->getDataContext().toString().c_str(), + ctxString.c_str() ); + }; + // check if the DataContext string of a Group declared in the XML is formatted as expected + testDataContextString( + "/Mesh/mesh1", + "mesh1 (CodeIncludedXML0, l.11)" ); + // check if the DataContext string of an implicitly created Group is formatted as expected + testDataContextString( + "/domain/MeshBodies/mesh1/meshLevels/Level0/ElementRegions/elementRegionsGroup/Region2/elementSubRegions", + "/domain/MeshBodies/mesh1/meshLevels/Level0/ElementRegions/elementRegionsGroup/Region2(CodeIncludedXML0,l.37)/elementSubRegions" ); } int main( int argc, char * * argv ) From f5e823f1d3c61eeda4506bb91a5109b69cb4bec8 Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Wed, 5 Jul 2023 16:25:48 +0200 Subject: [PATCH 47/85] improved getAttributeLine() robustness --- .../dataRepository/xmlWrapper.cpp | 80 +++++++++++++++---- 1 file changed, 63 insertions(+), 17 deletions(-) diff --git a/src/coreComponents/dataRepository/xmlWrapper.cpp b/src/coreComponents/dataRepository/xmlWrapper.cpp index 44ff98efabd..5e443bb0683 100644 --- a/src/coreComponents/dataRepository/xmlWrapper.cpp +++ b/src/coreComponents/dataRepository/xmlWrapper.cpp @@ -335,32 +335,78 @@ xmlNodePos::xmlNodePos( xmlDocument const & document_, string const & filePath_, bool xmlNodePos::isFound() const { return line != xmlDocument::npos; } +size_t findTagEnd( string const & xmlBuffer, size_t const offset ) +{ + bool outOfQuotes = true; + auto bufferEnd = xmlBuffer.cend(); + for( auto it = xmlBuffer.cbegin() + offset; it != bufferEnd; ++it ) + { + if( *it == '"' && *(it - 1) != '\\' ) + { + outOfQuotes = !outOfQuotes; + } + else if( outOfQuotes && *it == '>' ) + { + return it - xmlBuffer.cbegin(); + } + } + return string::npos; +} +size_t findAttribute( string const & attName, string const & xmlBuffer, size_t const tagBegin, size_t const tagEnd ) +{ + if( !attName.empty()) + { + size_t searchStart = tagBegin; + try + { + std::smatch m; + // we search for a string which is the attribute name followed by an '=', eventually separated by spaces + if( std::regex_search( xmlBuffer.cbegin() + searchStart, xmlBuffer.cbegin() + tagEnd, + m, std::regex( attName + "\\s*=\\s*\"" ))) + { + size_t candidatePos = m.position() + searchStart; + string previousString = xmlBuffer.substr( tagBegin, candidatePos - tagBegin ); + // we must be out of value surrounding quotes: the number of previous quotes '"' should be even (ignoring the inner quotes preceded + // by '\\') + size_t surroundingQuotesCount = 0; + size_t quotePos = 0; + while((quotePos = previousString.find( '"', quotePos + 1 )) != string::npos ) + { + if( previousString[quotePos - 1] != '\\' ) + ++surroundingQuotesCount; + } + + if(((surroundingQuotesCount % 1) == 0)) + { + return candidatePos; + } + searchStart = candidatePos + attName.size(); + } + } + catch( std::regex_error const & ) + {} + } + return xmlDocument::npos; +} + xmlAttributePos xmlNodePos::getAttributeLine( string const & attName ) const { string const * buffer = document.getOriginalBuffer( filePath ); - size_t tagEnd = xmlDocument::npos; - size_t attOffset = xmlDocument::npos; - size_t attLine = xmlDocument::npos; - size_t attOffsetInLine = xmlDocument::npos; + size_t attOffset = offset; + size_t attLine = line; + size_t attOffsetInLine = offsetInLine; if( isFound() && buffer != nullptr && offset < buffer->size() ) { - tagEnd = buffer->find( '>', offset ); + size_t tagEnd = findTagEnd( *buffer, offset ); if( tagEnd != string::npos ) { - std::smatch m; - try + attOffset = findAttribute( attName, *buffer, offset, tagEnd ); + if( attOffset != string::npos ) { - // we search for a string which is the attribute name followed by an '=', eventually separated by spaces - if( std::regex_search( buffer->cbegin() + offset, buffer->cbegin() + tagEnd, - m, std::regex( attName + "\\s*=" ) ) ) - { - attOffset = m.position() + offset; - attLine = line + std::count( buffer->cbegin() + offset, buffer->cbegin() + attOffset, '\n' ); - attOffsetInLine = attOffset - buffer->rfind( '\n', attOffset ); - } - } catch( std::regex_error const & e ) - { } + attLine = line + std::count( buffer->cbegin() + offset, buffer->cbegin() + attOffset, '\n' ); + attOffsetInLine = attOffset - buffer->rfind( '\n', attOffset ); + } } } From 729df9f835312ff333fd7191883872cc9dc58b15 Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Thu, 6 Jul 2023 12:17:17 +0200 Subject: [PATCH 48/85] Refactoring: hide xml_document implementation --- .../unitTests/testDruckerPrager.cpp | 12 +-- .../unitTests/testElasticIsotropic.cpp | 4 +- .../unitTests/testModifiedCamClay.cpp | 6 +- .../dataRepository/xmlWrapper.cpp | 50 ++++++----- .../dataRepository/xmlWrapper.hpp | 84 +++++++++++-------- .../fileIO/vtk/VTKPVDWriter.cpp | 10 +-- .../fileIO/vtk/VTKVTMWriter.cpp | 6 +- .../mainInterface/ProblemManager.cpp | 6 +- src/coreComponents/schema/schemaUtilities.cpp | 6 +- .../constitutiveTests/testDamage.cpp | 6 +- .../fluidFlowTests/testCompFlowUtils.hpp | 4 +- .../fluidFlowTests/testSingleFlowUtils.hpp | 4 +- .../testDofManagerUtils.hpp | 4 +- .../meshTests/testMeshGeneration.cpp | 4 +- .../unitTests/meshTests/testVTKImport.cpp | 4 +- .../testConformingVirtualElementOrder1.cpp | 8 +- .../unitTests/xmlTests/testXMLFile.cpp | 4 +- 17 files changed, 122 insertions(+), 100 deletions(-) diff --git a/src/coreComponents/constitutive/unitTests/testDruckerPrager.cpp b/src/coreComponents/constitutive/unitTests/testDruckerPrager.cpp index 0f140992601..2c5aec66ac8 100644 --- a/src/coreComponents/constitutive/unitTests/testDruckerPrager.cpp +++ b/src/coreComponents/constitutive/unitTests/testDruckerPrager.cpp @@ -72,8 +72,8 @@ void testDruckerPragerDriver() ""; xmlWrapper::xmlDocument xmlDocument; - xmlWrapper::xmlResult xmlResult = xmlDocument.load_buffer( inputStream.c_str(), - inputStream.size() ); + xmlWrapper::xmlResult xmlResult = xmlDocument.loadBuffer( inputStream.c_str(), + inputStream.size() ); if( !xmlResult ) { GEOS_LOG_RANK_0( "XML parsed with errors!" ); @@ -81,7 +81,7 @@ void testDruckerPragerDriver() GEOS_LOG_RANK_0( "Error offset: " << xmlResult.offset ); } - xmlWrapper::xmlNode xmlConstitutiveNode = xmlDocument.child( "Constitutive" ); + xmlWrapper::xmlNode xmlConstitutiveNode = xmlDocument.getChild( "Constitutive" ); constitutiveManager.processInputFileRecursive( xmlDocument, xmlConstitutiveNode ); constitutiveManager.postProcessInputRecursive(); @@ -189,8 +189,8 @@ void testDruckerPragerExtendedDriver() ""; xmlWrapper::xmlDocument xmlDocument; - xmlWrapper::xmlResult xmlResult = xmlDocument.load_buffer( inputStream.c_str(), - inputStream.size() ); + xmlWrapper::xmlResult xmlResult = xmlDocument.loadBuffer( inputStream.c_str(), + inputStream.size() ); if( !xmlResult ) { GEOS_LOG_RANK_0( "XML parsed with errors!" ); @@ -198,7 +198,7 @@ void testDruckerPragerExtendedDriver() GEOS_LOG_RANK_0( "Error offset: " << xmlResult.offset ); } - xmlWrapper::xmlNode xmlConstitutiveNode = xmlDocument.child( "Constitutive" ); + xmlWrapper::xmlNode xmlConstitutiveNode = xmlDocument.getChild( "Constitutive" ); constitutiveManager.processInputFileRecursive( xmlDocument, xmlConstitutiveNode ); constitutiveManager.postProcessInputRecursive(); diff --git a/src/coreComponents/constitutive/unitTests/testElasticIsotropic.cpp b/src/coreComponents/constitutive/unitTests/testElasticIsotropic.cpp index 0a2178e0a70..25f8b086000 100644 --- a/src/coreComponents/constitutive/unitTests/testElasticIsotropic.cpp +++ b/src/coreComponents/constitutive/unitTests/testElasticIsotropic.cpp @@ -71,7 +71,7 @@ TEST( ElasticIsotropicTests, testStateUpdatePoint ) ""; xmlWrapper::xmlDocument xmlDocument; - xmlWrapper::xmlResult xmlResult = xmlDocument.load_buffer( inputStream.c_str(), inputStream.size() ); + xmlWrapper::xmlResult xmlResult = xmlDocument.loadBuffer( inputStream.c_str(), inputStream.size() ); if( !xmlResult ) { GEOS_LOG_RANK_0( "XML parsed with errors!" ); @@ -79,7 +79,7 @@ TEST( ElasticIsotropicTests, testStateUpdatePoint ) GEOS_LOG_RANK_0( "Error offset: " << xmlResult.offset ); } - xmlWrapper::xmlNode xmlConstitutiveNode = xmlDocument.child( "Constitutive" ); + xmlWrapper::xmlNode xmlConstitutiveNode = xmlDocument.getChild( "Constitutive" ); constitutiveManager.processInputFileRecursive( xmlDocument, xmlConstitutiveNode ); constitutiveManager.postProcessInputRecursive(); diff --git a/src/coreComponents/constitutive/unitTests/testModifiedCamClay.cpp b/src/coreComponents/constitutive/unitTests/testModifiedCamClay.cpp index 8f4033d66ba..1ae9ba5831c 100644 --- a/src/coreComponents/constitutive/unitTests/testModifiedCamClay.cpp +++ b/src/coreComponents/constitutive/unitTests/testModifiedCamClay.cpp @@ -86,8 +86,8 @@ void testModifiedCamClayDriver() ""; xmlWrapper::xmlDocument xmlDocument; - xmlWrapper::xmlResult xmlResult = xmlDocument.load_buffer( inputStream.c_str(), - inputStream.size() ); + xmlWrapper::xmlResult xmlResult = xmlDocument.loadBuffer( inputStream.c_str(), + inputStream.size() ); if( !xmlResult ) { GEOS_LOG_RANK_0( "XML parsed with errors!" ); @@ -95,7 +95,7 @@ void testModifiedCamClayDriver() GEOS_LOG_RANK_0( "Error offset: " << xmlResult.offset ); } - xmlWrapper::xmlNode xmlConstitutiveNode = xmlDocument.child( "Constitutive" ); + xmlWrapper::xmlNode xmlConstitutiveNode = xmlDocument.getChild( "Constitutive" ); constitutiveManager.processInputFileRecursive( xmlDocument, xmlConstitutiveNode ); constitutiveManager.postProcessInputRecursive(); diff --git a/src/coreComponents/dataRepository/xmlWrapper.cpp b/src/coreComponents/dataRepository/xmlWrapper.cpp index 5e443bb0683..72599ec5079 100644 --- a/src/coreComponents/dataRepository/xmlWrapper.cpp +++ b/src/coreComponents/dataRepository/xmlWrapper.cpp @@ -96,7 +96,7 @@ void addNodeFileInfo( xmlNode targetNode, string const & filePath ) * @brief Returns true if the addNodeFileInfo() command has been called of the specified node. */ bool xmlDocument::hasNodeFileInfo() const -{ return !first_child().attribute( filePathString ).empty(); } +{ return !getFirstChild().attribute( filePathString ).empty(); } void xmlDocument::addIncludedXML( xmlNode & targetNode, int const level ) { @@ -124,8 +124,8 @@ void xmlDocument::addIncludedXML( xmlNode & targetNode, int const level ) }(); xmlDocument includedXmlDocument; - xmlResult const result = includedXmlDocument.load_file( includedFilePath.c_str(), - hasNodeFileInfo() ); + xmlResult const result = includedXmlDocument.loadFile( includedFilePath.c_str(), + hasNodeFileInfo() ); GEOS_THROW_IF( !result, GEOS_FMT( "Errors found while parsing included XML file {}\n" "Description: {}\nOffset: {}", includedFilePath, result.description(), result.offset ), @@ -136,7 +136,7 @@ void xmlDocument::addIncludedXML( xmlNode & targetNode, int const level ) // Currently, schema only allows tags at the top level (inside ). // We then proceed to merge each nested node from included file with the one in main. - xmlNode includedRootNode = includedXmlDocument.first_child(); + xmlNode includedRootNode = includedXmlDocument.getFirstChild(); GEOS_THROW_IF_NE_MSG( string( includedRootNode.name() ), string( targetNode.name() ), "Included document root does not match the including XML node", InputError ); @@ -177,7 +177,7 @@ string buildMultipleInputXML( string_array const & inputFileList, if( MpiWrapper::commRank() == 0 ) { xmlWrapper::xmlDocument compositeTree; - xmlWrapper::xmlNode compositeRoot = compositeTree.append_child( dataRepository::keys::ProblemManager ); + xmlWrapper::xmlNode compositeRoot = compositeTree.appendChild( dataRepository::keys::ProblemManager ); xmlWrapper::xmlNode includedRoot = compositeRoot.append_child( includedListTag ); for( auto & fileName: inputFileList ) @@ -186,7 +186,7 @@ string buildMultipleInputXML( string_array const & inputFileList, fileNode.append_attribute( "name" ) = fileName.c_str(); } - compositeTree.save_file( inputFileName.c_str() ); + compositeTree.saveFile( inputFileName.c_str() ); } // Everybody else has to wait before attempting to read @@ -207,14 +207,13 @@ constexpr size_t xmlDocument::npos; size_t documentId=0; xmlDocument::xmlDocument(): - pugi::xml_document(), + pugiDocument(), m_rootFilePath( "CodeIncludedXML" + std::to_string( documentId++ ) ) {} -xmlResult xmlDocument::load_string( const pugi::char_t * contents, bool loadNodeFileInfo, - unsigned int options ) +xmlResult xmlDocument::loadString( const pugi::char_t * contents, bool loadNodeFileInfo ) { - xmlResult result = pugi::xml_document::load_string( contents, options ); + xmlResult result = pugiDocument.load_string( contents, pugi::parse_default ); // keeping a copy of original buffer to allow line retrieval if( loadNodeFileInfo ) @@ -222,15 +221,14 @@ xmlResult xmlDocument::load_string( const pugi::char_t * contents, bool loadNode m_originalBuffers.clear(); m_originalBuffers[m_rootFilePath] = string( contents ); - addNodeFileInfo( first_child(), m_rootFilePath ); + addNodeFileInfo( getFirstChild(), m_rootFilePath ); } return result; } -xmlResult xmlDocument::load_file( const char * path, bool loadNodeFileInfo, - unsigned int options, pugi::xml_encoding encoding ) +xmlResult xmlDocument::loadFile( const char * path, bool loadNodeFileInfo ) { - xmlResult result = pugi::xml_document::load_file( path, options, encoding ); + xmlResult result = pugiDocument.load_file( path, pugi::parse_default, pugi::encoding_auto ); m_rootFilePath = getAbsolutePath( path ); // keeping a copy of original buffer to allow line retrieval @@ -243,15 +241,14 @@ xmlResult xmlDocument::load_file( const char * path, bool loadNodeFileInfo, m_originalBuffers.clear(); m_originalBuffers[m_rootFilePath] = string( buffer.str() ); - addNodeFileInfo( first_child(), getAbsolutePath( m_rootFilePath ) ); + addNodeFileInfo( getFirstChild(), getAbsolutePath( m_rootFilePath ) ); } return result; } -xmlResult xmlDocument::load_buffer( const void * contents, size_t size, bool loadNodeFileInfo, - unsigned int options, pugi::xml_encoding encoding ) +xmlResult xmlDocument::loadBuffer( const void * contents, size_t size, bool loadNodeFileInfo ) { - xmlResult result = pugi::xml_document::load_buffer( contents, size, options, encoding ); + xmlResult result = pugiDocument.load_buffer( contents, size, pugi::parse_default, pugi::encoding_auto ); //keeping a copy of original buffer if( loadNodeFileInfo ) @@ -259,15 +256,30 @@ xmlResult xmlDocument::load_buffer( const void * contents, size_t size, bool loa m_originalBuffers.clear(); m_originalBuffers[m_rootFilePath] = string( ( char const * )contents, size ); - addNodeFileInfo( first_child(), m_rootFilePath ); + addNodeFileInfo( getFirstChild(), m_rootFilePath ); } return result; } +xmlNode xmlDocument::appendChild( const char * name ) +{ return pugiDocument.append_child( name ); } + +xmlNode xmlDocument::appendChild( xmlTypes type ) +{ return pugiDocument.append_child( type ); } + +bool xmlDocument::saveFile( const char * path ) const +{ return pugiDocument.save_file( path ); } + string const & xmlDocument::getFilePath() const { return m_rootFilePath; } +xmlNode xmlDocument::getFirstChild() const +{ return pugiDocument.first_child(); } + +xmlNode xmlDocument::getChild( char const * name ) const +{ return pugiDocument.child( name ); } + string const & xmlDocument::getOriginalBuffer() const { return m_originalBuffers.find( m_rootFilePath )->second; } diff --git a/src/coreComponents/dataRepository/xmlWrapper.hpp b/src/coreComponents/dataRepository/xmlWrapper.hpp index c53a7c25843..02a1323c787 100644 --- a/src/coreComponents/dataRepository/xmlWrapper.hpp +++ b/src/coreComponents/dataRepository/xmlWrapper.hpp @@ -128,7 +128,7 @@ struct xmlNodePos : xmlAttributePos /// Wrapper class for the type of xml document. /// This class exists to intercept file / string loading methods, and to keep the loaded buffers, /// in order to retrieve the source file and line of nodes and attributes. -class xmlDocument : public pugi::xml_document +class xmlDocument { public: /// Error value for when an offset / line position is undefined. @@ -138,7 +138,24 @@ class xmlDocument : public pugi::xml_document * @brief Construct an empty xmlDocument that waits to load something. */ xmlDocument(); + /** + * @brief non-copyable + */ + xmlDocument( const xmlDocument & ) = delete; + /** + * @brief move constructor + */ + xmlDocument( xmlDocument && ) = default; + /** + * @return the first child of this document (typically in GEOS, the node) + */ + xmlNode getFirstChild() const; + /** + * @return a child with the specified name + * @param name the tag name of the node to find + */ + xmlNode getChild( const char * name ) const; /** * @return the original file buffer loaded during the last load_X() call on this object. */ @@ -155,7 +172,7 @@ class xmlDocument : public pugi::xml_document */ map< string, string > const & getOriginalBuffers() const; /** - * @return If load_file() has been loaded, returns the path of the source file. + * @return If loadFile() has been loaded, returns the path of the source file. * If another load method has been called, it returns a generated unique value. */ string const & getFilePath() const; @@ -170,32 +187,29 @@ class xmlDocument : public pugi::xml_document /** * @brief Load document from zero-terminated string. No encoding conversions are applied. * Free any previously loaded xml tree. - * Wrapper of pugi::xml_document::load_buffer() method. + * Wrapper of pugi::xml_document::loadBuffer() method. * @param contents the string containing the document content * @param loadNodeFileInfo Load the node source file info, allowing getNodePosition() to work. * @param options the parsing options * @return an xmlResult object representing the parsing resulting status. */ - xmlResult load_string( const pugi::char_t * contents, bool loadNodeFileInfo = false, - unsigned int options = pugi::parse_default ); + xmlResult loadString( const pugi::char_t * contents, bool loadNodeFileInfo = false ); /** * @brief Load document from file. Free any previously loaded xml tree. - * Wrapper of pugi::xml_document::load_buffer() method. + * Wrapper of pugi::xml_document::loadBuffer() method. * @param path the path of an xml file to load. * @param loadNodeFileInfo Load the node source file info, allowing getNodePosition() to work. * @param options the parsing options * @param encoding the encoding options * @return an xmlResult object representing the parsing resulting status. */ - xmlResult load_file( const char * path, bool loadNodeFileInfo = false, - unsigned int options = pugi::parse_default, - pugi::xml_encoding encoding = pugi::encoding_auto ); + xmlResult loadFile( const char * path, bool loadNodeFileInfo = false ); /** * @brief Load document from buffer. Copies/converts the buffer, so it may be deleted or changed * after the function returns. Free any previously loaded xml tree. - * Wrapper of pugi::xml_document::load_buffer() method. + * Wrapper of pugi::xml_document::loadBuffer() method. * @param contents the buffer containing the document content * @param size the size of the buffer in bytes * @param loadNodeFileInfo Load the node source file info, allowing getNodePosition() to work. @@ -203,36 +217,29 @@ class xmlDocument : public pugi::xml_document * @param encoding the encoding options * @return an xmlResult object representing the parsing resulting status. */ - xmlResult load_buffer( const void * contents, size_t size, bool loadNodeFileInfo = false, - unsigned int options = pugi::parse_default, - pugi::xml_encoding encoding = pugi::encoding_auto ); + xmlResult loadBuffer( const void * contents, size_t size, bool loadNodeFileInfo = false ); /** - * @name deleted methods we don't need to inherit + * @brief Add a root element to the document + * @param name the tag name of the node to add + * @return the added node + */ + xmlNode appendChild( const char * name ); + /** + * @brief Add a root element to the document + * @param type the type of the node to add to the root of the document. + * As an exemple, node_declaration is useful to add the "" node. + * @return the added node */ - ///@{ - /// @cond DO_NOT_DOCUMENT - xmlResult load_string( const pugi::char_t * contents, unsigned int options ) = delete; - xmlResult load_file( const char * path, unsigned int options, - pugi::xml_encoding encoding = pugi::encoding_auto ) = delete; - - xmlResult load_buffer( const void * contents, size_t size, unsigned int options, - pugi::xml_encoding encoding = pugi::encoding_auto ) = delete; - #ifndef PUGIXML_NO_STL - xmlResult load( std::basic_istream< char, std::char_traits< char > > & stream, - unsigned int options, - pugi::xml_encoding encoding = pugi::encoding_auto ) = delete; - xmlResult load( std::basic_istream< wchar_t, std::char_traits< wchar_t > > & stream, - unsigned int options ) = delete; - #endif - xmlResult load_file( const wchar_t * path, unsigned int options, - pugi::xml_encoding encoding = pugi::encoding_auto ) = delete; - xmlResult load_buffer_inplace( void * contents, size_t size, unsigned int options, - pugi::xml_encoding encoding = pugi::encoding_auto ) = delete; - xmlResult load_buffer_inplace_own( void * contents, size_t size, unsigned int options, - pugi::xml_encoding encoding = pugi::encoding_auto ) = delete; - /// @endcond - ///@} + xmlNode appendChild( xmlTypes type = xmlTypes::node_element ); + + /** + * @brief Save the XML to a file + * @param path the file path + * @return true if the file has successfuly been saved + * @return false otherwise + */ + bool saveFile( const char * path ) const; /** * @brief Function to add xml nodes from included files. @@ -252,6 +259,9 @@ class xmlDocument : public pugi::xml_document bool hasNodeFileInfo() const; private: + /// original xml_document object that this class aims to wrap + pugi::xml_document pugiDocument; + /// Used to retrieve node positions as pugixml buffer is private and processed. map< string, string > m_originalBuffers; /// @see getFilePath() diff --git a/src/coreComponents/fileIO/vtk/VTKPVDWriter.cpp b/src/coreComponents/fileIO/vtk/VTKPVDWriter.cpp index ff602df971d..e84aaced61c 100644 --- a/src/coreComponents/fileIO/vtk/VTKPVDWriter.cpp +++ b/src/coreComponents/fileIO/vtk/VTKPVDWriter.cpp @@ -24,11 +24,11 @@ VTKPVDWriter::VTKPVDWriter( string fileName ): m_fileName( std::move( fileName ) ) { // Declaration of XML version - auto declarationNode = m_pvdFile.append_child( pugi::node_declaration ); + auto declarationNode = m_pvdFile.appendChild( pugi::node_declaration ); declarationNode.append_attribute( "version" ) = "1.0"; // Declaration of the node VTKFile - auto vtkFileNode = m_pvdFile.append_child( "VTKFile" ); + auto vtkFileNode = m_pvdFile.appendChild( "VTKFile" ); vtkFileNode.append_attribute( "type" ) = "Collection"; vtkFileNode.append_attribute( "version" ) = "0.1"; @@ -42,12 +42,12 @@ void VTKPVDWriter::setFileName( string fileName ) void VTKPVDWriter::save() const { - m_pvdFile.save_file( m_fileName.c_str() ); + m_pvdFile.saveFile( m_fileName.c_str() ); } void VTKPVDWriter::addData( real64 time, string const & filePath ) const { - auto collectionNode = m_pvdFile.child( "VTKFile" ).child( "Collection" ); + auto collectionNode = m_pvdFile.getChild( "VTKFile" ).child( "Collection" ); auto dataSetNode = collectionNode.append_child( "DataSet" ); dataSetNode.append_attribute( "timestep" ) = time; dataSetNode.append_attribute( "file" ) = filePath.c_str(); @@ -55,7 +55,7 @@ void VTKPVDWriter::addData( real64 time, string const & filePath ) const void VTKPVDWriter::reinitData() const { - auto collectionNode = m_pvdFile.child( "VTKFile" ).child( "Collection" ); + auto collectionNode = m_pvdFile.getChild( "VTKFile" ).child( "Collection" ); while( collectionNode.remove_child( "DataSet" ) ) {} diff --git a/src/coreComponents/fileIO/vtk/VTKVTMWriter.cpp b/src/coreComponents/fileIO/vtk/VTKVTMWriter.cpp index 48bebc54232..15d391b1c09 100644 --- a/src/coreComponents/fileIO/vtk/VTKVTMWriter.cpp +++ b/src/coreComponents/fileIO/vtk/VTKVTMWriter.cpp @@ -26,11 +26,11 @@ VTKVTMWriter::VTKVTMWriter( string filePath ) : m_filePath( std::move( filePath ) ) { // Declaration of XML version - auto declarationNode = m_document.append_child( pugi::node_declaration ); + auto declarationNode = m_document.appendChild( pugi::node_declaration ); declarationNode.append_attribute( "version" ) = "1.0"; // Declaration of the node VTKFile - auto vtkFileNode = m_document.append_child( "VTKFile" ); + auto vtkFileNode = m_document.appendChild( "VTKFile" ); vtkFileNode.append_attribute( "type" ) = "vtkMultiBlockDataSet"; vtkFileNode.append_attribute( "version" ) = "1.0"; @@ -39,7 +39,7 @@ VTKVTMWriter::VTKVTMWriter( string filePath ) void VTKVTMWriter::write() const { - m_document.save_file( m_filePath.c_str() ); + m_document.saveFile( m_filePath.c_str() ); } void VTKVTMWriter::addDataSet( std::vector< string > const & blockPath, diff --git a/src/coreComponents/mainInterface/ProblemManager.cpp b/src/coreComponents/mainInterface/ProblemManager.cpp index 531bf5d80f8..7dc455fcaff 100644 --- a/src/coreComponents/mainInterface/ProblemManager.cpp +++ b/src/coreComponents/mainInterface/ProblemManager.cpp @@ -385,7 +385,7 @@ void ProblemManager::parseInputFile() // Load preprocessed xml file xmlWrapper::xmlDocument xmlDocument; - xmlWrapper::xmlResult const xmlResult = xmlDocument.load_file( inputFileName.c_str(), true ); + xmlWrapper::xmlResult const xmlResult = xmlDocument.loadFile( inputFileName.c_str(), true ); GEOS_THROW_IF( !xmlResult, GEOS_FMT( "Errors found while parsing XML file {}\nDescription: {}\nOffset: {}", inputFileName, xmlResult.description(), xmlResult.offset ), InputError ); @@ -398,7 +398,7 @@ void ProblemManager::parseInputString( string const & xmlString ) { // Load preprocessed xml file xmlWrapper::xmlDocument xmlDocument; - xmlWrapper::xmlResult xmlResult = xmlDocument.load_buffer( xmlString.c_str(), xmlString.length(), true ); + xmlWrapper::xmlResult xmlResult = xmlDocument.loadBuffer( xmlString.c_str(), xmlString.length(), true ); GEOS_THROW_IF( !xmlResult, GEOS_FMT( "Errors found while parsing XML string\nDescription: {}\nOffset: {}", xmlResult.description(), xmlResult.offset ), InputError ); @@ -410,7 +410,7 @@ void ProblemManager::parseInputString( string const & xmlString ) void ProblemManager::parseXMLDocument( xmlWrapper::xmlDocument & xmlDocument ) { // Extract the problem node and begin processing the user inputs - xmlWrapper::xmlNode xmlProblemNode = xmlDocument.child( this->getName().c_str() ); + xmlWrapper::xmlNode xmlProblemNode = xmlDocument.getChild( this->getName().c_str() ); processInputFileRecursive( xmlDocument, xmlProblemNode ); // The objects in domain are handled separately for now diff --git a/src/coreComponents/schema/schemaUtilities.cpp b/src/coreComponents/schema/schemaUtilities.cpp index 1a86bd3b15d..d59c6eec925 100644 --- a/src/coreComponents/schema/schemaUtilities.cpp +++ b/src/coreComponents/schema/schemaUtilities.cpp @@ -52,8 +52,8 @@ void ConvertDocumentationToSchema( string const & fname, "; xmlWrapper::xmlDocument schemaTree; - schemaTree.load_string( schemaBase.c_str() ); - xmlWrapper::xmlNode schemaRoot = schemaTree.child( "xsd:schema" ); + schemaTree.loadString( schemaBase.c_str() ); + xmlWrapper::xmlNode schemaRoot = schemaTree.getChild( "xsd:schema" ); // Build the simple schema types GEOS_LOG_RANK_0( " Basic datatypes" ); @@ -65,7 +65,7 @@ void ConvertDocumentationToSchema( string const & fname, // Write the schema to file GEOS_LOG_RANK_0( " Saving file" ); - schemaTree.save_file( fname.c_str()); + schemaTree.saveFile( fname.c_str()); GEOS_LOG_RANK_0( " Done!" ); } diff --git a/src/coreComponents/unitTests/constitutiveTests/testDamage.cpp b/src/coreComponents/unitTests/constitutiveTests/testDamage.cpp index 4e37b6e5645..e24645a23f9 100644 --- a/src/coreComponents/unitTests/constitutiveTests/testDamage.cpp +++ b/src/coreComponents/unitTests/constitutiveTests/testDamage.cpp @@ -46,8 +46,8 @@ TEST( DamageTests, testDamageSpectral ) ""; xmlWrapper::xmlDocument xmlDocument; - xmlWrapper::xmlResult xmlResult = xmlDocument.load_buffer( inputStream.c_str(), - inputStream.size() ); + xmlWrapper::xmlResult xmlResult = xmlDocument.loadBuffer( inputStream.c_str(), + inputStream.size() ); if( !xmlResult ) { GEOS_LOG_RANK_0( "XML parsed with errors!" ); @@ -55,7 +55,7 @@ TEST( DamageTests, testDamageSpectral ) GEOS_LOG_RANK_0( "Error offset: " << xmlResult.offset ); } - xmlWrapper::xmlNode xmlConstitutiveNode = xmlDocument.child( "Constitutive" ); + xmlWrapper::xmlNode xmlConstitutiveNode = xmlDocument.getChild( "Constitutive" ); constitutiveManager.processInputFileRecursive( xmlDocument, xmlConstitutiveNode ); constitutiveManager.postProcessInputRecursive(); diff --git a/src/coreComponents/unitTests/fluidFlowTests/testCompFlowUtils.hpp b/src/coreComponents/unitTests/fluidFlowTests/testCompFlowUtils.hpp index ff45cf31259..5a192240183 100644 --- a/src/coreComponents/unitTests/fluidFlowTests/testCompFlowUtils.hpp +++ b/src/coreComponents/unitTests/fluidFlowTests/testCompFlowUtils.hpp @@ -183,7 +183,7 @@ void fillNumericalJacobian( arrayView1d< real64 const > const & residual, void setupProblemFromXML( ProblemManager & problemManager, char const * const xmlInput ) { xmlWrapper::xmlDocument xmlDocument; - xmlWrapper::xmlResult xmlResult = xmlDocument.load_buffer( xmlInput, strlen( xmlInput ) ); + xmlWrapper::xmlResult xmlResult = xmlDocument.loadBuffer( xmlInput, strlen( xmlInput ) ); if( !xmlResult ) { GEOS_LOG_RANK_0( "XML parsed with errors!" ); @@ -199,7 +199,7 @@ void setupProblemFromXML( ProblemManager & problemManager, char const * const xm commandLine.registerWrapper< integer >( problemManager.viewKeys.xPartitionsOverride.key() ). setApplyDefaultValue( mpiSize ); - xmlWrapper::xmlNode xmlProblemNode = xmlDocument.child( dataRepository::keys::ProblemManager ); + xmlWrapper::xmlNode xmlProblemNode = xmlDocument.getChild( dataRepository::keys::ProblemManager ); problemManager.processInputFileRecursive( xmlDocument, xmlProblemNode ); DomainPartition & domain = problemManager.getDomainPartition(); diff --git a/src/coreComponents/unitTests/fluidFlowTests/testSingleFlowUtils.hpp b/src/coreComponents/unitTests/fluidFlowTests/testSingleFlowUtils.hpp index acf537bcb48..c3c58a63ea3 100644 --- a/src/coreComponents/unitTests/fluidFlowTests/testSingleFlowUtils.hpp +++ b/src/coreComponents/unitTests/fluidFlowTests/testSingleFlowUtils.hpp @@ -75,7 +75,7 @@ void fillNumericalJacobian( arrayView1d< real64 const > const & residual, void setupProblemFromXML( ProblemManager & problemManager, char const * const xmlInput ) { xmlWrapper::xmlDocument xmlDocument; - xmlWrapper::xmlResult xmlResult = xmlDocument.load_buffer( xmlInput, strlen( xmlInput ) ); + xmlWrapper::xmlResult xmlResult = xmlDocument.loadBuffer( xmlInput, strlen( xmlInput ) ); if( !xmlResult ) { GEOS_LOG_RANK_0( "XML parsed with errors!" ); @@ -91,7 +91,7 @@ void setupProblemFromXML( ProblemManager & problemManager, char const * const xm commandLine.registerWrapper< integer >( problemManager.viewKeys.xPartitionsOverride.key() ). setApplyDefaultValue( mpiSize ); - xmlWrapper::xmlNode xmlProblemNode = xmlDocument.child( dataRepository::keys::ProblemManager ); + xmlWrapper::xmlNode xmlProblemNode = xmlDocument.getChild( dataRepository::keys::ProblemManager ); problemManager.processInputFileRecursive( xmlDocument, xmlProblemNode ); DomainPartition & domain = problemManager.getDomainPartition(); diff --git a/src/coreComponents/unitTests/linearAlgebraTests/testDofManagerUtils.hpp b/src/coreComponents/unitTests/linearAlgebraTests/testDofManagerUtils.hpp index 217de9eed93..06f28489f5c 100644 --- a/src/coreComponents/unitTests/linearAlgebraTests/testDofManagerUtils.hpp +++ b/src/coreComponents/unitTests/linearAlgebraTests/testDofManagerUtils.hpp @@ -38,7 +38,7 @@ namespace testing void setupProblemFromXML( ProblemManager * const problemManager, char const * const xmlInput ) { xmlWrapper::xmlDocument xmlDocument; - xmlWrapper::xmlResult xmlResult = xmlDocument.load_buffer( xmlInput, strlen( xmlInput ) ); + xmlWrapper::xmlResult xmlResult = xmlDocument.loadBuffer( xmlInput, strlen( xmlInput ) ); if( !xmlResult ) { GEOS_LOG_RANK_0( "XML parsed with errors!" ); @@ -52,7 +52,7 @@ void setupProblemFromXML( ProblemManager * const problemManager, char const * co commandLine.registerWrapper< integer >( problemManager->viewKeys.xPartitionsOverride.key() ). setApplyDefaultValue( mpiSize ); - xmlWrapper::xmlNode xmlProblemNode = xmlDocument.child( dataRepository::keys::ProblemManager ); + xmlWrapper::xmlNode xmlProblemNode = xmlDocument.getChild( dataRepository::keys::ProblemManager ); problemManager->processInputFileRecursive( xmlDocument, xmlProblemNode ); // Open mesh levels diff --git a/src/coreComponents/unitTests/meshTests/testMeshGeneration.cpp b/src/coreComponents/unitTests/meshTests/testMeshGeneration.cpp index e3e0cdd08dd..8bc67e65dbe 100644 --- a/src/coreComponents/unitTests/meshTests/testMeshGeneration.cpp +++ b/src/coreComponents/unitTests/meshTests/testMeshGeneration.cpp @@ -99,10 +99,10 @@ class MeshGenerationTest : public ::testing::Test maxCoordInX, maxCoordInY, maxCoordInZ, numElemsInX, numElemsInY, numElemsInZ ); xmlWrapper::xmlDocument xmlDocument; - xmlWrapper::xmlResult xmlResult = xmlDocument.load_buffer( inputStream.c_str(), inputStream.size() ); + xmlWrapper::xmlResult xmlResult = xmlDocument.loadBuffer( inputStream.c_str(), inputStream.size() ); ASSERT_TRUE( xmlResult ); - xmlWrapper::xmlNode xmlProblemNode = xmlDocument.child( dataRepository::keys::ProblemManager ); + xmlWrapper::xmlNode xmlProblemNode = xmlDocument.getChild( dataRepository::keys::ProblemManager ); ProblemManager & problemManager = getGlobalState().getProblemManager(); problemManager.processInputFileRecursive( xmlDocument, xmlProblemNode ); diff --git a/src/coreComponents/unitTests/meshTests/testVTKImport.cpp b/src/coreComponents/unitTests/meshTests/testVTKImport.cpp index 7bf7ef719eb..971bd89bf9a 100644 --- a/src/coreComponents/unitTests/meshTests/testVTKImport.cpp +++ b/src/coreComponents/unitTests/meshTests/testVTKImport.cpp @@ -38,8 +38,8 @@ void TestMeshImport( string const & meshFilePath, V const & validate ) { string const meshNode = GEOS_FMT( R"()", meshFilePath ); xmlWrapper::xmlDocument xmlDocument; - xmlDocument.load_buffer( meshNode.c_str(), meshNode.size() ); - xmlWrapper::xmlNode xmlMeshNode = xmlDocument.child( "Mesh" ); + xmlDocument.loadBuffer( meshNode.c_str(), meshNode.size() ); + xmlWrapper::xmlNode xmlMeshNode = xmlDocument.getChild( "Mesh" ); conduit::Node node; Group root( "root", node ); diff --git a/src/coreComponents/unitTests/virtualElementTests/testConformingVirtualElementOrder1.cpp b/src/coreComponents/unitTests/virtualElementTests/testConformingVirtualElementOrder1.cpp index b1deee93aa9..4b7bc185ef3 100644 --- a/src/coreComponents/unitTests/virtualElementTests/testConformingVirtualElementOrder1.cpp +++ b/src/coreComponents/unitTests/virtualElementTests/testConformingVirtualElementOrder1.cpp @@ -280,14 +280,14 @@ TEST( ConformingVirtualElementOrder1, hexahedra ) ""; xmlWrapper::xmlDocument inputFile; - xmlWrapper::xmlResult xmlResult = inputFile.load_buffer( inputStream.c_str(), inputStream.size()); + xmlWrapper::xmlResult xmlResult = inputFile.loadBuffer( inputStream.c_str(), inputStream.size()); if( !xmlResult ) { GEOS_LOG_RANK_0( "XML parsed with errors!" ); GEOS_LOG_RANK_0( "Error description: " << xmlResult.description()); GEOS_LOG_RANK_0( "Error offset: " << xmlResult.offset ); } - xmlWrapper::xmlNode xmlProblemNode = inputFile.child( dataRepository::keys::ProblemManager ); + xmlWrapper::xmlNode xmlProblemNode = inputFile.getChild( dataRepository::keys::ProblemManager ); GeosxState state( std::make_unique< CommandLineOptions >( g_commandLineOptions ) ); @@ -333,14 +333,14 @@ TEST( ConformingVirtualElementOrder1, wedges ) " " ""; xmlWrapper::xmlDocument inputFile; - xmlWrapper::xmlResult xmlResult = inputFile.load_buffer( inputStream.c_str(), inputStream.size()); + xmlWrapper::xmlResult xmlResult = inputFile.loadBuffer( inputStream.c_str(), inputStream.size()); if( !xmlResult ) { GEOS_LOG_RANK_0( "XML parsed with errors!" ); GEOS_LOG_RANK_0( "Error description: " << xmlResult.description()); GEOS_LOG_RANK_0( "Error offset: " << xmlResult.offset ); } - xmlWrapper::xmlNode xmlProblemNode = inputFile.child( dataRepository::keys::ProblemManager ); + xmlWrapper::xmlNode xmlProblemNode = inputFile.getChild( dataRepository::keys::ProblemManager ); GeosxState state( std::make_unique< CommandLineOptions >( g_commandLineOptions ) ); diff --git a/src/coreComponents/unitTests/xmlTests/testXMLFile.cpp b/src/coreComponents/unitTests/xmlTests/testXMLFile.cpp index d056ee5d675..a39d0fcc479 100644 --- a/src/coreComponents/unitTests/xmlTests/testXMLFile.cpp +++ b/src/coreComponents/unitTests/xmlTests/testXMLFile.cpp @@ -167,7 +167,7 @@ TEST( testXML, testXMLFileLines ) problemManager.parseCommandLineInput(); Group & commandLine = problemManager.getGroup( problemManager.groupKeys.commandLine ); string const & inputFileName = commandLine.getReference< string >( problemManager.viewKeys.inputFileName ); - xmlDoc.load_file( inputFileName.c_str(), true ); + xmlDoc.loadFile( inputFileName.c_str(), true ); problemManager.parseXMLDocument( xmlDoc ); } @@ -178,7 +178,7 @@ TEST( testXML, testXMLFileLines ) } std::set< string > expectedElements; - getElementsRecursive( xmlDoc, xmlDoc.root().child( "Problem" ), expectedElements ); + getElementsRecursive( xmlDoc, xmlDoc.getFirstChild(), expectedElements ); std::set< string > verifiedElements; problemManager.forAllDataContext< DataFileContext >( [&]( DataFileContext const & ctx ) From 4864a308b3eae9d124a42ae8c57ff64674f6c913 Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Fri, 7 Jul 2023 14:48:17 +0200 Subject: [PATCH 49/85] xmlDocument refactorings --- .../dataRepository/DataContext.hpp | 2 +- .../dataRepository/GroupContext.hpp | 2 +- .../dataRepository/xmlWrapper.cpp | 27 +++++++++---------- .../dataRepository/xmlWrapper.hpp | 10 +++---- .../fileIO/vtk/VTKPVDWriter.cpp | 2 +- .../fileIO/vtk/VTKVTMWriter.cpp | 2 +- .../mainInterface/ProblemManager.cpp | 2 +- src/coreComponents/schema/schemaUtilities.cpp | 4 +-- .../unitTests/xmlTests/testXMLFile.cpp | 2 +- 9 files changed, 26 insertions(+), 27 deletions(-) diff --git a/src/coreComponents/dataRepository/DataContext.hpp b/src/coreComponents/dataRepository/DataContext.hpp index 320a5e23e2e..13e7b539339 100644 --- a/src/coreComponents/dataRepository/DataContext.hpp +++ b/src/coreComponents/dataRepository/DataContext.hpp @@ -154,7 +154,7 @@ class DataFileContext final : public DataContext size_t getOffset() const { return m_offset; } -protected: +private: /// @see getTypeName() string const m_typeName; diff --git a/src/coreComponents/dataRepository/GroupContext.hpp b/src/coreComponents/dataRepository/GroupContext.hpp index b63e2588081..5d13f47a5b0 100644 --- a/src/coreComponents/dataRepository/GroupContext.hpp +++ b/src/coreComponents/dataRepository/GroupContext.hpp @@ -82,7 +82,7 @@ class WrapperContext final : public GroupContext */ virtual string toString() const; -protected: +private: string const m_typeName; diff --git a/src/coreComponents/dataRepository/xmlWrapper.cpp b/src/coreComponents/dataRepository/xmlWrapper.cpp index 72599ec5079..218746b0fd1 100644 --- a/src/coreComponents/dataRepository/xmlWrapper.cpp +++ b/src/coreComponents/dataRepository/xmlWrapper.cpp @@ -124,8 +124,7 @@ void xmlDocument::addIncludedXML( xmlNode & targetNode, int const level ) }(); xmlDocument includedXmlDocument; - xmlResult const result = includedXmlDocument.loadFile( includedFilePath.c_str(), - hasNodeFileInfo() ); + xmlResult const result = includedXmlDocument.loadFile( includedFilePath, hasNodeFileInfo() ); GEOS_THROW_IF( !result, GEOS_FMT( "Errors found while parsing included XML file {}\n" "Description: {}\nOffset: {}", includedFilePath, result.description(), result.offset ), @@ -186,7 +185,7 @@ string buildMultipleInputXML( string_array const & inputFileList, fileNode.append_attribute( "name" ) = fileName.c_str(); } - compositeTree.saveFile( inputFileName.c_str() ); + compositeTree.saveFile( inputFileName ); } // Everybody else has to wait before attempting to read @@ -211,24 +210,24 @@ xmlDocument::xmlDocument(): m_rootFilePath( "CodeIncludedXML" + std::to_string( documentId++ ) ) {} -xmlResult xmlDocument::loadString( const pugi::char_t * contents, bool loadNodeFileInfo ) +xmlResult xmlDocument::loadString( string const & contents, bool loadNodeFileInfo ) { - xmlResult result = pugiDocument.load_string( contents, pugi::parse_default ); + xmlResult result = pugiDocument.load_string( contents.c_str(), pugi::parse_default ); // keeping a copy of original buffer to allow line retrieval if( loadNodeFileInfo ) { m_originalBuffers.clear(); - m_originalBuffers[m_rootFilePath] = string( contents ); + m_originalBuffers[m_rootFilePath] = contents; addNodeFileInfo( getFirstChild(), m_rootFilePath ); } return result; } -xmlResult xmlDocument::loadFile( const char * path, bool loadNodeFileInfo ) +xmlResult xmlDocument::loadFile( string const & path, bool loadNodeFileInfo ) { - xmlResult result = pugiDocument.load_file( path, pugi::parse_default, pugi::encoding_auto ); + xmlResult result = pugiDocument.load_file( path.c_str(), pugi::parse_default, pugi::encoding_auto ); m_rootFilePath = getAbsolutePath( path ); // keeping a copy of original buffer to allow line retrieval @@ -262,14 +261,14 @@ xmlResult xmlDocument::loadBuffer( const void * contents, size_t size, bool load return result; } -xmlNode xmlDocument::appendChild( const char * name ) -{ return pugiDocument.append_child( name ); } +xmlNode xmlDocument::appendChild( string const & name ) +{ return pugiDocument.append_child( name.c_str() ); } xmlNode xmlDocument::appendChild( xmlTypes type ) { return pugiDocument.append_child( type ); } -bool xmlDocument::saveFile( const char * path ) const -{ return pugiDocument.save_file( path ); } +bool xmlDocument::saveFile( string const & path ) const +{ return pugiDocument.save_file( path.c_str() ); } string const & xmlDocument::getFilePath() const { return m_rootFilePath; } @@ -277,8 +276,8 @@ string const & xmlDocument::getFilePath() const xmlNode xmlDocument::getFirstChild() const { return pugiDocument.first_child(); } -xmlNode xmlDocument::getChild( char const * name ) const -{ return pugiDocument.child( name ); } +xmlNode xmlDocument::getChild( string const & name ) const +{ return pugiDocument.child( name.c_str() ); } string const & xmlDocument::getOriginalBuffer() const { return m_originalBuffers.find( m_rootFilePath )->second; } diff --git a/src/coreComponents/dataRepository/xmlWrapper.hpp b/src/coreComponents/dataRepository/xmlWrapper.hpp index 02a1323c787..80e684a7dd0 100644 --- a/src/coreComponents/dataRepository/xmlWrapper.hpp +++ b/src/coreComponents/dataRepository/xmlWrapper.hpp @@ -155,7 +155,7 @@ class xmlDocument * @return a child with the specified name * @param name the tag name of the node to find */ - xmlNode getChild( const char * name ) const; + xmlNode getChild( string const & name ) const; /** * @return the original file buffer loaded during the last load_X() call on this object. */ @@ -193,7 +193,7 @@ class xmlDocument * @param options the parsing options * @return an xmlResult object representing the parsing resulting status. */ - xmlResult loadString( const pugi::char_t * contents, bool loadNodeFileInfo = false ); + xmlResult loadString( string const & contents, bool loadNodeFileInfo = false ); /** * @brief Load document from file. Free any previously loaded xml tree. @@ -204,7 +204,7 @@ class xmlDocument * @param encoding the encoding options * @return an xmlResult object representing the parsing resulting status. */ - xmlResult loadFile( const char * path, bool loadNodeFileInfo = false ); + xmlResult loadFile( string const & path, bool loadNodeFileInfo = false ); /** * @brief Load document from buffer. Copies/converts the buffer, so it may be deleted or changed @@ -224,7 +224,7 @@ class xmlDocument * @param name the tag name of the node to add * @return the added node */ - xmlNode appendChild( const char * name ); + xmlNode appendChild( string const & name ); /** * @brief Add a root element to the document * @param type the type of the node to add to the root of the document. @@ -239,7 +239,7 @@ class xmlDocument * @return true if the file has successfuly been saved * @return false otherwise */ - bool saveFile( const char * path ) const; + bool saveFile( string const & path ) const; /** * @brief Function to add xml nodes from included files. diff --git a/src/coreComponents/fileIO/vtk/VTKPVDWriter.cpp b/src/coreComponents/fileIO/vtk/VTKPVDWriter.cpp index e84aaced61c..a98d2391735 100644 --- a/src/coreComponents/fileIO/vtk/VTKPVDWriter.cpp +++ b/src/coreComponents/fileIO/vtk/VTKPVDWriter.cpp @@ -42,7 +42,7 @@ void VTKPVDWriter::setFileName( string fileName ) void VTKPVDWriter::save() const { - m_pvdFile.saveFile( m_fileName.c_str() ); + m_pvdFile.saveFile( m_fileName ); } void VTKPVDWriter::addData( real64 time, string const & filePath ) const diff --git a/src/coreComponents/fileIO/vtk/VTKVTMWriter.cpp b/src/coreComponents/fileIO/vtk/VTKVTMWriter.cpp index 15d391b1c09..8ecdd5091ee 100644 --- a/src/coreComponents/fileIO/vtk/VTKVTMWriter.cpp +++ b/src/coreComponents/fileIO/vtk/VTKVTMWriter.cpp @@ -39,7 +39,7 @@ VTKVTMWriter::VTKVTMWriter( string filePath ) void VTKVTMWriter::write() const { - m_document.saveFile( m_filePath.c_str() ); + m_document.saveFile( m_filePath ); } void VTKVTMWriter::addDataSet( std::vector< string > const & blockPath, diff --git a/src/coreComponents/mainInterface/ProblemManager.cpp b/src/coreComponents/mainInterface/ProblemManager.cpp index 7dc455fcaff..3f318b30dbd 100644 --- a/src/coreComponents/mainInterface/ProblemManager.cpp +++ b/src/coreComponents/mainInterface/ProblemManager.cpp @@ -385,7 +385,7 @@ void ProblemManager::parseInputFile() // Load preprocessed xml file xmlWrapper::xmlDocument xmlDocument; - xmlWrapper::xmlResult const xmlResult = xmlDocument.loadFile( inputFileName.c_str(), true ); + xmlWrapper::xmlResult const xmlResult = xmlDocument.loadFile( inputFileName, true ); GEOS_THROW_IF( !xmlResult, GEOS_FMT( "Errors found while parsing XML file {}\nDescription: {}\nOffset: {}", inputFileName, xmlResult.description(), xmlResult.offset ), InputError ); diff --git a/src/coreComponents/schema/schemaUtilities.cpp b/src/coreComponents/schema/schemaUtilities.cpp index d59c6eec925..524da722933 100644 --- a/src/coreComponents/schema/schemaUtilities.cpp +++ b/src/coreComponents/schema/schemaUtilities.cpp @@ -52,7 +52,7 @@ void ConvertDocumentationToSchema( string const & fname, "; xmlWrapper::xmlDocument schemaTree; - schemaTree.loadString( schemaBase.c_str() ); + schemaTree.loadString( schemaBase ); xmlWrapper::xmlNode schemaRoot = schemaTree.getChild( "xsd:schema" ); // Build the simple schema types @@ -65,7 +65,7 @@ void ConvertDocumentationToSchema( string const & fname, // Write the schema to file GEOS_LOG_RANK_0( " Saving file" ); - schemaTree.saveFile( fname.c_str()); + schemaTree.saveFile( fname ); GEOS_LOG_RANK_0( " Done!" ); } diff --git a/src/coreComponents/unitTests/xmlTests/testXMLFile.cpp b/src/coreComponents/unitTests/xmlTests/testXMLFile.cpp index a39d0fcc479..49ad4a301dd 100644 --- a/src/coreComponents/unitTests/xmlTests/testXMLFile.cpp +++ b/src/coreComponents/unitTests/xmlTests/testXMLFile.cpp @@ -167,7 +167,7 @@ TEST( testXML, testXMLFileLines ) problemManager.parseCommandLineInput(); Group & commandLine = problemManager.getGroup( problemManager.groupKeys.commandLine ); string const & inputFileName = commandLine.getReference< string >( problemManager.viewKeys.inputFileName ); - xmlDoc.loadFile( inputFileName.c_str(), true ); + xmlDoc.loadFile( inputFileName, true ); problemManager.parseXMLDocument( xmlDoc ); } From d5ab61ab7ce285dece8af858f5c0de5e59d7b464 Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Fri, 7 Jul 2023 17:28:03 +0200 Subject: [PATCH 50/85] WIP: more wrapping (xmlNode+xmlAttribute) --- .../dataRepository/ConduitRestart.cpp | 2 +- .../dataRepository/DataContext.cpp | 4 +- src/coreComponents/dataRepository/Group.cpp | 8 +- src/coreComponents/dataRepository/Wrapper.hpp | 2 +- .../dataRepository/WrapperBase.cpp | 10 +- .../dataRepository/xmlWrapper.cpp | 25 +- .../dataRepository/xmlWrapper.hpp | 263 +++++++++++++++++- .../fileIO/vtk/VTKPVDWriter.cpp | 18 +- .../fileIO/vtk/VTKVTMWriter.cpp | 18 +- .../mainInterface/ProblemManager.cpp | 16 +- .../mesh/ElementRegionManager.cpp | 6 +- src/coreComponents/schema/schemaUtilities.cpp | 68 ++--- .../fluidFlowTests/testCompFlowUtils.hpp | 4 +- .../fluidFlowTests/testSingleFlowUtils.hpp | 4 +- .../testDofManagerUtils.hpp | 2 +- .../meshTests/testMeshGeneration.cpp | 2 +- .../testConformingVirtualElementOrder1.cpp | 4 +- .../unitTests/xmlTests/testXMLFile.cpp | 2 +- 18 files changed, 350 insertions(+), 108 deletions(-) diff --git a/src/coreComponents/dataRepository/ConduitRestart.cpp b/src/coreComponents/dataRepository/ConduitRestart.cpp index 8a109807d01..6a27b23bcaf 100644 --- a/src/coreComponents/dataRepository/ConduitRestart.cpp +++ b/src/coreComponents/dataRepository/ConduitRestart.cpp @@ -64,7 +64,7 @@ string readRootNode( string const & rootPath ) conduit::Node node; conduit::relay::io::load( rootPath + ".root", "hdf5", node ); - int const nFiles = node.child( "number_of_files" ).value(); + int const nFiles = node.getChild( "number_of_files" ).value(); GEOS_THROW_IF_NE( nFiles, MpiWrapper::commSize(), InputError ); string const filePattern = node.fetch_existing( "file_pattern" ).as_string(); diff --git a/src/coreComponents/dataRepository/DataContext.cpp b/src/coreComponents/dataRepository/DataContext.cpp index b687de8830b..9d6145655af 100644 --- a/src/coreComponents/dataRepository/DataContext.cpp +++ b/src/coreComponents/dataRepository/DataContext.cpp @@ -41,10 +41,10 @@ std::ostream & operator<<( std::ostream & os, DataContext const & sc ) */ string getNodeName( xmlWrapper::xmlNode const & node ) { - xmlWrapper::xmlAttribute const nameAtt = node.attribute( "name" ); + xmlWrapper::xmlAttribute const nameAtt = node.getAttribute( "name" ); if( !nameAtt.empty() ) { - return string( node.attribute( "name" ).value() ); + return string( node.getAttribute( "name" ).value() ); } else { diff --git a/src/coreComponents/dataRepository/Group.cpp b/src/coreComponents/dataRepository/Group.cpp index 4373abe653b..b5655eb1e51 100644 --- a/src/coreComponents/dataRepository/Group.cpp +++ b/src/coreComponents/dataRepository/Group.cpp @@ -142,7 +142,7 @@ void Group::processInputFileRecursive( xmlWrapper::xmlDocument & xmlDocument, // Handle the case where the node was imported from a different input file // Set the path prefix to make sure all relative Path variables are interpreted correctly string const oldPrefix = Path::pathPrefix(); - xmlWrapper::xmlAttribute filePath = targetNode.attribute( xmlWrapper::filePathString ); + xmlWrapper::xmlAttribute filePath = targetNode.getAttribute( xmlWrapper::filePathString ); if( filePath ) { Path::pathPrefix() = getAbsolutePath( splitPath( filePath.value() ).first ); @@ -153,7 +153,7 @@ void Group::processInputFileRecursive( xmlWrapper::xmlDocument & xmlDocument, for( xmlWrapper::xmlNode childNode : targetNode.children() ) { // Get the child tag and name - string childName = childNode.attribute( "name" ).value(); + string childName = childNode.getAttribute( "name" ).value(); if( childName.empty() ) { childName = childNode.name(); @@ -210,14 +210,14 @@ void Group::processInputFile( xmlWrapper::xmlDocument const & xmlDocument, for( xmlWrapper::xmlAttribute attribute : targetNode.attributes() ) { - string const attributeName = attribute.name(); + string const attributeName = attribute.getName(); if( !xmlWrapper::isFileMetadataAttribute( attributeName ) ) { GEOS_THROW_IF( processedAttributes.count( attributeName ) == 0, GEOS_FMT( "XML Node at '{}' with name={} contains unused attribute '{}'.\n" "Valid attributes are:\n{}\nFor more details, please refer to documentation at:\n" "http://geosx-geosx.readthedocs-hosted.com/en/latest/docs/sphinx/userGuide/Index.html", - targetNode.path(), m_dataContext->toString(), attributeName, + targetNode.getPath(), m_dataContext->toString(), attributeName, dumpInputOptions() ), InputError ); } diff --git a/src/coreComponents/dataRepository/Wrapper.hpp b/src/coreComponents/dataRepository/Wrapper.hpp index dcb413c6fe9..1cd3e10589e 100644 --- a/src/coreComponents/dataRepository/Wrapper.hpp +++ b/src/coreComponents/dataRepository/Wrapper.hpp @@ -624,7 +624,7 @@ class Wrapper final : public WrapperBase GEOS_FMT( "XML Node {} ({}) with name={} is missing required attribute '{}'." "Available options are:\n {}\n For more details, please refer to documentation at:\n" "http://geosx-geosx.readthedocs-hosted.com/en/latest/docs/sphinx/userGuide/Index.html", - targetNode.name(), nodePos.toString(), targetNode.attribute( "name" ).value(), + targetNode.name(), nodePos.toString(), targetNode.getAttribute( "name" ).value(), getName(), dumpInputOptions( true ) ), InputError ); } diff --git a/src/coreComponents/dataRepository/WrapperBase.cpp b/src/coreComponents/dataRepository/WrapperBase.cpp index 34c4d642bd5..2c4884e11b9 100644 --- a/src/coreComponents/dataRepository/WrapperBase.cpp +++ b/src/coreComponents/dataRepository/WrapperBase.cpp @@ -109,9 +109,9 @@ int WrapperBase::setTotalviewDisplay() const void WrapperBase::createDataContext( xmlWrapper::xmlNode const & targetNode, xmlWrapper::xmlNodePos const & nodePos ) { - xmlWrapper::xmlAttribute att = targetNode.attribute( m_name.c_str() ); + xmlWrapper::xmlAttribute att = targetNode.getAttribute( m_name.c_str() ); xmlWrapper::xmlAttributePos attPos = nodePos.getAttributeLine( m_name ); - if( nodePos.isFound() && attPos.isFound() && !att.empty() ) + if( nodePos.isFound() && attPos.isFound() && !att.isEmpty() ) { m_dataContext = std::make_unique< DataFileContext >( targetNode, att, attPos ); } @@ -121,8 +121,8 @@ void WrapperBase::processInputException( std::exception const & ex, xmlWrapper::xmlNode const & targetNode, xmlWrapper::xmlNodePos const & nodePos ) const { - xmlWrapper::xmlAttribute const & attribute = targetNode.attribute( getName().c_str() ); - string const inputStr = string( attribute.value() ); + xmlWrapper::xmlAttribute const & attribute = targetNode.getAttribute( getName().c_str() ); + string const inputStr = string( attribute.getValue() ); xmlWrapper::xmlAttributePos const attPos = nodePos.getAttributeLine( getName() ); std::ostringstream oss; string const exStr = ex.what(); @@ -136,7 +136,7 @@ void WrapperBase::processInputException( std::exception const & ex, } else { - oss << targetNode.path() << " (name=" << targetNode.attribute( "name" ).value() << ")/" << getName(); + oss << targetNode.path() << " (name=" << targetNode.getAttribute( "name" ).getValue() << ")/" << getName(); } oss << "\n***** Input value: '" << inputStr << '\''; oss << ( exStr[0]=='\n' ? exStr : "'\n" + exStr ); diff --git a/src/coreComponents/dataRepository/xmlWrapper.cpp b/src/coreComponents/dataRepository/xmlWrapper.cpp index 218746b0fd1..228e55d0dd2 100644 --- a/src/coreComponents/dataRepository/xmlWrapper.cpp +++ b/src/coreComponents/dataRepository/xmlWrapper.cpp @@ -73,6 +73,7 @@ template void stringToInputVariable( Tensor< real32, 3 > & target, string const template void stringToInputVariable( Tensor< real64, 3 > & target, string const & inputValue ); template void stringToInputVariable( Tensor< real64, 6 > & target, string const & inputValue ); + /** * @brief Adds the filePath and character offset info on the node in filePathString * and charOffsetString attributes. This function allow to keep track of the source @@ -84,8 +85,8 @@ void addNodeFileInfo( xmlNode targetNode, string const & filePath ) { // we keep the file path and the character offset on each node so we keep track of these // informations, even if the nodes are manipulated within the xml hierarchy. - targetNode.append_attribute( filePathString ).set_value( filePath.c_str() ); - targetNode.append_attribute( charOffsetString ).set_value( targetNode.offset_debug() ); + targetNode.appendAttribute( filePathString ).set_value( filePath.c_str() ); + targetNode.appendAttribute( charOffsetString ).set_value( targetNode.offset_debug() ); for( xmlNode subNode : targetNode.children() ) { @@ -96,13 +97,13 @@ void addNodeFileInfo( xmlNode targetNode, string const & filePath ) * @brief Returns true if the addNodeFileInfo() command has been called of the specified node. */ bool xmlDocument::hasNodeFileInfo() const -{ return !getFirstChild().attribute( filePathString ).empty(); } +{ return !getFirstChild().getAttribute( filePathString ).empty(); } void xmlDocument::addIncludedXML( xmlNode & targetNode, int const level ) { GEOS_THROW_IF( level > 100, "XML include level limit reached, please check input for include loops", InputError ); - string const currentFilePath = targetNode.attribute( filePathString ).value(); + string const currentFilePath = targetNode.getAttribute( filePathString ).value(); // Schema currently allows a single unique , but a non-validating file may include multiple for( xmlNode includedNode : targetNode.children( includedListTag ) ) @@ -115,7 +116,7 @@ void xmlDocument::addIncludedXML( xmlNode & targetNode, int const level ) GEOS_THROW_IF_NE_MSG( string( fileNode.name() ), includedFileTag, GEOS_FMT( "<{}> must only contain <{}> tags", includedListTag, includedFileTag ), InputError ); - xmlAttribute const nameAttr = fileNode.attribute( "name" ); + xmlAttribute const nameAttr = fileNode.getAttribute( "name" ); string const fileName = nameAttr.value(); GEOS_THROW_IF( !nameAttr || fileName.empty(), GEOS_FMT( "<{}> tag must have a non-empty 'name' attribute", includedFileTag ), @@ -177,12 +178,12 @@ string buildMultipleInputXML( string_array const & inputFileList, { xmlWrapper::xmlDocument compositeTree; xmlWrapper::xmlNode compositeRoot = compositeTree.appendChild( dataRepository::keys::ProblemManager ); - xmlWrapper::xmlNode includedRoot = compositeRoot.append_child( includedListTag ); + xmlWrapper::xmlNode includedRoot = compositeRoot.appendChild( includedListTag ); for( auto & fileName: inputFileList ) { - xmlWrapper::xmlNode fileNode = includedRoot.append_child( includedFileTag ); - fileNode.append_attribute( "name" ) = fileName.c_str(); + xmlWrapper::xmlNode fileNode = includedRoot.appendChild( includedFileTag ); + fileNode.appendAttribute( "name" ) = fileName.c_str(); } compositeTree.saveFile( inputFileName ); @@ -264,7 +265,7 @@ xmlResult xmlDocument::loadBuffer( const void * contents, size_t size, bool load xmlNode xmlDocument::appendChild( string const & name ) { return pugiDocument.append_child( name.c_str() ); } -xmlNode xmlDocument::appendChild( xmlTypes type ) +xmlNode xmlDocument::appendChild( xmlNodeType type ) { return pugiDocument.append_child( type ); } bool xmlDocument::saveFile( string const & path ) const @@ -275,6 +276,8 @@ string const & xmlDocument::getFilePath() const xmlNode xmlDocument::getFirstChild() const { return pugiDocument.first_child(); } +xmlNode xmlDocument::getRoot() const +{ return pugiDocument.root(); } xmlNode xmlDocument::getChild( string const & name ) const { return pugiDocument.child( name.c_str() ); } @@ -296,8 +299,8 @@ xmlNodePos xmlDocument::getNodePosition( xmlNode const & node ) const size_t line = npos; size_t offsetInLine = npos; size_t offset = npos; - xmlAttribute filePathAtt = node.attribute( filePathString ); - xmlAttribute charOffsetAtt = node.attribute( charOffsetString ); + xmlAttribute filePathAtt = node.getAttribute( filePathString ); + xmlAttribute charOffsetAtt = node.getAttribute( charOffsetString ); string filePath; if( filePathAtt && charOffsetAtt ) diff --git a/src/coreComponents/dataRepository/xmlWrapper.hpp b/src/coreComponents/dataRepository/xmlWrapper.hpp index 80e684a7dd0..ff59fe36c71 100644 --- a/src/coreComponents/dataRepository/xmlWrapper.hpp +++ b/src/coreComponents/dataRepository/xmlWrapper.hpp @@ -50,18 +50,160 @@ namespace xmlWrapper /// Alias for the type of the result from an xml parse attempt. using xmlResult = pugi::xml_parse_result; -/// Alias for the type of an xml node. -/// An xmlNode behave as a pointer object: passing it by value to a function which modify it -/// will modify the original xmlNode object. -using xmlNode = pugi::xml_node; +/// Alias for the type variant of an xml node. +using xmlNodeType = pugi::xml_node_type; -/// Alias for the type of an xml attribute. +/// Wrapper class for the type of an xml attribute. /// An xmlAttribute behave as a pointer object: passing it by value to a function which modify it /// will modify the original xmlAttribute object. -using xmlAttribute = pugi::xml_attribute; +class xmlAttribute +{ +public: + /** + * @brief Construct a new empty xml Attribute object + */ + inline xmlAttribute(); -/// Alias for the type variant of an xml node. -using xmlTypes = pugi::xml_node_type; + /** + * @param r another xmlAttribute object + * @return true if the referenced underlying attribute object is the same one, false otherwise. + */ + inline bool operator==( const xmlAttribute & r ) const; + /** + * @param r another xmlAttribute object + * @return true if the referenced underlying attribute object is a different one, false otherwise. + */ + inline bool operator!=( const xmlAttribute & r ) const; + /** + * @return the same result as isEmpty() + */ + inline operator bool() const; + + /** + * @return true if the attribute is empty, false otherwise + */ + inline bool isEmpty() const; + + /** + * @return the attribute name, or "" if empty. + * @todo c++17: change return type to string_view + */ + inline string getName() const; + /** + * @brief Set the tag name of the node + * @param name the new name + */ + inline void setName( const string & name ); + + /** + * @return the attribute value, or "" if empty. + * @todo c++17: change return type to string_view + */ + inline string getValue() const; + /** + * @brief Set the value of the node + * @param value the new value + */ + inline void setValue( const string & value ); + + /** + * @return the next attribute in the containing node, or an empty one if there is none + */ + inline xmlAttribute nextAttribute() const; + /** + * @return the next attribute in the containing node, or an empty one if there is none + */ + inline xmlAttribute previousAttribute() const; + +private: + pugi::xml_attribute pugiAtt; + + /** + * @brief Construct a new reference to the same attribute object + */ + inline explicit xmlAttribute( pugi::xml_attribute const & attribute ); +}; + +/// Wrapper class for the type of an xml node. +/// An xmlNode behave as a pointer object: passing it by value to a function which modify it +/// will modify the original xmlNode object. +class xmlNode +{ +public: + /** + * @brief Construct a new empty xml Node object + */ + inline xmlNode(); + + /** + * @param r another xmlNode object + * @return true if the referenced underlying node object is the same one, false otherwise. + */ + inline bool operator==( const xmlNode & r ) const; + /** + * @param r another xmlNode object + * @return true if the referenced underlying node object is a different one, false otherwise. + */ + inline bool operator!=( const xmlNode & r ) const; + /** + * @return the same result as isEmpty() + */ + inline operator bool() const; + + /** + * @return true if the node is empty, false otherwise + */ + inline bool isEmpty() const; + + /** + * @return the node tag name, or "" if empty. + * @todo c++17: change return type to string_view + */ + inline string getName() const; + /** + * @brief Set the tag name of the node + * @param name the new name + */ + inline void setName( const string & name ); + + /** + * @return the node value, or "" if empty. + * @todo c++17: change return type to string_view + */ + inline string getValue() const; + /** + * @brief Set the value of the node + * @param value the new value + */ + inline void setValue( const string & value ); + + inline xmlNode getParent() const; + inline xmlNode getChild( string const & name ) const; + inline xmlAttribute getAttribute( string const & name ) const; + + inline xmlAttribute appendAttribute( string const & name ); + inline xmlNode appendChild( xmlNodeType type = node_element ); + inline xmlNode appendChild( string const & name ); + inline bool removeAttribute( const xmlAttribute & att ); + inline bool removeAttribute( string const & name ); + inline bool removeChild( const xmlNode & node ); + inline bool removeChild( string const & name ); + + inline xmlNodeType getType() const; + inline string getPath( char delimiter = '/' ) const; + /** + * @return the character offset where this node is in the xmlDocument + */ + inline ptrdiff_t getOffset() const; + +private: + pugi::xml_node pugiNode; + + /** + * @brief Construct a new reference to the same node object + */ + inline explicit xmlNode( pugi::xml_node const & node ); +}; class xmlDocument; @@ -151,6 +293,10 @@ class xmlDocument * @return the first child of this document (typically in GEOS, the node) */ xmlNode getFirstChild() const; + /** + * @return the DOM parent node of this document (which, typically in GEOS, contains the node and the declaration node) + */ + xmlNode getRoot() const; /** * @return a child with the specified name * @param name the tag name of the node to find @@ -231,7 +377,7 @@ class xmlDocument * As an exemple, node_declaration is useful to add the "" node. * @return the added node */ - xmlNode appendChild( xmlTypes type = xmlTypes::node_element ); + xmlNode appendChild( xmlNodeType type = xmlNodeType::node_element ); /** * @brief Save the XML to a file @@ -432,7 +578,7 @@ readAttributeAsType( T & rval, xmlNode const & targetNode, T_DEF const & defVal ) { - xmlAttribute const xmlatt = targetNode.attribute( name.c_str() ); + xmlAttribute const xmlatt = targetNode.getAttribute( name.c_str() ); if( !xmlatt.empty() ) { // parse the string/attribute into a value @@ -463,7 +609,7 @@ readAttributeAsType( T & rval, xmlNode const & targetNode, bool const required ) { - xmlAttribute const xmlatt = targetNode.attribute( name.c_str() ); + xmlAttribute const xmlatt = targetNode.getAttribute( name.c_str() ); bool const success = !(xmlatt.empty() && required); @@ -514,7 +660,100 @@ readAttributeAsType( T & rval, ///@} -} +inline xmlAttribute::xmlAttribute() +{} +inline xmlAttribute::xmlAttribute( pugi::xml_attribute const & attribute ): + pugiAtt( attribute.internal_object() ) +{} + +inline bool xmlAttribute::operator==( const xmlAttribute & r ) const +{ return pugiAtt.operator==(); } +inline bool xmlAttribute::operator!=( const xmlAttribute & r ) const +{ return pugiAtt.operator!=(); } +inline bool xmlAttribute::operator bool() const +{ return bool(pugiAtt); } + +inline bool xmlAttribute::isEmpty() const +{ return pugiAtt.empty(); } + +inline string xmlAttribute::getName() const +{ return pugiAtt.name(); } +inline void xmlAttribute::setName( const string & name ) +{ pugiAtt.set_name( name ); } + +inline string xmlAttribute::getValue() const +{ return pugiAtt.value(); } +inline void xmlAttribute::setValue( const string & value ) +{ pugiAtt.set_value( value ); } + +inline xmlAttribute xmlAttribute::next_attribute() +{ xmlAttribute( pugiAtt.next_attribute() ); } +inline xmlAttribute xmlAttribute::previous_attribute() +{ xmlAttribute( pugiAtt.previous_attribute() ); } + + +inline xmlNode::xmlNode(): + pugiNode() +{} +inline xmlNode::xmlNode( pugi::xml_node const & node ): + pugiNode( node.internal_object() ) +{} + +inline bool xmlNode::operator==( const xmlNode & r ) const +{ return pugiNode.operator==(); } +inline bool xmlNode::operator!=( const xmlNode & r ) const +{ return pugiNode.operator!=(); } +inline bool xmlAttribute::operator bool() const +{ return bool(pugiNode); } + +inline bool xmlNode::isEmpty() const +{ return pugiNode.empty(); } + +inline string xmlNode::getName() const +{ return pugiNode.name(); } +inline void xmlNode::setName( const string & name ) +{ pugiNode.set_name( name ); } + +inline void xmlNode::setValue( const string & value ) +{ pugiNode.set_value( value ); } +inline string xmlNode::getValue() const +{ return pugiNode.value(); } + +inline iterator xmlNode::begin() const +{ return pugiNode.begin(); } +inline iterator xmlNode::end() const +{ return pugiNode.end(); } +inline xmlNode xmlNode::getParent() const +{ return xmlNode( pugiNode.parent()); } +inline xmlNode xmlNode::getChild( string const & name ) const +{ return xmlNode( pugiNode.child( name )); } +inline xmlAttribute xmlNode::getAttribute( string const & name ) const +{ return xmlAttribute( pugiNode.attribute( name )); } + +inline xmlAttribute xmlNode::appendAttribute( string const & name ) +{ return xmlAttribute( pugiNode.append_attribute( name )); } +inline xmlNode xmlNode::appendChild( xmlNodeType type = node_element ) +{ return xmlNode( pugiNode.append_child()); } +inline xmlNode xmlNode::appendChild( string const & name ) +{ return xmlNode( pugiNode.append_child( name )); } +inline bool xmlNode::removeAttribute( const xmlAttribute & att ) +{ return pugiNode.remove_attribute( att ); } +inline bool xmlNode::removeAttribute( string const & name ) +{ return pugiNode.remove_attribute( name ); } +inline bool xmlNode::removeChild( const xmlNode & node ) +{ return pugiNode.remove_child( node ); } +inline bool xmlNode::removeChild( string const & name ) +{ return pugiNode.remove_child( name ); } + +inline xmlNodeType xmlNode::getType() const +{ return pugiNode.getType(); } +inline string xmlNode::getPath( char delimiter = '/' ) const +{ return pugiNode.getPath( delimiter ); } +inline ptrdiff_t xmlNode::getOffset() const +{ return pugiNode.getOffset(); } + + +} /* namespace xmlWrapper */ } /* namespace geos */ diff --git a/src/coreComponents/fileIO/vtk/VTKPVDWriter.cpp b/src/coreComponents/fileIO/vtk/VTKPVDWriter.cpp index a98d2391735..bc5ea174bea 100644 --- a/src/coreComponents/fileIO/vtk/VTKPVDWriter.cpp +++ b/src/coreComponents/fileIO/vtk/VTKPVDWriter.cpp @@ -25,14 +25,14 @@ VTKPVDWriter::VTKPVDWriter( string fileName ): { // Declaration of XML version auto declarationNode = m_pvdFile.appendChild( pugi::node_declaration ); - declarationNode.append_attribute( "version" ) = "1.0"; + declarationNode.appendAttribute( "version" ) = "1.0"; // Declaration of the node VTKFile auto vtkFileNode = m_pvdFile.appendChild( "VTKFile" ); - vtkFileNode.append_attribute( "type" ) = "Collection"; - vtkFileNode.append_attribute( "version" ) = "0.1"; + vtkFileNode.appendAttribute( "type" ) = "Collection"; + vtkFileNode.appendAttribute( "version" ) = "0.1"; - vtkFileNode.append_child( "Collection" ); + vtkFileNode.appendChild( "Collection" ); } void VTKPVDWriter::setFileName( string fileName ) @@ -47,15 +47,15 @@ void VTKPVDWriter::save() const void VTKPVDWriter::addData( real64 time, string const & filePath ) const { - auto collectionNode = m_pvdFile.getChild( "VTKFile" ).child( "Collection" ); - auto dataSetNode = collectionNode.append_child( "DataSet" ); - dataSetNode.append_attribute( "timestep" ) = time; - dataSetNode.append_attribute( "file" ) = filePath.c_str(); + auto collectionNode = m_pvdFile.getChild( "VTKFile" ).getChild( "Collection" ); + auto dataSetNode = collectionNode.appendChild( "DataSet" ); + dataSetNode.appendAttribute( "timestep" ) = time; + dataSetNode.appendAttribute( "file" ) = filePath.c_str(); } void VTKPVDWriter::reinitData() const { - auto collectionNode = m_pvdFile.getChild( "VTKFile" ).child( "Collection" ); + auto collectionNode = m_pvdFile.getChild( "VTKFile" ).getChild( "Collection" ); while( collectionNode.remove_child( "DataSet" ) ) {} diff --git a/src/coreComponents/fileIO/vtk/VTKVTMWriter.cpp b/src/coreComponents/fileIO/vtk/VTKVTMWriter.cpp index 8ecdd5091ee..3b6437f8bdf 100644 --- a/src/coreComponents/fileIO/vtk/VTKVTMWriter.cpp +++ b/src/coreComponents/fileIO/vtk/VTKVTMWriter.cpp @@ -27,14 +27,14 @@ VTKVTMWriter::VTKVTMWriter( string filePath ) { // Declaration of XML version auto declarationNode = m_document.appendChild( pugi::node_declaration ); - declarationNode.append_attribute( "version" ) = "1.0"; + declarationNode.appendAttribute( "version" ) = "1.0"; // Declaration of the node VTKFile auto vtkFileNode = m_document.appendChild( "VTKFile" ); - vtkFileNode.append_attribute( "type" ) = "vtkMultiBlockDataSet"; - vtkFileNode.append_attribute( "version" ) = "1.0"; + vtkFileNode.appendAttribute( "type" ) = "vtkMultiBlockDataSet"; + vtkFileNode.appendAttribute( "version" ) = "1.0"; - m_blockRoot = vtkFileNode.append_child( "vtkMultiBlockDataSet" ); + m_blockRoot = vtkFileNode.appendChild( "vtkMultiBlockDataSet" ); } void VTKVTMWriter::write() const @@ -56,13 +56,13 @@ void VTKVTMWriter::addDataSet( std::vector< string > const & blockPath, } else { - node = node.append_child( "Block" ); - node.append_attribute( "name" ) = blockName.c_str(); + node = node.appendChild( "Block" ); + node.appendAttribute( "name" ) = blockName.c_str(); } } - node = node.append_child( "DataSet" ); - node.append_attribute( "name" ) = dataSetName.c_str(); - node.append_attribute( "file" ) = filePath.c_str(); + node = node.appendChild( "DataSet" ); + node.appendAttribute( "name" ) = dataSetName.c_str(); + node.appendAttribute( "file" ) = filePath.c_str(); } } // namespace vtk diff --git a/src/coreComponents/mainInterface/ProblemManager.cpp b/src/coreComponents/mainInterface/ProblemManager.cpp index 3f318b30dbd..2731d095478 100644 --- a/src/coreComponents/mainInterface/ProblemManager.cpp +++ b/src/coreComponents/mainInterface/ProblemManager.cpp @@ -284,12 +284,12 @@ void ProblemManager::setSchemaDeviations( xmlWrapper::xmlNode schemaRoot, xmlWrapper::xmlNode schemaParent, integer documentationType ) { - xmlWrapper::xmlNode targetChoiceNode = schemaParent.child( "xsd:choice" ); - if( targetChoiceNode.empty() ) + xmlWrapper::xmlNode targetChoiceNode = schemaParent.getChild( "xsd:choice" ); + if( targetChoiceNode.isEmpty() ) { targetChoiceNode = schemaParent.prepend_child( "xsd:choice" ); - targetChoiceNode.append_attribute( "minOccurs" ) = "0"; - targetChoiceNode.append_attribute( "maxOccurs" ) = "unbounded"; + targetChoiceNode.appendAttribute( "minOccurs" ) = "0"; + targetChoiceNode.appendAttribute( "maxOccurs" ) = "unbounded"; } // These objects are handled differently during the xml read step, @@ -417,21 +417,21 @@ void ProblemManager::parseXMLDocument( xmlWrapper::xmlDocument & xmlDocument ) { DomainPartition & domain = getDomainPartition(); ConstitutiveManager & constitutiveManager = domain.getGroup< ConstitutiveManager >( groupKeys.constitutiveManager ); - xmlWrapper::xmlNode topLevelNode = xmlProblemNode.child( constitutiveManager.getName().c_str()); + xmlWrapper::xmlNode topLevelNode = xmlProblemNode.getChild( constitutiveManager.getName().c_str()); constitutiveManager.processInputFileRecursive( xmlDocument, topLevelNode ); // Open mesh levels MeshManager & meshManager = this->getGroup< MeshManager >( groupKeys.meshManager ); meshManager.generateMeshLevels( domain ); Group & meshBodies = domain.getMeshBodies(); - xmlWrapper::xmlNode elementRegionsNode = xmlProblemNode.child( MeshLevel::groupStructKeys::elemManagerString() ); + xmlWrapper::xmlNode elementRegionsNode = xmlProblemNode.getChild( MeshLevel::groupStructKeys::elemManagerString() ); for( xmlWrapper::xmlNode regionNode : elementRegionsNode.children() ) { - string const regionName = regionNode.attribute( "name" ).value(); + string const regionName = regionNode.attrgetAttributeibute( "name" ).value(); string const regionMeshBodyName = ElementRegionBase::verifyMeshBodyName( meshBodies, - regionNode.attribute( "meshBody" ).value() ); + regionNode.getAttribute( "meshBody" ).value() ); MeshBody & meshBody = domain.getMeshBody( regionMeshBodyName ); meshBody.forMeshLevels( [&]( MeshLevel & meshLevel ) diff --git a/src/coreComponents/mesh/ElementRegionManager.cpp b/src/coreComponents/mesh/ElementRegionManager.cpp index 9106cae830d..22aa6847950 100644 --- a/src/coreComponents/mesh/ElementRegionManager.cpp +++ b/src/coreComponents/mesh/ElementRegionManager.cpp @@ -97,12 +97,12 @@ void ElementRegionManager::setSchemaDeviations( xmlWrapper::xmlNode schemaRoot, xmlWrapper::xmlNode schemaParent, integer documentationType ) { - xmlWrapper::xmlNode targetChoiceNode = schemaParent.child( "xsd:choice" ); + xmlWrapper::xmlNode targetChoiceNode = schemaParent.getChild( "xsd:choice" ); if( targetChoiceNode.empty() ) { targetChoiceNode = schemaParent.prepend_child( "xsd:choice" ); - targetChoiceNode.append_attribute( "minOccurs" ) = "0"; - targetChoiceNode.append_attribute( "maxOccurs" ) = "unbounded"; + targetChoiceNode.appendAttribute( "minOccurs" ) = "0"; + targetChoiceNode.appendAttribute( "maxOccurs" ) = "unbounded"; } std::set< string > names; diff --git a/src/coreComponents/schema/schemaUtilities.cpp b/src/coreComponents/schema/schemaUtilities.cpp index 524da722933..61eb928c4ab 100644 --- a/src/coreComponents/schema/schemaUtilities.cpp +++ b/src/coreComponents/schema/schemaUtilities.cpp @@ -76,22 +76,22 @@ void AppendSimpleType( xmlWrapper::xmlNode & schemaRoot, { string const advanced_match_string = ".*[\\[\\]`$].*|"; - xmlWrapper::xmlNode newNode = schemaRoot.append_child( "xsd:simpleType" ); - newNode.append_attribute( "name" ) = name.c_str(); - xmlWrapper::xmlNode restrictionNode = newNode.append_child( "xsd:restriction" ); - restrictionNode.append_attribute( "base" ) = "xsd:string"; - xmlWrapper::xmlNode patternNode = restrictionNode.append_child( "xsd:pattern" ); + xmlWrapper::xmlNode newNode = schemaRoot.appendChild( "xsd:simpleType" ); + newNode.appendAttribute( "name" ) = name.c_str(); + xmlWrapper::xmlNode restrictionNode = newNode.appendChild( "xsd:restriction" ); + restrictionNode.appendAttribute( "base" ) = "xsd:string"; + xmlWrapper::xmlNode patternNode = restrictionNode.appendChild( "xsd:pattern" ); // Handle the default regex if( regex.empty() ) { GEOS_WARNING( "schema regex not defined for " << name ); - patternNode.append_attribute( "value" ) = "(?s).*"; + patternNode.appendAttribute( "value" ) = "(?s).*"; } else { string const patternString = advanced_match_string + regex; - patternNode.append_attribute( "value" ) = patternString.c_str(); + patternNode.appendAttribute( "value" ) = patternString.c_str(); } } @@ -122,38 +122,38 @@ void SchemaConstruction( Group & group, if( schemaParent.find_child_by_attribute( "xsd:element", "name", targetName.c_str()).empty()) { // Add the entries to the current and root nodes - xmlWrapper::xmlNode targetIncludeNode = schemaParent.append_child( "xsd:element" ); - targetIncludeNode.append_attribute( "name" ) = targetName.c_str(); - targetIncludeNode.append_attribute( "type" ) = typeName.c_str(); + xmlWrapper::xmlNode targetIncludeNode = schemaParent.appendChild( "xsd:element" ); + targetIncludeNode.appendAttribute( "name" ) = targetName.c_str(); + targetIncludeNode.appendAttribute( "type" ) = typeName.c_str(); // Add occurence conditions if((schemaType == InputFlags::REQUIRED_NONUNIQUE) || (schemaType == InputFlags::REQUIRED)) { - targetIncludeNode.append_attribute( "minOccurs" ) = "1"; + targetIncludeNode.appendAttribute( "minOccurs" ) = "1"; } if((schemaType == InputFlags::OPTIONAL) || (schemaType == InputFlags::REQUIRED)) { - targetIncludeNode.append_attribute( "maxOccurs" ) = "1"; + targetIncludeNode.appendAttribute( "maxOccurs" ) = "1"; } // Insert a new type into the root node if not present xmlWrapper::xmlNode targetTypeDefNode = schemaRoot.find_child_by_attribute( "xsd:complexType", "name", typeName.c_str()); if( targetTypeDefNode.empty()) { - targetTypeDefNode = schemaRoot.append_child( "xsd:complexType" ); - targetTypeDefNode.append_attribute( "name" ) = typeName.c_str(); + targetTypeDefNode = schemaRoot.appendChild( "xsd:complexType" ); + targetTypeDefNode.appendAttribute( "name" ) = typeName.c_str(); } // Add subgroups if( group.numSubGroups() > 0 ) { // Children are defined in a choice node - xmlWrapper::xmlNode targetChoiceNode = targetTypeDefNode.child( "xsd:choice" ); + xmlWrapper::xmlNode targetChoiceNode = targetTypeDefNode.getChild( "xsd:choice" ); if( targetChoiceNode.empty() ) { targetChoiceNode = targetTypeDefNode.prepend_child( "xsd:choice" ); - targetChoiceNode.append_attribute( "minOccurs" ) = "0"; - targetChoiceNode.append_attribute( "maxOccurs" ) = "unbounded"; + targetChoiceNode.appendAttribute( "minOccurs" ) = "0"; + targetChoiceNode.appendAttribute( "maxOccurs" ) = "unbounded"; } // Get a list of the subgroup names in alphabetic order @@ -175,13 +175,13 @@ void SchemaConstruction( Group & group, { // Enforce uniqueness of element names // Note: this must be done at the parent element level - xmlWrapper::xmlNode uniqueNameNode = targetIncludeNode.append_child( "xsd:unique" ); + xmlWrapper::xmlNode uniqueNameNode = targetIncludeNode.appendChild( "xsd:unique" ); string uniqueNameNodeStr = targetName + subName + "UniqueName"; - uniqueNameNode.append_attribute( "name" ) = uniqueNameNodeStr.c_str(); - xmlWrapper::xmlNode uniqueNameSelector = uniqueNameNode.append_child( "xsd:selector" ); - uniqueNameSelector.append_attribute( "xpath" ) = subName.c_str(); - xmlWrapper::xmlNode uniqueNameField = uniqueNameNode.append_child( "xsd:field" ); - uniqueNameField.append_attribute( "xpath" ) = "@name"; + uniqueNameNode.appendAttribute( "name" ) = uniqueNameNodeStr.c_str(); + xmlWrapper::xmlNode uniqueNameSelector = uniqueNameNode.appendChild( "xsd:selector" ); + uniqueNameSelector.appendAttribute( "xpath" ) = subName.c_str(); + xmlWrapper::xmlNode uniqueNameField = uniqueNameNode.appendChild( "xsd:field" ); + uniqueNameField.appendAttribute( "xpath" ) = "@name"; } SchemaConstruction( subGroup, schemaRoot, targetChoiceNode, documentationType ); @@ -232,14 +232,14 @@ void SchemaConstruction( Group & group, commentString += " => " + stringutilities::join( registrars.begin(), registrars.end(), ", " ); } - xmlWrapper::xmlNode commentNode = targetTypeDefNode.append_child( xmlWrapper::xmlTypes::node_comment ); + xmlWrapper::xmlNode commentNode = targetTypeDefNode.appendChild( xmlWrapper::xmlNodeType::node_comment ); commentNode.set_value( commentString.c_str()); // Write the valid schema attributes // Basic attributes - xmlWrapper::xmlNode attributeNode = targetTypeDefNode.append_child( "xsd:attribute" ); - attributeNode.append_attribute( "name" ) = attributeName.c_str(); + xmlWrapper::xmlNode attributeNode = targetTypeDefNode.appendChild( "xsd:attribute" ); + attributeNode.appendAttribute( "name" ) = attributeName.c_str(); string const wrappedTypeName = rtTypes::typeNames( wrapper.getTypeId() ); string const sanitizedName = std::regex_replace( wrappedTypeName, std::regex( "::" ), "_" ); @@ -248,7 +248,7 @@ void SchemaConstruction( Group & group, string const xmlSafeName = std::regex_replace( sanitizedName, std::regex( "std_(__cxx11_basic_)?string(<\\s*char,\\s*std_char_traits,\\s*std_allocator\\s*>)?" ), "string" ); GEOS_LOG_VAR( wrappedTypeName ); GEOS_LOG_VAR( xmlSafeName ); - attributeNode.append_attribute( "type" ) = xmlSafeName.c_str(); + attributeNode.appendAttribute( "type" ) = xmlSafeName.c_str(); // Check if the attribute has a previously unseen non-simple type with a custom validation regex if( schemaRoot.find_child_by_attribute( "xsd:simpleType", "name", xmlSafeName.c_str() ).empty() ) @@ -271,12 +271,12 @@ void SchemaConstruction( Group & group, { if( wrapper.hasDefaultValue() ) { - attributeNode.append_attribute( "default" ) = wrapper.getDefaultValueString().c_str(); + attributeNode.appendAttribute( "default" ) = wrapper.getDefaultValueString().c_str(); } } else if( documentationType == 0 ) { - attributeNode.append_attribute( "use" ) = "required"; + attributeNode.appendAttribute( "use" ) = "required"; } } } @@ -288,13 +288,13 @@ void SchemaConstruction( Group & group, // Only add this attribute if not present if( targetTypeDefNode.find_child_by_attribute( "xsd:attribute", "name", "name" ).empty()) { - xmlWrapper::xmlNode commentNode = targetTypeDefNode.append_child( xmlWrapper::xmlTypes::node_comment ); + xmlWrapper::xmlNode commentNode = targetTypeDefNode.appendChild( xmlWrapper::xmlNodeType::node_comment ); commentNode.set_value( "name => A name is required for any non-unique nodes" ); - xmlWrapper::xmlNode attributeNode = targetTypeDefNode.append_child( "xsd:attribute" ); - attributeNode.append_attribute( "name" ) = "name"; - attributeNode.append_attribute( "type" ) = "string"; - attributeNode.append_attribute( "use" ) = "required"; + xmlWrapper::xmlNode attributeNode = targetTypeDefNode.appendChild( "xsd:attribute" ); + attributeNode.appendAttribute( "name" ) = "name"; + attributeNode.appendAttribute( "type" ) = "string"; + attributeNode.appendAttribute( "use" ) = "required"; } } } diff --git a/src/coreComponents/unitTests/fluidFlowTests/testCompFlowUtils.hpp b/src/coreComponents/unitTests/fluidFlowTests/testCompFlowUtils.hpp index 5a192240183..55a06783d1d 100644 --- a/src/coreComponents/unitTests/fluidFlowTests/testCompFlowUtils.hpp +++ b/src/coreComponents/unitTests/fluidFlowTests/testCompFlowUtils.hpp @@ -205,14 +205,14 @@ void setupProblemFromXML( ProblemManager & problemManager, char const * const xm DomainPartition & domain = problemManager.getDomainPartition(); constitutive::ConstitutiveManager & constitutiveManager = domain.getConstitutiveManager(); - xmlWrapper::xmlNode topLevelNode = xmlProblemNode.child( constitutiveManager.getName().c_str()); + xmlWrapper::xmlNode topLevelNode = xmlProblemNode.getChild( constitutiveManager.getName().c_str()); constitutiveManager.processInputFileRecursive( xmlDocument, topLevelNode ); MeshManager & meshManager = problemManager.getGroup< MeshManager >( problemManager.groupKeys.meshManager ); meshManager.generateMeshLevels( domain ); ElementRegionManager & elementManager = domain.getMeshBody( 0 ).getBaseDiscretization().getElemManager(); - topLevelNode = xmlProblemNode.child( elementManager.getName().c_str()); + topLevelNode = xmlProblemNode.getChild( elementManager.getName().c_str()); elementManager.processInputFileRecursive( xmlDocument, topLevelNode ); problemManager.problemSetup(); diff --git a/src/coreComponents/unitTests/fluidFlowTests/testSingleFlowUtils.hpp b/src/coreComponents/unitTests/fluidFlowTests/testSingleFlowUtils.hpp index c3c58a63ea3..859c1e01da2 100644 --- a/src/coreComponents/unitTests/fluidFlowTests/testSingleFlowUtils.hpp +++ b/src/coreComponents/unitTests/fluidFlowTests/testSingleFlowUtils.hpp @@ -97,14 +97,14 @@ void setupProblemFromXML( ProblemManager & problemManager, char const * const xm DomainPartition & domain = problemManager.getDomainPartition(); constitutive::ConstitutiveManager & constitutiveManager = domain.getConstitutiveManager(); - xmlWrapper::xmlNode topLevelNode = xmlProblemNode.child( constitutiveManager.getName().c_str()); + xmlWrapper::xmlNode topLevelNode = xmlProblemNode.getChild( constitutiveManager.getName().c_str()); constitutiveManager.processInputFileRecursive( xmlDocument, topLevelNode ); MeshManager & meshManager = problemManager.getGroup< MeshManager >( problemManager.groupKeys.meshManager ); meshManager.generateMeshLevels( domain ); ElementRegionManager & elementManager = domain.getMeshBody( 0 ).getBaseDiscretization().getElemManager(); - topLevelNode = xmlProblemNode.child( elementManager.getName().c_str()); + topLevelNode = xmlProblemNode.getChild( elementManager.getName().c_str()); elementManager.processInputFileRecursive( xmlDocument, topLevelNode ); problemManager.problemSetup(); diff --git a/src/coreComponents/unitTests/linearAlgebraTests/testDofManagerUtils.hpp b/src/coreComponents/unitTests/linearAlgebraTests/testDofManagerUtils.hpp index 06f28489f5c..08b0ca70b77 100644 --- a/src/coreComponents/unitTests/linearAlgebraTests/testDofManagerUtils.hpp +++ b/src/coreComponents/unitTests/linearAlgebraTests/testDofManagerUtils.hpp @@ -61,7 +61,7 @@ void setupProblemFromXML( ProblemManager * const problemManager, char const * co meshManager.generateMeshLevels( domain ); ElementRegionManager & elementManager = domain.getMeshBody( 0 ).getBaseDiscretization().getElemManager(); - xmlWrapper::xmlNode topLevelNode = xmlProblemNode.child( elementManager.getName().c_str() ); + xmlWrapper::xmlNode topLevelNode = xmlProblemNode.getChild( elementManager.getName().c_str() ); elementManager.processInputFileRecursive( xmlDocument, topLevelNode ); elementManager.postProcessInputRecursive(); diff --git a/src/coreComponents/unitTests/meshTests/testMeshGeneration.cpp b/src/coreComponents/unitTests/meshTests/testMeshGeneration.cpp index 8bc67e65dbe..14b1c206d1c 100644 --- a/src/coreComponents/unitTests/meshTests/testMeshGeneration.cpp +++ b/src/coreComponents/unitTests/meshTests/testMeshGeneration.cpp @@ -112,7 +112,7 @@ class MeshGenerationTest : public ::testing::Test meshManager.generateMeshLevels( domain ); ElementRegionManager & elementManager = domain.getMeshBody( 0 ).getBaseDiscretization().getElemManager(); - xmlWrapper::xmlNode topLevelNode = xmlProblemNode.child( elementManager.getName().c_str() ); + xmlWrapper::xmlNode topLevelNode = xmlProblemNode.getChild( elementManager.getName().c_str() ); elementManager.processInputFileRecursive( xmlDocument, topLevelNode ); elementManager.postProcessInputRecursive(); diff --git a/src/coreComponents/unitTests/virtualElementTests/testConformingVirtualElementOrder1.cpp b/src/coreComponents/unitTests/virtualElementTests/testConformingVirtualElementOrder1.cpp index 4b7bc185ef3..5f3f4216e2c 100644 --- a/src/coreComponents/unitTests/virtualElementTests/testConformingVirtualElementOrder1.cpp +++ b/src/coreComponents/unitTests/virtualElementTests/testConformingVirtualElementOrder1.cpp @@ -301,7 +301,7 @@ TEST( ConformingVirtualElementOrder1, hexahedra ) meshManager.generateMeshLevels( domain ); MeshLevel & mesh = domain.getMeshBody( 0 ).getBaseDiscretization(); ElementRegionManager & elementManager = mesh.getElemManager(); - xmlWrapper::xmlNode topLevelNode = xmlProblemNode.child( elementManager.getName().c_str() ); + xmlWrapper::xmlNode topLevelNode = xmlProblemNode.getChild( elementManager.getName().c_str() ); elementManager.processInputFileRecursive( inputFile, topLevelNode ); elementManager.postProcessInputRecursive(); problemManager.problemSetup(); @@ -354,7 +354,7 @@ TEST( ConformingVirtualElementOrder1, wedges ) meshManager.generateMeshLevels( domain ); MeshLevel & mesh = domain.getMeshBody( 0 ).getBaseDiscretization(); ElementRegionManager & elementManager = mesh.getElemManager(); - xmlWrapper::xmlNode topLevelNode = xmlProblemNode.child( elementManager.getName().c_str() ); + xmlWrapper::xmlNode topLevelNode = xmlProblemNode.getChild( elementManager.getName().c_str() ); elementManager.processInputFileRecursive( inputFile, topLevelNode ); elementManager.postProcessInputRecursive(); problemManager.problemSetup(); diff --git a/src/coreComponents/unitTests/xmlTests/testXMLFile.cpp b/src/coreComponents/unitTests/xmlTests/testXMLFile.cpp index 49ad4a301dd..fed2015aaa2 100644 --- a/src/coreComponents/unitTests/xmlTests/testXMLFile.cpp +++ b/src/coreComponents/unitTests/xmlTests/testXMLFile.cpp @@ -61,7 +61,7 @@ void getElementsRecursive( xmlDocument const & document, xmlNode const & targetN // The Group name will be the name attribute value, or the node tag name if the name attribute // doesn't exist. string const groupName = [&]() { - xmlAttribute nameAtt = targetNode.attribute( "name" ); + xmlAttribute nameAtt = targetNode.getAttribute( "name" ); return nameAtt ? string( nameAtt.value() ) : string( targetNode.name() ); }(); From d463a931d12d90e052193b4efef8eeadc428557b Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Fri, 7 Jul 2023 17:32:47 +0200 Subject: [PATCH 51/85] Revert "WIP: more wrapping (xmlNode+xmlAttribute)" this work will be integrated in another PR This reverts commit d5ab61ab7ce285dece8af858f5c0de5e59d7b464. --- .../dataRepository/ConduitRestart.cpp | 2 +- .../dataRepository/DataContext.cpp | 4 +- src/coreComponents/dataRepository/Group.cpp | 8 +- src/coreComponents/dataRepository/Wrapper.hpp | 2 +- .../dataRepository/WrapperBase.cpp | 10 +- .../dataRepository/xmlWrapper.cpp | 25 +- .../dataRepository/xmlWrapper.hpp | 263 +----------------- .../fileIO/vtk/VTKPVDWriter.cpp | 18 +- .../fileIO/vtk/VTKVTMWriter.cpp | 18 +- .../mainInterface/ProblemManager.cpp | 16 +- .../mesh/ElementRegionManager.cpp | 6 +- src/coreComponents/schema/schemaUtilities.cpp | 68 ++--- .../fluidFlowTests/testCompFlowUtils.hpp | 4 +- .../fluidFlowTests/testSingleFlowUtils.hpp | 4 +- .../testDofManagerUtils.hpp | 2 +- .../meshTests/testMeshGeneration.cpp | 2 +- .../testConformingVirtualElementOrder1.cpp | 4 +- .../unitTests/xmlTests/testXMLFile.cpp | 2 +- 18 files changed, 108 insertions(+), 350 deletions(-) diff --git a/src/coreComponents/dataRepository/ConduitRestart.cpp b/src/coreComponents/dataRepository/ConduitRestart.cpp index 6a27b23bcaf..8a109807d01 100644 --- a/src/coreComponents/dataRepository/ConduitRestart.cpp +++ b/src/coreComponents/dataRepository/ConduitRestart.cpp @@ -64,7 +64,7 @@ string readRootNode( string const & rootPath ) conduit::Node node; conduit::relay::io::load( rootPath + ".root", "hdf5", node ); - int const nFiles = node.getChild( "number_of_files" ).value(); + int const nFiles = node.child( "number_of_files" ).value(); GEOS_THROW_IF_NE( nFiles, MpiWrapper::commSize(), InputError ); string const filePattern = node.fetch_existing( "file_pattern" ).as_string(); diff --git a/src/coreComponents/dataRepository/DataContext.cpp b/src/coreComponents/dataRepository/DataContext.cpp index 9d6145655af..b687de8830b 100644 --- a/src/coreComponents/dataRepository/DataContext.cpp +++ b/src/coreComponents/dataRepository/DataContext.cpp @@ -41,10 +41,10 @@ std::ostream & operator<<( std::ostream & os, DataContext const & sc ) */ string getNodeName( xmlWrapper::xmlNode const & node ) { - xmlWrapper::xmlAttribute const nameAtt = node.getAttribute( "name" ); + xmlWrapper::xmlAttribute const nameAtt = node.attribute( "name" ); if( !nameAtt.empty() ) { - return string( node.getAttribute( "name" ).value() ); + return string( node.attribute( "name" ).value() ); } else { diff --git a/src/coreComponents/dataRepository/Group.cpp b/src/coreComponents/dataRepository/Group.cpp index b5655eb1e51..4373abe653b 100644 --- a/src/coreComponents/dataRepository/Group.cpp +++ b/src/coreComponents/dataRepository/Group.cpp @@ -142,7 +142,7 @@ void Group::processInputFileRecursive( xmlWrapper::xmlDocument & xmlDocument, // Handle the case where the node was imported from a different input file // Set the path prefix to make sure all relative Path variables are interpreted correctly string const oldPrefix = Path::pathPrefix(); - xmlWrapper::xmlAttribute filePath = targetNode.getAttribute( xmlWrapper::filePathString ); + xmlWrapper::xmlAttribute filePath = targetNode.attribute( xmlWrapper::filePathString ); if( filePath ) { Path::pathPrefix() = getAbsolutePath( splitPath( filePath.value() ).first ); @@ -153,7 +153,7 @@ void Group::processInputFileRecursive( xmlWrapper::xmlDocument & xmlDocument, for( xmlWrapper::xmlNode childNode : targetNode.children() ) { // Get the child tag and name - string childName = childNode.getAttribute( "name" ).value(); + string childName = childNode.attribute( "name" ).value(); if( childName.empty() ) { childName = childNode.name(); @@ -210,14 +210,14 @@ void Group::processInputFile( xmlWrapper::xmlDocument const & xmlDocument, for( xmlWrapper::xmlAttribute attribute : targetNode.attributes() ) { - string const attributeName = attribute.getName(); + string const attributeName = attribute.name(); if( !xmlWrapper::isFileMetadataAttribute( attributeName ) ) { GEOS_THROW_IF( processedAttributes.count( attributeName ) == 0, GEOS_FMT( "XML Node at '{}' with name={} contains unused attribute '{}'.\n" "Valid attributes are:\n{}\nFor more details, please refer to documentation at:\n" "http://geosx-geosx.readthedocs-hosted.com/en/latest/docs/sphinx/userGuide/Index.html", - targetNode.getPath(), m_dataContext->toString(), attributeName, + targetNode.path(), m_dataContext->toString(), attributeName, dumpInputOptions() ), InputError ); } diff --git a/src/coreComponents/dataRepository/Wrapper.hpp b/src/coreComponents/dataRepository/Wrapper.hpp index 1cd3e10589e..dcb413c6fe9 100644 --- a/src/coreComponents/dataRepository/Wrapper.hpp +++ b/src/coreComponents/dataRepository/Wrapper.hpp @@ -624,7 +624,7 @@ class Wrapper final : public WrapperBase GEOS_FMT( "XML Node {} ({}) with name={} is missing required attribute '{}'." "Available options are:\n {}\n For more details, please refer to documentation at:\n" "http://geosx-geosx.readthedocs-hosted.com/en/latest/docs/sphinx/userGuide/Index.html", - targetNode.name(), nodePos.toString(), targetNode.getAttribute( "name" ).value(), + targetNode.name(), nodePos.toString(), targetNode.attribute( "name" ).value(), getName(), dumpInputOptions( true ) ), InputError ); } diff --git a/src/coreComponents/dataRepository/WrapperBase.cpp b/src/coreComponents/dataRepository/WrapperBase.cpp index 2c4884e11b9..34c4d642bd5 100644 --- a/src/coreComponents/dataRepository/WrapperBase.cpp +++ b/src/coreComponents/dataRepository/WrapperBase.cpp @@ -109,9 +109,9 @@ int WrapperBase::setTotalviewDisplay() const void WrapperBase::createDataContext( xmlWrapper::xmlNode const & targetNode, xmlWrapper::xmlNodePos const & nodePos ) { - xmlWrapper::xmlAttribute att = targetNode.getAttribute( m_name.c_str() ); + xmlWrapper::xmlAttribute att = targetNode.attribute( m_name.c_str() ); xmlWrapper::xmlAttributePos attPos = nodePos.getAttributeLine( m_name ); - if( nodePos.isFound() && attPos.isFound() && !att.isEmpty() ) + if( nodePos.isFound() && attPos.isFound() && !att.empty() ) { m_dataContext = std::make_unique< DataFileContext >( targetNode, att, attPos ); } @@ -121,8 +121,8 @@ void WrapperBase::processInputException( std::exception const & ex, xmlWrapper::xmlNode const & targetNode, xmlWrapper::xmlNodePos const & nodePos ) const { - xmlWrapper::xmlAttribute const & attribute = targetNode.getAttribute( getName().c_str() ); - string const inputStr = string( attribute.getValue() ); + xmlWrapper::xmlAttribute const & attribute = targetNode.attribute( getName().c_str() ); + string const inputStr = string( attribute.value() ); xmlWrapper::xmlAttributePos const attPos = nodePos.getAttributeLine( getName() ); std::ostringstream oss; string const exStr = ex.what(); @@ -136,7 +136,7 @@ void WrapperBase::processInputException( std::exception const & ex, } else { - oss << targetNode.path() << " (name=" << targetNode.getAttribute( "name" ).getValue() << ")/" << getName(); + oss << targetNode.path() << " (name=" << targetNode.attribute( "name" ).value() << ")/" << getName(); } oss << "\n***** Input value: '" << inputStr << '\''; oss << ( exStr[0]=='\n' ? exStr : "'\n" + exStr ); diff --git a/src/coreComponents/dataRepository/xmlWrapper.cpp b/src/coreComponents/dataRepository/xmlWrapper.cpp index 228e55d0dd2..218746b0fd1 100644 --- a/src/coreComponents/dataRepository/xmlWrapper.cpp +++ b/src/coreComponents/dataRepository/xmlWrapper.cpp @@ -73,7 +73,6 @@ template void stringToInputVariable( Tensor< real32, 3 > & target, string const template void stringToInputVariable( Tensor< real64, 3 > & target, string const & inputValue ); template void stringToInputVariable( Tensor< real64, 6 > & target, string const & inputValue ); - /** * @brief Adds the filePath and character offset info on the node in filePathString * and charOffsetString attributes. This function allow to keep track of the source @@ -85,8 +84,8 @@ void addNodeFileInfo( xmlNode targetNode, string const & filePath ) { // we keep the file path and the character offset on each node so we keep track of these // informations, even if the nodes are manipulated within the xml hierarchy. - targetNode.appendAttribute( filePathString ).set_value( filePath.c_str() ); - targetNode.appendAttribute( charOffsetString ).set_value( targetNode.offset_debug() ); + targetNode.append_attribute( filePathString ).set_value( filePath.c_str() ); + targetNode.append_attribute( charOffsetString ).set_value( targetNode.offset_debug() ); for( xmlNode subNode : targetNode.children() ) { @@ -97,13 +96,13 @@ void addNodeFileInfo( xmlNode targetNode, string const & filePath ) * @brief Returns true if the addNodeFileInfo() command has been called of the specified node. */ bool xmlDocument::hasNodeFileInfo() const -{ return !getFirstChild().getAttribute( filePathString ).empty(); } +{ return !getFirstChild().attribute( filePathString ).empty(); } void xmlDocument::addIncludedXML( xmlNode & targetNode, int const level ) { GEOS_THROW_IF( level > 100, "XML include level limit reached, please check input for include loops", InputError ); - string const currentFilePath = targetNode.getAttribute( filePathString ).value(); + string const currentFilePath = targetNode.attribute( filePathString ).value(); // Schema currently allows a single unique , but a non-validating file may include multiple for( xmlNode includedNode : targetNode.children( includedListTag ) ) @@ -116,7 +115,7 @@ void xmlDocument::addIncludedXML( xmlNode & targetNode, int const level ) GEOS_THROW_IF_NE_MSG( string( fileNode.name() ), includedFileTag, GEOS_FMT( "<{}> must only contain <{}> tags", includedListTag, includedFileTag ), InputError ); - xmlAttribute const nameAttr = fileNode.getAttribute( "name" ); + xmlAttribute const nameAttr = fileNode.attribute( "name" ); string const fileName = nameAttr.value(); GEOS_THROW_IF( !nameAttr || fileName.empty(), GEOS_FMT( "<{}> tag must have a non-empty 'name' attribute", includedFileTag ), @@ -178,12 +177,12 @@ string buildMultipleInputXML( string_array const & inputFileList, { xmlWrapper::xmlDocument compositeTree; xmlWrapper::xmlNode compositeRoot = compositeTree.appendChild( dataRepository::keys::ProblemManager ); - xmlWrapper::xmlNode includedRoot = compositeRoot.appendChild( includedListTag ); + xmlWrapper::xmlNode includedRoot = compositeRoot.append_child( includedListTag ); for( auto & fileName: inputFileList ) { - xmlWrapper::xmlNode fileNode = includedRoot.appendChild( includedFileTag ); - fileNode.appendAttribute( "name" ) = fileName.c_str(); + xmlWrapper::xmlNode fileNode = includedRoot.append_child( includedFileTag ); + fileNode.append_attribute( "name" ) = fileName.c_str(); } compositeTree.saveFile( inputFileName ); @@ -265,7 +264,7 @@ xmlResult xmlDocument::loadBuffer( const void * contents, size_t size, bool load xmlNode xmlDocument::appendChild( string const & name ) { return pugiDocument.append_child( name.c_str() ); } -xmlNode xmlDocument::appendChild( xmlNodeType type ) +xmlNode xmlDocument::appendChild( xmlTypes type ) { return pugiDocument.append_child( type ); } bool xmlDocument::saveFile( string const & path ) const @@ -276,8 +275,6 @@ string const & xmlDocument::getFilePath() const xmlNode xmlDocument::getFirstChild() const { return pugiDocument.first_child(); } -xmlNode xmlDocument::getRoot() const -{ return pugiDocument.root(); } xmlNode xmlDocument::getChild( string const & name ) const { return pugiDocument.child( name.c_str() ); } @@ -299,8 +296,8 @@ xmlNodePos xmlDocument::getNodePosition( xmlNode const & node ) const size_t line = npos; size_t offsetInLine = npos; size_t offset = npos; - xmlAttribute filePathAtt = node.getAttribute( filePathString ); - xmlAttribute charOffsetAtt = node.getAttribute( charOffsetString ); + xmlAttribute filePathAtt = node.attribute( filePathString ); + xmlAttribute charOffsetAtt = node.attribute( charOffsetString ); string filePath; if( filePathAtt && charOffsetAtt ) diff --git a/src/coreComponents/dataRepository/xmlWrapper.hpp b/src/coreComponents/dataRepository/xmlWrapper.hpp index ff59fe36c71..80e684a7dd0 100644 --- a/src/coreComponents/dataRepository/xmlWrapper.hpp +++ b/src/coreComponents/dataRepository/xmlWrapper.hpp @@ -50,160 +50,18 @@ namespace xmlWrapper /// Alias for the type of the result from an xml parse attempt. using xmlResult = pugi::xml_parse_result; -/// Alias for the type variant of an xml node. -using xmlNodeType = pugi::xml_node_type; - -/// Wrapper class for the type of an xml attribute. -/// An xmlAttribute behave as a pointer object: passing it by value to a function which modify it -/// will modify the original xmlAttribute object. -class xmlAttribute -{ -public: - /** - * @brief Construct a new empty xml Attribute object - */ - inline xmlAttribute(); - - /** - * @param r another xmlAttribute object - * @return true if the referenced underlying attribute object is the same one, false otherwise. - */ - inline bool operator==( const xmlAttribute & r ) const; - /** - * @param r another xmlAttribute object - * @return true if the referenced underlying attribute object is a different one, false otherwise. - */ - inline bool operator!=( const xmlAttribute & r ) const; - /** - * @return the same result as isEmpty() - */ - inline operator bool() const; - - /** - * @return true if the attribute is empty, false otherwise - */ - inline bool isEmpty() const; - - /** - * @return the attribute name, or "" if empty. - * @todo c++17: change return type to string_view - */ - inline string getName() const; - /** - * @brief Set the tag name of the node - * @param name the new name - */ - inline void setName( const string & name ); - - /** - * @return the attribute value, or "" if empty. - * @todo c++17: change return type to string_view - */ - inline string getValue() const; - /** - * @brief Set the value of the node - * @param value the new value - */ - inline void setValue( const string & value ); - - /** - * @return the next attribute in the containing node, or an empty one if there is none - */ - inline xmlAttribute nextAttribute() const; - /** - * @return the next attribute in the containing node, or an empty one if there is none - */ - inline xmlAttribute previousAttribute() const; - -private: - pugi::xml_attribute pugiAtt; - - /** - * @brief Construct a new reference to the same attribute object - */ - inline explicit xmlAttribute( pugi::xml_attribute const & attribute ); -}; - -/// Wrapper class for the type of an xml node. +/// Alias for the type of an xml node. /// An xmlNode behave as a pointer object: passing it by value to a function which modify it /// will modify the original xmlNode object. -class xmlNode -{ -public: - /** - * @brief Construct a new empty xml Node object - */ - inline xmlNode(); - - /** - * @param r another xmlNode object - * @return true if the referenced underlying node object is the same one, false otherwise. - */ - inline bool operator==( const xmlNode & r ) const; - /** - * @param r another xmlNode object - * @return true if the referenced underlying node object is a different one, false otherwise. - */ - inline bool operator!=( const xmlNode & r ) const; - /** - * @return the same result as isEmpty() - */ - inline operator bool() const; - - /** - * @return true if the node is empty, false otherwise - */ - inline bool isEmpty() const; - - /** - * @return the node tag name, or "" if empty. - * @todo c++17: change return type to string_view - */ - inline string getName() const; - /** - * @brief Set the tag name of the node - * @param name the new name - */ - inline void setName( const string & name ); - - /** - * @return the node value, or "" if empty. - * @todo c++17: change return type to string_view - */ - inline string getValue() const; - /** - * @brief Set the value of the node - * @param value the new value - */ - inline void setValue( const string & value ); - - inline xmlNode getParent() const; - inline xmlNode getChild( string const & name ) const; - inline xmlAttribute getAttribute( string const & name ) const; - - inline xmlAttribute appendAttribute( string const & name ); - inline xmlNode appendChild( xmlNodeType type = node_element ); - inline xmlNode appendChild( string const & name ); - inline bool removeAttribute( const xmlAttribute & att ); - inline bool removeAttribute( string const & name ); - inline bool removeChild( const xmlNode & node ); - inline bool removeChild( string const & name ); - - inline xmlNodeType getType() const; - inline string getPath( char delimiter = '/' ) const; - /** - * @return the character offset where this node is in the xmlDocument - */ - inline ptrdiff_t getOffset() const; +using xmlNode = pugi::xml_node; -private: - pugi::xml_node pugiNode; +/// Alias for the type of an xml attribute. +/// An xmlAttribute behave as a pointer object: passing it by value to a function which modify it +/// will modify the original xmlAttribute object. +using xmlAttribute = pugi::xml_attribute; - /** - * @brief Construct a new reference to the same node object - */ - inline explicit xmlNode( pugi::xml_node const & node ); -}; +/// Alias for the type variant of an xml node. +using xmlTypes = pugi::xml_node_type; class xmlDocument; @@ -293,10 +151,6 @@ class xmlDocument * @return the first child of this document (typically in GEOS, the node) */ xmlNode getFirstChild() const; - /** - * @return the DOM parent node of this document (which, typically in GEOS, contains the node and the declaration node) - */ - xmlNode getRoot() const; /** * @return a child with the specified name * @param name the tag name of the node to find @@ -377,7 +231,7 @@ class xmlDocument * As an exemple, node_declaration is useful to add the "" node. * @return the added node */ - xmlNode appendChild( xmlNodeType type = xmlNodeType::node_element ); + xmlNode appendChild( xmlTypes type = xmlTypes::node_element ); /** * @brief Save the XML to a file @@ -578,7 +432,7 @@ readAttributeAsType( T & rval, xmlNode const & targetNode, T_DEF const & defVal ) { - xmlAttribute const xmlatt = targetNode.getAttribute( name.c_str() ); + xmlAttribute const xmlatt = targetNode.attribute( name.c_str() ); if( !xmlatt.empty() ) { // parse the string/attribute into a value @@ -609,7 +463,7 @@ readAttributeAsType( T & rval, xmlNode const & targetNode, bool const required ) { - xmlAttribute const xmlatt = targetNode.getAttribute( name.c_str() ); + xmlAttribute const xmlatt = targetNode.attribute( name.c_str() ); bool const success = !(xmlatt.empty() && required); @@ -660,100 +514,7 @@ readAttributeAsType( T & rval, ///@} -inline xmlAttribute::xmlAttribute() -{} -inline xmlAttribute::xmlAttribute( pugi::xml_attribute const & attribute ): - pugiAtt( attribute.internal_object() ) -{} - -inline bool xmlAttribute::operator==( const xmlAttribute & r ) const -{ return pugiAtt.operator==(); } -inline bool xmlAttribute::operator!=( const xmlAttribute & r ) const -{ return pugiAtt.operator!=(); } -inline bool xmlAttribute::operator bool() const -{ return bool(pugiAtt); } - -inline bool xmlAttribute::isEmpty() const -{ return pugiAtt.empty(); } - -inline string xmlAttribute::getName() const -{ return pugiAtt.name(); } -inline void xmlAttribute::setName( const string & name ) -{ pugiAtt.set_name( name ); } - -inline string xmlAttribute::getValue() const -{ return pugiAtt.value(); } -inline void xmlAttribute::setValue( const string & value ) -{ pugiAtt.set_value( value ); } - -inline xmlAttribute xmlAttribute::next_attribute() -{ xmlAttribute( pugiAtt.next_attribute() ); } -inline xmlAttribute xmlAttribute::previous_attribute() -{ xmlAttribute( pugiAtt.previous_attribute() ); } - - -inline xmlNode::xmlNode(): - pugiNode() -{} -inline xmlNode::xmlNode( pugi::xml_node const & node ): - pugiNode( node.internal_object() ) -{} - -inline bool xmlNode::operator==( const xmlNode & r ) const -{ return pugiNode.operator==(); } -inline bool xmlNode::operator!=( const xmlNode & r ) const -{ return pugiNode.operator!=(); } -inline bool xmlAttribute::operator bool() const -{ return bool(pugiNode); } - -inline bool xmlNode::isEmpty() const -{ return pugiNode.empty(); } - -inline string xmlNode::getName() const -{ return pugiNode.name(); } -inline void xmlNode::setName( const string & name ) -{ pugiNode.set_name( name ); } - -inline void xmlNode::setValue( const string & value ) -{ pugiNode.set_value( value ); } -inline string xmlNode::getValue() const -{ return pugiNode.value(); } - -inline iterator xmlNode::begin() const -{ return pugiNode.begin(); } -inline iterator xmlNode::end() const -{ return pugiNode.end(); } -inline xmlNode xmlNode::getParent() const -{ return xmlNode( pugiNode.parent()); } -inline xmlNode xmlNode::getChild( string const & name ) const -{ return xmlNode( pugiNode.child( name )); } -inline xmlAttribute xmlNode::getAttribute( string const & name ) const -{ return xmlAttribute( pugiNode.attribute( name )); } - -inline xmlAttribute xmlNode::appendAttribute( string const & name ) -{ return xmlAttribute( pugiNode.append_attribute( name )); } -inline xmlNode xmlNode::appendChild( xmlNodeType type = node_element ) -{ return xmlNode( pugiNode.append_child()); } -inline xmlNode xmlNode::appendChild( string const & name ) -{ return xmlNode( pugiNode.append_child( name )); } -inline bool xmlNode::removeAttribute( const xmlAttribute & att ) -{ return pugiNode.remove_attribute( att ); } -inline bool xmlNode::removeAttribute( string const & name ) -{ return pugiNode.remove_attribute( name ); } -inline bool xmlNode::removeChild( const xmlNode & node ) -{ return pugiNode.remove_child( node ); } -inline bool xmlNode::removeChild( string const & name ) -{ return pugiNode.remove_child( name ); } - -inline xmlNodeType xmlNode::getType() const -{ return pugiNode.getType(); } -inline string xmlNode::getPath( char delimiter = '/' ) const -{ return pugiNode.getPath( delimiter ); } -inline ptrdiff_t xmlNode::getOffset() const -{ return pugiNode.getOffset(); } - - -} /* namespace xmlWrapper */ +} } /* namespace geos */ diff --git a/src/coreComponents/fileIO/vtk/VTKPVDWriter.cpp b/src/coreComponents/fileIO/vtk/VTKPVDWriter.cpp index bc5ea174bea..a98d2391735 100644 --- a/src/coreComponents/fileIO/vtk/VTKPVDWriter.cpp +++ b/src/coreComponents/fileIO/vtk/VTKPVDWriter.cpp @@ -25,14 +25,14 @@ VTKPVDWriter::VTKPVDWriter( string fileName ): { // Declaration of XML version auto declarationNode = m_pvdFile.appendChild( pugi::node_declaration ); - declarationNode.appendAttribute( "version" ) = "1.0"; + declarationNode.append_attribute( "version" ) = "1.0"; // Declaration of the node VTKFile auto vtkFileNode = m_pvdFile.appendChild( "VTKFile" ); - vtkFileNode.appendAttribute( "type" ) = "Collection"; - vtkFileNode.appendAttribute( "version" ) = "0.1"; + vtkFileNode.append_attribute( "type" ) = "Collection"; + vtkFileNode.append_attribute( "version" ) = "0.1"; - vtkFileNode.appendChild( "Collection" ); + vtkFileNode.append_child( "Collection" ); } void VTKPVDWriter::setFileName( string fileName ) @@ -47,15 +47,15 @@ void VTKPVDWriter::save() const void VTKPVDWriter::addData( real64 time, string const & filePath ) const { - auto collectionNode = m_pvdFile.getChild( "VTKFile" ).getChild( "Collection" ); - auto dataSetNode = collectionNode.appendChild( "DataSet" ); - dataSetNode.appendAttribute( "timestep" ) = time; - dataSetNode.appendAttribute( "file" ) = filePath.c_str(); + auto collectionNode = m_pvdFile.getChild( "VTKFile" ).child( "Collection" ); + auto dataSetNode = collectionNode.append_child( "DataSet" ); + dataSetNode.append_attribute( "timestep" ) = time; + dataSetNode.append_attribute( "file" ) = filePath.c_str(); } void VTKPVDWriter::reinitData() const { - auto collectionNode = m_pvdFile.getChild( "VTKFile" ).getChild( "Collection" ); + auto collectionNode = m_pvdFile.getChild( "VTKFile" ).child( "Collection" ); while( collectionNode.remove_child( "DataSet" ) ) {} diff --git a/src/coreComponents/fileIO/vtk/VTKVTMWriter.cpp b/src/coreComponents/fileIO/vtk/VTKVTMWriter.cpp index 3b6437f8bdf..8ecdd5091ee 100644 --- a/src/coreComponents/fileIO/vtk/VTKVTMWriter.cpp +++ b/src/coreComponents/fileIO/vtk/VTKVTMWriter.cpp @@ -27,14 +27,14 @@ VTKVTMWriter::VTKVTMWriter( string filePath ) { // Declaration of XML version auto declarationNode = m_document.appendChild( pugi::node_declaration ); - declarationNode.appendAttribute( "version" ) = "1.0"; + declarationNode.append_attribute( "version" ) = "1.0"; // Declaration of the node VTKFile auto vtkFileNode = m_document.appendChild( "VTKFile" ); - vtkFileNode.appendAttribute( "type" ) = "vtkMultiBlockDataSet"; - vtkFileNode.appendAttribute( "version" ) = "1.0"; + vtkFileNode.append_attribute( "type" ) = "vtkMultiBlockDataSet"; + vtkFileNode.append_attribute( "version" ) = "1.0"; - m_blockRoot = vtkFileNode.appendChild( "vtkMultiBlockDataSet" ); + m_blockRoot = vtkFileNode.append_child( "vtkMultiBlockDataSet" ); } void VTKVTMWriter::write() const @@ -56,13 +56,13 @@ void VTKVTMWriter::addDataSet( std::vector< string > const & blockPath, } else { - node = node.appendChild( "Block" ); - node.appendAttribute( "name" ) = blockName.c_str(); + node = node.append_child( "Block" ); + node.append_attribute( "name" ) = blockName.c_str(); } } - node = node.appendChild( "DataSet" ); - node.appendAttribute( "name" ) = dataSetName.c_str(); - node.appendAttribute( "file" ) = filePath.c_str(); + node = node.append_child( "DataSet" ); + node.append_attribute( "name" ) = dataSetName.c_str(); + node.append_attribute( "file" ) = filePath.c_str(); } } // namespace vtk diff --git a/src/coreComponents/mainInterface/ProblemManager.cpp b/src/coreComponents/mainInterface/ProblemManager.cpp index 2731d095478..3f318b30dbd 100644 --- a/src/coreComponents/mainInterface/ProblemManager.cpp +++ b/src/coreComponents/mainInterface/ProblemManager.cpp @@ -284,12 +284,12 @@ void ProblemManager::setSchemaDeviations( xmlWrapper::xmlNode schemaRoot, xmlWrapper::xmlNode schemaParent, integer documentationType ) { - xmlWrapper::xmlNode targetChoiceNode = schemaParent.getChild( "xsd:choice" ); - if( targetChoiceNode.isEmpty() ) + xmlWrapper::xmlNode targetChoiceNode = schemaParent.child( "xsd:choice" ); + if( targetChoiceNode.empty() ) { targetChoiceNode = schemaParent.prepend_child( "xsd:choice" ); - targetChoiceNode.appendAttribute( "minOccurs" ) = "0"; - targetChoiceNode.appendAttribute( "maxOccurs" ) = "unbounded"; + targetChoiceNode.append_attribute( "minOccurs" ) = "0"; + targetChoiceNode.append_attribute( "maxOccurs" ) = "unbounded"; } // These objects are handled differently during the xml read step, @@ -417,21 +417,21 @@ void ProblemManager::parseXMLDocument( xmlWrapper::xmlDocument & xmlDocument ) { DomainPartition & domain = getDomainPartition(); ConstitutiveManager & constitutiveManager = domain.getGroup< ConstitutiveManager >( groupKeys.constitutiveManager ); - xmlWrapper::xmlNode topLevelNode = xmlProblemNode.getChild( constitutiveManager.getName().c_str()); + xmlWrapper::xmlNode topLevelNode = xmlProblemNode.child( constitutiveManager.getName().c_str()); constitutiveManager.processInputFileRecursive( xmlDocument, topLevelNode ); // Open mesh levels MeshManager & meshManager = this->getGroup< MeshManager >( groupKeys.meshManager ); meshManager.generateMeshLevels( domain ); Group & meshBodies = domain.getMeshBodies(); - xmlWrapper::xmlNode elementRegionsNode = xmlProblemNode.getChild( MeshLevel::groupStructKeys::elemManagerString() ); + xmlWrapper::xmlNode elementRegionsNode = xmlProblemNode.child( MeshLevel::groupStructKeys::elemManagerString() ); for( xmlWrapper::xmlNode regionNode : elementRegionsNode.children() ) { - string const regionName = regionNode.attrgetAttributeibute( "name" ).value(); + string const regionName = regionNode.attribute( "name" ).value(); string const regionMeshBodyName = ElementRegionBase::verifyMeshBodyName( meshBodies, - regionNode.getAttribute( "meshBody" ).value() ); + regionNode.attribute( "meshBody" ).value() ); MeshBody & meshBody = domain.getMeshBody( regionMeshBodyName ); meshBody.forMeshLevels( [&]( MeshLevel & meshLevel ) diff --git a/src/coreComponents/mesh/ElementRegionManager.cpp b/src/coreComponents/mesh/ElementRegionManager.cpp index 22aa6847950..9106cae830d 100644 --- a/src/coreComponents/mesh/ElementRegionManager.cpp +++ b/src/coreComponents/mesh/ElementRegionManager.cpp @@ -97,12 +97,12 @@ void ElementRegionManager::setSchemaDeviations( xmlWrapper::xmlNode schemaRoot, xmlWrapper::xmlNode schemaParent, integer documentationType ) { - xmlWrapper::xmlNode targetChoiceNode = schemaParent.getChild( "xsd:choice" ); + xmlWrapper::xmlNode targetChoiceNode = schemaParent.child( "xsd:choice" ); if( targetChoiceNode.empty() ) { targetChoiceNode = schemaParent.prepend_child( "xsd:choice" ); - targetChoiceNode.appendAttribute( "minOccurs" ) = "0"; - targetChoiceNode.appendAttribute( "maxOccurs" ) = "unbounded"; + targetChoiceNode.append_attribute( "minOccurs" ) = "0"; + targetChoiceNode.append_attribute( "maxOccurs" ) = "unbounded"; } std::set< string > names; diff --git a/src/coreComponents/schema/schemaUtilities.cpp b/src/coreComponents/schema/schemaUtilities.cpp index 61eb928c4ab..524da722933 100644 --- a/src/coreComponents/schema/schemaUtilities.cpp +++ b/src/coreComponents/schema/schemaUtilities.cpp @@ -76,22 +76,22 @@ void AppendSimpleType( xmlWrapper::xmlNode & schemaRoot, { string const advanced_match_string = ".*[\\[\\]`$].*|"; - xmlWrapper::xmlNode newNode = schemaRoot.appendChild( "xsd:simpleType" ); - newNode.appendAttribute( "name" ) = name.c_str(); - xmlWrapper::xmlNode restrictionNode = newNode.appendChild( "xsd:restriction" ); - restrictionNode.appendAttribute( "base" ) = "xsd:string"; - xmlWrapper::xmlNode patternNode = restrictionNode.appendChild( "xsd:pattern" ); + xmlWrapper::xmlNode newNode = schemaRoot.append_child( "xsd:simpleType" ); + newNode.append_attribute( "name" ) = name.c_str(); + xmlWrapper::xmlNode restrictionNode = newNode.append_child( "xsd:restriction" ); + restrictionNode.append_attribute( "base" ) = "xsd:string"; + xmlWrapper::xmlNode patternNode = restrictionNode.append_child( "xsd:pattern" ); // Handle the default regex if( regex.empty() ) { GEOS_WARNING( "schema regex not defined for " << name ); - patternNode.appendAttribute( "value" ) = "(?s).*"; + patternNode.append_attribute( "value" ) = "(?s).*"; } else { string const patternString = advanced_match_string + regex; - patternNode.appendAttribute( "value" ) = patternString.c_str(); + patternNode.append_attribute( "value" ) = patternString.c_str(); } } @@ -122,38 +122,38 @@ void SchemaConstruction( Group & group, if( schemaParent.find_child_by_attribute( "xsd:element", "name", targetName.c_str()).empty()) { // Add the entries to the current and root nodes - xmlWrapper::xmlNode targetIncludeNode = schemaParent.appendChild( "xsd:element" ); - targetIncludeNode.appendAttribute( "name" ) = targetName.c_str(); - targetIncludeNode.appendAttribute( "type" ) = typeName.c_str(); + xmlWrapper::xmlNode targetIncludeNode = schemaParent.append_child( "xsd:element" ); + targetIncludeNode.append_attribute( "name" ) = targetName.c_str(); + targetIncludeNode.append_attribute( "type" ) = typeName.c_str(); // Add occurence conditions if((schemaType == InputFlags::REQUIRED_NONUNIQUE) || (schemaType == InputFlags::REQUIRED)) { - targetIncludeNode.appendAttribute( "minOccurs" ) = "1"; + targetIncludeNode.append_attribute( "minOccurs" ) = "1"; } if((schemaType == InputFlags::OPTIONAL) || (schemaType == InputFlags::REQUIRED)) { - targetIncludeNode.appendAttribute( "maxOccurs" ) = "1"; + targetIncludeNode.append_attribute( "maxOccurs" ) = "1"; } // Insert a new type into the root node if not present xmlWrapper::xmlNode targetTypeDefNode = schemaRoot.find_child_by_attribute( "xsd:complexType", "name", typeName.c_str()); if( targetTypeDefNode.empty()) { - targetTypeDefNode = schemaRoot.appendChild( "xsd:complexType" ); - targetTypeDefNode.appendAttribute( "name" ) = typeName.c_str(); + targetTypeDefNode = schemaRoot.append_child( "xsd:complexType" ); + targetTypeDefNode.append_attribute( "name" ) = typeName.c_str(); } // Add subgroups if( group.numSubGroups() > 0 ) { // Children are defined in a choice node - xmlWrapper::xmlNode targetChoiceNode = targetTypeDefNode.getChild( "xsd:choice" ); + xmlWrapper::xmlNode targetChoiceNode = targetTypeDefNode.child( "xsd:choice" ); if( targetChoiceNode.empty() ) { targetChoiceNode = targetTypeDefNode.prepend_child( "xsd:choice" ); - targetChoiceNode.appendAttribute( "minOccurs" ) = "0"; - targetChoiceNode.appendAttribute( "maxOccurs" ) = "unbounded"; + targetChoiceNode.append_attribute( "minOccurs" ) = "0"; + targetChoiceNode.append_attribute( "maxOccurs" ) = "unbounded"; } // Get a list of the subgroup names in alphabetic order @@ -175,13 +175,13 @@ void SchemaConstruction( Group & group, { // Enforce uniqueness of element names // Note: this must be done at the parent element level - xmlWrapper::xmlNode uniqueNameNode = targetIncludeNode.appendChild( "xsd:unique" ); + xmlWrapper::xmlNode uniqueNameNode = targetIncludeNode.append_child( "xsd:unique" ); string uniqueNameNodeStr = targetName + subName + "UniqueName"; - uniqueNameNode.appendAttribute( "name" ) = uniqueNameNodeStr.c_str(); - xmlWrapper::xmlNode uniqueNameSelector = uniqueNameNode.appendChild( "xsd:selector" ); - uniqueNameSelector.appendAttribute( "xpath" ) = subName.c_str(); - xmlWrapper::xmlNode uniqueNameField = uniqueNameNode.appendChild( "xsd:field" ); - uniqueNameField.appendAttribute( "xpath" ) = "@name"; + uniqueNameNode.append_attribute( "name" ) = uniqueNameNodeStr.c_str(); + xmlWrapper::xmlNode uniqueNameSelector = uniqueNameNode.append_child( "xsd:selector" ); + uniqueNameSelector.append_attribute( "xpath" ) = subName.c_str(); + xmlWrapper::xmlNode uniqueNameField = uniqueNameNode.append_child( "xsd:field" ); + uniqueNameField.append_attribute( "xpath" ) = "@name"; } SchemaConstruction( subGroup, schemaRoot, targetChoiceNode, documentationType ); @@ -232,14 +232,14 @@ void SchemaConstruction( Group & group, commentString += " => " + stringutilities::join( registrars.begin(), registrars.end(), ", " ); } - xmlWrapper::xmlNode commentNode = targetTypeDefNode.appendChild( xmlWrapper::xmlNodeType::node_comment ); + xmlWrapper::xmlNode commentNode = targetTypeDefNode.append_child( xmlWrapper::xmlTypes::node_comment ); commentNode.set_value( commentString.c_str()); // Write the valid schema attributes // Basic attributes - xmlWrapper::xmlNode attributeNode = targetTypeDefNode.appendChild( "xsd:attribute" ); - attributeNode.appendAttribute( "name" ) = attributeName.c_str(); + xmlWrapper::xmlNode attributeNode = targetTypeDefNode.append_child( "xsd:attribute" ); + attributeNode.append_attribute( "name" ) = attributeName.c_str(); string const wrappedTypeName = rtTypes::typeNames( wrapper.getTypeId() ); string const sanitizedName = std::regex_replace( wrappedTypeName, std::regex( "::" ), "_" ); @@ -248,7 +248,7 @@ void SchemaConstruction( Group & group, string const xmlSafeName = std::regex_replace( sanitizedName, std::regex( "std_(__cxx11_basic_)?string(<\\s*char,\\s*std_char_traits,\\s*std_allocator\\s*>)?" ), "string" ); GEOS_LOG_VAR( wrappedTypeName ); GEOS_LOG_VAR( xmlSafeName ); - attributeNode.appendAttribute( "type" ) = xmlSafeName.c_str(); + attributeNode.append_attribute( "type" ) = xmlSafeName.c_str(); // Check if the attribute has a previously unseen non-simple type with a custom validation regex if( schemaRoot.find_child_by_attribute( "xsd:simpleType", "name", xmlSafeName.c_str() ).empty() ) @@ -271,12 +271,12 @@ void SchemaConstruction( Group & group, { if( wrapper.hasDefaultValue() ) { - attributeNode.appendAttribute( "default" ) = wrapper.getDefaultValueString().c_str(); + attributeNode.append_attribute( "default" ) = wrapper.getDefaultValueString().c_str(); } } else if( documentationType == 0 ) { - attributeNode.appendAttribute( "use" ) = "required"; + attributeNode.append_attribute( "use" ) = "required"; } } } @@ -288,13 +288,13 @@ void SchemaConstruction( Group & group, // Only add this attribute if not present if( targetTypeDefNode.find_child_by_attribute( "xsd:attribute", "name", "name" ).empty()) { - xmlWrapper::xmlNode commentNode = targetTypeDefNode.appendChild( xmlWrapper::xmlNodeType::node_comment ); + xmlWrapper::xmlNode commentNode = targetTypeDefNode.append_child( xmlWrapper::xmlTypes::node_comment ); commentNode.set_value( "name => A name is required for any non-unique nodes" ); - xmlWrapper::xmlNode attributeNode = targetTypeDefNode.appendChild( "xsd:attribute" ); - attributeNode.appendAttribute( "name" ) = "name"; - attributeNode.appendAttribute( "type" ) = "string"; - attributeNode.appendAttribute( "use" ) = "required"; + xmlWrapper::xmlNode attributeNode = targetTypeDefNode.append_child( "xsd:attribute" ); + attributeNode.append_attribute( "name" ) = "name"; + attributeNode.append_attribute( "type" ) = "string"; + attributeNode.append_attribute( "use" ) = "required"; } } } diff --git a/src/coreComponents/unitTests/fluidFlowTests/testCompFlowUtils.hpp b/src/coreComponents/unitTests/fluidFlowTests/testCompFlowUtils.hpp index 55a06783d1d..5a192240183 100644 --- a/src/coreComponents/unitTests/fluidFlowTests/testCompFlowUtils.hpp +++ b/src/coreComponents/unitTests/fluidFlowTests/testCompFlowUtils.hpp @@ -205,14 +205,14 @@ void setupProblemFromXML( ProblemManager & problemManager, char const * const xm DomainPartition & domain = problemManager.getDomainPartition(); constitutive::ConstitutiveManager & constitutiveManager = domain.getConstitutiveManager(); - xmlWrapper::xmlNode topLevelNode = xmlProblemNode.getChild( constitutiveManager.getName().c_str()); + xmlWrapper::xmlNode topLevelNode = xmlProblemNode.child( constitutiveManager.getName().c_str()); constitutiveManager.processInputFileRecursive( xmlDocument, topLevelNode ); MeshManager & meshManager = problemManager.getGroup< MeshManager >( problemManager.groupKeys.meshManager ); meshManager.generateMeshLevels( domain ); ElementRegionManager & elementManager = domain.getMeshBody( 0 ).getBaseDiscretization().getElemManager(); - topLevelNode = xmlProblemNode.getChild( elementManager.getName().c_str()); + topLevelNode = xmlProblemNode.child( elementManager.getName().c_str()); elementManager.processInputFileRecursive( xmlDocument, topLevelNode ); problemManager.problemSetup(); diff --git a/src/coreComponents/unitTests/fluidFlowTests/testSingleFlowUtils.hpp b/src/coreComponents/unitTests/fluidFlowTests/testSingleFlowUtils.hpp index 859c1e01da2..c3c58a63ea3 100644 --- a/src/coreComponents/unitTests/fluidFlowTests/testSingleFlowUtils.hpp +++ b/src/coreComponents/unitTests/fluidFlowTests/testSingleFlowUtils.hpp @@ -97,14 +97,14 @@ void setupProblemFromXML( ProblemManager & problemManager, char const * const xm DomainPartition & domain = problemManager.getDomainPartition(); constitutive::ConstitutiveManager & constitutiveManager = domain.getConstitutiveManager(); - xmlWrapper::xmlNode topLevelNode = xmlProblemNode.getChild( constitutiveManager.getName().c_str()); + xmlWrapper::xmlNode topLevelNode = xmlProblemNode.child( constitutiveManager.getName().c_str()); constitutiveManager.processInputFileRecursive( xmlDocument, topLevelNode ); MeshManager & meshManager = problemManager.getGroup< MeshManager >( problemManager.groupKeys.meshManager ); meshManager.generateMeshLevels( domain ); ElementRegionManager & elementManager = domain.getMeshBody( 0 ).getBaseDiscretization().getElemManager(); - topLevelNode = xmlProblemNode.getChild( elementManager.getName().c_str()); + topLevelNode = xmlProblemNode.child( elementManager.getName().c_str()); elementManager.processInputFileRecursive( xmlDocument, topLevelNode ); problemManager.problemSetup(); diff --git a/src/coreComponents/unitTests/linearAlgebraTests/testDofManagerUtils.hpp b/src/coreComponents/unitTests/linearAlgebraTests/testDofManagerUtils.hpp index 08b0ca70b77..06f28489f5c 100644 --- a/src/coreComponents/unitTests/linearAlgebraTests/testDofManagerUtils.hpp +++ b/src/coreComponents/unitTests/linearAlgebraTests/testDofManagerUtils.hpp @@ -61,7 +61,7 @@ void setupProblemFromXML( ProblemManager * const problemManager, char const * co meshManager.generateMeshLevels( domain ); ElementRegionManager & elementManager = domain.getMeshBody( 0 ).getBaseDiscretization().getElemManager(); - xmlWrapper::xmlNode topLevelNode = xmlProblemNode.getChild( elementManager.getName().c_str() ); + xmlWrapper::xmlNode topLevelNode = xmlProblemNode.child( elementManager.getName().c_str() ); elementManager.processInputFileRecursive( xmlDocument, topLevelNode ); elementManager.postProcessInputRecursive(); diff --git a/src/coreComponents/unitTests/meshTests/testMeshGeneration.cpp b/src/coreComponents/unitTests/meshTests/testMeshGeneration.cpp index 14b1c206d1c..8bc67e65dbe 100644 --- a/src/coreComponents/unitTests/meshTests/testMeshGeneration.cpp +++ b/src/coreComponents/unitTests/meshTests/testMeshGeneration.cpp @@ -112,7 +112,7 @@ class MeshGenerationTest : public ::testing::Test meshManager.generateMeshLevels( domain ); ElementRegionManager & elementManager = domain.getMeshBody( 0 ).getBaseDiscretization().getElemManager(); - xmlWrapper::xmlNode topLevelNode = xmlProblemNode.getChild( elementManager.getName().c_str() ); + xmlWrapper::xmlNode topLevelNode = xmlProblemNode.child( elementManager.getName().c_str() ); elementManager.processInputFileRecursive( xmlDocument, topLevelNode ); elementManager.postProcessInputRecursive(); diff --git a/src/coreComponents/unitTests/virtualElementTests/testConformingVirtualElementOrder1.cpp b/src/coreComponents/unitTests/virtualElementTests/testConformingVirtualElementOrder1.cpp index 5f3f4216e2c..4b7bc185ef3 100644 --- a/src/coreComponents/unitTests/virtualElementTests/testConformingVirtualElementOrder1.cpp +++ b/src/coreComponents/unitTests/virtualElementTests/testConformingVirtualElementOrder1.cpp @@ -301,7 +301,7 @@ TEST( ConformingVirtualElementOrder1, hexahedra ) meshManager.generateMeshLevels( domain ); MeshLevel & mesh = domain.getMeshBody( 0 ).getBaseDiscretization(); ElementRegionManager & elementManager = mesh.getElemManager(); - xmlWrapper::xmlNode topLevelNode = xmlProblemNode.getChild( elementManager.getName().c_str() ); + xmlWrapper::xmlNode topLevelNode = xmlProblemNode.child( elementManager.getName().c_str() ); elementManager.processInputFileRecursive( inputFile, topLevelNode ); elementManager.postProcessInputRecursive(); problemManager.problemSetup(); @@ -354,7 +354,7 @@ TEST( ConformingVirtualElementOrder1, wedges ) meshManager.generateMeshLevels( domain ); MeshLevel & mesh = domain.getMeshBody( 0 ).getBaseDiscretization(); ElementRegionManager & elementManager = mesh.getElemManager(); - xmlWrapper::xmlNode topLevelNode = xmlProblemNode.getChild( elementManager.getName().c_str() ); + xmlWrapper::xmlNode topLevelNode = xmlProblemNode.child( elementManager.getName().c_str() ); elementManager.processInputFileRecursive( inputFile, topLevelNode ); elementManager.postProcessInputRecursive(); problemManager.problemSetup(); diff --git a/src/coreComponents/unitTests/xmlTests/testXMLFile.cpp b/src/coreComponents/unitTests/xmlTests/testXMLFile.cpp index fed2015aaa2..49ad4a301dd 100644 --- a/src/coreComponents/unitTests/xmlTests/testXMLFile.cpp +++ b/src/coreComponents/unitTests/xmlTests/testXMLFile.cpp @@ -61,7 +61,7 @@ void getElementsRecursive( xmlDocument const & document, xmlNode const & targetN // The Group name will be the name attribute value, or the node tag name if the name attribute // doesn't exist. string const groupName = [&]() { - xmlAttribute nameAtt = targetNode.getAttribute( "name" ); + xmlAttribute nameAtt = targetNode.attribute( "name" ); return nameAtt ? string( nameAtt.value() ) : string( targetNode.name() ); }(); From c03e81f2e323ac9910cf09d022239c9d8c3cb6b0 Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Mon, 10 Jul 2023 10:34:26 +0200 Subject: [PATCH 52/85] Merge commit '08ba8f7e2c04af6ad07ff7d3ddc54f091b8e8564' into feature/rey/error-xml-line-2 --- .github/workflows/ci_tests.yml | 35 +- .travis.yml | 12 +- host-configs/LLNL/lassen-base.cmake | 24 +- .../LLNL/lassen-clang-10-cuda-11.cmake | 16 + ...am.cmake => lassen-clang-13-cuda-11.cmake} | 14 +- host-configs/LLNL/lassen-clang10-cuda11.cmake | 30 - host-configs/LLNL/lassen-clang13-cuda11.cmake | 30 - .../LLNL/lassen-clang@upstream-NoMPI.cmake | 21 - ...8.3.1.cmake => lassen-gcc-8-cuda-11.cmake} | 2 +- host-configs/LLNL/quartz-base.cmake | 4 + ...z-clang@14.cmake => quartz-clang-14.cmake} | 2 +- ...uartz-gcc@12.cmake => quartz-gcc-12.cmake} | 2 +- ...z-icc@19.0.4.cmake => quartz-icc-19.cmake} | 2 +- ...ga-cce@15.0.0.cmake => tioga-cce-15.cmake} | 2 +- host-configs/Stanford/sherlock-base.cmake | 1 - ...4.1.2-openblas0.3.10-cuda11.5.0-sm80.cmake | 1 - .../pangea3-gcc8.4.1-openmpi-4.1.2.cmake | 5 +- host-configs/TOTAL/pecan-GPU.cmake | 6 +- .../c1-ppu/grav_seg_c1ppu_base.xml | 171 ++++ .../c1-ppu/grav_seg_c1ppu_drain.xml | 74 ++ .../c1-ppu/grav_seg_c1ppu_hyst.xml | 74 ++ .../tables/drainagePhaseVolFraction_gas.txt | 19 + .../tables/drainagePhaseVolFraction_water.txt | 16 + .../c1-ppu/tables/drainageRelPerm_gas.txt | 19 + .../c1-ppu/tables/drainageRelPerm_water.txt | 16 + .../tables/imbibitionPhaseVolFraction_gas.txt | 11 + .../imbibitionPhaseVolFraction_water.txt | 12 + .../c1-ppu/tables/imbibitionRelPerm_gas.txt | 11 + .../c1-ppu/tables/imbibitionRelPerm_water.txt | 12 + .../c1-ppu/tables/pvdg.txt | 3 + .../c1-ppu/tables/pvtw.txt | 2 + .../Class09Pb3/class09_pb3_benchmark.xml | 56 +- .../Class09Pb3/class09_pb3_smoke_3d.xml | 56 +- .../benchmarks/Egg/deadOilEgg_benchmark.xml | 761 +++++++++--------- .../benchmarks/Egg/deadOilEgg_smoke_3d.xml | 761 +++++++++--------- .../black_oil_wells_saturated_3d.xml | 57 +- .../black_oil_wells_unsaturated_3d.xml | 57 +- .../compositional_multiphase_wells_1d.xml | 61 +- .../compositional_multiphase_wells_2d.xml | 122 ++- .../dead_oil_wells_2d.xml | 128 ++- .../dead_oil_wells_hybrid_2d.xml | 122 ++- .../simpleCo2InjTutorial_smoke.xml | 30 +- .../staircase_co2_wells_3d.xml | 63 +- .../staircase_co2_wells_hybrid_3d.xml | 61 +- .../PoroElastic_staircase_co2_3d.xml | 72 +- .../PoroElastic_staircase_singlephase_3d.xml | 72 +- .../singlePhaseFlow/incompressible_pebi3d.xml | 1 + .../pebi3d_with_properties.vtu | Bin 886538 -> 186388 bytes .../compressible_single_phase_wells_1d.xml | 61 +- ...pressible_single_phase_wells_hybrid_1d.xml | 61 +- .../incompressible_single_phase_wells_2d.xml | 138 ++-- ...pressible_single_phase_wells_hybrid_2d.xml | 138 ++-- .../staircase_single_phase_wells_3d.xml | 61 +- ...staircase_single_phase_wells_hybrid_3d.xml | 61 +- integratedTests | 2 +- scripts/ci_build_and_test.sh | 2 +- src/CMakeLists.txt | 6 +- src/cmake/GeosxOptions.cmake | 13 +- src/coreComponents/LvArray | 2 +- .../codingUtilities/Utilities.hpp | 5 +- .../codingUtilities/tests/testGeosxTraits.cpp | 2 +- src/coreComponents/common/Format.hpp | 38 + .../common/GEOS_RAJA_Interface.hpp | 25 +- .../common/unitTests/testLifoStorage.cpp | 4 +- .../fluid/multifluid/MultiFluidUtils.hpp | 13 + .../functions/CubicEOSPhaseModel.hpp | 227 +++++- .../constitutive/unitTests/TestFluid.hpp | 129 +++ .../constitutive/unitTests/testCubicEOS.cpp | 498 ++++++++++-- .../dataRepository/ExecutableGroup.hpp | 2 +- .../dataRepository/ObjectCatalog.hpp | 4 +- .../dataRepository/wrapperHelpers.hpp | 4 +- src/coreComponents/events/PeriodicEvent.cpp | 10 +- .../fileIO/vtk/VTKPolyDataWriterInterface.cpp | 2 +- .../finiteVolume/FluxApproximationBase.cpp | 11 + .../finiteVolume/FluxApproximationBase.hpp | 46 ++ .../TwoPointFluxApproximation.cpp | 5 +- .../linearAlgebra/CMakeLists.txt | 2 + .../linearAlgebra/DofManager.cpp | 130 ++- .../linearAlgebra/DofManager.hpp | 39 +- .../CompositionalMultiphaseFVM.hpp | 2 - .../CompositionalMultiphaseReservoirFVM.hpp | 2 - ...positionalMultiphaseReservoirHybridFVM.hpp | 2 +- .../ReactiveCompositionalMultiphaseOBL.hpp | 1 - .../ThermalCompositionalMultiphaseFVM.hpp | 2 - .../ThermalMultiphasePoromechanics.hpp | 3 + .../ThermalSinglePhasePoromechanics.hpp | 3 + .../linearAlgebra/unitTests/CMakeLists.txt | 3 +- .../testReverseCutHillMcKeeOrdering.cpp | 121 +++ .../utilities/ReverseCutHillMcKeeOrdering.cpp | 367 +++++++++ .../utilities/ReverseCutHillMcKeeOrdering.hpp | 49 ++ .../mainInterface/ProblemManager.cpp | 10 +- .../mainInterface/ProblemManager.hpp | 2 +- src/coreComponents/mesh/CMakeLists.txt | 6 + src/coreComponents/mesh/CellElementRegion.cpp | 5 +- src/coreComponents/mesh/CellElementRegion.hpp | 2 +- .../mesh/CellElementSubRegion.cpp | 8 +- .../mesh/CellElementSubRegion.hpp | 2 +- src/coreComponents/mesh/DomainPartition.cpp | 13 +- src/coreComponents/mesh/DomainPartition.hpp | 19 - src/coreComponents/mesh/ElementRegionBase.hpp | 2 +- .../mesh/ElementRegionManager.cpp | 25 +- .../mesh/ElementRegionManager.hpp | 6 +- src/coreComponents/mesh/MeshBody.hpp | 19 +- src/coreComponents/mesh/MeshManager.cpp | 23 +- src/coreComponents/mesh/PerforationData.cpp | 8 +- src/coreComponents/mesh/PerforationData.hpp | 8 +- .../mesh/SurfaceElementRegion.cpp | 2 +- .../mesh/SurfaceElementRegion.hpp | 2 +- src/coreComponents/mesh/WellElementRegion.cpp | 20 +- src/coreComponents/mesh/WellElementRegion.hpp | 8 +- .../mesh/WellElementSubRegion.cpp | 73 +- .../mesh/WellElementSubRegion.hpp | 25 +- .../mesh/generators/CellBlock.hpp | 4 +- .../mesh/generators/CellBlockABC.hpp | 4 +- .../mesh/generators/CellBlockManager.cpp | 22 + .../mesh/generators/CellBlockManager.hpp | 38 +- .../mesh/generators/CellBlockManagerABC.hpp | 22 + .../mesh/generators/InternalMeshGenerator.cpp | 62 +- .../mesh/generators/InternalMeshGenerator.hpp | 6 +- .../mesh/generators/InternalWellGenerator.cpp | 74 +- .../mesh/generators/InternalWellGenerator.hpp | 154 ++-- .../generators/InternalWellboreGenerator.cpp | 4 +- .../mesh/generators/LineBlock.cpp | 41 + .../mesh/generators/LineBlock.hpp | 251 ++++++ .../mesh/generators/LineBlockABC.hpp | 164 ++++ .../mesh/generators/MeshGeneratorBase.cpp | 50 +- .../mesh/generators/MeshGeneratorBase.hpp | 42 +- .../mesh/generators/PartitionDescriptor.hpp | 97 +++ .../mesh/generators/VTKMeshGenerator.cpp | 15 +- .../mesh/generators/VTKMeshGenerator.hpp | 5 +- .../mesh/generators/VTKUtilities.cpp | 66 +- .../mesh/generators/VTKUtilities.hpp | 2 +- .../mesh/generators/WellGeneratorBase.cpp | 59 ++ .../mesh/generators/WellGeneratorBase.hpp | 244 ++++++ .../mpiCommunications/SpatialPartition.cpp | 4 +- .../mpiCommunications/SpatialPartition.hpp | 39 +- .../fluidFlow/CompositionalMultiphaseFVM.cpp | 5 + .../CompositionalMultiphaseHybridFVM.cpp | 4 + .../fluidFlow/FlowSolverBase.cpp | 46 +- .../fluidFlow/FlowSolverBaseKernels.hpp | 54 +- ...positionalMultiphaseFVMKernelUtilities.hpp | 353 ++++++-- ...ermalCompositionalMultiphaseFVMKernels.hpp | 67 +- .../fluidFlow/SinglePhaseFVM.cpp | 4 +- ...lizedCompositionalMultiphaseFVMKernels.hpp | 5 +- ...ermalCompositionalMultiphaseFVMKernels.hpp | 9 +- .../AcousticFirstOrderWaveEquationSEM.cpp | 46 +- .../AcousticFirstOrderWaveEquationSEM.hpp | 11 +- ...cousticFirstOrderWaveEquationSEMKernel.hpp | 10 +- .../ElasticFirstOrderWaveEquationSEM.cpp | 55 +- .../ElasticFirstOrderWaveEquationSEM.hpp | 11 +- ...ElasticFirstOrderWaveEquationSEMKernel.hpp | 9 +- .../wavePropagation/WaveSolverUtils.hpp | 42 +- .../docs/AcousticFirstOrderSEM_other.rst | 2 + .../docs/ElasticFirstOrderSEM_other.rst | 2 + .../schema/docs/InternalMesh.rst | 1 + .../schema/docs/InternalMesh_other.rst | 11 +- .../schema/docs/InternalWell.rst | 1 - .../schema/docs/InternalWellbore.rst | 1 + .../schema/docs/InternalWellbore_other.rst | 19 +- src/coreComponents/schema/docs/Mesh.rst | 1 - src/coreComponents/schema/docs/Mesh_other.rst | 1 - .../schema/docs/TwoPointFluxApproximation.rst | 19 +- src/coreComponents/schema/docs/VTKMesh.rst | 1 + .../schema/docs/VTKMesh_other.rst | 11 +- src/coreComponents/schema/schema.xsd | 224 +++--- src/coreComponents/schema/schema.xsd.other | 12 +- src/coreComponents/schema/schemaUtilities.cpp | 3 +- ...eservoirCompositionalMultiphaseMSWells.cpp | 49 +- .../testReservoirSinglePhaseMSWells.cpp | 49 +- src/docs/doxygen/GeosxConfig.hpp | 10 +- src/docs/sphinx/buildGuide/Prerequisites.rst | 2 +- .../developerGuide/Contributing/CodeStyle.rst | 2 +- src/main/main.cpp | 8 +- 173 files changed, 5792 insertions(+), 2550 deletions(-) create mode 100644 host-configs/LLNL/lassen-clang-10-cuda-11.cmake rename host-configs/LLNL/{lassen-clang@upstream.cmake => lassen-clang-13-cuda-11.cmake} (53%) delete mode 100644 host-configs/LLNL/lassen-clang10-cuda11.cmake delete mode 100644 host-configs/LLNL/lassen-clang13-cuda11.cmake delete mode 100644 host-configs/LLNL/lassen-clang@upstream-NoMPI.cmake rename host-configs/LLNL/{lassen-gcc@8.3.1.cmake => lassen-gcc-8-cuda-11.cmake} (94%) rename host-configs/LLNL/{quartz-clang@14.cmake => quartz-clang-14.cmake} (90%) rename host-configs/LLNL/{quartz-gcc@12.cmake => quartz-gcc-12.cmake} (93%) rename host-configs/LLNL/{quartz-icc@19.0.4.cmake => quartz-icc-19.cmake} (95%) rename host-configs/LLNL/{tioga-cce@15.0.0.cmake => tioga-cce-15.cmake} (95%) create mode 100644 inputFiles/compositionalMultiphaseFlow/c1-ppu/grav_seg_c1ppu_base.xml create mode 100644 inputFiles/compositionalMultiphaseFlow/c1-ppu/grav_seg_c1ppu_drain.xml create mode 100644 inputFiles/compositionalMultiphaseFlow/c1-ppu/grav_seg_c1ppu_hyst.xml create mode 100644 inputFiles/compositionalMultiphaseFlow/c1-ppu/tables/drainagePhaseVolFraction_gas.txt create mode 100644 inputFiles/compositionalMultiphaseFlow/c1-ppu/tables/drainagePhaseVolFraction_water.txt create mode 100644 inputFiles/compositionalMultiphaseFlow/c1-ppu/tables/drainageRelPerm_gas.txt create mode 100644 inputFiles/compositionalMultiphaseFlow/c1-ppu/tables/drainageRelPerm_water.txt create mode 100644 inputFiles/compositionalMultiphaseFlow/c1-ppu/tables/imbibitionPhaseVolFraction_gas.txt create mode 100644 inputFiles/compositionalMultiphaseFlow/c1-ppu/tables/imbibitionPhaseVolFraction_water.txt create mode 100644 inputFiles/compositionalMultiphaseFlow/c1-ppu/tables/imbibitionRelPerm_gas.txt create mode 100644 inputFiles/compositionalMultiphaseFlow/c1-ppu/tables/imbibitionRelPerm_water.txt create mode 100644 inputFiles/compositionalMultiphaseFlow/c1-ppu/tables/pvdg.txt create mode 100644 inputFiles/compositionalMultiphaseFlow/c1-ppu/tables/pvtw.txt create mode 100644 src/coreComponents/constitutive/unitTests/TestFluid.hpp create mode 100644 src/coreComponents/linearAlgebra/unitTests/testReverseCutHillMcKeeOrdering.cpp create mode 100644 src/coreComponents/linearAlgebra/utilities/ReverseCutHillMcKeeOrdering.cpp create mode 100644 src/coreComponents/linearAlgebra/utilities/ReverseCutHillMcKeeOrdering.hpp create mode 100644 src/coreComponents/mesh/generators/LineBlock.cpp create mode 100644 src/coreComponents/mesh/generators/LineBlock.hpp create mode 100644 src/coreComponents/mesh/generators/LineBlockABC.hpp create mode 100644 src/coreComponents/mesh/generators/PartitionDescriptor.hpp create mode 100644 src/coreComponents/mesh/generators/WellGeneratorBase.cpp create mode 100644 src/coreComponents/mesh/generators/WellGeneratorBase.hpp diff --git a/.github/workflows/ci_tests.yml b/.github/workflows/ci_tests.yml index 9bf6ab084bd..f780ab0497e 100644 --- a/.github/workflows/ci_tests.yml +++ b/.github/workflows/ci_tests.yml @@ -7,7 +7,7 @@ concurrency: cancel-in-progress: true env: - GEOSX_TPL_TAG: 220-932 + GEOSX_TPL_TAG: 224-965 jobs: # Matrix jobs will be cancelled if PR is a draft. @@ -88,7 +88,8 @@ jobs: linux_builds: name: ${{ matrix.name }} - runs-on: ubuntu-22.04 +# runs-on: ubuntu-22.04 + runs-on: ${{ matrix.os }} needs: [check_pull_request_is_not_a_draft] strategy: @@ -98,6 +99,7 @@ jobs: include: - name: Ubuntu CUDA debug (20.04, clang 10.0.0 + gcc 9.4.0, open-mpi 4.0.3, cuda-11.2.152) DOCKER_REPOSITORY: geosx/ubuntu20.04-clang10.0.0-cuda11.2.152 + OS: Runner_4core_16GB CMAKE_BUILD_TYPE: Debug BUILD_AND_TEST_ARGS: "--disable-unit-tests --build-exe-only --disable-schema-deployment" ENABLE_HYPRE: ON @@ -106,45 +108,54 @@ jobs: - name: Ubuntu CUDA (20.04, clang 10.0.0 + gcc 9.4.0, open-mpi 4.0.3, cuda-11.2.152) DOCKER_REPOSITORY: geosx/ubuntu20.04-clang10.0.0-cuda11.2.152 +# OS: Runner_8core_32GB + OS: ubuntu-22.04 CMAKE_BUILD_TYPE: Release BUILD_AND_TEST_ARGS: "--disable-unit-tests --disable-schema-deployment" ENABLE_HYPRE: ON ENABLE_HYPRE_DEVICE: CUDA ENABLE_TRILINOS: OFF - - name: Centos (7.6, gcc 8.3.1, open-mpi 1.10.7, cuda 10.1.243) - DOCKER_REPOSITORY: geosx/centos7.6.1810-gcc8.3.1-cuda10.1.243 + - name: Centos (7.7, gcc 8.3.1, open-mpi 1.10.7, cuda 11.8.0) + DOCKER_REPOSITORY: geosx/centos7.7-gcc8.3.1-cuda11.8.0 + OS: ubuntu-22.04 CMAKE_BUILD_TYPE: Release BUILD_AND_TEST_ARGS: "--disable-unit-tests --disable-schema-deployment" - name: Ubuntu (20.04, gcc 9.3.0, open-mpi 4.0.3) DOCKER_REPOSITORY: geosx/ubuntu20.04-gcc9 + OS: ubuntu-22.04 CMAKE_BUILD_TYPE: Release - name: Ubuntu debug (20.04, gcc 10.3.0, open-mpi 4.0.3) DOCKER_REPOSITORY: geosx/ubuntu20.04-gcc10 + OS: ubuntu-22.04 CMAKE_BUILD_TYPE: Debug - name: Ubuntu (20.04, gcc 10.3.0, open-mpi 4.0.3) DOCKER_REPOSITORY: geosx/ubuntu20.04-gcc10 + OS: ubuntu-22.04 CMAKE_BUILD_TYPE: Release # Matrix jobs that deploy to Google Cloud - - name: Pecan GPU (centos 7.7, gcc 8.2.0, open-mpi 4.0.1, mkl 2019.5, cuda 10.2.89p2) - DOCKER_REPOSITORY: geosx/pecan-gpu-gcc8.2.0-openmpi4.0.1-mkl2019.5-cuda10.2.89p2 + - name: Pecan GPU (centos 7.7, gcc 8.2.0, open-mpi 4.0.1, mkl 2019.5, cuda 11.5.119) + DOCKER_REPOSITORY: geosx/pecan-gpu-gcc8.2.0-openmpi4.0.1-mkl2019.5-cuda11.5.119 + OS: ubuntu-22.04 CMAKE_BUILD_TYPE: Release - BUILD_AND_TEST_ARGS: "--disable-unit-tests --disable-schema-deployment" + BUILD_AND_TEST_ARGS: "--build-exe-only --disable-unit-tests --disable-schema-deployment" HOST_CONFIG: host-configs/TOTAL/pecan-GPU.cmake GCP_BUCKET: geosx/Pecan-GPU - name: Pecan CPU (centos 7.7, gcc 8.2.0, open-mpi 4.0.1, mkl 2019.5) DOCKER_REPOSITORY: geosx/pecan-cpu-gcc8.2.0-openmpi4.0.1-mkl2019.5 + OS: ubuntu-22.04 CMAKE_BUILD_TYPE: Release HOST_CONFIG: host-configs/TOTAL/pecan-CPU.cmake GCP_BUCKET: geosx/Pecan-CPU - name: Pangea 2 (centos 7.6, gcc 8.3.0, open-mpi 2.1.5, mkl 2019.3) DOCKER_REPOSITORY: geosx/pangea2-gcc8.3.0-openmpi2.1.5-mkl2019.3 + OS: ubuntu-22.04 CMAKE_BUILD_TYPE: Release GCP_BUCKET: geosx/Pangea2 ENABLE_HYPRE: ON @@ -152,6 +163,7 @@ jobs: - name: Sherlock CPU (centos 7.9.2009, gcc 10.1.0, open-mpi 4.1.2, openblas 0.3.10) DOCKER_REPOSITORY: geosx/sherlock-gcc10.1.0-openmpi4.1.2-openblas0.3.10-zlib1.2.11 + OS: ubuntu-22.04 CMAKE_BUILD_TYPE: Release HOST_CONFIG: host-configs/Stanford/sherlock-gcc10-ompi4.1.2-openblas0.3.10.cmake GCP_BUCKET: geosx/Sherlock-CPU @@ -160,6 +172,7 @@ jobs: - name: Ubuntu (22.04, gcc 11.2.0, open-mpi 4.1.2) DOCKER_REPOSITORY: geosx/ubuntu22.04-gcc11 + OS: ubuntu-22.04 CMAKE_BUILD_TYPE: Release GCP_BUCKET: geosx/ubuntu22.04-gcc11 @@ -216,3 +229,11 @@ jobs: GEOSX_BUNDLE=${TMP_DIR}/${GEOSX_EXPORT_DIR}.tar.gz tar czf ${GEOSX_BUNDLE} --directory=${TMP_DIR} ${GEOSX_EXPORT_DIR} CLOUDSDK_PYTHON=python3 gsutil cp -a public-read ${GEOSX_BUNDLE} gs://${GCP_BUCKET}/ + + # Convenience job - passes when all other jobs have passed. + check_that_all_jobs_succeeded: + runs-on: ubuntu-22.04 + needs: [check_pull_request_is_not_a_draft, check_pull_request_is_assigned, check_submodules, code_style, documentation, linux_builds] + steps: + - name: Success + run: "true" diff --git a/.travis.yml b/.travis.yml index b056b8f85f3..57637628300 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,7 @@ vm: env: global: - - GEOSX_TPL_TAG=223-942 + - GEOSX_TPL_TAG=224-965 - secure: CGs2uH6efq1Me6xJWRr0BnwtwxoujzlowC4FHXHdWbNOkPsXf7nCgdaW5vthfD3bhnOeEUQSrfxdhTRtyU/NfcKLmKgGBnZOdUG4/JJK4gDSJ2Wp8LZ/mB0QEoODKVxbh+YtoAiHe3y4M9PGCs+wkNDw/3eEU00cK12DZ6gad0RbLjI3xkhEr/ZEZDZkcYg9yHAhl5bmpqoh/6QGnIg8mxIqdAtGDw+6tT0EgUqjeqc5bG5WwsamKzJItHSXD5zx8IJAlgDk4EzEGjZe0m56YnNfb9iwqqUsmL3Cuwgs7ByVDYw78JC5Kv42YqoxA5BxMT2mFsEe37TpYNXlzofU7ma2Duw9DGXWQd4IkTCcBxlyR0I0bfo0TmgO+y7PYG9lIyHPUkENemdozsZcWamqqkqegiEdRhDVYlSRo3mu7iCwTS6ZTALliVyEYjYxYb7oAnR3cNywXjblTCI8oKfgLSY+8WijM9SRl57JruIHLkLMCjmRI+cZBfv5tS2tYQTBPkygGrigrrN77ZiC7/TGyfggSN0+y0oYtOAgqEnBcKcreiibMW7tKcV2Z1RFD9ZvIkSc1EXLUPDP8FX1oyhmqBMqVo8LksrYLDJHQ05+F3YNgl2taSt7uMjQ4e8iZ3/IjFeMnbylDw+cj/RbS520HXsFPbWFm2Pb9pceA9n6GnY= # The integrated test repository contains large data (using git lfs) and we do not use them here. @@ -31,7 +31,7 @@ __geosx_linux_build: &__geosx_linux_build # Optional BUILD_AND_TEST_ARGS to pass arguments to travis_build_and_test.sh script. # # We extract the location of the GEOSX_TPL from the container... - - GEOSX_TPL_DIR=$(docker run --rm ${DOCKER_REPOSITORY}:${GEOSX_TPL_TAG} /bin/bash -c 'echo ${GEOSX_TPL_DIR}') + - GEOSX_TPL_DIR=$(docker run --rm ${DOCKER_REPOSITORY}:${GEOSX_TPL_TAG} /bin/bash -c 'echo ${GEOSX_TPL_DIR}' | tail -1 ) # ... so we can install GEOSX alongside. This is assumed for bundling the binaries, so consider modifying with care. - GEOSX_DIR=${GEOSX_TPL_DIR}/../GEOSX-${TRAVIS_COMMIT:0:7} # We need to know where the code folder is mounted inside the container so we can run the script at the proper location! @@ -181,17 +181,17 @@ jobs: - ENABLE_HYPRE_DEVICE=CUDA - ENABLE_TRILINOS=OFF - stage: builds - name: Centos (7.6, gcc 8.3.1, open-mpi 1.10.7, cuda 10.1.243) + name: Centos (7.7, gcc 8.3.1, open-mpi 1.10.7, cuda 11.8.0) <<: *__geosx_linux_build env: - - DOCKER_REPOSITORY=geosx/centos7.6.1810-gcc8.3.1-cuda10.1.243 + - DOCKER_REPOSITORY=geosx/centos7.7-gcc8.3.1-cuda11.8.0 - CMAKE_BUILD_TYPE=Release - BUILD_AND_TEST_ARGS="--disable-unit-tests --build-exe-only --disable-schema-deployment" - stage: builds - name: Pecan GPU (centos 7.7, gcc 8.2.0, open-mpi 4.0.1, mkl 2019.5, cuda 10.2.89p2) + name: Pecan GPU (centos 7.7, gcc 8.2.0, open-mpi 4.0.1, mkl 2019.5, cuda 11.5.119) <<: *__geosx_auto_deploy env: - - DOCKER_REPOSITORY=geosx/pecan-gpu-gcc8.2.0-openmpi4.0.1-mkl2019.5-cuda10.2.89p2 + - DOCKER_REPOSITORY=geosx/pecan-gpu-gcc8.2.0-openmpi4.0.1-mkl2019.5-cuda11.5.119 - CMAKE_BUILD_TYPE=Release - BUILD_AND_TEST_ARGS="--disable-unit-tests --build-exe-only --disable-schema-deployment" - HOST_CONFIG=host-configs/TOTAL/pecan-GPU.cmake diff --git a/host-configs/LLNL/lassen-base.cmake b/host-configs/LLNL/lassen-base.cmake index 3f40d24af50..72747c4f7d8 100644 --- a/host-configs/LLNL/lassen-base.cmake +++ b/host-configs/LLNL/lassen-base.cmake @@ -27,20 +27,19 @@ set(ENABLE_OPENMP ON CACHE BOOL "" FORCE) # LvArray sets this to the CMAKE_CXX_COMPILER. set(CMAKE_CUDA_HOST_COMPILER ${MPI_CXX_COMPILER} CACHE STRING "") +set(ENABLE_CUDA_NVTOOLSEXT OFF CACHE BOOL "") + # ESSL -set(ENABLE_ESSL ON CACHE BOOL "") -set(ESSL_INCLUDE_DIRS /usr/tcetmp/packages/essl/essl-6.2.1/include CACHE STRING "") -set(ESSL_LIBRARIES /usr/tcetmp/packages/essl/essl-6.2.1/lib64/libesslsmpcuda.so - /usr/tce/packages/xl/xl-beta-2019.06.20/alllibs/libxlsmp.so - /usr/tce/packages/xl/xl-beta-2019.06.20/alllibs/libxlfmath.so - /usr/tce/packages/xl/xl-beta-2019.06.20/alllibs/libxlf90_r.so +set(ENABLE_ESSL ON CACHE BOOL "" FORCE ) +set(ESSL_INCLUDE_DIRS /usr/tcetmp/packages/essl/essl-6.3.0.2/include CACHE STRING "" FORCE ) +set(ESSL_LIBRARIES /usr/tcetmp/packages/essl/essl-6.3.0.2/lib64/libesslsmpcuda.so ${CUDA_TOOLKIT_ROOT_DIR}/lib64/libcublas.so + ${CUDA_TOOLKIT_ROOT_DIR}/lib64/libcublasLt.so ${CUDA_TOOLKIT_ROOT_DIR}/lib64/libcudart.so - /usr/tcetmp/packages/essl/essl-6.2.1/lib64/liblapackforessl.so - /usr/tcetmp/packages/essl/essl-6.2.1/lib64/liblapackforessl_.so - /usr/tce/packages/xl/xl-beta-2019.06.20/alllibs/libxl.a - CACHE PATH "") - + /usr/tcetmp/packages/essl/essl-6.3.0.2/lib64/liblapackforessl.so + /usr/tcetmp/packages/essl/essl-6.3.0.2/lib64/liblapackforessl_.so + CACHE PATH "" FORCE ) + # TPL set(ENABLE_PAPI OFF CACHE BOOL "") set(SILO_BUILD_TYPE powerpc64-unknown-linux-gnu CACHE STRING "") @@ -50,8 +49,6 @@ set(ENABLE_FESAPI OFF CACHE BOOL "" FORCE) set(ENABLE_PVTPackage ON CACHE BOOL "") set(ENABLE_PETSC OFF CACHE BOOL "" FORCE ) - - set( ENABLE_HYPRE_DEVICE "CUDA" CACHE STRING "" FORCE ) if( ${ENABLE_HYPRE_DEVICE} STREQUAL "HIP" OR ${ENABLE_HYPRE_DEVICE} STREQUAL "CUDA" ) set(ENABLE_TRILINOS OFF CACHE BOOL "" FORCE ) @@ -60,7 +57,6 @@ else() set(GEOSX_LA_INTERFACE "Trilinos" CACHE STRING "" FORCE ) endif() - # Documentation set(ENABLE_UNCRUSTIFY OFF CACHE BOOL "" FORCE) set(ENABLE_DOXYGEN OFF CACHE BOOL "" FORCE) diff --git a/host-configs/LLNL/lassen-clang-10-cuda-11.cmake b/host-configs/LLNL/lassen-clang-10-cuda-11.cmake new file mode 100644 index 00000000000..74c04803ed8 --- /dev/null +++ b/host-configs/LLNL/lassen-clang-10-cuda-11.cmake @@ -0,0 +1,16 @@ +include(${CMAKE_CURRENT_LIST_DIR}/../../src/coreComponents/LvArray/host-configs/LLNL/lassen-clang-10-cuda-11.cmake) + +# Fortran +set(CMAKE_Fortran_COMPILER /usr/tce/packages/xl/xl-2022.08.19-cuda-11.8.0/bin/xlf_r CACHE PATH "") +set(CMAKE_Fortran_FLAGS_RELEASE "-O3 -DNDEBUG -qarch=pwr9 -qtune=pwr9" CACHE STRING "") +set(FORTRAN_MANGLE_NO_UNDERSCORE ON CACHE BOOL "") +set(OpenMP_Fortran_FLAGS "-qsmp=omp" CACHE STRING "") +set(OpenMP_Fortran_LIB_NAMES "" CACHE STRING "") + +# MPI +set(MPI_HOME /usr/tce/packages/spectrum-mpi/spectrum-mpi-rolling-release-clang-10.0.1-gcc-8.3.1 CACHE PATH "") +set(MPI_Fortran_COMPILER /usr/tce/packages/spectrum-mpi/spectrum-mpi-rolling-release-xl-2022.08.19-cuda-11.8.0/bin/mpifort CACHE PATH "") + +include(${CMAKE_CURRENT_LIST_DIR}/lassen-base.cmake) + +set(ENABLE_CUDA_NVTOOLSEXT OFF CACHE BOOL "") \ No newline at end of file diff --git a/host-configs/LLNL/lassen-clang@upstream.cmake b/host-configs/LLNL/lassen-clang-13-cuda-11.cmake similarity index 53% rename from host-configs/LLNL/lassen-clang@upstream.cmake rename to host-configs/LLNL/lassen-clang-13-cuda-11.cmake index 7ad701783b7..428274e26d2 100644 --- a/host-configs/LLNL/lassen-clang@upstream.cmake +++ b/host-configs/LLNL/lassen-clang-13-cuda-11.cmake @@ -1,21 +1,17 @@ -include(${CMAKE_CURRENT_LIST_DIR}/../../src/coreComponents/LvArray/host-configs/LLNL/lassen-clang@upstream.cmake) +include(${CMAKE_CURRENT_LIST_DIR}/../../src/coreComponents/LvArray/host-configs/LLNL/lassen-clang-13-cuda-11.cmake) # Fortran -set(CMAKE_Fortran_COMPILER /usr/tce/packages/xl/xl-2020.11.12/bin/xlf_r CACHE PATH "") +set(CMAKE_Fortran_COMPILER /usr/tce/packages/xl/xl-2022.08.19-cuda-11.8.0/bin/xlf_r CACHE PATH "") set(CMAKE_Fortran_FLAGS_RELEASE "-O3 -DNDEBUG -qarch=pwr9 -qtune=pwr9" CACHE STRING "") set(FORTRAN_MANGLE_NO_UNDERSCORE ON CACHE BOOL "") set(OpenMP_Fortran_FLAGS "-qsmp=omp" CACHE STRING "") set(OpenMP_Fortran_LIB_NAMES "" CACHE STRING "") # MPI -set(MPI_HOME /usr/tce/packages/spectrum-mpi/spectrum-mpi-rolling-release-clang-upstream-2019.03.26 CACHE PATH "") -set(MPI_Fortran_COMPILER /usr/tce/packages/spectrum-mpi/spectrum-mpi-rolling-release-xl-2020.11.12/bin/mpifort CACHE PATH "") +set(MPI_HOME /usr/tce/packages/spectrum-mpi/spectrum-mpi-rolling-release-clang-13.0.1-gcc-8.3.1 CACHE PATH "") +set(MPI_Fortran_COMPILER /usr/tce/packages/spectrum-mpi/spectrum-mpi-rolling-release-xl-2022.08.19-cuda-11.8.0/bin/mpifort CACHE PATH "") include(${CMAKE_CURRENT_LIST_DIR}/lassen-base.cmake) -# HYPRE options -set( ENABLE_HYPRE_DEVICE "CUDA" CACHE STRING "" ) -set( ENABLE_HYPRE_MIXINT TRUE CACHE STRING "" ) +set(ENABLE_CUDA_NVTOOLSEXT ON CACHE BOOL "") -# ATS -set(ATS_ARGUMENTS "--ats jsrun_omp --ats jsrun_bind=packed" CACHE PATH "") diff --git a/host-configs/LLNL/lassen-clang10-cuda11.cmake b/host-configs/LLNL/lassen-clang10-cuda11.cmake deleted file mode 100644 index fb49855c72e..00000000000 --- a/host-configs/LLNL/lassen-clang10-cuda11.cmake +++ /dev/null @@ -1,30 +0,0 @@ -include(${CMAKE_CURRENT_LIST_DIR}/../../src/coreComponents/LvArray/host-configs/LLNL/lassen-clang10-cuda11.cmake) - -# Fortran -set(CMAKE_Fortran_COMPILER /usr/tce/packages/xl/xl-2022.08.19-cuda-11.2.0/bin/xlf_r CACHE PATH "") -set(CMAKE_Fortran_FLAGS_RELEASE "-O3 -DNDEBUG -qarch=pwr9 -qtune=pwr9" CACHE STRING "") -set(FORTRAN_MANGLE_NO_UNDERSCORE ON CACHE BOOL "") -set(OpenMP_Fortran_FLAGS "-qsmp=omp" CACHE STRING "") -set(OpenMP_Fortran_LIB_NAMES "" CACHE STRING "") - -# MPI -set(MPI_HOME /usr/tce/packages/spectrum-mpi/spectrum-mpi-rolling-release-clang-10.0.1-gcc-8.3.1 CACHE PATH "") -set(MPI_Fortran_COMPILER /usr/tce/packages/spectrum-mpi/spectrum-mpi-rolling-release-xl-2022.08.19-cuda-11.2.0/bin/mpifort CACHE PATH "") - -include(${CMAKE_CURRENT_LIST_DIR}/lassen-base.cmake) - -set(ENABLE_CUDA_NVTOOLSEXT OFF CACHE BOOL "") - -set(ENABLE_ESSL ON CACHE BOOL "" FORCE ) -set(ESSL_INCLUDE_DIRS /usr/tcetmp/packages/essl/essl-6.3.0.2/include CACHE STRING "" FORCE ) -set(ESSL_LIBRARIES /usr/tcetmp/packages/essl/essl-6.3.0.2/lib64/libesslsmpcuda.so - /usr/tce/packages/xl/xl-beta-2019.06.20/alllibs/libxlsmp.so - /usr/tce/packages/xl/xl-beta-2019.06.20/alllibs/libxlfmath.so - /usr/tce/packages/xl/xl-beta-2019.06.20/alllibs/libxlf90_r.so - ${CUDA_TOOLKIT_ROOT_DIR}/lib64/libcublas.so - ${CUDA_TOOLKIT_ROOT_DIR}/lib64/libcublasLt.so - ${CUDA_TOOLKIT_ROOT_DIR}/lib64/libcudart.so - /usr/tcetmp/packages/essl/essl-6.3.0.2/lib64/liblapackforessl.so - /usr/tcetmp/packages/essl/essl-6.3.0.2/lib64/liblapackforessl_.so - /usr/tce/packages/xl/xl-beta-2019.06.20/alllibs/libxl.a - CACHE PATH "" FORCE ) \ No newline at end of file diff --git a/host-configs/LLNL/lassen-clang13-cuda11.cmake b/host-configs/LLNL/lassen-clang13-cuda11.cmake deleted file mode 100644 index f314c3c868c..00000000000 --- a/host-configs/LLNL/lassen-clang13-cuda11.cmake +++ /dev/null @@ -1,30 +0,0 @@ -include(${CMAKE_CURRENT_LIST_DIR}/../../src/coreComponents/LvArray/host-configs/LLNL/lassen-clang13-cuda11.cmake) - -# Fortran -set(CMAKE_Fortran_COMPILER /usr/tce/packages/xl/xl-2022.08.19-cuda-11.6.1/bin/xlf_r CACHE PATH "") -set(CMAKE_Fortran_FLAGS_RELEASE "-O3 -DNDEBUG -qarch=pwr9 -qtune=pwr9" CACHE STRING "") -set(FORTRAN_MANGLE_NO_UNDERSCORE ON CACHE BOOL "") -set(OpenMP_Fortran_FLAGS "-qsmp=omp" CACHE STRING "") -set(OpenMP_Fortran_LIB_NAMES "" CACHE STRING "") - -# MPI -set(MPI_HOME /usr/tce/packages/spectrum-mpi/spectrum-mpi-rolling-release-clang-13.0.1-gcc-8.3.1 CACHE PATH "") -set(MPI_Fortran_COMPILER /usr/tce/packages/spectrum-mpi/spectrum-mpi-rolling-release-xl-2022.08.19-cuda-11.6.1/bin/mpifort CACHE PATH "") - -include(${CMAKE_CURRENT_LIST_DIR}/lassen-base.cmake) - -set(ENABLE_CUDA_NVTOOLSEXT ON CACHE BOOL "") - -set(ENABLE_ESSL ON CACHE BOOL "" FORCE ) -set(ESSL_INCLUDE_DIRS /usr/tcetmp/packages/essl/essl-6.3.0.2/include CACHE STRING "" FORCE ) -set(ESSL_LIBRARIES /usr/tcetmp/packages/essl/essl-6.3.0.2/lib64/libesslsmpcuda.so - /usr/tce/packages/xl/xl-beta-2019.06.20/alllibs/libxlsmp.so - /usr/tce/packages/xl/xl-beta-2019.06.20/alllibs/libxlfmath.so - /usr/tce/packages/xl/xl-beta-2019.06.20/alllibs/libxlf90_r.so - ${CUDA_TOOLKIT_ROOT_DIR}/lib64/libcublas.so - ${CUDA_TOOLKIT_ROOT_DIR}/lib64/libcublasLt.so - ${CUDA_TOOLKIT_ROOT_DIR}/lib64/libcudart.so - /usr/tcetmp/packages/essl/essl-6.3.0.2/lib64/liblapackforessl.so - /usr/tcetmp/packages/essl/essl-6.3.0.2/lib64/liblapackforessl_.so - /usr/tce/packages/xl/xl-beta-2019.06.20/alllibs/libxl.a - CACHE PATH "" FORCE ) \ No newline at end of file diff --git a/host-configs/LLNL/lassen-clang@upstream-NoMPI.cmake b/host-configs/LLNL/lassen-clang@upstream-NoMPI.cmake deleted file mode 100644 index 90c016f9e4d..00000000000 --- a/host-configs/LLNL/lassen-clang@upstream-NoMPI.cmake +++ /dev/null @@ -1,21 +0,0 @@ -include(${CMAKE_CURRENT_LIST_DIR}/../../host-configs/LLNL/lassen-clang@upstream.cmake) - -set(CONFIG_NAME "lassen-clang@upstream-NoMPI" CACHE PATH "" FORCE) -set(GEOSX_TPL_ROOT_DIR /usr/gapps/GEOSX/thirdPartyLibs CACHE PATH "" FORCE) -set(GEOSX_TPL_DIR ${GEOSX_TPL_ROOT_DIR}/2020-09-18/install-${CONFIG_NAME}-release CACHE PATH "" FORCE) - -set(ENABLE_MPI OFF CACHE BOOL "" FORCE) -unset(MPI_ROOT CACHE ) -unset(MPI_C_COMPILER CACHE ) -unset(MPI_CXX_COMPILER CACHE ) -unset(MPI_Fortran_COMPILER CACHE ) -unset(MPIEXEC CACHE ) -unset(MPIEXEC_NUMPROC_FLAG CACHE ) -set(ENABLE_WRAP_ALL_TESTS_WITH_MPIEXEC OFF CACHE BOOL "") - -set(ENABLE_PVTPackage ON CACHE BOOL "" FORCE) -set(ENABLE_HYPRE OFF CACHE BOOL "" FORCE) -set(ENABLE_PETSC OFF CACHE BOOL "" FORCE) -set(ENABLE_PARMETIS OFF CACHE BOOL "" FORCE) -set(ENABLE_SUPERLU_DIST OFF CACHE BOOL "" FORCE) -set(CMAKE_CUDA_HOST_COMPILER ${CMAKE_CXX_COMPILER} CACHE STRING "" FORCE) diff --git a/host-configs/LLNL/lassen-gcc@8.3.1.cmake b/host-configs/LLNL/lassen-gcc-8-cuda-11.cmake similarity index 94% rename from host-configs/LLNL/lassen-gcc@8.3.1.cmake rename to host-configs/LLNL/lassen-gcc-8-cuda-11.cmake index 4d38f8094d9..fa12811e52e 100644 --- a/host-configs/LLNL/lassen-gcc@8.3.1.cmake +++ b/host-configs/LLNL/lassen-gcc-8-cuda-11.cmake @@ -1,4 +1,4 @@ -include(${CMAKE_CURRENT_LIST_DIR}/../../src/coreComponents/LvArray/host-configs/LLNL/lassen-gcc@8.3.1.cmake) +include(${CMAKE_CURRENT_LIST_DIR}/../../src/coreComponents/LvArray/host-configs/LLNL/lassen-gcc-8-cuda-11.cmake) # C++ # The "-march=native -mtune=native" which LvArray adds breaks the PVT package. diff --git a/host-configs/LLNL/quartz-base.cmake b/host-configs/LLNL/quartz-base.cmake index 0e1dce9560e..f5ff404044c 100644 --- a/host-configs/LLNL/quartz-base.cmake +++ b/host-configs/LLNL/quartz-base.cmake @@ -43,6 +43,10 @@ set(SPHINX_EXECUTABLE /usr/gapps/GEOSX/thirdPartyLibs/python/quartz-gcc-python/p set(ENABLE_FESAPI OFF CACHE BOOL "" FORCE) +# caliper +set(ENABLE_CALIPER ON CACHE BOOL "" FORCE) +set(ENABLE_CALIPER_HYPRE ON CACHE BOOL "" FORCE) + # MKL set(ENABLE_MKL ON CACHE BOOL "") set(MKL_ROOT /usr/tce/packages/mkl/mkl-2022.1.0) diff --git a/host-configs/LLNL/quartz-clang@14.cmake b/host-configs/LLNL/quartz-clang-14.cmake similarity index 90% rename from host-configs/LLNL/quartz-clang@14.cmake rename to host-configs/LLNL/quartz-clang-14.cmake index 73567edb2a1..797e6acb2f3 100644 --- a/host-configs/LLNL/quartz-clang@14.cmake +++ b/host-configs/LLNL/quartz-clang-14.cmake @@ -1,4 +1,4 @@ -include(${CMAKE_CURRENT_LIST_DIR}/../../src/coreComponents/LvArray/host-configs/LLNL/quartz-clang@14.cmake) +include(${CMAKE_CURRENT_LIST_DIR}/../../src/coreComponents/LvArray/host-configs/LLNL/quartz-clang-14.cmake) # Fortran set(CMAKE_Fortran_COMPILER /usr/tce/packages/gcc/gcc-12.1.1-magic/bin/gfortran CACHE PATH "") diff --git a/host-configs/LLNL/quartz-gcc@12.cmake b/host-configs/LLNL/quartz-gcc-12.cmake similarity index 93% rename from host-configs/LLNL/quartz-gcc@12.cmake rename to host-configs/LLNL/quartz-gcc-12.cmake index b3ca828a729..a4e92537ad6 100644 --- a/host-configs/LLNL/quartz-gcc@12.cmake +++ b/host-configs/LLNL/quartz-gcc-12.cmake @@ -1,4 +1,4 @@ -include(${CMAKE_CURRENT_LIST_DIR}/../../src/coreComponents/LvArray/host-configs/LLNL/quartz-gcc@12.cmake) +include(${CMAKE_CURRENT_LIST_DIR}/../../src/coreComponents/LvArray/host-configs/LLNL/quartz-gcc-12.cmake) # C++ # The "-march=native -mtune=native" which LvArray adds breaks the PVT package. diff --git a/host-configs/LLNL/quartz-icc@19.0.4.cmake b/host-configs/LLNL/quartz-icc-19.cmake similarity index 95% rename from host-configs/LLNL/quartz-icc@19.0.4.cmake rename to host-configs/LLNL/quartz-icc-19.cmake index c88c81a9949..a18d3f3da03 100644 --- a/host-configs/LLNL/quartz-icc@19.0.4.cmake +++ b/host-configs/LLNL/quartz-icc-19.cmake @@ -1,4 +1,4 @@ -include(${CMAKE_CURRENT_LIST_DIR}/../../src/coreComponents/LvArray/host-configs/LLNL/quartz-icc@19.0.4.cmake) +include(${CMAKE_CURRENT_LIST_DIR}/../../src/coreComponents/LvArray/host-configs/LLNL/quartz-icc-19.cmake) # Fortran set(CMAKE_Fortran_COMPILER ${COMPILER_DIR}/bin/intel64/ifort CACHE PATH "") diff --git a/host-configs/LLNL/tioga-cce@15.0.0.cmake b/host-configs/LLNL/tioga-cce-15.cmake similarity index 95% rename from host-configs/LLNL/tioga-cce@15.0.0.cmake rename to host-configs/LLNL/tioga-cce-15.cmake index 138c2555c47..e8da3052349 100644 --- a/host-configs/LLNL/tioga-cce@15.0.0.cmake +++ b/host-configs/LLNL/tioga-cce-15.cmake @@ -1,4 +1,4 @@ -include(${CMAKE_CURRENT_LIST_DIR}/../../src/coreComponents/LvArray/host-configs/LLNL/tioga-cce@15.0.0.cmake) +include(${CMAKE_CURRENT_LIST_DIR}/../../src/coreComponents/LvArray/host-configs/LLNL/tioga-cce-15.cmake) include(${CMAKE_CURRENT_LIST_DIR}/tioga-base.cmake) set( CONDUIT_DIR "${GEOSX_TPL_DIR}/conduit-0.8.7" CACHE PATH "" ) diff --git a/host-configs/Stanford/sherlock-base.cmake b/host-configs/Stanford/sherlock-base.cmake index 8b4a95c2bfe..2e45c12491c 100644 --- a/host-configs/Stanford/sherlock-base.cmake +++ b/host-configs/Stanford/sherlock-base.cmake @@ -21,7 +21,6 @@ set(ENABLE_WRAP_ALL_TESTS_WITH_MPIEXEC ON CACHE BOOL "") if(ENABLE_CUDA) set(CMAKE_CUDA_HOST_COMPILER ${MPI_CXX_COMPILER} CACHE STRING "") set(CMAKE_CUDA_COMPILER ${CUDA_TOOLKIT_ROOT_DIR}/bin/nvcc CACHE STRING "") - set(CMAKE_CUDA_STANDARD 14 CACHE STRING "") set(CMAKE_CUDA_FLAGS "-restrict -arch ${CUDA_ARCH} --expt-extended-lambda --expt-relaxed-constexpr -Werror cross-execution-space-call,reorder,deprecated-declarations" CACHE STRING "") set(CMAKE_CUDA_FLAGS_RELEASE "-O3 -DNDEBUG -Xcompiler -DNDEBUG -Xcompiler -O3" CACHE STRING "") set(CMAKE_CUDA_FLAGS_RELWITHDEBINFO "-g -lineinfo ${CMAKE_CUDA_FLAGS_RELEASE}" CACHE STRING "") diff --git a/host-configs/Stanford/sherlock-gcc10-ompi4.1.2-openblas0.3.10-cuda11.5.0-sm80.cmake b/host-configs/Stanford/sherlock-gcc10-ompi4.1.2-openblas0.3.10-cuda11.5.0-sm80.cmake index 9f5c8c95c5a..c87eee9c96e 100644 --- a/host-configs/Stanford/sherlock-gcc10-ompi4.1.2-openblas0.3.10-cuda11.5.0-sm80.cmake +++ b/host-configs/Stanford/sherlock-gcc10-ompi4.1.2-openblas0.3.10-cuda11.5.0-sm80.cmake @@ -15,7 +15,6 @@ set(CONFIG_NAME "sherlock-gcc10-ompi4.1.2-openblas0.3.10-cuda${CUDA_VERSION}-${C set(CMAKE_CUDA_HOST_COMPILER ${MPI_CXX_COMPILER} CACHE STRING "") set(CMAKE_CUDA_COMPILER ${CUDA_TOOLKIT_ROOT_DIR}/bin/nvcc CACHE STRING "") -set(CMAKE_CUDA_STANDARD 14 CACHE STRING "") set(CMAKE_CUDA_FLAGS "-restrict -arch ${CUDA_ARCH} --expt-extended-lambda --expt-relaxed-constexpr -Werror cross-execution-space-call,reorder,deprecated-declarations " CACHE STRING "") set(CMAKE_CUDA_FLAGS_RELEASE "-O3 -DNDEBUG -Xcompiler -DNDEBUG -Xcompiler -O3" CACHE STRING "") set(CMAKE_CUDA_FLAGS_RELWITHDEBINFO "-g -lineinfo ${CMAKE_CUDA_FLAGS_RELEASE}" CACHE STRING "") diff --git a/host-configs/TOTAL/pangea3-gcc8.4.1-openmpi-4.1.2.cmake b/host-configs/TOTAL/pangea3-gcc8.4.1-openmpi-4.1.2.cmake index 7061e89c6de..5f3c90ce11f 100644 --- a/host-configs/TOTAL/pangea3-gcc8.4.1-openmpi-4.1.2.cmake +++ b/host-configs/TOTAL/pangea3-gcc8.4.1-openmpi-4.1.2.cmake @@ -23,7 +23,6 @@ set(CMAKE_CXX_COMPILER g++ CACHE PATH "") set(CMAKE_CXX_FLAGS_RELEASE "-O3 -DNDEBUG -mcpu=power9 -mtune=power9" CACHE STRING "") set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-g ${CMAKE_CXX_FLAGS_RELEASE}" CACHE STRING "") set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g" CACHE STRING "") -set(CMAKE_CXX_STANDARD 14 CACHE STRING "") # Fortran options set(CMAKE_Fortran_COMPILER gfortran CACHE PATH "") @@ -57,9 +56,7 @@ if (DEFINED ENV{CUDA_ROOT}) set(CMAKE_CUDA_COMPILER ${CUDA_TOOLKIT_ROOT_DIR}/bin/nvcc CACHE STRING "") set(CUDA_ARCH sm_70 CACHE STRING "") set(CMAKE_CUDA_ARCHITECTURES 70 CACHE STRING "") - set(CMAKE_CUDA_STANDARD 14 CACHE STRING "") - ### The inclusion of -std=c++14 is a workaround for a cuda10/gcc8 bug ### - set(CMAKE_CUDA_FLAGS "-restrict -arch ${CUDA_ARCH} --expt-extended-lambda -Werror cross-execution-space-call,reorder,deprecated-declarations -Xcompiler -std=c++14" CACHE STRING "") + set(CMAKE_CUDA_FLAGS "-restrict -arch ${CUDA_ARCH} --expt-extended-lambda -Werror cross-execution-space-call,reorder,deprecated-declarations" CACHE STRING "") set(CMAKE_CUDA_FLAGS_RELEASE "-O3 -DNDEBUG -Xcompiler -DNDEBUG -Xcompiler -O3 -Xcompiler -mcpu=powerpc64le -Xcompiler -mtune=powerpc64le" CACHE STRING "") set(CMAKE_CUDA_FLAGS_RELWITHDEBINFO "-g -lineinfo ${CMAKE_CUDA_FLAGS_RELEASE}" CACHE STRING "") set(CMAKE_CUDA_FLAGS_DEBUG "-g -G -O0 -Xcompiler -O0" CACHE STRING "") diff --git a/host-configs/TOTAL/pecan-GPU.cmake b/host-configs/TOTAL/pecan-GPU.cmake index 0155ce192d6..1b6e419df99 100644 --- a/host-configs/TOTAL/pecan-GPU.cmake +++ b/host-configs/TOTAL/pecan-GPU.cmake @@ -3,14 +3,12 @@ include(${CMAKE_CURRENT_LIST_DIR}/pecan-CPU.cmake) # Now let's add what's dedicated to GPU. set(ENABLE_CUDA ON CACHE PATH "" FORCE) -set(CUDA_TOOLKIT_ROOT_DIR /hrtc/apps/cuda/10.2.89/x86_64 CACHE PATH "") +set(CUDA_TOOLKIT_ROOT_DIR /hrtc/apps/cuda/11.5.119/x86_64/centos7 CACHE PATH "") set(CMAKE_CUDA_HOST_COMPILER ${CMAKE_CXX_COMPILER} CACHE STRING "") set(CMAKE_CUDA_COMPILER ${CUDA_TOOLKIT_ROOT_DIR}/bin/nvcc CACHE STRING "") set(CUDA_ARCH sm_75 CACHE STRING "") -set(CMAKE_CUDA_STANDARD 14 CACHE STRING "") -### The inclusion of -std=c++14 is a workaround for a cuda10/gcc8 bug ### -set(CMAKE_CUDA_FLAGS "-restrict -arch ${CUDA_ARCH} --expt-relaxed-constexpr --expt-extended-lambda -Werror cross-execution-space-call,reorder,deprecated-declarations -Xcompiler -std=c++14" CACHE STRING "") +set(CMAKE_CUDA_FLAGS "-restrict -arch ${CUDA_ARCH} --expt-relaxed-constexpr --expt-extended-lambda -Werror cross-execution-space-call,reorder,deprecated-declarations" CACHE STRING "") set(CMAKE_CUDA_FLAGS_RELEASE "-O3 -DNDEBUG -Xcompiler -DNDEBUG -Xcompiler -O3" CACHE STRING "") set(CMAKE_CUDA_FLAGS_RELWITHDEBINFO "-g -lineinfo ${CMAKE_CUDA_FLAGS_RELEASE}" CACHE STRING "") set(CMAKE_CUDA_FLAGS_DEBUG "-g -G -O0 -Xcompiler -O0" CACHE STRING "") diff --git a/inputFiles/compositionalMultiphaseFlow/c1-ppu/grav_seg_c1ppu_base.xml b/inputFiles/compositionalMultiphaseFlow/c1-ppu/grav_seg_c1ppu_base.xml new file mode 100644 index 00000000000..d59b2b2c590 --- /dev/null +++ b/inputFiles/compositionalMultiphaseFlow/c1-ppu/grav_seg_c1ppu_base.xml @@ -0,0 +1,171 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inputFiles/compositionalMultiphaseFlow/c1-ppu/grav_seg_c1ppu_drain.xml b/inputFiles/compositionalMultiphaseFlow/c1-ppu/grav_seg_c1ppu_drain.xml new file mode 100644 index 00000000000..c30aae2d023 --- /dev/null +++ b/inputFiles/compositionalMultiphaseFlow/c1-ppu/grav_seg_c1ppu_drain.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inputFiles/compositionalMultiphaseFlow/c1-ppu/grav_seg_c1ppu_hyst.xml b/inputFiles/compositionalMultiphaseFlow/c1-ppu/grav_seg_c1ppu_hyst.xml new file mode 100644 index 00000000000..a82c573ad0f --- /dev/null +++ b/inputFiles/compositionalMultiphaseFlow/c1-ppu/grav_seg_c1ppu_hyst.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inputFiles/compositionalMultiphaseFlow/c1-ppu/tables/drainagePhaseVolFraction_gas.txt b/inputFiles/compositionalMultiphaseFlow/c1-ppu/tables/drainagePhaseVolFraction_gas.txt new file mode 100644 index 00000000000..31a86ba26cc --- /dev/null +++ b/inputFiles/compositionalMultiphaseFlow/c1-ppu/tables/drainagePhaseVolFraction_gas.txt @@ -0,0 +1,19 @@ +0.000 +0.010 +0.030 +0.050 +0.100 +0.150 +0.200 +0.250 +0.300 +0.350 +0.400 +0.450 +0.500 +0.550 +0.600 +0.650 +0.700 +0.750 +0.780 diff --git a/inputFiles/compositionalMultiphaseFlow/c1-ppu/tables/drainagePhaseVolFraction_water.txt b/inputFiles/compositionalMultiphaseFlow/c1-ppu/tables/drainagePhaseVolFraction_water.txt new file mode 100644 index 00000000000..470ef848551 --- /dev/null +++ b/inputFiles/compositionalMultiphaseFlow/c1-ppu/tables/drainagePhaseVolFraction_water.txt @@ -0,0 +1,16 @@ +0.22000 +0.25000 +0.30000 +0.35000 +0.40000 +0.45000 +0.50000 +0.55000 +0.60000 +0.65000 +0.66000 +0.68000 +0.72000 +0.82000 +0.91000 +1.00000 diff --git a/inputFiles/compositionalMultiphaseFlow/c1-ppu/tables/drainageRelPerm_gas.txt b/inputFiles/compositionalMultiphaseFlow/c1-ppu/tables/drainageRelPerm_gas.txt new file mode 100644 index 00000000000..f8686ac5297 --- /dev/null +++ b/inputFiles/compositionalMultiphaseFlow/c1-ppu/tables/drainageRelPerm_gas.txt @@ -0,0 +1,19 @@ +0.00000 +0.00200 +0.00700 +0.01000 +0.02000 +0.04000 +0.07500 +0.12700 +0.18000 +0.24000 +0.31000 +0.37300 +0.46000 +0.55000 +0.64000 +0.73000 +0.82500 +0.92000 +1.00000 diff --git a/inputFiles/compositionalMultiphaseFlow/c1-ppu/tables/drainageRelPerm_water.txt b/inputFiles/compositionalMultiphaseFlow/c1-ppu/tables/drainageRelPerm_water.txt new file mode 100644 index 00000000000..e7cc0d5c904 --- /dev/null +++ b/inputFiles/compositionalMultiphaseFlow/c1-ppu/tables/drainageRelPerm_water.txt @@ -0,0 +1,16 @@ +0.00000 +0.00100 +0.00300 +0.01000 +0.01800 +0.03500 +0.04000 +0.05700 +0.08800 +0.14500 +0.16000 +0.19000 +0.26300 +0.45500 +0.69200 +1. diff --git a/inputFiles/compositionalMultiphaseFlow/c1-ppu/tables/imbibitionPhaseVolFraction_gas.txt b/inputFiles/compositionalMultiphaseFlow/c1-ppu/tables/imbibitionPhaseVolFraction_gas.txt new file mode 100644 index 00000000000..9bc2c7bb41e --- /dev/null +++ b/inputFiles/compositionalMultiphaseFlow/c1-ppu/tables/imbibitionPhaseVolFraction_gas.txt @@ -0,0 +1,11 @@ +0.300 +0.350 +0.400 +0.450 +0.500 +0.550 +0.600 +0.650 +0.700 +0.750 +0.780 diff --git a/inputFiles/compositionalMultiphaseFlow/c1-ppu/tables/imbibitionPhaseVolFraction_water.txt b/inputFiles/compositionalMultiphaseFlow/c1-ppu/tables/imbibitionPhaseVolFraction_water.txt new file mode 100644 index 00000000000..6c8c2ec7dab --- /dev/null +++ b/inputFiles/compositionalMultiphaseFlow/c1-ppu/tables/imbibitionPhaseVolFraction_water.txt @@ -0,0 +1,12 @@ +0.22000 +0.25000 +0.30000 +0.35000 +0.40000 +0.45000 +0.50000 +0.55000 +0.60000 +0.65000 +0.66000 +0.70000 diff --git a/inputFiles/compositionalMultiphaseFlow/c1-ppu/tables/imbibitionRelPerm_gas.txt b/inputFiles/compositionalMultiphaseFlow/c1-ppu/tables/imbibitionRelPerm_gas.txt new file mode 100644 index 00000000000..3d969ee4d91 --- /dev/null +++ b/inputFiles/compositionalMultiphaseFlow/c1-ppu/tables/imbibitionRelPerm_gas.txt @@ -0,0 +1,11 @@ +0.0000 +0.03361965 +0.09509072 +0.17469281 +0.26895718 +0.37587908 +0.49410588 +0.62264458 +0.76072577 +0.90773047 +1. diff --git a/inputFiles/compositionalMultiphaseFlow/c1-ppu/tables/imbibitionRelPerm_water.txt b/inputFiles/compositionalMultiphaseFlow/c1-ppu/tables/imbibitionRelPerm_water.txt new file mode 100644 index 00000000000..d09ddcb2d22 --- /dev/null +++ b/inputFiles/compositionalMultiphaseFlow/c1-ppu/tables/imbibitionRelPerm_water.txt @@ -0,0 +1,12 @@ +0 +0.0156 +0.0680 +0.1409 +0.2296 +0.3317 +0.4455 +0.5700 +0.7044 +0.8479 +0.8776 +0.9382 diff --git a/inputFiles/compositionalMultiphaseFlow/c1-ppu/tables/pvdg.txt b/inputFiles/compositionalMultiphaseFlow/c1-ppu/tables/pvdg.txt new file mode 100644 index 00000000000..c6a5543817b --- /dev/null +++ b/inputFiles/compositionalMultiphaseFlow/c1-ppu/tables/pvdg.txt @@ -0,0 +1,3 @@ +# P[Pa] Bo[m3/sm3] Visc(Pa.s) +0 1.0 0.000023 +10000000 1.0 0.000023 diff --git a/inputFiles/compositionalMultiphaseFlow/c1-ppu/tables/pvtw.txt b/inputFiles/compositionalMultiphaseFlow/c1-ppu/tables/pvtw.txt new file mode 100644 index 00000000000..45a9437f743 --- /dev/null +++ b/inputFiles/compositionalMultiphaseFlow/c1-ppu/tables/pvtw.txt @@ -0,0 +1,2 @@ +#Pref[Pa] Bw[m3/sm3] Cp[1/Pa] Visc[Pa.s] +10000000 1.0 0.0000000000001 0.00055 diff --git a/inputFiles/compositionalMultiphaseWell/benchmarks/Class09Pb3/class09_pb3_benchmark.xml b/inputFiles/compositionalMultiphaseWell/benchmarks/Class09Pb3/class09_pb3_benchmark.xml index fd5b0db7d32..95b03293b68 100644 --- a/inputFiles/compositionalMultiphaseWell/benchmarks/Class09Pb3/class09_pb3_benchmark.xml +++ b/inputFiles/compositionalMultiphaseWell/benchmarks/Class09Pb3/class09_pb3_benchmark.xml @@ -16,34 +16,34 @@ logLevel="5" file="../../../../../GEOSXDATA/DataSets/Class09_p3/Johansen_faces.vtu" fieldsToImport="{ perm, poro }" - fieldNamesInGEOSX="{ rockPerm_permeability, rockPorosity_referencePorosity }" /> - - - - - - - + fieldNamesInGEOSX="{ rockPerm_permeability, rockPorosity_referencePorosity }" > + + + + + + + + diff --git a/inputFiles/compositionalMultiphaseWell/benchmarks/Class09Pb3/class09_pb3_smoke_3d.xml b/inputFiles/compositionalMultiphaseWell/benchmarks/Class09Pb3/class09_pb3_smoke_3d.xml index 431e1b415c0..8243537388a 100644 --- a/inputFiles/compositionalMultiphaseWell/benchmarks/Class09Pb3/class09_pb3_smoke_3d.xml +++ b/inputFiles/compositionalMultiphaseWell/benchmarks/Class09Pb3/class09_pb3_smoke_3d.xml @@ -16,34 +16,34 @@ nx="{ 5 }" ny="{ 5 }" nz="{ 5 }" - cellBlockNames="{ 1_hexahedra }"/> - - - - - - - + cellBlockNames="{ 1_hexahedra }"> + + + + + + + + diff --git a/inputFiles/compositionalMultiphaseWell/benchmarks/Egg/deadOilEgg_benchmark.xml b/inputFiles/compositionalMultiphaseWell/benchmarks/Egg/deadOilEgg_benchmark.xml index 1c5015e20b2..cf9f4f55ce9 100644 --- a/inputFiles/compositionalMultiphaseWell/benchmarks/Egg/deadOilEgg_benchmark.xml +++ b/inputFiles/compositionalMultiphaseWell/benchmarks/Egg/deadOilEgg_benchmark.xml @@ -13,405 +13,394 @@ name="mesh" file="../../../../../GEOSXDATA/DataSets/Egg/egg.vtu" fieldsToImport="{ PERM }" - fieldNamesInGEOSX="{ rockPerm_permeability }"/> + fieldNamesInGEOSX="{ rockPerm_permeability }"> - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + + diff --git a/inputFiles/compositionalMultiphaseWell/benchmarks/Egg/deadOilEgg_smoke_3d.xml b/inputFiles/compositionalMultiphaseWell/benchmarks/Egg/deadOilEgg_smoke_3d.xml index e516cc689a1..e60a2242e56 100644 --- a/inputFiles/compositionalMultiphaseWell/benchmarks/Egg/deadOilEgg_smoke_3d.xml +++ b/inputFiles/compositionalMultiphaseWell/benchmarks/Egg/deadOilEgg_smoke_3d.xml @@ -19,405 +19,394 @@ nx="{ 10 }" ny="{ 10 }" nz="{ 7 }" - cellBlockNames="{ DEFAULT_HEX }"/> + cellBlockNames="{ DEFAULT_HEX }" > - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + + diff --git a/inputFiles/compositionalMultiphaseWell/black_oil_wells_saturated_3d.xml b/inputFiles/compositionalMultiphaseWell/black_oil_wells_saturated_3d.xml index 06ea62af468..4c04ae1fb24 100644 --- a/inputFiles/compositionalMultiphaseWell/black_oil_wells_saturated_3d.xml +++ b/inputFiles/compositionalMultiphaseWell/black_oil_wells_saturated_3d.xml @@ -66,36 +66,35 @@ nx="{ 4 }" ny="{ 4 }" nz="{ 2 }" - cellBlockNames="{ cb }"/> + cellBlockNames="{ cb }"> - - - - - - + + + + + + + + cellBlockNames="{ cb }"> - - - - - - + + + + + + + - - - - - - - - + cellBlockNames="{ cb1 }"> + + + + + + + + + + cellBlockNames="{ cb1 }"> - - - - - + + + + + - - - - - + + + + + - - - - + + + + + - - - - - - - - - - - - - - - - - + cellBlockNames="{ cb1 }"> + + + + + + + + + + + + + + + + + + + cellBlockNames="{ cb1 }"> - - - - - + + + + + - - - - - + + + + + - - - - + + + + + + cellBlockNames="{ cellBlock }"> - - - + + + + diff --git a/inputFiles/compositionalMultiphaseWell/staircase_co2_wells_3d.xml b/inputFiles/compositionalMultiphaseWell/staircase_co2_wells_3d.xml index fb4e4df7dc6..531e9552c04 100644 --- a/inputFiles/compositionalMultiphaseWell/staircase_co2_wells_3d.xml +++ b/inputFiles/compositionalMultiphaseWell/staircase_co2_wells_3d.xml @@ -65,38 +65,37 @@ nx="{ 5, 5 }" ny="{ 5, 5 }" nz="{ 3, 3, 3, 3 }" - cellBlockNames="{ b00, b01, b02, b03, b04, b05, b06, b07, b08, b09, b10, b11, b12, b13, b14, b15 }"/> - - - - - - - - + cellBlockNames="{ b00, b01, b02, b03, b04, b05, b06, b07, b08, b09, b10, b11, b12, b13, b14, b15 }"> + + + + + + + + + - - - - - - - - + logLevel="2"> + + + + + + + + + + cellBlockNames="{ b00, b01, b02, b03, b04, b05, b06, b07, b08, b09, b10, b11, b12, b13, b14, b15 }"> - - - - + + + + - - - - - + + + + + + cellBlockNames="{ b00, b01, b02, b03, b04, b05, b06, b07, b08, b09, b10, b11, b12, b13, b14, b15 }"> - - - - + + + + - - - - - + + + + + 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 literal 886538 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 _uS}T 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 diff --git a/inputFiles/singlePhaseWell/compressible_single_phase_wells_1d.xml b/inputFiles/singlePhaseWell/compressible_single_phase_wells_1d.xml index 2fe9980bf2d..a1c122629e3 100644 --- a/inputFiles/singlePhaseWell/compressible_single_phase_wells_1d.xml +++ b/inputFiles/singlePhaseWell/compressible_single_phase_wells_1d.xml @@ -51,37 +51,36 @@ nx="{ 5 }" ny="{ 1 }" nz="{ 1 }" - cellBlockNames="{ cb1 }"/> - - - - - - - - + cellBlockNames="{ cb1 }"> + + + + + + + + + - - - - - - - - + cellBlockNames="{ cb1 }"> + + + + + + + + + - - - - - - - - - - - - - - - - - + cellBlockNames="{ cb1 }"> + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - + cellBlockNames="{ cb1 }"> + + + + + + + + + + + + + + + + + + - - - - - - - - + cellBlockNames="{ b00, b01, b02, b03, b04, b05, b06, b07, b08, b09, b10, b11, b12, b13, b14, b15 }"> + + + + + + + + + - - - - - - - - + cellBlockNames="{ b00, b01, b02, b03, b04, b05, b06, b07, b08, b09, b10, b11, b12, b13, b14, b15 }"> + + + + + + + + + _ROOT env vars when searching for packages via find_package() @@ -14,7 +14,7 @@ cmake_policy(SET CMP0056 NEW) # use CMAKE_EXE_LINKER_FLAGS in try_compile() in a ################################ project( geosx LANGUAGES C CXX ) include(GNUInstallDirs) -set( BLT_CXX_STD "c++14" CACHE STRING "Version of C++ standard" ) +set( BLT_CXX_STD "c++17" CACHE STRING "Version of C++ standard" ) if(CMAKE_BUILD_TYPE EQUAL "Debug") set( ENABLE_WARNINGS_AS_ERRORS "OFF" CACHE PATH "") else() diff --git a/src/cmake/GeosxOptions.cmake b/src/cmake/GeosxOptions.cmake index c905f6fe149..ca0c999bd57 100644 --- a/src/cmake/GeosxOptions.cmake +++ b/src/cmake/GeosxOptions.cmake @@ -114,9 +114,9 @@ endif() #message( "SPHINX_FOUND = ${SPHINX_FOUND}" ) #message( "SPHINX_EXECUTABLE = ${SPHINX_EXECUTABLE}" ) -if( NOT BLT_CXX_STD STREQUAL c++14 ) - MESSAGE( FATAL_ERROR "c++14 is NOT enabled. GEOSX requires c++14" ) -endif( NOT BLT_CXX_STD STREQUAL c++14 ) +if( NOT BLT_CXX_STD STREQUAL c++17 ) + MESSAGE( FATAL_ERROR "c++17 is NOT enabled. GEOSX requires c++17" ) +endif( NOT BLT_CXX_STD STREQUAL c++17 ) message( "CMAKE_CXX_COMPILER_ID = ${CMAKE_CXX_COMPILER_ID}" ) @@ -150,13 +150,6 @@ if( ${CMAKE_MAKE_PROGRAM} STREQUAL "ninja" OR ${CMAKE_MAKE_PROGRAM} MATCHES ".*/ set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${GEOSX_NINJA_FLAGS}" ) endif() -#set(CMAKE_CUDA_STANDARD 14 CACHE STRING "" FORCE) -#blt_append_custom_compiler_flag( FLAGS_VAR CMAKE_CUDA_FLAGS_RELEASE -# DEFAULT "-O3 -DNDEBUG -Xcompiler -DNDEBUG -Xcompiler -O3" ) -#blt_append_custom_compiler_flag( FLAGS_VAR CMAKE_CUDA_FLAGS_RELWITHDEBINFO -# DEFAULT "-lineinfo ${CMAKE_CUDA_FLAGS_RELEASE}" ) -#blt_append_custom_compiler_flag( FLAGS_VAR CMAKE_CUDA_FLAGS_DEBUG -# DEFAULT "-G -O0 -Xcompiler -O0" ) if( CMAKE_HOST_APPLE ) diff --git a/src/coreComponents/LvArray b/src/coreComponents/LvArray index 0699503c8a2..8f1a75156eb 160000 --- a/src/coreComponents/LvArray +++ b/src/coreComponents/LvArray @@ -1 +1 @@ -Subproject commit 0699503c8a2e1cae5f1e16887443e1ff86906ee3 +Subproject commit 8f1a75156eb2d1a5193664e319c3cf3e9e819730 diff --git a/src/coreComponents/codingUtilities/Utilities.hpp b/src/coreComponents/codingUtilities/Utilities.hpp index 256391fff4e..ba3749d793b 100644 --- a/src/coreComponents/codingUtilities/Utilities.hpp +++ b/src/coreComponents/codingUtilities/Utilities.hpp @@ -200,7 +200,10 @@ VAL findOption( mapBase< KEY, VAL, SORTED > const & map, auto const iter = map.find( option ); GEOS_THROW_IF( iter == map.end(), GEOS_FMT( "{}: unsupported option '{}' for {}.\nSupported options are: {}", - contextName, option, optionName, stringutilities::join( mapKeys( map ), ", " ) ), + contextName, + const_cast< typename std::remove_const< KEY & >::type >( option ), + optionName, + stringutilities::join( mapKeys( map ), ", " ) ), InputError ); return iter->second; } diff --git a/src/coreComponents/codingUtilities/tests/testGeosxTraits.cpp b/src/coreComponents/codingUtilities/tests/testGeosxTraits.cpp index 794658d1b77..dec8e43fa8d 100644 --- a/src/coreComponents/codingUtilities/tests/testGeosxTraits.cpp +++ b/src/coreComponents/codingUtilities/tests/testGeosxTraits.cpp @@ -90,7 +90,7 @@ TEST( testGeosxTraits, Pointer ) static_assert( std::is_same< Pointer< int >, int * >::value, "Should be true." ); static_assert( std::is_same< Pointer< R1Tensor >, R1Tensor * >::value, "Should be true." ); static_assert( std::is_same< Pointer< std::vector< double > >, double * >::value, "Should be true." ); - static_assert( std::is_same< Pointer< string >, char const * >::value, "Should be true." ); + static_assert( std::is_same< Pointer< string >, char * >::value, "Should be true." ); static_assert( std::is_same< Pointer< array3d< string > >, string * >::value, "Should be true." ); static_assert( std::is_same< Pointer< SortedArray< float > >, float const * >::value, "Should be true." ); diff --git a/src/coreComponents/common/Format.hpp b/src/coreComponents/common/Format.hpp index bc3d1ade6a4..b548b76a20d 100644 --- a/src/coreComponents/common/Format.hpp +++ b/src/coreComponents/common/Format.hpp @@ -15,6 +15,8 @@ #ifndef GEOS_COMMON_FORMAT_HPP_ #define GEOS_COMMON_FORMAT_HPP_ +#include + #if __cplusplus < 202002L #define GEOSX_USE_FMT #endif @@ -30,6 +32,42 @@ #define GEOS_FMT_NS std #endif +#ifdef GEOSX_USE_FMT +/** + * @brief fmtlib formatter for enum classes. + * @tparam T The type of the object being formatted. This should be an + * enum class. + */ +template< typename T > +struct fmt::formatter< T, std::enable_if_t< std::is_enum< T >::value > > +{ + /** + * @brief Parser for the fmtlib formatting library. + * @param ctx The context provided by the fmtlib library, which includes + * the format string. + * @return An iterator pointing to the end of the format string. + */ + template< typename ParseContext > + constexpr auto parse( ParseContext & ctx ) + { + return ctx.end(); + } + + /** + * @brief Formatter for the fmtlib formatting library. + * @param value The enum class object to format. + * @param ctx The context provided by the fmtlib library, which includes + * the output iterator where the formatted string should be written. + * @return An iterator pointing to the end of the formatted string. + */ + template< typename FormatContext > + auto format( const T & value, FormatContext & ctx ) + { + return fmt::format_to( ctx.out(), "{}", static_cast< std::underlying_type_t< T > >( value ) ); + } +}; +#endif + /** * @brief Interpolate arguments into a message format string. * @param msg the message format string, must be a constant expression diff --git a/src/coreComponents/common/GEOS_RAJA_Interface.hpp b/src/coreComponents/common/GEOS_RAJA_Interface.hpp index 26f8023ed83..82a238f69e7 100644 --- a/src/coreComponents/common/GEOS_RAJA_Interface.hpp +++ b/src/coreComponents/common/GEOS_RAJA_Interface.hpp @@ -70,10 +70,10 @@ void RAJA_INLINE parallelHostSync() { } #if defined( GEOS_USE_CUDA ) auto const parallelDeviceMemorySpace = LvArray::MemorySpace::cuda; -template< unsigned long BLOCK_SIZE = GEOSX_BLOCK_SIZE > +template< size_t BLOCK_SIZE = GEOSX_BLOCK_SIZE > using parallelDevicePolicy = RAJA::cuda_exec< BLOCK_SIZE >; -template< unsigned long BLOCK_SIZE = GEOSX_BLOCK_SIZE > +template< size_t BLOCK_SIZE = GEOSX_BLOCK_SIZE > using parallelDeviceAsyncPolicy = RAJA::cuda_exec_async< BLOCK_SIZE >; using parallelDeviceStream = RAJA::resources::Cuda; @@ -96,7 +96,7 @@ RAJA_INLINE parallelDeviceEvent forAll( RESOURCE && stream, const localIndex end auto const parallelDeviceMemorySpace = LvArray::MemorySpace::hip; -template< unsigned long BLOCK_SIZE = GEOSX_BLOCK_SIZE > +template< size_t BLOCK_SIZE = GEOSX_BLOCK_SIZE > using parallelDevicePolicy = RAJA::hip_exec< BLOCK_SIZE >; @@ -109,8 +109,8 @@ using parallelDeviceAtomic = RAJA::hip_atomic; void RAJA_INLINE parallelDeviceSync() { RAJA::synchronize< RAJA::hip_synchronize >( ); } // the async dispatch policy caused runtime issues as of rocm@4.5.2, hasn't been checked in rocm@5: -template< unsigned long BLOCK_SIZE = GEOSX_BLOCK_SIZE > -using parallelDeviceAsyncPolicy = parallelDevicePolicy< BLOCK_SIZE >;// RAJA::hip_exec_async< BLOCK_SIZE >; +template< size_t BLOCK_SIZE = GEOSX_BLOCK_SIZE > +using parallelDeviceAsyncPolicy = parallelDevicePolicy< BLOCK_SIZE >; // RAJA::hip_exec_async< BLOCK_SIZE >; template< typename POLICY, typename RESOURCE, typename LAMBDA > RAJA_INLINE parallelDeviceEvent forAll( RESOURCE && GEOS_UNUSED_PARAM( stream ), const localIndex end, LAMBDA && body ) @@ -122,10 +122,10 @@ RAJA_INLINE parallelDeviceEvent forAll( RESOURCE && GEOS_UNUSED_PARAM( stream ), auto const parallelDeviceMemorySpace = parallelHostMemorySpace; -template< unsigned long BLOCK_SIZE = 0 > +template< size_t BLOCK_SIZE = 0 > using parallelDevicePolicy = parallelHostPolicy; -template< unsigned long BLOCK_SIZE = 0 > +template< size_t BLOCK_SIZE = 0 > using parallelDeviceAsyncPolicy = parallelHostPolicy; using parallelDeviceStream = parallelHostStream; @@ -150,7 +150,8 @@ using parallelDeviceEvents = std::vector< parallelDeviceEvent >; namespace internalRajaInterface { template< typename > -struct PolicyMap; +struct PolicyMap +{}; template<> struct PolicyMap< serialPolicy > @@ -169,8 +170,8 @@ struct PolicyMap< RAJA::omp_parallel_for_exec > #endif #if defined(GEOS_USE_CUDA) -template< unsigned long BLOCK_SIZE > -struct PolicyMap< RAJA::cuda_exec< BLOCK_SIZE > > +template< typename X, typename Y, size_t BLOCK_SIZE, bool ASYNC > +struct PolicyMap< RAJA::policy::cuda::cuda_exec_explicit< X, Y, BLOCK_SIZE, ASYNC > > { using atomic = RAJA::cuda_atomic; using reduce = RAJA::cuda_reduce; @@ -178,8 +179,8 @@ struct PolicyMap< RAJA::cuda_exec< BLOCK_SIZE > > #endif #if defined(GEOS_USE_HIP) -template< unsigned long BLOCK_SIZE > -struct PolicyMap< RAJA::hip_exec< BLOCK_SIZE > > +template< size_t BLOCK_SIZE, bool ASYNC > +struct PolicyMap< RAJA::hip_exec< BLOCK_SIZE, ASYNC > > { using atomic = RAJA::hip_atomic; using reduce = RAJA::hip_reduce; diff --git a/src/coreComponents/common/unitTests/testLifoStorage.cpp b/src/coreComponents/common/unitTests/testLifoStorage.cpp index 0559b86c269..5120709963a 100644 --- a/src/coreComponents/common/unitTests/testLifoStorage.cpp +++ b/src/coreComponents/common/unitTests/testLifoStorage.cpp @@ -57,8 +57,8 @@ struct RAJAHelper< parallelHostPolicy > template< unsigned long THREADS_PER_BLOCK > using devicePolicy = RAJA::cuda_exec< THREADS_PER_BLOCK >; -template< unsigned long N > -struct RAJAHelper< RAJA::cuda_exec< N > > +template< typename X, typename Y, size_t BLOCK_SIZE, bool ASYNC > +struct RAJAHelper< RAJA::policy::cuda::cuda_exec_explicit< X, Y, BLOCK_SIZE, ASYNC > > { using ReducePolicy = RAJA::cuda_reduce; using AtomicPolicy = RAJA::cuda_atomic; diff --git a/src/coreComponents/constitutive/fluid/multifluid/MultiFluidUtils.hpp b/src/coreComponents/constitutive/fluid/multifluid/MultiFluidUtils.hpp index deff637e91f..3d98de053e0 100644 --- a/src/coreComponents/constitutive/fluid/multifluid/MultiFluidUtils.hpp +++ b/src/coreComponents/constitutive/fluid/multifluid/MultiFluidUtils.hpp @@ -76,6 +76,19 @@ struct MultiFluidVarView { MultiFluidVarView() = default; + GEOS_HOST_DEVICE + MultiFluidVarView ( MultiFluidVarView const & src ): + value( src.value ), + derivs( src.derivs ) + {} + + GEOS_HOST_DEVICE + MultiFluidVarView ( ArrayView< T, NDIM, USD > const & valueSrc, + ArrayView< T, NDIM + 1, USD_DC > const & derivsSrc ): + value( valueSrc ), + derivs( derivsSrc ) + {}; + ArrayView< T, NDIM, USD > value; ///< View into property values ArrayView< T, NDIM + 1, USD_DC > derivs; ///< View into property derivatives w.r.t. pressure, temperature, compositions diff --git a/src/coreComponents/constitutive/fluid/multifluid/compositional/functions/CubicEOSPhaseModel.hpp b/src/coreComponents/constitutive/fluid/multifluid/compositional/functions/CubicEOSPhaseModel.hpp index 700917c5989..9b8402c17af 100644 --- a/src/coreComponents/constitutive/fluid/multifluid/compositional/functions/CubicEOSPhaseModel.hpp +++ b/src/coreComponents/constitutive/fluid/multifluid/compositional/functions/CubicEOSPhaseModel.hpp @@ -106,10 +106,10 @@ struct CubicEOSPhaseModel * @param[in] criticalTemperature critical temperatures * @param[in] acentricFactor acentric factors * @param[in] binaryInteractionCoefficients binary coefficients (currently not implemented) - * @param[in] aPureCoefficient pure coefficient (A) - * @param[in] bPureCoefficient pure coefficient (B) - * @param[in] aMixtureCoefficient mixture coefficient (A) - * @param[in] bMixtureCoefficient mixture coefficient (B) + * @param[out] aPureCoefficient pure coefficient (A) + * @param[out] bPureCoefficient pure coefficient (B) + * @param[out] aMixtureCoefficient mixture coefficient (A) + * @param[out] bMixtureCoefficient mixture coefficient (B) */ GEOS_HOST_DEVICE GEOS_FORCE_INLINE @@ -127,6 +127,50 @@ struct CubicEOSPhaseModel real64 & aMixtureCoefficient, real64 & bMixtureCoefficient ); + /** + * @brief Compute the mixture coefficients derivatives + * @param[in] numComps number of components + * @param[in] pressure pressure + * @param[in] temperature temperature + * @param[in] composition composition of the phase + * @param[in] criticalPressure critical pressures + * @param[in] criticalTemperature critical temperatures + * @param[in] acentricFactor acentric factors + * @param[in] binaryInteractionCoefficients binary coefficients (currently not implemented) + * @param[in] aPureCoefficient pure coefficient (A) + * @param[in] bPureCoefficient pure coefficient (B) + * @param[in] aMixtureCoefficient mixture coefficient (A) + * @param[in] bMixtureCoefficient mixture coefficient (B) + * @param[out] daMixtureCoefficient_dp derivative of mixture coefficient (A) wrt pressure + * @param[out] dbMixtureCoefficient_dp derivative of mixture coefficient (B) wrt pressure + * @param[out] daMixtureCoefficient_dt derivative of mixture coefficient (A) wrt temperature + * @param[out] dbMixtureCoefficient_dt derivative of mixture coefficient (B) wrt temperature + * @param[out] daMixtureCoefficient_dz derivative of mixture coefficient (A) wrt composition + * @param[out] dbMixtureCoefficient_dz derivative of mixture coefficient (B) wrt composition + * @note Assumes that pressure and temperature are strictly positive + */ + GEOS_HOST_DEVICE + GEOS_FORCE_INLINE + static void + computeMixtureCoefficients( integer const numComps, + real64 const & pressure, + real64 const & temperature, + arrayView1d< real64 const > const composition, + arrayView1d< real64 const > const criticalPressure, + arrayView1d< real64 const > const criticalTemperature, + arrayView1d< real64 const > const acentricFactor, + real64 const & binaryInteractionCoefficients, + arraySlice1d< real64 const > const aPureCoefficient, + arraySlice1d< real64 const > const bPureCoefficient, + real64 const aMixtureCoefficient, + real64 const bMixtureCoefficient, + real64 & daMixtureCoefficient_dp, + real64 & dbMixtureCoefficient_dp, + real64 & daMixtureCoefficient_dt, + real64 & dbMixtureCoefficient_dt, + arraySlice1d< real64 > const daMixtureCoefficient_dz, + arraySlice1d< real64 > const dbMixtureCoefficient_dz ); + /** * @brief Compute the compressibility factor using compositions, BICs, and mixture coefficients * @param[in] numComps number of components @@ -150,6 +194,40 @@ struct CubicEOSPhaseModel real64 const & bMixtureCoefficient, real64 & compressibilityFactor ); + /** + * @brief Compute the compressibility factor derivatives using mixture coefficients + * @param[in] numComps number of components + * @param[in] aMixtureCoefficient mixture coefficient (A) + * @param[in] bMixtureCoefficient mixture coefficient (B) + * @param[in] compressibilityFactor the current compressibility factor + * @param[in] daMixtureCoefficient_dp derivative of mixture coefficient (A) wrt pressure + * @param[in] dbMixtureCoefficient_dp derivative of mixture coefficient (B) wrt pressure + * @param[in] daMixtureCoefficient_dt derivative of mixture coefficient (A) wrt temperature + * @param[in] dbMixtureCoefficient_dt derivative of mixture coefficient (B) wrt temperature + * @param[in] daMixtureCoefficient_dz derivative of mixture coefficient (A) wrt composition + * @param[in] dbMixtureCoefficient_dz derivative of mixture coefficient (B) wrt composition + * @param[out] dCompressibilityFactor_dp derivative of the compressibility factor wrt pressure + * @param[out] dCompressibilityFactor_dt derivative of the compressibility factor wrt temperature + * @param[out] dCompressibilityFactor_dz derivative of the compressibility factor wrt composition + * @note Assumes that pressure and temperature are strictly positive + */ + GEOS_HOST_DEVICE + GEOS_FORCE_INLINE + static void + computeCompressibilityFactor( integer const numComps, + real64 const & aMixtureCoefficient, + real64 const & bMixtureCoefficient, + real64 const & compressibilityFactor, + real64 const & daMixtureCoefficient_dp, + real64 const & dbMixtureCoefficient_dp, + real64 const & daMixtureCoefficient_dt, + real64 const & dbMixtureCoefficient_dt, + arraySlice1d< real64 const > const daMixtureCoefficient_dz, + arraySlice1d< real64 const > const dbMixtureCoefficient_dz, + real64 & dCompressibilityFactor_dp, + real64 & dCompressibilityFactor_dt, + arraySlice1d< real64 > const dCompressibilityFactor_dz ); + /** * @brief Compute the log of the fugacity coefficients using compositions, BICs, compressibility factor and mixture coefficients * @param[in] numComps number of components @@ -271,18 +349,14 @@ computeMixtureCoefficients( integer const numComps, real64 & aMixtureCoefficient, real64 & bMixtureCoefficient ) { - stackArray1d< real64, maxNumComps > m( numComps ); - for( integer ic = 0; ic < numComps; ++ic ) - { - m[ic] = EOS_TYPE::evaluate( acentricFactor[ic] ); - } - // mixture coefficients for( integer ic = 0; ic < numComps; ++ic ) { - aPureCoefficient[ic] = EOS_TYPE::omegaA * criticalTemperature[ic] * criticalTemperature[ic] * pressure - / ( criticalPressure[ic] * temperature * temperature ) * pow( 1.0 + m[ic] * ( 1.0 - sqrt( temperature / criticalTemperature[ic] ) ), 2.0 ); - bPureCoefficient[ic] = EOS_TYPE::omegaB * criticalTemperature[ic] * pressure / ( criticalPressure[ic] * temperature ); + real64 const m = EOS_TYPE::evaluate( acentricFactor[ic] ); + real64 const pr = pressure / criticalPressure[ic]; + real64 const tr = temperature / criticalTemperature[ic]; + aPureCoefficient[ic] = EOS_TYPE::omegaA * pr / (tr*tr) * pow( 1.0 + m * ( 1.0 - sqrt( tr ) ), 2.0 ); + bPureCoefficient[ic] = EOS_TYPE::omegaB * pr / tr; } aMixtureCoefficient = 0.0; @@ -297,6 +371,70 @@ computeMixtureCoefficients( integer const numComps, } } +template< typename EOS_TYPE > +GEOS_HOST_DEVICE +void +CubicEOSPhaseModel< EOS_TYPE >:: +computeMixtureCoefficients( integer const numComps, + real64 const & pressure, + real64 const & temperature, + arrayView1d< real64 const > const composition, + arrayView1d< real64 const > const criticalPressure, + arrayView1d< real64 const > const criticalTemperature, + arrayView1d< real64 const > const acentricFactor, + real64 const & binaryInteractionCoefficients, + arraySlice1d< real64 const > const aPureCoefficient, + arraySlice1d< real64 const > const bPureCoefficient, + real64 const aMixtureCoefficient, + real64 const bMixtureCoefficient, + real64 & daMixtureCoefficient_dp, + real64 & dbMixtureCoefficient_dp, + real64 & daMixtureCoefficient_dt, + real64 & dbMixtureCoefficient_dt, + arraySlice1d< real64 > const daMixtureCoefficient_dz, + arraySlice1d< real64 > const dbMixtureCoefficient_dz ) +{ + GEOS_UNUSED_VAR( criticalPressure ); + stackArray1d< real64, maxNumComps > daPureCoefficient_dx( numComps ); + // Calculate pressure derivatives + daMixtureCoefficient_dp = aMixtureCoefficient / pressure; + dbMixtureCoefficient_dp = bMixtureCoefficient / pressure; + // Calculate temperature derivatives + for( integer ic = 0; ic < numComps; ++ic ) + { + real64 const m = EOS_TYPE::evaluate( acentricFactor[ic] ); + real64 const sqrtTr = sqrt( temperature / criticalTemperature[ic] ); + real64 const mt = 1.0 + m * (1.0 - sqrtTr); + daPureCoefficient_dx[ic] = -aPureCoefficient[ic] * (2.0/temperature + m/(mt*sqrtTr*criticalTemperature[ic])); + } + daMixtureCoefficient_dt = 0.0; + dbMixtureCoefficient_dt = -bMixtureCoefficient / temperature; + for( integer ic = 0; ic < numComps; ++ic ) + { + for( integer jc = 0; jc < numComps; ++jc ) + { + real64 const coeff = composition[ic] * composition[jc] * ( 1.0 - binaryInteractionCoefficients ) / sqrt( aPureCoefficient[ic] * aPureCoefficient[jc] ); + daMixtureCoefficient_dt += 0.5 * coeff * (daPureCoefficient_dx[ic]*aPureCoefficient[jc] + daPureCoefficient_dx[jc]*aPureCoefficient[ic]); + } + } + // Calculate composition derivatives + for( integer ic = 0; ic < numComps; ++ic ) + { + daMixtureCoefficient_dz[ic] = 0.0; + dbMixtureCoefficient_dz[ic] = 0.0; + } + for( integer ic = 0; ic < numComps; ++ic ) + { + for( integer jc = 0; jc < numComps; ++jc ) + { + real64 const coeff = ( 1.0 - binaryInteractionCoefficients ) * sqrt( aPureCoefficient[ic] * aPureCoefficient[jc] ); + daMixtureCoefficient_dz[ic] += coeff * composition[jc]; + daMixtureCoefficient_dz[jc] += coeff * composition[ic]; + } + dbMixtureCoefficient_dz[ic] = bPureCoefficient[ic]; + } +} + template< typename EOS_TYPE > GEOS_HOST_DEVICE void @@ -366,6 +504,69 @@ computeCompressibilityFactor( integer const numComps, } } +template< typename EOS_TYPE > +GEOS_HOST_DEVICE +void +CubicEOSPhaseModel< EOS_TYPE >:: +computeCompressibilityFactor( integer const numComps, + real64 const & aMixtureCoefficient, + real64 const & bMixtureCoefficient, + real64 const & compressibilityFactor, + real64 const & daMixtureCoefficient_dp, + real64 const & dbMixtureCoefficient_dp, + real64 const & daMixtureCoefficient_dt, + real64 const & dbMixtureCoefficient_dt, + arraySlice1d< real64 const > const daMixtureCoefficient_dz, + arraySlice1d< real64 const > const dbMixtureCoefficient_dz, + real64 & dcompressibilityFactor_dp, + real64 & dcompressibilityFactor_dt, + arraySlice1d< real64 > const dcompressibilityFactor_dz ) +{ + // a Z3 + b Z2 + cZ + d = 0 + // Derivatives for a,b,c,d + // dadx is zero; + real64 dbdx = 0.0; + real64 dcdx = 0.0; + real64 dddx = 0.0; + + constexpr real64 d1pd2 = EOS_TYPE::delta1 + EOS_TYPE::delta2; + constexpr real64 d1xd2 = EOS_TYPE::delta1 * EOS_TYPE::delta2; + + constexpr real64 a = 1.0; + real64 const b = ( d1pd2 - 1.0 ) * bMixtureCoefficient - 1.0; + real64 const c = aMixtureCoefficient + d1xd2 * bMixtureCoefficient * bMixtureCoefficient + - d1pd2 * bMixtureCoefficient * ( bMixtureCoefficient + 1.0 ); + + // Implicit differentiation scale + real64 const denominator = (3.0*a*compressibilityFactor + 2.0*b)*compressibilityFactor + c; + constexpr real64 epsilon = LvArray::NumericLimits< real64 >::epsilon; + real64 const scalingFactor = fabs( denominator ) < epsilon ? 0.0 : -1.0 / denominator; + + // Pressure derivatives + dbdx = ( d1pd2 - 1.0 ) * dbMixtureCoefficient_dp; + dcdx = daMixtureCoefficient_dp + (2.0*(d1xd2-d1pd2)*bMixtureCoefficient-d1pd2)*dbMixtureCoefficient_dp; + dddx = -(aMixtureCoefficient*dbMixtureCoefficient_dp + daMixtureCoefficient_dp*bMixtureCoefficient + + d1xd2*((3.0*bMixtureCoefficient+2.0)*bMixtureCoefficient*dbMixtureCoefficient_dp)); + dcompressibilityFactor_dp = (((dbdx*compressibilityFactor) + dcdx)*compressibilityFactor + dddx) * scalingFactor; + + // Temperature derivatives + dbdx = ( d1pd2 - 1.0 ) * dbMixtureCoefficient_dt; + dcdx = daMixtureCoefficient_dt + (2.0*(d1xd2-d1pd2)*bMixtureCoefficient-d1pd2)*dbMixtureCoefficient_dt; + dddx = -(aMixtureCoefficient*dbMixtureCoefficient_dt + daMixtureCoefficient_dt*bMixtureCoefficient + + d1xd2*((3.0*bMixtureCoefficient+2.0)*bMixtureCoefficient*dbMixtureCoefficient_dt)); + dcompressibilityFactor_dt = (((dbdx*compressibilityFactor) + dcdx)*compressibilityFactor + dddx) * scalingFactor; + + // Composition derivatives + for( integer ic = 0; ic < numComps; ++ic ) + { + dbdx = ( d1pd2 - 1.0 ) * dbMixtureCoefficient_dz[ic]; + dcdx = daMixtureCoefficient_dz[ic] + (2.0*(d1xd2-d1pd2)*bMixtureCoefficient-d1pd2)*dbMixtureCoefficient_dz[ic]; + dddx = -(aMixtureCoefficient*dbMixtureCoefficient_dz[ic] + daMixtureCoefficient_dz[ic]*bMixtureCoefficient + + d1xd2*((3.0*bMixtureCoefficient+2.0)*bMixtureCoefficient*dbMixtureCoefficient_dz[ic])); + dcompressibilityFactor_dz[ic] = (((dbdx*compressibilityFactor) + dcdx)*compressibilityFactor + dddx) * scalingFactor; + } +} + template< typename EOS_TYPE > GEOS_HOST_DEVICE void diff --git a/src/coreComponents/constitutive/unitTests/TestFluid.hpp b/src/coreComponents/constitutive/unitTests/TestFluid.hpp new file mode 100644 index 00000000000..3784f5463be --- /dev/null +++ b/src/coreComponents/constitutive/unitTests/TestFluid.hpp @@ -0,0 +1,129 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2018-2020 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2020 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2018-2020 TotalEnergies + * Copyright (c) 2019- GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +/** + * @file TestFluid.hpp + */ + +#ifndef GEOS_CONSTITUTIVE_UNITTESTS_TESTFLUID_HPP_ +#define GEOS_CONSTITUTIVE_UNITTESTS_TESTFLUID_HPP_ + +#include "common/DataTypes.hpp" + +namespace geos +{ + +namespace testing +{ + +struct Fluid +{ + static constexpr integer NC = 11; + + static constexpr integer H2O = 0; + static constexpr integer CO2 = 1; + static constexpr integer N2 = 2; + static constexpr integer H2S = 3; + static constexpr integer C1 = 4; + static constexpr integer C2 = 5; + static constexpr integer C3 = 6; + static constexpr integer C4 = 7; + static constexpr integer C5 = 8; + static constexpr integer C8 = 9; + static constexpr integer C10 = 10; + + static constexpr integer Pc = 0; + static constexpr integer Tc = 1; + static constexpr integer Ac = 2; + static constexpr integer Mw = 3; + + static std::array< real64, 44 > data; +}; + +template< int NC > +using Feed = std::array< real64, NC >; + +template< int NC > +class TestFluid +{ +public: + ~TestFluid() = default; + + static std::unique_ptr< TestFluid< NC > > create( std::array< integer, NC > const & components ) + { + std::unique_ptr< TestFluid< NC > > testFluid( new TestFluid() ); + createArray( testFluid->criticalPressure, components, Fluid::Pc, Fluid::data ); + createArray( testFluid->criticalTemperature, components, Fluid::Tc, Fluid::data ); + createArray( testFluid->acentricFactor, components, Fluid::Ac, Fluid::data ); + createArray( testFluid->molecularWeight, components, Fluid::Mw, Fluid::data ); + return testFluid; + } + + arrayView1d< real64 const > const getCriticalPressure() const { return criticalPressure.toViewConst(); } + arrayView1d< real64 const > const getCriticalTemperature() const { return criticalTemperature.toViewConst(); } + arrayView1d< real64 const > const getCriticalVolume() const { return criticalVolume.toViewConst(); } + arrayView1d< real64 const > const getAcentricFactor() const { return acentricFactor.toViewConst(); } + arrayView1d< real64 const > const getMolecularWeight() const { return molecularWeight.toViewConst(); } + arrayView1d< real64 const > const getVolumeShift() const { return volumeShift.toViewConst(); } + +private: + TestFluid() = default; + + array1d< real64 > criticalPressure; + array1d< real64 > criticalTemperature; + array1d< real64 > criticalVolume; + array1d< real64 > acentricFactor; + array1d< real64 > molecularWeight; + array1d< real64 > volumeShift; + +private: + template< typename ARRAY, typename LIST, typename DATAARRAY > + static void createArray( ARRAY & array, LIST const & indices, integer const row, DATAARRAY const & data ) + { + for( auto const i : indices ) + { + array.emplace_back( data[Fluid::NC *row+i] ); + } + } +public: + template< typename ARRAY, typename LIST > + static void createArray( ARRAY & array, LIST const & data ) + { + for( auto const value : data ) + { + array.emplace_back( value ); + } + } +}; + +std::array< real64, 44 > Fluid::data = { + // -- Pc + 2.2050e+07, 7.3750e+06, 3.4000e+06, 8.9630e+06, 1.2960e+06, 4.8721e+06, + 4.2481e+06, 3.6400e+06, 4.5990e+06, 2.5300e+06, 1.4600e+06, + // -- Tc + 6.4700e+02, 3.0410e+02, 1.2620e+02, 3.7353e+02, 3.3150e+01, 3.0532e+02, + 3.6983e+02, 4.0785e+02, 1.9060e+02, 6.2200e+02, 7.8200e+02, + // -- Ac + 3.4400e-01, 2.3900e-01, 4.0000e-02, 9.4200e-02, -2.1900e-01, 9.9500e-02, + 1.5230e-01, 1.8440e-01, 1.1400e-02, 4.4300e-01, 8.1600e-01, + // -- Mw + 1.8015e+01, 4.4010e+01, 2.8013e+01, 3.4100e+01, 1.6043e+01, 3.0070e+01, + 4.4097e+01, 5.8124e+01, 7.2151e+01, 1.1423e+02, 1.4228e+02, +}; + +}// testing + +}// geos + +#endif //GEOS_CONSTITUTIVE_UNITTESTS_TESTFLUID_HPP_ diff --git a/src/coreComponents/constitutive/unitTests/testCubicEOS.cpp b/src/coreComponents/constitutive/unitTests/testCubicEOS.cpp index e41ad9eee85..4a1b08b3d9b 100644 --- a/src/coreComponents/constitutive/unitTests/testCubicEOS.cpp +++ b/src/coreComponents/constitutive/unitTests/testCubicEOS.cpp @@ -12,11 +12,11 @@ * ------------------------------------------------------------------------------------------------------------ */ - // Source includes #include "codingUtilities/UnitTestUtilities.hpp" #include "common/DataTypes.hpp" #include "constitutive/fluid/multifluid/compositional/functions/CubicEOSPhaseModel.hpp" +#include "TestFluid.hpp" // TPL includes #include @@ -25,31 +25,26 @@ using namespace geos; using namespace geos::testing; using namespace geos::constitutive; -static constexpr real64 relTol = 1e-5; +static constexpr real64 relTol = 1.0e-5; +static constexpr real64 absTol = 1.0e-8; TEST( CubicEOSTest, testCubicEOSTwoComponentsSRK ) { constexpr integer numComps = 2; + auto fluid = TestFluid< numComps >::create( {Fluid::C1, Fluid::C5} ); + + auto criticalPressure = fluid->getCriticalPressure(); + auto criticalTemperature = fluid->getCriticalTemperature(); + auto omega = fluid->getAcentricFactor(); + real64 binaryInteractionCoefficients = 0.0; // not implemented yet + real64 pressure = 0.0; real64 temperature = 0.0; array1d< real64 > composition( numComps ); - array1d< real64 > criticalPressure( numComps ); - array1d< real64 > criticalTemperature( numComps ); - array1d< real64 > omega( numComps ); - real64 binaryInteractionCoefficients = 0.0; // not implemented yet array1d< real64 > logFugacityCoefficients( numComps ); array1d< real64 > expectedLogFugacityCoefficients( numComps ); - criticalPressure[0] = 12.96e5; - criticalPressure[1] = 45.99e5; - - criticalTemperature[0] = 33.15; - criticalTemperature[1] = 190.6; - - omega[0] = -0.219; - omega[1] = 0.0114; - /////////////////////////////////////////// pressure = 1e6; @@ -132,31 +127,19 @@ TEST( CubicEOSTest, testCubicEOSFourComponentsPR ) { constexpr integer numComps = 4; + auto fluid = TestFluid< numComps >::create( {Fluid::N2, Fluid::C8, Fluid::C10, Fluid::H2O} ); + + auto criticalPressure = fluid->getCriticalPressure(); + auto criticalTemperature = fluid->getCriticalTemperature(); + auto omega = fluid->getAcentricFactor(); + real64 binaryInteractionCoefficients = 0.0; // not implemented yet + real64 pressure = 0.0; real64 temperature = 0.0; array1d< real64 > composition( numComps ); - array1d< real64 > criticalPressure( numComps ); - array1d< real64 > criticalTemperature( numComps ); - array1d< real64 > omega( numComps ); - real64 binaryInteractionCoefficients = 0.0; // not implemented yet array1d< real64 > logFugacityCoefficients( numComps ); array1d< real64 > expectedLogFugacityCoefficients( numComps ); - criticalPressure[0] = 34e5; - criticalPressure[1] = 25.3e5; - criticalPressure[2] = 14.6e5; - criticalPressure[3] = 220.5e5; - - criticalTemperature[0] = 126.2; - criticalTemperature[1] = 622.0; - criticalTemperature[2] = 782.0; - criticalTemperature[3] = 647.0; - - omega[0] = 0.04; - omega[1] = 0.443; - omega[2] = 0.816; - omega[3] = 0.344; - /////////////////////////////////////////// pressure = 1e7; @@ -236,33 +219,21 @@ TEST( CubicEOSTest, testCubicEOSFourComponentsPR ) TEST( CubicEOSTest, testCubicEOSFourComponentsSRK ) { - integer const numComps = 4; + constexpr integer numComps = 4; + + auto fluid = TestFluid< numComps >::create( {Fluid::N2, Fluid::C8, Fluid::C10, Fluid::H2O} ); + + auto criticalPressure = fluid->getCriticalPressure(); + auto criticalTemperature = fluid->getCriticalTemperature(); + auto omega = fluid->getAcentricFactor(); + real64 binaryInteractionCoefficients = 0.0; // not implemented yet real64 pressure = 0.0; real64 temperature = 0.0; array1d< real64 > composition( 4 ); - array1d< real64 > criticalPressure( 4 ); - array1d< real64 > criticalTemperature( 4 ); - array1d< real64 > omega( 4 ); - real64 binaryInteractionCoefficients = 0.0; // not implemented yet array1d< real64 > logFugacityCoefficients( 4 ); array1d< real64 > expectedLogFugacityCoefficients( 4 ); - criticalPressure[0] = 34e5; - criticalPressure[1] = 25.3e5; - criticalPressure[2] = 14.6e5; - criticalPressure[3] = 220.5e5; - - criticalTemperature[0] = 126.2; - criticalTemperature[1] = 622.0; - criticalTemperature[2] = 782.0; - criticalTemperature[3] = 647.0; - - omega[0] = 0.04; - omega[1] = 0.443; - omega[2] = 0.816; - omega[3] = 0.344; - /////////////////////////////////////////// pressure = 1e7; @@ -339,3 +310,422 @@ TEST( CubicEOSTest, testCubicEOSFourComponentsSRK ) checkRelativeError( logFugacityCoefficients[3], expectedLogFugacityCoefficients[3], relTol ); } + +// ----------------------------------------------------------------- +// Derivative tests +// ----------------------------------------------------------------- + +template< int NC > +using TestData = std::tuple< real64 const, real64 const, Feed< NC > const >; + +template< int NC > +struct TestFeed +{ + static std::array< Feed< NC >, 3 > const feeds; +}; + +template<> +std::array< Feed< 2 >, 3 > const TestFeed< 2 >::feeds = { + Feed< 2 >{0.100000, 0.900000}, + Feed< 2 >{0.500000, 0.500000}, + Feed< 2 >{0.001000, 0.999000} +}; + +template<> +std::array< Feed< 4 >, 3 > const TestFeed< 4 >::feeds = { + Feed< 4 >{0.030933, 0.319683, 0.637861, 0.011523}, + Feed< 4 >{0.000000, 0.349686, 0.637891, 0.012423}, + Feed< 4 >{0.000000, 0.349686, 0.650314, 0.000000} +}; + +template< int NC > +std::vector< TestData< NC > > generateTestData() +{ + std::array< real64 const, 2 > pressures( {1.83959e+06, 1.83959e+08} ); + std::array< real64 const, 2 > temperatures( {2.97150e+02, 3.63000e+02} ); + std::vector< TestData< NC > > testData; + for( const real64 pressure : pressures ) + { + for( const real64 temperature : temperatures ) + { + for( const auto & composition : TestFeed< NC >::feeds ) + { + testData.emplace_back( pressure, temperature, composition ); + } + } + } + return testData; +} + +template< typename EOS, int NC > +class DerivativeTestFixture : public ::testing::TestWithParam< TestData< NC > > +{ +public: + static constexpr integer numComps = NC; + using ParamType = std::tuple< real64 const, real64 const, Feed< NC > const >; +public: + DerivativeTestFixture(); + ~DerivativeTestFixture() = default; + +protected: + void checkDerivative( real64 const a, real64 const b, string const & name ) const + { + checkRelativeError( a, b, relTol, absTol, name ); + } + + std::unique_ptr< TestFluid< NC > > m_fluid; +}; + +template<> +DerivativeTestFixture< PengRobinsonEOS, 2 >::DerivativeTestFixture() + : m_fluid( TestFluid< 2 >::create( {Fluid::C1, Fluid::C5} )) +{} +template<> +DerivativeTestFixture< SoaveRedlichKwongEOS, 2 >::DerivativeTestFixture() + : m_fluid( TestFluid< 2 >::create( {Fluid::C1, Fluid::C5} )) +{} + +template<> +DerivativeTestFixture< PengRobinsonEOS, 4 >::DerivativeTestFixture() + : m_fluid( TestFluid< 4 >::create( {Fluid::N2, Fluid::C8, Fluid::C10, Fluid::H2O} )) +{} +template<> +DerivativeTestFixture< SoaveRedlichKwongEOS, 4 >::DerivativeTestFixture() + : m_fluid( TestFluid< 4 >::create( {Fluid::N2, Fluid::C8, Fluid::C10, Fluid::H2O} )) +{} + +template< typename EOS, int NC > +class MixCoeffDerivativeTestFixture : public DerivativeTestFixture< EOS, NC > +{ +public: + using DerivativeTestFixture< EOS, NC >::numComps; + using ParamType = typename DerivativeTestFixture< EOS, NC >::ParamType; +public: + void testNumericalDerivatives( ParamType const & testData ) const + { + auto criticalPressure = this->m_fluid->getCriticalPressure(); + auto criticalTemperature = this->m_fluid->getCriticalTemperature(); + auto omega = this->m_fluid->getAcentricFactor(); + real64 binaryInteractionCoefficients = 0.0; // not implemented yet + + array1d< real64 > aPureCoefficient( numComps ); + array1d< real64 > bPureCoefficient( numComps ); + real64 aMixtureCoefficient = 0.0; + real64 bMixtureCoefficient = 0.0; + real64 currentAMixtureCoefficient = 0.0; + real64 currentBMixtureCoefficient = 0.0; + real64 fdDerivative = 0.0; + + real64 daMixtureCoefficient_dp = 0.0; + real64 dbMixtureCoefficient_dp = 0.0; + real64 daMixtureCoefficient_dt = 0.0; + real64 dbMixtureCoefficient_dt = 0.0; + array1d< real64 > daMixtureCoefficient_dz( numComps ); + array1d< real64 > dbMixtureCoefficient_dz( numComps ); + + array1d< real64 > composition; + real64 const pressure = std::get< 0 >( testData ); + real64 const temperature = std::get< 1 >( testData ); + TestFluid< NC >::createArray( composition, std::get< 2 >( testData )); + + auto computeCoefficients = [&]( real64 const p, real64 const t, auto const & zmf, real64 & a, real64 & b ){ + CubicEOSPhaseModel< EOS >::computeMixtureCoefficients( + numComps, + p, t, zmf, + criticalPressure, criticalTemperature, omega, + binaryInteractionCoefficients, + aPureCoefficient, + bPureCoefficient, + a, b + ); + }; + + // Calculate values + computeCoefficients( pressure, temperature, composition, aMixtureCoefficient, bMixtureCoefficient ); + // Calculate derivatives + CubicEOSPhaseModel< EOS >::computeMixtureCoefficients( + numComps, + pressure, + temperature, + composition, + criticalPressure, + criticalTemperature, + omega, + binaryInteractionCoefficients, + aPureCoefficient, + bPureCoefficient, + aMixtureCoefficient, + bMixtureCoefficient, + daMixtureCoefficient_dp, + dbMixtureCoefficient_dp, + daMixtureCoefficient_dt, + dbMixtureCoefficient_dt, + daMixtureCoefficient_dz, + dbMixtureCoefficient_dz ); + // Compare against numerical derivatives + // -- Pressure derivative + real64 const dp = 1.0e-4 * pressure; + computeCoefficients( pressure-dp, temperature, composition, currentAMixtureCoefficient, currentBMixtureCoefficient ); + fdDerivative = -(currentAMixtureCoefficient - aMixtureCoefficient) / dp; + this->checkDerivative( daMixtureCoefficient_dp, fdDerivative, "Mixing Coeff A left pressure derivative" ); + fdDerivative = -(currentBMixtureCoefficient - bMixtureCoefficient) / dp; + this->checkDerivative( dbMixtureCoefficient_dp, fdDerivative, "Mixing Coeff B left pressure derivative" ); + computeCoefficients( pressure+dp, temperature, composition, currentAMixtureCoefficient, currentBMixtureCoefficient ); + fdDerivative = (currentAMixtureCoefficient - aMixtureCoefficient) / dp; + this->checkDerivative( daMixtureCoefficient_dp, fdDerivative, "Mixing Coeff A right pressure derivative" ); + fdDerivative = (currentBMixtureCoefficient - bMixtureCoefficient) / dp; + this->checkDerivative( dbMixtureCoefficient_dp, fdDerivative, "Mixing Coeff B right pressure derivative" ); + // -- Temperature derivative + real64 const dt = 1.0e-6 * temperature; + computeCoefficients( pressure, temperature-dt, composition, currentAMixtureCoefficient, currentBMixtureCoefficient ); + fdDerivative = -(currentAMixtureCoefficient - aMixtureCoefficient) / dt; + this->checkDerivative( daMixtureCoefficient_dt, fdDerivative, "Mixing Coeff A left temperature derivative" ); + fdDerivative = -(currentBMixtureCoefficient - bMixtureCoefficient) / dt; + this->checkDerivative( dbMixtureCoefficient_dt, fdDerivative, "Mixing Coeff B left temperature derivative" ); + computeCoefficients( pressure, temperature+dt, composition, currentAMixtureCoefficient, currentBMixtureCoefficient ); + fdDerivative = (currentAMixtureCoefficient - aMixtureCoefficient) / dt; + this->checkDerivative( daMixtureCoefficient_dt, fdDerivative, "Mixing Coeff A right temperature derivative" ); + fdDerivative = (currentBMixtureCoefficient - bMixtureCoefficient) / dt; + this->checkDerivative( dbMixtureCoefficient_dt, fdDerivative, "Mixing Coeff B right temperature derivative" ); + // -- Composition derivatives derivative + real64 const dz = 1.0e-7; + for( integer ic = 0; ic < numComps; ++ic ) + { + composition[ic] -= dz; + computeCoefficients( pressure, temperature, composition, currentAMixtureCoefficient, currentBMixtureCoefficient ); + fdDerivative = -(currentAMixtureCoefficient - aMixtureCoefficient) / dz; + this->checkDerivative( daMixtureCoefficient_dz[ic], fdDerivative, "Mixing Coeff A left composition derivative" ); + fdDerivative = -(currentBMixtureCoefficient - bMixtureCoefficient) / dz; + this->checkDerivative( dbMixtureCoefficient_dz[ic], fdDerivative, "Mixing Coeff B left composition derivative" ); + composition[ic] += 2.0*dz; + computeCoefficients( pressure, temperature, composition, currentAMixtureCoefficient, currentBMixtureCoefficient ); + fdDerivative = (currentAMixtureCoefficient - aMixtureCoefficient) / dz; + this->checkDerivative( daMixtureCoefficient_dz[ic], fdDerivative, "Mixing Coeff A right composition derivative" ); + fdDerivative = (currentBMixtureCoefficient - bMixtureCoefficient) / dz; + this->checkDerivative( dbMixtureCoefficient_dz[ic], fdDerivative, "Mixing Coeff B right composition derivative" ); + composition[ic] -= dz; + } + } +}; + +using MixCoeffDerivativePR2TestFixture = MixCoeffDerivativeTestFixture< PengRobinsonEOS, 2 >; +using MixCoeffDerivativePR4TestFixture = MixCoeffDerivativeTestFixture< PengRobinsonEOS, 4 >; +using MixCoeffDerivativeSRK2TestFixture = MixCoeffDerivativeTestFixture< SoaveRedlichKwongEOS, 2 >; +using MixCoeffDerivativeSRK4TestFixture = MixCoeffDerivativeTestFixture< SoaveRedlichKwongEOS, 4 >; + +TEST_P( MixCoeffDerivativePR2TestFixture, testNumericalDerivatives ) +{ + testNumericalDerivatives( GetParam() ); +} +TEST_P( MixCoeffDerivativePR4TestFixture, testNumericalDerivatives ) +{ + testNumericalDerivatives( GetParam() ); +} +TEST_P( MixCoeffDerivativeSRK2TestFixture, testNumericalDerivatives ) +{ + testNumericalDerivatives( GetParam() ); +} +TEST_P( MixCoeffDerivativeSRK4TestFixture, testNumericalDerivatives ) +{ + testNumericalDerivatives( GetParam() ); +} + +// 2-component fluid test +INSTANTIATE_TEST_SUITE_P( + CubicEOSTest, + MixCoeffDerivativePR2TestFixture, + ::testing::ValuesIn( generateTestData< 2 >()) + ); +INSTANTIATE_TEST_SUITE_P( + CubicEOSTest, + MixCoeffDerivativeSRK2TestFixture, + ::testing::ValuesIn( generateTestData< 2 >()) + ); + +// 4-component fluid test +INSTANTIATE_TEST_SUITE_P( + CubicEOSTest, + MixCoeffDerivativePR4TestFixture, + ::testing::ValuesIn( generateTestData< 4 >()) + ); + +INSTANTIATE_TEST_SUITE_P( + CubicEOSTest, + MixCoeffDerivativeSRK4TestFixture, + ::testing::ValuesIn( generateTestData< 4 >()) + ); + +template< typename EOS, int NC > +class CompressibilityDerivativeTestFixture : public DerivativeTestFixture< EOS, NC > +{ +public: + using DerivativeTestFixture< EOS, NC >::numComps; + using ParamType = typename DerivativeTestFixture< EOS, NC >::ParamType; +public: + void testNumericalDerivatives( ParamType const & testData ) const + { + auto criticalPressure = this->m_fluid->getCriticalPressure(); + auto criticalTemperature = this->m_fluid->getCriticalTemperature(); + auto omega = this->m_fluid->getAcentricFactor(); + real64 binaryInteractionCoefficients = 0.0; // not implemented yet + + array1d< real64 > aPureCoefficient( numComps ); + array1d< real64 > bPureCoefficient( numComps ); + real64 aMixtureCoefficient = 0.0; + real64 bMixtureCoefficient = 0.0; + real64 daMixtureCoefficient_dp = 0.0; + real64 dbMixtureCoefficient_dp = 0.0; + real64 daMixtureCoefficient_dt = 0.0; + real64 dbMixtureCoefficient_dt = 0.0; + array1d< real64 > daMixtureCoefficient_dz( numComps ); + array1d< real64 > dbMixtureCoefficient_dz( numComps ); + + real64 compressibilityFactor = 0.0; + real64 dCompressibilityFactor_dp = 0.0; + real64 dCompressibilityFactor_dt = 0.0; + array1d< real64 > dCompressibilityFactor_dz( numComps ); + + real64 currentCompressibilityFactor = 0.0; + real64 fdDerivative = 0.0; + + array1d< real64 > composition; + real64 const pressure = std::get< 0 >( testData ); + real64 const temperature = std::get< 1 >( testData ); + TestFluid< NC >::createArray( composition, std::get< 2 >( testData )); + + auto computeCompressibilityFactor = [&]( real64 const p, real64 const t, auto const & zmf, real64 & z ){ + CubicEOSPhaseModel< EOS >::computeMixtureCoefficients( + numComps, + p, t, zmf, + criticalPressure, criticalTemperature, omega, + binaryInteractionCoefficients, + aPureCoefficient, + bPureCoefficient, + aMixtureCoefficient, bMixtureCoefficient + ); + CubicEOSPhaseModel< EOS >::computeCompressibilityFactor( + numComps, + zmf, + binaryInteractionCoefficients, + aPureCoefficient, + bPureCoefficient, + aMixtureCoefficient, + bMixtureCoefficient, + z ); + }; + + // Calculate values + computeCompressibilityFactor( pressure, temperature, composition, compressibilityFactor ); + // Calculate derivatives + CubicEOSPhaseModel< EOS >::computeMixtureCoefficients( + numComps, + pressure, + temperature, + composition, + criticalPressure, + criticalTemperature, + omega, + binaryInteractionCoefficients, + aPureCoefficient, + bPureCoefficient, + aMixtureCoefficient, + bMixtureCoefficient, + daMixtureCoefficient_dp, + dbMixtureCoefficient_dp, + daMixtureCoefficient_dt, + dbMixtureCoefficient_dt, + daMixtureCoefficient_dz, + dbMixtureCoefficient_dz ); + CubicEOSPhaseModel< EOS >::computeCompressibilityFactor( + numComps, + aMixtureCoefficient, + bMixtureCoefficient, + compressibilityFactor, + daMixtureCoefficient_dp, + dbMixtureCoefficient_dp, + daMixtureCoefficient_dt, + dbMixtureCoefficient_dt, + daMixtureCoefficient_dz, + dbMixtureCoefficient_dz, + dCompressibilityFactor_dp, + dCompressibilityFactor_dt, + dCompressibilityFactor_dz ); + // Compare against numerical derivatives + // -- Pressure derivative + real64 const dp = 1.0e-4 * pressure; + computeCompressibilityFactor( pressure-dp, temperature, composition, currentCompressibilityFactor ); + fdDerivative = -(currentCompressibilityFactor - compressibilityFactor) / dp; + this->checkDerivative( dCompressibilityFactor_dp, fdDerivative, "Compressibility factor left pressure derivative" ); + computeCompressibilityFactor( pressure+dp, temperature, composition, currentCompressibilityFactor ); + fdDerivative = (currentCompressibilityFactor - compressibilityFactor) / dp; + this->checkDerivative( dCompressibilityFactor_dp, fdDerivative, "Compressibility factor right pressure derivative" ); + // -- Temperature derivative + real64 const dt = 1.0e-6 * temperature; + computeCompressibilityFactor( pressure, temperature-dt, composition, currentCompressibilityFactor ); + fdDerivative = -(currentCompressibilityFactor - compressibilityFactor) / dt; + this->checkDerivative( dCompressibilityFactor_dt, fdDerivative, "Compressibility factor left temperature derivative" ); + computeCompressibilityFactor( pressure, temperature+dt, composition, currentCompressibilityFactor ); + fdDerivative = (currentCompressibilityFactor - compressibilityFactor) / dt; + this->checkDerivative( dCompressibilityFactor_dt, fdDerivative, "Compressibility factor right temperature derivative" ); + // -- Composition derivatives derivative + real64 const dz = 1.0e-7; + for( integer ic = 0; ic < numComps; ++ic ) + { + composition[ic] -= dz; + computeCompressibilityFactor( pressure, temperature, composition, currentCompressibilityFactor ); + fdDerivative = -(currentCompressibilityFactor - compressibilityFactor) / dz; + this->checkDerivative( dCompressibilityFactor_dz[ic], fdDerivative, "Compressibility factor left composition derivative" ); + composition[ic] += 2.0*dz; + computeCompressibilityFactor( pressure, temperature, composition, currentCompressibilityFactor ); + fdDerivative = (currentCompressibilityFactor - compressibilityFactor) / dz; + this->checkDerivative( dCompressibilityFactor_dz[ic], fdDerivative, "Compressibility factor right composition derivative" ); + composition[ic] -= dz; + } + } +}; + +using CompressibilityDerivativePR2TestFixture = CompressibilityDerivativeTestFixture< PengRobinsonEOS, 2 >; +using CompressibilityDerivativePR4TestFixture = CompressibilityDerivativeTestFixture< PengRobinsonEOS, 4 >; +using CompressibilityDerivativeSRK2TestFixture = CompressibilityDerivativeTestFixture< SoaveRedlichKwongEOS, 2 >; +using CompressibilityDerivativeSRK4TestFixture = CompressibilityDerivativeTestFixture< SoaveRedlichKwongEOS, 4 >; + +TEST_P( CompressibilityDerivativePR2TestFixture, testNumericalDerivatives ) +{ + testNumericalDerivatives( GetParam() ); +} +TEST_P( CompressibilityDerivativePR4TestFixture, testNumericalDerivatives ) +{ + testNumericalDerivatives( GetParam() ); +} +TEST_P( CompressibilityDerivativeSRK2TestFixture, testNumericalDerivatives ) +{ + testNumericalDerivatives( GetParam() ); +} +TEST_P( CompressibilityDerivativeSRK4TestFixture, testNumericalDerivatives ) +{ + testNumericalDerivatives( GetParam() ); +} + +// 2-component fluid test +INSTANTIATE_TEST_SUITE_P( + CubicEOSTest, + CompressibilityDerivativePR2TestFixture, + ::testing::ValuesIn( generateTestData< 2 >()) + ); +INSTANTIATE_TEST_SUITE_P( + CubicEOSTest, + CompressibilityDerivativeSRK2TestFixture, + ::testing::ValuesIn( generateTestData< 2 >()) + ); + +// 4-component fluid test +INSTANTIATE_TEST_SUITE_P( + CubicEOSTest, + CompressibilityDerivativePR4TestFixture, + ::testing::ValuesIn( generateTestData< 4 >()) + ); +INSTANTIATE_TEST_SUITE_P( + CubicEOSTest, + CompressibilityDerivativeSRK4TestFixture, + ::testing::ValuesIn( generateTestData< 4 >()) + ); diff --git a/src/coreComponents/dataRepository/ExecutableGroup.hpp b/src/coreComponents/dataRepository/ExecutableGroup.hpp index cb7547e046e..69fa86db55b 100644 --- a/src/coreComponents/dataRepository/ExecutableGroup.hpp +++ b/src/coreComponents/dataRepository/ExecutableGroup.hpp @@ -116,7 +116,7 @@ class ExecutableGroup : public dataRepository::Group * @brief Get the target's time step behavior. * @return The time stepping type */ - TimesteppingBehavior getTimesteppingBehavior() { return m_timesteppingBehavior; } + TimesteppingBehavior getTimesteppingBehavior() const { return m_timesteppingBehavior; } private: diff --git a/src/coreComponents/dataRepository/ObjectCatalog.hpp b/src/coreComponents/dataRepository/ObjectCatalog.hpp index 8184efd9e15..4fdf4704f43 100644 --- a/src/coreComponents/dataRepository/ObjectCatalog.hpp +++ b/src/coreComponents/dataRepository/ObjectCatalog.hpp @@ -697,12 +697,12 @@ class CatalogEntryConstructor< BASETYPE, TYPE > * generation of a CatalogEntry prior to main(). */ #define REGISTER_CATALOG_ENTRY( BaseType, DerivedType, ... ) \ - namespace { geos::dataRepository::CatalogEntryConstructor< BaseType, DerivedType, __VA_ARGS__ > catEntry_ ## DerivedType; } + namespace { [[maybe_unused]] geos::dataRepository::CatalogEntryConstructor< BaseType, DerivedType, __VA_ARGS__ > catEntry_ ## DerivedType; } /** * @brief Same as REGISTER_CATALOG_ENTRY, but for classes with no-argument constructors. */ #define REGISTER_CATALOG_ENTRY0( BaseType, DerivedType ) \ - namespace { geos::dataRepository::CatalogEntryConstructor< BaseType, DerivedType > catEntry_ ## DerivedType; } + namespace { [[maybe_unused]] geos::dataRepository::CatalogEntryConstructor< BaseType, DerivedType > catEntry_ ## DerivedType; } #endif /* GEOS_DATAREPOSITORY_OBJECTCATALOG_HPP_ */ diff --git a/src/coreComponents/dataRepository/wrapperHelpers.hpp b/src/coreComponents/dataRepository/wrapperHelpers.hpp index 84ec1169f47..1552de88280 100644 --- a/src/coreComponents/dataRepository/wrapperHelpers.hpp +++ b/src/coreComponents/dataRepository/wrapperHelpers.hpp @@ -228,7 +228,7 @@ byteSizeOfElement() template< typename T > inline size_t byteSize( T const & value ) -{ return size( value ) * byteSizeOfElement< T >(); } +{ return wrapperHelpers::size( value ) * byteSizeOfElement< T >(); } template< typename T > @@ -259,7 +259,7 @@ capacity( T const & value ) template< typename T > std::enable_if_t< !traits::HasMemberFunction_capacity< T const >, localIndex > capacity( T const & value ) -{ return size( value ); } +{ return wrapperHelpers::size( value ); } diff --git a/src/coreComponents/events/PeriodicEvent.cpp b/src/coreComponents/events/PeriodicEvent.cpp index 5b6f02e6d81..db3a360dceb 100644 --- a/src/coreComponents/events/PeriodicEvent.cpp +++ b/src/coreComponents/events/PeriodicEvent.cpp @@ -237,14 +237,20 @@ void PeriodicEvent::cleanup( real64 const time_n, void PeriodicEvent::validate() const { + ExecutableGroup const * target = getEventTarget(); + if( target == nullptr ) + { + return; + } + GEOS_THROW_IF( m_timeFrequency > 0 && - getEventTarget()->getTimesteppingBehavior() == ExecutableGroup::TimesteppingBehavior::DeterminesTimeStepSize, + target->getTimesteppingBehavior() == ExecutableGroup::TimesteppingBehavior::DeterminesTimeStepSize, GEOS_FMT( "`{}`: This event targets an object that automatically selects the time step size. Therefore, `{}` cannot be used here. However, forcing a constant time step size can still be achived with `{}`.", getName(), viewKeyStruct::timeFrequencyString(), EventBase::viewKeyStruct::forceDtString() ), InputError ); GEOS_THROW_IF( m_cycleFrequency != 1 && - getEventTarget()->getTimesteppingBehavior() == ExecutableGroup::TimesteppingBehavior::DeterminesTimeStepSize, + target->getTimesteppingBehavior() == ExecutableGroup::TimesteppingBehavior::DeterminesTimeStepSize, GEOS_FMT( "`{}`: This event targets an object that automatically selects the time step size. Therefore, `{}` cannot be used here. However, forcing a constant time step size can still be achived with `{}`.", getName(), viewKeyStruct::cycleFrequencyString(), EventBase::viewKeyStruct::forceDtString() ), diff --git a/src/coreComponents/fileIO/vtk/VTKPolyDataWriterInterface.cpp b/src/coreComponents/fileIO/vtk/VTKPolyDataWriterInterface.cpp index ef0d0340df7..3ca94f5e54d 100644 --- a/src/coreComponents/fileIO/vtk/VTKPolyDataWriterInterface.cpp +++ b/src/coreComponents/fileIO/vtk/VTKPolyDataWriterInterface.cpp @@ -457,7 +457,7 @@ getVtkCells( CellElementRegion const & region, // Here we privilege code simplicity. This can be more efficient (less tests) if the code is // specialized for each type of subregion. // This is not a time sensitive part of the code. Can be optimized later if needed. - forAll< parallelHostPolicy >( subRegion.size(), [=, &connectivity, &offsets]( localIndex const c ) + forAll< parallelHostPolicy >( subRegion.size(), [&]( localIndex const c ) { localIndex const elemConnOffset = connOffset + c * numVtkData; auto const nodes = nodeList[c]; diff --git a/src/coreComponents/finiteVolume/FluxApproximationBase.cpp b/src/coreComponents/finiteVolume/FluxApproximationBase.cpp index a6e9004573e..134cfc99724 100644 --- a/src/coreComponents/finiteVolume/FluxApproximationBase.cpp +++ b/src/coreComponents/finiteVolume/FluxApproximationBase.cpp @@ -50,6 +50,17 @@ FluxApproximationBase::FluxApproximationBase( string const & name, Group * const setInputFlag( InputFlags::OPTIONAL ). setApplyDefaultValue( 1.0e-8 ). setDescription( "Relative tolerance for area calculations." ); + + registerWrapper( viewKeyStruct::upwindingSchemeString(), &m_upwindingParams.upwindingScheme ). + setInputFlag( dataRepository::InputFlags::OPTIONAL ). + setApplyDefaultValue( UpwindingScheme::PPU ). + setDescription( "Type of upwinding scheme. " + "Valid options:\n* " + EnumStrings< UpwindingScheme >::concat( "\n* " ) ); + +// registerWrapper( viewKeyStruct::epsC1PPUString(), &m_upwindingParams.epsC1PPU ). +// setApplyDefaultValue( 1e-10 ). +// setInputFlag( InputFlags::OPTIONAL ). +// setDescription( "Tolerance for C1-PPU smoothing" ); } FluxApproximationBase::CatalogInterface::CatalogType & diff --git a/src/coreComponents/finiteVolume/FluxApproximationBase.hpp b/src/coreComponents/finiteVolume/FluxApproximationBase.hpp index 346e856685f..a26320b39c3 100644 --- a/src/coreComponents/finiteVolume/FluxApproximationBase.hpp +++ b/src/coreComponents/finiteVolume/FluxApproximationBase.hpp @@ -31,6 +31,36 @@ namespace geos { +/** + * @brief Upwinding scheme. + */ +enum class UpwindingScheme : integer +{ + PPU, ///< PPU upwinding + C1PPU, ///< C1-PPU upwinding from https://doi.org/10.1016/j.advwatres.2017.07.028 +}; + +/** + * @brief Strings for upwinding scheme. + */ +ENUM_STRINGS( UpwindingScheme, + "PPU", + "C1PPU" ); + +/** + * @struct UpwindingParameters + * + * Structure to store upwinding parameters, such as upwinding scheme and related tolerances etc. + */ +struct UpwindingParameters +{ + /// PPU or C1-PPU + UpwindingScheme upwindingScheme; + + /// C1-PPU smoothing tolerance + //real64 epsC1PPU; +}; + /** * @class FluxApproximationBase * @@ -142,6 +172,13 @@ class FluxApproximationBase : public dataRepository::Group /// @return The key for fractureStencil static constexpr char const * fractureStencilString() { return "fractureStencil"; } + + /// @return The key for upwindingScheme + static constexpr char const * upwindingSchemeString() { return "upwindingScheme"; } + + /// @return The key for epsC1PPU + //static constexpr char const * epsC1PPUString() { return "epsC1PPU"; } + }; /** @@ -172,6 +209,12 @@ class FluxApproximationBase : public dataRepository::Group */ void setCoeffName( string const & name ); + /** + * @brief get the upwinding parameters. + * @return upwinding parameters structure. + */ + UpwindingParameters const & upwindingParams() const { return m_upwindingParams; } + protected: virtual void initializePreSubGroups() override; @@ -255,6 +298,9 @@ class FluxApproximationBase : public dataRepository::Group /// length scale of the mesh body real64 m_lengthScale; + /// upwinding parameters + UpwindingParameters m_upwindingParams; + }; template< typename TYPE > diff --git a/src/coreComponents/finiteVolume/TwoPointFluxApproximation.cpp b/src/coreComponents/finiteVolume/TwoPointFluxApproximation.cpp index bb19e420546..c1ed755c2b9 100644 --- a/src/coreComponents/finiteVolume/TwoPointFluxApproximation.cpp +++ b/src/coreComponents/finiteVolume/TwoPointFluxApproximation.cpp @@ -1110,6 +1110,7 @@ void TwoPointFluxApproximation::computeAquiferStencil( DomainPartition & domain, { regionFilter.insert( elemManager.getRegions().getIndex( regionName ) ); } + SortedArrayView< localIndex const > const regionFilterView = regionFilter.toViewConst(); // Step 1: count individual aquifers @@ -1153,7 +1154,7 @@ void TwoPointFluxApproximation::computeAquiferStencil( DomainPartition & domain, } // Filter out elements not in target regions - if( !regionFilter.contains( elemRegionList[iface][ke] )) + if( !regionFilterView.contains( elemRegionList[iface][ke] )) { continue; } @@ -1220,7 +1221,7 @@ void TwoPointFluxApproximation::computeAquiferStencil( DomainPartition & domain, } // Filter out elements not in target regions - if( !regionFilter.contains( elemRegionList[iface][ke] )) + if( !regionFilterView.contains( elemRegionList[iface][ke] )) { continue; } diff --git a/src/coreComponents/linearAlgebra/CMakeLists.txt b/src/coreComponents/linearAlgebra/CMakeLists.txt index aa51f7aba5c..85152e8e530 100644 --- a/src/coreComponents/linearAlgebra/CMakeLists.txt +++ b/src/coreComponents/linearAlgebra/CMakeLists.txt @@ -35,6 +35,7 @@ set( linearAlgebra_headers utilities/LinearSolverParameters.hpp utilities/LinearSolverResult.hpp utilities/NormalOperator.hpp + utilities/ReverseCutHillMcKeeOrdering.hpp utilities/TransposeOperator.hpp ) @@ -49,6 +50,7 @@ set( linearAlgebra_sources solvers/GmresSolver.cpp solvers/KrylovSolver.cpp solvers/SeparateComponentPreconditioner.cpp + utilities/ReverseCutHillMcKeeOrdering.cpp ) set( dependencyList ${parallelDeps} mesh denseLinearAlgebra ) diff --git a/src/coreComponents/linearAlgebra/DofManager.cpp b/src/coreComponents/linearAlgebra/DofManager.cpp index 94af11fb8a5..7c924dec95d 100644 --- a/src/coreComponents/linearAlgebra/DofManager.cpp +++ b/src/coreComponents/linearAlgebra/DofManager.cpp @@ -22,6 +22,7 @@ #include "common/TypeDispatch.hpp" #include "finiteVolume/FluxApproximationBase.hpp" #include "linearAlgebra/interfaces/InterfaceTypes.hpp" +#include "linearAlgebra/utilities/ReverseCutHillMcKeeOrdering.hpp" #include "mesh/mpiCommunications/CommunicationTools.hpp" #include "mesh/DomainPartition.hpp" #include "mesh/ElementRegionManager.hpp" @@ -173,9 +174,114 @@ void forMeshSupport( std::vector< DofManager::FieldSupport > const & support, } } +void fillTrivialPermutation( arrayView1d< localIndex > const permutation ) +{ + forAll< parallelHostPolicy >( permutation.size(), [&]( localIndex const i ) + { + permutation[i] = i; + } ); +} + } // namespace -void DofManager::createIndexArray( FieldDescription const & field ) +array1d< localIndex > DofManager::computePermutation( FieldDescription & field ) +{ + localIndex const fieldIndex = getFieldIndex( field.name ); + + // step 1: save the number of components, and then set it to 1 temporarily + // do not forget to restore at the end + // we set the number of components to 1 to compute the reordering on a smaller matrix + + localIndex const numComps = field.numComponents; + CompMask const globallyCoupledComps = field.globallyCoupledComponents; + field.numComponents = 1; + field.globallyCoupledComponents = CompMask( 1, true ); + + // step 2: compute field dimensions (local dofs, global dofs, etc) + // this is needed to make sure that the sparsity pattern function work properly + // in particular, this function defines the rankOffset (computed with number of components = 1) + + computeFieldDimensions( fieldIndex ); + + // the number of local dofs is available at this point, we allocate space for the permutation + array1d< localIndex > permutation( numLocalDofs( field.name ) ); + + // if no reordering is requesting, we just return the identity permutation + if( field.reorderingType == LocalReorderingType::None ) + { + fillTrivialPermutation( permutation ); + } + else + { + fillTrivialPermutation( permutation ); + computePermutation( field, permutation ); + } + + // reset the number of components + field.numComponents = numComps; + field.globallyCoupledComponents = globallyCoupledComps; + // reset the offsets, since they will be recomputed with the proper number of components + // this is important to get the right reordering for multiphysics problems + field.numLocalDof = 0; + field.rankOffset = 0; + field.numGlobalDof = 0; + field.globalOffset = 0; + field.blockOffset = 0; + + return permutation; +} + +void DofManager::computePermutation( FieldDescription const & field, + arrayView1d< localIndex > const permutation ) +{ + localIndex const fieldIndex = getFieldIndex( field.name ); + + // step 3: allocate and fill the dofNumber array + // this is needed to have dofNumber computed with numComps = 1 + // note that the dofNumbers will be recomputed once we have obtained the permutation + + createIndexArray( field, permutation.toViewConst() ); + + // step 4: compute the local sparsity pattern for this field + + SparsityPattern< globalIndex > pattern; + array1d< localIndex > rowSizes( numLocalDofs( field.name ) ); + // in a first pass, count the row lengths to allocate enough space + countRowLengthsOneBlock( rowSizes, fieldIndex, fieldIndex ); + + // resize the sparsity pattern now that we know the row sizes + pattern.resizeFromRowCapacities< parallelHostPolicy >( numLocalDofs( field.name ), + numGlobalDofs( field.name ), + rowSizes.data() ); + + // compute the sparsity pattern + setSparsityPatternOneBlock( pattern.toView(), fieldIndex, fieldIndex ); + + // step 5: call the reordering function + // the goal of this step is to fill the permutation array + + localIndex const * const offsets = pattern.getOffsets(); + globalIndex const * const columns = pattern.getColumns(); + array1d< localIndex > reversePermutation( permutation.size() ); + + if( field.reorderingType == LocalReorderingType::ReverseCutHillMcKee ) + { + reverseCutHillMcKeeOrdering:: + computePermutation( offsets, columns, rankOffset(), reversePermutation ); + } + else + { + GEOS_ERROR( "This local ordering type is not supported yet" ); + } + + forAll< parallelHostPolicy >( permutation.size(), [&]( localIndex const i ) + { + permutation[reversePermutation[i]] = i; + } ); +} + +void DofManager::createIndexArray( FieldDescription const & field, + arrayView1d< localIndex const > const permutation ) { LocationSwitch( field.location, [&]( auto const loc ) { @@ -203,7 +309,7 @@ void DofManager::createIndexArray( FieldDescription const & field ) // populate index array using a sequential counter forMeshLocation< LOC, false, serialPolicy >( mesh, regions, [&]( auto const locIdx ) { - helper::reference( indexArray, locIdx ) = field.rankOffset + field.numComponents * index++; + helper::reference( indexArray, locIdx ) = field.rankOffset + field.numComponents * permutation[index++]; } ); // synchronize across ranks @@ -367,6 +473,13 @@ void DofManager::addField( string const & fieldName, addField( fieldName, location, components, support ); } +void DofManager::setLocalReorderingType( string const & fieldName, + LocalReorderingType const reorderingType ) +{ + FieldDescription & field = m_fields[getFieldIndex( fieldName )]; + field.reorderingType = reorderingType; +} + void DofManager::disableGlobalCouplingForEquation( string const & fieldName, integer const c ) { @@ -1450,11 +1563,22 @@ void DofManager::reorderByRank() { GEOS_LAI_ASSERT( !m_reordered ); + std::map< string, array1d< localIndex > > permutations; + + // First loop: compute the local permutation + for( FieldDescription & field : m_fields ) + { + // compute local permutation of dofs, if needed + permutations[ field.name ] = computePermutation( field ); + } + + // Second loop: compute the dof number array for( FieldDescription & field : m_fields ) { + // compute field dimensions (local dofs, global dofs, etc) computeFieldDimensions( static_cast< localIndex >( getFieldIndex( field.name ) ) ); // allocate and fill index array - createIndexArray( field ); + createIndexArray( field, permutations.at( field.name ).toViewConst() ); } // update field offsets to account for renumbering diff --git a/src/coreComponents/linearAlgebra/DofManager.hpp b/src/coreComponents/linearAlgebra/DofManager.hpp index 57ef69642f0..db8b7a01c8d 100644 --- a/src/coreComponents/linearAlgebra/DofManager.hpp +++ b/src/coreComponents/linearAlgebra/DofManager.hpp @@ -109,6 +109,15 @@ class DofManager Stencil //!< connectivity is through a (set of) user-provided stencil(s) }; + /** + * @brief Indicates the type of (local to a rank) reordering applied to a given field + */ + enum class LocalReorderingType + { + None, ///< Do not reorder the variables + ReverseCutHillMcKee, ///< Use reverve CutHill-McKee reordering algorithm. + }; + /** * @brief Constructor. * @@ -176,6 +185,14 @@ class DofManager integer components, map< std::pair< string, string >, array1d< string > > const & regions ); + /** + * @brief Set the local reodering of the dof numbers + * @param [in] fieldName the name of the field + * @param [in] reorderingType the reordering type + */ + void setLocalReorderingType( string const & fieldName, + LocalReorderingType const reorderingType ); + /** * @brief Disable the global coupling for a given equation * @param [in] fieldName the name of the field @@ -493,6 +510,7 @@ class DofManager globalIndex blockOffset = 0; ///< offset of this field's block in a block-wise ordered system globalIndex rankOffset = 0; ///< field's first DoF on current processor (within its block, ignoring other fields) globalIndex globalOffset = 0; ///< global offset of field's DOFs on current processor for multi-field problems + LocalReorderingType reorderingType = LocalReorderingType::None; ///< Type of local reordering applied to this field }; /** @@ -519,8 +537,10 @@ class DofManager /** * @brief Create index array for the field * @param field the field descriptor + * @param permutation the local permutation used to fill the index array for this field */ - void createIndexArray( FieldDescription const & field ); + void createIndexArray( FieldDescription const & field, + arrayView1d< localIndex const > const permutation ); /** * @brief Remove an index array for the field @@ -528,6 +548,23 @@ class DofManager */ void removeIndexArray( FieldDescription const & field ); + /** + * @brief Compute a local reordering of the dofNumbers or alternatively, return a trivial permutation + * @param field the field descriptor + * @return permutation the local permutation used to fill the index array for this field + */ + array1d< localIndex > computePermutation( FieldDescription & field ); + + /** + * @brief Compute a local reordering of the dofNumbers + * @param field the field descriptor + * @param permutation the local permutation used to fill the index array for this field + * @detail This function throws an error if the field requires a trivial permutation + */ + void computePermutation( FieldDescription const & field, + arrayView1d< localIndex > const permutation ); + + /** * @brief Calculate or estimate the number of nonzero entries in each local row * @param rowLengths array of row lengths (values are be incremented, not overwritten) diff --git a/src/coreComponents/linearAlgebra/interfaces/hypre/mgrStrategies/CompositionalMultiphaseFVM.hpp b/src/coreComponents/linearAlgebra/interfaces/hypre/mgrStrategies/CompositionalMultiphaseFVM.hpp index 46ce449a883..93ae07276c7 100644 --- a/src/coreComponents/linearAlgebra/interfaces/hypre/mgrStrategies/CompositionalMultiphaseFVM.hpp +++ b/src/coreComponents/linearAlgebra/interfaces/hypre/mgrStrategies/CompositionalMultiphaseFVM.hpp @@ -102,8 +102,6 @@ class CompositionalMultiphaseFVM : public MGRStrategyBase< 2 > GEOS_LAI_CHECK_ERROR( HYPRE_MGRSetLevelSmoothType( precond.ptr, m_levelSmoothType ) ); GEOS_LAI_CHECK_ERROR( HYPRE_MGRSetLevelSmoothIters( precond.ptr, m_levelSmoothIters ) ); - GEOS_LAI_CHECK_ERROR( HYPRE_MGRSetTruncateCoarseGridThreshold( precond.ptr, 1e-20 )); // truncate intermediate/coarse grids - #if GEOS_USE_HYPRE_DEVICE == GEOS_USE_HYPRE_CUDA || GEOS_USE_HYPRE_DEVICE == GEOS_USE_HYPRE_HIP GEOS_LAI_CHECK_ERROR( HYPRE_MGRSetRelaxType( precond.ptr, getAMGRelaxationType( LinearSolverParameters::AMG::SmootherType::l1jacobi ) ) ); #endif diff --git a/src/coreComponents/linearAlgebra/interfaces/hypre/mgrStrategies/CompositionalMultiphaseReservoirFVM.hpp b/src/coreComponents/linearAlgebra/interfaces/hypre/mgrStrategies/CompositionalMultiphaseReservoirFVM.hpp index 2f4b90ca1de..6e09fbca559 100644 --- a/src/coreComponents/linearAlgebra/interfaces/hypre/mgrStrategies/CompositionalMultiphaseReservoirFVM.hpp +++ b/src/coreComponents/linearAlgebra/interfaces/hypre/mgrStrategies/CompositionalMultiphaseReservoirFVM.hpp @@ -118,8 +118,6 @@ class CompositionalMultiphaseReservoirFVM : public MGRStrategyBase< 3 > GEOS_LAI_CHECK_ERROR( HYPRE_MGRSetNonCpointsToFpoints( precond.ptr, 1 )); GEOS_LAI_CHECK_ERROR( HYPRE_MGRSetLevelSmoothType( precond.ptr, m_levelSmoothType ) ); GEOS_LAI_CHECK_ERROR( HYPRE_MGRSetLevelSmoothIters( precond.ptr, m_levelSmoothIters ) ); - GEOS_LAI_CHECK_ERROR( HYPRE_MGRSetTruncateCoarseGridThreshold( precond.ptr, 1e-20 )); // Low tolerance to remove only zeros - // if the wells are shut, using Gaussian elimination as F-relaxation for the well block is an overkill // in that case, we just use Jacobi if( mgrParams.areWellsShut ) diff --git a/src/coreComponents/linearAlgebra/interfaces/hypre/mgrStrategies/CompositionalMultiphaseReservoirHybridFVM.hpp b/src/coreComponents/linearAlgebra/interfaces/hypre/mgrStrategies/CompositionalMultiphaseReservoirHybridFVM.hpp index b6e6b3b6017..d8f8491f38a 100644 --- a/src/coreComponents/linearAlgebra/interfaces/hypre/mgrStrategies/CompositionalMultiphaseReservoirHybridFVM.hpp +++ b/src/coreComponents/linearAlgebra/interfaces/hypre/mgrStrategies/CompositionalMultiphaseReservoirHybridFVM.hpp @@ -134,7 +134,7 @@ class CompositionalMultiphaseReservoirHybridFVM : public MGRStrategyBase< 4 > GEOS_LAI_CHECK_ERROR( HYPRE_MGRSetLevelRestrictType( precond.ptr, toUnderlyingPtr( m_levelRestrictType ) ) ); GEOS_LAI_CHECK_ERROR( HYPRE_MGRSetCoarseGridMethod( precond.ptr, toUnderlyingPtr( m_levelCoarseGridMethod ) ) ); GEOS_LAI_CHECK_ERROR( HYPRE_MGRSetNonCpointsToFpoints( precond.ptr, 1 )); - GEOS_LAI_CHECK_ERROR( HYPRE_MGRSetTruncateCoarseGridThreshold( precond.ptr, 1e-20 )); + GEOS_LAI_CHECK_ERROR( HYPRE_MGRSetLevelSmoothType( precond.ptr, m_levelSmoothType ) ); GEOS_LAI_CHECK_ERROR( HYPRE_MGRSetLevelSmoothIters( precond.ptr, m_levelSmoothIters ) ); diff --git a/src/coreComponents/linearAlgebra/interfaces/hypre/mgrStrategies/ReactiveCompositionalMultiphaseOBL.hpp b/src/coreComponents/linearAlgebra/interfaces/hypre/mgrStrategies/ReactiveCompositionalMultiphaseOBL.hpp index dfe98a08f92..854c20eb942 100644 --- a/src/coreComponents/linearAlgebra/interfaces/hypre/mgrStrategies/ReactiveCompositionalMultiphaseOBL.hpp +++ b/src/coreComponents/linearAlgebra/interfaces/hypre/mgrStrategies/ReactiveCompositionalMultiphaseOBL.hpp @@ -96,7 +96,6 @@ class ReactiveCompositionalMultiphaseOBL : public MGRStrategyBase< 1 > GEOS_LAI_CHECK_ERROR( HYPRE_MGRSetNonCpointsToFpoints( precond.ptr, 1 )); GEOS_LAI_CHECK_ERROR( HYPRE_MGRSetLevelSmoothType( precond.ptr, m_levelSmoothType ) ); GEOS_LAI_CHECK_ERROR( HYPRE_MGRSetLevelSmoothIters( precond.ptr, m_levelSmoothIters ) ); - GEOS_LAI_CHECK_ERROR( HYPRE_MGRSetTruncateCoarseGridThreshold( precond.ptr, 1e-20 )); // truncate intermediate/coarse grids // Note: uncommenting HYPRE_MGRSetLevelFRelaxMethod and commenting HYPRE_MGRSetRelaxType breaks the recipe. This requires further // investigation diff --git a/src/coreComponents/linearAlgebra/interfaces/hypre/mgrStrategies/ThermalCompositionalMultiphaseFVM.hpp b/src/coreComponents/linearAlgebra/interfaces/hypre/mgrStrategies/ThermalCompositionalMultiphaseFVM.hpp index be617fbba95..16454c14280 100644 --- a/src/coreComponents/linearAlgebra/interfaces/hypre/mgrStrategies/ThermalCompositionalMultiphaseFVM.hpp +++ b/src/coreComponents/linearAlgebra/interfaces/hypre/mgrStrategies/ThermalCompositionalMultiphaseFVM.hpp @@ -106,8 +106,6 @@ class ThermalCompositionalMultiphaseFVM : public MGRStrategyBase< 2 > GEOS_LAI_CHECK_ERROR( HYPRE_MGRSetLevelSmoothType( precond.ptr, m_levelSmoothType ) ); GEOS_LAI_CHECK_ERROR( HYPRE_MGRSetLevelSmoothIters( precond.ptr, m_levelSmoothIters ) ); - GEOS_LAI_CHECK_ERROR( HYPRE_MGRSetTruncateCoarseGridThreshold( precond.ptr, 1e-20 )); // truncate intermediate/coarse grids - #if GEOS_USE_HYPRE_DEVICE == GEOS_USE_HYPRE_CUDA || GEOS_USE_HYPRE_DEVICE == GEOS_USE_HYPRE_HIP GEOS_LAI_CHECK_ERROR( HYPRE_MGRSetRelaxType( precond.ptr, getAMGRelaxationType( LinearSolverParameters::AMG::SmootherType::l1jacobi ) ) ); #endif diff --git a/src/coreComponents/linearAlgebra/interfaces/hypre/mgrStrategies/ThermalMultiphasePoromechanics.hpp b/src/coreComponents/linearAlgebra/interfaces/hypre/mgrStrategies/ThermalMultiphasePoromechanics.hpp index 4ed406e1f29..d37e08e2bc9 100644 --- a/src/coreComponents/linearAlgebra/interfaces/hypre/mgrStrategies/ThermalMultiphasePoromechanics.hpp +++ b/src/coreComponents/linearAlgebra/interfaces/hypre/mgrStrategies/ThermalMultiphasePoromechanics.hpp @@ -130,6 +130,9 @@ class ThermalMultiphasePoromechanics : public MGRStrategyBase< 3 > mgrData.coarseSolver.setup = HYPRE_BoomerAMGSetup; mgrData.coarseSolver.solve = HYPRE_BoomerAMGSolve; mgrData.coarseSolver.destroy = HYPRE_BoomerAMGDestroy; + + // Configure the BoomerAMG solver used as F-relaxation for the first level + setMechanicsFSolver( precond, mgrData ); } }; diff --git a/src/coreComponents/linearAlgebra/interfaces/hypre/mgrStrategies/ThermalSinglePhasePoromechanics.hpp b/src/coreComponents/linearAlgebra/interfaces/hypre/mgrStrategies/ThermalSinglePhasePoromechanics.hpp index 5fc08a4574f..b22dae60054 100644 --- a/src/coreComponents/linearAlgebra/interfaces/hypre/mgrStrategies/ThermalSinglePhasePoromechanics.hpp +++ b/src/coreComponents/linearAlgebra/interfaces/hypre/mgrStrategies/ThermalSinglePhasePoromechanics.hpp @@ -101,6 +101,9 @@ class ThermalSinglePhasePoromechanics : public MGRStrategyBase< 1 > mgrData.coarseSolver.setup = HYPRE_BoomerAMGSetup; mgrData.coarseSolver.solve = HYPRE_BoomerAMGSolve; mgrData.coarseSolver.destroy = HYPRE_BoomerAMGDestroy; + + // Configure the BoomerAMG solver used as F-relaxation for the first level + setMechanicsFSolver( precond, mgrData ); } }; diff --git a/src/coreComponents/linearAlgebra/unitTests/CMakeLists.txt b/src/coreComponents/linearAlgebra/unitTests/CMakeLists.txt index 06591fdb73f..e33bec1748e 100644 --- a/src/coreComponents/linearAlgebra/unitTests/CMakeLists.txt +++ b/src/coreComponents/linearAlgebra/unitTests/CMakeLists.txt @@ -6,7 +6,8 @@ set( parallel_tests Matrices Vectors ExternalSolvers - KrylovSolvers ) + KrylovSolvers + ReverseCutHillMcKeeOrdering ) set( nranks 2 ) diff --git a/src/coreComponents/linearAlgebra/unitTests/testReverseCutHillMcKeeOrdering.cpp b/src/coreComponents/linearAlgebra/unitTests/testReverseCutHillMcKeeOrdering.cpp new file mode 100644 index 00000000000..d79d9631f7d --- /dev/null +++ b/src/coreComponents/linearAlgebra/unitTests/testReverseCutHillMcKeeOrdering.cpp @@ -0,0 +1,121 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2018-2020 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2020 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2018-2020 TotalEnergies + * Copyright (c) 2019- GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +/** + * @file testReverseCutHillMcKeeOrdering.cpp + */ + +#include "codingUtilities/UnitTestUtilities.hpp" +#include "linearAlgebra/utilities/ReverseCutHillMcKeeOrdering.hpp" + +#include + +using namespace geos; + +TEST( ReverseCutHillMcKeeOrderingTest, reorder ) +{ + // the data in this test comes from PoroElastic_staircase_co2_3d.xml + // it is extracted of the flow dof numbers (MPI rank 1 in a 2-rank simulation) + + localIndex constexpr numRows = 144; + localIndex constexpr rankOffset = 144; + localIndex constexpr numNonZeros = 876; + + array1d< localIndex > permutation( numRows ); + localIndex const expectedPermutation[numRows] + { 16, 10, 17, 14, 4, 8, 11, 34, 15, 12, 2, 5, 6, 9, 28, 35, 32, 13, + 88, 0, 3, 22, 82, 7, 26, 29, 106, 33, 30, 89, 86, 76, 1, 20, 23, 80, 83, + 24, 27, 100, 107, 104, 31, 52, 87, 84, 74, 77, 18, 21, 94, 78, 81, 46, + 25, 98, 101, 124, 105, 102, 53, 50, 85, 72, 75, 40, 19, 92, 95, 79, 44, + 47, 96, 99, 118, 125, 122, 103, 70, 51, 48, 73, 38, 41, 90, 93, 112, 42, + 45, 64, 97, 116, 119, 123, 120, 71, 68, 49, 36, 39, 58, 91, 110, 113, 43, + 62, 65, 114, 117, 121, 142, 69, 66, 37, 56, 59, 108, 111, 60, 63, 136, + 115, 143, 140, 67, 54, 57, 130, 109, 61, 134, 137, 141, 138, 55, 128, 131, + 132, 135, 139, 126, 129, 133, 127 }; + localIndex const offsets[numRows+1] + { 0, 6, 13, 19, 26, 31, 37, 43, 50, 56, 63, 68, 74, 79, 85, 90, 96, + 100, 105, 112, 119, 126, 133, 139, 145, 152, 159, 166, 173, 179, 185, 191, + 197, 203, 209, 214, 219, 225, 231, 238, 245, 252, 259, 265, 271, 278, 285, + 292, 299, 304, 309, 315, 321, 327, 333, 339, 345, 352, 359, 366, 373, 379, + 385, 392, 399, 406, 413, 418, 423, 429, 435, 441, 447, 452, 458, 464, 471, + 477, 484, 489, 495, 501, 508, 514, 521, 525, 530, 535, 541, 546, 552, 559, + 566, 573, 580, 586, 592, 599, 606, 613, 620, 626, 632, 638, 644, 650, 656, + 661, 666, 673, 679, 686, 692, 698, 703, 710, 716, 723, 729, 735, 740, 746, + 751, 757, 762, 767, 771, 777, 782, 789, 795, 802, 808, 814, 819, 826, 832, + 839, 845, 850, 854, 860, 865, 871, 876 }; + globalIndex const columns[numNonZeros+1] + { 12, 144, 145, 146, 150, 220, 13, 144, 145, 147, 151, 162, 221, 14, 144, 146, + 147, 148, 152, 15, 145, 146, 147, 149, 153, 164, 16, 146, 148, 149, 154, 17, + 147, 148, 149, 155, 166, 144, 150, 151, 152, 156, 226, 145, 150, 151, 153, 157, + 168, 227, 146, 150, 152, 153, 154, 158, 147, 151, 152, 153, 155, 159, 170, 148, + 152, 154, 155, 160, 149, 153, 154, 155, 161, 172, 150, 156, 157, 158, 232, 151, + 156, 157, 159, 174, 233, 152, 156, 158, 159, 160, 153, 157, 158, 159, 161, 176, + 154, 158, 160, 161, 155, 159, 160, 161, 178, 120, 145, 162, 163, 164, 168, 184, + 121, 162, 163, 165, 169, 185, 234, 122, 147, 162, 164, 165, 166, 170, 123, 163, + 164, 165, 167, 171, 236, 124, 149, 164, 166, 167, 172, 125, 165, 166, 167, 173, + 238, 151, 162, 168, 169, 170, 174, 190, 163, 168, 169, 171, 175, 191, 240, 153, + 164, 168, 170, 171, 172, 176, 165, 169, 170, 171, 173, 177, 242, 155, 166, 170, + 172, 173, 178, 167, 171, 172, 173, 179, 244, 157, 168, 174, 175, 176, 196, 169, + 174, 175, 177, 197, 246, 159, 170, 174, 176, 177, 178, 171, 175, 176, 177, 179, + 248, 161, 172, 176, 178, 179, 173, 177, 178, 179, 250, 102, 180, 181, 182, 186, + 217, 103, 180, 181, 183, 187, 198, 104, 180, 182, 183, 184, 188, 219, 105, 181, + 182, 183, 185, 189, 200, 106, 162, 182, 184, 185, 190, 221, 107, 163, 183, 184, + 185, 191, 202, 180, 186, 187, 188, 192, 223, 181, 186, 187, 189, 193, 204, 182, + 186, 188, 189, 190, 194, 225, 183, 187, 188, 189, 191, 195, 206, 168, 184, 188, + 190, 191, 196, 227, 169, 185, 189, 190, 191, 197, 208, 186, 192, 193, 194, 229, + 187, 192, 193, 195, 210, 188, 192, 194, 195, 196, 231, 189, 193, 194, 195, 197, + 212, 174, 190, 194, 196, 197, 233, 175, 191, 195, 196, 197, 214, 30, 181, 198, + 199, 200, 204, 31, 198, 199, 201, 205, 270, 32, 183, 198, 200, 201, 202, 206, + 33, 199, 200, 201, 203, 207, 272, 34, 185, 200, 202, 203, 208, 234, 35, 201, + 202, 203, 209, 235, 274, 187, 198, 204, 205, 206, 210, 199, 204, 205, 207, 211, + 276, 189, 200, 204, 206, 207, 208, 212, 201, 205, 206, 207, 209, 213, 278, 191, + 202, 206, 208, 209, 214, 240, 203, 207, 208, 209, 215, 241, 280, 193, 204, 210, + 211, 212, 205, 210, 211, 213, 282, 195, 206, 210, 212, 213, 214, 207, 211, 212, + 213, 215, 284, 197, 208, 212, 214, 215, 246, 209, 213, 214, 215, 247, 286, 84, + 216, 217, 218, 222, 85, 180, 216, 217, 219, 223, 86, 216, 218, 219, 220, 224, + 87, 182, 217, 218, 219, 221, 225, 88, 144, 218, 220, 221, 226, 89, 145, 184, + 219, 220, 221, 227, 216, 222, 223, 224, 228, 186, 217, 222, 223, 225, 229, 218, + 222, 224, 225, 226, 230, 188, 219, 223, 224, 225, 227, 231, 150, 220, 224, 226, + 227, 232, 151, 190, 221, 225, 226, 227, 233, 222, 228, 229, 230, 192, 223, 228, + 229, 231, 224, 228, 230, 231, 232, 194, 225, 229, 230, 231, 233, 156, 226, 230, + 232, 233, 157, 196, 227, 231, 232, 233, 138, 163, 202, 234, 235, 236, 240, 139, + 203, 234, 235, 237, 241, 252, 140, 165, 234, 236, 237, 238, 242, 141, 235, 236, + 237, 239, 243, 254, 142, 167, 236, 238, 239, 244, 143, 237, 238, 239, 245, 256, + 169, 208, 234, 240, 241, 242, 246, 209, 235, 240, 241, 243, 247, 258, 171, 236, + 240, 242, 243, 244, 248, 237, 241, 242, 243, 245, 249, 260, 173, 238, 242, 244, + 245, 250, 239, 243, 244, 245, 251, 262, 175, 214, 240, 246, 247, 248, 215, 241, + 246, 247, 249, 264, 177, 242, 246, 248, 249, 250, 243, 247, 248, 249, 251, 266, + 179, 244, 248, 250, 251, 245, 249, 250, 251, 268, 66, 235, 252, 253, 254, 258, + 274, 67, 252, 253, 255, 259, 275, 68, 237, 252, 254, 255, 256, 260, 69, 253, 254, + 255, 257, 261, 70, 239, 254, 256, 257, 262, 71, 255, 256, 257, 263, 241, 252, 258, + 259, 260, 264, 280, 253, 258, 259, 261, 265, 281, 243, 254, 258, 260, 261, 262, + 266, 255, 259, 260, 261, 263, 267, 245, 256, 260, 262, 263, 268, 257, 261, 262, + 263, 269, 247, 258, 264, 265, 266, 286, 259, 264, 265, 267, 287, 249, 260, 264, + 266, 267, 268, 261, 265, 266, 267, 269, 251, 262, 266, 268, 269, 263, 267, 268, + 269, 48, 199, 270, 271, 272, 276, 49, 270, 271, 273, 277, 50, 201, 270, 272, 273, + 274, 278, 51, 271, 272, 273, 275, 279, 52, 203, 252, 272, 274, 275, 280, 53, 253, + 273, 274, 275, 281, 205, 270, 276, 277, 278, 282, 271, 276, 277, 279, 283, 207, + 272, 276, 278, 279, 280, 284, 273, 277, 278, 279, 281, 285, 209, 258, 274, 278, + 280, 281, 286, 259, 275, 279, 280, 281, 287, 211, 276, 282, 283, 284, 277, 282, + 283, 285, 213, 278, 282, 284, 285, 286, 279, 283, 284, 285, 287, 215, 264, 280, + 284, 286, 287, 265, 281, 285, 286, 287 }; + + reverseCutHillMcKeeOrdering:: + computePermutation( offsets, columns, rankOffset, permutation ); + + for( localIndex i = 0; i < numRows; ++i ) + { + EXPECT_EQ( permutation[i], expectedPermutation[i] ); + } +} diff --git a/src/coreComponents/linearAlgebra/utilities/ReverseCutHillMcKeeOrdering.cpp b/src/coreComponents/linearAlgebra/utilities/ReverseCutHillMcKeeOrdering.cpp new file mode 100644 index 00000000000..89b18650cd8 --- /dev/null +++ b/src/coreComponents/linearAlgebra/utilities/ReverseCutHillMcKeeOrdering.cpp @@ -0,0 +1,367 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2018-2020 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2020 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2018-2020 TotalEnergies + * Copyright (c) 2019- GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +/** + * @file ReverseCutHillMcKeeOrdering.hpp + */ + +#include "ReverseCutHillMcKeeOrdering.hpp" +#include "common/GEOS_RAJA_Interface.hpp" + +namespace geos +{ + +namespace reverseCutHillMcKeeOrdering +{ + +/** + * @brief This function finds the unvisited node with the minimum degree + * @param[in] numRows number of rows in the matrix to reorder + * @param[in] degree the degree array + * @param[in] marker the marker array for unvisited node + * @param[out] rootRef reference to the root (= node with the minimum degree) + */ +static void +findNodeWithMinDegree( localIndex const numRows, + arrayView1d< localIndex > const degree, + arrayView1d< localIndex > const marker, + localIndex & rootRef ) +{ + RAJA::ReduceMinLoc< serialReduce, localIndex > minDegree( LvArray::NumericLimits< localIndex >::max, -1 ); + + forAll< serialPolicy >( numRows, [=] ( localIndex const i ) + { + if( marker[i] < 0 ) + { + minDegree.minloc( degree[i], i ); + } + } ); + rootRef = minDegree.getLoc(); +} + +/** + * @brief This function build level structure start from root + * @param[in] offsets row offsets in the matrix to reorder + * @param[in] columns column indices in the matrix to reorder + * @param[in] numRows number of rows in the matrix to reorder + * @param[in] rankOffset offset of this rank (assuming numComps = 1) + * @param[in] root pointer to the root + * @param[out] marker the marker array for unvisited node + * @param[out] level_i points to the start/end of position on level_j, similar to CSR Matrix + * @param[out] level_j store node number on each level + * @param[out] numLevelsRef return the number of level on this level structure + */ +static void +buildLevel( localIndex const * const offsets, + globalIndex const * const columns, + localIndex const numRows, + localIndex const rankOffset, + localIndex const root, + arrayView1d< localIndex > const marker, + arrayView1d< localIndex > const level_i, + arrayView1d< localIndex > const level_j, + localIndex & numLevelsRef ) +{ + localIndex l1 = 0; + localIndex l2 = 0; + localIndex lCurrent = 0; + localIndex r1 = 0; + localIndex r2 = 0; + localIndex row_i = 0; + localIndex row_j = 0; + localIndex numLevels = 0; + + // set first level first + level_i[0] = 0; + level_j[0] = root; + marker[root] = 0; + numLevels = 1; + l1 = 0; + l2 = 1; + lCurrent = l2; + + // explore nbhds of all nodes in current level + while( l2 > l1 ) + { + level_i[numLevels++] = l2; + + // loop through last level + for( localIndex i = l1; i < l2; i++ ) + { + // the node to explore + row_i = level_j[i]; + r1 = offsets[row_i]; + r2 = offsets[row_i + 1]; + for( localIndex j = r1; j < r2; j++ ) + { + row_j = columns[j] - rankOffset; + if( row_j >= 0 && row_j < numRows ) + { + if( marker[row_j] < 0 ) + { + // unmarked row + marker[row_j] = 0; + level_j[lCurrent++] = row_j; + } + } + } + } + l1 = l2; + l2 = lCurrent; + } + // after this we always have a "ghost" last level + numLevels--; + + // reset marker + for( localIndex i = 0; i < l2; i++ ) + { + marker[level_j[i]] = -1; + } + + numLevelsRef = numLevels; +} + + +/** + * @brief This function find a pseudo-peripheral node start from root + * @param[in] offsets row offsets in the matrix to reorder + * @param[in] columns column indices in the matrix to reorder + * @param[in] numRows number of rows in the matrix to reorder + * @param[in] rankOffset offset of this rank (assuming numComps = 1) + * @param[out] rootRef on return will be a end of the pseudo-peripheral + * @param[out] marker the marker array for unvisited node + * @param[out] level_i level structure i + * @param[out] level_j level structure j + */ +static void +findPseudoPeripheralNode( localIndex const * const offsets, + globalIndex const * const columns, + localIndex const numRows, + localIndex const rankOffset, + localIndex & rootRef, + arrayView1d< localIndex > const marker, + arrayView1d< localIndex > const level_i, + arrayView1d< localIndex > const level_j ) +{ + localIndex r1 = 0; + localIndex r2 = 0; + localIndex row = 0; + localIndex minDegree = 0; + localIndex levDegree = 0; + localIndex numLevels = 0; + localIndex newNumLevels = 0; + localIndex root = rootRef; + + level_i.zero(); + level_j.zero(); + + // build initial level structure from root + buildLevel( offsets, columns, numRows, rankOffset, root, + marker, level_i, level_j, newNumLevels ); + + numLevels = newNumLevels - 1; + while( numLevels < newNumLevels ) + { + numLevels = newNumLevels; + r1 = level_i[numLevels - 1]; + r2 = level_i[numLevels]; + minDegree = numRows; + for( localIndex i = r1; i < r2; i++ ) + { + // select the last level, pick min-degree node + row = level_j[i]; + levDegree = offsets[row + 1] - offsets[row]; + if( minDegree > levDegree ) + { + minDegree = levDegree; + root = row; + } + } + buildLevel( offsets, columns, numRows, rankOffset, root, + marker, level_i, level_j, newNumLevels ); + } + rootRef = root; +} + +/** + * @brief This qsort is very specialized, not worth to put into utilities + * Sort a part of array perm based on degree value (ascend) + * That is, if degree[perm[i]] < degree[perm[j]], we should have i < j + * @param[in] perm the perm array + * @param[in] start start in perm + * @param[in] end end in perm + * @param[out] degree degree array + */ +static void +qsort( arrayView1d< localIndex > const perm, + localIndex const start, + localIndex const end, + arrayView1d< localIndex > const degree ) +{ + if( start >= end ) + { + return; + } + + std::swap( perm[start], perm[(start + end) / 2] ); + localIndex mid = start; + + // loop to split + for( localIndex i = start + 1; i <= end; i++ ) + { + if( degree[perm[i]] < degree[perm[start]] ) + { + std::swap( perm[++mid], perm[i] ); + } + } + std::swap( perm[start], perm[mid] ); + qsort( perm, mid + 1, end, degree ); + qsort( perm, start, mid - 1, degree ); +} + +/** + * @brief Last step in RCM, reverse it + * @param[out] perm perm array + * @param[in] start start position + * @param[in] end end position + */ +static void +reversePermutation( arrayView1d< localIndex > const perm, + localIndex const start, + localIndex const end ) +{ + localIndex i = 0; + localIndex j = 0; + localIndex const mid = (start + end + 1) / 2; + + for( i = start, j = end; i < mid; i++, j-- ) + { + std::swap( perm[i], perm[j] ); + } +} + + +/** + * @brief This function generate numbering for a connected component + * @param[in] offsets row offsets in the matrix to reorder + * @param[in] columns column indices in the matrix to reorder + * @param[in] numRows number of rows in the matrix to reorder + * @param[in] rankOffset offset of this rank (assuming numComps = 1) + * @param[in] root pointer to the root + * @param[out] marker the marker array for unvisited node + * @param[out] perm permutation array + * @param[out] currentNumRef number of nodes already have a perm value + */ +static void +computeNewOrdering( localIndex const * const offsets, + globalIndex const * const columns, + localIndex const numRows, + localIndex const rankOffset, + localIndex const root, + arrayView1d< localIndex > const marker, + arrayView1d< localIndex > const perm, + localIndex & currentNumRef ) +{ + localIndex i = 0; + localIndex j = 0; + localIndex l1 = 0; + localIndex l2 = 0; + localIndex r1 = 0; + localIndex r2 = 0; + localIndex row_i = 0; + localIndex row_j = 0; + localIndex rowStart = 0; + localIndex rowEnd = 0; + localIndex currentNum = currentNumRef; + + marker[root] = 0; + l1 = currentNum; + perm[currentNum++] = root; + l2 = currentNum; + + while( l2 > l1 ) + { + // loop through all nodes is current level + for( i = l1; i < l2; i++ ) + { + row_i = perm[i]; + r1 = offsets[row_i]; + r2 = offsets[row_i + 1]; + rowStart = currentNum; + for( j = r1; j < r2; j++ ) + { + row_j = columns[j] - rankOffset; + if( row_j >= 0 && row_j < numRows ) + { + if( marker[row_j] < 0 ) + { + // save the degree in marker and add it to perm */ + marker[row_j] = offsets[row_j + 1] - offsets[row_j]; + perm[currentNum++] = row_j; + } + } + } + rowEnd = currentNum; + qsort( perm, rowStart, rowEnd - 1, marker ); + } + l1 = l2; + l2 = currentNum; + } + + // reverse + reversePermutation( perm, currentNumRef, currentNum - 1 ); + currentNumRef = currentNum; +} + +void +computePermutation( localIndex const * const offsets, + globalIndex const * const columns, + localIndex const rankOffset, + arrayView1d< localIndex > const perm ) +{ + localIndex root = 0; + localIndex currentNum = 0; + localIndex const numRows = perm.size(); + + // get the degree for each node + array1d< localIndex > degree( numRows ); + array1d< localIndex > marker( numRows ); + // at most numRows levels + array1d< localIndex > level_i( numRows+1 ); + array1d< localIndex > level_j( numRows ); + forAll< parallelHostPolicy >( numRows, [&] ( localIndex const i ) + { + degree[i] = offsets[i + 1] - offsets[i]; + marker[i] = -1; + } ); + + // start RCM loop + currentNum = 0; + while( currentNum < numRows ) + { + // Find the unvisited node with the minimum degree + findNodeWithMinDegree( numRows, degree, marker, root ); + + // This is a new connected component + findPseudoPeripheralNode( offsets, columns, numRows, rankOffset, root, marker, level_i, level_j ); + + // Numbering of this component + computeNewOrdering( offsets, columns, numRows, rankOffset, root, marker, perm, currentNum ); + + } +} + +} // end namespace reverseCutHillMcKeeOrdering + +} // end namespace geos diff --git a/src/coreComponents/linearAlgebra/utilities/ReverseCutHillMcKeeOrdering.hpp b/src/coreComponents/linearAlgebra/utilities/ReverseCutHillMcKeeOrdering.hpp new file mode 100644 index 00000000000..fa74a5c5ce4 --- /dev/null +++ b/src/coreComponents/linearAlgebra/utilities/ReverseCutHillMcKeeOrdering.hpp @@ -0,0 +1,49 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2018-2020 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2020 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2018-2020 TotalEnergies + * Copyright (c) 2019- GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +/** + * @file ReverseCutHillMcKeeOrdering.hpp + */ + +#ifndef GEOS_LINEARALGEBRA_UTILITIES_REVERSECUTHILLMCKEEORDERING_HPP_ +#define GEOS_LINEARALGEBRA_UTILITIES_REVERSECUTHILLMCKEEORDERING_HPP_ + +#include "common/DataTypes.hpp" + +namespace geos +{ + +namespace reverseCutHillMcKeeOrdering +{ + +/** + * @brief This function actually does the RCM ordering of a symmetric csr matrix (entire) + The original implementation can be found in src/parcsr_ls/par_ilu.c in hypre + * @param[in] offsets row offsets in the matrix to reorder + * @param[in] columns column indices in the matrix to reorder + * @param[in] rankOffset offset of this rank (assuming numComps = 1) + * @param[out] perm the permutation array + */ +void +computePermutation( localIndex const * const offsets, + globalIndex const * const columns, + localIndex const rankOffset, + arrayView1d< localIndex > const perm ); + + +} // namespace reverseCutHillMcKeeOrdering + +} // namespace geos + +#endif // GEOS_LINEARALGEBRA_UTILITIES_REVERSECUTHILLMCKEEORDERING_HPP_ diff --git a/src/coreComponents/mainInterface/ProblemManager.cpp b/src/coreComponents/mainInterface/ProblemManager.cpp index 3f318b30dbd..f516cef7104 100644 --- a/src/coreComponents/mainInterface/ProblemManager.cpp +++ b/src/coreComponents/mainInterface/ProblemManager.cpp @@ -539,14 +539,14 @@ void ProblemManager::generateMesh() // setup the base discretizations (hard code this for now) domain.forMeshBodies( [&]( MeshBody & meshBody ) { - CellBlockManagerABC & cellBlockManager = meshBody.getGroup< CellBlockManagerABC >( keys::cellManager ); + CellBlockManagerABC const & cellBlockManager = meshBody.getCellBlockManager(); MeshLevel & baseMesh = meshBody.getBaseDiscretization(); array1d< string > junk; this->generateMeshLevel( baseMesh, cellBlockManager, nullptr, junk.toViewConst() ); ElementRegionManager & elemManager = baseMesh.getElemManager(); - elemManager.generateWells( meshManager, baseMesh ); + elemManager.generateWells( cellBlockManager, baseMesh ); } ); @@ -571,7 +571,7 @@ void ProblemManager::generateMesh() int const order = feDiscretization->getOrder(); string const & discretizationName = feDiscretization->getName(); arrayView1d< string const > const regionNames = discretizationPair.second; - CellBlockManagerABC & cellBlockManager = meshBody.getGroup< CellBlockManagerABC >( keys::cellManager ); + CellBlockManagerABC const & cellBlockManager = meshBody.getCellBlockManager(); // create a high order MeshLevel if( order > 1 ) @@ -610,7 +610,7 @@ void ProblemManager::generateMesh() domain.forMeshBodies( [&]( MeshBody & meshBody ) { - meshBody.deregisterGroup( keys::cellManager ); + meshBody.deregisterCellBlockManager(); meshBody.forMeshLevels( [&]( MeshLevel & meshLevel ) { @@ -700,7 +700,7 @@ ProblemManager::getDiscretizations() const } void ProblemManager::generateMeshLevel( MeshLevel & meshLevel, - CellBlockManagerABC & cellBlockManager, + CellBlockManagerABC const & cellBlockManager, Group const * const discretization, arrayView1d< string const > const & ) { diff --git a/src/coreComponents/mainInterface/ProblemManager.hpp b/src/coreComponents/mainInterface/ProblemManager.hpp index 6e761609a0f..627b43219e0 100644 --- a/src/coreComponents/mainInterface/ProblemManager.hpp +++ b/src/coreComponents/mainInterface/ProblemManager.hpp @@ -340,7 +340,7 @@ class ProblemManager : public dataRepository::Group getDiscretizations() const; void generateMeshLevel( MeshLevel & meshLevel, - CellBlockManagerABC & cellBlockManager, + CellBlockManagerABC const & cellBlockManager, Group const * const discretization, arrayView1d< string const > const & targetRegions ); diff --git a/src/coreComponents/mesh/CMakeLists.txt b/src/coreComponents/mesh/CMakeLists.txt index 22f41fe4c26..a19768c1d04 100644 --- a/src/coreComponents/mesh/CMakeLists.txt +++ b/src/coreComponents/mesh/CMakeLists.txt @@ -36,6 +36,8 @@ set( mesh_headers generators/CellBlockManager.hpp generators/CellBlockManagerABC.hpp generators/CellBlockUtilities.hpp + generators/LineBlock.hpp + generators/LineBlockABC.hpp generators/ExternalMeshGeneratorBase.hpp generators/FaceBlock.hpp generators/FaceBlockABC.hpp @@ -44,7 +46,9 @@ set( mesh_headers generators/InternalWellboreGenerator.hpp generators/MeshGeneratorBase.hpp generators/ParMETISInterface.hpp + generators/PartitionDescriptor.hpp generators/PrismUtilities.hpp + generators/WellGeneratorBase.hpp mpiCommunications/CommID.hpp mpiCommunications/CommunicationTools.hpp mpiCommunications/MPI_iCommData.hpp @@ -97,6 +101,7 @@ set( mesh_sources generators/CellBlock.cpp generators/CellBlockManager.cpp generators/CellBlockUtilities.cpp + generators/LineBlock.cpp generators/ExternalMeshGeneratorBase.cpp generators/FaceBlock.cpp generators/InternalMeshGenerator.cpp @@ -104,6 +109,7 @@ set( mesh_sources generators/InternalWellboreGenerator.cpp generators/MeshGeneratorBase.cpp generators/ParMETISInterface.cpp + generators/WellGeneratorBase.cpp mpiCommunications/CommID.cpp mpiCommunications/CommunicationTools.cpp mpiCommunications/MPI_iCommData.cpp diff --git a/src/coreComponents/mesh/CellElementRegion.cpp b/src/coreComponents/mesh/CellElementRegion.cpp index e4037110d19..2da60f4c1d6 100644 --- a/src/coreComponents/mesh/CellElementRegion.cpp +++ b/src/coreComponents/mesh/CellElementRegion.cpp @@ -13,6 +13,7 @@ */ #include "CellElementRegion.hpp" +#include "CellElementSubRegion.hpp" #include "mesh/generators/CellBlockABC.hpp" namespace geos @@ -32,14 +33,14 @@ CellElementRegion::CellElementRegion( string const & name, Group * const parent CellElementRegion::~CellElementRegion() {} -void CellElementRegion::generateMesh( Group & cellBlocks ) +void CellElementRegion::generateMesh( Group const & cellBlocks ) { Group & elementSubRegions = this->getGroup( viewKeyStruct::elementSubRegions() ); for( string const & cellBlockName : this->m_cellBlockNames ) { CellElementSubRegion & subRegion = elementSubRegions.registerGroup< CellElementSubRegion >( cellBlockName ); - CellBlockABC & source = cellBlocks.getGroup< CellBlockABC >( subRegion.getName() ); + CellBlockABC const & source = cellBlocks.getGroup< CellBlockABC >( subRegion.getName() ); subRegion.copyFromCellBlock( source ); } } diff --git a/src/coreComponents/mesh/CellElementRegion.hpp b/src/coreComponents/mesh/CellElementRegion.hpp index ff5e216966a..130423b42bc 100644 --- a/src/coreComponents/mesh/CellElementRegion.hpp +++ b/src/coreComponents/mesh/CellElementRegion.hpp @@ -113,7 +113,7 @@ class CellElementRegion : public ElementRegionBase } } - virtual void generateMesh( Group & cellBlocks ) override; + virtual void generateMesh( Group const & cellBlocks ) override; ///@} diff --git a/src/coreComponents/mesh/CellElementSubRegion.cpp b/src/coreComponents/mesh/CellElementSubRegion.cpp index 0bcd3c0c18d..17fb9691664 100644 --- a/src/coreComponents/mesh/CellElementSubRegion.cpp +++ b/src/coreComponents/mesh/CellElementSubRegion.cpp @@ -66,7 +66,7 @@ void CellElementSubRegion::resizePerElementValues( localIndex const newNumNodesP } -void CellElementSubRegion::copyFromCellBlock( CellBlockABC & cellBlock ) +void CellElementSubRegion::copyFromCellBlock( CellBlockABC const & cellBlock ) { // Defines the (unique) element type of this cell element region, // and its associated number of nodes, edges, faces. @@ -90,13 +90,13 @@ void CellElementSubRegion::copyFromCellBlock( CellBlockABC & cellBlock ) this->m_localToGlobalMap = cellBlock.localToGlobalMap(); this->constructGlobalToLocalMap(); - cellBlock.forExternalProperties( [&]( WrapperBase & wrapper ) + cellBlock.forExternalProperties( [&]( WrapperBase const & wrapper ) { types::dispatch( types::StandardArrays{}, wrapper.getTypeId(), true, [&]( auto array ) { using ArrayType = decltype( array ); - Wrapper< ArrayType > & wrapperT = Wrapper< ArrayType >::cast( wrapper ); - this->registerWrapper( wrapper.getName(), std::make_unique< ArrayType >( wrapperT.reference() ) ); + auto const src = Wrapper< ArrayType >::cast( wrapper ).reference().toViewConst(); + this->registerWrapper( wrapper.getName(), std::make_unique< ArrayType >( &src ) ); } ); } ); } diff --git a/src/coreComponents/mesh/CellElementSubRegion.hpp b/src/coreComponents/mesh/CellElementSubRegion.hpp index 98672230b16..37e6c3537e2 100644 --- a/src/coreComponents/mesh/CellElementSubRegion.hpp +++ b/src/coreComponents/mesh/CellElementSubRegion.hpp @@ -82,7 +82,7 @@ class CellElementSubRegion : public ElementSubRegionBase * @brief Fill the CellElementSubRegion by copying those of the source CellBlock * @param cellBlock the CellBlock which properties (connectivity info) will be copied. */ - void copyFromCellBlock( CellBlockABC & cellBlock ); + void copyFromCellBlock( CellBlockABC const & cellBlock ); ///@} diff --git a/src/coreComponents/mesh/DomainPartition.cpp b/src/coreComponents/mesh/DomainPartition.cpp index 4fe5ce56fe3..23c0e51366c 100644 --- a/src/coreComponents/mesh/DomainPartition.cpp +++ b/src/coreComponents/mesh/DomainPartition.cpp @@ -79,17 +79,18 @@ void DomainPartition::setupBaseLevelMeshGlobalInfo() GEOS_MARK_FUNCTION; #if defined(GEOSX_USE_MPI) + PartitionBase & partition1 = getReference< PartitionBase >( keys::partitionManager ); + SpatialPartition & partition = dynamic_cast< SpatialPartition & >(partition1); - if( m_metisNeighborList.empty() ) + const std::set< int > metisNeighborList = partition.getMetisNeighborList(); + if( metisNeighborList.empty() ) { - PartitionBase & partition1 = getReference< PartitionBase >( keys::partitionManager ); - SpatialPartition & partition = dynamic_cast< SpatialPartition & >(partition1); //get communicator, rank, and coordinates MPI_Comm cartcomm; { int reorder = 0; - MpiWrapper::cartCreate( MPI_COMM_GEOSX, 3, partition.m_Partitions.data(), partition.m_Periodic.data(), reorder, &cartcomm ); + MpiWrapper::cartCreate( MPI_COMM_GEOSX, 3, partition.getPartitions().data(), partition.m_Periodic.data(), reorder, &cartcomm ); GEOS_ERROR_IF( cartcomm == MPI_COMM_NULL, "Fail to run MPI_Cart_create and establish communications" ); } int const rank = MpiWrapper::commRank( MPI_COMM_GEOSX ); @@ -104,7 +105,7 @@ void DomainPartition::setupBaseLevelMeshGlobalInfo() } else { - for( integer const neighborRank : m_metisNeighborList ) + for( integer const neighborRank : metisNeighborList ) { m_neighbors.emplace_back( neighborRank ); } @@ -248,7 +249,7 @@ void DomainPartition::addNeighbors( const unsigned int idim, } else { - const int dim = partition.m_Partitions( LvArray::integerConversion< localIndex >( idim )); + const int dim = partition.getPartitions()( LvArray::integerConversion< localIndex >( idim )); const bool periodic = partition.m_Periodic( LvArray::integerConversion< localIndex >( idim )); for( int i = -1; i < 2; i++ ) { diff --git a/src/coreComponents/mesh/DomainPartition.hpp b/src/coreComponents/mesh/DomainPartition.hpp index 498e0b24de3..7a41174df1c 100644 --- a/src/coreComponents/mesh/DomainPartition.hpp +++ b/src/coreComponents/mesh/DomainPartition.hpp @@ -253,21 +253,6 @@ class DomainPartition : public dataRepository::Group getMeshBodies().forSubGroupsIndex< MeshBody >( std::forward< FUNCTION >( function ) ); } - - /** - * @brief Get the metis neighbors indices. @see DomainPartition#m_metisNeighborList - * @return Container of global indices. - */ - std::set< int > & getMetisNeighborList() - { return m_metisNeighborList; } - - /** - * @brief Get the metis neighbors indices, const version. @see DomainPartition#m_metisNeighborList - * @return Container of global indices. - */ - std::set< int > const & getMetisNeighborList() const - { return m_metisNeighborList; } - /** * @brief Get the neighbor communicators. @see DomainPartition#m_neighbors. * @return Container of communicators. @@ -284,10 +269,6 @@ class DomainPartition : public dataRepository::Group private: - /** - * @brief Contains the global indices of the metis neighbors in case `metis` is used. Empty otherwise. - */ - std::set< int > m_metisNeighborList; /** * @brief Contains all the communicators from this DomainPartition to its neighbors. */ diff --git a/src/coreComponents/mesh/ElementRegionBase.hpp b/src/coreComponents/mesh/ElementRegionBase.hpp index 825044d7fac..049173a74e6 100644 --- a/src/coreComponents/mesh/ElementRegionBase.hpp +++ b/src/coreComponents/mesh/ElementRegionBase.hpp @@ -102,7 +102,7 @@ class ElementRegionBase : public ObjectManagerBase * @brief Generate mesh. * @param blocks Cell or face blocks from where the mesh is extracted. */ - virtual void generateMesh( Group & blocks ) = 0; + virtual void generateMesh( Group const & blocks ) = 0; ///@} diff --git a/src/coreComponents/mesh/ElementRegionManager.cpp b/src/coreComponents/mesh/ElementRegionManager.cpp index 9106cae830d..23290e1b740 100644 --- a/src/coreComponents/mesh/ElementRegionManager.cpp +++ b/src/coreComponents/mesh/ElementRegionManager.cpp @@ -22,9 +22,11 @@ #include "SurfaceElementRegion.hpp" #include "FaceManager.hpp" #include "constitutive/ConstitutiveManager.hpp" -#include "mesh/MeshManager.hpp" +#include "mesh/NodeManager.hpp" +#include "mesh/MeshLevel.hpp" #include "mesh/utilities/MeshMapUtilities.hpp" #include "schema/schemaUtilities.hpp" +#include "mesh/generators/LineBlockABC.hpp" namespace geos { @@ -117,7 +119,7 @@ void ElementRegionManager::setSchemaDeviations( xmlWrapper::xmlNode schemaRoot, } } -void ElementRegionManager::generateMesh( CellBlockManagerABC & cellBlockManager ) +void ElementRegionManager::generateMesh( CellBlockManagerABC const & cellBlockManager ) { this->forElementRegions< CellElementRegion >( [&]( CellElementRegion & elemRegion ) { @@ -151,7 +153,7 @@ void ElementRegionManager::generateMesh( CellBlockManagerABC & cellBlockManager } -void ElementRegionManager::generateWells( MeshManager & meshManager, +void ElementRegionManager::generateWells( CellBlockManagerABC const & cellBlockManager, MeshLevel & meshLevel ) { NodeManager & nodeManager = meshLevel.getNodeManager(); @@ -170,18 +172,17 @@ void ElementRegionManager::generateWells( MeshManager & meshManager, { // get the global well geometry from the well generator - string const generatorName = wellRegion.getWellGeneratorName(); - InternalWellGenerator const & wellGeometry = - meshManager.getGroup< InternalWellGenerator >( generatorName ); - + LineBlockABC const & lineBlock = cellBlockManager.getLineBlock( wellRegion.getName() ); + wellRegion.setWellGeneratorName( lineBlock.getWellGeneratorName() ); + wellRegion.setWellControlsName( lineBlock.getWellControlsName() ); // generate the local data (well elements, nodes, perforations) on this well // note: each MPI rank knows the global info on the entire well (constructed earlier in InternalWellGenerator) // so we only need node and element offsets to construct the local-to-global maps in each wellElemSubRegion - wellRegion.generateWell( meshLevel, wellGeometry, nodeOffsetGlobal + wellNodeCount, elemOffsetGlobal + wellElemCount ); + wellRegion.generateWell( meshLevel, lineBlock, nodeOffsetGlobal + wellNodeCount, elemOffsetGlobal + wellElemCount ); // increment counters with global number of nodes and elements - wellElemCount += wellGeometry.getNumElements(); - wellNodeCount += wellGeometry.getNumNodes(); + wellElemCount += lineBlock.numElements(); + wellNodeCount += lineBlock.numNodes(); string const & subRegionName = wellRegion.getSubRegionName(); WellElementSubRegion & @@ -190,8 +191,8 @@ void ElementRegionManager::generateWells( MeshManager & meshManager, globalIndex const numWellElemsGlobal = MpiWrapper::sum( subRegion.size() ); - GEOS_ERROR_IF( numWellElemsGlobal != wellGeometry.getNumElements(), - "Invalid partitioning in well " << wellGeometry.getDataContext() << + GEOS_ERROR_IF( numWellElemsGlobal != lineBlock.getNumElements(), + "Invalid partitioning in well " << lineBlock.getDataContext() << ", subregion " << subRegion.getDataContext() ); } ); diff --git a/src/coreComponents/mesh/ElementRegionManager.hpp b/src/coreComponents/mesh/ElementRegionManager.hpp index 748e26008ed..d71bb59c0fc 100644 --- a/src/coreComponents/mesh/ElementRegionManager.hpp +++ b/src/coreComponents/mesh/ElementRegionManager.hpp @@ -146,14 +146,14 @@ class ElementRegionManager : public ObjectManagerBase * @brief Generate the mesh. * @param [in,out] cellBlockManager Reference to the abstract cell block manager. */ - void generateMesh( CellBlockManagerABC & cellBlockManager ); + void generateMesh( CellBlockManagerABC const & cellBlockManager ); /** * @brief Generate the wells. - * @param [in] meshManager pointer to meshManager + * @param [in] cellBlockManager pointer to cellBlockManager * @param [in] meshLevel pointer to meshLevel */ - void generateWells( MeshManager & meshManager, MeshLevel & meshLevel ); + void generateWells( CellBlockManagerABC const & cellBlockManager, MeshLevel & meshLevel ); /** * @brief Build sets from the node sets diff --git a/src/coreComponents/mesh/MeshBody.hpp b/src/coreComponents/mesh/MeshBody.hpp index 231c0dab745..c8bffd276eb 100644 --- a/src/coreComponents/mesh/MeshBody.hpp +++ b/src/coreComponents/mesh/MeshBody.hpp @@ -20,7 +20,7 @@ #define GEOS_MESH_MESHBODY_HPP_ #include "MeshLevel.hpp" - +#include "dataRepository/KeyNames.hpp" namespace geos { @@ -166,6 +166,23 @@ class MeshBody : public dataRepository::Group return m_globalLengthScale; } + /** + * @brief Get the Abstract representation of the CellBlockManager attached to the MeshBody. + * @return The CellBlockManager. + */ + CellBlockManagerABC const & getCellBlockManager() const + { + return this->getGroup< CellBlockManagerABC >( dataRepository::keys::cellManager ); + } + + /** + * @brief De register the CellBlockManager from this meshBody + */ + void deregisterCellBlockManager() + { + this->deregisterGroup( dataRepository::keys::cellManager ); + } + /** * @brief Data repository keys */ diff --git a/src/coreComponents/mesh/MeshManager.cpp b/src/coreComponents/mesh/MeshManager.cpp index 02b11074d52..923fd9b3783 100644 --- a/src/coreComponents/mesh/MeshManager.cpp +++ b/src/coreComponents/mesh/MeshManager.cpp @@ -14,10 +14,13 @@ #include "MeshManager.hpp" +#include "MeshBody.hpp" +#include "MeshLevel.hpp" -#include "mesh/mpiCommunications/CommunicationTools.hpp" #include "mesh/mpiCommunications/SpatialPartition.hpp" +#include "generators/CellBlockManagerABC.hpp" #include "generators/MeshGeneratorBase.hpp" +#include "mesh/mpiCommunications/CommunicationTools.hpp" #include "common/TimingMacros.hpp" #include @@ -59,7 +62,16 @@ void MeshManager::generateMeshes( DomainPartition & domain ) { forSubGroups< MeshGeneratorBase >( [&]( MeshGeneratorBase & meshGen ) { - meshGen.generateMesh( domain ); + MeshBody & meshBody = domain.getMeshBodies().registerGroup< MeshBody >( meshGen.getName() ); + meshBody.createMeshLevel( 0 ); + SpatialPartition & partition = dynamic_cast< SpatialPartition & >(domain.getReference< PartitionBase >( keys::partitionManager ) ); + + meshGen.generateMesh( meshBody, partition.getPartitions() ); + CellBlockManagerABC const & cellBlockManager = meshBody.getCellBlockManager(); + + meshBody.setGlobalLengthScale( cellBlockManager.getGlobalLength() ); + + partition = meshGen.getSpatialPartition(); } ); } @@ -68,11 +80,6 @@ void MeshManager::generateMeshLevels( DomainPartition & domain ) { this->forSubGroups< MeshGeneratorBase >( [&]( MeshGeneratorBase & meshGen ) { - if( dynamicCast< InternalWellGenerator * >( &meshGen ) ) - { - return; - } - string const & meshName = meshGen.getName(); domain.getMeshBodies().registerGroup< MeshBody >( meshName ).createMeshLevel( MeshBody::groupStructKeys::baseDiscretizationString() ); } ); @@ -141,7 +148,7 @@ void MeshManager::importFields( DomainPartition & domain ) WrapperBase & wrapper = subRegion.getWrapperBase( geosxFieldName ); if( generator.getLogLevel() >= 1 ) { - GEOS_LOG_RANK_0( "Importing field " << meshFieldName << " -> " << geosxFieldName << + GEOS_LOG_RANK_0( "Importing field " << meshFieldName << " into " << geosxFieldName << " on " << region.getName() << "/" << subRegion.getName() ); } diff --git a/src/coreComponents/mesh/PerforationData.cpp b/src/coreComponents/mesh/PerforationData.cpp index 1f82bb68c87..b839771f8e4 100644 --- a/src/coreComponents/mesh/PerforationData.cpp +++ b/src/coreComponents/mesh/PerforationData.cpp @@ -161,8 +161,8 @@ void PerforationData::computeWellTransmissibility( MeshLevel const & mesh, // get the index of the well element, and of the neighboring well nodes localIndex const wellElemIndex = m_wellElementIndex[iperf]; - localIndex const topNode = elemToNodeMap[wellElemIndex][InternalWellGenerator::NodeLocation::TOP]; - localIndex const bottomNode = elemToNodeMap[wellElemIndex][InternalWellGenerator::NodeLocation::BOTTOM]; + localIndex const topNode = elemToNodeMap[wellElemIndex][LineBlockABC::NodeLocation::TOP]; + localIndex const bottomNode = elemToNodeMap[wellElemIndex][LineBlockABC::NodeLocation::BOTTOM]; // using the direction of the segment, compute the perforation "direction" // that will be used to construct the Peaceman index real64 topToBottomVec[3] = { X[bottomNode][0], @@ -243,11 +243,11 @@ void PerforationData::getReservoirElementDimensions( MeshLevel const & mesh, dz /= dx * dy; } -void PerforationData::connectToWellElements( InternalWellGenerator const & wellGeometry, +void PerforationData::connectToWellElements( LineBlockABC const & lineBlock, unordered_map< globalIndex, localIndex > const & globalToLocalWellElemMap, globalIndex elemOffsetGlobal ) { - arrayView1d< globalIndex const > const & perfElemIndexGlobal = wellGeometry.getPerfElemIndex(); + arrayView1d< globalIndex const > const & perfElemIndexGlobal = lineBlock.getPerfElemIndex(); for( localIndex iperfLocal = 0; iperfLocal < size(); ++iperfLocal ) { diff --git a/src/coreComponents/mesh/PerforationData.hpp b/src/coreComponents/mesh/PerforationData.hpp index deb307330f9..1d819cc9f6e 100644 --- a/src/coreComponents/mesh/PerforationData.hpp +++ b/src/coreComponents/mesh/PerforationData.hpp @@ -22,7 +22,7 @@ #include "dataRepository/Group.hpp" #include "mesh/ObjectManagerBase.hpp" #include "mesh/ToElementRelation.hpp" -#include "mesh/generators/InternalWellGenerator.hpp" +#include "mesh/generators/LineBlockABC.hpp" namespace geos { @@ -110,7 +110,7 @@ class PerforationData : public ObjectManagerBase /** * @brief Set the global number of perforations used for well initialization. - * @param[in] nPerfs global number of perforations (obtained from InternalWellGenerator) + * @param[in] nPerfs global number of perforations (obtained from LineBlockABC) */ void setNumPerforationsGlobal( globalIndex nPerfs ) { m_numPerforationsGlobal = nPerfs; } @@ -203,11 +203,11 @@ class PerforationData : public ObjectManagerBase /** * @brief Connect each perforation to a local wellbore element. - * @param[in] wellGeometry InternalWellGenerator containing the global well topology + * @param[in] lineBlock LineBlockABC containing the global well topology * @param[in] globalToLocalWellElementMap global-to-local map of wellbore elements * @param[in] elemOffsetGlobal the offset of the first global well element ( = offset of last global mesh elem + 1 ) */ - void connectToWellElements( InternalWellGenerator const & wellGeometry, + void connectToWellElements( LineBlockABC const & lineBlock, unordered_map< globalIndex, localIndex > const & globalToLocalWellElementMap, globalIndex elemOffsetGlobal ); diff --git a/src/coreComponents/mesh/SurfaceElementRegion.cpp b/src/coreComponents/mesh/SurfaceElementRegion.cpp index 0e6340a35f8..f62f6d9885b 100644 --- a/src/coreComponents/mesh/SurfaceElementRegion.cpp +++ b/src/coreComponents/mesh/SurfaceElementRegion.cpp @@ -48,7 +48,7 @@ SurfaceElementRegion::~SurfaceElementRegion() {} -void SurfaceElementRegion::generateMesh( Group & faceBlocks ) +void SurfaceElementRegion::generateMesh( Group const & faceBlocks ) { Group & elementSubRegions = this->getGroup( viewKeyStruct::elementSubRegions() ); diff --git a/src/coreComponents/mesh/SurfaceElementRegion.hpp b/src/coreComponents/mesh/SurfaceElementRegion.hpp index 46338db6f97..2b9a1a7126c 100644 --- a/src/coreComponents/mesh/SurfaceElementRegion.hpp +++ b/src/coreComponents/mesh/SurfaceElementRegion.hpp @@ -98,7 +98,7 @@ class SurfaceElementRegion : public ElementRegionBase */ ///@{ - virtual void generateMesh( Group & faceBlocks ) override; + virtual void generateMesh( Group const & faceBlocks ) override; /** * @brief This function generates and adds entries to the face/fracture mesh. diff --git a/src/coreComponents/mesh/WellElementRegion.cpp b/src/coreComponents/mesh/WellElementRegion.cpp index 82a5a54adf2..5f28d28631f 100644 --- a/src/coreComponents/mesh/WellElementRegion.cpp +++ b/src/coreComponents/mesh/WellElementRegion.cpp @@ -41,9 +41,8 @@ WellElementRegion::WellElementRegion( string const & name, Group * const parent WellElementRegion::~WellElementRegion() {} - void WellElementRegion::generateWell( MeshLevel & mesh, - InternalWellGenerator const & wellGeometry, + LineBlockABC const & lineBlock, globalIndex nodeOffsetGlobal, globalIndex elemOffsetGlobal ) { @@ -55,17 +54,17 @@ void WellElementRegion::generateWell( MeshLevel & mesh, subRegion.setWellControlsName( m_wellControlsName ); PerforationData * const perforationData = subRegion.getPerforationData(); - perforationData->setNumPerforationsGlobal( wellGeometry.getNumPerforations() ); + perforationData->setNumPerforationsGlobal( lineBlock.numPerforations() ); - globalIndex const numElemsGlobal = wellGeometry.getNumElements(); - globalIndex const numPerforationsGlobal = wellGeometry.getNumPerforations(); + globalIndex const numElemsGlobal = lineBlock.numElements(); + globalIndex const numPerforationsGlobal = lineBlock.numPerforations(); // 1) select the local perforations based on connectivity to the local reservoir elements - subRegion.connectPerforationsToMeshElements( mesh, wellGeometry ); + subRegion.connectPerforationsToMeshElements( mesh, lineBlock ); globalIndex const matchedPerforations = MpiWrapper::sum( perforationData->size() ); GEOS_THROW_IF( matchedPerforations != numPerforationsGlobal, - "Invalid mapping perforation-to-element in well " << wellGeometry.getName() << "." << + "Invalid mapping perforation-to-element in well " << lineBlock.getName() << "." << " This happens when GEOSX cannot match a perforation with a reservoir element." << " There are two common reasons for this error:\n" << " 1- The most common reason for this error is that a perforation is on a section of " << @@ -74,12 +73,11 @@ void WellElementRegion::generateWell( MeshLevel & mesh, " Please try to move the perforation slightly (to the interior of the perforated cell) to see if it fixes the problem.", InputError ); - // 2) classify well elements based on connectivity to local mesh partition array1d< integer > elemStatusGlobal; elemStatusGlobal.resizeDefault( numElemsGlobal, WellElementSubRegion::WellElemStatus::UNOWNED ); - arrayView1d< globalIndex const > const & perfElemIdGlobal = wellGeometry.getPerfElemIndex(); + arrayView1d< globalIndex const > const & perfElemIdGlobal = lineBlock.getPerfElemIndex(); for( localIndex iperfGlobal = 0; iperfGlobal < numPerforationsGlobal; ++iperfGlobal ) { @@ -98,7 +96,7 @@ void WellElementRegion::generateWell( MeshLevel & mesh, // 3) select the local well elements and mark boundary nodes (for ghosting) subRegion.generate( mesh, - wellGeometry, + lineBlock, elemStatusGlobal, nodeOffsetGlobal, elemOffsetGlobal ); @@ -123,7 +121,7 @@ void WellElementRegion::generateWell( MeshLevel & mesh, // 5) construct the local perforation to well element map - perforationData->connectToWellElements( wellGeometry, + perforationData->connectToWellElements( lineBlock, subRegion.globalToLocalMap(), elemOffsetGlobal ); diff --git a/src/coreComponents/mesh/WellElementRegion.hpp b/src/coreComponents/mesh/WellElementRegion.hpp index f5268a0db83..c3416030931 100644 --- a/src/coreComponents/mesh/WellElementRegion.hpp +++ b/src/coreComponents/mesh/WellElementRegion.hpp @@ -21,7 +21,7 @@ #define GEOS_MESH_WELLELEMENTREGION_HPP_ #include "mesh/ElementRegionBase.hpp" -#include "mesh/generators/InternalWellGenerator.hpp" +#include "mesh/generators/LineBlockABC.hpp" namespace geos { @@ -121,17 +121,17 @@ class WellElementRegion : public ElementRegionBase /** * @brief Not implemented, this task is performed in GenerateWell. */ - virtual void generateMesh( Group & ) override {} + void generateMesh( Group const & ) override {} /** * @brief Build the local well elements and perforations from global well geometry. * @param[in] mesh the mesh object (single level only) - * @param[in] wellGeometry the InternalWellGenerator containing the global well topology + * @param[in] lineBlock the LineBlockABC containing the global well topology * @param[in] nodeOffsetGlobal the offset of the first global well node ( = offset of last global mesh node + 1 ) * @param[in] elemOffsetGlobal the offset of the first global well element ( = offset of last global mesh elem + 1 ) */ void generateWell( MeshLevel & mesh, - InternalWellGenerator const & wellGeometry, + LineBlockABC const & lineBlock, globalIndex nodeOffsetGlobal, globalIndex elemOffsetGlobal ); diff --git a/src/coreComponents/mesh/WellElementSubRegion.cpp b/src/coreComponents/mesh/WellElementSubRegion.cpp index 03c40e0f77e..a49cd9cb869 100644 --- a/src/coreComponents/mesh/WellElementSubRegion.cpp +++ b/src/coreComponents/mesh/WellElementSubRegion.cpp @@ -63,29 +63,29 @@ namespace /** * @brief Now that the well elements are assigned, collect the nodes and tag the boundary nodes between ranks The function WellElementSubRegion::AssignUnownedElements must have been called before this function - * @param[in] wellGeometry the InternalWellGenerator containing the global well topology + * @param[in] lineBlock the LineBlockABC containing the global well topology * @param[in] localElems set of local well elems. At this point all the well elems have been assigned * @param[out] localNodes set of local well nodes (includes boundary nodes) * @param[out] boundaryNodes set of local well nodes that are at the boundary between this rank and another rank */ -void collectLocalAndBoundaryNodes( InternalWellGenerator const & wellGeometry, +void collectLocalAndBoundaryNodes( LineBlockABC const & lineBlock, SortedArray< globalIndex > const & localElems, SortedArray< globalIndex > & localNodes, SortedArray< globalIndex > & boundaryNodes ) { // get the well connectivity - arrayView1d< globalIndex const > const & nextElemIdGlobal = wellGeometry.getNextElemIndex(); - arrayView1d< arrayView1d< globalIndex const > const > const & prevElemIdsGlobal = wellGeometry.getPrevElemIndices(); - arrayView2d< globalIndex const > const & elemToNodesGlobal = wellGeometry.getElemToNodesMap(); + arrayView1d< globalIndex const > const & nextElemIdGlobal = lineBlock.getNextElemIndex(); + arrayView1d< arrayView1d< globalIndex const > const > const & prevElemIdsGlobal = lineBlock.getPrevElemIndices(); + arrayView2d< globalIndex const > const & elemToNodesGlobal = lineBlock.getElemToNodesMap(); // loop over the local elements and collect the local and boundary nodes for( globalIndex currGlobal : localElems ) { // if the element is local, its two nodes are also local - globalIndex const inodeTopGlobal = elemToNodesGlobal[currGlobal][InternalWellGenerator::NodeLocation::TOP]; - globalIndex const inodeBottomGlobal = elemToNodesGlobal[currGlobal][InternalWellGenerator::NodeLocation::BOTTOM]; + globalIndex const inodeTopGlobal = elemToNodesGlobal[currGlobal][LineBlockABC::NodeLocation::TOP]; + globalIndex const inodeBottomGlobal = elemToNodesGlobal[currGlobal][LineBlockABC::NodeLocation::BOTTOM]; localNodes.insert( inodeTopGlobal ); localNodes.insert( inodeBottomGlobal ); @@ -343,7 +343,7 @@ bool searchLocalElements( MeshLevel const & mesh, } void WellElementSubRegion::generate( MeshLevel & mesh, - InternalWellGenerator const & wellGeometry, + LineBlockABC const & lineBlock, arrayView1d< integer > & elemStatusGlobal, globalIndex nodeOffsetGlobal, globalIndex elemOffsetGlobal ) @@ -364,14 +364,13 @@ void WellElementSubRegion::generate( MeshLevel & mesh, SortedArray< globalIndex > & unownedElems = elemSetsByStatus[WellElemStatus::UNOWNED]; // here we make sure that there are no shared elements - // this is enforced in the InternalWellGenerator that currently merges two perforations + // this is enforced in the LineBlockABC that currently merges two perforations // if they belong to the same well element. This is a temporary solution. // TODO: split the well elements that contain multiple perforations, so that no element is shared GEOS_THROW_IF( sharedElems.size() > 0, - "Well " << wellGeometry.getName() << " contains shared well elements", + "Well " << lineBlock.getName() << " contains shared well elements", InputError ); - // In Steps 1 and 2 we determine the local objects on this rank (elems and nodes) // Once this is done, in Steps 3, 4, and 5, we update the nodeManager and wellElementSubRegion (size, maps) @@ -383,7 +382,7 @@ void WellElementSubRegion::generate( MeshLevel & mesh, // ie., if the center of the well element falls in the domain owned by rank k // then the well element is assigned to rank k assignUnownedElementsInReservoir( mesh, - wellGeometry, + lineBlock, unownedElems, localElems, elemStatusGlobal ); @@ -394,7 +393,7 @@ void WellElementSubRegion::generate( MeshLevel & mesh, // In this function we also check that the resulting well partitioning is valid, that is, // we make sure that if two ranks are neighbors in the well, that are also neighbors in the // reservoir mesh - checkPartitioningValidity( wellGeometry, + checkPartitioningValidity( lineBlock, localElems, elemStatusGlobal ); @@ -404,7 +403,7 @@ void WellElementSubRegion::generate( MeshLevel & mesh, // 2) collect the local nodes and tag the boundary nodes using element info // now that all the elements have been assigned, we collected the local nodes // and tag the boundary nodes (i.e., the nodes in contact with both local and remote elems) - collectLocalAndBoundaryNodes( wellGeometry, + collectLocalAndBoundaryNodes( lineBlock, localElems, localNodes, boundaryNodes ); @@ -413,7 +412,7 @@ void WellElementSubRegion::generate( MeshLevel & mesh, // this is necessary to later use the node matching procedure // to place ghosts in DomainPartition::SetupCommunications updateNodeManagerSize( mesh, - wellGeometry, + lineBlock, localNodes, boundaryNodes, nodeOffsetGlobal ); @@ -421,7 +420,7 @@ void WellElementSubRegion::generate( MeshLevel & mesh, // 4) resize the well element subregion // and construct local to global, global to local, maps, etc constructSubRegionLocalElementMaps( mesh, - wellGeometry, + lineBlock, localElems, nodeOffsetGlobal, elemOffsetGlobal ); @@ -435,13 +434,13 @@ void WellElementSubRegion::generate( MeshLevel & mesh, void WellElementSubRegion::assignUnownedElementsInReservoir( MeshLevel & mesh, - InternalWellGenerator const & wellGeometry, + LineBlockABC const & lineBlock, SortedArray< globalIndex > const & unownedElems, SortedArray< globalIndex > & localElems, arrayView1d< integer > & elemStatusGlobal ) const { // get the well and reservoir element coordinates - arrayView2d< real64 const > const & wellElemCoordsGlobal = wellGeometry.getElemCoords(); + arrayView2d< real64 const > const & wellElemCoordsGlobal = lineBlock.getElemCoords(); // assign the well elements based on location wrt the reservoir elements // if the center of the well element falls in the domain owned by rank k @@ -488,15 +487,15 @@ void WellElementSubRegion::assignUnownedElementsInReservoir( MeshLevel & mesh, } -void WellElementSubRegion::checkPartitioningValidity( InternalWellGenerator const & wellGeometry, +void WellElementSubRegion::checkPartitioningValidity( LineBlockABC const & lineBlock, SortedArray< globalIndex > & localElems, arrayView1d< integer > & elemStatusGlobal ) const { - arrayView1d< arrayView1d< globalIndex const > const > const & prevElemIdsGlobal = wellGeometry.getPrevElemIndices(); + arrayView1d< arrayView1d< globalIndex const > const > const & prevElemIdsGlobal = lineBlock.getPrevElemIndices(); // we are going to make sure that the partitioning is good, // well element per well element, starting from the bottom of the well - for( globalIndex iwelemGlobal = wellGeometry.getNumElements()-1; iwelemGlobal >= 0; --iwelemGlobal ) + for( globalIndex iwelemGlobal = lineBlock.numElements()-1; iwelemGlobal >= 0; --iwelemGlobal ) { // communicate the status of this element @@ -520,7 +519,7 @@ void WellElementSubRegion::checkPartitioningValidity( InternalWellGenerator cons globalIndex const prevGlobal = prevElemIdsGlobal[iwelemGlobal][numBranches-1]; GEOS_THROW_IF( prevGlobal <= iwelemGlobal || prevGlobal < 0, - "The structure of well " << wellGeometry.getName() << " is invalid. " << + "The structure of well " << lineBlock.getName() << " is invalid. " << " The main reason for this error is that there may be no perforation" << " in the bottom well element of the well, which is required to have" << " a well-posed problem.", @@ -584,7 +583,7 @@ void WellElementSubRegion::checkPartitioningValidity( InternalWellGenerator cons } void WellElementSubRegion::updateNodeManagerSize( MeshLevel & mesh, - InternalWellGenerator const & wellGeometry, + LineBlockABC const & lineBlock, SortedArray< globalIndex > const & localNodes, SortedArray< globalIndex > const & boundaryNodes, globalIndex nodeOffsetGlobal ) @@ -602,7 +601,7 @@ void WellElementSubRegion::updateNodeManagerSize( MeshLevel & mesh, arrayView1d< globalIndex > const & nodeLocalToGlobal = nodeManager.localToGlobalMap(); - arrayView2d< real64 const > const & nodeCoordsGlobal = wellGeometry.getNodeCoords(); + arrayView2d< real64 const > const & nodeCoordsGlobal = lineBlock.getNodeCoords(); // local *well* index localIndex iwellNodeLocal = 0; @@ -638,16 +637,16 @@ void WellElementSubRegion::updateNodeManagerSize( MeshLevel & mesh, } void WellElementSubRegion::constructSubRegionLocalElementMaps( MeshLevel & mesh, - InternalWellGenerator const & wellGeometry, + LineBlockABC const & lineBlock, SortedArray< globalIndex > const & localElems, globalIndex nodeOffsetGlobal, globalIndex elemOffsetGlobal ) { // get the well geometry - arrayView1d< globalIndex const > const & nextElemIdGlobal = wellGeometry.getNextElemIndex(); - arrayView2d< real64 const > const & elemCoordsGlobal = wellGeometry.getElemCoords(); - arrayView2d< globalIndex const > const & elemToNodesGlobal = wellGeometry.getElemToNodesMap(); - arrayView1d< real64 const > const & elemVolumeGlobal = wellGeometry.getElemVolume(); + arrayView1d< globalIndex const > const & nextElemIdGlobal = lineBlock.getNextElemIndex(); + arrayView2d< real64 const > const & elemCoordsGlobal = lineBlock.getElemCoords(); + arrayView2d< globalIndex const > const & elemToNodesGlobal = lineBlock.getElemToNodesMap(); + arrayView1d< real64 const > const & elemVolumeGlobal = lineBlock.getElemVolume(); NodeManager const & nodeManager = mesh.getNodeManager(); @@ -696,20 +695,20 @@ void WellElementSubRegion::constructSubRegionLocalElementMaps( MeshLevel & mesh, LvArray::tensorOps::copy< 3 >( m_elementCenter[ iwelemLocal ], elemCoordsGlobal[ iwelemGlobal ] ); m_elementVolume[iwelemLocal] = elemVolumeGlobal[iwelemGlobal]; - m_radius[iwelemLocal] = wellGeometry.getElementRadius(); + m_radius[iwelemLocal] = lineBlock.getElementRadius(); // update local well elem to node map (note: nodes are in nodeManager ordering) // first get the global node indices in nodeManager ordering - globalIndex const inodeTopGlobal = nodeOffsetGlobal + elemToNodesGlobal[iwelemGlobal][InternalWellGenerator::NodeLocation::TOP]; - globalIndex const inodeBottomGlobal = nodeOffsetGlobal + elemToNodesGlobal[iwelemGlobal][InternalWellGenerator::NodeLocation::BOTTOM]; + globalIndex const inodeTopGlobal = nodeOffsetGlobal + elemToNodesGlobal[iwelemGlobal][LineBlockABC::NodeLocation::TOP]; + globalIndex const inodeBottomGlobal = nodeOffsetGlobal + elemToNodesGlobal[iwelemGlobal][LineBlockABC::NodeLocation::BOTTOM]; // then get the local node indices in nodeManager ordering localIndex const inodeTopLocal = nodeManager.globalToLocalMap( inodeTopGlobal ); localIndex const inodeBottomLocal = nodeManager.globalToLocalMap( inodeBottomGlobal ); - m_toNodesRelation[iwelemLocal][InternalWellGenerator::NodeLocation::TOP] = inodeTopLocal; - m_toNodesRelation[iwelemLocal][InternalWellGenerator::NodeLocation::BOTTOM] = inodeBottomLocal; + m_toNodesRelation[iwelemLocal][LineBlockABC::NodeLocation::TOP] = inodeTopLocal; + m_toNodesRelation[iwelemLocal][LineBlockABC::NodeLocation::BOTTOM] = inodeBottomLocal; } } @@ -752,10 +751,10 @@ void WellElementSubRegion::updateNodeManagerNodeToElementMap( MeshLevel & mesh ) } void WellElementSubRegion::connectPerforationsToMeshElements( MeshLevel & mesh, - InternalWellGenerator const & wellGeometry ) + LineBlockABC const & lineBlock ) { - arrayView2d< real64 const > const perfCoordsGlobal = wellGeometry.getPerfCoords(); - arrayView1d< real64 const > const perfWellTransmissibilityGlobal = wellGeometry.getPerfTransmissibility(); + arrayView2d< real64 const > const perfCoordsGlobal = lineBlock.getPerfCoords(); + arrayView1d< real64 const > const perfWellTransmissibilityGlobal = lineBlock.getPerfTransmissibility(); m_perforationData.resize( perfCoordsGlobal.size( 0 ) ); localIndex iperfLocal = 0; diff --git a/src/coreComponents/mesh/WellElementSubRegion.hpp b/src/coreComponents/mesh/WellElementSubRegion.hpp index 154d30f4f7a..d03b7e818fb 100644 --- a/src/coreComponents/mesh/WellElementSubRegion.hpp +++ b/src/coreComponents/mesh/WellElementSubRegion.hpp @@ -18,6 +18,7 @@ #include "mesh/ElementSubRegionBase.hpp" #include "mesh/InterObjectRelation.hpp" #include "mesh/PerforationData.hpp" +#include "mesh/generators/LineBlockABC.hpp" namespace geos { @@ -219,7 +220,7 @@ class WellElementSubRegion : public ElementSubRegionBase /** * @brief Build the local well elements from global well element data. * @param[in] mesh the mesh object (single level only) - * @param[in] wellGeometry the InternalWellGenerator containing the global well topology + * @param[in] lineBlock the LineBlockABC containing the global well topology * @param[in] elemStatus list of well element status, as determined by perforations connected * to local or remote mesh partitions. Status values are defined in * enum SegmentStatus. They are used to partition well elements. @@ -227,7 +228,7 @@ class WellElementSubRegion : public ElementSubRegionBase * @param[in] elemOffsetGlobal the offset of the first global well element ( = offset of last global mesh elem + 1 ) */ void generate( MeshLevel & mesh, - InternalWellGenerator const & wellGeometry, + LineBlockABC const & lineBlock, arrayView1d< integer > & elemStatus, globalIndex nodeOffsetGlobal, globalIndex elemOffsetGlobal ); @@ -235,10 +236,10 @@ class WellElementSubRegion : public ElementSubRegionBase /** * @brief For each perforation, find the reservoir element that contains the perforation. * @param[in] mesh the mesh object (single level only) - * @param[in] wellGeometry the InternalWellGenerator containing the global well topology + * @param[in] lineBlock the LineBlockABC containing the global well topology */ void connectPerforationsToMeshElements( MeshLevel & mesh, - InternalWellGenerator const & wellGeometry ); + LineBlockABC const & lineBlock ); /** * @brief Reconstruct the (local) map nextWellElemId using nextWellElemIdGlobal after the ghost exchange. @@ -336,7 +337,7 @@ class WellElementSubRegion : public ElementSubRegionBase * @brief Assign the unowned well elements (= well elem without perforation ) that are in the reservoir (and that can therefore be matched with a reservoir element) to an MPI rank. * @param[in] meshLevel the mesh object (single level only) - * @param[in] wellGeometry the InternalWellGenerator containing the global well topology + * @param[in] lineBlock the LineBlockABC containing the global well topology * @param[in] unownedElems set of unowned well elems. * @param[out] localElems set of local well elems. It contains the perforated well elements connected to local mesh elements before the call, and is filled @@ -345,28 +346,28 @@ class WellElementSubRegion : public ElementSubRegionBase * enum SegmentStatus. They are used to partition well elements. */ void assignUnownedElementsInReservoir( MeshLevel & mesh, - InternalWellGenerator const & wellGeometry, + LineBlockABC const & lineBlock, SortedArray< globalIndex > const & unownedElems, SortedArray< globalIndex > & localElems, arrayView1d< integer > & elemStatusGlobal ) const; /** * @brief Check that all the well elements have been assigned to a single rank. - * @param[in] wellGeometry the InternalWellGenerator containing the global well topology + * @param[in] lineBlock the LineBlockABC containing the global well topology * @param[out] localElems set of local well elems. * @param[out] wellElemStatus list of current well element status. Status values are defined in * enum SegmentStatus. They are used to partition well elements. * * This function also checks that if two ranks are neighbors in the well, they are also neighbors in the mesh. */ - void checkPartitioningValidity( InternalWellGenerator const & wellGeometry, + void checkPartitioningValidity( LineBlockABC const & lineBlock, SortedArray< globalIndex > & localElems, arrayView1d< integer > & elemStatusGlobal ) const; /** * @brief Add the well nodes to the nodeManager (properly resized). * @param[inout] meshLevel the mesh object (single level only) - * @param[in] wellGeometry the InternalWellGenerator containing the global well topology + * @param[in] lineBlock the LineBlockABC containing the global well topology * @param[in] localNodes set of local well nodes (includes boundary nodes). At this point all the nodes have been * collected * @param[in] boundaryNodes set of local well nodes that are at the boundary between this rank and another rank @@ -375,7 +376,7 @@ class WellElementSubRegion : public ElementSubRegionBase * The function WellElementSubRegion::CollectLocalAndBoundaryNodes must have been called before this function. */ void updateNodeManagerSize( MeshLevel & mesh, - InternalWellGenerator const & wellGeometry, + LineBlockABC const & lineBlock, SortedArray< globalIndex > const & localNodes, SortedArray< globalIndex > const & boundaryNodes, globalIndex nodeOffsetGlobal ); @@ -384,7 +385,7 @@ class WellElementSubRegion : public ElementSubRegionBase * @brief Construct the subregion's local to global maps, as well as other local maps (toNodes, nextWellElemId, * volume, etc). * @param[inout] meshLevel the mesh object (single level only) - * @param[in] wellGeometry the InternalWellGenerator containing the global well topology + * @param[in] lineBlock the LineBlockABC containing the global well topology * @param[in] localElems set of local well elems. At this point all the well elems have been assigned * @param[in] nodeOffsetGlobal the offset of the first global well node ( = offset of last global mesh node + 1 ) * @param[in] elemOffsetGlobal the offset of the first global well element ( = offset of last global mesh elem + 1 ) @@ -392,7 +393,7 @@ class WellElementSubRegion : public ElementSubRegionBase * The function WellElementSubRegion::UpdateNodeManagerSize must have been called before this function */ void constructSubRegionLocalElementMaps( MeshLevel & mesh, - InternalWellGenerator const & wellGeometry, + LineBlockABC const & lineBlock, SortedArray< globalIndex > const & localElems, globalIndex nodeOffsetGlobal, globalIndex elemOffsetGlobal ); diff --git a/src/coreComponents/mesh/generators/CellBlock.hpp b/src/coreComponents/mesh/generators/CellBlock.hpp index e58e05c90a3..dbb686c0f44 100644 --- a/src/coreComponents/mesh/generators/CellBlock.hpp +++ b/src/coreComponents/mesh/generators/CellBlock.hpp @@ -249,9 +249,9 @@ class CellBlock : public CellBlockABC /// Type of element in this block. ElementType m_elementType; - std::list< dataRepository::WrapperBase * > getExternalProperties() override + std::list< dataRepository::WrapperBase const * > getExternalProperties() const override { - std::list< dataRepository::WrapperBase * > result; + std::list< dataRepository::WrapperBase const * > result; for( string const & externalPropertyName : m_externalPropertyNames ) { result.push_back( &this->getWrapperBase( externalPropertyName ) ); diff --git a/src/coreComponents/mesh/generators/CellBlockABC.hpp b/src/coreComponents/mesh/generators/CellBlockABC.hpp index 11e7c542df7..fadde416894 100644 --- a/src/coreComponents/mesh/generators/CellBlockABC.hpp +++ b/src/coreComponents/mesh/generators/CellBlockABC.hpp @@ -117,7 +117,7 @@ class CellBlockABC : public dataRepository::Group * @see getExternalProperties() */ template< typename LAMBDA > - void forExternalProperties( LAMBDA && lambda ) + void forExternalProperties( LAMBDA && lambda ) const { for( auto * wrapperBase: this->getExternalProperties() ) { @@ -136,7 +136,7 @@ class CellBlockABC : public dataRepository::Group * @note There is some `constness` concern for this member function. * @see forExternalProperties(LAMBDA && lambda) */ - virtual std::list< dataRepository::WrapperBase * > getExternalProperties() = 0; + virtual std::list< dataRepository::WrapperBase const * > getExternalProperties() const = 0; }; } diff --git a/src/coreComponents/mesh/generators/CellBlockManager.cpp b/src/coreComponents/mesh/generators/CellBlockManager.cpp index 8da9e9b88ea..5e1350b5a8b 100644 --- a/src/coreComponents/mesh/generators/CellBlockManager.cpp +++ b/src/coreComponents/mesh/generators/CellBlockManager.cpp @@ -15,6 +15,7 @@ #include "CellBlockManager.hpp" #include "mesh/generators/CellBlockUtilities.hpp" +#include "mesh/generators/LineBlock.hpp" #include "mesh/utilities/MeshMapUtilities.hpp" #include @@ -29,6 +30,7 @@ CellBlockManager::CellBlockManager( string const & name, Group * const parent ): { this->registerGroup< Group >( viewKeyStruct::cellBlocks() ); this->registerGroup< Group >( viewKeyStruct::faceBlocks() ); + this->registerGroup< Group >( viewKeyStruct::lineBlocks() ); } void CellBlockManager::resize( integer_array const & numElements, @@ -677,11 +679,26 @@ Group & CellBlockManager::getCellBlocks() return this->getGroup( viewKeyStruct::cellBlocks() ); } +Group const & CellBlockManager::getFaceBlocks() const +{ + return this->getGroup( viewKeyStruct::faceBlocks() ); +} + Group & CellBlockManager::getFaceBlocks() { return this->getGroup( viewKeyStruct::faceBlocks() ); } +Group & CellBlockManager::getLineBlocks() +{ + return this->getGroup( viewKeyStruct::lineBlocks() ); +} + +LineBlockABC const & CellBlockManager::getLineBlock( string name ) const +{ + return this->getGroup( viewKeyStruct::lineBlocks() ).getGroup< LineBlockABC >( name ); +} + localIndex CellBlockManager::numNodes() const { return m_numNodes; @@ -739,6 +756,11 @@ FaceBlock & CellBlockManager::registerFaceBlock( string const & name ) return this->getFaceBlocks().registerGroup< FaceBlock >( name ); } +LineBlock & CellBlockManager::registerLineBlock( string const & name ) +{ + return this->getLineBlocks().registerGroup< LineBlock >( name ); +} + array2d< real64, nodes::REFERENCE_POSITION_PERM > CellBlockManager::getNodePositions() const { return m_nodesPositions; diff --git a/src/coreComponents/mesh/generators/CellBlockManager.hpp b/src/coreComponents/mesh/generators/CellBlockManager.hpp index d1eaead0842..46dae25d324 100644 --- a/src/coreComponents/mesh/generators/CellBlockManager.hpp +++ b/src/coreComponents/mesh/generators/CellBlockManager.hpp @@ -19,9 +19,13 @@ #ifndef GEOS_MESH_CELLBLOCKMANAGER_H_ #define GEOS_MESH_CELLBLOCKMANAGER_H_ -#include "mesh/generators/CellBlockManagerABC.hpp" #include "mesh/generators/CellBlock.hpp" #include "mesh/generators/FaceBlock.hpp" +#include "mesh/generators/InternalWellGenerator.hpp" +#include "mesh/generators/LineBlock.hpp" +#include "mesh/generators/LineBlockABC.hpp" +#include "mesh/generators/CellBlockManagerABC.hpp" +#include "mesh/generators/PartitionDescriptor.hpp" namespace geos { @@ -152,8 +156,12 @@ class CellBlockManager : public CellBlockManagerABC Group & getCellBlocks() override; + Group const & getFaceBlocks() const override; + Group & getFaceBlocks() override; + LineBlockABC const & getLineBlock( string name ) const override; + /** * @brief Registers and returns a cell block of name @p name. * @param name The name of the created cell block. @@ -168,6 +176,12 @@ class CellBlockManager : public CellBlockManagerABC */ FaceBlock & registerFaceBlock( string const & name ); + /** + * @brief Registers and returns a line block of name @p name. + * @param name The name of the created line block. + * @return A reference to the new line block. The CellBlockManager owns this new instance. + */ + LineBlock & registerLineBlock( string const & name ); /** * @brief Launch kernel function over all the sub-regions * @tparam LAMBDA type of the user-provided function @@ -179,6 +193,14 @@ class CellBlockManager : public CellBlockManagerABC this->getGroup( viewKeyStruct::cellBlocks() ).forSubGroups< CellBlock >( lambda ); } + real64 getGlobalLength() const override { return m_globalLength; } + + /** + * @brief Setter for the global length + * @param globalLength the global length + */ + void setGlobalLength( real64 globalLength ) { m_globalLength = globalLength; } + private: struct viewKeyStruct @@ -190,8 +212,20 @@ class CellBlockManager : public CellBlockManagerABC /// Face blocks key static constexpr char const * faceBlocks() { return "faceBlocks"; } + + /// Line blocks key + static constexpr char const * lineBlocks() + { return "lineBlocks"; } }; + /** + * @brief Returns a group containing the well blocks as @p LineBlockABC instances. + * @return Mutable reference to the well blocks group. + * + * @note It should probably be better not to expose a non-const accessor here. + */ + Group & getLineBlocks(); + /** * @brief Get cell block at index @p blockIndex. * @param[in] blockIndex The cell block index. @@ -236,6 +270,8 @@ class CellBlockManager : public CellBlockManagerABC std::map< string, SortedArray< localIndex > > m_nodeSets; + real64 m_globalLength; + localIndex m_numNodes; localIndex m_numFaces; localIndex m_numEdges; diff --git a/src/coreComponents/mesh/generators/CellBlockManagerABC.hpp b/src/coreComponents/mesh/generators/CellBlockManagerABC.hpp index 58c768d165d..ebc91ee10fc 100644 --- a/src/coreComponents/mesh/generators/CellBlockManagerABC.hpp +++ b/src/coreComponents/mesh/generators/CellBlockManagerABC.hpp @@ -17,6 +17,8 @@ #include "CellBlockUtilities.hpp" #include "dataRepository/Group.hpp" +#include "PartitionDescriptor.hpp" +#include "LineBlockABC.hpp" #include @@ -99,12 +101,26 @@ class CellBlockManagerABC : public dataRepository::Group */ virtual Group & getFaceBlocks() = 0; + /** + * @brief Returns LineBlockABC corresponding to the given identifier + * @param name the name of the required LineBlockABC + * @return The LineBlockABC associated with the given name + * + */ + virtual LineBlockABC const & getLineBlock( string name ) const = 0; + /** * @brief Returns a group containing the cell blocks as CellBlockABC instances * @return Const reference to the Group instance. */ virtual const Group & getCellBlocks() const = 0; + /** + * @brief Returns a group containing the face blocks as FaceBlockABC instances + * @return Const reference to the Group instance. + */ + virtual const Group & getFaceBlocks() const = 0; + /** * @brief Total number of nodes across all the cell blocks. * @return The total number of nodes. @@ -192,6 +208,12 @@ class CellBlockManagerABC : public dataRepository::Group */ virtual std::map< string, SortedArray< localIndex > > const & getNodeSets() const = 0; + /** + * @brief Getter for the global length + * @return the global length of the mesh + */ + virtual real64 getGlobalLength() const = 0; + /** * @brief Generates in place the high-order maps for this cell block manager. * @param[in] order The order of the discretization. diff --git a/src/coreComponents/mesh/generators/InternalMeshGenerator.cpp b/src/coreComponents/mesh/generators/InternalMeshGenerator.cpp index 8a518c207ed..1850338ffe3 100644 --- a/src/coreComponents/mesh/generators/InternalMeshGenerator.cpp +++ b/src/coreComponents/mesh/generators/InternalMeshGenerator.cpp @@ -17,14 +17,6 @@ */ #include "InternalMeshGenerator.hpp" - -#include "common/DataTypes.hpp" -#include "common/TimingMacros.hpp" -#include "mesh/DomainPartition.hpp" -#include "mesh/MeshBody.hpp" -#include "mesh/mpiCommunications/PartitionBase.hpp" -#include "mesh/mpiCommunications/SpatialPartition.hpp" -#include "mesh/MeshBody.hpp" #include "CellBlockManager.hpp" #include "common/DataTypes.hpp" @@ -539,22 +531,30 @@ static void getElemToNodesRelationInBox( ElementType const elementType, } } -/** - * @param partition - * @param domain - */ -void InternalMeshGenerator::generateMesh( DomainPartition & domain ) +void InternalMeshGenerator::fillCellBlockManager( CellBlockManager & cellBlockManager, array1d< int > const & partition ) { GEOS_MARK_FUNCTION; + m_spatialPartition.setPartitions( partition[0], partition[1], partition[2] ); + // Partition based on even spacing to get load balance + // Partition geometrical boundaries will be corrected in the end. + { + m_min[0] = m_vertices[0].front(); + m_min[1] = m_vertices[1].front(); + m_min[2] = m_vertices[2].front(); - MeshBody & meshBody = domain.getMeshBodies().registerGroup< MeshBody >( this->getName() ); + m_max[0] = m_vertices[0].back(); + m_max[1] = m_vertices[1].back(); + m_max[2] = m_vertices[2].back(); - // Make sure that the node manager fields are initialized + m_spatialPartition.setSizes( m_min, m_max ); + } - CellBlockManager & cellBlockManager = meshBody.registerGroup< CellBlockManager >( keys::cellManager ); + // Make sure that the node manager fields are initialized auto & nodeSets = cellBlockManager.getNodeSets(); - SpatialPartition & partition = dynamic_cast< SpatialPartition & >(domain.getReference< PartitionBase >( keys::partitionManager ) ); + real64 size[3] = LVARRAY_TENSOROPS_INIT_LOCAL_3( m_max ); + LvArray::tensorOps::subtract< 3 >( size, m_min ); + cellBlockManager.setGlobalLength( LvArray::tensorOps::l2Norm< 3 >( size ) ); // bool isRadialWithOneThetaPartition = false; @@ -574,24 +574,6 @@ void InternalMeshGenerator::generateMesh( DomainPartition & domain ) SortedArray< localIndex > & zposNodes = nodeSets["zpos"]; SortedArray< localIndex > & allNodes = nodeSets["all"]; - // Partition based on even spacing to get load balance - // Partition geometrical boundaries will be corrected in the end. - { - m_min[0] = m_vertices[0].front(); - m_min[1] = m_vertices[1].front(); - m_min[2] = m_vertices[2].front(); - - m_max[0] = m_vertices[0].back(); - m_max[1] = m_vertices[1].back(); - m_max[2] = m_vertices[2].back(); - - partition.setSizes( m_min, m_max ); - - real64 size[3] = LVARRAY_TENSOROPS_INIT_LOCAL_3( m_max ); - LvArray::tensorOps::subtract< 3 >( size, m_min ); - meshBody.setGlobalLengthScale( LvArray::tensorOps::l2Norm< 3 >( size ) ); - } - // Find elemCenters for even uniform element sizes array1d< array1d< real64 > > elemCenterCoords( 3 ); for( int i = 0; i < 3; ++i ) @@ -626,7 +608,7 @@ void InternalMeshGenerator::generateMesh( DomainPartition & domain ) // lastElemIndexInPartition[i] = -2; for( int k = 0; k < m_numElemsTotal[i]; ++k ) { - if( partition.isCoordInPartition( elemCenterCoords[i][k], i ) ) + if( m_spatialPartition.isCoordInPartition( elemCenterCoords[i][k], i ) ) { firstElemIndexInPartition[i] = k; break; @@ -637,7 +619,7 @@ void InternalMeshGenerator::generateMesh( DomainPartition & domain ) { for( int k = firstElemIndexInPartition[i]; k < m_numElemsTotal[i]; ++k ) { - if( partition.isCoordInPartition( elemCenterCoords[i][k], i ) ) + if( m_spatialPartition.isCoordInPartition( elemCenterCoords[i][k], i ) ) { lastElemIndexInPartition[i] = k; } @@ -731,7 +713,7 @@ void InternalMeshGenerator::generateMesh( DomainPartition & domain ) { numNodesInDir[i] = lastElemIndexInPartition[i] - firstElemIndexInPartition[i] + 2; } - reduceNumNodesForPeriodicBoundary( partition, numNodesInDir ); + reduceNumNodesForPeriodicBoundary( m_spatialPartition, numNodesInDir ); numNodes = numNodesInDir[0] * numNodesInDir[1] * numNodesInDir[2]; cellBlockManager.setNumNodes( numNodes ); @@ -758,7 +740,7 @@ void InternalMeshGenerator::generateMesh( DomainPartition & domain ) getNodePosition( globalIJK, m_trianglePattern, X[localNodeIndex] ); // Alter global node map for radial mesh - setNodeGlobalIndicesOnPeriodicBoundary( partition, globalIJK ); + setNodeGlobalIndicesOnPeriodicBoundary( m_spatialPartition, globalIJK ); nodeLocalToGlobal[localNodeIndex] = nodeGlobalIndex( globalIJK ); @@ -822,7 +804,7 @@ void InternalMeshGenerator::generateMesh( DomainPartition & domain ) // Reset the number of nodes in each dimension in case of periodic BCs so the element firstNodeIndex // calculation is correct? Not actually needed in parallel since we still have ghost nodes in that case and // the count has not been altered due to periodicity. - if( std::any_of( partition.m_Periodic.begin(), partition.m_Periodic.end(), []( int & dimPeriodic ) { return dimPeriodic == 1; } ) ) + if( std::any_of( m_spatialPartition.m_Periodic.begin(), m_spatialPartition.m_Periodic.end(), []( int & dimPeriodic ) { return dimPeriodic == 1; } ) ) { for( int i = 0; i < m_dim; ++i ) { diff --git a/src/coreComponents/mesh/generators/InternalMeshGenerator.hpp b/src/coreComponents/mesh/generators/InternalMeshGenerator.hpp index c08c8c72b17..ccad475b397 100644 --- a/src/coreComponents/mesh/generators/InternalMeshGenerator.hpp +++ b/src/coreComponents/mesh/generators/InternalMeshGenerator.hpp @@ -21,11 +21,12 @@ #include "codingUtilities/EnumStrings.hpp" #include "mesh/generators/MeshGeneratorBase.hpp" +#include "mesh/generators/CellBlockManager.hpp" +#include "mesh/mpiCommunications/SpatialPartition.hpp" namespace geos { -class SpatialPartition; /** * @class InternalMeshGenerator @@ -50,7 +51,6 @@ class InternalMeshGenerator : public MeshGeneratorBase */ static string catalogName() { return "InternalMesh"; } - virtual void generateMesh( DomainPartition & domain ) override; void importFieldOnArray( Block block, string const & blockName, @@ -263,6 +263,8 @@ class InternalMeshGenerator : public MeshGeneratorBase + virtual void fillCellBlockManager( CellBlockManager & cellBlockManager, array1d< int > const & partition ) override; + /** * @brief Convert ndim node spatialized index to node global index. * @param[in] node ndim spatialized array index diff --git a/src/coreComponents/mesh/generators/InternalWellGenerator.cpp b/src/coreComponents/mesh/generators/InternalWellGenerator.cpp index b3e74dcc9ad..b2b812960e2 100644 --- a/src/coreComponents/mesh/generators/InternalWellGenerator.cpp +++ b/src/coreComponents/mesh/generators/InternalWellGenerator.cpp @@ -20,30 +20,25 @@ #include "InternalWellGenerator.hpp" #include "mesh/mpiCommunications/CommunicationTools.hpp" -#include "mesh/DomainPartition.hpp" -#include "mesh/MeshBody.hpp" -#include "mesh/WellElementRegion.hpp" -#include "mesh/WellElementSubRegion.hpp" -#include "mesh/PerforationData.hpp" #include "mesh/Perforation.hpp" +#include "mesh/generators/LineBlockABC.hpp" +#include "LvArray/src/genericTensorOps.hpp" namespace geos { using namespace dataRepository; InternalWellGenerator::InternalWellGenerator( string const & name, Group * const parent ): - MeshGeneratorBase( name, parent ), + WellGeneratorBase( name, parent ), m_numElemsPerSegment( 0 ), m_minSegmentLength( 1e-2 ), m_minElemLength( 1e-3 ), m_radius( 0 ), m_wellRegionName( "" ), m_wellControlsName( "" ), - m_meshBodyName( "" ), m_numElems( 0 ), m_numNodesPerElem( 2 ), m_numNodes( 0 ), - m_numPerforations( 0 ), m_nDims( 3 ), m_polylineHeadNodeId( -1 ) { @@ -90,16 +85,6 @@ InternalWellGenerator::InternalWellGenerator( string const & name, Group * const setInputFlag( InputFlags::REQUIRED ). setSizedFromParent( 0 ). setDescription( "Name of the set of constraints associated with this well" ); - - registerWrapper( viewKeyStruct::meshNameString(), &m_meshBodyName ). - setInputFlag( InputFlags::REQUIRED ). - setSizedFromParent( 0 ). - setDescription( "Name of the reservoir mesh associated with this well" ); -} - -InternalWellGenerator::~InternalWellGenerator() -{ - // TODO Auto-generated destructor stub } void InternalWellGenerator::postProcessInput() @@ -124,10 +109,6 @@ void InternalWellGenerator::postProcessInput() "Invalid well region name in well " << getName(), InputError ); - GEOS_THROW_IF( m_meshBodyName.empty(), - "Invalid mesh name in well " << getName(), - InputError ); - GEOS_THROW_IF( m_wellControlsName.empty(), "Invalid well constraint name in well " << getName(), InputError ); @@ -137,30 +118,7 @@ void InternalWellGenerator::postProcessInput() // TODO: check that with no branching we can go from top to bottom and touch all the elements } -Group * InternalWellGenerator::createChild( string const & childKey, string const & childName ) -{ - if( childKey == viewKeyStruct::perforationString() ) - { - ++m_numPerforations; - - // keep track of the perforations that have been added - m_perforationList.emplace_back( childName ); - - return ®isterGroup< Perforation >( childName ); - } - else - { - GEOS_THROW( "Unrecognized node: " << childKey, InputError ); - } - return nullptr; -} - -void InternalWellGenerator::expandObjectCatalogs() -{ - createChild( viewKeyStruct::perforationString(), viewKeyStruct::perforationString() ); -} - -void InternalWellGenerator::generateMesh( DomainPartition & domain ) +void InternalWellGenerator::generateWellGeometry( ) { // count the number of well elements to create m_numElems = m_numElemsPerSegment * m_segmentToPolyNodeMap.size( 0 ); @@ -202,16 +160,6 @@ void InternalWellGenerator::generateMesh( DomainPartition & domain ) debugWellGeometry(); } - // get the element (sub) region to populate and save the well generator and constraints names - MeshLevel & meshLevel = domain.getMeshBody( 0 ).getBaseDiscretization(); - - ElementRegionManager & elemManager = meshLevel.getElemManager(); - WellElementRegion & - wellRegion = elemManager.getGroup( ElementRegionManager::groupKeyStruct::elementRegionsGroup() ). - getGroup< WellElementRegion >( this->m_wellRegionName ); - - wellRegion.setWellGeneratorName( this->getName() ); - wellRegion.setWellControlsName( m_wellControlsName ); } void InternalWellGenerator::constructPolylineNodeToSegmentMap() @@ -359,8 +307,8 @@ void InternalWellGenerator::discretizePolyline() // 2) set the node properties globalIndex const iwellNodeTop = iwelemCurrent; globalIndex const iwellNodeBottom = iwelemCurrent+1; - m_elemToNodesMap[iwelemCurrent][NodeLocation::TOP] = iwellNodeTop; - m_elemToNodesMap[iwelemCurrent][NodeLocation::BOTTOM] = iwellNodeBottom; + m_elemToNodesMap[iwelemCurrent][LineBlockABC::NodeLocation::TOP] = iwellNodeTop; + m_elemToNodesMap[iwelemCurrent][LineBlockABC::NodeLocation::BOTTOM] = iwellNodeBottom; real64 const scaleBottom = (iw + 1.0) / m_numElemsPerSegment; LvArray::tensorOps::copy< 3 >( m_nodeCoords[iwellNodeBottom], vPoly ); @@ -409,7 +357,7 @@ void InternalWellGenerator::connectPerforationsToWellElements() globalIndex iwelemBottom = m_numElems - 1; // check the validity of the perforation before starting - real64 const wellLength = m_nodeDistFromHead[m_elemToNodesMap[iwelemBottom][NodeLocation::BOTTOM]]; + real64 const wellLength = m_nodeDistFromHead[m_elemToNodesMap[iwelemBottom][LineBlockABC::NodeLocation::BOTTOM]]; GEOS_THROW_IF( m_perfDistFromHead[iperf] > wellLength, "Distance from perforation " << perf.getName() << " to head is larger than well polyline length for well " << getName() << "\n \n" @@ -426,7 +374,7 @@ void InternalWellGenerator::connectPerforationsToWellElements() globalIndex iwelemMid = static_cast< globalIndex >(floor( static_cast< real64 >(iwelemTop + iwelemBottom) / 2.0 )); real64 const headToBottomDist = - m_nodeDistFromHead[m_elemToNodesMap[iwelemMid][NodeLocation::BOTTOM]]; + m_nodeDistFromHead[m_elemToNodesMap[iwelemMid][LineBlockABC::NodeLocation::BOTTOM]]; if( headToBottomDist < m_perfDistFromHead[iperf] ) { @@ -453,8 +401,8 @@ void InternalWellGenerator::connectPerforationsToWellElements() m_perfElemId[iperf] = iwelemMatched; // compute the physical location of the perforation - globalIndex const inodeTop = m_elemToNodesMap[iwelemMatched][NodeLocation::TOP]; - globalIndex const inodeBottom = m_elemToNodesMap[iwelemMatched][NodeLocation::BOTTOM]; + globalIndex const inodeTop = m_elemToNodesMap[iwelemMatched][LineBlockABC::NodeLocation::TOP]; + globalIndex const inodeBottom = m_elemToNodesMap[iwelemMatched][LineBlockABC::NodeLocation::BOTTOM]; real64 const elemLength = m_nodeDistFromHead[inodeBottom] - m_nodeDistFromHead[inodeTop]; real64 const topToPerfDist = m_perfDistFromHead[iperf] - m_nodeDistFromHead[inodeTop]; @@ -629,5 +577,5 @@ void InternalWellGenerator::debugWellGeometry() const } -REGISTER_CATALOG_ENTRY( MeshGeneratorBase, InternalWellGenerator, string const &, Group * const ) +REGISTER_CATALOG_ENTRY( WellGeneratorBase, InternalWellGenerator, string const &, Group * const ) } diff --git a/src/coreComponents/mesh/generators/InternalWellGenerator.hpp b/src/coreComponents/mesh/generators/InternalWellGenerator.hpp index 69ef1c7a0f2..ca3622b7b60 100644 --- a/src/coreComponents/mesh/generators/InternalWellGenerator.hpp +++ b/src/coreComponents/mesh/generators/InternalWellGenerator.hpp @@ -20,7 +20,12 @@ #ifndef GEOS_MESH_GENERATORS_INTERNALWELLGENERATOR_HPP_ #define GEOS_MESH_GENERATORS_INTERNALWELLGENERATOR_HPP_ -#include "MeshGeneratorBase.hpp" +#include "WellGeneratorBase.hpp" + +#include "dataRepository/Group.hpp" +#include "codingUtilities/Utilities.hpp" +#include "common/DataTypes.hpp" + namespace geos { @@ -30,18 +35,10 @@ namespace geos * * This class processes the data of a single well from the XML and generates the well geometry */ -class InternalWellGenerator : public MeshGeneratorBase +class InternalWellGenerator : public WellGeneratorBase { public: - /** - * @brief Struct to define the top and bottom node of a segment. - */ - struct NodeLocation - { - static constexpr integer TOP = 0; /**< Top of the well */ - static constexpr integer BOTTOM = 1; /**< Bottom of the well */ - }; /** * @name Constructor / Destructor @@ -57,115 +54,96 @@ class InternalWellGenerator : public MeshGeneratorBase Group * const parent ); /** - * @brief Default destructor. + * @brief Get the catalog name. + * @return the name of this type in the catalog */ - virtual ~InternalWellGenerator() override; + static string catalogName() { return "InternalWell"; } - ///@} /** - * @name Static Factory Catalog Functions + * @brief Main function of the class that generates the well geometry */ - ///@{ + void generateWellGeometry( ) override; - /** - * @brief Get the catalog name. - * @return the name of this type in the catalog - */ - static string catalogName() { return "InternalWell"; } ///@} /** - * @name Overriding functions defined in MeshGeneratorBase and above + * @name Getters / Setters */ ///@{ + // getters for element data + /** - * @brief Creates a new sub-Group using the ObjectCatalog functionality. - * @param[in] childKey The name of the new object type's key in the - * ObjectCatalog. - * @param[in] childName The name of the new object in the collection of - * sub-Groups. - * @return A pointer to the new Group created by this function. + * @brief Get the global number of well elements. + * @return the global number of elements */ - virtual Group * createChild( string const & childKey, - string const & childName ) override; + globalIndex numElements() const override { return m_numElems; } /** - * @brief Expand any catalogs in the data structure. + * @brief Getter to the Segment to PolyNode mapping + * @return The Segment to PolyNode mapping as a 2D array */ - virtual void expandObjectCatalogs() override; + const array2d< globalIndex > & getSegmentToPolyNodeMap() const override { return m_segmentToPolyNodeMap; }; /** - * @brief Main function of the class that generates the well geometry - * @param[in] domain the domain object - */ - virtual void generateMesh( DomainPartition & domain ) override; - - void importFieldOnArray( Block block, - string const & blockName, - string const & meshFieldName, - bool isMaterialField, - dataRepository::WrapperBase & wrapper ) const override - { - GEOS_UNUSED_VAR( block ); - GEOS_UNUSED_VAR( blockName ); - GEOS_UNUSED_VAR( meshFieldName ); - GEOS_UNUSED_VAR( isMaterialField ); - GEOS_UNUSED_VAR( wrapper ); - } - - ///@} + * @brief Get the number of nodes per well element + * @return the number of nodes per well element + */ + globalIndex numNodesPerElement() const override { return m_numNodesPerElem; } /** - * @name Getters / Setters + * @brief Get the Coordinates of the polyline nodes + * @return the Coordinates of the polyline nodes */ - ///@{ + const array2d< real64 > & getPolyNodeCoord() const override { return m_polyNodeCoords; } - // getters for element data + /** + * @return The minimum segment length + */ + real64 getMinSegmentLength() const override { return m_minSegmentLength; } /** - * @brief Get the global number of well elements. - * @return the global number of elements + * @return The minimum element length */ - globalIndex getNumElements() const { return m_numElems; } + real64 getMinElemLength() const override { return m_minElemLength; } /** * @brief Get the physical location of the centers of well elements. * @return list of center locations of the well elements */ - arrayView2d< real64 const > getElemCoords() const { return m_elemCenterCoords; } + arrayView2d< real64 const > getElemCoords() const override { return m_elemCenterCoords; } /** * @brief Get the global indices mapping an element to the next. * @return list providing the global index of the next element for each element */ - arrayView1d< globalIndex const > getNextElemIndex() const { return m_nextElemId; } + arrayView1d< globalIndex const > getNextElemIndex() const override { return m_nextElemId; } /** * @brief Get the global indices mapping an element to the previous ones. * @return list providing the global indices of the previous elements for each element */ - arrayView1d< arrayView1d< globalIndex const > const > getPrevElemIndices() const { return m_prevElemId.toNestedViewConst(); } + arrayView1d< arrayView1d< globalIndex const > const > getPrevElemIndices() const override { return m_prevElemId.toNestedViewConst(); } /** * @brief Get the global indices of the well nodes nodes connected to each element. * @return list providing the global index of the well nodes for each well element */ - arrayView2d< globalIndex const > getElemToNodesMap() const { return m_elemToNodesMap; } + arrayView2d< globalIndex const > getElemToNodesMap() const override { return m_elemToNodesMap; } /** * @brief Get the volume of the well elements. * @return list of volumes of the well elements */ - arrayView1d< real64 const > getElemVolume() const { return m_elemVolume; } + arrayView1d< real64 const > getElemVolume() const override { return m_elemVolume; } /** * @brief Get the radius in the well. * @return the radius in the well */ - real64 getElementRadius() const { return m_radius; } + real64 getElementRadius() const override { return m_radius; } // getters for node data @@ -173,59 +151,45 @@ class InternalWellGenerator : public MeshGeneratorBase * @brief Get the global number of well nodes. * @return the global number of nodes */ - globalIndex getNumNodes() const { return m_numNodes; } + globalIndex numNodes() const override { return m_numNodes; } /** * @brief Get the physical location of the centers of well elements. * @return list of center locations of the well elements */ - arrayView2d< real64 const > getNodeCoords() const { return m_nodeCoords; } - - + arrayView2d< real64 const > getNodeCoords() const override { return m_nodeCoords; } // getters for perforation data - /** - * @brief Get the global number of perforations on this well. - * @return the global number of elements - */ - globalIndex getNumPerforations() const { return m_numPerforations; } - /** * @brief Get the locations of the perforations. * @return list of locations of all the perforations on the well */ - arrayView2d< real64 const > getPerfCoords() const { return m_perfCoords; } + arrayView2d< real64 const > getPerfCoords() const override { return m_perfCoords; } /** * @brief Get the well transmissibility at the perforations. * @return list of well transmissibility at all the perforations on the well */ - arrayView1d< real64 const > getPerfTransmissibility() const { return m_perfTransmissibility; } + arrayView1d< real64 const > getPerfTransmissibility() const override { return m_perfTransmissibility; } /** * @brief Get the global indices of the well elements connected to each perforation. * @return list providing the global index of the connected well element for each perforation */ - arrayView1d< globalIndex const > getPerfElemIndex() const { return m_perfElemId; } + arrayView1d< globalIndex const > getPerfElemIndex() const override { return m_perfElemId; } + + /** + * @returns The number of physical dimensions + */ + int getPhysicalDimensionsNumber() const override { return m_nDims; } ///@} - ///@cond DO_NOT_DOCUMENT - struct viewKeyStruct - { - constexpr static char const * polylineNodeCoordsString() { return "polylineNodeCoords"; } - constexpr static char const * polylineSegmentConnString() { return "polylineSegmentConn"; } - constexpr static char const * numElementsPerSegmentString() { return "numElementsPerSegment"; } - constexpr static char const * minSegmentLengthString() { return "minSegmentLength"; } - constexpr static char const * minElementLengthString() { return "minElementLength"; } - constexpr static char const * radiusString() { return "radius"; } - constexpr static char const * wellRegionNameString() { return "wellRegionName"; } - constexpr static char const * wellControlsNameString() { return "wellControlsName"; } - constexpr static char const * meshNameString() { return "meshName"; } - constexpr static char const * perforationString() { return "Perforation"; } - }; - /// @endcond + const string getWellRegionName() const override { return m_wellRegionName; } + const string getWellControlsName() const override { return m_wellControlsName; } + + protected: @@ -311,9 +275,6 @@ class InternalWellGenerator : public MeshGeneratorBase /// Name of the constraints associated with this well string m_wellControlsName; - /// Name of the mesh body associated with this well - string m_meshBodyName; - // Geometry of the well (later passed to the WellElementSubRegion) @@ -352,9 +313,6 @@ class InternalWellGenerator : public MeshGeneratorBase // perforation data - /// Global number of perforations - globalIndex m_numPerforations; - /// Absolute physical location of the perforation array2d< real64 > m_perfCoords; @@ -385,13 +343,9 @@ class InternalWellGenerator : public MeshGeneratorBase // Perforation data - /// List of perforation names - string_array m_perforationList; - /// Physical location of the perforation wrt to well head array1d< real64 > m_perfDistFromHead; }; } - #endif /* GEOS_MESH_GENERATORS_INTERNALWELLGENERATOR_HPP_ */ diff --git a/src/coreComponents/mesh/generators/InternalWellboreGenerator.cpp b/src/coreComponents/mesh/generators/InternalWellboreGenerator.cpp index df8b08be8de..33a63bcfbbf 100644 --- a/src/coreComponents/mesh/generators/InternalWellboreGenerator.cpp +++ b/src/coreComponents/mesh/generators/InternalWellboreGenerator.cpp @@ -306,11 +306,11 @@ void InternalWellboreGenerator::reduceNumNodesForPeriodicBoundary( SpatialPartit { if( m_isFullAnnulus ) { - if( partition.m_Partitions[1] == 1 ) + if( partition.getPartitions()[1] == 1 ) { numNodesInDir[1] -= 1; } - else if( partition.m_Partitions[1] > 1 ) + else if( partition.getPartitions()[1] > 1 ) { partition.m_Periodic[1] = 1; } diff --git a/src/coreComponents/mesh/generators/LineBlock.cpp b/src/coreComponents/mesh/generators/LineBlock.cpp new file mode 100644 index 00000000000..b49289325b4 --- /dev/null +++ b/src/coreComponents/mesh/generators/LineBlock.cpp @@ -0,0 +1,41 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2018-2020 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2020 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2018-2020 TotalEnergies + * Copyright (c) 2019- GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +/** + * @file DomainPartition.cpp + */ + +#include "mesh/Perforation.hpp" +#include "mesh/generators/LineBlock.hpp" +#include "mesh/generators/InternalWellGenerator.hpp" + + + +namespace geos +{ + +LineBlock::LineBlock( string const & name, Group * const parent ): + LineBlockABC( name, parent ) +{} + +void LineBlock::setPrevElemIndices( arrayView1d< arrayView1d< globalIndex const > const > prevElemIndices ) +{ + int size = prevElemIndices.size(); + m_prevElemId.resize( size ); + for( int i = 0; i < size; i++ ) + { + m_prevElemId[i] = prevElemIndices[i]; + } +} +} diff --git a/src/coreComponents/mesh/generators/LineBlock.hpp b/src/coreComponents/mesh/generators/LineBlock.hpp new file mode 100644 index 00000000000..96a889e9ab4 --- /dev/null +++ b/src/coreComponents/mesh/generators/LineBlock.hpp @@ -0,0 +1,251 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2018-2020 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2020 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2018-2020 TotalEnergies + * Copyright (c) 2020- GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +#ifndef GEOSX_WELLBLOCK_HPP +#define GEOSX_WELLBLOCK_HPP + +#include "mesh/generators/LineBlockABC.hpp" +#include "mesh/generators/InternalWellGenerator.hpp" + + +namespace geos +{ + +/** + * Implementation of the LineBlock responsible for modification/creation capabilities. + */ +class LineBlock : public LineBlockABC +{ +public: + /** + * @brief Constructor for this class. + * @param[in] name the name of this object manager + * @param[in] parent the parent Group + */ + LineBlock( string const & name, Group * const parent ); + + /** + * @name Getters / Setters + */ + ///@{ + + // getters for element data + + globalIndex numElements() const override final { return m_numElems; } + + /** + * @brief Set the global number of well elements. + * @param numElems the global number of elements + */ + void setNumElements( globalIndex numElems ) { m_numElems = numElems; } + + arrayView2d< real64 const > getElemCoords() const override final { return m_elemCenterCoords; } + + /** + * @brief Set the physical location of the centers of well elements. + * @param elemCenterCoords list of center locations of the well elements + */ + void setElemCoords( arrayView2d< real64 const > elemCenterCoords ) { m_elemCenterCoords = elemCenterCoords; } + + arrayView1d< globalIndex const > getNextElemIndex() const override final { return m_nextElemId; } + + /** + * @brief Set the global indices mapping an element to the next. + * @param nextElemId list providing the global index of the next element for each element + */ + void setNextElemIndex( arrayView1d< globalIndex const > nextElemId ) { m_nextElemId = nextElemId; } + + arrayView1d< arrayView1d< globalIndex const > const > getPrevElemIndices() const override final { return m_prevElemId.toNestedViewConst(); } + + + /** + * @brief Set the global indices mapping an element to the previous ones. + * @param prevElemIndices list providing the global indices of the previous elements for each element + */ + void setPrevElemIndices( arrayView1d< arrayView1d< globalIndex const > const > prevElemIndices ); + + arrayView2d< globalIndex const > getElemToNodesMap() const override final { return m_elemToNodesMap; } + + + /** + * @brief Set the global indices of the well nodes nodes connected to each element. + * @param elemToNodesMap list providing the global index of the well nodes for each well element + */ + void setElemToNodesMap( arrayView2d< globalIndex const > elemToNodesMap ) { m_elemToNodesMap = elemToNodesMap; } + + arrayView1d< real64 const > getElemVolume() const override final { return m_elemVolume; } + + + /** + * @brief Set the volume of the well elements. + * @param elemVolume list of volumes of the well elements + */ + void setElemVolume( arrayView1d< real64 const > elemVolume ) { m_elemVolume = elemVolume; } + + real64 getElementRadius() const override final { return m_radius; } + + + /** + * @brief Set the radius in the well. + * @param radius the radius in the well + */ + void setElementRadius( real64 radius ) { m_radius = radius; } + + // getters for node data + + globalIndex numNodes() const override final { return m_numNodes; } + + + /** + * @brief Set the global number of well nodes. + * @param numNodes the global number of nodes + */ + void setNumNodes( globalIndex numNodes ) { m_numNodes = numNodes; } + + arrayView2d< real64 const > getNodeCoords() const override final { return m_nodeCoords; } + + /** + * @brief Set the physical location of the centers of well elements. + * @param nodeCoords list of center locations of the well elements + */ + void setNodeCoords( arrayView2d< real64 const > nodeCoords ) { m_nodeCoords = nodeCoords; } + + + + // getters for perforation data + + globalIndex numPerforations() const override final { return m_numPerforations; } + + + /** + * @brief Set the global number of perforations on this well. + * @param numPerforations the global number of elements + */ + void setNumPerforations( globalIndex numPerforations ) { m_numPerforations = numPerforations; } + + arrayView2d< real64 const > getPerfCoords() const override final { return m_perfCoords; } + + + /** + * @brief Set the locations of the perforations. + * @param perfCoords list of locations of all the perforations on the well + */ + void setPerfCoords( arrayView2d< real64 const > perfCoords ) { m_perfCoords = perfCoords; } + + arrayView1d< real64 const > getPerfTransmissibility() const override final { return m_perfTransmissibility; } + + + /** + * @brief Set the well transmissibility at the perforations. + * @param perfTransmissibility list of well transmissibility at all the perforations on the well + */ + void setPerfTransmissibility( arrayView1d< real64 const > perfTransmissibility ) { m_perfTransmissibility = perfTransmissibility; } + + arrayView1d< globalIndex const > getPerfElemIndex() const override final { return m_perfElemId; } + + /** + * @brief Set the global indices of the well elements connected to each perforation. + * @param perfElemId list providing the global index of the connected well element for each perforation + */ + void setPerfElemIndex( arrayView1d< globalIndex const > perfElemId ) { m_perfElemId = perfElemId; } + + /** + * @brief Set the well controls name + * @param wellControlsName The well controls name + */ + void setWellControlsName( string const & wellControlsName ) { m_wellControlsName = wellControlsName; } + string const & getWellControlsName( ) const override final { return m_wellControlsName; } + + /** + * @brief Set the well genrator name + * @param wellGeneratorName The well genrator name + */ + void setWellGeneratorName( string const & wellGeneratorName ) { m_wellGeneratorName = wellGeneratorName; } + string const & getWellGeneratorName( ) const override final { return m_wellGeneratorName; } + + ///@} + + /// @endcond + + + +private: + + + // XML Input + + /// Radius area of the well (assumed to be valid for the entire well) + real64 m_radius; + + // Geometry of the well (later passed to the WellElementSubRegion) + + // well element data + + /// Global number of well elements + globalIndex m_numElems; + + /// Physical location of the center of the well element + array2d< real64 > m_elemCenterCoords; + + /// Global index of the next well element + array1d< globalIndex > m_nextElemId; + + /// Global indices of the prev well elements (maybe need multiple prevs for branching) + array1d< array1d< globalIndex > > m_prevElemId; + + /// Connectivity between elements and nodes + array2d< globalIndex > m_elemToNodesMap; + + /// Volume of well elements + array1d< real64 > m_elemVolume; + + + // well node data + + /// Global number of well nodes + globalIndex m_numNodes; + + /// Physical location of the nodes + array2d< real64 > m_nodeCoords; + + // perforation data + + /// Global number of perforations + globalIndex m_numPerforations; + + /// Absolute physical location of the perforation + array2d< real64 > m_perfCoords; + + /// Well Peaceman index at the perforation + array1d< real64 > m_perfTransmissibility; + + /// Global index of the well element + array1d< globalIndex > m_perfElemId; + + + + // Perforation data + + /// List of perforation names + string_array m_perforationList; + + /// Well controls name + string m_wellControlsName; + + /// Well genrator name + string m_wellGeneratorName; + +}; +} +#endif diff --git a/src/coreComponents/mesh/generators/LineBlockABC.hpp b/src/coreComponents/mesh/generators/LineBlockABC.hpp new file mode 100644 index 00000000000..f0a80a94e4e --- /dev/null +++ b/src/coreComponents/mesh/generators/LineBlockABC.hpp @@ -0,0 +1,164 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2018-2020 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2020 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2018-2020 TotalEnergies + * Copyright (c) 2020- GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +#ifndef GEOSX_WELLBLOCKABC_HPP +#define GEOSX_WELLBLOCKABC_HPP + +#include "dataRepository/Group.hpp" +#include "mesh/ElementType.hpp" +#include "common/DataTypes.hpp" +#include "dataRepository/Group.hpp" + +#include + +namespace geos +{ + +/** + * Abstract base class defining the information provided by any well block implementation. + * + * It's noteworthy that the WellblockABC is immutable oriented. + * The derived implementations need to have the modification/creation capabilities. + */ +class LineBlockABC : public dataRepository::Group +{ +public: + + /** + * @brief Struct to define the top and bottom node of a segment. + */ + struct NodeLocation + { + static constexpr integer TOP = 0; /**< Top of the well */ + static constexpr integer BOTTOM = 1; /**< Bottom of the well */ + }; + + /** + * @brief Constructor + * @param name The name of this Group. + * @param parent The parent Group. + */ + LineBlockABC( string const & name, + Group * const parent ) + : + Group( name, parent ) + { } + + /** + * @name Getters / Setters + */ + ///@{ + + // getters for element data + + /** + * @brief Get the global number of well elements. + * @return the global number of elements + */ + virtual globalIndex numElements() const = 0; + + /** + * @brief Get the physical location of the centers of well elements. + * @return list of center locations of the well elements + */ + virtual arrayView2d< real64 const > getElemCoords() const = 0; + + /** + * @brief Get the global indices mapping an element to the next. + * @return list providing the global index of the next element for each element + */ + virtual arrayView1d< globalIndex const > getNextElemIndex() const = 0; + + /** + * @brief Get the global indices mapping an element to the previous ones. + * @return list providing the global indices of the previous elements for each element + */ + virtual arrayView1d< arrayView1d< globalIndex const > const > getPrevElemIndices() const = 0; + + /** + * @brief Get the global indices of the well nodes nodes connected to each element. + * @return list providing the global index of the well nodes for each well element + */ + virtual arrayView2d< globalIndex const > getElemToNodesMap() const = 0; + + /** + * @brief Get the volume of the well elements. + * @return list of volumes of the well elements + */ + virtual arrayView1d< real64 const > getElemVolume() const = 0; + + /** + * @brief Get the radius in the well. + * @return the radius in the well + */ + virtual real64 getElementRadius() const = 0; + + // getters for node data + + /** + * @brief Get the global number of well nodes. + * @return the global number of nodes + */ + virtual globalIndex numNodes() const = 0; + + /** + * @brief Get the physical location of the centers of well elements. + * @return list of center locations of the well elements + */ + virtual arrayView2d< real64 const > getNodeCoords() const = 0; + + + + // getters for perforation data + + /** + * @brief Get the global number of perforations on this well. + * @return the global number of elements + */ + virtual globalIndex numPerforations() const = 0; + + /** + * @brief Get the locations of the perforations. + * @return list of locations of all the perforations on the well + */ + virtual arrayView2d< real64 const > getPerfCoords() const = 0; + + /** + * @brief Get the well transmissibility at the perforations. + * @return list of well transmissibility at all the perforations on the well + */ + virtual arrayView1d< real64 const > getPerfTransmissibility() const = 0; + + /** + * @brief Get the global indices of the well elements connected to each perforation. + * @return list providing the global index of the connected well element for each perforation + */ + virtual arrayView1d< globalIndex const > getPerfElemIndex() const = 0; + + /** + * @brief Get the well controls name + * @return The well controls name + */ + virtual string const & getWellControlsName() const = 0; + + /** + * @brief Get the well generator name + * @return The well generator name + */ + virtual string const & getWellGeneratorName() const = 0; +}; + +} + +#endif //GEOSX_WELLBLOCKABC_HPP diff --git a/src/coreComponents/mesh/generators/MeshGeneratorBase.cpp b/src/coreComponents/mesh/generators/MeshGeneratorBase.cpp index 6d4148f368d..5d6019c3eaa 100644 --- a/src/coreComponents/mesh/generators/MeshGeneratorBase.cpp +++ b/src/coreComponents/mesh/generators/MeshGeneratorBase.cpp @@ -13,6 +13,7 @@ */ #include "MeshGeneratorBase.hpp" +#include "mesh/generators/CellBlockManager.hpp" namespace geos { @@ -26,9 +27,18 @@ MeshGeneratorBase::MeshGeneratorBase( string const & name, Group * const parent Group * MeshGeneratorBase::createChild( string const & childKey, string const & childName ) { - // Mesh generators generally don't have child XML nodes, must override this method to enable - GEOS_THROW( GEOS_FMT( "Mesh '{}': invalid child XML node '{}' of type {}", getName(), childName, childKey ), - InputError ); + GEOS_LOG_RANK_0( "Adding Mesh attribute: " << childKey << ", " << childName ); + std::unique_ptr< WellGeneratorBase > wellGen = WellGeneratorBase::CatalogInterface::factory( childKey, childName, this ); + return &this->registerGroup< WellGeneratorBase >( childName, std::move( wellGen ) ); +} + +void MeshGeneratorBase::expandObjectCatalogs() +{ + // During schema generation, register one of each type derived from WellGeneratorBase here + for( auto & catalogIter: WellGeneratorBase::getCatalog()) + { + createChild( catalogIter.first, catalogIter.first ); + } } MeshGeneratorBase::CatalogInterface::CatalogType & MeshGeneratorBase::getCatalog() @@ -37,4 +47,38 @@ MeshGeneratorBase::CatalogInterface::CatalogType & MeshGeneratorBase::getCatalog return catalog; } +CellBlockManagerABC & MeshGeneratorBase::generateMesh( Group & parent, array1d< int > const & partition ) +{ + CellBlockManager & cellBlockManager = parent.registerGroup< CellBlockManager >( keys::cellManager ); + + fillCellBlockManager( cellBlockManager, partition ); + + this->attachWellInfo( cellBlockManager ); + + return cellBlockManager; +} + +void MeshGeneratorBase::attachWellInfo( CellBlockManager & cellBlockManager ) +{ + forSubGroups< WellGeneratorBase >( [&]( WellGeneratorBase & wellGen ) { + wellGen.generateWellGeometry( ); + LineBlock & lb = cellBlockManager.registerLineBlock( wellGen.getWellRegionName() ); + lb.setNumElements( wellGen.numElements() ); + lb.setElemCoords( wellGen.getElemCoords() ); + lb.setNextElemIndex( wellGen.getNextElemIndex() ); + lb.setPrevElemIndices( wellGen.getPrevElemIndices() ); + lb.setElemToNodesMap( wellGen.getElemToNodesMap() ); + lb.setElemVolume( wellGen.getElemVolume() ); + lb.setElementRadius( wellGen.getElementRadius() ); + lb.setNumNodes( wellGen.numNodes() ); + lb.setNodeCoords( wellGen.getNodeCoords() ); + lb.setNumPerforations( wellGen.numPerforations() ); + lb.setPerfCoords( wellGen.getPerfCoords() ); + lb.setPerfTransmissibility( wellGen.getPerfTransmissibility() ); + lb.setPerfElemIndex( wellGen.getPerfElemIndex() ); + lb.setWellControlsName( wellGen.getWellControlsName() ); + lb.setWellGeneratorName( wellGen.getName() ); + + } ); +} } diff --git a/src/coreComponents/mesh/generators/MeshGeneratorBase.hpp b/src/coreComponents/mesh/generators/MeshGeneratorBase.hpp index 61212fe86a4..003b2f6cd0f 100644 --- a/src/coreComponents/mesh/generators/MeshGeneratorBase.hpp +++ b/src/coreComponents/mesh/generators/MeshGeneratorBase.hpp @@ -19,18 +19,23 @@ #ifndef GEOS_MESH_GENERATORS_MESHGENERATORBASE_HPP #define GEOS_MESH_GENERATORS_MESHGENERATORBASE_HPP +#include "mesh/mpiCommunications/SpatialPartition.hpp" +#include "mesh/generators/CellBlockManagerABC.hpp" + #include "dataRepository/Group.hpp" #include "dataRepository/WrapperBase.hpp" #include "codingUtilities/Utilities.hpp" #include "common/DataTypes.hpp" + namespace geos { -namespace dataRepository -{} - -class DomainPartition; +// This forward declaration prevents from exposing the internals of the module, +// which are only accessed through some private functions signatures. +// In order to avoid this forward declaration, we could expose an ABC +// instead of exposing the MeshGeneratorBase implementation. +class CellBlockManager; /** * @class MeshGeneratorBase @@ -55,6 +60,9 @@ class MeshGeneratorBase : public dataRepository::Group */ static string catalogName() { return "MeshGeneratorBase"; } + /// This function is used to expand any catalogs in the data structure + virtual void expandObjectCatalogs() override; + /// using alias for templated Catalog meshGenerator type using CatalogInterface = dataRepository::CatalogInterface< MeshGeneratorBase, string const &, Group * const >; @@ -74,9 +82,11 @@ class MeshGeneratorBase : public dataRepository::Group /** * @brief Generate the mesh object the input mesh object. - * @param[in] domain the domain partition from which to construct the mesh object + * @param parent The parent group of the CellBlockManager. + * @param[in] partition Number of domaoins in each dimesion (X,Y,Z) + * @return The generated CellBlockManagerABC */ - virtual void generateMesh( DomainPartition & domain ) = 0; + CellBlockManagerABC & generateMesh( Group & parent, array1d< int > const & partition ); /** * @brief Describe which kind of block must be considered. @@ -122,12 +132,32 @@ class MeshGeneratorBase : public dataRepository::Group */ std::map< string, string > const & getSurfacicFieldsMapping() const { return m_surfacicFields; } + /** + * @brief Get the associatied SpatialPartition generated. + * @return The generated SpatialPartition + */ + SpatialPartition const & getSpatialPartition() const { return m_spatialPartition; } + protected: /// Mapping from volumic field source to GEOSX field. std::map< string, string > m_volumicFields; /// Mapping from surfacic field source to GEOSX field. std::map< string, string > m_surfacicFields; + + /// SpatialPartition associated with the mesh + SpatialPartition m_spatialPartition; + +private: + /** + * @brief Fill the cellBlockManager object . + * @param[inout] cellBlockManager the CellBlockManager that will receive the meshing information + * @param[in] partition The number of domains in each dimesion (X,Y,Z) + */ + virtual void fillCellBlockManager( CellBlockManager & cellBlockManager, array1d< int > const & partition ) = 0; + + void attachWellInfo( CellBlockManager & cellBlockManager ); + }; } diff --git a/src/coreComponents/mesh/generators/PartitionDescriptor.hpp b/src/coreComponents/mesh/generators/PartitionDescriptor.hpp new file mode 100644 index 00000000000..5e4ad6b782f --- /dev/null +++ b/src/coreComponents/mesh/generators/PartitionDescriptor.hpp @@ -0,0 +1,97 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2018-2020 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2020 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2018-2020 TotalEnergies + * Copyright (c) 2019- GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +/** + * @file PartitionDescriptor.hpp + */ + +#ifndef GEOSX_MESH_PARTITIONDESCRIPTOR_H_ +#define GEOSX_MESH_PARTITIONDESCRIPTOR_H_ + +#include "mesh/mpiCommunications/SpatialPartition.hpp" + +#include + + +namespace geos +{ + +/** + * @class PartitionDescriptor + * @brief Simple utility to retrieve partition information in case of Metis or Spatial partition. + */ +class PartitionDescriptor +{ +public: + /** + * @brief indicate if the partition is described using a Metis Neighbor list. + * @return A boolean indicating if the partition is described usins a Metis neighbor list. + */ + bool hasMetisNeighborList() const { return m_hasMetisNeighborList; } + + /** + * @brief Sets the boolean that indicates if the partition is described using a Metis Neighbor list. + * @param hasMetisNeighborList A boolean indicating if the partition is described usins a Metis neighbor list. + */ + void setHasMetisNeighborList( bool hasMetisNeighborList ) { m_hasMetisNeighborList = hasMetisNeighborList; } + + /** + * @brief Gets a reference to the list of metis neighbor list. + * @return A reference to the Metis neighbor list. + */ + std::set< int > const & getMetisNeighborList() const { return m_metisNeighborList; } + + /** + * @brief Sets the list of metis neighbor list. + * @param metisNeighborList A reference to the Metis neighbor list. + */ + void setMetisNeighborList( std::vector< int > const & metisNeighborList ) + { + m_metisNeighborList.clear(); + m_metisNeighborList.insert( metisNeighborList.cbegin(), metisNeighborList.cend() ); + } + + /** + * @brief indicate if the partition is described using a spatial partition. + * @return A boolean indicating if the parition is described using a spatial partition. + */ + bool hasSpatialPartition() const { return !m_hasMetisNeighborList; } + + /** + * @brief Sets the boolean that indicates if the partition is described using a Metis Neighbor list. + * @param hasSpatialPartition a boolean indicating if the parition is described using a spatial partition. + */ + void setHasSpatialPartition( bool hasSpatialPartition ) { m_hasMetisNeighborList = !hasSpatialPartition; } + + /** + * @brief Returns a reference to the spatialPartition + * @return The spatial partiton. + */ + SpatialPartition const & getSpatialPartition() const { return m_spatialPartition; } + + /** + * @brief Sets the spatialPartition + * @param spatialPartition The spatial partiton. + */ + void setSpatialPartition( SpatialPartition const & spatialPartition ) { m_spatialPartition = spatialPartition; } + +private: + + bool m_hasMetisNeighborList; //< Indicate if we use metis neighbor list or spatial partition to describe the partition + std::set< int > m_metisNeighborList; //< The list of neighbors computed wwith metis + SpatialPartition m_spatialPartition; //< The spatial partition +}; + +} +#endif /* GEOSX_MESH_PARTITIONDESCRIPTOR_H_ */ diff --git a/src/coreComponents/mesh/generators/VTKMeshGenerator.cpp b/src/coreComponents/mesh/generators/VTKMeshGenerator.cpp index bd8ca058988..041401ec342 100644 --- a/src/coreComponents/mesh/generators/VTKMeshGenerator.cpp +++ b/src/coreComponents/mesh/generators/VTKMeshGenerator.cpp @@ -21,7 +21,6 @@ #include "mesh/generators/VTKFaceBlockUtilities.hpp" #include "mesh/generators/VTKMeshGeneratorTools.hpp" #include "mesh/generators/CellBlockManager.hpp" -#include "mesh/MeshBody.hpp" #include "common/DataTypes.hpp" #include "common/DataLayouts.hpp" #include "common/MpiWrapper.hpp" @@ -73,7 +72,7 @@ VTKMeshGenerator::VTKMeshGenerator( string const & name, " If set to a positive value, the GlobalId arrays in the input mesh are used and required, and the simulation aborts if they are not available" ); } -void VTKMeshGenerator::generateMesh( DomainPartition & domain ) +void VTKMeshGenerator::fillCellBlockManager( CellBlockManager & cellBlockManager, array1d< int > const & ) { // TODO refactor void MeshGeneratorBase::generateMesh( DomainPartition & domain ) GEOS_MARK_FUNCTION; @@ -87,27 +86,21 @@ void VTKMeshGenerator::generateMesh( DomainPartition & domain ) GEOS_LOG_LEVEL_RANK_0( 2, " reading the dataset..." ); vtkSmartPointer< vtkDataSet > loadedMesh = vtk::loadMesh( m_filePath, m_mainBlockName ); GEOS_LOG_LEVEL_RANK_0( 2, " redistributing mesh..." ); - m_vtkMesh = vtk::redistributeMesh( *loadedMesh, comm, m_partitionMethod, m_partitionRefinement, m_useGlobalIds ); + m_vtkMesh = vtk::redistributeMesh( loadedMesh, comm, m_partitionMethod, m_partitionRefinement, m_useGlobalIds ); GEOS_LOG_LEVEL_RANK_0( 2, " finding neighbor ranks..." ); std::vector< vtkBoundingBox > boxes = vtk::exchangeBoundingBoxes( *m_vtkMesh, comm ); std::vector< int > const neighbors = vtk::findNeighborRanks( std::move( boxes ) ); - domain.getMetisNeighborList().insert( neighbors.begin(), neighbors.end() ); + m_spatialPartition.setMetisNeighborList( std::move( neighbors ) ); GEOS_LOG_LEVEL_RANK_0( 2, " done!" ); } - GEOS_LOG_RANK_0( GEOS_FMT( "{} '{}': generating GEOSX mesh data structure", catalogName(), getName() ) ); - MeshBody & meshBody = domain.getMeshBodies().registerGroup< MeshBody >( this->getName() ); - meshBody.createMeshLevel( 0 ); - - CellBlockManager & cellBlockManager = meshBody.registerGroup< CellBlockManager >( keys::cellManager ); GEOS_LOG_LEVEL_RANK_0( 2, " preprocessing..." ); m_cellMap = vtk::buildCellMap( *m_vtkMesh, m_attributeName ); GEOS_LOG_LEVEL_RANK_0( 2, " writing nodes..." ); - real64 const globalLength = writeNodes( getLogLevel(), *m_vtkMesh, m_nodesetNames, cellBlockManager, this->m_translate, this->m_scale ); - meshBody.setGlobalLengthScale( globalLength ); + cellBlockManager.setGlobalLength( writeNodes( getLogLevel(), *m_vtkMesh, m_nodesetNames, cellBlockManager, this->m_translate, this->m_scale ) ); GEOS_LOG_LEVEL_RANK_0( 2, " writing cells..." ); writeCells( getLogLevel(), *m_vtkMesh, m_cellMap, cellBlockManager ); diff --git a/src/coreComponents/mesh/generators/VTKMeshGenerator.hpp b/src/coreComponents/mesh/generators/VTKMeshGenerator.hpp index 0c15c50b6be..6b1675a2405 100644 --- a/src/coreComponents/mesh/generators/VTKMeshGenerator.hpp +++ b/src/coreComponents/mesh/generators/VTKMeshGenerator.hpp @@ -51,7 +51,8 @@ class VTKMeshGenerator : public ExternalMeshGeneratorBase /** * @brief Generate the mesh using the VTK library. - * @param[in] domain the DomainPartition to be written + * @param[inout] cellBlockManager the CellBlockManager that will receive the meshing information + * @param[in] partition the number of domain in each direction (x,y,z) for InternalMesh only, not used here * @details This method leverages the VTK library to load the meshes. * The supported formats are the official VTK ones dedicated to * unstructured grids (.vtu, .pvtu and .vtk) and structured grids (.vts, .vti and .pvts). @@ -85,7 +86,7 @@ class VTKMeshGenerator : public ExternalMeshGeneratorBase * surfaces of interest, with triangles and/or quads holding an attribute value * of 1, 2 or 3, three node sets named "1", "2" and "3" will be instantiated by this method */ - virtual void generateMesh( DomainPartition & domain ) override; + virtual void fillCellBlockManager( CellBlockManager & cellBlockManager, array1d< int > const & partition ) override; void importFieldOnArray( Block block, string const & blockName, diff --git a/src/coreComponents/mesh/generators/VTKUtilities.cpp b/src/coreComponents/mesh/generators/VTKUtilities.cpp index 29381965567..42fce8317d4 100644 --- a/src/coreComponents/mesh/generators/VTKUtilities.cpp +++ b/src/coreComponents/mesh/generators/VTKUtilities.cpp @@ -480,12 +480,12 @@ loadMesh( Path const & filePath, * @return the vtk grid with global ids attributes */ vtkSmartPointer< vtkDataSet > -generateGlobalIDs( vtkDataSet & mesh ) +generateGlobalIDs( vtkSmartPointer< vtkDataSet > mesh ) { GEOS_MARK_FUNCTION; vtkNew< vtkGenerateGlobalIds > generator; - generator->SetInputDataObject( &mesh ); + generator->SetInputDataObject( mesh ); generator->Update(); return vtkDataSet::SafeDownCast( generator->GetOutputDataObject( 0 ) ); } @@ -593,44 +593,64 @@ findNeighborRanks( std::vector< vtkBoundingBox > boundingBoxes ) return neighbors; } -vtkSmartPointer< vtkDataSet > -redistributeMesh( vtkDataSet & loadedMesh, - MPI_Comm const comm, - PartitionMethod const method, - int const partitionRefinement, - int const useGlobalIds ) +vtkSmartPointer< vtkDataSet > manageGlobalIds( vtkSmartPointer< vtkDataSet > mesh, int useGlobalIds ) { - GEOS_MARK_FUNCTION; + auto hasGlobalIds = []( vtkSmartPointer< vtkDataSet > m ) -> bool + { + return m->GetPointData()->GetGlobalIds() != nullptr && m->GetCellData()->GetGlobalIds() != nullptr; + }; - // Generate global IDs for vertices and cells, if needed - vtkSmartPointer< vtkDataSet > mesh; - bool globalIdsAvailable = loadedMesh.GetPointData()->GetGlobalIds() != nullptr - && loadedMesh.GetCellData()->GetGlobalIds() != nullptr; + { + // Add global ids on the fly if needed + int const me = hasGlobalIds( mesh ); + int everyone; + MpiWrapper::allReduce( &me, &everyone, 1, MPI_MAX, MPI_COMM_GEOSX ); + + if( everyone and not me ) + { + mesh->GetPointData()->SetGlobalIds( vtkIdTypeArray::New() ); + mesh->GetCellData()->SetGlobalIds( vtkIdTypeArray::New() ); + } + } + + vtkSmartPointer< vtkDataSet > output; + bool const globalIdsAvailable = hasGlobalIds( mesh ); if( useGlobalIds > 0 && !globalIdsAvailable ) { GEOS_ERROR( "Global IDs strictly required (useGlobalId > 0) but unavailable. Set useGlobalIds to 0 to build them automatically." ); } else if( useGlobalIds >= 0 && globalIdsAvailable ) { - mesh = &loadedMesh; - vtkIdTypeArray const * const globalCellId = vtkIdTypeArray::FastDownCast( mesh->GetCellData()->GetGlobalIds() ); - vtkIdTypeArray const * const globalPointId = vtkIdTypeArray::FastDownCast( mesh->GetPointData()->GetGlobalIds() ); - GEOS_ERROR_IF( globalCellId->GetNumberOfComponents() != 1 && globalCellId->GetNumberOfTuples() != mesh->GetNumberOfCells(), + output = mesh; + vtkIdTypeArray const * const globalCellId = vtkIdTypeArray::FastDownCast( output->GetCellData()->GetGlobalIds() ); + vtkIdTypeArray const * const globalPointId = vtkIdTypeArray::FastDownCast( output->GetPointData()->GetGlobalIds() ); + GEOS_ERROR_IF( globalCellId->GetNumberOfComponents() != 1 && globalCellId->GetNumberOfTuples() != output->GetNumberOfCells(), "Global cell IDs are invalid. Check the array or enable automatic generation (useGlobalId < 0)" ); - GEOS_ERROR_IF( globalPointId->GetNumberOfComponents() != 1 && globalPointId->GetNumberOfTuples() != mesh->GetNumberOfPoints(), + GEOS_ERROR_IF( globalPointId->GetNumberOfComponents() != 1 && globalPointId->GetNumberOfTuples() != output->GetNumberOfPoints(), "Global cell IDs are invalid. Check the array or enable automatic generation (useGlobalId < 0)" ); GEOS_LOG_RANK_0( "Using global Ids defined in VTK mesh" ); } else { - GEOS_LOG_RANK_0( "Generating global Ids from VTK mesh" ); - vtkNew< vtkGenerateGlobalIds > generator; - generator->SetInputDataObject( &loadedMesh ); - generator->Update(); - mesh = generateGlobalIDs( loadedMesh ); + GEOS_LOG_RANK( "Generating global Ids from VTK mesh" ); + output = generateGlobalIDs( mesh ); } + return output; +} + +vtkSmartPointer< vtkDataSet > +redistributeMesh( vtkSmartPointer< vtkDataSet > loadedMesh, + MPI_Comm const comm, + PartitionMethod const method, + int const partitionRefinement, + int const useGlobalIds ) +{ + GEOS_MARK_FUNCTION; + + // Generate global IDs for vertices and cells, if needed + vtkSmartPointer< vtkDataSet > mesh = manageGlobalIds( loadedMesh, useGlobalIds ); // Determine if redistribution is required vtkIdType const minCellsOnAnyRank = MpiWrapper::min( mesh->GetNumberOfCells(), comm ); diff --git a/src/coreComponents/mesh/generators/VTKUtilities.hpp b/src/coreComponents/mesh/generators/VTKUtilities.hpp index f6ce768b7b9..284239fc3f8 100644 --- a/src/coreComponents/mesh/generators/VTKUtilities.hpp +++ b/src/coreComponents/mesh/generators/VTKUtilities.hpp @@ -95,7 +95,7 @@ findNeighborRanks( std::vector< vtkBoundingBox > boundingBoxes ); * @return the vtk grid redistributed */ vtkSmartPointer< vtkDataSet > -redistributeMesh( vtkDataSet & loadedMesh, +redistributeMesh( vtkSmartPointer< vtkDataSet > loadedMesh, MPI_Comm const comm, PartitionMethod const method, int const partitionRefinement, diff --git a/src/coreComponents/mesh/generators/WellGeneratorBase.cpp b/src/coreComponents/mesh/generators/WellGeneratorBase.cpp new file mode 100644 index 00000000000..98f93ecef7f --- /dev/null +++ b/src/coreComponents/mesh/generators/WellGeneratorBase.cpp @@ -0,0 +1,59 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2018-2020 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2020 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2018-2020 TotalEnergies + * Copyright (c) 2019- GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +#include "WellGeneratorBase.hpp" +#include "mesh/Perforation.hpp" + +namespace geos +{ +using namespace dataRepository; + +WellGeneratorBase::WellGeneratorBase( string const & name, Group * const parent ): + Group( name, parent ), + m_numPerforations( 0 ) +{ + setInputFlags( InputFlags::OPTIONAL_NONUNIQUE ); +} + +Group * WellGeneratorBase::createChild( string const & childKey, string const & childName ) +{ + if( childKey == viewKeyStruct::perforationString() ) + { + ++m_numPerforations; + + // keep track of the perforations that have been added + m_perforationList.emplace_back( childName ); + + return ®isterGroup< Perforation >( childName ); + } + else + { + GEOS_THROW( "Unrecognized node: " << childKey, InputError ); + } + return nullptr; +} + + +void WellGeneratorBase::expandObjectCatalogs() +{ + createChild( viewKeyStruct::perforationString(), viewKeyStruct::perforationString() ); +} + +WellGeneratorBase::CatalogInterface::CatalogType & WellGeneratorBase::getCatalog() +{ + static WellGeneratorBase::CatalogInterface::CatalogType catalog; + return catalog; +} + +} diff --git a/src/coreComponents/mesh/generators/WellGeneratorBase.hpp b/src/coreComponents/mesh/generators/WellGeneratorBase.hpp new file mode 100644 index 00000000000..32be277dc9b --- /dev/null +++ b/src/coreComponents/mesh/generators/WellGeneratorBase.hpp @@ -0,0 +1,244 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2018-2020 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2020 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2018-2020 TotalEnergies + * Copyright (c) 2019- GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +/* + * @file WellGeneratorBase.hpp + * + */ + +#ifndef GEOS_MESH_GENERATORS_WELLGENERATORBASE_HPP_ +#define GEOS_MESH_GENERATORS_WELLGENERATORBASE_HPP_ + +#include "dataRepository/Group.hpp" +#include "codingUtilities/Utilities.hpp" +#include "common/DataTypes.hpp" + + +namespace geos +{ + +/** + * @class WellGeneratorBase + * + * This class processes the data of a single well from the XML and generates the well geometry + */ +class WellGeneratorBase : public dataRepository::Group +{ +public: + + /** + * @brief Constructor. + * @param name name of the object in the data hierarchy. + * @param parent pointer to the parent group in the data hierarchy. + */ + WellGeneratorBase( const string & name, + Group * const parent ); + + /** + * @brief Get the catalog name. + * @return the name of this type in the catalog + */ + static string catalogName() { return "WellGeneratorBase"; } + + /// This function is used to expand any catalogs in the data structure + virtual void expandObjectCatalogs() override; + + /// using alias for templated Catalog meshGenerator type + using CatalogInterface = dataRepository::CatalogInterface< WellGeneratorBase, string const &, Group * const >; + + + /** + * @brief Create a new geometric object (box, plane, etc) as a child of this group. + * @param childKey the catalog key of the new geometric object to create + * @param childName the name of the new geometric object in the repository + * @return the group child + */ + virtual Group * createChild( string const & childKey, string const & childName ) override; + + /** + * @brief Accessor for the singleton Catalog object + * @return a static reference to the Catalog object + */ + static CatalogInterface::CatalogType & getCatalog(); + + /** + * @brief Main function of the class that generates the well geometry + */ + virtual void generateWellGeometry( ) = 0; + + /** + * @name Getters / Setters + */ + ///@{ + + // getters for element data + + /** + * @brief Get the global number of well elements. + * @return the global number of elements + */ + virtual globalIndex numElements() const = 0; + + /** + * @brief Getter to the Segment to PolyNode mapping + * @return The Segment to PolyNode mapping as a 2D array + */ + virtual const array2d< globalIndex > & getSegmentToPolyNodeMap() const = 0; + + /** + * @brief Get the number of nodes per well element + * @return the number of nodes per well element + */ + virtual globalIndex numNodesPerElement() const = 0; + + /** + * @brief Get the Coordinates of the polyline nodes + * @return the Coordinates of the polyline nodes + */ + virtual const array2d< real64 > & getPolyNodeCoord() const = 0; + + /** + * @return The minimum segment length + */ + virtual real64 getMinSegmentLength() const = 0; + + /** + * @return The minimum element length + */ + virtual real64 getMinElemLength() const = 0; + + /** + * @return The list of perforation names + */ + const string_array & getPerforationList() const { return m_perforationList; } + + /** + * @brief Get the physical location of the centers of well elements. + * @return list of center locations of the well elements + */ + virtual arrayView2d< real64 const > getElemCoords() const = 0; + + /** + * @brief Get the global indices mapping an element to the next. + * @return list providing the global index of the next element for each element + */ + virtual arrayView1d< globalIndex const > getNextElemIndex() const = 0; + + /** + * @brief Get the global indices mapping an element to the previous ones. + * @return list providing the global indices of the previous elements for each element + */ + virtual arrayView1d< arrayView1d< globalIndex const > const > getPrevElemIndices() const = 0; + + /** + * @brief Get the global indices of the well nodes nodes connected to each element. + * @return list providing the global index of the well nodes for each well element + */ + virtual arrayView2d< globalIndex const > getElemToNodesMap() const = 0; + + /** + * @brief Get the volume of the well elements. + * @return list of volumes of the well elements + */ + virtual arrayView1d< real64 const > getElemVolume() const = 0; + + /** + * @brief Get the radius in the well. + * @return the radius in the well + */ + virtual real64 getElementRadius() const = 0; + + // getters for node data + + /** + * @brief Get the global number of well nodes. + * @return the global number of nodes + */ + virtual globalIndex numNodes() const = 0; + + /** + * @brief Get the physical location of the centers of well elements. + * @return list of center locations of the well elements + */ + virtual arrayView2d< real64 const > getNodeCoords() const = 0; + + + + // getters for perforation data + /** + * @brief Get the global number of perforations on this well. + * @return the global number of elements + */ + globalIndex numPerforations() const { return m_numPerforations; } + + /** + * @brief Get the locations of the perforations. + * @return list of locations of all the perforations on the well + */ + virtual arrayView2d< real64 const > getPerfCoords() const = 0; + + /** + * @brief Get the well transmissibility at the perforations. + * @return list of well transmissibility at all the perforations on the well + */ + virtual arrayView1d< real64 const > getPerfTransmissibility() const = 0; + + /** + * @brief Get the global indices of the well elements connected to each perforation. + * @return list providing the global index of the connected well element for each perforation + */ + virtual arrayView1d< globalIndex const > getPerfElemIndex() const = 0; + + /** + * @returns The number of physical dimensions + */ + virtual int getPhysicalDimensionsNumber() const = 0; + + /** + * Getter for the associated well region name + * @return the associated well region name + */ + virtual const string getWellRegionName() const = 0; + + /** + * Getter for the associated well control name + * @return the associated well control name + */ + virtual const string getWellControlsName() const = 0; + + ///@cond DO_NOT_DOCUMENT + struct viewKeyStruct + { + constexpr static char const * polylineNodeCoordsString() { return "polylineNodeCoords"; } + constexpr static char const * polylineSegmentConnString() { return "polylineSegmentConn"; } + constexpr static char const * numElementsPerSegmentString() { return "numElementsPerSegment"; } + constexpr static char const * minSegmentLengthString() { return "minSegmentLength"; } + constexpr static char const * minElementLengthString() { return "minElementLength"; } + constexpr static char const * radiusString() { return "radius"; } + constexpr static char const * wellRegionNameString() { return "wellRegionName"; } + constexpr static char const * wellControlsNameString() { return "wellControlsName"; } + constexpr static char const * meshNameString() { return "meshName"; } + constexpr static char const * perforationString() { return "Perforation"; } + }; + /// @endcond + +protected: + /// Global number of perforations + globalIndex m_numPerforations; + + /// List of perforation names + string_array m_perforationList; +}; +} +#endif /* GEOS_MESH_GENERATORS_WELLGENERATORBASE_HPP_ */ diff --git a/src/coreComponents/mesh/mpiCommunications/SpatialPartition.cpp b/src/coreComponents/mesh/mpiCommunications/SpatialPartition.cpp index 978c31907ac..50fe92701bb 100644 --- a/src/coreComponents/mesh/mpiCommunications/SpatialPartition.cpp +++ b/src/coreComponents/mesh/mpiCommunications/SpatialPartition.cpp @@ -47,7 +47,6 @@ real64 MapValueToRange( real64 value, real64 min, real64 max ) SpatialPartition::SpatialPartition(): PartitionBase(), - m_Partitions(), m_Periodic( nsdof ), m_coords( nsdof ), m_min{ 0.0 }, @@ -55,7 +54,8 @@ SpatialPartition::SpatialPartition(): m_blockSize{ 1.0 }, m_gridSize{ 0.0 }, m_gridMin{ 0.0 }, - m_gridMax{ 0.0 } + m_gridMax{ 0.0 }, + m_Partitions() { m_size = 0; m_rank = 0; diff --git a/src/coreComponents/mesh/mpiCommunications/SpatialPartition.hpp b/src/coreComponents/mesh/mpiCommunications/SpatialPartition.hpp index 0ea26d090b6..8e47d2cf056 100644 --- a/src/coreComponents/mesh/mpiCommunications/SpatialPartition.hpp +++ b/src/coreComponents/mesh/mpiCommunications/SpatialPartition.hpp @@ -45,8 +45,34 @@ class SpatialPartition : public PartitionBase int getColor() override; - /// number of partitions - array1d< int > m_Partitions; + /** + * @brief Get the metis neighbors indices, const version. @see DomainPartition#m_metisNeighborList + * @return Container of global indices. + */ + std::set< int > const & getMetisNeighborList() const + { + return m_metisNeighborList; + } + + /** + * @brief Sets the list of metis neighbor list. + * @param metisNeighborList A reference to the Metis neighbor list. + */ + void setMetisNeighborList( std::vector< int > const & metisNeighborList ) + { + m_metisNeighborList.clear(); + m_metisNeighborList.insert( metisNeighborList.cbegin(), metisNeighborList.cend() ); + } + + /** + * @brief Get the number of domains in each dimension for a regular partition with InternalMesh. + * @return An array containing number of partition in X, Y and Z directions. + */ + array1d< int > const & getPartitions() const + { + return m_Partitions; + } + /** * @brief Boolean like array of length 3 (space dimensions). * @@ -103,6 +129,15 @@ class SpatialPartition : public PartitionBase * @brief Ghost position (max). */ real64 m_contactGhostMax[3]; + + /// number of partitions + array1d< int > m_Partitions; + + /** + * @brief Contains the global indices of the metis neighbors in case `metis` is used. Empty otherwise. + */ + std::set< int > m_metisNeighborList; + }; } diff --git a/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseFVM.cpp b/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseFVM.cpp index 20bc41c0cdd..2034f7b3248 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseFVM.cpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseFVM.cpp @@ -80,6 +80,10 @@ void CompositionalMultiphaseFVM::setupDofs( DomainPartition const & domain, m_numDofPerCell, getMeshTargets() ); + // this call with instruct GEOS to reorder the dof numbers + //dofManager.setLocalReorderingType( viewKeyStruct::elemDofFieldString(), + // DofManager::LocalReorderingType::ReverseCutHillMcKee ); + // for the volume balance equation, disable global coupling // this equation is purely local (not coupled to neighbors or other physics) dofManager.disableGlobalCouplingForEquation( viewKeyStruct::elemDofFieldString(), @@ -141,6 +145,7 @@ void CompositionalMultiphaseFVM::assembleFluxTerms( real64 const dt, dofManager.rankOffset(), elemDofKey, m_hasCapPressure, + fluxApprox.upwindingParams(), getName(), mesh.getElemManager(), stencilWrapper, diff --git a/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseHybridFVM.cpp b/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseHybridFVM.cpp index 44bad9255f1..cde7df1dbbe 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseHybridFVM.cpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseHybridFVM.cpp @@ -267,6 +267,10 @@ void CompositionalMultiphaseHybridFVM::setupDofs( DomainPartition const & GEOS_U viewKeyStruct::elemDofFieldString(), DofManager::Connector::Face ); + // this call with instruct GEOS to reorder the dof numbers + //dofManager.setLocalReorderingType( viewKeyStruct::elemDofFieldString(), + // DofManager::LocalReorderingType::ReverseCutHillMcKee ); + // for the volume balance equation, disable global coupling // this equation is purely local (not coupled to neighbors or other physics) dofManager.disableGlobalCouplingForEquation( viewKeyStruct::elemDofFieldString(), diff --git a/src/coreComponents/physicsSolvers/fluidFlow/FlowSolverBase.cpp b/src/coreComponents/physicsSolvers/fluidFlow/FlowSolverBase.cpp index 87163e3867f..f571ce5796d 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/FlowSolverBase.cpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/FlowSolverBase.cpp @@ -257,7 +257,9 @@ void FlowSolverBase::initializePreSubGroups() void FlowSolverBase::validatePoreVolumes( DomainPartition const & domain ) const { real64 minPoreVolume = LvArray::NumericLimits< real64 >::max; - globalIndex numElemsBelowThreshold = 0; + real64 maxPorosity = -LvArray::NumericLimits< real64 >::max; + globalIndex numElemsBelowPoreVolumeThreshold = 0; + globalIndex numElemsAbovePorosityThreshold = 0; forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&] ( string const &, MeshLevel const & mesh, @@ -272,33 +274,51 @@ void FlowSolverBase::validatePoreVolumes( DomainPartition const & domain ) const arrayView2d< real64 const > const porosity = porousMaterial.getPorosity(); arrayView1d< real64 const > const volume = subRegion.getElementVolume(); + arrayView1d< integer const > const ghostRank = subRegion.ghostRank(); real64 minPoreVolumeInSubRegion = 0.0; - localIndex numElemsBelowThresholdInSubRegion = 0; - - flowSolverBaseKernels::MinimumPoreVolumeKernel:: - computeMinimumPoreVolume( subRegion.size(), - porosity, - volume, - minPoreVolumeInSubRegion, - numElemsBelowThresholdInSubRegion ); + real64 maxPorosityInSubRegion = 0.0; + localIndex numElemsBelowPoreVolumeThresholdInSubRegion = 0; + localIndex numElemsAbovePorosityThresholdInSubRegion = 0; + + flowSolverBaseKernels::MinPoreVolumeMaxPorosityKernel:: + computeMinPoreVolumeMaxPorosity( subRegion.size(), + ghostRank, + porosity, + volume, + minPoreVolumeInSubRegion, + maxPorosityInSubRegion, + numElemsBelowPoreVolumeThresholdInSubRegion, + numElemsAbovePorosityThresholdInSubRegion ); if( minPoreVolumeInSubRegion < minPoreVolume ) { minPoreVolume = minPoreVolumeInSubRegion; } - numElemsBelowThreshold += numElemsBelowThresholdInSubRegion; + if( maxPorosityInSubRegion > maxPorosity ) + { + maxPorosity = maxPorosityInSubRegion; + } + + numElemsBelowPoreVolumeThreshold += numElemsBelowPoreVolumeThresholdInSubRegion; + numElemsAbovePorosityThreshold += numElemsAbovePorosityThresholdInSubRegion; } ); } ); minPoreVolume = MpiWrapper::min( minPoreVolume ); - numElemsBelowThreshold = MpiWrapper::sum( numElemsBelowThreshold ); + maxPorosity = MpiWrapper::max( maxPorosity ); + numElemsBelowPoreVolumeThreshold = MpiWrapper::sum( numElemsBelowPoreVolumeThreshold ); + numElemsAbovePorosityThreshold = MpiWrapper::sum( numElemsAbovePorosityThreshold ); - GEOS_LOG_RANK_0_IF( numElemsBelowThreshold > 0, + GEOS_LOG_RANK_0_IF( numElemsBelowPoreVolumeThreshold > 0, GEOS_FMT( "\nWarning! The mesh contains {} elements with a pore volume below {} m^3." "\nThe minimum pore volume is {} m^3." "\nOur recommendation is to check the validity of mesh and/or increase the porosity in these elements.\n", - numElemsBelowThreshold, flowSolverBaseKernels::poreVolumeThreshold, minPoreVolume ) ); + numElemsBelowPoreVolumeThreshold, flowSolverBaseKernels::poreVolumeThreshold, minPoreVolume ) ); + GEOS_LOG_RANK_0_IF( numElemsAbovePorosityThreshold > 0, + GEOS_FMT( "\nWarning! The mesh contains {} elements with a porosity above 1." + "\nThe maximum porosity is {}.\n", + numElemsAbovePorosityThreshold, maxPorosity ) ); } void FlowSolverBase::initializePostInitialConditionsPreSubGroups() diff --git a/src/coreComponents/physicsSolvers/fluidFlow/FlowSolverBaseKernels.hpp b/src/coreComponents/physicsSolvers/fluidFlow/FlowSolverBaseKernels.hpp index a0b3f33c1d4..32697ddc709 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/FlowSolverBaseKernels.hpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/FlowSolverBaseKernels.hpp @@ -33,48 +33,72 @@ static constexpr real64 poreVolumeThreshold = 1e-4; /** - * @struct MinimumPoreVolumeKernel - * @brief Kernel to compute the min pore volume in a subRegion + * @struct MinPoreVolumeMaxPorosityKernel + * @brief Kernel to compute the min pore volume and the max porosity in a subRegion */ -struct MinimumPoreVolumeKernel +struct MinPoreVolumeMaxPorosityKernel { /* - * @brief Kernel computing the min pore volume + * @brief Kernel computing the min pore volume and the max porosity * @param[in] size the number of elements in the subRegion + * @param[in] ghostRank the ghost ranks * @param[in] porosity the current element porosity * @param[in] volume the current element volume * @param[out] minPoreVolumeInSubRegion the min pore volume - * @param[out] numElemsBelowThresholdInSubRegion the number of elements is below the threshold + * @param[out] maxPorosityInSubRegion the max porosity + * @param[out] numElemsBelowPoreVolumeThresholdInSubRegion the number of elements below the pore volume threshold + * @param[out] numElemsAbovePorosityThresholdInSubRegion the number of elements with a porosity above 1 */ inline static void - computeMinimumPoreVolume( localIndex const size, - arrayView2d< real64 const > const & porosity, - arrayView1d< real64 const > const & volume, - real64 & minPoreVolumeInSubRegion, - localIndex & numElemsBelowThresholdInSubRegion ) + computeMinPoreVolumeMaxPorosity( localIndex const size, + arrayView1d< integer const > const & ghostRank, + arrayView2d< real64 const > const & porosity, + arrayView1d< real64 const > const & volume, + real64 & minPoreVolumeInSubRegion, + real64 & maxPorosityInSubRegion, + localIndex & numElemsBelowPoreVolumeThresholdInSubRegion, + localIndex & numElemsAbovePorosityThresholdInSubRegion ) { RAJA::ReduceMin< parallelDeviceReduce, real64 > minPoreVolume( LvArray::NumericLimits< real64 >::max ); - RAJA::ReduceSum< parallelDeviceReduce, localIndex > numElemsBelowThreshold( 0.0 ); + RAJA::ReduceMax< parallelDeviceReduce, real64 > maxPorosity( -LvArray::NumericLimits< real64 >::max ); + RAJA::ReduceSum< parallelDeviceReduce, localIndex > numElemsBelowPoreVolumeThreshold( 0.0 ); + RAJA::ReduceSum< parallelDeviceReduce, localIndex > numElemsAbovePorosityThreshold( 0.0 ); real64 const pvThreshold = poreVolumeThreshold; - forAll< parallelDevicePolicy<> >( size, [porosity, + forAll< parallelDevicePolicy<> >( size, [ghostRank, + porosity, volume, pvThreshold, minPoreVolume, - numElemsBelowThreshold] GEOS_HOST_DEVICE ( localIndex const ei ) + maxPorosity, + numElemsBelowPoreVolumeThreshold, + numElemsAbovePorosityThreshold] GEOS_HOST_DEVICE ( localIndex const ei ) { + if( ghostRank[ei] >= 0 ) + { + return; + } + real64 const poreVolume = porosity[ei][0] * volume[ei]; if( poreVolume < pvThreshold ) { - numElemsBelowThreshold += 1; + numElemsBelowPoreVolumeThreshold += 1; } + if( porosity[ei][0] > 1 ) + { + numElemsAbovePorosityThreshold += 1; + } + minPoreVolume.min( poreVolume ); + maxPorosity.max( porosity[ei][0] ); } ); minPoreVolumeInSubRegion = minPoreVolume.get(); - numElemsBelowThresholdInSubRegion = numElemsBelowThreshold.get(); + maxPorosityInSubRegion = maxPorosity.get(); + numElemsBelowPoreVolumeThresholdInSubRegion = numElemsBelowPoreVolumeThreshold.get(); + numElemsAbovePorosityThresholdInSubRegion = numElemsAbovePorosityThreshold.get(); } }; diff --git a/src/coreComponents/physicsSolvers/fluidFlow/IsothermalCompositionalMultiphaseFVMKernelUtilities.hpp b/src/coreComponents/physicsSolvers/fluidFlow/IsothermalCompositionalMultiphaseFVMKernelUtilities.hpp index 5989342e2c0..62e3a7f9857 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/IsothermalCompositionalMultiphaseFVMKernelUtilities.hpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/IsothermalCompositionalMultiphaseFVMKernelUtilities.hpp @@ -32,83 +32,60 @@ namespace geos namespace isothermalCompositionalMultiphaseFVMKernelUtilities { +// TODO make input parameter +static constexpr real64 epsC1PPU = 1e-8; + template< typename VIEWTYPE > using ElementViewConst = ElementRegionManager::ElementViewConst< VIEWTYPE >; +using Deriv = constitutive::multifluid::DerivativeOffset; -struct FluxUtilities +struct PotGrad { - - using Deriv = constitutive::multifluid::DerivativeOffset; - - /** - * @brief Form the PhasePotentialUpwind from pressure gradient and gravitational head - * @tparam numComp number of components - * @tparam numFluxSupportPoints number of flux support points - * @param numPhase number of phases - * @param ip phase index - * @param hasCapPressure flag indicating if there is capillary pressure - * @param seri arraySlice of the stencil-implied element region index - * @param sesri arraySlice of the stencil-implied element subregion index - * @param sei arraySlice of the stencil-implied element index - * @param trans transmissibility at the connection - * @param dTrans_dPres derivative of transmissibility wrt pressure - * @param pres pressure - * @param gravCoef gravitational coefficient - * @param phaseMob phase mobility - * @param dPhaseMob derivative of phase mobility wrt pressure, temperature, comp density - * @param dPhaseVolFrac derivative of phase volume fraction wrt pressure, temperature, comp density - * @param dCompFrac_dCompDens derivative of component fraction wrt component density - * @param phaseMassDens phase mass density - * @param dPhaseMassDens derivative of phase mass density wrt pressure, temperature, comp fraction - * @param phaseCapPressure phase capillary pressure - * @param dPhaseCapPressure_dPhaseVolFrac derivative of phase capillary pressure wrt phase volume fraction - * @param k_up uptream index for this phase - * @param potGrad potential gradient for this phase - * @param phaseFlux phase flux - * @param dPhaseFlux_dP derivative of phase flux wrt pressure - * @param dPhaseFlux_dC derivative of phase flux wrt comp density - */ template< integer numComp, integer numFluxSupportPoints > GEOS_HOST_DEVICE static void - computePPUPhaseFlux( integer const numPhase, - integer const ip, - integer const hasCapPressure, - localIndex const ( &seri )[numFluxSupportPoints], - localIndex const ( &sesri )[numFluxSupportPoints], - localIndex const ( &sei )[numFluxSupportPoints], - real64 const ( &trans )[2], - real64 const ( &dTrans_dPres )[2], - ElementViewConst< arrayView1d< real64 const > > const & pres, - ElementViewConst< arrayView1d< real64 const > > const & gravCoef, - ElementViewConst< arrayView2d< real64 const, compflow::USD_PHASE > > const & phaseMob, - ElementViewConst< arrayView3d< real64 const, compflow::USD_PHASE_DC > > const & dPhaseMob, - ElementViewConst< arrayView3d< real64 const, compflow::USD_PHASE_DC > > const & dPhaseVolFrac, - ElementViewConst< arrayView3d< real64 const, compflow::USD_COMP_DC > > const & dCompFrac_dCompDens, - ElementViewConst< arrayView3d< real64 const, constitutive::multifluid::USD_PHASE > > const & phaseMassDens, - ElementViewConst< arrayView4d< real64 const, constitutive::multifluid::USD_PHASE_DC > > const & dPhaseMassDens, - ElementViewConst< arrayView3d< real64 const, constitutive::cappres::USD_CAPPRES > > const & phaseCapPressure, - ElementViewConst< arrayView4d< real64 const, constitutive::cappres::USD_CAPPRES_DS > > const & dPhaseCapPressure_dPhaseVolFrac, - localIndex & k_up, - real64 & potGrad, - real64 ( &phaseFlux ), - real64 ( & dPhaseFlux_dP )[numFluxSupportPoints], - real64 ( & dPhaseFlux_dC )[numFluxSupportPoints][numComp] ) + compute ( integer const numPhase, + integer const ip, + integer const hasCapPressure, + localIndex const ( &seri )[numFluxSupportPoints], + localIndex const ( &sesri )[numFluxSupportPoints], + localIndex const ( &sei )[numFluxSupportPoints], + real64 const ( &trans )[numFluxSupportPoints], + real64 const ( &dTrans_dPres )[numFluxSupportPoints], + ElementViewConst< arrayView1d< real64 const > > const & pres, + ElementViewConst< arrayView1d< real64 const > > const & gravCoef, + ElementViewConst< arrayView3d< real64 const, compflow::USD_PHASE_DC > > const & dPhaseVolFrac, + ElementViewConst< arrayView3d< real64 const, compflow::USD_COMP_DC > > const & dCompFrac_dCompDens, + ElementViewConst< arrayView3d< real64 const, constitutive::multifluid::USD_PHASE > > const & phaseMassDens, + ElementViewConst< arrayView4d< real64 const, constitutive::multifluid::USD_PHASE_DC > > const & dPhaseMassDens, + ElementViewConst< arrayView3d< real64 const, constitutive::cappres::USD_CAPPRES > > const & phaseCapPressure, + ElementViewConst< arrayView4d< real64 const, constitutive::cappres::USD_CAPPRES_DS > > const & dPhaseCapPressure_dPhaseVolFrac, + real64 & potGrad, + real64 ( & dPresGrad_dP )[numFluxSupportPoints], + real64 ( & dPresGrad_dC )[numFluxSupportPoints][numComp], + real64 ( & dGravHead_dP )[numFluxSupportPoints], + real64 ( & dGravHead_dC )[numFluxSupportPoints][numComp] ) { + // assign derivatives arrays to zero + for( integer i = 0; i < numFluxSupportPoints; ++i ) + { + dPresGrad_dP[i] = 0.0; + dGravHead_dP[i] = 0.0; + for( integer jc = 0; jc < numComp; ++jc ) + { + dPresGrad_dC[i][jc] = 0.0; + dGravHead_dC[i][jc] = 0.0; + } + } + // create local work arrays real64 densMean = 0.0; real64 dDensMean_dP[numFluxSupportPoints]{}; real64 dDensMean_dC[numFluxSupportPoints][numComp]{}; real64 presGrad = 0.0; - real64 dPresGrad_dP[numFluxSupportPoints]{}; - real64 dPresGrad_dC[numFluxSupportPoints][numComp]{}; - real64 gravHead = 0.0; - real64 dGravHead_dP[numFluxSupportPoints]{}; - real64 dGravHead_dC[numFluxSupportPoints][numComp]{}; - real64 dCapPressure_dC[numComp]{}; real64 dProp_dC[numComp]{}; @@ -198,10 +175,82 @@ struct FluxUtilities } } - // *** upwinding *** // compute phase potential gradient potGrad = presGrad - gravHead; + } + +}; + +struct PPUPhaseFlux +{ + /** + * @brief Form the PhasePotentialUpwind from pressure gradient and gravitational head + * @tparam numComp number of components + * @tparam numFluxSupportPoints number of flux support points + * @param numPhase number of phases + * @param ip phase index + * @param hasCapPressure flag indicating if there is capillary pressure + * @param seri arraySlice of the stencil-implied element region index + * @param sesri arraySlice of the stencil-implied element subregion index + * @param sei arraySlice of the stencil-implied element index + * @param trans transmissibility at the connection + * @param dTrans_dPres derivative of transmissibility wrt pressure + * @param pres pressure + * @param gravCoef gravitational coefficient + * @param phaseMob phase mobility + * @param dPhaseMob derivative of phase mobility wrt pressure, temperature, comp density + * @param dPhaseVolFrac derivative of phase volume fraction wrt pressure, temperature, comp density + * @param dCompFrac_dCompDens derivative of component fraction wrt component density + * @param phaseMassDens phase mass density + * @param dPhaseMassDens derivative of phase mass density wrt pressure, temperature, comp fraction + * @param phaseCapPressure phase capillary pressure + * @param dPhaseCapPressure_dPhaseVolFrac derivative of phase capillary pressure wrt phase volume fraction + * @param k_up uptream index for this phase + * @param potGrad potential gradient for this phase + * @param phaseFlux phase flux + * @param dPhaseFlux_dP derivative of phase flux wrt pressure + * @param dPhaseFlux_dC derivative of phase flux wrt comp density + */ + template< integer numComp, integer numFluxSupportPoints > + GEOS_HOST_DEVICE + static void + compute( integer const numPhase, + integer const ip, + integer const hasCapPressure, + //real64 const GEOS_UNUSED_PARAM( epsC1PPU ), + localIndex const ( &seri )[numFluxSupportPoints], + localIndex const ( &sesri )[numFluxSupportPoints], + localIndex const ( &sei )[numFluxSupportPoints], + real64 const ( &trans )[2], + real64 const ( &dTrans_dPres )[2], + ElementViewConst< arrayView1d< real64 const > > const & pres, + ElementViewConst< arrayView1d< real64 const > > const & gravCoef, + ElementViewConst< arrayView2d< real64 const, compflow::USD_PHASE > > const & phaseMob, + ElementViewConst< arrayView3d< real64 const, compflow::USD_PHASE_DC > > const & dPhaseMob, + ElementViewConst< arrayView3d< real64 const, compflow::USD_PHASE_DC > > const & dPhaseVolFrac, + ElementViewConst< arrayView3d< real64 const, compflow::USD_COMP_DC > > const & dCompFrac_dCompDens, + ElementViewConst< arrayView3d< real64 const, constitutive::multifluid::USD_PHASE > > const & phaseMassDens, + ElementViewConst< arrayView4d< real64 const, constitutive::multifluid::USD_PHASE_DC > > const & dPhaseMassDens, + ElementViewConst< arrayView3d< real64 const, constitutive::cappres::USD_CAPPRES > > const & phaseCapPressure, + ElementViewConst< arrayView4d< real64 const, constitutive::cappres::USD_CAPPRES_DS > > const & dPhaseCapPressure_dPhaseVolFrac, + localIndex & k_up, + real64 & potGrad, + real64 ( &phaseFlux ), + real64 ( & dPhaseFlux_dP )[numFluxSupportPoints], + real64 ( & dPhaseFlux_dC )[numFluxSupportPoints][numComp] ) + { + real64 dPresGrad_dP[numFluxSupportPoints]{}; + real64 dPresGrad_dC[numFluxSupportPoints][numComp]{}; + real64 dGravHead_dP[numFluxSupportPoints]{}; + real64 dGravHead_dC[numFluxSupportPoints][numComp]{}; + PotGrad::compute< numComp, numFluxSupportPoints >( numPhase, ip, hasCapPressure, seri, sesri, sei, trans, dTrans_dPres, pres, + gravCoef, dPhaseVolFrac, dCompFrac_dCompDens, phaseMassDens, dPhaseMassDens, + phaseCapPressure, dPhaseCapPressure_dPhaseVolFrac, potGrad, dPresGrad_dP, + dPresGrad_dC, dGravHead_dP, dGravHead_dC ); + + // *** upwinding *** + // choose upstream cell k_up = (potGrad >= 0) ? 0 : 1; @@ -236,7 +285,159 @@ struct FluxUtilities dPhaseFlux_dC[k_up][jc] += dPhaseMobSub[Deriv::dC+jc] * potGrad; } } +}; + +struct C1PPUPhaseFlux +{ + /** + * @brief Form the PhasePotentialUpwind from pressure gradient and gravitational head + * @tparam numComp number of components + * @tparam numFluxSupportPoints number of flux support points + * @param numPhase number of phases + * @param ip phase index + * @param hasCapPressure flag indicating if there is capillary pressure + * @param seri arraySlice of the stencil-implied element region index + * @param sesri arraySlice of the stencil-implied element subregion index + * @param sei arraySlice of the stencil-implied element index + * @param trans transmissibility at the connection + * @param dTrans_dPres derivative of transmissibility wrt pressure + * @param pres pressure + * @param gravCoef gravitational coefficient + * @param phaseMob phase mobility + * @param dPhaseMob derivative of phase mobility wrt pressure, temperature, comp density + * @param dPhaseVolFrac derivative of phase volume fraction wrt pressure, temperature, comp density + * @param dCompFrac_dCompDens derivative of component fraction wrt component density + * @param phaseMassDens phase mass density + * @param dPhaseMassDens derivative of phase mass density wrt pressure, temperature, comp fraction + * @param phaseCapPressure phase capillary pressure + * @param dPhaseCapPressure_dPhaseVolFrac derivative of phase capillary pressure wrt phase volume fraction + * @param k_up uptream index for this phase + * @param potGrad potential gradient for this phase + * @param phaseFlux phase flux + * @param dPhaseFlux_dP derivative of phase flux wrt pressure + * @param dPhaseFlux_dC derivative of phase flux wrt comp density + */ + template< integer numComp, integer numFluxSupportPoints > + GEOS_HOST_DEVICE + static void + compute( integer const numPhase, + integer const ip, + integer const hasCapPressure, + //real64 const epsC1PPU, + localIndex const ( &seri )[numFluxSupportPoints], + localIndex const ( &sesri )[numFluxSupportPoints], + localIndex const ( &sei )[numFluxSupportPoints], + real64 const ( &trans )[2], + real64 const ( &dTrans_dPres )[2], + ElementViewConst< arrayView1d< real64 const > > const & pres, + ElementViewConst< arrayView1d< real64 const > > const & gravCoef, + ElementViewConst< arrayView2d< real64 const, compflow::USD_PHASE > > const & phaseMob, + ElementViewConst< arrayView3d< real64 const, compflow::USD_PHASE_DC > > const & dPhaseMob, + ElementViewConst< arrayView3d< real64 const, compflow::USD_PHASE_DC > > const & dPhaseVolFrac, + ElementViewConst< arrayView3d< real64 const, compflow::USD_COMP_DC > > const & dCompFrac_dCompDens, + ElementViewConst< arrayView3d< real64 const, constitutive::multifluid::USD_PHASE > > const & phaseMassDens, + ElementViewConst< arrayView4d< real64 const, constitutive::multifluid::USD_PHASE_DC > > const & dPhaseMassDens, + ElementViewConst< arrayView3d< real64 const, constitutive::cappres::USD_CAPPRES > > const & phaseCapPressure, + ElementViewConst< arrayView4d< real64 const, constitutive::cappres::USD_CAPPRES_DS > > const & dPhaseCapPressure_dPhaseVolFrac, + localIndex & k_up, + real64 & potGrad, + real64 ( &phaseFlux ), + real64 ( & dPhaseFlux_dP )[numFluxSupportPoints], + real64 ( & dPhaseFlux_dC )[numFluxSupportPoints][numComp] ) + { + real64 dPresGrad_dP[numFluxSupportPoints]; + real64 dPresGrad_dC[numFluxSupportPoints][numComp]; + real64 dGravHead_dP[numFluxSupportPoints]; + real64 dGravHead_dC[numFluxSupportPoints][numComp]; + PotGrad::compute< numComp, numFluxSupportPoints >( numPhase, ip, hasCapPressure, seri, sesri, sei, trans, dTrans_dPres, pres, + gravCoef, dPhaseVolFrac, dCompFrac_dCompDens, phaseMassDens, dPhaseMassDens, + phaseCapPressure, dPhaseCapPressure_dPhaseVolFrac, potGrad, dPresGrad_dP, + dPresGrad_dC, dGravHead_dP, dGravHead_dC ); + + // *** upwinding *** + + // phase flux and derivatives + + // assuming TPFA in the code below + + real64 const mobility_i = phaseMob[seri[0]][sesri[0]][sei[0]][ip]; + real64 const mobility_j = phaseMob[seri[1]][sesri[1]][sei[1]][ip]; + + // compute phase flux, see Eqs. (66) and (69) from the reference above + real64 const smoEps = epsC1PPU; + real64 const tmpSqrt = sqrt( potGrad * potGrad + smoEps * smoEps ); + real64 const smoMax = 0.5 * (-potGrad + tmpSqrt); + + phaseFlux = potGrad * mobility_i - smoMax * (mobility_j - mobility_i); + + // derivativess + + // first part, mobility derivative + + // dP + { + real64 const dMob_dP = dPhaseMob[seri[0]][sesri[0]][sei[0]][ip][Deriv::dP]; + dPhaseFlux_dP[0] += potGrad * dMob_dP; + } + + // dC + { + arraySlice1d< real64 const, compflow::USD_PHASE_DC - 2 > + dPhaseMobSub = dPhaseMob[seri[0]][sesri[0]][sei[0]][ip]; + for( integer jc = 0; jc < numComp; ++jc ) + { + dPhaseFlux_dC[0][jc] += potGrad * dPhaseMobSub[Deriv::dC + jc]; + } + } + + real64 const tmpInv = 1.0 / tmpSqrt; + real64 const dSmoMax_x = 0.5 * (1.0 - potGrad * tmpInv); + + // pressure gradient and mobility difference depend on all points in the stencil + real64 const dMobDiff_sign[numFluxSupportPoints] = {-1.0, 1.0}; + for( integer ke = 0; ke < numFluxSupportPoints; ++ke ) + { + // dP + + real64 const dPotGrad_dP = dPresGrad_dP[ke] - dGravHead_dP[ke]; + + // first part + dPhaseFlux_dP[ke] += dPotGrad_dP * mobility_i; + + // second part + real64 const dSmoMax_dP = -dPotGrad_dP * dSmoMax_x; + dPhaseFlux_dP[ke] += -dSmoMax_dP * (mobility_j - mobility_i); + + real64 const dMob_dP = dPhaseMob[seri[ke]][sesri[ke]][sei[ke]][ip][Deriv::dP]; + dPhaseFlux_dP[ke] += -smoMax * dMobDiff_sign[ke] * dMob_dP; + + // dC + + arraySlice1d< real64 const, compflow::USD_PHASE_DC - 2 > + dPhaseMobSub = dPhaseMob[seri[ke]][sesri[ke]][sei[ke]][ip]; + + for( integer jc = 0; jc < numComp; ++jc ) + { + real64 const dPotGrad_dC = dPresGrad_dC[ke][jc] - dGravHead_dC[ke][jc]; + + // first part + dPhaseFlux_dC[ke][jc] += dPotGrad_dC * mobility_i; + + // second part + real64 const dSmoMax_dC = -dPotGrad_dC * dSmoMax_x; + dPhaseFlux_dC[ke][jc] += -dSmoMax_dC * (mobility_j - mobility_i); + dPhaseFlux_dC[ke][jc] += -smoMax * dMobDiff_sign[ke] * dPhaseMobSub[Deriv::dC + jc]; + } + } + + // choose upstream cell for composition upwinding + k_up = (phaseFlux >= 0) ? 0 : 1; + + } +}; +struct PhaseComponentFlux +{ /** * @brief Compute the component flux for a given phase * @tparam numComp number of components @@ -259,20 +460,20 @@ struct FluxUtilities template< localIndex numComp, localIndex numFluxSupportPoints > GEOS_HOST_DEVICE static void - computePhaseComponentFlux( localIndex const ip, - localIndex const k_up, - localIndex const ( &seri )[numFluxSupportPoints], - localIndex const ( &sesri )[numFluxSupportPoints], - localIndex const ( &sei )[numFluxSupportPoints], - ElementViewConst< arrayView4d< real64 const, constitutive::multifluid::USD_PHASE_COMP > > const & phaseCompFrac, - ElementViewConst< arrayView5d< real64 const, constitutive::multifluid::USD_PHASE_COMP_DC > > const & dPhaseCompFrac, - ElementViewConst< arrayView3d< real64 const, compflow::USD_COMP_DC > > const & dCompFrac_dCompDens, - real64 const & phaseFlux, - real64 const ( &dPhaseFlux_dP )[numFluxSupportPoints], - real64 const ( &dPhaseFlux_dC )[numFluxSupportPoints][numComp], - real64 ( & compFlux )[numComp], - real64 ( & dCompFlux_dP )[numFluxSupportPoints][numComp], - real64 ( & dCompFlux_dC )[numFluxSupportPoints][numComp][numComp] ) + compute( localIndex const ip, + localIndex const k_up, + localIndex const ( &seri )[numFluxSupportPoints], + localIndex const ( &sesri )[numFluxSupportPoints], + localIndex const ( &sei )[numFluxSupportPoints], + ElementViewConst< arrayView4d< real64 const, constitutive::multifluid::USD_PHASE_COMP > > const & phaseCompFrac, + ElementViewConst< arrayView5d< real64 const, constitutive::multifluid::USD_PHASE_COMP_DC > > const & dPhaseCompFrac, + ElementViewConst< arrayView3d< real64 const, compflow::USD_COMP_DC > > const & dCompFrac_dCompDens, + real64 const & phaseFlux, + real64 const ( &dPhaseFlux_dP )[numFluxSupportPoints], + real64 const ( &dPhaseFlux_dC )[numFluxSupportPoints][numComp], + real64 ( & compFlux )[numComp], + real64 ( & dCompFlux_dP )[numFluxSupportPoints][numComp], + real64 ( & dCompFlux_dC )[numFluxSupportPoints][numComp][numComp] ) { localIndex const er_up = seri[k_up]; localIndex const esr_up = sesri[k_up]; diff --git a/src/coreComponents/physicsSolvers/fluidFlow/IsothermalCompositionalMultiphaseFVMKernels.hpp b/src/coreComponents/physicsSolvers/fluidFlow/IsothermalCompositionalMultiphaseFVMKernels.hpp index cf55cfaa822..0682bd1ca99 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/IsothermalCompositionalMultiphaseFVMKernels.hpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/IsothermalCompositionalMultiphaseFVMKernels.hpp @@ -41,6 +41,7 @@ #include "physicsSolvers/fluidFlow/IsothermalCompositionalMultiphaseBaseKernels.hpp" #include "physicsSolvers/fluidFlow/IsothermalCompositionalMultiphaseFVMKernelUtilities.hpp" #include "physicsSolvers/fluidFlow/StencilAccessors.hpp" +#include "finiteVolume/FluxApproximationBase.hpp" namespace geos { @@ -393,7 +394,7 @@ class FaceBasedAssemblyKernelBase * @tparam STENCILWRAPPER the type of the stencil wrapper * @brief Define the interface for the assembly kernel in charge of flux terms */ -template< integer NUM_COMP, integer NUM_DOF, typename STENCILWRAPPER > +template< integer NUM_COMP, integer NUM_DOF, typename STENCILWRAPPER, typename PHASE_FLUX_COMPUTE = isothermalCompositionalMultiphaseFVMKernelUtilities::PPUPhaseFlux > class FaceBasedAssemblyKernel : public FaceBasedAssemblyKernelBase { public: @@ -437,6 +438,7 @@ class FaceBasedAssemblyKernel : public FaceBasedAssemblyKernelBase FaceBasedAssemblyKernel( integer const numPhases, globalIndex const rankOffset, integer const hasCapPressure, + //real64 const epsC1PPU, STENCILWRAPPER const & stencilWrapper, DofNumberAccessor const & dofNumberAccessor, CompFlowAccessors const & compFlowAccessors, @@ -460,8 +462,9 @@ class FaceBasedAssemblyKernel : public FaceBasedAssemblyKernelBase m_stencilWrapper( stencilWrapper ), m_seri( stencilWrapper.getElementRegionIndices() ), m_sesri( stencilWrapper.getElementSubRegionIndices() ), - m_sei( stencilWrapper.getElementIndices() ) - {} + m_sei( stencilWrapper.getElementIndices() )//, + //m_epsC1PPU( epsC1PPU ) + { } /** * @struct StackVariables @@ -610,12 +613,11 @@ class FaceBasedAssemblyKernel : public FaceBasedAssemblyKernelBase localIndex k_up = -1; - isothermalCompositionalMultiphaseFVMKernelUtilities:: - FluxUtilities:: - computePPUPhaseFlux< numComp, numFluxSupportPoints > + PHASE_FLUX_COMPUTE::template compute< numComp, numFluxSupportPoints > ( m_numPhases, ip, m_hasCapPressure, + //m_epsC1PPU, seri, sesri, sei, trans, dTrans_dPres, @@ -633,8 +635,7 @@ class FaceBasedAssemblyKernel : public FaceBasedAssemblyKernelBase dPhaseFlux_dC ); isothermalCompositionalMultiphaseFVMKernelUtilities:: - FluxUtilities:: - computePhaseComponentFlux< numComp, numFluxSupportPoints > + PhaseComponentFlux::compute< numComp, numFluxSupportPoints > ( ip, k_up, seri, sesri, sei, @@ -764,6 +765,9 @@ class FaceBasedAssemblyKernel : public FaceBasedAssemblyKernelBase typename STENCILWRAPPER::IndexContainerViewConstType const m_seri; typename STENCILWRAPPER::IndexContainerViewConstType const m_sesri; typename STENCILWRAPPER::IndexContainerViewConstType const m_sei; + + /// Tolerance for C1-PPU smoothing + //real64 const m_epsC1PPU; }; /** @@ -796,6 +800,7 @@ class FaceBasedAssemblyKernelFactory globalIndex const rankOffset, string const & dofKey, integer const hasCapPressure, + UpwindingParameters upwindingParams, string const & solverName, ElementRegionManager const & elemManager, STENCILWRAPPER const & stencilWrapper, @@ -803,25 +808,44 @@ class FaceBasedAssemblyKernelFactory CRSMatrixView< real64, globalIndex const > const & localMatrix, arrayView1d< real64 > const & localRhs ) { - isothermalCompositionalMultiphaseBaseKernels::internal::kernelLaunchSelectorCompSwitch( numComps, [&] ( auto NC ) + isothermalCompositionalMultiphaseBaseKernels::internal::kernelLaunchSelectorCompSwitch( numComps, [&]( auto NC ) { integer constexpr NUM_COMP = NC(); - integer constexpr NUM_DOF = NC()+1; + integer constexpr NUM_DOF = NC() + 1; ElementRegionManager::ElementViewAccessor< arrayView1d< globalIndex const > > dofNumberAccessor = elemManager.constructArrayViewAccessor< globalIndex, 1 >( dofKey ); dofNumberAccessor.setName( solverName + "/accessors/" + dofKey ); - using kernelType = FaceBasedAssemblyKernel< NUM_COMP, NUM_DOF, STENCILWRAPPER >; - typename kernelType::CompFlowAccessors compFlowAccessors( elemManager, solverName ); - typename kernelType::MultiFluidAccessors multiFluidAccessors( elemManager, solverName ); - typename kernelType::CapPressureAccessors capPressureAccessors( elemManager, solverName ); - typename kernelType::PermeabilityAccessors permeabilityAccessors( elemManager, solverName ); + if( upwindingParams.upwindingScheme == UpwindingScheme::C1PPU && isothermalCompositionalMultiphaseFVMKernelUtilities::epsC1PPU > 0 ) //upwindingParams.epsC1PPU + // > + // 0 + // ) + { + using kernelType = FaceBasedAssemblyKernel< NUM_COMP, NUM_DOF, STENCILWRAPPER, isothermalCompositionalMultiphaseFVMKernelUtilities::C1PPUPhaseFlux >; + typename kernelType::CompFlowAccessors compFlowAccessors( elemManager, solverName ); + typename kernelType::MultiFluidAccessors multiFluidAccessors( elemManager, solverName ); + typename kernelType::CapPressureAccessors capPressureAccessors( elemManager, solverName ); + typename kernelType::PermeabilityAccessors permeabilityAccessors( elemManager, solverName ); + + kernelType kernel( numPhases, rankOffset, hasCapPressure, /*upwindingParams.epsC1PPU,*/ stencilWrapper, dofNumberAccessor, + compFlowAccessors, multiFluidAccessors, capPressureAccessors, permeabilityAccessors, + dt, localMatrix, localRhs ); + kernelType::template launch< POLICY >( stencilWrapper.size(), kernel ); + } + else + { + using kernelType = FaceBasedAssemblyKernel< NUM_COMP, NUM_DOF, STENCILWRAPPER >; + typename kernelType::CompFlowAccessors compFlowAccessors( elemManager, solverName ); + typename kernelType::MultiFluidAccessors multiFluidAccessors( elemManager, solverName ); + typename kernelType::CapPressureAccessors capPressureAccessors( elemManager, solverName ); + typename kernelType::PermeabilityAccessors permeabilityAccessors( elemManager, solverName ); - kernelType kernel( numPhases, rankOffset, hasCapPressure, stencilWrapper, dofNumberAccessor, - compFlowAccessors, multiFluidAccessors, capPressureAccessors, permeabilityAccessors, - dt, localMatrix, localRhs ); - kernelType::template launch< POLICY >( stencilWrapper.size(), kernel ); + kernelType kernel( numPhases, rankOffset, hasCapPressure, /*upwindingParams.epsC1PPU,*/ stencilWrapper, dofNumberAccessor, + compFlowAccessors, multiFluidAccessors, capPressureAccessors, permeabilityAccessors, + dt, localMatrix, localRhs ); + kernelType::template launch< POLICY >( stencilWrapper.size(), kernel ); + } } ); } }; @@ -920,6 +944,7 @@ class DirichletFaceBasedAssemblyKernel : public FaceBasedAssemblyKernel< NUM_COM : Base( numPhases, rankOffset, hasCapPressure, + //0.0, // no C1-PPU stencilWrapper, dofNumberAccessor, compFlowAccessors, @@ -1305,10 +1330,10 @@ class DirichletFaceBasedAssemblyKernelFactory using FluidType = TYPEOFREF( fluid ); typename FluidType::KernelWrapper const fluidWrapper = fluid.createKernelWrapper(); - isothermalCompositionalMultiphaseBaseKernels::internal::kernelLaunchSelectorCompSwitch( numComps, [&] ( auto NC ) + isothermalCompositionalMultiphaseBaseKernels::internal::kernelLaunchSelectorCompSwitch( numComps, [&]( auto NC ) { integer constexpr NUM_COMP = NC(); - integer constexpr NUM_DOF = NC()+1; + integer constexpr NUM_DOF = NC() + 1; ElementRegionManager::ElementViewAccessor< arrayView1d< globalIndex const > > dofNumberAccessor = elemManager.constructArrayViewAccessor< globalIndex, 1 >( dofKey ); diff --git a/src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseFVM.cpp b/src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseFVM.cpp index f871ebcc01b..e9ea8e0e162 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseFVM.cpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseFVM.cpp @@ -618,8 +618,8 @@ char const faceBcLogMessage[] = "\nThe total number of target faces (including ghost faces) is {}. " "\nNote that if this number is equal to zero, the boundary condition will not be applied on this face set."; -char const incompleteBCLogmessage[] = "SinglePhaseFVM {}: at time {}, one or more Face boundary conditions are not complete. " - "Both pressure and temperature must be specified, one is missing."; +[[maybe_unused]] char const incompleteBCLogmessage[] = "SinglePhaseFVM {}: at time {}, one or more Face boundary conditions are not complete. " + "Both pressure and temperature must be specified, one is missing."; } diff --git a/src/coreComponents/physicsSolvers/fluidFlow/StabilizedCompositionalMultiphaseFVMKernels.hpp b/src/coreComponents/physicsSolvers/fluidFlow/StabilizedCompositionalMultiphaseFVMKernels.hpp index 12ae5b50e8a..aed6b6f1780 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/StabilizedCompositionalMultiphaseFVMKernels.hpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/StabilizedCompositionalMultiphaseFVMKernels.hpp @@ -137,6 +137,7 @@ class FaceBasedAssemblyKernel : public isothermalCompositionalMultiphaseFVMKerne : Base( numPhases, rankOffset, hasCapPressure, + //0.0, // no C1-PPU stencilWrapper, dofNumberAccessor, compFlowAccessors, @@ -337,10 +338,10 @@ class FaceBasedAssemblyKernelFactory arrayView1d< real64 > const & localRhs ) { isothermalCompositionalMultiphaseBaseKernels:: - internal::kernelLaunchSelectorCompSwitch( numComps, [&] ( auto NC ) + internal::kernelLaunchSelectorCompSwitch( numComps, [&]( auto NC ) { integer constexpr NUM_COMP = NC(); - integer constexpr NUM_DOF = NC()+1; + integer constexpr NUM_DOF = NC() + 1; ElementRegionManager::ElementViewAccessor< arrayView1d< globalIndex const > > dofNumberAccessor = elemManager.constructArrayViewAccessor< globalIndex, 1 >( dofKey ); diff --git a/src/coreComponents/physicsSolvers/fluidFlow/ThermalCompositionalMultiphaseFVMKernels.hpp b/src/coreComponents/physicsSolvers/fluidFlow/ThermalCompositionalMultiphaseFVMKernels.hpp index 6edd4287139..ec4a3b5bb79 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/ThermalCompositionalMultiphaseFVMKernels.hpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/ThermalCompositionalMultiphaseFVMKernels.hpp @@ -258,6 +258,7 @@ class FaceBasedAssemblyKernel : public isothermalCompositionalMultiphaseFVMKerne : Base( numPhases, rankOffset, hasCapPressure, + //0.0, // no C1-PPU stencilWrapper, dofNumberAccessor, compFlowAccessors, @@ -637,10 +638,10 @@ class FaceBasedAssemblyKernelFactory arrayView1d< real64 > const & localRhs ) { isothermalCompositionalMultiphaseBaseKernels:: - internal::kernelLaunchSelectorCompSwitch( numComps, [&] ( auto NC ) + internal::kernelLaunchSelectorCompSwitch( numComps, [&]( auto NC ) { integer constexpr NUM_COMP = NC(); - integer constexpr NUM_DOF = NC()+2; + integer constexpr NUM_DOF = NC() + 2; ElementRegionManager::ElementViewAccessor< arrayView1d< globalIndex const > > dofNumberAccessor = elemManager.constructArrayViewAccessor< globalIndex, 1 >( dofKey ); @@ -1081,10 +1082,10 @@ class DirichletFaceBasedAssemblyKernelFactory typename FluidType::KernelWrapper const fluidWrapper = fluid.createKernelWrapper(); isothermalCompositionalMultiphaseBaseKernels:: - internal::kernelLaunchSelectorCompSwitch( numComps, [&] ( auto NC ) + internal::kernelLaunchSelectorCompSwitch( numComps, [&]( auto NC ) { integer constexpr NUM_COMP = NC(); - integer constexpr NUM_DOF = NC()+2; + integer constexpr NUM_DOF = NC() + 2; ElementRegionManager::ElementViewAccessor< arrayView1d< globalIndex const > > dofNumberAccessor = elemManager.constructArrayViewAccessor< globalIndex, 1 >( dofKey ); diff --git a/src/coreComponents/physicsSolvers/wavePropagation/AcousticFirstOrderWaveEquationSEM.cpp b/src/coreComponents/physicsSolvers/wavePropagation/AcousticFirstOrderWaveEquationSEM.cpp index 82632d445a3..5b89220c17d 100644 --- a/src/coreComponents/physicsSolvers/wavePropagation/AcousticFirstOrderWaveEquationSEM.cpp +++ b/src/coreComponents/physicsSolvers/wavePropagation/AcousticFirstOrderWaveEquationSEM.cpp @@ -70,6 +70,16 @@ AcousticFirstOrderWaveEquationSEM::AcousticFirstOrderWaveEquationSEM( const std: setSizedFromParent( 0 ). setDescription( "Element containing the receivers" ); + registerWrapper( viewKeyStruct::sourceRegionString(), &m_sourceRegion ). + setInputFlag( InputFlags::FALSE ). + setSizedFromParent( 0 ). + setDescription( "Region containing the sources" ); + + registerWrapper( viewKeyStruct::receiverRegionString(), &m_receiverRegion ). + setInputFlag( InputFlags::FALSE ). + setSizedFromParent( 0 ). + setDescription( "Region containing the receivers" ); + } AcousticFirstOrderWaveEquationSEM::~AcousticFirstOrderWaveEquationSEM() @@ -145,9 +155,9 @@ void AcousticFirstOrderWaveEquationSEM::postProcessInput() m_uyNp1AtReceivers.resize( m_nsamplesSeismoTrace, numReceiversGlobal ); m_uzNp1AtReceivers.resize( m_nsamplesSeismoTrace, numReceiversGlobal ); m_sourceElem.resize( numSourcesGlobal ); + m_sourceRegion.resize( numSourcesGlobal ); m_rcvElem.resize( numReceiversGlobal ); - - + m_receiverRegion.resize( numReceiversGlobal ); } void AcousticFirstOrderWaveEquationSEM::precomputeSourceAndReceiverTerm( MeshLevel & mesh, arrayView1d< string const > const & regionNames ) @@ -165,6 +175,7 @@ void AcousticFirstOrderWaveEquationSEM::precomputeSourceAndReceiverTerm( MeshLev arrayView2d< real64 > const sourceConstants = m_sourceConstants.toView(); arrayView1d< localIndex > const sourceIsAccessible = m_sourceIsAccessible.toView(); arrayView1d< localIndex > const sourceElem = m_sourceElem.toView(); + arrayView1d< localIndex > const sourceRegion = m_sourceRegion.toView(); sourceNodeIds.setValues< EXEC_POLICY >( -1 ); sourceConstants.setValues< EXEC_POLICY >( -1 ); sourceIsAccessible.zero(); @@ -174,6 +185,7 @@ void AcousticFirstOrderWaveEquationSEM::precomputeSourceAndReceiverTerm( MeshLev arrayView2d< real64 > const receiverConstants = m_receiverConstants.toView(); arrayView1d< localIndex > const receiverIsLocal = m_receiverIsLocal.toView(); arrayView1d< localIndex > const rcvElem = m_rcvElem.toView(); + arrayView1d< localIndex > const receiverRegion = m_receiverRegion.toView(); receiverNodeIds.setValues< EXEC_POLICY >( -1 ); receiverConstants.setValues< EXEC_POLICY >( -1 ); receiverIsLocal.zero(); @@ -192,8 +204,7 @@ void AcousticFirstOrderWaveEquationSEM::precomputeSourceAndReceiverTerm( MeshLev } } - - mesh.getElemManager().forElementSubRegions< CellElementSubRegion >( regionNames, [&]( localIndex const, + mesh.getElemManager().forElementSubRegions< CellElementSubRegion >( regionNames, [&]( localIndex const regionIndex, CellElementSubRegion & elementSubRegion ) { GEOS_THROW_IF( elementSubRegion.getElementType() != ElementType::Hexahedron, @@ -218,6 +229,7 @@ void AcousticFirstOrderWaveEquationSEM::precomputeSourceAndReceiverTerm( MeshLev PrecomputeSourceAndReceiverKernel:: launch< EXEC_POLICY, FE_TYPE > ( elementSubRegion.size(), + regionIndex, numNodesPerElem, numFacesPerElem, X, @@ -232,11 +244,13 @@ void AcousticFirstOrderWaveEquationSEM::precomputeSourceAndReceiverTerm( MeshLev sourceElem, sourceNodeIds, sourceConstants, + sourceRegion, receiverCoordinates, receiverIsLocal, rcvElem, receiverNodeIds, receiverConstants, + receiverRegion, sourceValue, dt, timeSourceFrequency, @@ -439,6 +453,7 @@ real64 AcousticFirstOrderWaveEquationSEM::explicitStepInternal( real64 const & t arrayView2d< real64 const > const sourceConstants = m_sourceConstants.toView(); arrayView1d< localIndex const > const sourceIsAccessible = m_sourceIsAccessible.toView(); arrayView1d< localIndex const > const sourceElem = m_sourceElem.toView(); + arrayView1d< localIndex const > const sourceRegion = m_sourceRegion.toView(); arrayView2d< real32 const > const sourceValue = m_sourceValue.toView(); GEOS_LOG_RANK_0_IF( dt < epsilonLoc, "Warning! Value for dt: " << dt << "s is smaller than local threshold: " << epsilonLoc ); @@ -459,7 +474,7 @@ real64 AcousticFirstOrderWaveEquationSEM::explicitStepInternal( real64 const & t arrayView1d< real32 > const rhs = nodeManager.getField< wavesolverfields::ForcingRHS >(); - mesh.getElemManager().forElementSubRegions< CellElementSubRegion >( regionNames, [&]( localIndex const, + mesh.getElemManager().forElementSubRegions< CellElementSubRegion >( regionNames, [&]( localIndex const regionIndex, CellElementSubRegion & elementSubRegion ) { arrayView2d< localIndex const, cells::NODE_MAP_USD > const & elemsToNodes = elementSubRegion.nodeList(); @@ -491,6 +506,7 @@ real64 AcousticFirstOrderWaveEquationSEM::explicitStepInternal( real64 const & t PressureComputation< FE_TYPE > kernel2( finiteElement ); kernel2.template launch< EXEC_POLICY, ATOMIC_POLICY > ( elementSubRegion.size(), + regionIndex, nodeManager.size(), X, elemsToNodes, @@ -503,6 +519,7 @@ real64 AcousticFirstOrderWaveEquationSEM::explicitStepInternal( real64 const & t sourceValue, sourceIsAccessible, sourceElem, + sourceRegion, dt, cycleNumber, p_np1 ); @@ -511,9 +528,9 @@ real64 AcousticFirstOrderWaveEquationSEM::explicitStepInternal( real64 const & t arrayView2d< real32 > const uyReceivers = m_uyNp1AtReceivers.toView(); arrayView2d< real32 > const uzReceivers = m_uzNp1AtReceivers.toView(); - compute2dVariableAllSeismoTraces( time_n, dt, velocity_x, velocity_x, uxReceivers ); - compute2dVariableAllSeismoTraces( time_n, dt, velocity_y, velocity_y, uyReceivers ); - compute2dVariableAllSeismoTraces( time_n, dt, velocity_z, velocity_z, uzReceivers ); + compute2dVariableAllSeismoTraces( regionIndex, time_n, dt, velocity_x, velocity_x, uxReceivers ); + compute2dVariableAllSeismoTraces( regionIndex, time_n, dt, velocity_y, velocity_y, uyReceivers ); + compute2dVariableAllSeismoTraces( regionIndex, time_n, dt, velocity_z, velocity_z, uzReceivers ); } ); @@ -550,7 +567,7 @@ void AcousticFirstOrderWaveEquationSEM::cleanup( real64 const time_n, integer co { NodeManager & nodeManager = mesh.getNodeManager(); arrayView1d< real32 const > const p_np1 = nodeManager.getField< wavesolverfields::Pressure_np1 >(); - mesh.getElemManager().forElementSubRegions< CellElementSubRegion >( regionNames, [&]( localIndex const, + mesh.getElemManager().forElementSubRegions< CellElementSubRegion >( regionNames, [&]( localIndex const regionIndex, CellElementSubRegion & elementSubRegion ) { arrayView2d< real32 > const velocity_x = elementSubRegion.getField< wavesolverfields::Velocity_x >(); @@ -561,9 +578,9 @@ void AcousticFirstOrderWaveEquationSEM::cleanup( real64 const time_n, integer co arrayView2d< real32 > const uyReceivers = m_uyNp1AtReceivers.toView(); arrayView2d< real32 > const uzReceivers = m_uzNp1AtReceivers.toView(); - compute2dVariableAllSeismoTraces( time_n, 0, velocity_x, velocity_x, uxReceivers ); - compute2dVariableAllSeismoTraces( time_n, 0, velocity_y, velocity_y, uyReceivers ); - compute2dVariableAllSeismoTraces( time_n, 0, velocity_z, velocity_z, uzReceivers ); + compute2dVariableAllSeismoTraces( regionIndex, time_n, 0, velocity_x, velocity_x, uxReceivers ); + compute2dVariableAllSeismoTraces( regionIndex, time_n, 0, velocity_y, velocity_y, uyReceivers ); + compute2dVariableAllSeismoTraces( regionIndex, time_n, 0, velocity_z, velocity_z, uzReceivers ); } ); arrayView2d< real32 > const pReceivers = m_pressureNp1AtReceivers.toView(); @@ -594,7 +611,8 @@ void AcousticFirstOrderWaveEquationSEM::computeAllSeismoTraces( real64 const tim } } -void AcousticFirstOrderWaveEquationSEM::compute2dVariableAllSeismoTraces( real64 const time_n, +void AcousticFirstOrderWaveEquationSEM::compute2dVariableAllSeismoTraces( localIndex const regionIndex, + real64 const time_n, real64 const dt, arrayView2d< real32 const > const var_np1, arrayView2d< real32 const > const var_n, @@ -605,7 +623,7 @@ void AcousticFirstOrderWaveEquationSEM::compute2dVariableAllSeismoTraces( real64 (timeSeismo = m_dtSeismoTrace*indexSeismoTrace) <= (time_n + epsilonLoc) && indexSeismoTrace < m_nsamplesSeismoTrace; indexSeismoTrace++ ) { - WaveSolverUtils::compute2dVariableSeismoTrace( time_n, dt, timeSeismo, indexSeismoTrace, m_rcvElem, m_receiverConstants, m_receiverIsLocal, m_nsamplesSeismoTrace, + WaveSolverUtils::compute2dVariableSeismoTrace( time_n, dt, regionIndex, m_receiverRegion, timeSeismo, indexSeismoTrace, m_rcvElem, m_receiverConstants, m_receiverIsLocal, m_nsamplesSeismoTrace, m_outputSeismoTrace, var_np1, var_n, varAtReceivers ); } diff --git a/src/coreComponents/physicsSolvers/wavePropagation/AcousticFirstOrderWaveEquationSEM.hpp b/src/coreComponents/physicsSolvers/wavePropagation/AcousticFirstOrderWaveEquationSEM.hpp index 54171840aae..0a77f4089eb 100644 --- a/src/coreComponents/physicsSolvers/wavePropagation/AcousticFirstOrderWaveEquationSEM.hpp +++ b/src/coreComponents/physicsSolvers/wavePropagation/AcousticFirstOrderWaveEquationSEM.hpp @@ -102,7 +102,8 @@ class AcousticFirstOrderWaveEquationSEM : public WaveSolverBase * @param var_n the field values at time_n * @param var_receivers the array holding the trace values, where the output is written */ - virtual void compute2dVariableAllSeismoTraces( real64 const time_n, + virtual void compute2dVariableAllSeismoTraces( localIndex const regionIndex, + real64 const time_n, real64 const dt, arrayView2d< real32 const > const var_np1, arrayView2d< real32 const > const var_n, @@ -131,7 +132,9 @@ class AcousticFirstOrderWaveEquationSEM : public WaveSolverBase static constexpr char const * uzNp1AtReceiversString() { return "uzNp1AtReceivers"; } static constexpr char const * sourceElemString() { return "sourceElem"; } + static constexpr char const * sourceRegionString() { return "sourceRegion"; } static constexpr char const * receiverElemString() { return "rcvElem"; } + static constexpr char const * receiverRegionString() { return "receiverRegion"; } } waveEquationViewKeys; @@ -192,6 +195,12 @@ class AcousticFirstOrderWaveEquationSEM : public WaveSolverBase /// Array containing the elements which contain a source array1d< localIndex > m_sourceElem; + /// Array containing the elements which contain the region which the source belongs + array1d< localIndex > m_sourceRegion; + + /// Array containing the elements which contain the region which the receiver belongs + array1d< localIndex > m_receiverRegion; + /// Array containing the elements which contain a receiver array1d< localIndex > m_rcvElem; diff --git a/src/coreComponents/physicsSolvers/wavePropagation/AcousticFirstOrderWaveEquationSEMKernel.hpp b/src/coreComponents/physicsSolvers/wavePropagation/AcousticFirstOrderWaveEquationSEMKernel.hpp index e7ac5d9984e..e6d69a09fee 100644 --- a/src/coreComponents/physicsSolvers/wavePropagation/AcousticFirstOrderWaveEquationSEMKernel.hpp +++ b/src/coreComponents/physicsSolvers/wavePropagation/AcousticFirstOrderWaveEquationSEMKernel.hpp @@ -66,6 +66,7 @@ struct PrecomputeSourceAndReceiverKernel template< typename EXEC_POLICY, typename FE_TYPE > static void launch( localIndex const size, + localIndex const regionIndex, localIndex const numNodesPerElem, localIndex const numFacesPerElem, arrayView2d< real64 const, nodes::REFERENCE_POSITION_USD > const X, @@ -80,11 +81,13 @@ struct PrecomputeSourceAndReceiverKernel arrayView1d< localIndex > const sourceElem, arrayView2d< localIndex > const sourceNodeIds, arrayView2d< real64 > const sourceConstants, + arrayView1d< localIndex > const sourceRegion, arrayView2d< real64 const > const receiverCoordinates, arrayView1d< localIndex > const receiverIsLocal, arrayView1d< localIndex > const rcvElem, arrayView2d< localIndex > const receiverNodeIds, arrayView2d< real64 > const receiverConstants, + arrayView1d< localIndex > const receiverRegion, arrayView2d< real32 > const sourceValue, real64 const dt, real32 const timeSourceFrequency, @@ -127,6 +130,7 @@ struct PrecomputeSourceAndReceiverKernel sourceIsAccessible[isrc] = 1; sourceElem[isrc] = k; + sourceRegion[isrc] = regionIndex; real64 Ntest[FE_TYPE::numNodes]; FE_TYPE::calcN( coordsOnRefElem, Ntest ); @@ -174,6 +178,7 @@ struct PrecomputeSourceAndReceiverKernel coordsOnRefElem ); receiverIsLocal[ircv] = 1; rcvElem[ircv] = k; + receiverRegion[ircv] = regionIndex; real64 Ntest[FE_TYPE::numNodes]; FE_TYPE::calcN( coordsOnRefElem, Ntest ); @@ -443,6 +448,7 @@ struct PressureComputation * @tparam EXEC_POLICY the execution policy * @tparam ATOMIC_POLICY the atomic policy * @param[in] size the number of cells in the subRegion + * @param[in] regionIndex Index of the subregion * @param[in] size_node the number of nodes in the subRegion * @param[in] X coordinates of the nodes * @param[in] elemsToNodes map from element to nodes @@ -463,6 +469,7 @@ struct PressureComputation template< typename EXEC_POLICY, typename ATOMIC_POLICY > void launch( localIndex const size, + localIndex const regionIndex, localIndex const size_node, arrayView2d< real64 const, nodes::REFERENCE_POSITION_USD > const X, arrayView2d< localIndex const, cells::NODE_MAP_USD > const elemsToNodes, @@ -475,6 +482,7 @@ struct PressureComputation arrayView2d< real32 const > const sourceValue, arrayView1d< localIndex const > const sourceIsAccessible, arrayView1d< localIndex const > const sourceElem, + arrayView1d< localIndex const > const sourceRegion, real64 const dt, integer const cycleNumber, arrayView1d< real32 > const p_np1 ) @@ -552,7 +560,7 @@ struct PressureComputation { if( sourceIsAccessible[isrc] == 1 ) { - if( sourceElem[isrc]==k ) + if( sourceElem[isrc]==k && sourceRegion[isrc] == regionIndex ) { for( localIndex i = 0; i < numNodesPerElem; ++i ) { diff --git a/src/coreComponents/physicsSolvers/wavePropagation/ElasticFirstOrderWaveEquationSEM.cpp b/src/coreComponents/physicsSolvers/wavePropagation/ElasticFirstOrderWaveEquationSEM.cpp index 3f36f51b2d8..cc7d5fdd214 100644 --- a/src/coreComponents/physicsSolvers/wavePropagation/ElasticFirstOrderWaveEquationSEM.cpp +++ b/src/coreComponents/physicsSolvers/wavePropagation/ElasticFirstOrderWaveEquationSEM.cpp @@ -94,6 +94,16 @@ ElasticFirstOrderWaveEquationSEM::ElasticFirstOrderWaveEquationSEM( const std::s setSizedFromParent( 0 ). setDescription( "Element containing the receivers" ); + registerWrapper( viewKeyStruct::sourceRegionString(), &m_sourceRegion ). + setInputFlag( InputFlags::FALSE ). + setSizedFromParent( 0 ). + setDescription( "Region containing the sources" ); + + registerWrapper( viewKeyStruct::receiverRegionString(), &m_receiverRegion ). + setInputFlag( InputFlags::FALSE ). + setSizedFromParent( 0 ). + setDescription( "Region containing the receivers" ); + } ElasticFirstOrderWaveEquationSEM::~ElasticFirstOrderWaveEquationSEM() @@ -179,9 +189,11 @@ void ElasticFirstOrderWaveEquationSEM::postProcessInput() localIndex const numSourcesGlobal = m_sourceCoordinates.size( 0 ); m_sourceElem.resize( numSourcesGlobal ); + m_sourceRegion.resize( numSourcesGlobal ); localIndex const numReceiversGlobal = m_receiverCoordinates.size( 0 ); m_rcvElem.resize( numReceiversGlobal ); + m_receiverRegion.resize( numReceiversGlobal ); m_displacementxNp1AtReceivers.resize( m_nsamplesSeismoTrace, numReceiversGlobal ); m_displacementyNp1AtReceivers.resize( m_nsamplesSeismoTrace, numReceiversGlobal ); @@ -212,6 +224,7 @@ void ElasticFirstOrderWaveEquationSEM::precomputeSourceAndReceiverTerm( MeshLeve arrayView2d< real64 > const sourceConstants = m_sourceConstants.toView(); arrayView1d< localIndex > const sourceIsAccessible = m_sourceIsAccessible.toView(); arrayView1d< localIndex > const sourceElem = m_sourceElem.toView(); + arrayView1d< localIndex > const sourceRegion = m_sourceRegion.toView(); sourceNodeIds.setValues< serialPolicy >( -1 ); sourceConstants.setValues< serialPolicy >( -1 ); sourceIsAccessible.zero(); @@ -221,6 +234,7 @@ void ElasticFirstOrderWaveEquationSEM::precomputeSourceAndReceiverTerm( MeshLeve arrayView2d< real64 > const receiverConstants = m_receiverConstants.toView(); arrayView1d< localIndex > const receiverIsLocal = m_receiverIsLocal.toView(); arrayView1d< localIndex > const rcvElem = m_rcvElem.toView(); + arrayView1d< localIndex > const receiverRegion = m_receiverRegion.toView(); receiverNodeIds.setValues< serialPolicy >( -1 ); receiverConstants.setValues< serialPolicy >( -1 ); @@ -241,7 +255,7 @@ void ElasticFirstOrderWaveEquationSEM::precomputeSourceAndReceiverTerm( MeshLeve } } - mesh.getElemManager().forElementSubRegions< CellElementSubRegion >( regionNames, [&]( localIndex const, + mesh.getElemManager().forElementSubRegions< CellElementSubRegion >( regionNames, [&]( localIndex const regionIndex, CellElementSubRegion & elementSubRegion ) { @@ -267,6 +281,7 @@ void ElasticFirstOrderWaveEquationSEM::precomputeSourceAndReceiverTerm( MeshLeve PrecomputeSourceAndReceiverKernel:: launch< EXEC_POLICY, FE_TYPE > ( elementSubRegion.size(), + regionIndex, numNodesPerElem, numFacesPerElem, X, @@ -281,11 +296,13 @@ void ElasticFirstOrderWaveEquationSEM::precomputeSourceAndReceiverTerm( MeshLeve sourceElem, sourceNodeIds, sourceConstants, + sourceRegion, receiverCoordinates, receiverIsLocal, rcvElem, receiverNodeIds, receiverConstants, + receiverRegion, sourceValue, dt, timeSourceFrequency, @@ -505,6 +522,7 @@ real64 ElasticFirstOrderWaveEquationSEM::explicitStepInternal( real64 const & ti arrayView2d< real64 const > const sourceConstants = m_sourceConstants.toView(); arrayView1d< localIndex const > const sourceIsAccessible = m_sourceIsAccessible.toView(); arrayView1d< localIndex const > const sourceElem = m_sourceElem.toView(); + arrayView1d< localIndex const > const sourceRegion = m_sourceRegion.toView(); arrayView2d< real32 const > const sourceValue = m_sourceValue.toView(); @@ -528,7 +546,7 @@ real64 ElasticFirstOrderWaveEquationSEM::explicitStepInternal( real64 const & ti arrayView1d< real32 > const uy_np1 = nodeManager.getField< wavesolverfields::Displacementy_np1 >(); arrayView1d< real32 > const uz_np1 = nodeManager.getField< wavesolverfields::Displacementz_np1 >(); - mesh.getElemManager().forElementSubRegions< CellElementSubRegion >( regionNames, [&]( localIndex const, + mesh.getElemManager().forElementSubRegions< CellElementSubRegion >( regionNames, [&]( localIndex const regionIndex, CellElementSubRegion & elementSubRegion ) { @@ -559,6 +577,7 @@ real64 ElasticFirstOrderWaveEquationSEM::explicitStepInternal( real64 const & ti StressComputation< FE_TYPE > kernel( finiteElement ); kernel.template launch< EXEC_POLICY, ATOMIC_POLICY > ( elementSubRegion.size(), + regionIndex, X, elemsToNodes, ux_np1, @@ -572,6 +591,7 @@ real64 ElasticFirstOrderWaveEquationSEM::explicitStepInternal( real64 const & ti sourceConstants, sourceIsAccessible, sourceElem, + sourceRegion, sourceValue, dt, cycleNumber, @@ -614,12 +634,12 @@ real64 ElasticFirstOrderWaveEquationSEM::explicitStepInternal( real64 const & ti arrayView2d< real32 > const sigmaxzReceivers = m_sigmaxzNp1AtReceivers.toView(); arrayView2d< real32 > const sigmayzReceivers = m_sigmayzNp1AtReceivers.toView(); - compute2dVariableAllSeismoTraces( time_n, dt, stressxx, stressxx, sigmaxxReceivers ); - compute2dVariableAllSeismoTraces( time_n, dt, stressyy, stressyy, sigmayyReceivers ); - compute2dVariableAllSeismoTraces( time_n, dt, stresszz, stresszz, sigmazzReceivers ); - compute2dVariableAllSeismoTraces( time_n, dt, stressxy, stressxy, sigmaxyReceivers ); - compute2dVariableAllSeismoTraces( time_n, dt, stressxz, stressxz, sigmaxzReceivers ); - compute2dVariableAllSeismoTraces( time_n, dt, stressyz, stressyz, sigmayzReceivers ); + compute2dVariableAllSeismoTraces( regionIndex, time_n, dt, stressxx, stressxx, sigmaxxReceivers ); + compute2dVariableAllSeismoTraces( regionIndex, time_n, dt, stressyy, stressyy, sigmayyReceivers ); + compute2dVariableAllSeismoTraces( regionIndex, time_n, dt, stresszz, stresszz, sigmazzReceivers ); + compute2dVariableAllSeismoTraces( regionIndex, time_n, dt, stressxy, stressxy, sigmaxyReceivers ); + compute2dVariableAllSeismoTraces( regionIndex, time_n, dt, stressxz, stressxz, sigmaxzReceivers ); + compute2dVariableAllSeismoTraces( regionIndex, time_n, dt, stressyz, stressyz, sigmayzReceivers ); } ); @@ -673,7 +693,7 @@ void ElasticFirstOrderWaveEquationSEM::cleanup( real64 const time_n, { NodeManager & nodeManager = mesh.getNodeManager(); - mesh.getElemManager().forElementSubRegions< CellElementSubRegion >( regionNames, [&]( localIndex const, + mesh.getElemManager().forElementSubRegions< CellElementSubRegion >( regionNames, [&]( localIndex const regionIndex, CellElementSubRegion & elementSubRegion ) { arrayView2d< real32 const > const stressxx = elementSubRegion.getField< wavesolverfields::Stresstensorxx >(); @@ -690,12 +710,12 @@ void ElasticFirstOrderWaveEquationSEM::cleanup( real64 const time_n, arrayView2d< real32 > const sigmaxzReceivers = m_sigmaxzNp1AtReceivers.toView(); arrayView2d< real32 > const sigmayzReceivers = m_sigmayzNp1AtReceivers.toView(); - compute2dVariableAllSeismoTraces( time_n, 0, stressxx, stressxx, sigmaxxReceivers ); - compute2dVariableAllSeismoTraces( time_n, 0, stressyy, stressyy, sigmayyReceivers ); - compute2dVariableAllSeismoTraces( time_n, 0, stresszz, stresszz, sigmazzReceivers ); - compute2dVariableAllSeismoTraces( time_n, 0, stressxy, stressxy, sigmaxyReceivers ); - compute2dVariableAllSeismoTraces( time_n, 0, stressxz, stressxz, sigmaxzReceivers ); - compute2dVariableAllSeismoTraces( time_n, 0, stressyz, stressyz, sigmayzReceivers ); + compute2dVariableAllSeismoTraces( regionIndex, time_n, 0, stressxx, stressxx, sigmaxxReceivers ); + compute2dVariableAllSeismoTraces( regionIndex, time_n, 0, stressyy, stressyy, sigmayyReceivers ); + compute2dVariableAllSeismoTraces( regionIndex, time_n, 0, stresszz, stresszz, sigmazzReceivers ); + compute2dVariableAllSeismoTraces( regionIndex, time_n, 0, stressxy, stressxy, sigmaxyReceivers ); + compute2dVariableAllSeismoTraces( regionIndex, time_n, 0, stressxz, stressxz, sigmaxzReceivers ); + compute2dVariableAllSeismoTraces( regionIndex, time_n, 0, stressyz, stressyz, sigmayzReceivers ); } ); arrayView1d< real32 > const ux_np1 = nodeManager.getField< wavesolverfields::Displacementx_np1 >(); arrayView1d< real32 > const uy_np1 = nodeManager.getField< wavesolverfields::Displacementy_np1 >(); @@ -737,7 +757,8 @@ void ElasticFirstOrderWaveEquationSEM::computeAllSeismoTraces( real64 const time } } -void ElasticFirstOrderWaveEquationSEM::compute2dVariableAllSeismoTraces( real64 const time_n, +void ElasticFirstOrderWaveEquationSEM::compute2dVariableAllSeismoTraces( localIndex const regionIndex, + real64 const time_n, real64 const dt, arrayView2d< real32 const > const var_np1, arrayView2d< real32 const > const var_n, @@ -748,7 +769,7 @@ void ElasticFirstOrderWaveEquationSEM::compute2dVariableAllSeismoTraces( real64 (timeSeismo = m_dtSeismoTrace*indexSeismoTrace) <= (time_n + epsilonLoc) && indexSeismoTrace < m_nsamplesSeismoTrace; indexSeismoTrace++ ) { - WaveSolverUtils::compute2dVariableSeismoTrace( time_n, dt, timeSeismo, indexSeismoTrace, m_rcvElem, m_receiverConstants, m_receiverIsLocal, + WaveSolverUtils::compute2dVariableSeismoTrace( time_n, dt, regionIndex, m_receiverRegion, timeSeismo, indexSeismoTrace, m_rcvElem, m_receiverConstants, m_receiverIsLocal, m_nsamplesSeismoTrace, m_outputSeismoTrace, var_np1, var_n, varAtReceivers ); } } diff --git a/src/coreComponents/physicsSolvers/wavePropagation/ElasticFirstOrderWaveEquationSEM.hpp b/src/coreComponents/physicsSolvers/wavePropagation/ElasticFirstOrderWaveEquationSEM.hpp index b04b407c9a4..193fd30939d 100644 --- a/src/coreComponents/physicsSolvers/wavePropagation/ElasticFirstOrderWaveEquationSEM.hpp +++ b/src/coreComponents/physicsSolvers/wavePropagation/ElasticFirstOrderWaveEquationSEM.hpp @@ -101,7 +101,8 @@ class ElasticFirstOrderWaveEquationSEM : public WaveSolverBase * @param var_n the field values at time_n * @param varAtreceivers the array holding the trace values, where the output is written */ - virtual void compute2dVariableAllSeismoTraces( real64 const time_n, + virtual void compute2dVariableAllSeismoTraces( localIndex const regionIndex, + real64 const time_n, real64 const dt, arrayView2d< real32 const > const var_np1, arrayView2d< real32 const > const var_n, @@ -133,7 +134,9 @@ class ElasticFirstOrderWaveEquationSEM : public WaveSolverBase static constexpr char const * sigmayzNp1AtReceiversString() { return "sigmayzNp1AtReceivers"; } static constexpr char const * sourceElemString() { return "sourceElem"; } + static constexpr char const * sourceRegionString() { return "sourceRegion"; } static constexpr char const * receiverElemString() { return "rcvElem"; } + static constexpr char const * receiverRegionString() { return "receiverRegion"; } } waveEquationViewKeys; @@ -210,9 +213,15 @@ class ElasticFirstOrderWaveEquationSEM : public WaveSolverBase /// Array containing the elements which contain a source array1d< localIndex > m_sourceElem; + /// Array containing the elements which contain the region which the source belongs + array1d< localIndex > m_sourceRegion; + /// Array containing the elements which contain a receiver array1d< localIndex > m_rcvElem; + /// Array containing the elements which contain the region which the receiver belongs + array1d< localIndex > m_receiverRegion; + }; } /* namespace geos */ diff --git a/src/coreComponents/physicsSolvers/wavePropagation/ElasticFirstOrderWaveEquationSEMKernel.hpp b/src/coreComponents/physicsSolvers/wavePropagation/ElasticFirstOrderWaveEquationSEMKernel.hpp index dc9ce711f9a..59517914bf9 100644 --- a/src/coreComponents/physicsSolvers/wavePropagation/ElasticFirstOrderWaveEquationSEMKernel.hpp +++ b/src/coreComponents/physicsSolvers/wavePropagation/ElasticFirstOrderWaveEquationSEMKernel.hpp @@ -64,6 +64,7 @@ struct PrecomputeSourceAndReceiverKernel template< typename EXEC_POLICY, typename FE_TYPE > static void launch( localIndex const size, + localIndex const regionIndex, localIndex const numNodesPerElem, localIndex const numFacesPerElem, arrayView2d< real64 const, nodes::REFERENCE_POSITION_USD > const X, @@ -78,11 +79,13 @@ struct PrecomputeSourceAndReceiverKernel arrayView1d< localIndex > const sourceElem, arrayView2d< localIndex > const sourceNodeIds, arrayView2d< real64 > const sourceConstants, + arrayView1d< localIndex > const sourceRegion, arrayView2d< real64 const > const receiverCoordinates, arrayView1d< localIndex > const receiverIsLocal, arrayView1d< localIndex > const rcvElem, arrayView2d< localIndex > const receiverNodeIds, arrayView2d< real64 > const receiverConstants, + arrayView1d< localIndex > const receiverRegion, arrayView2d< real32 > const sourceValue, real64 const dt, real32 const timeSourceFrequency, @@ -124,6 +127,7 @@ struct PrecomputeSourceAndReceiverKernel coordsOnRefElem ); sourceIsAccessible[isrc] = 1; sourceElem[isrc] = k; + sourceRegion[isrc] = regionIndex; real64 Ntest[FE_TYPE::numNodes]; FE_TYPE::calcN( coordsOnRefElem, Ntest ); @@ -171,6 +175,7 @@ struct PrecomputeSourceAndReceiverKernel coordsOnRefElem ); receiverIsLocal[ircv] = 1; rcvElem[ircv] = k; + receiverRegion[ircv] = regionIndex; real64 Ntest[FE_TYPE::numNodes]; FE_TYPE::calcN( coordsOnRefElem, Ntest ); @@ -346,6 +351,7 @@ struct StressComputation template< typename EXEC_POLICY, typename ATOMIC_POLICY > void launch( localIndex const size, + localIndex const regionIndex, arrayView2d< real64 const, nodes::REFERENCE_POSITION_USD > const X, arrayView2d< localIndex const, cells::NODE_MAP_USD > const elemsToNodes, arrayView1d< real32 const > const ux_np1, @@ -359,6 +365,7 @@ struct StressComputation arrayView2d< real64 const > const sourceConstants, arrayView1d< localIndex const > const sourceIsLocal, arrayView1d< localIndex const > const sourceElem, + arrayView1d< localIndex const > const sourceRegion, arrayView2d< real32 const > const sourceValue, real64 const dt, integer const cycleNumber, @@ -480,7 +487,7 @@ struct StressComputation { if( sourceIsLocal[isrc] == 1 ) { - if( sourceElem[isrc]==k ) + if( sourceElem[isrc]==k && sourceRegion[isrc] == regionIndex ) { for( localIndex i = 0; i < numNodesPerElem; ++i ) { diff --git a/src/coreComponents/physicsSolvers/wavePropagation/WaveSolverUtils.hpp b/src/coreComponents/physicsSolvers/wavePropagation/WaveSolverUtils.hpp index adfcdde408b..c2eeb9a9f7a 100644 --- a/src/coreComponents/physicsSolvers/wavePropagation/WaveSolverUtils.hpp +++ b/src/coreComponents/physicsSolvers/wavePropagation/WaveSolverUtils.hpp @@ -130,6 +130,8 @@ struct WaveSolverUtils static void compute2dVariableSeismoTrace( real64 const time_n, real64 const dt, + localIndex const regionIndex, + arrayView1d< localIndex const > const receiverRegion, real64 const timeSeismo, localIndex iSeismo, arrayView1d< localIndex const > const rcvElem, @@ -152,16 +154,19 @@ struct WaveSolverUtils { if( receiverIsLocal[ircv] == 1 ) { - varAtReceivers[iSeismo][ircv] = 0.0; - real32 vtmp_np1 = 0.0; - real32 vtmp_n = 0.0; - for( localIndex inode = 0; inode < receiverConstants.size( 1 ); ++inode ) + if( receiverRegion[ircv] == regionIndex ) { - vtmp_np1 += var_np1[rcvElem[ircv]][inode] * receiverConstants[ircv][inode]; - vtmp_n += var_n[rcvElem[ircv]][inode] * receiverConstants[ircv][inode]; + varAtReceivers[iSeismo][ircv] = 0.0; + real32 vtmp_np1 = 0.0; + real32 vtmp_n = 0.0; + for( localIndex inode = 0; inode < receiverConstants.size( 1 ); ++inode ) + { + vtmp_np1 += var_np1[rcvElem[ircv]][inode] * receiverConstants[ircv][inode]; + vtmp_n += var_n[rcvElem[ircv]][inode] * receiverConstants[ircv][inode]; + } + // linear interpolation between the pressure value at time_n and time_(n+1) + varAtReceivers[iSeismo][ircv] = a1*vtmp_n + a2*vtmp_np1; } - // linear interpolation between the pressure value at time_n and time_(n+1) - varAtReceivers[iSeismo][ircv] = a1*vtmp_n + a2*vtmp_np1; } } ); } @@ -170,29 +175,30 @@ struct WaveSolverUtils // Output will then only be done via the previous code. if( iSeismo == nsamplesSeismoTrace - 1 ) { - forAll< serialPolicy >( receiverConstants.size( 0 ), [=] ( localIndex const ircv ) + if( outputSeismoTrace == 1 ) { - if( outputSeismoTrace == 1 ) + forAll< serialPolicy >( receiverConstants.size( 0 ), [=] ( localIndex const ircv ) { if( receiverIsLocal[ircv] == 1 ) { // Note: this "manual" output to file is temporary // It should be removed as soon as we can use TimeHistory to output data not registered on the mesh // TODO: remove saveSeismo and replace with TimeHistory - std::ofstream f( GEOS_FMT( "seismoTraceReceiver{:03}.txt", ircv ), std::ios::app ); - for( localIndex iSample = 0; iSample < nsamplesSeismoTrace; ++iSample ) + if( receiverRegion[ircv] == regionIndex ) { - f << iSample << " " << varAtReceivers[iSample][ircv] << std::endl; + std::ofstream f( GEOS_FMT( "seismoTraceReceiver{:03}.txt", ircv ), std::ios::app ); + for( localIndex iSample = 0; iSample < nsamplesSeismoTrace; ++iSample ) + { + f << iSample << " " << varAtReceivers[iSample][ircv] << std::endl; + } + f.close(); } - f.close(); } - } - } ); + } ); + } } - } - /** * @brief Check if the source point is inside an element or not */ diff --git a/src/coreComponents/schema/docs/AcousticFirstOrderSEM_other.rst b/src/coreComponents/schema/docs/AcousticFirstOrderSEM_other.rst index 91b6085a473..37156e3e38a 100644 --- a/src/coreComponents/schema/docs/AcousticFirstOrderSEM_other.rst +++ b/src/coreComponents/schema/docs/AcousticFirstOrderSEM_other.rst @@ -10,10 +10,12 @@ pressureNp1AtReceivers real32_array2d rcvElem integer_array Element containing the receivers receiverIsLocal integer_array Flag that indicates whether the receiver is local to this MPI rank receiverNodeIds integer_array2d Indices of the nodes (in the right order) for each receiver point +receiverRegion integer_array Region containing the receivers sourceConstants real64_array2d Constant part of the receiver for the nodes listed in m_receiverNodeIds sourceElem integer_array Element containing the sources sourceIsAccessible integer_array Flag that indicates whether the source is local to this MPI rank sourceNodeIds integer_array2d Indices of the nodes (in the right order) for each source point +sourceRegion integer_array Region containing the sources sourceValue real32_array2d Source Value of the sources useDAS integer Flag to indicate if DAS type of data will be modeled usePML integer Flag to apply PML diff --git a/src/coreComponents/schema/docs/ElasticFirstOrderSEM_other.rst b/src/coreComponents/schema/docs/ElasticFirstOrderSEM_other.rst index c89e3395606..3633753cf60 100644 --- a/src/coreComponents/schema/docs/ElasticFirstOrderSEM_other.rst +++ b/src/coreComponents/schema/docs/ElasticFirstOrderSEM_other.rst @@ -12,10 +12,12 @@ meshTargets geos_mapBase< std_pair< string, string >, LvArray_Ar rcvElem integer_array Element containing the receivers receiverIsLocal integer_array Flag that indicates whether the receiver is local to this MPI rank receiverNodeIds integer_array2d Indices of the nodes (in the right order) for each receiver point +receiverRegion integer_array Region containing the receivers sourceConstants real64_array2d Constant part of the receiver for the nodes listed in m_receiverNodeIds sourceElem integer_array Element containing the sources sourceIsAccessible integer_array Flag that indicates whether the source is local to this MPI rank sourceNodeIds integer_array2d Indices of the nodes (in the right order) for each source point +sourceRegion integer_array Region containing the sources sourceValue real32_array2d Source Value of the sources useDAS integer Flag to indicate if DAS type of data will be modeled usePML integer Flag to apply PML diff --git a/src/coreComponents/schema/docs/InternalMesh.rst b/src/coreComponents/schema/docs/InternalMesh.rst index 83088006483..d88ee9f4f46 100644 --- a/src/coreComponents/schema/docs/InternalMesh.rst +++ b/src/coreComponents/schema/docs/InternalMesh.rst @@ -17,6 +17,7 @@ yBias real64_array {1} Bias of element sizes in the y-directio yCoords real64_array required y-coordinates of each mesh block vertex zBias real64_array {1} Bias of element sizes in the z-direction within each mesh block (dz_left=(1+b)*L/N, dz_right=(1-b)*L/N) zCoords real64_array required z-coordinates of each mesh block vertex +InternalWell node :ref:`XML_InternalWell` ================= ============= ======== ======================================================================================================= diff --git a/src/coreComponents/schema/docs/InternalMesh_other.rst b/src/coreComponents/schema/docs/InternalMesh_other.rst index 14257041f64..aef746a9df7 100644 --- a/src/coreComponents/schema/docs/InternalMesh_other.rst +++ b/src/coreComponents/schema/docs/InternalMesh_other.rst @@ -1,9 +1,10 @@ -========== ==== =============================== -Name Type Description -========== ==== =============================== -meshLevels node :ref:`DATASTRUCTURE_meshLevels` -========== ==== =============================== +============ ==== ================================= +Name Type Description +============ ==== ================================= +InternalWell node :ref:`DATASTRUCTURE_InternalWell` +meshLevels node :ref:`DATASTRUCTURE_meshLevels` +============ ==== ================================= diff --git a/src/coreComponents/schema/docs/InternalWell.rst b/src/coreComponents/schema/docs/InternalWell.rst index cf54274bdea..253d791554d 100644 --- a/src/coreComponents/schema/docs/InternalWell.rst +++ b/src/coreComponents/schema/docs/InternalWell.rst @@ -4,7 +4,6 @@ Name Type Default Description ===================== =================== ======== ==================================================================================================== logLevel integer 0 Log level -meshName string required Name of the reservoir mesh associated with this well minElementLength real64 0.001 Minimum length of a well element, computed as (segment length / number of elements per segment ) [m] minSegmentLength real64 0.01 Minimum length of a well segment [m] name string required A name is required for any non-unique nodes diff --git a/src/coreComponents/schema/docs/InternalWellbore.rst b/src/coreComponents/schema/docs/InternalWellbore.rst index 21da9930522..9be2b55726c 100644 --- a/src/coreComponents/schema/docs/InternalWellbore.rst +++ b/src/coreComponents/schema/docs/InternalWellbore.rst @@ -23,6 +23,7 @@ xBias real64_array {1} Bias of element sizes in the yBias real64_array {1} Bias of element sizes in the y-direction within each mesh block (dy_left=(1+b)*L/N, dx_right=(1-b)*L/N) zBias real64_array {1} Bias of element sizes in the z-direction within each mesh block (dz_left=(1+b)*L/N, dz_right=(1-b)*L/N) zCoords real64_array required z-coordinates of each mesh block vertex +InternalWell node :ref:`XML_InternalWell` =========================== ============== ======== ============================================================================================================================================================================================================================ diff --git a/src/coreComponents/schema/docs/InternalWellbore_other.rst b/src/coreComponents/schema/docs/InternalWellbore_other.rst index 37368241205..24b84b089e7 100644 --- a/src/coreComponents/schema/docs/InternalWellbore_other.rst +++ b/src/coreComponents/schema/docs/InternalWellbore_other.rst @@ -1,13 +1,14 @@ -========== ============= ============================================================ -Name Type Description -========== ============= ============================================================ -nx integer_array Number of elements in the x-direction within each mesh block -ny integer_array Number of elements in the y-direction within each mesh block -xCoords real64_array x-coordinates of each mesh block vertex -yCoords real64_array y-coordinates of each mesh block vertex -meshLevels node :ref:`DATASTRUCTURE_meshLevels` -========== ============= ============================================================ +============ ============= ============================================================ +Name Type Description +============ ============= ============================================================ +nx integer_array Number of elements in the x-direction within each mesh block +ny integer_array Number of elements in the y-direction within each mesh block +xCoords real64_array x-coordinates of each mesh block vertex +yCoords real64_array y-coordinates of each mesh block vertex +InternalWell node :ref:`DATASTRUCTURE_InternalWell` +meshLevels node :ref:`DATASTRUCTURE_meshLevels` +============ ============= ============================================================ diff --git a/src/coreComponents/schema/docs/Mesh.rst b/src/coreComponents/schema/docs/Mesh.rst index a353a370806..7f991eed5d6 100644 --- a/src/coreComponents/schema/docs/Mesh.rst +++ b/src/coreComponents/schema/docs/Mesh.rst @@ -4,7 +4,6 @@ Name Type Default Description ================ ==== ======= =========================== InternalMesh node :ref:`XML_InternalMesh` -InternalWell node :ref:`XML_InternalWell` InternalWellbore node :ref:`XML_InternalWellbore` VTKMesh node :ref:`XML_VTKMesh` ================ ==== ======= =========================== diff --git a/src/coreComponents/schema/docs/Mesh_other.rst b/src/coreComponents/schema/docs/Mesh_other.rst index 02d974473f6..67c5596be54 100644 --- a/src/coreComponents/schema/docs/Mesh_other.rst +++ b/src/coreComponents/schema/docs/Mesh_other.rst @@ -4,7 +4,6 @@ Name Type Description ================ ==== ===================================== InternalMesh node :ref:`DATASTRUCTURE_InternalMesh` -InternalWell node :ref:`DATASTRUCTURE_InternalWell` InternalWellbore node :ref:`DATASTRUCTURE_InternalWellbore` VTKMesh node :ref:`DATASTRUCTURE_VTKMesh` ================ ==== ===================================== diff --git a/src/coreComponents/schema/docs/TwoPointFluxApproximation.rst b/src/coreComponents/schema/docs/TwoPointFluxApproximation.rst index a43fd7598a9..f750965a9d0 100644 --- a/src/coreComponents/schema/docs/TwoPointFluxApproximation.rst +++ b/src/coreComponents/schema/docs/TwoPointFluxApproximation.rst @@ -1,12 +1,15 @@ -=================== ======= ======== =========================================== -Name Type Default Description -=================== ======= ======== =========================================== -areaRelTol real64 1e-08 Relative tolerance for area calculations. -meanPermCoefficient real64 1 (no description available) -name string required A name is required for any non-unique nodes -usePEDFM integer 0 (no description available) -=================== ======= ======== =========================================== +=================== ==================== ======== ======================================================== +Name Type Default Description +=================== ==================== ======== ======================================================== +areaRelTol real64 1e-08 Relative tolerance for area calculations. +meanPermCoefficient real64 1 (no description available) +name string required A name is required for any non-unique nodes +upwindingScheme geos_UpwindingScheme PPU | Type of upwinding scheme. Valid options: + | * PPU + | * C1PPU +usePEDFM integer 0 (no description available) +=================== ==================== ======== ======================================================== diff --git a/src/coreComponents/schema/docs/VTKMesh.rst b/src/coreComponents/schema/docs/VTKMesh.rst index e8b568b86e4..a8960eaff72 100644 --- a/src/coreComponents/schema/docs/VTKMesh.rst +++ b/src/coreComponents/schema/docs/VTKMesh.rst @@ -19,6 +19,7 @@ surfacicFieldsInGEOSX string_array {} Names of the surfacic surfacicFieldsToImport string_array {} Surfacic fields to be imported from the external mesh file translate R1Tensor {0,0,0} Translate the coordinates of the vertices by a given vector (prior to scaling) useGlobalIds integer 0 Controls the use of global IDs in the input file for cells and points. If set to 0 (default value), the GlobalId arrays in the input mesh are used if available, and generated otherwise. If set to a negative value, the GlobalId arrays in the input mesh are not used, and generated global Ids are automatically generated. If set to a positive value, the GlobalId arrays in the input mesh are used and required, and the simulation aborts if they are not available +InternalWell node :ref:`XML_InternalWell` ====================== ======================== ========= ============================================================================================================================================================================================================================================================================================================================================================================================================================================================================ diff --git a/src/coreComponents/schema/docs/VTKMesh_other.rst b/src/coreComponents/schema/docs/VTKMesh_other.rst index 14257041f64..aef746a9df7 100644 --- a/src/coreComponents/schema/docs/VTKMesh_other.rst +++ b/src/coreComponents/schema/docs/VTKMesh_other.rst @@ -1,9 +1,10 @@ -========== ==== =============================== -Name Type Description -========== ==== =============================== -meshLevels node :ref:`DATASTRUCTURE_meshLevels` -========== ==== =============================== +============ ==== ================================= +Name Type Description +============ ==== ================================= +InternalWell node :ref:`DATASTRUCTURE_InternalWell` +meshLevels node :ref:`DATASTRUCTURE_meshLevels` +============ ==== ================================= diff --git a/src/coreComponents/schema/schema.xsd b/src/coreComponents/schema/schema.xsd index 576cb4d7202..b3fc3d76ef9 100644 --- a/src/coreComponents/schema/schema.xsd +++ b/src/coreComponents/schema/schema.xsd @@ -249,10 +249,6 @@ - - - - @@ -470,275 +466,275 @@ - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -762,43 +758,43 @@ - + - + - + - + - + - + - + - + - + @@ -1333,18 +1329,35 @@ stress - traction is applied to the faces as specified by the inner product of i - - - - + + + + + + + + + + + + + + + - - + + + + + + + + @@ -1380,8 +1393,6 @@ stress - traction is applied to the faces as specified by the inner product of i - - @@ -1410,6 +1421,14 @@ stress - traction is applied to the faces as specified by the inner product of i + + + + + + + + @@ -1452,6 +1471,14 @@ stress - traction is applied to the faces as specified by the inner product of i + + + + + + + + @@ -1493,17 +1520,17 @@ stress - traction is applied to the faces as specified by the inner product of i - + - + - + @@ -1740,11 +1767,20 @@ the relative residual norm satisfies: + + + + + + + @@ -1882,7 +1918,7 @@ the relative residual norm satisfies: - + @@ -1909,7 +1945,7 @@ the relative residual norm satisfies: - + diff --git a/src/coreComponents/schema/schema.xsd.other b/src/coreComponents/schema/schema.xsd.other index cbd84be6c61..890a518d197 100644 --- a/src/coreComponents/schema/schema.xsd.other +++ b/src/coreComponents/schema/schema.xsd.other @@ -356,13 +356,13 @@ - + @@ -374,6 +374,7 @@ + @@ -387,6 +388,7 @@ + @@ -524,6 +526,8 @@ + + @@ -532,6 +536,8 @@ + + @@ -676,6 +682,8 @@ + + @@ -684,6 +692,8 @@ + + diff --git a/src/coreComponents/schema/schemaUtilities.cpp b/src/coreComponents/schema/schemaUtilities.cpp index 524da722933..57496704bd2 100644 --- a/src/coreComponents/schema/schemaUtilities.cpp +++ b/src/coreComponents/schema/schemaUtilities.cpp @@ -176,7 +176,8 @@ void SchemaConstruction( Group & group, // Enforce uniqueness of element names // Note: this must be done at the parent element level xmlWrapper::xmlNode uniqueNameNode = targetIncludeNode.append_child( "xsd:unique" ); - string uniqueNameNodeStr = targetName + subName + "UniqueName"; + string path = std::regex_replace( group.getPath(), std::regex( "/" ), "" ); + string uniqueNameNodeStr = path + subName + "UniqueName"; uniqueNameNode.append_attribute( "name" ) = uniqueNameNodeStr.c_str(); xmlWrapper::xmlNode uniqueNameSelector = uniqueNameNode.append_child( "xsd:selector" ); uniqueNameSelector.append_attribute( "xpath" ) = subName.c_str(); diff --git a/src/coreComponents/unitTests/wellsTests/testReservoirCompositionalMultiphaseMSWells.cpp b/src/coreComponents/unitTests/wellsTests/testReservoirCompositionalMultiphaseMSWells.cpp index e1c08d851be..514b3fa1282 100644 --- a/src/coreComponents/unitTests/wellsTests/testReservoirCompositionalMultiphaseMSWells.cpp +++ b/src/coreComponents/unitTests/wellsTests/testReservoirCompositionalMultiphaseMSWells.cpp @@ -86,31 +86,30 @@ char const * xmlInput = nx="{3}" ny="{1}" nz="{1}" - cellBlockNames="{cb1}"/> - - - - - - + cellBlockNames="{cb1}"> + + + + + + + diff --git a/src/coreComponents/unitTests/wellsTests/testReservoirSinglePhaseMSWells.cpp b/src/coreComponents/unitTests/wellsTests/testReservoirSinglePhaseMSWells.cpp index a29bccbd212..aa190f0e0c9 100644 --- a/src/coreComponents/unitTests/wellsTests/testReservoirSinglePhaseMSWells.cpp +++ b/src/coreComponents/unitTests/wellsTests/testReservoirSinglePhaseMSWells.cpp @@ -81,31 +81,30 @@ char const * xmlInput = nx="{3}" ny="{1}" nz="{1}" - cellBlockNames="{cb1}"/> - - - - - - + cellBlockNames="{cb1}"> + + + + + + + diff --git a/src/docs/doxygen/GeosxConfig.hpp b/src/docs/doxygen/GeosxConfig.hpp index 11750047a35..a0000940e9a 100644 --- a/src/docs/doxygen/GeosxConfig.hpp +++ b/src/docs/doxygen/GeosxConfig.hpp @@ -33,7 +33,7 @@ #define GEOSX_USE_OPENMP /// Enables use of CUDA (CMake option ENABLE_CUDA) -/* #undef GEOS_USE_CUDA */ +#define GEOS_USE_CUDA /// Enables use of CUDA NVToolsExt (CMake option ENABLE_CUDA_NVTOOLSEXT) /* #undef GEOS_USE_CUDA_NVTOOLSEXT */ @@ -81,7 +81,7 @@ /// Denotes HYPRE using HIP #define GEOS_USE_HYPRE_HIP 2 /// Macro determining what parellel interface hypre is using -#define GEOS_USE_HYPRE_DEVICE GEOS_USE_HYPRE_CPU +#define GEOS_USE_HYPRE_DEVICE GEOS_USE_HYPRE_CUDA /// Enables use of SuperLU_dist library through HYPRE (CMake option ENABLE_SUPERLU_DIST) #define GEOSX_USE_SUPERLU_DIST @@ -126,10 +126,10 @@ #define GEOSX_BLOCK_SIZE 32 /// Version information for HDF5 -#define HDF5_VERSION 1.14.1 +#define HDF5_VERSION 1.12.1 /// Version information for Conduit -#define Conduit_VERSION 0.8.7 +#define Conduit_VERSION 0.8.2 /// Version information for RAJA #define RAJA_VERSION 2022.10.5 @@ -162,7 +162,7 @@ #define suitesparse_VERSION 5.7.9 /// Version information for VTK -#define VTK_VERSION 9.1.0 +#define VTK_VERSION 9.2.6 /// Version information for fmt #define fmt_VERSION 10.0.0 diff --git a/src/docs/sphinx/buildGuide/Prerequisites.rst b/src/docs/sphinx/buildGuide/Prerequisites.rst index e8e7f25822f..78251f997d5 100644 --- a/src/docs/sphinx/buildGuide/Prerequisites.rst +++ b/src/docs/sphinx/buildGuide/Prerequisites.rst @@ -12,7 +12,7 @@ Minimal requirements: - `CMake `_ build system generator (3.17+). - build tools (`GNU make `_ or `ninja `_ on Linux, XCode on MacOS). -- a C++ compiler with full c++14 standard support (`gcc `_ 8.3+ or `clang `_ 8.0+ are recommended). +- a C++ compiler with full c++17 standard support (`gcc `_ 8.3+ or `clang `_ 10.0+ are recommended). - `python `_ (2.7+ or 3.6+). - :code:`zlib`, :code:`blas` and :code:`lapack` libraries - any compatible MPI runtime and compilers (if building with MPI) diff --git a/src/docs/sphinx/developerGuide/Contributing/CodeStyle.rst b/src/docs/sphinx/developerGuide/Contributing/CodeStyle.rst index 0bbc547f44e..8d79b34dc5a 100644 --- a/src/docs/sphinx/developerGuide/Contributing/CodeStyle.rst +++ b/src/docs/sphinx/developerGuide/Contributing/CodeStyle.rst @@ -4,7 +4,7 @@ Code style Introduction ============ -GEOS is written in standard c++14. In general, target platforms are: +GEOS is written in standard c++17. In general, target platforms are: - Linux - Mac OS X diff --git a/src/main/main.cpp b/src/main/main.cpp index e2108c1949f..9074d88f24b 100644 --- a/src/main/main.cpp +++ b/src/main/main.cpp @@ -20,8 +20,6 @@ #include "mainInterface/ProblemManager.hpp" #include "mainInterface/GeosxState.hpp" -// System includes -#include using namespace geos; @@ -30,7 +28,7 @@ int main( int argc, char *argv[] ) { try { - std::chrono::system_clock::time_point const startTime = std::chrono::system_clock::now(); + std::chrono::system_clock::time_point startTime = std::chrono::system_clock::now(); std::unique_ptr< CommandLineOptions > commandLineOptions = basicSetup( argc, argv, true ); @@ -55,8 +53,8 @@ int main( int argc, char *argv[] ) basicCleanup(); - std::chrono::system_clock::time_point const endTime = std::chrono::system_clock::now(); - std::chrono::system_clock::duration const totalTime = endTime - startTime; + std::chrono::system_clock::time_point endTime = std::chrono::system_clock::now(); + std::chrono::system_clock::duration totalTime = endTime - startTime; GEOS_LOG_RANK_0( GEOS_FMT( "Finished at {:%Y-%m-%d %H:%M:%S}", endTime ) ); GEOS_LOG_RANK_0( GEOS_FMT( "total time {:%H:%M:%S}", totalTime ) ); From dad53ce2bf8845f167ec82ce330656b260a48058 Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Mon, 10 Jul 2023 14:10:22 +0200 Subject: [PATCH 53/85] wrong method name --- src/coreComponents/mesh/ElementRegionManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreComponents/mesh/ElementRegionManager.cpp b/src/coreComponents/mesh/ElementRegionManager.cpp index 23290e1b740..54bd076bb4f 100644 --- a/src/coreComponents/mesh/ElementRegionManager.cpp +++ b/src/coreComponents/mesh/ElementRegionManager.cpp @@ -191,7 +191,7 @@ void ElementRegionManager::generateWells( CellBlockManagerABC const & cellBlockM globalIndex const numWellElemsGlobal = MpiWrapper::sum( subRegion.size() ); - GEOS_ERROR_IF( numWellElemsGlobal != lineBlock.getNumElements(), + GEOS_ERROR_IF( numWellElemsGlobal != lineBlock.numElements(), "Invalid partitioning in well " << lineBlock.getDataContext() << ", subregion " << subRegion.getDataContext() ); From 39a0cdc1c1e4cf61d6f52719adbea6816c6d8eca Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Wed, 12 Jul 2023 16:19:25 +0200 Subject: [PATCH 54/85] DataContext architecture refactorization --- src/coreComponents/common/DataTypes.hpp | 4 ++ .../dataRepository/DataContext.cpp | 41 ++++++------ .../dataRepository/DataContext.hpp | 67 ++++++++++++------- .../dataRepository/GroupContext.cpp | 32 ++++++--- .../dataRepository/GroupContext.hpp | 25 ++++--- 5 files changed, 107 insertions(+), 62 deletions(-) diff --git a/src/coreComponents/common/DataTypes.hpp b/src/coreComponents/common/DataTypes.hpp index e7dc90fa52c..6c81b4d58e7 100644 --- a/src/coreComponents/common/DataTypes.hpp +++ b/src/coreComponents/common/DataTypes.hpp @@ -61,6 +61,7 @@ #include #include #include +#include /** * top level geosx namespace contains all code that is specific to GEOSX @@ -129,6 +130,9 @@ using globalIndex = GEOSX_GLOBALINDEX_TYPE; /// String type. using string = std::string; +/// String type. +using string_view = std::string_view; + /// 32-bit floating point type. using real32 = float; /// 64-bit floating point type. diff --git a/src/coreComponents/dataRepository/DataContext.cpp b/src/coreComponents/dataRepository/DataContext.cpp index b687de8830b..052a8ae4d33 100644 --- a/src/coreComponents/dataRepository/DataContext.cpp +++ b/src/coreComponents/dataRepository/DataContext.cpp @@ -28,12 +28,25 @@ DataContext::DataContext( string const & targetName ): m_targetName( targetName ) {} -std::ostream & operator<<( std::ostream & os, DataContext const & sc ) +std::ostream & operator<<( std::ostream & os, DataContext const & ctx ) { - os << sc.toString(); + os << ctx.toString(); return os; } +DataContext::ToStringInfo::ToStringInfo( string_view targetName, string_view filePath, int line ): + m_targetName( targetName ), + m_filePath( filePath ), + m_line( line ) +{} +DataContext::ToStringInfo::ToStringInfo( string_view targetName ): + m_targetName( targetName ), + m_filePath(), + m_line( xmlWrapper::xmlDocument::npos ) +{} +bool DataContext::ToStringInfo::hasInputFileInfo() const +{ return !m_filePath.empty() && m_line != xmlWrapper::xmlDocument::npos; } + /** * @return the node 'name' attribute if it exists, return the node tag name otherwise. @@ -75,34 +88,24 @@ DataFileContext::DataFileContext( xmlWrapper::xmlNode const & targetNode, string DataFileContext::toString() const { - std::ostringstream oss; - oss << m_targetName; if( m_line != xmlWrapper::xmlDocument::npos ) { - oss << " (" << splitPath( m_filePath ).second << ", l." << m_line << ")"; + return GEOS_FMT( "{} ({}, l.{})", m_targetName, splitPath( m_filePath ).second, m_line ); } else if( m_offset != xmlWrapper::xmlDocument::npos ) { - oss << " (" << splitPath( m_filePath ).second << ", offset " << m_offset << ")"; + return GEOS_FMT( "{} ({}, offset {})", m_targetName, splitPath( m_filePath ).second, m_offset ); } else { - oss << " (Source file not found)"; + return GEOS_FMT( "{} (Source file not found)", m_targetName ); } - return oss.str(); } -string DataFileContext::getTargetNameInPath( bool & foundNearestLine ) const -{ - std::ostringstream oss; - oss << m_targetName; - foundNearestLine = ( m_line != xmlWrapper::xmlDocument::npos ); - if( foundNearestLine ) - { - oss << "(" << splitPath( m_filePath ).second << ",l." << m_line << ")"; - } - return oss.str(); -} +DataContext::ToStringInfo DataFileContext::getToStringInfo() const +{ return ToStringInfo( m_targetName, m_filePath, m_line ); } + + } /* namespace dataRepository */ } /* namespace geos */ diff --git a/src/coreComponents/dataRepository/DataContext.hpp b/src/coreComponents/dataRepository/DataContext.hpp index 13e7b539339..f122318232a 100644 --- a/src/coreComponents/dataRepository/DataContext.hpp +++ b/src/coreComponents/dataRepository/DataContext.hpp @@ -54,36 +54,58 @@ class DataContext * object comes from. */ virtual string toString() const = 0; - /** - * @return The result of toString() properly suffixed by the name of a contained object. - */ - virtual string toString( string const & innerObjectName ) const - { return toString() + '/' + innerObjectName; } /** * @return Get the target object name */ string getTargetName() const { return m_targetName; } - - /** - * @return the target name formatted in such a way that it can be inserted in a hierarchy path. - * @param foundNearestLine reference to a boolean that is set to true if the file line of the - * data context could be determined, otherwise it is set to false. - */ - virtual string getTargetNameInPath( bool & fileLineFound ) const - { fileLineFound = false; return m_targetName; } - /** * @brief Insert contextual information in the provided stream. */ - friend std::ostream & operator<<( std::ostream & os, const DataContext & dt ); + friend std::ostream & operator<<( std::ostream & os, const DataContext & ctx ); protected: + // GroupContext & WrapperContext are friend class to be able to access to the protected method on other instances. + friend class GroupContext; + friend class WrapperContext; /// @see getObjectName() string const m_targetName; + /// This struct exposes the raw data of a DataContext instance that toString() need in order to format it. + /// This struct lifetime depends on that of the source DataContext. The DataContext is considered constant. + struct ToStringInfo + { + string_view m_targetName; + string_view m_filePath; + size_t m_line; + + /** + * @brief Construct a new ToStringInfo object from a DataContext that has input file info. + * @param targetName the target name + * @param filePath the input file path where the target is declared. + * @param line the line in the file where the target is declared. + */ + ToStringInfo( string_view targetName, string_view filePath, int line ); + /** + * @brief Construct a new ToStringInfo object from a DataContext that has no input file info. + * @param targetName the target name. + */ + ToStringInfo( string_view targetName ); + /** + * @return true if a location has been found to declare the target in an input file. + */ + bool hasInputFileInfo() const; + }; + + /** + * @brief This method exposes the raw data of a DataContext, in order to access and format it + * (notably in toString() implementations that need to access other DataContext instances). + * @return a ToStringInfo struct that contains the raw data contained in this DataContext instance. + */ + virtual ToStringInfo getToStringInfo() const = 0; + }; /// Stores information to retrieve where a target object has been declared in the input source @@ -111,16 +133,6 @@ class DataFileContext final : public DataContext * @return the target object name followed by the the file and line declaring it. */ virtual string toString() const; - /** - * @copydoc DataContext::toString() - */ - string toString( string const & innerObjectName ) const override - { return toString() + ", attribute " + innerObjectName; } - - /** - * @copydoc DataContext::getTargetNameInPath( bool & foundNearestLine ) const - */ - virtual string getTargetNameInPath( bool & foundNearestLine ) const override; /** * @return the type name in the source file (XML node tag name / attribute name). @@ -167,6 +179,11 @@ class DataFileContext final : public DataContext /// @see getOffset() size_t const m_offset; + /** + * @copydoc DataContext::getToStringInfo() + */ + ToStringInfo getToStringInfo() const override; + }; diff --git a/src/coreComponents/dataRepository/GroupContext.cpp b/src/coreComponents/dataRepository/GroupContext.cpp index 77a78cbe657..8251c37c0f3 100644 --- a/src/coreComponents/dataRepository/GroupContext.cpp +++ b/src/coreComponents/dataRepository/GroupContext.cpp @@ -34,22 +34,30 @@ GroupContext::GroupContext( Group & group ): string GroupContext::toString() const { - string path; - bool foundNearestLine = false; - for( Group const * parentGroup = &m_group; parentGroup->hasParent(); parentGroup = &parentGroup->getParent() ) + std::vector< string > parents; + Group const * parentGroup = &m_group; + // add all parent names in a path string, until we get some input file info to show + bool foundNearestLineInfo = false; + for(; parentGroup->hasParent(); parentGroup = &parentGroup->getParent() ) { - if( !foundNearestLine ) + ToStringInfo const info = parentGroup->getDataContext().getToStringInfo(); + if( !foundNearestLineInfo || info.hasInputFileInfo() ) { - path.insert( 0, '/' + parentGroup->getDataContext().getTargetNameInPath( foundNearestLine ) ); + // avoiding spaces here is intended as we don't want any line return within the path. + parents.push_back( GEOS_FMT( "{}({},l.{})", + info.m_targetName, info.m_filePath, info.m_line ) ); } else { - path.insert( 0, '/' + parentGroup->getName() ); + parents.push_back( GEOS_FMT( "/{}", info.m_targetName ) ); } } - return path; + return stringutilities::join( parents.rbegin(), parents.rend(), '/' ); } +DataContext::ToStringInfo GroupContext::getToStringInfo() const +{ return ToStringInfo( m_targetName ); } + WrapperContext::WrapperContext( WrapperBase & wrapper ): GroupContext( wrapper.getParent(), wrapper.getParent().getName() + '/' + wrapper.getName() ), @@ -58,7 +66,15 @@ WrapperContext::WrapperContext( WrapperBase & wrapper ): string WrapperContext::toString() const { - return m_group.getDataContext().toString( m_typeName ); + ToStringInfo const info = m_group.getDataContext().getToStringInfo(); + if( info.hasInputFileInfo()) + { + return GEOS_FMT( "{}, attribute {}", m_group.getDataContext().toString(), m_typeName ); + } + else + { + return GEOS_FMT( "{}->{}", m_group.getDataContext().toString(), m_typeName ); + } } diff --git a/src/coreComponents/dataRepository/GroupContext.hpp b/src/coreComponents/dataRepository/GroupContext.hpp index 5d13f47a5b0..0a264e1a3be 100644 --- a/src/coreComponents/dataRepository/GroupContext.hpp +++ b/src/coreComponents/dataRepository/GroupContext.hpp @@ -46,11 +46,6 @@ class GroupContext : public DataContext */ Group & getGroup() const; - /** - * @return the group path with the file & line of the first parent for which this information exists. - */ - virtual string toString() const; - protected: /** @@ -63,6 +58,16 @@ class GroupContext : public DataContext /// The reference to the Group related to this GroupContext. Group & m_group; +private: + + /** + * @return the group path with the file & line of the first parent for which this information exists. + */ + string toString() const override; + /** + * @copydoc DataContext::getToStringInfo() + */ + ToStringInfo getToStringInfo() const override; }; /// Dedicated implementation of GroupContext for Wrapper. @@ -77,15 +82,15 @@ class WrapperContext final : public GroupContext */ WrapperContext( WrapperBase & wrapper ); - /** - * @return the parent group DataContext followed by the wrapper name. - */ - virtual string toString() const; - private: string const m_typeName; + /** + * @return the parent group DataContext followed by the wrapper name. + */ + string toString() const override; + }; From ee76f622f6c19103fdf1aff169611388925de12c Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Mon, 17 Jul 2023 16:31:32 +0200 Subject: [PATCH 55/85] GroupContext / WrapperContext fixes --- .../dataRepository/GroupContext.cpp | 28 ++++++++----------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/src/coreComponents/dataRepository/GroupContext.cpp b/src/coreComponents/dataRepository/GroupContext.cpp index 8251c37c0f3..4bc2aec5205 100644 --- a/src/coreComponents/dataRepository/GroupContext.cpp +++ b/src/coreComponents/dataRepository/GroupContext.cpp @@ -34,25 +34,24 @@ GroupContext::GroupContext( Group & group ): string GroupContext::toString() const { + // we add all parent names in a path string, showing only the first input file info encountered std::vector< string > parents; - Group const * parentGroup = &m_group; - // add all parent names in a path string, until we get some input file info to show bool foundNearestLineInfo = false; - for(; parentGroup->hasParent(); parentGroup = &parentGroup->getParent() ) + for( Group const * parent = &m_group; parent->hasParent(); parent = &parent->getParent() ) { - ToStringInfo const info = parentGroup->getDataContext().getToStringInfo(); - if( !foundNearestLineInfo || info.hasInputFileInfo() ) + ToStringInfo const info = parent->getDataContext().getToStringInfo(); + if( info.hasInputFileInfo() && !foundNearestLineInfo ) { // avoiding spaces here is intended as we don't want any line return within the path. - parents.push_back( GEOS_FMT( "{}({},l.{})", - info.m_targetName, info.m_filePath, info.m_line ) ); + parents.push_back( GEOS_FMT( "{}({},l.{})", info.m_targetName, info.m_filePath, info.m_line ) ); + foundNearestLineInfo = true; } else { - parents.push_back( GEOS_FMT( "/{}", info.m_targetName ) ); + parents.push_back( string( info.m_targetName ) ); } } - return stringutilities::join( parents.rbegin(), parents.rend(), '/' ); + return '/' + stringutilities::join( parents.rbegin(), parents.rend(), '/' ); } DataContext::ToStringInfo GroupContext::getToStringInfo() const @@ -67,14 +66,9 @@ WrapperContext::WrapperContext( WrapperBase & wrapper ): string WrapperContext::toString() const { ToStringInfo const info = m_group.getDataContext().getToStringInfo(); - if( info.hasInputFileInfo()) - { - return GEOS_FMT( "{}, attribute {}", m_group.getDataContext().toString(), m_typeName ); - } - else - { - return GEOS_FMT( "{}->{}", m_group.getDataContext().toString(), m_typeName ); - } + return info.hasInputFileInfo() ? + GEOS_FMT( "{} ({}, l.{})", m_targetName, info.m_filePath, info.m_line ) : + GEOS_FMT( "{}->{}", m_group.getDataContext().toString(), m_typeName ); } From b6275759696e44309c1587703a59845db6ecbc3c Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Mon, 17 Jul 2023 16:32:11 +0200 Subject: [PATCH 56/85] Extended test for WrapperContext::toString() --- .../dataRepositoryTests/testGroupPath.cpp | 30 ++++++++++++++----- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/src/coreComponents/unitTests/dataRepositoryTests/testGroupPath.cpp b/src/coreComponents/unitTests/dataRepositoryTests/testGroupPath.cpp index 1451308b7fd..df216a74fa3 100644 --- a/src/coreComponents/unitTests/dataRepositoryTests/testGroupPath.cpp +++ b/src/coreComponents/unitTests/dataRepositoryTests/testGroupPath.cpp @@ -118,21 +118,35 @@ TEST( testGroupPath, testGlobalPaths ) // checks if the exception has been thrown as expected ASSERT_TRUE( trowHappened ); - auto const testDataContextString = [&]( string const & groupPath, string const & ctxString ) + auto const testGroupContextString = [&]( string const & groupPath, string const & ctxString ) { Group const * const groupToTest = &problem.getGroupByPath( groupPath ); ASSERT_NE( groupToTest, nullptr ); ASSERT_STREQ( groupToTest->getDataContext().toString().c_str(), ctxString.c_str() ); }; - // check if the DataContext string of a Group declared in the XML is formatted as expected - testDataContextString( - "/Mesh/mesh1", - "mesh1 (CodeIncludedXML0, l.11)" ); + auto const testWrapperContextString = [&]( string const & groupPath, string const & wrapperName, + string const & ctxString ) + { + Group const * const containingGroup = &problem.getGroupByPath( groupPath ); + ASSERT_NE( containingGroup, nullptr ); + WrapperBase const * const wrapperToTest = &containingGroup->getWrapperBase( wrapperName ); + ASSERT_NE( wrapperToTest, nullptr ); + ASSERT_STREQ( wrapperToTest->getDataContext().toString().c_str(), + ctxString.c_str() ); + }; + + // check if the DataContext string of a Group and a Wrapper declared in the XML is formatted as expected + testGroupContextString( "/Mesh/mesh1", + "mesh1 (CodeIncludedXML0, l.11)" ); + testWrapperContextString( "/Mesh/mesh1", "xCoords", + "mesh1/xCoords (CodeIncludedXML0, l.14)" ); + // check if the DataContext string of an implicitly created Group is formatted as expected - testDataContextString( - "/domain/MeshBodies/mesh1/meshLevels/Level0/ElementRegions/elementRegionsGroup/Region2/elementSubRegions", - "/domain/MeshBodies/mesh1/meshLevels/Level0/ElementRegions/elementRegionsGroup/Region2(CodeIncludedXML0,l.37)/elementSubRegions" ); + testGroupContextString( "/domain/MeshBodies/mesh1/meshLevels/Level0", + "/domain/MeshBodies/mesh1/meshLevels/Level0" ); + testWrapperContextString( "/domain/MeshBodies/mesh1/meshLevels/Level0", "meshLevel", + "/domain/MeshBodies/mesh1/meshLevels/Level0->meshLevel" ); } int main( int argc, char * * argv ) From 374223de30d4c424e5db1a5fe6412f07379fd759 Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Mon, 17 Jul 2023 18:06:05 +0200 Subject: [PATCH 57/85] docs fixes --- src/coreComponents/dataRepository/DataContext.hpp | 3 +++ src/coreComponents/dataRepository/xmlWrapper.hpp | 7 +------ 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/coreComponents/dataRepository/DataContext.hpp b/src/coreComponents/dataRepository/DataContext.hpp index f122318232a..a9b5fa039e1 100644 --- a/src/coreComponents/dataRepository/DataContext.hpp +++ b/src/coreComponents/dataRepository/DataContext.hpp @@ -77,8 +77,11 @@ class DataContext /// This struct lifetime depends on that of the source DataContext. The DataContext is considered constant. struct ToStringInfo { + /// the targetName of the DataContext string_view m_targetName; + /// the file path of the DataFileContext, if it exists (an empty string otherwise) string_view m_filePath; + /// the file line of the DataFileContext, if it exists (an empty string otherwise) size_t m_line; /** diff --git a/src/coreComponents/dataRepository/xmlWrapper.hpp b/src/coreComponents/dataRepository/xmlWrapper.hpp index 80e684a7dd0..c5359ebe191 100644 --- a/src/coreComponents/dataRepository/xmlWrapper.hpp +++ b/src/coreComponents/dataRepository/xmlWrapper.hpp @@ -148,7 +148,7 @@ class xmlDocument xmlDocument( xmlDocument && ) = default; /** - * @return the first child of this document (typically in GEOS, the node) + * @return the first child of this document (typically in GEOS, the Problem node) */ xmlNode getFirstChild() const; /** @@ -190,7 +190,6 @@ class xmlDocument * Wrapper of pugi::xml_document::loadBuffer() method. * @param contents the string containing the document content * @param loadNodeFileInfo Load the node source file info, allowing getNodePosition() to work. - * @param options the parsing options * @return an xmlResult object representing the parsing resulting status. */ xmlResult loadString( string const & contents, bool loadNodeFileInfo = false ); @@ -200,8 +199,6 @@ class xmlDocument * Wrapper of pugi::xml_document::loadBuffer() method. * @param path the path of an xml file to load. * @param loadNodeFileInfo Load the node source file info, allowing getNodePosition() to work. - * @param options the parsing options - * @param encoding the encoding options * @return an xmlResult object representing the parsing resulting status. */ xmlResult loadFile( string const & path, bool loadNodeFileInfo = false ); @@ -213,8 +210,6 @@ class xmlDocument * @param contents the buffer containing the document content * @param size the size of the buffer in bytes * @param loadNodeFileInfo Load the node source file info, allowing getNodePosition() to work. - * @param options the parsing options - * @param encoding the encoding options * @return an xmlResult object representing the parsing resulting status. */ xmlResult loadBuffer( const void * contents, size_t size, bool loadNodeFileInfo = false ); From 3b188ae7d1a0ef8d5d762144dbcf8d2ce8e254dd Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Tue, 18 Jul 2023 17:07:22 +0200 Subject: [PATCH 58/85] improving testGroupPath with more implicit groups & wrapper + got back to '/' char for WrapperContext --- .../dataRepository/GroupContext.cpp | 2 +- .../dataRepositoryTests/testGroupPath.cpp | 15 ++++++++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/coreComponents/dataRepository/GroupContext.cpp b/src/coreComponents/dataRepository/GroupContext.cpp index 4bc2aec5205..28392d24ce7 100644 --- a/src/coreComponents/dataRepository/GroupContext.cpp +++ b/src/coreComponents/dataRepository/GroupContext.cpp @@ -68,7 +68,7 @@ string WrapperContext::toString() const ToStringInfo const info = m_group.getDataContext().getToStringInfo(); return info.hasInputFileInfo() ? GEOS_FMT( "{} ({}, l.{})", m_targetName, info.m_filePath, info.m_line ) : - GEOS_FMT( "{}->{}", m_group.getDataContext().toString(), m_typeName ); + GEOS_FMT( "{}/{}", m_group.getDataContext().toString(), m_typeName ); } diff --git a/src/coreComponents/unitTests/dataRepositoryTests/testGroupPath.cpp b/src/coreComponents/unitTests/dataRepositoryTests/testGroupPath.cpp index df216a74fa3..71db7a13ddd 100644 --- a/src/coreComponents/unitTests/dataRepositoryTests/testGroupPath.cpp +++ b/src/coreComponents/unitTests/dataRepositoryTests/testGroupPath.cpp @@ -142,11 +142,16 @@ TEST( testGroupPath, testGlobalPaths ) testWrapperContextString( "/Mesh/mesh1", "xCoords", "mesh1/xCoords (CodeIncludedXML0, l.14)" ); - // check if the DataContext string of an implicitly created Group is formatted as expected - testGroupContextString( "/domain/MeshBodies/mesh1/meshLevels/Level0", - "/domain/MeshBodies/mesh1/meshLevels/Level0" ); - testWrapperContextString( "/domain/MeshBodies/mesh1/meshLevels/Level0", "meshLevel", - "/domain/MeshBodies/mesh1/meshLevels/Level0->meshLevel" ); + // check if the DataContext string of implicitly created Groups are formatted as expected + testGroupContextString( "/Solvers/lagsolve/NonlinearSolverParameters", + "/Solvers/lagsolve(CodeIncludedXML0,l.4)/NonlinearSolverParameters" ); + testGroupContextString( "/domain/MeshBodies/mesh1/meshLevels/Level0/ElementRegions/elementRegionsGroup/Region2/elementSubRegions", + "/domain/MeshBodies/mesh1/meshLevels/Level0/ElementRegions/elementRegionsGroup/Region2(CodeIncludedXML0,l.37)/elementSubRegions" ); + // check if the DataContext string of implicitly created Wrappers are formatted as expected + testWrapperContextString( "/Mesh/mesh1", "positionTolerance", + "mesh1/positionTolerance (CodeIncludedXML0, l.11)" ); + testWrapperContextString( "/Solvers/lagsolve/NonlinearSolverParameters", "newtonMaxIter", + "/Solvers/lagsolve(CodeIncludedXML0,l.4)/NonlinearSolverParameters/newtonMaxIter" ); } int main( int argc, char * * argv ) From e1ffd1df7d3cd79d33b49b0d12eac5f7c46c1390 Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Tue, 18 Jul 2023 17:09:51 +0200 Subject: [PATCH 59/85] GroupContext::toString() rewrite --- .../dataRepository/GroupContext.cpp | 30 +++++++++---------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/src/coreComponents/dataRepository/GroupContext.cpp b/src/coreComponents/dataRepository/GroupContext.cpp index 28392d24ce7..c3f3e8db86e 100644 --- a/src/coreComponents/dataRepository/GroupContext.cpp +++ b/src/coreComponents/dataRepository/GroupContext.cpp @@ -34,24 +34,22 @@ GroupContext::GroupContext( Group & group ): string GroupContext::toString() const { - // we add all parent names in a path string, showing only the first input file info encountered - std::vector< string > parents; - bool foundNearestLineInfo = false; - for( Group const * parent = &m_group; parent->hasParent(); parent = &parent->getParent() ) + std::vector< ToStringInfo > parentsInfo; + for( Group const * group = &m_group; group->hasParent(); group = &group->getParent() ) { - ToStringInfo const info = parent->getDataContext().getToStringInfo(); - if( info.hasInputFileInfo() && !foundNearestLineInfo ) - { - // avoiding spaces here is intended as we don't want any line return within the path. - parents.push_back( GEOS_FMT( "{}({},l.{})", info.m_targetName, info.m_filePath, info.m_line ) ); - foundNearestLineInfo = true; - } - else - { - parents.push_back( string( info.m_targetName ) ); - } + parentsInfo.push_back( group->getDataContext().getToStringInfo() ); } - return '/' + stringutilities::join( parents.rbegin(), parents.rend(), '/' ); + + std::ostringstream path; + auto lastFileInfo = &*std::find_if( parentsInfo.begin(), parentsInfo.end(), + []( ToStringInfo const & i ) { return i.hasInputFileInfo(); } ); + for( auto info = parentsInfo.rbegin(); info != parentsInfo.rend(); ++info ) + { + path << ( &*info != lastFileInfo ? + GEOS_FMT( "/{}", info->m_targetName ) : + GEOS_FMT( "/{}({},l.{})", info->m_targetName, info->m_filePath, info->m_line ) ); + } + return path.str(); } DataContext::ToStringInfo GroupContext::getToStringInfo() const From 630efc7980621401a1d2ae104667e15d3b41ded9 Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Wed, 19 Jul 2023 13:34:22 +0200 Subject: [PATCH 60/85] compilation bugfix --- src/coreComponents/dataRepository/Group.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreComponents/dataRepository/Group.hpp b/src/coreComponents/dataRepository/Group.hpp index 1a6d8146bda..c93ede64e58 100644 --- a/src/coreComponents/dataRepository/Group.hpp +++ b/src/coreComponents/dataRepository/Group.hpp @@ -1685,7 +1685,7 @@ Wrapper< T > & Group::registerWrapper( string const & name, return rval; } -template< typename TARGET_DC = DataContext, typename FUNC > +template< typename TARGET_DC, typename FUNC > void Group::forAllDataContext( FUNC func ) const { if( auto * groupCtx = dynamic_cast< TARGET_DC const * >( &getDataContext() ) ) From 7bdf50fa8f95d3d4f16fff95f99869ab2f63b935 Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Thu, 20 Jul 2023 10:46:52 +0200 Subject: [PATCH 61/85] compilation warning fix --- src/coreComponents/dataRepository/DataContext.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreComponents/dataRepository/DataContext.hpp b/src/coreComponents/dataRepository/DataContext.hpp index a9b5fa039e1..a9aed96c838 100644 --- a/src/coreComponents/dataRepository/DataContext.hpp +++ b/src/coreComponents/dataRepository/DataContext.hpp @@ -135,7 +135,7 @@ class DataFileContext final : public DataContext /** * @return the target object name followed by the the file and line declaring it. */ - virtual string toString() const; + string toString() const override; /** * @return the type name in the source file (XML node tag name / attribute name). From 9bcee6aa358096c39e8b8880d9edd8aa4f0a0d79 Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Mon, 21 Aug 2023 14:46:01 +0200 Subject: [PATCH 62/85] removed loadBuffer in favor of loadString (used string_view to support raw string loading) --- .../unitTests/testDruckerPrager.cpp | 6 ++--- .../unitTests/testElasticIsotropic.cpp | 2 +- .../unitTests/testModifiedCamClay.cpp | 3 +-- .../dataRepository/xmlWrapper.cpp | 22 ++++--------------- .../dataRepository/xmlWrapper.hpp | 13 +---------- .../mainInterface/ProblemManager.cpp | 2 +- .../constitutiveTests/testDamage.cpp | 3 +-- .../fluidFlowTests/testCompFlowUtils.hpp | 2 +- .../fluidFlowTests/testSingleFlowUtils.hpp | 2 +- .../testDofManagerUtils.hpp | 2 +- .../meshTests/testMeshGeneration.cpp | 2 +- .../unitTests/meshTests/testVTKImport.cpp | 2 +- .../testConformingVirtualElementOrder1.cpp | 4 ++-- 13 files changed, 18 insertions(+), 47 deletions(-) diff --git a/src/coreComponents/constitutive/unitTests/testDruckerPrager.cpp b/src/coreComponents/constitutive/unitTests/testDruckerPrager.cpp index 2c5aec66ac8..bf2e24ef63d 100644 --- a/src/coreComponents/constitutive/unitTests/testDruckerPrager.cpp +++ b/src/coreComponents/constitutive/unitTests/testDruckerPrager.cpp @@ -72,8 +72,7 @@ void testDruckerPragerDriver() ""; xmlWrapper::xmlDocument xmlDocument; - xmlWrapper::xmlResult xmlResult = xmlDocument.loadBuffer( inputStream.c_str(), - inputStream.size() ); + xmlWrapper::xmlResult xmlResult = xmlDocument.loadString( inputStream ); if( !xmlResult ) { GEOS_LOG_RANK_0( "XML parsed with errors!" ); @@ -189,8 +188,7 @@ void testDruckerPragerExtendedDriver() ""; xmlWrapper::xmlDocument xmlDocument; - xmlWrapper::xmlResult xmlResult = xmlDocument.loadBuffer( inputStream.c_str(), - inputStream.size() ); + xmlWrapper::xmlResult xmlResult = xmlDocument.loadString( inputStream ); if( !xmlResult ) { GEOS_LOG_RANK_0( "XML parsed with errors!" ); diff --git a/src/coreComponents/constitutive/unitTests/testElasticIsotropic.cpp b/src/coreComponents/constitutive/unitTests/testElasticIsotropic.cpp index 25f8b086000..5592872d7e1 100644 --- a/src/coreComponents/constitutive/unitTests/testElasticIsotropic.cpp +++ b/src/coreComponents/constitutive/unitTests/testElasticIsotropic.cpp @@ -71,7 +71,7 @@ TEST( ElasticIsotropicTests, testStateUpdatePoint ) ""; xmlWrapper::xmlDocument xmlDocument; - xmlWrapper::xmlResult xmlResult = xmlDocument.loadBuffer( inputStream.c_str(), inputStream.size() ); + xmlWrapper::xmlResult xmlResult = xmlDocument.loadString( inputStream ); if( !xmlResult ) { GEOS_LOG_RANK_0( "XML parsed with errors!" ); diff --git a/src/coreComponents/constitutive/unitTests/testModifiedCamClay.cpp b/src/coreComponents/constitutive/unitTests/testModifiedCamClay.cpp index 1ae9ba5831c..253971bccd1 100644 --- a/src/coreComponents/constitutive/unitTests/testModifiedCamClay.cpp +++ b/src/coreComponents/constitutive/unitTests/testModifiedCamClay.cpp @@ -86,8 +86,7 @@ void testModifiedCamClayDriver() ""; xmlWrapper::xmlDocument xmlDocument; - xmlWrapper::xmlResult xmlResult = xmlDocument.loadBuffer( inputStream.c_str(), - inputStream.size() ); + xmlWrapper::xmlResult xmlResult = xmlDocument.loadString( inputStream ); if( !xmlResult ) { GEOS_LOG_RANK_0( "XML parsed with errors!" ); diff --git a/src/coreComponents/dataRepository/xmlWrapper.cpp b/src/coreComponents/dataRepository/xmlWrapper.cpp index 218746b0fd1..c9631834bbc 100644 --- a/src/coreComponents/dataRepository/xmlWrapper.cpp +++ b/src/coreComponents/dataRepository/xmlWrapper.cpp @@ -210,15 +210,16 @@ xmlDocument::xmlDocument(): m_rootFilePath( "CodeIncludedXML" + std::to_string( documentId++ ) ) {} -xmlResult xmlDocument::loadString( string const & contents, bool loadNodeFileInfo ) +xmlResult xmlDocument::loadString( string_view content, bool loadNodeFileInfo ) { - xmlResult result = pugiDocument.load_string( contents.c_str(), pugi::parse_default ); + xmlResult result = pugiDocument.load_buffer( content.data(), content.size(), + pugi::parse_default, pugi::encoding_auto ); // keeping a copy of original buffer to allow line retrieval if( loadNodeFileInfo ) { m_originalBuffers.clear(); - m_originalBuffers[m_rootFilePath] = contents; + m_originalBuffers[m_rootFilePath] = content; addNodeFileInfo( getFirstChild(), m_rootFilePath ); } @@ -245,21 +246,6 @@ xmlResult xmlDocument::loadFile( string const & path, bool loadNodeFileInfo ) return result; } -xmlResult xmlDocument::loadBuffer( const void * contents, size_t size, bool loadNodeFileInfo ) -{ - xmlResult result = pugiDocument.load_buffer( contents, size, pugi::parse_default, pugi::encoding_auto ); - - //keeping a copy of original buffer - if( loadNodeFileInfo ) - { - m_originalBuffers.clear(); - m_originalBuffers[m_rootFilePath] = string( ( char const * )contents, size ); - - addNodeFileInfo( getFirstChild(), m_rootFilePath ); - } - - return result; -} xmlNode xmlDocument::appendChild( string const & name ) { return pugiDocument.append_child( name.c_str() ); } diff --git a/src/coreComponents/dataRepository/xmlWrapper.hpp b/src/coreComponents/dataRepository/xmlWrapper.hpp index c5359ebe191..43ad9a8f4cd 100644 --- a/src/coreComponents/dataRepository/xmlWrapper.hpp +++ b/src/coreComponents/dataRepository/xmlWrapper.hpp @@ -192,7 +192,7 @@ class xmlDocument * @param loadNodeFileInfo Load the node source file info, allowing getNodePosition() to work. * @return an xmlResult object representing the parsing resulting status. */ - xmlResult loadString( string const & contents, bool loadNodeFileInfo = false ); + xmlResult loadString( string_view content, bool loadNodeFileInfo = false ); /** * @brief Load document from file. Free any previously loaded xml tree. @@ -203,17 +203,6 @@ class xmlDocument */ xmlResult loadFile( string const & path, bool loadNodeFileInfo = false ); - /** - * @brief Load document from buffer. Copies/converts the buffer, so it may be deleted or changed - * after the function returns. Free any previously loaded xml tree. - * Wrapper of pugi::xml_document::loadBuffer() method. - * @param contents the buffer containing the document content - * @param size the size of the buffer in bytes - * @param loadNodeFileInfo Load the node source file info, allowing getNodePosition() to work. - * @return an xmlResult object representing the parsing resulting status. - */ - xmlResult loadBuffer( const void * contents, size_t size, bool loadNodeFileInfo = false ); - /** * @brief Add a root element to the document * @param name the tag name of the node to add diff --git a/src/coreComponents/mainInterface/ProblemManager.cpp b/src/coreComponents/mainInterface/ProblemManager.cpp index f516cef7104..45587ea34c3 100644 --- a/src/coreComponents/mainInterface/ProblemManager.cpp +++ b/src/coreComponents/mainInterface/ProblemManager.cpp @@ -398,7 +398,7 @@ void ProblemManager::parseInputString( string const & xmlString ) { // Load preprocessed xml file xmlWrapper::xmlDocument xmlDocument; - xmlWrapper::xmlResult xmlResult = xmlDocument.loadBuffer( xmlString.c_str(), xmlString.length(), true ); + xmlWrapper::xmlResult xmlResult = xmlDocument.loadString( xmlString, true ); GEOS_THROW_IF( !xmlResult, GEOS_FMT( "Errors found while parsing XML string\nDescription: {}\nOffset: {}", xmlResult.description(), xmlResult.offset ), InputError ); diff --git a/src/coreComponents/unitTests/constitutiveTests/testDamage.cpp b/src/coreComponents/unitTests/constitutiveTests/testDamage.cpp index e24645a23f9..972449c8b96 100644 --- a/src/coreComponents/unitTests/constitutiveTests/testDamage.cpp +++ b/src/coreComponents/unitTests/constitutiveTests/testDamage.cpp @@ -46,8 +46,7 @@ TEST( DamageTests, testDamageSpectral ) ""; xmlWrapper::xmlDocument xmlDocument; - xmlWrapper::xmlResult xmlResult = xmlDocument.loadBuffer( inputStream.c_str(), - inputStream.size() ); + xmlWrapper::xmlResult xmlResult = xmlDocument.loadString( inputStream ); if( !xmlResult ) { GEOS_LOG_RANK_0( "XML parsed with errors!" ); diff --git a/src/coreComponents/unitTests/fluidFlowTests/testCompFlowUtils.hpp b/src/coreComponents/unitTests/fluidFlowTests/testCompFlowUtils.hpp index 5a192240183..eebfa0a114c 100644 --- a/src/coreComponents/unitTests/fluidFlowTests/testCompFlowUtils.hpp +++ b/src/coreComponents/unitTests/fluidFlowTests/testCompFlowUtils.hpp @@ -183,7 +183,7 @@ void fillNumericalJacobian( arrayView1d< real64 const > const & residual, void setupProblemFromXML( ProblemManager & problemManager, char const * const xmlInput ) { xmlWrapper::xmlDocument xmlDocument; - xmlWrapper::xmlResult xmlResult = xmlDocument.loadBuffer( xmlInput, strlen( xmlInput ) ); + xmlWrapper::xmlResult xmlResult = xmlDocument.loadString( xmlInput ); if( !xmlResult ) { GEOS_LOG_RANK_0( "XML parsed with errors!" ); diff --git a/src/coreComponents/unitTests/fluidFlowTests/testSingleFlowUtils.hpp b/src/coreComponents/unitTests/fluidFlowTests/testSingleFlowUtils.hpp index c3c58a63ea3..2d9a1ee6ea1 100644 --- a/src/coreComponents/unitTests/fluidFlowTests/testSingleFlowUtils.hpp +++ b/src/coreComponents/unitTests/fluidFlowTests/testSingleFlowUtils.hpp @@ -75,7 +75,7 @@ void fillNumericalJacobian( arrayView1d< real64 const > const & residual, void setupProblemFromXML( ProblemManager & problemManager, char const * const xmlInput ) { xmlWrapper::xmlDocument xmlDocument; - xmlWrapper::xmlResult xmlResult = xmlDocument.loadBuffer( xmlInput, strlen( xmlInput ) ); + xmlWrapper::xmlResult xmlResult = xmlDocument.loadString( xmlInput ); if( !xmlResult ) { GEOS_LOG_RANK_0( "XML parsed with errors!" ); diff --git a/src/coreComponents/unitTests/linearAlgebraTests/testDofManagerUtils.hpp b/src/coreComponents/unitTests/linearAlgebraTests/testDofManagerUtils.hpp index 06f28489f5c..e63c90f7631 100644 --- a/src/coreComponents/unitTests/linearAlgebraTests/testDofManagerUtils.hpp +++ b/src/coreComponents/unitTests/linearAlgebraTests/testDofManagerUtils.hpp @@ -38,7 +38,7 @@ namespace testing void setupProblemFromXML( ProblemManager * const problemManager, char const * const xmlInput ) { xmlWrapper::xmlDocument xmlDocument; - xmlWrapper::xmlResult xmlResult = xmlDocument.loadBuffer( xmlInput, strlen( xmlInput ) ); + xmlWrapper::xmlResult xmlResult = xmlDocument.loadString( xmlInput ); if( !xmlResult ) { GEOS_LOG_RANK_0( "XML parsed with errors!" ); diff --git a/src/coreComponents/unitTests/meshTests/testMeshGeneration.cpp b/src/coreComponents/unitTests/meshTests/testMeshGeneration.cpp index 8bc67e65dbe..ab91b136ee2 100644 --- a/src/coreComponents/unitTests/meshTests/testMeshGeneration.cpp +++ b/src/coreComponents/unitTests/meshTests/testMeshGeneration.cpp @@ -99,7 +99,7 @@ class MeshGenerationTest : public ::testing::Test maxCoordInX, maxCoordInY, maxCoordInZ, numElemsInX, numElemsInY, numElemsInZ ); xmlWrapper::xmlDocument xmlDocument; - xmlWrapper::xmlResult xmlResult = xmlDocument.loadBuffer( inputStream.c_str(), inputStream.size() ); + xmlWrapper::xmlResult xmlResult = xmlDocument.loadString( inputStream ); ASSERT_TRUE( xmlResult ); xmlWrapper::xmlNode xmlProblemNode = xmlDocument.getChild( dataRepository::keys::ProblemManager ); diff --git a/src/coreComponents/unitTests/meshTests/testVTKImport.cpp b/src/coreComponents/unitTests/meshTests/testVTKImport.cpp index 971bd89bf9a..ebf8173b8bc 100644 --- a/src/coreComponents/unitTests/meshTests/testVTKImport.cpp +++ b/src/coreComponents/unitTests/meshTests/testVTKImport.cpp @@ -38,7 +38,7 @@ void TestMeshImport( string const & meshFilePath, V const & validate ) { string const meshNode = GEOS_FMT( R"()", meshFilePath ); xmlWrapper::xmlDocument xmlDocument; - xmlDocument.loadBuffer( meshNode.c_str(), meshNode.size() ); + xmlDocument.loadString( meshNode ); xmlWrapper::xmlNode xmlMeshNode = xmlDocument.getChild( "Mesh" ); conduit::Node node; diff --git a/src/coreComponents/unitTests/virtualElementTests/testConformingVirtualElementOrder1.cpp b/src/coreComponents/unitTests/virtualElementTests/testConformingVirtualElementOrder1.cpp index 4b7bc185ef3..1120fd74bec 100644 --- a/src/coreComponents/unitTests/virtualElementTests/testConformingVirtualElementOrder1.cpp +++ b/src/coreComponents/unitTests/virtualElementTests/testConformingVirtualElementOrder1.cpp @@ -280,7 +280,7 @@ TEST( ConformingVirtualElementOrder1, hexahedra ) ""; xmlWrapper::xmlDocument inputFile; - xmlWrapper::xmlResult xmlResult = inputFile.loadBuffer( inputStream.c_str(), inputStream.size()); + xmlWrapper::xmlResult xmlResult = inputFile.loadString( inputStream ); if( !xmlResult ) { GEOS_LOG_RANK_0( "XML parsed with errors!" ); @@ -333,7 +333,7 @@ TEST( ConformingVirtualElementOrder1, wedges ) " " ""; xmlWrapper::xmlDocument inputFile; - xmlWrapper::xmlResult xmlResult = inputFile.loadBuffer( inputStream.c_str(), inputStream.size()); + xmlWrapper::xmlResult xmlResult = inputFile.loadString( inputStream ); if( !xmlResult ) { GEOS_LOG_RANK_0( "XML parsed with errors!" ); From 60599e57c16b79e0cdf01fa57d3b549145e484e1 Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Mon, 21 Aug 2023 15:22:32 +0200 Subject: [PATCH 63/85] missing a pugi::node_declaration wrapping --- src/coreComponents/fileIO/vtk/VTKVTMWriter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreComponents/fileIO/vtk/VTKVTMWriter.cpp b/src/coreComponents/fileIO/vtk/VTKVTMWriter.cpp index 8ecdd5091ee..a7d3ba06be2 100644 --- a/src/coreComponents/fileIO/vtk/VTKVTMWriter.cpp +++ b/src/coreComponents/fileIO/vtk/VTKVTMWriter.cpp @@ -26,7 +26,7 @@ VTKVTMWriter::VTKVTMWriter( string filePath ) : m_filePath( std::move( filePath ) ) { // Declaration of XML version - auto declarationNode = m_document.appendChild( pugi::node_declaration ); + auto declarationNode = m_document.appendChild( xmlWrapper::xmlTypes::node_declaration ); declarationNode.append_attribute( "version" ) = "1.0"; // Declaration of the node VTKFile From ebffe3f55de734f3db1b61ab676f816148563ac6 Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Mon, 21 Aug 2023 15:46:38 +0200 Subject: [PATCH 64/85] changed to /** doxygen syntax --- .../dataRepository/DataContext.hpp | 21 +++++++++++++----- .../dataRepository/GroupContext.hpp | 16 ++++++++++---- .../dataRepository/xmlWrapper.hpp | 22 ++++++++++++++----- 3 files changed, 44 insertions(+), 15 deletions(-) diff --git a/src/coreComponents/dataRepository/DataContext.hpp b/src/coreComponents/dataRepository/DataContext.hpp index a9aed96c838..b3d641dc390 100644 --- a/src/coreComponents/dataRepository/DataContext.hpp +++ b/src/coreComponents/dataRepository/DataContext.hpp @@ -30,10 +30,14 @@ namespace dataRepository { -/// DataContext is an abstract class storing contextual information on an object: -/// - Either its line position in a file (if applicable, implementation in DataFileContext), -/// - or its location in the data hierarchy (implementation in GroupContext and WrapperContext). -/// Typically, the target object contains an unique_ptr< DataContext > instance of this class. +/** + * @class DataContext + * + * DataContext is an abstract class storing contextual information on an object: + * - Either its line position in a file (if applicable, implementation in DataFileContext), + * - or its location in the data hierarchy (implementation in GroupContext and WrapperContext). + * Typically, the target object contains an unique_ptr< DataContext > instance of this class. + */ class DataContext { public: @@ -111,8 +115,13 @@ class DataContext }; -/// Stores information to retrieve where a target object has been declared in the input source -/// file (e.g. XML). +/// +/** + * @class DataContext + * + * Stores information to retrieve where a target object has been declared in the input source + * file (e.g. XML). + */ class DataFileContext final : public DataContext { public: diff --git a/src/coreComponents/dataRepository/GroupContext.hpp b/src/coreComponents/dataRepository/GroupContext.hpp index 0a264e1a3be..d43b6506a99 100644 --- a/src/coreComponents/dataRepository/GroupContext.hpp +++ b/src/coreComponents/dataRepository/GroupContext.hpp @@ -29,8 +29,12 @@ namespace dataRepository { -/// Helps to know where a Group is in the hierarchy. -/// See DataContext class for more info. +/** + * @class GroupContext + * + * Helps to know where a Group is in the hierarchy. + * See DataContext class for more info. + */ class GroupContext : public DataContext { public: @@ -70,8 +74,12 @@ class GroupContext : public DataContext ToStringInfo getToStringInfo() const override; }; -/// Dedicated implementation of GroupContext for Wrapper. -/// See DataContext class for more info. +/** + * @class WrapperContext + * + * Dedicated implementation of GroupContext for Wrapper. + * See DataContext class for more info. + */ class WrapperContext final : public GroupContext { public: diff --git a/src/coreComponents/dataRepository/xmlWrapper.hpp b/src/coreComponents/dataRepository/xmlWrapper.hpp index 43ad9a8f4cd..c2c49595e41 100644 --- a/src/coreComponents/dataRepository/xmlWrapper.hpp +++ b/src/coreComponents/dataRepository/xmlWrapper.hpp @@ -65,7 +65,11 @@ using xmlTypes = pugi::xml_node_type; class xmlDocument; -/// Stores the source file path, and position in the file of a xml attribute. +/** + * @struct xmlAttributePos + * + * Stores the source file path, and position in the file of a xml attribute. + */ struct xmlAttributePos { /// Path of the file containing this element @@ -98,7 +102,11 @@ struct xmlAttributePos string toString() const; }; -/// Used to retrieve the position of dataRepository::Wrapper in XML +/** + * @struct xmlNodePos + * + * Used to retrieve the position of dataRepository::Wrapper in XML + */ struct xmlNodePos : xmlAttributePos { /// Reference of the main xmlDocument that contains all original file buffers. @@ -125,9 +133,13 @@ struct xmlNodePos : xmlAttributePos xmlAttributePos getAttributeLine( string const & attName ) const; }; -/// Wrapper class for the type of xml document. -/// This class exists to intercept file / string loading methods, and to keep the loaded buffers, -/// in order to retrieve the source file and line of nodes and attributes. +/** + * @class xmlDocument + * + * Wrapper class for the type of xml document. + * This class exists to intercept file / string loading methods, and to keep the loaded buffers, + * in order to retrieve the source file and line of nodes and attributes. + */ class xmlDocument { public: From 9795f54eace121e999b5f260bb8823c5d68124a6 Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Tue, 22 Aug 2023 12:00:41 +0200 Subject: [PATCH 65/85] Refactorings --- src/coreComponents/dataRepository/DataContext.cpp | 8 +++----- src/coreComponents/dataRepository/DataContext.hpp | 15 +++++++++------ .../dataRepository/GroupContext.cpp | 6 +++--- src/coreComponents/dataRepository/xmlWrapper.cpp | 2 +- src/coreComponents/dataRepository/xmlWrapper.hpp | 4 ++-- src/coreComponents/fileIO/vtk/VTKVTMWriter.cpp | 2 +- src/coreComponents/schema/schemaUtilities.cpp | 4 ++-- 7 files changed, 21 insertions(+), 20 deletions(-) diff --git a/src/coreComponents/dataRepository/DataContext.cpp b/src/coreComponents/dataRepository/DataContext.cpp index 052a8ae4d33..bb23227a729 100644 --- a/src/coreComponents/dataRepository/DataContext.cpp +++ b/src/coreComponents/dataRepository/DataContext.cpp @@ -34,15 +34,13 @@ std::ostream & operator<<( std::ostream & os, DataContext const & ctx ) return os; } -DataContext::ToStringInfo::ToStringInfo( string_view targetName, string_view filePath, int line ): +DataContext::ToStringInfo::ToStringInfo( string const & targetName, string const & filePath, size_t line ): m_targetName( targetName ), m_filePath( filePath ), m_line( line ) {} -DataContext::ToStringInfo::ToStringInfo( string_view targetName ): - m_targetName( targetName ), - m_filePath(), - m_line( xmlWrapper::xmlDocument::npos ) +DataContext::ToStringInfo::ToStringInfo( string const & targetName ): + DataContext::ToStringInfo::ToStringInfo( targetName, "", xmlWrapper::xmlDocument::npos ) {} bool DataContext::ToStringInfo::hasInputFileInfo() const { return !m_filePath.empty() && m_line != xmlWrapper::xmlDocument::npos; } diff --git a/src/coreComponents/dataRepository/DataContext.hpp b/src/coreComponents/dataRepository/DataContext.hpp index b3d641dc390..a04d6d64f6b 100644 --- a/src/coreComponents/dataRepository/DataContext.hpp +++ b/src/coreComponents/dataRepository/DataContext.hpp @@ -82,9 +82,9 @@ class DataContext struct ToStringInfo { /// the targetName of the DataContext - string_view m_targetName; + string m_targetName; /// the file path of the DataFileContext, if it exists (an empty string otherwise) - string_view m_filePath; + string m_filePath; /// the file line of the DataFileContext, if it exists (an empty string otherwise) size_t m_line; @@ -94,12 +94,12 @@ class DataContext * @param filePath the input file path where the target is declared. * @param line the line in the file where the target is declared. */ - ToStringInfo( string_view targetName, string_view filePath, int line ); + ToStringInfo( string const & targetName, string const & filePath, size_t line ); /** * @brief Construct a new ToStringInfo object from a DataContext that has no input file info. * @param targetName the target name. */ - ToStringInfo( string_view targetName ); + ToStringInfo( string const & targetName ); /** * @return true if a location has been found to declare the target in an input file. */ @@ -224,8 +224,11 @@ struct GEOS_FMT_NS::formatter< geos::dataRepository::DataContext > * @param ctx formatting state consisting of the formatting arguments and the output iterator * @return iterator to the output buffer (leaved unchanged) */ - constexpr auto parse( format_parse_context & ctx ) - { return ctx.begin(); } + auto parse( format_parse_context & ctx ) + { + GEOS_ERROR( "DataContext parsing is not implemented." ); + return ctx.begin(); + } }; #endif /* GEOS_DATAREPOSITORY_DATACONTEXT_HPP_ */ diff --git a/src/coreComponents/dataRepository/GroupContext.cpp b/src/coreComponents/dataRepository/GroupContext.cpp index c3f3e8db86e..524e44996a7 100644 --- a/src/coreComponents/dataRepository/GroupContext.cpp +++ b/src/coreComponents/dataRepository/GroupContext.cpp @@ -41,11 +41,11 @@ string GroupContext::toString() const } std::ostringstream path; - auto lastFileInfo = &*std::find_if( parentsInfo.begin(), parentsInfo.end(), - []( ToStringInfo const & i ) { return i.hasInputFileInfo(); } ); + auto lastFileInfo = std::find_if( parentsInfo.begin(), parentsInfo.end(), + []( ToStringInfo const & i ) { return i.hasInputFileInfo(); } ); for( auto info = parentsInfo.rbegin(); info != parentsInfo.rend(); ++info ) { - path << ( &*info != lastFileInfo ? + path << ( info.base() != lastFileInfo ? GEOS_FMT( "/{}", info->m_targetName ) : GEOS_FMT( "/{}({},l.{})", info->m_targetName, info->m_filePath, info->m_line ) ); } diff --git a/src/coreComponents/dataRepository/xmlWrapper.cpp b/src/coreComponents/dataRepository/xmlWrapper.cpp index c9631834bbc..c1e970b3404 100644 --- a/src/coreComponents/dataRepository/xmlWrapper.cpp +++ b/src/coreComponents/dataRepository/xmlWrapper.cpp @@ -250,7 +250,7 @@ xmlResult xmlDocument::loadFile( string const & path, bool loadNodeFileInfo ) xmlNode xmlDocument::appendChild( string const & name ) { return pugiDocument.append_child( name.c_str() ); } -xmlNode xmlDocument::appendChild( xmlTypes type ) +xmlNode xmlDocument::appendChild( xmlNodeType type ) { return pugiDocument.append_child( type ); } bool xmlDocument::saveFile( string const & path ) const diff --git a/src/coreComponents/dataRepository/xmlWrapper.hpp b/src/coreComponents/dataRepository/xmlWrapper.hpp index c2c49595e41..3f19f99149d 100644 --- a/src/coreComponents/dataRepository/xmlWrapper.hpp +++ b/src/coreComponents/dataRepository/xmlWrapper.hpp @@ -61,7 +61,7 @@ using xmlNode = pugi::xml_node; using xmlAttribute = pugi::xml_attribute; /// Alias for the type variant of an xml node. -using xmlTypes = pugi::xml_node_type; +using xmlNodeType = pugi::xml_node_type; class xmlDocument; @@ -227,7 +227,7 @@ class xmlDocument * As an exemple, node_declaration is useful to add the "" node. * @return the added node */ - xmlNode appendChild( xmlTypes type = xmlTypes::node_element ); + xmlNode appendChild( xmlNodeType type = xmlNodeType::node_element ); /** * @brief Save the XML to a file diff --git a/src/coreComponents/fileIO/vtk/VTKVTMWriter.cpp b/src/coreComponents/fileIO/vtk/VTKVTMWriter.cpp index a7d3ba06be2..b37ad3d79e1 100644 --- a/src/coreComponents/fileIO/vtk/VTKVTMWriter.cpp +++ b/src/coreComponents/fileIO/vtk/VTKVTMWriter.cpp @@ -26,7 +26,7 @@ VTKVTMWriter::VTKVTMWriter( string filePath ) : m_filePath( std::move( filePath ) ) { // Declaration of XML version - auto declarationNode = m_document.appendChild( xmlWrapper::xmlTypes::node_declaration ); + auto declarationNode = m_document.appendChild( xmlWrapper::xmlNodeType::node_declaration ); declarationNode.append_attribute( "version" ) = "1.0"; // Declaration of the node VTKFile diff --git a/src/coreComponents/schema/schemaUtilities.cpp b/src/coreComponents/schema/schemaUtilities.cpp index 57496704bd2..59500a04385 100644 --- a/src/coreComponents/schema/schemaUtilities.cpp +++ b/src/coreComponents/schema/schemaUtilities.cpp @@ -233,7 +233,7 @@ void SchemaConstruction( Group & group, commentString += " => " + stringutilities::join( registrars.begin(), registrars.end(), ", " ); } - xmlWrapper::xmlNode commentNode = targetTypeDefNode.append_child( xmlWrapper::xmlTypes::node_comment ); + xmlWrapper::xmlNode commentNode = targetTypeDefNode.append_child( xmlWrapper::xmlNodeType::node_comment ); commentNode.set_value( commentString.c_str()); @@ -289,7 +289,7 @@ void SchemaConstruction( Group & group, // Only add this attribute if not present if( targetTypeDefNode.find_child_by_attribute( "xsd:attribute", "name", "name" ).empty()) { - xmlWrapper::xmlNode commentNode = targetTypeDefNode.append_child( xmlWrapper::xmlTypes::node_comment ); + xmlWrapper::xmlNode commentNode = targetTypeDefNode.append_child( xmlWrapper::xmlNodeType::node_comment ); commentNode.set_value( "name => A name is required for any non-unique nodes" ); xmlWrapper::xmlNode attributeNode = targetTypeDefNode.append_child( "xsd:attribute" ); From 52624227a818172236eda5882bf4df0ae5a2ea15 Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Tue, 22 Aug 2023 13:43:52 +0200 Subject: [PATCH 66/85] Added a dedicated WrapperContext cpp & hpp file --- .../dataRepository/CMakeLists.txt | 2 + .../dataRepository/GroupContext.cpp | 14 ----- .../dataRepository/GroupContext.hpp | 28 --------- .../dataRepository/WrapperBase.cpp | 2 +- .../dataRepository/WrapperContext.cpp | 42 +++++++++++++ .../dataRepository/WrapperContext.hpp | 62 +++++++++++++++++++ 6 files changed, 107 insertions(+), 43 deletions(-) create mode 100644 src/coreComponents/dataRepository/WrapperContext.cpp create mode 100644 src/coreComponents/dataRepository/WrapperContext.hpp diff --git a/src/coreComponents/dataRepository/CMakeLists.txt b/src/coreComponents/dataRepository/CMakeLists.txt index fa77df2e871..346fb14073e 100644 --- a/src/coreComponents/dataRepository/CMakeLists.txt +++ b/src/coreComponents/dataRepository/CMakeLists.txt @@ -24,6 +24,7 @@ set( dataRepository_headers xmlWrapper.hpp DataContext.hpp GroupContext.hpp + WrapperContext.hpp ) # @@ -39,6 +40,7 @@ set( dataRepository_sources xmlWrapper.cpp DataContext.cpp GroupContext.cpp + WrapperContext.cpp ) set( dependencyList ${parallelDeps} codingUtilities pugixml ) diff --git a/src/coreComponents/dataRepository/GroupContext.cpp b/src/coreComponents/dataRepository/GroupContext.cpp index 524e44996a7..eb275229585 100644 --- a/src/coreComponents/dataRepository/GroupContext.cpp +++ b/src/coreComponents/dataRepository/GroupContext.cpp @@ -56,19 +56,5 @@ DataContext::ToStringInfo GroupContext::getToStringInfo() const { return ToStringInfo( m_targetName ); } -WrapperContext::WrapperContext( WrapperBase & wrapper ): - GroupContext( wrapper.getParent(), wrapper.getParent().getName() + '/' + wrapper.getName() ), - m_typeName( wrapper.getName() ) -{} - -string WrapperContext::toString() const -{ - ToStringInfo const info = m_group.getDataContext().getToStringInfo(); - return info.hasInputFileInfo() ? - GEOS_FMT( "{} ({}, l.{})", m_targetName, info.m_filePath, info.m_line ) : - GEOS_FMT( "{}/{}", m_group.getDataContext().toString(), m_typeName ); -} - - } /* namespace dataRepository */ } /* namespace geos */ diff --git a/src/coreComponents/dataRepository/GroupContext.hpp b/src/coreComponents/dataRepository/GroupContext.hpp index d43b6506a99..250182cda7c 100644 --- a/src/coreComponents/dataRepository/GroupContext.hpp +++ b/src/coreComponents/dataRepository/GroupContext.hpp @@ -20,7 +20,6 @@ #define GEOS_DATAREPOSITORY_GROUPCONTEXT_HPP_ #include "DataContext.hpp" -#include "WrapperBase.hpp" #include "Group.hpp" namespace geos @@ -74,33 +73,6 @@ class GroupContext : public DataContext ToStringInfo getToStringInfo() const override; }; -/** - * @class WrapperContext - * - * Dedicated implementation of GroupContext for Wrapper. - * See DataContext class for more info. - */ -class WrapperContext final : public GroupContext -{ -public: - - /** - * @brief Construct a new WrapperContext object - * @param wrapper the target Wrapper object - */ - WrapperContext( WrapperBase & wrapper ); - -private: - - string const m_typeName; - - /** - * @return the parent group DataContext followed by the wrapper name. - */ - string toString() const override; - -}; - } /* namespace dataRepository */ } /* namespace geos */ diff --git a/src/coreComponents/dataRepository/WrapperBase.cpp b/src/coreComponents/dataRepository/WrapperBase.cpp index 34c4d642bd5..fca8cccdaa5 100644 --- a/src/coreComponents/dataRepository/WrapperBase.cpp +++ b/src/coreComponents/dataRepository/WrapperBase.cpp @@ -18,7 +18,7 @@ #include "Group.hpp" #include "RestartFlags.hpp" -#include "GroupContext.hpp" +#include "WrapperContext.hpp" namespace geos diff --git a/src/coreComponents/dataRepository/WrapperContext.cpp b/src/coreComponents/dataRepository/WrapperContext.cpp new file mode 100644 index 00000000000..b9aa00ff731 --- /dev/null +++ b/src/coreComponents/dataRepository/WrapperContext.cpp @@ -0,0 +1,42 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2018-2020 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2020 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2018-2020 TotalEnergies + * Copyright (c) 2019- GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +/** + * @file WrapperContext.cpp + */ + +#include "WrapperContext.hpp" + +namespace geos +{ +namespace dataRepository +{ + + +WrapperContext::WrapperContext( WrapperBase & wrapper ): + GroupContext( wrapper.getParent(), wrapper.getParent().getName() + '/' + wrapper.getName() ), + m_typeName( wrapper.getName() ) +{} + +string WrapperContext::toString() const +{ + ToStringInfo const info = m_group.getDataContext().getToStringInfo(); + return info.hasInputFileInfo() ? + GEOS_FMT( "{} ({}, l.{})", m_targetName, info.m_filePath, info.m_line ) : + GEOS_FMT( "{}/{}", m_group.getDataContext().toString(), m_typeName ); +} + + +} /* namespace dataRepository */ +} /* namespace geos */ diff --git a/src/coreComponents/dataRepository/WrapperContext.hpp b/src/coreComponents/dataRepository/WrapperContext.hpp new file mode 100644 index 00000000000..bfa6c08d5e6 --- /dev/null +++ b/src/coreComponents/dataRepository/WrapperContext.hpp @@ -0,0 +1,62 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2018-2020 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2020 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2018-2020 TotalEnergies + * Copyright (c) 2019- GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +/** + * @file WrapperContext.hpp + */ + +#ifndef GEOS_DATAREPOSITORY_WRAPPERCONTEXT_HPP_ +#define GEOS_DATAREPOSITORY_WRAPPERCONTEXT_HPP_ + +#include "GroupContext.hpp" +#include "WrapperBase.hpp" + +namespace geos +{ +namespace dataRepository +{ + + +/** + * @class WrapperContext + * + * Dedicated implementation of GroupContext for Wrapper. + * See DataContext class for more info. + */ +class WrapperContext final : public GroupContext +{ +public: + + /** + * @brief Construct a new WrapperContext object + * @param wrapper the target Wrapper object + */ + WrapperContext( WrapperBase & wrapper ); + +private: + + string const m_typeName; + + /** + * @return the parent group DataContext followed by the wrapper name. + */ + string toString() const override; + +}; + + +} /* namespace dataRepository */ +} /* namespace geos */ + +#endif /* GEOS_DATAREPOSITORY_WRAPPERCONTEXT_HPP_ */ From 83c5116529aaf0f31d1dcb422ccf767270a6c067 Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Tue, 22 Aug 2023 13:44:06 +0200 Subject: [PATCH 67/85] refactoring --- src/coreComponents/dataRepository/WrapperBase.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/coreComponents/dataRepository/WrapperBase.cpp b/src/coreComponents/dataRepository/WrapperBase.cpp index fca8cccdaa5..c3d9a94938a 100644 --- a/src/coreComponents/dataRepository/WrapperBase.cpp +++ b/src/coreComponents/dataRepository/WrapperBase.cpp @@ -130,9 +130,10 @@ void WrapperBase::processInputException( std::exception const & ex, oss << "***** XML parsing error at node "; if( nodePos.isFound() ) { + string const & filePath = attPos.isFound() ? attPos.filePath : nodePos.filePath; + int const line = attPos.isFound() ? attPos.line : nodePos.line; oss << "named " << m_parent->getName() << ", attribute " << getName() - << " (" << splitPath( attPos.isFound() ? attPos.filePath : nodePos.filePath ).second - << ", l." << ( attPos.isFound() ? attPos.line : nodePos.line ) << ")."; + << " (" << splitPath( filePath ).second << ", l." << line << ")."; } else { From 4501dc4467943472b1f20c9f24ef57656f7a0bb8 Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Wed, 23 Aug 2023 16:18:27 +0200 Subject: [PATCH 68/85] uncrustify --- src/coreComponents/dataRepository/DataContext.hpp | 7 +++---- src/coreComponents/dataRepository/GroupContext.hpp | 2 +- src/coreComponents/dataRepository/WrapperContext.hpp | 2 +- src/coreComponents/dataRepository/xmlWrapper.hpp | 6 +++--- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/coreComponents/dataRepository/DataContext.hpp b/src/coreComponents/dataRepository/DataContext.hpp index a04d6d64f6b..07cd4dfe148 100644 --- a/src/coreComponents/dataRepository/DataContext.hpp +++ b/src/coreComponents/dataRepository/DataContext.hpp @@ -32,7 +32,7 @@ namespace dataRepository /** * @class DataContext - * + * * DataContext is an abstract class storing contextual information on an object: * - Either its line position in a file (if applicable, implementation in DataFileContext), * - or its location in the data hierarchy (implementation in GroupContext and WrapperContext). @@ -115,10 +115,9 @@ class DataContext }; -/// /** * @class DataContext - * + * * Stores information to retrieve where a target object has been declared in the input source * file (e.g. XML). */ @@ -225,7 +224,7 @@ struct GEOS_FMT_NS::formatter< geos::dataRepository::DataContext > * @return iterator to the output buffer (leaved unchanged) */ auto parse( format_parse_context & ctx ) - { + { GEOS_ERROR( "DataContext parsing is not implemented." ); return ctx.begin(); } diff --git a/src/coreComponents/dataRepository/GroupContext.hpp b/src/coreComponents/dataRepository/GroupContext.hpp index 250182cda7c..5c09d547e06 100644 --- a/src/coreComponents/dataRepository/GroupContext.hpp +++ b/src/coreComponents/dataRepository/GroupContext.hpp @@ -30,7 +30,7 @@ namespace dataRepository /** * @class GroupContext - * + * * Helps to know where a Group is in the hierarchy. * See DataContext class for more info. */ diff --git a/src/coreComponents/dataRepository/WrapperContext.hpp b/src/coreComponents/dataRepository/WrapperContext.hpp index bfa6c08d5e6..d948a0334b9 100644 --- a/src/coreComponents/dataRepository/WrapperContext.hpp +++ b/src/coreComponents/dataRepository/WrapperContext.hpp @@ -30,7 +30,7 @@ namespace dataRepository /** * @class WrapperContext - * + * * Dedicated implementation of GroupContext for Wrapper. * See DataContext class for more info. */ diff --git a/src/coreComponents/dataRepository/xmlWrapper.hpp b/src/coreComponents/dataRepository/xmlWrapper.hpp index 3f19f99149d..2892d8ded94 100644 --- a/src/coreComponents/dataRepository/xmlWrapper.hpp +++ b/src/coreComponents/dataRepository/xmlWrapper.hpp @@ -67,7 +67,7 @@ class xmlDocument; /** * @struct xmlAttributePos - * + * * Stores the source file path, and position in the file of a xml attribute. */ struct xmlAttributePos @@ -104,7 +104,7 @@ struct xmlAttributePos /** * @struct xmlNodePos - * + * * Used to retrieve the position of dataRepository::Wrapper in XML */ struct xmlNodePos : xmlAttributePos @@ -135,7 +135,7 @@ struct xmlNodePos : xmlAttributePos /** * @class xmlDocument - * + * * Wrapper class for the type of xml document. * This class exists to intercept file / string loading methods, and to keep the loaded buffers, * in order to retrieve the source file and line of nodes and attributes. From 44f4feded3966485d2729cc499690172e292fab7 Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Wed, 23 Aug 2023 16:20:52 +0200 Subject: [PATCH 69/85] Docs fixes --- src/coreComponents/dataRepository/DataContext.hpp | 2 +- src/coreComponents/dataRepository/xmlWrapper.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/coreComponents/dataRepository/DataContext.hpp b/src/coreComponents/dataRepository/DataContext.hpp index 07cd4dfe148..42c6564766f 100644 --- a/src/coreComponents/dataRepository/DataContext.hpp +++ b/src/coreComponents/dataRepository/DataContext.hpp @@ -116,7 +116,7 @@ class DataContext }; /** - * @class DataContext + * @class DataFileContext * * Stores information to retrieve where a target object has been declared in the input source * file (e.g. XML). diff --git a/src/coreComponents/dataRepository/xmlWrapper.hpp b/src/coreComponents/dataRepository/xmlWrapper.hpp index 2892d8ded94..4396dae97fc 100644 --- a/src/coreComponents/dataRepository/xmlWrapper.hpp +++ b/src/coreComponents/dataRepository/xmlWrapper.hpp @@ -200,7 +200,7 @@ class xmlDocument * @brief Load document from zero-terminated string. No encoding conversions are applied. * Free any previously loaded xml tree. * Wrapper of pugi::xml_document::loadBuffer() method. - * @param contents the string containing the document content + * @param content the string containing the document content * @param loadNodeFileInfo Load the node source file info, allowing getNodePosition() to work. * @return an xmlResult object representing the parsing resulting status. */ From 40879dd8b0f2ca23e87ee7e9b30b3c6b970fb4e8 Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Wed, 23 Aug 2023 17:13:30 +0200 Subject: [PATCH 70/85] reverse iterator bugfix --- src/coreComponents/dataRepository/GroupContext.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreComponents/dataRepository/GroupContext.cpp b/src/coreComponents/dataRepository/GroupContext.cpp index eb275229585..9c4aa1b61cd 100644 --- a/src/coreComponents/dataRepository/GroupContext.cpp +++ b/src/coreComponents/dataRepository/GroupContext.cpp @@ -45,7 +45,7 @@ string GroupContext::toString() const []( ToStringInfo const & i ) { return i.hasInputFileInfo(); } ); for( auto info = parentsInfo.rbegin(); info != parentsInfo.rend(); ++info ) { - path << ( info.base() != lastFileInfo ? + path << ( &*info != &*lastFileInfo ? GEOS_FMT( "/{}", info->m_targetName ) : GEOS_FMT( "/{}({},l.{})", info->m_targetName, info->m_filePath, info->m_line ) ); } From b792974e16d3bf5b82f12786689ca25cd88ccd03 Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Fri, 25 Aug 2023 12:15:16 +0200 Subject: [PATCH 71/85] DataContext format options fix --- .../dataRepository/DataContext.hpp | 20 ++++++------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/src/coreComponents/dataRepository/DataContext.hpp b/src/coreComponents/dataRepository/DataContext.hpp index 42c6564766f..d30b2d4c2a9 100644 --- a/src/coreComponents/dataRepository/DataContext.hpp +++ b/src/coreComponents/dataRepository/DataContext.hpp @@ -203,9 +203,12 @@ class DataFileContext final : public DataContext -/// Formatter to be able to directly use a DataContext as a GEOS_FMT() argument. +/** + * @brief Formatter to be able to directly use a DataContext as a GEOS_FMT() argument. + * Inherits from formatter to reuse its parse() method. + */ template<> -struct GEOS_FMT_NS::formatter< geos::dataRepository::DataContext > +struct GEOS_FMT_NS::formatter< geos::dataRepository::DataContext > : GEOS_FMT_NS::formatter< std::string > { /** * @brief Format the specified DataContext to a string. @@ -215,18 +218,7 @@ struct GEOS_FMT_NS::formatter< geos::dataRepository::DataContext > */ auto format( geos::dataRepository::DataContext const & dataContext, format_context & ctx ) { - return format_to( ctx.out(), dataContext.toString() ); - } - - /** - * @brief Method to parse a dataContext from a string. Not implemented! - * @param ctx formatting state consisting of the formatting arguments and the output iterator - * @return iterator to the output buffer (leaved unchanged) - */ - auto parse( format_parse_context & ctx ) - { - GEOS_ERROR( "DataContext parsing is not implemented." ); - return ctx.begin(); + return GEOS_FMT_NS::formatter< std::string >::format( dataContext.toString(), ctx ); } }; From 212e6112db76da9fd15e9f587d54ad437394a458 Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Fri, 25 Aug 2023 15:18:45 +0200 Subject: [PATCH 72/85] DataContext toString bugfix when no DataFileContext exist --- src/coreComponents/dataRepository/GroupContext.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreComponents/dataRepository/GroupContext.cpp b/src/coreComponents/dataRepository/GroupContext.cpp index 9c4aa1b61cd..c575ba274e9 100644 --- a/src/coreComponents/dataRepository/GroupContext.cpp +++ b/src/coreComponents/dataRepository/GroupContext.cpp @@ -45,7 +45,7 @@ string GroupContext::toString() const []( ToStringInfo const & i ) { return i.hasInputFileInfo(); } ); for( auto info = parentsInfo.rbegin(); info != parentsInfo.rend(); ++info ) { - path << ( &*info != &*lastFileInfo ? + path << ( std::prev( info.base() ) != lastFileInfo ? // Is `info` not pointing to the last file info? GEOS_FMT( "/{}", info->m_targetName ) : GEOS_FMT( "/{}({},l.{})", info->m_targetName, info->m_filePath, info->m_line ) ); } From 42f53b51c16c75a7af6e647ecc6a833445a3699b Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Fri, 25 Aug 2023 15:22:16 +0200 Subject: [PATCH 73/85] Added docs for xmlWrapper attribute line search --- src/coreComponents/dataRepository/xmlWrapper.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/coreComponents/dataRepository/xmlWrapper.cpp b/src/coreComponents/dataRepository/xmlWrapper.cpp index c1e970b3404..2efec4ca383 100644 --- a/src/coreComponents/dataRepository/xmlWrapper.cpp +++ b/src/coreComponents/dataRepository/xmlWrapper.cpp @@ -357,14 +357,16 @@ size_t findAttribute( string const & attName, string const & xmlBuffer, size_t c try { std::smatch m; - // we search for a string which is the attribute name followed by an '=', eventually separated by spaces + // As pugixml doesn't expose a way to get the attribute line or character offset, a regex + // seem like a suficient solution for GEOS XMLs. + // The regex search for the attribute name followed by an '=', eventually separated by spaces if( std::regex_search( xmlBuffer.cbegin() + searchStart, xmlBuffer.cbegin() + tagEnd, m, std::regex( attName + "\\s*=\\s*\"" ))) { size_t candidatePos = m.position() + searchStart; string previousString = xmlBuffer.substr( tagBegin, candidatePos - tagBegin ); - // we must be out of value surrounding quotes: the number of previous quotes '"' should be even (ignoring the inner quotes preceded - // by '\\') + // We must be out of value surrounding quotes: the number of previous quotes '"' should be even + // (ignoring the inner quotes preceded by '\\') size_t surroundingQuotesCount = 0; size_t quotePos = 0; while((quotePos = previousString.find( '"', quotePos + 1 )) != string::npos ) From 38d33d719289e96cf441b8d2c97024c96bd472c1 Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Fri, 25 Aug 2023 15:51:44 +0200 Subject: [PATCH 74/85] removed Group::forAllDataContext in profit of a private function in testXMLFile --- src/coreComponents/dataRepository/Group.hpp | 32 ------------------- .../unitTests/xmlTests/testXMLFile.cpp | 29 ++++++++++++++++- 2 files changed, 28 insertions(+), 33 deletions(-) diff --git a/src/coreComponents/dataRepository/Group.hpp b/src/coreComponents/dataRepository/Group.hpp index c93ede64e58..745a8d1c31f 100644 --- a/src/coreComponents/dataRepository/Group.hpp +++ b/src/coreComponents/dataRepository/Group.hpp @@ -1321,17 +1321,6 @@ class Group DataContext const & getWrapperDataContext( KEY key ) const { return getWrapperBase< KEY >( key ).getDataContext(); } - /** - * @brief Calls a lambda for the DataContext of this Group and the ones of its children Group and - * Wrapper if they can be casted to the requested class type. - * @tparam TARGET_DC the requested DataContext class or parent class type. - * @tparam LAMBDA the type of functor to call. The functor takes in parameter a DataContext of - * TARGET_DC type. - * @param func - */ - template< typename TARGET_DC = DataContext, typename FUNC > - void forAllDataContext( FUNC func ) const; - /** * @brief Access the group's parent. * @return reference to parent Group @@ -1685,27 +1674,6 @@ Wrapper< T > & Group::registerWrapper( string const & name, return rval; } -template< typename TARGET_DC, typename FUNC > -void Group::forAllDataContext( FUNC func ) const -{ - if( auto * groupCtx = dynamic_cast< TARGET_DC const * >( &getDataContext() ) ) - { - func( *groupCtx ); - } - for( auto const & wrapperIterator : m_wrappers ) - { - auto * wrapperCtx = dynamic_cast< TARGET_DC const * >( &wrapperIterator.second->getDataContext() ); - if( wrapperCtx ) - { - func( *wrapperCtx ); - } - } - for( auto subGroup : m_subGroups ) - { - subGroup.second->forAllDataContext< TARGET_DC >( func ); - } -} - } /* end namespace dataRepository */ } /* end namespace geos */ diff --git a/src/coreComponents/unitTests/xmlTests/testXMLFile.cpp b/src/coreComponents/unitTests/xmlTests/testXMLFile.cpp index 49ad4a301dd..88fed5c4a7e 100644 --- a/src/coreComponents/unitTests/xmlTests/testXMLFile.cpp +++ b/src/coreComponents/unitTests/xmlTests/testXMLFile.cpp @@ -83,6 +83,33 @@ void getElementsRecursive( xmlDocument const & document, xmlNode const & targetN getElementsRecursive( document, subNode, elementNames ); } } +/** + * @brief Calls a lambda for the DataContext of this Group and the ones of its children Group and + * Wrapper if they can be casted to a DataFileContext type. + * @tparam LAMBDA the type of functor to call. The functor takes in parameter a DataContext of + * TARGET_DC type. + * @param func the functor to call for each DataFileContext of the group and its children + */ +template< typename FUNC > +void forAllDataFileContext( Group const & group, FUNC func ) +{ + if( auto * groupCtx = dynamic_cast< DataFileContext const * >( &group.getDataContext() ) ) + { + func( *groupCtx ); + } + for( auto const & wrapperIterator : group.wrappers() ) + { + auto * wrapperCtx = dynamic_cast< DataFileContext const * >( &wrapperIterator.second->getDataContext() ); + if( wrapperCtx ) + { + func( *wrapperCtx ); + } + } + for( auto subGroup : group.getSubGroups() ) + { + forAllDataFileContext( *subGroup.second, func ); + } +} /** * @brief Verify if the DataFileContext data correctly locates from where the object comes from (in the * correct document/include, at the correct line and at the correct character offset). @@ -181,7 +208,7 @@ TEST( testXML, testXMLFileLines ) getElementsRecursive( xmlDoc, xmlDoc.getFirstChild(), expectedElements ); std::set< string > verifiedElements; - problemManager.forAllDataContext< DataFileContext >( [&]( DataFileContext const & ctx ) + forAllDataFileContext( problemManager, [&]( DataFileContext const & ctx ) { verifyDataFileContext( ctx, xmlDoc, verifiedElements ); } ); From 1bcf5ba5653c4e05796ac312d3255a4da82790ef Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Fri, 25 Aug 2023 16:02:45 +0200 Subject: [PATCH 75/85] refactoring DataContext::ToStringInfo --- src/coreComponents/dataRepository/DataContext.cpp | 4 +--- src/coreComponents/dataRepository/DataContext.hpp | 5 +++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/coreComponents/dataRepository/DataContext.cpp b/src/coreComponents/dataRepository/DataContext.cpp index bb23227a729..8ddf77b3e94 100644 --- a/src/coreComponents/dataRepository/DataContext.cpp +++ b/src/coreComponents/dataRepository/DataContext.cpp @@ -40,10 +40,8 @@ DataContext::ToStringInfo::ToStringInfo( string const & targetName, string const m_line( line ) {} DataContext::ToStringInfo::ToStringInfo( string const & targetName ): - DataContext::ToStringInfo::ToStringInfo( targetName, "", xmlWrapper::xmlDocument::npos ) + m_targetName( targetName ) {} -bool DataContext::ToStringInfo::hasInputFileInfo() const -{ return !m_filePath.empty() && m_line != xmlWrapper::xmlDocument::npos; } /** diff --git a/src/coreComponents/dataRepository/DataContext.hpp b/src/coreComponents/dataRepository/DataContext.hpp index d30b2d4c2a9..6b30face341 100644 --- a/src/coreComponents/dataRepository/DataContext.hpp +++ b/src/coreComponents/dataRepository/DataContext.hpp @@ -86,7 +86,7 @@ class DataContext /// the file path of the DataFileContext, if it exists (an empty string otherwise) string m_filePath; /// the file line of the DataFileContext, if it exists (an empty string otherwise) - size_t m_line; + size_t m_line = xmlWrapper::xmlDocument::npos; /** * @brief Construct a new ToStringInfo object from a DataContext that has input file info. @@ -103,7 +103,8 @@ class DataContext /** * @return true if a location has been found to declare the target in an input file. */ - bool hasInputFileInfo() const; + bool hasInputFileInfo() const + { return !m_filePath.empty() && m_line != xmlWrapper::xmlDocument::npos; } }; /** From 919830b7e4939bb3cab68118745b23da4bbae4c7 Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Wed, 30 Aug 2023 13:51:17 +0200 Subject: [PATCH 76/85] a few refactorings --- src/coreComponents/dataRepository/GroupContext.cpp | 6 +++--- src/coreComponents/dataRepository/GroupContext.hpp | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/coreComponents/dataRepository/GroupContext.cpp b/src/coreComponents/dataRepository/GroupContext.cpp index c575ba274e9..a3d52b269b0 100644 --- a/src/coreComponents/dataRepository/GroupContext.cpp +++ b/src/coreComponents/dataRepository/GroupContext.cpp @@ -45,9 +45,9 @@ string GroupContext::toString() const []( ToStringInfo const & i ) { return i.hasInputFileInfo(); } ); for( auto info = parentsInfo.rbegin(); info != parentsInfo.rend(); ++info ) { - path << ( std::prev( info.base() ) != lastFileInfo ? // Is `info` not pointing to the last file info? - GEOS_FMT( "/{}", info->m_targetName ) : - GEOS_FMT( "/{}({},l.{})", info->m_targetName, info->m_filePath, info->m_line ) ); + path << ( std::prev( info.base() ) == lastFileInfo ? // Is `info` pointing to the last file info? + GEOS_FMT( "/{}({},l.{})", info->m_targetName, info->m_filePath, info->m_line ) : + GEOS_FMT( "/{}", info->m_targetName )); } return path.str(); } diff --git a/src/coreComponents/dataRepository/GroupContext.hpp b/src/coreComponents/dataRepository/GroupContext.hpp index 5c09d547e06..327f00f5251 100644 --- a/src/coreComponents/dataRepository/GroupContext.hpp +++ b/src/coreComponents/dataRepository/GroupContext.hpp @@ -47,7 +47,7 @@ class GroupContext : public DataContext /** * @return the reference to the Group related to this GroupContext. */ - Group & getGroup() const; + Group const & getGroup() const; protected: From e47099fcee56ee9505b63c7501e6be424af1e8b8 Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Thu, 31 Aug 2023 15:12:42 +0200 Subject: [PATCH 77/85] Merge remote-tracking branch 'origin/develop' into feature/rey/error-xml-line-mesh --- .devcontainer/Dockerfile | 2 +- .github/workflows/ci_tests.yml | 306 +++++++ .github/workflows/epics-action.yaml | 15 - .gitlab-ci.yml | 23 +- .travis.yml | 253 ------ README.md | 16 +- geosx-key.json.enc | Bin 2320 -> 0 bytes host-configs/LLNL/lassen-base.cmake | 24 +- .../LLNL/lassen-clang-10-cuda-11.cmake | 16 + ...am.cmake => lassen-clang-13-cuda-11.cmake} | 14 +- host-configs/LLNL/lassen-clang10-cuda11.cmake | 30 - host-configs/LLNL/lassen-clang13-cuda11.cmake | 30 - .../LLNL/lassen-clang@upstream-NoMPI.cmake | 21 - ...8.3.1.cmake => lassen-gcc-8-cuda-11.cmake} | 2 +- host-configs/LLNL/quartz-base.cmake | 4 + ...z-clang@14.cmake => quartz-clang-14.cmake} | 2 +- ...uartz-gcc@12.cmake => quartz-gcc-12.cmake} | 2 +- ...z-icc@19.0.4.cmake => quartz-icc-19.cmake} | 2 +- ...ga-cce@15.0.0.cmake => tioga-cce-15.cmake} | 4 +- host-configs/Stanford/sherlock-base.cmake | 1 - ...4.1.2-openblas0.3.10-cuda11.5.0-sm80.cmake | 1 - .../pangea3-gcc8.4.1-openmpi-4.1.2.cmake | 5 +- host-configs/TOTAL/pecan-GPU.cmake | 6 +- host-configs/macOS_Matteo.cmake | 5 +- .../c1-ppu/grav_seg_c1ppu_base.xml | 171 ++++ .../c1-ppu/grav_seg_c1ppu_drain.xml | 74 ++ .../c1-ppu/grav_seg_c1ppu_hyst.xml | 74 ++ .../tables/drainagePhaseVolFraction_gas.txt | 19 + .../tables/drainagePhaseVolFraction_water.txt | 16 + .../c1-ppu/tables/drainageRelPerm_gas.txt | 19 + .../c1-ppu/tables/drainageRelPerm_water.txt | 16 + .../tables/imbibitionPhaseVolFraction_gas.txt | 11 + .../imbibitionPhaseVolFraction_water.txt | 12 + .../c1-ppu/tables/imbibitionRelPerm_gas.txt | 11 + .../c1-ppu/tables/imbibitionRelPerm_water.txt | 12 + .../c1-ppu/tables/pvdg.txt | 3 + .../c1-ppu/tables/pvtw.txt | 2 + .../deadoil_3ph_corey_1d_fractured.xml | 3 +- .../Class09Pb3/class09_pb3_benchmark.xml | 56 +- .../Class09Pb3/class09_pb3_smoke_3d.xml | 56 +- .../benchmarks/Egg/deadOilEgg_benchmark.xml | 761 +++++++++--------- .../benchmarks/Egg/deadOilEgg_smoke_3d.xml | 761 +++++++++--------- .../black_oil_wells_saturated_3d.xml | 57 +- .../black_oil_wells_unsaturated_3d.xml | 57 +- .../compositional_multiphase_wells_1d.xml | 61 +- .../compositional_multiphase_wells_2d.xml | 122 ++- .../dead_oil_wells_2d.xml | 128 ++- .../dead_oil_wells_hybrid_2d.xml | 122 ++- .../simpleCo2InjTutorial_smoke.xml | 30 +- .../staircase_co2_wells_3d.xml | 63 +- .../staircase_co2_wells_hybrid_3d.xml | 61 +- ...bFrac_Compression_CoulombFriction_base.xml | 1 + ...Frac_Compression_CoulombFriction_smoke.xml | 2 +- .../EmbFrac_Compression_Frictionless_base.xml | 1 + ...EmbFrac_Compression_Frictionless_smoke.xml | 2 +- .../SneddonRotated_base.xml | 151 ---- .../SneddonRotated_benchmark.xml | 63 +- .../SneddonRotated_benchmark2.xml | 43 +- .../SneddonRotated_smoke.xml | 64 +- .../Sneddon_benchmark.xml | 50 -- .../Sneddon_benchmark2.xml | 50 -- .../Sneddon_benchmark3.xml | 67 -- ...ml => Sneddon_embeddedFracShapes_base.xml} | 105 ++- .../Sneddon_embeddedFracShapes_smoke.xml | 62 ++ .../Sneddon_embeddedFrac_base.xml | 55 +- .../Sneddon_embeddedFrac_benchmark.xml | 81 +- .../Sneddon_embeddedFrac_smoke.xml | 73 +- ...eddedFrac_staticCondensation_benchmark.xml | 95 +++ ..._embeddedFrac_staticCondensation_smoke.xml | 93 +++ .../Sneddon_embeddedFrac_verification.xml | 108 +++ .../Sneddon_staticCondensation_benchmark.xml | 50 -- .../Sneddon_staticCondensation_benchmark2.xml | 50 -- .../scripts/sneddonCurveChecks.py | 157 ++++ .../kgdToughnessDominated_base.xml | 20 +- ...kgdToughnessDominated_poroelastic_base.xml | 225 ++++++ ...ughnessDominated_poroelastic_benchmark.xml | 117 +++ ...gdToughnessDominated_poroelastic_smoke.xml | 60 ++ .../kgdToughnessDominated_smoke.xml | 67 +- .../kgdViscosityDominated_base.xml | 23 +- ...kgdViscosityDominated_poroelastic_base.xml | 225 ++++++ ...scosityDominated_poroelastic_benchmark.xml | 117 +++ ...gdViscosityDominated_poroelastic_smoke.xml | 78 ++ .../kgdViscosityDominated_smoke.xml | 57 +- .../pennyShapedToughnessDominated_base.xml | 17 + ...pedToughnessDominated_poroelastic_base.xml | 197 +++++ ...ughnessDominated_poroelastic_benchmark.xml | 217 +++++ ...edToughnessDominated_poroelastic_smoke.xml | 99 +++ .../pennyShapedToughnessDominated_smoke.xml | 72 +- .../pennyShapedViscosityDominated_base.xml | 19 +- ...pedViscosityDominated_poroelastic_base.xml | 198 +++++ ...scosityDominated_poroelastic_benchmark.xml | 218 +++++ ...edViscosityDominated_poroelastic_smoke.xml | 100 +++ .../pennyShapedViscosityDominated_smoke.xml | 68 +- .../pknViscosityDominated_base.xml | 20 +- .../pknViscosityDominated_benchmark.xml | 3 +- ...pknViscosityDominated_poroelastic_base.xml | 198 +++++ ...scosityDominated_poroelastic_benchmark.xml | 208 +++++ ...knViscosityDominated_poroelastic_smoke.xml | 100 +++ .../pknViscosityDominated_smoke.xml | 63 +- .../scripts/HydrofractureSolutions.py | 27 + .../scripts/hydrofractureCurveChecks.py | 146 ++++ .../scripts/hydrofractureFigure.py | 27 +- .../ContactMechanics_PassingCrack_smoke.xml | 4 +- ...hanics_SingleFracCompression_benchmark.xml | 4 +- ...tMechanics_SingleFracCompression_smoke.xml | 4 +- .../ContactMechanics_TFrac_base.xml | 8 +- ...tMechanics_UnstructuredCrack_benchmark.xml | 4 +- ...ntactMechanics_UnstructuredCrack_smoke.xml | 4 +- ...chanics_slippingFault_horizontal_smoke.xml | 4 +- ...Mechanics_slippingFault_vertical_smoke.xml | 4 +- .../Sneddon_contactMechanics_base.xml | 4 +- .../deadOil_fractureMatrixFlow_edfm_base.xml | 1 + ...reMatrixFlow_edfm_horizontalFrac_smoke.xml | 2 +- ...tureMatrixFlow_edfm_inclinedFrac_smoke.xml | 2 +- .../deadoil_3ph_corey_2d_edfm.xml | 3 +- .../deadoil_3ph_corey_2d_impermeableFault.xml | 3 +- .../deadoil_3ph_corey_edfm_1d.xml | 3 +- ...3ph_corey_pedfm_impermeableFault_smoke.xml | 3 +- .../poromechanics/PoroElastic_Mandel_base.xml | 36 - ...l => PoroElastic_Mandel_benchmark_fim.xml} | 37 + ...oroElastic_Mandel_benchmark_sequential.xml | 109 +++ ...e.xml => PoroElastic_Mandel_smoke_fim.xml} | 37 + .../PoroElastic_Mandel_smoke_sequential.xml | 116 +++ ...PoroElastic_deadoil_3ph_baker_2d_base.xml} | 30 - .../PoroElastic_deadoil_3ph_baker_2d_fim.xml | 40 + ...lastic_deadoil_3ph_baker_2d_sequential.xml | 56 ++ .../PoroElastic_staircase_co2_3d.xml | 72 +- .../PoroElastic_staircase_singlephase_3d.xml | 72 +- ...ExponentialDecayPermeability_edfm_base.xml | 1 + ...xponentialDecayPermeability_edfm_smoke.xml | 2 +- .../PoroElastic_efem-edfm_base.xml | 3 +- .../PoroElastic_efem-edfm_eggModel_large.xml | 15 +- ... PoroElastic_efem-edfm_eggModel_small.xml} | 17 +- ...astic_efem-edfm_inclinedFrac_benchmark.xml | 2 +- ...roElastic_efem-edfm_inclinedFrac_smoke.xml | 2 +- ...PoroElastic_efem-edfm_pennyCrack_base.xml} | 134 +-- ...Elastic_efem-edfm_pennyCrack_benchmark.xml | 104 +++ ...PoroElastic_efem-edfm_pennyCrack_smoke.xml | 101 +++ ...lastic_efem-edfm_pressurizedFrac_smoke.xml | 3 +- ...astic_efem-edfm_verticalFrac_benchmark.xml | 2 +- ...roElastic_efem-edfm_verticalFrac_smoke.xml | 2 +- .../PoroElastic_embFractures_smoke.xml | 254 ------ .../SlipPermeability_embeddedFrac.xml | 3 +- .../SlipPermeability_pEDFM_base.xml | 1 + .../SlipPermeability_pEDFM_smoke.xml | 2 +- ...lisRichardsPermeability_efem-edfm_base.xml | 1 + ...isRichardsPermeability_efem-edfm_smoke.xml | 2 +- .../singlePhaseFlow/incompressible_pebi3d.xml | 1 + .../pebi3d_with_properties.vtu | Bin 886538 -> 186388 bytes ...lowWithGravity_edfm_verticalFrac_smoke.xml | 3 +- .../fractureMatrixFlow_edfm_base.xml | 1 + ...trixFlow_edfm_horizontalFrac_benchmark.xml | 2 +- ...reMatrixFlow_edfm_horizontalFrac_smoke.xml | 2 +- ...MatrixFlow_edfm_inclinedFrac_benchmark.xml | 2 +- ...tureMatrixFlow_edfm_inclinedFrac_smoke.xml | 2 +- ...ixFlow_pedfm_impermeableFracture_smoke.xml | 3 +- .../compressible_single_phase_wells_1d.xml | 61 +- ...pressible_single_phase_wells_hybrid_1d.xml | 61 +- .../incompressible_single_phase_wells_2d.xml | 138 ++-- ...pressible_single_phase_wells_hybrid_2d.xml | 138 ++-- .../staircase_single_phase_wells_3d.xml | 67 +- ...staircase_single_phase_wells_hybrid_3d.xml | 61 +- .../DruckerPragerWellbore_base.xml | 170 ++++ .../DruckerPragerWellbore_benchmark.xml | 72 ++ .../DruckerPragerWellbore_smoke.xml | 48 ++ .../ExtendedDruckerPragerWellbore_base.xml | 68 +- ...xtendedDruckerPragerWellbore_benchmark.xml | 41 +- .../ExtendedDruckerPragerWellbore_smoke.xml | 5 +- .../solidMechanics/beamBending_base.xml | 30 + .../solidMechanics/beamBending_curve.py | 32 + .../sedov_finiteStrain_smoke.xml | 10 + .../egsCollab_thermalFlow/TC_flowRate.csv | 26 + .../egsCollab_thermalFlow/TN_flowRate.csv | 26 + .../egsCollab_thermalFlow/TU_flowRate.csv | 27 + .../egsCollab_thermalFlow/TU_temperature.csv | 27 + .../bc_data/pressure.geos | 8 + .../egsCollab_thermalFlow/bc_data/temp.geos | 8 + .../egsCollab_thermalFlow/bc_data/xlin.geos | 2 + .../egsCollab_thermalFlow/bc_data/ylin.geos | 2 + .../egsCollab_thermalFlow/bc_data/zlin.geos | 2 + .../egsCollab_thermalFlow/egsCollabLaunch.sh | 17 + .../egsCollab_thermalFlow_base.xml | 110 +++ ...egsCollab_thermalFlow_initialCond_base.xml | 134 +++ ...sCollab_thermalFlow_initialCond_coarse.xml | 40 + ...egsCollab_thermalFlow_initialCond_fine.xml | 41 + .../egsCollab_thermalFlow_injection_base.xml | 211 +++++ ...egsCollab_thermalFlow_injection_coarse.xml | 51 ++ .../egsCollab_thermalFlow_injection_fine.xml | 50 ++ .../injectionSchedule_time.csv | 27 + .../productionSchedule_time.csv | 26 + .../fractureMatrixThermalFlow_base.xml | 76 ++ .../fractureMatrixThermalFlow_bc.xml | 65 ++ ...ctureMatrixThermalFlow_conforming_base.xml | 52 ++ ...ractureMatrixThermalFlow_conforming_bc.xml | 81 ++ ...MatrixThermalFlow_conforming_benchmark.xml | 51 ++ ...tureMatrixThermalFlow_conforming_smoke.xml | 56 ++ .../fractureMatrixThermalFlow_edfm_base.xml | 58 ++ ...ctureMatrixThermalFlow_edfm_benchmark.xml} | 40 +- .../fractureMatrixThermalFlow_edfm_smoke.xml | 66 ++ ...tic_consolidation_benchmark_sequential.xml | 6 +- .../ThermoPoroElastic_base.xml | 97 +++ .../ThermoPoroElastic_conforming_base.xml | 216 +++++ ...ThermoPoroElastic_conforming_benchmark.xml | 49 ++ .../ThermoPoroElastic_conforming_smoke.xml | 54 ++ .../ThermoPoroElastic_efem-edfm_base.xml | 86 ++ ...moPoroElastic_efem-edfm_eggModel_small.xml | 374 +++++++++ ...oroElastic_efem-edfm_verticalFrac_base.xml | 34 + ...astic_efem-edfm_verticalFrac_benchmark.xml | 142 ++++ ...roElastic_efem-edfm_verticalFrac_smoke.xml | 145 ++++ .../acous3D_firstOrder_small_base.xml | 2 +- integratedTests | 2 +- scripts/SchemaToRSTDocumentation.py | 2 +- scripts/ci_build_and_test.sh | 36 + ...t.sh => ci_build_and_test_in_container.sh} | 2 +- scripts/createRectangle.py | 137 ++++ scripts/postProcessing/SneddonValidation.py | 6 +- .../plotsolution_thermalFlow.py | 118 +++ src/CMakeLists.txt | 11 +- src/cmake/GeosxOptions.cmake | 14 +- src/cmake/blt | 2 +- src/conf.py | 18 +- src/coreComponents/LvArray | 2 +- .../codingUtilities/Utilities.hpp | 5 +- .../codingUtilities/tests/testGeosxTraits.cpp | 2 +- src/coreComponents/common/CMakeLists.txt | 12 +- ...{fixedSizeDeque.hpp => FixedSizeDeque.hpp} | 23 +- .../common/FixedSizeDequeWithMutexes.hpp | 167 ++++ src/coreComponents/common/Format.hpp | 38 + .../common/GEOS_RAJA_Interface.hpp | 25 +- src/coreComponents/common/GeosxMacros.hpp | 21 + src/coreComponents/common/LifoStorage.hpp | 195 +++++ .../common/LifoStorageCommon.hpp | 313 +++++++ src/coreComponents/common/LifoStorageCuda.hpp | 215 +++++ src/coreComponents/common/LifoStorageHost.hpp | 170 ++++ src/coreComponents/common/MpiWrapper.cpp | 24 + src/coreComponents/common/MpiWrapper.hpp | 6 + .../common/MultiMutexesLock.hpp | 88 ++ src/coreComponents/common/TypeDispatch.hpp | 168 +++- src/coreComponents/common/lifoStorage.hpp | 756 ----------------- .../common/unitTests/CMakeLists.txt | 2 +- .../common/unitTests/testFixedSizeDeque.cpp | 35 +- .../common/unitTests/testLifoStorage.cpp | 59 +- .../common/unitTests/testTypeDispatch.cpp | 143 +++- .../constitutive/CMakeLists.txt | 31 +- .../JFunctionCapillaryPressure.cpp | 14 +- .../docs/constitutiveDeveloperGuide.rst | 8 +- .../fluid/multifluid/MultiFluidUtils.hpp | 13 + .../CompositionalMultiphaseFluid.hpp | 1 + .../functions/CubicEOSPhaseModel.hpp | 227 +++++- .../functions/KValueInitialization.hpp | 83 ++ .../functions/NegativeTwoPhaseFlash.hpp | 217 +++++ .../compositional/functions/RachfordRice.cpp | 189 ----- .../compositional/functions/RachfordRice.hpp | 174 +++- .../constitutive/solid/CompressibleSolid.hpp | 17 +- .../constitutive/solid/CoupledSolid.hpp | 6 +- .../constitutive/solid/CoupledSolidBase.hpp | 36 + .../constitutive/solid/PorousSolid.hpp | 74 +- .../solid/porosity/BiotPorosity.cpp | 26 +- .../solid/porosity/BiotPorosity.hpp | 135 +++- .../solid/porosity/PorosityBase.cpp | 5 + .../solid/porosity/PorosityBase.hpp | 62 +- .../solid/porosity/PorosityFields.hpp | 18 + .../solid/porosity/PressurePorosity.hpp | 14 +- .../solid/porosity/ProppantPorosity.hpp | 8 +- ...PhaseVolumeWeightedThermalConductivity.cpp | 12 +- ...SinglePhaseConstantThermalConductivity.cpp | 34 +- ...SinglePhaseConstantThermalConductivity.hpp | 6 + .../SinglePhaseThermalConductivityBase.hpp | 9 + .../constitutive/unitTests/CMakeLists.txt | 2 + .../constitutive/unitTests/TestFluid.hpp | 129 +++ .../constitutive/unitTests/testCubicEOS.cpp | 498 ++++++++++-- .../unitTests/testKValueInitialization.cpp | 224 ++++++ .../unitTests/testNegativeTwoPhaseFlash.cpp | 387 +++++++++ .../unitTests/testRachfordRice.cpp | 42 +- .../dataRepository/ExecutableGroup.hpp | 2 +- .../dataRepository/MappedVector.hpp | 4 +- .../dataRepository/ObjectCatalog.hpp | 4 +- .../dataRepository/wrapperHelpers.hpp | 4 +- src/coreComponents/events/EventBase.hpp | 8 + src/coreComponents/events/PeriodicEvent.cpp | 12 +- .../FieldSpecificationBase.hpp | 24 +- src/coreComponents/fileIO/silo/SiloFile.cpp | 12 +- src/coreComponents/fileIO/silo/SiloFile.hpp | 16 +- .../fileIO/timeHistory/PackCollection.cpp | 6 +- .../fileIO/timeHistory/PackCollection.hpp | 4 +- .../fileIO/vtk/VTKPolyDataWriterInterface.cpp | 26 +- .../FiniteElementDiscretization.cpp | 14 +- ...H1_Hexahedron_Lagrange1_GaussLegendre2.hpp | 15 + .../H1_Pyramid_Lagrange1_Gauss5.hpp | 14 + .../H1_Tetrahedron_Lagrange1_Gauss1.hpp | 14 + .../H1_Wedge_Lagrange1_Gauss6.hpp | 14 + .../kernelInterface/kernelInterface.rst | 6 +- .../finiteElement/unitTests/CMakeLists.txt | 4 +- ...estQ3_Hexahedron_Lagrange_GaussLobatto.cpp | 16 +- ...estQ5_Hexahedron_Lagrange_GaussLobatto.cpp | 15 +- .../finiteVolume/FluxApproximationBase.cpp | 77 +- .../finiteVolume/FluxApproximationBase.hpp | 50 +- .../TwoPointFluxApproximation.cpp | 15 +- src/coreComponents/functions/CMakeLists.txt | 1 + src/coreComponents/functions/FunctionBase.hpp | 8 +- .../unitTests}/CMakeLists.txt | 12 +- .../unitTests}/testFunctions.cpp | 11 +- .../linearAlgebra/CMakeLists.txt | 3 + .../linearAlgebra/DofManager.cpp | 148 +++- .../linearAlgebra/DofManager.hpp | 39 +- .../linearAlgebra/docs/DofManager.rst | 4 +- .../interfaces/hypre/HypreMGR.cpp | 6 + .../interfaces/hypre/HypreMatrix.cpp | 6 +- .../CompositionalMultiphaseFVM.hpp | 2 - .../CompositionalMultiphaseReservoirFVM.hpp | 2 - ...positionalMultiphaseReservoirHybridFVM.hpp | 2 +- .../hypre/mgrStrategies/Hydrofracture.hpp | 2 +- .../ReactiveCompositionalMultiphaseOBL.hpp | 1 - ...glePhasePoromechanicsEmbeddedFractures.hpp | 48 +- .../SolidMechanicsEmbeddedFractures.hpp | 118 +++ .../ThermalCompositionalMultiphaseFVM.hpp | 2 - .../ThermalMultiphasePoromechanics.hpp | 3 + .../ThermalSinglePhasePoromechanics.hpp | 3 + .../linearAlgebra/unitTests/CMakeLists.txt | 3 +- .../testReverseCutHillMcKeeOrdering.cpp | 121 +++ .../utilities/ReverseCutHillMcKeeOrdering.cpp | 367 +++++++++ .../utilities/ReverseCutHillMcKeeOrdering.hpp | 49 ++ .../mainInterface/ProblemManager.cpp | 10 +- .../mainInterface/ProblemManager.hpp | 2 +- .../mainInterface/initialization.cpp | 1 + src/coreComponents/mesh/CMakeLists.txt | 17 +- src/coreComponents/mesh/CellElementRegion.cpp | 5 +- src/coreComponents/mesh/CellElementRegion.hpp | 2 +- .../mesh/CellElementSubRegion.cpp | 14 +- .../mesh/CellElementSubRegion.hpp | 2 +- src/coreComponents/mesh/DomainPartition.cpp | 13 +- src/coreComponents/mesh/DomainPartition.hpp | 19 - src/coreComponents/mesh/ElementRegionBase.hpp | 2 +- .../mesh/ElementRegionManager.cpp | 28 +- .../mesh/ElementRegionManager.hpp | 6 +- .../mesh/EmbeddedSurfaceSubRegion.cpp | 12 +- .../mesh/EmbeddedSurfaceSubRegion.hpp | 5 +- src/coreComponents/mesh/FaceManager.cpp | 4 +- src/coreComponents/mesh/MeshBody.hpp | 19 +- src/coreComponents/mesh/MeshLevel.cpp | 17 +- src/coreComponents/mesh/MeshManager.cpp | 23 +- src/coreComponents/mesh/MeshObjectPath.hpp | 35 +- src/coreComponents/mesh/PerforationData.cpp | 9 +- src/coreComponents/mesh/PerforationData.hpp | 8 +- .../mesh/SurfaceElementRegion.cpp | 2 +- .../mesh/SurfaceElementRegion.hpp | 2 +- src/coreComponents/mesh/WellElementRegion.cpp | 20 +- src/coreComponents/mesh/WellElementRegion.hpp | 8 +- .../mesh/WellElementSubRegion.cpp | 73 +- .../mesh/WellElementSubRegion.hpp | 25 +- .../mesh/generators/CellBlock.hpp | 4 +- .../mesh/generators/CellBlockABC.hpp | 4 +- .../mesh/generators/CellBlockManager.cpp | 42 +- .../mesh/generators/CellBlockManager.hpp | 38 +- .../mesh/generators/CellBlockManagerABC.hpp | 22 + .../mesh/generators/InternalMeshGenerator.cpp | 62 +- .../mesh/generators/InternalMeshGenerator.hpp | 6 +- .../mesh/generators/InternalWellGenerator.cpp | 75 +- .../mesh/generators/InternalWellGenerator.hpp | 154 ++-- .../generators/InternalWellboreGenerator.cpp | 4 +- .../mesh/generators/LineBlock.cpp | 41 + .../mesh/generators/LineBlock.hpp | 251 ++++++ .../mesh/generators/LineBlockABC.hpp | 164 ++++ .../mesh/generators/MeshGeneratorBase.cpp | 51 +- .../mesh/generators/MeshGeneratorBase.hpp | 42 +- .../mesh/generators/PartitionDescriptor.hpp | 97 +++ .../mesh/generators/VTKMeshGenerator.cpp | 15 +- .../mesh/generators/VTKMeshGenerator.hpp | 5 +- .../mesh/generators/VTKUtilities.cpp | 196 ++++- .../mesh/generators/VTKUtilities.hpp | 2 +- .../mesh/generators/WellGeneratorBase.cpp | 59 ++ .../mesh/generators/WellGeneratorBase.hpp | 244 ++++++ .../mpiCommunications/SpatialPartition.cpp | 4 +- .../mpiCommunications/SpatialPartition.hpp | 39 +- .../mesh/simpleGeometricObjects/Box.cpp | 10 + .../CustomPolarObject.cpp | 88 ++ .../CustomPolarObject.hpp | 144 ++++ .../mesh/simpleGeometricObjects/Disc.cpp | 84 ++ .../mesh/simpleGeometricObjects/Disc.hpp | 117 +++ .../GeometricObjectManager.hpp | 15 + .../PlanarGeometricObject.cpp | 50 ++ ...dedPlane.hpp => PlanarGeometricObject.hpp} | 71 +- .../{BoundedPlane.cpp => Rectangle.cpp} | 67 +- .../mesh/simpleGeometricObjects/Rectangle.hpp | 139 ++++ .../mesh/unitTests/testMeshObjectPath.cpp | 76 +- .../AverageOverQuadraturePointsKernel.hpp | 292 +++++++ .../physicsSolvers/CMakeLists.txt | 10 +- .../physicsSolvers/contact/ContactFields.hpp | 1 + .../contact/LagrangianContactSolver.cpp | 6 +- .../SolidMechanicsEmbeddedFractures.cpp | 4 +- .../fluidFlow/CompositionalMultiphaseBase.cpp | 49 +- .../fluidFlow/CompositionalMultiphaseBase.hpp | 4 + .../CompositionalMultiphaseBaseFields.hpp | 10 +- .../fluidFlow/CompositionalMultiphaseFVM.cpp | 5 + .../CompositionalMultiphaseHybridFVM.cpp | 4 + .../fluidFlow/FlowSolverBase.cpp | 164 +++- .../fluidFlow/FlowSolverBase.hpp | 16 + .../fluidFlow/FlowSolverBaseFields.hpp | 16 + .../fluidFlow/FlowSolverBaseKernels.hpp | 54 +- .../fluidFlow/FluxKernelsHelper.hpp | 176 +++- ...positionalMultiphaseFVMKernelUtilities.hpp | 353 ++++++-- ...ermalCompositionalMultiphaseFVMKernels.hpp | 67 +- .../fluidFlow/SinglePhaseBase.cpp | 38 +- .../fluidFlow/SinglePhaseBase.hpp | 23 +- .../fluidFlow/SinglePhaseFVM.cpp | 473 +++++++---- .../fluidFlow/SinglePhaseFVM.hpp | 14 +- .../fluidFlow/SinglePhaseFVMKernels.hpp | 246 +----- .../fluidFlow/SinglePhaseHybridFVM.cpp | 14 +- .../fluidFlow/SinglePhaseHybridFVM.hpp | 14 +- .../SinglePhaseProppantFluxKernels.cpp | 225 ++++++ .../SinglePhaseProppantFluxKernels.hpp | 123 +++ ...lizedCompositionalMultiphaseFVMKernels.hpp | 5 +- ...ermalCompositionalMultiphaseFVMKernels.hpp | 9 +- .../ThermalSinglePhaseFVMKernels.hpp | 18 +- .../wells/CompositionalMultiphaseWell.cpp | 20 +- .../fluidFlow/wells/WellControls.cpp | 5 - ...mpositionalMultiphaseReservoirAndWells.cpp | 5 + .../CoupledReservoirAndWellsBase.hpp | 135 ++-- .../multiphysics/HydrofractureSolver.cpp | 351 ++++---- .../multiphysics/HydrofractureSolver.hpp | 72 +- .../multiphysics/MultiphasePoromechanics.cpp | 133 ++- .../multiphysics/MultiphasePoromechanics.hpp | 24 +- .../multiphysics/SinglePhasePoromechanics.cpp | 176 +++- .../multiphysics/SinglePhasePoromechanics.hpp | 37 +- ...ePhasePoromechanicsConformingFractures.cpp | 87 +- ...ePhasePoromechanicsConformingFractures.hpp | 59 ++ ...glePhasePoromechanicsEmbeddedFractures.cpp | 122 ++- ...glePhasePoromechanicsEmbeddedFractures.hpp | 81 +- .../SinglePhasePoromechanicsFluxKernels.cpp | 669 --------------- .../SinglePhasePoromechanicsFluxKernels.hpp | 240 ------ .../SinglePhaseReservoirAndWells.cpp | 5 + .../PoromechanicsBase.hpp | 1 + .../PoromechanicsKernels.cmake | 2 + ...ePhasePoromechanicsConformingFractures.hpp | 329 ++++++++ .../SinglePhasePoromechanicsEFEM.hpp | 29 +- .../SinglePhasePoromechanicsEFEM_impl.hpp | 23 +- ...glePhasePoromechanicsEmbeddedFractures.hpp | 308 +++++++ ...ePhasePoromechanicsConformingFractures.hpp | 402 +++++++++ .../ThermalSinglePhasePoromechanicsEFEM.hpp | 193 +++++ ...ermalSinglePhasePoromechanicsEFEM_impl.hpp | 231 ++++++ ...glePhasePoromechanicsEmbeddedFractures.hpp | 418 ++++++++++ .../ThermoPoromechanicsKernels.cpp.template | 7 + .../poromechanicsKernels/policies.hpp.in | 1 + .../SolidMechanicsLagrangianFEM.cpp | 50 +- .../SolidMechanicsLagrangianFEM.hpp | 6 +- .../solidMechanics/docs/SolidMechanics.rst | 2 +- .../EmbeddedSurfaceGenerator.cpp | 13 +- .../EmbeddedSurfaceGenerator.hpp | 3 + .../AcousticFirstOrderWaveEquationSEM.cpp | 55 +- .../AcousticFirstOrderWaveEquationSEM.hpp | 13 +- ...cousticFirstOrderWaveEquationSEMKernel.hpp | 20 +- .../AcousticWaveEquationSEM.cpp | 187 +++-- .../AcousticWaveEquationSEMKernel.hpp | 20 +- .../ElasticFirstOrderWaveEquationSEM.cpp | 62 +- .../ElasticFirstOrderWaveEquationSEM.hpp | 13 +- ...ElasticFirstOrderWaveEquationSEMKernel.hpp | 19 +- .../ElasticWaveEquationSEM.cpp | 10 +- .../ElasticWaveEquationSEMKernel.hpp | 10 +- .../wavePropagation/WaveSolverBase.cpp | 42 +- .../wavePropagation/WaveSolverBase.hpp | 32 +- .../wavePropagation/WaveSolverUtils.hpp | 44 +- .../schema/docs/AcousticFirstOrderSEM.rst | 51 +- .../docs/AcousticFirstOrderSEM_other.rst | 2 + .../schema/docs/AcousticSEM.rst | 51 +- .../schema/docs/BiotPorosity_other.rst | 29 +- .../schema/docs/CustomPolarObject.rst | 15 + ..._other.rst => CustomPolarObject_other.rst} | 0 src/coreComponents/schema/docs/Disc.rst | 15 + src/coreComponents/schema/docs/Disc_other.rst | 9 + .../schema/docs/ElasticFirstOrderSEM.rst | 51 +- .../docs/ElasticFirstOrderSEM_other.rst | 2 + src/coreComponents/schema/docs/ElasticSEM.rst | 7 +- .../schema/docs/EmbeddedSurfaceGenerator.rst | 1 + src/coreComponents/schema/docs/Events.rst | 23 +- src/coreComponents/schema/docs/Geometry.rst | 18 +- .../schema/docs/Geometry_other.rst | 18 +- .../schema/docs/Hydrofracture.rst | 2 + .../schema/docs/Hydrofracture_other.rst | 21 +- .../schema/docs/InternalMesh.rst | 1 + .../schema/docs/InternalMesh_other.rst | 11 +- .../schema/docs/InternalWell.rst | 1 - .../schema/docs/InternalWellbore.rst | 1 + .../schema/docs/InternalWellbore_other.rst | 19 +- src/coreComponents/schema/docs/Mesh.rst | 1 - src/coreComponents/schema/docs/Mesh_other.rst | 1 - .../schema/docs/PackCollection.rst | 19 +- .../docs/{BoundedPlane.rst => Rectangle.rst} | 0 .../schema/docs/Rectangle_other.rst | 9 + ...ePhasePoromechanicsConformingFractures.rst | 1 + .../schema/docs/TwoPointFluxApproximation.rst | 19 +- .../docs/TwoPointFluxApproximation_other.rst | 2 +- src/coreComponents/schema/docs/VTKMesh.rst | 1 + .../schema/docs/VTKMesh_other.rst | 11 +- src/coreComponents/schema/docs/crusher.rst | 9 + .../schema/docs/crusher_other.rst | 9 + src/coreComponents/schema/schema.xsd | 381 +++++---- src/coreComponents/schema/schema.xsd.other | 30 +- src/coreComponents/schema/schemaUtilities.cpp | 3 +- src/coreComponents/unitTests/CMakeLists.txt | 2 +- .../unitTests/fluidFlowTests/CMakeLists.txt | 1 - ...eservoirCompositionalMultiphaseMSWells.cpp | 49 +- .../testReservoirSinglePhaseMSWells.cpp | 49 +- src/docs/doxygen/GeosxConfig.hpp | 16 +- src/docs/sphinx/CompleteXMLSchema.rst | 56 +- .../isothermalHystInjection/Example.rst | 8 +- .../intersectFrac/intersectFracFigure.py | 2 +- .../faultMechanics/mandel/Example.rst | 4 +- .../faultMechanics/mandel/mandelFigure.py | 4 +- .../singleFracCompressionFigure.py | 8 +- .../faultMechanics/sneddon/Example.rst | 16 +- .../faultMechanics/sneddon/sneddonFigure.py | 6 +- .../wellboreProblems/Index.rst | 2 + .../wellboreProblems/dpWellbore/Example.rst | 88 ++ .../dpWellbore/dpWellboreVerification.png | Bin 0 -> 109846 bytes .../dpWellbore_analyticalResults.py | 112 +++ .../dpWellbore/dpWellbore_plot.py | 236 ++++++ .../wellboreProblems/edpWellbore/Example.rst | 36 +- .../edpWellbore/Verification.png | Bin 371940 -> 0 bytes .../wellboreProblems/edpWellbore/WellMesh.png | Bin 70481 -> 361276 bytes .../edpWellbore/edpWellboreVerification.png | Bin 0 -> 127176 bytes .../edpWellbore_analyticalResults.py | 124 +++ .../edpWellbore/edpWellbore_plot.py | 245 ++++++ ...lastoPlasticWellboreAnalyticalSolutions.py | 91 +++ .../buildGuide/ContinuousIntegration.rst | 15 +- src/docs/sphinx/buildGuide/Prerequisites.rst | 2 +- .../developerGuide/Contributing/CodeStyle.rst | 2 +- .../Dockerfile-remote-dev.example | 6 +- .../Contributing/GitWorkflow.rst | 2 +- .../Contributing/InstallWin.rst | 8 +- .../Contributing/UsingDocker.rst | 10 +- src/docs/sphinx/requirements.txt | 2 + src/main/main.cpp | 8 +- src/pygeosx/pygeosx.cpp | 22 + 533 files changed, 24038 insertions(+), 7844 deletions(-) create mode 100644 .github/workflows/ci_tests.yml delete mode 100644 .github/workflows/epics-action.yaml delete mode 100644 .travis.yml delete mode 100644 geosx-key.json.enc create mode 100644 host-configs/LLNL/lassen-clang-10-cuda-11.cmake rename host-configs/LLNL/{lassen-clang@upstream.cmake => lassen-clang-13-cuda-11.cmake} (53%) delete mode 100644 host-configs/LLNL/lassen-clang10-cuda11.cmake delete mode 100644 host-configs/LLNL/lassen-clang13-cuda11.cmake delete mode 100644 host-configs/LLNL/lassen-clang@upstream-NoMPI.cmake rename host-configs/LLNL/{lassen-gcc@8.3.1.cmake => lassen-gcc-8-cuda-11.cmake} (94%) rename host-configs/LLNL/{quartz-clang@14.cmake => quartz-clang-14.cmake} (90%) rename host-configs/LLNL/{quartz-gcc@12.cmake => quartz-gcc-12.cmake} (93%) rename host-configs/LLNL/{quartz-icc@19.0.4.cmake => quartz-icc-19.cmake} (95%) rename host-configs/LLNL/{tioga-cce@15.0.0.cmake => tioga-cce-15.cmake} (89%) create mode 100644 inputFiles/compositionalMultiphaseFlow/c1-ppu/grav_seg_c1ppu_base.xml create mode 100644 inputFiles/compositionalMultiphaseFlow/c1-ppu/grav_seg_c1ppu_drain.xml create mode 100644 inputFiles/compositionalMultiphaseFlow/c1-ppu/grav_seg_c1ppu_hyst.xml create mode 100644 inputFiles/compositionalMultiphaseFlow/c1-ppu/tables/drainagePhaseVolFraction_gas.txt create mode 100644 inputFiles/compositionalMultiphaseFlow/c1-ppu/tables/drainagePhaseVolFraction_water.txt create mode 100644 inputFiles/compositionalMultiphaseFlow/c1-ppu/tables/drainageRelPerm_gas.txt create mode 100644 inputFiles/compositionalMultiphaseFlow/c1-ppu/tables/drainageRelPerm_water.txt create mode 100644 inputFiles/compositionalMultiphaseFlow/c1-ppu/tables/imbibitionPhaseVolFraction_gas.txt create mode 100644 inputFiles/compositionalMultiphaseFlow/c1-ppu/tables/imbibitionPhaseVolFraction_water.txt create mode 100644 inputFiles/compositionalMultiphaseFlow/c1-ppu/tables/imbibitionRelPerm_gas.txt create mode 100644 inputFiles/compositionalMultiphaseFlow/c1-ppu/tables/imbibitionRelPerm_water.txt create mode 100644 inputFiles/compositionalMultiphaseFlow/c1-ppu/tables/pvdg.txt create mode 100644 inputFiles/compositionalMultiphaseFlow/c1-ppu/tables/pvtw.txt delete mode 100644 inputFiles/efemFractureMechanics/SneddonRotated_base.xml delete mode 100644 inputFiles/efemFractureMechanics/Sneddon_benchmark.xml delete mode 100644 inputFiles/efemFractureMechanics/Sneddon_benchmark2.xml delete mode 100644 inputFiles/efemFractureMechanics/Sneddon_benchmark3.xml rename inputFiles/efemFractureMechanics/{Sneddon_staticCondensation_base.xml => Sneddon_embeddedFracShapes_base.xml} (58%) create mode 100644 inputFiles/efemFractureMechanics/Sneddon_embeddedFracShapes_smoke.xml create mode 100644 inputFiles/efemFractureMechanics/Sneddon_embeddedFrac_staticCondensation_benchmark.xml create mode 100644 inputFiles/efemFractureMechanics/Sneddon_embeddedFrac_staticCondensation_smoke.xml create mode 100644 inputFiles/efemFractureMechanics/Sneddon_embeddedFrac_verification.xml delete mode 100644 inputFiles/efemFractureMechanics/Sneddon_staticCondensation_benchmark.xml delete mode 100644 inputFiles/efemFractureMechanics/Sneddon_staticCondensation_benchmark2.xml create mode 100644 inputFiles/efemFractureMechanics/scripts/sneddonCurveChecks.py create mode 100644 inputFiles/hydraulicFracturing/kgdToughnessDominated_poroelastic_base.xml create mode 100644 inputFiles/hydraulicFracturing/kgdToughnessDominated_poroelastic_benchmark.xml create mode 100644 inputFiles/hydraulicFracturing/kgdToughnessDominated_poroelastic_smoke.xml create mode 100644 inputFiles/hydraulicFracturing/kgdViscosityDominated_poroelastic_base.xml create mode 100644 inputFiles/hydraulicFracturing/kgdViscosityDominated_poroelastic_benchmark.xml create mode 100644 inputFiles/hydraulicFracturing/kgdViscosityDominated_poroelastic_smoke.xml create mode 100644 inputFiles/hydraulicFracturing/pennyShapedToughnessDominated_poroelastic_base.xml create mode 100644 inputFiles/hydraulicFracturing/pennyShapedToughnessDominated_poroelastic_benchmark.xml create mode 100644 inputFiles/hydraulicFracturing/pennyShapedToughnessDominated_poroelastic_smoke.xml create mode 100644 inputFiles/hydraulicFracturing/pennyShapedViscosityDominated_poroelastic_base.xml create mode 100644 inputFiles/hydraulicFracturing/pennyShapedViscosityDominated_poroelastic_benchmark.xml create mode 100644 inputFiles/hydraulicFracturing/pennyShapedViscosityDominated_poroelastic_smoke.xml create mode 100644 inputFiles/hydraulicFracturing/pknViscosityDominated_poroelastic_base.xml create mode 100644 inputFiles/hydraulicFracturing/pknViscosityDominated_poroelastic_benchmark.xml create mode 100644 inputFiles/hydraulicFracturing/pknViscosityDominated_poroelastic_smoke.xml create mode 100644 inputFiles/hydraulicFracturing/scripts/hydrofractureCurveChecks.py rename inputFiles/poromechanics/{PoroElastic_Mandel_benchmark.xml => PoroElastic_Mandel_benchmark_fim.xml} (53%) create mode 100644 inputFiles/poromechanics/PoroElastic_Mandel_benchmark_sequential.xml rename inputFiles/poromechanics/{PoroElastic_Mandel_smoke.xml => PoroElastic_Mandel_smoke_fim.xml} (56%) create mode 100644 inputFiles/poromechanics/PoroElastic_Mandel_smoke_sequential.xml rename inputFiles/poromechanics/{PoroElastic_deadoil_3ph_baker_2d.xml => PoroElastic_deadoil_3ph_baker_2d_base.xml} (90%) create mode 100644 inputFiles/poromechanics/PoroElastic_deadoil_3ph_baker_2d_fim.xml create mode 100644 inputFiles/poromechanics/PoroElastic_deadoil_3ph_baker_2d_sequential.xml rename inputFiles/poromechanicsFractures/{PoroElastic_efem-edfm_eggModel.xml => PoroElastic_efem-edfm_eggModel_small.xml} (96%) rename inputFiles/poromechanicsFractures/{PoroElastic_embFractures.xml => PoroElastic_efem-edfm_pennyCrack_base.xml} (58%) create mode 100644 inputFiles/poromechanicsFractures/PoroElastic_efem-edfm_pennyCrack_benchmark.xml create mode 100644 inputFiles/poromechanicsFractures/PoroElastic_efem-edfm_pennyCrack_smoke.xml delete mode 100644 inputFiles/poromechanicsFractures/PoroElastic_embFractures_smoke.xml create mode 100644 inputFiles/solidMechanics/DruckerPragerWellbore_base.xml create mode 100644 inputFiles/solidMechanics/DruckerPragerWellbore_benchmark.xml create mode 100644 inputFiles/solidMechanics/DruckerPragerWellbore_smoke.xml create mode 100644 inputFiles/solidMechanics/beamBending_curve.py create mode 100644 inputFiles/thermalSinglePhaseFlowFractures/egsCollab_thermalFlow/TC_flowRate.csv create mode 100644 inputFiles/thermalSinglePhaseFlowFractures/egsCollab_thermalFlow/TN_flowRate.csv create mode 100644 inputFiles/thermalSinglePhaseFlowFractures/egsCollab_thermalFlow/TU_flowRate.csv create mode 100644 inputFiles/thermalSinglePhaseFlowFractures/egsCollab_thermalFlow/TU_temperature.csv create mode 100644 inputFiles/thermalSinglePhaseFlowFractures/egsCollab_thermalFlow/bc_data/pressure.geos create mode 100644 inputFiles/thermalSinglePhaseFlowFractures/egsCollab_thermalFlow/bc_data/temp.geos create mode 100644 inputFiles/thermalSinglePhaseFlowFractures/egsCollab_thermalFlow/bc_data/xlin.geos create mode 100644 inputFiles/thermalSinglePhaseFlowFractures/egsCollab_thermalFlow/bc_data/ylin.geos create mode 100644 inputFiles/thermalSinglePhaseFlowFractures/egsCollab_thermalFlow/bc_data/zlin.geos create mode 100644 inputFiles/thermalSinglePhaseFlowFractures/egsCollab_thermalFlow/egsCollabLaunch.sh create mode 100644 inputFiles/thermalSinglePhaseFlowFractures/egsCollab_thermalFlow/egsCollab_thermalFlow_base.xml create mode 100644 inputFiles/thermalSinglePhaseFlowFractures/egsCollab_thermalFlow/egsCollab_thermalFlow_initialCond_base.xml create mode 100644 inputFiles/thermalSinglePhaseFlowFractures/egsCollab_thermalFlow/egsCollab_thermalFlow_initialCond_coarse.xml create mode 100644 inputFiles/thermalSinglePhaseFlowFractures/egsCollab_thermalFlow/egsCollab_thermalFlow_initialCond_fine.xml create mode 100644 inputFiles/thermalSinglePhaseFlowFractures/egsCollab_thermalFlow/egsCollab_thermalFlow_injection_base.xml create mode 100644 inputFiles/thermalSinglePhaseFlowFractures/egsCollab_thermalFlow/egsCollab_thermalFlow_injection_coarse.xml create mode 100644 inputFiles/thermalSinglePhaseFlowFractures/egsCollab_thermalFlow/egsCollab_thermalFlow_injection_fine.xml create mode 100644 inputFiles/thermalSinglePhaseFlowFractures/egsCollab_thermalFlow/injectionSchedule_time.csv create mode 100644 inputFiles/thermalSinglePhaseFlowFractures/egsCollab_thermalFlow/productionSchedule_time.csv create mode 100644 inputFiles/thermalSinglePhaseFlowFractures/fractureMatrixThermalFlow_base.xml create mode 100644 inputFiles/thermalSinglePhaseFlowFractures/fractureMatrixThermalFlow_bc.xml create mode 100644 inputFiles/thermalSinglePhaseFlowFractures/fractureMatrixThermalFlow_conforming_base.xml create mode 100644 inputFiles/thermalSinglePhaseFlowFractures/fractureMatrixThermalFlow_conforming_bc.xml create mode 100644 inputFiles/thermalSinglePhaseFlowFractures/fractureMatrixThermalFlow_conforming_benchmark.xml create mode 100644 inputFiles/thermalSinglePhaseFlowFractures/fractureMatrixThermalFlow_conforming_smoke.xml create mode 100644 inputFiles/thermalSinglePhaseFlowFractures/fractureMatrixThermalFlow_edfm_base.xml rename inputFiles/{efemFractureMechanics/Sneddon_staticCondensation_smoke.xml => thermalSinglePhaseFlowFractures/fractureMatrixThermalFlow_edfm_benchmark.xml} (52%) create mode 100644 inputFiles/thermalSinglePhaseFlowFractures/fractureMatrixThermalFlow_edfm_smoke.xml create mode 100644 inputFiles/thermoPoromechanicsFractures/ThermoPoroElastic_base.xml create mode 100644 inputFiles/thermoPoromechanicsFractures/ThermoPoroElastic_conforming_base.xml create mode 100644 inputFiles/thermoPoromechanicsFractures/ThermoPoroElastic_conforming_benchmark.xml create mode 100644 inputFiles/thermoPoromechanicsFractures/ThermoPoroElastic_conforming_smoke.xml create mode 100644 inputFiles/thermoPoromechanicsFractures/ThermoPoroElastic_efem-edfm_base.xml create mode 100644 inputFiles/thermoPoromechanicsFractures/ThermoPoroElastic_efem-edfm_eggModel_small.xml create mode 100644 inputFiles/thermoPoromechanicsFractures/ThermoPoroElastic_efem-edfm_verticalFrac_base.xml create mode 100644 inputFiles/thermoPoromechanicsFractures/ThermoPoroElastic_efem-edfm_verticalFrac_benchmark.xml create mode 100644 inputFiles/thermoPoromechanicsFractures/ThermoPoroElastic_efem-edfm_verticalFrac_smoke.xml create mode 100755 scripts/ci_build_and_test.sh rename scripts/{travis_build_and_test.sh => ci_build_and_test_in_container.sh} (97%) create mode 100644 scripts/createRectangle.py create mode 100644 scripts/postProcessing/plotsolution_thermalFlow.py rename src/coreComponents/common/{fixedSizeDeque.hpp => FixedSizeDeque.hpp} (88%) create mode 100644 src/coreComponents/common/FixedSizeDequeWithMutexes.hpp create mode 100644 src/coreComponents/common/LifoStorage.hpp create mode 100644 src/coreComponents/common/LifoStorageCommon.hpp create mode 100644 src/coreComponents/common/LifoStorageCuda.hpp create mode 100644 src/coreComponents/common/LifoStorageHost.hpp create mode 100644 src/coreComponents/common/MultiMutexesLock.hpp delete mode 100644 src/coreComponents/common/lifoStorage.hpp create mode 100644 src/coreComponents/constitutive/fluid/multifluid/compositional/functions/KValueInitialization.hpp create mode 100644 src/coreComponents/constitutive/fluid/multifluid/compositional/functions/NegativeTwoPhaseFlash.hpp delete mode 100644 src/coreComponents/constitutive/fluid/multifluid/compositional/functions/RachfordRice.cpp create mode 100644 src/coreComponents/constitutive/unitTests/TestFluid.hpp create mode 100644 src/coreComponents/constitutive/unitTests/testKValueInitialization.cpp create mode 100644 src/coreComponents/constitutive/unitTests/testNegativeTwoPhaseFlash.cpp rename src/coreComponents/{unitTests/functionsTests => functions/unitTests}/CMakeLists.txt (68%) rename src/coreComponents/{unitTests/functionsTests => functions/unitTests}/testFunctions.cpp (99%) create mode 100644 src/coreComponents/linearAlgebra/interfaces/hypre/mgrStrategies/SolidMechanicsEmbeddedFractures.hpp create mode 100644 src/coreComponents/linearAlgebra/unitTests/testReverseCutHillMcKeeOrdering.cpp create mode 100644 src/coreComponents/linearAlgebra/utilities/ReverseCutHillMcKeeOrdering.cpp create mode 100644 src/coreComponents/linearAlgebra/utilities/ReverseCutHillMcKeeOrdering.hpp create mode 100644 src/coreComponents/mesh/generators/LineBlock.cpp create mode 100644 src/coreComponents/mesh/generators/LineBlock.hpp create mode 100644 src/coreComponents/mesh/generators/LineBlockABC.hpp create mode 100644 src/coreComponents/mesh/generators/PartitionDescriptor.hpp create mode 100644 src/coreComponents/mesh/generators/WellGeneratorBase.cpp create mode 100644 src/coreComponents/mesh/generators/WellGeneratorBase.hpp create mode 100644 src/coreComponents/mesh/simpleGeometricObjects/CustomPolarObject.cpp create mode 100644 src/coreComponents/mesh/simpleGeometricObjects/CustomPolarObject.hpp create mode 100644 src/coreComponents/mesh/simpleGeometricObjects/Disc.cpp create mode 100644 src/coreComponents/mesh/simpleGeometricObjects/Disc.hpp create mode 100644 src/coreComponents/mesh/simpleGeometricObjects/PlanarGeometricObject.cpp rename src/coreComponents/mesh/simpleGeometricObjects/{BoundedPlane.hpp => PlanarGeometricObject.hpp} (65%) rename src/coreComponents/mesh/simpleGeometricObjects/{BoundedPlane.cpp => Rectangle.cpp} (71%) create mode 100644 src/coreComponents/mesh/simpleGeometricObjects/Rectangle.hpp create mode 100644 src/coreComponents/mesh/utilities/AverageOverQuadraturePointsKernel.hpp create mode 100644 src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseProppantFluxKernels.cpp create mode 100644 src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseProppantFluxKernels.hpp delete mode 100644 src/coreComponents/physicsSolvers/multiphysics/SinglePhasePoromechanicsFluxKernels.cpp delete mode 100644 src/coreComponents/physicsSolvers/multiphysics/SinglePhasePoromechanicsFluxKernels.hpp create mode 100644 src/coreComponents/physicsSolvers/multiphysics/poromechanicsKernels/SinglePhasePoromechanicsConformingFractures.hpp create mode 100644 src/coreComponents/physicsSolvers/multiphysics/poromechanicsKernels/SinglePhasePoromechanicsEmbeddedFractures.hpp create mode 100644 src/coreComponents/physicsSolvers/multiphysics/poromechanicsKernels/ThermalSinglePhasePoromechanicsConformingFractures.hpp create mode 100644 src/coreComponents/physicsSolvers/multiphysics/poromechanicsKernels/ThermalSinglePhasePoromechanicsEFEM.hpp create mode 100644 src/coreComponents/physicsSolvers/multiphysics/poromechanicsKernels/ThermalSinglePhasePoromechanicsEFEM_impl.hpp create mode 100644 src/coreComponents/physicsSolvers/multiphysics/poromechanicsKernels/ThermalSinglePhasePoromechanicsEmbeddedFractures.hpp create mode 100644 src/coreComponents/schema/docs/CustomPolarObject.rst rename src/coreComponents/schema/docs/{BoundedPlane_other.rst => CustomPolarObject_other.rst} (100%) create mode 100644 src/coreComponents/schema/docs/Disc.rst create mode 100644 src/coreComponents/schema/docs/Disc_other.rst rename src/coreComponents/schema/docs/{BoundedPlane.rst => Rectangle.rst} (100%) create mode 100644 src/coreComponents/schema/docs/Rectangle_other.rst create mode 100644 src/coreComponents/schema/docs/crusher.rst create mode 100644 src/coreComponents/schema/docs/crusher_other.rst create mode 100644 src/docs/sphinx/advancedExamples/validationStudies/wellboreProblems/dpWellbore/Example.rst create mode 100644 src/docs/sphinx/advancedExamples/validationStudies/wellboreProblems/dpWellbore/dpWellboreVerification.png create mode 100644 src/docs/sphinx/advancedExamples/validationStudies/wellboreProblems/dpWellbore/dpWellbore_analyticalResults.py create mode 100644 src/docs/sphinx/advancedExamples/validationStudies/wellboreProblems/dpWellbore/dpWellbore_plot.py delete mode 100644 src/docs/sphinx/advancedExamples/validationStudies/wellboreProblems/edpWellbore/Verification.png create mode 100644 src/docs/sphinx/advancedExamples/validationStudies/wellboreProblems/edpWellbore/edpWellboreVerification.png create mode 100644 src/docs/sphinx/advancedExamples/validationStudies/wellboreProblems/edpWellbore/edpWellbore_analyticalResults.py create mode 100644 src/docs/sphinx/advancedExamples/validationStudies/wellboreProblems/edpWellbore/edpWellbore_plot.py create mode 100644 src/docs/sphinx/advancedExamples/validationStudies/wellboreProblems/elastoPlasticWellboreAnalyticalSolutions.py diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 1e60da80c6e..a478d629763 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,4 +1,4 @@ -# Replace `DEFINE_ME` by the value of `GEOSX_TPL_TAG` in the `.travis.yml` file. +# Replace `DEFINE_ME` by the value of `GEOSX_TPL_TAG` in the `.github/workflows/ci_tests.yml` file. FROM docker.io/geosx/ubuntu20.04-gcc10:DEFINE_ME RUN apt-get update diff --git a/.github/workflows/ci_tests.yml b/.github/workflows/ci_tests.yml new file mode 100644 index 00000000000..8e0fd894a15 --- /dev/null +++ b/.github/workflows/ci_tests.yml @@ -0,0 +1,306 @@ +name: GEOS CI +on: pull_request + +# Cancels in-progress workflows for a PR when updated +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + GEOSX_TPL_TAG: 236-58 + +jobs: + # Matrix jobs will be cancelled if PR is a draft. + # PR status must be "Open" to run CI. + check_pull_request_is_not_a_draft: + # We use the most recent ubuntu distribution available in Github Actions to ensure maximum support of google cloud's sdk. + runs-on: ubuntu-22.04 + steps: + - name: Check that the PR is not a draft (cancel rest of jobs otherwise) + run: | + echo "Is PR a draft?" + echo ${{ toJSON(github.event.pull_request.draft) }} + if [[ ${{ toJSON(github.event.pull_request.draft) }} == true ]]; then "false" ; else "true"; fi + + # PR must be assigned to be merged. + # This job will fail if this is not the case. + check_pull_request_is_assigned: + needs: [check_pull_request_is_not_a_draft] + runs-on: ubuntu-22.04 + steps: + # Assignee ID will be null if PR is not assigned + - name: Check that the PR is assigned + run: | + id=$(curl -H "Accept: application/vnd.github+json" \ + https://api.github.com/repos/${{ github.repository }}/pulls/${{ github.event.number }} \ + | jq '.assignee.id') + echo "Is PR Assigned? (Check for Assignee ID)" + echo $id + if [[ $id != null ]]; then "true" ; else "false"; fi + + check_submodules: + needs: [check_pull_request_is_not_a_draft] + runs-on: ubuntu-22.04 + steps: + # The integrated test submodule repository contains large data (using git lfs). + # To save time (and money) we do not let Github Actions automatically clone all our (lfs) subrepositories and do it by hand. + - name: Checkout Repository + uses: actions/checkout@v3 + with: + # Let script update submodules; Github Actions submodule history causes error + submodules: false + lfs: false + - name: Check that submodules are up to date + run: "scripts/test_submodule_updated.sh" + + code_style: + needs: [check_pull_request_is_not_a_draft] + runs-on: ubuntu-22.04 + steps: + - name: Checkout Repository + uses: actions/checkout@v3 + with: + submodules: true + lfs: false + - name: Check style + env: + DOCKER_REPOSITORY: geosx/ubuntu20.04-gcc9 + CMAKE_BUILD_TYPE: Release + BUILD_AND_TEST_ARGS: --test-code-style + run: ./scripts/ci_build_and_test.sh + + documentation: + needs: [check_pull_request_is_not_a_draft] + runs-on: ubuntu-22.04 + steps: + - name: Checkout Repository + uses: actions/checkout@v3 + with: + submodules: true + lfs: false + - name: Check documentation + env: + DOCKER_REPOSITORY: geosx/ubuntu20.04-gcc9 + CMAKE_BUILD_TYPE: Release + BUILD_AND_TEST_ARGS: --test-documentation + run: ./scripts/ci_build_and_test.sh + + + linux_builds: + name: ${{ matrix.name }} +# runs-on: ubuntu-22.04 + runs-on: ${{ matrix.os }} + needs: [check_pull_request_is_not_a_draft] + strategy: + + # In-progress jobs will not be cancelled if there is a failure + fail-fast : false + matrix: + include: + - name: Ubuntu (20.04, gcc 9.3.0, open-mpi 4.0.3) + DOCKER_REPOSITORY: geosx/ubuntu20.04-gcc9 + OS: ubuntu-22.04 + CMAKE_BUILD_TYPE: Release + + - name: Ubuntu debug (20.04, gcc 10.3.0, open-mpi 4.0.3) + DOCKER_REPOSITORY: geosx/ubuntu20.04-gcc10 + OS: ubuntu-22.04 + CMAKE_BUILD_TYPE: Debug + + - name: Ubuntu (20.04, gcc 10.3.0, open-mpi 4.0.3) + DOCKER_REPOSITORY: geosx/ubuntu20.04-gcc10 + OS: ubuntu-22.04 + CMAKE_BUILD_TYPE: Release + + # Matrix jobs that deploy to Google Cloud + - name: Pecan CPU (centos 7.7, gcc 8.2.0, open-mpi 4.0.1, mkl 2019.5) + DOCKER_REPOSITORY: geosx/pecan-cpu-gcc8.2.0-openmpi4.0.1-mkl2019.5 + OS: ubuntu-22.04 + CMAKE_BUILD_TYPE: Release + HOST_CONFIG: host-configs/TOTAL/pecan-CPU.cmake + GCP_BUCKET: geosx/Pecan-CPU + + - name: Pangea 2 (centos 7.6, gcc 8.3.0, open-mpi 2.1.5, mkl 2019.3) + DOCKER_REPOSITORY: geosx/pangea2-gcc8.3.0-openmpi2.1.5-mkl2019.3 + OS: ubuntu-22.04 + CMAKE_BUILD_TYPE: Release + GCP_BUCKET: geosx/Pangea2 + ENABLE_HYPRE: ON + ENABLE_TRILINOS: OFF + + - name: Sherlock CPU (centos 7.9.2009, gcc 10.1.0, open-mpi 4.1.2, openblas 0.3.10) + DOCKER_REPOSITORY: geosx/sherlock-gcc10.1.0-openmpi4.1.2-openblas0.3.10-zlib1.2.11 + OS: ubuntu-22.04 + CMAKE_BUILD_TYPE: Release + HOST_CONFIG: host-configs/Stanford/sherlock-gcc10-ompi4.1.2-openblas0.3.10.cmake + GCP_BUCKET: geosx/Sherlock-CPU + ENABLE_HYPRE: ON + ENABLE_TRILINOS: OFF + + - name: Ubuntu (22.04, gcc 11.2.0, open-mpi 4.1.2) + DOCKER_REPOSITORY: geosx/ubuntu22.04-gcc11 + OS: ubuntu-22.04 + CMAKE_BUILD_TYPE: Release + GCP_BUCKET: geosx/ubuntu22.04-gcc11 + + steps: + - name: Checkout Repository + uses: actions/checkout@v3 + with: + submodules: true + lfs: false + + - id: 'auth' + if: matrix.GCP_BUCKET + uses: 'google-github-actions/auth@v1' + with: + credentials_json: '${{ secrets.GOOGLE_CLOUD_GCP }}' + - name: 'Set up Cloud SDK' + if: matrix.GCP_BUCKET + uses: 'google-github-actions/setup-gcloud@v1' + with: + version: '>= 363.0.0' + + - name: Print environment + run: printenv + + # Build and test only + # Builds only the geosx executable (timeout when building tests) + - name: Build and test + if: ${{ !(matrix.GCP_BUCKET) }} + env: + DOCKER_REPOSITORY: ${{ matrix.DOCKER_REPOSITORY }} + CMAKE_BUILD_TYPE: ${{ matrix.CMAKE_BUILD_TYPE }} + BUILD_AND_TEST_ARGS: ${{ matrix.BUILD_AND_TEST_ARGS }} + ENABLE_HYPRE: ${{ matrix.ENABLE_HYPRE }} + ENABLE_HYPRE_DEVICE: ${{ matrix.ENABLE_HYPRE_DEVICE }} + ENABLE_TRILINOS: ${{ matrix.ENABLE_TRILINOS }} + run: ./scripts/ci_build_and_test.sh + + # Build, test, uploads GEOSX and its TPL to GCP/GCS using gcloud CLI + - name: Build and test and deploy + if: matrix.GCP_BUCKET + env: + DOCKER_REPOSITORY: ${{ matrix.DOCKER_REPOSITORY }} + CMAKE_BUILD_TYPE: ${{ matrix.CMAKE_BUILD_TYPE }} + BUILD_AND_TEST_ARGS: ${{ matrix.BUILD_AND_TEST_ARGS }} + HOST_CONFIG: ${{ matrix.HOST_CONFIG }} + ENABLE_HYPRE: ${{ matrix.ENABLE_HYPRE }} + ENABLE_TRILINOS: ${{ matrix.ENABLE_TRILINOS }} + GCP_BUCKET: ${{ matrix.GCP_BUCKET }} + COMMIT: ${{ github.event.pull_request.head.sha }} + run: | + source ./scripts/ci_build_and_test.sh + TMP_DIR=/tmp + GEOSX_EXPORT_DIR=GEOSX-and-TPL-${COMMIT:0:7} + docker cp -a ${CONTAINER_NAME}:${GEOSX_TPL_DIR}/.. ${TMP_DIR}/${GEOSX_EXPORT_DIR} + GEOSX_BUNDLE=${TMP_DIR}/${GEOSX_EXPORT_DIR}.tar.gz + tar czf ${GEOSX_BUNDLE} --directory=${TMP_DIR} ${GEOSX_EXPORT_DIR} + CLOUDSDK_PYTHON=python3 gsutil cp -a public-read ${GEOSX_BUNDLE} gs://${GCP_BUCKET}/ + + large_cuda_builds: + name: ${{ matrix.name }} +# runs-on: ubuntu-22.04 + runs-on: ${{ matrix.os }} + needs: [linux_builds] + strategy: + + # In-progress jobs will not be cancelled if there is a failure + fail-fast : false + matrix: + include: + - name: Ubuntu CUDA debug (20.04, clang 10.0.0 + gcc 9.4.0, open-mpi 4.0.3, cuda-11.8.89) + DOCKER_REPOSITORY: geosx/ubuntu20.04-clang10.0.0-cuda11.8.89 + OS: Runner_4core_16GB + CMAKE_BUILD_TYPE: Debug + BUILD_AND_TEST_ARGS: "--disable-unit-tests --build-exe-only --disable-schema-deployment" + ENABLE_HYPRE: ON + ENABLE_HYPRE_DEVICE: CUDA + ENABLE_TRILINOS: OFF + + - name: Ubuntu CUDA (20.04, clang 10.0.0 + gcc 9.4.0, open-mpi 4.0.3, cuda-11.8.89) + DOCKER_REPOSITORY: geosx/ubuntu20.04-clang10.0.0-cuda11.8.89 + OS: Runner_4core_16GB + CMAKE_BUILD_TYPE: Release + BUILD_AND_TEST_ARGS: "--disable-unit-tests --disable-schema-deployment" + ENABLE_HYPRE: ON + ENABLE_HYPRE_DEVICE: CUDA + ENABLE_TRILINOS: OFF + + - name: Centos (7.7, gcc 8.3.1, open-mpi 1.10.7, cuda 11.8.89) + DOCKER_REPOSITORY: geosx/centos7.7-gcc8.3.1-cuda11.8.89 + OS: Runner_4core_16GB + CMAKE_BUILD_TYPE: Release + BUILD_AND_TEST_ARGS: "--disable-unit-tests --disable-schema-deployment" + + # Matrix job that deploys to Google Cloud + - name: Pecan GPU (centos 7.7, gcc 8.2.0, open-mpi 4.0.1, mkl 2019.5, cuda 11.5.119) + DOCKER_REPOSITORY: geosx/pecan-gpu-gcc8.2.0-openmpi4.0.1-mkl2019.5-cuda11.5.119 + OS: Runner_4core_16GB + CMAKE_BUILD_TYPE: Release + BUILD_AND_TEST_ARGS: "--build-exe-only --disable-unit-tests --disable-schema-deployment" + HOST_CONFIG: host-configs/TOTAL/pecan-GPU.cmake + GCP_BUCKET: geosx/Pecan-GPU + + steps: + - name: Checkout Repository + uses: actions/checkout@v3 + with: + submodules: true + lfs: false + + - id: 'auth' + if: matrix.GCP_BUCKET + uses: 'google-github-actions/auth@v1' + with: + credentials_json: '${{ secrets.GOOGLE_CLOUD_GCP }}' + - name: 'Set up Cloud SDK' + if: matrix.GCP_BUCKET + uses: 'google-github-actions/setup-gcloud@v1' + with: + version: '>= 363.0.0' + + - name: Print environment + run: printenv + + # Build and test only + # Builds only the geosx executable (timeout when building tests) + - name: Build and test + if: ${{ !(matrix.GCP_BUCKET) }} + env: + DOCKER_REPOSITORY: ${{ matrix.DOCKER_REPOSITORY }} + CMAKE_BUILD_TYPE: ${{ matrix.CMAKE_BUILD_TYPE }} + BUILD_AND_TEST_ARGS: ${{ matrix.BUILD_AND_TEST_ARGS }} + ENABLE_HYPRE: ${{ matrix.ENABLE_HYPRE }} + ENABLE_HYPRE_DEVICE: ${{ matrix.ENABLE_HYPRE_DEVICE }} + ENABLE_TRILINOS: ${{ matrix.ENABLE_TRILINOS }} + run: ./scripts/ci_build_and_test.sh + + # Build, test, uploads GEOSX and its TPL to GCP/GCS using gcloud CLI + - name: Build and test and deploy + if: matrix.GCP_BUCKET + env: + DOCKER_REPOSITORY: ${{ matrix.DOCKER_REPOSITORY }} + CMAKE_BUILD_TYPE: ${{ matrix.CMAKE_BUILD_TYPE }} + BUILD_AND_TEST_ARGS: ${{ matrix.BUILD_AND_TEST_ARGS }} + HOST_CONFIG: ${{ matrix.HOST_CONFIG }} + ENABLE_HYPRE: ${{ matrix.ENABLE_HYPRE }} + ENABLE_TRILINOS: ${{ matrix.ENABLE_TRILINOS }} + GCP_BUCKET: ${{ matrix.GCP_BUCKET }} + COMMIT: ${{ github.event.pull_request.head.sha }} + run: | + source ./scripts/ci_build_and_test.sh + TMP_DIR=/tmp + GEOSX_EXPORT_DIR=GEOSX-and-TPL-${COMMIT:0:7} + docker cp -a ${CONTAINER_NAME}:${GEOSX_TPL_DIR}/.. ${TMP_DIR}/${GEOSX_EXPORT_DIR} + GEOSX_BUNDLE=${TMP_DIR}/${GEOSX_EXPORT_DIR}.tar.gz + tar czf ${GEOSX_BUNDLE} --directory=${TMP_DIR} ${GEOSX_EXPORT_DIR} + CLOUDSDK_PYTHON=python3 gsutil cp -a public-read ${GEOSX_BUNDLE} gs://${GCP_BUCKET}/ + + # Convenience job - passes when all other jobs have passed. + check_that_all_jobs_succeeded: + runs-on: ubuntu-22.04 + needs: [check_pull_request_is_not_a_draft, check_pull_request_is_assigned, check_submodules, code_style, documentation, linux_builds, large_cuda_builds] + steps: + - name: Success + run: "true" diff --git a/.github/workflows/epics-action.yaml b/.github/workflows/epics-action.yaml deleted file mode 100644 index 8fa44e201c4..00000000000 --- a/.github/workflows/epics-action.yaml +++ /dev/null @@ -1,15 +0,0 @@ -name: Update epics -on: - issues: - types: [opened, closed, reopened] -jobs: - epics: - runs-on: ubuntu-latest - name: Update epic issues - steps: - - name: Run epics action - uses: cloudaper/epics-action@v1.1.6 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - epic-label-name: EPIC - auto-close-epic: false \ No newline at end of file diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 70bb831e139..a8f4c6fbea1 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -30,8 +30,11 @@ stages: - mkdir ${SYSTEM} - # geosxats needs python2 to run - - module load python/2 + # newer geosxats needs python3 to run + - module load python/3 + + # GEOS requires CMake >=3.23 + - module load cmake/3.23.1 # CONFIGURE - echo "~~~~~~~~~~ START - configure ~~~~~~~~~~~" @@ -117,8 +120,12 @@ stages: stage: build variables: ALLOC_COMMAND: "salloc -N1 -ppdebug" - INTEGRATED_ALLOC_COMMAND: "salloc -N 1 -n 64 -ppdebug" + INTEGRATED_ALLOC_COMMAND: "salloc -N 10 -p pbatch" SYSTEM: "tioga" + before_script: + - module load rocm/5.4.3 + - module load cce/15.0.0 + - module load cmake/3.23.1 tags: - shell - tioga @@ -141,21 +148,21 @@ stages: # quartz job quartz_clang_14_build: variables: - HOST_CONFIG: "quartz-clang@14.cmake" + HOST_CONFIG: "quartz-clang-14.cmake" extends: [.build_on_quartz] #### # tioga job -tioga_clang_14_build: +tioga_cce_15_build: variables: - HOST_CONFIG: "tioga-cce@15.0.0.cmake" + HOST_CONFIG: "tioga-cce-15.cmake" extends: [.build_on_tioga] #### # lassen job -lassen_clang_upstream_build: +lassen_clang_10_cuda_11_build: variables: - HOST_CONFIG: "lassen-clang@upstream.cmake" + HOST_CONFIG: "lassen-clang-10-cuda-11.cmake" extends: [.build_on_lassen] #### diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 969b3ed5e50..00000000000 --- a/.travis.yml +++ /dev/null @@ -1,253 +0,0 @@ -language: shell -os: linux -dist: focal - -vm: - size: large - -env: - global: - - GEOSX_TPL_TAG=220-932 - - secure: CGs2uH6efq1Me6xJWRr0BnwtwxoujzlowC4FHXHdWbNOkPsXf7nCgdaW5vthfD3bhnOeEUQSrfxdhTRtyU/NfcKLmKgGBnZOdUG4/JJK4gDSJ2Wp8LZ/mB0QEoODKVxbh+YtoAiHe3y4M9PGCs+wkNDw/3eEU00cK12DZ6gad0RbLjI3xkhEr/ZEZDZkcYg9yHAhl5bmpqoh/6QGnIg8mxIqdAtGDw+6tT0EgUqjeqc5bG5WwsamKzJItHSXD5zx8IJAlgDk4EzEGjZe0m56YnNfb9iwqqUsmL3Cuwgs7ByVDYw78JC5Kv42YqoxA5BxMT2mFsEe37TpYNXlzofU7ma2Duw9DGXWQd4IkTCcBxlyR0I0bfo0TmgO+y7PYG9lIyHPUkENemdozsZcWamqqkqegiEdRhDVYlSRo3mu7iCwTS6ZTALliVyEYjYxYb7oAnR3cNywXjblTCI8oKfgLSY+8WijM9SRl57JruIHLkLMCjmRI+cZBfv5tS2tYQTBPkygGrigrrN77ZiC7/TGyfggSN0+y0oYtOAgqEnBcKcreiibMW7tKcV2Z1RFD9ZvIkSc1EXLUPDP8FX1oyhmqBMqVo8LksrYLDJHQ05+F3YNgl2taSt7uMjQ4e8iZ3/IjFeMnbylDw+cj/RbS520HXsFPbWFm2Pb9pceA9n6GnY= - -# The integrated test repository contains large data (using git lfs) and we do not use them here. -# To save time (and money) we do not let travis automatically clone all our (lfs) subrepositories and do it by hand. -git: - submodules: false - -__geosx_before_script: &__geosx_before_script - before_script: - - git submodule update --init --recursive src/cmake/blt - - git submodule update --init --recursive src/coreComponents/LvArray - - git submodule update --init --recursive src/coreComponents/constitutive/PVTPackage - - git submodule update --init --recursive src/coreComponents/fileIO/coupling/hdf5_interface - -__geosx_linux_build: &__geosx_linux_build - services: docker - <<: *__geosx_before_script - script: - # The linux build relies on two environment variables DOCKER_REPOSITORY and GEOSX_TPL_TAG to define the TPL version. - # And another CMAKE_BUILD_TYPE to define the build type we want for GEOSX. - # Optional BUILD_AND_TEST_ARGS to pass arguments to travis_build_and_test.sh script. - # - # We extract the location of the GEOSX_TPL from the container... - - GEOSX_TPL_DIR=$(docker run --rm ${DOCKER_REPOSITORY}:${GEOSX_TPL_TAG} /bin/bash -c 'echo ${GEOSX_TPL_DIR}') - # ... so we can install GEOSX alongside. This is assumed for bundling the binaries, so consider modifying with care. - - GEOSX_DIR=${GEOSX_TPL_DIR}/../GEOSX-${TRAVIS_COMMIT:0:7} - # We need to know where the code folder is mounted inside the container so we can run the script at the proper location! - # Since this information is repeated twice, we use a variable. - - TRAVIS_BUILD_DIR_MOUNT_POINT=/tmp/GEOSX - # We need to keep track of the building container (hence the `CONTAINER_NAME`) - # so we can extract the data from it later (if needed). Another solution would have been to use a mount point, - # but that would not have solved the problem for the TPLs (we would require extra action to copy them to the mount point). - - CONTAINER_NAME=geosx_build - # Now we can build GEOSX. - - docker run - --name=${CONTAINER_NAME} - --volume=${TRAVIS_BUILD_DIR}:${TRAVIS_BUILD_DIR_MOUNT_POINT} - --cap-add=SYS_PTRACE - -e HOST_CONFIG=${HOST_CONFIG:-host-configs/environment.cmake} - -e CMAKE_BUILD_TYPE - -e GEOSX_DIR=${GEOSX_DIR} - -e ENABLE_HYPRE=${ENABLE_HYPRE:-OFF} - -e ENABLE_HYPRE_DEVICE=${ENABLE_HYPRE_DEVICE:-CPU} - -e ENABLE_TRILINOS=${ENABLE_TRILINOS:-ON} - ${DOCKER_REPOSITORY}:${GEOSX_TPL_TAG} - ${TRAVIS_BUILD_DIR_MOUNT_POINT}/scripts/travis_build_and_test.sh ${BUILD_AND_TEST_ARGS}; - -__geosx_auto_deploy: &__geosx_auto_deploy - <<: *__geosx_linux_build - # We use the most recent ubuntu distribution available in travis-ci to ensure maximum support of google cloud's sdk. - dist: focal - addons: - apt: - sources: - - sourceline: 'deb https://packages.cloud.google.com/apt cloud-sdk main' - key_url: 'https://packages.cloud.google.com/apt/doc/apt-key.gpg' - packages: - - google-cloud-sdk - after_success: - # The temporary variable used at multiple locations. - - TMP_DIR=/tmp - # Extracting both GEOSX and its TPL from the stopped container... - - GEOSX_EXPORT_DIR=GEOSX-and-TPL-${TRAVIS_COMMIT:0:7} - - docker cp -a ${CONTAINER_NAME}:${GEOSX_TPL_DIR}/.. ${TMP_DIR}/${GEOSX_EXPORT_DIR} - # ... and packing it. - - GEOSX_BUNDLE=${TMP_DIR}/${GEOSX_EXPORT_DIR}.tar.gz - - tar czf ${GEOSX_BUNDLE} --directory=${TMP_DIR} ${GEOSX_EXPORT_DIR} - # Uploading to GCP/GCS using gcloud CLI - - GEOSX_GCLOUD_KEY=/tmp/geosx-key.json - - openssl aes-256-cbc -K $encrypted_5ac030ea614b_key -iv $encrypted_5ac030ea614b_iv - -in ${TRAVIS_BUILD_DIR}/geosx-key.json.enc -out ${GEOSX_GCLOUD_KEY} -d - - gcloud auth activate-service-account --key-file=${GEOSX_GCLOUD_KEY} - - CLOUDSDK_PYTHON=python3 gsutil cp -a public-read ${GEOSX_BUNDLE} gs://${GCP_BUCKET}/ - -__geosx_draft_script: &__geosx_draft_script - script: - # TRAVIS_PULL_REQUEST is false if job is not from a PR - - if [[ $TRAVIS_PULL_REQUEST == false ]]; then exit 0; fi; - - | - is_draft=$(curl -H "Accept: application/vnd.github.v3+json" \ - https://api.github.com/repos/$TRAVIS_REPO_SLUG/pulls/$TRAVIS_PULL_REQUEST | \ - jq ".draft") - - # CI jobs will be cancelled if PR is a draft. - # PR status must be "Open" to run CI. - - | - if [[ $is_draft == true ]]; then - curl -sS -H "Travis-API-Version: 3" \ - -H "Authorization: token $AUTH_VAR" \ - -X POST https://api.travis-ci.com/build/$TRAVIS_BUILD_ID/cancel - exit 1 - else - exit 0 - fi - -# PR must be assigned to be merged. -# This script will fail if this is not the case. -__geosx_assigned_script: &__geosx_assigned_script - script: - # TRAVIS_PULL_REQUEST is false if job is not from a PR - - if [[ $TRAVIS_PULL_REQUEST == false ]]; then exit 0; fi; - - | - is_assigned=$(curl -H "Accept: application/vnd.github.v3+json" \ - https://api.github.com/repos/$TRAVIS_REPO_SLUG/pulls/$TRAVIS_PULL_REQUEST | \ - jq ".assignees|length") - - if [[ $is_assigned == 0 ]]; then exit 1; else exit 0; fi - -__geosx_return_script: &__geosx_return_script - script: - # Verifies if all the "non_blocking_failures" jobs passed - - | - exit $(curl -sS -H "Travis-API-Version: 3" \ - -X GET https://api.travis-ci.com/build/$TRAVIS_BUILD_ID/jobs | \ - jq '[ .jobs[] | select( (.stage.name == "non_blocking_failures") and (.allow_failure == true) and (.state != "passed")) ] | length') - -stages: -- check_that_pull_request_is_not_a_draft -- non_blocking_failures -- builds -- check_that_non_blocking_failure_jobs_succeeded - -jobs: - allow_failures: - - name: code_style - - name: documentation - - name: check_submodules - - name: check_that_pull_request_is_assigned - include: - - stage: check_that_pull_request_is_not_a_draft - name: Check that the PR is not a draft (cancel job otherwise). - <<: *__geosx_draft_script - - stage: non_blocking_failures - name: code_style - <<: *__geosx_linux_build - env: - - DOCKER_REPOSITORY=geosx/ubuntu20.04-gcc9 - - CMAKE_BUILD_TYPE=Release - - BUILD_AND_TEST_ARGS=--test-code-style - - stage: non_blocking_failures - name: documentation - <<: *__geosx_linux_build - env: - - DOCKER_REPOSITORY=geosx/ubuntu20.04-gcc9 - - CMAKE_BUILD_TYPE=Release - - BUILD_AND_TEST_ARGS=--test-documentation - - stage: non_blocking_failures - name: check_submodules - script: scripts/test_submodule_updated.sh - - stage: non_blocking_failures - name: check_that_pull_request_is_assigned - <<: *__geosx_assigned_script - - stage: builds - name: Ubuntu CUDA debug (20.04, clang 10.0.0 + gcc 9.4.0, open-mpi 4.0.3, cuda-11.2.152) - <<: *__geosx_linux_build - # Builds only the geosx executable (timeout when building tests) - env: - - DOCKER_REPOSITORY=geosx/ubuntu20.04-clang10.0.0-cuda11.2.152 - - CMAKE_BUILD_TYPE=Debug - - BUILD_AND_TEST_ARGS="--disable-unit-tests --build-exe-only --disable-schema-deployment" - - ENABLE_HYPRE=ON - - ENABLE_HYPRE_DEVICE=CUDA - - ENABLE_TRILINOS=OFF - - stage: builds - name: Ubuntu CUDA (20.04, clang 10.0.0 + gcc 9.4.0, open-mpi 4.0.3, cuda-11.2.152) - <<: *__geosx_linux_build - env: - - DOCKER_REPOSITORY=geosx/ubuntu20.04-clang10.0.0-cuda11.2.152 - - CMAKE_BUILD_TYPE=Release - - BUILD_AND_TEST_ARGS="--disable-unit-tests --build-exe-only --disable-schema-deployment" - - ENABLE_HYPRE=ON - - ENABLE_HYPRE_DEVICE=CUDA - - ENABLE_TRILINOS=OFF - - stage: builds - name: Centos (7.6, gcc 8.3.1, open-mpi 1.10.7, cuda 10.1.243) - <<: *__geosx_linux_build - env: - - DOCKER_REPOSITORY=geosx/centos7.6.1810-gcc8.3.1-cuda10.1.243 - - CMAKE_BUILD_TYPE=Release - - BUILD_AND_TEST_ARGS="--disable-unit-tests --build-exe-only --disable-schema-deployment" - - stage: builds - name: Pecan GPU (centos 7.7, gcc 8.2.0, open-mpi 4.0.1, mkl 2019.5, cuda 10.2.89p2) - <<: *__geosx_auto_deploy - env: - - DOCKER_REPOSITORY=geosx/pecan-gpu-gcc8.2.0-openmpi4.0.1-mkl2019.5-cuda10.2.89p2 - - CMAKE_BUILD_TYPE=Release - - BUILD_AND_TEST_ARGS="--disable-unit-tests --disable-schema-deployment" - - HOST_CONFIG=host-configs/TOTAL/pecan-GPU.cmake - - GCP_BUCKET=geosx/Pecan-GPU - - stage: builds - name: Pecan CPU (centos 7.7, gcc 8.2.0, open-mpi 4.0.1, mkl 2019.5) - <<: *__geosx_auto_deploy - env: - - DOCKER_REPOSITORY=geosx/pecan-cpu-gcc8.2.0-openmpi4.0.1-mkl2019.5 - - CMAKE_BUILD_TYPE=Release - - HOST_CONFIG=host-configs/TOTAL/pecan-CPU.cmake - - GCP_BUCKET=geosx/Pecan-CPU - - stage: builds - name: Pangea 2 (centos 7.6, gcc 8.3.0, open-mpi 2.1.5, mkl 2019.3) - <<: *__geosx_auto_deploy - env: - - DOCKER_REPOSITORY=geosx/pangea2-gcc8.3.0-openmpi2.1.5-mkl2019.3 - - CMAKE_BUILD_TYPE=Release - - GCP_BUCKET=geosx/Pangea2 - - ENABLE_HYPRE=ON - - ENABLE_TRILINOS=OFF - - stage: builds - name: Sherlock CPU (centos 7.9.2009, gcc 10.1.0, open-mpi 4.1.2, openblas 0.3.10) - <<: *__geosx_auto_deploy - env: - - DOCKER_REPOSITORY=geosx/sherlock-gcc10.1.0-openmpi4.1.2-openblas0.3.10-zlib1.2.11 - - CMAKE_BUILD_TYPE=Release - - HOST_CONFIG=host-configs/Stanford/sherlock-gcc10-ompi4.1.2-openblas0.3.10.cmake - - GCP_BUCKET=geosx/Sherlock-CPU - - ENABLE_HYPRE=ON - - ENABLE_TRILINOS=OFF - - stage: builds - name: Ubuntu (20.04, gcc 9.3.0, open-mpi 4.0.3) - <<: *__geosx_linux_build - env: - - DOCKER_REPOSITORY=geosx/ubuntu20.04-gcc9 - - CMAKE_BUILD_TYPE=Release - - stage: builds - name: Ubuntu debug (20.04, gcc 10.3.0, open-mpi 4.0.3) - <<: *__geosx_linux_build - env: - - DOCKER_REPOSITORY=geosx/ubuntu20.04-gcc10 - - CMAKE_BUILD_TYPE=Debug - - stage: builds - name: Ubuntu (20.04, gcc 10.3.0, open-mpi 4.0.3) - <<: *__geosx_linux_build - env: - - DOCKER_REPOSITORY=geosx/ubuntu20.04-gcc10 - - CMAKE_BUILD_TYPE=Release - - stage: builds - name: Ubuntu (22.04, gcc 11.2.0, open-mpi 4.1.2) - <<: *__geosx_auto_deploy - env: - - DOCKER_REPOSITORY=geosx/ubuntu22.04-gcc11 - - CMAKE_BUILD_TYPE=Release - - GCP_BUCKET=geosx/ubuntu22.04-gcc11 - - stage: check_that_non_blocking_failure_jobs_succeeded - name: Checking if all the non blocking failures jobs (i.e. fail-at-end jobs) have succeeded... - <<: *__geosx_return_script diff --git a/README.md b/README.md index ff26386a199..a86c3c7dcbb 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ [![DOI](https://zenodo.org/badge/131810578.svg)](https://zenodo.org/badge/latestdoi/131810578) -Welcome to the GEOSX project! +Welcome to the GEOS project! ----------------------------- -GEOSX is a simulation framework for modeling coupled flow, transport, and geomechanics +GEOS is a simulation framework for modeling coupled flow, transport, and geomechanics in the subsurface. The code provides advanced solvers for a number of target applications, including - carbon sequestration, @@ -23,9 +23,9 @@ Documentation Our documentation is hosted [here](https://geosx-geosx.readthedocs-hosted.com/en/latest/?). -Who develops GEOSX? +Who develops GEOS? ------------------- -GEOSX is an open source project and is developed by a community of researchers at +GEOS is an open source project and is developed by a community of researchers at several institutions. The bulk of the code has been written by contributors from three main organizations: - Lawrence Livermore National Laboratory, @@ -38,13 +38,13 @@ and [acknowledgements](https://geosx-geosx.readthedocs-hosted.com/en/latest/docs/sphinx/Acknowledgements.html) page for more details. -How does GEOSX relate to GEOS? +How does GEOS relate to the earlier GEOS code? ------------------------------ -GEOSX is the offshoot of an earlier code developed at LLNL called GEOS. The new +GEOS is the offshoot of an earlier code developed at LLNL also called GEOS. The new code differs from our previous efforts in two important ways: - - GEOSX uses a fundamentally different programming model to achieve + - This new code GEOS uses a fundamentally different programming model to achieve high performance on the complicated chip architectures common on today's - HPC systems. The "X" in GEOSX emphasizes our goal of being ready for exascale-class systems as they are delivered. + HPC systems. This code is ready for exascale-class systems as they are delivered. - The new code has been released as an open-source effort to encourage collaboration within the research and industrial community. See the release notes below for details of the [LGPL 2.1 License](./LICENSE) that has been adopted. diff --git a/geosx-key.json.enc b/geosx-key.json.enc deleted file mode 100644 index 6541f68b9b6121a37daff47ce3c6e372667ea4e4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2320 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 diff --git a/host-configs/LLNL/lassen-base.cmake b/host-configs/LLNL/lassen-base.cmake index 3f40d24af50..72747c4f7d8 100644 --- a/host-configs/LLNL/lassen-base.cmake +++ b/host-configs/LLNL/lassen-base.cmake @@ -27,20 +27,19 @@ set(ENABLE_OPENMP ON CACHE BOOL "" FORCE) # LvArray sets this to the CMAKE_CXX_COMPILER. set(CMAKE_CUDA_HOST_COMPILER ${MPI_CXX_COMPILER} CACHE STRING "") +set(ENABLE_CUDA_NVTOOLSEXT OFF CACHE BOOL "") + # ESSL -set(ENABLE_ESSL ON CACHE BOOL "") -set(ESSL_INCLUDE_DIRS /usr/tcetmp/packages/essl/essl-6.2.1/include CACHE STRING "") -set(ESSL_LIBRARIES /usr/tcetmp/packages/essl/essl-6.2.1/lib64/libesslsmpcuda.so - /usr/tce/packages/xl/xl-beta-2019.06.20/alllibs/libxlsmp.so - /usr/tce/packages/xl/xl-beta-2019.06.20/alllibs/libxlfmath.so - /usr/tce/packages/xl/xl-beta-2019.06.20/alllibs/libxlf90_r.so +set(ENABLE_ESSL ON CACHE BOOL "" FORCE ) +set(ESSL_INCLUDE_DIRS /usr/tcetmp/packages/essl/essl-6.3.0.2/include CACHE STRING "" FORCE ) +set(ESSL_LIBRARIES /usr/tcetmp/packages/essl/essl-6.3.0.2/lib64/libesslsmpcuda.so ${CUDA_TOOLKIT_ROOT_DIR}/lib64/libcublas.so + ${CUDA_TOOLKIT_ROOT_DIR}/lib64/libcublasLt.so ${CUDA_TOOLKIT_ROOT_DIR}/lib64/libcudart.so - /usr/tcetmp/packages/essl/essl-6.2.1/lib64/liblapackforessl.so - /usr/tcetmp/packages/essl/essl-6.2.1/lib64/liblapackforessl_.so - /usr/tce/packages/xl/xl-beta-2019.06.20/alllibs/libxl.a - CACHE PATH "") - + /usr/tcetmp/packages/essl/essl-6.3.0.2/lib64/liblapackforessl.so + /usr/tcetmp/packages/essl/essl-6.3.0.2/lib64/liblapackforessl_.so + CACHE PATH "" FORCE ) + # TPL set(ENABLE_PAPI OFF CACHE BOOL "") set(SILO_BUILD_TYPE powerpc64-unknown-linux-gnu CACHE STRING "") @@ -50,8 +49,6 @@ set(ENABLE_FESAPI OFF CACHE BOOL "" FORCE) set(ENABLE_PVTPackage ON CACHE BOOL "") set(ENABLE_PETSC OFF CACHE BOOL "" FORCE ) - - set( ENABLE_HYPRE_DEVICE "CUDA" CACHE STRING "" FORCE ) if( ${ENABLE_HYPRE_DEVICE} STREQUAL "HIP" OR ${ENABLE_HYPRE_DEVICE} STREQUAL "CUDA" ) set(ENABLE_TRILINOS OFF CACHE BOOL "" FORCE ) @@ -60,7 +57,6 @@ else() set(GEOSX_LA_INTERFACE "Trilinos" CACHE STRING "" FORCE ) endif() - # Documentation set(ENABLE_UNCRUSTIFY OFF CACHE BOOL "" FORCE) set(ENABLE_DOXYGEN OFF CACHE BOOL "" FORCE) diff --git a/host-configs/LLNL/lassen-clang-10-cuda-11.cmake b/host-configs/LLNL/lassen-clang-10-cuda-11.cmake new file mode 100644 index 00000000000..74c04803ed8 --- /dev/null +++ b/host-configs/LLNL/lassen-clang-10-cuda-11.cmake @@ -0,0 +1,16 @@ +include(${CMAKE_CURRENT_LIST_DIR}/../../src/coreComponents/LvArray/host-configs/LLNL/lassen-clang-10-cuda-11.cmake) + +# Fortran +set(CMAKE_Fortran_COMPILER /usr/tce/packages/xl/xl-2022.08.19-cuda-11.8.0/bin/xlf_r CACHE PATH "") +set(CMAKE_Fortran_FLAGS_RELEASE "-O3 -DNDEBUG -qarch=pwr9 -qtune=pwr9" CACHE STRING "") +set(FORTRAN_MANGLE_NO_UNDERSCORE ON CACHE BOOL "") +set(OpenMP_Fortran_FLAGS "-qsmp=omp" CACHE STRING "") +set(OpenMP_Fortran_LIB_NAMES "" CACHE STRING "") + +# MPI +set(MPI_HOME /usr/tce/packages/spectrum-mpi/spectrum-mpi-rolling-release-clang-10.0.1-gcc-8.3.1 CACHE PATH "") +set(MPI_Fortran_COMPILER /usr/tce/packages/spectrum-mpi/spectrum-mpi-rolling-release-xl-2022.08.19-cuda-11.8.0/bin/mpifort CACHE PATH "") + +include(${CMAKE_CURRENT_LIST_DIR}/lassen-base.cmake) + +set(ENABLE_CUDA_NVTOOLSEXT OFF CACHE BOOL "") \ No newline at end of file diff --git a/host-configs/LLNL/lassen-clang@upstream.cmake b/host-configs/LLNL/lassen-clang-13-cuda-11.cmake similarity index 53% rename from host-configs/LLNL/lassen-clang@upstream.cmake rename to host-configs/LLNL/lassen-clang-13-cuda-11.cmake index 7ad701783b7..428274e26d2 100644 --- a/host-configs/LLNL/lassen-clang@upstream.cmake +++ b/host-configs/LLNL/lassen-clang-13-cuda-11.cmake @@ -1,21 +1,17 @@ -include(${CMAKE_CURRENT_LIST_DIR}/../../src/coreComponents/LvArray/host-configs/LLNL/lassen-clang@upstream.cmake) +include(${CMAKE_CURRENT_LIST_DIR}/../../src/coreComponents/LvArray/host-configs/LLNL/lassen-clang-13-cuda-11.cmake) # Fortran -set(CMAKE_Fortran_COMPILER /usr/tce/packages/xl/xl-2020.11.12/bin/xlf_r CACHE PATH "") +set(CMAKE_Fortran_COMPILER /usr/tce/packages/xl/xl-2022.08.19-cuda-11.8.0/bin/xlf_r CACHE PATH "") set(CMAKE_Fortran_FLAGS_RELEASE "-O3 -DNDEBUG -qarch=pwr9 -qtune=pwr9" CACHE STRING "") set(FORTRAN_MANGLE_NO_UNDERSCORE ON CACHE BOOL "") set(OpenMP_Fortran_FLAGS "-qsmp=omp" CACHE STRING "") set(OpenMP_Fortran_LIB_NAMES "" CACHE STRING "") # MPI -set(MPI_HOME /usr/tce/packages/spectrum-mpi/spectrum-mpi-rolling-release-clang-upstream-2019.03.26 CACHE PATH "") -set(MPI_Fortran_COMPILER /usr/tce/packages/spectrum-mpi/spectrum-mpi-rolling-release-xl-2020.11.12/bin/mpifort CACHE PATH "") +set(MPI_HOME /usr/tce/packages/spectrum-mpi/spectrum-mpi-rolling-release-clang-13.0.1-gcc-8.3.1 CACHE PATH "") +set(MPI_Fortran_COMPILER /usr/tce/packages/spectrum-mpi/spectrum-mpi-rolling-release-xl-2022.08.19-cuda-11.8.0/bin/mpifort CACHE PATH "") include(${CMAKE_CURRENT_LIST_DIR}/lassen-base.cmake) -# HYPRE options -set( ENABLE_HYPRE_DEVICE "CUDA" CACHE STRING "" ) -set( ENABLE_HYPRE_MIXINT TRUE CACHE STRING "" ) +set(ENABLE_CUDA_NVTOOLSEXT ON CACHE BOOL "") -# ATS -set(ATS_ARGUMENTS "--ats jsrun_omp --ats jsrun_bind=packed" CACHE PATH "") diff --git a/host-configs/LLNL/lassen-clang10-cuda11.cmake b/host-configs/LLNL/lassen-clang10-cuda11.cmake deleted file mode 100644 index fb49855c72e..00000000000 --- a/host-configs/LLNL/lassen-clang10-cuda11.cmake +++ /dev/null @@ -1,30 +0,0 @@ -include(${CMAKE_CURRENT_LIST_DIR}/../../src/coreComponents/LvArray/host-configs/LLNL/lassen-clang10-cuda11.cmake) - -# Fortran -set(CMAKE_Fortran_COMPILER /usr/tce/packages/xl/xl-2022.08.19-cuda-11.2.0/bin/xlf_r CACHE PATH "") -set(CMAKE_Fortran_FLAGS_RELEASE "-O3 -DNDEBUG -qarch=pwr9 -qtune=pwr9" CACHE STRING "") -set(FORTRAN_MANGLE_NO_UNDERSCORE ON CACHE BOOL "") -set(OpenMP_Fortran_FLAGS "-qsmp=omp" CACHE STRING "") -set(OpenMP_Fortran_LIB_NAMES "" CACHE STRING "") - -# MPI -set(MPI_HOME /usr/tce/packages/spectrum-mpi/spectrum-mpi-rolling-release-clang-10.0.1-gcc-8.3.1 CACHE PATH "") -set(MPI_Fortran_COMPILER /usr/tce/packages/spectrum-mpi/spectrum-mpi-rolling-release-xl-2022.08.19-cuda-11.2.0/bin/mpifort CACHE PATH "") - -include(${CMAKE_CURRENT_LIST_DIR}/lassen-base.cmake) - -set(ENABLE_CUDA_NVTOOLSEXT OFF CACHE BOOL "") - -set(ENABLE_ESSL ON CACHE BOOL "" FORCE ) -set(ESSL_INCLUDE_DIRS /usr/tcetmp/packages/essl/essl-6.3.0.2/include CACHE STRING "" FORCE ) -set(ESSL_LIBRARIES /usr/tcetmp/packages/essl/essl-6.3.0.2/lib64/libesslsmpcuda.so - /usr/tce/packages/xl/xl-beta-2019.06.20/alllibs/libxlsmp.so - /usr/tce/packages/xl/xl-beta-2019.06.20/alllibs/libxlfmath.so - /usr/tce/packages/xl/xl-beta-2019.06.20/alllibs/libxlf90_r.so - ${CUDA_TOOLKIT_ROOT_DIR}/lib64/libcublas.so - ${CUDA_TOOLKIT_ROOT_DIR}/lib64/libcublasLt.so - ${CUDA_TOOLKIT_ROOT_DIR}/lib64/libcudart.so - /usr/tcetmp/packages/essl/essl-6.3.0.2/lib64/liblapackforessl.so - /usr/tcetmp/packages/essl/essl-6.3.0.2/lib64/liblapackforessl_.so - /usr/tce/packages/xl/xl-beta-2019.06.20/alllibs/libxl.a - CACHE PATH "" FORCE ) \ No newline at end of file diff --git a/host-configs/LLNL/lassen-clang13-cuda11.cmake b/host-configs/LLNL/lassen-clang13-cuda11.cmake deleted file mode 100644 index f314c3c868c..00000000000 --- a/host-configs/LLNL/lassen-clang13-cuda11.cmake +++ /dev/null @@ -1,30 +0,0 @@ -include(${CMAKE_CURRENT_LIST_DIR}/../../src/coreComponents/LvArray/host-configs/LLNL/lassen-clang13-cuda11.cmake) - -# Fortran -set(CMAKE_Fortran_COMPILER /usr/tce/packages/xl/xl-2022.08.19-cuda-11.6.1/bin/xlf_r CACHE PATH "") -set(CMAKE_Fortran_FLAGS_RELEASE "-O3 -DNDEBUG -qarch=pwr9 -qtune=pwr9" CACHE STRING "") -set(FORTRAN_MANGLE_NO_UNDERSCORE ON CACHE BOOL "") -set(OpenMP_Fortran_FLAGS "-qsmp=omp" CACHE STRING "") -set(OpenMP_Fortran_LIB_NAMES "" CACHE STRING "") - -# MPI -set(MPI_HOME /usr/tce/packages/spectrum-mpi/spectrum-mpi-rolling-release-clang-13.0.1-gcc-8.3.1 CACHE PATH "") -set(MPI_Fortran_COMPILER /usr/tce/packages/spectrum-mpi/spectrum-mpi-rolling-release-xl-2022.08.19-cuda-11.6.1/bin/mpifort CACHE PATH "") - -include(${CMAKE_CURRENT_LIST_DIR}/lassen-base.cmake) - -set(ENABLE_CUDA_NVTOOLSEXT ON CACHE BOOL "") - -set(ENABLE_ESSL ON CACHE BOOL "" FORCE ) -set(ESSL_INCLUDE_DIRS /usr/tcetmp/packages/essl/essl-6.3.0.2/include CACHE STRING "" FORCE ) -set(ESSL_LIBRARIES /usr/tcetmp/packages/essl/essl-6.3.0.2/lib64/libesslsmpcuda.so - /usr/tce/packages/xl/xl-beta-2019.06.20/alllibs/libxlsmp.so - /usr/tce/packages/xl/xl-beta-2019.06.20/alllibs/libxlfmath.so - /usr/tce/packages/xl/xl-beta-2019.06.20/alllibs/libxlf90_r.so - ${CUDA_TOOLKIT_ROOT_DIR}/lib64/libcublas.so - ${CUDA_TOOLKIT_ROOT_DIR}/lib64/libcublasLt.so - ${CUDA_TOOLKIT_ROOT_DIR}/lib64/libcudart.so - /usr/tcetmp/packages/essl/essl-6.3.0.2/lib64/liblapackforessl.so - /usr/tcetmp/packages/essl/essl-6.3.0.2/lib64/liblapackforessl_.so - /usr/tce/packages/xl/xl-beta-2019.06.20/alllibs/libxl.a - CACHE PATH "" FORCE ) \ No newline at end of file diff --git a/host-configs/LLNL/lassen-clang@upstream-NoMPI.cmake b/host-configs/LLNL/lassen-clang@upstream-NoMPI.cmake deleted file mode 100644 index 90c016f9e4d..00000000000 --- a/host-configs/LLNL/lassen-clang@upstream-NoMPI.cmake +++ /dev/null @@ -1,21 +0,0 @@ -include(${CMAKE_CURRENT_LIST_DIR}/../../host-configs/LLNL/lassen-clang@upstream.cmake) - -set(CONFIG_NAME "lassen-clang@upstream-NoMPI" CACHE PATH "" FORCE) -set(GEOSX_TPL_ROOT_DIR /usr/gapps/GEOSX/thirdPartyLibs CACHE PATH "" FORCE) -set(GEOSX_TPL_DIR ${GEOSX_TPL_ROOT_DIR}/2020-09-18/install-${CONFIG_NAME}-release CACHE PATH "" FORCE) - -set(ENABLE_MPI OFF CACHE BOOL "" FORCE) -unset(MPI_ROOT CACHE ) -unset(MPI_C_COMPILER CACHE ) -unset(MPI_CXX_COMPILER CACHE ) -unset(MPI_Fortran_COMPILER CACHE ) -unset(MPIEXEC CACHE ) -unset(MPIEXEC_NUMPROC_FLAG CACHE ) -set(ENABLE_WRAP_ALL_TESTS_WITH_MPIEXEC OFF CACHE BOOL "") - -set(ENABLE_PVTPackage ON CACHE BOOL "" FORCE) -set(ENABLE_HYPRE OFF CACHE BOOL "" FORCE) -set(ENABLE_PETSC OFF CACHE BOOL "" FORCE) -set(ENABLE_PARMETIS OFF CACHE BOOL "" FORCE) -set(ENABLE_SUPERLU_DIST OFF CACHE BOOL "" FORCE) -set(CMAKE_CUDA_HOST_COMPILER ${CMAKE_CXX_COMPILER} CACHE STRING "" FORCE) diff --git a/host-configs/LLNL/lassen-gcc@8.3.1.cmake b/host-configs/LLNL/lassen-gcc-8-cuda-11.cmake similarity index 94% rename from host-configs/LLNL/lassen-gcc@8.3.1.cmake rename to host-configs/LLNL/lassen-gcc-8-cuda-11.cmake index 4d38f8094d9..fa12811e52e 100644 --- a/host-configs/LLNL/lassen-gcc@8.3.1.cmake +++ b/host-configs/LLNL/lassen-gcc-8-cuda-11.cmake @@ -1,4 +1,4 @@ -include(${CMAKE_CURRENT_LIST_DIR}/../../src/coreComponents/LvArray/host-configs/LLNL/lassen-gcc@8.3.1.cmake) +include(${CMAKE_CURRENT_LIST_DIR}/../../src/coreComponents/LvArray/host-configs/LLNL/lassen-gcc-8-cuda-11.cmake) # C++ # The "-march=native -mtune=native" which LvArray adds breaks the PVT package. diff --git a/host-configs/LLNL/quartz-base.cmake b/host-configs/LLNL/quartz-base.cmake index 0e1dce9560e..f5ff404044c 100644 --- a/host-configs/LLNL/quartz-base.cmake +++ b/host-configs/LLNL/quartz-base.cmake @@ -43,6 +43,10 @@ set(SPHINX_EXECUTABLE /usr/gapps/GEOSX/thirdPartyLibs/python/quartz-gcc-python/p set(ENABLE_FESAPI OFF CACHE BOOL "" FORCE) +# caliper +set(ENABLE_CALIPER ON CACHE BOOL "" FORCE) +set(ENABLE_CALIPER_HYPRE ON CACHE BOOL "" FORCE) + # MKL set(ENABLE_MKL ON CACHE BOOL "") set(MKL_ROOT /usr/tce/packages/mkl/mkl-2022.1.0) diff --git a/host-configs/LLNL/quartz-clang@14.cmake b/host-configs/LLNL/quartz-clang-14.cmake similarity index 90% rename from host-configs/LLNL/quartz-clang@14.cmake rename to host-configs/LLNL/quartz-clang-14.cmake index 73567edb2a1..797e6acb2f3 100644 --- a/host-configs/LLNL/quartz-clang@14.cmake +++ b/host-configs/LLNL/quartz-clang-14.cmake @@ -1,4 +1,4 @@ -include(${CMAKE_CURRENT_LIST_DIR}/../../src/coreComponents/LvArray/host-configs/LLNL/quartz-clang@14.cmake) +include(${CMAKE_CURRENT_LIST_DIR}/../../src/coreComponents/LvArray/host-configs/LLNL/quartz-clang-14.cmake) # Fortran set(CMAKE_Fortran_COMPILER /usr/tce/packages/gcc/gcc-12.1.1-magic/bin/gfortran CACHE PATH "") diff --git a/host-configs/LLNL/quartz-gcc@12.cmake b/host-configs/LLNL/quartz-gcc-12.cmake similarity index 93% rename from host-configs/LLNL/quartz-gcc@12.cmake rename to host-configs/LLNL/quartz-gcc-12.cmake index b3ca828a729..a4e92537ad6 100644 --- a/host-configs/LLNL/quartz-gcc@12.cmake +++ b/host-configs/LLNL/quartz-gcc-12.cmake @@ -1,4 +1,4 @@ -include(${CMAKE_CURRENT_LIST_DIR}/../../src/coreComponents/LvArray/host-configs/LLNL/quartz-gcc@12.cmake) +include(${CMAKE_CURRENT_LIST_DIR}/../../src/coreComponents/LvArray/host-configs/LLNL/quartz-gcc-12.cmake) # C++ # The "-march=native -mtune=native" which LvArray adds breaks the PVT package. diff --git a/host-configs/LLNL/quartz-icc@19.0.4.cmake b/host-configs/LLNL/quartz-icc-19.cmake similarity index 95% rename from host-configs/LLNL/quartz-icc@19.0.4.cmake rename to host-configs/LLNL/quartz-icc-19.cmake index c88c81a9949..a18d3f3da03 100644 --- a/host-configs/LLNL/quartz-icc@19.0.4.cmake +++ b/host-configs/LLNL/quartz-icc-19.cmake @@ -1,4 +1,4 @@ -include(${CMAKE_CURRENT_LIST_DIR}/../../src/coreComponents/LvArray/host-configs/LLNL/quartz-icc@19.0.4.cmake) +include(${CMAKE_CURRENT_LIST_DIR}/../../src/coreComponents/LvArray/host-configs/LLNL/quartz-icc-19.cmake) # Fortran set(CMAKE_Fortran_COMPILER ${COMPILER_DIR}/bin/intel64/ifort CACHE PATH "") diff --git a/host-configs/LLNL/tioga-cce@15.0.0.cmake b/host-configs/LLNL/tioga-cce-15.cmake similarity index 89% rename from host-configs/LLNL/tioga-cce@15.0.0.cmake rename to host-configs/LLNL/tioga-cce-15.cmake index 138c2555c47..c832a481ebf 100644 --- a/host-configs/LLNL/tioga-cce@15.0.0.cmake +++ b/host-configs/LLNL/tioga-cce-15.cmake @@ -1,10 +1,10 @@ -include(${CMAKE_CURRENT_LIST_DIR}/../../src/coreComponents/LvArray/host-configs/LLNL/tioga-cce@15.0.0.cmake) +include(${CMAKE_CURRENT_LIST_DIR}/../../src/coreComponents/LvArray/host-configs/LLNL/tioga-cce-15.cmake) include(${CMAKE_CURRENT_LIST_DIR}/tioga-base.cmake) set( CONDUIT_DIR "${GEOSX_TPL_DIR}/conduit-0.8.7" CACHE PATH "" ) set( HDF5_DIR "${GEOSX_TPL_DIR}/hdf5-1.14.1-2" CACHE PATH "" ) -set( BLAS_DIR "/opt/rocm-5.4.0/" CACHE PATH "" ) +set( BLAS_DIR "/opt/rocm-5.4.3/" CACHE PATH "" ) set( PUGIXML_DIR "${GEOSX_TPL_DIR}/pugixml-1.13" CACHE PATH "" ) set( FMT_DIR "${GEOSX_TPL_DIR}/fmt-10.0.0" CACHE PATH "" ) diff --git a/host-configs/Stanford/sherlock-base.cmake b/host-configs/Stanford/sherlock-base.cmake index 8b4a95c2bfe..2e45c12491c 100644 --- a/host-configs/Stanford/sherlock-base.cmake +++ b/host-configs/Stanford/sherlock-base.cmake @@ -21,7 +21,6 @@ set(ENABLE_WRAP_ALL_TESTS_WITH_MPIEXEC ON CACHE BOOL "") if(ENABLE_CUDA) set(CMAKE_CUDA_HOST_COMPILER ${MPI_CXX_COMPILER} CACHE STRING "") set(CMAKE_CUDA_COMPILER ${CUDA_TOOLKIT_ROOT_DIR}/bin/nvcc CACHE STRING "") - set(CMAKE_CUDA_STANDARD 14 CACHE STRING "") set(CMAKE_CUDA_FLAGS "-restrict -arch ${CUDA_ARCH} --expt-extended-lambda --expt-relaxed-constexpr -Werror cross-execution-space-call,reorder,deprecated-declarations" CACHE STRING "") set(CMAKE_CUDA_FLAGS_RELEASE "-O3 -DNDEBUG -Xcompiler -DNDEBUG -Xcompiler -O3" CACHE STRING "") set(CMAKE_CUDA_FLAGS_RELWITHDEBINFO "-g -lineinfo ${CMAKE_CUDA_FLAGS_RELEASE}" CACHE STRING "") diff --git a/host-configs/Stanford/sherlock-gcc10-ompi4.1.2-openblas0.3.10-cuda11.5.0-sm80.cmake b/host-configs/Stanford/sherlock-gcc10-ompi4.1.2-openblas0.3.10-cuda11.5.0-sm80.cmake index 9f5c8c95c5a..c87eee9c96e 100644 --- a/host-configs/Stanford/sherlock-gcc10-ompi4.1.2-openblas0.3.10-cuda11.5.0-sm80.cmake +++ b/host-configs/Stanford/sherlock-gcc10-ompi4.1.2-openblas0.3.10-cuda11.5.0-sm80.cmake @@ -15,7 +15,6 @@ set(CONFIG_NAME "sherlock-gcc10-ompi4.1.2-openblas0.3.10-cuda${CUDA_VERSION}-${C set(CMAKE_CUDA_HOST_COMPILER ${MPI_CXX_COMPILER} CACHE STRING "") set(CMAKE_CUDA_COMPILER ${CUDA_TOOLKIT_ROOT_DIR}/bin/nvcc CACHE STRING "") -set(CMAKE_CUDA_STANDARD 14 CACHE STRING "") set(CMAKE_CUDA_FLAGS "-restrict -arch ${CUDA_ARCH} --expt-extended-lambda --expt-relaxed-constexpr -Werror cross-execution-space-call,reorder,deprecated-declarations " CACHE STRING "") set(CMAKE_CUDA_FLAGS_RELEASE "-O3 -DNDEBUG -Xcompiler -DNDEBUG -Xcompiler -O3" CACHE STRING "") set(CMAKE_CUDA_FLAGS_RELWITHDEBINFO "-g -lineinfo ${CMAKE_CUDA_FLAGS_RELEASE}" CACHE STRING "") diff --git a/host-configs/TOTAL/pangea3-gcc8.4.1-openmpi-4.1.2.cmake b/host-configs/TOTAL/pangea3-gcc8.4.1-openmpi-4.1.2.cmake index 7061e89c6de..5f3c90ce11f 100644 --- a/host-configs/TOTAL/pangea3-gcc8.4.1-openmpi-4.1.2.cmake +++ b/host-configs/TOTAL/pangea3-gcc8.4.1-openmpi-4.1.2.cmake @@ -23,7 +23,6 @@ set(CMAKE_CXX_COMPILER g++ CACHE PATH "") set(CMAKE_CXX_FLAGS_RELEASE "-O3 -DNDEBUG -mcpu=power9 -mtune=power9" CACHE STRING "") set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-g ${CMAKE_CXX_FLAGS_RELEASE}" CACHE STRING "") set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g" CACHE STRING "") -set(CMAKE_CXX_STANDARD 14 CACHE STRING "") # Fortran options set(CMAKE_Fortran_COMPILER gfortran CACHE PATH "") @@ -57,9 +56,7 @@ if (DEFINED ENV{CUDA_ROOT}) set(CMAKE_CUDA_COMPILER ${CUDA_TOOLKIT_ROOT_DIR}/bin/nvcc CACHE STRING "") set(CUDA_ARCH sm_70 CACHE STRING "") set(CMAKE_CUDA_ARCHITECTURES 70 CACHE STRING "") - set(CMAKE_CUDA_STANDARD 14 CACHE STRING "") - ### The inclusion of -std=c++14 is a workaround for a cuda10/gcc8 bug ### - set(CMAKE_CUDA_FLAGS "-restrict -arch ${CUDA_ARCH} --expt-extended-lambda -Werror cross-execution-space-call,reorder,deprecated-declarations -Xcompiler -std=c++14" CACHE STRING "") + set(CMAKE_CUDA_FLAGS "-restrict -arch ${CUDA_ARCH} --expt-extended-lambda -Werror cross-execution-space-call,reorder,deprecated-declarations" CACHE STRING "") set(CMAKE_CUDA_FLAGS_RELEASE "-O3 -DNDEBUG -Xcompiler -DNDEBUG -Xcompiler -O3 -Xcompiler -mcpu=powerpc64le -Xcompiler -mtune=powerpc64le" CACHE STRING "") set(CMAKE_CUDA_FLAGS_RELWITHDEBINFO "-g -lineinfo ${CMAKE_CUDA_FLAGS_RELEASE}" CACHE STRING "") set(CMAKE_CUDA_FLAGS_DEBUG "-g -G -O0 -Xcompiler -O0" CACHE STRING "") diff --git a/host-configs/TOTAL/pecan-GPU.cmake b/host-configs/TOTAL/pecan-GPU.cmake index 0155ce192d6..1b6e419df99 100644 --- a/host-configs/TOTAL/pecan-GPU.cmake +++ b/host-configs/TOTAL/pecan-GPU.cmake @@ -3,14 +3,12 @@ include(${CMAKE_CURRENT_LIST_DIR}/pecan-CPU.cmake) # Now let's add what's dedicated to GPU. set(ENABLE_CUDA ON CACHE PATH "" FORCE) -set(CUDA_TOOLKIT_ROOT_DIR /hrtc/apps/cuda/10.2.89/x86_64 CACHE PATH "") +set(CUDA_TOOLKIT_ROOT_DIR /hrtc/apps/cuda/11.5.119/x86_64/centos7 CACHE PATH "") set(CMAKE_CUDA_HOST_COMPILER ${CMAKE_CXX_COMPILER} CACHE STRING "") set(CMAKE_CUDA_COMPILER ${CUDA_TOOLKIT_ROOT_DIR}/bin/nvcc CACHE STRING "") set(CUDA_ARCH sm_75 CACHE STRING "") -set(CMAKE_CUDA_STANDARD 14 CACHE STRING "") -### The inclusion of -std=c++14 is a workaround for a cuda10/gcc8 bug ### -set(CMAKE_CUDA_FLAGS "-restrict -arch ${CUDA_ARCH} --expt-relaxed-constexpr --expt-extended-lambda -Werror cross-execution-space-call,reorder,deprecated-declarations -Xcompiler -std=c++14" CACHE STRING "") +set(CMAKE_CUDA_FLAGS "-restrict -arch ${CUDA_ARCH} --expt-relaxed-constexpr --expt-extended-lambda -Werror cross-execution-space-call,reorder,deprecated-declarations" CACHE STRING "") set(CMAKE_CUDA_FLAGS_RELEASE "-O3 -DNDEBUG -Xcompiler -DNDEBUG -Xcompiler -O3" CACHE STRING "") set(CMAKE_CUDA_FLAGS_RELWITHDEBINFO "-g -lineinfo ${CMAKE_CUDA_FLAGS_RELEASE}" CACHE STRING "") set(CMAKE_CUDA_FLAGS_DEBUG "-g -G -O0 -Xcompiler -O0" CACHE STRING "") diff --git a/host-configs/macOS_Matteo.cmake b/host-configs/macOS_Matteo.cmake index 7e4b2d64c61..12e92486cca 100644 --- a/host-configs/macOS_Matteo.cmake +++ b/host-configs/macOS_Matteo.cmake @@ -1,5 +1,5 @@ site_name(HOST_NAME) -set(CONFIG_NAME "${HOST_NAME}-darwin-x86_64-clang@apple-mp" CACHE PATH "") +set(CONFIG_NAME "${HOST_NAME}-clang@apple-mp" CACHE PATH "") message("CONFIG_NAME = ${CONFIG_NAME}") set(CMAKE_C_COMPILER "/usr/bin/clang" CACHE PATH "") @@ -24,6 +24,9 @@ set( BLAS_LIBRARIES /Users/cusini1/local/opt/openblas/lib/libblas.dylib CACHE PA set( LAPACK_LIBRARIES /Users/cusini1/local/opt/openblas/lib/liblapack.dylib CACHE PATH "" FORCE ) set(ENABLE_DOXYGEN OFF CACHE BOOL "" FORCE) +set(ENABLE_MATHPRESSO OFF CACHE BOOL "" FORCE ) +set(GEOSX_BUILD_OBJ_LIBS ON CACHE BOOL "" FORCE) + #set( DOXYGEN_EXECUTABLE /usr/local/bin/doxygen CACHE PATH "" FORCE ) #set( SPHINX_EXECUTABLE /usr/local/bin/sphinx-build CACHE PATH "" FORCE ) diff --git a/inputFiles/compositionalMultiphaseFlow/c1-ppu/grav_seg_c1ppu_base.xml b/inputFiles/compositionalMultiphaseFlow/c1-ppu/grav_seg_c1ppu_base.xml new file mode 100644 index 00000000000..d59b2b2c590 --- /dev/null +++ b/inputFiles/compositionalMultiphaseFlow/c1-ppu/grav_seg_c1ppu_base.xml @@ -0,0 +1,171 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inputFiles/compositionalMultiphaseFlow/c1-ppu/grav_seg_c1ppu_drain.xml b/inputFiles/compositionalMultiphaseFlow/c1-ppu/grav_seg_c1ppu_drain.xml new file mode 100644 index 00000000000..c30aae2d023 --- /dev/null +++ b/inputFiles/compositionalMultiphaseFlow/c1-ppu/grav_seg_c1ppu_drain.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inputFiles/compositionalMultiphaseFlow/c1-ppu/grav_seg_c1ppu_hyst.xml b/inputFiles/compositionalMultiphaseFlow/c1-ppu/grav_seg_c1ppu_hyst.xml new file mode 100644 index 00000000000..a82c573ad0f --- /dev/null +++ b/inputFiles/compositionalMultiphaseFlow/c1-ppu/grav_seg_c1ppu_hyst.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inputFiles/compositionalMultiphaseFlow/c1-ppu/tables/drainagePhaseVolFraction_gas.txt b/inputFiles/compositionalMultiphaseFlow/c1-ppu/tables/drainagePhaseVolFraction_gas.txt new file mode 100644 index 00000000000..31a86ba26cc --- /dev/null +++ b/inputFiles/compositionalMultiphaseFlow/c1-ppu/tables/drainagePhaseVolFraction_gas.txt @@ -0,0 +1,19 @@ +0.000 +0.010 +0.030 +0.050 +0.100 +0.150 +0.200 +0.250 +0.300 +0.350 +0.400 +0.450 +0.500 +0.550 +0.600 +0.650 +0.700 +0.750 +0.780 diff --git a/inputFiles/compositionalMultiphaseFlow/c1-ppu/tables/drainagePhaseVolFraction_water.txt b/inputFiles/compositionalMultiphaseFlow/c1-ppu/tables/drainagePhaseVolFraction_water.txt new file mode 100644 index 00000000000..470ef848551 --- /dev/null +++ b/inputFiles/compositionalMultiphaseFlow/c1-ppu/tables/drainagePhaseVolFraction_water.txt @@ -0,0 +1,16 @@ +0.22000 +0.25000 +0.30000 +0.35000 +0.40000 +0.45000 +0.50000 +0.55000 +0.60000 +0.65000 +0.66000 +0.68000 +0.72000 +0.82000 +0.91000 +1.00000 diff --git a/inputFiles/compositionalMultiphaseFlow/c1-ppu/tables/drainageRelPerm_gas.txt b/inputFiles/compositionalMultiphaseFlow/c1-ppu/tables/drainageRelPerm_gas.txt new file mode 100644 index 00000000000..f8686ac5297 --- /dev/null +++ b/inputFiles/compositionalMultiphaseFlow/c1-ppu/tables/drainageRelPerm_gas.txt @@ -0,0 +1,19 @@ +0.00000 +0.00200 +0.00700 +0.01000 +0.02000 +0.04000 +0.07500 +0.12700 +0.18000 +0.24000 +0.31000 +0.37300 +0.46000 +0.55000 +0.64000 +0.73000 +0.82500 +0.92000 +1.00000 diff --git a/inputFiles/compositionalMultiphaseFlow/c1-ppu/tables/drainageRelPerm_water.txt b/inputFiles/compositionalMultiphaseFlow/c1-ppu/tables/drainageRelPerm_water.txt new file mode 100644 index 00000000000..e7cc0d5c904 --- /dev/null +++ b/inputFiles/compositionalMultiphaseFlow/c1-ppu/tables/drainageRelPerm_water.txt @@ -0,0 +1,16 @@ +0.00000 +0.00100 +0.00300 +0.01000 +0.01800 +0.03500 +0.04000 +0.05700 +0.08800 +0.14500 +0.16000 +0.19000 +0.26300 +0.45500 +0.69200 +1. diff --git a/inputFiles/compositionalMultiphaseFlow/c1-ppu/tables/imbibitionPhaseVolFraction_gas.txt b/inputFiles/compositionalMultiphaseFlow/c1-ppu/tables/imbibitionPhaseVolFraction_gas.txt new file mode 100644 index 00000000000..9bc2c7bb41e --- /dev/null +++ b/inputFiles/compositionalMultiphaseFlow/c1-ppu/tables/imbibitionPhaseVolFraction_gas.txt @@ -0,0 +1,11 @@ +0.300 +0.350 +0.400 +0.450 +0.500 +0.550 +0.600 +0.650 +0.700 +0.750 +0.780 diff --git a/inputFiles/compositionalMultiphaseFlow/c1-ppu/tables/imbibitionPhaseVolFraction_water.txt b/inputFiles/compositionalMultiphaseFlow/c1-ppu/tables/imbibitionPhaseVolFraction_water.txt new file mode 100644 index 00000000000..6c8c2ec7dab --- /dev/null +++ b/inputFiles/compositionalMultiphaseFlow/c1-ppu/tables/imbibitionPhaseVolFraction_water.txt @@ -0,0 +1,12 @@ +0.22000 +0.25000 +0.30000 +0.35000 +0.40000 +0.45000 +0.50000 +0.55000 +0.60000 +0.65000 +0.66000 +0.70000 diff --git a/inputFiles/compositionalMultiphaseFlow/c1-ppu/tables/imbibitionRelPerm_gas.txt b/inputFiles/compositionalMultiphaseFlow/c1-ppu/tables/imbibitionRelPerm_gas.txt new file mode 100644 index 00000000000..3d969ee4d91 --- /dev/null +++ b/inputFiles/compositionalMultiphaseFlow/c1-ppu/tables/imbibitionRelPerm_gas.txt @@ -0,0 +1,11 @@ +0.0000 +0.03361965 +0.09509072 +0.17469281 +0.26895718 +0.37587908 +0.49410588 +0.62264458 +0.76072577 +0.90773047 +1. diff --git a/inputFiles/compositionalMultiphaseFlow/c1-ppu/tables/imbibitionRelPerm_water.txt b/inputFiles/compositionalMultiphaseFlow/c1-ppu/tables/imbibitionRelPerm_water.txt new file mode 100644 index 00000000000..d09ddcb2d22 --- /dev/null +++ b/inputFiles/compositionalMultiphaseFlow/c1-ppu/tables/imbibitionRelPerm_water.txt @@ -0,0 +1,12 @@ +0 +0.0156 +0.0680 +0.1409 +0.2296 +0.3317 +0.4455 +0.5700 +0.7044 +0.8479 +0.8776 +0.9382 diff --git a/inputFiles/compositionalMultiphaseFlow/c1-ppu/tables/pvdg.txt b/inputFiles/compositionalMultiphaseFlow/c1-ppu/tables/pvdg.txt new file mode 100644 index 00000000000..c6a5543817b --- /dev/null +++ b/inputFiles/compositionalMultiphaseFlow/c1-ppu/tables/pvdg.txt @@ -0,0 +1,3 @@ +# P[Pa] Bo[m3/sm3] Visc(Pa.s) +0 1.0 0.000023 +10000000 1.0 0.000023 diff --git a/inputFiles/compositionalMultiphaseFlow/c1-ppu/tables/pvtw.txt b/inputFiles/compositionalMultiphaseFlow/c1-ppu/tables/pvtw.txt new file mode 100644 index 00000000000..45a9437f743 --- /dev/null +++ b/inputFiles/compositionalMultiphaseFlow/c1-ppu/tables/pvtw.txt @@ -0,0 +1,2 @@ +#Pref[Pa] Bw[m3/sm3] Cp[1/Pa] Visc[Pa.s] +10000000 1.0 0.0000000000001 0.00055 diff --git a/inputFiles/compositionalMultiphaseFlow/deadoil_3ph_corey_1d_fractured.xml b/inputFiles/compositionalMultiphaseFlow/deadoil_3ph_corey_1d_fractured.xml index acff991d96a..6f1addf105c 100644 --- a/inputFiles/compositionalMultiphaseFlow/deadoil_3ph_corey_1d_fractured.xml +++ b/inputFiles/compositionalMultiphaseFlow/deadoil_3ph_corey_1d_fractured.xml @@ -25,6 +25,7 @@ logLevel="1" discretization="fluidTPFA" targetRegions="{ Domain, Fracture }" + targetObjects="{ FracturePlane }" fractureRegion="Fracture"/> @@ -52,7 +53,7 @@ xMin="{ 8.99, -0.01, -0.01 }" xMax="{ 10.01, 1.01, 1.01 }"/> - - - - - - - - + fieldNamesInGEOSX="{ rockPerm_permeability, rockPorosity_referencePorosity }" > + + + + + + + + diff --git a/inputFiles/compositionalMultiphaseWell/benchmarks/Class09Pb3/class09_pb3_smoke_3d.xml b/inputFiles/compositionalMultiphaseWell/benchmarks/Class09Pb3/class09_pb3_smoke_3d.xml index 431e1b415c0..8243537388a 100644 --- a/inputFiles/compositionalMultiphaseWell/benchmarks/Class09Pb3/class09_pb3_smoke_3d.xml +++ b/inputFiles/compositionalMultiphaseWell/benchmarks/Class09Pb3/class09_pb3_smoke_3d.xml @@ -16,34 +16,34 @@ nx="{ 5 }" ny="{ 5 }" nz="{ 5 }" - cellBlockNames="{ 1_hexahedra }"/> - - - - - - - + cellBlockNames="{ 1_hexahedra }"> + + + + + + + + diff --git a/inputFiles/compositionalMultiphaseWell/benchmarks/Egg/deadOilEgg_benchmark.xml b/inputFiles/compositionalMultiphaseWell/benchmarks/Egg/deadOilEgg_benchmark.xml index 1c5015e20b2..cf9f4f55ce9 100644 --- a/inputFiles/compositionalMultiphaseWell/benchmarks/Egg/deadOilEgg_benchmark.xml +++ b/inputFiles/compositionalMultiphaseWell/benchmarks/Egg/deadOilEgg_benchmark.xml @@ -13,405 +13,394 @@ name="mesh" file="../../../../../GEOSXDATA/DataSets/Egg/egg.vtu" fieldsToImport="{ PERM }" - fieldNamesInGEOSX="{ rockPerm_permeability }"/> + fieldNamesInGEOSX="{ rockPerm_permeability }"> - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + + diff --git a/inputFiles/compositionalMultiphaseWell/benchmarks/Egg/deadOilEgg_smoke_3d.xml b/inputFiles/compositionalMultiphaseWell/benchmarks/Egg/deadOilEgg_smoke_3d.xml index e516cc689a1..e60a2242e56 100644 --- a/inputFiles/compositionalMultiphaseWell/benchmarks/Egg/deadOilEgg_smoke_3d.xml +++ b/inputFiles/compositionalMultiphaseWell/benchmarks/Egg/deadOilEgg_smoke_3d.xml @@ -19,405 +19,394 @@ nx="{ 10 }" ny="{ 10 }" nz="{ 7 }" - cellBlockNames="{ DEFAULT_HEX }"/> + cellBlockNames="{ DEFAULT_HEX }" > - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + + diff --git a/inputFiles/compositionalMultiphaseWell/black_oil_wells_saturated_3d.xml b/inputFiles/compositionalMultiphaseWell/black_oil_wells_saturated_3d.xml index 06ea62af468..4c04ae1fb24 100644 --- a/inputFiles/compositionalMultiphaseWell/black_oil_wells_saturated_3d.xml +++ b/inputFiles/compositionalMultiphaseWell/black_oil_wells_saturated_3d.xml @@ -66,36 +66,35 @@ nx="{ 4 }" ny="{ 4 }" nz="{ 2 }" - cellBlockNames="{ cb }"/> + cellBlockNames="{ cb }"> - - - - - - + + + + + + + + cellBlockNames="{ cb }"> - - - - - - + + + + + + + - - - - - - - - + cellBlockNames="{ cb1 }"> + + + + + + + + + + cellBlockNames="{ cb1 }"> - - - - - + + + + + - - - - - + + + + + - - - - + + + + + - - - - - - - - - - - - - - - - - + cellBlockNames="{ cb1 }"> + + + + + + + + + + + + + + + + + + + cellBlockNames="{ cb1 }"> - - - - - + + + + + - - - - - + + + + + - - - - + + + + + + cellBlockNames="{ cellBlock }"> - - - + + + + diff --git a/inputFiles/compositionalMultiphaseWell/staircase_co2_wells_3d.xml b/inputFiles/compositionalMultiphaseWell/staircase_co2_wells_3d.xml index fb4e4df7dc6..531e9552c04 100644 --- a/inputFiles/compositionalMultiphaseWell/staircase_co2_wells_3d.xml +++ b/inputFiles/compositionalMultiphaseWell/staircase_co2_wells_3d.xml @@ -65,38 +65,37 @@ nx="{ 5, 5 }" ny="{ 5, 5 }" nz="{ 3, 3, 3, 3 }" - cellBlockNames="{ b00, b01, b02, b03, b04, b05, b06, b07, b08, b09, b10, b11, b12, b13, b14, b15 }"/> - - - - - - - - + cellBlockNames="{ b00, b01, b02, b03, b04, b05, b06, b07, b08, b09, b10, b11, b12, b13, b14, b15 }"> + + + + + + + + + - - - - - - - - + logLevel="2"> + + + + + + + + + diff --git a/inputFiles/efemFractureMechanics/EmbFrac_Compression_CoulombFriction_smoke.xml b/inputFiles/efemFractureMechanics/EmbFrac_Compression_CoulombFriction_smoke.xml index 3e4422a6a32..71df36121f5 100644 --- a/inputFiles/efemFractureMechanics/EmbFrac_Compression_CoulombFriction_smoke.xml +++ b/inputFiles/efemFractureMechanics/EmbFrac_Compression_CoulombFriction_smoke.xml @@ -20,7 +20,7 @@ - diff --git a/inputFiles/efemFractureMechanics/EmbFrac_Compression_Frictionless_smoke.xml b/inputFiles/efemFractureMechanics/EmbFrac_Compression_Frictionless_smoke.xml index 7d99983a077..928fb8466be 100644 --- a/inputFiles/efemFractureMechanics/EmbFrac_Compression_Frictionless_smoke.xml +++ b/inputFiles/efemFractureMechanics/EmbFrac_Compression_Frictionless_smoke.xml @@ -20,7 +20,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/inputFiles/efemFractureMechanics/SneddonRotated_benchmark.xml b/inputFiles/efemFractureMechanics/SneddonRotated_benchmark.xml index 577fcbaf986..345ffa1cf2d 100644 --- a/inputFiles/efemFractureMechanics/SneddonRotated_benchmark.xml +++ b/inputFiles/efemFractureMechanics/SneddonRotated_benchmark.xml @@ -4,27 +4,66 @@ + name="./Sneddon_embeddedFrac_base.xml"/> + + + + + + + + + + + + name="mesh1" + elementTypes="{ C3D8 }" + xCoords="{ 0, 16, 24, 40 }" + yCoords="{ 0, 16, 24, 40 }" + zCoords="{ 0, 1 }" + nx="{ 10, 301, 10 }" + ny="{ 10, 101, 10 }" + nz="{ 1 }" + cellBlockNames="{ cb1 }"/> - diff --git a/inputFiles/efemFractureMechanics/SneddonRotated_benchmark2.xml b/inputFiles/efemFractureMechanics/SneddonRotated_benchmark2.xml index 28c606fa01f..8771a47709c 100644 --- a/inputFiles/efemFractureMechanics/SneddonRotated_benchmark2.xml +++ b/inputFiles/efemFractureMechanics/SneddonRotated_benchmark2.xml @@ -4,9 +4,48 @@ + name="./Sneddon_embeddedFrac_base.xml"/> + + + + + + + + + + + - + name="./Sneddon_embeddedFrac_base.xml"/> + + + + + + + + + + + + name="mesh1" + elementTypes="{ C3D8 }" + xCoords="{ 0, 16, 19, 21, 24, 40 }" + yCoords="{ 0, 16, 24, 40 }" + zCoords="{ 0, 1 }" + nx="{ 1, 1, 5, 1, 1 }" + ny="{ 1, 5, 1 }" + nz="{ 1 }" + cellBlockNames="{ cb1 }"/> - + dimensions="{ 2, 4 }"/> - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/inputFiles/efemFractureMechanics/Sneddon_benchmark2.xml b/inputFiles/efemFractureMechanics/Sneddon_benchmark2.xml deleted file mode 100644 index ffa02ce4dbb..00000000000 --- a/inputFiles/efemFractureMechanics/Sneddon_benchmark2.xml +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/inputFiles/efemFractureMechanics/Sneddon_benchmark3.xml b/inputFiles/efemFractureMechanics/Sneddon_benchmark3.xml deleted file mode 100644 index 80522cce769..00000000000 --- a/inputFiles/efemFractureMechanics/Sneddon_benchmark3.xml +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/inputFiles/efemFractureMechanics/Sneddon_staticCondensation_base.xml b/inputFiles/efemFractureMechanics/Sneddon_embeddedFracShapes_base.xml similarity index 58% rename from inputFiles/efemFractureMechanics/Sneddon_staticCondensation_base.xml rename to inputFiles/efemFractureMechanics/Sneddon_embeddedFracShapes_base.xml index 55f9a125399..43b39be9324 100644 --- a/inputFiles/efemFractureMechanics/Sneddon_staticCondensation_base.xml +++ b/inputFiles/efemFractureMechanics/Sneddon_embeddedFracShapes_base.xml @@ -2,7 +2,8 @@ + gravityVector="{0.0, 0.0, 0.0}"> + + logLevel="1"> @@ -33,10 +33,31 @@ name="SurfaceGenerator" discretization="FE1" targetRegions="{ Domain, Fracture }" + targetObjects="{ FracturePlane }" fractureRegion="Fracture" logLevel="1" mpiCommOrder="1"/> + + + + + + + + @@ -46,6 +67,7 @@ + + + + + defaultBulkModulus="16.66666666666666e9" + defaultShearModulus="1.0e10"/> + + + + + + - - - - - - + setNames="{ yneg, ypos }"/> - + + scale="-2.0e6"/> + + + + + + + - - + + + + diff --git a/inputFiles/efemFractureMechanics/Sneddon_embeddedFracShapes_smoke.xml b/inputFiles/efemFractureMechanics/Sneddon_embeddedFracShapes_smoke.xml new file mode 100644 index 00000000000..3325e3be65a --- /dev/null +++ b/inputFiles/efemFractureMechanics/Sneddon_embeddedFracShapes_smoke.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inputFiles/efemFractureMechanics/Sneddon_embeddedFrac_base.xml b/inputFiles/efemFractureMechanics/Sneddon_embeddedFrac_base.xml index b4ef0470fff..354cc55e5b4 100644 --- a/inputFiles/efemFractureMechanics/Sneddon_embeddedFrac_base.xml +++ b/inputFiles/efemFractureMechanics/Sneddon_embeddedFrac_base.xml @@ -1,56 +1,6 @@ - - - - - - - - - - - - - - - - - - - + fieldName="displacementJump" + setNames="{all}"/> @@ -154,7 +105,7 @@ + format="binary"/> - + + - - + + + + + + + + + + + + + cellBlockNames="{ cb1 }"/> - + + + + - + maxTime="10"> - - + target="/Outputs/timeHistoryOutput"/> - - + diff --git a/inputFiles/efemFractureMechanics/Sneddon_embeddedFrac_smoke.xml b/inputFiles/efemFractureMechanics/Sneddon_embeddedFrac_smoke.xml index 0827f7bb493..1c8ac89603a 100644 --- a/inputFiles/efemFractureMechanics/Sneddon_embeddedFrac_smoke.xml +++ b/inputFiles/efemFractureMechanics/Sneddon_embeddedFrac_smoke.xml @@ -6,19 +6,68 @@ name="./Sneddon_embeddedFrac_base.xml"/> + + + + + + + + + + + + + - - + + + + + + @@ -50,7 +99,7 @@ name="timeHistoryOutput" timeFrequency="1.0" targetExactTimestep="0" - target="/Outputs/timeHistoryOutput"/> + target="/Outputs/timeHistoryOutput"/> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inputFiles/efemFractureMechanics/Sneddon_embeddedFrac_staticCondensation_smoke.xml b/inputFiles/efemFractureMechanics/Sneddon_embeddedFrac_staticCondensation_smoke.xml new file mode 100644 index 00000000000..e6262531046 --- /dev/null +++ b/inputFiles/efemFractureMechanics/Sneddon_embeddedFrac_staticCondensation_smoke.xml @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inputFiles/efemFractureMechanics/Sneddon_embeddedFrac_verification.xml b/inputFiles/efemFractureMechanics/Sneddon_embeddedFrac_verification.xml new file mode 100644 index 00000000000..a38b2ff4402 --- /dev/null +++ b/inputFiles/efemFractureMechanics/Sneddon_embeddedFrac_verification.xml @@ -0,0 +1,108 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inputFiles/efemFractureMechanics/Sneddon_staticCondensation_benchmark.xml b/inputFiles/efemFractureMechanics/Sneddon_staticCondensation_benchmark.xml deleted file mode 100644 index 6f38d989438..00000000000 --- a/inputFiles/efemFractureMechanics/Sneddon_staticCondensation_benchmark.xml +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/inputFiles/efemFractureMechanics/Sneddon_staticCondensation_benchmark2.xml b/inputFiles/efemFractureMechanics/Sneddon_staticCondensation_benchmark2.xml deleted file mode 100644 index 0368eb8fb44..00000000000 --- a/inputFiles/efemFractureMechanics/Sneddon_staticCondensation_benchmark2.xml +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/inputFiles/efemFractureMechanics/scripts/sneddonCurveChecks.py b/inputFiles/efemFractureMechanics/scripts/sneddonCurveChecks.py new file mode 100644 index 00000000000..02a9ec61d64 --- /dev/null +++ b/inputFiles/efemFractureMechanics/scripts/sneddonCurveChecks.py @@ -0,0 +1,157 @@ +import numpy as np +import os +import sys +import xml.etree.ElementTree as ElementTree +import matplotlib +import matplotlib.pyplot as plt + +class Sneddon: + + def __init__(self, mechanicalParameters, length, pressure): + K = mechanicalParameters["bulkModulus"] + G = mechanicalParameters["shearModulus"] + E = (9 * K * G) / (3 * K + G) + nu = E / (2 * G) - 1 + + self.scaling = (4 * (1 - nu**2)) * pressure / E + self.halfLength = length + + def computeAperture(self, x): + return self.scaling * (self.halfLength**2 - x**2)**0.5 + +def getMechanicalParametersFromXML(xmlFilePath): + tree = ElementTree.parse(xmlFilePath) + + param = tree.find('Constitutive/ElasticIsotropic') + + mechanicalParameters = dict.fromkeys(["bulkModulus", "shearModulus"]) + mechanicalParameters["bulkModulus"] = float(param.get("defaultBulkModulus")) + mechanicalParameters["shearModulus"] = float(param.get("defaultShearModulus")) + return mechanicalParameters + +def getFracturePressureFromXML(xmlFilePath): + tree = ElementTree.parse(xmlFilePath) + + param = tree.findall('FieldSpecifications/FieldSpecification') + pressure = 0.0 + found_traction = False + for elem in param: + if elem.get("fieldName") == "traction" and elem.get("component") == "0": + pressure = float(elem.get("scale")) * (-1) + found_traction = True + if found_traction: break + + return pressure + + +def getFractureLengthFromXML(xmlFilePath): + tree = ElementTree.parse(xmlFilePath) + + rectangle = tree.find('Geometry/Rectangle') + dimensions = rectangle.get("dimensions") + dimensions = [float(i) for i in dimensions[1:-1].split(",")] + length = dimensions[0] / 2 + origin = rectangle.get("origin") + origin = [float(i) for i in origin[1:-1].split(",")] + + return length, origin[1] + +def sneddon_curve_check_solution(**kwargs): + # Read HDF5 + localX = np.squeeze(kwargs['displacementJump elementCenter'])[:, 0] + + #-------- Extract info from XML + xmlFilePath = "./Sneddon_embeddedFrac_base.xml" + + mechanicalParameters = getMechanicalParametersFromXML(xmlFilePath) + appliedPressure = getFracturePressureFromXML(xmlFilePath) + + # Get length of the fracture + xmlFilePath = "./Sneddon_embeddedFrac_benchmark.xml" + length, originShift = getFractureLengthFromXML(xmlFilePath) + + localX = localX - originShift + + analyticalSolution = Sneddon( mechanicalParameters, length, appliedPressure ) + aperture_analytical = np.empty(len(localX)) + i = 0 + for xCell in localX: + aperture_analytical[i] = analyticalSolution.computeAperture( xCell ) + i += 1 + + dispJumpAnalytical = np.zeros(np.shape(kwargs['displacementJump'])) + dispJump = kwargs['displacementJump'] + + dispJumpAnalytical[:, :, 0] = aperture_analytical + + return dispJumpAnalytical + + +def debug(): + #-------- EmbeddeFrac File path + import hdf5_wrapper + hdf5File1Path = "Output/displacementJump_embeddedFrac.hdf5" + + # Read HDF5 + data = hdf5_wrapper.hdf5_wrapper(hdf5File1Path).get_copy() + jump = data['displacementJump'] + jump = np.array(jump) + aperture_EmbeddeFrac = jump[0, :, 0] + x = data['displacementJump elementCenter'] + loc_EmbeddeFrac = x[0, :, 0] + + #-------- Extract info from XML + xmlFilePath = "../inputFiles/efemFractureMechanics/Sneddon_embeddedFrac_base.xml" + + mechanicalParameters = getMechanicalParametersFromXML(xmlFilePath) + appliedPressure = getFracturePressureFromXML(xmlFilePath) + + # Get length of the fracture + xmlFilePath = "../inputFiles/efemFractureMechanics/Sneddon_embeddedFrac_benchmark.xml" + length, originShift = getFractureLengthFromXML(xmlFilePath) + + print(length) + + loc_EmbeddeFrac = loc_EmbeddeFrac - originShift + + # Initialize Sneddon's analytical solution + sneddonAnalyticalSolution = Sneddon(mechanicalParameters, length, appliedPressure) + + # Plot analytical (continuous line) and numerical (markers) aperture solution + x_analytical = np.linspace(-length, length, 501, endpoint=True) + aperture_analytical = np.empty(len(x_analytical)) + i = 0 + for xCell in x_analytical: + aperture_analytical[i] = sneddonAnalyticalSolution.computeAperture(xCell) + i += 1 + + fsize = 30 + msize = 15 + lw = 6 + fig, ax = plt.subplots(1, figsize=(16, 12)) + cmap = plt.get_cmap("tab10") + + N1 = 1 + ax.plot(x_analytical, aperture_analytical * 1.0e3, color='k', label='Analytical Solution', lw=lw) + ax.plot(loc_EmbeddeFrac[0::N1], + aperture_EmbeddeFrac[0::N1] * 1.0e3, + 'o', + color=cmap(0), + label='Embedded Fracture', + markersize=msize * 0.6, + alpha=0.8) + + ax.grid() + ax.set_xlabel('Fracture Length [m]', size=fsize, weight="bold") + ax.set_ylabel('Fracture Aperture [mm]', size=fsize, weight="bold") + ax.legend(bbox_to_anchor=(0.5, 0.2), loc='center', borderaxespad=0., fontsize=fsize) + ax.xaxis.set_tick_params(labelsize=fsize) + ax.yaxis.set_tick_params(labelsize=fsize) + + plt.savefig("sneddon.png") + +if __name__ == "__main__": + debug() + + + diff --git a/inputFiles/hydraulicFracturing/kgdToughnessDominated_base.xml b/inputFiles/hydraulicFracturing/kgdToughnessDominated_base.xml index c2b83a49838..f246ebf15ea 100644 --- a/inputFiles/hydraulicFracturing/kgdToughnessDominated_base.xml +++ b/inputFiles/hydraulicFracturing/kgdToughnessDominated_base.xml @@ -209,6 +209,19 @@ name="areaCollection" objectPath="ElementRegions/Fracture/FractureSubRegion" fieldName="elementArea"/> + + + + + @@ -222,7 +235,12 @@ + filename="kgdToughnessDominated_output" /> + + diff --git a/inputFiles/hydraulicFracturing/kgdToughnessDominated_poroelastic_base.xml b/inputFiles/hydraulicFracturing/kgdToughnessDominated_poroelastic_base.xml new file mode 100644 index 00000000000..54480f05dd0 --- /dev/null +++ b/inputFiles/hydraulicFracturing/kgdToughnessDominated_poroelastic_base.xml @@ -0,0 +1,225 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inputFiles/hydraulicFracturing/kgdToughnessDominated_poroelastic_benchmark.xml b/inputFiles/hydraulicFracturing/kgdToughnessDominated_poroelastic_benchmark.xml new file mode 100644 index 00000000000..0cd76208721 --- /dev/null +++ b/inputFiles/hydraulicFracturing/kgdToughnessDominated_poroelastic_benchmark.xml @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inputFiles/hydraulicFracturing/kgdToughnessDominated_poroelastic_smoke.xml b/inputFiles/hydraulicFracturing/kgdToughnessDominated_poroelastic_smoke.xml new file mode 100644 index 00000000000..e82093849ec --- /dev/null +++ b/inputFiles/hydraulicFracturing/kgdToughnessDominated_poroelastic_smoke.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inputFiles/hydraulicFracturing/kgdToughnessDominated_smoke.xml b/inputFiles/hydraulicFracturing/kgdToughnessDominated_smoke.xml index fa9755bdf3a..b21bb677c40 100644 --- a/inputFiles/hydraulicFracturing/kgdToughnessDominated_smoke.xml +++ b/inputFiles/hydraulicFracturing/kgdToughnessDominated_smoke.xml @@ -4,17 +4,19 @@ - + + @@ -22,44 +24,59 @@ + xMax="{ 0.01, 1.01, 1.01 }"/> + xMax="{ 0.01, 0.51, 1.01 }"/> + xMax="{ 0.01, 1e6, 1.01 }"/> - - + maxTime="40.0"> - - + + + + + + + + + - + diff --git a/inputFiles/hydraulicFracturing/kgdViscosityDominated_base.xml b/inputFiles/hydraulicFracturing/kgdViscosityDominated_base.xml index 939b5e8f29e..1dbc79458d5 100644 --- a/inputFiles/hydraulicFracturing/kgdViscosityDominated_base.xml +++ b/inputFiles/hydraulicFracturing/kgdViscosityDominated_base.xml @@ -10,6 +10,7 @@ flowSolverName="SinglePhaseFlow" surfaceGeneratorName="SurfaceGen" logLevel="1" + initialDt="0.1" targetRegions="{ Fracture }" contactRelationName="fractureContact" maxNumResolves="2"> @@ -208,7 +209,20 @@ + fieldName="elementArea"/> + + + + + @@ -222,7 +236,12 @@ + filename="kgdViscosityDominated_output" /> + + diff --git a/inputFiles/hydraulicFracturing/kgdViscosityDominated_poroelastic_base.xml b/inputFiles/hydraulicFracturing/kgdViscosityDominated_poroelastic_base.xml new file mode 100644 index 00000000000..85e24c5015f --- /dev/null +++ b/inputFiles/hydraulicFracturing/kgdViscosityDominated_poroelastic_base.xml @@ -0,0 +1,225 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inputFiles/hydraulicFracturing/kgdViscosityDominated_poroelastic_benchmark.xml b/inputFiles/hydraulicFracturing/kgdViscosityDominated_poroelastic_benchmark.xml new file mode 100644 index 00000000000..5ff9d27fe35 --- /dev/null +++ b/inputFiles/hydraulicFracturing/kgdViscosityDominated_poroelastic_benchmark.xml @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inputFiles/hydraulicFracturing/kgdViscosityDominated_poroelastic_smoke.xml b/inputFiles/hydraulicFracturing/kgdViscosityDominated_poroelastic_smoke.xml new file mode 100644 index 00000000000..73689559b83 --- /dev/null +++ b/inputFiles/hydraulicFracturing/kgdViscosityDominated_poroelastic_smoke.xml @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inputFiles/hydraulicFracturing/kgdViscosityDominated_smoke.xml b/inputFiles/hydraulicFracturing/kgdViscosityDominated_smoke.xml index 3446b2af92c..87c12c67c72 100644 --- a/inputFiles/hydraulicFracturing/kgdViscosityDominated_smoke.xml +++ b/inputFiles/hydraulicFracturing/kgdViscosityDominated_smoke.xml @@ -5,17 +5,18 @@ - + @@ -23,21 +24,21 @@ + xMax="{ 0.01, 1.01, 1.01 }"/> + xMax="{ 0.01, 0.51, 1.01 }"/> + xMax="{ 0.01, 1e6, 1.01 }"/> + maxTime="40.0"> @@ -46,22 +47,36 @@ solver time-step request --> + name="sourcePressureCollection" + beginTime="2.0" + targetExactTimestep="0" + target="/Tasks/sourcePressureCollection"/> + + + + + + diff --git a/inputFiles/hydraulicFracturing/pennyShapedToughnessDominated_base.xml b/inputFiles/hydraulicFracturing/pennyShapedToughnessDominated_base.xml index b17c7bb3a88..f8643427fcc 100644 --- a/inputFiles/hydraulicFracturing/pennyShapedToughnessDominated_base.xml +++ b/inputFiles/hydraulicFracturing/pennyShapedToughnessDominated_base.xml @@ -159,6 +159,19 @@ name="areaCollection" objectPath="ElementRegions/Fracture/FractureSubRegion" fieldName="elementArea"/> + + + + + @@ -174,6 +187,10 @@ name="timeHistoryOutput" sources="{/Tasks/pressureCollection, /Tasks/apertureCollection, /Tasks/hydraulicApertureCollection, /Tasks/areaCollection}" filename="pennyShapedToughnessDominated_output" /> + diff --git a/inputFiles/hydraulicFracturing/pennyShapedToughnessDominated_poroelastic_base.xml b/inputFiles/hydraulicFracturing/pennyShapedToughnessDominated_poroelastic_base.xml new file mode 100644 index 00000000000..ae43b66eaa5 --- /dev/null +++ b/inputFiles/hydraulicFracturing/pennyShapedToughnessDominated_poroelastic_base.xml @@ -0,0 +1,197 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inputFiles/hydraulicFracturing/pennyShapedToughnessDominated_poroelastic_benchmark.xml b/inputFiles/hydraulicFracturing/pennyShapedToughnessDominated_poroelastic_benchmark.xml new file mode 100644 index 00000000000..166fa20cf2e --- /dev/null +++ b/inputFiles/hydraulicFracturing/pennyShapedToughnessDominated_poroelastic_benchmark.xml @@ -0,0 +1,217 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inputFiles/hydraulicFracturing/pennyShapedToughnessDominated_poroelastic_smoke.xml b/inputFiles/hydraulicFracturing/pennyShapedToughnessDominated_poroelastic_smoke.xml new file mode 100644 index 00000000000..9959607e44a --- /dev/null +++ b/inputFiles/hydraulicFracturing/pennyShapedToughnessDominated_poroelastic_smoke.xml @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inputFiles/hydraulicFracturing/pennyShapedToughnessDominated_smoke.xml b/inputFiles/hydraulicFracturing/pennyShapedToughnessDominated_smoke.xml index c55e69f0af2..9e9014f9dac 100644 --- a/inputFiles/hydraulicFracturing/pennyShapedToughnessDominated_smoke.xml +++ b/inputFiles/hydraulicFracturing/pennyShapedToughnessDominated_smoke.xml @@ -16,11 +16,12 @@ logLevel="1" targetRegions="{ Fracture }" contactRelationName="fractureContact" - maxNumResolves="5" - initialDt="0.1"> + maxNumResolves="1" + initialDt="0.01"> + newtonTol="1.0e-5" + newtonMaxIter="20" + lineSearchMaxCuts="3"/> @@ -48,51 +49,72 @@ - + elementTypes="{C3D8}" + xCoords="{ -25, 0, 25 }" + yCoords="{ 0, 10, 30 }" + zCoords="{ 0, 10, 30 }" + nx="{ 5, 5 }" + ny="{ 20, 5 }" + nz="{ 20, 5 }" + xBias="{ 0.6, -0.6 }" + yBias="{ 0.0, -0.6 }" + zBias="{ 0.0, -0.6 }" + cellBlockNames="{cb1}"/> - + xMin="{ -0.01, -0.01, -0.01 }" + xMax="{ 0.01, 1.01, 1.01 }"/> + xMin="{ -0.01, -0.01, -0.01 }" + xMax="{ 0.01, 0.51, 0.51 }"/> + xMin="{ -0.01, -0.01, -0.01 }" + xMax="{ 0.01, 1e6, 1e6 }"/> + maxTime="1.0"> + + + + + + + + diff --git a/inputFiles/hydraulicFracturing/pennyShapedViscosityDominated_base.xml b/inputFiles/hydraulicFracturing/pennyShapedViscosityDominated_base.xml index b539489f6c0..aa760f8649f 100644 --- a/inputFiles/hydraulicFracturing/pennyShapedViscosityDominated_base.xml +++ b/inputFiles/hydraulicFracturing/pennyShapedViscosityDominated_base.xml @@ -159,7 +159,20 @@ + fieldName="elementArea"/> + + + + + @@ -175,6 +188,10 @@ name="timeHistoryOutput" sources="{/Tasks/pressureCollection, /Tasks/apertureCollection, /Tasks/hydraulicApertureCollection, /Tasks/areaCollection}" filename="pennyShapedViscosityDominated_output" /> + diff --git a/inputFiles/hydraulicFracturing/pennyShapedViscosityDominated_poroelastic_base.xml b/inputFiles/hydraulicFracturing/pennyShapedViscosityDominated_poroelastic_base.xml new file mode 100644 index 00000000000..f186a7eb2fb --- /dev/null +++ b/inputFiles/hydraulicFracturing/pennyShapedViscosityDominated_poroelastic_base.xml @@ -0,0 +1,198 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inputFiles/hydraulicFracturing/pennyShapedViscosityDominated_poroelastic_benchmark.xml b/inputFiles/hydraulicFracturing/pennyShapedViscosityDominated_poroelastic_benchmark.xml new file mode 100644 index 00000000000..cf731e4fe59 --- /dev/null +++ b/inputFiles/hydraulicFracturing/pennyShapedViscosityDominated_poroelastic_benchmark.xml @@ -0,0 +1,218 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inputFiles/hydraulicFracturing/pennyShapedViscosityDominated_poroelastic_smoke.xml b/inputFiles/hydraulicFracturing/pennyShapedViscosityDominated_poroelastic_smoke.xml new file mode 100644 index 00000000000..a330b0c1102 --- /dev/null +++ b/inputFiles/hydraulicFracturing/pennyShapedViscosityDominated_poroelastic_smoke.xml @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inputFiles/hydraulicFracturing/pennyShapedViscosityDominated_smoke.xml b/inputFiles/hydraulicFracturing/pennyShapedViscosityDominated_smoke.xml index f7842c86884..714fae1379c 100644 --- a/inputFiles/hydraulicFracturing/pennyShapedViscosityDominated_smoke.xml +++ b/inputFiles/hydraulicFracturing/pennyShapedViscosityDominated_smoke.xml @@ -17,10 +17,10 @@ targetRegions="{ Fracture }" contactRelationName="fractureContact" maxNumResolves="1" - initialDt="0.1"> + initialDt="0.01"> + newtonTol="1.0e-5" + newtonMaxIter="30"/> @@ -48,52 +48,72 @@ - + elementTypes="{C3D8}" + xCoords="{ -25, 0, 25 }" + yCoords="{ 0, 10, 30 }" + zCoords="{ 0, 10, 30 }" + nx="{ 5, 5 }" + ny="{ 20, 5 }" + nz="{ 20, 5 }" + xBias="{ 0.6, -0.6 }" + yBias="{ 0.0, -0.6 }" + zBias="{ 0.0, -0.6 }" + cellBlockNames="{cb1}"/> + xMin="{ -0.01, -0.01, -0.01 }" + xMax="{ 0.01, 1.01, 1.01 }"/> - + xMin="{ -0.01, -0.01, -0.01 }" + xMax="{ 0.01, 0.51, 0.51 }"/> + xMin="{ -0.01, -0.01, -0.01 }" + xMax="{ 0.01, 1e6, 1e6 }"/> + maxTime="1.0"> + + + + + + + + diff --git a/inputFiles/hydraulicFracturing/pknViscosityDominated_base.xml b/inputFiles/hydraulicFracturing/pknViscosityDominated_base.xml index cf01b98fb65..add569e9aaa 100644 --- a/inputFiles/hydraulicFracturing/pknViscosityDominated_base.xml +++ b/inputFiles/hydraulicFracturing/pknViscosityDominated_base.xml @@ -159,7 +159,20 @@ + fieldName="elementArea"/> + + + + + @@ -176,6 +189,11 @@ sources="{/Tasks/pressureCollection, /Tasks/apertureCollection, /Tasks/hydraulicApertureCollection, /Tasks/areaCollection}" filename="pknViscosityDominated_output" /> + + diff --git a/inputFiles/hydraulicFracturing/pknViscosityDominated_benchmark.xml b/inputFiles/hydraulicFracturing/pknViscosityDominated_benchmark.xml index 7ba3b0b4dec..9b4d96a8d12 100644 --- a/inputFiles/hydraulicFracturing/pknViscosityDominated_benchmark.xml +++ b/inputFiles/hydraulicFracturing/pknViscosityDominated_benchmark.xml @@ -22,7 +22,8 @@ + maxTimeStepCuts="5" + maxAllowedResidualNorm="1e+15"/> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inputFiles/hydraulicFracturing/pknViscosityDominated_poroelastic_benchmark.xml b/inputFiles/hydraulicFracturing/pknViscosityDominated_poroelastic_benchmark.xml new file mode 100644 index 00000000000..4dde5d644d2 --- /dev/null +++ b/inputFiles/hydraulicFracturing/pknViscosityDominated_poroelastic_benchmark.xml @@ -0,0 +1,208 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inputFiles/hydraulicFracturing/pknViscosityDominated_poroelastic_smoke.xml b/inputFiles/hydraulicFracturing/pknViscosityDominated_poroelastic_smoke.xml new file mode 100644 index 00000000000..e78204ea5d2 --- /dev/null +++ b/inputFiles/hydraulicFracturing/pknViscosityDominated_poroelastic_smoke.xml @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inputFiles/hydraulicFracturing/pknViscosityDominated_smoke.xml b/inputFiles/hydraulicFracturing/pknViscosityDominated_smoke.xml index 37ad7448d9e..53fe7e4178a 100644 --- a/inputFiles/hydraulicFracturing/pknViscosityDominated_smoke.xml +++ b/inputFiles/hydraulicFracturing/pknViscosityDominated_smoke.xml @@ -16,10 +16,10 @@ logLevel="1" targetRegions="{ Fracture }" contactRelationName="fractureContact" - maxNumResolves="5" - initialDt="0.1"> + maxNumResolves="1" + initialDt="0.01"> @@ -43,7 +43,7 @@ name="SurfaceGen" targetRegions="{ Domain }" nodeBasedSIF="1" - rockToughness="0.1e6" + rockToughness="1e4" mpiCommOrder="1"/> @@ -51,48 +51,69 @@ + xMin="{ -0.1, -0.1, -0.1 }" + xMax="{ 2.1, 0.1, 5.1 }"/> + xMin="{ -0.1, -0.1, -0.1 }" + xMax="{ 1.1, 0.1, 1.1 }"/> + xMin="{ -0.1, -0.1, -0.1 }" + xMax="{ 1e6, 0.1, 5.1 }"/> + maxTime="4"> + + + + + + + + diff --git a/inputFiles/hydraulicFracturing/scripts/HydrofractureSolutions.py b/inputFiles/hydraulicFracturing/scripts/HydrofractureSolutions.py index 9200c7d571e..b4b59d48235 100644 --- a/inputFiles/hydraulicFracturing/scripts/HydrofractureSolutions.py +++ b/inputFiles/hydraulicFracturing/scripts/HydrofractureSolutions.py @@ -267,6 +267,33 @@ def PI_k0(self): return (math.pi / 8.0 * pow(math.pi / 12, 1.0 / 5.0)) +class PKN_viscosityStorageDominated: + + def __init__(self, E, nu, KIC, mu, Q0, t, h): + Ep = E / (1.0 - nu**2.0) + self.t = t + self.Q0 = Q0 + self.mu = mu + self.Ep = Ep + self.h = h + + def analyticalSolution(self): + t = self.t + Q0 = self.Q0 + mu = self.mu + Ep = self.Ep + h = self.h + + halfLength = 0.3817 * ((Ep * Q0**3.0 * t**4.0) / (mu * h**4.0))**(1.0 / 5.0) + + inletAperture = 3.0 * ((mu * Q0 * halfLength) / (Ep))**(1.0 / 4.0) + + inletPressure = ((16.0 * mu * Q0 * Ep**3.0 * halfLength) / (np.pi * h**4.0))**(1.0 / 4.0) + + return [halfLength, inletAperture, inletPressure] + + + if __name__ == "__main__": mu = 0.0005 E = 3.2e10 diff --git a/inputFiles/hydraulicFracturing/scripts/hydrofractureCurveChecks.py b/inputFiles/hydraulicFracturing/scripts/hydrofractureCurveChecks.py new file mode 100644 index 00000000000..3b388f2f68a --- /dev/null +++ b/inputFiles/hydraulicFracturing/scripts/hydrofractureCurveChecks.py @@ -0,0 +1,146 @@ +import numpy as np +import os +import sys + +# Check for the file containing hydraulic fracturing solutions +pwd = os.path.abspath(os.getcwd()) +if not os.path.isfile(os.path.join(pwd, 'HydrofractureSolutions.py')): + check_path = os.path.abspath(os.path.join(pwd, '../../../../inputFiles/hydraulicFracturing/scripts/')) + if os.path.isfile(os.path.join(check_path, 'HydrofractureSolutions.py')): + sys.path.append(check_path) + else: + raise Exception('Could not find HydrofractureSolutions.py') + +import HydrofractureSolutions + + +def get_youngs_modulus_poisson_ratio(bulk_modulus, shear_modulus): + E = 9.0 * bulk_modulus * shear_modulus / (3.0 * bulk_modulus + shear_modulus) + nu = (1.5 * bulk_modulus - shear_modulus) / (3.0 + bulk_modulus * shear_modulus) + return E, nu + + +def get_plane_strain_modulus(E, nu): + return E / (1.0 - nu**2.0) + + +def kgd_toughness_dominated_solutions(**kwargs): + E = 30.0e9 + nu = 0.25 + KIC = 1.0e6 + mu = 1.0e-6 + Q0 = -5e-2 * -2.0 / 1000.0 + xSource = 0.0 + + Ep = get_plane_strain_modulus(E, nu) + t = np.squeeze(kwargs['hydraulicAperture Time'][:, 0]) + radTimes = t[-1:] + hfsolns = HydrofractureSolutions.KGDSolutions() + kgdFrac = hfsolns.Solutions(mu, Ep, Q0, KIC, t, radTimes, xSource) + return kgdFrac[5], kgdFrac[7] + + +def kgd_toughness_dominated_pressure_curve(**kwargs): + return kgd_toughness_dominated_solutions(**kwargs)[0] + + +def kgd_toughness_dominated_aperture_curve(**kwargs): + return kgd_toughness_dominated_solutions(**kwargs)[1] + + +def kgd_viscosity_dominated_solutions(**kwargs): + E = 30.0e9 + nu = 0.25 + KIC = 1.0e4 + mu = 1.0e-3 + Q0 = -5e-2 * -2.0 / 1000.0 + xSource = 0.0 + + Ep = get_plane_strain_modulus(E, nu) + t = np.squeeze(kwargs['hydraulicAperture Time'][:, 0]) + radTimes = t[-1:] + hfsolns = HydrofractureSolutions.KGDSolutions() + kgdFrac = hfsolns.Solutions(mu, Ep, Q0, KIC, t, radTimes, xSource) + return kgdFrac[8], kgdFrac[10] + + +def kgd_viscosity_dominated_pressure_curve(**kwargs): + return kgd_viscosity_dominated_solutions(**kwargs)[0] + + +def kgd_viscosity_dominated_aperture_curve(**kwargs): + return kgd_viscosity_dominated_solutions(**kwargs)[1] + + +def penny_shaped_toughness_dominated_solutions(**kwargs): + bulk_modulus = 20.0e9 + shear_modulus = 12.0e9 + KIC = 3e6 + mu = 1.0e-6 + Q0 = -6.625 * -2.0 / 1000.0 + xSource = 0.0 + + E, nu = get_youngs_modulus_poisson_ratio(bulk_modulus, shear_modulus) + Ep = get_plane_strain_modulus(E, nu) + t = np.squeeze(kwargs['hydraulicAperture Time'][:, 0]) + radTimes = t[-1:] + hfsolns = HydrofractureSolutions.PennySolutions() + pennyFrac = hfsolns.Solutions(mu, Ep, Q0, KIC, t, radTimes, xSource) + return pennyFrac[5], pennyFrac[7] + + +def penny_shaped_toughness_dominated_pressure_curve(**kwargs): + return penny_shaped_toughness_dominated_solutions(**kwargs)[0] + + +def penny_shaped_toughness_dominated_aperture_curve(**kwargs): + return penny_shaped_toughness_dominated_solutions(**kwargs)[1] + + +def penny_shaped_viscosity_dominated_solutions(**kwargs): + bulk_modulus = 20.0e9 + shear_modulus = 12.0e9 + KIC = 0.3e6 + mu = 1.0e-3 + Q0 = -6.625 * -2.0 / 1000.0 + xSource = 0.0 + + E, nu = get_youngs_modulus_poisson_ratio(bulk_modulus, shear_modulus) + Ep = get_plane_strain_modulus(E, nu) + t = np.squeeze(kwargs['hydraulicAperture Time'][:, 0]) + radTimes = t[-1:] + hfsolns = HydrofractureSolutions.PennySolutions() + pennyFrac = hfsolns.Solutions(mu, Ep, Q0, KIC, t, radTimes, xSource) + return pennyFrac[8], pennyFrac[10] + + +def penny_shaped_viscosity_dominated_pressure_curve(**kwargs): + return penny_shaped_viscosity_dominated_solutions(**kwargs)[0] + + +def penny_shaped_viscosity_dominated_aperture_curve(**kwargs): + return penny_shaped_viscosity_dominated_solutions(**kwargs)[1] + + +def pkn_viscosity_dominated_solutions(**kwargs): + bulk_modulus = 20.0e9 + shear_modulus = 12.0e9 + KIC = 0.1e6 + mu = 1.0e-3 + Q0 = -6.625 * -2.0 / 1000.0 + xSource = 0.0 + height = 6.0 + + E, nu = get_youngs_modulus_poisson_ratio(bulk_modulus, shear_modulus) + t = np.squeeze(kwargs['hydraulicAperture Time'][:, 0]) + pknFrac = HydrofractureSolutions.PKN_viscosityStorageDominated(E, nu, KIC, mu, Q0, t, height) + halfLength, inletAperture, inletPressure = pknFrac.analyticalSolution() + return inletPressure, inletAperture + + +def pkn_viscosity_dominated_pressure_curve(**kwargs): + return pkn_viscosity_dominated_solutions(**kwargs)[0] + + +def pkn_viscosity_dominated_aperture_curve(**kwargs): + return pkn_viscosity_dominated_solutions(**kwargs)[1] diff --git a/inputFiles/hydraulicFracturing/scripts/hydrofractureFigure.py b/inputFiles/hydraulicFracturing/scripts/hydrofractureFigure.py index b76ae533487..739777bb336 100644 --- a/inputFiles/hydraulicFracturing/scripts/hydrofractureFigure.py +++ b/inputFiles/hydraulicFracturing/scripts/hydrofractureFigure.py @@ -67,31 +67,6 @@ def getParametersFromXML(xmlFilePath): return [maxTime, youngModulus, poissonRatio, toughness, viscosity, injectionRate, x_source] -class PKN_viscosityStorageDominated: - - def __init__(self, E, nu, KIC, mu, Q0, t, h): - Ep = E / (1.0 - nu**2.0) - self.t = t - self.Q0 = Q0 - self.mu = mu - self.Ep = Ep - self.h = h - - def analyticalSolution(self): - t = self.t - Q0 = self.Q0 - mu = self.mu - Ep = self.Ep - h = self.h - - halfLength = 0.3817 * ((Ep * Q0**3.0 * t**4.0) / (mu * h**4.0))**(1.0 / 5.0) - - inletAperture = 3.0 * ((mu * Q0 * halfLength) / (Ep))**(1.0 / 4.0) - - inletPressure = ((16.0 * mu * Q0 * Ep**3.0 * halfLength) / (np.pi * h**4.0))**(1.0 / 4.0) - - return [halfLength, inletAperture, inletPressure] - def main(xmlFilePathPrefix=''): if not xmlFilePathPrefix: @@ -136,7 +111,7 @@ def main(xmlFilePathPrefix=''): lablelist = ['Asymptotic ( $K_{IC}$ => 0, $C_{L}$ => 0 )', 'GEOSX ( $K_{IC}$ => 0, $C_{L}$ => 0 )'] elif xmlFilePathPrefix == 'pknViscosityDominated': - pknFrac = PKN_viscosityStorageDominated(E, nu, KIC, mu, Q0, t, xSource) + pknFrac = HydrofractureSolutions.PKN_viscosityStorageDominated(E, nu, KIC, mu, Q0, t, xSource) halfLength, inletAperture, inletPressure = pknFrac.analyticalSolution() lablelist = ['Asymptotic ( $K_{IC}$ => 0, $C_{L}$ => 0 )', 'GEOSX ( $K_{IC}$ => 0, $C_{L}$ => 0 )'] diff --git a/inputFiles/lagrangianContactMechanics/ContactMechanics_PassingCrack_smoke.xml b/inputFiles/lagrangianContactMechanics/ContactMechanics_PassingCrack_smoke.xml index ea516718b9f..4ba2c33f6d8 100644 --- a/inputFiles/lagrangianContactMechanics/ContactMechanics_PassingCrack_smoke.xml +++ b/inputFiles/lagrangianContactMechanics/ContactMechanics_PassingCrack_smoke.xml @@ -13,7 +13,7 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/inputFiles/multiphaseFlowFractures/deadOil_fractureMatrixFlow_edfm_horizontalFrac_smoke.xml b/inputFiles/multiphaseFlowFractures/deadOil_fractureMatrixFlow_edfm_horizontalFrac_smoke.xml index 3cbc180c20e..2f79dd921d4 100644 --- a/inputFiles/multiphaseFlowFractures/deadOil_fractureMatrixFlow_edfm_horizontalFrac_smoke.xml +++ b/inputFiles/multiphaseFlowFractures/deadOil_fractureMatrixFlow_edfm_horizontalFrac_smoke.xml @@ -16,7 +16,7 @@ xMin="{ 0.9, 0.90, -0.01 }" xMax="{ 1.01, 1.01, 0.11 }"/> - - @@ -58,7 +59,7 @@ xMin="{ 3.99, 3.99, -0.01 }" xMax="{ 5.01, 5.01, 1.01 }"/> - @@ -58,7 +59,7 @@ xMin="{ 9.99, 9.99, -0.01 }" xMax="{ 11.01, 11.01, 1.01 }"/> - @@ -52,7 +53,7 @@ xMin="{ 8.99, -0.01, -0.01 }" xMax="{ 10.01, 1.01, 1.01 }"/> - @@ -58,7 +59,7 @@ xMin="{ 9.99, 9.99, -0.01 }" xMax="{ 11.01, 11.01, 1.01 }"/> - - - - - - - - - - - - - - - - - diff --git a/inputFiles/poromechanics/PoroElastic_Mandel_benchmark.xml b/inputFiles/poromechanics/PoroElastic_Mandel_benchmark_fim.xml similarity index 53% rename from inputFiles/poromechanics/PoroElastic_Mandel_benchmark.xml rename to inputFiles/poromechanics/PoroElastic_Mandel_benchmark_fim.xml index dfd910206f2..4774c9bc607 100644 --- a/inputFiles/poromechanics/PoroElastic_Mandel_benchmark.xml +++ b/inputFiles/poromechanics/PoroElastic_Mandel_benchmark_fim.xml @@ -5,6 +5,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inputFiles/poromechanics/PoroElastic_Mandel_smoke.xml b/inputFiles/poromechanics/PoroElastic_Mandel_smoke_fim.xml similarity index 56% rename from inputFiles/poromechanics/PoroElastic_Mandel_smoke.xml rename to inputFiles/poromechanics/PoroElastic_Mandel_smoke_fim.xml index ddd276d6f18..98b6633fb9a 100644 --- a/inputFiles/poromechanics/PoroElastic_Mandel_smoke.xml +++ b/inputFiles/poromechanics/PoroElastic_Mandel_smoke_fim.xml @@ -5,6 +5,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inputFiles/poromechanics/PoroElastic_deadoil_3ph_baker_2d.xml b/inputFiles/poromechanics/PoroElastic_deadoil_3ph_baker_2d_base.xml similarity index 90% rename from inputFiles/poromechanics/PoroElastic_deadoil_3ph_baker_2d.xml rename to inputFiles/poromechanics/PoroElastic_deadoil_3ph_baker_2d_base.xml index 2bfe62b0aa7..8c2f626b281 100644 --- a/inputFiles/poromechanics/PoroElastic_deadoil_3ph_baker_2d.xml +++ b/inputFiles/poromechanics/PoroElastic_deadoil_3ph_baker_2d_base.xml @@ -1,36 +1,6 @@ - - - - - - - - - - + + + + + + + + + + + + + + + + + + + diff --git a/inputFiles/poromechanics/PoroElastic_deadoil_3ph_baker_2d_sequential.xml b/inputFiles/poromechanics/PoroElastic_deadoil_3ph_baker_2d_sequential.xml new file mode 100644 index 00000000000..5fbcf21ac12 --- /dev/null +++ b/inputFiles/poromechanics/PoroElastic_deadoil_3ph_baker_2d_sequential.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inputFiles/poromechanics/PoroElastic_staircase_co2_3d.xml b/inputFiles/poromechanics/PoroElastic_staircase_co2_3d.xml index af98fdc3f0d..c8f7945a8bf 100644 --- a/inputFiles/poromechanics/PoroElastic_staircase_co2_3d.xml +++ b/inputFiles/poromechanics/PoroElastic_staircase_co2_3d.xml @@ -85,45 +85,43 @@ nx="{ 3, 3 }" ny="{ 3, 3 }" nz="{ 2, 2, 2, 2 }" - cellBlockNames="{ b00, b01, b02, b03, b04, b05, b06, b07, b08, b09, b10, b11, b12, b13, b14, b15 }"/> + cellBlockNames="{ b00, b01, b02, b03, b04, b05, b06, b07, b08, b09, b10, b11, b12, b13, b14, b15 }"> - - - - + + + + - - - - - + + + + + + cellBlockNames="{ b00, b01, b02, b03, b04, b05, b06, b07, b08, b09, b10, b11, b12, b13, b14, b15 }"> - - - - + + + + - - - - - + + + + + diff --git a/inputFiles/poromechanicsFractures/ExponentialDecayPermeability_edfm_smoke.xml b/inputFiles/poromechanicsFractures/ExponentialDecayPermeability_edfm_smoke.xml index ece79f7c3d5..ad04e11527d 100755 --- a/inputFiles/poromechanicsFractures/ExponentialDecayPermeability_edfm_smoke.xml +++ b/inputFiles/poromechanicsFractures/ExponentialDecayPermeability_edfm_smoke.xml @@ -20,7 +20,7 @@ - @@ -105,7 +106,7 @@ @@ -66,7 +67,7 @@ @@ -94,14 +95,14 @@ xMin="{1181, 0.0, -800.0}" name="right" /> - - - - - - + setNames="{ yneg, ypos }"/> + setNames="{ ypos }"/> + + + + @@ -244,7 +184,7 @@ diff --git a/inputFiles/poromechanicsFractures/PoroElastic_efem-edfm_pennyCrack_benchmark.xml b/inputFiles/poromechanicsFractures/PoroElastic_efem-edfm_pennyCrack_benchmark.xml new file mode 100644 index 00000000000..39d8d65744a --- /dev/null +++ b/inputFiles/poromechanicsFractures/PoroElastic_efem-edfm_pennyCrack_benchmark.xml @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inputFiles/poromechanicsFractures/PoroElastic_efem-edfm_pennyCrack_smoke.xml b/inputFiles/poromechanicsFractures/PoroElastic_efem-edfm_pennyCrack_smoke.xml new file mode 100644 index 00000000000..c63a85e8ba1 --- /dev/null +++ b/inputFiles/poromechanicsFractures/PoroElastic_efem-edfm_pennyCrack_smoke.xml @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inputFiles/poromechanicsFractures/PoroElastic_efem-edfm_pressurizedFrac_smoke.xml b/inputFiles/poromechanicsFractures/PoroElastic_efem-edfm_pressurizedFrac_smoke.xml index ffc663b54df..71521e8a649 100644 --- a/inputFiles/poromechanicsFractures/PoroElastic_efem-edfm_pressurizedFrac_smoke.xml +++ b/inputFiles/poromechanicsFractures/PoroElastic_efem-edfm_pressurizedFrac_smoke.xml @@ -41,6 +41,7 @@ discretization="FE1" targetRegions="{ Domain, Fracture }" fractureRegion="Fracture" + targetObjects="{ FracturePlane }" logLevel="2" mpiCommOrder="1"/> @@ -59,7 +60,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/inputFiles/poromechanicsFractures/SlipPermeability_embeddedFrac.xml b/inputFiles/poromechanicsFractures/SlipPermeability_embeddedFrac.xml index fece1282950..32b0bab1859 100755 --- a/inputFiles/poromechanicsFractures/SlipPermeability_embeddedFrac.xml +++ b/inputFiles/poromechanicsFractures/SlipPermeability_embeddedFrac.xml @@ -41,6 +41,7 @@ name="SurfaceGenerator" discretization="FE1" targetRegions="{ Domain, Fracture }" + targetObjects="{ FracturePlane }" fractureRegion="Fracture" logLevel="2" mpiCommOrder="1"/> @@ -60,7 +61,7 @@ - diff --git a/inputFiles/poromechanicsFractures/SlipPermeability_pEDFM_smoke.xml b/inputFiles/poromechanicsFractures/SlipPermeability_pEDFM_smoke.xml index 77b38e05a69..bda8d4e0f89 100755 --- a/inputFiles/poromechanicsFractures/SlipPermeability_pEDFM_smoke.xml +++ b/inputFiles/poromechanicsFractures/SlipPermeability_pEDFM_smoke.xml @@ -20,7 +20,7 @@ - diff --git a/inputFiles/poromechanicsFractures/WillisRichardsPermeability_efem-edfm_smoke.xml b/inputFiles/poromechanicsFractures/WillisRichardsPermeability_efem-edfm_smoke.xml index 4cae4a89b60..46ffd4f9f99 100755 --- a/inputFiles/poromechanicsFractures/WillisRichardsPermeability_efem-edfm_smoke.xml +++ b/inputFiles/poromechanicsFractures/WillisRichardsPermeability_efem-edfm_smoke.xml @@ -20,7 +20,7 @@ - 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 literal 886538 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 _uS}T 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 diff --git a/inputFiles/singlePhaseFlowFractures/fractureMatrixFlowWithGravity_edfm_verticalFrac_smoke.xml b/inputFiles/singlePhaseFlowFractures/fractureMatrixFlowWithGravity_edfm_verticalFrac_smoke.xml index 1b375f73108..648b26903f6 100644 --- a/inputFiles/singlePhaseFlowFractures/fractureMatrixFlowWithGravity_edfm_verticalFrac_smoke.xml +++ b/inputFiles/singlePhaseFlowFractures/fractureMatrixFlowWithGravity_edfm_verticalFrac_smoke.xml @@ -20,6 +20,7 @@ logLevel="1" discretization="FE1" targetRegions="{ Domain, Fracture }" + targetObjects="{ FracturePlane }" fractureRegion="Fracture" mpiCommOrder="1"/> @@ -38,7 +39,7 @@ - diff --git a/inputFiles/singlePhaseFlowFractures/fractureMatrixFlow_edfm_horizontalFrac_benchmark.xml b/inputFiles/singlePhaseFlowFractures/fractureMatrixFlow_edfm_horizontalFrac_benchmark.xml index df2edef26b3..dab4dcd6770 100644 --- a/inputFiles/singlePhaseFlowFractures/fractureMatrixFlow_edfm_horizontalFrac_benchmark.xml +++ b/inputFiles/singlePhaseFlowFractures/fractureMatrixFlow_edfm_horizontalFrac_benchmark.xml @@ -29,7 +29,7 @@ xMin="{ -0.01, 0.9, -0.01 }" xMax="{ 1.01, 1.01, 1.01 }"/> - - - - @@ -48,7 +49,7 @@ xMin="{ -0.01, 0.9, -0.01 }" xMax="{ 1.01, 1.01, 1.01 }"/> - - - - - - - - - + cellBlockNames="{ cb1 }"> + + + + + + + + + - - - - - - - - + cellBlockNames="{ cb1 }"> + + + + + + + + + - - - - - - - - - - - - - - - - - + cellBlockNames="{ cb1 }"> + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - + cellBlockNames="{ cb1 }"> + + + + + + + + + + + + + + + + + + + targetBHP="5e4" + targetTotalRate="1e-7"/> - - - - - - - - + cellBlockNames="{ b00, b01, b02, b03, b04, b05, b06, b07, b08, b09, b10, b11, b12, b13, b14, b15 }"> + + + + + + + + + - - - - - - - - + cellBlockNames="{ b00, b01, b02, b03, b04, b05, b06, b07, b08, b09, b10, b11, b12, b13, b14, b15 }"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inputFiles/solidMechanics/DruckerPragerWellbore_benchmark.xml b/inputFiles/solidMechanics/DruckerPragerWellbore_benchmark.xml new file mode 100644 index 00000000000..c49535234b2 --- /dev/null +++ b/inputFiles/solidMechanics/DruckerPragerWellbore_benchmark.xml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inputFiles/solidMechanics/DruckerPragerWellbore_smoke.xml b/inputFiles/solidMechanics/DruckerPragerWellbore_smoke.xml new file mode 100644 index 00000000000..592e52567a0 --- /dev/null +++ b/inputFiles/solidMechanics/DruckerPragerWellbore_smoke.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inputFiles/solidMechanics/ExtendedDruckerPragerWellbore_base.xml b/inputFiles/solidMechanics/ExtendedDruckerPragerWellbore_base.xml index 744325c0a66..a2e0bd5cd8e 100644 --- a/inputFiles/solidMechanics/ExtendedDruckerPragerWellbore_base.xml +++ b/inputFiles/solidMechanics/ExtendedDruckerPragerWellbore_base.xml @@ -6,8 +6,8 @@ + coordinates="{ 0.0, 1.0, 1e99 }" + values="{ -11.25e6, -2.0e6, -2.0e6 }"/> @@ -17,13 +17,14 @@ + @@ -44,7 +45,7 @@ objectPath="ElementRegions/Omega/cb1" fieldName="rock_stress" component="0" - scale="-11250000.0"/> + scale="-11.25e6"/> + scale="-11.25e6"/> + scale="-15.0e6"/> @@ -78,7 +79,7 @@ fieldName="totalDisplacement" component="0" scale="0.0" - setNames="{ xneg, xpos }"/> + setNames="{ tpos, rpos }"/> + setNames="{ tneg, rpos }"/> - - - + + + + + + + + + + + + + + + + + + + + + + + + + - + - + maxTime="1.05"> + + + + + + + + + + + target="/Outputs/vtkOutput"/> diff --git a/inputFiles/solidMechanics/ExtendedDruckerPragerWellbore_smoke.xml b/inputFiles/solidMechanics/ExtendedDruckerPragerWellbore_smoke.xml index a0ed28bb28a..1112967e9ad 100644 --- a/inputFiles/solidMechanics/ExtendedDruckerPragerWellbore_smoke.xml +++ b/inputFiles/solidMechanics/ExtendedDruckerPragerWellbore_smoke.xml @@ -11,7 +11,7 @@ name="mesh1" elementTypes="{ C3D8 }" radius="{ 0.1, 5.0 }" - theta="{ 0, 180 }" + theta="{ 0, 90 }" zCoords="{ -1, 1 }" nr="{ 4 }" nt="{ 4 }" @@ -19,7 +19,6 @@ trajectory="{ { 0.0, 0.0, -1.0 }, { 0.0, 0.0, 1.0 } }" autoSpaceRadialElems="{ 1 }" - useCartesianOuterBoundary="0" cellBlockNames="{ cb1 }"/> @@ -36,7 +35,7 @@ + target="/Outputs/vtkOutput"/> + + + + + + + + + + + + + + diff --git a/inputFiles/solidMechanics/beamBending_curve.py b/inputFiles/solidMechanics/beamBending_curve.py new file mode 100644 index 00000000000..17d02f5efa9 --- /dev/null +++ b/inputFiles/solidMechanics/beamBending_curve.py @@ -0,0 +1,32 @@ + +import numpy as np + + +def convert_bulk_shear(bulk_modulus, shear_modulus): + return 9.0 * bulk_modulus * shear_modulus / (3.0 * bulk_modulus + shear_modulus) + + +def max_bending(x, youngs_modulus, moment_inertia, beam_length, force): + return (force * x * x) * (3.0 * beam_length - x) / (6.0 * youngs_modulus * moment_inertia) + + +def curve(**kwargs): + x = np.squeeze(kwargs['totalDisplacement ReferencePosition trace'][0, :, 0]) + t = np.squeeze(kwargs['totalDisplacement Time'][:, 0]) + shear_modulus = 4.16667e9 + bulk_modulus = 5.5556e9 + beam_size = [80.0, 8.0, 4.0] + traction = 1e6 + artificial_stiffness = 1.043 + + # Calculate max deflection + beam_I = beam_size[1] * (beam_size[2] ** 3) / 3 + youngs_modulus = convert_bulk_shear(bulk_modulus, shear_modulus) + f = traction * beam_size[1] * beam_size[2] + + # In the example, the traction is scaled by time + dy = np.zeros(np.shape(kwargs['totalDisplacement trace'])) + for ii, tb in enumerate(t): + dy[ii, :, 1] = max_bending(x, youngs_modulus*artificial_stiffness, beam_I, beam_size[0], f * (tb + 1.0)) + + return dy diff --git a/inputFiles/solidMechanics/sedov_finiteStrain_smoke.xml b/inputFiles/solidMechanics/sedov_finiteStrain_smoke.xml index a3cab955671..9cf7c28e898 100644 --- a/inputFiles/solidMechanics/sedov_finiteStrain_smoke.xml +++ b/inputFiles/solidMechanics/sedov_finiteStrain_smoke.xml @@ -43,7 +43,17 @@ name="solverApplications" forceDt="1.0e-5" target="/Solvers/lagsolve"/> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inputFiles/thermalSinglePhaseFlowFractures/egsCollab_thermalFlow/egsCollab_thermalFlow_initialCond_base.xml b/inputFiles/thermalSinglePhaseFlowFractures/egsCollab_thermalFlow/egsCollab_thermalFlow_initialCond_base.xml new file mode 100644 index 00000000000..0ec3e35393b --- /dev/null +++ b/inputFiles/thermalSinglePhaseFlowFractures/egsCollab_thermalFlow/egsCollab_thermalFlow_initialCond_base.xml @@ -0,0 +1,134 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inputFiles/thermalSinglePhaseFlowFractures/egsCollab_thermalFlow/egsCollab_thermalFlow_initialCond_coarse.xml b/inputFiles/thermalSinglePhaseFlowFractures/egsCollab_thermalFlow/egsCollab_thermalFlow_initialCond_coarse.xml new file mode 100644 index 00000000000..4b23c54cf0f --- /dev/null +++ b/inputFiles/thermalSinglePhaseFlowFractures/egsCollab_thermalFlow/egsCollab_thermalFlow_initialCond_coarse.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + diff --git a/inputFiles/thermalSinglePhaseFlowFractures/egsCollab_thermalFlow/egsCollab_thermalFlow_initialCond_fine.xml b/inputFiles/thermalSinglePhaseFlowFractures/egsCollab_thermalFlow/egsCollab_thermalFlow_initialCond_fine.xml new file mode 100644 index 00000000000..c164af31297 --- /dev/null +++ b/inputFiles/thermalSinglePhaseFlowFractures/egsCollab_thermalFlow/egsCollab_thermalFlow_initialCond_fine.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + diff --git a/inputFiles/thermalSinglePhaseFlowFractures/egsCollab_thermalFlow/egsCollab_thermalFlow_injection_base.xml b/inputFiles/thermalSinglePhaseFlowFractures/egsCollab_thermalFlow/egsCollab_thermalFlow_injection_base.xml new file mode 100644 index 00000000000..7b4e79b22f2 --- /dev/null +++ b/inputFiles/thermalSinglePhaseFlowFractures/egsCollab_thermalFlow/egsCollab_thermalFlow_injection_base.xml @@ -0,0 +1,211 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inputFiles/thermalSinglePhaseFlowFractures/egsCollab_thermalFlow/egsCollab_thermalFlow_injection_coarse.xml b/inputFiles/thermalSinglePhaseFlowFractures/egsCollab_thermalFlow/egsCollab_thermalFlow_injection_coarse.xml new file mode 100644 index 00000000000..4b2485fa9e0 --- /dev/null +++ b/inputFiles/thermalSinglePhaseFlowFractures/egsCollab_thermalFlow/egsCollab_thermalFlow_injection_coarse.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/inputFiles/thermalSinglePhaseFlowFractures/egsCollab_thermalFlow/egsCollab_thermalFlow_injection_fine.xml b/inputFiles/thermalSinglePhaseFlowFractures/egsCollab_thermalFlow/egsCollab_thermalFlow_injection_fine.xml new file mode 100644 index 00000000000..ac3fa3ef9c2 --- /dev/null +++ b/inputFiles/thermalSinglePhaseFlowFractures/egsCollab_thermalFlow/egsCollab_thermalFlow_injection_fine.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/inputFiles/thermalSinglePhaseFlowFractures/egsCollab_thermalFlow/injectionSchedule_time.csv b/inputFiles/thermalSinglePhaseFlowFractures/egsCollab_thermalFlow/injectionSchedule_time.csv new file mode 100644 index 00000000000..e06b5739a51 --- /dev/null +++ b/inputFiles/thermalSinglePhaseFlowFractures/egsCollab_thermalFlow/injectionSchedule_time.csv @@ -0,0 +1,27 @@ +0.00000000E+00 +1.57680000E+09 +1.57680086E+09 +1.57688640E+09 +1.57697280E+09 +1.57705920E+09 +1.57714560E+09 +1.57723200E+09 +1.57731840E+09 +1.57740394E+09 +1.57740480E+09 +1.57749120E+09 +1.57757674E+09 +1.57757760E+09 +1.57766400E+09 +1.57775040E+09 +1.57783680E+09 +1.57792320E+09 +1.57800960E+09 +1.57809600E+09 +1.57818240E+09 +1.57826794E+09 +1.57826880E+09 +1.57835434E+09 +1.57835520E+09 +1.57844160E+09 +1.57852800E+09 diff --git a/inputFiles/thermalSinglePhaseFlowFractures/egsCollab_thermalFlow/productionSchedule_time.csv b/inputFiles/thermalSinglePhaseFlowFractures/egsCollab_thermalFlow/productionSchedule_time.csv new file mode 100644 index 00000000000..1f790c35a08 --- /dev/null +++ b/inputFiles/thermalSinglePhaseFlowFractures/egsCollab_thermalFlow/productionSchedule_time.csv @@ -0,0 +1,26 @@ +0.00000000E+00 +1.57680000E+09 +1.57680086E+09 +1.57688554E+09 +1.57688640E+09 +1.57697280E+09 +1.57705920E+09 +1.57714560E+09 +1.57723114E+09 +1.57723200E+09 +1.57731840E+09 +1.57740480E+09 +1.57749120E+09 +1.57757760E+09 +1.57766400E+09 +1.57775040E+09 +1.57783680E+09 +1.57792320E+09 +1.57800960E+09 +1.57809600E+09 +1.57818240E+09 +1.57826880E+09 +1.57835434E+09 +1.57835520E+09 +1.57844160E+09 +1.57852800E+09 diff --git a/inputFiles/thermalSinglePhaseFlowFractures/fractureMatrixThermalFlow_base.xml b/inputFiles/thermalSinglePhaseFlowFractures/fractureMatrixThermalFlow_base.xml new file mode 100644 index 00000000000..6188a59b674 --- /dev/null +++ b/inputFiles/thermalSinglePhaseFlowFractures/fractureMatrixThermalFlow_base.xml @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inputFiles/thermalSinglePhaseFlowFractures/fractureMatrixThermalFlow_bc.xml b/inputFiles/thermalSinglePhaseFlowFractures/fractureMatrixThermalFlow_bc.xml new file mode 100644 index 00000000000..55a08e10138 --- /dev/null +++ b/inputFiles/thermalSinglePhaseFlowFractures/fractureMatrixThermalFlow_bc.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/inputFiles/thermalSinglePhaseFlowFractures/fractureMatrixThermalFlow_conforming_base.xml b/inputFiles/thermalSinglePhaseFlowFractures/fractureMatrixThermalFlow_conforming_base.xml new file mode 100644 index 00000000000..6a3c2e68250 --- /dev/null +++ b/inputFiles/thermalSinglePhaseFlowFractures/fractureMatrixThermalFlow_conforming_base.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inputFiles/thermalSinglePhaseFlowFractures/fractureMatrixThermalFlow_conforming_bc.xml b/inputFiles/thermalSinglePhaseFlowFractures/fractureMatrixThermalFlow_conforming_bc.xml new file mode 100644 index 00000000000..b096ff3beb2 --- /dev/null +++ b/inputFiles/thermalSinglePhaseFlowFractures/fractureMatrixThermalFlow_conforming_bc.xml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inputFiles/thermalSinglePhaseFlowFractures/fractureMatrixThermalFlow_conforming_benchmark.xml b/inputFiles/thermalSinglePhaseFlowFractures/fractureMatrixThermalFlow_conforming_benchmark.xml new file mode 100644 index 00000000000..1a134ab234b --- /dev/null +++ b/inputFiles/thermalSinglePhaseFlowFractures/fractureMatrixThermalFlow_conforming_benchmark.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inputFiles/thermalSinglePhaseFlowFractures/fractureMatrixThermalFlow_conforming_smoke.xml b/inputFiles/thermalSinglePhaseFlowFractures/fractureMatrixThermalFlow_conforming_smoke.xml new file mode 100644 index 00000000000..6b0fe25da70 --- /dev/null +++ b/inputFiles/thermalSinglePhaseFlowFractures/fractureMatrixThermalFlow_conforming_smoke.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inputFiles/thermalSinglePhaseFlowFractures/fractureMatrixThermalFlow_edfm_base.xml b/inputFiles/thermalSinglePhaseFlowFractures/fractureMatrixThermalFlow_edfm_base.xml new file mode 100644 index 00000000000..ce38e6d3b52 --- /dev/null +++ b/inputFiles/thermalSinglePhaseFlowFractures/fractureMatrixThermalFlow_edfm_base.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inputFiles/efemFractureMechanics/Sneddon_staticCondensation_smoke.xml b/inputFiles/thermalSinglePhaseFlowFractures/fractureMatrixThermalFlow_edfm_benchmark.xml similarity index 52% rename from inputFiles/efemFractureMechanics/Sneddon_staticCondensation_smoke.xml rename to inputFiles/thermalSinglePhaseFlowFractures/fractureMatrixThermalFlow_edfm_benchmark.xml index 7f60a1cff10..3ac1e442063 100644 --- a/inputFiles/efemFractureMechanics/Sneddon_staticCondensation_smoke.xml +++ b/inputFiles/thermalSinglePhaseFlowFractures/fractureMatrixThermalFlow_edfm_benchmark.xml @@ -1,55 +1,51 @@ - + name="./fractureMatrixThermalFlow_edfm_base.xml"/> + + - + dimensions="{ 25, 4 }"/> - + + maxTime="100" + logLevel="3"> + forceDt="10.0" + target="/Solvers/SinglePhaseFlow"/> - - - diff --git a/inputFiles/thermalSinglePhaseFlowFractures/fractureMatrixThermalFlow_edfm_smoke.xml b/inputFiles/thermalSinglePhaseFlowFractures/fractureMatrixThermalFlow_edfm_smoke.xml new file mode 100644 index 00000000000..00b24c41c41 --- /dev/null +++ b/inputFiles/thermalSinglePhaseFlowFractures/fractureMatrixThermalFlow_edfm_smoke.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inputFiles/thermoPoromechanics/ThermoPoroElastic_consolidation_benchmark_sequential.xml b/inputFiles/thermoPoromechanics/ThermoPoroElastic_consolidation_benchmark_sequential.xml index 6849bc7dafd..ee732399abb 100644 --- a/inputFiles/thermoPoromechanics/ThermoPoroElastic_consolidation_benchmark_sequential.xml +++ b/inputFiles/thermoPoromechanics/ThermoPoroElastic_consolidation_benchmark_sequential.xml @@ -20,7 +20,7 @@ couplingType="Sequential" newtonMaxIter="200" lineSearchAction="None" - newtonTol="1e-6" + newtonTol="1e-9" subcycling="1"/> @@ -36,7 +36,7 @@ + newtonTol="1.0e-10"/> @@ -52,7 +52,7 @@ + newtonTol="1.0e-10"/> diff --git a/inputFiles/thermoPoromechanicsFractures/ThermoPoroElastic_base.xml b/inputFiles/thermoPoromechanicsFractures/ThermoPoroElastic_base.xml new file mode 100644 index 00000000000..fcdaa19a12c --- /dev/null +++ b/inputFiles/thermoPoromechanicsFractures/ThermoPoroElastic_base.xml @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inputFiles/thermoPoromechanicsFractures/ThermoPoroElastic_conforming_base.xml b/inputFiles/thermoPoromechanicsFractures/ThermoPoroElastic_conforming_base.xml new file mode 100644 index 00000000000..8ce04b4f431 --- /dev/null +++ b/inputFiles/thermoPoromechanicsFractures/ThermoPoroElastic_conforming_base.xml @@ -0,0 +1,216 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inputFiles/thermoPoromechanicsFractures/ThermoPoroElastic_conforming_benchmark.xml b/inputFiles/thermoPoromechanicsFractures/ThermoPoroElastic_conforming_benchmark.xml new file mode 100644 index 00000000000..527bd7f653e --- /dev/null +++ b/inputFiles/thermoPoromechanicsFractures/ThermoPoroElastic_conforming_benchmark.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inputFiles/thermoPoromechanicsFractures/ThermoPoroElastic_conforming_smoke.xml b/inputFiles/thermoPoromechanicsFractures/ThermoPoroElastic_conforming_smoke.xml new file mode 100644 index 00000000000..4160d571cc9 --- /dev/null +++ b/inputFiles/thermoPoromechanicsFractures/ThermoPoroElastic_conforming_smoke.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inputFiles/thermoPoromechanicsFractures/ThermoPoroElastic_efem-edfm_base.xml b/inputFiles/thermoPoromechanicsFractures/ThermoPoroElastic_efem-edfm_base.xml new file mode 100644 index 00000000000..e44353d70c0 --- /dev/null +++ b/inputFiles/thermoPoromechanicsFractures/ThermoPoroElastic_efem-edfm_base.xml @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inputFiles/thermoPoromechanicsFractures/ThermoPoroElastic_efem-edfm_eggModel_small.xml b/inputFiles/thermoPoromechanicsFractures/ThermoPoroElastic_efem-edfm_eggModel_small.xml new file mode 100644 index 00000000000..2e83bc82161 --- /dev/null +++ b/inputFiles/thermoPoromechanicsFractures/ThermoPoroElastic_efem-edfm_eggModel_small.xml @@ -0,0 +1,374 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inputFiles/thermoPoromechanicsFractures/ThermoPoroElastic_efem-edfm_verticalFrac_base.xml b/inputFiles/thermoPoromechanicsFractures/ThermoPoroElastic_efem-edfm_verticalFrac_base.xml new file mode 100644 index 00000000000..d318b18154d --- /dev/null +++ b/inputFiles/thermoPoromechanicsFractures/ThermoPoroElastic_efem-edfm_verticalFrac_base.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + diff --git a/inputFiles/thermoPoromechanicsFractures/ThermoPoroElastic_efem-edfm_verticalFrac_benchmark.xml b/inputFiles/thermoPoromechanicsFractures/ThermoPoroElastic_efem-edfm_verticalFrac_benchmark.xml new file mode 100644 index 00000000000..86c38ff3b15 --- /dev/null +++ b/inputFiles/thermoPoromechanicsFractures/ThermoPoroElastic_efem-edfm_verticalFrac_benchmark.xml @@ -0,0 +1,142 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inputFiles/thermoPoromechanicsFractures/ThermoPoroElastic_efem-edfm_verticalFrac_smoke.xml b/inputFiles/thermoPoromechanicsFractures/ThermoPoroElastic_efem-edfm_verticalFrac_smoke.xml new file mode 100644 index 00000000000..ffa2948258d --- /dev/null +++ b/inputFiles/thermoPoromechanicsFractures/ThermoPoroElastic_efem-edfm_verticalFrac_smoke.xml @@ -0,0 +1,145 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inputFiles/wavePropagation/acous3D_firstOrder_small_base.xml b/inputFiles/wavePropagation/acous3D_firstOrder_small_base.xml index 9c66635f657..34bd0ac3c40 100644 --- a/inputFiles/wavePropagation/acous3D_firstOrder_small_base.xml +++ b/inputFiles/wavePropagation/acous3D_firstOrder_small_base.xml @@ -24,7 +24,7 @@ diff --git a/integratedTests b/integratedTests index 530ea7e5e5e..68983085a90 160000 --- a/integratedTests +++ b/integratedTests @@ -1 +1 @@ -Subproject commit 530ea7e5e5e977f4759c35a78fe21ca7929204d7 +Subproject commit 68983085a90739c871bc55e3be00d7d9044cc5ad diff --git a/scripts/SchemaToRSTDocumentation.py b/scripts/SchemaToRSTDocumentation.py index 97fcf83b736..2dcd03d6f95 100644 --- a/scripts/SchemaToRSTDocumentation.py +++ b/scripts/SchemaToRSTDocumentation.py @@ -17,7 +17,7 @@ def writeTableRST(file_name, values): L = [[len(x) for x in row] for row in values] # M = tuple(np.amax(np.array(L), axis=0)) - # np isn't in the docker images for travisCI + # np isn't in the docker images for our CI MAX = [None] * len(L[0]) for ii in range(0, len(L[0])): MAX[ii] = 0 diff --git a/scripts/ci_build_and_test.sh b/scripts/ci_build_and_test.sh new file mode 100755 index 00000000000..d2c09d6bd5c --- /dev/null +++ b/scripts/ci_build_and_test.sh @@ -0,0 +1,36 @@ +#!/bin/bash +env + +# The linux build relies on two environment variables DOCKER_REPOSITORY and GEOSX_TPL_TAG to define the TPL version. +# And another CMAKE_BUILD_TYPE to define the build type we want for GEOSX. +# Optional BUILD_AND_TEST_ARGS to pass arguments to build_test_helper.sh script. +# +# We extract the location of the GEOSX_TPL from the container... +GEOSX_TPL_DIR=$(docker run --rm ${DOCKER_REPOSITORY}:${GEOSX_TPL_TAG} /bin/bash -c 'echo ${GEOSX_TPL_DIR}' | tail -1) +# ... so we can install GEOSX alongside. This is assumed for bundling the binaries, so consider modifying with care. +GEOSX_DIR=${GEOSX_TPL_DIR}/../GEOSX-${GITHUB_SHA:0:7} +# We need to get the build directory +BUILD_DIR=${GITHUB_WORKSPACE} +# We need to know where the code folder is mounted inside the container so we can run the script at the proper location! +# Since this information is repeated twice, we use a variable. +BUILD_DIR_MOUNT_POINT=/tmp/GEOSX + + +# We need to keep track of the building container (hence the `CONTAINER_NAME`) +# so we can extract the data from it later (if needed). Another solution would have been to use a mount point, +# but that would not have solved the problem for the TPLs (we would require extra action to copy them to the mount point). +CONTAINER_NAME=geosx_build +# Now we can build GEOSX. +docker run \ + --name=${CONTAINER_NAME} \ + --volume=${BUILD_DIR}:${BUILD_DIR_MOUNT_POINT} \ + --cap-add=ALL \ + -e HOST_CONFIG=${HOST_CONFIG:-host-configs/environment.cmake} \ + -e CMAKE_BUILD_TYPE \ + -e GEOSX_DIR=${GEOSX_DIR} \ + -e ENABLE_HYPRE=${ENABLE_HYPRE:-OFF} \ + -e ENABLE_HYPRE_DEVICE=${ENABLE_HYPRE_DEVICE:-CPU} \ + -e ENABLE_TRILINOS=${ENABLE_TRILINOS:-ON} \ + ${DOCKER_REPOSITORY}:${GEOSX_TPL_TAG} \ + ${BUILD_DIR_MOUNT_POINT}/scripts/ci_build_and_test_in_container.sh ${BUILD_AND_TEST_ARGS}; + diff --git a/scripts/travis_build_and_test.sh b/scripts/ci_build_and_test_in_container.sh similarity index 97% rename from scripts/travis_build_and_test.sh rename to scripts/ci_build_and_test_in_container.sh index b3c031bde3e..4c3b8431b3b 100755 --- a/scripts/travis_build_and_test.sh +++ b/scripts/ci_build_and_test_in_container.sh @@ -50,7 +50,7 @@ fi # # The option `--oversubscribe` tells OpenMPI to allow more MPI ranks than the node has cores. # This is needed because our unit test `blt_mpi_smoke` is run in parallel with _hard coded_ 4 ranks. -# While our travis-ci nodes only have 2. +# While some of our ci nodes may have less cores available. # # In case we have more powerful nodes, consider removing `--oversubscribe` and use `--use-hwthread-cpus` instead. # This will tells OpenMPI to discover the number of hardware threads on the node, diff --git a/scripts/createRectangle.py b/scripts/createRectangle.py new file mode 100644 index 00000000000..a85b26e1eff --- /dev/null +++ b/scripts/createRectangle.py @@ -0,0 +1,137 @@ +import numpy as np +import xml.etree.ElementTree as ET +import matplotlib.pyplot as plt +from mpl_toolkits.mplot3d import Axes3D +from geosx_xml_tools import xml_formatter as xmlFormatter + + +def arrayToXMLArray( array ): + stringArray = '{%s}' % ', '.join(['%1.10f' % (x) for x in array]) + return stringArray + +class Rectangle: + + def __init__(self, *args ) -> None: + self.name = args[0] + if len(args) == 4: + self.normal = np.array( compute_normal_fromAngles( args[1], args[2] ), dtype=float ) + self.origin = np.array( args[3], dtype=float ) + self.strikeAngle = args[1] + self.dipAngle = args[2] + elif len(args) == 3: + self.normal = np.array( args[1], dtype=float ) + self.origin = np.array( args[2], dtype=float ) + self.strikeAngle = -1000 + self.dipAngle = -1000 + else: + print("Error: too many arguments to constructor") + + self.lengthVector = np.array([0.0, 0.0, 0.0], dtype=float ) + self.widthVector = np.array([0.0, 0.0, 0.0], dtype=float ) + self.dimensions = np.array([0.0, 0.0], dtype=float ) + self.compute_tangentVectors() + + def compute_tangentVectors( self ) -> None: + self.lengthVector = np.array( np.cross( self.normal, np.array([0.0,1.0,0.0]) ), dtype=float ) + self.lengthVector = self.lengthVector / np.linalg.norm(self.lengthVector) + self.widthVector = np.array( np.cross( self.normal, self.lengthVector ), dtype=float ) + self.widthVector = self.widthVector / np.linalg.norm(self.widthVector) + + assert np.allclose([1,1,1], [self.normal.dot(self.normal), self.lengthVector.dot(self.lengthVector), self.widthVector.dot(self.widthVector)]) + assert np.allclose([0,0,0], [self.normal.dot(self.lengthVector), self.lengthVector.dot(self.widthVector), self.normal.dot(self.widthVector)]) + + def print_XML_block(self): + + root = ET.Element("geos") + element = ET.SubElement(root, "Rectangle") + element.set( "name" , self.name ) + element.set( "normal", arrayToXMLArray( self.normal ) ) + element.set( "origin", arrayToXMLArray( self.origin ) ) + element.set( "lengthVector", arrayToXMLArray( self.lengthVector ) ) + element.set( "widthVector", arrayToXMLArray( self.widthVector ) ) + element.set( "dimensions", arrayToXMLArray( self.dimensions ) ) + + from xml.dom import minidom + + xmlstr = minidom.parseString(ET.tostring(root)).toprettyxml(indent=" ") + fileName = self.name + "_" + str(self.strikeAngle)+ "_" + str(self.dipAngle) + ".xml" + f = open( fileName, "w" ) + f.write( xmlstr ) + f.close() + xmlFormatter.format_file( fileName ) + +def plotPlane(xx, yy, plane): + # a plane is a*x+b*y+c*z+d=0 + # [a,b,c] is the normal. Thus, we have to calculate + # d and we're set + d = -plane.origin.dot(plane.normal) + + # calculate corresponding z + z = (-plane.normal[0] * xx - plane.normal[1] * yy - d) * 1. /plane.normal[2] + + # plot the surface + title = str(plane.strikeAngle) + " - " + str(plane.dipAngle) + fig = plt.figure( title ) + plt3d = fig.add_subplot(111, projection='3d') + plt3d.plot_surface(xx, yy, z) + plt3d.set_zlim3d(0, 140) + +def plot(planes): + + # create x,y + xx, yy = np.meshgrid(range(146), range(75)) + + for plane in planes: + plotPlane(xx, yy, plane) + +def compute_normal_fromTriangleVertices( points ): + V = points[1] - points[0] + W = points[2] - points[0] + normal = np.cross( V, W ) + + unit_normal = normal / np.linalg.norm(normal) + + print(unit_normal) + print(np.linalg.norm(unit_normal)) + + return unit_normal + +def compute_normal_fromAngles( strikeAngle, dipAngle ): + ''' + Function to compute the plane unit normal vector given the strike and dip angles + (in degrees). The strike should lie between 0 and 360 (negative ok) and the + dip is restricted to lie between 0 and 90 measured in the direction such that + when you look in the strike direction, the fault dips to your right. + ''' + deg_to_rad = np.pi/180 + strike = strikeAngle * deg_to_rad + dip = dipAngle * deg_to_rad + normal = [0.0, 0.0, 0.0] + normal[0] = -np.sin(dip)*np.sin(strike) # x component + normal[1] = np.sin(dip)*np.cos(strike) # y component + normal[2] = -np.cos(dip) # z component + + return normal + +def main(): + origin = [40, 37.5, 10] + planes = [] + planes.append(Rectangle("FracturePlane", -20, 60, origin)) + + # Vertex # 1; X, m: 1.2026E+03; Y, m: -8.5871E+02; Z, m: 3.3080E+02 + # Vertex # 2; X, m: 1.2038E+03; Y, m: -8.5901E+02; Z, m: 3.3003E+02 + # Vertex # 3; X, m: 1.2027E+03; Y, m: -8.5843E+02; Z, m: 3.2935E+02 + points = [ np.array([1.2026E+03, -8.5871E+02, 3.3080E+02]), + np.array([1.2038E+03, -8.5901E+02, 3.3003E+02]), + np.array([1.2027E+03, -8.5843E+02, 3.2935E+02]) ] + + planes.append(Rectangle("FracturePlane", compute_normal_fromTriangleVertices( points ), origin)) + plot(planes) + for plane in planes: + plane.print_XML_block() + plt.show() + +if __name__ == "__main__": + main() + + \ No newline at end of file diff --git a/scripts/postProcessing/SneddonValidation.py b/scripts/postProcessing/SneddonValidation.py index b9449fbcb33..c49d4b4c254 100644 --- a/scripts/postProcessing/SneddonValidation.py +++ b/scripts/postProcessing/SneddonValidation.py @@ -59,12 +59,12 @@ def getFracturePressureFromXML(xmlFilePath): def getFractureLengthFromXML(xmlFilePath): tree = ElementTree.parse(xmlFilePath) - boundedPlane = tree.find('Geometry/BoundedPlane') - dimensions = boundedPlane.get("dimensions") + rectangle = tree.find('Geometry/Rectangle') + dimensions = rectangle.get("dimensions") dimensions = [float(i) for i in dimensions[1:-1].split(",")] length = dimensions[0] / 2 - origin = boundedPlane.get("origin") + origin = rectangle.get("origin") origin = [float(i) for i in origin[1:-1].split(",")] return length, origin[0] diff --git a/scripts/postProcessing/plotsolution_thermalFlow.py b/scripts/postProcessing/plotsolution_thermalFlow.py new file mode 100644 index 00000000000..a7fc5695da3 --- /dev/null +++ b/scripts/postProcessing/plotsolution_thermalFlow.py @@ -0,0 +1,118 @@ +import matplotlib +import matplotlib.pyplot as plt +import numpy as np +import h5py +import xml.etree.ElementTree as ElementTree +from mpmath import * +import math +import argparse +import os + + +def main(directory): + # File paths + filename = "temperature_history.hdf5" + hdf5FilePathTemperature = os.path.join( directory, filename ) + + # Read simulation output from HDF5 file + hf = h5py.File(hdf5FilePathTemperature, 'r') + timeTemperature = hf.get('temperature Time') + timeTemperature = np.array(timeTemperature) + centerTemperature = hf.get('temperature elementCenter') + centerTemperature = np.array(centerTemperature) + xcord_elm = centerTemperature[0,:,0] + ycord_elm = centerTemperature[0,:,1] + zcord_elm = centerTemperature[0,:,2] + temperature = hf.get('temperature') + temperature = np.array(temperature) + print(ycord_elm) + + filename = "pressure_history.hdf5" + hdf5FilePathPressure = os.path.join( directory, filename ) + + hf = h5py.File(hdf5FilePathPressure, 'r') + pressure = np.array( hf.get('pressure') ) + + t_index = [0, 1, 2, 9] + tstar = [0, 100, 200, 900] + numFiles=len(tstar) + # Extract Curve + midY = 10 + xlist = [] + pplist = [] + temperaturelist = [] + for tt in range(len(t_index)): + xtemp = [] + ptemp = [] + ttemp = [] + for i in range(0,len(zcord_elm)): + if abs(ycord_elm[i] - midY) < 0.01: + print(i) + print(xcord_elm[i], pressure[tt,i]/1.0e6, temperature[tt,i]) + xtemp.append(xcord_elm[i]) + ptemp.append(pressure[tt,i]/1.0e6) + ttemp.append(temperature[tt,i]) + + xlist.append(xtemp) + pplist.append(ptemp) + temperaturelist.append(ttemp) + + xlist = np.array(xlist) + pplist = np.array(pplist) + temperaturelist = np.array(temperaturelist) + + + #Visualization + N1 = 1 + fsize = 32 + msize = 10 + lw = 4 + mew = 2 + malpha = 0.6 + lalpha = 0.8 + + fig, ax = plt.subplots(2, 2, figsize=(32, 18)) + colorlist = ['lime', 'orangered', 'black', 'deepskyblue'] + markerlist = ['o', 's', 'P', 'D'] + + for i in range(0,numFiles): + ax[0,0].plot(xlist[i][0::N1], pplist[i][0::N1], linestyle='None', marker=markerlist[i], alpha=0.4, markerfacecolor=colorlist[i], fillstyle='full', markersize=msize, label='GEOS_t='+str(tstar[i])+'s') + #ax[0,0].set_xlim([1, 10]) + #ax[0,0].set_ylim([-1, 11]) + ax[0,0].set_xlabel(r'r_d', size=fsize, weight="bold") + ax[0,0].set_ylabel(r'Pore Pressure (MPa)', size=fsize, weight="bold") + ax[0,0].legend(loc='upper right',fontsize=fsize*0.8) + ax[0,0].grid(True, which="both", ls="-") + ax[0,0].xaxis.set_tick_params(labelsize=fsize) + ax[0,0].yaxis.set_tick_params(labelsize=fsize) + + + for i in range(0,numFiles): + ax[1,0].plot(xlist[i][0::N1], temperaturelist[i][0::N1], linestyle='None', marker=markerlist[i], alpha=0.4, markerfacecolor=colorlist[i], fillstyle='full', markersize=msize, label='GEOS_t='+str(tstar[i])+'s') + #ax[1,0].set_xlim([1, 10]) + #ax[1,0].set_ylim([-1, 11]) + ax[1,0].set_xlabel(r'r_d', size=fsize, weight="bold") + ax[1,0].set_ylabel(r'Temperature (K)', size=fsize, weight="bold") + ax[1,0].legend(loc='upper right',fontsize=fsize*0.8) + ax[1,0].grid(True, which="both", ls="-") + ax[1,0].xaxis.set_tick_params(labelsize=fsize) + ax[1,0].yaxis.set_tick_params(labelsize=fsize) + + plt.subplots_adjust(left=0.1, bottom=0.1, right=0.9, top=0.9, wspace=0.4, hspace=0.4) + + fig.savefig('Verification_tutorial.png') + + plt.show() + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Plot solution of fractureMatrixThermalFlow") + parser.add_argument("-d", "--workingDir", type=str, help="Directory containing the solution") + + args, unknown_args = parser.parse_known_args() + if unknown_args: + print("unknown arguments %s" % unknown_args) + + directory = args.directory + + main( directory ) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 22053ac1830..abd2bb1e1fb 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,8 +1,8 @@ -cmake_minimum_required( VERSION 3.9 ) +cmake_minimum_required( VERSION 3.23 ) # At the moment we are manually passing the cuda arch flag. -if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.18.0") +if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.23.0") cmake_policy(SET CMP0104 OLD) # when using nvcc populate CMAKE_CUDA_ARCHITECTURES, raise error if we can't endif() cmake_policy(SET CMP0074 NEW) # dont ignore _ROOT env vars when searching for packages via find_package() @@ -14,7 +14,7 @@ cmake_policy(SET CMP0056 NEW) # use CMAKE_EXE_LINKER_FLAGS in try_compile() in a ################################ project( geosx LANGUAGES C CXX ) include(GNUInstallDirs) -set( BLT_CXX_STD "c++14" CACHE STRING "Version of C++ standard" ) +set( BLT_CXX_STD "c++17" CACHE STRING "Version of C++ standard" ) if(CMAKE_BUILD_TYPE EQUAL "Debug") set( ENABLE_WARNINGS_AS_ERRORS "OFF" CACHE PATH "") else() @@ -161,6 +161,11 @@ set_target_properties( geosx PROPERTIES INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/l set_target_properties( geosx PROPERTIES INSTALL_RPATH_USE_LINK_PATH TRUE ) install(TARGETS geosx RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}/bin) +# Duplicate the installed `geosx` executable and rename it `geos` to let the end users adapt their scripts. +install( FILES ${CMAKE_INSTALL_PREFIX}/bin/geosx + DESTINATION ${CMAKE_INSTALL_PREFIX}/bin + RENAME geos + PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE ) if( ENABLE_XML_UPDATES AND ENABLE_MPI AND UNIX AND NOT CMAKE_HOST_APPLE AND NOT ENABLE_CUDA AND NOT ENABLE_HIP ) diff --git a/src/cmake/GeosxOptions.cmake b/src/cmake/GeosxOptions.cmake index c905f6fe149..2b7ba6887c6 100644 --- a/src/cmake/GeosxOptions.cmake +++ b/src/cmake/GeosxOptions.cmake @@ -114,9 +114,9 @@ endif() #message( "SPHINX_FOUND = ${SPHINX_FOUND}" ) #message( "SPHINX_EXECUTABLE = ${SPHINX_EXECUTABLE}" ) -if( NOT BLT_CXX_STD STREQUAL c++14 ) - MESSAGE( FATAL_ERROR "c++14 is NOT enabled. GEOSX requires c++14" ) -endif( NOT BLT_CXX_STD STREQUAL c++14 ) +if( NOT BLT_CXX_STD STREQUAL c++17 ) + MESSAGE( FATAL_ERROR "c++17 is NOT enabled. GEOSX requires c++17" ) +endif( NOT BLT_CXX_STD STREQUAL c++17 ) message( "CMAKE_CXX_COMPILER_ID = ${CMAKE_CXX_COMPILER_ID}" ) @@ -150,14 +150,6 @@ if( ${CMAKE_MAKE_PROGRAM} STREQUAL "ninja" OR ${CMAKE_MAKE_PROGRAM} MATCHES ".*/ set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${GEOSX_NINJA_FLAGS}" ) endif() -#set(CMAKE_CUDA_STANDARD 14 CACHE STRING "" FORCE) -#blt_append_custom_compiler_flag( FLAGS_VAR CMAKE_CUDA_FLAGS_RELEASE -# DEFAULT "-O3 -DNDEBUG -Xcompiler -DNDEBUG -Xcompiler -O3" ) -#blt_append_custom_compiler_flag( FLAGS_VAR CMAKE_CUDA_FLAGS_RELWITHDEBINFO -# DEFAULT "-lineinfo ${CMAKE_CUDA_FLAGS_RELEASE}" ) -#blt_append_custom_compiler_flag( FLAGS_VAR CMAKE_CUDA_FLAGS_DEBUG -# DEFAULT "-G -O0 -Xcompiler -O0" ) - if( CMAKE_HOST_APPLE ) # set(GEOSX_LINK_PREPEND_FLAG "-Wl,-force_load" CACHE STRING "") diff --git a/src/cmake/blt b/src/cmake/blt index 655aa8c7987..5a792c1775e 160000 --- a/src/cmake/blt +++ b/src/cmake/blt @@ -1 +1 @@ -Subproject commit 655aa8c7987eca99d21408745dda1baa2de5de76 +Subproject commit 5a792c1775e7a7628d84dcde31652a689f1df7b5 diff --git a/src/conf.py b/src/conf.py index be390e95da2..85dec399e50 100644 --- a/src/conf.py +++ b/src/conf.py @@ -151,7 +151,10 @@ # -- Options for HTML output ------------------------------------------------- try: - import sphinx_rtd_theme + if read_the_docs_build: + import sphinx_rtd_theme + else: + import pydata_sphinx_theme except: html_theme = 'classic' html_theme_options = { @@ -160,9 +163,16 @@ } html_theme_path = [] else: - html_theme = 'sphinx_rtd_theme' - html_theme_options = {} - html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] + html_theme = "pydata_sphinx_theme" + html_theme_options = {'navigation_depth': -1, 'collapse_navigation': False} + html_theme_path = [ + "_themes", + ] + + if read_the_docs_build: + html_theme = 'sphinx_rtd_theme' + html_theme_options = {} + html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". diff --git a/src/coreComponents/LvArray b/src/coreComponents/LvArray index ea8c2787f80..bc43fa5f761 160000 --- a/src/coreComponents/LvArray +++ b/src/coreComponents/LvArray @@ -1 +1 @@ -Subproject commit ea8c2787f80bca38648a26d79a59dc8e26244110 +Subproject commit bc43fa5f76174475e2f3f9af706e379350779244 diff --git a/src/coreComponents/codingUtilities/Utilities.hpp b/src/coreComponents/codingUtilities/Utilities.hpp index 256391fff4e..ba3749d793b 100644 --- a/src/coreComponents/codingUtilities/Utilities.hpp +++ b/src/coreComponents/codingUtilities/Utilities.hpp @@ -200,7 +200,10 @@ VAL findOption( mapBase< KEY, VAL, SORTED > const & map, auto const iter = map.find( option ); GEOS_THROW_IF( iter == map.end(), GEOS_FMT( "{}: unsupported option '{}' for {}.\nSupported options are: {}", - contextName, option, optionName, stringutilities::join( mapKeys( map ), ", " ) ), + contextName, + const_cast< typename std::remove_const< KEY & >::type >( option ), + optionName, + stringutilities::join( mapKeys( map ), ", " ) ), InputError ); return iter->second; } diff --git a/src/coreComponents/codingUtilities/tests/testGeosxTraits.cpp b/src/coreComponents/codingUtilities/tests/testGeosxTraits.cpp index 794658d1b77..dec8e43fa8d 100644 --- a/src/coreComponents/codingUtilities/tests/testGeosxTraits.cpp +++ b/src/coreComponents/codingUtilities/tests/testGeosxTraits.cpp @@ -90,7 +90,7 @@ TEST( testGeosxTraits, Pointer ) static_assert( std::is_same< Pointer< int >, int * >::value, "Should be true." ); static_assert( std::is_same< Pointer< R1Tensor >, R1Tensor * >::value, "Should be true." ); static_assert( std::is_same< Pointer< std::vector< double > >, double * >::value, "Should be true." ); - static_assert( std::is_same< Pointer< string >, char const * >::value, "Should be true." ); + static_assert( std::is_same< Pointer< string >, char * >::value, "Should be true." ); static_assert( std::is_same< Pointer< array3d< string > >, string * >::value, "Should be true." ); static_assert( std::is_same< Pointer< SortedArray< float > >, float const * >::value, "Should be true." ); diff --git a/src/coreComponents/common/CMakeLists.txt b/src/coreComponents/common/CMakeLists.txt index b05e332db6d..e4b7580c11a 100644 --- a/src/coreComponents/common/CMakeLists.txt +++ b/src/coreComponents/common/CMakeLists.txt @@ -19,10 +19,18 @@ set( common_headers TimingMacros.hpp TypeDispatch.hpp initializeEnvironment.hpp + LifoStorage.hpp + LifoStorageCommon.hpp + LifoStorageHost.hpp + FixedSizeDeque.hpp + FixedSizeDequeWithMutexes.hpp + MultiMutexesLock.hpp ) + if ( ENABLE_CUDA ) - list( APPEND common_headers lifoStorage.hpp ) -endif() + list( APPEND common_headers LifoStorageCuda.hpp ) +endif( ) + # # Specify all sources # diff --git a/src/coreComponents/common/fixedSizeDeque.hpp b/src/coreComponents/common/FixedSizeDeque.hpp similarity index 88% rename from src/coreComponents/common/fixedSizeDeque.hpp rename to src/coreComponents/common/FixedSizeDeque.hpp index 303d91baf18..ea61af49a44 100644 --- a/src/coreComponents/common/fixedSizeDeque.hpp +++ b/src/coreComponents/common/FixedSizeDeque.hpp @@ -16,6 +16,7 @@ #include "LvArray/src/Array.hpp" #include "LvArray/src/memcpy.hpp" +#include "LvArray/src/ChaiBuffer.hpp" #include "common/Logger.hpp" /// Get the positive value of a module b @@ -24,14 +25,14 @@ namespace geos { template< typename T, typename INDEX_TYPE > /// Implement a double ended queue with fixed number of fixed size buffer to be stored -class fixedSizeDeque +class FixedSizeDeque { /// The integer type used for indexing. using IndexType = INDEX_TYPE; /// 1D array slice - using ArraySlice1D = LvArray::ArraySlice< T const, 1, 0, INDEX_TYPE >; + using ArraySlice1DLarge = LvArray::ArraySlice< T const, 1, 0, std::ptrdiff_t >; /// 2D array type. See LvArray:Array for details. - using Array2D = LvArray::Array< T, 2, camp::make_idx_seq_t< 2 >, IndexType, LvArray::ChaiBuffer >; + using Array2D = LvArray::Array< T, 2, camp::make_idx_seq_t< 2 >, std::ptrdiff_t, LvArray::ChaiBuffer >; public: /** * Create a fixed size double ended queue. @@ -41,7 +42,7 @@ class fixedSizeDeque * @param space Space used to store que queue. * @param stream Camp resource to perform the copies. */ - fixedSizeDeque( IndexType maxEntries, IndexType valuesPerEntry, LvArray::MemorySpace space, camp::resources::Resource stream ): + FixedSizeDeque( IndexType maxEntries, IndexType valuesPerEntry, LvArray::MemorySpace space, camp::resources::Resource stream ): m_stream( stream ) { GEOS_ERROR_IF( maxEntries < 0, "Fixed sized queue size must be positive" ); @@ -74,28 +75,28 @@ class fixedSizeDeque } /// @returns the first array in the queue - ArraySlice1D front() const + ArraySlice1DLarge front() const { GEOS_ERROR_IF( empty(), "Can't get front from empty queue" ); return m_storage[ POSITIVE_MODULO( m_begin, m_storage.size( 0 ) ) ]; } /// @returns the future first array in the queue after inc_front will be called - ArraySlice1D next_front() const + ArraySlice1DLarge next_front() const { GEOS_ERROR_IF( full(), "Can't increase in a full queue" ); return m_storage[ POSITIVE_MODULO( m_begin-1, m_storage.size( 0 ) ) ]; } /// @returns the last array of the queue - ArraySlice1D back() const + ArraySlice1DLarge back() const { GEOS_ERROR_IF( empty(), "Can't get back from empty queue" ); return m_storage[ POSITIVE_MODULO( m_end, m_storage.size( 0 ) ) ]; } /// @returns the future last array of the queue when inc_back will be called - ArraySlice1D next_back() const + ArraySlice1DLarge next_back() const { GEOS_ERROR_IF( full(), "Can't increase in a full queue" ); return m_storage[ POSITIVE_MODULO( m_end+1, m_storage.size( 0 ) ) ]; @@ -135,7 +136,8 @@ class fixedSizeDeque * @param src Array to emplace at the front of the queue * @return Event associated to the copy. */ - camp::resources::Event emplace_front( const ArraySlice1D & src ) + template< typename INDEX_TYPE2 > + camp::resources::Event emplace_front( const LvArray::ArraySlice< T const, 1, 0, INDEX_TYPE2 > & src ) { GEOS_ERROR_IF( full(), "Can't emplace in a full queue" ); camp::resources::Event e = LvArray::memcpy( m_stream, m_storage[ POSITIVE_MODULO( m_begin-1, m_storage.size( 0 ) ) ], src ); @@ -149,7 +151,8 @@ class fixedSizeDeque * @param src Array to emplace at the end of the queue * @return Event associated to the copy. */ - camp::resources::Event emplace_back( const ArraySlice1D & src ) + template< typename INDEX_TYPE2 > + camp::resources::Event emplace_back( const LvArray::ArraySlice< T const, 1, 0, INDEX_TYPE2 > & src ) { GEOS_ERROR_IF( full(), "Can't emplace in a full queue" ); camp::resources::Event e = LvArray::memcpy( m_stream, m_storage[ POSITIVE_MODULO( m_end+1, m_storage.size( 0 ) ) ], src ); diff --git a/src/coreComponents/common/FixedSizeDequeWithMutexes.hpp b/src/coreComponents/common/FixedSizeDequeWithMutexes.hpp new file mode 100644 index 00000000000..e6532dc1392 --- /dev/null +++ b/src/coreComponents/common/FixedSizeDequeWithMutexes.hpp @@ -0,0 +1,167 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2018-2020 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2020 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2018-2020 TotalEnergies + * Copyright (c) 2019- GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ +#ifndef FIXEDSIZEDEQUEWITHMUTEXES_HPP +#define FIXEDSIZEDEQUEWITHMUTEXES_HPP + +#include +#include +#include +#include + +#include "common/FixedSizeDeque.hpp" +#include "common/MultiMutexesLock.hpp" + +namespace geos +{ +/// Associate mutexes with the fixedSizeDeque +template< typename T, typename INDEX_TYPE > +class FixedSizeDequeWithMutexes : public FixedSizeDeque< T, INDEX_TYPE > +{ +public: + /// Mutex to protect access to the front + std::mutex m_frontMutex; + /// Mutex to protect access to the back + std::mutex m_backMutex; + /// Mutex to prevent two simulteaneous pop (can be an issue for last one) + std::mutex m_popMutex; + /// Mutex to prevent two simulteaneous emplace (can be an issue for last one) + std::mutex m_emplaceMutex; + /// Condition used to notify when queue is not full + std::condition_variable_any m_notFullCond; + /// Condition used to notify when queue is not empty + std::condition_variable_any m_notEmptyCond; + + /** + * Create a fixed size double ended queue with associated mutexes and condition variables. + * + * @param maxEntries Maximum number of array to store in the queue. + * @param valuesPerEntry Number of values in each array of the deque. + * @param space Space used to store que queue. + */ + FixedSizeDequeWithMutexes( int maxEntries, int valuesPerEntry, LvArray::MemorySpace space ): FixedSizeDeque< T, INDEX_TYPE >( maxEntries, valuesPerEntry, space, +#ifdef GEOSX_USE_CUDA + camp::resources::Resource{ camp::resources::Cuda{} } +#else + camp::resources::Resource{ camp::resources::Host{} } +#endif + ) {} + + /** + * Emplace on front from array with locks. + * + * @param array The array to emplace + * @returns Event to sync with the memcpy. + */ + camp::resources::Event emplaceFront( arrayView1d< T > array ) + { + LIFO_MARK_FUNCTION; + camp::resources::Event e; + { + auto lock = make_multilock( m_emplaceMutex, m_frontMutex ); + { + LIFO_MARK_SCOPE( waitingForBuffer ); + m_notFullCond.wait( lock, [ this ] { return !this->full(); } ); + } + { + LIFO_MARK_SCOPE( copy ); + e = FixedSizeDeque< T, INDEX_TYPE >::emplace_front( array.toSliceConst() ); + } + } + m_notEmptyCond.notify_all(); + return e; + } + + /** + * Pop from front to array with locks + * + * @param array The array to copy data into + * @returns Event to sync with the memcpy. + */ + camp::resources::Event popFront( arrayView1d< T > array ) + { + LIFO_MARK_FUNCTION; + camp::resources::Event e; + { + auto lock = make_multilock( m_popMutex, m_frontMutex ); + { + LIFO_MARK_SCOPE( waitingForBuffer ); + m_notEmptyCond.wait( lock, [ this ] { return !this->empty(); } ); + } + // deadlock can occur if frontMutex is taken after an + // emplaceMutex (inside pushAsync) but this is prevented by the + // pushWait() in popAsync. + { + LIFO_MARK_SCOPE( copy ); + camp::resources::Resource r = this->getStream(); + e = LvArray::memcpy( r, array.toSlice(), this->front() ); + this->pop_front(); + } + } + m_notFullCond.notify_all(); + return e; + } + + /** + * Emplace front from back of given queue + * + * @param q2 The queue to copy data from. + */ + void emplaceFrontFromBack( FixedSizeDequeWithMutexes< T, INDEX_TYPE > & q2 ) + { + LIFO_MARK_FUNCTION; + { + auto lock = make_multilock( m_emplaceMutex, q2.m_popMutex, m_frontMutex, q2.m_backMutex ); + while( this->full() || q2.empty() ) + { + { + LIFO_MARK_SCOPE( WaitForBufferToEmplace ); + m_notFullCond.wait( lock, [ this ] { return !this->full(); } ); + } + { + LIFO_MARK_SCOPE( WaitForBufferToPop ); + q2.m_notEmptyCond.wait( lock, [ &q2 ] { return !q2.empty(); } ); + } + } + LIFO_MARK_SCOPE( Transfert ); + this->emplace_front( q2.back() ).wait(); + q2.pop_back(); + } + q2.m_notFullCond.notify_all(); + m_notEmptyCond.notify_all(); + } + + /** + * Emplace back from front of given queue + * + * @param q2 The queue to copy data from. + */ + void emplaceBackFromFront( FixedSizeDequeWithMutexes< T, INDEX_TYPE > & q2 ) + { + LIFO_MARK_FUNCTION; + { + auto lock = make_multilock( m_emplaceMutex, q2.m_popMutex, m_backMutex, q2.m_frontMutex ); + while( this->full() || q2.empty() ) + { + m_notFullCond.wait( lock, [ this ] { return !this->full(); } ); + q2.m_notEmptyCond.wait( lock, [ &q2 ] { return !q2.empty(); } ); + } + this->emplace_back( q2.front() ).wait(); + q2.pop_front(); + } + m_notEmptyCond.notify_all(); + q2.m_notFullCond.notify_all(); + } +}; +} +#endif // FIXEDSIZEDEQUEWITHMUTEXES_HPP diff --git a/src/coreComponents/common/Format.hpp b/src/coreComponents/common/Format.hpp index bc3d1ade6a4..b548b76a20d 100644 --- a/src/coreComponents/common/Format.hpp +++ b/src/coreComponents/common/Format.hpp @@ -15,6 +15,8 @@ #ifndef GEOS_COMMON_FORMAT_HPP_ #define GEOS_COMMON_FORMAT_HPP_ +#include + #if __cplusplus < 202002L #define GEOSX_USE_FMT #endif @@ -30,6 +32,42 @@ #define GEOS_FMT_NS std #endif +#ifdef GEOSX_USE_FMT +/** + * @brief fmtlib formatter for enum classes. + * @tparam T The type of the object being formatted. This should be an + * enum class. + */ +template< typename T > +struct fmt::formatter< T, std::enable_if_t< std::is_enum< T >::value > > +{ + /** + * @brief Parser for the fmtlib formatting library. + * @param ctx The context provided by the fmtlib library, which includes + * the format string. + * @return An iterator pointing to the end of the format string. + */ + template< typename ParseContext > + constexpr auto parse( ParseContext & ctx ) + { + return ctx.end(); + } + + /** + * @brief Formatter for the fmtlib formatting library. + * @param value The enum class object to format. + * @param ctx The context provided by the fmtlib library, which includes + * the output iterator where the formatted string should be written. + * @return An iterator pointing to the end of the formatted string. + */ + template< typename FormatContext > + auto format( const T & value, FormatContext & ctx ) + { + return fmt::format_to( ctx.out(), "{}", static_cast< std::underlying_type_t< T > >( value ) ); + } +}; +#endif + /** * @brief Interpolate arguments into a message format string. * @param msg the message format string, must be a constant expression diff --git a/src/coreComponents/common/GEOS_RAJA_Interface.hpp b/src/coreComponents/common/GEOS_RAJA_Interface.hpp index 26f8023ed83..82a238f69e7 100644 --- a/src/coreComponents/common/GEOS_RAJA_Interface.hpp +++ b/src/coreComponents/common/GEOS_RAJA_Interface.hpp @@ -70,10 +70,10 @@ void RAJA_INLINE parallelHostSync() { } #if defined( GEOS_USE_CUDA ) auto const parallelDeviceMemorySpace = LvArray::MemorySpace::cuda; -template< unsigned long BLOCK_SIZE = GEOSX_BLOCK_SIZE > +template< size_t BLOCK_SIZE = GEOSX_BLOCK_SIZE > using parallelDevicePolicy = RAJA::cuda_exec< BLOCK_SIZE >; -template< unsigned long BLOCK_SIZE = GEOSX_BLOCK_SIZE > +template< size_t BLOCK_SIZE = GEOSX_BLOCK_SIZE > using parallelDeviceAsyncPolicy = RAJA::cuda_exec_async< BLOCK_SIZE >; using parallelDeviceStream = RAJA::resources::Cuda; @@ -96,7 +96,7 @@ RAJA_INLINE parallelDeviceEvent forAll( RESOURCE && stream, const localIndex end auto const parallelDeviceMemorySpace = LvArray::MemorySpace::hip; -template< unsigned long BLOCK_SIZE = GEOSX_BLOCK_SIZE > +template< size_t BLOCK_SIZE = GEOSX_BLOCK_SIZE > using parallelDevicePolicy = RAJA::hip_exec< BLOCK_SIZE >; @@ -109,8 +109,8 @@ using parallelDeviceAtomic = RAJA::hip_atomic; void RAJA_INLINE parallelDeviceSync() { RAJA::synchronize< RAJA::hip_synchronize >( ); } // the async dispatch policy caused runtime issues as of rocm@4.5.2, hasn't been checked in rocm@5: -template< unsigned long BLOCK_SIZE = GEOSX_BLOCK_SIZE > -using parallelDeviceAsyncPolicy = parallelDevicePolicy< BLOCK_SIZE >;// RAJA::hip_exec_async< BLOCK_SIZE >; +template< size_t BLOCK_SIZE = GEOSX_BLOCK_SIZE > +using parallelDeviceAsyncPolicy = parallelDevicePolicy< BLOCK_SIZE >; // RAJA::hip_exec_async< BLOCK_SIZE >; template< typename POLICY, typename RESOURCE, typename LAMBDA > RAJA_INLINE parallelDeviceEvent forAll( RESOURCE && GEOS_UNUSED_PARAM( stream ), const localIndex end, LAMBDA && body ) @@ -122,10 +122,10 @@ RAJA_INLINE parallelDeviceEvent forAll( RESOURCE && GEOS_UNUSED_PARAM( stream ), auto const parallelDeviceMemorySpace = parallelHostMemorySpace; -template< unsigned long BLOCK_SIZE = 0 > +template< size_t BLOCK_SIZE = 0 > using parallelDevicePolicy = parallelHostPolicy; -template< unsigned long BLOCK_SIZE = 0 > +template< size_t BLOCK_SIZE = 0 > using parallelDeviceAsyncPolicy = parallelHostPolicy; using parallelDeviceStream = parallelHostStream; @@ -150,7 +150,8 @@ using parallelDeviceEvents = std::vector< parallelDeviceEvent >; namespace internalRajaInterface { template< typename > -struct PolicyMap; +struct PolicyMap +{}; template<> struct PolicyMap< serialPolicy > @@ -169,8 +170,8 @@ struct PolicyMap< RAJA::omp_parallel_for_exec > #endif #if defined(GEOS_USE_CUDA) -template< unsigned long BLOCK_SIZE > -struct PolicyMap< RAJA::cuda_exec< BLOCK_SIZE > > +template< typename X, typename Y, size_t BLOCK_SIZE, bool ASYNC > +struct PolicyMap< RAJA::policy::cuda::cuda_exec_explicit< X, Y, BLOCK_SIZE, ASYNC > > { using atomic = RAJA::cuda_atomic; using reduce = RAJA::cuda_reduce; @@ -178,8 +179,8 @@ struct PolicyMap< RAJA::cuda_exec< BLOCK_SIZE > > #endif #if defined(GEOS_USE_HIP) -template< unsigned long BLOCK_SIZE > -struct PolicyMap< RAJA::hip_exec< BLOCK_SIZE > > +template< size_t BLOCK_SIZE, bool ASYNC > +struct PolicyMap< RAJA::hip_exec< BLOCK_SIZE, ASYNC > > { using atomic = RAJA::hip_atomic; using reduce = RAJA::hip_reduce; diff --git a/src/coreComponents/common/GeosxMacros.hpp b/src/coreComponents/common/GeosxMacros.hpp index 112856d5eb8..357b64bbfdb 100644 --- a/src/coreComponents/common/GeosxMacros.hpp +++ b/src/coreComponents/common/GeosxMacros.hpp @@ -111,4 +111,25 @@ void i_g_n_o_r_e( ARGS const & ... ) {} /// Macro to concatenate two tokens (user level) #define GEOS_CONCAT( A, B ) GEOS_CONCAT_IMPL( A, B ) +/** + * @brief [[maybe_unused]] when >= C++17, or compiler-specific implementations + * when < C++17 + */ +#if __cplusplus >= 201703L +#define GEOS_MAYBE_UNUSED [[maybe_unused]] +#else +// If not C++17 or later, check the compiler. + #ifdef _MSC_VER +// Microsoft Visual Studio +#define GEOS_MAYBE_UNUSED __pragma(warning(suppress: 4100)) + #elif defined(__GNUC__) || defined(__clang__) +// GCC or Clang +#define GEOS_MAYBE_UNUSED __attribute__((unused)) + #else +// If the compiler is unknown, we can't suppress the warning, +// so we define GEOS_MAYBE_UNUSED as an empty macro. +#define GEOS_MAYBE_UNUSED + #endif +#endif + #endif // GEOS_COMMON_GEOSXMACROS_HPP_ diff --git a/src/coreComponents/common/LifoStorage.hpp b/src/coreComponents/common/LifoStorage.hpp new file mode 100644 index 00000000000..821d28d4377 --- /dev/null +++ b/src/coreComponents/common/LifoStorage.hpp @@ -0,0 +1,195 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2018-2020 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2020 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2018-2020 TotalEnergies + * Copyright (c) 2019- GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ +#ifndef LIFOSTORAGE_HPP +#define LIFOSTORAGE_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common/GEOS_RAJA_Interface.hpp" +#include "common/TimingMacros.hpp" +#include "common/LifoStorageCommon.hpp" +#include "common/LifoStorageHost.hpp" +#ifdef GEOS_USE_CUDA +#include "common/LifoStorageCuda.hpp" +#endif + +namespace geos +{ +/** + * This class is used to store in a LIFO way buffers, first on device, then on host, then on disk. + */ +template< typename T, typename INDEX_TYPE > +class LifoStorage +{ + +public: + + + /** + * A LIFO storage will store numberOfBuffersToStoreDevice buffer on + * deevice, numberOfBuffersToStoreHost on host and the rest on disk. + * + * @param name Prefix of the files used to save the occurenncy of the saved buffer on disk. + * @param elemCnt Number of elments in the LvArray we want to store in the LIFO storage. + * @param numberOfBuffersToStoreOnDevice Maximum number of array to store on device memory. If negative opposite of the percent of left + * memory we want to use( -80 = use 80% of remaining memory ). + * @param numberOfBuffersToStoreOnHost Maximum number of array to store on host memory . If negative opposite of the percent of left + * memory we want to use( -80 = use 80% of remaining memory ). + * @param maxNumberOfBuffers Number of arrays expected to be stores in the LIFO. + */ + LifoStorage( std::string name, size_t elemCnt, int numberOfBuffersToStoreOnDevice, int numberOfBuffersToStoreOnHost, int maxNumberOfBuffers ): + m_maxNumberOfBuffers( maxNumberOfBuffers ), + m_bufferSize( elemCnt*sizeof( T ) ), + m_bufferCount( 0 ) + { + LIFO_LOG_RANK( " LIFO : maximum size "<< m_maxNumberOfBuffers << " buffers " ); + double bufferSize = ( ( double ) m_bufferSize ) / ( 1024.0 * 1024.0 ); + LIFO_LOG_RANK( " LIFO : buffer size "<< bufferSize << "MB" ); + if( numberOfBuffersToStoreOnDevice < 0 ) + { +#ifdef GEOS_USE_CUDA + numberOfBuffersToStoreOnDevice = LifoStorageCuda< T, INDEX_TYPE >::computeNumberOfBufferOnDevice( -numberOfBuffersToStoreOnDevice, m_bufferSize, m_maxNumberOfBuffers ); +#else + numberOfBuffersToStoreOnDevice = 0; +#endif + } + if( numberOfBuffersToStoreOnHost < 0 ) + { + numberOfBuffersToStoreOnHost = + LifoStorageCommon< T, INDEX_TYPE >::computeNumberOfBufferOnHost( -numberOfBuffersToStoreOnHost, m_bufferSize, m_maxNumberOfBuffers, numberOfBuffersToStoreOnDevice ); + } + LIFO_LOG_RANK( " LIFO : allocating "<< numberOfBuffersToStoreOnHost <<" buffers on host" ); + LIFO_LOG_RANK( " LIFO : allocating "<< numberOfBuffersToStoreOnDevice <<" buffers on device" ); +#ifdef GEOS_USE_CUDA + if( numberOfBuffersToStoreOnDevice > 0 ) + { + m_lifo = std::make_unique< LifoStorageCuda< T, INDEX_TYPE > >( name, elemCnt, numberOfBuffersToStoreOnDevice, numberOfBuffersToStoreOnHost, maxNumberOfBuffers ); + } + else +#endif + { + m_lifo = std::make_unique< LifoStorageHost< T, INDEX_TYPE > >( name, elemCnt, numberOfBuffersToStoreOnHost, maxNumberOfBuffers ); + } + + } + + /** + * Build a LIFO storage for a given LvArray array. + * + * @param name Prefix of the files used to save the occurenncy of the saved buffer on disk. + * @param array The LvArray that will be store in the LIFO. + * @param numberOfBuffersToStoreOnDevice Maximum number of array to store on device memory. + * @param numberOfBuffersToStoreOnHost Maximum number of array to store on host memory. + * @param maxNumberOfBuffers Number of arrays expected to be stores in the LIFO. + */ + LifoStorage( std::string name, arrayView1d< T > array, int numberOfBuffersToStoreOnDevice, int numberOfBuffersToStoreOnHost, int maxNumberOfBuffers ): + LifoStorage( name, array.size(), numberOfBuffersToStoreOnDevice, numberOfBuffersToStoreOnHost, maxNumberOfBuffers ) {} + + /** + * Asynchroneously push a copy of the given LvArray into the LIFO + * + * @param array The LvArray to store in the LIFO, should match the size of the data used in constructor. + */ + void pushAsync( arrayView1d< T > array ) + { + LIFO_MARK_FUNCTION; + m_lifo->pushAsync( array ); + } + + /** + * Waits for last push to be terminated + */ + void pushWait() + { + LIFO_MARK_FUNCTION; + m_lifo->pushWait(); + } + + /** + * Push a copy of the given LvArray into the LIFO + * + * @param array The LvArray to store in the LIFO, should match the size of the data used in constructor. + */ + void push( arrayView1d< T > array ) + { + LIFO_MARK_FUNCTION; + pushAsync( array ); + pushWait(); + } + + /** + * Asynchroneously copy last data from the LIFO into the LvArray. + * + * @param array LvArray to store data from the LIFO into it. + */ + void popAsync( arrayView1d< T > array ) + { + LIFO_MARK_FUNCTION; + m_lifo->popAsyncPrelude(); + m_lifo->popAsync( array ); + } + + /** + * Waits for last pop to be terminated + */ + void popWait() + { + LIFO_MARK_FUNCTION; + m_lifo->popWait(); + } + + /** + * Copy last data from the LIFO into the LvArray. + * + * @param array LvArray to store data from the LIFO into it. + */ + void pop( arrayView1d< T > array ) + { + LIFO_MARK_FUNCTION; + popAsync( array ); + popWait(); + } + + /** + * Check if the LIFO is empty + * + * @return true if the LIFO does not contain a buffer. + */ + bool empty() + { + return m_lifo->empty(); + } + +private: + /// number of buffers to be inserted into the LIFO + int m_maxNumberOfBuffers; + /// size of one buffer in bytes + size_t m_bufferSize; + /// counter of buffer stored in LIFO + int m_bufferCount; + + /// pointer either to CUDA aware or host only LifoStorage + std::unique_ptr< LifoStorageCommon< T, INDEX_TYPE > > m_lifo; + +}; +} +#endif // LIFOSTORAGE_HPP diff --git a/src/coreComponents/common/LifoStorageCommon.hpp b/src/coreComponents/common/LifoStorageCommon.hpp new file mode 100644 index 00000000000..6fec8404332 --- /dev/null +++ b/src/coreComponents/common/LifoStorageCommon.hpp @@ -0,0 +1,313 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2018-2020 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2020 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2018-2020 TotalEnergies + * Copyright (c) 2019- GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ +#ifndef LIFOSTORAGECOMMON_HPP +#define LIFOSTORAGECOMMON_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef LIFO_DISABLE_CALIPER +#define LIFO_MARK_FUNCTION +#define LIFO_MARK_SCOPE( a ) +#define LIFO_LOG_RANK( a ) std::cerr << a << std::endl; +#else +#define LIFO_MARK_FUNCTION GEOS_MARK_FUNCTION +#define LIFO_MARK_SCOPE( a ) GEOS_MARK_SCOPE( a ) +#define LIFO_LOG_RANK( a ) GEOS_LOG_RANK( a ) +#endif + +#include "common/GEOS_RAJA_Interface.hpp" +#include "common/TimingMacros.hpp" +#include "common/FixedSizeDequeWithMutexes.hpp" +#include "common/MultiMutexesLock.hpp" + + +namespace geos +{ +/** + * This class is used to store in a LIFO way buffers, first on device, then on host, then on disk. + */ +template< typename T, typename INDEX_TYPE > +class LifoStorageCommon +{ +public: + + + /** + * A LIFO storage will store numberOfBuffersToStoreDevice buffer on + * deevice, numberOfBuffersToStoreHost on host and the rest on disk. + * + * @param name Prefix of the files used to save the occurenncy of the saved buffer on disk. + * @param elemCnt Number of elments in the LvArray we want to store in the LIFO storage. + * @param numberOfBuffersToStoreOnHost Maximum number of array to store on host memory ( -1 = use 80% of remaining memory ). + * @param maxNumberOfBuffers Number of arrays expected to be stores in the LIFO. + */ + LifoStorageCommon( std::string name, size_t elemCnt, int numberOfBuffersToStoreOnHost, int maxNumberOfBuffers ): + m_maxNumberOfBuffers( maxNumberOfBuffers ), + m_bufferSize( elemCnt*sizeof( T ) ), + m_name( name ), + m_hostDeque( numberOfBuffersToStoreOnHost, elemCnt, LvArray::MemorySpace::host ), + m_bufferCount( 0 ), m_bufferToHostCount( 0 ), m_bufferToDiskCount( 0 ), + m_continue( true ), + m_hasPoppedBefore( false ) + { + m_worker[0] = std::thread( &LifoStorageCommon< T, INDEX_TYPE >::wait_and_consume_tasks, this, 0 ); + m_worker[1] = std::thread( &LifoStorageCommon< T, INDEX_TYPE >::wait_and_consume_tasks, this, 1 ); + } + + virtual ~LifoStorageCommon() + { + m_continue = false; + m_task_queue_not_empty_cond[0].notify_all(); + m_task_queue_not_empty_cond[1].notify_all(); + m_worker[0].join(); + m_worker[1].join(); + } + + /** + * Asynchroneously push a copy of the given LvArray into the LIFO + * + * @param array The LvArray to store in the LIFO, should match the size of the data used in constructor. + */ + virtual void pushAsync( arrayView1d< T > array ) = 0; + + /** + * Waits for last push to be terminated + */ + virtual void pushWait() = 0; + + /** + * Asynchroneously copy last data from the LIFO into the LvArray. + * + * @param array LvArray to store data from the LIFO into it. + */ + virtual void popAsync( arrayView1d< T > array ) = 0; + + /** + * Prelude for pop async + */ + void popAsyncPrelude( ) + { + LIFO_MARK_FUNCTION; + //wait the last push to avoid race condition + pushWait(); + if( m_hasPoppedBefore ) + { + // Ensure last pop is finished + popWait(); + } + else + { + if( m_maxNumberOfBuffers != m_bufferCount ) + LIFO_LOG_RANK( " LIFO : warning number of entered buffered (" << m_bufferCount + << ") != max LIFO size (" << m_maxNumberOfBuffers << ") !" ); + // Ensure that all push step are ended + for( int queueId = 0; queueId < 2; queueId++ ) + { + std::unique_lock< std::mutex > lock( m_task_queue_mutex[queueId] ); + m_task_queue_not_empty_cond[queueId].wait( lock, [ this, &queueId ] { return m_task_queue[queueId].empty(); } ); + } + } + m_hasPoppedBefore = true; + } + + /** + * Waits for last pop to be terminated + */ + virtual void popWait() = 0; + + /** + * Check if the LIFO is empty + * + * @return true if the LIFO does not contain a buffer. + */ + bool empty() + { + return m_bufferCount == 0; + } + + /** + * Compute the number of arrays that can be stored on host + * @param percent Percentage of the remaining device memory that can be dedicated to the LIFO storage. + * @param bufferSize Size of one buffer + * @param maxNumberOfBuffers Maximum number of buffers to store in the LIFO storage + * @param numberOfBuffersToStoreOnDevice The number of buffer that will be stored on device by the LIFO. + * @return The maximum number of buffer to allocate to fit in the percentage of the available memory. + */ + static int computeNumberOfBufferOnHost( int percent, size_t bufferSize, int maxNumberOfBuffers, int numberOfBuffersToStoreOnDevice ) + { + GEOS_ERROR_IF( percent > 100, "Error, percentage of memory should be smallerer than -100, check lifoOnHost (should be greater that -100)" ); + size_t free = sysconf( _SC_AVPHYS_PAGES ) * sysconf( _SC_PAGESIZE ); + int numberOfBuffersToStoreOnHost = std::max( 1, std::min( ( int )( 0.01 * percent * free / bufferSize ), maxNumberOfBuffers - numberOfBuffersToStoreOnDevice ) ); + double freeGB = ( ( double ) free ) / ( 1024.0 * 1024.0 * 1024.0 ) / MpiWrapper::nodeCommSize(); + LIFO_LOG_RANK( " LIFO : available memory on host " << freeGB << " GB" ); + return numberOfBuffersToStoreOnHost; + } + +protected: + + /// number of buffers to be inserted into the LIFO + int m_maxNumberOfBuffers; + /// size of one buffer in bytes + size_t m_bufferSize; + /// name used to store data on disk + std::string m_name; + /// Queue of data stored on host memory + FixedSizeDequeWithMutexes< T, INDEX_TYPE > m_hostDeque; + + /// counter of buffer stored in LIFO + int m_bufferCount; + /// counter of buffer pushed to host + int m_bufferToHostCount; + /// counter of buffer pushed to disk + int m_bufferToDiskCount; + + + /// condition used to tell m_worker queue has been filled or processed is stopped. + std::condition_variable m_task_queue_not_empty_cond[2]; + /// mutex to protect access to m_task_queue. + std::mutex m_task_queue_mutex[2]; + /// queue of task to be executed by m_worker. + std::deque< std::packaged_task< void() > > m_task_queue[2]; + /// thread to execute tasks. + std::thread m_worker[2]; + /// boolean to keep m_worker alive. + bool m_continue; + /// marker to detect first pop + bool m_hasPoppedBefore; + + /** + * Copy data from host memory to disk + * + * @param id ID of the buffer to store on disk. + */ + void hostToDisk( int id ) + { + LIFO_MARK_FUNCTION; + { + auto lock = make_multilock( m_hostDeque.m_popMutex, m_hostDeque.m_backMutex ); + writeOnDisk( m_hostDeque.back().dataIfContiguous(), id ); + m_hostDeque.pop_back(); + } + m_hostDeque.m_notFullCond.notify_all(); + } + + /** + * Copy data from disk to host memory + * + * @param id ID of the buffer to read on disk. + */ + void diskToHost( int id ) + { + LIFO_MARK_FUNCTION; + { + auto lock = make_multilock( m_hostDeque.m_emplaceMutex, m_hostDeque.m_backMutex ); + m_hostDeque.m_notFullCond.wait( lock, [ this ] { return !( m_hostDeque.full() ); } ); + readOnDisk( const_cast< T * >(m_hostDeque.next_back().dataIfContiguous()), id ); + m_hostDeque.inc_back(); + } + m_hostDeque.m_notEmptyCond.notify_all(); + } + /** + * Checks if a directory exists. + * + * @param dirName Directory name to check existence of. + * @return true is dirName exists and is a directory. + */ + bool dirExists( const std::string & dirName ) + { + struct stat buffer; + return stat( dirName.c_str(), &buffer ) == 0; + } + + /** + * Write data on disk + * + * @param d Data to store on disk. + * @param id ID of the buffer to read on disk + */ + void writeOnDisk( const T * d, int id ) + { + LIFO_MARK_FUNCTION; + std::string fileName = GEOS_FMT( "{}_{:08}.dat", m_name, id ); + int lastDirSeparator = fileName.find_last_of( "/\\" ); + std::string dirName = fileName.substr( 0, lastDirSeparator ); + if( string::npos != (size_t)lastDirSeparator && !dirExists( dirName )) + makeDirsForPath( dirName ); + + std::ofstream wf( fileName, std::ios::out | std::ios::binary ); + GEOS_ERROR_IF( !wf || wf.fail() || !wf.is_open(), + "Could not open file "<< fileName << " for writting" ); + wf.write( (const char *)d, m_bufferSize ); + GEOS_ERROR_IF( wf.bad() || wf.fail(), + "An error occured while writting "<< fileName ); + wf.close(); + } + + /** + * Read data from disk + * + * @param d Buffer to store data read from disk. + * @param id ID of the buffer on disk. + */ + void readOnDisk( T * d, int id ) + { + LIFO_MARK_FUNCTION; + std::string fileName = GEOS_FMT( "{}_{:08}.dat", m_name, id ); + std::ifstream wf( fileName, std::ios::in | std::ios::binary ); + GEOS_ERROR_IF( !wf, + "Could not open file "<< fileName << " for reading" ); + wf.read( (char *)d, m_bufferSize ); + wf.close(); + remove( fileName.c_str() ); + } + + +private: + /** + * Execute the tasks pushed in m_task_queue in the given order. + * + * @param queueId index of the queue (0: queue to handle device/host transfers; 1: host/disk transfers) + */ + void wait_and_consume_tasks( int queueId ) + { + LIFO_MARK_FUNCTION; + while( m_continue ) + { + std::unique_lock< std::mutex > lock( m_task_queue_mutex[queueId] ); + { + LIFO_MARK_SCOPE( waitForTask ); + m_task_queue_not_empty_cond[queueId].wait( lock, [ this, &queueId ] { return !( m_task_queue[queueId].empty() && m_continue ); } ); + } + if( m_continue == false ) break; + std::packaged_task< void() > task( std::move( m_task_queue[queueId].front() ) ); + m_task_queue[queueId].pop_front(); + lock.unlock(); + m_task_queue_not_empty_cond[queueId].notify_all(); + { + LIFO_MARK_SCOPE( runningTask ); + task(); + } + } + } +}; +} +#endif // LIFOSTORAGECOMMON_HPP diff --git a/src/coreComponents/common/LifoStorageCuda.hpp b/src/coreComponents/common/LifoStorageCuda.hpp new file mode 100644 index 00000000000..9345db2615d --- /dev/null +++ b/src/coreComponents/common/LifoStorageCuda.hpp @@ -0,0 +1,215 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2018-2020 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2020 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2018-2020 TotalEnergies + * Copyright (c) 2019- GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ +#ifndef LIFOSTORAGECUDA_HPP +#define LIFOSTORAGECUDA_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common/FixedSizeDeque.hpp" +#include "common/GEOS_RAJA_Interface.hpp" +#include "common/TimingMacros.hpp" +#include "common/FixedSizeDequeWithMutexes.hpp" +#include "common/MultiMutexesLock.hpp" +#include "common/LifoStorageCommon.hpp" + +namespace geos +{ +/** + * This class is used to store in a LIFO way buffers, first on device, then on host, then on disk. + */ + +template< typename T, typename INDEX_TYPE > +class LifoStorageCuda : public LifoStorageCommon< T, INDEX_TYPE > +{ + typedef LifoStorageCommon< T, INDEX_TYPE > baseLifo; +public: + + + /** + * A LIFO storage will store numberOfBuffersToStoreDevice buffer on + * deevice, numberOfBuffersToStoreHost on host and the rest on disk. + * + * @param name Prefix of the files used to save the occurenncy of the saved buffer on disk. + * @param elemCnt Number of elments in the LvArray we want to store in the LIFO storage. + * @param numberOfBuffersToStoreOnDevice Maximum number of array to store on device memory ( -1 = use 80% of remaining memory ). + * @param numberOfBuffersToStoreOnHost Maximum number of array to store on host memory ( -1 = use 80% of remaining memory ). + * @param maxNumberOfBuffers Number of arrays expected to be stores in the LIFO. + */ + LifoStorageCuda( std::string name, size_t elemCnt, int numberOfBuffersToStoreOnDevice, int numberOfBuffersToStoreOnHost, int maxNumberOfBuffers ): + LifoStorageCommon< T, INDEX_TYPE >( name, elemCnt, numberOfBuffersToStoreOnHost, maxNumberOfBuffers ), + m_deviceDeque( numberOfBuffersToStoreOnDevice, elemCnt, LvArray::MemorySpace::cuda ), + m_pushToDeviceEvents( maxNumberOfBuffers ), + m_popFromDeviceEvents( maxNumberOfBuffers ) + {} + + /** + * Asynchroneously push a copy of the given LvArray into the LIFO + * + * @param array The LvArray to store in the LIFO, should match the size of the data used in constructor. + */ + void pushAsync( arrayView1d< T > array ) override final + { + //To be sure 2 pushes are not mixed + pushWait(); + int id = baseLifo::m_bufferCount++; + GEOS_ERROR_IF( baseLifo::m_hostDeque.capacity() == 0 && m_deviceDeque.capacity() < baseLifo::m_maxNumberOfBuffers, + "Cannot save on a Lifo without host storage (please set lifoSize, lifoOnDevice and lifoOnHost in xml file)" ); + + m_pushToDeviceEvents[id] = m_deviceDeque.emplaceFront( array ); + + if( baseLifo::m_maxNumberOfBuffers - id > (int)m_deviceDeque.capacity() ) + { + LIFO_MARK_SCOPE( geosx::lifoStorage::pushAddTasks ); + // This buffer will go to host memory, and maybe on disk + std::packaged_task< void() > task( std::bind( &LifoStorageCuda< T, INDEX_TYPE >::deviceToHost, this, baseLifo::m_bufferToHostCount++ ) ); + { + std::unique_lock< std::mutex > lock( baseLifo::m_task_queue_mutex[0] ); + baseLifo::m_task_queue[0].emplace_back( std::move( task ) ); + } + baseLifo::m_task_queue_not_empty_cond[0].notify_all(); + } + } + + /** + * Waits for last push to be terminated + */ + void pushWait() override final + { + if( baseLifo::m_bufferCount > 0 ) + { + m_pushToDeviceEvents[baseLifo::m_bufferCount-1].wait(); + } + } + + /** + * Asynchroneously copy last data from the LIFO into the LvArray. + * + * @param array LvArray to store data from the LIFO into it. + */ + void popAsync( arrayView1d< T > array ) override final + { + LIFO_MARK_FUNCTION; + int id = --baseLifo::m_bufferCount; + + if( baseLifo::m_bufferToHostCount > 0 ) + { + LIFO_MARK_SCOPE( geosx::LifoStorageCuda::popAddTasks ); + // Trigger pull one buffer from host, and maybe from disk + std::packaged_task< void() > task( std::bind( &LifoStorageCuda< T, INDEX_TYPE >::hostToDevice, this, --baseLifo::m_bufferToHostCount, id ) ); + { + std::unique_lock< std::mutex > lock( baseLifo::m_task_queue_mutex[0] ); + baseLifo::m_task_queue[0].emplace_back( std::move( task ) ); + } + baseLifo::m_task_queue_not_empty_cond[0].notify_all(); + } + m_popFromDeviceEvents[id] = m_deviceDeque.popFront( array ); + } + + /** + * Waits for last pop to be terminated + */ + void popWait() override final + { + if( baseLifo::m_bufferCount < baseLifo::m_maxNumberOfBuffers ) + { + int bufferCount = baseLifo::m_bufferCount; + auto cuda_event = m_popFromDeviceEvents[bufferCount].try_get< camp::resources::CudaEvent >(); + if( cuda_event ) cudaEventSynchronize( cuda_event->getCudaEvent_t() ); + } + } + + /** + * Compute the number of arrays that can be stored on device + * @param percent Percentage of the remaining device memory that can be dedicated to the LIFO storage. + * @param bufferSize Size of one buffer + * @param maxNumberOfBuffers Maximum number of buffers to store in the LIFO storage + * @return The maximum number of buffer to allocate to fit in the percentage of the available memory. + */ + static int computeNumberOfBufferOnDevice( int percent, size_t bufferSize, int maxNumberOfBuffers ) + { + GEOS_ERROR_IF( percent > 100, "Error, percentage of memory should be smaller than 100, check lifoOnDevice (should be greater than -100)" ); + size_t free, total; + GEOS_ERROR_IF( cudaSuccess != cudaMemGetInfo( &free, &total ), "Error getting CUDA device available memory" ); + double freeGB = ( ( double ) free ) / ( 1024.0 * 1024.0 * 1024.0 ); + LIFO_LOG_RANK( " LIFO : available memory on device " << freeGB << " GB" ); + return std::min( ( int )( 0.01 * percent * free / bufferSize ), maxNumberOfBuffers ); + } + +private: + + /** + * Copy data from device memory to host memory + * + * @param ID of the buffer to copy on host. + */ + void deviceToHost( int id ) + { + LIFO_MARK_FUNCTION; + // The copy to host will only start when the data is copied on device buffer + baseLifo::m_hostDeque.getStream().wait_for( const_cast< camp::resources::Event * >( &m_pushToDeviceEvents[id] ) ); + baseLifo::m_hostDeque.emplaceFrontFromBack( m_deviceDeque ); + + if( baseLifo::m_maxNumberOfBuffers - id > (int)(m_deviceDeque.capacity() + baseLifo::m_hostDeque.capacity()) ) + { + // This buffer will go to host then maybe to disk + std::packaged_task< void() > task( std::bind( &LifoStorageCuda< T, INDEX_TYPE >::hostToDisk, this, baseLifo::m_bufferToDiskCount++ ) ); + { + std::unique_lock< std::mutex > lock( baseLifo::m_task_queue_mutex[1] ); + baseLifo::m_task_queue[1].emplace_back( std::move( task ) ); + } + baseLifo::m_task_queue_not_empty_cond[1].notify_all(); + } + } + + /** + * Copy data from host memory to device memory + * + * @param id ID of the buffer to load from host memory. + * @param id_pop ID of the last popped buffer from device + */ + void hostToDevice( int id, int id_pop ) + { + LIFO_MARK_FUNCTION; + // enqueue diskToHost on worker #2 if needed + if( baseLifo::m_bufferToDiskCount > 0 ) + { + // This buffer will go to host then to disk + std::packaged_task< void() > task( std::bind( &LifoStorageCuda< T, INDEX_TYPE >::diskToHost, this, --baseLifo::m_bufferToDiskCount ) ); + { + std::unique_lock< std::mutex > lock( baseLifo::m_task_queue_mutex[1] ); + baseLifo::m_task_queue[1].emplace_back( std::move( task ) ); + } + baseLifo::m_task_queue_not_empty_cond[1].notify_all(); + } + + m_deviceDeque.emplaceBackFromFront( baseLifo::m_hostDeque ); + } + + /// ueue of data stored on device + FixedSizeDequeWithMutexes< T, INDEX_TYPE > m_deviceDeque; + // Events associated to ith copies to device buffer + std::vector< camp::resources::Event > m_pushToDeviceEvents; + // Events associated to ith copies from device buffer + std::vector< camp::resources::Event > m_popFromDeviceEvents; +}; +} +#endif // LIFOSTORAGE_HPP diff --git a/src/coreComponents/common/LifoStorageHost.hpp b/src/coreComponents/common/LifoStorageHost.hpp new file mode 100644 index 00000000000..c11999de0cd --- /dev/null +++ b/src/coreComponents/common/LifoStorageHost.hpp @@ -0,0 +1,170 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2018-2020 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2020 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2018-2020 TotalEnergies + * Copyright (c) 2019- GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ +#ifndef LIFOSTORAGEHOST_HPP +#define LIFOSTORAGEHOST_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common/GEOS_RAJA_Interface.hpp" +#include "common/TimingMacros.hpp" +#include "common/LifoStorageCommon.hpp" + +namespace geos +{ +/** + * This class is used to store in a LIFO way buffers, first on device, then on host, then on disk. + */ +template< typename T, typename INDEX_TYPE > +class LifoStorageHost : public LifoStorageCommon< T, INDEX_TYPE > +{ + typedef LifoStorageCommon< T, INDEX_TYPE > baseLifo; + +public: + + + /** + * A LIFO storage will store numberOfBuffersToStoreDevice buffer on + * deevice, numberOfBuffersToStoreHost on host and the rest on disk. + * + * @param name Prefix of the files used to save the occurenncy of the saved buffer on disk. + * @param elemCnt Number of elments in the LvArray we want to store in the LIFO storage. + * @param numberOfBuffersToStoreOnHost Maximum number of array to store on host memory ( -1 = use 80% of remaining memory ). + * @param maxNumberOfBuffers Number of arrays expected to be stores in the LIFO. + */ + LifoStorageHost( std::string name, size_t elemCnt, int numberOfBuffersToStoreOnHost, int maxNumberOfBuffers ): + LifoStorageCommon< T, INDEX_TYPE >( name, elemCnt, numberOfBuffersToStoreOnHost, maxNumberOfBuffers ), + m_pushToHostFutures( maxNumberOfBuffers ), + m_popFromHostFutures( maxNumberOfBuffers ) + {} + + /** + * Asynchroneously push a copy of the given LvArray into the LIFO + * + * @param array The LvArray to store in the LIFO, should match the size of the data used in constructor. + */ + void pushAsync( arrayView1d< T > array ) override final + { + //To be sure 2 pushes are not mixed + pushWait(); + int id = baseLifo::m_bufferCount++; + GEOS_ERROR_IF( baseLifo::m_hostDeque.capacity() == 0, + "Cannot save on a Lifo without host storage (please set lifoSize, lifoOnDevice and lifoOnHost in xml file)" ); + + std::packaged_task< void() > task( std::bind( [ this ] ( int pushId, arrayView1d< T > pushedArray ) { + baseLifo::m_hostDeque.emplaceFront( pushedArray ); + + if( baseLifo::m_maxNumberOfBuffers - pushId > (int)baseLifo::m_hostDeque.capacity() ) + { + LIFO_MARK_SCOPE( geosx::lifoStorage::pushAddTasks ); + // This buffer will go to host memory, and maybe on disk + std::packaged_task< void() > t2( std::bind( &LifoStorageHost< T, INDEX_TYPE >::hostToDisk, this, baseLifo::m_bufferToDiskCount++ ) ); + { + std::unique_lock< std::mutex > l2( baseLifo::m_task_queue_mutex[1] ); + baseLifo::m_task_queue[1].emplace_back( std::move( t2 ) ); + } + baseLifo::m_task_queue_not_empty_cond[1].notify_all(); + } + }, id, array ) ); + m_pushToHostFutures[id] = task.get_future(); + { + std::unique_lock< std::mutex > lock( baseLifo::m_task_queue_mutex[0] ); + baseLifo::m_task_queue[0].emplace_back( std::move( task ) ); + } + baseLifo::m_task_queue_not_empty_cond[0].notify_all(); + } + + /** + * Waits for last push to be terminated + */ + void pushWait() override final + { + if( baseLifo::m_bufferCount > 0 ) + { + m_pushToHostFutures[baseLifo::m_bufferCount-1].wait(); + } + } + + /** + * Asynchroneously copy last data from the LIFO into the LvArray. + * + * @param array LvArray to store data from the LIFO into it. + */ + void popAsync( arrayView1d< T > array ) override final + { + int id = --baseLifo::m_bufferCount; + + std::packaged_task< void() > task( std::bind ( [ this ] ( arrayView1d< T > poppedArray ) { + baseLifo::m_hostDeque.popFront( poppedArray ); + + if( baseLifo::m_bufferToDiskCount > 0 ) + { + LIFO_MARK_SCOPE( geosx::LifoStorageHost::popAddTasks ); + // Trigger pull one buffer from host, and maybe from disk + std::packaged_task< void() > task2( std::bind( &LifoStorageHost< T, INDEX_TYPE >::diskToHost, this, --baseLifo::m_bufferToDiskCount ) ); + { + std::unique_lock< std::mutex > lock2( baseLifo::m_task_queue_mutex[1] ); + baseLifo::m_task_queue[1].emplace_back( std::move( task2 ) ); + } + baseLifo::m_task_queue_not_empty_cond[1].notify_all(); + } + }, array ) ); + m_popFromHostFutures[id] = task.get_future(); + { + std::unique_lock< std::mutex > lock( baseLifo::m_task_queue_mutex[0] ); + baseLifo::m_task_queue[0].emplace_back( std::move( task ) ); + } + baseLifo::m_task_queue_not_empty_cond[0].notify_all(); + } + + /** + * Waits for last pop to be terminated + */ + void popWait() override final + { + if( baseLifo::m_bufferCount < baseLifo::m_maxNumberOfBuffers ) + { + m_popFromHostFutures[baseLifo::m_bufferCount].wait(); + } + } + + /** + * Compute the number of arrays that can be stored on device + * @param percent Percentage of the remaining device memory that can be dedicated to the LIFO storage. + * @param bufferSize Size of one buffer + * @param maxNumberOfBuffers Maximum number of buffers to store in the LIFO storage + * @return The maximum number of buffer to allocate to fit in the percentage of the available memory. + */ + static int computeNumberOfBufferOnDevice( int percent, size_t bufferSize, int maxNumberOfBuffers ) + { + GEOS_UNUSED_VAR( percent, bufferSize, maxNumberOfBuffers ); + return 0; + } + +private: + + // Futures associated to push to host in case we have no device buffers + std::vector< std::future< void > > m_pushToHostFutures; + // Futures associated to pop from host in case we have no device buffers + std::vector< std::future< void > > m_popFromHostFutures; +}; +} +#endif // LIFOSTORAGEHOST_HPP diff --git a/src/coreComponents/common/MpiWrapper.cpp b/src/coreComponents/common/MpiWrapper.cpp index 4d009e9032e..e93e0e2c873 100644 --- a/src/coreComponents/common/MpiWrapper.cpp +++ b/src/coreComponents/common/MpiWrapper.cpp @@ -16,6 +16,7 @@ */ #include "MpiWrapper.hpp" +#include #if defined(__clang__) #pragma clang diagnostic push @@ -407,6 +408,29 @@ int MpiWrapper::activeWaitOrderedCompletePhase( const int participants, return MPI_SUCCESS; } +int MpiWrapper::nodeCommSize() +{ + // if not initialized then we guess there is no MPI. + if( !initialized() ) + return 1; + + int len; + std::array< char, MPI_MAX_PROCESSOR_NAME + 1 > hostname; + MPI_Get_processor_name( hostname.data(), &len ); + hostname[len] = '\0'; + int color = (int)std::hash< string >{} (hostname.data()); + if( color < 0 ) + color *= -1; + + /** + * Create intra-node communicator + */ + MPI_Comm nodeComm; + int nodeCommSize; + MPI_Comm_split( MPI_COMM_WORLD, color, -1, &nodeComm ); + MPI_Comm_size( nodeComm, &nodeCommSize ); + return nodeCommSize; +} } /* namespace geos */ #if defined(__clang__) diff --git a/src/coreComponents/common/MpiWrapper.hpp b/src/coreComponents/common/MpiWrapper.hpp index f98bf555aa7..5dde9e71200 100644 --- a/src/coreComponents/common/MpiWrapper.hpp +++ b/src/coreComponents/common/MpiWrapper.hpp @@ -285,6 +285,12 @@ struct MpiWrapper } #endif + /** + * @brief Compute the number of ranks allocated on the same node + * @return The number of MPI ranks on the current node. + */ + static int nodeCommSize(); + /** * @brief Strongly typed wrapper around MPI_Allgather. * @tparam T_SEND The pointer type for \p sendbuf diff --git a/src/coreComponents/common/MultiMutexesLock.hpp b/src/coreComponents/common/MultiMutexesLock.hpp new file mode 100644 index 00000000000..2bfd9ac83d4 --- /dev/null +++ b/src/coreComponents/common/MultiMutexesLock.hpp @@ -0,0 +1,88 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2018-2020 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2020 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2018-2020 TotalEnergies + * Copyright (c) 2019- GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ +#ifndef MULTIMUTEXESLOCK_HPP +#define MULTIMUTEXESLOCK_HPP + +#include +#include + +#include "codingUtilities/Utilities.hpp" + +namespace geos +{ + +/** + * @brief Class to handle locks using 2 mutexes + */ +template< typename ... Mutexes > +class MultiMutexesLock +{ +public: + /** + * @brief Construct a multi mutexes lock and lock the mutexes. + * @param mutexes The mutexes associated with the lock. + */ + MultiMutexesLock( Mutexes &... mutexes ) + : m_islocked( false ), m_mutexes( mutexes ... ) + { + lock(); + } + + /** + * @brief Unlock the mutexes and destroy the locks. + */ + ~MultiMutexesLock() + { + unlock(); + } + + /** + * @brief Lock the two mutexes using std::lock is not already owning lock + */ + void lock() + { + if( m_islocked ) return; + apply( []( auto && ... mutexes ){ std::lock( mutexes ... ); }, m_mutexes ); + m_islocked = true; + } + + /** + * @brief Unlock the two mutexes is owning them. + */ + void unlock() + { + if( !m_islocked ) return; + forEachArgInTuple( m_mutexes, []( auto & mutex, auto ){ mutex.unlock(); } ); + m_islocked = false; + } + +private: + /// Indicate if the mutexes are owned by the lock + bool m_islocked; + /// Array of references to the mutexes + std::tuple< Mutexes &... > m_mutexes; +}; + +/** + * @brief Helper to construct MultiMutexesLock (usage auto lock = make_multilock( mutex1, mutex2, ... )) + * @param mutexes List of mutex parameters + * @return A MultiMutexesLock using the mutexes given in parameter. + */ +template< typename ... Mutexes > +auto make_multilock( Mutexes && ... mutexes ) +{ + return MultiMutexesLock< Mutexes... >( std::forward< Mutexes >( mutexes )... ); +} +} +#endif // MULTIMUTEXESLOCK_HPP diff --git a/src/coreComponents/common/TypeDispatch.hpp b/src/coreComponents/common/TypeDispatch.hpp index 593a472daab..c7f30afdf1e 100644 --- a/src/coreComponents/common/TypeDispatch.hpp +++ b/src/coreComponents/common/TypeDispatch.hpp @@ -126,16 +126,16 @@ struct ArrayType< camp::list< T, camp::list< NDIM, LAYOUT > > > }; // Helper to apply a template to all types in a list -template< template< typename > class F, typename T > +template< template< typename ... > class F, typename T > struct ApplyImpl; -template< template< typename > class F, typename ... Ts > +template< template< typename ... > class F, typename ... Ts > struct ApplyImpl< F, camp::list< Ts ... > > { using types = camp::list< typename F< Ts >::type ... >; }; -template< template< typename > class F, typename T > +template< template< typename ... > class F, typename T > using Apply = typename ApplyImpl< F, T >::types; // Helper to increment values in an compile-time integer sequence @@ -151,7 +151,7 @@ struct IncrementImpl< camp::idx_seq< Is... >, N > template< typename T, int N > using Increment = typename IncrementImpl< T, N >::type; -} // namespace detail +} // namespace internal /** * @brief Construct a list of types. @@ -159,6 +159,12 @@ using Increment = typename IncrementImpl< T, N >::type; template< typename ... Ts > using TypeList = camp::list< Ts... >; +/** + * @brief Construct a list of list type. + */ +template< typename LIST > +using ListofTypeList = internal::Apply< camp::list, LIST >; + /** * @brief Concatenate multiple type lists. */ @@ -217,33 +223,116 @@ using StandardArrays = Join< RealArrays, IntegralArrays >; namespace internal { +/** + * @brief struct to define the hash of a tuple + * + */ +struct tuple_hash +{ + template< class... Ts > + std::size_t operator()( const std::tuple< Ts... > & t ) const + { + std::size_t hash = 0; + std::apply( [&hash]( auto &&... args ) + { + ((hash ^= std::hash< std::decay_t< decltype(args) > >{} (args)), ...); + }, t ); + return hash; + } +}; + +/** + * @brief Function to create a tuple + * @tparam Ts tuple types + */ +template< typename ... Ts > +auto createTypeIndexTuple( TypeList< Ts... > ) +{ + return std::make_tuple( std::type_index( typeid(Ts))... ); +} -template< typename ... Ts, std::size_t ... Is > -std::unordered_map< std::type_index, std::size_t > const & -getTypeIndexMap( TypeList< Ts... >, - std::index_sequence< Is... > ) +/** + * @brief Get the static/singleton instance of the type map + * @tparam LIST The + * @tparam Is integer sequence + * @return reference to the static map + */ +template< typename LIST, std::size_t ... Is > +auto const & getTypeMap( LIST, std::integer_sequence< std::size_t, Is... > ) { - static std::unordered_map< std::type_index, std::size_t > const result( { { typeid( Ts ), Is } ... } ); + using KeyType = decltype( createTypeIndexTuple( camp::first< LIST > {} ) ); + static std::unordered_map< KeyType, std::size_t, tuple_hash > const result = { { createTypeIndexTuple( camp::at_t< LIST, camp::num< Is > >{} ), Is } ... }; return result; } -template< typename ... Ts, typename LAMBDA > -bool dispatchViaTable( TypeList< Ts... > const types, - std::type_index const type, - LAMBDA && lambda ) + +template< typename T > +struct TypeIdentity { - static_assert( sizeof...(Ts) > 0, "Dispatching on empty type list not supported" ); + using type = T; +}; + +/** + * @brief Function to output string containing the types of a TypeList + * @tparam Ts The types contained in the TypeList + * @param pre string to prepend the printer with + * @param post string to postpend the printer with + * @param printer a function that prints the type Ts + * @return A string containing the types in the TypeList + */ +template< typename ... Ts, typename P > +string listToString( TypeList< Ts... >, + string const & pre, + string const & post, + P printer ) +{ + return ( ( pre + printer( TypeIdentity< Ts >{} ) + post ) + ... ); +} + +HAS_MEMBER_FUNCTION_NO_RTYPE( getTypeId, ); + +/** + * @brief Function to return the typeid() of a type or a wrapped type. + * @tparam T the type of object or the wrapper + * @param[in] object The object or wrapped object + * @return the result of typeid() + */ +template< typename T > +std::type_info const & typeIdWrapper( T const & object ) +{ + if constexpr ( HasMemberFunction_getTypeId< T > ) + return object.getTypeId(); + else + return typeid(object); +} + +/** + * @brief + * + * @tparam TypeTuples + * @tparam LAMBDA + * @param combinations + * @param type_index + * @param lambda + * @return true + * @return false + */ +template< typename ... TypeTuples, typename LAMBDA, typename Index_type > +bool dispatchViaTable( TypeList< TypeTuples... > const combinations, + LAMBDA && lambda, + Index_type const type_index ) +{ + static_assert( sizeof...(TypeTuples) > 0, "Dispatching on empty type list not supported" ); // Initialize a table of handlers, once per unique combination of type list and lambda using Handler = void (*)( LAMBDA && ); - static Handler const handlers[] = { []( LAMBDA && f ){ f( Ts{} ); } ... }; + static Handler const handlers[] = { []( LAMBDA && f ){ f( TypeTuples{} ); } ... }; + + auto const & typeIndexMap = getTypeMap( combinations, std::index_sequence_for< TypeTuples... >{} ); + auto const it = typeIndexMap.find( type_index ); - // Initialize a hashmap of std::type_index to contiguous indices, once per unique type list - auto const & typeIndexMap = getTypeIndexMap( types, std::index_sequence_for< Ts... >{} ); - auto const it = typeIndexMap.find( type ); if( it != typeIndexMap.end() ) { - GEOS_ASSERT_GT( sizeof...( Ts ), it->second ); // sanity check handlers[ it->second ]( std::forward< LAMBDA >( lambda ) ); return true; } @@ -254,30 +343,37 @@ bool dispatchViaTable( TypeList< Ts... > const types, /** * @brief Dispatch a generic worker function @p lambda based on runtime type. - * @tparam Ts list of types - * @tparam LAMBDA type of user-provided function or lambda, must have one auto argument - * @param types list of types as an object (for deduction) - * @param type type index of the runtime type - * @param errorIfTypeNotFound flag indicating whether dispatch should issue an error when type not matched - * @param lambda user-provided callable, will be called with a single prvalue of type indicated by @p type + * + * @tparam LIST type of the list of types + * @tparam LAMBDA type of user-provided function or lambda, must have one auto argument + * @tparam Ts types of the objects to be dispatched. + * @param combinations list of types + * @param lambda lambda user-provided callable + * @param objects objects to be dispatched * @return @p true iff type has been dispatch * - * @todo Do we want @p errorIfTypeNotFound parameter? Options: + * todo Do we want errorIfTypeNotFound parameter? Options: * - make it a template parameter (caller always knows whether or not it wants hard errors) * - make the caller process return value and raise error if needed (and however they want) */ -template< typename ... Ts, typename LAMBDA > -bool dispatch( TypeList< Ts... > const types, - std::type_index const type, - bool const errorIfTypeNotFound, - LAMBDA && lambda ) +template< typename LIST, typename LAMBDA, typename ... Ts > +bool dispatch( LIST const combinations, + LAMBDA && lambda, + Ts && ... objects ) { - bool const success = internal::dispatchViaTable( types, type, std::forward< LAMBDA >( lambda ) ); - if( !success && errorIfTypeNotFound ) + bool const success = internal::dispatchViaTable( combinations, + std::forward< LAMBDA >( lambda ), + std::make_tuple( std::type_index( internal::typeIdWrapper( objects ))... ) ); + + if( !success ) { - GEOS_ERROR( "Type " << LvArray::system::demangle( type.name() ) << " was not dispatched.\n" << - "Check the stack trace below and revise the type list passed to dispatch().\n" << - "If you are unsure about this error, please report it to GEOSX issue tracker." ); + auto typePrinter = []( auto t ){ return LvArray::system::demangle( typeid( typename decltype(t)::type ).name() ); }; + auto typeListPrinter = [typePrinter]( auto tlist ){ return internal::listToString( typename decltype( tlist )::type{}, "\n ", "", typePrinter ); }; + + GEOS_ERROR( "Types were not dispatched. The types of the input objects are:\n" << + "( "<<( ( "\n " + LvArray::system::demangle( internal::typeIdWrapper( objects ).name() ) ) + ... )<<" \n)\n"<< + "and the dispatch options are:\n"<< + internal::listToString( combinations, "\n(", "\n)", typeListPrinter ) ); } return success; } diff --git a/src/coreComponents/common/lifoStorage.hpp b/src/coreComponents/common/lifoStorage.hpp deleted file mode 100644 index 98a876cfc2a..00000000000 --- a/src/coreComponents/common/lifoStorage.hpp +++ /dev/null @@ -1,756 +0,0 @@ -/* - * ------------------------------------------------------------------------------------------------------------ - * SPDX-License-Identifier: LGPL-2.1-only - * - * Copyright (c) 2018-2020 Lawrence Livermore National Security LLC - * Copyright (c) 2018-2020 The Board of Trustees of the Leland Stanford Junior University - * Copyright (c) 2018-2020 TotalEnergies - * Copyright (c) 2019- GEOSX Contributors - * All rights reserved - * - * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. - * ------------------------------------------------------------------------------------------------------------ - */ -#ifndef LIFOSTORAGE_HPP -#define LIFOSTORAGE_HPP - -#include -#include -#include -#include -#include -#include -#include - -#include "common/fixedSizeDeque.hpp" -#include "common/GEOS_RAJA_Interface.hpp" -#include "common/TimingMacros.hpp" - -#define LIFO_MARK_FUNCTION if( std::getenv( "LIFO_TRACE_ON" ) != NULL ) { GEOS_MARK_FUNCTION; } -#define LIFO_MARK_SCOPE( a ) if( std::getenv( "LIFO_TRACE_ON" ) != NULL ) { GEOS_MARK_SCOPE( a ); } - - -namespace geos -{ - -/** - * @brief Class to handle locks using 2 mutexes - */ -class twoMutexLock -{ -public: - /** - * @brief Construct a dual mutex lock and lock the mutexes. - * @param mutex1 First mutex of the dual mutex - * @param mutex2 Second mutex of the dual mutex - */ - twoMutexLock( std::mutex & mutex1, std::mutex & mutex2 ): - m_islocked( false ), m_mutex1( &mutex1 ), m_mutex2( &mutex2 ) - { - lock(); - } - - /** - * @brief Unlock the mutexes and destroy the locks. - */ - ~twoMutexLock() - { - unlock(); - } - - /** - * @brief Lock the two mutexes using std::lock is not already owning lock - */ - void lock() - { - if( m_islocked ) return; - std::lock( *m_mutex1, *m_mutex2 ); - m_islocked = true; - } - - /** - * @brief Unlock the two mutexes is owning them. - */ - void unlock() - { - if( !m_islocked ) return; - m_mutex1->unlock(); - m_mutex2->unlock(); - m_islocked = false; - } -private: - /// Indicate if the mutexes are owned by the lock - bool m_islocked; - /// Pointer to the first mutex - std::mutex *m_mutex1; - /// Pointer to the second mutex - std::mutex *m_mutex2; -}; - -/** - * @brief Class to handle locks using 4 mutexes - */ -class fourMutexLock -{ -public: - /** - * @brief Construct a four mutex lock and lock the mutexes. - * @param mutex1 First mutex of the dual mutex - * @param mutex2 Second mutex of the dual mutex - * @param mutex3 Third mutex of the dual mutex - * @param mutex4 Fourth mutex of the dual mutex - */ - fourMutexLock( std::mutex & mutex1, std::mutex & mutex2, std::mutex & mutex3, std::mutex & mutex4 ): - m_islocked( false ), m_mutex1( &mutex1 ), m_mutex2( &mutex2 ), m_mutex3( &mutex3 ), m_mutex4( &mutex4 ) - { - lock(); - } - - /** - * @brief Unlock the mutexes and destroy the locks. - */ - ~fourMutexLock() - { - unlock(); - } - - /** - * @brief Lock the two mutexes using std::lock is not already owning lock - */ - void lock() - { - if( m_islocked ) return; - std::lock( *m_mutex1, *m_mutex2, *m_mutex3, *m_mutex4 ); - m_islocked = true; - } - - /** - * @brief Unlock the two mutexes is owning them. - */ - void unlock() - { - if( !m_islocked ) return; - m_mutex1->unlock(); - m_mutex2->unlock(); - m_mutex3->unlock(); - m_mutex4->unlock(); - m_islocked = false; - } -private: - /// Indicate if the mutexes are owned by the lock - bool m_islocked; - /// Pointer to the first mutex - std::mutex *m_mutex1; - /// Pointer to the second mutex - std::mutex *m_mutex2; - /// Pointer to the third mutex - std::mutex *m_mutex3; - /// Pointer to the fourth mutex - std::mutex *m_mutex4; -}; - -/// Associate mutexes with the fixedSizeDeque -template< typename T > -class fixedSizeDequeAndMutexes : public fixedSizeDeque< T, int > -{ -public: - /// Mutex to protect access to the front - std::mutex m_frontMutex; - /// Mutex to protect access to the back - std::mutex m_backMutex; - /// Mutex to prevent two simulteaneous pop (can be an issue for last one) - std::mutex m_popMutex; - /// Mutex to prevent two simulteaneous emplace (can be an issue for last one) - std::mutex m_emplaceMutex; - /// Condition used to notify when queue is not full - std::condition_variable_any m_notFullCond; - /// Condition used to notify when queue is not empty - std::condition_variable_any m_notEmptyCond; - - /** - * Create a fixed size double ended queue with associated mutexes and condition variables. - * - * @param maxEntries Maximum number of array to store in the queue. - * @param valuesPerEntry Number of values in each array of the deque. - * @param space Space used to store que queue. - */ - fixedSizeDequeAndMutexes( int maxEntries, int valuesPerEntry, LvArray::MemorySpace space ): fixedSizeDeque< T, int >( maxEntries, valuesPerEntry, space, -#ifdef GEOS_USE_CUDA - camp::resources::Resource{ camp::resources::Cuda{} } -#else - camp::resources::Resource{ camp::resources::Host{} } -#endif - ) {} - - /** - * Emplace on front from array with locks. - * - * @param array The array to emplace - * @returns Event to sync with the memcpy. - */ - camp::resources::Event emplaceFront( arrayView1d< T > array ) - { - LIFO_MARK_FUNCTION; - camp::resources::Event e; - { - twoMutexLock lock( m_emplaceMutex, m_frontMutex ); - { - LIFO_MARK_SCOPE( waitingForBuffer ); - m_notFullCond.wait( lock, [ this ] { return !this->full(); } ); - } - { - LIFO_MARK_SCOPE( copy ); - e = fixedSizeDeque< T, int >::emplace_front( array.toSliceConst() ); - } - } - m_notEmptyCond.notify_all(); - return e; - } - - /** - * Pop from front to array with locks - * - * @param array The array to copy data into - * @returns Event to sync with the memcpy. - */ - camp::resources::Event popFront( arrayView1d< T > array ) - { - LIFO_MARK_FUNCTION; - camp::resources::Event e; - { - twoMutexLock lock( m_popMutex, m_frontMutex ); - { - LIFO_MARK_SCOPE( waitingForBuffer ); - m_notEmptyCond.wait( lock, [ this ] { return !this->empty(); } ); - } - // deadlock can occur if frontMutex is taken after an - // emplaceMutex (inside pushAsync) but this is prevented by the - // pushWait() in popAsync. - { - LIFO_MARK_SCOPE( copy ); - camp::resources::Resource r = this->getStream(); - e = LvArray::memcpy( r, array.toSlice(), this->front() ); - this->pop_front(); - } - } - m_notFullCond.notify_all(); - return e; - } - - /** - * Emplace front from back of given queue - * - * @param q2 The queue to copy data from. - */ - void emplaceFrontFromBack( fixedSizeDequeAndMutexes< T > & q2 ) - { - LIFO_MARK_FUNCTION; - { - fourMutexLock lock( m_emplaceMutex, q2.m_popMutex, m_frontMutex, q2.m_backMutex ); - while( this->full() || q2.empty() ) - { - { - LIFO_MARK_SCOPE( WaitForBufferToEmplace ); - m_notFullCond.wait( lock, [ this ] { return !this->full(); } ); - } - { - LIFO_MARK_SCOPE( WaitForBufferToPop ); - q2.m_notEmptyCond.wait( lock, [ &q2 ] { return !q2.empty(); } ); - } - } - LIFO_MARK_SCOPE( Transfert ); - this->emplace_front( q2.back() ).wait(); - q2.pop_back(); - } - q2.m_notFullCond.notify_all(); - m_notEmptyCond.notify_all(); - } - - /** - * Emplace back from front of given queue - * - * @param q2 The queue to copy data from. - */ - void emplaceBackFromFront( fixedSizeDequeAndMutexes< T > & q2 ) - { - LIFO_MARK_FUNCTION; - { - fourMutexLock lock( m_emplaceMutex, q2.m_popMutex, m_backMutex, q2.m_frontMutex ); - while( this->full() || q2.empty() ) - { - m_notFullCond.wait( lock, [ this ] { return !this->full(); } ); - q2.m_notEmptyCond.wait( lock, [ &q2 ] { return !q2.empty(); } ); - } - this->emplace_back( q2.front() ).wait(); - q2.pop_front(); - } - m_notEmptyCond.notify_all(); - q2.m_notFullCond.notify_all(); - } -}; - -/** - * This class is used to store in a LIFO way buffers, first on device, then on host, then on disk. - */ -template< typename T > -class lifoStorage -{ - -private: - - /// number of buffers to be inserted into the LIFO - int m_maxNumberOfBuffers; - /// size of one buffer in bytes - size_t m_bufferSize; - /// name used to store data on disk - std::string m_name; -#ifdef GEOS_USE_CUDA - /// ueue of data stored on device - fixedSizeDequeAndMutexes< T > m_deviceDeque; -#endif - /// ueue of data stored on host memory - fixedSizeDequeAndMutexes< T > m_hostDeque; - - /// counter of buffer stored in LIFO - int m_bufferCount; - /// counter of buffer stored on disk - int m_bufferOnDiskCount; - -#ifdef GEOS_USE_CUDA - // Events associated to ith copies to device buffer - std::vector< camp::resources::Event > m_pushToDeviceEvents; -#endif - // Futures associated to push to host in case we have no device buffers - std::vector< std::future< void > > m_pushToHostFutures; -#ifdef GEOS_USE_CUDA - // Events associated to ith copies from device buffer - std::vector< camp::resources::Event > m_popFromDeviceEvents; -#endif - // Futures associated to pop from host in case we have no device buffers - std::vector< std::future< void > > m_popFromHostFutures; - - /// condition used to tell m_worker queue has been filled or processed is stopped. - std::condition_variable m_task_queue_not_empty_cond[2]; - /// mutex to protect access to m_task_queue. - std::mutex m_task_queue_mutex[2]; - /// queue of task to be executed by m_worker. - std::deque< std::packaged_task< void() > > m_task_queue[2]; - /// thread to execute tasks. - std::thread m_worker[2]; - /// boolean to keep m_worker alive. - bool m_continue = true; - -public: - - - /** - * A LIFO storage will store numberOfBuffersToStoreDevice buffer on - * deevice, numberOfBuffersToStoreHost on host and the rest on disk. - * - * @param name Prefix of the files used to save the occurenncy of the saved buffer on disk. - * @param elemCnt Number of elments in the LvArray we want to store in the LIFO storage. - * @param numberOfBuffersToStoreOnDevice Maximum number of array to store on device memory. - * @param numberOfBuffersToStoreOnHost Maximum number of array to store on host memory. - * @param maxNumberOfBuffers Number of arrays expected to be stores in the LIFO. - */ - lifoStorage( std::string name, size_t elemCnt, int numberOfBuffersToStoreOnDevice, int numberOfBuffersToStoreOnHost, int maxNumberOfBuffers ): - m_maxNumberOfBuffers( maxNumberOfBuffers ), - m_bufferSize( elemCnt*sizeof( T ) ), - m_name( name ), -#ifdef GEOS_USE_CUDA - m_deviceDeque( numberOfBuffersToStoreOnDevice, elemCnt, LvArray::MemorySpace::cuda ), -#endif - m_hostDeque( numberOfBuffersToStoreOnHost, elemCnt, LvArray::MemorySpace::host ), - m_bufferCount( 0 ), m_bufferOnDiskCount( 0 ), -#ifdef GEOS_USE_CUDA - m_pushToDeviceEvents( (numberOfBuffersToStoreOnDevice > 0)?maxNumberOfBuffers:0 ), - m_pushToHostFutures( (numberOfBuffersToStoreOnDevice > 0)?0:maxNumberOfBuffers ), - m_popFromDeviceEvents( (numberOfBuffersToStoreOnDevice > 0)?maxNumberOfBuffers:0 ), - m_popFromHostFutures( (numberOfBuffersToStoreOnDevice > 0)?0:maxNumberOfBuffers ) -#else - m_pushToHostFutures( maxNumberOfBuffers ), - m_popFromHostFutures( maxNumberOfBuffers ) -#endif - { -#ifndef GEOS_USE_CUDA - GEOS_UNUSED_VAR( numberOfBuffersToStoreOnDevice ); -#endif - m_worker[0] = std::thread( &lifoStorage< T >::wait_and_consume_tasks, this, 0 ); - m_worker[1] = std::thread( &lifoStorage< T >::wait_and_consume_tasks, this, 1 ); - } - - /** - * Build a LIFO storage for a given LvArray array. - * - * @param name Prefix of the files used to save the occurenncy of the saved buffer on disk. - * @param array The LvArray that will be store in the LIFO. - * @param numberOfBuffersToStoreOnDevice Maximum number of array to store on device memory. - * @param numberOfBuffersToStoreOnHost Maximum number of array to store on host memory. - * @param maxNumberOfBuffers Number of arrays expected to be stores in the LIFO. - */ - lifoStorage( std::string name, arrayView1d< T > array, int numberOfBuffersToStoreOnDevice, int numberOfBuffersToStoreOnHost, int maxNumberOfBuffers ): - lifoStorage( name, array.size(), numberOfBuffersToStoreOnDevice, numberOfBuffersToStoreOnHost, maxNumberOfBuffers ) {} - - ~lifoStorage() - { - m_continue = false; - m_task_queue_not_empty_cond[0].notify_all(); - m_task_queue_not_empty_cond[1].notify_all(); - m_worker[0].join(); - m_worker[1].join(); - } - - /** - * Asynchroneously push a copy of the given LvArray into the LIFO - * - * @param array The LvArray to store in the LIFO, should match the size of the data used in constructor. - */ - void pushAsync( arrayView1d< T > array ) - { - LIFO_MARK_FUNCTION; - //To be sure 2 pushes are not mixed - pushWait(); - int id = m_bufferCount++; - GEOS_ERROR_IF( m_hostDeque.capacity() == 0, - "Cannot save on a Lifo without host storage (please set lifoSize, lifoOnDevice and lifoOnHost in xml file)" ); - -#ifdef GEOS_USE_CUDA - if( m_deviceDeque.capacity() > 0 ) - { - m_pushToDeviceEvents[id] = m_deviceDeque.emplaceFront( array ); - - if( m_maxNumberOfBuffers - id > (int)m_deviceDeque.capacity() ) - { - LIFO_MARK_SCOPE( geos::lifoStorage< T >::pushAddTasks ); - // This buffer will go to host memory, and maybe on disk - std::packaged_task< void() > task( std::bind( &lifoStorage< T >::deviceToHost, this, id ) ); - { - std::unique_lock< std::mutex > lock( m_task_queue_mutex[0] ); - m_task_queue[0].emplace_back( std::move( task ) ); - } - m_task_queue_not_empty_cond[0].notify_all(); - } - } - else -#endif - { - std::packaged_task< void() > task( std::bind( [ this ] ( int pushId, arrayView1d< T > pushedArray ) { - m_hostDeque.emplaceFront( pushedArray ); - - if( m_maxNumberOfBuffers - pushId > (int)m_hostDeque.capacity() ) - { - LIFO_MARK_SCOPE( geos::lifoStorage< T >::pushAddTasks ); - // This buffer will go to host memory, and maybe on disk - std::packaged_task< void() > t2( std::bind( &lifoStorage< T >::hostToDisk, this, pushId ) ); - { - std::unique_lock< std::mutex > l2( m_task_queue_mutex[1] ); - m_task_queue[1].emplace_back( std::move( t2 ) ); - } - m_task_queue_not_empty_cond[1].notify_all(); - } - }, id, array ) ); - m_pushToHostFutures[id] = task.get_future(); - { - std::unique_lock< std::mutex > lock( m_task_queue_mutex[0] ); - m_task_queue[0].emplace_back( std::move( task ) ); - } - m_task_queue_not_empty_cond[0].notify_all(); - } - - } - - /** - * Waits for last push to be terminated - */ - void pushWait() - { - LIFO_MARK_FUNCTION; - - if( m_bufferCount > 0 ) - { -#ifdef GEOS_USE_CUDA - if( m_deviceDeque.capacity() > 0 ) - { - m_pushToDeviceEvents[m_bufferCount-1].wait(); - } - else -#endif - { - m_pushToHostFutures[m_bufferCount-1].wait(); - } - } - } - - /** - * Push a copy of the given LvArray into the LIFO - * - * @param array The LvArray to store in the LIFO, should match the size of the data used in constructor. - */ - void push( arrayView1d< T > array ) - { - LIFO_MARK_FUNCTION; - pushAsync( array ); - pushWait(); - } - - /** - * Asynchroneously copy last data from the LIFO into the LvArray. - * - * @param array LvArray to store data from the LIFO into it. - */ - void popAsync( arrayView1d< T > array ) - { - LIFO_MARK_FUNCTION; - //wait the last push to avoid race condition - pushWait(); - // Ensure last pop is finished - popWait(); - int id = --m_bufferCount; -#ifdef GEOS_USE_CUDA - if( m_deviceDeque.capacity() > 0 ) - { - m_popFromDeviceEvents[id] = m_deviceDeque.popFront( array ); - - if( id >= (int)m_deviceDeque.capacity() ) - { - LIFO_MARK_SCOPE( geos::lifoStorage< T >::popAddTasks ); - // Trigger pull one buffer from host, and maybe from disk - std::packaged_task< void() > task( std::bind( &lifoStorage< T >::hostToDevice, this, id - m_deviceDeque.capacity() ) ); - { - std::unique_lock< std::mutex > lock( m_task_queue_mutex[0] ); - m_task_queue[0].emplace_back( std::move( task ) ); - } - m_task_queue_not_empty_cond[0].notify_all(); - } - } - else -#endif - { - std::packaged_task< void() > task( std::bind ( [ this ] ( int popId, arrayView1d< T > poppedArray ) { - m_hostDeque.popFront( poppedArray ); - - if( popId >= (int)m_hostDeque.capacity() ) - { - LIFO_MARK_SCOPE( geos::lifoStorage< T >::popAddTasks ); - // Trigger pull one buffer from host, and maybe from disk - std::packaged_task< void() > task2( std::bind( &lifoStorage< T >::diskToHost, this, popId - m_hostDeque.capacity() ) ); - { - std::unique_lock< std::mutex > lock2( m_task_queue_mutex[1] ); - m_task_queue[1].emplace_back( std::move( task2 ) ); - } - m_task_queue_not_empty_cond[1].notify_all(); - } - }, id, array ) ); - m_popFromHostFutures[id] = task.get_future(); - { - std::unique_lock< std::mutex > lock( m_task_queue_mutex[0] ); - m_task_queue[0].emplace_back( std::move( task ) ); - } - m_task_queue_not_empty_cond[0].notify_all(); - } - } - - /** - * Waits for last pop to be terminated - */ - void popWait() - { - LIFO_MARK_FUNCTION; - if( m_bufferCount < m_maxNumberOfBuffers ) - { -#ifdef GEOS_USE_CUDA - if( m_deviceDeque.capacity() > 0 ) - { -#ifdef GEOS_USE_CUDA - cudaEventSynchronize( m_popFromDeviceEvents[m_bufferCount].get< camp::resources::CudaEvent >().getCudaEvent_t() ); -#endif - } - else -#endif - { - m_popFromHostFutures[m_bufferCount].wait(); - } - } - } - - /** - * Copy last data from the LIFO into the LvArray. - * - * @param array LvArray to store data from the LIFO into it. - */ - void pop( arrayView1d< T > array ) - { - LIFO_MARK_FUNCTION; - popAsync( array ); - popWait(); - } - - -private: - - /** - * Copy data from device memory to host memory - * - * @param ID of the buffer to copy on host. - */ -#ifdef GEOS_USE_CUDA - void deviceToHost( int id ) - { - LIFO_MARK_FUNCTION; - // The copy to host will only start when the data is copied on device buffer - m_hostDeque.getStream().wait_for( const_cast< camp::resources::Event * >( &m_pushToDeviceEvents[id] ) ); - m_hostDeque.emplaceFrontFromBack( m_deviceDeque ); - - if( m_maxNumberOfBuffers - id > (int)(m_deviceDeque.capacity() + m_hostDeque.capacity()) ) - { - // This buffer will go to host then maybe to disk - std::packaged_task< void() > task( std::bind( &lifoStorage< T >::hostToDisk, this, id ) ); - { - std::unique_lock< std::mutex > lock( m_task_queue_mutex[1] ); - m_task_queue[1].emplace_back( std::move( task ) ); - } - m_task_queue_not_empty_cond[1].notify_all(); - } - } -#endif - - /** - * Copy data from host memory to disk - * - * @param id ID of the buffer to store on disk. - */ - void hostToDisk( int id ) - { - LIFO_MARK_FUNCTION; - { - twoMutexLock lock( m_hostDeque.m_popMutex, m_hostDeque.m_backMutex ); - writeOnDisk( m_hostDeque.back().dataIfContiguous(), id ); - m_hostDeque.pop_back(); - } - m_hostDeque.m_notFullCond.notify_all(); - } - - /** - * Copy data from host memory to device memory - * - * @param id ID of the buffer to load from host memory. - */ -#ifdef GEOS_USE_CUDA - void hostToDevice( int id ) - { - LIFO_MARK_FUNCTION; - m_hostDeque.getStream().wait_for( const_cast< camp::resources::Event * >( &m_popFromDeviceEvents[ id + m_deviceDeque.capacity() ] ) ); - m_deviceDeque.emplaceBackFromFront( m_hostDeque ); - - // enqueue diskToHost on worker #2 if needed - if( id >= (int)m_hostDeque.capacity() ) - { - // This buffer will go to host then to disk - std::packaged_task< void() > task( std::bind( &lifoStorage< T >::diskToHost, this, id - m_hostDeque.capacity() ) ); - { - std::unique_lock< std::mutex > lock( m_task_queue_mutex[1] ); - m_task_queue[1].emplace_back( std::move( task ) ); - } - m_task_queue_not_empty_cond[1].notify_all(); - } - } -#endif - - /** - * Copy data from disk to host memory - * - * @param id ID of the buffer to read on disk. - */ - void diskToHost( int id ) - { - LIFO_MARK_FUNCTION; - { - twoMutexLock lock( m_hostDeque.m_emplaceMutex, m_hostDeque.m_backMutex ); - m_hostDeque.m_notFullCond.wait( lock, [ this ] { return !( m_hostDeque.full() ); } ); - readOnDisk( const_cast< T * >(m_hostDeque.next_back().dataIfContiguous()), id ); - m_hostDeque.inc_back(); - } - m_hostDeque.m_notEmptyCond.notify_all(); - } - /** - * Checks if a directory exists. - * - * @param dirName Directory name to check existence of. - * @return true is dirName exists and is a directory. - */ - bool dirExists( const std::string & dirName ) - { - struct stat buffer; - return stat( dirName.c_str(), &buffer ) == 0; - } - - /** - * Write data on disk - * - * @param d Data to store on disk. - * @param id ID of the buffer to read on disk - */ - void writeOnDisk( const T * d, int id ) - { - LIFO_MARK_FUNCTION; - std::string fileName = GEOS_FMT( "{}_{:08}.dat", m_name, id ); - int lastDirSeparator = fileName.find_last_of( "/\\" ); - std::string dirName = fileName.substr( 0, lastDirSeparator ); - if( string::npos != (size_t)lastDirSeparator && !dirExists( dirName )) - makeDirsForPath( dirName ); - - std::ofstream wf( fileName, std::ios::out | std::ios::binary ); - GEOS_ERROR_IF( !wf || wf.fail() || !wf.is_open(), - "Could not open file "<< fileName << " for writting" ); - wf.write( (const char *)d, m_bufferSize ); - GEOS_ERROR_IF( wf.bad() || wf.fail(), - "An error occured while writting "<< fileName ); - wf.close(); - } - - /** - * Read data from disk - * - * @param d Buffer to store data read from disk. - * @param id ID of the buffer on disk. - */ - void readOnDisk( T * d, int id ) - { - LIFO_MARK_FUNCTION; - std::string fileName = GEOS_FMT( "{}_{:08}.dat", m_name, id ); - std::ifstream wf( fileName, std::ios::in | std::ios::binary ); - GEOS_ERROR_IF( !wf, - "Could not open file "<< fileName << " for reading" ); - wf.read( (char *)d, m_bufferSize ); - wf.close(); - remove( fileName.c_str() ); - } - - /** - * Execute the tasks pushed in m_task_queue in the given order. - * - * @param queueId index of the queue (0: queue to handle device/host transfers; 1: host/disk transfers) - */ - void wait_and_consume_tasks( int queueId ) - { - LIFO_MARK_FUNCTION; - while( m_continue ) - { - std::unique_lock< std::mutex > lock( m_task_queue_mutex[queueId] ); - { - LIFO_MARK_SCOPE( waitForTask ); - m_task_queue_not_empty_cond[queueId].wait( lock, [ this, &queueId ] { return !( m_task_queue[queueId].empty() && m_continue ); } ); - } - if( m_continue == false ) break; - std::packaged_task< void() > task( std::move( m_task_queue[queueId].front() ) ); - m_task_queue[queueId].pop_front(); - lock.unlock(); - { - LIFO_MARK_SCOPE( runningTask ); - task(); - } - } - } -}; -} -#endif // LIFOSTORAGE_HPP diff --git a/src/coreComponents/common/unitTests/CMakeLists.txt b/src/coreComponents/common/unitTests/CMakeLists.txt index 8f89e886638..88f0cb14e05 100644 --- a/src/coreComponents/common/unitTests/CMakeLists.txt +++ b/src/coreComponents/common/unitTests/CMakeLists.txt @@ -4,8 +4,8 @@ set(gtest_geosx_tests testDataTypes.cpp - testTypeDispatch.cpp testFixedSizeDeque.cpp + testTypeDispatch.cpp testLifoStorage.cpp ) diff --git a/src/coreComponents/common/unitTests/testFixedSizeDeque.cpp b/src/coreComponents/common/unitTests/testFixedSizeDeque.cpp index cfe1bc49061..605892b7b54 100644 --- a/src/coreComponents/common/unitTests/testFixedSizeDeque.cpp +++ b/src/coreComponents/common/unitTests/testFixedSizeDeque.cpp @@ -1,5 +1,5 @@ -#include "mainInterface/initialization.hpp" -#include "common/fixedSizeDeque.hpp" +#include "common/DataTypes.hpp" +#include "common/FixedSizeDeque.hpp" #include "LvArray/src/Array.hpp" #include "LvArray/src/MallocBuffer.hpp" #if defined(LVARRAY_USE_CHAI) @@ -15,7 +15,7 @@ TEST( FixedSizeDequeTest, ZeroSizedDeque ) int maxArray = 0; int elemCnt = 10; camp::resources::Resource stream = { camp::resources::Host{} }; - fixedSizeDeque< float, int > empty_deque( maxArray, elemCnt, LvArray::MemorySpace::host, stream ); + FixedSizeDeque< float, int > empty_deque( maxArray, elemCnt, LvArray::MemorySpace::host, stream ); EXPECT_EQ( true, empty_deque.empty()); EXPECT_EQ( true, empty_deque.full()); } @@ -25,7 +25,7 @@ TEST( FixedSizeDequeTest, emplace_back ) int maxArray = 2; int elemCnt = 10; camp::resources::Resource stream = { camp::resources::Host{} }; - fixedSizeDeque< float, int > deque( maxArray, elemCnt, LvArray::MemorySpace::host, stream ); + FixedSizeDeque< float, int > deque( maxArray, elemCnt, LvArray::MemorySpace::host, stream ); EXPECT_EQ( true, deque.empty()); EXPECT_EQ( false, deque.full()); @@ -44,7 +44,7 @@ TEST( FixedSizeDequeTest, emplace_front_and_back ) int maxArray = 2; int elemCnt = 10; camp::resources::Resource stream = { camp::resources::Host{} }; - fixedSizeDeque< float, int > deque( maxArray, elemCnt, LvArray::MemorySpace::host, stream ); + FixedSizeDeque< float, int > deque( maxArray, elemCnt, LvArray::MemorySpace::host, stream ); EXPECT_EQ( true, deque.empty()); EXPECT_EQ( false, deque.full()); @@ -58,12 +58,31 @@ TEST( FixedSizeDequeTest, emplace_front_and_back ) EXPECT_EQ( true, deque.full()); } +TEST( FixedSizeDequeTest, LargeSizedDeque ) +{ + int maxArray = 10000; + int elemCnt = 100000; + camp::resources::Resource stream = { camp::resources::Host{} }; + FixedSizeDeque< float, int > deque( maxArray, elemCnt, LvArray::MemorySpace::host, stream ); + EXPECT_EQ( true, deque.empty()); + EXPECT_EQ( false, deque.full()); + + array1d< float > array( elemCnt ); + deque.emplace_front( array.toSliceConst() ); + EXPECT_EQ( false, deque.empty()); + EXPECT_EQ( false, deque.full()); + + deque.emplace_back( array.toSliceConst() ); + EXPECT_EQ( false, deque.empty()); + EXPECT_EQ( false, deque.full()); +} + TEST( FixedSizeDequeTest, emplace_and_pop ) { int maxArray = 2; int elemCnt = 10; camp::resources::Resource stream = { camp::resources::Host{} }; - fixedSizeDeque< float, int > deque( maxArray, elemCnt, LvArray::MemorySpace::host, stream ); + FixedSizeDeque< float, int > deque( maxArray, elemCnt, LvArray::MemorySpace::host, stream ); EXPECT_EQ( true, deque.empty()); EXPECT_EQ( false, deque.full()); @@ -105,7 +124,7 @@ TEST( FixedSizeDequeTest, emplace_and_pop_front ) int maxArray = 10; int elemCnt = 10; camp::resources::Resource stream = { camp::resources::Host{} }; - fixedSizeDeque< float, int > deque( maxArray, elemCnt, LvArray::MemorySpace::host, stream ); + FixedSizeDeque< float, int > deque( maxArray, elemCnt, LvArray::MemorySpace::host, stream ); EXPECT_EQ( true, deque.empty()); EXPECT_EQ( false, deque.full()); @@ -151,7 +170,7 @@ TEST( FixedSizeDequeTest, emplace_and_pop_front_cuda ) int maxArray = 10; int elemCnt = 10; camp::resources::Resource stream = { camp::resources::Cuda{} }; - fixedSizeDeque< float, int > deque( maxArray, elemCnt, LvArray::MemorySpace::cuda, stream ); + FixedSizeDeque< float, int > deque( maxArray, elemCnt, LvArray::MemorySpace::cuda, stream ); EXPECT_EQ( true, deque.empty()); EXPECT_EQ( false, deque.full()); diff --git a/src/coreComponents/common/unitTests/testLifoStorage.cpp b/src/coreComponents/common/unitTests/testLifoStorage.cpp index 0559b86c269..b2dfced38ff 100644 --- a/src/coreComponents/common/unitTests/testLifoStorage.cpp +++ b/src/coreComponents/common/unitTests/testLifoStorage.cpp @@ -1,5 +1,6 @@ #include "mainInterface/initialization.hpp" -#include "common/lifoStorage.hpp" +#define LIFO_DISABLE_CALIPER +#include "common/LifoStorage.hpp" #include "LvArray/src/Array.hpp" #include "LvArray/src/MallocBuffer.hpp" #if defined(LVARRAY_USE_CHAI) @@ -57,8 +58,8 @@ struct RAJAHelper< parallelHostPolicy > template< unsigned long THREADS_PER_BLOCK > using devicePolicy = RAJA::cuda_exec< THREADS_PER_BLOCK >; -template< unsigned long N > -struct RAJAHelper< RAJA::cuda_exec< N > > +template< typename X, typename Y, size_t BLOCK_SIZE, bool ASYNC > +struct RAJAHelper< RAJA::policy::cuda::cuda_exec_explicit< X, Y, BLOCK_SIZE, ASYNC > > { using ReducePolicy = RAJA::cuda_reduce; using AtomicPolicy = RAJA::cuda_atomic; @@ -75,7 +76,7 @@ void testLifoStorage( int elemCnt, int numberOfElementsOnDevice, int numberOfEle array1d< float > array( elemCnt ); array.move( local::RAJAHelper< POLICY >::space ); - lifoStorage< float > lifo( "lifo", array, numberOfElementsOnDevice, numberOfElementsOnHost, totalNumberOfBuffers ); + LifoStorage< float, localIndex > lifo( "lifo", array, numberOfElementsOnDevice, numberOfElementsOnHost, totalNumberOfBuffers ); for( int j = 0; j < totalNumberOfBuffers; j++ ) { @@ -96,12 +97,39 @@ void testLifoStorage( int elemCnt, int numberOfElementsOnDevice, int numberOfEle } } +template< typename POLICY > +void testLifoStorageBig( int elemCnt, int numberOfElementsOnDevice, int numberOfElementsOnHost, int totalNumberOfBuffers ) +{ + + array1d< float > array( elemCnt ); + array.move( local::RAJAHelper< POLICY >::space ); + LifoStorage< float, localIndex > lifo( "lifo", array, numberOfElementsOnDevice, numberOfElementsOnHost, totalNumberOfBuffers ); + + for( int j = 0; j < 10; j++ ) + { + + float * dataPointer = array.data(); + forAll< POLICY >( elemCnt, [dataPointer, j, elemCnt] GEOS_HOST_DEVICE ( int i ) { dataPointer[ i ] = j*elemCnt+i; } ); + lifo.push( array ); + } + + for( int j = 0; j < 10; j++ ) + { + lifo.pop( array ); + float * dataPointer = array.data(); + forAll< POLICY >( elemCnt, [dataPointer, j, elemCnt] GEOS_HOST_DEVICE ( int i ) + { + PORTABLE_EXPECT_EQ( dataPointer[ i ], (float)(10-j-1)*elemCnt+i ); + } ); + } +} + template< typename POLICY > void testLifoStorageAsync( int elemCnt, int numberOfElementsOnDevice, int numberOfElementsOnHost, int totalNumberOfBuffers ) { array1d< float > array( elemCnt ); array.move( local::RAJAHelper< POLICY >::space ); - lifoStorage< float > lifo( "lifo", array, numberOfElementsOnDevice, numberOfElementsOnHost, totalNumberOfBuffers ); + LifoStorage< float, localIndex > lifo( "lifo", array, numberOfElementsOnDevice, numberOfElementsOnHost, totalNumberOfBuffers ); for( int j = 0; j < totalNumberOfBuffers; j++ ) { @@ -147,6 +175,27 @@ TEST( LifoStorageTest, LifoStorageBufferOnCUDA ) testLifoStorage< local::devicePolicy< 32 > >( 10, 2, 3, 10 ); } +TEST( LifoStorageTest, LifoStorageBufferOnCUDAlarge ) +{ + testLifoStorageBig< parallelDevicePolicy< > >( 1000000, 2, 3, 10000 ); +} + +TEST( LifoStorageTest, LifoStorageBufferOnCUDAlargeAutoSizeHost ) +{ + testLifoStorageBig< parallelDevicePolicy< > >( 1000000, 2, -80, 10000 ); +} + +TEST( LifoStorageTest, LifoStorageBufferOnCUDAlargeAutoSizeDevice ) +{ + testLifoStorageBig< parallelDevicePolicy< > >( 1000000, -80, 3, 10000 ); +} + +TEST( LifoStorageTest, LifoStorageBufferOnCUDAlargeAutoSizeBoth ) +{ + testLifoStorageBig< parallelDevicePolicy< > >( 1000000, -80, -80, 10000 ); +} + + TEST( LifoStorageTest, LifoStorageBufferOnCUDANoDeviceBuffer ) { testLifoStorage< local::devicePolicy< 32 > >( 10, 0, 3, 10 ); diff --git a/src/coreComponents/common/unitTests/testTypeDispatch.cpp b/src/coreComponents/common/unitTests/testTypeDispatch.cpp index 3a56d7a50b6..837f09b7ce8 100644 --- a/src/coreComponents/common/unitTests/testTypeDispatch.cpp +++ b/src/coreComponents/common/unitTests/testTypeDispatch.cpp @@ -55,60 +55,121 @@ static_assert( std::is_same< types::ArrayTypes< types::TypeList< int, double >, > >::value, "ArrayTypes< , <1, 2> > failed" ); +namespace typeDispatchTest +{ +class A +{ +public: + A() = default; + virtual ~A() = default; + virtual string getTypeName() const {return "A";} +}; -template< typename TYPES > -class TypeDispatchTest : public ::testing::Test +class B : public A { -protected: +public: + virtual string getTypeName() const { return "B";} +}; - using TypeList = TYPES; +class C : public A +{ +public: + virtual string getTypeName() const { return "C";} +}; - template< typename ... Ts > - void matchAllTypes( types::TypeList< Ts... >, - types::TypeList<> ) - {} +class D : public A +{ +public: + virtual string getTypeName() const { return "D";} +}; - template< typename ... Ts, typename T, typename ... Rest > - void matchAllTypes( types::TypeList< Ts... > const list, - types::TypeList< T, Rest... > ) - { - // Test both that dispatch occurred and that type given to lambda matches expected - bool const success = types::dispatch( list, typeid( T ), false, []( auto v ) - { - EXPECT_TRUE( ( std::is_same< decltype( v ), T >::value ) ) << "Dispatch matched the wrong type"; - } ); - EXPECT_TRUE( success ) << "Dispatch failed to match the type"; - matchAllTypes( list, types::TypeList< Rest... >{} ); - } - - template< typename ... Ts > - void checkTypeNotInList( types::TypeList< Ts... > const list ) +} + +template< typename TRUE_TYPES_LIST, size_t I = 0, typename ... Ts > +typename std::enable_if< I == sizeof...(Ts), void >::type +checkType( types::TypeList< Ts... > ) +{ + return; +} + +template< typename TRUE_TYPES_LIST, size_t I = 0, typename ... Ts > +typename std::enable_if< (I < sizeof...(Ts)), void>::type +checkType( types::TypeList< Ts... > list ) +{ + EXPECT_TRUE( ( std::is_same< camp::at_t< decltype(list), camp::num< I > >, + camp::at_t< TRUE_TYPES_LIST, camp::num< I > > >::value ) ) << "Dispatch matched the wrong type"; + // check next element + checkType< TRUE_TYPES_LIST, I + 1 >( list ); +} + +template< typename TRUE_TYPES_LIST, typename ... Ts, typename ... Vs > +void testDispatch( types::TypeList< Ts... > const list, + Vs && ... objects ) +{ + bool const result = types::dispatch( list, []( auto tupleOfTypes ) { - struct TypeNotInList {}; - bool const success = types::dispatch( list, typeid( TypeNotInList ), false, []( auto ){} ); - EXPECT_FALSE( success ) << "Dispatch matched a type not in list"; - } + checkType< TRUE_TYPES_LIST >( tupleOfTypes ); + }, std::forward< Vs >( objects ) ... ); + EXPECT_TRUE( result ) << "Dispatch failed to match the type"; +} + +TEST( testDispatch, DispatchSimpleTypesSingles ) +{ + using Types = types::ListofTypeList< types::TypeList< int, float > >; + + int a = 0; + testDispatch< types::TypeList< int > >( Types{}, a ); + + float b = 0; + testDispatch< types::TypeList< float > >( Types{}, b ); + +} -}; -struct X {}; +TEST( testDispatch, DispatchSimpleTypes ) +{ + using Types = types::TypeList< types::TypeList< int, int >, + types::TypeList< int, float > >; -using TypeLists = ::testing::Types< - types::TypeList< int >, - types::TypeList< int, double, X >, - types::ArrayTypes< types::TypeList< int, double, X >, types::DimsUpTo< 3 > > - >; + int a = 0; + float b = 0.0; -TYPED_TEST_SUITE( TypeDispatchTest, TypeLists, ); + testDispatch< types::TypeList< int, float > >( Types{}, a, b ); + +} -TYPED_TEST( TypeDispatchTest, MatchAllTypes ) +TEST( testDispatch, DispatchVirtualTypePairs ) { - using Types = typename TestFixture::TypeList; - this->matchAllTypes( Types{}, Types{} ); + + using namespace typeDispatchTest; + using Types = types::TypeList< types::TypeList< B, B >, + types::TypeList< B, C >, + types::TypeList< B, D > >; + + std::unique_ptr< A > b = std::make_unique< B >(); + std::unique_ptr< A > c = std::make_unique< C >(); + + testDispatch< types::TypeList< B, C > >( Types{}, *b, *c ); +} + +TEST( testDispatch, DispatchVirtualTypeTriplets ) +{ + + using namespace typeDispatchTest; + using Types = types::TypeList< types::TypeList< B, B, B >, + types::TypeList< B, C, C >, + types::TypeList< B, C, D > >; + + std::unique_ptr< A > b = std::make_unique< B >(); + std::unique_ptr< A > c = std::make_unique< C >(); + std::unique_ptr< A > d = std::make_unique< D >(); + + testDispatch< types::TypeList< B, C, D > >( Types{}, *b, *c, *d ); } -TYPED_TEST( TypeDispatchTest, CheckTypeNotInList ) +int main( int ac, char * av[] ) { - using Types = typename TestFixture::TypeList; - this->checkTypeNotInList( Types{} ); + ::testing::InitGoogleTest( &ac, av ); + int const result = RUN_ALL_TESTS(); + return result; } diff --git a/src/coreComponents/constitutive/CMakeLists.txt b/src/coreComponents/constitutive/CMakeLists.txt index b6e586add40..dae5dfe621e 100644 --- a/src/coreComponents/constitutive/CMakeLists.txt +++ b/src/coreComponents/constitutive/CMakeLists.txt @@ -28,12 +28,12 @@ set( constitutive_headers fluid/multifluid/MultiFluidFields.hpp fluid/multifluid/PVTDriver.hpp fluid/multifluid/PVTDriverRunTest.hpp - fluid/multifluid/blackOil/BlackOilFluidBase.hpp + fluid/multifluid/blackOil/BlackOilFluidBase.hpp fluid/multifluid/blackOil/BlackOilFluid.hpp fluid/multifluid/blackOil/DeadOilFluid.hpp - fluid/multifluid/blackOil/PVTOData.hpp - fluid/multifluid/CO2Brine/CO2BrineFluid.hpp - fluid/multifluid/CO2Brine/PhaseModel.hpp + fluid/multifluid/blackOil/PVTOData.hpp + fluid/multifluid/CO2Brine/CO2BrineFluid.hpp + fluid/multifluid/CO2Brine/PhaseModel.hpp fluid/multifluid/CO2Brine/functions/PhillipsBrineDensity.hpp fluid/multifluid/CO2Brine/functions/PhillipsBrineViscosity.hpp fluid/multifluid/CO2Brine/functions/EzrokhiBrineDensity.hpp @@ -41,8 +41,8 @@ set( constitutive_headers fluid/multifluid/CO2Brine/functions/CO2Solubility.hpp fluid/multifluid/CO2Brine/functions/FenghourCO2Viscosity.hpp fluid/multifluid/CO2Brine/functions/FlashModelBase.hpp - fluid/multifluid/CO2Brine/functions/PVTFunctionBase.hpp - fluid/multifluid/CO2Brine/functions/NoOpPVTFunction.hpp + fluid/multifluid/CO2Brine/functions/PVTFunctionBase.hpp + fluid/multifluid/CO2Brine/functions/NoOpPVTFunction.hpp fluid/multifluid/CO2Brine/functions/PVTFunctionHelpers.hpp fluid/multifluid/CO2Brine/functions/SpanWagnerCO2Density.hpp fluid/multifluid/CO2Brine/functions/BrineEnthalpy.hpp @@ -50,8 +50,10 @@ set( constitutive_headers fluid/multifluid/CO2Brine/functions/CO2EOSSolver.hpp fluid/multifluid/CO2Brine/functions/PureWaterProperties.hpp fluid/multifluid/CO2Brine/functions/WaterDensity.hpp + fluid/multifluid/compositional/functions/CubicEOSPhaseModel.hpp + fluid/multifluid/compositional/functions/KValueInitialization.hpp + fluid/multifluid/compositional/functions/NegativeTwoPhaseFlash.hpp fluid/multifluid/compositional/functions/RachfordRice.hpp - fluid/multifluid/compositional/functions/CubicEOSPhaseModel.hpp fluid/multifluid/reactive/ReactiveBrineFluid.hpp fluid/multifluid/reactive/ReactiveMultiFluid.hpp fluid/multifluid/reactive/ReactiveMultiFluidFields.hpp @@ -60,10 +62,10 @@ set( constitutive_headers fluid/multifluid/reactive/chemicalReactions/EquilibriumReactions.hpp fluid/multifluid/reactive/chemicalReactions/KineticReactions.hpp fluid/multifluid/reactive/chemicalReactions/ReactionsBase.hpp - fluid/singlefluid/CompressibleSinglePhaseFluid.hpp + fluid/singlefluid/CompressibleSinglePhaseFluid.hpp fluid/singlefluid/ParticleFluid.hpp fluid/singlefluid/ParticleFluidBase.hpp - fluid/singlefluid/ParticleFluidSelector.hpp + fluid/singlefluid/ParticleFluidSelector.hpp fluid/singlefluid/ProppantSlurryFluid.hpp fluid/singlefluid/SingleFluidBase.hpp fluid/singlefluid/SingleFluidFields.hpp @@ -71,7 +73,7 @@ set( constitutive_headers fluid/singlefluid/SlurryFluidFields.hpp fluid/singlefluid/SingleFluidSelector.hpp fluid/singlefluid/SlurryFluidSelector.hpp - fluid/singlefluid/ThermalCompressibleSinglePhaseFluid.hpp + fluid/singlefluid/ThermalCompressibleSinglePhaseFluid.hpp permeability/CarmanKozenyPermeability.hpp permeability/ConstantPermeability.hpp permeability/ExponentialDecayPermeability.hpp @@ -163,7 +165,7 @@ set( constitutive_sources fluid/multifluid/blackOil/DeadOilFluid.cpp fluid/multifluid/blackOil/PVTDriverRunTestDeadOilFluid.cpp fluid/multifluid/blackOil/PVTOData.cpp - fluid/multifluid/CO2Brine/CO2BrineFluid.cpp + fluid/multifluid/CO2Brine/CO2BrineFluid.cpp fluid/multifluid/CO2Brine/PVTDriverRunTestCO2BrinePhillipsFluid.cpp fluid/multifluid/CO2Brine/PVTDriverRunTestCO2BrinePhillipsThermalFluid.cpp fluid/multifluid/CO2Brine/PVTDriverRunTestCO2BrineEzrokhiFluid.cpp @@ -181,15 +183,14 @@ set( constitutive_sources fluid/multifluid/CO2Brine/functions/CO2EOSSolver.cpp fluid/multifluid/CO2Brine/functions/PureWaterProperties.cpp fluid/multifluid/CO2Brine/functions/WaterDensity.cpp - fluid/multifluid/compositional/functions/RachfordRice.cpp fluid/multifluid/reactive/ReactiveBrineFluid.cpp fluid/multifluid/reactive/ReactiveMultiFluid.cpp fluid/multifluid/reactive/ReactiveFluidDriver.cpp fluid/multifluid/reactive/chemicalReactions/EquilibriumReactions.cpp fluid/multifluid/reactive/chemicalReactions/KineticReactions.cpp fluid/multifluid/reactive/chemicalReactions/ReactionsBase.cpp - fluid/singlefluid/CompressibleSinglePhaseFluid.cpp - fluid/singlefluid/ParticleFluid.cpp + fluid/singlefluid/CompressibleSinglePhaseFluid.cpp + fluid/singlefluid/ParticleFluid.cpp fluid/singlefluid/ParticleFluidBase.cpp fluid/singlefluid/ProppantSlurryFluid.cpp fluid/singlefluid/SingleFluidBase.cpp @@ -257,7 +258,7 @@ if( ENABLE_PVTPackage ) set( constitutive_sources ${constitutive_sources} fluid/multifluid/compositional/CompositionalMultiphaseFluid.cpp - fluid/multifluid/compositional/PVTDriverRunTestCompositionalMultiphaseFluid.cpp + fluid/multifluid/compositional/PVTDriverRunTestCompositionalMultiphaseFluid.cpp ) add_subdirectory( PVTPackage ) diff --git a/src/coreComponents/constitutive/capillaryPressure/JFunctionCapillaryPressure.cpp b/src/coreComponents/constitutive/capillaryPressure/JFunctionCapillaryPressure.cpp index 88ad2be0ccb..fb14237bc8c 100644 --- a/src/coreComponents/constitutive/capillaryPressure/JFunctionCapillaryPressure.cpp +++ b/src/coreComponents/constitutive/capillaryPressure/JFunctionCapillaryPressure.cpp @@ -247,7 +247,19 @@ void JFunctionCapillaryPressure::saveConvergedRockState( arrayView2d< real64 con permeability = convergedPermeability[ei][0][2]; } - real64 const porosityOverPermeability = pow( convergedPorosity[ei][0], porosityExponent ) / pow( permeability, permeabilityExponent ); + // here we compute an average of the porosity over quadrature points + // this average is exact for tets, regular pyramids/wedges/hexes, or for VEM + real64 porosityAveragedOverQuadraturePoints = 0; + for( integer i = 0; i < convergedPorosity.size( 1 ); ++i ) + { + porosityAveragedOverQuadraturePoints += convergedPorosity[ei][i]; + } + porosityAveragedOverQuadraturePoints /= convergedPorosity.size( 1 ); + porosityAveragedOverQuadraturePoints = + LvArray::math::max( porosityAveragedOverQuadraturePoints, LvArray::NumericLimits< real64 >::epsilon ); + + real64 const porosityOverPermeability = pow( porosityAveragedOverQuadraturePoints, porosityExponent ) + / pow( permeability, permeabilityExponent ); // units: // ------ diff --git a/src/coreComponents/constitutive/docs/constitutiveDeveloperGuide.rst b/src/coreComponents/constitutive/docs/constitutiveDeveloperGuide.rst index b9c26617fd9..224903eabfc 100644 --- a/src/coreComponents/constitutive/docs/constitutiveDeveloperGuide.rst +++ b/src/coreComponents/constitutive/docs/constitutiveDeveloperGuide.rst @@ -32,7 +32,7 @@ necessary for multiphase fluid models with properties defined for each component For example, a single-phase fluid model where density and viscosity are functions of the fluid pressure has the following members: -.. literalinclude:: /coreComponents/constitutive/fluid/SingleFluidBase.hpp +.. literalinclude:: /coreComponents/constitutive/fluid/singlefluid/SingleFluidBase.hpp :language: c++ :start-after: //START_SPHINX_INCLUDE_00 :end-before: //END_SPHINX_INCLUDE_00 @@ -44,7 +44,7 @@ This function also resizes all fields based on the size of the subregion and the points on it, by calling ``CONSTITUTIVE_MODEL::allocateConstitutiveData``. For the single phase fluid example used before, this call is: -.. literalinclude:: /coreComponents/constitutive/fluid/SingleFluidBase.cpp +.. literalinclude:: /coreComponents/constitutive/fluid/singlefluid/SingleFluidBase.cpp :language: c++ :start-after: //START_SPHINX_INCLUDE_00 :end-before: //END_SPHINX_INCLUDE_00 @@ -57,7 +57,7 @@ for each constitutive model class, a corresponding `nameOfTheModelUpdates`, whic ``LvArray::arrayView`` containers to the data, can be captured by value inside computational kernels. For example, for the single phase fluid model `Updates` are: -.. literalinclude:: /coreComponents/constitutive/fluid/SingleFluidBase.hpp +.. literalinclude:: /coreComponents/constitutive/fluid/singlefluid/SingleFluidBase.hpp :language: c++ :start-after: //START_SPHINX_INCLUDE_01 :end-before: //END_SPHINX_INCLUDE_01 @@ -65,7 +65,7 @@ For example, for the single phase fluid model `Updates` are: Because `Updates` classes are responsible for updating the fields owned by the constitutive models, they also implement all functions needed to perform property updates, such as: -.. literalinclude:: /coreComponents/constitutive/fluid/SingleFluidBase.hpp +.. literalinclude:: /coreComponents/constitutive/fluid/singlefluid/SingleFluidBase.hpp :language: c++ :start-after: //START_SPHINX_INCLUDE_02 :end-before: //END_SPHINX_INCLUDE_02 diff --git a/src/coreComponents/constitutive/fluid/multifluid/MultiFluidUtils.hpp b/src/coreComponents/constitutive/fluid/multifluid/MultiFluidUtils.hpp index deff637e91f..3d98de053e0 100644 --- a/src/coreComponents/constitutive/fluid/multifluid/MultiFluidUtils.hpp +++ b/src/coreComponents/constitutive/fluid/multifluid/MultiFluidUtils.hpp @@ -76,6 +76,19 @@ struct MultiFluidVarView { MultiFluidVarView() = default; + GEOS_HOST_DEVICE + MultiFluidVarView ( MultiFluidVarView const & src ): + value( src.value ), + derivs( src.derivs ) + {} + + GEOS_HOST_DEVICE + MultiFluidVarView ( ArrayView< T, NDIM, USD > const & valueSrc, + ArrayView< T, NDIM + 1, USD_DC > const & derivsSrc ): + value( valueSrc ), + derivs( derivsSrc ) + {}; + ArrayView< T, NDIM, USD > value; ///< View into property values ArrayView< T, NDIM + 1, USD_DC > derivs; ///< View into property derivatives w.r.t. pressure, temperature, compositions diff --git a/src/coreComponents/constitutive/fluid/multifluid/compositional/CompositionalMultiphaseFluid.hpp b/src/coreComponents/constitutive/fluid/multifluid/compositional/CompositionalMultiphaseFluid.hpp index 2a22e1cd4cd..d99f5bfa56e 100644 --- a/src/coreComponents/constitutive/fluid/multifluid/compositional/CompositionalMultiphaseFluid.hpp +++ b/src/coreComponents/constitutive/fluid/multifluid/compositional/CompositionalMultiphaseFluid.hpp @@ -279,6 +279,7 @@ CompositionalMultiphaseFluid::KernelWrapper:: GEOS_UNUSED_VAR( phaseEnthalpy, phaseInternalEnergy ); #if defined(GEOS_DEVICE_COMPILE) GEOS_ERROR( "This function cannot be used on GPU" ); + GEOS_UNUSED_VAR( m_fluid ); GEOS_UNUSED_VAR( pressure ); GEOS_UNUSED_VAR( temperature ); GEOS_UNUSED_VAR( composition ); diff --git a/src/coreComponents/constitutive/fluid/multifluid/compositional/functions/CubicEOSPhaseModel.hpp b/src/coreComponents/constitutive/fluid/multifluid/compositional/functions/CubicEOSPhaseModel.hpp index 700917c5989..9b8402c17af 100644 --- a/src/coreComponents/constitutive/fluid/multifluid/compositional/functions/CubicEOSPhaseModel.hpp +++ b/src/coreComponents/constitutive/fluid/multifluid/compositional/functions/CubicEOSPhaseModel.hpp @@ -106,10 +106,10 @@ struct CubicEOSPhaseModel * @param[in] criticalTemperature critical temperatures * @param[in] acentricFactor acentric factors * @param[in] binaryInteractionCoefficients binary coefficients (currently not implemented) - * @param[in] aPureCoefficient pure coefficient (A) - * @param[in] bPureCoefficient pure coefficient (B) - * @param[in] aMixtureCoefficient mixture coefficient (A) - * @param[in] bMixtureCoefficient mixture coefficient (B) + * @param[out] aPureCoefficient pure coefficient (A) + * @param[out] bPureCoefficient pure coefficient (B) + * @param[out] aMixtureCoefficient mixture coefficient (A) + * @param[out] bMixtureCoefficient mixture coefficient (B) */ GEOS_HOST_DEVICE GEOS_FORCE_INLINE @@ -127,6 +127,50 @@ struct CubicEOSPhaseModel real64 & aMixtureCoefficient, real64 & bMixtureCoefficient ); + /** + * @brief Compute the mixture coefficients derivatives + * @param[in] numComps number of components + * @param[in] pressure pressure + * @param[in] temperature temperature + * @param[in] composition composition of the phase + * @param[in] criticalPressure critical pressures + * @param[in] criticalTemperature critical temperatures + * @param[in] acentricFactor acentric factors + * @param[in] binaryInteractionCoefficients binary coefficients (currently not implemented) + * @param[in] aPureCoefficient pure coefficient (A) + * @param[in] bPureCoefficient pure coefficient (B) + * @param[in] aMixtureCoefficient mixture coefficient (A) + * @param[in] bMixtureCoefficient mixture coefficient (B) + * @param[out] daMixtureCoefficient_dp derivative of mixture coefficient (A) wrt pressure + * @param[out] dbMixtureCoefficient_dp derivative of mixture coefficient (B) wrt pressure + * @param[out] daMixtureCoefficient_dt derivative of mixture coefficient (A) wrt temperature + * @param[out] dbMixtureCoefficient_dt derivative of mixture coefficient (B) wrt temperature + * @param[out] daMixtureCoefficient_dz derivative of mixture coefficient (A) wrt composition + * @param[out] dbMixtureCoefficient_dz derivative of mixture coefficient (B) wrt composition + * @note Assumes that pressure and temperature are strictly positive + */ + GEOS_HOST_DEVICE + GEOS_FORCE_INLINE + static void + computeMixtureCoefficients( integer const numComps, + real64 const & pressure, + real64 const & temperature, + arrayView1d< real64 const > const composition, + arrayView1d< real64 const > const criticalPressure, + arrayView1d< real64 const > const criticalTemperature, + arrayView1d< real64 const > const acentricFactor, + real64 const & binaryInteractionCoefficients, + arraySlice1d< real64 const > const aPureCoefficient, + arraySlice1d< real64 const > const bPureCoefficient, + real64 const aMixtureCoefficient, + real64 const bMixtureCoefficient, + real64 & daMixtureCoefficient_dp, + real64 & dbMixtureCoefficient_dp, + real64 & daMixtureCoefficient_dt, + real64 & dbMixtureCoefficient_dt, + arraySlice1d< real64 > const daMixtureCoefficient_dz, + arraySlice1d< real64 > const dbMixtureCoefficient_dz ); + /** * @brief Compute the compressibility factor using compositions, BICs, and mixture coefficients * @param[in] numComps number of components @@ -150,6 +194,40 @@ struct CubicEOSPhaseModel real64 const & bMixtureCoefficient, real64 & compressibilityFactor ); + /** + * @brief Compute the compressibility factor derivatives using mixture coefficients + * @param[in] numComps number of components + * @param[in] aMixtureCoefficient mixture coefficient (A) + * @param[in] bMixtureCoefficient mixture coefficient (B) + * @param[in] compressibilityFactor the current compressibility factor + * @param[in] daMixtureCoefficient_dp derivative of mixture coefficient (A) wrt pressure + * @param[in] dbMixtureCoefficient_dp derivative of mixture coefficient (B) wrt pressure + * @param[in] daMixtureCoefficient_dt derivative of mixture coefficient (A) wrt temperature + * @param[in] dbMixtureCoefficient_dt derivative of mixture coefficient (B) wrt temperature + * @param[in] daMixtureCoefficient_dz derivative of mixture coefficient (A) wrt composition + * @param[in] dbMixtureCoefficient_dz derivative of mixture coefficient (B) wrt composition + * @param[out] dCompressibilityFactor_dp derivative of the compressibility factor wrt pressure + * @param[out] dCompressibilityFactor_dt derivative of the compressibility factor wrt temperature + * @param[out] dCompressibilityFactor_dz derivative of the compressibility factor wrt composition + * @note Assumes that pressure and temperature are strictly positive + */ + GEOS_HOST_DEVICE + GEOS_FORCE_INLINE + static void + computeCompressibilityFactor( integer const numComps, + real64 const & aMixtureCoefficient, + real64 const & bMixtureCoefficient, + real64 const & compressibilityFactor, + real64 const & daMixtureCoefficient_dp, + real64 const & dbMixtureCoefficient_dp, + real64 const & daMixtureCoefficient_dt, + real64 const & dbMixtureCoefficient_dt, + arraySlice1d< real64 const > const daMixtureCoefficient_dz, + arraySlice1d< real64 const > const dbMixtureCoefficient_dz, + real64 & dCompressibilityFactor_dp, + real64 & dCompressibilityFactor_dt, + arraySlice1d< real64 > const dCompressibilityFactor_dz ); + /** * @brief Compute the log of the fugacity coefficients using compositions, BICs, compressibility factor and mixture coefficients * @param[in] numComps number of components @@ -271,18 +349,14 @@ computeMixtureCoefficients( integer const numComps, real64 & aMixtureCoefficient, real64 & bMixtureCoefficient ) { - stackArray1d< real64, maxNumComps > m( numComps ); - for( integer ic = 0; ic < numComps; ++ic ) - { - m[ic] = EOS_TYPE::evaluate( acentricFactor[ic] ); - } - // mixture coefficients for( integer ic = 0; ic < numComps; ++ic ) { - aPureCoefficient[ic] = EOS_TYPE::omegaA * criticalTemperature[ic] * criticalTemperature[ic] * pressure - / ( criticalPressure[ic] * temperature * temperature ) * pow( 1.0 + m[ic] * ( 1.0 - sqrt( temperature / criticalTemperature[ic] ) ), 2.0 ); - bPureCoefficient[ic] = EOS_TYPE::omegaB * criticalTemperature[ic] * pressure / ( criticalPressure[ic] * temperature ); + real64 const m = EOS_TYPE::evaluate( acentricFactor[ic] ); + real64 const pr = pressure / criticalPressure[ic]; + real64 const tr = temperature / criticalTemperature[ic]; + aPureCoefficient[ic] = EOS_TYPE::omegaA * pr / (tr*tr) * pow( 1.0 + m * ( 1.0 - sqrt( tr ) ), 2.0 ); + bPureCoefficient[ic] = EOS_TYPE::omegaB * pr / tr; } aMixtureCoefficient = 0.0; @@ -297,6 +371,70 @@ computeMixtureCoefficients( integer const numComps, } } +template< typename EOS_TYPE > +GEOS_HOST_DEVICE +void +CubicEOSPhaseModel< EOS_TYPE >:: +computeMixtureCoefficients( integer const numComps, + real64 const & pressure, + real64 const & temperature, + arrayView1d< real64 const > const composition, + arrayView1d< real64 const > const criticalPressure, + arrayView1d< real64 const > const criticalTemperature, + arrayView1d< real64 const > const acentricFactor, + real64 const & binaryInteractionCoefficients, + arraySlice1d< real64 const > const aPureCoefficient, + arraySlice1d< real64 const > const bPureCoefficient, + real64 const aMixtureCoefficient, + real64 const bMixtureCoefficient, + real64 & daMixtureCoefficient_dp, + real64 & dbMixtureCoefficient_dp, + real64 & daMixtureCoefficient_dt, + real64 & dbMixtureCoefficient_dt, + arraySlice1d< real64 > const daMixtureCoefficient_dz, + arraySlice1d< real64 > const dbMixtureCoefficient_dz ) +{ + GEOS_UNUSED_VAR( criticalPressure ); + stackArray1d< real64, maxNumComps > daPureCoefficient_dx( numComps ); + // Calculate pressure derivatives + daMixtureCoefficient_dp = aMixtureCoefficient / pressure; + dbMixtureCoefficient_dp = bMixtureCoefficient / pressure; + // Calculate temperature derivatives + for( integer ic = 0; ic < numComps; ++ic ) + { + real64 const m = EOS_TYPE::evaluate( acentricFactor[ic] ); + real64 const sqrtTr = sqrt( temperature / criticalTemperature[ic] ); + real64 const mt = 1.0 + m * (1.0 - sqrtTr); + daPureCoefficient_dx[ic] = -aPureCoefficient[ic] * (2.0/temperature + m/(mt*sqrtTr*criticalTemperature[ic])); + } + daMixtureCoefficient_dt = 0.0; + dbMixtureCoefficient_dt = -bMixtureCoefficient / temperature; + for( integer ic = 0; ic < numComps; ++ic ) + { + for( integer jc = 0; jc < numComps; ++jc ) + { + real64 const coeff = composition[ic] * composition[jc] * ( 1.0 - binaryInteractionCoefficients ) / sqrt( aPureCoefficient[ic] * aPureCoefficient[jc] ); + daMixtureCoefficient_dt += 0.5 * coeff * (daPureCoefficient_dx[ic]*aPureCoefficient[jc] + daPureCoefficient_dx[jc]*aPureCoefficient[ic]); + } + } + // Calculate composition derivatives + for( integer ic = 0; ic < numComps; ++ic ) + { + daMixtureCoefficient_dz[ic] = 0.0; + dbMixtureCoefficient_dz[ic] = 0.0; + } + for( integer ic = 0; ic < numComps; ++ic ) + { + for( integer jc = 0; jc < numComps; ++jc ) + { + real64 const coeff = ( 1.0 - binaryInteractionCoefficients ) * sqrt( aPureCoefficient[ic] * aPureCoefficient[jc] ); + daMixtureCoefficient_dz[ic] += coeff * composition[jc]; + daMixtureCoefficient_dz[jc] += coeff * composition[ic]; + } + dbMixtureCoefficient_dz[ic] = bPureCoefficient[ic]; + } +} + template< typename EOS_TYPE > GEOS_HOST_DEVICE void @@ -366,6 +504,69 @@ computeCompressibilityFactor( integer const numComps, } } +template< typename EOS_TYPE > +GEOS_HOST_DEVICE +void +CubicEOSPhaseModel< EOS_TYPE >:: +computeCompressibilityFactor( integer const numComps, + real64 const & aMixtureCoefficient, + real64 const & bMixtureCoefficient, + real64 const & compressibilityFactor, + real64 const & daMixtureCoefficient_dp, + real64 const & dbMixtureCoefficient_dp, + real64 const & daMixtureCoefficient_dt, + real64 const & dbMixtureCoefficient_dt, + arraySlice1d< real64 const > const daMixtureCoefficient_dz, + arraySlice1d< real64 const > const dbMixtureCoefficient_dz, + real64 & dcompressibilityFactor_dp, + real64 & dcompressibilityFactor_dt, + arraySlice1d< real64 > const dcompressibilityFactor_dz ) +{ + // a Z3 + b Z2 + cZ + d = 0 + // Derivatives for a,b,c,d + // dadx is zero; + real64 dbdx = 0.0; + real64 dcdx = 0.0; + real64 dddx = 0.0; + + constexpr real64 d1pd2 = EOS_TYPE::delta1 + EOS_TYPE::delta2; + constexpr real64 d1xd2 = EOS_TYPE::delta1 * EOS_TYPE::delta2; + + constexpr real64 a = 1.0; + real64 const b = ( d1pd2 - 1.0 ) * bMixtureCoefficient - 1.0; + real64 const c = aMixtureCoefficient + d1xd2 * bMixtureCoefficient * bMixtureCoefficient + - d1pd2 * bMixtureCoefficient * ( bMixtureCoefficient + 1.0 ); + + // Implicit differentiation scale + real64 const denominator = (3.0*a*compressibilityFactor + 2.0*b)*compressibilityFactor + c; + constexpr real64 epsilon = LvArray::NumericLimits< real64 >::epsilon; + real64 const scalingFactor = fabs( denominator ) < epsilon ? 0.0 : -1.0 / denominator; + + // Pressure derivatives + dbdx = ( d1pd2 - 1.0 ) * dbMixtureCoefficient_dp; + dcdx = daMixtureCoefficient_dp + (2.0*(d1xd2-d1pd2)*bMixtureCoefficient-d1pd2)*dbMixtureCoefficient_dp; + dddx = -(aMixtureCoefficient*dbMixtureCoefficient_dp + daMixtureCoefficient_dp*bMixtureCoefficient + + d1xd2*((3.0*bMixtureCoefficient+2.0)*bMixtureCoefficient*dbMixtureCoefficient_dp)); + dcompressibilityFactor_dp = (((dbdx*compressibilityFactor) + dcdx)*compressibilityFactor + dddx) * scalingFactor; + + // Temperature derivatives + dbdx = ( d1pd2 - 1.0 ) * dbMixtureCoefficient_dt; + dcdx = daMixtureCoefficient_dt + (2.0*(d1xd2-d1pd2)*bMixtureCoefficient-d1pd2)*dbMixtureCoefficient_dt; + dddx = -(aMixtureCoefficient*dbMixtureCoefficient_dt + daMixtureCoefficient_dt*bMixtureCoefficient + + d1xd2*((3.0*bMixtureCoefficient+2.0)*bMixtureCoefficient*dbMixtureCoefficient_dt)); + dcompressibilityFactor_dt = (((dbdx*compressibilityFactor) + dcdx)*compressibilityFactor + dddx) * scalingFactor; + + // Composition derivatives + for( integer ic = 0; ic < numComps; ++ic ) + { + dbdx = ( d1pd2 - 1.0 ) * dbMixtureCoefficient_dz[ic]; + dcdx = daMixtureCoefficient_dz[ic] + (2.0*(d1xd2-d1pd2)*bMixtureCoefficient-d1pd2)*dbMixtureCoefficient_dz[ic]; + dddx = -(aMixtureCoefficient*dbMixtureCoefficient_dz[ic] + daMixtureCoefficient_dz[ic]*bMixtureCoefficient + + d1xd2*((3.0*bMixtureCoefficient+2.0)*bMixtureCoefficient*dbMixtureCoefficient_dz[ic])); + dcompressibilityFactor_dz[ic] = (((dbdx*compressibilityFactor) + dcdx)*compressibilityFactor + dddx) * scalingFactor; + } +} + template< typename EOS_TYPE > GEOS_HOST_DEVICE void diff --git a/src/coreComponents/constitutive/fluid/multifluid/compositional/functions/KValueInitialization.hpp b/src/coreComponents/constitutive/fluid/multifluid/compositional/functions/KValueInitialization.hpp new file mode 100644 index 00000000000..7d8d45d22d2 --- /dev/null +++ b/src/coreComponents/constitutive/fluid/multifluid/compositional/functions/KValueInitialization.hpp @@ -0,0 +1,83 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2018-2020 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2020 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2018-2020 TotalEnergies + * Copyright (c) 2019- GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +/** + * @file KValueInitialization.hpp + */ + +#ifndef GEOS_CONSTITUTIVE_FLUID_MULTIFLUID_COMPOSITIONAL_FUNCTIONS_KVALUEINITIALIZATION_HPP_ +#define GEOS_CONSTITUTIVE_FLUID_MULTIFLUID_COMPOSITIONAL_FUNCTIONS_KVALUEINITIALIZATION_HPP_ + +#include "common/DataTypes.hpp" + +namespace geos +{ + +namespace constitutive +{ + +struct KValueInitialization +{ +public: + /** + * @brief Calculate gas-liquid k-values based on the Wilson caorrelation + * @param[in] numComps number of components + * @param[in] pressure pressure + * @param[in] temperature temperature + * @param[in] criticalPressure critical pressures + * @param[in] criticalTemperature critical temperatures + * @param[in] acentricFactor acentric factors + * @param[out] kValues the calculated k-values + **/ + GEOS_HOST_DEVICE + GEOS_FORCE_INLINE + static void + computeWilsonGasLiquidKvalue( integer const numComps, + real64 const pressure, + real64 const temperature, + arrayView1d< real64 const > const criticalPressure, + arrayView1d< real64 const > const criticalTemperature, + arrayView1d< real64 const > const acentricFactor, + arraySlice1d< real64 > const kValues ) + { + for( integer ic = 0; ic < numComps; ++ic ) + { + real64 const pr = criticalPressure[ic] / pressure; + real64 const tr = criticalTemperature[ic] / temperature; + kValues[ic] = pr * exp( 5.37 * ( 1.0 + acentricFactor[ic] ) * ( 1.0 - tr ) ); + } + } + +/** + * @brief Calculate water-gas k-value + * @param[in] pressure pressure + * @param[in] temperature temperature + * @return The water component k-value + **/ + GEOS_HOST_DEVICE + GEOS_FORCE_INLINE + static double + computeWaterGasKvalue( double pressure, + double temperature ) + { + return exp( -4844.168051 / temperature + 12.93022442 ) * 1.0e5 / pressure; + } + +}; + +} // namespace constitutive + +} // namespace geos + +#endif //GEOS_CONSTITUTIVE_FLUID_MULTIFLUID_COMPOSITIONAL_FUNCTIONS_KVALUEINITIALIZATION_HPP_ diff --git a/src/coreComponents/constitutive/fluid/multifluid/compositional/functions/NegativeTwoPhaseFlash.hpp b/src/coreComponents/constitutive/fluid/multifluid/compositional/functions/NegativeTwoPhaseFlash.hpp new file mode 100644 index 00000000000..d91755bbeb6 --- /dev/null +++ b/src/coreComponents/constitutive/fluid/multifluid/compositional/functions/NegativeTwoPhaseFlash.hpp @@ -0,0 +1,217 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2018-2020 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2020 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2018-2020 TotalEnergies + * Copyright (c) 2019- GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +/** + * @file NegativeTwoPhaseFlash.hpp + */ + +#ifndef GEOS_CONSTITUTIVE_FLUID_MULTIFLUID_COMPOSITIONAL_FUNCTIONS_NEGATIVETWOPHASEFLASH_HPP_ +#define GEOS_CONSTITUTIVE_FLUID_MULTIFLUID_COMPOSITIONAL_FUNCTIONS_NEGATIVETWOPHASEFLASH_HPP_ + +#include "common/DataTypes.hpp" +#include "CubicEOSPhaseModel.hpp" +#include "RachfordRice.hpp" +#include "KValueInitialization.hpp" + +namespace geos +{ + +namespace constitutive +{ + +struct NegativeTwoPhaseFlash +{ +public: + /// Max number of components alloweeed in the class for now + static constexpr integer maxNumComps = 5; + /// Max number of iterations + static constexpr integer maxIterations = 200; + /// Epsilon used in the calculations + static constexpr real64 epsilon = LvArray::NumericLimits< real64 >::epsilon; + /// Tolerance for checking fugacity ratio convergence + static constexpr real64 fugacityTolerance = 1.0e-8; + + /** + * @brief Perform negative two-phase EOS flash + * @param[in] numComps number of components + * @param[in] pressure pressure + * @param[in] temperature temperature + * @param[in] composition composition of the mixture + * @param[in] criticalPressure critical pressures + * @param[in] criticalTemperature critical temperatures + * @param[in] acentricFactor acentric factors + * @param[in] binaryInteractionCoefficients binary coefficients (currently not implemented) + * @param[out] vapourPhaseMoleFraction the calculated vapour (gas) mole fraction + * @param[out] liquidComposition the calculated liquid phase composition + * @param[out] vapourComposition the calculated vapour phase composition + * @return an indicator of success of the flash + */ + template< typename EOS_TYPE_LIQUID, typename EOS_TYPE_VAPOUR > + GEOS_HOST_DEVICE + static bool compute( integer const numComps, + real64 const pressure, + real64 const temperature, + arrayView1d< real64 const > const composition, + arrayView1d< real64 const > const criticalPressure, + arrayView1d< real64 const > const criticalTemperature, + arrayView1d< real64 const > const acentricFactor, + real64 const & binaryInteractionCoefficients, + real64 & vapourPhaseMoleFraction, + arrayView1d< real64 > const liquidComposition, + arrayView1d< real64 > const vapourComposition ) + { + stackArray1d< real64, maxNumComps > logLiquidFugacity( numComps ); + stackArray1d< real64, maxNumComps > logVapourFugacity( numComps ); + stackArray1d< real64, maxNumComps > kVapourLiquid( numComps ); + stackArray1d< real64, maxNumComps > fugacityRatios( numComps ); + stackArray1d< integer, maxNumComps > presentComponentIds( numComps ); + + // Initialise compositions to feed composition + for( integer ic = 0; ic < numComps; ++ic ) + { + liquidComposition[ic] = composition[ic]; + vapourComposition[ic] = composition[ic]; + } + + // Check for machine-zero feed values + integer presentCount = 0; + for( integer ic = 0; ic < numComps; ++ic ) + { + if( epsilon < composition[ic] ) + { + presentComponentIds[presentCount++] = ic; + } + } + presentComponentIds.resize( presentCount ); + + KValueInitialization::computeWilsonGasLiquidKvalue( numComps, + pressure, + temperature, + criticalPressure, + criticalTemperature, + acentricFactor, + kVapourLiquid ); + + bool converged = false; + for( localIndex iterationCount = 0; iterationCount < maxIterations; ++iterationCount ) + { + // Solve Rachford-Rice Equation + vapourPhaseMoleFraction = RachfordRice::solve( kVapourLiquid, composition, presentComponentIds ); + + // Assign phase compositions + for( integer const ic : presentComponentIds ) + { + liquidComposition[ic] = composition[ic] / ( 1.0 + vapourPhaseMoleFraction * ( kVapourLiquid[ic] - 1.0 ) ); + vapourComposition[ic] = kVapourLiquid[ic] * liquidComposition[ic]; + } + + normalizeComposition( numComps, liquidComposition ); + normalizeComposition( numComps, vapourComposition ); + + // Compute the phase fugacities + CubicEOSPhaseModel< EOS_TYPE_LIQUID >::compute( numComps, + pressure, + temperature, + liquidComposition, + criticalPressure, + criticalTemperature, + acentricFactor, + binaryInteractionCoefficients, + logLiquidFugacity ); + CubicEOSPhaseModel< EOS_TYPE_VAPOUR >::compute( numComps, + pressure, + temperature, + vapourComposition, + criticalPressure, + criticalTemperature, + acentricFactor, + binaryInteractionCoefficients, + logVapourFugacity ); + + // Compute fugacity ratios and check convergence + converged = true; + + for( integer const ic : presentComponentIds ) + { + fugacityRatios[ic] = exp( logLiquidFugacity[ic] - logVapourFugacity[ic] ) * liquidComposition[ic] / vapourComposition[ic]; + if( fugacityTolerance < fabs( fugacityRatios[ic] - 1.0 ) ) + { + converged = false; + } + } + + if( converged ) + { + break; + } + + // Update K-values + for( integer const ic : presentComponentIds ) + { + kVapourLiquid[ic] *= fugacityRatios[ic]; + } + } + + // Retrieve physical bounds from negative flash values + if( vapourPhaseMoleFraction <= 0.0 ) + { + vapourPhaseMoleFraction = 0.0; + for( integer ic = 0; ic < numComps; ++ic ) + { + liquidComposition[ic] = composition[ic]; + } + } + else if( 1.0 <= vapourPhaseMoleFraction ) + { + vapourPhaseMoleFraction = 1.0; + for( integer ic = 0; ic < numComps; ++ic ) + { + vapourComposition[ic] = composition[ic]; + } + } + + return converged; + } + +private: + /** + * @brief Normalise a composition in place to ensure that the components add up to unity + * @param[in] numComps number of components + * @param[in/out] composition composition to be normalized + * @return the sum of the given values + */ + GEOS_HOST_DEVICE + GEOS_FORCE_INLINE + static real64 normalizeComposition( integer const numComps, + arraySlice1d< real64 > const composition ) + { + real64 totalMoles = 0.0; + for( integer ic = 0; ic < numComps; ++ic ) + { + totalMoles += composition[ic]; + } + real64 const oneOverTotalMoles = 1.0 / (totalMoles + epsilon); + for( integer ic = 0; ic < numComps; ++ic ) + { + composition[ic] *= oneOverTotalMoles; + } + return totalMoles; + } +}; + +} // namespace constitutive + +} // namespace geos + +#endif //GEOS_CONSTITUTIVE_FLUID_MULTIFLUID_COMPOSITIONAL_FUNCTIONS_NEGATIVETWOPHASEFLASH_HPP_ diff --git a/src/coreComponents/constitutive/fluid/multifluid/compositional/functions/RachfordRice.cpp b/src/coreComponents/constitutive/fluid/multifluid/compositional/functions/RachfordRice.cpp deleted file mode 100644 index 8795e457a94..00000000000 --- a/src/coreComponents/constitutive/fluid/multifluid/compositional/functions/RachfordRice.cpp +++ /dev/null @@ -1,189 +0,0 @@ -/* - * ------------------------------------------------------------------------------------------------------------ - * SPDX-License-Identifier: LGPL-2.1-only - * - * Copyright (c) 2018-2020 Lawrence Livermore National Security LLC - * Copyright (c) 2018-2020 The Board of Trustees of the Leland Stanford Junior University - * Copyright (c) 2018-2020 TotalEnergies - * Copyright (c) 2019- GEOSX Contributors - * All rights reserved - * - * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. - * ------------------------------------------------------------------------------------------------------------ - */ - -/** - * @file RachfordRice.hpp - */ - -#include "RachfordRice.hpp" - -namespace geos -{ - -namespace constitutive -{ - -real64 -RachfordRice::solve( arrayView1d< real64 const > const kValues, - arrayView1d< real64 const > const feed, - arrayView1d< integer const > const presentComponentIds ) -{ - real64 gasPhaseMoleFraction = 0; - - // min and max Kvalues for non-zero composition - real64 maxK = 0.0; - real64 minK = 1 / epsilon; - - for( integer i = 0; i < presentComponentIds.size(); ++i ) - { - integer const ic = presentComponentIds[i]; - if( kValues[ic] > maxK ) - { - maxK = kValues[ic]; - } - if( kValues[ic] < minK ) - { - minK = kValues[ic]; - } - } - - // check for trivial solutions. - // this corresponds to bad Kvalues - if( maxK < 1.0 ) - { - return 0.0; - } - if( minK > 1.0 ) - { - return 1.0; - } - - // start the solver loop - - // step 1: find solution window - real64 xMin = 1.0 / ( 1 - maxK ); - real64 xMax = 1.0 / ( 1 - minK ); - real64 const sqrtEpsilon = sqrt( epsilon ); - xMin += sqrtEpsilon * ( LvArray::math::abs( xMin ) + sqrtEpsilon ); - xMax -= sqrtEpsilon * ( LvArray::math::abs( xMax ) + sqrtEpsilon ); - - real64 currentError = 1 / epsilon; - - // step 2: start the SSI loop - real64 funcXMin = 0.0; - real64 funcXMid = 0.0; - real64 funcXMax = 0.0; - bool recomputeMin = true; - bool recomputeMax = true; - integer SSIIteration = 0; - - while( ( currentError > SSITolerance ) && ( SSIIteration < maxSSIIterations ) ) - { - real64 const xMid = 0.5 * ( xMin + xMax ); - if( recomputeMin ) - { - funcXMin = evaluate( kValues, feed, presentComponentIds, xMin ); - } - if( recomputeMax ) - { - funcXMax = evaluate( kValues, feed, presentComponentIds, xMax ); - } - funcXMid = evaluate( kValues, feed, presentComponentIds, xMid ); - - if( ( funcXMin < 0 ) && ( funcXMax < 0 ) ) - { - return gasPhaseMoleFraction = 0.0; - } - else if( ( funcXMin > 1 ) && ( funcXMax > 1 ) ) - { - return gasPhaseMoleFraction = 1.0; - } - else if( funcXMin * funcXMid < 0.0 ) - { - xMax = xMid; - recomputeMax = true; - recomputeMin = false; - } - else if( funcXMax * funcXMid < 0.0 ) - { - xMin = xMid; - recomputeMax = false; - recomputeMin = true; - } - - currentError = LvArray::math::min( LvArray::math::abs( funcXMax - funcXMin ), - LvArray::math::abs( xMax - xMin ) ); - SSIIteration++; - - // TODO: add warning if max number of SSI iterations is reached - } - - gasPhaseMoleFraction = 0.5 * ( xMax + xMin ); - - // step 3: start the Newton loop - integer newtonIteration = 0; - real64 newtonValue = gasPhaseMoleFraction; - - while( ( currentError > newtonTolerance ) && ( newtonIteration < maxNewtonIterations ) ) - { - real64 const deltaNewton = -evaluate( kValues, feed, presentComponentIds, newtonValue ) - / evaluateDerivative( kValues, feed, presentComponentIds, newtonValue ); - currentError = LvArray::math::abs( deltaNewton ) / LvArray::math::abs( newtonValue ); - - // test if we are stepping out of the [xMin;xMax] interval - if( newtonValue + deltaNewton < xMin ) - { - newtonValue = 0.5 * ( newtonValue + xMin ); - } - else if( newtonValue + deltaNewton > xMax ) - { - newtonValue = 0.5 * ( newtonValue + xMax ); - } - else - { - newtonValue = newtonValue + deltaNewton; - } - newtonIteration++; - - // TODO: add warning if max number of Newton iterations is reached - } - return gasPhaseMoleFraction = newtonValue; -} - -real64 -RachfordRice::evaluate( arrayView1d< real64 const > const kValues, - arrayView1d< real64 const > const feed, - arrayView1d< integer const > const presentComponentIds, - real64 const & x ) -{ - real64 value = 0.0; - for( integer ic = 0; ic < presentComponentIds.size(); ++ic ) - { - real64 const k = ( kValues[ic] - 1.0 ); - value += feed[ic] * k / ( 1.0 + x * k ); - } - return value; -} - -real64 -RachfordRice::evaluateDerivative( arrayView1d< real64 const > const kValues, - arrayView1d< real64 const > const feed, - arrayView1d< integer const > const presentComponentIds, - real64 const & x ) -{ - real64 value = 0.0; - for( integer i = 0; i < presentComponentIds.size(); ++i ) - { - integer const ic = presentComponentIds[i]; - real64 const k = ( kValues[ic] - 1.0 ); - real64 const r = k / ( 1.0 + x * k ); - value -= feed[ic] * r * r; - } - return value; -} - - -} // namespace constitutive - -} // namespace geos diff --git a/src/coreComponents/constitutive/fluid/multifluid/compositional/functions/RachfordRice.hpp b/src/coreComponents/constitutive/fluid/multifluid/compositional/functions/RachfordRice.hpp index f37f9e1691a..3c6b9cdd3ee 100644 --- a/src/coreComponents/constitutive/fluid/multifluid/compositional/functions/RachfordRice.hpp +++ b/src/coreComponents/constitutive/fluid/multifluid/compositional/functions/RachfordRice.hpp @@ -50,10 +50,133 @@ struct RachfordRice * @return the gas mole fraction **/ GEOS_HOST_DEVICE - static real64 - solve( arrayView1d< real64 const > const kValues, - arrayView1d< real64 const > const feed, - arrayView1d< integer const > const presentComponentIds ); + real64 + static + solve( arraySlice1d< real64 const > const kValues, + arraySlice1d< real64 const > const feed, + arraySlice1d< integer const > const presentComponentIds ) + { + real64 gasPhaseMoleFraction = 0; + + // min and max Kvalues for non-zero composition + real64 maxK = 0.0; + real64 minK = 1 / epsilon; + + for( integer i = 0; i < presentComponentIds.size(); ++i ) + { + integer const ic = presentComponentIds[i]; + if( kValues[ic] > maxK ) + { + maxK = kValues[ic]; + } + if( kValues[ic] < minK ) + { + minK = kValues[ic]; + } + } + + // check for trivial solutions. + // this corresponds to bad Kvalues + if( maxK < 1.0 ) + { + return 0.0; + } + if( minK > 1.0 ) + { + return 1.0; + } + + // start the solver loop + + // step 1: find solution window + real64 xMin = 1.0 / ( 1 - maxK ); + real64 xMax = 1.0 / ( 1 - minK ); + real64 const sqrtEpsilon = sqrt( epsilon ); + xMin += sqrtEpsilon * ( LvArray::math::abs( xMin ) + sqrtEpsilon ); + xMax -= sqrtEpsilon * ( LvArray::math::abs( xMax ) + sqrtEpsilon ); + + real64 currentError = 1 / epsilon; + + // step 2: start the SSI loop + real64 funcXMin = 0.0; + real64 funcXMid = 0.0; + real64 funcXMax = 0.0; + bool recomputeMin = true; + bool recomputeMax = true; + integer SSIIteration = 0; + + while( ( currentError > SSITolerance ) && ( SSIIteration < maxSSIIterations ) ) + { + real64 const xMid = 0.5 * ( xMin + xMax ); + if( recomputeMin ) + { + funcXMin = evaluate( kValues, feed, presentComponentIds, xMin ); + } + if( recomputeMax ) + { + funcXMax = evaluate( kValues, feed, presentComponentIds, xMax ); + } + funcXMid = evaluate( kValues, feed, presentComponentIds, xMid ); + + if( ( funcXMin < 0 ) && ( funcXMax < 0 ) ) + { + return gasPhaseMoleFraction = 0.0; + } + else if( ( funcXMin > 1 ) && ( funcXMax > 1 ) ) + { + return gasPhaseMoleFraction = 1.0; + } + else if( funcXMin * funcXMid < 0.0 ) + { + xMax = xMid; + recomputeMax = true; + recomputeMin = false; + } + else if( funcXMax * funcXMid < 0.0 ) + { + xMin = xMid; + recomputeMax = false; + recomputeMin = true; + } + + currentError = LvArray::math::min( LvArray::math::abs( funcXMax - funcXMin ), + LvArray::math::abs( xMax - xMin ) ); + SSIIteration++; + + // TODO: add warning if max number of SSI iterations is reached + } + + gasPhaseMoleFraction = 0.5 * ( xMax + xMin ); + + // step 3: start the Newton loop + integer newtonIteration = 0; + real64 newtonValue = gasPhaseMoleFraction; + + while( ( currentError > newtonTolerance ) && ( newtonIteration < maxNewtonIterations ) ) + { + real64 const deltaNewton = -evaluate( kValues, feed, presentComponentIds, newtonValue ) + / evaluateDerivative( kValues, feed, presentComponentIds, newtonValue ); + currentError = LvArray::math::abs( deltaNewton ) / LvArray::math::abs( newtonValue ); + + // test if we are stepping out of the [xMin;xMax] interval + if( newtonValue + deltaNewton < xMin ) + { + newtonValue = 0.5 * ( newtonValue + xMin ); + } + else if( newtonValue + deltaNewton > xMax ) + { + newtonValue = 0.5 * ( newtonValue + xMax ); + } + else + { + newtonValue = newtonValue + deltaNewton; + } + newtonIteration++; + + // TODO: add warning if max number of Newton iterations is reached + } + return gasPhaseMoleFraction = newtonValue; + } private: @@ -66,11 +189,22 @@ struct RachfordRice * @return the value of the Rachford-Rice function at x **/ GEOS_HOST_DEVICE - static real64 - evaluate( arrayView1d< real64 const > const kValues, - arrayView1d< real64 const > const feed, - arrayView1d< integer const > const presentComponentIds, - real64 const & x ); + real64 + static + evaluate( arraySlice1d< real64 const > const kValues, + arraySlice1d< real64 const > const feed, + arraySlice1d< integer const > const presentComponentIds, + real64 const & x ) + { + real64 value = 0.0; + for( integer i = 0; i < presentComponentIds.size(); ++i ) + { + integer const ic = presentComponentIds[i]; + real64 const k = ( kValues[ic] - 1.0 ); + value += feed[ic] * k / ( 1.0 + x * k ); + } + return value; + } /** * @brief Function evaluating the derivative of the Rachford-Rice function @@ -81,11 +215,23 @@ struct RachfordRice * @return the value of the derivative of the Rachford-Rice function at x **/ GEOS_HOST_DEVICE - static real64 - evaluateDerivative( arrayView1d< real64 const > const kValues, - arrayView1d< real64 const > const feed, - arrayView1d< integer const > const presentComponentIds, - real64 const & x ); + real64 + static + evaluateDerivative( arraySlice1d< real64 const > const kValues, + arraySlice1d< real64 const > const feed, + arraySlice1d< integer const > const presentComponentIds, + real64 const & x ) + { + real64 value = 0.0; + for( integer i = 0; i < presentComponentIds.size(); ++i ) + { + integer const ic = presentComponentIds[i]; + real64 const k = ( kValues[ic] - 1.0 ); + real64 const r = k / ( 1.0 + x * k ); + value -= feed[ic] * r * r; + } + return value; + } }; diff --git a/src/coreComponents/constitutive/solid/CompressibleSolid.hpp b/src/coreComponents/constitutive/solid/CompressibleSolid.hpp index ee067d37ed6..7e0d6cb66e3 100644 --- a/src/coreComponents/constitutive/solid/CompressibleSolid.hpp +++ b/src/coreComponents/constitutive/solid/CompressibleSolid.hpp @@ -55,10 +55,14 @@ class CompressibleSolidUpdates : public CoupledSolidUpdates< NullModel, PORO_TYP localIndex const q, real64 const & pressure, real64 const & pressure_n, + real64 const & pressure_k, real64 const & temperature, + real64 const & temperature_k, real64 const & temperature_n ) const override final { - m_porosityUpdate.updateFromPressureAndTemperature( k, q, pressure, pressure_n, temperature, temperature_n ); + m_porosityUpdate.updateFromPressureAndTemperature( k, q, + pressure, pressure_k, pressure_n, + temperature, temperature_k, temperature_n ); real64 const porosity = m_porosityUpdate.getPorosity( k, q ); m_permUpdate.updateFromPorosity( k, q, porosity ); } @@ -70,7 +74,14 @@ class CompressibleSolidUpdates : public CoupledSolidUpdates< NullModel, PORO_TYP real64 const & oldHydraulicAperture, real64 const & newHydraulicAperture ) const { - m_porosityUpdate.updateFromPressureAndTemperature( k, q, pressure, 0.0, 0.0, 0.0 ); + real64 const pressure_k = 0; + real64 const pressure_n = 0; + real64 const temperature = 0; + real64 const temperature_k = 0; + real64 const temperature_n = 0; + m_porosityUpdate.updateFromPressureAndTemperature( k, q, + pressure, pressure_k, pressure_n, + temperature, temperature_k, temperature_n ); m_permUpdate.updateFromAperture( k, q, oldHydraulicAperture, newHydraulicAperture ); } @@ -83,7 +94,7 @@ class CompressibleSolidUpdates : public CoupledSolidUpdates< NullModel, PORO_TYP real64 const ( &dispJump )[3], real64 const ( &traction )[3] ) const { - m_porosityUpdate.updateFromPressureAndTemperature( k, q, pressure, 0.0, 0.0, 0.0 ); + m_porosityUpdate.updateFromPressureAndTemperature( k, q, pressure, 0.0, 0.0, 0.0, 0.0, 0.0 ); m_permUpdate.updateFromApertureAndShearDisplacement( k, q, oldHydraulicAperture, newHydraulicAperture, pressure, dispJump, traction ); } diff --git a/src/coreComponents/constitutive/solid/CoupledSolid.hpp b/src/coreComponents/constitutive/solid/CoupledSolid.hpp index 58557692413..1e719d3ff31 100644 --- a/src/coreComponents/constitutive/solid/CoupledSolid.hpp +++ b/src/coreComponents/constitutive/solid/CoupledSolid.hpp @@ -84,11 +84,15 @@ class CoupledSolidUpdates virtual void updateStateFromPressureAndTemperature( localIndex const k, localIndex const q, real64 const & pressure, + real64 const & pressure_k, real64 const & pressure_n, real64 const & temperature, + real64 const & temperature_k, real64 const & temperature_n ) const { - GEOS_UNUSED_VAR( k, q, pressure, pressure_n, temperature, temperature_n ); + GEOS_UNUSED_VAR( k, q, + pressure, pressure_k, pressure_n, + temperature, temperature_k, temperature_n ); } GEOS_HOST_DEVICE diff --git a/src/coreComponents/constitutive/solid/CoupledSolidBase.hpp b/src/coreComponents/constitutive/solid/CoupledSolidBase.hpp index 087077af806..28ef6d3be21 100644 --- a/src/coreComponents/constitutive/solid/CoupledSolidBase.hpp +++ b/src/coreComponents/constitutive/solid/CoupledSolidBase.hpp @@ -179,6 +179,25 @@ class CoupledSolidBase : public ConstitutiveBase return getBasePorosityModel().getBiotCoefficient(); } + /** + * @brief Const/non-mutable accessor for the mean stress increment at the previous sequential iteration + * @return Accessor + */ + arrayView2d< real64 const > const getMeanEffectiveStressIncrement_k() const + { + return getBasePorosityModel().getMeanEffectiveStressIncrement_k(); + } + + /** + * @brief Non-const accessor for the mean stress increment at the previous sequential iteration + * @return Accessor + */ + arrayView1d< real64 > const getAverageMeanEffectiveStressIncrement_k() + { + return getBasePorosityModel().getAverageMeanEffectiveStressIncrement_k(); + } + + /** * @brief initialize the constitutive models fields. */ @@ -198,6 +217,14 @@ class CoupledSolidBase : public ConstitutiveBase } } + /** + * @brief ignore the porosity update (after initialization step) + */ + virtual void ignoreConvergedState() const + { + getBasePorosityModel().ignoreConvergedState(); + } + /** * @brief get a constant reference to the solid internal energy model * return a constant SolidInternalEnergy reference to the solid internal energy model @@ -227,6 +254,15 @@ class CoupledSolidBase : public ConstitutiveBase */ PorosityBase const & getBasePorosityModel() const { return this->getParent().template getGroup< PorosityBase >( m_porosityModelName ); } + + /** + * @brief get a PorosityBase reference to the porosity model + * return a PorosityBase reference to the porosity model + */ + PorosityBase & getBasePorosityModel() + { return this->getParent().template getGroup< PorosityBase >( m_porosityModelName ); } + + /** * @brief get a Permeability base constant reference to the permeability model * return a constant PermeabilityBase reference to the permeability model diff --git a/src/coreComponents/constitutive/solid/PorousSolid.hpp b/src/coreComponents/constitutive/solid/PorousSolid.hpp index 186c6d2b732..8e3fd98e3f6 100644 --- a/src/coreComponents/constitutive/solid/PorousSolid.hpp +++ b/src/coreComponents/constitutive/solid/PorousSolid.hpp @@ -56,11 +56,15 @@ class PorousSolidUpdates : public CoupledSolidUpdates< SOLID_TYPE, BiotPorosity, virtual void updateStateFromPressureAndTemperature( localIndex const k, localIndex const q, real64 const & pressure, + real64 const & pressure_k, real64 const & pressure_n, real64 const & temperature, + real64 const & temperature_k, real64 const & temperature_n ) const override final { - m_porosityUpdate.updateFromPressureAndTemperature( k, q, pressure, pressure_n, temperature, temperature_n ); + m_porosityUpdate.updateFromPressureAndTemperature( k, q, + pressure, pressure_k, pressure_n, + temperature, temperature_k, temperature_n ); } GEOS_HOST_DEVICE @@ -126,8 +130,8 @@ class PorousSolidUpdates : public CoupledSolidUpdates< SOLID_TYPE, BiotPorosity, real64 ( & totalStress )[6], DiscretizationOps & stiffness ) const { - real64 dTotalStress_dPressure[6]; - real64 dTotalStress_dTemperature[6]; + real64 dTotalStress_dPressure[6]{}; + real64 dTotalStress_dTemperature[6]{}; // Compute total stress increment and its derivative computeTotalStress( k, @@ -141,22 +145,11 @@ class PorousSolidUpdates : public CoupledSolidUpdates< SOLID_TYPE, BiotPorosity, dTotalStress_dTemperature, // To pass something here stiffness ); - // Compute porosity - real64 const deltaPressure = pressure - pressure_n; - real64 const deltaTemperature = temperature - temperature_n; - - - real64 const biotCoefficient = m_porosityUpdate.getBiotCoefficient( k ); - real64 const thermalExpansionCoefficient = m_solidUpdate.getThermalExpansionCoefficient( k ); + // Compute effective stress increment for the porosity + GEOS_UNUSED_VAR( pressure_n, temperature_n ); real64 const bulkModulus = m_solidUpdate.getBulkModulus( k ); - - real64 const effectiveMeanStressIncrement = bulkModulus * ( strainIncrement[0] + strainIncrement[1] + strainIncrement[2] ); - - real64 const totalMeanStressIncrement = effectiveMeanStressIncrement - biotCoefficient * deltaPressure - 3 * thermalExpansionCoefficient * bulkModulus * deltaTemperature; - - m_porosityUpdate.updateTotalMeanStressIncrement( k, q, totalMeanStressIncrement ); - - // The body force is calculated in the SolidMechanics kernel and the fluid contribution is neglected here + real64 const meanEffectiveStressIncrement = bulkModulus * ( strainIncrement[0] + strainIncrement[1] + strainIncrement[2] ); + m_porosityUpdate.updateMeanEffectiveStressIncrement( k, q, meanEffectiveStressIncrement ); } /** @@ -177,6 +170,32 @@ class PorousSolidUpdates : public CoupledSolidUpdates< SOLID_TYPE, BiotPorosity, m_solidUpdate.getElasticStiffness( k, q, stiffness ); } + /** + * @brief Return the stiffness at a given element (small-strain interface) + * + * @param [in] k the element number + * @param [out] biotCefficient the biot-coefficient + */ + GEOS_HOST_DEVICE + inline + void getBiotCoefficient( localIndex const k, real64 & biotCoefficient ) const + { + biotCoefficient = m_porosityUpdate.getBiotCoefficient( k ); + } + + /** + * @brief Return the stiffness at a given element (small-strain interface) + * + * @param [in] k the element number + * @param [out] thermalExpansionCoefficient the thermal expansion coefficient + */ + GEOS_HOST_DEVICE + inline + void getThermalExpansionCoefficient( localIndex const k, real64 & thermalExpansionCoefficient ) const + { + thermalExpansionCoefficient = m_solidUpdate.getThermalExpansionCoefficient( k ); + } + private: using CoupledSolidUpdates< SOLID_TYPE, BiotPorosity, ConstantPermeability >::m_solidUpdate; @@ -350,6 +369,25 @@ class PorousSolid : public CoupledSolid< SOLID_TYPE, BiotPorosity, ConstantPerme return getPorosityModel().getBiotCoefficient(); } + /** + * @brief Const/non-mutable accessor for the mean stress increment at the previous sequential iteration + * @return Accessor + */ + arrayView2d< real64 const > const getMeanStressIncrement_k() const + { + return getPorosityModel().getMeanStressIncrement_k(); + } + + /** + * @brief Non-const accessor for the mean stress increment at the previous sequential iteration + * @return Accessor + */ + arrayView1d< real64 > const getAverageMeanStressIncrement_k() + { + return getPorosityModel().getAverageMeanStressIncrement_k(); + } + + private: using CoupledSolid< SOLID_TYPE, BiotPorosity, ConstantPermeability >::getSolidModel; using CoupledSolid< SOLID_TYPE, BiotPorosity, ConstantPermeability >::getPorosityModel; diff --git a/src/coreComponents/constitutive/solid/porosity/BiotPorosity.cpp b/src/coreComponents/constitutive/solid/porosity/BiotPorosity.cpp index b83725ec4c4..1575a666a92 100644 --- a/src/coreComponents/constitutive/solid/porosity/BiotPorosity.cpp +++ b/src/coreComponents/constitutive/solid/porosity/BiotPorosity.cpp @@ -40,13 +40,15 @@ BiotPorosity::BiotPorosity( string const & name, Group * const parent ): setInputFlag( InputFlags::OPTIONAL ). setDescription( "Default thermal expansion coefficient" ); - registerField( fields::porosity::biotCoefficient{}, &m_biotCoefficient ); + registerField( fields::porosity::biotCoefficient{}, &m_biotCoefficient ). + setApplyDefaultValue( 1.0 ); // this is useful for sequential simulations, for the first flow solve + // ultimately, we want to be able to load the biotCoefficient from input directly, and this won't be necessary anymore registerField( fields::porosity::thermalExpansionCoefficient{}, &m_thermalExpansionCoefficient ); - registerWrapper( viewKeyStruct::meanStressIncrementString(), &m_meanStressIncrement ). - setApplyDefaultValue( 0.0 ). - setDescription( "Volumetric stress increment" ); + registerField( fields::porosity::meanEffectiveStressIncrement_k{}, &m_meanEffectiveStressIncrement_k ); + + registerField( fields::porosity::averageMeanEffectiveStressIncrement_k{}, &m_averageMeanEffectiveStressIncrement_k ); registerWrapper( viewKeyStruct::solidBulkModulusString(), &m_bulkModulus ). setApplyDefaultValue( 1e-6 ). @@ -58,7 +60,7 @@ void BiotPorosity::allocateConstitutiveData( dataRepository::Group & parent, { PorosityBase::allocateConstitutiveData( parent, numConstitutivePointsPerParentIndex ); - m_meanStressIncrement.resize( 0, numConstitutivePointsPerParentIndex ); + m_meanEffectiveStressIncrement_k.resize( 0, numConstitutivePointsPerParentIndex ); } void BiotPorosity::postProcessInput() @@ -90,6 +92,20 @@ void BiotPorosity::initializeState() const } ); } +void BiotPorosity::saveConvergedState() const +{ + PorosityBase::saveConvergedState(); + m_meanEffectiveStressIncrement_k.zero(); + m_averageMeanEffectiveStressIncrement_k.zero(); +} + +void BiotPorosity::ignoreConvergedState() const +{ + PorosityBase::ignoreConvergedState(); + m_meanEffectiveStressIncrement_k.zero(); + m_averageMeanEffectiveStressIncrement_k.zero(); +} + REGISTER_CATALOG_ENTRY( ConstitutiveBase, BiotPorosity, string const &, Group * const ) } /* namespace constitutive */ diff --git a/src/coreComponents/constitutive/solid/porosity/BiotPorosity.hpp b/src/coreComponents/constitutive/solid/porosity/BiotPorosity.hpp index eb729181289..56e29c9bf42 100644 --- a/src/coreComponents/constitutive/solid/porosity/BiotPorosity.hpp +++ b/src/coreComponents/constitutive/solid/porosity/BiotPorosity.hpp @@ -52,7 +52,8 @@ class BiotPorosityUpdates : public PorosityBaseUpdates arrayView1d< real64 > const & referencePorosity, arrayView1d< real64 > const & biotCoefficient, arrayView1d< real64 > const & thermalExpansionCoefficient, - arrayView2d< real64 > const & meanStressIncrement, + arrayView2d< real64 > const & meanEffectiveStressIncrement_k, + arrayView1d< real64 const > const & averageMeanEffectiveStressIncrement_k, arrayView1d< real64 > const & bulkModulus, real64 const & grainBulkModulus ): PorosityBaseUpdates( newPorosity, porosity_n, @@ -60,11 +61,12 @@ class BiotPorosityUpdates : public PorosityBaseUpdates dPorosity_dTemperature, initialPorosity, referencePorosity ), - m_biotCoefficient( biotCoefficient ), + m_grainBulkModulus( grainBulkModulus ), m_thermalExpansionCoefficient( thermalExpansionCoefficient ), - m_meanStressIncrement( meanStressIncrement ), + m_biotCoefficient( biotCoefficient ), m_bulkModulus( bulkModulus ), - m_grainBulkModulus( grainBulkModulus ) + m_meanEffectiveStressIncrement_k( meanEffectiveStressIncrement_k ), + m_averageMeanEffectiveStressIncrement_k( averageMeanEffectiveStressIncrement_k ) {} GEOS_HOST_DEVICE @@ -87,7 +89,7 @@ class BiotPorosityUpdates : public PorosityBaseUpdates real64 & dPorosity_dTemperature ) const { real64 const biotSkeletonModulusInverse = (m_biotCoefficient[k] - m_referencePorosity[k]) / m_grainBulkModulus; - real64 const porosityThermalExpansion = 3 * m_thermalExpansionCoefficient[k] * m_biotCoefficient[k]; + real64 const porosityThermalExpansion = 3 * m_thermalExpansionCoefficient[k] * ( m_biotCoefficient[k] - m_referencePorosity[k] ); real64 const porosity = m_porosity_n[k][q] + m_biotCoefficient[k] * LvArray::tensorOps::symTrace< 3 >( strainIncrement ) @@ -102,8 +104,10 @@ class BiotPorosityUpdates : public PorosityBaseUpdates } GEOS_HOST_DEVICE - void computePorosity( real64 const & pressure, - real64 const & temperature, + void computePorosity( real64 const & deltaPressureFromBeginningOfTimeStep, + real64 const & deltaPressureFromLastIteration, + real64 const & deltaTemperatureFromBeginningOfTimeStep, + real64 const & deltaTemperatureFromLastIteration, real64 const & porosity_n, real64 const & referencePorosity, real64 & porosity, @@ -111,33 +115,50 @@ class BiotPorosityUpdates : public PorosityBaseUpdates real64 & dPorosity_dTemperature, real64 const & biotCoefficient, real64 const & thermalExpansionCoefficient, - real64 const & meanStressIncrement, + real64 const & meanEffectiveStressIncrement_k, real64 const & bulkModulus ) const { real64 const biotSkeletonModulusInverse = (biotCoefficient - referencePorosity) / m_grainBulkModulus; - real64 const porosityThermalExpansion = 3 * thermalExpansionCoefficient * biotCoefficient; - - porosity = porosity_n + biotSkeletonModulusInverse * pressure + biotCoefficient * biotCoefficient / bulkModulus * pressure - - porosityThermalExpansion * temperature - + biotCoefficient * meanStressIncrement / bulkModulus; - - dPorosity_dPressure = biotSkeletonModulusInverse + biotCoefficient * biotCoefficient / bulkModulus; + real64 const porosityThermalExpansion = 3 * thermalExpansionCoefficient * ( biotCoefficient - referencePorosity ); + real64 const fixedStressPressureCoefficient = biotCoefficient * biotCoefficient / bulkModulus; + real64 const fixedStressTemperatureCoefficient = 3 * biotCoefficient * thermalExpansionCoefficient; + + porosity = porosity_n + + biotCoefficient * meanEffectiveStressIncrement_k / bulkModulus // change due to stress increment (at the previous + // sequential iteration) + + biotSkeletonModulusInverse * deltaPressureFromBeginningOfTimeStep // change due to pressure increment + - porosityThermalExpansion * deltaTemperatureFromBeginningOfTimeStep; // change due to temperature increment + dPorosity_dPressure = biotSkeletonModulusInverse; dPorosity_dTemperature = -porosityThermalExpansion; + + if( !isZero( meanEffectiveStressIncrement_k ) ) // TODO: find a better way to disable this at the first flow iteration + { + porosity += fixedStressPressureCoefficient * deltaPressureFromLastIteration // fixed-stress pressure term + + fixedStressTemperatureCoefficient * deltaTemperatureFromLastIteration; // fixed-stress temperature term + dPorosity_dPressure += fixedStressPressureCoefficient; + dPorosity_dTemperature += fixedStressTemperatureCoefficient; + } } GEOS_HOST_DEVICE virtual void updateFromPressureAndTemperature( localIndex const k, localIndex const q, - real64 const & pressure, - real64 const & pressure_n, + real64 const & pressure, // current + real64 const & pressure_k, // last iteration (for sequential) + real64 const & pressure_n, // last time step real64 const & temperature, + real64 const & temperature_k, real64 const & temperature_n ) const override final { - real64 const deltaPressure = pressure - pressure_n; - real64 const deltaTemperature = temperature - temperature_n; - - computePorosity( deltaPressure, - deltaTemperature, + real64 const deltaPressureFromBeginningOfTimeStep = pressure - pressure_n; + real64 const deltaPressureFromLastIteration = pressure - pressure_k; + real64 const deltaTemperatureFromBeginningOfTimeStep = temperature - temperature_n; + real64 const deltaTemperatureFromLastIteration = temperature - temperature_k; + + computePorosity( deltaPressureFromBeginningOfTimeStep, + deltaPressureFromLastIteration, + deltaTemperatureFromBeginningOfTimeStep, + deltaTemperatureFromLastIteration, m_porosity_n[k][q], m_referencePorosity[k], m_newPorosity[k][q], @@ -145,7 +166,7 @@ class BiotPorosityUpdates : public PorosityBaseUpdates m_dPorosity_dTemperature[k][q], m_biotCoefficient[k], m_thermalExpansionCoefficient[k], - m_meanStressIncrement[k][q], + m_averageMeanEffectiveStressIncrement_k[k], m_bulkModulus[k] ); } @@ -154,28 +175,37 @@ class BiotPorosityUpdates : public PorosityBaseUpdates real64 const bulkModulus ) const { m_bulkModulus[k] = bulkModulus; - m_biotCoefficient[k] = 1 - bulkModulus / m_grainBulkModulus; } GEOS_HOST_DEVICE - void updateTotalMeanStressIncrement( localIndex const k, - localIndex const q, - real64 const & totalMeanStressIncrement ) const + void updateMeanEffectiveStressIncrement( localIndex const k, + localIndex const q, + real64 const & meanEffectiveStressIncrement ) const { - m_meanStressIncrement[k][q] = totalMeanStressIncrement; + m_meanEffectiveStressIncrement_k[k][q] = meanEffectiveStressIncrement; } protected: - arrayView1d< real64 > m_biotCoefficient; - arrayView1d< real64 > m_thermalExpansionCoefficient; + /// Grain bulk modulus (read from XML) + real64 const m_grainBulkModulus; - arrayView2d< real64 > m_meanStressIncrement; + /// View on the thermal expansion coefficients (read from XML) + arrayView1d< real64 const > const m_thermalExpansionCoefficient; - arrayView1d< real64 > m_bulkModulus; + /// View on the Biot coefficient (updated by PorousSolid) + arrayView1d< real64 > const m_biotCoefficient; + + /// View on the bulk modulus (updated by PorousSolid) + arrayView1d< real64 > const m_bulkModulus; + + /// View on the mean stress increment at quadrature points (updated by PorousSolid) + arrayView2d< real64 > const m_meanEffectiveStressIncrement_k; + + /// View on the average mean stress increment + arrayView1d< real64 const > const m_averageMeanEffectiveStressIncrement_k; - real64 m_grainBulkModulus; }; class BiotPorosity : public PorosityBase @@ -194,7 +224,9 @@ class BiotPorosity : public PorosityBase { static constexpr char const *grainBulkModulusString() { return "grainBulkModulus"; } - static constexpr char const *meanStressIncrementString() { return "meanStressIncrement"; } + static constexpr char const *meanEffectiveStressIncrementString() { return "meanEffectiveStressIncrement"; } + + static constexpr char const *averageMeanEffectiveStressIncrementString() { return "averageMeanEffectiveStressIncrement"; } static constexpr char const *solidBulkModulusString() { return "solidBulkModulus"; } @@ -203,11 +235,25 @@ class BiotPorosity : public PorosityBase virtual void initializeState() const override final; + virtual void saveConvergedState() const override final; + + virtual void ignoreConvergedState() const override final; + virtual arrayView1d< real64 const > const getBiotCoefficient() const override final { return m_biotCoefficient.toViewConst(); } + virtual arrayView1d< real64 > const getAverageMeanEffectiveStressIncrement_k() override final + { + return m_averageMeanEffectiveStressIncrement_k.toView(); + } + + virtual arrayView2d< real64 const > const getMeanEffectiveStressIncrement_k() const override final + { + return m_meanEffectiveStressIncrement_k.toViewConst(); + } + using KernelWrapper = BiotPorosityUpdates; /** @@ -224,7 +270,8 @@ class BiotPorosity : public PorosityBase m_referencePorosity, m_biotCoefficient, m_thermalExpansionCoefficient, - m_meanStressIncrement, + m_meanEffectiveStressIncrement_k, + m_averageMeanEffectiveStressIncrement_k, m_bulkModulus, m_grainBulkModulus ); } @@ -232,17 +279,27 @@ class BiotPorosity : public PorosityBase protected: virtual void postProcessInput() override; - array1d< real64 > m_biotCoefficient; + /// Default thermal expansion coefficients (read from XML) + real64 m_defaultThermalExpansionCoefficient; + + /// Thermal expansion coefficients (read from XML) array1d< real64 > m_thermalExpansionCoefficient; + /// Biot coefficients (update in the update class, not read in input) + array1d< real64 > m_biotCoefficient; + + /// Bulk modulus (updated in the update class, not read in input) array1d< real64 > m_bulkModulus; - array2d< real64 > m_meanStressIncrement; + /// Mean stress increment (updated in the update class, not read in input) + array2d< real64 > m_meanEffectiveStressIncrement_k; - real64 m_grainBulkModulus; + /// Average mean stress increment (not read in input) + array1d< real64 > m_averageMeanEffectiveStressIncrement_k; - real64 m_defaultThermalExpansionCoefficient; + /// Grain bulk modulus (read from XML) + real64 m_grainBulkModulus; }; } /* namespace constitutive */ diff --git a/src/coreComponents/constitutive/solid/porosity/PorosityBase.cpp b/src/coreComponents/constitutive/solid/porosity/PorosityBase.cpp index 8059d228080..7bd1990640d 100644 --- a/src/coreComponents/constitutive/solid/porosity/PorosityBase.cpp +++ b/src/coreComponents/constitutive/solid/porosity/PorosityBase.cpp @@ -90,6 +90,11 @@ void PorosityBase::saveConvergedState() const m_porosity_n.setValues< parallelDevicePolicy<> >( m_newPorosity.toViewConst() ); } +void PorosityBase::ignoreConvergedState() const +{ + m_newPorosity.setValues< parallelDevicePolicy<> >( m_porosity_n.toViewConst() ); +} + void PorosityBase::initializeState() const { m_porosity_n.setValues< parallelDevicePolicy<> >( m_newPorosity.toViewConst() ); diff --git a/src/coreComponents/constitutive/solid/porosity/PorosityBase.hpp b/src/coreComponents/constitutive/solid/porosity/PorosityBase.hpp index 7424422cb90..4a8ca5ea528 100644 --- a/src/coreComponents/constitutive/solid/porosity/PorosityBase.hpp +++ b/src/coreComponents/constitutive/solid/porosity/PorosityBase.hpp @@ -45,11 +45,11 @@ class PorosityBaseUpdates localIndex numGauss() const { return m_newPorosity.size( 1 ); } PorosityBaseUpdates( arrayView2d< real64 > const & newPorosity, - arrayView2d< real64 > const & porosity_n, + arrayView2d< real64 const > const & porosity_n, arrayView2d< real64 > const & dPorosity_dPressure, arrayView2d< real64 > const & dPorosity_dTemperature, - arrayView2d< real64 > const & initialPorosity, - arrayView1d< real64 > const & referencePorosity ): + arrayView2d< real64 const > const & initialPorosity, + arrayView1d< real64 const > const & referencePorosity ): m_newPorosity( newPorosity ), m_porosity_n( porosity_n ), m_dPorosity_dPressure( dPorosity_dPressure ), @@ -80,7 +80,7 @@ class PorosityBaseUpdates } /** - * @brief Helper to save point stress back to m_newPorosity array + * @brief Helper to save porosity back to m_newPorosity array * * This is mostly defined for improving code readability. * @@ -132,26 +132,35 @@ class PorosityBaseUpdates virtual void updateFromPressureAndTemperature( localIndex const k, localIndex const q, real64 const & pressure, + real64 const & pressure_k, real64 const & pressure_n, real64 const & temperature, + real64 const & temperature_k, real64 const & temperature_n ) const { - GEOS_UNUSED_VAR( k, q, pressure, pressure_n, temperature, temperature_n ); + GEOS_UNUSED_VAR( k, q, pressure, pressure_k, pressure_n, temperature, temperature_k, temperature_n ); GEOS_ERROR( "updateFromPressureAndTemperature is not implemented for porosityBase." ); } protected: - arrayView2d< real64 > m_newPorosity; - arrayView2d< real64 > m_porosity_n; + /// New value of porosity + arrayView2d< real64 > const m_newPorosity; + + /// Value of porosity at the previous time step + arrayView2d< real64 const > const m_porosity_n; - arrayView2d< real64 > m_dPorosity_dPressure; + /// Derivative of porosity wrt pressure + arrayView2d< real64 > const m_dPorosity_dPressure; - arrayView2d< real64 > m_dPorosity_dTemperature; + /// Derivative of porosity wrt temperature + arrayView2d< real64 > const m_dPorosity_dTemperature; - arrayView2d< real64 > m_initialPorosity; + /// Initial porosity + arrayView2d< real64 const > const m_initialPorosity; - arrayView1d< real64 > m_referencePorosity; + /// Reference porosity + arrayView1d< real64 const > const m_referencePorosity; }; @@ -239,6 +248,10 @@ class PorosityBase : public ConstitutiveBase /// Save state data in preparation for next timestep virtual void saveConvergedState() const override; + /// Ignore the porosity update and return to the state of the system + /// This is useful after the initialization step + virtual void ignoreConvergedState() const; + /** * @brief Initialize newPorosity and porosity_n. */ @@ -246,12 +259,37 @@ class PorosityBase : public ConstitutiveBase virtual arrayView1d< real64 const > const getBiotCoefficient() const { - GEOS_ERROR( "getBiotPorosity() not implemented for this model" ); + GEOS_ERROR( "getBiotCoefficient() not implemented for this model" ); array1d< real64 > out; return out.toViewConst(); } + /** + * @brief Const/non-mutable accessor for the mean stress increment at the previous sequential iteration + * @return Accessor + */ + virtual arrayView2d< real64 const > const getMeanEffectiveStressIncrement_k() const + { + GEOS_ERROR( "getMeanEffectiveStressIncrement_k() not implemented for this model" ); + + array2d< real64 > out; + return out.toViewConst(); + } + + /** + * @brief Non-const accessor for the mean stress increment at the previous sequential iteration + * @return Accessor + */ + virtual arrayView1d< real64 > const getAverageMeanEffectiveStressIncrement_k() + { + GEOS_ERROR( "getAverageMeanEffectiveStressIncrement_k() not implemented for this model" ); + + array1d< real64 > out; + return out.toView(); + } + + using KernelWrapper = PorosityBaseUpdates; /** diff --git a/src/coreComponents/constitutive/solid/porosity/PorosityFields.hpp b/src/coreComponents/constitutive/solid/porosity/PorosityFields.hpp index f1469ac61e8..6bfb2226e56 100644 --- a/src/coreComponents/constitutive/solid/porosity/PorosityFields.hpp +++ b/src/coreComponents/constitutive/solid/porosity/PorosityFields.hpp @@ -94,6 +94,24 @@ DECLARE_FIELD( thermalExpansionCoefficient, WRITE_AND_READ, "Thermal expansion coefficient" ); +DECLARE_FIELD( meanEffectiveStressIncrement_k, + "meanEffectiveStressIncrement_k", + array2d< real64 >, + 0, + NOPLOT, + NO_WRITE, + "Mean effective stress increment at quadrature points at the previous sequential iteration" ); + +DECLARE_FIELD( averageMeanEffectiveStressIncrement_k, + "averageMeanEffectiveStressIncrement_k", + array1d< real64 >, + 0, + NOPLOT, + NO_WRITE, + "Mean effective stress increment averaged over quadrature points at the previous sequential iteration" ); + + + } } diff --git a/src/coreComponents/constitutive/solid/porosity/PressurePorosity.hpp b/src/coreComponents/constitutive/solid/porosity/PressurePorosity.hpp index 93bdd617400..584f69ce3e7 100644 --- a/src/coreComponents/constitutive/solid/porosity/PressurePorosity.hpp +++ b/src/coreComponents/constitutive/solid/porosity/PressurePorosity.hpp @@ -31,11 +31,11 @@ class PressurePorosityUpdates : public PorosityBaseUpdates public: PressurePorosityUpdates( arrayView2d< real64 > const & newPorosity, - arrayView2d< real64 > const & porosity_n, + arrayView2d< real64 const > const & porosity_n, arrayView2d< real64 > const & dPorosity_dPressure, arrayView2d< real64 > const & dPorosity_dTemperature, - arrayView2d< real64 > const & initialPorosity, - arrayView1d< real64 > const & referencePorosity, + arrayView2d< real64 const > const & initialPorosity, + arrayView1d< real64 const > const & referencePorosity, real64 const & referencePressure, real64 const & compressibility ): PorosityBaseUpdates( newPorosity, @@ -70,8 +70,10 @@ class PressurePorosityUpdates : public PorosityBaseUpdates virtual void updateFromPressureAndTemperature( localIndex const k, localIndex const q, real64 const & pressure, + real64 const & GEOS_UNUSED_PARAM( pressure_k ), real64 const & GEOS_UNUSED_PARAM( pressure_n ), real64 const & temperature, + real64 const & GEOS_UNUSED_PARAM( temperature_k ), real64 const & GEOS_UNUSED_PARAM( temperature_n ) ) const override final { computePorosity( pressure, @@ -84,9 +86,11 @@ class PressurePorosityUpdates : public PorosityBaseUpdates private: - real64 m_referencePressure; + /// Reference pressure used in the porosity model + real64 const m_referencePressure; - real64 m_compressibility; + /// Compressibility used in the porosity model + real64 const m_compressibility; }; diff --git a/src/coreComponents/constitutive/solid/porosity/ProppantPorosity.hpp b/src/coreComponents/constitutive/solid/porosity/ProppantPorosity.hpp index 56cf0a9db46..ef271b87883 100644 --- a/src/coreComponents/constitutive/solid/porosity/ProppantPorosity.hpp +++ b/src/coreComponents/constitutive/solid/porosity/ProppantPorosity.hpp @@ -31,11 +31,11 @@ class ProppantPorosityUpdates : public PorosityBaseUpdates public: ProppantPorosityUpdates( arrayView2d< real64 > const & newPorosity, - arrayView2d< real64 > const & porosity_n, + arrayView2d< real64 const > const & porosity_n, arrayView2d< real64 > const & dPorosity_dPressure, arrayView2d< real64 > const & dPorosity_dTemperature, - arrayView2d< real64 > const & initialPorosity, - arrayView1d< real64 > const & referencePorosity, + arrayView2d< real64 const > const & initialPorosity, + arrayView1d< real64 const > const & referencePorosity, real64 const & maxProppantConcentration ): PorosityBaseUpdates( newPorosity, porosity_n, @@ -64,7 +64,7 @@ class ProppantPorosityUpdates : public PorosityBaseUpdates private: - real64 m_maxProppantConcentration; + real64 const m_maxProppantConcentration; }; diff --git a/src/coreComponents/constitutive/thermalConductivity/MultiPhaseVolumeWeightedThermalConductivity.cpp b/src/coreComponents/constitutive/thermalConductivity/MultiPhaseVolumeWeightedThermalConductivity.cpp index 0ccbaf6feb3..5d8f996c0b5 100644 --- a/src/coreComponents/constitutive/thermalConductivity/MultiPhaseVolumeWeightedThermalConductivity.cpp +++ b/src/coreComponents/constitutive/thermalConductivity/MultiPhaseVolumeWeightedThermalConductivity.cpp @@ -98,15 +98,25 @@ void MultiPhaseVolumeWeightedThermalConductivity::initializeRockFluidState( arra void MultiPhaseVolumeWeightedThermalConductivity::saveConvergedRockFluidState( arrayView2d< real64 const > const & convergedPorosity, arrayView2d< real64 const, compflow::USD_PHASE > const & convergedPhaseVolumeFraction ) const { + // note that the update function is called here, and not in the solver, because porosity and phase volume fraction are treated explicitly KernelWrapper conductivityWrapper = createKernelWrapper(); forAll< parallelDevicePolicy<> >( conductivityWrapper.numElems(), [=] GEOS_HOST_DEVICE ( localIndex const k ) { + // here we compute an average of the porosity over quadrature points + // this average is exact for tets, regular pyramids/wedges/hexes, or for VEM + real64 porosityAveragedOverQuadraturePoints = 0; + for( integer i = 0; i < convergedPorosity.size( 1 ); ++i ) + { + porosityAveragedOverQuadraturePoints += convergedPorosity[k][i]; + } + porosityAveragedOverQuadraturePoints /= convergedPorosity.size( 1 ); + for( localIndex q = 0; q < conductivityWrapper.numGauss(); ++q ) { - conductivityWrapper.update( k, q, convergedPorosity[k][q], convergedPhaseVolumeFraction[k] ); + conductivityWrapper.update( k, q, porosityAveragedOverQuadraturePoints, convergedPhaseVolumeFraction[k] ); } } ); } diff --git a/src/coreComponents/constitutive/thermalConductivity/SinglePhaseConstantThermalConductivity.cpp b/src/coreComponents/constitutive/thermalConductivity/SinglePhaseConstantThermalConductivity.cpp index 4983bd1be07..0cd9199bb37 100644 --- a/src/coreComponents/constitutive/thermalConductivity/SinglePhaseConstantThermalConductivity.cpp +++ b/src/coreComponents/constitutive/thermalConductivity/SinglePhaseConstantThermalConductivity.cpp @@ -42,12 +42,9 @@ SinglePhaseConstantThermalConductivity::deliverClone( string const & name, return SinglePhaseThermalConductivityBase::deliverClone( name, parent ); } -void SinglePhaseConstantThermalConductivity::allocateConstitutiveData( dataRepository::Group & parent, - localIndex const numConstitutivePointsPerParentIndex ) +void SinglePhaseConstantThermalConductivity::initializeRockFluidState( arrayView2d< real64 const > const & initialPorosity ) const { - SinglePhaseThermalConductivityBase::allocateConstitutiveData( parent, numConstitutivePointsPerParentIndex ); - - for( localIndex ei = 0; ei < parent.size(); ++ei ) + for( localIndex ei = 0; ei < initialPorosity.size( 0 ); ++ei ) { // NOTE: enforcing 1 quadrature point for( localIndex q = 0; q < 1; ++q ) @@ -59,6 +56,33 @@ void SinglePhaseConstantThermalConductivity::allocateConstitutiveData( dataRepos } } +void SinglePhaseConstantThermalConductivity::update( arrayView2d< real64 const > const & initialPorosity ) const +{ + real64 thermalConductivityComponents[3]; + for( int i = 0; i<3; ++i ) + { + thermalConductivityComponents[i] = m_thermalConductivityComponents[i]; + } + arrayView3d< real64 > const effectiveConductivity = m_effectiveConductivity; + + forAll< parallelDevicePolicy<> >( initialPorosity.size( 0 ), [=] GEOS_HOST_DEVICE ( localIndex const ei ) + { + // NOTE: enforcing 1 quadrature point + for( localIndex q = 0; q < 1; ++q ) + { + effectiveConductivity[ei][q][0] = thermalConductivityComponents[0]; + effectiveConductivity[ei][q][1] = thermalConductivityComponents[1]; + effectiveConductivity[ei][q][2] = thermalConductivityComponents[2]; + } + } ); +} + +void SinglePhaseConstantThermalConductivity::allocateConstitutiveData( dataRepository::Group & parent, + localIndex const numConstitutivePointsPerParentIndex ) +{ + SinglePhaseThermalConductivityBase::allocateConstitutiveData( parent, numConstitutivePointsPerParentIndex ); +} + void SinglePhaseConstantThermalConductivity::postProcessInput() { GEOS_THROW_IF( m_thermalConductivityComponents[0] <= 0 || diff --git a/src/coreComponents/constitutive/thermalConductivity/SinglePhaseConstantThermalConductivity.hpp b/src/coreComponents/constitutive/thermalConductivity/SinglePhaseConstantThermalConductivity.hpp index e4c1e8ba8a6..ae343d61ed7 100644 --- a/src/coreComponents/constitutive/thermalConductivity/SinglePhaseConstantThermalConductivity.hpp +++ b/src/coreComponents/constitutive/thermalConductivity/SinglePhaseConstantThermalConductivity.hpp @@ -75,6 +75,12 @@ class SinglePhaseConstantThermalConductivity : public SinglePhaseThermalConducti virtual string getCatalogName() const override { return catalogName(); } + + virtual void initializeRockFluidState( arrayView2d< real64 const > const & initialPorosity ) const override final; + + virtual void update( arrayView2d< real64 const > const & porosity ) const override final; + + /// Type of kernel wrapper for in-kernel update using KernelWrapper = SinglePhaseConstantThermalConductivityUpdate; diff --git a/src/coreComponents/constitutive/thermalConductivity/SinglePhaseThermalConductivityBase.hpp b/src/coreComponents/constitutive/thermalConductivity/SinglePhaseThermalConductivityBase.hpp index 17d7f5f71b0..612f05623c2 100644 --- a/src/coreComponents/constitutive/thermalConductivity/SinglePhaseThermalConductivityBase.hpp +++ b/src/coreComponents/constitutive/thermalConductivity/SinglePhaseThermalConductivityBase.hpp @@ -115,6 +115,15 @@ class SinglePhaseThermalConductivityBase : public ConstitutiveBase virtual void saveConvergedRockFluidState( arrayView2d< real64 const > const & convergedPorosity ) const { GEOS_UNUSED_VAR( convergedPorosity ); } + /** + * @brief Update the thermal conductivity state + * @param[in] porosity the porosity field after reservoir initialization + * + * Note: this is needed because of the fracture subregions which do not exist at initialization + */ + virtual void update( arrayView2d< real64 const > const & porosity ) const + { GEOS_UNUSED_VAR( porosity ); } + /** * @brief Getter for the effective conductivities in the subRegion * @return an arrayView of effective conductivities diff --git a/src/coreComponents/constitutive/unitTests/CMakeLists.txt b/src/coreComponents/constitutive/unitTests/CMakeLists.txt index d6413741a98..b3a4c7ea2cc 100644 --- a/src/coreComponents/constitutive/unitTests/CMakeLists.txt +++ b/src/coreComponents/constitutive/unitTests/CMakeLists.txt @@ -6,7 +6,9 @@ set( gtest_geosx_tests testDamageUtilities.cpp testDruckerPrager.cpp testElasticIsotropic.cpp + testKValueInitialization.cpp testModifiedCamClay.cpp + testNegativeTwoPhaseFlash.cpp testParticleFluidEnums.cpp testPropertyConversions.cpp testCubicEOS.cpp diff --git a/src/coreComponents/constitutive/unitTests/TestFluid.hpp b/src/coreComponents/constitutive/unitTests/TestFluid.hpp new file mode 100644 index 00000000000..3784f5463be --- /dev/null +++ b/src/coreComponents/constitutive/unitTests/TestFluid.hpp @@ -0,0 +1,129 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2018-2020 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2020 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2018-2020 TotalEnergies + * Copyright (c) 2019- GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +/** + * @file TestFluid.hpp + */ + +#ifndef GEOS_CONSTITUTIVE_UNITTESTS_TESTFLUID_HPP_ +#define GEOS_CONSTITUTIVE_UNITTESTS_TESTFLUID_HPP_ + +#include "common/DataTypes.hpp" + +namespace geos +{ + +namespace testing +{ + +struct Fluid +{ + static constexpr integer NC = 11; + + static constexpr integer H2O = 0; + static constexpr integer CO2 = 1; + static constexpr integer N2 = 2; + static constexpr integer H2S = 3; + static constexpr integer C1 = 4; + static constexpr integer C2 = 5; + static constexpr integer C3 = 6; + static constexpr integer C4 = 7; + static constexpr integer C5 = 8; + static constexpr integer C8 = 9; + static constexpr integer C10 = 10; + + static constexpr integer Pc = 0; + static constexpr integer Tc = 1; + static constexpr integer Ac = 2; + static constexpr integer Mw = 3; + + static std::array< real64, 44 > data; +}; + +template< int NC > +using Feed = std::array< real64, NC >; + +template< int NC > +class TestFluid +{ +public: + ~TestFluid() = default; + + static std::unique_ptr< TestFluid< NC > > create( std::array< integer, NC > const & components ) + { + std::unique_ptr< TestFluid< NC > > testFluid( new TestFluid() ); + createArray( testFluid->criticalPressure, components, Fluid::Pc, Fluid::data ); + createArray( testFluid->criticalTemperature, components, Fluid::Tc, Fluid::data ); + createArray( testFluid->acentricFactor, components, Fluid::Ac, Fluid::data ); + createArray( testFluid->molecularWeight, components, Fluid::Mw, Fluid::data ); + return testFluid; + } + + arrayView1d< real64 const > const getCriticalPressure() const { return criticalPressure.toViewConst(); } + arrayView1d< real64 const > const getCriticalTemperature() const { return criticalTemperature.toViewConst(); } + arrayView1d< real64 const > const getCriticalVolume() const { return criticalVolume.toViewConst(); } + arrayView1d< real64 const > const getAcentricFactor() const { return acentricFactor.toViewConst(); } + arrayView1d< real64 const > const getMolecularWeight() const { return molecularWeight.toViewConst(); } + arrayView1d< real64 const > const getVolumeShift() const { return volumeShift.toViewConst(); } + +private: + TestFluid() = default; + + array1d< real64 > criticalPressure; + array1d< real64 > criticalTemperature; + array1d< real64 > criticalVolume; + array1d< real64 > acentricFactor; + array1d< real64 > molecularWeight; + array1d< real64 > volumeShift; + +private: + template< typename ARRAY, typename LIST, typename DATAARRAY > + static void createArray( ARRAY & array, LIST const & indices, integer const row, DATAARRAY const & data ) + { + for( auto const i : indices ) + { + array.emplace_back( data[Fluid::NC *row+i] ); + } + } +public: + template< typename ARRAY, typename LIST > + static void createArray( ARRAY & array, LIST const & data ) + { + for( auto const value : data ) + { + array.emplace_back( value ); + } + } +}; + +std::array< real64, 44 > Fluid::data = { + // -- Pc + 2.2050e+07, 7.3750e+06, 3.4000e+06, 8.9630e+06, 1.2960e+06, 4.8721e+06, + 4.2481e+06, 3.6400e+06, 4.5990e+06, 2.5300e+06, 1.4600e+06, + // -- Tc + 6.4700e+02, 3.0410e+02, 1.2620e+02, 3.7353e+02, 3.3150e+01, 3.0532e+02, + 3.6983e+02, 4.0785e+02, 1.9060e+02, 6.2200e+02, 7.8200e+02, + // -- Ac + 3.4400e-01, 2.3900e-01, 4.0000e-02, 9.4200e-02, -2.1900e-01, 9.9500e-02, + 1.5230e-01, 1.8440e-01, 1.1400e-02, 4.4300e-01, 8.1600e-01, + // -- Mw + 1.8015e+01, 4.4010e+01, 2.8013e+01, 3.4100e+01, 1.6043e+01, 3.0070e+01, + 4.4097e+01, 5.8124e+01, 7.2151e+01, 1.1423e+02, 1.4228e+02, +}; + +}// testing + +}// geos + +#endif //GEOS_CONSTITUTIVE_UNITTESTS_TESTFLUID_HPP_ diff --git a/src/coreComponents/constitutive/unitTests/testCubicEOS.cpp b/src/coreComponents/constitutive/unitTests/testCubicEOS.cpp index e41ad9eee85..4a1b08b3d9b 100644 --- a/src/coreComponents/constitutive/unitTests/testCubicEOS.cpp +++ b/src/coreComponents/constitutive/unitTests/testCubicEOS.cpp @@ -12,11 +12,11 @@ * ------------------------------------------------------------------------------------------------------------ */ - // Source includes #include "codingUtilities/UnitTestUtilities.hpp" #include "common/DataTypes.hpp" #include "constitutive/fluid/multifluid/compositional/functions/CubicEOSPhaseModel.hpp" +#include "TestFluid.hpp" // TPL includes #include @@ -25,31 +25,26 @@ using namespace geos; using namespace geos::testing; using namespace geos::constitutive; -static constexpr real64 relTol = 1e-5; +static constexpr real64 relTol = 1.0e-5; +static constexpr real64 absTol = 1.0e-8; TEST( CubicEOSTest, testCubicEOSTwoComponentsSRK ) { constexpr integer numComps = 2; + auto fluid = TestFluid< numComps >::create( {Fluid::C1, Fluid::C5} ); + + auto criticalPressure = fluid->getCriticalPressure(); + auto criticalTemperature = fluid->getCriticalTemperature(); + auto omega = fluid->getAcentricFactor(); + real64 binaryInteractionCoefficients = 0.0; // not implemented yet + real64 pressure = 0.0; real64 temperature = 0.0; array1d< real64 > composition( numComps ); - array1d< real64 > criticalPressure( numComps ); - array1d< real64 > criticalTemperature( numComps ); - array1d< real64 > omega( numComps ); - real64 binaryInteractionCoefficients = 0.0; // not implemented yet array1d< real64 > logFugacityCoefficients( numComps ); array1d< real64 > expectedLogFugacityCoefficients( numComps ); - criticalPressure[0] = 12.96e5; - criticalPressure[1] = 45.99e5; - - criticalTemperature[0] = 33.15; - criticalTemperature[1] = 190.6; - - omega[0] = -0.219; - omega[1] = 0.0114; - /////////////////////////////////////////// pressure = 1e6; @@ -132,31 +127,19 @@ TEST( CubicEOSTest, testCubicEOSFourComponentsPR ) { constexpr integer numComps = 4; + auto fluid = TestFluid< numComps >::create( {Fluid::N2, Fluid::C8, Fluid::C10, Fluid::H2O} ); + + auto criticalPressure = fluid->getCriticalPressure(); + auto criticalTemperature = fluid->getCriticalTemperature(); + auto omega = fluid->getAcentricFactor(); + real64 binaryInteractionCoefficients = 0.0; // not implemented yet + real64 pressure = 0.0; real64 temperature = 0.0; array1d< real64 > composition( numComps ); - array1d< real64 > criticalPressure( numComps ); - array1d< real64 > criticalTemperature( numComps ); - array1d< real64 > omega( numComps ); - real64 binaryInteractionCoefficients = 0.0; // not implemented yet array1d< real64 > logFugacityCoefficients( numComps ); array1d< real64 > expectedLogFugacityCoefficients( numComps ); - criticalPressure[0] = 34e5; - criticalPressure[1] = 25.3e5; - criticalPressure[2] = 14.6e5; - criticalPressure[3] = 220.5e5; - - criticalTemperature[0] = 126.2; - criticalTemperature[1] = 622.0; - criticalTemperature[2] = 782.0; - criticalTemperature[3] = 647.0; - - omega[0] = 0.04; - omega[1] = 0.443; - omega[2] = 0.816; - omega[3] = 0.344; - /////////////////////////////////////////// pressure = 1e7; @@ -236,33 +219,21 @@ TEST( CubicEOSTest, testCubicEOSFourComponentsPR ) TEST( CubicEOSTest, testCubicEOSFourComponentsSRK ) { - integer const numComps = 4; + constexpr integer numComps = 4; + + auto fluid = TestFluid< numComps >::create( {Fluid::N2, Fluid::C8, Fluid::C10, Fluid::H2O} ); + + auto criticalPressure = fluid->getCriticalPressure(); + auto criticalTemperature = fluid->getCriticalTemperature(); + auto omega = fluid->getAcentricFactor(); + real64 binaryInteractionCoefficients = 0.0; // not implemented yet real64 pressure = 0.0; real64 temperature = 0.0; array1d< real64 > composition( 4 ); - array1d< real64 > criticalPressure( 4 ); - array1d< real64 > criticalTemperature( 4 ); - array1d< real64 > omega( 4 ); - real64 binaryInteractionCoefficients = 0.0; // not implemented yet array1d< real64 > logFugacityCoefficients( 4 ); array1d< real64 > expectedLogFugacityCoefficients( 4 ); - criticalPressure[0] = 34e5; - criticalPressure[1] = 25.3e5; - criticalPressure[2] = 14.6e5; - criticalPressure[3] = 220.5e5; - - criticalTemperature[0] = 126.2; - criticalTemperature[1] = 622.0; - criticalTemperature[2] = 782.0; - criticalTemperature[3] = 647.0; - - omega[0] = 0.04; - omega[1] = 0.443; - omega[2] = 0.816; - omega[3] = 0.344; - /////////////////////////////////////////// pressure = 1e7; @@ -339,3 +310,422 @@ TEST( CubicEOSTest, testCubicEOSFourComponentsSRK ) checkRelativeError( logFugacityCoefficients[3], expectedLogFugacityCoefficients[3], relTol ); } + +// ----------------------------------------------------------------- +// Derivative tests +// ----------------------------------------------------------------- + +template< int NC > +using TestData = std::tuple< real64 const, real64 const, Feed< NC > const >; + +template< int NC > +struct TestFeed +{ + static std::array< Feed< NC >, 3 > const feeds; +}; + +template<> +std::array< Feed< 2 >, 3 > const TestFeed< 2 >::feeds = { + Feed< 2 >{0.100000, 0.900000}, + Feed< 2 >{0.500000, 0.500000}, + Feed< 2 >{0.001000, 0.999000} +}; + +template<> +std::array< Feed< 4 >, 3 > const TestFeed< 4 >::feeds = { + Feed< 4 >{0.030933, 0.319683, 0.637861, 0.011523}, + Feed< 4 >{0.000000, 0.349686, 0.637891, 0.012423}, + Feed< 4 >{0.000000, 0.349686, 0.650314, 0.000000} +}; + +template< int NC > +std::vector< TestData< NC > > generateTestData() +{ + std::array< real64 const, 2 > pressures( {1.83959e+06, 1.83959e+08} ); + std::array< real64 const, 2 > temperatures( {2.97150e+02, 3.63000e+02} ); + std::vector< TestData< NC > > testData; + for( const real64 pressure : pressures ) + { + for( const real64 temperature : temperatures ) + { + for( const auto & composition : TestFeed< NC >::feeds ) + { + testData.emplace_back( pressure, temperature, composition ); + } + } + } + return testData; +} + +template< typename EOS, int NC > +class DerivativeTestFixture : public ::testing::TestWithParam< TestData< NC > > +{ +public: + static constexpr integer numComps = NC; + using ParamType = std::tuple< real64 const, real64 const, Feed< NC > const >; +public: + DerivativeTestFixture(); + ~DerivativeTestFixture() = default; + +protected: + void checkDerivative( real64 const a, real64 const b, string const & name ) const + { + checkRelativeError( a, b, relTol, absTol, name ); + } + + std::unique_ptr< TestFluid< NC > > m_fluid; +}; + +template<> +DerivativeTestFixture< PengRobinsonEOS, 2 >::DerivativeTestFixture() + : m_fluid( TestFluid< 2 >::create( {Fluid::C1, Fluid::C5} )) +{} +template<> +DerivativeTestFixture< SoaveRedlichKwongEOS, 2 >::DerivativeTestFixture() + : m_fluid( TestFluid< 2 >::create( {Fluid::C1, Fluid::C5} )) +{} + +template<> +DerivativeTestFixture< PengRobinsonEOS, 4 >::DerivativeTestFixture() + : m_fluid( TestFluid< 4 >::create( {Fluid::N2, Fluid::C8, Fluid::C10, Fluid::H2O} )) +{} +template<> +DerivativeTestFixture< SoaveRedlichKwongEOS, 4 >::DerivativeTestFixture() + : m_fluid( TestFluid< 4 >::create( {Fluid::N2, Fluid::C8, Fluid::C10, Fluid::H2O} )) +{} + +template< typename EOS, int NC > +class MixCoeffDerivativeTestFixture : public DerivativeTestFixture< EOS, NC > +{ +public: + using DerivativeTestFixture< EOS, NC >::numComps; + using ParamType = typename DerivativeTestFixture< EOS, NC >::ParamType; +public: + void testNumericalDerivatives( ParamType const & testData ) const + { + auto criticalPressure = this->m_fluid->getCriticalPressure(); + auto criticalTemperature = this->m_fluid->getCriticalTemperature(); + auto omega = this->m_fluid->getAcentricFactor(); + real64 binaryInteractionCoefficients = 0.0; // not implemented yet + + array1d< real64 > aPureCoefficient( numComps ); + array1d< real64 > bPureCoefficient( numComps ); + real64 aMixtureCoefficient = 0.0; + real64 bMixtureCoefficient = 0.0; + real64 currentAMixtureCoefficient = 0.0; + real64 currentBMixtureCoefficient = 0.0; + real64 fdDerivative = 0.0; + + real64 daMixtureCoefficient_dp = 0.0; + real64 dbMixtureCoefficient_dp = 0.0; + real64 daMixtureCoefficient_dt = 0.0; + real64 dbMixtureCoefficient_dt = 0.0; + array1d< real64 > daMixtureCoefficient_dz( numComps ); + array1d< real64 > dbMixtureCoefficient_dz( numComps ); + + array1d< real64 > composition; + real64 const pressure = std::get< 0 >( testData ); + real64 const temperature = std::get< 1 >( testData ); + TestFluid< NC >::createArray( composition, std::get< 2 >( testData )); + + auto computeCoefficients = [&]( real64 const p, real64 const t, auto const & zmf, real64 & a, real64 & b ){ + CubicEOSPhaseModel< EOS >::computeMixtureCoefficients( + numComps, + p, t, zmf, + criticalPressure, criticalTemperature, omega, + binaryInteractionCoefficients, + aPureCoefficient, + bPureCoefficient, + a, b + ); + }; + + // Calculate values + computeCoefficients( pressure, temperature, composition, aMixtureCoefficient, bMixtureCoefficient ); + // Calculate derivatives + CubicEOSPhaseModel< EOS >::computeMixtureCoefficients( + numComps, + pressure, + temperature, + composition, + criticalPressure, + criticalTemperature, + omega, + binaryInteractionCoefficients, + aPureCoefficient, + bPureCoefficient, + aMixtureCoefficient, + bMixtureCoefficient, + daMixtureCoefficient_dp, + dbMixtureCoefficient_dp, + daMixtureCoefficient_dt, + dbMixtureCoefficient_dt, + daMixtureCoefficient_dz, + dbMixtureCoefficient_dz ); + // Compare against numerical derivatives + // -- Pressure derivative + real64 const dp = 1.0e-4 * pressure; + computeCoefficients( pressure-dp, temperature, composition, currentAMixtureCoefficient, currentBMixtureCoefficient ); + fdDerivative = -(currentAMixtureCoefficient - aMixtureCoefficient) / dp; + this->checkDerivative( daMixtureCoefficient_dp, fdDerivative, "Mixing Coeff A left pressure derivative" ); + fdDerivative = -(currentBMixtureCoefficient - bMixtureCoefficient) / dp; + this->checkDerivative( dbMixtureCoefficient_dp, fdDerivative, "Mixing Coeff B left pressure derivative" ); + computeCoefficients( pressure+dp, temperature, composition, currentAMixtureCoefficient, currentBMixtureCoefficient ); + fdDerivative = (currentAMixtureCoefficient - aMixtureCoefficient) / dp; + this->checkDerivative( daMixtureCoefficient_dp, fdDerivative, "Mixing Coeff A right pressure derivative" ); + fdDerivative = (currentBMixtureCoefficient - bMixtureCoefficient) / dp; + this->checkDerivative( dbMixtureCoefficient_dp, fdDerivative, "Mixing Coeff B right pressure derivative" ); + // -- Temperature derivative + real64 const dt = 1.0e-6 * temperature; + computeCoefficients( pressure, temperature-dt, composition, currentAMixtureCoefficient, currentBMixtureCoefficient ); + fdDerivative = -(currentAMixtureCoefficient - aMixtureCoefficient) / dt; + this->checkDerivative( daMixtureCoefficient_dt, fdDerivative, "Mixing Coeff A left temperature derivative" ); + fdDerivative = -(currentBMixtureCoefficient - bMixtureCoefficient) / dt; + this->checkDerivative( dbMixtureCoefficient_dt, fdDerivative, "Mixing Coeff B left temperature derivative" ); + computeCoefficients( pressure, temperature+dt, composition, currentAMixtureCoefficient, currentBMixtureCoefficient ); + fdDerivative = (currentAMixtureCoefficient - aMixtureCoefficient) / dt; + this->checkDerivative( daMixtureCoefficient_dt, fdDerivative, "Mixing Coeff A right temperature derivative" ); + fdDerivative = (currentBMixtureCoefficient - bMixtureCoefficient) / dt; + this->checkDerivative( dbMixtureCoefficient_dt, fdDerivative, "Mixing Coeff B right temperature derivative" ); + // -- Composition derivatives derivative + real64 const dz = 1.0e-7; + for( integer ic = 0; ic < numComps; ++ic ) + { + composition[ic] -= dz; + computeCoefficients( pressure, temperature, composition, currentAMixtureCoefficient, currentBMixtureCoefficient ); + fdDerivative = -(currentAMixtureCoefficient - aMixtureCoefficient) / dz; + this->checkDerivative( daMixtureCoefficient_dz[ic], fdDerivative, "Mixing Coeff A left composition derivative" ); + fdDerivative = -(currentBMixtureCoefficient - bMixtureCoefficient) / dz; + this->checkDerivative( dbMixtureCoefficient_dz[ic], fdDerivative, "Mixing Coeff B left composition derivative" ); + composition[ic] += 2.0*dz; + computeCoefficients( pressure, temperature, composition, currentAMixtureCoefficient, currentBMixtureCoefficient ); + fdDerivative = (currentAMixtureCoefficient - aMixtureCoefficient) / dz; + this->checkDerivative( daMixtureCoefficient_dz[ic], fdDerivative, "Mixing Coeff A right composition derivative" ); + fdDerivative = (currentBMixtureCoefficient - bMixtureCoefficient) / dz; + this->checkDerivative( dbMixtureCoefficient_dz[ic], fdDerivative, "Mixing Coeff B right composition derivative" ); + composition[ic] -= dz; + } + } +}; + +using MixCoeffDerivativePR2TestFixture = MixCoeffDerivativeTestFixture< PengRobinsonEOS, 2 >; +using MixCoeffDerivativePR4TestFixture = MixCoeffDerivativeTestFixture< PengRobinsonEOS, 4 >; +using MixCoeffDerivativeSRK2TestFixture = MixCoeffDerivativeTestFixture< SoaveRedlichKwongEOS, 2 >; +using MixCoeffDerivativeSRK4TestFixture = MixCoeffDerivativeTestFixture< SoaveRedlichKwongEOS, 4 >; + +TEST_P( MixCoeffDerivativePR2TestFixture, testNumericalDerivatives ) +{ + testNumericalDerivatives( GetParam() ); +} +TEST_P( MixCoeffDerivativePR4TestFixture, testNumericalDerivatives ) +{ + testNumericalDerivatives( GetParam() ); +} +TEST_P( MixCoeffDerivativeSRK2TestFixture, testNumericalDerivatives ) +{ + testNumericalDerivatives( GetParam() ); +} +TEST_P( MixCoeffDerivativeSRK4TestFixture, testNumericalDerivatives ) +{ + testNumericalDerivatives( GetParam() ); +} + +// 2-component fluid test +INSTANTIATE_TEST_SUITE_P( + CubicEOSTest, + MixCoeffDerivativePR2TestFixture, + ::testing::ValuesIn( generateTestData< 2 >()) + ); +INSTANTIATE_TEST_SUITE_P( + CubicEOSTest, + MixCoeffDerivativeSRK2TestFixture, + ::testing::ValuesIn( generateTestData< 2 >()) + ); + +// 4-component fluid test +INSTANTIATE_TEST_SUITE_P( + CubicEOSTest, + MixCoeffDerivativePR4TestFixture, + ::testing::ValuesIn( generateTestData< 4 >()) + ); + +INSTANTIATE_TEST_SUITE_P( + CubicEOSTest, + MixCoeffDerivativeSRK4TestFixture, + ::testing::ValuesIn( generateTestData< 4 >()) + ); + +template< typename EOS, int NC > +class CompressibilityDerivativeTestFixture : public DerivativeTestFixture< EOS, NC > +{ +public: + using DerivativeTestFixture< EOS, NC >::numComps; + using ParamType = typename DerivativeTestFixture< EOS, NC >::ParamType; +public: + void testNumericalDerivatives( ParamType const & testData ) const + { + auto criticalPressure = this->m_fluid->getCriticalPressure(); + auto criticalTemperature = this->m_fluid->getCriticalTemperature(); + auto omega = this->m_fluid->getAcentricFactor(); + real64 binaryInteractionCoefficients = 0.0; // not implemented yet + + array1d< real64 > aPureCoefficient( numComps ); + array1d< real64 > bPureCoefficient( numComps ); + real64 aMixtureCoefficient = 0.0; + real64 bMixtureCoefficient = 0.0; + real64 daMixtureCoefficient_dp = 0.0; + real64 dbMixtureCoefficient_dp = 0.0; + real64 daMixtureCoefficient_dt = 0.0; + real64 dbMixtureCoefficient_dt = 0.0; + array1d< real64 > daMixtureCoefficient_dz( numComps ); + array1d< real64 > dbMixtureCoefficient_dz( numComps ); + + real64 compressibilityFactor = 0.0; + real64 dCompressibilityFactor_dp = 0.0; + real64 dCompressibilityFactor_dt = 0.0; + array1d< real64 > dCompressibilityFactor_dz( numComps ); + + real64 currentCompressibilityFactor = 0.0; + real64 fdDerivative = 0.0; + + array1d< real64 > composition; + real64 const pressure = std::get< 0 >( testData ); + real64 const temperature = std::get< 1 >( testData ); + TestFluid< NC >::createArray( composition, std::get< 2 >( testData )); + + auto computeCompressibilityFactor = [&]( real64 const p, real64 const t, auto const & zmf, real64 & z ){ + CubicEOSPhaseModel< EOS >::computeMixtureCoefficients( + numComps, + p, t, zmf, + criticalPressure, criticalTemperature, omega, + binaryInteractionCoefficients, + aPureCoefficient, + bPureCoefficient, + aMixtureCoefficient, bMixtureCoefficient + ); + CubicEOSPhaseModel< EOS >::computeCompressibilityFactor( + numComps, + zmf, + binaryInteractionCoefficients, + aPureCoefficient, + bPureCoefficient, + aMixtureCoefficient, + bMixtureCoefficient, + z ); + }; + + // Calculate values + computeCompressibilityFactor( pressure, temperature, composition, compressibilityFactor ); + // Calculate derivatives + CubicEOSPhaseModel< EOS >::computeMixtureCoefficients( + numComps, + pressure, + temperature, + composition, + criticalPressure, + criticalTemperature, + omega, + binaryInteractionCoefficients, + aPureCoefficient, + bPureCoefficient, + aMixtureCoefficient, + bMixtureCoefficient, + daMixtureCoefficient_dp, + dbMixtureCoefficient_dp, + daMixtureCoefficient_dt, + dbMixtureCoefficient_dt, + daMixtureCoefficient_dz, + dbMixtureCoefficient_dz ); + CubicEOSPhaseModel< EOS >::computeCompressibilityFactor( + numComps, + aMixtureCoefficient, + bMixtureCoefficient, + compressibilityFactor, + daMixtureCoefficient_dp, + dbMixtureCoefficient_dp, + daMixtureCoefficient_dt, + dbMixtureCoefficient_dt, + daMixtureCoefficient_dz, + dbMixtureCoefficient_dz, + dCompressibilityFactor_dp, + dCompressibilityFactor_dt, + dCompressibilityFactor_dz ); + // Compare against numerical derivatives + // -- Pressure derivative + real64 const dp = 1.0e-4 * pressure; + computeCompressibilityFactor( pressure-dp, temperature, composition, currentCompressibilityFactor ); + fdDerivative = -(currentCompressibilityFactor - compressibilityFactor) / dp; + this->checkDerivative( dCompressibilityFactor_dp, fdDerivative, "Compressibility factor left pressure derivative" ); + computeCompressibilityFactor( pressure+dp, temperature, composition, currentCompressibilityFactor ); + fdDerivative = (currentCompressibilityFactor - compressibilityFactor) / dp; + this->checkDerivative( dCompressibilityFactor_dp, fdDerivative, "Compressibility factor right pressure derivative" ); + // -- Temperature derivative + real64 const dt = 1.0e-6 * temperature; + computeCompressibilityFactor( pressure, temperature-dt, composition, currentCompressibilityFactor ); + fdDerivative = -(currentCompressibilityFactor - compressibilityFactor) / dt; + this->checkDerivative( dCompressibilityFactor_dt, fdDerivative, "Compressibility factor left temperature derivative" ); + computeCompressibilityFactor( pressure, temperature+dt, composition, currentCompressibilityFactor ); + fdDerivative = (currentCompressibilityFactor - compressibilityFactor) / dt; + this->checkDerivative( dCompressibilityFactor_dt, fdDerivative, "Compressibility factor right temperature derivative" ); + // -- Composition derivatives derivative + real64 const dz = 1.0e-7; + for( integer ic = 0; ic < numComps; ++ic ) + { + composition[ic] -= dz; + computeCompressibilityFactor( pressure, temperature, composition, currentCompressibilityFactor ); + fdDerivative = -(currentCompressibilityFactor - compressibilityFactor) / dz; + this->checkDerivative( dCompressibilityFactor_dz[ic], fdDerivative, "Compressibility factor left composition derivative" ); + composition[ic] += 2.0*dz; + computeCompressibilityFactor( pressure, temperature, composition, currentCompressibilityFactor ); + fdDerivative = (currentCompressibilityFactor - compressibilityFactor) / dz; + this->checkDerivative( dCompressibilityFactor_dz[ic], fdDerivative, "Compressibility factor right composition derivative" ); + composition[ic] -= dz; + } + } +}; + +using CompressibilityDerivativePR2TestFixture = CompressibilityDerivativeTestFixture< PengRobinsonEOS, 2 >; +using CompressibilityDerivativePR4TestFixture = CompressibilityDerivativeTestFixture< PengRobinsonEOS, 4 >; +using CompressibilityDerivativeSRK2TestFixture = CompressibilityDerivativeTestFixture< SoaveRedlichKwongEOS, 2 >; +using CompressibilityDerivativeSRK4TestFixture = CompressibilityDerivativeTestFixture< SoaveRedlichKwongEOS, 4 >; + +TEST_P( CompressibilityDerivativePR2TestFixture, testNumericalDerivatives ) +{ + testNumericalDerivatives( GetParam() ); +} +TEST_P( CompressibilityDerivativePR4TestFixture, testNumericalDerivatives ) +{ + testNumericalDerivatives( GetParam() ); +} +TEST_P( CompressibilityDerivativeSRK2TestFixture, testNumericalDerivatives ) +{ + testNumericalDerivatives( GetParam() ); +} +TEST_P( CompressibilityDerivativeSRK4TestFixture, testNumericalDerivatives ) +{ + testNumericalDerivatives( GetParam() ); +} + +// 2-component fluid test +INSTANTIATE_TEST_SUITE_P( + CubicEOSTest, + CompressibilityDerivativePR2TestFixture, + ::testing::ValuesIn( generateTestData< 2 >()) + ); +INSTANTIATE_TEST_SUITE_P( + CubicEOSTest, + CompressibilityDerivativeSRK2TestFixture, + ::testing::ValuesIn( generateTestData< 2 >()) + ); + +// 4-component fluid test +INSTANTIATE_TEST_SUITE_P( + CubicEOSTest, + CompressibilityDerivativePR4TestFixture, + ::testing::ValuesIn( generateTestData< 4 >()) + ); +INSTANTIATE_TEST_SUITE_P( + CubicEOSTest, + CompressibilityDerivativeSRK4TestFixture, + ::testing::ValuesIn( generateTestData< 4 >()) + ); diff --git a/src/coreComponents/constitutive/unitTests/testKValueInitialization.cpp b/src/coreComponents/constitutive/unitTests/testKValueInitialization.cpp new file mode 100644 index 00000000000..1e03c31ff8f --- /dev/null +++ b/src/coreComponents/constitutive/unitTests/testKValueInitialization.cpp @@ -0,0 +1,224 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2018-2020 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2020 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2018-2020 TotalEnergies + * Copyright (c) 2019- GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +// Source includes +#include "common/DataTypes.hpp" +#include "codingUtilities/UnitTestUtilities.hpp" +#include "constitutive/fluid/multifluid/compositional/functions/KValueInitialization.hpp" + +// TPL includes +#include + +namespace geos +{ +namespace testing +{ +static constexpr real64 relTol = 1.0e-4; + +// Parameters are +// 0 - pressure +// 1 - temperature +// 2 - index of k-value +// 3 - expected k-value +template< int NC > +class WilsonKValueInitializationTestFixture : + public ::testing::TestWithParam< std::tuple< real64 const, real64 const, integer const, real64 const > > +{ +public: + using Feed = std::initializer_list< real64 const >; +public: + WilsonKValueInitializationTestFixture( Feed const Pc, Feed const Tc, Feed const omega ) + : numComps( NC ) + { + assign( criticalPressure, Pc ); + assign( criticalTemperature, Tc ); + assign( acentricFactor, omega ); + } + virtual ~WilsonKValueInitializationTestFixture() = default; + + void testKValues() + { + real64 const pressure = std::get< 0 >( GetParam()); + real64 const temperature = std::get< 1 >( GetParam()); + integer const compIndex = std::get< 2 >( GetParam()); + real64 const expectedKValue = std::get< 3 >( GetParam()); + + array1d< real64 > kValues( numComps ); + + constitutive::KValueInitialization:: + computeWilsonGasLiquidKvalue( numComps, + pressure, + temperature, + criticalPressure, + criticalTemperature, + acentricFactor, + kValues ); + + ASSERT_EQ( kValues.size(), NC ); + checkRelativeError( expectedKValue, kValues[compIndex], relTol ); + } + +private: + void assign( array1d< real64 > & values, Feed const data ) + { + for( Feed::const_iterator it = data.begin(); it != data.end(); ++it ) + { + values.emplace_back( *it ); + } + } + +protected: + const integer numComps; + array1d< real64 > criticalPressure; + array1d< real64 > criticalTemperature; + array1d< real64 > acentricFactor; +}; + +class WilsonKValues2CompFixture : public WilsonKValueInitializationTestFixture< 2 > +{ +public: + WilsonKValues2CompFixture(): + WilsonKValueInitializationTestFixture< 2 >( {12.96e5, 45.99e5}, {33.15, 190.6}, {-0.219, 0.0114} ) + {} +}; + +class WilsonKValues9CompFixture : public WilsonKValueInitializationTestFixture< 9 > +{ +public: + WilsonKValues9CompFixture(): + WilsonKValueInitializationTestFixture< 9 >( + {46.32700e5, 73.70000e5, 48.83900e5, 40.48100e5, 32.29200e5, 24.84900e5, 17.01500e5, 10.45300e5, 13.78000e5}, + {190.1580, 304.2020, 305.4000, 388.7960, 531.0700, 668.5570, 815.4070, 968.7040, 1105.1500}, + {0.0117, 0.2389, 0.0986, 0.1655, 0.2642, 0.3974, 0.6231, 1.0804, 1.15} ) + {} +}; + +TEST_P( WilsonKValues2CompFixture, testKValues ) +{ + testKValues(); +} + +TEST_P( WilsonKValues9CompFixture, testKValues ) +{ + testKValues(); +} + +// 2-component fluid test +INSTANTIATE_TEST_SUITE_P( + WilsonKValues2CompTest, + WilsonKValues2CompFixture, + ::testing::Values( + std::make_tuple( 1.01325e+5, 2.88650e+2, 0, 5.2375203367823417e+2 ), + std::make_tuple( 1.01325e+5, 2.88650e+2, 1, 2.8719541295945191e+2 ), + std::make_tuple( 1.00000e+6, 2.88650e+2, 0, 5.3069174812447081e+1 ), + std::make_tuple( 1.00000e+6, 2.88650e+2, 1, 2.9100075218116466e+1 ), + std::make_tuple( 5.00000e+6, 2.88650e+2, 0, 1.0613834962489415e+1 ), + std::make_tuple( 5.00000e+6, 2.88650e+2, 1, 5.8200150436232931e+0 ), + std::make_tuple( 1.00000e+8, 2.88650e+2, 0, 5.3069174812447081e-1 ), + std::make_tuple( 1.00000e+8, 2.88650e+2, 1, 2.9100075218116466e-1 ), + std::make_tuple( 1.01325e+5, 3.50000e+2, 0, 5.6989140865114678e+2 ), + std::make_tuple( 1.01325e+5, 3.50000e+2, 1, 5.3850288275236221e+2 ), + std::make_tuple( 1.00000e+6, 3.50000e+2, 0, 5.7744246981577454e+1 ), + std::make_tuple( 1.00000e+6, 3.50000e+2, 1, 5.4563804594883109e+1 ), + std::make_tuple( 5.00000e+6, 3.50000e+2, 0, 1.1548849396315489e+1 ), + std::make_tuple( 5.00000e+6, 3.50000e+2, 1, 1.0912760918976621e+1 ), + std::make_tuple( 1.00000e+8, 3.50000e+2, 0, 5.7744246981577454e-1 ), + std::make_tuple( 1.00000e+8, 3.50000e+2, 1, 5.4563804594883109e-1 ) + )); + +// 9-component fluid test +INSTANTIATE_TEST_SUITE_P( + WilsonKValues9CompTest, + WilsonKValues9CompFixture, + ::testing::Values( + std::make_tuple( 1.01325e+05, 2.88650e+02, 0, 2.91876325e+02 ), + std::make_tuple( 1.01325e+05, 2.88650e+02, 1, 5.08252148e+01 ), + std::make_tuple( 1.01325e+05, 2.88650e+02, 8, 8.91433739e-14 ), + std::make_tuple( 1.00000e+06, 2.88650e+02, 0, 2.95743686e+01 ), + std::make_tuple( 1.00000e+06, 2.88650e+02, 1, 5.14986489e+00 ), + std::make_tuple( 1.00000e+06, 2.88650e+02, 8, 9.03245236e-15 ), + std::make_tuple( 5.00000e+06, 2.88650e+02, 0, 5.91487373e+00 ), + std::make_tuple( 5.00000e+06, 2.88650e+02, 1, 1.02997298e+00 ), + std::make_tuple( 5.00000e+06, 2.88650e+02, 8, 1.80649047e-15 ), + std::make_tuple( 1.00000e+08, 2.88650e+02, 0, 2.95743686e-01 ), + std::make_tuple( 1.00000e+08, 2.88650e+02, 1, 5.14986489e-02 ), + std::make_tuple( 1.00000e+08, 2.88650e+02, 8, 9.03245236e-17 ), + std::make_tuple( 1.01325e+05, 3.50000e+02, 0, 5.46584215e+02 ), + std::make_tuple( 1.01325e+05, 3.50000e+02, 1, 1.73708806e+02 ), + std::make_tuple( 1.01325e+05, 3.50000e+02, 8, 2.06610518e-10 ), + std::make_tuple( 1.00000e+06, 3.50000e+02, 0, 5.53826456e+01 ), + std::make_tuple( 1.00000e+06, 3.50000e+02, 1, 1.76010447e+01 ), + std::make_tuple( 1.00000e+06, 3.50000e+02, 8, 2.09348108e-11 ), + std::make_tuple( 5.00000e+06, 3.50000e+02, 0, 1.10765291e+01 ), + std::make_tuple( 5.00000e+06, 3.50000e+02, 1, 3.52020894e+00 ), + std::make_tuple( 5.00000e+06, 3.50000e+02, 8, 4.18696215e-12 ), + std::make_tuple( 1.00000e+08, 3.50000e+02, 0, 5.53826456e-01 ), + std::make_tuple( 1.00000e+08, 3.50000e+02, 1, 1.76010447e-01 ), + std::make_tuple( 1.00000e+08, 3.50000e+02, 8, 2.09348108e-13 ) + )); + + +// Parameters are +// 0 - pressure +// 1 - temperature +// 2 - expected k-value +class GasWaterKValueInitializationTestFixture : + public ::testing::TestWithParam< std::tuple< real64 const, real64 const, real64 const > > +{ +public: + GasWaterKValueInitializationTestFixture() = default; + virtual ~GasWaterKValueInitializationTestFixture() = default; + + void testKValues() + { + real64 const pressure = std::get< 0 >( GetParam()); + real64 const temperature = std::get< 1 >( GetParam()); + real64 const expectedKValue = std::get< 2 >( GetParam()); + + real64 calculatedKValue = constitutive::KValueInitialization::computeWaterGasKvalue( pressure, temperature ); + checkRelativeError( expectedKValue, calculatedKValue, relTol ); + } +}; + +// gas-water test +TEST_P( GasWaterKValueInitializationTestFixture, testKValues ) +{ + testKValues(); +} + +// 9-component fluid test +INSTANTIATE_TEST_SUITE_P( + GasWaterKValueInitializationTest, + GasWaterKValueInitializationTestFixture, + ::testing::Values( + std::make_tuple( 9.50000e+04, 2.88650e+02, 2.23565965e-02 ), + std::make_tuple( 1.01325e+05, 2.88650e+02, 2.09610330e-02 ), + std::make_tuple( 1.00000e+06, 2.88650e+02, 2.12387667e-03 ), + std::make_tuple( 5.00000e+06, 2.88650e+02, 4.24775333e-04 ), + std::make_tuple( 1.00000e+08, 2.88650e+02, 2.12387667e-05 ), + std::make_tuple( 9.50000e+04, 3.50000e+02, 4.23601370e-01 ), + std::make_tuple( 1.01325e+05, 3.50000e+02, 3.97158945e-01 ), + std::make_tuple( 1.00000e+06, 3.50000e+02, 4.02421301e-02 ), + std::make_tuple( 5.00000e+06, 3.50000e+02, 8.04842603e-03 ), + std::make_tuple( 1.00000e+08, 3.50000e+02, 4.02421301e-04 ), + std::make_tuple( 9.50000e+04, 3.73150e+02, 9.99692622e-01 ), + std::make_tuple( 1.01325e+05, 3.73150e+02, 9.37288912e-01 ), + std::make_tuple( 1.00000e+06, 3.73150e+02, 9.49707991e-02 ), + std::make_tuple( 5.00000e+06, 3.73150e+02, 1.89941598e-02 ), + std::make_tuple( 1.00000e+08, 3.73150e+02, 9.49707991e-04 ) + )); + +} // testing + +} // geos diff --git a/src/coreComponents/constitutive/unitTests/testNegativeTwoPhaseFlash.cpp b/src/coreComponents/constitutive/unitTests/testNegativeTwoPhaseFlash.cpp new file mode 100644 index 00000000000..d9b52111ee0 --- /dev/null +++ b/src/coreComponents/constitutive/unitTests/testNegativeTwoPhaseFlash.cpp @@ -0,0 +1,387 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2018-2020 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2020 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2018-2020 TotalEnergies + * Copyright (c) 2019- GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +// Source includes +#include "codingUtilities/UnitTestUtilities.hpp" +#include "constitutive/fluid/multifluid/compositional/functions/NegativeTwoPhaseFlash.hpp" +#include "constitutive/fluid/multifluid/compositional/functions/CubicEOSPhaseModel.hpp" +#include "TestFluid.hpp" + +namespace geos +{ +namespace testing +{ + +template< int NC > +struct FluidData +{}; + +template<> +struct FluidData< 2 > +{ + static std::unique_ptr< TestFluid< 2 > > createFluid() + { + return TestFluid< 2 >::create( {Fluid::CO2, Fluid::C5} ); + } +}; + +template<> +struct FluidData< 4 > +{ + static std::unique_ptr< TestFluid< 4 > > createFluid() + { + return TestFluid< 4 >::create( {Fluid::N2, Fluid::C8, Fluid::C10, Fluid::H2O} ); + } +}; + +template< int NC > +using FlashData = std::tuple< + real64 const, // pressure + real64 const, // temperature + Feed< NC > const, // total composition + bool, // expected flash status (success/failure) + real64 const, // expected vapour fraction + Feed< NC > const, // expected liquid composition + Feed< NC > const // expected vapour composition + >; + +template< int NC, typename EOS_TYPE > +class NegativeTwoPhaseFlashTestFixture : public ::testing::TestWithParam< FlashData< NC > > +{ + static constexpr real64 relTol = 1.0e-5; + static constexpr real64 absTol = 1.0e-7; + static constexpr int numComps = NC; +public: + NegativeTwoPhaseFlashTestFixture() + : m_fluid( FluidData< NC >::createFluid() ) + {} + + ~NegativeTwoPhaseFlashTestFixture() = default; + + void testFlash( FlashData< NC > const & data ) + { + real64 const pressure = std::get< 0 >( data ); + real64 const temperature = std::get< 1 >( data ); + array1d< real64 > composition; + TestFluid< NC >::createArray( composition, std::get< 2 >( data )); + + bool const expectedStatus = std::get< 3 >( data ); + real64 const expectedVapourFraction = std::get< 4 >( data ); + + stackArray1d< real64, NC > expectedLiquidComposition; + TestFluid< NC >::createArray( expectedLiquidComposition, std::get< 5 >( data )); + stackArray1d< real64, NC > expectedVapourComposition; + TestFluid< NC >::createArray( expectedVapourComposition, std::get< 6 >( data )); + + real64 vapourFraction = -1.0; + array1d< real64 > liquidComposition( numComps ); + array1d< real64 > vapourComposition( numComps ); + + bool status = constitutive::NegativeTwoPhaseFlash::compute< EOS_TYPE, EOS_TYPE >( + numComps, + pressure, + temperature, + composition, + m_fluid->getCriticalPressure(), + m_fluid->getCriticalTemperature(), + m_fluid->getAcentricFactor(), + binaryInteractionCoefficients, + vapourFraction, + liquidComposition, + vapourComposition ); + + // Check the flash success result + ASSERT_EQ( expectedStatus, status ); + + if( !expectedStatus ) + { + return; + } + + // Check the vaopur fraction + checkRelativeError( expectedVapourFraction, vapourFraction, relTol, absTol ); + + // Check liquid composition + if( expectedVapourFraction < 1.0 - absTol ) + { + for( integer ic=0; ic > m_fluid{}; +}; + +using NegativeTwoPhaseFlash2CompPR = NegativeTwoPhaseFlashTestFixture< 2, constitutive::PengRobinsonEOS >; +using NegativeTwoPhaseFlash2CompSRK = NegativeTwoPhaseFlashTestFixture< 2, constitutive::SoaveRedlichKwongEOS >; +using NegativeTwoPhaseFlash4CompPR = NegativeTwoPhaseFlashTestFixture< 4, constitutive::PengRobinsonEOS >; +using NegativeTwoPhaseFlash4CompSRK = NegativeTwoPhaseFlashTestFixture< 4, constitutive::SoaveRedlichKwongEOS >; + +TEST_P( NegativeTwoPhaseFlash2CompPR, testNegativeFlash ) +{ + testFlash( GetParam() ); +} + +TEST_P( NegativeTwoPhaseFlash2CompSRK, testNegativeFlash ) +{ + testFlash( GetParam() ); +} + +TEST_P( NegativeTwoPhaseFlash4CompPR, testNegativeFlash ) +{ + testFlash( GetParam() ); +} + +TEST_P( NegativeTwoPhaseFlash4CompSRK, testNegativeFlash ) +{ + testFlash( GetParam() ); +} + +//------------------------------------------------------------------------------- +// Data generated by PVTPackage +//------------------------------------------------------------------------------- +INSTANTIATE_TEST_SUITE_P( + NegativeTwoPhaseFlash, + NegativeTwoPhaseFlash2CompPR, + ::testing::Values( + FlashData< 2 >( 1.000000e+05, 1.231500e+02, { 0.10000000, 0.90000000 }, true, 0.89038113, { 0.89566514, 0.10433486 }, { 0.00204205, 0.99795795 } ), + FlashData< 2 >( 1.000000e+05, 1.231500e+02, { 0.75000000, 0.25000000 }, true, 0.16300512, { 0.89566514, 0.10433486 }, { 0.00204205, 0.99795795 } ), + FlashData< 2 >( 1.000000e+05, 1.231500e+02, { 0.50000000, 0.50000000 }, true, 0.44276513, { 0.89566514, 0.10433486 }, { 0.00204205, 0.99795795 } ), + FlashData< 2 >( 1.000000e+05, 1.231500e+02, { 0.90000000, 0.10000000 }, true, 0.00000000, { 0.90000000, 0.10000000 }, { 0.00204205, 0.99795795 } ), + FlashData< 2 >( 1.013250e+05, 1.231500e+02, { 0.10000000, 0.90000000 }, true, 0.89010742, { 0.89366817, 0.10633183 }, { 0.00201380, 0.99798620 } ), + FlashData< 2 >( 1.013250e+05, 1.231500e+02, { 0.75000000, 0.25000000 }, true, 0.16112541, { 0.89366817, 0.10633183 }, { 0.00201380, 0.99798620 } ), + FlashData< 2 >( 1.013250e+05, 1.231500e+02, { 0.50000000, 0.50000000 }, true, 0.44150310, { 0.89366817, 0.10633183 }, { 0.00201380, 0.99798620 } ), + FlashData< 2 >( 1.013250e+05, 1.231500e+02, { 0.90000000, 0.10000000 }, true, 0.00000000, { 0.90000000, 0.10000000 }, { 0.00201380, 0.99798620 } ), + FlashData< 2 >( 1.000000e+05, 1.931500e+02, { 0.10000000, 0.90000000 }, true, 1.00000000, { 0.10000000, 0.90000000 }, { 0.10000000, 0.90000000 } ), + FlashData< 2 >( 1.000000e+05, 1.931500e+02, { 0.75000000, 0.25000000 }, true, 1.00000000, { 0.75000000, 0.25000000 }, { 0.75000000, 0.25000000 } ), + FlashData< 2 >( 1.000000e+06, 1.931500e+02, { 0.10000000, 0.90000000 }, true, 1.00000000, { 0.88618228, 0.11381772 }, { 0.10000000, 0.90000000 } ), + FlashData< 2 >( 1.000000e+06, 1.931500e+02, { 0.75000000, 0.25000000 }, true, 0.18928601, { 0.88618228, 0.11381772 }, { 0.16672986, 0.83327014 } ), + FlashData< 2 >( 1.000000e+06, 1.931500e+02, { 0.50000000, 0.50000000 }, true, 0.53677251, { 0.88618228, 0.11381772 }, { 0.16672986, 0.83327014 } ), + FlashData< 2 >( 1.000000e+06, 1.931500e+02, { 0.90000000, 0.10000000 }, true, 0.00000000, { 0.90000000, 0.10000000 }, { 0.16672986, 0.83327014 } ), + FlashData< 2 >( 1.000000e+08, 1.931500e+02, { 0.90000000, 0.10000000 }, true, 0.00000000, { 0.90000000, 0.10000000 }, { 0.90000000, 0.10000000 } ), + FlashData< 2 >( 1.000000e+05, 2.771500e+02, { 0.10000000, 0.90000000 }, true, 1.00000000, { 0.10000000, 0.90000000 }, { 0.10000000, 0.90000000 } ), + FlashData< 2 >( 5.000000e+06, 2.771500e+02, { 0.50000000, 0.50000000 }, true, 1.00000000, { 0.93119724, 0.06880276 }, { 0.50000000, 0.50000000 } ), + FlashData< 2 >( 5.000000e+06, 2.771500e+02, { 0.90000000, 0.10000000 }, true, 0.27263042, { 0.93119724, 0.06880276 }, { 0.81676673, 0.18323327 } ), + FlashData< 2 >( 1.000000e+07, 2.771500e+02, { 0.10000000, 0.90000000 }, true, 1.00000000, { 0.55705424, 0.44294576 }, { 0.10000000, 0.90000000 } ), + FlashData< 2 >( 1.000000e+07, 2.771500e+02, { 0.75000000, 0.25000000 }, true, 0.00000000, { 0.75000000, 0.25000000 }, { 0.55705421, 0.44294579 } ), + FlashData< 2 >( 1.000000e+08, 3.331500e+02, { 0.90000000, 0.10000000 }, true, 0.00000000, { 0.90000000, 0.10000000 }, { 0.90000000, 0.10000000 } ), + FlashData< 2 >( 1.000000e+05, 3.731500e+02, { 0.10000000, 0.90000000 }, true, 1.00000000, { 0.10000000, 0.90000000 }, { 0.10000000, 0.90000000 } ), + FlashData< 2 >( 5.000000e+06, 3.731500e+02, { 0.90000000, 0.10000000 }, true, 1.00000000, { 0.90000000, 0.10000000 }, { 0.90000000, 0.10000000 } ), + FlashData< 2 >( 1.000000e+07, 3.731500e+02, { 0.90000000, 0.10000000 }, true, 1.00000000, { 0.90000000, 0.10000000 }, { 0.90000000, 0.10000000 } ), + FlashData< 2 >( 1.000000e+08, 3.731500e+02, { 0.10000000, 0.90000000 }, true, 0.00000000, { 0.10000000, 0.90000000 }, { 0.10000000, 0.90000000 } ), + FlashData< 2 >( 1.000000e+08, 3.731500e+02, { 0.90000000, 0.10000000 }, true, 0.00000000, { 0.90000000, 0.10000000 }, { 0.90000000, 0.10000000 } ) + ) + ); + +INSTANTIATE_TEST_SUITE_P( + NegativeTwoPhaseFlash, + NegativeTwoPhaseFlash2CompSRK, + ::testing::Values( + FlashData< 2 >( 1.000000e+05, 1.231500e+02, { 0.10000000, 0.90000000 }, true, 0.89111708, { 0.90429170, 0.09570830 }, { 0.00172601, 0.99827399 } ), + FlashData< 2 >( 1.000000e+05, 1.231500e+02, { 0.75000000, 0.25000000 }, true, 0.17094789, { 0.90429170, 0.09570830 }, { 0.00172601, 0.99827399 } ), + FlashData< 2 >( 1.000000e+05, 1.231500e+02, { 0.50000000, 0.50000000 }, true, 0.44793604, { 0.90429170, 0.09570830 }, { 0.00172601, 0.99827399 } ), + FlashData< 2 >( 1.000000e+05, 1.231500e+02, { 0.90000000, 0.10000000 }, true, 0.00475500, { 0.90429170, 0.09570830 }, { 0.00172601, 0.99827399 } ), + FlashData< 2 >( 1.013250e+05, 1.231500e+02, { 0.10000000, 0.90000000 }, true, 0.89087481, { 0.90248098, 0.09751902 }, { 0.00170237, 0.99829763 } ), + FlashData< 2 >( 1.013250e+05, 1.231500e+02, { 0.75000000, 0.25000000 }, true, 0.16927687, { 0.90248098, 0.09751902 }, { 0.00170237, 0.99829763 } ), + FlashData< 2 >( 1.013250e+05, 1.231500e+02, { 0.50000000, 0.50000000 }, true, 0.44681454, { 0.90248098, 0.09751902 }, { 0.00170237, 0.99829763 } ), + FlashData< 2 >( 1.013250e+05, 1.231500e+02, { 0.90000000, 0.10000000 }, true, 0.00275426, { 0.90248098, 0.09751902 }, { 0.00170237, 0.99829763 } ), + FlashData< 2 >( 1.000000e+06, 1.231500e+02, { 0.10000000, 0.90000000 }, true, 0.00000000, { 0.10000000, 0.90000000 }, { 0.10000000, 0.90000000 } ), + FlashData< 2 >( 1.000000e+06, 1.231500e+02, { 0.50000000, 0.50000000 }, false, 0.00000000, { 0.50000000, 0.50000000 }, { 0.49950202, 0.50049798 } ), + FlashData< 2 >( 1.000000e+06, 1.231500e+02, { 0.90000000, 0.10000000 }, true, 0.00000000, { 0.90000000, 0.10000000 }, { 0.90000000, 0.10000000 } ), + FlashData< 2 >( 5.000000e+06, 1.231500e+02, { 0.75000000, 0.25000000 }, true, 0.00000000, { 0.75000000, 0.25000000 }, { 0.74999999, 0.25000001 } ), + FlashData< 2 >( 5.000000e+06, 1.231500e+02, { 0.50000000, 0.50000000 }, false, 0.00000000, { 0.50000000, 0.50000000 }, { 0.49979984, 0.50020016 } ), + FlashData< 2 >( 5.000000e+06, 1.231500e+02, { 0.90000000, 0.10000000 }, true, 0.00000000, { 0.90000000, 0.10000000 }, { 0.90000000, 0.10000000 } ), + FlashData< 2 >( 1.000000e+08, 1.231500e+02, { 0.90000000, 0.10000000 }, true, 0.00000000, { 0.90000000, 0.10000000 }, { 0.90000000, 0.10000000 } ), + FlashData< 2 >( 1.000000e+05, 1.931500e+02, { 0.10000000, 0.90000000 }, true, 1.00000000, { 0.10000000, 0.90000000 }, { 0.10000000, 0.90000000 } ), + FlashData< 2 >( 1.000000e+05, 1.931500e+02, { 0.75000000, 0.25000000 }, true, 1.00000000, { 0.75000000, 0.25000000 }, { 0.75000000, 0.25000000 } ), + FlashData< 2 >( 1.000000e+06, 1.931500e+02, { 0.10000000, 0.90000000 }, true, 1.00000000, { 0.89288212, 0.10711788 }, { 0.10000000, 0.90000000 } ), + FlashData< 2 >( 1.000000e+06, 1.931500e+02, { 0.75000000, 0.25000000 }, true, 0.19641336, { 0.89288212, 0.10711788 }, { 0.16542588, 0.83457412 } ), + FlashData< 2 >( 1.000000e+06, 1.931500e+02, { 0.50000000, 0.50000000 }, true, 0.54007664, { 0.89288212, 0.10711788 }, { 0.16542588, 0.83457412 } ), + FlashData< 2 >( 1.000000e+06, 1.931500e+02, { 0.90000000, 0.10000000 }, true, 0.00000000, { 0.90000000, 0.10000000 }, { 0.16542588, 0.83457412 } ), + FlashData< 2 >( 1.000000e+08, 1.931500e+02, { 0.50000000, 0.50000000 }, true, 0.00000000, { 0.50000000, 0.50000000 }, { 0.49999999, 0.50000001 } ), + FlashData< 2 >( 1.000000e+08, 1.931500e+02, { 0.90000000, 0.10000000 }, true, 0.00000000, { 0.90000000, 0.10000000 }, { 0.90000000, 0.10000000 } ), + FlashData< 2 >( 1.000000e+05, 2.771500e+02, { 0.10000000, 0.90000000 }, true, 1.00000000, { 0.10000000, 0.90000000 }, { 0.10000000, 0.90000000 } ), + FlashData< 2 >( 1.000000e+05, 2.771500e+02, { 0.90000000, 0.10000000 }, true, 1.00000000, { 0.90000000, 0.10000000 }, { 0.90000000, 0.10000000 } ), + FlashData< 2 >( 5.000000e+06, 2.771500e+02, { 0.50000000, 0.50000000 }, true, 1.00000000, { 0.93366481, 0.06633519 }, { 0.50000000, 0.50000000 } ), + FlashData< 2 >( 5.000000e+06, 2.771500e+02, { 0.90000000, 0.10000000 }, true, 0.30050187, { 0.93366481, 0.06633519 }, { 0.82163618, 0.17836382 } ), + FlashData< 2 >( 1.000000e+07, 2.771500e+02, { 0.10000000, 0.90000000 }, true, 1.00000000, { 0.55571828, 0.44428172 }, { 0.10000000, 0.90000000 } ), + FlashData< 2 >( 1.000000e+07, 2.771500e+02, { 0.50000000, 0.50000000 }, true, 1.00000000, { 0.55571830, 0.44428170 }, { 0.50000000, 0.50000000 } ), + FlashData< 2 >( 1.000000e+07, 2.771500e+02, { 0.90000000, 0.10000000 }, true, 0.00000000, { 0.90000000, 0.10000000 }, { 0.55571831, 0.44428169 } ), + FlashData< 2 >( 1.000000e+07, 2.886500e+02, { 0.10000000, 0.90000000 }, true, 1.00000000, { 0.66024784, 0.33975216 }, { 0.10000000, 0.90000000 } ), + FlashData< 2 >( 1.000000e+07, 2.886500e+02, { 0.75000000, 0.25000000 }, true, 0.00000000, { 0.75000000, 0.25000000 }, { 0.66024775, 0.33975225 } ), + FlashData< 2 >( 1.000000e+07, 2.886500e+02, { 0.50000000, 0.50000000 }, true, 1.00000000, { 0.66024781, 0.33975219 }, { 0.50000000, 0.50000000 } ), + FlashData< 2 >( 1.000000e+07, 2.886500e+02, { 0.90000000, 0.10000000 }, true, 0.00000000, { 0.90000000, 0.10000000 }, { 0.66024783, 0.33975217 } ), + FlashData< 2 >( 1.000000e+07, 3.731500e+02, { 0.90000000, 0.10000000 }, true, 1.00000000, { 0.90000000, 0.10000000 }, { 0.90000000, 0.10000000 } ), + FlashData< 2 >( 1.000000e+08, 3.731500e+02, { 0.10000000, 0.90000000 }, true, 0.00000000, { 0.10000000, 0.90000000 }, { 0.10000000, 0.90000000 } ) + ) + ); + +INSTANTIATE_TEST_SUITE_P( + NegativeTwoPhaseFlash, + NegativeTwoPhaseFlash4CompPR, + ::testing::Values( + FlashData< 4 >( 1.000000e+05, 1.931500e+02, { 0.05695100, 0.10481800, 0.10482200, 0.73340900 }, true, 0.05629583, { 0.00070397, 0.11107082, 0.11107506, 0.77715015 }, + { 0.99983707, 0.00000005, 0.00000000, 0.00016289 } ), + FlashData< 4 >( 1.000000e+05, 1.931500e+02, { 0.15695100, 0.10481800, 0.10482200, 0.63340900 }, true, 0.15624142, { 0.00088099, 0.12422747, 0.12423222, 0.75065932 }, + { 0.99978392, 0.00000003, 0.00000000, 0.00021605 } ), + FlashData< 4 >( 5.000000e+06, 1.931500e+02, { 0.05695100, 0.10481800, 0.10482200, 0.73340900 }, true, 0.02878730, { 0.02899884, 0.10792486, 0.10792898, 0.75514731 }, + { 0.99998806, 0.00000004, 0.00000000, 0.00001190 } ), + FlashData< 4 >( 5.000000e+06, 1.931500e+02, { 0.15695100, 0.10481800, 0.10482200, 0.63340900 }, true, 0.12514257, { 0.03636069, 0.11981152, 0.11981609, 0.72401170 }, + { 0.99998417, 0.00000003, 0.00000000, 0.00001580 } ), + FlashData< 4 >( 5.000000e+06, 1.931500e+02, { 0.00000000, 0.10481800, 0.10482200, 0.79036000 }, true, 0.79032837, { 0.00000000, 0.49991503, 0.49993411, 0.00015087 }, + { 0.00000000, 0.00000000, 0.00000000, 1.00000000 } ), + FlashData< 4 >( 5.000000e+06, 1.931500e+02, { 0.10481800, 0.00000000, 0.10482200, 0.79036000 }, true, 0.09334204, { 0.01265794, 0.00000000, 0.11561361, 0.87172845 }, + { 0.99999506, 0.00000000, 0.00000000, 0.00000494 } ), + FlashData< 4 >( 1.000000e+07, 1.931500e+02, { 0.15695100, 0.10481800, 0.10482200, 0.63340900 }, true, 0.10262297, { 0.06054432, 0.11680480, 0.11680932, 0.70584155 }, + { 0.99997026, 0.00000052, 0.00000000, 0.00002922 } ), + FlashData< 4 >( 1.000000e+08, 1.931500e+02, { 0.05695100, 0.10481800, 0.10482200, 0.73340900 }, true, 0.26661844, { 0.00000001, 0.00000000, 0.00000000, 0.99999999 }, + { 0.21360485, 0.39313860, 0.39315361, 0.00010294 } ), + FlashData< 4 >( 1.000000e+06, 2.771500e+02, { 0.10481800, 0.00000000, 0.10482200, 0.79036000 }, true, 0.10252763, { 0.00359465, 0.00000000, 0.11679690, 0.87960845 }, + { 0.99087343, 0.00000000, 0.00000008, 0.00912649 } ), + FlashData< 4 >( 1.000000e+06, 2.771500e+02, { 0.99000000, 0.00000000, 0.00000000, 0.01000000 }, true, 0.99071680, { 0.00000042, 0.00000000, 0.00000000, 0.99999958 }, + { 0.99927648, 0.00000000, 0.00000000, 0.00072352 } ), + FlashData< 4 >( 5.000000e+06, 2.771500e+02, { 0.05695100, 0.10481800, 0.10482200, 0.73340900 }, true, 0.02869272, { 0.02921217, 0.10791374, 0.10791847, 0.75495561 }, + { 0.99596684, 0.00002082, 0.00000001, 0.00401233 } ), + FlashData< 4 >( 5.000000e+06, 2.771500e+02, { 0.15695100, 0.10481800, 0.10482200, 0.63340900 }, true, 0.12793659, { 0.03394197, 0.12019282, 0.12019997, 0.72566524 }, + { 0.99542635, 0.00001751, 0.00000000, 0.00455613 } ), + FlashData< 4 >( 5.000000e+06, 2.771500e+02, { 0.00000000, 0.10481800, 0.10482200, 0.79036000 }, true, 0.78833888, { 0.00000000, 0.49521612, 0.49523502, 0.00954887 }, + { 0.00000000, 0.00000000, 0.00000000, 1.00000000 } ), + FlashData< 4 >( 5.000000e+06, 2.771500e+02, { 0.01000000, 0.00000000, 0.00000000, 0.99000000 }, true, 0.01000018, { 0.00000196, 0.00000000, 0.00000000, 0.99999804 }, + { 0.99978790, 0.00000000, 0.00000000, 0.00021210 } ), + FlashData< 4 >( 1.000000e+07, 2.771500e+02, { 0.05695100, 0.10481800, 0.10482200, 0.73340900 }, true, 0.00244308, { 0.05464929, 0.10507462, 0.10507872, 0.73519737 }, + { 0.99678327, 0.00003656, 0.00000004, 0.00318014 } ), + FlashData< 4 >( 1.000000e+07, 2.771500e+02, { 0.15695100, 0.10481800, 0.10482200, 0.63340900 }, true, 0.10021652, { 0.06345880, 0.11648904, 0.11649691, 0.70355525 }, + { 0.99636092, 0.00003083, 0.00000002, 0.00360823 } ), + FlashData< 4 >( 1.000000e+07, 2.771500e+02, { 0.00000000, 0.10481800, 0.10482200, 0.79036000 }, true, 0.78838676, { 0.00000000, 0.49532818, 0.49534708, 0.00932474 }, + { 0.00000000, 0.00000000, 0.00000000, 1.00000000 } ), + FlashData< 4 >( 1.000000e+05, 2.886500e+02, { 0.99000000, 0.00000000, 0.00000000, 0.01000000 }, true, 1.00000000, { 0.00000007, 0.00000000, 0.00000000, 0.99999993 }, + { 0.99000000, 0.00000000, 0.00000000, 0.01000000 } ), + FlashData< 4 >( 1.000000e+05, 2.886500e+02, { 0.01000000, 0.00000000, 0.00000000, 0.99000000 }, true, 0.01014979, { 0.00000007, 0.00000000, 0.00000000, 0.99999993 }, + { 0.98523527, 0.00000000, 0.00000000, 0.01476473 } ), + FlashData< 4 >( 1.000000e+07, 2.886500e+02, { 0.99000000, 0.00000000, 0.00000000, 0.01000000 }, true, 0.99033139, { 0.00000617, 0.00000000, 0.00000000, 0.99999383 }, + { 0.99966531, 0.00000000, 0.00000000, 0.00033469 } ), + FlashData< 4 >( 1.000000e+07, 2.886500e+02, { 0.01000000, 0.00000000, 0.00000000, 0.99000000 }, true, 0.00999724, { 0.00000617, 0.00000000, 0.00000000, 0.99999383 }, + { 0.99966531, 0.00000000, 0.00000000, 0.00033469 } ), + FlashData< 4 >( 1.000000e+08, 2.886500e+02, { 0.05695100, 0.10481800, 0.10482200, 0.73340900 }, true, 0.26901755, { 0.00001146, 0.00000000, 0.00000000, 0.99998854 }, + { 0.21166879, 0.38963257, 0.38964744, 0.00905121 } ), + FlashData< 4 >( 1.000000e+05, 3.331500e+02, { 0.10481800, 0.00000000, 0.10482200, 0.79036000 }, true, 0.86540955, { 0.00020370, 0.00000000, 0.77880508, 0.22099122 }, + { 0.12108785, 0.00000000, 0.00000263, 0.87890952 } ), + FlashData< 4 >( 1.000000e+05, 3.331500e+02, { 0.99000000, 0.00000000, 0.00000000, 0.01000000 }, true, 1.00000000, { 0.00000033, 0.00000000, 0.00000000, 0.99999967 }, + { 0.99000000, 0.00000000, 0.00000000, 0.01000000 } ), + FlashData< 4 >( 1.000000e+05, 3.331500e+02, { 0.01000000, 0.00000000, 0.00000000, 0.99000000 }, true, 0.01222390, { 0.00000033, 0.00000000, 0.00000000, 0.99999967 }, + { 0.81804281, 0.00000000, 0.00000000, 0.18195719 } ), + FlashData< 4 >( 1.000000e+07, 3.731500e+02, { 0.99000000, 0.00000000, 0.00000000, 0.01000000 }, true, 1.00000000, { 0.00012025, 0.00000000, 0.00000000, 0.99987975 }, + { 0.99000000, 0.00000000, 0.00000000, 0.01000000 } ), + FlashData< 4 >( 1.000000e+08, 3.731500e+02, { 0.15695100, 0.10481800, 0.10482200, 0.63340900 }, true, 0.00000000, { 0.15695100, 0.10481800, 0.10482200, 0.63340900 }, + { 0.93121567, 0.00696526, 0.00174080, 0.06007828 } ), + FlashData< 4 >( 1.000000e+08, 3.731500e+02, { 0.00000000, 0.10481800, 0.10482200, 0.79036000 }, true, 0.77592410, { 0.00000000, 0.46777900, 0.46779685, 0.06442416 }, + { 0.00000000, 0.00000000, 0.00000000, 1.00000000 } ), + FlashData< 4 >( 1.013250e+05, 4.731500e+02, { 0.10481800, 0.00000000, 0.10482200, 0.79036000 }, true, 0.91046790, { 0.00021364, 0.00000000, 0.97656019, 0.02322617 }, + { 0.11510441, 0.00000000, 0.01909844, 0.86579715 } ), + FlashData< 4 >( 1.013250e+05, 4.731500e+02, { 0.01000000, 0.00000000, 0.00000000, 0.99000000 }, true, 1.00000000, { 0.01000000, 0.00000000, 0.00000000, 0.99000000 }, + { 0.01000000, 0.00000000, 0.00000000, 0.99000000 } ), + FlashData< 4 >( 5.000000e+06, 4.731500e+02, { 0.01000000, 0.00000000, 0.00000000, 0.99000000 }, true, 0.01474382, { 0.00043465, 0.00000000, 0.00000000, 0.99956535 }, + { 0.64920515, 0.00000000, 0.00000000, 0.35079485 } ), + FlashData< 4 >( 1.000000e+07, 4.731500e+02, { 0.05695100, 0.10481800, 0.10482200, 0.73340900 }, true, 0.00000000, { 0.05695100, 0.10481800, 0.10482200, 0.73340900 }, + { 0.60848558, 0.01033586, 0.00049071, 0.38068785 } ), + FlashData< 4 >( 1.000000e+07, 4.731500e+02, { 0.15695100, 0.10481800, 0.10482200, 0.63340900 }, true, 0.13867033, { 0.08254496, 0.11986286, 0.12161793, 0.67597425 }, + { 0.61911282, 0.01136912, 0.00049663, 0.36902144 } ), + FlashData< 4 >( 1.000000e+07, 4.731500e+02, { 0.00000000, 0.10481800, 0.10482200, 0.79036000 }, true, 0.66978458, { 0.00000000, 0.31742202, 0.31743521, 0.36514277 }, + { 0.00000000, 0.00000053, 0.00000000, 0.99999947 } ), + FlashData< 4 >( 1.000000e+07, 4.731500e+02, { 0.10481800, 0.00000000, 0.10482200, 0.79036000 }, true, 0.08507006, { 0.05879909, 0.00000000, 0.11449503, 0.82670588 }, + { 0.59975210, 0.00000000, 0.00078842, 0.39945949 } ), + FlashData< 4 >( 1.000000e+07, 4.731500e+02, { 0.01000000, 0.00000000, 0.00000000, 0.99000000 }, true, 0.01121076, { 0.00102378, 0.00000000, 0.00000000, 0.99897622 }, + { 0.80170284, 0.00000000, 0.00000000, 0.19829716 } ), + FlashData< 4 >( 1.000000e+08, 4.731500e+02, { 0.05695100, 0.10481800, 0.10482200, 0.73340900 }, false, 0.00000000, { 0.05695100, 0.10481800, 0.10482200, 0.73340900 }, + { 0.72438623, 0.02564505, 0.01665696, 0.23331176 } ), + FlashData< 4 >( 1.000000e+08, 4.731500e+02, { 0.15695100, 0.10481800, 0.10482200, 0.63340900 }, false, 0.00000000, { 0.15695100, 0.10481800, 0.10482200, 0.63340900 }, + { 0.73612750, 0.02738195, 0.01777184, 0.21871871 } ), + FlashData< 4 >( 1.000000e+08, 4.731500e+02, { 0.00000000, 0.10481800, 0.10482200, 0.79036000 }, true, 0.72801768, { 0.00000000, 0.38538472, 0.38540005, 0.22921523 }, + { 0.00000000, 0.00000023, 0.00000000, 0.99999977 } ), + FlashData< 4 >( 1.000000e+08, 4.731500e+02, { 0.10481800, 0.00000000, 0.10482200, 0.79036000 }, false, 0.00000000, { 0.10481800, 0.00000000, 0.10482200, 0.79036000 }, + { 0.74504275, 0.00000000, 0.01613702, 0.23882023 } ) + ) + ); + +INSTANTIATE_TEST_SUITE_P( + NegativeTwoPhaseFlash, + NegativeTwoPhaseFlash4CompSRK, + ::testing::Values( + FlashData< 4 >( 1.000000e+05, 1.931500e+02, { 0.05695100, 0.10481800, 0.10482200, 0.73340900 }, true, 0.05645957, { 0.00052723, 0.11109010, 0.11109434, 0.77728833 }, + { 0.99989306, 0.00000003, 0.00000000, 0.00010691 } ), + FlashData< 4 >( 1.000000e+05, 1.931500e+02, { 0.15695100, 0.10481800, 0.10482200, 0.63340900 }, true, 0.15640660, { 0.00067175, 0.12425180, 0.12425654, 0.75081991 }, + { 0.99985756, 0.00000002, 0.00000000, 0.00014242 } ), + FlashData< 4 >( 1.000000e+05, 1.931500e+02, { 0.00000000, 0.10481800, 0.10482200, 0.79036000 }, true, 0.79033121, { 0.00000000, 0.49992180, 0.49994088, 0.00013733 }, + { 0.00000000, 0.00000000, 0.00000000, 1.00000000 } ), + FlashData< 4 >( 1.000000e+05, 1.931500e+02, { 0.10481800, 0.00000000, 0.10482200, 0.79036000 }, true, 0.10462698, { 0.00021850, 0.00000000, 0.11707076, 0.88271074 }, + { 0.99995588, 0.00000000, 0.00000000, 0.00004412 } ), + FlashData< 4 >( 5.000000e+06, 1.931500e+02, { 0.05695100, 0.10481800, 0.10482200, 0.73340900 }, true, 0.03575702, { 0.02198019, 0.10870496, 0.10870911, 0.76060573 }, + { 0.99999256, 0.00000002, 0.00000000, 0.00000742 } ), + FlashData< 4 >( 5.000000e+06, 1.931500e+02, { 0.15695100, 0.10481800, 0.10482200, 0.63340900 }, true, 0.13260833, { 0.02806574, 0.12084275, 0.12084737, 0.73024414 }, + { 0.99999008, 0.00000002, 0.00000000, 0.00000990 } ), + FlashData< 4 >( 1.000000e+05, 2.771500e+02, { 0.15695100, 0.10481800, 0.10482200, 0.63340900 }, true, 0.17767058, { 0.00055814, 0.12742716, 0.12746960, 0.74454511 }, + { 0.88079876, 0.00017392, 0.00000000, 0.11902732 } ), + FlashData< 4 >( 1.000000e+05, 2.771500e+02, { 0.00000000, 0.10481800, 0.10482200, 0.79036000 }, true, 0.78822635, { 0.00000000, 0.49495299, 0.49497188, 0.01007514 }, + { 0.00000000, 0.00000000, 0.00000000, 1.00000000 } ), + FlashData< 4 >( 1.000000e+05, 2.771500e+02, { 0.10481800, 0.00000000, 0.10482200, 0.79036000 }, true, 0.11241759, { 0.00027478, 0.00000000, 0.11809830, 0.88162692 }, + { 0.93022908, 0.00000000, 0.00000025, 0.06977066 } ), + FlashData< 4 >( 1.000000e+08, 2.771500e+02, { 0.05695100, 0.10481800, 0.10482200, 0.73340900 }, true, 0.00000000, { 0.05695100, 0.10481800, 0.10482200, 0.73340900 }, + { 0.13237045, 0.10086691, 0.43406051, 0.33270214 } ), + FlashData< 4 >( 1.000000e+08, 2.771500e+02, { 0.15695100, 0.10481800, 0.10482200, 0.63340900 }, true, 0.00000000, { 0.15695100, 0.10481800, 0.10482200, 0.63340900 }, + { 0.49094516, 0.08091778, 0.17876080, 0.24937626 } ), + FlashData< 4 >( 1.000000e+08, 2.771500e+02, { 0.00000000, 0.10481800, 0.10482200, 0.79036000 }, true, 0.78896344, { 0.00000000, 0.49668171, 0.49670067, 0.00661762 }, + { 0.00000000, 0.00000000, 0.00000000, 1.00000000 } ), + FlashData< 4 >( 1.013250e+05, 2.886500e+02, { 0.99000000, 0.00000000, 0.00000000, 0.01000000 }, true, 1.00000000, { 0.00000004, 0.00000000, 0.00000000, 0.99999996 }, + { 0.99000000, 0.00000000, 0.00000000, 0.01000000 } ), + FlashData< 4 >( 1.000000e+06, 2.886500e+02, { 0.99000000, 0.00000000, 0.00000000, 0.01000000 }, true, 0.99135986, { 0.00000042, 0.00000000, 0.00000000, 0.99999958 }, + { 0.99862829, 0.00000000, 0.00000000, 0.00137171 } ), + FlashData< 4 >( 1.000000e+06, 2.886500e+02, { 0.01000000, 0.00000000, 0.00000000, 0.99000000 }, true, 0.01001332, { 0.00000042, 0.00000000, 0.00000000, 0.99999958 }, + { 0.99862829, 0.00000000, 0.00000000, 0.00137171 } ), + FlashData< 4 >( 5.000000e+06, 2.886500e+02, { 0.05695100, 0.10481800, 0.10482200, 0.73340900 }, true, 0.03305889, { 0.02489908, 0.10840062, 0.10840577, 0.75829452 }, + { 0.99443907, 0.00002967, 0.00000001, 0.00553126 } ), + FlashData< 4 >( 1.000000e+07, 2.981500e+02, { 0.05695100, 0.10481800, 0.10482200, 0.73340900 }, true, 0.00905374, { 0.04838984, 0.10577509, 0.10577970, 0.74005537 }, + { 0.99398426, 0.00006297, 0.00000006, 0.00595271 } ), + FlashData< 4 >( 1.000000e+07, 2.981500e+02, { 0.15695100, 0.10481800, 0.10482200, 0.63340900 }, true, 0.10758197, { 0.05612207, 0.11744726, 0.11745840, 0.70897227 }, + { 0.99335094, 0.00005531, 0.00000004, 0.00659371 } ), + FlashData< 4 >( 1.000000e+07, 2.981500e+02, { 0.00000000, 0.10481800, 0.10482200, 0.79036000 }, true, 0.78640424, { 0.00000000, 0.49073071, 0.49074943, 0.01851986 }, + { 0.00000000, 0.00000000, 0.00000000, 1.00000000 } ), + FlashData< 4 >( 1.000000e+05, 3.331500e+02, { 0.99000000, 0.00000000, 0.00000000, 0.01000000 }, true, 1.00000000, { 0.00000022, 0.00000000, 0.00000000, 0.99999978 }, + { 0.99000000, 0.00000000, 0.00000000, 0.01000000 } ), + FlashData< 4 >( 1.000000e+08, 4.731500e+02, { 0.00000000, 0.10481800, 0.10482200, 0.79036000 }, true, 0.72419599, { 0.00000000, 0.38004425, 0.38005974, 0.23989601 }, + { 0.00000000, 0.00000038, 0.00000000, 0.99999962 } ), + FlashData< 4 >( 1.000000e+08, 4.731500e+02, { 0.10481800, 0.00000000, 0.10482200, 0.79036000 }, true, 0.00000000, { 0.10481800, 0.00000000, 0.10482200, 0.79036000 }, + { 0.84227242, 0.00000000, 0.00524021, 0.15248737 } ), + FlashData< 4 >( 1.000000e+08, 4.731500e+02, { 0.99000000, 0.00000000, 0.00000000, 0.01000000 }, true, 1.00000000, { 0.00566116, 0.00000000, 0.00000000, 0.99433884 }, + { 0.99000000, 0.00000000, 0.00000000, 0.01000000 } ), + FlashData< 4 >( 1.000000e+08, 4.731500e+02, { 0.01000000, 0.00000000, 0.00000000, 0.99000000 }, true, 0.00460663, { 0.00566116, 0.00000000, 0.00000000, 0.99433884 }, + { 0.94752980, 0.00000000, 0.00000000, 0.05247020 } ) + ) + ); + +} // testing + +} // geos diff --git a/src/coreComponents/constitutive/unitTests/testRachfordRice.cpp b/src/coreComponents/constitutive/unitTests/testRachfordRice.cpp index f8bb0305845..8f6014096cf 100644 --- a/src/coreComponents/constitutive/unitTests/testRachfordRice.cpp +++ b/src/coreComponents/constitutive/unitTests/testRachfordRice.cpp @@ -48,9 +48,9 @@ TEST( RachfordRiceTest, testRachfordRiceTwoComponents ) real64 const expectedVaporFraction1 = 1; real64 const vaporFraction1 = - RachfordRice::solve( kValues.toViewConst(), - feed.toViewConst(), - presentComponentIds.toViewConst() ); + RachfordRice::solve( kValues.toSliceConst(), + feed.toSliceConst(), + presentComponentIds.toSliceConst() ); checkRelativeError( vaporFraction1, expectedVaporFraction1, relTol ); @@ -67,9 +67,9 @@ TEST( RachfordRiceTest, testRachfordRiceTwoComponents ) real64 const expectedVaporFraction2 = 1; real64 const vaporFraction2 = - RachfordRice::solve( kValues.toViewConst(), - feed.toViewConst(), - presentComponentIds.toViewConst() ); + RachfordRice::solve( kValues.toSliceConst(), + feed.toSliceConst(), + presentComponentIds.toSliceConst() ); checkRelativeError( vaporFraction2, expectedVaporFraction2, relTol ); @@ -86,9 +86,9 @@ TEST( RachfordRiceTest, testRachfordRiceTwoComponents ) real64 const expectedVaporFraction3 = 1; real64 const vaporFraction3 = - RachfordRice::solve( kValues.toViewConst(), - feed.toViewConst(), - presentComponentIds.toViewConst() ); + RachfordRice::solve( kValues.toSliceConst(), + feed.toSliceConst(), + presentComponentIds.toSliceConst() ); checkRelativeError( vaporFraction3, expectedVaporFraction3, relTol ); @@ -105,9 +105,9 @@ TEST( RachfordRiceTest, testRachfordRiceTwoComponents ) real64 const expectedVaporFraction4 = 1; real64 const vaporFraction4 = - RachfordRice::solve( kValues.toViewConst(), - feed.toViewConst(), - presentComponentIds.toViewConst() ); + RachfordRice::solve( kValues.toSliceConst(), + feed.toSliceConst(), + presentComponentIds.toSliceConst() ); checkRelativeError( vaporFraction4, expectedVaporFraction4, relTol ); } @@ -140,9 +140,9 @@ TEST( RachfordRiceTest, testRachfordRiceFourComponents ) real64 const expectedVaporFraction1 = 0.0975568; real64 const vaporFraction1 = - RachfordRice::solve( kValues.toViewConst(), - feed.toViewConst(), - presentComponentIds.toViewConst() ); + RachfordRice::solve( kValues.toSliceConst(), + feed.toSliceConst(), + presentComponentIds.toSliceConst() ); checkRelativeError( vaporFraction1, expectedVaporFraction1, relTol ); @@ -165,9 +165,9 @@ TEST( RachfordRiceTest, testRachfordRiceFourComponents ) real64 const expectedVaporFraction2 = 0.045999; real64 const vaporFraction2 = - RachfordRice::solve( kValues.toViewConst(), - feed.toViewConst(), - presentComponentIds.toViewConst() ); + RachfordRice::solve( kValues.toSliceConst(), + feed.toSliceConst(), + presentComponentIds.toSliceConst() ); checkRelativeError( vaporFraction2, expectedVaporFraction2, relTol ); @@ -190,9 +190,9 @@ TEST( RachfordRiceTest, testRachfordRiceFourComponents ) real64 const expectedVaporFraction3 = 0.0130259; real64 const vaporFraction3 = - RachfordRice::solve( kValues.toViewConst(), - feed.toViewConst(), - presentComponentIds.toViewConst() ); + RachfordRice::solve( kValues.toSliceConst(), + feed.toSliceConst(), + presentComponentIds.toSliceConst() ); checkRelativeError( vaporFraction3, expectedVaporFraction3, relTol ); diff --git a/src/coreComponents/dataRepository/ExecutableGroup.hpp b/src/coreComponents/dataRepository/ExecutableGroup.hpp index cb7547e046e..69fa86db55b 100644 --- a/src/coreComponents/dataRepository/ExecutableGroup.hpp +++ b/src/coreComponents/dataRepository/ExecutableGroup.hpp @@ -116,7 +116,7 @@ class ExecutableGroup : public dataRepository::Group * @brief Get the target's time step behavior. * @return The time stepping type */ - TimesteppingBehavior getTimesteppingBehavior() { return m_timesteppingBehavior; } + TimesteppingBehavior getTimesteppingBehavior() const { return m_timesteppingBehavior; } private: diff --git a/src/coreComponents/dataRepository/MappedVector.hpp b/src/coreComponents/dataRepository/MappedVector.hpp index 1758637a6bd..0de9284e815 100644 --- a/src/coreComponents/dataRepository/MappedVector.hpp +++ b/src/coreComponents/dataRepository/MappedVector.hpp @@ -328,8 +328,8 @@ class MappedVector m_constValues.resize( index ); for( typename valueContainer::size_type i = index; i < m_values.size(); ++i ) { - m_constKeyValues.emplace_back( m_values[i].first, rawPtr( index ) ); - m_constValues.emplace_back( m_values[i].first, rawPtr( index ) ); + m_constKeyValues.emplace_back( m_values[i].first, rawPtr( i ) ); + m_constValues.emplace_back( m_values[i].first, rawPtr( i ) ); } // adjust lookup map indices diff --git a/src/coreComponents/dataRepository/ObjectCatalog.hpp b/src/coreComponents/dataRepository/ObjectCatalog.hpp index 8184efd9e15..bc74ffd9b72 100644 --- a/src/coreComponents/dataRepository/ObjectCatalog.hpp +++ b/src/coreComponents/dataRepository/ObjectCatalog.hpp @@ -697,12 +697,12 @@ class CatalogEntryConstructor< BASETYPE, TYPE > * generation of a CatalogEntry prior to main(). */ #define REGISTER_CATALOG_ENTRY( BaseType, DerivedType, ... ) \ - namespace { geos::dataRepository::CatalogEntryConstructor< BaseType, DerivedType, __VA_ARGS__ > catEntry_ ## DerivedType; } + namespace { GEOS_MAYBE_UNUSED geos::dataRepository::CatalogEntryConstructor< BaseType, DerivedType, __VA_ARGS__ > catEntry_ ## DerivedType; } /** * @brief Same as REGISTER_CATALOG_ENTRY, but for classes with no-argument constructors. */ #define REGISTER_CATALOG_ENTRY0( BaseType, DerivedType ) \ - namespace { geos::dataRepository::CatalogEntryConstructor< BaseType, DerivedType > catEntry_ ## DerivedType; } + namespace { GEOS_MAYBE_UNUSED geos::dataRepository::CatalogEntryConstructor< BaseType, DerivedType > catEntry_ ## DerivedType; } #endif /* GEOS_DATAREPOSITORY_OBJECTCATALOG_HPP_ */ diff --git a/src/coreComponents/dataRepository/wrapperHelpers.hpp b/src/coreComponents/dataRepository/wrapperHelpers.hpp index 84ec1169f47..1552de88280 100644 --- a/src/coreComponents/dataRepository/wrapperHelpers.hpp +++ b/src/coreComponents/dataRepository/wrapperHelpers.hpp @@ -228,7 +228,7 @@ byteSizeOfElement() template< typename T > inline size_t byteSize( T const & value ) -{ return size( value ) * byteSizeOfElement< T >(); } +{ return wrapperHelpers::size( value ) * byteSizeOfElement< T >(); } template< typename T > @@ -259,7 +259,7 @@ capacity( T const & value ) template< typename T > std::enable_if_t< !traits::HasMemberFunction_capacity< T const >, localIndex > capacity( T const & value ) -{ return size( value ); } +{ return wrapperHelpers::size( value ); } diff --git a/src/coreComponents/events/EventBase.hpp b/src/coreComponents/events/EventBase.hpp index 21d82201e4f..99aa5b3cfbb 100644 --- a/src/coreComponents/events/EventBase.hpp +++ b/src/coreComponents/events/EventBase.hpp @@ -321,6 +321,14 @@ class EventBase : public ExecutableGroup bool isActive( real64 const time ) const { return ( time >= m_beginTime ) && ( time < m_endTime ); } + /** + * @brief Is the event ready for cleanup? + * @param time The time at which we want to check if the event is cleanup. + * @return @p true if is ready for cleanup, @p false otherwise. + */ + bool isReadyForCleanup( real64 const time ) const + { return isActive( time ) || isZero( time - m_endTime ); } + /// The last time the event occurred. real64 m_lastTime; diff --git a/src/coreComponents/events/PeriodicEvent.cpp b/src/coreComponents/events/PeriodicEvent.cpp index 5b6f02e6d81..2017cb37592 100644 --- a/src/coreComponents/events/PeriodicEvent.cpp +++ b/src/coreComponents/events/PeriodicEvent.cpp @@ -218,7 +218,7 @@ void PeriodicEvent::cleanup( real64 const time_n, DomainPartition & domain ) { // Only call the cleanup method of the target/children if it is within its application time - if( isActive( time_n ) ) + if( isReadyForCleanup( time_n ) ) { ExecutableGroup * target = getEventTarget(); if( target != nullptr ) @@ -237,14 +237,20 @@ void PeriodicEvent::cleanup( real64 const time_n, void PeriodicEvent::validate() const { + ExecutableGroup const * target = getEventTarget(); + if( target == nullptr ) + { + return; + } + GEOS_THROW_IF( m_timeFrequency > 0 && - getEventTarget()->getTimesteppingBehavior() == ExecutableGroup::TimesteppingBehavior::DeterminesTimeStepSize, + target->getTimesteppingBehavior() == ExecutableGroup::TimesteppingBehavior::DeterminesTimeStepSize, GEOS_FMT( "`{}`: This event targets an object that automatically selects the time step size. Therefore, `{}` cannot be used here. However, forcing a constant time step size can still be achived with `{}`.", getName(), viewKeyStruct::timeFrequencyString(), EventBase::viewKeyStruct::forceDtString() ), InputError ); GEOS_THROW_IF( m_cycleFrequency != 1 && - getEventTarget()->getTimesteppingBehavior() == ExecutableGroup::TimesteppingBehavior::DeterminesTimeStepSize, + target->getTimesteppingBehavior() == ExecutableGroup::TimesteppingBehavior::DeterminesTimeStepSize, GEOS_FMT( "`{}`: This event targets an object that automatically selects the time step size. Therefore, `{}` cannot be used here. However, forcing a constant time step size can still be achived with `{}`.", getName(), viewKeyStruct::cycleFrequencyString(), EventBase::viewKeyStruct::forceDtString() ), diff --git a/src/coreComponents/fieldSpecification/FieldSpecificationBase.hpp b/src/coreComponents/fieldSpecification/FieldSpecificationBase.hpp index 23b35b58f14..b8499a8d572 100644 --- a/src/coreComponents/fieldSpecification/FieldSpecificationBase.hpp +++ b/src/coreComponents/fieldSpecification/FieldSpecificationBase.hpp @@ -649,16 +649,18 @@ void FieldSpecificationBase::applyFieldValue( SortedArrayView< localIndex const { dataRepository::WrapperBase & wrapper = dataGroup.getWrapperBase( fieldName ); - // This function is used in setting boundary/initial conditions on simulation fields. - // This is meaningful for 1/2/3D real arrays and sometimes 1D integer (indicator) arrays. - using FieldTypes = types::Join< types::ArrayTypes< types::RealTypes, types::DimsUpTo< 3 > >, - types::ArrayTypes< types::TypeList< integer >, types::DimsSingle< 1 > > >; - types::dispatch( FieldTypes{}, wrapper.getTypeId(), true, [&]( auto array ) + // // This function is used in setting boundary/initial conditions on simulation fields. + // // This is meaningful for 1/2/3D real arrays and sometimes 1D integer (indicator) arrays. + using FieldTypes = types::ListofTypeList< types::Join< types::ArrayTypes< types::RealTypes, types::DimsUpTo< 3 > >, + types::ArrayTypes< types::TypeList< integer >, types::DimsSingle< 1 > > > >; + + + types::dispatch( FieldTypes{}, [&]( auto tupleOfTypes ) { - using ArrayType = decltype( array ); + using ArrayType = camp::first< decltype( tupleOfTypes ) >; auto & wrapperT = dataRepository::Wrapper< ArrayType >::cast( wrapper ); applyFieldValueKernel< FIELD_OP, POLICY >( wrapperT.reference().toView(), targetSet, time, dataGroup ); - } ); + }, wrapper ); } template< typename FIELD_OP, typename POLICY, typename T, int NDIM, int USD > @@ -695,10 +697,10 @@ void FieldSpecificationBase::applyBoundaryConditionToSystem( SortedArrayView< lo arrayView1d< globalIndex const > const & dofMap = dataGroup.getReference< array1d< globalIndex > >( dofMapName ); // We're reading values from a field, which is only well-defined for dims 1 and 2 - using FieldTypes = types::ArrayTypes< types::RealTypes, types::DimsUpTo< 2 > >; - types::dispatch( FieldTypes{}, wrapper.getTypeId(), true, [&]( auto array ) + using FieldTypes = types::ListofTypeList< types::ArrayTypes< types::RealTypes, types::DimsUpTo< 2 > > >; + types::dispatch( FieldTypes{}, [&]( auto tupleOfTypes ) { - using ArrayType = decltype( array ); + using ArrayType = camp::first< decltype( tupleOfTypes ) >; auto const & wrapperT = dataRepository::Wrapper< ArrayType >::cast( wrapper ); applyBoundaryConditionToSystemKernel< FIELD_OP, POLICY >( targetSet, time, @@ -708,7 +710,7 @@ void FieldSpecificationBase::applyBoundaryConditionToSystem( SortedArrayView< lo matrix, rhs, wrapperT.reference() ); - } ); + }, wrapper ); } template< typename FIELD_OP, typename POLICY, typename LAMBDA > diff --git a/src/coreComponents/fileIO/silo/SiloFile.cpp b/src/coreComponents/fileIO/silo/SiloFile.cpp index 5d97efb42ae..85d6eb85542 100644 --- a/src/coreComponents/fileIO/silo/SiloFile.cpp +++ b/src/coreComponents/fileIO/silo/SiloFile.cpp @@ -1243,15 +1243,15 @@ void SiloFile::writeElementRegionSilo( ElementRegionBase const & elemRegion, string const & fieldName = wrapper.getName(); viewPointers[esr][fieldName] = &wrapper; - types::dispatch( types::StandardArrays{}, wrapper.getTypeId(), true, [&]( auto array ) + types::dispatch( types::ListofTypeList< types::StandardArrays >{}, [&]( auto tupleOfTypes ) { - using ArrayType = decltype( array ); + using ArrayType = camp::first< decltype( tupleOfTypes ) >; Wrapper< ArrayType > const & sourceWrapper = Wrapper< ArrayType >::cast( wrapper ); Wrapper< ArrayType > & newWrapper = fakeGroup.registerWrapper< ArrayType >( fieldName ); newWrapper.setPlotLevel( PlotLevel::LEVEL_0 ); newWrapper.reference().resize( ArrayType::NDIM, sourceWrapper.reference().dims() ); - } ); + }, wrapper ); } } } ); @@ -1263,9 +1263,9 @@ void SiloFile::writeElementRegionSilo( ElementRegionBase const & elemRegion, WrapperBase & wrapper = *wrapperIter.second; string const & fieldName = wrapper.getName(); - types::dispatch( types::StandardArrays{}, wrapper.getTypeId(), true, [&]( auto array ) + types::dispatch( types::ListofTypeList< types::StandardArrays >{}, [&]( auto tupleOfTypes ) { - using ArrayType = decltype( array ); + using ArrayType = camp::first< decltype( tupleOfTypes ) >; Wrapper< ArrayType > & wrapperT = Wrapper< ArrayType >::cast( wrapper ); ArrayType & targetArray = wrapperT.reference(); @@ -1294,7 +1294,7 @@ void SiloFile::writeElementRegionSilo( ElementRegionBase const & elemRegion, counter += subRegion.size(); } } ); - } ); + }, wrapper ); } writeGroupSilo( fakeGroup, diff --git a/src/coreComponents/fileIO/silo/SiloFile.hpp b/src/coreComponents/fileIO/silo/SiloFile.hpp index afb3547d84f..827cb809511 100644 --- a/src/coreComponents/fileIO/silo/SiloFile.hpp +++ b/src/coreComponents/fileIO/silo/SiloFile.hpp @@ -171,7 +171,7 @@ class SiloFile real64 const problemTime ); /** - * @todo Verify: documentation missing / incomplete. The TPL version of doxygen on Travis cannot parse + * @todo Verify: documentation missing / incomplete. The TPL version of doxygen on CI cannot parse * unnamed parameters, @p dummy parameter introduced to remove warning * * @param meshName name of the mesh in the silo db @@ -796,7 +796,7 @@ template<> inline real64 CastField( R1Tensor const & field, int const i ) } /** - * @todo Verify: the TPL version of doxygen on Travis cannot parse unnamed parameters, @p dummy + * @todo Verify: the TPL version of doxygen on CI cannot parse unnamed parameters, @p dummy * parameter introduced to remove warning * @param field the value to cast * @param dummy unused parameter @@ -809,7 +809,7 @@ template<> inline int CastField< int, int >( const int & field, int const dummy } /** - * @todo Verify: the TPL version of doxygen on Travis cannot parse unnamed parameters, @p dummy + * @todo Verify: the TPL version of doxygen on CI cannot parse unnamed parameters, @p dummy * parameter introduced to remove warning * @param field the value to cast * @param dummy unused parameter @@ -822,7 +822,7 @@ template<> inline long int CastField< long int, long int >( const long int & fie } /** - * @todo Verify: the TPL version of doxygen on Travis cannot parse unnamed parameters, @p dummy + * @todo Verify: the TPL version of doxygen on CI cannot parse unnamed parameters, @p dummy * parameter introduced to remove warning * @param field the value to cast * @param dummy unused parameter @@ -835,7 +835,7 @@ template<> inline int CastField< int, long int >( const long int & field, int co } /** - * @todo Verify: the TPL version of doxygen on Travis cannot parse unnamed parameters, @p dummy + * @todo Verify: the TPL version of doxygen on CI cannot parse unnamed parameters, @p dummy * parameter introduced to remove warning * @param field the value to cast * @param dummy unused parameter @@ -848,7 +848,7 @@ template<> inline long long int CastField< long long int, long long int >( const } /** - * @todo Verify: the TPL version of doxygen on Travis cannot parse unnamed parameters, @p dummy + * @todo Verify: the TPL version of doxygen on CI cannot parse unnamed parameters, @p dummy * parameter introduced to remove warning * @param field the value to cast * @param dummy unused parameter @@ -861,7 +861,7 @@ template<> inline int CastField< int, long long int >( const long long int & fie } /** - * @todo Verify: the TPL version of doxygen on Travis cannot parse unnamed parameters, @p dummy + * @todo Verify: the TPL version of doxygen on CI cannot parse unnamed parameters, @p dummy * parameter introduced to remove warning * @param field the value to cast * @param dummy unused parameter @@ -874,7 +874,7 @@ template<> inline real64 CastField< real64, real64 >( const real64 & field, int } /** - * @todo Verify: the TPL version of doxygen on Travis cannot parse unnamed parameters, @p dummy + * @todo Verify: the TPL version of doxygen on CI cannot parse unnamed parameters, @p dummy * parameter introduced to remove warning * @param field the value to cast * @param dummy unused parameter diff --git a/src/coreComponents/fileIO/timeHistory/PackCollection.cpp b/src/coreComponents/fileIO/timeHistory/PackCollection.cpp index 7a62eecec79..1bf183729ac 100644 --- a/src/coreComponents/fileIO/timeHistory/PackCollection.cpp +++ b/src/coreComponents/fileIO/timeHistory/PackCollection.cpp @@ -10,7 +10,6 @@ PackCollection::PackCollection ( string const & name, Group * parent ) , m_setNames( ) , m_setChanged( true ) , m_onlyOnSetChange( 0 ) - , m_disableCoordCollection( false ) , m_initialized( false ) { registerWrapper( PackCollection::viewKeysStruct::objectPathString(), &m_objectPath ). @@ -29,6 +28,11 @@ PackCollection::PackCollection ( string const & name, Group * parent ) setInputFlag( InputFlags::OPTIONAL ). setDefaultValue( 0 ). setDescription( "Whether or not to only collect when the collected sets of indices change in any way." ); + + registerWrapper( PackCollection::viewKeysStruct::disableCoordCollectionString(), &m_disableCoordCollection ). + setInputFlag( InputFlags::OPTIONAL ). + setDefaultValue( 0 ). + setDescription( "Whether or not to create coordinate meta-collectors if collected objects are mesh objects." ); } void PackCollection::initializePostSubGroups( ) diff --git a/src/coreComponents/fileIO/timeHistory/PackCollection.hpp b/src/coreComponents/fileIO/timeHistory/PackCollection.hpp index d5652228df6..03f3a0cee8a 100644 --- a/src/coreComponents/fileIO/timeHistory/PackCollection.hpp +++ b/src/coreComponents/fileIO/timeHistory/PackCollection.hpp @@ -74,11 +74,13 @@ class PackCollection : public HistoryCollectionBase static constexpr char const * fieldNameString() { return "fieldName"; } static constexpr char const * setNamesString() { return "setNames"; } static constexpr char const * onlyOnSetChangeString() { return "onlyOnSetChange"; } + static constexpr char const * disableCoordCollectionString() { return "disableCoordCollection"; } dataRepository::ViewKey objectPath = { "objectPath" }; dataRepository::ViewKey fieldName = { "fieldName" }; dataRepository::ViewKey setNames = { "setNames" }; dataRepository::ViewKey onlyOnSetChange = { "onlyOnSetChange" }; + dataRepository::ViewKey disableCoordCollection = { "disableCoordCollection" }; } viewKeys; /// @endcond @@ -125,7 +127,7 @@ class PackCollection : public HistoryCollectionBase localIndex m_onlyOnSetChange; /// Whether to create coordinate meta-collectors if collected objects are mesh objects (set to true for coordinate meta-collectors to /// avoid init recursion) - bool m_disableCoordCollection; + integer m_disableCoordCollection; /// Whether initializePostSubGroups has been called, since we only wan't to execute it once /// It is called explicitly by the output to ensure this is in a valid state to collect info from to perform setup /// It is also called by the normal initialization process diff --git a/src/coreComponents/fileIO/vtk/VTKPolyDataWriterInterface.cpp b/src/coreComponents/fileIO/vtk/VTKPolyDataWriterInterface.cpp index ef0d0340df7..d01bf903b57 100644 --- a/src/coreComponents/fileIO/vtk/VTKPolyDataWriterInterface.cpp +++ b/src/coreComponents/fileIO/vtk/VTKPolyDataWriterInterface.cpp @@ -457,7 +457,7 @@ getVtkCells( CellElementRegion const & region, // Here we privilege code simplicity. This can be more efficient (less tests) if the code is // specialized for each type of subregion. // This is not a time sensitive part of the code. Can be optimized later if needed. - forAll< parallelHostPolicy >( subRegion.size(), [=, &connectivity, &offsets]( localIndex const c ) + forAll< parallelHostPolicy >( subRegion.size(), [&]( localIndex const c ) { localIndex const elemConnOffset = connOffset + c * numVtkData; auto const nodes = nodeList[c]; @@ -513,9 +513,9 @@ writeField( WrapperBase const & wrapper, localIndex const offset, vtkDataArray * data ) { - types::dispatch( types::StandardArrays{}, wrapper.getTypeId(), true, [&]( auto array ) + types::dispatch( types::ListofTypeList< types::StandardArrays >{}, [&]( auto tupleOfTypes ) { - using ArrayType = decltype( array ); + using ArrayType = camp::first< decltype( tupleOfTypes ) >; using T = typename ArrayType::ValueType; vtkAOSDataArrayTemplate< T > * typedData = vtkAOSDataArrayTemplate< T >::FastDownCast( data ); auto const sourceArray = Wrapper< ArrayType >::cast( wrapper ).reference().toViewConst(); @@ -527,7 +527,7 @@ writeField( WrapperBase const & wrapper, typedData->SetTypedComponent( offset + i, compIndex++, value ); } ); } ); - } ); + }, wrapper ); } /** @@ -543,9 +543,9 @@ writeField( WrapperBase const & wrapper, localIndex const offset, vtkDataArray * data ) { - types::dispatch( types::StandardArrays{}, wrapper.getTypeId(), true, [&]( auto array ) + types::dispatch( types::ListofTypeList< types::StandardArrays >{}, [&]( auto tupleOfTypes ) { - using ArrayType = decltype( array ); + using ArrayType = camp::first< decltype( tupleOfTypes ) >; using T = typename ArrayType::ValueType; vtkAOSDataArrayTemplate< T > * typedData = vtkAOSDataArrayTemplate< T >::FastDownCast( data ); auto const sourceArray = Wrapper< ArrayType >::cast( wrapper ).reference().toViewConst(); @@ -557,7 +557,7 @@ writeField( WrapperBase const & wrapper, typedData->SetTypedComponent( offset + i, compIndex++, value ); } ); } ); - } ); + }, wrapper ); } /** @@ -731,14 +731,14 @@ writeElementField( Group const & subRegions, WrapperBase const & wrapper = subRegion.getWrapperBase( field ); if( first ) { - types::dispatch( types::StandardArrays{}, wrapper.getTypeId(), true, [&]( auto array ) + types::dispatch( types::ListofTypeList< types::StandardArrays >{}, [&]( auto tupleOfTypes ) { - using ArrayType = decltype( array ); + using ArrayType = camp::first< decltype( tupleOfTypes ) >; using T = typename ArrayType::ValueType; auto typedData = vtkAOSDataArrayTemplate< T >::New(); data.TakeReference( typedData ); setComponentMetadata( Wrapper< ArrayType >::cast( wrapper ), typedData ); - } ); + }, wrapper ); first = false; numDims = wrapper.numArrayDims(); } @@ -776,14 +776,14 @@ void VTKPolyDataWriterInterface::writeNodeFields( NodeManager const & nodeManage if( isFieldPlotEnabled( wrapper ) ) { vtkSmartPointer< vtkDataArray > data; - types::dispatch( types::StandardArrays{}, wrapper.getTypeId(), true, [&]( auto array ) + types::dispatch( types::ListofTypeList< types::StandardArrays >{}, [&]( auto tupleOfTypes ) { - using ArrayType = decltype( array ); + using ArrayType = camp::first< decltype( tupleOfTypes ) >; using T = typename ArrayType::ValueType; auto typedData = vtkAOSDataArrayTemplate< T >::New(); data.TakeReference( typedData ); setComponentMetadata( Wrapper< ArrayType >::cast( wrapper ), typedData ); - } ); + }, wrapper ); data->SetNumberOfTuples( nodeIndices.size() ); data->SetName( wrapper.getName().c_str() ); diff --git a/src/coreComponents/finiteElement/FiniteElementDiscretization.cpp b/src/coreComponents/finiteElement/FiniteElementDiscretization.cpp index e188980abd7..7c8beb1d7a1 100644 --- a/src/coreComponents/finiteElement/FiniteElementDiscretization.cpp +++ b/src/coreComponents/finiteElement/FiniteElementDiscretization.cpp @@ -101,7 +101,7 @@ FiniteElementDiscretization::factory( ElementType const parentElementShape ) con #if !defined( GEOS_USE_HIP ) return std::make_unique< H1_Wedge_VEM_Gauss1 >(); #else - GEOS_ERROR( "Cannot compile this on Crusher." ); + GEOS_ERROR( "Cannot compile this with HIP active." ); return nullptr; #endif } @@ -117,7 +117,7 @@ FiniteElementDiscretization::factory( ElementType const parentElementShape ) con #if !defined( GEOS_USE_HIP ) return std::make_unique< H1_Hexahedron_VEM_Gauss1 >(); #else - GEOS_ERROR( "Cannot compile this on Crusher." ); + GEOS_ERROR( "Cannot compile this with HIP active." ); return nullptr; #endif } @@ -126,7 +126,7 @@ FiniteElementDiscretization::factory( ElementType const parentElementShape ) con #if !defined( GEOS_USE_HIP ) return std::make_unique< Q1_Hexahedron_Lagrange_GaussLobatto >(); #else - GEOS_ERROR( "Cannot compile this on Crusher." ); + GEOS_ERROR( "Cannot compile this with HIP active." ); return nullptr; #endif } @@ -184,7 +184,7 @@ FiniteElementDiscretization::factory( ElementType const parentElementShape ) con "Element type Hexahedron with order 2 available only when using the Spectral Element Method" ); return std::make_unique< Q2_Hexahedron_Lagrange_GaussLobatto >(); #else - GEOS_ERROR( "Cannot compile this on Crusher." ); + GEOS_ERROR( "Cannot compile this with HIP active." ); #endif default: { @@ -204,7 +204,7 @@ FiniteElementDiscretization::factory( ElementType const parentElementShape ) con "Element type Hexahedron with order 3 available only when using the Spectral Element Method" ); return std::make_unique< Q3_Hexahedron_Lagrange_GaussLobatto >(); #else - GEOS_ERROR( "Cannot compile this on Crusher." ); + GEOS_ERROR( "Cannot compile this with HIP active." ); #endif default: { @@ -224,7 +224,7 @@ FiniteElementDiscretization::factory( ElementType const parentElementShape ) con "Element type Hexahedron with order 4 available only when using the Spectral Element Method" ); return std::make_unique< Q4_Hexahedron_Lagrange_GaussLobatto >(); #else - GEOS_ERROR( "Cannot compile this on Crusher." ); + GEOS_ERROR( "Cannot compile this with HIP active." ); #endif default: { @@ -244,7 +244,7 @@ FiniteElementDiscretization::factory( ElementType const parentElementShape ) con "Element type Hexahedron with order 5 available only when using the Spectral Element Method" ); return std::make_unique< Q5_Hexahedron_Lagrange_GaussLobatto >(); #else - GEOS_ERROR( "Cannot compile this on Crusher." ); + GEOS_ERROR( "Cannot compile this with HIP active." ); #endif default: { diff --git a/src/coreComponents/finiteElement/elementFormulations/H1_Hexahedron_Lagrange1_GaussLegendre2.hpp b/src/coreComponents/finiteElement/elementFormulations/H1_Hexahedron_Lagrange1_GaussLegendre2.hpp index 88f60143552..fe93d494822 100644 --- a/src/coreComponents/finiteElement/elementFormulations/H1_Hexahedron_Lagrange1_GaussLegendre2.hpp +++ b/src/coreComponents/finiteElement/elementFormulations/H1_Hexahedron_Lagrange1_GaussLegendre2.hpp @@ -252,6 +252,21 @@ class H1_Hexahedron_Lagrange1_GaussLegendre2 final : public FiniteElementBase real64 const (&X)[numNodes][3] ); + /** + * @brief Calculate the integration weights for a quadrature point. + * @param q Index of the quadrature point. + * @param X Array containing the coordinates of the support points. + * @param stack Variables allocated on the stack as filled by @ref setupStack. + * @return The product of the quadrature rule weight and the determinate of + * the parent/physical transformation matrix. + */ + GEOS_HOST_DEVICE + static real64 transformedQuadratureWeight( localIndex const q, + real64 const (&X)[numNodes][3], + StackVariables const & stack ) + { GEOS_UNUSED_VAR( stack ); return transformedQuadratureWeight( q, X ); } + + /** * @brief Calculates the isoparametric "Jacobian" transformation * matrix/mapping from the parent space to the physical space. diff --git a/src/coreComponents/finiteElement/elementFormulations/H1_Pyramid_Lagrange1_Gauss5.hpp b/src/coreComponents/finiteElement/elementFormulations/H1_Pyramid_Lagrange1_Gauss5.hpp index ac1744f27a2..dce5ae1c19d 100644 --- a/src/coreComponents/finiteElement/elementFormulations/H1_Pyramid_Lagrange1_Gauss5.hpp +++ b/src/coreComponents/finiteElement/elementFormulations/H1_Pyramid_Lagrange1_Gauss5.hpp @@ -209,6 +209,20 @@ class H1_Pyramid_Lagrange1_Gauss5 final : public FiniteElementBase static real64 transformedQuadratureWeight( localIndex const q, real64 const (&X)[numNodes][3] ); + /** + * @brief Calculate the integration weights for a quadrature point. + * @param q Index of the quadrature point. + * @param X Array containing the coordinates of the support points. + * @param stack Variables allocated on the stack as filled by @ref setupStack. + * @return The product of the quadrature rule weight and the determinate of + * the parent/physical transformation matrix. + */ + GEOS_HOST_DEVICE + static real64 transformedQuadratureWeight( localIndex const q, + real64 const (&X)[numNodes][3], + StackVariables const & stack ) + { GEOS_UNUSED_VAR( stack ); return transformedQuadratureWeight( q, X ); } + /** * @brief Calculates the isoparametric "Jacobian" transformation * matrix/mapping from the parent space to the physical space. diff --git a/src/coreComponents/finiteElement/elementFormulations/H1_Tetrahedron_Lagrange1_Gauss1.hpp b/src/coreComponents/finiteElement/elementFormulations/H1_Tetrahedron_Lagrange1_Gauss1.hpp index c7f696b377c..2e6356f6a9b 100644 --- a/src/coreComponents/finiteElement/elementFormulations/H1_Tetrahedron_Lagrange1_Gauss1.hpp +++ b/src/coreComponents/finiteElement/elementFormulations/H1_Tetrahedron_Lagrange1_Gauss1.hpp @@ -223,6 +223,20 @@ class H1_Tetrahedron_Lagrange1_Gauss1 final : public FiniteElementBase static real64 transformedQuadratureWeight( localIndex const q, real64 const (&X)[numNodes][3] ); + /** + * @brief Calculate the integration weights for a quadrature point. + * @param q Index of the quadrature point. + * @param X Array containing the coordinates of the support points. + * @param stack Variables allocated on the stack as filled by @ref setupStack. + * @return The product of the quadrature rule weight and the determinate of + * the parent/physical transformation matrix. + */ + GEOS_HOST_DEVICE + static real64 transformedQuadratureWeight( localIndex const q, + real64 const (&X)[numNodes][3], + StackVariables const & stack ) + { GEOS_UNUSED_VAR( stack ); return transformedQuadratureWeight( q, X ); } + /** * @brief Calculates the isoparametric "Jacobian" transformation * matrix/mapping from the parent space to the physical space. diff --git a/src/coreComponents/finiteElement/elementFormulations/H1_Wedge_Lagrange1_Gauss6.hpp b/src/coreComponents/finiteElement/elementFormulations/H1_Wedge_Lagrange1_Gauss6.hpp index b6ac4733efa..56c81663a9c 100644 --- a/src/coreComponents/finiteElement/elementFormulations/H1_Wedge_Lagrange1_Gauss6.hpp +++ b/src/coreComponents/finiteElement/elementFormulations/H1_Wedge_Lagrange1_Gauss6.hpp @@ -227,6 +227,20 @@ class H1_Wedge_Lagrange1_Gauss6 final : public FiniteElementBase static real64 transformedQuadratureWeight( localIndex const q, real64 const (&X)[numNodes][3] ); + /** + * @brief Calculate the integration weights for a quadrature point. + * @param q Index of the quadrature point. + * @param X Array containing the coordinates of the support points. + * @param stack Variables allocated on the stack as filled by @ref setupStack. + * @return The product of the quadrature rule weight and the determinate of + * the parent/physical transformation matrix. + */ + GEOS_HOST_DEVICE + static real64 transformedQuadratureWeight( localIndex const q, + real64 const (&X)[numNodes][3], + StackVariables const & stack ) + { GEOS_UNUSED_VAR( stack ); return transformedQuadratureWeight( q, X ); } + /** * @brief Calculates the isoparametric "Jacobian" transformation * matrix/mapping from the parent space to the physical space. diff --git a/src/coreComponents/finiteElement/kernelInterface/kernelInterface.rst b/src/coreComponents/finiteElement/kernelInterface/kernelInterface.rst index fe16b296fd5..a3a5381bac0 100644 --- a/src/coreComponents/finiteElement/kernelInterface/kernelInterface.rst +++ b/src/coreComponents/finiteElement/kernelInterface/kernelInterface.rst @@ -18,7 +18,7 @@ There are several main components of the FEMKI: patterns, and call the ``launch`` function. #. The kernel interface, which is specified by the - `finiteElement::KernelBase <../../../../doxygen_output/html/classgeos_1_1finite_element_1_1_implicit_kernel_base.html>`_ class. + `finiteElement::KernelBase <../../../doxygen_output/html/classgeos_1_1finite_element_1_1_implicit_kernel_base.html>`_ class. Each physics solver will define a class that contains its kernels functions, most likely deriving, or conforming to the API specified by the `KernelBase` class. Also part of this class will typically be a nested ``StackVariables`` @@ -35,7 +35,7 @@ There are several main components of the FEMKI: A Generic Element Looping Pattern --------------------------------- One example of a looping pattern is the -`regionBasedKernelApplication <../../../../doxygen_output/html/_kernel_base_8hpp.html#file_a22560f1fcca889307fdabb5fa7422c0d>`_ +`regionBasedKernelApplication <../../../doxygen_output/html/_kernel_base_8hpp.html#file_a22560f1fcca889307fdabb5fa7422c0d>`_ function. The contents of the looping function are displayed here: @@ -74,4 +74,4 @@ function are intended to provide a certain amount of modularity and flexibility for the physics implementations. The general purpose of each function is described by the function name, but may be further descibed by the function documentation found -`here <../../../../doxygen_output/html/classgeos_1_1finite_element_1_1_kernel_base.html>`_. +`here <../../../doxygen_output/html/classgeos_1_1finite_element_1_1_kernel_base.html>`_. diff --git a/src/coreComponents/finiteElement/unitTests/CMakeLists.txt b/src/coreComponents/finiteElement/unitTests/CMakeLists.txt index e0838dafcf2..e22fd94ea6f 100644 --- a/src/coreComponents/finiteElement/unitTests/CMakeLists.txt +++ b/src/coreComponents/finiteElement/unitTests/CMakeLists.txt @@ -10,8 +10,8 @@ set(testSources testH1_Wedge_Lagrange1_Gauss6.cpp testH1_Pyramid_Lagrange1_Gauss5.cpp testH1_TriangleFace_Lagrange1_Gauss1.cpp - # testQ3_Hexahedron_Lagrange_GaussLobatto.cpp - # testQ5_Hexahedron_Lagrange_GaussLobatto.cpp + testQ3_Hexahedron_Lagrange_GaussLobatto.cpp + testQ5_Hexahedron_Lagrange_GaussLobatto.cpp ) set( dependencyList gtest finiteElement ${parallelDeps} ) diff --git a/src/coreComponents/finiteElement/unitTests/testQ3_Hexahedron_Lagrange_GaussLobatto.cpp b/src/coreComponents/finiteElement/unitTests/testQ3_Hexahedron_Lagrange_GaussLobatto.cpp index 7309349db98..3bca3495eae 100644 --- a/src/coreComponents/finiteElement/unitTests/testQ3_Hexahedron_Lagrange_GaussLobatto.cpp +++ b/src/coreComponents/finiteElement/unitTests/testQ3_Hexahedron_Lagrange_GaussLobatto.cpp @@ -79,7 +79,8 @@ void testKernelDriver() } } ); - real64 xCoords[numNodes][3]; + array2d< real64 > xCoordsData( numNodes, 3 ); + arrayView2d< real64 > const & xCoords = xCoordsData; xCoords[0][0]=-1.0; xCoords[1][0]=-1.0/sqrt( 5.0 ); xCoords[2][0]=1.0/sqrt( 5.0 ); @@ -158,11 +159,22 @@ void testKernelDriver() forAll< POLICY >( 1, [=] GEOS_HOST_DEVICE ( localIndex const ) { + + real64 xLocal[numNodes][3]; + + for( localIndex a=0; a< numNodes; ++a ) + { + for( localIndex i=0; i<3; ++i ) + { + xLocal[a][i] = xCoords[a][i]; + } + } + for( localIndex q=0; q xCoordsData( numNodes, 3 ); + arrayView2d< real64 > const & xCoords = xCoordsData; xCoords[0][0]=-1.0; xCoords[1][0]=-sqrt( 1.0/21.0*(7.0+2.0*sqrt( 7.0 ))); xCoords[2][0]=-sqrt( 1.0/21.0*(7.0-2.0*sqrt( 7.0 ))); @@ -189,13 +190,23 @@ void testKernelDriver() [=] GEOS_HOST_DEVICE ( localIndex const ) { + real64 xLocal[numNodes][3]; + + for( localIndex a=0; a< numNodes; ++a ) + { + for( localIndex i=0; i<3; ++i ) + { + xLocal[a][i] = xCoords[a][i]; + } + } + for( localIndex q=0; q::concat( "\n* " ) ); + +// registerWrapper( viewKeyStruct::epsC1PPUString(), &m_upwindingParams.epsC1PPU ). +// setApplyDefaultValue( 1e-10 ). +// setInputFlag( InputFlags::OPTIONAL ). +// setDescription( "Tolerance for C1-PPU smoothing" ); } FluxApproximationBase::CatalogInterface::CatalogType & @@ -112,30 +123,43 @@ void FluxApproximationBase::initializePostInitialConditionsPreSubGroups() m_lengthScale = meshBody.getGlobalLengthScale(); meshBody.forMeshLevels( [&]( MeshLevel & mesh ) { - if( !(mesh.isShallowCopy() ) ) { // Group structure: mesh1/finiteVolumeStencils/myTPFA + // Compute the main cell-based stencil + computeCellStencil( mesh ); + + // Compute the fracture related stencils (within the fracture itself, + // but between the fracture and the matrix as well). + computeFractureStencil( mesh ); + Group & stencilParentGroup = mesh.getGroup( groupKeyStruct::stencilMeshGroupString() ); Group & stencilGroup = stencilParentGroup.getGroup( getName() ); // For each face-based Dirichlet boundary condition on target field, create a boundary stencil // TODO: Apply() should take a MeshLevel directly - fsManager.apply< FaceManager >( 0.0, // time = 0 - mesh, - m_fieldName, - [&] ( FieldSpecificationBase const &, - string const & setName, - SortedArrayView< localIndex const > const &, - FaceManager const &, - string const & ) + for( auto const & fieldName : m_fieldNames ) { - registerBoundaryStencil( stencilGroup, setName ); - } ); + fsManager.apply< FaceManager >( 0.0, // time = 0 + mesh, + fieldName, + [&] ( FieldSpecificationBase const &, + string const & setName, + SortedArrayView< localIndex const > const & faceSet, + FaceManager const &, + string const & ) + { + if( !stencilGroup.hasWrapper( setName ) ) + { + registerBoundaryStencil( stencilGroup, setName ); + computeBoundaryStencil( mesh, setName, faceSet ); + } + } ); + } // For each aquifer boundary condition, create a boundary stencil fsManager.apply< FaceManager, - AquiferBoundaryCondition >( 0.0, // time = 0 + AquiferBoundaryCondition >( 0.0, // time = 0 mesh, AquiferBoundaryCondition::catalogName(), [&] ( AquiferBoundaryCondition const &, @@ -146,27 +170,6 @@ void FluxApproximationBase::initializePostInitialConditionsPreSubGroups() { registerAquiferStencil( stencilGroup, setName ); } ); - - // Compute the main cell-based stencil - computeCellStencil( mesh ); - - // Compute the fracture related stencils (within the fracture itself, - // but between the fracture and the matrix as well). - computeFractureStencil( mesh ); - - // For each face-based boundary condition on target field, compute the boundary stencil weights - fsManager.apply< FaceManager >( 0.0, - mesh, - m_fieldName, - [&] ( FieldSpecificationBase const &, - string const & setName, - SortedArrayView< localIndex const > const & faceSet, - FaceManager const &, - string const & ) - { - computeBoundaryStencil( mesh, setName, faceSet ); - } ); - // Compute the aquifer stencil weights computeAquiferStencil( domain, mesh ); } @@ -174,9 +177,9 @@ void FluxApproximationBase::initializePostInitialConditionsPreSubGroups() } ); } -void FluxApproximationBase::setFieldName( string const & name ) +void FluxApproximationBase::addFieldName( string const & name ) { - m_fieldName = name; + m_fieldNames.emplace_back( name ); } void FluxApproximationBase::setCoeffName( string const & name ) diff --git a/src/coreComponents/finiteVolume/FluxApproximationBase.hpp b/src/coreComponents/finiteVolume/FluxApproximationBase.hpp index bbb3b4341a4..a26320b39c3 100644 --- a/src/coreComponents/finiteVolume/FluxApproximationBase.hpp +++ b/src/coreComponents/finiteVolume/FluxApproximationBase.hpp @@ -31,6 +31,36 @@ namespace geos { +/** + * @brief Upwinding scheme. + */ +enum class UpwindingScheme : integer +{ + PPU, ///< PPU upwinding + C1PPU, ///< C1-PPU upwinding from https://doi.org/10.1016/j.advwatres.2017.07.028 +}; + +/** + * @brief Strings for upwinding scheme. + */ +ENUM_STRINGS( UpwindingScheme, + "PPU", + "C1PPU" ); + +/** + * @struct UpwindingParameters + * + * Structure to store upwinding parameters, such as upwinding scheme and related tolerances etc. + */ +struct UpwindingParameters +{ + /// PPU or C1-PPU + UpwindingScheme upwindingScheme; + + /// C1-PPU smoothing tolerance + //real64 epsC1PPU; +}; + /** * @class FluxApproximationBase * @@ -142,6 +172,13 @@ class FluxApproximationBase : public dataRepository::Group /// @return The key for fractureStencil static constexpr char const * fractureStencilString() { return "fractureStencil"; } + + /// @return The key for upwindingScheme + static constexpr char const * upwindingSchemeString() { return "upwindingScheme"; } + + /// @return The key for epsC1PPU + //static constexpr char const * epsC1PPUString() { return "epsC1PPU"; } + }; /** @@ -164,7 +201,7 @@ class FluxApproximationBase : public dataRepository::Group * @brief set the name of the field. * @param name name of the field to be set. */ - void setFieldName( string const & name ); + void addFieldName( string const & name ); /** * @brief set the name of the coefficient. @@ -172,6 +209,12 @@ class FluxApproximationBase : public dataRepository::Group */ void setCoeffName( string const & name ); + /** + * @brief get the upwinding parameters. + * @return upwinding parameters structure. + */ + UpwindingParameters const & upwindingParams() const { return m_upwindingParams; } + protected: virtual void initializePreSubGroups() override; @@ -241,7 +284,7 @@ class FluxApproximationBase : public dataRepository::Group /// name of the primary solution field - string m_fieldName; + array1d< string > m_fieldNames; /// name of the coefficient field string m_coeffName; @@ -255,6 +298,9 @@ class FluxApproximationBase : public dataRepository::Group /// length scale of the mesh body real64 m_lengthScale; + /// upwinding parameters + UpwindingParameters m_upwindingParams; + }; template< typename TYPE > diff --git a/src/coreComponents/finiteVolume/TwoPointFluxApproximation.cpp b/src/coreComponents/finiteVolume/TwoPointFluxApproximation.cpp index 28d3d284913..c1ed755c2b9 100644 --- a/src/coreComponents/finiteVolume/TwoPointFluxApproximation.cpp +++ b/src/coreComponents/finiteVolume/TwoPointFluxApproximation.cpp @@ -293,7 +293,7 @@ void TwoPointFluxApproximation::addFractureFractureConnectionsDFM( MeshLevel & m // For now, we do not filter out connections for which numElems == 1 in this function. // Instead, the filter takes place in the single-phase FluxKernels specialized for the SurfaceElementStencil - // (see physicsSolvers/multiphysics/SinglePhasePoromechanicsFluxKernels.cpp). + // (see physicsSolvers/multiphysics/SinglePhaseProppantFluxKernels.cpp). // The reason for doing the filtering there and not here is that the ProppantTransport solver // needs the connections numElems == 1 to produce correct results. @@ -952,8 +952,12 @@ void TwoPointFluxApproximation::addEmbeddedFracturesToStencils( MeshLevel & mesh void TwoPointFluxApproximation::registerBoundaryStencil( Group & stencilGroup, string const & setName ) const { - stencilGroup.registerWrapper< BoundaryStencil >( setName ). - setRestartFlags( RestartFlags::NO_WRITE ); + if( !stencilGroup.hasWrapper( setName ) ) + { + // if not there yet, let's register the set name as a wrapper + stencilGroup.registerWrapper< BoundaryStencil >( setName ). + setRestartFlags( RestartFlags::NO_WRITE ); + } } void TwoPointFluxApproximation::computeBoundaryStencil( MeshLevel & mesh, @@ -1106,6 +1110,7 @@ void TwoPointFluxApproximation::computeAquiferStencil( DomainPartition & domain, { regionFilter.insert( elemManager.getRegions().getIndex( regionName ) ); } + SortedArrayView< localIndex const > const regionFilterView = regionFilter.toViewConst(); // Step 1: count individual aquifers @@ -1149,7 +1154,7 @@ void TwoPointFluxApproximation::computeAquiferStencil( DomainPartition & domain, } // Filter out elements not in target regions - if( !regionFilter.contains( elemRegionList[iface][ke] )) + if( !regionFilterView.contains( elemRegionList[iface][ke] )) { continue; } @@ -1216,7 +1221,7 @@ void TwoPointFluxApproximation::computeAquiferStencil( DomainPartition & domain, } // Filter out elements not in target regions - if( !regionFilter.contains( elemRegionList[iface][ke] )) + if( !regionFilterView.contains( elemRegionList[iface][ke] )) { continue; } diff --git a/src/coreComponents/functions/CMakeLists.txt b/src/coreComponents/functions/CMakeLists.txt index f2f6774dee4..06d83877863 100644 --- a/src/coreComponents/functions/CMakeLists.txt +++ b/src/coreComponents/functions/CMakeLists.txt @@ -42,4 +42,5 @@ blt_add_library( NAME functions target_include_directories( functions PUBLIC ${CMAKE_SOURCE_DIR}/coreComponents ) +add_subdirectory(unitTests) geosx_add_code_checks( PREFIX functions ) diff --git a/src/coreComponents/functions/FunctionBase.hpp b/src/coreComponents/functions/FunctionBase.hpp index c0f89abdcec..ce96b238f7b 100644 --- a/src/coreComponents/functions/FunctionBase.hpp +++ b/src/coreComponents/functions/FunctionBase.hpp @@ -178,10 +178,10 @@ void FunctionBase::evaluateT( dataRepository::Group const & group, dataRepository::WrapperBase const & wrapper = group.getWrapperBase( varName ); varSize[varIndex] = wrapper.numArrayComp(); - using Types = types::ArrayTypes< types::TypeList< real64 >, types::DimsUpTo< 2 > >; - types::dispatch( Types{}, wrapper.getTypeId(), true, [&]( auto array ) + using Types = types::ListofTypeList< types::ArrayTypes< types::TypeList< real64 >, types::DimsUpTo< 2 > > >; + types::dispatch( Types{}, [&]( auto tupleOfTypes ) { - using ArrayType = decltype( array ); + using ArrayType = camp::first< decltype( tupleOfTypes ) >; auto const view = dataRepository::Wrapper< ArrayType >::cast( wrapper ).reference().toViewConst(); view.move( hostMemorySpace, false ); for( int dim = 0; dim < ArrayType::NDIM; ++dim ) @@ -189,7 +189,7 @@ void FunctionBase::evaluateT( dataRepository::Group const & group, varStride[varIndex][dim] = view.strides()[dim]; } inputPtrs[varIndex] = view.data(); - } ); + }, wrapper ); } totalVarSize += varSize[varIndex]; } diff --git a/src/coreComponents/unitTests/functionsTests/CMakeLists.txt b/src/coreComponents/functions/unitTests/CMakeLists.txt similarity index 68% rename from src/coreComponents/unitTests/functionsTests/CMakeLists.txt rename to src/coreComponents/functions/unitTests/CMakeLists.txt index 51fc22b3ec7..32461b62344 100644 --- a/src/coreComponents/unitTests/functionsTests/CMakeLists.txt +++ b/src/coreComponents/functions/unitTests/CMakeLists.txt @@ -7,13 +7,13 @@ set( gtest_geosx_tests ) -set( dependencyList ${parallelDeps} gtest ) +set( dependencyList ${parallelDeps} gtest functions ) -if ( GEOSX_BUILD_SHARED_LIBS ) - set (dependencyList ${dependencyList} geosx_core ) -else() - set (dependencyList ${dependencyList} ${geosx_core_libs} ) -endif() +# if ( GEOSX_BUILD_SHARED_LIBS ) +# set (dependencyList ${dependencyList} geosx_core ) +# else() +# set (dependencyList ${dependencyList} ${geosx_core_libs} ) +# endif() # # Add gtest C++ based tests diff --git a/src/coreComponents/unitTests/functionsTests/testFunctions.cpp b/src/coreComponents/functions/unitTests/testFunctions.cpp similarity index 99% rename from src/coreComponents/unitTests/functionsTests/testFunctions.cpp rename to src/coreComponents/functions/unitTests/testFunctions.cpp index b28b7b45b1e..c942196a200 100644 --- a/src/coreComponents/unitTests/functionsTests/testFunctions.cpp +++ b/src/coreComponents/functions/unitTests/testFunctions.cpp @@ -20,7 +20,7 @@ #include "functions/TableFunction.hpp" #include "functions/MultivariableTableFunction.hpp" #include "functions/MultivariableTableFunctionKernels.hpp" -#include "mainInterface/GeosxState.hpp" +//#include "mainInterface/GeosxState.hpp" #ifdef GEOSX_USE_MATHPRESSO #include "functions/SymbolicFunction.hpp" @@ -904,11 +904,16 @@ int main( int argc, char * * argv ) { ::testing::InitGoogleTest( &argc, argv ); - geos::GeosxState state( geos::basicSetup( argc, argv ) ); + // geos::GeosxState state( geos::basicSetup( argc, argv ) ); + conduit::Node conduitNode; + dataRepository::Group rootNode( "root", conduitNode ); + + + FunctionManager functionManager( "FunctionManager", &rootNode ); int const result = RUN_ALL_TESTS(); - geos::basicCleanup(); + // geos::basicCleanup(); return result; } diff --git a/src/coreComponents/linearAlgebra/CMakeLists.txt b/src/coreComponents/linearAlgebra/CMakeLists.txt index aa51f7aba5c..271cb233b3c 100644 --- a/src/coreComponents/linearAlgebra/CMakeLists.txt +++ b/src/coreComponents/linearAlgebra/CMakeLists.txt @@ -35,6 +35,7 @@ set( linearAlgebra_headers utilities/LinearSolverParameters.hpp utilities/LinearSolverResult.hpp utilities/NormalOperator.hpp + utilities/ReverseCutHillMcKeeOrdering.hpp utilities/TransposeOperator.hpp ) @@ -49,6 +50,7 @@ set( linearAlgebra_sources solvers/GmresSolver.cpp solvers/KrylovSolver.cpp solvers/SeparateComponentPreconditioner.cpp + utilities/ReverseCutHillMcKeeOrdering.cpp ) set( dependencyList ${parallelDeps} mesh denseLinearAlgebra ) @@ -110,6 +112,7 @@ if( ENABLE_HYPRE ) interfaces/hypre/mgrStrategies/SinglePhasePoromechanicsReservoirFVM.hpp interfaces/hypre/mgrStrategies/SinglePhaseReservoirFVM.hpp interfaces/hypre/mgrStrategies/SinglePhaseReservoirHybridFVM.hpp + interfaces/hypre/mgrStrategies/SolidMechanicsEmbeddedFractures.hpp interfaces/hypre/mgrStrategies/ThermalCompositionalMultiphaseFVM.hpp interfaces/hypre/mgrStrategies/ThermalMultiphasePoromechanics.hpp interfaces/hypre/mgrStrategies/ThermalSinglePhasePoromechanics.hpp diff --git a/src/coreComponents/linearAlgebra/DofManager.cpp b/src/coreComponents/linearAlgebra/DofManager.cpp index 94af11fb8a5..12380d90a59 100644 --- a/src/coreComponents/linearAlgebra/DofManager.cpp +++ b/src/coreComponents/linearAlgebra/DofManager.cpp @@ -22,6 +22,7 @@ #include "common/TypeDispatch.hpp" #include "finiteVolume/FluxApproximationBase.hpp" #include "linearAlgebra/interfaces/InterfaceTypes.hpp" +#include "linearAlgebra/utilities/ReverseCutHillMcKeeOrdering.hpp" #include "mesh/mpiCommunications/CommunicationTools.hpp" #include "mesh/DomainPartition.hpp" #include "mesh/ElementRegionManager.hpp" @@ -173,9 +174,114 @@ void forMeshSupport( std::vector< DofManager::FieldSupport > const & support, } } +void fillTrivialPermutation( arrayView1d< localIndex > const permutation ) +{ + forAll< parallelHostPolicy >( permutation.size(), [&]( localIndex const i ) + { + permutation[i] = i; + } ); +} + } // namespace -void DofManager::createIndexArray( FieldDescription const & field ) +array1d< localIndex > DofManager::computePermutation( FieldDescription & field ) +{ + localIndex const fieldIndex = getFieldIndex( field.name ); + + // step 1: save the number of components, and then set it to 1 temporarily + // do not forget to restore at the end + // we set the number of components to 1 to compute the reordering on a smaller matrix + + localIndex const numComps = field.numComponents; + CompMask const globallyCoupledComps = field.globallyCoupledComponents; + field.numComponents = 1; + field.globallyCoupledComponents = CompMask( 1, true ); + + // step 2: compute field dimensions (local dofs, global dofs, etc) + // this is needed to make sure that the sparsity pattern function work properly + // in particular, this function defines the rankOffset (computed with number of components = 1) + + computeFieldDimensions( fieldIndex ); + + // the number of local dofs is available at this point, we allocate space for the permutation + array1d< localIndex > permutation( numLocalDofs( field.name ) ); + + // if no reordering is requesting, we just return the identity permutation + if( field.reorderingType == LocalReorderingType::None ) + { + fillTrivialPermutation( permutation ); + } + else + { + fillTrivialPermutation( permutation ); + computePermutation( field, permutation ); + } + + // reset the number of components + field.numComponents = numComps; + field.globallyCoupledComponents = globallyCoupledComps; + // reset the offsets, since they will be recomputed with the proper number of components + // this is important to get the right reordering for multiphysics problems + field.numLocalDof = 0; + field.rankOffset = 0; + field.numGlobalDof = 0; + field.globalOffset = 0; + field.blockOffset = 0; + + return permutation; +} + +void DofManager::computePermutation( FieldDescription const & field, + arrayView1d< localIndex > const permutation ) +{ + localIndex const fieldIndex = getFieldIndex( field.name ); + + // step 3: allocate and fill the dofNumber array + // this is needed to have dofNumber computed with numComps = 1 + // note that the dofNumbers will be recomputed once we have obtained the permutation + + createIndexArray( field, permutation.toViewConst() ); + + // step 4: compute the local sparsity pattern for this field + + SparsityPattern< globalIndex > pattern; + array1d< localIndex > rowSizes( numLocalDofs( field.name ) ); + // in a first pass, count the row lengths to allocate enough space + countRowLengthsOneBlock( rowSizes, fieldIndex, fieldIndex ); + + // resize the sparsity pattern now that we know the row sizes + pattern.resizeFromRowCapacities< parallelHostPolicy >( numLocalDofs( field.name ), + numGlobalDofs( field.name ), + rowSizes.data() ); + + // compute the sparsity pattern + setSparsityPatternOneBlock( pattern.toView(), fieldIndex, fieldIndex ); + + // step 5: call the reordering function + // the goal of this step is to fill the permutation array + + localIndex const * const offsets = pattern.getOffsets(); + globalIndex const * const columns = pattern.getColumns(); + array1d< localIndex > reversePermutation( permutation.size() ); + + if( field.reorderingType == LocalReorderingType::ReverseCutHillMcKee ) + { + reverseCutHillMcKeeOrdering:: + computePermutation( offsets, columns, rankOffset(), reversePermutation ); + } + else + { + GEOS_ERROR( "This local ordering type is not supported yet" ); + } + + forAll< parallelHostPolicy >( permutation.size(), [&]( localIndex const i ) + { + permutation[reversePermutation[i]] = i; + } ); +} + +void DofManager::createIndexArray( FieldDescription const & field, + arrayView1d< localIndex const > const permutation ) { LocationSwitch( field.location, [&]( auto const loc ) { @@ -203,7 +309,7 @@ void DofManager::createIndexArray( FieldDescription const & field ) // populate index array using a sequential counter forMeshLocation< LOC, false, serialPolicy >( mesh, regions, [&]( auto const locIdx ) { - helper::reference( indexArray, locIdx ) = field.rankOffset + field.numComponents * index++; + helper::reference( indexArray, locIdx ) = field.rankOffset + field.numComponents * permutation[index++]; } ); // synchronize across ranks @@ -367,6 +473,13 @@ void DofManager::addField( string const & fieldName, addField( fieldName, location, components, support ); } +void DofManager::setLocalReorderingType( string const & fieldName, + LocalReorderingType const reorderingType ) +{ + FieldDescription & field = m_fields[getFieldIndex( fieldName )]; + field.reorderingType = reorderingType; +} + void DofManager::disableGlobalCouplingForEquation( string const & fieldName, integer const c ) { @@ -407,6 +520,7 @@ processCouplingRegionList( std::set< string > inputList, // Check that both fields exist on all regions in the list auto const checkSupport = [®ions]( std::set< string > const & fieldRegions, string const & fieldName ) { + GEOS_UNUSED_VAR( fieldName ); // unused if geos_error_if is nulld // Both regions lists are sorted at this point GEOS_ERROR_IF( !std::includes( fieldRegions.begin(), fieldRegions.end(), regions.begin(), regions.end() ), GEOS_FMT( "Coupling domain is not a subset of {}'s support:\nCoupling: {}\nField: {}", @@ -459,6 +573,7 @@ processCouplingRegionList( std::vector< DofManager::FieldSupport > inputList, // Check that each input entry is included in both row and col field supports auto const checkSupport = [®ions]( std::vector< DofManager::FieldSupport > const & fieldRegions, string const & fieldName ) { + GEOS_UNUSED_VAR( fieldName ); // unused if geos_error_if is nulled for( DofManager::FieldSupport const & r : regions ) { auto const comp = [&r]( auto const & f ){ return RegionComp< std::equal_to<> >{} ( r, f ); }; @@ -1228,10 +1343,10 @@ void vectorToFieldImpl( arrayView1d< real64 const > const & localVector, // Restrict primary solution fields to 1-2D real arrays, // because applying component index is not well defined for 3D and higher - using FieldTypes = types::ArrayTypes< types::RealTypes, types::DimsUpTo< 2 > >; - types::dispatch( FieldTypes{}, wrapper.getTypeId(), true, [&]( auto array ) + using FieldTypes = types::ListofTypeList< types::ArrayTypes< types::RealTypes, types::DimsUpTo< 2 > > >; + types::dispatch( FieldTypes{}, [&]( auto tupleOfTypes ) { - using ArrayType = decltype( array ); + using ArrayType = camp::first< decltype( tupleOfTypes ) >; Wrapper< ArrayType > & wrapperT = Wrapper< ArrayType >::cast( wrapper ); vectorToFieldKernel< FIELD_OP, POLICY >( manager, localVector, @@ -1241,7 +1356,7 @@ void vectorToFieldImpl( arrayView1d< real64 const > const & localVector, scalingFactor, dofOffset, mask ); - } ); + }, wrapper ); } template< typename FIELD_OP, typename POLICY, typename FIELD_VIEW > @@ -1288,10 +1403,10 @@ void fieldToVectorImpl( arrayView1d< real64 > const & localVector, // Restrict primary solution fields to 1-2D real arrays, // because applying component index is not well defined for 3D and higher - using FieldTypes = types::ArrayTypes< types::RealTypes, types::DimsUpTo< 2 > >; - types::dispatch( FieldTypes{}, wrapper.getTypeId(), true, [&]( auto array ) + using FieldTypes = types::ListofTypeList< types::ArrayTypes< types::RealTypes, types::DimsUpTo< 2 > > >; + types::dispatch( FieldTypes{}, [&]( auto tupleOfTypes ) { - using ArrayType = decltype( array ); + using ArrayType = camp::first< decltype( tupleOfTypes ) >; Wrapper< ArrayType > const & wrapperT = Wrapper< ArrayType >::cast( wrapper ); fieldToVectorKernel< FIELD_OP, POLICY >( localVector, wrapperT.reference(), @@ -1300,7 +1415,7 @@ void fieldToVectorImpl( arrayView1d< real64 > const & localVector, scalingFactor, dofOffset, mask ); - } ); + }, wrapper ); } } // namespace @@ -1450,11 +1565,22 @@ void DofManager::reorderByRank() { GEOS_LAI_ASSERT( !m_reordered ); + std::map< string, array1d< localIndex > > permutations; + + // First loop: compute the local permutation + for( FieldDescription & field : m_fields ) + { + // compute local permutation of dofs, if needed + permutations[ field.name ] = computePermutation( field ); + } + + // Second loop: compute the dof number array for( FieldDescription & field : m_fields ) { + // compute field dimensions (local dofs, global dofs, etc) computeFieldDimensions( static_cast< localIndex >( getFieldIndex( field.name ) ) ); // allocate and fill index array - createIndexArray( field ); + createIndexArray( field, permutations.at( field.name ).toViewConst() ); } // update field offsets to account for renumbering diff --git a/src/coreComponents/linearAlgebra/DofManager.hpp b/src/coreComponents/linearAlgebra/DofManager.hpp index 57ef69642f0..db8b7a01c8d 100644 --- a/src/coreComponents/linearAlgebra/DofManager.hpp +++ b/src/coreComponents/linearAlgebra/DofManager.hpp @@ -109,6 +109,15 @@ class DofManager Stencil //!< connectivity is through a (set of) user-provided stencil(s) }; + /** + * @brief Indicates the type of (local to a rank) reordering applied to a given field + */ + enum class LocalReorderingType + { + None, ///< Do not reorder the variables + ReverseCutHillMcKee, ///< Use reverve CutHill-McKee reordering algorithm. + }; + /** * @brief Constructor. * @@ -176,6 +185,14 @@ class DofManager integer components, map< std::pair< string, string >, array1d< string > > const & regions ); + /** + * @brief Set the local reodering of the dof numbers + * @param [in] fieldName the name of the field + * @param [in] reorderingType the reordering type + */ + void setLocalReorderingType( string const & fieldName, + LocalReorderingType const reorderingType ); + /** * @brief Disable the global coupling for a given equation * @param [in] fieldName the name of the field @@ -493,6 +510,7 @@ class DofManager globalIndex blockOffset = 0; ///< offset of this field's block in a block-wise ordered system globalIndex rankOffset = 0; ///< field's first DoF on current processor (within its block, ignoring other fields) globalIndex globalOffset = 0; ///< global offset of field's DOFs on current processor for multi-field problems + LocalReorderingType reorderingType = LocalReorderingType::None; ///< Type of local reordering applied to this field }; /** @@ -519,8 +537,10 @@ class DofManager /** * @brief Create index array for the field * @param field the field descriptor + * @param permutation the local permutation used to fill the index array for this field */ - void createIndexArray( FieldDescription const & field ); + void createIndexArray( FieldDescription const & field, + arrayView1d< localIndex const > const permutation ); /** * @brief Remove an index array for the field @@ -528,6 +548,23 @@ class DofManager */ void removeIndexArray( FieldDescription const & field ); + /** + * @brief Compute a local reordering of the dofNumbers or alternatively, return a trivial permutation + * @param field the field descriptor + * @return permutation the local permutation used to fill the index array for this field + */ + array1d< localIndex > computePermutation( FieldDescription & field ); + + /** + * @brief Compute a local reordering of the dofNumbers + * @param field the field descriptor + * @param permutation the local permutation used to fill the index array for this field + * @detail This function throws an error if the field requires a trivial permutation + */ + void computePermutation( FieldDescription const & field, + arrayView1d< localIndex > const permutation ); + + /** * @brief Calculate or estimate the number of nonzero entries in each local row * @param rowLengths array of row lengths (values are be incremented, not overwritten) diff --git a/src/coreComponents/linearAlgebra/docs/DofManager.rst b/src/coreComponents/linearAlgebra/docs/DofManager.rst index 5c2ef41d8d7..d13e2ee7c9c 100644 --- a/src/coreComponents/linearAlgebra/docs/DofManager.rst +++ b/src/coreComponents/linearAlgebra/docs/DofManager.rst @@ -16,7 +16,7 @@ Key concepts are locations and connectors. Locations, that can be elements, faces, edges or nodes, represent where the DoF is assigned. For example, a DoF for pressure in a two-point flux approximation will be on a cell (i.e. element), while a displacement DoF for structural equations will be on a node. The counterparts of locations are connectors, that are the geometrical entities -that link together different DoFs are create the sparsity pattern. +that link together different DoFs that create the sparsity pattern. Connectors can be elements, faces, edges, nodes or none. Using the same example as before, connectors will be faces and cells, respectively. The case of a mass matrix, where every element is linked only to itself, is an example when there are no connectors, i.e. these have to be set to none. @@ -148,7 +148,7 @@ Unknowns are pressure, located on the element center, and displacements (*x* and For fluxes, a two-point flux approximation (TPFA) is used. The representation of the sparsity pattern of the :math:`\mathsf{C_L}` matrix (connectors/locations) for the simple mesh, shown in :numref:`meshDofManagerFig`, is reported in :numref:`CLDofManagerFig`. -It can be notices that the two unknowns for the displacements *x* and *y* are grouped together. +It can be noticed that the two unknowns for the displacements *x* and *y* are grouped together. Elements are the connectivity for DoF on nodes (Finite Element Method for displacements) and on elements (pressures). Faces are the connectivity for DoF on elements (Finite Volume Method for pressure), being the flux computation based on the pressure on the two adjacent elements. diff --git a/src/coreComponents/linearAlgebra/interfaces/hypre/HypreMGR.cpp b/src/coreComponents/linearAlgebra/interfaces/hypre/HypreMGR.cpp index 094baefa0f8..a595f7f6c07 100644 --- a/src/coreComponents/linearAlgebra/interfaces/hypre/HypreMGR.cpp +++ b/src/coreComponents/linearAlgebra/interfaces/hypre/HypreMGR.cpp @@ -38,6 +38,7 @@ #include "linearAlgebra/interfaces/hypre/mgrStrategies/ThermalCompositionalMultiphaseFVM.hpp" #include "linearAlgebra/interfaces/hypre/mgrStrategies/ThermalSinglePhasePoromechanics.hpp" #include "linearAlgebra/interfaces/hypre/mgrStrategies/ThermalMultiphasePoromechanics.hpp" +#include "linearAlgebra/interfaces/hypre/mgrStrategies/SolidMechanicsEmbeddedFractures.hpp" #include "LvArray/src/output.hpp" @@ -168,6 +169,11 @@ void hypre::mgr::createMGR( LinearSolverParameters const & params, setStrategy< SinglePhaseReservoirHybridFVM >( params.mgr, numComponentsPerField, precond, mgrData ); break; } + case LinearSolverParameters::MGR::StrategyType::solidMechanicsEmbeddedFractures: + { + setStrategy< SolidMechanicsEmbeddedFractures >( params.mgr, numComponentsPerField, precond, mgrData ); + break; + } default: { GEOS_ERROR( "Unsupported MGR strategy: " << params.mgr.strategy ); diff --git a/src/coreComponents/linearAlgebra/interfaces/hypre/HypreMatrix.cpp b/src/coreComponents/linearAlgebra/interfaces/hypre/HypreMatrix.cpp index c28b5a23985..c45c30a10b5 100644 --- a/src/coreComponents/linearAlgebra/interfaces/hypre/HypreMatrix.cpp +++ b/src/coreComponents/linearAlgebra/interfaces/hypre/HypreMatrix.cpp @@ -1010,8 +1010,10 @@ localIndex HypreMatrix::rowLength( globalIndex const globalRowIndex ) const cudaMemcpy( ia_offd_h, ia_offd + localRow, 2 * sizeof( HYPRE_Int ), cudaMemcpyDeviceToHost ); #elif GEOS_USE_HYPRE_DEVICE == GEOS_USE_HYPRE_HIP // Don't know if this is faster or slower than launching a kernel. We should deprecate this function in any case. - hipMemcpy( ia_diag_h, ia_diag + localRow, 2 * sizeof( HYPRE_Int ), hipMemcpyDeviceToHost ); - hipMemcpy( ia_offd_h, ia_offd + localRow, 2 * sizeof( HYPRE_Int ), hipMemcpyDeviceToHost ); + hipError_t err = hipMemcpy( ia_diag_h, ia_diag + localRow, 2 * sizeof( HYPRE_Int ), hipMemcpyDeviceToHost ); + GEOS_ERROR_IF( err != 0, GEOS_FMT( "{}", hipGetErrorString( err ) ) ); + err = hipMemcpy( ia_offd_h, ia_offd + localRow, 2 * sizeof( HYPRE_Int ), hipMemcpyDeviceToHost ); + GEOS_ERROR_IF( err != 0, GEOS_FMT( "{}", hipGetErrorString( err ) ) ); #else ia_diag_h[0] = ia_diag[localRow]; ia_diag_h[1] = ia_diag[localRow + 1]; ia_offd_h[0] = ia_offd[localRow]; ia_offd_h[1] = ia_offd[localRow + 1]; diff --git a/src/coreComponents/linearAlgebra/interfaces/hypre/mgrStrategies/CompositionalMultiphaseFVM.hpp b/src/coreComponents/linearAlgebra/interfaces/hypre/mgrStrategies/CompositionalMultiphaseFVM.hpp index 46ce449a883..93ae07276c7 100644 --- a/src/coreComponents/linearAlgebra/interfaces/hypre/mgrStrategies/CompositionalMultiphaseFVM.hpp +++ b/src/coreComponents/linearAlgebra/interfaces/hypre/mgrStrategies/CompositionalMultiphaseFVM.hpp @@ -102,8 +102,6 @@ class CompositionalMultiphaseFVM : public MGRStrategyBase< 2 > GEOS_LAI_CHECK_ERROR( HYPRE_MGRSetLevelSmoothType( precond.ptr, m_levelSmoothType ) ); GEOS_LAI_CHECK_ERROR( HYPRE_MGRSetLevelSmoothIters( precond.ptr, m_levelSmoothIters ) ); - GEOS_LAI_CHECK_ERROR( HYPRE_MGRSetTruncateCoarseGridThreshold( precond.ptr, 1e-20 )); // truncate intermediate/coarse grids - #if GEOS_USE_HYPRE_DEVICE == GEOS_USE_HYPRE_CUDA || GEOS_USE_HYPRE_DEVICE == GEOS_USE_HYPRE_HIP GEOS_LAI_CHECK_ERROR( HYPRE_MGRSetRelaxType( precond.ptr, getAMGRelaxationType( LinearSolverParameters::AMG::SmootherType::l1jacobi ) ) ); #endif diff --git a/src/coreComponents/linearAlgebra/interfaces/hypre/mgrStrategies/CompositionalMultiphaseReservoirFVM.hpp b/src/coreComponents/linearAlgebra/interfaces/hypre/mgrStrategies/CompositionalMultiphaseReservoirFVM.hpp index 2f4b90ca1de..6e09fbca559 100644 --- a/src/coreComponents/linearAlgebra/interfaces/hypre/mgrStrategies/CompositionalMultiphaseReservoirFVM.hpp +++ b/src/coreComponents/linearAlgebra/interfaces/hypre/mgrStrategies/CompositionalMultiphaseReservoirFVM.hpp @@ -118,8 +118,6 @@ class CompositionalMultiphaseReservoirFVM : public MGRStrategyBase< 3 > GEOS_LAI_CHECK_ERROR( HYPRE_MGRSetNonCpointsToFpoints( precond.ptr, 1 )); GEOS_LAI_CHECK_ERROR( HYPRE_MGRSetLevelSmoothType( precond.ptr, m_levelSmoothType ) ); GEOS_LAI_CHECK_ERROR( HYPRE_MGRSetLevelSmoothIters( precond.ptr, m_levelSmoothIters ) ); - GEOS_LAI_CHECK_ERROR( HYPRE_MGRSetTruncateCoarseGridThreshold( precond.ptr, 1e-20 )); // Low tolerance to remove only zeros - // if the wells are shut, using Gaussian elimination as F-relaxation for the well block is an overkill // in that case, we just use Jacobi if( mgrParams.areWellsShut ) diff --git a/src/coreComponents/linearAlgebra/interfaces/hypre/mgrStrategies/CompositionalMultiphaseReservoirHybridFVM.hpp b/src/coreComponents/linearAlgebra/interfaces/hypre/mgrStrategies/CompositionalMultiphaseReservoirHybridFVM.hpp index b6e6b3b6017..d8f8491f38a 100644 --- a/src/coreComponents/linearAlgebra/interfaces/hypre/mgrStrategies/CompositionalMultiphaseReservoirHybridFVM.hpp +++ b/src/coreComponents/linearAlgebra/interfaces/hypre/mgrStrategies/CompositionalMultiphaseReservoirHybridFVM.hpp @@ -134,7 +134,7 @@ class CompositionalMultiphaseReservoirHybridFVM : public MGRStrategyBase< 4 > GEOS_LAI_CHECK_ERROR( HYPRE_MGRSetLevelRestrictType( precond.ptr, toUnderlyingPtr( m_levelRestrictType ) ) ); GEOS_LAI_CHECK_ERROR( HYPRE_MGRSetCoarseGridMethod( precond.ptr, toUnderlyingPtr( m_levelCoarseGridMethod ) ) ); GEOS_LAI_CHECK_ERROR( HYPRE_MGRSetNonCpointsToFpoints( precond.ptr, 1 )); - GEOS_LAI_CHECK_ERROR( HYPRE_MGRSetTruncateCoarseGridThreshold( precond.ptr, 1e-20 )); + GEOS_LAI_CHECK_ERROR( HYPRE_MGRSetLevelSmoothType( precond.ptr, m_levelSmoothType ) ); GEOS_LAI_CHECK_ERROR( HYPRE_MGRSetLevelSmoothIters( precond.ptr, m_levelSmoothIters ) ); diff --git a/src/coreComponents/linearAlgebra/interfaces/hypre/mgrStrategies/Hydrofracture.hpp b/src/coreComponents/linearAlgebra/interfaces/hypre/mgrStrategies/Hydrofracture.hpp index 3f707dc5306..665b4c6c4e2 100644 --- a/src/coreComponents/linearAlgebra/interfaces/hypre/mgrStrategies/Hydrofracture.hpp +++ b/src/coreComponents/linearAlgebra/interfaces/hypre/mgrStrategies/Hydrofracture.hpp @@ -92,7 +92,7 @@ class Hydrofracture : public MGRStrategyBase< 1 > GEOS_LAI_CHECK_ERROR( HYPRE_BoomerAMGSetPrintLevel( mgrData.coarseSolver.ptr, 0 ) ); GEOS_LAI_CHECK_ERROR( HYPRE_BoomerAMGSetMaxIter( mgrData.coarseSolver.ptr, 1 ) ); GEOS_LAI_CHECK_ERROR( HYPRE_BoomerAMGSetTol( mgrData.coarseSolver.ptr, 0.0 ) ); - GEOS_LAI_CHECK_ERROR( HYPRE_BoomerAMGSetMaxLevels( mgrData.coarseSolver.ptr, 25 ) ); + GEOS_LAI_CHECK_ERROR( HYPRE_BoomerAMGSetMinCoarseSize( mgrData.coarseSolver.ptr, 1000 ) ); #if GEOS_USE_HYPRE_DEVICE == GEOS_USE_HYPRE_CUDA || GEOS_USE_HYPRE_DEVICE == GEOS_USE_HYPRE_HIP GEOS_LAI_CHECK_ERROR( HYPRE_BoomerAMGSetPrintLevel( mgrData.coarseSolver.ptr, 0 ) ); GEOS_LAI_CHECK_ERROR( HYPRE_BoomerAMGSetCoarsenType( mgrData.coarseSolver.ptr, toUnderlying( AMGCoarseningType::PMIS ) ) ); diff --git a/src/coreComponents/linearAlgebra/interfaces/hypre/mgrStrategies/ReactiveCompositionalMultiphaseOBL.hpp b/src/coreComponents/linearAlgebra/interfaces/hypre/mgrStrategies/ReactiveCompositionalMultiphaseOBL.hpp index dfe98a08f92..854c20eb942 100644 --- a/src/coreComponents/linearAlgebra/interfaces/hypre/mgrStrategies/ReactiveCompositionalMultiphaseOBL.hpp +++ b/src/coreComponents/linearAlgebra/interfaces/hypre/mgrStrategies/ReactiveCompositionalMultiphaseOBL.hpp @@ -96,7 +96,6 @@ class ReactiveCompositionalMultiphaseOBL : public MGRStrategyBase< 1 > GEOS_LAI_CHECK_ERROR( HYPRE_MGRSetNonCpointsToFpoints( precond.ptr, 1 )); GEOS_LAI_CHECK_ERROR( HYPRE_MGRSetLevelSmoothType( precond.ptr, m_levelSmoothType ) ); GEOS_LAI_CHECK_ERROR( HYPRE_MGRSetLevelSmoothIters( precond.ptr, m_levelSmoothIters ) ); - GEOS_LAI_CHECK_ERROR( HYPRE_MGRSetTruncateCoarseGridThreshold( precond.ptr, 1e-20 )); // truncate intermediate/coarse grids // Note: uncommenting HYPRE_MGRSetLevelFRelaxMethod and commenting HYPRE_MGRSetRelaxType breaks the recipe. This requires further // investigation diff --git a/src/coreComponents/linearAlgebra/interfaces/hypre/mgrStrategies/SinglePhasePoromechanicsEmbeddedFractures.hpp b/src/coreComponents/linearAlgebra/interfaces/hypre/mgrStrategies/SinglePhasePoromechanicsEmbeddedFractures.hpp index 8af1c7c2359..99f70e6a83c 100644 --- a/src/coreComponents/linearAlgebra/interfaces/hypre/mgrStrategies/SinglePhasePoromechanicsEmbeddedFractures.hpp +++ b/src/coreComponents/linearAlgebra/interfaces/hypre/mgrStrategies/SinglePhasePoromechanicsEmbeddedFractures.hpp @@ -57,10 +57,35 @@ class SinglePhasePoromechanicsEmbeddedFractures : public MGRStrategyBase< 2 > explicit SinglePhasePoromechanicsEmbeddedFractures( arrayView1d< int const > const & ) : MGRStrategyBase( 7 ) { - // we keep u and p - m_labels[0].push_back( 0 ); - m_labels[0].push_back( 1 ); - m_labels[0].push_back( 2 ); + + /// IDEAL strategy but it seg faults + // // we keep u and p + // m_labels[0].push_back( 0 ); + // m_labels[0].push_back( 1 ); + // m_labels[0].push_back( 2 ); + // m_labels[0].push_back( 6 ); + // // we keep p + // m_labels[1].push_back( 6 ); + + // setupLabels(); + + // // Level 0 + // m_levelFRelaxMethod[0] = MGRFRelaxationMethod::singleLevel; + // m_levelFRelaxType[0] = MGRFRelaxationType::gsElimWInverse; // gaussian elimination for the dispJump block + // m_levelInterpType[0] = MGRInterpolationType::blockJacobi; + // m_levelRestrictType[0] = MGRRestrictionType::injection; + // m_levelCoarseGridMethod[0] = MGRCoarseGridMethod::galerkin; + + // // Level 1 + // m_levelFRelaxMethod[1] = MGRFRelaxationMethod::amgVCycle; + // m_levelInterpType[1] = MGRInterpolationType::jacobi; + // m_levelRestrictType[1] = MGRRestrictionType::injection; + // m_levelCoarseGridMethod[1] = MGRCoarseGridMethod::nonGalerkin; + + // we keep w and p + m_labels[0].push_back( 3 ); + m_labels[0].push_back( 4 ); + m_labels[0].push_back( 5 ); m_labels[0].push_back( 6 ); // we keep p m_labels[1].push_back( 6 ); @@ -68,16 +93,17 @@ class SinglePhasePoromechanicsEmbeddedFractures : public MGRStrategyBase< 2 > setupLabels(); // Level 0 - m_levelFRelaxMethod[0] = MGRFRelaxationMethod::singleLevel; - m_levelInterpType[0] = MGRInterpolationType::blockJacobi; + m_levelFRelaxMethod[0] = MGRFRelaxationMethod::amgVCycle; + m_levelInterpType[0] = MGRInterpolationType::jacobi; m_levelRestrictType[0] = MGRRestrictionType::injection; - m_levelCoarseGridMethod[0] = MGRCoarseGridMethod::galerkin; + m_levelCoarseGridMethod[0] = MGRCoarseGridMethod::nonGalerkin; // Level 1 - m_levelFRelaxMethod[1] = MGRFRelaxationMethod::amgVCycle; - m_levelInterpType[1] = MGRInterpolationType::jacobi; + m_levelFRelaxMethod[1] = MGRFRelaxationMethod::singleLevel; + m_levelFRelaxType[1] = MGRFRelaxationType::gsElimWInverse; // gaussian elimination for the dispJump block + m_levelInterpType[1] = MGRInterpolationType::blockJacobi; m_levelRestrictType[1] = MGRRestrictionType::injection; - m_levelCoarseGridMethod[1] = MGRCoarseGridMethod::nonGalerkin; + m_levelCoarseGridMethod[1] = MGRCoarseGridMethod::galerkin; m_numGlobalSmoothSweeps = 0; } @@ -97,6 +123,7 @@ class SinglePhasePoromechanicsEmbeddedFractures : public MGRStrategyBase< 2 > mgrData.pointMarkers.data() ) ); GEOS_LAI_CHECK_ERROR( HYPRE_MGRSetLevelFRelaxMethod( precond.ptr, toUnderlyingPtr( m_levelFRelaxMethod ) ) ); + GEOS_LAI_CHECK_ERROR( HYPRE_MGRSetLevelFRelaxType( precond.ptr, toUnderlyingPtr( m_levelFRelaxType ) )); GEOS_LAI_CHECK_ERROR( HYPRE_MGRSetLevelInterpType( precond.ptr, toUnderlyingPtr( m_levelInterpType ) ) ); GEOS_LAI_CHECK_ERROR( HYPRE_MGRSetLevelRestrictType( precond.ptr, toUnderlyingPtr( m_levelRestrictType ) ) ); GEOS_LAI_CHECK_ERROR( HYPRE_MGRSetCoarseGridMethod( precond.ptr, toUnderlyingPtr( m_levelCoarseGridMethod ) ) ); @@ -104,6 +131,7 @@ class SinglePhasePoromechanicsEmbeddedFractures : public MGRStrategyBase< 2 > GEOS_LAI_CHECK_ERROR( HYPRE_MGRSetPMaxElmts( precond.ptr, 0 )); GEOS_LAI_CHECK_ERROR( HYPRE_MGRSetMaxGlobalSmoothIters( precond.ptr, m_numGlobalSmoothSweeps ) ); + GEOS_LAI_CHECK_ERROR( HYPRE_BoomerAMGCreate( &mgrData.coarseSolver.ptr ) ); GEOS_LAI_CHECK_ERROR( HYPRE_BoomerAMGSetPrintLevel( mgrData.coarseSolver.ptr, 0 ) ); GEOS_LAI_CHECK_ERROR( HYPRE_BoomerAMGSetMaxIter( mgrData.coarseSolver.ptr, 1 ) ); diff --git a/src/coreComponents/linearAlgebra/interfaces/hypre/mgrStrategies/SolidMechanicsEmbeddedFractures.hpp b/src/coreComponents/linearAlgebra/interfaces/hypre/mgrStrategies/SolidMechanicsEmbeddedFractures.hpp new file mode 100644 index 00000000000..0d341f4198c --- /dev/null +++ b/src/coreComponents/linearAlgebra/interfaces/hypre/mgrStrategies/SolidMechanicsEmbeddedFractures.hpp @@ -0,0 +1,118 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2018-2020 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2020 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2018-2020 TotalEnergies + * Copyright (c) 2019- GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +/** + * @file SolidMechanicsEmbeddedFractures.hpp + */ + +#ifndef GEOS_LINEARALGEBRA_INTERFACES_HYPREMGRSOLIDMECHANICSEMBEDDEDFRACTURES_HPP_ +#define GEOS_LINEARALGEBRA_INTERFACES_HYPREMGRSOLIDMECHANICSEMBEDDEDFRACTURES_HPP_ + +#include "linearAlgebra/interfaces/hypre/HypreMGR.hpp" + +namespace geos +{ + +namespace hypre +{ + +namespace mgr +{ + +/** + * @brief SolidmechanicsEmbeddedFractures strategy. + * + * dofLabel: 0 = displacement, x-component + * dofLabel: 1 = displacement, y-component + * dofLabel: 2 = displacement, z-component + * dofLabel: 3 = wn + * dofLabel: 4 = wt1 + * dofLabel: 5 = wt2 + * + * Ingredients: + * 1. F-points w (3 - 4 - 5), C-points displacement (0 - 1 - 2) + * 2. F-points smoother: gaussian elimination + * 3. C-points coarse-grid/Schur complement solver: boomer AMG + * 4. Global smoother: none + */ +class SolidMechanicsEmbeddedFractures : public MGRStrategyBase< 1 > +{ +public: + + /** + * @brief Constructor. + */ + explicit SolidMechanicsEmbeddedFractures( arrayView1d< int const > const & ) + : MGRStrategyBase( 6 ) + { + // we keep u + m_labels[0].push_back( 0 ); + m_labels[0].push_back( 1 ); + m_labels[0].push_back( 2 ); + + setupLabels(); + + // Level 0 + m_levelFRelaxMethod[0] = MGRFRelaxationMethod::singleLevel; + m_levelFRelaxType[0] = MGRFRelaxationType::gsElimWInverse; // gaussian elimination for the dispJump block + m_levelInterpType[0] = MGRInterpolationType::blockJacobi; + m_levelRestrictType[0] = MGRRestrictionType::injection; + m_levelCoarseGridMethod[0] = MGRCoarseGridMethod::galerkin; + + m_numGlobalSmoothSweeps = 0; + } + + /** + * @brief Setup the MGR strategy. + * @param precond preconditioner wrapper + * @param mgrData auxiliary MGR data + */ + void setup( LinearSolverParameters::MGR const &, + HyprePrecWrapper & precond, + HypreMGRData & mgrData ) + { + GEOS_LAI_CHECK_ERROR( HYPRE_MGRSetCpointsByPointMarkerArray( precond.ptr, + m_numBlocks, numLevels, + m_numLabels, m_ptrLabels, + mgrData.pointMarkers.data() ) ); + + GEOS_LAI_CHECK_ERROR( HYPRE_MGRSetLevelFRelaxMethod( precond.ptr, toUnderlyingPtr( m_levelFRelaxMethod ) ) ); + GEOS_LAI_CHECK_ERROR( HYPRE_MGRSetLevelInterpType( precond.ptr, toUnderlyingPtr( m_levelInterpType ) ) ); + GEOS_LAI_CHECK_ERROR( HYPRE_MGRSetLevelRestrictType( precond.ptr, toUnderlyingPtr( m_levelRestrictType ) ) ); + GEOS_LAI_CHECK_ERROR( HYPRE_MGRSetCoarseGridMethod( precond.ptr, toUnderlyingPtr( m_levelCoarseGridMethod ) ) ); + GEOS_LAI_CHECK_ERROR( HYPRE_MGRSetNonCpointsToFpoints( precond.ptr, 1 )); + GEOS_LAI_CHECK_ERROR( HYPRE_MGRSetPMaxElmts( precond.ptr, 0 )); + GEOS_LAI_CHECK_ERROR( HYPRE_MGRSetMaxGlobalSmoothIters( precond.ptr, m_numGlobalSmoothSweeps ) ); + + GEOS_LAI_CHECK_ERROR( HYPRE_BoomerAMGCreate( &mgrData.coarseSolver.ptr ) ); + GEOS_LAI_CHECK_ERROR( HYPRE_BoomerAMGSetPrintLevel( mgrData.coarseSolver.ptr, 0 ) ); + GEOS_LAI_CHECK_ERROR( HYPRE_BoomerAMGSetMaxIter( mgrData.coarseSolver.ptr, 1 ) ); + GEOS_LAI_CHECK_ERROR( HYPRE_BoomerAMGSetTol( mgrData.coarseSolver.ptr, 0.0 ) ); + GEOS_LAI_CHECK_ERROR( HYPRE_BoomerAMGSetStrongThreshold( mgrData.coarseSolver.ptr, 0.8 ) ); + GEOS_LAI_CHECK_ERROR( HYPRE_BoomerAMGSetRelaxOrder( mgrData.coarseSolver.ptr, 1 ) ); + GEOS_LAI_CHECK_ERROR( HYPRE_BoomerAMGSetNumFunctions( mgrData.coarseSolver.ptr, 3 ) ); + + mgrData.coarseSolver.setup = HYPRE_BoomerAMGSetup; + mgrData.coarseSolver.solve = HYPRE_BoomerAMGSolve; + mgrData.coarseSolver.destroy = HYPRE_BoomerAMGDestroy; + } +}; + +} // namespace mgr + +} // namespace hypre + +} // namespace geos + +#endif /*GEOS_LINEARALGEBRA_INTERFACES_HYPREMGRSOLIDMECHANICSEMBEDDEDFRACTURES_HPP_*/ diff --git a/src/coreComponents/linearAlgebra/interfaces/hypre/mgrStrategies/ThermalCompositionalMultiphaseFVM.hpp b/src/coreComponents/linearAlgebra/interfaces/hypre/mgrStrategies/ThermalCompositionalMultiphaseFVM.hpp index be617fbba95..16454c14280 100644 --- a/src/coreComponents/linearAlgebra/interfaces/hypre/mgrStrategies/ThermalCompositionalMultiphaseFVM.hpp +++ b/src/coreComponents/linearAlgebra/interfaces/hypre/mgrStrategies/ThermalCompositionalMultiphaseFVM.hpp @@ -106,8 +106,6 @@ class ThermalCompositionalMultiphaseFVM : public MGRStrategyBase< 2 > GEOS_LAI_CHECK_ERROR( HYPRE_MGRSetLevelSmoothType( precond.ptr, m_levelSmoothType ) ); GEOS_LAI_CHECK_ERROR( HYPRE_MGRSetLevelSmoothIters( precond.ptr, m_levelSmoothIters ) ); - GEOS_LAI_CHECK_ERROR( HYPRE_MGRSetTruncateCoarseGridThreshold( precond.ptr, 1e-20 )); // truncate intermediate/coarse grids - #if GEOS_USE_HYPRE_DEVICE == GEOS_USE_HYPRE_CUDA || GEOS_USE_HYPRE_DEVICE == GEOS_USE_HYPRE_HIP GEOS_LAI_CHECK_ERROR( HYPRE_MGRSetRelaxType( precond.ptr, getAMGRelaxationType( LinearSolverParameters::AMG::SmootherType::l1jacobi ) ) ); #endif diff --git a/src/coreComponents/linearAlgebra/interfaces/hypre/mgrStrategies/ThermalMultiphasePoromechanics.hpp b/src/coreComponents/linearAlgebra/interfaces/hypre/mgrStrategies/ThermalMultiphasePoromechanics.hpp index 4ed406e1f29..d37e08e2bc9 100644 --- a/src/coreComponents/linearAlgebra/interfaces/hypre/mgrStrategies/ThermalMultiphasePoromechanics.hpp +++ b/src/coreComponents/linearAlgebra/interfaces/hypre/mgrStrategies/ThermalMultiphasePoromechanics.hpp @@ -130,6 +130,9 @@ class ThermalMultiphasePoromechanics : public MGRStrategyBase< 3 > mgrData.coarseSolver.setup = HYPRE_BoomerAMGSetup; mgrData.coarseSolver.solve = HYPRE_BoomerAMGSolve; mgrData.coarseSolver.destroy = HYPRE_BoomerAMGDestroy; + + // Configure the BoomerAMG solver used as F-relaxation for the first level + setMechanicsFSolver( precond, mgrData ); } }; diff --git a/src/coreComponents/linearAlgebra/interfaces/hypre/mgrStrategies/ThermalSinglePhasePoromechanics.hpp b/src/coreComponents/linearAlgebra/interfaces/hypre/mgrStrategies/ThermalSinglePhasePoromechanics.hpp index 5fc08a4574f..b22dae60054 100644 --- a/src/coreComponents/linearAlgebra/interfaces/hypre/mgrStrategies/ThermalSinglePhasePoromechanics.hpp +++ b/src/coreComponents/linearAlgebra/interfaces/hypre/mgrStrategies/ThermalSinglePhasePoromechanics.hpp @@ -101,6 +101,9 @@ class ThermalSinglePhasePoromechanics : public MGRStrategyBase< 1 > mgrData.coarseSolver.setup = HYPRE_BoomerAMGSetup; mgrData.coarseSolver.solve = HYPRE_BoomerAMGSolve; mgrData.coarseSolver.destroy = HYPRE_BoomerAMGDestroy; + + // Configure the BoomerAMG solver used as F-relaxation for the first level + setMechanicsFSolver( precond, mgrData ); } }; diff --git a/src/coreComponents/linearAlgebra/unitTests/CMakeLists.txt b/src/coreComponents/linearAlgebra/unitTests/CMakeLists.txt index 06591fdb73f..e33bec1748e 100644 --- a/src/coreComponents/linearAlgebra/unitTests/CMakeLists.txt +++ b/src/coreComponents/linearAlgebra/unitTests/CMakeLists.txt @@ -6,7 +6,8 @@ set( parallel_tests Matrices Vectors ExternalSolvers - KrylovSolvers ) + KrylovSolvers + ReverseCutHillMcKeeOrdering ) set( nranks 2 ) diff --git a/src/coreComponents/linearAlgebra/unitTests/testReverseCutHillMcKeeOrdering.cpp b/src/coreComponents/linearAlgebra/unitTests/testReverseCutHillMcKeeOrdering.cpp new file mode 100644 index 00000000000..d79d9631f7d --- /dev/null +++ b/src/coreComponents/linearAlgebra/unitTests/testReverseCutHillMcKeeOrdering.cpp @@ -0,0 +1,121 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2018-2020 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2020 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2018-2020 TotalEnergies + * Copyright (c) 2019- GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +/** + * @file testReverseCutHillMcKeeOrdering.cpp + */ + +#include "codingUtilities/UnitTestUtilities.hpp" +#include "linearAlgebra/utilities/ReverseCutHillMcKeeOrdering.hpp" + +#include + +using namespace geos; + +TEST( ReverseCutHillMcKeeOrderingTest, reorder ) +{ + // the data in this test comes from PoroElastic_staircase_co2_3d.xml + // it is extracted of the flow dof numbers (MPI rank 1 in a 2-rank simulation) + + localIndex constexpr numRows = 144; + localIndex constexpr rankOffset = 144; + localIndex constexpr numNonZeros = 876; + + array1d< localIndex > permutation( numRows ); + localIndex const expectedPermutation[numRows] + { 16, 10, 17, 14, 4, 8, 11, 34, 15, 12, 2, 5, 6, 9, 28, 35, 32, 13, + 88, 0, 3, 22, 82, 7, 26, 29, 106, 33, 30, 89, 86, 76, 1, 20, 23, 80, 83, + 24, 27, 100, 107, 104, 31, 52, 87, 84, 74, 77, 18, 21, 94, 78, 81, 46, + 25, 98, 101, 124, 105, 102, 53, 50, 85, 72, 75, 40, 19, 92, 95, 79, 44, + 47, 96, 99, 118, 125, 122, 103, 70, 51, 48, 73, 38, 41, 90, 93, 112, 42, + 45, 64, 97, 116, 119, 123, 120, 71, 68, 49, 36, 39, 58, 91, 110, 113, 43, + 62, 65, 114, 117, 121, 142, 69, 66, 37, 56, 59, 108, 111, 60, 63, 136, + 115, 143, 140, 67, 54, 57, 130, 109, 61, 134, 137, 141, 138, 55, 128, 131, + 132, 135, 139, 126, 129, 133, 127 }; + localIndex const offsets[numRows+1] + { 0, 6, 13, 19, 26, 31, 37, 43, 50, 56, 63, 68, 74, 79, 85, 90, 96, + 100, 105, 112, 119, 126, 133, 139, 145, 152, 159, 166, 173, 179, 185, 191, + 197, 203, 209, 214, 219, 225, 231, 238, 245, 252, 259, 265, 271, 278, 285, + 292, 299, 304, 309, 315, 321, 327, 333, 339, 345, 352, 359, 366, 373, 379, + 385, 392, 399, 406, 413, 418, 423, 429, 435, 441, 447, 452, 458, 464, 471, + 477, 484, 489, 495, 501, 508, 514, 521, 525, 530, 535, 541, 546, 552, 559, + 566, 573, 580, 586, 592, 599, 606, 613, 620, 626, 632, 638, 644, 650, 656, + 661, 666, 673, 679, 686, 692, 698, 703, 710, 716, 723, 729, 735, 740, 746, + 751, 757, 762, 767, 771, 777, 782, 789, 795, 802, 808, 814, 819, 826, 832, + 839, 845, 850, 854, 860, 865, 871, 876 }; + globalIndex const columns[numNonZeros+1] + { 12, 144, 145, 146, 150, 220, 13, 144, 145, 147, 151, 162, 221, 14, 144, 146, + 147, 148, 152, 15, 145, 146, 147, 149, 153, 164, 16, 146, 148, 149, 154, 17, + 147, 148, 149, 155, 166, 144, 150, 151, 152, 156, 226, 145, 150, 151, 153, 157, + 168, 227, 146, 150, 152, 153, 154, 158, 147, 151, 152, 153, 155, 159, 170, 148, + 152, 154, 155, 160, 149, 153, 154, 155, 161, 172, 150, 156, 157, 158, 232, 151, + 156, 157, 159, 174, 233, 152, 156, 158, 159, 160, 153, 157, 158, 159, 161, 176, + 154, 158, 160, 161, 155, 159, 160, 161, 178, 120, 145, 162, 163, 164, 168, 184, + 121, 162, 163, 165, 169, 185, 234, 122, 147, 162, 164, 165, 166, 170, 123, 163, + 164, 165, 167, 171, 236, 124, 149, 164, 166, 167, 172, 125, 165, 166, 167, 173, + 238, 151, 162, 168, 169, 170, 174, 190, 163, 168, 169, 171, 175, 191, 240, 153, + 164, 168, 170, 171, 172, 176, 165, 169, 170, 171, 173, 177, 242, 155, 166, 170, + 172, 173, 178, 167, 171, 172, 173, 179, 244, 157, 168, 174, 175, 176, 196, 169, + 174, 175, 177, 197, 246, 159, 170, 174, 176, 177, 178, 171, 175, 176, 177, 179, + 248, 161, 172, 176, 178, 179, 173, 177, 178, 179, 250, 102, 180, 181, 182, 186, + 217, 103, 180, 181, 183, 187, 198, 104, 180, 182, 183, 184, 188, 219, 105, 181, + 182, 183, 185, 189, 200, 106, 162, 182, 184, 185, 190, 221, 107, 163, 183, 184, + 185, 191, 202, 180, 186, 187, 188, 192, 223, 181, 186, 187, 189, 193, 204, 182, + 186, 188, 189, 190, 194, 225, 183, 187, 188, 189, 191, 195, 206, 168, 184, 188, + 190, 191, 196, 227, 169, 185, 189, 190, 191, 197, 208, 186, 192, 193, 194, 229, + 187, 192, 193, 195, 210, 188, 192, 194, 195, 196, 231, 189, 193, 194, 195, 197, + 212, 174, 190, 194, 196, 197, 233, 175, 191, 195, 196, 197, 214, 30, 181, 198, + 199, 200, 204, 31, 198, 199, 201, 205, 270, 32, 183, 198, 200, 201, 202, 206, + 33, 199, 200, 201, 203, 207, 272, 34, 185, 200, 202, 203, 208, 234, 35, 201, + 202, 203, 209, 235, 274, 187, 198, 204, 205, 206, 210, 199, 204, 205, 207, 211, + 276, 189, 200, 204, 206, 207, 208, 212, 201, 205, 206, 207, 209, 213, 278, 191, + 202, 206, 208, 209, 214, 240, 203, 207, 208, 209, 215, 241, 280, 193, 204, 210, + 211, 212, 205, 210, 211, 213, 282, 195, 206, 210, 212, 213, 214, 207, 211, 212, + 213, 215, 284, 197, 208, 212, 214, 215, 246, 209, 213, 214, 215, 247, 286, 84, + 216, 217, 218, 222, 85, 180, 216, 217, 219, 223, 86, 216, 218, 219, 220, 224, + 87, 182, 217, 218, 219, 221, 225, 88, 144, 218, 220, 221, 226, 89, 145, 184, + 219, 220, 221, 227, 216, 222, 223, 224, 228, 186, 217, 222, 223, 225, 229, 218, + 222, 224, 225, 226, 230, 188, 219, 223, 224, 225, 227, 231, 150, 220, 224, 226, + 227, 232, 151, 190, 221, 225, 226, 227, 233, 222, 228, 229, 230, 192, 223, 228, + 229, 231, 224, 228, 230, 231, 232, 194, 225, 229, 230, 231, 233, 156, 226, 230, + 232, 233, 157, 196, 227, 231, 232, 233, 138, 163, 202, 234, 235, 236, 240, 139, + 203, 234, 235, 237, 241, 252, 140, 165, 234, 236, 237, 238, 242, 141, 235, 236, + 237, 239, 243, 254, 142, 167, 236, 238, 239, 244, 143, 237, 238, 239, 245, 256, + 169, 208, 234, 240, 241, 242, 246, 209, 235, 240, 241, 243, 247, 258, 171, 236, + 240, 242, 243, 244, 248, 237, 241, 242, 243, 245, 249, 260, 173, 238, 242, 244, + 245, 250, 239, 243, 244, 245, 251, 262, 175, 214, 240, 246, 247, 248, 215, 241, + 246, 247, 249, 264, 177, 242, 246, 248, 249, 250, 243, 247, 248, 249, 251, 266, + 179, 244, 248, 250, 251, 245, 249, 250, 251, 268, 66, 235, 252, 253, 254, 258, + 274, 67, 252, 253, 255, 259, 275, 68, 237, 252, 254, 255, 256, 260, 69, 253, 254, + 255, 257, 261, 70, 239, 254, 256, 257, 262, 71, 255, 256, 257, 263, 241, 252, 258, + 259, 260, 264, 280, 253, 258, 259, 261, 265, 281, 243, 254, 258, 260, 261, 262, + 266, 255, 259, 260, 261, 263, 267, 245, 256, 260, 262, 263, 268, 257, 261, 262, + 263, 269, 247, 258, 264, 265, 266, 286, 259, 264, 265, 267, 287, 249, 260, 264, + 266, 267, 268, 261, 265, 266, 267, 269, 251, 262, 266, 268, 269, 263, 267, 268, + 269, 48, 199, 270, 271, 272, 276, 49, 270, 271, 273, 277, 50, 201, 270, 272, 273, + 274, 278, 51, 271, 272, 273, 275, 279, 52, 203, 252, 272, 274, 275, 280, 53, 253, + 273, 274, 275, 281, 205, 270, 276, 277, 278, 282, 271, 276, 277, 279, 283, 207, + 272, 276, 278, 279, 280, 284, 273, 277, 278, 279, 281, 285, 209, 258, 274, 278, + 280, 281, 286, 259, 275, 279, 280, 281, 287, 211, 276, 282, 283, 284, 277, 282, + 283, 285, 213, 278, 282, 284, 285, 286, 279, 283, 284, 285, 287, 215, 264, 280, + 284, 286, 287, 265, 281, 285, 286, 287 }; + + reverseCutHillMcKeeOrdering:: + computePermutation( offsets, columns, rankOffset, permutation ); + + for( localIndex i = 0; i < numRows; ++i ) + { + EXPECT_EQ( permutation[i], expectedPermutation[i] ); + } +} diff --git a/src/coreComponents/linearAlgebra/utilities/ReverseCutHillMcKeeOrdering.cpp b/src/coreComponents/linearAlgebra/utilities/ReverseCutHillMcKeeOrdering.cpp new file mode 100644 index 00000000000..89b18650cd8 --- /dev/null +++ b/src/coreComponents/linearAlgebra/utilities/ReverseCutHillMcKeeOrdering.cpp @@ -0,0 +1,367 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2018-2020 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2020 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2018-2020 TotalEnergies + * Copyright (c) 2019- GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +/** + * @file ReverseCutHillMcKeeOrdering.hpp + */ + +#include "ReverseCutHillMcKeeOrdering.hpp" +#include "common/GEOS_RAJA_Interface.hpp" + +namespace geos +{ + +namespace reverseCutHillMcKeeOrdering +{ + +/** + * @brief This function finds the unvisited node with the minimum degree + * @param[in] numRows number of rows in the matrix to reorder + * @param[in] degree the degree array + * @param[in] marker the marker array for unvisited node + * @param[out] rootRef reference to the root (= node with the minimum degree) + */ +static void +findNodeWithMinDegree( localIndex const numRows, + arrayView1d< localIndex > const degree, + arrayView1d< localIndex > const marker, + localIndex & rootRef ) +{ + RAJA::ReduceMinLoc< serialReduce, localIndex > minDegree( LvArray::NumericLimits< localIndex >::max, -1 ); + + forAll< serialPolicy >( numRows, [=] ( localIndex const i ) + { + if( marker[i] < 0 ) + { + minDegree.minloc( degree[i], i ); + } + } ); + rootRef = minDegree.getLoc(); +} + +/** + * @brief This function build level structure start from root + * @param[in] offsets row offsets in the matrix to reorder + * @param[in] columns column indices in the matrix to reorder + * @param[in] numRows number of rows in the matrix to reorder + * @param[in] rankOffset offset of this rank (assuming numComps = 1) + * @param[in] root pointer to the root + * @param[out] marker the marker array for unvisited node + * @param[out] level_i points to the start/end of position on level_j, similar to CSR Matrix + * @param[out] level_j store node number on each level + * @param[out] numLevelsRef return the number of level on this level structure + */ +static void +buildLevel( localIndex const * const offsets, + globalIndex const * const columns, + localIndex const numRows, + localIndex const rankOffset, + localIndex const root, + arrayView1d< localIndex > const marker, + arrayView1d< localIndex > const level_i, + arrayView1d< localIndex > const level_j, + localIndex & numLevelsRef ) +{ + localIndex l1 = 0; + localIndex l2 = 0; + localIndex lCurrent = 0; + localIndex r1 = 0; + localIndex r2 = 0; + localIndex row_i = 0; + localIndex row_j = 0; + localIndex numLevels = 0; + + // set first level first + level_i[0] = 0; + level_j[0] = root; + marker[root] = 0; + numLevels = 1; + l1 = 0; + l2 = 1; + lCurrent = l2; + + // explore nbhds of all nodes in current level + while( l2 > l1 ) + { + level_i[numLevels++] = l2; + + // loop through last level + for( localIndex i = l1; i < l2; i++ ) + { + // the node to explore + row_i = level_j[i]; + r1 = offsets[row_i]; + r2 = offsets[row_i + 1]; + for( localIndex j = r1; j < r2; j++ ) + { + row_j = columns[j] - rankOffset; + if( row_j >= 0 && row_j < numRows ) + { + if( marker[row_j] < 0 ) + { + // unmarked row + marker[row_j] = 0; + level_j[lCurrent++] = row_j; + } + } + } + } + l1 = l2; + l2 = lCurrent; + } + // after this we always have a "ghost" last level + numLevels--; + + // reset marker + for( localIndex i = 0; i < l2; i++ ) + { + marker[level_j[i]] = -1; + } + + numLevelsRef = numLevels; +} + + +/** + * @brief This function find a pseudo-peripheral node start from root + * @param[in] offsets row offsets in the matrix to reorder + * @param[in] columns column indices in the matrix to reorder + * @param[in] numRows number of rows in the matrix to reorder + * @param[in] rankOffset offset of this rank (assuming numComps = 1) + * @param[out] rootRef on return will be a end of the pseudo-peripheral + * @param[out] marker the marker array for unvisited node + * @param[out] level_i level structure i + * @param[out] level_j level structure j + */ +static void +findPseudoPeripheralNode( localIndex const * const offsets, + globalIndex const * const columns, + localIndex const numRows, + localIndex const rankOffset, + localIndex & rootRef, + arrayView1d< localIndex > const marker, + arrayView1d< localIndex > const level_i, + arrayView1d< localIndex > const level_j ) +{ + localIndex r1 = 0; + localIndex r2 = 0; + localIndex row = 0; + localIndex minDegree = 0; + localIndex levDegree = 0; + localIndex numLevels = 0; + localIndex newNumLevels = 0; + localIndex root = rootRef; + + level_i.zero(); + level_j.zero(); + + // build initial level structure from root + buildLevel( offsets, columns, numRows, rankOffset, root, + marker, level_i, level_j, newNumLevels ); + + numLevels = newNumLevels - 1; + while( numLevels < newNumLevels ) + { + numLevels = newNumLevels; + r1 = level_i[numLevels - 1]; + r2 = level_i[numLevels]; + minDegree = numRows; + for( localIndex i = r1; i < r2; i++ ) + { + // select the last level, pick min-degree node + row = level_j[i]; + levDegree = offsets[row + 1] - offsets[row]; + if( minDegree > levDegree ) + { + minDegree = levDegree; + root = row; + } + } + buildLevel( offsets, columns, numRows, rankOffset, root, + marker, level_i, level_j, newNumLevels ); + } + rootRef = root; +} + +/** + * @brief This qsort is very specialized, not worth to put into utilities + * Sort a part of array perm based on degree value (ascend) + * That is, if degree[perm[i]] < degree[perm[j]], we should have i < j + * @param[in] perm the perm array + * @param[in] start start in perm + * @param[in] end end in perm + * @param[out] degree degree array + */ +static void +qsort( arrayView1d< localIndex > const perm, + localIndex const start, + localIndex const end, + arrayView1d< localIndex > const degree ) +{ + if( start >= end ) + { + return; + } + + std::swap( perm[start], perm[(start + end) / 2] ); + localIndex mid = start; + + // loop to split + for( localIndex i = start + 1; i <= end; i++ ) + { + if( degree[perm[i]] < degree[perm[start]] ) + { + std::swap( perm[++mid], perm[i] ); + } + } + std::swap( perm[start], perm[mid] ); + qsort( perm, mid + 1, end, degree ); + qsort( perm, start, mid - 1, degree ); +} + +/** + * @brief Last step in RCM, reverse it + * @param[out] perm perm array + * @param[in] start start position + * @param[in] end end position + */ +static void +reversePermutation( arrayView1d< localIndex > const perm, + localIndex const start, + localIndex const end ) +{ + localIndex i = 0; + localIndex j = 0; + localIndex const mid = (start + end + 1) / 2; + + for( i = start, j = end; i < mid; i++, j-- ) + { + std::swap( perm[i], perm[j] ); + } +} + + +/** + * @brief This function generate numbering for a connected component + * @param[in] offsets row offsets in the matrix to reorder + * @param[in] columns column indices in the matrix to reorder + * @param[in] numRows number of rows in the matrix to reorder + * @param[in] rankOffset offset of this rank (assuming numComps = 1) + * @param[in] root pointer to the root + * @param[out] marker the marker array for unvisited node + * @param[out] perm permutation array + * @param[out] currentNumRef number of nodes already have a perm value + */ +static void +computeNewOrdering( localIndex const * const offsets, + globalIndex const * const columns, + localIndex const numRows, + localIndex const rankOffset, + localIndex const root, + arrayView1d< localIndex > const marker, + arrayView1d< localIndex > const perm, + localIndex & currentNumRef ) +{ + localIndex i = 0; + localIndex j = 0; + localIndex l1 = 0; + localIndex l2 = 0; + localIndex r1 = 0; + localIndex r2 = 0; + localIndex row_i = 0; + localIndex row_j = 0; + localIndex rowStart = 0; + localIndex rowEnd = 0; + localIndex currentNum = currentNumRef; + + marker[root] = 0; + l1 = currentNum; + perm[currentNum++] = root; + l2 = currentNum; + + while( l2 > l1 ) + { + // loop through all nodes is current level + for( i = l1; i < l2; i++ ) + { + row_i = perm[i]; + r1 = offsets[row_i]; + r2 = offsets[row_i + 1]; + rowStart = currentNum; + for( j = r1; j < r2; j++ ) + { + row_j = columns[j] - rankOffset; + if( row_j >= 0 && row_j < numRows ) + { + if( marker[row_j] < 0 ) + { + // save the degree in marker and add it to perm */ + marker[row_j] = offsets[row_j + 1] - offsets[row_j]; + perm[currentNum++] = row_j; + } + } + } + rowEnd = currentNum; + qsort( perm, rowStart, rowEnd - 1, marker ); + } + l1 = l2; + l2 = currentNum; + } + + // reverse + reversePermutation( perm, currentNumRef, currentNum - 1 ); + currentNumRef = currentNum; +} + +void +computePermutation( localIndex const * const offsets, + globalIndex const * const columns, + localIndex const rankOffset, + arrayView1d< localIndex > const perm ) +{ + localIndex root = 0; + localIndex currentNum = 0; + localIndex const numRows = perm.size(); + + // get the degree for each node + array1d< localIndex > degree( numRows ); + array1d< localIndex > marker( numRows ); + // at most numRows levels + array1d< localIndex > level_i( numRows+1 ); + array1d< localIndex > level_j( numRows ); + forAll< parallelHostPolicy >( numRows, [&] ( localIndex const i ) + { + degree[i] = offsets[i + 1] - offsets[i]; + marker[i] = -1; + } ); + + // start RCM loop + currentNum = 0; + while( currentNum < numRows ) + { + // Find the unvisited node with the minimum degree + findNodeWithMinDegree( numRows, degree, marker, root ); + + // This is a new connected component + findPseudoPeripheralNode( offsets, columns, numRows, rankOffset, root, marker, level_i, level_j ); + + // Numbering of this component + computeNewOrdering( offsets, columns, numRows, rankOffset, root, marker, perm, currentNum ); + + } +} + +} // end namespace reverseCutHillMcKeeOrdering + +} // end namespace geos diff --git a/src/coreComponents/linearAlgebra/utilities/ReverseCutHillMcKeeOrdering.hpp b/src/coreComponents/linearAlgebra/utilities/ReverseCutHillMcKeeOrdering.hpp new file mode 100644 index 00000000000..fa74a5c5ce4 --- /dev/null +++ b/src/coreComponents/linearAlgebra/utilities/ReverseCutHillMcKeeOrdering.hpp @@ -0,0 +1,49 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2018-2020 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2020 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2018-2020 TotalEnergies + * Copyright (c) 2019- GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +/** + * @file ReverseCutHillMcKeeOrdering.hpp + */ + +#ifndef GEOS_LINEARALGEBRA_UTILITIES_REVERSECUTHILLMCKEEORDERING_HPP_ +#define GEOS_LINEARALGEBRA_UTILITIES_REVERSECUTHILLMCKEEORDERING_HPP_ + +#include "common/DataTypes.hpp" + +namespace geos +{ + +namespace reverseCutHillMcKeeOrdering +{ + +/** + * @brief This function actually does the RCM ordering of a symmetric csr matrix (entire) + The original implementation can be found in src/parcsr_ls/par_ilu.c in hypre + * @param[in] offsets row offsets in the matrix to reorder + * @param[in] columns column indices in the matrix to reorder + * @param[in] rankOffset offset of this rank (assuming numComps = 1) + * @param[out] perm the permutation array + */ +void +computePermutation( localIndex const * const offsets, + globalIndex const * const columns, + localIndex const rankOffset, + arrayView1d< localIndex > const perm ); + + +} // namespace reverseCutHillMcKeeOrdering + +} // namespace geos + +#endif // GEOS_LINEARALGEBRA_UTILITIES_REVERSECUTHILLMCKEEORDERING_HPP_ diff --git a/src/coreComponents/mainInterface/ProblemManager.cpp b/src/coreComponents/mainInterface/ProblemManager.cpp index b7ae6ee751d..6d2b7d81a2b 100644 --- a/src/coreComponents/mainInterface/ProblemManager.cpp +++ b/src/coreComponents/mainInterface/ProblemManager.cpp @@ -547,14 +547,14 @@ void ProblemManager::generateMesh() // setup the base discretizations (hard code this for now) domain.forMeshBodies( [&]( MeshBody & meshBody ) { - CellBlockManagerABC & cellBlockManager = meshBody.getGroup< CellBlockManagerABC >( keys::cellManager ); + CellBlockManagerABC const & cellBlockManager = meshBody.getCellBlockManager(); MeshLevel & baseMesh = meshBody.getBaseDiscretization(); array1d< string > junk; this->generateMeshLevel( baseMesh, cellBlockManager, nullptr, junk.toViewConst() ); ElementRegionManager & elemManager = baseMesh.getElemManager(); - elemManager.generateWells( meshManager, baseMesh ); + elemManager.generateWells( cellBlockManager, baseMesh ); } ); @@ -579,7 +579,7 @@ void ProblemManager::generateMesh() int const order = feDiscretization->getOrder(); string const & discretizationName = feDiscretization->getName(); arrayView1d< string const > const regionNames = discretizationPair.second; - CellBlockManagerABC & cellBlockManager = meshBody.getGroup< CellBlockManagerABC >( keys::cellManager ); + CellBlockManagerABC const & cellBlockManager = meshBody.getCellBlockManager(); // create a high order MeshLevel if( order > 1 ) @@ -618,7 +618,7 @@ void ProblemManager::generateMesh() domain.forMeshBodies( [&]( MeshBody & meshBody ) { - meshBody.deregisterGroup( keys::cellManager ); + meshBody.deregisterCellBlockManager(); meshBody.forMeshLevels( [&]( MeshLevel & meshLevel ) { @@ -708,7 +708,7 @@ ProblemManager::getDiscretizations() const } void ProblemManager::generateMeshLevel( MeshLevel & meshLevel, - CellBlockManagerABC & cellBlockManager, + CellBlockManagerABC const & cellBlockManager, Group const * const discretization, arrayView1d< string const > const & ) { diff --git a/src/coreComponents/mainInterface/ProblemManager.hpp b/src/coreComponents/mainInterface/ProblemManager.hpp index 4076ad754d1..d4acb34b0fd 100644 --- a/src/coreComponents/mainInterface/ProblemManager.hpp +++ b/src/coreComponents/mainInterface/ProblemManager.hpp @@ -339,7 +339,7 @@ class ProblemManager : public dataRepository::Group getDiscretizations() const; void generateMeshLevel( MeshLevel & meshLevel, - CellBlockManagerABC & cellBlockManager, + CellBlockManagerABC const & cellBlockManager, Group const * const discretization, arrayView1d< string const > const & targetRegions ); diff --git a/src/coreComponents/mainInterface/initialization.cpp b/src/coreComponents/mainInterface/initialization.cpp index 90db7686ce6..f1d7410295d 100644 --- a/src/coreComponents/mainInterface/initialization.cpp +++ b/src/coreComponents/mainInterface/initialization.cpp @@ -39,6 +39,7 @@ struct Arg : public option::Arg */ static option::ArgStatus unknown( option::Option const & option, bool ) { + GEOS_UNUSED_VAR( option ); // unused if geos_error_if is nulld GEOS_LOG_RANK( "Unknown option: " << option.name ); return option::ARG_ILLEGAL; } diff --git a/src/coreComponents/mesh/CMakeLists.txt b/src/coreComponents/mesh/CMakeLists.txt index 6d97acc5e41..27576fc54f2 100644 --- a/src/coreComponents/mesh/CMakeLists.txt +++ b/src/coreComponents/mesh/CMakeLists.txt @@ -36,6 +36,8 @@ set( mesh_headers generators/CellBlockManager.hpp generators/CellBlockManagerABC.hpp generators/CellBlockUtilities.hpp + generators/LineBlock.hpp + generators/LineBlockABC.hpp generators/ExternalMeshGeneratorBase.hpp generators/FaceBlock.hpp generators/FaceBlockABC.hpp @@ -44,7 +46,9 @@ set( mesh_headers generators/InternalWellboreGenerator.hpp generators/MeshGeneratorBase.hpp generators/ParMETISInterface.hpp + generators/PartitionDescriptor.hpp generators/PrismUtilities.hpp + generators/WellGeneratorBase.hpp mpiCommunications/CommID.hpp mpiCommunications/CommunicationTools.hpp mpiCommunications/MPI_iCommData.hpp @@ -52,12 +56,16 @@ set( mesh_headers mpiCommunications/NeighborData.hpp mpiCommunications/PartitionBase.hpp mpiCommunications/SpatialPartition.hpp - simpleGeometricObjects/BoundedPlane.hpp + simpleGeometricObjects/Rectangle.hpp + simpleGeometricObjects/Disc.hpp + simpleGeometricObjects/CustomPolarObject.hpp simpleGeometricObjects/Box.hpp simpleGeometricObjects/Cylinder.hpp simpleGeometricObjects/GeometricObjectManager.hpp simpleGeometricObjects/SimpleGeometricObjectBase.hpp + simpleGeometricObjects/PlanarGeometricObject.hpp simpleGeometricObjects/ThickPlane.hpp + utilities/AverageOverQuadraturePointsKernel.hpp utilities/CIcomputationKernel.hpp utilities/ComputationalGeometry.hpp utilities/MeshMapUtilities.hpp @@ -94,6 +102,7 @@ set( mesh_sources generators/CellBlock.cpp generators/CellBlockManager.cpp generators/CellBlockUtilities.cpp + generators/LineBlock.cpp generators/ExternalMeshGeneratorBase.cpp generators/FaceBlock.cpp generators/InternalMeshGenerator.cpp @@ -101,17 +110,21 @@ set( mesh_sources generators/InternalWellboreGenerator.cpp generators/MeshGeneratorBase.cpp generators/ParMETISInterface.cpp + generators/WellGeneratorBase.cpp mpiCommunications/CommID.cpp mpiCommunications/CommunicationTools.cpp mpiCommunications/MPI_iCommData.cpp mpiCommunications/NeighborCommunicator.cpp mpiCommunications/PartitionBase.cpp mpiCommunications/SpatialPartition.cpp - simpleGeometricObjects/BoundedPlane.cpp + simpleGeometricObjects/Rectangle.cpp + simpleGeometricObjects/Disc.cpp + simpleGeometricObjects/CustomPolarObject.cpp simpleGeometricObjects/Box.cpp simpleGeometricObjects/Cylinder.cpp simpleGeometricObjects/GeometricObjectManager.cpp simpleGeometricObjects/SimpleGeometricObjectBase.cpp + simpleGeometricObjects/PlanarGeometricObject.cpp simpleGeometricObjects/ThickPlane.cpp utilities/ComputationalGeometry.cpp ) diff --git a/src/coreComponents/mesh/CellElementRegion.cpp b/src/coreComponents/mesh/CellElementRegion.cpp index 54caf947302..a1921b1f46f 100644 --- a/src/coreComponents/mesh/CellElementRegion.cpp +++ b/src/coreComponents/mesh/CellElementRegion.cpp @@ -13,6 +13,7 @@ */ #include "CellElementRegion.hpp" +#include "CellElementSubRegion.hpp" #include "mesh/generators/CellBlockABC.hpp" namespace geos @@ -32,14 +33,14 @@ CellElementRegion::CellElementRegion( string const & name, Group * const parent CellElementRegion::~CellElementRegion() {} -void CellElementRegion::generateMesh( Group & cellBlocks ) +void CellElementRegion::generateMesh( Group const & cellBlocks ) { Group & elementSubRegions = this->getGroup( viewKeyStruct::elementSubRegions() ); for( string const & cellBlockName : this->m_cellBlockNames ) { CellElementSubRegion & subRegion = elementSubRegions.registerGroup< CellElementSubRegion >( cellBlockName ); - CellBlockABC & source = cellBlocks.getGroup< CellBlockABC >( subRegion.getName() ); + CellBlockABC const & source = cellBlocks.getGroup< CellBlockABC >( subRegion.getName() ); subRegion.copyFromCellBlock( source ); } } diff --git a/src/coreComponents/mesh/CellElementRegion.hpp b/src/coreComponents/mesh/CellElementRegion.hpp index ff5e216966a..130423b42bc 100644 --- a/src/coreComponents/mesh/CellElementRegion.hpp +++ b/src/coreComponents/mesh/CellElementRegion.hpp @@ -113,7 +113,7 @@ class CellElementRegion : public ElementRegionBase } } - virtual void generateMesh( Group & cellBlocks ) override; + virtual void generateMesh( Group const & cellBlocks ) override; ///@} diff --git a/src/coreComponents/mesh/CellElementSubRegion.cpp b/src/coreComponents/mesh/CellElementSubRegion.cpp index 7732e137249..550b3614871 100644 --- a/src/coreComponents/mesh/CellElementSubRegion.cpp +++ b/src/coreComponents/mesh/CellElementSubRegion.cpp @@ -66,7 +66,7 @@ void CellElementSubRegion::resizePerElementValues( localIndex const newNumNodesP } -void CellElementSubRegion::copyFromCellBlock( CellBlockABC & cellBlock ) +void CellElementSubRegion::copyFromCellBlock( CellBlockABC const & cellBlock ) { // Defines the (unique) element type of this cell element region, // and its associated number of nodes, edges, faces. @@ -90,14 +90,14 @@ void CellElementSubRegion::copyFromCellBlock( CellBlockABC & cellBlock ) this->m_localToGlobalMap = cellBlock.localToGlobalMap(); this->constructGlobalToLocalMap(); - cellBlock.forExternalProperties( [&]( WrapperBase & wrapper ) + cellBlock.forExternalProperties( [&]( WrapperBase const & wrapper ) { - types::dispatch( types::StandardArrays{}, wrapper.getTypeId(), true, [&]( auto array ) + types::dispatch( types::ListofTypeList< types::StandardArrays >{}, [&]( auto tupleOfTypes ) { - using ArrayType = decltype( array ); - Wrapper< ArrayType > & wrapperT = Wrapper< ArrayType >::cast( wrapper ); - this->registerWrapper( wrapper.getName(), std::make_unique< ArrayType >( wrapperT.reference() ) ); - } ); + using ArrayType = camp::first< decltype( tupleOfTypes ) >; + auto const src = Wrapper< ArrayType >::cast( wrapper ).reference().toViewConst(); + this->registerWrapper( wrapper.getName(), std::make_unique< ArrayType >( &src ) ); + }, wrapper ); } ); } diff --git a/src/coreComponents/mesh/CellElementSubRegion.hpp b/src/coreComponents/mesh/CellElementSubRegion.hpp index 98672230b16..37e6c3537e2 100644 --- a/src/coreComponents/mesh/CellElementSubRegion.hpp +++ b/src/coreComponents/mesh/CellElementSubRegion.hpp @@ -82,7 +82,7 @@ class CellElementSubRegion : public ElementSubRegionBase * @brief Fill the CellElementSubRegion by copying those of the source CellBlock * @param cellBlock the CellBlock which properties (connectivity info) will be copied. */ - void copyFromCellBlock( CellBlockABC & cellBlock ); + void copyFromCellBlock( CellBlockABC const & cellBlock ); ///@} diff --git a/src/coreComponents/mesh/DomainPartition.cpp b/src/coreComponents/mesh/DomainPartition.cpp index 4fe5ce56fe3..23c0e51366c 100644 --- a/src/coreComponents/mesh/DomainPartition.cpp +++ b/src/coreComponents/mesh/DomainPartition.cpp @@ -79,17 +79,18 @@ void DomainPartition::setupBaseLevelMeshGlobalInfo() GEOS_MARK_FUNCTION; #if defined(GEOSX_USE_MPI) + PartitionBase & partition1 = getReference< PartitionBase >( keys::partitionManager ); + SpatialPartition & partition = dynamic_cast< SpatialPartition & >(partition1); - if( m_metisNeighborList.empty() ) + const std::set< int > metisNeighborList = partition.getMetisNeighborList(); + if( metisNeighborList.empty() ) { - PartitionBase & partition1 = getReference< PartitionBase >( keys::partitionManager ); - SpatialPartition & partition = dynamic_cast< SpatialPartition & >(partition1); //get communicator, rank, and coordinates MPI_Comm cartcomm; { int reorder = 0; - MpiWrapper::cartCreate( MPI_COMM_GEOSX, 3, partition.m_Partitions.data(), partition.m_Periodic.data(), reorder, &cartcomm ); + MpiWrapper::cartCreate( MPI_COMM_GEOSX, 3, partition.getPartitions().data(), partition.m_Periodic.data(), reorder, &cartcomm ); GEOS_ERROR_IF( cartcomm == MPI_COMM_NULL, "Fail to run MPI_Cart_create and establish communications" ); } int const rank = MpiWrapper::commRank( MPI_COMM_GEOSX ); @@ -104,7 +105,7 @@ void DomainPartition::setupBaseLevelMeshGlobalInfo() } else { - for( integer const neighborRank : m_metisNeighborList ) + for( integer const neighborRank : metisNeighborList ) { m_neighbors.emplace_back( neighborRank ); } @@ -248,7 +249,7 @@ void DomainPartition::addNeighbors( const unsigned int idim, } else { - const int dim = partition.m_Partitions( LvArray::integerConversion< localIndex >( idim )); + const int dim = partition.getPartitions()( LvArray::integerConversion< localIndex >( idim )); const bool periodic = partition.m_Periodic( LvArray::integerConversion< localIndex >( idim )); for( int i = -1; i < 2; i++ ) { diff --git a/src/coreComponents/mesh/DomainPartition.hpp b/src/coreComponents/mesh/DomainPartition.hpp index 498e0b24de3..7a41174df1c 100644 --- a/src/coreComponents/mesh/DomainPartition.hpp +++ b/src/coreComponents/mesh/DomainPartition.hpp @@ -253,21 +253,6 @@ class DomainPartition : public dataRepository::Group getMeshBodies().forSubGroupsIndex< MeshBody >( std::forward< FUNCTION >( function ) ); } - - /** - * @brief Get the metis neighbors indices. @see DomainPartition#m_metisNeighborList - * @return Container of global indices. - */ - std::set< int > & getMetisNeighborList() - { return m_metisNeighborList; } - - /** - * @brief Get the metis neighbors indices, const version. @see DomainPartition#m_metisNeighborList - * @return Container of global indices. - */ - std::set< int > const & getMetisNeighborList() const - { return m_metisNeighborList; } - /** * @brief Get the neighbor communicators. @see DomainPartition#m_neighbors. * @return Container of communicators. @@ -284,10 +269,6 @@ class DomainPartition : public dataRepository::Group private: - /** - * @brief Contains the global indices of the metis neighbors in case `metis` is used. Empty otherwise. - */ - std::set< int > m_metisNeighborList; /** * @brief Contains all the communicators from this DomainPartition to its neighbors. */ diff --git a/src/coreComponents/mesh/ElementRegionBase.hpp b/src/coreComponents/mesh/ElementRegionBase.hpp index 825044d7fac..049173a74e6 100644 --- a/src/coreComponents/mesh/ElementRegionBase.hpp +++ b/src/coreComponents/mesh/ElementRegionBase.hpp @@ -102,7 +102,7 @@ class ElementRegionBase : public ObjectManagerBase * @brief Generate mesh. * @param blocks Cell or face blocks from where the mesh is extracted. */ - virtual void generateMesh( Group & blocks ) = 0; + virtual void generateMesh( Group const & blocks ) = 0; ///@} diff --git a/src/coreComponents/mesh/ElementRegionManager.cpp b/src/coreComponents/mesh/ElementRegionManager.cpp index 9106cae830d..4b7dbbbbd57 100644 --- a/src/coreComponents/mesh/ElementRegionManager.cpp +++ b/src/coreComponents/mesh/ElementRegionManager.cpp @@ -22,9 +22,11 @@ #include "SurfaceElementRegion.hpp" #include "FaceManager.hpp" #include "constitutive/ConstitutiveManager.hpp" -#include "mesh/MeshManager.hpp" +#include "mesh/NodeManager.hpp" +#include "mesh/MeshLevel.hpp" #include "mesh/utilities/MeshMapUtilities.hpp" #include "schema/schemaUtilities.hpp" +#include "mesh/generators/LineBlockABC.hpp" namespace geos { @@ -117,7 +119,7 @@ void ElementRegionManager::setSchemaDeviations( xmlWrapper::xmlNode schemaRoot, } } -void ElementRegionManager::generateMesh( CellBlockManagerABC & cellBlockManager ) +void ElementRegionManager::generateMesh( CellBlockManagerABC const & cellBlockManager ) { this->forElementRegions< CellElementRegion >( [&]( CellElementRegion & elemRegion ) { @@ -151,7 +153,7 @@ void ElementRegionManager::generateMesh( CellBlockManagerABC & cellBlockManager } -void ElementRegionManager::generateWells( MeshManager & meshManager, +void ElementRegionManager::generateWells( CellBlockManagerABC const & cellBlockManager, MeshLevel & meshLevel ) { NodeManager & nodeManager = meshLevel.getNodeManager(); @@ -170,18 +172,17 @@ void ElementRegionManager::generateWells( MeshManager & meshManager, { // get the global well geometry from the well generator - string const generatorName = wellRegion.getWellGeneratorName(); - InternalWellGenerator const & wellGeometry = - meshManager.getGroup< InternalWellGenerator >( generatorName ); - + LineBlockABC const & lineBlock = cellBlockManager.getLineBlock( wellRegion.getName() ); + wellRegion.setWellGeneratorName( lineBlock.getWellGeneratorName() ); + wellRegion.setWellControlsName( lineBlock.getWellControlsName() ); // generate the local data (well elements, nodes, perforations) on this well // note: each MPI rank knows the global info on the entire well (constructed earlier in InternalWellGenerator) // so we only need node and element offsets to construct the local-to-global maps in each wellElemSubRegion - wellRegion.generateWell( meshLevel, wellGeometry, nodeOffsetGlobal + wellNodeCount, elemOffsetGlobal + wellElemCount ); + wellRegion.generateWell( meshLevel, lineBlock, nodeOffsetGlobal + wellNodeCount, elemOffsetGlobal + wellElemCount ); // increment counters with global number of nodes and elements - wellElemCount += wellGeometry.getNumElements(); - wellNodeCount += wellGeometry.getNumNodes(); + wellElemCount += lineBlock.numElements(); + wellNodeCount += lineBlock.numNodes(); string const & subRegionName = wellRegion.getSubRegionName(); WellElementSubRegion & @@ -190,9 +191,9 @@ void ElementRegionManager::generateWells( MeshManager & meshManager, globalIndex const numWellElemsGlobal = MpiWrapper::sum( subRegion.size() ); - GEOS_ERROR_IF( numWellElemsGlobal != wellGeometry.getNumElements(), - "Invalid partitioning in well " << wellGeometry.getDataContext() << - ", subregion " << subRegion.getDataContext() ); + GEOS_ERROR_IF( numWellElemsGlobal != lineBlock.numElements(), + GEOS_FMT( "Invalid partitioning in well {}, subregion {}", + lineBlock.getDataContext(), wellRegion.getDataContext() ) ); } ); @@ -665,6 +666,7 @@ ElementRegionManager::getCellBlockToSubRegionMap( CellBlockManagerABC const & ce ElementRegionBase const & region, CellElementSubRegion const & subRegion ) { + GEOS_UNUSED_VAR( region ); // unused if geos_error_if is nulld localIndex const blockIndex = cellBlocks.getIndex( subRegion.getName() ); GEOS_ERROR_IF( blockIndex == Group::subGroupMap::KeyIndex::invalid_index, GEOS_FMT( "{}, subregion {}: Cell block not found at index {}.", diff --git a/src/coreComponents/mesh/ElementRegionManager.hpp b/src/coreComponents/mesh/ElementRegionManager.hpp index 748e26008ed..d71bb59c0fc 100644 --- a/src/coreComponents/mesh/ElementRegionManager.hpp +++ b/src/coreComponents/mesh/ElementRegionManager.hpp @@ -146,14 +146,14 @@ class ElementRegionManager : public ObjectManagerBase * @brief Generate the mesh. * @param [in,out] cellBlockManager Reference to the abstract cell block manager. */ - void generateMesh( CellBlockManagerABC & cellBlockManager ); + void generateMesh( CellBlockManagerABC const & cellBlockManager ); /** * @brief Generate the wells. - * @param [in] meshManager pointer to meshManager + * @param [in] cellBlockManager pointer to cellBlockManager * @param [in] meshLevel pointer to meshLevel */ - void generateWells( MeshManager & meshManager, MeshLevel & meshLevel ); + void generateWells( CellBlockManagerABC const & cellBlockManager, MeshLevel & meshLevel ); /** * @brief Build sets from the node sets diff --git a/src/coreComponents/mesh/EmbeddedSurfaceSubRegion.cpp b/src/coreComponents/mesh/EmbeddedSurfaceSubRegion.cpp index 83f3f5eb4aa..c5a637fa9fc 100644 --- a/src/coreComponents/mesh/EmbeddedSurfaceSubRegion.cpp +++ b/src/coreComponents/mesh/EmbeddedSurfaceSubRegion.cpp @@ -113,18 +113,18 @@ bool EmbeddedSurfaceSubRegion::addNewEmbeddedSurface( localIndex const cellIndex EmbeddedSurfaceNodeManager & embSurfNodeManager, EdgeManager const & edgeManager, FixedOneToManyRelation const & cellToEdges, - BoundedPlane const * fracture ) + PlanarGeometricObject const * fracture ) { - /* The goal is to add an embeddedSurfaceElem if it is contained within the BoundedPlane + /* The goal is to add an embeddedSurfaceElem if it is contained within the PlanarGeometricObject * - * A. Identify whether the cell falls within the bounded plane or not + * A. Identify whether the cell falls within the bounded planar object or not * * we loop over each edge: * - * 1. check if it is cut by the plane using the Dot product between the distance of each node + * 1. check if it is cut by the planar object using the Dot product between the distance of each node * from the origin and the normal vector. - * 2. If an edge is cut by the plane we look for the intersection between a line and a plane. - * 3. Once we have the intersection we check if it falls inside the bounded plane. + * 2. If an edge is cut by the planar object we look for the intersection between a line and a plane. + * 3. Once we have the intersection we check if it falls inside the bounded planar object. * * Only elements for which all intersection points fall within the fracture plane limits will be added. * If the fracture does not cut through the entire element we will just chop it (it's a discretization error). diff --git a/src/coreComponents/mesh/EmbeddedSurfaceSubRegion.hpp b/src/coreComponents/mesh/EmbeddedSurfaceSubRegion.hpp index 01ccdb4f750..95d5c649d4e 100644 --- a/src/coreComponents/mesh/EmbeddedSurfaceSubRegion.hpp +++ b/src/coreComponents/mesh/EmbeddedSurfaceSubRegion.hpp @@ -25,7 +25,8 @@ #include "EdgeManager.hpp" #include "EmbeddedSurfaceNodeManager.hpp" #include "CellElementSubRegion.hpp" -#include "simpleGeometricObjects/BoundedPlane.hpp" +//Do we really need this include Rectangle? +#include "simpleGeometricObjects/Rectangle.hpp" namespace geos { @@ -142,7 +143,7 @@ class EmbeddedSurfaceSubRegion : public SurfaceElementSubRegion EmbeddedSurfaceNodeManager & embSurfNodeManager, EdgeManager const & edgeManager, FixedOneToManyRelation const & cellToEdges, - BoundedPlane const * fracture ); + PlanarGeometricObject const * fracture ); /** * @brief inherit ghost rank from cell elements. diff --git a/src/coreComponents/mesh/FaceManager.cpp b/src/coreComponents/mesh/FaceManager.cpp index 3e0cdfc60ef..832eb85dcbb 100644 --- a/src/coreComponents/mesh/FaceManager.cpp +++ b/src/coreComponents/mesh/FaceManager.cpp @@ -197,14 +197,14 @@ void FaceManager::sortAllFaceNodes( NodeManager const & nodeManager, ElementRegionManager::ElementViewAccessor< arrayView2d< real64 const > > elemCenter = elemManager.constructArrayViewAccessor< real64, 2 >( ElementSubRegionBase::viewKeyStruct::elementCenterString() ); - forAll< parallelHostPolicy >( size(), [=, elemCenter = elemCenter.toNestedViewConst()]( localIndex const faceIndex ) + forAll< parallelHostPolicy >( size(), [=, elemCenter = elemCenter.toNestedViewConst(), &elemManager]( localIndex const faceIndex ) { // The face should be connected to at least one element. if( facesToElements( faceIndex, 0 ) < 0 && facesToElements( faceIndex, 1 ) < 0 ) { GEOS_ERROR( getDataContext() << ": Face " << faceIndex << " is not connected to any cell." << "You might have forgotten one cell type in the " << - elemManager.getWrapperDataContext( CellElementRegion::viewKeyStruct::sourceCellBlockNamesString() ) << + elemManager.getWrapperDataContext( CellElementRegion::viewKeyStruct::sourceCellBlockNamesString() ) << ", or your mesh might be invalid" ); } diff --git a/src/coreComponents/mesh/MeshBody.hpp b/src/coreComponents/mesh/MeshBody.hpp index 231c0dab745..c8bffd276eb 100644 --- a/src/coreComponents/mesh/MeshBody.hpp +++ b/src/coreComponents/mesh/MeshBody.hpp @@ -20,7 +20,7 @@ #define GEOS_MESH_MESHBODY_HPP_ #include "MeshLevel.hpp" - +#include "dataRepository/KeyNames.hpp" namespace geos { @@ -166,6 +166,23 @@ class MeshBody : public dataRepository::Group return m_globalLengthScale; } + /** + * @brief Get the Abstract representation of the CellBlockManager attached to the MeshBody. + * @return The CellBlockManager. + */ + CellBlockManagerABC const & getCellBlockManager() const + { + return this->getGroup< CellBlockManagerABC >( dataRepository::keys::cellManager ); + } + + /** + * @brief De register the CellBlockManager from this meshBody + */ + void deregisterCellBlockManager() + { + this->deregisterGroup( dataRepository::keys::cellManager ); + } + /** * @brief Data repository keys */ diff --git a/src/coreComponents/mesh/MeshLevel.cpp b/src/coreComponents/mesh/MeshLevel.cpp index 454324fd722..4fd7364d474 100644 --- a/src/coreComponents/mesh/MeshLevel.cpp +++ b/src/coreComponents/mesh/MeshLevel.cpp @@ -235,17 +235,18 @@ MeshLevel::MeshLevel( string const & name, newSubRegion.constructGlobalToLocalMap(); - CellBlockManagerABC & cellBlockManager = meshBody->getGroup< CellBlockManagerABC >( keys::cellManager ); - - cellBlockManager.generateHighOrderMaps( order, - maxVertexGlobalID, - maxEdgeGlobalID, - maxFaceGlobalID, - edgeLocalToGlobal, - faceLocalToGlobal ); } ); + } ); + CellBlockManagerABC & cellBlockManager = meshBody->getGroup< CellBlockManagerABC >( keys::cellManager ); + + cellBlockManager.generateHighOrderMaps( order, + maxVertexGlobalID, + maxEdgeGlobalID, + maxFaceGlobalID, + edgeLocalToGlobal, + faceLocalToGlobal ); ///////////////////////// diff --git a/src/coreComponents/mesh/MeshManager.cpp b/src/coreComponents/mesh/MeshManager.cpp index 02b11074d52..923fd9b3783 100644 --- a/src/coreComponents/mesh/MeshManager.cpp +++ b/src/coreComponents/mesh/MeshManager.cpp @@ -14,10 +14,13 @@ #include "MeshManager.hpp" +#include "MeshBody.hpp" +#include "MeshLevel.hpp" -#include "mesh/mpiCommunications/CommunicationTools.hpp" #include "mesh/mpiCommunications/SpatialPartition.hpp" +#include "generators/CellBlockManagerABC.hpp" #include "generators/MeshGeneratorBase.hpp" +#include "mesh/mpiCommunications/CommunicationTools.hpp" #include "common/TimingMacros.hpp" #include @@ -59,7 +62,16 @@ void MeshManager::generateMeshes( DomainPartition & domain ) { forSubGroups< MeshGeneratorBase >( [&]( MeshGeneratorBase & meshGen ) { - meshGen.generateMesh( domain ); + MeshBody & meshBody = domain.getMeshBodies().registerGroup< MeshBody >( meshGen.getName() ); + meshBody.createMeshLevel( 0 ); + SpatialPartition & partition = dynamic_cast< SpatialPartition & >(domain.getReference< PartitionBase >( keys::partitionManager ) ); + + meshGen.generateMesh( meshBody, partition.getPartitions() ); + CellBlockManagerABC const & cellBlockManager = meshBody.getCellBlockManager(); + + meshBody.setGlobalLengthScale( cellBlockManager.getGlobalLength() ); + + partition = meshGen.getSpatialPartition(); } ); } @@ -68,11 +80,6 @@ void MeshManager::generateMeshLevels( DomainPartition & domain ) { this->forSubGroups< MeshGeneratorBase >( [&]( MeshGeneratorBase & meshGen ) { - if( dynamicCast< InternalWellGenerator * >( &meshGen ) ) - { - return; - } - string const & meshName = meshGen.getName(); domain.getMeshBodies().registerGroup< MeshBody >( meshName ).createMeshLevel( MeshBody::groupStructKeys::baseDiscretizationString() ); } ); @@ -141,7 +148,7 @@ void MeshManager::importFields( DomainPartition & domain ) WrapperBase & wrapper = subRegion.getWrapperBase( geosxFieldName ); if( generator.getLogLevel() >= 1 ) { - GEOS_LOG_RANK_0( "Importing field " << meshFieldName << " -> " << geosxFieldName << + GEOS_LOG_RANK_0( "Importing field " << meshFieldName << " into " << geosxFieldName << " on " << region.getName() << "/" << subRegion.getName() ); } diff --git a/src/coreComponents/mesh/MeshObjectPath.hpp b/src/coreComponents/mesh/MeshObjectPath.hpp index 8b6d0a4fac3..983fb822fc0 100644 --- a/src/coreComponents/mesh/MeshObjectPath.hpp +++ b/src/coreComponents/mesh/MeshObjectPath.hpp @@ -180,6 +180,12 @@ class MeshObjectPath MeshLevel & meshLevel, FUNC && func ) const; + template< typename OBJECT_TYPE, + typename FUNC > + void forObjectsInPath( std::pair< string const, std::map< string, std::vector< string > > > const & levelPair, + MeshLevel const & meshLevel, + FUNC && func ) const; + /** * @brief A logical check for whether or not the m_objecType is consistent * with a specific OBJECT_TYPE @@ -269,38 +275,50 @@ template< typename OBJECT_TYPE, void MeshObjectPath::forObjectsInPath( std::pair< string const, std::map< string, std::vector< string > > > const & levelPair, MeshLevel & meshLevel, FUNC && func ) const +{ + forObjectsInPath< OBJECT_TYPE >( levelPair, const_cast< MeshLevel const & >( meshLevel ), [&]( OBJECT_TYPE const & object ) + { + func( const_cast< OBJECT_TYPE & >(object) ); + } ); +} + +template< typename OBJECT_TYPE, + typename FUNC > +void MeshObjectPath::forObjectsInPath( std::pair< string const, std::map< string, std::vector< string > > > const & levelPair, + MeshLevel const & meshLevel, + FUNC && func ) const { if( m_objectType == ObjectTypes::nodes ) { - func( dynamic_cast< OBJECT_TYPE & >(meshLevel.getNodeManager() ) ); + func( dynamic_cast< OBJECT_TYPE const & >(meshLevel.getNodeManager() ) ); } else if( m_objectType == ObjectTypes::edges ) { - func( dynamic_cast< OBJECT_TYPE & >(meshLevel.getEdgeManager()) ); + func( dynamic_cast< OBJECT_TYPE const & >(meshLevel.getEdgeManager()) ); } else if( m_objectType == ObjectTypes::faces ) { - func( dynamic_cast< OBJECT_TYPE & >(meshLevel.getFaceManager()) ); + func( dynamic_cast< OBJECT_TYPE const & >(meshLevel.getFaceManager()) ); } else if( m_objectType == ObjectTypes::elems ) { - ElementRegionManager & elemRegionMan = meshLevel.getElemManager(); + ElementRegionManager const & elemRegionMan = meshLevel.getElemManager(); for( auto & elemRegionPair : levelPair.second ) { - ElementRegionBase & elemRegion = elemRegionMan.getRegion( elemRegionPair.first ); + ElementRegionBase const & elemRegion = elemRegionMan.getRegion( elemRegionPair.first ); if( std::is_base_of< ElementRegionBase, OBJECT_TYPE >::value ) { - func( dynamic_cast< OBJECT_TYPE & >(elemRegion) ); + func( dynamic_cast< OBJECT_TYPE const & >(elemRegion) ); } else { for( auto & elemSubRegionName : elemRegionPair.second ) { - ElementSubRegionBase & subRegion = elemRegion.getSubRegion( elemSubRegionName ); + ElementSubRegionBase const & subRegion = elemRegion.getSubRegion( elemSubRegionName ); if( std::is_base_of< ElementSubRegionBase, OBJECT_TYPE >::value || std::is_same< dataRepository::Group, OBJECT_TYPE >::value ) { - func( dynamic_cast< OBJECT_TYPE & >(subRegion) ); + func( dynamic_cast< OBJECT_TYPE const & >(subRegion) ); } else { @@ -335,7 +353,6 @@ void MeshObjectPath::forObjectsInPath( dataRepository::Group const & meshBodies, for( auto const & meshLevelPair : meshBodyPair.second ) { MeshLevel const & meshLevel = meshBody.getMeshLevel( meshLevelPair.first ); - forObjectsInPath< OBJECT_TYPE, FUNC >( meshLevelPair, meshLevel, std::forward< FUNC >( func )); } } diff --git a/src/coreComponents/mesh/PerforationData.cpp b/src/coreComponents/mesh/PerforationData.cpp index cf7d60013b4..d5bbdd7e0ad 100644 --- a/src/coreComponents/mesh/PerforationData.cpp +++ b/src/coreComponents/mesh/PerforationData.cpp @@ -126,6 +126,7 @@ void PerforationData::computeWellTransmissibility( MeshLevel const & mesh, if( m_wellTransmissibility[iperf] >= 0 ) { WellElementRegion const & wellRegion = dynamicCast< WellElementRegion const & >( wellElemSubRegion.getParent().getParent() ); + GEOS_UNUSED_VAR( wellRegion ); // unused if geos_error_if is nulld GEOS_LOG_RANK_IF( isZero( m_wellTransmissibility[iperf] ), "\n \nWarning! Perforation " << wellRegion.getWellGeneratorName() << " is defined with a zero transmissibility.\n" << @@ -164,8 +165,8 @@ void PerforationData::computeWellTransmissibility( MeshLevel const & mesh, // get the index of the well element, and of the neighboring well nodes localIndex const wellElemIndex = m_wellElementIndex[iperf]; - localIndex const topNode = elemToNodeMap[wellElemIndex][InternalWellGenerator::NodeLocation::TOP]; - localIndex const bottomNode = elemToNodeMap[wellElemIndex][InternalWellGenerator::NodeLocation::BOTTOM]; + localIndex const topNode = elemToNodeMap[wellElemIndex][LineBlockABC::NodeLocation::TOP]; + localIndex const bottomNode = elemToNodeMap[wellElemIndex][LineBlockABC::NodeLocation::BOTTOM]; // using the direction of the segment, compute the perforation "direction" // that will be used to construct the Peaceman index real64 topToBottomVec[3] = { X[bottomNode][0], @@ -247,11 +248,11 @@ void PerforationData::getReservoirElementDimensions( MeshLevel const & mesh, dz /= dx * dy; } -void PerforationData::connectToWellElements( InternalWellGenerator const & wellGeometry, +void PerforationData::connectToWellElements( LineBlockABC const & lineBlock, unordered_map< globalIndex, localIndex > const & globalToLocalWellElemMap, globalIndex elemOffsetGlobal ) { - arrayView1d< globalIndex const > const & perfElemIndexGlobal = wellGeometry.getPerfElemIndex(); + arrayView1d< globalIndex const > const & perfElemIndexGlobal = lineBlock.getPerfElemIndex(); for( localIndex iperfLocal = 0; iperfLocal < size(); ++iperfLocal ) { diff --git a/src/coreComponents/mesh/PerforationData.hpp b/src/coreComponents/mesh/PerforationData.hpp index deb307330f9..1d819cc9f6e 100644 --- a/src/coreComponents/mesh/PerforationData.hpp +++ b/src/coreComponents/mesh/PerforationData.hpp @@ -22,7 +22,7 @@ #include "dataRepository/Group.hpp" #include "mesh/ObjectManagerBase.hpp" #include "mesh/ToElementRelation.hpp" -#include "mesh/generators/InternalWellGenerator.hpp" +#include "mesh/generators/LineBlockABC.hpp" namespace geos { @@ -110,7 +110,7 @@ class PerforationData : public ObjectManagerBase /** * @brief Set the global number of perforations used for well initialization. - * @param[in] nPerfs global number of perforations (obtained from InternalWellGenerator) + * @param[in] nPerfs global number of perforations (obtained from LineBlockABC) */ void setNumPerforationsGlobal( globalIndex nPerfs ) { m_numPerforationsGlobal = nPerfs; } @@ -203,11 +203,11 @@ class PerforationData : public ObjectManagerBase /** * @brief Connect each perforation to a local wellbore element. - * @param[in] wellGeometry InternalWellGenerator containing the global well topology + * @param[in] lineBlock LineBlockABC containing the global well topology * @param[in] globalToLocalWellElementMap global-to-local map of wellbore elements * @param[in] elemOffsetGlobal the offset of the first global well element ( = offset of last global mesh elem + 1 ) */ - void connectToWellElements( InternalWellGenerator const & wellGeometry, + void connectToWellElements( LineBlockABC const & lineBlock, unordered_map< globalIndex, localIndex > const & globalToLocalWellElementMap, globalIndex elemOffsetGlobal ); diff --git a/src/coreComponents/mesh/SurfaceElementRegion.cpp b/src/coreComponents/mesh/SurfaceElementRegion.cpp index 0e6340a35f8..f62f6d9885b 100644 --- a/src/coreComponents/mesh/SurfaceElementRegion.cpp +++ b/src/coreComponents/mesh/SurfaceElementRegion.cpp @@ -48,7 +48,7 @@ SurfaceElementRegion::~SurfaceElementRegion() {} -void SurfaceElementRegion::generateMesh( Group & faceBlocks ) +void SurfaceElementRegion::generateMesh( Group const & faceBlocks ) { Group & elementSubRegions = this->getGroup( viewKeyStruct::elementSubRegions() ); diff --git a/src/coreComponents/mesh/SurfaceElementRegion.hpp b/src/coreComponents/mesh/SurfaceElementRegion.hpp index 0e86f38a54a..c00ea71af46 100644 --- a/src/coreComponents/mesh/SurfaceElementRegion.hpp +++ b/src/coreComponents/mesh/SurfaceElementRegion.hpp @@ -98,7 +98,7 @@ class SurfaceElementRegion : public ElementRegionBase */ ///@{ - virtual void generateMesh( Group & faceBlocks ) override; + virtual void generateMesh( Group const & faceBlocks ) override; /** * @brief This function generates and adds entries to the face/fracture mesh. diff --git a/src/coreComponents/mesh/WellElementRegion.cpp b/src/coreComponents/mesh/WellElementRegion.cpp index b36dd88b0a2..08d0fbe4f4a 100644 --- a/src/coreComponents/mesh/WellElementRegion.cpp +++ b/src/coreComponents/mesh/WellElementRegion.cpp @@ -41,9 +41,8 @@ WellElementRegion::WellElementRegion( string const & name, Group * const parent WellElementRegion::~WellElementRegion() {} - void WellElementRegion::generateWell( MeshLevel & mesh, - InternalWellGenerator const & wellGeometry, + LineBlockABC const & lineBlock, globalIndex nodeOffsetGlobal, globalIndex elemOffsetGlobal ) { @@ -55,17 +54,17 @@ void WellElementRegion::generateWell( MeshLevel & mesh, subRegion.setWellControlsName( m_wellControlsName ); PerforationData * const perforationData = subRegion.getPerforationData(); - perforationData->setNumPerforationsGlobal( wellGeometry.getNumPerforations() ); + perforationData->setNumPerforationsGlobal( lineBlock.numPerforations() ); - globalIndex const numElemsGlobal = wellGeometry.getNumElements(); - globalIndex const numPerforationsGlobal = wellGeometry.getNumPerforations(); + globalIndex const numElemsGlobal = lineBlock.numElements(); + globalIndex const numPerforationsGlobal = lineBlock.numPerforations(); // 1) select the local perforations based on connectivity to the local reservoir elements - subRegion.connectPerforationsToMeshElements( mesh, wellGeometry ); + subRegion.connectPerforationsToMeshElements( mesh, lineBlock ); globalIndex const matchedPerforations = MpiWrapper::sum( perforationData->size() ); GEOS_THROW_IF( matchedPerforations != numPerforationsGlobal, - "Invalid mapping perforation-to-element in InternalWell " << wellGeometry.getDataContext() << "." << + "Invalid mapping perforation-to-element in InternalWell " << lineBlock.getDataContext() << "." << " This happens when GEOSX cannot match a perforation with a reservoir element." << " There are two common reasons for this error:\n" << " 1- The most common reason for this error is that a perforation is on a section of " << @@ -74,12 +73,11 @@ void WellElementRegion::generateWell( MeshLevel & mesh, " Please try to move the perforation slightly (to the interior of the perforated cell) to see if it fixes the problem.", InputError ); - // 2) classify well elements based on connectivity to local mesh partition array1d< integer > elemStatusGlobal; elemStatusGlobal.resizeDefault( numElemsGlobal, WellElementSubRegion::WellElemStatus::UNOWNED ); - arrayView1d< globalIndex const > const & perfElemIdGlobal = wellGeometry.getPerfElemIndex(); + arrayView1d< globalIndex const > const & perfElemIdGlobal = lineBlock.getPerfElemIndex(); for( localIndex iperfGlobal = 0; iperfGlobal < numPerforationsGlobal; ++iperfGlobal ) { @@ -98,7 +96,7 @@ void WellElementRegion::generateWell( MeshLevel & mesh, // 3) select the local well elements and mark boundary nodes (for ghosting) subRegion.generate( mesh, - wellGeometry, + lineBlock, elemStatusGlobal, nodeOffsetGlobal, elemOffsetGlobal ); @@ -123,7 +121,7 @@ void WellElementRegion::generateWell( MeshLevel & mesh, // 5) construct the local perforation to well element map - perforationData->connectToWellElements( wellGeometry, + perforationData->connectToWellElements( lineBlock, subRegion.globalToLocalMap(), elemOffsetGlobal ); diff --git a/src/coreComponents/mesh/WellElementRegion.hpp b/src/coreComponents/mesh/WellElementRegion.hpp index f5268a0db83..c3416030931 100644 --- a/src/coreComponents/mesh/WellElementRegion.hpp +++ b/src/coreComponents/mesh/WellElementRegion.hpp @@ -21,7 +21,7 @@ #define GEOS_MESH_WELLELEMENTREGION_HPP_ #include "mesh/ElementRegionBase.hpp" -#include "mesh/generators/InternalWellGenerator.hpp" +#include "mesh/generators/LineBlockABC.hpp" namespace geos { @@ -121,17 +121,17 @@ class WellElementRegion : public ElementRegionBase /** * @brief Not implemented, this task is performed in GenerateWell. */ - virtual void generateMesh( Group & ) override {} + void generateMesh( Group const & ) override {} /** * @brief Build the local well elements and perforations from global well geometry. * @param[in] mesh the mesh object (single level only) - * @param[in] wellGeometry the InternalWellGenerator containing the global well topology + * @param[in] lineBlock the LineBlockABC containing the global well topology * @param[in] nodeOffsetGlobal the offset of the first global well node ( = offset of last global mesh node + 1 ) * @param[in] elemOffsetGlobal the offset of the first global well element ( = offset of last global mesh elem + 1 ) */ void generateWell( MeshLevel & mesh, - InternalWellGenerator const & wellGeometry, + LineBlockABC const & lineBlock, globalIndex nodeOffsetGlobal, globalIndex elemOffsetGlobal ); diff --git a/src/coreComponents/mesh/WellElementSubRegion.cpp b/src/coreComponents/mesh/WellElementSubRegion.cpp index 4b9df39e825..48363175d31 100644 --- a/src/coreComponents/mesh/WellElementSubRegion.cpp +++ b/src/coreComponents/mesh/WellElementSubRegion.cpp @@ -63,29 +63,29 @@ namespace /** * @brief Now that the well elements are assigned, collect the nodes and tag the boundary nodes between ranks The function WellElementSubRegion::AssignUnownedElements must have been called before this function - * @param[in] wellGeometry the InternalWellGenerator containing the global well topology + * @param[in] lineBlock the LineBlockABC containing the global well topology * @param[in] localElems set of local well elems. At this point all the well elems have been assigned * @param[out] localNodes set of local well nodes (includes boundary nodes) * @param[out] boundaryNodes set of local well nodes that are at the boundary between this rank and another rank */ -void collectLocalAndBoundaryNodes( InternalWellGenerator const & wellGeometry, +void collectLocalAndBoundaryNodes( LineBlockABC const & lineBlock, SortedArray< globalIndex > const & localElems, SortedArray< globalIndex > & localNodes, SortedArray< globalIndex > & boundaryNodes ) { // get the well connectivity - arrayView1d< globalIndex const > const & nextElemIdGlobal = wellGeometry.getNextElemIndex(); - arrayView1d< arrayView1d< globalIndex const > const > const & prevElemIdsGlobal = wellGeometry.getPrevElemIndices(); - arrayView2d< globalIndex const > const & elemToNodesGlobal = wellGeometry.getElemToNodesMap(); + arrayView1d< globalIndex const > const & nextElemIdGlobal = lineBlock.getNextElemIndex(); + arrayView1d< arrayView1d< globalIndex const > const > const & prevElemIdsGlobal = lineBlock.getPrevElemIndices(); + arrayView2d< globalIndex const > const & elemToNodesGlobal = lineBlock.getElemToNodesMap(); // loop over the local elements and collect the local and boundary nodes for( globalIndex currGlobal : localElems ) { // if the element is local, its two nodes are also local - globalIndex const inodeTopGlobal = elemToNodesGlobal[currGlobal][InternalWellGenerator::NodeLocation::TOP]; - globalIndex const inodeBottomGlobal = elemToNodesGlobal[currGlobal][InternalWellGenerator::NodeLocation::BOTTOM]; + globalIndex const inodeTopGlobal = elemToNodesGlobal[currGlobal][LineBlockABC::NodeLocation::TOP]; + globalIndex const inodeBottomGlobal = elemToNodesGlobal[currGlobal][LineBlockABC::NodeLocation::BOTTOM]; localNodes.insert( inodeTopGlobal ); localNodes.insert( inodeBottomGlobal ); @@ -343,7 +343,7 @@ bool searchLocalElements( MeshLevel const & mesh, } void WellElementSubRegion::generate( MeshLevel & mesh, - InternalWellGenerator const & wellGeometry, + LineBlockABC const & lineBlock, arrayView1d< integer > & elemStatusGlobal, globalIndex nodeOffsetGlobal, globalIndex elemOffsetGlobal ) @@ -364,14 +364,13 @@ void WellElementSubRegion::generate( MeshLevel & mesh, SortedArray< globalIndex > & unownedElems = elemSetsByStatus[WellElemStatus::UNOWNED]; // here we make sure that there are no shared elements - // this is enforced in the InternalWellGenerator that currently merges two perforations + // this is enforced in the LineBlockABC that currently merges two perforations // if they belong to the same well element. This is a temporary solution. // TODO: split the well elements that contain multiple perforations, so that no element is shared GEOS_THROW_IF( sharedElems.size() > 0, - "InternalWell " << wellGeometry.getDataContext() << " contains shared well elements", + "InternalWell " << lineBlock.getDataContext() << " contains shared well elements", InputError ); - // In Steps 1 and 2 we determine the local objects on this rank (elems and nodes) // Once this is done, in Steps 3, 4, and 5, we update the nodeManager and wellElementSubRegion (size, maps) @@ -383,7 +382,7 @@ void WellElementSubRegion::generate( MeshLevel & mesh, // ie., if the center of the well element falls in the domain owned by rank k // then the well element is assigned to rank k assignUnownedElementsInReservoir( mesh, - wellGeometry, + lineBlock, unownedElems, localElems, elemStatusGlobal ); @@ -394,7 +393,7 @@ void WellElementSubRegion::generate( MeshLevel & mesh, // In this function we also check that the resulting well partitioning is valid, that is, // we make sure that if two ranks are neighbors in the well, that are also neighbors in the // reservoir mesh - checkPartitioningValidity( wellGeometry, + checkPartitioningValidity( lineBlock, localElems, elemStatusGlobal ); @@ -404,7 +403,7 @@ void WellElementSubRegion::generate( MeshLevel & mesh, // 2) collect the local nodes and tag the boundary nodes using element info // now that all the elements have been assigned, we collected the local nodes // and tag the boundary nodes (i.e., the nodes in contact with both local and remote elems) - collectLocalAndBoundaryNodes( wellGeometry, + collectLocalAndBoundaryNodes( lineBlock, localElems, localNodes, boundaryNodes ); @@ -413,7 +412,7 @@ void WellElementSubRegion::generate( MeshLevel & mesh, // this is necessary to later use the node matching procedure // to place ghosts in DomainPartition::SetupCommunications updateNodeManagerSize( mesh, - wellGeometry, + lineBlock, localNodes, boundaryNodes, nodeOffsetGlobal ); @@ -421,7 +420,7 @@ void WellElementSubRegion::generate( MeshLevel & mesh, // 4) resize the well element subregion // and construct local to global, global to local, maps, etc constructSubRegionLocalElementMaps( mesh, - wellGeometry, + lineBlock, localElems, nodeOffsetGlobal, elemOffsetGlobal ); @@ -435,13 +434,13 @@ void WellElementSubRegion::generate( MeshLevel & mesh, void WellElementSubRegion::assignUnownedElementsInReservoir( MeshLevel & mesh, - InternalWellGenerator const & wellGeometry, + LineBlockABC const & lineBlock, SortedArray< globalIndex > const & unownedElems, SortedArray< globalIndex > & localElems, arrayView1d< integer > & elemStatusGlobal ) const { // get the well and reservoir element coordinates - arrayView2d< real64 const > const & wellElemCoordsGlobal = wellGeometry.getElemCoords(); + arrayView2d< real64 const > const & wellElemCoordsGlobal = lineBlock.getElemCoords(); // assign the well elements based on location wrt the reservoir elements // if the center of the well element falls in the domain owned by rank k @@ -488,15 +487,15 @@ void WellElementSubRegion::assignUnownedElementsInReservoir( MeshLevel & mesh, } -void WellElementSubRegion::checkPartitioningValidity( InternalWellGenerator const & wellGeometry, +void WellElementSubRegion::checkPartitioningValidity( LineBlockABC const & lineBlock, SortedArray< globalIndex > & localElems, arrayView1d< integer > & elemStatusGlobal ) const { - arrayView1d< arrayView1d< globalIndex const > const > const & prevElemIdsGlobal = wellGeometry.getPrevElemIndices(); + arrayView1d< arrayView1d< globalIndex const > const > const & prevElemIdsGlobal = lineBlock.getPrevElemIndices(); // we are going to make sure that the partitioning is good, // well element per well element, starting from the bottom of the well - for( globalIndex iwelemGlobal = wellGeometry.getNumElements()-1; iwelemGlobal >= 0; --iwelemGlobal ) + for( globalIndex iwelemGlobal = lineBlock.numElements()-1; iwelemGlobal >= 0; --iwelemGlobal ) { // communicate the status of this element @@ -520,7 +519,7 @@ void WellElementSubRegion::checkPartitioningValidity( InternalWellGenerator cons globalIndex const prevGlobal = prevElemIdsGlobal[iwelemGlobal][numBranches-1]; GEOS_THROW_IF( prevGlobal <= iwelemGlobal || prevGlobal < 0, - "The structure of well " << wellGeometry.getDataContext() << " is invalid. " << + "The structure of well " << lineBlock.getDataContext() << " is invalid. " << " The main reason for this error is that there may be no perforation" << " in the bottom well element of the well, which is required to have" << " a well-posed problem.", @@ -584,7 +583,7 @@ void WellElementSubRegion::checkPartitioningValidity( InternalWellGenerator cons } void WellElementSubRegion::updateNodeManagerSize( MeshLevel & mesh, - InternalWellGenerator const & wellGeometry, + LineBlockABC const & lineBlock, SortedArray< globalIndex > const & localNodes, SortedArray< globalIndex > const & boundaryNodes, globalIndex nodeOffsetGlobal ) @@ -602,7 +601,7 @@ void WellElementSubRegion::updateNodeManagerSize( MeshLevel & mesh, arrayView1d< globalIndex > const & nodeLocalToGlobal = nodeManager.localToGlobalMap(); - arrayView2d< real64 const > const & nodeCoordsGlobal = wellGeometry.getNodeCoords(); + arrayView2d< real64 const > const & nodeCoordsGlobal = lineBlock.getNodeCoords(); // local *well* index localIndex iwellNodeLocal = 0; @@ -638,16 +637,16 @@ void WellElementSubRegion::updateNodeManagerSize( MeshLevel & mesh, } void WellElementSubRegion::constructSubRegionLocalElementMaps( MeshLevel & mesh, - InternalWellGenerator const & wellGeometry, + LineBlockABC const & lineBlock, SortedArray< globalIndex > const & localElems, globalIndex nodeOffsetGlobal, globalIndex elemOffsetGlobal ) { // get the well geometry - arrayView1d< globalIndex const > const & nextElemIdGlobal = wellGeometry.getNextElemIndex(); - arrayView2d< real64 const > const & elemCoordsGlobal = wellGeometry.getElemCoords(); - arrayView2d< globalIndex const > const & elemToNodesGlobal = wellGeometry.getElemToNodesMap(); - arrayView1d< real64 const > const & elemVolumeGlobal = wellGeometry.getElemVolume(); + arrayView1d< globalIndex const > const & nextElemIdGlobal = lineBlock.getNextElemIndex(); + arrayView2d< real64 const > const & elemCoordsGlobal = lineBlock.getElemCoords(); + arrayView2d< globalIndex const > const & elemToNodesGlobal = lineBlock.getElemToNodesMap(); + arrayView1d< real64 const > const & elemVolumeGlobal = lineBlock.getElemVolume(); NodeManager const & nodeManager = mesh.getNodeManager(); @@ -696,20 +695,20 @@ void WellElementSubRegion::constructSubRegionLocalElementMaps( MeshLevel & mesh, LvArray::tensorOps::copy< 3 >( m_elementCenter[ iwelemLocal ], elemCoordsGlobal[ iwelemGlobal ] ); m_elementVolume[iwelemLocal] = elemVolumeGlobal[iwelemGlobal]; - m_radius[iwelemLocal] = wellGeometry.getElementRadius(); + m_radius[iwelemLocal] = lineBlock.getElementRadius(); // update local well elem to node map (note: nodes are in nodeManager ordering) // first get the global node indices in nodeManager ordering - globalIndex const inodeTopGlobal = nodeOffsetGlobal + elemToNodesGlobal[iwelemGlobal][InternalWellGenerator::NodeLocation::TOP]; - globalIndex const inodeBottomGlobal = nodeOffsetGlobal + elemToNodesGlobal[iwelemGlobal][InternalWellGenerator::NodeLocation::BOTTOM]; + globalIndex const inodeTopGlobal = nodeOffsetGlobal + elemToNodesGlobal[iwelemGlobal][LineBlockABC::NodeLocation::TOP]; + globalIndex const inodeBottomGlobal = nodeOffsetGlobal + elemToNodesGlobal[iwelemGlobal][LineBlockABC::NodeLocation::BOTTOM]; // then get the local node indices in nodeManager ordering localIndex const inodeTopLocal = nodeManager.globalToLocalMap( inodeTopGlobal ); localIndex const inodeBottomLocal = nodeManager.globalToLocalMap( inodeBottomGlobal ); - m_toNodesRelation[iwelemLocal][InternalWellGenerator::NodeLocation::TOP] = inodeTopLocal; - m_toNodesRelation[iwelemLocal][InternalWellGenerator::NodeLocation::BOTTOM] = inodeBottomLocal; + m_toNodesRelation[iwelemLocal][LineBlockABC::NodeLocation::TOP] = inodeTopLocal; + m_toNodesRelation[iwelemLocal][LineBlockABC::NodeLocation::BOTTOM] = inodeBottomLocal; } } @@ -752,10 +751,10 @@ void WellElementSubRegion::updateNodeManagerNodeToElementMap( MeshLevel & mesh ) } void WellElementSubRegion::connectPerforationsToMeshElements( MeshLevel & mesh, - InternalWellGenerator const & wellGeometry ) + LineBlockABC const & lineBlock ) { - arrayView2d< real64 const > const perfCoordsGlobal = wellGeometry.getPerfCoords(); - arrayView1d< real64 const > const perfWellTransmissibilityGlobal = wellGeometry.getPerfTransmissibility(); + arrayView2d< real64 const > const perfCoordsGlobal = lineBlock.getPerfCoords(); + arrayView1d< real64 const > const perfWellTransmissibilityGlobal = lineBlock.getPerfTransmissibility(); m_perforationData.resize( perfCoordsGlobal.size( 0 ) ); localIndex iperfLocal = 0; diff --git a/src/coreComponents/mesh/WellElementSubRegion.hpp b/src/coreComponents/mesh/WellElementSubRegion.hpp index 154d30f4f7a..d03b7e818fb 100644 --- a/src/coreComponents/mesh/WellElementSubRegion.hpp +++ b/src/coreComponents/mesh/WellElementSubRegion.hpp @@ -18,6 +18,7 @@ #include "mesh/ElementSubRegionBase.hpp" #include "mesh/InterObjectRelation.hpp" #include "mesh/PerforationData.hpp" +#include "mesh/generators/LineBlockABC.hpp" namespace geos { @@ -219,7 +220,7 @@ class WellElementSubRegion : public ElementSubRegionBase /** * @brief Build the local well elements from global well element data. * @param[in] mesh the mesh object (single level only) - * @param[in] wellGeometry the InternalWellGenerator containing the global well topology + * @param[in] lineBlock the LineBlockABC containing the global well topology * @param[in] elemStatus list of well element status, as determined by perforations connected * to local or remote mesh partitions. Status values are defined in * enum SegmentStatus. They are used to partition well elements. @@ -227,7 +228,7 @@ class WellElementSubRegion : public ElementSubRegionBase * @param[in] elemOffsetGlobal the offset of the first global well element ( = offset of last global mesh elem + 1 ) */ void generate( MeshLevel & mesh, - InternalWellGenerator const & wellGeometry, + LineBlockABC const & lineBlock, arrayView1d< integer > & elemStatus, globalIndex nodeOffsetGlobal, globalIndex elemOffsetGlobal ); @@ -235,10 +236,10 @@ class WellElementSubRegion : public ElementSubRegionBase /** * @brief For each perforation, find the reservoir element that contains the perforation. * @param[in] mesh the mesh object (single level only) - * @param[in] wellGeometry the InternalWellGenerator containing the global well topology + * @param[in] lineBlock the LineBlockABC containing the global well topology */ void connectPerforationsToMeshElements( MeshLevel & mesh, - InternalWellGenerator const & wellGeometry ); + LineBlockABC const & lineBlock ); /** * @brief Reconstruct the (local) map nextWellElemId using nextWellElemIdGlobal after the ghost exchange. @@ -336,7 +337,7 @@ class WellElementSubRegion : public ElementSubRegionBase * @brief Assign the unowned well elements (= well elem without perforation ) that are in the reservoir (and that can therefore be matched with a reservoir element) to an MPI rank. * @param[in] meshLevel the mesh object (single level only) - * @param[in] wellGeometry the InternalWellGenerator containing the global well topology + * @param[in] lineBlock the LineBlockABC containing the global well topology * @param[in] unownedElems set of unowned well elems. * @param[out] localElems set of local well elems. It contains the perforated well elements connected to local mesh elements before the call, and is filled @@ -345,28 +346,28 @@ class WellElementSubRegion : public ElementSubRegionBase * enum SegmentStatus. They are used to partition well elements. */ void assignUnownedElementsInReservoir( MeshLevel & mesh, - InternalWellGenerator const & wellGeometry, + LineBlockABC const & lineBlock, SortedArray< globalIndex > const & unownedElems, SortedArray< globalIndex > & localElems, arrayView1d< integer > & elemStatusGlobal ) const; /** * @brief Check that all the well elements have been assigned to a single rank. - * @param[in] wellGeometry the InternalWellGenerator containing the global well topology + * @param[in] lineBlock the LineBlockABC containing the global well topology * @param[out] localElems set of local well elems. * @param[out] wellElemStatus list of current well element status. Status values are defined in * enum SegmentStatus. They are used to partition well elements. * * This function also checks that if two ranks are neighbors in the well, they are also neighbors in the mesh. */ - void checkPartitioningValidity( InternalWellGenerator const & wellGeometry, + void checkPartitioningValidity( LineBlockABC const & lineBlock, SortedArray< globalIndex > & localElems, arrayView1d< integer > & elemStatusGlobal ) const; /** * @brief Add the well nodes to the nodeManager (properly resized). * @param[inout] meshLevel the mesh object (single level only) - * @param[in] wellGeometry the InternalWellGenerator containing the global well topology + * @param[in] lineBlock the LineBlockABC containing the global well topology * @param[in] localNodes set of local well nodes (includes boundary nodes). At this point all the nodes have been * collected * @param[in] boundaryNodes set of local well nodes that are at the boundary between this rank and another rank @@ -375,7 +376,7 @@ class WellElementSubRegion : public ElementSubRegionBase * The function WellElementSubRegion::CollectLocalAndBoundaryNodes must have been called before this function. */ void updateNodeManagerSize( MeshLevel & mesh, - InternalWellGenerator const & wellGeometry, + LineBlockABC const & lineBlock, SortedArray< globalIndex > const & localNodes, SortedArray< globalIndex > const & boundaryNodes, globalIndex nodeOffsetGlobal ); @@ -384,7 +385,7 @@ class WellElementSubRegion : public ElementSubRegionBase * @brief Construct the subregion's local to global maps, as well as other local maps (toNodes, nextWellElemId, * volume, etc). * @param[inout] meshLevel the mesh object (single level only) - * @param[in] wellGeometry the InternalWellGenerator containing the global well topology + * @param[in] lineBlock the LineBlockABC containing the global well topology * @param[in] localElems set of local well elems. At this point all the well elems have been assigned * @param[in] nodeOffsetGlobal the offset of the first global well node ( = offset of last global mesh node + 1 ) * @param[in] elemOffsetGlobal the offset of the first global well element ( = offset of last global mesh elem + 1 ) @@ -392,7 +393,7 @@ class WellElementSubRegion : public ElementSubRegionBase * The function WellElementSubRegion::UpdateNodeManagerSize must have been called before this function */ void constructSubRegionLocalElementMaps( MeshLevel & mesh, - InternalWellGenerator const & wellGeometry, + LineBlockABC const & lineBlock, SortedArray< globalIndex > const & localElems, globalIndex nodeOffsetGlobal, globalIndex elemOffsetGlobal ); diff --git a/src/coreComponents/mesh/generators/CellBlock.hpp b/src/coreComponents/mesh/generators/CellBlock.hpp index e58e05c90a3..dbb686c0f44 100644 --- a/src/coreComponents/mesh/generators/CellBlock.hpp +++ b/src/coreComponents/mesh/generators/CellBlock.hpp @@ -249,9 +249,9 @@ class CellBlock : public CellBlockABC /// Type of element in this block. ElementType m_elementType; - std::list< dataRepository::WrapperBase * > getExternalProperties() override + std::list< dataRepository::WrapperBase const * > getExternalProperties() const override { - std::list< dataRepository::WrapperBase * > result; + std::list< dataRepository::WrapperBase const * > result; for( string const & externalPropertyName : m_externalPropertyNames ) { result.push_back( &this->getWrapperBase( externalPropertyName ) ); diff --git a/src/coreComponents/mesh/generators/CellBlockABC.hpp b/src/coreComponents/mesh/generators/CellBlockABC.hpp index 11e7c542df7..fadde416894 100644 --- a/src/coreComponents/mesh/generators/CellBlockABC.hpp +++ b/src/coreComponents/mesh/generators/CellBlockABC.hpp @@ -117,7 +117,7 @@ class CellBlockABC : public dataRepository::Group * @see getExternalProperties() */ template< typename LAMBDA > - void forExternalProperties( LAMBDA && lambda ) + void forExternalProperties( LAMBDA && lambda ) const { for( auto * wrapperBase: this->getExternalProperties() ) { @@ -136,7 +136,7 @@ class CellBlockABC : public dataRepository::Group * @note There is some `constness` concern for this member function. * @see forExternalProperties(LAMBDA && lambda) */ - virtual std::list< dataRepository::WrapperBase * > getExternalProperties() = 0; + virtual std::list< dataRepository::WrapperBase const * > getExternalProperties() const = 0; }; } diff --git a/src/coreComponents/mesh/generators/CellBlockManager.cpp b/src/coreComponents/mesh/generators/CellBlockManager.cpp index 52f7346f703..2eebe95394b 100644 --- a/src/coreComponents/mesh/generators/CellBlockManager.cpp +++ b/src/coreComponents/mesh/generators/CellBlockManager.cpp @@ -15,6 +15,7 @@ #include "CellBlockManager.hpp" #include "mesh/generators/CellBlockUtilities.hpp" +#include "mesh/generators/LineBlock.hpp" #include "mesh/utilities/MeshMapUtilities.hpp" #include @@ -29,6 +30,7 @@ CellBlockManager::CellBlockManager( string const & name, Group * const parent ): { this->registerGroup< Group >( viewKeyStruct::cellBlocks() ); this->registerGroup< Group >( viewKeyStruct::faceBlocks() ); + this->registerGroup< Group >( viewKeyStruct::lineBlocks() ); } void CellBlockManager::resize( integer_array const & numElements, @@ -290,13 +292,14 @@ void populateFaceMaps( Group const & cellBlocks, GEOS_ERROR_IF_NE( faceToBlocks.size( 1 ), 2 ); // loop over all the nodes. - forAll< parallelHostPolicy >( numNodes, [uniqueFaceOffsets, - lowestNodeToFaces, - faceToNodes, - faceToCells, - faceToBlocks, - &cellBlocks, - &faceBuilder]( localIndex const nodeIndex ) + forAll< parallelHostPolicy >( numNodes, [ numNodes, + uniqueFaceOffsets, + lowestNodeToFaces, + faceToNodes, + faceToCells, + faceToBlocks, + &cellBlocks, + &faceBuilder ]( localIndex const nodeIndex ) { localIndex nodesInFace[ CellBlockManager::maxNodesPerFace() ]; localIndex curFaceID = uniqueFaceOffsets[nodeIndex]; @@ -307,7 +310,7 @@ void populateFaceMaps( Group const & cellBlocks, CellBlock const & cb = cellBlocks.getGroup< CellBlock >( f0.blockIndex ); localIndex const numNodesInFace = cb.getFaceNodes( f0.cellIndex, f0.faceNumber, nodesInFace ); GEOS_ASSERT_EQ( numNodesInFace, numNodes ); - + GEOS_UNUSED_VAR( numNodes ); for( localIndex i = 0; i < numNodesInFace; ++i ) { @@ -677,11 +680,26 @@ Group & CellBlockManager::getCellBlocks() return this->getGroup( viewKeyStruct::cellBlocks() ); } +Group const & CellBlockManager::getFaceBlocks() const +{ + return this->getGroup( viewKeyStruct::faceBlocks() ); +} + Group & CellBlockManager::getFaceBlocks() { return this->getGroup( viewKeyStruct::faceBlocks() ); } +Group & CellBlockManager::getLineBlocks() +{ + return this->getGroup( viewKeyStruct::lineBlocks() ); +} + +LineBlockABC const & CellBlockManager::getLineBlock( string name ) const +{ + return this->getGroup( viewKeyStruct::lineBlocks() ).getGroup< LineBlockABC >( name ); +} + localIndex CellBlockManager::numNodes() const { return m_numNodes; @@ -739,6 +757,11 @@ FaceBlock & CellBlockManager::registerFaceBlock( string const & name ) return this->getFaceBlocks().registerGroup< FaceBlock >( name ); } +LineBlock & CellBlockManager::registerLineBlock( string const & name ) +{ + return this->getLineBlocks().registerGroup< LineBlock >( name ); +} + array2d< real64, nodes::REFERENCE_POSITION_PERM > CellBlockManager::getNodePositions() const { return m_nodesPositions; @@ -1060,11 +1083,12 @@ void CellBlockManager::generateHighOrderMaps( localIndex const order, cellBlock.resizeNumNodes( numNodesPerCell ); elemsToNodesNew = cellBlock.getElemToNode(); + localIndex const numCellElements = cellBlock.numElements(); // then loop through all the elements and assign the globalID according to the globalID of the Element // and insert the new local to global ID ( for the internal nodes of elements ) into the nodeLocalToGlobal // retrieve finite element type - for( localIndex iter_elem = 0; iter_elem < numLocalCells; ++iter_elem ) + for( localIndex iter_elem = 0; iter_elem < numCellElements; ++iter_elem ) { localIndex newCellNodes = 0; for( localIndex iter_vertex = 0; iter_vertex < numVerticesPerCell; iter_vertex++ ) diff --git a/src/coreComponents/mesh/generators/CellBlockManager.hpp b/src/coreComponents/mesh/generators/CellBlockManager.hpp index d1eaead0842..46dae25d324 100644 --- a/src/coreComponents/mesh/generators/CellBlockManager.hpp +++ b/src/coreComponents/mesh/generators/CellBlockManager.hpp @@ -19,9 +19,13 @@ #ifndef GEOS_MESH_CELLBLOCKMANAGER_H_ #define GEOS_MESH_CELLBLOCKMANAGER_H_ -#include "mesh/generators/CellBlockManagerABC.hpp" #include "mesh/generators/CellBlock.hpp" #include "mesh/generators/FaceBlock.hpp" +#include "mesh/generators/InternalWellGenerator.hpp" +#include "mesh/generators/LineBlock.hpp" +#include "mesh/generators/LineBlockABC.hpp" +#include "mesh/generators/CellBlockManagerABC.hpp" +#include "mesh/generators/PartitionDescriptor.hpp" namespace geos { @@ -152,8 +156,12 @@ class CellBlockManager : public CellBlockManagerABC Group & getCellBlocks() override; + Group const & getFaceBlocks() const override; + Group & getFaceBlocks() override; + LineBlockABC const & getLineBlock( string name ) const override; + /** * @brief Registers and returns a cell block of name @p name. * @param name The name of the created cell block. @@ -168,6 +176,12 @@ class CellBlockManager : public CellBlockManagerABC */ FaceBlock & registerFaceBlock( string const & name ); + /** + * @brief Registers and returns a line block of name @p name. + * @param name The name of the created line block. + * @return A reference to the new line block. The CellBlockManager owns this new instance. + */ + LineBlock & registerLineBlock( string const & name ); /** * @brief Launch kernel function over all the sub-regions * @tparam LAMBDA type of the user-provided function @@ -179,6 +193,14 @@ class CellBlockManager : public CellBlockManagerABC this->getGroup( viewKeyStruct::cellBlocks() ).forSubGroups< CellBlock >( lambda ); } + real64 getGlobalLength() const override { return m_globalLength; } + + /** + * @brief Setter for the global length + * @param globalLength the global length + */ + void setGlobalLength( real64 globalLength ) { m_globalLength = globalLength; } + private: struct viewKeyStruct @@ -190,8 +212,20 @@ class CellBlockManager : public CellBlockManagerABC /// Face blocks key static constexpr char const * faceBlocks() { return "faceBlocks"; } + + /// Line blocks key + static constexpr char const * lineBlocks() + { return "lineBlocks"; } }; + /** + * @brief Returns a group containing the well blocks as @p LineBlockABC instances. + * @return Mutable reference to the well blocks group. + * + * @note It should probably be better not to expose a non-const accessor here. + */ + Group & getLineBlocks(); + /** * @brief Get cell block at index @p blockIndex. * @param[in] blockIndex The cell block index. @@ -236,6 +270,8 @@ class CellBlockManager : public CellBlockManagerABC std::map< string, SortedArray< localIndex > > m_nodeSets; + real64 m_globalLength; + localIndex m_numNodes; localIndex m_numFaces; localIndex m_numEdges; diff --git a/src/coreComponents/mesh/generators/CellBlockManagerABC.hpp b/src/coreComponents/mesh/generators/CellBlockManagerABC.hpp index 58c768d165d..ebc91ee10fc 100644 --- a/src/coreComponents/mesh/generators/CellBlockManagerABC.hpp +++ b/src/coreComponents/mesh/generators/CellBlockManagerABC.hpp @@ -17,6 +17,8 @@ #include "CellBlockUtilities.hpp" #include "dataRepository/Group.hpp" +#include "PartitionDescriptor.hpp" +#include "LineBlockABC.hpp" #include @@ -99,12 +101,26 @@ class CellBlockManagerABC : public dataRepository::Group */ virtual Group & getFaceBlocks() = 0; + /** + * @brief Returns LineBlockABC corresponding to the given identifier + * @param name the name of the required LineBlockABC + * @return The LineBlockABC associated with the given name + * + */ + virtual LineBlockABC const & getLineBlock( string name ) const = 0; + /** * @brief Returns a group containing the cell blocks as CellBlockABC instances * @return Const reference to the Group instance. */ virtual const Group & getCellBlocks() const = 0; + /** + * @brief Returns a group containing the face blocks as FaceBlockABC instances + * @return Const reference to the Group instance. + */ + virtual const Group & getFaceBlocks() const = 0; + /** * @brief Total number of nodes across all the cell blocks. * @return The total number of nodes. @@ -192,6 +208,12 @@ class CellBlockManagerABC : public dataRepository::Group */ virtual std::map< string, SortedArray< localIndex > > const & getNodeSets() const = 0; + /** + * @brief Getter for the global length + * @return the global length of the mesh + */ + virtual real64 getGlobalLength() const = 0; + /** * @brief Generates in place the high-order maps for this cell block manager. * @param[in] order The order of the discretization. diff --git a/src/coreComponents/mesh/generators/InternalMeshGenerator.cpp b/src/coreComponents/mesh/generators/InternalMeshGenerator.cpp index d1f88d35b56..be5a63c1408 100644 --- a/src/coreComponents/mesh/generators/InternalMeshGenerator.cpp +++ b/src/coreComponents/mesh/generators/InternalMeshGenerator.cpp @@ -17,14 +17,6 @@ */ #include "InternalMeshGenerator.hpp" - -#include "common/DataTypes.hpp" -#include "common/TimingMacros.hpp" -#include "mesh/DomainPartition.hpp" -#include "mesh/MeshBody.hpp" -#include "mesh/mpiCommunications/PartitionBase.hpp" -#include "mesh/mpiCommunications/SpatialPartition.hpp" -#include "mesh/MeshBody.hpp" #include "CellBlockManager.hpp" #include "common/DataTypes.hpp" @@ -549,22 +541,30 @@ static void getElemToNodesRelationInBox( ElementType const elementType, } } -/** - * @param partition - * @param domain - */ -void InternalMeshGenerator::generateMesh( DomainPartition & domain ) +void InternalMeshGenerator::fillCellBlockManager( CellBlockManager & cellBlockManager, array1d< int > const & partition ) { GEOS_MARK_FUNCTION; + m_spatialPartition.setPartitions( partition[0], partition[1], partition[2] ); + // Partition based on even spacing to get load balance + // Partition geometrical boundaries will be corrected in the end. + { + m_min[0] = m_vertices[0].front(); + m_min[1] = m_vertices[1].front(); + m_min[2] = m_vertices[2].front(); - MeshBody & meshBody = domain.getMeshBodies().registerGroup< MeshBody >( this->getName() ); + m_max[0] = m_vertices[0].back(); + m_max[1] = m_vertices[1].back(); + m_max[2] = m_vertices[2].back(); - // Make sure that the node manager fields are initialized + m_spatialPartition.setSizes( m_min, m_max ); + } - CellBlockManager & cellBlockManager = meshBody.registerGroup< CellBlockManager >( keys::cellManager ); + // Make sure that the node manager fields are initialized auto & nodeSets = cellBlockManager.getNodeSets(); - SpatialPartition & partition = dynamic_cast< SpatialPartition & >(domain.getReference< PartitionBase >( keys::partitionManager ) ); + real64 size[3] = LVARRAY_TENSOROPS_INIT_LOCAL_3( m_max ); + LvArray::tensorOps::subtract< 3 >( size, m_min ); + cellBlockManager.setGlobalLength( LvArray::tensorOps::l2Norm< 3 >( size ) ); // bool isRadialWithOneThetaPartition = false; @@ -584,24 +584,6 @@ void InternalMeshGenerator::generateMesh( DomainPartition & domain ) SortedArray< localIndex > & zposNodes = nodeSets["zpos"]; SortedArray< localIndex > & allNodes = nodeSets["all"]; - // Partition based on even spacing to get load balance - // Partition geometrical boundaries will be corrected in the end. - { - m_min[0] = m_vertices[0].front(); - m_min[1] = m_vertices[1].front(); - m_min[2] = m_vertices[2].front(); - - m_max[0] = m_vertices[0].back(); - m_max[1] = m_vertices[1].back(); - m_max[2] = m_vertices[2].back(); - - partition.setSizes( m_min, m_max ); - - real64 size[3] = LVARRAY_TENSOROPS_INIT_LOCAL_3( m_max ); - LvArray::tensorOps::subtract< 3 >( size, m_min ); - meshBody.setGlobalLengthScale( LvArray::tensorOps::l2Norm< 3 >( size ) ); - } - // Find elemCenters for even uniform element sizes array1d< array1d< real64 > > elemCenterCoords( 3 ); for( int i = 0; i < 3; ++i ) @@ -636,7 +618,7 @@ void InternalMeshGenerator::generateMesh( DomainPartition & domain ) // lastElemIndexInPartition[i] = -2; for( int k = 0; k < m_numElemsTotal[i]; ++k ) { - if( partition.isCoordInPartition( elemCenterCoords[i][k], i ) ) + if( m_spatialPartition.isCoordInPartition( elemCenterCoords[i][k], i ) ) { firstElemIndexInPartition[i] = k; break; @@ -647,7 +629,7 @@ void InternalMeshGenerator::generateMesh( DomainPartition & domain ) { for( int k = firstElemIndexInPartition[i]; k < m_numElemsTotal[i]; ++k ) { - if( partition.isCoordInPartition( elemCenterCoords[i][k], i ) ) + if( m_spatialPartition.isCoordInPartition( elemCenterCoords[i][k], i ) ) { lastElemIndexInPartition[i] = k; } @@ -741,7 +723,7 @@ void InternalMeshGenerator::generateMesh( DomainPartition & domain ) { numNodesInDir[i] = lastElemIndexInPartition[i] - firstElemIndexInPartition[i] + 2; } - reduceNumNodesForPeriodicBoundary( partition, numNodesInDir ); + reduceNumNodesForPeriodicBoundary( m_spatialPartition, numNodesInDir ); numNodes = numNodesInDir[0] * numNodesInDir[1] * numNodesInDir[2]; cellBlockManager.setNumNodes( numNodes ); @@ -768,7 +750,7 @@ void InternalMeshGenerator::generateMesh( DomainPartition & domain ) getNodePosition( globalIJK, m_trianglePattern, X[localNodeIndex] ); // Alter global node map for radial mesh - setNodeGlobalIndicesOnPeriodicBoundary( partition, globalIJK ); + setNodeGlobalIndicesOnPeriodicBoundary( m_spatialPartition, globalIJK ); nodeLocalToGlobal[localNodeIndex] = nodeGlobalIndex( globalIJK ); @@ -832,7 +814,7 @@ void InternalMeshGenerator::generateMesh( DomainPartition & domain ) // Reset the number of nodes in each dimension in case of periodic BCs so the element firstNodeIndex // calculation is correct? Not actually needed in parallel since we still have ghost nodes in that case and // the count has not been altered due to periodicity. - if( std::any_of( partition.m_Periodic.begin(), partition.m_Periodic.end(), []( int & dimPeriodic ) { return dimPeriodic == 1; } ) ) + if( std::any_of( m_spatialPartition.m_Periodic.begin(), m_spatialPartition.m_Periodic.end(), []( int & dimPeriodic ) { return dimPeriodic == 1; } ) ) { for( int i = 0; i < m_dim; ++i ) { diff --git a/src/coreComponents/mesh/generators/InternalMeshGenerator.hpp b/src/coreComponents/mesh/generators/InternalMeshGenerator.hpp index 112306c4802..c11f8415494 100644 --- a/src/coreComponents/mesh/generators/InternalMeshGenerator.hpp +++ b/src/coreComponents/mesh/generators/InternalMeshGenerator.hpp @@ -21,11 +21,12 @@ #include "codingUtilities/EnumStrings.hpp" #include "mesh/generators/MeshGeneratorBase.hpp" +#include "mesh/generators/CellBlockManager.hpp" +#include "mesh/mpiCommunications/SpatialPartition.hpp" namespace geos { -class SpatialPartition; /** * @class InternalMeshGenerator @@ -50,7 +51,6 @@ class InternalMeshGenerator : public MeshGeneratorBase */ static string catalogName() { return "InternalMesh"; } - virtual void generateMesh( DomainPartition & domain ) override; void importFieldOnArray( Block block, string const & blockName, @@ -263,6 +263,8 @@ class InternalMeshGenerator : public MeshGeneratorBase + virtual void fillCellBlockManager( CellBlockManager & cellBlockManager, array1d< int > const & partition ) override; + /** * @brief Convert ndim node spatialized index to node global index. * @param[in] node ndim spatialized array index diff --git a/src/coreComponents/mesh/generators/InternalWellGenerator.cpp b/src/coreComponents/mesh/generators/InternalWellGenerator.cpp index 2805ad3b022..8ac1ff49723 100644 --- a/src/coreComponents/mesh/generators/InternalWellGenerator.cpp +++ b/src/coreComponents/mesh/generators/InternalWellGenerator.cpp @@ -20,12 +20,9 @@ #include "InternalWellGenerator.hpp" #include "mesh/mpiCommunications/CommunicationTools.hpp" -#include "mesh/DomainPartition.hpp" -#include "mesh/MeshBody.hpp" -#include "mesh/WellElementRegion.hpp" -#include "mesh/WellElementSubRegion.hpp" -#include "mesh/PerforationData.hpp" #include "mesh/Perforation.hpp" +#include "mesh/generators/LineBlockABC.hpp" +#include "LvArray/src/genericTensorOps.hpp" #include "physicsSolvers/fluidFlow/wells/WellControls.hpp" namespace geos @@ -33,18 +30,16 @@ namespace geos using namespace dataRepository; InternalWellGenerator::InternalWellGenerator( string const & name, Group * const parent ): - MeshGeneratorBase( name, parent ), + WellGeneratorBase( name, parent ), m_numElemsPerSegment( 0 ), m_minSegmentLength( 1e-2 ), m_minElemLength( 1e-3 ), m_radius( 0 ), m_wellRegionName( "" ), m_wellControlsName( "" ), - m_meshBodyName( "" ), m_numElems( 0 ), m_numNodesPerElem( 2 ), m_numNodes( 0 ), - m_numPerforations( 0 ), m_nDims( 3 ), m_polylineHeadNodeId( -1 ) { @@ -91,16 +86,6 @@ InternalWellGenerator::InternalWellGenerator( string const & name, Group * const setInputFlag( InputFlags::REQUIRED ). setSizedFromParent( 0 ). setDescription( "Name of the set of constraints associated with this well" ); - - registerWrapper( viewKeyStruct::meshNameString(), &m_meshBodyName ). - setInputFlag( InputFlags::REQUIRED ). - setSizedFromParent( 0 ). - setDescription( "Name of the reservoir mesh associated with this well" ); -} - -InternalWellGenerator::~InternalWellGenerator() -{ - // TODO Auto-generated destructor stub } void InternalWellGenerator::postProcessInput() @@ -130,11 +115,6 @@ void InternalWellGenerator::postProcessInput() ": Empty well region name.", InputError ); - GEOS_THROW_IF( m_meshBodyName.empty(), - "InternalWell " << getWrapperDataContext( viewKeyStruct::meshNameString() ) << - ": Empty mesh name.", - InputError ); - GEOS_THROW_IF( m_wellControlsName.empty(), "InternalWell " << getWrapperDataContext( viewKeyStruct::wellControlsNameString() ) << ": Empty well constraint name.", @@ -145,30 +125,7 @@ void InternalWellGenerator::postProcessInput() // TODO: check that with no branching we can go from top to bottom and touch all the elements } -Group * InternalWellGenerator::createChild( string const & childKey, string const & childName ) -{ - if( childKey == viewKeyStruct::perforationString() ) - { - ++m_numPerforations; - - // keep track of the perforations that have been added - m_perforationList.emplace_back( childName ); - - return ®isterGroup< Perforation >( childName ); - } - else - { - GEOS_THROW( getDataContext() << ": Unrecognized node with tag name " << childKey, InputError ); - } - return nullptr; -} - -void InternalWellGenerator::expandObjectCatalogs() -{ - createChild( viewKeyStruct::perforationString(), viewKeyStruct::perforationString() ); -} - -void InternalWellGenerator::generateMesh( DomainPartition & domain ) +void InternalWellGenerator::generateWellGeometry( ) { // count the number of well elements to create m_numElems = m_numElemsPerSegment * m_segmentToPolyNodeMap.size( 0 ); @@ -210,16 +167,6 @@ void InternalWellGenerator::generateMesh( DomainPartition & domain ) debugWellGeometry(); } - // get the element (sub) region to populate and save the well generator and constraints names - MeshLevel & meshLevel = domain.getMeshBody( 0 ).getBaseDiscretization(); - - ElementRegionManager & elemManager = meshLevel.getElemManager(); - WellElementRegion & - wellRegion = elemManager.getGroup( ElementRegionManager::groupKeyStruct::elementRegionsGroup() ). - getGroup< WellElementRegion >( this->m_wellRegionName ); - - wellRegion.setWellGeneratorName( this->getName() ); - wellRegion.setWellControlsName( m_wellControlsName ); } void InternalWellGenerator::constructPolylineNodeToSegmentMap() @@ -370,8 +317,8 @@ void InternalWellGenerator::discretizePolyline() // 2) set the node properties globalIndex const iwellNodeTop = iwelemCurrent; globalIndex const iwellNodeBottom = iwelemCurrent+1; - m_elemToNodesMap[iwelemCurrent][NodeLocation::TOP] = iwellNodeTop; - m_elemToNodesMap[iwelemCurrent][NodeLocation::BOTTOM] = iwellNodeBottom; + m_elemToNodesMap[iwelemCurrent][LineBlockABC::NodeLocation::TOP] = iwellNodeTop; + m_elemToNodesMap[iwelemCurrent][LineBlockABC::NodeLocation::BOTTOM] = iwellNodeBottom; real64 const scaleBottom = (iw + 1.0) / m_numElemsPerSegment; LvArray::tensorOps::copy< 3 >( m_nodeCoords[iwellNodeBottom], vPoly ); @@ -420,7 +367,7 @@ void InternalWellGenerator::connectPerforationsToWellElements() globalIndex iwelemBottom = m_numElems - 1; // check the validity of the perforation before starting - real64 const wellLength = m_nodeDistFromHead[m_elemToNodesMap[iwelemBottom][NodeLocation::BOTTOM]]; + real64 const wellLength = m_nodeDistFromHead[m_elemToNodesMap[iwelemBottom][LineBlockABC::NodeLocation::BOTTOM]]; GEOS_THROW_IF( m_perfDistFromHead[iperf] > wellLength, GEOS_FMT( "{}: Distance from well perforation to head ({} = {}) is larger than well" @@ -442,7 +389,7 @@ void InternalWellGenerator::connectPerforationsToWellElements() globalIndex iwelemMid = static_cast< globalIndex >(floor( static_cast< real64 >(iwelemTop + iwelemBottom) / 2.0 )); real64 const headToBottomDist = - m_nodeDistFromHead[m_elemToNodesMap[iwelemMid][NodeLocation::BOTTOM]]; + m_nodeDistFromHead[m_elemToNodesMap[iwelemMid][LineBlockABC::NodeLocation::BOTTOM]]; if( headToBottomDist < m_perfDistFromHead[iperf] ) { @@ -469,8 +416,8 @@ void InternalWellGenerator::connectPerforationsToWellElements() m_perfElemId[iperf] = iwelemMatched; // compute the physical location of the perforation - globalIndex const inodeTop = m_elemToNodesMap[iwelemMatched][NodeLocation::TOP]; - globalIndex const inodeBottom = m_elemToNodesMap[iwelemMatched][NodeLocation::BOTTOM]; + globalIndex const inodeTop = m_elemToNodesMap[iwelemMatched][LineBlockABC::NodeLocation::TOP]; + globalIndex const inodeBottom = m_elemToNodesMap[iwelemMatched][LineBlockABC::NodeLocation::BOTTOM]; real64 const elemLength = m_nodeDistFromHead[inodeBottom] - m_nodeDistFromHead[inodeTop]; real64 const topToPerfDist = m_perfDistFromHead[iperf] - m_nodeDistFromHead[inodeTop]; @@ -645,5 +592,5 @@ void InternalWellGenerator::debugWellGeometry() const } -REGISTER_CATALOG_ENTRY( MeshGeneratorBase, InternalWellGenerator, string const &, Group * const ) +REGISTER_CATALOG_ENTRY( WellGeneratorBase, InternalWellGenerator, string const &, Group * const ) } diff --git a/src/coreComponents/mesh/generators/InternalWellGenerator.hpp b/src/coreComponents/mesh/generators/InternalWellGenerator.hpp index 69ef1c7a0f2..ca3622b7b60 100644 --- a/src/coreComponents/mesh/generators/InternalWellGenerator.hpp +++ b/src/coreComponents/mesh/generators/InternalWellGenerator.hpp @@ -20,7 +20,12 @@ #ifndef GEOS_MESH_GENERATORS_INTERNALWELLGENERATOR_HPP_ #define GEOS_MESH_GENERATORS_INTERNALWELLGENERATOR_HPP_ -#include "MeshGeneratorBase.hpp" +#include "WellGeneratorBase.hpp" + +#include "dataRepository/Group.hpp" +#include "codingUtilities/Utilities.hpp" +#include "common/DataTypes.hpp" + namespace geos { @@ -30,18 +35,10 @@ namespace geos * * This class processes the data of a single well from the XML and generates the well geometry */ -class InternalWellGenerator : public MeshGeneratorBase +class InternalWellGenerator : public WellGeneratorBase { public: - /** - * @brief Struct to define the top and bottom node of a segment. - */ - struct NodeLocation - { - static constexpr integer TOP = 0; /**< Top of the well */ - static constexpr integer BOTTOM = 1; /**< Bottom of the well */ - }; /** * @name Constructor / Destructor @@ -57,115 +54,96 @@ class InternalWellGenerator : public MeshGeneratorBase Group * const parent ); /** - * @brief Default destructor. + * @brief Get the catalog name. + * @return the name of this type in the catalog */ - virtual ~InternalWellGenerator() override; + static string catalogName() { return "InternalWell"; } - ///@} /** - * @name Static Factory Catalog Functions + * @brief Main function of the class that generates the well geometry */ - ///@{ + void generateWellGeometry( ) override; - /** - * @brief Get the catalog name. - * @return the name of this type in the catalog - */ - static string catalogName() { return "InternalWell"; } ///@} /** - * @name Overriding functions defined in MeshGeneratorBase and above + * @name Getters / Setters */ ///@{ + // getters for element data + /** - * @brief Creates a new sub-Group using the ObjectCatalog functionality. - * @param[in] childKey The name of the new object type's key in the - * ObjectCatalog. - * @param[in] childName The name of the new object in the collection of - * sub-Groups. - * @return A pointer to the new Group created by this function. + * @brief Get the global number of well elements. + * @return the global number of elements */ - virtual Group * createChild( string const & childKey, - string const & childName ) override; + globalIndex numElements() const override { return m_numElems; } /** - * @brief Expand any catalogs in the data structure. + * @brief Getter to the Segment to PolyNode mapping + * @return The Segment to PolyNode mapping as a 2D array */ - virtual void expandObjectCatalogs() override; + const array2d< globalIndex > & getSegmentToPolyNodeMap() const override { return m_segmentToPolyNodeMap; }; /** - * @brief Main function of the class that generates the well geometry - * @param[in] domain the domain object - */ - virtual void generateMesh( DomainPartition & domain ) override; - - void importFieldOnArray( Block block, - string const & blockName, - string const & meshFieldName, - bool isMaterialField, - dataRepository::WrapperBase & wrapper ) const override - { - GEOS_UNUSED_VAR( block ); - GEOS_UNUSED_VAR( blockName ); - GEOS_UNUSED_VAR( meshFieldName ); - GEOS_UNUSED_VAR( isMaterialField ); - GEOS_UNUSED_VAR( wrapper ); - } - - ///@} + * @brief Get the number of nodes per well element + * @return the number of nodes per well element + */ + globalIndex numNodesPerElement() const override { return m_numNodesPerElem; } /** - * @name Getters / Setters + * @brief Get the Coordinates of the polyline nodes + * @return the Coordinates of the polyline nodes */ - ///@{ + const array2d< real64 > & getPolyNodeCoord() const override { return m_polyNodeCoords; } - // getters for element data + /** + * @return The minimum segment length + */ + real64 getMinSegmentLength() const override { return m_minSegmentLength; } /** - * @brief Get the global number of well elements. - * @return the global number of elements + * @return The minimum element length */ - globalIndex getNumElements() const { return m_numElems; } + real64 getMinElemLength() const override { return m_minElemLength; } /** * @brief Get the physical location of the centers of well elements. * @return list of center locations of the well elements */ - arrayView2d< real64 const > getElemCoords() const { return m_elemCenterCoords; } + arrayView2d< real64 const > getElemCoords() const override { return m_elemCenterCoords; } /** * @brief Get the global indices mapping an element to the next. * @return list providing the global index of the next element for each element */ - arrayView1d< globalIndex const > getNextElemIndex() const { return m_nextElemId; } + arrayView1d< globalIndex const > getNextElemIndex() const override { return m_nextElemId; } /** * @brief Get the global indices mapping an element to the previous ones. * @return list providing the global indices of the previous elements for each element */ - arrayView1d< arrayView1d< globalIndex const > const > getPrevElemIndices() const { return m_prevElemId.toNestedViewConst(); } + arrayView1d< arrayView1d< globalIndex const > const > getPrevElemIndices() const override { return m_prevElemId.toNestedViewConst(); } /** * @brief Get the global indices of the well nodes nodes connected to each element. * @return list providing the global index of the well nodes for each well element */ - arrayView2d< globalIndex const > getElemToNodesMap() const { return m_elemToNodesMap; } + arrayView2d< globalIndex const > getElemToNodesMap() const override { return m_elemToNodesMap; } /** * @brief Get the volume of the well elements. * @return list of volumes of the well elements */ - arrayView1d< real64 const > getElemVolume() const { return m_elemVolume; } + arrayView1d< real64 const > getElemVolume() const override { return m_elemVolume; } /** * @brief Get the radius in the well. * @return the radius in the well */ - real64 getElementRadius() const { return m_radius; } + real64 getElementRadius() const override { return m_radius; } // getters for node data @@ -173,59 +151,45 @@ class InternalWellGenerator : public MeshGeneratorBase * @brief Get the global number of well nodes. * @return the global number of nodes */ - globalIndex getNumNodes() const { return m_numNodes; } + globalIndex numNodes() const override { return m_numNodes; } /** * @brief Get the physical location of the centers of well elements. * @return list of center locations of the well elements */ - arrayView2d< real64 const > getNodeCoords() const { return m_nodeCoords; } - - + arrayView2d< real64 const > getNodeCoords() const override { return m_nodeCoords; } // getters for perforation data - /** - * @brief Get the global number of perforations on this well. - * @return the global number of elements - */ - globalIndex getNumPerforations() const { return m_numPerforations; } - /** * @brief Get the locations of the perforations. * @return list of locations of all the perforations on the well */ - arrayView2d< real64 const > getPerfCoords() const { return m_perfCoords; } + arrayView2d< real64 const > getPerfCoords() const override { return m_perfCoords; } /** * @brief Get the well transmissibility at the perforations. * @return list of well transmissibility at all the perforations on the well */ - arrayView1d< real64 const > getPerfTransmissibility() const { return m_perfTransmissibility; } + arrayView1d< real64 const > getPerfTransmissibility() const override { return m_perfTransmissibility; } /** * @brief Get the global indices of the well elements connected to each perforation. * @return list providing the global index of the connected well element for each perforation */ - arrayView1d< globalIndex const > getPerfElemIndex() const { return m_perfElemId; } + arrayView1d< globalIndex const > getPerfElemIndex() const override { return m_perfElemId; } + + /** + * @returns The number of physical dimensions + */ + int getPhysicalDimensionsNumber() const override { return m_nDims; } ///@} - ///@cond DO_NOT_DOCUMENT - struct viewKeyStruct - { - constexpr static char const * polylineNodeCoordsString() { return "polylineNodeCoords"; } - constexpr static char const * polylineSegmentConnString() { return "polylineSegmentConn"; } - constexpr static char const * numElementsPerSegmentString() { return "numElementsPerSegment"; } - constexpr static char const * minSegmentLengthString() { return "minSegmentLength"; } - constexpr static char const * minElementLengthString() { return "minElementLength"; } - constexpr static char const * radiusString() { return "radius"; } - constexpr static char const * wellRegionNameString() { return "wellRegionName"; } - constexpr static char const * wellControlsNameString() { return "wellControlsName"; } - constexpr static char const * meshNameString() { return "meshName"; } - constexpr static char const * perforationString() { return "Perforation"; } - }; - /// @endcond + const string getWellRegionName() const override { return m_wellRegionName; } + const string getWellControlsName() const override { return m_wellControlsName; } + + protected: @@ -311,9 +275,6 @@ class InternalWellGenerator : public MeshGeneratorBase /// Name of the constraints associated with this well string m_wellControlsName; - /// Name of the mesh body associated with this well - string m_meshBodyName; - // Geometry of the well (later passed to the WellElementSubRegion) @@ -352,9 +313,6 @@ class InternalWellGenerator : public MeshGeneratorBase // perforation data - /// Global number of perforations - globalIndex m_numPerforations; - /// Absolute physical location of the perforation array2d< real64 > m_perfCoords; @@ -385,13 +343,9 @@ class InternalWellGenerator : public MeshGeneratorBase // Perforation data - /// List of perforation names - string_array m_perforationList; - /// Physical location of the perforation wrt to well head array1d< real64 > m_perfDistFromHead; }; } - #endif /* GEOS_MESH_GENERATORS_INTERNALWELLGENERATOR_HPP_ */ diff --git a/src/coreComponents/mesh/generators/InternalWellboreGenerator.cpp b/src/coreComponents/mesh/generators/InternalWellboreGenerator.cpp index 2125787971d..9dfe5df2c53 100644 --- a/src/coreComponents/mesh/generators/InternalWellboreGenerator.cpp +++ b/src/coreComponents/mesh/generators/InternalWellboreGenerator.cpp @@ -307,11 +307,11 @@ void InternalWellboreGenerator::reduceNumNodesForPeriodicBoundary( SpatialPartit { if( m_isFullAnnulus ) { - if( partition.m_Partitions[1] == 1 ) + if( partition.getPartitions()[1] == 1 ) { numNodesInDir[1] -= 1; } - else if( partition.m_Partitions[1] > 1 ) + else if( partition.getPartitions()[1] > 1 ) { partition.m_Periodic[1] = 1; } diff --git a/src/coreComponents/mesh/generators/LineBlock.cpp b/src/coreComponents/mesh/generators/LineBlock.cpp new file mode 100644 index 00000000000..b49289325b4 --- /dev/null +++ b/src/coreComponents/mesh/generators/LineBlock.cpp @@ -0,0 +1,41 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2018-2020 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2020 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2018-2020 TotalEnergies + * Copyright (c) 2019- GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +/** + * @file DomainPartition.cpp + */ + +#include "mesh/Perforation.hpp" +#include "mesh/generators/LineBlock.hpp" +#include "mesh/generators/InternalWellGenerator.hpp" + + + +namespace geos +{ + +LineBlock::LineBlock( string const & name, Group * const parent ): + LineBlockABC( name, parent ) +{} + +void LineBlock::setPrevElemIndices( arrayView1d< arrayView1d< globalIndex const > const > prevElemIndices ) +{ + int size = prevElemIndices.size(); + m_prevElemId.resize( size ); + for( int i = 0; i < size; i++ ) + { + m_prevElemId[i] = prevElemIndices[i]; + } +} +} diff --git a/src/coreComponents/mesh/generators/LineBlock.hpp b/src/coreComponents/mesh/generators/LineBlock.hpp new file mode 100644 index 00000000000..96a889e9ab4 --- /dev/null +++ b/src/coreComponents/mesh/generators/LineBlock.hpp @@ -0,0 +1,251 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2018-2020 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2020 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2018-2020 TotalEnergies + * Copyright (c) 2020- GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +#ifndef GEOSX_WELLBLOCK_HPP +#define GEOSX_WELLBLOCK_HPP + +#include "mesh/generators/LineBlockABC.hpp" +#include "mesh/generators/InternalWellGenerator.hpp" + + +namespace geos +{ + +/** + * Implementation of the LineBlock responsible for modification/creation capabilities. + */ +class LineBlock : public LineBlockABC +{ +public: + /** + * @brief Constructor for this class. + * @param[in] name the name of this object manager + * @param[in] parent the parent Group + */ + LineBlock( string const & name, Group * const parent ); + + /** + * @name Getters / Setters + */ + ///@{ + + // getters for element data + + globalIndex numElements() const override final { return m_numElems; } + + /** + * @brief Set the global number of well elements. + * @param numElems the global number of elements + */ + void setNumElements( globalIndex numElems ) { m_numElems = numElems; } + + arrayView2d< real64 const > getElemCoords() const override final { return m_elemCenterCoords; } + + /** + * @brief Set the physical location of the centers of well elements. + * @param elemCenterCoords list of center locations of the well elements + */ + void setElemCoords( arrayView2d< real64 const > elemCenterCoords ) { m_elemCenterCoords = elemCenterCoords; } + + arrayView1d< globalIndex const > getNextElemIndex() const override final { return m_nextElemId; } + + /** + * @brief Set the global indices mapping an element to the next. + * @param nextElemId list providing the global index of the next element for each element + */ + void setNextElemIndex( arrayView1d< globalIndex const > nextElemId ) { m_nextElemId = nextElemId; } + + arrayView1d< arrayView1d< globalIndex const > const > getPrevElemIndices() const override final { return m_prevElemId.toNestedViewConst(); } + + + /** + * @brief Set the global indices mapping an element to the previous ones. + * @param prevElemIndices list providing the global indices of the previous elements for each element + */ + void setPrevElemIndices( arrayView1d< arrayView1d< globalIndex const > const > prevElemIndices ); + + arrayView2d< globalIndex const > getElemToNodesMap() const override final { return m_elemToNodesMap; } + + + /** + * @brief Set the global indices of the well nodes nodes connected to each element. + * @param elemToNodesMap list providing the global index of the well nodes for each well element + */ + void setElemToNodesMap( arrayView2d< globalIndex const > elemToNodesMap ) { m_elemToNodesMap = elemToNodesMap; } + + arrayView1d< real64 const > getElemVolume() const override final { return m_elemVolume; } + + + /** + * @brief Set the volume of the well elements. + * @param elemVolume list of volumes of the well elements + */ + void setElemVolume( arrayView1d< real64 const > elemVolume ) { m_elemVolume = elemVolume; } + + real64 getElementRadius() const override final { return m_radius; } + + + /** + * @brief Set the radius in the well. + * @param radius the radius in the well + */ + void setElementRadius( real64 radius ) { m_radius = radius; } + + // getters for node data + + globalIndex numNodes() const override final { return m_numNodes; } + + + /** + * @brief Set the global number of well nodes. + * @param numNodes the global number of nodes + */ + void setNumNodes( globalIndex numNodes ) { m_numNodes = numNodes; } + + arrayView2d< real64 const > getNodeCoords() const override final { return m_nodeCoords; } + + /** + * @brief Set the physical location of the centers of well elements. + * @param nodeCoords list of center locations of the well elements + */ + void setNodeCoords( arrayView2d< real64 const > nodeCoords ) { m_nodeCoords = nodeCoords; } + + + + // getters for perforation data + + globalIndex numPerforations() const override final { return m_numPerforations; } + + + /** + * @brief Set the global number of perforations on this well. + * @param numPerforations the global number of elements + */ + void setNumPerforations( globalIndex numPerforations ) { m_numPerforations = numPerforations; } + + arrayView2d< real64 const > getPerfCoords() const override final { return m_perfCoords; } + + + /** + * @brief Set the locations of the perforations. + * @param perfCoords list of locations of all the perforations on the well + */ + void setPerfCoords( arrayView2d< real64 const > perfCoords ) { m_perfCoords = perfCoords; } + + arrayView1d< real64 const > getPerfTransmissibility() const override final { return m_perfTransmissibility; } + + + /** + * @brief Set the well transmissibility at the perforations. + * @param perfTransmissibility list of well transmissibility at all the perforations on the well + */ + void setPerfTransmissibility( arrayView1d< real64 const > perfTransmissibility ) { m_perfTransmissibility = perfTransmissibility; } + + arrayView1d< globalIndex const > getPerfElemIndex() const override final { return m_perfElemId; } + + /** + * @brief Set the global indices of the well elements connected to each perforation. + * @param perfElemId list providing the global index of the connected well element for each perforation + */ + void setPerfElemIndex( arrayView1d< globalIndex const > perfElemId ) { m_perfElemId = perfElemId; } + + /** + * @brief Set the well controls name + * @param wellControlsName The well controls name + */ + void setWellControlsName( string const & wellControlsName ) { m_wellControlsName = wellControlsName; } + string const & getWellControlsName( ) const override final { return m_wellControlsName; } + + /** + * @brief Set the well genrator name + * @param wellGeneratorName The well genrator name + */ + void setWellGeneratorName( string const & wellGeneratorName ) { m_wellGeneratorName = wellGeneratorName; } + string const & getWellGeneratorName( ) const override final { return m_wellGeneratorName; } + + ///@} + + /// @endcond + + + +private: + + + // XML Input + + /// Radius area of the well (assumed to be valid for the entire well) + real64 m_radius; + + // Geometry of the well (later passed to the WellElementSubRegion) + + // well element data + + /// Global number of well elements + globalIndex m_numElems; + + /// Physical location of the center of the well element + array2d< real64 > m_elemCenterCoords; + + /// Global index of the next well element + array1d< globalIndex > m_nextElemId; + + /// Global indices of the prev well elements (maybe need multiple prevs for branching) + array1d< array1d< globalIndex > > m_prevElemId; + + /// Connectivity between elements and nodes + array2d< globalIndex > m_elemToNodesMap; + + /// Volume of well elements + array1d< real64 > m_elemVolume; + + + // well node data + + /// Global number of well nodes + globalIndex m_numNodes; + + /// Physical location of the nodes + array2d< real64 > m_nodeCoords; + + // perforation data + + /// Global number of perforations + globalIndex m_numPerforations; + + /// Absolute physical location of the perforation + array2d< real64 > m_perfCoords; + + /// Well Peaceman index at the perforation + array1d< real64 > m_perfTransmissibility; + + /// Global index of the well element + array1d< globalIndex > m_perfElemId; + + + + // Perforation data + + /// List of perforation names + string_array m_perforationList; + + /// Well controls name + string m_wellControlsName; + + /// Well genrator name + string m_wellGeneratorName; + +}; +} +#endif diff --git a/src/coreComponents/mesh/generators/LineBlockABC.hpp b/src/coreComponents/mesh/generators/LineBlockABC.hpp new file mode 100644 index 00000000000..f0a80a94e4e --- /dev/null +++ b/src/coreComponents/mesh/generators/LineBlockABC.hpp @@ -0,0 +1,164 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2018-2020 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2020 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2018-2020 TotalEnergies + * Copyright (c) 2020- GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +#ifndef GEOSX_WELLBLOCKABC_HPP +#define GEOSX_WELLBLOCKABC_HPP + +#include "dataRepository/Group.hpp" +#include "mesh/ElementType.hpp" +#include "common/DataTypes.hpp" +#include "dataRepository/Group.hpp" + +#include + +namespace geos +{ + +/** + * Abstract base class defining the information provided by any well block implementation. + * + * It's noteworthy that the WellblockABC is immutable oriented. + * The derived implementations need to have the modification/creation capabilities. + */ +class LineBlockABC : public dataRepository::Group +{ +public: + + /** + * @brief Struct to define the top and bottom node of a segment. + */ + struct NodeLocation + { + static constexpr integer TOP = 0; /**< Top of the well */ + static constexpr integer BOTTOM = 1; /**< Bottom of the well */ + }; + + /** + * @brief Constructor + * @param name The name of this Group. + * @param parent The parent Group. + */ + LineBlockABC( string const & name, + Group * const parent ) + : + Group( name, parent ) + { } + + /** + * @name Getters / Setters + */ + ///@{ + + // getters for element data + + /** + * @brief Get the global number of well elements. + * @return the global number of elements + */ + virtual globalIndex numElements() const = 0; + + /** + * @brief Get the physical location of the centers of well elements. + * @return list of center locations of the well elements + */ + virtual arrayView2d< real64 const > getElemCoords() const = 0; + + /** + * @brief Get the global indices mapping an element to the next. + * @return list providing the global index of the next element for each element + */ + virtual arrayView1d< globalIndex const > getNextElemIndex() const = 0; + + /** + * @brief Get the global indices mapping an element to the previous ones. + * @return list providing the global indices of the previous elements for each element + */ + virtual arrayView1d< arrayView1d< globalIndex const > const > getPrevElemIndices() const = 0; + + /** + * @brief Get the global indices of the well nodes nodes connected to each element. + * @return list providing the global index of the well nodes for each well element + */ + virtual arrayView2d< globalIndex const > getElemToNodesMap() const = 0; + + /** + * @brief Get the volume of the well elements. + * @return list of volumes of the well elements + */ + virtual arrayView1d< real64 const > getElemVolume() const = 0; + + /** + * @brief Get the radius in the well. + * @return the radius in the well + */ + virtual real64 getElementRadius() const = 0; + + // getters for node data + + /** + * @brief Get the global number of well nodes. + * @return the global number of nodes + */ + virtual globalIndex numNodes() const = 0; + + /** + * @brief Get the physical location of the centers of well elements. + * @return list of center locations of the well elements + */ + virtual arrayView2d< real64 const > getNodeCoords() const = 0; + + + + // getters for perforation data + + /** + * @brief Get the global number of perforations on this well. + * @return the global number of elements + */ + virtual globalIndex numPerforations() const = 0; + + /** + * @brief Get the locations of the perforations. + * @return list of locations of all the perforations on the well + */ + virtual arrayView2d< real64 const > getPerfCoords() const = 0; + + /** + * @brief Get the well transmissibility at the perforations. + * @return list of well transmissibility at all the perforations on the well + */ + virtual arrayView1d< real64 const > getPerfTransmissibility() const = 0; + + /** + * @brief Get the global indices of the well elements connected to each perforation. + * @return list providing the global index of the connected well element for each perforation + */ + virtual arrayView1d< globalIndex const > getPerfElemIndex() const = 0; + + /** + * @brief Get the well controls name + * @return The well controls name + */ + virtual string const & getWellControlsName() const = 0; + + /** + * @brief Get the well generator name + * @return The well generator name + */ + virtual string const & getWellGeneratorName() const = 0; +}; + +} + +#endif //GEOSX_WELLBLOCKABC_HPP diff --git a/src/coreComponents/mesh/generators/MeshGeneratorBase.cpp b/src/coreComponents/mesh/generators/MeshGeneratorBase.cpp index de912ce6a13..5d6019c3eaa 100644 --- a/src/coreComponents/mesh/generators/MeshGeneratorBase.cpp +++ b/src/coreComponents/mesh/generators/MeshGeneratorBase.cpp @@ -13,6 +13,7 @@ */ #include "MeshGeneratorBase.hpp" +#include "mesh/generators/CellBlockManager.hpp" namespace geos { @@ -26,10 +27,18 @@ MeshGeneratorBase::MeshGeneratorBase( string const & name, Group * const parent Group * MeshGeneratorBase::createChild( string const & childKey, string const & childName ) { - // Mesh generators generally don't have child XML nodes, must override this method to enable - GEOS_THROW( GEOS_FMT( "Mesh '{}': invalid child XML node '{}' of type {}", - getDataContext(), childName, childKey ), - InputError ); + GEOS_LOG_RANK_0( "Adding Mesh attribute: " << childKey << ", " << childName ); + std::unique_ptr< WellGeneratorBase > wellGen = WellGeneratorBase::CatalogInterface::factory( childKey, childName, this ); + return &this->registerGroup< WellGeneratorBase >( childName, std::move( wellGen ) ); +} + +void MeshGeneratorBase::expandObjectCatalogs() +{ + // During schema generation, register one of each type derived from WellGeneratorBase here + for( auto & catalogIter: WellGeneratorBase::getCatalog()) + { + createChild( catalogIter.first, catalogIter.first ); + } } MeshGeneratorBase::CatalogInterface::CatalogType & MeshGeneratorBase::getCatalog() @@ -38,4 +47,38 @@ MeshGeneratorBase::CatalogInterface::CatalogType & MeshGeneratorBase::getCatalog return catalog; } +CellBlockManagerABC & MeshGeneratorBase::generateMesh( Group & parent, array1d< int > const & partition ) +{ + CellBlockManager & cellBlockManager = parent.registerGroup< CellBlockManager >( keys::cellManager ); + + fillCellBlockManager( cellBlockManager, partition ); + + this->attachWellInfo( cellBlockManager ); + + return cellBlockManager; +} + +void MeshGeneratorBase::attachWellInfo( CellBlockManager & cellBlockManager ) +{ + forSubGroups< WellGeneratorBase >( [&]( WellGeneratorBase & wellGen ) { + wellGen.generateWellGeometry( ); + LineBlock & lb = cellBlockManager.registerLineBlock( wellGen.getWellRegionName() ); + lb.setNumElements( wellGen.numElements() ); + lb.setElemCoords( wellGen.getElemCoords() ); + lb.setNextElemIndex( wellGen.getNextElemIndex() ); + lb.setPrevElemIndices( wellGen.getPrevElemIndices() ); + lb.setElemToNodesMap( wellGen.getElemToNodesMap() ); + lb.setElemVolume( wellGen.getElemVolume() ); + lb.setElementRadius( wellGen.getElementRadius() ); + lb.setNumNodes( wellGen.numNodes() ); + lb.setNodeCoords( wellGen.getNodeCoords() ); + lb.setNumPerforations( wellGen.numPerforations() ); + lb.setPerfCoords( wellGen.getPerfCoords() ); + lb.setPerfTransmissibility( wellGen.getPerfTransmissibility() ); + lb.setPerfElemIndex( wellGen.getPerfElemIndex() ); + lb.setWellControlsName( wellGen.getWellControlsName() ); + lb.setWellGeneratorName( wellGen.getName() ); + + } ); +} } diff --git a/src/coreComponents/mesh/generators/MeshGeneratorBase.hpp b/src/coreComponents/mesh/generators/MeshGeneratorBase.hpp index 61212fe86a4..003b2f6cd0f 100644 --- a/src/coreComponents/mesh/generators/MeshGeneratorBase.hpp +++ b/src/coreComponents/mesh/generators/MeshGeneratorBase.hpp @@ -19,18 +19,23 @@ #ifndef GEOS_MESH_GENERATORS_MESHGENERATORBASE_HPP #define GEOS_MESH_GENERATORS_MESHGENERATORBASE_HPP +#include "mesh/mpiCommunications/SpatialPartition.hpp" +#include "mesh/generators/CellBlockManagerABC.hpp" + #include "dataRepository/Group.hpp" #include "dataRepository/WrapperBase.hpp" #include "codingUtilities/Utilities.hpp" #include "common/DataTypes.hpp" + namespace geos { -namespace dataRepository -{} - -class DomainPartition; +// This forward declaration prevents from exposing the internals of the module, +// which are only accessed through some private functions signatures. +// In order to avoid this forward declaration, we could expose an ABC +// instead of exposing the MeshGeneratorBase implementation. +class CellBlockManager; /** * @class MeshGeneratorBase @@ -55,6 +60,9 @@ class MeshGeneratorBase : public dataRepository::Group */ static string catalogName() { return "MeshGeneratorBase"; } + /// This function is used to expand any catalogs in the data structure + virtual void expandObjectCatalogs() override; + /// using alias for templated Catalog meshGenerator type using CatalogInterface = dataRepository::CatalogInterface< MeshGeneratorBase, string const &, Group * const >; @@ -74,9 +82,11 @@ class MeshGeneratorBase : public dataRepository::Group /** * @brief Generate the mesh object the input mesh object. - * @param[in] domain the domain partition from which to construct the mesh object + * @param parent The parent group of the CellBlockManager. + * @param[in] partition Number of domaoins in each dimesion (X,Y,Z) + * @return The generated CellBlockManagerABC */ - virtual void generateMesh( DomainPartition & domain ) = 0; + CellBlockManagerABC & generateMesh( Group & parent, array1d< int > const & partition ); /** * @brief Describe which kind of block must be considered. @@ -122,12 +132,32 @@ class MeshGeneratorBase : public dataRepository::Group */ std::map< string, string > const & getSurfacicFieldsMapping() const { return m_surfacicFields; } + /** + * @brief Get the associatied SpatialPartition generated. + * @return The generated SpatialPartition + */ + SpatialPartition const & getSpatialPartition() const { return m_spatialPartition; } + protected: /// Mapping from volumic field source to GEOSX field. std::map< string, string > m_volumicFields; /// Mapping from surfacic field source to GEOSX field. std::map< string, string > m_surfacicFields; + + /// SpatialPartition associated with the mesh + SpatialPartition m_spatialPartition; + +private: + /** + * @brief Fill the cellBlockManager object . + * @param[inout] cellBlockManager the CellBlockManager that will receive the meshing information + * @param[in] partition The number of domains in each dimesion (X,Y,Z) + */ + virtual void fillCellBlockManager( CellBlockManager & cellBlockManager, array1d< int > const & partition ) = 0; + + void attachWellInfo( CellBlockManager & cellBlockManager ); + }; } diff --git a/src/coreComponents/mesh/generators/PartitionDescriptor.hpp b/src/coreComponents/mesh/generators/PartitionDescriptor.hpp new file mode 100644 index 00000000000..5e4ad6b782f --- /dev/null +++ b/src/coreComponents/mesh/generators/PartitionDescriptor.hpp @@ -0,0 +1,97 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2018-2020 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2020 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2018-2020 TotalEnergies + * Copyright (c) 2019- GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +/** + * @file PartitionDescriptor.hpp + */ + +#ifndef GEOSX_MESH_PARTITIONDESCRIPTOR_H_ +#define GEOSX_MESH_PARTITIONDESCRIPTOR_H_ + +#include "mesh/mpiCommunications/SpatialPartition.hpp" + +#include + + +namespace geos +{ + +/** + * @class PartitionDescriptor + * @brief Simple utility to retrieve partition information in case of Metis or Spatial partition. + */ +class PartitionDescriptor +{ +public: + /** + * @brief indicate if the partition is described using a Metis Neighbor list. + * @return A boolean indicating if the partition is described usins a Metis neighbor list. + */ + bool hasMetisNeighborList() const { return m_hasMetisNeighborList; } + + /** + * @brief Sets the boolean that indicates if the partition is described using a Metis Neighbor list. + * @param hasMetisNeighborList A boolean indicating if the partition is described usins a Metis neighbor list. + */ + void setHasMetisNeighborList( bool hasMetisNeighborList ) { m_hasMetisNeighborList = hasMetisNeighborList; } + + /** + * @brief Gets a reference to the list of metis neighbor list. + * @return A reference to the Metis neighbor list. + */ + std::set< int > const & getMetisNeighborList() const { return m_metisNeighborList; } + + /** + * @brief Sets the list of metis neighbor list. + * @param metisNeighborList A reference to the Metis neighbor list. + */ + void setMetisNeighborList( std::vector< int > const & metisNeighborList ) + { + m_metisNeighborList.clear(); + m_metisNeighborList.insert( metisNeighborList.cbegin(), metisNeighborList.cend() ); + } + + /** + * @brief indicate if the partition is described using a spatial partition. + * @return A boolean indicating if the parition is described using a spatial partition. + */ + bool hasSpatialPartition() const { return !m_hasMetisNeighborList; } + + /** + * @brief Sets the boolean that indicates if the partition is described using a Metis Neighbor list. + * @param hasSpatialPartition a boolean indicating if the parition is described using a spatial partition. + */ + void setHasSpatialPartition( bool hasSpatialPartition ) { m_hasMetisNeighborList = !hasSpatialPartition; } + + /** + * @brief Returns a reference to the spatialPartition + * @return The spatial partiton. + */ + SpatialPartition const & getSpatialPartition() const { return m_spatialPartition; } + + /** + * @brief Sets the spatialPartition + * @param spatialPartition The spatial partiton. + */ + void setSpatialPartition( SpatialPartition const & spatialPartition ) { m_spatialPartition = spatialPartition; } + +private: + + bool m_hasMetisNeighborList; //< Indicate if we use metis neighbor list or spatial partition to describe the partition + std::set< int > m_metisNeighborList; //< The list of neighbors computed wwith metis + SpatialPartition m_spatialPartition; //< The spatial partition +}; + +} +#endif /* GEOSX_MESH_PARTITIONDESCRIPTOR_H_ */ diff --git a/src/coreComponents/mesh/generators/VTKMeshGenerator.cpp b/src/coreComponents/mesh/generators/VTKMeshGenerator.cpp index bd8ca058988..041401ec342 100644 --- a/src/coreComponents/mesh/generators/VTKMeshGenerator.cpp +++ b/src/coreComponents/mesh/generators/VTKMeshGenerator.cpp @@ -21,7 +21,6 @@ #include "mesh/generators/VTKFaceBlockUtilities.hpp" #include "mesh/generators/VTKMeshGeneratorTools.hpp" #include "mesh/generators/CellBlockManager.hpp" -#include "mesh/MeshBody.hpp" #include "common/DataTypes.hpp" #include "common/DataLayouts.hpp" #include "common/MpiWrapper.hpp" @@ -73,7 +72,7 @@ VTKMeshGenerator::VTKMeshGenerator( string const & name, " If set to a positive value, the GlobalId arrays in the input mesh are used and required, and the simulation aborts if they are not available" ); } -void VTKMeshGenerator::generateMesh( DomainPartition & domain ) +void VTKMeshGenerator::fillCellBlockManager( CellBlockManager & cellBlockManager, array1d< int > const & ) { // TODO refactor void MeshGeneratorBase::generateMesh( DomainPartition & domain ) GEOS_MARK_FUNCTION; @@ -87,27 +86,21 @@ void VTKMeshGenerator::generateMesh( DomainPartition & domain ) GEOS_LOG_LEVEL_RANK_0( 2, " reading the dataset..." ); vtkSmartPointer< vtkDataSet > loadedMesh = vtk::loadMesh( m_filePath, m_mainBlockName ); GEOS_LOG_LEVEL_RANK_0( 2, " redistributing mesh..." ); - m_vtkMesh = vtk::redistributeMesh( *loadedMesh, comm, m_partitionMethod, m_partitionRefinement, m_useGlobalIds ); + m_vtkMesh = vtk::redistributeMesh( loadedMesh, comm, m_partitionMethod, m_partitionRefinement, m_useGlobalIds ); GEOS_LOG_LEVEL_RANK_0( 2, " finding neighbor ranks..." ); std::vector< vtkBoundingBox > boxes = vtk::exchangeBoundingBoxes( *m_vtkMesh, comm ); std::vector< int > const neighbors = vtk::findNeighborRanks( std::move( boxes ) ); - domain.getMetisNeighborList().insert( neighbors.begin(), neighbors.end() ); + m_spatialPartition.setMetisNeighborList( std::move( neighbors ) ); GEOS_LOG_LEVEL_RANK_0( 2, " done!" ); } - GEOS_LOG_RANK_0( GEOS_FMT( "{} '{}': generating GEOSX mesh data structure", catalogName(), getName() ) ); - MeshBody & meshBody = domain.getMeshBodies().registerGroup< MeshBody >( this->getName() ); - meshBody.createMeshLevel( 0 ); - - CellBlockManager & cellBlockManager = meshBody.registerGroup< CellBlockManager >( keys::cellManager ); GEOS_LOG_LEVEL_RANK_0( 2, " preprocessing..." ); m_cellMap = vtk::buildCellMap( *m_vtkMesh, m_attributeName ); GEOS_LOG_LEVEL_RANK_0( 2, " writing nodes..." ); - real64 const globalLength = writeNodes( getLogLevel(), *m_vtkMesh, m_nodesetNames, cellBlockManager, this->m_translate, this->m_scale ); - meshBody.setGlobalLengthScale( globalLength ); + cellBlockManager.setGlobalLength( writeNodes( getLogLevel(), *m_vtkMesh, m_nodesetNames, cellBlockManager, this->m_translate, this->m_scale ) ); GEOS_LOG_LEVEL_RANK_0( 2, " writing cells..." ); writeCells( getLogLevel(), *m_vtkMesh, m_cellMap, cellBlockManager ); diff --git a/src/coreComponents/mesh/generators/VTKMeshGenerator.hpp b/src/coreComponents/mesh/generators/VTKMeshGenerator.hpp index 0c15c50b6be..6b1675a2405 100644 --- a/src/coreComponents/mesh/generators/VTKMeshGenerator.hpp +++ b/src/coreComponents/mesh/generators/VTKMeshGenerator.hpp @@ -51,7 +51,8 @@ class VTKMeshGenerator : public ExternalMeshGeneratorBase /** * @brief Generate the mesh using the VTK library. - * @param[in] domain the DomainPartition to be written + * @param[inout] cellBlockManager the CellBlockManager that will receive the meshing information + * @param[in] partition the number of domain in each direction (x,y,z) for InternalMesh only, not used here * @details This method leverages the VTK library to load the meshes. * The supported formats are the official VTK ones dedicated to * unstructured grids (.vtu, .pvtu and .vtk) and structured grids (.vts, .vti and .pvts). @@ -85,7 +86,7 @@ class VTKMeshGenerator : public ExternalMeshGeneratorBase * surfaces of interest, with triangles and/or quads holding an attribute value * of 1, 2 or 3, three node sets named "1", "2" and "3" will be instantiated by this method */ - virtual void generateMesh( DomainPartition & domain ) override; + virtual void fillCellBlockManager( CellBlockManager & cellBlockManager, array1d< int > const & partition ) override; void importFieldOnArray( Block block, string const & blockName, diff --git a/src/coreComponents/mesh/generators/VTKUtilities.cpp b/src/coreComponents/mesh/generators/VTKUtilities.cpp index 29381965567..d6ea945089e 100644 --- a/src/coreComponents/mesh/generators/VTKUtilities.cpp +++ b/src/coreComponents/mesh/generators/VTKUtilities.cpp @@ -480,12 +480,12 @@ loadMesh( Path const & filePath, * @return the vtk grid with global ids attributes */ vtkSmartPointer< vtkDataSet > -generateGlobalIDs( vtkDataSet & mesh ) +generateGlobalIDs( vtkSmartPointer< vtkDataSet > mesh ) { GEOS_MARK_FUNCTION; vtkNew< vtkGenerateGlobalIds > generator; - generator->SetInputDataObject( &mesh ); + generator->SetInputDataObject( mesh ); generator->Update(); return vtkDataSet::SafeDownCast( generator->GetOutputDataObject( 0 ) ); } @@ -593,31 +593,40 @@ findNeighborRanks( std::vector< vtkBoundingBox > boundingBoxes ) return neighbors; } -vtkSmartPointer< vtkDataSet > -redistributeMesh( vtkDataSet & loadedMesh, - MPI_Comm const comm, - PartitionMethod const method, - int const partitionRefinement, - int const useGlobalIds ) +vtkSmartPointer< vtkDataSet > manageGlobalIds( vtkSmartPointer< vtkDataSet > mesh, int useGlobalIds ) { - GEOS_MARK_FUNCTION; + auto hasGlobalIds = []( vtkSmartPointer< vtkDataSet > m ) -> bool + { + return m->GetPointData()->GetGlobalIds() != nullptr && m->GetCellData()->GetGlobalIds() != nullptr; + }; - // Generate global IDs for vertices and cells, if needed - vtkSmartPointer< vtkDataSet > mesh; - bool globalIdsAvailable = loadedMesh.GetPointData()->GetGlobalIds() != nullptr - && loadedMesh.GetCellData()->GetGlobalIds() != nullptr; + { + // Add global ids on the fly if needed + int const me = hasGlobalIds( mesh ); + int everyone; + MpiWrapper::allReduce( &me, &everyone, 1, MPI_MAX, MPI_COMM_GEOSX ); + + if( everyone and not me ) + { + mesh->GetPointData()->SetGlobalIds( vtkIdTypeArray::New() ); + mesh->GetCellData()->SetGlobalIds( vtkIdTypeArray::New() ); + } + } + + vtkSmartPointer< vtkDataSet > output; + bool const globalIdsAvailable = hasGlobalIds( mesh ); if( useGlobalIds > 0 && !globalIdsAvailable ) { GEOS_ERROR( "Global IDs strictly required (useGlobalId > 0) but unavailable. Set useGlobalIds to 0 to build them automatically." ); } else if( useGlobalIds >= 0 && globalIdsAvailable ) { - mesh = &loadedMesh; - vtkIdTypeArray const * const globalCellId = vtkIdTypeArray::FastDownCast( mesh->GetCellData()->GetGlobalIds() ); - vtkIdTypeArray const * const globalPointId = vtkIdTypeArray::FastDownCast( mesh->GetPointData()->GetGlobalIds() ); - GEOS_ERROR_IF( globalCellId->GetNumberOfComponents() != 1 && globalCellId->GetNumberOfTuples() != mesh->GetNumberOfCells(), + output = mesh; + vtkIdTypeArray const * const globalCellId = vtkIdTypeArray::FastDownCast( output->GetCellData()->GetGlobalIds() ); + vtkIdTypeArray const * const globalPointId = vtkIdTypeArray::FastDownCast( output->GetPointData()->GetGlobalIds() ); + GEOS_ERROR_IF( globalCellId->GetNumberOfComponents() != 1 && globalCellId->GetNumberOfTuples() != output->GetNumberOfCells(), "Global cell IDs are invalid. Check the array or enable automatic generation (useGlobalId < 0)" ); - GEOS_ERROR_IF( globalPointId->GetNumberOfComponents() != 1 && globalPointId->GetNumberOfTuples() != mesh->GetNumberOfPoints(), + GEOS_ERROR_IF( globalPointId->GetNumberOfComponents() != 1 && globalPointId->GetNumberOfTuples() != output->GetNumberOfPoints(), "Global cell IDs are invalid. Check the array or enable automatic generation (useGlobalId < 0)" ); GEOS_LOG_RANK_0( "Using global Ids defined in VTK mesh" ); @@ -625,12 +634,131 @@ redistributeMesh( vtkDataSet & loadedMesh, else { GEOS_LOG_RANK_0( "Generating global Ids from VTK mesh" ); - vtkNew< vtkGenerateGlobalIds > generator; - generator->SetInputDataObject( &loadedMesh ); - generator->Update(); - mesh = generateGlobalIDs( loadedMesh ); + output = generateGlobalIDs( mesh ); + } + + return output; +} + +/** + * @brief This function tries to make sure that no MPI rank is empty + * + * @param[in] mesh a vtk grid + * @param[in] comm the MPI communicator + * @return the vtk grid redistributed + */ +vtkSmartPointer< vtkDataSet > +ensureNoEmptyRank( vtkDataSet & mesh, + MPI_Comm const comm ) +{ + GEOS_MARK_FUNCTION; + + // step 1: figure out who is a donor and who is a recipient + localIndex const numElems = LvArray::integerConversion< localIndex >( mesh.GetNumberOfCells() ); + integer const numProcs = MpiWrapper::commSize( comm ); + + array1d< localIndex > elemCounts( numProcs ); + MpiWrapper::allGather( numElems, elemCounts, comm ); + + SortedArray< integer > recipientRanks; + array1d< integer > donorRanks; + recipientRanks.reserve( numProcs ); + donorRanks.reserve( numProcs ); + + for( integer iRank = 0; iRank < numProcs; ++iRank ) + { + if( elemCounts[iRank] == 0 ) + { + recipientRanks.insert( iRank ); + } + else if( elemCounts[iRank] > 1 ) // need at least two elems to be a donor + { + donorRanks.emplace_back( iRank ); + } + } + + // step 2: at this point, we need to determine the donors and which cells they donate + + // First we sort the donor in order of the number of elems they contain + std::stable_sort( donorRanks.begin(), donorRanks.end(), + [&elemCounts] ( auto i1, auto i2 ) + { return elemCounts[i1] < elemCounts[i2]; } ); + + // Then, if my position is "i" in the donorRanks array, I will send half of my elems to the i-th recipient + integer const myRank = MpiWrapper::commRank(); + auto const myPosition = + LvArray::sortedArrayManipulation::find( donorRanks.begin(), donorRanks.size(), myRank ); + bool const isDonor = myPosition != donorRanks.size(); + + // step 3: my rank was selected to donate cells, let's proceed + // we need to make a distinction between two configurations + + array1d< localIndex > newParts( numElems ); + newParts.setValues< parallelHostPolicy >( myRank ); + + // step 3.1: donorRanks.size() >= recipientRanks.size() + // we use a strategy that preserves load balancing + if( isDonor && donorRanks.size() >= recipientRanks.size() ) + { + if( myPosition < recipientRanks.size() ) + { + integer const recipientRank = recipientRanks[myPosition]; + for( localIndex iElem = numElems/2; iElem < numElems; ++iElem ) + { + newParts[iElem] = recipientRank; // I donate half of my cells + } + } + } + // step 3.2: donorRanks.size() < recipientRanks.size() + // this is the unhappy path: we don't care anymore about load balancing at this stage + // we just want the simulation to run and count on ParMetis/PTScotch to restore load balancing + else if( isDonor && donorRanks.size() < recipientRanks.size() ) + { + localIndex firstRecipientPosition = 0; + for( integer iRank = 0; iRank < myPosition; ++iRank ) + { + firstRecipientPosition += elemCounts[iRank] - 1; + } + if( firstRecipientPosition < recipientRanks.size() ) + { + bool const isLastDonor = myPosition == donorRanks.size() - 1; + localIndex const lastRecipientPosition = firstRecipientPosition + numElems - 1; + GEOS_THROW_IF( isLastDonor && ( lastRecipientPosition < recipientRanks.size() ), + "The current implementation is unable to guarantee that all ranks have at least one element", + std::runtime_error ); + + for( localIndex iElem = 1; iElem < numElems; ++iElem ) // I only keep my first element + { + // this is the brute force approach + // each donor donates all its elems except the first one + localIndex const recipientPosition = firstRecipientPosition + iElem - 1; + if( recipientPosition < recipientRanks.size() ) + { + newParts[iElem] = recipientRanks[recipientPosition]; + } + } + } } + GEOS_LOG_RANK_0_IF( donorRanks.size() < recipientRanks.size(), + "\nWarning! We strongly encourage the use of partitionRefinement > 5 for this number of MPI ranks \n" ); + + vtkSmartPointer< vtkPartitionedDataSet > const splitMesh = splitMeshByPartition( mesh, numProcs, newParts.toViewConst() ); + return vtk::redistribute( *splitMesh, MPI_COMM_GEOSX ); +} + + +vtkSmartPointer< vtkDataSet > +redistributeMesh( vtkSmartPointer< vtkDataSet > loadedMesh, + MPI_Comm const comm, + PartitionMethod const method, + int const partitionRefinement, + int const useGlobalIds ) +{ + GEOS_MARK_FUNCTION; + + // Generate global IDs for vertices and cells, if needed + vtkSmartPointer< vtkDataSet > mesh = manageGlobalIds( loadedMesh, useGlobalIds ); // Determine if redistribution is required vtkIdType const minCellsOnAnyRank = MpiWrapper::min( mesh->GetNumberOfCells(), comm ); @@ -640,6 +768,14 @@ redistributeMesh( vtkDataSet & loadedMesh, mesh = redistributeByKdTree( *mesh ); } + // Check if a rank does not have a cell after the redistribution + // If this is the case, we need a fix otherwise the next redistribution will fail + // We expect this function to only be called in some pathological cases + if( MpiWrapper::min( mesh->GetNumberOfCells(), comm ) == 0 ) + { + mesh = ensureNoEmptyRank( *mesh, comm ); + } + // Redistribute the mesh again using higher-quality graph partitioner if( partitionRefinement > 0 ) { @@ -1551,10 +1687,10 @@ void importMaterialField( std::vector< vtkIdType > const & cellIds, WrapperBase & wrapper ) { // Scalar material fields are stored as 2D arrays, vector/tensor are 3D - using ImportTypes = types::ArrayTypes< types::RealTypes, types::DimsRange< 2, 3 > >; - types::dispatch( ImportTypes{}, wrapper.getTypeId(), true, [&]( auto array ) + using ImportTypes = types::ListofTypeList< types::ArrayTypes< types::RealTypes, types::DimsRange< 2, 3 > > >; + types::dispatch( ImportTypes{}, [&]( auto tupleOfTypes ) { - using ArrayType = decltype( array ); + using ArrayType = camp::first< decltype( tupleOfTypes ) >; Wrapper< ArrayType > & wrapperT = Wrapper< ArrayType >::cast( wrapper ); auto const view = wrapperT.reference().toView(); @@ -1580,17 +1716,17 @@ void importMaterialField( std::vector< vtkIdType > const & cellIds, ++cellCount; } } ); - } ); + }, wrapper ); } void importRegularField( std::vector< vtkIdType > const & cellIds, vtkDataArray * vtkArray, WrapperBase & wrapper ) { - using ImportTypes = types::ArrayTypes< types::RealTypes, types::DimsRange< 1, 2 > >; - types::dispatch( ImportTypes{}, wrapper.getTypeId(), true, [&]( auto dstArray ) + using ImportTypes = types::ListofTypeList< types::ArrayTypes< types::RealTypes, types::DimsRange< 1, 2 > > >; + types::dispatch( ImportTypes{}, [&]( auto tupleOfTypes ) { - using ArrayType = decltype( dstArray ); + using ArrayType = camp::first< decltype( tupleOfTypes ) >; Wrapper< ArrayType > & wrapperT = Wrapper< ArrayType >::cast( wrapper ); auto const view = wrapperT.reference().toView(); @@ -1612,7 +1748,7 @@ void importRegularField( std::vector< vtkIdType > const & cellIds, ++cellCount; } } ); - } ); + }, wrapper ); } diff --git a/src/coreComponents/mesh/generators/VTKUtilities.hpp b/src/coreComponents/mesh/generators/VTKUtilities.hpp index f6ce768b7b9..284239fc3f8 100644 --- a/src/coreComponents/mesh/generators/VTKUtilities.hpp +++ b/src/coreComponents/mesh/generators/VTKUtilities.hpp @@ -95,7 +95,7 @@ findNeighborRanks( std::vector< vtkBoundingBox > boundingBoxes ); * @return the vtk grid redistributed */ vtkSmartPointer< vtkDataSet > -redistributeMesh( vtkDataSet & loadedMesh, +redistributeMesh( vtkSmartPointer< vtkDataSet > loadedMesh, MPI_Comm const comm, PartitionMethod const method, int const partitionRefinement, diff --git a/src/coreComponents/mesh/generators/WellGeneratorBase.cpp b/src/coreComponents/mesh/generators/WellGeneratorBase.cpp new file mode 100644 index 00000000000..98f93ecef7f --- /dev/null +++ b/src/coreComponents/mesh/generators/WellGeneratorBase.cpp @@ -0,0 +1,59 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2018-2020 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2020 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2018-2020 TotalEnergies + * Copyright (c) 2019- GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +#include "WellGeneratorBase.hpp" +#include "mesh/Perforation.hpp" + +namespace geos +{ +using namespace dataRepository; + +WellGeneratorBase::WellGeneratorBase( string const & name, Group * const parent ): + Group( name, parent ), + m_numPerforations( 0 ) +{ + setInputFlags( InputFlags::OPTIONAL_NONUNIQUE ); +} + +Group * WellGeneratorBase::createChild( string const & childKey, string const & childName ) +{ + if( childKey == viewKeyStruct::perforationString() ) + { + ++m_numPerforations; + + // keep track of the perforations that have been added + m_perforationList.emplace_back( childName ); + + return ®isterGroup< Perforation >( childName ); + } + else + { + GEOS_THROW( "Unrecognized node: " << childKey, InputError ); + } + return nullptr; +} + + +void WellGeneratorBase::expandObjectCatalogs() +{ + createChild( viewKeyStruct::perforationString(), viewKeyStruct::perforationString() ); +} + +WellGeneratorBase::CatalogInterface::CatalogType & WellGeneratorBase::getCatalog() +{ + static WellGeneratorBase::CatalogInterface::CatalogType catalog; + return catalog; +} + +} diff --git a/src/coreComponents/mesh/generators/WellGeneratorBase.hpp b/src/coreComponents/mesh/generators/WellGeneratorBase.hpp new file mode 100644 index 00000000000..32be277dc9b --- /dev/null +++ b/src/coreComponents/mesh/generators/WellGeneratorBase.hpp @@ -0,0 +1,244 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2018-2020 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2020 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2018-2020 TotalEnergies + * Copyright (c) 2019- GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +/* + * @file WellGeneratorBase.hpp + * + */ + +#ifndef GEOS_MESH_GENERATORS_WELLGENERATORBASE_HPP_ +#define GEOS_MESH_GENERATORS_WELLGENERATORBASE_HPP_ + +#include "dataRepository/Group.hpp" +#include "codingUtilities/Utilities.hpp" +#include "common/DataTypes.hpp" + + +namespace geos +{ + +/** + * @class WellGeneratorBase + * + * This class processes the data of a single well from the XML and generates the well geometry + */ +class WellGeneratorBase : public dataRepository::Group +{ +public: + + /** + * @brief Constructor. + * @param name name of the object in the data hierarchy. + * @param parent pointer to the parent group in the data hierarchy. + */ + WellGeneratorBase( const string & name, + Group * const parent ); + + /** + * @brief Get the catalog name. + * @return the name of this type in the catalog + */ + static string catalogName() { return "WellGeneratorBase"; } + + /// This function is used to expand any catalogs in the data structure + virtual void expandObjectCatalogs() override; + + /// using alias for templated Catalog meshGenerator type + using CatalogInterface = dataRepository::CatalogInterface< WellGeneratorBase, string const &, Group * const >; + + + /** + * @brief Create a new geometric object (box, plane, etc) as a child of this group. + * @param childKey the catalog key of the new geometric object to create + * @param childName the name of the new geometric object in the repository + * @return the group child + */ + virtual Group * createChild( string const & childKey, string const & childName ) override; + + /** + * @brief Accessor for the singleton Catalog object + * @return a static reference to the Catalog object + */ + static CatalogInterface::CatalogType & getCatalog(); + + /** + * @brief Main function of the class that generates the well geometry + */ + virtual void generateWellGeometry( ) = 0; + + /** + * @name Getters / Setters + */ + ///@{ + + // getters for element data + + /** + * @brief Get the global number of well elements. + * @return the global number of elements + */ + virtual globalIndex numElements() const = 0; + + /** + * @brief Getter to the Segment to PolyNode mapping + * @return The Segment to PolyNode mapping as a 2D array + */ + virtual const array2d< globalIndex > & getSegmentToPolyNodeMap() const = 0; + + /** + * @brief Get the number of nodes per well element + * @return the number of nodes per well element + */ + virtual globalIndex numNodesPerElement() const = 0; + + /** + * @brief Get the Coordinates of the polyline nodes + * @return the Coordinates of the polyline nodes + */ + virtual const array2d< real64 > & getPolyNodeCoord() const = 0; + + /** + * @return The minimum segment length + */ + virtual real64 getMinSegmentLength() const = 0; + + /** + * @return The minimum element length + */ + virtual real64 getMinElemLength() const = 0; + + /** + * @return The list of perforation names + */ + const string_array & getPerforationList() const { return m_perforationList; } + + /** + * @brief Get the physical location of the centers of well elements. + * @return list of center locations of the well elements + */ + virtual arrayView2d< real64 const > getElemCoords() const = 0; + + /** + * @brief Get the global indices mapping an element to the next. + * @return list providing the global index of the next element for each element + */ + virtual arrayView1d< globalIndex const > getNextElemIndex() const = 0; + + /** + * @brief Get the global indices mapping an element to the previous ones. + * @return list providing the global indices of the previous elements for each element + */ + virtual arrayView1d< arrayView1d< globalIndex const > const > getPrevElemIndices() const = 0; + + /** + * @brief Get the global indices of the well nodes nodes connected to each element. + * @return list providing the global index of the well nodes for each well element + */ + virtual arrayView2d< globalIndex const > getElemToNodesMap() const = 0; + + /** + * @brief Get the volume of the well elements. + * @return list of volumes of the well elements + */ + virtual arrayView1d< real64 const > getElemVolume() const = 0; + + /** + * @brief Get the radius in the well. + * @return the radius in the well + */ + virtual real64 getElementRadius() const = 0; + + // getters for node data + + /** + * @brief Get the global number of well nodes. + * @return the global number of nodes + */ + virtual globalIndex numNodes() const = 0; + + /** + * @brief Get the physical location of the centers of well elements. + * @return list of center locations of the well elements + */ + virtual arrayView2d< real64 const > getNodeCoords() const = 0; + + + + // getters for perforation data + /** + * @brief Get the global number of perforations on this well. + * @return the global number of elements + */ + globalIndex numPerforations() const { return m_numPerforations; } + + /** + * @brief Get the locations of the perforations. + * @return list of locations of all the perforations on the well + */ + virtual arrayView2d< real64 const > getPerfCoords() const = 0; + + /** + * @brief Get the well transmissibility at the perforations. + * @return list of well transmissibility at all the perforations on the well + */ + virtual arrayView1d< real64 const > getPerfTransmissibility() const = 0; + + /** + * @brief Get the global indices of the well elements connected to each perforation. + * @return list providing the global index of the connected well element for each perforation + */ + virtual arrayView1d< globalIndex const > getPerfElemIndex() const = 0; + + /** + * @returns The number of physical dimensions + */ + virtual int getPhysicalDimensionsNumber() const = 0; + + /** + * Getter for the associated well region name + * @return the associated well region name + */ + virtual const string getWellRegionName() const = 0; + + /** + * Getter for the associated well control name + * @return the associated well control name + */ + virtual const string getWellControlsName() const = 0; + + ///@cond DO_NOT_DOCUMENT + struct viewKeyStruct + { + constexpr static char const * polylineNodeCoordsString() { return "polylineNodeCoords"; } + constexpr static char const * polylineSegmentConnString() { return "polylineSegmentConn"; } + constexpr static char const * numElementsPerSegmentString() { return "numElementsPerSegment"; } + constexpr static char const * minSegmentLengthString() { return "minSegmentLength"; } + constexpr static char const * minElementLengthString() { return "minElementLength"; } + constexpr static char const * radiusString() { return "radius"; } + constexpr static char const * wellRegionNameString() { return "wellRegionName"; } + constexpr static char const * wellControlsNameString() { return "wellControlsName"; } + constexpr static char const * meshNameString() { return "meshName"; } + constexpr static char const * perforationString() { return "Perforation"; } + }; + /// @endcond + +protected: + /// Global number of perforations + globalIndex m_numPerforations; + + /// List of perforation names + string_array m_perforationList; +}; +} +#endif /* GEOS_MESH_GENERATORS_WELLGENERATORBASE_HPP_ */ diff --git a/src/coreComponents/mesh/mpiCommunications/SpatialPartition.cpp b/src/coreComponents/mesh/mpiCommunications/SpatialPartition.cpp index 978c31907ac..50fe92701bb 100644 --- a/src/coreComponents/mesh/mpiCommunications/SpatialPartition.cpp +++ b/src/coreComponents/mesh/mpiCommunications/SpatialPartition.cpp @@ -47,7 +47,6 @@ real64 MapValueToRange( real64 value, real64 min, real64 max ) SpatialPartition::SpatialPartition(): PartitionBase(), - m_Partitions(), m_Periodic( nsdof ), m_coords( nsdof ), m_min{ 0.0 }, @@ -55,7 +54,8 @@ SpatialPartition::SpatialPartition(): m_blockSize{ 1.0 }, m_gridSize{ 0.0 }, m_gridMin{ 0.0 }, - m_gridMax{ 0.0 } + m_gridMax{ 0.0 }, + m_Partitions() { m_size = 0; m_rank = 0; diff --git a/src/coreComponents/mesh/mpiCommunications/SpatialPartition.hpp b/src/coreComponents/mesh/mpiCommunications/SpatialPartition.hpp index 0ea26d090b6..8e47d2cf056 100644 --- a/src/coreComponents/mesh/mpiCommunications/SpatialPartition.hpp +++ b/src/coreComponents/mesh/mpiCommunications/SpatialPartition.hpp @@ -45,8 +45,34 @@ class SpatialPartition : public PartitionBase int getColor() override; - /// number of partitions - array1d< int > m_Partitions; + /** + * @brief Get the metis neighbors indices, const version. @see DomainPartition#m_metisNeighborList + * @return Container of global indices. + */ + std::set< int > const & getMetisNeighborList() const + { + return m_metisNeighborList; + } + + /** + * @brief Sets the list of metis neighbor list. + * @param metisNeighborList A reference to the Metis neighbor list. + */ + void setMetisNeighborList( std::vector< int > const & metisNeighborList ) + { + m_metisNeighborList.clear(); + m_metisNeighborList.insert( metisNeighborList.cbegin(), metisNeighborList.cend() ); + } + + /** + * @brief Get the number of domains in each dimension for a regular partition with InternalMesh. + * @return An array containing number of partition in X, Y and Z directions. + */ + array1d< int > const & getPartitions() const + { + return m_Partitions; + } + /** * @brief Boolean like array of length 3 (space dimensions). * @@ -103,6 +129,15 @@ class SpatialPartition : public PartitionBase * @brief Ghost position (max). */ real64 m_contactGhostMax[3]; + + /// number of partitions + array1d< int > m_Partitions; + + /** + * @brief Contains the global indices of the metis neighbors in case `metis` is used. Empty otherwise. + */ + std::set< int > m_metisNeighborList; + }; } diff --git a/src/coreComponents/mesh/simpleGeometricObjects/Box.cpp b/src/coreComponents/mesh/simpleGeometricObjects/Box.cpp index 25325c1d2d0..fc5aea65a46 100644 --- a/src/coreComponents/mesh/simpleGeometricObjects/Box.cpp +++ b/src/coreComponents/mesh/simpleGeometricObjects/Box.cpp @@ -65,6 +65,16 @@ void Box::postProcessInput() LvArray::tensorOps::add< 3 >( m_boxCenter, m_max ); LvArray::tensorOps::scale< 3 >( m_boxCenter, 0.5 ); + // reordering min and max to fit former interface but so that user can input any of the four diagonals + for( int i = 0; i < m_max.SIZE; ++i ) + { + if( m_max[i] 1e-20 ) { diff --git a/src/coreComponents/mesh/simpleGeometricObjects/CustomPolarObject.cpp b/src/coreComponents/mesh/simpleGeometricObjects/CustomPolarObject.cpp new file mode 100644 index 00000000000..22e1bc0c0fc --- /dev/null +++ b/src/coreComponents/mesh/simpleGeometricObjects/CustomPolarObject.cpp @@ -0,0 +1,88 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2018-2020 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2020 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2018-2020 TotalEnergies + * Copyright (c) 2019- GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +/** + * @file CustomPolarObject.cpp + */ + +#include "CustomPolarObject.hpp" +#include "LvArray/src/tensorOps.hpp" + +namespace geos +{ +using namespace dataRepository; + +CustomPolarObject::CustomPolarObject( const string & name, Group * const parent ): + PlanarGeometricObject( name, parent ), + m_center{ 0.0, 0.0, 0.0 }, + m_tolerance() +{ + registerWrapper( viewKeyStruct::centerString(), &m_center ). + setInputFlag( InputFlags::REQUIRED ). + setDescription( "(x,y,z) coordinates of the center of the CustomPolarObject" ); + + registerWrapper( viewKeyStruct::coefficientsString(), &m_coefficients ). + setInputFlag( InputFlags::REQUIRED ). + setDescription( "Coefficients of the CustomPolarObject function relating the local" + "radius to the angle theta." ); + + registerWrapper( viewKeyStruct::toleranceString(), &m_tolerance ). + setInputFlag( InputFlags::OPTIONAL ). + setDefaultValue( 1e-5 ). + setDescription( "Tolerance to determine if a point sits on the CustomPolarObject or not. " + "It is relative to the maximum dimension of the CustomPolarObject." ); + +} + +CustomPolarObject::~CustomPolarObject() +{} + +void CustomPolarObject::postProcessInput() +{ + // Make sure that you have an orthonormal basis. + LvArray::tensorOps::normalize< 3 >( m_normal ); + +} + +bool CustomPolarObject::isCoordInObject( real64 const ( &coord ) [3] ) const +{ + bool isInside = true; + + //gets the vector from coord to the center + real64 dummy[ 3 ] = LVARRAY_TENSOROPS_INIT_LOCAL_3( coord ); + LvArray::tensorOps::subtract< 3 >( dummy, m_center ); + + // 1. Check if point is on the plane of the CustomPolarObject + if( std::abs( LvArray::tensorOps::AiBi< 3 >( dummy, m_normal ) ) > m_tolerance ) + { + isInside = false; + } + + // 2. Get angle with the plane's length vector + real64 dummyNorm = sqrt( LvArray::tensorOps::l2NormSquared< 3 >( dummy )); + real64 cosTheta = LvArray::tensorOps::AiBi< 3 >( dummy, getLengthVector() )/(dummyNorm + 1e-15); //assume lengthVector is unitary + real64 theta = acos( cosTheta ); + + // 2. Check if it is inside the CustomPolarObject + if( LvArray::tensorOps::l2NormSquared< 3 >( dummy ) > getRadius( theta )*getRadius( theta ) ) + { + isInside = false; + } + + return isInside; +} + +REGISTER_CATALOG_ENTRY( SimpleGeometricObjectBase, CustomPolarObject, string const &, Group * const ) + +} /* namespace geosx */ diff --git a/src/coreComponents/mesh/simpleGeometricObjects/CustomPolarObject.hpp b/src/coreComponents/mesh/simpleGeometricObjects/CustomPolarObject.hpp new file mode 100644 index 00000000000..99faea5eca8 --- /dev/null +++ b/src/coreComponents/mesh/simpleGeometricObjects/CustomPolarObject.hpp @@ -0,0 +1,144 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2018-2020 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2020 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2018-2020 TotalEnergies + * Copyright (c) 2019- GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +/** + * @file CustomPolarObject.hpp + */ + +#ifndef GEOSX_MESH_SIMPLEGEOMETRICOBJECTS_CUSTOMPOLAROBJECT_HPP_ +#define GEOSX_MESH_SIMPLEGEOMETRICOBJECTS_CUSTOMPOLAROBJECT_HPP_ + +#include "PlanarGeometricObject.hpp" + +namespace geos +{ + +/** + * @class CustomPolarObject + * @brief Class to represent a geometric disc in GEOSX. + */ +class CustomPolarObject : public PlanarGeometricObject +{ +public: + + /** + * @name Constructor / Destructor + */ + ///@{ + + /** + * @brief Constructor. + * @param name name of the object in the data hierarchy. + * @param parent pointer to the parent group in the data hierarchy. + */ + CustomPolarObject( const string & name, + Group * const parent ); + + /** + * @brief Default destructor. + */ + virtual ~CustomPolarObject() override; + + ///@} + + /** + * @name Static Factory Catalog Functions + */ + ///@{ + + /** + * @brief Get the catalog name. + * @return the name of this class in the catalog + */ + static string catalogName() { return "CustomPolarObject"; } + + ///@} + + bool isCoordInObject( real64 const ( &coord ) [3] ) const override final; + + /** + * @name Getters + */ + ///@{ + + /** + * @brief Get the center of the CustomPolarObject. + * @return the center of the CustomPolarObject + */ + virtual R1Tensor & getCenter() override final {return m_center;} + + /** + * @copydoc getCenter() + */ + virtual R1Tensor const & getCenter() const override final {return m_center;} + + /** + * @brief Update the geometric function describing the boundary of the object. + * @param coefficients define all the coefficients of the radius function. + */ + void setCustomPolarObjectFunction( array1d< real64 > & coefficients ) + { + //m_radius.m_coefficients = coefficients; + m_coefficients = coefficients; + } + + /** + * @brief Get value of the radius of the surface for each angle theta + * @param angle the given angle + * @return the radius for that angle + */ + real64 getRadius( real64 angle ) const + { + real64 radius = 0; + integer count = 0; + for( auto coeff:m_coefficients ) + { + radius = radius + coeff*cos( count*angle ); + count++; + } + return radius; + } + +protected: + + /** + * @brief This function provides the capability to post process input values prior to + * any other initialization operations. + */ + virtual void postProcessInput() override final; + +private: + + /// center of the CustomPolarObject in (x,y,z) coordinates + R1Tensor m_center; + /// Coefficients of the polar function relating the radius to theta + array1d< real64 > m_coefficients; + /// tolerance to determine if a point sits on the CustomPolarObject or not + real64 m_tolerance; + + /// @cond DO_NOT_DOCUMENT + + struct viewKeyStruct + { + static constexpr char const * centerString() { return "center"; } + static constexpr char const * coefficientsString() { return "coefficients"; } + static constexpr char const * toleranceString() { return "tolerance"; } + }; + + /// @endcond + +}; +} /* namespace geosx */ + +#endif /* GEOSX_MESH_SIMPLEGEOMETRICOBJECTS_CustomPolarObject_HPP_*/ diff --git a/src/coreComponents/mesh/simpleGeometricObjects/Disc.cpp b/src/coreComponents/mesh/simpleGeometricObjects/Disc.cpp new file mode 100644 index 00000000000..eedd7bc979d --- /dev/null +++ b/src/coreComponents/mesh/simpleGeometricObjects/Disc.cpp @@ -0,0 +1,84 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2018-2020 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2020 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2018-2020 TotalEnergies + * Copyright (c) 2019- GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +/** + * @file Disc.cpp + */ + +#include "Disc.hpp" +#include "LvArray/src/tensorOps.hpp" + +namespace geos +{ +using namespace dataRepository; + +Disc::Disc( const string & name, Group * const parent ): + PlanarGeometricObject( name, parent ), + m_center{ 0.0, 0.0, 0.0 }, + m_radius( 1.0 ), + m_tolerance() +{ + registerWrapper( viewKeyStruct::centerString(), &m_center ). + setInputFlag( InputFlags::REQUIRED ). + setDescription( "(x,y,z) coordinates of the center of the disc" ); + + registerWrapper( viewKeyStruct::radiusString(), &m_radius ). + setInputFlag( InputFlags::REQUIRED ). + setDescription( "Radius of the disc." ); + + registerWrapper( viewKeyStruct::toleranceString(), &m_tolerance ). + setInputFlag( InputFlags::OPTIONAL ). + setDefaultValue( 1e-5 ). + setDescription( "Tolerance to determine if a point sits on the disc or not. " + "It is relative to the maximum dimension of the disc." ); + +} + +Disc::~Disc() +{} + +void Disc::postProcessInput() +{ + // Make sure that you have an orthonormal basis. + LvArray::tensorOps::normalize< 3 >( m_normal ); + m_tolerance = m_tolerance * m_radius; + +} + +bool Disc::isCoordInObject( real64 const ( &coord ) [3] ) const +{ + bool isInside = true; + + //gets the vector from coord to the center + real64 dummy[ 3 ] = LVARRAY_TENSOROPS_INIT_LOCAL_3( coord ); + LvArray::tensorOps::subtract< 3 >( dummy, m_center ); + + // 1. Check if point is on the plane of the disc + if( std::abs( LvArray::tensorOps::AiBi< 3 >( dummy, m_normal ) ) > m_tolerance ) + { + isInside = false; + } + + // 2. Check if it is inside the disc + if( LvArray::tensorOps::l2NormSquared< 3 >( dummy ) > m_radius*m_radius ) + { + isInside = false; + } + + return isInside; +} + +REGISTER_CATALOG_ENTRY( SimpleGeometricObjectBase, Disc, string const &, Group * const ) + +} /* namespace geosx */ diff --git a/src/coreComponents/mesh/simpleGeometricObjects/Disc.hpp b/src/coreComponents/mesh/simpleGeometricObjects/Disc.hpp new file mode 100644 index 00000000000..0c9b5a5c87b --- /dev/null +++ b/src/coreComponents/mesh/simpleGeometricObjects/Disc.hpp @@ -0,0 +1,117 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2018-2020 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2020 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2018-2020 TotalEnergies + * Copyright (c) 2019- GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +/** + * @file Disc.hpp + */ + +#ifndef GEOSX_MESH_SIMPLEGEOMETRICOBJECTS_DISC_HPP_ +#define GEOSX_MESH_SIMPLEGEOMETRICOBJECTS_DISC_HPP_ + +#include "PlanarGeometricObject.hpp" + +namespace geos +{ + +/** + * @class Disc + * @brief Class to represent a geometric disc in GEOSX. + */ +class Disc : public PlanarGeometricObject +{ +public: + + /** + * @name Constructor / Destructor + */ + ///@{ + + /** + * @brief Constructor. + * @param name name of the object in the data hierarchy. + * @param parent pointer to the parent group in the data hierarchy. + */ + Disc( const string & name, + Group * const parent ); + + /** + * @brief Default destructor. + */ + virtual ~Disc() override; + + ///@} + + /** + * @name Static Factory Catalog Functions + */ + ///@{ + + /** + * @brief Get the catalog name. + * @return the name of this class in the catalog + */ + static string catalogName() { return "Disc"; } + + ///@} + + bool isCoordInObject( real64 const ( &coord ) [3] ) const override final; + + /** + * @name Getters + */ + ///@{ + + /** + * @brief Get the center of the disc. + * @return the center of the disc + */ + virtual R1Tensor & getCenter() override final {return m_center;} + + /** + * @copydoc getCenter() + */ + virtual R1Tensor const & getCenter() const override final {return m_center;} + +protected: + + /** + * @brief This function provides the capability to post process input values prior to + * any other initialization operations. + */ + virtual void postProcessInput() override final; + +private: + + /// center of the disc in (x,y,z) coordinates + R1Tensor m_center; + /// Dimensions of the disc's radius + real64 m_radius; + /// tolerance to determine if a point sits on the disc or not + real64 m_tolerance; + + /// @cond DO_NOT_DOCUMENT + + struct viewKeyStruct + { + static constexpr char const * centerString() { return "center"; } + static constexpr char const * radiusString() { return "radius"; } + static constexpr char const * toleranceString() { return "tolerance"; } + }; + + /// @endcond + +}; +} /* namespace geosx */ + +#endif /* GEOSX_MESH_SIMPLEGEOMETRICOBJECTS_DISC_HPP_*/ diff --git a/src/coreComponents/mesh/simpleGeometricObjects/GeometricObjectManager.hpp b/src/coreComponents/mesh/simpleGeometricObjects/GeometricObjectManager.hpp index 4fb480a3019..bb02a280045 100644 --- a/src/coreComponents/mesh/simpleGeometricObjects/GeometricObjectManager.hpp +++ b/src/coreComponents/mesh/simpleGeometricObjects/GeometricObjectManager.hpp @@ -20,6 +20,7 @@ #define GEOS_MESH_SIMPLEGEOMETRICOBJECTS_GEOMETRICOBJECTMANAGER_HPP_ #include "dataRepository/Group.hpp" +#include "mesh/simpleGeometricObjects/SimpleGeometricObjectBase.hpp" namespace geos @@ -60,6 +61,20 @@ class GeometricObjectManager : public dataRepository::Group virtual Group * createChild( string const & childKey, string const & childName ) override; + /** + * @brief This function is used to launch a unction over the target geometric objects with region type = + * SimpleGeometricObjectBase. + * @tparam LOOKUP_CONTAINER type of container of names or indices + * @tparam LAMBDA type of the user-provided function + * @param targetObjects target geometric objects names or indices + * @param lambda kernel function + */ + template< typename OBJECTTYPE = SimpleGeometricObjectBase, typename ... OBJECTTYPES, typename LOOKUP_CONTAINER, typename LAMBDA > + void forGeometricObject( LOOKUP_CONTAINER const & targetObjects, LAMBDA && lambda ) + { + this->forSubGroups< OBJECTTYPE, OBJECTTYPES... >( targetObjects, std::forward< LAMBDA >( lambda ) ); + } + virtual void expandObjectCatalogs() override; private: diff --git a/src/coreComponents/mesh/simpleGeometricObjects/PlanarGeometricObject.cpp b/src/coreComponents/mesh/simpleGeometricObjects/PlanarGeometricObject.cpp new file mode 100644 index 00000000000..857cccc2a78 --- /dev/null +++ b/src/coreComponents/mesh/simpleGeometricObjects/PlanarGeometricObject.cpp @@ -0,0 +1,50 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2018-2020 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2020 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2018-2020 TotalEnergies + * Copyright (c) 2019- GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +/** + * @file PlanarGeometricObject.cpp + */ + +#include "PlanarGeometricObject.hpp" +#include "LvArray/src/tensorOps.hpp" + +namespace geos +{ +using namespace dataRepository; + +PlanarGeometricObject::PlanarGeometricObject( const string & name, Group * const parent ): + SimpleGeometricObjectBase( name, parent ), + m_normal{ 0.0, 0.0, 1.0 }, + m_lengthVector{ 0.0, 0.0, 0.0 }, + m_widthVector{ 0.0, 0.0, 0.0 } +{ + registerWrapper( viewKeyStruct::normalString(), &m_normal ). + setInputFlag( InputFlags::REQUIRED ). + setDescription( "Normal (n_x,n_y,n_z) to the plane (will be normalized automatically)" ); + + registerWrapper( viewKeyStruct::mLengthVectorString(), &m_lengthVector ). + setInputFlag( InputFlags::REQUIRED ). + setDescription( "Tangent vector defining the orthonormal basis along with the normal." ); + + registerWrapper( viewKeyStruct::mWidthVectorString(), &m_widthVector ). + setInputFlag( InputFlags::REQUIRED ). + setDescription( "Tangent vector defining the orthonormal basis along with the normal." ); +} + +PlanarGeometricObject::~PlanarGeometricObject() +{} + +//REGISTER_CATALOG_ENTRY( SimpleGeometricObjectBase, PlanarGeometricObject, string const &, Group * const ) + +} /* namespace geosx */ diff --git a/src/coreComponents/mesh/simpleGeometricObjects/BoundedPlane.hpp b/src/coreComponents/mesh/simpleGeometricObjects/PlanarGeometricObject.hpp similarity index 65% rename from src/coreComponents/mesh/simpleGeometricObjects/BoundedPlane.hpp rename to src/coreComponents/mesh/simpleGeometricObjects/PlanarGeometricObject.hpp index 4f3811220ec..417f3da27b5 100644 --- a/src/coreComponents/mesh/simpleGeometricObjects/BoundedPlane.hpp +++ b/src/coreComponents/mesh/simpleGeometricObjects/PlanarGeometricObject.hpp @@ -13,11 +13,11 @@ */ /** - * @file BoundedPlane.hpp + * @file PlanarGeometricObject.hpp */ -#ifndef GEOS_MESH_SIMPLEGEOMETRICOBJECTS_BOUNDEDPLANE_HPP_ -#define GEOS_MESH_SIMPLEGEOMETRICOBJECTS_BOUNDEDPLANE_HPP_ +#ifndef GEOSX_MESH_SIMPLEGEOMETRICOBJECTS_PLANARGEOMETRICOBJECT_HPP_ +#define GEOSX_MESH_SIMPLEGEOMETRICOBJECTS_PLANARGEOMETRICOBJECT_HPP_ #include "SimpleGeometricObjectBase.hpp" @@ -25,10 +25,10 @@ namespace geos { /** - * @class BoundedPlane - * @brief Class to represent a geometric box in GEOSX. + * @class PlanarGeometricObject + * @brief Abstract class to implement functions used by all bounded geometric objects in GEOSX, such as disc or plane. */ -class BoundedPlane : public SimpleGeometricObjectBase +class PlanarGeometricObject : public SimpleGeometricObjectBase { public: @@ -42,13 +42,13 @@ class BoundedPlane : public SimpleGeometricObjectBase * @param name name of the object in the data hierarchy. * @param parent pointer to the parent group in the data hierarchy. */ - BoundedPlane( const string & name, - Group * const parent ); + PlanarGeometricObject( const string & name, + Group * const parent ); /** * @brief Default destructor. */ - virtual ~BoundedPlane() override; + virtual ~PlanarGeometricObject() override; ///@} @@ -61,16 +61,16 @@ class BoundedPlane : public SimpleGeometricObjectBase * @brief Get the catalog name. * @return the name of this class in the catalog */ - static string catalogName() { return "BoundedPlane"; } + static string catalogName() { return "PlanarGeometricObject"; } ///@} - bool isCoordInObject( real64 const ( &coord ) [3] ) const override final; - /** - * @brief Find the bounds of the plane. + * @brief Check if the input coordinates are in the object. + * @param[in] coord the coordinates to test + * @return true if the coordinates are in the object, false otherwise */ - void findRectangleLimits(); + virtual bool isCoordInObject( real64 const ( &coord ) [3] ) const override = 0; /** * @name Getters @@ -88,17 +88,6 @@ class BoundedPlane : public SimpleGeometricObjectBase */ R1Tensor const & getNormal() const {return m_normal;} - /** - * @brief Get the origin of the plane. - * @return the origin of the plane - */ - R1Tensor & getCenter() {return m_origin;} - - /** - * @copydoc getCenter() - */ - R1Tensor const & getCenter() const {return m_origin;} - /** * @brief Get one of the tangent vectors defining the orthonormal basis along with the normal. * @return the tangent vector @@ -121,47 +110,43 @@ class BoundedPlane : public SimpleGeometricObjectBase */ R1Tensor const & getLengthVector() const {return m_lengthVector;} - -protected: + /** + * @brief Get the origin of the plane. + * @return the origin of the plane + */ + virtual R1Tensor & getCenter() = 0; /** - * @brief This function provides the capability to post process input values prior to - * any other initialization operations. + * @copydoc getCenter() */ - virtual void postProcessInput() override final; + virtual R1Tensor const & getCenter() const = 0; -private: +protected: - /// Origin point (x,y,z) of the plane (basically, any point on the plane) - R1Tensor m_origin; /// Normal (n_x,n_y,n_z) to the plane (will be normalized automatically) R1Tensor m_normal; /// Length vector in the orthonormal basis along with the normal R1Tensor m_lengthVector; /// Width vector in the orthonormal basis along with the normal R1Tensor m_widthVector; - /// Dimensions of the bounded plane - array1d< real64 > m_dimensions; - /// Length and width of the bounded plane - array2d< real64 > m_points; - /// tolerance to determine if a point sits on the plane or not + /// tolerance to determine if a point sits on the PlanarGeometricObject or not real64 m_tolerance; + /// tolerance to check if base is orthonormal + static constexpr real64 orthoNormalBaseTolerance = 1e-10; + /// @cond DO_NOT_DOCUMENT struct viewKeyStruct { - static constexpr char const * originString() { return "origin"; } static constexpr char const * normalString() { return "normal"; } - static constexpr char const * dimensionsString() { return "dimensions"; } static constexpr char const * mLengthVectorString() { return "lengthVector"; } static constexpr char const * mWidthVectorString() { return "widthVector"; } - static constexpr char const * toleranceString() { return "tolerance"; } }; /// @endcond }; -} /* namespace geos */ +} /* namespace geosx */ -#endif /* GEOS_MESH_SIMPLEGEOMETRICOBJECTS_BOUNDEDPLANE_HPP_*/ +#endif /* GEOSX_MESH_SIMPLEGEOMETRICOBJECTS_PLANARGEOMETRICOBJECT_HPP_*/ diff --git a/src/coreComponents/mesh/simpleGeometricObjects/BoundedPlane.cpp b/src/coreComponents/mesh/simpleGeometricObjects/Rectangle.cpp similarity index 71% rename from src/coreComponents/mesh/simpleGeometricObjects/BoundedPlane.cpp rename to src/coreComponents/mesh/simpleGeometricObjects/Rectangle.cpp index f3f8998afd1..71cb893e757 100644 --- a/src/coreComponents/mesh/simpleGeometricObjects/BoundedPlane.cpp +++ b/src/coreComponents/mesh/simpleGeometricObjects/Rectangle.cpp @@ -13,40 +13,25 @@ */ /** - * @file BoundedPlane.cpp + * @file Rectangle.cpp */ -#include "BoundedPlane.hpp" +#include "Rectangle.hpp" #include "LvArray/src/tensorOps.hpp" namespace geos { using namespace dataRepository; -BoundedPlane::BoundedPlane( const string & name, Group * const parent ): - SimpleGeometricObjectBase( name, parent ), +Rectangle::Rectangle( const string & name, Group * const parent ): + PlanarGeometricObject( name, parent ), m_origin{ 0.0, 0.0, 0.0 }, - m_normal{ 0.0, 0.0, 1.0 }, - m_lengthVector{ 0.0, 0.0, 0.0 }, - m_widthVector{ 0.0, 0.0, 0.0 }, m_tolerance() { registerWrapper( viewKeyStruct::originString(), &m_origin ). setInputFlag( InputFlags::REQUIRED ). setDescription( "Origin point (x,y,z) of the plane (basically, any point on the plane)" ); - registerWrapper( viewKeyStruct::normalString(), &m_normal ). - setInputFlag( InputFlags::REQUIRED ). - setDescription( "Normal (n_x,n_y,n_z) to the plane (will be normalized automatically)" ); - - registerWrapper( viewKeyStruct::mLengthVectorString(), &m_lengthVector ). - setInputFlag( InputFlags::REQUIRED ). - setDescription( "Tangent vector defining the orthonormal basis along with the normal." ); - - registerWrapper( viewKeyStruct::mWidthVectorString(), &m_widthVector ). - setInputFlag( InputFlags::REQUIRED ). - setDescription( "Tangent vector defining the orthonormal basis along with the normal." ); - registerWrapper( viewKeyStruct::dimensionsString(), &m_dimensions ). setInputFlag( InputFlags::REQUIRED ). setDescription( "Length and width of the bounded plane" ); @@ -61,10 +46,32 @@ BoundedPlane::BoundedPlane( const string & name, Group * const parent ): m_points.resize( 4, 3 ); } -BoundedPlane::~BoundedPlane() +//constructor given two points, used for 2.5D problems +Rectangle::Rectangle( const real64 oldX, const real64 oldY, + const real64 newX, const real64 newY, + const string & name, Group * const parent ): + PlanarGeometricObject( name, parent ), + m_origin{ 0.0, 0.0, 0.0 }, + m_tolerance( 1e-5 ) +{ + m_origin = { (oldX + newX)/2.0, (oldY + newY)/2.0, 0.0}; + m_normal = { -(newY-oldY), newX-oldX, 0.0 }; + m_lengthVector = { newX-oldX, newY-oldY, 0.0 }; + m_widthVector = { 0.0, 0.0, 1.0 }; + real64 norm = std::sqrt( pow( newX-oldX, 2 )+pow( newY-oldY, 2 )); + m_dimensions.resize( 2 ); + m_dimensions[0] = norm+1e-4; //small tolerance to ensure that both ends are contained in the plane - TODO: try to use m_tolerance + m_dimensions[1] = 5; //TODO: this is arbitrary, it only needs to be larger than the z thickness in the 2.5D model + + m_points.resize( 4, 3 ); + //parent->registerGroup< Rectangle >( name, std::move( this ) ); + this->postProcessInput(); +} + +Rectangle::~Rectangle() {} -void BoundedPlane::postProcessInput() +void Rectangle::postProcessInput() { // Make sure that you have an orthonormal basis. LvArray::tensorOps::normalize< 3 >( m_normal ); @@ -77,18 +84,16 @@ void BoundedPlane::postProcessInput() real64 vector[ 3 ]; LvArray::tensorOps::crossProduct( vector, m_lengthVector, m_widthVector ); - GEOS_ERROR_IF( std::fabs( std::fabs( LvArray::tensorOps::AiBi< 3 >( m_normal, vector )) - 1 ) > 1e-15 - || std::fabs( LvArray::tensorOps::AiBi< 3 >( m_widthVector, m_lengthVector )) > 1e-15, - getDataContext() << ": the 3 vectors provided in the BoundedPlane do not form an" << - " orthonormal basis!" ); + GEOS_ERROR_IF( std::fabs( std::fabs( LvArray::tensorOps::AiBi< 3 >( m_normal, vector )) - 1 ) > orthoNormalBaseTolerance + || std::fabs( LvArray::tensorOps::AiBi< 3 >( m_widthVector, m_lengthVector )) > orthoNormalBaseTolerance, + "Error: the 3 vectors provided in the Rectangle do not form an orthonormal basis!" ); - GEOS_ERROR_IF( m_dimensions.size() != 2, - getDataContext() << ": Need to provide both length and width!" ); + GEOS_ERROR_IF( m_dimensions.size() != 2, "Error: Need to provide both length and width!" ); findRectangleLimits(); } -void BoundedPlane::findRectangleLimits() +void Rectangle::findRectangleLimits() { real64 lengthVec[ 3 ] = LVARRAY_TENSOROPS_INIT_LOCAL_3( m_lengthVector ); real64 widthVec[ 3 ] = LVARRAY_TENSOROPS_INIT_LOCAL_3( m_widthVector ); @@ -122,7 +127,7 @@ void BoundedPlane::findRectangleLimits() } } -bool BoundedPlane::isCoordInObject( real64 const ( &coord ) [3] ) const +bool Rectangle::isCoordInObject( real64 const ( &coord ) [3] ) const { bool isInside = true; @@ -163,6 +168,6 @@ bool BoundedPlane::isCoordInObject( real64 const ( &coord ) [3] ) const return isInside; } -REGISTER_CATALOG_ENTRY( SimpleGeometricObjectBase, BoundedPlane, string const &, Group * const ) +REGISTER_CATALOG_ENTRY( SimpleGeometricObjectBase, Rectangle, string const &, Group * const ) -} /* namespace geos */ +} /* namespace geosx */ diff --git a/src/coreComponents/mesh/simpleGeometricObjects/Rectangle.hpp b/src/coreComponents/mesh/simpleGeometricObjects/Rectangle.hpp new file mode 100644 index 00000000000..ef86ffbf390 --- /dev/null +++ b/src/coreComponents/mesh/simpleGeometricObjects/Rectangle.hpp @@ -0,0 +1,139 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2018-2020 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2020 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2018-2020 TotalEnergies + * Copyright (c) 2019- GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +/** + * @file Rectangle.hpp + */ + +#ifndef GEOSX_MESH_SIMPLEGEOMETRICOBJECTS_RECTANGLE_HPP_ +#define GEOSX_MESH_SIMPLEGEOMETRICOBJECTS_RECTANGLE_HPP_ + +#include "PlanarGeometricObject.hpp" + +namespace geos +{ + +/** + * @class Rectangle + * @brief Class to represent a geometric box in GEOSX. + */ +class Rectangle : public PlanarGeometricObject +{ +public: + + /** + * @name Constructor / Destructor + */ + ///@{ + + /** + * @brief Constructor. + * @param name name of the object in the data hierarchy. + * @param parent pointer to the parent group in the data hierarchy. + */ + Rectangle( const string & name, + Group * const parent ); + + /** + * @brief Internal constructor. This is used to make planar cuts from point (oldX, oldY) to (newX, newY) + * in 2.5D problems. + * @param oldX x-coordinate of first point + * @param oldY y-coordinate of first point + * @param newX x-coordinate of second point + * @param newY y-coordinate of second point + * @param name name of the object in the data hierarchy. + * @param parent pointer to the parent group in the data hierarchy. + */ + Rectangle( const real64 oldX, const real64 oldY, const real64 newX, + const real64 newY, const string & name, Group * const parent ); + + /** + * @brief Default destructor. + */ + virtual ~Rectangle() override; + + ///@} + + /** + * @name Static Factory Catalog Functions + */ + ///@{ + + /** + * @brief Get the catalog name. + * @return the name of this class in the catalog + */ + static string catalogName() { return "Rectangle"; } + + ///@} + + bool isCoordInObject( real64 const ( &coord ) [3] ) const override final; + + /** + * @brief Find the bounds of the plane. + */ + void findRectangleLimits(); + + /** + * @name Getters + */ + ///@{ + + /** + * @brief Get the origin of the plane. + * @return the origin of the plane + */ + virtual R1Tensor & getCenter() override final {return m_origin;} + + /** + * @copydoc getCenter() + */ + virtual R1Tensor const & getCenter() const override final {return m_origin;} + + + +protected: + + /** + * @brief This function provides the capability to post process input values prior to + * any other initialization operations. + */ + virtual void postProcessInput() override final; + +private: + + /// Origin point (x,y,z) of the plane (basically, any point on the plane) + R1Tensor m_origin; + /// Dimensions of the bounded plane + array1d< real64 > m_dimensions; + /// Length and width of the bounded plane + array2d< real64 > m_points; + /// tolerance to determine if a point sits on the plane or not + real64 m_tolerance; + + /// @cond DO_NOT_DOCUMENT + + struct viewKeyStruct + { + static constexpr char const * originString() { return "origin"; } + static constexpr char const * dimensionsString() { return "dimensions"; } + static constexpr char const * toleranceString() { return "tolerance"; } + }; + + /// @endcond + +}; +} /* namespace geosx */ + +#endif /* GEOSX_MESH_SIMPLEGEOMETRICOBJECTS_RECTANGLE_HPP_*/ diff --git a/src/coreComponents/mesh/unitTests/testMeshObjectPath.cpp b/src/coreComponents/mesh/unitTests/testMeshObjectPath.cpp index d5021b24c01..5a20bb6e2a0 100644 --- a/src/coreComponents/mesh/unitTests/testMeshObjectPath.cpp +++ b/src/coreComponents/mesh/unitTests/testMeshObjectPath.cpp @@ -353,44 +353,44 @@ void checkRegionNames( std::vector< string > const & names ) EXPECT_TRUE( names[12] == "region1" ); } -// TEST( testMeshObjectPath, forObjectsInPath ) -// { -// TestMesh & testMesh = TestMesh::getTestMesh(); -// Group & meshBodies = testMesh.meshBodies(); -// Group const & meshBodiesConst = meshBodies; -// string const path = "*/*/ElementRegions"; -// MeshObjectPath meshObjectPath( path, meshBodiesConst ); - -// { -// std::vector< string > names; -// meshObjectPath.forObjectsInPath< CellElementSubRegion >( meshBodiesConst, -// [&]( ElementSubRegionBase const & elemSubRegionBase ) -// { -// names.push_back( elemSubRegionBase.getName() ); -// } ); -// checkSubRegionNames( names ); -// } - -// { -// std::vector< string > names; -// meshObjectPath.forObjectsInPath< CellElementSubRegion >( meshBodies, -// [&]( ElementSubRegionBase & elemSubRegionBase ) -// { -// names.push_back( elemSubRegionBase.getName() ); -// } ); -// checkSubRegionNames( names ); -// } - -// { -// std::vector< string > names; -// meshObjectPath.forObjectsInPath< CellElementRegion >( meshBodiesConst, -// [&]( CellElementRegion const & elemRegionBase ) -// { -// names.push_back( elemRegionBase.getName() ); -// } ); -// checkRegionNames( names ); -// } -// } +TEST( testMeshObjectPath, forObjectsInPathFromMeshBodies ) +{ + TestMesh & testMesh = TestMesh::getTestMesh(); + Group & meshBodies = testMesh.meshBodies(); + Group const & meshBodiesConst = meshBodies; + string const path = "*/*/ElementRegions"; + MeshObjectPath meshObjectPath( path, meshBodiesConst ); + + { + std::vector< string > names; + meshObjectPath.forObjectsInPath< CellElementSubRegion >( meshBodiesConst, + [&]( ElementSubRegionBase const & elemSubRegionBase ) + { + names.push_back( elemSubRegionBase.getName() ); + } ); + checkSubRegionNames( names ); + } + + { + std::vector< string > names; + meshObjectPath.forObjectsInPath< CellElementSubRegion >( meshBodies, + [&]( ElementSubRegionBase & elemSubRegionBase ) + { + names.push_back( elemSubRegionBase.getName() ); + } ); + checkSubRegionNames( names ); + } + + { + std::vector< string > names; + meshObjectPath.forObjectsInPath< CellElementRegion >( meshBodiesConst, + [&]( CellElementRegion const & elemRegionBase ) + { + names.push_back( elemRegionBase.getName() ); + } ); + checkRegionNames( names ); + } +} template< typename OBJECT_TYPE, typename CHECK_FUNC > void testForObjectInPathsMeshLevel( Group & meshBodies, diff --git a/src/coreComponents/mesh/utilities/AverageOverQuadraturePointsKernel.hpp b/src/coreComponents/mesh/utilities/AverageOverQuadraturePointsKernel.hpp new file mode 100644 index 00000000000..3530d3eb590 --- /dev/null +++ b/src/coreComponents/mesh/utilities/AverageOverQuadraturePointsKernel.hpp @@ -0,0 +1,292 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2018-2020 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2020 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2018-2020 TotalEnergies + * Copyright (c) 2019- GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +/** + * @file AverageOverQuadraturePointsKernel.hpp + */ + +#ifndef GEOS_MESH_UTILITIES_AVERAGEOVERQUADRATUREPOINTSKERNEL_HPP_ +#define GEOS_MESH_UTILITIES_AVERAGEOVERQUADRATUREPOINTSKERNEL_HPP_ + +#include "common/DataTypes.hpp" +#include "common/GEOS_RAJA_Interface.hpp" +#include "finiteElement/FiniteElementDispatch.hpp" +#include "mesh/CellElementSubRegion.hpp" + +namespace geos +{ + +/** + * @class AverageOverQuadraturePointsBase + * @tparam SUBREGION_TYPE the subRegion type + * @tparam FE_TYPE the finite element type + */ +template< typename SUBREGION_TYPE, + typename FE_TYPE > +class AverageOverQuadraturePointsBase +{ +public: + + /** + * @brief Constructor for the class + * @param nodeManager the node manager + * @param edgeManager the edge manager + * @param faceManager the face manager + * @param elementSubRegion the element subRegion + * @param finiteElementSpace the finite element space + */ + AverageOverQuadraturePointsBase( NodeManager & nodeManager, + EdgeManager const & edgeManager, + FaceManager const & faceManager, + SUBREGION_TYPE const & elementSubRegion, + FE_TYPE const & finiteElementSpace ): + m_finiteElementSpace( finiteElementSpace ), + m_elemsToNodes( elementSubRegion.nodeList().toViewConst() ), + m_X( nodeManager.referencePosition() ), + m_elementVolume( elementSubRegion.getElementVolume() ) + { + finiteElement::FiniteElementBase:: + initialize< FE_TYPE >( nodeManager, + edgeManager, + faceManager, + elementSubRegion, + m_meshData ); + } + + //***************************************************************************** + /** + * @copydoc finiteElement::KernelBase::StackVariables + */ + struct StackVariables + { +public: + + /** + * Default constructor + */ + GEOS_HOST_DEVICE + StackVariables(): + xLocal() + {} + + /// C-array stack storage for element local the nodal positions. + real64 xLocal[ FE_TYPE::maxSupportPoints ][ 3 ]; + + /// Stack variables needed for the underlying FEM type + typename FE_TYPE::StackVariables feStack; + }; + //*************************************************************************** + + /** + * @brief Performs the setup phase for the kernel. + * @param k The element index. + * @param stack The StackVariable object that hold the stack variables. + */ + GEOS_HOST_DEVICE + void setup( localIndex const k, + StackVariables & stack ) const + { + m_finiteElementSpace.template setup< FE_TYPE >( k, m_meshData, stack.feStack ); + + for( localIndex a = 0; a < FE_TYPE::maxSupportPoints; ++a ) + { + localIndex const localNodeIndex = m_elemsToNodes( k, a ); + + for( integer i = 0; i < 3; ++i ) + { + stack.xLocal[a][i] = m_X[localNodeIndex][i]; + } + } + } + +protected: + + /// The finite element space/discretization object for the element type in + /// the SUBREGION_TYPE. + FE_TYPE const & m_finiteElementSpace; + + /// The element to nodes map. + traits::ViewTypeConst< typename SUBREGION_TYPE::NodeMapType::base_type > const m_elemsToNodes; + + /// The reference position of the nodes + arrayView2d< real64 const, nodes::REFERENCE_POSITION_USD > const m_X; + + /// The volume of the elements + arrayView1d< real64 const > const m_elementVolume; + + /// Data structure containing mesh data used to setup the finite element + typename FE_TYPE::template MeshData< SUBREGION_TYPE > m_meshData; +}; + +/** + * @class AverageOverQuadraturePoints1D + * @tparam SUBREGION_TYPE the subRegion type + * @tparam FE_TYPE the finite element type + */ +template< typename SUBREGION_TYPE, + typename FE_TYPE > +class AverageOverQuadraturePoints1D : + public AverageOverQuadraturePointsBase< SUBREGION_TYPE, + FE_TYPE > +{ +public: + + /// Alias for the base class; + using Base = AverageOverQuadraturePointsBase< SUBREGION_TYPE, + FE_TYPE >; + + using Base::m_elementVolume; + + /** + * @brief Constructor for the class + * @param nodeManager the node manager + * @param edgeManager the edge manager + * @param faceManager the face manager + * @param elementSubRegion the element subRegion + * @param finiteElementSpace the finite element space + * @param property the property at quadrature points + * @param averageProperty the property averaged over quadrature points + */ + AverageOverQuadraturePoints1D( NodeManager & nodeManager, + EdgeManager const & edgeManager, + FaceManager const & faceManager, + SUBREGION_TYPE const & elementSubRegion, + FE_TYPE const & finiteElementSpace, + arrayView2d< real64 const > const property, + arrayView1d< real64 > const averageProperty ): + Base( nodeManager, + edgeManager, + faceManager, + elementSubRegion, + finiteElementSpace ), + m_property( property ), + m_averageProperty( averageProperty ) + {} + + /** + * @copydoc finiteElement::KernelBase::StackVariables + */ + struct StackVariables : Base::StackVariables + {}; + + /** + * @brief Performs the setup phase for the kernel. + * @param k The element index. + * @param stack The StackVariable object that hold the stack variables. + */ + GEOS_HOST_DEVICE + void setup( localIndex const k, + StackVariables & stack ) const + { + Base::setup( k, stack ); + m_averageProperty[k] = 0.0; + } + + /** + * @brief Increment the average property with the contribution of the property at this quadrature point + * @param k The element index + * @param q The quadrature point index + * @param stack The StackVariables object that hold the stack variables. + */ + GEOS_HOST_DEVICE + void quadraturePointKernel( localIndex const k, + localIndex const q, + StackVariables & stack ) const + { + real64 const weight = FE_TYPE::transformedQuadratureWeight( q, stack.xLocal, stack.feStack ) / m_elementVolume[k]; + m_averageProperty[k] += weight * m_property[k][q]; + } + + /** + * @brief Launch the kernel over the elements in the subRegion + * @tparam POLICY the kernel policy + * @tparam KERNEL_TYPE the type of kernel + * @param numElems the number of elements in the subRegion + * @param kernelComponent the kernel component + */ + template< typename POLICY, + typename KERNEL_TYPE > + static void + kernelLaunch( localIndex const numElems, + KERNEL_TYPE const & kernelComponent ) + { + forAll< POLICY >( numElems, + [=] GEOS_HOST_DEVICE ( localIndex const k ) + { + typename KERNEL_TYPE::StackVariables stack; + + kernelComponent.setup( k, stack ); + for( integer q = 0; q < FE_TYPE::numQuadraturePoints; ++q ) + { + kernelComponent.quadraturePointKernel( k, q, stack ); + } + } ); + } + +protected: + + /// The property living on quadrature points + arrayView2d< real64 const > const m_property; + + /// The average property + arrayView1d< real64 > const m_averageProperty; + +}; + + +/** + * @class AverageOverQuadraturePoints1DKernelFactory + * @brief Class to create and launch the kernel + */ +class AverageOverQuadraturePoints1DKernelFactory +{ +public: + + /** + * @brief Create a new kernel and launch + * @tparam SUBREGION_TYPE the subRegion type + * @tparam FE_TYPE the finite element type + * @tparam POLICY the kernel policy + * @param nodeManager the node manager + * @param edgeManager the edge manager + * @param faceManager the face manager + * @param elementSubRegion the element subRegion + * @param finiteElementSpace the finite element space + * @param property the property at quadrature points + * @param averageProperty the property averaged over quadrature points + */ + template< typename SUBREGION_TYPE, + typename FE_TYPE, + typename POLICY > + static void + createAndLaunch( NodeManager & nodeManager, + EdgeManager const & edgeManager, + FaceManager const & faceManager, + SUBREGION_TYPE const & elementSubRegion, + FE_TYPE const & finiteElementSpace, + arrayView2d< real64 const > const property, + arrayView1d< real64 > const averageProperty ) + { + AverageOverQuadraturePoints1D< SUBREGION_TYPE, FE_TYPE > + kernel( nodeManager, edgeManager, faceManager, elementSubRegion, finiteElementSpace, + property, averageProperty ); + + AverageOverQuadraturePoints1D< SUBREGION_TYPE, FE_TYPE >::template + kernelLaunch< POLICY >( elementSubRegion.size(), kernel ); + } +}; + +} + +#endif /* GEOS_MESH_UTILITIES_AVERAGEOVERQUADRATUREPOINTSKERNEL_HPP_ */ diff --git a/src/coreComponents/physicsSolvers/CMakeLists.txt b/src/coreComponents/physicsSolvers/CMakeLists.txt index 5ec5198178e..e834480cd08 100644 --- a/src/coreComponents/physicsSolvers/CMakeLists.txt +++ b/src/coreComponents/physicsSolvers/CMakeLists.txt @@ -50,6 +50,7 @@ set( physicsSolvers_headers fluidFlow/SinglePhaseHybridFVMKernels.hpp fluidFlow/SinglePhaseProppantBase.hpp fluidFlow/SinglePhaseProppantBaseKernels.hpp + fluidFlow/SinglePhaseProppantFluxKernels.hpp fluidFlow/StabilizedCompositionalMultiphaseFVMKernels.hpp fluidFlow/StencilAccessors.hpp fluidFlow/ThermalSinglePhaseBaseKernels.hpp @@ -80,14 +81,19 @@ set( physicsSolvers_headers multiphysics/poromechanicsKernels/PoromechanicsBase.hpp multiphysics/poromechanicsKernels/SinglePhasePoromechanics.hpp multiphysics/poromechanicsKernels/SinglePhasePoromechanics_impl.hpp + multiphysics/poromechanicsKernels/SinglePhasePoromechanicsConformingFractures.hpp multiphysics/poromechanicsKernels/SinglePhasePoromechanicsEFEM.hpp multiphysics/poromechanicsKernels/SinglePhasePoromechanicsEFEM_impl.hpp multiphysics/poromechanicsKernels/SinglePhasePoromechanicsFractures.hpp + multiphysics/poromechanicsKernels/SinglePhasePoromechanicsEmbeddedFractures.hpp multiphysics/poromechanicsKernels/ThermalMultiphasePoromechanics.hpp multiphysics/poromechanicsKernels/ThermalMultiphasePoromechanics_impl.hpp multiphysics/poromechanicsKernels/ThermalSinglePhasePoromechanics.hpp multiphysics/poromechanicsKernels/ThermalSinglePhasePoromechanics_impl.hpp - multiphysics/SinglePhasePoromechanicsFluxKernels.hpp + multiphysics/poromechanicsKernels/ThermalSinglePhasePoromechanicsEFEM.hpp + multiphysics/poromechanicsKernels/ThermalSinglePhasePoromechanicsEFEM_impl.hpp + multiphysics/poromechanicsKernels/ThermalSinglePhasePoromechanicsConformingFractures.hpp + multiphysics/poromechanicsKernels/ThermalSinglePhasePoromechanicsEmbeddedFractures.hpp multiphysics/SinglePhasePoromechanics.hpp multiphysics/SinglePhasePoromechanicsEmbeddedFractures.hpp multiphysics/SinglePhasePoromechanicsConformingFractures.hpp @@ -158,6 +164,7 @@ set( physicsSolvers_sources fluidFlow/SinglePhaseFVM.cpp fluidFlow/SinglePhaseHybridFVM.cpp fluidFlow/SinglePhaseProppantBase.cpp + fluidFlow/SinglePhaseProppantFluxKernels.cpp fluidFlow/wells/CompositionalMultiphaseWell.cpp fluidFlow/wells/CompositionalMultiphaseWellKernels.cpp fluidFlow/wells/SinglePhaseWell.cpp @@ -174,7 +181,6 @@ set( physicsSolvers_sources multiphysics/SinglePhasePoromechanics.cpp multiphysics/SinglePhasePoromechanicsEmbeddedFractures.cpp multiphysics/SinglePhasePoromechanicsConformingFractures.cpp - multiphysics/SinglePhasePoromechanicsFluxKernels.cpp multiphysics/SinglePhaseReservoirAndWells.cpp simplePDE/LaplaceBaseH1.cpp simplePDE/LaplaceFEM.cpp diff --git a/src/coreComponents/physicsSolvers/contact/ContactFields.hpp b/src/coreComponents/physicsSolvers/contact/ContactFields.hpp index b48d33f3e19..214ca10513c 100644 --- a/src/coreComponents/physicsSolvers/contact/ContactFields.hpp +++ b/src/coreComponents/physicsSolvers/contact/ContactFields.hpp @@ -20,6 +20,7 @@ #define GEOS_PHYSICSSOLVERS_CONTACT_CONTACTFIELDS_HPP_ #include "mesh/MeshFields.hpp" +#include "codingUtilities/EnumStrings.hpp" namespace geos { diff --git a/src/coreComponents/physicsSolvers/contact/LagrangianContactSolver.cpp b/src/coreComponents/physicsSolvers/contact/LagrangianContactSolver.cpp index 51db49cc3d2..b68f8b027c0 100644 --- a/src/coreComponents/physicsSolvers/contact/LagrangianContactSolver.cpp +++ b/src/coreComponents/physicsSolvers/contact/LagrangianContactSolver.cpp @@ -161,7 +161,7 @@ void LagrangianContactSolver::initializePreSubGroups() { FluxApproximationBase & fluxApprox = fvManager.getFluxApproximation( m_stabilizationName ); - fluxApprox.setFieldName( contact::traction::key() ); + fluxApprox.addFieldName( contact::traction::key() ); fluxApprox.setCoeffName( "penaltyStiffness" ); forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&] ( string const & meshBodyName, @@ -1284,9 +1284,7 @@ void LagrangianContactSolver::assembleStabilization( MeshLevel const & mesh, arrayView2d< localIndex const > const & faceToElemSubRegion = faceToElem.m_toElementSubRegion.toViewConst(); arrayView2d< localIndex const > const & faceToElemIndex = faceToElem.m_toElementIndex.toViewConst(); - // Form the SurfaceGenerator, get the fracture name and use it to retrieve the faceMap (from fracture element to face) - SurfaceGenerator const & surfaceGenerator = this->getParent().getGroup< SurfaceGenerator >( "SurfaceGen" ); - SurfaceElementRegion const & fractureRegion = elemManager.getRegion< SurfaceElementRegion >( surfaceGenerator.getFractureRegionName() ); + SurfaceElementRegion const & fractureRegion = elemManager.getRegion< SurfaceElementRegion >( getFractureRegionName() ); FaceElementSubRegion const & fractureSubRegion = fractureRegion.getUniqueSubRegion< FaceElementSubRegion >(); GEOS_ERROR_IF( !fractureSubRegion.hasField< contact::traction >(), "The fracture subregion must contain traction field." ); diff --git a/src/coreComponents/physicsSolvers/contact/SolidMechanicsEmbeddedFractures.cpp b/src/coreComponents/physicsSolvers/contact/SolidMechanicsEmbeddedFractures.cpp index 7968c0dae6c..b8e4ddecf53 100644 --- a/src/coreComponents/physicsSolvers/contact/SolidMechanicsEmbeddedFractures.cpp +++ b/src/coreComponents/physicsSolvers/contact/SolidMechanicsEmbeddedFractures.cpp @@ -66,18 +66,16 @@ void SolidMechanicsEmbeddedFractures::postProcessInput() m_solidSolver = &this->getParent().getGroup< SolidMechanicsLagrangianFEM >( m_solidSolverName ); LinearSolverParameters & linParams = m_linearSolverParameters.get(); - linParams.dofsPerNode = 3; if( m_useStaticCondensation ) { + linParams.dofsPerNode = 3; linParams.isSymmetric = true; linParams.amg.separateComponents = true; } else { linParams.mgr.strategy = LinearSolverParameters::MGR::StrategyType::solidMechanicsEmbeddedFractures; - linParams.mgr.separateComponents = true; - linParams.mgr.displacementFieldName = solidMechanics::totalDisplacement::key(); } } diff --git a/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseBase.cpp b/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseBase.cpp index d002977c8f0..9efbe5096e4 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseBase.cpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseBase.cpp @@ -223,17 +223,24 @@ void CompositionalMultiphaseBase::registerDataOnMesh( Group & meshBodies ) MultiFluidBase const & fluid = getConstitutiveModel< MultiFluidBase >( subRegion, fluidName ); subRegion.registerField< pressure >( getName() ); + subRegion.registerField< pressure_n >( getName() ); subRegion.registerField< initialPressure >( getName() ); subRegion.registerField< deltaPressure >( getName() ); // for reporting/stats purposes - subRegion.registerField< pressure_n >( getName() ); - subRegion.registerField< bcPressure >( getName() ); // needed for the application of boundary conditions + if( m_isFixedStressPoromechanicsUpdate ) + { + subRegion.registerField< pressure_k >( getName() ); // needed for the fixed-stress porosity update + } // these fields are always registered for the evaluation of the fluid properties subRegion.registerField< temperature >( getName() ); subRegion.registerField< temperature_n >( getName() ); subRegion.registerField< initialTemperature >( getName() ); subRegion.registerField< bcTemperature >( getName() ); // needed for the application of boundary conditions + if( m_isFixedStressPoromechanicsUpdate ) + { + subRegion.registerField< temperature_k >( getName() ); // needed for the fixed-stress porosity update + } // The resizing of the arrays needs to happen here, before the call to initializePreSubGroups, // to make sure that the dimensions are properly set before the timeHistoryOutput starts its initialization. @@ -786,19 +793,16 @@ void CompositionalMultiphaseBase::initializeFluidState( MeshLevel & mesh, } } ); - // 5. Save initial pressure (needed by the poromechanics solvers) - // Specifically, the initial pressure is used to compute a deltaPressure = currentPres - initPres in the total stress + // 5. Save initial pressure mesh.getElemManager().forElementSubRegions( regionNames, [&]( localIndex const, ElementSubRegionBase & subRegion ) { arrayView1d< real64 const > const pres = subRegion.getField< fields::flow::pressure >(); arrayView1d< real64 > const initPres = subRegion.getField< fields::flow::initialPressure >(); - arrayView1d< real64 > const temp = subRegion.template getField< fields::flow::temperature >(); - arrayView1d< real64 > const temp_n = subRegion.template getField< fields::flow::temperature_n >(); + arrayView1d< real64 const > const temp = subRegion.getField< fields::flow::temperature >(); arrayView1d< real64 > const initTemp = subRegion.template getField< fields::flow::initialTemperature >(); initPres.setValues< parallelDevicePolicy<> >( pres ); - temp_n.setValues< parallelDevicePolicy<> >( temp ); // to make sure temperature_n has a meaningful value in isothermal simulations - initTemp.setValues< parallelDevicePolicy<> >( temp ); // to make sure temperature_n has a meaningful value in isothermal simulations + initTemp.setValues< parallelDevicePolicy<> >( temp ); } ); } @@ -1998,7 +2002,14 @@ void CompositionalMultiphaseBase::implicitStepComplete( real64 const & time, // Step 3: save the converged solid state string const & solidName = subRegion.getReference< string >( viewKeyStruct::solidNamesString() ); CoupledSolidBase const & porousMaterial = getConstitutiveModel< CoupledSolidBase >( subRegion, solidName ); - porousMaterial.saveConvergedState(); + if( m_keepFlowVariablesConstantDuringInitStep ) + { + porousMaterial.ignoreConvergedState(); // newPorosity <- porosity_n + } + else + { + porousMaterial.saveConvergedState(); // porosity_n <- porosity + } // Step 4: save converged state for the relperm model to handle hysteresis arrayView2d< real64 const, compflow::USD_PHASE > const phaseVolFrac = @@ -2051,6 +2062,26 @@ void CompositionalMultiphaseBase::saveConvergedState( ElementSubRegionBase & sub compDens_n.setValues< parallelDevicePolicy<> >( compDens ); } +void CompositionalMultiphaseBase::saveIterationState( DomainPartition & domain ) const +{ + FlowSolverBase::saveIterationState( domain ); +} + +void CompositionalMultiphaseBase::saveIterationState( ElementSubRegionBase & subRegion ) const +{ + FlowSolverBase::saveIterationState( subRegion ); + + if( !subRegion.hasField< fields::flow::globalCompDensity_k >() ) + { + return; + } + + arrayView2d< real64 const, compflow::USD_COMP > const compDens = subRegion.template getField< fields::flow::globalCompDensity >(); + arrayView2d< real64, compflow::USD_COMP > const compDens_k = subRegion.template getField< fields::flow::globalCompDensity_k >(); + compDens_k.setValues< parallelDevicePolicy<> >( compDens ); +} + + void CompositionalMultiphaseBase::updateState( DomainPartition & domain ) { forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const &, diff --git a/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseBase.hpp b/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseBase.hpp index 6e94aa4d4a7..cb34abd20bd 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseBase.hpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseBase.hpp @@ -153,6 +153,10 @@ class CompositionalMultiphaseBase : public FlowSolverBase virtual void saveConvergedState( ElementSubRegionBase & subRegion ) const override final; + virtual void saveIterationState( DomainPartition & domain ) const override final; + + virtual void saveIterationState( ElementSubRegionBase & subRegion ) const override final; + virtual void updateState( DomainPartition & domain ) override final; /** diff --git a/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseBaseFields.hpp b/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseBaseFields.hpp index b1f56d022b8..d2b338e6db6 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseBaseFields.hpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseBaseFields.hpp @@ -53,7 +53,15 @@ DECLARE_FIELD( globalCompDensity_n, 0, NOPLOT, NO_WRITE, - "Global component density updates at the previous converged time step " ); + "Global component density updates at the previous converged time step" ); + +DECLARE_FIELD( globalCompDensity_k, + "globalCompDensity_k", + array2dLayoutComp, + 0, + NOPLOT, + NO_WRITE, + "Global component density updates at the previous sequential iteration" ); DECLARE_FIELD( globalCompFraction, "globalCompFraction", diff --git a/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseFVM.cpp b/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseFVM.cpp index 20bc41c0cdd..2034f7b3248 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseFVM.cpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseFVM.cpp @@ -80,6 +80,10 @@ void CompositionalMultiphaseFVM::setupDofs( DomainPartition const & domain, m_numDofPerCell, getMeshTargets() ); + // this call with instruct GEOS to reorder the dof numbers + //dofManager.setLocalReorderingType( viewKeyStruct::elemDofFieldString(), + // DofManager::LocalReorderingType::ReverseCutHillMcKee ); + // for the volume balance equation, disable global coupling // this equation is purely local (not coupled to neighbors or other physics) dofManager.disableGlobalCouplingForEquation( viewKeyStruct::elemDofFieldString(), @@ -141,6 +145,7 @@ void CompositionalMultiphaseFVM::assembleFluxTerms( real64 const dt, dofManager.rankOffset(), elemDofKey, m_hasCapPressure, + fluxApprox.upwindingParams(), getName(), mesh.getElemManager(), stencilWrapper, diff --git a/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseHybridFVM.cpp b/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseHybridFVM.cpp index 44bad9255f1..cde7df1dbbe 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseHybridFVM.cpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseHybridFVM.cpp @@ -267,6 +267,10 @@ void CompositionalMultiphaseHybridFVM::setupDofs( DomainPartition const & GEOS_U viewKeyStruct::elemDofFieldString(), DofManager::Connector::Face ); + // this call with instruct GEOS to reorder the dof numbers + //dofManager.setLocalReorderingType( viewKeyStruct::elemDofFieldString(), + // DofManager::LocalReorderingType::ReverseCutHillMcKee ); + // for the volume balance equation, disable global coupling // this equation is purely local (not coupled to neighbors or other physics) dofManager.disableGlobalCouplingForEquation( viewKeyStruct::elemDofFieldString(), diff --git a/src/coreComponents/physicsSolvers/fluidFlow/FlowSolverBase.cpp b/src/coreComponents/physicsSolvers/fluidFlow/FlowSolverBase.cpp index e9ede947817..ba6d124a516 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/FlowSolverBase.cpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/FlowSolverBase.cpp @@ -54,13 +54,42 @@ void updatePorosityAndPermeabilityFromPressureAndTemperature( POROUSWRAPPER_TYPE { porousWrapper.updateStateFromPressureAndTemperature( k, q, pressure[k], + pressure[k], // will not be used + pressure_n[k], // will not be used + temperature[k], + temperature[k], // will not be used + temperature_n[k] ); // will not be used + } + } ); +} + +template< typename POROUSWRAPPER_TYPE > +void updatePorosityAndPermeabilityFromPressureAndTemperature( POROUSWRAPPER_TYPE porousWrapper, + CellElementSubRegion & subRegion, + arrayView1d< real64 const > const & pressure, + arrayView1d< real64 const > const & pressure_k, + arrayView1d< real64 const > const & pressure_n, + arrayView1d< real64 const > const & temperature, + arrayView1d< real64 const > const & temperature_k, + arrayView1d< real64 const > const & temperature_n ) +{ + forAll< parallelDevicePolicy<> >( subRegion.size(), [=] GEOS_DEVICE ( localIndex const k ) + { + + for( localIndex q = 0; q < porousWrapper.numGauss(); ++q ) + { + porousWrapper.updateStateFromPressureAndTemperature( k, q, + pressure[k], + pressure_k[k], pressure_n[k], temperature[k], + temperature_k[k], temperature_n[k] ); } } ); } + template< typename POROUSWRAPPER_TYPE > void updatePorosityAndPermeabilityFromPressureAndAperture( POROUSWRAPPER_TYPE porousWrapper, SurfaceElementSubRegion & subRegion, @@ -84,7 +113,8 @@ FlowSolverBase::FlowSolverBase( string const & name, Group * const parent ): SolverBase( name, parent ), m_numDofPerCell( 0 ), - m_isThermal( 0 ) + m_isThermal( 0 ), + m_isFixedStressPoromechanicsUpdate( false ) { this->registerWrapper( viewKeyStruct::isThermalString(), &m_isThermal ). setApplyDefaultValue( 0 ). @@ -152,8 +182,12 @@ void FlowSolverBase::registerDataOnMesh( Group & meshBodies ) { FluxApproximationBase & fluxApprox = fvManager.getFluxApproximation( m_discretizationName ); - fluxApprox.setFieldName( fields::flow::pressure::key() ); + fluxApprox.addFieldName( fields::flow::pressure::key() ); fluxApprox.setCoeffName( fields::permeability::permeability::key() ); + if( m_isThermal ) + { + fluxApprox.addFieldName( fields::flow::temperature::key() ); + } } } @@ -163,15 +197,62 @@ void FlowSolverBase::saveConvergedState( ElementSubRegionBase & subRegion ) cons arrayView1d< real64 > const pres_n = subRegion.template getField< fields::flow::pressure_n >(); pres_n.setValues< parallelDevicePolicy<> >( pres ); - if( subRegion.hasField< fields::flow::temperature >() && - subRegion.hasField< fields::flow::temperature_n >() ) + arrayView1d< real64 const > const temp = subRegion.template getField< fields::flow::temperature >(); + arrayView1d< real64 > const temp_n = subRegion.template getField< fields::flow::temperature_n >(); + temp_n.setValues< parallelDevicePolicy<> >( temp ); + + GEOS_THROW_IF( subRegion.hasField< fields::flow::pressure_k >() != + subRegion.hasField< fields::flow::temperature_k >(), + GEOS_FMT( "`{}` and `{}` must be either both existing or both non-existing on subregion {}", + fields::flow::pressure_k::key(), fields::flow::temperature_k::key(), subRegion.getName() ), + std::runtime_error ); + + if( subRegion.hasField< fields::flow::pressure_k >() && + subRegion.hasField< fields::flow::temperature_k >() ) { - arrayView1d< real64 const > const temp = subRegion.template getField< fields::flow::temperature >(); - arrayView1d< real64 > const temp_n = subRegion.template getField< fields::flow::temperature_n >(); - temp_n.setValues< parallelDevicePolicy<> >( temp ); + arrayView1d< real64 > const pres_k = subRegion.template getField< fields::flow::pressure_k >(); + arrayView1d< real64 > const temp_k = subRegion.template getField< fields::flow::temperature_k >(); + pres_k.setValues< parallelDevicePolicy<> >( pres ); + temp_k.setValues< parallelDevicePolicy<> >( temp ); } } +void FlowSolverBase::saveIterationState( ElementSubRegionBase & subRegion ) const +{ + if( !( subRegion.hasField< fields::flow::pressure_k >() && + subRegion.hasField< fields::flow::temperature_k >() ) ) + { + return; + } + + arrayView1d< real64 const > const pres = subRegion.template getField< fields::flow::pressure >(); + arrayView1d< real64 const > const temp = subRegion.template getField< fields::flow::temperature >(); + arrayView1d< real64 > const pres_k = subRegion.template getField< fields::flow::pressure_k >(); + arrayView1d< real64 > const temp_k = subRegion.template getField< fields::flow::temperature_k >(); + pres_k.setValues< parallelDevicePolicy<> >( pres ); + temp_k.setValues< parallelDevicePolicy<> >( temp ); +} + +void FlowSolverBase::saveIterationState( DomainPartition & domain ) const +{ + forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const &, + MeshLevel & mesh, + arrayView1d< string const > const & regionNames ) + { + mesh.getElemManager().forElementSubRegions( regionNames, + [&]( localIndex const, + ElementSubRegionBase & subRegion ) + { + saveIterationState( subRegion ); + } ); + } ); +} + +void FlowSolverBase::enableFixedStressPoromechanicsUpdate() +{ + m_isFixedStressPoromechanicsUpdate = true; +} + void FlowSolverBase::setConstitutiveNamesCallSuper( ElementSubRegionBase & subRegion ) const { SolverBase::setConstitutiveNamesCallSuper( subRegion ); @@ -253,7 +334,9 @@ void FlowSolverBase::initializePreSubGroups() void FlowSolverBase::validatePoreVolumes( DomainPartition const & domain ) const { real64 minPoreVolume = LvArray::NumericLimits< real64 >::max; - globalIndex numElemsBelowThreshold = 0; + real64 maxPorosity = -LvArray::NumericLimits< real64 >::max; + globalIndex numElemsBelowPoreVolumeThreshold = 0; + globalIndex numElemsAbovePorosityThreshold = 0; forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&] ( string const &, MeshLevel const & mesh, @@ -268,33 +351,51 @@ void FlowSolverBase::validatePoreVolumes( DomainPartition const & domain ) const arrayView2d< real64 const > const porosity = porousMaterial.getPorosity(); arrayView1d< real64 const > const volume = subRegion.getElementVolume(); + arrayView1d< integer const > const ghostRank = subRegion.ghostRank(); real64 minPoreVolumeInSubRegion = 0.0; - localIndex numElemsBelowThresholdInSubRegion = 0; - - flowSolverBaseKernels::MinimumPoreVolumeKernel:: - computeMinimumPoreVolume( subRegion.size(), - porosity, - volume, - minPoreVolumeInSubRegion, - numElemsBelowThresholdInSubRegion ); + real64 maxPorosityInSubRegion = 0.0; + localIndex numElemsBelowPoreVolumeThresholdInSubRegion = 0; + localIndex numElemsAbovePorosityThresholdInSubRegion = 0; + + flowSolverBaseKernels::MinPoreVolumeMaxPorosityKernel:: + computeMinPoreVolumeMaxPorosity( subRegion.size(), + ghostRank, + porosity, + volume, + minPoreVolumeInSubRegion, + maxPorosityInSubRegion, + numElemsBelowPoreVolumeThresholdInSubRegion, + numElemsAbovePorosityThresholdInSubRegion ); if( minPoreVolumeInSubRegion < minPoreVolume ) { minPoreVolume = minPoreVolumeInSubRegion; } - numElemsBelowThreshold += numElemsBelowThresholdInSubRegion; + if( maxPorosityInSubRegion > maxPorosity ) + { + maxPorosity = maxPorosityInSubRegion; + } + + numElemsBelowPoreVolumeThreshold += numElemsBelowPoreVolumeThresholdInSubRegion; + numElemsAbovePorosityThreshold += numElemsAbovePorosityThresholdInSubRegion; } ); } ); minPoreVolume = MpiWrapper::min( minPoreVolume ); - numElemsBelowThreshold = MpiWrapper::sum( numElemsBelowThreshold ); + maxPorosity = MpiWrapper::max( maxPorosity ); + numElemsBelowPoreVolumeThreshold = MpiWrapper::sum( numElemsBelowPoreVolumeThreshold ); + numElemsAbovePorosityThreshold = MpiWrapper::sum( numElemsAbovePorosityThreshold ); - GEOS_LOG_RANK_0_IF( numElemsBelowThreshold > 0, + GEOS_LOG_RANK_0_IF( numElemsBelowPoreVolumeThreshold > 0, GEOS_FMT( "\nWarning! The mesh contains {} elements with a pore volume below {} m^3." "\nThe minimum pore volume is {} m^3." "\nOur recommendation is to check the validity of mesh and/or increase the porosity in these elements.\n", - numElemsBelowThreshold, flowSolverBaseKernels::poreVolumeThreshold, minPoreVolume ) ); + numElemsBelowPoreVolumeThreshold, flowSolverBaseKernels::poreVolumeThreshold, minPoreVolume ) ); + GEOS_LOG_RANK_0_IF( numElemsAbovePorosityThreshold > 0, + GEOS_FMT( "\nWarning! The mesh contains {} elements with a porosity above 1." + "\nThe maximum porosity is {}.\n", + numElemsAbovePorosityThreshold, maxPorosity ) ); } void FlowSolverBase::initializePostInitialConditionsPreSubGroups() @@ -357,11 +458,32 @@ void FlowSolverBase::updatePorosityAndPermeability( CellElementSubRegion & subRe string const & solidName = subRegion.getReference< string >( viewKeyStruct::solidNamesString() ); CoupledSolidBase & porousSolid = subRegion.template getConstitutiveModel< CoupledSolidBase >( solidName ); + GEOS_THROW_IF( subRegion.hasField< fields::flow::pressure_k >() != + subRegion.hasField< fields::flow::temperature_k >(), + GEOS_FMT( "`{}` and `{}` must be either both existing or both non-existing on subregion {}", + fields::flow::pressure_k::key(), fields::flow::temperature_k::key(), subRegion.getName() ), + std::runtime_error ); + constitutive::ConstitutivePassThru< CoupledSolidBase >::execute( porousSolid, [=, &subRegion] ( auto & castedPorousSolid ) { typename TYPEOFREF( castedPorousSolid ) ::KernelWrapper porousWrapper = castedPorousSolid.createKernelUpdates(); - updatePorosityAndPermeabilityFromPressureAndTemperature( porousWrapper, subRegion, pressure, pressure_n, temperature, temperature_n ); + if( subRegion.hasField< fields::flow::pressure_k >() && // for sequential simulations + subRegion.hasField< fields::flow::temperature_k >() ) + { + arrayView1d< real64 const > const & pressure_k = subRegion.getField< fields::flow::pressure_k >(); + arrayView1d< real64 const > const & temperature_k = subRegion.getField< fields::flow::temperature_k >(); + + updatePorosityAndPermeabilityFromPressureAndTemperature( porousWrapper, subRegion, + pressure, pressure_k, pressure_n, + temperature, temperature_k, temperature_n ); + } + else // for fully implicit simulations + { + updatePorosityAndPermeabilityFromPressureAndTemperature( porousWrapper, subRegion, + pressure, pressure_n, + temperature, temperature_n ); + } } ); } diff --git a/src/coreComponents/physicsSolvers/fluidFlow/FlowSolverBase.hpp b/src/coreComponents/physicsSolvers/fluidFlow/FlowSolverBase.hpp index fb1c26a9662..1819416d878 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/FlowSolverBase.hpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/FlowSolverBase.hpp @@ -75,10 +75,17 @@ class FlowSolverBase : public SolverBase static constexpr char const * solidInternalEnergyNamesString() { return "solidInternalEnergyNames"; } }; + void enableFixedStressPoromechanicsUpdate(); + void updatePorosityAndPermeability( CellElementSubRegion & subRegion ) const; virtual void updatePorosityAndPermeability( SurfaceElementSubRegion & subRegion ) const; + /** + * @brief Utility function to save the iteration state (useful for sequential simulations) + * @param[in] domain the domain partition + */ + virtual void saveIterationState( DomainPartition & domain ) const; /** * @brief For each equilibrium initial condition, loop over all the target cells and compute the min/max elevation @@ -128,6 +135,12 @@ class FlowSolverBase : public SolverBase */ virtual void saveConvergedState( ElementSubRegionBase & subRegion ) const; + /** + * @brief Utility function to save the state at the end of a sequential iteration + * @param[in] subRegion the element subRegion + */ + virtual void saveIterationState( ElementSubRegionBase & subRegion ) const; + /** * @brief Helper function to compute/report the elements with small pore volumes * @param[in] domain the domain partition @@ -149,6 +162,9 @@ class FlowSolverBase : public SolverBase /// flag to determine whether or not this is a thermal simulation integer m_isThermal; + /// enable the fixed stress poromechanics update of porosity + bool m_isFixedStressPoromechanicsUpdate; + private: virtual void setConstitutiveNames( ElementSubRegionBase & subRegion ) const override; diff --git a/src/coreComponents/physicsSolvers/fluidFlow/FlowSolverBaseFields.hpp b/src/coreComponents/physicsSolvers/fluidFlow/FlowSolverBaseFields.hpp index 115d2f3efe4..61240061e87 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/FlowSolverBaseFields.hpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/FlowSolverBaseFields.hpp @@ -48,6 +48,14 @@ DECLARE_FIELD( pressure_n, WRITE_AND_READ, "Pressure at the previous converged time step" ); +DECLARE_FIELD( pressure_k, + "pressure_k", + array1d< real64 >, + 0, + NOPLOT, + NO_WRITE, + "Pressure at the previous sequential iteration" ); + DECLARE_FIELD( initialPressure, "initialPressure", array1d< real64 >, @@ -104,6 +112,14 @@ DECLARE_FIELD( temperature_n, WRITE_AND_READ, "Temperature at the previous converged time step" ); +DECLARE_FIELD( temperature_k, + "temperature_k", + array1d< real64 >, + 0, + LEVEL_0, + NO_WRITE, + "Temperature at the previous sequential iteration" ); + DECLARE_FIELD( initialTemperature, "initialTemperature", array1d< real64 >, diff --git a/src/coreComponents/physicsSolvers/fluidFlow/FlowSolverBaseKernels.hpp b/src/coreComponents/physicsSolvers/fluidFlow/FlowSolverBaseKernels.hpp index a0b3f33c1d4..32697ddc709 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/FlowSolverBaseKernels.hpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/FlowSolverBaseKernels.hpp @@ -33,48 +33,72 @@ static constexpr real64 poreVolumeThreshold = 1e-4; /** - * @struct MinimumPoreVolumeKernel - * @brief Kernel to compute the min pore volume in a subRegion + * @struct MinPoreVolumeMaxPorosityKernel + * @brief Kernel to compute the min pore volume and the max porosity in a subRegion */ -struct MinimumPoreVolumeKernel +struct MinPoreVolumeMaxPorosityKernel { /* - * @brief Kernel computing the min pore volume + * @brief Kernel computing the min pore volume and the max porosity * @param[in] size the number of elements in the subRegion + * @param[in] ghostRank the ghost ranks * @param[in] porosity the current element porosity * @param[in] volume the current element volume * @param[out] minPoreVolumeInSubRegion the min pore volume - * @param[out] numElemsBelowThresholdInSubRegion the number of elements is below the threshold + * @param[out] maxPorosityInSubRegion the max porosity + * @param[out] numElemsBelowPoreVolumeThresholdInSubRegion the number of elements below the pore volume threshold + * @param[out] numElemsAbovePorosityThresholdInSubRegion the number of elements with a porosity above 1 */ inline static void - computeMinimumPoreVolume( localIndex const size, - arrayView2d< real64 const > const & porosity, - arrayView1d< real64 const > const & volume, - real64 & minPoreVolumeInSubRegion, - localIndex & numElemsBelowThresholdInSubRegion ) + computeMinPoreVolumeMaxPorosity( localIndex const size, + arrayView1d< integer const > const & ghostRank, + arrayView2d< real64 const > const & porosity, + arrayView1d< real64 const > const & volume, + real64 & minPoreVolumeInSubRegion, + real64 & maxPorosityInSubRegion, + localIndex & numElemsBelowPoreVolumeThresholdInSubRegion, + localIndex & numElemsAbovePorosityThresholdInSubRegion ) { RAJA::ReduceMin< parallelDeviceReduce, real64 > minPoreVolume( LvArray::NumericLimits< real64 >::max ); - RAJA::ReduceSum< parallelDeviceReduce, localIndex > numElemsBelowThreshold( 0.0 ); + RAJA::ReduceMax< parallelDeviceReduce, real64 > maxPorosity( -LvArray::NumericLimits< real64 >::max ); + RAJA::ReduceSum< parallelDeviceReduce, localIndex > numElemsBelowPoreVolumeThreshold( 0.0 ); + RAJA::ReduceSum< parallelDeviceReduce, localIndex > numElemsAbovePorosityThreshold( 0.0 ); real64 const pvThreshold = poreVolumeThreshold; - forAll< parallelDevicePolicy<> >( size, [porosity, + forAll< parallelDevicePolicy<> >( size, [ghostRank, + porosity, volume, pvThreshold, minPoreVolume, - numElemsBelowThreshold] GEOS_HOST_DEVICE ( localIndex const ei ) + maxPorosity, + numElemsBelowPoreVolumeThreshold, + numElemsAbovePorosityThreshold] GEOS_HOST_DEVICE ( localIndex const ei ) { + if( ghostRank[ei] >= 0 ) + { + return; + } + real64 const poreVolume = porosity[ei][0] * volume[ei]; if( poreVolume < pvThreshold ) { - numElemsBelowThreshold += 1; + numElemsBelowPoreVolumeThreshold += 1; } + if( porosity[ei][0] > 1 ) + { + numElemsAbovePorosityThreshold += 1; + } + minPoreVolume.min( poreVolume ); + maxPorosity.max( porosity[ei][0] ); } ); minPoreVolumeInSubRegion = minPoreVolume.get(); - numElemsBelowThresholdInSubRegion = numElemsBelowThreshold.get(); + maxPorosityInSubRegion = maxPorosity.get(); + numElemsBelowPoreVolumeThresholdInSubRegion = numElemsBelowPoreVolumeThreshold.get(); + numElemsAbovePorosityThresholdInSubRegion = numElemsAbovePorosityThreshold.get(); } }; diff --git a/src/coreComponents/physicsSolvers/fluidFlow/FluxKernelsHelper.hpp b/src/coreComponents/physicsSolvers/fluidFlow/FluxKernelsHelper.hpp index 92922658b29..684ba86a658 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/FluxKernelsHelper.hpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/FluxKernelsHelper.hpp @@ -47,6 +47,9 @@ void computeSinglePhaseFlux( localIndex const ( &seri )[2], ElementViewConst< arrayView2d< real64 const > > const & dDens_dPres, ElementViewConst< arrayView1d< real64 const > > const & mob, ElementViewConst< arrayView1d< real64 const > > const & dMob_dPres, + real64 & alpha, + real64 & mobility, + real64 & potGrad, real64 & fluxVal, real64 ( & dFlux_dP )[2], real64 & dFlux_dTrans ) @@ -62,11 +65,10 @@ void computeSinglePhaseFlux( localIndex const ( &seri )[2], } // compute potential difference - real64 potDif = 0.0; - real64 dPotDif_dTrans = 0.0; + real64 dpotGrad_dTrans = 0.0; real64 sumWeightGrav = 0.0; real64 potScale = 0.0; - int signPotDiff[2] = {1, -1}; + int signpotGradf[2] = {1, -1}; for( localIndex ke = 0; ke < 2; ++ke ) { @@ -78,8 +80,8 @@ void computeSinglePhaseFlux( localIndex const ( &seri )[2], real64 const gravD = gravCoef[er][esr][ei]; real64 const pot = transmissibility[ke] * ( pressure - densMean * gravD ); - potDif += pot; - dPotDif_dTrans += signPotDiff[ke] * ( pressure - densMean * gravD ); + potGrad += pot; + dpotGrad_dTrans += signpotGradf[ke] * ( pressure - densMean * gravD ); sumWeightGrav += transmissibility[ke] * gravD; potScale = fmax( potScale, fabs( pot ) ); @@ -90,9 +92,8 @@ void computeSinglePhaseFlux( localIndex const ( &seri )[2], real64 const upwAbsTol = fmax( potScale * upwRelTol, LvArray::NumericLimits< real64 >::epsilon ); // decide mobility coefficients - smooth variation in [-upwAbsTol; upwAbsTol] - real64 const alpha = ( potDif + upwAbsTol ) / ( 2 * upwAbsTol ); + alpha = ( potGrad + upwAbsTol ) / ( 2 * upwAbsTol ); - real64 mobility{}; real64 dMobility_dP[2]{}; if( alpha <= 0.0 || alpha >= 1.0 ) { @@ -113,18 +114,173 @@ void computeSinglePhaseFlux( localIndex const ( &seri )[2], } // compute the final flux and derivative w.r.t transmissibility - fluxVal = mobility * potDif; + fluxVal = mobility * potGrad; - dFlux_dTrans = mobility * dPotDif_dTrans; + dFlux_dTrans = mobility * dpotGrad_dTrans; for( localIndex ke = 0; ke < 2; ++ke ) { dFlux_dP[ke] = mobility * ( transmissibility[ke] - dDensMean_dP[ke] * sumWeightGrav ) - + dMobility_dP[ke] * potDif + dFlux_dTrans * dTrans_dPres[ke]; + + dMobility_dP[ke] * potGrad + dFlux_dTrans * dTrans_dPres[ke]; } } + +template< typename ENERGYFLUX_DERIVATIVE_TYPE > +GEOS_HOST_DEVICE +void computeEnthalpyFlux( localIndex const ( &seri )[2], + localIndex const ( &sesri )[2], + localIndex const ( &sei )[2], + real64 const ( &transmissibility )[2], + ElementViewConst< arrayView2d< real64 const > > const & enthalpy, + ElementViewConst< arrayView2d< real64 const > > const & dEnthalpy_dPressure, + ElementViewConst< arrayView2d< real64 const > > const & dEnthalpy_dTemperature, + ElementViewConst< arrayView1d< real64 const > > const & gravCoef, + ElementViewConst< arrayView2d< real64 const > > const & dDens_dTemp, + ElementViewConst< arrayView1d< real64 const > > const & dMob_dTemp, + real64 const & alpha, + real64 const & mobility, + real64 const & potGrad, + real64 const & massFlux, + real64 const & dMassFlux_dTrans, + real64 const ( &dMassFlux_dP )[2], + real64 ( & dMassFlux_dT )[2], + real64 & energyFlux, + real64 & dEnergyFlux_dTrans, + ENERGYFLUX_DERIVATIVE_TYPE & dEnergyFlux_dP, + ENERGYFLUX_DERIVATIVE_TYPE & dEnergyFlux_dT ) +{ + // Step 1: compute the derivatives of the mean density at the interface wrt temperature + + real64 dDensMean_dT[2]{0.0, 0.0}; + + for( integer ke = 0; ke < 2; ++ke ) + { + real64 const dDens_dT = dDens_dTemp[seri[ke]][sesri[ke]][sei[ke]][0]; + dDensMean_dT[ke] = 0.5 * dDens_dT; + } + + // Step 2: compute the derivatives of the potential difference wrt temperature + //***** calculation of flux ***** + + real64 dGravHead_dT[2]{0.0, 0.0}; + + // compute potential difference + for( integer ke = 0; ke < 2; ++ke ) + { + localIndex const er = seri[ke]; + localIndex const esr = sesri[ke]; + localIndex const ei = sei[ke]; + + // compute derivative of gravity potential difference wrt temperature + real64 const gravD = transmissibility[ke] * gravCoef[er][esr][ei]; + + for( integer i = 0; i < 2; ++i ) + { + dGravHead_dT[i] += dDensMean_dT[i] * gravD; + } + } + + // Step 3: compute the derivatives of the (upwinded) compFlux wrt temperature + // *** upwinding *** + + // Step 3.1: compute the derivative of the mass flux wrt temperature + for( integer ke = 0; ke < 2; ++ke ) + { + dMassFlux_dT[ke] -= dGravHead_dT[ke]; + } + + for( integer ke = 0; ke < 2; ++ke ) + { + dMassFlux_dT[ke] *= mobility; + } + + real64 dMob_dT[2]{}; + + if( alpha <= 0.0 || alpha >= 1.0 ) + { + localIndex const k_up = 1 - localIndex( fmax( fmin( alpha, 1.0 ), 0.0 ) ); + + dMob_dT[k_up] = dMob_dTemp[seri[k_up]][sesri[k_up]][sei[k_up]]; + } + else + { + real64 const mobWeights[2] = { alpha, 1.0 - alpha }; + for( integer ke = 0; ke < 2; ++ke ) + { + dMob_dT[ke] = mobWeights[ke] * dMob_dTemp[seri[ke]][sesri[ke]][sei[ke]]; + } + } + + // add contribution from upstream cell mobility derivatives + for( integer ke = 0; ke < 2; ++ke ) + { + dMassFlux_dT[ke] += dMob_dT[ke] * potGrad; + } + + // Step 4: compute the enthalpy flux + real64 enthalpyTimesMobWeight = 0.0; + real64 dEnthalpy_dP[2]{0.0, 0.0}; + real64 dEnthalpy_dT[2]{0.0, 0.0}; + + if( alpha <= 0.0 || alpha >= 1.0 ) + { + localIndex const k_up = 1 - localIndex( fmax( fmin( alpha, 1.0 ), 0.0 ) ); + + enthalpyTimesMobWeight = enthalpy[seri[k_up]][sesri[k_up]][sei[k_up]][0]; + dEnthalpy_dP[k_up] = dEnthalpy_dPressure[seri[k_up]][sesri[k_up]][sei[k_up]][0]; + dEnthalpy_dT[k_up] = dEnthalpy_dTemperature[seri[k_up]][sesri[k_up]][sei[k_up]][0]; + } + else + { + real64 const mobWeights[2] = { alpha, 1.0 - alpha }; + for( integer ke = 0; ke < 2; ++ke ) + { + enthalpyTimesMobWeight += mobWeights[ke] * enthalpy[seri[ke]][sesri[ke]][sei[ke]][0]; + dEnthalpy_dP[ke] = mobWeights[ke] * dEnthalpy_dPressure[seri[ke]][sesri[ke]][sei[ke]][0]; + dEnthalpy_dT[ke] = mobWeights[ke] * dEnthalpy_dTemperature[seri[ke]][sesri[ke]][sei[ke]][0]; + } + } + + energyFlux += massFlux * enthalpyTimesMobWeight; + dEnergyFlux_dTrans = enthalpyTimesMobWeight * dMassFlux_dTrans; + + for( integer ke = 0; ke < 2; ++ke ) + { + dEnergyFlux_dP[ke] += dMassFlux_dP[ke] * enthalpyTimesMobWeight; + dEnergyFlux_dT[ke] += dMassFlux_dT[ke] * enthalpyTimesMobWeight; + } + + for( integer ke = 0; ke < 2; ++ke ) + { + dEnergyFlux_dP[ke] += massFlux * dEnthalpy_dP[ke]; + dEnergyFlux_dT[ke] += massFlux * dEnthalpy_dT[ke]; + } +} + + +template< typename ENERGYFLUX_DERIVATIVE_TYPE > +GEOS_HOST_DEVICE +void computeConductiveFlux( localIndex const ( &seri )[2], + localIndex const ( &sesri )[2], + localIndex const ( &sei )[2], + ElementViewConst< arrayView1d< real64 const > > const & temperature, + real64 const ( &thermalTrans )[2], + real64 & energyFlux, + ENERGYFLUX_DERIVATIVE_TYPE & dEnergyFlux_dT ) +{ + for( integer ke = 0; ke < 2; ++ke ) + { + localIndex const er = seri[ke]; + localIndex const esr = sesri[ke]; + localIndex const ei = sei[ke]; + + energyFlux += thermalTrans[ke] * temperature[er][esr][ei]; + dEnergyFlux_dT[ke] += thermalTrans[ke]; + } +} + /******************************** AquiferBCKernel ********************************/ /** diff --git a/src/coreComponents/physicsSolvers/fluidFlow/IsothermalCompositionalMultiphaseFVMKernelUtilities.hpp b/src/coreComponents/physicsSolvers/fluidFlow/IsothermalCompositionalMultiphaseFVMKernelUtilities.hpp index 5989342e2c0..62e3a7f9857 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/IsothermalCompositionalMultiphaseFVMKernelUtilities.hpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/IsothermalCompositionalMultiphaseFVMKernelUtilities.hpp @@ -32,83 +32,60 @@ namespace geos namespace isothermalCompositionalMultiphaseFVMKernelUtilities { +// TODO make input parameter +static constexpr real64 epsC1PPU = 1e-8; + template< typename VIEWTYPE > using ElementViewConst = ElementRegionManager::ElementViewConst< VIEWTYPE >; +using Deriv = constitutive::multifluid::DerivativeOffset; -struct FluxUtilities +struct PotGrad { - - using Deriv = constitutive::multifluid::DerivativeOffset; - - /** - * @brief Form the PhasePotentialUpwind from pressure gradient and gravitational head - * @tparam numComp number of components - * @tparam numFluxSupportPoints number of flux support points - * @param numPhase number of phases - * @param ip phase index - * @param hasCapPressure flag indicating if there is capillary pressure - * @param seri arraySlice of the stencil-implied element region index - * @param sesri arraySlice of the stencil-implied element subregion index - * @param sei arraySlice of the stencil-implied element index - * @param trans transmissibility at the connection - * @param dTrans_dPres derivative of transmissibility wrt pressure - * @param pres pressure - * @param gravCoef gravitational coefficient - * @param phaseMob phase mobility - * @param dPhaseMob derivative of phase mobility wrt pressure, temperature, comp density - * @param dPhaseVolFrac derivative of phase volume fraction wrt pressure, temperature, comp density - * @param dCompFrac_dCompDens derivative of component fraction wrt component density - * @param phaseMassDens phase mass density - * @param dPhaseMassDens derivative of phase mass density wrt pressure, temperature, comp fraction - * @param phaseCapPressure phase capillary pressure - * @param dPhaseCapPressure_dPhaseVolFrac derivative of phase capillary pressure wrt phase volume fraction - * @param k_up uptream index for this phase - * @param potGrad potential gradient for this phase - * @param phaseFlux phase flux - * @param dPhaseFlux_dP derivative of phase flux wrt pressure - * @param dPhaseFlux_dC derivative of phase flux wrt comp density - */ template< integer numComp, integer numFluxSupportPoints > GEOS_HOST_DEVICE static void - computePPUPhaseFlux( integer const numPhase, - integer const ip, - integer const hasCapPressure, - localIndex const ( &seri )[numFluxSupportPoints], - localIndex const ( &sesri )[numFluxSupportPoints], - localIndex const ( &sei )[numFluxSupportPoints], - real64 const ( &trans )[2], - real64 const ( &dTrans_dPres )[2], - ElementViewConst< arrayView1d< real64 const > > const & pres, - ElementViewConst< arrayView1d< real64 const > > const & gravCoef, - ElementViewConst< arrayView2d< real64 const, compflow::USD_PHASE > > const & phaseMob, - ElementViewConst< arrayView3d< real64 const, compflow::USD_PHASE_DC > > const & dPhaseMob, - ElementViewConst< arrayView3d< real64 const, compflow::USD_PHASE_DC > > const & dPhaseVolFrac, - ElementViewConst< arrayView3d< real64 const, compflow::USD_COMP_DC > > const & dCompFrac_dCompDens, - ElementViewConst< arrayView3d< real64 const, constitutive::multifluid::USD_PHASE > > const & phaseMassDens, - ElementViewConst< arrayView4d< real64 const, constitutive::multifluid::USD_PHASE_DC > > const & dPhaseMassDens, - ElementViewConst< arrayView3d< real64 const, constitutive::cappres::USD_CAPPRES > > const & phaseCapPressure, - ElementViewConst< arrayView4d< real64 const, constitutive::cappres::USD_CAPPRES_DS > > const & dPhaseCapPressure_dPhaseVolFrac, - localIndex & k_up, - real64 & potGrad, - real64 ( &phaseFlux ), - real64 ( & dPhaseFlux_dP )[numFluxSupportPoints], - real64 ( & dPhaseFlux_dC )[numFluxSupportPoints][numComp] ) + compute ( integer const numPhase, + integer const ip, + integer const hasCapPressure, + localIndex const ( &seri )[numFluxSupportPoints], + localIndex const ( &sesri )[numFluxSupportPoints], + localIndex const ( &sei )[numFluxSupportPoints], + real64 const ( &trans )[numFluxSupportPoints], + real64 const ( &dTrans_dPres )[numFluxSupportPoints], + ElementViewConst< arrayView1d< real64 const > > const & pres, + ElementViewConst< arrayView1d< real64 const > > const & gravCoef, + ElementViewConst< arrayView3d< real64 const, compflow::USD_PHASE_DC > > const & dPhaseVolFrac, + ElementViewConst< arrayView3d< real64 const, compflow::USD_COMP_DC > > const & dCompFrac_dCompDens, + ElementViewConst< arrayView3d< real64 const, constitutive::multifluid::USD_PHASE > > const & phaseMassDens, + ElementViewConst< arrayView4d< real64 const, constitutive::multifluid::USD_PHASE_DC > > const & dPhaseMassDens, + ElementViewConst< arrayView3d< real64 const, constitutive::cappres::USD_CAPPRES > > const & phaseCapPressure, + ElementViewConst< arrayView4d< real64 const, constitutive::cappres::USD_CAPPRES_DS > > const & dPhaseCapPressure_dPhaseVolFrac, + real64 & potGrad, + real64 ( & dPresGrad_dP )[numFluxSupportPoints], + real64 ( & dPresGrad_dC )[numFluxSupportPoints][numComp], + real64 ( & dGravHead_dP )[numFluxSupportPoints], + real64 ( & dGravHead_dC )[numFluxSupportPoints][numComp] ) { + // assign derivatives arrays to zero + for( integer i = 0; i < numFluxSupportPoints; ++i ) + { + dPresGrad_dP[i] = 0.0; + dGravHead_dP[i] = 0.0; + for( integer jc = 0; jc < numComp; ++jc ) + { + dPresGrad_dC[i][jc] = 0.0; + dGravHead_dC[i][jc] = 0.0; + } + } + // create local work arrays real64 densMean = 0.0; real64 dDensMean_dP[numFluxSupportPoints]{}; real64 dDensMean_dC[numFluxSupportPoints][numComp]{}; real64 presGrad = 0.0; - real64 dPresGrad_dP[numFluxSupportPoints]{}; - real64 dPresGrad_dC[numFluxSupportPoints][numComp]{}; - real64 gravHead = 0.0; - real64 dGravHead_dP[numFluxSupportPoints]{}; - real64 dGravHead_dC[numFluxSupportPoints][numComp]{}; - real64 dCapPressure_dC[numComp]{}; real64 dProp_dC[numComp]{}; @@ -198,10 +175,82 @@ struct FluxUtilities } } - // *** upwinding *** // compute phase potential gradient potGrad = presGrad - gravHead; + } + +}; + +struct PPUPhaseFlux +{ + /** + * @brief Form the PhasePotentialUpwind from pressure gradient and gravitational head + * @tparam numComp number of components + * @tparam numFluxSupportPoints number of flux support points + * @param numPhase number of phases + * @param ip phase index + * @param hasCapPressure flag indicating if there is capillary pressure + * @param seri arraySlice of the stencil-implied element region index + * @param sesri arraySlice of the stencil-implied element subregion index + * @param sei arraySlice of the stencil-implied element index + * @param trans transmissibility at the connection + * @param dTrans_dPres derivative of transmissibility wrt pressure + * @param pres pressure + * @param gravCoef gravitational coefficient + * @param phaseMob phase mobility + * @param dPhaseMob derivative of phase mobility wrt pressure, temperature, comp density + * @param dPhaseVolFrac derivative of phase volume fraction wrt pressure, temperature, comp density + * @param dCompFrac_dCompDens derivative of component fraction wrt component density + * @param phaseMassDens phase mass density + * @param dPhaseMassDens derivative of phase mass density wrt pressure, temperature, comp fraction + * @param phaseCapPressure phase capillary pressure + * @param dPhaseCapPressure_dPhaseVolFrac derivative of phase capillary pressure wrt phase volume fraction + * @param k_up uptream index for this phase + * @param potGrad potential gradient for this phase + * @param phaseFlux phase flux + * @param dPhaseFlux_dP derivative of phase flux wrt pressure + * @param dPhaseFlux_dC derivative of phase flux wrt comp density + */ + template< integer numComp, integer numFluxSupportPoints > + GEOS_HOST_DEVICE + static void + compute( integer const numPhase, + integer const ip, + integer const hasCapPressure, + //real64 const GEOS_UNUSED_PARAM( epsC1PPU ), + localIndex const ( &seri )[numFluxSupportPoints], + localIndex const ( &sesri )[numFluxSupportPoints], + localIndex const ( &sei )[numFluxSupportPoints], + real64 const ( &trans )[2], + real64 const ( &dTrans_dPres )[2], + ElementViewConst< arrayView1d< real64 const > > const & pres, + ElementViewConst< arrayView1d< real64 const > > const & gravCoef, + ElementViewConst< arrayView2d< real64 const, compflow::USD_PHASE > > const & phaseMob, + ElementViewConst< arrayView3d< real64 const, compflow::USD_PHASE_DC > > const & dPhaseMob, + ElementViewConst< arrayView3d< real64 const, compflow::USD_PHASE_DC > > const & dPhaseVolFrac, + ElementViewConst< arrayView3d< real64 const, compflow::USD_COMP_DC > > const & dCompFrac_dCompDens, + ElementViewConst< arrayView3d< real64 const, constitutive::multifluid::USD_PHASE > > const & phaseMassDens, + ElementViewConst< arrayView4d< real64 const, constitutive::multifluid::USD_PHASE_DC > > const & dPhaseMassDens, + ElementViewConst< arrayView3d< real64 const, constitutive::cappres::USD_CAPPRES > > const & phaseCapPressure, + ElementViewConst< arrayView4d< real64 const, constitutive::cappres::USD_CAPPRES_DS > > const & dPhaseCapPressure_dPhaseVolFrac, + localIndex & k_up, + real64 & potGrad, + real64 ( &phaseFlux ), + real64 ( & dPhaseFlux_dP )[numFluxSupportPoints], + real64 ( & dPhaseFlux_dC )[numFluxSupportPoints][numComp] ) + { + real64 dPresGrad_dP[numFluxSupportPoints]{}; + real64 dPresGrad_dC[numFluxSupportPoints][numComp]{}; + real64 dGravHead_dP[numFluxSupportPoints]{}; + real64 dGravHead_dC[numFluxSupportPoints][numComp]{}; + PotGrad::compute< numComp, numFluxSupportPoints >( numPhase, ip, hasCapPressure, seri, sesri, sei, trans, dTrans_dPres, pres, + gravCoef, dPhaseVolFrac, dCompFrac_dCompDens, phaseMassDens, dPhaseMassDens, + phaseCapPressure, dPhaseCapPressure_dPhaseVolFrac, potGrad, dPresGrad_dP, + dPresGrad_dC, dGravHead_dP, dGravHead_dC ); + + // *** upwinding *** + // choose upstream cell k_up = (potGrad >= 0) ? 0 : 1; @@ -236,7 +285,159 @@ struct FluxUtilities dPhaseFlux_dC[k_up][jc] += dPhaseMobSub[Deriv::dC+jc] * potGrad; } } +}; + +struct C1PPUPhaseFlux +{ + /** + * @brief Form the PhasePotentialUpwind from pressure gradient and gravitational head + * @tparam numComp number of components + * @tparam numFluxSupportPoints number of flux support points + * @param numPhase number of phases + * @param ip phase index + * @param hasCapPressure flag indicating if there is capillary pressure + * @param seri arraySlice of the stencil-implied element region index + * @param sesri arraySlice of the stencil-implied element subregion index + * @param sei arraySlice of the stencil-implied element index + * @param trans transmissibility at the connection + * @param dTrans_dPres derivative of transmissibility wrt pressure + * @param pres pressure + * @param gravCoef gravitational coefficient + * @param phaseMob phase mobility + * @param dPhaseMob derivative of phase mobility wrt pressure, temperature, comp density + * @param dPhaseVolFrac derivative of phase volume fraction wrt pressure, temperature, comp density + * @param dCompFrac_dCompDens derivative of component fraction wrt component density + * @param phaseMassDens phase mass density + * @param dPhaseMassDens derivative of phase mass density wrt pressure, temperature, comp fraction + * @param phaseCapPressure phase capillary pressure + * @param dPhaseCapPressure_dPhaseVolFrac derivative of phase capillary pressure wrt phase volume fraction + * @param k_up uptream index for this phase + * @param potGrad potential gradient for this phase + * @param phaseFlux phase flux + * @param dPhaseFlux_dP derivative of phase flux wrt pressure + * @param dPhaseFlux_dC derivative of phase flux wrt comp density + */ + template< integer numComp, integer numFluxSupportPoints > + GEOS_HOST_DEVICE + static void + compute( integer const numPhase, + integer const ip, + integer const hasCapPressure, + //real64 const epsC1PPU, + localIndex const ( &seri )[numFluxSupportPoints], + localIndex const ( &sesri )[numFluxSupportPoints], + localIndex const ( &sei )[numFluxSupportPoints], + real64 const ( &trans )[2], + real64 const ( &dTrans_dPres )[2], + ElementViewConst< arrayView1d< real64 const > > const & pres, + ElementViewConst< arrayView1d< real64 const > > const & gravCoef, + ElementViewConst< arrayView2d< real64 const, compflow::USD_PHASE > > const & phaseMob, + ElementViewConst< arrayView3d< real64 const, compflow::USD_PHASE_DC > > const & dPhaseMob, + ElementViewConst< arrayView3d< real64 const, compflow::USD_PHASE_DC > > const & dPhaseVolFrac, + ElementViewConst< arrayView3d< real64 const, compflow::USD_COMP_DC > > const & dCompFrac_dCompDens, + ElementViewConst< arrayView3d< real64 const, constitutive::multifluid::USD_PHASE > > const & phaseMassDens, + ElementViewConst< arrayView4d< real64 const, constitutive::multifluid::USD_PHASE_DC > > const & dPhaseMassDens, + ElementViewConst< arrayView3d< real64 const, constitutive::cappres::USD_CAPPRES > > const & phaseCapPressure, + ElementViewConst< arrayView4d< real64 const, constitutive::cappres::USD_CAPPRES_DS > > const & dPhaseCapPressure_dPhaseVolFrac, + localIndex & k_up, + real64 & potGrad, + real64 ( &phaseFlux ), + real64 ( & dPhaseFlux_dP )[numFluxSupportPoints], + real64 ( & dPhaseFlux_dC )[numFluxSupportPoints][numComp] ) + { + real64 dPresGrad_dP[numFluxSupportPoints]; + real64 dPresGrad_dC[numFluxSupportPoints][numComp]; + real64 dGravHead_dP[numFluxSupportPoints]; + real64 dGravHead_dC[numFluxSupportPoints][numComp]; + PotGrad::compute< numComp, numFluxSupportPoints >( numPhase, ip, hasCapPressure, seri, sesri, sei, trans, dTrans_dPres, pres, + gravCoef, dPhaseVolFrac, dCompFrac_dCompDens, phaseMassDens, dPhaseMassDens, + phaseCapPressure, dPhaseCapPressure_dPhaseVolFrac, potGrad, dPresGrad_dP, + dPresGrad_dC, dGravHead_dP, dGravHead_dC ); + + // *** upwinding *** + + // phase flux and derivatives + + // assuming TPFA in the code below + + real64 const mobility_i = phaseMob[seri[0]][sesri[0]][sei[0]][ip]; + real64 const mobility_j = phaseMob[seri[1]][sesri[1]][sei[1]][ip]; + + // compute phase flux, see Eqs. (66) and (69) from the reference above + real64 const smoEps = epsC1PPU; + real64 const tmpSqrt = sqrt( potGrad * potGrad + smoEps * smoEps ); + real64 const smoMax = 0.5 * (-potGrad + tmpSqrt); + + phaseFlux = potGrad * mobility_i - smoMax * (mobility_j - mobility_i); + + // derivativess + + // first part, mobility derivative + + // dP + { + real64 const dMob_dP = dPhaseMob[seri[0]][sesri[0]][sei[0]][ip][Deriv::dP]; + dPhaseFlux_dP[0] += potGrad * dMob_dP; + } + + // dC + { + arraySlice1d< real64 const, compflow::USD_PHASE_DC - 2 > + dPhaseMobSub = dPhaseMob[seri[0]][sesri[0]][sei[0]][ip]; + for( integer jc = 0; jc < numComp; ++jc ) + { + dPhaseFlux_dC[0][jc] += potGrad * dPhaseMobSub[Deriv::dC + jc]; + } + } + + real64 const tmpInv = 1.0 / tmpSqrt; + real64 const dSmoMax_x = 0.5 * (1.0 - potGrad * tmpInv); + + // pressure gradient and mobility difference depend on all points in the stencil + real64 const dMobDiff_sign[numFluxSupportPoints] = {-1.0, 1.0}; + for( integer ke = 0; ke < numFluxSupportPoints; ++ke ) + { + // dP + + real64 const dPotGrad_dP = dPresGrad_dP[ke] - dGravHead_dP[ke]; + + // first part + dPhaseFlux_dP[ke] += dPotGrad_dP * mobility_i; + + // second part + real64 const dSmoMax_dP = -dPotGrad_dP * dSmoMax_x; + dPhaseFlux_dP[ke] += -dSmoMax_dP * (mobility_j - mobility_i); + + real64 const dMob_dP = dPhaseMob[seri[ke]][sesri[ke]][sei[ke]][ip][Deriv::dP]; + dPhaseFlux_dP[ke] += -smoMax * dMobDiff_sign[ke] * dMob_dP; + + // dC + + arraySlice1d< real64 const, compflow::USD_PHASE_DC - 2 > + dPhaseMobSub = dPhaseMob[seri[ke]][sesri[ke]][sei[ke]][ip]; + + for( integer jc = 0; jc < numComp; ++jc ) + { + real64 const dPotGrad_dC = dPresGrad_dC[ke][jc] - dGravHead_dC[ke][jc]; + + // first part + dPhaseFlux_dC[ke][jc] += dPotGrad_dC * mobility_i; + + // second part + real64 const dSmoMax_dC = -dPotGrad_dC * dSmoMax_x; + dPhaseFlux_dC[ke][jc] += -dSmoMax_dC * (mobility_j - mobility_i); + dPhaseFlux_dC[ke][jc] += -smoMax * dMobDiff_sign[ke] * dPhaseMobSub[Deriv::dC + jc]; + } + } + + // choose upstream cell for composition upwinding + k_up = (phaseFlux >= 0) ? 0 : 1; + + } +}; +struct PhaseComponentFlux +{ /** * @brief Compute the component flux for a given phase * @tparam numComp number of components @@ -259,20 +460,20 @@ struct FluxUtilities template< localIndex numComp, localIndex numFluxSupportPoints > GEOS_HOST_DEVICE static void - computePhaseComponentFlux( localIndex const ip, - localIndex const k_up, - localIndex const ( &seri )[numFluxSupportPoints], - localIndex const ( &sesri )[numFluxSupportPoints], - localIndex const ( &sei )[numFluxSupportPoints], - ElementViewConst< arrayView4d< real64 const, constitutive::multifluid::USD_PHASE_COMP > > const & phaseCompFrac, - ElementViewConst< arrayView5d< real64 const, constitutive::multifluid::USD_PHASE_COMP_DC > > const & dPhaseCompFrac, - ElementViewConst< arrayView3d< real64 const, compflow::USD_COMP_DC > > const & dCompFrac_dCompDens, - real64 const & phaseFlux, - real64 const ( &dPhaseFlux_dP )[numFluxSupportPoints], - real64 const ( &dPhaseFlux_dC )[numFluxSupportPoints][numComp], - real64 ( & compFlux )[numComp], - real64 ( & dCompFlux_dP )[numFluxSupportPoints][numComp], - real64 ( & dCompFlux_dC )[numFluxSupportPoints][numComp][numComp] ) + compute( localIndex const ip, + localIndex const k_up, + localIndex const ( &seri )[numFluxSupportPoints], + localIndex const ( &sesri )[numFluxSupportPoints], + localIndex const ( &sei )[numFluxSupportPoints], + ElementViewConst< arrayView4d< real64 const, constitutive::multifluid::USD_PHASE_COMP > > const & phaseCompFrac, + ElementViewConst< arrayView5d< real64 const, constitutive::multifluid::USD_PHASE_COMP_DC > > const & dPhaseCompFrac, + ElementViewConst< arrayView3d< real64 const, compflow::USD_COMP_DC > > const & dCompFrac_dCompDens, + real64 const & phaseFlux, + real64 const ( &dPhaseFlux_dP )[numFluxSupportPoints], + real64 const ( &dPhaseFlux_dC )[numFluxSupportPoints][numComp], + real64 ( & compFlux )[numComp], + real64 ( & dCompFlux_dP )[numFluxSupportPoints][numComp], + real64 ( & dCompFlux_dC )[numFluxSupportPoints][numComp][numComp] ) { localIndex const er_up = seri[k_up]; localIndex const esr_up = sesri[k_up]; diff --git a/src/coreComponents/physicsSolvers/fluidFlow/IsothermalCompositionalMultiphaseFVMKernels.hpp b/src/coreComponents/physicsSolvers/fluidFlow/IsothermalCompositionalMultiphaseFVMKernels.hpp index cf55cfaa822..0682bd1ca99 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/IsothermalCompositionalMultiphaseFVMKernels.hpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/IsothermalCompositionalMultiphaseFVMKernels.hpp @@ -41,6 +41,7 @@ #include "physicsSolvers/fluidFlow/IsothermalCompositionalMultiphaseBaseKernels.hpp" #include "physicsSolvers/fluidFlow/IsothermalCompositionalMultiphaseFVMKernelUtilities.hpp" #include "physicsSolvers/fluidFlow/StencilAccessors.hpp" +#include "finiteVolume/FluxApproximationBase.hpp" namespace geos { @@ -393,7 +394,7 @@ class FaceBasedAssemblyKernelBase * @tparam STENCILWRAPPER the type of the stencil wrapper * @brief Define the interface for the assembly kernel in charge of flux terms */ -template< integer NUM_COMP, integer NUM_DOF, typename STENCILWRAPPER > +template< integer NUM_COMP, integer NUM_DOF, typename STENCILWRAPPER, typename PHASE_FLUX_COMPUTE = isothermalCompositionalMultiphaseFVMKernelUtilities::PPUPhaseFlux > class FaceBasedAssemblyKernel : public FaceBasedAssemblyKernelBase { public: @@ -437,6 +438,7 @@ class FaceBasedAssemblyKernel : public FaceBasedAssemblyKernelBase FaceBasedAssemblyKernel( integer const numPhases, globalIndex const rankOffset, integer const hasCapPressure, + //real64 const epsC1PPU, STENCILWRAPPER const & stencilWrapper, DofNumberAccessor const & dofNumberAccessor, CompFlowAccessors const & compFlowAccessors, @@ -460,8 +462,9 @@ class FaceBasedAssemblyKernel : public FaceBasedAssemblyKernelBase m_stencilWrapper( stencilWrapper ), m_seri( stencilWrapper.getElementRegionIndices() ), m_sesri( stencilWrapper.getElementSubRegionIndices() ), - m_sei( stencilWrapper.getElementIndices() ) - {} + m_sei( stencilWrapper.getElementIndices() )//, + //m_epsC1PPU( epsC1PPU ) + { } /** * @struct StackVariables @@ -610,12 +613,11 @@ class FaceBasedAssemblyKernel : public FaceBasedAssemblyKernelBase localIndex k_up = -1; - isothermalCompositionalMultiphaseFVMKernelUtilities:: - FluxUtilities:: - computePPUPhaseFlux< numComp, numFluxSupportPoints > + PHASE_FLUX_COMPUTE::template compute< numComp, numFluxSupportPoints > ( m_numPhases, ip, m_hasCapPressure, + //m_epsC1PPU, seri, sesri, sei, trans, dTrans_dPres, @@ -633,8 +635,7 @@ class FaceBasedAssemblyKernel : public FaceBasedAssemblyKernelBase dPhaseFlux_dC ); isothermalCompositionalMultiphaseFVMKernelUtilities:: - FluxUtilities:: - computePhaseComponentFlux< numComp, numFluxSupportPoints > + PhaseComponentFlux::compute< numComp, numFluxSupportPoints > ( ip, k_up, seri, sesri, sei, @@ -764,6 +765,9 @@ class FaceBasedAssemblyKernel : public FaceBasedAssemblyKernelBase typename STENCILWRAPPER::IndexContainerViewConstType const m_seri; typename STENCILWRAPPER::IndexContainerViewConstType const m_sesri; typename STENCILWRAPPER::IndexContainerViewConstType const m_sei; + + /// Tolerance for C1-PPU smoothing + //real64 const m_epsC1PPU; }; /** @@ -796,6 +800,7 @@ class FaceBasedAssemblyKernelFactory globalIndex const rankOffset, string const & dofKey, integer const hasCapPressure, + UpwindingParameters upwindingParams, string const & solverName, ElementRegionManager const & elemManager, STENCILWRAPPER const & stencilWrapper, @@ -803,25 +808,44 @@ class FaceBasedAssemblyKernelFactory CRSMatrixView< real64, globalIndex const > const & localMatrix, arrayView1d< real64 > const & localRhs ) { - isothermalCompositionalMultiphaseBaseKernels::internal::kernelLaunchSelectorCompSwitch( numComps, [&] ( auto NC ) + isothermalCompositionalMultiphaseBaseKernels::internal::kernelLaunchSelectorCompSwitch( numComps, [&]( auto NC ) { integer constexpr NUM_COMP = NC(); - integer constexpr NUM_DOF = NC()+1; + integer constexpr NUM_DOF = NC() + 1; ElementRegionManager::ElementViewAccessor< arrayView1d< globalIndex const > > dofNumberAccessor = elemManager.constructArrayViewAccessor< globalIndex, 1 >( dofKey ); dofNumberAccessor.setName( solverName + "/accessors/" + dofKey ); - using kernelType = FaceBasedAssemblyKernel< NUM_COMP, NUM_DOF, STENCILWRAPPER >; - typename kernelType::CompFlowAccessors compFlowAccessors( elemManager, solverName ); - typename kernelType::MultiFluidAccessors multiFluidAccessors( elemManager, solverName ); - typename kernelType::CapPressureAccessors capPressureAccessors( elemManager, solverName ); - typename kernelType::PermeabilityAccessors permeabilityAccessors( elemManager, solverName ); + if( upwindingParams.upwindingScheme == UpwindingScheme::C1PPU && isothermalCompositionalMultiphaseFVMKernelUtilities::epsC1PPU > 0 ) //upwindingParams.epsC1PPU + // > + // 0 + // ) + { + using kernelType = FaceBasedAssemblyKernel< NUM_COMP, NUM_DOF, STENCILWRAPPER, isothermalCompositionalMultiphaseFVMKernelUtilities::C1PPUPhaseFlux >; + typename kernelType::CompFlowAccessors compFlowAccessors( elemManager, solverName ); + typename kernelType::MultiFluidAccessors multiFluidAccessors( elemManager, solverName ); + typename kernelType::CapPressureAccessors capPressureAccessors( elemManager, solverName ); + typename kernelType::PermeabilityAccessors permeabilityAccessors( elemManager, solverName ); + + kernelType kernel( numPhases, rankOffset, hasCapPressure, /*upwindingParams.epsC1PPU,*/ stencilWrapper, dofNumberAccessor, + compFlowAccessors, multiFluidAccessors, capPressureAccessors, permeabilityAccessors, + dt, localMatrix, localRhs ); + kernelType::template launch< POLICY >( stencilWrapper.size(), kernel ); + } + else + { + using kernelType = FaceBasedAssemblyKernel< NUM_COMP, NUM_DOF, STENCILWRAPPER >; + typename kernelType::CompFlowAccessors compFlowAccessors( elemManager, solverName ); + typename kernelType::MultiFluidAccessors multiFluidAccessors( elemManager, solverName ); + typename kernelType::CapPressureAccessors capPressureAccessors( elemManager, solverName ); + typename kernelType::PermeabilityAccessors permeabilityAccessors( elemManager, solverName ); - kernelType kernel( numPhases, rankOffset, hasCapPressure, stencilWrapper, dofNumberAccessor, - compFlowAccessors, multiFluidAccessors, capPressureAccessors, permeabilityAccessors, - dt, localMatrix, localRhs ); - kernelType::template launch< POLICY >( stencilWrapper.size(), kernel ); + kernelType kernel( numPhases, rankOffset, hasCapPressure, /*upwindingParams.epsC1PPU,*/ stencilWrapper, dofNumberAccessor, + compFlowAccessors, multiFluidAccessors, capPressureAccessors, permeabilityAccessors, + dt, localMatrix, localRhs ); + kernelType::template launch< POLICY >( stencilWrapper.size(), kernel ); + } } ); } }; @@ -920,6 +944,7 @@ class DirichletFaceBasedAssemblyKernel : public FaceBasedAssemblyKernel< NUM_COM : Base( numPhases, rankOffset, hasCapPressure, + //0.0, // no C1-PPU stencilWrapper, dofNumberAccessor, compFlowAccessors, @@ -1305,10 +1330,10 @@ class DirichletFaceBasedAssemblyKernelFactory using FluidType = TYPEOFREF( fluid ); typename FluidType::KernelWrapper const fluidWrapper = fluid.createKernelWrapper(); - isothermalCompositionalMultiphaseBaseKernels::internal::kernelLaunchSelectorCompSwitch( numComps, [&] ( auto NC ) + isothermalCompositionalMultiphaseBaseKernels::internal::kernelLaunchSelectorCompSwitch( numComps, [&]( auto NC ) { integer constexpr NUM_COMP = NC(); - integer constexpr NUM_DOF = NC()+1; + integer constexpr NUM_DOF = NC() + 1; ElementRegionManager::ElementViewAccessor< arrayView1d< globalIndex const > > dofNumberAccessor = elemManager.constructArrayViewAccessor< globalIndex, 1 >( dofKey ); diff --git a/src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseBase.cpp b/src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseBase.cpp index 499b0ecd4b7..05c2eab2ef5 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseBase.cpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseBase.cpp @@ -25,7 +25,6 @@ #include "constitutive/fluid/singlefluid/SingleFluidFields.hpp" #include "constitutive/fluid/singlefluid/SingleFluidSelector.hpp" #include "constitutive/permeability/PermeabilityFields.hpp" -#include "constitutive/solid/CoupledSolidBase.hpp" #include "constitutive/solid/SolidInternalEnergy.hpp" #include "constitutive/thermalConductivity/singlePhaseThermalConductivitySelector.hpp" #include "fieldSpecification/AquiferBoundaryCondition.hpp" @@ -81,12 +80,15 @@ void SinglePhaseBase::registerDataOnMesh( Group & meshBodies ) [&]( localIndex const, ElementSubRegionBase & subRegion ) { + subRegion.registerField< pressure >( getName() ); subRegion.registerField< pressure_n >( getName() ); subRegion.registerField< initialPressure >( getName() ); subRegion.registerField< deltaPressure >( getName() ); // for reporting/stats purposes - subRegion.registerField< pressure >( getName() ); - subRegion.registerField< bcPressure >( getName() ); // needed for the application of boundary conditions + if( m_isFixedStressPoromechanicsUpdate ) + { + subRegion.registerField< pressure_k >( getName() ); // needed for the fixed-stress porosity update + } subRegion.registerField< deltaVolume >( getName() ); @@ -94,6 +96,10 @@ void SinglePhaseBase::registerDataOnMesh( Group & meshBodies ) subRegion.registerField< temperature_n >( getName() ); subRegion.registerField< initialTemperature >( getName() ); subRegion.registerField< bcTemperature >( getName() ); // needed for the application of boundary conditions + if( m_isFixedStressPoromechanicsUpdate ) + { + subRegion.registerField< temperature_k >( getName() ); // needed for the fixed-stress porosity update + } subRegion.registerField< mobility >( getName() ); subRegion.registerField< dMobility_dPressure >( getName() ); @@ -266,6 +272,21 @@ void SinglePhaseBase::updateSolidInternalEnergyModel( ObjectManagerBase & dataGr thermalSinglePhaseBaseKernels::SolidInternalEnergyUpdateKernel::launch< parallelDevicePolicy<> >( dataGroup.size(), solidInternalEnergyWrapper, temp ); } +void SinglePhaseBase::updateThermalConductivity( ElementSubRegionBase & subRegion ) const +{ + //START_SPHINX_INCLUDE_COUPLEDSOLID + CoupledSolidBase const & porousSolid = + getConstitutiveModel< CoupledSolidBase >( subRegion, subRegion.template getReference< string >( viewKeyStruct::solidNamesString() ) ); + //END_SPHINX_INCLUDE_COUPLEDSOLID + + arrayView2d< real64 const > const porosity = porousSolid.getPorosity(); + + string const & thermalConductivityName = subRegion.template getReference< string >( viewKeyStruct::thermalConductivityNamesString() ); + SinglePhaseThermalConductivityBase const & conductivityMaterial = + getConstitutiveModel< SinglePhaseThermalConductivityBase >( subRegion, thermalConductivityName ); + conductivityMaterial.update( porosity ); +} + void SinglePhaseBase::updateFluidState( ObjectManagerBase & subRegion ) const { updateFluidModel( subRegion ); @@ -626,6 +647,8 @@ void SinglePhaseBase::implicitStepSetup( real64 const & GEOS_UNUSED_PARAM( time_ if( m_isThermal ) { updateSolidInternalEnergyModel( subRegion ); + updateThermalConductivity( subRegion ); + } } ); @@ -688,7 +711,14 @@ void SinglePhaseBase::implicitStepComplete( real64 const & time, CoupledSolidBase const & porousSolid = getConstitutiveModel< CoupledSolidBase >( subRegion, subRegion.template getReference< string >( viewKeyStruct::solidNamesString() ) ); - porousSolid.saveConvergedState(); + if( m_keepFlowVariablesConstantDuringInitStep ) + { + porousSolid.ignoreConvergedState(); // newPorosity <- porosity_n + } + else + { + porousSolid.saveConvergedState(); // porosity_n <- porosity + } if( m_isThermal ) { diff --git a/src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseBase.hpp b/src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseBase.hpp index b6f5a8fdc67..8f8b87340fc 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseBase.hpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseBase.hpp @@ -22,6 +22,9 @@ #include "physicsSolvers/fluidFlow/FlowSolverBase.hpp" #include "physicsSolvers/fluidFlow/SinglePhaseBaseKernels.hpp" #include "physicsSolvers/fluidFlow/ThermalSinglePhaseBaseKernels.hpp" +#include "constitutive/fluid/singlefluid/SingleFluidBase.hpp" +#include "constitutive/solid/CoupledSolidBase.hpp" + namespace geos { @@ -167,13 +170,13 @@ class SinglePhaseBase : public FlowSolverBase * @param jumpDofKey dofKey of the displacement jump */ virtual void - assemblePoroelasticFluxTerms( real64 const time_n, - real64 const dt, - DomainPartition const & domain, - DofManager const & dofManager, - CRSMatrixView< real64, globalIndex const > const & localMatrix, - arrayView1d< real64 > const & localRhs, - string const & jumpDofKey ) = 0; + assembleEDFMFluxTerms( real64 const time_n, + real64 const dt, + DomainPartition const & domain, + DofManager const & dofManager, + CRSMatrixView< real64, globalIndex const > const & localMatrix, + arrayView1d< real64 > const & localRhs, + string const & jumpDofKey ) = 0; /** * @brief assembles the flux terms for all cells for the hydrofracture case @@ -278,6 +281,12 @@ class SinglePhaseBase : public FlowSolverBase */ void updateSolidInternalEnergyModel( ObjectManagerBase & dataGroup ) const; + /** + * @brief Update thermal conductivity + * @param subRegion the group storing the required fields + */ + void updateThermalConductivity( ElementSubRegionBase & subRegion ) const; + /** * @brief Function to update fluid mobility * @param dataGroup group that contains the fields diff --git a/src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseFVM.cpp b/src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseFVM.cpp index 2e76491600f..88c9d35783d 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseFVM.cpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseFVM.cpp @@ -36,7 +36,12 @@ #include "physicsSolvers/fluidFlow/ThermalSinglePhaseBaseKernels.hpp" #include "physicsSolvers/fluidFlow/SinglePhaseFVMKernels.hpp" #include "physicsSolvers/fluidFlow/ThermalSinglePhaseFVMKernels.hpp" -#include "physicsSolvers/multiphysics/SinglePhasePoromechanicsFluxKernels.hpp" + +#include "physicsSolvers/fluidFlow/SinglePhaseProppantFluxKernels.hpp" +#include "physicsSolvers/multiphysics/poromechanicsKernels/SinglePhasePoromechanicsEmbeddedFractures.hpp" +#include "physicsSolvers/multiphysics/poromechanicsKernels/ThermalSinglePhasePoromechanicsEmbeddedFractures.hpp" +#include "physicsSolvers/multiphysics/poromechanicsKernels/SinglePhasePoromechanicsConformingFractures.hpp" +#include "physicsSolvers/multiphysics/poromechanicsKernels/ThermalSinglePhasePoromechanicsConformingFractures.hpp" /** * @namespace the geosx namespace that encapsulates the majority of the code @@ -48,7 +53,6 @@ using namespace dataRepository; using namespace constitutive; using namespace singlePhaseBaseKernels; using namespace singlePhaseFVMKernels; -using namespace singlePhasePoromechanicsFluxKernels; template< typename BASE > SinglePhaseFVM< BASE >::SinglePhaseFVM( const string & name, @@ -69,6 +73,13 @@ void SinglePhaseFVM< BASE >::initializePreSubGroups() { GEOS_ERROR( "A discretization deriving from FluxApproximationBase must be selected with SinglePhaseFVM" ); } + + if( m_isThermal ) + { + // For thermal simulations 2 pdes are considered so we let AMG know. + LinearSolverParameters & linParams = m_linearSolverParameters.get(); + linParams.amg.numFunctions = 2; + } } template< typename BASE > @@ -378,41 +389,41 @@ void SinglePhaseFVM< SinglePhaseProppantBase >::assembleFluxTerms( real64 const { typename TYPEOFREF( stencil ) ::KernelWrapper stencilWrapper = stencil.createKernelWrapper(); - typename FluxKernel::SinglePhaseFlowAccessors flowAccessors( elemManager, getName() ); - typename FluxKernel::SlurryFluidAccessors fluidAccessors( elemManager, getName() ); - typename FluxKernel::ProppantPermeabilityAccessors permAccessors( elemManager, getName() ); - - FaceElementFluxKernel::launch( stencilWrapper, - dt, - dofManager.rankOffset(), - elemDofNumber.toNestedViewConst(), - flowAccessors.get< fields::ghostRank >(), - flowAccessors.get< fields::flow::pressure >(), - flowAccessors.get< fields::flow::gravityCoefficient >(), - fluidAccessors.get< fields::singlefluid::density >(), - fluidAccessors.get< fields::singlefluid::dDensity_dPressure >(), - flowAccessors.get< fields::flow::mobility >(), - flowAccessors.get< fields::flow::dMobility_dPressure >(), - permAccessors.get< fields::permeability::permeability >(), - permAccessors.get< fields::permeability::dPerm_dPressure >(), - permAccessors.get< fields::permeability::dPerm_dDispJump >(), - permAccessors.get< fields::permeability::permeabilityMultiplier >(), - this->gravityVector(), - localMatrix, - localRhs ); + typename FaceBasedAssemblyKernelBase::SinglePhaseFlowAccessors flowAccessors( elemManager, getName() ); + typename FaceBasedAssemblyKernelBase::SlurryFluidAccessors fluidAccessors( elemManager, getName() ); + typename FaceBasedAssemblyKernelBase::ProppantPermeabilityAccessors permAccessors( elemManager, getName() ); + + singlePhaseProppantFluxKernels::FaceElementFluxKernel::launch( stencilWrapper, + dt, + dofManager.rankOffset(), + elemDofNumber.toNestedViewConst(), + flowAccessors.get< fields::ghostRank >(), + flowAccessors.get< fields::flow::pressure >(), + flowAccessors.get< fields::flow::gravityCoefficient >(), + fluidAccessors.get< fields::singlefluid::density >(), + fluidAccessors.get< fields::singlefluid::dDensity_dPressure >(), + flowAccessors.get< fields::flow::mobility >(), + flowAccessors.get< fields::flow::dMobility_dPressure >(), + permAccessors.get< fields::permeability::permeability >(), + permAccessors.get< fields::permeability::dPerm_dPressure >(), + permAccessors.get< fields::permeability::dPerm_dDispJump >(), + permAccessors.get< fields::permeability::permeabilityMultiplier >(), + this->gravityVector(), + localMatrix, + localRhs ); } ); } ); } template< typename BASE > -void SinglePhaseFVM< BASE >::assemblePoroelasticFluxTerms( real64 const GEOS_UNUSED_PARAM ( time_n ), - real64 const dt, - DomainPartition const & domain, - DofManager const & dofManager, - CRSMatrixView< real64, globalIndex const > const & localMatrix, - arrayView1d< real64 > const & localRhs, - string const & jumpDofKey ) +void SinglePhaseFVM< BASE >::assembleEDFMFluxTerms( real64 const GEOS_UNUSED_PARAM ( time_n ), + real64 const dt, + DomainPartition const & domain, + DofManager const & dofManager, + CRSMatrixView< real64, globalIndex const > const & localMatrix, + arrayView1d< real64 > const & localRhs, + string const & jumpDofKey ) { GEOS_MARK_FUNCTION; @@ -420,50 +431,74 @@ void SinglePhaseFVM< BASE >::assemblePoroelasticFluxTerms( real64 const GEOS_UNU FiniteVolumeManager const & fvManager = numericalMethodManager.getFiniteVolumeManager(); FluxApproximationBase const & fluxApprox = fvManager.getFluxApproximation( m_discretizationName ); - string const & pressureDofKey = dofManager.getKey( SinglePhaseBase::viewKeyStruct::elemDofFieldString() ); + string const & dofKey = dofManager.getKey( SinglePhaseBase::viewKeyStruct::elemDofFieldString() ); this->forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&] ( string const &, MeshLevel const & mesh, arrayView1d< string const > const & ) { - ElementRegionManager const & elemManager = mesh.getElemManager(); - - ElementRegionManager::ElementViewAccessor< arrayView1d< globalIndex const > > - pressureDofNumber = elemManager.constructArrayViewAccessor< globalIndex, 1 >( pressureDofKey ); - pressureDofNumber.setName( this->getName() + "/accessors/" + pressureDofKey ); - - ElementRegionManager::ElementViewAccessor< arrayView1d< globalIndex const > > - jumpDofNumber = elemManager.constructArrayViewAccessor< globalIndex, 1 >( jumpDofKey ); - jumpDofNumber.setName( this->getName() + "/accessors/" + jumpDofKey ); + fluxApprox.forStencils< CellElementStencilTPFA, EmbeddedSurfaceToCellStencil >( mesh, [&]( auto & stencil ) + { + typename TYPEOFREF( stencil ) ::KernelWrapper stencilWrapper = stencil.createKernelWrapper(); - ElementRegionManager::ElementViewAccessor< arrayView4d< real64 const > > dPerm_dDispJump = - elemManager.constructMaterialArrayViewAccessor< PermeabilityBase, real64, 4 >( fields::permeability::dPerm_dDispJump::key() ); + if( m_isThermal ) + { + thermalSinglePhaseFVMKernels:: + FaceBasedAssemblyKernelFactory::createAndLaunch< parallelDevicePolicy<> >( dofManager.rankOffset(), + dofKey, + this->getName(), + mesh.getElemManager(), + stencilWrapper, + dt, + localMatrix.toViewConstSizes(), + localRhs.toView() ); + } + else + { + singlePhaseFVMKernels:: + FaceBasedAssemblyKernelFactory::createAndLaunch< parallelDevicePolicy<> >( dofManager.rankOffset(), + dofKey, + this->getName(), + mesh.getElemManager(), + stencilWrapper, + dt, + localMatrix.toViewConstSizes(), + localRhs.toView() ); + } + } ); - fluxApprox.forStencils< CellElementStencilTPFA, SurfaceElementStencil, EmbeddedSurfaceToCellStencil >( mesh, [&]( auto & stencil ) + // Eventually, EmbeddedSurfaceToCellStencil should be moved here to account for the permeability of the fracture in the matrix-frac + // connection + fluxApprox.forStencils< SurfaceElementStencil >( mesh, [&]( auto & stencil ) { typename TYPEOFREF( stencil ) ::KernelWrapper stencilWrapper = stencil.createKernelWrapper(); - typename FluxKernel::SinglePhaseFlowAccessors flowAccessors( elemManager, this->getName() ); - typename FluxKernel::SinglePhaseFluidAccessors fluidAccessors( elemManager, this->getName() ); - typename FluxKernel::PermeabilityAccessors permAccessors( elemManager, this->getName() ); - - EmbeddedSurfaceFluxKernel::launch( stencilWrapper, - dt, - dofManager.rankOffset(), - pressureDofNumber.toNestedViewConst(), - jumpDofNumber.toNestedViewConst(), - flowAccessors.get< fields::ghostRank >(), - flowAccessors.get< fields::flow::pressure >(), - flowAccessors.get< fields::flow::gravityCoefficient >(), - fluidAccessors.get< fields::singlefluid::density >(), - fluidAccessors.get< fields::singlefluid::dDensity_dPressure >(), - flowAccessors.get< fields::flow::mobility >(), - flowAccessors.get< fields::flow::dMobility_dPressure >(), - permAccessors.get< fields::permeability::permeability >(), - permAccessors.get< fields::permeability::dPerm_dPressure >(), - dPerm_dDispJump.toNestedViewConst(), - localMatrix, - localRhs ); + if( m_isThermal ) + { + thermalSinglePhasePoromechanicsEmbeddedFracturesKernels:: + ConnectorBasedAssemblyKernelFactory::createAndLaunch< parallelDevicePolicy<> >( dofManager.rankOffset(), + dofKey, + jumpDofKey, + this->getName(), + mesh.getElemManager(), + stencilWrapper, + dt, + localMatrix.toViewConstSizes(), + localRhs.toView() ); + } + else + { + singlePhasePoromechanicsEmbeddedFracturesKernels:: + ConnectorBasedAssemblyKernelFactory::createAndLaunch< parallelDevicePolicy<> >( dofManager.rankOffset(), + dofKey, + jumpDofKey, + this->getName(), + mesh.getElemManager(), + stencilWrapper, + dt, + localMatrix.toViewConstSizes(), + localRhs.toView() ); + } } ); } ); @@ -491,39 +526,66 @@ void SinglePhaseFVM< BASE >::assembleHydrofracFluxTerms( real64 const GEOS_UNUSE MeshLevel const & mesh, arrayView1d< string const > const & ) { - ElementRegionManager const & elemManager = mesh.getElemManager(); - ElementRegionManager::ElementViewAccessor< arrayView1d< globalIndex const > > - elemDofNumber = elemManager.constructArrayViewAccessor< globalIndex, 1 >( dofKey ); - elemDofNumber.setName( this->getName() + "/accessors/" + dofKey ); + fluxApprox.forStencils< CellElementStencilTPFA, FaceElementToCellStencil >( mesh, [&]( auto & stencil ) + { + typename TYPEOFREF( stencil ) ::KernelWrapper stencilWrapper = stencil.createKernelWrapper(); - ElementRegionManager::ElementViewAccessor< arrayView4d< real64 const > > dPerm_dDispJump = - elemManager.constructMaterialArrayViewAccessor< PermeabilityBase, real64, 4 >( fields::permeability::dPerm_dDispJump::key() ); + if( m_isThermal ) + { + thermalSinglePhaseFVMKernels:: + FaceBasedAssemblyKernelFactory::createAndLaunch< parallelDevicePolicy<> >( dofManager.rankOffset(), + dofKey, + this->getName(), + mesh.getElemManager(), + stencilWrapper, + dt, + localMatrix.toViewConstSizes(), + localRhs.toView() ); + } + else + { + singlePhaseFVMKernels:: + FaceBasedAssemblyKernelFactory::createAndLaunch< parallelDevicePolicy<> >( dofManager.rankOffset(), + dofKey, + this->getName(), + mesh.getElemManager(), + stencilWrapper, + dt, + localMatrix.toViewConstSizes(), + localRhs.toView() ); + } + } ); - fluxApprox.forStencils< CellElementStencilTPFA, SurfaceElementStencil, FaceElementToCellStencil >( mesh, [&]( auto & stencil ) + fluxApprox.forStencils< SurfaceElementStencil >( mesh, [&]( auto & stencil ) { typename TYPEOFREF( stencil ) ::KernelWrapper stencilWrapper = stencil.createKernelWrapper(); - typename FluxKernel::SinglePhaseFlowAccessors flowAccessors( elemManager, this->getName() ); - typename FluxKernel::SinglePhaseFluidAccessors fluidAccessors( elemManager, this->getName() ); - typename FluxKernel::PermeabilityAccessors permAccessors( elemManager, this->getName() ); - - FaceElementFluxKernel::launch( stencilWrapper, - dt, - dofManager.rankOffset(), - elemDofNumber.toNestedViewConst(), - flowAccessors.get< fields::ghostRank >(), - flowAccessors.get< fields::flow::pressure >(), - flowAccessors.get< fields::flow::gravityCoefficient >(), - fluidAccessors.get< fields::singlefluid::density >(), - fluidAccessors.get< fields::singlefluid::dDensity_dPressure >(), - flowAccessors.get< fields::flow::mobility >(), - flowAccessors.get< fields::flow::dMobility_dPressure >(), - permAccessors.get< fields::permeability::permeability >(), - permAccessors.get< fields::permeability::dPerm_dPressure >(), - dPerm_dDispJump.toNestedViewConst(), - localMatrix, - localRhs, - dR_dAper ); + if( m_isThermal ) + { + thermalSinglePhasePoromechanicsConformingFracturesKernels:: + ConnectorBasedAssemblyKernelFactory::createAndLaunch< parallelDevicePolicy<> >( dofManager.rankOffset(), + dofKey, + this->getName(), + mesh.getElemManager(), + stencilWrapper, + dt, + localMatrix.toViewConstSizes(), + localRhs.toView(), + dR_dAper ); + } + else + { + singlePhasePoromechanicsConformingFracturesKernels:: + ConnectorBasedAssemblyKernelFactory::createAndLaunch< parallelDevicePolicy<> >( dofManager.rankOffset(), + dofKey, + this->getName(), + mesh.getElemManager(), + stencilWrapper, + dt, + localMatrix.toViewConstSizes(), + localRhs.toView(), + dR_dAper ); + } } ); } ); @@ -555,6 +617,10 @@ char const faceBcLogMessage[] = "the <{}> boundary condition '{}' is applied to the face set '{}' in '{}'. " "\nThe total number of target faces (including ghost faces) is {}. " "\nNote that if this number is equal to zero, the boundary condition will not be applied on this face set."; + +GEOS_MAYBE_UNUSED char const incompleteBCLogmessage[] = "SinglePhaseFVM {}: at time {}, one or more Face boundary conditions are not complete. " + "Both pressure and temperature must be specified, one is missing."; + } @@ -583,87 +649,97 @@ void SinglePhaseFVM< BASE >::applyFaceDirichletBC( real64 const time_n, FaceManager & faceManager = mesh.getFaceManager(); ElementRegionManager const & elemManager = mesh.getElemManager(); - // Take BCs defined for "temperature" field and apply values to "faceTemperature" - - fsManager.apply< FaceManager >( time_n + dt, - mesh, - fields::flow::temperature::key(), - [&] ( FieldSpecificationBase const & fs, - string const & setName, - SortedArrayView< localIndex const > const & targetSet, - FaceManager & targetGroup, - string const & ) + if( m_isThermal ) { - BoundaryStencil const & stencil = fluxApprox.getStencil< BoundaryStencil >( mesh, setName ); - - if( fs.getLogLevel() >= 1 && m_nonlinearSolverParameters.m_numNewtonIterations == 0 ) + std::set< string > pressureSets; + std::set< string > temperatureSets; + // Take BCs defined for "pressure" field and apply values to "facePressure" + fsManager.apply< FaceManager >( time_n + dt, + mesh, + fields::flow::pressure::key(), + [&] ( FieldSpecificationBase const & fs, + string const & setName, + SortedArrayView< localIndex const > const & targetSet, + FaceManager & targetGroup, + string const & ) { - globalIndex const numTargetFaces = MpiWrapper::sum< globalIndex >( stencil.size() ); - GEOS_LOG_RANK_0( GEOS_FMT( faceBcLogMessage, - this->getName(), time_n+dt, FieldSpecificationBase::catalogName(), - fs.getName(), setName, targetGroup.getName(), numTargetFaces ) ); - } - - if( stencil.size() == 0 ) + BoundaryStencil const & stencil = fluxApprox.getStencil< BoundaryStencil >( mesh, setName ); + + if( fs.getLogLevel() >= 1 && m_nonlinearSolverParameters.m_numNewtonIterations == 0 ) + { + globalIndex const numTargetFaces = MpiWrapper::sum< globalIndex >( stencil.size() ); + GEOS_LOG_RANK_0( GEOS_FMT( faceBcLogMessage, + this->getName(), time_n+dt, FieldSpecificationBase::catalogName(), + fs.getName(), setName, targetGroup.getName(), numTargetFaces ) ); + } + + if( stencil.size() == 0 ) + { + return; + } + + pressureSets.insert( setName ); + // first, evaluate BC to get primary field values (pressure) + fs.applyFieldValue< FieldSpecificationEqual, + parallelDevicePolicy<> >( targetSet, + time_n + dt, + targetGroup, + fields::flow::facePressure::key() ); + } ); + + // Take BCs defined for "temperature" field and apply values to "faceTemperature" + fsManager.apply< FaceManager >( time_n + dt, + mesh, + fields::flow::temperature::key(), + [&] ( FieldSpecificationBase const & fs, + string const & setName, + SortedArrayView< localIndex const > const & targetSet, + FaceManager & targetGroup, + string const & ) { - return; - } - - // Specify the bc value of the field - fs.applyFieldValue< FieldSpecificationEqual, - parallelDevicePolicy<> >( targetSet, - time_n + dt, - targetGroup, - fields::flow::faceTemperature::key() ); - } ); - - // Take BCs defined for "pressure" field and apply values to "facePressure" - fsManager.apply< FaceManager >( time_n + dt, - mesh, - fields::flow::pressure::key(), - [&] ( FieldSpecificationBase const & fs, - string const & setName, - SortedArrayView< localIndex const > const & targetSet, - FaceManager & targetGroup, - string const & ) - { - BoundaryStencil const & stencil = fluxApprox.getStencil< BoundaryStencil >( mesh, setName ); - - if( fs.getLogLevel() >= 1 && m_nonlinearSolverParameters.m_numNewtonIterations == 0 ) + BoundaryStencil const & stencil = fluxApprox.getStencil< BoundaryStencil >( mesh, setName ); + + if( fs.getLogLevel() >= 1 && m_nonlinearSolverParameters.m_numNewtonIterations == 0 ) + { + globalIndex const numTargetFaces = MpiWrapper::sum< globalIndex >( stencil.size() ); + GEOS_LOG_RANK_0( GEOS_FMT( faceBcLogMessage, + this->getName(), time_n+dt, FieldSpecificationBase::catalogName(), + fs.getName(), setName, targetGroup.getName(), numTargetFaces ) ); + } + + if( stencil.size() == 0 ) + { + return; + } + + temperatureSets.insert( setName ); + // Specify the bc value of the field + fs.applyFieldValue< FieldSpecificationEqual, + parallelDevicePolicy<> >( targetSet, + time_n + dt, + targetGroup, + fields::flow::faceTemperature::key() ); + + } ); + + GEOS_ERROR_IF( pressureSets != temperatureSets, GEOS_FMT( incompleteBCLogmessage, this->getName(), time_n + dt ) ); + + // Take BCs defined for "temperature" field and apply values to "faceTemperature" + for( auto const & setName : temperatureSets ) { - globalIndex const numTargetFaces = MpiWrapper::sum< globalIndex >( stencil.size() ); - GEOS_LOG_RANK_0( GEOS_FMT( faceBcLogMessage, - this->getName(), time_n+dt, FieldSpecificationBase::catalogName(), - fs.getName(), setName, targetGroup.getName(), numTargetFaces ) ); - } - - if( stencil.size() == 0 ) - { - return; - } - - // first, evaluate BC to get primary field values (pressure) - fs.applyFieldValue< FieldSpecificationEqual, - parallelDevicePolicy<> >( targetSet, - time_n + dt, - targetGroup, - fields::flow::facePressure::key() ); - + BoundaryStencil const & stencil = fluxApprox.getStencil< BoundaryStencil >( mesh, setName ); + BoundaryStencilWrapper const stencilWrapper = stencil.createKernelWrapper(); + + // TODO: currently we just use model from the first cell in this stencil + // since it's not clear how to create fluid kernel wrappers for arbitrary models. + // Can we just use cell properties for an approximate flux computation? + // Then we can forget about capturing the fluid model. + localIndex const er = stencil.getElementRegionIndices()( 0, 0 ); + localIndex const esr = stencil.getElementSubRegionIndices()( 0, 0 ); + ElementSubRegionBase & subRegion = mesh.getElemManager().getRegion( er ).getSubRegion( esr ); + string const & fluidName = subRegion.getReference< string >( BASE::viewKeyStruct::fluidNamesString() ); + SingleFluidBase & fluidBase = subRegion.getConstitutiveModel< SingleFluidBase >( fluidName ); - // TODO: currently we just use model from the first cell in this stencil - // since it's not clear how to create fluid kernel wrappers for arbitrary models. - // Can we just use cell properties for an approximate flux computation? - // Then we can forget about capturing the fluid model. - localIndex const er = stencil.getElementRegionIndices()( 0, 0 ); - localIndex const esr = stencil.getElementSubRegionIndices()( 0, 0 ); - ElementSubRegionBase & subRegion = mesh.getElemManager().getRegion( er ).getSubRegion( esr ); - string const & fluidName = subRegion.getReference< string >( BASE::viewKeyStruct::fluidNamesString() ); - SingleFluidBase & fluidBase = subRegion.getConstitutiveModel< SingleFluidBase >( fluidName ); - - BoundaryStencilWrapper const stencilWrapper = stencil.createKernelWrapper(); - - if( m_isThermal ) - { thermalSinglePhaseFVMKernels:: DirichletFaceBasedAssemblyKernelFactory:: createAndLaunch< parallelDevicePolicy<> >( dofManager.rankOffset(), @@ -677,8 +753,54 @@ void SinglePhaseFVM< BASE >::applyFaceDirichletBC( real64 const time_n, localMatrix, localRhs ); } - else + } + else + { + // Take BCs defined for "pressure" field and apply values to "facePressure" + fsManager.apply< FaceManager >( time_n + dt, + mesh, + fields::flow::pressure::key(), + [&] ( FieldSpecificationBase const & fs, + string const & setName, + SortedArrayView< localIndex const > const & targetSet, + FaceManager & targetGroup, + string const & ) { + BoundaryStencil const & stencil = fluxApprox.getStencil< BoundaryStencil >( mesh, setName ); + + if( fs.getLogLevel() >= 1 && m_nonlinearSolverParameters.m_numNewtonIterations == 0 ) + { + globalIndex const numTargetFaces = MpiWrapper::sum< globalIndex >( stencil.size() ); + GEOS_LOG_RANK_0( GEOS_FMT( faceBcLogMessage, + this->getName(), time_n+dt, FieldSpecificationBase::catalogName(), + fs.getName(), setName, targetGroup.getName(), numTargetFaces ) ); + } + + if( stencil.size() == 0 ) + { + return; + } + + // first, evaluate BC to get primary field values (pressure) + fs.applyFieldValue< FieldSpecificationEqual, + parallelDevicePolicy<> >( targetSet, + time_n + dt, + targetGroup, + fields::flow::facePressure::key() ); + + + // TODO: currently we just use model from the first cell in this stencil + // since it's not clear how to create fluid kernel wrappers for arbitrary models. + // Can we just use cell properties for an approximate flux computation? + // Then we can forget about capturing the fluid model. + localIndex const er = stencil.getElementRegionIndices()( 0, 0 ); + localIndex const esr = stencil.getElementSubRegionIndices()( 0, 0 ); + ElementSubRegionBase & subRegion = mesh.getElemManager().getRegion( er ).getSubRegion( esr ); + string const & fluidName = subRegion.getReference< string >( BASE::viewKeyStruct::fluidNamesString() ); + SingleFluidBase & fluidBase = subRegion.getConstitutiveModel< SingleFluidBase >( fluidName ); + + BoundaryStencilWrapper const stencilWrapper = stencil.createKernelWrapper(); + singlePhaseFVMKernels:: DirichletFaceBasedAssemblyKernelFactory:: createAndLaunch< parallelDevicePolicy<> >( dofManager.rankOffset(), @@ -691,11 +813,10 @@ void SinglePhaseFVM< BASE >::applyFaceDirichletBC( real64 const time_n, dt, localMatrix, localRhs ); - } - } ); - } ); - + } ); + } + } ); } template<> @@ -736,8 +857,8 @@ void SinglePhaseFVM< SinglePhaseBase >::applyAquiferBC( real64 const time, elemManager.constructArrayViewAccessor< globalIndex, 1 >( elemDofKey ); elemDofNumber.setName( this->getName() + "/accessors/" + elemDofKey ); - typename FluxKernel::SinglePhaseFlowAccessors flowAccessors( elemManager, this->getName() ); - typename FluxKernel::SinglePhaseFluidAccessors fluidAccessors( elemManager, this->getName() ); + typename FaceBasedAssemblyKernelBase::SinglePhaseFlowAccessors flowAccessors( elemManager, this->getName() ); + typename FaceBasedAssemblyKernelBase::SinglePhaseFluidAccessors fluidAccessors( elemManager, this->getName() ); fsManager.apply< FaceManager, AquiferBoundaryCondition >( time + dt, diff --git a/src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseFVM.hpp b/src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseFVM.hpp index b1c8fa8cf43..d03d04547e2 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseFVM.hpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseFVM.hpp @@ -160,13 +160,13 @@ class SinglePhaseFVM : public BASE arrayView1d< real64 > const & localRhs ) override; virtual void - assemblePoroelasticFluxTerms( real64 const time_n, - real64 const dt, - DomainPartition const & domain, - DofManager const & dofManager, - CRSMatrixView< real64, globalIndex const > const & localMatrix, - arrayView1d< real64 > const & localRhs, - string const & jumpDofKey ) override final; + assembleEDFMFluxTerms( real64 const time_n, + real64 const dt, + DomainPartition const & domain, + DofManager const & dofManager, + CRSMatrixView< real64, globalIndex const > const & localMatrix, + arrayView1d< real64 > const & localRhs, + string const & jumpDofKey ) override final; virtual void assembleHydrofracFluxTerms( real64 const time_n, diff --git a/src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseFVMKernels.hpp b/src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseFVMKernels.hpp index e68967c0eeb..f13e53176de 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseFVMKernels.hpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseFVMKernels.hpp @@ -33,7 +33,6 @@ #include "finiteVolume/FluxApproximationBase.hpp" #include "linearAlgebra/interfaces/InterfaceTypes.hpp" #include "physicsSolvers/fluidFlow/FlowSolverBaseFields.hpp" -#include "physicsSolvers/fluidFlow/FluxKernelsHelper.hpp" #include "physicsSolvers/fluidFlow/SinglePhaseBaseFields.hpp" #include "physicsSolvers/fluidFlow/SinglePhaseBaseKernels.hpp" #include "physicsSolvers/fluidFlow/StencilAccessors.hpp" @@ -45,8 +44,6 @@ namespace singlePhaseFVMKernels { using namespace constitutive; -using namespace fluxKernelsHelper; - /******************************** FaceBasedAssemblyKernelBase ********************************/ /** @@ -178,7 +175,7 @@ class FaceBasedAssemblyKernelBase * @tparam STENCILWRAPPER the type of the stencil wrapper * @brief Define the interface for the assembly kernel in charge of flux terms */ -template< integer NUM_DOF, typename STENCILWRAPPER > +template< integer NUM_EQN, integer NUM_DOF, typename STENCILWRAPPER > class FaceBasedAssemblyKernel : public FaceBasedAssemblyKernelBase { public: @@ -187,7 +184,7 @@ class FaceBasedAssemblyKernel : public FaceBasedAssemblyKernelBase static constexpr integer numDof = NUM_DOF; /// Compute time value for the number of equations - static constexpr integer numEqn = NUM_DOF; + static constexpr integer numEqn = NUM_EQN; /// Maximum number of elements at the face static constexpr localIndex maxNumElems = STENCILWRAPPER::maxNumPointsInFlux; @@ -602,13 +599,14 @@ class FaceBasedAssemblyKernelFactory CRSMatrixView< real64, globalIndex const > const & localMatrix, arrayView1d< real64 > const & localRhs ) { + integer constexpr NUM_EQN = 1; integer constexpr NUM_DOF = 1; ElementRegionManager::ElementViewAccessor< arrayView1d< globalIndex const > > dofNumberAccessor = elemManager.constructArrayViewAccessor< globalIndex, 1 >( dofKey ); dofNumberAccessor.setName( solverName + "/accessors/" + dofKey ); - using kernelType = FaceBasedAssemblyKernel< NUM_DOF, STENCILWRAPPER >; + using kernelType = FaceBasedAssemblyKernel< NUM_EQN, NUM_DOF, STENCILWRAPPER >; typename kernelType::SinglePhaseFlowAccessors flowAccessors( elemManager, solverName ); typename kernelType::SinglePhaseFluidAccessors fluidAccessors( elemManager, solverName ); typename kernelType::PermeabilityAccessors permAccessors( elemManager, solverName ); @@ -620,233 +618,6 @@ class FaceBasedAssemblyKernelFactory } }; -/******************************** FluxKernel ********************************/ - -// To delete it after verifying FaceBasedAssemblyKernel -struct FluxKernel -{ - /** - * @brief The type for element-based non-constitutive data parameters. - * Consists entirely of ArrayView's. - * - * Can be converted from ElementRegionManager::ElementViewAccessor - * by calling .toView() or .toViewConst() on an accessor instance - */ - template< typename VIEWTYPE > - using ElementViewConst = ElementRegionManager::ElementViewConst< VIEWTYPE >; - - using SinglePhaseFlowAccessors = - StencilAccessors< fields::ghostRank, - fields::flow::pressure, - fields::flow::pressure_n, - fields::flow::gravityCoefficient, - fields::flow::mobility, - fields::flow::dMobility_dPressure >; - - using SinglePhaseFluidAccessors = - StencilMaterialAccessors< SingleFluidBase, - fields::singlefluid::density, - fields::singlefluid::dDensity_dPressure >; - - using SlurryFluidAccessors = - StencilMaterialAccessors< SlurryFluidBase, - fields::singlefluid::density, - fields::singlefluid::dDensity_dPressure >; - - using PermeabilityAccessors = - StencilMaterialAccessors< PermeabilityBase, - fields::permeability::permeability, - fields::permeability::dPerm_dPressure >; - - using ProppantPermeabilityAccessors = - StencilMaterialAccessors< PermeabilityBase, - fields::permeability::permeability, - fields::permeability::dPerm_dPressure, - fields::permeability::dPerm_dDispJump, - fields::permeability::permeabilityMultiplier >; - - - /** - * @brief launches the kernel to assemble the flux contributions to the linear system. - * @tparam STENCIL_TYPE The type of the stencil that is being used. - * @param[in] stencil The stencil object. - * @param[in] dt The timestep for the integration step. - * @param[in] dofNumber The dofNumbers for each element - * @param[in] pres The pressures in each element - * @param[in] gravCoef The factor for gravity calculations (g*H) - * @param[in] dens The material density in each element - * @param[in] dDens_dPres The change in material density for each element - * @param[in] mob The fluid mobility in each element - * @param[in] dMob_dPres The derivative of mobility wrt pressure in each element - * @param[in] permeability - * @param[in] dPerm_dPres The derivative of permeability wrt pressure in each element - * @param[out] localMatrix The linear system matrix - * @param[out] localRhs The linear system residual - */ - template< typename STENCILWRAPPER_TYPE > - static void - launch( STENCILWRAPPER_TYPE const & stencilWrapper, - real64 const dt, - globalIndex const rankOffset, - ElementViewConst< arrayView1d< globalIndex const > > const & dofNumber, - ElementViewConst< arrayView1d< integer const > > const & ghostRank, - ElementViewConst< arrayView1d< real64 const > > const & pres, - ElementViewConst< arrayView1d< real64 const > > const & gravCoef, - ElementViewConst< arrayView2d< real64 const > > const & dens, - ElementViewConst< arrayView2d< real64 const > > const & dDens_dPres, - ElementViewConst< arrayView1d< real64 const > > const & mob, - ElementViewConst< arrayView1d< real64 const > > const & dMob_dPres, - ElementViewConst< arrayView3d< real64 const > > const & permeability, - ElementViewConst< arrayView3d< real64 const > > const & dPerm_dPres, - CRSMatrixView< real64, globalIndex const > const & localMatrix, - arrayView1d< real64 > const & localRhs ) - { - typename STENCILWRAPPER_TYPE::IndexContainerViewConstType const & seri = stencilWrapper.getElementRegionIndices(); - typename STENCILWRAPPER_TYPE::IndexContainerViewConstType const & sesri = stencilWrapper.getElementSubRegionIndices(); - typename STENCILWRAPPER_TYPE::IndexContainerViewConstType const & sei = stencilWrapper.getElementIndices(); - - constexpr localIndex maxNumElems = STENCILWRAPPER_TYPE::maxNumPointsInFlux; - constexpr localIndex maxStencilSize = STENCILWRAPPER_TYPE::maxStencilSize; - - forAll< parallelDevicePolicy<> >( stencilWrapper.size(), [stencilWrapper, dt, rankOffset, dofNumber, ghostRank, - pres, gravCoef, dens, dDens_dPres, mob, - dMob_dPres, permeability, dPerm_dPres, - seri, sesri, sei, localMatrix, localRhs] GEOS_HOST_DEVICE ( localIndex const iconn ) - { - localIndex const stencilSize = stencilWrapper.stencilSize( iconn ); - localIndex const numFluxElems = stencilWrapper.numPointsInFlux( iconn ); - - // working arrays - stackArray1d< globalIndex, maxNumElems > dofColIndices( stencilSize ); - stackArray1d< real64, maxNumElems > localFlux( numFluxElems ); - stackArray2d< real64, maxNumElems * maxStencilSize > localFluxJacobian( numFluxElems, stencilSize ); - - - // compute transmissibility - real64 transmissibility[STENCILWRAPPER_TYPE::maxNumConnections][2]; - real64 dTrans_dPres[STENCILWRAPPER_TYPE::maxNumConnections][2]; - - stencilWrapper.computeWeights( iconn, - permeability, - dPerm_dPres, - transmissibility, - dTrans_dPres ); - - compute( numFluxElems, - seri[iconn], - sesri[iconn], - sei[iconn], - transmissibility, - dTrans_dPres, - pres, - gravCoef, - dens, - dDens_dPres, - mob, - dMob_dPres, - dt, - localFlux, - localFluxJacobian ); - - - // extract DOF numbers - for( localIndex i = 0; i < stencilSize; ++i ) - { - dofColIndices[i] = dofNumber[seri( iconn, i )][sesri( iconn, i )][sei( iconn, i )]; - - } - - for( localIndex i = 0; i < numFluxElems; ++i ) - { - - if( ghostRank[seri( iconn, i )][sesri( iconn, i )][sei( iconn, i )] < 0 ) - { - globalIndex const globalRow = dofNumber[seri( iconn, i )][sesri( iconn, i )][sei( iconn, i )]; - localIndex const localRow = LvArray::integerConversion< localIndex >( globalRow - rankOffset ); - GEOS_ASSERT_GE( localRow, 0 ); - GEOS_ASSERT_GT( localMatrix.numRows(), localRow ); - - RAJA::atomicAdd( parallelDeviceAtomic{}, &localRhs[localRow], localFlux[i] ); - localMatrix.addToRowBinarySearchUnsorted< parallelDeviceAtomic >( localRow, - dofColIndices.data(), - localFluxJacobian[i].dataIfContiguous(), - stencilSize ); - - } - } - - } ); - } - - /** - * @brief Compute flux and its derivatives for a given tpfa connector. - * - * - */ - template< localIndex maxNumConnections > - GEOS_HOST_DEVICE - static void - compute( localIndex const numFluxElems, - arraySlice1d< localIndex const > const & seri, - arraySlice1d< localIndex const > const & sesri, - arraySlice1d< localIndex const > const & sei, - real64 const (&transmissibility)[maxNumConnections][2], - real64 const (&dTrans_dPres)[maxNumConnections][2], - ElementViewConst< arrayView1d< real64 const > > const & pres, - ElementViewConst< arrayView1d< real64 const > > const & gravCoef, - ElementViewConst< arrayView2d< real64 const > > const & dens, - ElementViewConst< arrayView2d< real64 const > > const & dDens_dPres, - ElementViewConst< arrayView1d< real64 const > > const & mob, - ElementViewConst< arrayView1d< real64 const > > const & dMob_dPres, - real64 const dt, - arraySlice1d< real64 > const & flux, - arraySlice2d< real64 > const & fluxJacobian ) - { - - localIndex k[2]; - localIndex connectionIndex = 0;; - for( k[0]=0; k[0] -class DirichletFaceBasedAssemblyKernel : public FaceBasedAssemblyKernel< NUM_DOF, +template< integer NUM_EQN, integer NUM_DOF, typename FLUIDWRAPPER > +class DirichletFaceBasedAssemblyKernel : public FaceBasedAssemblyKernel< NUM_EQN, NUM_DOF, BoundaryStencilWrapper > { public: @@ -882,7 +653,7 @@ class DirichletFaceBasedAssemblyKernel : public FaceBasedAssemblyKernel< NUM_DOF using AbstractBase::m_localMatrix; using AbstractBase::m_localRhs; - using Base = singlePhaseFVMKernels::FaceBasedAssemblyKernel< NUM_DOF, + using Base = singlePhaseFVMKernels::FaceBasedAssemblyKernel< NUM_EQN, NUM_DOF, BoundaryStencilWrapper >; using Base::numDof; using Base::numEqn; @@ -1138,7 +909,8 @@ class DirichletFaceBasedAssemblyKernelFactory typename FluidType::KernelWrapper fluidWrapper = fluid.createKernelWrapper(); integer constexpr NUM_DOF = 1; - using kernelType = DirichletFaceBasedAssemblyKernel< NUM_DOF, typename FluidType::KernelWrapper >; + integer constexpr NUM_EQN = 1; + using kernelType = DirichletFaceBasedAssemblyKernel< NUM_EQN, NUM_DOF, typename FluidType::KernelWrapper >; ElementRegionManager::ElementViewAccessor< arrayView1d< globalIndex const > > dofNumberAccessor = elemManager.constructArrayViewAccessor< globalIndex, 1 >( dofKey ); diff --git a/src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseHybridFVM.cpp b/src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseHybridFVM.cpp index fea0d3b945a..557a784f50a 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseHybridFVM.cpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseHybridFVM.cpp @@ -261,13 +261,13 @@ void SinglePhaseHybridFVM::assembleFluxTerms( real64 const GEOS_UNUSED_PARAM( ti } -void SinglePhaseHybridFVM::assemblePoroelasticFluxTerms( real64 const time_n, - real64 const dt, - DomainPartition const & domain, - DofManager const & dofManager, - CRSMatrixView< real64, globalIndex const > const & localMatrix, - arrayView1d< real64 > const & localRhs, - string const & jumpDofKey ) +void SinglePhaseHybridFVM::assembleEDFMFluxTerms( real64 const time_n, + real64 const dt, + DomainPartition const & domain, + DofManager const & dofManager, + CRSMatrixView< real64, globalIndex const > const & localMatrix, + arrayView1d< real64 > const & localRhs, + string const & jumpDofKey ) { GEOS_UNUSED_VAR ( jumpDofKey ); diff --git a/src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseHybridFVM.hpp b/src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseHybridFVM.hpp index a9f7f4ca6ee..f0e27f21016 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseHybridFVM.hpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseHybridFVM.hpp @@ -123,13 +123,13 @@ class SinglePhaseHybridFVM : public SinglePhaseBase arrayView1d< real64 > const & localRhs ) override; virtual void - assemblePoroelasticFluxTerms( real64 const time_n, - real64 const dt, - DomainPartition const & domain, - DofManager const & dofManager, - CRSMatrixView< real64, globalIndex const > const & localMatrix, - arrayView1d< real64 > const & localRhs, - string const & jumpDofKey ) override final; + assembleEDFMFluxTerms( real64 const time_n, + real64 const dt, + DomainPartition const & domain, + DofManager const & dofManager, + CRSMatrixView< real64, globalIndex const > const & localMatrix, + arrayView1d< real64 > const & localRhs, + string const & jumpDofKey ) override final; virtual void assembleHydrofracFluxTerms( real64 const time_n, diff --git a/src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseProppantFluxKernels.cpp b/src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseProppantFluxKernels.cpp new file mode 100644 index 00000000000..119f5cb3060 --- /dev/null +++ b/src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseProppantFluxKernels.cpp @@ -0,0 +1,225 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2018-2020 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2020 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2018-2020 TotalEnergies + * Copyright (c) 2019- GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +/** + * @file singlePhaseProppantFluxKernels.cpp + */ + +#include "physicsSolvers/fluidFlow/SinglePhaseFVMKernels.hpp" +#include "physicsSolvers/fluidFlow/FluxKernelsHelper.hpp" +#include "SinglePhaseProppantFluxKernels.hpp" + +namespace geos +{ + +namespace singlePhaseProppantFluxKernels +{ + +using namespace fluxKernelsHelper; + + +void FaceElementFluxKernel:: + launch( SurfaceElementStencilWrapper const & stencilWrapper, + real64 const dt, + globalIndex const rankOffset, + ElementViewConst< arrayView1d< globalIndex const > > const & pressureDofNumber, + ElementViewConst< arrayView1d< integer const > > const & ghostRank, + ElementViewConst< arrayView1d< real64 const > > const & pres, + ElementViewConst< arrayView1d< real64 const > > const & gravCoef, + ElementViewConst< arrayView2d< real64 const > > const & dens, + ElementViewConst< arrayView2d< real64 const > > const & dDens_dPres, + ElementViewConst< arrayView1d< real64 const > > const & mob, + ElementViewConst< arrayView1d< real64 const > > const & dMob_dPres, + ElementViewConst< arrayView3d< real64 const > > const & permeability, + ElementViewConst< arrayView3d< real64 const > > const & dPerm_dPres, + ElementViewConst< arrayView4d< real64 const > > const & dPerm_dDispJump, + ElementViewConst< arrayView3d< real64 const > > const & permeabilityMultiplier, + R1Tensor const & gravityVector, + CRSMatrixView< real64, globalIndex const > const & localMatrix, + arrayView1d< real64 > const & localRhs ) +{ + constexpr localIndex maxNumFluxElems = SurfaceElementStencilWrapper::maxNumPointsInFlux; + constexpr localIndex maxStencilSize = SurfaceElementStencilWrapper::maxStencilSize; + constexpr localIndex maxNumConnections = SurfaceElementStencilWrapper::maxNumConnections; + + typename SurfaceElementStencilWrapper::IndexContainerViewConstType const & seri = stencilWrapper.getElementRegionIndices(); + typename SurfaceElementStencilWrapper::IndexContainerViewConstType const & sesri = stencilWrapper.getElementSubRegionIndices(); + typename SurfaceElementStencilWrapper::IndexContainerViewConstType const & sei = stencilWrapper.getElementIndices(); + + forAll< parallelDevicePolicy<> >( stencilWrapper.size(), [=] GEOS_HOST_DEVICE ( localIndex const iconn ) + { + localIndex const stencilSize = stencilWrapper.stencilSize( iconn ); + localIndex const numFluxElems = stencilWrapper.numPointsInFlux( iconn ); + localIndex const numDofs = stencilSize; // pressures + + // For now, we have to filter out connections for which numElems == 1 in this function and not early on in + // TwoPointFluxApproximation.cpp. + // The reason for keeping the connections numElems == 1 is that the ProppantTransport solver needs these connections to produce correct + // results. + if( numFluxElems > 1 ) + { + + // working arrays + stackArray1d< globalIndex, maxNumFluxElems > dofColIndices( numDofs ); + stackArray1d< localIndex, maxNumFluxElems > localColIndices( numFluxElems ); + + stackArray1d< real64, maxNumFluxElems > localFlux( numFluxElems ); + stackArray2d< real64, maxNumFluxElems * maxStencilSize > localFluxJacobian( numFluxElems, numDofs ); + + // need to store this for later use in determining the dFlux_dU terms when using better permeabilty approximations. + stackArray2d< real64, maxNumFluxElems * maxStencilSize > dFlux_dAper( numFluxElems, stencilSize ); + + // compute transmissibility + real64 transmissibility[maxNumConnections][2]{}; + real64 dTrans_dPres[maxNumConnections][2]{}; + real64 dTrans_dDispJump[maxNumConnections][2][3]{}; + + GEOS_UNUSED_VAR( dPerm_dPres, dPerm_dDispJump ); + stencilWrapper.computeWeights( iconn, + permeability, + permeabilityMultiplier, + gravityVector, + transmissibility ); + + compute( stencilSize, + seri[iconn], + sesri[iconn], + sei[iconn], + transmissibility, + dTrans_dPres, + dTrans_dDispJump, + pres, + gravCoef, + dens, + dDens_dPres, + mob, + dMob_dPres, + dt, + localFlux, + localFluxJacobian, + dFlux_dAper ); + + // extract DOF numbers + for( localIndex i = 0; i < numDofs; ++i ) + { + dofColIndices[i] = pressureDofNumber[seri( iconn, i )][sesri( iconn, i )][sei( iconn, i )]; + localColIndices[i] = sei( iconn, i ); + } + + for( localIndex i = 0; i < numFluxElems; ++i ) + { + if( ghostRank[seri( iconn, i )][sesri( iconn, i )][sei( iconn, i )] < 0 ) + { + globalIndex const globalRow = pressureDofNumber[seri( iconn, i )][sesri( iconn, i )][sei( iconn, i )]; + localIndex const localRow = LvArray::integerConversion< localIndex >( globalRow - rankOffset ); + GEOS_ASSERT_GE( localRow, 0 ); + GEOS_ASSERT_GT( localMatrix.numRows(), localRow ); + + RAJA::atomicAdd( parallelDeviceAtomic{}, &localRhs[localRow], localFlux[i] ); + localMatrix.addToRowBinarySearchUnsorted< parallelDeviceAtomic >( localRow, + dofColIndices.data(), + localFluxJacobian[i].dataIfContiguous(), + stencilSize ); + + } + } + } + } ); + +} + + +template< localIndex MAX_NUM_CONNECTIONS > +GEOS_HOST_DEVICE +void +FaceElementFluxKernel::compute( localIndex const numFluxElems, + arraySlice1d< localIndex const > const & seri, + arraySlice1d< localIndex const > const & sesri, + arraySlice1d< localIndex const > const & sei, + real64 const (&transmissibility)[MAX_NUM_CONNECTIONS][2], + real64 const (&dTrans_dPres)[MAX_NUM_CONNECTIONS][2], + real64 const (&dTrans_dDispJump)[MAX_NUM_CONNECTIONS][2][3], + ElementViewConst< arrayView1d< real64 const > > const & pres, + ElementViewConst< arrayView1d< real64 const > > const & gravCoef, + ElementViewConst< arrayView2d< real64 const > > const & dens, + ElementViewConst< arrayView2d< real64 const > > const & dDens_dPres, + ElementViewConst< arrayView1d< real64 const > > const & mob, + ElementViewConst< arrayView1d< real64 const > > const & dMob_dPres, + real64 const dt, + arraySlice1d< real64 > const & flux, + arraySlice2d< real64 > const & fluxJacobian, + arraySlice2d< real64 > const & dFlux_dAperture ) +{ + + localIndex k[2]; + localIndex connectionIndex = 0; + for( k[0]=0; k[0] + using ElementViewConst = ElementRegionManager::ElementViewConst< VIEWTYPE >; + + + /** + * @brief launches the kernel to assemble the flux contributions to the linear system. + * @tparam SurfaceElementStencilWrapper The type of the stencil that is being used. + * @param[in] stencil The stencil object. + * @param[in] dt The timestep for the integration step. + * @param[in] dofNumber The dofNumbers for each element + * @param[in] pres The pressures in each element + * @param[in] gravCoef The factor for gravity calculations (g*H) + * @param[in] dens The material density in each element + * @param[in] dDens_dPres The change in material density for each element + * @param[in] mob The fluid mobility in each element + * @param[in] dMob_dPres The derivative of mobility wrt pressure in each element + * @param[in] permeability + * @param[in] dPerm_dPres The derivative of permeability wrt pressure in each element + * @param[in] permeabilityMultiplier + * @param[in] gravityVector + * @param[out] localMatrix The linear system matrix + * @param[out] localRhs The linear system residual + */ + static void + launch( SurfaceElementStencilWrapper const & stencilWrapper, + real64 const dt, + globalIndex const rankOffset, + ElementViewConst< arrayView1d< globalIndex const > > const & pressureDofNumber, + ElementViewConst< arrayView1d< integer const > > const & ghostRank, + ElementViewConst< arrayView1d< real64 const > > const & pres, + ElementViewConst< arrayView1d< real64 const > > const & gravCoef, + ElementViewConst< arrayView2d< real64 const > > const & dens, + ElementViewConst< arrayView2d< real64 const > > const & dDens_dPres, + ElementViewConst< arrayView1d< real64 const > > const & mob, + ElementViewConst< arrayView1d< real64 const > > const & dMob_dPres, + ElementViewConst< arrayView3d< real64 const > > const & permeability, + ElementViewConst< arrayView3d< real64 const > > const & dPerm_dPres, + ElementViewConst< arrayView4d< real64 const > > const & dPerm_dDispJump, + ElementViewConst< arrayView3d< real64 const > > const & permeabilityMultiplier, + R1Tensor const & gravityVector, + CRSMatrixView< real64, globalIndex const > const & localMatrix, + arrayView1d< real64 > const & localRhs ); + + + /** + * @brief Compute flux and its derivatives for a given tpfa connector. + * + * + */ + template< localIndex MAX_NUM_CONNECTIONS > + GEOS_HOST_DEVICE + static void + compute( localIndex const numFluxElems, + arraySlice1d< localIndex const > const & seri, + arraySlice1d< localIndex const > const & sesri, + arraySlice1d< localIndex const > const & sei, + real64 const (&transmissibility)[MAX_NUM_CONNECTIONS][2], + real64 const (&dTrans_dPres)[MAX_NUM_CONNECTIONS][2], + real64 const (&dTrans_dDispJump)[MAX_NUM_CONNECTIONS][2][3], + ElementViewConst< arrayView1d< real64 const > > const & pres, + ElementViewConst< arrayView1d< real64 const > > const & gravCoef, + ElementViewConst< arrayView2d< real64 const > > const & dens, + ElementViewConst< arrayView2d< real64 const > > const & dDens_dPres, + ElementViewConst< arrayView1d< real64 const > > const & mob, + ElementViewConst< arrayView1d< real64 const > > const & dMob_dPres, + real64 const dt, + arraySlice1d< real64 > const & flux, + arraySlice2d< real64 > const & fluxJacobian, + arraySlice2d< real64 > const & dFlux_dAperture ); +}; + + +} // namespace singlePhaseProppantFluxKernels + +} // namespace geos + +#endif //GEOSX_PHYSICSSOLVERS_MULTIPHYSICS_SINGLEPHASEPROPPANTFLUXKERNELS_HPP diff --git a/src/coreComponents/physicsSolvers/fluidFlow/StabilizedCompositionalMultiphaseFVMKernels.hpp b/src/coreComponents/physicsSolvers/fluidFlow/StabilizedCompositionalMultiphaseFVMKernels.hpp index 12ae5b50e8a..aed6b6f1780 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/StabilizedCompositionalMultiphaseFVMKernels.hpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/StabilizedCompositionalMultiphaseFVMKernels.hpp @@ -137,6 +137,7 @@ class FaceBasedAssemblyKernel : public isothermalCompositionalMultiphaseFVMKerne : Base( numPhases, rankOffset, hasCapPressure, + //0.0, // no C1-PPU stencilWrapper, dofNumberAccessor, compFlowAccessors, @@ -337,10 +338,10 @@ class FaceBasedAssemblyKernelFactory arrayView1d< real64 > const & localRhs ) { isothermalCompositionalMultiphaseBaseKernels:: - internal::kernelLaunchSelectorCompSwitch( numComps, [&] ( auto NC ) + internal::kernelLaunchSelectorCompSwitch( numComps, [&]( auto NC ) { integer constexpr NUM_COMP = NC(); - integer constexpr NUM_DOF = NC()+1; + integer constexpr NUM_DOF = NC() + 1; ElementRegionManager::ElementViewAccessor< arrayView1d< globalIndex const > > dofNumberAccessor = elemManager.constructArrayViewAccessor< globalIndex, 1 >( dofKey ); diff --git a/src/coreComponents/physicsSolvers/fluidFlow/ThermalCompositionalMultiphaseFVMKernels.hpp b/src/coreComponents/physicsSolvers/fluidFlow/ThermalCompositionalMultiphaseFVMKernels.hpp index 6edd4287139..ec4a3b5bb79 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/ThermalCompositionalMultiphaseFVMKernels.hpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/ThermalCompositionalMultiphaseFVMKernels.hpp @@ -258,6 +258,7 @@ class FaceBasedAssemblyKernel : public isothermalCompositionalMultiphaseFVMKerne : Base( numPhases, rankOffset, hasCapPressure, + //0.0, // no C1-PPU stencilWrapper, dofNumberAccessor, compFlowAccessors, @@ -637,10 +638,10 @@ class FaceBasedAssemblyKernelFactory arrayView1d< real64 > const & localRhs ) { isothermalCompositionalMultiphaseBaseKernels:: - internal::kernelLaunchSelectorCompSwitch( numComps, [&] ( auto NC ) + internal::kernelLaunchSelectorCompSwitch( numComps, [&]( auto NC ) { integer constexpr NUM_COMP = NC(); - integer constexpr NUM_DOF = NC()+2; + integer constexpr NUM_DOF = NC() + 2; ElementRegionManager::ElementViewAccessor< arrayView1d< globalIndex const > > dofNumberAccessor = elemManager.constructArrayViewAccessor< globalIndex, 1 >( dofKey ); @@ -1081,10 +1082,10 @@ class DirichletFaceBasedAssemblyKernelFactory typename FluidType::KernelWrapper const fluidWrapper = fluid.createKernelWrapper(); isothermalCompositionalMultiphaseBaseKernels:: - internal::kernelLaunchSelectorCompSwitch( numComps, [&] ( auto NC ) + internal::kernelLaunchSelectorCompSwitch( numComps, [&]( auto NC ) { integer constexpr NUM_COMP = NC(); - integer constexpr NUM_DOF = NC()+2; + integer constexpr NUM_DOF = NC() + 2; ElementRegionManager::ElementViewAccessor< arrayView1d< globalIndex const > > dofNumberAccessor = elemManager.constructArrayViewAccessor< globalIndex, 1 >( dofKey ); diff --git a/src/coreComponents/physicsSolvers/fluidFlow/ThermalSinglePhaseFVMKernels.hpp b/src/coreComponents/physicsSolvers/fluidFlow/ThermalSinglePhaseFVMKernels.hpp index 9177b1964c0..4f994baa255 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/ThermalSinglePhaseFVMKernels.hpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/ThermalSinglePhaseFVMKernels.hpp @@ -39,8 +39,8 @@ using namespace constitutive; * @tparam STENCILWRAPPER the type of the stencil wrapper * @brief Define the interface for the assembly kernel in charge of flux terms */ -template< integer NUM_DOF, typename STENCILWRAPPER > -class FaceBasedAssemblyKernel : public singlePhaseFVMKernels::FaceBasedAssemblyKernel< NUM_DOF, STENCILWRAPPER > +template< integer NUM_EQN, integer NUM_DOF, typename STENCILWRAPPER > +class FaceBasedAssemblyKernel : public singlePhaseFVMKernels::FaceBasedAssemblyKernel< NUM_EQN, NUM_DOF, STENCILWRAPPER > { public: @@ -66,7 +66,7 @@ class FaceBasedAssemblyKernel : public singlePhaseFVMKernels::FaceBasedAssemblyK using AbstractBase::m_mob; using AbstractBase::m_dens; - using Base = singlePhaseFVMKernels::FaceBasedAssemblyKernel< NUM_DOF, STENCILWRAPPER >; + using Base = singlePhaseFVMKernels::FaceBasedAssemblyKernel< NUM_EQN, NUM_DOF, STENCILWRAPPER >; using Base::numDof; using Base::numEqn; using Base::maxNumElems; @@ -455,12 +455,13 @@ class FaceBasedAssemblyKernelFactory arrayView1d< real64 > const & localRhs ) { integer constexpr NUM_DOF = 2; + integer constexpr NUM_EQN = 2; ElementRegionManager::ElementViewAccessor< arrayView1d< globalIndex const > > dofNumberAccessor = elemManager.constructArrayViewAccessor< globalIndex, 1 >( dofKey ); dofNumberAccessor.setName( solverName + "/accessors/" + dofKey ); - using KernelType = FaceBasedAssemblyKernel< NUM_DOF, STENCILWRAPPER >; + using KernelType = FaceBasedAssemblyKernel< NUM_EQN, NUM_DOF, STENCILWRAPPER >; typename KernelType::SinglePhaseFlowAccessors flowAccessors( elemManager, solverName ); typename KernelType::ThermalSinglePhaseFlowAccessors thermalFlowAccessors( elemManager, solverName ); typename KernelType::SinglePhaseFluidAccessors fluidAccessors( elemManager, solverName ); @@ -483,8 +484,8 @@ class FaceBasedAssemblyKernelFactory * @tparam FLUIDWRAPPER the type of the fluid wrapper * @brief Define the interface for the assembly kernel in charge of Dirichlet face flux terms */ -template< integer NUM_DOF, typename FLUIDWRAPPER > -class DirichletFaceBasedAssemblyKernel : public singlePhaseFVMKernels::DirichletFaceBasedAssemblyKernel< NUM_DOF, FLUIDWRAPPER > +template< integer NUM_EQN, integer NUM_DOF, typename FLUIDWRAPPER > +class DirichletFaceBasedAssemblyKernel : public singlePhaseFVMKernels::DirichletFaceBasedAssemblyKernel< NUM_EQN, NUM_DOF, FLUIDWRAPPER > { public: @@ -516,7 +517,7 @@ class DirichletFaceBasedAssemblyKernel : public singlePhaseFVMKernels::Dirichlet using AbstractBase::m_localMatrix; using AbstractBase::m_localRhs; - using Base = singlePhaseFVMKernels::DirichletFaceBasedAssemblyKernel< NUM_DOF, FLUIDWRAPPER >; + using Base = singlePhaseFVMKernels::DirichletFaceBasedAssemblyKernel< NUM_EQN, NUM_DOF, FLUIDWRAPPER >; using Base::numDof; using Base::numEqn; using Base::m_stencilWrapper; @@ -787,8 +788,9 @@ class DirichletFaceBasedAssemblyKernelFactory typename FluidType::KernelWrapper fluidWrapper = fluid.createKernelWrapper(); integer constexpr NUM_DOF = 2; + integer constexpr NUM_EQN = 2; - using kernelType = DirichletFaceBasedAssemblyKernel< NUM_DOF, typename FluidType::KernelWrapper >; + using kernelType = DirichletFaceBasedAssemblyKernel< NUM_EQN, NUM_DOF, typename FluidType::KernelWrapper >; ElementRegionManager::ElementViewAccessor< arrayView1d< globalIndex const > > dofNumberAccessor = elemManager.constructArrayViewAccessor< globalIndex, 1 >( dofKey ); diff --git a/src/coreComponents/physicsSolvers/fluidFlow/wells/CompositionalMultiphaseWell.cpp b/src/coreComponents/physicsSolvers/fluidFlow/wells/CompositionalMultiphaseWell.cpp index d309d3c9572..8325f84964a 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/wells/CompositionalMultiphaseWell.cpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/wells/CompositionalMultiphaseWell.cpp @@ -329,13 +329,25 @@ void CompositionalMultiphaseWell::validateInjectionStreams( WellElementSubRegion WellControls const & wellControls = getWellControls( subRegion ); // check well injection stream for injectors - if( wellControls.isInjector() ) + if( wellControls.isInjector()) { - arrayView1d< real64 const > const & injection = wellControls.getInjectionStream(); + arrayView1d< real64 const > const & injectionStream = wellControls.getInjectionStream(); + + integer const streamSize = injectionStream.size(); + GEOS_THROW_IF( ( streamSize == 0 ), + "WellControls '" << wellControls.getName() << "'" << + ": Injection stream not specified for well " << subRegion.getName(), + InputError ); + GEOS_THROW_IF( ( streamSize != m_numComponents ), + "WellControls '" << wellControls.getName() << "'" << + ": Injection stream for well " << subRegion.getName() << " should have " << + m_numComponents << " components.", + InputError ); + real64 compFracSum = 0; for( integer ic = 0; ic < m_numComponents; ++ic ) { - real64 const compFrac = injection[ic]; + real64 const compFrac = injectionStream[ic]; GEOS_THROW_IF( ( compFrac < 0.0 ) || ( compFrac > 1.0 ), "WellControls '" << wellControls.getName() << "'" << ": Invalid injection stream for well " << subRegion.getName(), @@ -371,7 +383,7 @@ void CompositionalMultiphaseWell::validateWellConstraints( real64 const & time_n InputError ); GEOS_THROW_IF( wellControls.isProducer() && currentControl == WellControls::Control::TOTALVOLRATE, "WellControls named " << wellControls.getName() << - ": Phase rate control is not available for producers", + ": Total rate control is not available for producers", InputError ); GEOS_THROW_IF( wellControls.isInjector() && targetTotalRate < 0.0, diff --git a/src/coreComponents/physicsSolvers/fluidFlow/wells/WellControls.cpp b/src/coreComponents/physicsSolvers/fluidFlow/wells/WellControls.cpp index 739b311bf6e..388a05e2301 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/wells/WellControls.cpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/wells/WellControls.cpp @@ -308,11 +308,6 @@ void WellControls::postProcessInput() } // 6.2) Check incoherent information - // A producer must be controlled by PhaseVolRate - GEOS_THROW_IF( (isProducer() && (m_inputControl == Control::TOTALVOLRATE)), - "WellControls '" << getName() << "': You have to control a producer with " - << EnumStrings< Control >::toString( Control::PHASEVOLRATE ), - InputError ); // An injector must be controlled by TotalVolRate GEOS_THROW_IF( (isInjector() && (m_inputControl == Control::PHASEVOLRATE)), diff --git a/src/coreComponents/physicsSolvers/multiphysics/CompositionalMultiphaseReservoirAndWells.cpp b/src/coreComponents/physicsSolvers/multiphysics/CompositionalMultiphaseReservoirAndWells.cpp index 82926535019..95fd9905148 100644 --- a/src/coreComponents/physicsSolvers/multiphysics/CompositionalMultiphaseReservoirAndWells.cpp +++ b/src/coreComponents/physicsSolvers/multiphysics/CompositionalMultiphaseReservoirAndWells.cpp @@ -279,6 +279,11 @@ assembleCouplingTerms( real64 const time_n, using ROFFSET = compositionalMultiphaseWellKernels::RowOffset; using COFFSET = compositionalMultiphaseWellKernels::ColOffset; + GEOS_THROW_IF( !Base::m_isWellTransmissibilityComputed, + GEOS_FMT( "{} `{}`: The well transmissibility has not been computed yet", + catalogName(), this->getName() ), + std::runtime_error ); + this->template forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&] ( string const &, MeshLevel const & mesh, arrayView1d< string const > const & regionNames ) diff --git a/src/coreComponents/physicsSolvers/multiphysics/CoupledReservoirAndWellsBase.hpp b/src/coreComponents/physicsSolvers/multiphysics/CoupledReservoirAndWellsBase.hpp index c1d71be292f..c1d2faf608c 100644 --- a/src/coreComponents/physicsSolvers/multiphysics/CoupledReservoirAndWellsBase.hpp +++ b/src/coreComponents/physicsSolvers/multiphysics/CoupledReservoirAndWellsBase.hpp @@ -94,7 +94,8 @@ class CoupledReservoirAndWellsBase : public CoupledSolver< RESERVOIR_SOLVER, WEL */ CoupledReservoirAndWellsBase ( const string & name, dataRepository::Group * const parent ) - : Base( name, parent ) + : Base( name, parent ), + m_isWellTransmissibilityComputed( false ) { this->template getWrapper< string >( Base::viewKeyStruct::discretizationString() ). setInputFlag( dataRepository::InputFlags::FALSE ); @@ -191,12 +192,87 @@ class CoupledReservoirAndWellsBase : public CoupledSolver< RESERVOIR_SOLVER, WEL DomainPartition & domain = this->template getGroupByPath< DomainPartition >( "/Problem/domain" ); - // Validate well perforations: Ensure that each perforation is in a region targetted by the solver + // Validate well perforations: Ensure that each perforation is in a region targeted by the solver if( !validateWellPerforations( domain )) { return; } + } + + virtual void + implicitStepSetup( real64 const & time_n, + real64 const & dt, + DomainPartition & domain ) override + { + Base::implicitStepSetup( time_n, dt, domain ); + + // we delay the computation of the transmissibility until the last minute + // because we want to make sure that the permeability has been updated (in the flow solver) + // this is necessary for some permeability models (like Karman-Kozeny) that do not use the imported permeability + // ultimately, we may want to use this mechanism to update the well transmissibility at each time step (if needed) + if( !m_isWellTransmissibilityComputed ) + { + computeWellTransmissibility( domain ); + m_isWellTransmissibilityComputed = true; + } + } + +protected: + /** + * @Brief loop over perforations and increase Jacobian matrix row lengths for reservoir and well elements accordingly + * @param domain the physical domain object + * @param dofManager degree-of-freedom manager associated with the linear system + * @param rowLengths the row-by-row length + */ + void + addCouplingNumNonzeros( DomainPartition & domain, + DofManager & dofManager, + arrayView1d< localIndex > const & rowLengths ) const + { + coupledReservoirAndWellsInternal:: + addCouplingNumNonzeros( this, + domain, + dofManager, + rowLengths, + wellSolver()->numDofPerResElement(), + wellSolver()->numDofPerWellElement(), + wellSolver()->resElementDofName(), + wellSolver()->wellElementDofName() ); + } + + /** + * @Brief add the sparsity pattern induced by the perforations + * @param domain the physical domain object + * @param dofManager degree-of-freedom manager associated with the linear system + * @param pattern the sparsity pattern + */ + virtual void + addCouplingSparsityPattern( DomainPartition const & domain, + DofManager const & dofManager, + SparsityPatternView< globalIndex > const & pattern ) const = 0; + + /// Flag to determine whether the well transmissibility needs to be computed + bool m_isWellTransmissibilityComputed; + +private: + + /** + * @brief Validate the well perforations ensuring that each perforation is located in a reservoir region that is also + * targeted by the solver + * @param domain the physical domain object + */ + bool validateWellPerforations( DomainPartition const & domain ) const + { + return coupledReservoirAndWellsInternal::validateWellPerforations( reservoirSolver(), wellSolver(), domain ); + } + + /** + * @brief Compute the transmissibility at all the perforations + * @param domain the physical domain object + */ + void computeWellTransmissibility( DomainPartition & domain ) const + { this->template forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&] ( string const &, MeshLevel & meshLevel, arrayView1d< string const > const & regionNames ) @@ -236,8 +312,15 @@ class CoupledReservoirAndWellsBase : public CoupledSolver< RESERVOIR_SOLVER, WEL arrayView1d< localIndex const > const resElemIndex = perforationData.getField< fields::perforation::reservoirElementIndex >(); - forAll< serialPolicy >( perforationData.size(), [=] GEOS_HOST_DEVICE ( localIndex const iperf ) + GEOS_UNUSED_VAR( perfLocation ); // unused if geos_error_if is nulld + GEOS_UNUSED_VAR( perfTrans ); // unused if geos_error_if is nulld + GEOS_UNUSED_VAR( resElemRegion ); // unused if geos_error_if is nulld + GEOS_UNUSED_VAR( resElemSubRegion ); // unused if geos_error_if is nulld + GEOS_UNUSED_VAR( resElemIndex ); // unused if geos_error_if is nulld + + forAll< serialPolicy >( perforationData.size(), [=] ( localIndex const iperf ) { + GEOS_UNUSED_VAR( iperf ); // unused if geos_error_if is nulld GEOS_LOG_RANK( GEOS_FMT( "Perforation at ({},{},{}); perforated element center: ({},{},{}); transmissibility: {} Pa.s.rm^3/s/Pa", perfLocation[iperf][0], perfLocation[iperf][1], perfLocation[iperf][2], elemCenter[resElemRegion[iperf]][resElemSubRegion[iperf]][resElemIndex[iperf]][0], @@ -250,52 +333,6 @@ class CoupledReservoirAndWellsBase : public CoupledSolver< RESERVOIR_SOLVER, WEL } ); } -protected: - - /** - * @Brief loop over perforations and increase Jacobian matrix row lengths for reservoir and well elements accordingly - * @param domain the physical domain object - * @param dofManager degree-of-freedom manager associated with the linear system - * @param rowLengths the row-by-row length - */ - void - addCouplingNumNonzeros( DomainPartition & domain, - DofManager & dofManager, - arrayView1d< localIndex > const & rowLengths ) const - { - coupledReservoirAndWellsInternal:: - addCouplingNumNonzeros( this, - domain, - dofManager, - rowLengths, - wellSolver()->numDofPerResElement(), - wellSolver()->numDofPerWellElement(), - wellSolver()->resElementDofName(), - wellSolver()->wellElementDofName() ); - } - - /** - * @Brief add the sparsity pattern induced by the perforations - * @param domain the physical domain object - * @param dofManager degree-of-freedom manager associated with the linear system - * @param pattern the sparsity pattern - */ - virtual void - addCouplingSparsityPattern( DomainPartition const & domain, - DofManager const & dofManager, - SparsityPatternView< globalIndex > const & pattern ) const = 0; - -private: - /** - * @brief Validate the well perforations ensuring that each perforation is located in a reservoir region that is also - * targetted by the solver - * @param domain the physical domain object - */ - bool validateWellPerforations( DomainPartition const & domain ) const - { - return coupledReservoirAndWellsInternal::validateWellPerforations( reservoirSolver(), wellSolver(), domain ); - } - }; diff --git a/src/coreComponents/physicsSolvers/multiphysics/HydrofractureSolver.cpp b/src/coreComponents/physicsSolvers/multiphysics/HydrofractureSolver.cpp index 39ef89cbc85..d3f7edaf7ca 100644 --- a/src/coreComponents/physicsSolvers/multiphysics/HydrofractureSolver.cpp +++ b/src/coreComponents/physicsSolvers/multiphysics/HydrofractureSolver.cpp @@ -23,6 +23,9 @@ #include "constitutive/fluid/singlefluid/SingleFluidBase.hpp" #include "physicsSolvers/multiphysics/HydrofractureSolverKernels.hpp" #include "physicsSolvers/solidMechanics/SolidMechanicsFields.hpp" +#include "physicsSolvers/multiphysics/SinglePhasePoromechanics.hpp" +#include "physicsSolvers/multiphysics/MultiphasePoromechanics.hpp" + namespace geos { @@ -31,13 +34,48 @@ using namespace constitutive; using namespace dataRepository; using namespace fields; -HydrofractureSolver::HydrofractureSolver( const string & name, - Group * const parent ) + +namespace +{ + +// This is meant to be specialized to work, see below +template< typename POROMECHANICS_SOLVER > class + HydrofractureSolverCatalogNames {}; + +// Class specialization for a POROMECHANICS_SOLVER set to SinglePhasePoromechanics +template<> class HydrofractureSolverCatalogNames< SinglePhasePoromechanics > +{ +public: + static string name() { return "Hydrofracture"; } +}; + +// Class specialization for a POROMECHANICS_SOLVER set to MultiphasePoromechanics +template<> class HydrofractureSolverCatalogNames< MultiphasePoromechanics > +{ +public: + static string name() { return "MultiphaseHydrofracture"; } +}; +} + +// provide a definition for catalogName() +template< typename POROMECHANICS_SOLVER > +string +HydrofractureSolver< POROMECHANICS_SOLVER >:: +catalogName() +{ + return HydrofractureSolverCatalogNames< POROMECHANICS_SOLVER >::name(); +} + + +template< typename POROMECHANICS_SOLVER > +HydrofractureSolver< POROMECHANICS_SOLVER >::HydrofractureSolver( const string & name, + Group * const parent ) : Base( name, parent ), m_contactRelationName(), m_surfaceGeneratorName(), m_surfaceGenerator( nullptr ), - m_maxNumResolves( 10 ) + m_maxNumResolves( 10 ), + m_isMatrixPoroelastic() { registerWrapper( viewKeyStruct::surfaceGeneratorNameString(), &m_surfaceGeneratorName ). setInputFlag( InputFlags::REQUIRED ). @@ -52,36 +90,23 @@ HydrofractureSolver::HydrofractureSolver( const string & name, setInputFlag( InputFlags::OPTIONAL ). setDescription( "Value to indicate how many resolves may be executed to perform surface generation after the execution of flow and mechanics solver. " ); + registerWrapper( viewKeyStruct::isMatrixPoroelasticString(), &m_isMatrixPoroelastic ). + setApplyDefaultValue( 0 ). + setInputFlag( InputFlags::OPTIONAL ); + m_numResolves[0] = 0; + // This may need to be different depending on whether poroelasticity is on or not. m_linearSolverParameters.get().mgr.strategy = LinearSolverParameters::MGR::StrategyType::hydrofracture; m_linearSolverParameters.get().mgr.separateComponents = false; m_linearSolverParameters.get().mgr.displacementFieldName = solidMechanics::totalDisplacement::key(); m_linearSolverParameters.get().dofsPerNode = 3; } - -void HydrofractureSolver::registerDataOnMesh( dataRepository::Group & meshBodies ) +template< typename POROMECHANICS_SOLVER > +void HydrofractureSolver< POROMECHANICS_SOLVER >::registerDataOnMesh( dataRepository::Group & meshBodies ) { - CoupledSolver::registerDataOnMesh( meshBodies ); - - forDiscretizationOnMeshTargets( meshBodies, [&] ( string const &, - MeshLevel & mesh, - arrayView1d< string const > const & regionNames ) - { - - ElementRegionManager & elemManager = mesh.getElemManager(); - - elemManager.forElementSubRegions< ElementSubRegionBase >( regionNames, - [&]( localIndex const, - ElementSubRegionBase & subRegion ) - { - subRegion.registerWrapper< string >( viewKeyStruct::porousMaterialNamesString() ). - setPlotLevel( PlotLevel::NOPLOT ). - setRestartFlags( RestartFlags::NO_WRITE ). - setSizedFromParent( 0 ); - } ); - } ); + Base::registerDataOnMesh( meshBodies ); #ifdef GEOSX_USE_SEPARATION_COEFFICIENT meshBodies.forSubGroups< MeshBody >( [&] ( MeshBody & meshBody ) @@ -107,34 +132,13 @@ void HydrofractureSolver::registerDataOnMesh( dataRepository::Group & meshBodies #endif } -void HydrofractureSolver::initializePreSubGroups() -{ - CoupledSolver::initializePreSubGroups(); - - DomainPartition & domain = this->getGroupByPath< DomainPartition >( "/Problem/domain" ); - - forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&] ( string const &, - MeshLevel & mesh, - arrayView1d< string const > const & regionNames ) - { - ElementRegionManager & elementRegionManager = mesh.getElemManager(); - elementRegionManager.forElementSubRegions< ElementSubRegionBase >( regionNames, - [&]( localIndex const, - ElementSubRegionBase & subRegion ) - { - string & porousName = subRegion.getReference< string >( viewKeyStruct::porousMaterialNamesString() ); - porousName = getConstitutiveName< CoupledSolidBase >( subRegion ); - GEOS_ERROR_IF( porousName.empty(), GEOS_FMT( "Solid model not found on subregion {}", subRegion.getName() ) ); - } ); - } ); -} - -void HydrofractureSolver::implicitStepSetup( real64 const & time_n, - real64 const & dt, - DomainPartition & domain ) +template< typename POROMECHANICS_SOLVER > +void HydrofractureSolver< POROMECHANICS_SOLVER >::implicitStepSetup( real64 const & time_n, + real64 const & dt, + DomainPartition & domain ) { updateDeformationForCoupling( domain ); - CoupledSolver::implicitStepSetup( time_n, dt, domain ); + Base::implicitStepSetup( time_n, dt, domain ); #ifdef GEOSX_USE_SEPARATION_COEFFICIENT MeshLevel & mesh = domain.getMeshBody( 0 ).getBaseDiscretization(); @@ -157,16 +161,22 @@ void HydrofractureSolver::implicitStepSetup( real64 const & time_n, } -void HydrofractureSolver::postProcessInput() +template< typename POROMECHANICS_SOLVER > +void HydrofractureSolver< POROMECHANICS_SOLVER >::postProcessInput() { - CoupledSolver::postProcessInput(); - m_surfaceGenerator = &this->getParent().getGroup< SurfaceGenerator >( m_surfaceGeneratorName ); + Base::postProcessInput(); + + static const std::set< integer > binaryOptions = { 0, 1 }; + GEOS_ERROR_IF( binaryOptions.count( m_isMatrixPoroelastic ) == 0, viewKeyStruct::isMatrixPoroelasticString() << " option can be either 0 (false) or 1 (true)" ); + + m_surfaceGenerator = &this->getParent().template getGroup< SurfaceGenerator >( m_surfaceGeneratorName ); } -real64 HydrofractureSolver::fullyCoupledSolverStep( real64 const & time_n, - real64 const & dt, - int const cycleNumber, - DomainPartition & domain ) +template< typename POROMECHANICS_SOLVER > +real64 HydrofractureSolver< POROMECHANICS_SOLVER >::fullyCoupledSolverStep( real64 const & time_n, + real64 const & dt, + int const cycleNumber, + DomainPartition & domain ) { real64 dtReturn = dt; @@ -244,8 +254,8 @@ real64 HydrofractureSolver::fullyCoupledSolverStep( real64 const & time_n, return dtReturn; } - -void HydrofractureSolver::updateDeformationForCoupling( DomainPartition & domain ) +template< typename POROMECHANICS_SOLVER > +void HydrofractureSolver< POROMECHANICS_SOLVER >::updateDeformationForCoupling( DomainPartition & domain ) { MeshLevel & meshLevel = domain.getMeshBody( 0 ).getBaseDiscretization(); ElementRegionManager & elemManager = meshLevel.getElemManager(); @@ -260,7 +270,7 @@ void HydrofractureSolver::updateDeformationForCoupling( DomainPartition & domain elemManager.forElementSubRegions< FaceElementSubRegion >( [&]( FaceElementSubRegion & subRegion ) { - ContactBase const & contact = getConstitutiveModel< ContactBase >( subRegion, m_contactRelationName ); + ContactBase const & contact = this->template getConstitutiveModel< ContactBase >( subRegion, m_contactRelationName ); arrayView1d< real64 > const aperture = subRegion.getElementAperture(); arrayView1d< real64 > const hydraulicAperture = subRegion.getField< flow::hydraulicAperture >(); @@ -317,53 +327,57 @@ void HydrofractureSolver::updateDeformationForCoupling( DomainPartition & domain //#endif } ); } - -void HydrofractureSolver::setupCoupling( DomainPartition const & domain, - DofManager & dofManager ) const +template< typename POROMECHANICS_SOLVER > +void HydrofractureSolver< POROMECHANICS_SOLVER >::setupCoupling( DomainPartition const & domain, + DofManager & dofManager ) const { GEOS_MARK_FUNCTION; + if( m_isMatrixPoroelastic ) + { + Base::setupCoupling( domain, dofManager ); + } + else + { + string const solidDiscretizationName = solidMechanicsSolver()->getDiscretizationName(); + string const flowDiscretizationName = flowSolver()->getDiscretizationName(); - string const solidDiscretizationName = solidMechanicsSolver()->getDiscretizationName(); - string const flowDiscretizationName = flowSolver()->getDiscretizationName(); - - - // restrict coupling to fracture regions only (as done originally in setupSystem) - map< std::pair< string, string >, array1d< string > > dispMeshTargets; - map< std::pair< string, string >, array1d< string > > presMeshTargets; + // restrict coupling to fracture regions only (as done originally in setupSystem) + map< std::pair< string, string >, array1d< string > > dispMeshTargets; + map< std::pair< string, string >, array1d< string > > presMeshTargets; - forDiscretizationOnMeshTargets( domain.getMeshBodies(), - [&] ( string const & meshBodyName, - MeshLevel const & meshLevel, - arrayView1d< string const > const & regionNames ) - { - array1d< string > regions; - ElementRegionManager const & elementRegionManager = meshLevel.getElemManager(); - elementRegionManager.forElementRegions< SurfaceElementRegion >( regionNames, - [&]( localIndex const, - SurfaceElementRegion const & region ) + forDiscretizationOnMeshTargets( domain.getMeshBodies(), + [&] ( string const & meshBodyName, + MeshLevel const & meshLevel, + arrayView1d< string const > const & regionNames ) { - regions.emplace_back( region.getName() ); - } ); - - dispMeshTargets[std::make_pair( meshBodyName, solidDiscretizationName )] = std::move( regions ); - presMeshTargets[std::make_pair( meshBodyName, flowDiscretizationName )] = std::move( regions ); - } ); + array1d< string > regions; + ElementRegionManager const & elementRegionManager = meshLevel.getElemManager(); + elementRegionManager.forElementRegions< SurfaceElementRegion >( regionNames, + [&]( localIndex const, + SurfaceElementRegion const & region ) + { + regions.emplace_back( region.getName() ); + } ); - dofManager.addCoupling( solidMechanics::totalDisplacement::key(), - SinglePhaseBase::viewKeyStruct::elemDofFieldString(), - DofManager::Connector::Elem, - dispMeshTargets ); + dispMeshTargets[std::make_pair( meshBodyName, solidDiscretizationName )] = std::move( regions ); + presMeshTargets[std::make_pair( meshBodyName, flowDiscretizationName )] = std::move( regions ); + } ); + dofManager.addCoupling( solidMechanics::totalDisplacement::key(), + SinglePhaseBase::viewKeyStruct::elemDofFieldString(), + DofManager::Connector::Elem, + dispMeshTargets ); + } } - -void HydrofractureSolver::setupSystem( DomainPartition & domain, - DofManager & dofManager, - CRSMatrix< real64, globalIndex > & localMatrix, - ParallelVector & rhs, - ParallelVector & solution, - bool const setSparsity ) +template< typename POROMECHANICS_SOLVER > +void HydrofractureSolver< POROMECHANICS_SOLVER >::setupSystem( DomainPartition & domain, + DofManager & dofManager, + CRSMatrix< real64, globalIndex > & localMatrix, + ParallelVector & rhs, + ParallelVector & solution, + bool const setSparsity ) { GEOS_MARK_FUNCTION; @@ -417,9 +431,10 @@ void HydrofractureSolver::setupSystem( DomainPartition & domain, setUpDflux_dApertureMatrix( domain, dofManager, localMatrix ); } -void HydrofractureSolver::addFluxApertureCouplingNNZ( DomainPartition & domain, - DofManager & dofManager, - arrayView1d< localIndex > const & rowLengths ) const +template< typename POROMECHANICS_SOLVER > +void HydrofractureSolver< POROMECHANICS_SOLVER >::addFluxApertureCouplingNNZ( DomainPartition & domain, + DofManager & dofManager, + arrayView1d< localIndex > const & rowLengths ) const { GEOS_MARK_FUNCTION; @@ -475,10 +490,10 @@ void HydrofractureSolver::addFluxApertureCouplingNNZ( DomainPartition & domain, } ); } - -void HydrofractureSolver::addFluxApertureCouplingSparsityPattern( DomainPartition & domain, - DofManager & dofManager, - SparsityPatternView< globalIndex > const & pattern ) const +template< typename POROMECHANICS_SOLVER > +void HydrofractureSolver< POROMECHANICS_SOLVER >::addFluxApertureCouplingSparsityPattern( DomainPartition & domain, + DofManager & dofManager, + SparsityPatternView< globalIndex > const & pattern ) const { GEOS_MARK_FUNCTION; @@ -546,29 +561,57 @@ void HydrofractureSolver::addFluxApertureCouplingSparsityPattern( DomainPartitio } } ); } - -void HydrofractureSolver::assembleSystem( real64 const time, - real64 const dt, - DomainPartition & domain, - DofManager const & dofManager, - CRSMatrixView< real64, globalIndex const > const & localMatrix, - arrayView1d< real64 > const & localRhs ) +template< typename POROMECHANICS_SOLVER > +void HydrofractureSolver< POROMECHANICS_SOLVER >::assembleSystem( real64 const time, + real64 const dt, + DomainPartition & domain, + DofManager const & dofManager, + CRSMatrixView< real64, globalIndex const > const & localMatrix, + arrayView1d< real64 > const & localRhs ) { GEOS_MARK_FUNCTION; - // Add Adp - // Apd App - - solidMechanicsSolver()->assembleSystem( time, - dt, - domain, - dofManager, - localMatrix, - localRhs ); - // Add - flowSolver()->assembleAccumulationTerms( domain, - dofManager, - localMatrix, - localRhs ); + + if( m_isMatrixPoroelastic ) + { + assembleElementBasedTerms( time, + dt, + domain, + dofManager, + localMatrix, + localRhs ); + + + forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const &, + MeshLevel & mesh, + arrayView1d< string const > const & regionNames ) + { + mesh.getElemManager().forElementSubRegions< SurfaceElementSubRegion >( regionNames, + [&]( localIndex const, + SurfaceElementSubRegion & subRegion ) + { + flowSolver()->accumulationAssemblyLaunch( dofManager, + subRegion, + localMatrix, + localRhs ); + } ); + } ); + } + else + { + + solidMechanicsSolver()->assembleSystem( time, + dt, + domain, + dofManager, + localMatrix, + localRhs ); + + flowSolver()->assembleAccumulationTerms( domain, + dofManager, + localMatrix, + localRhs ); + } + flowSolver()->assembleHydrofracFluxTerms( time, dt, domain, @@ -576,19 +619,19 @@ void HydrofractureSolver::assembleSystem( real64 const time, localMatrix, localRhs, getDerivativeFluxResidual_dAperture() ); - // App + assembleForceResidualDerivativeWrtPressure( domain, localMatrix, localRhs ); - // Adp + assembleFluidMassResidualDerivativeWrtDisplacement( domain, localMatrix ); - // Apd + this->getRefDerivativeFluxResidual_dAperture()->zero(); } -void -HydrofractureSolver:: - assembleForceResidualDerivativeWrtPressure( DomainPartition & domain, - CRSMatrixView< real64, globalIndex const > const & localMatrix, - arrayView1d< real64 > const & localRhs ) +template< typename POROMECHANICS_SOLVER > +void HydrofractureSolver< POROMECHANICS_SOLVER >:: +assembleForceResidualDerivativeWrtPressure( DomainPartition & domain, + CRSMatrixView< real64, globalIndex const > const & localMatrix, + arrayView1d< real64 > const & localRhs ) { GEOS_MARK_FUNCTION; MeshLevel & mesh = domain.getMeshBody( 0 ).getBaseDiscretization(); @@ -687,10 +730,10 @@ HydrofractureSolver:: } ); } -void -HydrofractureSolver:: - assembleFluidMassResidualDerivativeWrtDisplacement( DomainPartition const & domain, - CRSMatrixView< real64, globalIndex const > const & localMatrix ) +template< typename POROMECHANICS_SOLVER > +void HydrofractureSolver< POROMECHANICS_SOLVER >:: +assembleFluidMassResidualDerivativeWrtDisplacement( DomainPartition const & domain, + CRSMatrixView< real64, globalIndex const > const & localMatrix ) { GEOS_MARK_FUNCTION; @@ -714,10 +757,10 @@ HydrofractureSolver:: [&]( localIndex const, FaceElementSubRegion const & subRegion ) { - ContactBase const & contact = getConstitutiveModel< ContactBase >( subRegion, m_contactRelationName ); + ContactBase const & contact = this->template getConstitutiveModel< ContactBase >( subRegion, m_contactRelationName ); string const & fluidName = subRegion.getReference< string >( FlowSolverBase::viewKeyStruct::fluidNamesString() ); - SingleFluidBase const & fluid = getConstitutiveModel< SingleFluidBase >( subRegion, fluidName ); + SingleFluidBase const & fluid = this->template getConstitutiveModel< SingleFluidBase >( subRegion, fluidName ); arrayView1d< globalIndex const > const presDofNumber = subRegion.getReference< array1d< globalIndex > >( presDofKey ); arrayView1d< globalIndex const > const dispDofNumber = nodeManager.getReference< array1d< globalIndex > >( dispDofKey ); @@ -756,15 +799,15 @@ HydrofractureSolver:: } ); } ); } - -void HydrofractureSolver::updateState( DomainPartition & domain ) +template< typename POROMECHANICS_SOLVER > +void HydrofractureSolver< POROMECHANICS_SOLVER >::updateState( DomainPartition & domain ) { updateDeformationForCoupling( domain ); flowSolver()->updateState( domain ); } - -real64 HydrofractureSolver::setNextDt( real64 const & currentDt, - DomainPartition & domain ) +template< typename POROMECHANICS_SOLVER > +real64 HydrofractureSolver< POROMECHANICS_SOLVER >::setNextDt( real64 const & currentDt, + DomainPartition & domain ) { GEOS_UNUSED_VAR( domain ); real64 nextDt = 0.0; @@ -775,17 +818,16 @@ real64 HydrofractureSolver::setNextDt( real64 const & currentDt, } else { - SolverBase & surfaceGenerator = this->getParent().getGroup< SolverBase >( "SurfaceGen" ); - nextDt = surfaceGenerator.getTimestepRequest() < 1e99 ? surfaceGenerator.getTimestepRequest() : currentDt; + nextDt = m_surfaceGenerator->getTimestepRequest() < 1e99 ? m_surfaceGenerator->getTimestepRequest() : currentDt; } GEOS_LOG_LEVEL_RANK_0( 3, this->getName() << ": nextDt request is " << nextDt ); return nextDt; } - -void HydrofractureSolver::setUpDflux_dApertureMatrix( DomainPartition & domain, - DofManager const & dofManager, - CRSMatrix< real64, globalIndex > & localMatrix ) +template< typename POROMECHANICS_SOLVER > +void HydrofractureSolver< POROMECHANICS_SOLVER >::setUpDflux_dApertureMatrix( DomainPartition & domain, + DofManager const & dofManager, + CRSMatrix< real64, globalIndex > & localMatrix ) { std::unique_ptr< CRSMatrix< real64, localIndex > > & derivativeFluxResidual_dAperture = this->getRefDerivativeFluxResidual_dAperture(); @@ -848,5 +890,12 @@ void HydrofractureSolver::setUpDflux_dApertureMatrix( DomainPartition & domain, } ); } -REGISTER_CATALOG_ENTRY( SolverBase, HydrofractureSolver, string const &, Group * const ) +namespace +{ +typedef HydrofractureSolver< SinglePhasePoromechanics > SinglePhaseHydrofracture; +// typedef HydrofractureSolver< MultiphasePoromechanics > MultiphaseHydrofracture; +REGISTER_CATALOG_ENTRY( SolverBase, SinglePhaseHydrofracture, string const &, Group * const ) +// REGISTER_CATALOG_ENTRY( SolverBase, MultiphaseHydrofracture, string const &, Group * const ) +} + } /* namespace geos */ diff --git a/src/coreComponents/physicsSolvers/multiphysics/HydrofractureSolver.hpp b/src/coreComponents/physicsSolvers/multiphysics/HydrofractureSolver.hpp index aa1c86f5ebd..df9f76bc8a0 100644 --- a/src/coreComponents/physicsSolvers/multiphysics/HydrofractureSolver.hpp +++ b/src/coreComponents/physicsSolvers/multiphysics/HydrofractureSolver.hpp @@ -20,30 +20,41 @@ #define GEOS_PHYSICSSOLVERS_MULTIPHYSICS_HYDROFRACTURESOLVER_HPP_ #include "physicsSolvers/multiphysics/CoupledSolver.hpp" -#include "physicsSolvers/fluidFlow/SinglePhaseBase.hpp" -#include "physicsSolvers/solidMechanics/SolidMechanicsLagrangianFEM.hpp" #include "physicsSolvers/surfaceGeneration/SurfaceGenerator.hpp" +#include "physicsSolvers/multiphysics/SinglePhasePoromechanics.hpp" namespace geos { -class HydrofractureSolver : public CoupledSolver< SolidMechanicsLagrangianFEM, - SinglePhaseBase > +using dataRepository::Group; + +template< typename POROMECHANICS_SOLVER = SinglePhasePoromechanics > +class HydrofractureSolver : public POROMECHANICS_SOLVER { public: - using Base = CoupledSolver< SolidMechanicsLagrangianFEM, SinglePhaseBase >; + using Base = POROMECHANICS_SOLVER; using Base::m_solvers; + using Base::m_names; using Base::m_dofManager; using Base::m_localMatrix; using Base::m_rhs; using Base::m_solution; + using Base::m_linearSolverParameters; + + using Base::registerWrapper; + using Base::forDiscretizationOnMeshTargets; + using Base::getMeshModificationTimestamp; + using Base::getSystemSetupTimestamp; + using Base::nonlinearImplicitStep; + using Base::implicitStepComplete; + using Base::getLogLevel; + using Base::setSystemSetupTimestamp; + using Base::setupDofs; + using Base::flowSolver; + using Base::solidMechanicsSolver; + using Base::assembleElementBasedTerms; - enum class SolverType : integer - { - SolidMechanics = 0, - Flow = 1 - }; /** * @brief main constructor for HydrofractureSolver objects @@ -56,32 +67,10 @@ class HydrofractureSolver : public CoupledSolver< SolidMechanicsLagrangianFEM, /// Destructor for the class ~HydrofractureSolver() override {} - /** - * @brief name of the node manager in the object catalog - * @return string that contains the catalog name to generate a new HydrofractureSolver object through the object catalog. - */ - static string catalogName() - { - return "Hydrofracture"; - } - - /** - * @brief accessor for the pointer to the solid mechanics solver - * @return a pointer to the solid mechanics solver - */ - SolidMechanicsLagrangianFEM * solidMechanicsSolver() const - { - return std::get< toUnderlying( SolverType::SolidMechanics ) >( m_solvers ); - } + static string catalogName(); - /** - * @brief accessor for the pointer to the flow solver - * @return a pointer to the flow solver - */ - SinglePhaseBase * flowSolver() const - { - return std::get< toUnderlying( SolverType::Flow ) >( m_solvers ); - } + /// String used to form the solverName used to register solvers in CoupledSolver + static string coupledSolverAttributePrefix() { return "poromechanics"; } /** * @defgroup Solver Interface Functions @@ -93,7 +82,7 @@ class HydrofractureSolver : public CoupledSolver< SolidMechanicsLagrangianFEM, virtual void registerDataOnMesh( Group & MeshBodies ) override final; virtual void setupCoupling( DomainPartition const & domain, - DofManager & dofManager ) const override; + DofManager & dofManager ) const override final; virtual void setupSystem( DomainPartition & domain, DofManager & dofManager, @@ -150,10 +139,11 @@ class HydrofractureSolver : public CoupledSolver< SolidMechanicsLagrangianFEM, constexpr static char const * surfaceGeneratorNameString() { return "surfaceGeneratorName"; } - constexpr static char const * porousMaterialNamesString() { return "porousMaterialNames"; } - constexpr static char const * maxNumResolvesString() { return "maxNumResolves"; } + constexpr static char const * isMatrixPoroelasticString() { return "isMatrixPoroelastic"; } + + #ifdef GEOSX_USE_SEPARATION_COEFFICIENT constexpr static char const * separationCoeff0String() { return "separationCoeff0"; } constexpr static char const * apertureAtFailureString() { return "apertureAtFailure"; } @@ -164,8 +154,6 @@ class HydrofractureSolver : public CoupledSolver< SolidMechanicsLagrangianFEM, virtual void postProcessInput() override final; - virtual void initializePreSubGroups() override final; - /** * @Brief add the nnz induced by the flux-aperture coupling * @param domain the physical domain object @@ -209,14 +197,14 @@ class HydrofractureSolver : public CoupledSolver< SolidMechanicsLagrangianFEM, /// pointer to the surface generator SurfaceGenerator * m_surfaceGenerator; - std::unique_ptr< ParallelMatrix > m_blockDiagUU; - // it is only important for this case. std::unique_ptr< CRSMatrix< real64, localIndex > > m_derivativeFluxResidual_dAperture; integer m_maxNumResolves; integer m_numResolves[2]; + integer m_isMatrixPoroelastic; + }; diff --git a/src/coreComponents/physicsSolvers/multiphysics/MultiphasePoromechanics.cpp b/src/coreComponents/physicsSolvers/multiphysics/MultiphasePoromechanics.cpp index 040e351b68a..232505c7d09 100644 --- a/src/coreComponents/physicsSolvers/multiphysics/MultiphasePoromechanics.cpp +++ b/src/coreComponents/physicsSolvers/multiphysics/MultiphasePoromechanics.cpp @@ -22,6 +22,7 @@ #include "constitutive/fluid/multifluid/MultiFluidBase.hpp" #include "constitutive/solid/PorousSolid.hpp" +#include "mesh/utilities/AverageOverQuadraturePointsKernel.hpp" #include "physicsSolvers/fluidFlow/CompositionalMultiphaseBase.hpp" #include "physicsSolvers/fluidFlow/FlowSolverBaseFields.hpp" #include "physicsSolvers/multiphysics/poromechanicsKernels/MultiphasePoromechanics.hpp" @@ -78,6 +79,14 @@ void MultiphasePoromechanics::registerDataOnMesh( Group & meshBodies ) { SolverBase::registerDataOnMesh( meshBodies ); + if( getNonlinearSolverParameters().m_couplingType == NonlinearSolverParameters::CouplingType::Sequential ) + { + // to let the solid mechanics solver that there is a pressure and temperature RHS in the mechanics solve + solidMechanicsSolver()->enableFixedStressPoromechanicsUpdate(); + // to let the flow solver that saving pressure_k and temperature_k is necessary (for the fixed-stress porosity terms) + flowSolver()->enableFixedStressPoromechanicsUpdate(); + } + forDiscretizationOnMeshTargets( meshBodies, [&] ( string const &, MeshLevel & mesh, arrayView1d< string const > const & regionNames ) @@ -118,6 +127,19 @@ void MultiphasePoromechanics::setupCoupling( DomainPartition const & GEOS_UNUSED DofManager::Connector::Elem ); } +void MultiphasePoromechanics::setupDofs( DomainPartition const & domain, + DofManager & dofManager ) const +{ + // note that the order of operations matters a lot here (for instance for the MGR labels) + // we must set up dofs for solid mechanics first, and then for flow + // that's the reason why this function is here and not in CoupledSolvers.hpp + solidMechanicsSolver()->setupDofs( domain, dofManager ); + flowSolver()->setupDofs( domain, dofManager ); + + setupCoupling( domain, dofManager ); +} + + void MultiphasePoromechanics::assembleSystem( real64 const GEOS_UNUSED_PARAM( time ), real64 const dt, DomainPartition & domain, @@ -213,9 +235,6 @@ void MultiphasePoromechanics::assembleSystem( real64 const GEOS_UNUSED_PARAM( ti solidMechanicsSolver()->getMaxForce() = LvArray::math::max( mechanicsMaxForce, poromechanicsMaxForce ); - // tell the flow solver that this is a stress initialization step - flowSolver()->keepFlowVariablesConstantDuringInitStep( m_performStressInitialization ); - // step 3: compute the fluxes (face-based contributions) if( m_stabilizationType == StabilizationType::Global || @@ -262,6 +281,26 @@ void MultiphasePoromechanics::initializePostInitialConditionsPreSubGroups() { SolverBase::initializePostInitialConditionsPreSubGroups(); + arrayView1d< string const > const & poromechanicsTargetRegionNames = + this->getReference< array1d< string > >( SolverBase::viewKeyStruct::targetRegionsString() ); + arrayView1d< string const > const & solidMechanicsTargetRegionNames = + solidMechanicsSolver()->getReference< array1d< string > >( SolverBase::viewKeyStruct::targetRegionsString() ); + arrayView1d< string const > const & flowTargetRegionNames = + flowSolver()->getReference< array1d< string > >( SolverBase::viewKeyStruct::targetRegionsString() ); + for( integer i = 0; i < poromechanicsTargetRegionNames.size(); ++i ) + { + GEOS_THROW_IF( std::find( solidMechanicsTargetRegionNames.begin(), solidMechanicsTargetRegionNames.end(), poromechanicsTargetRegionNames[i] ) + == solidMechanicsTargetRegionNames.end(), + GEOS_FMT( "{} {}: region `{}` must be a target region of `{}`", + catalogName(), getName(), poromechanicsTargetRegionNames[i], solidMechanicsSolver()->getName() ), + InputError ); + GEOS_THROW_IF( std::find( flowTargetRegionNames.begin(), flowTargetRegionNames.end(), poromechanicsTargetRegionNames[i] ) + == flowTargetRegionNames.end(), + GEOS_FMT( "{} {}: region `{}` must be a target region of `{}`", + catalogName(), getName(), poromechanicsTargetRegionNames[i], flowSolver()->getName() ), + InputError ); + } + integer & isFlowThermal = flowSolver()->getReference< integer >( FlowSolverBase::viewKeyStruct::isThermalString() ); GEOS_LOG_RANK_0_IF( m_isThermal && !isFlowThermal, GEOS_FMT( "{} {}: The attribute `{}` of the flow solver `{}` is set to 1 since the poromechanics solver is thermal", @@ -278,11 +317,6 @@ void MultiphasePoromechanics::initializePreSubGroups() { SolverBase::initializePreSubGroups(); - if( getNonlinearSolverParameters().m_couplingType == NonlinearSolverParameters::CouplingType::Sequential ) - { - solidMechanicsSolver()->turnOnFixedStressThermoPoromechanicsFlag(); - } - GEOS_THROW_IF( m_stabilizationType == StabilizationType::Local, catalogName() << " " << getName() << ": Local stabilization has been disabled temporarily", InputError ); @@ -312,6 +346,14 @@ void MultiphasePoromechanics::initializePreSubGroups() } ); } +void MultiphasePoromechanics::implicitStepSetup( real64 const & time_n, + real64 const & dt, + DomainPartition & domain ) +{ + flowSolver()->keepFlowVariablesConstantDuringInitStep( m_performStressInitialization ); + Base::implicitStepSetup( time_n, dt, domain ); +} + void MultiphasePoromechanics::updateStabilizationParameters( DomainPartition & domain ) const { // Step 1: we loop over the regions where stabilization is active and collect their name @@ -378,27 +420,49 @@ void MultiphasePoromechanics::updateStabilizationParameters( DomainPartition & d void MultiphasePoromechanics::mapSolutionBetweenSolvers( DomainPartition & domain, integer const solverType ) { GEOS_MARK_FUNCTION; - if( solverType == static_cast< integer >( SolverType::SolidMechanics ) ) + + /// After the flow solver + if( solverType == static_cast< integer >( SolverType::Flow ) ) { + // save pressure and temperature at the end of this iteration + flowSolver()->FlowSolverBase::saveIterationState( domain ); + forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const &, MeshLevel & mesh, arrayView1d< string const > const & regionNames ) { + mesh.getElemManager().forElementSubRegions< CellElementSubRegion >( regionNames, [&]( localIndex const, auto & subRegion ) { - // update the porosity after a change in displacement (after mechanics solve) - // or a change in pressure/temperature (after a flow solve) - flowSolver()->updatePorosityAndPermeability( subRegion ); - // update the bulk density - // in fact, this is only needed after a flow solve, but we don't have a mechanism to know where we are in the outer loop // TODO: ideally, we would not recompute the bulk density, but a more general "rhs" containing the body force and the // pressure/temperature terms updateBulkDensity( subRegion ); } ); } ); } + + /// After the solid mechanics solver + if( solverType == static_cast< integer >( SolverType::SolidMechanics ) ) + { + // compute the average of the mean stress increment over quadrature points + averageMeanStressIncrement( domain ); + + forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const &, + MeshLevel & mesh, + arrayView1d< string const > const & regionNames ) + { + + mesh.getElemManager().forElementSubRegions< CellElementSubRegion >( regionNames, [&]( localIndex const, + auto & subRegion ) + { + // update the porosity after a change in displacement (after mechanics solve) + // or a change in pressure/temperature (after a flow solve) + flowSolver()->updatePorosityAndPermeability( subRegion ); + } ); + } ); + } } void MultiphasePoromechanics::updateBulkDensity( ElementSubRegionBase & subRegion ) @@ -420,6 +484,47 @@ void MultiphasePoromechanics::updateBulkDensity( ElementSubRegionBase & subRegio subRegion ); } +void MultiphasePoromechanics::averageMeanStressIncrement( DomainPartition & domain ) +{ + forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const &, + MeshLevel & mesh, + arrayView1d< string const > const & regionNames ) + { + mesh.getElemManager().forElementSubRegions< CellElementSubRegion >( regionNames, [&]( localIndex const, + auto & subRegion ) + { + // get the solid model (to access stress increment) + string const solidName = subRegion.template getReference< string >( viewKeyStruct::porousMaterialNamesString() ); + CoupledSolidBase & solid = getConstitutiveModel< CoupledSolidBase >( subRegion, solidName ); + + arrayView2d< real64 const > const meanStressIncrement_k = solid.getMeanEffectiveStressIncrement_k(); + arrayView1d< real64 > const averageMeanStressIncrement_k = solid.getAverageMeanEffectiveStressIncrement_k(); + + finiteElement::FiniteElementBase & subRegionFE = + subRegion.template getReference< finiteElement::FiniteElementBase >( solidMechanicsSolver()->getDiscretizationName() ); + + // determine the finite element type + finiteElement::FiniteElementDispatchHandler< BASE_FE_TYPES >:: + dispatch3D( subRegionFE, [&] ( auto const finiteElement ) + { + using FE_TYPE = decltype( finiteElement ); + + // call the factory and launch the kernel + AverageOverQuadraturePoints1DKernelFactory:: + createAndLaunch< CellElementSubRegion, + FE_TYPE, + parallelDevicePolicy<> >( mesh.getNodeManager(), + mesh.getEdgeManager(), + mesh.getFaceManager(), + subRegion, + finiteElement, + meanStressIncrement_k, + averageMeanStressIncrement_k ); + } ); + } ); + } ); +} + REGISTER_CATALOG_ENTRY( SolverBase, MultiphasePoromechanics, string const &, Group * const ) diff --git a/src/coreComponents/physicsSolvers/multiphysics/MultiphasePoromechanics.hpp b/src/coreComponents/physicsSolvers/multiphysics/MultiphasePoromechanics.hpp index a4e4ce1c5b3..f271de7461b 100644 --- a/src/coreComponents/physicsSolvers/multiphysics/MultiphasePoromechanics.hpp +++ b/src/coreComponents/physicsSolvers/multiphysics/MultiphasePoromechanics.hpp @@ -28,14 +28,11 @@ namespace geos { -// Note that in the current implementation, the order of the templates in CoupledSolver< ... > matters a lot -// Changing the order of these templates can break a lot of things (labels in MGR for instance) and must be done carefully -class MultiphasePoromechanics : public CoupledSolver< SolidMechanicsLagrangianFEM, - CompositionalMultiphaseBase > +class MultiphasePoromechanics : public CoupledSolver< CompositionalMultiphaseBase, SolidMechanicsLagrangianFEM > { public: - using Base = CoupledSolver< SolidMechanicsLagrangianFEM, CompositionalMultiphaseBase >; + using Base = CoupledSolver< CompositionalMultiphaseBase, SolidMechanicsLagrangianFEM >; using Base::m_solvers; using Base::m_dofManager; using Base::m_localMatrix; @@ -44,8 +41,8 @@ class MultiphasePoromechanics : public CoupledSolver< SolidMechanicsLagrangianFE enum class SolverType : integer { - SolidMechanics = 0, - Flow = 1 + Flow = 0, + SolidMechanics = 1 }; /// String used to form the solverName used to register solvers in CoupledSolver @@ -98,6 +95,13 @@ class MultiphasePoromechanics : public CoupledSolver< SolidMechanicsLagrangianFE virtual void setupCoupling( DomainPartition const & domain, DofManager & dofManager ) const override; + virtual void setupDofs( DomainPartition const & domain, + DofManager & dofManager ) const override; + + virtual void implicitStepSetup( real64 const & time_n, + real64 const & dt, + DomainPartition & domain ) override; + virtual void assembleSystem( real64 const time, real64 const dt, DomainPartition & domain, @@ -168,6 +172,12 @@ class MultiphasePoromechanics : public CoupledSolver< SolidMechanicsLagrangianFE */ void updateBulkDensity( ElementSubRegionBase & subRegion ); + /** + * @brief Helper function to average the mean stress increment over quadrature points + * @param[in] domain the domain partition + */ + void averageMeanStressIncrement( DomainPartition & domain ); + template< typename CONSTITUTIVE_BASE, typename KERNEL_WRAPPER, typename ... PARAMS > diff --git a/src/coreComponents/physicsSolvers/multiphysics/SinglePhasePoromechanics.cpp b/src/coreComponents/physicsSolvers/multiphysics/SinglePhasePoromechanics.cpp index c7c01637644..d3f3d09ba7c 100644 --- a/src/coreComponents/physicsSolvers/multiphysics/SinglePhasePoromechanics.cpp +++ b/src/coreComponents/physicsSolvers/multiphysics/SinglePhasePoromechanics.cpp @@ -24,6 +24,7 @@ #include "constitutive/fluid/singlefluid/SingleFluidBase.hpp" #include "linearAlgebra/solvers/BlockPreconditioner.hpp" #include "linearAlgebra/solvers/SeparateComponentPreconditioner.hpp" +#include "mesh/utilities/AverageOverQuadraturePointsKernel.hpp" #include "physicsSolvers/fluidFlow/SinglePhaseBase.hpp" #include "physicsSolvers/multiphysics/poromechanicsKernels/SinglePhasePoromechanics.hpp" #include "physicsSolvers/multiphysics/poromechanicsKernels/ThermalSinglePhasePoromechanics.hpp" @@ -63,6 +64,14 @@ void SinglePhasePoromechanics::registerDataOnMesh( Group & meshBodies ) { SolverBase::registerDataOnMesh( meshBodies ); + if( getNonlinearSolverParameters().m_couplingType == NonlinearSolverParameters::CouplingType::Sequential ) + { + // to let the solid mechanics solver that there is a pressure and temperature RHS in the mechanics solve + solidMechanicsSolver()->enableFixedStressPoromechanicsUpdate(); + // to let the flow solver that saving pressure_k and temperature_k is necessary (for the fixed-stress porosity terms) + flowSolver()->enableFixedStressPoromechanicsUpdate(); + } + forDiscretizationOnMeshTargets( meshBodies, [&] ( string const &, MeshLevel & mesh, arrayView1d< string const > const & regionNames ) @@ -98,15 +107,23 @@ void SinglePhasePoromechanics::setupCoupling( DomainPartition const & GEOS_UNUSE DofManager::Connector::Elem ); } +void SinglePhasePoromechanics::setupDofs( DomainPartition const & domain, + DofManager & dofManager ) const +{ + // note that the order of operations matters a lot here (for instance for the MGR labels) + // we must set up dofs for solid mechanics first, and then for flow + // that's the reason why this function is here and not in CoupledSolvers.hpp + solidMechanicsSolver()->setupDofs( domain, dofManager ); + flowSolver()->setupDofs( domain, dofManager ); + + setupCoupling( domain, dofManager ); +} + + void SinglePhasePoromechanics::initializePreSubGroups() { SolverBase::initializePreSubGroups(); - if( getNonlinearSolverParameters().m_couplingType == NonlinearSolverParameters::CouplingType::Sequential ) - { - solidMechanicsSolver()->turnOnFixedStressThermoPoromechanicsFlag(); - } - DomainPartition & domain = this->getGroupByPath< DomainPartition >( "/Problem/domain" ); forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&] ( string const &, @@ -136,6 +153,14 @@ void SinglePhasePoromechanics::initializePreSubGroups() } +void SinglePhasePoromechanics::implicitStepSetup( real64 const & time_n, + real64 const & dt, + DomainPartition & domain ) +{ + flowSolver()->keepFlowVariablesConstantDuringInitStep( m_performStressInitialization ); + Base::implicitStepSetup( time_n, dt, domain ); +} + void SinglePhasePoromechanics::setupSystem( DomainPartition & domain, DofManager & dofManager, CRSMatrix< real64, globalIndex > & localMatrix, @@ -161,6 +186,19 @@ void SinglePhasePoromechanics::initializePostInitialConditionsPreSubGroups() { SolverBase::initializePostInitialConditionsPreSubGroups(); + arrayView1d< string const > const & poromechanicsTargetRegionNames = + this->getReference< array1d< string > >( SolverBase::viewKeyStruct::targetRegionsString() ); + arrayView1d< string const > const & flowTargetRegionNames = + flowSolver()->getReference< array1d< string > >( SolverBase::viewKeyStruct::targetRegionsString() ); + for( integer i = 0; i < poromechanicsTargetRegionNames.size(); ++i ) + { + GEOS_THROW_IF( std::find( flowTargetRegionNames.begin(), flowTargetRegionNames.end(), poromechanicsTargetRegionNames[i] ) + == flowTargetRegionNames.end(), + GEOS_FMT( "{} {}: region `{}` must be a target region of `{}`", + catalogName(), getName(), poromechanicsTargetRegionNames[i], flowSolver()->getName() ), + InputError ); + } + integer & isFlowThermal = flowSolver()->getReference< integer >( FlowSolverBase::viewKeyStruct::isThermalString() ); GEOS_LOG_RANK_0_IF( m_isThermal && !isFlowThermal, GEOS_FMT( "{} {}: The attribute `{}` of the flow solver `{}` is set to 1 since the poromechanics solver is thermal", @@ -190,6 +228,33 @@ void SinglePhasePoromechanics::assembleSystem( real64 const time_n, GEOS_MARK_FUNCTION; + // Steps 1 and 2: compute element-based terms (mechanics and local flow terms) + assembleElementBasedTerms( time_n, + dt, + domain, + dofManager, + localMatrix, + localRhs ); + + // Step 3: compute the fluxes (face-based contributions) + flowSolver()->assembleFluxTerms( time_n, dt, + domain, + dofManager, + localMatrix, + localRhs ); + +} + +void SinglePhasePoromechanics::assembleElementBasedTerms( real64 const time_n, + real64 const dt, + DomainPartition & domain, + DofManager const & dofManager, + CRSMatrixView< real64, globalIndex const > const & localMatrix, + arrayView1d< real64 > const & localRhs ) +{ + GEOS_UNUSED_VAR( time_n ); + GEOS_UNUSED_VAR( dt ); + real64 poromechanicsMaxForce = 0.0; real64 mechanicsMaxForce = 0.0; @@ -268,31 +333,6 @@ void SinglePhasePoromechanics::assembleSystem( real64 const time_n, } ); solidMechanicsSolver()->getMaxForce() = LvArray::math::max( mechanicsMaxForce, poromechanicsMaxForce ); - - - // tell the flow solver that this is a stress initialization step - flowSolver()->keepFlowVariablesConstantDuringInitStep( m_performStressInitialization ); - - // step 3: compute the fluxes (face-based contributions) - - if( m_isThermal ) - { - flowSolver()->assembleFluxTerms( time_n, dt, - domain, - dofManager, - localMatrix, - localRhs ); - } - else - { - flowSolver()->assemblePoroelasticFluxTerms( time_n, dt, - domain, - dofManager, - localMatrix, - localRhs, - " " ); - } - } void SinglePhasePoromechanics::createPreconditioner() @@ -347,24 +387,47 @@ void SinglePhasePoromechanics::updateState( DomainPartition & domain ) void SinglePhasePoromechanics::mapSolutionBetweenSolvers( DomainPartition & domain, integer const solverType ) { GEOS_MARK_FUNCTION; - if( solverType == static_cast< integer >( SolverType::SolidMechanics ) ) + + /// After the flow solver + if( solverType == static_cast< integer >( SolverType::Flow ) ) { + // save pressure and temperature at the end of this iteration + flowSolver()->saveIterationState( domain ); + forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const &, MeshLevel & mesh, arrayView1d< string const > const & regionNames ) { + mesh.getElemManager().forElementSubRegions< CellElementSubRegion >( regionNames, [&]( localIndex const, auto & subRegion ) { - // update the porosity after a change in displacement (after mechanics solve) - // or a change in pressure/temperature (after a flow solve) - flowSolver()->updatePorosityAndPermeability( subRegion ); - // update the bulk density - // in fact, this is only needed after a flow solve, but we don't have a mechanism to know where we are in the outer loop // TODO: ideally, we would not recompute the bulk density, but a more general "rhs" containing the body force and the // pressure/temperature terms updateBulkDensity( subRegion ); + + } ); + } ); + } + + /// After the solid mechanics solver + if( solverType == static_cast< integer >( SolverType::SolidMechanics ) ) + { + // compute the average of the mean stress increment over quadrature points + averageMeanStressIncrement( domain ); + + forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const &, + MeshLevel & mesh, + arrayView1d< string const > const & regionNames ) + { + + mesh.getElemManager().forElementSubRegions< CellElementSubRegion >( regionNames, [&]( localIndex const, + auto & subRegion ) + { + // update the porosity after a change in displacement (after mechanics solve) + // or a change in pressure/temperature (after a flow solve) + flowSolver()->updatePorosityAndPermeability( subRegion ); } ); } ); } @@ -388,6 +451,47 @@ void SinglePhasePoromechanics::updateBulkDensity( ElementSubRegionBase & subRegi subRegion ); } +void SinglePhasePoromechanics::averageMeanStressIncrement( DomainPartition & domain ) +{ + forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const &, + MeshLevel & mesh, + arrayView1d< string const > const & regionNames ) + { + mesh.getElemManager().forElementSubRegions< CellElementSubRegion >( regionNames, [&]( localIndex const, + auto & subRegion ) + { + // get the solid model (to access stress increment) + string const solidName = subRegion.template getReference< string >( viewKeyStruct::porousMaterialNamesString() ); + CoupledSolidBase & solid = getConstitutiveModel< CoupledSolidBase >( subRegion, solidName ); + + arrayView2d< real64 const > const meanStressIncrement_k = solid.getMeanEffectiveStressIncrement_k(); + arrayView1d< real64 > const averageMeanStressIncrement_k = solid.getAverageMeanEffectiveStressIncrement_k(); + + finiteElement::FiniteElementBase & subRegionFE = + subRegion.template getReference< finiteElement::FiniteElementBase >( solidMechanicsSolver()->getDiscretizationName() ); + + // determine the finite element type + finiteElement::FiniteElementDispatchHandler< BASE_FE_TYPES >:: + dispatch3D( subRegionFE, [&] ( auto const finiteElement ) + { + using FE_TYPE = decltype( finiteElement ); + + // call the factory and launch the kernel + AverageOverQuadraturePoints1DKernelFactory:: + createAndLaunch< CellElementSubRegion, + FE_TYPE, + parallelDevicePolicy<> >( mesh.getNodeManager(), + mesh.getEdgeManager(), + mesh.getFaceManager(), + subRegion, + finiteElement, + meanStressIncrement_k, + averageMeanStressIncrement_k ); + } ); + } ); + } ); +} + REGISTER_CATALOG_ENTRY( SolverBase, SinglePhasePoromechanics, string const &, Group * const ) } /* namespace geos */ diff --git a/src/coreComponents/physicsSolvers/multiphysics/SinglePhasePoromechanics.hpp b/src/coreComponents/physicsSolvers/multiphysics/SinglePhasePoromechanics.hpp index 0323b9157c1..3d430283e88 100644 --- a/src/coreComponents/physicsSolvers/multiphysics/SinglePhasePoromechanics.hpp +++ b/src/coreComponents/physicsSolvers/multiphysics/SinglePhasePoromechanics.hpp @@ -26,14 +26,12 @@ namespace geos { -// Note that in the current implementation, the order of the templates in CoupledSolver< ... > matters a lot -// Changing the order of these templates can break a lot of things (labels in MGR for instance) and must be done carefully -class SinglePhasePoromechanics : public CoupledSolver< SolidMechanicsLagrangianFEM, - SinglePhaseBase > +class SinglePhasePoromechanics : public CoupledSolver< SinglePhaseBase, + SolidMechanicsLagrangianFEM > { public: - using Base = CoupledSolver< SolidMechanicsLagrangianFEM, SinglePhaseBase >; + using Base = CoupledSolver< SinglePhaseBase, SolidMechanicsLagrangianFEM >; using Base::m_solvers; using Base::m_dofManager; using Base::m_localMatrix; @@ -42,8 +40,8 @@ class SinglePhasePoromechanics : public CoupledSolver< SolidMechanicsLagrangianF enum class SolverType : integer { - SolidMechanics = 0, - Flow = 1 + Flow = 0, + SolidMechanics = 1 }; /// String used to form the solverName used to register solvers in CoupledSolver @@ -96,6 +94,13 @@ class SinglePhasePoromechanics : public CoupledSolver< SolidMechanicsLagrangianF virtual void setupCoupling( DomainPartition const & domain, DofManager & dofManager ) const override; + virtual void setupDofs( DomainPartition const & domain, + DofManager & dofManager ) const override; + + virtual void implicitStepSetup( real64 const & time_n, + real64 const & dt, + DomainPartition & domain ) override; + virtual void setupSystem( DomainPartition & domain, DofManager & dofManager, CRSMatrix< real64, globalIndex > & localMatrix, @@ -141,6 +146,15 @@ class SinglePhasePoromechanics : public CoupledSolver< SolidMechanicsLagrangianF virtual void initializePreSubGroups() override; + void assembleElementBasedTerms( real64 const time_n, + real64 const dt, + DomainPartition & domain, + DofManager const & dofManager, + CRSMatrixView< real64, globalIndex const > const & localMatrix, + arrayView1d< real64 > const & localRhs ); + /// flag to determine whether or not this is a thermal simulation + integer m_isThermal; + private: /** @@ -149,6 +163,12 @@ class SinglePhasePoromechanics : public CoupledSolver< SolidMechanicsLagrangianF */ void updateBulkDensity( ElementSubRegionBase & subRegion ); + /** + * @brief Helper function to average the mean stress increment + * @param[in] domain the domain partition + */ + void averageMeanStressIncrement( DomainPartition & domain ); + void createPreconditioner(); template< typename CONSTITUTIVE_BASE, @@ -162,9 +182,6 @@ class SinglePhasePoromechanics : public CoupledSolver< SolidMechanicsLagrangianF arrayView1d< real64 > const & localRhs, PARAMS && ... params ); - /// flag to determine whether or not this is a thermal simulation - integer m_isThermal; - /// Flag to indicate that the solver is going to perform stress initialization integer m_performStressInitialization; }; diff --git a/src/coreComponents/physicsSolvers/multiphysics/SinglePhasePoromechanicsConformingFractures.cpp b/src/coreComponents/physicsSolvers/multiphysics/SinglePhasePoromechanicsConformingFractures.cpp index 81c963bd12c..1a16a49db11 100644 --- a/src/coreComponents/physicsSolvers/multiphysics/SinglePhasePoromechanicsConformingFractures.cpp +++ b/src/coreComponents/physicsSolvers/multiphysics/SinglePhasePoromechanicsConformingFractures.cpp @@ -24,6 +24,7 @@ #include "linearAlgebra/solvers/SeparateComponentPreconditioner.hpp" #include "physicsSolvers/fluidFlow/SinglePhaseBase.hpp" #include "physicsSolvers/multiphysics/poromechanicsKernels/SinglePhasePoromechanics.hpp" +#include "physicsSolvers/multiphysics/poromechanicsKernels/ThermalSinglePhasePoromechanics.hpp" #include "physicsSolvers/multiphysics/poromechanicsKernels/SinglePhasePoromechanicsFractures.hpp" #include "physicsSolvers/solidMechanics/SolidMechanicsFields.hpp" #include "physicsSolvers/solidMechanics/SolidMechanicsLagrangianFEM.hpp" @@ -37,12 +38,23 @@ using namespace fields; SinglePhasePoromechanicsConformingFractures::SinglePhasePoromechanicsConformingFractures( const string & name, Group * const parent ) - : Base( name, parent ) -{} + : Base( name, parent ), + m_isThermal( 0 ) +{ + this->registerWrapper( viewKeyStruct::isThermalString(), &m_isThermal ). + setApplyDefaultValue( 0 ). + setInputFlag( InputFlags::OPTIONAL ). + setDescription( "Flag indicating whether the problem is thermal or not. Set isThermal=\"1\" to enable the thermal coupling" ); +} void SinglePhasePoromechanicsConformingFractures::initializePostInitialConditionsPostSubGroups() { contactSolver()->setSolidSolverDofFlags( false ); + + integer const & isPoromechanicsSolverThermal = poromechanicsSolver()->getReference< integer >( SinglePhasePoromechanics::viewKeyStruct::isThermalString() ); + + GEOS_ERROR_IF( isPoromechanicsSolverThermal != m_isThermal, GEOS_FMT( "{} {}: The attribute `{}` of the poromechanics solver `{}` must be set to the same value as for this solver.", + catalogName(), getName(), SinglePhasePoromechanics::viewKeyStruct::isThermalString(), poromechanicsSolver()->getName() ) ); } void SinglePhasePoromechanicsConformingFractures::setupCoupling( DomainPartition const & domain, @@ -193,32 +205,31 @@ void SinglePhasePoromechanicsConformingFractures::assembleCellBasedContributions MeshLevel & mesh, arrayView1d< string const > const & regionNames ) { - NodeManager const & nodeManager = mesh.getNodeManager(); - - string const dofKey = dofManager.getKey( fields::solidMechanics::totalDisplacement::key() ); - arrayView1d< globalIndex const > const & dispDofNumber = nodeManager.getReference< globalIndex_array >( dofKey ); - - string const pDofKey = dofManager.getKey( SinglePhaseBase::viewKeyStruct::elemDofFieldString() ); - - real64 const gravityVectorData[3] = LVARRAY_TENSOROPS_INIT_LOCAL_3( gravityVector() ); - - poromechanicsKernels::SinglePhasePoromechanicsKernelFactory kernelFactory( dispDofNumber, - dofManager.rankOffset(), - localMatrix, - localRhs, - gravityVectorData, - pDofKey, - FlowSolverBase::viewKeyStruct::fluidNamesString() ); - - // 1. Cell-based contributions to Kuu, Kup, Kpu, Kpp blocks - finiteElement:: - regionBasedKernelApplication< parallelDevicePolicy< >, - constitutive::PorousSolid< ElasticIsotropic >, - CellElementSubRegion >( mesh, - regionNames, - contactSolver()->getSolidSolver()->getDiscretizationName(), - SinglePhasePoromechanics::viewKeyStruct::porousMaterialNamesString(), - kernelFactory ); + string const flowDofKey = dofManager.getKey( SinglePhaseBase::viewKeyStruct::elemDofFieldString() ); + if( m_isThermal ) + { + assemblyLaunch< constitutive::PorousSolid< ElasticIsotropic >, // TODO: change once there is a cmake solution + thermalPoromechanicsKernels::ThermalSinglePhasePoromechanicsKernelFactory >( mesh, + dofManager, + regionNames, + SinglePhasePoromechanics::viewKeyStruct::porousMaterialNamesString(), + localMatrix, + localRhs, + flowDofKey, + FlowSolverBase::viewKeyStruct::fluidNamesString() ); + } + else + { + assemblyLaunch< constitutive::PorousSolid< ElasticIsotropic >, + poromechanicsKernels::SinglePhasePoromechanicsKernelFactory >( mesh, + dofManager, + regionNames, + SinglePhasePoromechanics::viewKeyStruct::porousMaterialNamesString(), + localMatrix, + localRhs, + flowDofKey, + FlowSolverBase::viewKeyStruct::fluidNamesString() ); + } mesh.getElemManager().forElementSubRegions< FaceElementSubRegion >( regionNames, [&]( localIndex const, FaceElementSubRegion const & subRegion ) @@ -720,6 +731,26 @@ void SinglePhasePoromechanicsConformingFractures::updateState( DomainPartition & Base::updateState( domain ); updateHydraulicApertureAndFracturePermeability( domain ); + + forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&] ( string const &, + MeshLevel & mesh, + arrayView1d< string const > const & regionNames ) + { + ElementRegionManager & elemManager = mesh.getElemManager(); + + elemManager.forElementSubRegions< FaceElementSubRegion >( regionNames, + [&]( localIndex const, + FaceElementSubRegion & subRegion ) + { + // update fluid model + poromechanicsSolver()->flowSolver()->updateFluidState( subRegion ); + if( m_isThermal ) + { + // update solid internal energy + poromechanicsSolver()->flowSolver()->updateSolidInternalEnergyModel( subRegion ); + } + } ); + } ); } void SinglePhasePoromechanicsConformingFractures::updateHydraulicApertureAndFracturePermeability( DomainPartition & domain ) diff --git a/src/coreComponents/physicsSolvers/multiphysics/SinglePhasePoromechanicsConformingFractures.hpp b/src/coreComponents/physicsSolvers/multiphysics/SinglePhasePoromechanicsConformingFractures.hpp index af2fe749d89..af47c3c3660 100644 --- a/src/coreComponents/physicsSolvers/multiphysics/SinglePhasePoromechanicsConformingFractures.hpp +++ b/src/coreComponents/physicsSolvers/multiphysics/SinglePhasePoromechanicsConformingFractures.hpp @@ -123,6 +123,12 @@ class SinglePhasePoromechanicsConformingFractures : public CoupledSolver< Single /**@}*/ + struct viewKeyStruct : Base::viewKeyStruct + { + /// Flag to indicate that the simulation is thermal + constexpr static char const * isThermalString() { return "isThermal"; } + }; + private: void assembleCellBasedContributions( real64 const time_n, @@ -187,6 +193,18 @@ class SinglePhasePoromechanicsConformingFractures : public CoupledSolver< Single DofManager const & dofManager, CRSMatrix< real64, globalIndex > & localMatrix ); + + template< typename CONSTITUTIVE_BASE, + typename KERNEL_WRAPPER, + typename ... PARAMS > + real64 assemblyLaunch( MeshLevel & mesh, + DofManager const & dofManager, + arrayView1d< string const > const & regionNames, + string const & materialNamesString, + CRSMatrixView< real64, globalIndex const > const & localMatrix, + arrayView1d< real64 > const & localRhs, + PARAMS && ... params ); + /** * @brief * @@ -213,8 +231,49 @@ class SinglePhasePoromechanicsConformingFractures : public CoupledSolver< Single std::unique_ptr< CRSMatrix< real64, localIndex > > m_derivativeFluxResidual_dAperture; string const m_pressureKey = SinglePhaseBase::viewKeyStruct::elemDofFieldString(); + + /// flag to determine whether or not this is a thermal simulation + integer m_isThermal; }; +template< typename CONSTITUTIVE_BASE, + typename KERNEL_WRAPPER, + typename ... PARAMS > +real64 SinglePhasePoromechanicsConformingFractures::assemblyLaunch( MeshLevel & mesh, + DofManager const & dofManager, + arrayView1d< string const > const & regionNames, + string const & materialNamesString, + CRSMatrixView< real64, globalIndex const > const & localMatrix, + arrayView1d< real64 > const & localRhs, + PARAMS && ... params ) +{ + GEOS_MARK_FUNCTION; + + NodeManager const & nodeManager = mesh.getNodeManager(); + + string const dofKey = dofManager.getKey( fields::solidMechanics::totalDisplacement::key() ); + arrayView1d< globalIndex const > const & dispDofNumber = nodeManager.getReference< globalIndex_array >( dofKey ); + + real64 const gravityVectorData[3] = LVARRAY_TENSOROPS_INIT_LOCAL_3( gravityVector() ); + + KERNEL_WRAPPER kernelWrapper( dispDofNumber, + dofManager.rankOffset(), + localMatrix, + localRhs, + gravityVectorData, + std::forward< PARAMS >( params )... ); + + return finiteElement:: + regionBasedKernelApplication< parallelDevicePolicy< >, + CONSTITUTIVE_BASE, + CellElementSubRegion >( mesh, + regionNames, + contactSolver()->getSolidSolver()->getDiscretizationName(), + materialNamesString, + kernelWrapper ); +} + + } /* namespace geos */ #endif /* GEOS_PHYSICSSOLVERS_MULTIPHYSICS_SINGLEPHASEPOROMECHANICSCONFORMINGFRACTURES_HPP_ */ diff --git a/src/coreComponents/physicsSolvers/multiphysics/SinglePhasePoromechanicsEmbeddedFractures.cpp b/src/coreComponents/physicsSolvers/multiphysics/SinglePhasePoromechanicsEmbeddedFractures.cpp index d4bfc93f6eb..10e8e0dbd20 100644 --- a/src/coreComponents/physicsSolvers/multiphysics/SinglePhasePoromechanicsEmbeddedFractures.cpp +++ b/src/coreComponents/physicsSolvers/multiphysics/SinglePhasePoromechanicsEmbeddedFractures.cpp @@ -17,14 +17,14 @@ */ #include "SinglePhasePoromechanicsEmbeddedFractures.hpp" - #include "constitutive/contact/ContactSelector.hpp" #include "constitutive/fluid/singlefluid/SingleFluidBase.hpp" #include "physicsSolvers/contact/SolidMechanicsEFEMKernelsHelper.hpp" -#include "physicsSolvers/contact/SolidMechanicsEmbeddedFractures.hpp" #include "physicsSolvers/fluidFlow/SinglePhaseBase.hpp" #include "physicsSolvers/multiphysics/poromechanicsKernels/SinglePhasePoromechanicsEFEM.hpp" #include "physicsSolvers/multiphysics/poromechanicsKernels/SinglePhasePoromechanics.hpp" +#include "physicsSolvers/multiphysics/poromechanicsKernels/ThermalSinglePhasePoromechanics.hpp" +#include "physicsSolvers/multiphysics/poromechanicsKernels/ThermalSinglePhasePoromechanicsEFEM.hpp" #include "physicsSolvers/solidMechanics/SolidMechanicsLagrangianFEM.hpp" #include "physicsSolvers/solidMechanics/SolidMechanicsFields.hpp" @@ -85,6 +85,8 @@ void SinglePhasePoromechanicsEmbeddedFractures::registerDataOnMesh( dataReposito void SinglePhasePoromechanicsEmbeddedFractures::initializePostInitialConditionsPreSubGroups() { + SinglePhasePoromechanics::initializePostInitialConditionsPreSubGroups(); + updateState( this->getGroupByPath< DomainPartition >( "/Problem/domain" ) ); } @@ -194,7 +196,7 @@ void SinglePhasePoromechanicsEmbeddedFractures::addCouplingNumNonzeros( DomainPa ElementRegionManager const & elemManager = mesh.getElemManager(); string const jumpDofKey = dofManager.getKey( fields::contact::dispJump::key() ); - string const pressureDofKey = dofManager.getKey( SinglePhaseBase::viewKeyStruct::elemDofFieldString() ); + string const flowDofKey = dofManager.getKey( SinglePhaseBase::viewKeyStruct::elemDofFieldString() ); globalIndex const rankOffset = dofManager.rankOffset(); @@ -216,7 +218,7 @@ void SinglePhasePoromechanicsEmbeddedFractures::addCouplingNumNonzeros( DomainPa getSubRegion< CellElementSubRegion >( embeddedSurfacesToCells.m_toElementSubRegion[k][0] ); arrayView1d< globalIndex const > const & - pressureDofNumber = subRegion.getReference< globalIndex_array >( pressureDofKey ); + flowDofNumber = subRegion.getReference< globalIndex_array >( flowDofKey ); localIndex cellElementIndex = embeddedSurfacesToCells.m_toElementIndex[k][0]; @@ -231,7 +233,7 @@ void SinglePhasePoromechanicsEmbeddedFractures::addCouplingNumNonzeros( DomainPa rowLengths[localRow + i] += 1; } - localIndex const localPressureRow = LvArray::integerConversion< localIndex >( pressureDofNumber[cellElementIndex] - rankOffset ); + localIndex const localPressureRow = LvArray::integerConversion< localIndex >( flowDofNumber[cellElementIndex] - rankOffset ); GEOS_ASSERT_GE( localPressureRow, 0 ); GEOS_ASSERT_GE( rowLengths.size(), localPressureRow + embeddedSurfaceSubRegion.numOfJumpEnrichments() ); @@ -258,11 +260,11 @@ void SinglePhasePoromechanicsEmbeddedFractures::addCouplingNumNonzeros( DomainPa elemManager.getRegion( seri[iconn][0] ).getSubRegion< EmbeddedSurfaceSubRegion >( sesri[iconn][0] ); arrayView1d< globalIndex const > const & - pressureDofNumber = embeddedSurfaceSubRegion.getReference< globalIndex_array >( pressureDofKey ); + flowDofNumber = embeddedSurfaceSubRegion.getReference< globalIndex_array >( flowDofKey ); for( localIndex k0=0; k0= 0 && rowNumber < rowLengths.size() ) @@ -274,6 +276,11 @@ void SinglePhasePoromechanicsEmbeddedFractures::addCouplingNumNonzeros( DomainPa if( k1 != k0 ) { rowLengths[ rowNumber ] += embeddedSurfaceSubRegion.numOfJumpEnrichments(); // number of jump enrichments. + if( m_isThermal ) + { + // energy flux is also coupled to dispJump + rowLengths[ rowNumber + 1 ] += embeddedSurfaceSubRegion.numOfJumpEnrichments(); + } } } } @@ -407,72 +414,39 @@ void SinglePhasePoromechanicsEmbeddedFractures::assembleSystem( real64 const tim arrayView1d< string const > const & regionNames ) { - NodeManager const & nodeManager = mesh.getNodeManager(); - ElementRegionManager const & elemManager = mesh.getElemManager(); - SurfaceElementRegion const & region = elemManager.getRegion< SurfaceElementRegion >( m_fracturesSolver->getFractureRegionName() ); - EmbeddedSurfaceSubRegion const & subRegion = region.getSubRegion< EmbeddedSurfaceSubRegion >( 0 ); - - string const dofKey = dofManager.getKey( fields::solidMechanics::totalDisplacement::key() ); - string const jumpDofKey = dofManager.getKey( fields::contact::dispJump::key() ); - - arrayView1d< globalIndex const > const & dispDofNumber = nodeManager.getReference< globalIndex_array >( dofKey ); - arrayView1d< globalIndex const > const & jumpDofNumber = subRegion.getReference< globalIndex_array >( jumpDofKey ); - - string const pDofKey = dofManager.getKey( SinglePhaseBase::viewKeyStruct::elemDofFieldString() ); - - real64 const gravityVectorData[3] = LVARRAY_TENSOROPS_INIT_LOCAL_3( gravityVector() ); - - - // 1. Cell-based contributions of standard poroelasticity - poromechanicsKernels::SinglePhasePoromechanicsKernelFactory - kernelFactory( dispDofNumber, - dofManager.rankOffset(), - localMatrix, - localRhs, - gravityVectorData, - pDofKey, - FlowSolverBase::viewKeyStruct::fluidNamesString() ); - - solidMechanicsSolver()->getMaxForce() = - finiteElement:: - regionBasedKernelApplication< parallelDevicePolicy< >, - constitutive::PorousSolid< ElasticIsotropic >, - CellElementSubRegion >( mesh, - regionNames, - solidMechanicsSolver()->getDiscretizationName(), - viewKeyStruct::porousMaterialNamesString(), - kernelFactory ); - - // 2. Add EFEM poroelastic contribution - poromechanicsEFEMKernels::SinglePhaseKernelFactory EFEMkernelFactory( subRegion, - dispDofNumber, - jumpDofNumber, - pDofKey, - dofManager.rankOffset(), - localMatrix, - localRhs, - gravityVectorData, - FlowSolverBase::viewKeyStruct::fluidNamesString() ); - - real64 maxTraction = - finiteElement:: - regionBasedKernelApplication< parallelDevicePolicy< >, - constitutive::PorousSolid< ElasticIsotropic >, - CellElementSubRegion >( mesh, - regionNames, - solidMechanicsSolver()->getDiscretizationName(), - viewKeyStruct::porousMaterialNamesString(), - EFEMkernelFactory ); - - GEOS_UNUSED_VAR( maxTraction ); + if( m_isThermal ) + { + solidMechanicsSolver()->getMaxForce() = + assemblyLaunch< constitutive::PorousSolid< ElasticIsotropic >, // TODO: change once there is a cmake solution + thermalPoromechanicsKernels::ThermalSinglePhasePoromechanicsKernelFactory, + thermoPoromechanicsEFEMKernels::ThermalSinglePhasePoromechanicsEFEMKernelFactory >( mesh, + dofManager, + regionNames, + SinglePhasePoromechanics::viewKeyStruct::porousMaterialNamesString(), + localMatrix, + localRhs ); + } + else + { + solidMechanicsSolver()->getMaxForce() = + assemblyLaunch< constitutive::PorousSolid< ElasticIsotropic >, + poromechanicsKernels::SinglePhasePoromechanicsKernelFactory, + poromechanicsEFEMKernels::SinglePhaseKernelFactory >( mesh, + dofManager, + regionNames, + SinglePhasePoromechanics::viewKeyStruct::porousMaterialNamesString(), + localMatrix, + localRhs ); + } // 3. Assemble poroelastic fluxes and all derivatives - flowSolver()->assemblePoroelasticFluxTerms( time_n, dt, - domain, - dofManager, - localMatrix, - localRhs, - jumpDofKey ); + string const jumpDofKey = dofManager.getKey( fields::contact::dispJump::key() ); + flowSolver()->assembleEDFMFluxTerms( time_n, dt, + domain, + dofManager, + localMatrix, + localRhs, + jumpDofKey ); } ); @@ -623,7 +597,11 @@ void SinglePhasePoromechanicsEmbeddedFractures::updateState( DomainPartition & d flowSolver()->updatePorosityAndPermeability( subRegion ); // update fluid model flowSolver()->updateFluidState( subRegion ); - + if( m_isThermal ) + { + // update solid internal energy + flowSolver()->updateSolidInternalEnergyModel( subRegion ); + } } ); } ); } diff --git a/src/coreComponents/physicsSolvers/multiphysics/SinglePhasePoromechanicsEmbeddedFractures.hpp b/src/coreComponents/physicsSolvers/multiphysics/SinglePhasePoromechanicsEmbeddedFractures.hpp index 7d17adb573c..e95af7cc1a0 100644 --- a/src/coreComponents/physicsSolvers/multiphysics/SinglePhasePoromechanicsEmbeddedFractures.hpp +++ b/src/coreComponents/physicsSolvers/multiphysics/SinglePhasePoromechanicsEmbeddedFractures.hpp @@ -20,12 +20,11 @@ #define GEOS_PHYSICSSOLVERS_MULTIPHYSICS_SINGLEPHASEPOROMECHANICSEMBEDDEDFRACTURES_HPP_ #include "physicsSolvers/multiphysics/SinglePhasePoromechanics.hpp" +#include "physicsSolvers/contact/SolidMechanicsEmbeddedFractures.hpp" namespace geos { -class SolidMechanicsEmbeddedFractures; - class SinglePhasePoromechanicsEmbeddedFractures : public SinglePhasePoromechanics { public: @@ -135,6 +134,16 @@ class SinglePhasePoromechanicsEmbeddedFractures : public SinglePhasePoromechanic private: + template< typename CONSTITUTIVE_BASE, + typename KERNEL_WRAPPER, + typename EFEM_KERNEL_WRAPPER > + real64 assemblyLaunch( MeshLevel & mesh, + DofManager const & dofManager, + arrayView1d< string const > const & regionNames, + string const & materialNamesString, + CRSMatrixView< real64, globalIndex const > const & localMatrix, + arrayView1d< real64 > const & localRhs ); + string m_fracturesSolverName; SolidMechanicsEmbeddedFractures * m_fracturesSolver; @@ -142,6 +151,74 @@ class SinglePhasePoromechanicsEmbeddedFractures : public SinglePhasePoromechanic }; +template< typename CONSTITUTIVE_BASE, + typename KERNEL_WRAPPER, + typename EFEM_KERNEL_WRAPPER > +real64 SinglePhasePoromechanicsEmbeddedFractures::assemblyLaunch( MeshLevel & mesh, + DofManager const & dofManager, + arrayView1d< string const > const & regionNames, + string const & materialNamesString, + CRSMatrixView< real64, globalIndex const > const & localMatrix, + arrayView1d< real64 > const & localRhs ) +{ + GEOS_MARK_FUNCTION; + + NodeManager const & nodeManager = mesh.getNodeManager(); + + ElementRegionManager const & elemManager = mesh.getElemManager(); + SurfaceElementRegion const & region = elemManager.getRegion< SurfaceElementRegion >( m_fracturesSolver->getFractureRegionName() ); + EmbeddedSurfaceSubRegion const & subRegion = region.getSubRegion< EmbeddedSurfaceSubRegion >( 0 ); + + string const dofKey = dofManager.getKey( fields::solidMechanics::totalDisplacement::key() ); + string const jumpDofKey = dofManager.getKey( fields::contact::dispJump::key() ); + arrayView1d< globalIndex const > const & dispDofNumber = nodeManager.getReference< globalIndex_array >( dofKey ); + arrayView1d< globalIndex const > const & jumpDofNumber = subRegion.getReference< globalIndex_array >( jumpDofKey ); + + string const flowDofKey = dofManager.getKey( SinglePhaseBase::viewKeyStruct::elemDofFieldString() ); + + real64 const gravityVectorData[3] = LVARRAY_TENSOROPS_INIT_LOCAL_3( gravityVector() ); + + KERNEL_WRAPPER kernelWrapper( dispDofNumber, + dofManager.rankOffset(), + localMatrix, + localRhs, + gravityVectorData, + flowDofKey, + FlowSolverBase::viewKeyStruct::fluidNamesString() ); + + real64 const maxForce = + finiteElement:: + regionBasedKernelApplication< parallelDevicePolicy< >, + CONSTITUTIVE_BASE, + CellElementSubRegion >( mesh, + regionNames, + m_fracturesSolver->getSolidSolver()->getDiscretizationName(), + materialNamesString, + kernelWrapper ); + + EFEM_KERNEL_WRAPPER EFEMkernelWrapper( subRegion, + dispDofNumber, + jumpDofNumber, + flowDofKey, + dofManager.rankOffset(), + localMatrix, + localRhs, + gravityVectorData, + FlowSolverBase::viewKeyStruct::fluidNamesString() ); + + finiteElement:: + regionBasedKernelApplication< parallelDevicePolicy< >, + CONSTITUTIVE_BASE, + CellElementSubRegion >( mesh, + regionNames, + m_fracturesSolver->getSolidSolver()->getDiscretizationName(), + materialNamesString, + EFEMkernelWrapper ); + + return maxForce; + +} + } /* namespace geos */ #endif /* GEOS_PHYSICSSOLVERS_MULTIPHYSICS_SINGLEPHASEPOROMECHANICSEMBEDDEDFRACTURES_HPP_ */ diff --git a/src/coreComponents/physicsSolvers/multiphysics/SinglePhasePoromechanicsFluxKernels.cpp b/src/coreComponents/physicsSolvers/multiphysics/SinglePhasePoromechanicsFluxKernels.cpp deleted file mode 100644 index f844d7f7aa5..00000000000 --- a/src/coreComponents/physicsSolvers/multiphysics/SinglePhasePoromechanicsFluxKernels.cpp +++ /dev/null @@ -1,669 +0,0 @@ -/* - * ------------------------------------------------------------------------------------------------------------ - * SPDX-License-Identifier: LGPL-2.1-only - * - * Copyright (c) 2018-2020 Lawrence Livermore National Security LLC - * Copyright (c) 2018-2020 The Board of Trustees of the Leland Stanford Junior University - * Copyright (c) 2018-2020 TotalEnergies - * Copyright (c) 2019- GEOSX Contributors - * All rights reserved - * - * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. - * ------------------------------------------------------------------------------------------------------------ - */ - -/** - * @file SinglePhasePoromechanicsFluxKernels.cpp - */ - -#include "physicsSolvers/fluidFlow/SinglePhaseFVMKernels.hpp" -#include "physicsSolvers/fluidFlow/FluxKernelsHelper.hpp" -#include "SinglePhasePoromechanicsFluxKernels.hpp" - -namespace geos -{ - -namespace singlePhasePoromechanicsFluxKernels -{ - -using namespace fluxKernelsHelper; - -/******************************** EmbeddedSurfaceFluxKernel ********************************/ - -template<> -void EmbeddedSurfaceFluxKernel:: - launch< CellElementStencilTPFAWrapper >( CellElementStencilTPFAWrapper const & stencilWrapper, - real64 const dt, - globalIndex const rankOffset, - ElementViewConst< arrayView1d< globalIndex const > > const & pressureDofNumber, - ElementViewConst< arrayView1d< globalIndex const > > const & jumpDofNumber, - ElementViewConst< arrayView1d< integer const > > const & ghostRank, - ElementViewConst< arrayView1d< real64 const > > const & pres, - ElementViewConst< arrayView1d< real64 const > > const & gravCoef, - ElementViewConst< arrayView2d< real64 const > > const & dens, - ElementViewConst< arrayView2d< real64 const > > const & dDens_dPres, - ElementViewConst< arrayView1d< real64 const > > const & mob, - ElementViewConst< arrayView1d< real64 const > > const & dMob_dPres, - ElementViewConst< arrayView3d< real64 const > > const & permeability, - ElementViewConst< arrayView3d< real64 const > > const & dPerm_dPres, - ElementViewConst< arrayView4d< real64 const > > const & dPerm_dDispJump, - CRSMatrixView< real64, globalIndex const > const & localMatrix, - arrayView1d< real64 > const & localRhs ) -{ - GEOS_UNUSED_VAR( jumpDofNumber ); - GEOS_UNUSED_VAR( dPerm_dDispJump ); - - singlePhaseFVMKernels::FluxKernel::launch( stencilWrapper, - dt, - rankOffset, - pressureDofNumber, - ghostRank, - pres, - gravCoef, - dens, - dDens_dPres, - mob, - dMob_dPres, - permeability, - dPerm_dPres, - localMatrix, - localRhs ); -} - -template<> -void EmbeddedSurfaceFluxKernel:: - launch< EmbeddedSurfaceToCellStencilWrapper >( EmbeddedSurfaceToCellStencilWrapper const & stencilWrapper, - real64 const dt, - globalIndex const rankOffset, - ElementViewConst< arrayView1d< globalIndex const > > const & pressureDofNumber, - ElementViewConst< arrayView1d< globalIndex const > > const & jumpDofNumber, - ElementViewConst< arrayView1d< integer const > > const & ghostRank, - ElementViewConst< arrayView1d< real64 const > > const & pres, - ElementViewConst< arrayView1d< real64 const > > const & gravCoef, - ElementViewConst< arrayView2d< real64 const > > const & dens, - ElementViewConst< arrayView2d< real64 const > > const & dDens_dPres, - ElementViewConst< arrayView1d< real64 const > > const & mob, - ElementViewConst< arrayView1d< real64 const > > const & dMob_dPres, - ElementViewConst< arrayView3d< real64 const > > const & permeability, - ElementViewConst< arrayView3d< real64 const > > const & dPerm_dPres, - ElementViewConst< arrayView4d< real64 const > > const & dPerm_dDispJump, - CRSMatrixView< real64, globalIndex const > const & localMatrix, - arrayView1d< real64 > const & localRhs ) -{ - GEOS_UNUSED_VAR( jumpDofNumber ); - GEOS_UNUSED_VAR( dPerm_dDispJump ); - - singlePhaseFVMKernels::FluxKernel::launch( stencilWrapper, - dt, - rankOffset, - pressureDofNumber, - ghostRank, - pres, - gravCoef, - dens, - dDens_dPres, - mob, - dMob_dPres, - permeability, - dPerm_dPres, - localMatrix, - localRhs ); -} - - -template<> -void EmbeddedSurfaceFluxKernel:: - launch< SurfaceElementStencilWrapper >( SurfaceElementStencilWrapper const & stencilWrapper, - real64 const dt, - globalIndex const rankOffset, - ElementViewConst< arrayView1d< globalIndex const > > const & pressureDofNumber, - ElementViewConst< arrayView1d< globalIndex const > > const & jumpDofNumber, - ElementViewConst< arrayView1d< integer const > > const & ghostRank, - ElementViewConst< arrayView1d< real64 const > > const & pres, - ElementViewConst< arrayView1d< real64 const > > const & gravCoef, - ElementViewConst< arrayView2d< real64 const > > const & dens, - ElementViewConst< arrayView2d< real64 const > > const & dDens_dPres, - ElementViewConst< arrayView1d< real64 const > > const & mob, - ElementViewConst< arrayView1d< real64 const > > const & dMob_dPres, - ElementViewConst< arrayView3d< real64 const > > const & permeability, - ElementViewConst< arrayView3d< real64 const > > const & dPerm_dPres, - ElementViewConst< arrayView4d< real64 const > > const & dPerm_dDispJump, - CRSMatrixView< real64, globalIndex const > const & localMatrix, - arrayView1d< real64 > const & localRhs ) -{ - constexpr localIndex MAX_NUM_FLUX_ELEMS = SurfaceElementStencilWrapper::maxNumPointsInFlux; - constexpr localIndex maxStencilSize = SurfaceElementStencilWrapper::maxStencilSize; - - typename SurfaceElementStencilWrapper::IndexContainerViewConstType const & seri = stencilWrapper.getElementRegionIndices(); - typename SurfaceElementStencilWrapper::IndexContainerViewConstType const & sesri = stencilWrapper.getElementSubRegionIndices(); - typename SurfaceElementStencilWrapper::IndexContainerViewConstType const & sei = stencilWrapper.getElementIndices(); - - forAll< parallelDevicePolicy<> >( stencilWrapper.size(), [=] GEOS_HOST_DEVICE ( localIndex const iconn ) - { - localIndex const stencilSize = stencilWrapper.stencilSize( iconn ); - localIndex const numFluxElems = stencilWrapper.numPointsInFlux( iconn ); - localIndex const numDofs = stencilSize * 4;// pressure and normal jump (3) - - // working arrays - stackArray1d< globalIndex, MAX_NUM_FLUX_ELEMS * 4 > dofColIndices( numDofs ); - stackArray1d< real64, MAX_NUM_FLUX_ELEMS > localFlux( numFluxElems ); - stackArray2d< real64, MAX_NUM_FLUX_ELEMS * maxStencilSize * 4 > localFluxJacobian( numFluxElems, numDofs ); - - // compute transmissibility - real64 transmissibility[SurfaceElementStencilWrapper::maxNumConnections][2]{}; - real64 dTrans_dPres[SurfaceElementStencilWrapper::maxNumConnections][2]{}; - real64 dTrans_dDispJump[SurfaceElementStencilWrapper::maxNumConnections][2][3]{}; // make sure that this is initialized! - - stencilWrapper.computeWeights( iconn, - permeability, - dPerm_dPres, - dPerm_dDispJump, - transmissibility, - dTrans_dPres, - dTrans_dDispJump ); - - compute( stencilSize, - seri[iconn], - sesri[iconn], - sei[iconn], - transmissibility, - dTrans_dPres, - dTrans_dDispJump, - pres, - gravCoef, - dens, - dDens_dPres, - mob, - dMob_dPres, - dt, - localFlux, - localFluxJacobian ); - - // extract DOF numbers - for( localIndex i = 0; i < stencilSize; ++i ) - { - localIndex localDofIndex = 4 * i; - dofColIndices[ localDofIndex ] = pressureDofNumber[seri( iconn, i )][sesri( iconn, i )][sei( iconn, i )]; - dofColIndices[ localDofIndex + 1 ] = jumpDofNumber[seri( iconn, i )][sesri( iconn, i )][sei( iconn, i )]; - dofColIndices[ localDofIndex + 2 ] = jumpDofNumber[seri( iconn, i )][sesri( iconn, i )][sei( iconn, i )] + 1; - dofColIndices[ localDofIndex + 3 ] = jumpDofNumber[seri( iconn, i )][sesri( iconn, i )][sei( iconn, i )] + 2; - } - - for( localIndex i = 0; i < numFluxElems; ++i ) - { - - if( ghostRank[seri( iconn, i )][sesri( iconn, i )][sei( iconn, i )] < 0 ) - { - globalIndex const globalRow = pressureDofNumber[seri( iconn, i )][sesri( iconn, i )][sei( iconn, i )]; - localIndex const localRow = LvArray::integerConversion< localIndex >( globalRow - rankOffset ); - GEOS_ASSERT_GE( localRow, 0 ); - GEOS_ASSERT_GT( localMatrix.numRows(), localRow ); - - RAJA::atomicAdd( parallelDeviceAtomic{}, &localRhs[localRow], localFlux[i] ); - localMatrix.addToRowBinarySearchUnsorted< parallelDeviceAtomic >( localRow, - dofColIndices.data(), - localFluxJacobian[i].dataIfContiguous(), - numDofs ); - - } - } - } ); -} - -template< localIndex MAX_NUM_CONNECTIONS > -GEOS_HOST_DEVICE -void -EmbeddedSurfaceFluxKernel:: - compute( localIndex const numFluxElems, - arraySlice1d< localIndex const > const & seri, - arraySlice1d< localIndex const > const & sesri, - arraySlice1d< localIndex const > const & sei, - real64 const (&transmissibility)[MAX_NUM_CONNECTIONS][2], - real64 const (&dTrans_dPres)[MAX_NUM_CONNECTIONS][2], - real64 const (&dTrans_dDispJump)[MAX_NUM_CONNECTIONS][2][3], - ElementViewConst< arrayView1d< real64 const > > const & pres, - ElementViewConst< arrayView1d< real64 const > > const & gravCoef, - ElementViewConst< arrayView2d< real64 const > > const & dens, - ElementViewConst< arrayView2d< real64 const > > const & dDens_dPres, - ElementViewConst< arrayView1d< real64 const > > const & mob, - ElementViewConst< arrayView1d< real64 const > > const & dMob_dPres, - real64 const dt, - arraySlice1d< real64 > const & flux, - arraySlice2d< real64 > const & fluxJacobian ) -{ - GEOS_UNUSED_VAR( numFluxElems ); - - real64 fluxVal = 0.0; - real64 dFlux_dTrans = 0.0; - real64 trans[2] = {transmissibility[0][0], transmissibility[0][1]}; - real64 dTrans[2] = { dTrans_dPres[0][0], dTrans_dPres[0][1] }; - real64 dFlux_dP[2] = {0.0, 0.0}; - localIndex const regionIndex[2] = {seri[0], seri[1]}; - localIndex const subRegionIndex[2] = {sesri[0], sesri[1]}; - localIndex const elementIndex[2] = {sei[0], sei[1]}; - - - computeSinglePhaseFlux( regionIndex, subRegionIndex, elementIndex, - trans, - dTrans, - pres, - gravCoef, - dens, - dDens_dPres, - mob, - dMob_dPres, - fluxVal, - dFlux_dP, - dFlux_dTrans ); - - - - // populate local flux vector and derivatives - flux[0] = dt * fluxVal; - flux[1] = -dt * fluxVal; - - real64 dFlux_dDispJump[2][3] = {{0.0, 0.0, 0.0}, {0.0, 0.0, 0.0}}; - for( localIndex i=0; i < 3; i++ ) - { - dFlux_dDispJump[0][i] = dt * dFlux_dTrans * dTrans_dDispJump[0][0][i]; - dFlux_dDispJump[1][i] = -dt * dFlux_dTrans * dTrans_dDispJump[0][1][i]; - } - for( localIndex ke = 0; ke < 2; ++ke ) - { - localIndex const dofIndex = 4*ke; - - fluxJacobian[0][dofIndex] = dt * dFlux_dP[ke]; - fluxJacobian[0][dofIndex+1] = dt * dFlux_dDispJump[ke][0]; - fluxJacobian[0][dofIndex+2] = dt * dFlux_dDispJump[ke][1]; - fluxJacobian[0][dofIndex+3] = dt * dFlux_dDispJump[ke][2]; - - fluxJacobian[1][dofIndex] = -dt * dFlux_dP[ke]; - fluxJacobian[1][dofIndex+1] = -dt * dFlux_dDispJump[ke][0]; - fluxJacobian[1][dofIndex+2] = -dt * dFlux_dDispJump[ke][1]; - fluxJacobian[1][dofIndex+3] = -dt * dFlux_dDispJump[ke][2]; - } -} - -/******************************** FaceElementFluxKernel ********************************/ - -template<> -void FaceElementFluxKernel:: - launch< CellElementStencilTPFAWrapper >( CellElementStencilTPFAWrapper const & stencilWrapper, - real64 const dt, - globalIndex const rankOffset, - ElementViewConst< arrayView1d< globalIndex const > > const & pressureDofNumber, - ElementViewConst< arrayView1d< integer const > > const & ghostRank, - ElementViewConst< arrayView1d< real64 const > > const & pres, - ElementViewConst< arrayView1d< real64 const > > const & gravCoef, - ElementViewConst< arrayView2d< real64 const > > const & dens, - ElementViewConst< arrayView2d< real64 const > > const & dDens_dPres, - ElementViewConst< arrayView1d< real64 const > > const & mob, - ElementViewConst< arrayView1d< real64 const > > const & dMob_dPres, - ElementViewConst< arrayView3d< real64 const > > const & permeability, - ElementViewConst< arrayView3d< real64 const > > const & dPerm_dPres, - ElementViewConst< arrayView4d< real64 const > > const & dPerm_dDispJump, - CRSMatrixView< real64, globalIndex const > const & localMatrix, - arrayView1d< real64 > const & localRhs, - CRSMatrixView< real64, localIndex const > const & dR_dAper ) -{ - GEOS_UNUSED_VAR( dPerm_dDispJump ); - GEOS_UNUSED_VAR( dR_dAper ); - - singlePhaseFVMKernels::FluxKernel::launch( stencilWrapper, - dt, - rankOffset, - pressureDofNumber, - ghostRank, - pres, - gravCoef, - dens, - dDens_dPres, - mob, - dMob_dPres, - permeability, - dPerm_dPres, - localMatrix, - localRhs ); -} - -template<> -void FaceElementFluxKernel:: - launch< FaceElementToCellStencilWrapper >( FaceElementToCellStencilWrapper const & stencilWrapper, - real64 const dt, - globalIndex const rankOffset, - ElementViewConst< arrayView1d< globalIndex const > > const & pressureDofNumber, - ElementViewConst< arrayView1d< integer const > > const & ghostRank, - ElementViewConst< arrayView1d< real64 const > > const & pres, - ElementViewConst< arrayView1d< real64 const > > const & gravCoef, - ElementViewConst< arrayView2d< real64 const > > const & dens, - ElementViewConst< arrayView2d< real64 const > > const & dDens_dPres, - ElementViewConst< arrayView1d< real64 const > > const & mob, - ElementViewConst< arrayView1d< real64 const > > const & dMob_dPres, - ElementViewConst< arrayView3d< real64 const > > const & permeability, - ElementViewConst< arrayView3d< real64 const > > const & dPerm_dPres, - ElementViewConst< arrayView4d< real64 const > > const & dPerm_dDispJump, - CRSMatrixView< real64, globalIndex const > const & localMatrix, - arrayView1d< real64 > const & localRhs, - CRSMatrixView< real64, localIndex const > const & dR_dAper ) -{ - GEOS_UNUSED_VAR( dPerm_dDispJump ); - GEOS_UNUSED_VAR( dR_dAper ); - - singlePhaseFVMKernels::FluxKernel::launch( stencilWrapper, - dt, - rankOffset, - pressureDofNumber, - ghostRank, - pres, - gravCoef, - dens, - dDens_dPres, - mob, - dMob_dPres, - permeability, - dPerm_dPres, - localMatrix, - localRhs ); -} - -template<> -void FaceElementFluxKernel:: - launch< SurfaceElementStencilWrapper >( SurfaceElementStencilWrapper const & stencilWrapper, - real64 const dt, - globalIndex const rankOffset, - ElementViewConst< arrayView1d< globalIndex const > > const & pressureDofNumber, - ElementViewConst< arrayView1d< integer const > > const & ghostRank, - ElementViewConst< arrayView1d< real64 const > > const & pres, - ElementViewConst< arrayView1d< real64 const > > const & gravCoef, - ElementViewConst< arrayView2d< real64 const > > const & dens, - ElementViewConst< arrayView2d< real64 const > > const & dDens_dPres, - ElementViewConst< arrayView1d< real64 const > > const & mob, - ElementViewConst< arrayView1d< real64 const > > const & dMob_dPres, - ElementViewConst< arrayView3d< real64 const > > const & permeability, - ElementViewConst< arrayView3d< real64 const > > const & dPerm_dPres, - ElementViewConst< arrayView4d< real64 const > > const & dPerm_dDispJump, - CRSMatrixView< real64, globalIndex const > const & localMatrix, - arrayView1d< real64 > const & localRhs, - CRSMatrixView< real64, localIndex const > const & dR_dAper ) -{ - constexpr localIndex maxNumFluxElems = SurfaceElementStencilWrapper::maxNumPointsInFlux; - constexpr localIndex maxStencilSize = SurfaceElementStencilWrapper::maxStencilSize; - - typename SurfaceElementStencilWrapper::IndexContainerViewConstType const & seri = stencilWrapper.getElementRegionIndices(); - typename SurfaceElementStencilWrapper::IndexContainerViewConstType const & sesri = stencilWrapper.getElementSubRegionIndices(); - typename SurfaceElementStencilWrapper::IndexContainerViewConstType const & sei = stencilWrapper.getElementIndices(); - - forAll< parallelDevicePolicy<> >( stencilWrapper.size(), [=] GEOS_HOST_DEVICE ( localIndex const iconn ) - { - localIndex const stencilSize = stencilWrapper.stencilSize( iconn ); - localIndex const numFluxElems = stencilWrapper.numPointsInFlux( iconn ); - localIndex const numDofs = stencilSize; // pressures - - // For now, we have to filter out connections for which numElems == 1 in this function and not early on in - // TwoPointFluxApproximation.cpp. - // The reason for keeping the connections numElems == 1 is that the ProppantTransport solver needs these connections to produce correct - // results. - if( numFluxElems > 1 ) - { - // working arrays - stackArray1d< globalIndex, maxNumFluxElems > dofColIndices( numDofs ); - stackArray1d< localIndex, maxNumFluxElems > localColIndices( numFluxElems ); - - stackArray1d< real64, maxNumFluxElems > localFlux( numFluxElems ); - stackArray2d< real64, maxNumFluxElems * maxStencilSize > localFluxJacobian( numFluxElems, numDofs ); - - // need to store this for later use in determining the dFlux_dU terms when using better permeabilty approximations. - stackArray2d< real64, maxNumFluxElems * maxStencilSize > dFlux_dAper( numFluxElems, stencilSize ); - - // compute transmissibility - real64 transmissibility[SurfaceElementStencilWrapper::maxNumConnections][2]{}; - real64 dTrans_dPres[SurfaceElementStencilWrapper::maxNumConnections][2]{}; - real64 dTrans_dDispJump[SurfaceElementStencilWrapper::maxNumConnections][2][3]{}; - - stencilWrapper.computeWeights( iconn, - permeability, - dPerm_dPres, - dPerm_dDispJump, - transmissibility, - dTrans_dPres, - dTrans_dDispJump ); - - compute( stencilSize, - seri[iconn], - sesri[iconn], - sei[iconn], - transmissibility, - dTrans_dPres, - dTrans_dDispJump, - pres, - gravCoef, - dens, - dDens_dPres, - mob, - dMob_dPres, - dt, - localFlux, - localFluxJacobian, - dFlux_dAper ); - - // extract DOF numbers - for( localIndex i = 0; i < numDofs; ++i ) - { - dofColIndices[i] = pressureDofNumber[seri( iconn, i )][sesri( iconn, i )][sei( iconn, i )]; - localColIndices[i] = sei( iconn, i ); - } - - for( localIndex i = 0; i < numFluxElems; ++i ) - { - if( ghostRank[seri( iconn, i )][sesri( iconn, i )][sei( iconn, i )] < 0 ) - { - globalIndex const globalRow = pressureDofNumber[seri( iconn, i )][sesri( iconn, i )][sei( iconn, i )]; - localIndex const localRow = LvArray::integerConversion< localIndex >( globalRow - rankOffset ); - GEOS_ASSERT_GE( localRow, 0 ); - GEOS_ASSERT_GT( localMatrix.numRows(), localRow ); - - RAJA::atomicAdd( parallelDeviceAtomic{}, &localRhs[localRow], localFlux[i] ); - localMatrix.addToRowBinarySearchUnsorted< parallelDeviceAtomic >( localRow, - dofColIndices.data(), - localFluxJacobian[i].dataIfContiguous(), - stencilSize ); - - dR_dAper.addToRowBinarySearch< parallelDeviceAtomic >( sei( iconn, i ), - localColIndices.data(), - dFlux_dAper[i].dataIfContiguous(), - stencilSize ); - } - } - } - } ); - -} - -void FaceElementFluxKernel:: - launch( SurfaceElementStencilWrapper const & stencilWrapper, - real64 const dt, - globalIndex const rankOffset, - ElementViewConst< arrayView1d< globalIndex const > > const & pressureDofNumber, - ElementViewConst< arrayView1d< integer const > > const & ghostRank, - ElementViewConst< arrayView1d< real64 const > > const & pres, - ElementViewConst< arrayView1d< real64 const > > const & gravCoef, - ElementViewConst< arrayView2d< real64 const > > const & dens, - ElementViewConst< arrayView2d< real64 const > > const & dDens_dPres, - ElementViewConst< arrayView1d< real64 const > > const & mob, - ElementViewConst< arrayView1d< real64 const > > const & dMob_dPres, - ElementViewConst< arrayView3d< real64 const > > const & permeability, - ElementViewConst< arrayView3d< real64 const > > const & dPerm_dPres, - ElementViewConst< arrayView4d< real64 const > > const & dPerm_dDispJump, - ElementViewConst< arrayView3d< real64 const > > const & permeabilityMultiplier, - R1Tensor const & gravityVector, - CRSMatrixView< real64, globalIndex const > const & localMatrix, - arrayView1d< real64 > const & localRhs ) -{ - constexpr localIndex maxNumFluxElems = SurfaceElementStencilWrapper::maxNumPointsInFlux; - constexpr localIndex maxStencilSize = SurfaceElementStencilWrapper::maxStencilSize; - constexpr localIndex maxNumConnections = SurfaceElementStencilWrapper::maxNumConnections; - - typename SurfaceElementStencilWrapper::IndexContainerViewConstType const & seri = stencilWrapper.getElementRegionIndices(); - typename SurfaceElementStencilWrapper::IndexContainerViewConstType const & sesri = stencilWrapper.getElementSubRegionIndices(); - typename SurfaceElementStencilWrapper::IndexContainerViewConstType const & sei = stencilWrapper.getElementIndices(); - - forAll< parallelDevicePolicy<> >( stencilWrapper.size(), [=] GEOS_HOST_DEVICE ( localIndex const iconn ) - { - localIndex const stencilSize = stencilWrapper.stencilSize( iconn ); - localIndex const numFluxElems = stencilWrapper.numPointsInFlux( iconn ); - localIndex const numDofs = stencilSize; // pressures - - // For now, we have to filter out connections for which numElems == 1 in this function and not early on in - // TwoPointFluxApproximation.cpp. - // The reason for keeping the connections numElems == 1 is that the ProppantTransport solver needs these connections to produce correct - // results. - if( numFluxElems > 1 ) - { - - // working arrays - stackArray1d< globalIndex, maxNumFluxElems > dofColIndices( numDofs ); - stackArray1d< localIndex, maxNumFluxElems > localColIndices( numFluxElems ); - - stackArray1d< real64, maxNumFluxElems > localFlux( numFluxElems ); - stackArray2d< real64, maxNumFluxElems * maxStencilSize > localFluxJacobian( numFluxElems, numDofs ); - - // need to store this for later use in determining the dFlux_dU terms when using better permeabilty approximations. - stackArray2d< real64, maxNumFluxElems * maxStencilSize > dFlux_dAper( numFluxElems, stencilSize ); - - // compute transmissibility - real64 transmissibility[maxNumConnections][2]{}; - real64 dTrans_dPres[maxNumConnections][2]{}; - real64 dTrans_dDispJump[maxNumConnections][2][3]{}; - GEOS_UNUSED_VAR( dPerm_dPres, dPerm_dDispJump ); - stencilWrapper.computeWeights( iconn, - permeability, - permeabilityMultiplier, - gravityVector, - transmissibility ); - - compute( stencilSize, - seri[iconn], - sesri[iconn], - sei[iconn], - transmissibility, - dTrans_dPres, - dTrans_dDispJump, - pres, - gravCoef, - dens, - dDens_dPres, - mob, - dMob_dPres, - dt, - localFlux, - localFluxJacobian, - dFlux_dAper ); - - // extract DOF numbers - for( localIndex i = 0; i < numDofs; ++i ) - { - dofColIndices[i] = pressureDofNumber[seri( iconn, i )][sesri( iconn, i )][sei( iconn, i )]; - localColIndices[i] = sei( iconn, i ); - } - - for( localIndex i = 0; i < numFluxElems; ++i ) - { - if( ghostRank[seri( iconn, i )][sesri( iconn, i )][sei( iconn, i )] < 0 ) - { - globalIndex const globalRow = pressureDofNumber[seri( iconn, i )][sesri( iconn, i )][sei( iconn, i )]; - localIndex const localRow = LvArray::integerConversion< localIndex >( globalRow - rankOffset ); - GEOS_ASSERT_GE( localRow, 0 ); - GEOS_ASSERT_GT( localMatrix.numRows(), localRow ); - - RAJA::atomicAdd( parallelDeviceAtomic{}, &localRhs[localRow], localFlux[i] ); - localMatrix.addToRowBinarySearchUnsorted< parallelDeviceAtomic >( localRow, - dofColIndices.data(), - localFluxJacobian[i].dataIfContiguous(), - stencilSize ); - - } - } - } - } ); - -} - - - -template< localIndex MAX_NUM_CONNECTIONS > -GEOS_HOST_DEVICE -void -FaceElementFluxKernel::compute( localIndex const numFluxElems, - arraySlice1d< localIndex const > const & seri, - arraySlice1d< localIndex const > const & sesri, - arraySlice1d< localIndex const > const & sei, - real64 const (&transmissibility)[MAX_NUM_CONNECTIONS][2], - real64 const (&dTrans_dPres)[MAX_NUM_CONNECTIONS][2], - real64 const (&dTrans_dDispJump)[MAX_NUM_CONNECTIONS][2][3], - ElementViewConst< arrayView1d< real64 const > > const & pres, - ElementViewConst< arrayView1d< real64 const > > const & gravCoef, - ElementViewConst< arrayView2d< real64 const > > const & dens, - ElementViewConst< arrayView2d< real64 const > > const & dDens_dPres, - ElementViewConst< arrayView1d< real64 const > > const & mob, - ElementViewConst< arrayView1d< real64 const > > const & dMob_dPres, - real64 const dt, - arraySlice1d< real64 > const & flux, - arraySlice2d< real64 > const & fluxJacobian, - arraySlice2d< real64 > const & dFlux_dAperture ) -{ - - localIndex k[2]; - localIndex connectionIndex = 0; - for( k[0]=0; k[0] - using ElementViewConst = ElementRegionManager::ElementViewConst< VIEWTYPE >; - - /** - * @brief launches the kernel to assemble the flux contributions to the linear system. - * @tparam SurfaceElementStencilWrapper The type of the stencil that is being used. - * @param[in] stencil The stencil object. - * @param[in] dt The timestep for the integration step. - * @param[in] dofNumber The dofNumbers for each element - * @param[in] pres The pressures in each element - * @param[in] gravCoef The factor for gravity calculations (g*H) - * @param[in] dens The material density in each element - * @param[in] dDens_dPres The change in material density for each element - * @param[in] mob The fluid mobility in each element - * @param[in] dMob_dPres The derivative of mobility wrt pressure in each element - * @param[in] permeability - * @param[in] dPerm_dPres The derivative of permeability wrt pressure in each element - * @param[out] localMatrix The linear system matrix - * @param[out] localRhs The linear system residual - */ - template< typename STENCIL_WRAPPER_TYPE > - static void - launch( STENCIL_WRAPPER_TYPE const & stencilWrapper, - real64 const dt, - globalIndex const rankOffset, - ElementViewConst< arrayView1d< globalIndex const > > const & pressureDofNumber, - ElementViewConst< arrayView1d< globalIndex const > > const & jumpDofNumber, - ElementViewConst< arrayView1d< integer const > > const & ghostRank, - ElementViewConst< arrayView1d< real64 const > > const & pres, - ElementViewConst< arrayView1d< real64 const > > const & gravCoef, - ElementViewConst< arrayView2d< real64 const > > const & dens, - ElementViewConst< arrayView2d< real64 const > > const & dDens_dPres, - ElementViewConst< arrayView1d< real64 const > > const & mob, - ElementViewConst< arrayView1d< real64 const > > const & dMob_dPres, - ElementViewConst< arrayView3d< real64 const > > const & permeability, - ElementViewConst< arrayView3d< real64 const > > const & dPerm_dPres, - ElementViewConst< arrayView4d< real64 const > > const & dPerm_dDispJump, - CRSMatrixView< real64, globalIndex const > const & localMatrix, - arrayView1d< real64 > const & localRhs ); - - /** - * @brief Compute flux and its derivatives for a given tpfa connector. - * - * - */ - template< localIndex MAX_NUM_CONNECTIONS > - GEOS_HOST_DEVICE - static void - compute( localIndex const numFluxElems, - arraySlice1d< localIndex const > const & seri, - arraySlice1d< localIndex const > const & sesri, - arraySlice1d< localIndex const > const & sei, - real64 const (&transmissibility)[MAX_NUM_CONNECTIONS][2], - real64 const (&dTrans_dPres)[MAX_NUM_CONNECTIONS][2], - real64 const (&dTrans_dDispJump)[MAX_NUM_CONNECTIONS][2][3], - ElementViewConst< arrayView1d< real64 const > > const & pres, - ElementViewConst< arrayView1d< real64 const > > const & gravCoef, - ElementViewConst< arrayView2d< real64 const > > const & dens, - ElementViewConst< arrayView2d< real64 const > > const & dDens_dPres, - ElementViewConst< arrayView1d< real64 const > > const & mob, - ElementViewConst< arrayView1d< real64 const > > const & dMob_dPres, - real64 const dt, - arraySlice1d< real64 > const & flux, - arraySlice2d< real64 > const & fluxJacobian ); -}; - - -/******************************** FaceElementFluxKernel ********************************/ - -struct FaceElementFluxKernel -{ - /** - * @brief The type for element-based non-constitutive data parameters. - * Consists entirely of ArrayView's. - * - * Can be converted from ElementRegionManager::ElementViewAccessor - * by calling .toView() or .toViewConst() on an accessor instance - */ - template< typename VIEWTYPE > - using ElementViewConst = ElementRegionManager::ElementViewConst< VIEWTYPE >; - -/** - * @brief launches the kernel to assemble the flux contributions to the linear system. - * @tparam SurfaceElementStencilWrapper The type of the stencil that is being used. - * @param[in] stencilWrapper The stencil wrapper object. - * @param[in] dt The timestep for the integration step. - * @param[in] rankOffset The rank offset - * @param[in] pressureDofNumber The pressure dof number for each element - * @param[in] ghostRank The ghost rank - * @param[in] pres The pressures in each element - * @param[in] gravCoef The factor for gravity calculations (g*H) - * @param[in] dens The material density in each element - * @param[in] dDens_dPres The change in material density for each element - * @param[in] mob The fluid mobility in each element - * @param[in] dMob_dPres The derivative of mobility wrt pressure in each element - * @param[in] permeability The permeability in each element - * @param[in] dPerm_dPres The derivative of permeability wrt pressure in each element - * @param[in] dPerm_dDispJump The derivative of permeability wrt aperture in each element - * @param[in] permeabilityMultiplier The permeability multiplier - * @param[in] gravityVector The gravity vector - * @param[out] localMatrix The linear system matrix - * @param[out] localRhs The linear system residual - */ - template< typename STENCIL_WRAPPER_TYPE > - static void - launch( STENCIL_WRAPPER_TYPE const & stencilWrapper, - real64 const dt, - globalIndex const rankOffset, - ElementViewConst< arrayView1d< globalIndex const > > const & pressureDofNumber, - ElementViewConst< arrayView1d< integer const > > const & ghostRank, - ElementViewConst< arrayView1d< real64 const > > const & pres, - ElementViewConst< arrayView1d< real64 const > > const & gravCoef, - ElementViewConst< arrayView2d< real64 const > > const & dens, - ElementViewConst< arrayView2d< real64 const > > const & dDens_dPres, - ElementViewConst< arrayView1d< real64 const > > const & mob, - ElementViewConst< arrayView1d< real64 const > > const & dMob_dPres, - ElementViewConst< arrayView3d< real64 const > > const & permeability, - ElementViewConst< arrayView3d< real64 const > > const & dPerm_dPres, - ElementViewConst< arrayView4d< real64 const > > const & dPerm_dDispJump, - CRSMatrixView< real64, globalIndex const > const & localMatrix, - arrayView1d< real64 > const & localRhs, - CRSMatrixView< real64, localIndex const > const & dR_dAper ); - - /** - * @brief launches the kernel to assemble the flux contributions to the linear system. - * @tparam SurfaceElementStencilWrapper The type of the stencil that is being used. - * @param[in] stencil The stencil object. - * @param[in] dt The timestep for the integration step. - * @param[in] dofNumber The dofNumbers for each element - * @param[in] pres The pressures in each element - * @param[in] gravCoef The factor for gravity calculations (g*H) - * @param[in] dens The material density in each element - * @param[in] dDens_dPres The change in material density for each element - * @param[in] mob The fluid mobility in each element - * @param[in] dMob_dPres The derivative of mobility wrt pressure in each element - * @param[in] permeability - * @param[in] dPerm_dPres The derivative of permeability wrt pressure in each element - * @param[in] permeabilityMultiplier - * @param[in] gravityVector - * @param[out] localMatrix The linear system matrix - * @param[out] localRhs The linear system residual - */ - static void - launch( SurfaceElementStencilWrapper const & stencilWrapper, - real64 const dt, - globalIndex const rankOffset, - ElementViewConst< arrayView1d< globalIndex const > > const & pressureDofNumber, - ElementViewConst< arrayView1d< integer const > > const & ghostRank, - ElementViewConst< arrayView1d< real64 const > > const & pres, - ElementViewConst< arrayView1d< real64 const > > const & gravCoef, - ElementViewConst< arrayView2d< real64 const > > const & dens, - ElementViewConst< arrayView2d< real64 const > > const & dDens_dPres, - ElementViewConst< arrayView1d< real64 const > > const & mob, - ElementViewConst< arrayView1d< real64 const > > const & dMob_dPres, - ElementViewConst< arrayView3d< real64 const > > const & permeability, - ElementViewConst< arrayView3d< real64 const > > const & dPerm_dPres, - ElementViewConst< arrayView4d< real64 const > > const & dPerm_dDispJump, - ElementViewConst< arrayView3d< real64 const > > const & permeabilityMultiplier, - R1Tensor const & gravityVector, - CRSMatrixView< real64, globalIndex const > const & localMatrix, - arrayView1d< real64 > const & localRhs ); - - - - /** - * @brief Compute flux and its derivatives for a given tpfa connector. - * - * - */ - template< localIndex MAX_NUM_CONNECTIONS > - GEOS_HOST_DEVICE - static void - compute( localIndex const numFluxElems, - arraySlice1d< localIndex const > const & seri, - arraySlice1d< localIndex const > const & sesri, - arraySlice1d< localIndex const > const & sei, - real64 const (&transmissibility)[MAX_NUM_CONNECTIONS][2], - real64 const (&dTrans_dPres)[MAX_NUM_CONNECTIONS][2], - real64 const (&dTrans_dDispJump)[MAX_NUM_CONNECTIONS][2][3], - ElementViewConst< arrayView1d< real64 const > > const & pres, - ElementViewConst< arrayView1d< real64 const > > const & gravCoef, - ElementViewConst< arrayView2d< real64 const > > const & dens, - ElementViewConst< arrayView2d< real64 const > > const & dDens_dPres, - ElementViewConst< arrayView1d< real64 const > > const & mob, - ElementViewConst< arrayView1d< real64 const > > const & dMob_dPres, - real64 const dt, - arraySlice1d< real64 > const & flux, - arraySlice2d< real64 > const & fluxJacobian, - arraySlice2d< real64 > const & dFlux_dAperture ); -}; - - -} // namespace singlePhasePoromechanicsFluxKernels - -} // namespace geos - -#endif //GEOS_PHYSICSSOLVERS_MULTIPHYSICS_SINGLEPHASEPOROMECHANICSFLUXKERNELS_HPP diff --git a/src/coreComponents/physicsSolvers/multiphysics/SinglePhaseReservoirAndWells.cpp b/src/coreComponents/physicsSolvers/multiphysics/SinglePhaseReservoirAndWells.cpp index 4224640248e..7afbf582b3c 100644 --- a/src/coreComponents/physicsSolvers/multiphysics/SinglePhaseReservoirAndWells.cpp +++ b/src/coreComponents/physicsSolvers/multiphysics/SinglePhaseReservoirAndWells.cpp @@ -249,6 +249,11 @@ assembleCouplingTerms( real64 const time_n, using ROFFSET = singlePhaseWellKernels::RowOffset; using COFFSET = singlePhaseWellKernels::ColOffset; + GEOS_THROW_IF( !Base::m_isWellTransmissibilityComputed, + GEOS_FMT( "{} `{}`: The well transmissibility has not been computed yet", + catalogName(), this->getName() ), + std::runtime_error ); + this->template forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&] ( string const &, MeshLevel const & mesh, arrayView1d< string const > const & regionNames ) diff --git a/src/coreComponents/physicsSolvers/multiphysics/poromechanicsKernels/PoromechanicsBase.hpp b/src/coreComponents/physicsSolvers/multiphysics/poromechanicsKernels/PoromechanicsBase.hpp index 248c24b4adf..d78db490584 100644 --- a/src/coreComponents/physicsSolvers/multiphysics/poromechanicsKernels/PoromechanicsBase.hpp +++ b/src/coreComponents/physicsSolvers/multiphysics/poromechanicsKernels/PoromechanicsBase.hpp @@ -299,6 +299,7 @@ class PoromechanicsBase : }; + } // namespace poromechanicsKernels } // namespace geos diff --git a/src/coreComponents/physicsSolvers/multiphysics/poromechanicsKernels/PoromechanicsKernels.cmake b/src/coreComponents/physicsSolvers/multiphysics/poromechanicsKernels/PoromechanicsKernels.cmake index 0ec19638e9d..cdb4dac5479 100644 --- a/src/coreComponents/physicsSolvers/multiphysics/poromechanicsKernels/PoromechanicsKernels.cmake +++ b/src/coreComponents/physicsSolvers/multiphysics/poromechanicsKernels/PoromechanicsKernels.cmake @@ -5,6 +5,8 @@ set( SinglePhasePoromechanicsEFEMPolicy "geos::parallelDevicePolicy< ${GEOSX_BLO set( MultiphasePoromechanicsPolicy "geos::parallelDevicePolicy< ${GEOSX_BLOCK_SIZE} >" ) set( ThermalMultiphasePoromechanicsPolicy "geos::parallelDevicePolicy< ${GEOSX_BLOCK_SIZE} >" ) set( ThermalSinglePhasePoromechanicsPolicy "geos::parallelDevicePolicy< ${GEOSX_BLOCK_SIZE} >" ) +set( ThermalSinglePhasePoromechanicsEFEMPolicy "geos::parallelDevicePolicy< ${GEOSX_BLOCK_SIZE} >" ) + configure_file( ${CMAKE_SOURCE_DIR}/${kernelPath}/policies.hpp.in ${CMAKE_BINARY_DIR}/generatedSrc/${kernelPath}/policies.hpp ) diff --git a/src/coreComponents/physicsSolvers/multiphysics/poromechanicsKernels/SinglePhasePoromechanicsConformingFractures.hpp b/src/coreComponents/physicsSolvers/multiphysics/poromechanicsKernels/SinglePhasePoromechanicsConformingFractures.hpp new file mode 100644 index 00000000000..fc69cd60364 --- /dev/null +++ b/src/coreComponents/physicsSolvers/multiphysics/poromechanicsKernels/SinglePhasePoromechanicsConformingFractures.hpp @@ -0,0 +1,329 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2018-2020 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2020 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2018-2020 TotalEnergies + * Copyright (c) 2019- GEOS Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +/** + * @file SinglePhasePoromechanicsConformingFractures.hpp + */ + +#ifndef GEOS_PHYSICSSOLVERS_MULTIPHYSICS_POROMECHANICSKERNELS_SINGLEPHASEPOROMECHANICSCONFORMINGFRACTURES_HPP +#define GEOS_PHYSICSSOLVERS_MULTIPHYSICS_POROMECHANICSKERNELS_SINGLEPHASEPOROMECHANICSCONFORMINGFRACTURES_HPP + +#include "physicsSolvers/fluidFlow/SinglePhaseFVMKernels.hpp" +#include "physicsSolvers/fluidFlow/FluxKernelsHelper.hpp" + +namespace geos +{ + +namespace singlePhasePoromechanicsConformingFracturesKernels +{ + +using namespace fluxKernelsHelper; +using namespace constitutive; + +template< integer NUM_EQN, integer NUM_DOF > +class ConnectorBasedAssemblyKernel : public singlePhaseFVMKernels::FaceBasedAssemblyKernel< NUM_EQN, NUM_DOF, SurfaceElementStencilWrapper > +{ +public: + + /** + * @brief The type for element-based data. Consists entirely of ArrayView's. + * + * Can be converted from ElementRegionManager::ElementViewConstAccessor + * by calling .toView() or .toViewConst() on an accessor instance + */ + template< typename VIEWTYPE > + using ElementViewConst = ElementRegionManager::ElementViewConst< VIEWTYPE >; + + using AbstractBase = singlePhaseFVMKernels::FaceBasedAssemblyKernelBase; + using DofNumberAccessor = AbstractBase::DofNumberAccessor; + using SinglePhaseFlowAccessors = AbstractBase::SinglePhaseFlowAccessors; + using SinglePhaseFluidAccessors = AbstractBase::SinglePhaseFluidAccessors; + using PermeabilityAccessors = AbstractBase::PermeabilityAccessors; + using FracturePermeabilityAccessors = StencilMaterialAccessors< PermeabilityBase, + fields::permeability::dPerm_dDispJump >; + + using AbstractBase::m_dt; + using AbstractBase::m_rankOffset; + using AbstractBase::m_dofNumber; + using AbstractBase::m_permeability; + using AbstractBase::m_dPerm_dPres; + using AbstractBase::m_gravCoef; + using AbstractBase::m_pres; + using AbstractBase::m_mob; + using AbstractBase::m_dMob_dPres; + using AbstractBase::m_dens; + using AbstractBase::m_dDens_dPres; + + using Base = singlePhaseFVMKernels::FaceBasedAssemblyKernel< NUM_EQN, NUM_DOF, SurfaceElementStencilWrapper >; + using Base::numDof; + using Base::numEqn; + using Base::maxNumElems; + using Base::maxNumConns; + using Base::maxStencilSize; + using Base::m_stencilWrapper; + using Base::m_seri; + using Base::m_sesri; + using Base::m_sei; + + ConnectorBasedAssemblyKernel( globalIndex const rankOffset, + SurfaceElementStencilWrapper const & stencilWrapper, + DofNumberAccessor const & flowDofNumberAccessor, + SinglePhaseFlowAccessors const & singlePhaseFlowAccessors, + SinglePhaseFluidAccessors const & singlePhaseFluidAccessors, + PermeabilityAccessors const & permeabilityAccessors, + FracturePermeabilityAccessors const & fracturePermeabilityAccessors, + real64 const & dt, + CRSMatrixView< real64, globalIndex const > const & localMatrix, + arrayView1d< real64 > const & localRhs, + CRSMatrixView< real64, localIndex const > const & dR_dAper ) + : Base( rankOffset, + stencilWrapper, + flowDofNumberAccessor, + singlePhaseFlowAccessors, + singlePhaseFluidAccessors, + permeabilityAccessors, + dt, + localMatrix, + localRhs ), + m_dR_dAper( dR_dAper ), + m_dPerm_dDispJump( fracturePermeabilityAccessors.get( fields::permeability::dPerm_dDispJump {} ) ) + {} + + + /** + * @struct StackVariables + * @brief Kernel variables (dof numbers, jacobian and residual) located on the stack + */ + struct StackVariables : public Base::StackVariables + { +public: + + /** + * @brief Constructor for the stack variables + * @param[in] size size of the stencil for this connection + * @param[in] numElems number of elements for this connection + */ + GEOS_HOST_DEVICE + StackVariables( localIndex const size, localIndex numElems ) + : Base::StackVariables( size, numElems ), + localColIndices( numElems ), + dFlux_dAperture( numElems, size ) + {} + + stackArray1d< localIndex, maxNumElems > localColIndices; + + stackArray2d< real64, maxNumElems * maxStencilSize > dFlux_dAperture; + + /// Derivatives of transmissibility with respect to the dispJump + real64 dTrans_dDispJump[maxNumConns][2][3]{}; + }; + + /** + * @brief Performs the setup phase for the kernel. + * @param[in] iconn the connection index + * @param[in] stack the stack variables + */ + GEOS_HOST_DEVICE + void setup( localIndex const iconn, + StackVariables & stack ) const + { + // set degrees of freedom indices for this face + for( integer i = 0; i < stack.stencilSize; ++i ) + { + globalIndex const offset = m_dofNumber[m_seri( iconn, i )][m_sesri( iconn, i )][m_sei( iconn, i )]; + for( integer jdof = 0; jdof < numDof; ++jdof ) + { + stack.dofColIndices[i * numDof + jdof] = offset + jdof; + } + stack.localColIndices[ i ] = m_sei( iconn, i ); + } + } + + /** + * @brief Compute the local flux contributions to the residual and Jacobian + * @tparam FUNC the type of the function that can be used to customize the computation of the flux + * @param[in] iconn the connection index + * @param[inout] stack the stack variables + * @param[in] NoOpFunc the function used to customize the computation of the flux + */ + template< typename FUNC = singlePhaseBaseKernels::NoOpFunc > + GEOS_HOST_DEVICE + void computeFlux( localIndex const iconn, + StackVariables & stack, + FUNC && kernelOp = singlePhaseBaseKernels::NoOpFunc{} ) const + + { + + m_stencilWrapper.computeWeights( iconn, + m_permeability, + m_dPerm_dPres, + m_dPerm_dDispJump, + stack.transmissibility, + stack.dTrans_dPres, + stack.dTrans_dDispJump ); + + + localIndex k[2]; + localIndex connectionIndex = 0; + for( k[0]=0; k[0] + GEOS_HOST_DEVICE + void complete( localIndex const iconn, + StackVariables & stack, + FUNC && kernelOp = singlePhaseBaseKernels::NoOpFunc{} ) const + { + // Call Base::complete to assemble the mass balance equations + // In the lambda, fill the dR_dAper matrix + Base::complete( iconn, stack, [&] ( integer const i, + localIndex const localRow ) + { + + localIndex const row = LvArray::integerConversion< localIndex >( m_sei( iconn, i ) ); + + m_dR_dAper.addToRowBinarySearch< parallelDeviceAtomic >( row, + stack.localColIndices.data(), + stack.dFlux_dAperture[i].dataIfContiguous(), + stack.stencilSize ); + // call the lambda to assemble additional terms, such as thermal terms + kernelOp( i, localRow ); + } ); + } + +private: + + CRSMatrixView< real64, localIndex const > m_dR_dAper; + + ElementViewConst< arrayView4d< real64 const > > const m_dPerm_dDispJump; +}; + + + +/** + * @class FaceBasedAssemblyKernelFactory + */ +class ConnectorBasedAssemblyKernelFactory +{ +public: + + /** + * @brief Create a new kernel and launch + * @tparam POLICY the policy used in the RAJA kernel + * @tparam STENCILWRAPPER the type of the stencil wrapper + * @param[in] rankOffset the offset of my MPI rank + * @param[in] dofKey string to get the element degrees of freedom numbers + * @param[in] solverName name of the solver (to name accessors) + * @param[in] elemManager reference to the element region manager + * @param[in] stencilWrapper reference to the stencil wrapper + * @param[in] dt time step size + * @param[inout] localMatrix the local CRS matrix + * @param[inout] localRhs the local right-hand side vector + */ + template< typename POLICY > + static void + createAndLaunch( globalIndex const rankOffset, + string const & dofKey, + string const & solverName, + ElementRegionManager const & elemManager, + SurfaceElementStencilWrapper const & stencilWrapper, + real64 const & dt, + CRSMatrixView< real64, globalIndex const > const & localMatrix, + arrayView1d< real64 > const & localRhs, + CRSMatrixView< real64, localIndex const > const & dR_dAper ) + { + integer constexpr NUM_DOF = 1; // pressure + integer constexpr NUM_EQN = 1; + + ElementRegionManager::ElementViewAccessor< arrayView1d< globalIndex const > > flowDofNumberAccessor = + elemManager.constructArrayViewAccessor< globalIndex, 1 >( dofKey ); + flowDofNumberAccessor.setName( solverName + "/accessors/" + dofKey ); + + using kernelType = ConnectorBasedAssemblyKernel< NUM_EQN, NUM_DOF >; + typename kernelType::SinglePhaseFlowAccessors flowAccessors( elemManager, solverName ); + typename kernelType::SinglePhaseFluidAccessors fluidAccessors( elemManager, solverName ); + typename kernelType::PermeabilityAccessors permAccessors( elemManager, solverName ); + typename kernelType::FracturePermeabilityAccessors fracPermAccessors( elemManager, solverName ); + + kernelType kernel( rankOffset, stencilWrapper, flowDofNumberAccessor, + flowAccessors, fluidAccessors, permAccessors, fracPermAccessors, + dt, localMatrix, localRhs, dR_dAper ); + + kernelType::template launch< POLICY >( stencilWrapper.size(), kernel ); + } +}; + +} // namespace SinglePhasePoromechanicsConformingFracturesKernels + +} // namespace geos + +#endif //GEOS_PHYSICSSOLVERS_MULTIPHYSICS_POROMECHANICSKERNELS_SINGLEPHASEPOROMECHANICSCONFORMINGFRACTURES_HPP diff --git a/src/coreComponents/physicsSolvers/multiphysics/poromechanicsKernels/SinglePhasePoromechanicsEFEM.hpp b/src/coreComponents/physicsSolvers/multiphysics/poromechanicsKernels/SinglePhasePoromechanicsEFEM.hpp index 2ab779d768b..95ac4f6ab25 100644 --- a/src/coreComponents/physicsSolvers/multiphysics/poromechanicsKernels/SinglePhasePoromechanicsEFEM.hpp +++ b/src/coreComponents/physicsSolvers/multiphysics/poromechanicsKernels/SinglePhasePoromechanicsEFEM.hpp @@ -29,20 +29,19 @@ namespace poromechanicsEFEMKernels { /** - * @brief Implements kernels for solving quasi-static single-phase poromechanics. - * @copydoc geos::finiteElement::ImplicitKernelBase - * @tparam NUM_NODES_PER_ELEM The number of nodes per element for the - * @p SUBREGION_TYPE. - * @tparam UNUSED An unused parameter since we are assuming that the test and - * trial space have the same number of support points. - * - * ### SinglePhasePoromechanics Description - * Implements the KernelBase interface functions required for solving the - * quasi-static single-phase poromechanics problem using one of the - * "finite element kernel application" functions such as - * geos::finiteElement::RegionBasedKernelApplication. - * + * @brief Internal struct to provide no-op defaults used in the inclusion + * of lambda functions into kernel component functions. + * @struct NoOpFunc */ +struct NoOpFunc +{ + template< typename ... Ts > + GEOS_HOST_DEVICE + constexpr void + operator()( Ts && ... ) const {} +}; + + template< typename SUBREGION_TYPE, typename CONSTITUTIVE_TYPE, typename FE_TYPE > @@ -219,10 +218,12 @@ class SinglePhasePoromechanicsEFEM : void setup( localIndex const k, StackVariables & stack ) const; + template< typename FUNC = poromechanicsEFEMKernels::NoOpFunc > GEOS_HOST_DEVICE void quadraturePointKernel( localIndex const k, localIndex const q, - StackVariables & stack ) const; + StackVariables & stack, + FUNC && kernelOp = poromechanicsEFEMKernels::NoOpFunc{} ) const; /** * @copydoc geos::finiteElement::ImplicitKernelBase::complete diff --git a/src/coreComponents/physicsSolvers/multiphysics/poromechanicsKernels/SinglePhasePoromechanicsEFEM_impl.hpp b/src/coreComponents/physicsSolvers/multiphysics/poromechanicsKernels/SinglePhasePoromechanicsEFEM_impl.hpp index eb8bc9ad1c8..4d489804bb3 100644 --- a/src/coreComponents/physicsSolvers/multiphysics/poromechanicsKernels/SinglePhasePoromechanicsEFEM_impl.hpp +++ b/src/coreComponents/physicsSolvers/multiphysics/poromechanicsKernels/SinglePhasePoromechanicsEFEM_impl.hpp @@ -19,7 +19,6 @@ #ifndef GEOS_PHYSICSSOLVERS_MULTIPHYSICS_POROMECHANICSKERNELS_SINGLEPHASEPOROMECHANICSEFEM_IMPL_HPP_ #define GEOS_PHYSICSSOLVERS_MULTIPHYSICS_POROMECHANICSKERNELS_SINGLEPHASEPOROMECHANICSEFEM_IMPL_HPP_ -#include "finiteElement/kernelInterface/ImplicitKernelBase.hpp" #include "physicsSolvers/contact/ContactFields.hpp" #include "constitutive/fluid/singlefluid/SingleFluidBase.hpp" #include "physicsSolvers/fluidFlow/FlowSolverBaseFields.hpp" @@ -176,12 +175,14 @@ setup( localIndex const k, template< typename SUBREGION_TYPE, typename CONSTITUTIVE_TYPE, typename FE_TYPE > +template< typename FUNC > GEOS_HOST_DEVICE GEOS_FORCE_INLINE void SinglePhasePoromechanicsEFEM< SUBREGION_TYPE, CONSTITUTIVE_TYPE, FE_TYPE >:: quadraturePointKernel( localIndex const k, localIndex const q, - StackVariables & stack ) const + StackVariables & stack, + FUNC && kernelOp ) const { localIndex const embSurfIndex = m_cellsToEmbeddedSurfaces[k][0]; @@ -196,15 +197,17 @@ quadraturePointKernel( localIndex const k, constexpr int nUdof = numNodesPerElem*3; // Gauss contribution to Kww, Kwu and Kuw blocks - real64 Kww_gauss[3][3], Kwu_gauss[3][nUdof], Kuw_gauss[nUdof][3], Kwpm_gauss[3]; + real64 Kww_gauss[3][3]{}, Kwu_gauss[3][nUdof]{}, Kuw_gauss[nUdof][3]{}, Kwpm_gauss[3]{}; // Compatibility, equilibrium and strain operators. The compatibility operator is constructed as // a 3 x 6 because it is more convenient for construction purposes (reduces number of local var). - real64 compMatrix[3][6], strainMatrix[6][nUdof], eqMatrix[3][6]; - real64 matBD[nUdof][6], matED[3][6]; - real64 const biotCoefficient = 1.0; + real64 compMatrix[3][6]{}, strainMatrix[6][nUdof]{}, eqMatrix[3][6]{}; + real64 matBD[nUdof][6]{}, matED[3][6]{}; + real64 biotCoefficient{}; + int Heaviside[ numNodesPerElem ]{}; + + m_constitutiveUpdate.getBiotCoefficient( k, biotCoefficient ); - int Heaviside[ numNodesPerElem ]; // TODO: asking for the stiffness here will only work for elastic models. most other models // need to know the strain increment to compute the current stiffness value. @@ -254,9 +257,13 @@ quadraturePointKernel( localIndex const k, LvArray::tensorOps::scaledAdd< 3, 3 >( stack.localKww, Kww_gauss, -detJ ); LvArray::tensorOps::scaledAdd< 3, nUdof >( stack.localKwu, Kwu_gauss, -detJ ); LvArray::tensorOps::scaledAdd< nUdof, 3 >( stack.localKuw, Kuw_gauss, -detJ ); - // No neg coz the effective stress is total stress - porePressure + + /// TODO: should this be negative??? + // I had No neg coz the total stress = effective stress - porePressure // and all signs are flipped here. LvArray::tensorOps::scaledAdd< 3 >( stack.localKwpm, Kwpm_gauss, detJ*biotCoefficient ); + + kernelOp( eqMatrix, detJ ); } template< typename SUBREGION_TYPE, diff --git a/src/coreComponents/physicsSolvers/multiphysics/poromechanicsKernels/SinglePhasePoromechanicsEmbeddedFractures.hpp b/src/coreComponents/physicsSolvers/multiphysics/poromechanicsKernels/SinglePhasePoromechanicsEmbeddedFractures.hpp new file mode 100644 index 00000000000..2edc90377d1 --- /dev/null +++ b/src/coreComponents/physicsSolvers/multiphysics/poromechanicsKernels/SinglePhasePoromechanicsEmbeddedFractures.hpp @@ -0,0 +1,308 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2018-2020 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2020 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2018-2020 TotalEnergies + * Copyright (c) 2019- GEOS Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +/** + * @file SinglePhasePoromechanicsEmbeddedFractures.hpp + */ + +#ifndef GEOS_PHYSICSSOLVERS_MULTIPHYSICS_POROMECHANICSKERNELS_SINGLEPHASEPOROMECHANICSEMBEDDEDFRACTURES_HPP +#define GEOS_PHYSICSSOLVERS_MULTIPHYSICS_POROMECHANICSKERNELS_SINGLEPHASEPOROMECHANICSEMBEDDEDFRACTURES_HPP + +#include "physicsSolvers/fluidFlow/SinglePhaseFVMKernels.hpp" +#include "physicsSolvers/fluidFlow/FluxKernelsHelper.hpp" + +namespace geos +{ + +namespace singlePhasePoromechanicsEmbeddedFracturesKernels +{ + +using namespace fluxKernelsHelper; +using namespace constitutive; + +template< integer NUM_EQN, integer NUM_DOF > +class ConnectorBasedAssemblyKernel : public singlePhaseFVMKernels::FaceBasedAssemblyKernel< NUM_EQN, NUM_DOF, SurfaceElementStencilWrapper > +{ +public: + + /** + * @brief The type for element-based data. Consists entirely of ArrayView's. + * + * Can be converted from ElementRegionManager::ElementViewConstAccessor + * by calling .toView() or .toViewConst() on an accessor instance + */ + template< typename VIEWTYPE > + using ElementViewConst = ElementRegionManager::ElementViewConst< VIEWTYPE >; + + using AbstractBase = singlePhaseFVMKernels::FaceBasedAssemblyKernelBase; + using DofNumberAccessor = AbstractBase::DofNumberAccessor; + using SinglePhaseFlowAccessors = AbstractBase::SinglePhaseFlowAccessors; + using SinglePhaseFluidAccessors = AbstractBase::SinglePhaseFluidAccessors; + using PermeabilityAccessors = AbstractBase::PermeabilityAccessors; + using FracturePermeabilityAccessors = StencilMaterialAccessors< PermeabilityBase, + fields::permeability::dPerm_dDispJump >; + using AbstractBase::m_dt; + using AbstractBase::m_rankOffset; + using AbstractBase::m_dofNumber; + using AbstractBase::m_permeability; + using AbstractBase::m_dPerm_dPres; + using AbstractBase::m_gravCoef; + using AbstractBase::m_pres; + using AbstractBase::m_mob; + using AbstractBase::m_dMob_dPres; + using AbstractBase::m_dens; + using AbstractBase::m_dDens_dPres; + + using Base = singlePhaseFVMKernels::FaceBasedAssemblyKernel< NUM_EQN, NUM_DOF, SurfaceElementStencilWrapper >; + using Base::numDof; + using Base::numEqn; + using Base::maxNumElems; + using Base::maxNumConns; + using Base::maxStencilSize; + using Base::m_stencilWrapper; + using Base::m_seri; + using Base::m_sesri; + using Base::m_sei; + using Base::m_ghostRank; + + + + ConnectorBasedAssemblyKernel( globalIndex const rankOffset, + SurfaceElementStencilWrapper const & stencilWrapper, + DofNumberAccessor const & flowDofNumberAccessor, + DofNumberAccessor const & dispJumpDofNumberAccessor, + SinglePhaseFlowAccessors const & singlePhaseFlowAccessors, + SinglePhaseFluidAccessors const & singlePhaseFluidAccessors, + PermeabilityAccessors const & permeabilityAccessors, + FracturePermeabilityAccessors const & edfmPermeabilityAccessors, + real64 const & dt, + CRSMatrixView< real64, globalIndex const > const & localMatrix, + arrayView1d< real64 > const & localRhs ) + : Base( rankOffset, + stencilWrapper, + flowDofNumberAccessor, + singlePhaseFlowAccessors, + singlePhaseFluidAccessors, + permeabilityAccessors, + dt, + localMatrix, + localRhs ), + m_dispJumpDofNumber( dispJumpDofNumberAccessor.toNestedViewConst() ), + m_dPerm_dDispJump( edfmPermeabilityAccessors.get( fields::permeability::dPerm_dDispJump {} ) ) + {} + + + /** + * @struct StackVariables + * @brief Kernel variables (dof numbers, jacobian and residual) located on the stack + */ + struct StackVariables : public Base::StackVariables + { +public: + + /** + * @brief Constructor for the stack variables + * @param[in] size size of the stencil for this connection + * @param[in] numElems number of elements for this connection + */ + GEOS_HOST_DEVICE + StackVariables( localIndex const size, localIndex numElems ) + : Base::StackVariables( size, numElems ) + {} + + /// Derivatives of transmissibility with respect to the dispJump + real64 dTrans_dDispJump[maxNumConns][2][3]{}; + }; + + /** + * @brief Performs the setup phase for the kernel. + * @param[in] iconn the connection index + * @param[in] stack the stack variables + */ + GEOS_HOST_DEVICE + void setup( localIndex const iconn, + StackVariables & stack ) const + { + // set degrees of freedom indices for this face + for( integer i = 0; i < stack.stencilSize; ++i ) + { + localIndex localDofIndex = numDof * i; + stack.dofColIndices[ localDofIndex ] = m_dofNumber[m_seri( iconn, i )][m_sesri( iconn, i )][m_sei( iconn, i )]; + stack.dofColIndices[ localDofIndex + 1 ] = m_dispJumpDofNumber[m_seri( iconn, i )][m_sesri( iconn, i )][m_sei( iconn, i )]; + stack.dofColIndices[ localDofIndex + 2 ] = m_dispJumpDofNumber[m_seri( iconn, i )][m_sesri( iconn, i )][m_sei( iconn, i )] + 1; + stack.dofColIndices[ localDofIndex + 3 ] = m_dispJumpDofNumber[m_seri( iconn, i )][m_sesri( iconn, i )][m_sei( iconn, i )] + 2; + } + } + + /** + * @brief Compute the local flux contributions to the residual and Jacobian + * @tparam FUNC the type of the function that can be used to customize the computation of the flux + * @param[in] iconn the connection index + * @param[inout] stack the stack variables + * @param[in] NoOpFunc the function used to customize the computation of the flux + */ + template< typename FUNC = singlePhaseBaseKernels::NoOpFunc > + GEOS_HOST_DEVICE + void computeFlux( localIndex const iconn, + StackVariables & stack, + FUNC && kernelOp = singlePhaseBaseKernels::NoOpFunc{} ) const + + { + + m_stencilWrapper.computeWeights( iconn, + m_permeability, + m_dPerm_dPres, + m_dPerm_dDispJump, + stack.transmissibility, + stack.dTrans_dPres, + stack.dTrans_dDispJump ); + + + real64 fluxVal = 0.0; + real64 dFlux_dTrans = 0.0; + /// EDFM connections are always only between 2 elements. There are no star connections. + real64 trans[2] = {stack.transmissibility[0][0], stack.transmissibility[0][1]}; + real64 dTrans[2] = { stack.dTrans_dPres[0][0], stack.dTrans_dPres[0][1] }; + real64 dFlux_dP[2] = {0.0, 0.0}; + localIndex const regionIndex[2] = {m_seri[iconn][0], m_seri[iconn][1]}; + localIndex const subRegionIndex[2] = {m_sesri[iconn][0], m_sesri[iconn][1]}; + localIndex const elementIndex[2] = {m_sei[iconn][0], m_sei[iconn][1]}; + real64 alpha = 0.0; + real64 mobility = 0.0; + real64 potGrad = 0.0; + + computeSinglePhaseFlux( regionIndex, subRegionIndex, elementIndex, + trans, + dTrans, + m_pres, + m_gravCoef, + m_dens, + m_dDens_dPres, + m_mob, + m_dMob_dPres, + alpha, + mobility, + potGrad, + fluxVal, + dFlux_dP, + dFlux_dTrans ); + + + + // populate local flux vector and derivatives + stack.localFlux[0] = m_dt * fluxVal; + stack.localFlux[1] = -m_dt * fluxVal; + + real64 dFlux_dDispJump[2][3] = {{0.0, 0.0, 0.0}, {0.0, 0.0, 0.0}}; + for( localIndex i=0; i < 3; i++ ) + { + dFlux_dDispJump[0][i] = dFlux_dTrans * stack.dTrans_dDispJump[0][0][i]; + dFlux_dDispJump[1][i] = -dFlux_dTrans * stack.dTrans_dDispJump[0][1][i]; + } + for( localIndex ke = 0; ke < 2; ++ke ) + { + localIndex const dofIndex = numDof*ke; + + stack.localFluxJacobian[0][dofIndex] = m_dt * dFlux_dP[ke]; + stack.localFluxJacobian[0][dofIndex+1] = m_dt * dFlux_dDispJump[ke][0]; + stack.localFluxJacobian[0][dofIndex+2] = m_dt * dFlux_dDispJump[ke][1]; + stack.localFluxJacobian[0][dofIndex+3] = m_dt * dFlux_dDispJump[ke][2]; + + stack.localFluxJacobian[1][dofIndex] = -m_dt * dFlux_dP[ke]; + stack.localFluxJacobian[1][dofIndex+1] = -m_dt * dFlux_dDispJump[ke][0]; + stack.localFluxJacobian[1][dofIndex+2] = -m_dt * dFlux_dDispJump[ke][1]; + stack.localFluxJacobian[1][dofIndex+3] = -m_dt * dFlux_dDispJump[ke][2]; + } + + kernelOp( regionIndex, subRegionIndex, elementIndex, iconn, alpha, mobility, potGrad, fluxVal, dFlux_dTrans, dFlux_dP ); + } + + +private: + + ElementViewConst< arrayView1d< globalIndex const > > const m_dispJumpDofNumber; + + ElementViewConst< arrayView4d< real64 const > > const m_dPerm_dDispJump; + +}; + + + +/** + * @class FaceBasedAssemblyKernelFactory + */ +class ConnectorBasedAssemblyKernelFactory +{ +public: + + /** + * @brief Create a new kernel and launch + * @tparam POLICY the policy used in the RAJA kernel + * @tparam STENCILWRAPPER the type of the stencil wrapper + * @param[in] rankOffset the offset of my MPI rank + * @param[in] dofKey string to get the element degrees of freedom numbers + * @param[in] solverName name of the solver (to name accessors) + * @param[in] elemManager reference to the element region manager + * @param[in] stencilWrapper reference to the stencil wrapper + * @param[in] dt time step size + * @param[inout] localMatrix the local CRS matrix + * @param[inout] localRhs the local right-hand side vector + */ + template< typename POLICY > + static void + createAndLaunch( globalIndex const rankOffset, + string const & pressureDofKey, + string const & dispJumpDofKey, + string const & solverName, + ElementRegionManager const & elemManager, + SurfaceElementStencilWrapper const & stencilWrapper, + real64 const & dt, + CRSMatrixView< real64, globalIndex const > const & localMatrix, + arrayView1d< real64 > const & localRhs ) + { + integer constexpr NUM_DOF = 4; // pressure + jumps + integer constexpr NUM_EQN = 1; // pressure + jumps + + + ElementRegionManager::ElementViewAccessor< arrayView1d< globalIndex const > > pressureDofNumberAccessor = + elemManager.constructArrayViewAccessor< globalIndex, 1 >( pressureDofKey ); + pressureDofNumberAccessor.setName( solverName + "/accessors/" + pressureDofKey ); + + ElementRegionManager::ElementViewAccessor< arrayView1d< globalIndex const > > dispJumpDofNumberAccessor = + elemManager.constructArrayViewAccessor< globalIndex, 1 >( dispJumpDofKey ); + dispJumpDofNumberAccessor.setName( solverName + "/accessors/" + dispJumpDofKey ); + + using kernelType = ConnectorBasedAssemblyKernel< NUM_EQN, NUM_DOF >; + typename kernelType::SinglePhaseFlowAccessors flowAccessors( elemManager, solverName ); + typename kernelType::SinglePhaseFluidAccessors fluidAccessors( elemManager, solverName ); + typename kernelType::PermeabilityAccessors permAccessors( elemManager, solverName ); + typename kernelType::FracturePermeabilityAccessors edfmPermAccessors( elemManager, solverName ); + + + kernelType kernel( rankOffset, stencilWrapper, + pressureDofNumberAccessor, dispJumpDofNumberAccessor, + flowAccessors, fluidAccessors, permAccessors, edfmPermAccessors, + dt, localMatrix, localRhs ); + + kernelType::template launch< POLICY >( stencilWrapper.size(), kernel ); + } +}; + + + +} // namespace SinglePhaseProppantFluxKernels + +} // namespace geos + +#endif //GEOS_PHYSICSSOLVERS_MULTIPHYSICS_POROMECHANICSKERNELS_SINGLEPHASEPOROMECHANICSEMBEDDEDFRACTURES_HPP diff --git a/src/coreComponents/physicsSolvers/multiphysics/poromechanicsKernels/ThermalSinglePhasePoromechanicsConformingFractures.hpp b/src/coreComponents/physicsSolvers/multiphysics/poromechanicsKernels/ThermalSinglePhasePoromechanicsConformingFractures.hpp new file mode 100644 index 00000000000..cb5492687ed --- /dev/null +++ b/src/coreComponents/physicsSolvers/multiphysics/poromechanicsKernels/ThermalSinglePhasePoromechanicsConformingFractures.hpp @@ -0,0 +1,402 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2018-2020 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2020 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2018-2020 TotalEnergies + * Copyright (c) 2019- GEOS Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +/** + * @file SinglePhasePoromechanicsConformingFractures.hpp + */ + +#ifndef GEOS_PHYSICSSOLVERS_MULTIPHYSICS_POROMECHANICSKERNELS_THERMALSINGLEPHASEPOROMECHANICSCONFORMINGFRACTURES_HPP +#define GEOS_PHYSICSSOLVERS_MULTIPHYSICS_POROMECHANICSKERNELS_THERMALSINGLEPHASEPOROMECHANICSCONFORMINGFRACTURES_HPP + +#include "physicsSolvers/multiphysics/poromechanicsKernels/SinglePhasePoromechanicsConformingFractures.hpp" + +namespace geos +{ + +namespace thermalSinglePhasePoromechanicsConformingFracturesKernels +{ + +using namespace fluxKernelsHelper; +using namespace constitutive; + +template< integer NUM_EQN, integer NUM_DOF > +class ConnectorBasedAssemblyKernel : public singlePhasePoromechanicsConformingFracturesKernels::ConnectorBasedAssemblyKernel< NUM_EQN, NUM_DOF > +{ +public: + + /** + * @brief The type for element-based data. Consists entirely of ArrayView's. + * + * Can be converted from ElementRegionManager::ElementViewConstAccessor + * by calling .toView() or .toViewConst() on an accessor instance + */ + template< typename VIEWTYPE > + using ElementViewConst = ElementRegionManager::ElementViewConst< VIEWTYPE >; + + using SinglePhaseFVMAbstractBase = singlePhaseFVMKernels::FaceBasedAssemblyKernelBase; + using DofNumberAccessor = SinglePhaseFVMAbstractBase::DofNumberAccessor; + using SinglePhaseFlowAccessors = SinglePhaseFVMAbstractBase::SinglePhaseFlowAccessors; + using SinglePhaseFluidAccessors = SinglePhaseFVMAbstractBase::SinglePhaseFluidAccessors; + using PermeabilityAccessors = SinglePhaseFVMAbstractBase::PermeabilityAccessors; + using FracturePermeabilityAccessors = StencilMaterialAccessors< PermeabilityBase, + fields::permeability::dPerm_dDispJump >; + using SinglePhaseFVMAbstractBase::m_dt; + using SinglePhaseFVMAbstractBase::m_rankOffset; + using SinglePhaseFVMAbstractBase::m_dofNumber; + using SinglePhaseFVMAbstractBase::m_gravCoef; + using SinglePhaseFVMAbstractBase::m_mob; + using SinglePhaseFVMAbstractBase::m_dens; + + using SinglePhaseFVMBase = singlePhaseFVMKernels::FaceBasedAssemblyKernel< NUM_EQN, NUM_DOF, SurfaceElementStencilWrapper >; + using SinglePhaseFVMBase::numDof; + using SinglePhaseFVMBase::numEqn; + using SinglePhaseFVMBase::maxNumElems; + using SinglePhaseFVMBase::maxNumConns; + using SinglePhaseFVMBase::maxStencilSize; + using SinglePhaseFVMBase::m_stencilWrapper; + using SinglePhaseFVMBase::m_seri; + using SinglePhaseFVMBase::m_sesri; + using SinglePhaseFVMBase::m_sei; + using SinglePhaseFVMBase::m_ghostRank; + + using Base = singlePhasePoromechanicsConformingFracturesKernels::ConnectorBasedAssemblyKernel< NUM_EQN, NUM_DOF >; + + using ThermalSinglePhaseFlowAccessors = + StencilAccessors< fields::flow::temperature, + fields::flow::dMobility_dTemperature >; + + using ThermalSinglePhaseFluidAccessors = + StencilMaterialAccessors< SingleFluidBase, + fields::singlefluid::dDensity_dTemperature, + fields::singlefluid::enthalpy, + fields::singlefluid::dEnthalpy_dPressure, + fields::singlefluid::dEnthalpy_dTemperature >; + + using ThermalConductivityAccessors = + StencilMaterialAccessors< SinglePhaseThermalConductivityBase, + fields::thermalconductivity::effectiveConductivity >; + + + + ConnectorBasedAssemblyKernel( globalIndex const rankOffset, + SurfaceElementStencilWrapper const & stencilWrapper, + DofNumberAccessor const & flowDofNumberAccessor, + SinglePhaseFlowAccessors const & singlePhaseFlowAccessors, + ThermalSinglePhaseFlowAccessors const & thermalSinglePhaseFlowAccessors, + SinglePhaseFluidAccessors const & singlePhaseFluidAccessors, + ThermalSinglePhaseFluidAccessors const & thermalSinglePhaseFluidAccessors, + PermeabilityAccessors const & permeabilityAccessors, + FracturePermeabilityAccessors const & edfmPermeabilityAccessors, + ThermalConductivityAccessors const & thermalConductivityAccessors, + real64 const & dt, + CRSMatrixView< real64, globalIndex const > const & localMatrix, + arrayView1d< real64 > const & localRhs, + CRSMatrixView< real64, localIndex const > const & dR_dAper ) + : Base( rankOffset, + stencilWrapper, + flowDofNumberAccessor, + singlePhaseFlowAccessors, + singlePhaseFluidAccessors, + permeabilityAccessors, + edfmPermeabilityAccessors, + dt, + localMatrix, + localRhs, + dR_dAper ), + m_temp( thermalSinglePhaseFlowAccessors.get( fields::flow::temperature {} ) ), + m_dMob_dTemp( thermalSinglePhaseFlowAccessors.get( fields::flow::dMobility_dTemperature {} ) ), + m_dDens_dTemp( thermalSinglePhaseFluidAccessors.get( fields::singlefluid::dDensity_dTemperature {} ) ), + m_enthalpy( thermalSinglePhaseFluidAccessors.get( fields::singlefluid::enthalpy {} ) ), + m_dEnthalpy_dPres( thermalSinglePhaseFluidAccessors.get( fields::singlefluid::dEnthalpy_dPressure {} ) ), + m_dEnthalpy_dTemp( thermalSinglePhaseFluidAccessors.get( fields::singlefluid::dEnthalpy_dTemperature {} ) ), + m_thermalConductivity( thermalConductivityAccessors.get( fields::thermalconductivity::effectiveConductivity {} ) ) + {} + + + /** + * @struct StackVariables + * @brief Kernel variables (dof numbers, jacobian and residual) located on the stack + */ + struct StackVariables : public Base::StackVariables + { +public: + + /** + * @brief Constructor for the stack variables + * @param[in] size size of the stencil for this connection + * @param[in] numElems number of elements for this connection + */ + GEOS_HOST_DEVICE + StackVariables( localIndex const size, localIndex numElems ) + : Base::StackVariables( size, numElems ), + energyFlux( 0.0 ), + dEnergyFlux_dTrans( 0.0 ), + dEnergyFlux_dP( size ), + dEnergyFlux_dT( size ), + dEnergyFlux_dDispJump( size, 3 ) + {} + using SinglePhaseFVMBase::StackVariables::stencilSize; + using SinglePhaseFVMBase::StackVariables::numFluxElems; + using SinglePhaseFVMBase::StackVariables::transmissibility; + using SinglePhaseFVMBase::StackVariables::dTrans_dPres; + using SinglePhaseFVMBase::StackVariables::dofColIndices; + using SinglePhaseFVMBase::StackVariables::localFlux; + using SinglePhaseFVMBase::StackVariables::localFluxJacobian; + + // Thermal transmissibility (for now, no derivatives) + + real64 thermalTransmissibility[maxNumConns][2]{}; + + // Energy fluxes and derivatives + + /// Energy fluxes + real64 energyFlux; + /// Derivative of the Energy fluxes wrt transmissibility + real64 dEnergyFlux_dTrans; + /// Derivatives of energy fluxes wrt pressure + stackArray1d< real64, maxStencilSize > dEnergyFlux_dP; + /// Derivatives of energy fluxes wrt temperature + stackArray1d< real64, maxStencilSize > dEnergyFlux_dT; + /// Derivatives of energy fluxes wrt dispJump + stackArray2d< real64, maxStencilSize *3 > dEnergyFlux_dDispJump{}; + + }; + + /** + * @brief Compute the local flux contributions to the residual and Jacobian + * @tparam FUNC the type of the function that can be used to customize the computation of the flux + * @param[in] iconn the connection index + * @param[inout] stack the stack variables + */ + GEOS_HOST_DEVICE + void computeFlux( localIndex const iconn, + StackVariables & stack ) const + + { + // *********************************************** + // First, we call the base computeFlux to compute: + // 1) compFlux and its derivatives (including derivatives wrt temperature), + // 2) enthalpy part of energyFlux and its derivatives (including derivatives wrt temperature) + // + // Computing dFlux_dT and the enthalpy flux requires quantities already computed in the base computeFlux, + // such as potGrad, fluxVal, and the indices of the upwind cell + // We use the lambda below (called **inside** the phase loop of the base computeFlux) to access these variables + Base::computeFlux( iconn, stack, [&] ( localIndex const (&k)[2], + localIndex const (&seri)[2], + localIndex const (&sesri)[2], + localIndex const (&sei)[2], + localIndex const, + real64 const & alpha, + real64 const & mobility, + real64 const & potGrad, + real64 const & massFlux, + real64 const & dMassFlux_dTrans, + real64 const (&dMassFlux_dP)[2] ) + { + real64 trans[2] = {stack.transmissibility[0][0], stack.transmissibility[0][1]}; + real64 dMassFlux_dT[2]{}; + + computeEnthalpyFlux( seri, sesri, sei, + trans, + m_enthalpy, + m_dEnthalpy_dPres, + m_dEnthalpy_dTemp, + m_gravCoef, + m_dDens_dTemp, + m_dMob_dTemp, + alpha, + mobility, + potGrad, + massFlux, + dMassFlux_dTrans, + dMassFlux_dP, + dMassFlux_dT, + stack.energyFlux, + stack.dEnergyFlux_dTrans, + stack.dEnergyFlux_dP, + stack.dEnergyFlux_dT ); + + // add dMassFlux_dT to localFluxJacobian + for( integer ke = 0; ke < 2; ++ke ) + { + localIndex const localDofIndexTemp = k[ke] * numDof + numDof - 1; + stack.localFluxJacobian[k[0]*numEqn][localDofIndexTemp] += m_dt * dMassFlux_dT[ke]; + stack.localFluxJacobian[k[1]*numEqn][localDofIndexTemp] -= m_dt * dMassFlux_dT[ke]; + } + } ); + + // ***************************************************** + // Computation of the conduction term in the energy flux + // Note that the enthalpy term in the energy was computed above + // Note that this term is computed using an explicit treatment of conductivity for now + + // Step 1: compute the thermal transmissibilities at this face + m_stencilWrapper.computeWeights( iconn, + m_thermalConductivity, + m_thermalConductivity, // we have to pass something here, so we just use thermal conductivity + stack.thermalTransmissibility, + stack.dTrans_dPres ); // again, we have to pass something here, but this is unused for now + + localIndex k[2]; + localIndex connectionIndex = 0; + + for( k[0] = 0; k[0] < stack.numFluxElems; ++k[0] ) + { + for( k[1] = k[0] + 1; k[1] < stack.numFluxElems; ++k[1] ) + { + real64 const thermalTrans[2] = { stack.thermalTransmissibility[connectionIndex][0], stack.thermalTransmissibility[connectionIndex][1] }; + + localIndex const seri[2] = {m_seri( iconn, k[0] ), m_seri( iconn, k[1] )}; + localIndex const sesri[2] = {m_sesri( iconn, k[0] ), m_sesri( iconn, k[1] )}; + localIndex const sei[2] = {m_sei( iconn, k[0] ), m_sei( iconn, k[1] )}; + + // Step 2: compute temperature difference at the interface + computeConductiveFlux( seri, sesri, sei, m_temp, thermalTrans, stack.energyFlux, stack.dEnergyFlux_dT ); + + // add energyFlux and its derivatives to localFlux and localFluxJacobian + stack.localFlux[k[0]*numEqn + numEqn - 1] += m_dt * stack.energyFlux; + stack.localFlux[k[1]*numEqn + numEqn - 1] -= m_dt * stack.energyFlux; + + for( integer ke = 0; ke < 2; ++ke ) + { + integer const localDofIndexPres = k[ke] * numDof; + stack.localFluxJacobian[k[0]*numEqn + numEqn - 1][localDofIndexPres] = m_dt * stack.dEnergyFlux_dP[ke]; + stack.localFluxJacobian[k[1]*numEqn + numEqn - 1][localDofIndexPres] = -m_dt * stack.dEnergyFlux_dP[ke]; + integer const localDofIndexTemp = localDofIndexPres + 1; + stack.localFluxJacobian[k[0]*numEqn + numEqn - 1][localDofIndexTemp] = m_dt * stack.dEnergyFlux_dT[ke]; + stack.localFluxJacobian[k[1]*numEqn + numEqn - 1][localDofIndexTemp] = -m_dt * stack.dEnergyFlux_dT[ke]; + } + + connectionIndex++; + } + } + } + + /** + * @brief Performs the complete phase for the kernel. + * @param[in] iconn the connection index + * @param[inout] stack the stack variables + */ + GEOS_HOST_DEVICE + void complete( localIndex const iconn, + StackVariables & stack ) const + { + // Call Base::complete to assemble the mass balance equations + // In the lambda, add contribution to residual and jacobian into the energy balance equation + Base::complete( iconn, stack, [&] ( integer const i, + localIndex const localRow ) + { + // The no. of fluxes is equal to the no. of equations in m_localRhs and m_localMatrix + RAJA::atomicAdd( parallelDeviceAtomic{}, &SinglePhaseFVMAbstractBase::m_localRhs[localRow + numEqn-1], stack.localFlux[i * numEqn + numEqn-1] ); + + SinglePhaseFVMAbstractBase::m_localMatrix.addToRowBinarySearchUnsorted< parallelDeviceAtomic >( localRow + numEqn-1, + stack.dofColIndices.data(), + stack.localFluxJacobian[i * numEqn + numEqn-1].dataIfContiguous(), + stack.stencilSize * numDof ); + + } ); + } + + +private: + + /// Views on temperature + ElementViewConst< arrayView1d< real64 const > > const m_temp; + + /// Views on derivatives of fluid mobilities + ElementViewConst< arrayView1d< real64 const > > const m_dMob_dTemp; + + /// Views on derivatives of fluid densities + ElementViewConst< arrayView2d< real64 const > > const m_dDens_dTemp; + + /// Views on enthalpies + ElementViewConst< arrayView2d< real64 const > > const m_enthalpy; + ElementViewConst< arrayView2d< real64 const > > const m_dEnthalpy_dPres; + ElementViewConst< arrayView2d< real64 const > > const m_dEnthalpy_dTemp; + + /// View on thermal conductivity + ElementViewConst< arrayView3d< real64 const > > m_thermalConductivity; + +}; + + + +/** + * @class FaceBasedAssemblyKernelFactory + */ +class ConnectorBasedAssemblyKernelFactory +{ +public: + + /** + * @brief Create a new kernel and launch + * @tparam POLICY the policy used in the RAJA kernel + * @tparam STENCILWRAPPER the type of the stencil wrapper + * @param[in] rankOffset the offset of my MPI rank + * @param[in] dofKey string to get the element degrees of freedom numbers + * @param[in] solverName name of the solver (to name accessors) + * @param[in] elemManager reference to the element region manager + * @param[in] stencilWrapper reference to the stencil wrapper + * @param[in] dt time step size + * @param[inout] localMatrix the local CRS matrix + * @param[inout] localRhs the local right-hand side vector + */ + template< typename POLICY > + static void + createAndLaunch( globalIndex const rankOffset, + string const & flowDofKey, + string const & solverName, + ElementRegionManager const & elemManager, + SurfaceElementStencilWrapper const & stencilWrapper, + real64 const & dt, + CRSMatrixView< real64, globalIndex const > const & localMatrix, + arrayView1d< real64 > const & localRhs, + CRSMatrixView< real64, localIndex const > const & dR_dAper ) + { + integer constexpr NUM_DOF = 2; // pressure + temperature + integer constexpr NUM_EQN = 2; // mass balance + energy balance + + + ElementRegionManager::ElementViewAccessor< arrayView1d< globalIndex const > > flowDofNumberAccessor = + elemManager.constructArrayViewAccessor< globalIndex, 1 >( flowDofKey ); + flowDofNumberAccessor.setName( solverName + "/accessors/" + flowDofKey ); + + using kernelType = ConnectorBasedAssemblyKernel< NUM_EQN, NUM_DOF >; + typename kernelType::SinglePhaseFlowAccessors flowAccessors( elemManager, solverName ); + typename kernelType::ThermalSinglePhaseFlowAccessors thermalFlowAccessors( elemManager, solverName ); + + typename kernelType::SinglePhaseFluidAccessors fluidAccessors( elemManager, solverName ); + typename kernelType::ThermalSinglePhaseFluidAccessors thermalFluidAccessors( elemManager, solverName ); + + typename kernelType::PermeabilityAccessors permAccessors( elemManager, solverName ); + typename kernelType::FracturePermeabilityAccessors edfmPermAccessors( elemManager, solverName ); + typename kernelType::ThermalConductivityAccessors thermalConductivityAccessors( elemManager, solverName ); + + kernelType kernel( rankOffset, stencilWrapper, + flowDofNumberAccessor, + flowAccessors, thermalFlowAccessors, fluidAccessors, thermalFluidAccessors, + permAccessors, edfmPermAccessors, thermalConductivityAccessors, + dt, localMatrix, localRhs, dR_dAper ); + + kernelType::template launch< POLICY >( stencilWrapper.size(), kernel ); + } +}; + + + +} // namespace SinglePhaseProppantFluxKernels + +} // namespace geos + +#endif //GEOS_PHYSICSSOLVERS_MULTIPHYSICS_POROMECHANICSKERNELS_SINGLEPHASEPOROMECHANICSCONFORMINGFRACTURES_HPP diff --git a/src/coreComponents/physicsSolvers/multiphysics/poromechanicsKernels/ThermalSinglePhasePoromechanicsEFEM.hpp b/src/coreComponents/physicsSolvers/multiphysics/poromechanicsKernels/ThermalSinglePhasePoromechanicsEFEM.hpp new file mode 100644 index 00000000000..460b57f3c0c --- /dev/null +++ b/src/coreComponents/physicsSolvers/multiphysics/poromechanicsKernels/ThermalSinglePhasePoromechanicsEFEM.hpp @@ -0,0 +1,193 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2018-2020 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2020 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2018-2020 TotalEnergies + * Copyright (c) 2019- GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +/** + * @file ThermalSinglePhasePoromechanicsEFEM.hpp + */ + +#ifndef GEOS_PHYSICSSOLVERS_MULTIPHYSICS_POROMECHANICSKERNELS_THERMALSINGLEPHASEPOROMECHANICSEFEM_HPP_ +#define GEOS_PHYSICSSOLVERS_MULTIPHYSICS_POROMECHANICSKERNELS_THERMALSINGLEPHASEPOROMECHANICSEFEM_HPP_ + +#include "physicsSolvers/multiphysics/poromechanicsKernels/SinglePhasePoromechanicsEFEM.hpp" + +namespace geos +{ + +namespace thermoPoromechanicsEFEMKernels +{ +template< typename SUBREGION_TYPE, + typename CONSTITUTIVE_TYPE, + typename FE_TYPE > +class ThermalSinglePhasePoromechanicsEFEM : + public poromechanicsEFEMKernels::SinglePhasePoromechanicsEFEM< SUBREGION_TYPE, + CONSTITUTIVE_TYPE, + FE_TYPE > +{ +public: + /// Alias for the base class; + using Base = poromechanicsEFEMKernels::SinglePhasePoromechanicsEFEM< SUBREGION_TYPE, + CONSTITUTIVE_TYPE, + FE_TYPE >; + + static constexpr int numNodesPerElem = Base::maxNumTestSupportPointsPerElem; + static constexpr int numQuadraturePointsPerElem = FE_TYPE::numQuadraturePoints; + + using Base::numDofPerTestSupportPoint; + using Base::numDofPerTrialSupportPoint; + using Base::m_dofNumber; + using Base::m_dofRankOffset; + using Base::m_matrix; + using Base::m_rhs; + using Base::m_elemsToNodes; + using Base::m_constitutiveUpdate; + using Base::m_finiteElementSpace; + using Base::m_fracturePresDofNumber; + using Base::m_matrixPresDofNumber; + using Base::m_wDofNumber; + using Base::m_fluidDensity; + using Base::m_fluidDensity_n; + using Base::m_dFluidDensity_dPressure; + using Base::m_porosity_n; + using Base::m_surfaceArea; + using Base::m_elementVolume; + using Base::m_deltaVolume; + using Base::m_cellsToEmbeddedSurfaces; + + + + ThermalSinglePhasePoromechanicsEFEM( NodeManager const & nodeManager, + EdgeManager const & edgeManager, + FaceManager const & faceManager, + localIndex const targetRegionIndex, + SUBREGION_TYPE const & elementSubRegion, + FE_TYPE const & finiteElementSpace, + CONSTITUTIVE_TYPE & inputConstitutiveType, + EmbeddedSurfaceSubRegion const & embeddedSurfSubRegion, + arrayView1d< globalIndex const > const dispDofNumber, + arrayView1d< globalIndex const > const jumpDofNumber, + string const inputFlowDofKey, + globalIndex const rankOffset, + CRSMatrixView< real64, globalIndex const > const inputMatrix, + arrayView1d< real64 > const inputRhs, + real64 const (&inputGravityVector)[3], + string const fluidModelKey ); + + //***************************************************************************** + /** + * @class StackVariables + * @copydoc geos::finiteElement::ImplicitKernelBase::StackVariables + * + * Adds a stack array for the displacement, incremental displacement, and the + * constitutive stiffness. + */ + struct StackVariables : public Base::StackVariables + { +public: + + /// The number of displacement dofs per element. + static constexpr int numUdofs = numNodesPerElem * 3; + + + /// The number of jump dofs per element. + static constexpr int numWdofs = 3; + + /// Constructor. + GEOS_HOST_DEVICE + StackVariables(): + Base::StackVariables(), + dFluidMassIncrement_dTemperature( 0.0 ), + energyIncrement( 0.0 ), + dEnergyIncrement_dJump( 0.0 ), + dEnergyIncrement_dPressure( 0.0 ), + dEnergyIncrement_dTemperature( 0.0 ), + localKwTm{ 0.0 } + {} + + /// Derivative of fluid mass accumulation wrt temperature + real64 dFluidMassIncrement_dTemperature{}; + /// Energy accumulation + real64 energyIncrement{}; + /// Derivative of energy accumulation wrt normal jump + real64 dEnergyIncrement_dJump{}; + /// Derivative of energy accumulation wrt pressure + real64 dEnergyIncrement_dPressure{}; + /// Derivative of energy accumulation wrt temperature + real64 dEnergyIncrement_dTemperature{}; + /// C-array storage for the element local KwTm matrix. + real64 localKwTm[numWdofs]{}; + }; + //***************************************************************************** + + + //START_kernelLauncher + template< typename POLICY, + typename KERNEL_TYPE > + static real64 + kernelLaunch( localIndex const numElems, + KERNEL_TYPE const & kernelComponent ); + //END_kernelLauncher + + + GEOS_HOST_DEVICE + void setup( localIndex const k, + StackVariables & stack ) const; + + GEOS_HOST_DEVICE + void quadraturePointKernel( localIndex const k, + localIndex const q, + StackVariables & stack ) const; + + /** + * @copydoc geos::finiteElement::ImplicitKernelBase::complete + */ + GEOS_HOST_DEVICE + real64 complete( localIndex const k, + StackVariables & stack ) const; + +private: + + /// Views on fluid density derivative wrt temperature + arrayView2d< real64 const > const m_dFluidDensity_dTemperature; + + /// Views on fluid internal energy + arrayView2d< real64 const > const m_fluidInternalEnergy_n; + arrayView2d< real64 const > const m_fluidInternalEnergy; + arrayView2d< real64 const > const m_dFluidInternalEnergy_dPressure; + arrayView2d< real64 const > const m_dFluidInternalEnergy_dTemperature; + + /// Views on temperature + arrayView1d< real64 const > const m_temperature_n; + arrayView1d< real64 const > const m_temperature; + + /// The rank-global fluid pressure array. + arrayView1d< real64 const > const m_matrixTemperature; +}; + + +using ThermalSinglePhasePoromechanicsEFEMKernelFactory = finiteElement::KernelFactory< ThermalSinglePhasePoromechanicsEFEM, + EmbeddedSurfaceSubRegion const &, + arrayView1d< globalIndex const > const, + arrayView1d< globalIndex const > const, + string const, + globalIndex const, + CRSMatrixView< real64, globalIndex const > const, + arrayView1d< real64 > const, + real64 const (&)[3], + string const >; + +} // namespace thermoPoromechanicsEFEMKernels + +} /* namespace geos */ + +#endif // GEOS_PHYSICSSOLVERS_MULTIPHYSICS_POROMECHANICSKERNELS_THERMALSINGLEPHASEPOROMECHANICSEFEM_HPP_ diff --git a/src/coreComponents/physicsSolvers/multiphysics/poromechanicsKernels/ThermalSinglePhasePoromechanicsEFEM_impl.hpp b/src/coreComponents/physicsSolvers/multiphysics/poromechanicsKernels/ThermalSinglePhasePoromechanicsEFEM_impl.hpp new file mode 100644 index 00000000000..d4033f51c97 --- /dev/null +++ b/src/coreComponents/physicsSolvers/multiphysics/poromechanicsKernels/ThermalSinglePhasePoromechanicsEFEM_impl.hpp @@ -0,0 +1,231 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2018-2020 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2020 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2018-2020 TotalEnergies + * Copyright (c) 2019- GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +/** + * @file ThermalSinglePhasePoromechanicsEFEM_impl.hpp + */ + +#ifndef GEOS_PHYSICSSOLVERS_MULTIPHYSICS_POROMECHANICSKERNELS_THERMALSINGLEPHASEPOROMECHANICSEFEM_IMPL_HPP_ +#define GEOS_PHYSICSSOLVERS_MULTIPHYSICS_POROMECHANICSKERNELS_THERMALSINGLEPHASEPOROMECHANICSEFEM_IMPL_HPP_ + +#include "physicsSolvers/multiphysics/poromechanicsKernels/ThermalSinglePhasePoromechanicsEFEM.hpp" + +namespace geos +{ + +namespace thermoPoromechanicsEFEMKernels +{ + +template< typename SUBREGION_TYPE, + typename CONSTITUTIVE_TYPE, + typename FE_TYPE > +ThermalSinglePhasePoromechanicsEFEM< SUBREGION_TYPE, CONSTITUTIVE_TYPE, FE_TYPE >:: +ThermalSinglePhasePoromechanicsEFEM( NodeManager const & nodeManager, + EdgeManager const & edgeManager, + FaceManager const & faceManager, + localIndex const targetRegionIndex, + SUBREGION_TYPE const & elementSubRegion, + FE_TYPE const & finiteElementSpace, + CONSTITUTIVE_TYPE & inputConstitutiveType, + EmbeddedSurfaceSubRegion const & embeddedSurfSubRegion, + arrayView1d< globalIndex const > const dispDofNumber, + arrayView1d< globalIndex const > const jumpDofNumber, + string const inputFlowDofKey, + globalIndex const rankOffset, + CRSMatrixView< real64, globalIndex const > const inputMatrix, + arrayView1d< real64 > const inputRhs, + real64 const (&inputGravityVector)[3], + string const fluidModelKey ): + Base( nodeManager, + edgeManager, + faceManager, + targetRegionIndex, + elementSubRegion, + finiteElementSpace, + inputConstitutiveType, + embeddedSurfSubRegion, + dispDofNumber, + jumpDofNumber, + inputFlowDofKey, + rankOffset, + inputMatrix, + inputRhs, + inputGravityVector, + fluidModelKey ), + m_dFluidDensity_dTemperature( embeddedSurfSubRegion.template getConstitutiveModel< constitutive::SingleFluidBase >( elementSubRegion.template getReference< string >( + fluidModelKey ) ).dDensity_dTemperature() ), + m_fluidInternalEnergy_n( embeddedSurfSubRegion.template getConstitutiveModel< constitutive::SingleFluidBase >( elementSubRegion.template getReference< string >( fluidModelKey ) ).internalEnergy_n() ), + m_fluidInternalEnergy( embeddedSurfSubRegion.template getConstitutiveModel< constitutive::SingleFluidBase >( elementSubRegion.template getReference< string >( fluidModelKey ) ).internalEnergy() ), + m_dFluidInternalEnergy_dPressure( embeddedSurfSubRegion.template getConstitutiveModel< constitutive::SingleFluidBase >( elementSubRegion.template getReference< string >( + fluidModelKey ) ).dInternalEnergy_dPressure() ), + m_dFluidInternalEnergy_dTemperature( embeddedSurfSubRegion.template getConstitutiveModel< constitutive::SingleFluidBase >( elementSubRegion.template getReference< string >( + fluidModelKey ) ).dInternalEnergy_dTemperature() ), + m_temperature_n( embeddedSurfSubRegion.template getField< fields::flow::temperature_n >() ), + m_temperature( embeddedSurfSubRegion.template getField< fields::flow::temperature >() ), + m_matrixTemperature( elementSubRegion.template getField< fields::flow::temperature >() ) + +{} + + +//START_kernelLauncher +template< typename SUBREGION_TYPE, + typename CONSTITUTIVE_TYPE, + typename FE_TYPE > +template< typename POLICY, + typename KERNEL_TYPE > +real64 +ThermalSinglePhasePoromechanicsEFEM< SUBREGION_TYPE, CONSTITUTIVE_TYPE, FE_TYPE >:: +kernelLaunch( localIndex const numElems, + KERNEL_TYPE const & kernelComponent ) +{ + return Base::template kernelLaunch< POLICY >( numElems, kernelComponent ); +} +//END_kernelLauncher + + +template< typename SUBREGION_TYPE, + typename CONSTITUTIVE_TYPE, + typename FE_TYPE > +GEOS_HOST_DEVICE +GEOS_FORCE_INLINE +void ThermalSinglePhasePoromechanicsEFEM< SUBREGION_TYPE, CONSTITUTIVE_TYPE, FE_TYPE >:: +setup( localIndex const k, + StackVariables & stack ) const +{ + Base::setup( k, stack ); +} + +template< typename SUBREGION_TYPE, + typename CONSTITUTIVE_TYPE, + typename FE_TYPE > +GEOS_HOST_DEVICE +GEOS_FORCE_INLINE +void ThermalSinglePhasePoromechanicsEFEM< SUBREGION_TYPE, CONSTITUTIVE_TYPE, FE_TYPE >:: +quadraturePointKernel( localIndex const k, + localIndex const q, + StackVariables & stack ) const +{ + + Base::quadraturePointKernel( k, q, stack, [&] ( real64 const (&eqMatrix)[3][6], + real64 const detJ ) + { + real64 KwTm_gauss[3]{}; + real64 thermalExpansionCoefficient{}; + + m_constitutiveUpdate.getThermalExpansionCoefficient( k, thermalExpansionCoefficient ); + + // assemble KwTmLocal + LvArray::tensorOps::fill< 3 >( KwTm_gauss, 0 ); + for( int i=0; i < 3; ++i ) + { + KwTm_gauss[0] += eqMatrix[0][i]; + KwTm_gauss[1] += eqMatrix[1][i]; + KwTm_gauss[2] += eqMatrix[2][i]; + } + LvArray::tensorOps::scaledAdd< 3 >( stack.localKwTm, KwTm_gauss, 3*detJ*thermalExpansionCoefficient ); + } ); + +} + +template< typename SUBREGION_TYPE, + typename CONSTITUTIVE_TYPE, + typename FE_TYPE > +GEOS_HOST_DEVICE +GEOS_FORCE_INLINE +real64 ThermalSinglePhasePoromechanicsEFEM< SUBREGION_TYPE, CONSTITUTIVE_TYPE, FE_TYPE >:: +complete( localIndex const k, + StackVariables & stack ) const +{ + real64 const maxForce = Base::complete( k, stack ); + + + // add pore pressure contribution + real64 localJumpResidual_tempContribution[3]{}; + LvArray::tensorOps::scaledAdd< 3 >( localJumpResidual_tempContribution, stack.localKwTm, m_matrixTemperature[ k ] ); + + globalIndex const matrixTemperatureColIndex = m_matrixPresDofNumber[k] + 1; + for( localIndex i=0; i < 3; ++i ) + { + localIndex const dof = LvArray::integerConversion< localIndex >( stack.jumpEqnRowIndices[ i ] ); + + if( dof < 0 || dof >= m_matrix.numRows() ) + continue; + + RAJA::atomicAdd< parallelDeviceAtomic >( &m_rhs[dof], localJumpResidual_tempContribution[i] ); + + m_matrix.template addToRowBinarySearchUnsorted< parallelDeviceAtomic >( dof, + &matrixTemperatureColIndex, + &stack.localKwTm[i], + 1 ); + } + + localIndex const embSurfIndex = m_cellsToEmbeddedSurfaces[k][0]; + // Energy balance accumulation + real64 const volume = m_elementVolume( embSurfIndex ) + m_deltaVolume( embSurfIndex ); + real64 const volume_n = m_elementVolume( embSurfIndex ); + real64 const fluidEnergy = m_fluidDensity( embSurfIndex, 0 ) * m_fluidInternalEnergy( embSurfIndex, 0 ) * volume; + real64 const fluidEnergy_n = m_fluidDensity_n( embSurfIndex, 0 ) * m_fluidInternalEnergy_n( embSurfIndex, 0 ) * volume_n; + + stack.dFluidMassIncrement_dTemperature = m_dFluidDensity_dTemperature( embSurfIndex, 0 ) * volume; + + stack.energyIncrement = fluidEnergy - fluidEnergy_n; + stack.dEnergyIncrement_dJump = m_fluidDensity( embSurfIndex, 0 ) * m_fluidInternalEnergy( embSurfIndex, 0 ) * m_surfaceArea[ embSurfIndex ]; + stack.dEnergyIncrement_dPressure = m_dFluidDensity_dPressure( embSurfIndex, 0 ) * m_fluidInternalEnergy( embSurfIndex, 0 ) * volume; + stack.dEnergyIncrement_dTemperature = ( m_dFluidDensity_dTemperature( embSurfIndex, 0 ) * m_fluidInternalEnergy( embSurfIndex, 0 ) + + m_fluidDensity( embSurfIndex, 0 ) * m_dFluidInternalEnergy_dTemperature( embSurfIndex, 0 ) ) * volume; + + globalIndex const fracturePressureDof = m_fracturePresDofNumber[ embSurfIndex ]; + globalIndex const fractureTemperatureDof = m_fracturePresDofNumber[ embSurfIndex ] + 1; + localIndex const massBalanceEquationIndex = fracturePressureDof - m_dofRankOffset; + localIndex const energyBalanceEquationIndex = massBalanceEquationIndex + 1; + + if( massBalanceEquationIndex >= 0 && massBalanceEquationIndex < m_matrix.numRows() ) + { + + m_matrix.template addToRowBinarySearchUnsorted< parallelDeviceAtomic >( massBalanceEquationIndex, + &fractureTemperatureDof, + &stack.dFluidMassIncrement_dTemperature, + 1 ); + } + + if( energyBalanceEquationIndex >= 0 && energyBalanceEquationIndex < m_matrix.numRows() ) + { + + m_matrix.template addToRowBinarySearchUnsorted< parallelDeviceAtomic >( energyBalanceEquationIndex, + &stack.jumpColIndices[0], + &stack.dEnergyIncrement_dJump, + 1 ); + + m_matrix.template addToRowBinarySearchUnsorted< parallelDeviceAtomic >( energyBalanceEquationIndex, + &fracturePressureDof, + &stack.dEnergyIncrement_dPressure, + 1 ); + + m_matrix.template addToRowBinarySearchUnsorted< parallelDeviceAtomic >( energyBalanceEquationIndex, + &fractureTemperatureDof, + &stack.dEnergyIncrement_dTemperature, + 1 ); + + RAJA::atomicAdd< serialAtomic >( &m_rhs[ energyBalanceEquationIndex ], stack.energyIncrement ); + } + + return maxForce; +} + + +} // namespace thermoPoromechanicsEFEMKernels + +} /* namespace geos */ + +#endif // GEOS_PHYSICSSOLVERS_MULTIPHYSICS_POROMECHANICSKERNELS_THERMALSINGLEPHASEPOROMECHANICSEFEM_IMPL_HPP_ diff --git a/src/coreComponents/physicsSolvers/multiphysics/poromechanicsKernels/ThermalSinglePhasePoromechanicsEmbeddedFractures.hpp b/src/coreComponents/physicsSolvers/multiphysics/poromechanicsKernels/ThermalSinglePhasePoromechanicsEmbeddedFractures.hpp new file mode 100644 index 00000000000..5df71d3b640 --- /dev/null +++ b/src/coreComponents/physicsSolvers/multiphysics/poromechanicsKernels/ThermalSinglePhasePoromechanicsEmbeddedFractures.hpp @@ -0,0 +1,418 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2018-2020 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2020 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2018-2020 TotalEnergies + * Copyright (c) 2019- GEOS Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +/** + * @file SinglePhasePoromechanicsEmbeddedFractures.hpp + */ + +#ifndef GEOS_PHYSICSSOLVERS_MULTIPHYSICS_POROMECHANICSKERNELS_THERMALSINGLEPHASEPOROMECHANICSEMBEDDEDFRACTURES_HPP +#define GEOS_PHYSICSSOLVERS_MULTIPHYSICS_POROMECHANICSKERNELS_THERMALSINGLEPHASEPOROMECHANICSEMBEDDEDFRACTURES_HPP + +#include "physicsSolvers/multiphysics/poromechanicsKernels/SinglePhasePoromechanicsEmbeddedFractures.hpp" + +namespace geos +{ + +namespace thermalSinglePhasePoromechanicsEmbeddedFracturesKernels +{ + +using namespace fluxKernelsHelper; +using namespace constitutive; + +template< integer NUM_EQN, integer NUM_DOF > +class ConnectorBasedAssemblyKernel : public singlePhasePoromechanicsEmbeddedFracturesKernels::ConnectorBasedAssemblyKernel< NUM_EQN, NUM_DOF > +{ +public: + + /** + * @brief The type for element-based data. Consists entirely of ArrayView's. + * + * Can be converted from ElementRegionManager::ElementViewConstAccessor + * by calling .toView() or .toViewConst() on an accessor instance + */ + template< typename VIEWTYPE > + using ElementViewConst = ElementRegionManager::ElementViewConst< VIEWTYPE >; + + using SinglePhaseFVMAbstractBase = singlePhaseFVMKernels::FaceBasedAssemblyKernelBase; + using DofNumberAccessor = SinglePhaseFVMAbstractBase::DofNumberAccessor; + using SinglePhaseFlowAccessors = SinglePhaseFVMAbstractBase::SinglePhaseFlowAccessors; + using SinglePhaseFluidAccessors = SinglePhaseFVMAbstractBase::SinglePhaseFluidAccessors; + using PermeabilityAccessors = SinglePhaseFVMAbstractBase::PermeabilityAccessors; + using FracturePermeabilityAccessors = StencilMaterialAccessors< PermeabilityBase, + fields::permeability::dPerm_dDispJump >; + using SinglePhaseFVMAbstractBase::m_dt; + using SinglePhaseFVMAbstractBase::m_rankOffset; + using SinglePhaseFVMAbstractBase::m_dofNumber; + using SinglePhaseFVMAbstractBase::m_gravCoef; + using SinglePhaseFVMAbstractBase::m_mob; + using SinglePhaseFVMAbstractBase::m_dens; + + using SinglePhaseFVMBase = singlePhaseFVMKernels::FaceBasedAssemblyKernel< NUM_EQN, NUM_DOF, SurfaceElementStencilWrapper >; + using SinglePhaseFVMBase::numDof; + using SinglePhaseFVMBase::numEqn; + using SinglePhaseFVMBase::maxNumElems; + using SinglePhaseFVMBase::maxNumConns; + using SinglePhaseFVMBase::maxStencilSize; + using SinglePhaseFVMBase::m_stencilWrapper; + using SinglePhaseFVMBase::m_seri; + using SinglePhaseFVMBase::m_sesri; + using SinglePhaseFVMBase::m_sei; + using SinglePhaseFVMBase::m_ghostRank; + + using Base = singlePhasePoromechanicsEmbeddedFracturesKernels::ConnectorBasedAssemblyKernel< NUM_EQN, NUM_DOF >; + + using ThermalSinglePhaseFlowAccessors = + StencilAccessors< fields::flow::temperature, + fields::flow::dMobility_dTemperature >; + + using ThermalSinglePhaseFluidAccessors = + StencilMaterialAccessors< SingleFluidBase, + fields::singlefluid::dDensity_dTemperature, + fields::singlefluid::enthalpy, + fields::singlefluid::dEnthalpy_dPressure, + fields::singlefluid::dEnthalpy_dTemperature >; + + using ThermalConductivityAccessors = + StencilMaterialAccessors< SinglePhaseThermalConductivityBase, + fields::thermalconductivity::effectiveConductivity >; + + + + ConnectorBasedAssemblyKernel( globalIndex const rankOffset, + SurfaceElementStencilWrapper const & stencilWrapper, + DofNumberAccessor const & flowDofNumberAccessor, + DofNumberAccessor const & dispJumpDofNumberAccessor, + SinglePhaseFlowAccessors const & singlePhaseFlowAccessors, + ThermalSinglePhaseFlowAccessors const & thermalSinglePhaseFlowAccessors, + SinglePhaseFluidAccessors const & singlePhaseFluidAccessors, + ThermalSinglePhaseFluidAccessors const & thermalSinglePhaseFluidAccessors, + PermeabilityAccessors const & permeabilityAccessors, + FracturePermeabilityAccessors const & edfmPermeabilityAccessors, + ThermalConductivityAccessors const & thermalConductivityAccessors, + real64 const & dt, + CRSMatrixView< real64, globalIndex const > const & localMatrix, + arrayView1d< real64 > const & localRhs ) + : Base( rankOffset, + stencilWrapper, + flowDofNumberAccessor, + dispJumpDofNumberAccessor, + singlePhaseFlowAccessors, + singlePhaseFluidAccessors, + permeabilityAccessors, + edfmPermeabilityAccessors, + dt, + localMatrix, + localRhs ), + m_temp( thermalSinglePhaseFlowAccessors.get( fields::flow::temperature {} ) ), + m_dMob_dTemp( thermalSinglePhaseFlowAccessors.get( fields::flow::dMobility_dTemperature {} ) ), + m_dDens_dTemp( thermalSinglePhaseFluidAccessors.get( fields::singlefluid::dDensity_dTemperature {} ) ), + m_enthalpy( thermalSinglePhaseFluidAccessors.get( fields::singlefluid::enthalpy {} ) ), + m_dEnthalpy_dPres( thermalSinglePhaseFluidAccessors.get( fields::singlefluid::dEnthalpy_dPressure {} ) ), + m_dEnthalpy_dTemp( thermalSinglePhaseFluidAccessors.get( fields::singlefluid::dEnthalpy_dTemperature {} ) ), + m_thermalConductivity( thermalConductivityAccessors.get( fields::thermalconductivity::effectiveConductivity {} ) ) + {} + + + /** + * @struct StackVariables + * @brief Kernel variables (dof numbers, jacobian and residual) located on the stack + */ + struct StackVariables : public Base::StackVariables + { +public: + + /** + * @brief Constructor for the stack variables + * @param[in] size size of the stencil for this connection + * @param[in] numElems number of elements for this connection + */ + GEOS_HOST_DEVICE + StackVariables( localIndex const size, localIndex numElems ) + : Base::StackVariables( size, numElems ), + energyFlux( 0.0 ), + dEnergyFlux_dTrans( 0.0 ), + dEnergyFlux_dP( size ), + dEnergyFlux_dT( size ), + dEnergyFlux_dDispJump( size, 3 ) + {} + using SinglePhaseFVMBase::StackVariables::stencilSize; + using SinglePhaseFVMBase::StackVariables::numFluxElems; + using SinglePhaseFVMBase::StackVariables::transmissibility; + using SinglePhaseFVMBase::StackVariables::dTrans_dPres; + using SinglePhaseFVMBase::StackVariables::dofColIndices; + using SinglePhaseFVMBase::StackVariables::localFlux; + using SinglePhaseFVMBase::StackVariables::localFluxJacobian; + + // Thermal transmissibility (for now, no derivatives) + + real64 thermalTransmissibility[maxNumConns][2]{}; + + // Energy fluxes and derivatives + + /// Energy fluxes + real64 energyFlux; + /// Derivative of the Energy fluxes wrt transmissibility + real64 dEnergyFlux_dTrans; + /// Derivatives of energy fluxes wrt pressure + stackArray1d< real64, maxStencilSize > dEnergyFlux_dP; + /// Derivatives of energy fluxes wrt temperature + stackArray1d< real64, maxStencilSize > dEnergyFlux_dT; + /// Derivatives of energy fluxes wrt dispJump + stackArray2d< real64, maxStencilSize *3 > dEnergyFlux_dDispJump{}; + + }; + + /** + * @brief Compute the local flux contributions to the residual and Jacobian + * @tparam FUNC the type of the function that can be used to customize the computation of the flux + * @param[in] iconn the connection index + * @param[inout] stack the stack variables + */ + GEOS_HOST_DEVICE + void computeFlux( localIndex const iconn, + StackVariables & stack ) const + + { + // *********************************************** + // First, we call the base computeFlux to compute: + // 1) compFlux and its derivatives (including derivatives wrt temperature), + // 2) enthalpy part of energyFlux and its derivatives (including derivatives wrt temperature) + // + // Computing dFlux_dT and the enthalpy flux requires quantities already computed in the base computeFlux, + // such as potGrad, fluxVal, and the indices of the upwind cell + // We use the lambda below (called **inside** the phase loop of the base computeFlux) to access these variables + Base::computeFlux( iconn, stack, [&] ( localIndex const (&seri)[2], + localIndex const (&sesri)[2], + localIndex const (&sei)[2], + localIndex const, + real64 const & alpha, + real64 const & mobility, + real64 const & potGrad, + real64 const & massFlux, + real64 const & dMassFlux_dTrans, + real64 const (&dMassFlux_dP)[2] ) + { + real64 trans[2] = {stack.transmissibility[0][0], stack.transmissibility[0][1]}; + real64 dMassFlux_dT[2]{}; + + computeEnthalpyFlux( seri, sesri, sei, + trans, + m_enthalpy, + m_dEnthalpy_dPres, + m_dEnthalpy_dTemp, + m_gravCoef, + m_dDens_dTemp, + m_dMob_dTemp, + alpha, + mobility, + potGrad, + massFlux, + dMassFlux_dTrans, + dMassFlux_dP, + dMassFlux_dT, + stack.energyFlux, + stack.dEnergyFlux_dTrans, + stack.dEnergyFlux_dP, + stack.dEnergyFlux_dT ); + + for( localIndex i=0; i < 3; i++ ) + { + stack.dEnergyFlux_dDispJump[0][i] = stack.dEnergyFlux_dTrans * stack.dTrans_dDispJump[0][0][i]; + stack.dEnergyFlux_dDispJump[1][i] = -stack.dEnergyFlux_dTrans * stack.dTrans_dDispJump[0][1][i]; + } + + // add dMassFlux_dT to localFluxJacobian + for( integer ke = 0; ke < 2; ++ke ) + { + localIndex const localDofIndexTemp = ke * numDof + 1; + stack.localFluxJacobian[0*numEqn][localDofIndexTemp] += m_dt * dMassFlux_dT[ke]; + stack.localFluxJacobian[1*numEqn][localDofIndexTemp] -= m_dt * dMassFlux_dT[ke]; + integer const localDofIndexDispJumpComponent = localDofIndexTemp + 1; + for( integer i=0; i<3; i++ ) + { + stack.localFluxJacobian[0*numEqn + numEqn - 1][localDofIndexDispJumpComponent + i] = m_dt * stack.dEnergyFlux_dDispJump[ke][i]; + stack.localFluxJacobian[1*numEqn + numEqn - 1][localDofIndexDispJumpComponent + i] = -m_dt * stack.dEnergyFlux_dDispJump[ke][i]; + } + } + } ); + + // ***************************************************** + // Computation of the conduction term in the energy flux + // Note that the enthalpy term in the energy was computed above + // Note that this term is computed using an explicit treatment of conductivity for now + + // Step 1: compute the thermal transmissibilities at this face + m_stencilWrapper.computeWeights( iconn, + m_thermalConductivity, + m_thermalConductivity, // we have to pass something here, so we just use thermal conductivity + stack.thermalTransmissibility, + stack.dTrans_dPres ); // again, we have to pass something here, but this is unused for now + + localIndex k[2]; + localIndex connectionIndex = 0; + + for( k[0] = 0; k[0] < stack.numFluxElems; ++k[0] ) + { + for( k[1] = k[0] + 1; k[1] < stack.numFluxElems; ++k[1] ) + { + real64 const thermalTrans[2] = { stack.thermalTransmissibility[connectionIndex][0], stack.thermalTransmissibility[connectionIndex][1] }; + + localIndex const seri[2] = {m_seri( iconn, k[0] ), m_seri( iconn, k[1] )}; + localIndex const sesri[2] = {m_sesri( iconn, k[0] ), m_sesri( iconn, k[1] )}; + localIndex const sei[2] = {m_sei( iconn, k[0] ), m_sei( iconn, k[1] )}; + + // Step 2: compute temperature difference at the interface + computeConductiveFlux( seri, sesri, sei, m_temp, thermalTrans, stack.energyFlux, stack.dEnergyFlux_dT ); + + // add energyFlux and its derivatives to localFlux and localFluxJacobian + stack.localFlux[k[0]*numEqn + numEqn - 1] += m_dt * stack.energyFlux; + stack.localFlux[k[1]*numEqn + numEqn - 1] -= m_dt * stack.energyFlux; + + for( integer ke = 0; ke < 2; ++ke ) + { + integer const localDofIndexPres = k[ke] * numDof; + stack.localFluxJacobian[k[0]*numEqn + numEqn - 1][localDofIndexPres] = m_dt * stack.dEnergyFlux_dP[ke]; + stack.localFluxJacobian[k[1]*numEqn + numEqn - 1][localDofIndexPres] = -m_dt * stack.dEnergyFlux_dP[ke]; + integer const localDofIndexTemp = localDofIndexPres + 1; + stack.localFluxJacobian[k[0]*numEqn + numEqn - 1][localDofIndexTemp] = m_dt * stack.dEnergyFlux_dT[ke]; + stack.localFluxJacobian[k[1]*numEqn + numEqn - 1][localDofIndexTemp] = -m_dt * stack.dEnergyFlux_dT[ke]; + + } + + connectionIndex++; + } + } + } + + /** + * @brief Performs the complete phase for the kernel. + * @param[in] iconn the connection index + * @param[inout] stack the stack variables + */ + GEOS_HOST_DEVICE + void complete( localIndex const iconn, + StackVariables & stack ) const + { + // Call SinglePhaseFVMBase::complete to assemble the mass balance equations + // In the lambda, add contribution to residual and jacobian into the energy balance equation + SinglePhaseFVMBase::complete( iconn, stack, [&] ( integer const i, + localIndex const localRow ) + { + // The no. of fluxes is equal to the no. of equations in m_localRhs and m_localMatrix + RAJA::atomicAdd( parallelDeviceAtomic{}, &SinglePhaseFVMAbstractBase::m_localRhs[localRow + numEqn-1], stack.localFlux[i * numEqn + numEqn-1] ); + + SinglePhaseFVMAbstractBase::m_localMatrix.addToRowBinarySearchUnsorted< parallelDeviceAtomic >( localRow + numEqn-1, + stack.dofColIndices.data(), + stack.localFluxJacobian[i * numEqn + numEqn-1].dataIfContiguous(), + stack.stencilSize * numDof ); + + } ); + } + + +private: + + /// Views on temperature + ElementViewConst< arrayView1d< real64 const > > const m_temp; + + /// Views on derivatives of fluid mobilities + ElementViewConst< arrayView1d< real64 const > > const m_dMob_dTemp; + + /// Views on derivatives of fluid densities + ElementViewConst< arrayView2d< real64 const > > const m_dDens_dTemp; + + /// Views on enthalpies + ElementViewConst< arrayView2d< real64 const > > const m_enthalpy; + ElementViewConst< arrayView2d< real64 const > > const m_dEnthalpy_dPres; + ElementViewConst< arrayView2d< real64 const > > const m_dEnthalpy_dTemp; + + /// View on thermal conductivity + ElementViewConst< arrayView3d< real64 const > > m_thermalConductivity; + +}; + + + +/** + * @class FaceBasedAssemblyKernelFactory + */ +class ConnectorBasedAssemblyKernelFactory +{ +public: + + /** + * @brief Create a new kernel and launch + * @tparam POLICY the policy used in the RAJA kernel + * @tparam STENCILWRAPPER the type of the stencil wrapper + * @param[in] rankOffset the offset of my MPI rank + * @param[in] dofKey string to get the element degrees of freedom numbers + * @param[in] solverName name of the solver (to name accessors) + * @param[in] elemManager reference to the element region manager + * @param[in] stencilWrapper reference to the stencil wrapper + * @param[in] dt time step size + * @param[inout] localMatrix the local CRS matrix + * @param[inout] localRhs the local right-hand side vector + */ + template< typename POLICY > + static void + createAndLaunch( globalIndex const rankOffset, + string const & pressureDofKey, + string const & dispJumpDofKey, + string const & solverName, + ElementRegionManager const & elemManager, + SurfaceElementStencilWrapper const & stencilWrapper, + real64 const & dt, + CRSMatrixView< real64, globalIndex const > const & localMatrix, + arrayView1d< real64 > const & localRhs ) + { + integer constexpr NUM_DOF = 5; // pressure + temperature + jumps + integer constexpr NUM_EQN = 2; // mass balance + energy balance + + + ElementRegionManager::ElementViewAccessor< arrayView1d< globalIndex const > > flowDofNumberAccessor = + elemManager.constructArrayViewAccessor< globalIndex, 1 >( pressureDofKey ); + flowDofNumberAccessor.setName( solverName + "/accessors/" + pressureDofKey ); + + ElementRegionManager::ElementViewAccessor< arrayView1d< globalIndex const > > dispJumpDofNumberAccessor = + elemManager.constructArrayViewAccessor< globalIndex, 1 >( dispJumpDofKey ); + dispJumpDofNumberAccessor.setName( solverName + "/accessors/" + dispJumpDofKey ); + + using kernelType = ConnectorBasedAssemblyKernel< NUM_EQN, NUM_DOF >; + typename kernelType::SinglePhaseFlowAccessors flowAccessors( elemManager, solverName ); + typename kernelType::ThermalSinglePhaseFlowAccessors thermalFlowAccessors( elemManager, solverName ); + + typename kernelType::SinglePhaseFluidAccessors fluidAccessors( elemManager, solverName ); + typename kernelType::ThermalSinglePhaseFluidAccessors thermalFluidAccessors( elemManager, solverName ); + + typename kernelType::PermeabilityAccessors permAccessors( elemManager, solverName ); + typename kernelType::FracturePermeabilityAccessors edfmPermAccessors( elemManager, solverName ); + typename kernelType::ThermalConductivityAccessors thermalConductivityAccessors( elemManager, solverName ); + + kernelType kernel( rankOffset, stencilWrapper, + flowDofNumberAccessor, dispJumpDofNumberAccessor, + flowAccessors, thermalFlowAccessors, fluidAccessors, thermalFluidAccessors, + permAccessors, edfmPermAccessors, thermalConductivityAccessors, + dt, localMatrix, localRhs ); + + kernelType::template launch< POLICY >( stencilWrapper.size(), kernel ); + } +}; + + + +} // namespace SinglePhaseProppantFluxKernels + +} // namespace geos + +#endif //GEOS_PHYSICSSOLVERS_MULTIPHYSICS_POROMECHANICSKERNELS_SINGLEPHASEPOROMECHANICSEMBEDDEDFRACTURES_HPP diff --git a/src/coreComponents/physicsSolvers/multiphysics/poromechanicsKernels/ThermoPoromechanicsKernels.cpp.template b/src/coreComponents/physicsSolvers/multiphysics/poromechanicsKernels/ThermoPoromechanicsKernels.cpp.template index 29bb6cbd0d3..41201ad1c40 100644 --- a/src/coreComponents/physicsSolvers/multiphysics/poromechanicsKernels/ThermoPoromechanicsKernels.cpp.template +++ b/src/coreComponents/physicsSolvers/multiphysics/poromechanicsKernels/ThermoPoromechanicsKernels.cpp.template @@ -1,7 +1,9 @@ #include "physicsSolvers/multiphysics/poromechanicsKernels/MultiphasePoromechanics_impl.hpp" #include "physicsSolvers/multiphysics/poromechanicsKernels/SinglePhasePoromechanics_impl.hpp" +#include "physicsSolvers/multiphysics/poromechanicsKernels/SinglePhasePoromechanicsEFEM_impl.hpp" #include "physicsSolvers/multiphysics/poromechanicsKernels/ThermalMultiphasePoromechanics_impl.hpp" #include "physicsSolvers/multiphysics/poromechanicsKernels/ThermalSinglePhasePoromechanics_impl.hpp" +#include "physicsSolvers/multiphysics/poromechanicsKernels/ThermalSinglePhasePoromechanicsEFEM_impl.hpp" #include "policies.hpp" @@ -23,6 +25,11 @@ namespace thermalPoromechanicsKernels INSTANTIATION( ThermalSinglePhasePoromechanics ) } +namespace thermoPoromechanicsEFEMKernels +{ + INSTANTIATION( ThermalSinglePhasePoromechanicsEFEM ) +} + } #undef INSTANTIATION \ No newline at end of file diff --git a/src/coreComponents/physicsSolvers/multiphysics/poromechanicsKernels/policies.hpp.in b/src/coreComponents/physicsSolvers/multiphysics/poromechanicsKernels/policies.hpp.in index 4c176181e0d..4542294ac2e 100644 --- a/src/coreComponents/physicsSolvers/multiphysics/poromechanicsKernels/policies.hpp.in +++ b/src/coreComponents/physicsSolvers/multiphysics/poromechanicsKernels/policies.hpp.in @@ -6,5 +6,6 @@ using SinglePhasePoromechanicsEFEMPolicy = @SinglePhasePoromechanicsEFEMPolicy@; using MultiphasePoromechanicsPolicy = @MultiphasePoromechanicsPolicy@; using ThermalMultiphasePoromechanicsPolicy = @ThermalMultiphasePoromechanicsPolicy@; using ThermalSinglePhasePoromechanicsPolicy = @ThermalSinglePhasePoromechanicsPolicy@; +using ThermalSinglePhasePoromechanicsEFEMPolicy = @ThermalSinglePhasePoromechanicsEFEMPolicy@; #endif /* GEOS_CORECOMPONENTS_PHYSICSSOLVERS_MULTIPHYSICS_POROMECHANICSKERNELS_CONFIG_HPP */ diff --git a/src/coreComponents/physicsSolvers/solidMechanics/SolidMechanicsLagrangianFEM.cpp b/src/coreComponents/physicsSolvers/solidMechanics/SolidMechanicsLagrangianFEM.cpp index 2be56283fd8..d256ea60d9b 100644 --- a/src/coreComponents/physicsSolvers/solidMechanics/SolidMechanicsLagrangianFEM.cpp +++ b/src/coreComponents/physicsSolvers/solidMechanics/SolidMechanicsLagrangianFEM.cpp @@ -60,7 +60,7 @@ SolidMechanicsLagrangianFEM::SolidMechanicsLagrangianFEM( const string & name, m_maxNumResolves( 10 ), m_strainTheory( 0 ), m_iComm( CommunicationTools::getInstance().getCommID() ), - m_fixedStressUpdateThermoPoromechanicsFlag( 0 ) + m_isFixedStressPoromechanicsUpdate( false ) { registerWrapper( viewKeyStruct::newmarkGammaString(), &m_newmarkGamma ). @@ -654,6 +654,8 @@ void SolidMechanicsLagrangianFEM::applyDisplacementBCImplicit( real64 const time FieldSpecificationManager const & fsManager = FieldSpecificationManager::getInstance(); + integer isDisplacementBCApplied[3]{}; + forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&] ( string const &, MeshLevel & mesh, arrayView1d< string const > const & ) @@ -677,8 +679,48 @@ void SolidMechanicsLagrangianFEM::applyDisplacementBCImplicit( real64 const time dofManager.rankOffset(), localMatrix, localRhs ); + + if( targetSet.size() > 0 && bc.getComponent() == 0 ) + { + isDisplacementBCApplied[0] = 1; + } + else if( targetSet.size() > 0 && bc.getComponent() == 1 ) + { + isDisplacementBCApplied[1] = 1; + } + else if( targetSet.size() > 0 && bc.getComponent() == 2 ) + { + isDisplacementBCApplied[2] = 1; + } + } ); } ); + + // if the log level is 0, we don't need the reduction below (hence this early check) + if( getLogLevel() >= 1 ) + { + integer isDisplacementBCAppliedGlobal[3]{}; + MpiWrapper::allReduce( isDisplacementBCApplied, + isDisplacementBCAppliedGlobal, + 3, + MpiWrapper::getMpiOp( MpiWrapper::Reduction::Max ), + MPI_COMM_GEOSX ); + + char const bcLogMessage[] = + "\nWarning!" + "\n{} `{}`: There is no displacement boundary condition applied to this problem in the {} direction. \n" + "The problem may be ill-posed.\n"; + GEOS_LOG_RANK_0_IF( isDisplacementBCAppliedGlobal[0] == 0, // target set is empty + GEOS_FMT( bcLogMessage, + catalogName(), getName(), 'x' ) ); + GEOS_LOG_RANK_0_IF( isDisplacementBCAppliedGlobal[1] == 0, // target set is empty + GEOS_FMT( bcLogMessage, + catalogName(), getName(), 'y' ) ); + GEOS_LOG_RANK_0_IF( isDisplacementBCAppliedGlobal[2] == 0, // target set is empty + GEOS_FMT( bcLogMessage, + catalogName(), getName(), 'z' ) ); + } + } void SolidMechanicsLagrangianFEM::applyTractionBC( real64 const time, @@ -970,7 +1012,7 @@ void SolidMechanicsLagrangianFEM::assembleSystem( real64 const GEOS_UNUSED_PARAM localMatrix.zero(); localRhs.zero(); - if( m_fixedStressUpdateThermoPoromechanicsFlag ) + if( m_isFixedStressPoromechanicsUpdate ) { GEOS_UNUSED_VAR( dt ); assemblyLaunch< constitutive::PorousSolid< ElasticIsotropic >, // TODO: change once there is a cmake solution @@ -1336,9 +1378,9 @@ SolidMechanicsLagrangianFEM::scalingForSystemSolution( DomainPartition const & d return 1.0; } -void SolidMechanicsLagrangianFEM::turnOnFixedStressThermoPoromechanicsFlag() +void SolidMechanicsLagrangianFEM::enableFixedStressPoromechanicsUpdate() { - m_fixedStressUpdateThermoPoromechanicsFlag = 1; + m_isFixedStressPoromechanicsUpdate = true; } REGISTER_CATALOG_ENTRY( SolverBase, SolidMechanicsLagrangianFEM, string const &, dataRepository::Group * const ) diff --git a/src/coreComponents/physicsSolvers/solidMechanics/SolidMechanicsLagrangianFEM.hpp b/src/coreComponents/physicsSolvers/solidMechanics/SolidMechanicsLagrangianFEM.hpp index 29b67c8dbc5..54b645ba339 100644 --- a/src/coreComponents/physicsSolvers/solidMechanics/SolidMechanicsLagrangianFEM.hpp +++ b/src/coreComponents/physicsSolvers/solidMechanics/SolidMechanicsLagrangianFEM.hpp @@ -219,7 +219,7 @@ class SolidMechanicsLagrangianFEM : public SolverBase DofManager const & dofManager, arrayView1d< real64 const > const & localSolution ) override; - void turnOnFixedStressThermoPoromechanicsFlag(); + void enableFixedStressPoromechanicsUpdate(); struct viewKeyStruct : SolverBase::viewKeyStruct { @@ -289,7 +289,7 @@ class SolidMechanicsLagrangianFEM : public SolverBase integer m_strainTheory; string m_contactRelationName; MPI_iCommData m_iComm; - integer m_fixedStressUpdateThermoPoromechanicsFlag; + bool m_isFixedStressPoromechanicsUpdate; /// Rigid body modes array1d< ParallelVector > m_rigidBodyModes; @@ -338,7 +338,7 @@ void SolidMechanicsLagrangianFEM::assemblyLaunch( DomainPartition & domain, gravityVectorData, std::forward< PARAMS >( params )... ); - if( m_fixedStressUpdateThermoPoromechanicsFlag ) + if( m_isFixedStressPoromechanicsUpdate ) { m_maxForce = finiteElement:: regionBasedKernelApplication< parallelDevicePolicy< >, diff --git a/src/coreComponents/physicsSolvers/solidMechanics/docs/SolidMechanics.rst b/src/coreComponents/physicsSolvers/solidMechanics/docs/SolidMechanics.rst index 1b9869e2f9e..d0020fd3770 100644 --- a/src/coreComponents/physicsSolvers/solidMechanics/docs/SolidMechanics.rst +++ b/src/coreComponents/physicsSolvers/solidMechanics/docs/SolidMechanics.rst @@ -118,7 +118,7 @@ step acceleration. u^{n+1} &= \tilde{u}^{n+1} + \beta a^{n+1} \Delta t^2 \\ v^{n+1} &= \tilde{v}^{n+1} + \gamma a^{n+1} \Delta t -The acceleration and velocity may now be expressed in terms of displacement, and ultimatly in terms +The acceleration and velocity may now be expressed in terms of displacement, and ultimately in terms of the incremental displacement. .. math:: diff --git a/src/coreComponents/physicsSolvers/surfaceGeneration/EmbeddedSurfaceGenerator.cpp b/src/coreComponents/physicsSolvers/surfaceGeneration/EmbeddedSurfaceGenerator.cpp index ce1caf92647..531a85b01a1 100644 --- a/src/coreComponents/physicsSolvers/surfaceGeneration/EmbeddedSurfaceGenerator.cpp +++ b/src/coreComponents/physicsSolvers/surfaceGeneration/EmbeddedSurfaceGenerator.cpp @@ -33,7 +33,7 @@ #include "mesh/utilities/CIcomputationKernel.hpp" #include "physicsSolvers/solidMechanics/kernels/SolidMechanicsLagrangianFEMKernels.hpp" #include "mesh/simpleGeometricObjects/GeometricObjectManager.hpp" -#include "mesh/simpleGeometricObjects/BoundedPlane.hpp" +#include "mesh/simpleGeometricObjects/Rectangle.hpp" #include "physicsSolvers/fluidFlow/FlowSolverBaseFields.hpp" @@ -71,6 +71,10 @@ EmbeddedSurfaceGenerator::EmbeddedSurfaceGenerator( const string & name, setInputFlag( dataRepository::InputFlags::OPTIONAL ). setApplyDefaultValue( "FractureRegion" ); + registerWrapper( viewKeyStruct::targetObjectsNameString(), &m_targetObjectsName ). + setInputFlag( dataRepository::InputFlags::REQUIRED ). + setDescription( "List of geometric objects that will be used to initialized the embedded surfaces/fractures." ); + // this->getWrapper< string >( viewKeyStruct::discretizationString() ). // setInputFlag( InputFlags::FALSE ); @@ -128,7 +132,8 @@ void EmbeddedSurfaceGenerator::initializePostSubGroups() NewObjectLists newObjects; // Loop over all the fracture planes - geometricObjManager.forSubGroups< BoundedPlane >( [&]( BoundedPlane & fracture ) + geometricObjManager.forGeometricObject< PlanarGeometricObject >( m_targetObjectsName, [&]( localIndex const, + PlanarGeometricObject & fracture ) { /* 1. Find out if an element is cut by the fracture or not. * Loop over all the elements and for each one of them loop over the nodes and compute the @@ -200,7 +205,7 @@ void EmbeddedSurfaceGenerator::initializePostSubGroups() } } );// end loop over cells } );// end loop over subregions - } );// end loop over thick planes + } );// end loop over planes // Launch kernel to compute connectivity index of each fractured element. elemManager.forElementSubRegionsComplete< CellElementSubRegion >( @@ -219,7 +224,7 @@ void EmbeddedSurfaceGenerator::initializePostSubGroups() using KERNEL_TYPE = decltype( kernel ); - KERNEL_TYPE::template launchCIComputationKernel< parallelDevicePolicy< 32 >, KERNEL_TYPE >( kernel ); + KERNEL_TYPE::template launchCIComputationKernel< parallelDevicePolicy< >, KERNEL_TYPE >( kernel ); } ); } ); diff --git a/src/coreComponents/physicsSolvers/surfaceGeneration/EmbeddedSurfaceGenerator.hpp b/src/coreComponents/physicsSolvers/surfaceGeneration/EmbeddedSurfaceGenerator.hpp index a73487d8dbb..ee0ba0e8c44 100644 --- a/src/coreComponents/physicsSolvers/surfaceGeneration/EmbeddedSurfaceGenerator.hpp +++ b/src/coreComponents/physicsSolvers/surfaceGeneration/EmbeddedSurfaceGenerator.hpp @@ -112,6 +112,7 @@ class EmbeddedSurfaceGenerator : public SolverBase { constexpr static char const * solidMaterialNameString() {return "solidMaterialNames"; } constexpr static char const * fractureRegionNameString() {return "fractureRegion"; } + constexpr static char const * targetObjectsNameString() {return "targetObjects"; } constexpr static char const * mpiCommOrderString() { return "mpiCommOrder"; } //TODO: rock toughness should be a material parameter, and we need to make rock toughness to KIC a constitutive @@ -121,6 +122,8 @@ class EmbeddedSurfaceGenerator : public SolverBase // fracture region name string m_fractureRegionName; + // target geometric objects to turn into fractures + array1d< string > m_targetObjectsName; // Flag for consistent communication ordering int m_mpiCommOrder; }; diff --git a/src/coreComponents/physicsSolvers/wavePropagation/AcousticFirstOrderWaveEquationSEM.cpp b/src/coreComponents/physicsSolvers/wavePropagation/AcousticFirstOrderWaveEquationSEM.cpp index 82632d445a3..ad01f33626a 100644 --- a/src/coreComponents/physicsSolvers/wavePropagation/AcousticFirstOrderWaveEquationSEM.cpp +++ b/src/coreComponents/physicsSolvers/wavePropagation/AcousticFirstOrderWaveEquationSEM.cpp @@ -70,6 +70,16 @@ AcousticFirstOrderWaveEquationSEM::AcousticFirstOrderWaveEquationSEM( const std: setSizedFromParent( 0 ). setDescription( "Element containing the receivers" ); + registerWrapper( viewKeyStruct::sourceRegionString(), &m_sourceRegion ). + setInputFlag( InputFlags::FALSE ). + setSizedFromParent( 0 ). + setDescription( "Region containing the sources" ); + + registerWrapper( viewKeyStruct::receiverRegionString(), &m_receiverRegion ). + setInputFlag( InputFlags::FALSE ). + setSizedFromParent( 0 ). + setDescription( "Region containing the receivers" ); + } AcousticFirstOrderWaveEquationSEM::~AcousticFirstOrderWaveEquationSEM() @@ -87,6 +97,7 @@ void AcousticFirstOrderWaveEquationSEM::initializePreSubGroups() void AcousticFirstOrderWaveEquationSEM::registerDataOnMesh( Group & meshBodies ) { + WaveSolverBase::registerDataOnMesh( meshBodies ); forDiscretizationOnMeshTargets( meshBodies, [&] ( string const &, MeshLevel & mesh, @@ -145,9 +156,9 @@ void AcousticFirstOrderWaveEquationSEM::postProcessInput() m_uyNp1AtReceivers.resize( m_nsamplesSeismoTrace, numReceiversGlobal ); m_uzNp1AtReceivers.resize( m_nsamplesSeismoTrace, numReceiversGlobal ); m_sourceElem.resize( numSourcesGlobal ); + m_sourceRegion.resize( numSourcesGlobal ); m_rcvElem.resize( numReceiversGlobal ); - - + m_receiverRegion.resize( numReceiversGlobal ); } void AcousticFirstOrderWaveEquationSEM::precomputeSourceAndReceiverTerm( MeshLevel & mesh, arrayView1d< string const > const & regionNames ) @@ -155,8 +166,8 @@ void AcousticFirstOrderWaveEquationSEM::precomputeSourceAndReceiverTerm( MeshLev NodeManager const & nodeManager = mesh.getNodeManager(); FaceManager const & faceManager = mesh.getFaceManager(); - arrayView2d< real64 const, nodes::REFERENCE_POSITION_USD > const - X = nodeManager.referencePosition().toViewConst(); + arrayView2d< wsCoordType const, nodes::REFERENCE_POSITION_USD > const + X = nodeManager.getField< fields::referencePosition32 >().toViewConst(); arrayView2d< real64 const > const faceNormal = faceManager.faceNormal(); arrayView2d< real64 const > const faceCenter = faceManager.faceCenter(); @@ -165,6 +176,7 @@ void AcousticFirstOrderWaveEquationSEM::precomputeSourceAndReceiverTerm( MeshLev arrayView2d< real64 > const sourceConstants = m_sourceConstants.toView(); arrayView1d< localIndex > const sourceIsAccessible = m_sourceIsAccessible.toView(); arrayView1d< localIndex > const sourceElem = m_sourceElem.toView(); + arrayView1d< localIndex > const sourceRegion = m_sourceRegion.toView(); sourceNodeIds.setValues< EXEC_POLICY >( -1 ); sourceConstants.setValues< EXEC_POLICY >( -1 ); sourceIsAccessible.zero(); @@ -174,6 +186,7 @@ void AcousticFirstOrderWaveEquationSEM::precomputeSourceAndReceiverTerm( MeshLev arrayView2d< real64 > const receiverConstants = m_receiverConstants.toView(); arrayView1d< localIndex > const receiverIsLocal = m_receiverIsLocal.toView(); arrayView1d< localIndex > const rcvElem = m_rcvElem.toView(); + arrayView1d< localIndex > const receiverRegion = m_receiverRegion.toView(); receiverNodeIds.setValues< EXEC_POLICY >( -1 ); receiverConstants.setValues< EXEC_POLICY >( -1 ); receiverIsLocal.zero(); @@ -192,8 +205,7 @@ void AcousticFirstOrderWaveEquationSEM::precomputeSourceAndReceiverTerm( MeshLev } } - - mesh.getElemManager().forElementSubRegions< CellElementSubRegion >( regionNames, [&]( localIndex const, + mesh.getElemManager().forElementSubRegions< CellElementSubRegion >( regionNames, [&]( localIndex const regionIndex, CellElementSubRegion & elementSubRegion ) { GEOS_THROW_IF( elementSubRegion.getElementType() != ElementType::Hexahedron, @@ -218,6 +230,7 @@ void AcousticFirstOrderWaveEquationSEM::precomputeSourceAndReceiverTerm( MeshLev PrecomputeSourceAndReceiverKernel:: launch< EXEC_POLICY, FE_TYPE > ( elementSubRegion.size(), + regionIndex, numNodesPerElem, numFacesPerElem, X, @@ -232,11 +245,13 @@ void AcousticFirstOrderWaveEquationSEM::precomputeSourceAndReceiverTerm( MeshLev sourceElem, sourceNodeIds, sourceConstants, + sourceRegion, receiverCoordinates, receiverIsLocal, rcvElem, receiverNodeIds, receiverConstants, + receiverRegion, sourceValue, dt, timeSourceFrequency, @@ -288,7 +303,7 @@ void AcousticFirstOrderWaveEquationSEM::initializePostInitialConditionsPreSubGro /// get the array of indicators: 1 if the face is on the boundary; 0 otherwise arrayView1d< integer > const & facesDomainBoundaryIndicator = faceManager.getDomainBoundaryIndicator(); - arrayView2d< real64 const, nodes::REFERENCE_POSITION_USD > const X = nodeManager.referencePosition().toViewConst(); + arrayView2d< wsCoordType const, nodes::REFERENCE_POSITION_USD > const X = nodeManager.getField< fields::referencePosition32 >().toViewConst(); /// get table containing face to nodes map ArrayOfArraysView< localIndex const > const facesToNodes = faceManager.nodeList().toViewConst(); @@ -439,6 +454,7 @@ real64 AcousticFirstOrderWaveEquationSEM::explicitStepInternal( real64 const & t arrayView2d< real64 const > const sourceConstants = m_sourceConstants.toView(); arrayView1d< localIndex const > const sourceIsAccessible = m_sourceIsAccessible.toView(); arrayView1d< localIndex const > const sourceElem = m_sourceElem.toView(); + arrayView1d< localIndex const > const sourceRegion = m_sourceRegion.toView(); arrayView2d< real32 const > const sourceValue = m_sourceValue.toView(); GEOS_LOG_RANK_0_IF( dt < epsilonLoc, "Warning! Value for dt: " << dt << "s is smaller than local threshold: " << epsilonLoc ); @@ -450,7 +466,7 @@ real64 AcousticFirstOrderWaveEquationSEM::explicitStepInternal( real64 const & t { NodeManager & nodeManager = mesh.getNodeManager(); - arrayView2d< real64 const, nodes::REFERENCE_POSITION_USD > const X = nodeManager.referencePosition().toViewConst(); + arrayView2d< wsCoordType const, nodes::REFERENCE_POSITION_USD > const X = nodeManager.getField< fields::referencePosition32 >().toViewConst(); arrayView1d< real32 const > const mass = nodeManager.getField< wavesolverfields::MassVector >(); arrayView1d< real32 const > const damping = nodeManager.getField< wavesolverfields::DampingVector >(); @@ -459,7 +475,7 @@ real64 AcousticFirstOrderWaveEquationSEM::explicitStepInternal( real64 const & t arrayView1d< real32 > const rhs = nodeManager.getField< wavesolverfields::ForcingRHS >(); - mesh.getElemManager().forElementSubRegions< CellElementSubRegion >( regionNames, [&]( localIndex const, + mesh.getElemManager().forElementSubRegions< CellElementSubRegion >( regionNames, [&]( localIndex const regionIndex, CellElementSubRegion & elementSubRegion ) { arrayView2d< localIndex const, cells::NODE_MAP_USD > const & elemsToNodes = elementSubRegion.nodeList(); @@ -491,6 +507,7 @@ real64 AcousticFirstOrderWaveEquationSEM::explicitStepInternal( real64 const & t PressureComputation< FE_TYPE > kernel2( finiteElement ); kernel2.template launch< EXEC_POLICY, ATOMIC_POLICY > ( elementSubRegion.size(), + regionIndex, nodeManager.size(), X, elemsToNodes, @@ -503,6 +520,7 @@ real64 AcousticFirstOrderWaveEquationSEM::explicitStepInternal( real64 const & t sourceValue, sourceIsAccessible, sourceElem, + sourceRegion, dt, cycleNumber, p_np1 ); @@ -511,9 +529,9 @@ real64 AcousticFirstOrderWaveEquationSEM::explicitStepInternal( real64 const & t arrayView2d< real32 > const uyReceivers = m_uyNp1AtReceivers.toView(); arrayView2d< real32 > const uzReceivers = m_uzNp1AtReceivers.toView(); - compute2dVariableAllSeismoTraces( time_n, dt, velocity_x, velocity_x, uxReceivers ); - compute2dVariableAllSeismoTraces( time_n, dt, velocity_y, velocity_y, uyReceivers ); - compute2dVariableAllSeismoTraces( time_n, dt, velocity_z, velocity_z, uzReceivers ); + compute2dVariableAllSeismoTraces( regionIndex, time_n, dt, velocity_x, velocity_x, uxReceivers ); + compute2dVariableAllSeismoTraces( regionIndex, time_n, dt, velocity_y, velocity_y, uyReceivers ); + compute2dVariableAllSeismoTraces( regionIndex, time_n, dt, velocity_z, velocity_z, uzReceivers ); } ); @@ -550,7 +568,7 @@ void AcousticFirstOrderWaveEquationSEM::cleanup( real64 const time_n, integer co { NodeManager & nodeManager = mesh.getNodeManager(); arrayView1d< real32 const > const p_np1 = nodeManager.getField< wavesolverfields::Pressure_np1 >(); - mesh.getElemManager().forElementSubRegions< CellElementSubRegion >( regionNames, [&]( localIndex const, + mesh.getElemManager().forElementSubRegions< CellElementSubRegion >( regionNames, [&]( localIndex const regionIndex, CellElementSubRegion & elementSubRegion ) { arrayView2d< real32 > const velocity_x = elementSubRegion.getField< wavesolverfields::Velocity_x >(); @@ -561,9 +579,9 @@ void AcousticFirstOrderWaveEquationSEM::cleanup( real64 const time_n, integer co arrayView2d< real32 > const uyReceivers = m_uyNp1AtReceivers.toView(); arrayView2d< real32 > const uzReceivers = m_uzNp1AtReceivers.toView(); - compute2dVariableAllSeismoTraces( time_n, 0, velocity_x, velocity_x, uxReceivers ); - compute2dVariableAllSeismoTraces( time_n, 0, velocity_y, velocity_y, uyReceivers ); - compute2dVariableAllSeismoTraces( time_n, 0, velocity_z, velocity_z, uzReceivers ); + compute2dVariableAllSeismoTraces( regionIndex, time_n, 0, velocity_x, velocity_x, uxReceivers ); + compute2dVariableAllSeismoTraces( regionIndex, time_n, 0, velocity_y, velocity_y, uyReceivers ); + compute2dVariableAllSeismoTraces( regionIndex, time_n, 0, velocity_z, velocity_z, uzReceivers ); } ); arrayView2d< real32 > const pReceivers = m_pressureNp1AtReceivers.toView(); @@ -594,7 +612,8 @@ void AcousticFirstOrderWaveEquationSEM::computeAllSeismoTraces( real64 const tim } } -void AcousticFirstOrderWaveEquationSEM::compute2dVariableAllSeismoTraces( real64 const time_n, +void AcousticFirstOrderWaveEquationSEM::compute2dVariableAllSeismoTraces( localIndex const regionIndex, + real64 const time_n, real64 const dt, arrayView2d< real32 const > const var_np1, arrayView2d< real32 const > const var_n, @@ -605,7 +624,7 @@ void AcousticFirstOrderWaveEquationSEM::compute2dVariableAllSeismoTraces( real64 (timeSeismo = m_dtSeismoTrace*indexSeismoTrace) <= (time_n + epsilonLoc) && indexSeismoTrace < m_nsamplesSeismoTrace; indexSeismoTrace++ ) { - WaveSolverUtils::compute2dVariableSeismoTrace( time_n, dt, timeSeismo, indexSeismoTrace, m_rcvElem, m_receiverConstants, m_receiverIsLocal, m_nsamplesSeismoTrace, + WaveSolverUtils::compute2dVariableSeismoTrace( time_n, dt, regionIndex, m_receiverRegion, timeSeismo, indexSeismoTrace, m_rcvElem, m_receiverConstants, m_receiverIsLocal, m_nsamplesSeismoTrace, m_outputSeismoTrace, var_np1, var_n, varAtReceivers ); } diff --git a/src/coreComponents/physicsSolvers/wavePropagation/AcousticFirstOrderWaveEquationSEM.hpp b/src/coreComponents/physicsSolvers/wavePropagation/AcousticFirstOrderWaveEquationSEM.hpp index 54171840aae..97bcfe470ce 100644 --- a/src/coreComponents/physicsSolvers/wavePropagation/AcousticFirstOrderWaveEquationSEM.hpp +++ b/src/coreComponents/physicsSolvers/wavePropagation/AcousticFirstOrderWaveEquationSEM.hpp @@ -31,7 +31,7 @@ class AcousticFirstOrderWaveEquationSEM : public WaveSolverBase { public: - using EXEC_POLICY = parallelDevicePolicy< 32 >; + using EXEC_POLICY = parallelDevicePolicy< >; using ATOMIC_POLICY = parallelDeviceAtomic; @@ -102,7 +102,8 @@ class AcousticFirstOrderWaveEquationSEM : public WaveSolverBase * @param var_n the field values at time_n * @param var_receivers the array holding the trace values, where the output is written */ - virtual void compute2dVariableAllSeismoTraces( real64 const time_n, + virtual void compute2dVariableAllSeismoTraces( localIndex const regionIndex, + real64 const time_n, real64 const dt, arrayView2d< real32 const > const var_np1, arrayView2d< real32 const > const var_n, @@ -131,7 +132,9 @@ class AcousticFirstOrderWaveEquationSEM : public WaveSolverBase static constexpr char const * uzNp1AtReceiversString() { return "uzNp1AtReceivers"; } static constexpr char const * sourceElemString() { return "sourceElem"; } + static constexpr char const * sourceRegionString() { return "sourceRegion"; } static constexpr char const * receiverElemString() { return "rcvElem"; } + static constexpr char const * receiverRegionString() { return "receiverRegion"; } } waveEquationViewKeys; @@ -192,6 +195,12 @@ class AcousticFirstOrderWaveEquationSEM : public WaveSolverBase /// Array containing the elements which contain a source array1d< localIndex > m_sourceElem; + /// Array containing the elements which contain the region which the source belongs + array1d< localIndex > m_sourceRegion; + + /// Array containing the elements which contain the region which the receiver belongs + array1d< localIndex > m_receiverRegion; + /// Array containing the elements which contain a receiver array1d< localIndex > m_rcvElem; diff --git a/src/coreComponents/physicsSolvers/wavePropagation/AcousticFirstOrderWaveEquationSEMKernel.hpp b/src/coreComponents/physicsSolvers/wavePropagation/AcousticFirstOrderWaveEquationSEMKernel.hpp index e7ac5d9984e..712e909fdb5 100644 --- a/src/coreComponents/physicsSolvers/wavePropagation/AcousticFirstOrderWaveEquationSEMKernel.hpp +++ b/src/coreComponents/physicsSolvers/wavePropagation/AcousticFirstOrderWaveEquationSEMKernel.hpp @@ -66,9 +66,10 @@ struct PrecomputeSourceAndReceiverKernel template< typename EXEC_POLICY, typename FE_TYPE > static void launch( localIndex const size, + localIndex const regionIndex, localIndex const numNodesPerElem, localIndex const numFacesPerElem, - arrayView2d< real64 const, nodes::REFERENCE_POSITION_USD > const X, + arrayView2d< WaveSolverBase::wsCoordType const, nodes::REFERENCE_POSITION_USD > const X, arrayView1d< integer const > const elemGhostRank, arrayView2d< localIndex const, cells::NODE_MAP_USD > const & elemsToNodes, arrayView2d< localIndex const > const elemsToFaces, @@ -80,11 +81,13 @@ struct PrecomputeSourceAndReceiverKernel arrayView1d< localIndex > const sourceElem, arrayView2d< localIndex > const sourceNodeIds, arrayView2d< real64 > const sourceConstants, + arrayView1d< localIndex > const sourceRegion, arrayView2d< real64 const > const receiverCoordinates, arrayView1d< localIndex > const receiverIsLocal, arrayView1d< localIndex > const rcvElem, arrayView2d< localIndex > const receiverNodeIds, arrayView2d< real64 > const receiverConstants, + arrayView1d< localIndex > const receiverRegion, arrayView2d< real32 > const sourceValue, real64 const dt, real32 const timeSourceFrequency, @@ -127,6 +130,7 @@ struct PrecomputeSourceAndReceiverKernel sourceIsAccessible[isrc] = 1; sourceElem[isrc] = k; + sourceRegion[isrc] = regionIndex; real64 Ntest[FE_TYPE::numNodes]; FE_TYPE::calcN( coordsOnRefElem, Ntest ); @@ -174,6 +178,7 @@ struct PrecomputeSourceAndReceiverKernel coordsOnRefElem ); receiverIsLocal[ircv] = 1; rcvElem[ircv] = k; + receiverRegion[ircv] = regionIndex; real64 Ntest[FE_TYPE::numNodes]; FE_TYPE::calcN( coordsOnRefElem, Ntest ); @@ -214,7 +219,7 @@ struct MassMatrixKernel template< typename EXEC_POLICY, typename ATOMIC_POLICY > void launch( localIndex const size, - arrayView2d< real64 const, nodes::REFERENCE_POSITION_USD > const X, + arrayView2d< WaveSolverBase::wsCoordType const, nodes::REFERENCE_POSITION_USD > const X, arrayView2d< localIndex const, cells::NODE_MAP_USD > const elemsToNodes, arrayView1d< real32 const > const velocity, arrayView1d< real32 const > const density, @@ -274,7 +279,7 @@ struct DampingMatrixKernel template< typename EXEC_POLICY, typename ATOMIC_POLICY > void launch( localIndex const size, - arrayView2d< real64 const, nodes::REFERENCE_POSITION_USD > const X, + arrayView2d< WaveSolverBase::wsCoordType const, nodes::REFERENCE_POSITION_USD > const X, arrayView2d< localIndex const > const facesToElems, ArrayOfArraysView< localIndex const > const facesToNodes, arrayView1d< integer const > const facesDomainBoundaryIndicator, @@ -346,7 +351,7 @@ struct VelocityComputation template< typename EXEC_POLICY, typename ATOMIC_POLICY > void launch( localIndex const size, - arrayView2d< real64 const, nodes::REFERENCE_POSITION_USD > const X, + arrayView2d< WaveSolverBase::wsCoordType const, nodes::REFERENCE_POSITION_USD > const X, arrayView2d< localIndex const, cells::NODE_MAP_USD > const elemsToNodes, arrayView1d< real32 const > const p_np1, arrayView1d< real32 const > const density, @@ -443,6 +448,7 @@ struct PressureComputation * @tparam EXEC_POLICY the execution policy * @tparam ATOMIC_POLICY the atomic policy * @param[in] size the number of cells in the subRegion + * @param[in] regionIndex Index of the subregion * @param[in] size_node the number of nodes in the subRegion * @param[in] X coordinates of the nodes * @param[in] elemsToNodes map from element to nodes @@ -463,8 +469,9 @@ struct PressureComputation template< typename EXEC_POLICY, typename ATOMIC_POLICY > void launch( localIndex const size, + localIndex const regionIndex, localIndex const size_node, - arrayView2d< real64 const, nodes::REFERENCE_POSITION_USD > const X, + arrayView2d< WaveSolverBase::wsCoordType const, nodes::REFERENCE_POSITION_USD > const X, arrayView2d< localIndex const, cells::NODE_MAP_USD > const elemsToNodes, arrayView2d< real32 const > const velocity_x, arrayView2d< real32 const > const velocity_y, @@ -475,6 +482,7 @@ struct PressureComputation arrayView2d< real32 const > const sourceValue, arrayView1d< localIndex const > const sourceIsAccessible, arrayView1d< localIndex const > const sourceElem, + arrayView1d< localIndex const > const sourceRegion, real64 const dt, integer const cycleNumber, arrayView1d< real32 > const p_np1 ) @@ -552,7 +560,7 @@ struct PressureComputation { if( sourceIsAccessible[isrc] == 1 ) { - if( sourceElem[isrc]==k ) + if( sourceElem[isrc]==k && sourceRegion[isrc] == regionIndex ) { for( localIndex i = 0; i < numNodesPerElem; ++i ) { diff --git a/src/coreComponents/physicsSolvers/wavePropagation/AcousticWaveEquationSEM.cpp b/src/coreComponents/physicsSolvers/wavePropagation/AcousticWaveEquationSEM.cpp index 3323ac210c4..98ae308427d 100644 --- a/src/coreComponents/physicsSolvers/wavePropagation/AcousticWaveEquationSEM.cpp +++ b/src/coreComponents/physicsSolvers/wavePropagation/AcousticWaveEquationSEM.cpp @@ -59,7 +59,7 @@ void AcousticWaveEquationSEM::initializePreSubGroups() void AcousticWaveEquationSEM::registerDataOnMesh( Group & meshBodies ) { - + WaveSolverBase::registerDataOnMesh( meshBodies ); forDiscretizationOnMeshTargets( meshBodies, [&] ( string const &, MeshLevel & mesh, arrayView1d< string const > const & ) @@ -99,11 +99,6 @@ void AcousticWaveEquationSEM::registerDataOnMesh( Group & meshBodies ) subRegion.registerField< fields::PartialGradient >( this->getName() ); } ); - arrayView1d< real32 > const p_dt2 = nodeManager.getField< fields::PressureDoubleDerivative >(); - int const rank = MpiWrapper::commRank( MPI_COMM_GEOSX ); - std::string lifoPrefix = GEOS_FMT( "lifo/rank_{:05}/pdt2_shot{:06}", rank, m_shotIndex ); - m_lifo = std::unique_ptr< lifoStorage< real32 > >( new lifoStorage< real32 >( lifoPrefix, p_dt2, m_lifoOnDevice, m_lifoOnHost, m_lifoSize ) ); - } ); } @@ -122,11 +117,12 @@ void AcousticWaveEquationSEM::postProcessInput() void AcousticWaveEquationSEM::precomputeSourceAndReceiverTerm( MeshLevel & mesh, arrayView1d< string const > const & regionNames ) { + GEOS_MARK_FUNCTION; NodeManager const & nodeManager = mesh.getNodeManager(); FaceManager const & faceManager = mesh.getFaceManager(); - arrayView2d< real64 const, nodes::REFERENCE_POSITION_USD > const - X = nodeManager.referencePosition().toViewConst(); + arrayView2d< wsCoordType const, nodes::REFERENCE_POSITION_USD > const + X32 = nodeManager.getField< fields::referencePosition32 >().toViewConst(); arrayView2d< real64 const > const faceNormal = faceManager.faceNormal(); arrayView2d< real64 const > const faceCenter = faceManager.faceCenter(); @@ -183,31 +179,34 @@ void AcousticWaveEquationSEM::precomputeSourceAndReceiverTerm( MeshLevel & mesh, constexpr localIndex numNodesPerElem = FE_TYPE::numNodes; localIndex const numFacesPerElem = elementSubRegion.numFacesPerElement(); - acousticWaveEquationSEMKernels:: - PrecomputeSourceAndReceiverKernel:: - launch< EXEC_POLICY, FE_TYPE > - ( elementSubRegion.size(), - numNodesPerElem, - numFacesPerElem, - X, - elemGhostRank, - elemsToNodes, - elemsToFaces, - elemCenter, - faceNormal, - faceCenter, - sourceCoordinates, - sourceIsAccessible, - sourceNodeIds, - sourceConstants, - receiverCoordinates, - receiverIsLocal, - receiverNodeIds, - receiverConstants, - sourceValue, - dt, - timeSourceFrequency, - rickerOrder ); + { + GEOS_MARK_SCOPE( acousticWaveEquationSEMKernels::PrecomputeSourceAndReceiverKernel ); + acousticWaveEquationSEMKernels:: + PrecomputeSourceAndReceiverKernel:: + launch< EXEC_POLICY, FE_TYPE > + ( elementSubRegion.size(), + numNodesPerElem, + numFacesPerElem, + X32, + elemGhostRank, + elemsToNodes, + elemsToFaces, + elemCenter, + faceNormal, + faceCenter, + sourceCoordinates, + sourceIsAccessible, + sourceNodeIds, + sourceConstants, + receiverCoordinates, + receiverIsLocal, + receiverNodeIds, + receiverConstants, + sourceValue, + dt, + timeSourceFrequency, + rickerOrder ); + } } ); } ); } @@ -235,8 +234,11 @@ void AcousticWaveEquationSEM::addSourceToRightHandSide( integer const & cycleNum void AcousticWaveEquationSEM::initializePostInitialConditionsPreSubGroups() { - - WaveSolverBase::initializePostInitialConditionsPreSubGroups(); + GEOS_MARK_FUNCTION; + { + GEOS_MARK_SCOPE( WaveSolverBase::initializePostInitialConditionsPreSubGroups ); + WaveSolverBase::initializePostInitialConditionsPreSubGroups(); + } if( m_usePML ) { AcousticWaveEquationSEM::initializePML(); @@ -258,57 +260,66 @@ void AcousticWaveEquationSEM::initializePostInitialConditionsPreSubGroups() /// get the array of indicators: 1 if the face is on the boundary; 0 otherwise arrayView1d< integer > const & facesDomainBoundaryIndicator = faceManager.getDomainBoundaryIndicator(); - arrayView2d< real64 const, nodes::REFERENCE_POSITION_USD > const X = nodeManager.referencePosition().toViewConst(); + arrayView2d< wsCoordType const, nodes::REFERENCE_POSITION_USD > const X32 = nodeManager.getField< fields::referencePosition32 >().toViewConst(); /// get face to node map ArrayOfArraysView< localIndex const > const facesToNodes = faceManager.nodeList().toViewConst(); // mass matrix to be computed in this function arrayView1d< real32 > const mass = nodeManager.getField< fields::MassVector >(); - mass.zero(); + { + GEOS_MARK_SCOPE( mass_zero ); + mass.zero(); + } /// damping matrix to be computed for each dof in the boundary of the mesh arrayView1d< real32 > const damping = nodeManager.getField< fields::DampingVector >(); - damping.zero(); - + { + GEOS_MARK_SCOPE( damping_zero ); + damping.zero(); + } /// get array of indicators: 1 if face is on the free surface; 0 otherwise arrayView1d< localIndex const > const freeSurfaceFaceIndicator = faceManager.getField< fields::FreeSurfaceFaceIndicator >(); mesh.getElemManager().forElementSubRegions< CellElementSubRegion >( regionNames, [&]( localIndex const, CellElementSubRegion & elementSubRegion ) { - arrayView2d< localIndex const, cells::NODE_MAP_USD > const elemsToNodes = elementSubRegion.nodeList(); arrayView2d< localIndex const > const facesToElements = faceManager.elementList(); arrayView1d< real32 const > const velocity = elementSubRegion.getField< fields::MediumVelocity >(); /// Partial gradient if gradient as to be computed arrayView1d< real32 > grad = elementSubRegion.getField< fields::PartialGradient >(); - grad.zero(); - + { + grad.zero(); + } finiteElement::FiniteElementBase const & fe = elementSubRegion.getReference< finiteElement::FiniteElementBase >( getDiscretizationName() ); finiteElement::FiniteElementDispatchHandler< SEM_FE_TYPES >::dispatch3D( fe, [&] ( auto const finiteElement ) { using FE_TYPE = TYPEOFREF( finiteElement ); - - acousticWaveEquationSEMKernels::MassMatrixKernel< FE_TYPE > kernelM( finiteElement ); - - kernelM.template launch< EXEC_POLICY, ATOMIC_POLICY >( elementSubRegion.size(), - X, - elemsToNodes, - velocity, - mass ); - - acousticWaveEquationSEMKernels::DampingMatrixKernel< FE_TYPE > kernelD( finiteElement ); - - kernelD.template launch< EXEC_POLICY, ATOMIC_POLICY >( faceManager.size(), - X, - facesToElements, - facesToNodes, - facesDomainBoundaryIndicator, - freeSurfaceFaceIndicator, - velocity, - damping ); + { + GEOS_MARK_SCOPE( MassMatrixKernel ); + acousticWaveEquationSEMKernels::MassMatrixKernel< FE_TYPE > kernelM( finiteElement ); + + kernelM.template launch< EXEC_POLICY, ATOMIC_POLICY >( elementSubRegion.size(), + X32, + elemsToNodes, + velocity, + mass ); + } + { + GEOS_MARK_SCOPE( DampingMatrixKernel ); + acousticWaveEquationSEMKernels::DampingMatrixKernel< FE_TYPE > kernelD( finiteElement ); + + kernelD.template launch< EXEC_POLICY, ATOMIC_POLICY >( faceManager.size(), + X32, + facesToElements, + facesToNodes, + facesDomainBoundaryIndicator, + freeSurfaceFaceIndicator, + velocity, + damping ); + } } ); } ); } ); @@ -318,6 +329,7 @@ void AcousticWaveEquationSEM::initializePostInitialConditionsPreSubGroups() void AcousticWaveEquationSEM::applyFreeSurfaceBC( real64 time, DomainPartition & domain ) { + GEOS_MARK_FUNCTION; FieldSpecificationManager & fsManager = FieldSpecificationManager::getInstance(); FunctionManager const & functionManager = FunctionManager::getInstance(); @@ -420,7 +432,7 @@ void AcousticWaveEquationSEM::initializePML() NodeManager & nodeManager = mesh.getNodeManager(); /// WARNING: the array below is one of the PML auxiliary variables arrayView1d< real32 > const indicatorPML = nodeManager.getField< fields::AuxiliaryVar4PML >(); - arrayView2d< real64 const, nodes::REFERENCE_POSITION_USD > const X = nodeManager.referencePosition().toViewConst(); + arrayView2d< wsCoordType const, nodes::REFERENCE_POSITION_USD > const X32 = nodeManager.getField< fields::referencePosition32 >().toViewConst(); indicatorPML.zero(); real32 xInteriorMin[3]{}; @@ -479,20 +491,20 @@ void AcousticWaveEquationSEM::initializePML() forAll< EXEC_POLICY >( nodeManager.size(), [=] GEOS_HOST_DEVICE ( localIndex const a ) { - xMinGlobal.min( X[a][0] ); - yMinGlobal.min( X[a][1] ); - zMinGlobal.min( X[a][2] ); - xMaxGlobal.max( X[a][0] ); - yMaxGlobal.max( X[a][1] ); - zMaxGlobal.max( X[a][2] ); + xMinGlobal.min( X32[a][0] ); + yMinGlobal.min( X32[a][1] ); + zMinGlobal.min( X32[a][2] ); + xMaxGlobal.max( X32[a][0] ); + yMaxGlobal.max( X32[a][1] ); + zMaxGlobal.max( X32[a][2] ); if( !isZero( indicatorPML[a] - 1.0 )) { - xMinInterior.min( X[a][0] ); - yMinInterior.min( X[a][1] ); - zMinInterior.min( X[a][2] ); - xMaxInterior.max( X[a][0] ); - yMaxInterior.max( X[a][1] ); - zMaxInterior.max( X[a][2] ); + xMinInterior.min( X32[a][0] ); + yMinInterior.min( X32[a][1] ); + zMinInterior.min( X32[a][2] ); + xMaxInterior.max( X32[a][0] ); + yMaxInterior.max( X32[a][1] ); + zMaxInterior.max( X32[a][2] ); } } ); @@ -563,7 +575,7 @@ void AcousticWaveEquationSEM::initializePML() waveSpeedPMLKernel< FE_TYPE > kernel( finiteElement ); kernel.template launch< EXEC_POLICY, ATOMIC_POLICY > ( targetSet, - X, + X32, elemToNodesViewConst, vel, xMin, @@ -654,7 +666,7 @@ void AcousticWaveEquationSEM::applyPML( real64 const time, DomainPartition & dom arrayView2d< real32 > const grad_n = nodeManager.getField< fields::AuxiliaryVar2PML >(); arrayView1d< real32 > const divV_n = nodeManager.getField< fields::AuxiliaryVar3PML >(); arrayView1d< real32 const > const u_n = nodeManager.getField< fields::AuxiliaryVar4PML >(); - arrayView2d< real64 const, nodes::REFERENCE_POSITION_USD > const X = nodeManager.referencePosition().toViewConst(); + arrayView2d< wsCoordType const, nodes::REFERENCE_POSITION_USD > const X32 = nodeManager.getField< fields::referencePosition32 >().toViewConst(); /// Select the subregions concerned by the PML (specified in the xml by the Field Specification) /// 'targetSet' contains the indices of the elements in a given subregion @@ -711,7 +723,7 @@ void AcousticWaveEquationSEM::applyPML( real64 const time, DomainPartition & dom PMLKernel< FE_TYPE > kernel( finiteElement ); kernel.template launch< EXEC_POLICY, ATOMIC_POLICY > ( targetSet, - X, + X32, elemToNodesViewConst, vel, p_n, @@ -768,8 +780,15 @@ real64 AcousticWaveEquationSEM::explicitStepForward( real64 const & time_n, arrayView1d< real32 > const p_dt2 = nodeManager.getField< fields::PressureDoubleDerivative >(); - if( NULL == std::getenv( "DISABLE_LIFO" ) ) + if( m_enableLifo ) { + if( !m_lifo ) + { + int const rank = MpiWrapper::commRank( MPI_COMM_GEOSX ); + std::string lifoPrefix = GEOS_FMT( "lifo/rank_{:05}/pdt2_shot{:06}", rank, m_shotIndex ); + m_lifo = std::make_unique< LifoStorage< real32, localIndex > >( lifoPrefix, p_dt2, m_lifoOnDevice, m_lifoOnHost, m_lifoSize ); + } + m_lifo->pushWait(); } forAll< EXEC_POLICY >( nodeManager.size(), [=] GEOS_HOST_DEVICE ( localIndex const nodeIdx ) @@ -777,7 +796,7 @@ real64 AcousticWaveEquationSEM::explicitStepForward( real64 const & time_n, p_dt2[nodeIdx] = (p_np1[nodeIdx] - 2*p_n[nodeIdx] + p_nm1[nodeIdx])/(dt*dt); } ); - if( NULL == std::getenv( "DISABLE_LIFO" ) ) + if( m_enableLifo ) { // Need to tell LvArray data is on GPU to avoir HtoD copy p_dt2.move( MemorySpace::cuda, false ); @@ -852,9 +871,11 @@ real64 AcousticWaveEquationSEM::explicitStepBackward( real64 const & time_n, arrayView1d< real32 > const p_dt2 = nodeManager.getField< fields::PressureDoubleDerivative >(); - if( NULL == std::getenv( "DISABLE_LIFO" ) ) + if( m_enableLifo ) { m_lifo->pop( p_dt2 ); + if( m_lifo->empty() ) + delete m_lifo.release(); } else { @@ -980,7 +1001,7 @@ real64 AcousticWaveEquationSEM::explicitStepInternal( real64 const & time_n, arrayView2d< real32 > const grad_n = nodeManager.getField< fields::AuxiliaryVar2PML >(); arrayView1d< real32 > const divV_n = nodeManager.getField< fields::AuxiliaryVar3PML >(); arrayView1d< real32 > const u_n = nodeManager.getField< fields::AuxiliaryVar4PML >(); - arrayView2d< real64 const, nodes::REFERENCE_POSITION_USD > const X = nodeManager.referencePosition().toViewConst(); + arrayView2d< wsCoordType const, nodes::REFERENCE_POSITION_USD > const X32 = nodeManager.getField< fields::referencePosition32 >().toViewConst(); real32 const xMin[ 3 ] = {param.xMinPML[0], param.xMinPML[1], param.xMinPML[2]}; real32 const xMax[ 3 ] = {param.xMaxPML[0], param.xMaxPML[1], param.xMaxPML[2]}; @@ -1000,11 +1021,11 @@ real64 AcousticWaveEquationSEM::explicitStepInternal( real64 const & time_n, if( freeSurfaceNodeIndicator[a] != 1 ) { real32 sigma[3]; - real64 xLocal[ 3 ]; + real32 xLocal[ 3 ]; for( integer i=0; i<3; ++i ) { - xLocal[i] = X[a][i]; + xLocal[i] = X32[a][i]; } acousticWaveEquationSEMKernels::PMLKernelHelper::computeDampingProfilePML( diff --git a/src/coreComponents/physicsSolvers/wavePropagation/AcousticWaveEquationSEMKernel.hpp b/src/coreComponents/physicsSolvers/wavePropagation/AcousticWaveEquationSEMKernel.hpp index a7e997df7ee..7293a2a79b6 100644 --- a/src/coreComponents/physicsSolvers/wavePropagation/AcousticWaveEquationSEMKernel.hpp +++ b/src/coreComponents/physicsSolvers/wavePropagation/AcousticWaveEquationSEMKernel.hpp @@ -60,7 +60,7 @@ struct PrecomputeSourceAndReceiverKernel launch( localIndex const size, localIndex const numNodesPerElem, localIndex const numFacesPerElem, - arrayView2d< real64 const, nodes::REFERENCE_POSITION_USD > const X, + arrayView2d< WaveSolverBase::wsCoordType const, nodes::REFERENCE_POSITION_USD > const X, arrayView1d< integer const > const elemGhostRank, arrayView2d< localIndex const, cells::NODE_MAP_USD > const & elemsToNodes, arrayView2d< localIndex const > const elemsToFaces, @@ -203,7 +203,7 @@ struct MassMatrixKernel template< typename EXEC_POLICY, typename ATOMIC_POLICY > void launch( localIndex const size, - arrayView2d< real64 const, nodes::REFERENCE_POSITION_USD > const X, + arrayView2d< WaveSolverBase::wsCoordType const, nodes::REFERENCE_POSITION_USD > const X, arrayView2d< localIndex const, cells::NODE_MAP_USD > const elemsToNodes, arrayView1d< real32 const > const velocity, arrayView1d< real32 > const mass ) @@ -261,7 +261,7 @@ struct DampingMatrixKernel template< typename EXEC_POLICY, typename ATOMIC_POLICY > void launch( localIndex const size, - arrayView2d< real64 const, nodes::REFERENCE_POSITION_USD > const X, + arrayView2d< WaveSolverBase::wsCoordType const, nodes::REFERENCE_POSITION_USD > const X, arrayView2d< localIndex const > const facesToElems, ArrayOfArraysView< localIndex const > const facesToNodes, arrayView1d< integer const > const facesDomainBoundaryIndicator, @@ -323,7 +323,7 @@ struct PMLKernelHelper */ GEOS_HOST_DEVICE inline - static void computeDampingProfilePML( real64 const (&xLocal)[3], + static void computeDampingProfilePML( real32 const (&xLocal)[3], real32 const (&xMin)[3], real32 const (&xMax)[3], real32 const (&dMin)[3], @@ -403,7 +403,7 @@ struct PMLKernel template< typename EXEC_POLICY, typename ATOMIC_POLICY > void launch( SortedArrayView< localIndex const > const targetSet, - arrayView2d< real64 const, nodes::REFERENCE_POSITION_USD > const X, + arrayView2d< WaveSolverBase::wsCoordType const, nodes::REFERENCE_POSITION_USD > const X, traits::ViewTypeConst< CellElementSubRegion::NodeMapType > const elemToNodesViewConst, arrayView1d< real32 const > const velocity, arrayView1d< real32 const > const p_n, @@ -432,6 +432,7 @@ struct PMLKernel /// coordinates of the element nodes real64 xLocal[ numNodesPerElem ][ 3 ]; + real32 xLocal32[ numNodesPerElem ][ 3 ]; /// local arrays to store the pressure at all nodes and its gradient at a given node real64 pressure[ numNodesPerElem ]; @@ -456,6 +457,7 @@ struct PMLKernel for( int j=0; j<3; ++j ) { xLocal[i][j] = X[elemToNodesViewConst[k][i]][j]; + xLocal32[i][j] = X[elemToNodesViewConst[k][i]][j]; auxV[j][i] = v_n[elemToNodesViewConst[k][i]][j]; } } @@ -483,7 +485,7 @@ struct PMLKernel /// compute the PML damping profile PMLKernelHelper::computeDampingProfilePML( - xLocal[i], + xLocal32[i], xMin, xMax, dMin, @@ -549,7 +551,7 @@ struct waveSpeedPMLKernel template< typename EXEC_POLICY, typename ATOMIC_POLICY > void launch( SortedArrayView< localIndex const > const targetSet, - arrayView2d< real64 const, nodes::REFERENCE_POSITION_USD > const X, + arrayView2d< WaveSolverBase::wsCoordType const, nodes::REFERENCE_POSITION_USD > const X, traits::ViewTypeConst< CellElementSubRegion::NodeMapType > const elemToNodesViewConst, arrayView1d< real32 const > const velocity, real32 const (&xMin)[3], @@ -733,7 +735,7 @@ class ExplicitAcousticSEM : public finiteElement::KernelBase< SUBREGION_TYPE, Base( elementSubRegion, finiteElementSpace, inputConstitutiveType ), - m_X( nodeManager.referencePosition() ), + m_X( nodeManager.getField< fields::referencePosition32 >() ), m_p_n( nodeManager.getField< fields::Pressure_n >() ), m_stiffnessVector( nodeManager.getField< fields::StiffnessVector >() ), m_dt( dt ) @@ -807,7 +809,7 @@ class ExplicitAcousticSEM : public finiteElement::KernelBase< SUBREGION_TYPE, protected: /// The array containing the nodal position array. - arrayView2d< real64 const, nodes::REFERENCE_POSITION_USD > const m_X; + arrayView2d< WaveSolverBase::wsCoordType const, nodes::REFERENCE_POSITION_USD > const m_X; /// The array containing the nodal pressure array. arrayView1d< real32 const > const m_p_n; diff --git a/src/coreComponents/physicsSolvers/wavePropagation/ElasticFirstOrderWaveEquationSEM.cpp b/src/coreComponents/physicsSolvers/wavePropagation/ElasticFirstOrderWaveEquationSEM.cpp index 3f36f51b2d8..1eb94a47555 100644 --- a/src/coreComponents/physicsSolvers/wavePropagation/ElasticFirstOrderWaveEquationSEM.cpp +++ b/src/coreComponents/physicsSolvers/wavePropagation/ElasticFirstOrderWaveEquationSEM.cpp @@ -94,6 +94,16 @@ ElasticFirstOrderWaveEquationSEM::ElasticFirstOrderWaveEquationSEM( const std::s setSizedFromParent( 0 ). setDescription( "Element containing the receivers" ); + registerWrapper( viewKeyStruct::sourceRegionString(), &m_sourceRegion ). + setInputFlag( InputFlags::FALSE ). + setSizedFromParent( 0 ). + setDescription( "Region containing the sources" ); + + registerWrapper( viewKeyStruct::receiverRegionString(), &m_receiverRegion ). + setInputFlag( InputFlags::FALSE ). + setSizedFromParent( 0 ). + setDescription( "Region containing the receivers" ); + } ElasticFirstOrderWaveEquationSEM::~ElasticFirstOrderWaveEquationSEM() @@ -109,6 +119,7 @@ void ElasticFirstOrderWaveEquationSEM::initializePreSubGroups() void ElasticFirstOrderWaveEquationSEM::registerDataOnMesh( Group & meshBodies ) { + WaveSolverBase::registerDataOnMesh( meshBodies ); forDiscretizationOnMeshTargets( meshBodies, [&] ( string const &, MeshLevel & mesh, @@ -179,9 +190,11 @@ void ElasticFirstOrderWaveEquationSEM::postProcessInput() localIndex const numSourcesGlobal = m_sourceCoordinates.size( 0 ); m_sourceElem.resize( numSourcesGlobal ); + m_sourceRegion.resize( numSourcesGlobal ); localIndex const numReceiversGlobal = m_receiverCoordinates.size( 0 ); m_rcvElem.resize( numReceiversGlobal ); + m_receiverRegion.resize( numReceiversGlobal ); m_displacementxNp1AtReceivers.resize( m_nsamplesSeismoTrace, numReceiversGlobal ); m_displacementyNp1AtReceivers.resize( m_nsamplesSeismoTrace, numReceiversGlobal ); @@ -203,7 +216,7 @@ void ElasticFirstOrderWaveEquationSEM::precomputeSourceAndReceiverTerm( MeshLeve NodeManager const & nodeManager = mesh.getNodeManager(); FaceManager const & faceManager = mesh.getFaceManager(); - arrayView2d< real64 const, nodes::REFERENCE_POSITION_USD > const X = nodeManager.referencePosition().toViewConst(); + arrayView2d< wsCoordType const, nodes::REFERENCE_POSITION_USD > const X = nodeManager.getField< fields::referencePosition32 >().toViewConst(); arrayView2d< real64 const > const faceNormal = faceManager.faceNormal(); arrayView2d< real64 const > const faceCenter = faceManager.faceCenter(); @@ -212,6 +225,7 @@ void ElasticFirstOrderWaveEquationSEM::precomputeSourceAndReceiverTerm( MeshLeve arrayView2d< real64 > const sourceConstants = m_sourceConstants.toView(); arrayView1d< localIndex > const sourceIsAccessible = m_sourceIsAccessible.toView(); arrayView1d< localIndex > const sourceElem = m_sourceElem.toView(); + arrayView1d< localIndex > const sourceRegion = m_sourceRegion.toView(); sourceNodeIds.setValues< serialPolicy >( -1 ); sourceConstants.setValues< serialPolicy >( -1 ); sourceIsAccessible.zero(); @@ -221,6 +235,7 @@ void ElasticFirstOrderWaveEquationSEM::precomputeSourceAndReceiverTerm( MeshLeve arrayView2d< real64 > const receiverConstants = m_receiverConstants.toView(); arrayView1d< localIndex > const receiverIsLocal = m_receiverIsLocal.toView(); arrayView1d< localIndex > const rcvElem = m_rcvElem.toView(); + arrayView1d< localIndex > const receiverRegion = m_receiverRegion.toView(); receiverNodeIds.setValues< serialPolicy >( -1 ); receiverConstants.setValues< serialPolicy >( -1 ); @@ -241,7 +256,7 @@ void ElasticFirstOrderWaveEquationSEM::precomputeSourceAndReceiverTerm( MeshLeve } } - mesh.getElemManager().forElementSubRegions< CellElementSubRegion >( regionNames, [&]( localIndex const, + mesh.getElemManager().forElementSubRegions< CellElementSubRegion >( regionNames, [&]( localIndex const regionIndex, CellElementSubRegion & elementSubRegion ) { @@ -267,6 +282,7 @@ void ElasticFirstOrderWaveEquationSEM::precomputeSourceAndReceiverTerm( MeshLeve PrecomputeSourceAndReceiverKernel:: launch< EXEC_POLICY, FE_TYPE > ( elementSubRegion.size(), + regionIndex, numNodesPerElem, numFacesPerElem, X, @@ -281,11 +297,13 @@ void ElasticFirstOrderWaveEquationSEM::precomputeSourceAndReceiverTerm( MeshLeve sourceElem, sourceNodeIds, sourceConstants, + sourceRegion, receiverCoordinates, receiverIsLocal, rcvElem, receiverNodeIds, receiverConstants, + receiverRegion, sourceValue, dt, timeSourceFrequency, @@ -338,7 +356,7 @@ void ElasticFirstOrderWaveEquationSEM::initializePostInitialConditionsPreSubGrou /// get the array of indicators: 1 if the face is on the boundary; 0 otherwise arrayView1d< integer > const & facesDomainBoundaryIndicator = faceManager.getDomainBoundaryIndicator(); - arrayView2d< real64 const, nodes::REFERENCE_POSITION_USD > const X = nodeManager.referencePosition().toViewConst(); + arrayView2d< wsCoordType const, nodes::REFERENCE_POSITION_USD > const X = nodeManager.getField< fields::referencePosition32 >().toViewConst(); arrayView2d< real64 const > const faceNormal = faceManager.faceNormal(); /// get face to node map @@ -505,6 +523,7 @@ real64 ElasticFirstOrderWaveEquationSEM::explicitStepInternal( real64 const & ti arrayView2d< real64 const > const sourceConstants = m_sourceConstants.toView(); arrayView1d< localIndex const > const sourceIsAccessible = m_sourceIsAccessible.toView(); arrayView1d< localIndex const > const sourceElem = m_sourceElem.toView(); + arrayView1d< localIndex const > const sourceRegion = m_sourceRegion.toView(); arrayView2d< real32 const > const sourceValue = m_sourceValue.toView(); @@ -516,7 +535,7 @@ real64 ElasticFirstOrderWaveEquationSEM::explicitStepInternal( real64 const & ti NodeManager & nodeManager = mesh.getNodeManager(); - arrayView2d< real64 const, nodes::REFERENCE_POSITION_USD > const X = nodeManager.referencePosition().toViewConst(); + arrayView2d< wsCoordType const, nodes::REFERENCE_POSITION_USD > const X = nodeManager.getField< fields::referencePosition32 >().toViewConst(); arrayView1d< real32 const > const mass = nodeManager.getField< wavesolverfields::MassVector >(); arrayView1d< real32 > const dampingx = nodeManager.getField< wavesolverfields::DampingVectorx >(); @@ -528,7 +547,7 @@ real64 ElasticFirstOrderWaveEquationSEM::explicitStepInternal( real64 const & ti arrayView1d< real32 > const uy_np1 = nodeManager.getField< wavesolverfields::Displacementy_np1 >(); arrayView1d< real32 > const uz_np1 = nodeManager.getField< wavesolverfields::Displacementz_np1 >(); - mesh.getElemManager().forElementSubRegions< CellElementSubRegion >( regionNames, [&]( localIndex const, + mesh.getElemManager().forElementSubRegions< CellElementSubRegion >( regionNames, [&]( localIndex const regionIndex, CellElementSubRegion & elementSubRegion ) { @@ -559,6 +578,7 @@ real64 ElasticFirstOrderWaveEquationSEM::explicitStepInternal( real64 const & ti StressComputation< FE_TYPE > kernel( finiteElement ); kernel.template launch< EXEC_POLICY, ATOMIC_POLICY > ( elementSubRegion.size(), + regionIndex, X, elemsToNodes, ux_np1, @@ -572,6 +592,7 @@ real64 ElasticFirstOrderWaveEquationSEM::explicitStepInternal( real64 const & ti sourceConstants, sourceIsAccessible, sourceElem, + sourceRegion, sourceValue, dt, cycleNumber, @@ -614,12 +635,12 @@ real64 ElasticFirstOrderWaveEquationSEM::explicitStepInternal( real64 const & ti arrayView2d< real32 > const sigmaxzReceivers = m_sigmaxzNp1AtReceivers.toView(); arrayView2d< real32 > const sigmayzReceivers = m_sigmayzNp1AtReceivers.toView(); - compute2dVariableAllSeismoTraces( time_n, dt, stressxx, stressxx, sigmaxxReceivers ); - compute2dVariableAllSeismoTraces( time_n, dt, stressyy, stressyy, sigmayyReceivers ); - compute2dVariableAllSeismoTraces( time_n, dt, stresszz, stresszz, sigmazzReceivers ); - compute2dVariableAllSeismoTraces( time_n, dt, stressxy, stressxy, sigmaxyReceivers ); - compute2dVariableAllSeismoTraces( time_n, dt, stressxz, stressxz, sigmaxzReceivers ); - compute2dVariableAllSeismoTraces( time_n, dt, stressyz, stressyz, sigmayzReceivers ); + compute2dVariableAllSeismoTraces( regionIndex, time_n, dt, stressxx, stressxx, sigmaxxReceivers ); + compute2dVariableAllSeismoTraces( regionIndex, time_n, dt, stressyy, stressyy, sigmayyReceivers ); + compute2dVariableAllSeismoTraces( regionIndex, time_n, dt, stresszz, stresszz, sigmazzReceivers ); + compute2dVariableAllSeismoTraces( regionIndex, time_n, dt, stressxy, stressxy, sigmaxyReceivers ); + compute2dVariableAllSeismoTraces( regionIndex, time_n, dt, stressxz, stressxz, sigmaxzReceivers ); + compute2dVariableAllSeismoTraces( regionIndex, time_n, dt, stressyz, stressyz, sigmayzReceivers ); } ); @@ -673,7 +694,7 @@ void ElasticFirstOrderWaveEquationSEM::cleanup( real64 const time_n, { NodeManager & nodeManager = mesh.getNodeManager(); - mesh.getElemManager().forElementSubRegions< CellElementSubRegion >( regionNames, [&]( localIndex const, + mesh.getElemManager().forElementSubRegions< CellElementSubRegion >( regionNames, [&]( localIndex const regionIndex, CellElementSubRegion & elementSubRegion ) { arrayView2d< real32 const > const stressxx = elementSubRegion.getField< wavesolverfields::Stresstensorxx >(); @@ -690,12 +711,12 @@ void ElasticFirstOrderWaveEquationSEM::cleanup( real64 const time_n, arrayView2d< real32 > const sigmaxzReceivers = m_sigmaxzNp1AtReceivers.toView(); arrayView2d< real32 > const sigmayzReceivers = m_sigmayzNp1AtReceivers.toView(); - compute2dVariableAllSeismoTraces( time_n, 0, stressxx, stressxx, sigmaxxReceivers ); - compute2dVariableAllSeismoTraces( time_n, 0, stressyy, stressyy, sigmayyReceivers ); - compute2dVariableAllSeismoTraces( time_n, 0, stresszz, stresszz, sigmazzReceivers ); - compute2dVariableAllSeismoTraces( time_n, 0, stressxy, stressxy, sigmaxyReceivers ); - compute2dVariableAllSeismoTraces( time_n, 0, stressxz, stressxz, sigmaxzReceivers ); - compute2dVariableAllSeismoTraces( time_n, 0, stressyz, stressyz, sigmayzReceivers ); + compute2dVariableAllSeismoTraces( regionIndex, time_n, 0, stressxx, stressxx, sigmaxxReceivers ); + compute2dVariableAllSeismoTraces( regionIndex, time_n, 0, stressyy, stressyy, sigmayyReceivers ); + compute2dVariableAllSeismoTraces( regionIndex, time_n, 0, stresszz, stresszz, sigmazzReceivers ); + compute2dVariableAllSeismoTraces( regionIndex, time_n, 0, stressxy, stressxy, sigmaxyReceivers ); + compute2dVariableAllSeismoTraces( regionIndex, time_n, 0, stressxz, stressxz, sigmaxzReceivers ); + compute2dVariableAllSeismoTraces( regionIndex, time_n, 0, stressyz, stressyz, sigmayzReceivers ); } ); arrayView1d< real32 > const ux_np1 = nodeManager.getField< wavesolverfields::Displacementx_np1 >(); arrayView1d< real32 > const uy_np1 = nodeManager.getField< wavesolverfields::Displacementy_np1 >(); @@ -737,7 +758,8 @@ void ElasticFirstOrderWaveEquationSEM::computeAllSeismoTraces( real64 const time } } -void ElasticFirstOrderWaveEquationSEM::compute2dVariableAllSeismoTraces( real64 const time_n, +void ElasticFirstOrderWaveEquationSEM::compute2dVariableAllSeismoTraces( localIndex const regionIndex, + real64 const time_n, real64 const dt, arrayView2d< real32 const > const var_np1, arrayView2d< real32 const > const var_n, @@ -748,7 +770,7 @@ void ElasticFirstOrderWaveEquationSEM::compute2dVariableAllSeismoTraces( real64 (timeSeismo = m_dtSeismoTrace*indexSeismoTrace) <= (time_n + epsilonLoc) && indexSeismoTrace < m_nsamplesSeismoTrace; indexSeismoTrace++ ) { - WaveSolverUtils::compute2dVariableSeismoTrace( time_n, dt, timeSeismo, indexSeismoTrace, m_rcvElem, m_receiverConstants, m_receiverIsLocal, + WaveSolverUtils::compute2dVariableSeismoTrace( time_n, dt, regionIndex, m_receiverRegion, timeSeismo, indexSeismoTrace, m_rcvElem, m_receiverConstants, m_receiverIsLocal, m_nsamplesSeismoTrace, m_outputSeismoTrace, var_np1, var_n, varAtReceivers ); } } diff --git a/src/coreComponents/physicsSolvers/wavePropagation/ElasticFirstOrderWaveEquationSEM.hpp b/src/coreComponents/physicsSolvers/wavePropagation/ElasticFirstOrderWaveEquationSEM.hpp index b04b407c9a4..a4c3c6a3266 100644 --- a/src/coreComponents/physicsSolvers/wavePropagation/ElasticFirstOrderWaveEquationSEM.hpp +++ b/src/coreComponents/physicsSolvers/wavePropagation/ElasticFirstOrderWaveEquationSEM.hpp @@ -33,7 +33,7 @@ class ElasticFirstOrderWaveEquationSEM : public WaveSolverBase { public: - using EXEC_POLICY = parallelDevicePolicy< 32 >; + using EXEC_POLICY = parallelDevicePolicy< >; using ATOMIC_POLICY = parallelDeviceAtomic; static constexpr real64 epsilonLoc = 1e-8; @@ -101,7 +101,8 @@ class ElasticFirstOrderWaveEquationSEM : public WaveSolverBase * @param var_n the field values at time_n * @param varAtreceivers the array holding the trace values, where the output is written */ - virtual void compute2dVariableAllSeismoTraces( real64 const time_n, + virtual void compute2dVariableAllSeismoTraces( localIndex const regionIndex, + real64 const time_n, real64 const dt, arrayView2d< real32 const > const var_np1, arrayView2d< real32 const > const var_n, @@ -133,7 +134,9 @@ class ElasticFirstOrderWaveEquationSEM : public WaveSolverBase static constexpr char const * sigmayzNp1AtReceiversString() { return "sigmayzNp1AtReceivers"; } static constexpr char const * sourceElemString() { return "sourceElem"; } + static constexpr char const * sourceRegionString() { return "sourceRegion"; } static constexpr char const * receiverElemString() { return "rcvElem"; } + static constexpr char const * receiverRegionString() { return "receiverRegion"; } } waveEquationViewKeys; @@ -210,9 +213,15 @@ class ElasticFirstOrderWaveEquationSEM : public WaveSolverBase /// Array containing the elements which contain a source array1d< localIndex > m_sourceElem; + /// Array containing the elements which contain the region which the source belongs + array1d< localIndex > m_sourceRegion; + /// Array containing the elements which contain a receiver array1d< localIndex > m_rcvElem; + /// Array containing the elements which contain the region which the receiver belongs + array1d< localIndex > m_receiverRegion; + }; } /* namespace geos */ diff --git a/src/coreComponents/physicsSolvers/wavePropagation/ElasticFirstOrderWaveEquationSEMKernel.hpp b/src/coreComponents/physicsSolvers/wavePropagation/ElasticFirstOrderWaveEquationSEMKernel.hpp index dc9ce711f9a..c4564bbf791 100644 --- a/src/coreComponents/physicsSolvers/wavePropagation/ElasticFirstOrderWaveEquationSEMKernel.hpp +++ b/src/coreComponents/physicsSolvers/wavePropagation/ElasticFirstOrderWaveEquationSEMKernel.hpp @@ -64,9 +64,10 @@ struct PrecomputeSourceAndReceiverKernel template< typename EXEC_POLICY, typename FE_TYPE > static void launch( localIndex const size, + localIndex const regionIndex, localIndex const numNodesPerElem, localIndex const numFacesPerElem, - arrayView2d< real64 const, nodes::REFERENCE_POSITION_USD > const X, + arrayView2d< WaveSolverBase::wsCoordType const, nodes::REFERENCE_POSITION_USD > const X, arrayView1d< integer const > const elemGhostRank, arrayView2d< localIndex const, cells::NODE_MAP_USD > const & elemsToNodes, arrayView2d< localIndex const > const elemsToFaces, @@ -78,11 +79,13 @@ struct PrecomputeSourceAndReceiverKernel arrayView1d< localIndex > const sourceElem, arrayView2d< localIndex > const sourceNodeIds, arrayView2d< real64 > const sourceConstants, + arrayView1d< localIndex > const sourceRegion, arrayView2d< real64 const > const receiverCoordinates, arrayView1d< localIndex > const receiverIsLocal, arrayView1d< localIndex > const rcvElem, arrayView2d< localIndex > const receiverNodeIds, arrayView2d< real64 > const receiverConstants, + arrayView1d< localIndex > const receiverRegion, arrayView2d< real32 > const sourceValue, real64 const dt, real32 const timeSourceFrequency, @@ -124,6 +127,7 @@ struct PrecomputeSourceAndReceiverKernel coordsOnRefElem ); sourceIsAccessible[isrc] = 1; sourceElem[isrc] = k; + sourceRegion[isrc] = regionIndex; real64 Ntest[FE_TYPE::numNodes]; FE_TYPE::calcN( coordsOnRefElem, Ntest ); @@ -171,6 +175,7 @@ struct PrecomputeSourceAndReceiverKernel coordsOnRefElem ); receiverIsLocal[ircv] = 1; rcvElem[ircv] = k; + receiverRegion[ircv] = regionIndex; real64 Ntest[FE_TYPE::numNodes]; FE_TYPE::calcN( coordsOnRefElem, Ntest ); @@ -210,7 +215,7 @@ struct MassMatrixKernel template< typename EXEC_POLICY, typename ATOMIC_POLICY > void launch( localIndex const size, - arrayView2d< real64 const, nodes::REFERENCE_POSITION_USD > const X, + arrayView2d< WaveSolverBase::wsCoordType const, nodes::REFERENCE_POSITION_USD > const X, arrayView2d< localIndex const, cells::NODE_MAP_USD > const elemsToNodes, arrayView1d< real32 const > const density, arrayView1d< real32 > const mass ) @@ -268,7 +273,7 @@ struct DampingMatrixKernel template< typename EXEC_POLICY, typename ATOMIC_POLICY > void launch( localIndex const size, - arrayView2d< real64 const, nodes::REFERENCE_POSITION_USD > const X, + arrayView2d< WaveSolverBase::wsCoordType const, nodes::REFERENCE_POSITION_USD > const X, arrayView2d< localIndex const > const facesToElems, ArrayOfArraysView< localIndex const > const facesToNodes, arrayView1d< integer const > const facesDomainBoundaryIndicator, @@ -346,7 +351,8 @@ struct StressComputation template< typename EXEC_POLICY, typename ATOMIC_POLICY > void launch( localIndex const size, - arrayView2d< real64 const, nodes::REFERENCE_POSITION_USD > const X, + localIndex const regionIndex, + arrayView2d< WaveSolverBase::wsCoordType const, nodes::REFERENCE_POSITION_USD > const X, arrayView2d< localIndex const, cells::NODE_MAP_USD > const elemsToNodes, arrayView1d< real32 const > const ux_np1, arrayView1d< real32 const > const uy_np1, @@ -359,6 +365,7 @@ struct StressComputation arrayView2d< real64 const > const sourceConstants, arrayView1d< localIndex const > const sourceIsLocal, arrayView1d< localIndex const > const sourceElem, + arrayView1d< localIndex const > const sourceRegion, arrayView2d< real32 const > const sourceValue, real64 const dt, integer const cycleNumber, @@ -480,7 +487,7 @@ struct StressComputation { if( sourceIsLocal[isrc] == 1 ) { - if( sourceElem[isrc]==k ) + if( sourceElem[isrc]==k && sourceRegion[isrc] == regionIndex ) { for( localIndex i = 0; i < numNodesPerElem; ++i ) { @@ -520,7 +527,7 @@ struct VelocityComputation void launch( localIndex const size, localIndex const size_node, - arrayView2d< real64 const, nodes::REFERENCE_POSITION_USD > const X, + arrayView2d< WaveSolverBase::wsCoordType const, nodes::REFERENCE_POSITION_USD > const X, arrayView2d< localIndex const, cells::NODE_MAP_USD > const elemsToNodes, arrayView2d< real32 const > const stressxx, arrayView2d< real32 const > const stressyy, diff --git a/src/coreComponents/physicsSolvers/wavePropagation/ElasticWaveEquationSEM.cpp b/src/coreComponents/physicsSolvers/wavePropagation/ElasticWaveEquationSEM.cpp index c2567524ea1..3416e82f7ef 100644 --- a/src/coreComponents/physicsSolvers/wavePropagation/ElasticWaveEquationSEM.cpp +++ b/src/coreComponents/physicsSolvers/wavePropagation/ElasticWaveEquationSEM.cpp @@ -179,6 +179,7 @@ void ElasticWaveEquationSEM::initializePreSubGroups() void ElasticWaveEquationSEM::registerDataOnMesh( Group & meshBodies ) { + WaveSolverBase::registerDataOnMesh( meshBodies ); forDiscretizationOnMeshTargets( meshBodies, [&] ( string const &, MeshLevel & mesh, @@ -283,8 +284,8 @@ void ElasticWaveEquationSEM::precomputeSourceAndReceiverTerm( MeshLevel & mesh, NodeManager const & nodeManager = mesh.getNodeManager(); FaceManager const & faceManager = mesh.getFaceManager(); - arrayView2d< real64 const, nodes::REFERENCE_POSITION_USD > const X = - nodeManager.referencePosition().toViewConst(); + arrayView2d< wsCoordType const, nodes::REFERENCE_POSITION_USD > const X = + nodeManager.getField< fields::referencePosition32 >().toViewConst(); arrayView2d< real64 const > const faceNormal = faceManager.faceNormal(); arrayView2d< real64 const > const faceCenter = faceManager.faceCenter(); @@ -525,7 +526,7 @@ void ElasticWaveEquationSEM::initializePostInitialConditionsPreSubGroups() /// get the array of indicators: 1 if the face is on the boundary; 0 otherwise arrayView1d< integer const > const & facesDomainBoundaryIndicator = faceManager.getDomainBoundaryIndicator(); - arrayView2d< real64 const, nodes::REFERENCE_POSITION_USD > const X = nodeManager.referencePosition().toViewConst(); + arrayView2d< wsCoordType const, nodes::REFERENCE_POSITION_USD > const X = nodeManager.getField< fields::referencePosition32 >().toViewConst(); arrayView2d< real64 const > const faceNormal = faceManager.faceNormal(); /// get face to node map @@ -621,9 +622,6 @@ void ElasticWaveEquationSEM::applyFreeSurfaceBC( real64 const time, DomainPartit arrayView1d< localIndex > const freeSurfaceNodeIndicator = nodeManager.getField< fields::FreeSurfaceNodeIndicator >(); - freeSurfaceFaceIndicator.zero(); - freeSurfaceNodeIndicator.zero(); - fsManager.apply( time, domain.getMeshBody( 0 ).getMeshLevel( m_discretizationName ), string( "FreeSurface" ), diff --git a/src/coreComponents/physicsSolvers/wavePropagation/ElasticWaveEquationSEMKernel.hpp b/src/coreComponents/physicsSolvers/wavePropagation/ElasticWaveEquationSEMKernel.hpp index 723946c87dc..a776dec8a6c 100644 --- a/src/coreComponents/physicsSolvers/wavePropagation/ElasticWaveEquationSEMKernel.hpp +++ b/src/coreComponents/physicsSolvers/wavePropagation/ElasticWaveEquationSEMKernel.hpp @@ -65,7 +65,7 @@ struct PrecomputeSourceAndReceiverKernel static void launch( localIndex const size, localIndex const numFacesPerElem, - arrayView2d< real64 const, nodes::REFERENCE_POSITION_USD > const X, + arrayView2d< WaveSolverBase::wsCoordType const, nodes::REFERENCE_POSITION_USD > const X, arrayView1d< integer const > const elemGhostRank, arrayView2d< localIndex const, cells::NODE_MAP_USD > const & elemsToNodes, arrayView2d< localIndex const > const elemsToFaces, @@ -239,7 +239,7 @@ struct MassMatrixKernel //std::enable_if_t< geos::is_sem_formulation< std::remove_cv_t< FE_TYPE_ > >::value, void > void launch( localIndex const size, - arrayView2d< real64 const, nodes::REFERENCE_POSITION_USD > const X, + arrayView2d< WaveSolverBase::wsCoordType const, nodes::REFERENCE_POSITION_USD > const X, arrayView2d< localIndex const, cells::NODE_MAP_USD > const elemsToNodes, arrayView1d< real32 const > const density, arrayView1d< real32 > const mass ) @@ -298,7 +298,7 @@ struct DampingMatrixKernel //std::enable_if_t< geos::is_sem_formulation< std::remove_cv_t< FE_TYPE_ > >::value, void > void launch( localIndex const size, - arrayView2d< real64 const, nodes::REFERENCE_POSITION_USD > const X, + arrayView2d< WaveSolverBase::wsCoordType const, nodes::REFERENCE_POSITION_USD > const X, arrayView2d< localIndex const > const facesToElems, ArrayOfArraysView< localIndex const > const facesToNodes, arrayView1d< integer const > const facesDomainBoundaryIndicator, @@ -428,7 +428,7 @@ class ExplicitElasticSEM : public finiteElement::KernelBase< SUBREGION_TYPE, Base( elementSubRegion, finiteElementSpace, inputConstitutiveType ), - m_X( nodeManager.referencePosition() ), + m_X( nodeManager.getField< fields::referencePosition32 >() ), m_ux_n( nodeManager.getField< fields::Displacementx_n >() ), m_uy_n( nodeManager.getField< fields::Displacementy_n >() ), m_uz_n( nodeManager.getField< fields::Displacementz_n >() ), @@ -531,7 +531,7 @@ class ExplicitElasticSEM : public finiteElement::KernelBase< SUBREGION_TYPE, protected: /// The array containing the nodal position array. - arrayView2d< real64 const, nodes::REFERENCE_POSITION_USD > const m_X; + arrayView2d< WaveSolverBase::wsCoordType const, nodes::REFERENCE_POSITION_USD > const m_X; /// The array containing the nodal displacement array in x direction. arrayView1d< real32 > const m_ux_n; diff --git a/src/coreComponents/physicsSolvers/wavePropagation/WaveSolverBase.cpp b/src/coreComponents/physicsSolvers/wavePropagation/WaveSolverBase.cpp index b42324a7dfe..64b69f3c103 100644 --- a/src/coreComponents/physicsSolvers/wavePropagation/WaveSolverBase.cpp +++ b/src/coreComponents/physicsSolvers/wavePropagation/WaveSolverBase.cpp @@ -27,6 +27,8 @@ #include "mainInterface/ProblemManager.hpp" #include "mesh/mpiCommunications/CommunicationTools.hpp" +#include + namespace geos { @@ -93,20 +95,25 @@ WaveSolverBase::WaveSolverBase( const std::string & name, setApplyDefaultValue( 0 ). setDescription( "Set the current shot for temporary files" ); - registerWrapper( viewKeyStruct::lifoSizeString(), &m_lifoSize ). + registerWrapper( viewKeyStruct::enableLifoString(), &m_enableLifo ). setInputFlag( InputFlags::OPTIONAL ). setApplyDefaultValue( 0 ). - setDescription( "Set the capacity of the lifo storage" ); + setDescription( "Set to 1 to enable LIFO storage feature" ); + + registerWrapper( viewKeyStruct::lifoSizeString(), &m_lifoSize ). + setInputFlag( InputFlags::OPTIONAL ). + setApplyDefaultValue( std::numeric_limits< int >::max() ). + setDescription( "Set the capacity of the lifo storage (should be the total number of buffers to store in the LIFO)" ); registerWrapper( viewKeyStruct::lifoOnDeviceString(), &m_lifoOnDevice ). setInputFlag( InputFlags::OPTIONAL ). - setApplyDefaultValue( 0 ). - setDescription( "Set the capacity of the lifo device storage" ); + setApplyDefaultValue( -80 ). + setDescription( "Set the capacity of the lifo device storage (if negative, opposite of percentage of remaining memory)" ); registerWrapper( viewKeyStruct::lifoOnHostString(), &m_lifoOnHost ). setInputFlag( InputFlags::OPTIONAL ). - setApplyDefaultValue( 0 ). - setDescription( "Set the capacity of the lifo host storage" ); + setApplyDefaultValue( -80 ). + setDescription( "Set the capacity of the lifo host storage (if negative, opposite of percentage of remaining memory)" ); registerWrapper( viewKeyStruct::usePMLString(), &m_usePML ). @@ -169,6 +176,29 @@ void WaveSolverBase::reinit() initializePostInitialConditionsPreSubGroups(); } +void WaveSolverBase::registerDataOnMesh( Group & meshBodies ) +{ + forDiscretizationOnMeshTargets( meshBodies, [&] ( string const &, + MeshLevel & mesh, + arrayView1d< string const > const & ) + { + NodeManager & nodeManager = mesh.getNodeManager(); + + nodeManager.registerField< fields::referencePosition32 >( this->getName() ); + arrayView2d< real64 const, nodes::REFERENCE_POSITION_USD > const X = nodeManager.referencePosition().toViewConst(); + + nodeManager.getField< fields::referencePosition32 >().resizeDimension< 1 >( X.size( 1 ) ); + arrayView2d< wsCoordType, nodes::REFERENCE_POSITION_USD > const X32 = nodeManager.getField< fields::referencePosition32 >(); + for( int i = 0; i < X.size( 0 ); i++ ) + { + for( int j = 0; j < X.size( 1 ); j++ ) + { + X32[i][j] = X[i][j]; + } + } + } ); +} + void WaveSolverBase::initializePreSubGroups() { SolverBase::initializePreSubGroups(); diff --git a/src/coreComponents/physicsSolvers/wavePropagation/WaveSolverBase.hpp b/src/coreComponents/physicsSolvers/wavePropagation/WaveSolverBase.hpp index d8169b03c10..14beb4b005f 100644 --- a/src/coreComponents/physicsSolvers/wavePropagation/WaveSolverBase.hpp +++ b/src/coreComponents/physicsSolvers/wavePropagation/WaveSolverBase.hpp @@ -23,7 +23,7 @@ #include "mesh/MeshFields.hpp" #include "physicsSolvers/SolverBase.hpp" -#include "common/lifoStorage.hpp" +#include "common/LifoStorage.hpp" #if !defined( GEOS_USE_HIP ) #include "finiteElement/elementFormulations/Qk_Hexahedron_Lagrange_GaussLobatto.hpp" #endif @@ -49,7 +49,8 @@ class WaveSolverBase : public SolverBase { public: - using EXEC_POLICY = parallelDevicePolicy< 32 >; + using EXEC_POLICY = parallelDevicePolicy< >; + using wsCoordType = real32; WaveSolverBase( const std::string & name, Group * const parent ); @@ -100,6 +101,7 @@ class WaveSolverBase : public SolverBase static constexpr char const * forwardString() { return "forward"; } static constexpr char const * saveFieldsString() { return "saveFields"; } static constexpr char const * shotIndexString() { return "shotIndex"; } + static constexpr char const * enableLifoString() { return "enableLifo"; } static constexpr char const * lifoSizeString() { return "lifoSize"; } static constexpr char const * lifoOnDeviceString() { return "lifoOnDevice"; } static constexpr char const * lifoOnHostString() { return "lifoOnHost"; } @@ -188,6 +190,10 @@ class WaveSolverBase : public SolverBase DomainPartition & domain, bool const computeGradient ) = 0; + + virtual void registerDataOnMesh( Group & meshBodies ) override; + + localIndex getNumNodesPerElem(); /// Coordinates of the sources in the mesh @@ -253,17 +259,20 @@ class WaveSolverBase : public SolverBase /// Flag that indicates whether the receiver is local or not to the MPI rank array1d< localIndex > m_receiverIsLocal; - /// lifo size + /// Flag to enable LIFO + localIndex m_enableLifo; + + /// lifo size (should be the total number of buffer to save in LIFO) localIndex m_lifoSize; - /// Number of buffers to store on device by LIFO + /// Number of buffers to store on device by LIFO (if negative, opposite of percentage of remaining memory) localIndex m_lifoOnDevice; - /// Number of buffers to store on host by LIFO + /// Number of buffers to store on host by LIFO (if negative, opposite of percentage of remaining memory) localIndex m_lifoOnHost; /// LIFO to store p_dt2 - std::unique_ptr< lifoStorage< real32 > > m_lifo; + std::unique_ptr< LifoStorage< real32, localIndex > > m_lifo; struct parametersPML { @@ -287,6 +296,17 @@ class WaveSolverBase : public SolverBase }; +namespace fields +{ +using reference32Type = array2d< WaveSolverBase::wsCoordType, nodes::REFERENCE_POSITION_PERM >; +DECLARE_FIELD( referencePosition32, + "referencePosition32", + reference32Type, + 0, + NOPLOT, + WRITE_AND_READ, + "Copy of the referencePosition from NodeManager in 32 bits integer" ); +} } /* namespace geos */ #endif /* GEOS_PHYSICSSOLVERS_WAVEPROPAGATION_WAVESOLVERBASE_HPP_ */ diff --git a/src/coreComponents/physicsSolvers/wavePropagation/WaveSolverUtils.hpp b/src/coreComponents/physicsSolvers/wavePropagation/WaveSolverUtils.hpp index adfcdde408b..fddb4470bf8 100644 --- a/src/coreComponents/physicsSolvers/wavePropagation/WaveSolverUtils.hpp +++ b/src/coreComponents/physicsSolvers/wavePropagation/WaveSolverUtils.hpp @@ -130,6 +130,8 @@ struct WaveSolverUtils static void compute2dVariableSeismoTrace( real64 const time_n, real64 const dt, + localIndex const regionIndex, + arrayView1d< localIndex const > const receiverRegion, real64 const timeSeismo, localIndex iSeismo, arrayView1d< localIndex const > const rcvElem, @@ -152,16 +154,19 @@ struct WaveSolverUtils { if( receiverIsLocal[ircv] == 1 ) { - varAtReceivers[iSeismo][ircv] = 0.0; - real32 vtmp_np1 = 0.0; - real32 vtmp_n = 0.0; - for( localIndex inode = 0; inode < receiverConstants.size( 1 ); ++inode ) + if( receiverRegion[ircv] == regionIndex ) { - vtmp_np1 += var_np1[rcvElem[ircv]][inode] * receiverConstants[ircv][inode]; - vtmp_n += var_n[rcvElem[ircv]][inode] * receiverConstants[ircv][inode]; + varAtReceivers[iSeismo][ircv] = 0.0; + real32 vtmp_np1 = 0.0; + real32 vtmp_n = 0.0; + for( localIndex inode = 0; inode < receiverConstants.size( 1 ); ++inode ) + { + vtmp_np1 += var_np1[rcvElem[ircv]][inode] * receiverConstants[ircv][inode]; + vtmp_n += var_n[rcvElem[ircv]][inode] * receiverConstants[ircv][inode]; + } + // linear interpolation between the pressure value at time_n and time_(n+1) + varAtReceivers[iSeismo][ircv] = a1*vtmp_n + a2*vtmp_np1; } - // linear interpolation between the pressure value at time_n and time_(n+1) - varAtReceivers[iSeismo][ircv] = a1*vtmp_n + a2*vtmp_np1; } } ); } @@ -170,29 +175,30 @@ struct WaveSolverUtils // Output will then only be done via the previous code. if( iSeismo == nsamplesSeismoTrace - 1 ) { - forAll< serialPolicy >( receiverConstants.size( 0 ), [=] ( localIndex const ircv ) + if( outputSeismoTrace == 1 ) { - if( outputSeismoTrace == 1 ) + forAll< serialPolicy >( receiverConstants.size( 0 ), [=] ( localIndex const ircv ) { if( receiverIsLocal[ircv] == 1 ) { // Note: this "manual" output to file is temporary // It should be removed as soon as we can use TimeHistory to output data not registered on the mesh // TODO: remove saveSeismo and replace with TimeHistory - std::ofstream f( GEOS_FMT( "seismoTraceReceiver{:03}.txt", ircv ), std::ios::app ); - for( localIndex iSample = 0; iSample < nsamplesSeismoTrace; ++iSample ) + if( receiverRegion[ircv] == regionIndex ) { - f << iSample << " " << varAtReceivers[iSample][ircv] << std::endl; + std::ofstream f( GEOS_FMT( "seismoTraceReceiver{:03}.txt", ircv ), std::ios::app ); + for( localIndex iSample = 0; iSample < nsamplesSeismoTrace; ++iSample ) + { + f << iSample << " " << varAtReceivers[iSample][ircv] << std::endl; + } + f.close(); } - f.close(); } - } - } ); + } ); + } } - } - /** * @brief Check if the source point is inside an element or not */ @@ -265,7 +271,7 @@ struct WaveSolverUtils static void computeCoordinatesOnReferenceElement( real64 const (&coords)[3], arraySlice1d< localIndex const, cells::NODE_MAP_USD - 1 > const elemsToNodes, - arrayView2d< real64 const, nodes::REFERENCE_POSITION_USD > const X, + arrayView2d< WaveSolverBase::wsCoordType const, nodes::REFERENCE_POSITION_USD > const X, real64 (& coordsOnRefElem)[3] ) { real64 xLocal[FE_TYPE::numNodes][3]{}; diff --git a/src/coreComponents/schema/docs/AcousticFirstOrderSEM.rst b/src/coreComponents/schema/docs/AcousticFirstOrderSEM.rst index ed50903dfe2..000b0ace852 100644 --- a/src/coreComponents/schema/docs/AcousticFirstOrderSEM.rst +++ b/src/coreComponents/schema/docs/AcousticFirstOrderSEM.rst @@ -1,29 +1,30 @@ -========================= ============== ======== ======================================================================================================================================================================================================================================================================================================================== -Name Type Default Description -========================= ============== ======== ======================================================================================================================================================================================================================================================================================================================== -cflFactor real64 0.5 Factor to apply to the `CFL condition `_ when calculating the maximum allowable time step. Values should be in the interval (0,1] -discretization string required Name of discretization object (defined in the :ref:`NumericalMethodsManager`) to use for this solver. For instance, if this is a Finite Element Solver, the name of a :ref:`FiniteElement` should be specified. If this is a Finite Volume Method, the name of a :ref:`FiniteVolume` discretization should be specified. -dtSeismoTrace real64 0 Time step for output pressure at receivers -forward integer 1 Set to 1 to compute forward propagation -initialDt real64 1e+99 Initial time-step value required by the solver to the event manager. -lifoOnDevice integer 0 Set the capacity of the lifo device storage -lifoOnHost integer 0 Set the capacity of the lifo host storage -lifoSize integer 0 Set the capacity of the lifo storage -linearDASGeometry real64_array2d {{0}} Geometry parameters for a linear DAS fiber (dip, azimuth, gauge length) -logLevel integer 0 Log level -name string required A name is required for any non-unique nodes -outputSeismoTrace integer 0 Flag that indicates if we write the seismo trace in a file .txt, 0 no output, 1 otherwise -receiverCoordinates real64_array2d required Coordinates (x,y,z) of the receivers -rickerOrder integer 2 Flag that indicates the order of the Ricker to be used o, 1 or 2. Order 2 by default -saveFields integer 0 Set to 1 to save fields during forward and restore them during backward -shotIndex integer 0 Set the current shot for temporary files -sourceCoordinates real64_array2d required Coordinates (x,y,z) of the sources -targetRegions string_array required Allowable regions that the solver may be applied to. Note that this does not indicate that the solver will be applied to these regions, only that allocation will occur such that the solver may be applied to these regions. The decision about what regions this solver will beapplied to rests in the EventManager. -timeSourceFrequency real32 required Central frequency for the time source -LinearSolverParameters node unique :ref:`XML_LinearSolverParameters` -NonlinearSolverParameters node unique :ref:`XML_NonlinearSolverParameters` -========================= ============== ======== ======================================================================================================================================================================================================================================================================================================================== +========================= ============== ========== ======================================================================================================================================================================================================================================================================================================================== +Name Type Default Description +========================= ============== ========== ======================================================================================================================================================================================================================================================================================================================== +cflFactor real64 0.5 Factor to apply to the `CFL condition `_ when calculating the maximum allowable time step. Values should be in the interval (0,1] +discretization string required Name of discretization object (defined in the :ref:`NumericalMethodsManager`) to use for this solver. For instance, if this is a Finite Element Solver, the name of a :ref:`FiniteElement` should be specified. If this is a Finite Volume Method, the name of a :ref:`FiniteVolume` discretization should be specified. +dtSeismoTrace real64 0 Time step for output pressure at receivers +enableLifo integer 0 Set to 1 to enable LIFO storage feature +forward integer 1 Set to 1 to compute forward propagation +initialDt real64 1e+99 Initial time-step value required by the solver to the event manager. +lifoOnDevice integer -80 Set the capacity of the lifo device storage (if negative, opposite of percentage of remaining memory) +lifoOnHost integer -80 Set the capacity of the lifo host storage (if negative, opposite of percentage of remaining memory) +lifoSize integer 2147483647 Set the capacity of the lifo storage (should be the total number of buffers to store in the LIFO) +linearDASGeometry real64_array2d {{0}} Geometry parameters for a linear DAS fiber (dip, azimuth, gauge length) +logLevel integer 0 Log level +name string required A name is required for any non-unique nodes +outputSeismoTrace integer 0 Flag that indicates if we write the seismo trace in a file .txt, 0 no output, 1 otherwise +receiverCoordinates real64_array2d required Coordinates (x,y,z) of the receivers +rickerOrder integer 2 Flag that indicates the order of the Ricker to be used o, 1 or 2. Order 2 by default +saveFields integer 0 Set to 1 to save fields during forward and restore them during backward +shotIndex integer 0 Set the current shot for temporary files +sourceCoordinates real64_array2d required Coordinates (x,y,z) of the sources +targetRegions string_array required Allowable regions that the solver may be applied to. Note that this does not indicate that the solver will be applied to these regions, only that allocation will occur such that the solver may be applied to these regions. The decision about what regions this solver will beapplied to rests in the EventManager. +timeSourceFrequency real32 required Central frequency for the time source +LinearSolverParameters node unique :ref:`XML_LinearSolverParameters` +NonlinearSolverParameters node unique :ref:`XML_NonlinearSolverParameters` +========================= ============== ========== ======================================================================================================================================================================================================================================================================================================================== diff --git a/src/coreComponents/schema/docs/AcousticFirstOrderSEM_other.rst b/src/coreComponents/schema/docs/AcousticFirstOrderSEM_other.rst index 91b6085a473..37156e3e38a 100644 --- a/src/coreComponents/schema/docs/AcousticFirstOrderSEM_other.rst +++ b/src/coreComponents/schema/docs/AcousticFirstOrderSEM_other.rst @@ -10,10 +10,12 @@ pressureNp1AtReceivers real32_array2d rcvElem integer_array Element containing the receivers receiverIsLocal integer_array Flag that indicates whether the receiver is local to this MPI rank receiverNodeIds integer_array2d Indices of the nodes (in the right order) for each receiver point +receiverRegion integer_array Region containing the receivers sourceConstants real64_array2d Constant part of the receiver for the nodes listed in m_receiverNodeIds sourceElem integer_array Element containing the sources sourceIsAccessible integer_array Flag that indicates whether the source is local to this MPI rank sourceNodeIds integer_array2d Indices of the nodes (in the right order) for each source point +sourceRegion integer_array Region containing the sources sourceValue real32_array2d Source Value of the sources useDAS integer Flag to indicate if DAS type of data will be modeled usePML integer Flag to apply PML diff --git a/src/coreComponents/schema/docs/AcousticSEM.rst b/src/coreComponents/schema/docs/AcousticSEM.rst index ed50903dfe2..000b0ace852 100644 --- a/src/coreComponents/schema/docs/AcousticSEM.rst +++ b/src/coreComponents/schema/docs/AcousticSEM.rst @@ -1,29 +1,30 @@ -========================= ============== ======== ======================================================================================================================================================================================================================================================================================================================== -Name Type Default Description -========================= ============== ======== ======================================================================================================================================================================================================================================================================================================================== -cflFactor real64 0.5 Factor to apply to the `CFL condition `_ when calculating the maximum allowable time step. Values should be in the interval (0,1] -discretization string required Name of discretization object (defined in the :ref:`NumericalMethodsManager`) to use for this solver. For instance, if this is a Finite Element Solver, the name of a :ref:`FiniteElement` should be specified. If this is a Finite Volume Method, the name of a :ref:`FiniteVolume` discretization should be specified. -dtSeismoTrace real64 0 Time step for output pressure at receivers -forward integer 1 Set to 1 to compute forward propagation -initialDt real64 1e+99 Initial time-step value required by the solver to the event manager. -lifoOnDevice integer 0 Set the capacity of the lifo device storage -lifoOnHost integer 0 Set the capacity of the lifo host storage -lifoSize integer 0 Set the capacity of the lifo storage -linearDASGeometry real64_array2d {{0}} Geometry parameters for a linear DAS fiber (dip, azimuth, gauge length) -logLevel integer 0 Log level -name string required A name is required for any non-unique nodes -outputSeismoTrace integer 0 Flag that indicates if we write the seismo trace in a file .txt, 0 no output, 1 otherwise -receiverCoordinates real64_array2d required Coordinates (x,y,z) of the receivers -rickerOrder integer 2 Flag that indicates the order of the Ricker to be used o, 1 or 2. Order 2 by default -saveFields integer 0 Set to 1 to save fields during forward and restore them during backward -shotIndex integer 0 Set the current shot for temporary files -sourceCoordinates real64_array2d required Coordinates (x,y,z) of the sources -targetRegions string_array required Allowable regions that the solver may be applied to. Note that this does not indicate that the solver will be applied to these regions, only that allocation will occur such that the solver may be applied to these regions. The decision about what regions this solver will beapplied to rests in the EventManager. -timeSourceFrequency real32 required Central frequency for the time source -LinearSolverParameters node unique :ref:`XML_LinearSolverParameters` -NonlinearSolverParameters node unique :ref:`XML_NonlinearSolverParameters` -========================= ============== ======== ======================================================================================================================================================================================================================================================================================================================== +========================= ============== ========== ======================================================================================================================================================================================================================================================================================================================== +Name Type Default Description +========================= ============== ========== ======================================================================================================================================================================================================================================================================================================================== +cflFactor real64 0.5 Factor to apply to the `CFL condition `_ when calculating the maximum allowable time step. Values should be in the interval (0,1] +discretization string required Name of discretization object (defined in the :ref:`NumericalMethodsManager`) to use for this solver. For instance, if this is a Finite Element Solver, the name of a :ref:`FiniteElement` should be specified. If this is a Finite Volume Method, the name of a :ref:`FiniteVolume` discretization should be specified. +dtSeismoTrace real64 0 Time step for output pressure at receivers +enableLifo integer 0 Set to 1 to enable LIFO storage feature +forward integer 1 Set to 1 to compute forward propagation +initialDt real64 1e+99 Initial time-step value required by the solver to the event manager. +lifoOnDevice integer -80 Set the capacity of the lifo device storage (if negative, opposite of percentage of remaining memory) +lifoOnHost integer -80 Set the capacity of the lifo host storage (if negative, opposite of percentage of remaining memory) +lifoSize integer 2147483647 Set the capacity of the lifo storage (should be the total number of buffers to store in the LIFO) +linearDASGeometry real64_array2d {{0}} Geometry parameters for a linear DAS fiber (dip, azimuth, gauge length) +logLevel integer 0 Log level +name string required A name is required for any non-unique nodes +outputSeismoTrace integer 0 Flag that indicates if we write the seismo trace in a file .txt, 0 no output, 1 otherwise +receiverCoordinates real64_array2d required Coordinates (x,y,z) of the receivers +rickerOrder integer 2 Flag that indicates the order of the Ricker to be used o, 1 or 2. Order 2 by default +saveFields integer 0 Set to 1 to save fields during forward and restore them during backward +shotIndex integer 0 Set the current shot for temporary files +sourceCoordinates real64_array2d required Coordinates (x,y,z) of the sources +targetRegions string_array required Allowable regions that the solver may be applied to. Note that this does not indicate that the solver will be applied to these regions, only that allocation will occur such that the solver may be applied to these regions. The decision about what regions this solver will beapplied to rests in the EventManager. +timeSourceFrequency real32 required Central frequency for the time source +LinearSolverParameters node unique :ref:`XML_LinearSolverParameters` +NonlinearSolverParameters node unique :ref:`XML_NonlinearSolverParameters` +========================= ============== ========== ======================================================================================================================================================================================================================================================================================================================== diff --git a/src/coreComponents/schema/docs/BiotPorosity_other.rst b/src/coreComponents/schema/docs/BiotPorosity_other.rst index 8512163fc1c..962677d8251 100644 --- a/src/coreComponents/schema/docs/BiotPorosity_other.rst +++ b/src/coreComponents/schema/docs/BiotPorosity_other.rst @@ -1,18 +1,19 @@ -=========================== ============== ======================================================= -Name Type Description -=========================== ============== ======================================================= -biotCoefficient real64_array Biot coefficient -dPorosity_dPressure real64_array2d Derivative of rock porosity with respect to pressure -dPorosity_dTemperature real64_array2d Derivative of rock porosity with respect to temperature -initialPorosity real64_array2d Initial porosity -meanStressIncrement real64_array2d Volumetric stress increment -porosity real64_array2d Rock porosity -porosity_n real64_array2d Rock porosity at the previous converged time step -referencePorosity real64_array Reference porosity -solidBulkModulus real64_array Solid bulk modulus -thermalExpansionCoefficient real64_array Thermal expansion coefficient -=========================== ============== ======================================================= +===================================== ============== ==================================================================================================== +Name Type Description +===================================== ============== ==================================================================================================== +averageMeanEffectiveStressIncrement_k real64_array Mean effective stress increment averaged over quadrature points at the previous sequential iteration +biotCoefficient real64_array Biot coefficient +dPorosity_dPressure real64_array2d Derivative of rock porosity with respect to pressure +dPorosity_dTemperature real64_array2d Derivative of rock porosity with respect to temperature +initialPorosity real64_array2d Initial porosity +meanEffectiveStressIncrement_k real64_array2d Mean effective stress increment at quadrature points at the previous sequential iteration +porosity real64_array2d Rock porosity +porosity_n real64_array2d Rock porosity at the previous converged time step +referencePorosity real64_array Reference porosity +solidBulkModulus real64_array Solid bulk modulus +thermalExpansionCoefficient real64_array Thermal expansion coefficient +===================================== ============== ==================================================================================================== diff --git a/src/coreComponents/schema/docs/CustomPolarObject.rst b/src/coreComponents/schema/docs/CustomPolarObject.rst new file mode 100644 index 00000000000..1461fd89ada --- /dev/null +++ b/src/coreComponents/schema/docs/CustomPolarObject.rst @@ -0,0 +1,15 @@ + + +============ ============ ======== ========================================================================================================================================= +Name Type Default Description +============ ============ ======== ========================================================================================================================================= +center R1Tensor required (x,y,z) coordinates of the center of the CustomPolarObject +coefficients real64_array required Coefficients of the CustomPolarObject function relating the localradius to the angle theta. +lengthVector R1Tensor required Tangent vector defining the orthonormal basis along with the normal. +name string required A name is required for any non-unique nodes +normal R1Tensor required Normal (n_x,n_y,n_z) to the plane (will be normalized automatically) +tolerance real64 1e-05 Tolerance to determine if a point sits on the CustomPolarObject or not. It is relative to the maximum dimension of the CustomPolarObject. +widthVector R1Tensor required Tangent vector defining the orthonormal basis along with the normal. +============ ============ ======== ========================================================================================================================================= + + diff --git a/src/coreComponents/schema/docs/BoundedPlane_other.rst b/src/coreComponents/schema/docs/CustomPolarObject_other.rst similarity index 100% rename from src/coreComponents/schema/docs/BoundedPlane_other.rst rename to src/coreComponents/schema/docs/CustomPolarObject_other.rst diff --git a/src/coreComponents/schema/docs/Disc.rst b/src/coreComponents/schema/docs/Disc.rst new file mode 100644 index 00000000000..7a2813b33f8 --- /dev/null +++ b/src/coreComponents/schema/docs/Disc.rst @@ -0,0 +1,15 @@ + + +============ ======== ======== =============================================================================================================== +Name Type Default Description +============ ======== ======== =============================================================================================================== +center R1Tensor required (x,y,z) coordinates of the center of the disc +lengthVector R1Tensor required Tangent vector defining the orthonormal basis along with the normal. +name string required A name is required for any non-unique nodes +normal R1Tensor required Normal (n_x,n_y,n_z) to the plane (will be normalized automatically) +radius real64 required Radius of the disc. +tolerance real64 1e-05 Tolerance to determine if a point sits on the disc or not. It is relative to the maximum dimension of the disc. +widthVector R1Tensor required Tangent vector defining the orthonormal basis along with the normal. +============ ======== ======== =============================================================================================================== + + diff --git a/src/coreComponents/schema/docs/Disc_other.rst b/src/coreComponents/schema/docs/Disc_other.rst new file mode 100644 index 00000000000..adf1c1b8aec --- /dev/null +++ b/src/coreComponents/schema/docs/Disc_other.rst @@ -0,0 +1,9 @@ + + +==== ==== ============================ +Name Type Description +==== ==== ============================ + (no documentation available) +==== ==== ============================ + + diff --git a/src/coreComponents/schema/docs/ElasticFirstOrderSEM.rst b/src/coreComponents/schema/docs/ElasticFirstOrderSEM.rst index ed50903dfe2..000b0ace852 100644 --- a/src/coreComponents/schema/docs/ElasticFirstOrderSEM.rst +++ b/src/coreComponents/schema/docs/ElasticFirstOrderSEM.rst @@ -1,29 +1,30 @@ -========================= ============== ======== ======================================================================================================================================================================================================================================================================================================================== -Name Type Default Description -========================= ============== ======== ======================================================================================================================================================================================================================================================================================================================== -cflFactor real64 0.5 Factor to apply to the `CFL condition `_ when calculating the maximum allowable time step. Values should be in the interval (0,1] -discretization string required Name of discretization object (defined in the :ref:`NumericalMethodsManager`) to use for this solver. For instance, if this is a Finite Element Solver, the name of a :ref:`FiniteElement` should be specified. If this is a Finite Volume Method, the name of a :ref:`FiniteVolume` discretization should be specified. -dtSeismoTrace real64 0 Time step for output pressure at receivers -forward integer 1 Set to 1 to compute forward propagation -initialDt real64 1e+99 Initial time-step value required by the solver to the event manager. -lifoOnDevice integer 0 Set the capacity of the lifo device storage -lifoOnHost integer 0 Set the capacity of the lifo host storage -lifoSize integer 0 Set the capacity of the lifo storage -linearDASGeometry real64_array2d {{0}} Geometry parameters for a linear DAS fiber (dip, azimuth, gauge length) -logLevel integer 0 Log level -name string required A name is required for any non-unique nodes -outputSeismoTrace integer 0 Flag that indicates if we write the seismo trace in a file .txt, 0 no output, 1 otherwise -receiverCoordinates real64_array2d required Coordinates (x,y,z) of the receivers -rickerOrder integer 2 Flag that indicates the order of the Ricker to be used o, 1 or 2. Order 2 by default -saveFields integer 0 Set to 1 to save fields during forward and restore them during backward -shotIndex integer 0 Set the current shot for temporary files -sourceCoordinates real64_array2d required Coordinates (x,y,z) of the sources -targetRegions string_array required Allowable regions that the solver may be applied to. Note that this does not indicate that the solver will be applied to these regions, only that allocation will occur such that the solver may be applied to these regions. The decision about what regions this solver will beapplied to rests in the EventManager. -timeSourceFrequency real32 required Central frequency for the time source -LinearSolverParameters node unique :ref:`XML_LinearSolverParameters` -NonlinearSolverParameters node unique :ref:`XML_NonlinearSolverParameters` -========================= ============== ======== ======================================================================================================================================================================================================================================================================================================================== +========================= ============== ========== ======================================================================================================================================================================================================================================================================================================================== +Name Type Default Description +========================= ============== ========== ======================================================================================================================================================================================================================================================================================================================== +cflFactor real64 0.5 Factor to apply to the `CFL condition `_ when calculating the maximum allowable time step. Values should be in the interval (0,1] +discretization string required Name of discretization object (defined in the :ref:`NumericalMethodsManager`) to use for this solver. For instance, if this is a Finite Element Solver, the name of a :ref:`FiniteElement` should be specified. If this is a Finite Volume Method, the name of a :ref:`FiniteVolume` discretization should be specified. +dtSeismoTrace real64 0 Time step for output pressure at receivers +enableLifo integer 0 Set to 1 to enable LIFO storage feature +forward integer 1 Set to 1 to compute forward propagation +initialDt real64 1e+99 Initial time-step value required by the solver to the event manager. +lifoOnDevice integer -80 Set the capacity of the lifo device storage (if negative, opposite of percentage of remaining memory) +lifoOnHost integer -80 Set the capacity of the lifo host storage (if negative, opposite of percentage of remaining memory) +lifoSize integer 2147483647 Set the capacity of the lifo storage (should be the total number of buffers to store in the LIFO) +linearDASGeometry real64_array2d {{0}} Geometry parameters for a linear DAS fiber (dip, azimuth, gauge length) +logLevel integer 0 Log level +name string required A name is required for any non-unique nodes +outputSeismoTrace integer 0 Flag that indicates if we write the seismo trace in a file .txt, 0 no output, 1 otherwise +receiverCoordinates real64_array2d required Coordinates (x,y,z) of the receivers +rickerOrder integer 2 Flag that indicates the order of the Ricker to be used o, 1 or 2. Order 2 by default +saveFields integer 0 Set to 1 to save fields during forward and restore them during backward +shotIndex integer 0 Set the current shot for temporary files +sourceCoordinates real64_array2d required Coordinates (x,y,z) of the sources +targetRegions string_array required Allowable regions that the solver may be applied to. Note that this does not indicate that the solver will be applied to these regions, only that allocation will occur such that the solver may be applied to these regions. The decision about what regions this solver will beapplied to rests in the EventManager. +timeSourceFrequency real32 required Central frequency for the time source +LinearSolverParameters node unique :ref:`XML_LinearSolverParameters` +NonlinearSolverParameters node unique :ref:`XML_NonlinearSolverParameters` +========================= ============== ========== ======================================================================================================================================================================================================================================================================================================================== diff --git a/src/coreComponents/schema/docs/ElasticFirstOrderSEM_other.rst b/src/coreComponents/schema/docs/ElasticFirstOrderSEM_other.rst index c89e3395606..3633753cf60 100644 --- a/src/coreComponents/schema/docs/ElasticFirstOrderSEM_other.rst +++ b/src/coreComponents/schema/docs/ElasticFirstOrderSEM_other.rst @@ -12,10 +12,12 @@ meshTargets geos_mapBase< std_pair< string, string >, LvArray_Ar rcvElem integer_array Element containing the receivers receiverIsLocal integer_array Flag that indicates whether the receiver is local to this MPI rank receiverNodeIds integer_array2d Indices of the nodes (in the right order) for each receiver point +receiverRegion integer_array Region containing the receivers sourceConstants real64_array2d Constant part of the receiver for the nodes listed in m_receiverNodeIds sourceElem integer_array Element containing the sources sourceIsAccessible integer_array Flag that indicates whether the source is local to this MPI rank sourceNodeIds integer_array2d Indices of the nodes (in the right order) for each source point +sourceRegion integer_array Region containing the sources sourceValue real32_array2d Source Value of the sources useDAS integer Flag to indicate if DAS type of data will be modeled usePML integer Flag to apply PML diff --git a/src/coreComponents/schema/docs/ElasticSEM.rst b/src/coreComponents/schema/docs/ElasticSEM.rst index 91629226f27..acc22024dcc 100644 --- a/src/coreComponents/schema/docs/ElasticSEM.rst +++ b/src/coreComponents/schema/docs/ElasticSEM.rst @@ -6,11 +6,12 @@ Name Type Default Description cflFactor real64 0.5 Factor to apply to the `CFL condition `_ when calculating the maximum allowable time step. Values should be in the interval (0,1] discretization string required Name of discretization object (defined in the :ref:`NumericalMethodsManager`) to use for this solver. For instance, if this is a Finite Element Solver, the name of a :ref:`FiniteElement` should be specified. If this is a Finite Volume Method, the name of a :ref:`FiniteVolume` discretization should be specified. dtSeismoTrace real64 0 Time step for output pressure at receivers +enableLifo integer 0 Set to 1 to enable LIFO storage feature forward integer 1 Set to 1 to compute forward propagation initialDt real64 1e+99 Initial time-step value required by the solver to the event manager. -lifoOnDevice integer 0 Set the capacity of the lifo device storage -lifoOnHost integer 0 Set the capacity of the lifo host storage -lifoSize integer 0 Set the capacity of the lifo storage +lifoOnDevice integer -80 Set the capacity of the lifo device storage (if negative, opposite of percentage of remaining memory) +lifoOnHost integer -80 Set the capacity of the lifo host storage (if negative, opposite of percentage of remaining memory) +lifoSize integer 2147483647 Set the capacity of the lifo storage (should be the total number of buffers to store in the LIFO) linearDASGeometry real64_array2d {{0}} Geometry parameters for a linear DAS fiber (dip, azimuth, gauge length) logLevel integer 0 Log level name string required A name is required for any non-unique nodes diff --git a/src/coreComponents/schema/docs/EmbeddedSurfaceGenerator.rst b/src/coreComponents/schema/docs/EmbeddedSurfaceGenerator.rst index 3ea40b5a87e..fed721b718b 100644 --- a/src/coreComponents/schema/docs/EmbeddedSurfaceGenerator.rst +++ b/src/coreComponents/schema/docs/EmbeddedSurfaceGenerator.rst @@ -10,6 +10,7 @@ initialDt real64 1e+99 Initial time-step value re logLevel integer 0 Log level mpiCommOrder integer 0 Flag to enable MPI consistent communication ordering name string required A name is required for any non-unique nodes +targetObjects string_array required List of geometric objects that will be used to initialized the embedded surfaces/fractures. targetRegions string_array required Allowable regions that the solver may be applied to. Note that this does not indicate that the solver will be applied to these regions, only that allocation will occur such that the solver may be applied to these regions. The decision about what regions this solver will beapplied to rests in the EventManager. LinearSolverParameters node unique :ref:`XML_LinearSolverParameters` NonlinearSolverParameters node unique :ref:`XML_NonlinearSolverParameters` diff --git a/src/coreComponents/schema/docs/Events.rst b/src/coreComponents/schema/docs/Events.rst index 1d75e447682..4073b6fdc2c 100644 --- a/src/coreComponents/schema/docs/Events.rst +++ b/src/coreComponents/schema/docs/Events.rst @@ -1,15 +1,16 @@ -============= ======= ============ =================================================== -Name Type Default Description -============= ======= ============ =================================================== -logLevel integer 0 Log level -maxCycle integer 2147483647 Maximum simulation cycle for the global event loop. -maxTime real64 1.79769e+308 Maximum simulation time for the global event loop. -minTime real64 0 Start simulation time for the global event loop. -HaltEvent node :ref:`XML_HaltEvent` -PeriodicEvent node :ref:`XML_PeriodicEvent` -SoloEvent node :ref:`XML_SoloEvent` -============= ======= ============ =================================================== +================ ================================== ============ =================================================== +Name Type Default Description +================ ================================== ============ =================================================== +logLevel integer 0 Log level +maxCycle integer 2147483647 Maximum simulation cycle for the global event loop. +maxTime real64 1.79769e+308 Maximum simulation time for the global event loop. +minTime real64 0 Start simulation time for the global event loop. +timeOutputFormat geos_EventManager_TimeOutputFormat seconds Format of the time in the GEOS log. +HaltEvent node :ref:`XML_HaltEvent` +PeriodicEvent node :ref:`XML_PeriodicEvent` +SoloEvent node :ref:`XML_SoloEvent` +================ ================================== ============ =================================================== diff --git a/src/coreComponents/schema/docs/Geometry.rst b/src/coreComponents/schema/docs/Geometry.rst index 5b988ffe5ce..43bc76a5f42 100644 --- a/src/coreComponents/schema/docs/Geometry.rst +++ b/src/coreComponents/schema/docs/Geometry.rst @@ -1,12 +1,14 @@ -============ ==== ======= ======================= -Name Type Default Description -============ ==== ======= ======================= -BoundedPlane node :ref:`XML_BoundedPlane` -Box node :ref:`XML_Box` -Cylinder node :ref:`XML_Cylinder` -ThickPlane node :ref:`XML_ThickPlane` -============ ==== ======= ======================= +================= ==== ======= ============================ +Name Type Default Description +================= ==== ======= ============================ +Box node :ref:`XML_Box` +CustomPolarObject node :ref:`XML_CustomPolarObject` +Cylinder node :ref:`XML_Cylinder` +Disc node :ref:`XML_Disc` +Rectangle node :ref:`XML_Rectangle` +ThickPlane node :ref:`XML_ThickPlane` +================= ==== ======= ============================ diff --git a/src/coreComponents/schema/docs/Geometry_other.rst b/src/coreComponents/schema/docs/Geometry_other.rst index 353dbeff5d9..af15f983b0a 100644 --- a/src/coreComponents/schema/docs/Geometry_other.rst +++ b/src/coreComponents/schema/docs/Geometry_other.rst @@ -1,12 +1,14 @@ -============ ==== ================================= -Name Type Description -============ ==== ================================= -BoundedPlane node :ref:`DATASTRUCTURE_BoundedPlane` -Box node :ref:`DATASTRUCTURE_Box` -Cylinder node :ref:`DATASTRUCTURE_Cylinder` -ThickPlane node :ref:`DATASTRUCTURE_ThickPlane` -============ ==== ================================= +================= ==== ====================================== +Name Type Description +================= ==== ====================================== +Box node :ref:`DATASTRUCTURE_Box` +CustomPolarObject node :ref:`DATASTRUCTURE_CustomPolarObject` +Cylinder node :ref:`DATASTRUCTURE_Cylinder` +Disc node :ref:`DATASTRUCTURE_Disc` +Rectangle node :ref:`DATASTRUCTURE_Rectangle` +ThickPlane node :ref:`DATASTRUCTURE_ThickPlane` +================= ==== ====================================== diff --git a/src/coreComponents/schema/docs/Hydrofracture.rst b/src/coreComponents/schema/docs/Hydrofracture.rst index 6bc73d1770f..0165e3f988d 100644 --- a/src/coreComponents/schema/docs/Hydrofracture.rst +++ b/src/coreComponents/schema/docs/Hydrofracture.rst @@ -7,6 +7,8 @@ cflFactor real64 0.5 Factor to apply to the `CFL cond contactRelationName string required Name of contact relation to enforce constraints on fracture boundary. flowSolverName string required Name of the flow solver used by the coupled solver initialDt real64 1e+99 Initial time-step value required by the solver to the event manager. +isMatrixPoroelastic integer 0 (no description available) +isThermal integer 0 Flag indicating whether the problem is thermal or not. Set isThermal="1" to enable the thermal coupling logLevel integer 0 Log level maxNumResolves integer 10 Value to indicate how many resolves may be executed to perform surface generation after the execution of flow and mechanics solver. name string required A name is required for any non-unique nodes diff --git a/src/coreComponents/schema/docs/Hydrofracture_other.rst b/src/coreComponents/schema/docs/Hydrofracture_other.rst index 42b4339e2e5..b11a7da184e 100644 --- a/src/coreComponents/schema/docs/Hydrofracture_other.rst +++ b/src/coreComponents/schema/docs/Hydrofracture_other.rst @@ -1,14 +1,15 @@ -========================= ============================================================================================================================================================== ======================================================================================================================================================================================================================================================================================================================== -Name Type Description -========================= ============================================================================================================================================================== ======================================================================================================================================================================================================================================================================================================================== -discretization string Name of discretization object (defined in the :ref:`NumericalMethodsManager`) to use for this solver. For instance, if this is a Finite Element Solver, the name of a :ref:`FiniteElement` should be specified. If this is a Finite Volume Method, the name of a :ref:`FiniteVolume` discretization should be specified. -maxStableDt real64 Value of the Maximum Stable Timestep for this solver. -meshTargets geos_mapBase< std_pair< string, string >, LvArray_Array< string, 1, camp_int_seq< long, 0l >, int, LvArray_ChaiBuffer >, std_integral_constant< bool, true > > MeshBody/Region combinations that the solver will be applied to. -LinearSolverParameters node :ref:`DATASTRUCTURE_LinearSolverParameters` -NonlinearSolverParameters node :ref:`DATASTRUCTURE_NonlinearSolverParameters` -SolverStatistics node :ref:`DATASTRUCTURE_SolverStatistics` -========================= ============================================================================================================================================================== ======================================================================================================================================================================================================================================================================================================================== +=========================== ============================================================================================================================================================== ======================================================================================================================================================================================================================================================================================================================== +Name Type Description +=========================== ============================================================================================================================================================== ======================================================================================================================================================================================================================================================================================================================== +discretization string Name of discretization object (defined in the :ref:`NumericalMethodsManager`) to use for this solver. For instance, if this is a Finite Element Solver, the name of a :ref:`FiniteElement` should be specified. If this is a Finite Volume Method, the name of a :ref:`FiniteVolume` discretization should be specified. +maxStableDt real64 Value of the Maximum Stable Timestep for this solver. +meshTargets geos_mapBase< std_pair< string, string >, LvArray_Array< string, 1, camp_int_seq< long, 0l >, int, LvArray_ChaiBuffer >, std_integral_constant< bool, true > > MeshBody/Region combinations that the solver will be applied to. +performStressInitialization integer Flag to indicate that the solver is going to perform stress initialization +LinearSolverParameters node :ref:`DATASTRUCTURE_LinearSolverParameters` +NonlinearSolverParameters node :ref:`DATASTRUCTURE_NonlinearSolverParameters` +SolverStatistics node :ref:`DATASTRUCTURE_SolverStatistics` +=========================== ============================================================================================================================================================== ======================================================================================================================================================================================================================================================================================================================== diff --git a/src/coreComponents/schema/docs/InternalMesh.rst b/src/coreComponents/schema/docs/InternalMesh.rst index 83088006483..d88ee9f4f46 100644 --- a/src/coreComponents/schema/docs/InternalMesh.rst +++ b/src/coreComponents/schema/docs/InternalMesh.rst @@ -17,6 +17,7 @@ yBias real64_array {1} Bias of element sizes in the y-directio yCoords real64_array required y-coordinates of each mesh block vertex zBias real64_array {1} Bias of element sizes in the z-direction within each mesh block (dz_left=(1+b)*L/N, dz_right=(1-b)*L/N) zCoords real64_array required z-coordinates of each mesh block vertex +InternalWell node :ref:`XML_InternalWell` ================= ============= ======== ======================================================================================================= diff --git a/src/coreComponents/schema/docs/InternalMesh_other.rst b/src/coreComponents/schema/docs/InternalMesh_other.rst index 14257041f64..aef746a9df7 100644 --- a/src/coreComponents/schema/docs/InternalMesh_other.rst +++ b/src/coreComponents/schema/docs/InternalMesh_other.rst @@ -1,9 +1,10 @@ -========== ==== =============================== -Name Type Description -========== ==== =============================== -meshLevels node :ref:`DATASTRUCTURE_meshLevels` -========== ==== =============================== +============ ==== ================================= +Name Type Description +============ ==== ================================= +InternalWell node :ref:`DATASTRUCTURE_InternalWell` +meshLevels node :ref:`DATASTRUCTURE_meshLevels` +============ ==== ================================= diff --git a/src/coreComponents/schema/docs/InternalWell.rst b/src/coreComponents/schema/docs/InternalWell.rst index cf54274bdea..253d791554d 100644 --- a/src/coreComponents/schema/docs/InternalWell.rst +++ b/src/coreComponents/schema/docs/InternalWell.rst @@ -4,7 +4,6 @@ Name Type Default Description ===================== =================== ======== ==================================================================================================== logLevel integer 0 Log level -meshName string required Name of the reservoir mesh associated with this well minElementLength real64 0.001 Minimum length of a well element, computed as (segment length / number of elements per segment ) [m] minSegmentLength real64 0.01 Minimum length of a well segment [m] name string required A name is required for any non-unique nodes diff --git a/src/coreComponents/schema/docs/InternalWellbore.rst b/src/coreComponents/schema/docs/InternalWellbore.rst index 21da9930522..9be2b55726c 100644 --- a/src/coreComponents/schema/docs/InternalWellbore.rst +++ b/src/coreComponents/schema/docs/InternalWellbore.rst @@ -23,6 +23,7 @@ xBias real64_array {1} Bias of element sizes in the yBias real64_array {1} Bias of element sizes in the y-direction within each mesh block (dy_left=(1+b)*L/N, dx_right=(1-b)*L/N) zBias real64_array {1} Bias of element sizes in the z-direction within each mesh block (dz_left=(1+b)*L/N, dz_right=(1-b)*L/N) zCoords real64_array required z-coordinates of each mesh block vertex +InternalWell node :ref:`XML_InternalWell` =========================== ============== ======== ============================================================================================================================================================================================================================ diff --git a/src/coreComponents/schema/docs/InternalWellbore_other.rst b/src/coreComponents/schema/docs/InternalWellbore_other.rst index 37368241205..24b84b089e7 100644 --- a/src/coreComponents/schema/docs/InternalWellbore_other.rst +++ b/src/coreComponents/schema/docs/InternalWellbore_other.rst @@ -1,13 +1,14 @@ -========== ============= ============================================================ -Name Type Description -========== ============= ============================================================ -nx integer_array Number of elements in the x-direction within each mesh block -ny integer_array Number of elements in the y-direction within each mesh block -xCoords real64_array x-coordinates of each mesh block vertex -yCoords real64_array y-coordinates of each mesh block vertex -meshLevels node :ref:`DATASTRUCTURE_meshLevels` -========== ============= ============================================================ +============ ============= ============================================================ +Name Type Description +============ ============= ============================================================ +nx integer_array Number of elements in the x-direction within each mesh block +ny integer_array Number of elements in the y-direction within each mesh block +xCoords real64_array x-coordinates of each mesh block vertex +yCoords real64_array y-coordinates of each mesh block vertex +InternalWell node :ref:`DATASTRUCTURE_InternalWell` +meshLevels node :ref:`DATASTRUCTURE_meshLevels` +============ ============= ============================================================ diff --git a/src/coreComponents/schema/docs/Mesh.rst b/src/coreComponents/schema/docs/Mesh.rst index a353a370806..7f991eed5d6 100644 --- a/src/coreComponents/schema/docs/Mesh.rst +++ b/src/coreComponents/schema/docs/Mesh.rst @@ -4,7 +4,6 @@ Name Type Default Description ================ ==== ======= =========================== InternalMesh node :ref:`XML_InternalMesh` -InternalWell node :ref:`XML_InternalWell` InternalWellbore node :ref:`XML_InternalWellbore` VTKMesh node :ref:`XML_VTKMesh` ================ ==== ======= =========================== diff --git a/src/coreComponents/schema/docs/Mesh_other.rst b/src/coreComponents/schema/docs/Mesh_other.rst index 02d974473f6..67c5596be54 100644 --- a/src/coreComponents/schema/docs/Mesh_other.rst +++ b/src/coreComponents/schema/docs/Mesh_other.rst @@ -4,7 +4,6 @@ Name Type Description ================ ==== ===================================== InternalMesh node :ref:`DATASTRUCTURE_InternalMesh` -InternalWell node :ref:`DATASTRUCTURE_InternalWell` InternalWellbore node :ref:`DATASTRUCTURE_InternalWellbore` VTKMesh node :ref:`DATASTRUCTURE_VTKMesh` ================ ==== ===================================== diff --git a/src/coreComponents/schema/docs/PackCollection.rst b/src/coreComponents/schema/docs/PackCollection.rst index 483b51015f0..dec879215a6 100644 --- a/src/coreComponents/schema/docs/PackCollection.rst +++ b/src/coreComponents/schema/docs/PackCollection.rst @@ -1,13 +1,14 @@ -=============== ============ ======== =========================================================================================== -Name Type Default Description -=============== ============ ======== =========================================================================================== -fieldName string required The name of the (packable) field associated with the specified object to retrieve data from -name string required A name is required for any non-unique nodes -objectPath string required The name of the object from which to retrieve field values. -onlyOnSetChange integer 0 Whether or not to only collect when the collected sets of indices change in any way. -setNames string_array {} The set(s) for which to retrieve data. -=============== ============ ======== =========================================================================================== +====================== ============ ======== =========================================================================================== +Name Type Default Description +====================== ============ ======== =========================================================================================== +disableCoordCollection integer 0 Whether or not to create coordinate meta-collectors if collected objects are mesh objects. +fieldName string required The name of the (packable) field associated with the specified object to retrieve data from +name string required A name is required for any non-unique nodes +objectPath string required The name of the object from which to retrieve field values. +onlyOnSetChange integer 0 Whether or not to only collect when the collected sets of indices change in any way. +setNames string_array {} The set(s) for which to retrieve data. +====================== ============ ======== =========================================================================================== diff --git a/src/coreComponents/schema/docs/BoundedPlane.rst b/src/coreComponents/schema/docs/Rectangle.rst similarity index 100% rename from src/coreComponents/schema/docs/BoundedPlane.rst rename to src/coreComponents/schema/docs/Rectangle.rst diff --git a/src/coreComponents/schema/docs/Rectangle_other.rst b/src/coreComponents/schema/docs/Rectangle_other.rst new file mode 100644 index 00000000000..adf1c1b8aec --- /dev/null +++ b/src/coreComponents/schema/docs/Rectangle_other.rst @@ -0,0 +1,9 @@ + + +==== ==== ============================ +Name Type Description +==== ==== ============================ + (no documentation available) +==== ==== ============================ + + diff --git a/src/coreComponents/schema/docs/SinglePhasePoromechanicsConformingFractures.rst b/src/coreComponents/schema/docs/SinglePhasePoromechanicsConformingFractures.rst index c0c906375c6..0f1f18acffa 100644 --- a/src/coreComponents/schema/docs/SinglePhasePoromechanicsConformingFractures.rst +++ b/src/coreComponents/schema/docs/SinglePhasePoromechanicsConformingFractures.rst @@ -6,6 +6,7 @@ Name Type Default Description LagrangianContactSolverName string required Name of the LagrangianContact solver used by the coupled solver cflFactor real64 0.5 Factor to apply to the `CFL condition `_ when calculating the maximum allowable time step. Values should be in the interval (0,1] initialDt real64 1e+99 Initial time-step value required by the solver to the event manager. +isThermal integer 0 Flag indicating whether the problem is thermal or not. Set isThermal="1" to enable the thermal coupling logLevel integer 0 Log level name string required A name is required for any non-unique nodes poromechanicsSolverName string required Name of the poromechanics solver used by the coupled solver diff --git a/src/coreComponents/schema/docs/TwoPointFluxApproximation.rst b/src/coreComponents/schema/docs/TwoPointFluxApproximation.rst index a43fd7598a9..f750965a9d0 100644 --- a/src/coreComponents/schema/docs/TwoPointFluxApproximation.rst +++ b/src/coreComponents/schema/docs/TwoPointFluxApproximation.rst @@ -1,12 +1,15 @@ -=================== ======= ======== =========================================== -Name Type Default Description -=================== ======= ======== =========================================== -areaRelTol real64 1e-08 Relative tolerance for area calculations. -meanPermCoefficient real64 1 (no description available) -name string required A name is required for any non-unique nodes -usePEDFM integer 0 (no description available) -=================== ======= ======== =========================================== +=================== ==================== ======== ======================================================== +Name Type Default Description +=================== ==================== ======== ======================================================== +areaRelTol real64 1e-08 Relative tolerance for area calculations. +meanPermCoefficient real64 1 (no description available) +name string required A name is required for any non-unique nodes +upwindingScheme geos_UpwindingScheme PPU | Type of upwinding scheme. Valid options: + | * PPU + | * C1PPU +usePEDFM integer 0 (no description available) +=================== ==================== ======== ======================================================== diff --git a/src/coreComponents/schema/docs/TwoPointFluxApproximation_other.rst b/src/coreComponents/schema/docs/TwoPointFluxApproximation_other.rst index d2fcb20403e..4eb2df4c174 100644 --- a/src/coreComponents/schema/docs/TwoPointFluxApproximation_other.rst +++ b/src/coreComponents/schema/docs/TwoPointFluxApproximation_other.rst @@ -7,7 +7,7 @@ cellStencil geos_CellElementStencilTPFA coefficientName string Name of coefficient field edfmStencil geos_EmbeddedSurfaceToCellStencil (no description available) faceElementToCellStencil geos_FaceElementToCellStencil (no description available) -fieldName string Name of primary solution field +fieldName string_array Name of primary solution field fractureStencil geos_SurfaceElementStencil (no description available) targetRegions geos_mapBase< string, LvArray_Array< string, 1, camp_int_seq< long, 0l >, int, LvArray_ChaiBuffer >, std_integral_constant< bool, true > > List of regions to build the stencil for ======================== ========================================================================================================================================== ======================================== diff --git a/src/coreComponents/schema/docs/VTKMesh.rst b/src/coreComponents/schema/docs/VTKMesh.rst index e8b568b86e4..a8960eaff72 100644 --- a/src/coreComponents/schema/docs/VTKMesh.rst +++ b/src/coreComponents/schema/docs/VTKMesh.rst @@ -19,6 +19,7 @@ surfacicFieldsInGEOSX string_array {} Names of the surfacic surfacicFieldsToImport string_array {} Surfacic fields to be imported from the external mesh file translate R1Tensor {0,0,0} Translate the coordinates of the vertices by a given vector (prior to scaling) useGlobalIds integer 0 Controls the use of global IDs in the input file for cells and points. If set to 0 (default value), the GlobalId arrays in the input mesh are used if available, and generated otherwise. If set to a negative value, the GlobalId arrays in the input mesh are not used, and generated global Ids are automatically generated. If set to a positive value, the GlobalId arrays in the input mesh are used and required, and the simulation aborts if they are not available +InternalWell node :ref:`XML_InternalWell` ====================== ======================== ========= ============================================================================================================================================================================================================================================================================================================================================================================================================================================================================ diff --git a/src/coreComponents/schema/docs/VTKMesh_other.rst b/src/coreComponents/schema/docs/VTKMesh_other.rst index 14257041f64..aef746a9df7 100644 --- a/src/coreComponents/schema/docs/VTKMesh_other.rst +++ b/src/coreComponents/schema/docs/VTKMesh_other.rst @@ -1,9 +1,10 @@ -========== ==== =============================== -Name Type Description -========== ==== =============================== -meshLevels node :ref:`DATASTRUCTURE_meshLevels` -========== ==== =============================== +============ ==== ================================= +Name Type Description +============ ==== ================================= +InternalWell node :ref:`DATASTRUCTURE_InternalWell` +meshLevels node :ref:`DATASTRUCTURE_meshLevels` +============ ==== ================================= diff --git a/src/coreComponents/schema/docs/crusher.rst b/src/coreComponents/schema/docs/crusher.rst new file mode 100644 index 00000000000..5c143d46c6a --- /dev/null +++ b/src/coreComponents/schema/docs/crusher.rst @@ -0,0 +1,9 @@ + + +==== ==== ======= ============== +Name Type Default Description +==== ==== ======= ============== +Run node unique :ref:`XML_Run` +==== ==== ======= ============== + + diff --git a/src/coreComponents/schema/docs/crusher_other.rst b/src/coreComponents/schema/docs/crusher_other.rst new file mode 100644 index 00000000000..fb5ee6f8329 --- /dev/null +++ b/src/coreComponents/schema/docs/crusher_other.rst @@ -0,0 +1,9 @@ + + +==== ==== ======================== +Name Type Description +==== ==== ======================== +Run node :ref:`DATASTRUCTURE_Run` +==== ==== ======================== + + diff --git a/src/coreComponents/schema/schema.xsd b/src/coreComponents/schema/schema.xsd index 512c471a00f..16c05fcf8f5 100644 --- a/src/coreComponents/schema/schema.xsd +++ b/src/coreComponents/schema/schema.xsd @@ -219,18 +219,26 @@ - - - - + + + + + + + + + + + + @@ -241,10 +249,6 @@ - - - - @@ -462,275 +466,275 @@ - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -754,43 +758,43 @@ - + - + - + - + - + - + - + - + - + @@ -804,6 +808,8 @@ + + @@ -904,6 +910,11 @@ + + + + + @@ -1228,28 +1239,14 @@ stress - traction is applied to the faces as specified by the inner product of i - + + + - - - - - - - - - - - - - - - - @@ -1260,6 +1257,22 @@ stress - traction is applied to the faces as specified by the inner product of i + + + + + + + + + + + + + + + + @@ -1272,6 +1285,38 @@ stress - traction is applied to the faces as specified by the inner product of i + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1284,18 +1329,35 @@ stress - traction is applied to the faces as specified by the inner product of i - - - - + + + + + + + + + + + + + + + - - + + + + + + + + @@ -1331,8 +1393,6 @@ stress - traction is applied to the faces as specified by the inner product of i - - @@ -1361,6 +1421,14 @@ stress - traction is applied to the faces as specified by the inner product of i + + + + + + + + @@ -1403,6 +1471,14 @@ stress - traction is applied to the faces as specified by the inner product of i + + + + + + + + @@ -1444,17 +1520,17 @@ stress - traction is applied to the faces as specified by the inner product of i - + - + - + @@ -1691,11 +1767,20 @@ the relative residual norm satisfies: + + + + + + + @@ -1833,7 +1918,7 @@ the relative residual norm satisfies: - + @@ -1860,7 +1945,7 @@ the relative residual norm satisfies: - + @@ -1884,16 +1969,18 @@ the relative residual norm satisfies: + + - - - - - - + + + + + + @@ -1928,16 +2015,18 @@ the relative residual norm satisfies: + + - - - - - - + + + + + + @@ -2166,16 +2255,18 @@ Equal to 1 for surface conditions, and to 0 for reservoir conditions--> + + - - - - - - + + + + + + @@ -2210,16 +2301,18 @@ Equal to 1 for surface conditions, and to 0 for reservoir conditions--> + + - - - - - - + + + + + + @@ -2264,6 +2357,8 @@ Equal to 1 for surface conditions, and to 0 for reservoir conditions--> + + @@ -2302,6 +2397,10 @@ Equal to 1 for surface conditions, and to 0 for reservoir conditions--> + + + + @@ -2625,6 +2724,8 @@ Local - Add stabilization only to interiors of macro elements.--> + + @@ -2928,6 +3029,8 @@ Local - Add stabilization only to interiors of macro elements.--> + + diff --git a/src/coreComponents/schema/schema.xsd.other b/src/coreComponents/schema/schema.xsd.other index 42938f1a29e..430f93436c5 100644 --- a/src/coreComponents/schema/schema.xsd.other +++ b/src/coreComponents/schema/schema.xsd.other @@ -326,13 +326,14 @@ - + + + - @@ -341,7 +342,10 @@ + + + @@ -352,13 +356,13 @@ - + @@ -370,6 +374,7 @@ + @@ -383,6 +388,7 @@ + @@ -431,7 +437,7 @@ - + @@ -520,6 +526,8 @@ + + @@ -528,6 +536,8 @@ + + @@ -672,6 +682,8 @@ + + @@ -680,6 +692,8 @@ + + @@ -758,6 +772,8 @@ + + @@ -1168,6 +1184,8 @@ + + @@ -1176,8 +1194,8 @@ - - + + diff --git a/src/coreComponents/schema/schemaUtilities.cpp b/src/coreComponents/schema/schemaUtilities.cpp index 1a86bd3b15d..8c581476e26 100644 --- a/src/coreComponents/schema/schemaUtilities.cpp +++ b/src/coreComponents/schema/schemaUtilities.cpp @@ -176,7 +176,8 @@ void SchemaConstruction( Group & group, // Enforce uniqueness of element names // Note: this must be done at the parent element level xmlWrapper::xmlNode uniqueNameNode = targetIncludeNode.append_child( "xsd:unique" ); - string uniqueNameNodeStr = targetName + subName + "UniqueName"; + string path = std::regex_replace( group.getPath(), std::regex( "/" ), "" ); + string uniqueNameNodeStr = path + subName + "UniqueName"; uniqueNameNode.append_attribute( "name" ) = uniqueNameNodeStr.c_str(); xmlWrapper::xmlNode uniqueNameSelector = uniqueNameNode.append_child( "xsd:selector" ); uniqueNameSelector.append_attribute( "xpath" ) = subName.c_str(); diff --git a/src/coreComponents/unitTests/CMakeLists.txt b/src/coreComponents/unitTests/CMakeLists.txt index 65534ae22ea..c36ec70d92a 100644 --- a/src/coreComponents/unitTests/CMakeLists.txt +++ b/src/coreComponents/unitTests/CMakeLists.txt @@ -2,7 +2,7 @@ add_subdirectory( xmlTests ) add_subdirectory( virtualElementTests ) add_subdirectory( linearAlgebraTests ) add_subdirectory( dataRepositoryTests ) -add_subdirectory( functionsTests ) +#add_subdirectory( functionsTests ) add_subdirectory( fieldSpecificationTests ) add_subdirectory( constitutiveTests ) add_subdirectory( meshTests ) diff --git a/src/coreComponents/unitTests/fluidFlowTests/CMakeLists.txt b/src/coreComponents/unitTests/fluidFlowTests/CMakeLists.txt index 5b775edebaf..e7bb8f2bd81 100644 --- a/src/coreComponents/unitTests/fluidFlowTests/CMakeLists.txt +++ b/src/coreComponents/unitTests/fluidFlowTests/CMakeLists.txt @@ -4,7 +4,6 @@ set( gtest_geosx_tests testSinglePhaseBaseKernels.cpp - testSinglePhaseFVMKernels.cpp testThermalCompMultiphaseFlow.cpp testThermalSinglePhaseFlow.cpp ) diff --git a/src/coreComponents/unitTests/wellsTests/testReservoirCompositionalMultiphaseMSWells.cpp b/src/coreComponents/unitTests/wellsTests/testReservoirCompositionalMultiphaseMSWells.cpp index e1c08d851be..514b3fa1282 100644 --- a/src/coreComponents/unitTests/wellsTests/testReservoirCompositionalMultiphaseMSWells.cpp +++ b/src/coreComponents/unitTests/wellsTests/testReservoirCompositionalMultiphaseMSWells.cpp @@ -86,31 +86,30 @@ char const * xmlInput = nx="{3}" ny="{1}" nz="{1}" - cellBlockNames="{cb1}"/> - - - - - - + cellBlockNames="{cb1}"> + + + + + + + diff --git a/src/coreComponents/unitTests/wellsTests/testReservoirSinglePhaseMSWells.cpp b/src/coreComponents/unitTests/wellsTests/testReservoirSinglePhaseMSWells.cpp index a29bccbd212..aa190f0e0c9 100644 --- a/src/coreComponents/unitTests/wellsTests/testReservoirSinglePhaseMSWells.cpp +++ b/src/coreComponents/unitTests/wellsTests/testReservoirSinglePhaseMSWells.cpp @@ -81,31 +81,30 @@ char const * xmlInput = nx="{3}" ny="{1}" nz="{1}" - cellBlockNames="{cb1}"/> - - - - - - + cellBlockNames="{cb1}"> + + + + + + + diff --git a/src/docs/doxygen/GeosxConfig.hpp b/src/docs/doxygen/GeosxConfig.hpp index 11750047a35..98135d0b29e 100644 --- a/src/docs/doxygen/GeosxConfig.hpp +++ b/src/docs/doxygen/GeosxConfig.hpp @@ -69,7 +69,7 @@ /// Parsed hypre version information #define HYPRE_VERSION_MAJOR 2 /// Parsed hypre version information - #define HYPRE_VERSION_MINOR 28 + #define HYPRE_VERSION_MINOR 29 /// Parsed hypre version information #define HYPRE_VERSION_PATCH 0 #endif @@ -126,16 +126,16 @@ #define GEOSX_BLOCK_SIZE 32 /// Version information for HDF5 -#define HDF5_VERSION 1.14.1 +#define HDF5_VERSION 1.12.1 /// Version information for Conduit -#define Conduit_VERSION 0.8.7 +#define Conduit_VERSION 0.8.2 /// Version information for RAJA -#define RAJA_VERSION 2022.10.5 +#define RAJA_VERSION 2023.6.1 /// Version information for umpire -#define umpire_VERSION 2022.10.0 +#define umpire_VERSION 2023.6.0 /// Version information for chai /* #undef chai_VERSION */ @@ -144,7 +144,7 @@ #define adiak_VERSION .. /// Version information for caliper -#define caliper_VERSION 2.8.0 +#define caliper_VERSION 2.10.0 /// Version information for Metis #define METIS_VERSION 5.1.0 @@ -153,7 +153,7 @@ #define PARAMETIS_VERSION 4.0.3 /// Version information for scotch -#define scotch_VERSION 6.0.9 +#define scotch_VERSION 7.0.3 /// Version information for superlu_dist #define superlu_dist_VERSION 6.3.0 @@ -162,7 +162,7 @@ #define suitesparse_VERSION 5.7.9 /// Version information for VTK -#define VTK_VERSION 9.1.0 +#define VTK_VERSION 9.2.6 /// Version information for fmt #define fmt_VERSION 10.0.0 diff --git a/src/docs/sphinx/CompleteXMLSchema.rst b/src/docs/sphinx/CompleteXMLSchema.rst index 0d975db77b7..eb52d4dbff6 100644 --- a/src/docs/sphinx/CompleteXMLSchema.rst +++ b/src/docs/sphinx/CompleteXMLSchema.rst @@ -57,13 +57,6 @@ Element: Blueprint .. include:: ../../coreComponents/schema/docs/Blueprint.rst -.. _XML_BoundedPlane: - -Element: BoundedPlane -===================== -.. include:: ../../coreComponents/schema/docs/BoundedPlane.rst - - .. _XML_Box: Element: Box @@ -260,6 +253,13 @@ Element: Coulomb .. include:: ../../coreComponents/schema/docs/Coulomb.rst +.. _XML_CustomPolarObject: + +Element: CustomPolarObject +========================== +.. include:: ../../coreComponents/schema/docs/CustomPolarObject.rst + + .. _XML_Cylinder: Element: Cylinder @@ -309,6 +309,13 @@ Element: Dirichlet .. include:: ../../coreComponents/schema/docs/Dirichlet.rst +.. _XML_Disc: + +Element: Disc +============= +.. include:: ../../coreComponents/schema/docs/Disc.rst + + .. _XML_DruckerPrager: Element: DruckerPrager @@ -848,6 +855,13 @@ Element: ReactiveFluidDriver .. include:: ../../coreComponents/schema/docs/ReactiveFluidDriver.rst +.. _XML_Rectangle: + +Element: Rectangle +================== +.. include:: ../../coreComponents/schema/docs/Rectangle.rst + + .. _XML_RelpermDriver: Element: RelpermDriver @@ -1272,13 +1286,6 @@ Datastructure: Blueprint .. include:: ../../coreComponents/schema/docs/Blueprint_other.rst -.. _DATASTRUCTURE_BoundedPlane: - -Datastructure: BoundedPlane -=========================== -.. include:: ../../coreComponents/schema/docs/BoundedPlane_other.rst - - .. _DATASTRUCTURE_Box: Datastructure: Box @@ -1482,6 +1489,13 @@ Datastructure: Coulomb .. include:: ../../coreComponents/schema/docs/Coulomb_other.rst +.. _DATASTRUCTURE_CustomPolarObject: + +Datastructure: CustomPolarObject +================================ +.. include:: ../../coreComponents/schema/docs/CustomPolarObject_other.rst + + .. _DATASTRUCTURE_Cylinder: Datastructure: Cylinder @@ -1531,6 +1545,13 @@ Datastructure: Dirichlet .. include:: ../../coreComponents/schema/docs/Dirichlet_other.rst +.. _DATASTRUCTURE_Disc: + +Datastructure: Disc +=================== +.. include:: ../../coreComponents/schema/docs/Disc_other.rst + + .. _DATASTRUCTURE_DruckerPrager: Datastructure: DruckerPrager @@ -2084,6 +2105,13 @@ Datastructure: ReactiveFluidDriver .. include:: ../../coreComponents/schema/docs/ReactiveFluidDriver_other.rst +.. _DATASTRUCTURE_Rectangle: + +Datastructure: Rectangle +======================== +.. include:: ../../coreComponents/schema/docs/Rectangle_other.rst + + .. _DATASTRUCTURE_RelpermDriver: Datastructure: RelpermDriver diff --git a/src/docs/sphinx/advancedExamples/validationStudies/carbonStorage/isothermalHystInjection/Example.rst b/src/docs/sphinx/advancedExamples/validationStudies/carbonStorage/isothermalHystInjection/Example.rst index 3ebfc7474c8..bc15312c9fe 100644 --- a/src/docs/sphinx/advancedExamples/validationStudies/carbonStorage/isothermalHystInjection/Example.rst +++ b/src/docs/sphinx/advancedExamples/validationStudies/carbonStorage/isothermalHystInjection/Example.rst @@ -25,7 +25,7 @@ This benchmark test is based on the XML file located below: .. code-block:: console - ../../../../../../../inputFiles/compositionalMultiphaseWell/benchmarks/Class09Pb3/class09_pb3_smoke.xml + ../../../../../../../inputFiles/compositionalMultiphaseWell/benchmarks/Class09Pb3/class09_pb3_smoke_3d.xml ../../../../../../../inputFiles/compositionalMultiphaseWell/benchmarks/Class09Pb3/class09_pb3_drainageOnly_iterative_base.xml ------------------------------------------------------------------------ @@ -71,7 +71,7 @@ values over the cell. The structured mesh is generated using some helpers python scripts from the formatted Point/Cells list provided. It is then imported using ``meshImport`` -.. literalinclude:: ../../../../../../../inputFiles/compositionalMultiphaseWell/benchmarks/Class09Pb3/class09_pb3_smoke.xml +.. literalinclude:: ../../../../../../../inputFiles/compositionalMultiphaseWell/benchmarks/Class09Pb3/class09_pb3_smoke_3d.xml :language: xml :start-after: :end-before: @@ -236,7 +236,7 @@ In order to output partitioning of CO2 mass, we use reservoir statistics impleme ``flowSolverName`` pointing to the dedicated solver and ``computeRegionStatistics`` set to 1 to compute statistics by regions. The ``setNames`` field is set to 3 as it is its attribute tag in the input *vtu* mesh. -.. literalinclude:: ../../../../../../../inputFiles/compositionalMultiphaseWell/benchmarks/Class09Pb3/class09_pb3_smoke.xml +.. literalinclude:: ../../../../../../../inputFiles/compositionalMultiphaseWell/benchmarks/Class09Pb3/class09_pb3_smoke_3d.xml :language: xml :start-after: :end-before: @@ -244,7 +244,7 @@ The ``setNames`` field is set to 3 as it is its attribute tag in the input *vtu* and an **Event** for this to occur recursively with a `forceDt` argument for the period over which statistics are output and `target` pointing towards the aforementioned **Task**. -.. literalinclude:: ../../../../../../../inputFiles/compositionalMultiphaseWell/benchmarks/Class09Pb3/class09_pb3_smoke.xml +.. literalinclude:: ../../../../../../../inputFiles/compositionalMultiphaseWell/benchmarks/Class09Pb3/class09_pb3_smoke_3d.xml :language: xml :start-after: :end-before: diff --git a/src/docs/sphinx/advancedExamples/validationStudies/faultMechanics/intersectFrac/intersectFracFigure.py b/src/docs/sphinx/advancedExamples/validationStudies/faultMechanics/intersectFrac/intersectFracFigure.py index 94be71fb823..63ad214a36f 100644 --- a/src/docs/sphinx/advancedExamples/validationStudies/faultMechanics/intersectFrac/intersectFracFigure.py +++ b/src/docs/sphinx/advancedExamples/validationStudies/faultMechanics/intersectFrac/intersectFracFigure.py @@ -71,7 +71,7 @@ def getFracturePressureFromXML(xmlFilePath): def getFractureGeometryFromXML(xmlFilePath): tree = ElementTree.parse(xmlFilePath) - param = tree.findall('Geometry/BoundedPlane') + param = tree.findall('Geometry/Rectangle') for elem in param: if elem.get("name") == "fracture1": diff --git a/src/docs/sphinx/advancedExamples/validationStudies/faultMechanics/mandel/Example.rst b/src/docs/sphinx/advancedExamples/validationStudies/faultMechanics/mandel/Example.rst index 1c1aa0dda60..313f9944f5a 100644 --- a/src/docs/sphinx/advancedExamples/validationStudies/faultMechanics/mandel/Example.rst +++ b/src/docs/sphinx/advancedExamples/validationStudies/faultMechanics/mandel/Example.rst @@ -21,7 +21,7 @@ This example uses no external input files and everything required is contained w .. code-block:: console - inputFiles/poromechanics/PoroElastic_Mandel_benchmark.xml + inputFiles/poromechanics/PoroElastic_Mandel_benchmark_fim.xml ------------------------------------------------------------------ @@ -93,7 +93,7 @@ Such eight-node hexahedral elements are defined as ``C3D8`` elementTypes, and th with one group of cell blocks named here ``cb1``. -.. literalinclude:: ../../../../../../../inputFiles/poromechanics/PoroElastic_Mandel_benchmark.xml +.. literalinclude:: ../../../../../../../inputFiles/poromechanics/PoroElastic_Mandel_benchmark_fim.xml :language: xml :start-after: :end-before: diff --git a/src/docs/sphinx/advancedExamples/validationStudies/faultMechanics/mandel/mandelFigure.py b/src/docs/sphinx/advancedExamples/validationStudies/faultMechanics/mandel/mandelFigure.py index 4f35bcf659c..c031d6ab19a 100644 --- a/src/docs/sphinx/advancedExamples/validationStudies/faultMechanics/mandel/mandelFigure.py +++ b/src/docs/sphinx/advancedExamples/validationStudies/faultMechanics/mandel/mandelFigure.py @@ -19,7 +19,7 @@ def __init__(self, hydromechanicalParameters, alen, blen, appliedTraction): p0 = 1. / 3. / alen * B * (1. + nuu) * F alpha_n = [] - eps = np.finfo(np.float).eps + eps = np.finfo(np.float64).eps coef = (1 - nu) / (nuu - nu) n = 1 @@ -144,7 +144,7 @@ def main(): hdf5File1Path = "pressure_history.hdf5" hdf5File2Path = "displacement_history.hdf5" xmlFile1Path = "../../../../../../../inputFiles/poromechanics/PoroElastic_Mandel_base.xml" - xmlFile2Path = "../../../../../../../inputFiles/poromechanics/PoroElastic_Mandel_benchmark.xml" + xmlFile2Path = "../../../../../../../inputFiles/poromechanics/PoroElastic_Mandel_benchmark_fim.xml" # Read HDF5 # Global Coordinate of Element Center diff --git a/src/docs/sphinx/advancedExamples/validationStudies/faultMechanics/singleFracCompression/singleFracCompressionFigure.py b/src/docs/sphinx/advancedExamples/validationStudies/faultMechanics/singleFracCompression/singleFracCompressionFigure.py index b1afc89b216..e4c5f331e8c 100644 --- a/src/docs/sphinx/advancedExamples/validationStudies/faultMechanics/singleFracCompression/singleFracCompressionFigure.py +++ b/src/docs/sphinx/advancedExamples/validationStudies/faultMechanics/singleFracCompression/singleFracCompressionFigure.py @@ -64,13 +64,13 @@ def getCompressiveStressFromXML(xmlFilePath): def getFractureGeometryFromXML(xmlFilePath): tree = ElementTree.parse(xmlFilePath) - boundedPlane = tree.find('Geometry/BoundedPlane') - dimensions = boundedPlane.get("dimensions") + rectangle = tree.find('Geometry/Rectangle') + dimensions = rectangle.get("dimensions") dimensions = [float(i) for i in dimensions[1:-1].split(",")] length = dimensions[0] / 2 - origin = boundedPlane.get("origin") + origin = rectangle.get("origin") origin = [float(i) for i in origin[1:-1].split(",")] - direction = boundedPlane.get("lengthVector") + direction = rectangle.get("lengthVector") direction = [float(i) for i in direction[1:-1].split(",")] inclination = atan(direction[1] / direction[0]) diff --git a/src/docs/sphinx/advancedExamples/validationStudies/faultMechanics/sneddon/Example.rst b/src/docs/sphinx/advancedExamples/validationStudies/faultMechanics/sneddon/Example.rst index 15117baf8fa..9d2515ba0e5 100644 --- a/src/docs/sphinx/advancedExamples/validationStudies/faultMechanics/sneddon/Example.rst +++ b/src/docs/sphinx/advancedExamples/validationStudies/faultMechanics/sneddon/Example.rst @@ -22,7 +22,7 @@ The xml input files for the case with EmbeddedFractures solver are located at: .. code-block:: console inputFiles/efemFractureMechanics/Sneddon_embeddedFrac_base.xml - inputFiles/efemFractureMechanics/Sneddon_embeddedFrac_benchmark.xml + inputFiles/efemFractureMechanics/Sneddon_embeddedFrac_verification.xml The xml input files for the case with LagrangianContact solver are located at: @@ -129,7 +129,7 @@ For the case with EmbeddedFractures solver, we add multiple events defining solv - a periodic event specifying the execution of the embedded fractures solver. - three periodic events specifying the output of simulations results. -.. literalinclude:: ../../../../../../../inputFiles/efemFractureMechanics/Sneddon_embeddedFrac_benchmark.xml +.. literalinclude:: ../../../../../../../inputFiles/efemFractureMechanics/Sneddon_embeddedFrac_verification.xml :language: xml :start-after: :end-before: @@ -146,7 +146,7 @@ We use the internal mesh generator to create a large domain along the Z axes, 121 elements along the X axis and 921 elements along the Y axis. -.. literalinclude:: ../../../../../../../inputFiles/efemFractureMechanics/Sneddon_embeddedFrac_benchmark.xml +.. literalinclude:: ../../../../../../../inputFiles/efemFractureMechanics/Sneddon_embeddedFrac_verification.xml :language: xml :start-after: :end-before: @@ -201,7 +201,7 @@ The static fracture is defined by a nodeset occupying a small region within the - The test case with EmbeddedFractures solver: -.. literalinclude:: ../../../../../../../inputFiles/efemFractureMechanics/Sneddon_embeddedFrac_base.xml +.. literalinclude:: ../../../../../../../inputFiles/efemFractureMechanics/Sneddon_embeddedFrac_verification.xml :language: xml :start-after: :end-before: @@ -234,7 +234,7 @@ In this example, a task is specified to output fracture aperture (normal opening - The test case with EmbeddedFractures solver: -.. literalinclude:: ../../../../../../../inputFiles/efemFractureMechanics/Sneddon_embeddedFrac_base.xml +.. literalinclude:: ../../../../../../../inputFiles/efemFractureMechanics/Sneddon_embeddedFrac_verification.xml :language: xml :start-after: :end-before: @@ -269,11 +269,11 @@ Running GEOS To run these three cases, use the following commands: -``path/to/geosx -i inputFiles/efemFractureMechanics/Sneddon_embeddedFrac_benchmark.xml`` +``path/to/geos -i inputFiles/efemFractureMechanics/Sneddon_embeddedFrac_verification.xml`` -``path/to/geosx -i inputFiles/lagrangianContactMechanics/Sneddon_contactMechanics_benchmark.xml`` +``path/to/geos -i inputFiles/lagrangianContactMechanics/Sneddon_contactMechanics_benchmark.xml`` -``path/to/geosx -i inputFiles/hydraulicFracturing/Sneddon_hydroFrac_benchmark.xml`` +``path/to/geos -i inputFiles/hydraulicFracturing/Sneddon_hydroFrac_benchmark.xml`` --------------------------------- Inspecting results diff --git a/src/docs/sphinx/advancedExamples/validationStudies/faultMechanics/sneddon/sneddonFigure.py b/src/docs/sphinx/advancedExamples/validationStudies/faultMechanics/sneddon/sneddonFigure.py index db652835301..c16c015da0c 100644 --- a/src/docs/sphinx/advancedExamples/validationStudies/faultMechanics/sneddon/sneddonFigure.py +++ b/src/docs/sphinx/advancedExamples/validationStudies/faultMechanics/sneddon/sneddonFigure.py @@ -51,11 +51,11 @@ def getFracturePressureFromXML(xmlFilePath): def getFractureLengthFromXML(xmlFilePath): tree = ElementTree.parse(xmlFilePath) - boundedPlane = tree.find('Geometry/BoundedPlane') - dimensions = boundedPlane.get("dimensions") + rectangle = tree.find('Geometry/Rectangle') + dimensions = rectangle.get("dimensions") dimensions = [float(i) for i in dimensions[1:-1].split(",")] length = dimensions[0] / 2 - origin = boundedPlane.get("origin") + origin = rectangle.get("origin") origin = [float(i) for i in origin[1:-1].split(",")] return length, origin[0] diff --git a/src/docs/sphinx/advancedExamples/validationStudies/wellboreProblems/Index.rst b/src/docs/sphinx/advancedExamples/validationStudies/wellboreProblems/Index.rst index bfaaba0d859..8504c89329f 100644 --- a/src/docs/sphinx/advancedExamples/validationStudies/wellboreProblems/Index.rst +++ b/src/docs/sphinx/advancedExamples/validationStudies/wellboreProblems/Index.rst @@ -14,6 +14,8 @@ Wellbore Problems deviatedElasticWellbore/Example edpWellbore/Example + + dpWellbore/Example mccWellbore/Example diff --git a/src/docs/sphinx/advancedExamples/validationStudies/wellboreProblems/dpWellbore/Example.rst b/src/docs/sphinx/advancedExamples/validationStudies/wellboreProblems/dpWellbore/Example.rst new file mode 100644 index 00000000000..df0e8f5ba23 --- /dev/null +++ b/src/docs/sphinx/advancedExamples/validationStudies/wellboreProblems/dpWellbore/Example.rst @@ -0,0 +1,88 @@ +.. _ExampleDPWellbore: + + +######################################################### +Drucker-Prager Model with Hardening for Wellbore Problems +######################################################### + + +**Context** + +This is an alternative to the example :ref:`ExampleEDPWellbore`, and the Drucker-Prager constitutive with cohesion hardening (see :ref:`DruckerPrager`) is hereby considered. Analytical solutions to this problem are not provided from literature work, however they can be derived following `(Chen and Abousleiman 2017) `__. Details of those solutions are given in Python scripts associated to this example. + +**Input file** + +This example uses no external input files and everything required is +contained within two xml files that are located at: + +.. code-block:: console + + inputFiles/solidMechanics/DruckerPragerWellbore_base.xml + +.. code-block:: console + + inputFiles/solidMechanics/DruckerPragerWellbore_benchmark.xml + +The related integrated test is + +.. code-block:: console + + inputFiles/solidMechanics/DruckerPragerWellbore_smoke.xml + + +The Drucker-Prager material properties are specified in the ``Constitutive`` section: + +.. literalinclude:: ../../../../../../../inputFiles/solidMechanics/DruckerPragerWellbore_base.xml + :language: xml + :start-after: + :end-before: + + +Here, ``rock`` is designated as the material in the computational domain. Drucker Prager model ``DruckerPrager`` is used to simulate the elastoplastic behavior of ``rock``. The material parameters, ``defaultFrictionAngle``, ``defaultDilationAngle`` and ``defaultCohesion`` denote the friction angle, the dilation angle, and the cohesion, respectively. In this example, the hardening of the cohesion is described by a linear hardening law, which is governed by the parameter ``defaultHardeningRate``. The constitutive parameters such as the density, the bulk modulus, and the shear modulus are specified in the International System of Units. + +The parameters used in the simulation are summarized in the following table. + ++------------------+-------------------------+------------------+---------------+ +| Symbol | Parameter | Unit | Value | ++==================+=========================+==================+===============+ +| :math:`K` | Bulk modulus | [MPa] | 500 | ++------------------+-------------------------+------------------+---------------+ +| :math:`G` | Shear Modulus | [MPa] | 300 | ++------------------+-------------------------+------------------+---------------+ +| :math:`c` | Cohesion | [MPa] | 0.1 | ++------------------+-------------------------+------------------+---------------+ +| :math:`\phi` | Friction Angle | [degree] | 15.27 | ++------------------+-------------------------+------------------+---------------+ +| :math:`\psi` | Dilation Angle | [degree] | 15.0 | ++------------------+-------------------------+------------------+---------------+ +| :math:`h` | Hardening Rate | [MPa] | 10.0 | ++------------------+-------------------------+------------------+---------------+ +| :math:`\sigma_h` | Horizontal Stress | [MPa] | -11.25 | ++------------------+-------------------------+------------------+---------------+ +| :math:`\sigma_v` | Vertical Stress | [MPa] | -15.0 | ++------------------+-------------------------+------------------+---------------+ +| :math:`a_0` | Initial Well Radius | [m] | 0.1 | ++------------------+-------------------------+------------------+---------------+ +| :math:`p_w` | Mud Pressure | [MPa] | -2.0 | ++------------------+-------------------------+------------------+---------------+ + +The validation of GEOS results against analytical results is shown in the figure below: + +.. plot:: docs/sphinx/advancedExamples/validationStudies/wellboreProblems/dpWellbore/dpWellbore_plot.py + +.. _edpWellboreVerificationFig: +.. figure:: dpWellboreVerification.png + :align: center + :width: 1000 + :figclass: align-center + + Validation of GEOS results. + +------------------------------------------------------------------ +To go further +------------------------------------------------------------------ + +**Feedback on this example** + +This concludes the example on Plasticity Model for Wellbore Problems. +For any feedback on this example, please submit a `GitHub issue on the project's GitHub page `_. diff --git a/src/docs/sphinx/advancedExamples/validationStudies/wellboreProblems/dpWellbore/dpWellboreVerification.png b/src/docs/sphinx/advancedExamples/validationStudies/wellboreProblems/dpWellbore/dpWellboreVerification.png new file mode 100644 index 0000000000000000000000000000000000000000..f639e4f63988f2b91957267b92e6da8efc4b33bb GIT binary patch literal 109846 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

os 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�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

Y4y@`pFq17@R;AXs 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%0DcD5z&Z*XywEj-2B{X$3DFJsuS4Mgq5@@% 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 diff --git a/src/docs/sphinx/advancedExamples/validationStudies/wellboreProblems/edpWellbore/WellMesh.png b/src/docs/sphinx/advancedExamples/validationStudies/wellboreProblems/edpWellbore/WellMesh.png index 8ec133b344f1ebfbb126ce65623bc5bb048f4bf7..9d8032b176780d73546cd33bb7df227d56e2bc5f 100644 GIT binary patch literal 361276 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¬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%0Dktvnn#8Bd6?=^BpHk<(z8Vyn= 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

    2VC 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 literal 70481 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 diff --git a/src/docs/sphinx/advancedExamples/validationStudies/wellboreProblems/edpWellbore/edpWellboreVerification.png b/src/docs/sphinx/advancedExamples/validationStudies/wellboreProblems/edpWellbore/edpWellboreVerification.png new file mode 100644 index 0000000000000000000000000000000000000000..01993620854e461ea2273f24c4ff1dc4b3e24feb GIT binary patch literal 127176 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 p ze-Ce)FYs?A4GVpAc?Nt|whG-zfF&P0zfv+SV>#jtGNzPbKc3Y;U)5 zAC}u?yJOISQhxeiwhiboMsLtp1j>+-GJA7CCp+A=Ws=w}{J{?MTz~ZhNs}#_v0Z`G zQi7c!jVtYsfp``#hn@;Jm`alkYU)bnDbuuz7?T5u7fc*#QOxLW?TDluCJ*S}s}oThi+w6?dMuM^T?VdK9rQO8vYN zSS>TV^*Y1=uzMnK+!4kT*F0p+s+NY_-4<^keZUtnsArXc;Hmf;zm8(~)%zosA zkVuH#h9gco*I&Nzk`{Hc4pznfzRxN~&dCBMtOCFwnaAQgkL^!d!PWaz6s%^A_C3YT zuHQz{I*oPP%PX*j`*QD)h~WDUNwE_wZPd3crHtq8;fQ)vj(3RIrBN_B{zKNr8tUOu z5k)7NQ)bhhuh?atX~zgUP=FIpW*I6l(7+4VfZ4NwN-vSeM1Q_5asbGiE37+jDYeNN z&NKxG`|Ue=u7R{D24>`lZ``OlYJkJ{Rw{7VCO0|ICsynOLQqf_zL~fFWQVD*pBZvr z%Zz7f>_5KVIJ(}d+J1dr-|2C*G?2h^ozLFq!gpv`AHfk6( ztSNf>Z6P|T)lcD+TEbEl8cT^K0__yblAw}ZTcSx7$3lLR>9W6r7T=YcHKiZJeSG#& zMg{t-Fa1A0YBL)M^V$$-X1K$BbVzkFbr03O&L`x)meRn)haq+4Cf-yu0M7xa39~@d zXBQFCcN(wg2THN`U{ps1%rc<87X~|gjRHL0E20#>_Z!%Ja8gsk5KlTkZ=&Icb9tjYsCTReuWnfYbz_p zFfqkZ{^AMWUfHOJ4>Xl7pj^lnQ3&C!wQ)V5Uf)Ui!E;}e#;U*5qvZMgBy1pO1@vQ+SvJPX71Z8~3<2P^XtOmWOcz1I-@*_?6@D}c zY6xu9?xg!lbalgF8SC~Z$JSruzNZE4iBFY({(jRUoI8K#-NqOg;Zf`ZTWL78+9SdZFfYB8fFOqt=amF1s$}kFV8s9o=ZK{ zU>sCxWe;daxm;XaGGv3GA3uInkGN*^d@X6PAX&?CYgCJd|9fCNG|0cvw`8>lfWj-& z(wbXd{>he-kot0)v|ujoD-Il@wZ0hLX>jd(y6C?)zqW{ZIhhyY=3Wm2KnbwH9@Tu7Q>T86qMGk!uKx zEFa~j)Aq?0Y|FfYeIB*H9J@_rLJX&@=%`-WIflXoYq@$*4fW5V{vUZ?_} z^#EMS-qwscNLNZH&NEF#Aiv&oa4H4U9(J#Z#&9NcTg{>e8)glo`>$JfMCK}d{pjo6 zRC{HdbCyS+RhA4wGd+%;KoOgsv=R_xEBU*fs*cWyAa}n@5+wk021FsQr)YtR+1kk5 z+lBS1e5ujp%Q`f+fJ zVWe0%I!}jfgTLC@=G3#6q%QA!2AFNHJKkGMwx7AJA@%FEVhmj}uM)u(Dipq%`16}g zyEN@{{S6tX+=A-j)oJtrp7P$=+vz=g^~I>k{|L;I$ljACnl(24*s7-fsV1`?KQjE( zOdl)7$A^7X#8C;0-+#_cvgy*Cu}{@K*)g6GoMgpot`3dR@SjG8!F%%c?6`Pf){2p(cljHK82n z`kZ1Rpaf`alAnWx92-;b>!OQ#F=Z)79-ixZ!sdgf3cL(jmEVUdtY&YJUuBQ0Bp`sS ze;=Z#=^EoNjq_(|?Xun}ok*EXlU)$0-8)H$KXXp~bpuk|yXRK29?72?K_NR$q4T1| zl%II#`SZN$@ygro6=F^kOBqWk&Qo*NJv>nZ-$P*0@aJTr;db;^8N*k^fH-^sj$IAJueYY! z+=9@_Q=j3Khje@fc*WpXm?yz`$ny)-`m;hV^1*r$GzIeT=} zFyQc4XdE&Z;j1hoLzV`yj?c*9PF;vy*BiIGydlt5^AA@4n9!Rjf**eFy`6Ldr0G08 zP@-TdrgHf7PyuW|fxq*U&+qv*A-}E^p`TJqy`t!K4lA@NHU%La7p;`uReW8!tI>X_ zT&C5`u4i8Atk6F|Yl%OR%jD>B85L;2CGtPm|EYaNA7EH8+JV#V|0E}P$3Y+!KcWpF z)@RGscjEt;EiLAlL^`cF_+g^JP*SP=F##!6c)5>nj=L%??+-BYk1#tZoIcr;Uc~n0 zGGv}=frewR<6*~Z{4Cnp$SI$IfWR1&7i5RZp!Qsptt~~hEy%jsh1MkO))K#@Hx1WI zKqN^YJ{@nb$ogC9n|5&r12-6t;5FH4=z)Iwqr$#P>i`uNejXkcyE-Bo4gSW&2Dw&y z1b;Mqf2^Xt(Ac#jWjPF%hv#99E@5#y2m{>yN4aqe85pFZ*I6Ij>=4hle zZYD^OQX`p{IyzO5ZJu8tYBW&Y+}sMg%})=6EX@YFQSkG=j4B4f`oJ?i`_cQDKB7@D zbYr*&0)fr3Af9o1z|v7@9vGXWCWt^~@+ES_?~8Shse?9V-%H~Fk4ot1@0Tnv`3dS( z=qr;F$b#D@ls!5;-g#Q6%1z%mXsqCG@J9$z!>_9=Ad*jPSU~|&eUpJt?nz4cTo~a_ zPt$bsjK3-$-+ay3Uax3X^xp3nAXM%}X-EW3pH2rHP@n89s^AV+a+mn$+uzU7N{XD6 zvQ@oM>i+g>Og2zo=-F&K_A>sj5a~~$TFXu9;5tA@Vs&dSUPD>h&-OgUuWv&uQ!l_e zPN`*+I){>`Ip*xu2fTDtv7?fzU zo%m+cNsm>rnIPquv~(o(Z;p-e#OFO>g6nRQ3K-vCziI`5YXFy;H0?TnEFTf0ju2uM z9Cu4Ll2?by=Bb-|po?luRt;;jmZZcK=&Q(lAzzKs%~UlM@7pS8wvgIy5 zxp}99`EU)$SSM?l&A_N(v6%`k#VymO^mIw>zkglWX!J?)C*GCKTiy<1S=|d8ozd4J>I=)SC&vjB8jVZDNGKH^AmTw@V=9lksRCoxSvYtI=*^dDE?_G6igWbV?E(JY_AXw2xr9Qri zA*%_2$8)jlk8$KkhBNbiaN{y*g8D^Xf_zPUB1au=%1VLa313b^-8MIGMl@mxiD(5Y zNyr7N)ZX`Ofr%h^(8othkKmQ|ZJ;b3CRqgOI5)G;RCNJfo5}3f`1WmKI{^}?IAcgy zSQJ41#_!zLc!#H zsyH>)`B3v%CRp=UVR)fQbp-tf4yz6aN=>qFmmnQ!6V;JSzJ`XY?FlDL%;jEDG5;`! zg5f{V3-rk_RMhZaAL^FEt?Fg@Aj&)uHuP4}gi<_8@Od((hu^sC=mc~RlPdR{ld38CCYv?Oy-`Ul z0poi*Q)Y9UJq-VN{o2?l3Z?h$0CRBSe7t=%!40ktL2TIIkE-c7UKgByS#HRm2bIR+ zCOs~-w%*icM7c+$pKd>-rT!Z7M1i)N4HVrO(hh-$vbXf~h}!w3Zo36Oxo}p#Pi!(8 zM-nV7@Mbp$03xE6WfAeMyA7J`Y$yU#@b_Rwkw9bx=F{GT!NeResspBK<#)EX`@x_Y z_)8QnYv;JF^K?2(1Yq}e=fc~tV7P9xj?1ro0=MOLHsjrP;I7=w0ffx|i@CRs%5q=Z zMZssm6Q||!~zAx03;-(O9Z4-Wg0ZTv{KR`(wzpw^8v}D(60~)EyXLNY|RLJT#Rg-$3%~_FI6L2pNCa#^rx?|%6?b;$UDuQOmFx3g;HHIba^{$ zX`k^CU|M8Ul)q?6n#Ldz7y>%L*3NFUcy+R|WOHNe&#*a_vibXOnj%tq=<@KESDC)CQFGN8CpOUe3$L8P_@Qp}M1X>+Tr0prAd~ zlf04F#HUnwgGUMTuntn1#DP~fQ;4obyk)p4NrH4X8rC&Iq5MLFM&_uO9*i6+`nR7| z5Eyru%6jwxHR1Ta=VkNr{pxI`ETxy9Z%IvBTCypV|EOPlTF8oh;`u;TaClE~XdZV! zQ3888G~NqNMo>TpeRz-_&F5T9+;j$YztV+M23mEfm92_7r2=?=e)PN=*4y9DkmBcF zGQf?=ab{(%mDp259OVxp!RG}06 z_Fev~A7sz&?A4}aJ|gPslZM5bXg$?UL?CUY5RQP`Op-*{1|N518vs?l<8@w(@XQ}L zAP?cPl)7*`X-K7jx%t!M<mJ__@zJ4NmDve%~osIAPOw_sD#EcI%(9fh5S5FVYt^S`CR&+mBc`k{$>>DV!1%N)=6c-UD{k38<+V5sUONmP5Mt=f+?rn-!QQFgpepE#UzLv<`% zvC79<85kNj9X`Jp)Xdj;Br&W|MiAYOxZvz%^lNX#uLT>02Hjzztj$qx;}b0qyU%ay-t zP(RfzSL~qBehv#402|KrR^|w#3f=Ll5W=?bV#_(Rqf-*846W*jY58t79+XBS8nzK? zL`y5vmM$|;B@-k0T?_wL<|cksMDSOd*-&ips8=b)b+Wu|IFCLet-;O1qY1cdbEMeb z<<-?B^nt;Xvqgx=s0|U4g>1PI_QL<9CNOo?8UGJKP#=>IzN-de32#65b9eyGT{;rx zttu2-oU|%Q67RfpZGCc5FYYY)FIfkNLX+{fD9sqDq`#HEl|7m4Rvl7Z`QK|S;nST` zo88#4pJ&6k*uh{bXCNcngM7SZvqX853p)p8@<7`H57w_r!$7t8nXdFyr#bx<>v5c1N%oIqGvufN(?uNUqZ8}7WJbXRme6R|3W zjYCg;W#giMfAt*lWs%x{@*$m(?a8!($K|f844U*!!%^b-wnMQG{fs_rX03*ArQhLm z`xuuY$WA78^)?-ym*xW==sm+N{zlk3Te)>nR{b6XXzi?ugVQ#>7e)pro~l{0oTnqC zw}GdGg9>xQ)YRwc#iB=>J^3{b(zY!oYwv}f(L4vUMl{V71aPdRT^&r@jz3>d^xora=*RZ-hN2y*Jz~$yoss-As z?Kw|Uuqs)BTLMOiy0^A62Fb~-?R$P6o2fn%1-r-K6rXnB(2nG zDb-@=&S)Aft$1}HGz9PXed`n{QmaLrKGiCfSwNN2dyum8k#Br_d~`=LhiaaA*>AB@ zf^p^wD?F`IXk!MO$Ou>;c`8A7@Sf2BH;lnz`}+IHtxMLDDaD4u$V910q1LyxpQb2x z{<&1!&m{5m(vJsGJf7o6nNO8=bSR71&6!#qH+`uj4L9rh&Do*Ag$dp%u6w7SmZ#m| zrg;W>jpng-uLZMj{i^wt8iE3zK3`v3&MY4SsMs>r!Y|sA$h3X?fc`HDf)U*2HbU0R zM|^4!nEDWX+f8J(GE(QLu`n{~Be?~MX8vF6!e`{4G&)yax665k9$Zy6_O_Y6iqIMQ zFZxVJKoBjA_|9vsKV7GrUr7wS&0}zD{mcpTx`V6R_xurC$gkM5o10T$ti^xBDCu;i z!v`oDEoPz-fUuf&E_Q1wfoHuBzWrU*!hVst2Zg=8L*K{wZ^ChRXrjbC;`F>;g))8W z-1AEFB3UjUgc6mAO(Pxkuz8~I?yf!+38V?*v=EJ(!iEyDz}AhAjgil&55)y zy}GamrlzThatVU0sVvuq;`ky{<1dO&fr`HgEyn+)Xxtt*p`0d$6dDy{Uf^*9zfn$WoS-xE z4zr*1eERINlMQ4o()TiE!#ylr)m@6pzpMiQwY?|jcVDijShYRvYfz=rtnR4UQ8r1) z-uMl*jV(uSZEOh9J(2YKF{{6QD_I5W5e*mo-NE>gVRLqg&+&rUJROluzVu>DE}pHpHzgJFl-;7KB7$t;h1{R4K0KkbHc6<_gtIoMN$Bltttb zVtQMr%8wfXSu7pSr~+jXAz|<<^$}97wDXzQR$a~DW7<>AAc`Hrhr=_&Zu zCb5XQ;*oV07dltuto6x9RPcmtnB%W^Lpq~D)PHQR#Hth<^CYW1=0T8IC2|0meRHfw zOQN4YXO>NpXKu_jV{U9!e}9{b>*6KDu~VT#ZRVe9yvTcHRL(eGnf!)&Si2^O+Ya%O zOcHo74QOcRzU<{9vP@6#8_Fe8zZfyVTW`=*G*H4#nCM76hlQ!0@R8iE=1hbCT-wRn zGFFGu3X6WVJ2f);a&{I*gt6JP0tG2DT5NeE&Txuaz~=(HIw!vgyMN`EP$T*@4rNbp0% zs?q#9Fjw3vl37Nr@;IoO?}WtTXA!v|`N+k^MnFoCZoERWNt9D1H$to2E9QjWL=dk0pKlH^%ppxH{-!rA zDVoTLMX0JovR$to(r4V0(ZhU5J&y+v4W*;f_)YIv+9M}ekrv5sq}wT@3^@xAa}MGD zD}4?~of$!4a`@Pc5=dM1zi2rcjgv=pb#bcIh2rusdgnlZpc_f5!|RJNdyb}Cg>EwVGeyq0SAyDc-JDe|T5rnA^LIhA$#u`k=T ze51d83D9|-q%H9}E07vNb%>(AzJ{CK#Et%nz zkkfLbJQvxK^F)e7B+=lxM&tRio;`cEZ3!xb`}pSAUqr#$C``nlSYn^57CR`RlOnQj z`L!z!I-;~qR4*yUVdEsSvDBXk)wi_z`+pImIDGRS=9oOTylq|0PlnDr_a5xe$Toqofh6!G!0VKRNUWV@ zRB2f5`l9ozYBP(lK&4M%prKSCM*sD#wN(KTVIor}uD%IVlqvLvs2+lNLmX7sIDlWAqC?GN+#?xNix^-*g8j}VZvqJWb@gwTi6YURBRn`7=IOmg!OzF%w8TZnl}w_r zN41cIcVO64Ttoy1-=uStd%5SR~;Le34(m89?B3#3Fr0sz$)#Sp}lr_#UvTGDmztI3`x zlpR#;J|I^Xg!HXek!Xmo9YK_N?LCO=kwC~D&4U7~$5`o7h@<)mEtPK(?RkwWuZ9v- z^8S_eoh6!}m=?(xW4NDzT5JWDdDUih=4RWQtjjJLHM< z#NN*?{00rt7JDaMw>v}h2Qew=u*)M(o(Kq%(btbZ#;zCvsNme0Ggq-G8}L*KZ#N2$ zQJf0(I5_$Aew^~86ICm)o|4~w@`=CXSlwARBDor$p9q{L-WFkoMU~pZS^i4PXDBbg zV@CK7ZbfvtgiQxgZhq-U+f=Qom!-#A8rB!3Uhcib(lVF(@KEPbO{AEy67kWKk5Ev| zEG)F(%Mr2we@@khq)c3y$PfQqSwC>#-}UYc3DH0=Tr#XeT&O5U6P|>V7>gR#j#?(R zRC?W~k3$ZnA$T_I$hQuTj-fPOv$i%t7mQY?3hvI_?k~xSm5$kc#6v9>a8i!4H(fZx zqk?}(OrCvqeauYG6A>@$``E{E9xzQ@vKaP(STB|+e@Vzqa)zm_0ZGa4k#O!an#Dn6 zY~%Ndm~}Tdw<`G>*K)hf^+n$@4rI+f`cEuonuVTmD>Br-3Lf6Of#kgE)L%}B7Nrrf zlS?U_al^3`+SKx$NFqfrcj7tO*kPQPgm?;udydK_rNdBZP70w4r+0RChEC^PmzitB zr9r&ZEd6?VA}QRkG1e39&Et#AAuhrcrjKB~8!O*;{Iox^NEAUUL~+?0f2=>O+k2(| zT3@G7)ZJD4DT%K9JY9r#WdE@Zkn(+)o{e-M7HHy)We|9rtXd$@yrGn#D|F^v=Vu-& z#BQ#XZ1RTP4LZ$lK+4kU&UL)r6=2y2B8Si%Gv|baObGr~A=cCnAF&f?d}xiDNgGRF z3!TfoGRu)!a}QS!2|=pgy=aoA`^(3^xgN1Yj5AUn&tqxcK;91#^Y*k(ghe7jjfH^r zaW@7IZV7YkcHVR(w&wm5d_NJn^#Cm*f%@Y1LbzHv@?aJjPtqZ+Gc`3;dzDD6M#>#= zR7=0(yASw^-*Qy$LHxa|L`Ec3=5qNK22jaJlkV@Ds*ifTzkqC|&;e%uVdjh=jV=;khl6KBe`?hBq_!6fRU@1cUoA0n}E<5Hy3~Y7Wz4L})q+HpBMJ1VV-T^)-bN@@f+_C8vTMSLW&w z(x0XAc?*Y9`Zp8j1E%}XO!k)?(^k6n0!fv&$5to1z8BvhCl_}Q@}h}Zn26zyTKk5})t(uw+<2UZUVK!fed%B|~23b0pyVXK8 zB<~GzT^jo~e}q*%wJ^X+nU$+frN!mMB-f?A8cJ%$u|rYb9(#ZC=$tBf7Er*d)G*vO z8KE>0K!gedLm4CTK5=#sBsKs;gSp{)l1JNwG`mxE(2Ql|-@m5oNE{srOTwYDGLh3m z)Lllf?i1OwYjDO#iSH*O{eh=hq)+3*P^hGI-ht8=V+hKTZE}=o^O2a}LdcPm6jF## z+0lj=cS5W9ulzg|W~eWtu|P+#klZ{yAK(yswU0Uy$CZ$jv#!B3!kO{!35OWOed z5+eaXwsu9oyoChw2td^%q1N5^fePT9iOcD>lS1mfA0zRZaKp=Dty)0iN2JZ-%igsN z+kJ$&0x%45mi{{-RsxMUdu>p|C0&G|H=(A%54SeR9(Zm9ctthyZ6$+A7s+Yu{>qBc zNXHYL&!iIdj94Tur|}yxBY-9mT4KO}DBd zI9?Z^dC0y4^mETq*0bSk>e_mr5hZUqP<7OEb1KL4ocdQs;$DGTvXzLI8^6cn*ImM` zP)j$CaB|ZZYzipcJn2_o?Qd}2OC?2`847IA=C9MVj>}eQtDILHr@V520fBM3wgWqa z22c~j%hSUlLF6Liy#9c6LKOnhp>fxRz}e)wbY ztJOTKiFX3Vzami6AqXoE`FjEo4Vza?_lrL#?5*4Ha!hrz$%>D5w*8tc>8Y^w8A8X! zCNOX3l;l(EB!M6c6p0#1j&)@+rPgVrA!F*JE?S?Yv0S< z#o4HRB~Az{+O19VdGVGxoHoj#+$7*Kcr}=V2^;LFv-PI&jRV@(txV2%dGM|HInDde zl~GleBga!@&gZ^1EDk~QOiWz4UmwSWtdAea`0%zEZS@%bA$l(8F8C2sdJ?fqUPa8^ zAX?5)30l=C&RHy*BWwE4RCBTsXL@VWQ2WG#hE?|`QlHAl=Lz8#Li0y(y#6?_y~1+O zG4^~OR_*-1USLY%KE(Uf3_t&Q9s9C zqhBd#Z`I;;1m@Mr>VmPAi7`XD{I|N+6@Aai^w=IY4XeLvxGPGX(c1W~t#12?UJ{%V z1LFsMB>vbw3k?-K!=bEeyp!VHqP_jej8;0^Z@(H8rM7NXYT2r3rWGlsX@cmDIKS)H ze^DPfoZ?kk@5yc=ob-JmQY%LrJ+>xik$Iv|iOG*7x*OeBHW* z#~E)adUJJh)8h2_Gq)X-%~fG+149#fn;OqUOveK1Wr>Jg=S|+J%_moHkv;1N9%A(u zvFhv#I@Ji1s#z^6@^l*a7SW|_+k4bp&BGL+@ig*c02d%2`?SLJ;$k2;LkkJ?6H!I` z%=ZyAFTUwTi{X2T>%4kD{_JdVHZJm=K|rvA8n;@>>L>26B5h0O4Cu~+F`ye?zar(N zPLC7?4ttB>D$29Ip?{x$OdH^GQVGe_aj$39{r%-nKdU_7$~lphc#FlZ??bvjEaWmV z!vOD{J%fh#BW?M2VG5JJaV3MYJy8|K_BpmCfq`?STgBfN87V-(Z_BOwI@x)ew}X?} zyH>64mS)|;vyU}jw)lMb=By0Z=nsDdH^ATw&};grPcEAE{X+muIB98w%x{#rQz@XS z38e{B&b5+8^sJlr1w#*)9Xb<>P0v?hp`4?tAZU|pSE=yd&2 zZ&rab^4={ra#8O+vY+@oD}p|rw}94BZsqUw*^O8GL#d9Co!*V+ncP=mWmyjo%pnhi z$8wQwtw-><_hnZ9v%fPDsQzpWp%3pp(C8D@I64|XQRj{5bzBs3Q{6?O$AiZzhVNA6 zJ&n-|_jhoJ<|^^qLz!xR%Dv-_Y`5B}z=Ltw;n~drvdVHrw=5mkD$3Wg1p;TGuqG5KSf=(;J5Dh`asOD17s%XpM5f&OZc?k$fZYfKXn9i-n&-16=}(- z=e4F62Wz|HejO-f$}(<~!PQY%9m2rUM?Q0cXzSqPBJzWnlOrZ!u>4Q?CWHDc^i=2= z#F89mK38;XZgePKFt3lS85lyELojo&#nQ;v6HD_~BpQA(Yc&_`a2~W3LwB5Q+!o29 zoDG3+ciZpig{A%m3ej7zZEU#L8J{I1X1N4wrEE?fo)vDd6s(`=Dj)EnpJA3isMFSOt zqYeE2{nl{o=ZEVJK{!tIQ8N+|?r3b_x)Tyv?0lLJq9XmN8qT2WuMa!`-bOH8IMd^S zAG9MLs9BJsZmPdO3a&>(RBGr{(l_bcK2lW^mOKnFsg2c&J|#s?TXFeeRVnl4YR@K8 z{wWf11f#nPAm9{S7zqgSMSt=x@dO$n!lE z`VWmZ3TH{ca*{CZg%k}0b9?)kW?ewXo2V$ym!Ajg0@;15VCSSBUtdL3=PwpovS*Dx zLucxQEUWaLb4)+L=aE)dUaq)g@$39zcv)DR6*o#M5l)r!=8GS$dlvAUyAr@S>i7pl z=sb!`0x@uNcW+d8UPo5ah9bbx1_p0o5??VM34zR{Yb`A=GtngBTM!PV;-$X5|E((^ zI!vPPMjR3;da|p44a_2u@`mw4@n9~{Ur7_-cK$STm?(Y=U#%7G*HLiB08!eO?|#q! zmh!nj@J#_`Ic9++5L2Q zf}5PBp=h0l`tY8(y2a+T?y$8CF=p+dEts{0o4RRrb4_;U&yI75M+Us%z5TxGJ+uXn zT(}tq3#Mvp@9W3k4>={Q$g0+v$E|zprFpc)?-zVJZXgtdzT3qze=#DH@mXNVok|?RmB8S!^?9QH0Fmg4W9FEynByV!W6$lD7&A z3!8Gyv}(gdlz~H#7?V}bFe9>jaIz<00?6NO#x^PPNlbNcAi1nSiO`4?z_yji`z@^{2O<*+!+`K4ik)l?Vh#o|L>F;m(sRik&+5f_`iKa>> zP2|4oVWuXG8N;CkIt6mht3b3H&{a?#Iil4#os7EWu3<+&z<If~wtF#q5<0Lwvu*~C5~m@)#f#%!dshzqZM>T&YI9r)N|nQsS0 z5Cae3ol}_RbDC7Tr0Y#9LWz`T{4TS>IO?=a>?T0-rA8CLb#BjM6^wzJiwXd#-R;qC_NO=g-@r9O6_pyTJ%i<_o$o^W{{ zezFORKus#qQoUwm% z0R#$Y18JeIDKXOZwTX0PdUep6V5?FLaQi(W%D&8~M(G2*se0 z`-O`OQMxBh(TO^#fa+=vq8D=;MyfzI8kJ9odgmP}`ac7HSIx^D<5VFu=Ofq@L}UOc zNJ2-4S8|8Mogw-buoLpoKmHgF#Av4{h703u?=D%R3ms#T00KoKl=z0mh-H6QKDW52 zfI^7Cpyr3`uah#N{DERa91E^yiDF9y4E~t#UcNYugyFSRbsr&|aZHrL#d!haA3ppq^E;8X|UFpXWD&= zF{j@oX?12Y|FEOjyi`kT=gy%Sy5kK2p{w210sI^3YK1`#n`_II9ls9}ZftP=1mFhu zA!CP0be8;;V;KBp0CE?2;i{;}+TX7aLHwmJ!c$0qF~nF&;y}aU#3xiHHjIVU{85Sb zD7)f45biBsDMUX4WQn#d1}z^C4Mzg!9B!pg0Lvw1WVmW@@~}A8Vw8wvGbas(X6W@mCRe0Oir6 zI`c*WGC8giQ)Ahuj3$Dz9VmP*&j@$muulDFTa?IcUuGcHC(Eqv-i<2n=PEg#;kc8+ zDn;{>Gx?*W57%QHS3Mqo-lEm>V*8-u_172wWCC!MMV< zJ}^!oi!mJ7a}xad1T0VF=JKDLj_h#*z(u4g5ME`=)rH647~#lPdP0FP4;IxTy9axZ zvHwI-lV!hXBJtm_eM7L}1W1SrGzy9zVxGasca3w&qER1lUWE}?5Z;15{u-i}%`dSb z(tEI{!4AX@zkd#2b$5~HZYGzVQkz`~{ss&!o*B(l9zGqcsmENyrUL6H#54{R9T|Bg zL(j;!%5b>(H-L~K)+~xi>AS+y{}2-kK^~oH+k8YE;TVn>1JKL}(wYm#jWNNEd5x%4(mt9?e~3KAT8vo$rNW29n& zd1JnXW8jl`M-Y{o537QeiNmL7ZIgNG4>`0z4GHX&40-8@O%<&&iHe^X%Gj{kNR&-@ z2Lx^@K?2_#H2(jh9ta>kqj?9{v?5_^756$7{P7?Sy(!Hv61l_ExJ0|l_u;pxeQ%tV$6ud z=I3Y~Knd@-wO(y(tUkdE#3YpGE3os#0Z!=<$^JOp)~LunKUolC-6L)9@o#5Fq3Pze z{To)P!hb$+uSW7P(6GmPX(ALjaaJ2sO_aH2u%1h2SZXONQYB>TmO zlJ)O-Zr%{rwR@#;XKwL!rs?l%BGccFkUSR7VJs^?v26PG)9^L>^6@5Mv@UVap#t)g zGrgPQt&2@WD$5VgOxJ@sdkR~)mOB{Lm`gJuXrhvK#VT`d0 zB_IvRkyPH57~h>k0hmO*h@TP93856Ygo?&SS)x26TKpbup-Tr zV6z}szemi!!<|ax7Bdwg3)lptlLXo$*)+|vO#BkOyHEm!BMy1uNWl-3B&y5ZyCwJ! zF{l?i*iDyF@#AnKrorKxfE!6aKzI2gWNR^Q=ZdrzFqZ`DujR$|y~f2-Phj1uh4wb{EHYh@KVUV<*YfCLUc+!tn38?nRP*1d*HR?Jemt09&C0m`$J82Z z%n4py4}e_^kR)|X+FuBco+|d0Irb5J-%3A)!MtIAfRWoo1-a|G2*kr9+N+IYQ^74` zCaU zJ~4OUnw3AhL8hn0Px^%a#p4)Z+7Tg>5cfF#0Xz;9hw%nMauS17`elpnP}0(V0vONR z^^}l25PlHsnkam*1biN1g%Vc><`9i#wW>)8K6%aWI@CvQKjX2i4_@GK1LPWCwP562t3Ev>Y4hH+{9I~ zCPUn**p7U-5g|$E9a)EqLLYq{G5+)crbrjfRI%BOCFe9@){;a6T9pBC*F@2+Umq#I znGy;GqMFU`?a(CNCrAr$N&)PfS$^Hz1QkRy`}iaPaML=%xB7Hoj*Wm2D8qL-l#m(> zW9U%u4?F4(&3jynwKD!`2Wsi)NiO!*CWPyEExAMW0s{q ztIo!8g~*h6lvQr7-t`R4wDnT8YE?_upYhL+N|bDd*=@#!4?3`$h9KW9JCFCr``y_K zi^TN>SR;mr>%m^DRHJ~FDfTI$T`@B=W5q{qxa?cWh;vZ2#7V@!EWyv(9mhCh*D(%i zN51RVuM>14LG`0Jg`P41r-bjIEyt=7G)ZcNS_?F@k!+2t@knxTTW$j>_f3$uw>M5g z|KFj!BcZ%$al?50kwlk?=9<`sR#V-fazS-(80_41=k0w0F++G@eKiH61~P5Z>cz$3 zR;oUmzc`|W9DWVTb{f-G5%8;YH6LASU$_(RPk36HXjrwpA+te1gBNrD}G?GaXhDP5eaMVJSFuK|M+lBeIayb0%rX6;hb&XAd!`CbloLtk|&u z^CRjI;>`0@PpNOBYsFuakS&Z}w_mtL8f{JC z;!?i6x;iDE^!Cx$==)LT+OPJ8lz#onam=|oVIvH$_hGuWYq#AP2e6y910S$TiGUWn z!C*ZeVJ{9xzDCl0(Q1zsBi$xt-Xyb_8+Y$3($_sb)n4&SLNSQj$Z^BroLP}8GHWrA z4(D@$W!R-u1-9cRCC~O=0$qLLR0Khn{=W(9g^l4R$Vq=>tWLp5K{TVx@K$PmcQ6%?7NU>Mo3 zvPb3pjz>o_Rx?9PQ_X%ky(!{3B;a#DuPcS$I{!Ov_sVoQ-O=775f8kRWlW2EJw*Qz z?$5^HyCMY=F`VOyk>jN!FaJ3%;uU85-R6Lx(w+H5eA?ts>Ls6uoyc}V5(5G*C!)G& zmou%4_m1VKp3k_QL=_w3VP~Yv|7koTPJa@X+!OOsRAv+J(hiP}w>2aye=lxH_LQft zeK*kE!aApSlXJ$z?PaYDh`|IX$B3Vf_Pa4Q%l}znjda|$(=Z7!o<*ZU4Br0NSq_E# zS!QN&;^1#quHYf1rzBQtr?(r;qn{?`vJ%A;78C*85czG8&gjlBAx1_PJF~$G=Ouet zaKJA`t+!3^z9BEJ?s9eew)aS7)L%K!e=O^zH@jT2X0ZC)0b8F+#>c;W91z|)*KUN- zi4EUe(RA^cCzcGMSZa=yelJ&MRT=c#37Y1Ko)XcoHoT)z2GZi|?2kuMg4LeXe*ErF zGe=pwj^fkYq}wm{jz6oMPq)q2v)LF5i}g>8YMdR+%K^;JISGaZ3fxwlo}n~*dS@_Y z-bvLf1X*Y_+qZ4g$IUg0?a_c+)&35V{Rfejtm9xMOL!CFMsWMOMLS|{`90`9fwbBB zXMWHT2e}TQ<0!5WoY&Dp-&#QTabc1zqK(H;MC9YKWa3)XUIiu^H26_m!p+O;j^)Yxz`*+2i*e3+b0cxY*x~?o z30319u|J`=ELB?`T$t*Vb2sPUi{fy9F>sESc90|E-GvKo6UD{iV?n9s`|07A?`%SK z>7O+>$Tjpl-tx%H|;k!NEZSV5DM-1w(_(p zu^f$3iUQ_VfcZgL;+ctS4?4 zi}h_67kQqT_}<9N3qkd*@Y063@4oWt-_-YvaPdy1>tTASI$P2p-{sNZkuYjK!K-k9 zmT%!Ghhb**$%}GTwG({$RK(0~SfNG;LAe_h7liic2}cPIXpA^gY~LmlZ8HJ&zMJAu zD->)lhDS{vD4TJ>4}VpVY~GjOt&7P7t`*;SvbOe$5$DbK51tdJ&Zz_>E3D<1NVMoK zajKl%zN1}K0Dskuu-B(7|GeST{3nepQJa0~vaUxxJ=fW@>vj1T?bmfNO7YJg4C~Ez z+Z468rd7~d*`!#rY?pPTf5{kmj^iKh-3ET=J(&2kUHQ_z_#H@(YnHxwKU#Xoe6F_q z;tnn9dcN@Qhm~fzzwDC@*R7B_{4%q%MD#}-!qA5-P<{@~I|xs{o*XOwVOcb_D);HB zW!F@J*xu;qXtIN%+X%ITspYyoX$jTa&z4V{BllvZkHpmq@NDs--Tsa*LFLl(@~z}# zXNs=(4*iKsUy2X=m9f)iNUD4+KC=|M+pc%a-?jzO(>+hE5n=Qj%3t@D*u^z`(RGPz$uDNmf&AVc((2aH^&MC>S0sbcha5mWi*Z1`pb z4tPV4)X}Pff~Q}1OzUmizuzoT!j?jU1M1L`4tH7g=tz4TYwf}x&%8q^DH41WK5Ik^6k}yfydLI?BY?=D=qu7z2No4t51U4zpT&}HN{U)lt%XBSw zY3#e_bLX||4n|qUPV?U-Zrs?fcAakMK7YOwO%VUyTel7px@rm=zf%(`i6#*G@EcmX(--(vK4dAHENDe3obX_Ej!1;rZv8( zH9gj-{^*hZbGpmR8TU&J}9Wk6j5$A zhpHOmu6Wa4xpUxW4p*v<`2qdqfOdlLF?Odl{e*C>4*d-%{>H~vgBHUExBCxX-W?0x>%)a=f} zP+ggh)_1b87V_kW2^R#2^>gBT4CzM9p8;|Pak7MjXP)~_hY#MHv3(TjPhS~(Ybndr zI5(4=EY<#(iN%xVq7zZfS%+OWy8rmIFmL(vsWe)mPmAUv8Oz-*2w=8O55?Cr3`p-6 zW<7I;e7Kt?NI+V+ZE|?mxALv_i!I)jRyyBpnRb8aIeDmA2+OhzUWlgdSBV|6mbVj! zqnI`6WvK4t%DOP<9ik$e&b1kp>;1Cbtf;@7T(;3|(QI^4Hpt5S1ewOm)jb}QeEFn$ znWE{>J4*+u`-CUPT3nwV+kYZ@G9I5{^r%?BuuR&Vg~pd{rM3HW&8|C4Bh)gaq^nD{ ztNze^5r~<|q@|_z#f#Os`0yJzz@6uS%YgrwOG%074f^}R$rn?6S$`B*INm9hb^PMT zbA!oYdV8vUj$q8fXBU5E8wZB<9pZE+Jg;;AX1v<{R0RgW7 z>BL`R3~qW$dJJ#x8xIdZ*2d)DdU|qmg=5O27b8j7?ZeKIQDt=&TfT*#Sy|j%4Jzz# zmkNy2iTfQ@M0aLL^}veK;`JpT(>JW7wb0Sn855ex`s*Pwi08hOedN%gW}Ygq*;a^E4cUUU(|ttoWC5#@DX&V$I4Yq@J; z!g+vJ_zhr^M}K=izj1RTUF^( z^7j4vr1V)%C3X4tpdncoMNTIs<0#rMW0o)Gxw*dWTNybyd0&c)%kz;&w(ddq9Io^F zb*H0;qQ8EwA@A|tpuPC+V&dL+h9$2lVis#_%W`frGLlV|q=W^%iSLeooOi*tgR5D7 zM#f0?DCJRCg=oP`?&bWRC%0a*dc)Eo^!RUA z5LT9`~dB%4s!`}gi(3V%Zh`o`Ve<<+YlFrat=H|AN#slNw6n;=m5Xm$pMt(YfP z8oenJNT8smmX=}~8mAi?8{dY79gvc$=6MW!e;f21yY}xFGdJhK9C3HFvU04P56-33 z&COXrLbL;-*6+a257N#ZMi7-ce@~rPCj;YxI2GqAf=O1kWF0u_W^6~DXf*K>ZFAo8rZ`%ZO0Bmm$ zGSc@@Q&U4pT?(W!6#!gNfK6{Xq%((3oNz<6_ZuEceJIdFLPLo^pPQdw<&mMi@8QFT zshOGIzIvo|}~ef#C-IVEii;L2XZ-cw?M0 z@dl%AvMrwC)zZ@X)!rVU%tcSX1x?o0(|5v2zP>K#Lv8HrBu52>gdQz#UiO7~fE)zW zRTY&}P#=)JQ4kL_X{@Q)S6NvJD%0m;Y)XnZl*jZuJcm&~ZXv_pxfA9&aV#~aw6Wpg z2;{V!4}8ip3oUXg!aHCe-3~~18(HR)9$IWl3>&zmG7}mhe|H?%hKj^)aYtn2hT6OuDRph? zxm9I{+slD1+pd=W6LsWo%I4bMR~KIi3N0|Qdsc5)29BnEQhmO;c5^K&i7nNV?Pp$w z;170oc6MTi9C2742sE{@xN-F=IRUDU z#KbzBtyXZ(hE`#8Z!!hX<-Rn*m$1Cyx# zqChW%sIbZf15G2(RF*1=cLe5Y<4M(Dz42&|{E9;N(G9zR~8lBTApxJz2PmS+_$ zPdP*(M;Bmr6;Ua6paxmKTeouoe*BXOB}tGLPQ9Bl!tE(TVR9IW4o3xL+A}^=0}N*>An@WJ`8- zX?@hQFtxIdhYM3WORdfu+oj(!AMm{R&yT^-2Q-|`R-4Nd8k$=^s5 z2B~>f)tCh}G&$rkODlpe}mYIi_H^xgxN9UYnwV4=DF&b>RTx41_ zv6^8;EiEnG>UyOTvM3V<i)=lpO(%w7-!-cWNQ=6XMuT45=k z@F6-n3Tta?vSJxOChZ?=q92awoVd9r*;Rqema%yX~T&D2EWx z6giLlseLRne zVZvxOc)+B<4&w?YN7e7!_9T!~wHKGp@UXs(y?sB>4?(B3Tp4aEHrflUGWEII+~?0f zliu!hGq5!-HkKJlA8&npo`L?-03w2KdsS1D{$p}-^<(wmS@~D2-COA`^&_N?#DA!+ z+bQmO_-^+5VDh*-u0v96h8_No6&+3Z$u!D)Y5rzU%bP{ZY5iex@L#<0k<@b@H|-oZ zIr)C;GOD**5-X;Z9@;Kn<^?$g|0~|WbrZ>BItt%~oi|QFo$!5bq_Nt;2G{%R@Nl{V zrv%Ba9@Nm+--S9IJxylK)-YB{b#=Ps{#=W&&JH9k08(}lh zyE^4gx3SS}KkDiJ#xRCf#Ey7sSkYLHC>$xy8?~@++rIq+Hekh%AFpmPegT58gG^_^ z9is1TQUUCrL8|L4JcUzJTSo`OMajN@Z>WQYa^r`PxY=*;W8q{+|sY<>0lX4k}yDM4i6me(gVf<;uO* z(;tTmBJEt5=c}rZ4E3Bf_`0LFJ`? zD#wm5S+{x7G&epmbE*RpCgmSkGlh3zo?B4BAS6URIXOw3G+)fgj$d}j?WE9U@>eT! zTAtBiWMcY&%TtE!r--m{w7fJE6VoFwj*_8TW`^(jiX9vr{^IaEb;)X9cX#(2Z|~Rc z?t4*vQfA8vkEF!K?Nm@uz&_pyHCBM~Cf?lfOIGZ_Vs|nyFjP(y;hD?WUbyLb;w8%9 zJt+Y)I+L|HcX|OEl9hJ8mrG917@(823Tf@#^}^;}XM!(wv+~KM87cIE=w9Bc5dAxT zsu?~hQ7(ba+_h&9DLQ5q#-sX-Z+bBet7YjK8D_Y2^VUInlr}bI)M#HiP5TsI%V9)E8pn zq-FvMjzw4Oh#vgkB8?Awg$KU&(jv; zzYgJ0F9RUNZQ6UqKO~2Jrk#C?c=2fBPK2ZsAKpAkdte{6%yg@|tmwSI(vdrUxAAJMSoHopGwl?}+4o?AY4S}%h9C8i|8458bKEzM3fTPB*oW|{!8GWd zAB;}s;~f#{1YX{hp42KTDvW#S`uh8;Pl@6xZz_(9W5K72srpAobTz$Q;_hAC2H{P! z`fiqZo-l*U{lZJyaqZf*gD@=43Rk&H$=_zejp|>J8f9^EVBSDa?=`A{yorRqLaElk zW?gPEqICUsbh7ER<1*8ETk|q?>kIvyJ0_{$bgrq`WY6B1OV!sm)DPIamRMjn|F7%K zRl%7?^hc#tqAxr9H{Y?clc$T+RgX*#3@ie+DFb#@551(tWG6LV@Jmo^o#r~h@7{f> zuHH)&?j|NC{y#qCCZi}1$ZPzA)8SW!9tD&sM>nRMySs}!1>IQIlRti}3N=<>UetXs zW3uIFT~R{3^k6q*;!CenhlUJAEq48E*b>0C^YD?ea_vip61u=##4!Jonrhu%7cN>3 zN|Go$*&K%$5{~t3O-%aNl8QJTp>4-wn{zafoho4O(%69d@aAJ@?c&NCive9`Hgk`L z4X0C*EMFA*->!NmOs$yn$^32$lVNXF^%ON)Ca5?y*OsQq$N+E?Uv8)_JWxsPW1&Mh zSt`WMxp{dXaA`vBustLsB;vz|=eWgsQCJVRXC+0O*xGVI1n~lQ6$v#I(ZZ9xVfjMrel%uZD=T;72K)|~G!63FeKZ&4m|7{%iZ1Hv%$Ni?iQf== z`h?ZcH}8Uksvpe||JfAvt-N|}PuaKh$???JJ8$x5e@Yk?^nR0A1`|oLX$hyPnb}o& z`D0(ce4)I2=}jU9@l=S;|HnK2qjpOd$xhE&GOsvy&^Z^c9m$T(Cc2XoV*;PT$Pxzq z*6uNnz5nuk$I{@*(yvj2n)Hd}$By;A^k~jXEQ;=13j*{{#>^ucU*;i1ROi5Gw_wR>dfx=>~pMSBzp<>!;g{?`vLDHk+))f%6r$Uv_r(5>zEj6!aWF3i8bQ#S0yl56jET z<9Xf*5j?c8uwXMgxL-y_<|pnrp7noTy}BkPwHFHYuMoIsJogg5lJZ$Vi8f&Kx8VzmWq^8nG#Y6Nrvxtsr~Ke zd7u5h-}fE=2wappoio&Qm(gDzkkFBl|FVz<#m_J3 zl1dHdx`iA}R(X_xZn>$^TJMwuNm234tSp*(DASp*bgh=6SC3Unr~3>5zgAqoehYS? z)2C01_10^s=K3~#xjXu4;SS{^VWB~cx7X}7jN6b|%4r)SGTUrxY_!PcE*A^?0%}6l z-69W9PiK^TlqYp{!T8$Iy>k4-&j~qPv(w?j?3$XIUg6iwkqE*|U;}{N$k}54`0*po z@xr$rv!0}eL!>#uj?}dR&U5%4#Qw|l#=%$ye z==B04O-ed;`Cwahx zdV5LNH6fYxwBVu?wN>v@)p%^mg_@tC1yPR4Z3hUR&Ey`Yp7SR<0`ZcuV?*t{kR({x zY*bL-MDKT}aIbezE zWMt2Shg!o0fpnKtR7!tQ7%OUf<1_g1!BhYb{=L&UL+7_l)lCFv!LzKG&k1r^qqV@J zfUna57S!;cTC!ll0_hkd8~L=fY|6uMKljNd=T8UwyQ?qoOs0)9OAm!p^&L!1ygBRJ z?{ZH*-Wb$Y5WFQO=%ykLtA2qmC=RFxioH|XoIbymaGKDG^l@Be7Ubv?76XWNy;-x?pRkc2-ExhGTaqgA7 zR9!B{J$v?0C_r18xIN?7>-$~cGaK)!x=qc!6Z>sNsLIjm1G}TXaxB}hBqE9)Q33m? ziRf$087ka`@b6U5v;-S%$f<>FivuBBI&-tnd0yj`+6r-RV;d>f(A8F<40}eilp%Eq z%9{$5XjDGyxb2}il3BaH-Y9E+-duUrEL`=qgYXhQ!>{}+=-=F-+c-lVlzcCP&zUnD?w>@LIQGoHh-AeoanFg6Bq~ZWe_{Z!J}9 z$;x3Hkw3(TLQd&3_-_4N!}VrmqN+NLd(_H1$)WRDABUp%=zA9j2kjH8PA-;ZBM5U< z-d^mU(k4<}O$i97_|~U=Yj4Fv|DfyF?_Z+{xIFeQdMJUGfVKc9W=qyFSGG-DJy{K_ zrKLmARN(L$2HCnBt~F$2F`%&R$q(3ZYcD&Feep|~nZ57*3zlrUZO#~zxMH=ebajug zgptIXa=}@-EnX`!@AHU{+mvv8dcNT9F;+#T#iqAYwf-baJblG>{#x(!M0OE&J2UQ^ zuDpu-&z>lP4% zxub=}!7c-IAhRCG<+EG+Op^=TJw3^3arWG~RZ>3@l|ZTh>?RF>oruR`c=cV!n3d4b!IqntBoKoJ!La_)d}|%Rtr{5}m1THl?U zZuax*8fK+v8|f@!-Xi3*BDq~(Y|7W7wlTS4u&p9on7-slgxW~^s~H`bEYlu#G_{a5v(`98JgE`^3I zwha{w1$FPr#gX-g@R6K#9fSu|S6iEc^d(k7K|vc^+s<20wRHCG&7zh85ZMLnY5$8X zc2YVVIl=+g7xFpHj--h1ZhrZ43+xPt4WkEaNt;dAp55~UJ=6$dEM!`@^xsbjlBB6> ztxIgFY-?MZoSbY^?8=MUzVpL}AUu5BR&xynm#!+7D_|YJ* zfu{^$+I{nxpMn!rkKQm}&Ab|Rauw&UyB%(cDN9>#K9@-u8xJl%2nvOceO5CL$CzM? zW>aq4`a_hDPnQPHJlk4P@}7R%C7OzaC{6kKi(op`^M)`~Qgaf_$Q*0FV>exKS7a z7;<}td+K;>6IUo477K}$_SH*R5JK@ZE-G*=KQ9e$+*0r-Vt61}Lqu3?ZEfi( zE48#9ZZ52kaz=b*$h~{__PhrnItgcz*iYD076Wx>#H0SGx>|Sg)3r0908Ncz1L0qU zH0op;I7}umrV(0w{i#^XJc3LeJFDlGyeU z4Z;GPj7q6yTQT1JwD872TOtedjYtv&LtG!pilN&w~94fKqS*^qGi zp|cYV97h^fB=3>6($Wh>7nCmoiv1g=WME=qQW{+C<8MGZFMug)5P+j3)!6vwpPhYu z;rK}~W6M^osBCWLLiU}WgTs|kNwJfFv{A123OxmjSnB=F4Ur^Rjhl3I7{Co|MWu)b za?RexWyV)DmIF`Rs}faI+kWbJpn}So3#mk3+|Z%L{>6mVLkQ1y`0&t*=tuA0lc$P_vj4z=N6()x zhNz(ztf3aF_iOJbohZVAxUWo&G0Je!ho8p(NA z)6yt-|7SB?J&zqrXlZ@+4CsE06S(n`83?zW!M46Ca#mzSg%WuU~)u zbrzNc;Vr=AoL(UwAoLF9M)Q5IgJbw-(~CAypSC`7;nubEJk+?x_XIji`%Ywk(B4$h2 zrp!nA&XJBNl1QVksfLjkn8`X(l)pyz=OAp52Og^egz*ovk zs|Moelc*fS&dt`du(4Icpfx<)sYB65*iO&NLS1&$3^|Yb-_ai2*Ada4y-_8;)~Eh? z4$s*OEV7OqEguZGdwI`H757Zk;GoV`7f3gJ06q?jAbjti7dMG5HaHA2D*H}=r>Cbo zqCSi-t!Kvt%6|*ahH2>n)-(1<=Uuz#@%=oT!p$@j>@6@P6L7tvrDX~JZ7u$9&%{ze zL3Rs^7fK~7eojt0Rfe)ss%07ifAlUr1lx)SySar$LxvG1ac{P>-T2;@s$JkvQ4`%2o<2Lqqj=)x{cGk9dXK$(W9B0C^K&eM(b?LaxO-Pt zWBO}Rjz#YE_mCf4&CX_NYHD(+f8aHE>htGAPyh%K2AlHg)hk2`Fncs4y6oP~5T{Sw zYyJ>x^JZb8)VUS4Ajj6PUvF+VqO&RbjiHIj`PXfL>7T*NwC>625 z`1|+Ac=UK{cWU$p0>3Tw|H%i^^c2dMw^JayHtpRj0kSI&*gDQ9N(Gi3O65pQ%_2eo zpc}7Bz1L!ch>MR;S3DJO=y6m&H+(Swq%BoH-YzLoP>Jm_2m~YL@#2%q z-o0IT9h>{dq{wb&aL^)NUo7O(C4MU~qR7u7>?=AjIuR2o3mcp4n`i!Js}>IOTC{vb zt%SE8iBI$qYzto|W3#V1a~v0?`FnT-g)Pc#Xi5eD-pa#iCO0;vyj@z*CFV&To%F?b zcH|c7>FVlRZHI53qNb)s#44C4oDqvXc8`lW3>`O-#DD|w5kS+RE{%&E931A46YBO! zDJViM6B|3b zZqPtBcijj*FMh8<9>gF{f=WnPI(vFTp+oLQ4MbK7jyhWW!G#oX(qWJ(J^b;J-^Rv9 z1XLXfNDweJHr9pMW9s`?PLQKG2fKji#cPbAMAZWoi!FhGVmv%N`xAVg+_`c^h-fxG zd~iagRRJy$pMDii5N5;U{zWT1{%M5|CT9$zhkA0gv$VogxbQ0=FrbP& zW1qMSQV`IwBI0RLl^O``nFV}mFc1$|{{tRQx8ui?>>Om()%lUfVe#rXtW=PxvqK!S z3m+LM^DQMuI&OM@+be5xlLCVNUd9n@va+(W8F%6XpCE~x0dCPKd?r=sdT>V`Lv;+B z04Hgiil*M7JVQ)zg|(x982mOBKul30kfQ>e2GwI5oi<7{RJ@M?gTr_e1YO83z$WqC zIDAxnCuEt}SA@b|vkMQU4IE6!Vq~G%+;pOY-hd#xc#92!OTkU-f>qV#&Y|U?N+I@; zQJ$4EFuR1;7#L;*6jYZR1B^c28k?A?C3_7Z{)IL9#z+2)suhn84`&7%%yjthVMKNm zy#;eSZkuAU4)RUNG9KYgaW7sRg8jCx+RvM|>iT|{_YCPPcm>&wudprQqi0~Ihf{y# zO=Lv&qgyvAhMG}XJ20nY*~wlF$8_u(cRhL+!r>bU-?&p#idyrF7w7C88sTZr(fGi} z$A=yJR%IBynVDIyR`U`{US1w{o1B}mz!Fg0tXh6a+9-LpySqDc0?=wdUcX-T^eHD~f*ylh%IkJ^n&=jrI*AOY&bbv` z)~Acp*H~)t!cK1~ORh}$KB$T?p{WpQFJL_l?x1(8tgl~)P+c>cpv&m~dUFS@hSAZ3 z=acMi6}czY&hP1SKLabOBfIM0wGI*v_yB6A)f-buBVuCou%{6@aM+yDa_NGUfhz#C zc7te~L{knK7@JqP4mH;eA9{{e9Sm$GEJ<$_Y)!=$3m(W~(n^XGsVj^S(co6k#qN0L6I62Muj9~p$ zv=?_V*R<^&t@Qy|Z@gTmMFhv_UodfZS62v1;n1)!sqsVN>8CQzpZOS(y@`vl;=S|Y;h1}gC_P0w+KuuNtaUKw7Ius*% z-8@b*>`NnHdKm-?NSfk6H%1cF@AY~`g2BYYHw;QRS3QBr==FE)XHSgtH>fnSJ!TRvUK{Ta>I zU;3E$>_x?#O_zJ-hi39q^O;GZ?KcvWSx@*xNxm5$uldnFSh?px(eAQcA;aI{DAH}I zPz=WcIo;b3tniR)BUhfcr_*B&r%5-jt$MnyR>I$Wl~OaOMl{oQNXczW+3@v?Jp!N6 z_Exl7c9@X|J}$}~rHX4U&9WC#Bm-$9ZcG#6L;l%tr)GEkJUd@srD`BN*ujSaC&ZirhZx`1%Ub;xn%t2x1 zBz*vc>9PBsg8Rc>{y0{9^X8$HLrE%43_WMYFKjr4Z)daRyGf=GAhmt=_$x)HpMRw3 zEh;{xL%_>xMYn?K7=nDiQ;dv6)>}<0kYCqoM;x`9-tUrg$9>)VM>E9Iww@_J6F9~_JXjEvn)=!993F#+ zf!t}o5zm87R~DQ;U9I-!U^jzbqF`TQ^*SY7iS9!30yKFs#!oM|m4@UVu)yA`8X&O$ zwRuO}{6*!Zi(;9>jMV&s!6b!p|JZyvf%l+up$b3wr9q))E>?i`&^xvZmvU8vfQO2D zaWOi5Gok)G-cQ>j=kMLhv)BUvoy}oyQ`OnyML(*Az zhG1862A2H$8Pe6w(BHrM=HW{xG{%QYD$+I$#OmjzcWY$DtQ4iEpbI1rr8n(j}#JzSngAOtCep$f$aB}nX_?^QQ zlBwCa=5!x)0DQ06a_T7}H)D_M_=nw#v+8hco*Jp88$&~u-zmADl&IZ@G|1&gX33v3yTFVo(g zz^V!Q-Hg7v=PCL8`Sa$iZTw#0V&`Byjrp$j8^ufP9~gLvCR7tmDAo`W)UziO;rjVS z^f!+RJMH`az3`rAuw9AjJQ{xN6W4J6^{#@Y@{D|P=0`PZh@60Y!c=82;$9CA>x~GS^PV;mvbVRtaOslK z;cdKc6CE5JK0 z0V>4((7uH|-AM2+@qjZz|Fr>Pz1qSGAvQmLDN!_mO)A9rZ9vC{v3=0aVipiN?UB$# zI03A%YnU-*9EgH=v$OL`6iyOS4Nsm_05w!R@LY^i0l~=!$TUG^XI#I&N>fu1J*`VS zjKr*9@VTHy0F3&%G9q_AcPfv>_v7;@{DXV_F@QxhMB<@M{=mJy{_;v5>p z0$;T9hFaz_b`IHjZGHQpr>BR7!bsU{ViJ45YNay7_c1_%WC7`I>A2dm91vY4WQ1WT zI~Sqs2*EiEk(dNw3el=1wp>zmPw&~6*Yj~`NUqgk2Z#yK509W20*KECWA`e!C+wa) zd2+VJd$cDKt3Guh?nDSNH6Un!Tqd+GA(b{pwP|2|ari9)b(S>U_~5~VMX4BLSDzn~ zHvzCR4Csq=$CkP&TCAzyl9|)))7#zI_of{03=vu9DkdZovqGF@!2;#B5~YOY-Q02Q ziL)D5OK$yf<^b)T^pyKG2$3scr_HPf_O=pn(%ZNgH$7=+;K?#h7KOx-RoHJh_-tZg z=FOd4jGYiRhupYv_@4jw&#x_B-F3ev!V+>eHC2Ih^{9jr1t8X43h+aG{36ibMWFxU z-+Tn2wSZEI0yLvtN~jO`$u{u2X+5vdCxt(mYaK!U2lgeb?l|H-JA0AiBWp6WRN1bs`l}7 zTb>!S?ziDCr|&O#-YRHVj0mrd%@`I1ikgY|?vK2^8;p<*R?JAPL7zE-IYI`l!ir8o zd&Z4NfGcX{Ou&SD>V3dZcJwh-U}K3&k~F-nt-Jt|nMEF69!Guss?1k~Xxj-(3%zop z06Zn9HqK5mKtWTBrl>GYRrnY?V1P{kTyk${Da*3&O8K5TM-ldCEDex*$Up2jN54+F zmB$Dmj(6V?&{o@x2& zJ5CBWDC+!1%fl@W0^vpb1pXwR*4NV`R(uo;B-kj!31Rfu@Ewr4UQtOY1nM3NTG(xS z%c0az_58>tq~*s#fn_NadNFgE(ddqQp3Imd4i{1C+YP)iMto?+NTGcgNiNycRNzU( zU?d%Ee1=1$EO`a96^y zyOK^3P-In869*nO^k$DBd0B&ulbosE-n%Jlb~8ck=?0xLu;FXy+ldwdkt&iJev@2i zI8KaoEg<+PG$`0TE6_F|%t-6JtZfKbqHL(;b`y&U=tsg3fH1AkYY@0Zy`&uB{%+={ z_S|em^wOY9{KGTfgJMno95>Y+-*Ih=wn?ndoh>m5XTD)?C4@A#r{qy&m#jobjAr67 zfG7$9)Sv_keme>`W`s6c7=RB8&y1)}CBXcfAm<+K1pFl)xY%REbcjU*T7O-CZhrj>KFIxc{8=KSa-K&bFd{*bA zYbh%$bMx~@LmB$$-8&(iBByTLAhMi<*sZ@c;^`MF2y6U{@s~Z9n+xjm#PnogV;f0{ zghFQx#F`Aa(99_SHNOb;Jb_(|?oH1SPE>T@MbLxe!KPZyLgu10CCiWBWfcnc0r5lE zcoIH=3hky_64FKVA3^4+`cJKcV8G$yNl;j71Or8;0Bk%JzxWrqwRJm}0~vz1Bgy-; z+WMUJx!=1>!#Qg>V**DR-Tq)QN%`P;I6^;DS9GL+*Xt1Pk5ASb?lb?lP+T6!O5Lqn zcY_8q&n-Fnu9Q_qMg|AK)|@{GWdwJIto{8Z`0mNemoJ;=-nnj^90I_fSXZuGx#BWA z2fM%~XXmSL+m6HMgX;t`W^rJrsHv;`kXH2J7=2t4FsOi8mBD zGVlG9UVbOnS9Nvx)F_zb5$K1S{kdbNk=1QS({lN71&YnG9Tn+?dc|Xa&kjM1w*>nq zh4Sjno81tLlk*;jgG|eL!oe72l}YzPfXKyo)%Y^N@lHT30I^?IRaS~UhSH1wuT;pHVTC}lkCA%0s)(MJHZn8diKAuJ4stuJNV*06Tn<_)he>tP%8drUaWsa zZ}PG#6XC~=TSJ+Jd1gFMn1n@n=|5W`P43X zF#GCChqXK3`D#NF7pC5Nmrx%T7PlG|=5PYk@6`N0&?>XD%!d~jo$yxv4(h+RrQSSH zz7vqcW#U?H?jm&XIWdO)?Uov^k2q2C965KE)z|%z?&JEnSn$)-HtSZjd$M^5+M@2;<}9n-LABB-1Q&vAZavE3q4o2A0H$u}|En zB$*(EsDAvos-|WEfsXz`dTyIE|J;{!mnJC$2Qvh#ABT}9C^ca5_4lhpUDGbA9F4{C z(FMA}&BKF~Z$#D!0*!uN)_G5T7>5i40YF~Z7^nrXjtNi5Itr``$5z+0yj@Ya;9&`G%8}|5|BGg}9eW`)9S9QY0c-Yy` z6Dcs-+s0*1Lp##E4z||nN#mkcE)U|H(@V`(%KGDS)S1H@;aqzybV7rxQ>U_XS0}x8 zX=T}lgrxU86rb;3VnBM*alMc7!;CWmQ5oCzuit|e9;`1Wy;cg-Qq?I%-?sAg-KDlT zpdN3eoB7QF)w!-j-ve5MPyj@R3bp|!`lyGekp%kh9MX!H{&p^Y@k>5 z>yVw(r|lz^j20Fm>-i?%3yQVoOG6cq?)tYv9fc^d)-Ho@1f&2w`yaK@x*hRe0~cyo zw}jqqRxp-zyCR@8QF?5&QTXwT7d-G~n!9Yyd0+gOTwNGCg$9AaSB0zOK^fkJhL@dO zo0ypHNAHBCNqH9x5o#`d{L$9__4R{m0cbk=(p103kGm9G$ETw;T>s%Sdk)bI-1O`- zAqNMs8^Lx)4C2t}Qo7!RMhnxju3n{MW@gU$6V^~4A0J{3y>zYQ5l?8jifu|tkYfPd z#FySZzH-DaiXJ!iOMRgwJ9uk`NV@fa0OiBqg7)70_B$md*3!QyCz-HwLyd(7UYp8R z^!4^JJbb9_r_p?O4Hpl?6N5FD*Lq^U)$`8*7vjRMhQ2W!q7{@mm5^ldp8qehb``OC zS-T7o>HVXI$t?cpdQDCHk8=7{)-yr|UsVp!=Q*$&UtYNShm(?Mdc8EP5QUHz5Nio^ zMM;%KqoY3bU9E-xBjsaRUMi48B>do1I){d0AWpApZ(mVz^nER^lBg&B5EuBQv9TJn zBS|9%6UtR^6hy)e!b!%{@~F$2#}cgz#&L2`83B?Ds6)yYH8=DAvKrtYXmyhtTjWRU z2kEhnh*L>c*0^(RSoYMgX4(cF5myl?W*1O4Ly2@>Cuc+dxKU9_Tfu_P289KWnz!Gd z?ziyTYO2dShGGSRHe2YqV+`AU$429BPQPym+o~X(lTkJ*fSl$}uL`fFp+p0DZo;x? z5f3T_@yD2!Cypa@GiZeEHWDl$0NbTjul4gD`xFOI+IK znH&}LUv%2Zf4+QaMJodw3^j-&YIbAZH<-XCwE(-{CRm5C8!v_z1bre-Mx+JDa`9q_+vf`d?Q&-z6Y?I9%x*Fp zhdw)lbdw^B8Q&e%XD2O&Gu-JwmhaC|EM0_D_Z?uJ{&9VBkW-7Y{^&(>E~Kgh=yw78LPndzZIrlE>7_LrKvkYI3k9 zI`?Q%8QBR5z*l^sA~{=L`X0^TW%z9bt%w^1sd-Yg_MeMzEqu;7onXDii_`Ov`Q@=u zam8JXXD==CgKiqh8Nw$^-G!`+0rPXWvZlC6(i)W3W`2jU&2-}et9JnwF>rL!O*>|MA24~CJ5l#O&$>^irSw7pqLV+qg=eG^N zPdXu$^Ry7VhBv+H26{4nMNCaN!jN3ZS76iG>lZVAsAlai%GyS5ZSq<=fB*4nJ!KQv z9pO_p!)K`e55FPl980Kfjdj`SB;Q85^6Th~FqgMb zKcvnvUR1F@uA8w1a>Fe+t)C(OmptShoj|2q{S_SP5h(-F>&z5gYGD2sya9cK|5hF= zKki)zk(p>*cH??r#yJzS)89-P85z@%W!L=dSumX6T0cLpg+az}fQbLmi4yb``@$k9 zZivkp;}jO|mRo)jPHFVd;i$DlR8*{sm-Ytk*>;{}I;2{En7bE0uJ*%(>TN8gLp>K| ztyt#E{As{Y_~6@R7c}`Vs4rDizVYK#w$)-ry1ALJf#Qx>vSz$2^iUH*^{GAhk|@cu zvIg4aYXL@QFaaAX>~7~U9@@am7bDjq7+X&UGq`zr>OiwL+?(WB`Ro}tdnh9iwNYkK6|- zT}MIMXWuS>EE%aFE4o#|@*Bwj-7g|SazJBF&9U_~gb zpMfV}6Hw*C+zlQ;M-j06BcPfdK*v@{pO9*Rt(=@g!o=N6=BkqSX|rR;E`+iV!?FwX z#^q&9WaK6Y>_}F`g^L$=A(e)JBR!KSZ$ChXiov+}>q=->igvui^7<=h=H4tp^XCo! z{{_v**mC{YrEN)V4#$qILj@F z&ybs=s;=IR-XkJ1GB`4l74Mh;86?fKp9F!6m=8Zt0dPZjcxdRHAXmHARG~l47op44F1wBsQHM^==>g=2GWQE|wg|!=TF|p7S;=Z5n-whQ8iU}RG z^|0j>9{=ia8=mFX+uXQl0n(r0|8ymA8RDk_`P+n+a( zup$(mHPB&DDCoqNt=-FpE*PTsPYa7(4jp1c3eUaAU*DH=ZsMwMIq))XIe6zq+NHZR zQL1@-#sEk7!V_ra7$gA)T^AN8u$Y^-Zk>Cb3V#MN8i0}$g)$=((=MF#a3uyqE~)jS zhX=Ky88$&KP8M6Q z0+X5MW6*k*QnOCtEJ3GEU)p-#i-Av${uHOReg1Y)(Rc{`>@O%EaNd}$cxw(65jrGm78$oWHtLG0@)H_-kCN2=-Oz#t@tW`A`w00ZDdKvi_3+dNNT=~T*heo8qUt1Zr{a%T4%V+sMlu2cePTbHPuoGny{9H5GxR?n?ixze0XAF3+O_!82~jPj(ikh35IbNSDz)8pZ9k(g z&_J#^+`bMr=VgE-Tn5O=3D5r)qy}IUhB!DD5*Yp1q@=C4ZjXBGph=hP)$5KASJTUR z1pA}DKIL>w@Yix?Or6zgn*OXy4c~?%rA@2s)WK8-66QtG#(xZ6x8z&oX8u<}CKH7y zxPNN~b$~A*cPha)iwzbOFr;r0y+A7kW+sIwxmLQ}E1D9~X6V8KFDWE@iQg4=&^hMFdi?YyryzhNEDM8(( zyl)$T;yn`WRP4gpfNO#FG#I9g)p=`s~GvD$IB15)fbm zf)ArpCx~_uONBv2_0?jznlW>kPsjgvzR=p6@V^vQhBNhGPRIwgozoS*Kno4acwM+w_Oq|5j@v z_-GwGw9!Dc9zA)&f)2q+{e+r?x5ey&1=WG@bMYzZ zgue&FmCS3~Yvy9|jJ7k7wiWSTTTW9Dd^R=Cpf$HMprgy(p+EANcdxE)aYESD1?L|q zgoing=ea86fGtPPhzKS~vtbdDvlqh`%sJPc!3l+200MB;&D9juQ$+q!eeV08Ri8;y z_86bduxQbuoEtsD-!Sq#2)KVbm{TaA_3*PJV9+5GXFz!ximD1=#$N_{;w(Vo2hkQl zbpW-a-U4fLj=j<)9t#ZE*SvQx4}?vj@PA}hl7kD z+p)qM^QmuxJnkvW@FdCBa4vrF3v1&4elQ>ctimVy|#iEhAcB; z(v0;8?Fa)~KQ=bjj6JiiCrXz1Dsak!2jKu;1G8BdLVJ{#=H<&P257i-(r>3$zlqMN zWeEua1}7#dX+7Yas5@Y#RNjY;*B#T9NnFQ~fTviPGP3HpXbe(B;Lhd0DJ~cWTh?O+ z1`7SvyvOS_E`I;|{xSTnvdJ3qXOQnjIzChl*oSQgZ0dGi0A%GNbZm!G$zJqbM9q(6 z6@#vP!<~ti4yiC)5UZ#=2E)JsMC2z6J)^8XmKs^KTuC1^Ro)6GdRIyb25e3jljA01 zF^D$ObDvE}2u0a~Hgh{cz2xQPFKqAv;#`^Fb7!%cPBrUs^Jw_@h&Gu7O@RR-pB?$< zM`p2YKP8DS&|<8@1QjNz7uc37F{&JSo9&|*LV5c@GT?&Mfy*1{;9=twrhoPfW{**e zW-e~C<;s`euLZRSz86^-@}6XkpjfyIq47O;lzkiRiBMAfce+VIvy3BtAPzd(E8ib@L(B?*a zjh6BAui_6ft7FjgrDtVzadaD({?Jv)`iFV$zv)J||FnoWJ5UCfjg*vBd~5o69Wd9M z(5OI(VjMqbW@T&N-P=o$S-+X_wgHaJO|(zr)?;fWmT%m%uL_j5$s(%xjTL=~)7e#} zL%P&Ot$he1IE+Ms90tNg;Q^2{YS2U!f?b=Q{ssVH_o@3;2i#7~v=+RK z`8JP=xh@mFXs9fYz*AI5=tsk--r$~xCr5T?wv)#ccnEPc-}6G~>{)d4uLGel`W$tz z4H=wBc`+OZXh%B-X%p4fzVnwd`r~aXk3-;iu2*+y{9PK6#zweOU^zdmn`w1s5dzYT z&*Q*x#+-pkOjmJ2C0z0j)ia3`ea*a7j(&QYvi0%88e<~(bakcO*V*5EV&))caqKtM zLSpvUb*)ZNqIQ#ln}kK?rd-kT-?3MjdH@+1M6?8R9SdluFs~#CO0W*h-#!>1!My(Bz zY-5u1dd9HNE(vh^QXEj`{ATtqvdvZjvKEEY7>&F+)ZRo#n}WU+(b|F*^=Ef9UQ*!% zleQIw7!mDZLJ1RkD@1S6i#QaI!Dfe2o`EZnTawTznwY9apgBt2~ znSGnd%LR7-y3!R&$iUtK!c!Co2P_SneS9|HY#xTlifHgD1eqZ2bTYs?4O=cGL&W7N z)(ieXmrA8>3pXV%=YilB^8eYB^|>agD_xg4JoIA)pRi}&zHfJS)7C-@aS~n+3Z?0a z<|2SW%3E~@2uG2D84cidK${J4glE4q8(UjDqSI0FJu+fII`&%xZRun03=X1Rrbc6I zZYR3y_yU4aldtTFE**=%{tz*Ho!I}@?u%`Iv)#*=77N^mV5Q12JS1Uc{)>t!9Abp` zh3S9$@zslqBw{#)wN8@((DUdolvPb8m?nd5`T|DnE>wwS*y|KW=C(D=t(Xpf(#|1%dF zy=OND2R;JUA=Y>0;>8_%GygC%0#jqSmFgoQjJh z&TJC#N#v*4wp^x4Pftp^TUFg(T6x&>U0D6JQJ)hVb!a@5(<4RfDJ^Mgd#&y9hP5$Gzf zVz(hN(g6VvdnR!sp;ifosuO1f3%*kW5*1e8GbDsVUEQuoH0y-2ML$)RWV^zk6$LXJ zICf45CrE-; zk{##z*$s&AjR*>&KxM0gy$d3`SC~9Q6-31f0S6ljtxafvN!)PWj^c&T4Z1Wv0B8oc zS90Y+7$Gu_6nt~6a*MgT;|>~ZnI(fjcmIlm27m!9sum~MFhn$`g*5MPKD?Ztzv|`7 zJ8yl!M=eu1B0w2Qk43Tr`pm=}XfHz%rgaS~nIvJ8Va7RqK*bQmorb^r^!6S~3{fKL zKz-Km*#g%KO{*TzkBmcLulIM{S0<|wo8fPfRD5AR`dTXgU-%(H*9d-y{+L+QNd&UN zWjMl~LZOY=)z!sy4LOjpL*5L!xB_9>NDZ@7-LPR7W+edurM~t~63g8Nj3ulsHGBPY z^Wzjh`5K^p_BF<~hh5gNwY9l92xxWXeMqqrPZ#aN1i{nTcL*VZ zLxp(p$=|@#NP*Ks7@tcB_B?2hc<=xc83^7I@h*@$8SF0u1^$=w2g(5-qI|1H2MBw~ z%p&q$5KWQH$%$Q9&bTu}4OYP7q41DNV3> zc15$VSjU2yM+u)V!FV=)@U=`5I3Mf-3a^j8xg&?1>x=>F*!u2WHMxfjG4h@po0Q8- zq)6vb1yJxkMj(e6Pb-QH;;$v$1qJY&px8LjaB-Puy+3eF5y>(*FJ_bo$Tr7wxVdi( zS%4(L8LPiIBj&z`5VrLAJn1RiNur6u-n1K#5=mnul>wlaI$=D#uS1AN7V!R;q4FX& zTsQ6>vq(UBIUHu#7Q4&7zpKKk-j2-y``J|zBi)k2Gdne?Kve9bJ}^&0f@xlK^zh*r zeCR}8?ONFZqd0KAQ0x*JjR-^}(ZHVlX4 z_he{o#=Dhl-}%ynr`SH@>b1Z>Rz}9oUKY zzb+C77!F5knxeA(KPN-GGo0P=O4QV)E4~I-4TZ5`t6YI8=3_vmBl;g!Q0$ILZyqZ3 z-8fBi2u!~S=6(Jc<+XKTh!AeXWT`E0%L9es`Nl;C;cfR!0zW1DiysoB08tWZf{+%O z-3Eq+=Bj26qh}bfGgqNNp`*Zuf!!rkgyqD{lrq9W-LTAaBHy(i6B2^n&NAK3C$lPEcYf}bKbZ4mpB;g z3|GzO_$+edaExTy4EH-WU z?-?j36I92L2Qvaf$sHx((cAQ}58vqwRp&O7n}9xuoRbtcv`~pRxYDMs?Kv{^-0(V| z)RoT(dOA8&h4LUkEu(TXqVkIFA?JedQ$$7#bOGZdq#ksi*cuF`zS@9TORje^Nta>JD^+Q2xF2;K$ANi6F z?%$tq@69Hw9fEo?4GH(`x}Y+fmq->kazgU64nL1)5{PO#Rnzus6&MAQc>$J&qzVGw zg*IQR#FW&YCV)HApk~7<0lbIgP7t;#BqW4Dz6NFgruO>n^M{u|zj2n(kubbMPJl=m zn}JsVrSJ@0Z3*>7y+{JWNJ9fZvc8c~6=Vd78}|L1?)LnKmpJEF$hyMJSE`r;7yR=jTpuY{x*vKfi(eqJ4|5|V(2m$|u(RUQ05@*6k3iHF&BgWNGterNRWhR4UryeLF8b?SAm}Az?(C zbFCF{am*xj2X~4xQhx(w!EU6Hk%V5HSEtYqI>YQAe@sa@U~(l<0HCrUQ29I}N%S)I zsbr9dm>46LElCMM)Wpc#?E5(xPxfoquDMT*9l&Paje)W?XbltUmoAJrsxI%~@6}lu zwy=hD-o5-_z?e9NWWT*4=n<-Pf*yIaW#$#_!bEA1wMlQ1()~JsyqO<=n&>BQRtPtY z;lqisFT*Vbpmvdqd(+(9tgWvf(Gqex9x=}j^D)m- ze$DoyEPZeO>Vk-NKOvIikIjk-}+>5OM6-1YU?%L3G#(^S zV(w`;gsy&KKAy~!BYW7BCNT2d(Le&>ROt|j9SwwFDJw0 z_*_J3)_}UANF9z7uNpIuU`@u=x4QqbuW#xezkYvy4}@H<0O}HYgsfI!$mvNmw={JD z@)Dx1+5{h=HY5gY7v<)bsy!V!53^**&v&XdFfKv0n)^kOfHmYxpLN9mM-cu;LY3tM z-PV&E?MSCKT@D<5cwcl&@H|peoC4h#h2)&N#)f7GNYB2Op0!K_+Z$V1CRIqSDQB^)r%3PAcKC6G@uh+g$GL9m$Ii9t&^rRu7 zF(O+CF|iTZ0VK}$Fk--kluUdn?-51bgctKBc2gYc9{{^zA2g^(SEh)@9aFZmjAwvVbOPgmGATAg z77qeyFcSKaIPmS?LZ)c}i*=PD^&{CUlpwUvWTKNAx!kbs@TgyS;{biEaQ1x&W?Ko` z(g}Sa3~7Z_B+1QS^ohtfWw3?1_(Ntr$t45BHa+bJ6>*aHHU|eG;KpJtbD{r&=wKvqRXB=X*X|j!N7c0`slOt z1<0(H{iX~B7YFuH2>H?)c`Nk8aPkHa&UOEYc?O|f~$N-l0?8_2~`j#(XF zRxI?`8>^h!2Edbsirbt&PG788fn{@Y(da;fw8EV|b&CPgnikC=Q3u)~i~)%$*UzcN z^dZ4tvQqYi1@~`%+5kama)#&Fqp5KvM>18 ze=N{vDTs-N_5@tc$Dx8a1KC^k{gD2-SgG)?h55=BqaqgpT~Kl)79e~34y9fIzm0!F z*J@#L?)`D)(``HRwCuz0d&n0{nRwNjpYw0ib%YSDwLQT9k-LzxZgKzj0G&q%<*icl zb?nuW+LuT^_NG;VOIT5UExWcKJ})d(;w_dwgQykkvn~&7TU(2bXJAE21Hq;A&1Z4z zdcefnkF&6I{Oi^KdaK*oO0QxqtPq{$pm~>m+%?|6p(W8XXPdhR9xmTVMy=&REyM)B zY-4~1Nd-h@N?;-~3J9ozGo*;{))6xSd|1e#1AdJBAR^z$|p*A27@+6zW!r2=F-cyYW*v5hkG` zT?7CB^!vFPVd8fo*U; z_n&m7;m@4kKY#s)cYZS=&m#GXTR6X8;6>-Z^cUc>hE6$>2kLziY(t281lZu^yedGy zkq9?3TB_sfS^8h^P!F4w<;um3o3M+$v(RShj9Imr1JDjW0KG zh1uu74G(LhX>1i_k^H%mZ zkR-C5#2yQ24xT8^rcj2Z40Jb9eaJ5|n7~qs0XzCq_Yr{l`j87BB!yyYn~0ek#kJspbO$V3M;Kb!e(e$IWHawZ{Jb|o zufQ%4*=6!$l|!$BfIgD38yGP{s!oJC5iKu;q&{U>Z2%AFh&>}aj&!K{nzRi8X&3SI zkaxcXjKvYWd!HfHA!N1*VW%zMl?Mibe4tR?1^#)jy@XlQ6 zTxvNp0{;FWl{Xo6&~aH_2WkYM8EO_j^ImLY6`+9TWtfCk12{|4j?jxiVM|-J6RV20 zAYu7NN)Vh^1g^k7dHYDm3X<@RPx;`6w@lH$mg?%*Z`l%9ZYx5c-`lWY(^=`C0N6*c z;@ghd;7(Da6IBA>@jbRJTvzEAzr$POE@Ua0m2lPP#;@%sVM<1vqTxSvyU}qiIie`B z!UGE*5=w|_Ca`h)HmvQ|gZ~g42a=9YIafDY8(s_ikWP!-T*9sGC$n2 z5ey_iN8-4L3|8^zI}K#k3IS9)2}LZ4ZUY64oH_j%Gd$!t!>6~a4Ey7yFaT^axMiCd zOGw%mkqDZomX;8{T@rGl9G0lFgYf=p{5Qw#v5T2#d?#s+ct(Qp1~ew{Is zb%s~}>nn%i`R{+6CvWh+=f9{ye{)TEcmL1NIezB%R|frjC-ZBAK*LSFtr-rBP)M?~p?|`wQ8C;uZp=*AZLX`M)T8 z6L2iowr$v~K?+4Flx{*qhB8G(UVQYu5rTu5dTH%ThWOp(g0 z3>ot6r?sBMU_^K+V*KN)@sR00w&eyr%l>siP#zQ|{uemv4|#7k0RE)V%|2I{!rFode9 zt<^tz2e8oLqo%e0dM7wMZy0|(&inE8ucwc%n}yCemw=d0^k+o)kDZN+;cW%(QIp?S zWjk^inpTp>q0fO)ESz0VpcHi(!0J7+b%>bZ!-$UKWxmZs)z=!yPUCLEXu4Y zJT7h-@zfk>%a^2s$uPPRZqONtwea2e__N>NZAzUaP&C?k;y;W|7B-iXQ;M~PEPe68 z(q2)=at}8SY}Nvt@PCZWm@kiaow4~@Q^+5sfX$J!R%DkF;0$o4h?N%Tc1fAo{G&>6 z2x3h-K7R}M{NvOW>FcHapaS}1u1XLZLe~S6iqRGM#~=z-wY`|4bK**^-679>i&ez)IL#gpzCwoOfzG zA{)^fz#pd!JzHY!9f0iMz@32p?Dh^PUSbD}w)YDtrDW1FJ@$1B2tUn%JR#g-^xMop zV9->O40Z@Z_wH?@TZIZpWanG7Kg;O+kwK}g^Y9c;oDnUs7zC8G7bU(w(7NS7 zlZcHDaS}&X(r8+~WXURYrJM0Vhn@?qJl=poU`x10j<+IURd>mV?&myQI#-! zK@ldAuX^;;?#2Rxyv!%Z?*GGGUkOYhqUECdh^tNMmM1PwTHXTm*r9s0*CSc_lUnQu zvyBjzo`F(9)LB9{L`94{#*A=AZ6R(*0H(Pxb|u_zP#B4;F%tI6z4QOp6C3*1@GDpiS2@Zk(>H&n>oQvEx&l1x`?nfK7q5gV?VD;qVU)?o#zqY+REdkff6My<&| zG`^$d|2By@_<&WK?1l$`1541H-g50Kue-|boFLzaKgmRzy-*UhX@FNmF{XCXuxa2C z;E$=>(*OCIHeLd2O7BM%@Yi}uiVOhOTI}UofMj*|^$8U;qp@;B$4REpfP@op#}MWB z62uH;-?9n7Rwxp1Iar;^|KW-(F+z4V^z-@q>pm7`%5!pZQf!F9S7Ret?A3ZEbC82#NHEB&kK6ZfY?L`cz8lKm@ty?iN!^vLMN;Wv|3fjByf~X7+;H3+sk0SM7kl= zp1YyKi8U0uPKrq~mnK%U*da+)Akw{JJc}Pd{3>9Iv=2##q(}0RF+xIfep~*mAkGF_^?SC&oJ^T-eKgNM z&4&Rtd99Do^AK+cbm&j}%*0xa3^Vnz@lA=(PfLysvBH2;$g0(=nRXZab6#F9o)af#$0-^xFV+G1XZNW<;^6h^=^m@lCGNc!@>0cL`v$CH!)-+DZ3LJdUE zMC1s#cLc13>Q1N{Qy1)b+17FoqA5^r@ghFHTek||IZS-9E#nA#X@?N}u=OO$M-3gF zDij1{?JpStf>u@rp$XD^p!5WS{52f)hsfggLD7pJAJqVPh=x0H^~HLShd<|^5h$4* zR{A~Mu^zFi8p}^fV#fd}I`uw0uK|HOH<9+^kOX<#_^XL&@z0{NTE#?-d znrat2gJ$_PjyOh2{wP+-FBDsRmw5F=1};Xdw9{l(N(UE)WcK@brx9|j>}9O2 zBbdQ>&C(Y6C+m4J}EDFkheG)~E8~#_Ng{ z>!-?dg+E-QO_az;8jEN!*~~;V>TB1nGt^7K7caz`zw+|3xA9-*;bhuZN1qWSq(H`o zu*krW+Zbk%m>mpNUSG2nY64YAKDCdV|APlTk$?N>CoET=OrZ4|19Lew2PdCwXGBxt zVv+J6Oci`xlF>h|Dt2eoj$_)OB7V91 zp))?P`h4Em4GX#f8iKEMZ?CdX5qXlE_%J;^dX=bzS5xZ~!3RyvPoLVakyxsk+k8Z8 zt5xcg+`W#uk@@M5%{Ox>ZMq^AI~5wXDQ8Kzc63+&%Wt)J`<46edPpCA+upA*B?DVi z@YbwiruV5+SpN)2e$E}3bh)!bGcVZs;q0!4J1Sm^$iBV6@TxmHIwmn2&DWYE-%D_1 zoh9|Imp&zitQ>(s4dl>?CT$-acbr4#DITIt`Kq9(_@z6+;YoP4(`Pyrm1a(9)BK9f zp`VomX}<5z{oOd3GDTHam)3-&dTeZLl3h)DH*CPS^WIF&`1Hrmnma0zgI=6_Ip*TM z7_CmS@1v*zdp?+LwGV-7i0^ukm$=yy$zrClv3F_fR!i8A1d+>3xf2{3+K(iKZtb(u zYTCwq*H=FPJQ=svFej38SkO`{FGkF88OoRWP0<@9Hf{37tQ`~>9cV@;eu=v`^wPRV zR&p;tKkOL4v8kn{^AL$t8aqyA8v{mveMtZtBDwhlQ@GiI9(76y`OK17*}*mce2SAJ`dSU3r_e8R}v?&D()_+TU+e6Uw} z;^Sd)ixTCQ?Rd(%Xk0~&v&if0$*$%<;b=8v%@Ot{?*uD~(E}k^-d539HyNTqo~61w zeIK*Bf&PBVBSN|v8PNm9G`-$hnpmb~W}Zprn*yCi)~Pj8aNl*p9v{c%FqdE7bU=G9 zLy;?DnkSBmfywgY<3o^NtI{T~E?bkc%Lna5EZi!9&L;x~!?@5743VC%U)7LO{8&~n zSblpG3)1Zkz+z+&4@4QZ=6|W+BqkT@a&X+S@L^U}4GMl1;%t%4Fg$HamO2Vrt*l)x zBDs-4^F^>VF_b_BkYy}RZGH6cA=;hIIPxF<7T|;3g3VYqYLI2z;-?&Zem5Qf=A#vU zd)XQ>b$lrO_6C4IWMA=ZSvyYX*z$6H#%WuSvu_@+48Ujf1FKtgVNM34ge0IddEUA9 zO|||;TLfKV6wakZJc_G-_hbzd>(+nH&&8CY%i|1!*YFH`H^tVU#16oS`NPbPP9R;x zx~y+OS4vhjVZ?JO+~%PK|#1;&6#IPSDfqLyUZtZ@eJj}jWg6P z4{E0h*{JjD!&x<8(qomYMLMh%l)%S8+Vw-f+184hN*9?v{I>+!x0fNuvyC0A&$_zW zB2CW$b#eThFcB>R5VSMs?vC^Y8Pnx``o5fr3+`^Gd|Qqk_oy5)%z zB|nw*q=UQuS}SI6w$)^a=J<<`Zv`kGRL}b=(;EX;s(zfs z!Jm?Rq@pG&d8_z8zZ&_+dgG(Up#1#&;>uMVH1{uGdy1#hERfvVSg1KCI4$YgmI6hGn!GwbcY zKg0)EIw+{u29X*Ep&1*zii(dni8;Hx54y-fJNWcSd9K4`$f9O!TEzd~}Z7A$3Ev3Nny~oyaKgc@g0Kf0OT@P%u9MS{dCk^q1 z1@-RL+r_+XoAaFf+=$O)2!1zZ>fo{6lzBWr8Z+UZH=as_zw{(KGaU>{U=k4iT+Z&j zKQ0njrGs3NLDsv1nZJBZZ&<-{TbemqGH~zJ!Syjgy(VwpHV<4%$dSft%SX-&_Y&#Z z7QXyVoLn$k{AZXaAP^1m>0ZZy7CoHI#^P7r`5;K+h}50q?>2t_xb$>;R^Gas-QQ1r z%ECt)cvn7ip+$Q3HH!T3s7RhwG!^mzd!UP`z}0@`kXgJR%?l{#Ax`FBhD;EUA0ZdM zc=3W*F98BOGjI+}Z4jeG%wNhPp(ta`E5=njn!Zl__z(zc_ZGWC2&8OkBZ*xg{Az-M z%>uphFSjhSlyXLAzxoVc8#cIMUQk_CbrrY%Jys(UMk?7fNql%n@d@AU=V!X^LAYT1 zW~Yw2^ru{hcez_(`?%G}M5`t!$U1CtD1B$sVEfW7x}TL`8W;8k*;iu{<)jAt6nX;v zbQ3wyw_j^X7QaHoWJBf3xq7uaYV{w0U!e{s#wI;IVUDU$hWEtU-g^LQ0oIL0 z;XOA#P-A^_QpR%E@wGb~VM#n_iCPSG2{Gpb@GSkMO;|9tuY&nh)3UvL(;l?|BE0Cu z3K;0%OIcZaYp=g`l9M;dt=dZ7d2#PES31F#uquYLXD1c%f`cy@9Jy3~S0N(i*rlbH zA$eJ~etjLfJU56_oV*Chn@GOPltzlwVDL^b8JJ*vOgD|eztcK{a1;lE)id-`SauaT z=%y1V`vCUhp4b3eG|_BI2mgjU2#NfdOO#!@b2~VAIo>B#ZWvHyW7cuCWZigsho^>5 zqlerH$X)zaKS>k2sB{6ee3+ldcQ`*M|J;tTr>FZHi&ivv0_3hKKUQO%o_BTNoBBIH zL~>fM`eN=LPmrxJr(j~&{nP$__57S#7i(rtvHJ%HGoU;_yKFiO$oYdZ(t zEFJ3m6~>usOYvDhrX7tyK?*@!Eh@E$ll5rvXSRck*5-|gW(>us`orci&5QO}ajK26 zQ7Y;IfYQlURoCfS^0?zQ?oCZ~y?c=@*K#Zmi;_f9$zA-A{b*t!PHs{Qu$so(&P#*jXrs~ae>a=my__na5VHz*pq@0=-l05k=S*g?EG z*{_@&O4maJZ?AoPsL~|A*wfwP07cNFvWxNirOvHedMh{wlGrH_talx!4FO5Cl>-V~2dy?c()L*Lfs$ zk^2eR8neA5AdA#yB}K(n`-~*Kam6%KY)Ms_5ovh|)IS?7JcdN4X9J5-Yj~7!b7Vi7 znLuEFe2nkb%LSlm{Eo{_WtoHo=5l?g+M6Dfgv2PxDmn<+%HUP zUBWMbvr5e8$!Y~0+3QKSJlAixVj>?FWvn%XvA_az0R7za`Rfi?W5topfc;2hDS1w)dK)k#X4saWsZWWjX;HogD<)};wxzX zDWe`gcb%&D+^!RxVd7S#FCLv4Xui=*iy)00CH7nWgM*s}?mx~f?IdO;OINHY?mq?S zC+zNCaztpBsF)_%_foc+y*$4ZV;5LZqXf)Kp9fiT!>&t_cs^f%Ojj>C7yaUed@Wp= z(cFQ}mg_3-!ZUBCm0WO^ac1)BNXfHpq5LZr<})uWxuvl!vK8oqWNNXVPMmtqg{Mo` zGJDcprdfmguhB1wRO7Fr)=_%*7LUe&SCjiaYwnxQJYlopw{DR=F>X-WVdnY>vc^ev zs(MasU){TsV!4*nvK47ZrCbujhr*xQIcmOL!^v6EoWA*tM2CvSwd>a_aIet%%`zbY zhF7AX3E$B~ek$sNJt(%e$7*A6AL#k#x2-xwPyWI(@F~m-)1Jvom@R48Wjpd%U0TP{ zQ(PdxyGHfU0#FZkx|7e=fp^j^X36rCp1G%S{W2`fTr$k-*#Ee94^mH8_U~WmnY1&mdaa8h`Q9GU9s*>Df{nNFZ144FGH>p% z8a{2^DIBKl+*`rn>GM13M_Tfm)?IbYoq3w3CMxgWhlJ7mTwGmCPl#^s>11%$d32wY zYc-XJQ3|Q6hu@K5<(0kc+LeoTEB4#_39X`MlYI7`NByc6#9x7hVMG~^ad*phO?9pLT zrp`n_C$TU#8(k@xU!k!E@n%ukcd9whgrwLcMbDinTUgs<6HmzF)2Jq6u`_x|vkNNEN^el?s9Cl+h4-YHB#D+Fp>lON^#7%p1a;-E|vTp9S z(h2Sx>loQzsz?^pIg}Q?-s$7XxI4lvd0R<^>_6ZUNQr%gRV)Gm0?nCS z{-JuxXU_`#=?ScKbN`{19~4Bfky?wDnFODkn;j{gWIrIS5hl1#ugf`w{e& z(eP$rD;}D?Nc;PM%n1&QEhXU@Ui?ZIZ{SYnfaHLI#?DX>3t%-8DDT960iKpfnTIS61{+(ji!jpa3godOJ_ZUxl}D!=73LNe^mh)+2oh=DzF5BLctDP9fd*e|0RB+PHv zQ)pqj={}I2zgEg}cR-c3EU)`y%eV7`&|>IrzcId1u!ny%%rp4P%CzVyxPIsVPfH@jc; zamn-Y{V*yONUjj36Ka#80a|!$n!gRgzGQ#~Q-eO(%6!jbSOFx=1`G_~S}lW&lW0u~ z|Izx_nAH00tv!Fd(+-E>cK;uPO*GvDPUW${ z5U#hO$`IkW@8`6uBX zmtT?NRwF~7%9%tBI*RO_;-(#3RRQGTF={kGHl$nyav>WEzTJk}7|BqSp+Nb+5xjE} z>)LHG+(RS(6_OnAj82?h;f^YMEh09?nxAJZFo5zaC$eZu35OF>Yl$@=&R*hfUj|a< z=6y0r)fYmLLSy$86E{4%gD=n9nVFlrW3+v=@S;<=8-XHC&lFz;2*|u_b%JJ;7>G0< z@>G~HmtL&BF5zhw-5p-3tK!?v)eN53405uC_oWS%UAZ^yDtJ$3|JvL3ARobaIcl5Dg(72x}PiHC8VtT1c4A)rAsV=vHQsu zf+IYu*kAL;p>R@h8~=~O7n>K4u)XbraD+QOv?PLJ-x1^vcYyo?>;$x!5jT?4pb zNt-uRQ#5=*0_)PuvUsJRy(8@qhJYl&^wl@Mlu zjkSqCKc-e3ITLF=aG{0nR z-%V<1TuB#yGUeCGTeI3V9i5t*YRF2Q@zYR#yIiv97KL`t_{!@~pLS|}xs`}IZv8@s z#yP!#g{v-C>&7n3W~Ls>VVUR{Xj4$6W)_YpYs`6R4TSi^a)aciwsDZY-bbAuc}E|x z!tdt#O~NcLd-f4t3kU5a&=RWO!0nG)3_LPw&&|D`=V^0EZP4Oy-Mp=;q<*XHmgh!~ zomymSBA%V49dJ6T;x_&2P`u)WaB;9I&*4c1w)O*Nbw|ldhWx;ja&J09b)aEX!I$8( z@E4NMKnMm=>HI-RBxr^H;Cg=kw?L-D%_*Q2^<$2sb@mR#osC4?-~{MaSEi>Ey&O=!1|7NQc!;}|A>cBf>M-L}(P!c-LW ziOwBg=5>M1AE_`OuD9}C_{*a*`{w0btAmt#PHw{49k-6Gm_*+hMQRBMDb;`B~_i&Z+ zGwUzRT4fs}he8NUEHCvl%oKF8;%E=I?b~kEv7}`7mlEBwZ;~~Q!3XSgYk9 z2ljC;JP!^$A0UUe?w4Jcq{iw{;nngFG^ig=PV!xEnTbQa>;Z|? zgcELhJ2c^{n{cTV zFgQqLF;1(!;a-MB{4AY}=%^YRmdF-);nJ>8@O}sl1&}QIxD?aj!;a#EpO5p^|E-6C zU%q*TMyfTNbEwO9q1y^F?6(<}=?#P@KE%ivNAwz99&p|DWyEA#h}>C=6;kv`XLb$$ zy~D-)hWR98puX~xDytr}hef~NYhA8=V;%i;XU2F{v*g-mb~lDXa$*Kb-lQ~mYPRgP z8QKs}xqkCz-@6BGrCV`UY>>BBZ#{AYmq`X<9{9IUPc}1`(KE6T=L=-Z{g4;7yu7d# zRf{#+kXUZCwJ=+po7vi4v~(t)41xvMQBz_B?OxoE`x)D`QwFhj8DvIe%0;uTpwX7* z7haDA-!c+W{1;dRUx$ammh(^7otgiMMT&CvAL)prX6octgU74jXdsLnD}$ ziCy_mZv^qUK>~J}96XA2-objQgASFiFoohzyn&_Gl8!yR-M#zA4_cjMsrG#pFOq4I zfcqep?L!PBHwl+29-l6N$HFrVA@A@*^`-dJuaK6>eU*(n^8U#4$GI0PO1$2^%r)Pu zk#4PxM>itI^Oa7Pp|$;z^4q>T*HE)kxcI?wU0mloPkR|YRC6r3bnad z#ZSO*EEbXtP#EH^D@I9s1a>3V)zspDprS8{m9^7<;#zLw|N7^sf`oIlpK zSPN5Q0t=>0)Lp=69^Vcw2w*T>{1J>3sm;~@SEz=OFB8Qhtk?W8iXD5!rI*-3AnI?Z zhHO#WU|{Zt|ip>80h3Q!@h2BreMQyirvpz#EA2iA21R^y** zX=DUTUfJmtr-CoYUfN!^+--$lj}}|N3O0exjexAQ37W} zA!FT_ilB{%I-Txh<*RyGK;)Pw=R5rS+Y^?&W1eP~>QemX*iDh*Iu-q8Ku3FUB?2K} z%X?6C$c|9Lf4ismINV-#XS#ffb;m?!qP544KCE!6vZ79s{qS@!(A|VZZL}SjF!Y1! zM#4Vaq@92(-j-s1TB!v~qGR6Rc5i^g0-4fL+f_9b*_^hX$;UgkoE zy+_K%^+F>(J$s<6fKircbQ-m2c606p(OgMPR9en%*r2ST@tys5x#IY)#yx4&X@%YA zpLDTC#TKY;IiV-*(xFi#=GOT1m}J?EYVXhbr!RW-3N{=)NgbNolM^%Rb^nTA&!7GW znroodUd{k$Da`RezkB#m)GdXv@I*yTi}E=RXg|l=wWpr;-w7v=7Qj9sL1Pj{$_MZ- z6fhsq`!p8moSz9y<5!1k*BHnE&U4de;p1jMa?3?--mL%lRwEENSMji7rgnV|`ln=p z9l8D&L176W=Ny#>`n%ltK;D+t6>~S_XeE3DK`suWYa?i{Q1c(wxn;Ybnp7Byx3!Ms zh>5h+?ai+|j1u0ZOJvY(zAWk6%VoI!w9dh&(lU017k}~kwlVyn402h0Jt6wlB;e8C z17~Y(OG{78Y>bv^ADIQ8DpKTl{Nb7VJ_#nd;!E^VFyyR>@28$Tw8Awz7-&WqfIPt1 znqVczf}w>j7_;ZQ2;*A@N-;zu9Rp^~#Bu>;KFPgg00c-ysfeKf3p8Vr)vs|9gH%HE ziY_?!z(RFE{IY~;#lw9EAHOTk_s~dfvR%1s8Brw}4pPRycPc?(R4?3#%i`14 z%p$-S>(O(^dGxS@#{takTYb|Rm)~Cw(eX>p%I|TyG8pF=)Be=?_~>MSoI~19h7|&* z|7CRLAMCNn4n`^Y4kYz9N*s3gf>HyS*7Mb~Fy>kTaR|u`=-J804c0_rfB@cnsp;Eu zRGb?&xoT+Wz!%dPK9OB^d;7bmF>)<`-)m)gxr zP0DwBB&<2e@5Z{M|52!l<`LTtYiXOO96dPWBvzrGmq8Pu8R^XO&5CbKh3*BDEc2u# z-_Sizvr1I5WO9Xx2<9G?fh!HKD;oTN{m$3>(3i4pL$TXxS0mSN7vt*kyxUFAs?Lz)KFww=UQf`eur=^P^t1g!xxx>M*A!Y9!!W+Sex&WFLmQnc4;#@P(O7vN`s zESC&KDwV$)^~+*T~;Lhu?mw%yXgJOF9RKbNbI@LGoR@m*5*u$2Au9^Iyt)zeW{s(P)-U0 zSHKL-zSAFkP0E%fK!x1=UVcKqT@UL~u@Xb`C1Xsch461<5#2v`-98PNv$Lt}Zg{!t ztkic|Lzw!oH)r12X$sF4E<}z`lxk8wjKBL$-Je4!#@p}IN0vNIc1E*L2p!kkNRlBJ z8v33dU}yiPb(Gq=6Y|6a5>P7>OuP2n9xm|V8W&Q;YVk&62v0!_(T@J=Rp#+wzo&E$}twFwQ>T9k-_F>qH#tq zTSeOUOTR@xi`TfY0QZ!Q!osg7A?$Grcr7VtWyG`1*mj=L;ffbTlhTyRl_voCtyrOR zw$~QU(+4?jvuvnVd{Ve!)rz9!ZgiPYbA}aiemK&SZvDnj_3syI7kS3JAUqJgjFPwW>uc4KV^s!C%9@s z@azU*4g`^1EjtMIgp!-qx(_>ozg9nRG8H+@awM(hIjRqx0F#su$u94K6&t!If4mYX zQ}5ogu`}-`CQXPNXoTP{vmaj<#U7w|o7qEyKhE|uJq5Z3AcNjE%lIWjyl<-Uz^5}6 zirGI+%T}~a;`g2ePb+W4Nwy`nIt#3;ueuT)ilyIJ6 zmn~C#*%s%hm)0;0&+1!WHD4=(Tym281mBR#`T;v5x~!l=tv9=eHyC>C7#7c57+1Vd z`lPOPv%^_g#c|yauJTgf>GX^|TMr6l3pRH16n3lBFn|_&a%eMTc3#Hz_6emiaRDX;=OwFQn zn^wknWsaP9^X!C(LBcJJNtEB3a4s)n5HFo5I>9?n4xajZ42H9V5fhN}YXJ8?Hp=8j z$!Lw}Z}8YMy^9xAUeaOsfRjhWUWb#lAK->5FUB7_(z4;fz@;rU&D<_5~*xZhhg# z0TQjwodnN6+6o5mX$QA5bkX-1Z()?P($SUsGL*k_^sSPFi<8%6;BgEDzQx*-UID)+ zf7@~Gb0 zf5Vx;$M3oET%hS4;@clUoa`{i!|e7dr;uj%w{I6mZmRQxTJkPm(pdamD%ad7qr8&p z=SmLn`~%+bv;EdOGq`eV_NzBTCe!OL+h#}i z*fGqpJZE`}m%An8Nr&r6fir2|ey(xFm50})B`NHeU0?2@R~*>5M|F94F)lGEhCvv9 zS{+?(R(Z)cn>&jKNr+~h6ZLA57)6!DaKYsTx^_773{spxrhb!mEdwhhJ}dbLa>JkZ z(WA|Pc#;9n0W4|j+(bS7DS>{q7!yX0y|mF)rCoM9{y!ngjoO-ZaVVnfc$(Zz`XlrU zLfaefeSaHv-&J>m7`4NFj}`bVK=OD;S(@o43gZ1sMT#)$MazKu2@E+aAg0wuGlN%9OkfyPg52iudJ@JZ zq3FR6K0df<8P7ItFqH{y=$1?DT|$W5wacmQnvD~pgBn`DRFhmWTo1A`*0KEs#txQR zvTyI~3rRZ-&=3r7|we3V8nMQ`^#ZIBtoYnAmlY3%6UA2^gd@JUeOlD$a3%(LwOV3RmH_ zk$Y^0!xCd7p5XX(z^9Is`D!bZp6Pf~A7oK!qA zP?H~DlIwa3#P{>zU)yYK#xGIDN#u5y2Vo--?3IrKpE8n4N1C4g7`Q{jgIUhI!?6Y% zX>f}+W)@HKB0hvVc|F*Bl{8+^;X(^CR==}L>g<!7(4@k>3f^?%4Y{WDJ@}{L_a3HHBEV14QJ@S+9d(VjCWF5PnIKAc za2{x4Sj)zmE!?q^F_iAOvAE&6S5rGs>yf0?ZNYl(?@xF8sI;oMg|y>8mTRTz!!@Il zi<m=AF=F6NiB36nZXo1L zk4qIT&uFtrx0V#ffZ_Y~(t*3^a%^B_km^5oH~sWqkb&ya4jfnVD|nttEkh&>>(SmWA&kki_VyX$=Z zytckDj)_WMKX5Q&ngLI}Ah>s86!(Yh0y%f?MEMowyhI;V^-gU>cuMYB-pU!E9~5+O zhz!5*?{&Ghhcm`2GGhx8#=Iw3F}`HxFA+cR{@OtLBfL;H~Ewv)j8xEhP>z?#$n1kTGc8MfYLTxIU%FwnB8+?Cv3n{bQPb zMm7BjA2eyPzz`Z#yZmP+dO}s%(#-4Y5%vjJ&tUPw?m}p3rDrgN*juB&j=s3EX zy+L@>sfjZJE7z&jIATyp6Gcw|rLAX}Dj!TY!svH-Znv@&$22%kB@cJg59|}w35ata z{@bajbRVst_+ZOd|NNbR@^A9CDAl%YJ62fZdye&#!CPK~sRM=Q#3omJ@eimS>Tlo9 zk*84|GFmN=<+yH<-5Z1>4{o4Dc6MrJp;FIzqxfOUhT$ijk^66s&l+Y~?g2UMZbpgg z7WWk_eiECC;GlHOt%sQ?oPlFGt-l(7kN1Zw!AuwaH24tElbl&_+ZLhgoq$Zq=ou*7 z7E_}fYnru_XjFwpC?U-w#{B}aIXaM8Iu6_;3!9*b*P@@FTF!j3LB2}*@^#lRR@sY@ z0J_aJz|J0F=8sD29cF8$*EZPN_8a8uc}fy5n0sztu2BXeyWcQg9Dd#j)bU1ca$Rkd zb9Kkcm34|sQyAZ^T)TquH$;NDlZm~fab(B8kMEKPEEcnz)}u*FGhx<=`z)bM_h#wI zlXKhsTtAg+sRVj%*x%-W9VfTf)l~&tG1t>fiJCNL95=bj2?GLpkL|InTXSkoZgBnrahaZIeFk4hKIN`NgCOrQDSqE;avk_Vba}L+TurHm1Azdz;3Qk~>?UF-WMrb4 z1(fNQu$B^NQouPkmv&C!W9G`OM|S^sKb5=uSV4Pva!EpA)}ARE{)CrIHX zBlF?qwI3lAM2ILkEgbhr435T+D;qM(V zbb6ep_?TRY-vnw4L980ZyNCf~`RqhByZy&3;Ty2S!C_&;Qy~NeQ~@!>i(zX1CHiGa zQpKUG|Au4*tNa@X-}2P^;JuIm0J#HXybXHzpW(t0vX;w|@1`oOgJGd8@7zu2Ge1F= zR-S}{dFASe-c?;*wmNzFj&|`QJt`4H;2r?`Cr~*oVswEb!^#_R!e7QVK~G?Ga!03T zo&{Y1-Gu^~7IqXRO3oLq-uDT3c_DqLF4#w&okMRfPR|`zT=*PoTjCXygi zaJ0U+Jlg^WY!XDz&z;($K|5J;8T=Ee3qK9W?)ab0>2*kV?vSbpd?&kj!tvm8MnWXR+`P+!ZhQ|luv>}u7E~wl7_ubdR4ejw59CYb-$-Kva_qy+}aXw ztLwJs@Y1k$Xc|0@%y%v-Fo&(OlusS94%_ez)C24&PoMn*Edp9Y?rzEe`HLi*JC)CpK;ah0~@8& z*(GyaCVZEFK*h5L6CX2`g$7QON zFRr-G6Zk@YfH||8zpiW}p}3ni{)ZCEc5Z&=0+|xQy5az4(ov2)R2r!N>FbN;q>FyN zRT~l$7az>9F5J3m$so$#QYN;ZV96aWwii-f(Gni% zu z|K^*gY>{4T`P0B{oQnS1)3bZLkG0DJ+}4r5liQk73~Y~2??sMaJ0*2_OrqM>_>G_f zYTo9q;&`PV{|qBy%u(Y-v@hzEXY=zrWlyttOzmySS;c)aoo5{OuW;Z_ky`tg{-sqs z(}Ya-AxjB5Fg?M^0B??en|Jn82IjP#`(6~Pxykgs?D94g^W2G4-P|Te7o?FEXw!X~1Ce5FqLMR@-pkVBe+BE}jV#2}> zK4Nj~Sa#uO`#G6sE&m(e#?jQZo9_83jC15IvtQ6FU9R@!C^MuprQSkk)^w_YxiVwAOx~zjRX=^ z=cXX*we!lho1x%Mq(X*uXUCpD<5TU3e@sBkVK+cL4hf|Tlyh;jwJ?jpUC5QzDZkQ8 zl|#)gFZ`i}GAk9U&BV(3bztDm(M;`}&s=)g#eVd@&-4R1`uovAx0g)b>vS;nF8tBP zl*ev8QdWHaNhkF}>%RB5ZaZ5Pa)0?UxPDRI*(=TxoFg56;QztY?toq#>xGb*_aP(= zt$m>J8jJIKP}xH-9J?r)zFYL8f?34m_oT~S2Kn|`MOVO70Gmb4WK)^npfqPU$-VO9 zL-~gfFPj~AKrH&-7@?iD$2K|ZCEEAcGBUEWh| z4+_xAgB5A6Tx-Ac^lq$xb6==?j(6N|@4T(l5s4~YZEGL)G05Mv(oh@Syh6 z!zl^oN}qF=o0)Z|+)t~UqCu|snEnkje`ymhLlMi}68u8L{1N{kCf&-hTt~ zHm%4k?q=`H)`cx>ZTYIqbG~vd-t^z;&u`k^a{9m3v=uRTtE{qDxol*bu`2%K>y{;7 zHfWN?^un_+bJ%~CyYsbk_GJzECW@b%h2B0R{!=ZEZ)ds}znkeIqrLa|YEdh`reoQH zj-71q@%dbj;}4MGAEAU!c%p*mXOoxRL-~AkTP&GJMMnM7^{nhjVClFPabQR>JD3FW z-tV+-dRYdC73<0dBhyM3e|7!{d-%^wPf`}lr!2k=#`-ZY-}cA6CSO7z#=DfsthA}FvAN4q%2GP^{1*QjwHIE2 zHSU%&qpRQX-;UFS?-l;qx+tw2hFQQWE||U;$J9k>_qR>#}FoxQ=no=7L?B%9o?ZEv`7++$*B zT4Jg)T)rZn;WC~EO@unK>d!idieg6BE$tV!kWwV5U55xU!+z%&cRy?BE?vmow$pKm zPF!N^u56ak4%)?0=43{p%MLdLMryK-v&*+1`d*KdSW&0^7S#6H-qRTTx<72^V#-_E zOZ^Z*ciU43A@wE+fT%iv9&d_R9$J`qlN5cu9Ks zaePN*!XEfOoGE73Q;Gd1obN!lMU3eU3-@uIyb%`O_gOu!DYT2U!pXbsYQ0Hqsxn_q zLcSKh?aEFX;Wo|L6^6A!iY`)we@KUz84cBbkE<%FN)K{ zg}sUpWC~&&J|#F9-Bw(~#^Q*3}p*ySE+DvJxVQjqv16pv<6h5qwW@AIA zXeoVkM5?h$g1l<#HBI71kqB`i=22Nv&gcCh))DUdP;Sz;Q(%-|ic^ZA`f^N`ZiTC@ zDlIsptLSZF*Osxd*P#U)d|H~lWqO4FY#8>6`{w4rdwJl~hv6-PD?z0s@&sYMzrhdG z%InjW0@>$T4;(OWUkqQ7c0}u-VG=FzOt@`1Pe9bx5Alwy&aP8}K7WVGW{o3;x)?4{ zwe!}qv4Hqtz$m{G^E~l+ThZL)<}c~FXU;18m0r=lepR#5jX&n!Qnt3R=W`8@joD3c zg|XUV3@|@&X|=?%?b|~JKR>W0@{yoK-NnQ7995y`_%*p7oe%fD+>vc;Iq7)nU0Kb| z^`Z1Umw7J7_-fHTp}$*Q-@YqCSM}nT(W{k_XC*2f@3%OPl*XUzyVLQ>8+m}n`{x&0 z?dE>fg}WCm$Rnh+EvAP8*ePOuG1Lbl$h+(QwUcKJM*Ty7w)AI4F?-`VmV{iUXXDEs)y=c`35=bwPP_>G8f4HxCe9MoWY zQv0>rH41)jWW4A3-em7XmZX5^+;%nr_WdVJXC`+= zf7uZ}+acA}UB_pUGyYEV$Zum`zjAt@0e{#1UKz5#Idfcs- zgNO(5S4-Gpt#$1m9#MW9i?#lVM-VEtw$?^H-5nuZnse&V3Qo>6>TLn<%lmJh zK7)5LHaYor?$gGW07QbS@-;=-Gb<+Vx2-ID?-TvLV889LU=Id-JG8nvq-gdvpTMIgjp{+4()Ranp&qB^V%C8xd;-@L#c?zA<4B zBj}6o2MMM9aQXcc`a<1Qm zYf$E7S>ec)@>^+GzxkPOb6Y$JFIBs@TPlsN?QhU4u+^u!VTmg(gn%;`v9DEdp@9DWx~?u5+)LcYFslWkKZe*8djacUFM1T4 z)gX;XK%07W!=K_^<4ZoKE!}y>B<0WIM=QAV&r@5mo~5t0{CyG#BTqUYdp@)4Df=8d zTT!ku_bHYQ1<3Ym975CjH0RdPp_|Zl*m*N|;b>hrPxQe8<389R`R<3rLG}cc+9(3(}2rhlG-MF3$Pt|G#_BI5o}~9QI!8 zjX9tBgs*m^%qLu1EYpj%7~_R|AOh}LGNp;*wcS9GFF1dicV;1OFsjX~ldF2hudp1$ z9PsGwI899eh(w@?JQql~15`H*Mtc8HB))$a1It)nAc6L0DZoAQbA!S=dY$IpOuhb_ zGR7F>i20s?oB?6AMvF3;@)rjS8YULzJ}l5ivo*gbmPIS^ec4FzX(O*d`9E7BjytAp zG6=d^sMMVKpp2jckC`s%iJ1)@j?bIy#d3#yGJ25wHzj ztv~Q8?*qyM?1RXaTB83!5^0Dk9asA%Kt5!RursLf+DtZ4@Gl&Zt>{Ufn24~o3M?}p zbVOxw#&x}Z^9Jz(gzg(a@|Le8%TtsDtfkg{$rzo9S6c%y{(!qE5?T1IsOV`$>@CI_mSfI0lmu|dvCN77x>mR$WT|+6Pt490d zRnSh6=RS0K^Pk>LRuS@Pqtnxcp%HXiZP9pNgWn`SiNN*ymwz^~8p=#)&*CTBBsilv z&SQMod7-i7E#0@{kg%zbQ{BB2=_DKB(e|EemgqXH2?ipT}&_ z5QZt3#F@az1CfV-qXisG$P5GMSJJD`ofI-d%N~!c3=|rQqst0b$r0)y5BvfGdWhKF ze}%@XS?HC(ef{{vD(9bjpvGc%&LUn=;5xl2JJgIfY zno$sssjE7x*UzYgeOrTO4B7DR0OXNET%Q3jYC#{X04grn<&R))i~_qAz`~mb8&nFk zHS0ZMz$s(vE2$0QZj|VCXa*7|AZ&!PlJlwKZ%}#^M*R6hQ0@dU-1Fz5?eCHLbxsab z()_^e-5dw($R;pIf)Ftn3|ns6icsgYe0!ow#Q*z!UYjM??DDefx9|Ip8;2UPE25(1 z^T%sIwMh{q-}!`#Y)rT@8wIr`hVZ~IrI;V)=silVtOIQn>sIp;reg#Y?l`A^nkReb zWO%*56R&LxdhQJr)b6WmG~#@~>_)4gdp|Hvfn4Bm-M^_zOaYn(<{Q=BnFQzCcfM4w zEq$aO!3D;TlW1`rZXrJ0p_uGAOCpN>Q?PuE)Q0tiGW2C<4k9Qg?dwMD?eMHGj(;3> zqw=oXCtS}i?~b!qO@AomwCo*3MEfP0lkq%i^w z1eNRLqXKZ9zV#fhw>|ou7lkW62nxtxN}+u03gpyq4Ml1ru?+yASTfzWsT2!vJ&)2y z%S?Oi8ZAE!zZrWpgkzisjwlVa_w5jF7EJg_8s)OdoAgo&ZvzEDhODI^`X~HNBUwdZTc<%*IOgv z_k`7%7m?sNwngy-&ih7Qj%}yn30?MSFq@8!R!R3~RkN^fF;4ureIdbXt>w63acopW z#X4a&d>w^b(LKj`H$-f(+VhbU-L8M=At4l0u5ZrI!xGQGG=Ki8`Tqd{lf@AD z{4hA3EYq0|UpqY=6Aq&q@-ewb^cB6LBr;&H8iIEp=$W(LxQ_oNFi<;nWX*VuzbN8y z@SC?a*G)hqO6?D^MbP!=okSO{DO(VZxoK4*_d{FF`SlggM1?--@sqdKxe;|47t>_X zaN^hOmnlj}xfoV8og`~&P5>8Xc9```_68Ln6-((zX{Hl z|GUx_l=NgmxLP$$tvC|7m%^vGgwE3eNKzTVD-%g>`j5v5q4M=A7Jl>CkL4I8ZP- z0a_i6c=;>e@86?|A7nF_^fVcSe4+HFmMxduxZ%j~?u_{qFXdryd6|IH48uO&sBCns z)(1<%Fe#urZ6B~KOq!5C+|RM!;)Vo1WJCAW=|3H=B_`p7XoI!*D~l*u&VC$z%Fe6R4ho8MJ@$myJ>!>8WJO{V-=-1A;i zu8CVI4SYK>uBL3!TqVUVds>bC1M-;w-0nL0;6pzeE;Hz_+x3^m!TF0(-OU%+`Wmvx)% zkhQkK$f;5Jyu?$cdj-GwadCJX-Mglmc^XQ{Q}C}BMMW-;j~r_LQylNG(u}vV{$H#9 z6)q^wkfZ_-DM!F{eBb-jBU_~)@r_)dSd^HUPmB0-XwCOlXU1&4^xhM%nDTn{jq)K3 zT@4ytKamdx$mfmG20P&5;Ix=>8ms*YF9O-*mzgj9e~K{$u|0+<$|lM;{;KyV;YaWhA-x*!n5+VUgc{+lI57y(|112>)t8^*zZYA^4RV7bIWp%`m@IF z#hl6%Go4Sz*y{sIhQ)(R_}m`CZRYAV^P8;3<-OgT_7^iN7v23Mtq?j8$OBPOXNmsj z5VNH_EMQIxg6NC%2hBvetV_YD2L8~;lc|U!R-j)Cvs?o&rwI}QmX`-p{hhC$ zV8_uR2*w70a;n~tWSjV+Uj*8*%a<=Ve)}dm|3PF8LB)7}sw*3}-~3UK42QVbhX^UutLft4ObTX|k>+Pvq;Ejm`Y_x4gFfVxzo@WjrEVz7bZQI5_TULO@hq zJFv?_5@q#0eKPLjlu>>7x-;Sn0kP!+d;jpgr|dcGuD6i|7#YbVDgXPp#jZHF*}G0fFaYo zdE%8}_BY+>J@e7K;Nb2bUR2>l8_Y~4y1t$U$T1$4oAJ8FkO!Jb5>C@b(DR3v7O0ib z4v0Soc3nI8?7-lFJ~Vr`96lD+gHgNa_p+J;k4u*mRn{801iQ3ib$cqsKCA7$HZ7piz6 zN^oSfVMg{;c-gL*@|r$`+aj}ZFz`Ywqd;OO0i{1s_EEX>x}do+TLq(hXT%r+LY%JP z;h6y40f$mB9zutv*VZf%5;dMhBkIqodz0WFp9b0ZN`t3Xx^!D)*DlqHSXv5|TPXy* zk7osqK}#Nf7DpA9&idgyC5@1RR^SG=iz5C!x;o<_>A(5?N0b-vQ zx@N*eb0a)0cA$)gJ@homvXvDmh0lPU1#$A9fun&i6oF<2w5yAaZloR}E$ciiwsxhmH8hpn^jvkCLA5otaVf<-#VHys* z>Z{+lww6joiOb!H?hP5ZQdc%YPzis}%+$OUZS@rQAS~oHkG{*D%C}pr5MpfK+ z3E@PgrrS7M3|sqoSCui?&?_Rg!5-q<*chK=bJY8i(4C{wgx3-ID$AX?MO^iiZUdmMa8Hj#y(4KEZkide6*rHeqo=Cd=i$zsO z(@3kTr>z8?>zvWAUg7msc5@3@P7W0s zZ+#-&!qt@)$rO3kZQtziO1we#bNd|QpVPUHfR2jQ-v_y45lt64-KbNjPh~1AZ^Bup z(Qub)1VpOf(&J&DfORYT|3{K(sYgSKQ8B;s%8jFErWyP^`J5kb|0?u?v{e(0li83T zmG{U5up$}1yuPAwFQ%M&_adWGtIN86A^jDTY$f*X+{9;oXHoyGt_3p0;WzMQp zR8GCs4{kC!MRr7EU%NGMyR90cKENpLg99?gY%@BavtDmYV4Z|(5R8HlCasU12Nc*lLK z2*}8Gc;K9h@P9#$0mdWGz%Lvz1P9xL6c8ALzNuyM&VngW@ipuDuM^>vqVxM2l>d8H z!z4TX49Vz&k91z0f8}+;seHhr>A?Bh{eX5i$r~Y9OkjrySxcAKR5rf62+Ak%1=&cl zCjFw}M8X%0O^x3!_0^lZ@8gbl5au;Jhv80KU~q7{_t`0o!S0*)(<8J$a6)zgZ2}xC zh@=y|W#vnZrIE_a-9OF%$RQYK=oU6*yW&MUkUpmT<{Wg7kpc%n5o!ZfW_Bm$+B3np zNmqYDlO9^=PRgrYw!Ji~I3g{Ec4n8aNJa@yR67MKQ#65Y#noVsv~z_!5Ys5{LBKcu z`_Ix0MC?N0nCb(77XnNX(?x|fd&r;x;}0Z~0!cOkUXn3^Xv}Z8k*3`CuMS?{sPhdG ziznjmg^~y?2?ssS>8e#eU+fmIJqXiW`%iYz6JQIklLw1!=yN?0hJ;G=M*3-tF^T3p z8Vkww(G0bQ`kOQohOQrWLU9xMzQk~3E#iR%Niwh_V+MtCjD`;Wfk?y+=ZL4dpWiUWI}T4x`l5^aK|KSnO^v=LQAn&i zBOKgLcWm>uN(hXSI4%Z$X}n}@?Myv}jTTsy;DraN;dJ!s{qcQ?)N${s2%5JBnzInV zb)T)-l$|5B_ZnygEjE9M%%M%#?C|r^jg{O_=@kRNam*+DTkCm)r)`Hc!M<2fh*%Du zi6JLOycCTQwH9Ld13a(@>2xJ9%y;*vrP_CxL3dcWK>F}y%H5gG+GYT?t@Cnmm@k^6 z5BaG0+6258iD;g`Ep)txfyRWvjqi-HYvlQF4b!j*8#}P~H{LY8(QCVL%I~@}*dEmwA}n_sYYAtq z(8@ePM8@uA#}J!6?FP=kI~9Y+t5^6{=6T%xsi}z9fJKNn`!t*$VuN^r)G9%BLF8^e zWg;BFfR}-Pk20zUt=Z=h!~Vd(&r2GfmlA~FLcc2qH4ox=iI_?|Z;*l162MGEO^i4J z0se}V%y9h&)jc5y%T-Lh%zw%aTp?8=;L^l!Y^X+DZqS!5w?QMZGbtwDh!>G5j!Pj7 zF6PlMv8vKJ@j}*qgZ7am9Q>N?pvDBPzTHYc1B_b`cOhU(q=FzG=|cEwHNmLOuJMnC zWS#FXhJ8MlE#3dvdcpt>DnaO&KCEtFo~KvVgJY>;wqgL{0{~`TcHr8`9Q<5ltzFlr zpurFB4NabW?Kf&AiRiU9jvue!T==y)+yW|_Ch+uLosHzws`rYKZ5A|YD!)=$Q(dyT z=LzOKUzc=K4-D$o$rIlieg?_?4)E>eiVeld$jEGMZ9l;?fM9k%kcc6HbMF2hnLy1W zmp^{z^JgdhI-(QaL9?$BhY%T(rBzc-D|@~ck^!8O-viob25{Xp;2W5N+=LdUcus^j z^b>CHEl96ICI391Oim8C9i4HD=cqr?YO1Yx#5xMbFGN9KnenvbqrjeMe`7>3K|ZpF z%lla^Pa1Q2i%~sd_~WwnAr%aL+ixq`Cup>!^!Y=EM@!> zS~tA>xh&*t<%vE=u>^Izu6TKE&4#cW_~p5daRGkiwC1DVs@p|^!1=yyimYu5p!x?l zm7w8{XIxJ$e{I`PWb_mZ#=jR3pVyC6J8Y6yZYYf{e$zgRfCf}xa%r`|?e0tu{b0eM~@vj!Kqlo+v<^Z6jR6KVWC^i7C zy^Nsox!@F2n?QvhCS_6tQ77!i?;ZaRsDN%8wb1=WMcem}3-szytQRe;zaq2k|L)1n z1~~q+Q{Fv7?UKASPb@$IE2+}*V4%Efq3a&fc1L%PJbV#?3@2I$JEskaFzB0yzH1TeXlFptLk&K>wIGZ_N_k`s z1iF$Dii{&PM-q)4R&HSGdLrI2M}?c!2~o(&du-;kx3}HD{ip zU2GONJpb;$ZF|$hX6WX>48oQ()xnt|*5paV8Lqq2RT;M}qZ<~aJ(g9(ZBONRX>T>7@Q9s~iAW^37e&DY>S5Z*^ zYjkCx1&tj4M&{|?#ScseoMjd|#(Uw&a(;|GvzP2ClI}Bcvf}d zQM89W#+&j=;uy50Ff&5jkvzbe21GE4z?IQvHGyT0T%>yTbPZ&pkVg=HQ%!s zWJWv)QOMsAgIFM_d-OrV;5dANBP%KR~)5IPio!6ns6wn|tl1+~n-iA2wUEB}@8thllMomKo%Y3AY_6!_Z$gx!j6>PX%{RshBoO3b|DnW_WU&AlAaplA2Z%i@RMlNmCA!yCJzmFmvI?oAleNypa3)OD3jQHg;x}E9w zG^<`*^nq5}Zf`*e=2OEwyw}?pRZO71-H;PsDrEZ0(N?F5=VaZ{tnRhRisf8r|ALx9u$#*LS@UR9J!sUNp8Y z?9D?si&G!H(M&DPrF7$jNg^!_7nfQnoYr6ZUH#IGfdm6cZ>0YAKRN){q359J0U*%W zA_u4$;07h};>TAwmRx7v5F%U;wd4v^MXTft*)96T%p`=R?e(>Kr0N ztgDD`XCw)U?a%9OqNv?#K)eFSZn+Uu*MNmXHc^RzB31YuOx)M$(t2Oee`#NtenV(Y zH->=@6w+VpF`5yU`W!JIEGtq%J?}VN-Dq#Pcc#y1`OAH3b7b741VUBpreKQP3?0?R z$R_BF-jnl1A+}>$M?wD|a8_Pm68r~EhYRljVuTcahyV%Xn-OrZq_2Lg@`V!xpY{W$ zzR$P9C_71ToZV>-lM7C3Qm6)T2B01pu39mQB zJA#X^&@{F5W}i{Xa{pLnSQQ&N{;N02xq6)SgIH%O_@E z+UhU-*s{m`h|1myjqi5jl19i=;hkb$#M%LN5U6yQ(JK*{f-5zgBb*k9?HX4C`iXEb z0V)oEdR>5ff|1&FaCqMei5#zA`$&LRaL7iR&Q=3DpA(FR zgClY{;D!4x)PCPzHwIq6*!C}#%ea%tf%-AGA0`PwY$!e%Stp2;#DLsJ$fkWEchL#L zZc?G42Z>=4$j8#$li(`&1n&`1*aOvIY!qCu@R9UNxVDoF$AHB14iEvEME(FU#nELc z`n9X1&^Hll;_6Bn^^9#TE zi`YE%qdCFw{yjX6^uOP)Kx0wcX~|XM-bek5^uf#TV$4(CcrRXbCgF9(X+%IIQ1kw} zM7hZ&xRP3r2o&tirIE|nJMl2W)w{bSPo-Gv25>I^*HMGG4C288f&s+HeX%DAak-C& zydjVwZNp)(4PR_ZrI`E^I153YZkRFxS8al2zdIrYg^g( z@!J57xiXQC6}q69WlV z2@8FTMS}ad(#0_?y$ztzmFWAI#2vn|BMdM%!kY&JZ0~7LxS#rf5eJDM5viV_CIy&= zy44DNJpUKm`P~>|7zmGo&UFqGh(64!Tb$1fE<7i2xjRt*Ul+g8_W8wM2X{c0I*1di zk0^w^;$gFz=yOupHNf}!lP5`-p2X*mUwG4nz+^u$9z;`)s7WtU0yaNri%HBAodMTV z@%D;5)EP24DcZZqPN17QB=)bv&Vy{Na5xfRm>MCkBpl=xxu*Mn2dh(lf@1dn0amA~ z%FtGT2kZSQuQ-?FnFirem6nv0l=}j-NU16={UlEOju zJS2o8Vlwde8M`9^^xY9l+RrL%>AELRgvgg3^W+y_Swh#$P%TOZsw;7%dwIk0*W>uN z0*_wO_~w^Y7kmHkdL|@5q9QuvZo}*e+RQ;Z$t27;hwXjqY=C< z@cj;h5mz5X1Vi{qBbf27?cG@0XeA`i)Hy>R4CjdWxuyYi1akmn4k1C^BB}w@V$pE=teuc#4k!K_9$Z^W$b2;ZcVl&N7=S32r;D;Qi5b3 zSdc9D&EF0rP`dQe=Rsqn;mM6fYp|bE?mM6FlL7^*FAbt@z^G@K59hjg$MR1|7>l5t zkSHY{ZuVFGRtfYycf9HSR0oOgMhO6wBLcGe(Z9Eh1Bpdb?m~+Pz$FaT*2oj*Zl4!a zwBSf7wk-Jc+!Um#L(*AN?C?Dq&ke{Ng6;S&Ffg#LIgHQaK+CbIDc|C`NI18eMqG)ZvzslP37LB+|sBB98X|xseTu`7Ho3AJSmmg1a@>l%W6$=MPu}`|s z3Ti4?Hs=vQl`jn7eER~YUO-S#-ND!JtoQ`Ih0d21QGV!U@@pAYq{zxE#zSNbcGX%n zkkA42P<{cPz5NS`UZdX!a@KuaKH&{se{sdbVom=l3+!jm@}F`$eI{0M zje=qSZQYXyvd(jd|7pYD8Q>`q85w$14xC0vAVS>2p($FMtcwG$9JyT8dx;)9;_m*@ zi)A)=S4k-@?O-VUT&sZ|2~1w1TFx<41_bG+rqd|HihWoS6_KS9BGfSY_|?V$@HIf_ z7$P+JuC0`|jTYXYpncQ2;Wq%7m@ z@%lfUh`Iy-4CJfb1MA9kSgmjeVnOc(8sv5ISd>jYaX^{ZkwRlf);_6#8z)TDVdib9 zWQt%kc7(55!{zs6!^TBRWF_;#!0$%}9vF~~s!_2n zd{-Cu2bP$ilSD)&GOQT}I4hhL%$_8jr@$}IP@kF#-6sMc3|k*AcBX6=9sJqHuz2je zr3;C*JIe*`QEK)sSD&a{HQJ1+L-jMN1qS5NjUst~BU(VA2Sm~k{3>eCPLIIAo)8`a zC{nk9q6N8ilXtLwlv$X_B&Y{9oGCF!nfTrje*VsLGhRfAZ~|$>i=*T@!dZdMy#Tcm zl5Gm^OIMM2M(~5q(rKvA)A_}c3uE7W?Yj22$xj#x?den!J;Fh3gs<`EI&Z<5PgmR* z)7NbgKsHz-H=BIHx?sS8T8($*IU=b#p91yd2d#mi6a3c&s1I+Q<>zUe+-&R6MCML`(t*wPyLrRypI z>+2|%b@-gW7UsUrVEeZ%hK3ALdMDt;dJD@fe%{BG6q!4(ZeeX*BO#H1?Fl0JhcFJz z00A}>CL0{KpD?C8or9;m*>F2C5>eKv!_GjVhUnUh|ByG$s}UuoA)0h7<1*r;3|^|z z0HcPNBD{U1rUXD@n4x(0ry)@|zT--NK!RlS4%6UrufC5AC-nzMVLdE@_r~9Fok5as z6YuKF^Q9A5Rojo(3F$)%P$<67PR}!i?_@`3S}!h97xblO#jwGCK$yo{Teg5ze^rnb zFk+%ZsHpD#QP!?Ap4AG5sWDi$^u@&J{Tx$|-f|vUoF_QV?I004o84(Gg8yXqUM}TwJ;uAVk+bdq#dA>VHza*5??6sPZ@w0vw%ald0MnpMy9Gz% zCB)YT4i^9+s1HUeLLNy*%ikP+AC}I1`z4-ro(JjD6aBhD2y`7~_&&ah#Maa;SpB!y znnphi9_~N43pia`fywyRuo44tGx#$B(Ahkx*CxQM_N(n~h^h~ki;E?d z0Ocm=BJK;YA{(Gq3aX%LFeN}-1ipq)!mQUHqLmN{E;Re2e4Jt9S#FNyDh1smTPd|- zL_?-1O*C!vDC%YPG>Kb$7e3ZpJvO>Mb+#vfVqq#Xgn3LqYuXMLRXATANH`HJ8~hu7 z!o7$>^1`6M;DK}t=vm6`Ux&v&esi0;MR@kB?Xk;MUCym3&I;g8AaMi`!~^DnQ7sGz zei97~;kNsn#D0eE1lYh)1~a+%WQ&3=%MPN>4;MfC1UFK)aY04UJTk%~`J^id+(q8w z4z@H4nVcne$ZGvri|H}dDXX(8<_TMS7LjgTUu%2%9PA5eWfMD*(ks;ySVVxwd<0N* z6gE)@_NIb50khAT&D3wl$B#Hdkg2TUCD7I;2~MwKKeT4| z7A!T>*-|b(!TWTbfo9Cp!{dXcx7asUij>u+{M!0ojv5+!HwOM5l{G9s&wB;OZ1^ff z>w*u}$J{zt$K$oGKXI?Oow&_hbeRomNgEN)bGODnmzq){AsWO;zb}@Uf)E1gTED&w z!Rw59Wi-IJP+RWzFfVo&gG9B`5G zBA1Dq2XY;|zCl>A!#|gQ{>Ud^8xLq5iD4r-Y1!iGLUZ{oO~gk)M&<>k{5%Nt1(q!! z^|xS26@;zh(SHeVM0lKF{jTwHV(e#J%Z` zAry6YZ6pFlVk3vrAKAZX-)R?^>o8$q`B``eqU%4un0*3!jT!<1XB8`8wUR+g1zj~< zQ6;RZ$g0+qLR2O0Ja!kr@ZgER7`<{l+I-aSE}l99%9`{}4klb*$5e!N1Lvjt;YY-;pl?Y>Ncl)2fN*|kC+euF;6quJAB z-D!TKQkSCLr4%zsy>R1sQ$AYxi>S&Q80>B=zLVsriKZXwj2wg>NIiw{ZRRmP&*K71j^TR|D9R zeq?**^y#cVuY1KvP_Wdpgc=i%TiV*0mmIBpaTlKwUF@M#?(SNNnPigKifxD0N3mNd z)bxHBd}vX29J2)}Narvti^cyexMf_(ni|!g5+X6>Nf*hqxON-cKPkmqIVVJ8Iqa>d z1J&v4>_g?qikga&W^_xya36z(PbhVQfV(n6Q-BHlT?I6;aApFtLFOzSA@QB?*5b$V zDH_(!C+FYmyTFYNl&+0(4yXACPUH9Qtq)cMC`>r*)BpB;U;wgB_`|=`WBamALL|j@ zZ#VufQCJBTX|?hPE|={Wm~FnTTTw5`DQU*Agaz~%n!Ew<2II)hAiLnRT2&QuKx{Q8 z;V1XlsD16n^p^;`QY%1HJYQj;Q1q!{mCa`0*^&ZCR2z^Jp;PbLrjLagZhLd{6PTxz z+xYmfXx1@aCAwXAluvD@97b5S6bH=vaKR&~SnggjU%xQ}B0o3JNy$Jj4Qg?cKGV1R zLM1wEezloYiBIFn$aOW8bCiHqCa?T^QUMpeF{rjKEs`nC?`oR;Vr`lA1a^4#xLplc z0-6GzsSd*hnjCo1cD}Fq;`PDZ7whWP0U&pjLIxn9O__ioy?ghrdfDm4ix(vPE`_`f zm5x^>8kbo$L#rLaqS#cjGoqh;Q2V?n^Io~it`AR03~$l$l*6!@9Y;-}OXn24F4D^r zvZH)i_I?&7?TT;8DrSk$(1~6i7KPMz8Mu7bS3kT$+1}OBFGz6<-qdGh9f8onkXzhJQd*RFLWok z|6E6uHdo2GpMz&G?z@}cQ_%B+t`j+KiKC&~6KU-@_@fG)X?b;+MGI;Q~|34zdk$av(z zeQalI>j#wTd%)9ZgME$|+eRhLuCM38MP+R2Gtn7ycfzycm#D0~*xORKvbov2EE=>y zq;zu2)SK-$8B#JU>6IAe!~@;f`u3B@ZjpWG$F;TcJlySt$%PUi{Wz-_=;)>3aRbrF z-^9bv=;`~yHyl^Q=w!t5G$f0P9PLWip_+|(4(IJ{QrJDV&^nusmSzD*JV_52kE;W) zV5CkYqnNXRS?~lJLm3;e{ZoN$R|XuC2R4)Aa>*Dm#D1K5FFEyJ8xkGboieOXT4g@I zSn|w#s=gbyaGJ++xm56Iwq$qSZGP2kz(;BHpZJ7?w)Fhm1CidIbR5Ah@m0Ei9yi9hc#7{o^-SJKNiBr4+WAbB4>y z8EJ2@X~YZMWO_TIb(ANn2|Q2A6!T&uW|?%ubH&}9ztzN6yJo}EP<(}T)gfal0md~J z6Pt5OKF17smQuZ+Tl>qcG!`*2J_$2wC?9U{nKh!8a|)OxBx2c`w?4FTuz7y&*AQpA zN=!nM4SZg?J}eZflJmxf?K@z&tirO6<#o)41k`>2uk+MOp1~rHq;73#p=V@lZ<9lR z_+tBWe?ii2*F%Z2O0&uZGp`+X$EDjWI;@e5$PtCx%)UEtTCh7_z38wt{zzP2;Viv4 zT^^4Bg%YJ#SA6$2^mLDH89Wff7qVTpM+K^tb&5tfQQ5WNlE0hmNn15d4tMoES)-p%8lJ9OF75i4>xk5JCa`@fn4{?_TsY8 zgTt}j!;LNH@YHv$Oy=rGW+Z(sm(vV<$cC#F%m<~ga6bIuEAzBHd(Li~XxYr3%An9m zD{eI!KNxz46zEVFfvLFKD@s{6?~Vk%xc!;&UngTgs*9sb!`OGn(Qa3ak2^Si|0!{7 z)WA#UnMQ7&44aowTi33CyfW5KX;_Ye;<3jVtMSxR%)>02;}doWv1 zoUI)D`2f!POU-`bu*L8M>0uvuMHnylUAuVU0zEG;Z^!5cLEuw>q#@2n33Q#vEqAIe zEbN11p`~`CP>nQOo}Q@h0X7Yhv7Eh$lrq___RB*$GzB|prFs}``|;}bx%tVbSj=ggHfn#4grVpy)hnXB{ywc$}YoNYk4s#Z6L>Hf)HiL z#Qyx<`wy77ALQjz|Nf54l#aW*b5IbItOH**id8Ec__T&RhXPhwrly&>%Ct`}p-^u~ zk!K&QCuUQ7=PCtrTI$mM4&fjPFCcx2|9kH>9{%!UuRa!1%^bVzZ7;+)c7)*>zU>D#fpKJfRP@weh6dUI%}Z zk}O(cuk$+3$5UsStS7py)>kN&SaxYxUO}ODNCTge@C_atPc`F79vx_K+8N%jJ8`;y zl}NXq0(|dM4kT8;wLtm&sZbxv=kcW#C9_7KUBe^Oy1XJcNrm)kvzf;r$B}EwdEKqW zlW{n|YH{*uPyK=JEjSTTSe1i$UE+#0&e1pU)2`p`tg`G*iiW%1ewP9SE&wDxhcyL` ztr>73W&@uI3k&P>S3W2%Z;Dl7GzPKiD7SA2eX!i_N3ryh74#y{DO!h1)b>uviOIog zho??Dn{&>HG}BQ*|He$sK#`j$-7`!S$}-DRZMlOjtzs&QUm%UGo}JFR-i}5DnXGj8 zKu{QRgM(Y{9t@NMl+aMZ$xoZeQPx`r%N*v;j(NPqQ|4OBXzC5xZgba7L?eZ?D%~FG zP5EV=#vhW@>KArv^4@JbPODZ)(B834u_K0s5Y$Q)M8RS;!8q~49~NMLUR=$}z!-Hi zeH6oTVmZJ4f>aRX_x;HtV4P7Za;-(?PlkJ1Q}`fibfqr{zia1ai$&1L?Ar zS2}{vp>r}H3@P~#9{X8YNqt$O{g#P8d^xhYjhe0-_wS0vcB~F6sl36WQ_3HIMSKW5 z*7Wbryi_h6*P7^0@X&NS2(nCM8CYe)x;j>y-sEH9Mk(iow?{iW+5YXp6ZLw&H2VDH z#UY-!e1e@LXU=bC$EuXTh`Vs81Otz3ccZ2c15Zqr{|-5yvJ^qb+3xhK-s-c{^=#!l zkHgoQaKE`T1x%a#KGxNELyN=T_7;fc%OQ^@tP%(f6AVk{;2u6iDPwecC zJK%F0>F@6ke%O{%C_O?NNeN0Xde5UgxM0=BPJAz=PS&N(eoG4tjhD_Pxp9N4O{)C< z7bx-QO<}>>3i#cT{&+ixNKepvN1TD`F{66_NZ35npkfZeakcP(vC2=Kj57%xA$Znx zW9wRxEb4;+y=bVN9Ei$ct@|eRcg06!a!DvCSYZsVmZuy<^5ESSsN;owpEQyC ztimlF%hlx}=vvHallXV+cHdKq!2XHfn}dY~ehj>K)JweLBTvwx*Yt%F}X5dT!FGJIB4{-NB@MH5TRGAG5zJ*utiSNe>!|7zp?OPste zrM2UA@#GY!Pbd(EMGlxpCMD_k?LjNT3rR9jV`SX605Asqgb=#1s1!}Z0S1R4ZrWy}Fs zL$C%T6S(M}0I=}<54j+7nWdRbXzYi%HPamyHfJ^(b^L*Pb@z2Qx9A0@q@11Bc0N94 zlrWlu!QXq$?8VID#%1acY^Q~#V>q6IKWF>)Q3$1wHZ;J?>u`}&kl$Ye0_*a6Mc3g5 zyqC7N1v>y(-V^slG7TLrIK3R;1bhn)Wx#pYIpKc;WMdf!G?glZGlmR$F`p)@MHvQe zRbH3pqYXOk#tsQgqfQ=iT$E$+_5AQ258bAR4y}JUSO46UK~84+Oc++uIXfRn{lnWZ z>#v~(Q^O^ol=lLketu@=4zLuZ6L^&xPWIzK0=wIcCuI1S2d3SOKpyn-dzs>7wY*x8 zW>bGp5~%%bAZk|3i7M6g{SZzC!VQc4wUKaZfiLEOU|xpf{UM+^V6A~1^r>*I*j@&x z1gijQmC@2LnWQL^2jS&5(b9)afJiZMx6p6Tp6$E09XBF`y1sbDDvD$0mKI~X&yuu=fJQf7>iuy zM_Sy!xD7-hDL@PmX8f&Nx6%M+m*nX90{$Kr5Y1s{Z?6FU$sCqCpwavL z=Z~R!NK@N{mfPArXa&qRG?&YUQX9^qDBQ%%e+b8NW`*EVZlvXD+`R<_!Tg1s@SDWM zF=lYK;xzw&DcUV!;w;#hqs|$yP9>q!{j>$gD#^VjkJGK@KDs0^?fkgsG7seUMktfa z#+_$xCR}3?udZ0B!`dXvr#+PhoCm54h}CNVub&){?QrbTrpi5+5t@ihiE1T?Usi~orvCKI=>AIp?asw$?dfLp8%CNSWNX>H* zm;N=d+UHkSb1EyjS3C%uYMdnxCYc2GKI|^Ujhq=TP;rrseA*8jl?;C=anbCn=lxyX ziAxOR7$}~`lP6yqT^T)~U@`8AGVTihHQVL5w<69~s~eoLwc9!D4I3R^_5{n6GxUHD z$!Dy~)NjFTsCO6T)8vbE16Q0YaadDXX&KwoR?FzrBKkU7i2RZ-ogI9NRWeU6oMwk| zoSTOiMbBO7O;5xv`$k3mmjJ)!yTJQH7cnKkouKaES8JCe)cvsM5cz6xFc6#Mpddv! zXIAI}fY<>#*8V(}7AQU6fev>zixq>rLs~T@Fu5ayYJ3^GY{Y zGW#X6+|K}3mRj&TqEe-%eo1QJ;^8%z71x2^eauQD$Y*ol-=Ll={96w-ZBqao4KG=E z)v>OG)&G6wwkyZLGct0W=Zzkb`r)GdfNMiAt#y1R{x)5y?LpJ-5CbI?9azQkX{j$} zJ6n!wUHizL#TL883u^U)OI(g$WV!7jyQK=TuGbp_V-*&idj2uFm4DE|b~{6l(P|Jg z!o8=l32h|(YkCHZ=r2$jB@4KxvTAke$()`V_tnYWH4&Y+TLInB5VpMe1p9F9V2c_AS_Ab*wZj(Q}YdXG$zVFrkEZtvve- z7e%jH>FrbJ!VRa&_Xrk^)I@=iUGII@BpZ)c9BL~LNW!vq> z@T`Ap0bsL|OQIb`@1mR=x>3_3RbFALH)!o#WLRSu74V{>vLiHEltl2e=iLJ{{kQ<6 zyz!AV>wTpNOI$B(^x9ekrbp$I67IOk} zx5Fflfu8;?$rk*|YZC2^Mq7Z(qytX=OGu1 zgM&kq<2azBV1MKQ4iRy|in4JodtVR4n6{lucy~~!Z?mUp`%RafQ5SnMl439NHT-EZ z3SY00-B3Pyy1qAkMxi)dUAVh99~enDf(vJS0@c&b)WiMtzJdk`SwXW&j?$n(da$6?pSTU#+TU*3_FNhzE z8_jx-NzJk)mhzKpBokzI++Zf>r;lV|%F|{kqaQD8$5*&9zdpm#MmFO$ z<-PH9Hi&X)`q`^_<79Lq6gS2J-bIYXD-}#!%IekqQuSxMKg;$eC*>=w`(Qm^iCs&_ zD;{Xcc?gFPq=}_)e8{-a?tCtRa`BJ{UhfTIcl#tj#S%nxXMOn2`WT&b4Anr^T^ich zy~(44==SL7!@Y>p1EG?YnLW#U+bcnUO!QaSOi7i^0FiM3M*c9BpVO0t%~*G@iJbLS zgC@bX?QQuo^S&y3`Pr`v2Oht-2F70Ah6&k$x0{Xvm$aOwemwPZj(>yvf%-%O88Pbi z)qlSEzAjPUW`m?;lvt8CD{yhw%c!2F=4+*0qU2<{ogSIcW-j6eQv={d+drTZuxjye z!Y^8IY~e>ulOW?XKuS3(LkVuf&tnrxbVSG9*Sy8yr)S#b`v6OWFdh17$#9at5Bk7W6P`Y-< z(|t)pT>g#ztzGq_pXAAi>mi+yl^YZDrDE-H}T;76$_*36@84v({Yy`EfGCDvRMv#*vasg+P-f9 zz=k8mEUJ@LoxUL*NtOJ$=uy&|xo|)1{Dy zU+n+1_nuKvrE9ln*>0O{q-`TgPzfqIh!O;B6BJRhWXVbn0+O|DBe}>yP)U*{XHaPg z0wOtsNX|->eCJa3-rsl68RwiIcidm6MvtM;R@GYXdfz9`IUmYF4$7~)Z7ihE1|geY z5*|)+zrW6^7~{z_`lfv=K%529HFSjCv|{TL+rKN~)-Jl2WG8g1&TvNgiA-Tar$@xpJ9^xn9drF-G&3t*8$WG_xw%85JM9IDgf6kg~HelFxarMx<`!S?aWaFcawlr}dAbr!Rw6 z{t5H8W3Kaop?6EI*Bi&0V>z{AWS`X>`Fd`>QAX;a)Cb?+a+P@_o~Uif6C(CjK){yI zck1gKiWkGG7Jg@r$u`}ooZ+f7A!@ggTv)(9^-{!m;gx2+jmh;FFVN(~Pm$8aB2aF_Us zED-?7T#XdJdetc0EVzbg<7W`+s1dc5bT>)2L5bvT_xWzqi~ERQ2Bx`OXT6$U`DI3D zJlUzrb#p8(!D%txA&?Sw0wqxq+d96mHlvr4O93|acVM8be)qUy^68D4e!0ZdX0q5C6KY<_ z7HYPur45g>Wt$kv`r|=2C5H}7x{fdAEdHIGyWDlOaqq&qCMl1eu-a%m&}d3Ld&3|yk9&5LZczO^ zOnHg=pLXdU}iC7>AC^HGe&%KqVy`^pH9jWd+U1^J-xN-xfS%gr4v>C;B+JCI_D_ifIg% zm7)l>jwI&_m!_a`B#7AlZt>6C|NKEgq2~89ett$vp$HfGjCmIe`}dp{Xfxwbm)Lz& zl72%hDV)(X!Lln)9-XQ}!&2b)UZLM=|6~h^35QHfI&8bA+#sqF#*eVSNd|i968xrN z=9RzMb}!8u*;Pz-#xl5{(QqjeJc0RGrk%?xu;ltd(fKPDv*~7Po#n&R)0T~HIe%YH z{aM!2{8i_GjqkRdpsUvi&o(F3PA47Y#@8)d4L>^4tQT;;tEN{y)xA=+k(b6!rOI&8d?U5;n}u63s7r58hf`?6;fwLN0Ed7x4lGj<9C} zdw;sKubf85WW5oelB_&m zUWr9bJ!t6Nwp+upd|OIw8HN>4WNnC&eF*|>+{RcxRVJNL$V-Jw$9n!4N2&H^n|8qu{k;m3%}HS^34L?vW;Pw^Yhk@7ZMKp2vom#V8Sv7T;4_jy za?L5MtH7vn$7#M%-PtOIJ>zDZR@t0nq3VEYS6#a<@r6qYqYVlx3r145qm;RoX_(I3 z-kJscB{VF`jkk0Dm#wkF(*Z}$SgDphG;e->PbpruCAyF9xR~5Pn4(G&zs~BnUq~0; z(OuBS4v7|*nl94k6kCiyMSv0>ZrlIio)V)nxrGVIxdbK7AtaT_lwxEbmQa4+sPH)f zeE)M2tBUlFJ!dtJBq&@mD@2wy!LNnneA+&e>VnulHlT85q-btTTah|>=q z2%d|h?XcE+bTx%8_Ve8vwPU{$a!egn6kZasI41TP`P4-s7~PrRb$6OWr>vSZNh4;^RaKG8Z<7=}%p?5aM(@bGV zM!&pI&b7AnPgFf8Ro|3iDjJu~YdCXIT!ryHjOsI9KT}!wt!>-384CBAc2ur{katb{ z*UDcut$P^6$!=o2Av&A#=-*4RSdA=Q$ zI__jtEE(J*tQTTjMt}w@&Yt8bPM_bcC0oMo!S0m&P2VYH&2)y#&wZTh7t##%=9&q_ z#)ZWiFHG=s_7t~9R=hVX)ylUq`ciB^X_^8a%pcQ(cC&Tt64{?XR3=j4?u=d_I*69} z3e`XMa}%BqPB$LI#S<=Gy2Nk%krs7TNsM$;1T!PsxQ<>KbXCO-aIVHd#B0pD)dc=-1BRqQ#4{9(Nluh{W`L&?av6>!}>Kll4Uni7jT)915mzde~u?#Q=|2bVEe*EL@DSr$AXZLzqCNg_Dr zN<%E~mr&#j3<(J-p~Zx=+`C%s9|DcM7LrtBTXpJMw=cT?zFwbE8gvVw{htl~$!OE& z7`=T5zOjE97$KHX0J>5N{wzj?k-8E*JVsw!dqw%SN44ysqfjU{duN;%jojp@sci$+ z=o~`GOIa$#1%i4dcdRQWI=#?y-;eIo$jAr*V{SFcGgd9gK2~L6)3#Y-uF~Byz(zn) z($mv3)Kg|=XRBZL)YZv>hgwtb42?-e=pZXV=RISR4})z{lpoB|v%AIMLiCId%1+c@ zT3)p^77vI`I;}dtu#n5oVH3v5tfSC9W7nK;^(0xL`b`j#C6GuL+-{^x1hR%smcQ*$ zN!owFj$_H~>*s9y5#NB2o1*kW0rSqPuWn#|#qVEl`7|ezw`^HBN;mzp>_L?3kaEya z>TmT)WEK!?zS!XA(92v^|z68ZNaNkrf7i51hYRac<74?9rf+wXMx<;wE*ym;P{wqd^gqR-P3e$oJQ`2Oj_M&UAcI%`qv}Pvhg&7)8x(+Ky^mxT~hKZUl^4 zG$1wyI;t*fS=(GEZ3Ch}AOiD^(T+?640!w=6}<#BLD%pwn%O!hAy@VCWaOQTXuj5i zi!uH9nwC#^`0c@`OW)s84UH_gl!uu#U#g~Wcrp73P}}HX4bik=4UsfG?e`IMM~KTj zq?j0RyH(4mP~IdFxv^2k+DN^pPlb1=p&7;BQaiO=k4OA%Jo8|Z5|2g)!budgKRY?h3WV58Xs?U`!kDO*8D~@z^5VpmkltNZ|M2ir-pl^xBY~Cx%6#-b zwdgfW3sS|%eoID~S_;hckGUZ^{xXWP*i=*W$Fd0W1EYI)$KI2Gws&Ch6$wBu#K ze!k=G6Ct6G#q8PjJ)Tm)_nBY8dT){{SBhqS!?!Jiikmsk4~Q!`@`^B`CZC?)gDQTk^M+W}3KTg; z0YpQKE}AI;W(pj9#kj@8yatbf6!)ifr9aeQhAJ0Q+~RK!1v5k#cy?V%T1aUKvD2T^ z{A#=0+Pz_8FM%gg_M_AA%eBKa4{r~7hTbVHp%Z4I6SK%U%9?GIbe)H9iT<}^%Bd0a z`b#rQQ>yQ7qbEn8V3-UA1t)--+j*tfNu!dpJ@R493jcpHv)XqkfWk$_%aRr2CAmr` zCLTAeS&vaaa66jF;Jtc`Of=;t-JBe}Sz5M{-l%LYq^7doOTh$FrKW7A)hGK}BN@aj zWGBM*A+A^olMJk3a#2rG8to{M42=1md7@CR9juqiGlFI}ZrpegO51Xf*d0Kx^9V4*C(EK@tb`Xw&>YY1zFu)lTm@)bS<0(x*5tjt^6cGbIj;Y;LSNmJ} ztry<*PjV=yS>#M;=IgNxLi#eu)5p6chf}D7_HEIo~s*;cW|#nKii z7?{(9zY`Tq=74a**@`tnUr1PdYaZwCQ+L@2#0eCCJ$}ux<5P?F$ zbjivY^NuqZ-7LBD5T_F85LuXC$`6&k?aly`Nco*W|09G$812|YQH{TUyEPf*UK6x% zUbKfcGuyqi-6HO0y=m3r(*t~BY1iw`2CJn6YeNqWRq2}d;ZUBPq&jAage~=N?id=r zm_yp!!Bo3^MVJ1jJ?KZR#HFS6?a4P2h4d%y3jZ4KNnA4iqJpMB-+zH1lPRbn&SPk& z$y8TqHhmr&zDihl=78Y&7zwuJ^Fg7%>9jM<%jql z(<))76EesNx3=Z;pK^0+rF^|rx3=)DLVYH)yRp+-lShZ{mF&+tvMD{TRAbGMlJm>;CfpppwO> z{ldZ$|N846fu|MA>5n6!xdcC-qHa_eDivJwx3jdTTIx{waJ+12om^Cqf@19;@{?Zo z(xnycs0caJ1FQya(xyuWW}#(ZFXP+LBXtc9DAc$J_J^; z7HJA*9$+)-e4=Zvi-H{@ONSuxBHY|9{Nzo&!-|Kg>)tzdA@#8iWg!RIjuNdMyKbwW zGNn#cZp+o}I9jMmAz-Uv&5l7Gs;1LfA-Ga=s8+}#nhm|03CAzg_-gSehgN|S0aF18 zw+a3ze1T5PtqAS!hT#7G{sSX_ub}p{=IDfpkD-$)yDka2udDyLzI=HtlrOZkqCBiq zGw()D1j~r4ODc1(r5P~hYuB?zCTSl;sO8(l{*O-XZKeI2KX9xbH1`xM41a9+)<0y% zH(V#o(dRbxL6E3rYe^Tpvbj<{+H9X@f~56T;~vt59>KU-VsQ*Rr#180~AnnH+)@78jPW~>=mFuV$>mn(52Hn%o0?-MC8nQ3? z?{UESxB}t|-b4ckNz9?qjp%D&sKg1fQHCpENdTJXV8ExJs89*U+5>NI*Wm{E*`vah zY`;;v@t%TG-H?4{am@X7k^E7;Z4E8)A}lAv$;%@*GY13S>#=kb0qSJ=;JC(UgVF4< zStdmLLiY}A^cE>dLqINRx*)RLQ&Ld1Bi^%%*03wO=19-EXXXM}mAEx5Vq!w}z0r<(Eax1V+jO$1_PmP;;(`8+sS!lnT2|Uscp`_;3NK9UEQ#EU3 znXUgrEVQR%AxL|5{R(qDOLg~`)V&TqpJHJ+ri{ckbCBOnr`ME&W3PLXO2$C;FwQ9aCcA8 z5+2RpNb{TeT=gEzDPna>}}3kTetrH zt@UJoV^F43sOmJ9^D_@#eZ%0n4{-V(RRE&Teav9Lbyg58Q zsv{oDX*eVuWUypex8%_N9gj!^k7!$cUztKMXT!a%L$|*qsdkFAyJ7CPmzJ2yUS~$7 z)SH6YX)W3%qC|d11VMPcG0(gn0~iP>Nj*LN%Q3DUH&?QTZ&iQ$hU{o)&f#tf@hHM{ z+ye{j{AHg}&~a`_4IFDq@2p^EV!5e4aHe>zzf&@Ug#fa-%`aE|rsMtNgGozLvi=L- zzA<&_G7yVvu;!E?$1wWkN`gG&t=c1~VidY4CHf`GIk+bta=#;gk}E7T;&yV6e5O#H z0;mNOGoi%wAs77vkk(TY3)AQPC&BpHyVo8fn{|!uV_?zwGBvX+D)I@}DxsTcsQc9N z=Ua|R{rJYe|C!+8h!#kpbo?{lkmtYmFf)-Imb(+=YsCpw@l= zA-Jus%Sc_ykD`Ox+A=?GJZrG>KE}go=~3_j0k&&Yb#&4?3_Z-6b=fZ7ME{m}`EJG6 zt+MjSJAK!^)g_SNV9B>+p`UOy@!(pHM0RgdiYlXyGWxFgv2qAqq#v)mUjVmQlGls~ zHR#Qg$58<-p{h2=ouSjsrSM|u?q<1qF)}4FFflGys5q8t?EHP#Kv?KpRiq!wld$;4gV(o>hV+~iHKBcg# z1OP!}($zM^p!hx)Xs;2>oE*>Ila`?O?djQ#T|ck&O*4QCGds(C>0O(ob6Pizefwwg z-LC7y!#n17D~+?JFq1(zAlQCWGo|A>y$nC!diM!g@!sk?JwK50#(K0m3YuA}76^Xg z_=6OFZoTJMQ)y0{CMr{u{>~&wEI`Y+6adWbW$8J_dr7 zL@X+WfVnKUjI$-_1{QW%uB>gYj6wN79p0$zy5zKEh!Q1`x@I#$s+6>m?Ao-1mcr}? z|H=DPv@vUpi{qdv5UqF_(I2)7yUfVbiYnqb^A)G%TciCxwS#EnBX3_ZZ>7B+jqVE* z*<|mhi+#_^n$JI9GB4%YygPz=CD0KorLeJ1v~Vpf8mJf~FvWB@*F(-zl{`(M#E9Ch zyn<-%m|9XiXkVv1-s>g3+PvJZU$#5E@q;lqc?H9U5+wHC8dXRT_c>qOT;*|+zi(yx z^3dmV%gzJ6tfN{AAWBHBaAsS7`uWu)8+hQ)r_$3El2mZs6+(H@&{eRqO6_IgVV3U` zdpMq5*&5&*aE!d{w9)F6*H@O~efZ~FFL2^<(#D#p@x6@Q~Tid9eZkAztC^qt~rdsL@hUy$9ppdSEgseCzqB6$2hJyyAfO8 z>)o`X$FoY^SicbKeL%ePdL~HFMz-nYwLa@%GIfg2%4?d=F^>Rku7kut?BtlL-r{?k?!gTIo%9XPfBcepYlx zK}>8Zv#YyH8OKb8T66O5jm;g~wh!hb4LJW_clk;NQruSM6V>@UQ-{+cS_5Ph=ho|{ z*_ipSfP}I<7^Ty6{&{}?mw#QRDvV@N$T_*?#nRX&rOW{7r5QDQDY8s`k72s+(CCgRC)Tp+L#f)Iug|8nE}SB5|i1(jHj z0DFah9ZOoYbA1(Gyl7aSZwTR0sEZs;N=Z<0of{W-x>Y9iw3s$2plQF%w15`nws@#; z#D4b83l)bY>5?`}R?10MI~#X-I~ZEKm< z@&$!S$9gf56G&R8wRzL=b+I2Fe_pLaeDY9s>vX)E>ggzjux`T~H-_~>q2Bs-G7`cc zFDsVcr!J_pqy2(e-e7MCG&pnx`w1lo#v&aVg8i0GtFt&dh^i``|+wg1j8P~v+Gv* zuF-n&T7#9lTfmY%HS=!TWI>ltWU1MIRBNl`uQ$Pn+zd}hq!@jyr_sc9z$IrLuZMWc z9)2-gN*jYh9hrALCkh+jfQRaBJ>Q?-K$>C0U4^M<+et!)1L8{#7ydikZ2*3zQ)@1tQPn;dGACE}3ir(O}Xv_C)k?yC2Z zDJ!k|dWGH+1v9hd#5XG414h=0^9{`h>bWlVQm;q;DzN=&NM_(Ve2+)JLZ3w&aPkMyN8JrD~r%c zHXXI-2M-^bU=jhiP{F8j5jCSLSFRYgZvYjK0gd7K9YJF+hMcq$XovIf59tgnXDSvr z_z$i+u3c9%S;~9y=IU{iFTDfy4&Zf>Y^GcZpYYl2An5DHR9jjbiyUpc?OL_MgdHrF z3!rkBh_Smrt2~eB-(w+YMLE8?|__a-Kk33uyk2<(JH^A%Y{mAV((4T<1EC0z`}Ys#guZ?I2GzDE6ug&qG}{Sqw)eLgc=c06 zyk%POL$zE*IrlEGAD|g6DeD#eyib(++y^-%HX*8h#KE z-oKxhXfC0O;$K+k949XeX`+v33Qo&Fg*J0yy07oW530OZZf-6r34AGw^YepL+6N`U z)UvMnrGnX+Hayu{GovEC>Ii;V8f^1@Q!6+%YsI!SxWRAXJ*x!D>x|e(`L~~khK447 zyH@#?Ysa49{}eE2K(Q{>=FrHgu?heD`Lpy|Yl=oZs(FTLt$5}n?}{H=PLmE#;mY;v zA)?D8jIOKSDmn@s(ysc@r(pQXZU#|J$`XGcRC?kU8Nil6KQsiVUX80qPn(zrHe^gmrZqe&zsP81>M1 zzT)CylvrcfOWh_u2ym+P=Nb6u`}2ECEeM?b`GewX>!?S`R^C8>|9ky`I9<@=X)t2i zGy^1(<5lv{1QVBLb{CMZ&DU8t?0^0J=rPnsf4=VS_O%j!!Gkt2N~bcg5d>BJ1S6wm zhdan5#19ZpP%3t^bN%|3YR}WAZ#K4OXno)AbzMwAdkd_W7=j{>YOD~qdPUV_Y3P~% z#!-8ADz}1#hesaNF=FQpf{1^F-(&>B&-T}{y!|`-GwyuT!(c6mD;@^}!5gM`U$us` zNXsAfLgmm(YleKKa-tF$u`8QuYSG}?RGYQh$#m<$aZBy>+8FBx<6ILCT1#I%xRlbg zMI4=-lTf@;PuF`u_tIhaf`B;mH&rDh9%Fi13gmN>^eeoVA$&5CMV0!tM=V-wW7QU{ zJ&i&A;9S{kkz%dGGNK5EdUtq_{TfC2k-FJ2S8Sb5Zz5Q;dcOSfj@8v z0}VHTHF$*u!|$MoRXB$m8H4(G^C2CVzR#7DT)7oSn-%#iquL{rUHOUtUnvq5d01O2 zaSNS%5zbp*zI-`@!5DTUpWgoAC#0?IErX8S94dCE5UuOTwTvR#azb`v0^BAm)sxnk zHsSFrHGT4nOOM9VO4l23DCeM3XbPd!J>Fd<5U!3vuXU8D)5*K57OW2~wB>!6KbKp< z?8T<(uA5O|XGoYwYT|HLi} zFl3lw@HPJR+CRH&>qN@oIQWv26yv>lEV}a8SMcy>uY$G5o9v7|;dPKhy^Z6lan)pc zl_ct)o){+FCUhqL!?-m;{rT1)#YN-KBEyT1HK_RKvy-_MLl~pL11)|pkWPe7oR6=X zv<~Ibe<+VP?;7uM9HwJDcI=G>k5D+1KJ4GQ$$rV!-tobKHiKAhZTGa`@cu75T6GMJ zlEx*mbe>psgONteH`RZ0cgtCiX65$rjwlL6FW{8?yYF+2a^`#Esxt>2CCOwSb)XW7 z$pz)U$1izeySEFdw;CqR5@?iY^kJ`}{UGx0_N^Qtag-9diRb@94(6`Dz|J)M{n!T^ z=jrY^nWMx!<iiu$&b{E#;!|M_}sSW^2f;!E5OGFem0BRWr15Vz? zCZk)=E~|(({Ag@k94q{g;fH{9@yD`$t^FBReHZD3ZcAW|G8`Vu`UmUJ4hHL>0c+%h z$%@MPHikI;Qu^hd^{AB7?#;L~FqoHR)~*17Xo5&(aQGoaTw>9a8;50dxLmmul%(ly z{sK!i`{6)pX=P!ArfSBxf8)yWuE-+I6)v5U~)yuBDjx4TNE{JmZat}PhTxr*^lxN_00xhbgn_|5j8^#zF%|Q;lb*| z6{>;2N10nm7OMXzPI~fS8sp$#Th~oA0t;@B0{A#??DN~R6I>O8Xmf!T8UZ^x*e8F7 z>zSkAS>lVp$Q!_bEli&~1&x@E^~F-O1@kW|R)}y_#A2|vdeNP90@?zz!iq{TL{gdx zr%T2k`_6Kx=C5~L)Gb;cdVqEDa}dl=HE2BrBj@QvU9Lk5NmAA=T^|Zy z({}eehou;pcl&3KU$`lTm6Rl)ZE*_Ncet`WPpIREujUs!c!#+igY< z8)r=*hUTidN(q;6M9?ONX_lphdza&#J2x@!6*@1)m-G-Pt%iSFTqJBDNP<6+P!K}G zp}7W$=gA|Pj5tQpAn_8t+_rQOVjOxLR<#^jwi_|sC;`t!lejIwfvc!8C%Wga73ds>*2yc3kM0ydYn9|6wFjg|5t5f2- zq7;2~_oMQOr#3SiRnYv1jEsaDkKD8(8m7DE{Cx=I10a~8_aUX-` zxydN}@3w;mrpSDgBC*h2U56RM8LVJ`zPNMmI1i0OB1r*pweB?-tn()DQbC>2r8FKZ zg*Gq27sYqkdo|@&LZh$u@slTHOXL7nEu3vvEPSfH zh$+2*&sQSaQnB!nfs)eR-+sF>H`&?xeLSty6Ow5(Sot#m7!D?Huf1CpU@Vh&2i^sFiRv!sYP;3xCO3_|!s=)eG6i5k!|fpfY$O z>*xn~W{-ErI&1|IPY_6~t9~c0>~eQ^@Ang@=YiNw-{)78rUbpW*eGv?h|$W4jk|Qt zw)maf+Jm)JHsHtjK%6wcv)054b8tKiisN!wD z^#(d7n-xXU>n&i-lY(tA-e?TFJ+pm<5L+E<&y26_p}z3Zyr)PF(AI0Ql7SFM%fg@+ zO>;3Ia@5#^q*HLJ7Pb>r+Y2uU-ILD>&=OAZKPgAJgVFIn|NOJT-wc_FGL9P{sYSG< z!~Sxj2~qCrYS{Sc;*}(eF9_~IK=A`Cr}(_Nm{HAPG<_k=@IPe;Bbv{hI@#zl#w;KZLB#U1JQuIRx3Wsb}kYyKf-MiqxPKt+Vzv>P3Q$GVF39UN+=an+exS9h~?c81NMsvh^~Z70yZHQ*}+(B zur@l1?H2ov9!-Ly;H;kE!eo_e2y5*s=p05Ma-j@&)F`D`^!43NJoq5YB1^>18HWQ< zv9YmvIEg_E35Za};DFC|%aL-2u?nr2BRG$78RIZbtyn8Cp$kTE;?x#4MxuS7lFb2C zL;(Q>(wj+QIqauG>O?XoGRZN)R^|nWu^EB7}I(P$BLx=mpsW zKuG0>y00+(kyX3sj#DRKgHC@AIRVrsDIo!o&HK$mQqbBNsG1|86JO2zIA} zp599graFTu93pFTjg%NTKsM+K0=?M;W${2@rw3JiJ@%h|2?x@7bDGqZiYSy2$ZESP%mkqSF?(dv7EniJ`6^(p8B;4nrFn!+D8 z6A=uch!}t=CgQh&C-ox+H74IyNLR!A*Vx=HG+^n)1ds%z<4G9q zK%Yc)p1fe7Z^Ec+%4c_Vn)IA7k zESylHb|xt!L)hhjfPfenLZVz7nggnaCy;GSkcz2NFSJjA;BwUZGO^OwKc}!GaIJx; zKcZ$87!WYt>8I!%>LlD^|HfZfP<1Xt8 zIs$Dp9Oa%$+~_lho_c&c@4SbnrxJ`lAqRVYa_8NmB|HRBDg5We?14W|Ap5g~30S{m zx&>1~h`SPV{fJ@`k>kPbH%M)lFd7Yo=JRFDD!HEH{(37(fItjoJ4|aAL!Fw?S0W1S zec>L^O@W+XHTS(Wym9}bL-9D*l9aP%ZO^eN=-!o&x+3wMhddi`s0d~X9QlExN5_BM ztti^mxLXKPx4!Guu3_F5^3kB=u3KGaVPIx7%jV_kE_?~q7G zPvcM!8nYPka*a?|!z$)k_c!RIY2>CNe|~u~$^{g^T7M=YMHuTTE=1hIL0+Dz=Hd(R z_p5n(U;{#WFnoOPD=JJU+R|ASVjlO~Lx^eQx@Y*>LvW@VQ!B zwR@)x-z4k1JWQk#mT tan(beta) = q/p + param_b = q/p + cos2Beta = 1.0/(param_b**2.0 + 1.0) + + dFdp = -param_b + dFdq = 1.0 + dFdbeta = -p/cos2Beta + + return dFdp, dFdq, dFdbeta + +def hardening_EDP(param_b, param_m, param_b_f, param_b_i): + # gammaP is the deviatoric plastic strain + # param_b = gammaP/(c + gammaP) * (param_b_f - param_b_i) + param_b_i + # that yields: gammaP/(c + gammaP) = (param_b-param_b_i)/(param_b_f - param_b_i) + # or c/(c + gammaP) = (param_b_f-param_b)/(param_b_f - param_b_i) + # The derivative of the hardening law gives: dparam_bdGammaP = (param_b_f-param_b_i)c/(c + gammaP)**2 + # or dparam_bdGammaP = (param_b_f-param_b)**2/(param_b_f - param_b_i)/c + # Finally dBetadGammaP = cos2Beta*(param_b_f-param_b)**2/(param_b_f - param_b_i)/c + + # Validation against Chen and Abousleiman 2017 + # h = -dFdbeta*dBetadGammaP + # h = p*(param_b_f-param_b)**2/(param_b_f - param_b_i)/c + # y = 1/h = c(param_b_f - param_b_i)p/(p*param_b_f - q)**2.0 that is identical to Chen and Abousleiman 2017 + + cos2Beta = 1.0/(param_b**2+1.0) + return cos2Beta*(param_b_f-param_b)**2/(param_b_f - param_b_i)/param_m + +def compute_q_ep_boudary_EDP(sh,sv,param_b_i): + p0,q0 = epwAnal.invariants(sh,sh,sv) + return param_b_i*p0 + +def compute_param_b(frictionAngle): + sin_frictionAngle = np.sin(frictionAngle) + return 6.0*sin_frictionAngle/(3.0-sin_frictionAngle) + +def EDP(a0_a_ratio, sh, sv, nu, a0, G, initialFrictionAngle, finalFrictionAngle, param_m): + a = a0/a0_a_ratio + xi_well = 1.0 - a0/a # the auxiliary variable xi at the wellbore, xi = (a-a0)/a = 1-1/(a/a0) + nPoints = 1000 + + initialFrictionAngle *= np.pi/180.0 # converted to rad + finalFrictionAngle *= np.pi/180.0 # converted to rad + + param_b_i = compute_param_b(initialFrictionAngle) + param_b_f = compute_param_b(finalFrictionAngle) + + # ELastic trial + pw = 2.0*G*xi_well + sh + r_e,sr_e,s0_e,sz_e = epwAnal.solution_elastic(sh,sv,1.0,pw) + p_e, q_e = epwAnal.invariants(sr_e[0],s0_e[0],sz_e[0]) + + if ((q_e/p_e)`_, `centos7.7.1908-clang9.0.0 `_, `centos7.6.1810-gcc8.3.1-cuda10.1.243 `_ etc.) obviously reflect the OS and the compiler flavour used. -For each image, the unique ``${TRAVIS_PULL_REQUEST}-${TRAVIS_BUILD_NUMBER}`` tag is used so we can connect the related code source in a rather convenient way. +For each image, the unique tag ``${PULL_REQUEST_NUMBER}-${BUILD_NUMBER}`` (defined as ``${{ github.event.number }}-${{ github.run_number }}`` in github actions) is used so we can connect the related code source in a rather convenient way. Each docker contains the ``org.opencontainers.image.created`` and ``org.opencontainers.image.revision`` labels to provide additional information. -For the OSX builds, we construct a tarball of the TPLs and save them in a remote cloud storage. -There is currently only one mac osx tested environment (xcode 11.2) and the same ``${TRAVIS_PULL_REQUEST}-${TRAVIS_BUILD_NUMBER}`` pattern is used as an identifier for the build. -An important counterpart to using a tarball and not a docker image is that the tarball does not provide the whole system the precompiled binaries rely on. -Problems may arise since we use the rolling release `Homebrew `_ (to install open-mpi in particular). -To circumvent this potential issue, the brew version is fixed to a specific commit (see BREW_HASH variable in `third party's .travis.yml `_) -and stored as a metainformation of the tarball blob inside the cloud storage. -It is therefore possible for GEOS to recover this informatiom and build against the same revision of brew packages. -Note that the ``TRAVIS_PULL_REQUEST``, ``TRAVIS_BUILD_NUMBER`` and ``TRAVIS_COMMIT`` are also stored as metainformation in the same way -(have a look at the OSX build section of `GEOS's .travis.yml `_ to see how to retrieve these informations). - -There thus is only one unique identifier for both dockers and mac osx builds for one TPL code base. -It is necessary to define the global environment ``GEOSX_TPL_TAG`` (`e.g.` something like ``82-254``) to build against one selected version of the TPL. +It is necessary to define the global environment ``GEOSX_TPL_TAG`` (`e.g.` something like ``235-52``) in the `.github/workflows/ci_tests.yml `_ file, to build against one selected version of the TPL. It must be mentioned that one and only one version of the compiled TPL tarball is stored per pull request (older ones are removed automatically). Therefore, a client building against a work in progress PR may experience a 404 error sooner or later. diff --git a/src/docs/sphinx/buildGuide/Prerequisites.rst b/src/docs/sphinx/buildGuide/Prerequisites.rst index e8e7f25822f..78251f997d5 100644 --- a/src/docs/sphinx/buildGuide/Prerequisites.rst +++ b/src/docs/sphinx/buildGuide/Prerequisites.rst @@ -12,7 +12,7 @@ Minimal requirements: - `CMake `_ build system generator (3.17+). - build tools (`GNU make `_ or `ninja `_ on Linux, XCode on MacOS). -- a C++ compiler with full c++14 standard support (`gcc `_ 8.3+ or `clang `_ 8.0+ are recommended). +- a C++ compiler with full c++17 standard support (`gcc `_ 8.3+ or `clang `_ 10.0+ are recommended). - `python `_ (2.7+ or 3.6+). - :code:`zlib`, :code:`blas` and :code:`lapack` libraries - any compatible MPI runtime and compilers (if building with MPI) diff --git a/src/docs/sphinx/developerGuide/Contributing/CodeStyle.rst b/src/docs/sphinx/developerGuide/Contributing/CodeStyle.rst index 0bbc547f44e..8d79b34dc5a 100644 --- a/src/docs/sphinx/developerGuide/Contributing/CodeStyle.rst +++ b/src/docs/sphinx/developerGuide/Contributing/CodeStyle.rst @@ -4,7 +4,7 @@ Code style Introduction ============ -GEOS is written in standard c++14. In general, target platforms are: +GEOS is written in standard c++17. In general, target platforms are: - Linux - Mac OS X diff --git a/src/docs/sphinx/developerGuide/Contributing/Dockerfile-remote-dev.example b/src/docs/sphinx/developerGuide/Contributing/Dockerfile-remote-dev.example index 4ea993c4790..15e46a7cf90 100644 --- a/src/docs/sphinx/developerGuide/Contributing/Dockerfile-remote-dev.example +++ b/src/docs/sphinx/developerGuide/Contributing/Dockerfile-remote-dev.example @@ -17,9 +17,9 @@ RUN ln -fs /usr/share/zoneinfo/America/Los_Angeles /etc/localtime && \ dpkg-reconfigure -f noninteractive tzdata # You will need cmake to build GEOSX. -ARG CMAKE_VERSION=3.16.8 +ARG CMAKE_VERSION=3.23.3 RUN apt-get install -y --no-install-recommends curl ca-certificates && \ - curl -fsSL https://cmake.org/files/v${CMAKE_VERSION%.[0-9]*}/cmake-${CMAKE_VERSION}-Linux-x86_64.tar.gz | tar --directory=/usr/local --strip-components=1 -xzf - && \ + curl -fsSL https://cmake.org/files/v${CMAKE_VERSION%.[0-9]*}/cmake-${CMAKE_VERSION}-linux-x86_64.tar.gz | tar --directory=/usr/local --strip-components=1 -xzf - && \ apt-get purge --auto-remove -y curl ca-certificates RUN apt-get autoremove -y @@ -33,7 +33,7 @@ RUN echo "PermitRootLogin prohibit-password" >> /etc/ssh/sshd_config RUN echo "PermitUserEnvironment yes" >> /etc/ssh/sshd_config RUN mkdir -p -m 700 /root/.ssh # Put your own public key here! -RUN echo "ssh-rsa [#a public ssh key]" > /root/.ssh/authorized_keys +RUN echo "ssh-rsa AAAAB... your public ssh key here ...EinP5Q== somebody@somewhere.org" > /root/.ssh/authorized_keys # Some important variables are provided through the environment. # You need to explicitly tell sshd to forward them. diff --git a/src/docs/sphinx/developerGuide/Contributing/GitWorkflow.rst b/src/docs/sphinx/developerGuide/Contributing/GitWorkflow.rst index 9d9835c5e7f..ffc7eb48097 100644 --- a/src/docs/sphinx/developerGuide/Contributing/GitWorkflow.rst +++ b/src/docs/sphinx/developerGuide/Contributing/GitWorkflow.rst @@ -402,7 +402,7 @@ Resolving Submodule Changes in Primary Branch PRs When you conduct work on a submodule during work on a primary GEOS branch with an open PR, the merging procedure requires that the submodule referenced by the GEOS PR branch be consistent with the submodule in the main branch of the project. -This is checked and enforced via TravisCI. +This is checked and enforced via our CI. Thus, in order to merge a PR that includes modifications to submodules, the various PRs for each repository should be staged and finalized, to the point they are all ready to be merged, diff --git a/src/docs/sphinx/developerGuide/Contributing/InstallWin.rst b/src/docs/sphinx/developerGuide/Contributing/InstallWin.rst index 17564169739..6fa4b1d6d55 100644 --- a/src/docs/sphinx/developerGuide/Contributing/InstallWin.rst +++ b/src/docs/sphinx/developerGuide/Contributing/InstallWin.rst @@ -81,13 +81,13 @@ There are two things you may have noticed reading through the ``Dockerfile`` : .. code:: shell - PS> $env:VERSION='212-910' + PS> $env:VERSION='224-965' PS> $env:IMG='ubuntu20.04-gcc9' PS> $env:REMOTE_DEV_IMG="remote-dev-${env:IMG}" Please note the preposition of ``env:`` in the windows formalisme. The ``${ORG}`` variable will be hard-coded as ``geosx``. The last variable will be use -as the image name. ``212-910`` refers to a specific version of the TPLs which may not be up to date. Please refer to :ref:`Continuous_Integration_process` for further info. +as the image name. ``224-965`` refers to a specific version of the TPLs which may not be up to date. Please refer to :ref:`Continuous_Integration_process` for further info. - You'll need to generate a ssh-key to be able to access the container without the need for defining a password. This can be done from the *PowerShell*, @@ -110,7 +110,7 @@ The preliminary tasks are now done. Let us build the image that will be containe .. code:: shell PS> cd [path-to-dockerfile-folder]/ - PS > docker build --build-arg ORG=geosx --build-arg IMG=${env:IMG} --build-arg VERSION=${env:VERSION} -t ${env:REMOTE_DEV_IMG}:${env:VERSION} -f Dockerfile + PS > docker build --build-arg ORG=geosx --build-arg IMG=${env:IMG} --build-arg VERSION=${env:VERSION} -t ${env:REMOTE_DEV_IMG}:${env:VERSION} -f Dockerfile . As described above, we are passing our environment variables in the building stage, which offer the flexibility of changing the version or image by a simple redefinition. A log updating or pulling the different layers should be displayed afterwards and on the last line the *image id*. We can check that the image is created using ``PowerShell`` CLI: @@ -145,7 +145,7 @@ Coming back to our ``PowerShell`` terminal, we can check that our container is r PS > docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES - 1efffac66c4c remote-dev-ubuntu20.04-gcc9:212-910 "/usr/sbin/sshd -D" Less than a second ago Up 18 seconds 0.0.0.0:64000->22/tcp, :::64000->22/tcp remote-dev-ubuntu20.04-gcc9-212-910 + 1efffac66c4c remote-dev-ubuntu20.04-gcc9:224-965 "/usr/sbin/sshd -D" Less than a second ago Up 18 seconds 0.0.0.0:64000->22/tcp, :::64000->22/tcp remote-dev-ubuntu20.04-gcc9-224-965 PS > ssh root@localhost -p 64000 Enter passphrase for key 'C:\***********/.ssh/id_rsa': diff --git a/src/docs/sphinx/developerGuide/Contributing/UsingDocker.rst b/src/docs/sphinx/developerGuide/Contributing/UsingDocker.rst index 626f9dd23b6..e6b53ecbbce 100644 --- a/src/docs/sphinx/developerGuide/Contributing/UsingDocker.rst +++ b/src/docs/sphinx/developerGuide/Contributing/UsingDocker.rst @@ -15,7 +15,7 @@ There are multiple options to use the exposed docker images. - It is also possible to develop directly in the cloud using `GitHub codespaces `_. This product will let you buy a machine in the cloud with an environment already configured to build and run ``geos``. To make it work, create a branch and/or fork the repository, then select the version of the TPL you need by replacing the `DEFINE_ME` token in this `Dockerfile `_ before creating your ``codespace`` instance. - (You may most probably stick to the ``GEOSX_TPL_TAG`` of the ``.travis.yml`` file). + (You may most probably stick to the ``GEOSX_TPL_TAG`` of the ``.github/workflows/ci_tests.yml`` file). *Then clone the submodules*. You do not need to run the ``scripts/config-build.py`` scripts since ``cmake`` and ``vscode`` are already configured. Last, run ``cmake`` through the ``vscode`` interface and start hacking! @@ -30,7 +30,7 @@ Once you've installed docker, you must select from our `docker registry `_ to see what matches your needs. - There may be risks of kernel inconsistency between the container and the host, but if you have relatively modern systems (and/or if you do not interact directly with the kernel like ``perf``) it should be fine. -- You may have noticed that our docker containers are tagged like ``155-669``. Please refer to :ref:`Continuous_Integration_process` for further information. +- You may have noticed that our docker containers are tagged like ``224-965``. Please refer to :ref:`Continuous_Integration_process` for further information. Now that you've selected your target environment, you must be aware that just running a TPL docker image is not enough to let you develop. You'll have to add extra tools. @@ -38,11 +38,11 @@ You'll have to add extra tools. The following `example` is for our ``ubuntu`` flavors. You'll notice the arguments ``IMG``, ``VERSION``, ``ORG``. While surely overkill for most cases, if you develop in GEOS on a regular basis you'll appreciate being able to switch containers easily. -For example, simply create the image ``remote-dev-ubuntu20.04-gcc9:212-910`` by running +For example, simply create the image ``remote-dev-ubuntu20.04-gcc9:224-965`` by running .. code-block:: console - export VERSION=212-910 + export VERSION=224-965 export IMG=ubuntu20.04-gcc9 export REMOTE_DEV_IMG=remote-dev-${IMG} docker build --build-arg ORG=geosx --build-arg IMG=${IMG} --build-arg VERSION=${VERSION} -t ${REMOTE_DEV_IMG}:${VERSION} -f /path/to/Dockerfile . @@ -59,7 +59,7 @@ I like to do docker run --cap-add=SYS_PTRACE -d --name ${REMOTE_DEV_IMG}-${VERSION} -p 64000:22 -p 11111:11111 -p 64010-64020:64010-64020 ${REMOTE_DEV_IMG}:${VERSION} -that creates the container ``remote-dev-ubuntu20.04-gcc9-212-910``, running instance of ``remote-dev-ubuntu20.04-gcc9:212-910``. +that creates the container ``remote-dev-ubuntu20.04-gcc9-224-965``, running instance of ``remote-dev-ubuntu20.04-gcc9:224-965``. - Note that you'll have to access your remote development instance though port ``64000`` (forwarded to standard port ``22`` by docker). - Additional port ``11111`` and ports from ``64010`` to ``64020`` will be open if you need them (remote `paraview connection `_ , multiple instances of `gdbserver `_, ...). diff --git a/src/docs/sphinx/requirements.txt b/src/docs/sphinx/requirements.txt index 37435817287..20ebcb80095 100644 --- a/src/docs/sphinx/requirements.txt +++ b/src/docs/sphinx/requirements.txt @@ -4,6 +4,8 @@ numpy h5py mpmath docutils<0.18 +pandas # using plantuml for diagrams sphinxcontrib-plantuml sphinx-argparse +pydata-sphinx-theme diff --git a/src/main/main.cpp b/src/main/main.cpp index e2108c1949f..9074d88f24b 100644 --- a/src/main/main.cpp +++ b/src/main/main.cpp @@ -20,8 +20,6 @@ #include "mainInterface/ProblemManager.hpp" #include "mainInterface/GeosxState.hpp" -// System includes -#include using namespace geos; @@ -30,7 +28,7 @@ int main( int argc, char *argv[] ) { try { - std::chrono::system_clock::time_point const startTime = std::chrono::system_clock::now(); + std::chrono::system_clock::time_point startTime = std::chrono::system_clock::now(); std::unique_ptr< CommandLineOptions > commandLineOptions = basicSetup( argc, argv, true ); @@ -55,8 +53,8 @@ int main( int argc, char *argv[] ) basicCleanup(); - std::chrono::system_clock::time_point const endTime = std::chrono::system_clock::now(); - std::chrono::system_clock::duration const totalTime = endTime - startTime; + std::chrono::system_clock::time_point endTime = std::chrono::system_clock::now(); + std::chrono::system_clock::duration totalTime = endTime - startTime; GEOS_LOG_RANK_0( GEOS_FMT( "Finished at {:%Y-%m-%d %H:%M:%S}", endTime ) ); GEOS_LOG_RANK_0( GEOS_FMT( "total time {:%H:%M:%S}", totalTime ) ); diff --git a/src/pygeosx/pygeosx.cpp b/src/pygeosx/pygeosx.cpp index c4d1990f4e1..3210a2365ea 100644 --- a/src/pygeosx/pygeosx.cpp +++ b/src/pygeosx/pygeosx.cpp @@ -232,6 +232,27 @@ PyObject * run( PyObject * self, PyObject * args ) noexcept return PyLong_FromLong( static_cast< int >( g_state->getState() ) ); } +static constexpr char const * getStateDocString = + "getState()\n" + "--\n\n" + "_______\n" + "int\n" + " Return the state of GEOS. 0 : UNINITIALIZED, 1 : INITIALIZED, 2 : READY_TO_RUN, 3 : COMPLETED"; + +PyObject * getState( PyObject * self, PyObject * args ) noexcept +{ + GEOS_UNUSED_VAR( self, args ); + + if( g_state == nullptr ) + { + return PyLong_FromLong( 0 ); + } + else + { + return PyLong_FromLong( static_cast< int >( g_state->getState() ) ); + } +} + static constexpr char const * finalizeDocString = "_finalize()\n" "--\n\n" @@ -327,6 +348,7 @@ static PyMethodDef pygeosxFuncs[] = { { "reinit", geos::reinit, METH_VARARGS, geos::reinitDocString }, { "apply_initial_conditions", geos::applyInitialConditions, METH_NOARGS, geos::applyInitialConditionsDocString }, { "run", geos::run, METH_NOARGS, geos::runDocString }, + { "getState", geos::getState, METH_NOARGS, geos::getStateDocString }, { "_finalize", geos::finalize, METH_NOARGS, geos::finalizeDocString }, { nullptr, nullptr, 0, nullptr } /* Sentinel */ }; From b3ffe3a841e6d383680a756078918725d06d0d0b Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Fri, 15 Sep 2023 10:27:26 +0200 Subject: [PATCH 78/85] merge mistake fix --- src/coreComponents/mainInterface/ProblemManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreComponents/mainInterface/ProblemManager.cpp b/src/coreComponents/mainInterface/ProblemManager.cpp index aa86da3f065..6a022fa1a2d 100644 --- a/src/coreComponents/mainInterface/ProblemManager.cpp +++ b/src/coreComponents/mainInterface/ProblemManager.cpp @@ -440,7 +440,7 @@ void ProblemManager::parseXMLDocument( xmlWrapper::xmlDocument & xmlDocument ) { ElementRegionManager & elementManager = meshLevel.getElemManager(); Group * newRegion = elementManager.createChild( regionNode.name(), regionName ); - newRegion->processInputFileRecursive( xmlDocument, xmlDocument, regionNode ); + newRegion->processInputFileRecursive( xmlDocument, regionNode ); } ); } catch( InputError const & e ) From c85cc911202757ef825a86836d6d32adf474c048 Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Fri, 15 Sep 2023 13:40:35 +0200 Subject: [PATCH 79/85] GroupContext::toString(): showing file-name instead of file-path --- src/coreComponents/dataRepository/GroupContext.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/coreComponents/dataRepository/GroupContext.cpp b/src/coreComponents/dataRepository/GroupContext.cpp index a3d52b269b0..3f63d2d92aa 100644 --- a/src/coreComponents/dataRepository/GroupContext.cpp +++ b/src/coreComponents/dataRepository/GroupContext.cpp @@ -46,7 +46,8 @@ string GroupContext::toString() const for( auto info = parentsInfo.rbegin(); info != parentsInfo.rend(); ++info ) { path << ( std::prev( info.base() ) == lastFileInfo ? // Is `info` pointing to the last file info? - GEOS_FMT( "/{}({},l.{})", info->m_targetName, info->m_filePath, info->m_line ) : + GEOS_FMT( "/{}({},l.{})", + info->m_targetName, splitPath( info->m_filePath ).second, info->m_line ) : GEOS_FMT( "/{}", info->m_targetName )); } return path.str(); From fa57ee3cad1489f97ed0b56ab24f093f97bb982c Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Fri, 15 Sep 2023 16:25:55 +0200 Subject: [PATCH 80/85] Show InternalWell name instead of region name + get type name by using catalogName() method --- src/coreComponents/mesh/WellElementRegion.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/coreComponents/mesh/WellElementRegion.cpp b/src/coreComponents/mesh/WellElementRegion.cpp index 08d0fbe4f4a..154c86a672a 100644 --- a/src/coreComponents/mesh/WellElementRegion.cpp +++ b/src/coreComponents/mesh/WellElementRegion.cpp @@ -20,6 +20,7 @@ #include "common/MpiWrapper.hpp" #include "mesh/WellElementSubRegion.hpp" +#include "mesh/generators/InternalWellGenerator.hpp" namespace geos { @@ -64,7 +65,8 @@ void WellElementRegion::generateWell( MeshLevel & mesh, globalIndex const matchedPerforations = MpiWrapper::sum( perforationData->size() ); GEOS_THROW_IF( matchedPerforations != numPerforationsGlobal, - "Invalid mapping perforation-to-element in InternalWell " << lineBlock.getDataContext() << "." << + "Invalid mapping perforation-to-element in "<< + InternalWellGenerator::catalogName() << " " << getWellGeneratorName() << "." << " This happens when GEOSX cannot match a perforation with a reservoir element." << " There are two common reasons for this error:\n" << " 1- The most common reason for this error is that a perforation is on a section of " << From 642d444fd3d0ee778ef0c39937478bcd0d762572 Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Fri, 15 Sep 2023 16:55:25 +0200 Subject: [PATCH 81/85] InternalWell error message refactoring --- .../mesh/generators/InternalWellGenerator.cpp | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/coreComponents/mesh/generators/InternalWellGenerator.cpp b/src/coreComponents/mesh/generators/InternalWellGenerator.cpp index 8ac1ff49723..83531b8cf63 100644 --- a/src/coreComponents/mesh/generators/InternalWellGenerator.cpp +++ b/src/coreComponents/mesh/generators/InternalWellGenerator.cpp @@ -370,15 +370,13 @@ void InternalWellGenerator::connectPerforationsToWellElements() real64 const wellLength = m_nodeDistFromHead[m_elemToNodesMap[iwelemBottom][LineBlockABC::NodeLocation::BOTTOM]]; GEOS_THROW_IF( m_perfDistFromHead[iperf] > wellLength, - GEOS_FMT( "{}: Distance from well perforation to head ({} = {}) is larger than well" - " polyline length ({})\n \n You should check the following values:" - "\n 1 - {}\n 2 - {}, Z values", - perf.getWrapperDataContext( Perforation::viewKeyStruct::distanceFromHeadString() ), - Perforation::viewKeyStruct::distanceFromHeadString(), - m_perfDistFromHead[iperf], wellLength, - perf.getWrapperDataContext( Perforation::viewKeyStruct::distanceFromHeadString() ), - WellControls::viewKeyStruct::refElevString(), m_wellControlsName, - getWrapperDataContext( viewKeyStruct::polylineNodeCoordsString() ) ), + perf.getWrapperDataContext( Perforation::viewKeyStruct::distanceFromHeadString() ) << + ": Distance from well perforation to head (" << + Perforation::viewKeyStruct::distanceFromHeadString() << " = " << m_perfDistFromHead[iperf] + ") is larger than well polyline length (" << wellLength << + ")\n \n You should check the following values:" << + "\n 1 - " << perf.getWrapperDataContext( Perforation::viewKeyStruct::distanceFromHeadString() ) << + "\n 2 - " << getWrapperDataContext( viewKeyStruct::polylineNodeCoordsString() ) << ", Z values", InputError ); // start binary search From 37103eabea6619af487131458419aa4bef546532 Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Mon, 18 Sep 2023 12:11:40 +0200 Subject: [PATCH 82/85] error message factorization --- .../mesh/generators/CellBlockUtilities.cpp | 58 +++++++++---------- .../mesh/generators/PrismUtilities.hpp | 18 +++--- 2 files changed, 36 insertions(+), 40 deletions(-) diff --git a/src/coreComponents/mesh/generators/CellBlockUtilities.cpp b/src/coreComponents/mesh/generators/CellBlockUtilities.cpp index 0f1c720d01c..866fd55e489 100644 --- a/src/coreComponents/mesh/generators/CellBlockUtilities.cpp +++ b/src/coreComponents/mesh/generators/CellBlockUtilities.cpp @@ -23,12 +23,24 @@ namespace geos { + +/** + * @brief error message to output when the number the number of node is insufficient for a given + * face of a primitive. + */ +constexpr std::string_view nodeCountError = "Not enough nodes for {} element (face index = {})."; +/** + * @brief error message to output when the number the number of node is insufficient for a given + * face of a primitive. + */ +constexpr std::string_view faceIndexError = "Local face index out of range for {} element: face index = {}."; + + static localIndex getFaceNodesHex( localIndex const faceNum, arraySlice1d< localIndex const, cells::NODE_MAP_USD-1 > const & elemNodes, Span< localIndex > const faceNodes ) { - GEOS_ERROR_IF_LT_MSG( faceNodes.size(), 4, - "Invalid number of nodes for face (face index = " << faceNum << ")." ); + GEOS_ERROR_IF_LT_MSG( faceNodes.size(), 4, GEOS_FMT( nodeCountError, "Hexahedron", faceNum ) ); switch( faceNum ) { case 0: @@ -81,7 +93,7 @@ static localIndex getFaceNodesHex( localIndex const faceNum, } default: { - GEOS_ERROR( "Invalid local face index for element of type Hexahedron: " << faceNum ); + GEOS_ERROR( GEOS_FMT( faceIndexError, "Hexahedron", faceNum ) ); } } return 4; @@ -95,8 +107,7 @@ static localIndex getFaceNodesWedge( localIndex const faceNum, { case 0: { - GEOS_ERROR_IF_LT_MSG( faceNodes.size(), 4, - "Invalid number of nodes for face (face index = " << faceNum << ")." ); + GEOS_ERROR_IF_LT_MSG( faceNodes.size(), 4, GEOS_FMT( nodeCountError, "Wedge", faceNum ) ); faceNodes[0] = elemNodes[0]; faceNodes[1] = elemNodes[1]; faceNodes[2] = elemNodes[5]; @@ -105,8 +116,7 @@ static localIndex getFaceNodesWedge( localIndex const faceNum, } case 1: { - GEOS_ERROR_IF_LT_MSG( faceNodes.size(), 4, - "Invalid number of nodes for face (face index = " << faceNum << ")." ); + GEOS_ERROR_IF_LT_MSG( faceNodes.size(), 4, GEOS_FMT( nodeCountError, "Wedge", faceNum ) ); faceNodes[0] = elemNodes[0]; faceNodes[1] = elemNodes[2]; faceNodes[2] = elemNodes[3]; @@ -115,8 +125,7 @@ static localIndex getFaceNodesWedge( localIndex const faceNum, } case 2: { - GEOS_ERROR_IF_LT_MSG( faceNodes.size(), 3, - "Invalid number of nodes for face (face index = " << faceNum << ")." ); + GEOS_ERROR_IF_LT_MSG( faceNodes.size(), 3, GEOS_FMT( nodeCountError, "Wedge", faceNum ) ); faceNodes[0] = elemNodes[0]; faceNodes[1] = elemNodes[4]; faceNodes[2] = elemNodes[2]; @@ -124,8 +133,7 @@ static localIndex getFaceNodesWedge( localIndex const faceNum, } case 3: { - GEOS_ERROR_IF_LT_MSG( faceNodes.size(), 3, - "Invalid number of nodes for face (face index = " << faceNum << ")." ); + GEOS_ERROR_IF_LT_MSG( faceNodes.size(), 3, GEOS_FMT( nodeCountError, "Wedge", faceNum ) ); faceNodes[0] = elemNodes[1]; faceNodes[1] = elemNodes[3]; faceNodes[2] = elemNodes[5]; @@ -133,8 +141,7 @@ static localIndex getFaceNodesWedge( localIndex const faceNum, } case 4: { - GEOS_ERROR_IF_LT_MSG( faceNodes.size(), 4, - "Invalid number of nodes for face (face index = " << faceNum << ")." ); + GEOS_ERROR_IF_LT_MSG( faceNodes.size(), 4, GEOS_FMT( nodeCountError, "Wedge", faceNum ) ); faceNodes[0] = elemNodes[2]; faceNodes[1] = elemNodes[4]; faceNodes[2] = elemNodes[5]; @@ -143,8 +150,7 @@ static localIndex getFaceNodesWedge( localIndex const faceNum, } default: { - GEOS_ERROR( "Invalid local face index for element of type Wedge: " << faceNum ); - return 0; + GEOS_ERROR( GEOS_FMT( faceIndexError, "Wedge", faceNum ) ); } } } @@ -153,8 +159,7 @@ static localIndex getFaceNodesTet( localIndex const faceNum, arraySlice1d< localIndex const, cells::NODE_MAP_USD-1 > const & elemNodes, Span< localIndex > const faceNodes ) { - GEOS_ERROR_IF_LT_MSG( faceNodes.size(), 3, - "Invalid number of nodes for face (face index = " << faceNum << ")." ); + GEOS_ERROR_IF_LT_MSG( faceNodes.size(), 3, GEOS_FMT( nodeCountError, "Tetrahedron", faceNum ) ); switch( faceNum ) { case 0: @@ -187,7 +192,7 @@ static localIndex getFaceNodesTet( localIndex const faceNum, } default: { - GEOS_ERROR( "Invalid local face index for element of type Tetrahedron: " << faceNum ); + GEOS_ERROR( GEOS_FMT( faceIndexError, "Tetrahedron", faceNum ) ); } } return 3; @@ -201,8 +206,7 @@ static localIndex getFaceNodesPyramid( localIndex const faceNum, { case 0: { - GEOS_ERROR_IF_LT_MSG( faceNodes.size(), 3, - "Invalid number of nodes for face (face index = " << faceNum << ")." ); + GEOS_ERROR_IF_LT_MSG( faceNodes.size(), 3, GEOS_FMT( nodeCountError, "Pyramid", faceNum ) ); faceNodes[0] = elemNodes[0]; faceNodes[1] = elemNodes[1]; faceNodes[2] = elemNodes[4]; @@ -210,8 +214,7 @@ static localIndex getFaceNodesPyramid( localIndex const faceNum, } case 1: { - GEOS_ERROR_IF_LT_MSG( faceNodes.size(), 4, - "Invalid number of nodes for face (face index = " << faceNum << ")." ); + GEOS_ERROR_IF_LT_MSG( faceNodes.size(), 4, GEOS_FMT( nodeCountError, "Pyramid", faceNum ) ); faceNodes[0] = elemNodes[0]; faceNodes[1] = elemNodes[2]; faceNodes[2] = elemNodes[3]; @@ -220,8 +223,7 @@ static localIndex getFaceNodesPyramid( localIndex const faceNum, } case 2: { - GEOS_ERROR_IF_LT_MSG( faceNodes.size(), 3, - "Invalid number of nodes for face (face index = " << faceNum << ")." ); + GEOS_ERROR_IF_LT_MSG( faceNodes.size(), 3, GEOS_FMT( nodeCountError, "Pyramid", faceNum ) ); faceNodes[0] = elemNodes[0]; faceNodes[1] = elemNodes[4]; faceNodes[2] = elemNodes[2]; @@ -229,8 +231,7 @@ static localIndex getFaceNodesPyramid( localIndex const faceNum, } case 3: { - GEOS_ERROR_IF_LT_MSG( faceNodes.size(), 3, - "Invalid number of nodes for face (face index = " << faceNum << ")." ); + GEOS_ERROR_IF_LT_MSG( faceNodes.size(), 3, GEOS_FMT( nodeCountError, "Pyramid", faceNum ) ); faceNodes[0] = elemNodes[1]; faceNodes[1] = elemNodes[3]; faceNodes[2] = elemNodes[4]; @@ -238,8 +239,7 @@ static localIndex getFaceNodesPyramid( localIndex const faceNum, } case 4: { - GEOS_ERROR_IF_LT_MSG( faceNodes.size(), 3, - "Invalid number of nodes for face (face index = " << faceNum << ")." ); + GEOS_ERROR_IF_LT_MSG( faceNodes.size(), 3, GEOS_FMT( nodeCountError, "Pyramid", faceNum ) ); faceNodes[0] = elemNodes[2]; faceNodes[1] = elemNodes[4]; faceNodes[2] = elemNodes[3]; @@ -247,7 +247,7 @@ static localIndex getFaceNodesPyramid( localIndex const faceNum, } default: { - GEOS_ERROR( "Invalid local face index for element of type Pyramid: " << faceNum ); + GEOS_ERROR( GEOS_FMT( faceIndexError, "Pyramid", faceNum ) ); return 0; } } diff --git a/src/coreComponents/mesh/generators/PrismUtilities.hpp b/src/coreComponents/mesh/generators/PrismUtilities.hpp index 645e12e828a..89d992ec1f6 100644 --- a/src/coreComponents/mesh/generators/PrismUtilities.hpp +++ b/src/coreComponents/mesh/generators/PrismUtilities.hpp @@ -39,11 +39,11 @@ localIndex getFaceNodesPrism( localIndex const faceNum, { static_assert( N > 4, "Function getFaceNodePrism can be called for a prism with N-sided polygon base where N > 5." ); + static constexpr auto nodeCountError = "Not enough nodes for {} element (face index = {})."; if( faceNum == 0 ) { - GEOS_ERROR_IF_LT_MSG( faceNodes.size(), 4, - "Invalid number of nodes for face (face index = " << faceNum << ")." ); + GEOS_ERROR_IF_LT_MSG( faceNodes.size(), 4, GEOS_FMT( nodeCountError, N, faceNum ) ); faceNodes[0] = elemNodes[0]; faceNodes[1] = elemNodes[1]; faceNodes[2] = elemNodes[N+1]; @@ -52,8 +52,7 @@ localIndex getFaceNodesPrism( localIndex const faceNum, } else if( faceNum == 1 ) { - GEOS_ERROR_IF_LT_MSG( faceNodes.size(), N, - "Invalid number of nodes for face (face index = " << faceNum << ")." ); + GEOS_ERROR_IF_LT_MSG( faceNodes.size(), N, GEOS_FMT( nodeCountError, N, faceNum ) ); faceNodes[0] = elemNodes[0]; for( localIndex i = 1; i < N; ++i ) { @@ -63,8 +62,7 @@ localIndex getFaceNodesPrism( localIndex const faceNum, } else if( faceNum == 2 ) { - GEOS_ERROR_IF_LT_MSG( faceNodes.size(), 4, - "Invalid number of nodes for face (face index = " << faceNum << ")." ); + GEOS_ERROR_IF_LT_MSG( faceNodes.size(), 4, GEOS_FMT( nodeCountError, N, faceNum ) ); faceNodes[0] = elemNodes[0]; faceNodes[1] = elemNodes[N]; faceNodes[2] = elemNodes[N*2-1]; @@ -73,8 +71,7 @@ localIndex getFaceNodesPrism( localIndex const faceNum, } else if( faceNum >= 3 && faceNum <= N ) { - GEOS_ERROR_IF_LT_MSG( faceNodes.size(), 4, - "Invalid number of nodes for face (face index = " << faceNum << ")." ); + GEOS_ERROR_IF_LT_MSG( faceNodes.size(), 4, GEOS_FMT( nodeCountError, N, faceNum ) ); faceNodes[0] = elemNodes[faceNum-2]; faceNodes[1] = elemNodes[faceNum-1]; faceNodes[2] = elemNodes[N+faceNum-1]; @@ -83,8 +80,7 @@ localIndex getFaceNodesPrism( localIndex const faceNum, } else if( faceNum == N + 1 ) { - GEOS_ERROR_IF_LT_MSG( faceNodes.size(), N, - "Invalid number of nodes for face (face index = " << faceNum << ")." ); + GEOS_ERROR_IF_LT_MSG( faceNodes.size(), N, GEOS_FMT( nodeCountError, N, faceNum ) ); for( localIndex i = 0; i < N; ++i ) { faceNodes[i] = elemNodes[i+N]; @@ -93,7 +89,7 @@ localIndex getFaceNodesPrism( localIndex const faceNum, } else { - GEOS_ERROR( GEOS_FMT( "Invalid local face index for element of type Prism{}: {}", N, faceNum ) ); + GEOS_ERROR( GEOS_FMT( "Local face index out of range for Prism{} element: face index = {}.", N, faceNum ) ); return 0; } From 5dc1dbff9db37df6fda61ee006da925efc8a159a Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Mon, 18 Sep 2023 12:15:35 +0200 Subject: [PATCH 83/85] compilation fixes --- src/coreComponents/mesh/generators/CellBlockUtilities.cpp | 1 + src/coreComponents/mesh/generators/InternalWellGenerator.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/coreComponents/mesh/generators/CellBlockUtilities.cpp b/src/coreComponents/mesh/generators/CellBlockUtilities.cpp index 866fd55e489..5eda717b5ee 100644 --- a/src/coreComponents/mesh/generators/CellBlockUtilities.cpp +++ b/src/coreComponents/mesh/generators/CellBlockUtilities.cpp @@ -151,6 +151,7 @@ static localIndex getFaceNodesWedge( localIndex const faceNum, default: { GEOS_ERROR( GEOS_FMT( faceIndexError, "Wedge", faceNum ) ); + return 0; } } } diff --git a/src/coreComponents/mesh/generators/InternalWellGenerator.cpp b/src/coreComponents/mesh/generators/InternalWellGenerator.cpp index 83531b8cf63..3ff6dc8e4b9 100644 --- a/src/coreComponents/mesh/generators/InternalWellGenerator.cpp +++ b/src/coreComponents/mesh/generators/InternalWellGenerator.cpp @@ -372,7 +372,7 @@ void InternalWellGenerator::connectPerforationsToWellElements() GEOS_THROW_IF( m_perfDistFromHead[iperf] > wellLength, perf.getWrapperDataContext( Perforation::viewKeyStruct::distanceFromHeadString() ) << ": Distance from well perforation to head (" << - Perforation::viewKeyStruct::distanceFromHeadString() << " = " << m_perfDistFromHead[iperf] + Perforation::viewKeyStruct::distanceFromHeadString() << " = " << m_perfDistFromHead[iperf] << ") is larger than well polyline length (" << wellLength << ")\n \n You should check the following values:" << "\n 1 - " << perf.getWrapperDataContext( Perforation::viewKeyStruct::distanceFromHeadString() ) << From 66528548ea09ef7b2fbeebf002652460be9df5af Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Wed, 25 Oct 2023 14:20:11 +0200 Subject: [PATCH 84/85] A few more message edits --- src/coreComponents/mesh/ElementType.hpp | 2 ++ .../mesh/generators/CellBlockUtilities.cpp | 6 ++-- .../mesh/generators/InternalMeshGenerator.cpp | 13 ++++--- .../mesh/generators/PrismUtilities.hpp | 13 +++---- .../mesh/generators/VTKUtilities.cpp | 35 +++++++++++-------- 5 files changed, 39 insertions(+), 30 deletions(-) diff --git a/src/coreComponents/mesh/ElementType.hpp b/src/coreComponents/mesh/ElementType.hpp index d068756ce53..14586c56d24 100644 --- a/src/coreComponents/mesh/ElementType.hpp +++ b/src/coreComponents/mesh/ElementType.hpp @@ -109,6 +109,8 @@ ENUM_STRINGS( ElementType, "HendecagonalPrism", "Polyhedron" ); +inline auto constexpr generalMeshErrorAdvice = "\nPlease consider checking the validity of your mesh with the `mesh_doctor` GEOS python tools."; + } // namespace geos #endif //GEOS_MESH_ELEMENTTYPE_HPP diff --git a/src/coreComponents/mesh/generators/CellBlockUtilities.cpp b/src/coreComponents/mesh/generators/CellBlockUtilities.cpp index 5eda717b5ee..cbfc2fa0ad2 100644 --- a/src/coreComponents/mesh/generators/CellBlockUtilities.cpp +++ b/src/coreComponents/mesh/generators/CellBlockUtilities.cpp @@ -28,12 +28,12 @@ namespace geos * @brief error message to output when the number the number of node is insufficient for a given * face of a primitive. */ -constexpr std::string_view nodeCountError = "Not enough nodes for {} element (face index = {})."; +static const string nodeCountError = "Not enough nodes for {} element (face index = {}).\n" + string( generalMeshErrorAdvice ); /** * @brief error message to output when the number the number of node is insufficient for a given * face of a primitive. */ -constexpr std::string_view faceIndexError = "Local face index out of range for {} element: face index = {}."; +static const string faceIndexError = "Local face index out of range for {} element: face index = {}.\n" + string( generalMeshErrorAdvice ); static localIndex getFaceNodesHex( localIndex const faceNum, @@ -308,7 +308,7 @@ localIndex getFaceNodes( ElementType const elementType, } default: { - GEOS_ERROR( "Invalid element type " << elementType << " at face index " << faceNumber ); + GEOS_ERROR( "Invalid element type " << elementType << " at face index " << faceNumber << "." << generalMeshErrorAdvice ); } } return 0; diff --git a/src/coreComponents/mesh/generators/InternalMeshGenerator.cpp b/src/coreComponents/mesh/generators/InternalMeshGenerator.cpp index d273fd3ef39..ae2421e6c33 100644 --- a/src/coreComponents/mesh/generators/InternalMeshGenerator.cpp +++ b/src/coreComponents/mesh/generators/InternalMeshGenerator.cpp @@ -140,7 +140,7 @@ void InternalMeshGenerator::postProcessInput() } if( failFlag ) { - GEOS_ERROR( getDataContext() << ": vertex/element mismatch InternalMeshGenerator::ReadXMLPost()" ); + GEOS_ERROR( getDataContext() << ": vertex/element mismatch." << generalMeshErrorAdvice ); } // If specified, check to make sure bias values have the correct length @@ -153,7 +153,7 @@ void InternalMeshGenerator::postProcessInput() } if( failFlag ) { - GEOS_ERROR( getDataContext() << ": element/bias mismatch InternalMeshGenerator::ReadXMLPost()" ); + GEOS_ERROR( getDataContext() << ": element/bias mismatch." << generalMeshErrorAdvice ); } } @@ -168,8 +168,8 @@ void InternalMeshGenerator::postProcessInput() } else { - GEOS_ERROR( getDataContext() << ": InternalMeshGenerator: The number of element types is" - " inconsistent with the number of total cell blocks." ); + GEOS_ERROR( getDataContext() << ": InternalMeshGenerator: The number of element types is inconsistent" << + " with the number of total cell blocks." << generalMeshErrorAdvice ); } } @@ -201,8 +201,7 @@ void InternalMeshGenerator::postProcessInput() } else { - GEOS_ERROR( getDataContext() << ": Incorrect number of regionLayout entries specified in" - " InternalMeshGenerator::ReadXML()" ); + GEOS_ERROR( getDataContext() << ": Incorrect number of regionLayout entries specified." ); } } } @@ -536,7 +535,7 @@ static void getElemToNodesRelationInBox( ElementType const elementType, } default: { - GEOS_ERROR( "InternalMeshGenerator: unsupported element type " << elementType ); + GEOS_ERROR( "InternalMeshGenerator: unsupported element type " << elementType << "." << generalMeshErrorAdvice ); } } } diff --git a/src/coreComponents/mesh/generators/PrismUtilities.hpp b/src/coreComponents/mesh/generators/PrismUtilities.hpp index 89d992ec1f6..2096905e012 100644 --- a/src/coreComponents/mesh/generators/PrismUtilities.hpp +++ b/src/coreComponents/mesh/generators/PrismUtilities.hpp @@ -43,7 +43,7 @@ localIndex getFaceNodesPrism( localIndex const faceNum, if( faceNum == 0 ) { - GEOS_ERROR_IF_LT_MSG( faceNodes.size(), 4, GEOS_FMT( nodeCountError, N, faceNum ) ); + GEOS_ERROR_IF_LT_MSG( faceNodes.size(), 4, GEOS_FMT( nodeCountError, N, faceNum ) << generalMeshErrorAdvice ); faceNodes[0] = elemNodes[0]; faceNodes[1] = elemNodes[1]; faceNodes[2] = elemNodes[N+1]; @@ -52,7 +52,7 @@ localIndex getFaceNodesPrism( localIndex const faceNum, } else if( faceNum == 1 ) { - GEOS_ERROR_IF_LT_MSG( faceNodes.size(), N, GEOS_FMT( nodeCountError, N, faceNum ) ); + GEOS_ERROR_IF_LT_MSG( faceNodes.size(), N, GEOS_FMT( nodeCountError, N, faceNum ) << generalMeshErrorAdvice ); faceNodes[0] = elemNodes[0]; for( localIndex i = 1; i < N; ++i ) { @@ -62,7 +62,7 @@ localIndex getFaceNodesPrism( localIndex const faceNum, } else if( faceNum == 2 ) { - GEOS_ERROR_IF_LT_MSG( faceNodes.size(), 4, GEOS_FMT( nodeCountError, N, faceNum ) ); + GEOS_ERROR_IF_LT_MSG( faceNodes.size(), 4, GEOS_FMT( nodeCountError, N, faceNum ) << generalMeshErrorAdvice ); faceNodes[0] = elemNodes[0]; faceNodes[1] = elemNodes[N]; faceNodes[2] = elemNodes[N*2-1]; @@ -71,7 +71,7 @@ localIndex getFaceNodesPrism( localIndex const faceNum, } else if( faceNum >= 3 && faceNum <= N ) { - GEOS_ERROR_IF_LT_MSG( faceNodes.size(), 4, GEOS_FMT( nodeCountError, N, faceNum ) ); + GEOS_ERROR_IF_LT_MSG( faceNodes.size(), 4, GEOS_FMT( nodeCountError, N, faceNum ) << generalMeshErrorAdvice ); faceNodes[0] = elemNodes[faceNum-2]; faceNodes[1] = elemNodes[faceNum-1]; faceNodes[2] = elemNodes[N+faceNum-1]; @@ -80,7 +80,7 @@ localIndex getFaceNodesPrism( localIndex const faceNum, } else if( faceNum == N + 1 ) { - GEOS_ERROR_IF_LT_MSG( faceNodes.size(), N, GEOS_FMT( nodeCountError, N, faceNum ) ); + GEOS_ERROR_IF_LT_MSG( faceNodes.size(), N, GEOS_FMT( nodeCountError, N, faceNum ) << generalMeshErrorAdvice ); for( localIndex i = 0; i < N; ++i ) { faceNodes[i] = elemNodes[i+N]; @@ -89,7 +89,8 @@ localIndex getFaceNodesPrism( localIndex const faceNum, } else { - GEOS_ERROR( GEOS_FMT( "Local face index out of range for Prism{} element: face index = {}.", N, faceNum ) ); + GEOS_ERROR( GEOS_FMT( "Local face index out of range for Prism{} element: face index = {}.", + N, faceNum ) << generalMeshErrorAdvice ); return 0; } diff --git a/src/coreComponents/mesh/generators/VTKUtilities.cpp b/src/coreComponents/mesh/generators/VTKUtilities.cpp index 49f5e9a75a7..345e7c5c80a 100644 --- a/src/coreComponents/mesh/generators/VTKUtilities.cpp +++ b/src/coreComponents/mesh/generators/VTKUtilities.cpp @@ -425,7 +425,8 @@ VTKLegacyDatasetType getVTKLegacyDatasetType( vtkSmartPointer< vtkDataSetReader } else { - GEOS_ERROR( "Unsupported legacy VTK dataset format" ); + GEOS_ERROR( "Unsupported legacy VTK dataset format.\nLegacy supported formats are: " << + EnumStrings< VTKLegacyDatasetType >::concat( ", " ) << '.' ); } return {}; } @@ -475,7 +476,8 @@ loadMesh( Path const & filePath, vtkCompositeDataSet * compositeDataSet = reader->GetOutput(); if( !compositeDataSet->IsA( "vtkMultiBlockDataSet" ) ) { - GEOS_ERROR( "Unsupported vtk multi-block format in file \"" << filePath << "\"" ); + GEOS_ERROR( "Unsupported vtk multi-block format in file \"" << filePath << "\"" << + generalMeshErrorAdvice ); } vtkMultiBlockDataSet * multiBlockDataSet = vtkMultiBlockDataSet::SafeDownCast( compositeDataSet ); @@ -494,7 +496,8 @@ loadMesh( Path const & filePath, } } } - GEOS_ERROR( "Could not find mesh \"" << blockName << "\" in multi-block vtk file \"" << filePath << "\"" ); + GEOS_ERROR( "Could not find mesh \"" << blockName << "\" in multi-block vtk file \"" << filePath << "\"" << + generalMeshErrorAdvice ); return {}; } else @@ -531,7 +534,7 @@ loadMesh( Path const & filePath, case VTKMeshExtension::pvti: return parallelRead( vtkSmartPointer< vtkXMLPImageDataReader >::New() ); default: { - GEOS_ERROR( extension << " is not a recognized extension for VTKMesh. Please use .vtk, .vtu, .vtr, .vts, .vti, .pvtu, .pvtr, .pvts or .ptvi." ); + GEOS_ERROR( extension << " is not a recognized extension for VTKMesh. Please use ." << EnumStrings< VTKMeshExtension >::concat( ", ." ) ); break; } } @@ -745,9 +748,11 @@ vtkSmartPointer< vtkDataSet > manageGlobalIds( vtkSmartPointer< vtkDataSet > mes vtkIdTypeArray const * const globalCellId = vtkIdTypeArray::FastDownCast( output->GetCellData()->GetGlobalIds() ); vtkIdTypeArray const * const globalPointId = vtkIdTypeArray::FastDownCast( output->GetPointData()->GetGlobalIds() ); GEOS_ERROR_IF( globalCellId->GetNumberOfComponents() != 1 && globalCellId->GetNumberOfTuples() != output->GetNumberOfCells(), - "Global cell IDs are invalid. Check the array or enable automatic generation (useGlobalId < 0)" ); + "Global cell IDs are invalid. Check the array or enable automatic generation (useGlobalId < 0)." << + generalMeshErrorAdvice ); GEOS_ERROR_IF( globalPointId->GetNumberOfComponents() != 1 && globalPointId->GetNumberOfTuples() != output->GetNumberOfPoints(), - "Global cell IDs are invalid. Check the array or enable automatic generation (useGlobalId < 0)" ); + "Global cell IDs are invalid. Check the array or enable automatic generation (useGlobalId < 0)." << + generalMeshErrorAdvice ); GEOS_LOG_RANK_0( "Using global Ids defined in VTK mesh" ); } @@ -1038,7 +1043,7 @@ geos::ElementType buildGeosxPolyhedronType( vtkCell * const cell ) case 11: return geos::ElementType::Prism11; default: { - GEOS_ERROR( "Prism with " << numQuads << " sides is not supported." ); + GEOS_ERROR( "Prism with " << numQuads << " sides is not supported." << generalMeshErrorAdvice ); return{}; } } @@ -1068,7 +1073,8 @@ ElementType convertVtkToGeosxElementType( vtkCell *cell ) case VTK_POLYHEDRON: return buildGeosxPolyhedronType( cell ); default: { - GEOS_ERROR( cell->GetCellType() << " is not a recognized cell type to be used with the VTKMeshGenerator" ); + GEOS_ERROR( cell->GetCellType() << " is not a recognized cell type to be used with the VTKMeshGenerator" << + generalMeshErrorAdvice ); return {}; } } @@ -1392,7 +1398,7 @@ std::vector< localIndex > getWedgeNodeOrderingFromPolyhedron( vtkCell * const ce } } - GEOS_ERROR_IF( iFace == numFaces, "Invalid wedge." ); + GEOS_ERROR_IF( iFace == numFaces, "Invalid wedge." << generalMeshErrorAdvice ); // Get global pointIds for the first triangle for( localIndex i = 0; i < 3; ++i ) @@ -1487,7 +1493,7 @@ std::vector< localIndex > getPyramidNodeOrderingFromPolyhedron( vtkCell * const } } - GEOS_ERROR_IF( iFace == numFaces, "Invalid pyramid." ); + GEOS_ERROR_IF( iFace == numFaces, "Invalid pyramid." << generalMeshErrorAdvice ); // Get global pointIds for the base vtkCell * cellFace = cell->GetFace( iFace ); @@ -1561,7 +1567,7 @@ std::vector< localIndex > getPrismNodeOrderingFromPolyhedron( vtkCell * const ce } } - GEOS_ERROR_IF( iFace == numFaces, "Invalid prism." ); + GEOS_ERROR_IF( iFace == numFaces, "Invalid prism." << generalMeshErrorAdvice ); // Get global pointIds for the first base vtkCell *cellFace = cell->GetFace( iFace ); @@ -2058,9 +2064,10 @@ real64 writeNodes( integer const logLevel, // TODO: remove this check once the input mesh is cleaned of duplicate points via a filter // and make launch policy parallel again GEOS_ERROR_IF( nodeGlobalIds.count( pointGlobalID ) > 0, - GEOS_FMT( "Duplicate point detected: globalID = {}\n" - "Consider cleaning the dataset in Paraview using 'Clean to grid' filter.\n" - "Make sure partitionRefinement is set to 1 or higher (this may help).", + GEOS_FMT( "At least one duplicate point detected (globalID = {})\n" << + generalMeshErrorAdvice << "\n" << + "Potential fixes :\n- Consider cleaning the dataset in Paraview using 'Clean to grid' filter.\n" + "- Make sure partitionRefinement is set to 1 or higher.", pointGlobalID ) ); nodeGlobalIds.insert( pointGlobalID ); } ); From b97246a7e12c79f3b62f45146abfe076b146b2cc Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Wed, 25 Oct 2023 15:18:00 +0200 Subject: [PATCH 85/85] compil fix + doc --- src/coreComponents/mesh/ElementType.hpp | 1 + src/coreComponents/mesh/generators/VTKUtilities.cpp | 5 ++--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/coreComponents/mesh/ElementType.hpp b/src/coreComponents/mesh/ElementType.hpp index 14586c56d24..883e5782ca3 100644 --- a/src/coreComponents/mesh/ElementType.hpp +++ b/src/coreComponents/mesh/ElementType.hpp @@ -109,6 +109,7 @@ ENUM_STRINGS( ElementType, "HendecagonalPrism", "Polyhedron" ); +/// String available for mesh errors inline auto constexpr generalMeshErrorAdvice = "\nPlease consider checking the validity of your mesh with the `mesh_doctor` GEOS python tools."; } // namespace geos diff --git a/src/coreComponents/mesh/generators/VTKUtilities.cpp b/src/coreComponents/mesh/generators/VTKUtilities.cpp index 345e7c5c80a..2e5fe4fc6b9 100644 --- a/src/coreComponents/mesh/generators/VTKUtilities.cpp +++ b/src/coreComponents/mesh/generators/VTKUtilities.cpp @@ -2064,11 +2064,10 @@ real64 writeNodes( integer const logLevel, // TODO: remove this check once the input mesh is cleaned of duplicate points via a filter // and make launch policy parallel again GEOS_ERROR_IF( nodeGlobalIds.count( pointGlobalID ) > 0, - GEOS_FMT( "At least one duplicate point detected (globalID = {})\n" << - generalMeshErrorAdvice << "\n" << + GEOS_FMT( "At least one duplicate point detected (globalID = {}).{}\n" "Potential fixes :\n- Consider cleaning the dataset in Paraview using 'Clean to grid' filter.\n" "- Make sure partitionRefinement is set to 1 or higher.", - pointGlobalID ) ); + pointGlobalID, generalMeshErrorAdvice ) ); nodeGlobalIds.insert( pointGlobalID ); } );

{ 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 literal 0 HcmV?d00001 diff --git a/src/docs/sphinx/advancedExamples/validationStudies/wellboreProblems/dpWellbore/dpWellbore_analyticalResults.py b/src/docs/sphinx/advancedExamples/validationStudies/wellboreProblems/dpWellbore/dpWellbore_analyticalResults.py new file mode 100644 index 00000000000..bc2b008433d --- /dev/null +++ b/src/docs/sphinx/advancedExamples/validationStudies/wellboreProblems/dpWellbore/dpWellbore_analyticalResults.py @@ -0,0 +1,112 @@ +import sys +sys.path.append('../') +import numpy as np +import elastoPlasticWellboreAnalyticalSolutions as epwAnal + +#Note: this solution is developed by Chen and Abousleiman (2017) for the case with zero cohesion + +def elastic_plastic_boundary_DP(sh,sv,q_ep_boundary): + # See eq. 25 of Chen and Abousleiman 2013 + sr0 = sh - np.sqrt(sh**2 - (4.0 * sh**2 + sv**2 - 2 * sh * sv - q_ep_boundary**2) / 3.0) + s00 = 2.0*sh - sr0 + sz0 = sv + + return sr0, s00, sz0 + +def yeild_DP(p, q, a, b): + return q - b*p - a + +def dFdpq_DP(p, q, param_b): + dFdp = -param_b + dFdq = 1.0 + dFda = -1.0 + + return dFdp, dFdq, dFda + +def dGdpq_DP(p, q, param_b_potential): + dGdp = -param_b_potential + dGdq = 1.0 + + return dGdp, dGdq + +def compute_q_ep_boudary_DP(sh,sv,param_a,param_b): + p0,q0 = epwAnal.invariants(sh,sh,sv) + return param_b*p0 + param_a + +def compute_param_b(frictionAngle): + sin_frictionAngle = np.sin(frictionAngle) + return 6.0*sin_frictionAngle/(3.0-sin_frictionAngle) + +def DruckerPragerModel(a0_a_ratio, sh, sv, nu, a0, G, defaultCohesion, defaultFrictionAngle, defaultDilationAngle, defaultHardeningRate): + a = a0/a0_a_ratio + xi_well = 1.0 - a0/a # the auxiliary variable xi at the wellbore, xi = (a-a0)/a = 1-1/(a/a0) + nPoints = 1000 + + defaultFrictionAngle *= np.pi/180.0 # converted to rad + defaultDilationAngle *= np.pi/180.0 # converted to rad + + param_b = compute_param_b(defaultFrictionAngle) + param_b_potential = compute_param_b(defaultDilationAngle) + param_a = defaultCohesion * param_b / np.tan(defaultFrictionAngle) + + # ELastic trial + pw = 2.0*G*xi_well + sh + r_e,sr_e,s0_e,sz_e = epwAnal.solution_elastic(sh,sv,1.0,pw) + p_e, q_e = epwAnal.invariants(sr_e[0],s0_e[0],sz_e[0]) + + if (yeild_DP(p_e, q_e, param_a, param_b)<=0): # Pure elastic wellbore + return [0],[0],[0],[0],r_e,sr_e,s0_e,sz_e,pw,p_e,q_e + + else: # Elastic-plastic wellbore + q_ep_boundary = compute_q_ep_boudary_DP(sh,sv,param_a,param_b) + # Elastic-Plastic boundary + sr_ep_boundary, s0_ep_boundary, sz_ep_boundary = elastic_plastic_boundary_DP(sh,sv,q_ep_boundary) + sr_p = [sr_ep_boundary] + s0_p = [s0_ep_boundary] + sz_p = [sz_ep_boundary] + epsV_p = [0] # strain calculated starting from the reference state that is the ep boundary + + # Eq. 36 in Chen and Abousleiman 2017 + xi_ep_boundary = (sr_ep_boundary - sh) / 2.0 / G + + # Mesh from elastic-plastic boundary to wellbore + dxi = (xi_well - xi_ep_boundary) / (nPoints - 1) + xi = np.linspace(xi_ep_boundary, xi_well, nPoints) + + for i in range(1, nPoints): + + p,q = epwAnal.invariants(sr_p[i-1],s0_p[i-1],sz_p[i-1]) + + exp_epsilon_V = np.exp(epsV_p[i-1])# epsV is the volumetric strain from the elastic-plastic boundary + + E = 2.0 * G * (1.0 + nu) + + param_a = q - param_b*p # update the hardening parameter with plastic yield condition F=0 + dFdp, dFdq, dFda = dFdpq_DP(p,q,param_b) + dGdp, dGdq = dGdpq_DP(p,q,param_b_potential) + dadLambda = defaultHardeningRate + + dFdHardningParam,dHardningParamdPlasticVar = dFda,dadLambda + + sr_i, s0_i, sz_i, epsV_i = epwAnal.solution_plastic(sr_p[i - 1], s0_p[i - 1], sz_p[i - 1], epsV_p[i-1], xi[i - 1], dxi, dGdp, dGdq, E, nu, dFdHardningParam,dHardningParamdPlasticVar,1.0) + + sr_p.append(sr_i) + s0_p.append(s0_i) + sz_p.append(sz_i) + epsV_p.append(epsV_i) + + # Wellbore surface stress + pw = sr_i + p = (sr_i + s0_i + sz_i)/3.0 + q = np.sqrt(0.5) * np.sqrt( (sr_i-s0_i)**2.0 + (sr_i-sz_i)**2.0 + (s0_i-sz_i)**2.0 ) + + # Compute the normalized radial coordinate r/a + r_p = epwAnal.compute_radialCoordinate(xi, epsV_p) + + # Elastic zone + r_ep_boundary = r_p[0] + r_e,sr_e,s0_e,sz_e = epwAnal.solution_elastic(sh,sv,r_ep_boundary,sr_ep_boundary) + + return r_p,sr_p,s0_p,sz_p,r_e,sr_e,s0_e,sz_e,pw,p,q + + diff --git a/src/docs/sphinx/advancedExamples/validationStudies/wellboreProblems/dpWellbore/dpWellbore_plot.py b/src/docs/sphinx/advancedExamples/validationStudies/wellboreProblems/dpWellbore/dpWellbore_plot.py new file mode 100644 index 00000000000..f4cc634c4b7 --- /dev/null +++ b/src/docs/sphinx/advancedExamples/validationStudies/wellboreProblems/dpWellbore/dpWellbore_plot.py @@ -0,0 +1,236 @@ +import matplotlib +matplotlib.use('Agg') +import matplotlib.pyplot as plt +import numpy as np +import h5py +from xml.etree import ElementTree +import dpWellbore_analyticalResults as dpAnal +import elastoPlasticWellboreAnalyticalSolutions as epwAnal + +def extractDataFromXMLList(paramList): + # Extract data from a list in XML such as "{ 1, 2, 3}" + return paramList.replace('{', '').replace('}', '').strip().split(',') + +def getDataFromXML(xmlFilePathPrefix): + # Get wellbore inner radius + xmlFilePath = xmlFilePathPrefix + "_benchmark.xml" + tree = ElementTree.parse(xmlFilePath) + + meshParam = tree.find('Mesh/InternalWellbore') + radii = extractDataFromXMLList( meshParam.get("radius") ) + a0 = float(radii[0]) + + # Get the temperature change on the inner surface of the wellbore + xmlFilePath = xmlFilePathPrefix + "_base.xml" + tree = ElementTree.parse(xmlFilePath) + + fsParams = tree.findall('FieldSpecifications/FieldSpecification') + for fsParam in fsParams: + if fsParam.get('name') == 'stressXX': + sh = float( fsParam.get('scale') ) + if fsParam.get('name') == 'stressZZ': + sv = float( fsParam.get('scale') ) + + pw = float( extractDataFromXMLList(tree.find('Functions/TableFunction').get('values') )[1]) + + defaultBulkModulus = float( tree.find('Constitutive/DruckerPrager').get('defaultBulkModulus') ) + defaultShearModulus = float( tree.find('Constitutive/DruckerPrager').get('defaultShearModulus') ) + defaultCohesion = float( tree.find('Constitutive/DruckerPrager').get('defaultCohesion') ) + defaultFrictionAngle = float( tree.find('Constitutive/DruckerPrager').get('defaultFrictionAngle') ) + defaultDilationAngle = float( tree.find('Constitutive/DruckerPrager').get('defaultDilationAngle') ) + defaultHardeningRate = float( tree.find('Constitutive/DruckerPrager').get('defaultHardeningRate') ) + + return [a0, sh, sv, pw, defaultBulkModulus, defaultShearModulus, defaultCohesion, defaultFrictionAngle, defaultDilationAngle, defaultHardeningRate] + +def stressRotation(stress, phi_x): + rotx = np.array([[np.cos(phi_x), np.sin(phi_x), 0.], [-np.sin(phi_x), np.cos(phi_x), 0.], [0., 0., 1.]]) + return np.dot(np.dot(np.transpose(rotx), stress), rotx) + +def main(): + xmlFilePathPrefix = "DruckerPragerWellbore" + xmlData = getDataFromXML(xmlFilePathPrefix) + + # Initial wellbore radius + a0 = xmlData[0] + + # Initial stresses + sh = -xmlData[1] #negative sign because of positive sign convention for compression stress in the analytical solution + sv = -xmlData[2] #negative sign because of positive sign convention for compression stress in the analytical solution + + # Boundary condition on the wellbore surface + pw = -xmlData[3] #negative sign because of positive sign convention for compression stress in the analytical solution + # Equivalent displacement condition for the analytical solution + a0_a_ratio = 1.1 # This ratio corresponds to pw, extracted from the relationship between pw and a0/a (see bottom-right figure) + + # Elastic moduli + K = xmlData[4] + G = xmlData[5] + nu = (3.0*K-2.0*G) / (6.0*K+2.0*G) + + # Elasto-plastic parameters + defaultCohesion = xmlData[6] + defaultFrictionAngle = xmlData[7] # deg + defaultDilationAngle = xmlData[8] + defaultHardeningRate = xmlData[9] + + # Get stress, time and element center from GEOSX results + stress_field_name = 'rock_stress' + hf_stress = h5py.File('stressHistory_rock.hdf5', 'r') + time = np.array( hf_stress.get(stress_field_name + ' Time') ) + center = np.array( hf_stress.get(stress_field_name + ' elementCenter') ) + stress = np.array( hf_stress.get(stress_field_name) ) + + # Get the deformed wellbore radius + hf_disp = h5py.File("displacementHistory.hdf5", 'r') + displacement = np.array( hf_disp.get('totalDisplacement') ) + node_position = np.array( hf_disp.get('totalDisplacement ReferencePosition') ) + da = displacement[:, 0, 0] + a = a0 + da + + # Compute total stress, the stress of each element is the average value of its eight Gauss points + nTimeSteps = 39 + stress_xx = ( sum(stress[nTimeSteps,:,6*i+0] for i in range(8)) )/8.0 + stress_yy = ( sum(stress[nTimeSteps,:,6*i+1] for i in range(8)) )/8.0 + stress_zz = ( sum(stress[nTimeSteps,:,6*i+2] for i in range(8)) )/8.0 + stress_yz = ( sum(stress[nTimeSteps,:,6*i+3] for i in range(8)) )/8.0 + stress_xz = ( sum(stress[nTimeSteps,:,6*i+4] for i in range(8)) )/8.0 + stress_xy = ( sum(stress[nTimeSteps,:,6*i+5] for i in range(8)) )/8.0 + + # Coordinate of elemnt center + nElements = center.shape[1] + xCoord = center[0, :, 0] + yCoord = center[0, :, 1] + + rCoord = np.sqrt( xCoord*xCoord + yCoord*yCoord ) + + # Compute stress components in cylindrical coordinate system + stress_rr = np.zeros(stress_xx.shape) + stress_tt = np.zeros(stress_xx.shape) + + for idx_elem in range(stress.shape[1]): + stressMatrix_cartesian = np.array([[stress_xx[idx_elem],stress_xy[idx_elem],stress_xz[idx_elem]],\ + [stress_xy[idx_elem],stress_yy[idx_elem],stress_yz[idx_elem]],\ + [stress_xz[idx_elem],stress_yz[idx_elem],stress_zz[idx_elem]]]) + + if(yCoord[idx_elem] != 0): + phi_x = np.arctan( xCoord[idx_elem]/yCoord[idx_elem] ) + else: + phi_x = 0 + + stressMatrix_cylindirical = stressRotation(stressMatrix_cartesian, phi_x) + stress_rr[idx_elem] = stressMatrix_cylindirical[1][1] + stress_tt[idx_elem] = stressMatrix_cylindirical[0][0] + + # Stress invariants at wellbore surface + stress_xx_wellboresurface = ( sum(stress[:,0,6*i+0] for i in range(8)) )/8.0 + stress_yy_wellboresurface = ( sum(stress[:,0,6*i+1] for i in range(8)) )/8.0 + stress_zz_wellboresurface = ( sum(stress[:,0,6*i+2] for i in range(8)) )/8.0 + stress_yz_wellboresurface = ( sum(stress[:,0,6*i+3] for i in range(8)) )/8.0 + stress_xz_wellboresurface = ( sum(stress[:,0,6*i+4] for i in range(8)) )/8.0 + stress_xy_wellboresurface = ( sum(stress[:,0,6*i+5] for i in range(8)) )/8.0 + p = (stress_xx_wellboresurface + stress_yy_wellboresurface + stress_zz_wellboresurface)/3.0 + q = np.sqrt(1.5) * np.sqrt( (stress_xx_wellboresurface-p)**2.0 + (stress_yy_wellboresurface-p)**2.0 + (stress_zz_wellboresurface-p)**2.0 + \ + stress_xy_wellboresurface**2.0 + stress_xz_wellboresurface**2.0 + stress_yz_wellboresurface**2.0 ) + + # Analytical results + r_ep_analytic,srr_ep_analytic,stt_ep_analytic,szz_ep_analytic,r_elas_analytic,srr_elas_analytic,stt_elas_analytic,szz_elas_analytic,\ + pw_analytic,p_wellsurface_analytic,q_wellsurface_analytic = dpAnal.DruckerPragerModel(a0_a_ratio, sh, sv, nu, a0, G, defaultCohesion, defaultFrictionAngle, defaultDilationAngle, defaultHardeningRate) + + # Compute pressure at wellbore surface + list_a0_a_ratio = np.arange(1.0,a0_a_ratio,0.001) + list_pw_analytic = [] + list_p_wellsurface_analytic = [] + list_q_wellsurface_analytic = [] + + for a0_a_ratio_val in list_a0_a_ratio: + tmp = dpAnal.DruckerPragerModel(a0_a_ratio_val, sh, sv, nu, a0, G, defaultCohesion, defaultFrictionAngle, defaultDilationAngle, defaultHardeningRate) + pw_analytic = tmp[8] + p_wellsurface_analytic = tmp[9] + q_wellsurface_analytic = tmp[10] + list_pw_analytic.append(pw_analytic) + list_p_wellsurface_analytic.append(p_wellsurface_analytic) + list_q_wellsurface_analytic.append(q_wellsurface_analytic) + + # Plots + plt.figure(figsize=(15,10)) + + # Plot GEOS results versus analytical results for elasto-plastic material + plt.subplot(2,2,1) + plt.plot( rCoord/rCoord[0],-stress_rr,'r+',label=r'$\sigma_{rr}$: GEOS') + plt.plot(r_ep_analytic,srr_ep_analytic, 'r', label=r'$\sigma_{rr}$: Analytical Plasticity') + plt.plot(r_elas_analytic,srr_elas_analytic, 'r') + + plt.plot( rCoord/rCoord[0],-stress_tt,'b+',label=r'$\sigma_{\theta\theta}$: GEOS') + plt.plot(r_ep_analytic,stt_ep_analytic, 'b', label=r'$\sigma_{\theta\theta}$: Analytical Plasticity') + plt.plot(r_elas_analytic,stt_elas_analytic, 'b') + + plt.plot( rCoord/rCoord[0],-stress_zz,'g+',label=r'$\sigma_{zz}$: GEOS') + plt.plot(r_ep_analytic,szz_ep_analytic, 'g', label=r'$\sigma_{zz}$: Analytical Plasticity') + plt.plot(r_elas_analytic,szz_elas_analytic, 'g') + + plt.plot([r_ep_analytic[0],r_ep_analytic[0]],[0.0,20e6],'k--', label='Elastic-Plastic boundary') + + plt.ylabel('Stresses [Pa]') + plt.xlabel(r'Normalized radial coordinate, $r/a$') + plt.xlim(1.0,10.0) + plt.ylim(0.0,20e6) + plt.yticks(np.linspace(0.0,20e6,11)) + plt.xscale('log', subs=range(2,10)) + plt.legend() + + # Plot GEOS results for elasto-plastic material versus analytical results for elastic material + r_elas_assumed,srr_elas_assumed,stt_elas_assumed,szz_elas_assumed = epwAnal.solution_elastic(sh,sv,1.0,pw) + + plt.subplot(2,2,2) + plt.plot( rCoord/rCoord[0],-stress_rr,'r+',label=r'$\sigma_{rr}$: GEOS') + plt.plot(r_elas_assumed,srr_elas_assumed, 'r', label=r'$\sigma_{rr}$: Analytical Elasticity') + + plt.plot( rCoord/rCoord[0],-stress_tt,'b+',label=r'$\sigma_{\theta\theta}$: GEOS') + plt.plot(r_elas_assumed,stt_elas_assumed, 'b', label=r'$\sigma_{\theta\theta}$: Analytical Elasticity') + + plt.plot( rCoord/rCoord[0],-stress_zz,'g+',label=r'$\sigma_{zz}$: GEOS') + plt.plot(r_elas_assumed,szz_elas_assumed, 'g', label=r'$\sigma_{zz}$: Analytical Elasticity') + + plt.ylabel('Stresses [Pa]') + plt.xlabel(r'Normalized radial coordinate, $r/a$') + plt.xlim(1.0,10.0) + plt.ylim(0.0,20e6) + plt.yticks(np.linspace(0.0,20e6,11)) + plt.xscale('log', subs=range(2,10)) + plt.legend() + + # Plot the stress path on the p-q plan when the well surface pressure decrease from the in-situ condition + defaultFrictionAngle *= np.pi/180.0 # converted to rad + param_b = dpAnal.compute_param_b(defaultFrictionAngle) + param_a = defaultCohesion * param_b / np.tan(defaultFrictionAngle) + p_yieldSurface = np.arange(0,15e6,1) + q_yieldSurface_i = p_yieldSurface*param_b + param_a + + plt.subplot(2,2,3) + plt.plot(-p,q,'ko',label='GEOS') + plt.plot(list_p_wellsurface_analytic,list_q_wellsurface_analytic,'b',label='Analytical') + plt.plot(p_yieldSurface,q_yieldSurface_i,'g--',label='Initial Yield surface') + + plt.xlabel('p (Pa)') + plt.ylabel('q (Pa)') + #plt.xlim(0,15e6) + #plt.ylim(0,10e6) + plt.legend() + + # Plot the wellbore deformation a0/a versus wellbore surface pressure pw + pw = stress_xx_wellboresurface + + plt.subplot(2,2,4) + plt.plot(a0/a, -pw,'ko',label='GEOS') + plt.plot(list_a0_a_ratio,list_pw_analytic,'b',label='Analytical') + plt.xlabel('a0/a') + plt.ylabel('pw (Pa)') + plt.xlim(1.0,a0_a_ratio) + plt.ylim(0,12e6) + plt.legend() + + plt.savefig('dpWellboreVerification.png') + +if __name__ == "__main__": + main() + diff --git a/src/docs/sphinx/advancedExamples/validationStudies/wellboreProblems/edpWellbore/Example.rst b/src/docs/sphinx/advancedExamples/validationStudies/wellboreProblems/edpWellbore/Example.rst index b39b4fe9759..ea699e22054 100644 --- a/src/docs/sphinx/advancedExamples/validationStudies/wellboreProblems/edpWellbore/Example.rst +++ b/src/docs/sphinx/advancedExamples/validationStudies/wellboreProblems/edpWellbore/Example.rst @@ -2,7 +2,7 @@ #################################################### -Elasto-Plastic Near-Well Deformation +Extended Drucker-Prager Model for Wellbore Problems #################################################### @@ -33,6 +33,7 @@ contained within two xml files that are located at: inputFiles/solidMechanics/ExtendedDruckerPragerWellbore_benchmark.xml +The Python scripts for post-processing GEOS results, analytical restuls and validation plots are also provided in this example. ------------------------------------------------------------------ Description of the case @@ -75,12 +76,10 @@ Following figure shows the generated mesh that is used for solving this 3D wellb Let us take a closer look at the geometry of this wellbore problem. We use the internal mesh generator ``InternalWellbore`` to create a rock domain -(:math:`10\, m \, \times 5 \, m \, \times 2 \, m`), with a wellbore of +(:math:`10\, m \, \times 10 \, m \, \times 2 \, m`), with a wellbore of initial radius equal to :math:`0.1` m. Coordinates of ``trajectory`` defines the wellbore trajectory, which represents a vertical well in this example. -By turning on ``autoSpaceRadialElems="{ 1 }"``, the internal mesh generator automatically sets number and spacing of elements in the radial direction, which overrides the values of ``nr``. -With ``useCartesianOuterBoundary="0"``, a Cartesian aligned outer boundary on the outer block is enforced. -In this way, a structured three-dimensional mesh is created with 100 x 80 x 2 elements in the radial, tangential and z directions, respectively. All the elements are eight-node hexahedral elements (``C3D8``) and refinement is performed +By turning on ``autoSpaceRadialElems="{ 1 }"``, the internal mesh generator automatically sets number and spacing of elements in the radial direction, which overrides the values of ``nr``. In this way, a structured three-dimensional mesh is created. All the elements are eight-node hexahedral elements (``C3D8``) and refinement is performed to conform with the wellbore geometry. This mesh is defined as a cell block with the name ``cb1``. @@ -121,8 +120,7 @@ A homogeneous domain with one solid material is assumed, whose mechanical proper Recall that in the ``SolidMechanics_LagrangianFEM`` section, ``rock`` is designated as the material in the computational domain. Here, Extended Drucker Prager model ``ExtendedDruckerPrager`` is used to simulate the elastoplastic behavior of ``rock``. -As for the material parameters, ``defaultInitialFrictionAngle``, ``defaultResidualFrictionAngle`` and ``defaultCohesion`` denote the initial friction angle, the residual friction angle, and cohesion, respectively, as defined by the Mohr-Coulomb failure envelope. -As the residual friction angle ``defaultResidualFrictionAngle`` is larger than the initial one ``defaultInitialFrictionAngle``, a strain hardening model is adopted, whose hardening rate is given as ``defaultHardening="0.01"``. +As for the material parameters, ``defaultInitialFrictionAngle``, ``defaultResidualFrictionAngle`` and ``defaultCohesion`` denote the initial friction angle, the residual friction angle, and cohesion, respectively, as defined by the Mohr-Coulomb failure envelope. In this example, zero cohesion is considered to consist with the reference analytical results. As the residual friction angle ``defaultResidualFrictionAngle`` is larger than the initial one ``defaultInitialFrictionAngle``, a strain hardening model is adopted, whose hardening rate is given as ``defaultHardening="0.01"``. If the residual friction angle is set to be less than the initial one, strain weakening will take place. Setting ``defaultDilationRatio="1.0"`` corresponds to an associated flow rule. The constitutive parameters such as the density, the bulk modulus, and the shear modulus are specified in the International System of Units. @@ -138,8 +136,8 @@ The next step is to specify fields, including: - The boundary conditions (the reduction of wellbore pressure and constraints of the outer boundaries have to be set) In this example, we need to specify isotropic horizontal stress (:math:`\sigma_h` = -11.25 MPa) and vertical stress (:math:`\sigma_v` = -15.0 MPa). -To reach equilibrium, a compressive traction :math:`P_w` = -11.25 MPa is instantaneously applied at the wellbore wall ``rneg`` at time :math:`t` = 0 s, which will then be gradually reduced to a lower value (-2.0 MPa) to let wellbore contract. -The remaining parts of the outer boundaries are subjected to roller constraints. +To reach equilibrium, a compressive traction :math:`p_w` = -11.25 MPa is instantaneously applied at the wellbore wall ``rneg`` at time :math:`t` = 0 s, which will then be gradually reduced to a lower absolute value (-2.0 MPa) to let wellbore contract. +The boundaries at ``tneg`` and ``tpos`` are subjected to roller constraints. The plane strain condition is ensured by fixing the vertical displacement at ``zneg`` and ``zpos`` The far-field boundary is fixed in horizontal displacement. These boundary conditions are set up through the ``FieldSpecifications`` section. @@ -149,7 +147,7 @@ These boundary conditions are set up through the ``FieldSpecifications`` section :end-before: -With ``tractionType="normal"``, traction is applied to the wellbore wall ``rneg`` as a pressure specified from the product of scale ``scale="-11.25e6"`` and the outward face normal. +With ``tractionType="normal"``, traction is applied to the wellbore wall ``rneg`` as a pressure specified from the product of scale ``scale="1.0"`` and the outward face normal. A table function ``timeFunction`` is used to define the time-dependent traction ``ExternalLoad``. The ``coordinates`` and ``values`` form a time-magnitude pair for the loading time history. In this case, the loading magnitude decreases linearly as the time evolves. @@ -167,7 +165,7 @@ You may note : - ``fieldName`` is the name of the field registered in GEOS; - Component ``0``, ``1``, and ``2`` refer to the x, y, and z direction, respectively; - And the non-zero values given by ``Scale`` indicate the magnitude of the loading; - - Some shorthand, such as ``xneg`` and ``xpos``, are used as the locations where the boundary conditions are applied in the computational domain. For instance, ``xneg`` means the portion of the computational domain located at the left-most in the x-axis, while ``xpos`` refers to the portion located at the right-most area in the x-axis. Similar shorthand include ``ypos``, ``yneg``, ``zpos``, and ``zneg``; + - Some shorthand, such as ``tpos`` and ``xpos``, are used as the locations where the boundary conditions are applied in the computational domain. For instance, ``tpos`` means the portion of the computational domain located at the left-most in the x-axis, while ``xpos`` refers to the portion located at the right-most area in the x-axis. Similar shorthand include ``ypos``, ``tneg``, ``zpos``, and ``zneg``; - The mud pressure loading has a negative value due to the negative sign convention for compressive stress in GEOS. @@ -180,13 +178,13 @@ The parameters used in the simulation are summarized in the following table. +------------------+-------------------------+------------------+---------------+ | :math:`G` | Shear Modulus | [MPa] | 300 | +------------------+-------------------------+------------------+---------------+ -| :math:`C` | Cohesion | [MPa] | 0.0 | +| :math:`c` | Cohesion | [MPa] | 0.0 | +------------------+-------------------------+------------------+---------------+ | :math:`\phi_i` | Initial Friction Angle | [degree] | 15.27 | +------------------+-------------------------+------------------+---------------+ | :math:`\phi_r` | Residual Friction Angle | [degree] | 23.05 | +------------------+-------------------------+------------------+---------------+ -| :math:`c_h` | Hardening Rate | [-] | 0.01 | +| :math:`m` | Hardening Rate | [-] | 0.01 | +------------------+-------------------------+------------------+---------------+ | :math:`\sigma_h` | Horizontal Stress | [MPa] | -11.25 | +------------------+-------------------------+------------------+---------------+ @@ -194,24 +192,24 @@ The parameters used in the simulation are summarized in the following table. +------------------+-------------------------+------------------+---------------+ | :math:`a_0` | Initial Well Radius | [m] | 0.1 | +------------------+-------------------------+------------------+---------------+ -| :math:`P_w` | Mud Pressure | [MPa] | -2.0 | +| :math:`p_w` | Mud Pressure | [MPa] | -2.0 | +------------------+-------------------------+------------------+---------------+ --------------------------------- Inspecting results --------------------------------- -In the above example, we requested silo-format output files. We can therefore import these into VisIt and use python scripts to visualize the outcome. Below figure shows the comparisons between the numerical predictions (marks) and the corresponding analytical solutions (solid curves) with respect to the distributions of normal stress components, stress path, the supporting wellbore pressure and wellbore size. It is clear that the GEOS predictions are in excellent agreement with the analytical results. +In the above example, we requested hdf5 output files. We can therefore use python scripts to visualize the outcome. Below figure shows the comparisons between the numerical predictions (marks) and the corresponding analytical solutions (solid curves) with respect to the distributions of principal stress components, stress path on the wellbore surface, the supporting wellbore pressure and wellbore size. It is clear that the GEOS predictions are in excellent agreement with the analytical results. On the top-right figure, we added also a comparison between GEOS results for elasto-plastic material and the anlytical solutions of an elastic material. Note that the elastic solutions are differed from the elasto-plastic results even in the elastic zone (r/a>2). +.. plot:: docs/sphinx/advancedExamples/validationStudies/wellboreProblems/edpWellbore/edpWellbore_plot.py -.. _problemVerificationEDPWellboreFig: -.. figure:: Verification.png +.. _edpWellboreVerificationFig: +.. figure:: edpWellboreVerification.png :align: center :width: 1000 :figclass: align-center - Comparing GEOS results with analytical solutions - + Validation of GEOS results. For the same wellbore problem, using different constitutive models (plastic vs. elastic), obviously, distinct differences in rock deformation and distribution of resultant stresses is also observed and highlighted. diff --git a/src/docs/sphinx/advancedExamples/validationStudies/wellboreProblems/edpWellbore/Verification.png b/src/docs/sphinx/advancedExamples/validationStudies/wellboreProblems/edpWellbore/Verification.png deleted file mode 100644 index e962163a5f0086bb049f26935459811d72bcc7cc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 371940 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