From 7601706e7f9a0e9844c2249b1021b47bae84b358 Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Thu, 23 Mar 2023 14:17:33 +0100 Subject: [PATCH 01/68] 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/68] 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/68] 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/68] 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/68] 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/68] 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/68] 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/68] - 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/68] 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/68] 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/68] 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/68] 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/68] - 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/68] 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/68] 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/68] 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 703d88b6e591c6481c4deb07266ef9bbbcb835c6 Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Wed, 10 May 2023 11:19:46 +0200 Subject: [PATCH 17/68] 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 18/68] 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 19/68] 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 026786071683f23320abfe87d671241addd51a38 Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Tue, 16 May 2023 11:18:15 +0200 Subject: [PATCH 20/68] 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 21/68] 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 22/68] 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 23/68] 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 24/68] 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 25/68] 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 26/68] 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 d79b0c9f71147e6c173d565aa69ecfb10867e19f Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Tue, 23 May 2023 11:48:37 +0200 Subject: [PATCH 27/68] 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 28/68] 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 17759eb031c2c73e3469f47aa718f88479129a9f Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Tue, 20 Jun 2023 15:06:19 +0200 Subject: [PATCH 29/68] 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 30/68] 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 31/68] 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 1a5c02a536872b46ca94119a0c9d1d05662c48a9 Mon Sep 17 00:00:00 2001 From: MelReyCG Date: Thu, 29 Jun 2023 17:31:31 +0200 Subject: [PATCH 32/68] 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 33/68] 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 34/68] 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 35/68] 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 36/68] 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 37/68] 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 38/68] 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 39/68] 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 40/68] 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 41/68] 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 42/68] 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 43/68] 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 44/68] 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 }"> - - - - + + + + - - - - - + + + + + 6?;3_7qhAv(~cGvNeCgnw24h10g_my`1SMXoIEE_=B>Wn z^>+7E&vf3hr#xL82O``9B0Sukzx=O9k^k1kRh?bSzy8ks6Z`xB`k(*fFRrtkW_kQu z^Ss4>{e4%~P1S}?TgB0=%A()D3$Xvh{r+3;<~=#&%5U0ud-l#Y>uPPpMU?K|Kqpc{_^uxUq9k!JB!2k zH?u8*xUv%anw3rc*WU#}Re%5dbrk10e&l3pR4EZzip1?DK;{6GW)?CA8$9Ve7WXtzYW#{{5Jx|rz8G7-@JbK z>tEwV&k3QipBwl6FdTYKAp}|`1TXrDmW`w7#u8KWDTTU4h|m#M9Sn>0X;>WA!(!JD zi*TM6>uXvZ4r#Fy(;}Q~iuGkv9Cn*x$8L(ycoggUQ5?cYu~Q#KI9e3zY*8Gxi(>a& z6yfq#tnattaJ&^e`BsE8zgQQ3aoGFCj`xeul8SXJ6^EGAu}Y0ZT^Xa%%5Z>XwlV@> zwp|$>U_Dnx@~n)}vN9ZCd9REBm>ny_11z~R5;-tNv%qkGWf2$wFxv-)2UvVyB)npb zEX8nuWvdtgFpCw#11v@{5=J*hL)~zIWvUwiFx%*c2Uri?NFJpzT9k$ZEN`U|05iWd zJiwAlBau8~H1!MzSmvG)05jJ!Jiy{SBjE&NWD14@EE~ZHfLSCM9$;yLk!ZFt8ry~g zEYG$P05iunJiwy1k51V0%Q7(nVCE%;2UtR4 zBmyx;HZdGv*%2cEW(hGo04AU(k0C#V4J=>-Q`msnr^OI9uz(FrVFRYPDTc6t1#Dmn z8!*{NF@y~)U;|UwfT=EuA#7j)8<@fd%;&8b!Uh(wfhlajl>K4|8(6>wrmz8%mx>{5 zV9`qLlu)CA4XDt43iQzZ4G^LG9AKgU22i2<6zHM*8z4gWIlw~y4WL5zDbPdrH$a5$ zbAW~Z8$gBbQ=o_LZ-5Bh=Ku@+H-HM=r$7(g-vAN1&jA+tZvYj#Pk|n~zX2k2p93uP z-vBCfp8`E}e*;A5J_lInzX4R}J_UN{{sxH9eGahDe*>t{eG2r@{S6SI`y61Q{{~Q@ z`xNM*`x_ua_c_2q{|%r*_bJdr_cuU-a0pj6ogb#U{LGO0uexHdl;tKLa!0R=z z0VpuXE8z7S@Bsyw;1%$C4Y+^+7|{FcH2^c*;xXW;t9mq8)p!lmqYN|!-GDlv6tu7E z(Y&hh8mLEi&>N@^8h|RGVo;AJL5m!9MlE%Km|}% z2uFqxIu>XP+JHu&2`B+oZFMxTRc8d6fu^7vPzRKP_MJMKcdD}ly@TFBeb4|@0TmN< zG)Yuv23mmTpf0EfDuA*?9T`M*EYKFT0gXTtP(sWE!+wNEfHMNkKvU2Sr~^tt`?E2^ zBfwdL-a&7mK4<`{fQpVW!Xv<$ffk@Ss0-?W3ZN`CMtB4`7HA9FfJUGRRmY_89i{O5 z$=)9UDc}Ho0Uk&J2k2e!KngfOFMtP9zyW%L_eVeqI6&Wm2U5TR`UpIb0uIm<@IVSU zgqQk(C5?a-aDYAo52Szt^c(O%3OGPd!2>Da0R6m`MnDQUKz|1hq<{nTK6oGn9H3Xg z11aDD{Unw~KngfOUw{WvzyW#}Jdgqo&|4UTVc9JByUL0wP> zQ~>QMj#C^5EkILH7u2EZSt$H7$^Fy7^|QGP6L4W%u7AqF-@zB)Tksq3K6n>=1fGId zzzg7s2YCVV0^|kA3y=qY2Va11!EeC(;9c+$cnV$tFMubkgf5V>xd9az(%CSiQwIJH zz5w5X-+=eQyWk`66ubgn08cE)w~&WQCoSY#$b)~GKmoo5zXAU;0T+A(o`QdwfB>E} zkdGiAK|X?f1bOgx@CEo5{06)a-UT0lr{ERv0(g=@oJoJ9{e4A0lo#l0q=u% z!AIaJcm=#bq(B~!!G{UVjpuA`(BZ^*PA3K)0LD`{3_5_S-%Q|+<98e{aJ<0r7ROs0 z|1bd`$9)`kaool62*)EF|1bds#}ynGa9qG~qMg0gKPRbwT5tU9u<_H~#y^L?f7<$f zcJ%#p@B8QYXA_A0lN0$_5c#PWLEeEpg*=5kg}efJ1@a2y70CZ=0tE5|@&xh(^1X6e z50vaMP}1E%IfrxQw4E#2ajvBMxpIyt%4suEGJm3^?nHqJC@05IvcOPM&rr_E2=W=^ zGstIjBl(IZR+qC(k(@-RE>B&tU>{I_2jufjQNg2_2*pI(ba! z=r*Aa2}qg8_*pV^vvumC3*2n?aIQRZ|}79>&Tktj`GG3qqQCD$QU5*_#P$m2CG^vT&(nZzgb5vc0Ti@l46y zOkl5MF0W*frDSg=5G!cEg7yn&zkv1&Xup8=3uwQ9_6umgfc6V$zkv1&Xup8=3uwQ9 z_6umgfc6u#pP>B&?I&nILHh~XPtbmX_7k+9p#22xCul!G`w7}l(EdjNmA{+7i4LwO zIuX}&a9Ps{+tWea)5)g^T>i)!m)JF zuypci0yP~}HJvCS9h4!R2r9wDtJpD85e}!tI-O!NIW2aNX%Q~+Vtvaonaqowlo#RD zE!Me<$)sECIJXE*u2?r*fyF?v)3_oWo5lKU76->HcGN7wRa301rZ~VCvQwHO)T3fu zM#aI4ik%P@p{*6`PQzqWD|SR{!@;;+pT_NBJ#KgXxDDs$c6~j!hr_wuiRU()IPLo4 zw1=J3?pUV{4ce}2ECNE>?o`@_qgA_pEe5u$cK2Mh;j(Jiuf@QzYIic6WwW4NzZL`g zpxyC78(K=cZm|%Em3GG{Z8+51b*f`Ap|`t--iC{^UEfM9Cdzgvm2Ein+I8+>G2yj4 z&TB(cXxEK^#e~rAG@%X0cDp{??ZL6z9kttV)wS!YYY#!!?v$<#^`u>wNqg{;b|)ll zXp?r`krtEjc2C5`ul3-6>-FH4=~w-SO8{$u6zH)QNP!+xffVRJTmo1Nq(G19Rtof3 z3#35*;S#`FAO(7?1yZ2LS|A1b50?Pe0x8gAEsz2|AqP&u5>dpGe}W}H-~z%7d;uPC z0pVwtfQRD(jsq?ruvqdNSn>ldAXwmA@PG>lKf44H949yqxPYKq;$&cn1>ge02z&+} zZ~-9&4_|5lxBy%N;)H?+TtMhsaWZek0&oFg3H}ZqZ~@^4-Uko309*p%L;(-DfFQ== zWD<)7-~z%7d;uPC0U-zPf(KjxE&*{OfCpSaU>R{@Fk%6?fM9`d!2>QJG~grffD6DS zAWjH)z$MIxxtjfaU_6Wgyl$W^s1F)}DxhQp6UcBp!*S3S)CY|~6;P7m_y)%}I1bu^ z`k)c00!m;4bBg0o`5p9S0zT-Q2{4D86o;~R50|+M6L1f?;U3Bn_&fLld<#AW?}K;2 zN8las3U~oL0pAOd7a%V{UVuFKJNN>83qA$!gLlD4;2rP^cmX^C-y7m(Za@Wwm>Y&z zj={e3qAr5$JYhU#R3k#98R_pCUAL~z@&wI3wiJ_6DYv9;8XA~6L7&t z;2rQU6A-`?@O=dN2=Wo+BglilgD=3h;8XBEco%#G-T|+G7r+znJ%K!dJb^rcJor2K z0(=WT1@D7*!AIa7@CtYVJiG$T2`YM-z})DUbK_x6jD9gO9;RXRE5mr$A0}|e@jH$e zI9}j*i{mYhf0%%e<35hNIPT(jgyRv8f0zIqvr7fX;UHZII8N-|X8hc4{c^DJFY}FG zOg4UQZ2WTM`sQ`Ha@&xh(@&xiECTs2g?tP74dgeF-#~r?c^~pVPLf(bE3;7805#%GtN06tGr;w+Rr;t}5 zuRvabyaIUv@&e=q$P17skSCBQO5TZ@G&uhQ=7K~ggSi>9(d4ghyLxXMbmVit?R~57 z?Z?mG{*^trKW^mx+JXDE1NUnO?$-|7uN}BwJ8-{t;C}7E{n~;1wFCES2kzGn+^-$D zUpsKWcHn;P!2Q~R`?Ul2YX|OEMCLyxBJm?RR)3mCH_fldw?w-4?78v8+|HfDpJmMPGW$c)9It@? zxS!WH;g_Vj-Z%{gCh(p#N1MlPGDJSze7G8|)|>u3n=a(-DQ0*+S+o|JG+z?v7`bV9 z1qmf@IBH~I33_rn{+*kYKeceUTP+Q1{AVo`CHC8YUy}o+@4x<8Ywfp`!B>hPTI-;- z!@=&ABDg(`%;{=e-4286@HCq+Ru_er(&DQ`|JrJ97YFpS)o8uCBk|?@RMvm}d$(tQ zYPoG~tUqfu`%kPQeQoh8pY+d~YQ6OOmJRvVRC6#M%m?G)X0%AHdNr+-^w{@i%~X5Z zQRu$H42~9Hns{)%DN6 zfAXcK4$OWdj&rfJgz0>ZTna=_)U)|68O)7kG`h5-m38DEHF5`ehC3QT)bj7>_#0); z=4w0oe3~G~cwW9I3M2h`89#q3-x7*f{6Cx4#`x#Wz?@Bf|1H0U$L1|R8=1zR0TK8; zE>BJR*WdXcObRD;k9iyamX%SA4C5c4l&`1d7ahs3&i`^h5GHTZU-j|x8Gjt2uQPtt z>A&TSe<44%`}@`Y^6!cQ`I3qKdQ1I1iP)UX|M}2-^cR@idm=Vp8~fS({v4Wv;PZW< z9T%V4c97_yB+iAfiw>Cqq>BlW)Q^bE|KmxR|L7!KB!T`)!rYU8Tq1Tcjith5ro|9} z_X`BxPZ4-;KJsFS!21OP@23d7Hzl_iBJh5J!22lz?@f*?h6uc0An<;Qz+r=N>Y#ZI#XwRWSx5WxgIM z24WWH$YC-jTV=l6D#-p)=HU~S6qd?-i(pN}FwXJhLL~-ezCWUpf~?GASuut)WxhdH z4`LeU$Z;~Jdu8tK6~p9}d4x&~V@sJkmSP~rasH~rpvTJmRf)kSyouxslZ@LTk}noW zzL+BU!tBrO5Xlz{BwtLCd|`@CJ4Euu0?8LsBwwIe?GVWq3nX7mk$hpQt9FRwiv^M| zrbxappH(|V^2Gwl7gHo(m~zk#k$ka0^2HR%7r3o|$?VdwXFj`zR+|FL!a zLx{(hJ<9iewlmk7hh)$9aer1w#rv?@8_5(2eGTwtM! zbYksM9Pac`)lMp7 zmRwz8d5l#tp1~rQnJckKd!IlPz_fU9A>CFefe`v8LlmZts zKBE%dk-H$V&c>WM?2~T$XltI@v_Zi;-PWgKl91zatWu40?&KcT396YrLahrMq0z`f za%`Fk>bcwBS5!*3iAgs+bz)Dqh3>AKXryFYWz~uI&<_+cyS1+>mv?Vzb5zQ!JX!5a z+l%)>qdv8|@B%^!;bo4ViS(37B~nTry!jBcMsC!?rU^|1X}+ttJ?Q&CZqz;*59H?iW+;)z4)f%?#{Oe3#L_(&!8 zgBRR$o17CG5`o0S==KN{1;xZVtJSwn7c<(PIh=ykT?jneONC^aZGvc@6noyK3d5M1 zE3{BFvrBm9#xaD*<(Xlw_Hvk!WF2z_6x)f5I z1?N<%WSc7PL%tt{vl?efK4PRh`Y-jR=gt;9@9uk@5U6mc(SrIhX&t_q~b z%r2#Ao7mO3mHEU=vX#;)f#(OMc2plUjE|CKn<3vCD-^Hs$w?+xyY-^B1$zL zCdCTH@G3QT1@Csp7)%esKG3Kj@}07o?TPBH6kJwVE%}2fDJP9c^V?%=cAD~J42}wq zHJ23Qjnr_OxLk0mbS{M9*iG_=mmlbpN2TPlZgx{e)RePrr~Bu#lL<=b3S!PsQr;M% z9CM|C>2So=l(nXCMY_AMG@{BFOM2AEW#`Q*CJv$o-Rvm~7)`M`GY!=QY+!j3vt~ke zDJNS+_B%yF&p&N~dU@KTG8%8sC}*bCb$WY7^|`rS%&xUZ$+ktj*QQ}G)a|3(ipP!# zr%fC-qb6S?9Kq!zM%B9gDv|Ebu6xE&!e13hn|1B;*oKkoK$Aztd@>rtjF&foUWJnG zBvlNkzGq~&-D{HARBA$>)-sW%JCE$_ocDI*iNn6x%5>e`Iy_VAXX7QEI36=w+DM$& z2qk+PB+vR9$FN~Jdyx4IIG*OFBim1>=|3@w=M93|xDbSPC6VUX5Dpm8D# zNj??`)d(|N#Yv)hoEniZloORKJ)-cMQdpfd;uu~F)MpkY&m<(#ctlRxn9!s^v*GKQOeS7#dbOWlood>;2OCb;u#xkDC*8Q(sgU^F%Y~eYTKSo z_I)t1ZLSgsGcCD#+6kJsM@4IotM!6c+$tta$*KD^RW_X6j!s&lD;RmQVJ2(d^@)?z z7GLhpoqir^%$1LFXVh;Pa!r<0&{z}zW6CNa{+O4I^;0AP9zMi=dZy|(x#9^&IizMi zxi6JHC!p-*;(Qk6J9`gM#mmoAl})?5eM{8i*i@CcU}!_a!~NN)jNjSNcgl2H+9a33 zopR$P84nNVDjdxAWuxrXRqsvP5qqIp(rDW4<|HRn+gDs$VGde%Qten^)G|RK&W5z* zU_qC@%nVf+%KlzaHqyH6dJ>KdsuIgB-z#&{#7xCpm0-M5W#vXCK_Qn_ePB4)_@LYb z8GlmAE>+1Pj;F1)jU$AjY4q0h_gmL0d@MfvvOAT!Zg18xqvV0QI$DnhdX=QKN={Yg zHD09`s@V0t!Ih`*$q8}0vKam0;Q51Rf6_3s(Rh=fh-yR*d{~#dc1@CAz@y(l*-EMF zd`&}n>AlXcv@<6X)>6c|XbRj3?|EC8iW(PHk|-ZIuJl!AWmzqr=`N-mzIbk`G$V0r znJ2x}l32q~+9Y~lKdf?PKY6W{^~AI)TL>1h5Hm~})z;ZratB5tu_DT8l+^jBw;h_dHRZ@IlNo zL~TS)av8_Y-DD@OXj$(TPwL}olQi8lWmao1lET=p6a8@26OD^y(M`Iwir2)AZ=b$k zr!*naAWIGKpFtgG2PV;)glmso9BM!tHzZ+C6DB%YuHCFWZE#U0+C?RMl<<-cy`zTPhs#CGlaoua z4qvlE45O;$RuhJ1q;+5O55o!^b^nk#!R_jVFs>7l$x0c=PtpmB=B`@2%bo1#X8k_s zW=>uT=JLTZC?z-LAMETFbZjY>w%(~n@2uTQrhbs#D`{`qO2&GQ=IP~HcFHWPLQN*#~5NmQ2C8V}ttIz2FCo|h! z49{2|_-MjOU^k@*~3@RD3&OVWEXH#RPf{}MLiI#q6H3laBMc}W*ed60jbR2&E zM!SvalY3zZJPo_(+rI^;2$Y*+GPSG?|K_>A$T@VpPXlQcnUZiT)F1C*A2BQC)>+zw zE1BY;5e(PwvLDyc*jqvNQQZD;&oa$=W^V@bNykw+B)VyR=nWb@rnbZ6E z?+2O$rsmyl-KD&=+Jt>lanH;`%ps5O{UDPY`*uz(5UyQ~nv9Y4!!^F#|Al#e-VfJt z!sp*%vLHA~Wky4-#^X|OUiZ*ks*KZKGP9^NhE?-7`uL;0>LvUeef82;wIJ17e;J%y zG4K7cST!A2HlC((GR67#A@gAPqegL!OI5_$gvZm(uZ>4ncP;8AzWPk_tIBSQZmkJ| zO!2<{==WQJ_onc&6TfRh6Q*)2m~ET9>&>r(E{d3%!GC|SANyq=SJd%r=E8HU1nPmW zRlWU#&W~jLsq>dkBFl$DiW$UScKBWY{L6OAH~nJhMsN4>(rHqK3fk7Nu*y9@(5I%^ zFvf$A$M1dJakCYS>cg&=;`r?vA8mix3sWC|vJtM0*w5=5O}u~WEA$PuzW0qC2k-3( zR*GM7UZiPtDZk!xZN+Sl{(~jSc&2nikEd1M3Dm2wba@{u9E(}VPvfszeAqwx<oxX_cujtOhM@l>E{D_NTJT%U8liJrSexoa9d@v#ID#!XG_i^RCDwz*A?nORvN+`| z?OVH8ANW9~E?zj981GkS={hot-sU;^8gt4?It(wZvcZ_}R-#{*Y4P0afpiGFJjXx` z7Wgqwc@G>RZFx^B*<4!#g<&Z_mg#G?b;Bwy^@rsjE-#0I9C3JzI@%I z;(pn@9+KHIpXVP`R2q}K1rExx`?0u$>-Uj$RG@Ad>Y)>~hm%SaRb?#DiO*_>txy!U=jZ#py{9385) zpi>96=x5sF`!zoFtXuN<`mSTkV^IzNyS<<<#;<#``18A+YnVTfvH9wYnP8jmcFTn4 zLjOJvSjEBT<$AS)I*`GKmeS(;_}3P!@;X*Ii`OyO0uv@R`bBy& z8+6#~xng<3{;loZ{ndByt>T&)izT-qr?2~BnEm_nPqR7=PeWEF_$pPGpC|Sv_VH1d zFi;h5WIh@S2M2V4d8m=#=YuMcp~)a&Fm@SHyjE$!Zv?AaDYNFHW!AIZ2!~{`s!P11 zhp)M)sO*aK(^3Y~O5+#w5A(pUbBU+dHw-?CAhURFf0>`Y&kJsa6o0&rU-Qzp`JT_o zy7Dj0Yv1MG<|U)IdCA9o@ylGURz&aH+`-Z?w<6}(ci_i$KIXjskMFwD{ywjIom@ZvnjwkG9j5zgGuL@o;A5^PylIC^=9-}#{jrCSWBj3yKiVrT zr1Q7A^_RYStsV9b`RFe=^`f`yZ*i{5j$6#_FdfacT9WXj!~)Mtl7(()uQ{-$3i$3? zaQK|p$kf~0i5aKZFv-jA``p7ZTZGQP=8jmDO2@Z;#Jm`e^ta!SZcvLj?AYL8=ydBS~I&1m&c`82k_}RZtx7XjU@tW^`%tbZS#Q!|s_qpwtxoh** zSLmDKbAIZWaZTUbgSj5{?wwL8zMZS|p+|ca^SiUeV4XqZ5$11DOIr{x#$xHUHu5VG z9=|{6Q~jn-tW#1fp1!RQv52uh=Z0VJTM2{)@Axjk`r$R#*8)3&oxZOXzgKPw@A|`rU)V{4lKjxxIMUGe= zeOcdPe$9Pcr+!E<6L3xnX9V=&x@o@v6Pg?_U1MIHm@}bgX!pu z!eEUHmUs*EB2Z^xs_fH=FlFVPw*#-AaHo#{cHr0Fq8~1z+O5Y=foA!lD~fF(RhQx! zNViMD9DV%MZi8)Xq5-DShM z_ZVGP{dqg>o!dxUvBRA%pR%F0_D9Tok|vYox*M5uZ)jasr*1u6C->1@=Z2S+c-l_Y z^=+ii*)*KU>vp6K+#z$FpOW3||B7>e`1iKhFUC~z+`=Q^{Xf*cze_LhLfOp!dpiER z`ruFO{v7OV>;(n?H`(`pRNH?Z^By0@Mabmv`AK{D|BWuP@!Y9MS-_7DpSgr0Iu5b1 zrQLa^F3S<>RKx~fYj=?z4L&HX_>6KIN5a`l|Uf&4EX7aCNf7UPXYm~1(I+quGGn_1?TD?9T2_2}c z7sif32U_gNnc6rBZ3dTL$A0b?6??Q+e7La)po{L4Vi}p4ULMm$Ryv1N;;-9Z$Nrvv zfp7J>=VS9EBJd>d5e)Z$O!-fQG3*;&)Iv{9q7WkG}V%qGe*wa&Pq?}=VACnYc zr81sLg}9-|q1m4|>V#hW-{Z$Uy^p_zFNb~Y`MmE{eO8+>IU)GI*GD_}My|>az8;i#s5Wo=!OZp#{+01eclUA zZsN-yHil2=)2{+Q=aKE+WFF@u?f>B0{OUi1?{4;O~@&(dp8UP`O2`SJ-Vx*4pn1^mTN^QLozkbUa>!#^f1 zrSvlf;fw94AAZ2A@u^dIO9kxJ`f$%gSGHMCv2o}mhyuz_fAssj_7sxM zH=n2cM5y~$KRHjm+czI5abxyPC-44j=Ix%D>gCY#hU6_i1isr2zS`wyU%Y(c@Z)~? zSYKoMEkxqwU;P$O0EhKYV-&&d94_=_&wKA^pV#rv0`t}uoEsZ3pL_2g@g|9P+@HPu z>B~HH{MrkiS=noxo?qism0j!yD}+Snr8?oBia3bS--x{_@!Tx1TN8+jDiKNR>y5J zZ5B3!$~18tgt*@33C~`1>9`%M&26FX*^3IHc=})Y-1AOiYIZCiu`m5o98A-B%zr{( z7}&4d(BQaFBJ7C=VMuT%<3%sTl9z+0C`Kl>M)uT8?mr| zQ_mXDjPTrkp0&on=SQW^7ftCu&n`M$D0=UqN%d4s;$WD(@&B@&#w$b>xBo%59KMYw z5;$X%w}LoWXz`Qz4?6dM+&JkG3TZz#rW`HAaw2#m!LhXs(bfHB^%T6ZHxD1Zbee~J zL8wq8(2~32tDyytK->AIq1}@{@uAdv-phJe=eu;_uIKT7vY0&DjYd@OjGukn& zomIqRh$$suD0Pha=-Py0mm%&L(lJ^`H?t`@I%2aB&)75QnsTsx7qFn5GoK}_F>!s~{`qXSM$%`u6 zw))X+JK=b=#}-$|B1st2;N7Xvl~s1~^QX+o>*|Wnuup1!HN2+?Op8pn&dd%wypr5l zlaSA239p5^cZpXwRk>5I^(G~BY!5t%CCXz~nBhnjs>X`f5-%`* z;g|Q50{17AEmi)CfY_kPe3@j;`HBy)w--rFh}D;oHBN^&Mc`WDf}mjo(=nU7DAZdS zJx6V8yvEyTe4jB|@jUDeL*q!!EyIVs3U6W3oa2F_Tou_CgW2do16Fm@vg1iDk{sdJ z_`I(&J?U+Cx!w*8J?VwM5M#csbd`IuM&t|E*{Datl4nI{ugUjIrL*U4He)*RMk9k? z?bt-V7JkB=_ql-2JWsdWijDa^5S2%_an!3U*`@E@jsmuusxYCRf=zh+c5JW3NHNu} za`tk&%6UhztnH@4rr*Pfn|6+u%b|xqHEFBLWRWv4; zv`uNq2kYc0#t5l3AD2e4OCxbeSPma&s+cZ)AXryM07jjjSE!b^+BF=Fs)xN3^mEni z#{5dLZA#knjg1$KE-2Q$?UarplzDw-S^qxr3wOBSn9XJEOo_I*-c2j!1Djp5Lhdz7 z1Y&qRYqGY8ygq8xw!!Cl(QkF7-EPdMvk-+^*Jl^;$%@q$1DVzreoDQ->c*~rj!8$&-t>_lrcjsX=pZ3h{C4t6<;2?eW*zT~ zYI*gioa2-H8OqGD3<$ZS)T3BCx3VmojgQ!IgoG#oNz|*TDD{q8wA-vp!n-p)pG&u! zhSr_srfjx-WpMhf)B=99>2C(_XAiBDeD$vT(_$ou+EW;k;6crD#CS}t z1`l?93UA)St`I>z8pQYG91$xO?-%l*uSl@cAETt+om3?ZQ+7Y)JWhGC(mnG#Z5ykM z!)>6L-(y&QE)#NYbI)VcD=`;(mlpT<5s z0z5FaIqSuKk9vVsw6?|ejKr0L;B~deNEdbNQ3;=;R{4om-O9iVk2lrH@x*ficF~up zb$r~`WbyR*OETeW314`}vcSI^Zc{(m-rGk9Gd}WiRIt~*)Xjt>pwr^9R>Fiqh+3_T zo?XO@ENSbjbJdoKLYRaSL0okPm<~9PxRx_oaq;x4>$0DmsDMGq?G8SMfE;?)!s#n|Q73 z^7Ojmi8&~c1rw^B+>*{dE=z>UbMtCsg?6_J&#r~lt2RLv?Zl*MIcA&TKIVwx@50J^ z=oJ#yI!m2jj$G_+JDUsh^R`=cxukUl)-2ijHs+A*$+iAsU_6&vuBgVU_ZP@^(gmAH z``V^#wczM;!JUrLYA-bHcw5{S?0KL_>SPhc&P?S(EfoF!5bcf#w!dyDFImK=o@*WU zxw=Iv$tGu{YHrm`O{^rjABW><%{^hfHlC>it)P7=#jI-_x9x6VY0YrBX7kWxS5d(F z*Ui%5+?6o%RMj0Hy>^_FnH8A$did6_ZrT`;Gy)QiE)Qf{JOU*1oR9>ho)>!jEaO>i zJ!V^jpK;YywT`2b9>^WdmfF?!a>hgKa+UdwT6^oI7!gnYzQ?4@0%#r}#c3M=R2B-QSS@~h5L5~sPa zX>ST?=HjlYHK!BWx~e3G@5ru~$dM=~`@$(5Kl2ndT~D5?)$jDF3H|$h^nLpt1KoNqEI^5@K{Hh^Q96PLdD**+l+r|oRYYQ%hv1e zyChLM$94*&8&)1$+98`iFZJUZlnfF*AJ*mWw2piWbe7o~h~TbcjEw1XnV$kbRu)y( zQq)h4^a8`=FIZ87v34f%3!{3Xs5ZV~nh4rJO0w{b`g zXo!raHg`^{`xv+L)>a(9duIR9&cAuiT}~Ic|5^2=UElg{N7dEm`It&-wbwO%Jpb$Z z5oV3Ae*8FpeC2=OO)Htb>tiGD6is>S&sYC_(NWtPJ`(%o3v=Y^ak6UIXwG;YhrF)p z)y{i@b<#^;WKY8tw@@*C`_ONX`%ipX`Z(W(^%1~;PFV4%mJomIqhk>ulKY;-1DWNu zgnPXIO=afL+m$ABuJ)D9*K_`$>-$>r$wxnb>4Str1%Ow-baJIzZ`V2|+rqAz5Szi2 zCHum=+m!>4g|{c$aug}A`^)dlLOZCA5}hjyYD1M zdmBglgHAU0_j?HCb%&5y%}~r@b5t;Tr#-BSVL+p0J?(HfG-Y4|c``Z+3Q% zlhfPadW|8E@9<%jW4AB6dYvMHgqmv0KgwnnyxKxlo%^r_=zj2_mscmf&RZV!H=VxQ z?y=~QU-D z8rA!ApkTzGuT5O}+iy~s;7VziMkfwgY&I)AK{>!kT@wmSlVSM!JP{?006_I}1Bl?R4v)pPu@_ zEf-;?jHaSlsijUObs?!kR8-Naa`nh*P4Wp73}t4_wMi-k>Ov4#os{Zjzf02Hs#M}J z8If>mFo%tz&UA6wsmk=_GZXHY(NP^8(_LBj1Ddr_&pIb`gmkr+!=f_WbReT;>!=`Xc@-R zGS9Vp?|1$}xzipV8r@53Qc=&5m@Y966}i#PR$|IsjGv$TGpChf%XAYtD8wd4rd0($ zA#B0jlxZ7e(y?t-$@$UV<@>dHeITx(?$UMQ4WAncM2~wkDTjMXyi3^{dXR^aj^eeJhpg1Qk7= zswF=dd-1eOC7j&xf0uciXv)exYpAiJm%>YV7o`;ANIQ_IIGiOSjB+PZ?dkMR!y^#` zlX>DZOaa4D*VN+0^ifHdkfQX{b39|tMoF#CdOj0d-kk@McVUo9L!X!t-bjJ00A#46 z2GKc3aSq`Y?;BKM*c={?9h|npO=AlZPLRdxpP&u8Em5II{pX-}`KW1vRNfvy$$t3w)9*PZ<+{ShN^vdcuzTE6) zKMdeXvr{RHBC9}5GtdrJCq()OM z=nr?EavWtno=a1%E~$`m*Qcjv%|Q010zZeN2Okw-8FRxR3}7)$o{d4Gu$gL&GnM!v z>Xw+IIAS&4RD*{yOXXeHxp1PXlWfn#*-pksy)M;`MfD3dRjrdG zc2i4J`cbe(S)44{48rl4BTJWbqa+@cb-cTY$v~9H&$L_KwdIgD;;1>y+N*7@#@bS+ zV=+F9v-34Z!r3+@W-t}(gmT8}i6Rp>skG;05j-|Jqb~RVo2@t7QdNnzML&p#0D|&S zL`6kGMMU4E2oMz!slWbqa<0A4xwqZ6iZ-2@X$nL!qE~}=krXg8`I*?&(M;z_E1c=> z({LKXI%4L|E_e21=95_-K?F&CA@K^q9yvKE0!u1xfFo^bv#lYOAw|RuxfKt;ea)hK ztJ;-r;S8QeVon%FD|eYgK|xx<8`$=u2%)Mo-RcnV71p}cBpG)mzlIr_X?{4CEnrP5 zqglnbcz0)ZNai{IIFp@h`gb79iXcJ_Av+fZu@H6{=w646Dk+LT+vLW1)|p%J4C{L_ z8C-r!2kcF<#GokBWpViEZGUCJ*pr*}Bl&1zz2S=9MyiBKY0AfD&LJ_U7kbHRPuiB& z8w(4y+SSO@k>yye$|HjYKrtIhr*7%$*mA4`IroZ-sH>UDC*_Ki*I{~od~z$F-12G1 zmfEsYOsp<(GESyEo4qyPy_~do+v?kDEV`q0K;BDozgsX8``-scxZ1udL^o@eJzr3n z11P#`7NhV8iebhHI3vtF3HQ}~-v~lu5atulTE z-b%@BhNP?`i7lUBk(c`PxO|HKiHz|xA3u9t<$@3GXq(P!f4uO7#>u{JFRw1Wi#a-< zgy9CsJAQKA_xYEcJs9PwlNq?9PrU6gSjkhnb@>?kIKVe4^PRwRlQiTLz&FzPLBtep zqksdhtzs1P(xyF0{&9>?% zh|-_hiyvL2wqScLnOi&w%Ut`5v+Eqn~ZLD9GbYnq%-SFs{#3DqHfrDiy^VQ}9SYie?G7D4rzQS%PNdD40@2 z0U~{ncEQUe>#=f~V+D#UGtcW4bj%~TD__$gyyj1b^JW>!>U8w*@o2lEp zP4+i)6x!iI+U$gndm>6_7E0y(u!;?dA8;^gJ*^LScH5GzZ6x_|=7__&Et<$=Vb0hs z+*sx5)zuD2JR`GISDRyDYJ2OmOJd^u3eI%FqwJI0ohu~f9?%Fd7Ly<4Oyb*+VAA%G z*o1xyrY*mYhSEmib~&vG(_OsYxeL7iV@J@L=S1;iT8tX=t5S>`ot8C!PDqQIi~K70 zr#&#Ef=VM|E^bdt`sHojT9*Ng*k9LG62)C9JVTa^^hi-t$$EajFw9-Bf_fj`;Q6ANFL(=7|6w7x>%gzHx8QYwXldt)Px3)@# zaVd6N`HW1!=co!S{)tGqJOkDEaU!i6QC*?uVyzro#V-_o?NEk@9?A|s=ADogNHD*i zZAH2nQUbqkBHR(vko6MS$NQLXQ#bg84x z`^}DLBA)~<{v6a!7c=P@_TMls(OSy*bg0FGUvy`h@WM)s+6Ke6U|^<_FB%cPV)>Yj_$yetp;Iha?;^2Yp^9jDg)A^CP!*;PA3QprcL z-tspWPE(_8PTl7xb-nmba2WRF{R^Mw;qY8k+l3Pc#JHv_e-##a=|<*!pSVU^}LsE>N2z5zd}E? zGQrs*+NUwip1aehs4kEZbnxs(Y)*ccPhp=pH!4bFt@X*lna;8q3BIQoW87%lyYtzn z0;W%Uds9>F43ihGdj%m{B3lWkd^L2W7D3pNRyn)Zw%00GRL0BD6uVSi$i@accL{rB~-zLLx9ly68uf*;3nuG))*p7a&#n|tYgJW=)h zg3W>3((}6txE9Z?PfGiOM7gt8WcFJomlg}HR)zMUe-TXD`q^DL`%>E@rK}bJWl&?; zEy~V7T46NA>h8o9)4p^1 z>;9_ul4JPJmiOFed`CWtv)heNWfZ%sSj7~XxUUkTMvr4aDkNRJ{Lb!<{^C-7Jjk+S zh=AP?6B)q%#>y_9zhccsb1t1PYmOTIh zeFk-H0+ol>lXKcGwe4$~nk0<`v1rN3FesWR$PARa1`m)4WeSIlQ-sJk>4$b-$<(ldF5-5bt6i zkR;~LrLQS+i}>GlxR*M(Ax*IPyN;qxM zuNy}&tlrwlybfsFG8Dh8NPqS#3$WuEv&k;BW#*ax5IH|JDnfz>t{WGRXK#S3dY(oC<2j=|^>3 z#0Mk=W*9R#@Yv>t7+$mW^`ubS57ah$#bg8RoLW7)BgY|!6#^PR?yBS~S2uR4NOG?3 zd$sA19d*)uf9N^BtpwMC#_3^`%fS_j3iNMKa=G#vTjU8NdaK%FMV&J%C0>-&xTi5Q zLg9R|Q}u}J)B`Rgnx3~(3tb6!52&g=iHrp?Cy4Lt3Z%WihSKzh4sE zfMrUj``>ubB*vF5%U#7xzSI562X~mO&_D!3v}je`937$jB)XyIx~Sus1cq3YUkL}4h=WN7xTh$rU6=nUPH9c|F|M3iZvDXw6#FQJ=C zEV!V)PS=$alo2J-uSxyTjRAA+lT1CQ^@{)KhKEu z4B3ob=70Cqkd(J6E1lw;A!b9^cO}vl_sT}KyE}e?W6reqhHco^VQ2okw>9xq^L#B* z6c*U1An(~hDUdGA^qjR}-Na<;{!lgvey}o-Y5{esg3K88(yH$BB@6B8DBu}UQ5W`} zU|X7VzG(t?M~!IVA&-`7N>Q<`m9XGV+%xHClx^fmr&RI^KZp+}tRICO%bp(kV!iLT z086n4_DaD2^p7(`k5{atl*`p4DfV+K-4@%q7Lt`Ge1S0a!+ni9qL@9V&tGzyXvC>n zbHYJ7v0w@Lzy<3Z`*jlU@D+EzBiOstH!NrpgF9K%{v`O!i>HfdlInWL=j0ZV01d9LGLE7W{p)qdN_f%Kz z)MM)rm|qcf4eQO{oN(tG63k{uJeqUu<@pX3bput$-|t~V%IxdS{`*XK%^GTVu<$Hy z-rpDcJw`-=Mp#`&*J9DNzL%6x4@mobD zE9n7&^Q(NNEg-d9Liu>F)u@(+cDDTnQhc^my^^zHBy%&u>(CZaj9bMT0bcJ7-yZcJ zjq$nT%Z_OIlZpCpAgu`fsFn>Ob>v+rR*ag>j_m9L>eQ)bRn^T~i-C4NZw(DD@-J0s zd|e{zn8gPyHEEv#utTSp#47XCQoVvhF5&Ay={yU1el#R*#tuSSQwRA6y{f8eCL4Nc2gvZT~RVTk9 zv|U|#o}mQua8DHQ8A9?`-{1MBUfyu#xwJ1>&(yBEG)Fs`H}KnNEB^=N7bpPqVRj@i z!{N*s=0T&TY(cxcP1|7ZlkN53hK#nSu0H;NzV3hsR2G@9Tz+`r=F+)g)3 zR-bHIGjZCMl;kDyx?@hYl1J`}QOobRs23W>Z;g4*-GEO8(u)b%F?XbDI>&J8=R$Gk z*=#<|kGQ9Ksa--(;@ytu9l)7VDz=yK79M>+ME!Uh##+aq&0;Yg*_FmLwvYGi{IW@} z>o!K;kp1Jbwzk=QyS2i?N?fJ2lARt8dM~II3gu!1m|wU~7*O*q;@*UbtEnnyN`?h_ zaQbfT5DI(#fy;K%b}s zqvz&F?Gp;pm^-}J49pnxdMm5+aOXXaC$lAI9l>dc?gH%|Gz)8ze&ja!&jd@n+j0dl z9IQ?AHibA^9V~f6Q6{H~)sJp#Sn0PlOfd_hp1dUqAIL1EbFpisr#Rau#++N}IJ*XU zdT`OXB-5gS6p*VW05D$)NT?e{YjSJ_Zo#?>H;cK&M*F!X^7daM)T#Iaz8FAEnnV%$ zH$3TMQ@r?9(z&XDtjx>vbOb*4;^r0lcOV&G)Pvcj&8lZ0i=>gqKuU_bM^_MD<=S*+ zW!{CZNT+&k&CS-FA>#~qO*W9e!dFC=2tv14o%^98UNC2w>%#<@VS1xjmIS~YX+ZHe-p^42*^*z8D* zhr(Wblcb6?f=yGAsD6k~x1P+Ok>E>O!PUF5s9WV}gxYOD^AWHLbjHV) z}F~b)Y54WAAXdmbOKtF;qsZ>$z0nU_1S>nQRrqBCHzrxDz&8 zlFZbNUMMtzxM;(x92CB&W66Q@46N|j@1?-WMhn$7={)WsJyftggFfp8if#l<+e~Xq zA?#$TAu71MxNiL65Yu&ci1Z>Cyq~O*W&~f1eC;rEv5%<5@uWWK`HY}vNpGJHna!PW zj`ryU?Gw(@yp)dKA&L#ST{7vrtpVUL44P8%ylx8`cD~d%#cxI>K;{dQR%k6_c&X?h zWN^W2b}5DzhZxdq|Ekmi^UqiDdUh>60A$*G&uQc)HJ{wCC zEOEH)Vhh*ra8L}a95@K`qA+Wi$>O!_N=Lx8+~8tW+?DQrXNn0(z4nO&)^5u^H1DYH z&{D`6YqnsVE!+v9En9k%%%PDsldlPS@!Z}L(sQgc>0WMC^2;6S7wsc6Ucs3-a23{W zlVdWQYv>3uCP)rooMEwq5)Ht+8bDZ!TXwlf%d zHADC$?Iv*%yKBu9#rh6uJM?bd%{C7SGJ&^!KiciiU3Iy$0qmA$vC+Hh{5N*6?@2Tg z#Q1j&(@k6rXnPY$+lDuT@8}D;+|KR0S^3T8vCmVOa)mA4X0=Bwr_aQzZ!LO5?!2Z@ zAmGifi?_5aaFyGW`c8|zDeJ%Q=_!G14p?hElPC3A6TP)l&9hAUFTO6fwe zI5+rk^Zo}vd*u#Y(+-^zcXP0qy8wB8iu2%+1Y@J50)~o~{TA)LqCW~G9(1Cdhgg(|WJ=z%FeD-;{ zZ@?0!ucXs!A7ry)54)mM76vB8jrF4)cFyK4i|2a$y|Eb|HFGguFWWbW#H7eZrBa8U zT;1%<=KabcoA%abHk*aOTR)m|WJm7Qu7hHC=iISe>a;UjFCfP@Q9hhB>L1RMo1ext zKXz>gLBEjItueccmByG(TPxOPDi1%Ls?wJ%I;R?2p^`$?9^6ogob;T*V&;;CJV$tV z#4En|jT3HWDv`V8IS);VX6MAXLECg7&oQPQZ*@=RWt-xSrajz+tBnLfXX6(AFRT$i z^&Pa_=Ev)fh@X;HH}J2VfxB@oBJ0FqENmMr5o&`PVeJ|68>XP3RH7ygFa`@-wpzTw zUtO{dg2d%VF`1z~2@d27aM{qphuGSM%4&c2h$(P;)QMuN{&PYjm<3(9IEXgYLiumU?dl5gxrkHlmt+A zydB${Oex}zoZ(67+bYVE^&mR%>lSDOw@Rp|pS)f)S~0y#`6EgS$v3358HHy_f10S? zR>N8q@2om<<*C&VL~*~zBtPuh)QjQhXcuF$dGSpKp5_}n>jIGjkJ!+pGr12TBpjJ| zE;wX@jPS^Ota=F0U%~~R6lJ1A2F0o>^i0$04ks+qp|8u4$f!{@SBG14=-SL`n5`4k~X^y!Xp%x(zWbLzsfnLI8eu1Sf0a9LG!-qj3j9@`S`595D=ti#uPvRQiucVfBUL+c8FeGc`%aI5t zARO9$uH4oK4myJ(q|Gz=5OnI>3mR^qdtgkOo(aBn=YC1DB;qxX&J+o30L(?+_Z}j zyUUU&2rn6&A*bcle}-f6X&aA4;gj`|0*e;umDA3$Vy2??7=0!&;My4a-5W5adT79$B2*Gx%`-H2n_Yv3lL%d?H>RS>)Bdg=- zwURQPYXYT=G7~+ECD$#4lzD=M6Dwj#>J5wQ-i100&G>o-+;$O%1Cg6py*FP9N6rCl z$DkQk*DwQhB&PndlXn5c?m?q(weODP3w7<3=EwAbxA-hOxv5NM8}7yGH?7ZI@+m%> zrJPfzo$6k_om=e&a13w~2P( zf*;G@W|RPPN12c(Ysq-ONqqWA{h_!G#e-z}6*@@~T$r*2&jl_n-dhv3xN{*(2>ZR> z`|*iGlRfsx)JC*qgXN1Z+1OnDUOc%?rX79hz1$t~Z25@ApF&vmvah#?Cqe84$!aU; zuTY#xil*B_=Z=phHvq}~KhllX$CrH}Y2`L^0#`$)l>MXq*6<@ka6`01+iF{wJ(&Lx z?}`h-lnk&HDA?5pCFPV?5H(C&(c7hzBV`B1cyM2UGqrPJ8sKnJw_-=7!pbGzn0P5x z0Ou<=B0|s3zi1PZ`^83&My?Vl)D}DucbQImpiYfciGJ`Ps)bnw@&A!h@vF3#Q>{F$ zA#MPSJ>%+LCL{6FV3JUXT??l74Avx!Hxp|8LWM6T3U3cr<0ILp0l>2x zzh7nnjluWd*KoA7J`!YrQd?~lR2!;cB7(&--j^i^aXKWPiyl|#V3gzl+;wm0$G!{d z>{i^6D*YLUldf7a0xt4_!}~i?pP&=k%+GvuNzlaQg}b;J+3+xiRib6FUgI|1!>{^w z6$r^kp$epbs-959MYBuUD-EhN zQop)8Hatmk;XY#}yUi>-7cB6$!qH{jt;6(N;T|gY5{*0*v5dpY_!b{sM!@}NIHIQ6 zYaaLhc|X#=&BEH|rAuEtO)nTC8rrDrmhZ^gU;R5N07GdT5sUq@HT?Fg_X~8NB&+_& z%IZn#Z&}A9#A^X~vx)b|<yQPr%7$nn~?8T8qvfFdjbWegsJlM1~*~KX0 z0Fo+5+A}&pwqA$RJ(OS!-~xfX9WBN;l+6KW#@0^SdAn=Ko>P)`027W5-VPq{aq18oEev}WQ$S^-LBBS@FG+6BR6PJE_Q&GpFDqoJ+`$^GNTZ~84;3zy3 zQ~79&xq zHsfBS_W4^Y#!no|YtE8R4(>=5-l~s|ey!SWcHrLa7aFP1LwA+L5CKlWF@d>gVX=$_ z)$NX$Y851y4Z6tcW~P~`Y!m&aBbvQ-F7oICjR7nYtd{x(-zTlUsBynHSm`@uOF5oz zT3LrHPX@emrTMpVl=kK0b9%RhPLKj&Qk+$!=BvZcnRj)oY6~jOa=u{RQ8cg)$mB@g zXW@sQQ<_DuOU%!92R!fTwdno#>Gphd1iyH&5iPQdH=E^&M;;`@l)M&Hj?h5lG483z(adSF-@4(@E z@6k)OP|J5KzYeM1xeI)vDgho;NXk8j#Abm60hehn%RAi* zmCEri@%NjSNVIODZLLBR^HZkrcz$Xu4T&+WmE!(c(H2{xiajsHp0jq37@H3gUY8fW zXlwX$yvK$vMzan7J~I@;evu4nwNhQ?SyWTn=6mzeqV`PLyRmM8uO9k>plh_t;H*hk zn06}g+B}3AnfA^buluNL^F*umvQ&gyrHyts)*ZA8BG-@@bhHlTAVL`?WJ%Ac|C-o9=rCh#)eA|yu@+(w3T#Vf>kd|vk>w(*~G zV0C${_xoKf$D=pDs!pW;rgc3O?G?~8Z&)BY9eYn3kAYzsUy8bGxF*_Vz^vZX2zWc= zXrF8{;q8@D- zbMO9smfQ(OyR2)m75}QQen%utf%dD(Y96g$=p5I-(2&qmav)VLzmzuHzdb~kMdVl! z@g;4h`VR|a@hV96T8G(>D{=7m+-dz=Y0xw^t6V|H2_^JINb2&hzS?wAjQY)2zb6Cm zITthYD=mBpeWOuJ;`1Q2?}ETRu-IzBH2@m(a>GuV`?~snYe?b4D}k=}%cQUfWTT3h z=qK^blAMgqS(vyz_*Tz(B3IN_#q95W4Zbc3mH(b8+8Kxbq-hd2)4pH9H-S!iL1t9* zU-;97b@0Y>?f#A3i&CTfD}wOy_q9#2($-0Tv1QDSCHAQh(QM!z5j24H?;?18XU z2QP;j=&e=2gW+Xz;@7>9)hZ0oN1KCyZ|9JHTHErL!ZZpkZF%U7mLYYQPD)Ir=5>g4?Dv!A}m6rX$#(jBN0T8u%k|4gD` z-03pbZS2_x)_1Pi8GoOJCag8I*l_tK>g`{(P{CcviP~q=e5%7d)lFM+z8Y$8FZiO#4C%c(Bpjy!0?Y$t1PRpE+q$)o!l`ygkM4xf31R$3s& zV&kvo@mN=cMQi*J)wSH9x9@OYf7lApH@rX6x$9jYv(dv!^vHLX&&oMM3we z|Ltcy(_Re#s>_`7W%O%7dpvomH0hGf;qBv#RSaVbq!o<|^Z;u<(~R^1n^*COO0;9H zm%cAbVedz3a^$+kHoT?XbWJ#0E*FPicRmeWH&=gZqc$@e|HD6LAjN$PS7edw;t*Z_ zRC;-$*HAHDn3F_fG3L7_(M)t#)Dc@VQI*}zN{tq{lmG`*l~T1kEW6$Q&)Y_Cq%*Wd zCRt{;FH)FOx!>4+$$J?4u#;)FzII_5ya0GX$mLq7-YtU^ zuF~VL3!jc;#NFQfI2MSrqc%TE+pycgy-ru!zLwluXBgck7ww2=#tvx(+VUk?wdh4_ zJN2zuMN`(!``vr8{47TnbXgTXNP`d`n(T^KEH-6xrQJMP9WD}5o;)M_)Na;>jl8@9mX z&Rup@V`pBG2A(ng3^zUpMz_qRdt(VvX*UX*y4nRs#u`U!SexJ}%8;TAY+V&^m>NJmh|%iIdF6vc>GE+uJK5V2#;Bjz7jmhE7Ju95zO<~E6PZGr!fQIT* z5z%-V{jL4vf%5*v&}elIH%6q3ZHJ|z?_+oK`2H%eO$Ux~AF27kymI5)5=|oQ&(1-0 z2(!0To&r{p6H(ySUJ!qie1u#XDro zoNnVAt+EWq$tbl#G>DtC^ciD=ggtf6aG=y&{#vmJYT^3!wsN%h09=^-5{Q)(+v0Yw z666NQ(h@p*nQTU4E)2aCaFEKi;;BWrktoRob`u`f_(?1ooypPnd2;{{$}N3>HAEEQ zJi%DgR{&Tw@ea3)!*;I4Em^;uUav|F-v`M{fuXmuA+MN@jNEqC@5KC3{(@)Ay72ap zIA7@b4pO<&HG9-A9{N9nqBkUzCUvfD3efqT4Kw^2eQ8PX1ggB@RK2u>g7%iN<+i4y zA6HOO!YEBa>0j>2yXjrS=dA^j{kj>reBOLS6NVC;bR0RM*M{m*5ZevzTq#{g@Sq`- zR*9bL8?TfyjdekFmDWrq9%qq$IBY~1GUavzHd2nUw1@_B0L9Ax!QTISc61inlv4w^(1>FLNTmQl_ z*jaR11nv?oz-H06X70rv7-64d@vPm-yd*dA0?9}a+=tcyw}zAITVjc6)X7HcVcmQk zAGB%0+rhnG{BZS7X8Gq1w(Fn0Y2cbG7a@WGI042tm`oPRP zNBSg?-Uf};As7HH8$Cy8%^+De(emIWP`jMC`3q?`Il0PXxNLL4-Ui2+dVLD>2z}3> z8mwGL61e$I`|7u6;I3V{PNBI&;H|Xl)N5DKgaCMKO}v-EBq_*^BBH&E6So?0t93A5?xl5AF}A z$1N*pqabE%FJ9|j`k78t{@&WPe*_#wF~bLFXa5tXDzvfp8BO;;9aV}eH{?xhnl}*8 z+l9od}O6U}Eb)awrY;{w6`NT>vaptRV;P)pFXzF^tp{ zip~vJKYeV_@z4%~m8N*ExhUcnjCQYJ?^z<))ypRogFPG;u1Gi0YKY_mvg7%0)5o*D z7+g`hHnTk+NNCB-EoI(?%edXsf+HliJ}}zaRup8dsBsUQ;&QQ zkI)$fPo^9xznAEOq9P#Y)rZ)Be21R9fS6TWYY9$Oc5a znyEON?J39?m27kYiODUWK`GV_h=UwwM}YOn5y=5)f)%{qcCjuy8dL5LHpHGOjZ=#+ zjlT9C)Sqpn0WdQbtl3 z$!A%;xDuTs%p){27pLO)g%Q5(PY-M70X@QfviL-uemb&tW9gOVL8|7Y3Y5^=lbys2 z-M64c`8CcP=fA!HQF7a9mwXiFHu#zU&Y+Hl2g+K2QQzR^Z`|q6>kW17!VE5jrMqF! zPy@UURF4+UO>&F%aKuQ^Po})WavAjGRsGM|R7JDdM+3AF>2~mg!H2*z&`aD+slVO@ z+P$TqI@zUSEwW~;r5?*wxscMYYB}5lW}mIYK3U2;=MHDlrzvNeI3tZIh~$O3A*#r? z$$bbiIsMzzJE?fa&0OY-^E0x#)CI3g|!N~~YiiQM$L*`1wAM+fIZa%)hn z8`K5JHyE2pkk^3Im$>Y`9FRP9tW=L<9TW{+x9E@hr{A+4;1;2~GH{y1Dd|RReW|5q zWo$55ICI0h#la`i)wK2?nmXk4As(c_pY4a;!WSX(7Lr_gZJBo}oYx#Zk17hKhAzVh zdAKNNaE7hA4??Ur;AI>)StelwoTo*Kun=@gdno#|h5bFbK4Je;M@fNQe4H}Sx)AP_ znfgHLy0;YR5+eE^w2re-I&oA(pW2gpfG!lFD0#Ah$_p|}Ht5}sMc_=%;S(S>zpX%o z7h}-9+RRFd;{8CTK~Pui$4wEgTIe(k?i=-}oZ4-n4iMuY!IxSbaG96p?N(q(m%8lV z2sLY*oPPYNkgeQRnbT+<-6i$j zt{f2Wuf5LFqG*+E2445vX*Z_I4>&JSM0==Wr~v-dhtRSNb+DuKwVZe$*-gTD-`d(% zqcX5pC|}BjmSiF~yMw#&yLQoV8}zBp4DKCx$##5C`~b9y-oDr7Xe;rLCxQd09TBnH zo&WJfa^jodiCP$v{+A~jI6J6OxDS@U2d3^TdP&`#icq6$!U`2Re4y`u6tMq`C(>n` z7a|$aFUB8-7Hb2iC1_75(`YA6__(b0Hgov}`@^s*>^I;Tpim}cmhb|Ah}H!J3a*Hd zT%pkl_HqW$Yy+M1!)^5ag3I-7qi277O{@1#sVVUz`7KF&oUCcg6~Ih0^$P46+{RZP z`D5`P-UQYq2ppfLvTOdp+sNXJJIWT|NKO2L&nz~ee`_c;m38?aH^R%K<`+i!hgZ2n za&lpT{Qcu~iVxLXG(t9z(hJp2Frrr2B(&^}oXkr7%*7PSka+tu67YNs0Rqo}Hwzqn zVD$i&9iNQ8F>cWcu%V*966Dqrcu9&8<{kd$f@L8QjDZ+W&N9tm-Pcdy-=& z-0BNBvjE{Wh|CgDiykxia-MjC5l%)i%>Xfj6UJF$LA`1iI^&OzYQb7>2ITm3G%f$b zCgZmlQ}O-}hrPc8q5fH6s(^>^p$Z#Yaj&6Z2Iih9HHf(8a^f6tr0{0Ep2WU&4LObo zWWE9C#EDZ8v$~y4&@X*eOs=+|3qpvl0y0zK)(t&pdd_g9E~H^5tw4>acfJB%{@ifC z-^A&{$EvS@N2SwTZlp=vwJ9`o760$^wurpA?S)qiKji~Q{M7p*vK#NI1C>7`Dhyl; z3TP4lBby_s0z5P{i^`dq7^AkRYhI6^ZOCs288Wh>R}cYUjiAdSOtOqz{=JT=qZ4NK zkW$D>G2!^z-z_#Zc78zH1@2|SyBpbUEBFW!@g!EF+Q9k9A#JXH*5I*Jy5(B_CaQ86 zPxs@Cv#(b?0(FUJuG3?WqZP8RrKF-gw}LL?N>l{ktb6n`$WcmuRN^)3V*TQa?B)o2 zl>J1qbQm^1bT4oipGm;J!Gq1#rq|D+Soa4Tda0*KI5;d(=^uT{yp zGQ6I6s^INI?zCb!!E(R-gsGMgvnsKgB(?%xPh=oQ@Azg&-0oBd%{k()dK~}2o4l&A z2gooWIbL;1f+!9cPDQ$B$!ipY&*ty>o>Yr&kXZGP^U)?TeA@|sybvr5FGrHwQs>l@ z&OHGc9YUt-yPAvpLcx?+9$R;-U|a$muMcGN*8lhQ?d76T`~zbl2e3NoyXHfIg+rGh zEpup}u+@bZc}mrWc1wO;aA~g_f@R8$&xL~JYxgq7W!JazDA|{c;%N9 zF=U#rpd(EfWRWhdCgAcEkFU1R15f4XJXYKJj5GQ_o~GkC+PjdSf+RXuk+4Q=a3jCa zc0d{>oL+IgEsn(D8xXxYe1f{Swz`n$6%v(KIEen!WyBnE>cpqcIoTKMFnzTa`YCf& zl;iKTfUl^pKDKOiXciwW-@+}Y`&)2Ef_dHLH87iLEvJZ{&wJ!#uZbmm+6vySa8SRi zK~OKrp~L)a!y}cqq~y1r@(ToEf2qcJuAl>(Cmo7Tb*ny@cw3Np)-zg@A2byEKYV>0 z>pr<6q_1nHwPherX1m0)w=g38+w;Ch%+a_=tv?>O7q-PH9gFS?Nz8I%<>i6=S`E>} z(HW@O^gCc+hy7R{TF6jpOnCt&_&T~lQIP71vBjDX;f06HyI|I}cz3k*xVP|XBws9@ z1ZN-jg*E0^gkY7QAv%dclXIDLF-ZY@z0s%Sl8M_M=jyScXf!hYPOIb!P3;Vtj(?wX zDX*=)wgn$O>3G`9Ee7B6n)opA=RJ(?nt9a!(*hL^7u;(!>#6^74jOrV0i+?hecGQ! zcdwR-AsX8DPrualbm;qCr=#f?)-FaJtvqcMFdJ8Iz1*wlwB^}yg^p8a`sXhJD2zR2DW4aAKf7lu zB!%%d<1=;^`gZgbiR>~_7=j8FC2^BCo3Y?@OxhuDco&0|<90O5LH_dsN6%nQ_u1D6NRC~G*`9O{Kf>w;c2_N82AWF+RPW$rH*EcfAb>p)M{{ zn|)aSt3$k^y_GAVS&#?rWmq@BpNk&JYdxWh7$KVK z7@t-((scsq8kBw&(w)K{62U`Tws$>T>8jZ21%W|D!>Ax3d_)3ihe-r}=WE zqo)fZzV3G|1V0J(O2JtR)@O6|uYP>PXE7cvQ8_i=uN2-x9QekS*|}-?g{N-f`GIC8svfVm0!@OAf8%(Ucxfrx;6%%{oRzR zj2w*HefoB^s<5^yYDt_nk5qA7>HY?Ps6pExfa*1&uvR-?@Io#qemS0jc22o~w;H33 zwiFe~;e>T&@~R)qJxnfQhxQ1Q=Bxy?8)=ni6wF`H_OE*TV==jak+jx zlC4~Q8Ev>m39ypro=AHbftjYtg|*`*mDkt`daX4VZqGr3dLGb$Ps)migCCWe;IY3f zMeWG;?UK_@Zz->#U0m4B=JtN+M!P&|mv8qLvap3*^dhD1qZ8MK#+JW$uLTbyMjk?9 z*PeYO-qlm5B%NHy&aLffwy4BPaP z$glHxE4}>Q!Ljna@y5&VRcBWfqNHWfY?sYWtD-CRCadmp4w_k&F0?vtZ*kW8;ffPy zwi;etJTR5cLDLZRj6PxfuuWcDu_YEaIJ_0Zdc{Z8*cSz8wazg~#-1PXK3#+d)J$ zb?y1}Wf44j6njC2(F(g`TOFqaeqn`viT6H_5zHn7jZ*v!4|;cyYM><8Fy3F@plUuz zMHg?l#Qqs|alI)7y#KnL?@ZB36?76YFwB}QgQRyLA*RFmTnwS}lib6eG*8jIlFj># z&%g2w%)~ym>{I)2CK)&*6En0|@m-ySF0@&9&waV2^@muayI$jD8cu0J8TG2IpW{KL z)xj_059eyrUK-oFsh>lXiY_l|#2Q!4cux;bbsSMTdnL!Ps&7@oul&8Ugud!yKinUQ zN|%Qw_%N;we@lW&2>a!jxu z&F@jYkWr-SLeI z3{|y5H=rdszO}2Ap$7AnstLfu&3)m%B}08q!)ho}<&h11UPeIx#F`eVd5{VZ#}(dv zl??s4R#U&(z3?Cz`u$}VfU0GFkFk;N1nFbUNx@#L5@8i)g2#J2v>QjH1AK>?UlP$B zK;}35k&YtB_8Qd3Hu*gjdd)-!;)Sy`7|u|hkUilP97o$^XV39Z4W~i%pji<9mQ`h? zM06BX?~)0EY%<^&NSzT8(JXXbC~k5*ouQY-1-{H&Uyy~`Fv8VSJU5hHpQg6^ zol{Pkifb<0H>Hqf?3J|Ed>kS(_rl^xoVAo;K5Ks9S4bjrM=J(fiJ3LUcWa=vN*@&R zGVBl)D&7DN*gRttrR4k~6bf^AtPb%=6*CGZ7PEy@1cGpYxIdBuek~739UaVMvlDNC z!E0x3ZrS%(@Q@mK)@Ifg^nHv9`Nnx?*{id=hEIK!C^#BGDK;cUlCKZVKM@WN zD9>;uP(Q&+LE)fak8_-s^9cHbCsO%Jjva1*0X#BaT=}|Cne0TwIf#9vwL1~fD45tT zzu_SIl%q{4<8WN*=Z|o{k2b7l&lhsNhc?DYi>{!<)>go4uiBrPkla8bhZG#mCnR4Z zwu{|EOV7(>SQUb>0gA>VCC!Zw8bb==k%Y?6h(h6*42a1@#K^63;z);jL1VkF<~grK zSEqoRsC`@O?30JbhCxHzw#VqgacSvlm$w{c@b_OAy_Ja}WW|y*?VXihfY$g8u z@By9iA~|!4m3IU^LbV*?gpGFDsUTv5Y~j?6Sn$IX5MnyAL9dOXKS6pA$PPOmE!|Fg zHV>~!DDUjNd_zp<6QOL4d07$oS$xK1N}q^xeH`GjjJap*dHPiZVB2FD%xjK~al5u~ zI|_b!0}50J_ZeJk;10z5yF&U;;tAsq!686SGD_C$FofpbLV}DpnnXwvfx_>&c>o!K zP|BUyYP$s6pNfHgM<`%_NKF{$5JYzDQAcjxfZU=#>j*j8AlWOCAR0y@GC#1+a1#_M z4G}O1`O7&fQcv{S=+5&S7v3Wt=7AZ(T}s+p>!MR)EcLVag75G{YwGi*Zu zLuwiam8SMWthL=;Qj29$@3=vH)-Pv)RO%PN?6#brZRG>T3plDzf-H9Z0jbj^0|IiZ z6%nuFCYK?tZU(5uj0;j0Ud@n}DO!i_yh25ip^z^U!tyTESYx^wtO3cJPh&&Kog&ry zud6&>q#y&^BLNo+T>%-&2wY{*9{`?L{B61Y=5{UYE zVq8Z;>ud%BsvX4a%ykT<+PCL)(Z#gU8eyipjD>A>!GV!vEDxUvZCjBD8P3E37MUqt zu%jZBc{@{(oNi=eWTCrWh~0^0EDiE+hPuwpF@DI6CMcz>`^90z1V z5P2b9&sQnZX902tiaCtkn`N1tuCe#=3S-p;zX(yrC6A*GS{$|)4}Akoocmf=F#_lO zWdIPJ3B+)CqKJy!{UL}52nBpo18ea&TuAqEM{ZoePdIir!m@`Y_B+K$wh|V#1127- zJVvNp^FV#a^KeMX@|)1OmEYh6{P{i^(0!;Kay)&aNQXrkY?+}gJU)}4W_Uz|-gh4G zH+_~CQ!6o+!oAQWWMmv)Nw^%xt`IRP#K#lc7y}46*h8CaYxKN1d3>Dkm0^<}*W#p{(?DcWasa;URzI5Oa83b%J{4}_{2;^hpsEDQ(57k9S zV2tSI;H+2=HXyjS;k;^S>U21*#SmEL=b#1tDNpRvC`nm8ei-G5d{>&6iO0*8h!E@4 z{Z<8pi7pV4J`@mP>aTS!fkHh8A835~>Ih=cJJ3eH3tQs6q75yo5hU&7akPH?4G{_JDd7+C!Ovc-}8Qr40kYmh6l2DFT24(N4j^ zRlw~Scj*D0oA;>>H0lCTGeIW}q3g|HYoj)Akg`Z!WWr*_L_`!pY8Rq?peTj`wSMDB z^qFOpZ8lQgdsTUIsr$(}*+!z7 z!-ueLMCQ2LD9tP46bfH*k|+j8!g~O>4Pq4{24%*gghtub;KRxHow0dFoXi{cp01#+ zPAr*-P2N`a$q9L<#wiJIFTmp@)&EH2Vp`EvQlYo|NS^`*R5M7G7zB;DBt29fE;Q-v zJcl3M=q+*#N8H)ysk38Vd}-Q>)62-18|u&F)9d#{`mFb1`w!|Ookrdm!4tC_rG3{< z)u|OzWV);c!k&RbOh~o{1+BxTC3(@<-=;cbsPVh_s@$mAKBIY0lnQ0^D1m5BF}H#v z2T#ykmRS<8-*r~xn5jy?ni_$@PBCmq3b4ASEpyv}N7i2@bk#!9QiZBGtElCM(m9mN z)KAfB%Dfbn$ml1C(S~j$!!V%FP51>d9J0=H$W~>Jm<}G`3de^>F6@3eJ}2tN#GI{~ zvpgCQf}DmJ@tr*bY(n7c-AvI zRuW5ZnTMV-Iu<*HdD?kQU6`1K07bL~$gzjt>qDn4Eam((bMp(BztL%l>Y^+G?Hp-O zFXoC8#E7Z?c_Jk_^&hO>Jw5g!!(%;~HmUSX4kG5P3&n=44vBldc@4SdY(ExP z?CHtV)e;|@1YKowKpu!89IwRY{_}3FRXJ}FBKY>nCq7?G$E+XuM^*;%;B-bEbfxR$ zVjqslIov0myCI;eYD-kROW}7xX`BNbKs4;0BixosZ1NUAMEA<8Wcm9Jy$bLN9HM@l z;}`|qW+bB*8d$$;{g^cAvqAL6hbpi&LchqwNp%mX1emkQ+n~2dd=3xe+jphCc)j3S z2rqDeK}SLkPfOFM^gNlo;+LAFjl)$4fG@b$Xx||jVr0r!y^6%%iM1h)Gm@JxD=;58 z1(pz%Hqan0U25MXy^kKOC!J9H9|6BN$&%Qmmym`20HE9e=k0VVVJu@@;uL2%B32bb zHIg{b?WL+1rgP(T=GHw?F$9)nBw{`>Y4*G^GS)!+#+A-fNyut0@t7lhWHYiT3BexS zaqmb>9z7-AWpDlT`hBJmAlf-O0TD$*aj80Gz=s2rU<39x6tNR$^s;X2b@--*G{}u- z-%;&Gt+Tsa>GZ@yk&OGMk8swJq}?j@hl2@^7W^mTw@82P(^nLRr9|#afkZI?PThW7$2{ECW5V4p>qEGKp~wMs4j*XL zYf^4u1@0b5a2C?}$v~t7?8x0lNwc4{xyK1)V86tI2)S_{bK0wD1eRETen&|@-P*t4 zV|bg$OHCbL6*CKMj(CmX4K3>JS$dRdO}{gbuv`R-97nZx9(SDhcri!7!Vk;ULbs`- zRnqO#qNpg^mfVRT-v*~qWKT+7)!F+czf z9YKOE9Ab{x{QTr?GVE}^89k&J-hO|yZW`3jR_{jlxYsECXd>8lGq(@kmcr~VK&K?$ zwlnU3EJ9n+I&8)J7S{~RkKltEOl_#&e*5W9@zk8%yH2?{m`SH(rl8yGzO>r&G zz!)3wxPWuP=}>^csz)?lW-QoF>+GEPJbJM~;3himTZPPo_+Xc8(B(zY@ zn=9n8aAY|L3n$ceK97n9PmdTgpcbd^t1jpsIB4GkZ^I)Lp?fo1t*ZV$%VWka#iR!V z&A0RSH@7GV`C}CEVWQQY-2Xh!m2t3h*aBS}_hBpS(j$EfQ!L8&8+Md$J!aFyKD3B01mVQ8E*ic#T&c1)DE)zp{vsDqQax1%@*U1VOmJRp|N6 zX`hdXIf-2RN7nsapW%pu?$G%a`=@vGewl=!0T5Xd?>D{=7*r219-YRIFtfk@Wd;!% zc54E_5c-HD5qQLuZXT~f>(PwgLwD_b-Y%9$p0fzL9Mh^n63Cx*Q%<5QP~pH*a zvork?#JdKjgo87Q-Wm=XfGi(M-!rN3zrK{3m`7A{@8UmeF%J7%2O+6gNDZRtb+DV3zuy;pt~M8V*V;3LC)_Gr zn{H;n(BFxPl9K7g1>`_*@HHrsU1-dq*U;a1cMM}jEBlfeJ%O_v-+2;8L5ij}xxv1O zPA8IRSJ2e|dv7i5|BU67Ch^B(aYt|h_SC;?wo11eoPN=RNmN+(V~TzFtfbLAE5@Jy z&Ilw9IxF1a7V1e@_mk%b`_LRx)Y0s9^jeG5A?$>-hvMM=`K)l?b?GT&^A9vVhJ&*K z!kT8|UAXNg`D<@%a)8m?Q|w=;Y764hQN$`U3}3bkPv$0Jfw->rUqT?UC%$1nRhQ!d zwV}#AJ@LLl16@%&LNd_@GEAgZs0}h03c*q!u1N`tc-4520vo^r*gnCwYL0n^F+?ZY z5g%GhYoWHL{I_3eMKTUp!mJ7STs;v8gg!Is ziQrMs-xc2tVUK4u&b%kQfMI@@O|Lg@WM2cJa4gl{!})`iMAE8QLP2u`!m0R5ve*lB z@V`YBt%#kDEf%IYkr1iCI;ITiV=b~bibQ=3jN`Nuv0VeGXg`7$qb9fCc$zeoMGWYh zgwqB?r0pJpnR6)AFybYqZrB2}ZF~)~+VP46f)!4E&A(paJl3C4dG&2>q6REk%h|wf zE41rfM;r?jF=Y2k2S4@LSoiSh%vi2r)LWG+x=X`&bXbB^|HWG-xa|NDEWH|}tAJfZ&tCK&qzz}}tCBe9%gM&^Ed zT42!AmUR$0fBS9PTpwr4=Q^UWk`GWRrnyb^^9mD#H*`l<&}{MX=egma=t3WbwAH-# zm;sKb7J#%R13gH`Z2?0glb2xD7l*nj1U>c!94E<#5+f0}%IFlOGTB^>8>` z4xjin6$MS7^#IDA8+KdWq*nHB`#l#%>brX}*q1v>7j_$gR`at!onHmqaEcg<*4}1t z-z3r-euvPjyc1TU!e~)cVg#HFw&nDQF%$W2>K|xUi`g4u>DVJ=7K$~Q!#!}y(;F%9 zO7YhA((Lphf#xdv_sX*AB zBOXqG&ECZ8srH6%1@;(e+B53pr_pW~GY%t&fyw}ItJeO0?=0<+ks07Lc8S7yZR_ud zWK`)ueKvSY7f)F|ZR98^77vJ!_!$X^&^>_ByA7i4F5R8N?G=F!*-grlxGBdbBkA;l zaMY7K`N~G=n;>Sdj_dgD%A!lGr~;Z{!F0#og3Hg*9XKJ0S>v8o5qm1pLoaTqdSm`# zSUad(&F0kmW`8EGg44?+Lxj(7dL%1+Rw3M?p}KNZ^}X}DADeC<^8Mo+_l@(}aPjmR zztsL4K!dk+q$d@UhRpMOnJ+RH+uiJwG{w*~i6!3Zc4T$uog~Y*J|$(MsY`3FTo$%> zF&-aA^ys!VevaJlV7hM)hxPd~Cb*K1h;Pxqe6I^PjvZ_x zd{5X^hFJGVy1(PJE25)@0}JncUqZXdk%ehj?_{>f*59EiUkL|6Lb4z*0?!Xpy=JvW z-cz(7rFDyZi0azw4J!ou0 z3|6qVJ|p|w+Sb=U1J}!;gszFyl^k|9g}9;gbDPlXvk?D*JlM6~dpB%Aq2d+x?qrz@ ztSV{X;EL}iZ-*iRw(#AJaS~M(9n}a|!)ANv$w35RdU{d;xCvxY{$;0unX>Yv z`h8h&KSA6*kv|IZzAo)+G>?$6wbs>urDJQf9MAyk0Q#9@XvK&ypa)W&erb2SCNYmy z;x8GKli}*yk9sdJn|sS6h7wxyqZcGiA@dm8Stjb8e{+lPUO74r0(O9H5k($i;qvKjxXtAg9r10i%#z zk%b(R`-0e0FkN=8w87kl4&D{V=|NQOR&ZL@6D})SZ~=*)1wnY+Mj5Y6g@t=4(ZlpZ!@4HVaYigwS&xV5ldA)X7FC1uR(V#&m2z_8lv8ivr*&bx=I7^jaz~S)TV{V zRZbb?=pB$<5&nKA7P+Ck5xEz^2A186=aMo!dlK}AARsI$8se2j8Xx4YO$i1s*JBB& zhg2m&&GLFI*3K~81uQ7(*?8DvdX6`v{plrLaMNAK+fYgaNxQ?Y+;*^kW{{f^@75!9 zVG5}7$(zHRpw)SNkR;e&n6K@i9fzA&aGQ>7P?u~pSkE`^VEC1%t>l*JhB@pE3kBky~} zd0~(I+tl?zVNVHU$TG(UM%Q!OB(W>mTWk{Q@oR+|5$lSX?jQ<(&5!nj^deRjkf{8D z%z{<(W?VO&2eT`~%CzTQX{43yXhh|~AU5ePOy-Xp88#1SB}UL1y>?6HFdM>FazV1l z#IcGnX?VQ-MS#zQ&fBcdh(U@8u&FJP>2K(k0jNm`9~<(&9BKwKsmPOn!ZlR35}?!! zGi}`x@nsqkvQ%>E7uTan7mzZc88^I#0StetaIPt2VSM+}+vDx)+ghBi0#*@*q|IJ~ zqyyZi8x;dcMZ@$|WMI`g{f0>Z^?q}*(aDJ)z}j1e)cx_cLBE%rl_BMsH-1X!sx84l*E3Pz*RP=jR05#ccAz!> z*=jL@#klWhQ!!td9T0YoAG+NcJH6Sh5!sCcJKD*7FOJ>S9mx_oEHDb04SADpBk{SD z>G~irb1k2+?@QW{lm(U+;UDN_u-$ zO+q@E2-dmL}RgQq5aCbCWkq zJx)|OrTEutIG>i5AnnXc2BE4CJ`c)32i!x7K08reB5#`S)7ZR7mK*49Z%5zu3rzui zCr|Oq&YgE^(KSq0ilCh0vW!n+5bn79opW+q;V%o5pV)E}QxWCf-;xdRaQ|)eH`pk< zik!JfN2xM9=Q%{h_|*-jNXksS7wdkNyo*Q%6ddss*6XJqE?#isw#XxK3vfaZH!Iom zG+t4vCQA6hn>x+NhCmi_jQTt}9?vg!-j7qtve@aJpb>?r_y{>NyWYS_Ikp9h zsPlbbJ>_}aHU4hl?rBqn7N#15@bHg%3&`(uAFKY(@t>3eYKVhkU0zOFO&n~@sjnoT zT`AMow z{`JrQCgo@U+_vp=`Tzd&FaP>?f9sb|@{hkC>z-%-{x|bk;g<#_g{1HP{fQ=SOM;euA|k|610TUpxf(lc6`uPl?E4ZBloKCvntfN4?W~s=?-nhjeK;e~TU7&(r}UCfl{OjegGy($`WW0%Lw8 z#2)X(caDIMiAcs#@tSLOl%=)S&4`0!JBcnWIb8IKWHaOV!Rv!xLDlDH`JCP{{B_$> zv!ebT)_Z#vAbbnNK*_Q2)QS-}zV1f;6Lz{7MX)38(CLj3$N9w!Vo-RaWt51eB`R^- z%%vKr;7b^{-d~o0?jZ0Y4d6lQ^9efe3Z6rD_Bz72UG}AVj9y#~`WfDX;&yOagmh%= z-05`&3Jt?lo_f4;rAPj&m(v&px1BcRcSM;|}Eear=9}&pox4 zr#j~5?U;yp4C94gTY35C@dfkhg6#3*X1Q^z+84ZrrQ*8Dk0CmB@Z8@YP%#`{=VBYP zGd_7&*uzlK&0s7^I7QkTP8D&H(yIH|ovRbs{n~U9w_Cg+VtZly zeXzBL9@_`%h&DL(v#LkpMwKV4eWLfuh|8bH&)#65k~HoO&`lsW2@kxr)$pDbjMMxW z_5SMNy|4z}%XR_;!AzczeVVG^TTEBg2zb1PpD$EtLVGY}djrar1(M~B zU|SbRLNIr1LWs2e>6O%me?7FW5rYw`UTXLOe$NYFoXG9oBVlZraUAtY`S0r-W*qVp zF`TbDKBwW}09Mzy>IzvT(H+-eG=rsR-H!8Oyx-%DJ6uVH3=Jd>o*Tj-Z=;~z#XDD< z+YctBhU$U^5Xw`H`RGtkWFaFBr`owBHzAsJjz`bIb7Z7bf((#HB9D0VK?H4&Ljr#awV_c4YCJ3kq9F**gAxe-*9=!f~NZC;hqF z_7>PoSTLFfpY87bYwjqvm^-HHU-Q>`;ZBrsYEGDY)d1&lg6aINs9DBoc4}*P z#yp~6Zu(tv%)n!5=Yn^iUltPgW=+SWbN}m}0loY8y|aJjoZ^Bso!{IzVQ$1t`)6(( zxz|Hv*NZzuzJ)P*R6@)J8%;zv;rJUP%KscAmhr)@s46y7Zs8=FvL|ekw7uC9`0gsq zjm-*Y#6?J;5yl#@WYB*v9&pB=1`8YVpr$}Yw-)!v;d(|WbWlGbju7#{-?*qTKfi(d z+uwR}RUYiK-rfDV#JMck{l1q2kMBkP_xgfNlZcyr8x%Q!X00nY-Q;PeG46D%x10UD zJ>PnFM#uu@99p%a;Iw+y@tHDv@48|xMqgxVcN@>2>vL-clY(n^eE)UN1Rk2anFMP> zM~Ej6hlkfxVT||_-ix6;`*t5@{QqzvQ?Nq2T`4+I%*d>-yU368?RU zdzQr7oZ()J5u`y+?%{_wB$7T-O*a-58HASy59^fiM0 zf^mHi-|hY;yK@ai{Zc}q&u$xLFpLskisekJF0(V{A`n=0Vypw4<6%!2 z9@%D(bzp1$)&YzWf5G`L%CAnnw{E|=^mmWrV)z&$yZdE%zy5oSY=3()k((-qVCO&U z5cYX{ntlt|_ri$p8Z*{7OTna)?f2Lc%)fmVBn{RjuIG}b|Ll1!<#*qnfA_5%&=I>& zEyMp{2~}|4=Epn9k@LRTdE)=;QR|MgmiX_Z*6;N7e?4fi^#43);kWRVKJp|wX*Uor$sU$yyFbr_!~SxQUi#7!KuzoMI%%r6E? z@IYfivZM&5L_d}TL$13MJR}9fUQ*d)Pj&&AJEh%(tGM>jxy>`tKm(u8q_hGzsD5PR zG|n&$3&atTA-m_@gB7zPuuoYb9E6VXvx{PB3P!~yVz>g$tw5(c6Y)B_1AkMe{CSR< znE|tjiC@4%O1w4)2}y`=vA($yuftWKg91BWwB#e|cEL)$k=o@}w=EPfFYDR@4FP-; zghhy6K`}IK_=~6ABTb$`DIQK+VPSSW2z=651TOe0NRqCLDvrS$CJFqN9y6VTw^tFn zjE4>9U~Agk37;ZM<6tw`$iy33Er(oK3OhB4@4Tmx=wEjNKp3R`7NCm`^D?sZLXOW+ zS_pSRx1W~$VJieN=OU3v_2p6{Jpipiu4}q$yucSLBHu7LI9XJoVWny|Fmy9CV1_qIv zB_C)9L0ohie~okEZ^>^Z+g1@Dfq)aOo^uqWVD%4va+rOFNTUbI$-Z?vvBfZlpbDJQ0Is_O`|o3CZH7&Q|#d8@@;^o2*~$Y z-W3`mp?4BF78h;TM5>JNHyu6H4}=x8j;^B?&MGQZwnb*~gV+TMBdD=sjAfOGlA+5Q zQ?WqH(TF-1s3o(t4^lm+tG$Qo*CO|LO>pIsZMlmn6Y3**mNvh!6(1ou(+pDH^vpFP zCZcJJNGEe|$47^c*jTBxpdXD^iW=-<&cKVwbJ8dme zxd;rU6Sz72Q=N640tF>``E1D~0l;^;{!B88P@+*Wx=8N1@kL^7Ia}<$DX@m%laM;% z{#bRL1rJ5hW4pUPT}|qP-m55)YF$Jkc8U0$VYiIg2XzI}7YB#%cW-y>m04SNt=LPw zo%!cMN^`##)piB*MPD3IoCS3+nGoBayf+H<{s`WyG3Fn*oil>|U@UY>A3a&IJyFC#pWM)c2Xq{w+a>~noVXH@ z!6JoHORa9dLO)Z*5^cS2;0@Ss@`mi3?dVwrgAVK4A@}up5jITWB}lLuM{Rix>WlFb z#^@*#IRIs&5HsrltJsOX<55;vlY`{_fx4=Z|VQA$6Z*csKjSeMnu&c(*R z<7NWx*YOExid{}2B0w-d*f873gMdirm=pH&MGWIhba+cLsWGaxW(wRx?lmUtg7lwc zV{vwQfu62{&LD--hLX$~M%6y@a!E7}xyb9%j?|&nFCP8!&z> zIU)5p19ml-P$|DdnZD*%aE}B&9h4-=AhX)HYk)+qTN!Jx{EvR6q?%%nRXlm*D?G^p z!(St{<+j!=!C(^|#aNkr*2HM{)JPYYNwcFz(UGXaa3Z+Y1m;hPFc;J=lIUa)=ylid zs6kSECbwUaZdvXHdHe=oDF)Sdz>g%PmSoTK47zBL>TPoR0{8BS-aE`7z{U_I9OkV@ z%rUXwJ4RbUHS9w@G(U*SoI@9YFp-`G@k!%;oeUW1K0i3bLqjf{GwvEu4+ehxVL@P1 z^@c>xNrKgSi7u?8cB2FnUqlpZk?x_9GH)BQXL57W4bS5fzk&fb`WQT-=9FePcpb zmFc@^ChP)^^V>I>(C%k*2L%yS7=%KXxc>(7Ji&tDf~ojtjZ!z+2nlQs6IQs<^f5eN zh-4P%eKB3hZ<1y%GU^gf)>!isM*t$;%W)NUiST6K;FgTK%@7Qk({GA^B*-`P9fIII z`3%ZM7NTNZO}@5keIvZVK@Pj+E_0)pHYJEw#wR+5ISj5yulTK#^U#Kw=!^w}Ad;Eh zdkMy_{La5=jbKgq+`hX2_k5i~nnqWNTTAYy8d-vkJ^9AE+HNO0!I@ZSFt$yJJHfRS zBXk8_N8uF~F4AYv6|Z4s{WCvAQ3x>~IC~8e=|a#Um7flbPS$4!+_c9<`5fNP2%+yK zH~nnDa}>#a%8HA&03f$gKAk%3UN-MAqtXMkU6A~Y42Qy7f|GRelGmqq zAY|=lpA!E0T4=5!Nznx`V&d_cU|+|Qn`jbz=5^7)W%Cvez>KXXou%)(g|MUGmR zijdFK(^F99X(VEsRCss5@jXQ@pk(Zr>BRy+Z=^L#OtAl}J@QO?w(L!@s63`=0pJ12 zEX_^Z0g{@)s2h^r`oq(L_=1judRroK22oA)z_dZk1(>CEqK%XSs z&=};sv)Cs+vmuwo_MGwF?zQ?E5)r&r$}SQ)`jw(G&v7+#NGk*@?aR*@}qZOJ*>U-bOct=aS=ivuGzUIz#3j(Z317 zNTi<7OZxz1%OQfGz3})c^!88o3M-$D@I}(#IBl1SmXO1htJ4&KI3~w%ELFZQWIv)G z9+$yUt8D~f6V`!ir>OO?sTi(ep+Qu7j!My&g)0=v9dEvEkx%>d;q;>`gb7S1A10@KAGVXFxY?e(~C4-0tEC?Hdqg4tJpiPXwRs{O&lUMUx8s z73x003?*2?Z#_$#Vq(phvtBvA4B_LEUxmnXDeEs_6i6qSFf~|mGP!$C{Bw*c>1o+S z7wY&|UsS>Vyv7hoAtl@w%m&*{psm%k- zMe@OTEDdgjMo-<6n*-^7zIz*Rl{Y=Ym1p19Z`mLR^cx7300wB>&Vrn)w>>t1-aAY+O1(hR^BtO^p}qdVH0 z4CW7pKJn9Fy-$2aa8H^O)@S0b(vioFtemB*l>pVZai^5qN!5VU7{S5VobBUQ>%$Fc zY{eK^C4lcBor_U32{e3WA2s$D5IF_-25u4hi5nExi8h@8)4A~R*<<~DtR7qy;rdOV z$>eF~Gu%e-Mk*J~Z$$K(6H)9WesSJ9R7{^9?h6n)ZHxb*2?LL&5Ck>p;h0GuDRr3` z<%--hl<_WXL`*c?gugPv+<9T2DXe%6hmQ}=> zU>n8xK;f|5E#3}0siBV(^@-f{4pY5Jk*ptPGJD?T7f7vM~*G;CM&CERyiOO>j@}RT>3x%5bB9E3@&E3Mqifgou{* zN?-yU@%LmON**g`)$PYI?ukdFVQeAF5<3W4^dtyguM?Q=5<=0HaG}MV8x!zI>o4QR zXOf`N>v8^EXP!PUWFPRA+#UEUPOSH9nRSVJz7Bhfifz%PfLXP@99>JDVfEN6c`N4t zjdJL^@lofv#Te_Pg6N^Tpdq_D8{1f6n=JxmS8{;L9A2IyV=LKIV{I|p5L;XBT{Kfs%xMI|}yb60-n0_To4vs0X0TiX#W%?ZMpxH39 zxk>R{vuFaE$VTHi6hzjxQ1*J#nncZpMalt4F!QFehomJ~ zo!M%#k`BiTzj_@ z-0@pte8dbqsC=!(v5Y-$iTjLY#RxaHa(quQ&EU2_fG~BL##F z&Lc_kuXm@S2v44Ytr|iZ-mSV=h};OJhf!N;1OvcdMo}u4gwkwcdW0v z!-6fH$^v_l^oryiXp!GE)4svmAfHE$tRS*OQ+Uu9@^g}GB5L_8xGHSPqr1UyNGD2v zQLQ&v?LKMcN*31;6xVsr#FDsR2UICZ*9v?sjxaYH$|aYfJ3u_<0>By+FRg7Ti>~T4 z>Dz-i;;;gjuHw+6gv#pjY3s5FF*rO)!aP#T#F2~GU`CgPOl#olk{sk{DC{Lb3yEwH zO8V~4Uc`G@Cp8Rp=G&!>FDQhyLHry9Y4Ntw5u8LMQX(y8wa*#B?9xq511;o}{J|ll z%iBj%TT5f_dpq&omgt3$a)gxM2#7PJX98@d`@ZGTKDY6W;F_?Cw*!PilzY*~+(n3j zo25u788_)KzUKyKoL-c2)4D1NE~Z*28Sx`LT4i9~e=L4Kix? zLD3%X$L%F5cgj`t_7Cg&dVfR19WeYQ$h(>6m?k)FO8_kj-;rcINdl^GMAT&(a4;5% z1PP~RmPp&0L>kwGik`$Kb)RaWFT`%QrX1&{W6ISi*~&HhhUGi+B4vC^Q@MF`j<*}Z-q^0woms9k z^L4+mQ&R>+kLh6o)LudiLQd$Rj`fLoUPP*~$r4nYfXCf6{xE5%x?AK8Go=%p|n|rYslTcJzcR%5|T1G`rV>^gfuE6~O2L_xZ@!WRQ!;w#nYkNbyR_>8!Dg z<%>#;zmZ4o2ylE|iUEVksBLVey-Y+hWI4-odmKb04r=nT<{U(Eu_lAF|JU|@=Q4X! zX9b~}NFMLFosFY%dNzRtd@y4eO2-*w}>sMB09W~38E9#$K(uqMmn$z zz-w~Cp1=uW-IQe0AzZc8+o!`-S^#xIF1R#3 z)4iZVZ;7f`2CoOrTJ{3b9Unc| ze3(j83(m~1M_LYizk>so;=bHeskKXE?C?~3r{7geC#+wNG{{`!SKhAl-RCRvk%r=Y zBYCs2M}V`;)LXrPV|Vzog9c8um7OMKRj4V}N;nUPd`WiOA6NlZKInnfd~(RMCYhjJ!5qNFvHd(hn1Q_b5u`=` zzK)XoMn3r|9rDj_?vz%fFW%BL)}c6}JV`)O;liMksS-)(F#!RPi;kWu9FqB*RyV>| zT5!F057972arsFwM6qB2#)l_Ruq!fv4ln6xpbf=oxp}|)hPWk&EDg!@P=3)sdpP%^eXt_(^i~9vA@*_d-V+XKJdw zEeh0%Xc=E|T5$?#KjS<-4N8!11Nh-qEO_4F8OHlg0(&Ki7i^*}gvZ+v<^WG4gbA38 ze)^7IF!v+%lo_T+4|3L?#R2A3)IF#pth9!wUR$N1FQ za!e}Zn2IdZ!>-3z)_sqO$wb>IIsrz@y;D>SQ$#?_!vS(UP#slnLM)3f-p7B=fAB+( z$hptbP4XQ_?-?}Z7NqN7*=S&BDghtjzBS5Z@n z>=6qLvqz>G`gf@4{CrJsSisWRj#R9SoD78CuGWTXw-HZec6Pp&jkMb4$x+W9MI;*` zRwKQ!Z$wOTHwd|U*f!31a#2I9JX<3L)JB8v%~bHX;+7mjvF2-T9uZymKi2Rv-{bRq z#~Q7zg#RqVO%SUmx?im|fVWbm0JU15u~x9`rxy<~BWt!3VC*$%gjq2o1)oZ-TKiP_ zB6)A^u^0jB`Y57LKb5bh0q|yeKlT*tC7ce>G@b0WR7&ZH z8N-}6w*oCY=>Om8kzp6RjW7_yO{pjLW`n#Ws=wpqHsn6egTDr;S_#>#ky}8{xGH5f{ohn`E&Nk!#_yXP4TXlA2 z`gnE>@RJ!-$4G3zV1*zkf<`uPu!1B^W!lY3 zb~TiA7!~w~kw}oYAh%K+b;r?aZ5%cs##JUkGW>F@Twh4a5p2BTMe;F@O>LAe_Q(Bc zRO>xe6nwT!(edCztBkBSvdALIU6yQ%9`hiNto`oQ7fd-A+DqpXBF1CL9Ho{;+S$O1 z1Fh=`U;FjCi@>itXE2ALv-pB2m7vRY&E;j4G!sIO7KHrhXHNO7rZsPBDfcNq;IlMy zJMi9f7`uY?KGN~bu8qhtP_}1Le(Wab_F@%bG+CDC=%P?T6!n*V@8hC9ud(KVi?}F^^2;*s|GzV`C_8L1RJ`)q!n;g+ z`I=Af(Fe;zSsU7X3hIM5JIH1}oHYz-R5A8#9;d7^7VDk3CXJ*?TX8hFcfN zOgX~>kN!oIHbse~x58(J7RGS2wC6>BJh-(8yk0LfBaKUbh{>5zhUdN5s6 zKk`S-TOW}eiMA8i5VTvkCjdP0%;>AOXOL#E&~}d|1zwmh#iPxn_JjGzv^--i@0ErK z3%JO&Bi5$d=#)SfJ!yXDGk~1!+fEV>KGiB9w&M2QFs#kg;#1lT-^Gn~$65utY*zTR_RN73l2t37AHz zb7psXq8ROKB~bN?)}|Pd0T>*X1`tWcR@c+(eedDkKDKaa$_+fs@;Vpy zS8j?4O%?_+u)?E&dQs)lG2sunuBIhYSK8*O%LWV9B68WDGjp68+*qF$$y$f>#&e+Z zH~2P_g3zp~6zS*UjbmU1z}-*Bz&t}?9%+P}5Xyu2s%%sqLCI1zvkfl6de(k>3Wr#{ z#*Ub)bA3N*3TSNL2w%>4;Bc3=uT!&!s-Cvu*K_Xh!6I-OUbXYarArw*w1~rLllx06 zx5?d`qk%~ac%OSuk3s={HxzpqjoWl=q`6m!`oe`nq$3O{;-eqKewL5b99N^Z^9Fp9 zH+!t}%fOr(QwL5=2g});Bl1cA5_Gn7%79W>bWCdR2^%gDIm|tGo6LX*HHB zdYfUIZG_-ipJ$p**Pa?fE?{F`jC`G^XIwFRV2{&|6NdMi7+va(imRI1X&~O$ND3q! zz-n#cZ~^jV59E!juGpDk<#h;KzzjWOp8`>no$k!AcM&0)5&;Z|?X$R)!!oc%CaOD| zbqRlebyE(YIUvIhk@vx#Akyx|QR+jk0>8}%t;GvLUP>^WXh?CP(+Iilt@r}n5x?b< z=Auhq@MRKx0X0e7s^hltZ*vc%`!mSSP(luZc5BD-wQLMhwa7%?==c$o>v+wa566H8 z2GUvrmdY2)&x8&y++A8bH?aQ<&&fF|Uif~`1W=~-ZV~?8Ap0PXU3pu6XM`VJ^ePc> z=(j0_mr7glv0P2zSyHr|IY>dXn9Rje2fqqH8-or zSTe&|;+Nf`Szjootd}d)MYmgv2@4=pz~94ZP7iCY5t{Fj_8cvYKENmO#HP=BZiyYHz{nA5Px1(_BK^evoB#Sg zdv*Yb_3{-pe(S5MIBEp%uD+nwh0%F6P9IF6AQzgLdKL!V-~zf!WtS zDp~2>av48}C;q)TL%|p_8)rX?n#>)Z2yT@!?IH?3a&<&<$llFwHkCpL5eGt?DZ7SI znkH2u!1;b27n=38}fsSGCY4AT# z5s{-uoA%A5)FI2F+UBjQ1g*L*{__?dP=ym4g0m5~wDm!_oxlV5K3!c?Q&zU zxW}D3R}R=^s|!8eUH#^#w)jG$fDA0;d0e#>zDtD}q@C&*R#>2+mN|$~rj&W>fNejp z;5IOAcT_NOufA(JaN(IJP$>P*SfQPg(i&yIxRMZniC_0z>I7{5Ra&SIQIIE{6 zc^OZ>iGZxQ$b?xCvj9mbQdQTRBepYwVQ|XdCP+-642Xz)a!PZ#*+oE zCwjN!$TwTqkWoVh@G^N_+4*#BsI0JbdK9ty96o&9hSG0e%x@i@{Zdx@9sst4M++r@ zuYReJYcgYs-J*nPbDTEjTlL#^BetR5A;PhoVYUxkymxKi3lyv+=BML)L%ntYu4V~r zJ`F?+9rRLRm93nMMnYLIz&9fWcqJGxuwh+K{S05ndbifnD6k zye;)D7SqkgbGAEH>hBz7`Q610cQcHDc;#m)vPG=vw(7z*tUe>K?~qs|W9TVe|L$ee zIC@@l0~M(zNPuoDCqDqaykY8T{BDmM+^v)OeZ>Fpx9dpwj0=6RsQSs7g@>iOSUUad z@C^>@eP6Hq_5Q4#{d#qn-srkS3)bH)HIxXw#ZFp&j zdq!vZL?-XZUM=-Z7c@i$jZA{QMnAF|Hx4xHjUH2Ia&@w0_g#z8b%|iQeH55?ZKIH8 zRf`AUz;jG?y4nwC@VUIfUcVcMDig|6T~@e#`w8J49zh9?_!qr@FM6w{eAFE-zDx#msZ(H7PHKOoo(-wZX}|;3dU8 zH3y&9(F1D3{z;}Pf}uXxJHPSd$pV?B9avG&I?FavfZ?;!vuWsIS)=x(v-^(tPb6p0oSZE6Mw7mQnVDR1&=#s7T<(!YNf#sbiWZ5OneBf4>e6VgeY zeHYDZmEG%VUUz*X+S@6;!)GAuf1NWVAB%NL1KGWq5YfV1CrtF7`FFZ;$Nc$TCu0HdnOEbc^$;h;&T}ho#YZdxWR4k1jIgZrFD)W1lURl>P}9FG zaq;bCcLAOS=om$0Gjkb=u((Pb1s}sRH!#67K7G=T)zy}IHY`2M+1=A%B$WUY zb&dVdHm-HmKoM2|k4Mr>;NmPacgm@Kjo4az5-T0b#KKHbccICEe&8aVIm`U$3PcWZduB3Y<8cN<1f}pD-IG$>bjV$Q{9EuIL%59uHEd;&pQe@`&& zM$BS9fRqc!j(O(*R-6`d`nD1csJwu;KidSz&EW{~r;+Pm*M<8msa#Vk9nGrKBwUWZ_y(~Z8k-Ng05>D;5TPYKyJ9IeSbc&=To12!q(;y3hKAHXiXF&-q`F+R}AE%8e?amudl3v=Ev##JJlJyO~r zF`n)spthVeV2V%xMe5@pBG6TzMCt{7c@Cj|AWyEe?$V-|^4x;8D`i3e|g zF=`+an9+DwA>I;WNFldVkqT@0F=FIl)N$mg*ZwMx73k>=#aiaQ$41}oVre#R^Um{Q z*F4Dx)xhr!KZZPhZ}csApr<)T(X)5H`(4Qls+FGrk-_u%3ADj;4~x_YEZvb`GLc2_S_MY+h@+jb@`xI-@9WLM9iW4x;9Qwipk7+Q8O_f#TW z7EVq`uVjD&4)9APKON)w10CZEXAYl5V%MC&r@1<}lTDyYr}ixIT_AHd$8iSzg4o9U zIx$2a=aLqXJ53sVYFMi*>F6qp9K z|IjcNpmHAF1=gkyvDC1guCFdi8fX}+4uxUxjtcDes5NPe3l?ToG=E={ODSeK$>G-U z`umM*NDHqow^3^0Mz1g_{=Jh!WNA|jg9P9h?jC5r%D5znHN~!Kx>O)cm1F0AjpojM z+?=3PGDeejJeb)3f!}{o&d{#2wzet*Z>WZIlpSNZvk}@%lEH?J)04YMD?MMYIo(12; zQ@#}-l=H9O;JX-N10``T|Flij011(V;z-h0%|mH?4DuT3XvdN8?{n*+mBGUyJOWJl z^gevu1FAop6&(}~^{E$w@)UjZ0ub_>I0n#d#_cd#>sXPTQv%&1QeAN`BxP>A!0-2( zb|$K=S3m-Iv3}2gjORZLMd)@ep8#Go?l7rINFORs`>nlU&XLj|4tZSWo~pT%tyCu> z21_NV)J7>yW8iFcRTF_pg!gNVmo43H+S}PZqV=-W17)WeU#&zo0W@ISJ&$#1TJm@r zo>94$pXf`aL)upk|LCa0&2^bmr3`yNwE8q7mPHoJ?{K_~F zuY?RIZ`*U-eG;~w7{t-P$_EOA?-0#bUF?bFD?^}Hd5R2bf#nI=c#KIdOV@x;wioh# z%#18;K=mfRoj;DRBQLid0pB0uufc?r@hhNuIvINkO?=$U3?)(RL?z$MPXNy@zZM=& z&kJ`yzhk4}8fqY}g!YCXH8n?~SbR}1usPF7BKmDm-&E3YAG*2l_`rYcx-jEx`?HSe zJiBcGBc}%_Ij`6XpvRUFpa6{zW;f{FeOiI5R6bg0uj|~n_7zzpXNIu@MCI6-#%_gq zhQi4qNv6m#wu@=vO;dVEZdrX3OYhehkQXeBJetStH7bMb0RABRn;tb9G)K_Wqi6<%=3G$ zZ$0?gV8~s{6~J=w9s3vWi76d}3)V?cEqXAS?rnQkQO*8>?X$)0rb~E8z7Uq_uue3~ zXO^}f)LOsZ(ATwBnBY-NA}hE*b=wvX`bqM!;L`yW%PW=B0No^ac0=wGqZibWN4M zwaWXiSrs#oz9an0OJkKo2h8`Lv9{S$lh^corj}b7TVo|+pKc%q{RG2SAmMyAYTGwX z3EB>E{N59eo&kZ<8AqShOk-23I(~G=Ub;hBDp?kZ7DQ@4cQc{uC5p{ zNCd<0SwB{&N5VdB)`;k!!^L9n3y(8B?AqeyorWF%__DK+`7{n$`vgi}og!i;)?u6t zf*7`Y1USfu*C>_(>Ou5G9S$L6pl`Vzic>zLJ9hjqpv-(+c%#wV2CQ5UoEx7UlIu~+ z0pdISYB_#b#fpN)R}RhwS3DG?!pDFyMQ0`$PLsMw+Xf8LZ(uy_8J2M8ypQRD=qlL- z?m(7jvGzh`k(~0;&u~pgOzmpXOl@zHsBU<_>kF{KlW(9}u&kqYG32%JDLAX>f)P};sQmd)k1vzOaeX$dX8 zhsr()wkXYL`Dl*_c@50IIqWrgQH&srhpCOKg?v=6J0!DT-KO!`_1h=gHq@pkRD|0r zjGE{Lx2hp!<-|>FQKW?wj(vpcoT@g3v8P%Gg0DT3%3OGO~tVd%v*>XP7X<(WY>fz*0gKhIjA_K^B#6(DsS}D2_9&J6@8y1Y87&@^scok|mq2z_@Gr5I8ziLS3 zo0JAJ)2FCs5p%;cD&bI#1CF6jW~?yi8eRmv;?_!@BMNc6lh)5;Ezk5e88yn56pJK0 zDji(4ox=_C7`h}70(|l?NtZl!tb<&es@G~iP=bwnRhR}sj`5V6W+hu1(Hh7^HAja`htlIzQc^;NzZq+ z`LyaIzQ}zxrf`f-kx*oo>N=L?bI#Valk-y!4tS0)XwB?^^G+y7mYPj>k^KP#%}ZW5E9x6~O{Se&}Y9h4ijD zb)P-6QOu;Ojhk8(p=4-g`xWVLEz2(f*gRaveREtHpbx|uDzjMdWWMW(BSQ$I&k!(! z!z1@+@Q@yb#t33B=ygm4Idm|k$_&0^`BjmS+)?uNVL|IG-nDDQSHSioD{30PmH|P; z*tC4*z8fje@uRMt$5boOY=i;c#Rl`fTmNjy2g$(y3|sBCgxK)M zTovv0trJs4rn<-xIb9l81FYY_ItRd9j?5ug#e|oQ6>R|78M2RI*kmI<`lHSipI#tM zj_m0YJ8bl9FUGz_FA$!2BaF0O`D9>78G_WC<9@aW9#2=Hu;haRyL(Se2_8vDvRizf(X zH@;_Tw#}~o9M(d{Qrmr&JrpmCs)>!^h|D;=XZS4Sg^NJ81B|TVEZBCkYX%5>41(2Ra^A$<)ec{g;1q?Ev8xW4%!hM*%p!S}=n>GgRi;a(k zcbpW6A}DX}?q5&Ru5y;Vj2*5pF}RB~g^Wnw8xU|!g{zH#78IXiyJ*~7A%^fLEsb68 zw;sGUpG!c^uP1@U0W_(2&vFs?*FeIfTX0p1%U+=FDVP=h_ZzSx;i-+{T%D{ z3uCzVN)Dhq7RfAlA#f55eJ_x=x2-W8^d9+juHaEbZ@d7C@pI>bF+8jhBa+;R0BJE` z+wnP9aTTgo0zPtAyK@I1KREPHk*>pYiN3XmBzcYbiPqRh=$n+qdP3a|^#f9V57CC* zMS1J2b<&8KLG8OMGbQy)OgT^3Kpp}7K=8gxqJui!z-pIQK8L05;^oxs8hX^IvGB{O z5$}TA)$P=n!>=oUf!W9LoZGowof7lRQgn79d!(@MCqlu|{V8GCP3Lpk3jYw zZ|pv)Dtk7GQj~U$y-XAdFRc1Ofb>?FxvQ%oPwr5a;yt2}Sez1d+-+ii7T=_afq9;8 z@dLk)CE}X`>_A_gvfcs%VI^P?x6^j1f){%K-jp0hIIPjYL0OO91PF^HzfBV>a^BrD zoGtM3g!MH#U6y;u`)4J1;hn#fq46>E%-3op*w8vW>OjEZ?kGayS7olg1l8`d;Z;a@Y=Jro6-K?s;lS1MJ;OqI z>QKmbUf_AI$K!r`n5ty@k{u-GJ(0i;10d34uP#!|busXp!>hG#573LBSKwgYQ)++W z?q}KSRS$M7jHyW(P88!&H$Co)T=Bl$%f6KAArX)j_Y~J0+@Fj2(<1QbLl%His!`@Z zbpT?YcaNjB)V;!)(%i?a4-g-lEP?N}C-6BW{3v>xb9~Xa07Tg*_m1JV*4|Az`YZi6 zA;=l6=UV557sM+h?>RA)&A3?_BXXE0E|kd*Ll%<}o{URPt`H8ZHi&LEcK79&<1BUL z3;To=-U_^??p&5(=e-yR-qYAqh|lAJY4^JgTU&a--?>h%#RRz~AR{V0lw`ehFNVAr zZI=U1a2 zD>Ug0OKRfnWNlqm)Al_90Rgb$l_G){^n5{+fb;2_|D8{pf9KOEgHP3@q0-=plnM*} zV48m*6U7b<&E8(|g0udOZ)ct+lb(GMx^N)Xy;Z+Eujo1Z2HwslgCTMSi6Wwxv)L3( zl*fayyz7Inl9R1*9OCp!=G$V**$yD}YqZuCig4d+jOXpNCHvgt&1DXP1~VPyQVI^{ zFp#gywTjyMPds-%);=HkyCuqF6RXgVgXa54V2GQC=Sj<+dS%rzXz|_e z8B$BT+UaUqZrUYQ^g?Ae&R$a(emAMn23Q}d4K;!?S!E7+f{AL17Vde^UNDo_Upe|5@dajQT z0|ea{eFq3F2srtCScc(Z6IxJ4>xkCl3K8NRlm78!8*= zdDGXK3E1syFN(qPNRdU;NjTj-v1xEeuGBK|IB9vi+0k=~DBSYURMVr_Xk#Z%dPQ_? zWvj0rrG-NX?yuI@ZE|%4w_6%wJF5WSDB?*Y zACGY8acz|#3o4wws9z!MoIFP`o+LmL)e5_>%~wXA)|X5%YIzgJ7au{(X@AkxJc+O+ zT+!qU8LR+%en}98w%07ol3InOF}VYqpv{7=%Z-Mbh*f-J$TMFOwUy@$mUbCW9l@7M zT0J%Xxg)bd1Gc82 z;aM3dIKy-9e&5{Tp=}(e)Dx$G{$MhV#o-rKp1f?ucOxcqWW>pbWQz!3S1w1B%!9|A z#$luDT8CFAH}!bI#H<9o?1N3{@1jL%jt8PxadZl2p5w#Fj@ zHkq8@8F9dZXfZ?lfKgj+FNXLFFP!5$hJ<>APGXDy7_ycq=+U)Bx6iWZ3T$Uoz*HY& z_V`RQ4f5k0)2($>#4B*Xjo|)3X@(`ixeXl_#j5wQRO}7>?h{V;ALQtnEcc^RF!`uT zF)Z|i9uM;ndW{pxq8;-(-Ac9%f~Ie`>*~YU@8=eOKNlR#zn>en zI{u#W5#CX_!5f5sB2qZlaFqAj!%U~HHJ2mMIx4uB+Svo#FJrw-Vbpi;5=3+M?g_!) zoXp7rKfSyXPu3<>2fV=D9LOzp4&6EX;Q0SojgC~2QyhJi1w`-4Gw#{jrvn4-Q>tDe zST}F8%xCWi=R#}m;PmUTTq*)TZ8Lir_L`9`<6kXBr)|H_>1+Jpdn@m5EanTbTH(p4 z4+_FnGj>2AMzl^K7nH}k%8=C;=yV4#_E%k&I4CWMYm$!X0s`Z&;kjiU(lf6*0{pI$ zO@76ecDpb=Y+$U|Az^p?HibInwiAoO%)s0OOhGvVsZ;;1*VIk|6?q6ml3#exVt~`o zXfq-fcLEbm9l7$$%GUtg(7nWxU7$T8BXjto!Fh=Ky%k8Oai-PCvN?t8Y4^5g5bdK1 z$=a=iEH`A^4DlV&%$jU}!XCa#fzt~?mAC~D-IEmu3JO-|T?i-U<=jEY0O-Y3GHUjB zCLaEsiPc;W9rWv#htY$p1>9FfoxJo#5C@VSiPg6EpA*lpw1oy!P>N@K{6wvA**TcQ z%()=k6YWG1gJL{dx~J1uu#0!4UaTQnksmSE%?=yS6Gg@$D!(pU1Z@!q@5drub7^J7 zHq+ZSeb91KK>%Oz*_54zynX3M#ly=2V1tK{3QwFdupW2^9yb=unt_mOo1&HhNHGE2 zBbmL#udR;377%MfRtEmVp^++_T(kwxqHDJx4ZRzWw;|?x%$A$?WIbXdw#%CIR9@ifeF~wA4QsrJN0L zEDa#CEa@e=Z>dugIyD5$r`v~4rK54r!O;MPwA~$ZOPb7a{$DPi?-r z(iV0KXG9oe4wD(2U>s}Qd--*N3>^_$u~pwO*VPJV-WPUgA|AbyafaZ@=8l}-Wwl`!!sGYH=HS2`rj(n|qD6Zqo z1E>a;*TGn!ZakfqBhVha0+vS)ma2vgo2pE%su#58lu!vb(64mZlz7K&Xdw|@U#d$S zN(ppPC7H;@h3a9g&t$mc_#A3zgr@K}*nLQAXU~N$dfzAHM0d?7mm1QaK+Mtbuwxpj z<;_+w?RKMvZ3L3d#o*^ltnv!d$)^q>6+4i}TEp@l#PAR=3+Tjyzk|v&!F8Xs9;^sS zE$WFw2kc8<8b9@zyQ^Cq6Hn)JY*a`UvAD%D;&&e{-xci zR+BjyeK*dZyO>NzHuo`+amsG{h0wmpjSiN>lt=g0s1&VSPkL=|o37K<@@bPu$^bJ= zU!>J0p0xneeARa!8>Y$bkk6C(8}hP;s++8g5Yy?SwOapjkEy}9H+7fEDvuL+pEb`Y=TGg>ycBk9#y(B2aya)17M&-Rs+ z^xz?ZbP1XFZ)a3tZ@*||RbF5`>@OWd5gm*YaITn}qx_ysa5jKFMmER48ls~8(b9@$ zPGCC~^hwD!H!2_T%&Nwow{bIvfFd$aYjS=7T@_851hP~5t3B4;CW((3L9kLHmJmdU z&EGxBcMpOIJQ76JR_iA&{P$LS%vulk^UlUc>fDi+&4v?jvTFGWexGNCTtrMjx4gS5 zKI-eG)_aR36`=|L^)8D&P?W)f$564KarZJD^`(pdv+1Ho&^`1BrMN#D8|yBgjVC^c z6$#U%nGfnB55`F{%&T~7954{fw;vgjnGNvwZ({+$Md>?T+P&Ww5Gu3aQJt=;*sb9e zAG7#Z2baG(7Cq{EX=%PUkEpNOp$DP;{MY|5?USncF1Yk60Nnh3*VHv!9255b$^%pt z?V-2+odfw3NCZ9iKoWi;+UlKfx>d~j(;zd>1D01=dA`0!_|5Z5;S!BC)L$f!q5H;A z|KnhdxofQNJ`2K%6X_8~tDr-m%eQUSh#ZZB;apPa4VqNIImbYJ`qeTcns1f#y=K3L zS3cPrbHmDEqw^lIJ`KyymO(|37i6)4c%W+^Uyj+ZPe5(l!aNf1fD6B>ip|^+!8h2y zZklpKV|VOohOsxsuUd`dPFRtTLWmlO{>G|y(~h=>bk~;(s0hqQ=ldg{Nzpy~jxAi8 z738OP7;cR+0+aWtP_)KYu=M-=U-L(+yg;`MD;+!G!PD_t( z??wT2!WYU)3_L*K*xPeJA7ij)(PyHi&wt6I$$52qK&Z7%=A~T0J84uz5eLTwJa~l* zRw%hYOaj!LM1r{++RLs%x@#AK+}>aeUIeoo7%Dh1v2510-X)0r+cuMzdLsHB{~JSj z+4-tDdV~&`-Bn)Rc9b*s|p;<<{&B;AUL_R3Mz>e#tasyo~|zc+5afjhnm>0MieP3o{X zd1p8wB^e3a8{Xc9V_(7lhl|CS$Kh2Oe4bLkP8i{Rjj8MB4z0jwX~dQjVXb{28TD4Z znino``k23XJv1)vi#JiOjZoE zs|NLa9X(q6&Ra-15I|fV{152HAu%C-WpM=~=8MHGuz2K^y}2q)nplAkP#M4pmWHZ= z@pvSWmskX1if9uj7Zq7cnEnh}u*PQiVZ)yWL`dhU(#|zTw^AXfe_pQw*~7C-F^3Vo z)}6{HZx%@I_DUhJODwxfm9Uak=rH4z2uf&!;fhx)o}AiX1x+l1LmSASnQWAt01AY;6R<0hJ8X)}}0lj1yVMrcz_0CbK=HX`m{)|{#nR+p1 z9~rl1O`h49+(n!fG|h+$cop-|?P6mhgOs&dYcV+7R&H!4BAXCwR*LMmXgd|gS)H{< zoc;7z0`+R27RyJH{4yT`6vfu*+TX?bq2J{0e!A^<9}5xhoqW>vwS@@!N8h7UoQv~u zyPwSSV>PpuqSY_IkK3f5Pp#V^c+N#Iq{k&urxLP35i)2U5hdQf;7fN~HGvj=%v_*%MVqPppc1$J z2AUL|tj@GaL>eB~eD%UA)B!~M@87EwAD4X-mh$+6q+)SJKd_)iM+Nh&EBQLmmV$XB zQ?A*Qou1^=vr<#-;K2#|r+ZkTrhUQO@|*lnLT0!h4D6%4B|SLlVg0;dKBoH3?PlN{ z29rQ=wB7Bh8BZ9EbPFP+r-FUii7z5|4aX2abDnG_Uk0=D2GF}BcwhcVRrZ9&<@ z7e_JbS*L_UGiP(vA|=i}7weuaEL3Tr$s5c`w;2ELxm!L?V#y`87&N%%_q=JQ#t1;x z6hlX~F!}Xmvvn{BBLb5!xa@VVz(28XYm!|rtN5O&_FLD2x*=t}Uw~kpNq0fBEA)0c z6f;JH{Dn90)18*`iHvDQU;mzGI!x7hIK@}ePOs;8ewl&RcEBr*OomSXdilgS87MO| z7ECL}aDRwBxFUTA*n)zo%^lB3aK1ojt8diP5gPtZ`{f5HQfh#ag6mNf%^Th~)4A+| zZ@!-W{#Plez_8;%ECNR>_N(}Vrb-MAX>dE`N=g#7EcR-1gI%SkkG4PR5uM;as`wYS zv6l56(#BRc%du?Ay6>ZEqoam4&~H*ER5!e_5|0jgoh& z;yu{9Mb)?RXktB-oFc5iy56S&?!LPFHmK#!H#NY^%8iwKjRKdz&q% zy9T;O1dafrN>edX;yS^bc@q!dILE;)I12wHia;MKhc9tpH~silz0GI7E|c8NU@4=^wQCjkzA`&KVk z_&v^DsBJLH#Y*(Wg(v?$yY?A))uHV{Ud|6x+l!Hhx12);+P)!#Qsw6FymDXhW06o* zZxCqTg1MsvKSj?yrDdDUVI0rFY`d%=LzBU(b?P<(**5^5tpwm1S9^4TG(a1!dqu1m z{_{Qw(p^u%KUC5&lCP${RoIuU<0}VZGNeOD2D9){w=vql<2($tGcEej~TFVhI(^)b%JCvCB>wB-+R>0##ve=GkoZED9Dk*>%M6ZM)Iz=lB1{r>Mgf$CN- zYuEj~CypCEBmFv16F7W;@`F;#Usq}7c1P_nheyHMY5_WxBNhoW@MI2GYG@BE-k|5Y zUMj2U&d@(9f}ZxHq~Ou&aJq%66b%!+-cz`RLQx%V2ryt?n$M;lBf*zX>KkJ(X!mu^x-G97(O^DdD@1ZBMp|@dA9w^G zYt%PKOecD(9?eXToq5@);Hjf7Jor%=`k`70=$SiF#i|M8{-Q8dy+;P`TT05Q7Fl+@ z0*g)|$>ajJomE*|xVpXwFK>-Jq(xwuS+>x@$*h#S z_CbaLEUfZodcW;KEv%ADDyzoNN9{&;LY_}N2UW7RQD1;*N1FQ9@!%AaJ1p=q;r_IH zdSAG5>Xk&@w0=-np|pdljUfF_XF(l*pKb|#FazKhPx!?vg*Gvn7}8%E=djDlPIvm& zesmR}T1F?eLSIpUzjIrJL%t}A=-6~sz3(zQyq+%VY{%xq3&A4H1JImf?GXF%|pp{t8BI zf<%;|x?otc1-@^5P}hzm)0h3LtJ>&T z4LFhWWB?B(*YE%+7}H(k33-;thCx1#rl;$?g&$uLgq9rgFNlF@8axm!jE|9F6zo4j z*||WMeD~yF>n)&N3Yr*f#>PkH(B<6V{HiG8)l*dlu1x1Yt?r8hc?v~Gl9J2=o}Nd0 z`C>!BNFkU7b0-nCb0fl$XxKA=+n!aNrhI6gXtz7H*-#0ifv<$P%4r#&k@u;w_tc$T zyMtYP$OFGSId9P!^J>kTA5+Npp{qmlUZ_SP7`dl&)pzuqGM4kc8Bt&kXaHngr8>FxV@nWR=GJkIq?|7Cke0nev{EG1G{CD2w4^NE!CA40;_w&A% z;Mo?k3GEQ()DwAWgDtyb^OqQxIsp;n{p<2AHxK<^UwJlrFr%=nsjc60%cUs5b4wTA z+f?Wm8BbWPD7jbA3@<1 zPHrk)FWLdIF^JsQLrrY|K<%S1B(-Zp%Vn0sJK62wL4C8l3^k$EY!t7GhYfTKw&;=Q zm}5P6#?6l*g9NGzuK&S6e)^m*JItYXapIBFYpeiw4>ei`%x@ofZP3+%N(|1mwz2pg ze-ygXXoDwi7HNGT=sF(M?ra}k^hojd!(@E~I_1&}**69&GZ0m9xAE`il(hSLoVF5@ z7&r4sw7RDYtze4=K+$iu)&U(Ceq2NlWSEV5L_V*3IouWVYe z`10V|qX+U-#O&$tMdwY?20dTC8*l|n>?18cER1HjHy(|EB5$#b1lAi4!uYBQ^-Hux zNu1qLVh6=b#dDOln71vU6FOW?C@8>c$pK5d@^ z1BDM*L3sshyu3zI%lL+Yw4TXMFn#OGAe~1m#-7U?&EVaOEx;_L=TayA9Tlosk*-t# zOjp@$f)^BS3WQSbet%eJ9P%ThNB~^OGh$xUpP*9PfkH5?G`SzZ&_{$Y2#u(VT0mtn zU~eN2+jyvY*O@eMwZLh*0@oVeuF1QE%H9h8^An#j0g9!uV+xGQdLf_;x(!fP0P zmHGsouJXa}LfQ?ax`qdC`>+2o_}Uqy19H@1yVfU{!eO>+;X8s*J~k93K9iQ zM`k9ozwjq@Kvb-c0B+bEOhfX~!5G6@PzyZ?JhQ($YSrU#J<1GsR4z~4Q%>L$r*buT z*mC!d2YdXwEgLiL_Ku+zZ%sa;-lirrusL-Q;f?WQaVS|DD+yUxHidt4EaZIW#!U z7nZ8I587|viGRLnJ`Heh!83U@$f&fx$I)g{DI(!O!h=d+VW4gY>EXt2LKe7vS4~WZNi$Hh*X`n25TlMLF&2 zW;*}%o|wXy`E^_1dDh-DEDvBs2Rdd5+KjDL-1(-oNXX=i*L*6-EAzbI9F*dXm(H-Y zeq;t}eb5F$F}oVqVUh5-`~05mUuxW)fV4adC~wJit~|}raFot|PH~Gp@1)GaP&d1x ziG8W{9Evh$YI?lj%NNc)pk4810PPC%eMM!=VA@o1r>D~QLT#||{gDjwr~`dmerGMq z|9#%K0_^Rv9&tHQ;lj3?n7^`x|* zPmjAMU(%hASIgT6$ktd1FAu%i``1Gj=%SFVDe>L3$b#can_d!Q<|6c=Px2 zVq)@%W1I zeVQ(qKL)w#??7e#T7QwJv2U;$%n^>dQj?Wf;=aGNFI9s(w)}akP~)h=KDi?BW7m9# zEXkiF$Ni~$Xl251pP zcyDc>2kf-?V#KU>xJFSAjL3vS`l~jW@Ca84I(eOLx(2T;cTxAbSKhuh-8Xj-*q}rU za?)17TYyaIsc4I0q2_|O|0WveM=8sZUaR^?Tw_p`K%kb&>>JS&RUCMjD~I)Tih@ri zDXR9uHP=)^fF(TTC0aA)p0hD{Ks}uUGk^H%#);#96fO`!5MQgsZOe2N?1E>p6rWAK zg(1W^;^3lMt{Irc5oS%>M>~rUueEHl_Jyp>RoqGrX_Pa4;+&L^zZ$f9K@EgE5%(g} zFMrA)ihQGSPXOsdl8QM83n5;81HhP$S9bX2sm<*{N@BRI0RyTovIAcOY(YUYQ0JBS zdgZxo1`;OfqYI=pFt$$Rc=h-5(qG;!e!fWJop|I0tg0>!<*VAD*nduYh7sHbLJ7k_ zgr$Kf9qoX>ERT{4wQbzb zHXdDI{P*%Z<06&@t4~6tLQF1ZM<5@S)!KB~SIrE; z9t$-3qicF8?%jO}3Nt>)yDg%9SGOi6OE3_-m)$mB|Lr=+w+`;)Tdx9rap`r&J@lL1 zuF*54dpYVq-IV{^>SV{A;|hf5)$2UR(0vx?&Sg9u%ALhv+u4j?qu@5LpZncT8sC~K zN3w=a6u;((e6RTq5>1)zZ3s)vx*}{Y6)g+hPR??jVT7vFf+YL7+no^G28N-Rk_YA6 z7nMk+W<`&a^!+V6;jDMJ6Cp{-%Srufo^?+H^LW!_8Me@2aS; zJ2O3jo=DDp6#VXNMa=c?o-25l0*Kzh)Xq+3eP(+rUFQ?~@Vq*t0K2bHlPk4#oHaj7vnmPjzeMT%S06N=n}>)Emk^kz95>o~bpBn0c{sO(6~@$FgYMV&55D}) zt+T{z13mM)u#v(w0Z|0!w5!DKbAC6IH_JiGny-uR_Ef#`%>W4h&AfyvnX_n^y$8*1 znVO^3)V78{$4(aOtg58CDo!z`$c@vVXv68Wqg$H7Y^5jYi8NH#`>$q{U=)f`Y3pP> z`esRu)R^j4VR0%F2*oAWk8$N)2i|(3ts(HADg!?b_4$tXP*%(-QgWfWkE+T!m?phZ zP;}LwqNQr5{_clxqun_bI)%rt&sL^W;;e z<-6FTUeJqR_bJZR*3oITd+a_>tmLY#?0=2Aa_yVic_hyDdl2K3;e4)SWPfB1Gb!6L z%ar-!$EKcCJLf?B`Rc-V2HLdE&=ybridx=>R4!%Rh2=Sylac+-#*>q~Wi9lCm0wL4 zwl(en1{g=ot7sdS)*E(~gq=Ykd@0)vZDiZEy_krXtG=uR#os@ROg!le5TaM?G$eii zcqadfNRMii&H#Qkl^g#OjIXYGW6*I{7uYqH{bD(jbi*M}wtw#VJ}#JP`(BhD^Ri85 z7MDXh5UZmy?xpUANXIU_$zVY7uJ43IqCX2DJeVM;JkHSXg z*|{Yq5G=~)CH5NHRN0)qWF3mD3oX)#*q{z|tNI?aA%|}oy}yHbI)!P42`etRa@+U0 z_DB~6rvf&6q|XqhJpH6kKDTb+h$Z2=UrqV>m;IpKWhS4Tz>yjs$#seH`mxywQfFJg z)^yt0cb^MHt{6p!j%EZqPre>V6RYEk%8)iiv}BB3M@XyJ?@`5HuLa+mlg?AHUVKEJ zFG;-a$XHeSkvOicdA9aGrlC0quOC=vyh(`d92=Uyc<-jf>82#g zqd;|bFT{;^D}@AI$)M|p+Qg1zLqOK0*s+fgJCKVhT+&l4cC2k9YJI;-!_{Yx<3yDF>EXOLJ&YyxtvVXsXZnpSW~K+_0Y3+IVcodHnE^bcm*cpKp zYPK@ns=j90S>TM{!GaN9;#GZ0R&3GbQbDU9OdnS#4-K1O`XldhkSsMGmM1`rcK6S* zMi!s53f*=`^w(gfs`jpX1mkGncj|JAH|k8tk%LxY;~4B>%qz5%2GZi!I)b4`YJX_? z9)_5ai`uV#3>Ab9SyrXQ_VBF{PsA2Ui|da5ccxsw;oq5Z;Gs2N|N9)m-(AGO_$R10 zFgDeuuEeD$-}YC-dZl45LLd1~>PMn0Qk_KCC{#n+2Uk6z7_j&sv znvMTHZ}Z>hWjfSk-M`N(*Q==rQepdYOln)PI8}|(etWwnouelu7NbVt3QYsoSyB7_ zbjy<8u#qIKbuAYQ`F<>dnU*!HtTz>2-vqA_G>IkpL6g~qxZqe|7w%FK!gN5 z`U4BU3vI(^Q*X)JG&5_}hIHjVl2k96TXK;N8C!1Z?5sqoD^&<6aSF4~cH_mmI1f?9 z$;Kne9=5eNAK-6ErxE2#_9zX#*a@*LsF#dcrx@rnd|>Fhw7q!>TiwY}7qYK4!2Tjk z)^aeMzX+~S%WuK&0QQ_+k1lrqS&JR+2Le0?b4~uR^>zG*Y%m?rZl7uTT7EwXz*-&3 zeVaY~yozyen1qL4lWD>Y7NuQZCTUR;jeN49ZAAk)BI{c6AoedFiGNQ0O;#%5)`a->kcLjL~>7ylS zMu1;FMZ(-<9x>?LX#Sd2R1UtzHeosZ!dEt|+{2eg6qccolcUC5CId)>vm|q_6U=B` zVJ8;l=Aa3t^zAQ9y0CBw>mN>@6?g1n*~6-$;+)`@Pnyd>Ww*cWX8Oh7d1^!efFqE5 zoE3;!`jK%-hs$}d6HlSJW(AKEPsm0;(E3s8B-1(#nD%UpT_Hz|{c2Xmg8T^(7)<;)V z0!fZQ_;+Q55(Q0;|9G{%KUT0>*2?CtS_9|v{RP>hG2UCf0c8o3fT6b~O`UYS!r@0x2jZ!^-));!8h2kf9I>6(+zdQF!Xm0v?5XZ`?I zaf(%URS=kXk3)fJ*9l)BFFqW~&Y%66O*P?DS~nCfIAbKD1Dgs7jCTuNf2S(h zZBx%*dI&p-WoBYdN-d4HO_si1&O$?{@?w4(#Z6u487Qs!ocJ+yDlE>wbO^oBeEaG{ zhwZUsic>!IuG<{p6j5nKhcfjpMfLX487GB97{@i0-ypa9f{U4ck$!LW=)+)O{aLen z8j0HV;TNJZael~5d4w;3=^D6ZZ2OzNDP&&p_5^2=$l6UKJw~qYcsJ7bmxi>LNSkDH zP-7BEQ`TvVrZ)zFS0Cm2kfq^_f&}VX-d*vt+EciJ%lkL8Z}t0ZVoR&byy}Y2aU2{3 z$|1gXn{b+KZHIQ`l|oa$n2lGS*%9n(85`(E%j^;a!qSktwwX<+%6Cqn)bC6+CQJ1D zN4@1$ms6m7P&$esE_7;+*u%Efd5{1T4GS9-t;F+rW!Y{ju)C6h( z^T;#Sr|Q)&b@6@Ckqh6a)_ijkur6Hwu(EbGj}nj*8VOKn)b&_EeHIy32S~ zNw5%OH7wU8osyR;zj!B9+`4U#_13P4vy54({nn}{j>Mhv4!`y~TT5`v4Na8}pCil`R{Uz`uXM@j>cgqqf7Ac!ky+j9wFlb7 z&JLlq%BBa!NH=56+O+ZVTSqQ}`BpXe$y+-P#iGqPK+tbtcCqFBAfo@f*31p{;(;(= z-K;t1Yo6=RUfuLT!5!;)336kr+zCbg1vz5C*UX(FR0@tOhk1RF`OPS9^f1#;^=yklm@GJdxo43;+)2_#$;x|#fOH{nS4^@o}dl(W>>ta=U!rq5(NAB z{1ST9@XqR|0eDzKW8jfFhLZnL=~aGhuVM!^SW*Hn&&X%*aJ&4iIGtZ_yWUCj!M}w| zK^)m`bxr35Q=-Y7ob?jKOHkbyRAjq%PCk#y;Pkx{dH^;5pvghJNVoXfjUkP?g&MRu z`!^qXu+_*TV}5&7&hYc_k(@l!4J?L@Ay%KO;&6Wp!kjxy*LLESjOXUvgpC`yT`;?V zILq4J8{gE&UDvM(5wXekl+!qtmobu6<4V|*Wk5B$#Ro;%d?A8nzia%So8n-!z!#;} z8keYZO>CV|vI4nSrgv4%W-=OAUqXK*owpuItSM|!ePZzY?ToN|6K49Tc1D>5EwNsH zlGQWp$}fLihWeTJ@)}L!({U?@ijf@7niiuY+1V&It`kDI{G)wC45*I!@z;56;$*S< zM7w^$J=fIlhny?ys?%k4%5BEeK|M#*>y?I5fM;#{S9a@g3k|d9iefEn9(yK|KX$ly zM`TLOqiPIg;w6u{Di*u?Z-$AE*ib|LS(s}_-_QH=wMHt)4tjwa#<*B46 z8ED%LwZ#u=i^JSo`K_;0&%KYLnZ)iNX4HdJzOxo_SBPaU)*dAT*f8eBSJ>&N1-uJT z=t2!FQDs1NVS!L}6Bmiy(-imD49F!pQ0qv6-W76|cKIENy9r{)G?m(USwVu(Dc1tj&7rIQ8mS0U8q&`>%gy7iXJ&8g#LJr?&sjGhtHVbm@UAM}v7+ zMWqw{`W5k3LKE)#oPmd{^(ClktE+N7tq(t^n+dmgQ|I+Gm-?q2*S)#nLfWQ@^d_P5 zX#Qfk4hUTa!!8bH^mX!Sklb#5ovfsT_-yYco4n?)-zO`JmP&!sbw?0K`E_e6qS(y;;Xfn8~U5k*|81BJZi>ZD))^nAbh zwrkjHs5Ujr^V6^YisE%?+2!0Jx)CvKNY|_AXi>BmnNI2^-G3W%)g!v&s_#Xfhn>&z zc(<@CAV1R2e^D2H{R5TizV6(Ly||$As6Wh=@6-+#K`p-I@S*rvIVw6BDf^LL+SI0- z?6)e{)S-k8{~%jbge_ByUbQ|%ia>d+qFZTJ~+|g8TiZ#`=xclr> z**U@M+$w4FyeFWT|4*D$%M(io7>28~x z>B)XCc3_#3!SLjllpV1@LK&-I2yThbtODg16f;&ix^0qOv~aU^lO@6hSfM=A$vE#n z*FjRjs)fS;7V`5XyF?iq5NVj+lRd`I!?RK@Yp?9j%W%B~>FOO0!>5_QX6yTAH(&3b zyUS|)*sq@V`M9$`tcO*+ikG)Yx}RQ`=fxc#;WqVp52G`lcHZNQKIl(R@6+=>T1KP$ zjKhD%V6Y|{%@TGL7D?-81u4e66$z%}ns_ItC|%7u)ziurZEbn&i*}=B`Ub8TVpbs% zQIwLhGCgY_^Z{B2oybfWkTBN$Sgwi1mwpnarG~}9F3hShCRmrd7R=V(P$O$nG=775 z^3?#B`r}n@*cx*Z(T@6sn~6Je7zCz1=~tVJw8+#SCKDa8{}IcP<=5RC8Q6xoASe?< zQ@3Z$vhx-rkK|0Tak;-V@m9#YFz-z((Yn5q^rgMqUExbuySA`dY6ofI_X8-6C$wDa z>Zgq+F}syV)&FSZ3>~nB4fci8e3I$;3*iL!(8{~L*$_PbX$;rr$BlkKd1yw}G15i> zy}I>#;(ivNw1mhs6mqA4Whv67mD8Ppr+it3bG1aK#{aw^gVqajN1^fftt+x-FEh72 zg9pb~KNE#3KWo<-r|g|h$&)zJP2ZT2G>cSe9JzhjnSM0Uk=Li^j-on9X7(ibDevem zoob-th`|KgWhP-WQGc3H$n36wG*rO(37R|#A^u&5nt(MFZs-}G#1Zx13IP1(8KJU8 z&nu2zA6*y`|2+xd;%H9NZ?$ZngYd)>VT&ZSobIh=qew=@_ki!y)(1OB!@2+*b4$!Y zPnKh$SXK$zk*kjo_NACg_|?B5=$*!LuSt4-z?3p~=qdI(DaI%Aw*$kJZ2jxr09Jg< zx)*C?#X@*9zcZm6b=@~DfTZT*nLdQaRvk^IX6&_!w^g2lv%T%7<~fTW;~3x4NnIrF zB{3i3Y$h(}#GUx3&rI&CgBUM5fkx|-d3ZU}AWE!j?kVDEk8HYM& zY1QU86Y+;0PQf702Rhb3C^ zW+?ie5)HcxxYwA3{q=&li*zA8xqc;QkD%80r_<l(>ETnHgwynnEt*jAkSmtV*Wzh%8PU-i+>YcbO8Dxs;z$eB>) zPXn75=dSzuDgbOeeCaw79(NyEGe(EMyOHx;tQfR*^-nsJQT?Q>wbFTSu3d#AH^H0B zrSpBFE6xsLxi?(t13y1$GwJyKQVR(~>*pZ!kdB6qLW1D9Y0ZIUCgs@X(L&++b+DwHJW`s z`Wk#XoBO+|_1SY6(l>1vkRXZCR0Kx9jP*Jyn;8@OxBv9eH1C?7YO?EWLZ*qW_M?pi zA&*H;wpW4fiJcP#cjMLF`-2b%xhZX2t&6UNq=RVP{oIJ!*Ahu)XGvX$>xjyp@RA8QK|qW@_@ zOK3dW)IHs*EwYnJG=_g?Tgk~s7WkyCg|0XiE9&`ox$W98O>d>>EFac1y}H_Thu@=? z_ts$hIR{1#3#JRqW*V&b+KAw0+-u0M2tt4*A za4_2D#&@WVVJRkMfo?nGfg2@;=_RxgWeS z`f1s3P(@v9_ktSp@Bf=X#a)3q4CDVo!#hT?8eg-`Kzx=gVGbXtt~P#CLd)xPmD~GK@Octfd(Gxk z<*KcB(H7iA42!l z#6)`hryVuffmgXze_hW%80_LRrM?66F3Nem?GP_>T>l2DeMDt_S~H)L+hqp$=zZ23 zq8}qcqS)=oX$rOAN0-!yjL6zOscXFu{{fHms$R_dY&3=*WLv@a2fMCpjYE`{LZMyy z>Mict+Os{MfEy#ZGR_0EJ$1nh02ynm4On0g%QkW0zdC>|`+^~_vkBmODY9uHOml+e z)>-hxvYDc{ML?tzT|GZzGJ<8>eike*%x|+0+1t6isnNvfam`43wY%OHeu?4!ft%;L z*rG)$W`22?x~;b0vJ~qJzzw^X8Qr(v^{mOe63<>vM?Z>`F8Cdr>(f|%hCw+OUJ5zk zdjT2XT?5+|T|CIt!%$!yCoFKbh7a>2Vi|J8gNv0M-OybjEs>({uw5g&WJbt9B4FP4 zG8Fcs?V8yrj#us*Io+x)r{KT5=H?`N{n^+ni-H30xJ4I{bPGHYt1aarb}e-$CQ5r& z?Rxm{TU}uK=&`%Ese!N!$FHEvfpjeh$^I$~w$u+RK!uq!bN4lg2e9|Kh?D`E9de!U znyG)9MlViokj4Y;3^~`^Q z&m8-$#;rM(^9>=~Mfq8a4A`%N>q^X3!4Smsg35g`qYljv7(zpYLamsucLw7)dnELkaka8#*byu1Eo+nTK=w2~Ad9Wp;tX5#!;YmeD6w35g$VS(Vu z+IsHZ?rX!K`U)kA#xhh|T&7&#R_#0FnRt%SVHyCW=3oJSM$Q|dvKqcJ^uQ}*#+;La z^;7I)T{4Li1F|QC$8Vc9EJa5C(-&9s3BZ>CLs~Yz3U7c39MQ)n&rUM51TwYm_DL4FWn;bU*-VbPg)jrqW4&tOdU77jZhm zMf2QKIuBgK82G1xbgh(K&7?qr)q4X%`aAd1Veil1)b8INugtnMTcIaoK~%-Jl%2I~ zgi5*1xK~(32k%s=;63p2DNfwI=5;m?26iZKdo3gJ;=9L zFT%N>-K>e7gd%}P3^EGylY&YhAY!S35W1dzXvKeZc{gP0IQw()62l5ikl3%6xIQHE zrdH1tNjUmZ$v_7Lt)@|}TTP>lrqM#vnEtxry+aaY%W%#~?`9#a5_i*23+r9oqxXX^ z=4@WfRh1w>V^j~z!p-3^+bP3xf-O=)3t73&c9VDW(B>ijtLa)|W1$bv@t-#Eo3z?M zf7)+l8Mhi1iuPiLa&N?q!Dx5!L8g?wVq<-Xe0!$31dAobCG~CQ2;TD~25?>jM;DUdC&i88K3RvZAj-)M?JJ9e}9e{4h*YhYWTN|wD8-vk6NPa z$1C+2Wdf+}r)60KYK8VA$L=*+O)3gPj((hP+Ckm;ofN^6jd)Uj-`_&k?Z2n7s)>73 z@|I$NXvQbq#vJl*qxq(3IxTIOA<^+22X>mz=RudAw@XGENXg_*L-(nEp*Pp2*|b2> zuqK#&rEUO?1+5O!A8rkW?29z3r!bD>VT(mcIzjHG&Fh}P03=Ri;D3v_{#HlbZMA+C zTH)P}V7A>o+=_2|i&DQJ#hdtfLI-yVd!!^=QVx2Z{KBZ_3Zg7yk9L9$%GqhG$^R&` zovZ>{LMvw<)~2-(I}0Oyyp3%8)<~^pW^-YQAE5o%|I>ch@2*hZS!BAcKF>#Y2Z0jjy^LO#dEuRaBXR#*gxM91@bpg~m=5v4N|CX3 zoYO#wS8aQ=x((Ia*KDLd`x!7Lw?Cbq_W08c=>&M`Bto6smm{pvHSVANthxBzoN3K> zFU^%U9~wb1BhxtA{Am)o_}iVln=jyCNMCI805ZGgzHTr*sIP>1_ok+?^21Z!ibV3C zKMXGSgay=V+4t+x`1{P2bpO+up)=#Q+Syt?#}fF#zoi(iuBu|v_q1ufB9N+Est+cp zO+lzOez~6&Xy>d3%VE$mtfByoNTd0O#{q4)Ot>o=mG3-x>=AO@Q371X)x0FWz&a6o19@gIIb|$}|a*1S?+{jx>5h!UJ z^X+FK-0nnj2K~T(Yvt#iB^Q1D_kJV3xJA?yXnl@fmdjjC@0NFm?8t*yZTh#wCfoX~ zfSdGHgpg?j`~*Aqx9W|w3En2n_fTZfp zK5NeKxl|CxVsPVvIqg`PFVPszL#ck}YN8iMyUCbnEsW!v z1c>V$ZW3bB-#X1)P~2Oy2w@*?@C>*9?~KQnV4u83Di z>cRf3nMunK!R?wooi&z3L1;aL(Z&=SkPA1QfLOf9n~Ns>yhDx%5#6meuzqe5mxQ9d zNR$q9wG)0jp&pWROuknWtTCT^U3nCBj>9t za>79!JQAG}hRRXJ3hU>X^M_y7XU3Y$>x0*B)%;8GUCjh>jux=pn*?Ov&F$-*yW(qQ zDk6tEJH!pZxnLNy`rz-Kxgx^UAt^KPd1h?3sb)9BR|8 zP!ATFf?J~SnrMX7TkZBEXP4DSF?7G8Fwf3J$o5Z*xbdZ16j|^s{?4~_zebf|{M}8< z&y9UjbY?|1K&KFoF+O<&%&hfykC?aA4cBVHLn#c?u8F>*cN69BP!&I}50b>J$=$WS z*}WAf61PMSumz?l;%xFzah)%@sp{R|+6&N?_ESB!2K~P}${DiN;>_rS1!SMJNqGk% z<1FDPT`bM7`{$wHaLBM&oG)44+ufz{Zm^s#hP}>Zu)o|!)A?X@F{hfh8jGjRX=pv+C}gdUh9%Rv)_ifGe!c3H-ao;Ph5R0Qk?8N) zk`os!DqZQ8RkMTZ>;(Ops(TVVwXV?b>8rXEj+#T(gZEo><>kU}cw!cCg^>b_PcpQH zK`s=#==z0Ul9ccxDj0ETZ`UoYS$*zBFXG@)fS_e~k7Ty=+x5q4p-$b{Ty-AS+FE{t z0Ik>Ii(GkVnrM!5@^sR*wo<2LL}(MZOf*^bZj#vvdzE>yM}flMVKZG@4U^SZYP z)PjIs;e_S4&61543dI$f7}kZX9~dJ=VVozxYw@~U#rrKVPzVnQL~Gw1;RPPp-_}}= zL_SR>R%AaPec!ttb{TonBmu%AnhRy#6?FaHtS7S7lkjB`pi|W@E6b9&MW*>7ez}mz zR(VUJot?agsYwcedH&1pF_i7nh*%?ol-E;QK{c2=$)E}j77hI;bQm6@)|?04(d5!B z%sJ0D=B$8U$}5q9SMYiMPOA%-UzqFaKrx_Py7hEW4#tp|IJi zcM(+C2eQXS-_^iS-*{&qLhqwFuW2q+q&K3I4S0;@>-V01sk+)+chvs+NT{pPaxNa8 zNDZ?0-CD{0bU(AMsy{JlR%M;I4{Pl(yc{2=Ls8tfa>uV%ABDz#4Ya($H&WLGm3_q% z;;-!3>b5=V*bV&-!@E^lkglEgan!08FR-)8LH7;OkeFTcWN{mppz>wlAjVg_2jSik zZSN<;7qH%n(0m(Q7vE`yt{|U?s}!#B=`4$`(ntGiXeaT7;Ff~Qj@2F?baQU@yKQAD zSLJG`a-Pg2VXVcAGb9^K*;YxX;OmOgNj7w+5^i zqh1Mv41_)&d@{;xNM&ib2+=-u5lfm|b3CK??O4B#Ht(N~gE63_RD<|)7drbd zW6{11=~2_`tKWnUh0M^YvHSTsj0fXR_{E=x#uG+PR>~9o*WN(0{CQ#H9|9)fdl7sNx z=gsx}Tvq1)Wji1D^=2lL&!xrrR75kAdFgIdYhJpIZ6&KraITP*NXBLeaah-F##oI} z-PtAn%d!&RzPXQ}3TUaedB+~Y1CmXCxu2CiSIJ>BNY1KeN0*KKSvTx9r#=1xMkvfg zTC3NVoWPw46`pX|5_5bq6Z@vq=$i<}iRO2AuV^#jlYS+tpa{&r;q{e*1!MlANz>oyRcJF1REjR(=xeU%Au%XSl?e=7`N2>;97 zyEy>Y0eqJ2tN?fd zTu(eEVd~y7S83AGo1eeA;Gjd&m>#R-C}-t<2Z#G8Pq9uQyz6iS2=U$sh?9kNuN*e| z60_ch@7z@Hq%>AWm-8w}6~*2*9KNjiHYDn>>VM#1G#comDnN%U^KEwym;XJo?T(H{ zM6DD^glO@$v_9oSKrmy+LzfWbT@>HikmtU#?F#Q-->jIvLh1IQxsp_!N%?mG zbGhl)U6honB);`ccFV*^h6%>Yx?_7;Pv@le2>x&^YN&buu(0d(e`X_QAnEt=cD=w6B3I6662CvJRgEoTA@NVCNE$CO*Fp#kYgKPt{^RW7K{| z^%qa>5DEMngSkhj&udX4{B4UNsQc7o02J?ruXwONeP`RiZWDiVlpe=uLYOm|byora zkO_x>@5{Y*Pt?)qK>ga)CLB4){&$`;hiT7q_fGnPpFDHx;{vC%-KRYKOMy-z!6X%b>l);Nh}oIw2{ z`o>lwsb9`M$&fG7z8;Rcst1<&AQp3+)fT{%$AZ`g+whvXc8I&|LLxy$_w2;+MgWp! z&pvO3R&Hvb8~AMryso!P?>N&Y$Z_otQ=*jgNoez^1>&B4v^8@_8jX@s=IDHO`D*v8 z_lMu>-ZzB{l#WX^vVLc|=f9eu<=M=f7p&Rv#fh=>V4c67!wy>tIcPOk~kMD1n(^Z?+RM`2@6vZWhwU-p&>8MU3_=jju*hp=KQpw{h zyV-xm9N7qO{s@7o!nj?8-u&`~GvR;7oThCA!L zO_w8jgCIPaaUMP{ajf7)Nj$6Gb?aNUVn^;Lim`TyP}9vx-8E9&FdG&JGsd28zx9Tx zzpYp}or|9Ud&1cwYxv0HO z@iSjb_n1lygT|Ue9?buK?(*;F_Jpk;6B>Q-ZS&hao~wRJB)56Bu`~A4r`eFJE2EZR zT~yuM%u>sa+sx9$vZZRmk|rg z?DBuLQuLSU95U{$R3_clHsR45IHo&Ic(g-dlW8E9BRIpL?kTSW;lv6&-aw zoz)u_1PH@hL^Zp?2*2Kq1ma)cxKj$3lcQ)HCeX!^@CB0h(DCRiqCM-G8~kZ$?XhS` zaiIza?9>mK&(%YEq`kYgt$SY$z&_5*YjU{g7Em-*7_2|GH86*c7&slfepPO#c>vIh z44FPZovzJHH%GlXs;J*6>lL-D-kWbxx@OeZV_?yZJWyX+7R-i8PE#G(xY_Y9UpB(u z=Pht{BXi(?Z!?3=@W#7{=Ns8ynKWlkd2wA1>1O*o+g8ad2tHW-`~-JhtpyT}*pRt$ z>)WPjjNL5pocY}|R@bW9PJ~)X2fp~M(Eqp?^yYB8(G{jyw<|g22o3UrwRS8w>MvC5 zC+pr12r@UH)!D69zi@;6)79QU8mrNNndor4y5`bWV1EDq+0Nd$5fd6&7}Ia6jShmq@}LQ9Z#`DZV&m0%tM>+Ggxi*yr{XbL_X-+ z=j=5P0%d89!c#Ms+(H#MlPKusFcxm~O^Lj;dVslwW;T404Q>4v8ANiq5I%dd5EG!K z>B>BuRrm*kz@{u4XMB6-q~{tLNG$!JBlGB;@K+WX8lfQPc<5(-`XN@E+fy`pB)J!A0iu7Z|6KH68O+P!c8>{C&xC@If+II zKkpLuC@rv6yD(UU8@evI<`W+jn5RzP?10}@pG3GAfsL_>{qNqc&-RT>XOVp3J?L2a z9?4zR`?tzN+ocW*sKXFOJfc=Cq9^g;X7u5Om?>;yk{*kIXDwQP)8{Zy8Vp(}2ih5p z&oYar(*W_`f9v-vibA~rh#B~psfR)zvKtxd>Xjajd0^##COkh~hTSF)LA~B*ysm5i zMOfv7zpKf<@+vUzJ)#>*AywDuc4S9ivB#RO>dvK7c!%?SJuh$*!K@A{J)Ylcv1|O@ ziP5LcbS~)IJ9)E|EZtOkpK`ip9S}{=2%9+_>!BZ}#;2dC>Y)AEl?gju2`v`5%}3vR zF6~uVMc?6P6UkSxXX&!}+7g4)J>iiF3ePyau16s`%XR+p<8!!7?~-43?+H7lX*u?p zG2Jmc4`IG!8UZsA>3}CH9l!<%03NHV6RtV@Yn_*2H770njYi@X7dnsR5J_hN>)kM9 z-bLL(-}f;^Ovp@7Vq$vtf(i27P4&|*MRi|){FG3UR%g6qShrEjc)$E=J6nX$1X1po z)q!#+@*+-)--7-e-(i1koH>uaY}3SUCme{_#ENQKTs|Lw&Rirx){nlUp~-yV)z-jj zsNCqcny+W|d)WidCv>Cl*_zasu~juX(v?y7gw#_$SY=RlWb-p*o>aQNP$pq0Jkcy# z&3qx`YSzVPuttW;yX^fo{?w9doqx9XvI4?vUbWBuhuW;9G3h|AZMN@05-mNj0N_)Z zag2c|WW87jNbNw7yzLuT-~FM}{SjwO-iLX%i(i-0=VK4FL-hH_eR_Phr_DietkjRb72Wnq8?};bMc_ zt_wh2j5E+vpVYJ~^q2*E=f=A*SJ52f&scOD-E)a#aSW9AccPXsDIyWHTL=)%?qF7%2>_(WGOl7-Jh=X zkNkRL@48|rdO0;2n*xkd{DrA0enu19hbdO7aIGnG*1`O`ME3Pc2at5V-tM!| zjw?@Cqlc(p@)k|dP$lrj&BJ8Fz^JlI>l|nV8g2H}qOG?tPqI@?hFtu!;I6A0SG@L? z`}V=B{T)jSH|)C6xSx*&EAIypoA)AY2D|XR=<~~f-9uf~{nM$t30@yGq}PNL#w#JO z^}Qf|iF?`;x4L9I#U6+PE9yI^VM8gtQ|c^hK#qzG<>9*3{Gk!g&CS+SUw9UhIAO z3y`L34)X(e_p9@Ntk3Q~cge-*iO#}5E*4FrA&Nw!QD9t|4VyGPuF!5EtdTi4+$Ok#TxI12`TFEWxXtQl$a0#?=|l;}d6|MJXc;O7QP9xQq5W71v>jSM7Jw z=I+H7aJp$yz|R`X-_6WVy}0lQrV5o4`bf$Dr;qfNfBMLWN3$&>&d7JKry&4ZWC=79 zlIGgi3dh7CBCBgJ9Kv1#TrR$&4fziK26&_BMbSqr`W42hF_a%+@~!KEG4C&o9qzEz zS5QOF-j^)Zh4cHxEx)JmrsObp4~GT`{p&-tjtac5se*ZbU6L_iWP2+fXstI%c*o;O zqw4DY(t;43aW(wJ!PJ*KuIU&WR?*<47bWHscCuxt{xLHs8DM6Rc!ef0xf6**7>oD< z#}caXDkWa!Wyah=6}$=nmq$!$<%FI)Y5h(9wqdIBD6B zd)SY*y!`XwU$b}8Af$ob&2IabJ}!>tpHGT97fdMCS+*^o;?FzlR@}Z5v56CIYr429 zw?ai-8`b>vKAs>G)pvzmc>X7oyxx41D0!`b%M@1HS$-Fz&{I!}@nTdTJ-P^}ulXub zvbila(%ARj#~0A3v(6Z*)y!(8g-gNpx#ouxL-dE%Il9>s&$c+oZ}xP3?zOD>Cz9LD z4y!jT*|W1!)O~_Bz-N|nj>c<2!YK;s#mN()J27sln%FZtDygaFxq&gs6?6qCycQ6V z!|!{b`2vsPxZ7Fu1{uPcj3OBGkq+p}VQOhl7Y4)j_PyL=mGFO?55 z`HDXwG%rC2)o?eHxSSWFP=F?j(rG~wFD z-uon?W58FTwO@$-e2#Wb_3E%{i-yL`xQKFd66YyQ~}GaHzKhLcCBy_|nA{yb99kI4;IRPUfs zeP6|p*-e~BnM#9E0_kbxsA&Yq#j1znuW0=~^|$cf%uS}^AWhvfa58f%25@d)C(as? zWfYP<7-E-BD&IKL>d)a#SOGai>JH7wdn6VxXuXH9gQG2%tPtAwPq_%zl(}o}#V2`J zdXWA?s}~MS#dLC8i56$|)cn{@T1E&Hu?#304={A=bU!x{mTtYr;hE*}*$^{lehhmMS0#$XM9dMmJ$Jn&KsUWmKW`>%Mj>81TG298NM7t~24?f&#$ zwShG&oTj5z@>i?LbO`O@{0n_cn*`^dg)DQtHfz!Xg+%N4T}(W_@I3d`>dn7Mz%x0e z#khv=*Zborc4;xQs+OMQ9H8J3ci2?2i}1%$^gnN5giz?|mXoWRJ%!-|outH_jSxw_ zZ~l0QO3Q^Ux+!bGyQ=tq`pPGkC`8yjUL<=0u z1)53LAr8Q=wJ1$jZG6%DgIR5XZ3aP{Ah{$olv#bK$9*)%2e-D8yCF;((`vN2b#>>S z7SDC&;rt!PZMNIMQVatDc4YJCOBiF0#dpf*xhEHCovC(aZFTgtIk^B{f6uafHRC1{tYRnB_Bva)C5$}Py{uAUy4uar}=6Ow_3u=q{QBNfR-n#9*S;0ufNAI?)bjjVZAs84~U|Gc%@cz zmEu`F_?ho7U{0GH5;wUQ@nhwZ;zznLsc+y$9ICKZi0 z@u5i3hk7#*x{h_9w=zl7dVhFce|>=*o>td6v@@TBr7|~`h?6nMes|JZUP3Kh@mE}& zc>PRx*t1ulkS8oR`O#AX;THLX~h@Qm-yQ~0MKfLRb_9to1!`m*T&}G7&xtxsfz*6cD^Y61l|!&K z+WSw#*`6(8>m5?VIs+ZQwuLlX0f=NFj7)}pRYI@&%-3p7-b?vpM`5JWJ!2*ZeI-H6 zE{IcS=prG$*mNjsl_DAy@;(wA-M5_U=WMDQNj%tRXJhAn^D#Zo z`D3zmZ6>#zJ#~7#n(U(gbd@o0>E(I4ij^Thp=XHK(4mvXt8db{HeH^o$DZ@&nxXKMcGBxib$ z8Hq6mut7~T{E-KLM*cWUs%h&B&YWk83SzdP}4`cS4>i2qjwmT+H%=4#D>}5Z@ zt1dpmOY<`?N^JkfztoowB-;hB-d<$M!d)ZUp{d4owh#J9NM1*Rs2<37dK1Jwcyy10 zAoW>1>X)#}U|%4?SYP@GYn7*FUv=Fn?%^UXl@6sAD@=3qovbX%; zkNKfvTmEBqABq^`Pg$2INJ^69!c>>aDEi05+Rs-VvQb?N`9Y%(z8n6 zxaM=x%$1ZPPCJp-OT1}gj@z7w6S}05QIyqTZ^T~J=rV`*7A-4RyrA0^=hx>fbhhOb zW56l7T2amn9Aylsj(Pq?IaCV`aG_iR1K>;<9$*R87=(?Z5>?YabdOyYh5?I|9CtppO0iSv&IL5;G)bXS6KM zH^o&44hX*ho|vNcyZ#p5)kT#Cg0?&-nXbIHW)gmXg>H-x7h&ps?<`pIbCdEfeQIsJ zl%lzV?YPCJ-uYD{6#fDgH?Kce{>KrrJouh`z1(>y!;VY-Y1oY2zjI(Ah37SB>I_$F?gqBq2`jKh@1#;uQISfX{&Zrlo#$fhmOER< znlUYae~jWc=rLp#*n#sX=#8SFJAlW zMd#A(KVffqg203DpKB;vQ+@_W@e-D~C%g}wED9MJTSmgWY#TTNLU^Qd?p_)fpV~n` zTAu1AKx_wDuP76_D5Im%zL;#&CH&{vsX9+UoMCCcFX{ssz3*rI9#H|;>%i;lLHB7d zqman5d81_6`8uSKXE45wA4^C)QKtvG@Z6s;xO<_C?HDEu;kyc+LK^!)eM5LvpAeDp z#ZjD1c>%@0W&LO-4Zy~O&m3WMNxGTt-X=`-b<<*)aQ=GpW?0%?sUFb?&#;uBELz#z z%*|7zn?ywz$KzB>>qH2(YU`!yi?4B*v{ko3B(GGUG;$R|CIqfFaV?r00h}9jE(sOk z$J-_!;aN)hQ+Nn(R~2d%883|-uYB3d?Klwxuch>t4TKMmw&S=Clzuh|HQ#o~zhdn@ zl%#qWzND5-Q5L!xWIWIsTNt1B!6l^B`f^9c$7Oi+6vKsfuI1zsNI_BoNm-O7VEXV@m z>7LgHyj9vjch$k|<;~AJnLss{!%+_7T3s}f^DxW}bDL1#B-Cr$-I|hf_f0f3AAI(s z7ZcIt=J`}}z4~m>_tu5CRn-~CRn8%;;*k?DqK>D0=S&}(n)T~k?zgHvuE5rY$lORD z=1vhvf6s9;&;Lb2AkVz~#<1&QO59pzQ+D(h5s4X+UmkZZ$C!6M@ zX0XFLByMwsxdNb_e+(x%xvA>zQSE_WcziQw+|Obu=DqQcs#WFrS`5;sQ)E&syJpT` z2H*KB^erMMAkJkyV>Evjr~dVuJt9)tM!YdeFWHa~sBpZp#aZDoMzFs!1}ZuzI0Oxa z3W>Q1;OLmr{pvhvG`oXJ5Iv1hN?TJ z`Na;E1O@AnWMPK}TXjv~S;l#V!)$7|KFfxuIY>^7FpH7w<#9nHiY~hIwYxcN^&L?~ zj1McV1-w782D&F-)cq`aQLL+51F$u&v|NE1k`cS*8J!$@1Q18tQ#7C3%qyKqQf@3R z=EcBhd^R(Q8ch1fot90MD2_J%F7U@r^1E9vro$EI%bq{$4_Pg|!|P`;U#*8y$c0}; zV)p!8>f`Le^H#Q1%)#S)IoB`0w;etVL`&g(ejM?2zg&0sX?n?Xe@9-tMW%XS=a9Hc zD|o;Plj>+^1TBaJTUGBJSEUt*BC98D41S#PY=36v*zU$Kti{Gt$fS!PU1rr|YL-?g zrS0chiY?~RB{jSl_lBt7ts><9-jLOE^h?cRp{CO3cUhckNz*yH`IoF$S*goRX_fp~ z|Jxa4sIWrqs9mH|jEzu^zZCH*5d=#U&$a5~xKo{w zv}aaCS?a8>8ssY|dt@9%A8+KRe3PEfdPq_7vyQ!Yl{v7${OfZ`pU#@%2`P7#y0iUX z;Yh$VN7`xCEV_@Jm(8+F^PJk@H9jbJ>N?4U%eY+v)E>)w-~f62LIadM#xZyJb4vN>X?@woXGylp z!=Zr(I#SAa$BNX0&N&)mp`F~wiz00N1&%$onngSH=C{E574Jc7w5vMLy6( zf`HY&TWcVzv!6VECML|563fMkHbPdqf@Qu z_=pqrS_!v7;n7*y=u+PCmw3w`>u*d!W9taKAQVNXz!N8q!L$7Htxv+U4X4gDW4H}O zk^m*M^RGw}?>gkGQ(NM-W0LTfAe3u;*`Fi6UFyH;wNbJ7{c>8bVkOpGVGs$WO^|-RA@57-V<#?Aik;KqWcHWhLcpU!2ccpsPlZ41{h5 zTef__X1ae5^g+FyC+xI}>?D3ppwa5Q2<>3RD`NUS_c)$H#AU~OdexUKTqmeHCYV{M z3jrNe9QjQVXp^)1+n3^dCwWAbw0c(F_$TA>@3D@9pSR}uy}5s^3H4 zypmCCui>Y};C1iSm@|jEh40K4c0xvZ_rAIyv9!p<_uZ?`pBB88T9;d^8f>)FY_1Y~ zH}Cp;l%5VFKL!Y%+U32Q5qaO2*tw4B9`85@_`Q?pC-YTXUBlLEwOumG+ifhf>fV-b zkf>%{zBq65zI;3n&iknoC2Z~q@6zFfe+@v6{#D&S&ocz(dVIvvo5WAfx?*%Ug2{he zK}TmwM>j8JH`hY4F+D4ALve?IdYL7XFk=_9P1G@?yNwu_jPO)6zZhw~j%&R;_@6X) zE6}q(Q`BQ~YjbQ|aN!9x5kjy=neB(t0K?gYSV?sc;m{3*b^5|nnssdZTYmp+v!6Wy zNa@LZ^RXZ!BWm|@IMerK^n>fWXR63Kj-Pu5`58uga$`|^HtoK(Yce6lWMCXn!QnEw zv}f1n7Ce|~vOu1y6l7H%omx2ge|z+C08Y{G^Ri|7oWJ%=c*c$?x+_q{3dZ?!{>_&7 zEbO6w&a9chgU3s-plA_civZHWBgvX@YQ@avV6i0OO>bIO#^Nn=2ib3RL^9Vm1P3jZ z@SB{M_RKeqCP+}7wk@BRiTB@imsG}_*A38`o2PE}8;1w1&ZByn_1^Z1Hj(^ce}x|IC%eKF5ZoDpk`_{4%n?rI>E;u&M{V)*Sk>m%?)N1 z&T-!l=Qt^(7|fiA`*C2syA2f$10pf11X1x%>s;<&w8?ee-{$r^@|XwithcMoaA@>9 zZ$ji>v)dw)f8qH2Wgm&4KIRX7v#>mlCiX>$nDU)T90uR~vfyZV{n`!?KXap0cu@~B zXUNwsF}e(^u!{C(Yj%vng(hf((0%T2aZflRBr+f6XCJcLAeck;STNrx30s>_|NO9t z>l6RA=(RL^88{uac;Vj9Em@O$$OKLM{4o#-{5jl_YQV@Dl*8e4L?bK9?NWl9xprEqw!w?OVPuM9Cif(;n3 z)B*^%1LtS>+MZ{y)RkdD%q9!(@AbA;W=>FN*ow(>_EVKhQoQ`QcX@>foL_i9GfOSe z@_>#L28(TLV&>}{wclntz?EGx>?__Qphd(Wff|T1b6e;9Tgu!I#OJr^y0LF%s z`R*Pb?KW>umclq`vXTDp_1o`o+pYzD&`Nls^2`y~S-AQ{Trp|h z{q|M-c`mEv%)BzQ(6n(T|6mu)u*xwIrrvC_l51_%L%g%N1$)rAu^*}m~7fr|kNP#!j$>A!HOP?vsI%?IuKbN%-!0-j& zYi)pi+}lNXwP0CQ3j5B(T*(R_j;mS}#7lRo=+txLkLQdqEdc_P?yv1zMl z$DUwiu%z^ZBLSm^#<1ts!83=$eT^x3Lf#))sNE`?s#h7GN@Aul2Vp&2Gz_mVvYF03 zXY+wC;WP7;=G+v?%K$5OxXhMZql4?(Jsi!{I~1Hg{Dd2-75|j`TMXE%^YmV2tG-(I zXj0)K;wu7gpYJqTo310Omyrf?!9~t)JiJfSj)lxVR$_(HCiQNI*h-&n<@)5VzKdoG z)=8<%+>xt^%!8py-QV9wk9GA_+x2f0TyTNNee!7bIWv%|^fr&Qc$;4m1(FGXYMz{7 zHsG5V_xN26k=qD-r?MSo(y#bjnDSsh5v%*nc?@Zmslzf+2m(Lg%Um{(Olq2lG`QR) z0hH~*a1OR%j_^Xaq_^qE^D*(*oy8dnh=+LQyd&`-@Di%qEZ4-_+7DgGO$~k1FOW2& z>bQrr0}6lm3GGSnRd^1Tin6o31|K@eQKH<8atp*g-rE3dtc7eP_MZ7#WTq%RmOPuf zg@U)c{Kbd4XMsCt8(fp#$$Y6_J%uuIyp=h{k0Z}y>@?3s;Ss{ z3!HOxnyKmFLcD2uA-2n2=S4WgHovRCok!bcq{Jx&gyhwV>fUTrKvfZCjRv>#NW6b! z&}EB==LqG^R>9qsvmG%`*-#u&Xo73G(M=$)-dr$vBD6g3(z^wZVSMJ{$Ns5${8)Sn z{xJ||f3qgct#Ms;P6rSyUnV0!T9~Ho-IQ3XmaM>Z@_h50!vIhTm+Ru3QqFQ0;$n|-)x`PzNod!fN12RM>NX1!CPVg7oT zeaK((mi!+T3*HN_-k8@xeJMxfS|B&aRa1+H)y94ouKfKVnE& zQ7gIQE^Yci#6HhY$O26pu0%eP1`p=ns^esm^+Q*_<=T}Ahksc=?IWtD_*l8epOz1| z=CnU_oxjxN8o3mI*yiQ*#P3iwef9!?a9Jt*=W=d1#lIcd&)HJE+Uf%PmDL!}OoCG^ zbetIRT|7unc01L-Yjz=B$f3BI>pewr>6@o*o*DRQ_`FT$>HQ?i*}Y3Vf7xEZ%uKA5 z?2>=fHf&DR%(-!#+}oc~yuoZnB_cjot9tLOB_T5ge<~f7vXVMy(h8vW8C?`lTVm)y%?FbUF3m2&2TmtAHNzm+$rvFEEQz<*GVfm z0HMiUvuBVt1+H^kzFx9YR}0hh4-*U8PbOxQzO=BoCo6v7qH)XI-|jmp_By9^bsI#c zC{DoDzB-Qw`^h6P)1|`No^tx$=>>iPaMf$|-hBLvAL->W235Rgs_l&+n&zw6{Vm%=+ zVA!}A)ea4~M!u2%M)wYy0cWd)wX6u> z*_I{k%L!-mm9A^^e+4Qo>j zgxA;8zwvCsQMqo(z1`W|? z4_8M;&m!hfSE~!zNFYuq7k+2t^L=aXos&PW^I+ds>uZ?@`OZjrarQjlv*};|WeY6z z)ho%XXjIB?p|er886}Ut;@nAH_4EKonCgXX*THwfc~Hn}r)|HF9S#Zs9D?Y4PBjr4 z@&)_98CaB&Sx}K1gu)o7Exj{D8yto8=$w8WcsH?n_9LBb;m6R6zmC9cF=GyCn^yuB z_o%W+nEC_nu;Qt`x+~oAXTci4=Ku1D(dX}d9rt1CpMekyI~i0}TEdb>T8aMX;)9DY zqWa?w#+)gKrXgGctCIqbgeutl`7TAJW9NzIgzK!|QzexwV(1#%jq}OBUVYeYb#b=? z)>w_Ay5^$tmqnDno!gkSECI|Uvi}%jA83d|p7m5C=NT8oXLo(mtlrXpCFW=q3gO24 zeqNp{Ov|17qcex~r)lkLZ^Jszq0QPxB!sRl-OBiAt^|UxjrT1hgXpw((D}L954>`g z0U_7G4Pr#bc#2u|{Khq(Vs*(Q96T2*{PoY)2kKp3s{3!!ETC16N%r93vL65Z&Yf?p z^E=D`^E=DMpWoSmo7TJ1|n{Sm*ufUXaVS zA8edQN>vFJpU57+$&Qs6(1^2Ny$fOb9nO4|Nlf-ZKM0T>n@V^{{yIB?;CuG! zz4COHgF}~}eett_+Y<9=0@O=~e4yLmo|)`n50*d%5w70|6?>(ohtU0knu0&dll~kdke;Q zPmPFD?LwtyGxqw2f(;YXio^w8f!B?4D{(AX7v)9YbzD8lLEXMmV?9$D^`3c}%r0Mh z4YCF**w0q4f9;XPHde5puovV#*KOLE-JGaq(C70~FeCQLf%aQXYi691P-8@%^^I$0 zCgF-R#^Ld#}sXTi~?wDmbH(}bBTBM^(%Roa4qL<`S%R;D?!IG zFVaD8jgSaQ`f+tmoOis<%gp4*Hbz{hSNrY>xTiYj^L}@4Pf*VZTYSuX= zk~jGlO+4Ped#19we-n*0Glinh0uCr&mrJa+Q|py^23e4kU6J*E@ZM^?`W)X+1Z=6vKY<`_k;x2X7sBs9B-ohyDk`R zmyNSj&osUmSRjh6k_Tr}gY!~wvDrb)XO}dbSLf9`Z_V>v+I7~gMS89w#1UAMqmN($ zw9S0bt{vxEk;q9$8Bqnit`yuRuP&z*rR20!O`|y2=GsT-tMad?ZoH4kzs)n>C@A^6 zXaD`(H{$qn{EC0P$3`kEDuI>$V+TFo+HwjGH!k!u+QQ_{e~E(hM4a$l4i37@Uk+{p zL&-de7Jr`%)?CL~q{8 z_*KmKx9ynGg}5=`Qdb;WQO}+Yzw;Br((C-hkQ!}a=zSZ3;KvTv%GF;y!^y^YCm+!a zZ*AwkrL*EOTg%58Rdapym(i>_Yuo!@i%PfSF{a9n4NnV)~6!|ZV zt)IMmK5~aEBU>dxKLoXwkKMyW!HgVu>26!)Z9G0P8S;h&^uJ{OmQ`P#o0XlqR(C!U zzpKu_{D{w#!2*l3|y}!R-*!}$q$5W1k4fAU*q&DRMZ`|9$N>VKHj2l2E zOPA)km z;sUOW}UX|hBnM^+YgF%>+E~c z<`uEiiyn{y%(_M+ZE|>-MwPFfhWhu0e?Y_hw!tbJ^kcV zTn!i)MK$Ypyu8UUW)AXe!N;32FH(D)keSEiW5c(CkQ|0?uQePd6RZ56_cYTk&(OS5B8{ zWFxkzx=iD*PG&1XLVfy|X&nD$8n@I!$TSYc`^2L%h*o9FHVjmh9!hSKg(n`SMRO3(?^*}=%fAD2k{tcQ^Qg^iwb?E` zkzfUGaa0%>M66~x6Ls^qXI&iGYaic_Zcq6m?!wFY;DNhk{i%{?MA_^Ph>TjpiNvrv zcq?|Lw7_lt`6vzR;#6jVXNLVpit|06C(q25I!-Ue70%PUjLrnvPwkNOLyfxF+m<*W zn^`^DO4O18V5*B$vs ze{#SHE@wbt;egXI!xMfw+MCG^M=Ko=Iy)*r#sA#TPi-_ZAb9+9KlifURNOI-)l97M zSRdyjL4U2#2~?A$>xAtAgXz)HItI-LAoJes=VMXbP9(VSRuMb=+v2RKX^6?wi7^pT)O?6Oz-Dh;YqL#HL#z8hP{%v9wm`sGcM8V7NlYA92Ke zx%1>a>X+`HVmvuej7ra|=5!ckX7Bc`=dIo70ZyTJb_HEA0+69#~%5UW5dn6$XPca{$ZW6bf!?sh0D9uUF0Lu5(&o+@q@ zN6$D5$ba8ME1vbp(GURj^G6=oALkGjQ_ErYOD2eSlWXn(I3_JuEBllz66n%PUH(G} z2@eTR-#jP9TvE*=D>;zBqB_6b&Fr-_P*+Zk%}sbB7GP2XatJ-p3aXF2GM8=TMV>Yj z2Lo`k^2ZGLdC@XfNmU#4j&pja5wF?V$3fbgGhkuw^8ftLu7NT@8=ALRgO4PCXVRtc5_=rzsupDDC9MKnUy@VP z4a|U|mg3+uvGA*9o_K+A13Jl;1TFsv6GD8gpXRG@?dMqxk1D(y9$1uKB2ml)1%qA_ zL5vHU-8}LPu8S(&z(6m1S~be9r6hab0Sm%}85jN$VZ%NPrEv{v1-m(kgywpoD|NnE z4mt41bmUp#?by#!ML6C*l->$qn;VcUwAr`(7<0vHRngc*yB$Wae%~k=Dc;Tc6%Ph= zP~Iqq*tBP(`F2PY?~#3vk?dY=`7uIf*14y^kOPq0@&07-X#f8m(Bl*-;Y$;*gd|CS zaIDJ9EAY|6vrk^{ZDP_%0y{UQLL%Z+;%|clNF8nl{fF6+HF=&5+1aDgJ#yAycq1S7 zMF^bD<2H-01@CTLJOwZtTw4PBQE_egpZ|Fy*D+axNx@)B+8CBb(cOOfyS*tYZ+r?x zL|=@_7;x|nxiPU9W-a+?(e@rb_Gb*nk5_U5Nl8V8sc~7BAl(uaU^vyha>RAev|~NK z*j3-6kLi44Oh6(P(vgQb%Jjz8DH4xGN?GvgDRYVK2G^nL?=^ebPhK;1Jm*R#GwJLp zY2fi}YHD-uDl`SyHa$3nkz{zBdFVO$uNB$8fB*}2$ng>jkZqlybm-f1J|aIMCO9=7 z>v}snzlEtUFF4{Wn!Sd<_Q&@a#6J~D#mL0{X3n32*1#sW^+|IFxb78)VwnUNYhcML z1*_kiQwfylEk$*r_`_vtN)AyuOU%R~)d-&po>si-$U|{8^um6aSlk{e`|5FL#P0BX zio2PemoEP>{xI1-Ulo2fQ@Lv#W+%1XJoup+%A!E|mC$?}7SBS<+}4IUQ9Dz;6=M`YJPxbR zl^1B_%@uu1kG61S><%9b*;^;DPjD@Gnad;aYa(mIMK8Gu^d!c?lWva~+@U%YD(iqX zzS8zK3vzWuXC-_ELYIR%yF%7wod7xHGFpVzT9Z5YbzhC_s`N0W&y7J4{$r?wJNVA& z`!|ry!u$LRT3>mI50OT_KGXvF#s<;2RX$zqaaQoWhERBt_o#)!F|>D1k>piTz8eL# zOJWQ*JXvv%)XWmb3j1oIoQw0^Xw>~8X@II&>`7-lsf(gtEWWJda$6o1T!&6v5DUT& z$3Rc0{V^_}^7N*1cNPJ4$Hz9^ge^OD!akhCQ7^|%^HvJDX#p}4V`k6#C`a&xS}wVJ z)J*jL(ist4s!usgH zcpAiAA!fQE3Bt0h^<);4#g2|k6ltQu_{H%X$Ee%AlMIe-TjYjSV)1 zkKt{!-3`~hAL;mWKK%4&&mRO5b^mi-Z)s1L(9?3uU{;l#A+*h$JtTYWKqYX})2V7K@pOKNN)-@5aRxZ6$E4S)4q=(vx== z2`tFqZBf>4vo8CeL{mXk`aI+bipg`MG+)C;rFe=ZY=V1xu+VN5?e<1-dz$Edw?eqI zfG2hvjJCC4`~G=@DGB{NC39H7nYudVBOa;!uLY9o#ls-PeUR;9uE>KsOz-WC3lTsPP$?J&~|36>YrBEn)xU}5S)kd;o@ZD=R@sk zjD{Pr4it>1=nqA7DB?Nim8~>n*t2p-J55VIv0^UuSvBX7-|^hZmB-Q(rX0_c-ayM{ z?a7jI0J8)gy80WA@{B)}X}jh1W`@DVd&6;ix_1!eBqi zIrZf_r{v`>B>sYj0T29jzDi=XnncY1vu3ud^vbr+Z}usJin+{ZtOEJFmo~nGL47U< zjdVko$1W_4XFGo@6o#7q`2+|39CnX2gB_AuZ}lb~#q-aamdSX@EsgVsoetf0%aZ=G zWmelggTI`Yl}sDm&;~^!iPCR{s$T7~Go7b6ZM7w!WKH~y%vHh57Sh9F7kHW}(^KmK z8TIZw;@>_=p(R6|(@^`&E;0?eVZDurN6YcsImfidxsJn**sh^n_toaH^>Xa)->-^7 zZRD++|CmqSHLT{F*YD1RA$rh+e)jr^!k)PZ@bH0!23h#^MeXQalYTxGl2fly)=ut#6qLp6 zXsOxGJ>{=zv{N0RFUzhc3_mOBk1K;7``;{QZE3HD*?wQwZtj6V?hTlIc0Pt9{Ow7e zJ(G9)@anYvUt;8?Ya~wcSC;W=NmuJo)dQS7Q$VK9$cHh?WT}z zOJ3+^X|(3uE{1rl1oHUwbqv+jMW>AQ4MeAjb?}CnxuDjlUJv6*y|+Dsb0{;2{H^9c zs0NW<&S&#=t4h>8{g=E|ZMNbR#+m8(B~z6zbzl%VYj|yUd}e3+qgU?+SnkpQQPcUo zvxlcXyUpUck|eKPB$8&@6obwMo!mCG7d8wv>BmciO3Pgn11 zevfnQD4KJbaY+2nN1Vi3_?l(IYWNm8PU~`J`rHdyfQyT4${B)z`an4HZo1@$?Tf+~ z^3jJ6?pi-V;i3@0^;t&3yY$3Ev)w##TG{QN=e(()l>HDrn|X(Uey!4n2Y9ls#6wzm zv2a)KZ@8}$I#f`!BhpT?$`XnfbDvD38LW_1n*4o!*09qSe3|0TdzwJ;*a$;#^@PH> z4(189-_;^ven`N;?PV_{1x?Jl#Yl3JlmX{Q9R`aVd6YQIxn|?r(q??A&Px1j zQRzI#(J^#J^}X8cyd_GW05=v_Ssf%XI{0LVm)v7Ts)BI8gTfrNQ8vbv7p|1ZgpoD zDXBb=E8LxAunO_%>=@wEmhLgu6beLbbQ}IP3DA zKeNDj#)O^Xx6E_DZl!+5KV)>@@efbV^%pGn`aAnta0j{rj=O`M=FB(bkskl_lyDu_cKnlE z+Xs(vC@_xPa%~NVd;HYdms+!;&4aVWoPq5!YUPeO?{`_ark%YmT;~cvBOarHr`R$w zh!e)kb^k4SP_60;d&yEuURAZ{n9kmzS#=f8F*_qopS9g>LE;lEM`S<+(qH#F zfwE&O9%xf0oP|s3VJ0$pO_geEFt5715nV&h37HE!ltOp@8kYy=A}t!rl^^E5Js~Hr zx5vWK|1M{hQT>GIxq&4l2^Ge6AkN1eZd|w{b}Qk2;){a!_8q*kQ}(7?z*fDoPo2A7 z7=4JA?HQLfX5b^D`~Z`83eo!cjJ%!SLRN{=XW|Nfc`G6BGR^8&{O#tp?sye$xZ_ZJ z`B4Q=RynSi$Hq5Oe1+4NL11^UgPYv>Lq+;zLIJ>Y;_XCxvCTQ&c5cJ$N0e>ZktZru zck1#<@5^LnX;w|-_XmbNS>(V6{d&PNWXgGSws z>nY=32-DODg}Yx&?d#)H8Q*Hoc7Dq_*S0P(GQ!{BbrRlH^?ZRZ1^U)+EK&JlMRWkSm_Cw2lQBVrrkUlGuJylu+DT>6onhDnr~o>x>#nAFCV=oLCD3mq&`(fG}&Xcns9{v@#UJVFg^Ivm>1C2`RU~pZkFax*&q^;|)JNh4Z0gFks~) zZZI*_3z01c!0#-*5#qA8=|HW8h#la>gVb?nOu}RBON{dr=J2MD#EBz5MOXwxmL9Z* zdLTIYitBrioFmn&X_s@%$=ZYRH*paNzYNCuBE&(}j$UH4<4iXrMr}^3>pF7how8`c zcl`_~vO>7)Q4X8hDD&1_kfH!1!&b!(FIL|potyndBfln02eS9xX(l;MzxG2;|4*)* z2eY5;e=1{6<~v8{YCO7}BLw9cnrR~*T4P`D#{sOb89BkEUa8Yumthna{?*(v=7Y7E z?#tc6coeu%3#znsg08zC_gnH#TjA}$t$SMv1J>0-6uB{lO7cX!-ize4UyEkEE4yoW zMXv1YR-=zt@sl$EwUSDUMKs)2nKZS~qUQK0<(9A1+>ImapF4Lf$e*uC&{xO-v->|g zD{34!D&J4nc_lSR=9nGCDq93Zk0ZqGeeW<7cbH$anZ{;#5W7@|OXsiYau{ zk;o3}#_@L^H;z2EuY)j_RaMZ{%7y9)kZY!*$5w7Vend!0bD}o(L@W0k0!b_ z9mv?OV2NU#KZhp-Bxl>5mCV&Urr}$Ac1**o*D(!6JU1{6uk26++{4d&W`0S@Y{m?F zca3fag+p5;953oEckiXD6t*%4n{%T*8*Yix`K$6?VzdK>=zo7KQC8L)YV?%&EqvJC zS*Xv~#hQP&i^8@XzKqpnB|YTC2Yy+=z3RHvr2b&9%J{acDKNCJaKznv9G>1jt@N3H z@xz%f5wzEMyegejlNZTUtzNWX;lp*F*g#(391hdb5eGcCBOHOMCqd=(cStm}l?vAT zS5?rln~S&n@eR35MaYI4ShJ=7$1sYlN5`;3v>;R=@s|~Ajt3s;^^vXDT;4JTv7jJj z?k(la>wwkV(`1?4UU1A8Gz#~2lYZwC+3r0$$Yx{Yrx(LT^E&nJ<6H&&a%e0HN*}8L zi8h{dy?|@@y7a52$6mcMQF+3S{t_kqRNV&d1QUNKM00YD?2E}YFLHTw!0iWXRJ&){ znEy^B=kk21cISRpalFzPr{g03b3aFIA}RV*yGkz2dY*QJxM})MHR}FsaAi9Ac_pDz^dpa8Ii(IXRpA=eD@rpN zYl~utrK}~#X-~OdZAsiYSy3315PrseVFuDfv#KTe%WqFGrK$CX)>F3TD8S`F6ng~W$pMuzPtvp{U%VHq?+w8oCRQnVscX!} z{ADi?HgTEeZ!I8?XPvT!K&{8a%3f`TDRXfLWs@nt4;#)K_*oo{>tv5u;ZR&oo#N8Ly{e z3xwlcO*KDp4HKVn>rCgG_XX`90FnkmzYYe-W&y6m9ye_9z{>z|wqYr=BwcwTjH1R}~vWEkDwxqi=1T>&y zf4#ar2Kw=Q?eK%kT)~!5XrMC~uahD47=HaGz!TLOjK64n{=*f@t1CRgjco5$OgXlo zV?%v~%l1@Cm86)5vsr$?$d*%g_F1zZkLg*)dtHW))9>x>LjNM&E zg@DcB*lEJ}aq+TG_^%W4@UHtPNxx^&OYQ~`;uQWCxz6-@#9VYr6H6aoLyuFYy4XN! zS@hlVhx`7V`1UXCfzvx)3l;J4&B@N|vSgd75_}EK)XNI~duex^!G6;9oee9fkyyn~ zG88wSZ0W5jtS7?=5%UxuFJtAZ^degQY(Am14MB>WxcN3reBD-0KRae0yN zfM#=@8HQ|cB2`f{g{U}O2Bfj~_#^wCQiJXIl-rpaW?zfhuuYRnsxYC2-$TLzfTksE0J64!i9Q}i2F?U`+MM= zF0IHj&}DSV7j+42*`f^Tl5*mf|C;XxmY5!KT7jY4;2BQac|+TsY$5V2^k>_Y+#h@YG#d9Mp6mVkhr%sOEREfwpas?Pr|GdwN#hIkgfFyayioDCpT4tU0 zS+&iKd>-pq=bIniFaTlxurmD7HxeaITyJ8W_O&7StHVpYdy{#kJ_#H4>T81b839_u zd|15a$af`gR5nMknZ3wXT3&x+gPX9gA}jXQIq4i-XVk&$V(k5jkGFRYnhh2~o`Wvg zH4?5Ls}aLM*bDGN5wXmBZE_(C!Fct(aWhNSW$Vd(@KT z*n_?0je>K!TXD66x4%uA8r@@KY%6J^5C`3&Nn>OIOL9?fJQH!1aC{g!{vZaDN$=SC zb_#8yN8S7Taw-S6G`~{?a20{9$mSh9kViKHUc^w(O?y+_8*XU7kb4C($qUxC5A(hk zufjeL*Rvw0dRd=_ z&zu!(zTgN3)oB@XowD4JoQGqMLc@SN%RPlpQ*JJhFwyWEMdUUs)hmnR(@BdvF7~ZB z$bld7PwI_?hpqNbqzdaTP)8U?UyJWWBalYBc7Oi9$Xi`Z_dNX}$`wX!epqs-uP4R& z<3-os5HYmS3~pq@&yccmdCat*;7i3X=4I!@XXlqSPv=a(0@JaShAH=5s27I%6C2ri zWOB~L-f0Zl+Iw@@F#YLA3?B?ZFft~4?v@J6pM@__;xeArz872H@V$vPXo7?%w?X3p z)ohWXWE2BsWQ&0Df5aZOwn~EN&H#}`e-kuVzPSEyGVevjIZ4WwoC)WdEIPrX&x6iX zUy)%M`9xlF(|mc6_+)VVRv|bJ4E4UYqGuKR*Sp3XKEBI@70ii0&#{)xsXv}$@{uhs ze^1tC@uRJZe@^JdX+k}M02oN^(+Bck=Y&4w=9wFMb*vBB^ll1%xG+WL-yjv}^eb5e z5We~V9)G3jqBcy~#U*Rw=sVW?qioRoDN^XBSK+;Rly*|$5!y5>d|a+#<4kv_Fq-7* zd$*Y#hkNnZvr7X24UbVAglx%Ic$ypp!abDf`sYdfB3jc2bscfksNQy$jl7c-en&^) zSw#M%v?dy6V`ahBHMiQT7isQY2=a|nq$fT`nNXCsFP^EHdheV;D%u{Z_0yO|Pbi9@ zfsAXSo(Si)1_+;TF1%RT7@aoK7v$>5L5_0dzt^|wUSFPEzeC!Jg#3dW&rh5o5LbG_141NgfB&S|5c z7u5#3o|ENo@iS_(;<$BQ4e=^i6?}hr9PuNx(7+PPK0&1>{xRZQkduH_-9V{2|DNYo z&*vt+RJ_Og2~Eq9>@Z_LlElnJ#>ct%ye11FjSjATCQg$j|9PPoJd7W?YEKKJhlf0t z9==Y)sLnpu5~;z((?UWu+Q?xSghkIJ0GD{=YLb2bEsW?1O?`I^OZ9&?B>s`BtxW${brjHj~-RP77t=?0vpp)_Aq1BsD28AQ+?d- zu;Cqb(j9j{xtxK)N2B*TJ6>Vb7fJ zfoex@K@nJ(4F=#b2>RpQP zH2}k-T6#G~DwG(#0fN#~pwn{x?580SN(JxWzwh1;Tn5&EA3{AE>A0J1H9!oUI{kMo z&llJ9t&$=ac>VZGuKX<>&M)n~941cZ==IghtaHy1DASXhu77%F@T=HV(_t8h``yg4 zR3x4NODtmfESrDcJlN%_)<2!La;3sp{yhjtOVtxmN+Z5r;+VbDH92XRx6}@)xJu|( zq{88yRbJdM?TG!KmXMdsCo1>Y5^Ttnkr;}W#k@ikK)&GGM= z6J6ikHAlHoKv*bgmi|`YdMQn9_f;9Y)n{Dnb}*jEP?n{p!AkkKi6&exE)P#y;cpCI z{_zy}MFR@o1HW~&kuJ|nI|_K6?$nRoOIL|m9cMV$ag27a$+vXf@nvk1J6q`rZ5~h8 ze~tA^wfqUPS2{w{>@HQYb6<`Xcs-5AAJJdgnuq{lXnvCYI@1M0Ph3gt3*Zdl#`b3>b}SxEL_*F=n{pFRY-bkq{oP$rDs zW}W4#h%L=JL1Zzw7Nc?H?=5h={OOi`Fa!1IHBS`Hu^pRhd3PO7O!a)%X4|2_T!~z* zy|l5Z_&nZcn@$s*C{_NfD7$6dGh!>v#EYEe-IpkJrE7eg+d*ck-fkp}sEplEONx(6 zanxVtF>x&bha%OK zmpd^zZ8l-ZwxQ}i&%xRGuFN)F$Qh906Vvei!+3klXy}MECfZ`O5O0vn!isRbr+i*8 zLD)NZFO=c`kE=IXQ@&2l(l|dM>V3SEv7;3?RM$r? z&Z}F)$Gq#L6{6Gz)ABmW-)XcotU&HpYkOF#eSMx;E1`$e?;u6Qjje1ui<6uK(y|*0 zP#%{z#UU|U!D;l=!~ZnV@(w9;U;hY~nUu5>R$O`vlPsOG2cuV|Zi1Du2-_`nu=1rL z6^|_VuACo;zLJl{qe?BtJ&Mo1Kh~A>v^+4Qu)DpcGlqU1s+_hm4J5Sq4UuZ$RY_kers}30C?DnY8-c<-O`&|D1?NLrBP0Stl z*VMk5PrR|YUQEg-X;Qk^R>Es_KljR$2&g81yQZf7_frt zKBiMkZ>4jQR)xF~6s(ab#T6;SBGMY;7#R|fhryc?YgS&r|9RDgA8RX$;uX)j`#b6W zluFEsTnc966Q3LY+Bwj`7SHS~&e}P?pE+SPp}v@Hw00ep!hV~V{@@XQ;`@)7^w~6? z4?eeO&p;3(cZNKUQTcMa^PbEt@2k8eHKk;M`AcwZ z-;kJJ`)=ll$yj}|=n`1p>`+MYX`|P#dxSR0omOALK1S^`h&rDp|W7rz`RWK%xl8e-^W%B*)rVY&z z0y+FlqaD`3Ifrl67QQaiSJ4S@S=3(1`DKSQ=M?H2|EmKTclW?(YadjJ^UvKI z&XqmVn`X)aWhSSs}`;92= zdUZ}G5!m{LdYe>NyK-<>UC=8a{DtMDG{T)T75^r4n*y4`hzB`AT-DfPV7zO1YelU} z-#DLkpT7wBgJ9B1(k-I@--(TqZJ04(MiKZyc9(D{}w8ILOBu=5zqy$~_;=9`751guB3 zYy4x6i;uXPE_!jR#iP~sz>LVCQSH~<>J85t?xG&-I%7YX=iMyeUE7tkXnT-8_tXlr zHu!j&hO8|Pm>}y@)sZ=U2}{Iol^Nmmsi;$=q33@XP&Ic4DyB}< zH{YUl0=s|x1>aWF#QP+?m@EJ4EW4TzpG_8D9bcP})nT<^KaeJ2`E~ckv&h5nq=RL1*b_u=eK6Xexzq!!?OHcBeI!~y9uN12$il2L zRm%EhtlFz_TzxZ#ONYObyLC>J370^S+@8JiVTAgC-Al@0!A54^Y!6zyOCg2#!#-UT zD)R7G79+b|<0>@O`Hm7y9_L3RY)hC;U9A=e8`o0<4+)G(O@2it&7D*qDogN|-lwmL zquo=En5!-ZY)_t6=Cn;#KFR2JCW5E8#Yg#{dpoKQ*8WiYlRvi9_3L@Brv8Z9;ISC> zy?ru}XUq7t-UpNY^?V)mABPX@pZ$I|fA5dk-)%4rH_>zT`y064XZ_lo->X6Iv$-5U zFZKB;wNhAgeag0BwrC+b%B##VJ!mjUmGaOJblMfR+hcU;d#9UgdhKlsTw#)dKLvwR zk+v4bJ1$Y#-8sRs0rFTJX7pfJ22{33JRf9_y=4aKp}ozWJ0VQl6wK>fdPBOpk3^Lj z^mOnSiGe=m$b3I+Ci3_qB){=aeJ%#R=}hib>JP_9NY~R0>HPSi2IX37Tc>H0SH3@x z7kD_}s(X8CLY6yfsPEW{=HBiKn@F|Nj!_`1>3-|pHS?I?7IMFH=A6|<^-ZyR?)lBI z6Dp2sG|Q7j_Zdg@X+JV`O^xcg<377VE%zgi2veUF>BZCvL%wBQTv2exr+xSvnzlYt zAL_2X4V^LYpwx~QDy>A`Q4j}{6|C#~!i4Ht{`a?z7R+Q@XqWl-?Do`)Vgv-%WWMT= z>1+WD1xel87`5o;fVMdG{jyGYZq6yJtfVnyCrpJW5-y8WVf zOZ4iRQ4kNCc@#6fPCe(;DhyF*XcGuoUf)Ao_%(mAX^1xxxY0^dcjfiOT*oP!@jqsM zM?HOL2WQzf0?8P-kKo+Y>Nom5nU@;wey5hIdku%JEdvAA^>rEqFgFRI6@$h4ffMf| zd|vb<1N*m4VC2159JdbUpQj6lTDM2q8a#lm+L}GL7PsK{E#~?I-vztSPr!VFVWnEB zxm_;H+r8XqB*eJS>)~dhGr??;ta4&*-Meq`AuRb^WZO(!aK>g3l=Ff0V5gk^;2T={ zX3^`5KbYnMxHD}>z2FLSRDBaeG?xcGI*SPghxoMPn~?rW;~g9a8`3;#6s7MZp-waa ze^)ZE2~jl^2+zeEo)6*KTY4yJ=J7&VqkOp`CR`tSN2Uo*t!iZMq=}={m>+fANc+3E ztp~0Z&ox;L)-B2SMbxaKax9!aoE@T3ppPD~BFIfLcI$Z#mRb9EmiBA*_4s-hi=Y`>38sp2ltKFD`Xl*AiOhZTO)#>xlE(|RfU^5uaIIXAp; z+%o)z5E+wP*2*NyaaxDBoj{pnO-s@gRtHjo=8 ze1dq|*h;yO#tSX2`eGJtT=TdLxkg*V(q|nMsXZi&^&XAV9Wy+Z?$!7fS6?T;X_G*F zg@SP{m06S=2eweCJNp{xIco+zOt+W-Wq0K^UpDG^Vyi|lN(^Q(JQ%RB=nnoLwrGKD z#xks{JbNF&+y}T$YdiWIN(F4g)gQmBV`~`EldNBCQdfKOczXun10B9l@a!GO#aBuH zW|*KrfI4wMQKGO9$&8j+%85_ZK2=$o&<*eJnRBb{ke#s zzvr@6F4~I_WP7Rn`U@~qUp`?Hy{!oVvrJ{mdE*tgj|Oc%Sk>`sV~*OZztYcK1K zvdn6G^rnCN-iD=zih9AaUiz0Q4c}@2FZ!NlhyA&UIX{9v|HwcKKO=GQu0%UKEc%gU zFDK!QMMTEVt(iNh)0o@B`6`;!^~tN#m6+>yw%TBp-NN8zi+e38-Q!S6_S&wM=47*k zB>-L^O1nLJWAn;T{@@NPS|_yuD#x@F^Z>nRq1r)H-vPonf%QyrUyA$h|j z9vP=$#~+(^K)RA)k2%Qd&CZ99p)1su{c53~#P8UaI5x ztM{ojZuQ>6+DkA{7DnLS&jVk1^S7jxBXWXHXIT<2YWjvDW-?XfP3;i}RVN6}o=ec%L*Bs&}gcN?~I$eZiHD4Cgp8R^5 z>%-m#fyEGz`Zwmb_4@6Mol4Ee-5So7&Fo%NDgggMS;aCJm{}*rvb0 z#XP37cq2w0AGwOwuit?6;V>vE%y@!ThER;6?yQwT_>Voy)7zqn~9<<>cZG(^> z*4;xB6#C^mi`vbOT-)ES^usz5!q2$u-pz^Mk0*_Ci#NNfeD4YgX1Zl9B=cN;8*qEt zz_nz@+uE5s0&Tx?22bf@ z>zTUYb}s^xfB`uEFk=c*FkM+`H|W&QMjt_^h7&)~1CRfR-h1Vy9eP`TLtnYN#ThWy z#!v|HOU((i1CFb*SO4bOd+G|moHoxS2$S_Hx;&MS+Hk%(2|}h$@s5-VF$l<<$1$Ra ztH&U3DGg73*ZwSo(&~HZF*w?PJiD2=+xGp^edq1i3^q)Y#ObC!9XQ+3Fw(8~>bJzK zkHYl#F*ZS;LyjwX)!lj=2~0}l8Sr>=9rFs3ho<q39(ei*W=wwj z&Gq|a$z9gP@7Zu)&zkZrT(jc8b*AV`1lwr~Y6H*OEn>@hD^Xt>P-xcD?_l>E^~P4T zvFzSBw3v5~h;H}O?^zOSr{UQH&&uNqbWLSC4v%)V9^M|cwu!5Tj4@=qjd4mgDLQkkHm)UCwu&zqKlhe>-BuxmG#HS6v;`qgtf8Yo7q~JpU z^GTWqX@Z9Ud&)*1^NoZ|fad&H0-^G~F)*tz^HD+_aQt&>I-?>|g z!)*B29iY1z+1)d36t7l)Byp*39L;zP%QXKxD1`Vuq~(o!U0%e*x)K5msTA9e%d!d>Z=8{c_$Kf%wjD9-k+rGbEAng1Y9x zEsIMqJy$v4m|%7jkSR#_L}1|3y^YOjSS22ZjPIObfLYPsJVodCcM^5!KIgmKwtq^t zI@LV^xJXNLv}DhARO(o}&0Lsz6@-b8bSY^H4$2$h1pe+2m3{Yj+l(@kE8qi!XC@BP zOk+qVQ69V7mrh2N0m!xPKd0aQ4nJ}1tBZeUu-R|eox#MH-<`q2e|uJX9(H?{&^*oH zTrEdO*x04DPG;`mQkpf4r1Zx8uTgyeYNK(A_B(yH`|=x`JALjk`ZHT`JKQj%RW6R1 z`0xg4FesZsQxcyJPU{p{CI?CSRi=JzNJh6lyoGww7uxqS)>|i3QAgC@u*{t^drPe- z-}rb>RN;zsYbDmTcD#ri^u_mdnv?g1N&j7|MpY`OkJgA;K^~!+vzOe2Suu#lDdX|V z?}q$HcY~oYnOo&dGw9OpgvG4tJJ}d9Y^Hi861pKkq%@c^)FSMmA^i7_)pogNL40z& z6E_Cp<& zhkGoj8~+v-*O|Vlyeon?23@Gycb~tTorjbFE@c(+cUs-w`HuTa)=ZN9MR)Hs&)LS{eGJJ}&zrA8Swg zdeJkPh~3|o8jl%xuB26pBh`|j{VK(W2{1VkX?MDXsXwq{y}w%6EG@F8tJR2mWT}#L zO|5uiE9n5uY|{=3~F~5t;7?Pw_ zPj3x?RMJZAQyPDbSsW~pV|Au`#}{gszG%M{#gMONtlQI#D8ZN{50JJLZL7C8C@xoO zz7bdO<3Zyt9&g3o=d8NFk6voeNvzhr3EvY#WtT3Ja{RF4sH zMRi+BHg-~FVxklL&{_ZPof5l|@P4{xousGi%K7#v`Ia}d{Iebp6Jf$MamoRb4xY%j)0jL+=~=?oxE@=H60;(UYH~Lh z;*gtM)~aQSA0*im>E2uQSZnL&+3FoMs}_DQB^~SBVPx@mcgu^dO6!>hf&;yct*eN` zcT_&T_8BtTvQ>A7+sf7mJX(ZiIkb3~hs4)mzci)qXat-M=i=bKZ@vB1J4QEXH?XCF zoxl*6*9ZOA)|F%B!tnQaUPctv1&}8Co7u!>S?l7EMtHY~Y;#}PJa1{Lg{DnI!;fog zlA7`5&-=FS@_->GF{(${_vDWz*n2{^l6}G+yo^h$?Gp_QgICMgo8D2IT_lh91z9021KUjc(;cFX7k zG_1HcG7sHZKr$!7YX-;c`jn{D?gVL3G6+g?XxMWg41$+%$Ey^hnTAdQK1Mw6@|5C522qJMkKl*XhoLI<4+ zrZ_5$QT$RFfoJsBOb2@^U5%29JOxtPZ>v3^T5CMQk>GEvCaq|B!&CRu^;t07`XF3W zI}ekp6h1YL)a#{Ay>GF@BR2Y42O#3nb>hYkj5OREQwwDr1LaYXOT8R+xd`QQVTJeh zgNe;o)Gz-fJZVev<5L^VHDwG4ZMXRIM>HeX=$557GZqi#yfk;U&%L2_ll?w@m+1uL z9wXV*h?k1xKNoHj{;`(h&XI?l8Ft0xKC~4pBG4L_wg&g+tenf9rHJw=lnpm(C8VBy z{CCtx(fOe7v>TgWz#(ET=DjkuqM==`XqWg-lmx>hu!h;wqA+33hO#YCTl9+98I7UN zrX%s zZYLEw>kuP8UhaQJ`L?fG3**tOtgPHw^YEOTtWU+hR-N#Iv+Wm~-8G&UXR_cIUUNgAV@@p*XAE8*~^=?VYookUW+$|19aE$Yc_7P5>{yWgxPWmPO*t<3<1DE<~JeMQkh_9GMe6ZO3JyLt9t-sxd;IHg+ISKBNT z0C>eztT*!s9jS9`=e=(^kksKTuso4D7U8S^xsP+JXvL6-wIM529m~H`b#F$G&=uJ+ z_cxnWH^8sdoCro?zq>$n=(p;g68@!o#(shJJqS{>lcGB5k%SZbr?2Eh z?fHKH4d36ZdKhW5bMm-4v(0mu4v$W!YiT5bj80Snwz@P5J>ay|4YG0}tLsm+1*M0m z!_5Io`G|Pl)=RLIAI_Gfe3^zaqJw2pCq_r_YO9LMQK3GSvK;Do%UqK9VsRxODKVf||KK)%>U@Fds$YI)J9Y=JUYL{+6lOQO9zt^^m5rS@E}eI+{-Jg8 zE{E8-6&L(KbCDK*D06T*+?d)K$e?b?!5?6MdG28Xg0ml^r}#`wNTmCKuha30*{_6F zQnkAL=rKpbfK$ncnV1+Ii>18J%vDudYQT^|@uNIgE{}IVT>NaSQiQ%!UF1ii0D%yf zc|9W4xR}YsV?AkylF@x9FatYM+|ODI zm(Wq((fQKqS-g2gnjVn)qtCA!eD#8UgQ~F_f+@RS@HZT+=a9sXS><@w-ky&^g8uK$^G_JNUfg< z_PAKUA5XNdCqk~uqvk#gk0tEA?nuSd-e_t{0&?&;Y%9CC#&A@)@W{o~dyS)$2YCn~RRq&*>+-?=ii_7P zzgf@b?-U9QYeH_G57zM0kE7N1>&94#=oB994Xi+%_Zo_qku{r%TrOM!vw^07rC$ec zg=ST*f5Jq|4Q8M)p$KrNs{BsB@viLQkZv7sXhWUt?@o^YUFs436|4lRgSk3mD~)c( z&MCBfdgjsikep6CF7!#wJ1+F*3l{LXR96b!dUt;f{^_Dyrzbmux*{5^G-*H%l9~CP zx;jQvp-ujr7h!xnYG;vWWa$PCrrEhcj(;5oXT@!T#+_B6+w^^RA z$`#?OW=Q7x0#X3w)G~`xek)xdUq{qV5cuFwPAiJkM~jF>m8O;ChlYLtiC)+_u9$9x?bJ$Q{OMr zLrdHEJTPrxplZi5TmuE@8(`%X@-1H3b3^t#W*|;!1c+RyDGn*kW}u z#ln41n;XsuSUs81{o+J|*wOLr(2bk_xRT__{&6Lf7tcz>?_#sHi?8=~SZ<@fVYj_3 zvi&u(PQ5%@A5?R2?Z9Ec4@}3$4x<9Ztb3<_fnZ%L?PFaP*z%VzRMzwsnFJ4*+w5d+ zX`o~zv>GeK)7iOxb!Ef3y&p~C^AJMgbEBMT%v}TShfUyv11nb@Ra@?^H};MycQ=v) zThhn_;~&TrX!7sT@rBeKO}6k;y}nnC!I~QsDqC9w)fr)Q>)_zE7gpcwd0(evk>LJ9 z>D4_Ob}wN#x~-}q8U&(63z8ZVN)Yqdx!(r|@{eEKyOBiv)ia0MGSbPAyl6YGT7$GQ zb8k02FRYgt1vH^O{I2ZfeeQ2xz(}PPpgAy59^`8 zkeV3}<>#MqPY377I5bRyut37U<{EdmFLTzc$b5GkpWC7wtYD{Eu9bN5qilUOL-w)LKsQ)Iywu&itqD!DTh=A zdr@lk7F471_}mnhD$Jgjj;(Yc0EPD_S3h~!Kac2St$Ox+Ea~xbD~L;pEa3BFt+auqt+g$t zH!@-E?U!hC=fOHr?srWIZm_k$4|0oOJqvB>;kr0NR)3oy&ul0f?06U}#M_sTy{G<&V?YN2(A zm$3M(()AuS29@1+02A<8Mm!e%lyH1r-{DA38nGCwwa6dKZ?;g_QdY{_B#f- z^Y4u4;t=Ee{t2x)-Mt2UB?_1LDXG^IAOy)b4=s%rP&Wv+~kcAMLw1it^92dO**Sh14DMQQz9yO^9^& zi|R4fgZWt*rL|#3UVj@bS|ZosA{_K);$2s#%qZ(SX%|pm{^y5gDT^Mt#&Xn$Z%kJw z%(o{<$ff3xPYJ57JlUR@mHS<}qpESHcC3XBMD}mbcztqQF_J<}jD61?>)pQRHZ$Tl zD39u3@~Msmpg;%xu1BsrU?#s6mhsIu!t(u(LrM70%Z9~__1AD#H~q9p6+<|{<}Z4+ zb?D>qewWi--1Dnf(SLq3H|1e4M`AsMyU>%)Oan3n2p@>D(_EpRN*Oe*yqPI7YMaHj zrjDy)Is6%WCwrbc-jlgG@q1*#HjWQEvHyQ}Fek_sie1cqn;{k!9(DAw86&m>p3&Ti zVYGWF((^u&>ti@0jPfya_@P4iLQqJJ;NLxDV61JNng;u5oS%(YuEdT}aA?^5b}@?M zw0%-#K#yFt)d=U<+oJ_?Icp5cMwgqai(}%#rbeL)Cb!8b2P&7Awp_5*1XPxkpa#aY zIK}iu^`V% zR&qblmJH|>6~MN*WbA?qmI)q+i;nTn7r%C;+3CT^VLgVPw806@S-f2pk?&Q0+L!nC z>jKHtIX;==^{xYR^z7UQ%J<8SN;0A??8|LK*Ef;H)9fGn(t8HH9|8AK3mlC4YKFA{ z%I{~z14SK_aMjzGNw9$~@=YJ&Q!BWgr)`g0eq7Ft-5l1oWsW}T&zy0|ev}={m4O^F z@!qC%Oap0^*_SpOaBy+m#yoUB6@}+Xw=ft?m@oEG!vC#a&q!^HFiSrUpcc_9$1`L1 z62b>-Gn4x2A?f%+acdSwLaRan1EvL_?Ob<(tyN(;2ykV1h&1LniBH`7wtMhcjy#_lV9oAGP3th zcNr0cmv-PCG~&2(L;DxQpwAZwyNVU4guZazDI=X{#)`u|>|njv-f&yOt6bzWvFsU2 z%e&t8>*w|2_0Pe`;EDhw&#hogIPbf-AGYv?Vhz93!OX^^C@b}^SCx;JVrFlrt=DlZ zw9?EvzdyEc5Bd6@?xA+m&*MLrsE*q+IV=S9Wa<)tBWa!}mHf`{Y}7sfO?LA^?s|9C zA~=A-PSa)>U!;*OY+><9Zzdny-y|+)nbPB{Kf<}9(vus6<&yK>MeuiVM`OK!A9lji z*EvX!c>45ehqOf=#KNbeoqIaD7|i%LkB9 zOH0pOhrcrf{8kdr%@G~eUU}|B04J?+UWp^Q?Wx`(t7UE`>%oI-k&FKif zy2%xhIU+CeKqbZ&*8*`CR}`fLnxBD!j7d{QN*eO;FSqk2pk{$D$a@b|Cow~ zz12Ftq+MA*#=KL8wh~-=&pN#J9MGs)m^OuMJlB2k3jSlpCpY|DE^2RWN6ok zbELOCUx(75Pjc%q@wcSdCh`AyB&lxUkqqkZ+x2E%i+gk|)^u{~DAnT>IWzda!ASk- z=sr466N7a`9g1b=3{9)_-|sQwe$}m!iWJXDx`wnTs{3v!IfI^BUQkKbM%x=sq-x=y z<6-Dg(O=sg91-xYo&ZL4z1zEHQtI04;f!T5ZThuwy%tRSy#Md#DsYxT{PnaGnH9Bo zOf-}gpTE*Fq}lz)WS7u4PHBtt3A0HMQlo|OG4rm0s_1;~E|A10oM@rA!9;)RN8@V{ z`&$^P+rrTWAK?ilwa~G~%x>HlZ~d>d(5S%+W50@`m-fC4{c`+aF<{Tc{|;rp;JE>F zxq~M}S>!C1$N0~Bg|F{ZJm!OidX9Nd&bQcn9hmc`gtCrbMhtDsmjCBk)h8y!)O6lJ zIA>-yNb6GUohTSyG_v56fGNmL^fG?Y&?{G8{kReqO)kP>u7xK`Huh0*E$Y9asLhcs zhjB9x_3pKsYipTERC>;B=S;53H|tIoLl&>+NPV&HR(quo#DTWj5aZco0cl zc=`2qk!G%c`7&-&e=gUT`_2I0*r@OXMcC3yC=kZtMF z|A-vaZ9tnhG5Q0bTsDESnw(>XvU%Rsdqlqd=o-l)M?ccSJw0Dr+A5iS4_IKSd%Es- z4>)cXFRJX9a0RX^QqI(NB%2#U8Ph1NPU6s0x8lOPSCuAsO;Xu?h^hDFG&S`tkCd@@ z?cq<<+|QahF437;HjJqoCi)OU>@xg#9LJoVg~~Q74j?OQ(+augClwcEX0ApDahADT z^KIl=YNTyG3e}Uoofq!;SPuL#xsCPO(2sMHk_c?R zITpZOH*1Ij(Ow zz2L)(mYREFk#U;mwr-5o!=)n`V} zWX!B2igr5T))MDZAhbUl2}^Le#hyicO6#$zFHWV|{k&W<2#>3b7qZT$vAdu7x_6zt zzedKt-3!ff@P=I#l{9iobSF-KQ#VQXFUv9G4a@xsc8ctn<46g~IBtIrfdN*!s+Ph>g)p`^U*D*ppcQ>`Oqsu;>Q8Ob4BGQmN4z`-UAS zC&A+uS`1dp`U<`kKDSeG;l+LJ8nDiFt3EpX>dY4T)o09TF;qpx!#= zR2UrxO?5H&Ad=rI$9IW(Hz`L!+lCsfPgSyxrpE%>FrGxPn%nCoUJzcwnEdk@Zlzh( zM1SV}9oNF_#R9MleHhqw9*pK;A8GFR^egsZ=o} zxhgojEy>B$RVPO87BEL8lD%**|-;> zs(lV(G=-HGc$6L{g7ypJ@tVCN4oz=FK7AJ_?pa)YHATteSA&-Aka2kTdLQB6q6j0+ z9C1PrqkUHE^-mg9DHj@v&j#2l3p#(BV{Lw9S3|u?Q{f+DaKSN=$Tu@OrO7qEygJ<( zNYt8YeH;d`a_Y2`UKk$F!z=SB2xUCQt(HoAxQDIbGp;%F%R^|XK_h-_72F$nIj@N(MebR!VGuA={b zT)~8=ds>t_6~U*tt3;&3c6@*wddQ6D{cLCmb@yYbE+@333z}*5iGiSNCbMM+mblp~ z{LbNa@eS}X>cgk`F$!2@nwT!%*--eNJng;G^4NK;zHs->;ypizYGdB%f$VinYMT_& z%dr%zc&%P05z28F)4L=pdU_UyZ#CI-gV{&hA!=b1G@2w{y8;$3lRpJVj z#lt60;Z+MKkXtumqx@UCS)^w3mv|frziXq{cnk%RQ@tGjLR~(q!TfM_&4U-U+FO2# zH|CiII;67yOsFX^Fn@T?giMYD#-w5wvEm3M;C^Vl0^xjB=R}(nSLZ)JRYN%3?HTLk zJcsRr1Vr=NS}6{;8lZ%;mFLWSGp)5c4le3tu}~CjvE`}O>TJBYN-&}FllC8T>0*B4 zgEE}T3(hu`^7-%-}o`If^S&;q_-N0{}*+XBuAQI4^|cl8Y2U__zXbRa`^-5K7;Z z6nQ=E>{}&!U*?8xkZxE zF%X60e+)!;6DrTpaqMaqk}U5D02lDXRckz~)q3Dbv>tCviS2|-`DI2r*S)kXesre0 z{jmvFm%cA@EX(G+xl2ds{w+5GT0d3(YX?F%2eaRJ8L>4l_bLS$lW@M@8weu@XNnIbA zM?Y@GDGe0>bXV5wYHPrG6BM&wDZ`_>w&#ZW$^pxRB9Q{{iS-@s)F^)sYYO8^KbK*i zcu3$g?Hcs$hMa^$Dn@w(^57_Ev9-ew*4G=h2MHrfmT)mygo|#F3s|{ z)ykh-Z6@=N@U*s84wJd3GaZzS=kU1}`$$vER=g~bUF_WUQkwyFv^gj$A-l^)B`&9` zo_FUu{J*;NBudGd^&)_dJGxPDGakx9dIW#z>SFS;EUy|pHgG-)Fospi+rJkiqhCBu z$7ppiig?G}QJ0^PD_Pg@ z{U$N??5JAXea-iZi6bgezH5!sj8bg8FTNitcsH4~VJcv$myvbLX(%4lbuJC$DSEVWMF)ye9L~$ohefuTf-ZMbQTrs zDJ04vt;O#-IBN=QGIunjwRz0HN2@3}^zmtx>}5gcU$X9{8lF{$D7(oJu6v-yviIaW zV`eG#(s#4~H!@QldBniY#eMWM^^lZ zE~*Duxa{rWuy1ejauI*fn1T(`a1XaOzfd~db!! zTkoF^PSM`af)5o}J7uQ)iPgvN=ch#g^+HuN@fOt~VHJ^P9wUJdimxlbI z4pXNRnDdEOH5(G0iY6UV*+@(3gK#96J6}iHK*Z0NOx-|Z{Z}S^+ohP1Ty>xxf`s?Y zQ+6wc>tW?&Fd>gIGP>7ML>ImGgy^^pf94+O}KTa zbMMOoo-wL;QL?0TzSIVo9`o-;ZcOq%{ZWIs9k?q~Q;Hsm80=*VUy0r=sxN%q4h~ zCB<(q_kgZ3t@znO4=&G7^OSx$D)+;nL_2-1dz^&=87%gwgl%SpXWFxl})kjWC<=q0m1uW}vQ!cMQw*4#Z#qhzTwS^rj6<%&c!VD!kp>IXxfv0Qa7b?(1X z?p`8co{Pc-Zc3bv3qS0^#-44vCiSMZkLC%nH5<$ zd^$q2cOP)%(@X3>IbhAt4I>y+bN+zFpPLRn+#U!3<4RV#ES`aXqf3snlfE>(qg7L* zmn#aSkiU+FC+gcW>yOkaTM<{7r8u*g@9jIpJ*17HybS+od2$_|rCoO*U%!xX2S3jT4(WzpFlVn=Ro^!@9bHL zdpKGXc9v*Y(L~h~)Uc|QLVV81{uQsQ^&qR!trPu!e{=BZ)(Jv;k2vr@>qz~a55N}{ zgAU}7rH(G*H-w6P35M`ISsv_5ChtB$**-FT3D{Fxu@=|+BQWo!Wv#ePe-ks0dP6nD zLoLxxLN%{qid;vp2C0wRe2kX+NeVW-t~{_k#@byjnHyJB#0p-u4LwCJ8BvNE$gGc| zN_;pW*hw?<7^y%A`i40JZKi7?)DNZf-0yAbSRmYz)QiivYL3!ts!~qf(sz~w!@m^| zTlClP@;iPLBXK<>=1pr%T!=d2NZ1&cPm^(*vdrjpEmelpj5W??*EfDB-qa|gg%z7( zzgJxlAi%>z;F0M~!KeNDrPMBmT3yV#Xc9T^Tg|U}7A{Gbh!PpN>@@|i4VHRrtn1$U9 z`X94Ut=f__an#SgNtm}ym|JT6@4HSKpzA!}i`wI73gsM@rP8~FYS{jvYt+2E7`r|> zkW*Z{Pf-;NbVGzlr3+{9UIzL)HewP#Mt$~CzHR?F^GCSbG@)cHK|KpEPj}0=UNQ z9?O%6>eUVe&?C$Ln06}g4HfsEx+*U>Q0!k>K{fG(x1qT6hvBb9q4yu}OyM2U&qMAf z!XI=7S-5LbsL^n|NId& zw|PA3-6Vnb^4ZR{+d$U`qg3H`zFuvfNi-YPr>bt^-fB+FP2cZ#TZ=&Tym-pN<6T!G zO7bK5rAP~Xjj2x|G601hl@x(SeEx(-(Je(LZ5T|Ahf~!Uk{k4P%hhr@IXVBiN&Wn!)qyG-Q1V1DXD+6daY=e$TJWYVH|k)eLt=Se-ni`ODP zP-o?hX0&)Wt-_ng?t=z~QtjQptUj$mk+Bn^QVUx-$*>F>s$J-WbuZy2&hIz1vZ_3& zz5?&>pbq&J1DY&bQ>jDar7pB9d9*43YFSnEpUK^A%ao$NUwcpH+^%tS=cT)MFy4*_ z$GNpvSXQz9nwGi%B+tIQ*#YTcc;qex>Vo~uQlRK0Qgiy`1-j?QiePx07eZOqo31yyYbfa?#j$TQ-Rui%+!eb>ih`)-6=}?_ujB5uK&30*YkYEIT}H_<~2Fl;EkpW z12CYaqBy^rywEE+BSlv)==vNLE1ai7Gycc+yLsn8OkcLv^S!=p_R9mmx1keycA&c7nK3YrYtX8tRYVK+s4{5%8b8y&WKmw6=K;4T5LiU+8ai zRgJ~f$MJXOz}MV+*fu$pa=pheX!}E$I4oUT$jsl2G8Zf6w{7BNOnnFI%1YOge{9BJ zXzXph4kU0OSy z55SscteeAVLdJl<9fo$Ze8k#DJ&a3$Lhp^W^=;v`xTYmL+Q8y|{*{Ydue5O$59ZCOK&nR-kdymp7pwy612&bW$9n5Xs}UThU93U3PxV4ZgLW_hE0;= zMzn8zLy&vcyL2YT9d&CqnUDVUwg6ZTI1AQ-c%$AFFI@HlU>eTPk{=irkh=vf;gZ8Z!!tUMr9D#zy7YOKKGu7TM->mmK_$wkUMwg%5MqHB~v~N z?JG~)t#%^jPtf2SVV}2KV8j(3JL(mkH5kSEKEFHN77C_eKB!ev4!-87x5*)LL-?`t zTF-&u04+ai9!nI}KJFC2{u^bXxg)=@3p&XW;mWeTeEAZ59)F%`n6P0@*c*a5h z>01WjOl~dkb}@$5w2$6C9K1Q87*9S@pE*6n8(97@wF_?&8QR0R5#5{0a*B}zY|WS$ ze^C3P5Ew0LPCX~yUHtd<&C0hmG7fBtPtw)yv!>d6gW(hQWlSPu`PUR)O^^qgT=PnM zS$!+qTRka9`gs?&YF$7~oAui{+vD)x`)EYnYTiXYN_JXns{}o7_S`=Txh|+=eEQ_A ztefG9I!{J&n6z((B$i;afd1D_5EY`zl2v{0(U*`zF&Ub3Z=@fG+ZkpDaC%%wsWpeN z3?NR7ctGFSADAxL8eiuKUA|G*!3>xKF}Px&%^COhy+CJbyg2fl2YVL=8@|0qT_ifR z@`(D(%Lfl#_IJWdy0}6nS1?zEWU}*WCg;B<#V7Pq)`)H?tZkTU*8xbBIAem_1HhyS z9?PH42sUBis?i(SR>cdO%k7B33(&_TQH;Vw;#-Ai7gt0Tg`U+}uIeA}WPe*Lil3Ip z`DK5CU~sN78C|;Wap;lv5mpkVL52gzNb8w9L34oT*q8Tw`jVT8+bupQVSUa$H!oPj znST&ZStgKeUgy%&EMNWp>&HJ|zgYd&-L$5;O@IIU!~gy4M>oCF-#<+hT7;iJ%*6YC zIsf@%cO&?Dc8k+z20N*){rMw%Hssd*{uveZt(kuQINpG}cZ&M=PhR=&tK0bdrvxR> z?B~C~?Z*%8py6wYfBN9uHSMak`2P3r)Eed-tV-z<0x$!VvuQa!+i5yUb{mIp?x#YTH$%hr$HULHBUKj)#J9r!#{0IW&_p2Sq>qupbMIL%>ev{TnL*n=$Wyx4 zNnjAh9C)b9`TZGQiaqZY;hTM+AOqf7qe(9BEa$9wK4bLLcF5Im$Q(e5 z^E-C7k<*1d+O*m7wc|`Y);ijr9#2jq8eJGu60`@&rd+A+kkGA%A*qBwe9aZuzU%Gk|lY*x4ii=gQP>($OeeDp?k zT0FEU(phoSz@xi%^bd2$JaG{1wvc3u5GW-{1AB!PRP_U3*~N0-31E7q*v8QPJsF)W z8{E>tCPX&!o*Ep6OSZ~%+ey^yXtBjLa-qF`F~^{e^Zlot<$%C1bQ|p5edTE^p=S1( zwz*go!MXs1*7W|ocQh%D#j@(a_E*k&|Djnd*OyAf79-Q&JvFIJj1%173VHQf@T;*z zpjdfa6qk2j8G#6t=HNET#OuW34sUWVi;BdR9~Vrl9a|tF9C`O~ns|(@t>+$Ra(~dP z;-2n$>tOfp$J?i{9heDqjt{tybU9ZGOOWehB^Fg~tN-M2q4O|hgjZ`=<2wV zi%zCn-eHW=E6x*pZ)WoBQ1!9QT9^>B?%=tOcB}b@GJY6ZA3hKc@UA6i%EG3foL&aCIVJcz*W5O#HNLjbq~gqH*_*GLY;W88JrnNlk6;bmRZqJ~ zbCYS$F*LCK0gYDTyK)_+w^u_Fc*1Kv1|t_6vM$U1IMu|K2wAPW{$fH;SBam)y7Qhg zR_|Wqe9~+$&>RAv^Ad}9mo1-fYV$9O$B}MI+t(dJH$yg^_Sh|o@0oWYPF?m1fcxUC z5n>#trT5{|AejZ8F_894)fKrL?ufS(TsO@4iP5RWJ-$!F&s+@d44yZL?#P%1T`dOH zdnV>uF97X2u*WVxc@fbZxw0#{d=C6_>gjfq(DjXRiyU*EUA1`%sy< zQE(xgSQWiEugDE96vHk6ck`@FGfdV`&v;tl`{#)tND@G|B$h)ODE2J{$Hh?iLQCew zv%-W@AD|Iu=uNw{qxfX<%5kFIy~69Hj)#n$08W;6#A{`uU&LbG_`o7OlW}!WhG#gJ zuR)u*)~!LLu=oLkxpz1voyPC$1=%b^Z5&qwmDA;r3@@vl4jUXN?rU#I2IbX`v7x|1 zWm+`58DiwH$~vl-8QraSJPMR_+jWDXXM?rI``MnZqo~|&<@@;*Jo4uSHKLjDkUOyF zRaqDEragjNasYKX53k^~fg%*9!3}j`N97FXf^{Hm`PjkjY`u8r(9qpQr;`-Z^Uj%h zJH$K;`y|=0f>-kL*$-ycja!_`7_tB8M-S}U?#F9{_9=Wr`qjoK9;dQ?3+k6xZXfJ& zg<}p9=Lz!QQPbLUGueTr<~vrw>_I#6%=r=Z;YG})JNgZEyil^%(KQ8b zfGDoV;}An|S+vvn=n74&(B!1^aZe1#6M5%tQ+;^YPRui>jjuqk;INDlz*b(=kG)V< zpw+*w;<}9iIQ+U7um!`tz~g*@x^-`fkzX8+2|3?sk)?bA5G!hBIEgqIq;9Tn@+tNS>ISWr+2 zaU^_R5J9A{po|nxgjWc6COKkG4tQ!#9vE^VjtQgqP%JmPRIj;UKy301a=lFqq>h_x zTFk|~K7nVC=M#^sVa>PNbHh6tT9~D`--h*bCf*HKEYwVpZ14M=EMd74pKg)ShtS-!14r-V==9<5B71*6FgsGc%f+P?l}-HD`KyZ(86oF_*9N z046{$tXHJbw^^4IyN68D5Fgq3pRhJ&xE`HI0;XM_`oTUYlN%_*gz4FH5>}BS-@oH9 z_tC%NF!%~G8X=0i&{a4$S&p4`*_m!2L&5uV$yZreY9Vdu1j_i)Kvwu>1P^ zC}$oJU+)IlFEM6Am-h=@#L6UD{LC&Ybz=WaQyEAJpztiWNi)OAPt zkgCiWszN=xaEQa$t&k(JWH5rL6z#uus|WM~}rAAQr) zB}$2q_*3DqF1E-oo#J~PDWAyQY~}v)cXsPglOyVPfn<`UfgVmJ9gKAM?k-F%5a=m#2M=> zlif}%VZy6bNpGE_{(zjN)H6fYtZWJ=xJ8VA;?Zgp>oEh#nFBr+Op}bdKr)}+?7)qnq|+|7+V?U z3~=Q_i6v7!he6$|!(PuOoUV9oJKl@6YU|T~vZVNLj3w1e{KPu;b2>KkciC||{UyhXkPVX8Lx^@Qh^Zn$_}JA| zP~|gBS9HQXV5;S|AHMf;lQNuV4ducSF=!#D;a)s9#IHSaVh8YY>CF0f!~Xxf_+RWY tt*Z9VHv1R5FVZ;udhv@r*KODIF)FHQvT2Is@4tSr7jL`91k>Mt{RebT3#kAA literal 886538 zcmeF(2b2`$+U|WM=bUrSX~D zA_#(@f&wa>zN`M%l-upM_q*P`&UfB*PO%pIckxiuQ~gZ$-F@w9iEi7valL*6WAlz2 zH#9aTf1AN0Mh+j9YCr_82!Zw(F4Ly<&&Q zi;?B#{bGB@=50D^ zP>=;B?BHIpkv(bIeQ=-HCjBCt^3UgCr{(>9_p$E%sr*l;ns-R=-Xmfo zxA?bTT}I~}UFM(eO0@03zqxPq1`g?No6lXUQ@3fWhOImN>$_2|QrW*b7yoXQtNeF= zue(vMa#U2gzursTi^%SmiQE1^Eyk_!Bl*i|*J=}P+@x8Ze|_2&DwgHfpTE0*+78PreqAHfFP_A5M|L=8IDpxIEHuA*! z$6x+ki9FOB4)&%+WMVds9hY~oJ5|Evx;AarvPsu^4VyM>UAJq?noaB1&7XJRke=N~ zx|20Bb>f~4k@eWYeMa_;$zRSLpZ@oXZrgp}sMx&yA|og=Sjzt8Z}aB;>s@oltiC+D zjQiDpKG(?E{=+Rs{nIV}bu<3mEk>95$GiXM@Aj{E=KsMh`L9jI|H2EEy(Ij{uTxQ# z%KgoASdaUts45l8|8>Iu>2>O_uS(u)@Cg6s|4%Q#?zFd?^j>$pm;e9PGy0z&`U$Nqzxc}}fy<7P=?|}dQ1pcchSU##^RMr2_$?`WR*d{U&BX5AbZM3rSzxVXY zRVi2DujApruz&wlEP8kE`M>*2t5&J}*LQ~h-823F;Rj!(Qq@Wo{`$5s?tG)mxK+;^ z#F3|(bw&F_beX?vlhHM4X7-%R8#}n?kY4=;_le0ry!#mMJx$m1{o?(@uSfV(d`y4| zF%c%lB$yPFVRB4?DKQnM#x$4~(_wndfEh6pX2vX-6|-S>%z-&E7v{!1m>2V5ek_0m zu@Dxp5^R>x?JK?g%v18ZU}tc`WB zF4n{P*Z>=1BW#ReY=TX(88*ij*b-Y|YixsUu^qO@4)`c`#7@{5yI@!BhTX9T_QYNo zi@mWA_C-CN`t#=jI1mTnU>t%&aTpHA5jYY@;bZG^oQBi! zah!oq;7oiHpTb%AG(LmR;%uCQ&*5Br9$&zDI3Hic1-K9w;bL5ZOK}-4$Cq#guEbUN zGOoroxE5c*S8*M#$JcNJzK(C;Mtl=D;bz=|Z{gec4sOM5xEp*57#_#3@C2U3Q+OKB;8{F}U*mba zfEV!_yoBH4WxRsl;Z?kb-{W<>fj{6)yoEpFPxv$5#yj{6{)%_;9^S{_@OS(JAK*ib z_lOrB!T6W}6JjDvj7cylCd1^I0#jltOpR$UEvCctm;p0lCd`akFe_%m?3e>{VlK>$ zc`z^L!~9qP3t}NGj76|07Q^CL0!v~kERAKbEJk5DERPkiB38o6SOu$MHLQ-&7=sRm zum;w|T38$FU|p<-^|1jq#75W{!`K9yVl!-xEwClF!q(UZ+hRLxj~(z)?1-JPGj_qQ z*bTd55A2D(Fcy1bAMA_$us;sKfj9^U;}9H*!*Do`z>zo#N8=bAi{o%SPQZyc2`A$e zoQjX(G@Oo);|zQPXX2Ci6wbn@@fmy;XX6}v4(HC(z=gO77vmCKipy|0 zzJx1qC9cAkaW$^NwfG9YitBJazJ?p{b$kOi;+wb$H{%w33*W|fa4T-Z?f5S4!1wTd z+=(CHhqwzr!rk~WeuAIk9{ddV;y(Nw_u~Ql0uSOLJd8*1D1M2@@Hl>jC-5Ym!qa#L z&*C}!8qebeyolf6CHxjI;}!f4ui`cQ9 z%z-&E7v{!1m>2V5ek_0mu@Dxp5^ zR>x?JK?g%v18ZU}tc`WBF4n{P*Z>=1BW#ReY=TX(88*ij*b-Y|YixsUu^qO@4)`c` z#7@{5yI@!BhTX9T_QYNoi@mWA_QihK9|zz-9E5{$2oA+zI2=ddNF0TuaSV>daX20) z;6$8+lW_`8#m8_OPRGY_20no^@kx9NXW`TM3_gpqaSlF*bMbk60q5a-d=VGmLR^H4 zaS1NPWw;z)!WFm@SK-UJ8rR@jd<9>{b+{g1!wvX4zJVL@P27Z=aSOhMZ{s_-6}RDb zd>41%d-y)?#1HU8+=U4W>UjH#ZCcuQ42oqxxOp3`cIi|prmHH~gCVSeHL(`f#yVIR>tTItfDN$`HpVbE!KT;@n_~-XiLJ0Tw!ya8 z4%=e~d=xukC+v(}uq$@M?$`r+VlRxv-q;8GVn6JU18^V?!ofHMhvG0Cjw5g+j>6G6 z2FKz!9FG%lB2L1|I0dKTV>k_`;BtC_+@M(MopT*fY2cN^a_&mOV^Kd@C zhzoEbF2cpQ1efA6T#hf{3S5b+@MT<$Yj7>Tg0JE_T#v8e27Ddgz>WAOZoeHA z@g3ZX+i*L+i#zZ=d>?n>2lyfG!jEt_evF^sr?>|{!@al+=U_wlUi7^Q##blTqQ(#I=g{d(Orp0ua z9y4G@%!HXS3ueV^m>qLqPRxb5F%Ra&e3%~#U_mT|g|P@0#bQ_-OJGSXg{83!mc=M6 zhvl&XR>VqJ8LMDbtcKMw8e`DG5Z1t&SPN@o9juG>us$}xhS&%jV;GxYQ*4IKu?4oo zR@fTbU|Vd5?Xd$siXE{NcE&E)6}w?~?14S87sg_5?1O!=ANI!qI1mTnU>t%&aTpHA z5jYY@;bZG^oQBi!ah!oq;7oiHpTb%AG(LmR;%uCQ&*5Br z9$&zDI3Hic1-K9w;bL5ZOK}-4$Cq#guEbUNGOoroxE5c*S8*M#$JcNJzK(C;Mtl=D z;bz=|Z{gec4sOM5xEp*57#_#3@C2U3Q+OKB;8{F}U*mbafEV!_yoBH4WxRsl;Z?kb-{W<>fj{6) zyoEpFPxv$5#yj{6{)%_;9^S{_@OS(JAK*ibmx$MYjE@O0Atu7am;{qzGE9ysFeRqK z)R+d-VmeHZ889Pe!pxWjvtl;PjyW(V=EB^V2lHY+%#Q`IAQr;HSOkk=F)WTHuq2kk z(pUz|VicCc@>l^YVkNAMRj?{n!|E7~G3a0jYhX>Rg|)E`*2Q{Q9~)ppY=n(5j7_j9 zHpAxF0$XA$Y>jQOEw;n<*a08Kj@Su1V;Ag--LO0Mz@FF(W3e~(!M@lJ`{Mu{h=Xu2 z4#A-~42R*ZsI1b0-1e}PIa57H8srVR9!|C`q&cG*dCO(Nz;VgU_pTTEw zHqOE4a4tTNFW@|!k1yf^T!@QsF)qQSxD1!$OSl48;wpR@SK}I7i?86TxDMCjYq$Yl z$2V{zzKNS~Gj74R@NIktx8gS3j_=|Qd=KBpo%jKMh`aD3+>IaOC-^Du!Ow6n?!(V< zKOVp@@E{(-!*~Rb;+J>~kKsJnOoM4L9j3<&m=QB!X3T_y z7RM4;5=&ueEQ4h+3d>=6tbi4<5?014SQV>bb&SRsbTEW9uqM{R+E@qcVm+*n4X`0L z!p0cJCfF34VRLMOEwL50#x~d%+hKd`fRAEF?1Y`M3wFhB*d2RdPwa)U*cY>d<>`IbbK6V;1f6#pTwtd z7Cw#7;IlXz=iqZV7oW!$a30Rb7jXeD#6`Fmm*7%dhRg9KT!AZb6~2tCaSg7;SMXI_ zhwJe*+<>p+8@LhQ#7(#vx8Pg&Hok*faT{*OcX0>4hwtM~`~W}1UHB31#*gt6{1o@# zXSf&l;pey?58xMg5D(#DJc38@OFV|h@hd!mC-D@X#xr;p&*9g29xvcU{01-Kw|E(^ z;CFZxui^K29dFMq>;*7{VG@6Ki2@tb=v2 z9@fVO*bp0GV+>;xY>LgWIkv!-*a}-?8*Gd1uswFbN3kPz!p_(QyJ9!&jy)Jra4e3)@i+k|;v}4mQ*bIihSP95K8`c+ z37m;f;!`*apT=kKS)7e?@Hw1|&*KX?59i~HxBwU8B3z71a49as<@ge=z?HZPU&htA z2G`;%_$sc$_4pcYz}N8&+=y@DCftl$@GX2B-@&c84Y%XFxC7tA_i-nFfFI&6{0MjB z$M^|;ihJ-g+>870bKH*y@C!VMhwv~S!K3&k9>e4K6`sJ8cnVMB89a;U@M}De7w{r} zgO~7Iyo^`yJG_e5@O!+DH}D6%iMQ}a{0V=?+js|m!C&z%-oyL&8~%=e-~)V!@sjfT zkMS`9Cd5RT7?WU9OoquZ1*XJQm>SbyT1i(0EQZCg1eU~7SQ^Vth3Kh>fr@hOr4Y#b($XTVP9Ug{`p-w#9bX9y{Qp*bzHnXY7Jqu^V>B z9@rCmVJ!B>KG+xgVSgNe191=z#vwQqhv9G>fg^Dgj>a)K7RTXuoPZN?5>Cb`I29km zX*eAp#~Jtp&crA2DV&8*<1_dy&c-?T9L~k(@dccR^YKMofD3UEF2*Ie6qn(0d-Yw4#5ZvhZpJP67QT(|;8xs*+woo8f$!n_xD!9X z4{;ZMguC%$`~*M6J@^^!#eMiW?#Bc81s=phco>i1QT!5*;c@&5PvA*Bg{Schp2c(c zHJ--{coDzBOZY8b#w++8Ud3zpJzmEf_ygX=TlgdXgg@hLyo0~suXq>l;eGrKf5$)Y z0Y1cd$$0(8_?Q3_Vj@h8NiZoU!{nF(Q(`JijcG6~ro;4@0W)GI%#2wuD`vy&m;-ZS zF3gR2FfZoA{8#`BVj(PyMX)Fq!{S&1OJXT3jb*SbMqxQDj}@>YR>I0y1*>8;td7wb zgARtU2G+z{SR3nLU95-ou>m&3M%Wm`*aVwmGi;76uqC#_*4PHyVmoY)9q>`?h@G%A zcEPUL4ZC9x?1{ZF7JFkK?2G-dKMufwI0y&h5FCoba5#>@kvIxR;}{%^<8VAqz==2s zC*u^HijUzmoQ{v<415A-;*AD0?xzv_#!UAg}4Y8;}Tqo z%Wyfqge!0*uELjbHLk(6_zJ#?>u^23h8ys8d;>S)o45%#;}(1i-^O=vD{jN>_%80i z_waq(i67vHxC=kR-S{znf}i3Z{0#TvKKvZ_;{p5v58@#_j7RV&eu>BMIDUmE@FbqX z(|88Y;yL^p&*KHWh~MBP{1z|c75omb;x+spuj38;0dL|h{1Jb`pYb-{!C&xKyo>kn zKK_Qk;~)3{A7Z@Zy#8Z+On?b75hlhYm=u#?a!i3KF%_o9G?*6CVS3Df88H)P#w?f> zvtf43fjKc3=Egjj7xQ6$EPw^E5EjNFSQLw4aV&u)u@siZGFTR)upE}h3Rn> zRk0dY$7qZ}2SZo`Yho>|jdidt*2DVP02^W>Y>Z)Sf=#g*Hpdp&5?f(wY=dpF9k#~~ z_$YS7PS_c{U{~yh-LVJu#9kPSy|EAW#eUcy2jD;)goAMi4#i$H#F7K7ljwNqh=t;nVmGK8v$)4nBu-@p*g!=iz*O5f|V> zT!f2p2`t;mf!h*Wg-w1z*K=xE^1_4fr~~fgABn+=QEP3%-SK<2$$& zx8Zht7kA)$_&)B$5AZ|Wg&*N={1`vMPjL@^hI?@zevbR`0Dgf7@em%yBX|_Q#AA3I zzrquE5>Mf2JcDQP9Da@G@d94NZ}1X+i3veMW!o|1*m*O&9jxXT~T#2jjWn7JGa4o)qui`pfkFVhdd>!Awjrb;R!p*n^ z-@>=?9o&lBa67(>JMcYxA9vyh_#y7Xk8n4BjGy4AxCcMOy|@oQ$NhK!zrcfd2oK{C zJc?i9F+7f6;R!s6r|>kM!LxV{zsB=;0Wab=cnQD7%XkI9!>f1=zsKu%1AoAqcng2T zpYUhAjd$=D{1xxwJ-m;<;qUkdKEQ_&yZK`exYu?QB$VptqYU`Z^6rLhc_ z#V9O?<*@=*#7bBht6){EhSf0|W6;46*1(!r3u|K?tc&%qJ~qIH*a#bA7@J^IY=+IT z1-8Ui*c#hlTWp8zu>(Ge9kCO3#xB?uyJ2_ifjzMo#$s>mgMG0d_QwG@5C`F49D+k} z7!Jn~I1)$UXdHuMaU71v2{;ia;bfeGQ}HpJhSTwJoPkf^Onefb!ddt=fn1fIlGcpA^(Sv-ed<9WP*7x5dsgx}(2yn^51RlJ7Z<8{1& zKj2Ngg+Jm?_%q(dJNOI!ig)oI-pAkYcl-k%;6sd;ir0UPj|ng#Cc?y+1e0PiOpYlq zC8omEmUgW1_w#lCxB2QCfsq{u_91v2*b^am90ZSp z;BgQ<4}v)m%zFb9G;5X^yK4g_-`m;=Ea2ik!Q&u!9t3kBm;=Ea2Fb9G;5X^yK4g_-`m;=Ea2Fb9G;5X^yK4g_-`m;=Ea2Fb9G;5X^yK4g_-`m;=Ea2Fb9G;5X^yK z4g_-`m;=Ea2Fb9G;5X^yK4g_-`m;=Ea2 zFb9G;5X^yK4g_-`m;=Ea2Fb9G;5X^yK4g_-`m;=Ea23veMW!o|1*m*O&9jxXT~T#2jjWn7JG za4o)qui`pfkFVhdd>!Awjrb;R!p*n^-@>=?9o&lBa67(>JMcYxA9vyh_#y7Xk8n4B zjGy4AxCcMOy|@oQ$NhK!zrcfd2oK{CJc?i9F+7f6;R!s6r|>kM!LxV{zsB=;0Wab= zcnQD7%XkI9!>f1=zsKu%1AoAqcng2TpYUhAjd$=D{1xxwJ-m;<;qUkdKEQ_SbyT1i(0EQZCg1eU~7SQ^Vth3Kh>fr@hOr4Y#b($XTVP9Ug{`p-w#9bX9y{Qp*bzHnXY7Jqu^V>B z9@rCmVJ!B>KG+xgVSgNe191=z#vwQqhv9G>fg^Dgj>a)K7RTXuoPZN?5>Cb`I29km zX*eAp#~Jtp&crA2DV&8*<1_dy&c-?T9L~k(@dccR^YKMofD3UEF2*Ie6qn(0d-Yw4#5ZvhZpJP67QT(|;8xs*+woo8f$!n_xD!9X z4{;ZMguC%$`~*M6J@^^!#eMiW?#Bc81s=phco>i1QT!5*;c@&5PvA*Bg{Schp2c(c zHJ--{coDzBOZY8b#w++8Ud3zpJzmEf_ygX=TlgdXgg@hLyo0~suXq>l;eGrKf5$)Y z0Y1cd@p=8n_?Q3_Vj@h8NiZoU!{nF(Q(`JijcG6~ro;4@0W)GI%#2wuD`vy&m;-ZS zF3gR2FfZoA{8#`BVj(PyMX)Fq!{S&1OJXT3jb*SbMqxQDj}@>YR>I0y1*>8;td7wb zgARtU2G+z{SR3nLU95-ou>m&3M%Wm`*aVwmGi;76uqC#_*4PHyVmoY)9q>`?h@G%A zcEPUL4ZC9x?1{ZF7JFkK?2G-dKMufwI0y&h5FCoba5#>@kvIxR;}{%^<8VAqz==2s zC*u^HijUzmoQ{v<415A-;*AD0?xzv_#!UAg}4Y8;}Tqo z%Wyfqge!0*uELjbHLk(6_zJ#?>u^23h8ys8d;>S)o45%#;}(1i-^O=vD{jN>_%80i z_waq(i67vHxC=kR-S{znf}i3Z{0#TvKKvZ_;{p5v58@#_j7RV&eu>BMIDUmE@FbqX z(|88Y;yL^p&*KHWh~MBP{1z|c75omb;x+spuj38;0dL|h{1Jb`pYb-{!C&xKyo>kn zKK_Qk;~)3{A7Z=&y#8Z+On?b75hlhYm=u#?a!i3KF%_o9G?*6CVS3Df88H)P#w?f> zvtf43fjKc3=Egjj7xQ6$EPw^E5EjNFSQLw4aV&u)u@siZGFTR)upE}h3Rn> zRk0dY$7qZ}2SZo`Yho>|jdidt*2DVP02^W>Y>Z)Sf=#g*Hpdp&5?f(wY=dpF9k#~~ z_$YS7PS_c{U{~yh-LVJu#9kPSy|EAW#eUcy2jD;)goAMi4#i$H#F7K7ljwNqh=t;nVmGK8v$)4nBu-@p*g!=iz*O5f|V> zT!f2p2`t;mf!h*Wg-w1z*K=xE^1_4fr~~fgABn+=QEP3%-SK<2$$& zx8Zht7kA)$_&)B$5AZ|Wg&*N={1`vMPjL@^hI?@zevbR`0Dgf7@em%yBX|_Q#AA3I zzrquE5>Mf2JcDQP9Da@G@d94NZ}1X+i3veMW!o|1*m*O&9jxXT~T#2jjWn7JGa4o)qui`pfkFVhdd>!Awjrb;R!p*n^ z-@>=?9o&lBa67(>JMcYxA9vyh_#y7Xk8n4BjGy4AxCcMOy|@oQ$NhK!zrcfd2oK{C zJc?i9F+7f6;R!s6r|>kM!LxV{zsB=;0Wab=cnQD7%XkI9!>f1=zsKu%1AoAqcng2T zpYUhAjd$=D{1xxwJ-m;<;qUkdKEQ_&yZK`exYu?QB$VptqYU`Z^6rLhc_ z#V9O?<*@=*#7bBht6){EhSf0|W6;46*1(!r3u|K?tc&%qJ~qIH*a#bA7@J^IY=+IT z1-8Ui*c#hlTWp8zu>(Ge9kCO3#xB?uyJ2_ifjzMo#$s>mgMG0d_QwG@5C`F49D+k} z7!Jn~I1)$UXdHuMaU71v2{;ia;bfeGQ}HpJhSTwJoPkf^Onefb!ddt=fn1fIlGcpA^(Sv-ed<9WP*7x5dsgx}(2yn^51RlJ7Z<8{1& zKj2Ngg+Jm?_%q(dJNOI!ig)oI-pAkYcl-k%;6sd;nAd-dj|ng#Cc?y+1e0PiOpYlq zC8omEmKKhN=wJwIU`?!rwXqJ?#d=sD8(>3hgpDzb zO|U68!{*omTVgA0jcu?kw!`+=0UyPV*apO!S6mpa z+gZ0_RkUtD#i4hib#5c;?2Ohq>@0O6THh;uzZ^08UPn*lj?wv-N>ngL-^;ST*TI=Z z;_kO)?L+leMPqcHGgsA!*|nj$d)$uV&0|Uy?d0C~Jovy2E@d4FXrZRbBD!f zpEC~_8#C*wx93IcJ{{9w=*#Zye3p1~%=!A>*Cu^qbIij6-tB+*{g#;1XNtPFb7IQb zn0Fi1cJtYSmt!u?Na%iTpZeEgQfFx5=6MFg~3sJn;5_T_YvH9zX+1fB9am#?*RzxH^W zd`{Ze+qil4xdP6jbl&&f-lCw>vygW_lV2_99E|nOvuN!?PJ`VQ-Q$bzE97)uH^=>6 zO9$3=%9r2b=7%*JI=y>ec5|){4V?q~Q@Y3h@t&<0c5|!frq1{_N!(+G4`}K9Qg^hw zZdv>G-13%A#VwWGW4dkZ=4$(9`O3p%?jH8eIKY{{+xxk+A34BzdiOB*cJ}2M>Wn$w z!_5zR4s#xP#XDxo*>TR6YpvYd`f9}#$9~&wM!si6`|V}j7hBVPv8?;zt$X*y`??zL z_o_E(nzMaqH+P>utuWnr>Gj=iUb<_JbEi~C_n1s`=Q{UOdHbJW!2)OS;IZzy<;ZQh z`S{R9ar;^F%f)fuw}0OyPUh3zWABkcOXAM0@Vcc=xqOA)^Y4*nS=>I)deomWmOJI! z;O^VAUDi1E+wSePgKM1>3ro7+xBrTDPR-?yxOqdK*PJ>Pz5N_h;x%Vuovhya>;8ajma9i$9Q|`?XBgNzbuJcpED@R{aWuBZx1c+ zoYBNxPuqE$^YQqe?j9!jVVhIG%@#MeJFwe{TA0N>X2_Dg&a^q+IgePr*GW32tGiw| z-9Bg3p0;it+;^YTb^e67tbN}2)_$i^jScSkoQnR!Nml-CH`iGCg;RQVargMU*$z4t zZzOi}o!G<9*y8crV@?k^>`Xm&)y?^59d>$W^7g-Ki6hP<<@315&#Z97N!;_gd(5kQ zj=S2PTfTmOhWoSFTlIpob({CRJDC2GQ)Z^O=k01faUCR(^-_G0T z=4~>CuC(^X4cTXe($-rxxZ_+P}|At+f(#Ra4qzS#fZMGptsO_>s?(JtCmn#%Kpon{$^*_%QdgU7X z{6;?a?`?0VMRJ}JW4$@-&UHN|3y0EP*W=J;q#lQbL!(A`dpO~4;n46w3*FnVl(0x> zT6AMKe{{B3sMDj~Jb80{@zDFNF1YKKwGWn;&rj=~$EeH2Lo+jb``yZKHyvLXG!(Ka1v-N`+PoJms$Ut5Pa7d#^ViTcj@?N>+Kh`&i01x^$>} zn`7SBE+`#JSE0ANzHV39(C+%)tVn&QeCY6|G4457*4{RJsZ!`%PH#WgG_D+)o~4v~ zI~!iF5-L@&y?gvO=cw;TNieQs(ns2M-;q7TTGmgu7nmqi9##XUnrjt#|kN zT1O|edy;qGy?yoe-11B7z2}i-oj-feZQ0Mk`&o3)UMn;%aaDJ{WA|F2bl=u@bNOd# zhsr;bz|G^X)CqO1+21|?W>@Qke%|Zl=kC-AeR9d$^ZP04hK{%E=^meVYyD8&_j0*; z-nsgr+pW&H`*!$?hN1mM2Do|k{YIf@4|}immbGucR%;Tvb|JtdoLp|Sq%00Ik3tEQ~Udid^0#R*3nf7dVb9AFNq35f6vv+EW zwxRB8y~kdo1#LqGZKl}@1@Z`F14Hm6f)U#2c@PJXv@XyRkuF;2}cp~=G^ch4=^ zt}dZ@r@i-b#rAg%J)gIe`+ZMt?iMQ6aisgTmbE{twsZ^4yX)PTSslBFsug(GyT2=X zgi0mzUb_o4?HMYZ^OSo#lh^eOC79RNJ?31lexcRpyq{_Fr~8FAT=4dGaj1VN>e>%jSpSF@BJ)_kC+fzG1Gf*++e`OP~lg8aF3~%cv5KXjje7Tvtn}S zxAvvnbJq8MaDZpX52-?GlZ@}F;C_tDmMUo7kXTK0Z>XJP+)VE=x1 z@2qDjTc=aDzNTzFNZGocvh_h_>xs(N0hO%-DqD|JwhpLl=Vn=b)3W-eW%W(V>YJ9; zH!Z7gT2|k*tiGwN@1?$J>*|}9)i*7xZ(3I0w5+~qS$)&8`lg%do0io#Evs)@R^PO& zzG+!~)3W-eW%W(V>YJ9;H!Z7gT2|k*tiEYkeN$O`qP}VC>YJ9;H!Z7gT2|k*tiEYk zebch~re*a_%j%n!)i*7xZ(3I0w5+~qS$)&8`le;|P0Q+=men^ct8ZFX-?Xg0X<2>K zvihcF^-X2%oBF1$t8Xe>-?Xg0X<2>KvihcF^-as_o0io#El0j*WX4#Z^s;pxWqZG_ zZ0|djtvf5*>$qk0P0Q+=men^ct8ZFX-&EFpSKqXC^-as_o0io#Evs)@R^PO&zNu_| z)3W-eW%W(V>YJ9;H!Z7gy6f~!%j%n!)i*8M{#oBt*52B>^-Zl?-&EEfs&Cr5`le;| zP0Q+=men^ct8ZFX-?Xg0sjPig-?Vl0P0Q+=men^ct8ZFX-?Xg0X<2>KvihcF^-X2# zo0io#Evs)@R^PO2k3s93%Gys`x4x-$>zm5jPxVb(SKqX(zG+!~)3W-eW%W(V>YJ9; zH!b(3Z(3I0v>bUkwAX6)eAG8>-Lm#yebd&}H!Z7gT2|k*tiEYkebch~re*a_W$T-k z?YV1xQ(1dx>()25Zhcc(d#Ju?>*|}9)i*7xZ(3I0w5+~qS$)&8`le;|P0Q+=%GyKq zOTz%8l)i*7xZ(3I0w5+~q**a$AeBAdr>YKLi{r`yG{IqO+ z(_i;yqpe%tv~~4O%j%n!)i*7xZz^ja)HiKiebch~re*a_%j%n!)i*7xZ(3I0w5+~q zxeKvihcF^-X2#o0io#E!$&Lk73K!H??kk zQ(61w9q;`t)Hm%I^-as_o0io#Evs)@R^PO&zG+!~)3W-eW%W(V>YK{iH}y?hSKqX( zzG+!~)3W-eW%W(V>YJ9;H!Z7gT2|k*tiGviebch~re*a_%j%n!)i*7xZ(3I0w5+~q zS$)&8`le;|P0Q+=%Gw|GO1O(-W%W(V>YJ9;H!Z7gT2|k*tiEYkeN)-` zre*a_%j%n!)i*7xZ(3I0w5+~qS$)&8`le;|P0Q+=men_vwLj{cwywTuS$)&8`le;| zP0Q+=men^ct8ZGi@1wbA+4`o|t#2w@-&D4~scd~y+4`ok^-X2#o66QVm91|o>;Brg z*slZo!)`bB`lgq?zUgJJZ+hA5n_l+%rkB0G>1D5PdfDrnUiSK?m%YB}Wv_2~**mwe zwEcqGwk?%Gwk?%Gwk?%Gwk?% zGwk?%Gwk?%Gwk?%Gm_)_X4vujX4vujX4vujX4vujW@I0@zG*)rzi);ezi);ezi);e zzi);ezi);ezi);ezi);ezi)b3d*b)au;cg5u;cg5u;cg5u;cg5u;cg5u;cg5$j^f7 zn|5FPz8QA>z8N`xu5a2ge%}l`e%}l`e%}l`e%}l`e%}l`e&3Aj8`n4OHvPUCcKp5> zcKp5>xsP1mwB!B08Fu`>8Fu`>>1FMk-#5dK-#5MN_06#3_sy{5_sy{5_sy{5_sy{5 z_sy_-ZPPlZom=F4(l@>A^-V8(ebdWc-_*MGO)q=zOT&)eH^YwKHzWJQ^-Vk8@0*dI z1=lxiUDr1)`+YO)_@z-wZo`-;Aus_07m*fa{xfyx%v&?zLI3Z+hAG*N*Y~rkB0GsblQDkeBWHre(ix zh8@3eh8@3eh8@3eh8@3eh8@3eh8@3edRdP#zi);ezi);ezi);ezi);k*K)nS9pm@S$bIDcrmg#Z)63dVzi)c$Uf z>1FM|-#5dK-!~(3kL#Q6edqe7WxsER9lvje9lvizUdyzg{p+e5!^h8@3eh8@3eh8@3eh8@3eMjnS;-?Ve^`)1_zi0hlS?)S~G~mb-^s-&w zwB!B0>1D5PYF&>*_06#3_sz&TaDCHm$M2hA$M2h7);{=sGwk?%Gwk?%Gwk?%Gwk?% zGqQ(V-?Z=L_sy{5_sy{5_sz)H#`Vp}dnc}M+SmGh)63dhzi);ezi&onCf7IZYyG|% zdA;NMrmg#ZGwk?%)5~7pjLd4TZ`$#G-}JKAH@)ojO&#Mse*C@}cKp5>**C6l+HLxM zGwk?%GxFHr`lcP@_sy{5_sy{5_sz)rAFglO@qXWoyhd<+)7Eu;)3V<;!;arK!;arK zBabDnZ`$#G-wZo`-wZo`-wZo`-wZo`-?X2pcYV`t$M2h7_WEYzdCm1rJI3#uVaM;A zk=F&TZ`v__-wZo`-;B%(u5a2ge%}l`e&3ATCf7IZ7{70NS&u8fZ$@6nxxQ(~`+YN# zwe#iJlD9sY3sVaY1!|ak@M&Jrmg#Z zGja}G-?VkVZ-yPeZ${p~aedQ{@%yGdkJLB4?Db7AdwtW(Uf=Yx*EhZF^-V8(ebdWc z-}JKAH@)ojO)qmen^ct8ZFX-?Xg0X<2>KvihcF^-as_o67oL>YKK%zG+!~)3W-e zW%W(V>YJ9;H!Z7gx|zOdS$)&8`le;|P0Q+=men^ct8ZFX-?Xg0X<2>KvihcF^-as_ zo0io#m9;18o3^gLX<2>KvihcF^-as_o0io#Evs)@R^PO&zG+!~)3W-eW%W(V>YJ9; zH!Z7gT2|k*tiEYkebch~re*a_%j%n!)i*7xZ(3I0RMx(!Z`!*0rn2=-%j%n!)i*7x zZ(3I0w5+~qS=To$N4}@~TiJS%vb|qdw)dUN_8v>wUdt`3Z(3I0w5+~qS$)&8`lhn( zyZWZBt8ZFX-?Xg0X<2>KvihcF^-X2#o0fHb)3W-eW%W(V>YJ9;H{Esmre*a_%j%n! zZU3xqD%<`l+x1Ol>zm5jL;G6mo3^gLX<2>KvihcF^-as_o0io#Evs)TYoFCOZC!oS zvihcF^-as_o0io#Evs)@R^PO&zG+!~Q`!2aW%W(V>YJ9;H!Z7gDr-O0H??kkQ(60| zzG>^~o0io#Evs)@R^PO&zG+!~)3W-eW%W(V>YJ7$FN5~~3A^W`zG>^azG+!~)3W-e zW%W(Vy1r>yebch~re*a_%j%oTc74;b`lhn>P<>PD);E>4hw7WQuD)qmebch~re*a_ z%j%n!)i*7xZ(3I0w5-0VtUXlUv~~4O%j%n!)i*7xZz}6CufA#P>YJ9;H!Z7gT2|k* ztiGviebch~rn2=-W$mG^Z`!*0re*a_%j%n!)i;&359*t?uD)qmebch~re*a_%j%n! z)i*7xZ(3I0w5+~qS$)&8`lhn>R(;df)i*7xZ(3I0w5+~qS$)&8`lhn=P0Q+=mTh14 z7`AMEQ|s0@m9=l)@!so{`lcPDzG+!~)3W-eW%W(V>YJ9;H!Z7gT2|k*tiEYkeN$Qc zroL(G>YJ9;H!Z7gT2|k*tiEYkebch~re*a_%j%n!)i;%`Z(3I0w5+~qS$)&8`le;| zP0Q+=men^ct8ZFX-?Xg0X<2YJ9; zH!Z7gD%Kvihc*KvihcF z^-as_o0io#Evs)@R^PO&zG+!~)3W-eW%W(V>YJ9;HYKK%zNyUTY2*5)t*dWZ zR^PO&zG+!~)3W-e<;eGRe=A$(QMUK%%J#lf+1@`X+iSUH^-as_o0io#Evs)@R^L?C zeOKSKb@ffl>YJ9;H!Z7gT2|k*tiGwt=V{~mrmd@QT2|k*tiEYkebZf!yS^ED48&dE zj68n$?>Frj^-X2lUu8Z|>mI}BY2BKvihcF^-as_o0io#Evs)T+deAWf4^y2ebch~re*a_%j%oT+H>_yTUXz- ztiEYkebch~re*a_W$*f?JwENf)AXL>mias_>)sx!Z`#+YZ(3I0w5+~qS$$Jk`=GvQ z>*|}9)i*7xZ(3I0w5+~qS$)&8`le;|P0Q+=men^ct8Xf6Z`C(#U47HC`le;|P0Q+= zmen^ct8XgvdD^(XY3p8p^!D5`pQm-#`8+Mhc#j|Nc<*PSzG=6kzG+!~)3W-eW%W(V z>YJ9;H!Z7gT2|k*tiEYkeN$QcroL(G>YJ9;H!Z7gT2|k*tiEYkebch~re*a_%j%n! z)i;&-JZ)Uxv~~4O%j%n!)i*7xZ(3I0w5+~qS$)&8`le;|P0Q+=%Gw|GO*|}9)i*7xZ(3I0w5+~q zS$)&8`le;|P0Q+=men^ct8ZFX-?Xg0X<2>KvihcF^-as_o0io#Evs)@R^PO&zG+!~ z)3W-eW%W&E?VI|ht*dV;Ti>*->zkIHAy85PN^-as_o0io#Evs)@ zR^L>%zG+$4H!Z7gT2|k*tiEYkebZg1Z(3I0w5+~qS$$Jkdu!|bdqM6o);IlQyggLk zv~~4O%j%n!)i*7xZ(3I0w5+~qS$$Jk`>eic>*|}9)i*7xZ(3I0w5+~qS$)&8`le;| zP0Q+=%GNh6>-wf;^-as_o0io#m9?Mhn_9QNsqDSK^uCsVFDRtGX~(EYJ9;H!Z7gT2|k*tiEYkebch~rZWFt zP^71&)V~+xX6>Q+rX6qD`lhn>(AN3)fc8vO_W%W(V>YJ9;H!Z7gT2|k*tiEYk zeN)*~=HCknsc+i4`le;|P0Q+=%6iPJZ`!*0re*a_%j%n!)i*7xZz@~gw5-0VY<*K% zd#Ju?>*|}9)i*7xZ(3I0R5q3Q_ku#|o3^gLX<2>KvihcF^-as_o0io#Evs)@R^PO& zzG+!~Q(1efzG>^~o0io#Evs)@R^PO&zG+!~Q`!2aWnJI2?Cq;wv%J2kb?ckTo;tpU zyNBwV_IK)=men^ct8ZFX-?Xg0X<2>KvihcF^-as_o0io#m9=l`o3^gLX<2>KvihcF z^-as_o0io#Evs)@R^PO&zG+!~Q`!2aWnJI2tiEYkebch~re*a_%j%n!)i*7xZ(3I0 zw5+~qS$$Jk`=h>T>*|}9)i>Qt-?Xg0X<2>KvihcF^-as_o0io#Evs)T^X~-?Xg0X<2>KvihcF^-as_ zo0io#-5mFM+H7%s)7I5DEvs)@R^PO&zG+!~)3W-eW%W(V>YJ9;H!Z7gT2|k*tiGwN zJyGAZb@ffl>YJ9;H!Z7gT2|k*tiEYkebch~re*a_%j%n!)i*7xZ(3I0w5+~qS$)&8 z`le;|P0Q+=men^ct8ZFX-?Xg0X<2>Kvihd7_Dy}$*3~zaz0cETi|d=VuD)qmebch~ zre*a_%j%n!Bi}PJ59~9aUbfDoZ13rn?R}@Ry?;`+*M7_Do0io#Evs)@R^PO&zNxJH zuD)sO>YJ9;H!Z7gT2|k*tiEYkeN)-{JZ-kPzG>^~o0io#Evs)@R^N2j<33Ltc?@uU z)7I5DEvs)TYj4#zweEeM);nH%sJ>~(sBcKvihcF^-as_o66c}^-Wt> z-?Xg0X<2>KvihcF^-as_o0io#Evs)@R^L?iK2IB&UHtc(wywTuS$)&8`lhn>Q+-qG z|BJ5k0QFW>)hIsP4o-+A7j@B2FM`@Zh$y5G)c_!WMhw(z|9jAz1c#xwfO;QGzr`pw|_ z&EWdY;QGzr`pw|_&EWdY;6<;&;y=Oln{xB7-;B@oo5A&)!S$QL^_#)bZwA+I2G?%}*KY>bZwA+I z2G?)OBaia%o5A&)!S$QL^_#)Nn$a{bq3eW^nyxaQ$X*{bq3ero8a;v_*F{zrPuu>o?_vpQkN+ zUif)hpDEN&;rYT@=roAJ4RGq`>;xPCLZelxg! zGq`>;xPCLZelxg!Gq`?JZhrKe@wt98xPG&kZ~l4OqQA58{muAXzZqP=8C<^^T)!Dy zzZqP=DKGpyZPDME`S)q#bNyy;{bq3eW^nyxaQ$X*{bq3eW^nyxaQ$X*{ifXf=r`kY z{bq3eW^nyxaQ$X*{bq3eW^nyx@R+CQ{wex-+5#{9JZ*s&exA0#3qMa=;Dw*3E%3t6 z(-wH)=V=SP@bk0c%N$JM+xquZ;xPCLZelxg!Gq`>;xPCLZepBvT`px)U zzZqP=8C<^^T)!DyzZqP=8C<_v%<-GS{r+Zf{bq3eW^nyxaQ$X*{bq3eW^nyxaQ$X* z{bq3eW^nyxaQ&v-oai^>bNyy;{bq3eW^nyxaQ$X*{bq3eW^nyxaQ$X*{bq3eW^nyx zaQ$X*{bq3eW^nyxaQ$X*{bq3eW^nyxaQ$X*{bq3eW^nzc+`Q>G<8%F{Jp5*GzrPt= zzZqP=8C<^^T)!DyzZtw}&SJkQ56>fyzpu;V@164a`;$E0<-zrv!S$QL^_#)b;uHOu<-wdwb z46fe{uHOu<-wdwb46fe{uHTf0-wdwb46fe{uHOu<-;|qA{idIX-;|qA{bqcw-wdwb z46fe{uHOu<-wdwb46fe{uHOu<-wa;#IxPMZT)!zd|N71NT)!DyzZqP=8C<^^T)!Dy zzZqP=DG$FHJbE|&eOkFW)NlHE_)WPv)NjV;`pw|_&EWdY;QGzr`pw|_&EWdY;QGzr z`b~M{Q67FXxPCLZelxg!Gq`?JZqD_a@wt98xPCLZelxg!Gq`?J9)2^pep4QPQ*I9R zoAJ4RGq`>;xPCLZep7B9^qcXyelxg!Gq`>;xPCLZelxg!Gq`>;xPCLZelxg!Gq`?J zZf^CP@wt98xPCLZelxg!Gq`>;xPDU}elxg!GkE0H8V(+Q)6c_i%FSEh`NDfjzZuWy zH-qapgX=eg>oopPl8K3JngX=eg>ooooo;xPCLZelxg!Gq`>;xPDU}elxg!Gq`>;xPCLZelxg!Gq`>; zxPCLZelxg!Gq`>;xPDV^e)OC1xqdUaelxg!Gq`>;xPCLZelxg!GkDBX)JOQc!q3zC zdH79v_)U5EO?miDdH79v_)U5EO?miDdH7Aa`;E24e?|YUQSrL?_i2lH;rBNSpBH|A zv%m|#zgggg-`_0o!tZYuc;WXq3%v0An+0C@{mlX|{QhQv7k+=Uz+-K(9{px;{bq3e zW^nyxaQ$X*{bq3eW^nzc+`06d@wt98xPCLZelxg!Gq`>;xPCLZezTZw{{79$oBd{d zuHOu<-wdwb46fe{uHOu<-wdwb46fe{uHOu<-wdwb46fgln-l$Je6HUNuHOu<-wdwb z46fe{uHOu<-wdwb46fe{uHOu<-wdwb46fe{uHOu<-wdwb46fe{uHOu<-wdwb46fe{ zuHOu<-wdwbl$$sGW_+&Slox(~v+`!Y8K3JngX=eg>o^J4%dF1i; zb$R@~QyzbRlE=F|xPCLZelxg!Gq`>;xPDXa-u0XDxqdUaelxg!Gq`>;xPCLZep6of z{msgo{bqcw-wdwb46fe{uHP*FeDm*b7S+J!-`^~%pUuC&S-I$b^#319fk%ELHa;)> z{$_y}et*+v3OUqo#`F5k;QGzr`pw|_&EWdY;QGzr`c1ic)^En=`pw|_&EWdY;QGzr z`pw|_&EWdY;QGzr`b~M^_cx2~uK)e}w442AJfq(XuHTfKPyME!7k+=U@Vqst-;8JU zo5A&)!S$QL^_#)o?`*U%wfj>oo; zxPCLZelxg!Gq`>;xPCLZep4QKlox(~v*;c4zki>0v)_zo^qax;n{som-;B@oo5A&) z!S$QL^_#)i_-wv_@SE{W@WStJ7I@+JHw)an6`n7gg?=+)^_#)bZwA+I2G?%}*KY>bZ_3S&eltGTZwA+I7W2)& zzghHmw#~o4S@d_{&A-1{RR90`_h~o#&4|@+2G?%}*Kf)TzrR`ZcV@o78K3JngX=eg z>oooobZwA+I2G?%}*KY>bZwA+I7IXY&aKFD9 zT)!DyzZqP=8C<^^T)!DyzZqP=8C<^^T)!DyzZqP=8C<_9Hz)ed_*}mkT)!DyzZqP= z8C<^^T)!DyzZqP=8C<^^T)!DyzZqP=8C<^^T)!DyzZqP=8C<^^T)!DyzZqP=8C<^^ zT)!DyzZqP=DK~HW&G=luDG$FH-0yD&*KY>bZwA+I2G?%}*KYjH{)~tW^nyxaQ$X*{bq3eW^nzcJp5*G zzrPt=zZqP=8C<^^T)$cTIes&^elxg!Gq`?J9{H7r-;{^nl$%5SW_+&S46fe{uHOu< z-wdwb46fe{uHTfKXZ>b;uHOu<-wdwb46fe{uHOu<-wdwb46fe{uHTf0-wdwb4DRo?`*Q@`ow;Wy>xQ@ooobZwA+I2G?%}*KY>bZ_2}O29Ms2?{CV@p?=fP!*9yXp?)(y z*KY>bZwA+I2G?%}*KY>bZwA+I2G?%}*Kf)rkMi)F!S$QL{r+Zf{bq3errezCH{)~t zW^nyxaQ$X*{bq3erab&+aQ&t{{HEL->Nn$a{bq3eW^nyxaQ&v-Jm@#$bNyy;{bq3e zW^nyxaQ$X*{bq3eW^nyxaQ$X*{bq3errg}>H{)~tW^nyxaQ$X*{bq3eW^nzcJp5*G z{bumUt2G=v{HC9W-;|rT!t;gqlzuaw(QgLVZwA+I2G?%}*KY>bZwA+I2G?%}*KY>b zZ_3S^eltGTZwA+I2G?%}*KY>bZwA+I2G?%}*KY>bZwA+I%ENC4*KY>*`sGT)!DyzZqP=8Qkx02G?%} z*KY>bZ_2}O2G?%}*KY>bZwA+I2G?%}*KY>bZwA+I2G?%}*KY>bZ_3S&eltGTZwA+I z2G?%}*KY>bZwA+I2G?%}k9msv2!B`bn|>aCQyzX(9)43Eep4QPQyzX(9)43Eep4QP zQ|^9aErtII>ni+vK?NRu)Afeml!xDxhu@Tk-;{^nl!xDxhu@Tk-;{^nlozn`X1^I@ z{bq3eW^nyxaQ$X*{bq3eW^nzcJTU&~H|6F*zZswFH-qapgX=eg>o; zxPCLZelxg!Gq`>;xPCLZelxg!Gq`>;xPCLZelxg!Gq`?J9vFZ0oAM#cHx&Kuf9K79 zGq`>;xPCLZelxg!Gq`>;xPCLZelxg!Gq`>;xPCLZelxg!Gq`>;xPCLZelxg!Gq`>; zxPCLZelxg!Gq`>;xPDU}7=QGe!S$Q+@SDN)o5A&)!S$QL^_#)bZwA+I2G?%}*KY>b zZwA+I2G?%}*Kf+hZwA+I2G?%}*KY>bZ_10XIG_4WKM%ht=kKG%v4wvxsPksO8PDi9 zgX=eg>ooNov7{HEL->Nn$a{bq3eW^nyxaQ$X*{bq3e zW^nyxaQ$X*{ieK-$HM2~H{)~tW^nyxaQ$X*{iZxH{^&P@>oo?`$ zH-qap<>5Ew=1{*GpX)b+>oo=Z8K3JngX=eg>ooo?_r@khTIT)!DyzZqP=8C<^^T)!DyzbOyD8C<^^oV@aG^4|+8d>(%D zf6tUCUR&Y$vc+7#8PDi9gX=eg>ooMb8C<^^ zT)!DyzZqP=8C<^^T)!DyzZqP=8C<^^T)!y~zZqP=8C<^^T)!DyzZqP=8C<^^T)!Dy zzZqP=8C<^^T)!y~j6eF#;QGzr`psgF-wdwb46fe{uHOu<-wdwb46fe{uHTf0-wdwb z46fe{uHOu<-wdwb46fe{uHOu<-wdwb46fe{uHTdg#vlD=aQ$X*{bq3eW^nyxaQ$X* z{bq3eX7IwFaCik?`1gVeJp87ghu@Tk-;{^nl!xDxhu@Tk-;{^nl!xDxi!)ByIu=u6 zR`LHWQDQduG58g@2K;@i0{H{nr4U|XUvPhL6wi+ar-FH&mFRo|t_Slx-HCr3d>YL2 z%piUy_zsxoX+``UU){0d5gT{4j7hnBzVmei`^NxJ4ZCGr+lE zj{AxD-@re?E#ip(99#>21LpXhxp(#i4+IYa)%han{Hh*k{9Y;kSjNYJ>U>GuFQ9tA zGgfDB;*J5;JAtt}tBCs={0UV5560^4L;L~YQK0jj$ap?+Z-a}#&%jln^Zw1b`~z-< z&K_W%_a)*dfqA~K(OC;-{a(cN1)m4A?mXfbfM0-Fez>mO{;8);ka05ENcXnc* zr9j_r`!OC#obR7k8RvOdpzHhR2gZ56$B6g+Gl+4X=S|{$|14pg=eeDD-#_;-&hrc- z-uKT0#(5s!GfmLR`o35C6Q6Z`pQzIi9rb*VsM7}>^{(c5b?Pxzrx|14AL{i)N4ec7-rl9W+b=B*I&H&K&hq^ZquU>P;zCYA`l6dt7G0yKL^%|p--$&~7OYuJM zd+4?l@AJNQh7zBj??{~QoktnxxbejM-kHug#|0&T&(Sp8?JR zbKE-OeSd6Zoa5dl-uFg+ejDxq-xogrev12ovCnUx;(R|Wvv==Ou_Q396@# z?}f!l&-hh*zIDS@e_&n{?2-9i1*&kdcBGFeK45u3t-lt zkDl*^rHsD>^L%`4I!ly7cVBRS@F4JP0?q*|fLDQ4!0O;O#BB@i2_6BS%X8;}mB3oy zjy$&$xCgi&crbVo&tD8y1S^9zKprpQa34N!zi073xX&vYyT6@zeiv{raBuJuo-Yet z4qgGe4|R-pKL?`YKGajk{ZvD52c9=iyE8UVhcdp9=gre)jLp;4j6J84#FYj;uOk_I zUS){86!hG#V@!LMunw#*>)-(NtS9Tiy0RXsqUXA{BW^d)bsoalb)8RKdC+y%WV}7k zo5x)lo5#Z$U%>O`u^eObcr9bsrLKCe^Dy*Wm%8e?&TG&!U*^d^JRY4BK=Wmu?7w>G z+z7fJ*Ks)UM}xDoUO;B z&{O|(#`(Oh%i8Fv--NN}Zr>b^j(Itb@mZkfZ~vGd^Kt{a%|QFz`@()d5xtW@``vrP zey@*C12ErT-q$Cia|)R6G4JVy=-dQ)Z+Neo2lst8dS$@#LGKaoS@Yrko1@vx`V=-aRAx)0ayJnhi8Z(YBA>$)#Sw*qM2x_`JSbpgYIC^_2vD$&erI*0bOU_zv~@|?kLdp zS_iK44)ofC9YELX`Mb^$=#2%(f#$(FFduiKcNggXtpoG$JUTCcH}HI8usL`OcpKOe zyc>Ly_8g!nCjC23oijMWuiE(Ez_s?_aSU)c@o(wi5t_kS+Z)fa2?qS>o>_dEi z(0vSJ?0zONo&x5+wI8~ndq0@_);^eq&dZ?nX&+es*6V}lJp@{(_JQ?p-M)(MYoK*% zU0OE}pl3Z=f7Zt<#Jj(IUhcC8I!}V`GoP3HorUfk(DSjrx}fs}*c0?TtSirBHac@b z^XL6x-X2G%5BM}_{=7fT+f4M{2Hyca7w-|zryqL#LC?c`#B*7Q&LS}Pm;LO%d!g4G z>SnuyM{s7GP zhW#`Iy`f;fH|(Pi(fJ6p9<6)p-TEAZ-dNDOwC=5U>-1A}KLf3kT%Xp>ICP%}t&?1r z*3IYWegV4woL}=W9^Dr~^N{mvK31Xo6=+>pchzgd9~jrp<{mEX1oAg1e#y_ zZY_G|`5(srf&QF9?vsh=S?BW@F9a8ZOTgSeYtXmuH!=1bKz{$q@2h#vo{Q(~xxB|b zOF_@s^YNTLpKZ4;Jh&a`KJ3>y=(wME8NUy@Zu@m(T7UljV(u%?{R8wq1an_`-aD|) z9l_lHvx&E^K4!ce%>Dld>$R@@xxro0&;2?R9qZ{M#<@>7plkj3a~ruo=MwL^En~a_ z%>DT%x}KXqU$h(R&3$See1hJmVD3}Tzcf0#gXS@xllfeS-g?kH=JPV22cvrkXddlL z^SKhe&%kd%^JzbtSAQPHp94A+ee-G_%&U3$4&CoT^J+fKtNAz#-NV6Lx8~yq^nL_$ z-I|9Z&^Z#!=f9SC*SUdlK6ihv%=I0QUatQ&#Cu*F8UGFD`uFE%J+G6{Jq`4nRuk_z z{ml4J@E_1~^5>O2r=!t389W`#b?Z6(h2ADG*RAKnj}ezR6+P?eYvNq*PmEpne~hgs ze~!)d`txkA`%Lt|BF=o6Z}V(EO3=so=eNwac{U$sp>KV8e%6`ix0Uw;Xq|a})|=;d zHoE75)|q*>-pupA?5`yHJA>ApdAI(|`x)q$0WSfq?{Cok4Rl`T+lF~|0j+m`j?$lh zbYAB>mwC#9=F7g^7QOAj?LqToUz(ru(YXM$ZvJ51*3H)F>;UcxS~vduqjhr*dKZG_ zK9azCVBJc{-2hsQ#ts=X=1q+ylKm!F&%`hZWJO1lphG+dehVyQ8-+Xn&e-`_w#N zhVE6M_kjJXzJ06yKIk6|dLP)g&SU>N&y~zm1GInaQ~PIcbPoUz2kjgC)4sU^y{h1~ zpzF?kf z-R0<+ms*VNd;8tK+z-72!6QNY-hQ_)E2CEpybiR!Jx}Y~b36)N>)Ug*zCFL%=-KD4 z*FJZ>`=fU>XrH@Y``q)kqcef3$F>$DENPprq|(K`XO4!uXL$9m}92-=V8xlVOlr+uiN z>r}^e=KI5Y>2%h02AJ;;@1rKHt10MxZQXfaTQ}CrY3O=STYuiu){k}57=7(1{*&*wPS=Q-u~qUUo1>++oPJ#7BYL@(dJ=Iv&5e&;!T_%3|DFStK= z!ZwA!NSp|k1+N0DfGg4S>wfDQ=Xpx;d};6yFwZlC_?h54V4mkU*0}-v7tHevCw>Gt z4$Sj>O#E{2b1=`-h4@Fn$H6?$6yo0iXM%a2*2H%JJAuygFk|N%iOx9iCD8d^XY9Op z5Z@kjo`)E#KZ3Zip!%;dR<{*#?UL?;jMW`Z+?b^MDr0qTC$4SMeSopL!-yN5bYEet zZkwdnjq&}Ux}%cbG{!H3>UJeA>-6UH=fEN0RN}MVd_MmG{1D9XlhJt{d;`pJ-w^*3 z_%pag9P!J*kHH+b1O2!&xC^*N9Pz(_etj&*9Yy?c;K|??ajbh!@Ide&FvnklP9?A! zSRGVnucULVdZ6)_r}(;zZvfTVo48{@^-f@{&K1Pf0oA*au{!$@cK~=4sQ!tJ)w`1T zs$gxVuzGb z|ABjiHf~=WTS<^ZlVtPvX@X$k_LXdUMfH?>)x8Kh#yP7dit#-yiC#HxHe~pzjZLpCn$r zL5zKWs5^&v^_DQs?MTt0KJR;IXo~lF-#Z@>pPwI3obR3KjC0&7;(hP@ z!Z^n*Cf@hXa>hAsYwFwg&aRAe+&bcYk8EU|;|?a?_s2<$bKEwp+xJF({#@dGU-2z$##MusQlI zzz*PjU>ERy@Lb}~11o{Gz*~6kR`71{QSc(3yBMqpRt9T;ckp~`up`(Rd;mO)=iT>} zjNNZDo^zj_7`wkqc)l!nId}!whUeRY_kj0;?n52p-A^@i+=qJVxSww5UC8t1=`zOV z>1xKUc-}nS#n?PO#MpBxL)@jH=XD)p&#NVIcY>bVV~njU>%jW54yvMOJy{RdmG#gS zJ=b+UapghRS(CBrx{bK@pzC~)@dZ3@9?LN{kJmE3o#)MCJI3bm5yq}dUG-e&HR!o6 zb=7m752I(k%#(ds51ku9^JSjwznYg zqkbK98-woG^Kc&Zd!XA7%;#)9)<#eLCXDlWTbJF@Q@=lB&)vQ;Kj!5Iben;mzx`u= z%*&JL4g~FY?+g3AK6(v6``vrPe(!}&Z!q6q-q#J$xe3honD=xabe;yiH@sKPgZpld zUQ6&c(0jyt)_l1CLFf+whl2Uub6)dtGy2Y(??2}=FVCRseEFWq_n38KA2nsZ7GS=A z^8IEV*ZegBVLC+=kujlhDy3c{G-+Fi5e%+`Ix@|$%YrVV9Vd#wl zM}zjQ_kn%e3cWkP_Mm<1ePG`XM{fi;7PMd0bsw(ZdD@|GzpCp#T)*>-LEpZ0{r0Wv z&R<`&Z(YBA>$*pxI}XhEu=V44-pRV}2J?Mvy?CC_qx&LgA9yd=4|k#05w!lj7wm%< z&>0Wf$L7KQH4mN8vv1wM{p$W-LNDJl*7H5++zaM=#yXyW&P34t+3(&n?)N_QI)m=h ze)oQH-;>at40=BHiT&aEbVaut==s4I2_X*H^TW98d7CN)R+z;m4d_09- zZ!q_R`8FSKp*tVUb!WbxMz1fJ>&`sCgU$lb{hD9%>i(ZWZ!qY-&8zt|5AUM86m)%g z|E_Z+x}!kXnfLE{SD?ERbiLMr>l}gJSa2NZdOd&Fxg5REz|TSRU>%r`=h1rsbpO_Y zdH4dIFTp2yz8^RUd=?xEjt5@^=McXTTmrriegv)pzXE#^KLE`0jp6f2;8buP@r%Jc z-=}=O7F-YZB;NT3G9C>&-$cf9iF2O!7=Hpf&l<+Lf1X3f`gw`*WH9&72k2NoUo-v& z>_c3C(De^v>^>$io&vs2{36hOEMx3`Rx@4)=DxKbrlI>XnETc~_!gb-K!u>P&r zSJ8V7v`*~<>)*Ql0o@-#>(simZeBspdbIwmkMD_hfBC%J=PY#QfbKJ&m;3!4-Ho8< zV|{r(v(cLidLGu5=kW(Re}d-E`@_7=MCWbr9nk!Ff0(xo==}r!3wkczBc9Jf^cI1h zhxdr*;y*036<`0#{bfJ9?|JCW2N!_3&+KROu!;5l4gLq_K6n#7^Zg#ufrDZ-I-!cfouQT7SPV-(R3#cix71t!MM*e9mLumNJj^ZN8jGee>nl zDf9jGIy%<-`;0#T^ZoP_>$cvvXWcu1`QEUfK1A;$Fy9;Y(T?cs1X_>Qz4dN=eu~~_ zpmk~8TkqEC?&$6TS|_&$xa>(=|Bm+N?W()ot*IxyF--#1b3AawnHi09{h^$9wj<9f#5 zgPx!FRcZ7*M}Lmza6Y##ytk~2Z_)b>v>v>-tcOF@c!`o zrPhVtmpu~w+;7X!u`YgM{4<#Q&7XU)F8sNNqtVZOXPy0m-mhTpJL~HhbdCi*r<_;M z>o4^F1wE&nU(f3lbk6|ItNpeX9rODS}+`x-0>=Kk^fKi0k9Z}R(Te*envt9j0zi|6dQY|A{`fu6JH<2idi=dr)@LHA+5 zZcO{x2HovJ*KNQ0eM{G$zrUFK%5&cVy&b{aSDyEUtn(r;_x~TP*SgvTyiJ)a&Sju^%;#i24@U10&^+ezGM_cjtqGb(`_g=tLT?Z7P|$qZkLI-kdKJN| zLGx-J%&U1g4Bf*)^J+fKtNFMF-D|;Ix8~yr^o|5`-I|A5=v)Wp^FNSy*LghSeD2kV zcYXC3=lb8Dc+cx3#;1X~{;Qzlc{O0%81$U>Bi?g5n(@it>7eISnRw6Xdd3aGCSa~x z&*>EOP6cz_dOkOy(+ISl4j|6;9>v&opUK#Is!F`;t?HnHqYkcES}3h zzh%D7v-xO-zV+q#S!bT#+321JT4$c0_2&6CNB36HIy2AKn|VG1-7?@Mpmk^7tv~bL z6#bT98_@bb2%Y0V=XJhw`MfM>y;n!)2GDt(?-o993z{$c(mb7y?ggNEvMxrBtbZ}Ol2S(QOYNL);0V`*7VC z@p%QX4skbv?!$H8!RH;oV~O)zoyYT3|5Bc-OPuHFJf5Tack*1m2dvAA=v4yqJzyPn zMCWeM{xskAsd>H(y{ka`(|p^f=J_sk?*qLD>{s>eTlKF*zXs@iVBb2A{p&oPnCAh| z{;^N(pDWO<3SJA^H}jpjs zTHl_h_3b&xtd<8MNz5ony}V*JzS^ab6Y`E#EQ&}j&| zFZ1U9dZW_^^jut@y6U)2&%^bptB&im?yYy%SD$scPV3z|cYVEBm+Q0+y-%#idg$E< zT8G{v)?-ifo&xPh^<1YquG2nL&vmNfI`jSEz0`ztH3jqi;eFJfbqxT$udO@pYwN~( zX^gJ-wDspbZT(m`{m}OwRoC^Z=eoT=)pNb-xbFO3^n7k$eV$W(FM2*tvM$dl-^1qb zX7uv?Yu=tg=LGOXuq=2LSOvUs+rsbn)d$;x_kmr&QaoQ8JOs@1RN(nb!J1&6=QsAT z0sI%t^BhO~@!%O?p66rYmxG^!d7fQ}-xb^w%=1hk{ta*@nCDqX9)1QlfO(#g#E%1C z0-fh|#?H3_ozKCqLFfC4vGa}~ek|xbuQ67CIdPwX>i@`C-QmQINxH8xR`+A#K25qm zFjjXMaif#&D~#1$M%*V!_j|_bj!Jsd7{3gvyE5s0%lJD`-KoT7o%wwJ0r(-fp7^Zy zH=pl7UF`_w_;1kp3H%w%aR(896nHeaMI7_)4DJHvxC@ED1S|_~5l8%S;K^W)t4;h3 zU_)?=IN~dT)xhdtj&Fm`-C#HHeo&pulTKarK;!R8@lP;*5>)33;_86v-N;y-dx`4- zs`nIQb*?0?Dp(s-zdmF2IuYL$><&6lFUF@4cRF}BSP{(gHYTnK*c|K#=6Mf5=Rh#e zSCzPGVAkJ6J^u&pg-+HzmH0Ek%fYOFJn_D7E@oT-yb7!g)AY{j-{Jo@Zy`egEvoIM3sI zW)V7B-}lN^#AjXKC+fV7j(WaF)cJ?~tLOVeow>xT^B!a0AL{*yj(Xd&KHneesy7dv z#h~vGb=BL1?$+q}{!n)g@#-yM?E6FAjp(RX621IhQg0zT`F*5Li4^bizK1?a@jmZ+ zXD8zG^Q(ySz4Hs>9JdegzITpcoa44;-@bQtWt`*AA>Q{+ImS8eVB&p`oWwZC)ga#Y zM+3$=?p)%1Z{+81Av#CwmcXM7{#y~ouvet+V==(hknfcJr2!27{L%sUty2TlT~fG>l$5O*thH~1*{EYCd$z6eeS@8G%CU`Mbs z_y9P9=SPC$!O7q&U^AX~-<=q{-+?^mK3`(&{@U<-Tksz6UT_r8j|L}z6G8W(j`8lN z8#?YoJ$2mAH1t~Wym`8dv3Yuk@o=6uPcJYwPp>idoLUlhC+K-S#@O>3LfrG9=k_LJ z>&iN?zN~|;=vhzJgLP#+OhwOi-9}t{&~-k@*mVshZY=0JUuAqd&zr|~jLqXCjEC{O zc^t#oJifu$b*ZbK>wFkJ*QKs{uJd*D%$Iqx4|}5X6llK8ll?arowq>O<2u~mv5km(VGn}0NtnO;5_R0K(`<0emxK8QGXV?3&DKO)?;_{)bGzYpSN{613mQ@ zG4|Z;8}nmcopon+^L&cFee3$|Ti2bxzG&aNe*4ySuR!;6FyF)0kLUS3>wXc;_p$Zjd47TJ zSD=01y8>;COm_y09|`JS7my=UC-B=jbO?$dtvesSMx(ftPWeC!kZ!}FPn?ljQzu}|y|&u2Zl--7Nx zpTBvSj_#YFdC2E)K7K*>SI~TTZl0ILuKHJ|2T8+_PypzF)~cbzNHT?x9*ynolb8@i=H*J~ZP&gJNR27V5@UeDik z`hA~0(A^U>57vSC_yWBzLHBPRn1{X4*&Ce0^9#Wx;QQc5;41Jda3kmC*Kz#%(e~*2 z^&G!0voD{|BYrWM=lhh;*MjT8P0Z)lz4Cm!qkkZH2soE`=X;OwC!q7KVf<&B$FJL! zM$dWnXPo=z19YsPuNi*>=Kk^fDAvyb=pF>VP23{T^)F-WK2|ec2mZr8{5q!l@av=Q z$FI8{%zU|T?T2sC{SM51Yabkn&S9YSX&+es*6R=G{Rmp8_JQ?p-5!DNk)U;IU0OHa zqh~!@f7Zw0#Jj(IUheaEbT)$SGoP3HJptX5K+nhe@_hb4?@!S4u&z9h6VW*tG=JV7 z=4}Hy|A7C3=Fj`Xyd96;>EIcl=i)u$`INApK+nT_#B(_dowLE*U-q;6-o!fp2LA(d zpV`mm;Z*eV*WYp<{ED9W-j?;31atlS{Ri`Y9`pG9j(iVVXTLDdU*Oj0Zv*Cg(E9W1 z)Tc0yUw1y2vGr`;oX>g8+jh)jeVZ@mQQv&|b;^7{{lxmM_wCW$0nGQ)QLNK?zkuC2WVYd_tv|0dKtPELF**fr**R@x_g1v zNv=!lrV_e-ztH{X{F;Y-(A^g_4>`Z)<4W|e0<8<{&brtez5PJz!8)@Z{JM2z^l}~V znsg3gd@z{nw;Vd^RcBlS^!&W9N~7aB9>Vx=(DU=Yx)dGHu_ohdLF-~G;;oBA86O5( z58hkW!`0|q16l{(AG;B6T^zyqNYFa){wR-*by17)bzttdozbx_j$(W?nETD2d$2BQ zqkBD=`_4K$2EAj!+;`Sj9dzn~o>R`N=XDBtXMmnl&adZn6S_@7^J>2xh>rO^o$;CA z*`WEg@2a6^o|`bf8Eg*bKG`2V>-<#4XMyK{Wx(7&RnWEW8!`6#X)PGH1U+ZZ#dG#t z&SReQLC@Lq@ti%MThYA@bRYKXN$9wra~WR%x^DZ`?_0Y5ThP57%zfp#Ux?mCVD2l= zyA?WjfVuxqB;L9z%eWkv`@cRq)>T`^?ZDix$D?CCUCcQ5X+7etpVo|Xf1XUd=XMF> z@?h@IhUj>1Z5X!)bDvrVm!fwWnETZ8zZ0FiK=YW-$$Zv8uO?_7^Ld%i2he>GG>`VB z`K*9mMeu6SeA3YTu z!6u;R)R}nC>2bz=!2V#aThHkx^csP=Zatr;(di3XPgRL?y|o#;?wc7~PhE+3z1DZh%+DN+dP|(fjnn@d4ATJ=hqzFTS4p0^RwPOzd`6e2U=(5 z*?Ke2P0?)$wgIg>^KSi__W|e+0Y`z>cXf1b0G-$QZsGH`p!I$~I!}Vm>wM4h`DoC5 z*_Y<&Hgs-C+oLHw>@Y* z+CN!;B)Vh4I>g-wx)0ZV2cLHUdl2^&=ssNc2tFSN)+Nq!bso=C{X2Q?3F16Y=kXlX zf1c;^Jz!mSMDK1e-vid+cywL_?N9STmU z{Rcqr1N+u_>|f`3iFsZD?H~Kp{<#<3uHYk}ePe&xHxtpD3cdll?%YSN|9*5I23>FN zC)fQly03$|FV%Yxy@$Zum+HKV&TF7`XCAD(d(bg2k21FI%!73|0X_3Fow0pyzuT9c z(d!022HN-byL~wsy=mZ^p!Mx}THl^ycXX|9&(Zq!{AQqMpSxcB-1T-r?{UyRcfIzx z>z#t`Owj&TH|JaZ9_ZWO>gIf_KMQ^P(mdOj=J^S9?ML%#Kbqg!=o#nv82>aneL>?q z7vtYSX94K`%%A)0jZPoXeVI4+Hy@q1LC?kYsjH6b^gLXjy6U)2>)v{IeZ5$h>$Kjj zbJsVIb-7OK(EG%C?1|n}pmpdyVm;19?=8@NRL^y)<2vm_^<1Yqt~1{s-b?*i*8niz zAKpicSl43E``WtmzP4_xmwxDaPg{T9)7Fo5vk-mnQFUFfdam31Q$5$Kj_c0vMbGC+ z*5^6p_oC-Bhjn>Q`5rcZ&!Cs@U-R}ZIyZv#!M5OiU>ESIl7-*z>jjPmCxKJI3Os)) zSQE_ibl~|r!3V)S&vC>b51s+$d2S%S9@rGj^Xx+WuHc?vo~JDF<-kf{o@X6-{u$f= z=6Mb#{%G)cFwe7s_|L(wLFf62vGeVQ&Ys`_pz|HY*m;)|{~72!KQdN-SK{^n)jyK4 zx*rqwY0~|HvAVkuw|mk(g0Z^Gi2Eeze$QClorx=*bPs2&?#iV1E#vP%bxS3^Lm3|i zs=J=Jtn)XY??6562p&Rw);o>QF9a_FbNoT*90eW?=D6y_*9NZ#w}>PD60j_o<605l z25bv%5l8$DU_&s+btnEwun)LJ9PxL9-N5_796t)37r|-Z%b+^_oRBhN7OkT9rb*FsPiZB>TJupjwDXK zlNqab9%J7h>Z-R1-L26(g6Gv$?^HfN2lV}+?nZRfD~X=(4|Pu>UcEAm^Lt6X66oaj zkveCkc%Sz@v{Q=rdEYx16Q7^ohdAFm$1u)uR}$}grw-#BcMkEscgiu&aV?1Vz0;0y zj;leu?~w+KbKC>O`~K+7ILF;Wyzh#>_=@{p|-iUbbarKPvOT72Edd62F-upZ2bs^q+JL{cByzhha z8D9Zr{l@6|Ubv0%yESApMv--82r-hIEs*!{l8bMEtN#_n$v&yNNtfD^%$ zJpT!}8e9Xq4|R-pKhw~0AL^;&e!fL-IM17>7Z{tT*BF1y^XBPG#^&iq#-7s<;+_XR zuQwTcULO+o1?ai`%GkQH4y-ThU@Cgnll5R-Sr6;cb6rD;8w(+LaSUVg_y*%;JZ~O9Wo#aQX6(AuRnK+4j-KmMS3TGH6ME*$JlThH(Rm9rU*^gF z`xBkNK-c3s+}})eW`her*X4TL=LYot0RIEsr{~~2>d!)VA?SWR59d+;cXUf|f8}$w z9%rDZ{vyWtysgXM&{cmc^gVa`#{8IPZ`^`GCzqV%n?Lf~X_pRsh zKJ$D4dM>$tJ)iB--2rs{*1PNW>qaZl{RDKq*1PNU>qn)~Ee+bY-Us&W$LK8wKLhPs z?*sdG7xePi(d<`s-G}RUo=?%YU)6OVuHSifXFmJZ_1m|uJAZxAzIFZft?SNTU(ENg z_2YSd!MeW!^L=c+c%FN)?tMY~z+$B@Iy3*jqx%PFzO6I!egb+Yg1H~exB2)By}!ZS59Zr^oPzFYV6Hp! z{V#g|fw}I?^BL%z3A$hNYhK;|HoP~t1KqcIHJ|3;Ty)O|U0>e6>)Z|9QlRV1`**$N z(X9ZwUhBYh?uyS$a=f`@>9oyxC!<@qi{zZzH*{4>qB zEqbLv=i8sLUuSb3zixLadd^dYaqgcT(6N3FV0;jm`{zP*te>iktAqcr{;i02{W~*u zANw&r81(CoXQSgj{Q9W-sm%Xg1I&GEKOBngVPNiC``~JHt^uu2`@s6QUXMWUNYFa9 z53GOdwideAf!3*YY26%-p7m(`Ss&LD@BZ?6xz7{OISF*1`Mlikjp#N2Js<1K^EnZ{ zlR?kJy7D~gqtg&Hf8HPF?Ra!f2hRY_pZAA(tA}0_uqo)dc#n8KXQ6jC=y`aLcrMM* zX%6Q8vY*}espy>so(bkYv!BgFBlH`CH-otkjz!OWpU1cinCrhTI_CXW#x22o4_ar( zpmz#*4tOq@??LOY4!SpiEx=nq>)E_HpYxcv^O?u`Heb%8zWKTh{d_+ig^u-p0pkn7 zd_UDj$9lh=aVs$28}`#h=v@rvd&54u1D)2O^=RE&@7CvK=v4%*OY7cxw@&Xuw*7(ykAb=0E%>B~^UF*ItNoE zb8Ex6J(&Bm4?3RPD8^&K+^5#To#@>K=05fOpGW5f&^+dIGM^8i_aJB<^Ld%iSI~VG zG>`VB`RssRNAMxgeA2bz=!2Y1;G?{qMX(r>h!9`%MThHlf^!kFiZatrO&{+UlPhE*~z1Uc2(ARJ@B7ht5_DeYdzQ~fgVy`Y=*$6~*ZJP( z^G`tYWnY@7q38|+&69m;em+8H8ED<~BF?%Q%y>9B2DEPG5pUfrW&APtDQG>~KUsey zx?@4>(f-N$E71K6>_Oa9p!;y$BlvtAIE%QqK=v_NjS(0li6}{b|1KQ}g^Kx@$r20sB>b z`&Rvz(0>K=KCo|{$NqJmubJn2(EhPc?VpM0P6gip?Hl{kzFC9bdhln^b>}{E{V${Y zI_P?HKe_Jj(ESO_eW~87=)DH!zEtN2bbbV_JM&=OO+d%IOlNG}nFs4`HG1ad7smFz z{cc}QMsFJUCTQQ=@Al<4=zRTur}gbQ&Oq1t_8hHm&+j+%>~q&^pS#{E=*-0QapStR}PV3%!cYX6% zm+Q3Nt#jA6iFLV7>(KkedYp^iTcCC5Jz_oniQZqJ{ivSnRL6DNhw8abbzEn@KfIR~ zv985nzCXN=w%V@nduCgM-q+Th_qBCny(~o6d)oT*p0<9hn-b`IkE-i>)pOn6pX#|@ zbzFCTFM2+6SfA&V-;18lM$W}^%J;DOdl$WY|C+aL*xys=^#VtOlfWt9TRcAx`~+MJ zt^+&p{GH%~V4i0j&p!{o3g&rkAif^h6wLEHNqkRm0GQ_~OME%75}4;{OME-3 z-qnn+0o6T(xU6#;pI-=G1lA-z>ow-{R^T0Aj<1eRZSZ<9$K6kSckpp=i#X!jfNjAX zH=OuU;An7*IO3lK`+zxa2Jv&ix4|vqh<_2B2EGjD_?75<1%3;D2dXn6>C9FSG=6o8 z|AX;HP@Rdy%>vbXi?KRui2EH>?=QycyhPkoa0aOUJjUvMP5gTBH_&-DG44lPe{c{u z9?bJDByJJ71Y8B?dAp+14b1aRC2ks+^&1g)Gk6b}b^8+k3^)PI`aOyFeKV5rIB*g; z8Jvrb@0}HlKL^)>-+=9j^ZoNE<2>(Jp7;GTopGM8F7dv9S~AY_JVCtgpCOF%JeLsf z`=>JFJWm_qegAZ3oaga9b2d6z-}g#$;NG(|y(4*Eos$`>a~|XC zh*Pg2WA$!jd<4&{tKO-6ehyfR=haoO5udjJeSfHX67lMlVeI=u-3G*~*OGC5FR6DH zI{AI1PO}v6^S*~JPVqkPd#5$=`S~k}^Sx7tagOUmyziYJjB{KI;(hP5W1Qm#6YqOx z4C5U40P(&@dNa;(uMqG1V?N^?_bl@-xEH+EXDbL7)aa@P#xnZ z^7(tje+a6l&Kf=+ob-(Mo?n`DjPqXaOT72Edd4px-g{g<={hjru5bwR6^%@iJ z``|Xl_kvl!A9}tQhBBTA=J}T3iFJJii;b4|ouGIQSmVyYH_VyWeej&V3%h*!`{K`A@*r z;2N+L&zA=G1NR5rhdRc)pKsA|AL^;&ehx+NW1crpUotjNKQi8h=grgJjLp-Lj6J6h ziTeWdynbcudF@EtUZCf8EMx1+I`2%CuwG(lB zfUffh#>;r#Jbud9JpRmhXP!5YyE8VAM>BR^>Z<2De?rf7sjHsrJPJMYWuENAKhgOM zG+*Y){yQ0+Q$W|_I^5p|bp8PU16`Nvai7PdcOrNu=srCM=TZN6bW3o5xnIx2dDK4v z-Lt@a&er2^=&HXJ`uV)A%j3{f|7^ydyM1GR%*#gR*%p1z-~KT_=H(=G&jamu?+g2V z6S{wc_Ph6n{eCJsr-AwY^1l8Doqxf6k9kj@j?NjN_lEbXd2rt)+4qj%PN4UQ_pJGF z|7Fm>2)r1~_nz~bk8RL*-hBT#pLsbKUFXa9Ouomg8~bQ$=GzX;_fNjxtRwsD9OgeC z^gMFkdM?{D&kmsHlKa>5xd7b@LDz4+yKcX3R0`eFpzF2XU8i3^s(|jLpndCoVBh+6 zuKe{g`_}uwzAcMhIj|yVzpCp#T)*?|&id_Fb=`;Scb?18w{Km)ee1gO*B9+u*Kgmt z?(*nX0`om={dk^xvF?4rd>>mcp6BK0UIp3*-V64_-stTETL0b)_Q4hCTnXC8=E43o z4}N{szIFfhtNX8tUcP6n=lxjM{$RdmtmDe)Q~}+e{q8;Eeh)&|?if!>*5t~>MG6rGzv_iKL5tNT9}z4Jl$ZC=f%dAJ4L+d$Ws_wPE(qgw%V zoq7MRw>`QYK-X&>xXyCuRRk-6uGjN-o$b);2;L2v2kXFmT#nuqp!>HD%)>qC+zaNf z@020_0`Ow+O7JSM0rAbimf-DRYp@e|AL!Sm{5n^j?=tkOfi=NK#J2$Re0TAAH}FBw zudg|uU$46qUFWO9xFK=Qb1UOJLFehhIQP$m=vY5h8CM5$|FlBK`svE}e$cNoo=v># z_v@hUqcZ<{4X_FE%|Z9khOztU%=iH?_pSYKHM-Y;xo_=*htPQ#v_9)(2t0x zL}w6~`^$cI-;K~~4BiapKC_?ALtpg!fzN=s59*?4zHepR63q4g1UlyZImSc4d=FY@ zbw)gm;9&4s(0Vp+&gVSl?KbAIzRj2OsBgZ8qMz@l+UQvCw=-@9 z=KHBTI@bFz#>2sUZ`e&^pO=Y2Cbt?gY^N=lq(7PUzkTnunZU^YIe8lR)djy0b3)`gLb?tq1GO zdYFjLWH8rpJL1*3pYa1=uHP|9?`6iXfS#ZC)t%^gjt??^1oZs8ubxN8b9|NY8=!UJ zy=7fIgx!5Yu{n4Iy>*7(ykAc>K_s3Xttc&T4-vo2NwL!@I%mZ z_FOz?&*eGh847yNo{#73`Fw!xN1*$#UwfnDex7AK40PT0>wI)v|ND%Wfw`|d_u=S` z0CQh?-XEj09L)XSi+Jm5G~+Q~?*Dn{SXZAg{uIpp+7lh?X(Z#^r*nz7epWEf{n>|j z&utXrv0(1cx6$$3Rx_WtTWGV3A!JE)|uyL zy?K5m(cJ;G&djs*W}X+L`ysdzwC>Ei^=ICW%2kjsG)c#q6?t1WN(7v%h?VJ74I|Mu$ zbltg+T>p3I{sg+-+)u9iFm#Usb6=|W1A0G#xi8f@0-Ym4>&`q_cdOAcFTXIh?#zRA zw;y`u+xjtP66#l^<1YquG2nL z&vmNfI`jSEy|fkYovp!qe|R6A&AQG3y|1l1?`!MEdMSaf_q6rrJ#GD1H)o;mJ*uwj zRnK*Mf2!wt)p6bVz3BODIeiK-Fhl2k&5Ih(hhn`>edzEpX=W}$v0Dl1UJWmqe6C42MdFBv57hDYHdD;@+ z4!j%8^Nc2b4EQ3L=cz&b^j`U761Dk;Df|xy?%T?92^1Wc)!o=_i-La zH^;q9{0wj=xJ4ZCqrlN%j{BJSmEb4f7IDPS0sZ<|j{A-Hjo?4v7IEl&1#aOpQ+z4z zoqfSW!NWjxR->n1%|6HY{Zjmij86j9S%aQ_Og;UQI{OoM0;t|8jMe!XJ-^POPgP%^ zr``d?9|9f+I?t($7oxieTmt%al{{|=;(3?L_st5%e%)&=W53?>Cpx}&c4MD=g1+AlVmy|3-#^nC=XpOv*Z0pa zjPra?5byhE2;)4@Y~p?Ye8@P@(}sB8Kb;xpc}5ZM`)4xaJdf|0=ICU7-z$TN&$_-( z)M5qI*D`Vdu>h(cKz2_L$;(2w|YsBX*K;Iwgs@E5t!JzLC zbsG?`UQ5QlKh*6_ym~_z=l7C&&Ctp3BXtI*c%Sz@)H=ocyziZn#OLQb5$AiS2jd*~ z67jxwW--ojgNgUOGlp@FTS~m|olhC(xL1hxJu;thj{BZ?-#>pd&T;P(?|UOZzdiSW z?+c$_mg0O*`25Z(&iBK6#C-^=WBeLE-S z{~vkt9#_x!Da3n!XT5dAdv9mGe#HAe7|M7enDrN;=X>EJ#%sVlUrBVf1NQ_E1P=xe z1IwUuK3EB?2G#(t0kJa{Eo9lRFY zmgn8~0gT=6c|7Mnt1@bRe)(c6XR z&C}kD&C`*L%ksQ=x`MHJx{k5uv?FnQfu7f~j6JW5h`Sv0-0Cv6uB-#=%Q`p&J?qJO zu&%6!n&`Q%orv25be%^qc3l?}R}plbwHWWr^X74P#^&*8#+UHCdAy9VdAy#n>rz)e z*Lf6ru1j6@TxV_c%$Iqx4^Kwt6wrK`C;P7{XtH)rg*+c)ONyqtvY zd7$TS|Ck^1(g59CLHphN!hSy$z0*MZ-Fw4+Z-h=`FyCL^*QcX%2AJ~1GHhn}<5+)&P=DIWAP0_m<%ynm;2cYu|=zh(wd3FD{pm!VSzRj!o zG!M_BI}~(%dH=4nJ-Qu0*O~Y4ddH$W4s^ZNf$MCCUPth5(Di!$u5%1}Q&o={Gg13XM!A{_PU~l3FfIk5P=>&t%50fVpq&hlkL87|eZZAH0Un z>!9^%A6Wm^>!avB23n`~f%R|QPDl4m&^om)t(!;CvmUKK>*EdL-CsU0_xTh$y+QYx z&&&P3h3pI+$o0X+}v%JZ0q&fB2*^ZqbzJ<;h84gk%c_lJ3#i{2t|G3dE? zk9a-<(HjJM9^NCK%X{c70ds%Z&+fY~di}s>z}#o{vw2v6{zC9wF!#X|=$Y^57!Lt+ z{m(|nynn#>LonZi)>#kqo(2bl&w}|LwEkwH`wqAid>^!)&71Q%k9ix)Jl41Qavt@~ z*GK5*`>8uR*84EV!@+z%%|OR`U&i=jFy9;Y(+KoNg8AOCkCvme0<<2jd+Xi$d;z`j zpmk~8TkqECm*}nnt&?1z*3FCPP5`ZwT$k3(SLm(=-G9!nd3XukNuYVi`86M3qq`Qg zF04E2Vj_BzLF>UfvmVx<^9`8mcudlHnei)NuHR3S-gk_@2R%RUtLM@29A9Pp2I%>D zUwwg&=lBETpF!)wd&|0b4ZYVv>%n`=diW8YpFr!t`(rHe*2Q$jZ-Ul=_s3`GSQo!A z{uRvqHVPftN%yIPK|M31kIltHEz1C~3Yh7zyZ}XYC@7wcQkKQKeImP;VUgyxw&hLq= ztMm3DI@b4V*5AM_(E2)ezo2WK{riF(#PRP8;+%Ydo_)T7^=7ygeh1_H{7hW?{yb}c zpXTqc{Jomz?74W(p364kY=@q+=i@nhK8frvFLWQy>pFDY&$q0^V8Y8}$i{T*6Y>tS83M-sYM z!`Qdh;{X zWpt}S&*=y{p3|SK|AE=i_niEDCC}*^^sa|Dz}UB*(>Zj{!`Qc;PZe~kLi_0`e&&0U zwRz_tU;D|wV>4g>p3S^(ME@srtcUfs&ekI*`tk3#thaTx9yQRnzdS$t%=62IZX&eL zJU{!*^Q(z&ZD^laXZy`MXGiw}crmo^th@bZ-K(Qt2iAx7_c7{r8XDJld5BX0+VB1y zrGNiuT;ttDoCeT(IhWQcFS-{(>*QQopStMW4DFl0$=klkjZQvz3AAtg`$zkx7JBty zLufxbKT$tFx&@*A==?dSL1k| z>K8`ezd!XnjpI40-x&RP57?JQ(YqAJd%!+yg3j&G`Ly27sdX-b-sRBwwBF9Cb-oSV zJE8Z0^QyjctA26xOGEDi=hisRuW_0Y=Pu~{IH%6fW$2cKS3~E<`E+jXK(8gd2by=B zBl9nXZW1)#I8Wx?8r`-q&ZT-~(7Ou8xm2eOI(I|+&N|q4#n7=XWm(&I*1^7Oik@|8 z$J)7f-kr-5=v@iRLFe9icP^Ww*9zVX?QhT1{`MS`(Y3!lNBi6JYmc6DZobaB`CftE zHPAUXU+3I>TcCR%biUP%^;W+;`p&nyvEJ%;K;OBv&d#NEz7}2Q(fT@%*0&>i`guP3 zS3##L)X#I#zZ*K;q5HFb?z1vF*F*PZ-P~VSbRK}7i}|Umj(K_>=BKVY=4s#C@8)+M zd6}pEZl9aq{p4ky_M!KQ{a6vbO3*&^94CoYsJiB>o_TwJs%O6Hn0I_H zdOj7%&vT0JMbD=bd3jFp9=3irq8IO9>-G>jXR-6=@B(~(IlKZ^x*+pE&vzYc0Pln? z;6C)y;Bgq^6vDnRECXYlHTZu4H^CU^G6(zyV2PL55gE{6#mb_ z7hsI@5&oaSFJO!_0RJc82xy$~tc|w-op<3fXuMUdjr$n>gQ0PrWv%{O_$`9!f67|j z{`fr}bjPt)cRqga1l^Ua)qNDdK|%K!*6Pl~Z(-2=gtfW@gWed{W1+fl2fdG3r$Ti{ z;}>NUf^C2S9kb3f|@{IbDZuqcditKoM8tO=XI z822dW{tJxpO5%4VjQSh!`vx9@QTIIg=0K+yjQVHr_kEKeze3RWS_#$_(eb^L!unR| z`>i?az3BV?Il(%{Er`AEpR%lDywCCX{qr5`80RnYJq<5FFUFaNzwe)=tYe&A==lCQ z!aBzBJ@YC$QQ!B<7W|{G?-O-iMn^s0BkFvOj(Wa7)OiVib!M^l{h{7h=%}}iweJsg z)tidWOz8VVUG+AgvlaUOPb=g|_lLUc@K^6U*73ch-VAi&`$(P5!C!mdLvw?_ z_P%#^;vemo;OBd1HS6ei7=Pb8r&ve7t@!)i*~2>e<)*)V?_9z<`u&Q(?~x4F(XTZA zzCS9nj(&N_+xJGazX?Cz7usJK{CrPne{=Bj{g4yC3!pms`+lf_e;ufvI=&Zj2R;40 z=W7KW{k+%DQ$O!<_4KcbzxTL$`u~i-_jlC00)OxAsJ9V+-v`@S{|KXg0(!m|^0K}R z#&|W+sReI^cfuC1HGG(Q_J&Wur{O3#7T$zkZFoCu2Oq(%4}1zvfVW_m0-M0*@Gkfm z_5APdro!mYYaWFds%y4eeoLtJ-6ps+gJ91{be7t zM9+S*AM7jpVKjQ?RTsZT&^+6)Hm`p84Tk19j`hvhTgQg1t>ZncAI07}KF-=YKF8X; z)K$+s+oES)>Z)g+`34&b#-8^L{^iU7_>tz2Ur1MQ0j}_m}te1L$;v@gDP@ei@zV(0jvs z)jGKEhtcZ``$6v!?^)~N{$EG`O*j|Ed(XJm<015o8}C2kS(n$)HD0`D;yq^HI7dB+ z*Bi$BC*E)Nk@Gc^__Lws5$D!(d4xE9pyv|j*YkM;-8sykf;N12{?=d(SI=9{j&h32k-hzvu^Qx}*naUm7o+<=bPl{1oQEgT8w%}z?*-@J zJ#>~p=h!+pzt&*{dd{u;cV6B9GW6m-V?Pf=XE==ajD5Tmoe!YpuzI7oqjG&#e1ebiRaf9;~I6rgHv457aUJm2@>_ErOx2q502a z?LL;W{s?}J{}$*zcCmIpM_B&~wAAETQJw2zoC;3?N8^x{0zh}|SfS!;2<@tPx-dE7` zu&+FiztQ;zT0id(>-GgYU&Bq%`gwm?w=?Kv<9jN9U+uYgk9a%EQjcQE$hP?XkXg*_Pc#r1l^+0K8gKl z-yB5u5VTKXU)nd9qU-Mq-G8jFbvTUfPtZEV`dW|T=wA-)3;WK#_z}G$(0;Jb><7QM zE`eU`<2^y=80%kQ>|cLxqFyO<{e6h%=Y5rij^}us_3zO0^S&yKp66JG_0`b6@ZPd7 zenT%E+7I4a_QO@^BtiSY`(rQu_QeU-e?a@d`@`Rt+86#_wjBC#-gcp5Uz}w9Cyevv z-#yqD{@ueh=*PLU&rYHD7mRaff0ak)TIe~&x_Vyc(96y};W@?ndR|q~tq!fL^Y#mR z);AlvIbbemeVsdh|8JfB`+^&ZFG-rqO*`!s)l zl0{QY8_E6+V2 zdKbYsSDtr0^1KDc`Tv`I?W+RlT>|6$Ux$u;)qr(F80Ym2`u0>#NYl&VIAl5 zAM`x8i_t3x<9zyeNuFDMbQ{4qr}jZ%^oqbZr=EXfbZ&#zF`kq4ER9|nXdUBuSrN%YCWv0^=OOkJuvpI^(c#8 zIT-uaI$THuf!0U>E;>%X(X9>(L8+`^)pQ&pf}H=+=hzndfJ}d43P0+Xvcb z*4ciu&ehSa1M5Tk&br%w*1aeCec?c8f0sh10yM7iZsPL>(0*@?PA6zw<2}OXgP`?t zF0E5tbZ>^%$+@&X{m^+7+BetXXW!IfT@N;d_RanH+c&*g_lJ)|`_cJ{`YGr(g7%~H z6ZHq6I~bP7uM%`0=6wsF-wHe6*9E!{^L~uapMclm=eZik^Hjevb{+BaJdNWysy_s~ zcn{c@P0+g?#(Tg%9E#3U(D}69&Z%|24ZS;|^J%@EQ|tUBx=%yz0q0eH=T`k@=-&ms z51d=$IKRdjL7Zox^W&U4KX;(p65a!y8|Txx8IIm)_#8CvI7jB+8r`%$}?K#@tp5H|DoOAPa&ds+4diO!++c)DjKM8&3(mFer*103P&ZG5p9d$H(Rl#6FYD(1rlIpP^jyqOU3JXU^DsYk)iF=|-hMZ~`^n2Z?RWd!{HBtZdD@5G zC-!4!^twR%(0jywdEz=TJTKRL4By{o%dzAbItK@&52WdX>CpLhoz) z&imTFv0r+i>pgA%c~9Fv_RS3Ry+_qGU-it}`%^vhRmZ&Jd(rdhM1G!Ad@p)FlgZ0- ziubVfdkDRF|5~@#(5VEkgAL%Fum$XrH}gN=cRw5ipN6AgA?ypoGBC!u75m1p4UBP4 zvyU?{J9;rr1^g?*>M+LHjm{o;5XLwK@V^9J3S*p)@c#^c0b`tB$@@=u2F5rG@P8LB zgT`6K+IV}>IS7wJh8iXE$IHvTHUvU-p8y{p}PBm-fyhap}H&Zi#i+md+iGCD2d# z;6*U{A4BIP{1ZmMQurssYv8~9$me2M07k!h_}7OG;J^IvuK=%y(XT!Jo!|rTUw-)C z4qL(2F!~Qf=P5V_j)m$p4LTjwgZd8({x7ng4Ar>*YPYKpBj_;W*=tOx9^=xSVzBF`1{^z$U6F!#^3iyW!BN}F8qCebY&g=Zo=R9MzntfKi?PH z-yHmWPiX&W@bmpp1HU>@9sPYj^uoU{R8O7Zd|oT)>F+(?JLu@=ylUe=evs9y~|-wSnF-vML1hw0zm@Co=d90kY1*NHnD zz6(EuAHh`k2!4IwQ*Z)&1G_o!eYhGvhTQ--6pnwI+Wig0eh?f6hr_qAUkI1N51{){M}PM-1|9dIo;vR5WAyrCZ=IfGZJnNFJs*4P z^d4*L^eJo4sV{y*py%~GYtQRV{1!vc?Q_=lm3?4;*$1Q1v!Cn-`^tV;fu4Ew!*4J& z&vC5HYc76^pn0xj{V4X<@p0DH@j2G>u(yuyu(pn$u{JMt)iclW=$V(g>Y3*%^sJY4 zat>cYX9~1l*2($%3Z3=Pe9XiBy@1Y(@D*rY=HouUK<`WV4RoKLgK^ZKgzgOJemxK4 zsJ|B7%`l#`{WuXl^E&$!m(HS~=e??2;Nmv7NEUc6`GJ!aoHM>B~x8^-%5-f#Ai^R<=u+o9(X z=hkz1gE(`b=Mv}F^Z6d#9nk#kck}jpqqosr2+h}iH_u(@?SpC1x%ED9Zs()-7F-0K zTkiwsb~k!^-~s5os_QIqCE++5yVZ4v+7tiw$x<5hZzyZ4Oy{Sdw7(0w}Z-Y@R^7j%z7&&N4&K0Kcl z=za`6ALqpR@O+M=`x|us@%*jBYIHw`)*+t1^*Dv@U(kAZZl0I*_z2xK@C#^NJV(#Z zy8Md%X?O-&cl*rxuSNGuXua(->wXrUzhRsQ>uo*Oqqh;pd9dEr;~csP{CpGp&U$Y` z?;9BV&N^pDCkJ%D*4Mha|8LRT4&AqPwVu`?54w4w`NjR4=U#O8LGz6JH{XKj7J}w$ zADHJJ^bWv-(0o0A^Yr&VMbW(!S_k{UdK^OUN9g|T1M5%>oy*`l>^H;j;P-GRJPdz= z8T5hQA{e1olJPywj&+olrydvmd3CqB*@HgHz)@jgq zKePTP#PNH(!sr?23f6IccA#Vb9A$kB#`*F0DE3cDbW6dn@!JB;e-~@_afJ1+(C;1n z9@BmJ{iyr#d)Ly$i*xHd{Dy8ijC1Q8T!l^&v_G8#``>;&f!-g`K6MW4fBUv9y5*pK zYG2wnzoTbA+JE-P)%d%=cwX-FEIJv`ea7>0zm?Ff3_TzF%k%jgy?>zRVPAP3*P(Mg zw0_B+34Tw(E53QShtGk-2kgY&&7Mh^T~-`F6eo9k9aOM(5VUId^ykV`#gCk zpqm56Idh(^Lsj(S_iu3y{zBh+C!&7=jQ#KLAFO+A;`sZHcn{iVr-*Y7=0-mcjQ61Z z=l9fAh~xLpH?g*#t()=-&zL3;WK#xD34#(0;Jb><7QMZjN5;<4b~0 zDb}T7?B9mysMnhHUC{IMzAB84=U9gI)zI_vzG{q)=h%kzJM?w7Ui?XcCLHoe_qY*mxMLX8_!Z>diqhntrv%Ut#d8?0(ebJuveK5|Q zeO4a5Yhj!_`>O*w9iitG>*{$`L9aUWoML@FuWsn}gx1x0yAmDidjsnmVNGa#ox4`( zS?32?KLj6!aZav4&pxlpx(2KT>%cfaEzq^^yR-K9X}wwZg`TtL;yHURwTV*~dd{AY z=j{3PLAM`tAI@uKbllHPtZ#N7rK4^^IZ7|NM=RX9UC!uwW=VU$aLaz<9j`6&#=QHSzgVxcxw4S%3*96`Tt*7&7 zU7tX2D0~)LSLtY$3#?y; zufo{3o>MpUy2IGFp3ii2UV-*gOZ?2YJ!|uRh_(GR8h`Vh$lAPLV|^!n*28*RXY0`m zyQlH99@g7BTaQ`T*)aFFzHlJ4@2tE1 zXWeI_|0aAJ+TX3w=>&~yyhr$a5VYUNqB9v9*LZL6`9f&DoJ;G}58X$hb#gAP&s=oo zLHp)@{Op_Ftoy^qp?xzIfBR-O>-q2%_)UTC!@S?(^LJrK{5)6Vc%JGH!R|%;JWu0zj_NPQF5Uz7$P1p3cF?*r%7IL@zemJ#O@==?aR z&d+dkN5ki!bK`tEHy@z40)7U~JI;~$k41MpG~YN+=ADY}Dj4Tdy>aM03*%g>vl5+8 zp?zl^?7Ly;SeFT`?K|sW-z`PYx~yjH+&k~iZ)U&_PzaXepAWIJneV;-266>mwDQU-Y53sOXy93_M!KP{rDAn z>!I_go_VTcp3b3q=BbW(#{0v2=~eQY3FH0YeYAzVwnFb~`_B8?zOi3spzA$t|9MZ_ zKlaUL^u0&bHDC42+xt^J^Hs;Z<9pHbnM{73Q+zLaKI_QKbBgz{^?MDyc>h|rZ_()j z?}vln({L1=a$)9wzHcgA2tR}$!CSF!4BNmM=LzhGz;Q6fsepe)SRKYVo$&7rd%_r} z0RET2OJR)D0RM*Yb{OOQN}d0NXJCv|8vkoxMHu7k#s45Y3XOA;webp~b15tdjhD>Y zxO?zF0FCnpYxOU|uP9W%9BXxVE-zP~Ad7?<&?wP~GGBMV$otC?C8Cmcc*jRpaw|@D>>TOQDktuYu98HU914 zeehp?_}7OGVD#&c|3Ekh{>u;lPVfO3{U+i+8NLkv<%j=Ma10y^qyO9JybnKysZgC^ zK_~v4qyN(2|0U~nP@UoEO@iu8VXe*w_^pNNt!J&y2>eFFiBSEitkqkF{|dMU8fOFR z9{4>7ABIC=j5`CrSK;e$35;=DqSFe-c%$(f1EYRb{BDFzVbtx8|3h#XjQXAN_kA;f z^%Kzd+DO(fq2qgJ0qb|+hj2M;grD!9cC2IE!Pxu$nZP>6yB2@nKXq8gI34l#{nM9q zjB_#mzJE%vj&bVa@B61Y>lnxPOfGbyzVDTq_(xseC+cKFM?K#o>fC^idcHr@`G@;g zokaA?;ium9tktW{+V_XL>YYb7H+p5US698Nd|nIs{!ljq9rZ3i&-aJAmGM`v4(s?{ zQZFYu@qMIDjo`1n@1gv`Uwhv>DfmbG;`sUADbG6kHN)TcP6yV}uNMBkcN(&ee!cPc zz4JKh=yw*)6g{=PS&{TulCzR>>B;OBcn`+337_d_rI`a*T| zAI|5q@P8Akr_Kj_-aF{&?>#>|=;-IY-W`AMarN|n1%K~x_4IFnzxQ|48->63cGRne zzwd*(tnYwPzXy807y7Xt4r9F6na|nqUHBpV2&TgCh`SvggulRFVLE&Rzd7)IxEg+s z-46H@JO$svZUI~Zm%~rs9_;tR!|)jV9nQkueJ^9}ez#%gK990?e{W;I5H5uuzd=k+;j z&+7;L4nfcDFV^;zePDmt2P@FCpX>+w%6>SGo_Wp1ZxJ-lm8{KcCw>Q@d7fZB4}0tQ z4r}Z98S7oxTgUyZt>d4p%}ZVN%ySib=B2KB=6Mo5>t&ss!>`a;53QGVa{m57=NvR2 z^KgG(pz|gC2AY@oxX&}_{S9+)zqn7&!8q!#MRzlFzn+J2)IW=UPW0nB+mCC|Q-2HV zc;5EqX>`@kg}&$R+*lv$vJTyC(DQeGtdDicAWkCs&b#-8^S%MSjnH}b-f-T}qnChQ zyuZA!zeZ;hjQ5!LbT)LdL+=gmRqNoszeDc_xD$Ghc+Xl7_kRKU7s326-h0Ni9^ayG z+<5;P&${G6*Ld-siT9X&;~Z@z-gX%8pLoC7N6uGn;^&2)N1R*F<$L1nfSya7U(e@4 zbn`*;x8Kd%?~V4Mn+DC-em76QKPrT7Vd&g?A2_$W(c1$LKpsliI7Nu(+?v00Yu@quMd#N1om=yc-!H~{*#7Z650UpzFy6=Zi|1L4 zyf25&f%k&*@FRMMq5bc@;2d0rPI2fQTL2HmIg?)~DvuSB;L^n9EX=fm?kj_z;J^Knj`56`Czx>rH> zAJ5-9oI>|6XdUADTaWVSUJI><=jM4?k6+O}4bMR9;yHSL)}=K16`SO)q%mEU{Cc((($Eez~CeU(DKllwe&N`n_XKblgXM*6yb{>$_l_Tj${_bdzA5 zTj$_zblO7u(>bvJ?bovCm4o)Fb7245x9!lq7uu)xrG0ZXdiJCJXMfy-zx#{l6E6?M8bRK}#&-=r=RYd0oSRGnF?+@$N8NCN# zPw2ULk9a;c(5neO5APArr58F6!#H2gv-_@!UNv|ljC1BZTZiuG_ka(}o^h;OUElKaBT=^K=V(DKOp}&e3D&41o5deQ&?ppSPjc z1lpJOz5Q;VK8fy7XrILXv~O-lw<)wwVqe-fPoX;uy8l>T>(C6{JE3)m^|c-&(0v-( z7xtZfaR++Mq5WW=*$>0f83|(_Hw-$hS>FX?|2`h{#wsQI80XIZnuN}a&~u7)^}M>F*Ase9vA&+y zbaZDz>*~C`a#dx^YNTLpE>Bxh3><7?TU{3d4%<&(7c`3Y3P{$8?5KS zI9Hx~fAk)Majrb?`RKd_)c5CH(E5 z1+3$IK7haHHjwpT80Yh4bUe4WSucWdPVIvs=sgMJoO=F?(RmMA$9PWG^BMHULF*XL z%X)r-?n-DKolEQa1bRc^v(S1vkJj~F^p?O+p>?$m*3~+UNB22sU9E?8wH~X`{S3yw zwH_1DdmhHVwGOM%`5eacAA`SnzQj77`^Wg3-&d?-|Bu4o^P0wb28{jx5jvjNM%J65 z=QI+3&*=r$FT+=%=d>Ju&*=-+U&Adh_O0hM9lcjz>|4)g6FT2O`)M?O<~xzKdB4Wm zep-RQ`L1DY-rur*8b9k{y{)tLn1$Vk_*oC@ZJn*hHtg&#&(A*d{9Z?Q4z$laKl{z| z`wrb5&_1)y_M3H{iSC>5ZD`+Fcl*z}Z$-&4_92aRjI z@A*6pS}*6)I?Y9Q9<)x*rS;i~&Ms)*OvTT>naz4Wd5 z<2kB-2)lR>*q2MtdmqMoz&<>T&QH+!wBF9Cb$$=M525pEy`59*{3E)*K<@$PRek4H z{blHX0=*BMTjMyt#yLuy-=Xv4oH{=rpt}No2Avz{)4BN>z2optXx?#-%s&;~RnUCn zJehYox+h_rOZ8Tw_bH5Xsm=*>{($zKb+GT2qGMfFv$pT7gMD`dJ?nCcwR7*hJD1DR z`xt%>oqOlqxjcs6Z}2Z@e|w(xx97M9UHjW}w7)&S)95+p=IflB??>o;0iAR6b#hD-^qouV>|9#sFVS@#t*`TFeg8&JKhH=1P3U|B_48cx z&z>*yKZle9xrlcRL?xsF;C}EJ@Zt@JmdZ0y|jhAw!(OS zcpv5B9CAbNYx~ao+P<-0Hlyo3ZU1>s+duYAPV~J;)iq!B%-j1@J@Zw^yyJV(^I1oJ zo>P1;dOjJPi{}*YVe9uTdh!0XZh6?>6!fOTh44f85nPY`2ABqafxp5hupa`)!5HUV z>=(n8FvjVGe`nYe#yFGle+kZnF-`;g8^YURj57%T$Kg{j#wm^eHLxO#aqhzZKG+$? zI0f;)6qba>NoH-lM(ErQTSDWtXKma|@GlCDQ;xOz4e@IN)xVdux&`nn5_HS5R<{9u zw*}pHtku03zrsQHYS!x3$FFhFy@$2Bg@WEytdpR+w+6ktS+|AimccLTRO9n{@D|tx z|ESl4&-=s2VDxW|PJ4JCjDBPBp9o)o|MJ6sARGjv-+cVvh6~}p{P3R)Uxv|d4gTxk z*YIC{_`eT7hN&?6??dM&_!~@z>MRX9U#bW7KN9@^W}N}m`2fGQP`&l6)%h8}vrxTr ztkqeD-wL<}s=tA?dPng;4o^eloM$})zgOYwa0!fYH{-Vjeg_Z376)8 z)bEbpLvR?3y075>8e9sa{!94#zFENfUHBnf4!=Uj_s(9{2jMU97#xhB@1F^*W86j9 z`~F$YI>zgWzwe*EtYe%P@%R1nChHidKK{OcnzN2^2IBAgXC&(w$M;N4bfUiRm51?< zy1q};xd9#Ze2=K}AUf)m!(N^1S*uf<^}YD1_W*14`mipGy}IgEohwI;?$vsM{5P_4=}o?huc!+WQ_#3I5vq-Wh;@v~Px=@0|{; zqu&VpeeXw&-TgMO@s!>B(4J>Ls+S$_ayyzltAXgfRze}TWk zba(+edEuq-N>~~u!SC_g0e^z0;Dy-bgO|hda1VBS;bC|T{thp}z91|POTnw*HtgN^ zQP%D^5j*!;lC}HWhkY760)K{uurCZtz$>8pP)C3F^BX$uLp^oe&sFH{#@;&p$l5yn z!MXtU*6A|V)~Ot8&*=yJ4nfcBFV>#dMfeqip4+vo?JN7h{<05_qh~+a5B8P)PzF8o z+KJx*Xr3onn^%7PibC@&%X$~~)^R^;>-Z<@i?O$kMOa(MYgn6?y6TzdN%YK1UG>Z} z89nP|ot(pe&^ZUKmvwUfu1BW|G#~SDe`nD78|L7CF)#CRpB2%&4&Dger{`cC_0OW8 z6MgsVc^F6iO6b;r@tp0))99+73;lTB_GJb1)UV0fb9Zj6k9El)P9plAzw={#tV?Bd zYeVPV`@(rYk8T3G&b#-9^IjF5YB1hk-q+dC$qwT^<~@A_I@O`~hWDyN`o@j-pYg2AP3Rgg-ZSwYvu~WE+{DWZ zszv;|(DR6M>$zM=oP5x8iSz6E+>CBLX#V!QdHcOlA#@8v^R?g2)9;UNMYl0@ZoLnj zTfgUu-=8_R-UrTY1N0igCeV3R*L|43af*nnCB-Iyk@9 z!S7d{Tleq0y8o8w#e2qnEGx; z`8X%ehv!oU-K(JIht09v#r_2(5?b=6P9<(&$!z ze*bP=JV(#Zy4;0+C)gQUcl*rxS3>_fXua(->)r*O`(d01>uo)%pjQpXd9dErqZ_(C zVC*~VT^+p}VeC8W+!LLLp!>DH*46#rgkD|fzOAeEv<{D;+Yg#w+`oA?Libi^o^k)? zI~d(3p!wPd=GhRvCh&G>zMj8%K91f{_!P7b_JQ?iiryX2{o4oDVHi5YVf=ol4*oa8 z6xa;j3A^Io3-*PN!U1pud>Z<_l;3m3c(*jje$9A(U)LC2 zB5IJeHj-RQQ3ac-T1XVDoC?N8^x{0zf;hi20b79%k#M(y$7J@VPAP3Q_*=D zT0id(>(&{a2VqZW{k%V{+e_%Z3THyk#e2l_>4o0I(DU#f@myx1^E!<4(-Ar_P6yij{4SXF8c9)YLAZn{wVAIFy2oS(Xrp>v7Qg(z2Q7P zhTZ@e?+xeZEp!$@`_aC)-|f#Q(HjcwOZ(n_w@=?gcL}slVt?8h=~k7ek72<;2|&b}Cq-biRa*k|^`2k0z^v5y}QI%8Qs17rWb z6ZBG9e*!%}@2er`c#h*(KLVjiL76Mao*lW$G%v@`U@E6&OV!j-it8K zo&B{IoiCy16zl4FO-FAg^qgXSJ+DpZZiUv>c^iX{^?jA~Yw&eweVx0H(X-B5Sbqz@ zgK~*4c~-uem+9izW;{xHn<)B06k~V#dG#t<`8Et^qf5(&)M_Yf$mP| zKAhKS=(wLZSkHsz?YwS8$Nax%y$i;<^4#a6_ZEzE<$3Q$XAg|?KNWxbY9Z@)V4VLA z=-5|jtoOq>uP>ovKP_M#=kzQ5?Vr7@<9xo1zvuQg>qRim=hx_XZu?jtfN@UkgT?5* z2jiT2{)f={5n9K1PS*1i^j1Ra7|+Xk{*LYmXdRtP>-jEvOW>!_dODBR^&onO;UCbt zS_kWD9af?H8MLm}!@63Jlj!~lW8Yej)#!Z=W8YebQ|SB!uvbLX&hkQ@7Ht#&>e~6#;u-?|$dThh)7j&$L z^|sE|BN2W3%k#6(JiqVI-2v@0&(D7I{4PK@AGFV`v;AhBx1#$4+z0JD>u _uS}T z1Pej?I~ARE(749?p3l>u{hm(!GN5sdcOh{KL+j;STBn`p?t<3IxwJm{(YYAfHyiM? zZ??1E4fjL)<~;e@H+j)30EK8<}C|rx*dgwmPdk>!wFoyS3>Ur=T&{@R{f*s{|>zmoLl2Kzs4y^oU5Vp^Vf>tNrNK+n3AXYJfO@6P2h^nQbXLFe9icP>kzcNM%A+TWh1{p~rPM%VuK z9PMwhxw_ij(OVm_PhC=r*7tHzuV{LSCzcX(?0Y*u^<0I?;Nxby+`cF>(Q$Mok#V| zQyuek4%IVHb<8u~AKpv3IEUOY-XGpaHOZ?M^uD(5yszyW`z0s3-qZG<_q6?E-_$_g zdsJQXRnNS=Kh-l|b<8`y7d@X0&c$lhKt~G zxCU;82jFpd7Us;AnP&l59F~QZVQttLwuT*G57-|LgX7>7I2$g4sc=2q2Gig%cpB!+ zotbZbSR5w9YOo$`0^7hYuooNzN5Tnk8k`Lm!eww3To1RwGFxC*X=Tj6ea7^cIsFem^0(D`98m;@`s+ORQf3ERV- zZ~z<)C&8I;0bBn#4_JV`qSU4HZ zf{WmCxCU;7JD`83?%&n>ceJ_rcS%853RZwMUsro+=Pd;ZLO zB_AvbOTqH6I;;nqz}Bz>>Qmm3}?dma4B2`*TJoDFFXQIzzmr4V)g-x!cwq2 ztO9Gn2Cx}y3p>Fcupb-@N5FA#3Y-lWz@;!1u7R824tM|_gQs9Zfy{m7g@s`$SOM06 z4PZ0a7IuUE;0QPYPKR^h61Wnshuh#@cm$q=372pVusBSDm0>N|2)2YBU=KI|j)W88 z47dO;g{$B?xDD=wN8w4By5F807!0B)#HiFGz zJJ=2OfrH^lI1x^VbKqi_3fI6*a3?$jkHgb2p=f6QiLf9n0n5TlusW;U{BZ&4u&J(1UMbeh6~|R zm;QYh!EiL31ZTnpa2Z?;H^S{O4IYE1 zVa_Wv&!HeJ0h3`BSQj>dZD1$Z8xDbE;bb@)E`-bBD!37DhX>#>coya^m6>m0SPE8v z)nN*32HU}|un!ypN5e^QCY%qK!qspq+zXGwlQ5xl!vFnWURV^Cf)!v5*Z?+zZDCi~ z2M&g#;UqW{E`%%K8n_wmhDYE@cn;<*lbLrhm;@`sTCfpp3ERUSus<9M$HGZ)2Am5Q z!xeB1+zfZZL+}_p1rx5K-mow%3CqK3ur6#2Tfz>o2kZxjz_D-=oB`*Qbug=_GVOR=QfHhzWYzo`KZg2n`0Vlxea4uW|SHg90E8Go_z>_ecY-XPMVKJBl zE5TZ@5o`(D!=7*u90@1FnQ%T_3RB@axE1b&N8l-#yieX4AWr-%$=N>Z$VfBCc|p59&7?z!%nal8~{ha32-``50}AJ za3kCS55aVJ7UsT&zJ;Y=1y~)Xz-F*5>NJ4}N|;0c%k zbC;(-U~yO$R))1;1K13i&0$;E1@?sf;9xi!PJ+|n9JmlJg)8AYxE1b(hhaKA3v*UrU$6vBhSgwQ z*ci5fU0^>r6pn*a;B2@Eu7GRcCb$D0g6S{==B>y)z*4Y0tPWFPGuRe(gZ<$!I37-i zbKqjQ9Ik;e13p>Q0W0%yU6a0Ofsx5ESQ z7(5NLUzeF@epn19!OE~UYy_Lb_OJ)+2ZzFOa0;9Qm%voG4sL^K@EAM=vsb2XVF{QF ztH8Rj32Y5J!Cr6x90te1$#5o|50}7HxDIZGyWt^t9G-^Rug}asA1n&XzzVQBtOuLG zHn0=y3H!rga2%WrXTgPVDO?HH!)1Y&VuvdVz>gXft%nCcmN)Qr(i-=_63W9x*a7y2gW+g63C@HI z;4-)hu7}%T8axJ1!`#)F2Ur4@g;ii(*aWtQU0@$L1dfGM;4HWhE`_V$dbk~?!Q=2O zOuQj8-@>pIEDx*06xa;5g_HOTh}TI;;nq!FI4G8~{ha32-``3zxuDxE5}PyWtUd5@x?C zGtYdm7%T%T!CJ5pYz{lXUT_c`2`9lBa6ViNQ{h^;74C*d;VGE2c4oc>U5T3`fHWa2lKi=ffp16|RMw;ZAr69*3u4&RY`x z@Ba$G5-=H7fwf^1*amik{oqhI4o-oy;Uc&ku7(@o4tNNr!*eh(B{Sc`up}%GtHTu7 z6t;ssV1GCaPJq+k9JmlJhpXWxxC0)BCtyPT%slhKVz3OX1Z%-YusLiGd%%8hC>#eT z!&z_eBL+IYYz2r zALjcupT8OErN43Xk9Iyc5B0Toe=#q0jIUkXhtJJ7+Qqoq#d_UAJ??_$?LO}b^%_n+ zo`LSiyq^p8iu(GgW8TKm-se#-=4pI&)r8Z};JQqAztD0LMV}>cu>buWr<{9_~Nt#r-8x0c^Zy{>c_nGH;(?%&gbT#zV_BZJ@=VaTy@-!d8(_P&!e99 z@jcOu`gDT5;KNWoeMY^wAMMOn zJ>yvi_ho$Te6FrK+Q+`sei(Hf3!j1N*q7=orJkwqlThdXnoqPd5A}_wUChh<8DBef z+>d#xtDVo)RY&_+FYRYhpEuwfsE+kgXB+kT9_|Qr`>**#JM&QAc-qCh+@JBaQ^)<7 zr@GqtTwQgvkM+_%mHK=ES3z~GmpbXx=L|d<>h@priFW3pzVWn+dAUF1Yp0I;F;8{1 z^SQd}XrD+OyuY;5?|YvA1dqUdP`{{qVfIXe%c1`*NrhPd*Zj0I-YVi6FDpIsHV^l! zp3mKvd1|j++^_N6k9O*4?{jryU%DUdPqME+U>dY9V_g5+rpi&z!lADE8%O_WXZ&bq z{_Zd4rH=8nQ_uaFXVmk#y6R{j>*ap5KSg~`!2QsA#r(=spR%w>sF(i6(LdT5KiZkU zb%=SXV|?x6K74Mz+8I}SpBp!k{X38D$2|Vxc{)4{4+OuM=e5)+2^NP%q4^y^$GGZ5 zJM)Ql?pwW>pE}0ZF7C(Y=Br)Yzt7#DcIKzO&&@B^d0nWZdGF=(ui;nF^U27T`9a*5 z|E+S_sP8}6nV&kwRVUi{+;cIG_Ugs_)G@wxaX&scU+v=leeStLyO^JL)=B#%)X#lc zhxhqhyTjDWeY&5YLLK!}SHEcIbK|P3y>Vkc>KH%TMLq4}z4s#fn+nax{1@`M_uiN6 za|1L_^GOSJi2C}eW8TKm-se#-=4pI&)r+B^LC%(L%lwwPFtY+asR9M+-)l(X=_HJ|Dxr zUw|{9`Y~_y)$zGH)KFH?zB)cv$Go*O5A~y+I_}ebsjHqk?#DdU)z0TpPy2WeHK#sZp>fP#-I3H~ z8Z>U)k9zw1Tz~bnGp@SPP95{gDy}-wPTj2b?f3M(LmkZ1eEhzAcBq4P|8+mc(?05} z7xQr+#@9|g?R{>(aXz$vhW(C%gP}UkhdQ6I@0D;7G~R#BM?2%2PmH5o%*%Mj*DmhE z=jN-Oakclk`-}C`eh&3{6HbTfSTA*UP@f;*CTP6>nvZtIH=h_syO@{pjIUkXhtJJd zJL78abN3hPrTr@Evj%hyD{-+#?VJL8*AjH6x5%l#Q&yES1SJ~vYv(?qo^{YpJ?(w2UhI=ip)UF#;B$4e zlb8Ec&+m`5i+bAYubukZ`#kEIk9Aki_)$-LpQ{)5{U!S|zWJ;R`~Evy<_9r9zwb4^ ze(LHM?R;)rb+wOr=H))3p3kG6_Hp0uu|M-L?s$ zAN9;DtGGUodfF$lANN0*^-TB1cd|zL{x*)s)7K3@A_I222BiI7^ zza~~U)Iopa=pXHTZXW7u@BU(5>KI=;b=;46s;iyP)m2CP*e8_3|FZc1HDe>S}LX^_-Ke;;Iwv)Q$VTkhq0m5tt7e|7H?s3~z(=pmzGH zt6#M9xpCFiUcH!)I>y&d9qoOtZk!MM*Z#OX)WQ6mkJyiQhI+)j^*4_G(a!kM&VF@& z>bXz%74_miv@>7zjH|uRjT`Ib`|A?wbR~2iopb9|oBA|_t)O!n=U;#0=pXHTZXW7u z@BY*?zpUb_<9^IjUG;n(^|X)m($D+)D(Y1LS|=Ir{ky4G16UKPtDkHAqn+`io$n7n zk9nzMeC^^sd~Uwl8CQFs8#mS~J9Y8j6~$Tm-t&FxzN=FY|2@%+weQ3Dp4Hzt`bRtC zM?2%VKlRKntGMd9AM=cQK973Z$9h=@&#ff&$`8HgwTtK3l6s}UTG0JvgpH&?7 zVqWSPU%R*upPR3C#?{{E_G7G<>x-yQY3TfWuegt!sK+hvF6g}w?;HJ%qkpvXxp}Cs zz57$o{IZIxj{7lBb=C8E)YIO)+`s4Vxs{?`#*6mxJX=#Q^N9P?-#Ge5JL5+?`@;RH zXMS15jr-8feAP3q_C7bR@vN6~?$0IHP_OdP`PSa~_ve@Us8iAq8?ToLz z&yAy9%u7AvYp0(3G0&*yb9L3x-hTERJtymXHFdfUmV@@Ke(}8SppK8$1B zsH2_FjjN9K+No#W=3$>`7x$r_d1|MQ`*%O;YUgux)zLoI**^B3EK6N0L*u$1>ujHU zuePJEU7>l!{Tj!(QOAAw+_>s!ubq14Z64;OojUF(tNh}=wR2zU8Bcqk8$Z_B_mThJ zNus{jL-pK`b@qMbzmM8d-v^-a;(m>%p3l{DALgUI&yA;@I_8;GT=m?a`9?jTyMOni zy>WdW<7yx4?E9-Ub#)G^u}+5md)+$w{*3e2gF3f|{(Ij1jHjN@)zi*=wD-C3w2S$v zXMFA4m%8dW@6pcvyPs(1bK@IN`#jVw7xd>$e?GjH&nrNG&elHuoY|3jc7pyqZrGv_#%kN`4Q=cx- z?=k&;$Gp{7$LH!qJM&OK+NooHS;dX}(9V2~Ydr0JZhU_pw{HF%?OfTHwWwQ7cmwqN zJpKK7KF&{X>i95x5c<8J`5IqcpR22#`DyQS<7yZ4RL}U@xleV~aUP?c^>AO&F4oC- z?q5B1wD-BXiPYKq((i|?kNc~No%7*5>F4)HasS;p2j?fwoAK21xq9xye6;tu@w8LN zJhO`HzSLF6I;t1d&(ZrWJ~?aa@5 zTQ7fp(az`Us-wN<;2e0bdLQ0MzBQmfFZuJ8e(`>Nh;!=&{dp?>{AE1#e6F7RFdyxG z?tQJDI_8;G+_)d@%vU|*S_k(Pbt{=BAL%uhYzYp0(3GvBD^zN4M{QqQ>B z``oy(&Sj~i{bhgL!so^_PwVW@Yq5VGqrO?~!+7esPxah~`DpKR<7uakd1e*YeW|NX z?0fgEz0cj3agC?F&y64JTrTvh=Tx81>q38Svd;co75ir(b?yiKxyt1Si6{)ddAmIJ@;dtQP1bh3E(d!9STaLB8^Cc zgtSPPN_U5Xv~-7bmvncRfFKIe2#S=1LAQi})cJ7c$A@w4wcbzH;kjnd%r*Po_rCYv zvjC4i9=_rDz~4AL{0-^$9B};7r3bH1f2`zT|FUj<5Blz2A-LsJty|Yw*S;5hr|+Bm z_DQv_`J+R(dEoTv;(>?b7jOG%oVobq8?NW2YaTc}@cMW=XZx~!a;4Bd)Jgooq^UoWm+JEd@_Kkk-j!RxAqz>(C^zhmD@R^q% zczw?&93DQ0boI^EKJn3&gSlE)zxm*MT`rS6H%{Fo)&66@vM!xlPD-99r`nf#AH$DF zACLLq_`u&dJp2vm_8f5h(xnHlPruit^=|+2{BNDywn(+!txL~C?>lEEzh9?%9{8g} zw|U_7>EeNh;}>uHX`H$Ew9moyymZY2hX-CCZ|Cg(x-UB<*Bw)>bI*mG`#xWoTrW!X z9Q1QT51&3hbMOPNZ{5J*;d4m0=YiuFA6+?^t9AA9;(_m+tq1SD?UV1Wsk@}Qe{!}C zyzeeZzL%w5n(F?VgAU#1fzzjp2Of@JyzK|B=fKA&oVoG#T#dtv$Nas|dM>U1UnbYx zQoR@Lv;7=ho;c<)iuF^y%Y)hvOe_`-1Cv@bL-P^Xr=n zPG0mohsNQ>Bd@+c8&vLm?~}jppSpAE=Be(_@s&IO*X8dwre2bIS}MQv@#^D+b6?=~ znV#nQC7@w({oZn*9CZ z)YI#JxDRxj#~k`}@xa6Ji+AXL^c?v3#Mg7{n+uLEyuKVdXYY6K+r5+LzNtS;-7r>6I=EUUL>*I#}{p!^7Qcq1a*G9#wj~9+VczwEX{P8=aYfij)tY3V1 zBHh!0}6$9=tyN-Z!>LKF+t!k%#2(2d6qW**Bc4`uuup^1CI~xr#qJbejiG zpDrGFIDYZApT?PsPv>R0o|mq9;PAlf-+b~;jUEsbMNQ$@af|-2S4!o zo)mO3>uYBXB z)7Qi4)58M~hmTMC{J^z;e0;*;f!D{|>(ajAJNwAwb5iPwsh)G|(thFl`p)Eaf9idy z_5uFr&}|+#eY$wy;rPYde&Bize0;*08*k6mIJ|hw-|N!;ZJ#_Pxt^NpJKQ?%{r17+ z`B19wb$;;U(Z>TvA70CIg-2Y z1K(x$CbtJt?@9Gs^z%j^uRdP$^9Qd_7Y+}?b7jOH4!(*NyfB4LA z9{Vpo{pN%3=fLkphb70;Qh%L#T&nYk^NZ)e??tyK&xce0l={b1=axR_(8sHf*IfL; z>(hn9!|#x8&jrUXK6%imXHK}z3y&P&@WJcj>*v7v&pdpenfu~-@?3ikoIiW6Kj(f7 zJ@@qR*%$DcgCBT(dT@C79MUxxUOaNd2WNisG>%^5=y%S}oz8)0CEv4C4^7=SRnC3> z{7dqEEcLcjzn_?c9zK11aQwjQJEy|o;d4k=|Ln}sKJn3&gSlE)zxm+p6Jtcrck?;< z=W|n?!w<|q!~358YjS)%)%m>7^Z4=T<1rr`ANU)Ghrc1+o&%0wy7b`n>G!&H{y8MM zI1gTuf4(%;`Nz6+KI(Jmv&rwdROci9=+JE*IDNW!;Nkei+kP5nEix?f9lFf}r%x9TJRHAx+Yg+1;GO6A zh3k3gnghz5UzJ?1Otqibf8^ZH)4!7IKU3{Dy+6^zr;pDZ{J`sbKH>22 zIi%b3!10TZt{lwOy83wW!1q4uJ?Wfrb@II;)qBxC>;36G@QM-+1Zt^>F(1 z@W8|2m;GDn34~OXs$ylGCfHucXrJIq2cj$7f!C z;PvUj;o);g*Ian<@QttM#%C@#eDM1CI%n?#zk^(yd~ZzsO{)D{&fWul|9B?(zLxrC zs{NfmI&_-{PM z(!Oth{bTaFKh=I>|FJH6zy5D>`#jZt)B6)WeERsz!4JH?{T~hwpF_Gm4;;Vv=*q!d zt*eh05B%84&-%2l_-?o^{rgfMN_`;J{^EN3kME0@lk;b({(H$^8QXFmA$4cBwiH4mJe@b>(T!;42w<0NO#gY&X;q;uVa!9AM#aOyp&*QDZc zF7I=r|6Ys#?nwXlI$q3u>{mwp{|iP>^&aAn4&CN~)2E9E z9*$qU?FX*sz{e*XKJ%N$djd`$FCO^L*?RC@bXRiyQ|ez*-CsFdFTR^T%6jYnE|m4f zA04{Q1E)_H4?G;dc-s%0dEnbG96fUn{db%6@%G&If9DnNIq#jvlIveno%`YYJvUa? z?bxZl5BSE9M;{LyeRzHELAdsXk3Jke^YGbx`grlc_qz1`_=n{4Wa?9?-s9f;)}`;u z53?S}OJ3urde8T{fF3@5eCFi`UY{Nu9zKV3&4m{a-}riNeCC3~2d|H>*QM_u=LYZH z$CLA&sn(_MuYS+_@5_IX-1)&vr>}?8r-ug~jz7HZ0}hWlhWz1coO4I7OW!m0fBWpy z$?1vIyHl-8-&4K6PmpyvZtBOCA04{?!xJ5Sx_IE_!Y|(T1Bb^vL;moY-#p$s`1G3( zzSpJip=UDRv#H*1-iOwu@1%*6<5yF?_xe4FACEpBzTx=5-#9${4e9nAaQxDx2d_`R z*QI^NbN^cMdNtMlYrnQGdmou5xlNsFfA0O99zK11=HLfj-@XKghtDD1o(GO!e01et zuGZDZiwC}QwqN+3c`o_Bp8B^``-zRWT|Dq`{Nimt zaCpo!Q^zq_>?|s(uX8pgFT;EQ0UbD~k^EPef{!XfM9Y6T- z=;MK-53lceglk{;=)>VN51-Aaj~5So=jHw7{>%A;t6T{kivZ zdieD5nS&pAeR^(lRb>An9_a(XBAi&Xc? zeYGx~3#Le3)1}Ux>OS^;r$e`S%%M*g4?G;dc-s#g9`g+O!)Jc;m=B+R^TBt{|4N=O zq*~7-@;zxku}{f)(&X9C+dTQZ{igRZI&_=I9Qt(ez{By2xBbB3G0%{{o*T|w_~^px z+Xp&l=L+uw`|~Ki+f$t@QvW;Nuex zpZU$>eFdkF7Z3c{S-19E=M4Ln{br2#8aMUxT<1LEdgqbe#}-J=i>A(%>Kwx_T|Sx* zPMf?pu4_;rcaCrC~((Sq6_{Ap=`t;1%IJud>ar*S>!CUvv z9rgjw+gEZw#!U6S1K-d0g2{WKRNqB>ObvjmZ8b5W^RPSZ`y7%_1$!X!##Z%`?_1-oI9lFf}r%x9TJRCm0`QQt#=fKA& zT+gjC=PnoZrnl{!i-I$#ue1_szPI^9;#xmee1lE}81S_PU}&w|UH~ zPZtk796rAJ;0q3qd4~M;+;HZ?M-N_~e&_sN^7Y=ZKTnjL@tH@?GbYb|e=JqG_q=rI zHjg><>EeNh;}>uHfx}}SdEkY^XMXc|U%}}&AAG;he0P2=xlWww-16mlDEGtn=+epa zN2$&;eNMuUM;{O0aD3oz93K9LbbAgse(BPK*Qej>(szx0&%QBfa{Fqk?;h*YcTewY z%O}4drut6ej}G1Dfzzjp2Of@JyzK|B=fKA&96s}#$36dXkem&Ls+H>Ii?K^0t+=rj0E}QE7Z4Nqgn+HyxE*^L|e0=l4 z7hKPQk59OsTi;x8^x*aBch1&}`|_>iI#sIk_?PGR?9;s-ev({QNp)WD^F2L$`uNPj z54^s01BZvtA>Ez_j$eFqd9@HROc0Q z(4pHraQbxdz{By2xBbBN9QgQz>$&yK1xFWN-+s|K``+;#I9YQ2PU@7YzGLL@bL+U$Nc8O3#X444}3rW&Xdl8(e;=-x+}28UAA5b!!>5l=F8sjj``(1Z!{?ALKJ)S0d_6ZlbJ4{EuaCEL_8n(G zvmecnobmA~XWx;%U#*>->Gd4+@af|-2S4!o^x*LDIizbYym;)R_~6WMp2q37E_^@# zzGLiX_Kg{nv+ozrzwevgr`AcnzH9i#OQ)}g)2D|A9*#e}?E|j;~r%w+LJRCkg>GK2E{_*h%hX-CCZ|7wlT9@BV zPP3)j59H-Jv0gV!P8+4#H~7JiM;{LyeRzF3aP12peK>sP;j{Vl@#2B+yzI;7ah=@V zPkHrzi{Jg|`-~rtJ|6SYhu4<}93K9Lbj^Vm55M@}nh&n?vM)MU&YE>4clSwN_Dko_ z4YID>kG_9+>Gbt{>(j#n4~LIW`uxD*F~^WUJdMNKc{%4e_spEUW=ZvZE-&XR=b`nI z*ZQfxsP;j{Vl@#2B+ynMHq$8~adKjr0nrhU2}eV_5t z>Fdp>PY(|~9DjJ*2OJ)A4Ee*?IJtFRzPtSnF=y7*T&cdd<>hk;PeZBeg>EVHg;}389fWu>sA%FN9C%4Ya|2{NR)}MXTKI{8hUjBEZb+hj5r}p3e z?#7QtACLLq_`u&dJp2vm>YJy1(xulp`kq60`;q5|dVSZ! z(Sz5w@4&SmeDvY)!RzDeb@HFgIcesYJaxR(57W0!W*P1Of2dzRb;Z=hQfE!&$Mtkv z-#C3f@WbP4UwG)l^&I;A!qJ1*r{8(~EqU;3zSr{iXDY7=lZQF@ohE;usPb|>9oIKb zpAY=-=4)Sg=)?6K`usMob>TX%-z5)z&3AwPerM(Ne%75i`29S8{~(ng*VA!*&fI{4t}4{-=C_yTu;aKjnn4?KfL+c z7asa>J%>KOjcZ-F&TGHq@vBt6&3R$%-{!rx@@qZU<1+_6c>U&U-+bW3 z*K^?EvvKC{dEh!HIQqRVw@-e%q#lxr#~j|P7bLe!Q*TYh-}5!U>+tYR2VTGV+9!Ry z&1a4we?2c8-p=b6$!F)(gDW@w*UH>KWOxwUWC({X*{^!dOKF9&?)G*|QCF%O@3@#!}oe7`65Pd+E4 zo}GG5Dn9z&6E`NOds81v{c9?{_J@Zqp2q3(MF-xz`1l;s?Rnt%#YayL=AsX$j~5So zuS@vrlk07%zpcEjOFXY6&v#P)T{(}epT_ZlpB`NMG9P_7Jm%pOFPuJJJn)?t{A0=I ziPR@6H+kV1E8kh;rjD0NZ)E*6jt~6w;Mx~G`fzy6!zW%ieY|+!J1_WmlF!Gf?^kZ} z!ZTg+nJsmu%57x*G>#Aa^x)c;`RK#pF%O@3;q>w1f$x0}&UNGFxtcO{s?>>7?R(9; zX!2S)b(Pc~rH-sWIC}ii8{*@e5A)*FHy5Aqa6KuI(me1pCC?dCCrPy~ zTX)^$xK8Ram8<6jQ#3lDv`o` zT`6_B$`21+JdNY0appE}`@=&YuIJI`7p~{mHy<2-@cR69&Y$KzVt&5AmRv_k{VX^+ z&rvz@y>$LQZ|av{C~h3onC&DXg02PY@^my(~kI$jLr_PefA0E1R8mG?}9r)(sb4VAD`S`_)Pv89Jfrry?UHD#?Pba^(Qq3n1zxP|0 z6C}53Q_U?W|9b>KuBYSr#_98cAHMnc8`8yN9zOBn!_zpto!1@7>Csfa&H20hU0&~H zosN;b%wzud)91(abX?y!eLnERHy?jPx_HdPCtiGb8i%*@Ixl%#oyxa4&#%0mNFFbw zn#cT4R$i{BT2LEqK4*pPF3w<_pg6_75cwbM)NJ z2k&}(aCGoAu6@zPiw_U~aCm!ezr&xHx#{qQ_qNP?Un<@9iO===uEz&Q7jNV6@HM21 zr*U}u`8Xu=9-Df4DjxpqC%0zqyHg*jJlZ#YJoF|&D=H&8VD&3w3pX>Eqj}MM6-p1kKYe*MQ{rla_|If-}Wc|R==cjex+L!t0!}T2c z{KC%8Fo-u+Q>dNmb~yztDFe7>7Hbt=7)^#ezrpVonEU-an1^&I;A!qJ7-H(&2V@Dt~G z8ZFiJcwO)J@cAn*IDD>eeK>mX%@5ap@X?3E2d~d>&kg6gNiz4Csd$@bnasUVDxB-^ zx~_5b8pkKT_JfCh*r%c%}M9$`TH1Z{=FLW)Vz3?&EFTO99&Pw^^Mb)oB80) z*S_%3hcid>@e79!ULRktlTqc7>iTcwpYhIDdEi^F@@YQvxE>#Vc>U(%NACFOHy^w{ zzMlJ&?3eWU@%t;@*)w1J^LsCx>+rg+ar$)e!Z#mZ=4d|s=7aCKU(DQm@cmN$PJi;u z%@2*vkS-qc@rxIqzWL1q4@a+Y^gHKylk4)SE2gfJx@78nsdC;b zIq#FYU+RIWyQXfP${!xOcp9hA7ae$f?GF!qxSmI!UpRVlFc*C|eY|+!Z#vPh1({1?U8y|>dz`i*Wq5 z+h@+*Qx8hrH+6?peq2w-^^Mc#13x^z_JxN&93J!Vi5E^EFCO?_C-Wqa(zYt%xSLX z>p9@~#YY!jpKj;1aPs+K>W@=BkDhaR{UZ77mU>94=d_=H*VA!*A1de^czRVeE9eq(#2;U zKATU!`QSS*_uqY6EqN`Gx?rlj`hFgqymm?5Hr0Htr{ns@(Qlk}fvvo6esPafuPzMcb)Uwm}o_33tAo^#(N z>m{%CQ$64E^8EXLIW~D6m+HCa2R|NtJaF{k_36O1FMRaj@R^6t=F`WE2fn|5-6#33 zm7F$7T_@GLb#CqZb!757IrZpN>)IT2=r#|WK3zQUaQOJlbhsXTp!3(F4 z7Y}^rY#rMt*G{gRrqVT!oUL>F>QTw{lvKWYUOIG}#~k`}@xa63Ekm8Kk)kW;PCJ{q>Imd z{5GGy`OO1QSHE@PJ7@1N&!^{L%jCRns`I*>y+8Z;J3Toclj=OrA3c2f_~7_~*QW>9 z{_xREp!%-+B4nWuxS?ajM@d{T?GPzsH=Id`?RBJEq@v_{NV% zA5Y`>z~4AL{0-^$9B};7r3bH1zyCWIzN0-?*5$Uz?dPdmrTRTj?!M>y`8hW^o|Ae; zs^9(irH@x1FC2gH`gGyiCqDXc=ERG~^Vm3fn5%J}6Fu|e!vn96xA$4U56Q=TTjYLt zK0HtMS-&6k{J+k9@ci`iMh~ApK6CH`uTKvS51&K2=E93dj`(_ReCC3~2d|HBoc!;H1e80fyU+uemFs$cJuCO^ z>{Q=V{ryD`pFTcw@B^=JzlOuZ=a6pC1II5ux^ggA>+0jh1K&AY2ky&`$#UpWoq2{26Pahu~Kk)k24P5)fM;{KK`S=~;Gk?#6Prv!#J7?cjzK4F1 zTz5&`KGpZBocsIg{N#FR>IJF3U-_elPahu~Kk)j#*WlV8KKgL@%*Su@nY(f3!bcZg zpKj;uJIMO7K6XyN^!Svs@3me(mn7ezIq2atCq8rV1FugH4iBG0y5_=*r`Nr?;q}c$ zmma)6{m$9*9>=eR!i;m4zohi^DO z@HY+*e?z)G2OPh2>A~yM?|sj@^*!jjd*9@?PpWlm-?OfLFZxcuKKWgjYF+b3hi>!0 z>C?pn563Uw_R~0X@yRz_&r8=laCqSL@pjJkW&7m*$@PF#=UeAzIrsj2V{*MIb?AIe z51;clK6CH`uWuiR!^7v0uD-e2CqBAzFjwp1X&m0p*>hz5AC`O%PxZd?ev@-QPq!!E zJ5s&R`u#@_pFTeG@&m8$`Gmv6=a6pC1II5ux^ggA>+0jh1K+=kdhWar?C1L>=Oa^( zNcFtfuRV9Y@86KT?@aw&s^`jFboppLIDPtf;Nkek+rHq;2k$+^H(bw6*F11?!i&fJ zjl+uvPOjc-_D%cKQMnICr#gSY_dfUgZ`S{D$?1et`;UFgzR}O!ACuR;sYCl3J$&{(eCFi`Uf=TxhlkH0U43)4 zPkeOcV6N8HZ$9{5m(C|ACD)Tv?LYP_>(aU9{^a>Us(q>VG5mP+@t6;e5B!b8!{3l@ z&jH6TU3&2P^m|=e@Afax|C!0{*QwUKb?JHNedo`~?@y_o2ma{LZ5}v%x_IE>_{H0P z8fPv(?Q?KFFJ1G%;epr3+c~?x?#qSA^`cbk+;bu4zRyo5*T1ED4*I#Fhfg1$IrxFs zw{GC@@HwR0^T6?okFFfd)w=q4@xXV^)`R!n1&fIu=uEycTWB%S}J(t%1<;nGmRPROmY(GbTPoDos z_5S1sKOTKNaP;BzJ&$nh3m<(reCFY^`SkJPf$#I|G0EkW)N@m>OuZ`gtkg47<$hOk zdNB3z)PJV_EA=m_kEWW7E+5SYr%xXbJRJXc+ZSBVgO5+Ro?qWwaPp$xIW!J09(ncs zIlgk|`?~!7#?(tvPfK-w?y21Qe>s1DE%n*dhg12bk5?ZrocjW=FITwsiH|;W&AFn=MIR4=E>B8aRcSzTqc=1@j`0&V~=Yhi~H}f~2e)GZ0 z)p^Bt)J@6#=2YiV=T~^&SFb1MH&UHb`&^43k3Jsr!SR8=ad`L}((O6m_@zq^UY~yN z8_rG6x6YBbCbwHsotx|%&Q*PWeLMNRmFir@A04{Q1E)_H4?G;dc-v3o%*ChkGF;C~ z*F11|;Pvs!3EunKx#l;?)%z6Ad)c|?&E)9)3Wo=8Uk9g82M;_PKECMk0jJ-5@V&mQ zYwPj%nftC(`>*|4ZoR($lN>%uwLkZMP7j|xK6CH`ukU$*!^7v0ZqEb9FFv|*Fjwp9 zs;}1a{VyX{>nFAI( z@tU7MczwEXc=#RC#b;i=o3H1`XD+&Ma_jlw^jjCc_YLPz`EFvaTKxSte|K)_a}IsH`gqO7AG|(YI6V9g>GoW3 z{Nj@beR}4E>%8#D0S+I$KE8eqod3+j_n&iLJWrl$&w=x2&*k4+HuT)n!)IT>XAXYg z_36Rk;d4mWTzK)w5g(lS&C@t~jicW=J9jz<{w4W7mU>&N-!J6c=g%>-?#D`fC+pwu zC+47sPahu~Kk)j_sc?As9MaV{SNp_AR}SWCUH#^Rw@)~C_-_7d@_aniIo!D&-uLv_ z$#a}k=kq?#QbS>D&v~^U^gB93FUmyq&Z6tMij{&EJ#n z^Qqpia`yi1^O66~(!|Nx`CJJ0b8*Ync-a-N)D@WJcj z>zqAD*8jhf>pxTNC-xsX_wzJqa{WfC{igRPdieD5nU^1Uea|Nx9zKV3dmcD`@zIro zxms5rFCO^bXT2w#GhRr(|48*-w9k5fI*&}2T>bk?y=VExOQ)}g)2D|A9u6O$^!b6q zV~!zzcp8Vd*QImWQ_1Jm)K^mR@o8N;xAnh=eX8U~ujinLPamIo`GMD`2Zx8xAzgFf z#pB-(kFV#(XD&E=@cQ^VXYT{QgFKUbUrT*6)&4DK?*YGmOq6`5N&R-J{hdELbejiG zpDrGFIDYZAA2{>Cw_muPm#%r>@WJbwzn_2aW$(Y&lj|F)ekbw#_j~l4$@5#Oeox^C zKOTKNaP;Bzy^rDA7e4xM_{_s+^XcQo1K;b?`_+5w?d0@U>eH#dtF6m^FMcO^O`AIO z-)W!5LhtDBheRH)>e01etuGZCWKKRbrd((UD-^ulz)F)DXPsq97 zpVKGb=~Dgoy?xJ^gC0J8d~p20>wC|_;o);gSKnOi6CYhUn5%X5n-9L9f8QDJCeQa$ zog;7f0Nhe zsrD25k9FDm_4kt7T&eb(-k<2<)5m8He&F@(|8RKt9MbK1;P}NyR}SWCU46WG;QRMR z`-<;|my_>jsiWlgk}vYl_805h{^R>%%H%v}>b$9Qr`m_iMVF7}gVU#v2Of@pyzL9l zeDLiXuIHv}9ymGS?fDyr7mu9!-!tR9>>TM_HzN0CjMPz6KgoT1G1YVBT;Au#d2(MC zNS!Zr=)YgV2Vc!=PJKRx{`(8&!Q1}u;W6KkKYa2rpK~xiJaX%~;pE$V@`A$yuaDP# zuupl<*kA37U&(zOJ=J^0b>2h0&n}qz_We}vA^zylZ5}v%x_IE>_{H0P;Cc>xe8S-~ zzj?eT;Pmn0f$yBH2j4{><+&Iw`Hq?D{>s^U@!iz_-KwGg-Vq(T&0`LIxBNeiuauN&REHH>{RD|_6!~K9=`GQ-1y7|hYwyKU$0BwLCy`{yW=G150bxi>HDkS^NS?k znN#_}OQ)}g)2D|A9*#e}?E?;vIfneEeNh;}>uHfx}~-A%FPHZyxU*eEQ7?-|N!%&_v1etEt{^-iOwu z@1&)Y-w#v0_xe4FACEpBzTx=5-#9${4e9nAaQxDx2d_`R*QI^Nb3aY;nmX0~YrnQG zdmmXXxviRNfA0O99zK11=HLfj-@XKghtDD1o(GO!e01etuGZDZiwC}QwqN+3`C9V* zX6l5g_7geVPkhHLoqT_sx@4+-${cj)HV>RWT|Dq`{NimtaCpo!4}LuQc;M*6>w6yI+7~|haQMu_ zXY=Xf#RK1Yd4IY8a-KPPO_?e$@6Eoi>m{$1Q~AbAr>}?8r-ug~jz7HZ1Frq!;}Z@a zyuNkT&$099-f9=c=jD@UKW|&+ z@AjMC$LP>)9&_l^#RCt=FW&Y8hsQia{(5dWbK#>4uWujdoSiGY5A4tLCg<;`I#lbhtK@x@xFr7$BPHP|93a%4EvV- zW`X3pXzE<4&Lj3~=aJsWex96nOx--yIfh@ld^8`NK7Bm!aQx$KUvNDSK0e{_$-`Xs zW4O)rqroY-Jd>R^0ia$$2O_!rmm98FMYiFc;Wbi z*Ox0C9)5>(doDPB@yUZeJ##irZsu>CK7D%da&_*o4|v`dO#Ta{`rd)>=X=}ayTvA70<{57)l%(TBrl9zL5-A1@yG-q*dCXH7l}r!Jm4U#j=AecgL|gXHv! z)SXkePW9e42OYZ21E)_H4?G+`zWLw_uIIqVCtT01Z!S1`@cQ&SXXhF7@UuknT_n}H zT+Yr@?RS^tyF;pTJAd@>>Enar2VS2ZT>HaEAI@BO@yM}p<~L8{^y$-s@0@2yjoK%_HY^l4rj^ep$KqymaU`k2&<|;(>?b7jOH4!y_Mg;Dy6ye)D)= z!Ra?2e810pcP^b=f0XLn;ymPi=6iJaw1f$wv$?@;r&Zq?+wT&nLM-%Hkaf0xobC^_$)>N}~wtLWp^$7?SB z;PvGNhlk%GU322a!#}>BAD_A4@X5{m&8LqS4}3od&f&gmR?7YOY3j16&exs;=WpLZ z`{zFVDs`_^=Wlb+q1!xg`gHNY!{Otb55C}f4t#vV_1yaAf};noPrq}vUfh?TB-d3^ zoyVQu<=pGx(Byhxs`GlE@9E*w$7c?H;PtH=I6Qm~>GnKu{Nkf42XnQqK3+WV_6hGb z{@_JaF{k_4$WuU-;<5;WH1P&8LqS4}7mn=Mm4D z=VbNdwM?q>h;`|_($CRh$!*V6=M{6%q1!xg`gHNY!|{u^{lN7c`1pkDx%JHjM;Bh- ze$hGm-tir{LULUrb>&pwF>>~u?5$2T8*!Sx*Y z_=Lk_e)Hgk)5nVkzMp^RN$0>dldJvO`@-|z=gA|Ir+vKNBlz*? zb4VAT`S@+Vo*SRJ=;DFb$J;skjzMp^JG4?b2#yZK__lxJ>_f79pM<-w3HGJcx)7Qi4 z)58M~#~y})7Qi4)58M~ zhmTMC{J^z;e0;*;f!D{|d0B_n<%Y>=qg4BWygVn?>j}x}#8mqRKlt(J4) z7av^n!F68tMd!*5vaaOrKFP~|>HK+o)|LCw_YW_fzMgM=dU)XB@bO8XA2>Yb81jdw zadvzmP*LWYFlKlLRX%0Gcn+HyxE*^L|e(|;+xSj(apK#{JJ9G|# z!;8oKy)ONJW**nMU+%AU>36mE=|1*-$4jTLH=jN|Jn(S*;cXvqc+4^64`1Wt)_M8g zht|#dvv1mGeP7GV|88_l)}8&-{@dT(`0?oDF&`Ws_#20Zzad?H^R!R8^cqLsa|mxg z^4xEc-2AQx*Za$_lb7Ee;qbX$-}P|x;Pvf0aP0>leK>sZ`uKXC%o6|0r>>Z~Sn8~) z*2(%2+9!3t)SXf{NKF~xdOEIeoIW4;;qkREJoMpu4t;*%=)vpL@4O~V9{iebn*4pD z%4^98nS+7}-Ba6N}Uzm02MxX$bSJV*SR@ALfqgH-El zrU;pX-?{SlnN#_3JssCKPM;6_@c7yn9{O-Shd#fJYhAd`>&fK7ulb(O-=C_y#*2_S z_?{9P&fNIm`n-B!^0+MZwp70OzczpOo_spFJfHeb zD&OXPsq$+**W)t>J$U`*Yu|j}#n*G-;j?k(?|I-lCph}OE-y%am!{sDipL!Gy{D4f zbE$8q;_vyI-*tHSrUS3veC?Ay-sUsMkiVW64sYjme)72_^_I$wKY2Zwe4b5xt8#1K z&F?xq=A#3z-+b+pKHlb&hxwbY=Y_-Dd7YPhE>8VT<;I`9o=85=q`q0XweRM49Uk-1 zf!A-o_DLUa^U1^f&DZn7;qAOGN*=_@ar%7VhnE9B zbDFF9@R)~By!iB+55C_MHzuEZQy)wHYbrka-V?7Sr%zMIih;3H>9s#Rbn!G!pD#M_ z=EcY7kZ#Wd$1gs5axfQtIDNc$;Co%dzmi!0(QDigAK!eK7oWbl_=Jb+ zdFh!44iCIO-d>mHfnPUyu9LbfWg-Bk<70(>Q(_XKsA$4-b8~o=2ZwxSn6% zd~p21>+{z+&rvz@y>$LQZ|ak)p6fPn{*zI$JM-JEZQKx^L<}sq3evjKD(|Pvi9Yq66Q2d=Ba2F(1Ep z@#&l2Jn(S(tqb4la)RVHZL0a?F=760T`m!!)lW7ozRme$l>%f2Y&eG<8Me8k9qjS ziw{rZ@Y=WF{eJ&Ya^c&YaR1Kw8zp}*N6+1S@UF)PM+Z;i+815C`0(%#hqveUJN$i_ zn+{)i-^tvc)%@)fpX>Eqj}MM6-p1kK%e;8;;b|P+em?x3es|`6Boz;T_TRTN_s6ND zM;L$m#*arI58rTn;BOor{)Tk%n1@fiboHALzVq^X`aQ|%;Z%C&kk?zu>66q^Bg{w7 zhaZnV9=_rDz~4AL{0-^$9B};7r3bH1zw`1t`CZBB(NsL7&#!B8xboj^nM)DbvKhW)Y@VQ>!_4wfE;%yur zzJ_%1G!F03+%g@ydi3FX4t;*%=)&upuk(VRDfxUib?Q_+^1`!j1UF1wHI?4T z`hla*PwT+7FM9OhdJcVl;poEao3HmFc)w%MU%BCRz2C#PNdrF|z9D@$dhpE;*M8{H zhrtw#l#r4bO?|8S) z|Iz;T&fnph58m}~_~G@Nj~}_?Gf(ru>*MRWXU}}}`I)2U-XsF;&+onTU5D3ojnk)# z7ry!UGDq|2Hy?b@J$dHigKxj1(qA$E2VZpk&I;!`ysm4UK3%-<&Bxb}u72~u_wT=- z=KS@Q%>VUNzRclwYPro3V+*D(o65I2`*&2*vkS-qc@rxIqzWL1q z4@a+Y^gHLRlJh>P`=uV3x@+pzsd7Fe_v5Ep!%FDLkIlHVSwho$~374EF$bye!^sb^P?uEXoP#_7|=3s1NG z;GqwP2VNgj&{J5Tu>l>%f2Yz^b?F$cm zI6UUz6EB=TUOe!fOtEeaQ*M9KOhr>(}}?pmN~P z^U>?^rplvzyPl5g8%Mu!)~osOnbTa&*K@$}i;ph6KHbji7s+S0)I(A|kDhaRou7QJ zNWC@HbK1|p>*=_@ar%7Vhc_QS{)Tk%n1@fi`1G3(zVq^)uwNgZyndPb^Hh2DzJEvZ zx;*uqRDN7f$Mub)-#G6NeC-PleYl=OpIEbgFpUtP=eDIx@`|rLToV<2P-8NNTeLrtWUYDkx zn`%DS({X*{=r_)~z}LR;(1+_e^!bIO3$IVN^V%+X?40_mRL{SCMPBD6k4sX2n`&R^ z{l@ilT;Di-KJdev4)IT2=r#|W zK3zQUaQOJlbhsXTp!3(F47Y}^rY#rMtk4mnmq|!ByoUL>F>hF{5gQ?5$2T8*!QnB_kiVWA&RqED!RyoSoV|~ncaBKDr>5dFkDR@)oR5B& zd>=}s*YncDr;pDZ{J`tegTuq;kS;#+@!Nd*<~I*KUH#UD@0`8AJfEI})06Wtsm|+i z_WtbW?~&wuSE}##UWIF4_~^soGY_B5r;is8eCOqNmlKoE zNvVFX^m~lF{2p^(^0`0N@0fny;Tu05eLRih1ApW2@HeE}bHMRSmma)6{r>M<_>T5m zS(oP~w{ud@NcDT3+(hm6pZMs*nG-J_&tv1{ zVXnq?PV~%=4-dRP-ri^ZJ|rLW{W|x<^Wk~2&-(qS=l@gggXgE8H+uN=@tK1kczt?s zc=#OBH5Xnya>Unj<1-f=K6rh6Or$tlY=5Q++S_zH(jfuYbvXdo0!WRDXZb!>5nW9Q?rR+ppp9@HwR0^T6?o zkFFfd)w=q4@xXV^)`9zSQS!Yw^}JN)OgZ;@_*?RQCiRI_=TLLd!>5lAjvsh^>jtj< z;iC_S&wTt2@tMEp!KdGR@SU^oD&IrrC)Z0;FG%%$D(C*bdNR2_m-Rub=0W z^V6xmtN5csw|U_7>EeNh;}>uHf$KT&@d;;cyw<0=;PAogP!uR?0AIbHfslIpl#*arI4;+1Xedl1f_Jxl=96s~#*?jtV@xXUpzQa6c z*7dc?%lDGJe0TNp^HTEi9mO|ZI(-mN2ynNqZoqVoI zbzXD6lb7%Q7n0A5sm^hI?!%8q9}nMfeBf^!9{z@Odk#2$>C%JOr{DXYb?bZ3clY(l z?YdO!*1l(5`(E^&{z~$DIn}!6j}G1Dfzzjp2Of@JyzQrP=HioYxSp4;dEoHC>*MX5 z?aTJb8mnjSvqZ+zz92VUPk4u^-&AzgiQwNHF>^}hdV^8O(8{Z!ADx#;rId~o{o z@xa6JkGFlnnGfE3h;O)_o344_(t5XldH(;L-2Rkmy<3-_ zhu(L-5+kFfdLHvR{07_#M(UC*JnYA09dMJaG8rW`55DoPP7c%hh?schu|2{f$)T zQRi29-&fy^;I~qpQ~O+tACEpBzTx=5-#9${4e9nAaQxDx2d_`R_YLPJ=UeB6jyeL8sH;qdWAmk&7o=7aC`WnEj3|H<4RrP_b( z*K+IiJxdIHH`V^!`#C*)`uNPj54^tT1r86NL%KZ=9KZPJ%E4T%tB)5CeD8bqQ|E<` zlkbPA_EGzueb%{Rwiui>)&9yiUOIg}oIX7~@NoF}q|Xmr`^U#893FUmyuB{%8@{tY zNKT)pewOMvw=V4$zOQGFfw@xWOtla2M~80n!0FS)0}sb9-u466bKv6>&fIu=uEycT zWBy*3_HX;-h@5{$N%b9WUG{!EPlV@9^}Wsyemwek;ON8a+qdD`7e4xM_{_s+^XcQo z1K;OM=YHQ;pJp9?k@`uh_3k;6yYB(hn9!|#x8&jrUXK6%imXHK}z3y&P&@WJcj z>*v7v&pdp8CHKek#!IULRlQ>^ZXjCrz&3NVT8Xf8^ZH)ABK} zT&n%1_a}Pz^zoU4A9#JwCmbF=hje=$IDYZbm4mrjS067P_}*u|C!I4UOTJ%E^oV^G9{;^aHtd{!IRQo%Bbm%q@oIYJV@NoR%Z9j14fp5QXJuhAJz~O_} zH-A6>-pk&9-%PIGO7%O5=fB^hKaTKEQvII74}LuQc;M*6>w6!=wJ&`1;qaM<&*sy| ziwC~frT44%*msiCw5b!M`mVMv`@OhEjIN%#M5^z4bI`-5j}ML?czy3#I6Qm~>FS%S zed41l2XnQqe)GY1&fc5eW78+s=~Bl{^*tfyet)hNqid!vn(BMT9Q5$%{QJ(BA$iW2>Kx(R;raLdv388Dlj?lY=MnsP^zoPv zjt~5e!^7W@ZqEV7FI{@@`t*BU+V}0R-%DO|rP@#IKh|aM*Bi&c=Bf6Z-k<2<)5m8H ze&F@(|8RKt9MbK1;P}NyR}SWCU46WG;QRMR`-<;|DU z%~H2Y-6GXKlz$vSmyhOy)2ELI9*%#!?F-I)@a-F}=ca2OI62|%`5T89kDU77GvmDM z9O+y)PwvM8sq>}Io;rD|=gPUf&y8Eg*w0h9PTeGR#nhA$eDKx0aQb}Ufrm2>-u4HF z$9zNn@X5z~&cXQb$gSswlW+6M3l0ywK3@01KIJ`Qf3+_znEUqqRPPzrc@OnIyKRj8 zEY*96KRR@q2Tq?Z9(Xu@@wOkho&z7BaQMt`9`6Y_eY|+!J7?>`chPr~>-?z;rMkaz zwqATUZ5RXFq;8jLeep+!Zu7wD)5QZ1$1mRY17{xi_6tYP+(UEdTvA70;k5UzdUqYsDAJbX5vK3+WVy)Jz}&YFA{ zOIA~UQb4b@*c=7O!ujj^RE;xMf z`uKWX`VMk#@ZMb{InSJGUHbm&_xuhquwE)Zc(V~sxnC`Lt(t28wO?D8y^kCm0|%wrpL;*2hfg1$IrxFsw=cor;d4m0 z=YiuFA6+?^t9AA9;(_m+?H9ghmQKDuPF*t9ej;c4iSL-*WAu>JT~qB-=Ac8jdEoTv z;(>?b7jOH4!(*Nye?2#xx$x12*Ox=*Y+vy_c@BP>e1DQ^Uy-wYr=P1|#mJ$l_8tD{ z&}|+#eY$wy;rPYde&Bize0;*;GrxK43vl{)@xb>!>v^;OS5K~Mq&lzJXZv|OEW(GU zI@j@oACEpBIQsDVo=3R$g^xZQKJ)O|eEN9tz;|BWU+%x0*Gpb2r^?HFv+wJ%F?v8M z-+1Zt^>F(1@W8|IhqryewSRnk!r_D0x6b-Gb{<_ZxonuaX{!C#e(gDSUfnOkC#0T| zYJcwioE|=XeCFT>UY{Nu9zKV3@tKd`=IgofnTswSczwK`vwhnBx>oYtEOn#Q6;kEg z`}L7AaBAv_sr#m;jG%{4A0He)@cQwNHEgo!3r^;K`}>Ez>j$gX;;PvVEy7b;(DLJi~ zx<#t{{D{yCxZRFou0qjZ+ai2L$`U%p-&eNJRHAx+YcNb^9=dxx#7%( zk1o8veV}u8uJAsvKW~+sf0pW8A!p}~-tW(dk+V~sJNTnRw|U_7>EeNh;}>uHf$KT& z@d<~|{O0k#g44%~2fqJzH|Grdmi^}E$#=)p%~PF6?AOjCy^oy}Ll>owJLP_Cle%u|Dyi;I zpD+2kIEKzjJtpA}m@ zxx+r-dD}MmZ_pfUY~yF>^x&0es)Q|JES_7%h`FV{azX)7p6M5^G6S#K0Y{p;PvUjwLg6H z;mm~>j~p9ke)BX=pFTbK&Ux+RxPI#Psk^4SZ`O^RkBYJ5QZGooEY*GObw!76^W^do z`gHNY!{Otb55C~=m}kge&kbiTeDvV;>37cSBv0=R`|~d=ck{^k=m_@vYn8S;nE{O0k#!l&PS@clmX-MM>m-6Pex#d*m4%=hS(5xgqZ zd8W@v`0?oD;Tw(*{EfrI-;i$40mm<0dhq)6dtLgjvG3V8_DOELrTXr%E`9g(zII)V zT#@QKi9b4Yn+HyxE*^L|e(|;+xSj(apK$ohZyx(7oIYMW@O=*U9cmue9h7|cPWAoc zd&&Cl?^1fdiJ@y#eJAyI6@9$=c+JHhyuQ5P@bEjNYfij)_{Z1t<1-f=KDn8{`SkJP zf$!(QIox;6{<$B&O5H2f`Py^f{OvpF#u)u=>NTm(-{zo0w|U_7>EeNh!^bxte8Ke` z`1pkDx%JHjM-N_~e&=kxxG#q$*8@|X$DQBh-0R`C7`QprdA-l~^ziB9GY3EL`qm8` z9zKV3dmcD`@zIroxms5rFCKXNg!dYM@CPT~LsEV3z_-6!V(`{f-$i`m$D@x2jy}9T z|8VUKAALA{=HavX^zq_>?{(=s;yLr29G1NHOm!ZyE}d8UIl4WDuTFJdF$W#G%>$=T z7Y{rfzj)gZT+e}zPq?02-(3I4*nP+UH`jd}KV+0WN|Kc=gp82v(oiAUijWbK5iMCQ z*&~}sMk;$XWRH-Qky&O&ODZ$hAJ_B#{o~o~+-|??pV#mAc|FhL^?bd~Iq&m6=XYH$ zI(6ygtrtCK?>pWD_sv|tpLjr`_ZV~bp5wjo;u!c%;$?~6bNEw7-R98AtBXTVhsQS` ze9?6uczn{~=wCm$bn>`3^nL%^Pud3_oVi-BJulqf-5#&*sTDkG`)f+GLu z552s4bU1vD)zud+j&&4|PXGF8oqX4&@B80-jP=aAaaiW;{l)$7{ifHcYh%=V4d1xx z^zn4^>fzAS@rTfzAS;qj?HKXmOMk54)rdU@QQm+R1Vd35GJL+ z0~d#1Ji6x5^}MW$_LWEFx-xg?$-JzW_MbPVu=CO94_BQ&o^N^eaOmmq_*9=CIvjnB z`NL@)ZqLg;$G+!?%nZkI`!%0)uC%&c4(qe z$>ZYC_q@Ef=*My9?tGe;_nG$TeDrz7Ri}^FpS*fF^mP2;whuZSeT@0TYn{3Eyu5e& z4sm?0s}mBvZ=09*ao;Cyi^1O|dJpFtA4eXCPJMcLb?Dj`p89ln`r)&A^0+wkz0dWX z(!R&@-1GIg%*}U7`&{2Kdtc*ud~1yRj;RlI)NKx(yt+8_bo}DBAG+=Xk54*%T$%heJ=tA8z}g!_mi>KfKnNThGhC z4;`NC&$?-y^?q$${@v)h7_**QfBU@~A4eWXe{_7{w+@HDvAXj5X`kw<*E;pxhxFDX z_x*{Po9~Kry}sO@gzt`Yc#fBMJe_*<^41-?_JgNB9Ui?rUSB7l$${TY+%IvD#Lp(W zPL9kUE=s&4@$AH-5_8z#cy%1#I(a_u>G9eZj{0=nhdjS@>e0)q-}72C2lA`G_4E5$ zi5bJNSN@<6e!rdH_pW(4ULD7`PM!~Zdi}L89QEnC4|#rD*LCT7UW?>Fe)ad>{Jv^V+BSxXshYn7{6q4!7rZf9CUW;$LfS{F&Eu`NPbK z^CjZ9@8&xWM}O+j%Qvrms*l?|^BD8j{nFv~yza|<9!mU6&5b|vnl^u!De+y2`0cy- zj>FNPI`s0*YoF@lHqSi9{B^%{xIM22GoR-YpRc*`XI?Yr5A!6>n~2}O9j}h#TPM#4 zKD{~M>2s_uj(+&W#glIyeLp9j&3s-UKYL{Nkx+4*F7`P97JBzOPIAIRjWAalu5KjBmhMH$QBgxJjaVlj^5+ zeBi4`*S^$KpAJVqeB#o{B6XV?6&d*U96>P@Pj*71R_9$ouV zPklNZ{qTuPCy$Fm-}9p1IDoAZH&4VdFPww(!!e16C#pB8ep<%|zIt@+OFi}JaP-3` zE}c9s4t=kCbdK9SpaT*QO#E7+b+5T+WnPyhUY>YfVh$hFQMWmC>a}i+$2TAP#go?; zpY(LyuX_5S!=abQ?dwuM^oIv@SmM_cGp?cQUYEqRiRaf`9j}h#Tc>{O_}5?i!cm_N zPd|J%PaYSCzUQSM`Xf{D(8O;fnpgX~A&F}eFQ|DrULD7`PW{&LZw~DXM}4~PL!Mtc zb?N2R?fY0id>j?f!HN4P?vv;~Zl5=1PFE#fns{N&4@X^`*74IieaFtw{&3W%>we_< zrR)CX^+(4ay*z(C=WTMJ{`vlDe%~Q++eC9duI9-1&-44qiO1KRan!|WojhOWM;|*w z`@>P6uKSVam#+Jl*I(<}ADubTe2hl zHy&Nzk6Yxx-4gdo+&A%?iJwVyogI<6oSArT;>C#D@Ts)lC;r6^1&Vef?@~zKB^1FF$mOt#As2}}rp5OU#ygH6=ojf1-^v&aM ztS*jz_{7D-X&r9QYuX$*dm`WZ^u6D_R?Q#QNz{-2SF3qBULD7`PM!~Zdi~+?H&z!% zKYZfi;j|9dx<&8%{bc!pZ@%b!w_h&v&`0;(JbK6D(W!&ey7r|mE*=j5bhzEO@9?kZ zJDoaw;rjl*boy4eed0M@-tl;J>f*Kzhp(}^IIY9&`@{G2m(%wYF^0om+8yT4AC^d5 zArZfQ%nke)z;ySH5}lJulzWU(LKG4@f1C-Tw# z;p52T@J+`De(P}f8>`!W(DAFTdi3(@_q=>3e<|~tG9Vm%n3wPKi|2nZk zzUlbDZygSQV|8)#!zZq~^39{~b;x)2Apmvw$DKEGSvFDK?g!8D@{Y%&Qx~^&IDC!O z#c3VxSl_CP~vQfIB9dh z*(5(~n)uN~^(NI1o%;NA9lG|Vp89m%hdjS@>e9>WujfU-S^%pjE}w{FUO0Q?hdmQ_ zOjK`D{m`k;PuHPqU+Sq(*L}$IOQ$Zqy#9J#^qU8;W#W2?IOc_Ocz!rK@mq=NO{yO{ z_4(;KbnQz$_363~d4B2CrI*)VuS4{{V}Bta$KyKwyv*g)nin0OtC)p3051NG(jeeyf* zDK!tg3u`{j(~slv@ag57#}9va>Nk&G96^!wKAI=rJo@hY<0-%g-@c=&zhC<1OWpnJd^--;ajlbA7nfdLJif;2$~TX`zyB_k z0xKncB#|$5eWy0JZPM>9iQh=%Tc7qh@KlF6! zwNCw>^T~NHyeRRK#LE-UO*|#hobOD*rxTw^d_M8f#Jds`1{`&9S|`t!I`nw$4@Z5v z?nj+5mmaVE z;HXcBLobip&-Wjvzn>>wk$7?9nTfeghd-pyV~KxB{Bz<1i3x+_)p305eJ!qhfiEOd0ZU&zD`ccJT6RhJ-Q!o@5mpXN_2hp{oy!V$F)vgU0ix~@%S36i_<#X z?)$9tdw${tiDxIO|6u;`MBC0a>*UqNrN?VOIO@~k(97fY{o(p`eOy*^ z;LrWh*W+_3jNiT;ua4ter+({PulmE&=UCnDgN|Q3b?N2R?RlM%`TQ*Lmx=C2_qlo9 zpF)31{9B^?wC{h%tK<0A$@76vuRlEg#_HnehfiEQ`R390ygVnY*T2cUewuh%qIvbY z|IZZuW8yuD{5W16$G1-X)_H#5wJ#j?>ADYje(BVumshvvbxP)8UvX*v?z!iAYF>Ax zkp0B7`Mc*~KW81Uj^kUWe(TiHA0B^Wb@BAWXY=HnN8j^u{++j9WL`f>JU!98`h5O1 zjXaWgZ=(Jjua4ter+({P7kKRpM}4~PL!Mtcb?N2R?Rotu^EfB**NN_b>xy~ZmqHIG z{v*-4(Cdxk)p3055#V|8)#!zV7DeDmmgUe3Ss_N$toKFq7n=igJ9kM7U$ z>Nvi2>bK5yp+7wH7^~ZT(D93>F1@_EJummU_mb-~uNxBG-{$51_x|#73cr%*zUK!Y zM;?bxeR_Fy=-L;a`gC~u;j?-2xH$Cv{_8xM@70;pEs57Ax^C@T`+WT?M&C&MPonEu zAL^*v96EV*ap>vr_~yfPLf3uZ@kxiHfBoRn$>ZYC_nckF*2!x!*IN_S)sH#5&aJEe zW*`?ZU)`@d>NW>PUR@k|Iy}Dl;EN7NKV$y7Z@M9U;i*S2uYS+j^T>YZs?7JcL_GbN zv*(rl(Tg!KS^TKi{i=s2kEaiQ=;hU;!{KwRE}s7QZJxaT^+T_&eAlJ#IeUJ&Kivm+ zWX{(m+OM0l=jZ?2f0^@3F=Ri_pL%%mcy#>G%d1D%{_xbN(-$s|`@MDgpCbLVPG0?q zdeQgu%zLKy!y7Z#n-aZe=kTH5GhdCt*Al&t^No)qk3*+Ey}b8Ty7q;qJ{_KZ_-vj$ zE)IRq%XgQXGoRlk`d;aKjCuJU^LmW^H_>-Y-*@=N$C1Zr9Uu6u!{KkNZuddQue$2d z%d6l2e=fX7yRTfA_hxSQB;J|md!D&_pYQu;nhf|YiBl%}?#HkCxbnDk{L#y+OV>W} z)Th%YF3!aLIQ;+r(3yw6TGw+@Pyg!T(97fYI_vw8`RMQVoDcVh`^h@%`%(A*rkn@& zPv39q;mPCagCBZ%_2_W;9ILA@TpV-6>%Q^yMTbW(k2g)uuX%glx39N<{$1vLSEA>s z=dbT8IKDshK7R7deX2yyU(ajbWBBKTujbOp^MON8rytz*M~9=oF@N&r(f#1{+~~}K zjz9A@FYhmO^0+wk&V&2Fx@tYVJLmEDiQbpIUpcPV*Qs;f-kj)ts^7oV!;{C;2S4=k z)@wQ(KF8{IKXm-!scR1U>bml{IP^Vd*Mal$VCMT!;(dwsndaQr!;G2lOo{$GB=({D zP!CTYkB%RDdDjhH`@>V84o`plj`8&0{ou(rkG|*Zy~_L0{h8|{iGN7+ernGBel=a@ zJ4@p9iQZrNQx8ubkB%RDdGBj+5Ic%y+C0_3-qGrw@MU<<+CZ;d896zHo8+y4N?oyuQ>`k6vE=p0oEW*O&A3 zXy*JtqW30q_FmQ3&#amA42j;u_)|yS=FrKji$hPxFK+vx>pt)%d}cqQZ(P@>zM4lb zkJoed{IH(?F>`%9(R0;v*qr-0^484t?TMbd{hU@0PaaPn{LssL-q7LjIaas(q2m`% zU31V^*OkY`q4yq?aSZmk_MLyqd>>2n9!uZ**S94xTcY<}zVUJ7ap=^im$wh5YhQTk z)8Xld&*sVF;?VcJykEJ`+$T?EUf!3?YvR2tKe*oK$ltw3@r|oaA5SN*9?pb5_a}e2 zWA}0U$Kz97I^6E3=jHwWiOlE8MEf=SJM;41KYM<7N1}aP@B8p^;{(5SIQ)&( z?LO%ERaZTFdG&kUbKQC$^xpmF%t~uzd>&oNe(D!#y_nqf~b^4ji`Co}ICc0m& z*Y3Ms_vgyI7foCw(S4;ab@^x>oxJ)u^mP2=wl6yU(R*I;P1k*^s~u_=C z%r)a0tee)Sf9E{>C(-_czSp^TC$U(feF{JLIPy4j>eI_xFX`GBp89ln`r)&A^0+wk zy>7VQT>r0RPOl|ef2>>9jlS>RlX)$bIJT~-hiBcx(=R{t^6pPM96rbD%Im9r;;Cy6 z`s%v!&7<$@(*ESXnd=*g)*tIt#y8lvyf;5AlW1M)bqpUz9!Gz4eBieZhrhA9-3J}N z>Z(UCuYO;bu6OH~`+uqc-<0TjcU`(4dfizm^Lt;S`++}o)NKx(yt+8_bo}DBpVsM% zPwO0A_p7dc=y2%eaeL0ruk$iPKr<$~&fOR0+~;|X%yrE~_d(w`>fy=b>4P77dDjgc z4xeLnyB|7!@zga3eRW-VTpaqIv+KcgZ~7FRHF1_i=g*v72cCDUXTEDEekjrT)rUIj zHiu4LT^xEkesS9mUH5^G;QOUv%9M9-nmGzr4Qa%uD^AL+fyH%&X7O zt2saV=KGxi&Yd`O;^c|$izRFB{C_gPub=qA#N`u%8Pvy>$ED+sUfx{k+9#g+bo#`_ z;lFj}p|95UoYd1l9uB=cZm;Vv=lqzD{@xMbOo>w@TGyATdGqt}{JvJ=3UxlzQMWnz zkXILno{nGKvGdV=;PHvqeaq{MPF;F=bLcsHzI)!z5zw58Z%(uyH|KuNeM{)HMfvbzS-9(f4}udah^Jjq^TN3eTQ6ZQ^8! z)|;g>&%Qp^%kS$Xu9~=9VladHxbnDk{L#y+ONYbnSY3VM;<$eCaLl3mp~Evb{Wnj( zdGzL*F%90M=1HM>6YWRsU+KMHeJVd}kZ7OU`&xV)c^v)G@qynu9R9}Yb{};7s;eHo zy!yRv*f-hV+DFbG;CzYpP1X(js@}hDl=*F#XkW#jI_fruPF`IcdOCh_+fVEC#i#u; zUH7Z5e&}%M<#El4-t*eN=3Pm6p3-?P+xL7rbM$;5Z ztLw_+;?VcHXFauFSRw@%OSFz=e1mn?zGAB+KAUKLKAuiqJsf&EJU-RuhpzqO z@kxh6FOS>TrFFx5_M!p3H*x7i_qprRdg1+gi_GiuiQ6Vx2l!J*-R98AtBXTV$1iUC zq3b^I_@vV}ZuiwXTpa!Pb!q*!PA(hJa*5u)m~1?%of)mu-`|ZI`%pqWhxnH}!Gl zarMt1y}Y_~IQ)*)#nUg}&FlX0^rbGHxpn_^@?Dp{*A4qn>znmvg#cGfTsYCXVISP< z*G@SfJ11_I$e%juHiu4LT^xEkesS9m9gcp+{Nd?eKh|G7`R390ec*f1LIEtF_`byV zB-)SIzqk*4FWNNo+%fSBiJwcfZ|QxG`ndAADL?Q>FRv~g4!>h{yDvI^@ytVg_4G;C z^TII)Iy`!LyuJ_Y|MbK6N&&i`+}G{{`_JxcmzjIuW_@S3qj}C{=vAX)g z#W6=bI{oXXb?UWF{hqUZr+wi2Q*hW2=8ULLpS?D=Z{WMA{)0M}0Rd^Km!-`*d6 zJ@frqqUSGv>ZscsI(c<*=;`>yZ9jDSp|_vom#+I&S3h)k^zwK;XZMlo|6>7tG|_ru z{W0ghpT3#7?vrS}>GerHJb65Q@Ix=}{-nd^b23$KILmw-djYXnp5T9d(;SC$BCJJsrQe?T1c3^zE0f z`&Cyzba?dg`tSSSbJ_FnQvq#|=sQWqHuQ7!J4t*u(f1U7@NwjE=+vi|_dKR+UwG=% z;pvCZ=E>vY(D!xe`RX~gQ3`IDxJIJ)YS(2y7r&o*9hCUxMDO+bP!CTYkB%RDdCyro z96rbD%Im9r;;Cy6`s%v!&7G;QOUv&DTZ{Kv?x4QbFGbh~czje4c z=G6b48T)1XNc+0&1Kc@r$Hc7@Kc48mvM=v_<4HL$rzM_}cx>W+iNOtg@YP&8c|LIH z>GXr!{^)S@H|7t|eDr4@jE7@x-8Y^2HqX51aOmZ6od@fb=Zy8$y0}Y#J0*I~IL>pZ z*V)r^o_?6NbZ?UR@k|I(~864_)_x$0r@0{`KQIK_`!kL*H|DJ$NtLGN3Oc zelgMcHD}k0_okyW*HaUJl<4~6PaSoeLnp5;4m};exb25dKlJUFPCb2(^&yYjeOv!C zhQV{rb7$8S{8FNQKYc&v&Pd|NiQW(R#>bJzp;Mn;-gA(yec`E3ho>Jtn z_s7qs&>o31ElqnF3)>(YCWeS_!jZUJtQnDGtXzxp|UW&}qh@`I~RA5SN*9u7Snf4J>~4o4qj z{_t97-_h5l_ZjQIb@t0CynEtSiLOiUQ@y_bB=b5e@tB&QI_fq@zw+wh(9`jY+kWV9 z^fTrUPyhPy+`*G?9(`Yz-iP)M;46uqZ=Q!4*Wf+rr}^P$iJp7?oW#eG$Kjif5B%2Q z@HbYs`=H}jUG?bY)$i-lI^(|oc0k`swEkMJU6;L%{33I^BGLNX>$!S(@_72-hhE;g zM2ExYSl#Z2j$b@=%|TyXR~{FKzUOSc@ILd^6#P!&UWwKdbGDv%kNJ7#`^&_06RlJF zP)FV7(8;TdLr=#qZu_Cb(a)H_?wd|ucbn>`3^u5ly-(3F(1@!$y`?ZX1 z==<%;Bz}`B?e)w#jJT4A>&&%`6`8VeyQuu&G^YYy6 z^Ll**mnHIzt4<$JC$An3Jsp3z?SroU>-*S#biV+OPCPEr`fI&*AKR~9 zlKI?}cx$5dxz}^`@Z|CI!4JK>dUQB^j@8A}AHU7(zVY;>E)KmsZqL~|ZGAl?pyLyd zN!&NloO`{#I`h3P@#e&f6La{W9-cfN9Y6H))_=P8ho?Rrp8oi4p1l6`Lysq~zIyb1 z|J#@SAb?{Nt-IFgjBBu8`)z)>CDD4^>o-1*JPzMZ(UCuYO;b zp8NX;aB$*@iO!Sr>bkTqxHR*+D)IJ2=dsVbI_fq@AM)zr(9`jY+kWV9^fTrUPyhPS zAD(>k=zGrJ%z^Gl*YipF-Fjl3GUtmj&%WR8$nVyhUdPl?w>kQdR~Lt#j$hpNLx-cE zF@N1RoxbqYrI)u3^qlQ0JP)kTCkOb$MEeSJw(scm{?5$#_lfo${HddEbLiyN#i6I; z7q|V;bsuz>;;W={7fKAd=0qUUxngF5OqhfZEy9C|uDzWLyb zuKU2_ldk)g*B707^z!QWob6}y!_Q9wJTuY0+??&F+V3No?*obU?fj{SCyz(R552s4 zbnOpMeL8*N;+SLW^sk@R$*Zp(eb4#O6gV>R$BE}AI&ZEUbG{~Xydm)qiH{~auYFyq zqi%Cjc92&Whn^0PZ$9{u~rRtJ{6h@vE+S^z!QWb?Lpvx@X_)|yS=FrKji$hPxFK+vx>pt-Kq{Gv{eypQ(^0+wky$|*tsvpN) z5#WW1-haF=xxV|oRK34s&QB$JPwMw7^>O8K^~E2(ym`^#@Hu<8}Xd`l7=# zH~lwH9v6qc?*sdA?=_dE;I9)eNVLCpAJ~6;4|+D|;U9@lCfa}NLmhRSLnp5;4m}+n z-+b^z*L~peN!NYL>x)i3dU^GG&aM~dV)HMfvbzON}9D3_S#xd|m|BDp-Wuo^T`u6wNB>tA@y@+po z9C;i%_37pLr)yt$>eJ!rhtKB8vz#ce-y-3K0@bltbSzUb7Ym$zQ@oW1XO54<>_-y~j^=sm`qz2|su z{B!2|&&1~vz31?!j=IgElUEmqo(_+1KKP>RKJfUY!_mKfaOvc6ap?R0x1Y2Ryec5; zwdX~~HuQe-#U!lb{T#u^k;ma%eR_HOQaT*|#_HnfhtKB8H;=xrOYeo==Prq{s}r3k z=ihbN?}yK1ZvRSj9{c)G4^JM?T==1v_r6Jo!{=CCJpJ+8yzU!MU+Ut}%j5Q(y~kP4 ztVh=bh{vZndynk(>ff2OdfkV5c=CAq;D=sbJvtmd$Li_}7son^N2h=Nv`)V3()a!E zJ;r)w-MBV|y}x99gZGBu~90QB@xHGaIC-oP2!D2_4vV6r;n$TR}Y7t4v$au`Jrq7czn{~(97fYyj+K_%bNna zInjDxUhWgu>uX88o@m|R2OmcshfaNZd3EU87oPfbc>3Y9dGfe8^gS=@vVI(A?#`!q z^?Hl%eDrz7$C1a;pZfIj=0S(U-&kFJ;NtL$N7p>Mo|kpezVgO^%-wl1FYBfK=c`FL zAASCC)#>B;mRAplo(_*s_4%R0(Z`rSoYvv?yzF!Ad#+Ex8xp;ro0t8S{m{!vyprfW zo^N~{c^o?R>E+d-YhQTk)8Xld&*sVF;?VcJytnAbapvxPnwR&P_UU}|dB#G1T!XY=H7ap-%W>pP`=kLS7P>#Z^1JEeWD@0h)>@jMZscs zI(c<*=;`>yZ9jC~2Oghv`oLog{+&_S?|~8<=>57$`964>u3EvgzdVP6QK)yTD;W=L3@pS6Z%UgHo+7F)k zba?dgczvB5ne%;7;w6b^CmxlUFx-%SpH6%x@u9>U6ZvtxI*xChJRkVEeY);L zo?klk=;hV#dF_=!@T_4$MLO`=L4T!f9(rLeY);Lp5NAW zUAmsvYMBSW`dd4{uU_-oBlFM)zhBGmd)B-hua4teC(j2yz5d!4j{0=nhdjTn>$-G3 zuemah1rzz!=b|;Q^)io567|FP7B#QtI}V3$b?D`r*FM$9#cTgKe6~*CcyzsAogwp> zHF1GNzWARbzh@l78u`Q8i5n;Kt=~`NcmBGbAIQq!X!|IvahY~kR#P9x^?>HR3)uESfUi(xZw|V*)^Vj{-;r6_y z%OGY>oG%fFKl55G^Z8)nhBdeL-F(O4=uaJb`R27x^>Lf05B)c<`=!I}c}<%^%#`@9 zL>&IiYt_tWt;A2)+}d~Z9fzYob?D`r*FM$9ZJs{#-@NXZ4!7quV+Ju#;=GAC{F&F9 zna`&ZH>kO_Z^x_S_}0nuflqG^c>L+Bc{uvv6BkdudG!69m^*`5Dskn+4TE_>zdUWkef9liW=!Z{SI(b|i`koj4t{KSgiF+jCm>13& zna^2?XV=^&)lcjAz*mp1ed$kqIvoA*iAyJsi$mY@qCYqTIVSP&L>%+NxhnIyIq|xh z+obww9Uu7W(X}uAsZWQaA3kyE$3o|l-h49$HoiRTiZPkcOa zQvK1X$Dev*JihtRFP^-<_@t-ne$~?t9S*%bZeN%Bp}#Kkyf*Rt#Dt;izLYusC-I4z ztK-#ieCyP29sl}kUpVU1;pvCZ=E>vY(D%IbLw`dCb4}s}HLv#fO6K$L#3yTBj#tO= zty8~s{F_7j!cm{D`;h0CPF;F=b^AWn4<9#XK364Pns{Mi#xbf+L?+kSA=Z(Z}~dfnSP{cV@X5C31!?+L@P>GQ-yKKVJn=Foh{;qa{v zy?pc9r~0^f?H`BF*6ACMuJ6YqGKe!1&rQ5I@uI{d6ElwC`uyR6#77hVocMI&4T=2W zsEgA&dA`)4Zyujxb#e5^FD{;Po{-lSWWNud`>f4{EgMc(GQ=vcsQ-YO*<3MN8j(4%Ut;83)gr19Rkot_uV{t$K%ndgVVb9 zr7kWW4*ztx-M8=XOQ&yj_`>!5ecJ%kZJ&6Kmv=lKow~TK!{KYJE>7!k`~H|e{VtKX zLLv@-`FYqVeQ%YxQzCx*#>bJzF$X$6@LPw&-&kE7{qTvau6*<8dtUQp5KAU5pQxTb z%xlBUXY0fr6Zz==@NwjE_@?6nzjZkLjn(Zw==fDvJ$iZddtSbiFP?#{n24hf^YVRu z%gkx#nqT*ak0X!6Hyt1Nt;69@U$}TU`r#9oPQH2cX>Vfvoi~G6Hc=h^aW}|;+b62q z{opxX-tl;J>f*Kzhp(}^IIY7S>)ZGKRca1=^5c8?ZZ!wGN$p2H`sU-cAAP7#het2Z zZ{HvEo21`O6F-`WlfMo)hotXA6Zc6}Z&Llxsn1W>p=)32sZZB^$n#65F1@_|dS3K< zWFUJc?wE*UUN~oGKIbH!lBnLK`k_;wpRPmKzSL8nuKSSZmrh-JdHwag=nv08j!yhm zB93|CT$lOWlz2s=dXwshPJMp54qf|FPkp-XL!Mtcb?N2xm$oL>U;6VhkW&*KkL&oy z1Gqa8pAOIQ@{Xrdk6s?1uKnPtPlrb@kJo+EIqv-Q{i8(O<~$M5eTj6A!*yKi)N37| zcKFIP{C-c(!SU)izIF2ErayZ9wJ#j?>GaV& ze(CV&&#}5V`r{WDPhS7}p{G-?b?Wz=@63TuCq9$-eBz^tcO{zhl=;Iu6X!~tH*wa) zsS>*V=ThaT^L{8fFr?uS2q>C`g^eW_0;kBdVe8x!mH-8t~_#1|5OpO`RA zode&VxKQGo6X_g>>$uj*tBXsI*M4x+r^BI_pWqJv|3A51hd<=N#}faN_~*n266vQ; zpR*;tD{;=m84`2Y;COW$-#U3d@agf|7moULIQroemrfoRhd%90Tqk$rz^4*jkM0ND zDRST(iLTGSKOBeaxYo(5i%YLA9$#a1aaxDleLt82Pb5B>_)ucPFk=p!J@FlhGbPeF z4%cz5lUEm)9%U)QheqkTJG9mls${noi&^@qow zzM9v4(D93>F1@_EJ+J$7;GYuzmgs(TpPScona|r2=TCH>_Wkd8bsXP1c|P#z^@qpb zSX~_b@QI5j-#q%Bm*<4_`kw*)G4Y;6^Xhef;mqr;iEl~d$MNbozIE!i&hrDWec`B2 z*L}$IOQ$Zqyt+NFyK1ce@?> zA3kyMNvi2>bK5yp+7wS^xwShgN|Q3b?N2R z?RmM+y_dWk&?|}VZ}W2hdw*FX^LkIB`<@?s9C;i%_37o+p=)1w>eJ!rhtKB8*q4zW++WHxmDo=(^2VhCW}5Cb3N7Vu`M6eW;^ubLiyN#i6Ie*?ho>KN_Pnw`S|o|(64mQ|)x(p=(+5BF^6Jsy z@Hti&Pk;P2PhS7}p;uSF>(cj}J-^(a?t>{Z=a&MuUpHsZ&%Xawh+y$V`+5Gl_sUW})Th%YE{^-Lb>^Y3*7cm!(?1>#y*zHOv%U|R zkN)12^Wpw*KV{5=??>JL`yz1v^!=tDo;;pD_@S3qj}C{=vAX)g#W6>`?i){Eba?dg zc+=!Ny?J}zx39N*=zj2eZgl2A$Detdm-iPsd0ZTN=fQnoU9}!go%8tSMDI)9uN>Fw z>-&>fInn!6zkjKRCy%EOe(2?`*K|01j@9jc==jA`*BtcKb>(q!=zGqt1LtMN%y*_l z|6LLLOmpt*Va+7gN?bM3K2#s-;mPCC@k1}~x}j@-c5tzrp8mTZJo)C)_nf^~ zc^{fC^PMGe`b6)i=G^aBt0nQF#MKkMzw)OZo;)5MKlJk6*XY_Gp89ln`s25G`fi=R z@YJQ3SGVWvJ;?Rt`j|QMRgX_|_P*BF&j%wI>q9*}ed6hZA9{K9=y3QPtE(?uoWAb$ zO)sx6b=9MnSHI`%{mS*_Jk6Rp&yeW7$(+4c_4TuM1ZyOEui{S~b(=#cuPzQf9lyBk zhpzj;5xsCVJoH z8y`m=hfaNZdHZ0x_JyZD9iD#pY@R$W4t>wddzkyobv;K0h$q+^6KHx)A5JfKIqy%9-pnlb^p@!yu9Dfo`Jk0(SFVT&b++$uao(FJkdU`_kH*{ z@;H3c@qynu9R9}Yb{};7s;eHoy!yTFxo*7=dheby1A1qo>o(&VT-V+gy{E4m!6y@4 z*ZirYZgc45)y1Kw;}^I6v`$}q@=e$Ms;eJ59C~@&p0jn?IyradI!~hgt^Kn(_xikk z5}!&O+h41PXa9|-4}R$7t>bh!e2&$X*H`<*Q`a2y)pc=Nhud>@AG!V)%6u12^t|$X zGv~gaHceu)M9;H+{;7v2kEdUL=;hs?bU1vD)$M-h_{CG#9Q4(7<#BQ7`@5+7&hx-} zK3C?vXyPJ??icH|`>xmh^)l}*5;sqDU+GI-KAJ}-uRabv9sjuPi%x&^oHgh zht8aEarEChTpT)c&A12aruFIFNi3FV|3TmD+-EZP&nDWZ@Pm&dk3*+Ey}b33u6^OD zPlu-;KAR_xi$mY*hWpL+|DFtHsYL6Kbt_{T`o8;I65Aw>t!wJxS@-bt%MZQ0`;!ib z&#}7l`f8te>Y9VTx~_cl==-{~KY4HFx=f<=$9k2Y2m6-KXP(<7T9(e0)q-`Az<-TLMJUnv86U!v3EheI!q+jDk)otHH-*EJJe=k5!0?(_WRB=$;lAM|~r z9-cg&KKP-Rciqt8@Htku`=R3(PhE4+SJ#!t#i8#xyB<9ER?mFbPW(`!^JmVk1JAoX zllWTVR}-CIeW;^ubLiyN#i6I;7q|V;bsuG1T!XY=H7ap-$LyLbk%Y~m`3A5HvN z;`f`GpFqncT3zS@tcWXOx!t9U+VJFJUV&xap>vz$8BG9-47n0bltzazUa(L z{hmYXaBwBk;~Cq~ z&-wk6I52V7MDGLqsfQDny!`+j4xeLn<@MD*@zga3eRW;==F#_hvvlU@x^dpu z%kS$Xu9~=9qV;Cm%(JhLZ{_!W6L(MCAyHrIgtCMhh84noajBT?Q1@rxq6<`c`n=cd@mr+S2`TrK8{Xa9UOW( zJigTBgHFDA^nHD~u3e9xN#9#0T7RwA=GNEukx3k#XnpSWTs=H_Jbmy(FYkV#!{KwR zZudjSFP^&Qps%hgkBdX!>z?)0eqpQ3_p^!C(foC=&e~TTler$1Xno}ySDikdPF_76 zdOAEl)#rz<{p0aTheI!q+t;Oa!+Z7?8O-Msw@q}PXDoyD!u$0RNt}>)e4=%LKXue? z4xPNZIP`S<;JtnT>bM$FRv~g4!>h{@$}1g^SXaLeW^=lZrwkfeAlJ#b;CZ?`ewb^ zDd%P9#LW^j?!i8|*RLN&a9ZMFiTtUfZgc45)y1Kw;}^I6(BbH3%pacq^<(|TlW!h< z-v_=IZJIgmnD~Xn&n4QA*uS_Bd@njQ^E@T-)Wjbo+PCyRM}1s*Tz&CJFRv~g4!>h{ zyDvI^@ytVg_4G;C^TII)Iy`!LyuJ_Y|MbK6E;%pmC--&6J=lMCU#CYfcHgUqXI;S4 z2S4=k>e1owIaXI+xH#sBN2h=Nv`)R&so!(9@3as6V&=PR;>L--Uzl_6KYx_O8Hooc z`hKDh_3-5J==hh$q+^6KHx)8X-{K0kCg z`WW+v(>mO~F73fn3 z>f+GT@r&Dj==4M1e(Abjb@fAsM=!7czW+U!J^#Lwxqdg%car>d=;!D!GtXZo`kulM zK8`#No%;0hp2u|U3r~GIJpJ(5Jb7Fk`o1nbUp>dZpTQiI_~k_J)fvms&&A&)ab@C9 z61~^!Lp?lsJUV{pH;=yO?78VVc1Y%WaN_QX-Y3kt zpPyGJaaH13iQZ@Qp&p(*9vwgQ@}9GFIDC%PmDgAM#8cNC^wo9cn@8XGzxRwoGta{k z?IY|v^7G*R;5ZtLw_+;?Vc^ zM(c|Ah66I+;}cI#JTcMw;`+A!c)z$TbG|L{&cxdjtwZ`!myhPr$*Ye;PscxQ`=Zky zefy^CzSY$aojKul|E~;wg#8ChnK$zOpaxed8TDFZU$g zm3T|yC5im=!B=zXDR6+dT84!=abQbsjR# z!E?s?YF#`%=k14yo->Z~9O`xU-Uxo5=sConI_fruPF`IcdOCh_+Yep$fyXBup8oaY zIYB3ni$mXYc0G76Iy!SbHStG@&aXMUUc5Kml*HYM_a(Z%_)|yS=FrKji$hPxFK+vx z(+_?7rBhGeV|~cucHh?jjBoIq^V~Tji61B0_tW=t?*7d44~gCn_{PVP$Dvc7Ufy$% zu6^ODPlu-;KAR_xi$mYnrT539GLW+q&q?$g_uS7|2Je?Q=7)z8A5Qd~?|p%Kc=CAq z<%eEgJvtmd$Li_}7l&`W?i){Eba?dgczs=Z53+CY+&wdMJ|Zz;@cz}$`3ExJ>l69G zRi}@qlUEOio{m4<_CbfEk1>CEt+Vgw>(cv-_1`-ClMLvr#A6aOj=}p>ukVjU@L=N2 zH9vLKZH|8B)y1Kw;}^I6(BbH3%pacq_2apNC*M5!zAn8F{WNp@S)%8g=V5*xyeIuJ z^ZQex=UzW2@p0sF_@?6nzjZkLjn(Zw==fDvJ$iZd`?|EwxbJ_F!CaAO{k2|aJVUP| ze@)^qiPq;{&(*_|$I}Nt^zzmvIvhU7>UKYL{Nkx=4*Kf4^0+wkJ!k8M_nDt(zQ0U7 zH_>`x&ejv}F^?tjx5P&ityB6?N8RSo$*YS)PscBA`=P_p&zQgNn@(SN>e9=bL(kc| z;(l@;{5tdfRibspoUJ>3U;QJ3zb9IE_)|yS=FrKji$hPxFK+vx>pt-Kq{Gv{eyj_0 z^0+wkz0SJdT>n>QuD?mNU&~*IzTaNRJpY+!U&jwVjyw*X`tB?e)w#j zJT4A>&&%`6`8Vh5GoZ^7&C7GM&+E&X*K>({-qbi6ZumQPacnsA9{J~ zKVAF7Q=bk`fBZI2UjO=`$CFoIJ^H@??aO|fIo^_J-L+2V=fQsMznR|~iPqy@zwvS8 zarma=1HW}R{EgM^KIr&WS3P=p_4~T?+`lvfxhnDYMCZwQ&3Fd;f@kx?i-~W_32`3# zysM*bbMzsvE)G2%zqsv(4o5#@{_ym#AN}FUH;=yOd{O49N+)+uv-I)C_| z`z>>}-t;=Aj=Ig!hrGHt^mP2<{*Ryh_vLZ)Gv=@Rrkf&tbzORS>p;)hzQXgs`g~{R z{QE@v3Ujva==FZe_<3`peFuN)sM{Pmd3AB<>G;KMKXly(9-nl0`qz)=6`edT4t@W- zn|+3L%X)K9=KEmc?-K1tGRDDvq}Q>xL@;CGv3j@9kH==jAm5B1g4XY0&O|E-f(Up;zr&6o%4fcx#RrRujYs8GOw8vr%LqP)`vRkHiu4LT^xEkJihthi>~{? zSC78ud`;$fL*gG2A5C=LTsP+Y@BHDF#OX8FSreVtzOK|!w>kQiR~Lt#4v%j>_@cwn z&zQgNn@(SN>e0)q-*dh;^Yq-XKL4@it{-#$PyW!)kGE#d>UF>BsM{QU$g7J(PscBA z`=P@zpD}-U`qz)=6`p+aCiGdKGseMt=VSTd@kIL;`yqPoqqAk6Z%?$J>3tGDjyw+E zbbR2q4u`+7y4?pIzv`+-FRy-Im)@tWd)AGoGoU{udhc;vdhh9V?VXw5+Y-Ge@u!Zu z&7qT57l)pXU)=UX*L~nk_{?>Lr+@ueNAcuwap-#=>^)RJj{8gI`&6R$AMZ;Ub)z1ITF1m^?Q~2xbhR+^v@stMBe#~4u{{dy86T&n|t?KE1qsDIE@fV|DTL!)Np4n@8W*rT0SbbI)Wj|4MY8oPXD4 zzaP$(xh3eFdEGajzSPB`m&ffndyli8S&#mm zIpgtZ&fX(?y?S@%tX}t_9-cg&KKP-RSC0;d&#}7t!o{(U;?e0}KdqDRy7U>t#QpC* z#(HMm_)q5S{Uv`Lyx;UXwOGL3Yxu@hr;n$TR}Y7tjz8S?LD&BA_@u+5m$z>Ayy&g7 z|IJ|DNW?KO9P96U16(FiJ$`W2>Er3-)x)8u!{bwZe(2gi9-nkL^zyhpFV~^#^0f@+ z^+fA|dAUzquS*5EbfR^GAAB5n96I&s<<+5UUwG=%;pvCZ=E>vY(D%Ho%ldJgxjUcc z)$1+3^U>!SA4eWXf9liAn+F{Ze`9s^fs4a09$oY3dS2E=`^r}{7;|@?%*%Rd|G8v< z&PSg=Ty^?*zU9@!p{K*+Q+eI`sL)X6W)ThJK51-AG$Hk%Vd3kTqkK@eU`7|%@Gwsv)=<|%LP9Lv7 zdG&DU>G;EKA9OhS81skMI&8TeV~E{^{Dy7c`_KaO+0oL|?a?`rMSdF=C!t4<%U zKY8_V=;`>wZ69qs0TVo?3tVy&E4#9!Gz4 zeBieZhrhA9^7?6?>Z;c|_1%Z`)+6`*ne0(vcj(#= zp89ln^zwLpo!pQ<{qJA;-^Y9?zu%bXI(a34cV;1+D{-d8SCi+*@#;9fb@F`R)8n-- z9QEnC4|#s+)T5VIzvp#s=E1N2ev#k(ce2du(E#+p?_cx#A8THYSI6Yx#Z8n%CI@=!4($^7}b8FUPCn_}0nuflsf$_JyN9UH2i+ zZ|k})UC(R1%wv;8zV*39&FfnM9FnLXzK^JRHQ#YKe5*q*-@NvzJ}zGS$KkVe`o^Q{ z{puQ-$J&V-C-TMrC-Qs3@a4?qYl#OZ@~z+fYkpnN@p$@Bk6ylc?VAr=yzT>s&(`U` z`=RSO(W#%dC%((Bp22-6aic^WeOUMQ4B)GY-%rHv{+jPN9KO||mv3JCR3Eo_`WW-q z{nFv~yjIJ6KA5;+&5b|v+9QCkBp#HA-@cpgI2`?{LoeUF_NhK@^UPz+U-wIg+w)pA z^I0qL(=|8#%xm`m_D=j>B7XaBzTlcsDvAW$49lv<$nS;L6r<2FUq0bm5o)h#3 zW`GAL9+HUTy2QCWpsNzEPE>DF{j`n`eD&zsmwM{c;pm4?TsnDN9QvLY{TZ3hS&3)Y z+{_E-{(v4#d?-=9N%hk@KJe9}YhUWAPlux)K5^;fadGH-Ui4RGJ~t;`S93EjoEHOn zJ@KVP^(NI%>-fM|kFI^Gr#>Bye)z@?>A3kyMa9W4k^ExbZIxUfJ zeIB0Q&Fk6#?n%^-{;$jL{5W16$G1+N4}ALO@i$f%M?ZYx;^DLox97Ec=CN-g-}>C6 z=5qaQwT@o-v)oAw8K-|u%wf^WX)e78S2 z^Uz24-8_27w z;cKiePU~=Edt&|FDE)4gxKknyf7X-l2XIW{4-@g*H$IL$jycfrf!{hD{>JL!=!Z{S zb>*8!-}Blq^VvFa$3*q?VO|FXaBSi!iF|Z__&D-7eADrP-#Q%r#_Dz-bo{ET9=*K! zJulzMx6GV&uKDT1ynLTOI-t`M@w-2K9C;kR>G;5J9gaT7>f-2!Ph557n@68u|^V_MLvWnggHw_+EZy&4F%G z`%#a+`FQO|AL`TL(aZB2yA$g#{UPc1(8PTbaois`zJFg6;Dw3mO{yO{_4(;KbnQz$ z_363~d4B2CrI*)V&x`)-%;%iMQxb8^3+JJL9!|U~QN2m^L#IAJU5BoHsi!_&_aVEVj#tO= zt&=x5e0u%0FC6vh^wB(Xpu?k=$4mPY_s3l|7so%9-*KnP9NM4n$8^o3cRU?Fy?pcd zF?T%uG>={$ulv3wKd8@-@2|LTf+KjkFT-1^39{qh zvnEa@=D(-4V&?kE#Pt$4NL)K{r9}R4)WvC?JYVY2D6sNIO@~k(97e-=EVEe z^yzQ5#CIjmnK(nD=lkjbd^GXXiR&h=k;sqZ)p305eJ!qhfiEOd0ZU& zv_Em3OpzbvNOV2AA8=R596pig`t18-;<)e}*SfBYORp{-Ut@LKKOJuOJ!5{DJ@Flh zGbO6OX8K(x@#BeWCDJ(#*Kw_rR~MHaul?YtPlrPe9=r+w+<(^Lbn1{E6;I_qlnk7QjamH%fG$ z_Wkd8bsXP1c|P#z^@qpbSX~_b@QI5j-#q%Bm*<4_dg09Lt%+|*G_PLwHw*B?i62Ph z$MNbozIE!i&hrDWec`B2*L}$IOQ$Zqyt+NFsWK1yin;T5&pppm^I9nY`-%1QchAFq z&N^Nl$G1-X)~TaEJpRV&;^~La=E*mYzUSrqJ8$!4Ub7@llW1OjJ~s^TLy4;->d*1& zIKFl2x6XBe*S>Jnr|UlC`K42rUS8dv*R+|(%!vyoy8o>!=Cx`7A57dN(YnyCy$Fm-|xT9lld;1IW3d8SfcATe;xXKZIO9xm-yL4*R?*>QMWmC^6KKy z)8X;WhwFr{`@rLq4oCm`!KIVO#i8#xyN<1s@6I5XO;lGu=IlDRu6`zS-9C}8?pGak zo1+hTb#dtF@c8C~FFG9kjQQ)n>GXxC9=*K!J!j7&`<+EH$mJ68^kdGRSN2DnXTCcm zs@MIhhbNDx4}R$7)uY4VbF40&{`hU4y#Dn=udaO8rSCa=ez`y02PCET7 ziN06*Zew1)$7~zW=M#O$^xcMUd>nb4*71SgIvoDS>UJM={Hm)Sy}bJU|L4McwEN0+ zxk~2tfy5OPea|y@@AG~C>=wb75_eAY-H%`OapiI8_@kFsm#%%{sZXa*Tpagf>&!!6 zt?N0dr++*gdU@PlXMGA=fnNse#*~-??>JL7jholKYhQchbNDx4}R$7)uY4V zbF8kuaB<8LulvT+7abnGJl-_BO&spqfnD>%MC^ge#a%zc+c z&tK1L-(&dagRkb&$@76jPp2Q;_D6@KzcGLE=F$D&_1x&pfsQ}(HZSilbn>`3^v;9( zz`ANZe18%vCwgD;5Z ztLw_+;?Vb;T?fv~ni=F;iK`~sXPR?g4|`?4dnfLmXdkK%_3-5J==hLn`2!)WHQ9=mG2>JbSpO5z+XUBCM-{ZdjdEP$H^E$`#x~|vtdcC`EmdPMj zN?kV9`P7{Ie6>gBx?k#^sm`zb(Zi>Y4~`#redjf}_J@x?96tH@Z9cghCl@}t@cMLn z&dx!eFVDw{87MtI&DnXapPzj*-=Q4z@X3i!4u0VE>A~UQb4XV%ymwhy@WkB`sB;r0H)^}L+#*T{Uj`T=PeVZu7wD z)5QZ1$1mRY(>S^K58lJpWr|PTQnff2>>i^H{%kr)FNKrw*-a^zd2t@X5;$yuSAn4iBG0y83dp zPkeOEL9W)-Z$9{pVYL6ZKlyqFxLvCC$9mW(x^qG1cW$cpfj>HQn+HyxE*^L| ze(|=S#>vH}bq=oc(v=4e54=9!p0oSwzU-Aj?w#s6_g5l= z4u0VEJvVT8_#D#hJaGKtqiYUwwXQy1Jn%hd&x7yYo*Cr+sr#k6f9C8t@V&b%^SvhZ z>Qwhv4mxz32Tq?Z9(Xu@@wOkh&Vi3lIJxn5uEycTBY)zL)>-eR=l_5V^1xKzMeA(8 zM?VVTk5hd=`N5Az9}gUTczy39T>HXD9}b^9d^VpxUOe!-v^8cli#@-}gz~DK%y6l$tj`Kh58-Og*pe zhx zN!>kl`_zna?2PC}#=*<0?RQpl;SNj|1tD8b}bE)SK0KcwE1Y8~K@ z4&CN~)2E9E9*$qU?FX)N;NufcZoHkVad`2_pZKHa()w+kJS&4dJJmVdbJ^?dy&=3W z)p?yC{CM>7z|n`-w{F9=FMRaj@X5nx^XcQo1K;~g`+n!E(=(?Zq@I@QdG{WfyYqo_ z+1+XUG4-xg??u0F^zrKBm7hO&eY$XX_#M*4CokX4*ZJ|uMHkN8IzOC#>%#ZCVIOLJ zv)-JS`*MEj_fu2G?1Ov#dLZ}X!PH-;@<)em^T6rT#RCt=FW&Y8hew_vfB58=$NGy; zzxm+%J@9+c_cFk9QqN62HPwE^{w3oZ^Lx>4`NRFGe@gv*s(nlEbLivM$14|q@cMM& z@bEjN+qvNQ#b+M$>B$M#^TJ~eaQNW$@%4LP|0fUM7v#QppS;)k^O*f-=XxmjW9Yr7 zhtIlzPY!O8ri%-!II$_n&{x zd>>A|HP!DYa?r!4j}ML?czyd+I6Qm~>FUeXKJn2t2f12Tzxm)3f3#0=ZvJ5gcyX$I zxP7~Qiu3d%A$l~`{=E0|`0?oDkq?d!{EfrI-;i$Sfa8}gJ$QZk{ao7r*r(bLUX{6B zoofG+e;l(v>V4?bncp+1_DB5Dq1!xg`gHNY!|{u^{WMN4KJ9zqIxk&$;PAlf7 zzS=+8*Ze30y(ZQ7)tr5Qdw=xT%=g(;-(UXd&}|+#eY$wy;rPYde&FPRx1Zw|uJh8B z2M!;+KE9r__sH}AlMM3ORO^ZL$DI3p`g`X3T&ne^*C%@T^zq5T54^tj6AllbL%N*@ zj$eFq%|Wi#)yInmK4TcIv%Zt|8P{cyKTh>sw9fi|+K)UR!oQ{Z&hm|yPG1kFPY(|~ z96mnj^8<%Rjv;?|8i%)^OZ&3RGM^h#f1Zku&-{7JzU_(3>E+ayQt5RLdieD5$;%JC zK0P=*d=BZ#g%=Os_&PT}x!~}@>*MP=`yTimB<9#4_;sX#2>x?zRSLUH)W7Fr}~}5``_Qu ze}?eYRKKV2gCCDR9yt2&`o70-?F%1$IDGQ(*?jtV@xb?U>HF$C_RGxamek8rovZVY zWBpzHSLXG4>XWI?^>Wa|r;iVgA9#J=SvWj=4(aO4)jsjjH3zv`SHJn-d(OU_zGJ`2 zAa6~*B-MGsocsIv@67d$)W=etXXK!VPahu~Kk)j#vv7F$9MaX7t9|05YYuX?u72~u zC;sUDch0yi1N?QWeT037_uu*B%@DnnYJbuD5&U@c@yG|q2mZ$4;crN{bHMRSmma)6 z{eCX3`_|XrXI_6uwVqgi^5?N$um795jUNH)O|MV%@af}|gCBT(>pvVGK8JKW4;;Vv z=$eCEt*eh04}AaLXkBq`_*n*dPwIWCe@wN$c)qPa&KLhk!++P;e=m80%-=dB7hOJ@ z4^E#x9(XwZ@wP8G`QY0(T<4}M51cvS?fi|yi^rV$-!o&sY#(V~cW>^;gQ@qY-j({( zRPU92`Ty)21MuG+>Hl8G3%Q^C^TAj1!s+vY2Odryy#M1b|K1%u@_i6rc$?3B{P$?= zgW>R)Tjz!|-{vzfI6UzBc-@EGmNDNM>#KF~p#UC8^__8@?@+I^lSE*mRNo=~=+JE* zIDNW!;Nkei+kW6W2R=UG@X0Ta?*yDaUOezUXU~^&(H$A&pHlyv>i(Lu=f%0{y*zLK z^WTMvAb)h|HV>RW-J$1{U%c%HpFHsG7ml9XL;u|-eY~AJ;~4F4eCK?39?l>iNwx2{ zzv=JXq?zN#Qk@U@#*arI4;+1XecwU2_Jxl=96ovYY(9Owc;NfFbbkC@=JR;!6RE!A zzWe#}nDgbkxdBszc*<1Y`Q8`M!>5l=UVh;9>A~UQb4XV%ym!AqyFhtsEr2Of?;yzK)Hj~qk(@HNi9qn}IX z8SB4w_Q}lYvD7~r%w+LJRE;`+Xr0x$HyleK6ri4S>lYoOZKBLWG?Tej%BdcU+cBD{pt)M{6gx2 zsn+LS&*|aQ$0r9r@cQ)N@bEdLi%&j&o3C@@lZ!4MczwJ*XX~`}_1_ufc$xG2`RC_T z&AHd>IYPWp>intGr}9S+pFTb~e&F@3|8VUOAALA{^6}ez`tr*Ik58XIJ@|hA?aTg~ z0e+Bb-L+18|LxblnE5S`YCZ1t8$TX>Jbc6PfxmHh_#4vg9B};7r3bH1Kk-KIzwiEw znbRAo6J)UNllz+Xn0>*FA)GyRkyQ7w?>im3%_E0CT|Dq`{NimtaCqby@`q1;dE~>V z-+b^r=f7ug-bc^#hcajDiFL}HrwdWPZ;R&d)|+0(=+JE*IrQn`frsN4Z~KA6BhQe( z&J8CQKDzMw)`6b0eTDCV^?Aa~d7@PN3Ujva==FZF2z)8kzJotHbejiGpDrGFIDYZA zAGpqek54#!^2_6U1*eY}4}AacZuS}0E$hw1neXJOUR| z7=G#U(R^_F^zp#M@sGEC!F3*de8S-~54o(za6KnH^26bQ*T>uU$G%f;zNg3y_(!KZb$@z)$=3?GA4{apojP+Wzx46yB-qRbCbVu z`t<3+o2z|?b-?>JNdO;Bb>4yR_j{?#d+Ai?BEIqC(Z>TvA70=457)l%(TBq)51-Aa zj~5SoVvY7GzRU0CIh{0fnlkmnslLnBb>HpzLby!oim8jI`fkfXhi>!0>C?pn4~LI$ zKKO#`9QgQz>)iTs!O?@)r=R$vIor?3!_UVv-^o(#%gxz-s{O7Mf#p)|+xer1Pahu~ zKk)kW;MyNP`fzgL#bb_*lV6_3>C>kN-*bL5gZoeF$1>NcQr$PtjXBR5qIpx7O?Ur=Ky5p69nRINuHH^VFF$K6%V} zt`PP2W0m}!UgxDlw|V5yr;7(3j$gd(2M&)sL;mo|FOTmPKK$=T7Y{rfzj)gZT<5^YCmcTc<*|;!>Ep!% z-}_+aP%$(Osbx!JY6@9$=c;(^`Uf;an@bEjND<@t& z{NwBV_~e4aXKwO0pFUnZ@EOHXD9}b^9d^VpxUOe!LHG2Q;N4#g=li4z_Po>(A zcrNW%`aRk>0&Aq&ugF1%Zu7wD)5QZ1$1mRY1J^n5@d?+t_2q)23$Jgz=s7#@I0sIj zxqd!%rc~z`b9T;gZd@nCo20Iv>YT$L9lFf}r%x9TJRCm0`QQt#bKv6>4v+lu;Dyu2 ziw8bq8@>PbllFnL2VlMSz3~3`esa^y(>mVY5&U@c@$gL_Uf;eH4iA4ry7=Vbv-$L! z4?gim@4s`Q^V|%X*Bq(tll$+v?DOHeA^uvb``FJ1J$(B3%!MC#edkR$JbVu6;**cx z=Ih+}M~IXg%8dbL>u=yeWy`1J9~!4JGXJvcmk4(ZB; z7msxmADsO1G)}*D;WNI``|liMJ+p4i6`=Er_uu)Z*Qw1jU*{UW@zUw*;q>X@frsM{ zZ~K62|M>WX!w0W#-Ryb6TW7x*zyhgw%nOh8_v@L{cB%CE!AqyFhtsEr2ObU|pY-{G zYybH8gu?@`kGJRLIrLn9A%OW)tq11iJ@LG5lR0ghYTe)mKOTKNaP;Bz>AEp!%-}ACA%i}t8cR$Um*IWGVN8e}sc=YkeM;~6_JmB!~H>4{EUOfEbgKIvx zo|kpezH+_*%-wx5FYBfK=hk_y+>gG0c=jLU9Wk0lK=CxI-b3EVp@#y1$qYtl72d;hLqYsBq9zL5-A1@yGo|kir zJgzf$_tU(bXWFOx(f1iIoxWZ^eR_D{;rPSbKH%`kG2{sb@Y#I&c=5m|*62LpcS`#n-*eyB z1v59lQ`+bH9kcf}zQ^B)kl!)ophLHL;PmO@frsN4Z~KAk9QgQzlN;~QJ_HUg9{KyZ z^!u4Su5-WKU(coA)!L`~*!LYToxWZ^eR_D{;rPSbKH%`kG2{IZPJ|6kt_`u&dJp2vm>dVtU>C$T)eeWT>^~ig_Nap5u zMYvvHcFer|?g)p^_4=-dqX(~V-GOUA_~^sogV)E`&&hxChv|}IhSVui-%a0hGH?C| zueDRxOM2yj)Mm^^Mc#13$cc?F$cmxXz)^Z{u1QuIKfe{DEKj{*b@lUh{f8&z&6n zj-NTblRiJLr{ns@>GOdf9$)*yLm#el==0mS)`jbNU7A1eE8jKw`(-t+$1@K(_8)dOEIeoIW4;;pJ;zc<94*4t;(b*Sc^$uOst^@22uC=Lt2h8#0eyrOLzi?KQ9F zcO4$S>A>qZU;Ctw7hn6w!)N2<#s`-;qy6e$naBRA-$~_*|HJZk-^t5EbWQ55seH?O zeg4j0>$x5u96fmb=4;=4;KkQD@bK9<`8y9>&k2rx#x?p~cF+7_ztrPW@yOx3dRgXn zb?Pru@pr!FcO4$S>A>qZU;CtwxB284^4EFc@b*=5tl*Ej73H z-Tbb@BOe`j{pM?*^zk;I9P&3`=Y_-D^V&Us*eCVdsd)G^uS+tYD^q__b8Fwt?>ap4 z(Sg@*zV=BUZ}Z6^fAe)-IJ`Ztz4M2oQ;$i-!=HIwk@?(|dUMUKeY>8H>l>%f2Yz^S zz{j6l&4))GKJntyZ$9|M8+|8^%6v{wy(smEsrcyoPTUxxyHg)deI%7$`@=&QPvi9Y zq6054K0b$ZI}aSc_~@B~T=e1e@#2Bc_(tCe_@Ae7YwE93@pvxryp(yqk^1kN^SJtH z93S}U!L={>=)>WWhfln4`grlc_q^aAPUErE$5Zi`7oJITewaLUid1^z>Zfsh;HL-I zzUa}1!y^x$c;WQ%;(_mZ!M~Bl`>AiI;xR8gvu8f@r_Nn-8&^M#;{!iExb`I^H|N1?`iV)2~)>QHRlCuj(o3{zb}@$P|X<+T|AA`cVEm8 zUf%YHhdx~A(dQSg^Xtpkxb_EUPVg^U5Ne+P$lu{+%)I23XQrB?>+rg+ar)+o7oM*B zf)@|{#x)2t%O^jdsyQ^j>+tYR2VTGV+9!Ry_}V`n zJ{u=DKDfjky^rr?9+RapRqFJq)203=IM3NUnagsiE2plLx_0Wksr=!gi>Gn=e9?h# zK0b$Z@yN$7UVQrU%L5Om-@5P_*XZ2#Wd87as(j|*_kPdi$1}ItQsp)$|9b>KuBYSr z#_98cAHMnc8`8xi51)AP;b|P+p4YGQhX+&nmh(6HyLr8p=XB!COCI^(PM;sw({X*{ z^!dOK-+cTH>Ee-xPrUf>G!Adi>yrH8x>Ua9ytL-^SmyD3syy;PUh{H29oIKbpAY=- z^5NreNEeSheB#B2r*U|#Tkw9rKRa{bn=d%O+uxUY$kDl*58n0o;OO9KT>GMn7atz} z;qZ2Dzr&xA+;sTDdu#IElS;RJ;&Z*e>+!+S#oIVMd=2U1X&m0f8?C>`Jmvt$2mZ$4;ZH8S`0&WXCtf)H=7aBf9h*O#mU>PqJvq$l zmdxj_)caHU=zRF`=;PrVjt~5e!^7W@Zs&mGmo7bcefm8wzmuPw#`&pu&@q|MS*djR$9r?;a&Ic#&V$eO z`mVGmThjRO!{^FVgpZq+M+>h2A;Knr%J^1Fw*M8)n4~Gw4pWnn9 zt-tVoZ@(>oYg6%fKk)ed`_1HkuI4eWe&Fcy(>ieNOFsH=okO2rIJ)rq^7Xvn{oejW z0QaZjF)uuR|DH1Y;18$L8&^MY^!aHWxb{VlK3wO}=NFDHyuN%rFL=LqzZbyEsd&r_ z&)k{M7gA?Qr8lmA;OO(yI&kfa9(}mZq0cWIU3h)@dL4rQWZtKbq`Drj>-`?SM9m8h zpX*y6jvjpT!?hoL^x^Qq>+{>W;avBru^0*!!et7-nOE-EeZN zKE8%@^_veqw{i6Q@7>AwQ0m`O`I5u$)aEvpIZTo|Z7Sb#_V3tu=;CRdK3{a;%>^Hy zL%Mk6;}-B>Q_Xqj5FMU+Wa=@g`={=b${!xO zcp9hA7ae$f?GF!qxXz=`FC0B{kc&Q?K3+WV<^;b)=C^w4#;IRQh1)grIym)vsk_x2 zU5D3ojnk)#7oKkW!9yPo54=9!#2KBhmdzhlOWiPaoz&%0ec$&?&Vy3Foq9y-Ua9=J zo{sAqr_Tp|czo>(4}Ca1^6-flP9HBG_Eh#SNEc7z@OJLylXs2OHB(nerN4Lb9+G-!>OQG(uEXoP#_7|=3y-h;;GqwP z2VNg<;*8!8&#&iW{Q&Ur=l$sC@#vaI`*uAY*Ef!S<2y!iB+55DK+ zJ7K-vB!E>?mrOOUUiZJBc^#1Y%~XC|PsjC*qu)5+4}9$l4}G}Kq0cWIU3h)EJ+H;{ z2m6Zk^3T3|zNhB3OXgueaa8`<_praSuBYSr#?fya9r^I_H>8VC9zL5-zxm*MUhcp9 zwow2pr7o3fUVT50&Aj$Y{Z^`cuBYSr#?fz_=K^2*!b2afbLjI6M;Bh7ZqIA!{9(n^ zucmtctt;lWd*-ok>UUGE3%%aBo{sAqr_Tp|c=_=0H>8V49zOBn({Dcbo|pUYzI~b!w{jo*(>p^zp#ahu5bA*S_%4hr=fipUtO_7Y}@5jecivpUn4bnbUTuo2Pni z?OXePoe<(5q@I-Oxt4WLvfE0wR#ONVar$e~Xc4?G+`zWLw_4v#!T{yI0DT=?k0>(lQ! z`yScvY?}FQpNdZ&bN0QmKRQ0dXQ$HZy!7zt@bUxfVfNfHpvwhF{oOxR2d3vhzIN$j3=;MK-53ldM3fI2y z(TBq)51-Aaj~5So&&%&F+otjLRKHjHJ;uEJ9&<+Kb7rdFG5x;7H-0?&cpAqC{>I_q zZ%DUu!0}6$9=tyN{LcYn=H?vjz4BasD}ZmN?wsoPJl8wV_xp2k=J>~c=5pZoIMBb%ifvqKB>E>+Gm<`KMz-g z_{!8vQtd(lKyI|q5b zJRkdJzV!GsXXmwkey)nZP!4+d$=T7Y{rfzj)gZT<5^YC!E}PJ)d&H z;e*%5*K_v$u$~`~xgMPAyXre^&ix(vQHXz%>bu+DX?pnd@yWptyuR-Z93DQ0bUP0m zzxe2ygIukvj~5So#y2{z+vnPM9vHwusm`&^pS^$maprn$s`D=2`0?oDfuj$vZyyZT zzVOk9!zT})&8LqS4}8zdIm~A~yMPrT9j*>meW=-hoo=5~0h=hnLCxprQ3PXBoXewOOF=8q2D=7H0viw7Q# zU%c(7adPp=H(ckXD-RqVczwJ*XX~oddT<4}M51cvS#Up>?@Zy0p zSKl@3ruFH>08UD^|A6mx?zfry?^5kk_`#1y9}gUTczx?7T>HXD9}b^9d^VpxUOe!L zHTn*E-#q`PW=^N4T7Rrt){TDeejnnyQ-{_ydibn+_~hjWUf=r(hlkH0U46OQCqBC7 zAXn?^Hy?b)H`@Q(pPZS-4^pi^)+^7Yeajy*&p)PGmwFw;k4GPmd~kf=ZyX-}hIBgz z9KUqw!RymcywP*%dAELf|1Zeg&Q0~adoH~Xz3w~|fj^~sANZp~w|U_7>EeNh;}>uH zX`EbqTIb+8FI{=y@WAWi?K!)@?#tzw>lLYvxOse}U z2OYZ21E)_H4?G;dc-s$L=fKA&oZNUjSL5*Fkw0S_t+U=s&;O4C_;IT5qII_4qrYaJ ze@pfKsb@Y#I&c=5pZe)iEnTi;~#JPg6ll^_=M~H`f|aU7yX_?Q$-dq^6ABRdeV6ANl($sZXcgpUN+Ny!v?I+!uI#bA@Z4_~^sQ zi5Cz5jWZ9q8rO58CqF(s@cMW&p3y$#l$yJIKh58-Og%5vx_)QPo1Yi*_oq@HNafFc zpxZof`gHNY!|{uE=zeq#e0<{T-1>6C(S_GHhr}Mu+4tS|_WI28=cyN^+K-!af9GEe z@k^->r#cVtM-QJqJ~)2h_3a1X@bEdLt1nmk#7Ea0kiTD- zdP(ZpsVQUkWS;$eyqv#3pZZwpeW`NM$E%MQjz4&Px^Q^-9nzH(FCNb?K0M~odEoGw zoBYkE-+b`qYQN$fbyFHQr`nI&zrs6T{WEiZHPt?~_qF)(=;M(Ojt~5e!^7W@Zs&mG zmo7bcefo(vS~u*Q>~HNOf0?=6l4{>%-LS9f{p-IX@Or9!6@PT-HV>RWT|Dq`{Nimt zjgyN{`(?P!OIIE^Jn;H>%?aN3+P>x&nXB(9obR%I&uf{Z?<*W0ynP*&paQOJ5 z%Lkl(^TGG?<+=7e{x-SqNVWc2ug$HW@Bf7Oy;SRSujlme>En}wA9#K53mhIkhjcp+ z9KZPJnuA=etB)5Ce8x9A*I7^P7w!z;cd6D<>z;MizT*AN_1#qKE8lqO^!0H1^zgvL z;p3A&KXC0IAD?h|;Pvq))@a?cZa8P(o;m#?^`2Dkx#!Y);e7p0h{w-HVZ2o90DpAo zHV>RWT|Dq`{NimtaGe7mpKx;H?Oct+i%0&9ZS-7Pzpaz^25?`hbGYa7f7aW~bHY^T zb$;;U(Z>TvA70=ZDj8UHD!% z>_e?@)|&@%10GEMb*gp4KDgJfi983XZ$*$lI&_-{PM@Ats(MYm;+_ox0T_4ldvBla)(^O)a@-poAxdpSQ6q5tOZ_AR~7p^sM| zuU!1W>(hn9!|#x8=Yr!GpLx)yCnsFb3y(R#;e*%5*YAP-pFDg&lpElE@?Lun>_0n~ ze{b2)druFabpf9o{J`tegTuq;kgi;K@t7k%IQivi9KFWT?>XCd+6VqQ^L;q=)>OY= zm~-zxKbq%$($qI1?b7jOG%oLqd`_ri5vx}!Y# z|2KyRULSAI+4t4{$-d^VneVfyzOUx&``h~?|DB~zX3oC9a?qjMJaGDS@xa6Ji?{v2 z$pddc$1hyxr7I5{K6rh6J!kKc=l}1S>vO5r6YGyT_xm(m=KASW>rJmu^ziB9lY<|4 zeeWk69zKV3I}aSc_~@F0T&=5*7Y}^KH(F*4h2;em(4$0vP$;PA*X(hh7!{?B$TzK)!62JI5H$J)G@WJcj>pA-#_#Nb_%=eYl*HW$D=IlG* z_m8RLch=O;rCQ(lqeHiO;PmO@frsN4Z~K9h2fqEnbzZviz~O_}mp@}0z5l+;zJLD= z;MLU8?-(elC4q zeaHTlIlZ3xWU6zu=d!67z{yS&9nZ{eG_7V0SzW>f2b7r1%rP^Qgegr=reLV8P@qxc_ zc=#LA?Hq9Y(xnHlPrsi_>%R5%znRzg0a{P2Kc358ufLeNEu3n->Gg>oK7D+0@B^=J z{fEQD=a6paf#Vk+U2~ADb@lP$fzKGmJfGGT=Z1fzF<$08Vd?~_))&`Xf1EF7ioimt zi={4-Y8{e`E+5SYr%xXbJRJXc+ZUXC@a-F}bJLXv&YbXe{>I_OV^00=nXzBCkF>A* zQ0~XXsUJ@LAUEcP)ckqOzP$I1i{`#8nYwuD(0{*z55AgLPJKRx{`(8^;B9~S@W?mh z51;wSXCI6YkGXYjIP+~j^Mb5l=UVh;9>A~UQb4XV%ymA!`C?b zj(#qkXRQC$*^dV}dFuO_o9EJbs@M0GGOy)R=dbzEq1!z2>eIyo563Uw_5+7Uo*{qu zA~yM&p1ZwrFF)8KWhN9q*{Ni*PhE>M>fjbHcYiX_j*nbpFTc0_<`5AF2Uj9 zb4a)I!10TZt~tony83wW!1tW37tS-&1n`;EsZy;c=4?H2j#(}9{c`Hcsn#hu=+JE* zIDNW!;Nkei+kW8i$TQ@xbHmAnk1o8vIrN;ZE8ZvX!RG?}Y^rs|oUJ?kUVSz5{Yt8J zhd(-Wn+HyxE*^L|e(|;+xXyu(PdI$?%VS-D)5nVkzSmjro9BPF%=PoB_G{MJe&06E zJU2ZhUgl#RIR8x94n~w!Y30z(T3> zr%s=0&b?lLE%V(zb=%Z+Qu(8YPahu~Kk)k2f4KICk3Jke`S@)tF z&yc^)4JQ{qy72nefu6H{h3|p&d9eV$lxkmL&h{O>-tU|_@0M!c!5$=T7Y{rf zzj)gZT<5^YCmcTccXk^Bi3vCkzU8X8KJ#X zcSyC5;g>ES%?GDX9}hen|9IOMT<5{ZCmcTWkjr`u*K@)nKO7!-eY|~t>^tS=dxhMO zB~s^3ojKM0>HQ^N`$TBh)GbmsNadG4UVXf9{K4y+D;yqvhjcp^9KZO?gFZbu8)t6v zH%^~EJ$Q4q@30Pd-_53>b|MFr21~lL5FVh!0FS)0}qFf zZ$9{f>m2y_gzMb;a>3Dq*QcMcjpl4WBM(0-1-M+QeYrW?Pqp9uGT*&Y?c4dIhfg0L z96#{-^x)bbKKgKS;l*Q)jgw!V#_7|i2j6p^Gk=&jb=lOFQ{6YujX7_YIc}A@XX^f` z?rT3+bm%sZy!v$Uz{BC=n-9L=@W?aduXDr6g^wP*KK+bu^gPd%KlpA~pI6D>@yTP( zn`fT={WzfJ-g)WJZ5}!F>EeNh;}>uHfx{z@dEkY^C%-(tS8)2x2jAZ_=g!qK*VR+) zTkMB?&zwgO$~+HFwV&yI5`H}Tc=(3n1ApW2@HeE}IpFxEOAlV3e#S9+E}d(vd)AG$ z16(!LxyN(q+|%pY;hEoosm@9K(V^QsaQbxdz{By2xBb9%4t#vV;geq;>nNN)UOe!L zKRTy4hsxu+4Kv@hQk{RCmptEnE~WSF2pyK{oYdzk`gry6%EceNzInmn;de+^PP};d z$JhDs$pwec+~jXQeY|+!Gw#uQU?1*Wvt9sSOzGS_2L z?bmyMPY<6yJ~{Y-*Z17O;o);gxAVa9i;u23$kn>~c=5nnCw$lVgWo9g{c@`F4t)DN zHgi2L)wzgo{CM>7z|n`-=O35un{@agu&%7ra2e?M6{fOt% zex=`|?`3X>q}s2@L5FVh!0FS)0}sb9-u466Iq>lb*SYoOf};zsZ@uU_JMTCLt`oo} zsq3dY$C$Hoj&tJ?nd|pck4|;Y;g1g8=7H0viw7PKAK!fN1=l(7@d<}VetGc1>Ep!% z-|xTuqEe@z&*sx_KKP7d z^!_^+I?t^ez}HgUC->iT+2_L}Gq)2`-N$}D=;71HXDpO44;o);g7oU9mHect) zCl_5j@cMXr&dzbxGwac20pjD+oSh?ky*e>-rq?;>;nT+_2S4!o^x*LDIixEWUOd)O zd~ov1(>VRsh41&@ImUWs-Pk;Hc7E~xJKyv=byDW*T*EhBI(~r%w+LJRCkg>GK2E{_*h% zhX-CCZ_mqf=(*e`bJ{l5dSG7O6VL1EnbR4m)(w8}eA4F!4v!o|{_r#oZ_mp<$G&IF z%xkMu=X3M2zp@`XCG$Ep)j6JT{CM>7z|n`-rvul%@X?3ECl8;^r;is8e9z0dMIP6g zyZdQg&NJ=P{pkCQmrh?VpFTZ2@NoR$Z69!W4w*=lUJ9_cgx9XJvkV$CQH(-R6PQr;7(3j$gd(2d;DA;}cG9yhHmCIJ|h|@8{C* zXY#nt{c?Xjmws1kpYCJdcf54^dinI};em(a4{!T`!z0I#KYWcdx1N{(eQ1k3f7VUw ztn;;b`QMFB&U0rywf^?G8$TX>Jo3TufxmHh_#4vIm#2NwrPnz6-a~lnk@tSb%+2qL zaJ{~qn|b-&5e}d0^<57~4_@E81J{1=(TBqauaB>vlX>HR?bLNsS4f>N)pN2{{)G#N zryiNQPwLjG{J5Tu>l>%f2Yz^b?F$cmxXz)^FC0C1efmAGsWK0K<(oBspStF?a^@ii zzZ>Q6tJJ()PsjC*)8_*}ynO8o4}G}Kq0evQS{JV8^>*GPe&riKb9yIz&)3|UhaCJa zoWIXg^Kv~M*Edd|5B%`*wJ$vM;W~#tzm02MxSrSJnFqh}J)6HjQS+K2^N@qzPv-Aa z*1TL#$MucV=L0{yeC-PleYnn{&u`;e7p~`ZL+0_TRKDfBz2^0Dp1Xgi%ER|N`8z+& z?>anibl~-yuYJ1<=NCXQu&tm z#hPF1xgMV!^x*ZIuYL1@7hmVV!)N2ullCph~3Twa#>U7h;NR6KH6_nyeyo=N>z zD*n#b{I0{pHywEW=4+qy@iw0vL;gB19NwPSrJ2uFskhYJ_%pA^GoPnZU$42f@8)+M z9{K3N>o;Hfq>s1x%tQX>>%4GydtR4hK3AsxqUOe*c|Df-JeB%d&8>Ymzw7YGM+aWN z`PwIayv=7G@;6`Sg~QwPx+3$rDfQ->8-M2Ym(1s%sjt@D+PCZJxV~}veBg&S2YhnM z)qHs5;S(=D{pN$uI7Z)z8}o;|Qy)%!Bo!Zh--%Z;r?KoiCQbcVD!ulHhc2GR>GMSg zUS52B4(WCtIDYZbGY7fo!|CJ21K-aj{7aeZ8>#=Ud3!GL%$#}7o;pX(d0hQ8jt~6w z;M$jb^x^Qx!zW%ieY|+!dtUI9<~f@@b&6Cx=7ndO%xC%36>4td>Zfsh;HL-IzT~41 zhesYh@xtlj#RK2-f}cI}nLl;znwxpy*);RnHg$`d+qn8^93S}U!L={>=)>WWhfln4 z`grlc=QSIxdvLB>K66?>b%WH^Q>}Z=yLaYwbm}pw2d9p!KR9~)(Hr99n-6*M>C43@ zJY46cCl4GRczwM6T*?E#MdrDA>Kdt@%ho+Pb37^akeaLO>A1de^c%;&eC-PleK>sb z@Y#I&c=5pZyySu3D)ZSab-_riHLm@^c^=_s%>3jshnezsxb-qGIptZu=IA=Su4|k=UA*vg+YcW4jcY!* zUiUu812;i(^27h9@^{~f1(I`-R6hAxqvp{3uEWDO9eDlbYoGM-;%onS_-vfq_~80| zoG0^GE_LP9byC+(oj28Uwq@qBSL*($N2DH}x>YKFc8V4 z9zOBn!_zptJ+HU&piZ2*@Ga-t>6_PFnbVS~^2k4T{?3o<>A1de`h4JrZ$AEpbn(c; zCtiGb8i%*%^;qWdd@A2^K3?;hJoA`7RUY~M?*ja|o{sAqr_Tp|_~zqpNEeSheB#B2 zr*U|#Tkw9rzb|v)TTZxtdoMF5IXZXqO&GlE@xjr-)429U7cV|M{KMhx+^1q)tQRaZZ zedEWYkH;L~_`u&dJp2vm;*p0>yma-O55DK+_w>6mr~6as$zfivXHFlaemL{uqx0d% zqmPGgI6m+<4iA4rx}5`#U%K?*_38J#{7!yH=Ja4H9y!eGwan?g)QK}M{GAU!9(_D~ z!|{Q?ad`L}(#0bWpLpr&Hy?bjLpNtW_omX}AMdM~&xbM}x}67~>-9(1=fC3yM;CA7 z@bERHi>GmThjRP9|IwO5=k|N~WXX>gZd~)wgO?XSzW?zP-I0Fgh!0*LU%wyles6y> zxt~kL(>ieNOFsH=okO2rIJ)rq^7Xvnr^xd^W$K4h@t7B$ z6*8X{Qx{LAH?Dr*==0M$aP5m8eYnn{&o3NZczyYLUhs2gK3_$=A2)5Qx< zxBcLu4~GX{A8&u(_e{QnQoo&gMCx9tzVDYM=e4Q7Nd0;0<*EF*o{sAqr_Tp|czo>( z4}Ca1^6-flP9HBG_Eh#SNEc7z z@OJLKllPF+LsR!jrGG{8UYGjQ)GJfrT!+_njnk)#7am{x!9yPo54=9!em^|Fo{ytz z4*YpP`gy#m=Fz@gPsjC*qu)5st9A1de^czPEe@z&*sx_KKP!O`|rLTn|bY*`mI#+>ic<1 z=5=-I#i{bSo{sAqN565N3w-Sh4}G}Kq0cWIU3h)EJ+Iv}k9||Wo9g|yu9(*)na5SB zze=?(^m^lZI<9Y=J|Fnu<-^C{kS-p1_{57(zxm*MUhcp9_MMua9Ol*c^VXVQ=W{(B z*Ef!S<2)Dg;WH2Uo3C@g@r#cxyguEYm-pPc?6p_MBT+ zf1A19o61+`r9-!Qw3UV8ZS@yWptygofRJbVu6;**cx=F^v79(cO?tqb3C_WkmHdJoPE z@Z?ncb#wOp?Dy}1%=wN~`+5H8;nT+l#}B+dJ-GIVk3O7Sc=34O8z;X!jnk)355B)= z&Y8}Kr)93Er#ff*p7lBNuFUi9ROfNN@#E3Q14kcT-+2|Tec_`Ihff|pn@=Av9{8S@ z-(Akge9lbud!^rF%**dF_hdeQNcB6W-*@=Nk4GO*2?k{e(BPK*QcL& zqyNr@bF}x$b9r&*_QTZkQ~jQ2?#}c5{yZ9?M^Yb5^}8Rx^zrKBh2sxipDtYc#77@a zPP}-$kBu`Axf<7Vq9;E-Jn;H>Gv?9$%*$Fb^br)K6roneWQm@ zADnYZ)4eZBqjIRRXd>U-+@>-QBr zet+nF{Qa5xL#e*MzSn+_;hzt_nio!=4?OU2^5AWMaCqb!@~3Yeod;jf4bB|k_%mw10tbMD*2sm@b<{-TFZADHaE9}b^<{0{NS-+A!qHy?b@*}2Mj=+XeLPQ5JE`P7{IeD!$d`b_E*sm`zb z(Zi>Y4~`#redjf}_J@x?96tH@Z9cghCl@}t@cMLn&dx!eFVDwS0n+2soSoPD`FT3? z9m+uupPcyQ;0Iox9vmJ%hjit_i>IG^x#9KYqDv26pMKBT`O5R}?8r-ug~jz7HZ1Frq!C*N?Lm##c; zc;NN%_MEND*2xgu}z< zkZ$LJ;};)YbC9cb_3`3?&lpC(i+b;T53J`mWX`vz{wCG?V!ig>^}7FZgx*PgJJow7 z7hOJ@4^E#x9(XwZ@wP8G`QUwr_=fA;bmf6FC%ky%Zya7caOUc}X5F+t{WkaEcd7Ot z@V(CcCv$%{)jowE{CM>7z|n`-w_d`vFMRaj@X5nx^XcQo1D`RBzJuO3&;RcOxI5MQ zW8JcD^n3T;%xf$^pbo8T^zd2t@X5;$yuSAn4iBG0y83dpPkeOEL9W)-Z$9{bF6~eL zkh%Ub)%s(-@?6@tjGuWzDWcp#cAs>UsBEdLMe-nI!Z3NUHaNKRR@q2Tq?Z9(Xu@@wT7F$;GF24zBakl?M(F zyguHZv-|75JQ=`WQa$J13v=%K{PE0ns#Nbmzc=*o>En}wA9#Jw4ICamhjcp+9KZPJ znuA=etB)5Ce9zhQ;Jf!k0MDjAlj{DNv**C~ZpzH}ld03By1#PJq1!xg`gHNY!|{u^ z{lIk&e0;*mjkj|(4lf@0d!6-Odj9{Kx&AHHchNfA@6o3+&uLSAKl#CrM;{LyeRzHE zBV7B!M;{KKJbX5vK3+WVKGWuQN9J;G>Z7U8rT#tjcly8gWoekk*rEY-hPVY>YD zM^h(Gm5VMP%?GDXAI}J%f6oQ~c-t4g&V!FnxX!OH7o2&~?>RIMFCO#i`*T;#o$r6- z@2{jjoqB(&`}08vJKs#X53{CzBGtb)hhO@5_3^^FFYx+w;o2uY`fzgM#bf@BGY`2M z*K?vLKR!J0`gk+m(LUwQn!9{2ugsrCF2^++4tS|_Qe2RN_{xhe%zeY4~`#reft48JbVu6>dVzW@zFI0xms7h`QUrKxhHe<+_>*A=kL#_K9+i4s`X~P z%(I`5S@L)P9>B@-&l9G~MIWy|UO4{X_36Ul;de+^PP}+LzxeQ&L+63RXKwO0pMLYf zo2&hbbJRaG_g7QxN9|wXov%KVIe#|QKDGC?`0?oDkq?d!{EfrI-;i$Sfa8}gJ$QZk z8OLbduy3-zwU7K)fUl?8H(59At9t+X`OI&&RQoFa=+JE*IDNW!;Nkei+kP4+7oYaa zaGjT~JaBm6_3@e$yzjMr&1;#f? z@8`>N?Ror90Pm$*f34T%*3b96nZp-St}?8r-ug~4j-TN`GITy z`1pjw1Fw%aV;HS_)(z+EcLEqcAN=uBz2}}w>xJ|6Jek+RsSBlA2l%5yw|U_7>EeNh z;}>uHf$JRj_=J-iZ|7IZpFJ@4Klb9X*)E?Y2jTO@UX zRPRNbx?vw`eY4(7l>0Mr z>RY+cx?vyO>(`fZKbB0LE0sSwbejiGpDrGFIDYZAA2>Yn4Ee(+zdY7oeEQ7?pRtYJ z1HTu&X^sJWB=x`fyZwm$i}%3qMRR7J{=J+_$=T7Y{rfzj)hEp6RmJpa>WuAfe|o>+g(x!YPY!#a~bM@~p^_}G#FP**~PM;ngcsP7~(&q;b zj~qk(@H7r@KbQ7pQ)WK1q|Tg*k5A8~ecOte*M_MZq|)mg^ziB9lb0WOeR^3z)*T>g$_C4@B$kdtdtf`+%wSJqk?||PwR>^!fO8si8^_@RDbejiG zpDrGFIDYZAA2@m7+b>+_r7I5{K6rik`~CM__Wk=z=K9%Gzms_X`#bvO%=0U$eox^C zKOTKNaP;BzeUIVV7e4xM_~hZU`SkJPf$!(i_tkgo^O@6ZsUJ^uuJ&B^cX5-Ab<~diY zeT037_uu(rv&?bxRQrqGkKo6nk4HW@KJYgV4}U|todb?vy7b`n>GyMK-M7AeG4onD z)p}z6@m%(L{q@Xkhg9oLuTS*w>En}wA9#K1KO7!Dhjcp+9KZPJnuA=etB)5Ce8xBW zz0tbj+%QuB3#BfWx=5<^#q(|balTkTbKX96=hPijtwVCr<)iuF^y%Y)hvOe_`+}1X zzJ0@WZo2ZonG@d5-#EN@%qe3U^FG)w+eg~hEt>nWWa{Fn3#87FipRdZ_l-M6@SCZ- zq;8kGZYuwL@YTF<`h4Jlhm!|y`-8(H-;h6i<|ChdFg`ry*16%#xB1Kq4iCIOUiZN| z=@Hks?Lsk^6ozWAdEeNh;}>uH zfs+Tm{ld|cdnkuK-p+0Pw_ov{^W9k{b6qyoz8}87b9-c-d!{-c@QojjJ{~yw@cO=k zaP12peK>sb@Y#I&c=5pZbLspzU*@wy>WZnp~r%w+LJRE;`+XoyTIfne+d9fJo$T^y*O6m0 zw{NFfpL;#0hfg1$9Q?rRTbJPQ@HwR0dEofPN7o$WYF&N2c;I`^)(hvE)iU2Nr>>l8 zJuzqNiF3?BneTC_`=?r`hdEoTv;(>?b7jOH4!z0g-zs?OO7e2c1`sUDcwyt=e zya!*+e7}-vT`_0tPQO>*&3wO;YText-ID~@4x-pnVH`YQmx0me&ffZkB4tK zKJYgV4}U|todb?vy7b`n>GyN#yT4xMv}x*&sqT~e>bbNpI4bixKK0yG_p$Fg9lFgU zhdy0A@NoR%Z9i~$8YT@IB|XGf(fM=Xt06-Fjl3GUvlH&wk&|%ipax zy^hhL+dOjU)5QZ1$1mRY1BXYRA%C44PA+_O;q|QpJ!kt0-vjIO&YAOWsrD7-Y~RuA z{rQ>mMXB~3{L!J?JaGDS@xa6Ji?{v2bq;)d!r_x&9^WfCeY|+!Gq%zH?q;81-Ll?% zGr+x5cSyA#v0mGc^g8y#%=wDcb5iYN_@&E7^TFxU#{&8Q*Lm>q35U--7z|n`-_x{7RFMRaj@X5nx^XcQo1K;bq@AB4}&mO7!rtXsJyKG(e-99aIx-|8w z)C*F5x8EeNh!^bxte8F`Ne0;)nZhg7n=)vpL?>XDg$ivTmneSey_T}bm zKh=J(&U`OVwQuK-9zK11aQwjQ(}Qb&_~^sQg%^)GHcozd8mCX69(>Pvv&?a;)IC%8 zPj%lsH|Bg|=6GuAWvSPsy086Q(V^Qs^6JyY0}qFfZ$9{f!z0g-zs?OO7e0FM`t*Cw zn`fTB8`kFoYVPuw^GW~5*j+~dSC-8J|5G9;og$%hh;(C7l8OR?2ojP?gOt)Cpmd9L z3J6F@i-gjMfP}Q5bczT_oVA|se0(uy?X~dD9DmpBnYpgL_r34?%X2XE?C-}VHTUk9 z58u|&hdN(6_;~Wt?L2sN%*Qu$A@q0;HmSagO4XK-Ohv8ebAE=kDmVZV;#j)r%MMvWBcDZ#W_?zt~)ry zeS^+F&P$%}K9};lK6CzE&^f8kRs89y)76)J`0D0`M@QZvUwzW0BR{?FpPs((=$V`T zTTh)X9sGU|?8BXF4$S>HG`LUD{@QzB|Lq)fP42^u!7GCH-}>Ogw{`H;`O?A1qbIi< za^ZC!^yI|rzSZ@G#}8kff6v+T;=cSMb3G_%KW_hS&iy?6A#=ShXusb3dw%rP>FGlr ze09$a9vwM{e7he!dFk;r2Yt1#I$b*W)(PJ=`S1_Pe19Hv-ofvDzt3E62s#(ZO`ncB z9X$T{>hj}tE_(d&=;=q!)>EfT2fv?7`w{P%_vEn5Yww`_i09INrQf5QGPlcv_AC0} z!?$(t)cMlE$CHcac;aS zbGg|KY2^$X&vwH2z@&0bmZobuWnz8M@POPUwZnHv-Q+l55J#F=R)VX12V58gYJ|2 z@44*r;nkVjAA{~=KOg+)snatTdGOVpH}UAmIpj-EfAY3o_f1bpu9=Q>Uj7dGOWw;n9(E$X8!<=~zeU;ptyL%~Nk*{C@wP zW2|S^jiWPX=NIq4^G&Z)w`RW1HFDGC)7Rsv^P_{0Cm-F;f!F!z$%#h~U){Rd^TM~z z{xWkqDM-h>=vaU6$(;TiBOM*z`VRCp4YoFr@MpJ4SDF(QKy5)A77mhUgx66ACI1XamcIMZ_nAH&bvpXvkFRbXcy#0&^3?}jI`Y!PYdyT4mvzy;^7uSg=I%b3m-W*A z^Uge1?nmE0x_tV2xz+j6!N;Q~Cx3bH=;&i8AD!mW?RnYf*!LWpc^wyYJ~uD>EBm3_ zGp{>>&hc{7r=v~>k3YUTAH2>*k3Swg{m9vR>U8Pg_q?22^y4~ncR$U`d8Tu^AAO(c z^6BgKr_PTKKAwDZI|m*eeGKKJ*F1CUc{z9c9pdCXSEmG>x6R9W-0u^A$#ZpI&^cUg z`gGLk;PJ;-=Y!X|=<&y+ryn_6Pn|9u{NCsKozlL?_uTjOSDBmNDeZIpj@kPf-{X5T zKfhz@gAd=70D|HIKjd5Z`*_y+1W`^SdHmuP^s!UVeAPqvv{c z*W>ZSSGVrqbsl>B@#x{J)9dGChC-G`-(NMpJX2TkoS=M{iiiA*Yk0G z^VH>_kFUSZMTb9L_n|It^V%1$=kzk)8 z2Yr0~buK#m@wyLnd7Ib1cs;L4GY@(7H%hdWzdoON%oWs+-1FAFTHkeaFc5-mgB9c{~$*D=3%zFXr#QlapsI(*)-X%B|lSYkuwLdV2cchp*mx zom&pN^tul^ayC!@-49;R36Fn2myc(De-HjENJk%@%gHjgse^L_>34sv?>ahi^TAhd zz0S#>ZtLk|C|~!BN4MwoSmyI|@XeZ=eC9Q2<}+1r_L^JgZhhC$(H|dt_15d0{OPuy zdFa3Ox?eoHJ+DVIpQnOv)ZFAVuSqhWDTA}s+&XvbyN-_j_~5IzUgzXbxAn|J|E<^k z;?eDSJ(>Bu7JR+tCZBmtk@?ILoVDiGxn0l4_03b4gFe1F(9@^BT91x? zJMn7fGba0uPX{Lo(&O(tF>~g$aPX_auLb#aK018qG*4YFKKS~jC+CoF_k$-dJ$~k( zFaCJybm`#tbBX`C%yrJ-Ts3dcC7m@h&-H@q*PK6Yp61CxpC4Z5(jR|3I{J~5E}lAF zI`};={I6y{%LbRLxtSN8oid+Yg1gq-K5m}o$w8kVUgy#ue>^(+k&`Z-I$b*WJum$A zGM`O@8`j*+i_Q_5&xyffYHlAlPxIuU&kwJ2>5o4i9sS5j7f+oo9sFMR@Lab`=Jd1R zLBYL(*1gufAoIF5cwO+a;K$8}$4@?fLwa({pU8_L)DQkK zndi~Ly@Q_1_Ps50yft`v&DHgMT;DwY&68h$or?~CJbL<(v-Q;J(!uX}=?DL~%;%`! zJ~gk-cSq*)r{EPeFW2*Nee?J?PkwXgTy*&3bsy^T;_<~-=iBeGe&jel^Eo1TU~u1{ z_qcQ3nK|7Oye9a&njam$bebnm^YpFX&PRtoUiYIeFJAYru0K5a@YUt(Id4&OlzY$o zee2+sHRoT|9Ob?=e?L8Va?P0zUpmcGcVEm8U%#D?4u8DvM_pdL?q6Mh&Fg%4o=5y| zXMXxJhc)tdyaO{Yed_0DHAmOcbzSq+`O?Mb+j;2lZ(i%+^}4rE`dch0kNm6V@4ge8 zr_Ub+<&85AGM-IOsV$ zHgh>YcyaKm;FZDSg7VSfOQ(72a`C}$JvoPb>F7^hy7bibuOECo_4dW@=W>P2Z{48& z%){^fp3B`cx5I+^HYfi)LLS%iaeedD<)Dw>dh!kV($SBcbm`G)9^IbTJekv%f^zHg z3;DZwZIC%_AJmWjH_YGVaXlZ`H&0y-`uMFU-;ggI{m4m|9-ZdV?Ria-c}yRaTc3XK zH?L(gkJW?v(f@KaFW2*Nee=}ippUOVdh!kV($SBcbm`G)9$o7ezTfZ1%e>^43(xQN zOJpAU=)PMI-}UtH_|R!y=i*D39v%7d=yuC;iCV-9$7&~F|c`G$Py z=toYveAQbIzvt!m^p7&HPh?*F^kH7JXFdxBmki3${n4kRPDgG$Ip{Z!j(kJD-3Ok$ zeEH$4^Y3~2o&5dGYl6&+jy}xG@ALC#PG73|b$|5fsMC=fPY(Lcqa&Zb=+dL3A35pb zska_}uS0%kA2)O2BR}0)Gnd7Ke7hfdu2*+GJv_d2n@2~kAzwPpqdU~M-}@&{|8(S( z$M5CK)Ew|WZa@6+Tc2L%(FcD#did({_WOZ9SNfehI9-sA_k+&*>HGV^Zv^>$+&p;v ze#hRn=0?}`mt`(z*1YiO4f*5o!*6}O&Z8gxc=Yhq2)4D{F_IY4qm?>TUDQY_Q~J34|+dNtA6QTk-wi^b8tN$ z*Edhy-1LX9zs^O6Kb}5XPhLEF`0DifIoYP>;`)8_ce-cPJm~$d=F@umaXmfy`0A}E zkGa$1-+K7!^t$iO(;t6%{QgS!r1aPM{N9V_I=ZfFo;qK;_^l_GK3Y$`_3*pzZ>4WJ zB%+ZtKNF}{rm62>HEvU)q-;I^*gn>ZIOO= z2<{V^()>U8`4@ceo{uB|!9=l$sC@wJ*q=XO0G*Ef%U^E|Km zqo+@OwO;puCoes|`09LnUgu>#zYX3H^gepe&Fitu=O4j;1-+;J{=1%!>zk)82Yr0~ z(UWh;myUkqq)Sh|_3(RMz7y8#n=`LVf@cTKtJnSiWM0n(9|_9idOogi9{=X~e$eY& zbok?SAL{br@x@o?+w(dj^RTbDCjZ@c&-c{49?U%KCtl5e_dV?Itn2x>zIptc$47tk z!RQ}LG$YS`DW(z_u$`x`g1)W*Ef%U^E?;yIu{-O zc-@D(ym)-^)%o_k&dof26TC6#{kN`|*Q1%o)4_iStqZ;0xSo&eo2M=ZeSH1VlW)kE zj(+5%OHaM^@OxhFzx(!wnx8(*tMBJqHNWo9^?Y34JpRq|TG8!^ z=iBr0o;#P^o_XC7^nRO{_uu*DgUsv0p!Z%L`gGLk;PJ;-=Y!X|=<&y+ryn_6Pn|9u z{67D>Pv-l_%<0d;TZ5ik`_{f+?`B@()@&LrGt-0Pi{FpCwScl zJvs5{=wCl{@zm+k!S6YHj;)h_%3SXa^3{(yd(N$^?`5vzX3lbTzkK+%jy}}+(!s~0 zC$}7O;nC60P`>UPPha%-;j8oSIr|>j@7$94{v}9HKj!RvWq*K{O~#-J^pz5qD#m7-aP&5r+Mo9`Qi8X%sJEf@XpNjuAp1JE$NShk^UzoGdQSZG zPmd11I^AAp{XS$q`nx~(!~5ZVvd;SbsQVv3_rd$q?;AgQ>h$y>5577-JUVg?`Ra=< z9do4Debdty9zA?@dX`1=cHXzIw|~AbbAB-Bd+Ph^_Z2#Rf9QSuCo=a5gTBAM*M5(Y zUkw10tRPNiSgU(ZZ{^CbZot{4A!B@9lReHIS)OSx&A%)c+mOOocnw=Y34h1aI&EDt9<d~dw`RMV-qo+T4o&WIk-8_Ba@x@o?+jDjf@_czdp3Z#v$!X5cYyJF8l{pOc!H=Fk z>FGlre06?!bmScJ)fZhl{oLytUtM2(`QfYc?>RePdA{7IXENs}g3e9m>|E8)&or6y zGJ98@znXz!N-%2Zs)-3{Pg5(9$oJ*UO!*X_s?fO zF9hw^?C;FWxqtf1XNI7CT<`nn(^01*H=Z2yn@2~!A>Zx;PhP(K@YVVEy63rd9(3-0 zIdgj{=()A-d9Ixoozp*;`OOscT+7FYZ|mTx^QD82CokR3(>#62DK}pC%U3^mbnw;b z_MEND*2z~h*VlsfxAxEG-0Snqnd>aUq5U;KdiLM+^dS$vx^*0nj+{fj>iX)O^!S>C zzS@^g^XT@Ry+@w^w=>`W1bwf3-^{t+r@1rVd4j%Y{r%%dPo18A<-u3?e&W%QbI7;* z!IPIBUvtn``>NBWgP-yJ?{`t}o$rD5{FM;j4ZaifzF4olcfIa^KJ%V8_=TYNN?&~C zXgxf2{&ev1#62X`RFCe);MLj}E>%-JY}i z>%L5$xlR%EoO>_Kx$pA|nd{es-h+N`_|a3Rrw@7X)jcEIqo%LRN{-@1cKNIv_w9fW>v})$LTG01X9{P0D>EQ9lSNA^RbuN1R z@#yJC&el_>O9$WQ-Q3>KAI8mmCJs&){A}=3!3lz{pFeY2Jh)8o8^Lb|zY_dXP+xrI zXgxf2{&ev1iWVnFaAA;=Fz2NUVVQ)${+MC_e{ADGY6*%exmNr zLN#~!*T~<$6I>~{WKdrIbk*tN$%n6Qu6Ui39)CQ2(xoGR^UOnE&FeYw(?2~r`08|f zUH>5W$9(iRL+-Hgx!sCmtZVo+X-*?~J88gq%1wS3MANT$2@BFtj&ozTz4LT3V$B&*mJv@2v)$IrH z=*T(btFEukNsq5N=&OCzTMxh2o3S!S&yD;3`TTwQ;3UEEg1!ffWS;$etd+m79$YrK zL{MM+>8jJklMi2=FCHCvhkW%(myYL`9vyS&e(>m-oBmr*z4h?T)qce}YL?7>)}Z~U z{VTrn)!Lc!Izju?-q+HnqfSSEcyiEh9v%6He7g@kdHM3gSLfgBhJBO$t$pMincM6^ z`zGs#eO2#YznA%~8?>*Ij}PD0!Bgi;2Om#fx}B$a`jXRr8L#{0s~75j@YTI9cy#0(^6h@`U8Pg_qu02wO?2u z^PMkf9kuRRXYDIC&0K#Fw7$wsmrq}hr_PTKJ{~ZaLAjt9f+k z=)a#!>$i1s@yvCJpmVtAve(k3YV;bsMj9(c_OtPd{?Do;qDR z_`Sch?{~ghICEMoxKPmZ?maSh=L6@mEi$(s1vd|RFZzAsPgk9;{^i40=Zi;2-XULl z`jxx&x_^55;)`c)-9Mgs`{MVyVIOLJv)(M7`|_pWJVEP*eQ>W|+vR?2AKV})A0NK0 zgQw1y4nCf|bUP0o9sLaDqo;rUSbyoMw;q1K2YxS_J9AtzxKwb#p#6ybi}%3qMc>an zw+U_=+$?C{()%3#bk*tVOFn#czIb%x9rEqI@Z_au9{l;~6R+n*#~kqJ;j7c@_rU&7 zKXQLL_r?33=C z?>XCd+6R6m^ZjaY&Y<5f%(?fUJ7&H+1=kDu{X`%9=&94glLuejJ{6CSoI}3q`s$qY z_?m;h+E=~x@U0W}9nQ^P%RH9}+K1b><2z6PICI=NXn)@OdHQtJ>F5tn4*JcbBj1p3 z_kkxbUw-)N{QJ4I|FKWCA6zMOTRCX|V4=Qnctp4`y=`I@NFGDb-r})@#Ll3 zd77s$IqiG#x?jHf!J~t(PPgam`)dDWU$biFyGqda)tr5Qdw;Zd=DSzW_g6kXd|L-k zoi81HJbCGM9z6Zv+t101*ZuO<4<0>yb$UH#?~&*Ko0;o3g4Pr3k2&}Iv|r}BZ_s+v z>k~hE>h$y>55Bth6OWFZL%!V)p1kzXnmKD58u|oQ|C(uA5UJood-`p_?;K8`{k=2JbL)*`tSGO zciH!E?aXzZpx;Tn|NR~PdFJ_xpx;yEp-)Gh4jzAeb>CyW&P9(u9zFfY*?Q`9>EQQs z>HF$C_Pxw$-QWsA=W5Soe-{tWybcTQ9(1nP2S0l1^zh`tSNEO8qa){#ue!cECq2IA zps)5-Z$12;v+t(w*!r34dckFb&J*U`-_Ij6*CT?v1f6H}!H=FgJv@2v)qQ92=*T(b ztFEukNsq5N=&OCzTMxhAf9H(vXPz4b?IY|vy#LN0M`eyj2kkFiW|`LygVq!4kLR-2>tAMWrv$Axy*}}yr%q2F^5Cmm z|MBR^Ipo{@;K@smuQ}+eebwpG!SCN2tt-wAYh=D#2Dc9WC}@50d|Q8u*1e>(Vh^3&~Hc>2Td+<4tLU;W^j6W#8=d35QRQ^q{T`(VFpA8B8= zRfyXMw+U_@{8o^TeR=O2Ps@EdJ9tL$q~HNT`Q?zSb@9~Ypo5R6AG)0nkBxN#?q3aL1tgYtEh*=cW@f z*E56X20dT$@!{J#c;@zeKEAL?|wZ|lGPitn87&Q6)@kAwF8 z`2C$bFZ29$(D^`a`gGLk;PJ;-_Z`IRT=e+k(bJEdt*1_x4t_tE&W}IHe0B})7W5tW z-S=ENUml-1T^RgL(09J~1^no#)6=g!`0D)d=*T(bt1r5A^!0e^{OI80$w#+y;L*{?P(FIiv+wBV(s{=E zZ=KyebJ``iY0z`&Jk{&_MVZ$H!4qqKeE7DGe%1NX!N-%AZs)x}pQkj(4gp!L^!?YZoA77dVKNK&7tRPUGYA74-UVxl-#-McJM!`2+d6pa zeCgoh$xFBM;B_DL8R7e7Rx1+#h2(ZR=)k8bC{>-_ZO#G{9=?m6rC*naeY%;kjOuY%TJ>$UgTe)a0i=dR$rLF;p` z=ltlY)6<7M`0D)d=*T(bOHY6DwqEy5PhWiL;H%T^Ia{Z#uSaISCkIaq?jJPgUa$X{ z`Tiw%ckrs9eEjIC)5DVoU)}nT*ZJu2$D^k|d0S6i|N6nFr_P@re!u_rWxvcEPYPOh zt<&Cr`?Y&Azdr}9$Gv{jr=w0sZag{YH;;~dL%!Vyp1ged;j8oS=hAooz|850;Hg3P z$$j-)+811tdEFAcKj=R8edoisb@ZXmmkvIjymUJc9v%G*<)f#6{pgRLdh6l$ocGH- zy^o&f)AD!giFL}HugpCAeS08(x8C$R#)og~=tG?^9eg}_>2@AGI{F#P*L~yZiymKm zb?ZRS*}lT}!1{c8=6qJrzQUaCJ9@qUYv%lL(7r=HK73mTPn|Cvd^~ySb{@R$gPxpt z^z^SE-zz+Ix^(dSpS#&-ShuVu*1 ze>(Vh^3&~Hc-;>@Iq~S3hrX=Gcs(aN`p2V#uTHn`kA0`U<-RcYw@y~r>jmEPds<8s%Pm! zXFlfze-k_-=(}uP_ualTb9yZJbnwBT@3ubp@NFGDb-r})@#x7dhg^8w2R%9Qx^H!T z;qk*)=ihU-pV5yz7iGTZ2kpzv*?y|?{yp=3B52<(A3u8P^zh`tSLcV<`RMV-(-&Pj z=GZ*_>!*3@{Q2ScoR7*Jj|=`fcyZ8u^W2#8pEAcgf{zEE3A(TST=C)CI{H=TO9vm1 zp4@WCg-1s}L;1RIJblsQhp*1R=X`YL>APWlzNF@^A9KDn^X%`(vo-hbmk;08(T6%; zI{0|<((OEWbj-&*=;G1SzkYnL@YGumzrSbBotI{=mj&%x?1y~MoJaqec|I4kpXq%P zeLCuNJv>2Az96m(D%CuDz7`{Uhj{ zBp)BXt%Ik|mkvIjymUJcUiU#yPCR=0*N=4+Pn|9u{N4vUhw8_5*Jr-J3p)QeFL}QE zT*~i_%=yKjb5ftH_|sLVt1tQR)y)f!j=V#@`lL%oetO+MJ$>QPGdKOWo;qDR`28N( zhdb9?llyUF@QR@QwfDgO+d1gf+=qV$UkKWN>w^#9*1=QfO9vm1p4@WCh1Y%1lM}D| zR@WCEKYVrmJ!j90`|^j(^}3+_xc$31_w(>p=K6Zje!chi{OGCE(}z6x>Yf`sI&u#A zc0YLX(&K9m`f6Wwx^(cZ6TWNm;r~AKy&>qlgWvhy%v}E!bS{#cJ{@&Bc>M9z<;Uw> z^!Ve^(~q33r%sm+em|G?Bi=Lb$xWHpmP0PQ?t`A3cy#oyAG&zzbm`#t`)@yKA9zdVYQ6To@c#FH@}11n zI^N$A`gGLk$ju*L-M$o$j(kJD^z#4UMem|Gah0b$VXI_5{x=-%E=d#a-uVilT z2HnSgKKRj7r)Mtm;Hx`t;?a?F$d{h}lhd4? zBYVAiFLUPCeek2FPEQ~5;H&e)qa){#ufFKgv5wNi)4zV2r{2E!{r)@0SkJ5*w`R`H zFW!IWn_j2>oB2A|$W50|UyrBGj}AVbd~`bpUgxJLCmua~b?au&3*S0>Pv-RJARY6f zWBncb)1!a!^f*C&^3dhe*W;=4ql1q}Pfq^w;B|g_a^lg!SEt+a@*H|D@5-F+4q6Y) z%X{K^9g{hY6|`>1L!XX19X$T{>U{7z7d`%X^z#5VFgWvPAF6+m2=I(x)SFgA9 z-H*P{^y#S6(I0<&b@RZZBj1p(KIqbsmmXg0;q|<%i}saw=D9L=_sP7hm-e3@W!-W= z`u@@7)7Q(b&W{d09z8kv%Y#QpA4B=*G>>l2%Ra}x=l0C&j-d0odD&mt4}Fk%eHe6( zmzzEvbvk(b@zwd@buN1R@#yJC&el_>O9#K_<=mnl*O|NfX{5nk+b#G>C(aPeXidr?R$LBeP8d*-26^ypX+zb-q-jZkDK}V9aA5C z__hw7I$t{Yc=FQiJb2v)Jvs68O?PM?f=8E*{`k*S z|KIib-y2cqM+e^=yh{V{>;tqig>-gjGy<^?~Zu%T(9nW zJbw7<)*ZagLytcmJ$!X~{hS<^`+a5b>fnXJJ$1@8>X<9a@>Z=Sjw z^zrF+E;{`2x({`E@%Z7Z^Y3~6B=eA0e~0AnKdpIPoO$R&-rwi%m(;vm&&T!6QzK?#AM&1(zaLxkay=i{H&0y-`uO_m zTy*&3bsy^THm`m0dS1(A9`fpMmHd7An%AzGhd$)pD}Udu=H+@mu5X^Y9Q5(^*SYBM z$Ll`S&k2uzKbMnbep3hM2-4Aq@9OfI+seW31?hKxt?xQIa`VAgZ@tdRpKj~v zV<=zui$}NTHEHHERdDv2n|$WAT;{VGp}VcpB01Ot+{pX)^{Bp{qez9Z@tdRpKj}!hyGiy`^BT%^O_>_ znI$-D%}qY@`g-QGc5t1VTjzE?AJ;ceT@L#A=0H!M`f5Eo`jL|^J@wYZ@9)ISna{$( zuLi#sq{rWP;yans7Qvl@KMwNie02EIX`Z@VeDL*4PtGCV?gvj^di=~mU;OdZ>C(aP z=MsO-%yqrs`ZaIQC7pvZ&m)3I)|@|Xp61CxpC4Z5(jR|3I{J~5E}lAFI`};={GBqN zU4pyT+{}y4d6~}z!3%3{A2(0)FSP4t}qDc&@u3bGkNoUGTD?b+2`w z%)DL;z8-uo_;K^$@sp3=ke=Lf=$D?lzU0Km>wfv^2agWEI^BLQ^@D#~=6P%I@}TFk zec#U<{~LV1=IVMru5TXy=E<+W&P9hm9zFfY*?Q`9>EQRg^n-s#=JTiE6*aHU_hIJq zUhsvQm+SeszIptcC%-v#E;{`2x({`E@%ZAa^X>OoKXTle`P>q`CiuIc_qcO@lsUZ< zd^PxD&5sUWI?a=(dHU9G=cB_PulrG#7q9zQ*B_pI`0DcYoPSkwl>5^B{q*3;HRpS4 zj&lDqfB$RnFEwX6eCae#-F-1XeEoJlI{fjvA9Z>0x_@>3HLvsGc^>f(%>49a4nND^ z@vh0d^r@d~YmTm?>$>Kt^QDW=xAV~9-@Mkt>veDQ^!KBnJo4|Izxz&{ls-=l$|=v~ zHHX%B9UZy(;H$S@=j2b9UgxJHXY=$;53k?HV>6HQgBJ&{3SJpJF6cSCJ#%>?_)PHS z;7h?fg7VSfOQ(72a`C}$JvoPb>F7^hy7bibuOECo_4dW@=W_SV@35f$%){^fp393e zx0{0cHYfi)LLS%iaeedD<)Dw>dh!kV($SBcbm`G)9^IbT2AR|LLAmw0Vg7DjM`uoF z2lb=>WAb--T+he#%~O|yK7Q-TH{?r4KXTHgN2hsodtS?C9;*lC*5`6HuU#^a{e$|^ z|E@JJ*Yk0G^VH>_kFP&^@(uaY(T|*T>CtH(UF#OU-|v^mT;!Gu&+qnIXCC_KzFQC9 z_4M%g&}m-h;!Bqv9r^L-cHe%7UnG6=k&CY1-?vQPd^;yS*Q>jp9v)x1&7&jNkT0F) z(e3wRj`X`gaOof&`K%}3OW&IYw+qtm-1O zJ@Z*8xMYx@KFn*~%xCl9HbFVMKl*gk>Bx;I2mR*Jk#ESi`@oZzFF$;B{yi_hlh2$tobvd+eCL`2-pB2SAAal8 z>pc44k4Fz*UEY2_@Yhej-w%ExNXPp@=g9PZRB+!QzmJ;-kH0+agV(wE@yF{v)aAwF zi?6P~o)`YEna^&)ZGv>ni_V3a&u@Zf1o?g3Jb3)&X&=1K#g9K;_n|H?9$$QQ{q?-? zkI8&a2p$}yV_tM_%Y5z%ULWN5ar5Bum#2O3Iu}3wc-@D(ym)-^)%Dlw5dLME&zUth zx~_jNb9t!dg-375ACDh?>*IAE{qV=5hp#Sg_l@Ve%hUI{LAtH;eENPgi03-Gu4^8@ z=E+H~^U&enJi2u7`u#Yq`s8y({(g4Q`|&{aOZSER{gIl3>-o68dFtk-KYaalE;{`2 z^wE0q;?cubr`ONP88sKz|1N*0`(Vw3-itM#*3*yc>CwkmZ#{X;ogV+z!&j%*eV>&6 z_{-z>SGs>rf1S_oy?Cyp>$>Kt^QDX5dUENb_0(GrzxzHQeaj*Dfpx#HPTz9zy{7KB z>*%_!dFp)W;_I89TtmLGz!A6+yZ6*}r4c;Y+7^ z>T>bHHy3(x4*Am2pS*PGsq0@q_;~!9$G_+N*Zltvc`5ix@b%y`!3TrpJVE9A@Kb|^WI{4;<|4`=lT=4DS!$G`H zWnP~R&K>-8&CzvqUDrHyzI5^Vb{;zX@#x^I)9vs3^()>U8`4@ceo{UaL9C=l$sCah94#=XO0G*Ef%U^E|Kmqo+@OwO;puCoes| z`09LnUXNuy{|Npo=za8_o7be7&u4;j1ih#I{=1%!>zk)82Yr0~(UWh;myUkqq)Sh| z_3(RMz7y8#|72dz1|JEUSFii?WM0z-Cko2rdOogi9{=X~e$eY&bok?SAL{br@x@o? z+w*!b^RTaYHUHgr&-c{4Cd@qSCuYun_dV?Itn2x>zIptc$47tkfppd{kfix>zl{Fd7cY;or?~CyzWC?UOc||>U?`% zk7gcE2mc-P{##efYm&@ks^DBf>q4(LuIJ@=3;oeE5B;}Z_kkxbJ-+zre0yHrbLWx|GOrJV z-f#2r{yV=cka;Z_^xn%upN={mJpTCVeDFFKJ^py~^do2Msnex{-{)WV$$a0&Y8}KA7!p%GH2&(-?KhvE|hsL9CRL+n?4Q@-??y(_Fj1|C(hg^3QiF8d!D&F&-eSYOy>Bt z;Fp4a_mh`DU3I#6^5LuV#p|5(_~Yr5E*W`RH%_ z+z;=E_sKfz_oMEAsoV$ePrq;c=&94whdlV|{P5_=IpnJ^x^&EuUiVE;UwHKJ)#+JA z&D(k3zTW=%6PfdbLElr~U%#)=@%uyXZ4_r*a=Z9durDzH(i!uV2Z1 z`)bg6s?T5i=&94whdlV|)@wXEat`@+KX~%e<7*E3YF~A_bntu5o&)z~ip+P)pntE3 zeWp40^YHb|cg5hcLHkgB@S~?r4^JL^b43^z=zjAM)U<^TVSf=a8?y=+f!uUf=lY z`r^wEU!8x?+4;)z|Sk0&qP&V$!| z(32BS-*i2n`og1!uTHP$?E7IopEh&-Y|wZ0f8XK!dvg0bvTEk~&7kjYf2aA;Q>Uj7 zdGOVJZ}8~IIpo{@;K@smuQ}+eebwpG!FLXFp0v-k@BB>WJ6+H@7Qgqet7Wd=2s-b| zO`ncB9X$T{>h{5Sor@lSJbL<(v-Q;J(!uX}Ifr@AJl8X3Ud~JA<=oZp&$lx#=P0@9 z^6BgG)cMiD$CHn4=fLay^yF+FUGFbm&&&CK`pjpBp#7Ttoq0L;ub%mQD`+3r`#$<~ z)al5LCkOrJ(UEV+xBI}8moGnjb^g8Xd2XEtox4Amxy=;x-1@$FuALX1)7Q-W)(Cp8 z<>SM*b@0^r(!s}*mu}~2p1$Oi8?XE2s~)OGg{WU*&_TTjMArHQ~bsUe5oI}3q`s$qY_?m;h+LuoA==PkwN1p$=Gv9fFzE{3) z=G^bo_cPxOg1%?{{o_YZot}Q>!B_Ww;?a?F$hZ5!lb0S}bI@1&s?()|-@l7`?|cue z=bz7<=M8=#=zXzXd+&PPUn}$8D7azJd!;YFa&_0D-?l;TgM57Wwho>; zUpn}B^3v@*&C{2h);YZHm#=>C=-{i(_&xbMJ*Y_kG?ybNxxsd(iI< zKYHr)^dS$vy5|Ouj+{fj-4C9;^!S>CzS>uvE*<=yv**EgZ~4r3mEg)j_s^U?2flZ^ zWxjg__YAtf`ryO2b@0^r(!s}*mu~05>ptkoiKlP6-BukSAduN{e1bsi{p-)Gh4jzAeb?+ly=c30SkDh+yY&~_lbntsWJAdY~cyO8EH-g^` zekJ&&pt*07Ic*i(Ik<0dzu=C+?SuN_D@W_$sq?3Uk0(Ff&V|?g(32Cd`&ZW&o_X=_ zIW&(h9rNn@vrx@l?ltoF?*vy0E*W%xHm|wM|Fiu4kl-G{ZG!Uhr>jmE&wasHH&?vQ zNsm9CKIzhtzj@}Nujcif`01Y>9ej1Vy{<1%bJyRu^7j>kO9!p%o7TMLIUs-kX>hx` zAMOL+*3pMLUpn}B^3ol;AKeE%Iq7xZ>iWXti?41DJ!ju{-`j6zo@)lb8nhob=l;$g zn0X!)+$rchARj+^>h$pB!B@8*z@sDQkgvMFIww88=Af_kRc}4~UT+r396dMg`&#+? z>cM4$O9ZVqTV|g9d>ov=?;qSHxOGrp{OPLG#gh+Toi838d53)UNtcf2mmVE+=zj3% znVbGwPrdc<&DDOzIcn|9eVw5FsQoLx^VQEY=U)WvQ+r=apN={m{o%<$zj<`z8}jWw z@Z{yo4_}>suN(GF_P6$t-^<+A4ca$ZH|(o=|9W`lcUaKANC=-{iU`+n7tYtmW?rWRPYzlKptkoiKlP6-B=c30SkDh+yY&~_lbnttBY2WXBwMFLiqu}O2&%5`?+?@}c%YK!)ofFHPQ*6aT1>5DI(xpn_|>g|i)>xO-(_04*-UGB^F z!3~1e4g26;zs}12I6HWBP(D6U! z^!?0no8Y#=&4TtL_AlN8zZV^qd7cqGGx*D(eM|3i_|sLVt1tQR)%oJlk$1?q`@)l# zo_X-+r%$||7aeoJqld3fuipdvKmEwPL+*?B$$RZRu>b77&dL24dhhwsvo6rnhdlV| z{P5_=IpnJ^x^&Eu9-jX7(>#97U8vnCkOrJ(UEV+xBI}8moGnjb^iTa+W*+6+7IrLx$POW|M6VfAN4-; zx0&CiLHi^5`0#BVJaxWw@bToO+j*L&FFEad@w#8W`oW`vuTHn;?E7l}WM8v)=DSzW z_tl(ze|vv)dFFdr(Dzq9K73mTPn|Cvd^~ySb{;(a;M>p1i`V_~)ejy$e06#~XYY~c zf4|Ii-=Oux`eV-hK3$o){w`>}>Gg>pJ#~8ekOyDg`-w+K&LQ9K2Txvle9b{$?W<0g z4t}q*zLWMD`)9uU1br8+v%a79BUfdvR|I`$<)+K0ug6p8M+YB|o}B#U!K0&(p?q|j zN4K9#`?B3KpM!%31?kD@xwLQlP3Clc@VX$s?t>pab$a@h2Vb2Z9vwM{eDy__j@yb^Z7I@4M{#_w&s47eT+1c>nu5dPC;HbXairpmVk7vcHQrXI?i2FA6%>>w_OX zb$WR6;H&%2;?a?F$X8uoos%A4bI@1&s<$3~&)Ij=ckIZ_^@!juLFWl`?(gRxGuK;! z7X+PW^udpwIz2pj@YQ{1@#x4o)msn0-+$+fqcYEn}5}Q-anL>yPKM z*Xw&SxBG(Dn_i#z(Nm|V4|(v_t^as*a_$JZS6)xPR<>EQS8jn)kBkk)>%l$Yzct-G~-~mDJm3?{d8z0Dhc_jE?@Xx`kgYwHE zSL@=b%RvVpPd{`!A08e34dtU}KKio{rboxzx^F!5Z9VhCql2$b*L|=~`Oa8ht&8X6 zzMU2HopGJ-P_MIp%YAw{=sP4IAHJ=Fr_PrSKAyaEI}cv>K~GLRdivLo?*yJYT{`$Z zXU~Ik(FvLBnZa{|?yos}UYwim%3L1`J{t6V$;XFp>)@&LrGt+rFWt_Aryu;zi^osj zLw%^z?Y^!5_A9<~zB}h-uD=f2_v80>?y=1C@u2g8-1Oip>7 z>m! z9d$Z#x0%`>$T^y*O50fw>N^;=U&hG z(Nm|V4|(v_txI@xa_$JZS6)xPR<>EQRAtryNSmu9{<1TPL+Pt4hR;vDnO z%=cfxXM)x#eemJiI(X`Q>EPqZOSkjj(b3OPzU~`OU-bCmtD8g5*}CF=@*don`Tikj zT`_0tPQO?G&V1hrT6g5*!?$(t)cMlE$CHGUUf^yU4)7Rsv^P_{0Cm-F;f!F!z$%#h~U)^)o@3H;p)tSp( z!Fz+&U+cB^*nahu%xBC*qknENr=?KxYgt*?K~eE$->J9t&loO``~H}f7Za~>=Bay_T~=&94whdlV|)_=UtM~^=q zJ^jhsdg}Vu4?aD0{`~O!{kJc>Cv*IB(7J1#_Ws+ijh#7;6SN-p`c0pXIvu(3EPqZOSkjj(b3OPK6?7skN)VXw;q1a`O3`G`{;RoAb+=>Sf|YSrOdV8w@+ry)|+0( z`0#BVeW>%LgO4XK-OhtYM?XXPx^Fyv(c_D+ZXM`3+gJD=SfBrzIX@hB&pC^Wb$K^yI{&r+@wUUg4?JrGwx9+|542x@En2 zB=dbTcwf+d#CmN%((Bknne!CEPXz5_*1;Mr-P3tKi$rS*Zt6w6OW#G=*xPH z*K?wye>^()>U8`5*mvq%?x%7;9tz$Tye{be^!`$=DRVzQ6?{MI)9XQb`O{UWizgqx zy1C-fk$1?q`@)l#o_X-+r_bh@oBo@p&YvH?x!QMF2fS~8%lsb=I`823`#o{yKS|KJ zNN)Oc)al^y$5;3M<8>~2{PF1NN6yw$r%MOF*LC0JJ2Rihf=>q@4Eiox*L}A?%JVsC z<~3Dt!l3WAKKSr$9Xxfubnx-$$t{Omc-;p*Iq|x0b$#LS!&m3ubGDz+k34_Re4hx~ zmz%TwROg*K^PN0s-!30Ndg}D>XlsVoJ zd_4F}(0%jVnDcvijz0`embp$7bYJ_q;={Lf^sCO74n7_|x#f@xkB)wZ@^#;M`l81V zU!8x?`PR(ScfEPqZOSkjj(J`N)eDw6M zAKxo_>aB<0-!tdVe`c=F1?^kxhkVbRN2kj?KO3~4>3tG?I_h-f#*>47^XSMoPYE}d(vd)AGYGPi#OoqIf&&ON=Z&6N3lCg_|bA0NK0gQw1y4nCf|bUP1T z_d!oiJbL=qk98DJoh}{x-UmB}>c@3&WWFy3oqwE{Jl}mT8jJ! zmwfo@=7mQ`-XULo(xoFmz3!i$zVPUooBmr*oh}{xeh=)!ooimr{rGqAg`oYl_rU(! zIcVnGhq;0?1ns}|!G~|_;HmSagO5i~ZaL(_>ptkoiPwFr>kE$`zB>P&v**Qqc`I{$ zJ!n5}|8CCxJj|K7&Kk5|@BKYLdg}D_ArHQ~=LV0CoI}3d51zdA_?m;h+E<+}9enGA z@0xu0Z)U##3OeuLcfQ#(*ExdDMRL=pqfQ5pKfb#Bc%6$Le>{5nk+b#G>C(aP=hA+} zd*(fPJM(%zXg}h)v|s7>Xzt8y`k?)aKKSr$9Xxfubnx-yrQ3P%x(|AC;&tEZ`oiOj zuWr5QIXmw-2fm!S{wMfa&^gAOopYQUKbN`A6PzXJoFg9}zO93&&X*289zD6`kPEN- zpeH9D9sTQvE}lAFI{5wm+fUjDzLU9HuYE7P|Gl65Lgr~5@9zkGI_h-f=8vy#Uy4UZ zz9C{Ee4T6Lrpu?V z$5ZD=2Om#9x}5{B^V5?Pj~>3db+hM%Z=D@Gzq^bRq+?!mtiOw9PKyQk$wQYyl;_I*==(>PPhT&$IzKx2c=Y7tFAp9a zeGKKJ(>%I8FZ&$(o)0pw4};F<=4F3nKeRyRwP4UWUT*qy)al^y$5-cr*SYBN$D^ko zIa^PiE*<=ymvf7LTxag?r+GQgbWZo9?=xLKeZBtF`O(40laFraz@wv&p?vh3XKp<& z=Wf44jF)x(6G7)~^Ku^d`@|A?u9gfshs#Z$jyfGY{`l&A@H!Vg{&@8CBWLTW)1`yo z`&_?M+V}XL`@W8wx%r*aKG*MF5tn4*JcbBj1p(x_&w* zUw+Nw?>)q~9(nJ_&wJ~4MZ8{Lmdd>R?ubXv_3Ez2uKQ;2$H9|=w*~(loN}BI zf63tH!9#EQVJ@GKnMD0o=#+Thc{@$(NRmkMqXJTrK2 z@Ppvo`4?%d9o#?o+u%dN4}!B~U0FT2NAUdM6G7|N0(qa-4(=cPZSaZUxOtzfYg^^N zpBVg8@V(&YCLTR6s|B|To*29#_;PUaNk;q?f;$G!4L%SYYtj*Kj^LWXeS#MS9|(>$ z*@*we;F`gGf)@oJ2#z)Rh`&g1yWq*eJA$tVXP9EdUpBa1@VMa3!PkQ`Oc{P~%iwXr zn}aU|r=BYO;C8{Yf)58j49+w4h`&zofZ(OUhlAryGvX}~+$?x>@ao_{gHuc!esHtk z(ZQ>OPX@>P%!ofv@LR!Mf+q)W4!$0odb-hdO9#If+$VT$@b2Khf}i>9=(-hxTLq5_ zUKM;I_^Ihf`~`w*2loh`AG|mCc5s#%M%S$#+%|Y@@TTDN!HH)a@fQxR5!@koLh$#& zXM^L+G`eo);EKU*g2x0e3*HxeCpgXLM%OPITr0S5@Z#V-!MB1_e16RT{b#}8I>9}I zX9e#Fz8;)<=FxRa2R9BL9=tC2LU8I?!Vhj7JT7>1@WbFdvyS-d1P=&a8hkSN>DflS z1%vAZ_Y7VTd^kAX>?8gX!3~1H2woa|GWh8^!Vhi`JUV!5@U7tI=N$3Z3jQQ`Uhvl7 zTfyn(8u7m#+&XxA@Sfnv1jrdCjHw+#ayfAoQ z@SWg{3y-c_CAfX?_~7q?j|InGWW=8{_>JIB!PA4c244wIvFPZ!C4!p;e-XSi_;7H% z#YX&jg5L`68N48PNAUIF42zGhTQ<05@bKV8!MlV13VvpZ(RE)5em}TJ@XX-t!PkNl zFFCqy-ryR+9fC&&F9|*#oN%epb#n#38Qdm#Xz;S&Bf*b?vn@TkewE<%!Q+G12VV?M z^Q94gx#0G}8R}LxYzE9|?Zqt0VqG!3~4Q z1g{A`7o6y8WB%_yO9nR&9uvGK_-b(GWk&pO27em7B=}%(oMlJ6nS@V4Ol z!MTZU$Ac3tKjJMG+$4BN@XFw`!3kFgKe$Qokl>ZUmx8l@eZ*fQxOecJ z;QhgOgR`$V;;$OqEqG4w{@}a8nN|uvxLfel;9bFYgR`$Z;;$P#EO>eF>EOhxjCe~0 zw+J2+27eX2J@{I1`qjb@ZW}x{cx~|M;P~Gd@#hV$5!@|!PVlba zo59JxIl6AK;QGP+g1-*_Irx5X=G8~ntr*-Ucuerd;B&!=zBS@66x=X)VDQ4=Bf(F6 zd&HkDxJq!>;3>gdf-eRqSYveEoWXAdcMAS8cy;jM;D^Ck*Bo8Ha&XJwLBWfHcL!e% zerm1Jb&Cf#4elR2KX`la`QXIg8C|zX!#yY}e1<%2r}PYvD`d^0%h zIwSt7!QFzF2cHg3{M`|6so)mDV}myZpASyFZt@2=3LX}`Huzd_s_%{X%LKOw9vi$V z_*!tP^+x=af_ntd4?Yz9)cPad0>SSG_XwUJyf^qkaF*|nu3I~}fAFf{KZ28QFybv9 z+%$Mp@P^<&f|GAJ;(sN$Y4E7v4Z(i|C*Nqy|NUo$;6A~Nf{zBr+IYnKVsOpiKEZQ? z4+O{hLHNNngS!XM4L%TjFF400qwCfT?i0Ks_(E`!O-H+h!SS~m@fQwm6x=&_PVoNVyTO^Z z9$mM5aFgJE!Cwde9Q=21%56s1EfoB2aIfIm!CQjQ1Si;bblsf6uLm~^9v%Ec@X6qK z+l{W9HMneW%i!U`D}s*($J%~$-5kNyf;$FJ3*H@kFF3~zqw7`+?if5b_&{)y9Y?&S zgIflV3*H=jJvhToBmT0%ErW*#uM55qochNj{>s7af+q)W4!$0oVdwCJ+Xc@GJ{%lx zml1El;AX*J1g{Q092{@g5r3ZGI>9}Imj<54?+bnu zoNeC`f0f|&!Q+G12cHQ}u-}M3UvRzPzQOZ?w+3GcPO<;!x+Q`e1P=&a8hkkTVQ|(1 zM%S$z+$DH&@aEtP!Kn`%@s|#696UUDUGUN1SU(%_zZhIIxO?!l;0?hig5w@Ex^C{^ z>cMS;hXpSW{w+A>;L&w61-~5JB)DJjjNpyISA(BDWOV(K!3~3Z1y2dy5_~Z@>CZ>k zEgD=exNq>h;61_r2Iu_6=(@Fn`v%Vo-V+@A&=GIG;17ex2X6_!5uEX`5r387uEA4+ zw*+4dPILH(zg%$p;Mu`DgYN`qJR+6CFL`FB#k>cvA4T;H$yU9y8*v82oYYq~LAAe+NH%Z1}++2Tuy#7JNUr z*l{ENCcz_vR|cOAPI&x?w^(rf;32^)gU<#hJYmG2Ke&GIkl-c3$Ac4|82#Xnf~N-m z7<@B0(=SK-RfD?)PYvD`9COl$H*av=;NHRC1pgKs^Q-WK>jw7@o)f$)_-1h0lSkKm zIk-vikl>ZU$AjaXGUCr1Trs#!@R;B=!KZ=~ojSU1q2PCedj-!9-WPl)IOA!f>y`^{ zA3Q#IeelKLG^daGzyB;6TrYTV@Z#WKgZ~XqcgE*PJh7{=yM|nc%j;!-CfapAJs^n-PEE;147a04khFecjf8oP;m%X zu2lA*#|djR8f~5*V+u18Vrshcef|D<{qz2O@0ZItEM_%RztS_8Gg-`PHvT&L4CZnU zOIgRXHPL5r99OWCb!=M@eKx1FfO~meVRW6C&FL)QUM3br-%_hs$HgpV4O2^Em%$uP;g>wX z#_JNUz6|DY3X56Iw4b8S;yA8gB@@?2cQ5;L92av7>)3Wf?6NtDpRv@K)%c6gV6S$1qSkHEw^qj!YxQ+E}w^`2#{EDa8 z;+N=pGLH*b!c%No9(_0FaREzsj7_)bIfQdr!c)9!YxMm%o-0|!2ERs^&VHQCLLOn0 zis&EY3!KG59$}MhdJg1duH;spV~g#v>&86J5b<`&j4bw~7%GM6*?B`bKEx9p7FBOJtuT*Qq$$&_8O>&V_5#d-Xi z+j)iUcgMaLM{pLuU==U(u065q$vn>GS{~#Ld!z5nA)L##JjkZ|qVLA>T*)dn*dJXw z2l5?$!75(n-3MaVkCR!*eZ2ml`y9x3Sjc^Bawz&tj^M}K&WmhwIN|Eccu)zLq}QCz|@o@dKrdggN>%XpqG z|A@Xj^SO}gSj(1wy3aSbnmgF=cyt~4JZEq<_cP^0^j$fWv-ty0GUcS6Lphs8+|PzJ z(ci<~9L0q!V=bGVirxJjz_<7bf8k%eqc(P#e1)@E$RoV|boA-$$H`pDt-Q>;&%~}L z^SFQ|JjNUTioPA6;&8st@3@zBOgkI<9vsG*{FD_u&71y?T?anLXqh~+%XJO7RTagOD3?qSk@(LKQDIE`y~m`U||4&pSf z;bA6S(eouPWd;9ZtAxa>gQq!~OSy>`*eX%a(fp8`c!9S!i2g~A=2Grtk)jPqH_8s2to^gTF?^I6IorZ#e)Ih?{zS-}fzl@z-t`6}OKa#~uK^zOrl d=jIR29h^Dn^+8>eyQKH;+pEXR!*aVO{|~CAEYJV| 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 45/68] 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 46/68] 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 47/68] 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 48/68] 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 49/68] 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 50/68] 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 51/68] 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 52/68] 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 53/68] 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 54/68] 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 55/68] 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 56/68] 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 57/68] 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 58/68] 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 59/68] 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 60/68] 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 61/68] 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 62/68] 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 63/68] 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 64/68] 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 65/68] 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 66/68] 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 67/68] 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 68/68] 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: