From f3e7ac34c99b8f418413e45b4c505d23aca84fff Mon Sep 17 00:00:00 2001 From: Dickson Kachuma <81433670+dkachuma@users.noreply.github.com> Date: Mon, 11 Dec 2023 12:32:56 -0600 Subject: [PATCH 01/40] Cleanup compositional fluid model (#2812) - Removes some terrible debugging code - Moves some physical constants to `common/PhysicsConstants.hpp`. - Ensures that the `m_componentNames` private member in `ComponentProperties` is used. - Marks functions in `geos::units as GEOS_HOST_DEVICE`. These are called in some compute kernels causing excessive warnings sometimes (Host-device decorate conversion functions #2860). --- src/coreComponents/common/PhysicsConstants.hpp | 16 ++++++++++++---- src/coreComponents/common/Units.hpp | 2 ++ .../CO2Brine/functions/CO2Solubility.cpp | 3 +-- .../fluid/multifluid/MultiFluidConstants.hpp | 11 +---------- .../functions/CompositionalProperties.cpp | 8 ++++---- .../functions/CubicEOSPhaseModel.hpp | 4 ++-- .../compositional/models/ComponentProperties.hpp | 2 +- .../compositional/models/FunctionBase.hpp | 4 ++-- .../chemicalReactions/KineticReactions.hpp | 4 ++-- .../constitutive/solid/ElasticOrthotropic.hpp | 2 +- .../solid/ElasticTransverseIsotropic.hpp | 2 +- 11 files changed, 29 insertions(+), 29 deletions(-) diff --git a/src/coreComponents/common/PhysicsConstants.hpp b/src/coreComponents/common/PhysicsConstants.hpp index 1d884741a4d..410e45fbc16 100644 --- a/src/coreComponents/common/PhysicsConstants.hpp +++ b/src/coreComponents/common/PhysicsConstants.hpp @@ -16,8 +16,8 @@ * @file PhysicsConstants.hpp * @brief Regroups useful constants that are globally used for math and physics computations. */ -#ifndef GEOS_MATH_PHYSICSCONSTANTS_HPP_ -#define GEOS_MATH_PHYSICSCONSTANTS_HPP_ +#ifndef GEOS_COMMON_PHYSICSCONSTANTS_HPP_ +#define GEOS_COMMON_PHYSICSCONSTANTS_HPP_ namespace geos { @@ -25,15 +25,23 @@ namespace geos namespace constants { - /** * @brief Zero degree Celsius in Kelvin */ constexpr double zeroDegreesCelsiusInKelvin = 273.15; +/** + * @brief Shorthand for pi + */ +constexpr double pi = 3.141592653589793238; + +/** + * @brief Universal gas constant + */ +constexpr double gasConstant = 8.31446261815324; } // end namespace constants } // end namespace geos -#endif //GEOS_MATH_PHYSICSCONSTANTS_HPP_ +#endif //GEOS_COMMON_PHYSICSCONSTANTS_HPP_ diff --git a/src/coreComponents/common/Units.hpp b/src/coreComponents/common/Units.hpp index f71f799c074..ea89b8381a0 100644 --- a/src/coreComponents/common/Units.hpp +++ b/src/coreComponents/common/Units.hpp @@ -33,12 +33,14 @@ namespace units * @return the input Kelvin degrees converted in Celsius * @param kelvin degrees input */ +GEOS_HOST_DEVICE inline constexpr double convertKToC( double kelvin ) { return kelvin - constants::zeroDegreesCelsiusInKelvin; } /** * @return the input Celsius degrees converted in Kelvin * @param celsius degrees input */ +GEOS_HOST_DEVICE inline constexpr double convertCToK( double celsius ) { return celsius + constants::zeroDegreesCelsiusInKelvin; } diff --git a/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/CO2Solubility.cpp b/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/CO2Solubility.cpp index 623ceafaafa..eaf6f9efec4 100644 --- a/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/CO2Solubility.cpp +++ b/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/CO2Solubility.cpp @@ -20,7 +20,6 @@ #include "constitutive/fluid/multifluid/CO2Brine/functions/CO2EOSSolver.hpp" #include "constitutive/fluid/multifluid/CO2Brine/functions/PVTFunctionHelpers.hpp" -#include "constitutive/fluid/multifluid/MultiFluidConstants.hpp" #include "functions/FunctionManager.hpp" #include "common/Units.hpp" @@ -42,7 +41,7 @@ namespace constexpr real64 P_Pa_f = 1e+5; constexpr real64 P_c = 73.773 * P_Pa_f; constexpr real64 T_c = 304.1282; -constexpr real64 Rgas = MultiFluidConstants::gasConstant; +constexpr real64 Rgas = constants::gasConstant; constexpr real64 V_c = Rgas*T_c/P_c; // these coefficients are in Table (A1) of Duan and Sun (2003) diff --git a/src/coreComponents/constitutive/fluid/multifluid/MultiFluidConstants.hpp b/src/coreComponents/constitutive/fluid/multifluid/MultiFluidConstants.hpp index b0c6585e41b..4f69f3efa17 100644 --- a/src/coreComponents/constitutive/fluid/multifluid/MultiFluidConstants.hpp +++ b/src/coreComponents/constitutive/fluid/multifluid/MultiFluidConstants.hpp @@ -20,6 +20,7 @@ #define GEOS_CONSTITUTIVE_FLUID_MULTIFLUID_MULTIFLUIDCONSTANTS_HPP_ #include "LvArray/src/Macros.hpp" +#include "common/PhysicsConstants.hpp" namespace geos { @@ -40,16 +41,6 @@ struct MultiFluidConstants */ static constexpr integer MAX_NUM_PHASES = 4; - /** - * @brief Shorthand for pi - */ - static constexpr real64 pi = 3.141592653589793238; - - /** - * @brief Universal gas constant - */ - static constexpr real64 gasConstant = 8.31446261815324; - /** * @brief Epsilon used in the calculations to check against zero */ diff --git a/src/coreComponents/constitutive/fluid/multifluid/compositional/functions/CompositionalProperties.cpp b/src/coreComponents/constitutive/fluid/multifluid/compositional/functions/CompositionalProperties.cpp index 2f9e760574a..165801fa177 100644 --- a/src/coreComponents/constitutive/fluid/multifluid/compositional/functions/CompositionalProperties.cpp +++ b/src/coreComponents/constitutive/fluid/multifluid/compositional/functions/CompositionalProperties.cpp @@ -44,7 +44,7 @@ void CompositionalProperties::computeMolarDensity( integer const numComps, real64 & molarDensity ) { - real64 vEos = MultiFluidConstants::gasConstant * temperature * compressibilityFactor / pressure; + real64 vEos = constants::gasConstant * temperature * compressibilityFactor / pressure; real64 vCorrected = vEos; for( integer ic = 0; ic < numComps; ++ic ) @@ -91,17 +91,17 @@ void CompositionalProperties::computeMolarDensity( integer const numComps, real64 dvCorrected_dx = 0.0; // Pressure derivative - dvCorrected_dx = MultiFluidConstants::gasConstant * temperature * (dCompressibilityFactor_dp - compressibilityFactor / pressure) / pressure; + dvCorrected_dx = constants::gasConstant * temperature * (dCompressibilityFactor_dp - compressibilityFactor / pressure) / pressure; dMolarDensity_dp = -molarDensity * molarDensity * dvCorrected_dx; // Temperature derivative - dvCorrected_dx = MultiFluidConstants::gasConstant * (temperature * dCompressibilityFactor_dt + compressibilityFactor) / pressure; + dvCorrected_dx = constants::gasConstant * (temperature * dCompressibilityFactor_dt + compressibilityFactor) / pressure; dMolarDensity_dt = -molarDensity * molarDensity * dvCorrected_dx; // Composition derivative for( integer ic = 0; ic < numComps; ++ic ) { - dvCorrected_dx = MultiFluidConstants::gasConstant * temperature * dCompressibilityFactor_dz[ic] / pressure + volumeShift[ic]; + dvCorrected_dx = constants::gasConstant * temperature * dCompressibilityFactor_dz[ic] / pressure + volumeShift[ic]; dMolarDensity_dz[ic] = -molarDensity * molarDensity * dvCorrected_dx; } } diff --git a/src/coreComponents/constitutive/fluid/multifluid/compositional/functions/CubicEOSPhaseModel.hpp b/src/coreComponents/constitutive/fluid/multifluid/compositional/functions/CubicEOSPhaseModel.hpp index 2cfbaae1dac..8adfa65457e 100644 --- a/src/coreComponents/constitutive/fluid/multifluid/compositional/functions/CubicEOSPhaseModel.hpp +++ b/src/coreComponents/constitutive/fluid/multifluid/compositional/functions/CubicEOSPhaseModel.hpp @@ -627,8 +627,8 @@ solveCubicPolynomial( real64 const & m3, real64 const theta = acos( r / sqrt( qCubed ) ); real64 const qSqrt = sqrt( q ); roots[0] = -2 * qSqrt * cos( theta / 3 ) - a1 / 3; - roots[1] = -2 * qSqrt * cos( ( theta + 2 * MultiFluidConstants::pi ) / 3 ) - a1 / 3; - roots[2] = -2 * qSqrt * cos( ( theta + 4 * MultiFluidConstants::pi ) / 3 ) - a1 / 3; + roots[1] = -2 * qSqrt * cos( ( theta + 2 * constants::pi ) / 3 ) - a1 / 3; + roots[2] = -2 * qSqrt * cos( ( theta + 4 * constants::pi ) / 3 ) - a1 / 3; numRoots = 3; } // one real root diff --git a/src/coreComponents/constitutive/fluid/multifluid/compositional/models/ComponentProperties.hpp b/src/coreComponents/constitutive/fluid/multifluid/compositional/models/ComponentProperties.hpp index 903eba1c28a..2dec4ae3c57 100644 --- a/src/coreComponents/constitutive/fluid/multifluid/compositional/models/ComponentProperties.hpp +++ b/src/coreComponents/constitutive/fluid/multifluid/compositional/models/ComponentProperties.hpp @@ -62,7 +62,7 @@ class ComponentProperties final * @brief Get the number of components * @return The number of components */ - integer getNumberOfComponents() const { return m_componentNames.size( 0 ); } + integer getNumberOfComponents() const { return m_componentNames.size(); } struct KernelWrapper { diff --git a/src/coreComponents/constitutive/fluid/multifluid/compositional/models/FunctionBase.hpp b/src/coreComponents/constitutive/fluid/multifluid/compositional/models/FunctionBase.hpp index 81dbb177da7..3ccb6c97f8a 100644 --- a/src/coreComponents/constitutive/fluid/multifluid/compositional/models/FunctionBase.hpp +++ b/src/coreComponents/constitutive/fluid/multifluid/compositional/models/FunctionBase.hpp @@ -45,8 +45,8 @@ enum class FunctionType : integer class FunctionBaseUpdate { public: - - FunctionBaseUpdate() = default; + GEOS_HOST_DEVICE FunctionBaseUpdate(){} + GEOS_HOST_DEVICE FunctionBaseUpdate( FunctionBaseUpdate const & ){} /** * @brief Move the KernelWrapper to the given execution space, optionally touching it. diff --git a/src/coreComponents/constitutive/fluid/multifluid/reactive/chemicalReactions/KineticReactions.hpp b/src/coreComponents/constitutive/fluid/multifluid/reactive/chemicalReactions/KineticReactions.hpp index 7cd552f12a6..9b470dff5c0 100644 --- a/src/coreComponents/constitutive/fluid/multifluid/reactive/chemicalReactions/KineticReactions.hpp +++ b/src/coreComponents/constitutive/fluid/multifluid/reactive/chemicalReactions/KineticReactions.hpp @@ -22,7 +22,7 @@ #include "ReactionsBase.hpp" #include "constitutive/fluid/multifluid/Layouts.hpp" -#include "constitutive/fluid/multifluid/MultiFluidConstants.hpp" +#include "common/PhysicsConstants.hpp" namespace geos { @@ -43,7 +43,7 @@ class KineticReactions : public ReactionsBase { public: - static constexpr real64 RConst = MultiFluidConstants::gasConstant; + static constexpr real64 RConst = constants::gasConstant; KernelWrapper( integer const numPrimarySpecies, integer const numSecondarySpecies, diff --git a/src/coreComponents/constitutive/solid/ElasticOrthotropic.hpp b/src/coreComponents/constitutive/solid/ElasticOrthotropic.hpp index 42a42d545a5..56731703729 100644 --- a/src/coreComponents/constitutive/solid/ElasticOrthotropic.hpp +++ b/src/coreComponents/constitutive/solid/ElasticOrthotropic.hpp @@ -158,7 +158,7 @@ class ElasticOrthotropicUpdates : public SolidBaseUpdates GEOS_HOST_DEVICE virtual real64 getShearModulus( localIndex const k ) const override final { - return std::max( std::max( m_c44[k], m_c55[k] ), m_c66[k] ); + return LvArray::math::max( LvArray::math::max( m_c44[k], m_c55[k] ), m_c66[k] ); } private: diff --git a/src/coreComponents/constitutive/solid/ElasticTransverseIsotropic.hpp b/src/coreComponents/constitutive/solid/ElasticTransverseIsotropic.hpp index 3e361f892d1..de8833cbcb0 100644 --- a/src/coreComponents/constitutive/solid/ElasticTransverseIsotropic.hpp +++ b/src/coreComponents/constitutive/solid/ElasticTransverseIsotropic.hpp @@ -150,7 +150,7 @@ class ElasticTransverseIsotropicUpdates : public SolidBaseUpdates GEOS_HOST_DEVICE virtual real64 getShearModulus( localIndex const k ) const override final { - return std::max( m_c44[k], m_c66[k] ); + return LvArray::math::max( m_c44[k], m_c66[k] ); } From acbed1a29f0a92916a708726a7263b11faffc436 Mon Sep 17 00:00:00 2001 From: tbeltzun <129868353+tbeltzun@users.noreply.github.com> Date: Tue, 12 Dec 2023 04:11:42 +0100 Subject: [PATCH 02/40] IO: filter mesh levels for VTK (#2723) Add the ability to filter mesh levels to avoid saving un-needed data (as e.g. in the case of high-order SEM visualization). --- .../benchmarks/elas3D_benchmark_base.xml | 3 +- .../wavePropagation/elas3D_DAS_smoke.xml | 1 + .../wavePropagation/elas3D_small_base.xml | 1 + integratedTests | 2 +- .../fileIO/Outputs/VTKOutput.cpp | 6 ++++ .../fileIO/Outputs/VTKOutput.hpp | 4 +++ .../fileIO/vtk/VTKPolyDataWriterInterface.cpp | 28 +++++++++++++++---- .../fileIO/vtk/VTKPolyDataWriterInterface.hpp | 11 ++++++++ src/coreComponents/schema/docs/VTK.rst | 1 + src/coreComponents/schema/schema.xsd | 2 ++ 10 files changed, 51 insertions(+), 8 deletions(-) diff --git a/inputFiles/wavePropagation/benchmarks/elas3D_benchmark_base.xml b/inputFiles/wavePropagation/benchmarks/elas3D_benchmark_base.xml index a1759a7b731..36a95b6e247 100644 --- a/inputFiles/wavePropagation/benchmarks/elas3D_benchmark_base.xml +++ b/inputFiles/wavePropagation/benchmarks/elas3D_benchmark_base.xml @@ -220,7 +220,8 @@ + levelNames="{ FE1 }" + plotLevel="3"/> diff --git a/inputFiles/wavePropagation/elas3D_small_base.xml b/inputFiles/wavePropagation/elas3D_small_base.xml index 0ed1a918810..8581b072068 100644 --- a/inputFiles/wavePropagation/elas3D_small_base.xml +++ b/inputFiles/wavePropagation/elas3D_small_base.xml @@ -198,6 +198,7 @@ diff --git a/integratedTests b/integratedTests index 490987ae0c4..d7244a7b383 160000 --- a/integratedTests +++ b/integratedTests @@ -1 +1 @@ -Subproject commit 490987ae0c4f2c0b25a889766d2f1060ffd7ace8 +Subproject commit d7244a7b3839764c0c8328af24563c537fc7dfd0 diff --git a/src/coreComponents/fileIO/Outputs/VTKOutput.cpp b/src/coreComponents/fileIO/Outputs/VTKOutput.cpp index d6c912d7bac..b9b6256bc1a 100644 --- a/src/coreComponents/fileIO/Outputs/VTKOutput.cpp +++ b/src/coreComponents/fileIO/Outputs/VTKOutput.cpp @@ -36,6 +36,7 @@ VTKOutput::VTKOutput( string const & name, m_plotLevel(), m_onlyPlotSpecifiedFieldNames(), m_fieldNames(), + m_levelNames(), m_writer( getOutputDirectory() + '/' + m_plotFileRoot ) { enableLogLevelInput(); @@ -70,6 +71,10 @@ VTKOutput::VTKOutput( string const & name, setInputFlag( InputFlags::OPTIONAL ). setDescription( "Names of the fields to output. If this attribute is specified, GEOSX outputs all the fields specified by the user, regardless of their `plotLevel`" ); + registerWrapper( viewKeysStruct::levelNames, &m_levelNames ). + setInputFlag( InputFlags::OPTIONAL ). + setDescription( "Names of mesh levels to output." ); + registerWrapper( viewKeysStruct::binaryString, &m_writeBinaryData ). setApplyDefaultValue( m_writeBinaryData ). setInputFlag( InputFlags::OPTIONAL ). @@ -88,6 +93,7 @@ void VTKOutput::postProcessInput() { m_writer.setOutputLocation( getOutputDirectory(), m_plotFileRoot ); m_writer.setFieldNames( m_fieldNames.toViewConst() ); + m_writer.setLevelNames( m_levelNames.toViewConst() ); m_writer.setOnlyPlotSpecifiedFieldNamesFlag( m_onlyPlotSpecifiedFieldNames ); string const fieldNamesString = viewKeysStruct::fieldNames; diff --git a/src/coreComponents/fileIO/Outputs/VTKOutput.hpp b/src/coreComponents/fileIO/Outputs/VTKOutput.hpp index 93abd3e865d..c87b92a0513 100644 --- a/src/coreComponents/fileIO/Outputs/VTKOutput.hpp +++ b/src/coreComponents/fileIO/Outputs/VTKOutput.hpp @@ -94,6 +94,7 @@ class VTKOutput : public OutputBase static constexpr auto outputRegionTypeString = "outputRegionType"; static constexpr auto onlyPlotSpecifiedFieldNames = "onlyPlotSpecifiedFieldNames"; static constexpr auto fieldNames = "fieldNames"; + static constexpr auto levelNames = "levelNames"; } vtkOutputViewKeys; /// @endcond @@ -120,6 +121,9 @@ class VTKOutput : public OutputBase /// array of names of the fields to output array1d< string > m_fieldNames; + /// array of names of the mesh levels to output (an empty array means all levels are saved) + array1d< string > m_levelNames; + /// VTK output mode vtk::VTKOutputMode m_writeBinaryData = vtk::VTKOutputMode::BINARY; diff --git a/src/coreComponents/fileIO/vtk/VTKPolyDataWriterInterface.cpp b/src/coreComponents/fileIO/vtk/VTKPolyDataWriterInterface.cpp index 1016b332851..fce83637687 100644 --- a/src/coreComponents/fileIO/vtk/VTKPolyDataWriterInterface.cpp +++ b/src/coreComponents/fileIO/vtk/VTKPolyDataWriterInterface.cpp @@ -1156,14 +1156,24 @@ void VTKPolyDataWriterInterface::writeVtmFile( integer const cycle, { if( meshLevel.isShallowCopy() ) - { return; + + string const & meshLevelName = meshLevel.getName(); + + if( !m_levelNames.empty()) + { + if( m_levelNames.find( meshLevelName ) == m_levelNames.end()) + return; } + string const & meshBodyName = meshBody.getName(); + ElementRegionManager const & elemManager = meshLevel.getElemManager(); + ParticleManager const & particleManager = meshLevel.getParticleManager(); - string const meshPath = joinPath( getCycleSubFolder( cycle ), meshBody.getName(), meshLevel.getName() ); + string const meshPath = joinPath( getCycleSubFolder( cycle ), meshBodyName, meshLevelName ); + int const mpiSize = MpiWrapper::commSize(); auto addElementRegion = [&]( ElementRegionBase const & region ) @@ -1180,8 +1190,9 @@ void VTKPolyDataWriterInterface::writeVtmFile( integer const cycle, auto addParticleRegion = [&]( ParticleRegionBase const & region ) { - std::vector< string > const blockPath{ meshBody.getName(), meshLevel.getName(), region.getCatalogName(), region.getName() }; - string const regionPath = joinPath( meshPath, region.getName() ); + string const & regionName = region.getName(); + std::vector< string > const blockPath{ meshBodyName, meshLevelName, region.getCatalogName(), regionName }; + string const regionPath = joinPath( meshPath, regionName ); for( int i = 0; i < mpiSize; i++ ) { string const dataSetName = getRankFileName( i ); @@ -1290,15 +1301,20 @@ void VTKPolyDataWriterInterface::write( real64 const time, { if( meshLevel.isShallowCopy() ) - { return; + + string const & meshLevelName = meshLevel.getName(); + + if( !m_levelNames.empty()) + { + if( m_levelNames.find( meshLevelName ) == m_levelNames.end()) + return; } ElementRegionManager const & elemManager = meshLevel.getElemManager(); ParticleManager const & particleManager = meshLevel.getParticleManager(); NodeManager const & nodeManager = meshLevel.getNodeManager(); EmbeddedSurfaceNodeManager const & embSurfNodeManager = meshLevel.getEmbSurfNodeManager(); - string const & meshLevelName = meshLevel.getName(); string const & meshBodyName = meshBody.getName(); if( m_requireFieldRegistrationCheck && !m_fieldNames.empty() ) diff --git a/src/coreComponents/fileIO/vtk/VTKPolyDataWriterInterface.hpp b/src/coreComponents/fileIO/vtk/VTKPolyDataWriterInterface.hpp index 8d1fc54290a..1baa383407c 100644 --- a/src/coreComponents/fileIO/vtk/VTKPolyDataWriterInterface.hpp +++ b/src/coreComponents/fileIO/vtk/VTKPolyDataWriterInterface.hpp @@ -148,6 +148,14 @@ class VTKPolyDataWriterInterface m_fieldNames.insert( fieldNames.begin(), fieldNames.end() ); } + /** + * @brief Set the names of the mesh levels to output + * @param[in] levelNames the mesh levels to output (an empty array means all levels are saved) + */ + void setLevelNames( arrayView1d< string const > const & levelNames ) + { + m_levelNames.insert( levelNames.begin(), levelNames.end() ); + } /** * @brief Main method of this class. Write all the files for one time step. @@ -318,6 +326,9 @@ class VTKPolyDataWriterInterface /// Names of the fields to output std::set< string > m_fieldNames; + /// Names of the mesh levels to output (an empty array means all levels are saved) + std::set< string > m_levelNames; + /// The previousCycle integer m_previousCycle; diff --git a/src/coreComponents/schema/docs/VTK.rst b/src/coreComponents/schema/docs/VTK.rst index 4ba3e3b3870..3a376546ee3 100644 --- a/src/coreComponents/schema/docs/VTK.rst +++ b/src/coreComponents/schema/docs/VTK.rst @@ -6,6 +6,7 @@ Name Type Default Description childDirectory string Child directory path fieldNames groupNameRef_array {} Names of the fields to output. If this attribute is specified, GEOSX outputs all the fields specified by the user, regardless of their `plotLevel` format geos_vtk_VTKOutputMode binary Output data format. Valid options: ``binary``, ``ascii`` +levelNames string_array {} Names of mesh levels to output. logLevel integer 0 Log level name groupName required A name is required for any non-unique nodes onlyPlotSpecifiedFieldNames integer 0 If this flag is equal to 1, then we only plot the fields listed in `fieldNames`. Otherwise, we plot all the fields with the required `plotLevel`, plus the fields listed in `fieldNames` diff --git a/src/coreComponents/schema/schema.xsd b/src/coreComponents/schema/schema.xsd index 3498d60b3e9..c9e84b3858e 100644 --- a/src/coreComponents/schema/schema.xsd +++ b/src/coreComponents/schema/schema.xsd @@ -1981,6 +1981,8 @@ the relative residual norm satisfies: + + From 8eab6ef2e4d8bfc26d0d77ba35b36f3c6d7e9474 Mon Sep 17 00:00:00 2001 From: Pavel Tomin Date: Tue, 12 Dec 2023 14:33:02 -0600 Subject: [PATCH 03/40] Fixes for brine density computes, output internally constructed PVT tables for CO2 fluid into CSV files (#2792) Two critical fixes: - Fix for pressure units in calculatePureWaterDensity (bug introduced in Implemented fixes in water properties computation (density, enthalpy) #2686), thanks @jhuang2601 - Fix for phase molar weight - Output activated through logLevel > 0; needed for users to check props correctness and consistency. --- .../co2_flux_3d.xml | 1 + .../thermalMultiphaseFlow/co2_thermal_2d.xml | 1 + integratedTests | 2 +- .../codingUtilities/UnitTestUtilities.hpp | 8 +- src/coreComponents/common/Units.hpp | 4 + .../multifluid/CO2Brine/CO2BrineFluid.cpp | 11 +- .../fluid/multifluid/CO2Brine/PhaseModel.hpp | 12 +- .../CO2Brine/functions/BrineEnthalpy.cpp | 10 +- .../CO2Brine/functions/BrineEnthalpy.hpp | 3 +- .../CO2Brine/functions/CO2Enthalpy.cpp | 7 +- .../CO2Brine/functions/CO2Enthalpy.hpp | 3 +- .../CO2Brine/functions/CO2Solubility.cpp | 7 +- .../CO2Brine/functions/CO2Solubility.hpp | 3 +- .../functions/EzrokhiBrineDensity.cpp | 10 +- .../functions/EzrokhiBrineDensity.hpp | 12 +- .../functions/EzrokhiBrineViscosity.cpp | 7 +- .../functions/EzrokhiBrineViscosity.hpp | 3 +- .../functions/FenghourCO2Viscosity.cpp | 19 +-- .../functions/FenghourCO2Viscosity.hpp | 3 +- .../CO2Brine/functions/FlashModelBase.hpp | 3 +- .../CO2Brine/functions/NoOpPVTFunction.hpp | 5 +- .../CO2Brine/functions/PVTFunctionBase.hpp | 40 ++++- .../functions/PhillipsBrineDensity.cpp | 19 ++- .../functions/PhillipsBrineDensity.hpp | 48 ++---- .../functions/PhillipsBrineViscosity.cpp | 7 +- .../functions/PhillipsBrineViscosity.hpp | 3 +- .../functions/PureWaterProperties.cpp | 15 +- .../functions/SpanWagnerCO2Density.cpp | 7 +- .../functions/SpanWagnerCO2Density.hpp | 3 +- .../CO2Brine/functions/WaterDensity.cpp | 7 +- .../CO2Brine/functions/WaterDensity.hpp | 4 +- .../reactive/ReactiveBrineFluid.cpp | 3 +- .../functions/TableFunction.cpp | 71 +++++++++ .../functions/TableFunction.hpp | 6 + .../fluidFlow/CompositionalMultiphaseBase.cpp | 2 +- .../CompositionalMultiphaseStatistics.cpp | 147 +++++++++++++----- .../CompositionalMultiphaseStatistics.hpp | 15 +- .../wells/CompositionalMultiphaseWell.cpp | 4 +- .../fluidFlow/wells/SinglePhaseWell.cpp | 5 +- .../multiphysics/MultiphasePoromechanics.cpp | 2 +- .../SolidMechanicsStatistics.cpp | 66 ++++++-- .../SolidMechanicsStatistics.hpp | 7 +- .../BrooksCoreyStone2RelativePermeability.rst | 19 +++ ...sCoreyStone2RelativePermeability_other.rst | 15 ++ .../schema/docs/CO2BrineEzrokhiFluid.rst | 1 + .../docs/CO2BrineEzrokhiThermalFluid.rst | 1 + .../schema/docs/CO2BrinePhillipsFluid.rst | 1 + .../docs/CO2BrinePhillipsThermalFluid.rst | 1 + ...tionalMultiphaseReservoirPoromechanics.rst | 24 +++ ...seReservoirPoromechanicsInitialization.rst | 12 ++ ...rvoirPoromechanicsInitialization_other.rst | 9 ++ ...MultiphaseReservoirPoromechanics_other.rst | 15 ++ .../SinglePhaseReservoirPoromechanics.rst | 18 +++ ...seReservoirPoromechanicsInitialization.rst | 12 ++ ...rvoirPoromechanicsInitialization_other.rst | 9 ++ ...inglePhaseReservoirPoromechanics_other.rst | 15 ++ ...VanGenuchtenStone2RelativePermeability.rst | 19 +++ ...uchtenStone2RelativePermeability_other.rst | 15 ++ src/coreComponents/schema/schema.xsd | 8 + .../testCO2BrinePVTModels.cpp | 6 +- .../constitutiveTests/testMultiFluid.cpp | 24 +-- 61 files changed, 662 insertions(+), 177 deletions(-) create mode 100644 src/coreComponents/schema/docs/BrooksCoreyStone2RelativePermeability.rst create mode 100644 src/coreComponents/schema/docs/BrooksCoreyStone2RelativePermeability_other.rst create mode 100644 src/coreComponents/schema/docs/CompositionalMultiphaseReservoirPoromechanics.rst create mode 100644 src/coreComponents/schema/docs/CompositionalMultiphaseReservoirPoromechanicsInitialization.rst create mode 100644 src/coreComponents/schema/docs/CompositionalMultiphaseReservoirPoromechanicsInitialization_other.rst create mode 100644 src/coreComponents/schema/docs/CompositionalMultiphaseReservoirPoromechanics_other.rst create mode 100644 src/coreComponents/schema/docs/SinglePhaseReservoirPoromechanics.rst create mode 100644 src/coreComponents/schema/docs/SinglePhaseReservoirPoromechanicsInitialization.rst create mode 100644 src/coreComponents/schema/docs/SinglePhaseReservoirPoromechanicsInitialization_other.rst create mode 100644 src/coreComponents/schema/docs/SinglePhaseReservoirPoromechanics_other.rst create mode 100644 src/coreComponents/schema/docs/VanGenuchtenStone2RelativePermeability.rst create mode 100644 src/coreComponents/schema/docs/VanGenuchtenStone2RelativePermeability_other.rst diff --git a/inputFiles/compositionalMultiphaseFlow/co2_flux_3d.xml b/inputFiles/compositionalMultiphaseFlow/co2_flux_3d.xml index f3334b3e9d4..e0b3cecde11 100644 --- a/inputFiles/compositionalMultiphaseFlow/co2_flux_3d.xml +++ b/inputFiles/compositionalMultiphaseFlow/co2_flux_3d.xml @@ -119,6 +119,7 @@ absTol && delta > relTol * value ) + if( delta > absTol && delta > relTol * (value + 1.0) ) { return ::testing::AssertionFailure() << std::scientific << std::setprecision( 5 ) - << " relative error: " << delta / value + << " error norm: " << delta / (value + 1.0) << " (" << v1 << " vs " << v2 << ")," - << " exceeds " << relTol <<". " + << " exceeds " << relTol << ". " << " absolute error: " << delta << " exeeds " - << absTol <:: CO2BrineFluid( string const & name, Group * const parent ): MultiFluidBase( name, parent ) { + enableLogLevelInput(); + registerWrapper( viewKeyStruct::phasePVTParaFilesString(), &m_phasePVTParaFiles ). setInputFlag( InputFlags::REQUIRED ). setRestartFlags( RestartFlags::NO_WRITE ). @@ -311,8 +313,10 @@ void CO2BrineFluid< PHASE1, PHASE2, FLASH >::createPVTModels() InputError ); // then, we are ready to instantiate the phase models - m_phase1 = std::make_unique< PHASE1 >( getName() + "_phaseModel1", phase1InputParams, m_componentNames, m_componentMolarWeight ); - m_phase2 = std::make_unique< PHASE2 >( getName() + "_phaseModel2", phase2InputParams, m_componentNames, m_componentMolarWeight ); + m_phase1 = std::make_unique< PHASE1 >( getName() + "_phaseModel1", phase1InputParams, m_componentNames, m_componentMolarWeight, + getLogLevel() > 0 && logger::internal::rank==0 ); + m_phase2 = std::make_unique< PHASE2 >( getName() + "_phaseModel2", phase2InputParams, m_componentNames, m_componentMolarWeight, + getLogLevel() > 0 && logger::internal::rank==0 ); // 2) Create the flash model { @@ -336,7 +340,8 @@ void CO2BrineFluid< PHASE1, PHASE2, FLASH >::createPVTModels() strs, m_phaseNames, m_componentNames, - m_componentMolarWeight ); + m_componentMolarWeight, + getLogLevel() > 0 && logger::internal::rank==0 ); } } else diff --git a/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/PhaseModel.hpp b/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/PhaseModel.hpp index 1fd0fea36a6..9b8d794ee58 100644 --- a/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/PhaseModel.hpp +++ b/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/PhaseModel.hpp @@ -57,19 +57,23 @@ struct PhaseModel PhaseModel( string const & phaseModelName, array1d< array1d< string > > const & inputParams, string_array const & componentNames, - array1d< real64 > const & componentMolarWeight ) + array1d< real64 > const & componentMolarWeight, + bool const printTable ) : density( phaseModelName + "_" + Density::catalogName(), inputParams[InputParamOrder::DENSITY], componentNames, - componentMolarWeight ), + componentMolarWeight, + printTable ), viscosity( phaseModelName + "_" + Viscosity::catalogName(), inputParams[InputParamOrder::VISCOSITY], componentNames, - componentMolarWeight ), + componentMolarWeight, + printTable ), enthalpy( phaseModelName + "_" + Enthalpy::catalogName(), inputParams[InputParamOrder::ENTHALPY], componentNames, - componentMolarWeight ) + componentMolarWeight, + printTable ) {} /// The phase density model diff --git a/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/BrineEnthalpy.cpp b/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/BrineEnthalpy.cpp index 4707c3179d3..8c21a92c8f6 100644 --- a/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/BrineEnthalpy.cpp +++ b/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/BrineEnthalpy.cpp @@ -183,7 +183,8 @@ TableFunction const * makeBrineEnthalpyTable( string_array const & inputParams, BrineEnthalpy::BrineEnthalpy( string const & name, string_array const & inputParams, string_array const & componentNames, - array1d< real64 > const & componentMolarWeight ): + array1d< real64 > const & componentMolarWeight, + bool const printTable ): PVTFunctionBase( name, componentNames, componentMolarWeight ) @@ -196,6 +197,11 @@ BrineEnthalpy::BrineEnthalpy( string const & name, m_CO2EnthalpyTable = makeCO2EnthalpyTable( inputParams, m_functionName, FunctionManager::getInstance() ); m_brineEnthalpyTable = makeBrineEnthalpyTable( inputParams, m_functionName, FunctionManager::getInstance() ); + if( printTable ) + { + m_CO2EnthalpyTable->print( m_CO2EnthalpyTable->getName() ); + m_brineEnthalpyTable->print( m_brineEnthalpyTable->getName() ); + } } void BrineEnthalpy::checkTablesParameters( real64 const pressure, @@ -219,7 +225,7 @@ BrineEnthalpy::createKernelWrapper() const m_waterIndex ); } -REGISTER_CATALOG_ENTRY( PVTFunctionBase, BrineEnthalpy, string const &, string_array const &, string_array const &, array1d< real64 > const & ) +REGISTER_CATALOG_ENTRY( PVTFunctionBase, BrineEnthalpy, string const &, string_array const &, string_array const &, array1d< real64 > const &, bool const ) } // namespace PVTProps diff --git a/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/BrineEnthalpy.hpp b/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/BrineEnthalpy.hpp index 4f63eb07e36..1017ed50fea 100644 --- a/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/BrineEnthalpy.hpp +++ b/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/BrineEnthalpy.hpp @@ -90,7 +90,8 @@ class BrineEnthalpy : public PVTFunctionBase BrineEnthalpy( string const & name, string_array const & inputParams, string_array const & componentNames, - array1d< real64 > const & componentMolarWeight ); + array1d< real64 > const & componentMolarWeight, + bool const printTable ); static string catalogName() { return "BrineEnthalpy"; } diff --git a/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/CO2Enthalpy.cpp b/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/CO2Enthalpy.cpp index afde18ff57b..eb2875471d9 100644 --- a/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/CO2Enthalpy.cpp +++ b/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/CO2Enthalpy.cpp @@ -251,7 +251,8 @@ TableFunction const * makeCO2EnthalpyTable( string_array const & inputParams, CO2Enthalpy::CO2Enthalpy( string const & name, string_array const & inputParams, string_array const & componentNames, - array1d< real64 > const & componentMolarWeight ): + array1d< real64 > const & componentMolarWeight, + bool const printTable ): PVTFunctionBase( name, componentNames, componentMolarWeight ) @@ -260,6 +261,8 @@ CO2Enthalpy::CO2Enthalpy( string const & name, m_CO2Index = PVTFunctionHelpers::findName( componentNames, expectedCO2ComponentNames, "componentNames" ); m_CO2EnthalpyTable = makeCO2EnthalpyTable( inputParams, m_functionName, FunctionManager::getInstance() ); + if( printTable ) + m_CO2EnthalpyTable->print( m_CO2EnthalpyTable->getName() ); } @@ -301,7 +304,7 @@ CO2Enthalpy::createKernelWrapper() const m_CO2Index ); } -REGISTER_CATALOG_ENTRY( PVTFunctionBase, CO2Enthalpy, string const &, string_array const &, string_array const &, array1d< real64 > const & ) +REGISTER_CATALOG_ENTRY( PVTFunctionBase, CO2Enthalpy, string const &, string_array const &, string_array const &, array1d< real64 > const &, bool const ) } // namespace PVTProps diff --git a/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/CO2Enthalpy.hpp b/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/CO2Enthalpy.hpp index 96d335fd66f..5ce85a9645d 100644 --- a/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/CO2Enthalpy.hpp +++ b/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/CO2Enthalpy.hpp @@ -78,7 +78,8 @@ class CO2Enthalpy : public PVTFunctionBase CO2Enthalpy( string const & name, string_array const & inputParams, string_array const & componentNames, - array1d< real64 > const & componentMolarWeight ); + array1d< real64 > const & componentMolarWeight, + bool const printTable ); static string catalogName() { return "CO2Enthalpy"; } diff --git a/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/CO2Solubility.cpp b/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/CO2Solubility.cpp index eaf6f9efec4..0cd74175e64 100644 --- a/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/CO2Solubility.cpp +++ b/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/CO2Solubility.cpp @@ -267,7 +267,8 @@ CO2Solubility::CO2Solubility( string const & name, string_array const & inputParams, string_array const & phaseNames, string_array const & componentNames, - array1d< real64 > const & componentMolarWeight ): + array1d< real64 > const & componentMolarWeight, + bool const printTable ): FlashModelBase( name, componentNames, componentMolarWeight ) @@ -292,6 +293,8 @@ CO2Solubility::CO2Solubility( string const & name, m_phaseLiquidIndex = PVTFunctionHelpers::findName( phaseNames, expectedWaterPhaseNames, "phaseNames" ); m_CO2SolubilityTable = makeSolubilityTable( inputParams, m_modelName, FunctionManager::getInstance() ); + if( printTable ) + m_CO2SolubilityTable->print( m_CO2SolubilityTable->getName() ); } void CO2Solubility::checkTablesParameters( real64 const pressure, @@ -311,7 +314,7 @@ CO2Solubility::KernelWrapper CO2Solubility::createKernelWrapper() const m_phaseLiquidIndex ); } -REGISTER_CATALOG_ENTRY( FlashModelBase, CO2Solubility, string const &, string_array const &, string_array const &, string_array const &, array1d< real64 > const & ) +REGISTER_CATALOG_ENTRY( FlashModelBase, CO2Solubility, string const &, string_array const &, string_array const &, string_array const &, array1d< real64 > const &, bool const ) } // end namespace PVTProps diff --git a/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/CO2Solubility.hpp b/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/CO2Solubility.hpp index bd12c9a7870..77fa7562fb3 100644 --- a/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/CO2Solubility.hpp +++ b/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/CO2Solubility.hpp @@ -99,7 +99,8 @@ class CO2Solubility : public FlashModelBase string_array const & inputParams, string_array const & phaseNames, string_array const & componentNames, - array1d< real64 > const & componentMolarWeight ); + array1d< real64 > const & componentMolarWeight, + bool const printTable ); static string catalogName() { return "CO2Solubility"; } diff --git a/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/EzrokhiBrineDensity.cpp b/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/EzrokhiBrineDensity.cpp index beb3fa855ff..ec24045a0fa 100644 --- a/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/EzrokhiBrineDensity.cpp +++ b/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/EzrokhiBrineDensity.cpp @@ -36,7 +36,8 @@ namespace PVTProps EzrokhiBrineDensity::EzrokhiBrineDensity( string const & name, string_array const & inputPara, string_array const & componentNames, - array1d< real64 > const & componentMolarWeight ): + array1d< real64 > const & componentMolarWeight, + bool const printTable ): PVTFunctionBase( name, componentNames, componentMolarWeight ) @@ -50,6 +51,11 @@ EzrokhiBrineDensity::EzrokhiBrineDensity( string const & name, makeCoefficients( inputPara ); m_waterSatDensityTable = PureWaterProperties::makeSaturationDensityTable( m_functionName, FunctionManager::getInstance() ); m_waterSatPressureTable = PureWaterProperties::makeSaturationPressureTable( m_functionName, FunctionManager::getInstance() ); + if( printTable ) + { + m_waterSatDensityTable->print( m_waterSatDensityTable->getName() ); + m_waterSatPressureTable->print( m_waterSatPressureTable->getName() ); + } } void EzrokhiBrineDensity::makeCoefficients( string_array const & inputPara ) @@ -96,7 +102,7 @@ EzrokhiBrineDensity::createKernelWrapper() const m_coef2 ); } -REGISTER_CATALOG_ENTRY( PVTFunctionBase, EzrokhiBrineDensity, string const &, string_array const &, string_array const &, array1d< real64 > const & ) +REGISTER_CATALOG_ENTRY( PVTFunctionBase, EzrokhiBrineDensity, string const &, string_array const &, string_array const &, array1d< real64 > const &, bool const ) } // end namespace PVTProps diff --git a/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/EzrokhiBrineDensity.hpp b/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/EzrokhiBrineDensity.hpp index 9acdd42e7e8..0f66d19ba4e 100644 --- a/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/EzrokhiBrineDensity.hpp +++ b/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/EzrokhiBrineDensity.hpp @@ -107,7 +107,8 @@ class EzrokhiBrineDensity : public PVTFunctionBase EzrokhiBrineDensity( string const & name, string_array const & inputPara, string_array const & componentNames, - array1d< real64 > const & componentMolarWeight ); + array1d< real64 > const & componentMolarWeight, + bool const printTable ); virtual ~EzrokhiBrineDensity() override = default; @@ -201,20 +202,23 @@ void EzrokhiBrineDensityUpdate::compute( real64 const & pressure, // compute only common part of derivatives w.r.t. CO2 and water phase compositions // later to be multiplied by (phaseComposition[m_waterIndex]) and ( -phaseComposition[m_CO2Index] ) respectively real64 const exponent_dPhaseComp = coefPhaseComposition * m_componentMolarWeight[m_CO2Index] * m_componentMolarWeight[m_waterIndex] * waterMWInv * waterMWInv; - real64 const exponentPowered = useMass ? pow( 10, exponent ) : pow( 10, exponent ) / m_componentMolarWeight[m_waterIndex]; + real64 exponentPowered = pow( 10, exponent ); value = waterDensity * exponentPowered; real64 const dValueCoef = LvArray::math::log( 10 ) * value; - - real64 const dValue_dPhaseComp = dValueCoef * exponent_dPhaseComp; dValue[Deriv::dP] = dValueCoef * exponent_dPressure + waterDensity_dPressure * exponentPowered; dValue[Deriv::dT] = dValueCoef * exponent_dTemperature + waterDensity_dTemperature * exponentPowered; // here, we multiply common part of derivatives by specific coefficients + real64 const dValue_dPhaseComp = dValueCoef * exponent_dPhaseComp; dValue[Deriv::dC+m_CO2Index] = dValue_dPhaseComp * phaseComposition[m_waterIndex] * dPhaseComposition[m_CO2Index][Deriv::dC+m_CO2Index]; dValue[Deriv::dC+m_waterIndex] = dValue_dPhaseComp * ( -phaseComposition[m_CO2Index] ) * dPhaseComposition[m_waterIndex][Deriv::dC+m_waterIndex]; + if( !useMass ) + { + divideByPhaseMolarWeight( phaseComposition, dPhaseComposition, value, dValue ); + } } } // end namespace PVTProps diff --git a/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/EzrokhiBrineViscosity.cpp b/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/EzrokhiBrineViscosity.cpp index 426a63c9a72..ec63aa192a9 100644 --- a/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/EzrokhiBrineViscosity.cpp +++ b/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/EzrokhiBrineViscosity.cpp @@ -36,7 +36,8 @@ namespace PVTProps EzrokhiBrineViscosity::EzrokhiBrineViscosity( string const & name, string_array const & inputPara, string_array const & componentNames, - array1d< real64 > const & componentMolarWeight ): + array1d< real64 > const & componentMolarWeight, + bool const printTable ): PVTFunctionBase( name, componentNames, componentMolarWeight ) @@ -49,6 +50,8 @@ EzrokhiBrineViscosity::EzrokhiBrineViscosity( string const & name, makeCoefficients( inputPara ); m_waterViscosityTable = PureWaterProperties::makeSaturationViscosityTable( m_functionName, FunctionManager::getInstance() ); + if( printTable ) + m_waterViscosityTable->print( m_waterViscosityTable->getName() ); } void EzrokhiBrineViscosity::makeCoefficients( string_array const & inputPara ) @@ -90,7 +93,7 @@ EzrokhiBrineViscosity::createKernelWrapper() const m_coef2 ); } -REGISTER_CATALOG_ENTRY( PVTFunctionBase, EzrokhiBrineViscosity, string const &, string_array const &, string_array const &, array1d< real64 > const & ) +REGISTER_CATALOG_ENTRY( PVTFunctionBase, EzrokhiBrineViscosity, string const &, string_array const &, string_array const &, array1d< real64 > const &, bool const ) } // end namespace PVTProps diff --git a/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/EzrokhiBrineViscosity.hpp b/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/EzrokhiBrineViscosity.hpp index 2afdb6873aa..f007f573f83 100644 --- a/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/EzrokhiBrineViscosity.hpp +++ b/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/EzrokhiBrineViscosity.hpp @@ -96,7 +96,8 @@ class EzrokhiBrineViscosity : public PVTFunctionBase EzrokhiBrineViscosity( string const & name, string_array const & inputPara, string_array const & componentNames, - array1d< real64 > const & componentMolarWeight ); + array1d< real64 > const & componentMolarWeight, + bool const printTable ); virtual ~EzrokhiBrineViscosity() override = default; diff --git a/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/FenghourCO2Viscosity.cpp b/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/FenghourCO2Viscosity.cpp index 6320be58bda..9f7ed16bc4a 100644 --- a/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/FenghourCO2Viscosity.cpp +++ b/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/FenghourCO2Viscosity.cpp @@ -34,9 +34,8 @@ namespace PVTProps namespace { -void fenghourCO2ViscosityFunction( real64 const & temperatureCent, - real64 const & density, - real64 & viscosity ) +real64 fenghourCO2ViscosityFunction( real64 const & temperatureCent, + real64 const & density ) { constexpr real64 espar = 251.196; constexpr real64 esparInv = 1.0 / espar; @@ -67,7 +66,7 @@ void fenghourCO2ViscosityFunction( real64 const & temperatureCent, real64 const vxcess = density * (d11 + density * (d21 + d2*d2*(d64 / (Tred*Tred*Tred) + d2*(d81 + d82/Tred)))); // equation (1) of Fenghour and Wakeham (1998) - viscosity = 1e-6 * (vlimit + vxcess + vcrit); + return 1e-6 * (vlimit + vxcess + vcrit); } void calculateCO2Viscosity( PTTableCoordinates const & tableCoords, @@ -82,9 +81,8 @@ void calculateCO2Viscosity( PTTableCoordinates const & tableCoords, { for( localIndex j = 0; j < nTemperatures; ++j ) { - fenghourCO2ViscosityFunction( tableCoords.getTemperature( j ), - densities[j*nPressures+i], - viscosities[j*nPressures+i] ); + real64 const T = tableCoords.getTemperature( j ); + viscosities[j*nPressures+i] = fenghourCO2ViscosityFunction( T, densities[j*nPressures+i] ); } } } @@ -137,12 +135,15 @@ TableFunction const * makeViscosityTable( string_array const & inputParams, FenghourCO2Viscosity::FenghourCO2Viscosity( string const & name, string_array const & inputParams, string_array const & componentNames, - array1d< real64 > const & componentMolarWeight ) + array1d< real64 > const & componentMolarWeight, + bool const printTable ) : PVTFunctionBase( name, componentNames, componentMolarWeight ) { m_CO2ViscosityTable = makeViscosityTable( inputParams, m_functionName, FunctionManager::getInstance() ); + if( printTable ) + m_CO2ViscosityTable->print( m_CO2ViscosityTable->getName() ); } void FenghourCO2Viscosity::checkTablesParameters( real64 const pressure, @@ -159,7 +160,7 @@ FenghourCO2Viscosity::createKernelWrapper() const *m_CO2ViscosityTable ); } -REGISTER_CATALOG_ENTRY( PVTFunctionBase, FenghourCO2Viscosity, string const &, string_array const &, string_array const &, array1d< real64 > const & ) +REGISTER_CATALOG_ENTRY( PVTFunctionBase, FenghourCO2Viscosity, string const &, string_array const &, string_array const &, array1d< real64 > const &, bool const ) } // end namespace PVTProps diff --git a/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/FenghourCO2Viscosity.hpp b/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/FenghourCO2Viscosity.hpp index 1aa208afc3f..e8205e8745b 100644 --- a/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/FenghourCO2Viscosity.hpp +++ b/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/FenghourCO2Viscosity.hpp @@ -74,7 +74,8 @@ class FenghourCO2Viscosity : public PVTFunctionBase FenghourCO2Viscosity( string const & name, string_array const & inputParams, string_array const & componentNames, - array1d< real64 > const & componentMolarWeight ); + array1d< real64 > const & componentMolarWeight, + bool const printTable ); virtual ~FenghourCO2Viscosity() override = default; diff --git a/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/FlashModelBase.hpp b/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/FlashModelBase.hpp index f7de275e572..314f7498dde 100644 --- a/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/FlashModelBase.hpp +++ b/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/FlashModelBase.hpp @@ -82,7 +82,8 @@ class FlashModelBase string_array const &, string_array const &, string_array const &, - array1d< real64 > const & >; + array1d< real64 > const &, + bool const >; static typename CatalogInterface::CatalogType & getCatalog() { static CatalogInterface::CatalogType catalog; diff --git a/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/NoOpPVTFunction.hpp b/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/NoOpPVTFunction.hpp index 67b57bda644..47721c43539 100644 --- a/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/NoOpPVTFunction.hpp +++ b/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/NoOpPVTFunction.hpp @@ -69,12 +69,13 @@ class NoOpPVTFunction : public PVTFunctionBase NoOpPVTFunction( string const & name, string_array const & inputPara, string_array const & componentNames, - array1d< real64 > const & componentMolarWeight ) + array1d< real64 > const & componentMolarWeight, + bool const printTable ) : PVTFunctionBase( name, componentNames, componentMolarWeight ) { - GEOS_UNUSED_VAR( inputPara ); + GEOS_UNUSED_VAR( inputPara, printTable ); } virtual ~NoOpPVTFunction() override = default; diff --git a/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/PVTFunctionBase.hpp b/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/PVTFunctionBase.hpp index bff7ea00f7a..7bfea2c71a8 100644 --- a/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/PVTFunctionBase.hpp +++ b/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/PVTFunctionBase.hpp @@ -54,6 +54,43 @@ class PVTFunctionBaseUpdate protected: + template< int USD > + GEOS_HOST_DEVICE + real64 computePhaseMolarWeight( arraySlice1d< real64 const, USD > const & phaseComposition ) const + { + integer const numComp = phaseComposition.size(); + real64 MT = 0.0; + for( integer i = 0; i < numComp; i++ ) + { + MT += phaseComposition[i] * m_componentMolarWeight[i]; + } + return MT; + } + + template< int USD1, int USD2, int USD3 > + GEOS_HOST_DEVICE + void divideByPhaseMolarWeight( arraySlice1d< real64 const, USD1 > const & phaseComposition, + arraySlice2d< real64 const, USD2 > const & dPhaseComposition, + real64 & value, arraySlice1d< real64, USD3 > const & dValue ) const + { + integer const numComp = phaseComposition.size(); + integer const numDerivs = dValue.size(); + + real64 const MT = computePhaseMolarWeight( phaseComposition ); + + value /= MT; + + for( int der = 0; der < numDerivs; der++ ) + { + real64 dMT = 0.0; + for( int ic = 0; ic < numComp; ic++ ) + { + dMT += dPhaseComposition[ic][der] * m_componentMolarWeight[ic]; + } + dValue[der] = ( dValue[der] - value * dMT ) / MT; // value is already divided by MT + } + } + /// Array storing the component molar weights arrayView1d< real64 const > m_componentMolarWeight; @@ -79,7 +116,8 @@ class PVTFunctionBase string const &, array1d< string > const &, array1d< string > const &, - array1d< real64 > const & >; + array1d< real64 > const &, + bool const >; static typename CatalogInterface::CatalogType & getCatalog() { static CatalogInterface::CatalogType catalog; diff --git a/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/PhillipsBrineDensity.cpp b/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/PhillipsBrineDensity.cpp index 5b16d60080d..0cb55b9d879 100644 --- a/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/PhillipsBrineDensity.cpp +++ b/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/PhillipsBrineDensity.cpp @@ -61,9 +61,10 @@ void calculateBrineDensity( PTTableCoordinates const & tableCoords, for( localIndex j = 0; j < nTemperatures; ++j ) { + real64 const T = tableCoords.getTemperature( j ); // see Phillips et al. (1981), equations (4) and (5), pages 14 and 15 real64 const x = c1 * exp( a1 * salinity ) - + c2 * exp( a2 * tableCoords.getTemperature( j ) ) + + c2 * exp( a2 * T ) + c3 * exp( a3 * P ); densities[j*nPressures+i] = (AA + BB * x + CC * x * x + DD * x * x * x) * 1000.0; } @@ -89,7 +90,7 @@ void calculatePureWaterDensity( PTTableCoordinates const & tableCoords, for( localIndex i = 0; i < nPressures; ++i ) { - real64 const P = tableCoords.getPressure( i ) / 1e5; + real64 const P = tableCoords.getPressure( i ); for( localIndex j = 0; j < nTemperatures; ++j ) { @@ -112,14 +113,15 @@ TableFunction const * makeDensityTable( string_array const & inputParams, string const & functionName, FunctionManager & functionManager ) { + GEOS_THROW_IF_LT_MSG( inputParams.size(), 9, + GEOS_FMT( "{}: insufficient number of model parameters", functionName ), + InputError ); + // initialize the (p,T) coordinates PTTableCoordinates tableCoords; PVTFunctionHelpers::initializePropertyTable( inputParams, tableCoords ); // initialize salinity - GEOS_THROW_IF_LT_MSG( inputParams.size(), 9, - GEOS_FMT( "{}: insufficient number of model parameters", functionName ), - InputError ); real64 salinity; try { @@ -168,7 +170,8 @@ TableFunction const * makeDensityTable( string_array const & inputParams, PhillipsBrineDensity::PhillipsBrineDensity( string const & name, string_array const & inputParams, string_array const & componentNames, - array1d< real64 > const & componentMolarWeight ): + array1d< real64 > const & componentMolarWeight, + bool const printTable ): PVTFunctionBase( name, componentNames, componentMolarWeight ) @@ -180,6 +183,8 @@ PhillipsBrineDensity::PhillipsBrineDensity( string const & name, m_waterIndex = PVTFunctionHelpers::findName( componentNames, expectedWaterComponentNames, "componentNames" ); m_brineDensityTable = makeDensityTable( inputParams, m_functionName, FunctionManager::getInstance() ); + if( printTable ) + m_brineDensityTable->print( m_brineDensityTable->getName() ); } PhillipsBrineDensity::KernelWrapper @@ -198,7 +203,7 @@ void PhillipsBrineDensity::checkTablesParameters( real64 const pressure, m_brineDensityTable->checkCoord( temperature, 1 ); } -REGISTER_CATALOG_ENTRY( PVTFunctionBase, PhillipsBrineDensity, string const &, string_array const &, string_array const &, array1d< real64 > const & ) +REGISTER_CATALOG_ENTRY( PVTFunctionBase, PhillipsBrineDensity, string const &, string_array const &, string_array const &, array1d< real64 > const &, bool const ) } // namespace PVTProps diff --git a/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/PhillipsBrineDensity.hpp b/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/PhillipsBrineDensity.hpp index 26381194127..85208dd7e70 100644 --- a/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/PhillipsBrineDensity.hpp +++ b/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/PhillipsBrineDensity.hpp @@ -84,7 +84,8 @@ class PhillipsBrineDensity : public PVTFunctionBase PhillipsBrineDensity( string const & name, string_array const & inputParams, string_array const & componentNames, - array1d< real64 > const & componentMolarWeight ); + array1d< real64 > const & componentMolarWeight, + bool const printTable ); static string catalogName() { return "PhillipsBrineDensity"; } @@ -187,37 +188,22 @@ void PhillipsBrineDensityUpdate::compute( real64 const & pressure, // Brine density // equation (1) from Garcia (2001) - if( useMass ) + value = density + + m_componentMolarWeight[m_CO2Index] * conc + - concDensVol; + dValue[Deriv::dP] = densityDeriv[0] + + m_componentMolarWeight[m_CO2Index] * dConc_dPres + - dConcDensVol_dPres; + dValue[Deriv::dT] = densityDeriv[1] + + m_componentMolarWeight[m_CO2Index] * dConc_dTemp + - dConcDensVol_dTemp; + dValue[Deriv::dC+m_CO2Index] = m_componentMolarWeight[m_CO2Index] * dConc_dComp[m_CO2Index] + - dConcDensVol_dComp[m_CO2Index]; + dValue[Deriv::dC+m_waterIndex] = m_componentMolarWeight[m_CO2Index] * dConc_dComp[m_waterIndex] + - dConcDensVol_dComp[m_waterIndex]; + if( !useMass ) { - value = density - + m_componentMolarWeight[m_CO2Index] * conc - - concDensVol; - dValue[Deriv::dP] = densityDeriv[0] - + m_componentMolarWeight[m_CO2Index] * dConc_dPres - - dConcDensVol_dPres; - dValue[Deriv::dT] = densityDeriv[1] - + m_componentMolarWeight[m_CO2Index] * dConc_dTemp - - dConcDensVol_dTemp; - dValue[Deriv::dC+m_CO2Index] = m_componentMolarWeight[m_CO2Index] * dConc_dComp[m_CO2Index] - - dConcDensVol_dComp[m_CO2Index]; - dValue[Deriv::dC+m_waterIndex] = m_componentMolarWeight[m_CO2Index] * dConc_dComp[m_waterIndex] - - dConcDensVol_dComp[m_waterIndex]; - } - else - { - value = density / m_componentMolarWeight[m_waterIndex] - + conc - - concDensVol / m_componentMolarWeight[m_waterIndex]; - dValue[Deriv::dP] = densityDeriv[0] / m_componentMolarWeight[m_waterIndex] - + dConc_dPres - - dConcDensVol_dPres / m_componentMolarWeight[m_waterIndex]; - dValue[Deriv::dT] = densityDeriv[1] / m_componentMolarWeight[m_waterIndex] - + dConc_dTemp - - dConcDensVol_dTemp / m_componentMolarWeight[m_waterIndex]; - dValue[Deriv::dC+m_CO2Index] = dConc_dComp[m_CO2Index] - - dConcDensVol_dComp[m_CO2Index] / m_componentMolarWeight[m_waterIndex]; - dValue[Deriv::dC+m_waterIndex] = dConc_dComp[m_waterIndex] - - dConcDensVol_dComp[m_waterIndex] / m_componentMolarWeight[m_waterIndex]; + divideByPhaseMolarWeight( phaseComposition, dPhaseComposition, value, dValue ); } } diff --git a/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/PhillipsBrineViscosity.cpp b/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/PhillipsBrineViscosity.cpp index 8c818d5fe60..0c8c8466256 100644 --- a/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/PhillipsBrineViscosity.cpp +++ b/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/PhillipsBrineViscosity.cpp @@ -35,12 +35,15 @@ namespace PVTProps PhillipsBrineViscosity::PhillipsBrineViscosity( string const & name, string_array const & inputPara, string_array const & componentNames, - array1d< real64 > const & componentMolarWeight ): + array1d< real64 > const & componentMolarWeight, + bool const printTable ): PVTFunctionBase( name, componentNames, componentMolarWeight ) { m_waterViscosityTable = PureWaterProperties::makeSaturationViscosityTable( m_functionName, FunctionManager::getInstance() ); + if( printTable ) + m_waterViscosityTable->print( m_waterViscosityTable->getName() ); makeCoefficients( inputPara ); } @@ -88,7 +91,7 @@ PhillipsBrineViscosity::createKernelWrapper() const m_coef1 ); } -REGISTER_CATALOG_ENTRY( PVTFunctionBase, PhillipsBrineViscosity, string const &, string_array const &, string_array const &, array1d< real64 > const & ) +REGISTER_CATALOG_ENTRY( PVTFunctionBase, PhillipsBrineViscosity, string const &, string_array const &, string_array const &, array1d< real64 > const &, bool const ) } // end namespace PVTProps diff --git a/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/PhillipsBrineViscosity.hpp b/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/PhillipsBrineViscosity.hpp index 90b2440c399..8edc4419d64 100644 --- a/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/PhillipsBrineViscosity.hpp +++ b/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/PhillipsBrineViscosity.hpp @@ -82,7 +82,8 @@ class PhillipsBrineViscosity : public PVTFunctionBase PhillipsBrineViscosity( string const & name, string_array const & inputPara, string_array const & componentNames, - array1d< real64 > const & componentMolarWeight ); + array1d< real64 > const & componentMolarWeight, + bool const printTable ); virtual ~PhillipsBrineViscosity() override = default; diff --git a/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/PureWaterProperties.cpp b/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/PureWaterProperties.cpp index 94d1bda2a57..af9a4f2c6da 100644 --- a/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/PureWaterProperties.cpp +++ b/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/PureWaterProperties.cpp @@ -36,9 +36,10 @@ PureWaterProperties::makeSaturationViscosityTable( string const & functionName, array1d< array1d< real64 > > temperatures; array1d< real64 > viscosities; + integer const nValues = 26; temperatures.resize( 1 ); - temperatures[0].resize( 26 ); - viscosities.resize( 26 ); + temperatures[0].resize( nValues ); + viscosities.resize( nValues ); temperatures[0][0] = 0.01; temperatures[0][1] = 10; @@ -116,9 +117,10 @@ PureWaterProperties::makeSaturationDensityTable( string const & functionName, array1d< array1d< real64 > > temperatures; array1d< real64 > densities; + integer const nValues = 26; temperatures.resize( 1 ); - temperatures[0].resize( 26 ); - densities.resize( 26 ); + temperatures[0].resize( nValues ); + densities.resize( nValues ); temperatures[0][0] = 0.01; temperatures[0][1] = 10; @@ -196,9 +198,10 @@ PureWaterProperties::makeSaturationPressureTable( string const & functionName, array1d< array1d< real64 > > temperatures; array1d< real64 > pressures; + integer const nValues = 26; temperatures.resize( 1 ); - temperatures[0].resize( 26 ); - pressures.resize( 26 ); + temperatures[0].resize( nValues ); + pressures.resize( nValues ); temperatures[0][0] = 0.01; temperatures[0][1] = 10; diff --git a/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/SpanWagnerCO2Density.cpp b/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/SpanWagnerCO2Density.cpp index 3ca6b6aee1f..fff330ed92e 100644 --- a/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/SpanWagnerCO2Density.cpp +++ b/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/SpanWagnerCO2Density.cpp @@ -272,7 +272,8 @@ void SpanWagnerCO2Density::calculateCO2Density( string const & functionName, SpanWagnerCO2Density::SpanWagnerCO2Density( string const & name, string_array const & inputParams, string_array const & componentNames, - array1d< real64 > const & componentMolarWeight ): + array1d< real64 > const & componentMolarWeight, + bool const printTable ): PVTFunctionBase( name, componentNames, componentMolarWeight ) @@ -281,6 +282,8 @@ SpanWagnerCO2Density::SpanWagnerCO2Density( string const & name, m_CO2Index = PVTFunctionHelpers::findName( componentNames, expectedCO2ComponentNames, "componentNames" ); m_CO2DensityTable = makeDensityTable( inputParams, m_functionName, FunctionManager::getInstance() ); + if( printTable ) + m_CO2DensityTable->print( m_CO2DensityTable->getName() ); } void SpanWagnerCO2Density::checkTablesParameters( real64 const pressure, @@ -298,7 +301,7 @@ SpanWagnerCO2Density::createKernelWrapper() const m_CO2Index ); } -REGISTER_CATALOG_ENTRY( PVTFunctionBase, SpanWagnerCO2Density, string const &, string_array const &, string_array const &, array1d< real64 > const & ) +REGISTER_CATALOG_ENTRY( PVTFunctionBase, SpanWagnerCO2Density, string const &, string_array const &, string_array const &, array1d< real64 > const &, bool const ) } // namespace PVTProps diff --git a/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/SpanWagnerCO2Density.hpp b/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/SpanWagnerCO2Density.hpp index d1b2d266fe5..181559cd7bd 100644 --- a/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/SpanWagnerCO2Density.hpp +++ b/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/SpanWagnerCO2Density.hpp @@ -79,7 +79,8 @@ class SpanWagnerCO2Density : public PVTFunctionBase SpanWagnerCO2Density( string const &, string_array const & inputParams, string_array const & componentNames, - array1d< real64 > const & componentMolarWeight ); + array1d< real64 > const & componentMolarWeight, + bool const printTable ); static string catalogName() { return "SpanWagnerCO2Density"; } diff --git a/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/WaterDensity.cpp b/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/WaterDensity.cpp index e222d98b9a7..244bdcd73c5 100644 --- a/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/WaterDensity.cpp +++ b/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/WaterDensity.cpp @@ -35,13 +35,16 @@ namespace PVTProps WaterDensity::WaterDensity( string const & name, string_array const & inputParams, string_array const & componentNames, - array1d< real64 > const & componentMolarWeight ): + array1d< real64 > const & componentMolarWeight, + bool const printTable ): PVTFunctionBase( name, componentNames, componentMolarWeight ) { GEOS_UNUSED_VAR( inputParams ); m_waterDensityTable = PureWaterProperties::makeSaturationDensityTable( m_functionName, FunctionManager::getInstance() ); + if( printTable ) + m_waterDensityTable->print( m_waterDensityTable->getName() ); } void WaterDensity::checkTablesParameters( real64 const pressure, @@ -58,7 +61,7 @@ WaterDensity::createKernelWrapper() const *m_waterDensityTable ); } -REGISTER_CATALOG_ENTRY( PVTFunctionBase, WaterDensity, string const &, string_array const &, string_array const &, array1d< real64 > const & ) +REGISTER_CATALOG_ENTRY( PVTFunctionBase, WaterDensity, string const &, string_array const &, string_array const &, array1d< real64 > const &, bool const ) } // namespace PVTProps diff --git a/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/WaterDensity.hpp b/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/WaterDensity.hpp index 8cc49429498..e1bb09c0276 100644 --- a/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/WaterDensity.hpp +++ b/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/WaterDensity.hpp @@ -74,10 +74,10 @@ class WaterDensity : public PVTFunctionBase WaterDensity( string const & name, string_array const & inputParams, string_array const & componentNames, - array1d< real64 > const & componentMolarWeight ); + array1d< real64 > const & componentMolarWeight, + bool const printTable ); static string catalogName() { return "WaterDensity"; } - virtual string getCatalogName() const final { return catalogName(); } /** diff --git a/src/coreComponents/constitutive/fluid/multifluid/reactive/ReactiveBrineFluid.cpp b/src/coreComponents/constitutive/fluid/multifluid/reactive/ReactiveBrineFluid.cpp index 132834e5cef..f118a6a6819 100644 --- a/src/coreComponents/constitutive/fluid/multifluid/reactive/ReactiveBrineFluid.cpp +++ b/src/coreComponents/constitutive/fluid/multifluid/reactive/ReactiveBrineFluid.cpp @@ -193,7 +193,8 @@ void ReactiveBrineFluid< PHASE > ::createPVTModels() InputError ); // then, we are ready to instantiate the phase models - m_phase = std::make_unique< PHASE >( getName() + "_phaseModel1", phase1InputParams, m_componentNames, m_componentMolarWeight ); + m_phase = std::make_unique< PHASE >( getName() + "_phaseModel1", phase1InputParams, m_componentNames, m_componentMolarWeight, + getLogLevel() > 0 && logger::internal::rank==0 ); } template< typename PHASE > diff --git a/src/coreComponents/functions/TableFunction.cpp b/src/coreComponents/functions/TableFunction.cpp index 98584bd0c1c..892b2289627 100644 --- a/src/coreComponents/functions/TableFunction.cpp +++ b/src/coreComponents/functions/TableFunction.cpp @@ -180,6 +180,77 @@ void TableFunction::checkCoord( real64 const coord, localIndex const dim ) const SimulationError ); } +void TableFunction::print( std::string const & filename ) const +{ + std::ofstream os( filename + ".csv" ); + + integer const numDimensions = LvArray::integerConversion< integer >( m_coordinates.size() ); + + if( numDimensions != 2 ) + { + // print header + + for( integer d = 0; d < numDimensions; d++ ) + { + os << units::getDescription( getDimUnit( d )) << ","; + } + os << units::getDescription( m_valueUnit ) << "\n"; + + // print values + + // prepare dividers + std::vector< integer > div( numDimensions ); + div[0] = 1; + for( integer d = 1; d < numDimensions; d++ ) + { + div[d] = div[d-1] * m_coordinates[d-1].size(); + } + // loop through all the values + for( integer v = 0; v < m_values.size(); v++ ) + { + // find coords indices + std::vector< integer > idx( numDimensions ); + integer r = v; + for( integer d = numDimensions-1; d >= 0; d-- ) + { + idx[d] = r / div[d]; + r = r % div[d]; + } + // finally print out in right order + for( integer d = 0; d < numDimensions; d++ ) + { + arraySlice1d< real64 const > const coords = m_coordinates[d]; + os << coords[idx[d]] << ","; + } + os << m_values[v] << "\n"; + } + } + else // numDimensions == 2 + { + arraySlice1d< real64 const > const coordsX = m_coordinates[0]; + arraySlice1d< real64 const > const coordsY = m_coordinates[1]; + integer const nX = coordsX.size(); + integer const nY = coordsY.size(); + os< 0 ) + { + if( MpiWrapper::commRank() == 0 ) + { + makeDirsForPath( m_outputDir ); + } + // wait till the dir is created by rank 0 + MPI_Barrier( MPI_COMM_WORLD ); + } } void CompositionalMultiphaseStatistics::registerDataOnMesh( Group & meshBodies ) @@ -109,7 +121,37 @@ void CompositionalMultiphaseStatistics::registerDataOnMesh( Group & meshBodies ) regionStatistics.phaseMass.resizeDimension< 0 >( numPhases ); regionStatistics.trappedPhaseMass.resizeDimension< 0 >( numPhases ); regionStatistics.immobilePhaseMass.resizeDimension< 0 >( numPhases ); - regionStatistics.dissolvedComponentMass.resizeDimension< 0, 1 >( numPhases, numComps ); + regionStatistics.componentMass.resizeDimension< 0, 1 >( numPhases, numComps ); + + // write output header + if( getLogLevel() > 0 && MpiWrapper::commRank() == 0 ) + { + std::ofstream outputFile( m_outputDir + "/" + regionNames[i] + ".csv" ); + integer const useMass = m_solver->getReference< integer >( CompositionalMultiphaseBase::viewKeyStruct::useMassFlagString() ); + string const massUnit = useMass ? "kg" : "mol"; + outputFile << + "Time [s],Min pressure [Pa],Average pressure [Pa],Max pressure [Pa],Min delta pressure [Pa],Max delta pressure [Pa]," << + "Min temperature [Pa],Average temperature [Pa],Max temperature [Pa],Total dynamic pore volume [rm^3]"; + for( integer ip = 0; ip < numPhases; ++ip ) + outputFile << ",Phase " << ip << " dynamic pore volume [rm^3]"; + for( integer ip = 0; ip < numPhases; ++ip ) + outputFile << ",Phase " << ip << " mass [" << massUnit << "]"; + for( integer ip = 0; ip < numPhases; ++ip ) + outputFile << ",Trapped phase " << ip << " mass (metric 1) [" << massUnit << "]"; + for( integer ip = 0; ip < numPhases; ++ip ) + outputFile << ",Non-trapped phase " << ip << " mass (metric 1) [" << massUnit << "]"; + for( integer ip = 0; ip < numPhases; ++ip ) + outputFile << ",Immobile phase " << ip << " mass (metric 2) [" << massUnit << "]"; + for( integer ip = 0; ip < numPhases; ++ip ) + outputFile << ",Mobile phase " << ip << " mass (metric 2) [" << massUnit << "]"; + for( integer ip = 0; ip < numPhases; ++ip ) + { + for( integer ic = 0; ic < numComps; ++ic ) + outputFile << ",Component " << ic << " (phase " << ip << ") mass [" << massUnit << "]"; + } + outputFile << std::endl; + outputFile.close(); + } } } @@ -130,7 +172,7 @@ void CompositionalMultiphaseStatistics::registerDataOnMesh( Group & meshBodies ) } ); } -bool CompositionalMultiphaseStatistics::execute( real64 const GEOS_UNUSED_PARAM( time_n ), +bool CompositionalMultiphaseStatistics::execute( real64 const time_n, real64 const dt, integer const GEOS_UNUSED_PARAM( cycleNumber ), integer const GEOS_UNUSED_PARAM( eventCounter ), @@ -143,19 +185,22 @@ bool CompositionalMultiphaseStatistics::execute( real64 const GEOS_UNUSED_PARAM( { if( m_computeRegionStatistics ) { - computeRegionStatistics( mesh, regionNames ); + // current time is time_n + dt + computeRegionStatistics( time_n + dt, mesh, regionNames ); } } ); if( m_computeCFLNumbers ) { - computeCFLNumbers( dt, domain ); + // current time is time_n + dt + computeCFLNumbers( time_n + dt, dt, domain ); } return false; } -void CompositionalMultiphaseStatistics::computeRegionStatistics( MeshLevel & mesh, +void CompositionalMultiphaseStatistics::computeRegionStatistics( real64 const time, + MeshLevel & mesh, arrayView1d< string const > const & regionNames ) const { GEOS_MARK_FUNCTION; @@ -188,7 +233,7 @@ void CompositionalMultiphaseStatistics::computeRegionStatistics( MeshLevel & mes regionStatistics.phaseMass.setValues< serialPolicy >( 0.0 ); regionStatistics.trappedPhaseMass.setValues< serialPolicy >( 0.0 ); regionStatistics.immobilePhaseMass.setValues< serialPolicy >( 0.0 ); - regionStatistics.dissolvedComponentMass.setValues< serialPolicy >( 0.0 ); + regionStatistics.componentMass.setValues< serialPolicy >( 0.0 ); } // Step 2: increment the average/min/max quantities for all the subRegions @@ -237,7 +282,7 @@ void CompositionalMultiphaseStatistics::computeRegionStatistics( MeshLevel & mes array1d< real64 > subRegionTrappedPhaseMass( numPhases ); array1d< real64 > subRegionImmobilePhaseMass( numPhases ); array1d< real64 > subRegionRelpermPhaseMass( numPhases ); - array2d< real64 > subRegionDissolvedComponentMass( numPhases, numComps ); + array2d< real64 > subRegionComponentMass( numPhases, numComps ); isothermalCompositionalMultiphaseBaseKernels:: StatisticsKernel:: @@ -270,7 +315,7 @@ void CompositionalMultiphaseStatistics::computeRegionStatistics( MeshLevel & mes subRegionPhaseMass.toView(), subRegionTrappedPhaseMass.toView(), subRegionImmobilePhaseMass.toView(), - subRegionDissolvedComponentMass.toView() ); + subRegionComponentMass.toView() ); ElementRegionBase & region = elemManager.getRegion( subRegion.getParent().getParent().getName() ); RegionStatistics & regionStatistics = region.getReference< RegionStatistics >( viewKeyStruct::regionStatisticsString() ); @@ -314,7 +359,7 @@ void CompositionalMultiphaseStatistics::computeRegionStatistics( MeshLevel & mes for( integer ic = 0; ic < numComps; ++ic ) { - regionStatistics.dissolvedComponentMass[ip][ic] += subRegionDissolvedComponentMass[ip][ic]; + regionStatistics.componentMass[ip][ic] += subRegionComponentMass[ip][ic]; } } @@ -343,7 +388,7 @@ void CompositionalMultiphaseStatistics::computeRegionStatistics( MeshLevel & mes regionStatistics.totalPoreVolume += regionStatistics.phasePoreVolume[ip]; for( integer ic = 0; ic < numComps; ++ic ) { - regionStatistics.dissolvedComponentMass[ip][ic] = MpiWrapper::sum( regionStatistics.dissolvedComponentMass[ip][ic] ); + regionStatistics.componentMass[ip][ic] = MpiWrapper::sum( regionStatistics.componentMass[ip][ic] ); } } regionStatistics.averagePressure = MpiWrapper::sum( regionStatistics.averagePressure ); @@ -363,41 +408,65 @@ void CompositionalMultiphaseStatistics::computeRegionStatistics( MeshLevel & mes integer const useMass = m_solver->getReference< integer >( CompositionalMultiphaseBase::viewKeyStruct::useMassFlagString() ); string const massUnit = useMass ? "kg" : "mol"; - GEOS_LOG_LEVEL_RANK_0( 1, getName() << ", " << regionNames[i] - << ": Pressure (min, average, max): " - << regionStatistics.minPressure << ", " << regionStatistics.averagePressure << ", " << regionStatistics.maxPressure << " Pa" ); - GEOS_LOG_LEVEL_RANK_0( 1, getName() << ", " << regionNames[i] - << ": Delta pressure (min, max): " - << regionStatistics.minDeltaPressure << ", " << regionStatistics.maxDeltaPressure << " Pa" ); - GEOS_LOG_LEVEL_RANK_0( 1, getName() << ", " << regionNames[i] - << ": Temperature (min, average, max): " - << regionStatistics.minTemperature << ", " << regionStatistics.averageTemperature << ", " << regionStatistics.maxTemperature << " K" ); - GEOS_LOG_LEVEL_RANK_0( 1, getName() << ", " << regionNames[i] - << ": Total dynamic pore volume: " << regionStatistics.totalPoreVolume << " rm^3" ); - GEOS_LOG_LEVEL_RANK_0( 1, getName() << ", " << regionNames[i] - << ": Phase dynamic pore volumes: " << regionStatistics.phasePoreVolume << " rm^3" ); - GEOS_LOG_LEVEL_RANK_0( 1, getName() << ", " << regionNames[i] - << ": Phase mass: " << regionStatistics.phaseMass << " " << massUnit ); + GEOS_LOG_LEVEL_RANK_0( 1, GEOS_FMT( "{}, {} (time {} s): Pressure (min, average, max): {}, {}, {} Pa", + getName(), regionNames[i], time, regionStatistics.minPressure, regionStatistics.averagePressure, regionStatistics.maxPressure ) ); + GEOS_LOG_LEVEL_RANK_0( 1, GEOS_FMT( "{}, {} (time {} s): Delta pressure (min, max): {}, {} Pa", + getName(), regionNames[i], time, regionStatistics.minDeltaPressure, regionStatistics.maxDeltaPressure ) ); + GEOS_LOG_LEVEL_RANK_0( 1, GEOS_FMT( "{}, {} (time {} s): Temperature (min, average, max): {}, {}, {} K", + getName(), regionNames[i], time, regionStatistics.minTemperature, regionStatistics.averageTemperature, regionStatistics.maxTemperature ) ); + GEOS_LOG_LEVEL_RANK_0( 1, GEOS_FMT( "{}, {} (time {} s): Total dynamic pore volume: {} rm^3", + getName(), regionNames[i], time, regionStatistics.totalPoreVolume ) ); + GEOS_LOG_LEVEL_RANK_0( 1, GEOS_FMT( "{}, {} (time {} s): Phase dynamic pore volume: {} rm^3", + getName(), regionNames[i], time, regionStatistics.phasePoreVolume ) ); + GEOS_LOG_LEVEL_RANK_0( 1, GEOS_FMT( "{}, {} (time {} s): Phase mass: {} {}", + getName(), regionNames[i], time, regionStatistics.phaseMass, massUnit ) ); // metric 1: trapping computed with the Land trapping coefficient (similar to Eclipse) - GEOS_LOG_LEVEL_RANK_0( 1, getName() << ", " << regionNames[i] - << ": Trapped phase mass (metric 1): " << regionStatistics.trappedPhaseMass << " " << massUnit ); - GEOS_LOG_LEVEL_RANK_0( 1, getName() << ", " << regionNames[i] - << ": Non-trapped phase mass (metric 1): " << nonTrappedPhaseMass << " " << massUnit ); + GEOS_LOG_LEVEL_RANK_0( 1, GEOS_FMT( "{}, {} (time {} s): Trapped phase mass (metric 1): {} {}", + getName(), regionNames[i], time, regionStatistics.trappedPhaseMass, massUnit ) ); + GEOS_LOG_LEVEL_RANK_0( 1, GEOS_FMT( "{}, {} (time {} s): Non-trapped phase mass (metric 1): {} {}", + getName(), regionNames[i], time, nonTrappedPhaseMass, massUnit ) ); // metric 2: immobile phase mass computed with a threshold on relative permeability - GEOS_LOG_LEVEL_RANK_0( 1, getName() << ", " << regionNames[i] - << ": Immobile phase mass (metric 2): " << regionStatistics.immobilePhaseMass << " " << massUnit ); - GEOS_LOG_LEVEL_RANK_0( 1, getName() << ", " << regionNames[i] - << ": Mobile phase mass (metric 2): " << mobilePhaseMass << " " << massUnit ); + GEOS_LOG_LEVEL_RANK_0( 1, GEOS_FMT( "{}, {} (time {} s): Immobile phase mass (metric 2): {} {}", + getName(), regionNames[i], time, regionStatistics.immobilePhaseMass, massUnit ) ); + GEOS_LOG_LEVEL_RANK_0( 1, GEOS_FMT( "{}, {} (time {} s): Mobile phase mass (metric 2): {} {}", + getName(), regionNames[i], time, mobilePhaseMass, massUnit ) ); + GEOS_LOG_LEVEL_RANK_0( 1, GEOS_FMT( "{}, {} (time {} s): Component mass: {} {}", + getName(), regionNames[i], time, regionStatistics.componentMass, massUnit ) ); - GEOS_LOG_LEVEL_RANK_0( 1, getName() << ", " << regionNames[i] - << ": Dissolved component mass: " << regionStatistics.dissolvedComponentMass << " " << massUnit ); + if( getLogLevel() > 0 && MpiWrapper::commRank() == 0 ) + { + std::ofstream outputFile( m_outputDir + "/" + regionNames[i] + ".csv", std::ios_base::app ); + outputFile << time << "," << regionStatistics.minPressure << "," << regionStatistics.averagePressure << "," << regionStatistics.maxPressure << "," << + regionStatistics.minDeltaPressure << "," << regionStatistics.maxDeltaPressure << "," << regionStatistics.minTemperature << "," << + regionStatistics.averageTemperature << "," << regionStatistics.maxTemperature << "," << regionStatistics.totalPoreVolume; + for( integer ip = 0; ip < numPhases; ++ip ) + outputFile << "," << regionStatistics.phasePoreVolume[ip]; + for( integer ip = 0; ip < numPhases; ++ip ) + outputFile << "," << regionStatistics.phaseMass[ip]; + for( integer ip = 0; ip < numPhases; ++ip ) + outputFile << "," << regionStatistics.trappedPhaseMass[ip]; + for( integer ip = 0; ip < numPhases; ++ip ) + outputFile << "," << nonTrappedPhaseMass[ip]; + for( integer ip = 0; ip < numPhases; ++ip ) + outputFile << "," << regionStatistics.immobilePhaseMass[ip]; + for( integer ip = 0; ip < numPhases; ++ip ) + outputFile << "," << mobilePhaseMass[ip]; + for( integer ip = 0; ip < numPhases; ++ip ) + { + for( integer ic = 0; ic < numComps; ++ic ) + outputFile << "," << regionStatistics.componentMass[ip][ic]; + } + outputFile << std::endl; + outputFile.close(); + } } } -void CompositionalMultiphaseStatistics::computeCFLNumbers( real64 const & dt, +void CompositionalMultiphaseStatistics::computeCFLNumbers( real64 const time, + real64 const dt, DomainPartition & domain ) const { GEOS_MARK_FUNCTION; @@ -546,8 +615,8 @@ void CompositionalMultiphaseStatistics::computeCFLNumbers( real64 const & dt, real64 const globalMaxPhaseCFLNumber = MpiWrapper::max( localMaxPhaseCFLNumber ); real64 const globalMaxCompCFLNumber = MpiWrapper::max( localMaxCompCFLNumber ); - GEOS_LOG_LEVEL_RANK_0( 1, getName() << ": Max phase CFL number: " << globalMaxPhaseCFLNumber ); - GEOS_LOG_LEVEL_RANK_0( 1, getName() << ": Max component CFL number: " << globalMaxCompCFLNumber ); + GEOS_LOG_LEVEL_RANK_0( 1, GEOS_FMT( "{} (time {} s): Max phase CFL number: {}", getName(), time, globalMaxPhaseCFLNumber ) ); + GEOS_LOG_LEVEL_RANK_0( 1, GEOS_FMT( "{} (time {} s): Max component CFL number: {}", getName(), time, globalMaxCompCFLNumber ) ); } diff --git a/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseStatistics.hpp b/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseStatistics.hpp index 97abb1abb5e..8c448195e56 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseStatistics.hpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseStatistics.hpp @@ -115,26 +115,30 @@ class CompositionalMultiphaseStatistics : public FieldStatisticsBase< Compositio array1d< real64 > trappedPhaseMass; /// immobile region phase mass array1d< real64 > immobilePhaseMass; - /// dissolved region component mass - array2d< real64 > dissolvedComponentMass; + /// region component mass + array2d< real64 > componentMass; }; /** * @brief Compute some statistics on the reservoir (average field pressure, etc) + * @param[in] time current time * @param[in] mesh the mesh level object * @param[in] regionNames the array of target region names */ - void computeRegionStatistics( MeshLevel & mesh, + void computeRegionStatistics( real64 const time, + MeshLevel & mesh, arrayView1d< string const > const & regionNames ) const; /** * @brief Compute CFL numbers + * @param[in] time current time * @param[in] dt the time step size * @param[in] domain the domain partition */ - void computeCFLNumbers( real64 const & dt, + void computeCFLNumbers( real64 const time, + real64 const dt, DomainPartition & domain ) const; void postProcessInput() override; @@ -150,6 +154,9 @@ class CompositionalMultiphaseStatistics : public FieldStatisticsBase< Compositio /// Threshold to decide whether a phase is considered "mobile" or not real64 m_relpermThreshold; + // Output directory + string const m_outputDir; + }; diff --git a/src/coreComponents/physicsSolvers/fluidFlow/wells/CompositionalMultiphaseWell.cpp b/src/coreComponents/physicsSolvers/fluidFlow/wells/CompositionalMultiphaseWell.cpp index 9134dddb42c..ca537b2f6a6 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/wells/CompositionalMultiphaseWell.cpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/wells/CompositionalMultiphaseWell.cpp @@ -261,9 +261,9 @@ void CompositionalMultiphaseWell::registerDataOnMesh( Group & meshBodies ) integer const numPhase = m_numPhases; // format: time,bhp,total_rate,total_vol_rate,phase0_vol_rate,phase1_vol_rate,... std::ofstream outputFile( m_ratesOutputDir + "/" + wellControlsName + ".csv" ); - outputFile << "time [s],bhp [Pa],total rate [" << massUnit << "/s],total " << conditionKey << " volumetric rate [" << unitKey << "m3/s]"; + outputFile << "Time [s],BHP [Pa],Total rate [" << massUnit << "/s],Total " << conditionKey << " Volumetric rate [" << unitKey << "m3/s]"; for( integer ip = 0; ip < numPhase; ++ip ) - outputFile << ",phase" << ip << " " << conditionKey << " volumetric rate [" << unitKey << "m3/s]"; + outputFile << ",Phase" << ip << " " << conditionKey << " volumetric rate [" << unitKey << "m3/s]"; outputFile << std::endl; outputFile.close(); } diff --git a/src/coreComponents/physicsSolvers/fluidFlow/wells/SinglePhaseWell.cpp b/src/coreComponents/physicsSolvers/fluidFlow/wells/SinglePhaseWell.cpp index 6acb1125406..9f61be73ac7 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/wells/SinglePhaseWell.cpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/wells/SinglePhaseWell.cpp @@ -98,8 +98,8 @@ void SinglePhaseWell::registerDataOnMesh( Group & meshBodies ) string const unitKey = useSurfaceConditions ? "s" : "r"; // format: time,bhp,total_rate,total_vol_rate std::ofstream outputFile( m_ratesOutputDir + "/" + wellControlsName + ".csv" ); - outputFile << "time [s],bhp [Pa],total rate [kg/s],total " << conditionKey << " volumetric rate ["<( viewKeyStruct::fluidNamesString() ); @@ -1088,6 +1088,7 @@ void SinglePhaseWell::printRates( real64 const & time_n, wellControlsName, currentTotalRate, conditionKey, currentTotalVolRate, unitKey ) ); outputFile << "," << currentTotalRate << "," << currentTotalVolRate << std::endl; } ); + outputFile.close(); } ); } ); } diff --git a/src/coreComponents/physicsSolvers/multiphysics/MultiphasePoromechanics.cpp b/src/coreComponents/physicsSolvers/multiphysics/MultiphasePoromechanics.cpp index 29d1ebb0394..f1a32b53665 100644 --- a/src/coreComponents/physicsSolvers/multiphysics/MultiphasePoromechanics.cpp +++ b/src/coreComponents/physicsSolvers/multiphysics/MultiphasePoromechanics.cpp @@ -335,7 +335,7 @@ void MultiphasePoromechanics< FLOW_SOLVER >::updateState( DomainPartition & doma } ); GEOS_LOG_LEVEL_RANK_0( 1, GEOS_FMT( " {}: Max phase volume fraction change: {}", - this->getName(), GEOS_FMT( "{:.{}f}", maxDeltaPhaseVolFrac, 2 ) ) ); + this->getName(), GEOS_FMT( "{:.{}f}", maxDeltaPhaseVolFrac, 4 ) ) ); } template< typename FLOW_SOLVER > diff --git a/src/coreComponents/physicsSolvers/solidMechanics/SolidMechanicsStatistics.cpp b/src/coreComponents/physicsSolvers/solidMechanics/SolidMechanicsStatistics.cpp index 8fdc3403e29..9a13598b751 100644 --- a/src/coreComponents/physicsSolvers/solidMechanics/SolidMechanicsStatistics.cpp +++ b/src/coreComponents/physicsSolvers/solidMechanics/SolidMechanicsStatistics.cpp @@ -32,9 +32,26 @@ using namespace fields; SolidMechanicsStatistics::SolidMechanicsStatistics( const string & name, Group * const parent ): - Base( name, parent ) + Base( name, parent ), + m_outputDir( name ) {} +void SolidMechanicsStatistics::postProcessInput() +{ + Base::postProcessInput(); + + // create dir for output + if( getLogLevel() > 0 ) + { + if( MpiWrapper::commRank() == 0 ) + { + makeDirsForPath( m_outputDir ); + } + // wait till the dir is created by rank 0 + MPI_Barrier( MPI_COMM_WORLD ); + } +} + void SolidMechanicsStatistics::registerDataOnMesh( Group & meshBodies ) { // the fields have to be registered in "registerDataOnMesh" (and not later) @@ -58,11 +75,20 @@ void SolidMechanicsStatistics::registerDataOnMesh( Group & meshBodies ) nodeStatistics.minDisplacement.resizeDimension< 0 >( 3 ); nodeStatistics.maxDisplacement.resizeDimension< 0 >( 3 ); + + // write output header + if( getLogLevel() > 0 && MpiWrapper::commRank() == 0 ) + { + std::ofstream outputFile( m_outputDir + "/" + mesh.getName() + "_node_statistics" + ".csv" ); + outputFile << "Time [s],Min displacement X [m],Min displacement Y [m],Min displacement Z [m]," + << "Max displacement X [m],Max displacement Y [m],Max displacement Z [m]" << std::endl; + outputFile.close(); + } } ); } -bool SolidMechanicsStatistics::execute( real64 const GEOS_UNUSED_PARAM( time_n ), - real64 const GEOS_UNUSED_PARAM( dt ), +bool SolidMechanicsStatistics::execute( real64 const time_n, + real64 const dt, integer const GEOS_UNUSED_PARAM( cycleNumber ), integer const GEOS_UNUSED_PARAM( eventCounter ), real64 const GEOS_UNUSED_PARAM( eventProgress ), @@ -72,12 +98,13 @@ bool SolidMechanicsStatistics::execute( real64 const GEOS_UNUSED_PARAM( time_n ) MeshLevel & mesh, arrayView1d< string const > const & ) { - computeNodeStatistics( mesh ); + // current time is time_n + dt + computeNodeStatistics( mesh, time_n + dt ); } ); return false; } -void SolidMechanicsStatistics::computeNodeStatistics( MeshLevel & mesh ) const +void SolidMechanicsStatistics::computeNodeStatistics( MeshLevel & mesh, real64 const time ) const { GEOS_MARK_FUNCTION; @@ -102,7 +129,8 @@ void SolidMechanicsStatistics::computeNodeStatistics( MeshLevel & mesh ) const maxDispZ, minDispX, minDispY, - minDispZ] GEOS_HOST_DEVICE ( localIndex const a ) + minDispZ] + GEOS_HOST_DEVICE ( localIndex const a ) { if( ghostRank[a] < 0 ) { @@ -138,14 +166,24 @@ void SolidMechanicsStatistics::computeNodeStatistics( MeshLevel & mesh ) const MpiWrapper::getMpiOp( MpiWrapper::Reduction::Min ), MPI_COMM_GEOSX ); - GEOS_LOG_LEVEL_RANK_0( 1, getName() << ": Min displacement (X, Y, Z): " - << nodeStatistics.minDisplacement[0] << ", " - << nodeStatistics.minDisplacement[1] << ", " - << nodeStatistics.minDisplacement[2] << " m" ); - GEOS_LOG_LEVEL_RANK_0( 1, getName() << ": Max displacement (X, Y, Z): " - << nodeStatistics.maxDisplacement[0] << ", " - << nodeStatistics.maxDisplacement[1] << ", " - << nodeStatistics.maxDisplacement[2] << " m" ); + GEOS_LOG_LEVEL_RANK_0( 1, GEOS_FMT( "{} (time {} s): Min displacement (X, Y, Z): {}, {}, {} m", + getName(), time, nodeStatistics.minDisplacement[0], + nodeStatistics.minDisplacement[1], nodeStatistics.minDisplacement[2] ) ); + GEOS_LOG_LEVEL_RANK_0( 1, GEOS_FMT( "{} (time {} s): Max displacement (X, Y, Z): {}, {}, {} m", + getName(), time, nodeStatistics.maxDisplacement[0], + nodeStatistics.maxDisplacement[1], nodeStatistics.maxDisplacement[2] ) ); + + if( getLogLevel() > 0 && MpiWrapper::commRank() == 0 ) + { + std::ofstream outputFile( m_outputDir + "/" + mesh.getName() + "_node_statistics" + ".csv", std::ios_base::app ); + outputFile << time; + for( integer i = 0; i < 3; ++i ) + outputFile << "," << nodeStatistics.minDisplacement[i]; + for( integer i = 0; i < 3; ++i ) + outputFile << "," << nodeStatistics.maxDisplacement[i]; + outputFile << std::endl; + outputFile.close(); + } } REGISTER_CATALOG_ENTRY( TaskBase, diff --git a/src/coreComponents/physicsSolvers/solidMechanics/SolidMechanicsStatistics.hpp b/src/coreComponents/physicsSolvers/solidMechanics/SolidMechanicsStatistics.hpp index eeab460666c..31d4c45c74d 100644 --- a/src/coreComponents/physicsSolvers/solidMechanics/SolidMechanicsStatistics.hpp +++ b/src/coreComponents/physicsSolvers/solidMechanics/SolidMechanicsStatistics.hpp @@ -66,7 +66,7 @@ class SolidMechanicsStatistics : public FieldStatisticsBase< SolidMechanicsLagra * @brief Compute node-based statistics on the reservoir * @param[in] mesh the mesh level object */ - void computeNodeStatistics( MeshLevel & mesh ) const; + void computeNodeStatistics( MeshLevel & mesh, real64 const time ) const; private: @@ -89,7 +89,12 @@ class SolidMechanicsStatistics : public FieldStatisticsBase< SolidMechanicsLagra array1d< real64 > maxDisplacement; }; + void postProcessInput() override; + void registerDataOnMesh( Group & meshBodies ) override; + + // Output directory + string const m_outputDir; }; diff --git a/src/coreComponents/schema/docs/BrooksCoreyStone2RelativePermeability.rst b/src/coreComponents/schema/docs/BrooksCoreyStone2RelativePermeability.rst new file mode 100644 index 00000000000..c10a2a48b2f --- /dev/null +++ b/src/coreComponents/schema/docs/BrooksCoreyStone2RelativePermeability.rst @@ -0,0 +1,19 @@ + + +======================= ================== ======== ========================================================================================================================================================== +Name Type Default Description +======================= ================== ======== ========================================================================================================================================================== +gasOilRelPermExponent real64_array {1} | Rel perm power law exponent for the pair (gas phase, oil phase) at residual water saturation + | The expected format is "{ gasExp, oilExp }", in that order +gasOilRelPermMaxValue real64_array {0} | Maximum rel perm value for the pair (gas phase, oil phase) at residual water saturation + | The expected format is "{ gasMax, oilMax }", in that order +name groupName required A name is required for any non-unique nodes +phaseMinVolumeFraction real64_array {0} Minimum volume fraction value for each phase +phaseNames groupNameRef_array required List of fluid phases +waterOilRelPermExponent real64_array {1} | Rel perm power law exponent for the pair (water phase, oil phase) at residual gas saturation + | The expected format is "{ waterExp, oilExp }", in that order +waterOilRelPermMaxValue real64_array {0} | Maximum rel perm value for the pair (water phase, oil phase) at residual gas saturation + | The expected format is "{ waterMax, oilMax }", in that order +======================= ================== ======== ========================================================================================================================================================== + + diff --git a/src/coreComponents/schema/docs/BrooksCoreyStone2RelativePermeability_other.rst b/src/coreComponents/schema/docs/BrooksCoreyStone2RelativePermeability_other.rst new file mode 100644 index 00000000000..e6dd24aeb13 --- /dev/null +++ b/src/coreComponents/schema/docs/BrooksCoreyStone2RelativePermeability_other.rst @@ -0,0 +1,15 @@ + + +=============================== ============== ======================================================================================================================= +Name Type Description +=============================== ============== ======================================================================================================================= +dPhaseRelPerm_dPhaseVolFraction real64_array4d Derivative of phase relative permeability with respect to phase volume fraction +phaseOrder integer_array (no description available) +phaseRelPerm real64_array3d Phase relative permeability +phaseRelPerm_n real64_array3d Phase relative permeability at previous time +phaseTrappedVolFraction real64_array3d Phase trapped volume fraction +phaseTypes integer_array (no description available) +volFracScale real64 Factor used to scale the phase capillary pressure, defined as: one minus the sum of the phase minimum volume fractions. +=============================== ============== ======================================================================================================================= + + diff --git a/src/coreComponents/schema/docs/CO2BrineEzrokhiFluid.rst b/src/coreComponents/schema/docs/CO2BrineEzrokhiFluid.rst index 4642cc407cf..8b77d7787d5 100644 --- a/src/coreComponents/schema/docs/CO2BrineEzrokhiFluid.rst +++ b/src/coreComponents/schema/docs/CO2BrineEzrokhiFluid.rst @@ -7,6 +7,7 @@ checkPVTTablesRanges integer 1 Enable (1) or disable (0) an er componentMolarWeight real64_array {0} Component molar weights componentNames string_array {} List of component names flashModelParaFile path required Name of the file defining the parameters of the flash model +logLevel integer 0 Log level name groupName required A name is required for any non-unique nodes phaseNames groupNameRef_array {} List of fluid phases phasePVTParaFiles path_array required Names of the files defining the parameters of the viscosity and density models diff --git a/src/coreComponents/schema/docs/CO2BrineEzrokhiThermalFluid.rst b/src/coreComponents/schema/docs/CO2BrineEzrokhiThermalFluid.rst index 4642cc407cf..8b77d7787d5 100644 --- a/src/coreComponents/schema/docs/CO2BrineEzrokhiThermalFluid.rst +++ b/src/coreComponents/schema/docs/CO2BrineEzrokhiThermalFluid.rst @@ -7,6 +7,7 @@ checkPVTTablesRanges integer 1 Enable (1) or disable (0) an er componentMolarWeight real64_array {0} Component molar weights componentNames string_array {} List of component names flashModelParaFile path required Name of the file defining the parameters of the flash model +logLevel integer 0 Log level name groupName required A name is required for any non-unique nodes phaseNames groupNameRef_array {} List of fluid phases phasePVTParaFiles path_array required Names of the files defining the parameters of the viscosity and density models diff --git a/src/coreComponents/schema/docs/CO2BrinePhillipsFluid.rst b/src/coreComponents/schema/docs/CO2BrinePhillipsFluid.rst index 4642cc407cf..8b77d7787d5 100644 --- a/src/coreComponents/schema/docs/CO2BrinePhillipsFluid.rst +++ b/src/coreComponents/schema/docs/CO2BrinePhillipsFluid.rst @@ -7,6 +7,7 @@ checkPVTTablesRanges integer 1 Enable (1) or disable (0) an er componentMolarWeight real64_array {0} Component molar weights componentNames string_array {} List of component names flashModelParaFile path required Name of the file defining the parameters of the flash model +logLevel integer 0 Log level name groupName required A name is required for any non-unique nodes phaseNames groupNameRef_array {} List of fluid phases phasePVTParaFiles path_array required Names of the files defining the parameters of the viscosity and density models diff --git a/src/coreComponents/schema/docs/CO2BrinePhillipsThermalFluid.rst b/src/coreComponents/schema/docs/CO2BrinePhillipsThermalFluid.rst index 4642cc407cf..8b77d7787d5 100644 --- a/src/coreComponents/schema/docs/CO2BrinePhillipsThermalFluid.rst +++ b/src/coreComponents/schema/docs/CO2BrinePhillipsThermalFluid.rst @@ -7,6 +7,7 @@ checkPVTTablesRanges integer 1 Enable (1) or disable (0) an er componentMolarWeight real64_array {0} Component molar weights componentNames string_array {} List of component names flashModelParaFile path required Name of the file defining the parameters of the flash model +logLevel integer 0 Log level name groupName required A name is required for any non-unique nodes phaseNames groupNameRef_array {} List of fluid phases phasePVTParaFiles path_array required Names of the files defining the parameters of the viscosity and density models diff --git a/src/coreComponents/schema/docs/CompositionalMultiphaseReservoirPoromechanics.rst b/src/coreComponents/schema/docs/CompositionalMultiphaseReservoirPoromechanics.rst new file mode 100644 index 00000000000..c85eff65ff8 --- /dev/null +++ b/src/coreComponents/schema/docs/CompositionalMultiphaseReservoirPoromechanics.rst @@ -0,0 +1,24 @@ + + +=========================== ==================================== ======== ====================================================================================================================================================================================================================================================================================================================== +Name Type Default Description +=========================== ==================================== ======== ====================================================================================================================================================================================================================================================================================================================== +cflFactor real64 0.5 Factor to apply to the `CFL condition `_ when calculating the maximum allowable time step. Values should be in the interval (0,1] +initialDt real64 1e+99 Initial time-step value required by the solver to the event manager. +isThermal integer 0 Flag indicating whether the problem is thermal or not. Set isThermal="1" to enable the thermal coupling +logLevel integer 0 Log level +name groupName required A name is required for any non-unique nodes +reservoirAndWellsSolverName groupNameRef required Name of the reservoirAndWells solver used by the coupled solver +solidSolverName groupNameRef required Name of the solid solver used by the coupled solver +stabilizationMultiplier real64 1 Constant multiplier of stabilization strength. +stabilizationRegionNames groupNameRef_array {} Regions where stabilization is applied. +stabilizationType geos_stabilization_StabilizationType None | Stabilization type. Options are: + | None - Add no stabilization to mass equation, + | Global - Add stabilization to all faces, + | Local - Add stabilization only to interiors of macro elements. +targetRegions groupNameRef_array required Allowable regions that the solver may be applied to. Note that this does not indicate that the solver will be applied to these regions, only that allocation will occur such that the solver may be applied to these regions. The decision about what regions this solver will beapplied to rests in the EventManager. +LinearSolverParameters node unique :ref:`XML_LinearSolverParameters` +NonlinearSolverParameters node unique :ref:`XML_NonlinearSolverParameters` +=========================== ==================================== ======== ====================================================================================================================================================================================================================================================================================================================== + + diff --git a/src/coreComponents/schema/docs/CompositionalMultiphaseReservoirPoromechanicsInitialization.rst b/src/coreComponents/schema/docs/CompositionalMultiphaseReservoirPoromechanicsInitialization.rst new file mode 100644 index 00000000000..27129634c0b --- /dev/null +++ b/src/coreComponents/schema/docs/CompositionalMultiphaseReservoirPoromechanicsInitialization.rst @@ -0,0 +1,12 @@ + + +=========================== ============ ======== ========================================================================== +Name Type Default Description +=========================== ============ ======== ========================================================================== +logLevel integer 0 Log level +name groupName required A name is required for any non-unique nodes +performStressInitialization integer required Flag to indicate that the solver is going to perform stress initialization +poromechanicsSolverName groupNameRef required Name of the poromechanics solver +=========================== ============ ======== ========================================================================== + + diff --git a/src/coreComponents/schema/docs/CompositionalMultiphaseReservoirPoromechanicsInitialization_other.rst b/src/coreComponents/schema/docs/CompositionalMultiphaseReservoirPoromechanicsInitialization_other.rst new file mode 100644 index 00000000000..adf1c1b8aec --- /dev/null +++ b/src/coreComponents/schema/docs/CompositionalMultiphaseReservoirPoromechanicsInitialization_other.rst @@ -0,0 +1,9 @@ + + +==== ==== ============================ +Name Type Description +==== ==== ============================ + (no documentation available) +==== ==== ============================ + + diff --git a/src/coreComponents/schema/docs/CompositionalMultiphaseReservoirPoromechanics_other.rst b/src/coreComponents/schema/docs/CompositionalMultiphaseReservoirPoromechanics_other.rst new file mode 100644 index 00000000000..80b71ab722d --- /dev/null +++ b/src/coreComponents/schema/docs/CompositionalMultiphaseReservoirPoromechanics_other.rst @@ -0,0 +1,15 @@ + + +=========================== ============================================================================================================================================================== ======================================================================================================================================================================================================================================================================================================================== +Name Type Description +=========================== ============================================================================================================================================================== ======================================================================================================================================================================================================================================================================================================================== +discretization groupNameRef Name of discretization object (defined in the :ref:`NumericalMethodsManager`) to use for this solver. For instance, if this is a Finite Element Solver, the name of a :ref:`FiniteElement` should be specified. If this is a Finite Volume Method, the name of a :ref:`FiniteVolume` discretization should be specified. +maxStableDt real64 Value of the Maximum Stable Timestep for this solver. +meshTargets geos_mapBase< std_pair< string, string >, LvArray_Array< string, 1, camp_int_seq< long, 0l >, int, LvArray_ChaiBuffer >, std_integral_constant< bool, true > > MeshBody/Region combinations that the solver will be applied to. +performStressInitialization integer Flag to indicate that the solver is going to perform stress initialization +LinearSolverParameters node :ref:`DATASTRUCTURE_LinearSolverParameters` +NonlinearSolverParameters node :ref:`DATASTRUCTURE_NonlinearSolverParameters` +SolverStatistics node :ref:`DATASTRUCTURE_SolverStatistics` +=========================== ============================================================================================================================================================== ======================================================================================================================================================================================================================================================================================================================== + + diff --git a/src/coreComponents/schema/docs/SinglePhaseReservoirPoromechanics.rst b/src/coreComponents/schema/docs/SinglePhaseReservoirPoromechanics.rst new file mode 100644 index 00000000000..e19bd9b1ac7 --- /dev/null +++ b/src/coreComponents/schema/docs/SinglePhaseReservoirPoromechanics.rst @@ -0,0 +1,18 @@ + + +=========================== ================== ======== ====================================================================================================================================================================================================================================================================================================================== +Name Type Default Description +=========================== ================== ======== ====================================================================================================================================================================================================================================================================================================================== +cflFactor real64 0.5 Factor to apply to the `CFL condition `_ when calculating the maximum allowable time step. Values should be in the interval (0,1] +initialDt real64 1e+99 Initial time-step value required by the solver to the event manager. +isThermal integer 0 Flag indicating whether the problem is thermal or not. Set isThermal="1" to enable the thermal coupling +logLevel integer 0 Log level +name groupName required A name is required for any non-unique nodes +reservoirAndWellsSolverName groupNameRef required Name of the reservoirAndWells solver used by the coupled solver +solidSolverName groupNameRef required Name of the solid solver used by the coupled solver +targetRegions groupNameRef_array required Allowable regions that the solver may be applied to. Note that this does not indicate that the solver will be applied to these regions, only that allocation will occur such that the solver may be applied to these regions. The decision about what regions this solver will beapplied to rests in the EventManager. +LinearSolverParameters node unique :ref:`XML_LinearSolverParameters` +NonlinearSolverParameters node unique :ref:`XML_NonlinearSolverParameters` +=========================== ================== ======== ====================================================================================================================================================================================================================================================================================================================== + + diff --git a/src/coreComponents/schema/docs/SinglePhaseReservoirPoromechanicsInitialization.rst b/src/coreComponents/schema/docs/SinglePhaseReservoirPoromechanicsInitialization.rst new file mode 100644 index 00000000000..27129634c0b --- /dev/null +++ b/src/coreComponents/schema/docs/SinglePhaseReservoirPoromechanicsInitialization.rst @@ -0,0 +1,12 @@ + + +=========================== ============ ======== ========================================================================== +Name Type Default Description +=========================== ============ ======== ========================================================================== +logLevel integer 0 Log level +name groupName required A name is required for any non-unique nodes +performStressInitialization integer required Flag to indicate that the solver is going to perform stress initialization +poromechanicsSolverName groupNameRef required Name of the poromechanics solver +=========================== ============ ======== ========================================================================== + + diff --git a/src/coreComponents/schema/docs/SinglePhaseReservoirPoromechanicsInitialization_other.rst b/src/coreComponents/schema/docs/SinglePhaseReservoirPoromechanicsInitialization_other.rst new file mode 100644 index 00000000000..adf1c1b8aec --- /dev/null +++ b/src/coreComponents/schema/docs/SinglePhaseReservoirPoromechanicsInitialization_other.rst @@ -0,0 +1,9 @@ + + +==== ==== ============================ +Name Type Description +==== ==== ============================ + (no documentation available) +==== ==== ============================ + + diff --git a/src/coreComponents/schema/docs/SinglePhaseReservoirPoromechanics_other.rst b/src/coreComponents/schema/docs/SinglePhaseReservoirPoromechanics_other.rst new file mode 100644 index 00000000000..80b71ab722d --- /dev/null +++ b/src/coreComponents/schema/docs/SinglePhaseReservoirPoromechanics_other.rst @@ -0,0 +1,15 @@ + + +=========================== ============================================================================================================================================================== ======================================================================================================================================================================================================================================================================================================================== +Name Type Description +=========================== ============================================================================================================================================================== ======================================================================================================================================================================================================================================================================================================================== +discretization groupNameRef Name of discretization object (defined in the :ref:`NumericalMethodsManager`) to use for this solver. For instance, if this is a Finite Element Solver, the name of a :ref:`FiniteElement` should be specified. If this is a Finite Volume Method, the name of a :ref:`FiniteVolume` discretization should be specified. +maxStableDt real64 Value of the Maximum Stable Timestep for this solver. +meshTargets geos_mapBase< std_pair< string, string >, LvArray_Array< string, 1, camp_int_seq< long, 0l >, int, LvArray_ChaiBuffer >, std_integral_constant< bool, true > > MeshBody/Region combinations that the solver will be applied to. +performStressInitialization integer Flag to indicate that the solver is going to perform stress initialization +LinearSolverParameters node :ref:`DATASTRUCTURE_LinearSolverParameters` +NonlinearSolverParameters node :ref:`DATASTRUCTURE_NonlinearSolverParameters` +SolverStatistics node :ref:`DATASTRUCTURE_SolverStatistics` +=========================== ============================================================================================================================================================== ======================================================================================================================================================================================================================================================================================================================== + + diff --git a/src/coreComponents/schema/docs/VanGenuchtenStone2RelativePermeability.rst b/src/coreComponents/schema/docs/VanGenuchtenStone2RelativePermeability.rst new file mode 100644 index 00000000000..00d74f74608 --- /dev/null +++ b/src/coreComponents/schema/docs/VanGenuchtenStone2RelativePermeability.rst @@ -0,0 +1,19 @@ + + +========================== ================== ======== ================================================================================================================================================================== +Name Type Default Description +========================== ================== ======== ================================================================================================================================================================== +gasOilRelPermExponentInv real64_array {0.5} | Rel perm power law exponent inverse for the pair (gas phase, oil phase) at residual water saturation + | The expected format is "{ gasExp, oilExp }", in that order +gasOilRelPermMaxValue real64_array {0} | Maximum rel perm value for the pair (gas phase, oil phase) at residual water saturation + | The expected format is "{ gasMax, oilMax }", in that order +name groupName required A name is required for any non-unique nodes +phaseMinVolumeFraction real64_array {0} Minimum volume fraction value for each phase +phaseNames groupNameRef_array required List of fluid phases +waterOilRelPermExponentInv real64_array {0.5} | Rel perm power law exponent inverse for the pair (water phase, oil phase) at residual gas saturation + | The expected format is "{ waterExp, oilExp }", in that order +waterOilRelPermMaxValue real64_array {0} | Maximum rel perm value for the pair (water phase, oil phase) at residual gas saturation + | The expected format is "{ waterMax, oilMax }", in that order +========================== ================== ======== ================================================================================================================================================================== + + diff --git a/src/coreComponents/schema/docs/VanGenuchtenStone2RelativePermeability_other.rst b/src/coreComponents/schema/docs/VanGenuchtenStone2RelativePermeability_other.rst new file mode 100644 index 00000000000..e6dd24aeb13 --- /dev/null +++ b/src/coreComponents/schema/docs/VanGenuchtenStone2RelativePermeability_other.rst @@ -0,0 +1,15 @@ + + +=============================== ============== ======================================================================================================================= +Name Type Description +=============================== ============== ======================================================================================================================= +dPhaseRelPerm_dPhaseVolFraction real64_array4d Derivative of phase relative permeability with respect to phase volume fraction +phaseOrder integer_array (no description available) +phaseRelPerm real64_array3d Phase relative permeability +phaseRelPerm_n real64_array3d Phase relative permeability at previous time +phaseTrappedVolFraction real64_array3d Phase trapped volume fraction +phaseTypes integer_array (no description available) +volFracScale real64 Factor used to scale the phase capillary pressure, defined as: one minus the sum of the phase minimum volume fractions. +=============================== ============== ======================================================================================================================= + + diff --git a/src/coreComponents/schema/schema.xsd b/src/coreComponents/schema/schema.xsd index c9e84b3858e..cc6252a0d79 100644 --- a/src/coreComponents/schema/schema.xsd +++ b/src/coreComponents/schema/schema.xsd @@ -3724,6 +3724,8 @@ The expected format is "{ waterMax, oilMax }", in that order--> + + @@ -3740,6 +3742,8 @@ The expected format is "{ waterMax, oilMax }", in that order--> + + @@ -3756,6 +3760,8 @@ The expected format is "{ waterMax, oilMax }", in that order--> + + @@ -3772,6 +3778,8 @@ The expected format is "{ waterMax, oilMax }", in that order--> + + diff --git a/src/coreComponents/unitTests/constitutiveTests/testCO2BrinePVTModels.cpp b/src/coreComponents/unitTests/constitutiveTests/testCO2BrinePVTModels.cpp index 24cee2654a9..5012287a2f1 100644 --- a/src/coreComponents/unitTests/constitutiveTests/testCO2BrinePVTModels.cpp +++ b/src/coreComponents/unitTests/constitutiveTests/testCO2BrinePVTModels.cpp @@ -364,7 +364,8 @@ std::unique_ptr< MODEL > makePVTFunction( string const & filename, pvtFunction = std::make_unique< MODEL >( strs[1], strs, componentNames, - componentMolarWeight ); + componentMolarWeight, + true ); // print PVT tables } } GEOS_ERROR_IF( pvtFunction == nullptr, @@ -405,7 +406,8 @@ std::unique_ptr< MODEL > makeFlashModel( string const & filename, strs, phaseNames, componentNames, - componentMolarWeight ); + componentMolarWeight, + true ); // print PVT tables } } GEOS_ERROR_IF( flashModel == nullptr, diff --git a/src/coreComponents/unitTests/constitutiveTests/testMultiFluid.cpp b/src/coreComponents/unitTests/constitutiveTests/testMultiFluid.cpp index 3e56c1629b1..2297aaba3f3 100644 --- a/src/coreComponents/unitTests/constitutiveTests/testMultiFluid.cpp +++ b/src/coreComponents/unitTests/constitutiveTests/testMultiFluid.cpp @@ -904,14 +904,14 @@ TEST_F( CO2BrinePhillipsFluidTest, checkAgainstPreviousImplementationMolar ) dynamicCast< CO2BrinePhillipsFluid * >( fluid )->createKernelWrapper(); real64 const savedTotalDens[] = - { 5881.8128183956969224, 5869.522096458530541, 5854.9469601674582009, 9180.9455320478591602, 9157.2045503913905122, 9129.1751063784995495, 15755.475565136142905, 15696.691553847707837, - 15627.990771463533747 }; + { 5881.9010529428224, 5869.6094131788523, 5855.0332090690354, 9181.3523596865525, 9157.6071613646127, 9129.5728206336826, 15757.685798517123, 15698.877814368472, + 15630.149353340639 }; real64 const savedGasPhaseFrac[] = { 0.29413690046142371148, 0.29415754810481165027, 0.29418169867697463449, 0.29194010802017489326, 0.29196434961986583723, 0.29199266189550621142, 0.2890641335638892695, 0.28908718137828937067, 0.28911404840933618843 }; real64 const savedWaterDens[] = - { 53286.457784368176362, 53264.389103437584708, 53237.751306267287873, 53229.257940878436784, 53207.597127679167897, 53181.436584967217641, 53197.49848403003125, 53176.033397316634364, - 53150.105086882285832 }; + { 53296.719183517576, 53274.578175308554, 53247.856162690216, 53248.577831698305, 53226.801031868054, 53200.505577694363, 53232.959859840405, 53211.345942175059, + 53185.244751356993 }; real64 const savedGasDens[] = { 1876.2436091302606656, 1872.184636376355229, 1867.3711104617746059, 3053.1548401973859654, 3044.5748249030266379, 3034.4507978134674886, 5769.0622621289458039, 5742.8476745352018042, 5712.2837704249559465 }; @@ -970,11 +970,11 @@ TEST_F( CO2BrinePhillipsFluidTest, checkAgainstPreviousImplementationMass ) dynamicCast< CO2BrinePhillipsFluid * >( fluid )->createKernelWrapper(); real64 const savedTotalDens[] = - { 238.33977561940088208, 237.86350488026934613, 237.29874890241927687, 354.01144731214282046, 353.18618684355078585, 352.21120673560858449, 550.02182875764299297, 548.3889751707506548, - 546.47580480217254717 }; + { 238.31504112633914, 237.83897306400553, 237.27445306546298, 353.95258514794097, 353.12773295711992, 352.1532278769692, 549.90502586392017, 548.2725957521294, + 546.35992000222234 }; real64 const savedGasPhaseFrac[] = - { 0.28562868803317220667, 0.28567941665326646028, 0.285738749802139258, 0.28022484140718162404, 0.2802844989853667812, 0.28035417162546172332, 0.2731355646393489045, 0.27319238868618361815, - 0.2732586251114847431 }; + { 0.28566797890570228, 0.28571845092287312, 0.28577748565482669, 0.28029804182709406, 0.28035729907078311, 0.2804265068556816, 0.27326788204506247, 0.27332422114692956, + 0.27338989611171055 }; real64 const savedWaterDens[] = { 970.85108546544745423, 970.4075834766143771, 969.87385780866463847, 974.23383396044232541, 973.78856424100911227, 973.25280170872576946, 979.48333010951580491, 979.04147229150635212, 978.50977403260912979 }; @@ -994,11 +994,11 @@ TEST_F( CO2BrinePhillipsFluidTest, checkAgainstPreviousImplementationMass ) { 1.9042384704865343673e-05, 1.9062615947696152414e-05, 1.9086923154230274463e-05, 2.0061713844617985449e-05, 2.0075955757102255573e-05, 2.0093249989250199265e-05, 2.3889596884008691474e-05, 2.3865756080512667728e-05, 2.3839170076324036522e-05 }; real64 const savedWaterPhaseGasComp[] = - { 0.02005966592318779787, 0.019990461277537684842, 0.019909503061226688919, 0.027365230280837819082, 0.027285226317914228894, 0.027191770514265831832, 0.036759501299346700187, - 0.036684965747010883641, 0.036598063202886929601 }; + { 0.020063528822832473, 0.019994285300494085, 0.019913282008777046, 0.027375162661670061, 0.027295074213708581, 0.02720152052681666, 0.036784005129927563, + 0.036709327088310859, 0.036622259649145526 }; real64 const savedWaterPhaseWaterComp[] = - { 0.9797478006656266114, 0.97981828292617156873, 0.97990072673671935188, 0.97227194517798976037, 0.97235397979974458327, 0.97244979317916002692, 0.9625743441996873484, 0.9626514061444874093, - 0.962741233539301966 }; + { 0.97993647117716742, 0.98000571469950604, 0.98008671799122282, 0.97262483733832983, 0.97270492578629131, 0.97279847947318321, 0.96321599487007259, 0.96329067291168902, + 0.96337774035085455 }; integer counter = 0; for( integer i = 0; i < 3; ++i ) From 5e1c3b6516e7d0ca54eced060f5a6fada4acbb22 Mon Sep 17 00:00:00 2001 From: Pavel Tomin Date: Tue, 12 Dec 2023 19:35:22 -0600 Subject: [PATCH 04/40] Fix issues #2837 and #2838 (#2843) - Fix failing case (`compositionalMultiphaseFlow/deadoil_3ph_corey_1d_fractured.xml`) and correct PVT table. - Fix convergence problem for `multiphaseFlowFractures/deadoil_3ph_corey_2d_impermeableFault.xml` by disabling line search. --- .../deadoil_3ph_corey_1d_fractured.xml | 10 ++++++++-- inputFiles/compositionalMultiphaseFlow/pvdo.txt | 14 +++++++------- .../deadoil_3ph_corey_2d_impermeableFault.xml | 1 + inputFiles/multiphaseFlowFractures/pvdo.txt | 14 +++++++------- integratedTests | 2 +- 5 files changed, 24 insertions(+), 17 deletions(-) diff --git a/inputFiles/compositionalMultiphaseFlow/deadoil_3ph_corey_1d_fractured.xml b/inputFiles/compositionalMultiphaseFlow/deadoil_3ph_corey_1d_fractured.xml index 6f1addf105c..35d52ad08c2 100644 --- a/inputFiles/compositionalMultiphaseFlow/deadoil_3ph_corey_1d_fractured.xml +++ b/inputFiles/compositionalMultiphaseFlow/deadoil_3ph_corey_1d_fractured.xml @@ -10,7 +10,8 @@ useMass="0" targetRegions="{ Domain, Fracture }"> @@ -23,7 +24,7 @@ @@ -99,6 +100,11 @@ + + + diff --git a/inputFiles/compositionalMultiphaseFlow/pvdo.txt b/inputFiles/compositionalMultiphaseFlow/pvdo.txt index df0be305c70..ce761452b11 100644 --- a/inputFiles/compositionalMultiphaseFlow/pvdo.txt +++ b/inputFiles/compositionalMultiphaseFlow/pvdo.txt @@ -1,8 +1,8 @@ # P[Pa] Bo[m3/sm3] Visc(Pa.s) -2000000 1.02 0.000975 -5000000 1.03 0.00091 -10000000 1.04 0.00083 -20000000 1.05 0.000695 -30000000 1.07 0.000594 -40000000 1.08 0.00051 -50000000.7 1.09 0.000449 +2000000 1.09 0.000449 +5000000 1.08 0.00051 +10000000 1.07 0.000594 +20000000 1.05 0.000695 +30000000 1.04 0.00083 +40000000 1.03 0.00091 +50000000.7 1.02 0.000975 diff --git a/inputFiles/multiphaseFlowFractures/deadoil_3ph_corey_2d_impermeableFault.xml b/inputFiles/multiphaseFlowFractures/deadoil_3ph_corey_2d_impermeableFault.xml index 3b037407799..3ba4ba2a899 100644 --- a/inputFiles/multiphaseFlowFractures/deadoil_3ph_corey_2d_impermeableFault.xml +++ b/inputFiles/multiphaseFlowFractures/deadoil_3ph_corey_2d_impermeableFault.xml @@ -15,6 +15,7 @@ targetRegions="{ Domain, Fracture }" initialDt="1e4"> Date: Thu, 14 Dec 2023 03:20:04 +0700 Subject: [PATCH 05/40] Adding check and warning message for defaultThermalExpansionCoefficient (#2809) There are two different Thermal Expansion Coefficients (TEC), one for updating stress/strain and one for updating the porosity w.r.t temperature change. Actually a same name is used for these two TEC in the input XML interface. This PR aims to specify two different name for these two different parameters to avoid potential user confusion as shown in the issue #2407 . --- .../ThermoPoroElastic_consolidation_base.xml | 4 +- .../ThermoPoroElastic_staircase_co2_smoke.xml | 8 +-- ...asticWellbore_ImperfectInterfaces_base.xml | 6 +- .../CasedThermoElasticWellbore_base.xml | 6 +- .../ThermoPoroElasticWellbore_base.xml | 4 +- integratedTests | 2 +- .../constitutive/solid/SolidBase.hpp | 12 ++-- .../solid/porosity/BiotPorosity.hpp | 2 +- .../schema/docs/BiotPorosity.rst | 16 ++--- .../schema/docs/CeramicDamage.rst | 30 ++++---- .../schema/docs/DamageElasticIsotropic.rst | 38 +++++------ .../docs/DamageSpectralElasticIsotropic.rst | 38 +++++------ .../docs/DamageVolDevElasticIsotropic.rst | 38 +++++------ src/coreComponents/schema/docs/DelftEgg.rst | 32 ++++----- .../schema/docs/DruckerPrager.rst | 30 ++++---- .../schema/docs/ElasticIsotropic.rst | 22 +++--- .../ElasticIsotropicPressureDependent.rst | 22 +++--- .../schema/docs/ElasticOrthotropic.rst | 50 +++++++------- .../docs/ElasticTransverseIsotropic.rst | 2 +- .../schema/docs/ExtendedDruckerPrager.rst | 32 ++++----- .../schema/docs/ModifiedCamClay.rst | 28 ++++---- .../schema/docs/PerfectlyPlastic.rst | 24 +++---- .../schema/docs/ViscoDruckerPrager.rst | 32 ++++----- .../docs/ViscoExtendedDruckerPrager.rst | 34 +++++----- .../schema/docs/ViscoModifiedCamClay.rst | 30 ++++---- src/coreComponents/schema/schema.xsd | 68 +++++++++---------- .../analyticalResults.py | 6 +- 27 files changed, 309 insertions(+), 307 deletions(-) diff --git a/inputFiles/thermoPoromechanics/ThermoPoroElastic_consolidation_base.xml b/inputFiles/thermoPoromechanics/ThermoPoroElastic_consolidation_base.xml index 9fba5a43d5f..f2fde537baf 100644 --- a/inputFiles/thermoPoromechanics/ThermoPoroElastic_consolidation_base.xml +++ b/inputFiles/thermoPoromechanics/ThermoPoroElastic_consolidation_base.xml @@ -35,7 +35,7 @@ defaultDensity="2400" defaultBulkModulus="1e4" defaultShearModulus="2.143e3" - defaultThermalExpansionCoefficient="3e-7"/> + defaultDrainedLinearTEC="3e-7"/> @@ -43,7 +43,7 @@ name="rockPorosity" grainBulkModulus="1.0e27" defaultReferencePorosity="0.2" - defaultThermalExpansionCoefficient="3e-7"/> + defaultPorosityTEC="3e-7"/> diff --git a/inputFiles/thermoPoromechanics/ThermoPoroElastic_staircase_co2_smoke.xml b/inputFiles/thermoPoromechanics/ThermoPoroElastic_staircase_co2_smoke.xml index 4b4f424cd89..97101133740 100644 --- a/inputFiles/thermoPoromechanics/ThermoPoroElastic_staircase_co2_smoke.xml +++ b/inputFiles/thermoPoromechanics/ThermoPoroElastic_staircase_co2_smoke.xml @@ -165,7 +165,7 @@ defaultDensity="2650" defaultBulkModulus="5.e9" defaultPoissonRatio="0.25" - defaultThermalExpansionCoefficient="1e-5"/> + defaultDrainedLinearTEC="1e-5"/> @@ -173,7 +173,7 @@ name="rockPorosityChannel" grainBulkModulus="1.0e27" defaultReferencePorosity="0.2" - defaultThermalExpansionCoefficient="1e-5"/> + defaultPorosityTEC="1e-5"/> + defaultDrainedLinearTEC="1e-5"/> @@ -194,7 +194,7 @@ name="rockPorosityBarrier" grainBulkModulus="1.0e27" defaultReferencePorosity="0.05" - defaultThermalExpansionCoefficient="1e-5"/> + defaultPorosityTEC="1e-5"/> + defaultDrainedLinearTEC="1.2e-5"/> + defaultDrainedLinearTEC="2.0e-5"/> + defaultDrainedLinearTEC="2.0e-5"/> diff --git a/inputFiles/wellbore/CasedThermoElasticWellbore_base.xml b/inputFiles/wellbore/CasedThermoElasticWellbore_base.xml index 94c6bf9017c..41f9e2ef493 100644 --- a/inputFiles/wellbore/CasedThermoElasticWellbore_base.xml +++ b/inputFiles/wellbore/CasedThermoElasticWellbore_base.xml @@ -175,21 +175,21 @@ defaultDensity="7500" defaultBulkModulus="159.4202899e9" defaultShearModulus="86.61417323e9" - defaultThermalExpansionCoefficient="1.2e-5"/> + defaultDrainedLinearTEC="1.2e-5"/> + defaultDrainedLinearTEC="2.0e-5"/> + defaultDrainedLinearTEC="2.0e-5"/> diff --git a/inputFiles/wellbore/ThermoPoroElasticWellbore_base.xml b/inputFiles/wellbore/ThermoPoroElasticWellbore_base.xml index f264c696b33..1f04e96db15 100644 --- a/inputFiles/wellbore/ThermoPoroElasticWellbore_base.xml +++ b/inputFiles/wellbore/ThermoPoroElasticWellbore_base.xml @@ -67,7 +67,7 @@ name="rockPorosity" defaultReferencePorosity="0.001" grainBulkModulus="23.5e9" - defaultThermalExpansionCoefficient="4e-5"/> + defaultPorosityTEC="4e-5"/> @@ -110,7 +110,7 @@ defaultDensity="2700" defaultBulkModulus="20.7e9" defaultShearModulus="12.4e9" - defaultThermalExpansionCoefficient="4e-5"/> + defaultDrainedLinearTEC="4e-5"/> diff --git a/integratedTests b/integratedTests index 2096e193de9..b08da69e4e0 160000 --- a/integratedTests +++ b/integratedTests @@ -1 +1 @@ -Subproject commit 2096e193de907b18815da633f5390cf47dc6c4a9 +Subproject commit b08da69e4e0f57b744b4f5496a829322b8fc3820 diff --git a/src/coreComponents/constitutive/solid/SolidBase.hpp b/src/coreComponents/constitutive/solid/SolidBase.hpp index fb91c53c330..e91a3323242 100644 --- a/src/coreComponents/constitutive/solid/SolidBase.hpp +++ b/src/coreComponents/constitutive/solid/SolidBase.hpp @@ -557,11 +557,13 @@ class SolidBase : public constitutive::ConstitutiveBase static constexpr char const * defaultDensityString() { return "defaultDensity"; } ///< Default density key static constexpr char const * thermalExpansionCoefficientString() { return "thermalExpansionCoefficient"; } // Thermal expansion // coefficient key - static constexpr char const * defaultThermalExpansionCoefficientString() { return "defaultThermalExpansionCoefficient"; } // Default - // thermal - // expansion - // coefficient - // key + static constexpr char const * defaultThermalExpansionCoefficientString() { return "defaultDrainedLinearTEC"; } // Default + // drained + // linear + // thermal + // expansion + // coefficient + // key }; /** diff --git a/src/coreComponents/constitutive/solid/porosity/BiotPorosity.hpp b/src/coreComponents/constitutive/solid/porosity/BiotPorosity.hpp index 428cad81576..d7a0280fb9c 100644 --- a/src/coreComponents/constitutive/solid/porosity/BiotPorosity.hpp +++ b/src/coreComponents/constitutive/solid/porosity/BiotPorosity.hpp @@ -228,7 +228,7 @@ class BiotPorosity : public PorosityBase static constexpr char const *solidBulkModulusString() { return "solidBulkModulus"; } - static constexpr char const *defaultThermalExpansionCoefficientString() { return "defaultThermalExpansionCoefficient"; } + static constexpr char const *defaultThermalExpansionCoefficientString() { return "defaultPorosityTEC"; } } viewKeys; virtual void initializeState() const override final; diff --git a/src/coreComponents/schema/docs/BiotPorosity.rst b/src/coreComponents/schema/docs/BiotPorosity.rst index 906b76b732a..2bc5c5f2678 100644 --- a/src/coreComponents/schema/docs/BiotPorosity.rst +++ b/src/coreComponents/schema/docs/BiotPorosity.rst @@ -1,12 +1,12 @@ -================================== ========= ======== =========================================== -Name Type Default Description -================================== ========= ======== =========================================== -defaultReferencePorosity real64 required Default value of the reference porosity -defaultThermalExpansionCoefficient real64 0 Default thermal expansion coefficient -grainBulkModulus real64 required Grain bulk modulus -name groupName required A name is required for any non-unique nodes -================================== ========= ======== =========================================== +======================== ========= ======== =========================================== +Name Type Default Description +======================== ========= ======== =========================================== +defaultPorosityTEC real64 0 Default thermal expansion coefficient +defaultReferencePorosity real64 required Default value of the reference porosity +grainBulkModulus real64 required Grain bulk modulus +name groupName required A name is required for any non-unique nodes +======================== ========= ======== =========================================== diff --git a/src/coreComponents/schema/docs/CeramicDamage.rst b/src/coreComponents/schema/docs/CeramicDamage.rst index b79c9c86c20..fa671dd48bc 100644 --- a/src/coreComponents/schema/docs/CeramicDamage.rst +++ b/src/coreComponents/schema/docs/CeramicDamage.rst @@ -1,19 +1,19 @@ -================================== ========= ======== ==================================================================== -Name Type Default Description -================================== ========= ======== ==================================================================== -compressiveStrength real64 required Compressive strength -crackSpeed real64 required Crack speed -defaultBulkModulus real64 -1 Default Bulk Modulus Parameter -defaultDensity real64 required Default Material Density -defaultPoissonRatio real64 -1 Default Poisson's Ratio -defaultShearModulus real64 -1 Default Shear Modulus Parameter -defaultThermalExpansionCoefficient real64 0 Default Linear Thermal Expansion Coefficient of the Solid Rock Frame -defaultYoungModulus real64 -1 Default Young's Modulus -maximumStrength real64 required Maximum theoretical strength -name groupName required A name is required for any non-unique nodes -tensileStrength real64 required Tensile strength -================================== ========= ======== ==================================================================== +======================= ========= ======== ==================================================================== +Name Type Default Description +======================= ========= ======== ==================================================================== +compressiveStrength real64 required Compressive strength +crackSpeed real64 required Crack speed +defaultBulkModulus real64 -1 Default Bulk Modulus Parameter +defaultDensity real64 required Default Material Density +defaultDrainedLinearTEC real64 0 Default Linear Thermal Expansion Coefficient of the Solid Rock Frame +defaultPoissonRatio real64 -1 Default Poisson's Ratio +defaultShearModulus real64 -1 Default Shear Modulus Parameter +defaultYoungModulus real64 -1 Default Young's Modulus +maximumStrength real64 required Maximum theoretical strength +name groupName required A name is required for any non-unique nodes +tensileStrength real64 required Tensile strength +======================= ========= ======== ==================================================================== diff --git a/src/coreComponents/schema/docs/DamageElasticIsotropic.rst b/src/coreComponents/schema/docs/DamageElasticIsotropic.rst index e44a8e4181a..19236b59dcf 100644 --- a/src/coreComponents/schema/docs/DamageElasticIsotropic.rst +++ b/src/coreComponents/schema/docs/DamageElasticIsotropic.rst @@ -1,23 +1,23 @@ -================================== ========= ======== ==================================================================== -Name Type Default Description -================================== ========= ======== ==================================================================== -compressiveStrength real64 0 Compressive strength from the uniaxial compression test -criticalFractureEnergy real64 required Critical fracture energy -criticalStrainEnergy real64 required Critical stress in a 1d tension test -defaultBulkModulus real64 -1 Default Bulk Modulus Parameter -defaultDensity real64 required Default Material Density -defaultPoissonRatio real64 -1 Default Poisson's Ratio -defaultShearModulus real64 -1 Default Shear Modulus Parameter -defaultThermalExpansionCoefficient real64 0 Default Linear Thermal Expansion Coefficient of the Solid Rock Frame -defaultYoungModulus real64 -1 Default Young's Modulus -degradationLowerLimit real64 0 The lower limit of the degradation function -deltaCoefficient real64 -1 Coefficient in the calculation of the external driving force -extDrivingForceFlag integer 0 Whether to have external driving force. Can be 0 or 1 -lengthScale real64 required Length scale l in the phase-field equation -name groupName required A name is required for any non-unique nodes -tensileStrength real64 0 Tensile strength from the uniaxial tension test -================================== ========= ======== ==================================================================== +======================= ========= ======== ==================================================================== +Name Type Default Description +======================= ========= ======== ==================================================================== +compressiveStrength real64 0 Compressive strength from the uniaxial compression test +criticalFractureEnergy real64 required Critical fracture energy +criticalStrainEnergy real64 required Critical stress in a 1d tension test +defaultBulkModulus real64 -1 Default Bulk Modulus Parameter +defaultDensity real64 required Default Material Density +defaultDrainedLinearTEC real64 0 Default Linear Thermal Expansion Coefficient of the Solid Rock Frame +defaultPoissonRatio real64 -1 Default Poisson's Ratio +defaultShearModulus real64 -1 Default Shear Modulus Parameter +defaultYoungModulus real64 -1 Default Young's Modulus +degradationLowerLimit real64 0 The lower limit of the degradation function +deltaCoefficient real64 -1 Coefficient in the calculation of the external driving force +extDrivingForceFlag integer 0 Whether to have external driving force. Can be 0 or 1 +lengthScale real64 required Length scale l in the phase-field equation +name groupName required A name is required for any non-unique nodes +tensileStrength real64 0 Tensile strength from the uniaxial tension test +======================= ========= ======== ==================================================================== diff --git a/src/coreComponents/schema/docs/DamageSpectralElasticIsotropic.rst b/src/coreComponents/schema/docs/DamageSpectralElasticIsotropic.rst index e44a8e4181a..19236b59dcf 100644 --- a/src/coreComponents/schema/docs/DamageSpectralElasticIsotropic.rst +++ b/src/coreComponents/schema/docs/DamageSpectralElasticIsotropic.rst @@ -1,23 +1,23 @@ -================================== ========= ======== ==================================================================== -Name Type Default Description -================================== ========= ======== ==================================================================== -compressiveStrength real64 0 Compressive strength from the uniaxial compression test -criticalFractureEnergy real64 required Critical fracture energy -criticalStrainEnergy real64 required Critical stress in a 1d tension test -defaultBulkModulus real64 -1 Default Bulk Modulus Parameter -defaultDensity real64 required Default Material Density -defaultPoissonRatio real64 -1 Default Poisson's Ratio -defaultShearModulus real64 -1 Default Shear Modulus Parameter -defaultThermalExpansionCoefficient real64 0 Default Linear Thermal Expansion Coefficient of the Solid Rock Frame -defaultYoungModulus real64 -1 Default Young's Modulus -degradationLowerLimit real64 0 The lower limit of the degradation function -deltaCoefficient real64 -1 Coefficient in the calculation of the external driving force -extDrivingForceFlag integer 0 Whether to have external driving force. Can be 0 or 1 -lengthScale real64 required Length scale l in the phase-field equation -name groupName required A name is required for any non-unique nodes -tensileStrength real64 0 Tensile strength from the uniaxial tension test -================================== ========= ======== ==================================================================== +======================= ========= ======== ==================================================================== +Name Type Default Description +======================= ========= ======== ==================================================================== +compressiveStrength real64 0 Compressive strength from the uniaxial compression test +criticalFractureEnergy real64 required Critical fracture energy +criticalStrainEnergy real64 required Critical stress in a 1d tension test +defaultBulkModulus real64 -1 Default Bulk Modulus Parameter +defaultDensity real64 required Default Material Density +defaultDrainedLinearTEC real64 0 Default Linear Thermal Expansion Coefficient of the Solid Rock Frame +defaultPoissonRatio real64 -1 Default Poisson's Ratio +defaultShearModulus real64 -1 Default Shear Modulus Parameter +defaultYoungModulus real64 -1 Default Young's Modulus +degradationLowerLimit real64 0 The lower limit of the degradation function +deltaCoefficient real64 -1 Coefficient in the calculation of the external driving force +extDrivingForceFlag integer 0 Whether to have external driving force. Can be 0 or 1 +lengthScale real64 required Length scale l in the phase-field equation +name groupName required A name is required for any non-unique nodes +tensileStrength real64 0 Tensile strength from the uniaxial tension test +======================= ========= ======== ==================================================================== diff --git a/src/coreComponents/schema/docs/DamageVolDevElasticIsotropic.rst b/src/coreComponents/schema/docs/DamageVolDevElasticIsotropic.rst index e44a8e4181a..19236b59dcf 100644 --- a/src/coreComponents/schema/docs/DamageVolDevElasticIsotropic.rst +++ b/src/coreComponents/schema/docs/DamageVolDevElasticIsotropic.rst @@ -1,23 +1,23 @@ -================================== ========= ======== ==================================================================== -Name Type Default Description -================================== ========= ======== ==================================================================== -compressiveStrength real64 0 Compressive strength from the uniaxial compression test -criticalFractureEnergy real64 required Critical fracture energy -criticalStrainEnergy real64 required Critical stress in a 1d tension test -defaultBulkModulus real64 -1 Default Bulk Modulus Parameter -defaultDensity real64 required Default Material Density -defaultPoissonRatio real64 -1 Default Poisson's Ratio -defaultShearModulus real64 -1 Default Shear Modulus Parameter -defaultThermalExpansionCoefficient real64 0 Default Linear Thermal Expansion Coefficient of the Solid Rock Frame -defaultYoungModulus real64 -1 Default Young's Modulus -degradationLowerLimit real64 0 The lower limit of the degradation function -deltaCoefficient real64 -1 Coefficient in the calculation of the external driving force -extDrivingForceFlag integer 0 Whether to have external driving force. Can be 0 or 1 -lengthScale real64 required Length scale l in the phase-field equation -name groupName required A name is required for any non-unique nodes -tensileStrength real64 0 Tensile strength from the uniaxial tension test -================================== ========= ======== ==================================================================== +======================= ========= ======== ==================================================================== +Name Type Default Description +======================= ========= ======== ==================================================================== +compressiveStrength real64 0 Compressive strength from the uniaxial compression test +criticalFractureEnergy real64 required Critical fracture energy +criticalStrainEnergy real64 required Critical stress in a 1d tension test +defaultBulkModulus real64 -1 Default Bulk Modulus Parameter +defaultDensity real64 required Default Material Density +defaultDrainedLinearTEC real64 0 Default Linear Thermal Expansion Coefficient of the Solid Rock Frame +defaultPoissonRatio real64 -1 Default Poisson's Ratio +defaultShearModulus real64 -1 Default Shear Modulus Parameter +defaultYoungModulus real64 -1 Default Young's Modulus +degradationLowerLimit real64 0 The lower limit of the degradation function +deltaCoefficient real64 -1 Coefficient in the calculation of the external driving force +extDrivingForceFlag integer 0 Whether to have external driving force. Can be 0 or 1 +lengthScale real64 required Length scale l in the phase-field equation +name groupName required A name is required for any non-unique nodes +tensileStrength real64 0 Tensile strength from the uniaxial tension test +======================= ========= ======== ==================================================================== diff --git a/src/coreComponents/schema/docs/DelftEgg.rst b/src/coreComponents/schema/docs/DelftEgg.rst index 96ec77b6129..a509cdd295e 100644 --- a/src/coreComponents/schema/docs/DelftEgg.rst +++ b/src/coreComponents/schema/docs/DelftEgg.rst @@ -1,20 +1,20 @@ -================================== ========= ======== ==================================================================== -Name Type Default Description -================================== ========= ======== ==================================================================== -defaultBulkModulus real64 -1 Default Bulk Modulus Parameter -defaultCslSlope real64 1 Slope of the critical state line -defaultDensity real64 required Default Material Density -defaultPoissonRatio real64 -1 Default Poisson's Ratio -defaultPreConsolidationPressure real64 -1.5 Initial preconsolidation pressure -defaultRecompressionIndex real64 0.002 Recompresion Index -defaultShapeParameter real64 1 Shape parameter for the yield surface -defaultShearModulus real64 -1 Default Shear Modulus Parameter -defaultThermalExpansionCoefficient real64 0 Default Linear Thermal Expansion Coefficient of the Solid Rock Frame -defaultVirginCompressionIndex real64 0.005 Virgin compression index -defaultYoungModulus real64 -1 Default Young's Modulus -name groupName required A name is required for any non-unique nodes -================================== ========= ======== ==================================================================== +=============================== ========= ======== ==================================================================== +Name Type Default Description +=============================== ========= ======== ==================================================================== +defaultBulkModulus real64 -1 Default Bulk Modulus Parameter +defaultCslSlope real64 1 Slope of the critical state line +defaultDensity real64 required Default Material Density +defaultDrainedLinearTEC real64 0 Default Linear Thermal Expansion Coefficient of the Solid Rock Frame +defaultPoissonRatio real64 -1 Default Poisson's Ratio +defaultPreConsolidationPressure real64 -1.5 Initial preconsolidation pressure +defaultRecompressionIndex real64 0.002 Recompresion Index +defaultShapeParameter real64 1 Shape parameter for the yield surface +defaultShearModulus real64 -1 Default Shear Modulus Parameter +defaultVirginCompressionIndex real64 0.005 Virgin compression index +defaultYoungModulus real64 -1 Default Young's Modulus +name groupName required A name is required for any non-unique nodes +=============================== ========= ======== ==================================================================== diff --git a/src/coreComponents/schema/docs/DruckerPrager.rst b/src/coreComponents/schema/docs/DruckerPrager.rst index 92559d3ebc8..3f5d4ef7249 100644 --- a/src/coreComponents/schema/docs/DruckerPrager.rst +++ b/src/coreComponents/schema/docs/DruckerPrager.rst @@ -1,19 +1,19 @@ -================================== ========= ======== ==================================================================== -Name Type Default Description -================================== ========= ======== ==================================================================== -defaultBulkModulus real64 -1 Default Bulk Modulus Parameter -defaultCohesion real64 0 Initial cohesion -defaultDensity real64 required Default Material Density -defaultDilationAngle real64 30 Dilation angle (degrees) -defaultFrictionAngle real64 30 Friction angle (degrees) -defaultHardeningRate real64 0 Cohesion hardening/softening rate -defaultPoissonRatio real64 -1 Default Poisson's Ratio -defaultShearModulus real64 -1 Default Shear Modulus Parameter -defaultThermalExpansionCoefficient real64 0 Default Linear Thermal Expansion Coefficient of the Solid Rock Frame -defaultYoungModulus real64 -1 Default Young's Modulus -name groupName required A name is required for any non-unique nodes -================================== ========= ======== ==================================================================== +======================= ========= ======== ==================================================================== +Name Type Default Description +======================= ========= ======== ==================================================================== +defaultBulkModulus real64 -1 Default Bulk Modulus Parameter +defaultCohesion real64 0 Initial cohesion +defaultDensity real64 required Default Material Density +defaultDilationAngle real64 30 Dilation angle (degrees) +defaultDrainedLinearTEC real64 0 Default Linear Thermal Expansion Coefficient of the Solid Rock Frame +defaultFrictionAngle real64 30 Friction angle (degrees) +defaultHardeningRate real64 0 Cohesion hardening/softening rate +defaultPoissonRatio real64 -1 Default Poisson's Ratio +defaultShearModulus real64 -1 Default Shear Modulus Parameter +defaultYoungModulus real64 -1 Default Young's Modulus +name groupName required A name is required for any non-unique nodes +======================= ========= ======== ==================================================================== diff --git a/src/coreComponents/schema/docs/ElasticIsotropic.rst b/src/coreComponents/schema/docs/ElasticIsotropic.rst index 84c48b891ff..15e42d76f2b 100644 --- a/src/coreComponents/schema/docs/ElasticIsotropic.rst +++ b/src/coreComponents/schema/docs/ElasticIsotropic.rst @@ -1,15 +1,15 @@ -================================== ========= ======== ==================================================================== -Name Type Default Description -================================== ========= ======== ==================================================================== -defaultBulkModulus real64 -1 Default Bulk Modulus Parameter -defaultDensity real64 required Default Material Density -defaultPoissonRatio real64 -1 Default Poisson's Ratio -defaultShearModulus real64 -1 Default Shear Modulus Parameter -defaultThermalExpansionCoefficient real64 0 Default Linear Thermal Expansion Coefficient of the Solid Rock Frame -defaultYoungModulus real64 -1 Default Young's Modulus -name groupName required A name is required for any non-unique nodes -================================== ========= ======== ==================================================================== +======================= ========= ======== ==================================================================== +Name Type Default Description +======================= ========= ======== ==================================================================== +defaultBulkModulus real64 -1 Default Bulk Modulus Parameter +defaultDensity real64 required Default Material Density +defaultDrainedLinearTEC real64 0 Default Linear Thermal Expansion Coefficient of the Solid Rock Frame +defaultPoissonRatio real64 -1 Default Poisson's Ratio +defaultShearModulus real64 -1 Default Shear Modulus Parameter +defaultYoungModulus real64 -1 Default Young's Modulus +name groupName required A name is required for any non-unique nodes +======================= ========= ======== ==================================================================== diff --git a/src/coreComponents/schema/docs/ElasticIsotropicPressureDependent.rst b/src/coreComponents/schema/docs/ElasticIsotropicPressureDependent.rst index faad5c074ce..96015fd3861 100644 --- a/src/coreComponents/schema/docs/ElasticIsotropicPressureDependent.rst +++ b/src/coreComponents/schema/docs/ElasticIsotropicPressureDependent.rst @@ -1,15 +1,15 @@ -================================== ========= ======== ==================================================================== -Name Type Default Description -================================== ========= ======== ==================================================================== -defaultDensity real64 required Default Material Density -defaultRecompressionIndex real64 0.002 Recompresion Index -defaultRefPressure real64 -1 Reference Pressure -defaultRefStrainVol real64 0 Reference Volumetric Strain -defaultShearModulus real64 -1 Elastic Shear Modulus Parameter -defaultThermalExpansionCoefficient real64 0 Default Linear Thermal Expansion Coefficient of the Solid Rock Frame -name groupName required A name is required for any non-unique nodes -================================== ========= ======== ==================================================================== +========================= ========= ======== ==================================================================== +Name Type Default Description +========================= ========= ======== ==================================================================== +defaultDensity real64 required Default Material Density +defaultDrainedLinearTEC real64 0 Default Linear Thermal Expansion Coefficient of the Solid Rock Frame +defaultRecompressionIndex real64 0.002 Recompresion Index +defaultRefPressure real64 -1 Reference Pressure +defaultRefStrainVol real64 0 Reference Volumetric Strain +defaultShearModulus real64 -1 Elastic Shear Modulus Parameter +name groupName required A name is required for any non-unique nodes +========================= ========= ======== ==================================================================== diff --git a/src/coreComponents/schema/docs/ElasticOrthotropic.rst b/src/coreComponents/schema/docs/ElasticOrthotropic.rst index 2fc6d92fe1a..f2d55b8530f 100644 --- a/src/coreComponents/schema/docs/ElasticOrthotropic.rst +++ b/src/coreComponents/schema/docs/ElasticOrthotropic.rst @@ -1,29 +1,29 @@ -================================== ========= ======== ==================================================================== -Name Type Default Description -================================== ========= ======== ==================================================================== -defaultC11 real64 -1 Default C11 Component of Voigt Stiffness Tensor -defaultC12 real64 -1 Default C12 Component of Voigt Stiffness Tensor -defaultC13 real64 -1 Default C13 Component of Voigt Stiffness Tensor -defaultC22 real64 -1 Default C22 Component of Voigt Stiffness Tensor -defaultC23 real64 -1 Default C23 Component of Voigt Stiffness Tensor -defaultC33 real64 -1 Default C33 Component of Voigt Stiffness Tensor -defaultC44 real64 -1 Default C44 Component of Voigt Stiffness Tensor -defaultC55 real64 -1 Default C55 Component of Voigt Stiffness Tensor -defaultC66 real64 -1 Default C66 Component of Voigt Stiffness Tensor -defaultDensity real64 required Default Material Density -defaultE1 real64 -1 Default Young's Modulus E1 -defaultE2 real64 -1 Default Young's Modulus E2 -defaultE3 real64 -1 Default Young's Modulus E3 -defaultG12 real64 -1 Default Shear Modulus G12 -defaultG13 real64 -1 Default Shear Modulus G13 -defaultG23 real64 -1 Default Shear Modulus G23 -defaultNu12 real64 -1 Default Poission's Ratio Nu12 -defaultNu13 real64 -1 Default Poission's Ratio Nu13 -defaultNu23 real64 -1 Default Poission's Ratio Nu23 -defaultThermalExpansionCoefficient real64 0 Default Linear Thermal Expansion Coefficient of the Solid Rock Frame -name groupName required A name is required for any non-unique nodes -================================== ========= ======== ==================================================================== +======================= ========= ======== ==================================================================== +Name Type Default Description +======================= ========= ======== ==================================================================== +defaultC11 real64 -1 Default C11 Component of Voigt Stiffness Tensor +defaultC12 real64 -1 Default C12 Component of Voigt Stiffness Tensor +defaultC13 real64 -1 Default C13 Component of Voigt Stiffness Tensor +defaultC22 real64 -1 Default C22 Component of Voigt Stiffness Tensor +defaultC23 real64 -1 Default C23 Component of Voigt Stiffness Tensor +defaultC33 real64 -1 Default C33 Component of Voigt Stiffness Tensor +defaultC44 real64 -1 Default C44 Component of Voigt Stiffness Tensor +defaultC55 real64 -1 Default C55 Component of Voigt Stiffness Tensor +defaultC66 real64 -1 Default C66 Component of Voigt Stiffness Tensor +defaultDensity real64 required Default Material Density +defaultDrainedLinearTEC real64 0 Default Linear Thermal Expansion Coefficient of the Solid Rock Frame +defaultE1 real64 -1 Default Young's Modulus E1 +defaultE2 real64 -1 Default Young's Modulus E2 +defaultE3 real64 -1 Default Young's Modulus E3 +defaultG12 real64 -1 Default Shear Modulus G12 +defaultG13 real64 -1 Default Shear Modulus G13 +defaultG23 real64 -1 Default Shear Modulus G23 +defaultNu12 real64 -1 Default Poission's Ratio Nu12 +defaultNu13 real64 -1 Default Poission's Ratio Nu13 +defaultNu23 real64 -1 Default Poission's Ratio Nu23 +name groupName required A name is required for any non-unique nodes +======================= ========= ======== ==================================================================== diff --git a/src/coreComponents/schema/docs/ElasticTransverseIsotropic.rst b/src/coreComponents/schema/docs/ElasticTransverseIsotropic.rst index 8f7b6e25362..e07838a1fd3 100644 --- a/src/coreComponents/schema/docs/ElasticTransverseIsotropic.rst +++ b/src/coreComponents/schema/docs/ElasticTransverseIsotropic.rst @@ -9,10 +9,10 @@ defaultC33 real64 -1 Default Stiffness Paramete defaultC44 real64 -1 Default Stiffness Parameter C44 defaultC66 real64 -1 Default Stiffness Parameter C66 defaultDensity real64 required Default Material Density +defaultDrainedLinearTEC real64 0 Default Linear Thermal Expansion Coefficient of the Solid Rock Frame defaultPoissonRatioAxialTransverse real64 -1 Default Axial-Transverse Poisson's Ratio defaultPoissonRatioTransverse real64 -1 Default Transverse Poisson's Ratio defaultShearModulusAxialTransverse real64 -1 Default Axial-Transverse Shear Modulus -defaultThermalExpansionCoefficient real64 0 Default Linear Thermal Expansion Coefficient of the Solid Rock Frame defaultYoungModulusAxial real64 -1 Default Axial Young's Modulus defaultYoungModulusTransverse real64 -1 Default Transverse Young's Modulus name groupName required A name is required for any non-unique nodes diff --git a/src/coreComponents/schema/docs/ExtendedDruckerPrager.rst b/src/coreComponents/schema/docs/ExtendedDruckerPrager.rst index 5973577923e..a196499ef95 100644 --- a/src/coreComponents/schema/docs/ExtendedDruckerPrager.rst +++ b/src/coreComponents/schema/docs/ExtendedDruckerPrager.rst @@ -1,20 +1,20 @@ -================================== ========= ======== ==================================================================== -Name Type Default Description -================================== ========= ======== ==================================================================== -defaultBulkModulus real64 -1 Default Bulk Modulus Parameter -defaultCohesion real64 0 Initial cohesion -defaultDensity real64 required Default Material Density -defaultDilationRatio real64 1 Dilation ratio [0,1] (ratio = tan dilationAngle / tan frictionAngle) -defaultHardening real64 0 Hardening parameter (hardening rate is faster for smaller values) -defaultInitialFrictionAngle real64 30 Initial friction angle (degrees) -defaultPoissonRatio real64 -1 Default Poisson's Ratio -defaultResidualFrictionAngle real64 30 Residual friction angle (degrees) -defaultShearModulus real64 -1 Default Shear Modulus Parameter -defaultThermalExpansionCoefficient real64 0 Default Linear Thermal Expansion Coefficient of the Solid Rock Frame -defaultYoungModulus real64 -1 Default Young's Modulus -name groupName required A name is required for any non-unique nodes -================================== ========= ======== ==================================================================== +============================ ========= ======== ==================================================================== +Name Type Default Description +============================ ========= ======== ==================================================================== +defaultBulkModulus real64 -1 Default Bulk Modulus Parameter +defaultCohesion real64 0 Initial cohesion +defaultDensity real64 required Default Material Density +defaultDilationRatio real64 1 Dilation ratio [0,1] (ratio = tan dilationAngle / tan frictionAngle) +defaultDrainedLinearTEC real64 0 Default Linear Thermal Expansion Coefficient of the Solid Rock Frame +defaultHardening real64 0 Hardening parameter (hardening rate is faster for smaller values) +defaultInitialFrictionAngle real64 30 Initial friction angle (degrees) +defaultPoissonRatio real64 -1 Default Poisson's Ratio +defaultResidualFrictionAngle real64 30 Residual friction angle (degrees) +defaultShearModulus real64 -1 Default Shear Modulus Parameter +defaultYoungModulus real64 -1 Default Young's Modulus +name groupName required A name is required for any non-unique nodes +============================ ========= ======== ==================================================================== diff --git a/src/coreComponents/schema/docs/ModifiedCamClay.rst b/src/coreComponents/schema/docs/ModifiedCamClay.rst index 5bc050cd142..1784d9660fb 100644 --- a/src/coreComponents/schema/docs/ModifiedCamClay.rst +++ b/src/coreComponents/schema/docs/ModifiedCamClay.rst @@ -1,18 +1,18 @@ -================================== ========= ======== ==================================================================== -Name Type Default Description -================================== ========= ======== ==================================================================== -defaultCslSlope real64 1 Slope of the critical state line -defaultDensity real64 required Default Material Density -defaultPreConsolidationPressure real64 -1.5 Initial preconsolidation pressure -defaultRecompressionIndex real64 0.002 Recompresion Index -defaultRefPressure real64 -1 Reference Pressure -defaultRefStrainVol real64 0 Reference Volumetric Strain -defaultShearModulus real64 -1 Elastic Shear Modulus Parameter -defaultThermalExpansionCoefficient real64 0 Default Linear Thermal Expansion Coefficient of the Solid Rock Frame -defaultVirginCompressionIndex real64 0.005 Virgin compression index -name groupName required A name is required for any non-unique nodes -================================== ========= ======== ==================================================================== +=============================== ========= ======== ==================================================================== +Name Type Default Description +=============================== ========= ======== ==================================================================== +defaultCslSlope real64 1 Slope of the critical state line +defaultDensity real64 required Default Material Density +defaultDrainedLinearTEC real64 0 Default Linear Thermal Expansion Coefficient of the Solid Rock Frame +defaultPreConsolidationPressure real64 -1.5 Initial preconsolidation pressure +defaultRecompressionIndex real64 0.002 Recompresion Index +defaultRefPressure real64 -1 Reference Pressure +defaultRefStrainVol real64 0 Reference Volumetric Strain +defaultShearModulus real64 -1 Elastic Shear Modulus Parameter +defaultVirginCompressionIndex real64 0.005 Virgin compression index +name groupName required A name is required for any non-unique nodes +=============================== ========= ======== ==================================================================== diff --git a/src/coreComponents/schema/docs/PerfectlyPlastic.rst b/src/coreComponents/schema/docs/PerfectlyPlastic.rst index a229423a361..019ce1379dc 100644 --- a/src/coreComponents/schema/docs/PerfectlyPlastic.rst +++ b/src/coreComponents/schema/docs/PerfectlyPlastic.rst @@ -1,16 +1,16 @@ -================================== ========= ============ ==================================================================== -Name Type Default Description -================================== ========= ============ ==================================================================== -defaultBulkModulus real64 -1 Default Bulk Modulus Parameter -defaultDensity real64 required Default Material Density -defaultPoissonRatio real64 -1 Default Poisson's Ratio -defaultShearModulus real64 -1 Default Shear Modulus Parameter -defaultThermalExpansionCoefficient real64 0 Default Linear Thermal Expansion Coefficient of the Solid Rock Frame -defaultYieldStress real64 1.79769e+308 Default yield stress -defaultYoungModulus real64 -1 Default Young's Modulus -name groupName required A name is required for any non-unique nodes -================================== ========= ============ ==================================================================== +======================= ========= ============ ==================================================================== +Name Type Default Description +======================= ========= ============ ==================================================================== +defaultBulkModulus real64 -1 Default Bulk Modulus Parameter +defaultDensity real64 required Default Material Density +defaultDrainedLinearTEC real64 0 Default Linear Thermal Expansion Coefficient of the Solid Rock Frame +defaultPoissonRatio real64 -1 Default Poisson's Ratio +defaultShearModulus real64 -1 Default Shear Modulus Parameter +defaultYieldStress real64 1.79769e+308 Default yield stress +defaultYoungModulus real64 -1 Default Young's Modulus +name groupName required A name is required for any non-unique nodes +======================= ========= ============ ==================================================================== diff --git a/src/coreComponents/schema/docs/ViscoDruckerPrager.rst b/src/coreComponents/schema/docs/ViscoDruckerPrager.rst index 9ed800b1d7b..0fcfe196f5d 100644 --- a/src/coreComponents/schema/docs/ViscoDruckerPrager.rst +++ b/src/coreComponents/schema/docs/ViscoDruckerPrager.rst @@ -1,20 +1,20 @@ -================================== ========= ======== ==================================================================== -Name Type Default Description -================================== ========= ======== ==================================================================== -defaultBulkModulus real64 -1 Default Bulk Modulus Parameter -defaultCohesion real64 0 Initial cohesion -defaultDensity real64 required Default Material Density -defaultDilationAngle real64 30 Dilation angle (degrees) -defaultFrictionAngle real64 30 Friction angle (degrees) -defaultHardeningRate real64 0 Cohesion hardening/softening rate -defaultPoissonRatio real64 -1 Default Poisson's Ratio -defaultShearModulus real64 -1 Default Shear Modulus Parameter -defaultThermalExpansionCoefficient real64 0 Default Linear Thermal Expansion Coefficient of the Solid Rock Frame -defaultYoungModulus real64 -1 Default Young's Modulus -name groupName required A name is required for any non-unique nodes -relaxationTime real64 required Relaxation time -================================== ========= ======== ==================================================================== +======================= ========= ======== ==================================================================== +Name Type Default Description +======================= ========= ======== ==================================================================== +defaultBulkModulus real64 -1 Default Bulk Modulus Parameter +defaultCohesion real64 0 Initial cohesion +defaultDensity real64 required Default Material Density +defaultDilationAngle real64 30 Dilation angle (degrees) +defaultDrainedLinearTEC real64 0 Default Linear Thermal Expansion Coefficient of the Solid Rock Frame +defaultFrictionAngle real64 30 Friction angle (degrees) +defaultHardeningRate real64 0 Cohesion hardening/softening rate +defaultPoissonRatio real64 -1 Default Poisson's Ratio +defaultShearModulus real64 -1 Default Shear Modulus Parameter +defaultYoungModulus real64 -1 Default Young's Modulus +name groupName required A name is required for any non-unique nodes +relaxationTime real64 required Relaxation time +======================= ========= ======== ==================================================================== diff --git a/src/coreComponents/schema/docs/ViscoExtendedDruckerPrager.rst b/src/coreComponents/schema/docs/ViscoExtendedDruckerPrager.rst index 2f3b4d6d43f..6f9cc0dda2b 100644 --- a/src/coreComponents/schema/docs/ViscoExtendedDruckerPrager.rst +++ b/src/coreComponents/schema/docs/ViscoExtendedDruckerPrager.rst @@ -1,21 +1,21 @@ -================================== ========= ======== ==================================================================== -Name Type Default Description -================================== ========= ======== ==================================================================== -defaultBulkModulus real64 -1 Default Bulk Modulus Parameter -defaultCohesion real64 0 Initial cohesion -defaultDensity real64 required Default Material Density -defaultDilationRatio real64 1 Dilation ratio [0,1] (ratio = tan dilationAngle / tan frictionAngle) -defaultHardening real64 0 Hardening parameter (hardening rate is faster for smaller values) -defaultInitialFrictionAngle real64 30 Initial friction angle (degrees) -defaultPoissonRatio real64 -1 Default Poisson's Ratio -defaultResidualFrictionAngle real64 30 Residual friction angle (degrees) -defaultShearModulus real64 -1 Default Shear Modulus Parameter -defaultThermalExpansionCoefficient real64 0 Default Linear Thermal Expansion Coefficient of the Solid Rock Frame -defaultYoungModulus real64 -1 Default Young's Modulus -name groupName required A name is required for any non-unique nodes -relaxationTime real64 required Relaxation time -================================== ========= ======== ==================================================================== +============================ ========= ======== ==================================================================== +Name Type Default Description +============================ ========= ======== ==================================================================== +defaultBulkModulus real64 -1 Default Bulk Modulus Parameter +defaultCohesion real64 0 Initial cohesion +defaultDensity real64 required Default Material Density +defaultDilationRatio real64 1 Dilation ratio [0,1] (ratio = tan dilationAngle / tan frictionAngle) +defaultDrainedLinearTEC real64 0 Default Linear Thermal Expansion Coefficient of the Solid Rock Frame +defaultHardening real64 0 Hardening parameter (hardening rate is faster for smaller values) +defaultInitialFrictionAngle real64 30 Initial friction angle (degrees) +defaultPoissonRatio real64 -1 Default Poisson's Ratio +defaultResidualFrictionAngle real64 30 Residual friction angle (degrees) +defaultShearModulus real64 -1 Default Shear Modulus Parameter +defaultYoungModulus real64 -1 Default Young's Modulus +name groupName required A name is required for any non-unique nodes +relaxationTime real64 required Relaxation time +============================ ========= ======== ==================================================================== diff --git a/src/coreComponents/schema/docs/ViscoModifiedCamClay.rst b/src/coreComponents/schema/docs/ViscoModifiedCamClay.rst index b0600507f3b..2649b896438 100644 --- a/src/coreComponents/schema/docs/ViscoModifiedCamClay.rst +++ b/src/coreComponents/schema/docs/ViscoModifiedCamClay.rst @@ -1,19 +1,19 @@ -================================== ========= ======== ==================================================================== -Name Type Default Description -================================== ========= ======== ==================================================================== -defaultCslSlope real64 1 Slope of the critical state line -defaultDensity real64 required Default Material Density -defaultPreConsolidationPressure real64 -1.5 Initial preconsolidation pressure -defaultRecompressionIndex real64 0.002 Recompresion Index -defaultRefPressure real64 -1 Reference Pressure -defaultRefStrainVol real64 0 Reference Volumetric Strain -defaultShearModulus real64 -1 Elastic Shear Modulus Parameter -defaultThermalExpansionCoefficient real64 0 Default Linear Thermal Expansion Coefficient of the Solid Rock Frame -defaultVirginCompressionIndex real64 0.005 Virgin compression index -name groupName required A name is required for any non-unique nodes -relaxationTime real64 required Relaxation time -================================== ========= ======== ==================================================================== +=============================== ========= ======== ==================================================================== +Name Type Default Description +=============================== ========= ======== ==================================================================== +defaultCslSlope real64 1 Slope of the critical state line +defaultDensity real64 required Default Material Density +defaultDrainedLinearTEC real64 0 Default Linear Thermal Expansion Coefficient of the Solid Rock Frame +defaultPreConsolidationPressure real64 -1.5 Initial preconsolidation pressure +defaultRecompressionIndex real64 0.002 Recompresion Index +defaultRefPressure real64 -1 Reference Pressure +defaultRefStrainVol real64 0 Reference Volumetric Strain +defaultShearModulus real64 -1 Elastic Shear Modulus Parameter +defaultVirginCompressionIndex real64 0.005 Virgin compression index +name groupName required A name is required for any non-unique nodes +relaxationTime real64 required Relaxation time +=============================== ========= ======== ==================================================================== diff --git a/src/coreComponents/schema/schema.xsd b/src/coreComponents/schema/schema.xsd index cc6252a0d79..78b4b40157d 100644 --- a/src/coreComponents/schema/schema.xsd +++ b/src/coreComponents/schema/schema.xsd @@ -3608,10 +3608,10 @@ Local - Add stabilization only to interiors of macro elements.--> + + - - @@ -3806,12 +3806,12 @@ The expected format is "{ waterMax, oilMax }", in that order--> + + - - @@ -4043,12 +4043,12 @@ The expected format is "{ waterMax, oilMax }", in that order--> + + - - @@ -4075,12 +4075,12 @@ The expected format is "{ waterMax, oilMax }", in that order--> + + - - @@ -4107,12 +4107,12 @@ The expected format is "{ waterMax, oilMax }", in that order--> + + - - @@ -4167,6 +4167,8 @@ For instance, if "oil" is before "gas" in "phaseNames", the table order should b + + @@ -4177,8 +4179,6 @@ For instance, if "oil" is before "gas" in "phaseNames", the table order should b - - @@ -4195,6 +4195,8 @@ For instance, if "oil" is before "gas" in "phaseNames", the table order should b + + @@ -4203,8 +4205,6 @@ For instance, if "oil" is before "gas" in "phaseNames", the table order should b - - @@ -4215,12 +4215,12 @@ For instance, if "oil" is before "gas" in "phaseNames", the table order should b + + - - @@ -4229,6 +4229,8 @@ For instance, if "oil" is before "gas" in "phaseNames", the table order should b + + @@ -4237,8 +4239,6 @@ For instance, if "oil" is before "gas" in "phaseNames", the table order should b - - @@ -4263,6 +4263,8 @@ For instance, if "oil" is before "gas" in "phaseNames", the table order should b + + @@ -4281,8 +4283,6 @@ For instance, if "oil" is before "gas" in "phaseNames", the table order should b - - @@ -4299,14 +4299,14 @@ For instance, if "oil" is before "gas" in "phaseNames", the table order should b + + - - @@ -4331,6 +4331,8 @@ For instance, if "oil" is before "gas" in "phaseNames", the table order should b + + @@ -4341,8 +4343,6 @@ For instance, if "oil" is before "gas" in "phaseNames", the table order should b - - @@ -4421,6 +4421,8 @@ If you want to do a three-phase simulation, please use instead wettingIntermedia + + @@ -4431,8 +4433,6 @@ If you want to do a three-phase simulation, please use instead wettingIntermedia - - @@ -4505,12 +4505,12 @@ If you want to do a three-phase simulation, please use instead wettingIntermedia + + - - @@ -4915,6 +4915,8 @@ The expected format is "{ waterMax, oilMax }", in that order--> + + @@ -4923,8 +4925,6 @@ The expected format is "{ waterMax, oilMax }", in that order--> - - @@ -4941,6 +4941,8 @@ The expected format is "{ waterMax, oilMax }", in that order--> + + @@ -4951,8 +4953,6 @@ The expected format is "{ waterMax, oilMax }", in that order--> - - @@ -4965,6 +4965,8 @@ The expected format is "{ waterMax, oilMax }", in that order--> + + @@ -4975,8 +4977,6 @@ The expected format is "{ waterMax, oilMax }", in that order--> - - diff --git a/src/docs/sphinx/advancedExamples/validationStudies/wellboreProblems/thermoPoroElasticWellbore/analyticalResults.py b/src/docs/sphinx/advancedExamples/validationStudies/wellboreProblems/thermoPoroElasticWellbore/analyticalResults.py index bab0ff411e9..c4ae5d7c182 100644 --- a/src/docs/sphinx/advancedExamples/validationStudies/wellboreProblems/thermoPoroElasticWellbore/analyticalResults.py +++ b/src/docs/sphinx/advancedExamples/validationStudies/wellboreProblems/thermoPoroElasticWellbore/analyticalResults.py @@ -32,7 +32,7 @@ def getDataFromXML(xmlFilePathPrefix): drainedBulkModulusRock = float( tree.find('Constitutive/ElasticIsotropic').get('defaultBulkModulus') ) defaultShearModulus = float( tree.find('Constitutive/ElasticIsotropic').get('defaultShearModulus') ) - defaultThermalExpansionCoefficient = float( tree.find('Constitutive/ElasticIsotropic').get('defaultThermalExpansionCoefficient') ) + defaultDrainedLinearTEC = float( tree.find('Constitutive/ElasticIsotropic').get('defaultDrainedLinearTEC') ) defaultReferencePorosity = float( tree.find('Constitutive/BiotPorosity').get('defaultReferencePorosity') ) grainBulkModulus = float( tree.find('Constitutive/BiotPorosity').get('grainBulkModulus') ) fluidCompressibility = float( tree.find('Constitutive/ThermalCompressibleSinglePhaseFluid').get('compressibility') ) @@ -42,7 +42,7 @@ def getDataFromXML(xmlFilePathPrefix): volumetricHeatCapacity = float( tree.find('Constitutive/SolidInternalEnergy').get('volumetricHeatCapacity') ) permeability = float( extractDataFromXMLList( tree.find('Constitutive/ConstantPermeability').get('permeabilityComponents') )[0] ) - return [ri, Ti, drainedBulkModulusRock, defaultShearModulus, defaultThermalExpansionCoefficient, defaultReferencePorosity, grainBulkModulus, fluidCompressibility, fluidViscosity, fluidThermalExpansionCoefficient, permeability, thermalConductivity, volumetricHeatCapacity] + return [ri, Ti, drainedBulkModulusRock, defaultShearModulus, defaultDrainedLinearTEC, defaultReferencePorosity, grainBulkModulus, fluidCompressibility, fluidViscosity, fluidThermalExpansionCoefficient, permeability, thermalConductivity, volumetricHeatCapacity] def analyticalResults(t): xmlFilePathPrefix = "../../../../../../../inputFiles/wellbore/ThermoPoroElasticWellbore" @@ -85,7 +85,7 @@ def analyticalResults(t): Ku = K + M*alpha*alpha S = (3.0*Ku + 4.0*G) /M /(3.0*K+4.0*G) - beta_s = beta_d + beta_s = beta_d # TODO: update for the case porosityTEC != drainedLinearTEC beta_v = porosity*(beta_f - beta_s) beta_e = beta_d*alpha + beta_v From e67133bc0d36cc83d4ae31941c57944e74007688 Mon Sep 17 00:00:00 2001 From: Pavel Tomin Date: Wed, 13 Dec 2023 15:42:40 -0600 Subject: [PATCH 06/40] Fix issue #2882 by using output dir in wells rates output (#2884) --- .../fluidFlow/CompositionalMultiphaseStatistics.cpp | 3 ++- .../physicsSolvers/fluidFlow/wells/WellSolverBase.cpp | 3 ++- .../physicsSolvers/solidMechanics/SolidMechanicsStatistics.cpp | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseStatistics.cpp b/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseStatistics.cpp index 5de05a638c4..a53e19c9626 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseStatistics.cpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseStatistics.cpp @@ -31,6 +31,7 @@ #include "physicsSolvers/fluidFlow/FlowSolverBaseFields.hpp" #include "physicsSolvers/fluidFlow/IsothermalCompositionalMultiphaseBaseKernels.hpp" #include "physicsSolvers/fluidFlow/IsothermalCompositionalMultiphaseFVMKernels.hpp" +#include "fileIO/Outputs/OutputBase.hpp" namespace geos @@ -44,7 +45,7 @@ CompositionalMultiphaseStatistics::CompositionalMultiphaseStatistics( const stri Base( name, parent ), m_computeCFLNumbers( 0 ), m_computeRegionStatistics( 1 ), - m_outputDir( name ) + m_outputDir( joinPath( OutputBase::getOutputDirectory(), name ) ) { registerWrapper( viewKeyStruct::computeCFLNumbersString(), &m_computeCFLNumbers ). setApplyDefaultValue( 0 ). diff --git a/src/coreComponents/physicsSolvers/fluidFlow/wells/WellSolverBase.cpp b/src/coreComponents/physicsSolvers/fluidFlow/wells/WellSolverBase.cpp index babfb17a4fd..0699870a3df 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/wells/WellSolverBase.cpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/wells/WellSolverBase.cpp @@ -26,6 +26,7 @@ #include "physicsSolvers/fluidFlow/FlowSolverBaseFields.hpp" #include "physicsSolvers/fluidFlow/wells/WellControls.hpp" #include "physicsSolvers/fluidFlow/wells/WellSolverBaseFields.hpp" +#include "fileIO/Outputs/OutputBase.hpp" namespace geos { @@ -38,7 +39,7 @@ WellSolverBase::WellSolverBase( string const & name, : SolverBase( name, parent ), m_numDofPerWellElement( 0 ), m_numDofPerResElement( 0 ), - m_ratesOutputDir( name + "_rates" ) + m_ratesOutputDir( joinPath( OutputBase::getOutputDirectory(), name + "_rates" ) ) { this->getWrapper< string >( viewKeyStruct::discretizationString() ). setInputFlag( InputFlags::FALSE ); diff --git a/src/coreComponents/physicsSolvers/solidMechanics/SolidMechanicsStatistics.cpp b/src/coreComponents/physicsSolvers/solidMechanics/SolidMechanicsStatistics.cpp index 9a13598b751..d4c7bfa3152 100644 --- a/src/coreComponents/physicsSolvers/solidMechanics/SolidMechanicsStatistics.cpp +++ b/src/coreComponents/physicsSolvers/solidMechanics/SolidMechanicsStatistics.cpp @@ -22,6 +22,7 @@ #include "mainInterface/ProblemManager.hpp" #include "physicsSolvers/PhysicsSolverManager.hpp" #include "physicsSolvers/solidMechanics/SolidMechanicsLagrangianFEM.hpp" +#include "fileIO/Outputs/OutputBase.hpp" namespace geos { @@ -33,7 +34,7 @@ using namespace fields; SolidMechanicsStatistics::SolidMechanicsStatistics( const string & name, Group * const parent ): Base( name, parent ), - m_outputDir( name ) + m_outputDir( joinPath( OutputBase::getOutputDirectory(), name ) ) {} void SolidMechanicsStatistics::postProcessInput() From bbbab34f0ee745be643fb7d4d9fd200586d3669e Mon Sep 17 00:00:00 2001 From: tbeltzun <129868353+tbeltzun@users.noreply.github.com> Date: Thu, 14 Dec 2023 04:11:50 +0100 Subject: [PATCH 07/40] fix elastic seismos bug (#2849) The rewrite of #2729 introduced a bug in the elastic solver, where the derived class had members shadowing the parent class variables. The cleanup was done in #2557 but was somehow missed when cherry-picking commits to create #2729. This PR: - fixes elastic solver bug (member variables shadowing), reported by @shenchengyi, reproducer using `inputFiles/wavePropagation/elas3D_DAS_smoke.xml`: empty seismos time and sampled value; - make `initTrace` consistent with `writeSeismoTrace` i.e. don't touch the file if `outputSeismoTrace` is unset; - remove redundant / unused `ElasticWaveEquationSEM::getNumNodesPerElem`; - code style consistency across `wavePropagation` solvers files (e.g. use `e` for element); - use `pow(..., n)` patterns for clarity. --- integratedTests | 2 +- .../AcousticFirstOrderWaveEquationSEM.cpp | 4 +- ...cousticFirstOrderWaveEquationSEMKernel.hpp | 15 +++-- .../AcousticVTIWaveEquationSEM.cpp | 4 +- .../AcousticVTIWaveEquationSEMKernel.hpp | 9 ++- .../AcousticWaveEquationSEM.cpp | 10 ++- .../AcousticWaveEquationSEMKernel.hpp | 16 ++--- .../ElasticFirstOrderWaveEquationSEM.cpp | 2 - ...ElasticFirstOrderWaveEquationSEMKernel.hpp | 10 +-- .../ElasticWaveEquationSEM.cpp | 61 +++---------------- .../ElasticWaveEquationSEM.hpp | 17 ------ .../ElasticWaveEquationSEMKernel.hpp | 27 ++++---- .../wavePropagation/WaveSolverUtils.hpp | 8 ++- 13 files changed, 57 insertions(+), 128 deletions(-) diff --git a/integratedTests b/integratedTests index b08da69e4e0..5ffebf05d09 160000 --- a/integratedTests +++ b/integratedTests @@ -1 +1 @@ -Subproject commit b08da69e4e0f57b744b4f5496a829322b8fc3820 +Subproject commit 5ffebf05d093b7492328e782c68c1612e6ce14af diff --git a/src/coreComponents/physicsSolvers/wavePropagation/AcousticFirstOrderWaveEquationSEM.cpp b/src/coreComponents/physicsSolvers/wavePropagation/AcousticFirstOrderWaveEquationSEM.cpp index c7ec3d9d330..fc8fb56b5a2 100644 --- a/src/coreComponents/physicsSolvers/wavePropagation/AcousticFirstOrderWaveEquationSEM.cpp +++ b/src/coreComponents/physicsSolvers/wavePropagation/AcousticFirstOrderWaveEquationSEM.cpp @@ -210,7 +210,6 @@ void AcousticFirstOrderWaveEquationSEM::precomputeSourceAndReceiverTerm( MeshLev { using FE_TYPE = TYPEOFREF( finiteElement ); - constexpr localIndex numNodesPerElem = FE_TYPE::numNodes; localIndex const numFacesPerElem = elementSubRegion.numFacesPerElement(); acousticFirstOrderWaveEquationSEMKernels:: @@ -218,7 +217,6 @@ void AcousticFirstOrderWaveEquationSEM::precomputeSourceAndReceiverTerm( MeshLev launch< EXEC_POLICY, FE_TYPE > ( elementSubRegion.size(), regionIndex, - numNodesPerElem, numFacesPerElem, X, elemGhostRank, @@ -343,7 +341,7 @@ void AcousticFirstOrderWaveEquationSEM::initializePostInitialConditionsPreSubGro } ); } ); - WaveSolverUtils::initTrace( "seismoTraceReceiver", getName(), m_receiverConstants.size( 0 ), m_receiverIsLocal ); + WaveSolverUtils::initTrace( "seismoTraceReceiver", getName(), m_outputSeismoTrace, m_receiverConstants.size( 0 ), m_receiverIsLocal ); } diff --git a/src/coreComponents/physicsSolvers/wavePropagation/AcousticFirstOrderWaveEquationSEMKernel.hpp b/src/coreComponents/physicsSolvers/wavePropagation/AcousticFirstOrderWaveEquationSEMKernel.hpp index ac793524a2a..9347166af08 100644 --- a/src/coreComponents/physicsSolvers/wavePropagation/AcousticFirstOrderWaveEquationSEMKernel.hpp +++ b/src/coreComponents/physicsSolvers/wavePropagation/AcousticFirstOrderWaveEquationSEMKernel.hpp @@ -39,7 +39,6 @@ struct PrecomputeSourceAndReceiverKernel * @tparam EXEC_POLICY execution policy * @tparam FE_TYPE finite element type * @param[in] size the number of cells in the subRegion - * @param[in] numNodesPerElem number of nodes per element * @param[in] numFacesPerElem number of faces per element * @param[in] nodeCoords coordinates of the nodes * @param[in] elemGhostRank rank of the ghost element @@ -68,7 +67,6 @@ struct PrecomputeSourceAndReceiverKernel static void launch( localIndex const size, localIndex const regionIndex, - localIndex const numNodesPerElem, localIndex const numFacesPerElem, arrayView2d< WaveSolverBase::wsCoordType const, nodes::REFERENCE_POSITION_USD > const nodeCoords, arrayView1d< integer const > const elemGhostRank, @@ -95,6 +93,7 @@ struct PrecomputeSourceAndReceiverKernel real32 const timeSourceDelay, localIndex const rickerOrder ) { + constexpr localIndex numNodesPerElem = FE_TYPE::numNodes; forAll< EXEC_POLICY >( size, [=] GEOS_HOST_DEVICE ( localIndex const k ) { @@ -133,7 +132,7 @@ struct PrecomputeSourceAndReceiverKernel sourceIsAccessible[isrc] = 1; sourceElem[isrc] = k; sourceRegion[isrc] = regionIndex; - real64 Ntest[FE_TYPE::numNodes]; + real64 Ntest[numNodesPerElem]; FE_TYPE::calcN( coordsOnRefElem, Ntest ); for( localIndex a = 0; a < numNodesPerElem; ++a ) @@ -181,7 +180,7 @@ struct PrecomputeSourceAndReceiverKernel rcvElem[ircv] = k; receiverRegion[ircv] = regionIndex; - real64 Ntest[FE_TYPE::numNodes]; + real64 Ntest[numNodesPerElem]; FE_TYPE::calcN( coordsOnRefElem, Ntest ); for( localIndex a = 0; a < numNodesPerElem; ++a ) @@ -227,26 +226,26 @@ struct MassMatrixKernel arrayView1d< real32 > const mass ) { - forAll< EXEC_POLICY >( size, [=] GEOS_HOST_DEVICE ( localIndex const k ) + forAll< EXEC_POLICY >( size, [=] GEOS_HOST_DEVICE ( localIndex const e ) { constexpr localIndex numNodesPerElem = FE_TYPE::numNodes; constexpr localIndex numQuadraturePointsPerElem = FE_TYPE::numQuadraturePoints; - real32 const invC2 = 1.0 / ( density[k] * velocity[k] * velocity[k] ); + real32 const invC2 = 1.0 / ( density[e] * pow( velocity[e], 2 ) ); real64 xLocal[ numNodesPerElem ][ 3 ]; for( localIndex a = 0; a < numNodesPerElem; ++a ) { for( localIndex i = 0; i < 3; ++i ) { - xLocal[a][i] = nodeCoords( elemsToNodes( k, a ), i ); + xLocal[a][i] = nodeCoords( elemsToNodes( e, a ), i ); } } for( localIndex q = 0; q < numQuadraturePointsPerElem; ++q ) { real32 const localIncrement = invC2 * m_finiteElement.computeMassTerm( q, xLocal ); - RAJA::atomicAdd< ATOMIC_POLICY >( &mass[elemsToNodes[k][q]], localIncrement ); + RAJA::atomicAdd< ATOMIC_POLICY >( &mass[elemsToNodes( e, q )], localIncrement ); } } ); // end loop over element } diff --git a/src/coreComponents/physicsSolvers/wavePropagation/AcousticVTIWaveEquationSEM.cpp b/src/coreComponents/physicsSolvers/wavePropagation/AcousticVTIWaveEquationSEM.cpp index 8922a92e8cd..28851545659 100644 --- a/src/coreComponents/physicsSolvers/wavePropagation/AcousticVTIWaveEquationSEM.cpp +++ b/src/coreComponents/physicsSolvers/wavePropagation/AcousticVTIWaveEquationSEM.cpp @@ -168,14 +168,12 @@ void AcousticVTIWaveEquationSEM::precomputeSourceAndReceiverTerm( MeshLevel & me { using FE_TYPE = TYPEOFREF( finiteElement ); - constexpr localIndex numNodesPerElem = FE_TYPE::numNodes; localIndex const numFacesPerElem = elementSubRegion.numFacesPerElement(); acousticVTIWaveEquationSEMKernels:: PrecomputeSourceAndReceiverKernel:: launch< EXEC_POLICY, FE_TYPE > ( elementSubRegion.size(), - numNodesPerElem, numFacesPerElem, X32, elemGhostRank, @@ -314,7 +312,7 @@ void AcousticVTIWaveEquationSEM::initializePostInitialConditionsPreSubGroups() } ); } ); - WaveSolverUtils::initTrace( "seismoTraceReceiver", getName(), m_receiverConstants.size( 0 ), m_receiverIsLocal ); + WaveSolverUtils::initTrace( "seismoTraceReceiver", getName(), m_outputSeismoTrace, m_receiverConstants.size( 0 ), m_receiverIsLocal ); } void AcousticVTIWaveEquationSEM::precomputeSurfaceFieldIndicator( DomainPartition & domain ) diff --git a/src/coreComponents/physicsSolvers/wavePropagation/AcousticVTIWaveEquationSEMKernel.hpp b/src/coreComponents/physicsSolvers/wavePropagation/AcousticVTIWaveEquationSEMKernel.hpp index bd062c38fa2..52434125c57 100644 --- a/src/coreComponents/physicsSolvers/wavePropagation/AcousticVTIWaveEquationSEMKernel.hpp +++ b/src/coreComponents/physicsSolvers/wavePropagation/AcousticVTIWaveEquationSEMKernel.hpp @@ -39,7 +39,6 @@ struct PrecomputeSourceAndReceiverKernel * @tparam EXEC_POLICY execution policy * @tparam FE_TYPE finite element type * @param[in] size the number of cells in the subRegion - * @param[in] numNodesPerElem number of nodes per element * @param[in] numFacesPerElem number of faces per element * @param[in] nodeCoords coordinates of the nodes * @param[in] elemGhostRank the ghost ranks @@ -64,7 +63,6 @@ struct PrecomputeSourceAndReceiverKernel template< typename EXEC_POLICY, typename FE_TYPE > static void launch( localIndex const size, - localIndex const numNodesPerElem, localIndex const numFacesPerElem, arrayView2d< WaveSolverBase::wsCoordType const, nodes::REFERENCE_POSITION_USD > const nodeCoords, arrayView1d< integer const > const elemGhostRank, @@ -87,6 +85,7 @@ struct PrecomputeSourceAndReceiverKernel real32 const timeSourceDelay, localIndex const rickerOrder ) { + constexpr localIndex numNodesPerElem = FE_TYPE::numNodes; forAll< EXEC_POLICY >( size, [=] GEOS_HOST_DEVICE ( localIndex const k ) { @@ -123,7 +122,7 @@ struct PrecomputeSourceAndReceiverKernel coordsOnRefElem ); sourceIsAccessible[isrc] = 1; - real64 Ntest[FE_TYPE::numNodes]; + real64 Ntest[numNodesPerElem]; FE_TYPE::calcN( coordsOnRefElem, Ntest ); for( localIndex a = 0; a < numNodesPerElem; ++a ) @@ -170,7 +169,7 @@ struct PrecomputeSourceAndReceiverKernel receiverIsLocal[ircv] = 1; - real64 Ntest[FE_TYPE::numNodes]; + real64 Ntest[numNodesPerElem]; FE_TYPE::calcN( coordsOnRefElem, Ntest ); for( localIndex a = 0; a < numNodesPerElem; ++a ) @@ -220,7 +219,7 @@ struct MassMatrixKernel constexpr localIndex numNodesPerElem = FE_TYPE::numNodes; constexpr localIndex numQuadraturePointsPerElem = FE_TYPE::numQuadraturePoints; - real32 const invC2 = 1.0 / ( velocity[e] * velocity[e] ); + real32 const invC2 = 1.0 / pow( velocity[e], 2 ); real64 xLocal[ numNodesPerElem ][ 3 ]; for( localIndex a = 0; a < numNodesPerElem; ++a ) { diff --git a/src/coreComponents/physicsSolvers/wavePropagation/AcousticWaveEquationSEM.cpp b/src/coreComponents/physicsSolvers/wavePropagation/AcousticWaveEquationSEM.cpp index 6cbadc9ec14..eb4ed4d8329 100644 --- a/src/coreComponents/physicsSolvers/wavePropagation/AcousticWaveEquationSEM.cpp +++ b/src/coreComponents/physicsSolvers/wavePropagation/AcousticWaveEquationSEM.cpp @@ -172,7 +172,6 @@ void AcousticWaveEquationSEM::precomputeSourceAndReceiverTerm( MeshLevel & mesh, { using FE_TYPE = TYPEOFREF( finiteElement ); - constexpr localIndex numNodesPerElem = FE_TYPE::numNodes; localIndex const numFacesPerElem = elementSubRegion.numFacesPerElement(); { @@ -181,7 +180,6 @@ void AcousticWaveEquationSEM::precomputeSourceAndReceiverTerm( MeshLevel & mesh, PrecomputeSourceAndReceiverKernel:: launch< EXEC_POLICY, FE_TYPE > ( elementSubRegion.size(), - numNodesPerElem, numFacesPerElem, X32, elemGhostRank, @@ -323,7 +321,7 @@ void AcousticWaveEquationSEM::initializePostInitialConditionsPreSubGroups() } ); } ); - WaveSolverUtils::initTrace( "seismoTraceReceiver", getName(), m_receiverConstants.size( 0 ), m_receiverIsLocal ); + WaveSolverUtils::initTrace( "seismoTraceReceiver", getName(), m_outputSeismoTrace, m_receiverConstants.size( 0 ), m_receiverIsLocal ); } @@ -853,7 +851,7 @@ real64 AcousticWaveEquationSEM::explicitStepBackward( real64 const & time_n, EventManager const & event = getGroupByPath< EventManager >( "/Problem/Events" ); real64 const & maxTime = event.getReference< real64 >( EventManager::viewKeyStruct::maxTimeString() ); - int const maxCycle = int(round( maxTime/dt )); + int const maxCycle = int(round( maxTime / dt )); if( computeGradient && cycleNumber < maxCycle ) { @@ -962,8 +960,8 @@ real64 AcousticWaveEquationSEM::explicitStepInternal( real64 const & time_n, EventManager const & event = getGroupByPath< EventManager >( "/Problem/Events" ); real64 const & minTime = event.getReference< real64 >( EventManager::viewKeyStruct::minTimeString() ); - integer const cycleForSource = int(round( -minTime/dt + cycleNumber )); - //std::cout<<"cycle GEOSX = "< static void launch( localIndex const size, - localIndex const numNodesPerElem, localIndex const numFacesPerElem, arrayView2d< WaveSolverBase::wsCoordType const, nodes::REFERENCE_POSITION_USD > const nodeCoords, arrayView1d< integer const > const elemGhostRank, @@ -81,9 +79,11 @@ struct PrecomputeSourceAndReceiverKernel real32 const timeSourceDelay, localIndex const rickerOrder ) { + constexpr localIndex numNodesPerElem = FE_TYPE::numNodes; forAll< EXEC_POLICY >( size, [=] GEOS_HOST_DEVICE ( localIndex const k ) { + real64 const center[3] = { elemCenter[k][0], elemCenter[k][1], elemCenter[k][2] }; @@ -117,7 +117,7 @@ struct PrecomputeSourceAndReceiverKernel coordsOnRefElem ); sourceIsAccessible[isrc] = 1; - real64 Ntest[FE_TYPE::numNodes]; + real64 Ntest[numNodesPerElem]; FE_TYPE::calcN( coordsOnRefElem, Ntest ); for( localIndex a = 0; a < numNodesPerElem; ++a ) @@ -164,7 +164,7 @@ struct PrecomputeSourceAndReceiverKernel receiverIsLocal[ircv] = 1; - real64 Ntest[FE_TYPE::numNodes]; + real64 Ntest[numNodesPerElem]; FE_TYPE::calcN( coordsOnRefElem, Ntest ); for( localIndex a = 0; a < numNodesPerElem; ++a ) @@ -216,7 +216,7 @@ struct MassMatrixKernel constexpr localIndex numNodesPerElem = FE_TYPE::numNodes; constexpr localIndex numQuadraturePointsPerElem = FE_TYPE::numQuadraturePoints; - real32 const invC2 = 1.0 / ( density[e] * velocity[e] * velocity[e] ); + real32 const invC2 = 1.0 / ( density[e] * pow( velocity[e], 2 ) ); real64 xLocal[ numNodesPerElem ][ 3 ]; for( localIndex a = 0; a < numNodesPerElem; ++a ) { @@ -803,9 +803,9 @@ class ExplicitAcousticSEM : public finiteElement::KernelBase< SUBREGION_TYPE, { m_finiteElementSpace.template computeStiffnessTerm( q, stack.xLocal, [&] ( int i, int j, real64 val ) { - real32 invDensity = 1./m_density[k]; - real32 const localIncrement = invDensity*val*m_p_n[m_elemsToNodes[k][j]]; - RAJA::atomicAdd< parallelDeviceAtomic >( &m_stiffnessVector[m_elemsToNodes[k][i]], localIncrement ); + real32 invDensity = 1.0 / m_density[k]; + real32 const localIncrement = invDensity*val*m_p_n[m_elemsToNodes( k, j )]; + RAJA::atomicAdd< parallelDeviceAtomic >( &m_stiffnessVector[m_elemsToNodes( k, i )], localIncrement ); } ); } diff --git a/src/coreComponents/physicsSolvers/wavePropagation/ElasticFirstOrderWaveEquationSEM.cpp b/src/coreComponents/physicsSolvers/wavePropagation/ElasticFirstOrderWaveEquationSEM.cpp index 1e4364f840c..29c61faa2e5 100644 --- a/src/coreComponents/physicsSolvers/wavePropagation/ElasticFirstOrderWaveEquationSEM.cpp +++ b/src/coreComponents/physicsSolvers/wavePropagation/ElasticFirstOrderWaveEquationSEM.cpp @@ -260,7 +260,6 @@ void ElasticFirstOrderWaveEquationSEM::precomputeSourceAndReceiverTerm( MeshLeve { using FE_TYPE = TYPEOFREF( finiteElement ); - constexpr localIndex numNodesPerElem = FE_TYPE::numNodes; localIndex const numFacesPerElem = elementSubRegion.numFacesPerElement(); elasticFirstOrderWaveEquationSEMKernels:: @@ -268,7 +267,6 @@ void ElasticFirstOrderWaveEquationSEM::precomputeSourceAndReceiverTerm( MeshLeve launch< EXEC_POLICY, FE_TYPE > ( elementSubRegion.size(), regionIndex, - numNodesPerElem, numFacesPerElem, X, elemGhostRank, diff --git a/src/coreComponents/physicsSolvers/wavePropagation/ElasticFirstOrderWaveEquationSEMKernel.hpp b/src/coreComponents/physicsSolvers/wavePropagation/ElasticFirstOrderWaveEquationSEMKernel.hpp index c4414f17c0e..262fe06824e 100644 --- a/src/coreComponents/physicsSolvers/wavePropagation/ElasticFirstOrderWaveEquationSEMKernel.hpp +++ b/src/coreComponents/physicsSolvers/wavePropagation/ElasticFirstOrderWaveEquationSEMKernel.hpp @@ -62,7 +62,6 @@ struct PrecomputeSourceAndReceiverKernel static void launch( localIndex const size, localIndex const regionIndex, - localIndex const numNodesPerElem, localIndex const numFacesPerElem, arrayView2d< WaveSolverBase::wsCoordType const, nodes::REFERENCE_POSITION_USD > const nodeCoords, arrayView1d< integer const > const elemGhostRank, @@ -89,6 +88,7 @@ struct PrecomputeSourceAndReceiverKernel real32 const timeSourceDelay, localIndex const rickerOrder ) { + constexpr localIndex numNodesPerElem = FE_TYPE::numNodes; forAll< EXEC_POLICY >( size, [=] GEOS_HOST_DEVICE ( localIndex const k ) { @@ -126,7 +126,7 @@ struct PrecomputeSourceAndReceiverKernel sourceIsAccessible[isrc] = 1; sourceElem[isrc] = k; sourceRegion[isrc] = regionIndex; - real64 Ntest[FE_TYPE::numNodes]; + real64 Ntest[numNodesPerElem]; FE_TYPE::calcN( coordsOnRefElem, Ntest ); for( localIndex a = 0; a < numNodesPerElem; ++a ) @@ -174,7 +174,7 @@ struct PrecomputeSourceAndReceiverKernel rcvElem[ircv] = k; receiverRegion[ircv] = regionIndex; - real64 Ntest[FE_TYPE::numNodes]; + real64 Ntest[numNodesPerElem]; FE_TYPE::calcN( coordsOnRefElem, Ntest ); for( localIndex a = 0; a < numNodesPerElem; ++a ) @@ -377,8 +377,8 @@ struct StressComputation } } - mu[k] = density[k] * velocityVs[k] * velocityVs[k]; - lambda[k] = density[k] * velocityVp[k] * velocityVp[k] - 2.0*mu[k]; + mu[k] = density[k] * pow( velocityVs[k], 2 ); + lambda[k] = density[k] * pow( velocityVp[k], 2 ) - 2.0*mu[k]; real32 uelemxx[numNodesPerElem] = {0.0}; real32 uelemyy[numNodesPerElem] = {0.0}; diff --git a/src/coreComponents/physicsSolvers/wavePropagation/ElasticWaveEquationSEM.cpp b/src/coreComponents/physicsSolvers/wavePropagation/ElasticWaveEquationSEM.cpp index 2de2021b306..b52daa13cb1 100644 --- a/src/coreComponents/physicsSolvers/wavePropagation/ElasticWaveEquationSEM.cpp +++ b/src/coreComponents/physicsSolvers/wavePropagation/ElasticWaveEquationSEM.cpp @@ -88,49 +88,6 @@ ElasticWaveEquationSEM::~ElasticWaveEquationSEM() // TODO Auto-generated destructor stub } -localIndex ElasticWaveEquationSEM::getNumNodesPerElem() -{ - DomainPartition & domain = getGroupByPath< DomainPartition >( "/Problem/domain" ); - - NumericalMethodsManager const & numericalMethodManager = domain.getNumericalMethodManager(); - - FiniteElementDiscretizationManager const & - feDiscretizationManager = numericalMethodManager.getFiniteElementDiscretizationManager(); - - FiniteElementDiscretization const * const - feDiscretization = feDiscretizationManager.getGroupPointer< FiniteElementDiscretization >( m_discretizationName ); - GEOS_THROW_IF( feDiscretization == nullptr, - getDataContext() << ": FE discretization not found: " << m_discretizationName, - InputError ); - - localIndex numNodesPerElem = 0; - forDiscretizationOnMeshTargets( domain.getMeshBodies(), - [&]( string const &, - MeshLevel const & mesh, - arrayView1d< string const > const & regionNames ) - { - ElementRegionManager const & elemManager = mesh.getElemManager(); - elemManager.forElementRegions( regionNames, - [&] ( localIndex const, - ElementRegionBase const & elemRegion ) - { - elemRegion.forElementSubRegions( [&]( ElementSubRegionBase const & elementSubRegion ) - { - finiteElement::FiniteElementBase const & - fe = elementSubRegion.getReference< finiteElement::FiniteElementBase >( getDiscretizationName() ); - localIndex const numSupportPoints = fe.getNumSupportPoints(); - if( numSupportPoints > numNodesPerElem ) - { - numNodesPerElem = numSupportPoints; - } - } ); - } ); - - - } ); - return numNodesPerElem; -} - void ElasticWaveEquationSEM::initializePreSubGroups() { @@ -234,7 +191,7 @@ void ElasticWaveEquationSEM::postProcessInput() { m_nsamplesSeismoTrace = 0; } - localIndex const nsamples = int(maxTime/dt) + 1; + localIndex const nsamples = int(maxTime / dt) + 1; localIndex const numSourcesGlobal = m_sourceCoordinates.size( 0 ); m_sourceIsAccessible.resize( numSourcesGlobal ); @@ -353,9 +310,9 @@ void ElasticWaveEquationSEM::precomputeSourceAndReceiverTerm( MeshLevel & mesh, } ); } -void ElasticWaveEquationSEM::computeDAS ( arrayView2d< real32 > const xCompRcv, - arrayView2d< real32 > const yCompRcv, - arrayView2d< real32 > const zCompRcv ) +void ElasticWaveEquationSEM::computeDAS( arrayView2d< real32 > const xCompRcv, + arrayView2d< real32 > const yCompRcv, + arrayView2d< real32 > const zCompRcv ) { arrayView2d< real64 const > const linearDASGeometry = m_linearDASGeometry.toViewConst(); @@ -399,9 +356,9 @@ void ElasticWaveEquationSEM::computeDAS ( arrayView2d< real32 > const xCompRcv, { // store strain data in the z-component of the receiver (copied to x after resize) zCompRcv[iSample][ircv] = - cd * ca * ( xCompRcv[iSample][numReceiversGlobal+ircv] - xCompRcv[iSample][ircv] ) - + cd * sa * ( yCompRcv[iSample][numReceiversGlobal+ircv] - yCompRcv[iSample][ircv] ) - + sd * ( zCompRcv[iSample][numReceiversGlobal+ircv] - zCompRcv[iSample][ircv] ); + cd * ca * ( xCompRcv[iSample][numReceiversGlobal+ircv] - xCompRcv[iSample][ircv] ) + + cd * sa * ( yCompRcv[iSample][numReceiversGlobal+ircv] - yCompRcv[iSample][ircv] ) + + sd * ( zCompRcv[iSample][numReceiversGlobal+ircv] - zCompRcv[iSample][ircv] ); zCompRcv[iSample][ircv] /= linearDASGeometry[ircv][2]; } @@ -544,8 +501,8 @@ void ElasticWaveEquationSEM::initializePostInitialConditionsPreSubGroups() } ); } ); - WaveSolverUtils::initTrace( "seismoTraceReceiver", getName(), m_receiverConstants.size( 0 ), m_receiverIsLocal ); - WaveSolverUtils::initTrace( "dasTraceReceiver", getName(), m_linearDASGeometry.size( 0 ), m_receiverIsLocal ); + WaveSolverUtils::initTrace( "seismoTraceReceiver", getName(), m_outputSeismoTrace, m_receiverConstants.size( 0 ), m_receiverIsLocal ); + WaveSolverUtils::initTrace( "dasTraceReceiver", getName(), m_outputSeismoTrace, m_linearDASGeometry.size( 0 ), m_receiverIsLocal ); } diff --git a/src/coreComponents/physicsSolvers/wavePropagation/ElasticWaveEquationSEM.hpp b/src/coreComponents/physicsSolvers/wavePropagation/ElasticWaveEquationSEM.hpp index 119b40da706..0cb7afd3d6f 100644 --- a/src/coreComponents/physicsSolvers/wavePropagation/ElasticWaveEquationSEM.hpp +++ b/src/coreComponents/physicsSolvers/wavePropagation/ElasticWaveEquationSEM.hpp @@ -171,11 +171,6 @@ class ElasticWaveEquationSEM : public WaveSolverBase */ virtual void applyPML( real64 const time, DomainPartition & domain ) override; - localIndex getNumNodesPerElem(); - - /// Indices of the nodes (in the right order) for each source point - array2d< localIndex > m_sourceNodeIds; - /// Constant part of the source for the nodes listed in m_sourceNodeIds in x-direction array2d< real64 > m_sourceConstantsx; @@ -185,18 +180,6 @@ class ElasticWaveEquationSEM : public WaveSolverBase /// Constant part of the source for the nodes listed in m_sourceNodeIds in x-direction array2d< real64 > m_sourceConstantsz; - /// Flag that indicates whether the source is accessible or not to the MPI rank - array1d< localIndex > m_sourceIsAccessible; - - /// Indices of the element nodes (in the right order) for each receiver point - array2d< localIndex > m_receiverNodeIds; - - /// Basis function evaluated at the receiver for the nodes listed in m_receiverNodeIds - array2d< real64 > m_receiverConstants; - - /// Flag that indicates whether the receiver is local or not to the MPI rank - array1d< localIndex > m_receiverIsLocal; - /// Displacement_np1 at the receiver location for each time step for each receiver (x-component) array2d< real32 > m_displacementXNp1AtReceivers; diff --git a/src/coreComponents/physicsSolvers/wavePropagation/ElasticWaveEquationSEMKernel.hpp b/src/coreComponents/physicsSolvers/wavePropagation/ElasticWaveEquationSEMKernel.hpp index ebdd56542f4..609f97bd245 100644 --- a/src/coreComponents/physicsSolvers/wavePropagation/ElasticWaveEquationSEMKernel.hpp +++ b/src/coreComponents/physicsSolvers/wavePropagation/ElasticWaveEquationSEMKernel.hpp @@ -90,12 +90,11 @@ struct PrecomputeSourceAndReceiverKernel R1Tensor const sourceForce, R2SymTensor const sourceMoment ) { + constexpr localIndex numNodesPerElem = FE_TYPE::numNodes; forAll< EXEC_POLICY >( size, [=] GEOS_HOST_DEVICE ( localIndex const k ) { - constexpr localIndex numNodesPerElem = FE_TYPE::numNodes; - real64 const center[3] = { elemCenter[k][0], elemCenter[k][1], elemCenter[k][2] }; @@ -141,8 +140,8 @@ struct PrecomputeSourceAndReceiverKernel coordsOnRefElem ); sourceIsAccessible[isrc] = 1; - real64 N[FE_TYPE::numNodes]; - real64 gradN[FE_TYPE::numNodes][3]; + real64 N[numNodesPerElem]; + real64 gradN[numNodesPerElem][3]; FE_TYPE::calcN( coordsOnRefElem, N ); FE_TYPE::calcGradN( coordsOnRefElem, xLocal, gradN ); R2SymTensor moment = sourceMoment; @@ -169,7 +168,6 @@ struct PrecomputeSourceAndReceiverKernel } } // end loop over all sources - // Step 2: locate the receivers, and precompute the receiver term /// loop over all the receivers that haven't been found yet @@ -196,11 +194,8 @@ struct PrecomputeSourceAndReceiverKernel elemsToNodes[k], nodeCoords, coordsOnRefElem ); - receiverIsLocal[ircv] = 1; - real64 Ntest[numNodesPerElem]; - FE_TYPE::calcN( coordsOnRefElem, Ntest ); for( localIndex a = 0; a < numNodesPerElem; ++a ) @@ -478,8 +473,8 @@ class ExplicitElasticSEM : public finiteElement::KernelBase< SUBREGION_TYPE, stack.xLocal[ a ][ i ] = m_nodeCoords[ nodeIndex ][ i ]; } } - stack.mu = m_density[k] * m_velocityVs[k] * m_velocityVs[k]; - stack.lambda = m_density[k] *m_velocityVp[k] * m_velocityVp[k] - 2.0*stack.mu; + stack.mu = m_density[k] * pow( m_velocityVs[k], 2 ); + stack.lambda = m_density[k] * pow( m_velocityVp[k], 2 ) - 2.0 * stack.mu; } /** @@ -508,13 +503,13 @@ class ExplicitElasticSEM : public finiteElement::KernelBase< SUBREGION_TYPE, real32 const Ryz_ij = val*(stack.lambda*J[p][1]*J[r][2]+stack.mu*J[p][2]*J[r][1]); real32 const Rzy_ij = val*(stack.mu*J[p][1]*J[r][2]+stack.lambda*J[p][2]*J[r][1]); - real32 const localIncrementx = (Rxx_ij * m_ux_n[m_elemsToNodes[k][j]] + Rxy_ij*m_uy_n[m_elemsToNodes[k][j]] + Rxz_ij*m_uz_n[m_elemsToNodes[k][j]]); - real32 const localIncrementy = (Ryx_ij * m_ux_n[m_elemsToNodes[k][j]] + Ryy_ij*m_uy_n[m_elemsToNodes[k][j]] + Ryz_ij*m_uz_n[m_elemsToNodes[k][j]]); - real32 const localIncrementz = (Rzx_ij * m_ux_n[m_elemsToNodes[k][j]] + Rzy_ij*m_uy_n[m_elemsToNodes[k][j]] + Rzz_ij*m_uz_n[m_elemsToNodes[k][j]]); + real32 const localIncrementx = (Rxx_ij * m_ux_n[m_elemsToNodes( k, j )] + Rxy_ij*m_uy_n[m_elemsToNodes( k, j )] + Rxz_ij*m_uz_n[m_elemsToNodes( k, j )]); + real32 const localIncrementy = (Ryx_ij * m_ux_n[m_elemsToNodes( k, j )] + Ryy_ij*m_uy_n[m_elemsToNodes( k, j )] + Ryz_ij*m_uz_n[m_elemsToNodes( k, j )]); + real32 const localIncrementz = (Rzx_ij * m_ux_n[m_elemsToNodes( k, j )] + Rzy_ij*m_uy_n[m_elemsToNodes( k, j )] + Rzz_ij*m_uz_n[m_elemsToNodes( k, j )]); - RAJA::atomicAdd< parallelDeviceAtomic >( &m_stiffnessVectorx[m_elemsToNodes[k][i]], localIncrementx ); - RAJA::atomicAdd< parallelDeviceAtomic >( &m_stiffnessVectory[m_elemsToNodes[k][i]], localIncrementy ); - RAJA::atomicAdd< parallelDeviceAtomic >( &m_stiffnessVectorz[m_elemsToNodes[k][i]], localIncrementz ); + RAJA::atomicAdd< parallelDeviceAtomic >( &m_stiffnessVectorx[m_elemsToNodes( k, i )], localIncrementx ); + RAJA::atomicAdd< parallelDeviceAtomic >( &m_stiffnessVectory[m_elemsToNodes( k, i )], localIncrementy ); + RAJA::atomicAdd< parallelDeviceAtomic >( &m_stiffnessVectorz[m_elemsToNodes( k, i )], localIncrementz ); } ); } diff --git a/src/coreComponents/physicsSolvers/wavePropagation/WaveSolverUtils.hpp b/src/coreComponents/physicsSolvers/wavePropagation/WaveSolverUtils.hpp index ca52e3af4c0..1e856597658 100644 --- a/src/coreComponents/physicsSolvers/wavePropagation/WaveSolverUtils.hpp +++ b/src/coreComponents/physicsSolvers/wavePropagation/WaveSolverUtils.hpp @@ -80,9 +80,12 @@ struct WaveSolverUtils */ static void initTrace( char const * prefix, string const & name, + bool const outputSeismoTrace, localIndex const nReceivers, arrayView1d< localIndex const > const receiverIsLocal ) { + if( !outputSeismoTrace ) return; + string const outputDir = OutputBase::getOutputDirectory(); RAJA::ReduceSum< ReducePolicy< serialPolicy >, localIndex > count( 0 ); @@ -101,7 +104,7 @@ struct WaveSolverUtils } /** - * @brief Convenient helper for 3D vectors calling 3 times the scalar version. + * @brief Convenient helper for 3D vectors calling 3 times the scalar version with only the sampled variable argument changed. */ static void writeSeismoTraceVector( char const * prefix, string const & name, @@ -140,6 +143,7 @@ struct WaveSolverUtils std::ofstream f( fn, std::ios::app ); if( f ) { + GEOS_LOG_RANK( GEOS_FMT( "Append to seismo trace file {}", fn ) ); for( localIndex iSample = 0; iSample < nsamplesSeismoTrace; ++iSample ) { // index - time - value @@ -208,7 +212,7 @@ struct WaveSolverUtils arrayView2d< real32 const > const var_n, arrayView2d< real32 > varAtReceivers ) { - real64 const time_np1 = time_n+dt; + real64 const time_np1 = time_n + dt; real32 const a1 = dt < epsilonLoc ? 1.0 : (time_np1 - timeSeismo) / dt; real32 const a2 = 1.0 - a1; From a9d105c725eec8ae735d276cd07ca80fe1e7250a Mon Sep 17 00:00:00 2001 From: MelReyCG <122801580+MelReyCG@users.noreply.github.com> Date: Thu, 14 Dec 2023 06:50:08 +0100 Subject: [PATCH 08/40] SourceFlux & RegionStatistics crash fixes (#2842) 1 When a SourceFlux is targeting an empty set (typically when a Box is intersecting with a non-simulated region) Action: continue the code normally, output a message if logLevel > 1. 2 When a region pore volume is left to zero (because it is not necessary to fill it to the user). Action: continue the code normally, output a message if logLevel > 1. --- .../fluidFlow/CompositionalMultiphaseBase.cpp | 9 +++++++++ .../CompositionalMultiphaseStatistics.cpp | 16 ++++++++++++++-- .../physicsSolvers/fluidFlow/SinglePhaseBase.cpp | 9 +++++++++ .../fluidFlow/SinglePhaseStatistics.cpp | 11 ++++++++++- 4 files changed, 42 insertions(+), 3 deletions(-) diff --git a/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseBase.cpp b/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseBase.cpp index b28ee4a4bf9..8230eafb306 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseBase.cpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseBase.cpp @@ -1452,6 +1452,15 @@ void CompositionalMultiphaseBase::applySourceFluxBC( real64 const time, { return; } + if( !subRegion.hasWrapper( dofKey ) ) + { + if( fs.getLogLevel() >= 1 ) + { + GEOS_LOG_RANK( GEOS_FMT( "{}: trying to apply SourceFlux, but its targetSet named '{}' intersects with non-simulated region named '{}'.", + getDataContext(), setName, subRegion.getName() ) ); + } + return; + } arrayView1d< globalIndex const > const dofNumber = subRegion.getReference< array1d< globalIndex > >( dofKey ); arrayView1d< integer const > const ghostRank = subRegion.ghostRank(); diff --git a/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseStatistics.cpp b/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseStatistics.cpp index a53e19c9626..4e8df54a4e5 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseStatistics.cpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseStatistics.cpp @@ -393,9 +393,21 @@ void CompositionalMultiphaseStatistics::computeRegionStatistics( real64 const ti } } regionStatistics.averagePressure = MpiWrapper::sum( regionStatistics.averagePressure ); - regionStatistics.averagePressure /= regionStatistics.totalUncompactedPoreVolume; regionStatistics.averageTemperature = MpiWrapper::sum( regionStatistics.averageTemperature ); - regionStatistics.averageTemperature /= regionStatistics.totalUncompactedPoreVolume; + if( regionStatistics.totalUncompactedPoreVolume > 0 ) + { + float invTotalUncompactedPoreVolume = 1.0 / regionStatistics.totalUncompactedPoreVolume; + regionStatistics.averagePressure *= invTotalUncompactedPoreVolume; + regionStatistics.averageTemperature *= invTotalUncompactedPoreVolume; + } + else + { + regionStatistics.averagePressure = 0.0; + regionStatistics.averageTemperature = 0.0; + GEOS_LOG_LEVEL_RANK_0( 1, getName() << ", " << regionNames[i] + << ": Cannot compute average pressure because region pore volume is zero." ); + } + // helpers to report statistics array1d< real64 > nonTrappedPhaseMass( numPhases ); diff --git a/src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseBase.cpp b/src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseBase.cpp index 6ec5d7c9854..c94e705ba3e 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseBase.cpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseBase.cpp @@ -1028,6 +1028,15 @@ void SinglePhaseBase::applySourceFluxBC( real64 const time_n, { return; } + if( !subRegion.hasWrapper( dofKey ) ) + { + if( fs.getLogLevel() >= 1 ) + { + GEOS_LOG_RANK( GEOS_FMT( "{}: trying to apply SourceFlux, but its targetSet named '{}' intersects with non-simulated region named '{}'.", + getDataContext(), setName, subRegion.getName() ) ); + } + return; + } arrayView1d< globalIndex const > const dofNumber = subRegion.getReference< array1d< globalIndex > >( dofKey ); arrayView1d< integer const > const ghostRank = subRegion.ghostRank(); diff --git a/src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseStatistics.cpp b/src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseStatistics.cpp index 9f711fad10c..e8a0bcd17dd 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseStatistics.cpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseStatistics.cpp @@ -182,7 +182,16 @@ void SinglePhaseStatistics::computeRegionStatistics( MeshLevel & mesh, regionStatistics.totalUncompactedPoreVolume = MpiWrapper::sum( regionStatistics.totalUncompactedPoreVolume ); regionStatistics.totalPoreVolume = MpiWrapper::sum( regionStatistics.totalPoreVolume ); regionStatistics.averagePressure = MpiWrapper::sum( regionStatistics.averagePressure ); - regionStatistics.averagePressure /= regionStatistics.totalUncompactedPoreVolume; + if( regionStatistics.totalUncompactedPoreVolume > 0 ) + { + regionStatistics.averagePressure /= regionStatistics.totalUncompactedPoreVolume; + } + else + { + regionStatistics.averagePressure = 0.0; + GEOS_LOG_LEVEL_RANK_0( 1, getName() << ", " << regionNames[i] + << ": Cannot compute average pressure because region pore volume is zero." ); + } GEOS_LOG_LEVEL_RANK_0( 1, getName() << ", " << regionNames[i] << ": Pressure (min, average, max): " From e9dec15bd63c83477b9f9dfa17d84d55cb42fd10 Mon Sep 17 00:00:00 2001 From: Dickson Kachuma <81433670+dkachuma@users.noreply.github.com> Date: Thu, 14 Dec 2023 10:23:32 -0600 Subject: [PATCH 09/40] Fix Rachford-Rice solver (#2854) - Fixes a bug on the SSI portion of the Rachford-Rice solver. - Adds a unit test that follows this path. --- .../compositional/functions/RachfordRice.hpp | 63 ++++++++----------- .../unitTests/testRachfordRice.cpp | 39 +++++++++++- 2 files changed, 65 insertions(+), 37 deletions(-) diff --git a/src/coreComponents/constitutive/fluid/multifluid/compositional/functions/RachfordRice.hpp b/src/coreComponents/constitutive/fluid/multifluid/compositional/functions/RachfordRice.hpp index ed60c11f8c7..af7c05bb428 100644 --- a/src/coreComponents/constitutive/fluid/multifluid/compositional/functions/RachfordRice.hpp +++ b/src/coreComponents/constitutive/fluid/multifluid/compositional/functions/RachfordRice.hpp @@ -98,48 +98,37 @@ struct RachfordRice real64 currentError = 1 / epsilon; // step 2: start the SSI loop - real64 funcXMin = 0.0; + // Evaluate at the bounds + real64 funcXMin = evaluate( kValues, feed, presentComponentIds, xMin ); + real64 funcXMax = evaluate( kValues, feed, presentComponentIds, xMax ); real64 funcXMid = 0.0; - real64 funcXMax = 0.0; - bool recomputeMin = true; - bool recomputeMax = true; + + // If the bound values are the same sign then we have a trivial solution + if( 0.0 < funcXMin * funcXMax ) + { + gasPhaseMoleFraction = (0.0 < funcXMin) ? 1.0 : 0.0; + return gasPhaseMoleFraction; + } + integer SSIIteration = 0; while( ( currentError > SSITolerance ) && ( SSIIteration < maxSSIIterations ) ) { real64 const xMid = 0.5 * ( xMin + xMax ); - if( recomputeMin ) - { - funcXMin = evaluate( kValues, feed, presentComponentIds, xMin ); - } - if( recomputeMax ) - { - funcXMax = evaluate( kValues, feed, presentComponentIds, xMax ); - } funcXMid = evaluate( kValues, feed, presentComponentIds, xMid ); - if( ( funcXMin < 0 ) && ( funcXMax < 0 ) ) - { - return gasPhaseMoleFraction = 0.0; - } - else if( ( funcXMin > 1 ) && ( funcXMax > 1 ) ) - { - return gasPhaseMoleFraction = 1.0; - } - else if( funcXMin * funcXMid < 0.0 ) + if( 0.0 < funcXMax * funcXMid ) { xMax = xMid; - recomputeMax = true; - recomputeMin = false; + funcXMax = funcXMid; } - else if( funcXMax * funcXMid < 0.0 ) + else if( 0.0 < funcXMin * funcXMid ) { xMin = xMid; - recomputeMax = false; - recomputeMin = true; + funcXMin = funcXMid; } - currentError = LvArray::math::min( LvArray::math::abs( funcXMax - funcXMin ), + currentError = LvArray::math::min( LvArray::math::abs( funcXMid ), LvArray::math::abs( xMax - xMin ) ); SSIIteration++; @@ -151,26 +140,28 @@ struct RachfordRice // step 3: start the Newton loop integer newtonIteration = 0; real64 newtonValue = gasPhaseMoleFraction; + real64 funcNewton = evaluate( kValues, feed, presentComponentIds, newtonValue ); while( ( currentError > newtonTolerance ) && ( newtonIteration < maxNewtonIterations ) ) { - real64 const deltaNewton = -evaluate( kValues, feed, presentComponentIds, newtonValue ) - / evaluateDerivative( kValues, feed, presentComponentIds, newtonValue ); - currentError = LvArray::math::abs( deltaNewton ) / LvArray::math::abs( newtonValue ); + real64 deltaNewton = -funcNewton / evaluateDerivative( kValues, feed, presentComponentIds, newtonValue ); // test if we are stepping out of the [xMin;xMax] interval if( newtonValue + deltaNewton < xMin ) { - newtonValue = 0.5 * ( newtonValue + xMin ); + deltaNewton = 0.5 * ( xMin - newtonValue ); } else if( newtonValue + deltaNewton > xMax ) { - newtonValue = 0.5 * ( newtonValue + xMax ); - } - else - { - newtonValue = newtonValue + deltaNewton; + deltaNewton = 0.5 * ( xMax - newtonValue ); } + + newtonValue = newtonValue + deltaNewton; + + funcNewton = evaluate( kValues, feed, presentComponentIds, newtonValue ); + + currentError = LvArray::math::min( LvArray::math::abs( funcNewton ), + LvArray::math::abs( deltaNewton ) ); newtonIteration++; // TODO: add warning if max number of Newton iterations is reached diff --git a/src/coreComponents/constitutive/unitTests/testRachfordRice.cpp b/src/coreComponents/constitutive/unitTests/testRachfordRice.cpp index 8f6014096cf..0b7f3230735 100644 --- a/src/coreComponents/constitutive/unitTests/testRachfordRice.cpp +++ b/src/coreComponents/constitutive/unitTests/testRachfordRice.cpp @@ -110,8 +110,45 @@ TEST( RachfordRiceTest, testRachfordRiceTwoComponents ) presentComponentIds.toSliceConst() ); checkRelativeError( vaporFraction4, expectedVaporFraction4, relTol ); -} + //////////////////////////////////////// + + kValues[0] = 0.9; + kValues[1] = 1.09733; + + feed[0] = 1.0e-10; + feed[1] = 1.0 - feed[0]; + + presentComponentIds[0] = 0; + presentComponentIds[1] = 1; + + real64 const expectedVaporFraction5 = 1; + real64 const vaporFraction5 = + RachfordRice::solve( kValues.toSliceConst(), + feed.toSliceConst(), + presentComponentIds.toSliceConst() ); + + checkRelativeError( vaporFraction5, expectedVaporFraction5, relTol ); + + //////////////////////////////////////// + + kValues[0] = 1.09733; + kValues[1] = 0.9; + + feed[0] = 1.0e-10; + feed[1] = 1.0 - feed[0]; + + presentComponentIds[0] = 0; + presentComponentIds[1] = 1; + + real64 const expectedVaporFraction6 = 0.0; + real64 const vaporFraction6 = + RachfordRice::solve( kValues.toSliceConst(), + feed.toSliceConst(), + presentComponentIds.toSliceConst() ); + + checkRelativeError( vaporFraction6, expectedVaporFraction6, relTol ); +} TEST( RachfordRiceTest, testRachfordRiceFourComponents ) { From 2017843bd79e3cfc58d9066fd20e832252e56ef5 Mon Sep 17 00:00:00 2001 From: "Victor A. P. Magri" <50467563+victorapm@users.noreply.github.com> Date: Thu, 14 Dec 2023 22:30:35 -0500 Subject: [PATCH 10/40] Fix use of minCompDens inside chopNegativeDensities kernel (#2889) Fix use of minCompDens inside chopNegativeDensities kernel --- .../physicsSolvers/fluidFlow/CompositionalMultiphaseBase.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseBase.cpp b/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseBase.cpp index 8230eafb306..85892802b01 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseBase.cpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseBase.cpp @@ -1937,6 +1937,7 @@ void CompositionalMultiphaseBase::chopNegativeDensities( DomainPartition & domai using namespace isothermalCompositionalMultiphaseBaseKernels; integer const numComp = m_numComponents; + real64 const minCompDens = m_minCompDens; forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const &, MeshLevel & mesh, @@ -1957,9 +1958,9 @@ void CompositionalMultiphaseBase::chopNegativeDensities( DomainPartition & domai { for( integer ic = 0; ic < numComp; ++ic ) { - if( compDens[ei][ic] < m_minCompDens ) + if( compDens[ei][ic] < minCompDens ) { - compDens[ei][ic] = m_minCompDens; + compDens[ei][ic] = minCompDens; } } } From 95ddd59474d78c5a151e3f2dff47e26e8037c2ca Mon Sep 17 00:00:00 2001 From: Brian Han Date: Thu, 14 Dec 2023 19:31:24 -0800 Subject: [PATCH 11/40] Clean up untracked files if Gitlab CI repo clone is pre-initialized from previous run (#2892) When Gitlab CI reuses a repo clone for testing, untracked files can cause errors with git, preventing updates to newer commits (nightlyTests run that errored out due to untracked files in integratedTests submodule: https://lc.llnl.gov/gitlab/settgast/GEOSX/-/pipelines/359642). --- .gitlab-ci.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index f905724ba95..033f29acf68 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -21,6 +21,10 @@ stages: - git submodule set-url integratedTests git@github.com:GEOS-DEV/integratedTests.git - git submodule set-url src/coreComponents/fileIO/coupling/hdf5_interface https://github.com/GEOS-DEV/hdf5_interface.git + # Clean up directory and submodules when pre-initialized from previous CI run + - git clean -x -f -d + - git submodule foreach --recursive git clean -x -f -d + # Update submodules - git submodule update --init --recursive src/cmake/blt - git submodule update --init --recursive src/coreComponents/LvArray From 96e7c4df50176d931e69d64f3c2b27880be32f7e Mon Sep 17 00:00:00 2001 From: Jacques Franc <49998870+jafranc@users.noreply.github.com> Date: Mon, 18 Dec 2023 22:37:17 +0100 Subject: [PATCH 12/40] Computing next time-step size from CFL (#2600) This PR is aiming at having the following time-step size being calculated so that it honors CFL < `m_cflFactor`. The max value is set for phase or component CFL, which ever is the largest. --- .../4comp_2ph_1d.xml | 26 +-- integratedTests | 2 +- .../physicsSolvers/SolverBase.cpp | 27 ++- .../physicsSolvers/SolverBase.hpp | 11 + .../fluidFlow/CompositionalMultiphaseBase.cpp | 198 ++++++++++++++++++ .../fluidFlow/CompositionalMultiphaseBase.hpp | 20 ++ .../CompositionalMultiphaseStatistics.cpp | 150 +------------ .../docs/CompositionalMultiphaseFVM.rst | 1 + .../docs/CompositionalMultiphaseHybridFVM.rst | 1 + src/coreComponents/schema/schema.xsd | 4 + 10 files changed, 266 insertions(+), 174 deletions(-) diff --git a/inputFiles/compositionalMultiphaseFlow/4comp_2ph_1d.xml b/inputFiles/compositionalMultiphaseFlow/4comp_2ph_1d.xml index e2bbab3190d..556e49fdea4 100644 --- a/inputFiles/compositionalMultiphaseFlow/4comp_2ph_1d.xml +++ b/inputFiles/compositionalMultiphaseFlow/4comp_2ph_1d.xml @@ -7,6 +7,8 @@ logLevel="1" discretization="fluidTPFA" targetRegions="{ Region1 }" + initialDt="1e5" + targetFlowCFL="2" temperature="297.15"> + timeFrequency="5e6" + target="/Outputs/vtkOutput"/> - - - - - + diff --git a/integratedTests b/integratedTests index 5ffebf05d09..e3fd6bee8c9 160000 --- a/integratedTests +++ b/integratedTests @@ -1 +1 @@ -Subproject commit 5ffebf05d093b7492328e782c68c1612e6ce14af +Subproject commit e3fd6bee8c9dfd2ccb893278f369abf49b41a845 diff --git a/src/coreComponents/physicsSolvers/SolverBase.cpp b/src/coreComponents/physicsSolvers/SolverBase.cpp index a20cdddfaf7..76b9ae227b2 100644 --- a/src/coreComponents/physicsSolvers/SolverBase.cpp +++ b/src/coreComponents/physicsSolvers/SolverBase.cpp @@ -292,32 +292,34 @@ real64 SolverBase::setNextDt( real64 const & currentDt, real64 const nextDtNewton = setNextDtBasedOnNewtonIter( currentDt ); real64 const nextDtStateChange = setNextDtBasedOnStateChange( currentDt, domain ); - if( nextDtNewton < nextDtStateChange ) // time step size decided based on convergence + if( nextDtNewton < nextDtStateChange ) // time step size decided based on convergence { integer const iterDecreaseLimit = m_nonlinearSolverParameters.timeStepDecreaseIterLimit(); integer const iterIncreaseLimit = m_nonlinearSolverParameters.timeStepIncreaseIterLimit(); if( nextDtNewton > currentDt ) { - GEOS_LOG_LEVEL_RANK_0( 1, GEOS_FMT( "{}: Newton solver converged in less than {} iterations, time-step required will be increased.", - getName(), iterIncreaseLimit ) ); + GEOS_LOG_LEVEL_RANK_0( 1, GEOS_FMT( + "{}: Newton solver converged in less than {} iterations, time-step required will be increased.", + getName(), iterIncreaseLimit )); } else if( nextDtNewton < currentDt ) { - GEOS_LOG_LEVEL_RANK_0( 1, GEOS_FMT( "{}: Newton solver converged in more than {} iterations, time-step required will be decreased.", - getName(), iterDecreaseLimit ) ); + GEOS_LOG_LEVEL_RANK_0( 1, GEOS_FMT( + "{}: Newton solver converged in more than {} iterations, time-step required will be decreased.", + getName(), iterDecreaseLimit )); } } - else // time step size decided based on state change + else // time step size decided based on state change { if( nextDtStateChange > currentDt ) { GEOS_LOG_LEVEL_RANK_0( 1, GEOS_FMT( "{}: Time-step required will be increased based on state change.", - getName() ) ); + getName())); } else if( nextDtStateChange < currentDt ) { GEOS_LOG_LEVEL_RANK_0( 1, GEOS_FMT( "{}: Time-step required will be decreased based on state change.", - getName() ) ); + getName())); } } @@ -355,6 +357,15 @@ real64 SolverBase::setNextDtBasedOnNewtonIter( real64 const & currentDt ) return nextDt; } + +real64 SolverBase::setNextDtBasedOnCFL( const geos::real64 & currentDt, geos::DomainPartition & domain ) +{ + GEOS_UNUSED_VAR( currentDt, domain ); + return LvArray::NumericLimits< real64 >::max; // i.e., not implemented +} + + + real64 SolverBase::linearImplicitStep( real64 const & time_n, real64 const & dt, integer const GEOS_UNUSED_PARAM( cycleNumber ), diff --git a/src/coreComponents/physicsSolvers/SolverBase.hpp b/src/coreComponents/physicsSolvers/SolverBase.hpp index 413113b0633..8129e754402 100644 --- a/src/coreComponents/physicsSolvers/SolverBase.hpp +++ b/src/coreComponents/physicsSolvers/SolverBase.hpp @@ -164,6 +164,17 @@ class SolverBase : public ExecutableGroup virtual real64 setNextDtBasedOnStateChange( real64 const & currentDt, DomainPartition & domain ); + /** + * @brief function to set the next dt based on state change + * @param [in] currentDt the current time step size + * @param[in] domain the domain object + * @return the prescribed time step size + */ + virtual real64 setNextDtBasedOnCFL( real64 const & currentDt, + DomainPartition & domain ); + + + /** * @brief Entry function for an explicit time integration step * @param time_n time at the beginning of the step diff --git a/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseBase.cpp b/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseBase.cpp index 85892802b01..f4897c4b9b6 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseBase.cpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseBase.cpp @@ -44,6 +44,7 @@ #include "physicsSolvers/fluidFlow/CompositionalMultiphaseBaseFields.hpp" #include "physicsSolvers/fluidFlow/FlowSolverBaseFields.hpp" #include "physicsSolvers/fluidFlow/IsothermalCompositionalMultiphaseBaseKernels.hpp" +#include "physicsSolvers/fluidFlow/IsothermalCompositionalMultiphaseFVMKernels.hpp" #include "physicsSolvers/fluidFlow/ThermalCompositionalMultiphaseBaseKernels.hpp" #if defined( __INTEL_COMPILER ) @@ -125,6 +126,12 @@ CompositionalMultiphaseBase::CompositionalMultiphaseBase( const string & name, setApplyDefaultValue( 1 ). setDescription( "Flag indicating whether local (cell-wise) chopping of negative compositions is allowed" ); + this->registerWrapper( viewKeyStruct::targetFlowCFLString(), &m_targetFlowCFL ). + setApplyDefaultValue( -1. ). + setInputFlag( InputFlags::OPTIONAL ). + setDescription( "Target CFL condition `CFL condition `_" + "when computing the next timestep." ); + this->registerWrapper( viewKeyStruct::useTotalMassEquationString(), &m_useTotalMassEquation ). setSizedFromParent( 0 ). setInputFlag( InputFlags::OPTIONAL ). @@ -250,8 +257,10 @@ void CompositionalMultiphaseBase::registerDataOnMesh( Group & meshBodies ) ElementSubRegionBase & subRegion ) { { + if( m_hasCapPressure ) { + subRegion.registerWrapper< string >( viewKeyStruct::capPressureNamesString() ). setPlotLevel( PlotLevel::NOPLOT ). setRestartFlags( RestartFlags::NO_WRITE ). @@ -294,6 +303,20 @@ void CompositionalMultiphaseBase::registerDataOnMesh( Group & meshBodies ) GEOS_FMT( "Dispersion model not found on subregion {}", subRegion.getName() ), InputError ); } + + + if( m_targetFlowCFL > 0 ) + { + + subRegion.registerField< fields::flow::phaseOutflux >( getName() ). + reference().resizeDimension< 1 >( m_numPhases ); + + subRegion.registerField< fields::flow::componentOutflux >( getName() ). + reference().resizeDimension< 1 >( m_numComponents ); + subRegion.registerField< fields::flow::phaseCFLNumber >( getName() ); + subRegion.registerField< fields::flow::componentCFLNumber >( getName() ); + } + } string const & fluidName = subRegion.getReference< string >( viewKeyStruct::fluidNamesString() ); @@ -2057,6 +2080,172 @@ real64 CompositionalMultiphaseBase::setNextDtBasedOnStateChange( real64 const & return std::min( std::min( nextDtPressure, nextDtPhaseVolFrac ), nextDtTemperature ); } +real64 CompositionalMultiphaseBase::setNextDtBasedOnCFL( const geos::real64 & currentDt, geos::DomainPartition & domain ) +{ + + real64 maxPhaseCFL, maxCompCFL; + + computeCFLNumbers( domain, currentDt, maxPhaseCFL, maxCompCFL ); + + GEOS_LOG_LEVEL_RANK_0( 1, getName() << ": Max phase CFL number: " << maxPhaseCFL ); + GEOS_LOG_LEVEL_RANK_0( 1, getName() << ": Max component CFL number: " << maxCompCFL ); + + return std::min( m_targetFlowCFL*currentDt/maxCompCFL, m_targetFlowCFL*currentDt/maxPhaseCFL ); + +} + +void CompositionalMultiphaseBase::computeCFLNumbers( geos::DomainPartition & domain, const geos::real64 & dt, + geos::real64 & maxPhaseCFL, geos::real64 & maxCompCFL ) +{ + GEOS_MARK_FUNCTION; + + integer const numPhases = numFluidPhases(); + integer const numComps = numFluidComponents(); + + // Step 1: reset the arrays involved in the computation of CFL numbers + forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const &, + MeshLevel & mesh, + arrayView1d< string const > const & regionNames ) + { + mesh.getElemManager().forElementSubRegions( regionNames, + [&]( localIndex const, + ElementSubRegionBase & subRegion ) + { + arrayView2d< real64, compflow::USD_PHASE > const & phaseOutflux = + subRegion.getField< fields::flow::phaseOutflux >(); + arrayView2d< real64, compflow::USD_COMP > const & compOutflux = + subRegion.getField< fields::flow::componentOutflux >(); + phaseOutflux.zero(); + compOutflux.zero(); + } ); + + // Step 2: compute the total volumetric outflux for each reservoir cell by looping over faces + NumericalMethodsManager & numericalMethodManager = domain.getNumericalMethodManager(); + FiniteVolumeManager & fvManager = numericalMethodManager.getFiniteVolumeManager(); + FluxApproximationBase & fluxApprox = fvManager.getFluxApproximation( getDiscretizationName() ); + + isothermalCompositionalMultiphaseFVMKernels:: + CFLFluxKernel::CompFlowAccessors compFlowAccessors( mesh.getElemManager(), getName() ); + isothermalCompositionalMultiphaseFVMKernels:: + CFLFluxKernel::MultiFluidAccessors multiFluidAccessors( mesh.getElemManager(), getName() ); + isothermalCompositionalMultiphaseFVMKernels:: + CFLFluxKernel::PermeabilityAccessors permeabilityAccessors( mesh.getElemManager(), getName() ); + isothermalCompositionalMultiphaseFVMKernels:: + CFLFluxKernel::RelPermAccessors relPermAccessors( mesh.getElemManager(), getName() ); + + // TODO: find a way to compile with this modifiable accessors in CompFlowAccessors, and remove them from here + ElementRegionManager::ElementViewAccessor< arrayView2d< real64, compflow::USD_PHASE > > const phaseOutfluxAccessor = + mesh.getElemManager().constructViewAccessor< array2d< real64, compflow::LAYOUT_PHASE >, + arrayView2d< real64, compflow::USD_PHASE > >( fields::flow::phaseOutflux::key() ); + + ElementRegionManager::ElementViewAccessor< arrayView2d< real64, compflow::USD_COMP > > const compOutfluxAccessor = + mesh.getElemManager().constructViewAccessor< array2d< real64, compflow::LAYOUT_COMP >, + arrayView2d< real64, compflow::USD_COMP > >( fields::flow::componentOutflux::key() ); + + + fluxApprox.forAllStencils( mesh, [&] ( auto & stencil ) + { + + typename TYPEOFREF( stencil ) ::KernelWrapper stencilWrapper = stencil.createKernelWrapper(); + + // While this kernel is waiting for a factory class, pass all the accessors here + isothermalCompositionalMultiphaseBaseKernels::KernelLaunchSelector1 + < isothermalCompositionalMultiphaseFVMKernels::CFLFluxKernel >( numComps, + numPhases, + dt, + stencilWrapper, + compFlowAccessors.get( fields::flow::pressure{} ), + compFlowAccessors.get( fields::flow::gravityCoefficient{} ), + compFlowAccessors.get( fields::flow::phaseVolumeFraction{} ), + permeabilityAccessors.get( fields::permeability::permeability{} ), + permeabilityAccessors.get( fields::permeability::dPerm_dPressure{} ), + relPermAccessors.get( fields::relperm::phaseRelPerm{} ), + multiFluidAccessors.get( fields::multifluid::phaseViscosity{} ), + multiFluidAccessors.get( fields::multifluid::phaseDensity{} ), + multiFluidAccessors.get( fields::multifluid::phaseMassDensity{} ), + multiFluidAccessors.get( fields::multifluid::phaseCompFraction{} ), + phaseOutfluxAccessor.toNestedView(), + compOutfluxAccessor.toNestedView() ); + } ); + } ); + + // Step 3: finalize the (cell-based) computation of the CFL numbers + real64 localMaxPhaseCFLNumber = 0.0; + real64 localMaxCompCFLNumber = 0.0; + + forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const &, + MeshLevel & mesh, + arrayView1d< string const > const & regionNames ) + { + mesh.getElemManager().forElementSubRegions( regionNames, + [&]( localIndex const, + ElementSubRegionBase & subRegion ) + { + arrayView2d< real64 const, compflow::USD_PHASE > const & phaseOutflux = + subRegion.getField< fields::flow::phaseOutflux >(); + arrayView2d< real64 const, compflow::USD_COMP > const & compOutflux = + subRegion.getField< fields::flow::componentOutflux >(); + + arrayView1d< real64 > const & phaseCFLNumber = subRegion.getField< fields::flow::phaseCFLNumber >(); + arrayView1d< real64 > const & compCFLNumber = subRegion.getField< fields::flow::componentCFLNumber >(); + + arrayView1d< real64 const > const & volume = subRegion.getElementVolume(); + + arrayView2d< real64 const, compflow::USD_COMP > const & compDens = + subRegion.getField< fields::flow::globalCompDensity >(); + arrayView2d< real64 const, compflow::USD_COMP > const compFrac = + subRegion.getField< fields::flow::globalCompFraction >(); + arrayView2d< real64, compflow::USD_PHASE > const phaseVolFrac = + subRegion.getField< fields::flow::phaseVolumeFraction >(); + + Group const & constitutiveModels = subRegion.getGroup( ElementSubRegionBase::groupKeyStruct::constitutiveModelsString() ); + + string const & fluidName = subRegion.getReference< string >( CompositionalMultiphaseBase::viewKeyStruct::fluidNamesString() ); + MultiFluidBase const & fluid = constitutiveModels.getGroup< MultiFluidBase >( fluidName ); + arrayView3d< real64 const, multifluid::USD_PHASE > const & phaseVisc = fluid.phaseViscosity(); + + string const & relpermName = subRegion.getReference< string >( CompositionalMultiphaseBase::viewKeyStruct::relPermNamesString() ); + RelativePermeabilityBase const & relperm = constitutiveModels.getGroup< RelativePermeabilityBase >( relpermName ); + arrayView3d< real64 const, relperm::USD_RELPERM > const & phaseRelPerm = relperm.phaseRelPerm(); + arrayView4d< real64 const, relperm::USD_RELPERM_DS > const & dPhaseRelPerm_dPhaseVolFrac = relperm.dPhaseRelPerm_dPhaseVolFraction(); + + string const & solidName = subRegion.getReference< string >( CompositionalMultiphaseBase::viewKeyStruct::solidNamesString() ); + CoupledSolidBase const & solid = constitutiveModels.getGroup< CoupledSolidBase >( solidName ); + arrayView2d< real64 const > const & porosity = solid.getPorosity(); + + real64 subRegionMaxPhaseCFLNumber = 0.0; + real64 subRegionMaxCompCFLNumber = 0.0; + + isothermalCompositionalMultiphaseBaseKernels::KernelLaunchSelector2 + < isothermalCompositionalMultiphaseFVMKernels::CFLKernel >( numComps, numPhases, + subRegion.size(), + volume, + porosity, + compDens, + compFrac, + phaseVolFrac, + phaseRelPerm, + dPhaseRelPerm_dPhaseVolFrac, + phaseVisc, + phaseOutflux, + compOutflux, + phaseCFLNumber, + compCFLNumber, + subRegionMaxPhaseCFLNumber, + subRegionMaxCompCFLNumber ); + + localMaxPhaseCFLNumber = LvArray::math::max( localMaxPhaseCFLNumber, subRegionMaxPhaseCFLNumber ); + localMaxCompCFLNumber = LvArray::math::max( localMaxCompCFLNumber, subRegionMaxCompCFLNumber ); + + } ); + } ); + + maxPhaseCFL = MpiWrapper::max( localMaxPhaseCFLNumber ); + maxCompCFL = MpiWrapper::max( localMaxCompCFLNumber ); + +} + + void CompositionalMultiphaseBase::resetStateToBeginningOfStep( DomainPartition & domain ) { GEOS_MARK_FUNCTION; @@ -2255,4 +2444,13 @@ void CompositionalMultiphaseBase::updateState( DomainPartition & domain ) GEOS_LOG_LEVEL_RANK_0( 1, GEOS_FMT( " {}: Max phase volume fraction change = {}", getName(), fmt::format( "{:.{}f}", maxDeltaPhaseVolFrac, 4 ) ) ); } +real64 CompositionalMultiphaseBase::setNextDt( const geos::real64 & currentDt, geos::DomainPartition & domain ) +{ + + if( m_targetFlowCFL<0 ) + return SolverBase::setNextDt( currentDt, domain ); + else + return setNextDtBasedOnCFL( currentDt, domain ); +} + } // namespace geos diff --git a/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseBase.hpp b/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseBase.hpp index a90c23fe6b4..18a4b7e4481 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseBase.hpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseBase.hpp @@ -244,6 +244,8 @@ class CompositionalMultiphaseBase : public FlowSolverBase static constexpr char const * targetRelativePresChangeString() { return "targetRelativePressureChangeInTimeStep"; } static constexpr char const * targetRelativeTempChangeString() { return "targetRelativeTemperatureChangeInTimeStep"; } static constexpr char const * targetPhaseVolFracChangeString() { return "targetPhaseVolFractionChangeInTimeStep"; } + static constexpr char const * targetFlowCFLString() { return "targetFlowCFL"; } + // nonlinear solver parameters @@ -356,6 +358,20 @@ class CompositionalMultiphaseBase : public FlowSolverBase virtual real64 setNextDtBasedOnStateChange( real64 const & currentDt, DomainPartition & domain ) override; + void computeCFLNumbers( DomainPartition & domain, real64 const & dt, real64 & maxPhaseCFL, real64 & maxCompCFL ); + + /** + * @brief function to set the next time step size + * @param[in] currentDt the current time step size + * @param[in] domain the domain object + * @return the prescribed time step size + */ + real64 setNextDt( real64 const & currentDt, + DomainPartition & domain ) override; + + virtual real64 setNextDtBasedOnCFL( real64 const & currentDt, + DomainPartition & domain ) override; + virtual void initializePostInitialConditionsPreSubGroups() override; integer useTotalMassEquation() const { return m_useTotalMassEquation; } @@ -366,6 +382,7 @@ class CompositionalMultiphaseBase : public FlowSolverBase virtual void initializePreSubGroups() override; + /** * @brief Utility function that checks the consistency of the constitutive models * @param[in] domain the domain partition @@ -460,6 +477,9 @@ class CompositionalMultiphaseBase : public FlowSolverBase /// name of the fluid constitutive model used as a reference for component/phase description string m_referenceFluidModelName; + /// the targeted CFL for timestep + real64 m_targetFlowCFL; + private: /** diff --git a/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseStatistics.cpp b/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseStatistics.cpp index 4e8df54a4e5..fd01f5d3898 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseStatistics.cpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseStatistics.cpp @@ -483,153 +483,11 @@ void CompositionalMultiphaseStatistics::computeCFLNumbers( real64 const time, DomainPartition & domain ) const { GEOS_MARK_FUNCTION; + real64 maxPhaseCFL, maxCompCFL; + m_solver->computeCFLNumbers( domain, dt, maxPhaseCFL, maxCompCFL ); - integer const numPhases = m_solver->numFluidPhases(); - integer const numComps = m_solver->numFluidComponents(); - - // Step 1: reset the arrays involved in the computation of CFL numbers - m_solver->forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const &, - MeshLevel & mesh, - arrayView1d< string const > const & regionNames ) - { - mesh.getElemManager().forElementSubRegions( regionNames, - [&]( localIndex const, - ElementSubRegionBase & subRegion ) - { - arrayView2d< real64, compflow::USD_PHASE > const & phaseOutflux = - subRegion.getField< fields::flow::phaseOutflux >(); - arrayView2d< real64, compflow::USD_COMP > const & compOutflux = - subRegion.getField< fields::flow::componentOutflux >(); - phaseOutflux.zero(); - compOutflux.zero(); - } ); - - // Step 2: compute the total volumetric outflux for each reservoir cell by looping over faces - NumericalMethodsManager & numericalMethodManager = domain.getNumericalMethodManager(); - FiniteVolumeManager & fvManager = numericalMethodManager.getFiniteVolumeManager(); - FluxApproximationBase & fluxApprox = fvManager.getFluxApproximation( m_solver->getDiscretizationName() ); - - isothermalCompositionalMultiphaseFVMKernels:: - CFLFluxKernel::CompFlowAccessors compFlowAccessors( mesh.getElemManager(), getName() ); - isothermalCompositionalMultiphaseFVMKernels:: - CFLFluxKernel::MultiFluidAccessors multiFluidAccessors( mesh.getElemManager(), getName() ); - isothermalCompositionalMultiphaseFVMKernels:: - CFLFluxKernel::PermeabilityAccessors permeabilityAccessors( mesh.getElemManager(), getName() ); - isothermalCompositionalMultiphaseFVMKernels:: - CFLFluxKernel::RelPermAccessors relPermAccessors( mesh.getElemManager(), getName() ); - - // TODO: find a way to compile with this modifiable accessors in CompFlowAccessors, and remove them from here - ElementRegionManager::ElementViewAccessor< arrayView2d< real64, compflow::USD_PHASE > > const phaseOutfluxAccessor = - mesh.getElemManager().constructViewAccessor< array2d< real64, compflow::LAYOUT_PHASE >, - arrayView2d< real64, compflow::USD_PHASE > >( fields::flow::phaseOutflux::key() ); - - ElementRegionManager::ElementViewAccessor< arrayView2d< real64, compflow::USD_COMP > > const compOutfluxAccessor = - mesh.getElemManager().constructViewAccessor< array2d< real64, compflow::LAYOUT_COMP >, - arrayView2d< real64, compflow::USD_COMP > >( fields::flow::componentOutflux::key() ); - - - fluxApprox.forAllStencils( mesh, [&] ( auto & stencil ) - { - - typename TYPEOFREF( stencil ) ::KernelWrapper stencilWrapper = stencil.createKernelWrapper(); - - // While this kernel is waiting for a factory class, pass all the accessors here - isothermalCompositionalMultiphaseBaseKernels::KernelLaunchSelector1 - < isothermalCompositionalMultiphaseFVMKernels::CFLFluxKernel >( numComps, - numPhases, - dt, - stencilWrapper, - compFlowAccessors.get( fields::flow::pressure{} ), - compFlowAccessors.get( fields::flow::gravityCoefficient{} ), - compFlowAccessors.get( fields::flow::phaseVolumeFraction{} ), - permeabilityAccessors.get( fields::permeability::permeability{} ), - permeabilityAccessors.get( fields::permeability::dPerm_dPressure{} ), - relPermAccessors.get( fields::relperm::phaseRelPerm{} ), - multiFluidAccessors.get( fields::multifluid::phaseViscosity{} ), - multiFluidAccessors.get( fields::multifluid::phaseDensity{} ), - multiFluidAccessors.get( fields::multifluid::phaseMassDensity{} ), - multiFluidAccessors.get( fields::multifluid::phaseCompFraction{} ), - phaseOutfluxAccessor.toNestedView(), - compOutfluxAccessor.toNestedView() ); - } ); - } ); - - // Step 3: finalize the (cell-based) computation of the CFL numbers - real64 localMaxPhaseCFLNumber = 0.0; - real64 localMaxCompCFLNumber = 0.0; - - m_solver->forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const &, - MeshLevel & mesh, - arrayView1d< string const > const & regionNames ) - { - mesh.getElemManager().forElementSubRegions( regionNames, - [&]( localIndex const, - ElementSubRegionBase & subRegion ) - { - arrayView2d< real64 const, compflow::USD_PHASE > const & phaseOutflux = - subRegion.getField< fields::flow::phaseOutflux >(); - arrayView2d< real64 const, compflow::USD_COMP > const & compOutflux = - subRegion.getField< fields::flow::componentOutflux >(); - - arrayView1d< real64 > const & phaseCFLNumber = subRegion.getField< fields::flow::phaseCFLNumber >(); - arrayView1d< real64 > const & compCFLNumber = subRegion.getField< fields::flow::componentCFLNumber >(); - - arrayView1d< real64 const > const & volume = subRegion.getElementVolume(); - - arrayView2d< real64 const, compflow::USD_COMP > const & compDens = - subRegion.getField< fields::flow::globalCompDensity >(); - arrayView2d< real64 const, compflow::USD_COMP > const compFrac = - subRegion.getField< fields::flow::globalCompFraction >(); - arrayView2d< real64, compflow::USD_PHASE > const phaseVolFrac = - subRegion.getField< fields::flow::phaseVolumeFraction >(); - - Group const & constitutiveModels = subRegion.getGroup( ElementSubRegionBase::groupKeyStruct::constitutiveModelsString() ); - - string const & fluidName = subRegion.getReference< string >( CompositionalMultiphaseBase::viewKeyStruct::fluidNamesString() ); - MultiFluidBase const & fluid = constitutiveModels.getGroup< MultiFluidBase >( fluidName ); - arrayView3d< real64 const, multifluid::USD_PHASE > const & phaseVisc = fluid.phaseViscosity(); - - string const & relpermName = subRegion.getReference< string >( CompositionalMultiphaseBase::viewKeyStruct::relPermNamesString() ); - RelativePermeabilityBase const & relperm = constitutiveModels.getGroup< RelativePermeabilityBase >( relpermName ); - arrayView3d< real64 const, relperm::USD_RELPERM > const & phaseRelPerm = relperm.phaseRelPerm(); - arrayView4d< real64 const, relperm::USD_RELPERM_DS > const & dPhaseRelPerm_dPhaseVolFrac = relperm.dPhaseRelPerm_dPhaseVolFraction(); - - string const & solidName = subRegion.getReference< string >( CompositionalMultiphaseBase::viewKeyStruct::solidNamesString() ); - CoupledSolidBase const & solid = constitutiveModels.getGroup< CoupledSolidBase >( solidName ); - arrayView2d< real64 const > const & porosity = solid.getPorosity(); - - real64 subRegionMaxPhaseCFLNumber = 0.0; - real64 subRegionMaxCompCFLNumber = 0.0; - - isothermalCompositionalMultiphaseBaseKernels::KernelLaunchSelector2 - < isothermalCompositionalMultiphaseFVMKernels::CFLKernel >( numComps, numPhases, - subRegion.size(), - volume, - porosity, - compDens, - compFrac, - phaseVolFrac, - phaseRelPerm, - dPhaseRelPerm_dPhaseVolFrac, - phaseVisc, - phaseOutflux, - compOutflux, - phaseCFLNumber, - compCFLNumber, - subRegionMaxPhaseCFLNumber, - subRegionMaxCompCFLNumber ); - - localMaxPhaseCFLNumber = LvArray::math::max( localMaxPhaseCFLNumber, subRegionMaxPhaseCFLNumber ); - localMaxCompCFLNumber = LvArray::math::max( localMaxCompCFLNumber, subRegionMaxCompCFLNumber ); - - } ); - } ); - - real64 const globalMaxPhaseCFLNumber = MpiWrapper::max( localMaxPhaseCFLNumber ); - real64 const globalMaxCompCFLNumber = MpiWrapper::max( localMaxCompCFLNumber ); - - GEOS_LOG_LEVEL_RANK_0( 1, GEOS_FMT( "{} (time {} s): Max phase CFL number: {}", getName(), time, globalMaxPhaseCFLNumber ) ); - GEOS_LOG_LEVEL_RANK_0( 1, GEOS_FMT( "{} (time {} s): Max component CFL number: {}", getName(), time, globalMaxCompCFLNumber ) ); + GEOS_LOG_LEVEL_RANK_0( 1, GEOS_FMT( "{} (time {} s): Max phase CFL number: {}", getName(), time, maxPhaseCFL ) ); + GEOS_LOG_LEVEL_RANK_0( 1, GEOS_FMT( "{} (time {} s): Max component CFL number: {}", getName(), time, maxCompCFL ) ); } diff --git a/src/coreComponents/schema/docs/CompositionalMultiphaseFVM.rst b/src/coreComponents/schema/docs/CompositionalMultiphaseFVM.rst index ae387d086bc..0fb7bf0fb89 100644 --- a/src/coreComponents/schema/docs/CompositionalMultiphaseFVM.rst +++ b/src/coreComponents/schema/docs/CompositionalMultiphaseFVM.rst @@ -25,6 +25,7 @@ scalingType geos_CompositionalMultiphaseFVM_Scalin | * Global | * Local solutionChangeScalingFactor real64 0.5 Damping factor for solution change targets +targetFlowCFL real64 -1 Target CFL condition `CFL condition `_when computing the next timestep. targetPhaseVolFractionChangeInTimeStep real64 0.2 Target (absolute) change in phase volume fraction in a time step targetRegions groupNameRef_array required Allowable regions that the solver may be applied to. Note that this does not indicate that the solver will be applied to these regions, only that allocation will occur such that the solver may be applied to these regions. The decision about what regions this solver will beapplied to rests in the EventManager. targetRelativePressureChangeInTimeStep real64 0.2 Target (relative) change in pressure in a time step (expected value between 0 and 1) diff --git a/src/coreComponents/schema/docs/CompositionalMultiphaseHybridFVM.rst b/src/coreComponents/schema/docs/CompositionalMultiphaseHybridFVM.rst index 56e166a6161..d2a1bd31852 100644 --- a/src/coreComponents/schema/docs/CompositionalMultiphaseHybridFVM.rst +++ b/src/coreComponents/schema/docs/CompositionalMultiphaseHybridFVM.rst @@ -17,6 +17,7 @@ maxRelativeTemperatureChange real64 0.5 Maximum (r minCompDens real64 1e-10 Minimum allowed global component density name groupName required A name is required for any non-unique nodes solutionChangeScalingFactor real64 0.5 Damping factor for solution change targets +targetFlowCFL real64 -1 Target CFL condition `CFL condition `_when computing the next timestep. targetPhaseVolFractionChangeInTimeStep real64 0.2 Target (absolute) change in phase volume fraction in a time step targetRegions groupNameRef_array required Allowable regions that the solver may be applied to. Note that this does not indicate that the solver will be applied to these regions, only that allocation will occur such that the solver may be applied to these regions. The decision about what regions this solver will beapplied to rests in the EventManager. targetRelativePressureChangeInTimeStep real64 0.2 Target (relative) change in pressure in a time step (expected value between 0 and 1) diff --git a/src/coreComponents/schema/schema.xsd b/src/coreComponents/schema/schema.xsd index 78b4b40157d..a13aa839d04 100644 --- a/src/coreComponents/schema/schema.xsd +++ b/src/coreComponents/schema/schema.xsd @@ -2253,6 +2253,8 @@ the relative residual norm satisfies: + + @@ -2315,6 +2317,8 @@ the relative residual norm satisfies: + + From b65466dd337a37e234adcdaaeab9f3f3062081bd Mon Sep 17 00:00:00 2001 From: sohailwaziri <90070246+sohailwaziri@users.noreply.github.com> Date: Tue, 19 Dec 2023 14:43:14 -0800 Subject: [PATCH 13/40] Aitken relaxation on averageMeanTotalStressIncrement (#2628) Aitken relaxation is implemented for poromechanics. The acceleration parameter is `averageMeanTotalStressIncrement`. Porosity update formulation (in `BiotPorosity.hpp`) is changed from effective stress formulation to total stress formulation. --- .../smallEggModel/smallEggModel.xml | 296 +++++++++++ .../validationCase/validationCase.xml | 503 ++++++++++++++++++ integratedTests | 2 +- .../codingUtilities/Utilities.hpp | 50 ++ .../constitutive/solid/CoupledSolidBase.hpp | 42 +- .../constitutive/solid/PorousSolid.hpp | 8 +- .../solid/porosity/BiotPorosity.cpp | 14 +- .../solid/porosity/BiotPorosity.hpp | 78 +-- .../solid/porosity/PorosityBase.hpp | 22 +- .../solid/porosity/PorosityFields.hpp | 12 +- .../physicsSolvers/CMakeLists.txt | 1 + .../NonlinearSolverParameters.cpp | 5 + .../NonlinearSolverParameters.hpp | 17 + .../multiphysics/CoupledSolver.hpp | 50 +- .../multiphysics/MultiphasePoromechanics.cpp | 21 +- .../multiphysics/MultiphasePoromechanics.hpp | 24 +- .../PoromechanicsInitialization.cpp | 1 + .../multiphysics/PoromechanicsSolver.hpp | 215 ++++++++ .../multiphysics/SinglePhasePoromechanics.cpp | 20 +- .../multiphysics/SinglePhasePoromechanics.hpp | 24 +- .../schema/docs/BiotPorosity_other.rst | 30 +- .../schema/docs/NonlinearSolverParameters.rst | 1 + src/coreComponents/schema/schema.xsd | 7 + src/coreComponents/schema/schema.xsd.other | 8 +- 24 files changed, 1288 insertions(+), 163 deletions(-) create mode 100755 inputFiles/poromechanics/nonlinearAcceleration/smallEggModel/smallEggModel.xml create mode 100755 inputFiles/poromechanics/nonlinearAcceleration/validationCase/validationCase.xml create mode 100644 src/coreComponents/physicsSolvers/multiphysics/PoromechanicsSolver.hpp diff --git a/inputFiles/poromechanics/nonlinearAcceleration/smallEggModel/smallEggModel.xml b/inputFiles/poromechanics/nonlinearAcceleration/smallEggModel/smallEggModel.xml new file mode 100755 index 00000000000..0a6d34d930b --- /dev/null +++ b/inputFiles/poromechanics/nonlinearAcceleration/smallEggModel/smallEggModel.xml @@ -0,0 +1,296 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inputFiles/poromechanics/nonlinearAcceleration/validationCase/validationCase.xml b/inputFiles/poromechanics/nonlinearAcceleration/validationCase/validationCase.xml new file mode 100755 index 00000000000..af9a6c20b5c --- /dev/null +++ b/inputFiles/poromechanics/nonlinearAcceleration/validationCase/validationCase.xml @@ -0,0 +1,503 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/integratedTests b/integratedTests index e3fd6bee8c9..5f5644774f2 160000 --- a/integratedTests +++ b/integratedTests @@ -1 +1 @@ -Subproject commit e3fd6bee8c9dfd2ccb893278f369abf49b41a845 +Subproject commit 5f5644774f2c3b4b489f1fa23b7f8e5597b5e5ea diff --git a/src/coreComponents/codingUtilities/Utilities.hpp b/src/coreComponents/codingUtilities/Utilities.hpp index c0ee8430b04..b6e39ba6667 100644 --- a/src/coreComponents/codingUtilities/Utilities.hpp +++ b/src/coreComponents/codingUtilities/Utilities.hpp @@ -22,6 +22,7 @@ #include "codingUtilities/StringUtilities.hpp" #include "common/DataTypes.hpp" #include "LvArray/src/limits.hpp" +#include "common/GEOS_RAJA_Interface.hpp" namespace geos { @@ -414,6 +415,55 @@ struct BitFlags }; +// Temporary functions (axpy, scale, dot) used in Aitken's acceleration. Will be removed once the nonlinear +// acceleration implementation scheme will use LAI vectors. See issue #2891 +// (https://github.com/GEOS-DEV/GEOS/issues/2891) + +template< typename VECTOR_TYPE, typename SCALAR_TYPE > +VECTOR_TYPE axpy( VECTOR_TYPE const & vec1, + VECTOR_TYPE const & vec2, + SCALAR_TYPE const alpha ) +{ + GEOS_ASSERT( vec1.size() == vec2.size() ); + const localIndex N = vec1.size(); + VECTOR_TYPE result( N ); + RAJA::forall< parallelHostPolicy >( RAJA::TypedRangeSegment< localIndex >( 0, N ), + [&] GEOS_HOST ( localIndex const i ) + { + result[i] = vec1[i] + alpha * vec2[i]; + } ); + return result; +} + +template< typename VECTOR_TYPE, typename SCALAR_TYPE > +VECTOR_TYPE scale( VECTOR_TYPE const & vec, + SCALAR_TYPE const scalarMult ) +{ + const localIndex N = vec.size(); + VECTOR_TYPE result( N ); + RAJA::forall< parallelHostPolicy >( RAJA::TypedRangeSegment< localIndex >( 0, N ), + [&] GEOS_HOST ( localIndex const i ) + { + result[i] = scalarMult * vec[i]; + } ); + return result; +} + +template< typename VECTOR_TYPE > +real64 dot( VECTOR_TYPE const & vec1, + VECTOR_TYPE const & vec2 ) +{ + GEOS_ASSERT( vec1.size() == vec2.size()); + RAJA::ReduceSum< parallelHostReduce, real64 > result( 0.0 ); + const localIndex N = vec1.size(); + RAJA::forall< parallelHostPolicy >( RAJA::TypedRangeSegment< localIndex >( 0, N ), + [&] GEOS_HOST ( localIndex const i ) + { + result += vec1[i] * vec2[i]; + } ); + return result.get(); +} + } // namespace geos #endif /* GEOS_CODINGUTILITIES_UTILITIES_H_ */ diff --git a/src/coreComponents/constitutive/solid/CoupledSolidBase.hpp b/src/coreComponents/constitutive/solid/CoupledSolidBase.hpp index 28ef6d3be21..35306907a4e 100644 --- a/src/coreComponents/constitutive/solid/CoupledSolidBase.hpp +++ b/src/coreComponents/constitutive/solid/CoupledSolidBase.hpp @@ -180,21 +180,22 @@ class CoupledSolidBase : public ConstitutiveBase } /** - * @brief Const/non-mutable accessor for the mean stress increment at the previous sequential iteration + * @brief Const/non-mutable accessor for the mean total stress increment + * (with respect to the previous converged time level) at the previous sequential iteration * @return Accessor */ - arrayView2d< real64 const > const getMeanEffectiveStressIncrement_k() const + arrayView2d< real64 const > const getMeanTotalStressIncrement_k() const { - return getBasePorosityModel().getMeanEffectiveStressIncrement_k(); + return getBasePorosityModel().getMeanTotalStressIncrement_k(); } /** - * @brief Non-const accessor for the mean stress increment at the previous sequential iteration + * @brief Non-const accessor for the mean total stress increment at the previous sequential iteration * @return Accessor */ - arrayView1d< real64 > const getAverageMeanEffectiveStressIncrement_k() + arrayView1d< real64 > const getAverageMeanTotalStressIncrement_k() { - return getBasePorosityModel().getAverageMeanEffectiveStressIncrement_k(); + return getBasePorosityModel().getAverageMeanTotalStressIncrement_k(); } @@ -232,6 +233,20 @@ class CoupledSolidBase : public ConstitutiveBase SolidInternalEnergy const & getSolidInternalEnergyModel() const { return this->getParent().template getGroup< SolidInternalEnergy >( m_solidInternalEnergyModelName ); } + /** + * @brief get a PorosityBase constant reference to the porosity model + * return a constant PorosityBase reference to the porosity model + */ + PorosityBase const & getBasePorosityModel() const + { return this->getParent().template getGroup< PorosityBase >( m_porosityModelName ); } + + /** + * @brief get a PorosityBase reference to the porosity model + * return a PorosityBase reference to the porosity model + */ + PorosityBase & getBasePorosityModel() + { return this->getParent().template getGroup< PorosityBase >( m_porosityModelName ); } + protected: /// the name of the solid model @@ -248,21 +263,6 @@ class CoupledSolidBase : public ConstitutiveBase private: - /** - * @brief get a PorosityBase constant reference to the porosity model - * return a constant PorosityBase reference to the porosity model - */ - PorosityBase const & getBasePorosityModel() const - { return this->getParent().template getGroup< PorosityBase >( m_porosityModelName ); } - - /** - * @brief get a PorosityBase reference to the porosity model - * return a PorosityBase reference to the porosity model - */ - PorosityBase & getBasePorosityModel() - { return this->getParent().template getGroup< PorosityBase >( m_porosityModelName ); } - - /** * @brief get a Permeability base constant reference to the permeability model * return a constant PermeabilityBase reference to the permeability model diff --git a/src/coreComponents/constitutive/solid/PorousSolid.hpp b/src/coreComponents/constitutive/solid/PorousSolid.hpp index 3c035cc49f4..4d6ca176a9b 100644 --- a/src/coreComponents/constitutive/solid/PorousSolid.hpp +++ b/src/coreComponents/constitutive/solid/PorousSolid.hpp @@ -147,11 +147,15 @@ class PorousSolidUpdates : public CoupledSolidUpdates< SOLID_TYPE, BiotPorosity, dTotalStress_dTemperature, // To pass something here stiffness ); - // Compute effective stress increment for the porosity + // Compute total stress increment for the porosity update GEOS_UNUSED_VAR( pressure_n, temperature_n ); real64 const bulkModulus = m_solidUpdate.getBulkModulus( k ); real64 const meanEffectiveStressIncrement = bulkModulus * ( strainIncrement[0] + strainIncrement[1] + strainIncrement[2] ); - m_porosityUpdate.updateMeanEffectiveStressIncrement( k, q, meanEffectiveStressIncrement ); + real64 const biotCoefficient = m_porosityUpdate.getBiotCoefficient( k ); + real64 const thermalExpansionCoefficient = m_solidUpdate.getThermalExpansionCoefficient( k ); + real64 const meanTotalStressIncrement = meanEffectiveStressIncrement - biotCoefficient * ( pressure - pressure_n ) + - 3 * thermalExpansionCoefficient * bulkModulus * ( temperature - temperature_n ); + m_porosityUpdate.updateMeanTotalStressIncrement( k, q, meanTotalStressIncrement ); } /** diff --git a/src/coreComponents/constitutive/solid/porosity/BiotPorosity.cpp b/src/coreComponents/constitutive/solid/porosity/BiotPorosity.cpp index 1575a666a92..1494de982b9 100644 --- a/src/coreComponents/constitutive/solid/porosity/BiotPorosity.cpp +++ b/src/coreComponents/constitutive/solid/porosity/BiotPorosity.cpp @@ -46,9 +46,9 @@ BiotPorosity::BiotPorosity( string const & name, Group * const parent ): registerField( fields::porosity::thermalExpansionCoefficient{}, &m_thermalExpansionCoefficient ); - registerField( fields::porosity::meanEffectiveStressIncrement_k{}, &m_meanEffectiveStressIncrement_k ); + registerField( fields::porosity::meanTotalStressIncrement_k{}, &m_meanTotalStressIncrement_k ); - registerField( fields::porosity::averageMeanEffectiveStressIncrement_k{}, &m_averageMeanEffectiveStressIncrement_k ); + registerField( fields::porosity::averageMeanTotalStressIncrement_k{}, &m_averageMeanTotalStressIncrement_k ); registerWrapper( viewKeyStruct::solidBulkModulusString(), &m_bulkModulus ). setApplyDefaultValue( 1e-6 ). @@ -60,7 +60,7 @@ void BiotPorosity::allocateConstitutiveData( dataRepository::Group & parent, { PorosityBase::allocateConstitutiveData( parent, numConstitutivePointsPerParentIndex ); - m_meanEffectiveStressIncrement_k.resize( 0, numConstitutivePointsPerParentIndex ); + m_meanTotalStressIncrement_k.resize( 0, numConstitutivePointsPerParentIndex ); } void BiotPorosity::postProcessInput() @@ -95,15 +95,15 @@ void BiotPorosity::initializeState() const void BiotPorosity::saveConvergedState() const { PorosityBase::saveConvergedState(); - m_meanEffectiveStressIncrement_k.zero(); - m_averageMeanEffectiveStressIncrement_k.zero(); + m_meanTotalStressIncrement_k.zero(); + m_averageMeanTotalStressIncrement_k.zero(); } void BiotPorosity::ignoreConvergedState() const { PorosityBase::ignoreConvergedState(); - m_meanEffectiveStressIncrement_k.zero(); - m_averageMeanEffectiveStressIncrement_k.zero(); + m_meanTotalStressIncrement_k.zero(); + m_averageMeanTotalStressIncrement_k.zero(); } diff --git a/src/coreComponents/constitutive/solid/porosity/BiotPorosity.hpp b/src/coreComponents/constitutive/solid/porosity/BiotPorosity.hpp index d7a0280fb9c..918efea6b67 100644 --- a/src/coreComponents/constitutive/solid/porosity/BiotPorosity.hpp +++ b/src/coreComponents/constitutive/solid/porosity/BiotPorosity.hpp @@ -52,8 +52,8 @@ class BiotPorosityUpdates : public PorosityBaseUpdates arrayView1d< real64 > const & referencePorosity, arrayView1d< real64 > const & biotCoefficient, arrayView1d< real64 > const & thermalExpansionCoefficient, - arrayView2d< real64 > const & meanEffectiveStressIncrement_k, - arrayView1d< real64 const > const & averageMeanEffectiveStressIncrement_k, + arrayView2d< real64 > const & meanTotalStressIncrement_k, + arrayView1d< real64 > const & averageMeanTotalStressIncrement_k, arrayView1d< real64 > const & bulkModulus, real64 const & grainBulkModulus ): PorosityBaseUpdates( newPorosity, porosity_n, @@ -65,8 +65,8 @@ class BiotPorosityUpdates : public PorosityBaseUpdates m_thermalExpansionCoefficient( thermalExpansionCoefficient ), m_biotCoefficient( biotCoefficient ), m_bulkModulus( bulkModulus ), - m_meanEffectiveStressIncrement_k( meanEffectiveStressIncrement_k ), - m_averageMeanEffectiveStressIncrement_k( averageMeanEffectiveStressIncrement_k ) + m_meanTotalStressIncrement_k( meanTotalStressIncrement_k ), + m_averageMeanTotalStressIncrement_k( averageMeanTotalStressIncrement_k ) {} GEOS_HOST_DEVICE @@ -105,9 +105,7 @@ class BiotPorosityUpdates : public PorosityBaseUpdates GEOS_HOST_DEVICE void computePorosity( real64 const & deltaPressureFromBeginningOfTimeStep, - real64 const & deltaPressureFromLastIteration, real64 const & deltaTemperatureFromBeginningOfTimeStep, - real64 const & deltaTemperatureFromLastIteration, real64 const & porosity_n, real64 const & referencePorosity, real64 & porosity, @@ -115,7 +113,7 @@ class BiotPorosityUpdates : public PorosityBaseUpdates real64 & dPorosity_dTemperature, real64 const & biotCoefficient, real64 const & thermalExpansionCoefficient, - real64 const & meanEffectiveStressIncrement_k, + real64 const & averageMeanTotalStressIncrement_k, real64 const & bulkModulus ) const { real64 const biotSkeletonModulusInverse = (biotCoefficient - referencePorosity) / m_grainBulkModulus; @@ -123,17 +121,18 @@ class BiotPorosityUpdates : public PorosityBaseUpdates real64 const fixedStressPressureCoefficient = biotCoefficient * biotCoefficient / bulkModulus; real64 const fixedStressTemperatureCoefficient = 3 * biotCoefficient * thermalExpansionCoefficient; + // total stress formulation for porosity update porosity = porosity_n - + biotCoefficient * meanEffectiveStressIncrement_k / bulkModulus // change due to stress increment (at the previous - // sequential iteration) + + biotCoefficient * averageMeanTotalStressIncrement_k / bulkModulus // change due to stress increment (at the previous + // sequential iteration) + biotSkeletonModulusInverse * deltaPressureFromBeginningOfTimeStep // change due to pressure increment - porosityThermalExpansion * deltaTemperatureFromBeginningOfTimeStep; // change due to temperature increment dPorosity_dPressure = biotSkeletonModulusInverse; dPorosity_dTemperature = -porosityThermalExpansion; // Fixed-stress part - porosity += fixedStressPressureCoefficient * deltaPressureFromLastIteration // fixed-stress pressure term - + fixedStressTemperatureCoefficient * deltaTemperatureFromLastIteration; // fixed-stress temperature term + porosity += fixedStressPressureCoefficient * deltaPressureFromBeginningOfTimeStep // fixed-stress pressure term + + fixedStressTemperatureCoefficient * deltaTemperatureFromBeginningOfTimeStep; // fixed-stress temperature term dPorosity_dPressure += fixedStressPressureCoefficient; dPorosity_dTemperature += fixedStressTemperatureCoefficient; } @@ -142,21 +141,17 @@ class BiotPorosityUpdates : public PorosityBaseUpdates virtual void updateFromPressureAndTemperature( localIndex const k, localIndex const q, real64 const & pressure, // current - real64 const & pressure_k, // last iteration (for sequential) + real64 const & GEOS_UNUSED_PARAM( pressure_k ), // last iteration (for sequential) real64 const & pressure_n, // last time step real64 const & temperature, - real64 const & temperature_k, + real64 const & GEOS_UNUSED_PARAM( temperature_k ), real64 const & temperature_n ) const override final { real64 const deltaPressureFromBeginningOfTimeStep = pressure - pressure_n; - real64 const deltaPressureFromLastIteration = pressure - pressure_k; real64 const deltaTemperatureFromBeginningOfTimeStep = temperature - temperature_n; - real64 const deltaTemperatureFromLastIteration = temperature - temperature_k; computePorosity( deltaPressureFromBeginningOfTimeStep, - deltaPressureFromLastIteration, deltaTemperatureFromBeginningOfTimeStep, - deltaTemperatureFromLastIteration, m_porosity_n[k][q], m_referencePorosity[k], m_newPorosity[k][q], @@ -164,7 +159,7 @@ class BiotPorosityUpdates : public PorosityBaseUpdates m_dPorosity_dTemperature[k][q], m_biotCoefficient[k], m_thermalExpansionCoefficient[k], - m_averageMeanEffectiveStressIncrement_k[k], + m_averageMeanTotalStressIncrement_k[k], m_bulkModulus[k] ); } @@ -177,11 +172,11 @@ class BiotPorosityUpdates : public PorosityBaseUpdates } GEOS_HOST_DEVICE - void updateMeanEffectiveStressIncrement( localIndex const k, - localIndex const q, - real64 const & meanEffectiveStressIncrement ) const + void updateMeanTotalStressIncrement( localIndex const k, + localIndex const q, + real64 const & meanTotalStressIncrement ) const { - m_meanEffectiveStressIncrement_k[k][q] = meanEffectiveStressIncrement; + m_meanTotalStressIncrement_k[k][q] = meanTotalStressIncrement; } protected: @@ -198,11 +193,11 @@ class BiotPorosityUpdates : public PorosityBaseUpdates /// View on the bulk modulus (updated by PorousSolid) arrayView1d< real64 > const m_bulkModulus; - /// View on the mean stress increment at quadrature points (updated by PorousSolid) - arrayView2d< real64 > const m_meanEffectiveStressIncrement_k; + /// View on the mean total stress increment at quadrature points (updated by PorousSolid) + arrayView2d< real64 > const m_meanTotalStressIncrement_k; - /// View on the average mean stress increment - arrayView1d< real64 const > const m_averageMeanEffectiveStressIncrement_k; + /// View on the average mean total stress increment + arrayView1d< real64 > const m_averageMeanTotalStressIncrement_k; }; @@ -222,9 +217,9 @@ class BiotPorosity : public PorosityBase { static constexpr char const *grainBulkModulusString() { return "grainBulkModulus"; } - static constexpr char const *meanEffectiveStressIncrementString() { return "meanEffectiveStressIncrement"; } + static constexpr char const *meanTotalStressIncrementString() { return "meanTotalStressIncrement"; } - static constexpr char const *averageMeanEffectiveStressIncrementString() { return "averageMeanEffectiveStressIncrement"; } + static constexpr char const *averageMeanTotalStressIncrementString() { return "averageMeanTotalStressIncrement"; } static constexpr char const *solidBulkModulusString() { return "solidBulkModulus"; } @@ -242,14 +237,21 @@ class BiotPorosity : public PorosityBase return m_biotCoefficient.toViewConst(); } - virtual arrayView1d< real64 > const getAverageMeanEffectiveStressIncrement_k() override final + virtual arrayView1d< real64 > const getAverageMeanTotalStressIncrement_k() override final { - return m_averageMeanEffectiveStressIncrement_k.toView(); + return m_averageMeanTotalStressIncrement_k.toView(); } - virtual arrayView2d< real64 const > const getMeanEffectiveStressIncrement_k() const override final + virtual arrayView2d< real64 const > const getMeanTotalStressIncrement_k() const override final { - return m_meanEffectiveStressIncrement_k.toViewConst(); + return m_meanTotalStressIncrement_k.toViewConst(); + } + + GEOS_HOST_DEVICE + void updateAverageMeanTotalStressIncrement( localIndex const k, + real64 const & averageMeanTotalStressIncrement ) const + { + m_averageMeanTotalStressIncrement_k[ k ] = averageMeanTotalStressIncrement; } using KernelWrapper = BiotPorosityUpdates; @@ -268,8 +270,8 @@ class BiotPorosity : public PorosityBase m_referencePorosity, m_biotCoefficient, m_thermalExpansionCoefficient, - m_meanEffectiveStressIncrement_k, - m_averageMeanEffectiveStressIncrement_k, + m_meanTotalStressIncrement_k, + m_averageMeanTotalStressIncrement_k, m_bulkModulus, m_grainBulkModulus ); } @@ -290,11 +292,11 @@ class BiotPorosity : public PorosityBase /// Bulk modulus (updated in the update class, not read in input) array1d< real64 > m_bulkModulus; - /// Mean stress increment (updated in the update class, not read in input) - array2d< real64 > m_meanEffectiveStressIncrement_k; + /// Mean total stress increment (updated in the update class, not read in input) + array2d< real64 > m_meanTotalStressIncrement_k; - /// Average mean stress increment (not read in input) - array1d< real64 > m_averageMeanEffectiveStressIncrement_k; + /// Average mean total stress increment (not read in input) + array1d< real64 > m_averageMeanTotalStressIncrement_k; /// Grain bulk modulus (read from XML) real64 m_grainBulkModulus; diff --git a/src/coreComponents/constitutive/solid/porosity/PorosityBase.hpp b/src/coreComponents/constitutive/solid/porosity/PorosityBase.hpp index 4a8ca5ea528..4485e30c089 100644 --- a/src/coreComponents/constitutive/solid/porosity/PorosityBase.hpp +++ b/src/coreComponents/constitutive/solid/porosity/PorosityBase.hpp @@ -266,29 +266,39 @@ class PorosityBase : public ConstitutiveBase } /** - * @brief Const/non-mutable accessor for the mean stress increment at the previous sequential iteration + * @brief Const/non-mutable accessor for the mean total stress increment at the previous sequential iteration * @return Accessor */ - virtual arrayView2d< real64 const > const getMeanEffectiveStressIncrement_k() const + virtual arrayView2d< real64 const > const getMeanTotalStressIncrement_k() const { - GEOS_ERROR( "getMeanEffectiveStressIncrement_k() not implemented for this model" ); + GEOS_ERROR( "getMeanTotalStressIncrement_k() not implemented for this model" ); array2d< real64 > out; return out.toViewConst(); } /** - * @brief Non-const accessor for the mean stress increment at the previous sequential iteration + * @brief Non-const accessor for the average mean total stress increment at the previous sequential iteration * @return Accessor */ - virtual arrayView1d< real64 > const getAverageMeanEffectiveStressIncrement_k() + virtual arrayView1d< real64 > const getAverageMeanTotalStressIncrement_k() { - GEOS_ERROR( "getAverageMeanEffectiveStressIncrement_k() not implemented for this model" ); + GEOS_ERROR( "getAverageMeanTotalStressIncrement_k() not implemented for this model" ); array1d< real64 > out; return out.toView(); } + GEOS_HOST_DEVICE + inline + void savePorosity( localIndex const k, + localIndex const q, + real64 const & porosity, + real64 const & dPorosity_dPressure ) const + { + m_newPorosity[k][q] = porosity; + m_dPorosity_dPressure[k][q] = dPorosity_dPressure; + } using KernelWrapper = PorosityBaseUpdates; diff --git a/src/coreComponents/constitutive/solid/porosity/PorosityFields.hpp b/src/coreComponents/constitutive/solid/porosity/PorosityFields.hpp index 6bfb2226e56..f36a43b440f 100644 --- a/src/coreComponents/constitutive/solid/porosity/PorosityFields.hpp +++ b/src/coreComponents/constitutive/solid/porosity/PorosityFields.hpp @@ -94,21 +94,21 @@ DECLARE_FIELD( thermalExpansionCoefficient, WRITE_AND_READ, "Thermal expansion coefficient" ); -DECLARE_FIELD( meanEffectiveStressIncrement_k, - "meanEffectiveStressIncrement_k", +DECLARE_FIELD( meanTotalStressIncrement_k, + "meanTotalStressIncrement_k", array2d< real64 >, 0, NOPLOT, NO_WRITE, - "Mean effective stress increment at quadrature points at the previous sequential iteration" ); + "Mean total stress increment at quadrature points at the previous sequential iteration" ); -DECLARE_FIELD( averageMeanEffectiveStressIncrement_k, - "averageMeanEffectiveStressIncrement_k", +DECLARE_FIELD( averageMeanTotalStressIncrement_k, + "averageMeanTotalStressIncrement_k", array1d< real64 >, 0, NOPLOT, NO_WRITE, - "Mean effective stress increment averaged over quadrature points at the previous sequential iteration" ); + "Mean total stress increment averaged over quadrature points at the previous sequential iteration" ); diff --git a/src/coreComponents/physicsSolvers/CMakeLists.txt b/src/coreComponents/physicsSolvers/CMakeLists.txt index 547cce5990c..d831037067e 100644 --- a/src/coreComponents/physicsSolvers/CMakeLists.txt +++ b/src/coreComponents/physicsSolvers/CMakeLists.txt @@ -66,6 +66,7 @@ set( physicsSolvers_headers multiphysics/CompositionalMultiphaseReservoirAndWells.hpp multiphysics/CoupledReservoirAndWellsBase.hpp multiphysics/CoupledSolver.hpp + multiphysics/PoromechanicsSolver.hpp multiphysics/FlowProppantTransportSolver.hpp multiphysics/HydrofractureSolver.hpp multiphysics/HydrofractureSolverKernels.hpp diff --git a/src/coreComponents/physicsSolvers/NonlinearSolverParameters.cpp b/src/coreComponents/physicsSolvers/NonlinearSolverParameters.cpp index 96ced600bbd..af316a23062 100644 --- a/src/coreComponents/physicsSolvers/NonlinearSolverParameters.cpp +++ b/src/coreComponents/physicsSolvers/NonlinearSolverParameters.cpp @@ -154,6 +154,11 @@ NonlinearSolverParameters::NonlinearSolverParameters( string const & name, setApplyDefaultValue( 0 ). setDescription( "Flag to decide whether to iterate between sequentially coupled solvers or not." ); + this->registerWrapper( viewKeysStruct::nonlinearAccelerationTypeString(), &m_nonlinearAccelerationType ). + setApplyDefaultValue( NonlinearAccelerationType::None ). + setInputFlag( dataRepository::InputFlags::OPTIONAL ). + setDescription( "Nonlinear acceleration type for sequential solver." ); + } void NonlinearSolverParameters::postProcessInput() diff --git a/src/coreComponents/physicsSolvers/NonlinearSolverParameters.hpp b/src/coreComponents/physicsSolvers/NonlinearSolverParameters.hpp index 99e5dd52634..2a41b03d7d4 100644 --- a/src/coreComponents/physicsSolvers/NonlinearSolverParameters.hpp +++ b/src/coreComponents/physicsSolvers/NonlinearSolverParameters.hpp @@ -128,6 +128,7 @@ class NonlinearSolverParameters : public dataRepository::Group static constexpr char const * couplingTypeString() { return "couplingType"; } static constexpr char const * sequentialConvergenceCriterionString() { return "sequentialConvergenceCriterion"; } static constexpr char const * subcyclingOptionString() { return "subcycling"; } + static constexpr char const * nonlinearAccelerationTypeString() { return "nonlinearAccelerationType"; } } viewKeys; /** @@ -167,6 +168,15 @@ class NonlinearSolverParameters : public dataRepository::Group NumberOfNonlinearIterations ///< convergence achieved when the subproblems convergence is achieved in less than minNewtonIteration }; + /** + * @brief Nonlinear acceleration type + */ + enum class NonlinearAccelerationType : integer + { + None, ///< no acceleration + Aitken ///< Aitken acceleration + }; + /** * @brief Calculates the upper limit for the number of iterations to allow a * decrease to the next time step. @@ -305,6 +315,9 @@ class NonlinearSolverParameters : public dataRepository::Group /// Flag to specify whether subcycling is allowed or not in sequential schemes integer m_subcyclingOption; + /// Type of nonlinear acceleration for sequential solver + NonlinearAccelerationType m_nonlinearAccelerationType; + /// Value used to make sure that residual normalizers are not too small when computing residual norm real64 m_minNormalizer = 1e-12; }; @@ -326,6 +339,10 @@ ENUM_STRINGS( NonlinearSolverParameters::SequentialConvergenceCriterion, "ResidualNorm", "NumberOfNonlinearIterations" ); +ENUM_STRINGS( NonlinearSolverParameters::NonlinearAccelerationType, + "None", + "Aitken" ); + } /* namespace geos */ #endif /* GEOS_PHYSICSSOLVERS_NONLINEARSOLVERPARAMETERS_HPP_ */ diff --git a/src/coreComponents/physicsSolvers/multiphysics/CoupledSolver.hpp b/src/coreComponents/physicsSolvers/multiphysics/CoupledSolver.hpp index 4c8b254ee0c..0ae7961357f 100644 --- a/src/coreComponents/physicsSolvers/multiphysics/CoupledSolver.hpp +++ b/src/coreComponents/physicsSolvers/multiphysics/CoupledSolver.hpp @@ -400,24 +400,24 @@ class CoupledSolver : public SolverBase integer & iter = solverParams.m_numNewtonIterations; iter = 0; bool isConverged = false; + + // Reset the states of all solvers if any of them had to restart + forEachArgInTuple( m_solvers, [&]( auto & solver, auto ) + { + solver->resetStateToBeginningOfStep( domain ); + solver->getSolverStatistics().initializeTimeStepStatistics(); // initialize counters for subsolvers + } ); + resetStateToBeginningOfStep( domain ); + /// Sequential coupling loop while( iter < solverParams.m_maxIterNewton ) { - if( iter == 0 ) - { - // Reset the states of all solvers if any of them had to restart - forEachArgInTuple( m_solvers, [&]( auto & solver, auto ) - { - solver->resetStateToBeginningOfStep( domain ); - solver->getSolverStatistics().initializeTimeStepStatistics(); // initialize counters for subsolvers - } ); - resetStateToBeginningOfStep( domain ); - } - // Increment the solver statistics for reporting purposes // Pass a "0" as argument (0 linear iteration) to skip the output of linear iteration stats at the end m_solverStatistics.logNonlinearIteration( 0 ); + startSequentialIteration( iter, domain ); + // Solve the subproblems nonlinearly forEachArgInTuple( m_solvers, [&]( auto & solver, auto idx ) { @@ -451,6 +451,10 @@ class CoupledSolver : public SolverBase } ); break; } + else + { + finishSequentialIteration( iter, domain ); + } // Add convergence check: ++iter; } @@ -570,6 +574,18 @@ class CoupledSolver : public SolverBase NonlinearSolverParameters::viewKeysStruct::lineSearchActionString(), EnumStrings< NonlinearSolverParameters::LineSearchAction >::toString( NonlinearSolverParameters::LineSearchAction::None ) ), InputError ); + + if( m_nonlinearSolverParameters.m_nonlinearAccelerationType != NonlinearSolverParameters::NonlinearAccelerationType::None ) + validateNonlinearAcceleration(); + } + + virtual void validateNonlinearAcceleration() + { + GEOS_THROW ( GEOS_FMT( "{}: Nonlinear acceleration {} is not supported by {} solver '{}'", + getWrapperDataContext( NonlinearSolverParameters::viewKeysStruct::nonlinearAccelerationTypeString() ), + EnumStrings< NonlinearSolverParameters::NonlinearAccelerationType >::toString( m_nonlinearSolverParameters.m_nonlinearAccelerationType ), + getCatalogName(), getName()), + InputError ); } void @@ -581,6 +597,18 @@ class CoupledSolver : public SolverBase } ); } + virtual void startSequentialIteration( integer const & iter, + DomainPartition & domain ) + { + GEOS_UNUSED_VAR( iter, domain ); + } + + virtual void finishSequentialIteration( integer const & iter, + DomainPartition & domain ) + { + GEOS_UNUSED_VAR( iter, domain ); + } + protected: /// Pointers of the single-physics solvers diff --git a/src/coreComponents/physicsSolvers/multiphysics/MultiphasePoromechanics.cpp b/src/coreComponents/physicsSolvers/multiphysics/MultiphasePoromechanics.cpp index f1a32b53665..2a663a93c48 100644 --- a/src/coreComponents/physicsSolvers/multiphysics/MultiphasePoromechanics.cpp +++ b/src/coreComponents/physicsSolvers/multiphysics/MultiphasePoromechanics.cpp @@ -25,6 +25,7 @@ #include "mesh/utilities/AverageOverQuadraturePointsKernel.hpp" #include "physicsSolvers/fluidFlow/CompositionalMultiphaseBase.hpp" #include "physicsSolvers/fluidFlow/FlowSolverBaseFields.hpp" +#include "physicsSolvers/multiphysics/CompositionalMultiphaseReservoirAndWells.hpp" #include "physicsSolvers/multiphysics/poromechanicsKernels/MultiphasePoromechanics.hpp" #include "physicsSolvers/multiphysics/poromechanicsKernels/ThermalMultiphasePoromechanics.hpp" #include "physicsSolvers/solidMechanics/SolidMechanicsFields.hpp" @@ -497,7 +498,7 @@ void MultiphasePoromechanics< FLOW_SOLVER >::mapSolutionBetweenSolvers( DomainPa GEOS_MARK_FUNCTION; /// After the flow solver - if( solverType == static_cast< integer >( SolverType::Flow ) ) + if( solverType == static_cast< integer >( Base::SolverType::Flow ) ) { // save pressure and temperature at the end of this iteration flowSolver()->saveIterationState( domain ); @@ -519,10 +520,10 @@ void MultiphasePoromechanics< FLOW_SOLVER >::mapSolutionBetweenSolvers( DomainPa } /// After the solid mechanics solver - if( solverType == static_cast< integer >( SolverType::SolidMechanics ) ) + if( solverType == static_cast< integer >( Base::SolverType::SolidMechanics ) ) { - // compute the average of the mean stress increment over quadrature points - averageMeanStressIncrement( domain ); + // compute the average of the mean total stress increment over quadrature points + averageMeanTotalStressIncrement( domain ); this->template forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const &, MeshLevel & mesh, @@ -538,6 +539,8 @@ void MultiphasePoromechanics< FLOW_SOLVER >::mapSolutionBetweenSolvers( DomainPa } ); } ); } + // call base method (needed to perform nonlinear acceleration) + Base::mapSolutionBetweenSolvers( domain, solverType ); } template< typename FLOW_SOLVER > @@ -561,7 +564,7 @@ void MultiphasePoromechanics< FLOW_SOLVER >::updateBulkDensity( ElementSubRegion } template< typename FLOW_SOLVER > -void MultiphasePoromechanics< FLOW_SOLVER >::averageMeanStressIncrement( DomainPartition & domain ) +void MultiphasePoromechanics< FLOW_SOLVER >::averageMeanTotalStressIncrement( DomainPartition & domain ) { this->template forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const &, MeshLevel & mesh, @@ -574,8 +577,8 @@ void MultiphasePoromechanics< FLOW_SOLVER >::averageMeanStressIncrement( DomainP string const solidName = subRegion.template getReference< string >( viewKeyStruct::porousMaterialNamesString() ); CoupledSolidBase & solid = this->template getConstitutiveModel< CoupledSolidBase >( subRegion, solidName ); - arrayView2d< real64 const > const meanStressIncrement_k = solid.getMeanEffectiveStressIncrement_k(); - arrayView1d< real64 > const averageMeanStressIncrement_k = solid.getAverageMeanEffectiveStressIncrement_k(); + arrayView2d< real64 const > const meanTotalStressIncrement_k = solid.getMeanTotalStressIncrement_k(); + arrayView1d< real64 > const averageMeanTotalStressIncrement_k = solid.getAverageMeanTotalStressIncrement_k(); finiteElement::FiniteElementBase & subRegionFE = subRegion.template getReference< finiteElement::FiniteElementBase >( solidMechanicsSolver()->getDiscretizationName() ); @@ -595,8 +598,8 @@ void MultiphasePoromechanics< FLOW_SOLVER >::averageMeanStressIncrement( DomainP mesh.getFaceManager(), subRegion, finiteElement, - meanStressIncrement_k, - averageMeanStressIncrement_k ); + meanTotalStressIncrement_k, + averageMeanTotalStressIncrement_k ); } ); } ); } ); diff --git a/src/coreComponents/physicsSolvers/multiphysics/MultiphasePoromechanics.hpp b/src/coreComponents/physicsSolvers/multiphysics/MultiphasePoromechanics.hpp index 6dff80a74c4..1aad27cab3e 100644 --- a/src/coreComponents/physicsSolvers/multiphysics/MultiphasePoromechanics.hpp +++ b/src/coreComponents/physicsSolvers/multiphysics/MultiphasePoromechanics.hpp @@ -22,8 +22,7 @@ #include "physicsSolvers/multiphysics/CoupledSolver.hpp" #include "constitutive/solid/CoupledSolidBase.hpp" #include "physicsSolvers/fluidFlow/CompositionalMultiphaseBase.hpp" -#include "physicsSolvers/multiphysics/CompositionalMultiphaseReservoirAndWells.hpp" -#include "physicsSolvers/solidMechanics/SolidMechanicsLagrangianFEM.hpp" +#include "physicsSolvers/multiphysics/PoromechanicsSolver.hpp" namespace geos @@ -45,26 +44,17 @@ ENUM_STRINGS( StabilizationType, } template< typename FLOW_SOLVER > -class MultiphasePoromechanics : public CoupledSolver< FLOW_SOLVER, SolidMechanicsLagrangianFEM > +class MultiphasePoromechanics : public PoromechanicsSolver< FLOW_SOLVER > { public: - using Base = CoupledSolver< FLOW_SOLVER, SolidMechanicsLagrangianFEM >; + using Base = PoromechanicsSolver< FLOW_SOLVER >; using Base::m_solvers; using Base::m_dofManager; using Base::m_localMatrix; using Base::m_rhs; using Base::m_solution; - enum class SolverType : integer - { - Flow = 0, - SolidMechanics = 1 - }; - - /// String used to form the solverName used to register solvers in CoupledSolver - static string coupledSolverAttributePrefix() { return "poromechanics"; } - /** * @brief main constructor for MultiphasePoromechanics Objects * @param name the name of this instantiation of MultiphasePoromechanics in the repository @@ -92,7 +82,7 @@ class MultiphasePoromechanics : public CoupledSolver< FLOW_SOLVER, SolidMechanic */ SolidMechanicsLagrangianFEM * solidMechanicsSolver() const { - return std::get< toUnderlying( SolverType::SolidMechanics ) >( m_solvers ); + return std::get< toUnderlying( Base::SolverType::SolidMechanics ) >( m_solvers ); } /** @@ -101,7 +91,7 @@ class MultiphasePoromechanics : public CoupledSolver< FLOW_SOLVER, SolidMechanic */ FLOW_SOLVER * flowSolver() const { - return std::get< toUnderlying( SolverType::Flow ) >( m_solvers ); + return std::get< toUnderlying( Base::SolverType::Flow ) >( m_solvers ); } /** @@ -188,10 +178,10 @@ class MultiphasePoromechanics : public CoupledSolver< FLOW_SOLVER, SolidMechanic void updateBulkDensity( ElementSubRegionBase & subRegion ); /** - * @brief Helper function to average the mean stress increment over quadrature points + * @brief Helper function to average the mean total stress increment over quadrature points * @param[in] domain the domain partition */ - void averageMeanStressIncrement( DomainPartition & domain ); + void averageMeanTotalStressIncrement( DomainPartition & domain ); template< typename CONSTITUTIVE_BASE, typename KERNEL_WRAPPER, diff --git a/src/coreComponents/physicsSolvers/multiphysics/PoromechanicsInitialization.cpp b/src/coreComponents/physicsSolvers/multiphysics/PoromechanicsInitialization.cpp index c0c79fe0c8c..cb0e6541120 100644 --- a/src/coreComponents/physicsSolvers/multiphysics/PoromechanicsInitialization.cpp +++ b/src/coreComponents/physicsSolvers/multiphysics/PoromechanicsInitialization.cpp @@ -24,6 +24,7 @@ #include "mainInterface/ProblemManager.hpp" #include "physicsSolvers/fluidFlow/SinglePhaseBase.hpp" #include "physicsSolvers/multiphysics/SinglePhaseReservoirAndWells.hpp" +#include "physicsSolvers/multiphysics/CompositionalMultiphaseReservoirAndWells.hpp" namespace geos { diff --git a/src/coreComponents/physicsSolvers/multiphysics/PoromechanicsSolver.hpp b/src/coreComponents/physicsSolvers/multiphysics/PoromechanicsSolver.hpp new file mode 100644 index 00000000000..9aa70c5f1a7 --- /dev/null +++ b/src/coreComponents/physicsSolvers/multiphysics/PoromechanicsSolver.hpp @@ -0,0 +1,215 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2018-2020 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2020 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2018-2020 TotalEnergies + * Copyright (c) 2019- GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +/** + * @file PoromechanicsSolver.hpp + * + */ + +#ifndef GEOS_PHYSICSSOLVERS_MULTIPHYSICS_POROMECHANICSSOLVER_HPP_ +#define GEOS_PHYSICSSOLVERS_MULTIPHYSICS_POROMECHANICSSOLVER_HPP_ + +#include "physicsSolvers/multiphysics/CoupledSolver.hpp" +#include "physicsSolvers/solidMechanics/SolidMechanicsLagrangianFEM.hpp" +#include "constitutive/solid/PorousSolid.hpp" +#include "codingUtilities/Utilities.hpp" + +namespace geos +{ + +template< typename FLOW_SOLVER > +class PoromechanicsSolver : public CoupledSolver< FLOW_SOLVER, + SolidMechanicsLagrangianFEM > +{ +public: + + using Base = CoupledSolver< FLOW_SOLVER, + SolidMechanicsLagrangianFEM >; + using Base::m_solvers; + using Base::m_dofManager; + using Base::m_localMatrix; + using Base::m_rhs; + using Base::m_solution; + + enum class SolverType : integer + { + Flow = 0, + SolidMechanics = 1 + }; + + /// String used to form the solverName used to register solvers in CoupledSolver + static string coupledSolverAttributePrefix() { return "poromechanics"; } + + /** + * @brief main constructor for CoupledSolver Objects + * @param name the name of this instantiation of CoupledSolver in the repository + * @param parent the parent group of this instantiation of CoupledSolver + */ + PoromechanicsSolver( const string & name, + dataRepository::Group * const parent ) + : Base( name, parent ) + {} + +protected: + + /* Implementation of Nonlinear Acceleration (Aitken) of averageMeanTotalStressIncrement */ + + void recordAverageMeanTotalStressIncrement( DomainPartition & domain, + array1d< real64 > & averageMeanTotalStressIncrement ) + { + averageMeanTotalStressIncrement.resize( 0 ); + SolverBase::forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const &, + MeshLevel & mesh, + arrayView1d< string const > const & regionNames ) { + mesh.getElemManager().forElementSubRegions< CellElementSubRegion >( regionNames, [&]( localIndex const, + auto & subRegion ) { + // get the solid model (to access stress increment) + string const solidName = subRegion.template getReference< string >( "porousMaterialNames" ); + geos::constitutive::CoupledSolidBase & solid = SolverBase::getConstitutiveModel< geos::constitutive::CoupledSolidBase >( + subRegion, solidName ); + + arrayView1d< const real64 > const & averageMeanTotalStressIncrement_k = solid.getAverageMeanTotalStressIncrement_k(); + for( localIndex k = 0; k < localIndex( averageMeanTotalStressIncrement_k.size()); k++ ) + { + averageMeanTotalStressIncrement.emplace_back( averageMeanTotalStressIncrement_k[k] ); + } + } ); + } ); + } + + void applyAcceleratedAverageMeanTotalStressIncrement( DomainPartition & domain, + array1d< real64 > & averageMeanTotalStressIncrement ) + { + integer i = 0; + SolverBase::forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const &, + MeshLevel & mesh, + arrayView1d< string const > const & regionNames ) { + mesh.getElemManager().forElementSubRegions< CellElementSubRegion >( regionNames, [&]( localIndex const, + auto & subRegion ) { + // get the solid model (to access stress increment) + string const solidName = subRegion.template getReference< string >( "porousMaterialNames" ); + geos::constitutive::CoupledSolidBase & solid = SolverBase::getConstitutiveModel< geos::constitutive::CoupledSolidBase >( + subRegion, solidName ); + auto & porosityModel = dynamic_cast< geos::constitutive::BiotPorosity const & >( solid.getBasePorosityModel()); + arrayView1d< real64 > const & averageMeanTotalStressIncrement_k = solid.getAverageMeanTotalStressIncrement_k(); + for( localIndex k = 0; k < localIndex( averageMeanTotalStressIncrement_k.size()); k++ ) + { + porosityModel.updateAverageMeanTotalStressIncrement( k, averageMeanTotalStressIncrement[i] ); + i++; + } + } ); + } ); + } + + real64 computeAitkenRelaxationFactor( array1d< real64 > const & s0, + array1d< real64 > const & s1, + array1d< real64 > const & s1_tilde, + array1d< real64 > const & s2_tilde, + real64 const omega0 ) + { + array1d< real64 > r1 = axpy( s1_tilde, s0, -1.0 ); + array1d< real64 > r2 = axpy( s2_tilde, s1, -1.0 ); + + // diff = r2 - r1 + array1d< real64 > diff = axpy( r2, r1, -1.0 ); + + real64 const denom = dot( diff, diff ); + real64 const numer = dot( r1, diff ); + + real64 omega1 = 1.0; + if( !isZero( denom )) + { + omega1 = -1.0 * omega0 * numer / denom; + } + return omega1; + } + + array1d< real64 > computeUpdate( array1d< real64 > const & s1, + array1d< real64 > const & s2_tilde, + real64 const omega1 ) + { + return axpy( scale( s1, 1.0 - omega1 ), + scale( s2_tilde, omega1 ), + 1.0 ); + } + + void startSequentialIteration( integer const & iter, + DomainPartition & domain ) override + { + if( this->getNonlinearSolverParameters().m_nonlinearAccelerationType == NonlinearSolverParameters::NonlinearAccelerationType::Aitken ) + { + if( iter == 0 ) + { + recordAverageMeanTotalStressIncrement( domain, m_s1 ); + } + else + { + m_s0 = m_s1; + m_s1 = m_s2; + m_s1_tilde = m_s2_tilde; + m_omega0 = m_omega1; + } + } + } + + void finishSequentialIteration( integer const & iter, + DomainPartition & domain ) override + { + if( this->getNonlinearSolverParameters().m_nonlinearAccelerationType == NonlinearSolverParameters::NonlinearAccelerationType::Aitken ) + { + if( iter == 0 ) + { + m_s2 = m_s2_tilde; + m_omega1 = 1.0; + } + else + { + m_omega1 = computeAitkenRelaxationFactor( m_s0, m_s1, m_s1_tilde, m_s2_tilde, m_omega0 ); + m_s2 = computeUpdate( m_s1, m_s2_tilde, m_omega1 ); + applyAcceleratedAverageMeanTotalStressIncrement( domain, m_s2 ); + } + } + } + + virtual void mapSolutionBetweenSolvers( DomainPartition & domain, integer const solverType ) override + { + if( solverType == static_cast< integer >( SolverType::SolidMechanics ) && + this->getNonlinearSolverParameters().m_nonlinearAccelerationType== NonlinearSolverParameters::NonlinearAccelerationType::Aitken ) + { + recordAverageMeanTotalStressIncrement( domain, m_s2_tilde ); + } + } + + virtual void validateNonlinearAcceleration() override + { + if( MpiWrapper::commSize( MPI_COMM_GEOSX ) > 1 ) + { + GEOS_ERROR( "Nonlinear acceleration is not implemented for MPI runs" ); + } + } + + /// Member variables needed for Nonlinear Acceleration ( Aitken ). Naming convention follows ( Jiang & Tchelepi, 2019 ) + array1d< real64 > m_s0; // Accelerated averageMeanTotalStresIncrement @ outer iteration v ( two iterations ago ) + array1d< real64 > m_s1; // Accelerated averageMeanTotalStresIncrement @ outer iteration v + 1 ( previous iteration ) + array1d< real64 > m_s1_tilde; // Unaccelerated averageMeanTotalStresIncrement @ outer iteration v + 1 ( previous iteration ) + array1d< real64 > m_s2; // Accelerated averageMeanTotalStresIncrement @ outer iteration v + 2 ( current iteration ) + array1d< real64 > m_s2_tilde; // Unaccelerated averageMeanTotalStresIncrement @ outer iteration v + 1 ( current iteration ) + real64 m_omega0; // Old Aitken relaxation factor + real64 m_omega1; // New Aitken relaxation factor + +}; + +} /* namespace geos */ + +#endif //GEOS_PHYSICSSOLVERS_MULTIPHYSICS_POROMECHANICSSOLVER_HPP_ diff --git a/src/coreComponents/physicsSolvers/multiphysics/SinglePhasePoromechanics.cpp b/src/coreComponents/physicsSolvers/multiphysics/SinglePhasePoromechanics.cpp index dd87ed0f528..54d2259e097 100644 --- a/src/coreComponents/physicsSolvers/multiphysics/SinglePhasePoromechanics.cpp +++ b/src/coreComponents/physicsSolvers/multiphysics/SinglePhasePoromechanics.cpp @@ -454,7 +454,7 @@ void SinglePhasePoromechanics< FLOW_SOLVER >::mapSolutionBetweenSolvers( DomainP GEOS_MARK_FUNCTION; /// After the flow solver - if( solverType == static_cast< integer >( SolverType::Flow ) ) + if( solverType == static_cast< integer >( Base::SolverType::Flow ) ) { // save pressure and temperature at the end of this iteration flowSolver()->saveIterationState( domain ); @@ -477,10 +477,10 @@ void SinglePhasePoromechanics< FLOW_SOLVER >::mapSolutionBetweenSolvers( DomainP } /// After the solid mechanics solver - if( solverType == static_cast< integer >( SolverType::SolidMechanics ) ) + if( solverType == static_cast< integer >( Base::SolverType::SolidMechanics ) ) { - // compute the average of the mean stress increment over quadrature points - averageMeanStressIncrement( domain ); + // compute the average of the mean total stress increment over quadrature points + averageMeanTotalStressIncrement( domain ); this->template forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const &, MeshLevel & mesh, @@ -496,6 +496,8 @@ void SinglePhasePoromechanics< FLOW_SOLVER >::mapSolutionBetweenSolvers( DomainP } ); } ); } + // call base method (needed to perform nonlinear acceleration) + Base::mapSolutionBetweenSolvers( domain, solverType ); } template< typename FLOW_SOLVER > @@ -518,7 +520,7 @@ void SinglePhasePoromechanics< FLOW_SOLVER >::updateBulkDensity( ElementSubRegio } template< typename FLOW_SOLVER > -void SinglePhasePoromechanics< FLOW_SOLVER >::averageMeanStressIncrement( DomainPartition & domain ) +void SinglePhasePoromechanics< FLOW_SOLVER >::averageMeanTotalStressIncrement( DomainPartition & domain ) { this->template forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const &, MeshLevel & mesh, @@ -531,8 +533,8 @@ void SinglePhasePoromechanics< FLOW_SOLVER >::averageMeanStressIncrement( Domain string const solidName = subRegion.template getReference< string >( viewKeyStruct::porousMaterialNamesString() ); CoupledSolidBase & solid = this->template getConstitutiveModel< CoupledSolidBase >( subRegion, solidName ); - arrayView2d< real64 const > const meanStressIncrement_k = solid.getMeanEffectiveStressIncrement_k(); - arrayView1d< real64 > const averageMeanStressIncrement_k = solid.getAverageMeanEffectiveStressIncrement_k(); + arrayView2d< real64 const > const meanTotalStressIncrement_k = solid.getMeanTotalStressIncrement_k(); + arrayView1d< real64 > const averageMeanTotalStressIncrement_k = solid.getAverageMeanTotalStressIncrement_k(); finiteElement::FiniteElementBase & subRegionFE = subRegion.template getReference< finiteElement::FiniteElementBase >( solidMechanicsSolver()->getDiscretizationName() ); @@ -552,8 +554,8 @@ void SinglePhasePoromechanics< FLOW_SOLVER >::averageMeanStressIncrement( Domain mesh.getFaceManager(), subRegion, finiteElement, - meanStressIncrement_k, - averageMeanStressIncrement_k ); + meanTotalStressIncrement_k, + averageMeanTotalStressIncrement_k ); } ); } ); } ); diff --git a/src/coreComponents/physicsSolvers/multiphysics/SinglePhasePoromechanics.hpp b/src/coreComponents/physicsSolvers/multiphysics/SinglePhasePoromechanics.hpp index 060ad19fe5b..5a8bbad8593 100644 --- a/src/coreComponents/physicsSolvers/multiphysics/SinglePhasePoromechanics.hpp +++ b/src/coreComponents/physicsSolvers/multiphysics/SinglePhasePoromechanics.hpp @@ -19,34 +19,24 @@ #ifndef GEOS_PHYSICSSOLVERS_MULTIPHYSICS_SINGLEPHASEPOROMECHANICS_HPP_ #define GEOS_PHYSICSSOLVERS_MULTIPHYSICS_SINGLEPHASEPOROMECHANICS_HPP_ -#include "physicsSolvers/multiphysics/CoupledSolver.hpp" -#include "physicsSolvers/solidMechanics/SolidMechanicsLagrangianFEM.hpp" +#include "physicsSolvers/multiphysics/PoromechanicsSolver.hpp" + namespace geos { template< typename FLOW_SOLVER > -class SinglePhasePoromechanics : public CoupledSolver< FLOW_SOLVER, - SolidMechanicsLagrangianFEM > +class SinglePhasePoromechanics : public PoromechanicsSolver< FLOW_SOLVER > { public: - using Base = CoupledSolver< FLOW_SOLVER, SolidMechanicsLagrangianFEM >; + using Base = PoromechanicsSolver< FLOW_SOLVER >; using Base::m_solvers; using Base::m_dofManager; using Base::m_localMatrix; using Base::m_rhs; using Base::m_solution; - enum class SolverType : integer - { - Flow = 0, - SolidMechanics = 1 - }; - - /// String used to form the solverName used to register solvers in CoupledSolver - static string coupledSolverAttributePrefix() { return "poromechanics"; } - /** * @brief main constructor for SinglePhasePoromechanics objects * @param name the name of this instantiation of SinglePhasePoromechanics in the repository @@ -74,7 +64,7 @@ class SinglePhasePoromechanics : public CoupledSolver< FLOW_SOLVER, */ SolidMechanicsLagrangianFEM * solidMechanicsSolver() const { - return std::get< toUnderlying( SolverType::SolidMechanics ) >( m_solvers ); + return std::get< toUnderlying( Base::SolverType::SolidMechanics ) >( m_solvers ); } /** @@ -83,7 +73,7 @@ class SinglePhasePoromechanics : public CoupledSolver< FLOW_SOLVER, */ FLOW_SOLVER * flowSolver() const { - return std::get< toUnderlying( SolverType::Flow ) >( m_solvers ); + return std::get< toUnderlying( Base::SolverType::Flow ) >( m_solvers ); } /** @@ -173,7 +163,7 @@ class SinglePhasePoromechanics : public CoupledSolver< FLOW_SOLVER, * @brief Helper function to average the mean stress increment * @param[in] domain the domain partition */ - void averageMeanStressIncrement( DomainPartition & domain ); + void averageMeanTotalStressIncrement( DomainPartition & domain ); void createPreconditioner(); diff --git a/src/coreComponents/schema/docs/BiotPorosity_other.rst b/src/coreComponents/schema/docs/BiotPorosity_other.rst index 962677d8251..49c9bd3d373 100644 --- a/src/coreComponents/schema/docs/BiotPorosity_other.rst +++ b/src/coreComponents/schema/docs/BiotPorosity_other.rst @@ -1,19 +1,19 @@ -===================================== ============== ==================================================================================================== -Name Type Description -===================================== ============== ==================================================================================================== -averageMeanEffectiveStressIncrement_k real64_array Mean effective stress increment averaged over quadrature points at the previous sequential iteration -biotCoefficient real64_array Biot coefficient -dPorosity_dPressure real64_array2d Derivative of rock porosity with respect to pressure -dPorosity_dTemperature real64_array2d Derivative of rock porosity with respect to temperature -initialPorosity real64_array2d Initial porosity -meanEffectiveStressIncrement_k real64_array2d Mean effective stress increment at quadrature points at the previous sequential iteration -porosity real64_array2d Rock porosity -porosity_n real64_array2d Rock porosity at the previous converged time step -referencePorosity real64_array Reference porosity -solidBulkModulus real64_array Solid bulk modulus -thermalExpansionCoefficient real64_array Thermal expansion coefficient -===================================== ============== ==================================================================================================== +================================= ============== ================================================================================================ +Name Type Description +================================= ============== ================================================================================================ +averageMeanTotalStressIncrement_k real64_array Mean total stress increment averaged over quadrature points at the previous sequential iteration +biotCoefficient real64_array Biot coefficient +dPorosity_dPressure real64_array2d Derivative of rock porosity with respect to pressure +dPorosity_dTemperature real64_array2d Derivative of rock porosity with respect to temperature +initialPorosity real64_array2d Initial porosity +meanTotalStressIncrement_k real64_array2d Mean total stress increment at quadrature points at the previous sequential iteration +porosity real64_array2d Rock porosity +porosity_n real64_array2d Rock porosity at the previous converged time step +referencePorosity real64_array Reference porosity +solidBulkModulus real64_array Solid bulk modulus +thermalExpansionCoefficient real64_array Thermal expansion coefficient +================================= ============== ================================================================================================ diff --git a/src/coreComponents/schema/docs/NonlinearSolverParameters.rst b/src/coreComponents/schema/docs/NonlinearSolverParameters.rst index 4067911d031..c8511aad1ff 100644 --- a/src/coreComponents/schema/docs/NonlinearSolverParameters.rst +++ b/src/coreComponents/schema/docs/NonlinearSolverParameters.rst @@ -25,6 +25,7 @@ minNormalizer real64 newtonMaxIter integer 5 Maximum number of iterations that are allowed in a Newton loop. newtonMinIter integer 1 Minimum number of iterations that are required before exiting the Newton loop. newtonTol real64 1e-06 The required tolerance in order to exit the Newton iteration loop. +nonlinearAccelerationType geos_NonlinearSolverParameters_NonlinearAccelerationType None Nonlinear acceleration type for sequential solver. normType geos_solverBaseKernels_NormType Linfinity | Norm used by the flow solver to check nonlinear convergence. Valid options: | * Linfinity | * L2 diff --git a/src/coreComponents/schema/schema.xsd b/src/coreComponents/schema/schema.xsd index a13aa839d04..a081d6b6ada 100644 --- a/src/coreComponents/schema/schema.xsd +++ b/src/coreComponents/schema/schema.xsd @@ -1807,6 +1807,8 @@ the relative residual norm satisfies: + + @@ -1843,6 +1845,11 @@ the relative residual norm satisfies: + + + + + diff --git a/src/coreComponents/schema/schema.xsd.other b/src/coreComponents/schema/schema.xsd.other index b7344dc01a1..88a655235d3 100644 --- a/src/coreComponents/schema/schema.xsd.other +++ b/src/coreComponents/schema/schema.xsd.other @@ -1358,8 +1358,8 @@ - - + + @@ -1368,8 +1368,8 @@ - - + + From 3534076a5d8f4336b2bb6e353eb4569840d17fdb Mon Sep 17 00:00:00 2001 From: Pavel Tomin Date: Tue, 19 Dec 2023 19:43:46 -0600 Subject: [PATCH 14/40] Revert target region check accidentally commented in #2568 (#2893) --- ...oroElastic_staircase_co2_3d_sequential.xml | 2 +- ...ic_staircase_singlephase_3d_sequential.xml | 2 +- integratedTests | 2 +- .../multiphysics/MultiphasePoromechanics.cpp | 23 ++++++++----------- .../multiphysics/SinglePhasePoromechanics.cpp | 4 ---- 5 files changed, 12 insertions(+), 21 deletions(-) diff --git a/inputFiles/poromechanics/PoroElastic_staircase_co2_3d_sequential.xml b/inputFiles/poromechanics/PoroElastic_staircase_co2_3d_sequential.xml index eef1b2b0eb9..2775e1326bf 100755 --- a/inputFiles/poromechanics/PoroElastic_staircase_co2_3d_sequential.xml +++ b/inputFiles/poromechanics/PoroElastic_staircase_co2_3d_sequential.xml @@ -13,7 +13,7 @@ solidSolverName="linearElasticity" reservoirAndWellsSolverName="reservoirAndWells" logLevel="1" - targetRegions="{ channel, barrier, wellRegion1, wellRegion2 }"> + targetRegions="{ channel, barrier }"> + targetRegions="{ channel, barrier }"> ::initializePostInitialConditionsPreS arrayView1d< string const > const & poromechanicsTargetRegionNames = this->template getReference< array1d< string > >( SolverBase::viewKeyStruct::targetRegionsString() ); -// arrayView1d< string const > const & solidMechanicsTargetRegionNames = -// solidMechanicsSolver()->template getReference< array1d< string > >( SolverBase::viewKeyStruct::targetRegionsString() ); + arrayView1d< string const > const & solidMechanicsTargetRegionNames = + solidMechanicsSolver()->template getReference< array1d< string > >( SolverBase::viewKeyStruct::targetRegionsString() ); arrayView1d< string const > const & flowTargetRegionNames = flowSolver()->template getReference< array1d< string > >( SolverBase::viewKeyStruct::targetRegionsString() ); for( integer i = 0; i < poromechanicsTargetRegionNames.size(); ++i ) { -// Pavel: disabled to avoid false triggering for well regions -// GEOS_THROW_IF( std::find( solidMechanicsTargetRegionNames.begin(), solidMechanicsTargetRegionNames.end(), -// poromechanicsTargetRegionNames[i] ) -// == solidMechanicsTargetRegionNames.end(), -// GEOS_FMT( "{} {}: region {} must be a target region of {}", -// getCatalogName(), getDataContext(), poromechanicsTargetRegionNames[i], -// solidMechanicsSolver()->getDataContext() ), -// InputError ); + GEOS_THROW_IF( std::find( solidMechanicsTargetRegionNames.begin(), solidMechanicsTargetRegionNames.end(), + poromechanicsTargetRegionNames[i] ) + == solidMechanicsTargetRegionNames.end(), + GEOS_FMT( "{} {}: region {} must be a target region of {}", + getCatalogName(), this->getDataContext(), poromechanicsTargetRegionNames[i], + solidMechanicsSolver()->getDataContext() ), + InputError ); GEOS_THROW_IF( std::find( flowTargetRegionNames.begin(), flowTargetRegionNames.end(), poromechanicsTargetRegionNames[i] ) == flowTargetRegionNames.end(), GEOS_FMT( "{} {}: region `{}` must be a target region of `{}`", @@ -400,10 +399,6 @@ void MultiphasePoromechanics< FLOW_SOLVER >::initializePreSubGroups() [&]( localIndex const, ElementSubRegionBase & subRegion ) { - // skip the wells - if( subRegion.getCatalogName() == "wellElementSubRegion" ) - return; - string & porousName = subRegion.getReference< string >( viewKeyStruct::porousMaterialNamesString() ); porousName = this->template getConstitutiveName< CoupledSolidBase >( subRegion ); GEOS_ERROR_IF( porousName.empty(), GEOS_FMT( "{}: Solid model not found on subregion {}", diff --git a/src/coreComponents/physicsSolvers/multiphysics/SinglePhasePoromechanics.cpp b/src/coreComponents/physicsSolvers/multiphysics/SinglePhasePoromechanics.cpp index 54d2259e097..8830fa394c2 100644 --- a/src/coreComponents/physicsSolvers/multiphysics/SinglePhasePoromechanics.cpp +++ b/src/coreComponents/physicsSolvers/multiphysics/SinglePhasePoromechanics.cpp @@ -184,10 +184,6 @@ void SinglePhasePoromechanics< FLOW_SOLVER >::initializePreSubGroups() [&]( localIndex const, ElementSubRegionBase & subRegion ) { - // skip the wells - if( subRegion.getCatalogName() == "wellElementSubRegion" ) - return; - string & porousName = subRegion.getReference< string >( viewKeyStruct::porousMaterialNamesString() ); porousName = this->template getConstitutiveName< CoupledSolidBase >( subRegion ); GEOS_THROW_IF( porousName.empty(), From 52f57f96da2138b71cdc505b2ac2e4da409a45e2 Mon Sep 17 00:00:00 2001 From: MelReyCG <122801580+MelReyCG@users.noreply.github.com> Date: Wed, 20 Dec 2023 05:30:57 +0100 Subject: [PATCH 15/40] Simulation duration output fix + timestep separator (#2877) - Fix the ending simulation duration output which were unable to show the passed days (when exceeding 24h, the output restarted to 0), - Add a new timestep separator, which, in comparison with the previous one, is more visible than and readable for the users. This is a WIP and is subject to change later. The old timestep output is temporarily kept in order to keep compatibility with log reading scripts that are currently in use to read the GEOS outputs. The ending duration output proposed is of the following form: ``` For 111 nanoseconds : (old) run time 00:00:00.0000001 (new) run time 00h00m00s (1.11e-07 s) For 111 milliseconds : (old) run time 00:00:00.1110000 (new) run time 00h00m00s (0.111 s) For 2min, 25s, 16ms : (old) run time 00:02:25.0160000 (new) run time 00h02m25s (145.016 s) For 22h, 25min, 45s, 16ms : (old) run time 22:25:45.0160000 (new) run time 22h25m45s (80745.016 s) For 20j, 12h, 2min, 25s : (old) run time 12:02:25.0000000 (new) run time 20d, 12h02m25s (1771345 s) For 1 an, 20j, 12h, 2min, 25s : (old) run time 17:51:37.0000000 (new) run time 1y, 20d, 12h02m25s (33328297 s) For 292 ans, 20j, 12h, 2min, 25s : (old) run time 07:28:49.0000000 (new) run time 292y, 20d, 12h02m25s (9216401329 s) This PR proposes new components in the `units` namespace: - `units::TimeFormatInfo` which is the intermediate structure to format durations (it is needed as there was no satisfying solution with `std`/`fmt` formatters), - A few time constants. One of them is `units::YearDays`, which value is `365.2425`, following the C++20 standard, - Alias types `units::Days` and `units::Years` are added, and compatible with `std::chrono` functionalities. --- src/coreComponents/common/CMakeLists.txt | 1 + src/coreComponents/common/Units.cpp | 92 ++++++++++++ src/coreComponents/common/Units.hpp | 94 ++++++++++++ .../common/unitTests/CMakeLists.txt | 3 +- .../common/unitTests/testUnits.cpp | 136 ++++++++++++++++++ src/coreComponents/events/EventManager.cpp | 41 ++++-- src/coreComponents/events/EventManager.hpp | 9 -- src/main/main.cpp | 7 +- 8 files changed, 355 insertions(+), 28 deletions(-) create mode 100644 src/coreComponents/common/Units.cpp create mode 100644 src/coreComponents/common/unitTests/testUnits.cpp diff --git a/src/coreComponents/common/CMakeLists.txt b/src/coreComponents/common/CMakeLists.txt index f555696f508..fd5edeee558 100644 --- a/src/coreComponents/common/CMakeLists.txt +++ b/src/coreComponents/common/CMakeLists.txt @@ -44,6 +44,7 @@ set( common_sources MpiWrapper.cpp Path.cpp initializeEnvironment.cpp + Units.cpp ) set( dependencyList ${parallelDeps} lvarray pugixml::pugixml RAJA chai conduit::conduit fmt::fmt ) diff --git a/src/coreComponents/common/Units.cpp b/src/coreComponents/common/Units.cpp new file mode 100644 index 00000000000..ae2b5541a22 --- /dev/null +++ b/src/coreComponents/common/Units.cpp @@ -0,0 +1,92 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * 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 Units.cpp + */ + +#include "Units.hpp" + +namespace geos +{ + +namespace units +{ + + +TimeFormatInfo::TimeFormatInfo( double const totalSeconds, int const years, int const days, + int const hours, int const minutes, int const seconds ): + m_totalSeconds( totalSeconds ), + m_years( years ), + m_days( days ), + m_hours( hours ), + m_minutes( minutes ), + m_seconds( seconds ) +{} + +string TimeFormatInfo::toString() const +{ + std::ostringstream oss; + if( m_years != 0 ) + { + oss << m_years << "y, " << m_days << "d, "; + } + else if( m_days != 0 ) + { + oss << m_days << "d, "; + } + oss << GEOS_FMT( "{:0>2}h{:0>2}m{:0>2}s ({} s)", + m_hours, m_minutes, m_seconds, m_totalSeconds ); + return oss.str(); +} + +std::ostream & operator<<( std::ostream & os, TimeFormatInfo const & info ) +{ + os << info.toString(); + return os; +} + + +template< typename DURATION > +TimeFormatInfo TimeFormatInfo::fromDuration( DURATION const value ) +{ + using namespace std::chrono; + + auto const totalYears = duration_cast< units::Years >( value ); + auto const daysOut = duration_cast< units::Days >( value - totalYears ); + auto const hoursOut = duration_cast< hours >( value - totalYears - daysOut ); + auto const minutesOut = duration_cast< minutes >( value - totalYears - daysOut - hoursOut ); + auto const secondsOut = duration_cast< seconds >( value - totalYears - daysOut - hoursOut - minutesOut ); + + return TimeFormatInfo( duration< double >( value ).count(), int( totalYears.count() ), + int( daysOut.count() ), int( hoursOut.count() ), + int( minutesOut.count() ), int( secondsOut.count() ) ); +} +// available specializations +template TimeFormatInfo TimeFormatInfo::fromDuration< SystemClock::duration >( SystemClock::duration duration ); + +TimeFormatInfo TimeFormatInfo::fromSeconds( double const seconds ) +{ + int totalYears = int( seconds / YearSeconds ); + int daysOut = int( ( seconds - totalYears * YearSeconds ) / DaySeconds ); + int hoursOut = int( ( seconds - totalYears * YearSeconds - daysOut * DaySeconds ) / HourSeconds ); + int minutesOut = int( ( seconds - totalYears * YearSeconds - daysOut * DaySeconds - hoursOut * HourSeconds ) / MinuteSeconds ); + int secondsOut = int( seconds - totalYears * YearSeconds - daysOut * DaySeconds - hoursOut * HourSeconds - minutesOut * MinuteSeconds ); + + return TimeFormatInfo( seconds, totalYears, daysOut, hoursOut, minutesOut, secondsOut ); +} + + +} // end namespace units + +} // end namespace geos diff --git a/src/coreComponents/common/Units.hpp b/src/coreComponents/common/Units.hpp index 1151800188e..43e8fc680e3 100644 --- a/src/coreComponents/common/Units.hpp +++ b/src/coreComponents/common/Units.hpp @@ -147,8 +147,102 @@ inline string formatValue( real64 value, Unit unit ) } +/// Clock in use in GEOS to manipulate system times. +using SystemClock = std::chrono::system_clock; + +/// One year = 365.2425 days (= 146097 / 400) following the Gregorian calendar and the C++ convention. +using YearDaysRatio = std::ratio< 146097, 400 >; +/// Day helper duration type, equivalent to C++20 std::chrono::days. +using Days = std::chrono::duration< int64_t, std::ratio_multiply< std::ratio< 24 >, std::chrono::hours::period > >; +/// Year helper duration type, equivalent to C++20 std::chrono::years. +using Years = std::chrono::duration< int64_t, std::ratio_multiply< YearDaysRatio, Days::period > >; + +/// Days in one year (following the Gregorian calendar and the C++ convention) = 365.2425 days (= 146097 / 400). +static constexpr double YearDays = ( double )YearDaysRatio::num / YearDaysRatio::den; +/// Seconds in a minute +static constexpr double MinuteSeconds = 60.0; +/// Seconds in a hour +static constexpr double HourSeconds = 60.0 * MinuteSeconds; +/// Seconds in a day +static constexpr double DaySeconds = 24.0 * HourSeconds; +/// Seconds in a year +static constexpr double YearSeconds = YearDays * DaySeconds; + + +/** + * @brief Stores information that is useful to duration strings. Based on the geos::units time constants + */ +struct TimeFormatInfo +{ + /// Total time (including the decimal part) this instance represents in seconds + double m_totalSeconds = 0.0; + /// Number of integral years to show + int m_years = 0; + /// Number of integral days to show + int m_days = 0; + /// Number of integral hours to show + int m_hours = 0; + /// Number of integral minutes to show + int m_minutes = 0; + /// Number of integral seconds to show + int m_seconds = 0; + + + /** + * @brief Construct a TimeFormatInfo from raw data (which must be coherent) + * @param totalSeconds The total time (including the decimal part) this instance represents in seconds + * @param years Number of integral years to show + * @param days Number of integral days to show + * @param hours Number of integral hours to show + * @param minutes Number of integral minutes to show + * @param seconds Number of integral seconds to show + */ + TimeFormatInfo( double totalSeconds, int years, int days, int hours, int minutes, int seconds ); + /** + * @return A TimeFormatInfo constructed from the seconds to represent + * @param seconds the total time to represents in seconds (including the decimal part) + */ + static TimeFormatInfo fromSeconds( double const seconds ); + /** + * @return A TimeFormatInfo constructed from a standard typed duration value + * @param duration the duration to represents, in SystemClock::duration type + * (more types can be added by adding std::chrono::duration template specialisations). + */ + template< typename DURATION > static TimeFormatInfo fromDuration( DURATION duration ); + + /** + * @brief Insert the string representation information in the provided stream. + */ + friend std::ostream & operator<<( std::ostream & os, TimeFormatInfo const & ctx ); + + /** + * @return a user friendly string representation of this structure. + */ + string toString() const; +}; + + } // end namespace units } // end namespace geos + +/** + * @brief Formatter to be able to directly use a DurationInfo as a GEOS_FMT() argument + */ +template<> +struct GEOS_FMT_NS::formatter< geos::units::TimeFormatInfo > : GEOS_FMT_NS::formatter< std::string > +{ + /** + * @brief Format the specified TimeFormatInfo to a string. + * @param durationData the TimeFormatInfo 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::units::TimeFormatInfo const & durationData, format_context & ctx ) + { + return GEOS_FMT_NS::formatter< std::string >::format( durationData.toString(), ctx ); + } +}; + #endif //GEOS_MATH_PHYSICSCONSTANTS_HPP_ diff --git a/src/coreComponents/common/unitTests/CMakeLists.txt b/src/coreComponents/common/unitTests/CMakeLists.txt index 0367171706e..814fd512cec 100644 --- a/src/coreComponents/common/unitTests/CMakeLists.txt +++ b/src/coreComponents/common/unitTests/CMakeLists.txt @@ -3,7 +3,8 @@ set( gtest_geosx_tests testDataTypes.cpp testFixedSizeDeque.cpp testTypeDispatch.cpp - testLifoStorage.cpp ) + testLifoStorage.cpp + testUnits.cpp ) if ( ENABLE_CALIPER ) list( APPEND gtest_geosx_tests diff --git a/src/coreComponents/common/unitTests/testUnits.cpp b/src/coreComponents/common/unitTests/testUnits.cpp new file mode 100644 index 00000000000..2ab6ef2cbc3 --- /dev/null +++ b/src/coreComponents/common/unitTests/testUnits.cpp @@ -0,0 +1,136 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * 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 "common/Units.hpp" + +#include + +using namespace geos; +using namespace geos::units; + + +struct DurationCase +{ + string m_expectedString; + SystemClock::duration m_systemDuration; + double m_simDuration; + + template< class DURATION > + DurationCase( string_view expectedString, DURATION durationValue ): + m_expectedString( expectedString ), + m_systemDuration( std::chrono::duration_cast< SystemClock::duration >( durationValue ) ), + m_simDuration( std::chrono::duration_cast< std::chrono::duration< double > >( durationValue ).count() ) + {} +}; + +TEST( Units, SystemDurationFormatTest ) +{ + using namespace std::chrono; + + std::vector< DurationCase > durationCases = { + + DurationCase( + "00h00m00s (1.11e-07 s)", + nanoseconds( 111 ) ), + + DurationCase( + "00h00m00s (0.000111 s)", + microseconds( 111 ) ), + + DurationCase( + "00h00m00s (0.111 s)", + milliseconds( 111 ) ), + + DurationCase( + "00h02m25s (145.016 s)", + seconds( 145 ) + milliseconds( 16 ) ), + + DurationCase( + "22h25m45s (80745.016 s)", + hours( 20 ) + minutes( 145 ) + seconds( 45 ) + milliseconds( 16 ) ), + + DurationCase( + "20d, 12h02m25s (1771345.016 s)", + hours( long( 24 * 20.5 ) ) + seconds( 145 ) + milliseconds( 16 ) ), + + DurationCase( + "20d, 12h02m25s (1771345.016 s)", + Days( 20 ) + hours( 12 ) + seconds( 145 ) + milliseconds( 16 ) ), + + DurationCase( + "1y, 0d, 00h00m00s (31556952 s)", + Years( 1 ) ), + + DurationCase( + "1y, 0d, 12h00m00s (31600152 s)", + Years( 1 ) + hours( 12 ) ), + + DurationCase( + "1y, 20d, 12h02m25s (33328297.016 s)", + Years( 1 ) + hours( long( 24 * 20.5 ) ) + seconds( 145 ) + milliseconds( 16 ) ), + + DurationCase( + "1y, 20d, 12h02m25s (33328297.016 s)", + Years( 1 ) + Days( 20 ) + hours( 12 ) + seconds( 145 ) + milliseconds( 16 ) ), + + DurationCase( + "12y, 362d, 05h49m12s (409981176 s)", + Years( 13 ) - Days( 3 ) ), + + DurationCase( + "13y, 365d, 05h48m12s (441797268 s)", + Years( 14 ) - minutes( 1 ) ), + + DurationCase( + "14y, 0d, 00h00m00s (441797328 s)", + Years( 14 ) ), + + DurationCase( + "14y, 0d, 12h00m00s (441840528 s)", + Years( 14 ) + hours( 12 ) ), + + DurationCase( + "100y, 20d, 12h02m25s (3157466545.016 s)", + Years( 100 ) + Days( 20 ) + hours( 12 ) + seconds( 145 ) + milliseconds( 16 ) ), + + DurationCase( + "292y, 20d, 12h02m25s (9216401329.016 s)", + Years( 292 ) + Days( 20 ) + hours( 12 ) + seconds( 145 ) + milliseconds( 16 ) ), + + DurationCase( + "5500y, 20d, 12h02m25s (173565007345 s)", + Years( 5500 ) + Days( 20 ) + hours( 12 ) + seconds( 145 ) ), + + }; + + const SystemClock::duration maxDuration = SystemClock::duration::max(); + const string errorInfo = GEOS_FMT( "(Max possible duration = {} s)", + duration_cast< seconds >( maxDuration ).count() ); + + // Duration with more than 292 years are not supported by the SystemClock type. + double maxSystemTime = duration_cast< seconds, double, std::ratio< 1 > >( SystemClock::duration::max() ).count(); + + for( DurationCase const & durationCase : durationCases ) + { + // testing "double" typed time (which has a limit that is way higher than the tests cases) + EXPECT_STREQ( durationCase.m_expectedString.c_str(), + TimeFormatInfo::fromSeconds( durationCase.m_simDuration ).toString().c_str() ) << errorInfo; + + if( durationCase.m_simDuration <= maxSystemTime ) + { + EXPECT_STREQ( durationCase.m_expectedString.c_str(), + TimeFormatInfo::fromDuration( durationCase.m_systemDuration ).toString().c_str() ) << errorInfo; + } + } +} diff --git a/src/coreComponents/events/EventManager.cpp b/src/coreComponents/events/EventManager.cpp index d06353fae8d..3ebb6c2af44 100644 --- a/src/coreComponents/events/EventManager.cpp +++ b/src/coreComponents/events/EventManager.cpp @@ -21,6 +21,7 @@ #include "common/TimingMacros.hpp" #include "events/EventBase.hpp" #include "mesh/mpiCommunications/CommunicationTools.hpp" +#include "common/Units.hpp" namespace geos { @@ -38,6 +39,7 @@ EventManager::EventManager( string const & name, m_dt(), m_cycle(), m_currentSubEvent(), + // TODO: default to TimeOutputFormat::full? m_timeOutputFormat( TimeOutputFormat::seconds ) { setInputFlags( InputFlags::REQUIRED ); @@ -224,39 +226,48 @@ bool EventManager::run( DomainPartition & domain ) void EventManager::outputTime() const { + // The formating here is a work in progress. + GEOS_LOG_RANK_0( GEOS_FMT( "\n" + "------------------- TIMESTEP START -------------------\n" + " - Time: {}\n" + " - Delta Time: {}\n" + " - Cycle: {}\n" + "------------------------------------------------------\n\n", + units::TimeFormatInfo::fromSeconds( m_time ), + units::TimeFormatInfo::fromSeconds( m_dt ), + m_cycle )); + + // We are keeping the old outputs to keep compatibility with current log reading scripts. if( m_timeOutputFormat==TimeOutputFormat::full ) { - integer const yearsOut = m_time / YEAR; - integer const daysOut = (m_time - yearsOut * YEAR) / DAY; - integer const hoursOut = (m_time - yearsOut * YEAR - daysOut * DAY) / HOUR; - integer const minutesOut = (m_time - yearsOut * YEAR - daysOut * DAY - hoursOut * HOUR) / MINUTE; - integer const secondsOut = m_time - yearsOut * YEAR - daysOut * DAY - hoursOut * HOUR - minutesOut * MINUTE; + units::TimeFormatInfo info = units::TimeFormatInfo::fromSeconds( m_time ); - GEOS_LOG_RANK_0( GEOS_FMT( "Time: {} years, {} days, {} hrs, {} min, {} s, dt: {} s, Cycle: {}", yearsOut, daysOut, hoursOut, minutesOut, secondsOut, m_dt, m_cycle ) ); + GEOS_LOG_RANK_0( GEOS_FMT( "Time: {} years, {} days, {} hrs, {} min, {} s, dt: {} s, Cycle: {}\n", + info.m_years, info.m_days, info.m_hours, info.m_minutes, info.m_seconds, m_dt, m_cycle ) ); } else if( m_timeOutputFormat==TimeOutputFormat::years ) { - real64 const yearsOut = m_time / YEAR; - GEOS_LOG_RANK_0( GEOS_FMT( "Time: {:.2f} years, dt: {} s, Cycle: {}", yearsOut, m_dt, m_cycle ) ); + real64 const yearsOut = m_time / units::YearSeconds; + GEOS_LOG_RANK_0( GEOS_FMT( "Time: {:.2f} years, dt: {} s, Cycle: {}\n", yearsOut, m_dt, m_cycle ) ); } else if( m_timeOutputFormat==TimeOutputFormat::days ) { - real64 const daysOut = m_time / DAY; - GEOS_LOG_RANK_0( GEOS_FMT( "Time: {:.2f} days, dt: {} s, Cycle: {}", daysOut, m_dt, m_cycle ) ); + real64 const daysOut = m_time / units::DaySeconds; + GEOS_LOG_RANK_0( GEOS_FMT( "Time: {:.2f} days, dt: {} s, Cycle: {}\n", daysOut, m_dt, m_cycle ) ); } else if( m_timeOutputFormat==TimeOutputFormat::hours ) { - real64 const hoursOut = m_time / HOUR; - GEOS_LOG_RANK_0( GEOS_FMT( "Time: {:.2f} hrs, dt: {} s, Cycle: {}", hoursOut, m_dt, m_cycle ) ); + real64 const hoursOut = m_time / units::HourSeconds; + GEOS_LOG_RANK_0( GEOS_FMT( "Time: {:.2f} hrs, dt: {} s, Cycle: {}\n", hoursOut, m_dt, m_cycle ) ); } else if( m_timeOutputFormat==TimeOutputFormat::minutes ) { - real64 const minutesOut = m_time / MINUTE; - GEOS_LOG_RANK_0( GEOS_FMT( "Time: {:.2f} min, dt: {} s, Cycle: {}", minutesOut, m_dt, m_cycle ) ); + real64 const minutesOut = m_time / units::MinuteSeconds; + GEOS_LOG_RANK_0( GEOS_FMT( "Time: {:.2f} min, dt: {} s, Cycle: {}\n", minutesOut, m_dt, m_cycle ) ); } else if( m_timeOutputFormat == TimeOutputFormat::seconds ) { - GEOS_LOG_RANK_0( GEOS_FMT( "Time: {:4.2e} s, dt: {} s, Cycle: {}", m_time, m_dt, m_cycle ) ); + GEOS_LOG_RANK_0( GEOS_FMT( "Time: {:4.2e} s, dt: {} s, Cycle: {}\n", m_time, m_dt, m_cycle ) ); } else { diff --git a/src/coreComponents/events/EventManager.hpp b/src/coreComponents/events/EventManager.hpp index a573ee15e7a..c839346d19a 100644 --- a/src/coreComponents/events/EventManager.hpp +++ b/src/coreComponents/events/EventManager.hpp @@ -126,15 +126,6 @@ class EventManager : public dataRepository::Group full }; - /// seconds in a minute - static constexpr integer MINUTE = 60; - /// seconds in a hour - static constexpr integer HOUR = 60 * MINUTE; - /// seconds in a day - static constexpr integer DAY = 24 * HOUR; - /// seconds in a year - static constexpr integer YEAR = 365 * DAY; - private: diff --git a/src/main/main.cpp b/src/main/main.cpp index 62c7137e8a8..795228f32e3 100644 --- a/src/main/main.cpp +++ b/src/main/main.cpp @@ -16,6 +16,7 @@ #include "common/DataTypes.hpp" #include "common/Format.hpp" #include "common/TimingMacros.hpp" +#include "common/Units.hpp" #include "mainInterface/initialization.hpp" #include "mainInterface/ProblemManager.hpp" #include "mainInterface/GeosxState.hpp" @@ -60,9 +61,9 @@ int main( int argc, char *argv[] ) 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 ) ); - GEOS_LOG_RANK_0( GEOS_FMT( "initialization time {:%H:%M:%S}", initTime ) ); - GEOS_LOG_RANK_0( GEOS_FMT( "run time {:%H:%M:%S}", runTime ) ); + GEOS_LOG_RANK_0( GEOS_FMT( "total time {}", units::TimeFormatInfo::fromDuration( totalTime ) ) ); + GEOS_LOG_RANK_0( GEOS_FMT( "initialization time {}", units::TimeFormatInfo::fromDuration( initTime ) ) ); + GEOS_LOG_RANK_0( GEOS_FMT( "run time {}", units::TimeFormatInfo::fromDuration( runTime ) ) ); return 0; } From c59c435f8185466888c6ab105ccc4fee04dbb5ca Mon Sep 17 00:00:00 2001 From: William R Tobin <4522899+wrtobin@users.noreply.github.com> Date: Wed, 20 Dec 2023 13:49:45 -0800 Subject: [PATCH 16/40] RAJA `loop_exec` deprecated for `seq_exec` (#2890) * loop_exec->seq_exec, lvarray --------- Co-authored-by: TotoGaz <49004943+TotoGaz@users.noreply.github.com> --- src/coreComponents/LvArray | 2 +- src/coreComponents/common/GEOS_RAJA_Interface.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/coreComponents/LvArray b/src/coreComponents/LvArray index 2c370fb5b48..e6b7f90c1aa 160000 --- a/src/coreComponents/LvArray +++ b/src/coreComponents/LvArray @@ -1 +1 @@ -Subproject commit 2c370fb5b485e94ca549dbd7a769ddb8f40d2605 +Subproject commit e6b7f90c1aaa53992962ce6510dc000cc06c1943 diff --git a/src/coreComponents/common/GEOS_RAJA_Interface.hpp b/src/coreComponents/common/GEOS_RAJA_Interface.hpp index 82a238f69e7..2aad742dc77 100644 --- a/src/coreComponents/common/GEOS_RAJA_Interface.hpp +++ b/src/coreComponents/common/GEOS_RAJA_Interface.hpp @@ -32,7 +32,7 @@ namespace geos auto const hostMemorySpace = LvArray::MemorySpace::host; -using serialPolicy = RAJA::loop_exec; +using serialPolicy = RAJA::seq_exec; using serialAtomic = RAJA::seq_atomic; using serialReduce = RAJA::seq_reduce; From 4c52463e1ce8c0c5f2b6807ae3978e741ea53d16 Mon Sep 17 00:00:00 2001 From: Matteo Cusini <49037133+CusiniM@users.noreply.github.com> Date: Thu, 21 Dec 2023 02:56:20 +0100 Subject: [PATCH 17/40] Define a single quadrature point for FaceElements (#2784) Currently, there is no need to create a FE for FaceElementsSubRegions since we don't do any FE calculation on them. In the future, we may want to define 2D spaces for them but it's not easy since they can heterogeneous. In any case, this PR allows to run solidMechanics simulations on external meshes with conforming fractures. --- integratedTests | 2 +- .../mainInterface/ProblemManager.cpp | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/integratedTests b/integratedTests index 2eecf0f4231..8a77a4cde80 160000 --- a/integratedTests +++ b/integratedTests @@ -1 +1 @@ -Subproject commit 2eecf0f42317165a7494b99535c57096a20c8f56 +Subproject commit 8a77a4cde80a723cca581c1d94d718f03cb2d837 diff --git a/src/coreComponents/mainInterface/ProblemManager.cpp b/src/coreComponents/mainInterface/ProblemManager.cpp index c3df6c5c7d3..dcdbf1b47bb 100644 --- a/src/coreComponents/mainInterface/ProblemManager.cpp +++ b/src/coreComponents/mainInterface/ProblemManager.cpp @@ -925,7 +925,7 @@ map< std::tuple< string, string, string, string >, localIndex > ProblemManager:: if( feDiscretization != nullptr ) { - elemRegion.forElementSubRegions< CellElementSubRegion, FaceElementSubRegion >( [&]( auto & subRegion ) + elemRegion.forElementSubRegions< CellElementSubRegion >( [&]( auto & subRegion ) { std::unique_ptr< finiteElement::FiniteElementBase > newFE = feDiscretization->factory( subRegion.getElementType() ); @@ -961,6 +961,20 @@ map< std::tuple< string, string, string, string >, localIndex > ProblemManager:: numQuadraturePointsInList = std::max( numQuadraturePointsInList, numQuadraturePoints ); } ); } ); + + // For now FaceElementSubRegions do not have a FE type associated with them. They don't need one for now and + // it would have to be a heterogeneous one coz they are usually heterogeneous subregions. + elemRegion.forElementSubRegions< FaceElementSubRegion >( [&]( FaceElementSubRegion const & subRegion ) + { + localIndex & numQuadraturePointsInList = regionQuadrature[ std::make_tuple( meshBodyName, + meshLevel.getName(), + regionName, + subRegion.getName() ) ]; + + localIndex const numQuadraturePoints = 1; + + numQuadraturePointsInList = std::max( numQuadraturePointsInList, numQuadraturePoints ); + } ); } else //if( fvFluxApprox != nullptr ) { From 341e46161b5aa72416775b3993c0c7a0ceafeb6e Mon Sep 17 00:00:00 2001 From: Dickson Kachuma <81433670+dkachuma@users.noreply.github.com> Date: Wed, 20 Dec 2023 22:44:33 -0600 Subject: [PATCH 18/40] Include water vapourisation in CO2/Brine flash (#2811) --- integratedTests | 2 +- .../CO2Brine/functions/CO2Solubility.cpp | 38 ++- .../CO2Brine/functions/CO2Solubility.hpp | 225 ++++++++++++------ .../constitutiveTests/CMakeLists.txt | 4 +- .../testCO2BrinePVTModels.cpp | 14 +- .../constitutiveTests/testPVT_CO2.txt | 100 ++++---- .../constitutiveTests/testPVT_CO2Brine.xml | 118 +++++++++ ...T_CO2Brine_testCo2BrineEzrokhiMixtureA.txt | 31 +++ ...T_CO2Brine_testCo2BrineEzrokhiMixtureB.txt | 31 +++ ..._CO2Brine_testCo2BrinePhillipsMixtureA.txt | 31 +++ ..._CO2Brine_testCo2BrinePhillipsMixtureB.txt | 31 +++ .../testPVT_data/brinePVTEzrokhi.txt | 2 + 12 files changed, 493 insertions(+), 134 deletions(-) create mode 100644 src/coreComponents/unitTests/constitutiveTests/testPVT_CO2Brine.xml create mode 100644 src/coreComponents/unitTests/constitutiveTests/testPVT_CO2Brine_testCo2BrineEzrokhiMixtureA.txt create mode 100644 src/coreComponents/unitTests/constitutiveTests/testPVT_CO2Brine_testCo2BrineEzrokhiMixtureB.txt create mode 100644 src/coreComponents/unitTests/constitutiveTests/testPVT_CO2Brine_testCo2BrinePhillipsMixtureA.txt create mode 100644 src/coreComponents/unitTests/constitutiveTests/testPVT_CO2Brine_testCo2BrinePhillipsMixtureB.txt create mode 100755 src/coreComponents/unitTests/constitutiveTests/testPVT_data/brinePVTEzrokhi.txt diff --git a/integratedTests b/integratedTests index 8a77a4cde80..ef66865cc66 160000 --- a/integratedTests +++ b/integratedTests @@ -1 +1 @@ -Subproject commit 8a77a4cde80a723cca581c1d94d718f03cb2d837 +Subproject commit ef66865cc6632df149ea1ea2f7076e607ea15701 diff --git a/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/CO2Solubility.cpp b/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/CO2Solubility.cpp index 0cd74175e64..262ba55c081 100644 --- a/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/CO2Solubility.cpp +++ b/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/CO2Solubility.cpp @@ -245,7 +245,7 @@ TableFunction const * makeSolubilityTable( string_array const & inputParams, array1d< real64 > values( tableCoords.nPressures() * tableCoords.nTemperatures() ); calculateCO2Solubility( functionName, tolerance, tableCoords, salinity, values ); - string const tableName = functionName + "_table"; + string const tableName = functionName + "_co2Dissolution_table"; if( functionManager.hasGroup< TableFunction >( tableName ) ) { return functionManager.getGroupPointer< TableFunction >( tableName ); @@ -261,6 +261,35 @@ TableFunction const * makeSolubilityTable( string_array const & inputParams, } } +TableFunction const * makeVapourisationTable( string_array const & inputParams, + string const & functionName, + FunctionManager & functionManager ) +{ + // initialize the (p,T) coordinates + PTTableCoordinates tableCoords; + PVTFunctionHelpers::initializePropertyTable( inputParams, tableCoords ); + + // Currently initialise to all zeros + + array1d< real64 > values( tableCoords.nPressures() * tableCoords.nTemperatures() ); + values.zero(); + + string const tableName = functionName + "_waterVaporization_table"; + if( functionManager.hasGroup< TableFunction >( tableName ) ) + { + return functionManager.getGroupPointer< TableFunction >( tableName ); + } + else + { + TableFunction * const vapourisationTable = dynamicCast< TableFunction * >( functionManager.createChild( "TableFunction", tableName ) ); + vapourisationTable->setTableCoordinates( tableCoords.getCoords(), + { units::Pressure, units::TemperatureInC } ); + vapourisationTable->setTableValues( values, units::Solubility ); + vapourisationTable->setInterpolationMethod( TableFunction::InterpolationType::Linear ); + return vapourisationTable; + } +} + } // namespace CO2Solubility::CO2Solubility( string const & name, @@ -293,8 +322,12 @@ CO2Solubility::CO2Solubility( string const & name, m_phaseLiquidIndex = PVTFunctionHelpers::findName( phaseNames, expectedWaterPhaseNames, "phaseNames" ); m_CO2SolubilityTable = makeSolubilityTable( inputParams, m_modelName, FunctionManager::getInstance() ); + m_WaterVapourisationTable = makeVapourisationTable( inputParams, m_modelName, FunctionManager::getInstance() ); if( printTable ) + { m_CO2SolubilityTable->print( m_CO2SolubilityTable->getName() ); + m_WaterVapourisationTable->print( m_WaterVapourisationTable->getName() ); + } } void CO2Solubility::checkTablesParameters( real64 const pressure, @@ -302,12 +335,15 @@ void CO2Solubility::checkTablesParameters( real64 const pressure, { m_CO2SolubilityTable->checkCoord( pressure, 0 ); m_CO2SolubilityTable->checkCoord( temperature, 1 ); + m_WaterVapourisationTable->checkCoord( pressure, 0 ); + m_WaterVapourisationTable->checkCoord( temperature, 1 ); } CO2Solubility::KernelWrapper CO2Solubility::createKernelWrapper() const { return KernelWrapper( m_componentMolarWeight, *m_CO2SolubilityTable, + *m_WaterVapourisationTable, m_CO2Index, m_waterIndex, m_phaseGasIndex, diff --git a/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/CO2Solubility.hpp b/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/CO2Solubility.hpp index 77fa7562fb3..c2ca26b3f5c 100644 --- a/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/CO2Solubility.hpp +++ b/src/coreComponents/constitutive/fluid/multifluid/CO2Brine/functions/CO2Solubility.hpp @@ -46,17 +46,21 @@ class CO2SolubilityUpdate final : public FlashModelBaseUpdate CO2SolubilityUpdate( arrayView1d< real64 const > const & componentMolarWeight, TableFunction const & CO2SolubilityTable, + TableFunction const & waterVapourisationTable, integer const CO2Index, integer const waterIndex, integer const phaseGasIndex, integer const phaseLiquidIndex ) : FlashModelBaseUpdate( componentMolarWeight ), m_CO2SolubilityTable( CO2SolubilityTable.createKernelWrapper() ), + //m_WaterVapourisationTable( waterVapourisationTable.createKernelWrapper() ), m_CO2Index( CO2Index ), m_waterIndex( waterIndex ), m_phaseGasIndex( phaseGasIndex ), m_phaseLiquidIndex( phaseLiquidIndex ) - {} + { + GEOS_UNUSED_VAR( waterVapourisationTable ); + } template< int USD1 > GEOS_HOST_DEVICE @@ -70,13 +74,20 @@ class CO2SolubilityUpdate final : public FlashModelBaseUpdate { FlashModelBaseUpdate::move( space, touch ); m_CO2SolubilityTable.move( space, touch ); + //m_WaterVapourisationTable.move( space, touch ); } protected: + /// Expected number of components + static constexpr integer numComps = 2; + /// Table with CO2 solubility tabulated as a function (P,T) TableFunction::KernelWrapper m_CO2SolubilityTable; + /// Table with water vapourisation as a function (P,T) + //TableFunction::KernelWrapper m_WaterVapourisationTable; + /// Index of the CO2 phase integer m_CO2Index; @@ -125,6 +136,9 @@ class CO2Solubility : public FlashModelBase /// Table to compute solubility as a function of pressure and temperature TableFunction const * m_CO2SolubilityTable; + /// Table to compute water vapourisation as a function of pressure and temperature + TableFunction const * m_WaterVapourisationTable; + /// Index of the CO2 component integer m_CO2Index; @@ -150,112 +164,173 @@ CO2SolubilityUpdate::compute( real64 const & pressure, { using Deriv = multifluid::DerivativeOffset; - // solubility mol/kg(water) X = Csat/W + // Solubility of CO2 is read from the tables in the form of moles of CO2 per kg of water + // Solubility of water is read from the tables in the form of moles of water per kg of CO2 real64 const input[2] = { pressure, temperature }; - real64 solubilityDeriv[2]{}; - real64 solubility = m_CO2SolubilityTable.compute( input, solubilityDeriv ); - solubility *= m_componentMolarWeight[m_waterIndex]; - for( integer ic = 0; ic < 2; ++ic ) + real64 co2SolubilityDeriv[2]{}; + real64 watSolubilityDeriv[2]{0.0, 0.0}; + real64 co2Solubility = m_CO2SolubilityTable.compute( input, co2SolubilityDeriv ); + real64 watSolubility = 0.0; //m_WaterVapourisationTable.compute( input, watSolubilityDeriv ); + + // Convert the solubility to mole/mole + co2Solubility *= m_componentMolarWeight[m_waterIndex]; + watSolubility *= m_componentMolarWeight[m_CO2Index]; + for( integer const ic : {Deriv::dP, Deriv::dT} ) { - solubilityDeriv[ic] *= m_componentMolarWeight[m_waterIndex]; + co2SolubilityDeriv[ic] *= m_componentMolarWeight[m_waterIndex]; + watSolubilityDeriv[ic] *= m_componentMolarWeight[m_CO2Index]; } - // Y = C/W = z/(1-z) - real64 Y = 0.0; - real64 dY_dCompFrac[2]{}; + real64 const z_co2 = compFraction[m_CO2Index]; + real64 const z_wat = compFraction[m_waterIndex]; - if( compFraction[m_CO2Index] > 1.0 - minForDivision ) + real64 const determinant = 1.0 - co2Solubility*watSolubility; + + GEOS_ERROR_IF_LT_MSG ( LvArray::math::abs( determinant ), minForDivision, + GEOS_FMT( "Failed to calculate solubility at pressure {} Pa and temperature {} C.", pressure, temperature ) ); + + real64 invDeterminant = 0.0; + real64 invDeterminantDeriv[] = { 0.0, 0.0 }; + + invDeterminant = 1.0 / determinant; + for( integer const ic : {Deriv::dP, Deriv::dT} ) { - Y = compFraction[m_CO2Index] / minForDivision; - dY_dCompFrac[m_CO2Index] = 1.0 / minForDivision; - dY_dCompFrac[m_waterIndex] = 0.0; + invDeterminantDeriv[ic] = invDeterminant*invDeterminant*(co2Solubility*watSolubilityDeriv[ic] + watSolubility*co2SolubilityDeriv[ic]); + } + + real64 x_co2 = co2Solubility * (z_wat - watSolubility * z_co2) * invDeterminant; + real64 x_co2Deriv[4]{ 0.0, 0.0, 0.0, 0.0 }; + if( minForDivision < x_co2 ) + { + // Pressure and temperature derivatives + for( integer const ic : {Deriv::dP, Deriv::dT} ) + { + x_co2Deriv[ic] = co2SolubilityDeriv[ic] * (z_wat - watSolubility * z_co2) * invDeterminant + - co2Solubility * watSolubilityDeriv[ic] * z_co2 * invDeterminant + + co2Solubility * (z_wat - watSolubility * z_co2) * invDeterminantDeriv[ic]; + } + // Composition derivatives + x_co2Deriv[Deriv::dC+m_CO2Index] = -co2Solubility * watSolubility * invDeterminant; + x_co2Deriv[Deriv::dC+m_waterIndex] = co2Solubility * invDeterminant; } else { - real64 const oneMinusCompFracInv = 1.0 / (1.0 - compFraction[m_CO2Index]); - Y = compFraction[m_CO2Index] * oneMinusCompFracInv; - dY_dCompFrac[m_CO2Index] = oneMinusCompFracInv * oneMinusCompFracInv; - dY_dCompFrac[m_waterIndex] = 0.0; + x_co2 = 0.0; } - auto setZero = []( real64 & val ){ val = 0.0; }; - LvArray::forValuesInSlice( phaseFraction.derivs, setZero ); - LvArray::forValuesInSlice( phaseCompFraction.derivs, setZero ); - - if( Y < solubility ) + real64 y_wat = watSolubility * (z_co2 - x_co2); + real64 y_watDeriv[4]{ 0.0, 0.0, 0.0, 0.0 }; + if( minForDivision < y_wat ) { - // liquid phase only - - // 1) Compute phase fractions - - phaseFraction.value[m_phaseLiquidIndex] = 1.0; - phaseFraction.value[m_phaseGasIndex] = 0.0; - - // 2) Compute phase component fractions - - phaseCompFraction.value[m_phaseGasIndex][m_CO2Index] = 1.0; - phaseCompFraction.value[m_phaseGasIndex][m_waterIndex] = 0.0; - for( localIndex ic = 0; ic < 2; ++ic ) + // Pressure and temperature derivatives + for( integer const ic : {Deriv::dP, Deriv::dT} ) { - phaseCompFraction.value[m_phaseLiquidIndex][ic] = compFraction[ic]; - - for( localIndex jc = 0; jc < 2; ++jc ) - { - phaseCompFraction.derivs[m_phaseLiquidIndex][ic][Deriv::dC+jc] = (ic == jc ) ? 1.0 : 0.0; - phaseCompFraction.derivs[m_phaseGasIndex][ic][Deriv::dC+jc] = 0.0; - } + y_watDeriv[ic] = watSolubilityDeriv[ic] * (z_co2 - x_co2) - watSolubility * x_co2Deriv[ic]; } + // Composition derivatives + y_watDeriv[Deriv::dC+m_CO2Index] = watSolubility*(1.0 - x_co2Deriv[Deriv::dC+m_CO2Index]); + y_watDeriv[Deriv::dC+m_waterIndex] = -watSolubility * x_co2Deriv[Deriv::dC+m_waterIndex]; } else { - // two-phase flow + y_wat = 0.0; + } - // 1) Compute phase fractions + // Liquid and vapour phase fractions + real64 const L = x_co2 + z_wat - y_wat; + real64 const V = y_wat + z_co2 - x_co2; // = 1 - L; - // liquid phase fraction = (Csat + W) / (C + W) = (Csat/W + 1) / (C/W + 1) - real64 const onePlusYInv = 1.0 / ( 1.0 + Y ); - phaseFraction.value[m_phaseLiquidIndex] = (solubility + 1.0) * onePlusYInv; + if( minForDivision < L && minForDivision < V ) + { + // Two phases - phaseFraction.derivs[m_phaseLiquidIndex][Deriv::dP] = solubilityDeriv[0] * onePlusYInv; - phaseFraction.derivs[m_phaseLiquidIndex][Deriv::dT] = solubilityDeriv[1] * onePlusYInv; - phaseFraction.derivs[m_phaseLiquidIndex][Deriv::dC+m_CO2Index] = - -dY_dCompFrac[m_CO2Index] * phaseFraction.value[m_phaseLiquidIndex] * onePlusYInv; - phaseFraction.derivs[m_phaseLiquidIndex][Deriv::dC+m_waterIndex] = - -dY_dCompFrac[m_waterIndex] * phaseFraction.value[m_phaseLiquidIndex] * onePlusYInv; + // 1) Compute phase fractions and derivatives - phaseFraction.value[m_phaseGasIndex] = 1.0 - phaseFraction.value[m_phaseLiquidIndex]; + real64 const dL_dP = x_co2Deriv[Deriv::dP] - y_watDeriv[Deriv::dP]; + real64 const dL_dT = x_co2Deriv[Deriv::dT] - y_watDeriv[Deriv::dT]; + real64 const dL_dzco2 = x_co2Deriv[Deriv::dC+m_CO2Index] - y_watDeriv[Deriv::dC+m_CO2Index]; + real64 const dL_dzwat = x_co2Deriv[Deriv::dC+m_waterIndex] + 1.0 - y_watDeriv[Deriv::dC+m_waterIndex]; - phaseFraction.derivs[m_phaseGasIndex][Deriv::dP] = -phaseFraction.derivs[m_phaseLiquidIndex][Deriv::dP]; - phaseFraction.derivs[m_phaseGasIndex][Deriv::dT] = -phaseFraction.derivs[m_phaseLiquidIndex][Deriv::dT]; - phaseFraction.derivs[m_phaseGasIndex][Deriv::dC+m_CO2Index] = -phaseFraction.derivs[m_phaseLiquidIndex][Deriv::dC+m_CO2Index]; - phaseFraction.derivs[m_phaseGasIndex][Deriv::dC+m_waterIndex] = -phaseFraction.derivs[m_phaseLiquidIndex][Deriv::dC+m_waterIndex]; + real64 const dV_dP = y_watDeriv[Deriv::dP] - x_co2Deriv[Deriv::dP]; + real64 const dV_dT = y_watDeriv[Deriv::dT] - x_co2Deriv[Deriv::dT]; + real64 const dV_dzco2 = y_watDeriv[Deriv::dC+m_CO2Index] + 1.0 - x_co2Deriv[Deriv::dC+m_CO2Index]; + real64 const dV_dzwat = y_watDeriv[Deriv::dC+m_waterIndex] - x_co2Deriv[Deriv::dC+m_waterIndex]; - // 2) Compute phase component fractions + phaseFraction.value[m_phaseLiquidIndex] = L; - // liquid phase composition CO2 = Csat / (Csat + W) = (Csat/W) / (Csat/W + 1) - real64 const onePlusSolubilityInv = 1.0 / ( 1.0 + solubility ); - phaseCompFraction.value[m_phaseLiquidIndex][m_CO2Index] = solubility * onePlusSolubilityInv; + phaseFraction.derivs[m_phaseLiquidIndex][Deriv::dP] = dL_dP; + phaseFraction.derivs[m_phaseLiquidIndex][Deriv::dT] = dL_dT; + phaseFraction.derivs[m_phaseLiquidIndex][Deriv::dC+m_CO2Index] = dL_dzco2; + phaseFraction.derivs[m_phaseLiquidIndex][Deriv::dC+m_waterIndex] = dL_dzwat; - phaseCompFraction.derivs[m_phaseLiquidIndex][m_CO2Index][Deriv::dP] = solubilityDeriv[0] * (onePlusSolubilityInv*onePlusSolubilityInv); - phaseCompFraction.derivs[m_phaseLiquidIndex][m_CO2Index][Deriv::dT] = solubilityDeriv[1] * (onePlusSolubilityInv*onePlusSolubilityInv); + phaseFraction.value[m_phaseGasIndex] = V; - phaseCompFraction.value[m_phaseLiquidIndex][m_waterIndex] = 1.0 - phaseCompFraction.value[m_phaseLiquidIndex][m_CO2Index]; + phaseFraction.derivs[m_phaseGasIndex][Deriv::dP] = dV_dP; + phaseFraction.derivs[m_phaseGasIndex][Deriv::dT] = dV_dT; + phaseFraction.derivs[m_phaseGasIndex][Deriv::dC+m_CO2Index] = dV_dzco2; + phaseFraction.derivs[m_phaseGasIndex][Deriv::dC+m_waterIndex] = dV_dzwat; - phaseCompFraction.derivs[m_phaseLiquidIndex][m_waterIndex][Deriv::dP] = -phaseCompFraction.derivs[m_phaseLiquidIndex][m_CO2Index][Deriv::dP]; - phaseCompFraction.derivs[m_phaseLiquidIndex][m_waterIndex][Deriv::dT] = -phaseCompFraction.derivs[m_phaseLiquidIndex][m_CO2Index][Deriv::dT]; + // 2) Compute phase component fractions and derivatives - // gas phase composition CO2 = 1.0 + // 2.1) Assigning the number of moles in each phase + phaseCompFraction.value[m_phaseLiquidIndex][m_CO2Index] = x_co2; + phaseCompFraction.value[m_phaseLiquidIndex][m_waterIndex] = z_wat - y_wat; + phaseCompFraction.value[m_phaseGasIndex][m_CO2Index] = z_co2 - x_co2; + phaseCompFraction.value[m_phaseGasIndex][m_waterIndex] = y_wat; - phaseCompFraction.value[m_phaseGasIndex][m_CO2Index] = 1.0; - phaseCompFraction.value[m_phaseGasIndex][m_waterIndex] = 0.0; + for( integer const kc : {Deriv::dP, Deriv::dT, Deriv::dC+m_CO2Index, Deriv::dC+m_waterIndex} ) + { + phaseCompFraction.derivs[m_phaseLiquidIndex][m_CO2Index][kc] = x_co2Deriv[kc]; + phaseCompFraction.derivs[m_phaseLiquidIndex][m_waterIndex][kc] = -y_watDeriv[kc]; + phaseCompFraction.derivs[m_phaseGasIndex][m_CO2Index][kc] = -x_co2Deriv[kc]; + phaseCompFraction.derivs[m_phaseGasIndex][m_waterIndex][kc] = y_watDeriv[kc]; + } + phaseCompFraction.derivs[m_phaseLiquidIndex][m_waterIndex][Deriv::dC+m_waterIndex] += 1.0; + phaseCompFraction.derivs[m_phaseGasIndex][m_CO2Index][Deriv::dC+m_CO2Index] += 1.0; - phaseCompFraction.derivs[m_phaseGasIndex][m_CO2Index][Deriv::dP] = 0.0; - phaseCompFraction.derivs[m_phaseGasIndex][m_waterIndex][Deriv::dT] = 0.0; - phaseCompFraction.derivs[m_phaseGasIndex][m_CO2Index][Deriv::dP] = 0.0; - phaseCompFraction.derivs[m_phaseGasIndex][m_waterIndex][Deriv::dT] = 0.0; - // phaseCompFraction does not depend on globalComponentFraction + // 2.2) Divide by the number of moles in the phase to get the phase mole fraction + // Update: phaseCompFraction[ip][jc] <- phaseCompFraction[ip][jc] / phaseFraction[ip] + for( integer const ip : {m_phaseLiquidIndex, m_phaseGasIndex} ) + { + real64 const invFractionSqr = 1.0 / (phaseFraction.value[ip] * phaseFraction.value[ip]); + for( integer const jc : {m_CO2Index, m_waterIndex} ) + { + for( integer const kc : {Deriv::dP, Deriv::dT, Deriv::dC+m_CO2Index, Deriv::dC+m_waterIndex} ) + { + phaseCompFraction.derivs[ip][jc][kc] = ( phaseCompFraction.derivs[ip][jc][kc]*phaseFraction.value[ip] + - phaseCompFraction.value[ip][jc]*phaseFraction.derivs[ip][kc])*invFractionSqr; + } + phaseCompFraction.value[ip][jc] /= phaseFraction.value[ip]; + } + } + } + else + { + // Single phase: Select the present phase + integer const activePhase = minForDivision < L ? m_phaseLiquidIndex : m_phaseGasIndex; + + // Zero out everything to start + auto setZero = []( real64 & val ){ val = 0.0; }; + LvArray::forValuesInSlice( phaseFraction.value, setZero ); + LvArray::forValuesInSlice( phaseCompFraction.value, setZero ); + LvArray::forValuesInSlice( phaseFraction.derivs, setZero ); + LvArray::forValuesInSlice( phaseCompFraction.derivs, setZero ); + + // 1) Compute phase fractions + phaseFraction.value[activePhase] = 1.0; + + // 2) Compute phase component fractions + // Setup default values which will be overridden for the active phase + phaseCompFraction.value[m_phaseGasIndex][m_CO2Index] = 1.0; + phaseCompFraction.value[m_phaseLiquidIndex][m_waterIndex] = 1.0; + // Set the global composition as the composition of the active phase + for( integer ic = 0; ic < numComps; ++ic ) + { + phaseCompFraction.value[activePhase][ic] = compFraction[ic]; + phaseCompFraction.derivs[activePhase][ic][Deriv::dC+ic] = 1.0; + } } } diff --git a/src/coreComponents/unitTests/constitutiveTests/CMakeLists.txt b/src/coreComponents/unitTests/constitutiveTests/CMakeLists.txt index 408bc222462..5ec347f77c6 100644 --- a/src/coreComponents/unitTests/constitutiveTests/CMakeLists.txt +++ b/src/coreComponents/unitTests/constitutiveTests/CMakeLists.txt @@ -19,7 +19,9 @@ set( gtest_triaxial_xmls set( gtest_pvt_xmls testPVT.xml - testPVT_PhaseComposition.xml ) + testPVT_CO2Brine.xml + testPVT_PhaseComposition.xml + ) set( gtest_reactivefluid_xmls testReactiveFluid.xml ) diff --git a/src/coreComponents/unitTests/constitutiveTests/testCO2BrinePVTModels.cpp b/src/coreComponents/unitTests/constitutiveTests/testCO2BrinePVTModels.cpp index 5012287a2f1..723a30af1da 100644 --- a/src/coreComponents/unitTests/constitutiveTests/testCO2BrinePVTModels.cpp +++ b/src/coreComponents/unitTests/constitutiveTests/testCO2BrinePVTModels.cpp @@ -216,7 +216,8 @@ void testNumericalDerivatives( FLASH_WRAPPER const & flashModelWrapper, real64 const temperature, arraySlice1d< real64 const > const & compFraction, real64 const perturbParameter, - real64 const relTol ) + real64 const relTol, + real64 const absTol = std::numeric_limits< real64 >::epsilon() ) { using namespace multifluid; using Deriv = multifluid::DerivativeOffset; @@ -312,12 +313,12 @@ void testNumericalDerivatives( FLASH_WRAPPER const & flashModelWrapper, { checkRelativeError( (perturbedPhaseFracAndDeriv.value[j]-phaseFracAndDeriv.value[j])/dC, phaseFracAndDeriv.derivs[j][Deriv::dC+i], - relTol ); + relTol, absTol ); for( integer k = 0; k < numComp; ++k ) { checkRelativeError( (perturbedPhaseCompFracAndDeriv.value[j][k]-phaseCompFracAndDeriv.value[j][k])/dC, phaseCompFracAndDeriv.derivs[j][k][Deriv::dC+i], - relTol ); + relTol, absTol ); } } } @@ -876,8 +877,9 @@ TEST_F( CO2SolubilityTest, co2SolubilityValuesAndDeriv ) real64 const deltaComp = 0.2; real64 const eps = sqrt( std::numeric_limits< real64 >::epsilon()); - real64 const relTolPrevImpl = 5e-4; - real64 const relTolDeriv = 5e-5; + real64 constexpr relTolPrevImpl = 5e-4; + real64 constexpr relTolDeriv = 5e-5; + real64 constexpr absTolDeriv = 1.0e-7; real64 const savedGasPhaseFrac[] = { 0.298158785, 0.298183347, 0.2982033821, 0.295950309, 0.2959791448, 0.2960026365, 0.2926988393, 0.292724834, 0.2927459702, 0.499837295, 0.499854799, 0.4998690769, 0.4982634386, 0.4982839883, @@ -901,7 +903,7 @@ TEST_F( CO2SolubilityTest, co2SolubilityValuesAndDeriv ) testValuesAgainstPreviousImplementation( flashModelWrapper, P[iPres], TC[iTemp], comp, savedGasPhaseFrac[counter], savedWaterPhaseGasComp[counter], relTolPrevImpl ); - testNumericalDerivatives( flashModelWrapper, P[iPres], TC[iTemp], comp, eps, relTolDeriv ); + testNumericalDerivatives( flashModelWrapper, P[iPres], TC[iTemp], comp, eps, relTolDeriv, absTolDeriv ); counter++; } } diff --git a/src/coreComponents/unitTests/constitutiveTests/testPVT_CO2.txt b/src/coreComponents/unitTests/constitutiveTests/testPVT_CO2.txt index 86b4d12b8dc..ee1e6fa8ebe 100644 --- a/src/coreComponents/unitTests/constitutiveTests/testPVT_CO2.txt +++ b/src/coreComponents/unitTests/constitutiveTests/testPVT_CO2.txt @@ -5,53 +5,53 @@ # columns 5-6 = phase fractions # columns 7-8 = phase densities # columns 9-10 = phase viscosities -0.0000e+00 1.0000e+06 3.5000e+02 1.5581e+01 1.0000e+00 4.1138e-11 1.5581e+01 1.0033e+03 1.7476e-05 9.9525e-04 -2.0408e-02 2.0000e+06 3.5000e+02 3.2165e+01 1.0000e+00 4.1359e-11 3.2165e+01 1.0050e+03 1.7601e-05 9.9525e-04 -4.0816e-02 3.0000e+06 3.5000e+02 4.9901e+01 1.0000e+00 4.1563e-11 4.9901e+01 1.0066e+03 1.7778e-05 9.9525e-04 -6.1224e-02 4.0000e+06 3.5000e+02 6.8976e+01 1.0000e+00 4.1749e-11 6.8976e+01 1.0081e+03 1.8019e-05 9.9525e-04 -8.1633e-02 5.0000e+06 3.5000e+02 8.9619e+01 1.0000e+00 4.1919e-11 8.9619e+01 1.0096e+03 1.8338e-05 9.9525e-04 -1.0204e-01 6.0000e+06 3.5000e+02 1.1212e+02 1.0000e+00 4.2074e-11 1.1212e+02 1.0109e+03 1.8757e-05 9.9525e-04 -1.2245e-01 7.0000e+06 3.5000e+02 1.3682e+02 1.0000e+00 4.2213e-11 1.3682e+02 1.0122e+03 1.9300e-05 9.9525e-04 -1.4286e-01 8.0000e+06 3.5000e+02 1.6416e+02 1.0000e+00 4.2339e-11 1.6416e+02 1.0134e+03 2.0004e-05 9.9525e-04 -1.6327e-01 9.0000e+06 3.5000e+02 1.9463e+02 1.0000e+00 4.2450e-11 1.9463e+02 1.0145e+03 2.0915e-05 9.9525e-04 -1.8367e-01 1.0000e+07 3.5000e+02 2.2880e+02 1.0000e+00 4.2549e-11 2.2880e+02 1.0156e+03 2.2097e-05 9.9525e-04 -2.0408e-01 1.1000e+07 3.5000e+02 2.6713e+02 1.0000e+00 4.2635e-11 2.6713e+02 1.0166e+03 2.3623e-05 9.9525e-04 -2.2449e-01 1.2000e+07 3.5000e+02 3.0970e+02 1.0000e+00 4.2710e-11 3.0970e+02 1.0175e+03 2.5569e-05 9.9525e-04 -2.4490e-01 1.3000e+07 3.5000e+02 3.5571e+02 1.0000e+00 4.2774e-11 3.5571e+02 1.0184e+03 2.7974e-05 9.9525e-04 -2.6531e-01 1.4000e+07 3.5000e+02 4.0315e+02 1.0000e+00 4.2830e-11 4.0315e+02 1.0193e+03 3.0787e-05 9.9525e-04 -2.8571e-01 1.5000e+07 3.5000e+02 4.4920e+02 1.0000e+00 4.2878e-11 4.4920e+02 1.0201e+03 3.3852e-05 9.9525e-04 -3.0612e-01 1.6000e+07 3.5000e+02 4.9148e+02 1.0000e+00 4.2921e-11 4.9148e+02 1.0209e+03 3.6971e-05 9.9525e-04 -3.2653e-01 1.7000e+07 3.5000e+02 5.2886e+02 1.0000e+00 4.2959e-11 5.2886e+02 1.0216e+03 3.9987e-05 9.9525e-04 -3.4694e-01 1.8000e+07 3.5000e+02 5.6137e+02 1.0000e+00 4.2994e-11 5.6137e+02 1.0224e+03 4.2821e-05 9.9525e-04 -3.6735e-01 1.9000e+07 3.5000e+02 5.8957e+02 1.0000e+00 4.3027e-11 5.8957e+02 1.0231e+03 4.5454e-05 9.9525e-04 -3.8776e-01 2.0000e+07 3.5000e+02 6.1418e+02 1.0000e+00 4.3057e-11 6.1418e+02 1.0238e+03 4.7891e-05 9.9525e-04 -4.0816e-01 2.1000e+07 3.5000e+02 6.3583e+02 1.0000e+00 4.3086e-11 6.3583e+02 1.0245e+03 5.0154e-05 9.9525e-04 -4.2857e-01 2.2000e+07 3.5000e+02 6.5507e+02 1.0000e+00 4.3114e-11 6.5507e+02 1.0252e+03 5.2266e-05 9.9525e-04 -4.4898e-01 2.3000e+07 3.5000e+02 6.7233e+02 1.0000e+00 4.3140e-11 6.7233e+02 1.0259e+03 5.4246e-05 9.9525e-04 -4.6939e-01 2.4000e+07 3.5000e+02 6.8796e+02 1.0000e+00 4.3166e-11 6.8796e+02 1.0266e+03 5.6114e-05 9.9525e-04 -4.8980e-01 2.5000e+07 3.5000e+02 7.0222e+02 1.0000e+00 4.3191e-11 7.0222e+02 1.0273e+03 5.7883e-05 9.9525e-04 -5.1020e-01 2.6000e+07 3.5000e+02 7.1531e+02 1.0000e+00 4.3215e-11 7.1531e+02 1.0280e+03 5.9568e-05 9.9525e-04 -5.3061e-01 2.7000e+07 3.5000e+02 7.2741e+02 1.0000e+00 4.3239e-11 7.2741e+02 1.0287e+03 6.1177e-05 9.9525e-04 -5.5102e-01 2.8000e+07 3.5000e+02 7.3865e+02 1.0000e+00 4.3262e-11 7.3865e+02 1.0294e+03 6.2721e-05 9.9525e-04 -5.7143e-01 2.9000e+07 3.5000e+02 7.4914e+02 1.0000e+00 4.3284e-11 7.4914e+02 1.0300e+03 6.4206e-05 9.9525e-04 -5.9184e-01 3.0000e+07 3.5000e+02 7.5898e+02 1.0000e+00 4.3306e-11 7.5898e+02 1.0307e+03 6.5640e-05 9.9525e-04 -6.1224e-01 3.1000e+07 3.5000e+02 7.6825e+02 1.0000e+00 4.3328e-11 7.6825e+02 1.0314e+03 6.7027e-05 9.9525e-04 -6.3265e-01 3.2000e+07 3.5000e+02 7.7700e+02 1.0000e+00 4.3349e-11 7.7700e+02 1.0320e+03 6.8372e-05 9.9525e-04 -6.5306e-01 3.3000e+07 3.5000e+02 7.8529e+02 1.0000e+00 4.3370e-11 7.8529e+02 1.0327e+03 6.9679e-05 9.9525e-04 -6.7347e-01 3.4000e+07 3.5000e+02 7.9317e+02 1.0000e+00 4.3391e-11 7.9317e+02 1.0333e+03 7.0953e-05 9.9525e-04 -6.9388e-01 3.5000e+07 3.5000e+02 8.0068e+02 1.0000e+00 4.3411e-11 8.0068e+02 1.0340e+03 7.2195e-05 9.9525e-04 -7.1429e-01 3.6000e+07 3.5000e+02 8.0785e+02 1.0000e+00 4.3431e-11 8.0785e+02 1.0346e+03 7.3409e-05 9.9525e-04 -7.3469e-01 3.7000e+07 3.5000e+02 8.1472e+02 1.0000e+00 4.3450e-11 8.1472e+02 1.0353e+03 7.4597e-05 9.9525e-04 -7.5510e-01 3.8000e+07 3.5000e+02 8.2130e+02 1.0000e+00 4.3470e-11 8.2130e+02 1.0359e+03 7.5761e-05 9.9525e-04 -7.7551e-01 3.9000e+07 3.5000e+02 8.2763e+02 1.0000e+00 4.3489e-11 8.2763e+02 1.0366e+03 7.6904e-05 9.9525e-04 -7.9592e-01 4.0000e+07 3.5000e+02 8.3372e+02 1.0000e+00 4.3508e-11 8.3372e+02 1.0372e+03 7.8025e-05 9.9525e-04 -8.1633e-01 4.1000e+07 3.5000e+02 8.3960e+02 1.0000e+00 4.3527e-11 8.3960e+02 1.0378e+03 7.9128e-05 9.9525e-04 -8.3673e-01 4.2000e+07 3.5000e+02 8.4526e+02 1.0000e+00 4.3545e-11 8.4526e+02 1.0385e+03 8.0214e-05 9.9525e-04 -8.5714e-01 4.3000e+07 3.5000e+02 8.5074e+02 1.0000e+00 4.3564e-11 8.5074e+02 1.0391e+03 8.1283e-05 9.9525e-04 -8.7755e-01 4.4000e+07 3.5000e+02 8.5605e+02 1.0000e+00 4.3582e-11 8.5605e+02 1.0398e+03 8.2337e-05 9.9525e-04 -8.9796e-01 4.5000e+07 3.5000e+02 8.6119e+02 1.0000e+00 4.3600e-11 8.6119e+02 1.0404e+03 8.3376e-05 9.9525e-04 -9.1837e-01 4.6000e+07 3.5000e+02 8.6617e+02 1.0000e+00 4.3618e-11 8.6617e+02 1.0410e+03 8.4403e-05 9.9525e-04 -9.3878e-01 4.7000e+07 3.5000e+02 8.7102e+02 1.0000e+00 4.3636e-11 8.7102e+02 1.0416e+03 8.5416e-05 9.9525e-04 -9.5918e-01 4.8000e+07 3.5000e+02 8.7572e+02 1.0000e+00 4.3653e-11 8.7572e+02 1.0423e+03 8.6418e-05 9.9525e-04 -9.7959e-01 4.9000e+07 3.5000e+02 8.8030e+02 1.0000e+00 4.3670e-11 8.8030e+02 1.0429e+03 8.7409e-05 9.9525e-04 -1.0000e+00 5.0000e+07 3.5000e+02 8.8476e+02 1.0000e+00 4.3688e-11 8.8476e+02 1.0435e+03 8.8389e-05 9.9525e-04 +0.0000e+00 1.0000e+06 3.5000e+02 1.5581e+01 1.0000e+00 0.0000e+00 1.5581e+01 1.0022e+03 1.7476e-05 4.1330e-04 +2.0408e-02 2.0000e+06 3.5000e+02 3.2165e+01 1.0000e+00 0.0000e+00 3.2165e+01 1.0028e+03 1.7601e-05 4.1330e-04 +4.0816e-02 3.0000e+06 3.5000e+02 4.9901e+01 1.0000e+00 0.0000e+00 4.9901e+01 1.0034e+03 1.7778e-05 4.1330e-04 +6.1224e-02 4.0000e+06 3.5000e+02 6.8976e+01 1.0000e+00 0.0000e+00 6.8976e+01 1.0041e+03 1.8019e-05 4.1330e-04 +8.1633e-02 5.0000e+06 3.5000e+02 8.9619e+01 1.0000e+00 0.0000e+00 8.9619e+01 1.0047e+03 1.8338e-05 4.1330e-04 +1.0204e-01 6.0000e+06 3.5000e+02 1.1212e+02 1.0000e+00 0.0000e+00 1.1212e+02 1.0053e+03 1.8757e-05 4.1330e-04 +1.2245e-01 7.0000e+06 3.5000e+02 1.3682e+02 1.0000e+00 0.0000e+00 1.3682e+02 1.0059e+03 1.9300e-05 4.1330e-04 +1.4286e-01 8.0000e+06 3.5000e+02 1.6416e+02 1.0000e+00 0.0000e+00 1.6416e+02 1.0065e+03 2.0004e-05 4.1330e-04 +1.6327e-01 9.0000e+06 3.5000e+02 1.9463e+02 1.0000e+00 0.0000e+00 1.9463e+02 1.0072e+03 2.0915e-05 4.1330e-04 +1.8367e-01 1.0000e+07 3.5000e+02 2.2880e+02 1.0000e+00 0.0000e+00 2.2880e+02 1.0078e+03 2.2097e-05 4.1330e-04 +2.0408e-01 1.1000e+07 3.5000e+02 2.6713e+02 1.0000e+00 0.0000e+00 2.6713e+02 1.0084e+03 2.3623e-05 4.1330e-04 +2.2449e-01 1.2000e+07 3.5000e+02 3.0970e+02 1.0000e+00 0.0000e+00 3.0970e+02 1.0090e+03 2.5569e-05 4.1330e-04 +2.4490e-01 1.3000e+07 3.5000e+02 3.5571e+02 1.0000e+00 0.0000e+00 3.5571e+02 1.0096e+03 2.7974e-05 4.1330e-04 +2.6531e-01 1.4000e+07 3.5000e+02 4.0315e+02 1.0000e+00 0.0000e+00 4.0315e+02 1.0102e+03 3.0787e-05 4.1330e-04 +2.8571e-01 1.5000e+07 3.5000e+02 4.4920e+02 1.0000e+00 0.0000e+00 4.4920e+02 1.0108e+03 3.3852e-05 4.1330e-04 +3.0612e-01 1.6000e+07 3.5000e+02 4.9148e+02 1.0000e+00 0.0000e+00 4.9148e+02 1.0114e+03 3.6971e-05 4.1330e-04 +3.2653e-01 1.7000e+07 3.5000e+02 5.2886e+02 1.0000e+00 0.0000e+00 5.2886e+02 1.0120e+03 3.9987e-05 4.1330e-04 +3.4694e-01 1.8000e+07 3.5000e+02 5.6137e+02 1.0000e+00 0.0000e+00 5.6137e+02 1.0126e+03 4.2821e-05 4.1330e-04 +3.6735e-01 1.9000e+07 3.5000e+02 5.8957e+02 1.0000e+00 0.0000e+00 5.8957e+02 1.0133e+03 4.5454e-05 4.1330e-04 +3.8776e-01 2.0000e+07 3.5000e+02 6.1418e+02 1.0000e+00 0.0000e+00 6.1418e+02 1.0139e+03 4.7891e-05 4.1330e-04 +4.0816e-01 2.1000e+07 3.5000e+02 6.3583e+02 1.0000e+00 0.0000e+00 6.3583e+02 1.0145e+03 5.0154e-05 4.1330e-04 +4.2857e-01 2.2000e+07 3.5000e+02 6.5507e+02 1.0000e+00 0.0000e+00 6.5507e+02 1.0151e+03 5.2266e-05 4.1330e-04 +4.4898e-01 2.3000e+07 3.5000e+02 6.7233e+02 1.0000e+00 0.0000e+00 6.7233e+02 1.0157e+03 5.4246e-05 4.1330e-04 +4.6939e-01 2.4000e+07 3.5000e+02 6.8796e+02 1.0000e+00 0.0000e+00 6.8796e+02 1.0163e+03 5.6114e-05 4.1330e-04 +4.8980e-01 2.5000e+07 3.5000e+02 7.0222e+02 1.0000e+00 0.0000e+00 7.0222e+02 1.0169e+03 5.7883e-05 4.1330e-04 +5.1020e-01 2.6000e+07 3.5000e+02 7.1531e+02 1.0000e+00 0.0000e+00 7.1531e+02 1.0174e+03 5.9568e-05 4.1330e-04 +5.3061e-01 2.7000e+07 3.5000e+02 7.2741e+02 1.0000e+00 0.0000e+00 7.2741e+02 1.0180e+03 6.1177e-05 4.1330e-04 +5.5102e-01 2.8000e+07 3.5000e+02 7.3865e+02 1.0000e+00 0.0000e+00 7.3865e+02 1.0186e+03 6.2721e-05 4.1330e-04 +5.7143e-01 2.9000e+07 3.5000e+02 7.4914e+02 1.0000e+00 0.0000e+00 7.4914e+02 1.0192e+03 6.4206e-05 4.1330e-04 +5.9184e-01 3.0000e+07 3.5000e+02 7.5898e+02 1.0000e+00 0.0000e+00 7.5898e+02 1.0198e+03 6.5640e-05 4.1330e-04 +6.1224e-01 3.1000e+07 3.5000e+02 7.6825e+02 1.0000e+00 0.0000e+00 7.6825e+02 1.0204e+03 6.7027e-05 4.1330e-04 +6.3265e-01 3.2000e+07 3.5000e+02 7.7700e+02 1.0000e+00 0.0000e+00 7.7700e+02 1.0210e+03 6.8372e-05 4.1330e-04 +6.5306e-01 3.3000e+07 3.5000e+02 7.8529e+02 1.0000e+00 0.0000e+00 7.8529e+02 1.0216e+03 6.9679e-05 4.1330e-04 +6.7347e-01 3.4000e+07 3.5000e+02 7.9317e+02 1.0000e+00 0.0000e+00 7.9317e+02 1.0222e+03 7.0953e-05 4.1330e-04 +6.9388e-01 3.5000e+07 3.5000e+02 8.0068e+02 1.0000e+00 0.0000e+00 8.0068e+02 1.0228e+03 7.2195e-05 4.1330e-04 +7.1429e-01 3.6000e+07 3.5000e+02 8.0785e+02 1.0000e+00 0.0000e+00 8.0785e+02 1.0233e+03 7.3409e-05 4.1330e-04 +7.3469e-01 3.7000e+07 3.5000e+02 8.1472e+02 1.0000e+00 0.0000e+00 8.1472e+02 1.0239e+03 7.4597e-05 4.1330e-04 +7.5510e-01 3.8000e+07 3.5000e+02 8.2130e+02 1.0000e+00 0.0000e+00 8.2130e+02 1.0245e+03 7.5761e-05 4.1330e-04 +7.7551e-01 3.9000e+07 3.5000e+02 8.2763e+02 1.0000e+00 0.0000e+00 8.2763e+02 1.0251e+03 7.6904e-05 4.1330e-04 +7.9592e-01 4.0000e+07 3.5000e+02 8.3372e+02 1.0000e+00 0.0000e+00 8.3372e+02 1.0257e+03 7.8025e-05 4.1330e-04 +8.1633e-01 4.1000e+07 3.5000e+02 8.3960e+02 1.0000e+00 0.0000e+00 8.3960e+02 1.0263e+03 7.9128e-05 4.1330e-04 +8.3673e-01 4.2000e+07 3.5000e+02 8.4526e+02 1.0000e+00 0.0000e+00 8.4526e+02 1.0268e+03 8.0214e-05 4.1330e-04 +8.5714e-01 4.3000e+07 3.5000e+02 8.5074e+02 1.0000e+00 0.0000e+00 8.5074e+02 1.0274e+03 8.1283e-05 4.1330e-04 +8.7755e-01 4.4000e+07 3.5000e+02 8.5605e+02 1.0000e+00 0.0000e+00 8.5605e+02 1.0280e+03 8.2337e-05 4.1330e-04 +8.9796e-01 4.5000e+07 3.5000e+02 8.6119e+02 1.0000e+00 0.0000e+00 8.6119e+02 1.0286e+03 8.3376e-05 4.1330e-04 +9.1837e-01 4.6000e+07 3.5000e+02 8.6617e+02 1.0000e+00 0.0000e+00 8.6617e+02 1.0292e+03 8.4403e-05 4.1330e-04 +9.3878e-01 4.7000e+07 3.5000e+02 8.7102e+02 1.0000e+00 0.0000e+00 8.7102e+02 1.0297e+03 8.5416e-05 4.1330e-04 +9.5918e-01 4.8000e+07 3.5000e+02 8.7572e+02 1.0000e+00 0.0000e+00 8.7572e+02 1.0303e+03 8.6418e-05 4.1330e-04 +9.7959e-01 4.9000e+07 3.5000e+02 8.8030e+02 1.0000e+00 0.0000e+00 8.8030e+02 1.0309e+03 8.7409e-05 4.1330e-04 +1.0000e+00 5.0000e+07 3.5000e+02 8.8476e+02 1.0000e+00 0.0000e+00 8.8476e+02 1.0315e+03 8.8389e-05 4.1330e-04 diff --git a/src/coreComponents/unitTests/constitutiveTests/testPVT_CO2Brine.xml b/src/coreComponents/unitTests/constitutiveTests/testPVT_CO2Brine.xml new file mode 100644 index 00000000000..7363117393c --- /dev/null +++ b/src/coreComponents/unitTests/constitutiveTests/testPVT_CO2Brine.xml @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/coreComponents/unitTests/constitutiveTests/testPVT_CO2Brine_testCo2BrineEzrokhiMixtureA.txt b/src/coreComponents/unitTests/constitutiveTests/testPVT_CO2Brine_testCo2BrineEzrokhiMixtureA.txt new file mode 100644 index 00000000000..9df0c13cef2 --- /dev/null +++ b/src/coreComponents/unitTests/constitutiveTests/testPVT_CO2Brine_testCo2BrineEzrokhiMixtureA.txt @@ -0,0 +1,31 @@ +# column 1 = time +# column 2 = pressure +# column 3 = temperature +# column 4 = density +# column 5 = total compressibility +# columns 6-7 = phase fractions +# columns 8-9 = phase densities +# columns 10-11 = phase viscosities +# columns 12-13 = gas phase fractions [co2, water] +# columns 14-15 = water phase fractions [co2, water] +0.0000e+00 1.0000e+06 3.5000e+02 7.0237e+01 2.5631e-11 2.0920e-01 7.9080e-01 1.5581e+01 9.7518e+02 1.7476e-05 3.6959e-04 1.0000e+00 0.0000e+00 2.2780e-03 9.9772e-01 +5.0000e-02 6.4444e+06 3.5000e+02 4.2182e+02 1.3896e-07 1.9034e-01 8.0966e-01 1.2309e+02 9.8215e+02 1.8998e-05 3.6959e-04 1.0000e+00 0.0000e+00 1.1952e-02 9.8805e-01 +1.0000e-01 1.1889e+07 3.5000e+02 7.0391e+02 6.0490e-08 1.7984e-01 8.2016e-01 3.0497e+02 9.8703e+02 2.5353e-05 3.6959e-04 1.0000e+00 0.0000e+00 1.7255e-02 9.8275e-01 +1.5000e-01 1.7333e+07 3.5000e+02 8.6432e+02 1.7707e-08 1.7488e-01 8.2512e-01 5.3970e+02 9.9060e+02 4.0932e-05 3.6959e-04 1.0000e+00 0.0000e+00 1.9739e-02 9.8026e-01 +2.0000e-01 2.2778e+07 3.5000e+02 9.1703e+02 6.6890e-09 1.7189e-01 8.2811e-01 6.6850e+02 9.9372e+02 5.3806e-05 3.6959e-04 1.0000e+00 0.0000e+00 2.1232e-02 9.7877e-01 +2.5000e-01 2.8222e+07 3.5000e+02 9.4163e+02 3.5690e-09 1.6949e-01 8.3051e-01 7.4098e+02 9.9671e+02 6.3051e-05 3.6959e-04 1.0000e+00 0.0000e+00 2.2429e-02 9.7757e-01 +3.0000e-01 3.3667e+07 3.5000e+02 9.5727e+02 2.5032e-09 1.6736e-01 8.3264e-01 7.9054e+02 9.9964e+02 7.0528e-05 3.6959e-04 1.0000e+00 0.0000e+00 2.3484e-02 9.7652e-01 +3.5000e-01 3.9111e+07 3.5000e+02 9.6883e+02 1.8830e-09 1.6542e-01 8.3458e-01 8.2831e+02 1.0025e+03 7.7028e-05 3.6959e-04 1.0000e+00 0.0000e+00 2.4448e-02 9.7555e-01 +4.0000e-01 4.4556e+07 3.5000e+02 9.7812e+02 1.5612e-09 1.6360e-01 8.3640e-01 8.5890e+02 1.0054e+03 8.2914e-05 3.6959e-04 1.0000e+00 0.0000e+00 2.5348e-02 9.7465e-01 +4.5000e-01 5.0000e+07 3.5000e+02 9.8599e+02 3.6882e-10 1.6188e-01 8.3812e-01 8.8476e+02 1.0083e+03 8.8389e-05 3.6959e-04 1.0000e+00 0.0000e+00 2.6197e-02 9.7380e-01 +5.0000e-01 3.0000e+07 3.0400e+02 9.7666e+02 2.5443e-09 1.6783e-01 8.3217e-01 8.0121e+02 1.0218e+03 7.1971e-05 7.8492e-04 1.0000e+00 0.0000e+00 2.3250e-02 9.7675e-01 +5.5000e-01 1.0000e+07 2.5800e+02 6.6446e+02 8.1110e-08 1.8014e-01 8.1986e-01 2.5862e+02 1.0141e+03 2.2796e-05 1.7914e-03 1.0000e+00 0.0000e+00 1.7105e-02 9.8290e-01 +6.0000e-01 1.0000e+07 2.7156e+02 6.6447e+02 8.1111e-08 1.8014e-01 8.1986e-01 2.5862e+02 1.0142e+03 2.2796e-05 1.7914e-03 1.0000e+00 0.0000e+00 1.7105e-02 9.8290e-01 +6.5000e-01 1.0000e+07 2.8511e+02 6.6429e+02 8.1088e-08 1.8014e-01 8.1986e-01 2.5862e+02 1.0136e+03 2.2796e-05 1.2463e-03 1.0000e+00 0.0000e+00 1.7105e-02 9.8290e-01 +7.0000e-01 1.0000e+07 2.9867e+02 6.6334e+02 8.0969e-08 1.8014e-01 8.1986e-01 2.5862e+02 1.0110e+03 2.2796e-05 8.8041e-04 1.0000e+00 0.0000e+00 1.7105e-02 9.8290e-01 +7.5000e-01 1.0000e+07 3.1222e+02 6.6170e+02 8.0763e-08 1.8014e-01 8.1986e-01 2.5862e+02 1.0063e+03 2.2796e-05 6.6611e-04 1.0000e+00 0.0000e+00 1.7105e-02 9.8290e-01 +8.0000e-01 1.0000e+07 3.2578e+02 6.5950e+02 8.0485e-08 1.8014e-01 8.1986e-01 2.5862e+02 1.0001e+03 2.2796e-05 5.2535e-04 1.0000e+00 0.0000e+00 1.7105e-02 9.8290e-01 +8.5000e-01 1.0000e+07 3.3933e+02 6.5685e+02 8.0151e-08 1.8014e-01 8.1986e-01 2.5862e+02 9.9273e+02 2.2796e-05 4.2735e-04 1.0000e+00 0.0000e+00 1.7105e-02 9.8290e-01 +9.0000e-01 1.0000e+07 3.5289e+02 6.0588e+02 7.6868e-08 1.8266e-01 8.1734e-01 2.2287e+02 9.8369e+02 2.2020e-05 3.5529e-04 1.0000e+00 0.0000e+00 1.5834e-02 9.8417e-01 +9.5000e-01 1.0000e+07 3.6644e+02 5.8269e+02 7.5133e-08 1.8266e-01 8.1734e-01 2.0825e+02 9.7414e+02 2.1800e-05 3.0346e-04 1.0000e+00 0.0000e+00 1.5834e-02 9.8417e-01 +1.0000e+00 1.0000e+07 3.8000e+02 5.7962e+02 7.4722e-08 1.8266e-01 8.1734e-01 2.0825e+02 9.6370e+02 2.1860e-05 2.6310e-04 1.0000e+00 0.0000e+00 1.5834e-02 9.8417e-01 diff --git a/src/coreComponents/unitTests/constitutiveTests/testPVT_CO2Brine_testCo2BrineEzrokhiMixtureB.txt b/src/coreComponents/unitTests/constitutiveTests/testPVT_CO2Brine_testCo2BrineEzrokhiMixtureB.txt new file mode 100644 index 00000000000..658585d20f6 --- /dev/null +++ b/src/coreComponents/unitTests/constitutiveTests/testPVT_CO2Brine_testCo2BrineEzrokhiMixtureB.txt @@ -0,0 +1,31 @@ +# column 1 = time +# column 2 = pressure +# column 3 = temperature +# column 4 = density +# column 5 = total compressibility +# columns 6-7 = phase fractions +# columns 8-9 = phase densities +# columns 10-11 = phase viscosities +# columns 12-13 = gas phase fractions [co2, water] +# columns 14-15 = water phase fractions [co2, water] +0.0000e+00 1.0000e+06 3.5000e+02 3.0153e+01 6.8331e-12 5.0890e-01 4.9110e-01 1.5581e+01 9.7518e+02 1.7476e-05 3.6959e-04 1.0000e+00 0.0000e+00 2.2780e-03 9.9772e-01 +5.0000e-02 6.4444e+06 3.5000e+02 2.1973e+02 1.8071e-07 4.9719e-01 5.0281e-01 1.2309e+02 9.8215e+02 1.8998e-05 3.6959e-04 1.0000e+00 0.0000e+00 1.1952e-02 9.8805e-01 +1.0000e-01 1.1889e+07 3.5000e+02 4.7060e+02 1.0674e-07 4.9067e-01 5.0933e-01 3.0497e+02 9.8703e+02 2.5353e-05 3.6959e-04 1.0000e+00 0.0000e+00 1.7255e-02 9.8275e-01 +1.5000e-01 1.7333e+07 3.5000e+02 7.0387e+02 3.8725e-08 4.8759e-01 5.1241e-01 5.3970e+02 9.9060e+02 4.0932e-05 3.6959e-04 1.0000e+00 0.0000e+00 1.9739e-02 9.8026e-01 +2.0000e-01 2.2778e+07 3.5000e+02 8.0378e+02 1.5410e-08 4.8573e-01 5.1427e-01 6.6850e+02 9.9372e+02 5.3806e-05 3.6959e-04 1.0000e+00 0.0000e+00 2.1232e-02 9.7877e-01 +2.5000e-01 2.8222e+07 3.5000e+02 8.5399e+02 8.1962e-09 4.8424e-01 5.1576e-01 7.4098e+02 9.9671e+02 6.3051e-05 3.6959e-04 1.0000e+00 0.0000e+00 2.2429e-02 9.7757e-01 +3.0000e-01 3.3667e+07 3.5000e+02 8.8642e+02 5.6757e-09 4.8292e-01 5.1708e-01 7.9054e+02 9.9964e+02 7.0528e-05 3.6959e-04 1.0000e+00 0.0000e+00 2.3484e-02 9.7652e-01 +3.5000e-01 3.9111e+07 3.5000e+02 9.1030e+02 4.1615e-09 4.8171e-01 5.1829e-01 8.2831e+02 1.0025e+03 7.7028e-05 3.6959e-04 1.0000e+00 0.0000e+00 2.4448e-02 9.7555e-01 +4.0000e-01 4.4556e+07 3.5000e+02 9.2924e+02 3.3747e-09 4.8058e-01 5.1942e-01 8.5890e+02 1.0054e+03 8.2914e-05 3.6959e-04 1.0000e+00 0.0000e+00 2.5348e-02 9.7465e-01 +4.5000e-01 5.0000e+07 3.5000e+02 9.4501e+02 2.1952e-10 4.7951e-01 5.2049e-01 8.8476e+02 1.0083e+03 8.8389e-05 3.6959e-04 1.0000e+00 0.0000e+00 2.6197e-02 9.7380e-01 +5.0000e-01 3.0000e+07 3.0400e+02 9.0182e+02 5.7438e-09 4.8321e-01 5.1679e-01 8.0121e+02 1.0218e+03 7.1971e-05 7.8492e-04 1.0000e+00 0.0000e+00 2.3250e-02 9.7675e-01 +5.5000e-01 1.0000e+07 2.5800e+02 4.1666e+02 1.3361e-07 4.9085e-01 5.0915e-01 2.5862e+02 1.0141e+03 2.2796e-05 1.7914e-03 1.0000e+00 0.0000e+00 1.7105e-02 9.8290e-01 +6.0000e-01 1.0000e+07 2.7156e+02 4.1666e+02 1.3361e-07 4.9085e-01 5.0915e-01 2.5862e+02 1.0142e+03 2.2796e-05 1.7914e-03 1.0000e+00 0.0000e+00 1.7105e-02 9.8290e-01 +6.5000e-01 1.0000e+07 2.8511e+02 4.1662e+02 1.3360e-07 4.9085e-01 5.0915e-01 2.5862e+02 1.0136e+03 2.2796e-05 1.2463e-03 1.0000e+00 0.0000e+00 1.7105e-02 9.8290e-01 +7.0000e-01 1.0000e+07 2.9867e+02 4.1638e+02 1.3352e-07 4.9085e-01 5.0915e-01 2.5862e+02 1.0110e+03 2.2796e-05 8.8041e-04 1.0000e+00 0.0000e+00 1.7105e-02 9.8290e-01 +7.5000e-01 1.0000e+07 3.1222e+02 4.1598e+02 1.3339e-07 4.9085e-01 5.0915e-01 2.5862e+02 1.0063e+03 2.2796e-05 6.6611e-04 1.0000e+00 0.0000e+00 1.7105e-02 9.8290e-01 +8.0000e-01 1.0000e+07 3.2578e+02 4.1544e+02 1.3321e-07 4.9085e-01 5.0915e-01 2.5862e+02 1.0001e+03 2.2796e-05 5.2535e-04 1.0000e+00 0.0000e+00 1.7105e-02 9.8290e-01 +8.5000e-01 1.0000e+07 3.3933e+02 4.1479e+02 1.3300e-07 4.9085e-01 5.0915e-01 2.5862e+02 9.9273e+02 2.2796e-05 4.2735e-04 1.0000e+00 0.0000e+00 1.7105e-02 9.8290e-01 +9.0000e-01 1.0000e+07 3.5289e+02 3.6691e+02 1.2027e-07 4.9242e-01 5.0758e-01 2.2287e+02 9.8369e+02 2.2020e-05 3.5529e-04 1.0000e+00 0.0000e+00 1.5834e-02 9.8417e-01 +9.5000e-01 1.0000e+07 3.6644e+02 3.4654e+02 1.1513e-07 4.9242e-01 5.0758e-01 2.0825e+02 9.7414e+02 2.1800e-05 3.0346e-04 1.0000e+00 0.0000e+00 1.5834e-02 9.8417e-01 +1.0000e+00 1.0000e+07 3.8000e+02 3.4587e+02 1.1490e-07 4.9242e-01 5.0758e-01 2.0825e+02 9.6370e+02 2.1860e-05 2.6310e-04 1.0000e+00 0.0000e+00 1.5834e-02 9.8417e-01 diff --git a/src/coreComponents/unitTests/constitutiveTests/testPVT_CO2Brine_testCo2BrinePhillipsMixtureA.txt b/src/coreComponents/unitTests/constitutiveTests/testPVT_CO2Brine_testCo2BrinePhillipsMixtureA.txt new file mode 100644 index 00000000000..8906785dbfb --- /dev/null +++ b/src/coreComponents/unitTests/constitutiveTests/testPVT_CO2Brine_testCo2BrinePhillipsMixtureA.txt @@ -0,0 +1,31 @@ +# column 1 = time +# column 2 = pressure +# column 3 = temperature +# column 4 = density +# column 5 = total compressibility +# columns 6-7 = phase fractions +# columns 8-9 = phase densities +# columns 10-11 = phase viscosities +# columns 12-13 = gas phase fractions [co2, water] +# columns 14-15 = water phase fractions [co2, water] +0.0000e+00 1.0000e+06 3.5000e+02 7.0350e+01 -0.0000e+00 2.0920e-01 7.9080e-01 1.5581e+01 1.0033e+03 1.7476e-05 4.1330e-04 1.0000e+00 0.0000e+00 2.2780e-03 9.9772e-01 +5.0000e-02 6.4444e+06 3.5000e+02 4.2620e+02 1.4060e-07 1.9028e-01 8.0972e-01 1.2309e+02 1.0115e+03 1.8998e-05 4.1330e-04 1.0000e+00 0.0000e+00 1.1952e-02 9.8805e-01 +1.0000e-01 1.1889e+07 3.5000e+02 7.1657e+02 6.1766e-08 1.7971e-01 8.2029e-01 3.0497e+02 1.0174e+03 2.5353e-05 4.1330e-04 1.0000e+00 0.0000e+00 1.7255e-02 9.8275e-01 +1.5000e-01 1.7333e+07 3.5000e+02 8.8389e+02 1.8255e-08 1.7472e-01 8.2528e-01 5.3970e+02 1.0219e+03 4.0932e-05 4.1330e-04 1.0000e+00 0.0000e+00 1.9739e-02 9.8026e-01 +2.0000e-01 2.2778e+07 3.5000e+02 9.3956e+02 6.9892e-09 1.7171e-01 8.2829e-01 6.6850e+02 1.0258e+03 5.3806e-05 4.1330e-04 1.0000e+00 0.0000e+00 2.1232e-02 9.7877e-01 +2.5000e-01 2.8222e+07 3.5000e+02 9.6584e+02 3.7839e-09 1.6928e-01 8.3072e-01 7.4098e+02 1.0295e+03 6.3051e-05 4.1330e-04 1.0000e+00 0.0000e+00 2.2429e-02 9.7757e-01 +3.0000e-01 3.3667e+07 3.5000e+02 9.8271e+02 2.6836e-09 1.6714e-01 8.3286e-01 7.9054e+02 1.0331e+03 7.0528e-05 4.1330e-04 1.0000e+00 0.0000e+00 2.3484e-02 9.7652e-01 +3.5000e-01 3.9111e+07 3.5000e+02 9.9529e+02 2.0386e-09 1.6518e-01 8.3482e-01 8.2831e+02 1.0366e+03 7.7028e-05 4.1330e-04 1.0000e+00 0.0000e+00 2.4448e-02 9.7555e-01 +4.0000e-01 4.4556e+07 3.5000e+02 1.0055e+03 1.7012e-09 1.6334e-01 8.3666e-01 8.5890e+02 1.0401e+03 8.2914e-05 4.1330e-04 1.0000e+00 0.0000e+00 2.5348e-02 9.7465e-01 +4.5000e-01 5.0000e+07 3.5000e+02 1.0141e+03 -0.0000e+00 1.6160e-01 8.3840e-01 8.8476e+02 1.0435e+03 8.8389e-05 4.1330e-04 1.0000e+00 0.0000e+00 2.6197e-02 9.7380e-01 +5.0000e-01 3.0000e+07 3.0400e+02 9.8820e+02 2.6714e-09 1.6761e-01 8.3239e-01 8.0121e+02 1.0369e+03 7.1971e-05 8.6631e-04 1.0000e+00 0.0000e+00 2.3250e-02 9.7675e-01 +5.5000e-01 1.0000e+07 2.5800e+02 6.6631e+02 8.1373e-08 1.8000e-01 8.2000e-01 2.5862e+02 1.0189e+03 2.2796e-05 1.9511e-03 1.0000e+00 0.0000e+00 1.7105e-02 9.8290e-01 +6.0000e-01 1.0000e+07 2.7156e+02 6.6683e+02 8.1481e-08 1.8000e-01 8.2000e-01 2.5862e+02 1.0204e+03 2.2796e-05 1.9588e-03 1.0000e+00 0.0000e+00 1.7105e-02 9.8290e-01 +6.5000e-01 1.0000e+07 2.8511e+02 6.6723e+02 8.1565e-08 1.8001e-01 8.1999e-01 2.5862e+02 1.0216e+03 2.2796e-05 1.3681e-03 1.0000e+00 0.0000e+00 1.7105e-02 9.8290e-01 +7.0000e-01 1.0000e+07 2.9867e+02 6.6753e+02 8.1627e-08 1.8001e-01 8.1999e-01 2.5862e+02 1.0224e+03 2.2796e-05 9.7022e-04 1.0000e+00 0.0000e+00 1.7105e-02 9.8290e-01 +7.5000e-01 1.0000e+07 3.1222e+02 6.6772e+02 8.1666e-08 1.8001e-01 8.1999e-01 2.5862e+02 1.0230e+03 2.2796e-05 7.3691e-04 1.0000e+00 0.0000e+00 1.7105e-02 9.8290e-01 +8.0000e-01 1.0000e+07 3.2578e+02 6.6781e+02 8.1685e-08 1.8002e-01 8.1998e-01 2.5862e+02 1.0232e+03 2.2796e-05 5.8345e-04 1.0000e+00 0.0000e+00 1.7105e-02 9.8290e-01 +8.5000e-01 1.0000e+07 3.3933e+02 6.6780e+02 8.1682e-08 1.8002e-01 8.1998e-01 2.5862e+02 1.0232e+03 2.2796e-05 4.7645e-04 1.0000e+00 0.0000e+00 1.7105e-02 9.8290e-01 +9.0000e-01 1.0000e+07 3.5289e+02 6.1511e+02 7.8253e-08 1.8256e-01 8.1744e-01 2.2287e+02 1.0135e+03 2.2020e-05 3.9764e-04 1.0000e+00 0.0000e+00 1.5834e-02 9.8417e-01 +9.5000e-01 1.0000e+07 3.6644e+02 5.9255e+02 7.6620e-08 1.8256e-01 8.1744e-01 2.0825e+02 1.0080e+03 2.1800e-05 3.4093e-04 1.0000e+00 0.0000e+00 1.5834e-02 9.8417e-01 +1.0000e+00 1.0000e+07 3.8000e+02 5.9234e+02 7.6569e-08 1.8255e-01 8.1745e-01 2.0825e+02 1.0072e+03 2.1860e-05 2.9672e-04 1.0000e+00 0.0000e+00 1.5834e-02 9.8417e-01 diff --git a/src/coreComponents/unitTests/constitutiveTests/testPVT_CO2Brine_testCo2BrinePhillipsMixtureB.txt b/src/coreComponents/unitTests/constitutiveTests/testPVT_CO2Brine_testCo2BrinePhillipsMixtureB.txt new file mode 100644 index 00000000000..8bb1d224ed8 --- /dev/null +++ b/src/coreComponents/unitTests/constitutiveTests/testPVT_CO2Brine_testCo2BrinePhillipsMixtureB.txt @@ -0,0 +1,31 @@ +# column 1 = time +# column 2 = pressure +# column 3 = temperature +# column 4 = density +# column 5 = total compressibility +# columns 6-7 = phase fractions +# columns 8-9 = phase densities +# columns 10-11 = phase viscosities +# columns 12-13 = gas phase fractions [co2, water] +# columns 14-15 = water phase fractions [co2, water] +0.0000e+00 1.0000e+06 3.5000e+02 3.0166e+01 -0.0000e+00 5.0890e-01 4.9110e-01 1.5581e+01 1.0033e+03 1.7476e-05 4.1330e-04 1.0000e+00 0.0000e+00 2.2780e-03 9.9772e-01 +5.0000e-02 6.4444e+06 3.5000e+02 2.2049e+02 1.8140e-07 4.9709e-01 5.0291e-01 1.2309e+02 1.0115e+03 1.8998e-05 4.1330e-04 1.0000e+00 0.0000e+00 1.1952e-02 9.8805e-01 +1.0000e-01 1.1889e+07 3.5000e+02 4.7415e+02 1.0761e-07 4.9045e-01 5.0955e-01 3.0497e+02 1.0174e+03 2.5353e-05 4.1330e-04 1.0000e+00 0.0000e+00 1.7255e-02 9.8275e-01 +1.5000e-01 1.7333e+07 3.5000e+02 7.1192e+02 3.9232e-08 4.8731e-01 5.1269e-01 5.3970e+02 1.0219e+03 4.0932e-05 4.1330e-04 1.0000e+00 0.0000e+00 1.9739e-02 9.8026e-01 +2.0000e-01 2.2778e+07 3.5000e+02 8.1448e+02 1.5684e-08 4.8541e-01 5.1459e-01 6.6850e+02 1.0258e+03 5.3806e-05 4.1330e-04 1.0000e+00 0.0000e+00 2.1232e-02 9.7877e-01 +2.5000e-01 2.8222e+07 3.5000e+02 8.6629e+02 8.3806e-09 4.8387e-01 5.1613e-01 7.4098e+02 1.0295e+03 6.3051e-05 4.1330e-04 1.0000e+00 0.0000e+00 2.2429e-02 9.7757e-01 +3.0000e-01 3.3667e+07 3.5000e+02 8.9988e+02 5.8250e-09 4.8252e-01 5.1748e-01 7.9054e+02 1.0331e+03 7.0528e-05 4.1330e-04 1.0000e+00 0.0000e+00 2.3484e-02 9.7652e-01 +3.5000e-01 3.9111e+07 3.5000e+02 9.2471e+02 4.2862e-09 4.8127e-01 5.1873e-01 8.2831e+02 1.0366e+03 7.7028e-05 4.1330e-04 1.0000e+00 0.0000e+00 2.4448e-02 9.7555e-01 +4.0000e-01 4.4556e+07 3.5000e+02 9.4444e+02 3.4851e-09 4.8011e-01 5.1989e-01 8.5890e+02 1.0401e+03 8.2914e-05 4.1330e-04 1.0000e+00 0.0000e+00 2.5348e-02 9.7465e-01 +4.5000e-01 5.0000e+07 3.5000e+02 9.6092e+02 -0.0000e+00 4.7901e-01 5.2099e-01 8.8476e+02 1.0435e+03 8.8389e-05 4.1330e-04 1.0000e+00 0.0000e+00 2.6197e-02 9.7380e-01 +5.0000e-01 3.0000e+07 3.0400e+02 9.0796e+02 5.8367e-09 4.8281e-01 5.1719e-01 8.0121e+02 1.0369e+03 7.1971e-05 8.6631e-04 1.0000e+00 0.0000e+00 2.3250e-02 9.7675e-01 +5.5000e-01 1.0000e+07 2.5800e+02 4.1718e+02 1.3377e-07 4.9062e-01 5.0938e-01 2.5862e+02 1.0189e+03 2.2796e-05 1.9511e-03 1.0000e+00 0.0000e+00 1.7105e-02 9.8290e-01 +6.0000e-01 1.0000e+07 2.7156e+02 4.1731e+02 1.3383e-07 4.9063e-01 5.0937e-01 2.5862e+02 1.0204e+03 2.2796e-05 1.9588e-03 1.0000e+00 0.0000e+00 1.7105e-02 9.8290e-01 +6.5000e-01 1.0000e+07 2.8511e+02 4.1740e+02 1.3388e-07 4.9063e-01 5.0937e-01 2.5862e+02 1.0216e+03 2.2796e-05 1.3681e-03 1.0000e+00 0.0000e+00 1.7105e-02 9.8290e-01 +7.0000e-01 1.0000e+07 2.9867e+02 4.1747e+02 1.3391e-07 4.9064e-01 5.0936e-01 2.5862e+02 1.0224e+03 2.2796e-05 9.7022e-04 1.0000e+00 0.0000e+00 1.7105e-02 9.8290e-01 +7.5000e-01 1.0000e+07 3.1222e+02 4.1752e+02 1.3393e-07 4.9064e-01 5.0936e-01 2.5862e+02 1.0230e+03 2.2796e-05 7.3691e-04 1.0000e+00 0.0000e+00 1.7105e-02 9.8290e-01 +8.0000e-01 1.0000e+07 3.2578e+02 4.1754e+02 1.3394e-07 4.9064e-01 5.0936e-01 2.5862e+02 1.0232e+03 2.2796e-05 5.8345e-04 1.0000e+00 0.0000e+00 1.7105e-02 9.8290e-01 +8.5000e-01 1.0000e+07 3.3933e+02 4.1754e+02 1.3394e-07 4.9064e-01 5.0936e-01 2.5862e+02 1.0232e+03 2.2796e-05 4.7645e-04 1.0000e+00 0.0000e+00 1.7105e-02 9.8290e-01 +9.0000e-01 1.0000e+07 3.5289e+02 3.6904e+02 1.2104e-07 4.9224e-01 5.0776e-01 2.2287e+02 1.0135e+03 2.2020e-05 3.9764e-04 1.0000e+00 0.0000e+00 1.5834e-02 9.8417e-01 +9.5000e-01 1.0000e+07 3.6644e+02 3.4874e+02 1.1593e-07 4.9224e-01 5.0776e-01 2.0825e+02 1.0080e+03 2.1800e-05 3.4093e-04 1.0000e+00 0.0000e+00 1.5834e-02 9.8417e-01 +1.0000e+00 1.0000e+07 3.8000e+02 3.4869e+02 1.1591e-07 4.9223e-01 5.0777e-01 2.0825e+02 1.0072e+03 2.1860e-05 2.9672e-04 1.0000e+00 0.0000e+00 1.5834e-02 9.8417e-01 diff --git a/src/coreComponents/unitTests/constitutiveTests/testPVT_data/brinePVTEzrokhi.txt b/src/coreComponents/unitTests/constitutiveTests/testPVT_data/brinePVTEzrokhi.txt new file mode 100755 index 00000000000..01dc19cc64f --- /dev/null +++ b/src/coreComponents/unitTests/constitutiveTests/testPVT_data/brinePVTEzrokhi.txt @@ -0,0 +1,2 @@ +DensityFun EzrokhiBrineDensity 0.1033 -2.2991e-5 -2.3658e-6 +ViscosityFun EzrokhiBrineViscosity 0 0 0 From 39e75de52fbb5b36d862ddb7adc95c16250771e1 Mon Sep 17 00:00:00 2001 From: Pavel Tomin Date: Thu, 21 Dec 2023 00:15:57 -0600 Subject: [PATCH 19/40] Time step cut support for sequential solver (#2861) Currently the simulation would stop if outer loop didn't converge. This commit adds time step cut logic. Bonus: - print next dt in sub-timestepping process - add `setNextDtBasedOnNewtonIter` for coupled solver to avoid subsolvers doing a lot of iters - when pressure change is computed - switch from relative to absolute for pres < 1 --- .../physicsSolvers/SolverBase.cpp | 4 +- .../physicsSolvers/SolverBase.hpp | 2 +- .../fluidFlow/CompositionalMultiphaseBase.cpp | 5 +- .../multiphysics/CoupledSolver.hpp | 147 ++++++++++++------ .../multiphysics/MultiphasePoromechanics.cpp | 2 +- 5 files changed, 108 insertions(+), 52 deletions(-) diff --git a/src/coreComponents/physicsSolvers/SolverBase.cpp b/src/coreComponents/physicsSolvers/SolverBase.cpp index 76b9ae227b2..5ea5eea1754 100644 --- a/src/coreComponents/physicsSolvers/SolverBase.cpp +++ b/src/coreComponents/physicsSolvers/SolverBase.cpp @@ -273,7 +273,7 @@ bool SolverBase::execute( real64 const time_n, if( getLogLevel() >= 1 && dtRemaining > 0.0 ) { - GEOS_LOG_LEVEL_RANK_0( 1, GEOS_FMT( "{}: sub-step = {}, accepted dt = {}, remaining dt = {}", getName(), subStep, dtAccepted, dtRemaining ) ); + GEOS_LOG_LEVEL_RANK_0( 1, GEOS_FMT( "{}: sub-step = {}, accepted dt = {}, next dt = {}, remaining dt = {}", getName(), subStep, dtAccepted, nextDt, dtRemaining ) ); } } @@ -820,7 +820,7 @@ real64 SolverBase::nonlinearImplicitStep( real64 const & time_n, } else { - GEOS_ERROR( getDataContext() << ": Nonconverged solutions not allowed. Terminating..." ); + GEOS_ERROR( "Nonconverged solutions not allowed. Terminating..." ); } } diff --git a/src/coreComponents/physicsSolvers/SolverBase.hpp b/src/coreComponents/physicsSolvers/SolverBase.hpp index 8129e754402..f6174cd9b57 100644 --- a/src/coreComponents/physicsSolvers/SolverBase.hpp +++ b/src/coreComponents/physicsSolvers/SolverBase.hpp @@ -153,7 +153,7 @@ class SolverBase : public ExecutableGroup * @param[in] currentDt the current time step size * @return the prescribed time step size */ - real64 setNextDtBasedOnNewtonIter( real64 const & currentDt ); + virtual real64 setNextDtBasedOnNewtonIter( real64 const & currentDt ); /** * @brief function to set the next dt based on state change diff --git a/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseBase.cpp b/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseBase.cpp index f4897c4b9b6..c2edc85b3c8 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseBase.cpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseBase.cpp @@ -2035,8 +2035,9 @@ real64 CompositionalMultiphaseBase::setNextDtBasedOnStateChange( real64 const & { if( ghostRank[ei] < 0 ) { - subRegionMaxPresChange.max( LvArray::math::abs( pres[ei] - pres_n[ei] ) / LvArray::math::max( pres_n[ei], LvArray::NumericLimits< real64 >::epsilon ) ); - subRegionMaxTempChange.max( LvArray::math::abs( temp[ei] - temp_n[ei] ) / LvArray::math::max( temp_n[ei], LvArray::NumericLimits< real64 >::epsilon ) ); + // switch from relative to absolute when pressure less than 1 + subRegionMaxPresChange.max( LvArray::math::abs( pres[ei] - pres_n[ei] ) / LvArray::math::max( LvArray::math::abs( pres_n[ei] ), 1.0 ) ); + subRegionMaxTempChange.max( LvArray::math::abs( temp[ei] - temp_n[ei] ) / LvArray::math::max( LvArray::math::abs( temp_n[ei] ), 1.0 ) ); for( integer ip = 0; ip < numPhase; ++ip ) { subRegionMaxPhaseVolFracChange.max( LvArray::math::abs( phaseVolFrac[ei][ip] - phaseVolFrac_n[ei][ip] ) ); diff --git a/src/coreComponents/physicsSolvers/multiphysics/CoupledSolver.hpp b/src/coreComponents/physicsSolvers/multiphysics/CoupledSolver.hpp index 0ae7961357f..ad49c26ea2c 100644 --- a/src/coreComponents/physicsSolvers/multiphysics/CoupledSolver.hpp +++ b/src/coreComponents/physicsSolvers/multiphysics/CoupledSolver.hpp @@ -318,6 +318,18 @@ class CoupledSolver : public SolverBase return nextDt; } + virtual real64 setNextDtBasedOnNewtonIter( real64 const & currentDt ) override + { + real64 nextDt = SolverBase::setNextDtBasedOnNewtonIter( currentDt ); + forEachArgInTuple( m_solvers, [&]( auto & solver, auto ) + { + real64 const singlePhysicsNextDt = + solver->setNextDtBasedOnNewtonIter( currentDt ); + nextDt = LvArray::math::min( singlePhysicsNextDt, nextDt ); + } ); + return nextDt; + } + virtual void cleanup( real64 const time_n, integer const cycleNumber, integer const eventCounter, @@ -369,10 +381,6 @@ class CoupledSolver : public SolverBase { GEOS_MARK_FUNCTION; - real64 dtReturn = dt; - - real64 dtReturnTemporary; - Timestamp const meshModificationTimestamp = getMeshModificationTimestamp( domain ); // First call Coupled Solver setup (important for poromechanics initialization for sequentially coupled) @@ -397,73 +405,120 @@ class CoupledSolver : public SolverBase } ); NonlinearSolverParameters & solverParams = getNonlinearSolverParameters(); - integer & iter = solverParams.m_numNewtonIterations; - iter = 0; - bool isConverged = false; + integer const maxNumberDtCuts = solverParams.m_maxTimeStepCuts; + real64 const dtCutFactor = solverParams.m_timeStepCutFactor; + integer & dtAttempt = solverParams.m_numTimeStepAttempts; - // Reset the states of all solvers if any of them had to restart - forEachArgInTuple( m_solvers, [&]( auto & solver, auto ) - { - solver->resetStateToBeginningOfStep( domain ); - solver->getSolverStatistics().initializeTimeStepStatistics(); // initialize counters for subsolvers - } ); - resetStateToBeginningOfStep( domain ); + bool isConverged = false; + // dt may be cut during the course of this step, so we are keeping a local + // value to track the achieved dt for this step. + real64 stepDt = dt; - /// Sequential coupling loop - while( iter < solverParams.m_maxIterNewton ) + // outer loop attempts to apply full timestep, and managed the cutting of the timestep if + // required. + for( dtAttempt = 0; dtAttempt < maxNumberDtCuts; ++dtAttempt ) { - // Increment the solver statistics for reporting purposes - // Pass a "0" as argument (0 linear iteration) to skip the output of linear iteration stats at the end - m_solverStatistics.logNonlinearIteration( 0 ); + // TODO configuration loop - startSequentialIteration( iter, domain ); + // Reset the states of all solvers if any of them had to restart + forEachArgInTuple( m_solvers, [&]( auto & solver, auto ) + { + solver->resetStateToBeginningOfStep( domain ); + solver->getSolverStatistics().initializeTimeStepStatistics(); // initialize counters for subsolvers + } ); + resetStateToBeginningOfStep( domain ); - // Solve the subproblems nonlinearly - forEachArgInTuple( m_solvers, [&]( auto & solver, auto idx ) + integer & iter = solverParams.m_numNewtonIterations; + iter = 0; + /// Sequential coupling loop + while( iter < solverParams.m_maxIterNewton ) { - GEOS_LOG_LEVEL_RANK_0( 1, GEOS_FMT( " Iteration {:2}: {}", iter+1, solver->getName() ) ); - dtReturnTemporary = solver->nonlinearImplicitStep( time_n, - dtReturn, + // Increment the solver statistics for reporting purposes + // Pass a "0" as argument (0 linear iteration) to skip the output of linear iteration stats at the end + m_solverStatistics.logNonlinearIteration( 0 ); + + startSequentialIteration( iter, domain ); + + // Solve the subproblems nonlinearly + forEachArgInTuple( m_solvers, [&]( auto & solver, auto idx ) + { + GEOS_LOG_LEVEL_RANK_0( 1, GEOS_FMT( " Iteration {:2}: {}", iter + 1, solver->getName() ) ); + real64 solverDt = solver->nonlinearImplicitStep( time_n, + stepDt, cycleNumber, domain ); - mapSolutionBetweenSolvers( domain, idx() ); + mapSolutionBetweenSolvers( domain, idx() ); - if( dtReturnTemporary < dtReturn ) + if( solverDt < stepDt ) // subsolver had to cut the time step + { + iter = 0; // restart outer loop + stepDt = solverDt; // sync time step + } + } ); + + // Check convergence of the outer loop + isConverged = checkSequentialConvergence( iter, + time_n, + stepDt, + domain ); + + if( isConverged ) { - iter = 0; - dtReturn = dtReturnTemporary; + // Save Time step statistics for the subsolvers + forEachArgInTuple( m_solvers, [&]( auto & solver, + auto ) + { + solver->getSolverStatistics().saveTimeStepStatistics(); + } ); + break; + } + else + { + finishSequentialIteration( iter, domain ); } - } ); - // Check convergence of the outer loop - isConverged = checkSequentialConvergence( iter, - time_n, - dtReturn, - domain ); + ++iter; + } if( isConverged ) { - // Save Time step statistics for the subsolvers - forEachArgInTuple( m_solvers, [&]( auto & solver, auto ) - { - solver->getSolverStatistics().saveTimeStepStatistics(); - } ); + // get out of time loop break; } else { - finishSequentialIteration( iter, domain ); + // cut timestep, go back to beginning of step and restart the Newton loop + stepDt *= dtCutFactor; + GEOS_LOG_LEVEL_RANK_0 ( 1, GEOS_FMT( "New dt = {}", stepDt ) ); + + // notify the solver statistics counter that this is a time step cut + m_solverStatistics.logTimeStepCut(); + forEachArgInTuple( m_solvers, [&]( auto & solver, + auto ) + { + solver->getSolverStatistics().logTimeStepCut(); + } ); } - // Add convergence check: - ++iter; } - GEOS_ERROR_IF( !isConverged, getDataContext() << ": sequentiallyCoupledSolverStep did not converge!" ); + if( !isConverged ) + { + GEOS_LOG_RANK_0( "Convergence not achieved." ); + + if( m_nonlinearSolverParameters.m_allowNonConverged > 0 ) + { + GEOS_LOG_RANK_0( "The accepted solution may be inaccurate." ); + } + else + { + GEOS_ERROR( "Nonconverged solutions not allowed. Terminating..." ); + } + } - implicitStepComplete( time_n, dt, domain ); + implicitStepComplete( time_n, stepDt, domain ); - return dtReturn; + return stepDt; } /** diff --git a/src/coreComponents/physicsSolvers/multiphysics/MultiphasePoromechanics.cpp b/src/coreComponents/physicsSolvers/multiphysics/MultiphasePoromechanics.cpp index 5e2f1690581..899254b1c64 100644 --- a/src/coreComponents/physicsSolvers/multiphysics/MultiphasePoromechanics.cpp +++ b/src/coreComponents/physicsSolvers/multiphysics/MultiphasePoromechanics.cpp @@ -335,7 +335,7 @@ void MultiphasePoromechanics< FLOW_SOLVER >::updateState( DomainPartition & doma } ); } ); - GEOS_LOG_LEVEL_RANK_0( 1, GEOS_FMT( " {}: Max phase volume fraction change: {}", + GEOS_LOG_LEVEL_RANK_0( 1, GEOS_FMT( " {}: Max phase volume fraction change = {}", this->getName(), GEOS_FMT( "{:.{}f}", maxDeltaPhaseVolFrac, 4 ) ) ); } From d0ea846eacb1fdc78eec621805e65e0ccb18e956 Mon Sep 17 00:00:00 2001 From: tbeltzun <129868353+tbeltzun@users.noreply.github.com> Date: Thu, 11 Jan 2024 18:24:55 +0100 Subject: [PATCH 20/40] =?UTF-8?q?Acoustic=20-=20elastic=20coupling=20(SEM,?= =?UTF-8?q?=202=E2=81=BF=E1=B5=88=20order=20wave=20equation)=20(#2813)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../acous3D_Q3_firstOrder_small_base.xml | 4 +- .../wavePropagation/acous3D_Q3_small_base.xml | 4 +- .../acous3D_Q5_firstOrder_small_base.xml | 4 +- .../wavePropagation/acous3D_Q5_small_base.xml | 4 +- .../acous3D_firstOrder_small_base.xml | 4 +- .../wavePropagation/acous3D_pml_smoke.xml | 8 +- .../wavePropagation/acous3D_small_base.xml | 4 +- .../wavePropagation/acous3D_vti_smoke.xml | 2 +- .../acouselas3D_Q2_abc_smoke.xml | 154 +++++++ .../benchmarks/acous3D_benchmark_base.xml | 2 +- .../benchmarks/acouselas3D.xml | 215 ++++++++++ .../benchmarks/elas3D_benchmark_base.xml | 6 +- .../wavePropagation/elas3D_DAS_smoke.xml | 6 +- .../elas3D_Q3_firstOrder_small_base.xml | 6 +- .../wavePropagation/elas3D_Q3_small_base.xml | 6 +- .../elas3D_Q5_firstOrder_small_base.xml | 6 +- .../wavePropagation/elas3D_Q5_small_base.xml | 6 +- .../elas3D_firstOrder_small_base.xml | 6 +- .../wavePropagation/elas3D_small_base.xml | 6 +- integratedTests | 2 +- src/coreComponents/mesh/CellElementRegion.cpp | 3 +- .../physicsSolvers/CMakeLists.txt | 17 +- .../AcousticElasticWaveEquationSEM.cpp | 221 ++++++++++ .../AcousticElasticWaveEquationSEM.hpp | 212 ++++++++++ .../AcousticElasticWaveEquationSEMKernel.hpp | 102 +++++ .../AcousticFirstOrderWaveEquationSEM.cpp | 26 +- .../AcousticVTIWaveEquationSEM.cpp | 26 +- .../AcousticWaveEquationSEM.cpp | 400 ++++++++++-------- .../AcousticWaveEquationSEM.hpp | 38 +- .../AcousticWaveEquationSEMKernel.hpp | 35 +- .../ElasticFirstOrderWaveEquationSEM.cpp | 34 +- .../ElasticWaveEquationSEM.cpp | 303 +++++++------ .../ElasticWaveEquationSEM.hpp | 46 +- .../ElasticWaveEquationSEMKernel.hpp | 16 +- .../wavePropagation/WaveSolverBase.cpp | 22 +- .../wavePropagation/WaveSolverBase.hpp | 9 + .../wavePropagation/WaveSolverBaseFields.hpp | 60 ++- .../testWavePropagation.cpp | 4 +- .../testWavePropagationAcousticFirstOrder.cpp | 4 +- 39 files changed, 1559 insertions(+), 474 deletions(-) create mode 100644 inputFiles/wavePropagation/acouselas3D_Q2_abc_smoke.xml create mode 100644 inputFiles/wavePropagation/benchmarks/acouselas3D.xml create mode 100644 src/coreComponents/physicsSolvers/wavePropagation/AcousticElasticWaveEquationSEM.cpp create mode 100644 src/coreComponents/physicsSolvers/wavePropagation/AcousticElasticWaveEquationSEM.hpp create mode 100644 src/coreComponents/physicsSolvers/wavePropagation/AcousticElasticWaveEquationSEMKernel.hpp diff --git a/inputFiles/wavePropagation/acous3D_Q3_firstOrder_small_base.xml b/inputFiles/wavePropagation/acous3D_Q3_firstOrder_small_base.xml index 9c66635f657..cb071a26703 100644 --- a/inputFiles/wavePropagation/acous3D_Q3_firstOrder_small_base.xml +++ b/inputFiles/wavePropagation/acous3D_Q3_firstOrder_small_base.xml @@ -47,7 +47,7 @@ name="cellVelocity" initialCondition="1" objectPath="mesh/FE1/ElementRegions/Region/cb" - fieldName="mediumVelocity" + fieldName="acousticVelocity" scale="1500.0" setNames="{ all }"/> @@ -55,7 +55,7 @@ name="cellDensity" initialCondition="1" objectPath="mesh/FE1/ElementRegions/Region/cb" - fieldName="mediumDensity" + fieldName="acousticDensity" scale="1.0" setNames="{ all }"/> diff --git a/inputFiles/wavePropagation/acous3D_Q3_small_base.xml b/inputFiles/wavePropagation/acous3D_Q3_small_base.xml index 9b8ec3c4e95..26f0631f8fa 100644 --- a/inputFiles/wavePropagation/acous3D_Q3_small_base.xml +++ b/inputFiles/wavePropagation/acous3D_Q3_small_base.xml @@ -63,7 +63,7 @@ name="cellVelocity" initialCondition="1" objectPath="mesh/FE2/ElementRegions/Region/cb" - fieldName="mediumVelocity" + fieldName="acousticVelocity" scale="1500" setNames="{ all }"/> @@ -71,7 +71,7 @@ name="cellDensity" initialCondition="1" objectPath="mesh/FE2/ElementRegions/Region/cb" - fieldName="mediumDensity" + fieldName="acousticDensity" scale="1" setNames="{ all }"/> diff --git a/inputFiles/wavePropagation/acous3D_Q5_firstOrder_small_base.xml b/inputFiles/wavePropagation/acous3D_Q5_firstOrder_small_base.xml index 6f88e1e2e20..32018f54c95 100644 --- a/inputFiles/wavePropagation/acous3D_Q5_firstOrder_small_base.xml +++ b/inputFiles/wavePropagation/acous3D_Q5_firstOrder_small_base.xml @@ -47,7 +47,7 @@ name="cellVelocity" initialCondition="1" objectPath="mesh/FE1/ElementRegions/Region/cb" - fieldName="mediumVelocity" + fieldName="acousticVelocity" scale="1500.0" setNames="{ all }"/> @@ -55,7 +55,7 @@ name="cellDensity" initialCondition="1" objectPath="mesh/FE1/ElementRegions/Region/cb" - fieldName="mediumDensity" + fieldName="acousticDensity" scale="1.0" setNames="{ all }"/> diff --git a/inputFiles/wavePropagation/acous3D_Q5_small_base.xml b/inputFiles/wavePropagation/acous3D_Q5_small_base.xml index 1ecc941b4d7..99697f8ff77 100644 --- a/inputFiles/wavePropagation/acous3D_Q5_small_base.xml +++ b/inputFiles/wavePropagation/acous3D_Q5_small_base.xml @@ -63,7 +63,7 @@ name="cellVelocity" initialCondition="1" objectPath="mesh/FE2/ElementRegions/Region/cb" - fieldName="mediumVelocity" + fieldName="acousticVelocity" scale="1500" setNames="{ all }"/> @@ -71,7 +71,7 @@ name="cellDensity" initialCondition="1" objectPath="mesh/FE2/ElementRegions/Region/cb" - fieldName="mediumDensity" + fieldName="acousticDensity" scale="1500" setNames="{ all }"/> diff --git a/inputFiles/wavePropagation/acous3D_firstOrder_small_base.xml b/inputFiles/wavePropagation/acous3D_firstOrder_small_base.xml index 34bd0ac3c40..90e96ffc053 100644 --- a/inputFiles/wavePropagation/acous3D_firstOrder_small_base.xml +++ b/inputFiles/wavePropagation/acous3D_firstOrder_small_base.xml @@ -47,7 +47,7 @@ name="cellVelocity" initialCondition="1" objectPath="mesh/FE1/ElementRegions/Region/cb" - fieldName="mediumVelocity" + fieldName="acousticVelocity" scale="1500.0" setNames="{ all }"/> @@ -55,7 +55,7 @@ name="cellDensity" initialCondition="1" objectPath="mesh/FE1/ElementRegions/Region/cb" - fieldName="mediumDensity" + fieldName="acousticDensity" scale="1.0" setNames="{ all }"/> diff --git a/inputFiles/wavePropagation/acous3D_pml_smoke.xml b/inputFiles/wavePropagation/acous3D_pml_smoke.xml index 8d94dd3b873..7e2b978d31d 100644 --- a/inputFiles/wavePropagation/acous3D_pml_smoke.xml +++ b/inputFiles/wavePropagation/acous3D_pml_smoke.xml @@ -181,7 +181,7 @@ name="cellVelocity1" initialCondition="1" objectPath="ElementRegions/interiorDomain" - fieldName="mediumVelocity" + fieldName="acousticVelocity" scale="2000" setNames="{ all }"/> @@ -189,7 +189,7 @@ name="cellDensity1" initialCondition="1" objectPath="ElementRegions/interiorDomain" - fieldName="mediumDensity" + fieldName="acousticDensity" scale="1" setNames="{ all }"/> @@ -198,7 +198,7 @@ name="cellVelocity2" initialCondition="1" objectPath="ElementRegions/pmlDomain" - fieldName="mediumVelocity" + fieldName="acousticVelocity" scale="2000" setNames="{ all }"/> @@ -206,7 +206,7 @@ name="cellDensity2" initialCondition="1" objectPath="ElementRegions/pmlDomain" - fieldName="mediumDensity" + fieldName="acousticDensity" scale="1" setNames="{ all }"/> diff --git a/inputFiles/wavePropagation/acous3D_small_base.xml b/inputFiles/wavePropagation/acous3D_small_base.xml index 6b611740154..694ced3e1e0 100644 --- a/inputFiles/wavePropagation/acous3D_small_base.xml +++ b/inputFiles/wavePropagation/acous3D_small_base.xml @@ -63,7 +63,7 @@ name="cellVelocity" initialCondition="1" objectPath="ElementRegions/Region/cb" - fieldName="mediumVelocity" + fieldName="acousticVelocity" scale="1500" setNames="{ all }"/> @@ -71,7 +71,7 @@ name="cellDensity" initialCondition="1" objectPath="ElementRegions/Region/cb" - fieldName="mediumDensity" + fieldName="acousticDensity" scale="1" setNames="{ all }"/> diff --git a/inputFiles/wavePropagation/acous3D_vti_smoke.xml b/inputFiles/wavePropagation/acous3D_vti_smoke.xml index c3da486c9b3..ef9127fe278 100644 --- a/inputFiles/wavePropagation/acous3D_vti_smoke.xml +++ b/inputFiles/wavePropagation/acous3D_vti_smoke.xml @@ -162,7 +162,7 @@ name="cellVelocity" initialCondition="1" objectPath="ElementRegions/Region/cb" - fieldName="mediumVelocity" + fieldName="acousticVelocity" scale="1500" setNames="{ all }"/> diff --git a/inputFiles/wavePropagation/acouselas3D_Q2_abc_smoke.xml b/inputFiles/wavePropagation/acouselas3D_Q2_abc_smoke.xml new file mode 100644 index 00000000000..ddbc4a0e177 --- /dev/null +++ b/inputFiles/wavePropagation/acouselas3D_Q2_abc_smoke.xml @@ -0,0 +1,154 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inputFiles/wavePropagation/benchmarks/acous3D_benchmark_base.xml b/inputFiles/wavePropagation/benchmarks/acous3D_benchmark_base.xml index 3ae51015bdb..78c0c4dc1d1 100644 --- a/inputFiles/wavePropagation/benchmarks/acous3D_benchmark_base.xml +++ b/inputFiles/wavePropagation/benchmarks/acous3D_benchmark_base.xml @@ -101,7 +101,7 @@ name="cellVelocity" initialCondition="1" objectPath="ElementRegions/Region/cb" - fieldName="mediumVelocity" + fieldName="acousticVelocity" scale="1500" setNames="{ all }"/> diff --git a/inputFiles/wavePropagation/benchmarks/acouselas3D.xml b/inputFiles/wavePropagation/benchmarks/acouselas3D.xml new file mode 100644 index 00000000000..7ee7cc3e86e --- /dev/null +++ b/inputFiles/wavePropagation/benchmarks/acouselas3D.xml @@ -0,0 +1,215 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inputFiles/wavePropagation/benchmarks/elas3D_benchmark_base.xml b/inputFiles/wavePropagation/benchmarks/elas3D_benchmark_base.xml index 36a95b6e247..b3562deee89 100644 --- a/inputFiles/wavePropagation/benchmarks/elas3D_benchmark_base.xml +++ b/inputFiles/wavePropagation/benchmarks/elas3D_benchmark_base.xml @@ -168,7 +168,7 @@ name="cellVelocityVp" initialCondition="1" objectPath="ElementRegions/Region/cb" - fieldName="mediumVelocityVp" + fieldName="elasticVelocityVp" scale="2000" setNames="{ all }"/> @@ -176,7 +176,7 @@ name="cellVelocityVs" initialCondition="1" objectPath="ElementRegions/Region/cb" - fieldName="mediumVelocityVs" + fieldName="elasticVelocityVs" scale="1155" setNames="{ all }"/> @@ -184,7 +184,7 @@ name="cellDensity" initialCondition="1" objectPath="ElementRegions/Region/cb" - fieldName="mediumDensity" + fieldName="elasticDensity" scale="1" setNames="{ all }"/> diff --git a/inputFiles/wavePropagation/elas3D_DAS_smoke.xml b/inputFiles/wavePropagation/elas3D_DAS_smoke.xml index 2fc03065ba3..916d9a8fc38 100644 --- a/inputFiles/wavePropagation/elas3D_DAS_smoke.xml +++ b/inputFiles/wavePropagation/elas3D_DAS_smoke.xml @@ -133,7 +133,7 @@ name="cellVelocityVp" initialCondition="1" objectPath="ElementRegions/Region/cb" - fieldName="mediumVelocityVp" + fieldName="elasticVelocityVp" scale="4000" setNames="{ all }"/> @@ -141,7 +141,7 @@ name="cellVelocityVs" initialCondition="1" objectPath="ElementRegions/Region/cb" - fieldName="mediumVelocityVs" + fieldName="elasticVelocityVs" scale="2000" setNames="{ all }"/> @@ -149,7 +149,7 @@ name="cellDensity" initialCondition="1" objectPath="ElementRegions/Region/cb" - fieldName="mediumDensity" + fieldName="elasticDensity" scale="2000" setNames="{ all }"/> diff --git a/inputFiles/wavePropagation/elas3D_Q3_firstOrder_small_base.xml b/inputFiles/wavePropagation/elas3D_Q3_firstOrder_small_base.xml index e3fca7e9744..9c833d44be1 100644 --- a/inputFiles/wavePropagation/elas3D_Q3_firstOrder_small_base.xml +++ b/inputFiles/wavePropagation/elas3D_Q3_firstOrder_small_base.xml @@ -64,7 +64,7 @@ name="cellVelocityVp" initialCondition="1" objectPath="mesh/FE1/ElementRegions/Region/cb" - fieldName="mediumVelocityVp" + fieldName="elasticVelocityVp" scale="1500" setNames="{ all }"/> @@ -72,7 +72,7 @@ name="cellVelocityVs" initialCondition="1" objectPath="mesh/FE1/ElementRegions/Region/cb" - fieldName="mediumVelocityVs" + fieldName="elasticVelocityVs" scale="1060" setNames="{ all }"/> @@ -80,7 +80,7 @@ name="cellDensity" initialCondition="1" objectPath="mesh/FE1/ElementRegions/Region/cb" - fieldName="mediumDensity" + fieldName="elasticDensity" scale="1" setNames="{ all }"/> diff --git a/inputFiles/wavePropagation/elas3D_Q3_small_base.xml b/inputFiles/wavePropagation/elas3D_Q3_small_base.xml index 04155600218..c8e1063beb9 100644 --- a/inputFiles/wavePropagation/elas3D_Q3_small_base.xml +++ b/inputFiles/wavePropagation/elas3D_Q3_small_base.xml @@ -147,7 +147,7 @@ name="cellVelocityVp" initialCondition="1" objectPath="mesh/FE1/ElementRegions/Region/cb" - fieldName="mediumVelocityVp" + fieldName="elasticVelocityVp" scale="1500" setNames="{ all }"/> @@ -155,7 +155,7 @@ name="cellVelocityVs" initialCondition="1" objectPath="mesh/FE1/ElementRegions/Region/cb" - fieldName="mediumVelocityVs" + fieldName="elasticVelocityVs" scale="1060" setNames="{ all }"/> @@ -163,7 +163,7 @@ name="cellDensity" initialCondition="1" objectPath="mesh/FE1/ElementRegions/Region/cb" - fieldName="mediumDensity" + fieldName="elasticDensity" scale="1" setNames="{ all }"/> diff --git a/inputFiles/wavePropagation/elas3D_Q5_firstOrder_small_base.xml b/inputFiles/wavePropagation/elas3D_Q5_firstOrder_small_base.xml index 2e215fd1406..c21bd3328ea 100644 --- a/inputFiles/wavePropagation/elas3D_Q5_firstOrder_small_base.xml +++ b/inputFiles/wavePropagation/elas3D_Q5_firstOrder_small_base.xml @@ -64,7 +64,7 @@ name="cellVelocityVp" initialCondition="1" objectPath="mesh/FE1/ElementRegions/Region/cb" - fieldName="mediumVelocityVp" + fieldName="elasticVelocityVp" scale="1500" setNames="{ all }"/> @@ -72,7 +72,7 @@ name="cellVelocityVs" initialCondition="1" objectPath="mesh/FE1/ElementRegions/Region/cb" - fieldName="mediumVelocityVs" + fieldName="elasticVelocityVs" scale="1060" setNames="{ all }"/> @@ -80,7 +80,7 @@ name="cellDensity" initialCondition="1" objectPath="mesh/FE1/ElementRegions/Region/cb" - fieldName="mediumDensity" + fieldName="elasticDensity" scale="1" setNames="{ all }"/> diff --git a/inputFiles/wavePropagation/elas3D_Q5_small_base.xml b/inputFiles/wavePropagation/elas3D_Q5_small_base.xml index b811a1c3a3e..a8e82a7b9e3 100644 --- a/inputFiles/wavePropagation/elas3D_Q5_small_base.xml +++ b/inputFiles/wavePropagation/elas3D_Q5_small_base.xml @@ -147,7 +147,7 @@ name="cellVelocityVp" initialCondition="1" objectPath="mesh/FE1/ElementRegions/Region/cb" - fieldName="mediumVelocityVp" + fieldName="elasticVelocityVp" scale="1500" setNames="{ all }"/> @@ -155,7 +155,7 @@ name="cellVelocityVs" initialCondition="1" objectPath="mesh/FE1/ElementRegions/Region/cb" - fieldName="mediumVelocityVs" + fieldName="elasticVelocityVs" scale="1060" setNames="{ all }"/> @@ -163,7 +163,7 @@ name="cellDensity" initialCondition="1" objectPath="mesh/FE1/ElementRegions/Region/cb" - fieldName="mediumDensity" + fieldName="elasticDensity" scale="1" setNames="{ all }"/> diff --git a/inputFiles/wavePropagation/elas3D_firstOrder_small_base.xml b/inputFiles/wavePropagation/elas3D_firstOrder_small_base.xml index 8833ceb5454..7c3fa5783a6 100644 --- a/inputFiles/wavePropagation/elas3D_firstOrder_small_base.xml +++ b/inputFiles/wavePropagation/elas3D_firstOrder_small_base.xml @@ -64,7 +64,7 @@ name="cellVelocityVp" initialCondition="1" objectPath="ElementRegions/Region/cb" - fieldName="mediumVelocityVp" + fieldName="elasticVelocityVp" scale="1500" setNames="{ all }"/> @@ -72,7 +72,7 @@ name="cellVelocityVs" initialCondition="1" objectPath="ElementRegions/Region/cb" - fieldName="mediumVelocityVs" + fieldName="elasticVelocityVs" scale="1060" setNames="{ all }"/> @@ -80,7 +80,7 @@ name="cellDensity" initialCondition="1" objectPath="ElementRegions/Region/cb" - fieldName="mediumDensity" + fieldName="elasticDensity" scale="1" setNames="{ all }"/> diff --git a/inputFiles/wavePropagation/elas3D_small_base.xml b/inputFiles/wavePropagation/elas3D_small_base.xml index 8581b072068..2f79d894055 100644 --- a/inputFiles/wavePropagation/elas3D_small_base.xml +++ b/inputFiles/wavePropagation/elas3D_small_base.xml @@ -154,7 +154,7 @@ name="cellVelocityVp" initialCondition="1" objectPath="ElementRegions/Region/cb" - fieldName="mediumVelocityVp" + fieldName="elasticVelocityVp" scale="1500" setNames="{ all }"/> @@ -162,7 +162,7 @@ name="cellVelocityVs" initialCondition="1" objectPath="ElementRegions/Region/cb" - fieldName="mediumVelocityVs" + fieldName="elasticVelocityVs" scale="1060" setNames="{ all }"/> @@ -170,7 +170,7 @@ name="cellDensity" initialCondition="1" objectPath="ElementRegions/Region/cb" - fieldName="mediumDensity" + fieldName="elasticDensity" scale="1" setNames="{ all }"/> diff --git a/integratedTests b/integratedTests index ef66865cc66..b5332575b73 160000 --- a/integratedTests +++ b/integratedTests @@ -1 +1 @@ -Subproject commit ef66865cc6632df149ea1ea2f7076e607ea15701 +Subproject commit b5332575b730ed88cef3f18475203d1d5ca1b207 diff --git a/src/coreComponents/mesh/CellElementRegion.cpp b/src/coreComponents/mesh/CellElementRegion.cpp index e1ff92e4940..3d614db6606 100644 --- a/src/coreComponents/mesh/CellElementRegion.cpp +++ b/src/coreComponents/mesh/CellElementRegion.cpp @@ -28,7 +28,8 @@ CellElementRegion::CellElementRegion( string const & name, Group * const parent setInputFlag( InputFlags::REQUIRED ); registerWrapper( viewKeyStruct::coarseningRatioString(), &m_coarseningRatio ). - setInputFlag( InputFlags::OPTIONAL ); + setInputFlag( InputFlags::OPTIONAL ). + setApplyDefaultValue( 0.0 ); } CellElementRegion::~CellElementRegion() diff --git a/src/coreComponents/physicsSolvers/CMakeLists.txt b/src/coreComponents/physicsSolvers/CMakeLists.txt index d831037067e..0d2514f94bd 100644 --- a/src/coreComponents/physicsSolvers/CMakeLists.txt +++ b/src/coreComponents/physicsSolvers/CMakeLists.txt @@ -21,8 +21,8 @@ set( physicsSolvers_headers fluidFlow/IsothermalCompositionalMultiphaseBaseKernels.hpp fluidFlow/ThermalCompositionalMultiphaseBaseKernels.hpp fluidFlow/CompositionalMultiphaseFVM.hpp - fluidFlow/IsothermalCompositionalMultiphaseFVMKernels.hpp - fluidFlow/IsothermalCompositionalMultiphaseFVMKernelUtilities.hpp + fluidFlow/IsothermalCompositionalMultiphaseFVMKernels.hpp + fluidFlow/IsothermalCompositionalMultiphaseFVMKernelUtilities.hpp fluidFlow/ThermalCompositionalMultiphaseFVMKernels.hpp fluidFlow/CompositionalMultiphaseHybridFVM.hpp fluidFlow/CompositionalMultiphaseHybridFVMKernels.hpp @@ -32,7 +32,7 @@ set( physicsSolvers_headers fluidFlow/ReactiveCompositionalMultiphaseOBLKernels.hpp fluidFlow/FlowSolverBase.hpp fluidFlow/FlowSolverBaseFields.hpp - fluidFlow/FlowSolverBaseKernels.hpp + fluidFlow/FlowSolverBaseKernels.hpp fluidFlow/FluxKernelsHelper.hpp fluidFlow/HybridFVMHelperKernels.hpp fluidFlow/proppantTransport/ProppantTransport.hpp @@ -80,11 +80,11 @@ set( physicsSolvers_headers multiphysics/poromechanicsKernels/PoromechanicsBase.hpp multiphysics/poromechanicsKernels/SinglePhasePoromechanics.hpp multiphysics/poromechanicsKernels/SinglePhasePoromechanics_impl.hpp - multiphysics/poromechanicsKernels/SinglePhasePoromechanicsConformingFractures.hpp + multiphysics/poromechanicsKernels/SinglePhasePoromechanicsConformingFractures.hpp multiphysics/poromechanicsKernels/SinglePhasePoromechanicsEFEM.hpp multiphysics/poromechanicsKernels/SinglePhasePoromechanicsEFEM_impl.hpp multiphysics/poromechanicsKernels/SinglePhasePoromechanicsFractures.hpp - multiphysics/poromechanicsKernels/SinglePhasePoromechanicsEmbeddedFractures.hpp + multiphysics/poromechanicsKernels/SinglePhasePoromechanicsEmbeddedFractures.hpp multiphysics/poromechanicsKernels/ThermalMultiphasePoromechanics.hpp multiphysics/poromechanicsKernels/ThermalMultiphasePoromechanics_impl.hpp multiphysics/poromechanicsKernels/ThermalSinglePhasePoromechanics.hpp @@ -138,7 +138,9 @@ set( physicsSolvers_headers wavePropagation/AcousticFirstOrderWaveEquationSEM.hpp wavePropagation/AcousticFirstOrderWaveEquationSEMKernel.hpp wavePropagation/AcousticVTIWaveEquationSEM.hpp - wavePropagation/AcousticVTIWaveEquationSEMKernel.hpp ) + wavePropagation/AcousticVTIWaveEquationSEMKernel.hpp + wavePropagation/AcousticElasticWaveEquationSEM.hpp + wavePropagation/AcousticElasticWaveEquationSEMKernel.hpp ) # Specify solver sources set( physicsSolvers_sources @@ -200,7 +202,8 @@ set( physicsSolvers_sources wavePropagation/ElasticWaveEquationSEM.cpp wavePropagation/ElasticFirstOrderWaveEquationSEM.cpp wavePropagation/AcousticFirstOrderWaveEquationSEM.cpp - wavePropagation/AcousticVTIWaveEquationSEM.cpp ) + wavePropagation/AcousticVTIWaveEquationSEM.cpp + wavePropagation/AcousticElasticWaveEquationSEM.cpp ) include( solidMechanics/kernels/SolidMechanicsKernels.cmake) include( multiphysics/poromechanicsKernels/PoromechanicsKernels.cmake) diff --git a/src/coreComponents/physicsSolvers/wavePropagation/AcousticElasticWaveEquationSEM.cpp b/src/coreComponents/physicsSolvers/wavePropagation/AcousticElasticWaveEquationSEM.cpp new file mode 100644 index 00000000000..3e0e5743550 --- /dev/null +++ b/src/coreComponents/physicsSolvers/wavePropagation/AcousticElasticWaveEquationSEM.cpp @@ -0,0 +1,221 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * 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 AcousticElasticWaveEquationSEM.cpp + */ + +#include "AcousticElasticWaveEquationSEM.hpp" +#include "AcousticElasticWaveEquationSEMKernel.hpp" +#include "dataRepository/Group.hpp" +#include +#include + +namespace geos +{ +using namespace dataRepository; + +void AcousticElasticWaveEquationSEM::registerDataOnMesh( Group & meshBodies ) +{ + SolverBase::registerDataOnMesh( meshBodies ); + + forDiscretizationOnMeshTargets( meshBodies, [&] ( string const &, + MeshLevel & mesh, + arrayView1d< string const > const & ) + { + NodeManager & nodeManager = mesh.getNodeManager(); + nodeManager.registerField< fields::CouplingVectorx >( getName() ); + nodeManager.registerField< fields::CouplingVectory >( getName() ); + nodeManager.registerField< fields::CouplingVectorz >( getName() ); + } ); +} + +void AcousticElasticWaveEquationSEM::initializePostInitialConditionsPreSubGroups() +{ + SolverBase::initializePostInitialConditionsPreSubGroups(); + + auto acousSolver = acousticSolver(); + auto elasSolver = elasticSolver(); + + auto acousNodesSet = acousSolver->getSolverNodesSet(); + auto elasNodesSet = elasSolver->getSolverNodesSet(); + + for( auto val : acousNodesSet ) + { + if( elasNodesSet.contains( val ) ) + m_interfaceNodesSet.insert( val ); + } + localIndex const numInterfaceNodes = MpiWrapper::sum( m_interfaceNodesSet.size() ); + GEOS_THROW_IF( numInterfaceNodes == 0, "Failed to compute interface: check xml input (solver order)", std::runtime_error ); + + m_acousRegions = acousSolver->getReference< array1d< string > >( SolverBase::viewKeyStruct::targetRegionsString() ); + m_elasRegions = elasSolver->getReference< array1d< string > >( SolverBase::viewKeyStruct::targetRegionsString() ); + + DomainPartition & domain = getGroupByPath< DomainPartition >( "/Problem/domain" ); + + forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&] ( string const &, + MeshLevel & mesh, + arrayView1d< string const > const & GEOS_UNUSED_PARAM( regionNames ) ) + { + NodeManager & nodeManager = mesh.getNodeManager(); + FaceManager & faceManager = mesh.getFaceManager(); + ElementRegionManager & elemManager = mesh.getElemManager(); + + arrayView2d< wsCoordType const, nodes::REFERENCE_POSITION_USD > const nodeCoords = nodeManager.getField< fields::referencePosition32 >().toViewConst(); + + arrayView2d< real64 const > const faceNormals = faceManager.faceNormal().toViewConst(); + ArrayOfArraysView< localIndex const > const faceToNode = faceManager.nodeList().toViewConst(); + arrayView2d< localIndex const > const faceToSubRegion = faceManager.elementSubRegionList(); + arrayView2d< localIndex const > const faceToRegion = faceManager.elementRegionList(); + arrayView2d< localIndex const > const faceToElement = faceManager.elementList(); + + arrayView1d< real32 > const couplingVectorx = nodeManager.getField< fields::CouplingVectorx >(); + couplingVectorx.zero(); + + arrayView1d< real32 > const couplingVectory = nodeManager.getField< fields::CouplingVectory >(); + couplingVectory.zero(); + + arrayView1d< real32 > const couplingVectorz = nodeManager.getField< fields::CouplingVectorz >(); + couplingVectorz.zero(); + + elemManager.forElementRegions( m_acousRegions, [&] ( localIndex const regionIndex, ElementRegionBase const & elemRegion ) + { + elemRegion.forElementSubRegionsIndex( [&]( localIndex const subRegionIndex, ElementSubRegionBase const & elementSubRegion ) + { + finiteElement::FiniteElementBase const & + fe = elementSubRegion.getReference< finiteElement::FiniteElementBase >( getDiscretizationName() ); + + finiteElement::FiniteElementDispatchHandler< SEM_FE_TYPES >::dispatch3D( fe, [&] ( auto const finiteElement ) + { + using FE_TYPE = TYPEOFREF( finiteElement ); + + acousticElasticWaveEquationSEMKernels::CouplingKernel< FE_TYPE > kernelC; + kernelC.template launch< EXEC_POLICY, ATOMIC_POLICY >( faceManager.size(), + nodeCoords, + regionIndex, + subRegionIndex, + faceToSubRegion, + faceToRegion, + faceToElement, + faceToNode, + faceNormals, + couplingVectorx, + couplingVectory, + couplingVectorz ); + } ); + } ); + } ); + } ); +} + +real64 AcousticElasticWaveEquationSEM::solverStep( real64 const & time_n, + real64 const & dt, + int const cycleNumber, + DomainPartition & domain ) +{ + GEOS_MARK_FUNCTION; + + auto acousSolver = acousticSolver(); + auto elasSolver = elasticSolver(); + + SortedArrayView< localIndex const > const interfaceNodesSet = m_interfaceNodesSet.toViewConst(); + + forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&] ( string const &, + MeshLevel & mesh, + arrayView1d< string const > const & GEOS_UNUSED_PARAM( regionNames ) ) + { + NodeManager & nodeManager = mesh.getNodeManager(); + + arrayView1d< real32 const > const acousticMass = nodeManager.getField< fields::AcousticMassVector >(); + arrayView1d< real32 const > const elasticMass = nodeManager.getField< fields::ElasticMassVector >(); + arrayView1d< localIndex > const acousticFSNodeIndicator = nodeManager.getField< fields::AcousticFreeSurfaceNodeIndicator >(); + arrayView1d< localIndex > const elasticFSNodeIndicator = nodeManager.getField< fields::ElasticFreeSurfaceNodeIndicator >(); + + arrayView1d< real32 const > const p_n = nodeManager.getField< fields::Pressure_n >(); + arrayView1d< real32 const > const ux_nm1 = nodeManager.getField< fields::Displacementx_nm1 >(); + arrayView1d< real32 const > const uy_nm1 = nodeManager.getField< fields::Displacementy_nm1 >(); + arrayView1d< real32 const > const uz_nm1 = nodeManager.getField< fields::Displacementz_nm1 >(); + arrayView1d< real32 const > const ux_n = nodeManager.getField< fields::Displacementx_n >(); + arrayView1d< real32 const > const uy_n = nodeManager.getField< fields::Displacementy_n >(); + arrayView1d< real32 const > const uz_n = nodeManager.getField< fields::Displacementz_n >(); + arrayView1d< real32 const > const atoex = nodeManager.getField< fields::CouplingVectorx >(); + arrayView1d< real32 const > const atoey = nodeManager.getField< fields::CouplingVectory >(); + arrayView1d< real32 const > const atoez = nodeManager.getField< fields::CouplingVectorz >(); + + arrayView1d< real32 > const p_np1 = nodeManager.getField< fields::Pressure_np1 >(); + arrayView1d< real32 > const ux_np1 = nodeManager.getField< fields::Displacementx_np1 >(); + arrayView1d< real32 > const uy_np1 = nodeManager.getField< fields::Displacementy_np1 >(); + arrayView1d< real32 > const uz_np1 = nodeManager.getField< fields::Displacementz_np1 >(); + + real32 const dt2 = pow( dt, 2 ); + + elasSolver->computeUnknowns( time_n, dt, cycleNumber, domain, mesh, m_elasRegions ); + + forAll< EXEC_POLICY >( interfaceNodesSet.size(), [=] GEOS_HOST_DEVICE ( localIndex const n ) + { + localIndex const a = interfaceNodesSet[n]; + if( elasticFSNodeIndicator[a] == 1 ) + return; + + real32 const aux = -p_n[a] / elasticMass[a]; + real32 const localIncrementx = dt2 * atoex[a] * aux; + real32 const localIncrementy = dt2 * atoey[a] * aux; + real32 const localIncrementz = dt2 * atoez[a] * aux; + + RAJA::atomicAdd< ATOMIC_POLICY >( &ux_np1[a], localIncrementx ); + RAJA::atomicAdd< ATOMIC_POLICY >( &uy_np1[a], localIncrementy ); + RAJA::atomicAdd< ATOMIC_POLICY >( &uz_np1[a], localIncrementz ); + } ); + + elasSolver->synchronizeUnknowns( time_n, dt, cycleNumber, domain, mesh, m_elasRegions ); + + acousSolver->computeUnknowns( time_n, dt, cycleNumber, domain, mesh, m_acousRegions ); + + forAll< EXEC_POLICY >( interfaceNodesSet.size(), [=] GEOS_HOST_DEVICE ( localIndex const n ) + { + localIndex const a = interfaceNodesSet[n]; + if( acousticFSNodeIndicator[a] == 1 ) + return; + + real32 const localIncrement = ( + atoex[a] * ( ux_np1[a] - 2.0 * ux_n[a] + ux_nm1[a] ) + + atoey[a] * ( uy_np1[a] - 2.0 * uy_n[a] + uy_nm1[a] ) + + atoez[a] * ( uz_np1[a] - 2.0 * uz_n[a] + uz_nm1[a] ) + ) / acousticMass[a]; + + RAJA::atomicAdd< ATOMIC_POLICY >( &p_np1[a], localIncrement ); + } ); + + acousSolver->synchronizeUnknowns( time_n, dt, cycleNumber, domain, mesh, m_acousRegions ); + + acousSolver->prepareNextTimestep( mesh ); + elasSolver->prepareNextTimestep( mesh ); + } ); + + return dt; +} + +void AcousticElasticWaveEquationSEM::cleanup( real64 const time_n, + integer const cycleNumber, + integer const eventCounter, + real64 const eventProgress, + DomainPartition & domain ) +{ + elasticSolver()->cleanup( time_n, cycleNumber, eventCounter, eventProgress, domain ); + acousticSolver()->cleanup( time_n, cycleNumber, eventCounter, eventProgress, domain ); +} + +REGISTER_CATALOG_ENTRY( SolverBase, AcousticElasticWaveEquationSEM, string const &, Group * const ) + +} /* namespace geos */ diff --git a/src/coreComponents/physicsSolvers/wavePropagation/AcousticElasticWaveEquationSEM.hpp b/src/coreComponents/physicsSolvers/wavePropagation/AcousticElasticWaveEquationSEM.hpp new file mode 100644 index 00000000000..326b248abd8 --- /dev/null +++ b/src/coreComponents/physicsSolvers/wavePropagation/AcousticElasticWaveEquationSEM.hpp @@ -0,0 +1,212 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * 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 AcousticElasticWaveEquationSEM.hpp + */ + +#ifndef SRC_CORECOMPONENTS_PHYSICSSOLVERS_WAVEPROPAGATION_ACOUSTICELASTICWAVEEQUATIONSEM_HPP_ +#define SRC_CORECOMPONENTS_PHYSICSSOLVERS_WAVEPROPAGATION_ACOUSTICELASTICWAVEEQUATIONSEM_HPP_ + +#include "physicsSolvers/wavePropagation/ElasticWaveEquationSEM.hpp" +#include "physicsSolvers/wavePropagation/AcousticWaveEquationSEM.hpp" +#include "physicsSolvers/SolverBase.hpp" +#include + +namespace geos +{ + +template< typename ... SOLVERS > +class CoupledWaveSolver : public SolverBase +{ + +public: + + /** + * @brief main constructor for CoupledWaveSolver Objects + * @param name the name of this instantiation of CoupledWaveSolver in the repository + * @param parent the parent group of this instantiation of CoupledWaveSolver + */ + CoupledWaveSolver( const string & name, + Group * const parent ) + : SolverBase( name, parent ) + { + forEachArgInTuple( m_solvers, [&]( auto solver, auto idx ) + { + using SolverType = TYPEOFPTR( solver ); + string const key = SolverType::coupledSolverAttributePrefix() + "SolverName"; + registerWrapper( key, &m_names[idx()] ). + setRTTypeName( rtTypes::CustomTypes::groupNameRef ). + setInputFlag( dataRepository::InputFlags::REQUIRED ). + setDescription( "Name of the " + SolverType::coupledSolverAttributePrefix() + " solver used by the coupled solver" ); + } ); + } + + /// deleted copy constructor + CoupledWaveSolver( CoupledWaveSolver const & ) = delete; + + /// default move constructor + CoupledWaveSolver( CoupledWaveSolver && ) = default; + + /// deleted assignment operator + CoupledWaveSolver & operator=( CoupledWaveSolver const & ) = delete; + + /// deleted move operator + CoupledWaveSolver & operator=( CoupledWaveSolver && ) = delete; + + virtual void + postProcessInput() override final + { + SolverBase::postProcessInput(); + + forEachArgInTuple( m_solvers, [&]( auto & solver, auto idx ) + { + using SolverPtr = TYPEOFREF( solver ); + using SolverType = TYPEOFPTR( SolverPtr {} ); + solver = getParent().template getGroupPointer< SolverType >( m_names[idx()] ); + GEOS_THROW_IF( solver == nullptr, + GEOS_FMT( "Could not find solver '{}' of type {}", + m_names[idx()], LvArray::system::demangleType< SolverType >() ), + InputError ); + } ); + } + +protected: + + /// Pointers of the single-physics solvers + std::tuple< SOLVERS *... > m_solvers; + + /// Names of the single-physics solvers + std::array< string, sizeof...( SOLVERS ) > m_names; +}; + + +class AcousticElasticWaveEquationSEM : public CoupledWaveSolver< AcousticWaveEquationSEM, ElasticWaveEquationSEM > +{ +public: + using Base = CoupledWaveSolver< AcousticWaveEquationSEM, ElasticWaveEquationSEM >; + using Base::m_solvers; + using wsCoordType = AcousticWaveEquationSEM::wsCoordType; + + enum class SolverType : integer + { + AcousticWaveEquationSEM = 0, + ElasticWaveEquationSEM = 1 + }; + + /// String used to form the solverName used to register solvers in CoupledWaveSolver + static string coupledSolverAttributePrefix() { return "acousticelastic"; } + + using EXEC_POLICY = parallelDevicePolicy< >; + using ATOMIC_POLICY = AtomicPolicy< EXEC_POLICY >; + + virtual void registerDataOnMesh( Group & meshBodies ) override final; + + /** + * @brief main constructor for AcousticElasticWaveEquationSEM objects + * @param name the name of this instantiation of AcousticElasticWaveEquationSEM in the repository + * @param parent the parent group of this instantiation of AcousticElasticWaveEquationSEM + */ + AcousticElasticWaveEquationSEM( const string & name, + Group * const parent ) + : Base( name, parent ) + { } + + /// Destructor for the class + ~AcousticElasticWaveEquationSEM() override {} + + /** + * @brief name of the node manager in the object catalog + * @return string that contains the catalog name to generate a new AcousticElasticWaveEquationSEM object through the object catalog. + */ + static string catalogName() { return "AcousticElasticSEM"; } + + /** + * @copydoc SolverBase::getCatalogName() + */ + string getCatalogName() const override { return catalogName(); } + + /** + * @brief accessor for the pointer to the solid mechanics solver + * @return a pointer to the solid mechanics solver + */ + AcousticWaveEquationSEM * acousticSolver() const + { + return std::get< toUnderlying( SolverType::AcousticWaveEquationSEM ) >( m_solvers ); + } + + /** + * @brief accessor for the pointer to the flow solver + * @return a pointer to the flow solver + */ + ElasticWaveEquationSEM * elasticSolver() const + { + return std::get< toUnderlying( SolverType::ElasticWaveEquationSEM ) >( m_solvers ); + } + + // (requires not to be private because it is called from GEOS_HOST_DEVICE method) + virtual real64 + solverStep( real64 const & time_n, + real64 const & dt, + integer const cycleNumber, + DomainPartition & domain ) override; + + virtual void + cleanup( real64 const time_n, + integer const cycleNumber, + integer const eventCounter, + real64 const eventProgress, + DomainPartition & domain ) override; + +protected: + + virtual void initializePostInitialConditionsPreSubGroups() override; + + SortedArray< localIndex > m_interfaceNodesSet; + arrayView1d< string const > m_acousRegions; + arrayView1d< string const > m_elasRegions; +}; + +namespace fields +{ + +DECLARE_FIELD( CouplingVectorx, + "couplingVectorx", + array1d< real32 >, + 0, + NOPLOT, + WRITE_AND_READ, + "Coupling term on x." ); + +DECLARE_FIELD( CouplingVectory, + "couplingVectory", + array1d< real32 >, + 0, + NOPLOT, + WRITE_AND_READ, + "Coupling term on y." ); + +DECLARE_FIELD( CouplingVectorz, + "couplingVectorz", + array1d< real32 >, + 0, + NOPLOT, + WRITE_AND_READ, + "Coupling term on z." ); +} + +} /* namespace geos */ + +#endif /* SRC_CORECOMPONENTS_PHYSICSSOLVERS_WAVEPROPAGATION_ACOUSTICELASTICWAVEEQUATIONSEM_HPP_ */ diff --git a/src/coreComponents/physicsSolvers/wavePropagation/AcousticElasticWaveEquationSEMKernel.hpp b/src/coreComponents/physicsSolvers/wavePropagation/AcousticElasticWaveEquationSEMKernel.hpp new file mode 100644 index 00000000000..aa3a56b1c0b --- /dev/null +++ b/src/coreComponents/physicsSolvers/wavePropagation/AcousticElasticWaveEquationSEMKernel.hpp @@ -0,0 +1,102 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * 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 AcousticElasticWaveEquationSEMKernel.hpp + */ + +#ifndef SRC_CORECOMPONENTS_PHYSICSSOLVERS_WAVEPROPAGATION_ACOUSTICELASTICWAVEEQUATIONSEMKERNEL_HPP_ +#define SRC_CORECOMPONENTS_PHYSICSSOLVERS_WAVEPROPAGATION_ACOUSTICELASTICWAVEEQUATIONSEMKERNEL_HPP_ + +#include "finiteElement/kernelInterface/KernelBase.hpp" +#if !defined( GEOS_USE_HIP ) +#include "finiteElement/elementFormulations/Qk_Hexahedron_Lagrange_GaussLobatto.hpp" +#endif + +#include + +namespace geos +{ + +namespace acousticElasticWaveEquationSEMKernels +{ + +template< typename FE_TYPE > +struct CouplingKernel +{ + static constexpr localIndex numNodesPerFace = FE_TYPE::numNodesPerFace; + + template< typename EXEC_POLICY, typename ATOMIC_POLICY > + void + launch( localIndex const size, + arrayView2d< WaveSolverBase::wsCoordType const, + nodes::REFERENCE_POSITION_USD > const nodeCoords, + localIndex const regionIndex, + localIndex const subRegionIndex, + arrayView2d< localIndex const > const faceToSubRegion, + arrayView2d< localIndex const > const faceToRegion, + arrayView2d< localIndex const > const faceToElement, + ArrayOfArraysView< localIndex const > const facesToNodes, + arrayView2d< real64 const > const faceNormals, + arrayView1d< real32 > const couplingVectorx, + arrayView1d< real32 > const couplingVectory, + arrayView1d< real32 > const couplingVectorz ) + { + forAll< EXEC_POLICY >( size, [=] GEOS_HOST_DEVICE ( localIndex const f ) + { + localIndex e0 = faceToElement( f, 0 ), e1 = faceToElement( f, 1 ); + localIndex er0 = faceToRegion( f, 0 ), er1 = faceToRegion( f, 1 ); + localIndex esr0 = faceToSubRegion( f, 0 ), esr1 = faceToSubRegion( f, 1 ); + + if( e0 != -1 && e1 != -1 && er0 != er1 ) // an interface is defined as a transition between regions + { + // check that one of the region is the fluid subregion for the fluid -> solid coupling term + if((er0 == regionIndex && esr0 == subRegionIndex) || (er1 == regionIndex && esr1 == subRegionIndex)) + { + real64 xLocal[ numNodesPerFace ][ 3 ]; + for( localIndex a = 0; a < numNodesPerFace; ++a ) + { + for( localIndex i = 0; i < 3; ++i ) + { + xLocal[a][i] = nodeCoords( facesToNodes( f, a ), i ); + } + } + + // determine normal sign for fluid -> solid coupling + localIndex sgn = er0 == regionIndex ? 1 : (er1 == regionIndex ? -1 : 0); + + for( localIndex q = 0; q < numNodesPerFace; ++q ) + { + real64 const aux = FE_TYPE::computeDampingTerm( q, xLocal ); + + real32 const localIncrementx = aux * (sgn * faceNormals( f, 0 )); + real32 const localIncrementy = aux * (sgn * faceNormals( f, 1 )); + real32 const localIncrementz = aux * (sgn * faceNormals( f, 2 )); + + RAJA::atomicAdd< ATOMIC_POLICY >( &couplingVectorx[facesToNodes( f, q )], localIncrementx ); + RAJA::atomicAdd< ATOMIC_POLICY >( &couplingVectory[facesToNodes( f, q )], localIncrementy ); + RAJA::atomicAdd< ATOMIC_POLICY >( &couplingVectorz[facesToNodes( f, q )], localIncrementz ); + } + } + } + } ); + + } +}; + +} /* namespace acousticElasticWaveEquationSEMKernels */ + +} /* namespace geos */ + +#endif /* SRC_CORECOMPONENTS_PHYSICSSOLVERS_WAVEPROPAGATION_ACOUSTICELASTICWAVEEQUATIONSEMKERNEL_HPP_ */ diff --git a/src/coreComponents/physicsSolvers/wavePropagation/AcousticFirstOrderWaveEquationSEM.cpp b/src/coreComponents/physicsSolvers/wavePropagation/AcousticFirstOrderWaveEquationSEM.cpp index fc8fb56b5a2..bdc1f623278 100644 --- a/src/coreComponents/physicsSolvers/wavePropagation/AcousticFirstOrderWaveEquationSEM.cpp +++ b/src/coreComponents/physicsSolvers/wavePropagation/AcousticFirstOrderWaveEquationSEM.cpp @@ -96,19 +96,19 @@ void AcousticFirstOrderWaveEquationSEM::registerDataOnMesh( Group & meshBodies ) nodeManager.registerField< wavesolverfields::Pressure_np1, wavesolverfields::ForcingRHS, - wavesolverfields::MassVector, + wavesolverfields::AcousticMassVector, wavesolverfields::DampingVector, - wavesolverfields::FreeSurfaceNodeIndicator >( getName() ); + wavesolverfields::AcousticFreeSurfaceNodeIndicator >( getName() ); FaceManager & faceManager = mesh.getFaceManager(); - faceManager.registerField< wavesolverfields::FreeSurfaceFaceIndicator >( getName() ); + faceManager.registerField< wavesolverfields::AcousticFreeSurfaceFaceIndicator >( getName() ); ElementRegionManager & elemManager = mesh.getElemManager(); elemManager.forElementSubRegions< CellElementSubRegion >( [&]( CellElementSubRegion & subRegion ) { - subRegion.registerField< wavesolverfields::MediumVelocity >( getName() ); - subRegion.registerField< wavesolverfields::MediumDensity >( getName() ); + subRegion.registerField< wavesolverfields::AcousticVelocity >( getName() ); + subRegion.registerField< wavesolverfields::AcousticDensity >( getName() ); subRegion.registerField< wavesolverfields::Velocity_x >( getName() ); subRegion.registerField< wavesolverfields::Velocity_y >( getName() ); @@ -294,7 +294,7 @@ void AcousticFirstOrderWaveEquationSEM::initializePostInitialConditionsPreSubGro ArrayOfArraysView< localIndex const > const facesToNodes = faceManager.nodeList().toViewConst(); // mass matrix to be computed in this function - arrayView1d< real32 > const mass = nodeManager.getField< wavesolverfields::MassVector >(); + arrayView1d< real32 > const mass = nodeManager.getField< wavesolverfields::AcousticMassVector >(); /// damping matrix to be computed for each dof in the boundary of the mesh arrayView1d< real32 > const damping = nodeManager.getField< wavesolverfields::DampingVector >(); @@ -302,15 +302,15 @@ void AcousticFirstOrderWaveEquationSEM::initializePostInitialConditionsPreSubGro mass.zero(); /// get array of indicators: 1 if face is on the free surface; 0 otherwise - arrayView1d< localIndex const > const freeSurfaceFaceIndicator = faceManager.getField< wavesolverfields::FreeSurfaceFaceIndicator >(); + arrayView1d< localIndex const > const freeSurfaceFaceIndicator = faceManager.getField< wavesolverfields::AcousticFreeSurfaceFaceIndicator >(); mesh.getElemManager().forElementSubRegions< CellElementSubRegion >( regionNames, [&]( localIndex const, CellElementSubRegion & elementSubRegion ) { arrayView2d< localIndex const, cells::NODE_MAP_USD > const elemsToNodes = elementSubRegion.nodeList(); arrayView2d< localIndex const > const elemsToFaces = elementSubRegion.faceList(); - arrayView1d< real32 const > const velocity = elementSubRegion.getField< wavesolverfields::MediumVelocity >(); - arrayView1d< real32 const > const density = elementSubRegion.getField< wavesolverfields::MediumDensity >(); + arrayView1d< real32 const > const velocity = elementSubRegion.getField< wavesolverfields::AcousticVelocity >(); + arrayView1d< real32 const > const density = elementSubRegion.getField< wavesolverfields::AcousticDensity >(); finiteElement::FiniteElementBase const & fe = elementSubRegion.getReference< finiteElement::FiniteElementBase >( getDiscretizationName() ); @@ -358,10 +358,10 @@ void AcousticFirstOrderWaveEquationSEM::applyFreeSurfaceBC( real64 const time, D ArrayOfArraysView< localIndex const > const faceToNodeMap = faceManager.nodeList().toViewConst(); /// array of indicators: 1 if a face is on on free surface; 0 otherwise - arrayView1d< localIndex > const freeSurfaceFaceIndicator = faceManager.getField< wavesolverfields::FreeSurfaceFaceIndicator >(); + arrayView1d< localIndex > const freeSurfaceFaceIndicator = faceManager.getField< wavesolverfields::AcousticFreeSurfaceFaceIndicator >(); /// array of indicators: 1 if a node is on on free surface; 0 otherwise - arrayView1d< localIndex > const freeSurfaceNodeIndicator = nodeManager.getField< wavesolverfields::FreeSurfaceNodeIndicator >(); + arrayView1d< localIndex > const freeSurfaceNodeIndicator = nodeManager.getField< wavesolverfields::AcousticFreeSurfaceNodeIndicator >(); freeSurfaceFaceIndicator.zero(); @@ -455,7 +455,7 @@ real64 AcousticFirstOrderWaveEquationSEM::explicitStepInternal( real64 const & t arrayView2d< wsCoordType const, nodes::REFERENCE_POSITION_USD > const X = nodeManager.getField< fields::referencePosition32 >().toViewConst(); - arrayView1d< real32 const > const mass = nodeManager.getField< wavesolverfields::MassVector >(); + arrayView1d< real32 const > const mass = nodeManager.getField< wavesolverfields::AcousticMassVector >(); arrayView1d< real32 const > const damping = nodeManager.getField< wavesolverfields::DampingVector >(); arrayView1d< real32 > const p_np1 = nodeManager.getField< wavesolverfields::Pressure_np1 >(); @@ -466,7 +466,7 @@ real64 AcousticFirstOrderWaveEquationSEM::explicitStepInternal( real64 const & t CellElementSubRegion & elementSubRegion ) { arrayView2d< localIndex const, cells::NODE_MAP_USD > const & elemsToNodes = elementSubRegion.nodeList(); - arrayView1d< real32 const > const density = elementSubRegion.getField< wavesolverfields::MediumDensity >(); + arrayView1d< real32 const > const density = elementSubRegion.getField< wavesolverfields::AcousticDensity >(); arrayView2d< real32 > const velocity_x = elementSubRegion.getField< wavesolverfields::Velocity_x >(); arrayView2d< real32 > const velocity_y = elementSubRegion.getField< wavesolverfields::Velocity_y >(); arrayView2d< real32 > const velocity_z = elementSubRegion.getField< wavesolverfields::Velocity_z >(); diff --git a/src/coreComponents/physicsSolvers/wavePropagation/AcousticVTIWaveEquationSEM.cpp b/src/coreComponents/physicsSolvers/wavePropagation/AcousticVTIWaveEquationSEM.cpp index 28851545659..08e6b724d8f 100644 --- a/src/coreComponents/physicsSolvers/wavePropagation/AcousticVTIWaveEquationSEM.cpp +++ b/src/coreComponents/physicsSolvers/wavePropagation/AcousticVTIWaveEquationSEM.cpp @@ -69,20 +69,20 @@ void AcousticVTIWaveEquationSEM::registerDataOnMesh( Group & meshBodies ) fields::wavesolverfields::Pressure_q_n, fields::wavesolverfields::Pressure_q_np1, fields::wavesolverfields::ForcingRHS, - fields::wavesolverfields::MassVector, + fields::wavesolverfields::AcousticMassVector, fields::wavesolverfields::DampingVector_p, fields::wavesolverfields::DampingVector_pq, fields::wavesolverfields::DampingVector_q, fields::wavesolverfields::DampingVector_qp, fields::wavesolverfields::StiffnessVector_p, fields::wavesolverfields::StiffnessVector_q, - fields::wavesolverfields::FreeSurfaceNodeIndicator, + fields::wavesolverfields::AcousticFreeSurfaceNodeIndicator, fields::wavesolverfields::LateralSurfaceNodeIndicator, fields::wavesolverfields::BottomSurfaceNodeIndicator >( getName() ); FaceManager & faceManager = mesh.getFaceManager(); - faceManager.registerField< fields::wavesolverfields::FreeSurfaceFaceIndicator >( getName() ); + faceManager.registerField< fields::wavesolverfields::AcousticFreeSurfaceFaceIndicator >( getName() ); faceManager.registerField< fields::wavesolverfields::LateralSurfaceFaceIndicator >( getName() ); faceManager.registerField< fields::wavesolverfields::BottomSurfaceFaceIndicator >( getName() ); @@ -93,7 +93,7 @@ void AcousticVTIWaveEquationSEM::registerDataOnMesh( Group & meshBodies ) subRegion.registerField< fields::wavesolverfields::Delta >( getName() ); subRegion.registerField< fields::wavesolverfields::Epsilon >( getName() ); subRegion.registerField< fields::wavesolverfields::F >( getName() ); - subRegion.registerField< fields::wavesolverfields::MediumVelocity >( getName() ); + subRegion.registerField< fields::wavesolverfields::AcousticVelocity >( getName() ); } ); } ); } @@ -115,7 +115,7 @@ void AcousticVTIWaveEquationSEM::precomputeSourceAndReceiverTerm( MeshLevel & me NodeManager const & nodeManager = mesh.getNodeManager(); FaceManager const & faceManager = mesh.getFaceManager(); - arrayView2d< wsCoordType const, nodes::REFERENCE_POSITION_USD > const X32 = + arrayView2d< wsCoordType const, nodes::REFERENCE_POSITION_USD > const nodeCoords32 = nodeManager.getField< fields::referencePosition32 >().toViewConst(); arrayView2d< real64 const > const faceNormal = faceManager.faceNormal(); @@ -175,7 +175,7 @@ void AcousticVTIWaveEquationSEM::precomputeSourceAndReceiverTerm( MeshLevel & me launch< EXEC_POLICY, FE_TYPE > ( elementSubRegion.size(), numFacesPerElem, - X32, + nodeCoords32, elemGhostRank, elemsToNodes, elemsToFaces, @@ -248,7 +248,7 @@ void AcousticVTIWaveEquationSEM::initializePostInitialConditionsPreSubGroups() ArrayOfArraysView< localIndex const > const facesToNodes = faceManager.nodeList().toViewConst(); // mass matrix to be computed in this function - arrayView1d< real32 > const mass = nodeManager.getField< fields::wavesolverfields::MassVector >(); + arrayView1d< real32 > const mass = nodeManager.getField< fields::wavesolverfields::AcousticMassVector >(); mass.zero(); /// damping matrices to be computed for each dof in the boundary of the mesh arrayView1d< real32 > const damping_p = nodeManager.getField< fields::wavesolverfields::DampingVector_p >(); @@ -261,7 +261,7 @@ void AcousticVTIWaveEquationSEM::initializePostInitialConditionsPreSubGroups() damping_qp.zero(); /// get array of indicators: 1 if face is on the free surface; 0 otherwise - arrayView1d< localIndex const > const freeSurfaceFaceIndicator = faceManager.getField< fields::wavesolverfields::FreeSurfaceFaceIndicator >(); + arrayView1d< localIndex const > const freeSurfaceFaceIndicator = faceManager.getField< fields::wavesolverfields::AcousticFreeSurfaceFaceIndicator >(); arrayView1d< localIndex const > const lateralSurfaceFaceIndicator = faceManager.getField< fields::wavesolverfields::LateralSurfaceFaceIndicator >(); arrayView1d< localIndex const > const bottomSurfaceFaceIndicator = faceManager.getField< fields::wavesolverfields::BottomSurfaceFaceIndicator >(); @@ -271,7 +271,7 @@ void AcousticVTIWaveEquationSEM::initializePostInitialConditionsPreSubGroups() arrayView2d< localIndex const, cells::NODE_MAP_USD > const elemsToNodes = elementSubRegion.nodeList(); arrayView2d< localIndex const > const elemsToFaces = elementSubRegion.faceList(); - arrayView1d< real32 const > const velocity = elementSubRegion.getField< fields::wavesolverfields::MediumVelocity >(); + arrayView1d< real32 const > const velocity = elementSubRegion.getField< fields::wavesolverfields::AcousticVelocity >(); arrayView1d< real32 const > const epsilon = elementSubRegion.getField< fields::wavesolverfields::Epsilon >(); arrayView1d< real32 const > const delta = elementSubRegion.getField< fields::wavesolverfields::Delta >(); arrayView1d< real32 const > const vti_f = elementSubRegion.getField< fields::wavesolverfields::F >(); @@ -422,10 +422,10 @@ void AcousticVTIWaveEquationSEM::applyFreeSurfaceBC( real64 time, DomainPartitio ArrayOfArraysView< localIndex const > const faceToNodeMap = faceManager.nodeList().toViewConst(); /// array of indicators: 1 if a face is on on free surface; 0 otherwise - arrayView1d< localIndex > const freeSurfaceFaceIndicator = faceManager.getField< fields::wavesolverfields::FreeSurfaceFaceIndicator >(); + arrayView1d< localIndex > const freeSurfaceFaceIndicator = faceManager.getField< fields::wavesolverfields::AcousticFreeSurfaceFaceIndicator >(); /// array of indicators: 1 if a node is on on free surface; 0 otherwise - arrayView1d< localIndex > const freeSurfaceNodeIndicator = nodeManager.getField< fields::wavesolverfields::FreeSurfaceNodeIndicator >(); + arrayView1d< localIndex > const freeSurfaceNodeIndicator = nodeManager.getField< fields::wavesolverfields::AcousticFreeSurfaceNodeIndicator >(); fsManager.apply< FaceManager >( time, domain.getMeshBody( 0 ).getMeshLevel( m_discretizationName ), @@ -551,7 +551,7 @@ real64 AcousticVTIWaveEquationSEM::explicitStepInternal( real64 const & time_n, { NodeManager & nodeManager = mesh.getNodeManager(); - arrayView1d< real32 const > const mass = nodeManager.getField< fields::wavesolverfields::MassVector >(); + arrayView1d< real32 const > const mass = nodeManager.getField< fields::wavesolverfields::AcousticMassVector >(); arrayView1d< real32 const > const damping_p = nodeManager.getField< fields::wavesolverfields::DampingVector_p >(); arrayView1d< real32 const > const damping_q = nodeManager.getField< fields::wavesolverfields::DampingVector_q >(); arrayView1d< real32 const > const damping_pq = nodeManager.getField< fields::wavesolverfields::DampingVector_pq >(); @@ -565,7 +565,7 @@ real64 AcousticVTIWaveEquationSEM::explicitStepInternal( real64 const & time_n, arrayView1d< real32 > const q_n = nodeManager.getField< fields::wavesolverfields::Pressure_q_n >(); arrayView1d< real32 > const q_np1 = nodeManager.getField< fields::wavesolverfields::Pressure_q_np1 >(); - arrayView1d< localIndex const > const freeSurfaceNodeIndicator = nodeManager.getField< fields::wavesolverfields::FreeSurfaceNodeIndicator >(); + arrayView1d< localIndex const > const freeSurfaceNodeIndicator = nodeManager.getField< fields::wavesolverfields::AcousticFreeSurfaceNodeIndicator >(); arrayView1d< localIndex const > const lateralSurfaceNodeIndicator = nodeManager.getField< fields::wavesolverfields::LateralSurfaceNodeIndicator >(); arrayView1d< localIndex const > const bottomSurfaceNodeIndicator = nodeManager.getField< fields::wavesolverfields::BottomSurfaceNodeIndicator >(); arrayView1d< real32 > const stiffnessVector_p = nodeManager.getField< fields::wavesolverfields::StiffnessVector_p >(); diff --git a/src/coreComponents/physicsSolvers/wavePropagation/AcousticWaveEquationSEM.cpp b/src/coreComponents/physicsSolvers/wavePropagation/AcousticWaveEquationSEM.cpp index eb4ed4d8329..d822d1b5a06 100644 --- a/src/coreComponents/physicsSolvers/wavePropagation/AcousticWaveEquationSEM.cpp +++ b/src/coreComponents/physicsSolvers/wavePropagation/AcousticWaveEquationSEM.cpp @@ -71,10 +71,10 @@ void AcousticWaveEquationSEM::registerDataOnMesh( Group & meshBodies ) fields::Pressure_np1, fields::PressureDoubleDerivative, fields::ForcingRHS, - fields::MassVector, + fields::AcousticMassVector, fields::DampingVector, fields::StiffnessVector, - fields::FreeSurfaceNodeIndicator >( getName() ); + fields::AcousticFreeSurfaceNodeIndicator >( getName() ); /// register PML auxiliary variables only when a PML is specified in the xml if( m_usePML ) @@ -89,14 +89,14 @@ void AcousticWaveEquationSEM::registerDataOnMesh( Group & meshBodies ) } FaceManager & faceManager = mesh.getFaceManager(); - faceManager.registerField< fields::FreeSurfaceFaceIndicator >( getName() ); + faceManager.registerField< fields::AcousticFreeSurfaceFaceIndicator >( getName() ); ElementRegionManager & elemManager = mesh.getElemManager(); elemManager.forElementSubRegions< CellElementSubRegion >( [&]( CellElementSubRegion & subRegion ) { - subRegion.registerField< fields::MediumVelocity >( getName() ); - subRegion.registerField< fields::MediumDensity >( getName() ); + subRegion.registerField< fields::AcousticVelocity >( getName() ); + subRegion.registerField< fields::AcousticDensity >( getName() ); subRegion.registerField< fields::PartialGradient >( getName() ); } ); @@ -106,7 +106,6 @@ void AcousticWaveEquationSEM::registerDataOnMesh( Group & meshBodies ) void AcousticWaveEquationSEM::postProcessInput() { - WaveSolverBase::postProcessInput(); m_pressureNp1AtReceivers.resize( m_nsamplesSeismoTrace, m_receiverCoordinates.size( 0 ) + 1 ); @@ -120,12 +119,11 @@ void AcousticWaveEquationSEM::precomputeSourceAndReceiverTerm( MeshLevel & mesh, FaceManager const & faceManager = mesh.getFaceManager(); arrayView2d< wsCoordType const, nodes::REFERENCE_POSITION_USD > const - X32 = nodeManager.getField< fields::referencePosition32 >().toViewConst(); + nodeCoords32 = nodeManager.getField< fields::referencePosition32 >().toViewConst(); arrayView2d< real64 const > const faceNormal = faceManager.faceNormal(); arrayView2d< real64 const > const faceCenter = faceManager.faceCenter(); - arrayView2d< real64 const > const sourceCoordinates = m_sourceCoordinates.toViewConst(); arrayView2d< localIndex > const sourceNodeIds = m_sourceNodeIds.toView(); arrayView2d< real64 > const sourceConstants = m_sourceConstants.toView(); @@ -181,7 +179,7 @@ void AcousticWaveEquationSEM::precomputeSourceAndReceiverTerm( MeshLevel & mesh, launch< EXEC_POLICY, FE_TYPE > ( elementSubRegion.size(), numFacesPerElem, - X32, + nodeCoords32, elemGhostRank, elemsToNodes, elemsToFaces, @@ -263,7 +261,7 @@ void AcousticWaveEquationSEM::initializePostInitialConditionsPreSubGroups() ArrayOfArraysView< localIndex const > const facesToNodes = faceManager.nodeList().toViewConst(); // mass matrix to be computed in this function - arrayView1d< real32 > const mass = nodeManager.getField< fields::MassVector >(); + arrayView1d< real32 > const mass = nodeManager.getField< fields::AcousticMassVector >(); { GEOS_MARK_SCOPE( mass_zero ); mass.zero(); @@ -275,7 +273,7 @@ void AcousticWaveEquationSEM::initializePostInitialConditionsPreSubGroups() damping.zero(); } /// get array of indicators: 1 if face is on the free surface; 0 otherwise - arrayView1d< localIndex const > const freeSurfaceFaceIndicator = faceManager.getField< fields::FreeSurfaceFaceIndicator >(); + arrayView1d< localIndex const > const freeSurfaceFaceIndicator = faceManager.getField< fields::AcousticFreeSurfaceFaceIndicator >(); elemManager.forElementSubRegions< CellElementSubRegion >( regionNames, [&]( localIndex const, CellElementSubRegion & elementSubRegion ) @@ -286,8 +284,10 @@ void AcousticWaveEquationSEM::initializePostInitialConditionsPreSubGroups() arrayView2d< localIndex const, cells::NODE_MAP_USD > const elemsToNodes = elementSubRegion.nodeList(); arrayView2d< localIndex const > const elemsToFaces = elementSubRegion.faceList(); - arrayView1d< real32 const > const velocity = elementSubRegion.getField< fields::MediumVelocity >(); - arrayView1d< real32 const > const density = elementSubRegion.getField< fields::MediumDensity >(); + computeTargetNodeSet( elemsToNodes, elementSubRegion.size(), fe.getNumQuadraturePoints() ); + + arrayView1d< real32 const > const velocity = elementSubRegion.getField< fields::AcousticVelocity >(); + arrayView1d< real32 const > const density = elementSubRegion.getField< fields::AcousticDensity >(); /// Partial gradient if gradient as to be computed arrayView1d< real32 > grad = elementSubRegion.getField< fields::PartialGradient >(); @@ -341,13 +341,13 @@ void AcousticWaveEquationSEM::applyFreeSurfaceBC( real64 time, DomainPartition & ArrayOfArraysView< localIndex const > const faceToNodeMap = faceManager.nodeList().toViewConst(); /// array of indicators: 1 if a face is on on free surface; 0 otherwise - arrayView1d< localIndex > const freeSurfaceFaceIndicator = faceManager.getField< fields::FreeSurfaceFaceIndicator >(); + arrayView1d< localIndex > const freeSurfaceFaceIndicator = faceManager.getField< fields::AcousticFreeSurfaceFaceIndicator >(); /// array of indicators: 1 if a node is on on free surface; 0 otherwise - arrayView1d< localIndex > const freeSurfaceNodeIndicator = nodeManager.getField< fields::FreeSurfaceNodeIndicator >(); + arrayView1d< localIndex > const freeSurfaceNodeIndicator = nodeManager.getField< fields::AcousticFreeSurfaceNodeIndicator >(); - //freeSurfaceFaceIndicator.zero(); - //freeSurfaceNodeIndicator.zero(); + // freeSurfaceFaceIndicator.zero(); + // freeSurfaceNodeIndicator.zero(); fsManager.apply< FaceManager >( time, domain.getMeshBody( 0 ).getMeshLevel( m_discretizationName ), @@ -430,7 +430,7 @@ void AcousticWaveEquationSEM::initializePML() NodeManager & nodeManager = mesh.getNodeManager(); /// WARNING: the array below is one of the PML auxiliary variables arrayView1d< real32 > const indicatorPML = nodeManager.getField< fields::AuxiliaryVar4PML >(); - arrayView2d< wsCoordType const, nodes::REFERENCE_POSITION_USD > const X32 = nodeManager.getField< fields::referencePosition32 >().toViewConst(); + arrayView2d< wsCoordType const, nodes::REFERENCE_POSITION_USD > const nodeCoords32 = nodeManager.getField< fields::referencePosition32 >().toViewConst(); indicatorPML.zero(); real32 xInteriorMin[3]{}; @@ -489,20 +489,20 @@ void AcousticWaveEquationSEM::initializePML() forAll< EXEC_POLICY >( nodeManager.size(), [=] GEOS_HOST_DEVICE ( localIndex const a ) { - xMinGlobal.min( X32[a][0] ); - yMinGlobal.min( X32[a][1] ); - zMinGlobal.min( X32[a][2] ); - xMaxGlobal.max( X32[a][0] ); - yMaxGlobal.max( X32[a][1] ); - zMaxGlobal.max( X32[a][2] ); + xMinGlobal.min( nodeCoords32[a][0] ); + yMinGlobal.min( nodeCoords32[a][1] ); + zMinGlobal.min( nodeCoords32[a][2] ); + xMaxGlobal.max( nodeCoords32[a][0] ); + yMaxGlobal.max( nodeCoords32[a][1] ); + zMaxGlobal.max( nodeCoords32[a][2] ); if( !isZero( indicatorPML[a] - 1.0 )) { - xMinInterior.min( X32[a][0] ); - yMinInterior.min( X32[a][1] ); - zMinInterior.min( X32[a][2] ); - xMaxInterior.max( X32[a][0] ); - yMaxInterior.max( X32[a][1] ); - zMaxInterior.max( X32[a][2] ); + xMinInterior.min( nodeCoords32[a][0] ); + yMinInterior.min( nodeCoords32[a][1] ); + zMinInterior.min( nodeCoords32[a][2] ); + xMaxInterior.max( nodeCoords32[a][0] ); + yMaxInterior.max( nodeCoords32[a][1] ); + zMaxInterior.max( nodeCoords32[a][2] ); } } ); @@ -558,7 +558,7 @@ void AcousticWaveEquationSEM::initializePML() CellElementSubRegion::NodeMapType const & elemToNodes = subRegion.getReference< CellElementSubRegion::NodeMapType >( CellElementSubRegion::viewKeyStruct::nodeListString() ); traits::ViewTypeConst< CellElementSubRegion::NodeMapType > const elemToNodesViewConst = elemToNodes.toViewConst(); - arrayView1d< real32 const > const vel = subRegion.getReference< array1d< real32 > >( fields::MediumVelocity::key()); + arrayView1d< real32 const > const vel = subRegion.getReference< array1d< real32 > >( fields::AcousticVelocity::key()); finiteElement::FiniteElementBase const & fe = subRegion.getReference< finiteElement::FiniteElementBase >( getDiscretizationName() ); @@ -573,7 +573,7 @@ void AcousticWaveEquationSEM::initializePML() waveSpeedPMLKernel< FE_TYPE > kernel( finiteElement ); kernel.template launch< EXEC_POLICY, ATOMIC_POLICY > ( targetSet, - X32, + nodeCoords32, elemToNodesViewConst, vel, xMin, @@ -611,15 +611,15 @@ void AcousticWaveEquationSEM::initializePML() /// add safeguards when PML thickness is negative or too small for( integer i=0; i<3; ++i ) { - if( param.thicknessMinXYZPML[i]<=minThicknessPML ) + if( param.thicknessMinXYZPML[i] <= minThicknessPML ) { - param.thicknessMinXYZPML[i]=LvArray::NumericLimits< real32 >::max; - param.waveSpeedMinXYZPML[i]=0; + param.thicknessMinXYZPML[i] = LvArray::NumericLimits< real32 >::max; + param.waveSpeedMinXYZPML[i] = 0; } if( param.thicknessMaxXYZPML[i]<=minThicknessPML ) { - param.thicknessMaxXYZPML[i]=LvArray::NumericLimits< real32 >::max; - param.waveSpeedMaxXYZPML[i]=0; + param.thicknessMaxXYZPML[i] = LvArray::NumericLimits< real32 >::max; + param.waveSpeedMaxXYZPML[i] = 0; } } @@ -664,7 +664,7 @@ void AcousticWaveEquationSEM::applyPML( real64 const time, DomainPartition & dom arrayView2d< real32 > const grad_n = nodeManager.getField< fields::AuxiliaryVar2PML >(); arrayView1d< real32 > const divV_n = nodeManager.getField< fields::AuxiliaryVar3PML >(); arrayView1d< real32 const > const u_n = nodeManager.getField< fields::AuxiliaryVar4PML >(); - arrayView2d< wsCoordType const, nodes::REFERENCE_POSITION_USD > const X32 = nodeManager.getField< fields::referencePosition32 >().toViewConst(); + arrayView2d< wsCoordType const, nodes::REFERENCE_POSITION_USD > const nodeCoords32 = nodeManager.getField< fields::referencePosition32 >().toViewConst(); /// Select the subregions concerned by the PML (specified in the xml by the Field Specification) /// 'targetSet' contains the indices of the elements in a given subregion @@ -688,7 +688,7 @@ void AcousticWaveEquationSEM::applyPML( real64 const time, DomainPartition & dom traits::ViewTypeConst< CellElementSubRegion::NodeMapType > const elemToNodesViewConst = elemToNodes.toViewConst(); /// Array view of the wave speed - arrayView1d< real32 const > const vel = subRegion.getReference< array1d< real32 > >( fields::MediumVelocity::key()); + arrayView1d< real32 const > const vel = subRegion.getReference< array1d< real32 > >( fields::AcousticVelocity::key()); /// Get the object needed to determine the type of the element in the subregion finiteElement::FiniteElementBase const & @@ -721,7 +721,7 @@ void AcousticWaveEquationSEM::applyPML( real64 const time, DomainPartition & dom PMLKernel< FE_TYPE > kernel( finiteElement ); kernel.template launch< EXEC_POLICY, ATOMIC_POLICY > ( targetSet, - X32, + nodeCoords32, elemToNodesViewConst, vel, p_n, @@ -779,7 +779,7 @@ real64 AcousticWaveEquationSEM::explicitStepForward( real64 const & time_n, } forAll< EXEC_POLICY >( nodeManager.size(), [=] GEOS_HOST_DEVICE ( localIndex const nodeIdx ) { - p_dt2[nodeIdx] = (p_np1[nodeIdx] - 2*p_n[nodeIdx] + p_nm1[nodeIdx])/(dt*dt); + p_dt2[nodeIdx] = (p_np1[nodeIdx] - 2*p_n[nodeIdx] + p_nm1[nodeIdx]) / pow( dt, 2 ); } ); if( m_enableLifo ) @@ -818,11 +818,7 @@ real64 AcousticWaveEquationSEM::explicitStepForward( real64 const & time_n, } - forAll< EXEC_POLICY >( nodeManager.size(), [=] GEOS_HOST_DEVICE ( localIndex const a ) - { - p_nm1[a] = p_n[a]; - p_n[a] = p_np1[a]; - } ); + prepareNextTimestep( mesh ); } ); return dtOut; @@ -843,7 +839,7 @@ real64 AcousticWaveEquationSEM::explicitStepBackward( real64 const & time_n, { NodeManager & nodeManager = mesh.getNodeManager(); - arrayView1d< real32 const > const mass = nodeManager.getField< fields::MassVector >(); + arrayView1d< real32 const > const mass = nodeManager.getField< fields::AcousticMassVector >(); arrayView1d< real32 > const p_nm1 = nodeManager.getField< fields::Pressure_nm1 >(); arrayView1d< real32 > const p_n = nodeManager.getField< fields::Pressure_n >(); @@ -888,7 +884,7 @@ real64 AcousticWaveEquationSEM::explicitStepBackward( real64 const & time_n, elemManager.forElementSubRegions< CellElementSubRegion >( regionNames, [&]( localIndex const, CellElementSubRegion & elementSubRegion ) { - arrayView1d< real32 const > const velocity = elementSubRegion.getField< fields::MediumVelocity >(); + arrayView1d< real32 const > const velocity = elementSubRegion.getField< fields::AcousticVelocity >(); arrayView1d< real32 > grad = elementSubRegion.getField< fields::PartialGradient >(); arrayView2d< localIndex const, cells::NODE_MAP_USD > const & elemsToNodes = elementSubRegion.nodeList(); constexpr localIndex numNodesPerElem = 8; @@ -908,179 +904,217 @@ real64 AcousticWaveEquationSEM::explicitStepBackward( real64 const & time_n, } ); } - forAll< EXEC_POLICY >( nodeManager.size(), [=] GEOS_HOST_DEVICE ( localIndex const a ) - { - p_nm1[a] = p_n[a]; - p_n[a] = p_np1[a]; - } ); + prepareNextTimestep( mesh ); } ); return dtOut; } -real64 AcousticWaveEquationSEM::explicitStepInternal( real64 const & time_n, - real64 const & dt, - integer cycleNumber, - DomainPartition & domain ) +void AcousticWaveEquationSEM::prepareNextTimestep( MeshLevel & mesh ) { - GEOS_MARK_FUNCTION; + NodeManager & nodeManager = mesh.getNodeManager(); - GEOS_LOG_RANK_0_IF( dt < epsilonLoc, "Warning! Value for dt: " << dt << "s is smaller than local threshold: " << epsilonLoc ); + arrayView1d< real32 > const p_nm1 = nodeManager.getField< fields::Pressure_nm1 >(); + arrayView1d< real32 > const p_n = nodeManager.getField< fields::Pressure_n >(); + arrayView1d< real32 > const p_np1 = nodeManager.getField< fields::Pressure_np1 >(); - forDiscretizationOnMeshTargets( domain.getMeshBodies(), - [&] ( string const &, - MeshLevel & mesh, - arrayView1d< string const > const & regionNames ) + arrayView1d< real32 > const stiffnessVector = nodeManager.getField< fields::StiffnessVector >(); + arrayView1d< real32 > const rhs = nodeManager.getField< fields::ForcingRHS >(); + + SortedArrayView< localIndex const > const solverTargetNodesSet = m_solverTargetNodesSet.toViewConst(); + + forAll< EXEC_POLICY >( solverTargetNodesSet.size(), [=] GEOS_HOST_DEVICE ( localIndex const n ) { - NodeManager & nodeManager = mesh.getNodeManager(); + localIndex const a = solverTargetNodesSet[n]; - arrayView1d< real32 const > const mass = nodeManager.getField< fields::MassVector >(); - arrayView1d< real32 const > const damping = nodeManager.getField< fields::DampingVector >(); + p_nm1[a] = p_n[a]; + p_n[a] = p_np1[a]; - arrayView1d< real32 > const p_nm1 = nodeManager.getField< fields::Pressure_nm1 >(); - arrayView1d< real32 > const p_n = nodeManager.getField< fields::Pressure_n >(); - arrayView1d< real32 > const p_np1 = nodeManager.getField< fields::Pressure_np1 >(); + stiffnessVector[a] = rhs[a] = 0.0; + } ); +} - arrayView1d< localIndex const > const freeSurfaceNodeIndicator = nodeManager.getField< fields::FreeSurfaceNodeIndicator >(); - arrayView1d< real32 > const stiffnessVector = nodeManager.getField< fields::StiffnessVector >(); - arrayView1d< real32 > const rhs = nodeManager.getField< fields::ForcingRHS >(); +void AcousticWaveEquationSEM::computeUnknowns( real64 const & time_n, + real64 const & dt, + integer cycleNumber, + DomainPartition & domain, + MeshLevel & mesh, + arrayView1d< string const > const & regionNames ) +{ + NodeManager & nodeManager = mesh.getNodeManager(); - bool const usePML = m_usePML; + arrayView1d< real32 const > const mass = nodeManager.getField< fields::AcousticMassVector >(); + arrayView1d< real32 const > const damping = nodeManager.getField< fields::DampingVector >(); - auto kernelFactory = acousticWaveEquationSEMKernels::ExplicitAcousticSEMFactory( dt ); + arrayView1d< real32 > const p_nm1 = nodeManager.getField< fields::Pressure_nm1 >(); + arrayView1d< real32 > const p_n = nodeManager.getField< fields::Pressure_n >(); + arrayView1d< real32 > const p_np1 = nodeManager.getField< fields::Pressure_np1 >(); - finiteElement:: - regionBasedKernelApplication< EXEC_POLICY, - constitutive::NullModel, - CellElementSubRegion >( mesh, - regionNames, - getDiscretizationName(), - "", - kernelFactory ); + arrayView1d< localIndex const > const freeSurfaceNodeIndicator = nodeManager.getField< fields::AcousticFreeSurfaceNodeIndicator >(); + arrayView1d< real32 > const stiffnessVector = nodeManager.getField< fields::StiffnessVector >(); + arrayView1d< real32 > const rhs = nodeManager.getField< fields::ForcingRHS >(); - EventManager const & event = getGroupByPath< EventManager >( "/Problem/Events" ); - real64 const & minTime = event.getReference< real64 >( EventManager::viewKeyStruct::minTimeString() ); - integer const cycleForSource = int(round( -minTime / dt + cycleNumber )); + auto kernelFactory = acousticWaveEquationSEMKernels::ExplicitAcousticSEMFactory( dt ); + + finiteElement:: + regionBasedKernelApplication< EXEC_POLICY, + constitutive::NullModel, + CellElementSubRegion >( mesh, + regionNames, + getDiscretizationName(), + "", + kernelFactory ); - addSourceToRightHandSide( cycleForSource, rhs ); + EventManager const & event = getGroupByPath< EventManager >( "/Problem/Events" ); + real64 const & minTime = event.getReference< real64 >( EventManager::viewKeyStruct::minTimeString() ); + integer const cycleForSource = int(round( -minTime / dt + cycleNumber )); + addSourceToRightHandSide( cycleForSource, rhs ); - /// calculate your time integrators - real64 const dt2 = dt*dt; + /// calculate your time integrators + real64 const dt2 = pow( dt, 2 ); - if( !usePML ) + SortedArrayView< localIndex const > const solverTargetNodesSet = m_solverTargetNodesSet.toViewConst(); + if( !m_usePML ) + { + GEOS_MARK_SCOPE ( updateP ); + forAll< EXEC_POLICY >( solverTargetNodesSet.size(), [=] GEOS_HOST_DEVICE ( localIndex const n ) + { + localIndex const a = solverTargetNodesSet[n]; + if( freeSurfaceNodeIndicator[a] != 1 ) + { + p_np1[a] = p_n[a]; + p_np1[a] *= 2.0 * mass[a]; + p_np1[a] -= (mass[a] - 0.5 * dt * damping[a]) * p_nm1[a]; + p_np1[a] += dt2 * (rhs[a] - stiffnessVector[a]); + p_np1[a] /= mass[a] + 0.5 * dt * damping[a]; + } + } ); + } + else + { + parametersPML const & param = getReference< parametersPML >( viewKeyStruct::parametersPMLString() ); + arrayView2d< real32 > const v_n = nodeManager.getField< fields::AuxiliaryVar1PML >(); + arrayView2d< real32 > const grad_n = nodeManager.getField< fields::AuxiliaryVar2PML >(); + arrayView1d< real32 > const divV_n = nodeManager.getField< fields::AuxiliaryVar3PML >(); + arrayView1d< real32 > const u_n = nodeManager.getField< fields::AuxiliaryVar4PML >(); + arrayView2d< wsCoordType const, nodes::REFERENCE_POSITION_USD > const + nodeCoords32 = nodeManager.getField< fields::referencePosition32 >().toViewConst(); + + real32 const xMin[3] = {param.xMinPML[0], param.xMinPML[1], param.xMinPML[2]}; + real32 const xMax[3] = {param.xMaxPML[0], param.xMaxPML[1], param.xMaxPML[2]}; + real32 const dMin[3] = {param.thicknessMinXYZPML[0], param.thicknessMinXYZPML[1], param.thicknessMinXYZPML[2]}; + real32 const dMax[3] = {param.thicknessMaxXYZPML[0], param.thicknessMaxXYZPML[1], param.thicknessMaxXYZPML[2]}; + real32 const cMin[3] = {param.waveSpeedMinXYZPML[0], param.waveSpeedMinXYZPML[1], param.waveSpeedMinXYZPML[2]}; + real32 const cMax[3] = {param.waveSpeedMaxXYZPML[0], param.waveSpeedMaxXYZPML[1], param.waveSpeedMaxXYZPML[2]}; + real32 const r = param.reflectivityPML; + + /// apply the main function to update some of the PML auxiliary variables + /// Compute (divV) and (B.pressureGrad - C.auxUGrad) vectors for the PML region + applyPML( time_n, domain ); + + GEOS_MARK_SCOPE ( updatePWithPML ); + forAll< EXEC_POLICY >( solverTargetNodesSet.size(), [=] GEOS_HOST_DEVICE ( localIndex const n ) { - GEOS_MARK_SCOPE ( updateP ); - forAll< EXEC_POLICY >( nodeManager.size(), [=] GEOS_HOST_DEVICE ( localIndex const a ) + localIndex const a = solverTargetNodesSet[n]; + if( freeSurfaceNodeIndicator[a] != 1 ) { - if( freeSurfaceNodeIndicator[a] != 1 ) + real32 sigma[3]; + real32 xLocal[ 3 ]; + + for( integer i=0; i<3; ++i ) { - p_np1[a] = p_n[a]; - p_np1[a] *= 2.0*mass[a]; - p_np1[a] -= (mass[a]-0.5*dt*damping[a])*p_nm1[a]; - p_np1[a] += dt2*(rhs[a]-stiffnessVector[a]); - p_np1[a] /= mass[a]+0.5*dt*damping[a]; + xLocal[i] = nodeCoords32[a][i]; } - } ); - } - else - { - parametersPML const & param = getReference< parametersPML >( viewKeyStruct::parametersPMLString() ); - arrayView2d< real32 > const v_n = nodeManager.getField< fields::AuxiliaryVar1PML >(); - arrayView2d< real32 > const grad_n = nodeManager.getField< fields::AuxiliaryVar2PML >(); - arrayView1d< real32 > const divV_n = nodeManager.getField< fields::AuxiliaryVar3PML >(); - arrayView1d< real32 > const u_n = nodeManager.getField< fields::AuxiliaryVar4PML >(); - arrayView2d< wsCoordType const, nodes::REFERENCE_POSITION_USD > const X32 = nodeManager.getField< fields::referencePosition32 >().toViewConst(); - - real32 const xMin[ 3 ] = {param.xMinPML[0], param.xMinPML[1], param.xMinPML[2]}; - real32 const xMax[ 3 ] = {param.xMaxPML[0], param.xMaxPML[1], param.xMaxPML[2]}; - real32 const dMin[ 3 ] = {param.thicknessMinXYZPML[0], param.thicknessMinXYZPML[1], param.thicknessMinXYZPML[2]}; - real32 const dMax[ 3 ] = {param.thicknessMaxXYZPML[0], param.thicknessMaxXYZPML[1], param.thicknessMaxXYZPML[2]}; - real32 const cMin[ 3 ] = {param.waveSpeedMinXYZPML[0], param.waveSpeedMinXYZPML[1], param.waveSpeedMinXYZPML[2]}; - real32 const cMax[ 3 ] = {param.waveSpeedMaxXYZPML[0], param.waveSpeedMaxXYZPML[1], param.waveSpeedMaxXYZPML[2]}; - real32 const r = param.reflectivityPML; - /// apply the main function to update some of the PML auxiliary variables - /// Compute (divV) and (B.pressureGrad - C.auxUGrad) vectors for the PML region - applyPML( time_n, domain ); + acousticWaveEquationSEMKernels::PMLKernelHelper::computeDampingProfilePML( + xLocal, + xMin, + xMax, + dMin, + dMax, + cMin, + cMax, + r, + sigma ); + + real32 const alpha = sigma[0] + sigma[1] + sigma[2]; - GEOS_MARK_SCOPE ( updatePWithPML ); - forAll< EXEC_POLICY >( nodeManager.size(), [=] GEOS_HOST_DEVICE ( localIndex const a ) - { - if( freeSurfaceNodeIndicator[a] != 1 ) - { - real32 sigma[3]; - real32 xLocal[ 3 ]; + p_np1[a] = dt2 * ((rhs[a] - stiffnessVector[a]) / mass[a] - divV_n[a]) - + (1 - 0.5*alpha*dt)*p_nm1[a] + 2 * p_n[a]; - for( integer i=0; i<3; ++i ) - { - xLocal[i] = X32[a][i]; - } + p_np1[a] = p_np1[a] / (1 + 0.5 * alpha * dt); - acousticWaveEquationSEMKernels::PMLKernelHelper::computeDampingProfilePML( - xLocal, - xMin, - xMax, - dMin, - dMax, - cMin, - cMax, - r, - sigma ); + for( integer i=0; i<3; ++i ) + { + v_n[a][i] = (1 - dt * sigma[i]) * v_n[a][i] - dt * grad_n[a][i]; + } + u_n[a] += dt * p_n[a]; + } + } ); + } +} - real32 const alpha = sigma[0] + sigma[1] + sigma[2]; +void AcousticWaveEquationSEM::synchronizeUnknowns( real64 const & time_n, + real64 const & dt, + integer const, + DomainPartition & domain, + MeshLevel & mesh, + arrayView1d< string const > const & ) +{ + NodeManager & nodeManager = mesh.getNodeManager(); - p_np1[a] = dt2*( (rhs[a] - stiffnessVector[a])/mass[a] - divV_n[a]) - - (1 - 0.5*alpha*dt)*p_nm1[a] - + 2*p_n[a]; + arrayView1d< real32 > const p_n = nodeManager.getField< fields::Pressure_n >(); + arrayView1d< real32 > const p_np1 = nodeManager.getField< fields::Pressure_np1 >(); - p_np1[a] = p_np1[a] / (1 + 0.5*alpha*dt); + arrayView1d< real32 > const stiffnessVector = nodeManager.getField< fields::StiffnessVector >(); + arrayView1d< real32 > const rhs = nodeManager.getField< fields::ForcingRHS >(); - for( integer i=0; i<3; ++i ) - { - v_n[a][i] = (1 - dt*sigma[i])*v_n[a][i] - dt*grad_n[a][i]; - } - u_n[a] += dt*p_n[a]; - } - } ); - } + /// synchronize pressure fields + FieldIdentifiers fieldsToBeSync; + fieldsToBeSync.addFields( FieldLocation::Node, { fields::Pressure_np1::key() } ); - /// synchronize pressure fields - FieldIdentifiers fieldsToBeSync; - fieldsToBeSync.addFields( FieldLocation::Node, { fields::Pressure_np1::key() } ); + if( m_usePML ) + { + fieldsToBeSync.addFields( FieldLocation::Node, { + fields::AuxiliaryVar1PML::key(), + fields::AuxiliaryVar4PML::key() } ); + } - if( usePML ) - { - fieldsToBeSync.addFields( FieldLocation::Node, { - fields::AuxiliaryVar1PML::key(), - fields::AuxiliaryVar4PML::key() } ); - } + CommunicationTools & syncFields = CommunicationTools::getInstance(); + syncFields.synchronizeFields( fieldsToBeSync, + mesh, + domain.getNeighbors(), + true ); + /// compute the seismic traces since last step. + arrayView2d< real32 > const pReceivers = m_pressureNp1AtReceivers.toView(); - CommunicationTools & syncFields = CommunicationTools::getInstance(); - syncFields.synchronizeFields( fieldsToBeSync, - mesh, - domain.getNeighbors(), - true ); + computeAllSeismoTraces( time_n, dt, p_np1, p_n, pReceivers ); + incrementIndexSeismoTrace( time_n ); - /// compute the seismic traces since last step. - arrayView2d< real32 > const pReceivers = m_pressureNp1AtReceivers.toView(); - computeAllSeismoTraces( time_n, dt, p_np1, p_n, pReceivers ); - incrementIndexSeismoTrace( time_n ); + if( m_usePML ) + { + arrayView2d< real32 > const grad_n = nodeManager.getField< fields::AuxiliaryVar2PML >(); + arrayView1d< real32 > const divV_n = nodeManager.getField< fields::AuxiliaryVar3PML >(); + grad_n.zero(); + divV_n.zero(); + } +} - /// prepare next step - forAll< EXEC_POLICY >( nodeManager.size(), [=] GEOS_HOST_DEVICE ( localIndex const a ) - { - stiffnessVector[a] = 0.0; - rhs[a] = 0.0; - } ); +real64 AcousticWaveEquationSEM::explicitStepInternal( real64 const & time_n, + real64 const & dt, + integer const cycleNumber, + DomainPartition & domain ) +{ + GEOS_MARK_FUNCTION; - if( usePML ) - { - arrayView2d< real32 > const grad_n = nodeManager.getField< fields::AuxiliaryVar2PML >(); - arrayView1d< real32 > const divV_n = nodeManager.getField< fields::AuxiliaryVar3PML >(); - grad_n.zero(); - divV_n.zero(); - } + GEOS_LOG_RANK_0_IF( dt < epsilonLoc, "Warning! Value for dt: " << dt << "s is smaller than local threshold: " << epsilonLoc ); + forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&] ( string const &, + MeshLevel & mesh, + arrayView1d< string const > const & regionNames ) + { + computeUnknowns( time_n, dt, cycleNumber, domain, mesh, regionNames ); + synchronizeUnknowns( time_n, dt, cycleNumber, domain, mesh, regionNames ); } ); return dt; diff --git a/src/coreComponents/physicsSolvers/wavePropagation/AcousticWaveEquationSEM.hpp b/src/coreComponents/physicsSolvers/wavePropagation/AcousticWaveEquationSEM.hpp index 9d1f4db8094..67ea9982df2 100644 --- a/src/coreComponents/physicsSolvers/wavePropagation/AcousticWaveEquationSEM.hpp +++ b/src/coreComponents/physicsSolvers/wavePropagation/AcousticWaveEquationSEM.hpp @@ -46,6 +46,8 @@ class AcousticWaveEquationSEM : public WaveSolverBase AcousticWaveEquationSEM & operator=( AcousticWaveEquationSEM const & ) = delete; AcousticWaveEquationSEM & operator=( AcousticWaveEquationSEM && ) = delete; + /// String used to form the solverName used to register solvers in CoupledSolver + static string coupledSolverAttributePrefix() { return "acoustic"; } static string catalogName() { return "AcousticSEM"; } /** @@ -117,6 +119,22 @@ class AcousticWaveEquationSEM : public WaveSolverBase integer const cycleNumber, DomainPartition & domain ); + void computeUnknowns( real64 const & time_n, + real64 const & dt, + integer const cycleNumber, + DomainPartition & domain, + MeshLevel & mesh, + arrayView1d< string const > const & regionNames ); + + void synchronizeUnknowns( real64 const & time_n, + real64 const & dt, + integer const cycleNumber, + DomainPartition & domain, + MeshLevel & mesh, + arrayView1d< string const > const & regionNames ); + + void prepareNextTimestep( MeshLevel & mesh ); + protected: virtual void postProcessInput() override final; @@ -187,8 +205,8 @@ DECLARE_FIELD( ForcingRHS, WRITE_AND_READ, "RHS" ); -DECLARE_FIELD( MassVector, - "massVector", +DECLARE_FIELD( AcousticMassVector, + "acousticMassVector", array1d< real32 >, 0, NOPLOT, @@ -203,16 +221,16 @@ DECLARE_FIELD( DampingVector, WRITE_AND_READ, "Diagonal of the Damping Matrix." ); -DECLARE_FIELD( MediumVelocity, - "mediumVelocity", +DECLARE_FIELD( AcousticVelocity, + "acousticVelocity", array1d< real32 >, 0, NOPLOT, WRITE_AND_READ, "Medium velocity of the cell" ); -DECLARE_FIELD( MediumDensity, - "mediumDensity", +DECLARE_FIELD( AcousticDensity, + "acousticDensity", array1d< real32 >, 0, NOPLOT, @@ -227,16 +245,16 @@ DECLARE_FIELD( StiffnessVector, WRITE_AND_READ, "Stiffness vector contains R_h*Pressure_n." ); -DECLARE_FIELD( FreeSurfaceFaceIndicator, - "freeSurfaceFaceIndicator", +DECLARE_FIELD( AcousticFreeSurfaceFaceIndicator, + "acousticFreeSurfaceFaceIndicator", array1d< localIndex >, 0, NOPLOT, WRITE_AND_READ, "Free surface indicator, 1 if a face is on free surface 0 otherwise." ); -DECLARE_FIELD( FreeSurfaceNodeIndicator, - "freeSurfaceNodeIndicator", +DECLARE_FIELD( AcousticFreeSurfaceNodeIndicator, + "acousticFreeSurfaceNodeIndicator", array1d< localIndex >, 0, NOPLOT, diff --git a/src/coreComponents/physicsSolvers/wavePropagation/AcousticWaveEquationSEMKernel.hpp b/src/coreComponents/physicsSolvers/wavePropagation/AcousticWaveEquationSEMKernel.hpp index a8d6e3d7b64..4ca784b5c7f 100644 --- a/src/coreComponents/physicsSolvers/wavePropagation/AcousticWaveEquationSEMKernel.hpp +++ b/src/coreComponents/physicsSolvers/wavePropagation/AcousticWaveEquationSEMKernel.hpp @@ -83,7 +83,6 @@ struct PrecomputeSourceAndReceiverKernel forAll< EXEC_POLICY >( size, [=] GEOS_HOST_DEVICE ( localIndex const k ) { - real64 const center[3] = { elemCenter[k][0], elemCenter[k][1], elemCenter[k][2] }; @@ -122,7 +121,7 @@ struct PrecomputeSourceAndReceiverKernel for( localIndex a = 0; a < numNodesPerElem; ++a ) { - sourceNodeIds[isrc][a] = elemsToNodes[k][a]; + sourceNodeIds[isrc][a] = elemsToNodes( k, a ); sourceConstants[isrc][a] = Ntest[a]; } @@ -169,7 +168,7 @@ struct PrecomputeSourceAndReceiverKernel for( localIndex a = 0; a < numNodesPerElem; ++a ) { - receiverNodeIds[ircv][a] = elemsToNodes[k][a]; + receiverNodeIds[ircv][a] = elemsToNodes( k, a ); receiverConstants[ircv][a] = Ntest[a]; } } @@ -385,7 +384,7 @@ struct PMLKernel * @tparam ATOMIC_POLICY the atomic policy * @param[in] targetSet list of cells in the target set * @param[in] nodeCoords coordinates of the nodes - * @param[in] elemToNodesViewConst constant array view of map from element to nodes + * @param[in] elemToNodes constant array view of map from element to nodes * @param[in] velocity cell-wise velocity * @param[in] p_n pressure field at time n * @param[in] v_n PML auxiliary field at time n @@ -404,7 +403,7 @@ struct PMLKernel void launch( SortedArrayView< localIndex const > const targetSet, arrayView2d< WaveSolverBase::wsCoordType const, nodes::REFERENCE_POSITION_USD > const nodeCoords, - traits::ViewTypeConst< CellElementSubRegion::NodeMapType > const elemToNodesViewConst, + traits::ViewTypeConst< CellElementSubRegion::NodeMapType > const elemToNodes, arrayView1d< real32 const > const velocity, arrayView1d< real32 const > const p_n, arrayView2d< real32 const > const v_n, @@ -452,13 +451,13 @@ struct PMLKernel /// copy from global to local arrays for( localIndex i=0; i( &grad_n[elemToNodesViewConst[k][i]][j], localIncrementArray[j]/numNodesPerElem ); + RAJA::atomicAdd< ATOMIC_POLICY >( &grad_n[elemToNodes( k, i )][j], localIncrementArray[j]/numNodesPerElem ); } /// compute beta.pressure + gamma.u - c^2 * divV where beta and gamma are functions of the damping profile real32 const beta = sigma[0]*sigma[1]+sigma[0]*sigma[2]+sigma[1]*sigma[2]; real32 const gamma = sigma[0]*sigma[1]*sigma[2]; - real32 const localIncrement = beta*p_n[elemToNodesViewConst[k][i]] - + gamma*u_n[elemToNodesViewConst[k][i]] + real32 const localIncrement = beta*p_n[elemToNodes( k, i )] + + gamma*u_n[elemToNodes( k, i )] - c*c*( auxVGrad[0][0] + auxVGrad[1][1] + auxVGrad[2][2] ); - RAJA::atomicAdd< ATOMIC_POLICY >( &divV_n[elemToNodesViewConst[k][i]], localIncrement/numNodesPerElem ); + RAJA::atomicAdd< ATOMIC_POLICY >( &divV_n[elemToNodes( k, i )], localIncrement/numNodesPerElem ); } } ); } @@ -539,7 +538,7 @@ struct waveSpeedPMLKernel * @tparam ATOMIC_POLICY the atomic policy * @param[in] targetSet list of cells in the target set * @param[in] nodeCoords coordinates of the nodes - * @param[in] elemToNodesViewConst constant array view of map from element to nodes + * @param[in] elemToNodes constant array view of map from element to nodes * @param[in] velocity cell-wise velocity * @param[in] xMin coordinate limits of the inner PML boundaries, left-front-top * @param[in] xMax coordinate limits of the inner PML boundaries, right-back-bottom @@ -552,7 +551,7 @@ struct waveSpeedPMLKernel void launch( SortedArrayView< localIndex const > const targetSet, arrayView2d< WaveSolverBase::wsCoordType const, nodes::REFERENCE_POSITION_USD > const nodeCoords, - traits::ViewTypeConst< CellElementSubRegion::NodeMapType > const elemToNodesViewConst, + traits::ViewTypeConst< CellElementSubRegion::NodeMapType > const elemToNodes, arrayView1d< real32 const > const velocity, real32 const (&xMin)[3], real32 const (&xMax)[3], @@ -594,7 +593,7 @@ struct waveSpeedPMLKernel { for( localIndex i=0; i() ), m_p_n( nodeManager.getField< fields::Pressure_n >() ), m_stiffnessVector( nodeManager.getField< fields::StiffnessVector >() ), - m_density( elementSubRegion.template getField< fields::MediumDensity >() ), + m_density( elementSubRegion.template getField< fields::AcousticDensity >() ), m_dt( dt ) { GEOS_UNUSED_VAR( edgeManager ); diff --git a/src/coreComponents/physicsSolvers/wavePropagation/ElasticFirstOrderWaveEquationSEM.cpp b/src/coreComponents/physicsSolvers/wavePropagation/ElasticFirstOrderWaveEquationSEM.cpp index 29c61faa2e5..512ee4e9ded 100644 --- a/src/coreComponents/physicsSolvers/wavePropagation/ElasticFirstOrderWaveEquationSEM.cpp +++ b/src/coreComponents/physicsSolvers/wavePropagation/ElasticFirstOrderWaveEquationSEM.cpp @@ -121,22 +121,22 @@ void ElasticFirstOrderWaveEquationSEM::registerDataOnMesh( Group & meshBodies ) wavesolverfields::Displacementy_np1, wavesolverfields::Displacementz_np1, wavesolverfields::ForcingRHS, - wavesolverfields::MassVector, + wavesolverfields::ElasticMassVector, wavesolverfields::DampingVectorx, wavesolverfields::DampingVectory, wavesolverfields::DampingVectorz, - wavesolverfields::FreeSurfaceNodeIndicator >( getName() ); + wavesolverfields::ElasticFreeSurfaceNodeIndicator >( getName() ); FaceManager & faceManager = mesh.getFaceManager(); - faceManager.registerField< wavesolverfields::FreeSurfaceFaceIndicator >( getName() ); + faceManager.registerField< wavesolverfields::ElasticFreeSurfaceFaceIndicator >( getName() ); ElementRegionManager & elemManager = mesh.getElemManager(); elemManager.forElementSubRegions< CellElementSubRegion >( [&]( CellElementSubRegion & subRegion ) { - subRegion.registerField< wavesolverfields::MediumVelocityVp >( getName() ); - subRegion.registerField< wavesolverfields::MediumVelocityVs >( getName() ); - subRegion.registerField< wavesolverfields::MediumDensity >( getName() ); + subRegion.registerField< wavesolverfields::ElasticVelocityVp >( getName() ); + subRegion.registerField< wavesolverfields::ElasticVelocityVs >( getName() ); + subRegion.registerField< wavesolverfields::ElasticDensity >( getName() ); subRegion.registerField< wavesolverfields::Lambda >( getName() ); subRegion.registerField< wavesolverfields::Mu >( getName() ); @@ -349,7 +349,7 @@ void ElasticFirstOrderWaveEquationSEM::initializePostInitialConditionsPreSubGrou ArrayOfArraysView< localIndex const > const facesToNodes = faceManager.nodeList().toViewConst(); // mass matrix to be computed in this function - arrayView1d< real32 > const mass = nodeManager.getField< wavesolverfields::MassVector >(); + arrayView1d< real32 > const mass = nodeManager.getField< wavesolverfields::ElasticMassVector >(); mass.zero(); /// damping matrix to be computed for each dof in the boundary of the mesh arrayView1d< real32 > const dampingx = nodeManager.getField< wavesolverfields::DampingVectorx >(); @@ -360,7 +360,7 @@ void ElasticFirstOrderWaveEquationSEM::initializePostInitialConditionsPreSubGrou dampingz.zero(); /// get array of indicators: 1 if face is on the free surface; 0 otherwise - arrayView1d< localIndex const > const freeSurfaceFaceIndicator = faceManager.getField< wavesolverfields::FreeSurfaceFaceIndicator >(); + arrayView1d< localIndex const > const freeSurfaceFaceIndicator = faceManager.getField< wavesolverfields::ElasticFreeSurfaceFaceIndicator >(); mesh.getElemManager().forElementSubRegions< CellElementSubRegion >( regionNames, [&]( localIndex const, CellElementSubRegion & elementSubRegion ) @@ -368,9 +368,9 @@ void ElasticFirstOrderWaveEquationSEM::initializePostInitialConditionsPreSubGrou arrayView2d< localIndex const, cells::NODE_MAP_USD > const elemsToNodes = elementSubRegion.nodeList(); arrayView2d< localIndex const > const elemsToFaces = elementSubRegion.faceList(); - arrayView1d< real32 > const density = elementSubRegion.getField< wavesolverfields::MediumDensity >(); - arrayView1d< real32 > const velocityVp = elementSubRegion.getField< wavesolverfields::MediumVelocityVp >(); - arrayView1d< real32 > const velocityVs = elementSubRegion.getField< wavesolverfields::MediumVelocityVs >(); + arrayView1d< real32 > const density = elementSubRegion.getField< wavesolverfields::ElasticDensity >(); + arrayView1d< real32 > const velocityVp = elementSubRegion.getField< wavesolverfields::ElasticVelocityVp >(); + arrayView1d< real32 > const velocityVs = elementSubRegion.getField< wavesolverfields::ElasticVelocityVs >(); finiteElement::FiniteElementBase const & fe = elementSubRegion.getReference< finiteElement::FiniteElementBase >( getDiscretizationName() ); @@ -425,10 +425,10 @@ void ElasticFirstOrderWaveEquationSEM::applyFreeSurfaceBC( real64 const time, Do ArrayOfArraysView< localIndex const > const faceToNodeMap = faceManager.nodeList().toViewConst(); /// set array of indicators: 1 if a face is on on free surface; 0 otherwise - arrayView1d< localIndex > const freeSurfaceFaceIndicator = faceManager.getField< wavesolverfields::FreeSurfaceFaceIndicator >(); + arrayView1d< localIndex > const freeSurfaceFaceIndicator = faceManager.getField< wavesolverfields::ElasticFreeSurfaceFaceIndicator >(); /// set array of indicators: 1 if a node is on on free surface; 0 otherwise - arrayView1d< localIndex > const freeSurfaceNodeIndicator = nodeManager.getField< wavesolverfields::FreeSurfaceNodeIndicator >(); + arrayView1d< localIndex > const freeSurfaceNodeIndicator = nodeManager.getField< wavesolverfields::ElasticFreeSurfaceNodeIndicator >(); freeSurfaceFaceIndicator.zero(); freeSurfaceNodeIndicator.zero(); @@ -523,7 +523,7 @@ real64 ElasticFirstOrderWaveEquationSEM::explicitStepInternal( real64 const & ti arrayView2d< wsCoordType const, nodes::REFERENCE_POSITION_USD > const X = nodeManager.getField< fields::referencePosition32 >().toViewConst(); - arrayView1d< real32 const > const mass = nodeManager.getField< wavesolverfields::MassVector >(); + arrayView1d< real32 const > const mass = nodeManager.getField< wavesolverfields::ElasticMassVector >(); arrayView1d< real32 > const dampingx = nodeManager.getField< wavesolverfields::DampingVectorx >(); arrayView1d< real32 > const dampingy = nodeManager.getField< wavesolverfields::DampingVectory >(); arrayView1d< real32 > const dampingz = nodeManager.getField< wavesolverfields::DampingVectorz >(); @@ -539,9 +539,9 @@ real64 ElasticFirstOrderWaveEquationSEM::explicitStepInternal( real64 const & ti arrayView2d< localIndex const, cells::NODE_MAP_USD > const & elemsToNodes = elementSubRegion.nodeList(); - arrayView1d< real32 const > const velocityVp = elementSubRegion.getField< wavesolverfields::MediumVelocityVp >(); - arrayView1d< real32 const > const velocityVs = elementSubRegion.getField< wavesolverfields::MediumVelocityVs >(); - arrayView1d< real32 const > const density = elementSubRegion.getField< wavesolverfields::MediumDensity >(); + arrayView1d< real32 const > const velocityVp = elementSubRegion.getField< wavesolverfields::ElasticVelocityVp >(); + arrayView1d< real32 const > const velocityVs = elementSubRegion.getField< wavesolverfields::ElasticVelocityVs >(); + arrayView1d< real32 const > const density = elementSubRegion.getField< wavesolverfields::ElasticDensity >(); arrayView1d< real32 > const lambda = elementSubRegion.getField< wavesolverfields::Lambda >(); arrayView1d< real32 > const mu = elementSubRegion.getField< wavesolverfields::Mu >(); diff --git a/src/coreComponents/physicsSolvers/wavePropagation/ElasticWaveEquationSEM.cpp b/src/coreComponents/physicsSolvers/wavePropagation/ElasticWaveEquationSEM.cpp index b52daa13cb1..a8104bb9309 100644 --- a/src/coreComponents/physicsSolvers/wavePropagation/ElasticWaveEquationSEM.cpp +++ b/src/coreComponents/physicsSolvers/wavePropagation/ElasticWaveEquationSEM.cpp @@ -96,13 +96,11 @@ void ElasticWaveEquationSEM::initializePreSubGroups() localIndex const numNodesPerElem = getNumNodesPerElem(); localIndex const numSourcesGlobal = m_sourceCoordinates.size( 0 ); - m_sourceNodeIds.resize( numSourcesGlobal, numNodesPerElem ); m_sourceConstantsx.resize( numSourcesGlobal, numNodesPerElem ); m_sourceConstantsy.resize( numSourcesGlobal, numNodesPerElem ); m_sourceConstantsz.resize( numSourcesGlobal, numNodesPerElem ); localIndex const numReceiversGlobal = m_receiverCoordinates.size( 0 ); - m_receiverNodeIds.resize( numReceiversGlobal, numNodesPerElem ); m_receiverConstants.resize( numReceiversGlobal, numNodesPerElem ); } @@ -130,25 +128,25 @@ void ElasticWaveEquationSEM::registerDataOnMesh( Group & meshBodies ) fields::ForcingRHSx, fields::ForcingRHSy, fields::ForcingRHSz, - fields::MassVector, + fields::ElasticMassVector, fields::DampingVectorx, fields::DampingVectory, fields::DampingVectorz, fields::StiffnessVectorx, fields::StiffnessVectory, fields::StiffnessVectorz, - fields::FreeSurfaceNodeIndicator >( getName() ); + fields::ElasticFreeSurfaceNodeIndicator >( getName() ); FaceManager & faceManager = mesh.getFaceManager(); - faceManager.registerField< fields::FreeSurfaceFaceIndicator >( getName() ); + faceManager.registerField< fields::ElasticFreeSurfaceFaceIndicator >( getName() ); ElementRegionManager & elemManager = mesh.getElemManager(); elemManager.forElementSubRegions< CellElementSubRegion >( [&]( CellElementSubRegion & subRegion ) { - subRegion.registerField< fields::MediumVelocityVp >( getName() ); - subRegion.registerField< fields::MediumVelocityVs >( getName() ); - subRegion.registerField< fields::MediumDensity >( getName() ); + subRegion.registerField< fields::ElasticVelocityVp >( getName() ); + subRegion.registerField< fields::ElasticVelocityVs >( getName() ); + subRegion.registerField< fields::ElasticDensity >( getName() ); } ); } ); @@ -158,17 +156,8 @@ void ElasticWaveEquationSEM::registerDataOnMesh( Group & meshBodies ) void ElasticWaveEquationSEM::postProcessInput() { - WaveSolverBase::postProcessInput(); - GEOS_ERROR_IF( m_sourceCoordinates.size( 1 ) != 3, - getWrapperDataContext( WaveSolverBase::viewKeyStruct::sourceCoordinatesString() ) << - ": Invalid number of physical coordinates for the sources" ); - - GEOS_ERROR_IF( m_receiverCoordinates.size( 1 ) != 3, - getWrapperDataContext( WaveSolverBase::viewKeyStruct::receiverCoordinatesString() ) << - ": Invalid number of physical coordinates for the receivers" ); - EventManager const & event = getGroupByPath< EventManager >( "/Problem/Events" ); real64 const & maxTime = event.getReference< real64 >( EventManager::viewKeyStruct::maxTimeString() ); real64 dt = 0; @@ -185,20 +174,18 @@ void ElasticWaveEquationSEM::postProcessInput() if( m_dtSeismoTrace > 0 ) { - m_nsamplesSeismoTrace = int( maxTime / m_dtSeismoTrace) + 1; + m_nsamplesSeismoTrace = int( maxTime / m_dtSeismoTrace ) + 1; } else { m_nsamplesSeismoTrace = 0; } - localIndex const nsamples = int(maxTime / dt) + 1; + localIndex const nsamples = int( maxTime / dt ) + 1; + localIndex const numReceiversGlobal = m_receiverCoordinates.size( 0 ); localIndex const numSourcesGlobal = m_sourceCoordinates.size( 0 ); m_sourceIsAccessible.resize( numSourcesGlobal ); - localIndex const numReceiversGlobal = m_receiverCoordinates.size( 0 ); - m_receiverIsLocal.resize( numReceiversGlobal ); - m_displacementXNp1AtReceivers.resize( m_nsamplesSeismoTrace, numReceiversGlobal + 1 ); m_displacementYNp1AtReceivers.resize( m_nsamplesSeismoTrace, numReceiversGlobal + 1 ); m_displacementZNp1AtReceivers.resize( m_nsamplesSeismoTrace, numReceiversGlobal + 1 ); @@ -427,8 +414,7 @@ void ElasticWaveEquationSEM::initializePostInitialConditionsPreSubGroups() DomainPartition & domain = getGroupByPath< DomainPartition >( "/Problem/domain" ); - real64 const time = 0.0; - applyFreeSurfaceBC( time, domain ); + applyFreeSurfaceBC( 0.0, domain ); forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&] ( string const &, MeshLevel & mesh, @@ -443,7 +429,7 @@ void ElasticWaveEquationSEM::initializePostInitialConditionsPreSubGroups() arrayView2d< wsCoordType const, nodes::REFERENCE_POSITION_USD > const nodeCoords = nodeManager.getField< fields::referencePosition32 >().toViewConst(); // mass matrix to be computed in this function - arrayView1d< real32 > const mass = nodeManager.getField< fields::MassVector >(); + arrayView1d< real32 > const mass = nodeManager.getField< fields::ElasticMassVector >(); mass.zero(); /// damping matrix to be computed for each dof in the boundary of the mesh arrayView1d< real32 > const dampingx = nodeManager.getField< fields::DampingVectorx >(); @@ -454,7 +440,7 @@ void ElasticWaveEquationSEM::initializePostInitialConditionsPreSubGroups() dampingz.zero(); /// get array of indicators: 1 if face is on the free surface; 0 otherwise - arrayView1d< localIndex const > const freeSurfaceFaceIndicator = faceManager.getField< fields::FreeSurfaceFaceIndicator >(); + arrayView1d< localIndex const > const freeSurfaceFaceIndicator = faceManager.getField< fields::ElasticFreeSurfaceFaceIndicator >(); arrayView1d< integer const > const & facesDomainBoundaryIndicator = faceManager.getDomainBoundaryIndicator(); ArrayOfArraysView< localIndex const > const facesToNodes = faceManager.nodeList().toViewConst(); arrayView2d< real64 const > const faceNormal = faceManager.faceNormal(); @@ -468,9 +454,11 @@ void ElasticWaveEquationSEM::initializePostInitialConditionsPreSubGroups() arrayView2d< localIndex const, cells::NODE_MAP_USD > const elemsToNodes = elementSubRegion.nodeList(); arrayView2d< localIndex const > const elemsToFaces = elementSubRegion.faceList(); - arrayView1d< real32 const > const density = elementSubRegion.getField< fields::MediumDensity >(); - arrayView1d< real32 const > const velocityVp = elementSubRegion.getField< fields::MediumVelocityVp >(); - arrayView1d< real32 const > const velocityVs = elementSubRegion.getField< fields::MediumVelocityVs >(); + computeTargetNodeSet( elemsToNodes, elementSubRegion.size(), fe.getNumQuadraturePoints() ); + + arrayView1d< real32 const > const density = elementSubRegion.getField< fields::ElasticDensity >(); + arrayView1d< real32 const > const velocityVp = elementSubRegion.getField< fields::ElasticVelocityVp >(); + arrayView1d< real32 const > const velocityVs = elementSubRegion.getField< fields::ElasticVelocityVs >(); finiteElement::FiniteElementDispatchHandler< SEM_FE_TYPES >::dispatch3D( fe, [&] ( auto const finiteElement ) { @@ -527,10 +515,10 @@ void ElasticWaveEquationSEM::applyFreeSurfaceBC( real64 const time, DomainPartit ArrayOfArraysView< localIndex const > const faceToNodeMap = faceManager.nodeList().toViewConst(); /// set array of indicators: 1 if a face is on on free surface; 0 otherwise - arrayView1d< localIndex > const freeSurfaceFaceIndicator = faceManager.getField< fields::FreeSurfaceFaceIndicator >(); + arrayView1d< localIndex > const freeSurfaceFaceIndicator = faceManager.getField< fields::ElasticFreeSurfaceFaceIndicator >(); /// set array of indicators: 1 if a node is on on free surface; 0 otherwise - arrayView1d< localIndex > const freeSurfaceNodeIndicator = nodeManager.getField< fields::FreeSurfaceNodeIndicator >(); + arrayView1d< localIndex > const freeSurfaceNodeIndicator = nodeManager.getField< fields::ElasticFreeSurfaceNodeIndicator >(); fsManager.apply( time, @@ -603,127 +591,188 @@ real64 ElasticWaveEquationSEM::explicitStepBackward( real64 const & time_n, return dtOut; } -real64 ElasticWaveEquationSEM::explicitStepInternal( real64 const & time_n, - real64 const & dt, - integer const cycleNumber, - DomainPartition & domain ) +void ElasticWaveEquationSEM::computeUnknowns( real64 const &, + real64 const & dt, + integer const cycleNumber, + DomainPartition &, + MeshLevel & mesh, + arrayView1d< string const > const & regionNames ) { - GEOS_MARK_FUNCTION; + NodeManager & nodeManager = mesh.getNodeManager(); - GEOS_LOG_RANK_0_IF( dt < epsilonLoc, "Warning! Value for dt: " << dt << "s is smaller than local threshold: " << epsilonLoc ); + arrayView1d< real32 const > const mass = nodeManager.getField< fields::ElasticMassVector >(); + arrayView1d< real32 const > const dampingx = nodeManager.getField< fields::DampingVectorx >(); + arrayView1d< real32 const > const dampingy = nodeManager.getField< fields::DampingVectory >(); + arrayView1d< real32 const > const dampingz = nodeManager.getField< fields::DampingVectorz >(); + arrayView1d< real32 > const stiffnessVectorx = nodeManager.getField< fields::StiffnessVectorx >(); + arrayView1d< real32 > const stiffnessVectory = nodeManager.getField< fields::StiffnessVectory >(); + arrayView1d< real32 > const stiffnessVectorz = nodeManager.getField< fields::StiffnessVectorz >(); - forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&] ( string const &, - MeshLevel & mesh, - arrayView1d< string const > const & regionNames ) + arrayView1d< real32 > const ux_nm1 = nodeManager.getField< fields::Displacementx_nm1 >(); + arrayView1d< real32 > const uy_nm1 = nodeManager.getField< fields::Displacementy_nm1 >(); + arrayView1d< real32 > const uz_nm1 = nodeManager.getField< fields::Displacementz_nm1 >(); + arrayView1d< real32 > const ux_n = nodeManager.getField< fields::Displacementx_n >(); + arrayView1d< real32 > const uy_n = nodeManager.getField< fields::Displacementy_n >(); + arrayView1d< real32 > const uz_n = nodeManager.getField< fields::Displacementz_n >(); + arrayView1d< real32 > const ux_np1 = nodeManager.getField< fields::Displacementx_np1 >(); + arrayView1d< real32 > const uy_np1 = nodeManager.getField< fields::Displacementy_np1 >(); + arrayView1d< real32 > const uz_np1 = nodeManager.getField< fields::Displacementz_np1 >(); + + /// get array of indicators: 1 if node on free surface; 0 otherwise + arrayView1d< localIndex const > const freeSurfaceNodeIndicator = nodeManager.getField< fields::ElasticFreeSurfaceNodeIndicator >(); + + arrayView1d< real32 > const rhsx = nodeManager.getField< fields::ForcingRHSx >(); + arrayView1d< real32 > const rhsy = nodeManager.getField< fields::ForcingRHSy >(); + arrayView1d< real32 > const rhsz = nodeManager.getField< fields::ForcingRHSz >(); + + auto kernelFactory = elasticWaveEquationSEMKernels::ExplicitElasticSEMFactory( dt ); + + finiteElement:: + regionBasedKernelApplication< EXEC_POLICY, + constitutive::NullModel, + CellElementSubRegion >( mesh, + regionNames, + getDiscretizationName(), + "", + kernelFactory ); + + + addSourceToRightHandSide( cycleNumber, rhsx, rhsy, rhsz ); + + real64 const dt2 = pow( dt, 2 ); + SortedArrayView< localIndex const > const solverTargetNodesSet = m_solverTargetNodesSet.toViewConst(); + forAll< EXEC_POLICY >( solverTargetNodesSet.size(), [=] GEOS_HOST_DEVICE ( localIndex const n ) { - NodeManager & nodeManager = mesh.getNodeManager(); + localIndex const a = solverTargetNodesSet[n]; + if( freeSurfaceNodeIndicator[a] != 1 ) + { + ux_np1[a] = ux_n[a]; + ux_np1[a] *= 2.0*mass[a]; + ux_np1[a] -= (mass[a]-0.5*dt*dampingx[a])*ux_nm1[a]; + ux_np1[a] += dt2*(rhsx[a]-stiffnessVectorx[a]); + ux_np1[a] /= mass[a]+0.5*dt*dampingx[a]; + uy_np1[a] = uy_n[a]; + uy_np1[a] *= 2.0*mass[a]; + uy_np1[a] -= (mass[a]-0.5*dt*dampingy[a])*uy_nm1[a]; + uy_np1[a] += dt2*(rhsy[a]-stiffnessVectory[a]); + uy_np1[a] /= mass[a]+0.5*dt*dampingy[a]; + uz_np1[a] = uz_n[a]; + uz_np1[a] *= 2.0*mass[a]; + uz_np1[a] -= (mass[a]-0.5*dt*dampingz[a])*uz_nm1[a]; + uz_np1[a] += dt2*(rhsz[a]-stiffnessVectorz[a]); + uz_np1[a] /= mass[a]+0.5*dt*dampingz[a]; + } + } ); +} - arrayView1d< real32 const > const mass = nodeManager.getField< fields::MassVector >(); - arrayView1d< real32 const > const dampingx = nodeManager.getField< fields::DampingVectorx >(); - arrayView1d< real32 const > const dampingy = nodeManager.getField< fields::DampingVectory >(); - arrayView1d< real32 const > const dampingz = nodeManager.getField< fields::DampingVectorz >(); - arrayView1d< real32 > const stiffnessVectorx = nodeManager.getField< fields::StiffnessVectorx >(); - arrayView1d< real32 > const stiffnessVectory = nodeManager.getField< fields::StiffnessVectory >(); - arrayView1d< real32 > const stiffnessVectorz = nodeManager.getField< fields::StiffnessVectorz >(); +void ElasticWaveEquationSEM::synchronizeUnknowns( real64 const & time_n, + real64 const & dt, + integer const, + DomainPartition & domain, + MeshLevel & mesh, + arrayView1d< string const > const & ) +{ + NodeManager & nodeManager = mesh.getNodeManager(); + arrayView1d< real32 > const stiffnessVectorx = nodeManager.getField< fields::StiffnessVectorx >(); + arrayView1d< real32 > const stiffnessVectory = nodeManager.getField< fields::StiffnessVectory >(); + arrayView1d< real32 > const stiffnessVectorz = nodeManager.getField< fields::StiffnessVectorz >(); - arrayView1d< real32 > const ux_nm1 = nodeManager.getField< fields::Displacementx_nm1 >(); - arrayView1d< real32 > const uy_nm1 = nodeManager.getField< fields::Displacementy_nm1 >(); - arrayView1d< real32 > const uz_nm1 = nodeManager.getField< fields::Displacementz_nm1 >(); - arrayView1d< real32 > const ux_n = nodeManager.getField< fields::Displacementx_n >(); - arrayView1d< real32 > const uy_n = nodeManager.getField< fields::Displacementy_n >(); - arrayView1d< real32 > const uz_n = nodeManager.getField< fields::Displacementz_n >(); - arrayView1d< real32 > const ux_np1 = nodeManager.getField< fields::Displacementx_np1 >(); - arrayView1d< real32 > const uy_np1 = nodeManager.getField< fields::Displacementy_np1 >(); - arrayView1d< real32 > const uz_np1 = nodeManager.getField< fields::Displacementz_np1 >(); + arrayView1d< real32 > const ux_nm1 = nodeManager.getField< fields::Displacementx_nm1 >(); + arrayView1d< real32 > const uy_nm1 = nodeManager.getField< fields::Displacementy_nm1 >(); + arrayView1d< real32 > const uz_nm1 = nodeManager.getField< fields::Displacementz_nm1 >(); + arrayView1d< real32 > const ux_n = nodeManager.getField< fields::Displacementx_n >(); + arrayView1d< real32 > const uy_n = nodeManager.getField< fields::Displacementy_n >(); + arrayView1d< real32 > const uz_n = nodeManager.getField< fields::Displacementz_n >(); + arrayView1d< real32 > const ux_np1 = nodeManager.getField< fields::Displacementx_np1 >(); + arrayView1d< real32 > const uy_np1 = nodeManager.getField< fields::Displacementy_np1 >(); + arrayView1d< real32 > const uz_np1 = nodeManager.getField< fields::Displacementz_np1 >(); - /// get array of indicators: 1 if node on free surface; 0 otherwise - arrayView1d< localIndex const > const freeSurfaceNodeIndicator = nodeManager.getField< fields::FreeSurfaceNodeIndicator >(); + arrayView1d< real32 > const rhsx = nodeManager.getField< fields::ForcingRHSx >(); + arrayView1d< real32 > const rhsy = nodeManager.getField< fields::ForcingRHSy >(); + arrayView1d< real32 > const rhsz = nodeManager.getField< fields::ForcingRHSz >(); - arrayView1d< real32 > const rhsx = nodeManager.getField< fields::ForcingRHSx >(); - arrayView1d< real32 > const rhsy = nodeManager.getField< fields::ForcingRHSy >(); - arrayView1d< real32 > const rhsz = nodeManager.getField< fields::ForcingRHSz >(); + /// synchronize displacement fields + FieldIdentifiers fieldsToBeSync; + fieldsToBeSync.addFields( FieldLocation::Node, { fields::Displacementx_np1::key(), fields::Displacementy_np1::key(), fields::Displacementz_np1::key() } ); - auto kernelFactory = elasticWaveEquationSEMKernels::ExplicitElasticSEMFactory( dt ); + CommunicationTools & syncFields = CommunicationTools::getInstance(); + syncFields.synchronizeFields( fieldsToBeSync, + domain.getMeshBody( 0 ).getMeshLevel( m_discretizationName ), + domain.getNeighbors(), + true ); - finiteElement:: - regionBasedKernelApplication< EXEC_POLICY, - constitutive::NullModel, - CellElementSubRegion >( mesh, - regionNames, - getDiscretizationName(), - "", - kernelFactory ); + // compute the seismic traces since last step. + arrayView2d< real32 > const uXReceivers = m_displacementXNp1AtReceivers.toView(); + arrayView2d< real32 > const uYReceivers = m_displacementYNp1AtReceivers.toView(); + arrayView2d< real32 > const uZReceivers = m_displacementZNp1AtReceivers.toView(); + computeAllSeismoTraces( time_n, dt, ux_np1, ux_n, uXReceivers ); + computeAllSeismoTraces( time_n, dt, uy_np1, uy_n, uYReceivers ); + computeAllSeismoTraces( time_n, dt, uz_np1, uz_n, uZReceivers ); - addSourceToRightHandSide( cycleNumber, rhsx, rhsy, rhsz ); + incrementIndexSeismoTrace( time_n ); +} +void ElasticWaveEquationSEM::prepareNextTimestep( MeshLevel & mesh ) +{ + NodeManager & nodeManager = mesh.getNodeManager(); + arrayView1d< real32 > const ux_nm1 = nodeManager.getField< fields::Displacementx_nm1 >(); + arrayView1d< real32 > const uy_nm1 = nodeManager.getField< fields::Displacementy_nm1 >(); + arrayView1d< real32 > const uz_nm1 = nodeManager.getField< fields::Displacementz_nm1 >(); + arrayView1d< real32 > const ux_n = nodeManager.getField< fields::Displacementx_n >(); + arrayView1d< real32 > const uy_n = nodeManager.getField< fields::Displacementy_n >(); + arrayView1d< real32 > const uz_n = nodeManager.getField< fields::Displacementz_n >(); + arrayView1d< real32 > const ux_np1 = nodeManager.getField< fields::Displacementx_np1 >(); + arrayView1d< real32 > const uy_np1 = nodeManager.getField< fields::Displacementy_np1 >(); + arrayView1d< real32 > const uz_np1 = nodeManager.getField< fields::Displacementz_np1 >(); - real64 const dt2 = dt*dt; - forAll< EXEC_POLICY >( nodeManager.size(), [=] GEOS_HOST_DEVICE ( localIndex const a ) - { - if( freeSurfaceNodeIndicator[a] != 1 ) - { - ux_np1[a] = ux_n[a]; - ux_np1[a] *= 2.0*mass[a]; - ux_np1[a] -= (mass[a]-0.5*dt*dampingx[a])*ux_nm1[a]; - ux_np1[a] += dt2*(rhsx[a]-stiffnessVectorx[a]); - ux_np1[a] /= mass[a]+0.5*dt*dampingx[a]; - uy_np1[a] = uy_n[a]; - uy_np1[a] *= 2.0*mass[a]; - uy_np1[a] -= (mass[a]-0.5*dt*dampingy[a])*uy_nm1[a]; - uy_np1[a] += dt2*(rhsy[a]-stiffnessVectory[a]); - uy_np1[a] /= mass[a]+0.5*dt*dampingy[a]; - uz_np1[a] = uz_n[a]; - uz_np1[a] *= 2.0*mass[a]; - uz_np1[a] -= (mass[a]-0.5*dt*dampingz[a])*uz_nm1[a]; - uz_np1[a] += dt2*(rhsz[a]-stiffnessVectorz[a]); - uz_np1[a] /= mass[a]+0.5*dt*dampingz[a]; - } - } ); + arrayView1d< real32 > const stiffnessVectorx = nodeManager.getField< fields::StiffnessVectorx >(); + arrayView1d< real32 > const stiffnessVectory = nodeManager.getField< fields::StiffnessVectory >(); + arrayView1d< real32 > const stiffnessVectorz = nodeManager.getField< fields::StiffnessVectorz >(); - /// synchronize pressure fields - FieldIdentifiers fieldsToBeSync; - fieldsToBeSync.addFields( FieldLocation::Node, { fields::Displacementx_np1::key(), fields::Displacementy_np1::key(), fields::Displacementz_np1::key() } ); + arrayView1d< real32 > const rhsx = nodeManager.getField< fields::ForcingRHSx >(); + arrayView1d< real32 > const rhsy = nodeManager.getField< fields::ForcingRHSy >(); + arrayView1d< real32 > const rhsz = nodeManager.getField< fields::ForcingRHSz >(); - CommunicationTools & syncFields = CommunicationTools::getInstance(); - syncFields.synchronizeFields( fieldsToBeSync, - domain.getMeshBody( 0 ).getMeshLevel( m_discretizationName ), - domain.getNeighbors(), - true ); + SortedArrayView< localIndex const > const solverTargetNodesSet = m_solverTargetNodesSet.toViewConst(); - // compute the seismic traces since last step. - arrayView2d< real32 > const uXReceivers = m_displacementXNp1AtReceivers.toView(); - arrayView2d< real32 > const uYReceivers = m_displacementYNp1AtReceivers.toView(); - arrayView2d< real32 > const uZReceivers = m_displacementZNp1AtReceivers.toView(); + forAll< EXEC_POLICY >( solverTargetNodesSet.size(), [=] GEOS_HOST_DEVICE ( localIndex const n ) + { + localIndex const a = solverTargetNodesSet[n]; + ux_nm1[a] = ux_n[a]; + uy_nm1[a] = uy_n[a]; + uz_nm1[a] = uz_n[a]; + ux_n[a] = ux_np1[a]; + uy_n[a] = uy_np1[a]; + uz_n[a] = uz_np1[a]; + + stiffnessVectorx[a] = stiffnessVectory[a] = stiffnessVectorz[a] = 0.0; + rhsx[a] = rhsy[a] = rhsz[a] = 0.0; + } ); - computeAllSeismoTraces( time_n, dt, ux_np1, ux_n, uXReceivers ); - computeAllSeismoTraces( time_n, dt, uy_np1, uy_n, uYReceivers ); - computeAllSeismoTraces( time_n, dt, uz_np1, uz_n, uZReceivers ); +} - incrementIndexSeismoTrace( time_n ); +real64 ElasticWaveEquationSEM::explicitStepInternal( real64 const & time_n, + real64 const & dt, + integer const cycleNumber, + DomainPartition & domain ) +{ + GEOS_MARK_FUNCTION; - forAll< EXEC_POLICY >( nodeManager.size(), [=] GEOS_HOST_DEVICE ( localIndex const a ) - { - ux_nm1[a] = ux_n[a]; - uy_nm1[a] = uy_n[a]; - uz_nm1[a] = uz_n[a]; - ux_n[a] = ux_np1[a]; - uy_n[a] = uy_np1[a]; - uz_n[a] = uz_np1[a]; - - stiffnessVectorx[a] = 0.0; - stiffnessVectory[a] = 0.0; - stiffnessVectorz[a] = 0.0; - rhsx[a] = 0.0; - rhsy[a] = 0.0; - rhsz[a] = 0.0; - } ); + GEOS_LOG_RANK_0_IF( dt < epsilonLoc, "Warning! Value for dt: " << dt << "s is smaller than local threshold: " << epsilonLoc ); + + forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&] ( string const &, + MeshLevel & mesh, + arrayView1d< string const > const & regionNames ) + { + computeUnknowns( time_n, dt, cycleNumber, domain, mesh, regionNames ); + synchronizeUnknowns( time_n, dt, cycleNumber, domain, mesh, regionNames ); + prepareNextTimestep( mesh ); } ); return dt; - } void ElasticWaveEquationSEM::cleanup( real64 const time_n, diff --git a/src/coreComponents/physicsSolvers/wavePropagation/ElasticWaveEquationSEM.hpp b/src/coreComponents/physicsSolvers/wavePropagation/ElasticWaveEquationSEM.hpp index 0cb7afd3d6f..6054a6d63d0 100644 --- a/src/coreComponents/physicsSolvers/wavePropagation/ElasticWaveEquationSEM.hpp +++ b/src/coreComponents/physicsSolvers/wavePropagation/ElasticWaveEquationSEM.hpp @@ -51,6 +51,8 @@ class ElasticWaveEquationSEM : public WaveSolverBase ElasticWaveEquationSEM & operator=( ElasticWaveEquationSEM const & ) = delete; ElasticWaveEquationSEM & operator=( ElasticWaveEquationSEM && ) = delete; + /// String used to form the solverName used to register solvers in CoupledSolver + static string coupledSolverAttributePrefix() { return "elastic"; } static string catalogName() { return "ElasticSEM"; } /** @@ -136,6 +138,22 @@ class ElasticWaveEquationSEM : public WaveSolverBase real64 const & dt, integer const cycleNumber, DomainPartition & domain ); + + void computeUnknowns( real64 const & time_n, + real64 const & dt, + integer const cycleNumber, + DomainPartition & domain, + MeshLevel & mesh, + arrayView1d< string const > const & regionNames ); + + void synchronizeUnknowns( real64 const & time_n, + real64 const & dt, + integer const cycleNumber, + DomainPartition & domain, + MeshLevel & mesh, + arrayView1d< string const > const & regionNames ); + + void prepareNextTimestep( MeshLevel & mesh ); protected: virtual void postProcessInput() override final; @@ -174,10 +192,10 @@ class ElasticWaveEquationSEM : public WaveSolverBase /// Constant part of the source for the nodes listed in m_sourceNodeIds in x-direction array2d< real64 > m_sourceConstantsx; - /// Constant part of the source for the nodes listed in m_sourceNodeIds in x-direction + /// Constant part of the source for the nodes listed in m_sourceNodeIds in y-direction array2d< real64 > m_sourceConstantsy; - /// Constant part of the source for the nodes listed in m_sourceNodeIds in x-direction + /// Constant part of the source for the nodes listed in m_sourceNodeIds in z-direction array2d< real64 > m_sourceConstantsz; /// Displacement_np1 at the receiver location for each time step for each receiver (x-component) @@ -297,8 +315,8 @@ DECLARE_FIELD( ForcingRHSz, WRITE_AND_READ, "RHS for z-direction" ); -DECLARE_FIELD( MassVector, - "massVector", +DECLARE_FIELD( ElasticMassVector, + "elasticMassVector", array1d< real32 >, 0, NOPLOT, @@ -353,40 +371,40 @@ DECLARE_FIELD( StiffnessVectorz, WRITE_AND_READ, "z-component of stiffness vector." ); -DECLARE_FIELD( MediumVelocityVp, - "mediumVelocityVp", +DECLARE_FIELD( ElasticVelocityVp, + "elasticVelocityVp", array1d< real32 >, 0, NOPLOT, WRITE_AND_READ, "P-waves speed in the cell" ); -DECLARE_FIELD( MediumVelocityVs, - "mediumVelocityVs", +DECLARE_FIELD( ElasticVelocityVs, + "elasticVelocityVs", array1d< real32 >, 0, NOPLOT, WRITE_AND_READ, "S-waves speed in the cell" ); -DECLARE_FIELD( MediumDensity, - "mediumDensity", +DECLARE_FIELD( ElasticDensity, + "elasticDensity", array1d< real32 >, 0, NOPLOT, WRITE_AND_READ, "Medium density of the cell" ); -DECLARE_FIELD( FreeSurfaceFaceIndicator, - "freeSurfaceFaceIndicator", +DECLARE_FIELD( ElasticFreeSurfaceFaceIndicator, + "elasticFreeSurfaceFaceIndicator", array1d< localIndex >, 0, NOPLOT, WRITE_AND_READ, "Free surface indicator, 1 if a face is on free surface 0 otherwise." ); -DECLARE_FIELD( FreeSurfaceNodeIndicator, - "freeSurfaceNodeIndicator", +DECLARE_FIELD( ElasticFreeSurfaceNodeIndicator, + "elasticFreeSurfaceNodeIndicator", array1d< localIndex >, 0, NOPLOT, diff --git a/src/coreComponents/physicsSolvers/wavePropagation/ElasticWaveEquationSEMKernel.hpp b/src/coreComponents/physicsSolvers/wavePropagation/ElasticWaveEquationSEMKernel.hpp index 609f97bd245..636abda029a 100644 --- a/src/coreComponents/physicsSolvers/wavePropagation/ElasticWaveEquationSEMKernel.hpp +++ b/src/coreComponents/physicsSolvers/wavePropagation/ElasticWaveEquationSEMKernel.hpp @@ -148,7 +148,7 @@ struct PrecomputeSourceAndReceiverKernel for( localIndex q=0; q< numNodesPerElem; ++q ) { real64 inc[3] = { 0, 0, 0 }; - sourceNodeIds[isrc][q] = elemsToNodes[k][q]; + sourceNodeIds[isrc][q] = elemsToNodes( k, q ); inc[0] += sourceForce[0] * N[q]; inc[1] += sourceForce[1] * N[q]; inc[2] += sourceForce[2] * N[q]; @@ -200,7 +200,7 @@ struct PrecomputeSourceAndReceiverKernel for( localIndex a = 0; a < numNodesPerElem; ++a ) { - receiverNodeIds[ircv][a] = elemsToNodes[k][a]; + receiverNodeIds[ircv][a] = elemsToNodes( k, a ); receiverConstants[ircv][a] = Ntest[a]; } } @@ -420,9 +420,9 @@ class ExplicitElasticSEM : public finiteElement::KernelBase< SUBREGION_TYPE, m_stiffnessVectorx( nodeManager.getField< fields::StiffnessVectorx >() ), m_stiffnessVectory( nodeManager.getField< fields::StiffnessVectory >() ), m_stiffnessVectorz( nodeManager.getField< fields::StiffnessVectorz >() ), - m_density( elementSubRegion.template getField< fields::MediumDensity >() ), - m_velocityVp( elementSubRegion.template getField< fields::MediumVelocityVp >() ), - m_velocityVs( elementSubRegion.template getField< fields::MediumVelocityVs >() ), + m_density( elementSubRegion.template getField< fields::ElasticDensity >() ), + m_velocityVp( elementSubRegion.template getField< fields::ElasticVelocityVp >() ), + m_velocityVs( elementSubRegion.template getField< fields::ElasticVelocityVs >() ), m_dt( dt ) { GEOS_UNUSED_VAR( edgeManager ); @@ -527,13 +527,13 @@ class ExplicitElasticSEM : public finiteElement::KernelBase< SUBREGION_TYPE, /// The array containing the nodal displacement array in z direction. arrayView1d< real32 > const m_uz_n; - /// The array containing the product of the stiffness matrix and the nodal pressure. + /// The array containing the product of the stiffness matrix and the nodal displacement. arrayView1d< real32 > const m_stiffnessVectorx; - /// The array containing the product of the stiffness matrix and the nodal pressure. + /// The array containing the product of the stiffness matrix and the nodal displacement. arrayView1d< real32 > const m_stiffnessVectory; - /// The array containing the product of the stiffness matrix and the nodal pressure. + /// The array containing the product of the stiffness matrix and the nodal displacement. arrayView1d< real32 > const m_stiffnessVectorz; /// The array containing the density of the medium diff --git a/src/coreComponents/physicsSolvers/wavePropagation/WaveSolverBase.cpp b/src/coreComponents/physicsSolvers/wavePropagation/WaveSolverBase.cpp index 239426e6dc9..47ef2849df0 100644 --- a/src/coreComponents/physicsSolvers/wavePropagation/WaveSolverBase.cpp +++ b/src/coreComponents/physicsSolvers/wavePropagation/WaveSolverBase.cpp @@ -201,12 +201,12 @@ void WaveSolverBase::registerDataOnMesh( Group & meshBodies ) arrayView2d< real64 const, nodes::REFERENCE_POSITION_USD > const X = nodeManager.referencePosition().toViewConst(); nodeManager.getField< fields::referencePosition32 >().resizeDimension< 1 >( X.size( 1 ) ); - arrayView2d< wsCoordType, nodes::REFERENCE_POSITION_USD > const X32 = nodeManager.getField< fields::referencePosition32 >(); + arrayView2d< wsCoordType, nodes::REFERENCE_POSITION_USD > const nodeCoords32 = nodeManager.getField< fields::referencePosition32 >(); for( int i = 0; i < X.size( 0 ); i++ ) { for( int j = 0; j < X.size( 1 ); j++ ) { - X32[i][j] = X[i][j]; + nodeCoords32[i][j] = X[i][j]; } } } ); @@ -400,6 +400,24 @@ localIndex WaveSolverBase::getNumNodesPerElem() return numNodesPerElem; } +void WaveSolverBase::computeTargetNodeSet( arrayView2d< localIndex const, cells::NODE_MAP_USD > const & elemsToNodes, + localIndex const subRegionSize, + localIndex const numQuadraturePointsPerElem ) +{ + array1d< localIndex > scratch( subRegionSize * numQuadraturePointsPerElem ); + localIndex i = 0; + for( localIndex e = 0; e < subRegionSize; ++e ) + { + for( localIndex q = 0; q < numQuadraturePointsPerElem; ++q ) + { + scratch[i++] = elemsToNodes( e, q ); + } + } + std::ptrdiff_t const numUniqueValues = LvArray::sortedArrayManipulation::makeSortedUnique( scratch.begin(), scratch.end() ); + + m_solverTargetNodesSet.insert( scratch.begin(), scratch.begin() + numUniqueValues ); +} + void WaveSolverBase::incrementIndexSeismoTrace( real64 const time_n ) { while( (m_dtSeismoTrace * m_indexSeismoTrace) <= (time_n + epsilonLoc) && m_indexSeismoTrace < m_nsamplesSeismoTrace ) diff --git a/src/coreComponents/physicsSolvers/wavePropagation/WaveSolverBase.hpp b/src/coreComponents/physicsSolvers/wavePropagation/WaveSolverBase.hpp index 820f5e8f58c..cc67eba65de 100644 --- a/src/coreComponents/physicsSolvers/wavePropagation/WaveSolverBase.hpp +++ b/src/coreComponents/physicsSolvers/wavePropagation/WaveSolverBase.hpp @@ -124,6 +124,12 @@ class WaveSolverBase : public SolverBase */ void reinit() override final; + SortedArray< localIndex > const & getSolverNodesSet() { return m_solverTargetNodesSet; } + + void computeTargetNodeSet( arrayView2d< localIndex const, cells::NODE_MAP_USD > const & elemsToNodes, + localIndex const subRegionSize, + localIndex const numQuadraturePointsPerElem ); + protected: virtual void postProcessInput() override; @@ -317,6 +323,9 @@ class WaveSolverBase : public SolverBase /// LIFO to store p_dt2 std::unique_ptr< LifoStorage< real32, localIndex > > m_lifo; + /// A set of target nodes IDs that will be handled by the current solver + SortedArray< localIndex > m_solverTargetNodesSet; + struct parametersPML { /// Mininum (x,y,z) coordinates of inner PML boundaries diff --git a/src/coreComponents/physicsSolvers/wavePropagation/WaveSolverBaseFields.hpp b/src/coreComponents/physicsSolvers/wavePropagation/WaveSolverBaseFields.hpp index c55fdb8cc2d..333139c21c1 100644 --- a/src/coreComponents/physicsSolvers/wavePropagation/WaveSolverBaseFields.hpp +++ b/src/coreComponents/physicsSolvers/wavePropagation/WaveSolverBaseFields.hpp @@ -160,8 +160,8 @@ DECLARE_FIELD( ForcingRHS, WRITE_AND_READ, "RHS" ); -DECLARE_FIELD( MassVector, - "massVector", +DECLARE_FIELD( AcousticMassVector, + "acousticMassVector", array1d< real32 >, 0, NOPLOT, @@ -208,32 +208,32 @@ DECLARE_FIELD( DampingVector_qp, WRITE_AND_READ, "Diagonal of the Damping Matrix for p terms in q equation." ); -DECLARE_FIELD( MediumVelocity, - "mediumVelocity", +DECLARE_FIELD( AcousticVelocity, + "acousticVelocity", array1d< real32 >, 0, NOPLOT, WRITE_AND_READ, "Medium velocity of the cell" ); -DECLARE_FIELD( MediumDensity, - "mediumDensity", +DECLARE_FIELD( AcousticDensity, + "acousticDensity", array1d< real32 >, 0, NOPLOT, WRITE_AND_READ, "Medium density of the cell" ); -DECLARE_FIELD( FreeSurfaceFaceIndicator, - "freeSurfaceFaceIndicator", +DECLARE_FIELD( AcousticFreeSurfaceFaceIndicator, + "acousticFreeSurfaceFaceIndicator", array1d< localIndex >, 0, NOPLOT, WRITE_AND_READ, "Free surface indicator, 1 if a face is on free surface 0 otherwise." ); -DECLARE_FIELD( FreeSurfaceNodeIndicator, - "freeSurfaceNodeIndicator", +DECLARE_FIELD( AcousticFreeSurfaceNodeIndicator, + "acousticFreeSurfaceNodeIndicator", array1d< localIndex >, 0, NOPLOT, @@ -272,6 +272,14 @@ DECLARE_FIELD( BottomSurfaceNodeIndicator, WRITE_AND_READ, "Bottom surface indicator, 1 if a face is on the bottom surface 0 otherwise." ); +DECLARE_FIELD( ElasticMassVector, + "elasticMassVector", + array1d< real32 >, + 0, + NOPLOT, + WRITE_AND_READ, + "Diagonal of the Mass Matrix." ); + DECLARE_FIELD( Displacementx_np1, "displacementx_np1", array1d< real32 >, @@ -368,22 +376,46 @@ DECLARE_FIELD( DampingVectorz, WRITE_AND_READ, "Diagonal Damping Matrix in z-direction." ); -DECLARE_FIELD( MediumVelocityVp, - "mediumVelocityVp", +DECLARE_FIELD( ElasticVelocityVp, + "elasticVelocityVp", array1d< real32 >, 0, NOPLOT, WRITE_AND_READ, "P-waves speed in the cell" ); -DECLARE_FIELD( MediumVelocityVs, - "mediumVelocityVs", +DECLARE_FIELD( ElasticVelocityVs, + "elasticVelocityVs", array1d< real32 >, 0, NOPLOT, WRITE_AND_READ, "S-waves speed in the cell" ); +DECLARE_FIELD( ElasticDensity, + "elasticDensity", + array1d< real32 >, + 0, + NOPLOT, + WRITE_AND_READ, + "Medium density of the cell" ); + +DECLARE_FIELD( ElasticFreeSurfaceFaceIndicator, + "elasticFreeSurfaceFaceIndicator", + array1d< localIndex >, + 0, + NOPLOT, + WRITE_AND_READ, + "Free surface indicator, 1 if a face is on free surface 0 otherwise." ); + +DECLARE_FIELD( ElasticFreeSurfaceNodeIndicator, + "elasticFreeSurfaceNodeIndicator", + array1d< localIndex >, + 0, + NOPLOT, + WRITE_AND_READ, + "Free surface indicator, 1 if a node is on free surface 0 otherwise." ); + DECLARE_FIELD( Lambda, "lambda", array1d< real32 >, diff --git a/src/coreComponents/unitTests/wavePropagationTests/testWavePropagation.cpp b/src/coreComponents/unitTests/wavePropagationTests/testWavePropagation.cpp index 44fcc1dce8a..4edd9e63d6f 100644 --- a/src/coreComponents/unitTests/wavePropagationTests/testWavePropagation.cpp +++ b/src/coreComponents/unitTests/wavePropagationTests/testWavePropagation.cpp @@ -124,14 +124,14 @@ char const * xmlInput = name="cellVelocity" initialCondition="1" objectPath="ElementRegions/Region/cb" - fieldName="mediumVelocity" + fieldName="acousticVelocity" scale="1500" setNames="{ all }"/> From be24ffe117e8b5b2ac009e19e1307188ff80a195 Mon Sep 17 00:00:00 2001 From: Pavel Tomin Date: Thu, 11 Jan 2024 11:33:46 -0600 Subject: [PATCH 21/40] fix issue #2871 (#2916) --- .../capillaryPressure/BrooksCoreyCapillaryPressure.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreComponents/constitutive/capillaryPressure/BrooksCoreyCapillaryPressure.cpp b/src/coreComponents/constitutive/capillaryPressure/BrooksCoreyCapillaryPressure.cpp index 5e11be9caed..10518b167d8 100644 --- a/src/coreComponents/constitutive/capillaryPressure/BrooksCoreyCapillaryPressure.cpp +++ b/src/coreComponents/constitutive/capillaryPressure/BrooksCoreyCapillaryPressure.cpp @@ -88,7 +88,7 @@ void BrooksCoreyCapillaryPressure::postProcessInput() if( m_phaseTypes[ip] != CapillaryPressureBase::REFERENCE_PHASE ) { - GEOS_THROW_IF_LT_MSG( m_phaseCapPressureExponentInv[ip], 1.0, + GEOS_THROW_IF_LE_MSG( m_phaseCapPressureExponentInv[ip], 0.0, errorMsg( viewKeyStruct::phaseCapPressureExponentInvString() ), InputError ); GEOS_THROW_IF_LT_MSG( m_phaseEntryPressure[ip], 0.0, From 4ec0901fa9bfd9a81224e378eef82ece3409cba6 Mon Sep 17 00:00:00 2001 From: Pavel Tomin Date: Thu, 11 Jan 2024 11:35:19 -0600 Subject: [PATCH 22/40] Poromechanics solvers cleanup (#2913) --- .../fluidFlow/CompositionalMultiphaseFVM.hpp | 8 - .../fluidFlow/FlowSolverBase.hpp | 2 - .../fluidFlow/SinglePhaseHybridFVM.hpp | 7 - .../multiphysics/MultiphasePoromechanics.cpp | 273 ++++-------------- .../multiphysics/MultiphasePoromechanics.hpp | 64 +--- .../multiphysics/PoromechanicsSolver.hpp | 251 +++++++++++++++- .../multiphysics/SinglePhasePoromechanics.cpp | 255 ++-------------- .../multiphysics/SinglePhasePoromechanics.hpp | 61 +--- 8 files changed, 328 insertions(+), 593 deletions(-) diff --git a/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseFVM.hpp b/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseFVM.hpp index b44138bbb67..82776f70485 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseFVM.hpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseFVM.hpp @@ -180,14 +180,6 @@ class CompositionalMultiphaseFVM : public CompositionalMultiphaseBase virtual void initializePreSubGroups() override; - /** - * @brief Compute the largest CFL number in the domain - * @param dt the time step size - * @param domain the domain containing the mesh and fields - */ - void - computeCFLNumbers( real64 const & dt, DomainPartition & domain ); - struct DBCParameters { /// Flag to enable Dissipation Based Continuation Method diff --git a/src/coreComponents/physicsSolvers/fluidFlow/FlowSolverBase.hpp b/src/coreComponents/physicsSolvers/fluidFlow/FlowSolverBase.hpp index 61f8b0f5f5a..8fcb9d89522 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/FlowSolverBase.hpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/FlowSolverBase.hpp @@ -63,8 +63,6 @@ class FlowSolverBase : public SolverBase virtual void registerDataOnMesh( Group & MeshBodies ) override; - localIndex numDofPerCell() const { return m_numDofPerCell; } - struct viewKeyStruct : SolverBase::viewKeyStruct { // misc inputs diff --git a/src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseHybridFVM.hpp b/src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseHybridFVM.hpp index dafb18f6a54..2f124db3319 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseHybridFVM.hpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseHybridFVM.hpp @@ -176,13 +176,6 @@ class SinglePhaseHybridFVM : public SinglePhaseBase /**@}*/ - - struct viewKeyStruct : SinglePhaseBase::viewKeyStruct - { - // primary face-based field - static constexpr char const * deltaFacePressureString() { return "deltaFacePressure"; } - }; - virtual void initializePreSubGroups() override; virtual void initializePostInitialConditionsPreSubGroups() override; diff --git a/src/coreComponents/physicsSolvers/multiphysics/MultiphasePoromechanics.cpp b/src/coreComponents/physicsSolvers/multiphysics/MultiphasePoromechanics.cpp index 899254b1c64..8c003debc24 100644 --- a/src/coreComponents/physicsSolvers/multiphysics/MultiphasePoromechanics.cpp +++ b/src/coreComponents/physicsSolvers/multiphysics/MultiphasePoromechanics.cpp @@ -22,7 +22,6 @@ #include "constitutive/fluid/multifluid/MultiFluidBase.hpp" #include "constitutive/solid/PorousSolid.hpp" -#include "mesh/utilities/AverageOverQuadraturePointsKernel.hpp" #include "physicsSolvers/fluidFlow/CompositionalMultiphaseBase.hpp" #include "physicsSolvers/fluidFlow/FlowSolverBaseFields.hpp" #include "physicsSolvers/multiphysics/CompositionalMultiphaseReservoirAndWells.hpp" @@ -72,8 +71,7 @@ catalogName() template< typename FLOW_SOLVER > MultiphasePoromechanics< FLOW_SOLVER >::MultiphasePoromechanics( const string & name, Group * const parent ) - : Base( name, parent ), - m_isThermal( 0 ) + : Base( name, parent ) { this->registerWrapper( viewKeyStruct::stabilizationTypeString(), &m_stabilizationType ). setInputFlag( InputFlags::OPTIONAL ). @@ -92,16 +90,6 @@ MultiphasePoromechanics< FLOW_SOLVER >::MultiphasePoromechanics( const string & setInputFlag( InputFlags::OPTIONAL ). setDescription( "Constant multiplier of stabilization strength." ); - this->registerWrapper( viewKeyStruct::isThermalString(), &m_isThermal ). - setApplyDefaultValue( 0 ). - setInputFlag( InputFlags::OPTIONAL ). - setDescription( "Flag indicating whether the problem is thermal or not. Set isThermal=\"1\" to enable the thermal coupling" ); - - this->registerWrapper( viewKeyStruct::performStressInitializationString(), &m_performStressInitialization ). - setApplyDefaultValue( false ). - setInputFlag( InputFlags::FALSE ). - setDescription( "Flag to indicate that the solver is going to perform stress initialization" ); - LinearSolverParameters & linearSolverParameters = this->m_linearSolverParameters.get(); linearSolverParameters.mgr.strategy = LinearSolverParameters::MGR::StrategyType::multiphasePoromechanics; linearSolverParameters.mgr.separateComponents = true; @@ -114,7 +102,7 @@ void MultiphasePoromechanics< FLOW_SOLVER >::postProcessInput() { Base::postProcessInput(); - GEOS_ERROR_IF( flowSolver()->catalogName() == "CompositionalMultiphaseReservoir" && + GEOS_ERROR_IF( this->flowSolver()->catalogName() == "CompositionalMultiphaseReservoir" && this->getNonlinearSolverParameters().couplingType() != NonlinearSolverParameters::CouplingType::Sequential, GEOS_FMT( "{}: {} solver is only designed to work for {} = {}", this->getDataContext(), catalogName(), NonlinearSolverParameters::viewKeysStruct::couplingTypeString(), @@ -125,46 +113,26 @@ void MultiphasePoromechanics< FLOW_SOLVER >::postProcessInput() template< typename FLOW_SOLVER > void MultiphasePoromechanics< FLOW_SOLVER >::registerDataOnMesh( Group & meshBodies ) { - SolverBase::registerDataOnMesh( meshBodies ); - - if( this->getNonlinearSolverParameters().m_couplingType == NonlinearSolverParameters::CouplingType::Sequential ) - { - // to let the solid mechanics solver that there is a pressure and temperature RHS in the mechanics solve - solidMechanicsSolver()->enableFixedStressPoromechanicsUpdate(); - // to let the flow solver that saving pressure_k and temperature_k is necessary (for the fixed-stress porosity terms) - flowSolver()->enableFixedStressPoromechanicsUpdate(); - } + Base::registerDataOnMesh( meshBodies ); - this->template forDiscretizationOnMeshTargets( meshBodies, [&] ( string const &, - MeshLevel & mesh, - arrayView1d< string const > const & regionNames ) + if( m_stabilizationType == StabilizationType::Global || + m_stabilizationType == StabilizationType::Local ) { - ElementRegionManager & elemManager = mesh.getElemManager(); - - elemManager.forElementSubRegions< ElementSubRegionBase >( regionNames, - [&]( localIndex const, - ElementSubRegionBase & subRegion ) + this->template forDiscretizationOnMeshTargets( meshBodies, [&] ( string const &, + MeshLevel & mesh, + arrayView1d< string const > const & regionNames ) { - subRegion.registerWrapper< string >( viewKeyStruct::porousMaterialNamesString() ). - setPlotLevel( PlotLevel::NOPLOT ). - setRestartFlags( RestartFlags::NO_WRITE ). - setSizedFromParent( 0 ); + ElementRegionManager & elemManager = mesh.getElemManager(); - if( m_stabilizationType == StabilizationType::Global || - m_stabilizationType == StabilizationType::Local ) + elemManager.forElementSubRegions< ElementSubRegionBase >( regionNames, + [&]( localIndex const, + ElementSubRegionBase & subRegion ) { subRegion.registerField< fields::flow::macroElementIndex >( this->getName() ); subRegion.registerField< fields::flow::elementStabConstant >( this->getName() ); - } - - if( this->getNonlinearSolverParameters().m_couplingType == NonlinearSolverParameters::CouplingType::Sequential ) - { - // register the bulk density for use in the solid mechanics solver - // ideally we would resize it here as well, but the solid model name is not available yet (see below) - subRegion.registerField< fields::poromechanics::bulkDensity >( this->getName() ); - } + } ); } ); - } ); + } } template< typename FLOW_SOLVER > @@ -176,20 +144,6 @@ void MultiphasePoromechanics< FLOW_SOLVER >::setupCoupling( DomainPartition cons DofManager::Connector::Elem ); } -template< typename FLOW_SOLVER > -void MultiphasePoromechanics< FLOW_SOLVER >::setupDofs( DomainPartition const & domain, - DofManager & dofManager ) const -{ - // note that the order of operations matters a lot here (for instance for the MGR labels) - // we must set up dofs for solid mechanics first, and then for flow - // that's the reason why this function is here and not in CoupledSolvers.hpp - solidMechanicsSolver()->setupDofs( domain, dofManager ); - flowSolver()->setupDofs( domain, dofManager ); - - setupCoupling( domain, dofManager ); -} - - template< typename FLOW_SOLVER > void MultiphasePoromechanics< FLOW_SOLVER >::assembleSystem( real64 const GEOS_UNUSED_PARAM( time ), real64 const dt, @@ -215,7 +169,7 @@ void MultiphasePoromechanics< FLOW_SOLVER >::assembleSystem( real64 const GEOS_U string const flowDofKey = dofManager.getKey( CompositionalMultiphaseBase::viewKeyStruct::elemDofFieldString() ); - if( m_isThermal ) + if( this->m_isThermal ) { poromechanicsMaxForce = assemblyLaunch< constitutive::PorousSolid< ElasticIsotropic >, // TODO: change once there is a cmake solution @@ -227,9 +181,9 @@ void MultiphasePoromechanics< FLOW_SOLVER >::assembleSystem( real64 const GEOS_U localRhs, dt, flowDofKey, - flowSolver()->numFluidComponents(), - flowSolver()->numFluidPhases(), - flowSolver()->useTotalMassEquation(), + this->flowSolver()->numFluidComponents(), + this->flowSolver()->numFluidPhases(), + this->flowSolver()->useTotalMassEquation(), FlowSolverBase::viewKeyStruct::fluidNamesString() ); } else @@ -244,18 +198,18 @@ void MultiphasePoromechanics< FLOW_SOLVER >::assembleSystem( real64 const GEOS_U localRhs, dt, flowDofKey, - flowSolver()->numFluidComponents(), - flowSolver()->numFluidPhases(), - flowSolver()->useTotalMassEquation(), + this->flowSolver()->numFluidComponents(), + this->flowSolver()->numFluidPhases(), + this->flowSolver()->useTotalMassEquation(), FlowSolverBase::viewKeyStruct::fluidNamesString() ); } } ); // step 2: apply mechanics solver on its target regions not included in the poromechanics solver target regions - solidMechanicsSolver()->forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&] ( string const &, - MeshLevel & mesh, - arrayView1d< string const > const & regionNames ) + this->solidMechanicsSolver()->forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&] ( string const &, + MeshLevel & mesh, + arrayView1d< string const > const & regionNames ) { // collect the target region of the mechanics solver not included in the poromechanics target regions @@ -289,7 +243,7 @@ void MultiphasePoromechanics< FLOW_SOLVER >::assembleSystem( real64 const GEOS_U } ); - solidMechanicsSolver()->getMaxForce() = LvArray::math::max( mechanicsMaxForce, poromechanicsMaxForce ); + this->solidMechanicsSolver()->getMaxForce() = LvArray::math::max( mechanicsMaxForce, poromechanicsMaxForce ); // step 3: compute the fluxes (face-based contributions) @@ -297,19 +251,19 @@ void MultiphasePoromechanics< FLOW_SOLVER >::assembleSystem( real64 const GEOS_U m_stabilizationType == StabilizationType::Local ) { updateStabilizationParameters( domain ); - flowSolver()->assembleStabilizedFluxTerms( dt, - domain, - dofManager, - localMatrix, - localRhs ); + this->flowSolver()->assembleStabilizedFluxTerms( dt, + domain, + dofManager, + localMatrix, + localRhs ); } else { - flowSolver()->assembleFluxTerms( dt, - domain, - dofManager, - localMatrix, - localRhs ); + this->flowSolver()->assembleFluxTerms( dt, + domain, + dofManager, + localMatrix, + localRhs ); } } @@ -326,11 +280,11 @@ void MultiphasePoromechanics< FLOW_SOLVER >::updateState( DomainPartition & doma [&]( localIndex const, CellElementSubRegion & subRegion ) { - real64 const deltaPhaseVolFrac = flowSolver()->updateFluidState( subRegion ); + real64 const deltaPhaseVolFrac = this->flowSolver()->updateFluidState( subRegion ); maxDeltaPhaseVolFrac = LvArray::math::max( maxDeltaPhaseVolFrac, deltaPhaseVolFrac ); - if( m_isThermal ) + if( this->m_isThermal ) { - flowSolver()->updateSolidInternalEnergyModel( subRegion ); + this->flowSolver()->updateSolidInternalEnergyModel( subRegion ); } } ); } ); @@ -342,14 +296,14 @@ void MultiphasePoromechanics< FLOW_SOLVER >::updateState( DomainPartition & doma template< typename FLOW_SOLVER > void MultiphasePoromechanics< FLOW_SOLVER >::initializePostInitialConditionsPreSubGroups() { - SolverBase::initializePostInitialConditionsPreSubGroups(); + Base::initializePostInitialConditionsPreSubGroups(); arrayView1d< string const > const & poromechanicsTargetRegionNames = this->template getReference< array1d< string > >( SolverBase::viewKeyStruct::targetRegionsString() ); arrayView1d< string const > const & solidMechanicsTargetRegionNames = - solidMechanicsSolver()->template getReference< array1d< string > >( SolverBase::viewKeyStruct::targetRegionsString() ); + this->solidMechanicsSolver()->template getReference< array1d< string > >( SolverBase::viewKeyStruct::targetRegionsString() ); arrayView1d< string const > const & flowTargetRegionNames = - flowSolver()->template getReference< array1d< string > >( SolverBase::viewKeyStruct::targetRegionsString() ); + this->flowSolver()->template getReference< array1d< string > >( SolverBase::viewKeyStruct::targetRegionsString() ); for( integer i = 0; i < poromechanicsTargetRegionNames.size(); ++i ) { GEOS_THROW_IF( std::find( solidMechanicsTargetRegionNames.begin(), solidMechanicsTargetRegionNames.end(), @@ -357,22 +311,22 @@ void MultiphasePoromechanics< FLOW_SOLVER >::initializePostInitialConditionsPreS == solidMechanicsTargetRegionNames.end(), GEOS_FMT( "{} {}: region {} must be a target region of {}", getCatalogName(), this->getDataContext(), poromechanicsTargetRegionNames[i], - solidMechanicsSolver()->getDataContext() ), + this->solidMechanicsSolver()->getDataContext() ), InputError ); GEOS_THROW_IF( std::find( flowTargetRegionNames.begin(), flowTargetRegionNames.end(), poromechanicsTargetRegionNames[i] ) == flowTargetRegionNames.end(), GEOS_FMT( "{} {}: region `{}` must be a target region of `{}`", - getCatalogName(), this->getDataContext(), poromechanicsTargetRegionNames[i], flowSolver()->getDataContext() ), + getCatalogName(), this->getDataContext(), poromechanicsTargetRegionNames[i], this->flowSolver()->getDataContext() ), InputError ); } - integer & isFlowThermal = flowSolver()->isThermal(); - GEOS_WARNING_IF( m_isThermal && !isFlowThermal, + integer & isFlowThermal = this->flowSolver()->isThermal(); + GEOS_WARNING_IF( this->m_isThermal && !isFlowThermal, GEOS_FMT( "{} {}: The attribute `{}` of the flow solver `{}` is set to 1 since the poromechanics solver is thermal", - getCatalogName(), this->getName(), FlowSolverBase::viewKeyStruct::isThermalString(), flowSolver()->getName() ) ); - isFlowThermal = m_isThermal; + getCatalogName(), this->getName(), FlowSolverBase::viewKeyStruct::isThermalString(), this->flowSolver()->getName() ) ); + isFlowThermal = this->m_isThermal; - if( m_isThermal ) + if( this->m_isThermal ) { this->m_linearSolverParameters.get().mgr.strategy = LinearSolverParameters::MGR::StrategyType::thermalMultiphasePoromechanics; } @@ -381,46 +335,12 @@ void MultiphasePoromechanics< FLOW_SOLVER >::initializePostInitialConditionsPreS template< typename FLOW_SOLVER > void MultiphasePoromechanics< FLOW_SOLVER >::initializePreSubGroups() { - SolverBase::initializePreSubGroups(); + Base::initializePreSubGroups(); GEOS_THROW_IF( m_stabilizationType == StabilizationType::Local, this->getWrapperDataContext( viewKeyStruct::stabilizationTypeString() ) << ": Local stabilization has been disabled temporarily", InputError ); - - DomainPartition & domain = this->template getGroupByPath< DomainPartition >( "/Problem/domain" ); - - this->template forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&] ( string const &, - MeshLevel & mesh, - arrayView1d< string const > const & regionNames ) - { - ElementRegionManager & elementRegionManager = mesh.getElemManager(); - elementRegionManager.forElementSubRegions< ElementSubRegionBase >( regionNames, - [&]( localIndex const, - ElementSubRegionBase & subRegion ) - { - string & porousName = subRegion.getReference< string >( viewKeyStruct::porousMaterialNamesString() ); - porousName = this->template getConstitutiveName< CoupledSolidBase >( subRegion ); - GEOS_ERROR_IF( porousName.empty(), GEOS_FMT( "{}: Solid model not found on subregion {}", - this->getDataContext(), subRegion.getName() ) ); - - if( subRegion.hasField< fields::poromechanics::bulkDensity >() ) - { - // get the solid model to know the number of quadrature points and resize the bulk density - CoupledSolidBase const & solid = this->template getConstitutiveModel< CoupledSolidBase >( subRegion, porousName ); - subRegion.getField< fields::poromechanics::bulkDensity >().resizeDimension< 1 >( solid.getDensity().size( 1 ) ); - } - } ); - } ); -} - -template< typename FLOW_SOLVER > -void MultiphasePoromechanics< FLOW_SOLVER >::implicitStepSetup( real64 const & time_n, - real64 const & dt, - DomainPartition & domain ) -{ - flowSolver()->keepFlowVariablesConstantDuringInitStep( m_performStressInitialization ); - Base::implicitStepSetup( time_n, dt, domain ); } template< typename FLOW_SOLVER > @@ -487,57 +407,6 @@ void MultiphasePoromechanics< FLOW_SOLVER >::updateStabilizationParameters( Doma } ); } -template< typename FLOW_SOLVER > -void MultiphasePoromechanics< FLOW_SOLVER >::mapSolutionBetweenSolvers( DomainPartition & domain, integer const solverType ) -{ - GEOS_MARK_FUNCTION; - - /// After the flow solver - if( solverType == static_cast< integer >( Base::SolverType::Flow ) ) - { - // save pressure and temperature at the end of this iteration - flowSolver()->saveIterationState( domain ); - - this->template forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const &, - MeshLevel & mesh, - arrayView1d< string const > const & regionNames ) - { - - mesh.getElemManager().forElementSubRegions< CellElementSubRegion >( regionNames, [&]( localIndex const, - auto & subRegion ) - { - // update the bulk density - // TODO: ideally, we would not recompute the bulk density, but a more general "rhs" containing the body force and the - // pressure/temperature terms - updateBulkDensity( subRegion ); - } ); - } ); - } - - /// After the solid mechanics solver - if( solverType == static_cast< integer >( Base::SolverType::SolidMechanics ) ) - { - // compute the average of the mean total stress increment over quadrature points - averageMeanTotalStressIncrement( domain ); - - this->template forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const &, - MeshLevel & mesh, - arrayView1d< string const > const & regionNames ) - { - - mesh.getElemManager().forElementSubRegions< CellElementSubRegion >( regionNames, [&]( localIndex const, - auto & subRegion ) - { - // update the porosity after a change in displacement (after mechanics solve) - // or a change in pressure/temperature (after a flow solve) - flowSolver()->updatePorosityAndPermeability( subRegion ); - } ); - } ); - } - // call base method (needed to perform nonlinear acceleration) - Base::mapSolutionBetweenSolvers( domain, solverType ); -} - template< typename FLOW_SOLVER > void MultiphasePoromechanics< FLOW_SOLVER >::updateBulkDensity( ElementSubRegionBase & subRegion ) { @@ -552,54 +421,12 @@ void MultiphasePoromechanics< FLOW_SOLVER >::updateBulkDensity( ElementSubRegion // update the bulk density poromechanicsKernels:: MultiphaseBulkDensityKernelFactory:: - createAndLaunch< parallelDevicePolicy<> >( flowSolver()->numFluidPhases(), + createAndLaunch< parallelDevicePolicy<> >( this->flowSolver()->numFluidPhases(), fluid, solid, subRegion ); } -template< typename FLOW_SOLVER > -void MultiphasePoromechanics< FLOW_SOLVER >::averageMeanTotalStressIncrement( DomainPartition & domain ) -{ - this->template forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const &, - MeshLevel & mesh, - arrayView1d< string const > const & regionNames ) - { - mesh.getElemManager().forElementSubRegions< CellElementSubRegion >( regionNames, [&]( localIndex const, - auto & subRegion ) - { - // get the solid model (to access stress increment) - string const solidName = subRegion.template getReference< string >( viewKeyStruct::porousMaterialNamesString() ); - CoupledSolidBase & solid = this->template getConstitutiveModel< CoupledSolidBase >( subRegion, solidName ); - - arrayView2d< real64 const > const meanTotalStressIncrement_k = solid.getMeanTotalStressIncrement_k(); - arrayView1d< real64 > const averageMeanTotalStressIncrement_k = solid.getAverageMeanTotalStressIncrement_k(); - - finiteElement::FiniteElementBase & subRegionFE = - subRegion.template getReference< finiteElement::FiniteElementBase >( solidMechanicsSolver()->getDiscretizationName() ); - - // determine the finite element type - finiteElement::FiniteElementDispatchHandler< BASE_FE_TYPES >:: - dispatch3D( subRegionFE, [&] ( auto const finiteElement ) - { - using FE_TYPE = decltype( finiteElement ); - - // call the factory and launch the kernel - AverageOverQuadraturePoints1DKernelFactory:: - createAndLaunch< CellElementSubRegion, - FE_TYPE, - parallelDevicePolicy<> >( mesh.getNodeManager(), - mesh.getEdgeManager(), - mesh.getFaceManager(), - subRegion, - finiteElement, - meanTotalStressIncrement_k, - averageMeanTotalStressIncrement_k ); - } ); - } ); - } ); -} - template class MultiphasePoromechanics< CompositionalMultiphaseBase >; template class MultiphasePoromechanics< CompositionalMultiphaseReservoirAndWells< CompositionalMultiphaseBase > >; diff --git a/src/coreComponents/physicsSolvers/multiphysics/MultiphasePoromechanics.hpp b/src/coreComponents/physicsSolvers/multiphysics/MultiphasePoromechanics.hpp index 1aad27cab3e..67d14459e8f 100644 --- a/src/coreComponents/physicsSolvers/multiphysics/MultiphasePoromechanics.hpp +++ b/src/coreComponents/physicsSolvers/multiphysics/MultiphasePoromechanics.hpp @@ -19,10 +19,8 @@ #ifndef GEOS_PHYSICSSOLVERS_MULTIPHYSICS_MULTIPHASEPOROMECHANICS_HPP_ #define GEOS_PHYSICSSOLVERS_MULTIPHYSICS_MULTIPHASEPOROMECHANICS_HPP_ -#include "physicsSolvers/multiphysics/CoupledSolver.hpp" -#include "constitutive/solid/CoupledSolidBase.hpp" -#include "physicsSolvers/fluidFlow/CompositionalMultiphaseBase.hpp" #include "physicsSolvers/multiphysics/PoromechanicsSolver.hpp" +#include "physicsSolvers/fluidFlow/CompositionalMultiphaseBase.hpp" namespace geos @@ -76,24 +74,6 @@ class MultiphasePoromechanics : public PoromechanicsSolver< FLOW_SOLVER > */ string getCatalogName() const override { return catalogName(); } - /** - * @brief accessor for the pointer to the solid mechanics solver - * @return a pointer to the solid mechanics solver - */ - SolidMechanicsLagrangianFEM * solidMechanicsSolver() const - { - return std::get< toUnderlying( Base::SolverType::SolidMechanics ) >( m_solvers ); - } - - /** - * @brief accessor for the pointer to the flow solver - * @return a pointer to the flow solver - */ - FLOW_SOLVER * flowSolver() const - { - return std::get< toUnderlying( Base::SolverType::Flow ) >( m_solvers ); - } - /** * @defgroup Solver Interface Functions * @@ -108,13 +88,6 @@ class MultiphasePoromechanics : public PoromechanicsSolver< FLOW_SOLVER > virtual void setupCoupling( DomainPartition const & domain, DofManager & dofManager ) const override; - virtual void setupDofs( DomainPartition const & domain, - DofManager & dofManager ) const override; - - virtual void implicitStepSetup( real64 const & time_n, - real64 const & dt, - DomainPartition & domain ) override; - virtual void assembleSystem( real64 const time, real64 const dt, DomainPartition & domain, @@ -132,15 +105,6 @@ class MultiphasePoromechanics : public PoromechanicsSolver< FLOW_SOLVER > */ void updateStabilizationParameters( DomainPartition & domain ) const; - /* - * @brief Utility function to set the stress initialization flag - * @param[in] performStressInitialization true if the solver has to initialize stress, false otherwise - */ - void setStressInitialization( integer const performStressInitialization ) - { m_performStressInitialization = performStressInitialization; } - - virtual void mapSolutionBetweenSolvers( DomainPartition & domain, integer const solverType ) override final; - protected: virtual void initializePostInitialConditionsPreSubGroups() override; @@ -149,9 +113,6 @@ class MultiphasePoromechanics : public PoromechanicsSolver< FLOW_SOLVER > struct viewKeyStruct : Base::viewKeyStruct { - /// Names of the porous materials - constexpr static char const * porousMaterialNamesString() { return "porousMaterialNames"; } - /// Type of stabilization used in the simulation constexpr static char const * stabilizationTypeString() { return "stabilizationType"; } @@ -160,13 +121,6 @@ class MultiphasePoromechanics : public PoromechanicsSolver< FLOW_SOLVER > /// Multiplier on stabilization constexpr static char const * stabilizationMultiplierString() { return "stabilizationMultiplier"; } - - /// Flag to determine whether or not this is aa thermal simulation - constexpr static char const * isThermalString() { return "isThermal"; } - - /// Flag to indicate that the solver is going to perform stress initialization - constexpr static char const * performStressInitializationString() { return "performStressInitialization"; } - }; private: @@ -175,13 +129,7 @@ class MultiphasePoromechanics : public PoromechanicsSolver< FLOW_SOLVER > * @brief Helper function to recompute the bulk density * @param[in] subRegion the element subRegion */ - void updateBulkDensity( ElementSubRegionBase & subRegion ); - - /** - * @brief Helper function to average the mean total stress increment over quadrature points - * @param[in] domain the domain partition - */ - void averageMeanTotalStressIncrement( DomainPartition & domain ); + virtual void updateBulkDensity( ElementSubRegionBase & subRegion ) override; template< typename CONSTITUTIVE_BASE, typename KERNEL_WRAPPER, @@ -204,12 +152,6 @@ class MultiphasePoromechanics : public PoromechanicsSolver< FLOW_SOLVER > /// Multiplier on stabilization constant real64 m_stabilizationMultiplier; - /// flag to determine whether or not this is a thermal simulation - integer m_isThermal; - - /// Flag to indicate that the solver is going to perform stress initialization - integer m_performStressInitialization; - }; template< typename FLOW_SOLVER > @@ -247,7 +189,7 @@ real64 MultiphasePoromechanics< FLOW_SOLVER >::assemblyLaunch( MeshLevel & mesh, CONSTITUTIVE_BASE, CellElementSubRegion >( mesh, regionNames, - solidMechanicsSolver()->getDiscretizationName(), + this->solidMechanicsSolver()->getDiscretizationName(), materialNamesString, kernelWrapper ); } diff --git a/src/coreComponents/physicsSolvers/multiphysics/PoromechanicsSolver.hpp b/src/coreComponents/physicsSolvers/multiphysics/PoromechanicsSolver.hpp index 9aa70c5f1a7..94964512394 100644 --- a/src/coreComponents/physicsSolvers/multiphysics/PoromechanicsSolver.hpp +++ b/src/coreComponents/physicsSolvers/multiphysics/PoromechanicsSolver.hpp @@ -21,8 +21,11 @@ #define GEOS_PHYSICSSOLVERS_MULTIPHYSICS_POROMECHANICSSOLVER_HPP_ #include "physicsSolvers/multiphysics/CoupledSolver.hpp" +#include "physicsSolvers/multiphysics/PoromechanicsFields.hpp" #include "physicsSolvers/solidMechanics/SolidMechanicsLagrangianFEM.hpp" +#include "constitutive/solid/CoupledSolidBase.hpp" #include "constitutive/solid/PorousSolid.hpp" +#include "mesh/utilities/AverageOverQuadraturePointsKernel.hpp" #include "codingUtilities/Utilities.hpp" namespace geos @@ -58,8 +61,147 @@ class PoromechanicsSolver : public CoupledSolver< FLOW_SOLVER, */ PoromechanicsSolver( const string & name, dataRepository::Group * const parent ) - : Base( name, parent ) - {} + : Base( name, parent ), + m_isThermal( 0 ) + { + this->registerWrapper( viewKeyStruct::isThermalString(), &m_isThermal ). + setApplyDefaultValue( 0 ). + setInputFlag( dataRepository::InputFlags::OPTIONAL ). + setDescription( "Flag indicating whether the problem is thermal or not. Set isThermal=\"1\" to enable the thermal coupling" ); + + this->registerWrapper( viewKeyStruct::performStressInitializationString(), &m_performStressInitialization ). + setApplyDefaultValue( false ). + setInputFlag( dataRepository::InputFlags::FALSE ). + setDescription( "Flag to indicate that the solver is going to perform stress initialization" ); + } + + virtual void initializePreSubGroups() override + { + Base::initializePreSubGroups(); + + DomainPartition & domain = this->template getGroupByPath< DomainPartition >( "/Problem/domain" ); + + this->template forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&] ( string const &, + MeshLevel & mesh, + arrayView1d< string const > const & regionNames ) + { + ElementRegionManager & elementRegionManager = mesh.getElemManager(); + elementRegionManager.forElementSubRegions< ElementSubRegionBase >( regionNames, + [&]( localIndex const, + ElementSubRegionBase & subRegion ) + { + string & porousName = subRegion.getReference< string >( viewKeyStruct::porousMaterialNamesString() ); + porousName = this->template getConstitutiveName< constitutive::CoupledSolidBase >( subRegion ); + GEOS_THROW_IF( porousName.empty(), + GEOS_FMT( "{} {} : Solid model not found on subregion {}", + this->getCatalogName(), this->getDataContext().toString(), subRegion.getName() ), + InputError ); + + if( subRegion.hasField< fields::poromechanics::bulkDensity >() ) + { + // get the solid model to know the number of quadrature points and resize the bulk density + constitutive::CoupledSolidBase const & solid = this->template getConstitutiveModel< constitutive::CoupledSolidBase >( subRegion, porousName ); + subRegion.getField< fields::poromechanics::bulkDensity >().resizeDimension< 1 >( solid.getDensity().size( 1 ) ); + } + } ); + } ); + } + + virtual void registerDataOnMesh( dataRepository::Group & meshBodies ) override + { + SolverBase::registerDataOnMesh( meshBodies ); + + if( this->getNonlinearSolverParameters().m_couplingType == NonlinearSolverParameters::CouplingType::Sequential ) + { + // to let the solid mechanics solver that there is a pressure and temperature RHS in the mechanics solve + solidMechanicsSolver()->enableFixedStressPoromechanicsUpdate(); + // to let the flow solver that saving pressure_k and temperature_k is necessary (for the fixed-stress porosity terms) + flowSolver()->enableFixedStressPoromechanicsUpdate(); + } + + SolverBase::forDiscretizationOnMeshTargets( meshBodies, [&] ( string const &, + MeshLevel & mesh, + arrayView1d< string const > const & regionNames ) + { + ElementRegionManager & elemManager = mesh.getElemManager(); + + elemManager.forElementSubRegions< ElementSubRegionBase >( regionNames, + [&]( localIndex const, + ElementSubRegionBase & subRegion ) + { + subRegion.registerWrapper< string >( viewKeyStruct::porousMaterialNamesString() ). + setPlotLevel( dataRepository::PlotLevel::NOPLOT ). + setRestartFlags( dataRepository::RestartFlags::NO_WRITE ). + setSizedFromParent( 0 ); + + if( this->getNonlinearSolverParameters().m_couplingType == NonlinearSolverParameters::CouplingType::Sequential ) + { + // register the bulk density for use in the solid mechanics solver + // ideally we would resize it here as well, but the solid model name is not available yet (see below) + subRegion.registerField< fields::poromechanics::bulkDensity >( this->getName() ); + } + } ); + } ); + } + + virtual void implicitStepSetup( real64 const & time_n, + real64 const & dt, + DomainPartition & domain ) override + { + flowSolver()->keepFlowVariablesConstantDuringInitStep( m_performStressInitialization ); + Base::implicitStepSetup( time_n, dt, domain ); + } + + virtual void setupDofs( DomainPartition const & domain, + DofManager & dofManager ) const override + { + // note that the order of operations matters a lot here (for instance for the MGR labels) + // we must set up dofs for solid mechanics first, and then for flow + // that's the reason why this function is here and not in CoupledSolvers.hpp + solidMechanicsSolver()->setupDofs( domain, dofManager ); + flowSolver()->setupDofs( domain, dofManager ); + + this->setupCoupling( domain, dofManager ); + } + + /** + * @brief accessor for the pointer to the solid mechanics solver + * @return a pointer to the solid mechanics solver + */ + SolidMechanicsLagrangianFEM * solidMechanicsSolver() const + { + return std::get< toUnderlying( SolverType::SolidMechanics ) >( m_solvers ); + } + + /** + * @brief accessor for the pointer to the flow solver + * @return a pointer to the flow solver + */ + FLOW_SOLVER * flowSolver() const + { + return std::get< toUnderlying( SolverType::Flow ) >( m_solvers ); + } + + /* + * @brief Utility function to set the stress initialization flag + * @param[in] performStressInitialization true if the solver has to initialize stress, false otherwise + */ + void setStressInitialization( integer const performStressInitialization ) + { + m_performStressInitialization = performStressInitialization; + } + + struct viewKeyStruct : Base::viewKeyStruct + { + /// Names of the porous materials + constexpr static char const * porousMaterialNamesString() { return "porousMaterialNames"; } + + /// Flag to indicate that the simulation is thermal + constexpr static char const * isThermalString() { return "isThermal"; } + + /// Flag to indicate that the solver is going to perform stress initialization + constexpr static char const * performStressInitializationString() { return "performStressInitialization"; } + }; protected: @@ -76,7 +218,7 @@ class PoromechanicsSolver : public CoupledSolver< FLOW_SOLVER, auto & subRegion ) { // get the solid model (to access stress increment) string const solidName = subRegion.template getReference< string >( "porousMaterialNames" ); - geos::constitutive::CoupledSolidBase & solid = SolverBase::getConstitutiveModel< geos::constitutive::CoupledSolidBase >( + constitutive::CoupledSolidBase & solid = SolverBase::getConstitutiveModel< constitutive::CoupledSolidBase >( subRegion, solidName ); arrayView1d< const real64 > const & averageMeanTotalStressIncrement_k = solid.getAverageMeanTotalStressIncrement_k(); @@ -99,9 +241,9 @@ class PoromechanicsSolver : public CoupledSolver< FLOW_SOLVER, auto & subRegion ) { // get the solid model (to access stress increment) string const solidName = subRegion.template getReference< string >( "porousMaterialNames" ); - geos::constitutive::CoupledSolidBase & solid = SolverBase::getConstitutiveModel< geos::constitutive::CoupledSolidBase >( + constitutive::CoupledSolidBase & solid = SolverBase::getConstitutiveModel< constitutive::CoupledSolidBase >( subRegion, solidName ); - auto & porosityModel = dynamic_cast< geos::constitutive::BiotPorosity const & >( solid.getBasePorosityModel()); + auto & porosityModel = dynamic_cast< constitutive::BiotPorosity const & >( solid.getBasePorosityModel()); arrayView1d< real64 > const & averageMeanTotalStressIncrement_k = solid.getAverageMeanTotalStressIncrement_k(); for( localIndex k = 0; k < localIndex( averageMeanTotalStressIncrement_k.size()); k++ ) { @@ -184,6 +326,52 @@ class PoromechanicsSolver : public CoupledSolver< FLOW_SOLVER, virtual void mapSolutionBetweenSolvers( DomainPartition & domain, integer const solverType ) override { + GEOS_MARK_FUNCTION; + + /// After the flow solver + if( solverType == static_cast< integer >( SolverType::Flow ) ) + { + // save pressure and temperature at the end of this iteration + flowSolver()->saveIterationState( domain ); + + this->template forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const &, + MeshLevel & mesh, + arrayView1d< string const > const & regionNames ) + { + + mesh.getElemManager().forElementSubRegions< CellElementSubRegion >( regionNames, [&]( localIndex const, + auto & subRegion ) + { + // update the bulk density + // TODO: ideally, we would not recompute the bulk density, but a more general "rhs" containing the body force and the + // pressure/temperature terms + updateBulkDensity( subRegion ); + } ); + } ); + } + + /// After the solid mechanics solver + if( solverType == static_cast< integer >( SolverType::SolidMechanics ) ) + { + // compute the average of the mean total stress increment over quadrature points + averageMeanTotalStressIncrement( domain ); + + this->template forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const &, + MeshLevel & mesh, + arrayView1d< string const > const & regionNames ) + { + + mesh.getElemManager().forElementSubRegions< CellElementSubRegion >( regionNames, [&]( localIndex const, + auto & subRegion ) + { + // update the porosity after a change in displacement (after mechanics solve) + // or a change in pressure/temperature (after a flow solve) + flowSolver()->updatePorosityAndPermeability( subRegion ); + } ); + } ); + } + + // needed to perform nonlinear acceleration if( solverType == static_cast< integer >( SolverType::SolidMechanics ) && this->getNonlinearSolverParameters().m_nonlinearAccelerationType== NonlinearSolverParameters::NonlinearAccelerationType::Aitken ) { @@ -191,6 +379,53 @@ class PoromechanicsSolver : public CoupledSolver< FLOW_SOLVER, } } + /** + * @brief Helper function to average the mean total stress increment over quadrature points + * @param[in] domain the domain partition + */ + void averageMeanTotalStressIncrement( DomainPartition & domain ) + { + this->template forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const &, + MeshLevel & mesh, + arrayView1d< string const > const & regionNames ) + { + mesh.getElemManager().forElementSubRegions< CellElementSubRegion >( regionNames, [&]( localIndex const, + auto & subRegion ) + { + // get the solid model (to access stress increment) + string const solidName = subRegion.template getReference< string >( viewKeyStruct::porousMaterialNamesString() ); + constitutive::CoupledSolidBase & solid = this->template getConstitutiveModel< constitutive::CoupledSolidBase >( subRegion, solidName ); + + arrayView2d< real64 const > const meanTotalStressIncrement_k = solid.getMeanTotalStressIncrement_k(); + arrayView1d< real64 > const averageMeanTotalStressIncrement_k = solid.getAverageMeanTotalStressIncrement_k(); + + finiteElement::FiniteElementBase & subRegionFE = + subRegion.template getReference< finiteElement::FiniteElementBase >( solidMechanicsSolver()->getDiscretizationName() ); + + // determine the finite element type + finiteElement::FiniteElementDispatchHandler< BASE_FE_TYPES >:: + dispatch3D( subRegionFE, [&] ( auto const finiteElement ) + { + using FE_TYPE = decltype( finiteElement ); + + // call the factory and launch the kernel + AverageOverQuadraturePoints1DKernelFactory:: + createAndLaunch< CellElementSubRegion, + FE_TYPE, + parallelDevicePolicy<> >( mesh.getNodeManager(), + mesh.getEdgeManager(), + mesh.getFaceManager(), + subRegion, + finiteElement, + meanTotalStressIncrement_k, + averageMeanTotalStressIncrement_k ); + } ); + } ); + } ); + } + + virtual void updateBulkDensity( ElementSubRegionBase & subRegion ) = 0; + virtual void validateNonlinearAcceleration() override { if( MpiWrapper::commSize( MPI_COMM_GEOSX ) > 1 ) @@ -199,6 +434,12 @@ class PoromechanicsSolver : public CoupledSolver< FLOW_SOLVER, } } + /// Flag to determine whether or not this is a thermal simulation + integer m_isThermal; + + /// Flag to indicate that the solver is going to perform stress initialization + integer m_performStressInitialization; + /// Member variables needed for Nonlinear Acceleration ( Aitken ). Naming convention follows ( Jiang & Tchelepi, 2019 ) array1d< real64 > m_s0; // Accelerated averageMeanTotalStresIncrement @ outer iteration v ( two iterations ago ) array1d< real64 > m_s1; // Accelerated averageMeanTotalStresIncrement @ outer iteration v + 1 ( previous iteration ) diff --git a/src/coreComponents/physicsSolvers/multiphysics/SinglePhasePoromechanics.cpp b/src/coreComponents/physicsSolvers/multiphysics/SinglePhasePoromechanics.cpp index 8830fa394c2..4ab8faa8214 100644 --- a/src/coreComponents/physicsSolvers/multiphysics/SinglePhasePoromechanics.cpp +++ b/src/coreComponents/physicsSolvers/multiphysics/SinglePhasePoromechanics.cpp @@ -24,7 +24,6 @@ #include "constitutive/fluid/singlefluid/SingleFluidBase.hpp" #include "linearAlgebra/solvers/BlockPreconditioner.hpp" #include "linearAlgebra/solvers/SeparateComponentPreconditioner.hpp" -#include "mesh/utilities/AverageOverQuadraturePointsKernel.hpp" #include "physicsSolvers/fluidFlow/SinglePhaseBase.hpp" #include "physicsSolvers/multiphysics/SinglePhaseReservoirAndWells.hpp" #include "physicsSolvers/multiphysics/poromechanicsKernels/SinglePhasePoromechanics.hpp" @@ -73,19 +72,8 @@ catalogName() template< typename FLOW_SOLVER > SinglePhasePoromechanics< FLOW_SOLVER >::SinglePhasePoromechanics( const string & name, Group * const parent ) - : Base( name, parent ), - m_isThermal( 0 ) + : Base( name, parent ) { - this->registerWrapper( viewKeyStruct::isThermalString(), &m_isThermal ). - setApplyDefaultValue( 0 ). - setInputFlag( InputFlags::OPTIONAL ). - setDescription( "Flag indicating whether the problem is thermal or not. Set isThermal=\"1\" to enable the thermal coupling" ); - - this->registerWrapper( viewKeyStruct::performStressInitializationString(), &m_performStressInitialization ). - setApplyDefaultValue( false ). - setInputFlag( InputFlags::FALSE ). - setDescription( "Flag to indicate that the solver is going to perform stress initialization" ); - LinearSolverParameters & linearSolverParameters = this->m_linearSolverParameters.get(); linearSolverParameters.mgr.strategy = LinearSolverParameters::MGR::StrategyType::singlePhasePoromechanics; linearSolverParameters.mgr.separateComponents = true; @@ -98,7 +86,7 @@ void SinglePhasePoromechanics< FLOW_SOLVER >::postProcessInput() { Base::postProcessInput(); - GEOS_ERROR_IF( flowSolver()->catalogName() == "CompositionalMultiphaseReservoir" && + GEOS_ERROR_IF( this->flowSolver()->catalogName() == "SinglePhaseReservoir" && this->getNonlinearSolverParameters().couplingType() != NonlinearSolverParameters::CouplingType::Sequential, GEOS_FMT( "{}: {} solver is only designed to work for {} = {}", this->getName(), catalogName(), NonlinearSolverParameters::viewKeysStruct::couplingTypeString(), @@ -106,46 +94,6 @@ void SinglePhasePoromechanics< FLOW_SOLVER >::postProcessInput() )); } -template< typename FLOW_SOLVER > -void SinglePhasePoromechanics< FLOW_SOLVER >::registerDataOnMesh( Group & meshBodies ) -{ - SolverBase::registerDataOnMesh( meshBodies ); - - if( this->getNonlinearSolverParameters().m_couplingType == NonlinearSolverParameters::CouplingType::Sequential ) - { - // to let the solid mechanics solver that there is a pressure and temperature RHS in the mechanics solve - solidMechanicsSolver()->enableFixedStressPoromechanicsUpdate(); - // to let the flow solver that saving pressure_k and temperature_k is necessary (for the fixed-stress porosity terms) - flowSolver()->enableFixedStressPoromechanicsUpdate(); - } - - this->template forDiscretizationOnMeshTargets( meshBodies, [&] ( string const &, - MeshLevel & mesh, - arrayView1d< string const > const & regionNames ) - { - - ElementRegionManager & elemManager = mesh.getElemManager(); - - elemManager.forElementSubRegions< ElementSubRegionBase >( regionNames, - [&]( localIndex const, - ElementSubRegionBase & subRegion ) - { - subRegion.registerWrapper< string >( viewKeyStruct::porousMaterialNamesString() ). - setPlotLevel( PlotLevel::NOPLOT ). - setRestartFlags( RestartFlags::NO_WRITE ). - setSizedFromParent( 0 ); - - if( this->getNonlinearSolverParameters().m_couplingType == NonlinearSolverParameters::CouplingType::Sequential ) - { - // register the bulk density for use in the solid mechanics solver - // ideally we would resize it here as well, but the solid model name is not available yet (see below) - subRegion.registerField< fields::poromechanics::bulkDensity >( this->getName() ); - } - - } ); - } ); -} - template< typename FLOW_SOLVER > void SinglePhasePoromechanics< FLOW_SOLVER >::setupCoupling( DomainPartition const & GEOS_UNUSED_PARAM( domain ), DofManager & dofManager ) const @@ -155,63 +103,6 @@ void SinglePhasePoromechanics< FLOW_SOLVER >::setupCoupling( DomainPartition con DofManager::Connector::Elem ); } -template< typename FLOW_SOLVER > -void SinglePhasePoromechanics< FLOW_SOLVER >::setupDofs( DomainPartition const & domain, - DofManager & dofManager ) const -{ - // note that the order of operations matters a lot here (for instance for the MGR labels) - // we must set up dofs for solid mechanics first, and then for flow - // that's the reason why this function is here and not in CoupledSolvers.hpp - solidMechanicsSolver()->setupDofs( domain, dofManager ); - flowSolver()->setupDofs( domain, dofManager ); - - setupCoupling( domain, dofManager ); -} - -template< typename FLOW_SOLVER > -void SinglePhasePoromechanics< FLOW_SOLVER >::initializePreSubGroups() -{ - SolverBase::initializePreSubGroups(); - - DomainPartition & domain = this->template getGroupByPath< DomainPartition >( "/Problem/domain" ); - - this->template forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&] ( string const &, - MeshLevel & mesh, - arrayView1d< string const > const & regionNames ) - { - ElementRegionManager & elementRegionManager = mesh.getElemManager(); - elementRegionManager.forElementSubRegions< ElementSubRegionBase >( regionNames, - [&]( localIndex const, - ElementSubRegionBase & subRegion ) - { - string & porousName = subRegion.getReference< string >( viewKeyStruct::porousMaterialNamesString() ); - porousName = this->template getConstitutiveName< CoupledSolidBase >( subRegion ); - GEOS_THROW_IF( porousName.empty(), - GEOS_FMT( "{} {} : Solid model not found on subregion {}", - getCatalogName(), this->getDataContext().toString(), subRegion.getName() ), - InputError ); - - if( subRegion.hasField< fields::poromechanics::bulkDensity >() ) - { - // get the solid model to know the number of quadrature points and resize the bulk density - CoupledSolidBase const & solid = this->template getConstitutiveModel< CoupledSolidBase >( subRegion, porousName ); - subRegion.getField< fields::poromechanics::bulkDensity >().resizeDimension< 1 >( solid.getDensity().size( 1 ) ); - } - - } ); - } ); - -} - -template< typename FLOW_SOLVER > -void SinglePhasePoromechanics< FLOW_SOLVER >::implicitStepSetup( real64 const & time_n, - real64 const & dt, - DomainPartition & domain ) -{ - flowSolver()->keepFlowVariablesConstantDuringInitStep( m_performStressInitialization ); - Base::implicitStepSetup( time_n, dt, domain ); -} - template< typename FLOW_SOLVER > void SinglePhasePoromechanics< FLOW_SOLVER >::setupSystem( DomainPartition & domain, DofManager & dofManager, @@ -237,35 +128,35 @@ void SinglePhasePoromechanics< FLOW_SOLVER >::setupSystem( DomainPartition & dom template< typename FLOW_SOLVER > void SinglePhasePoromechanics< FLOW_SOLVER >::initializePostInitialConditionsPreSubGroups() { - SolverBase::initializePostInitialConditionsPreSubGroups(); + Base::initializePostInitialConditionsPreSubGroups(); arrayView1d< string const > const & poromechanicsTargetRegionNames = this->template getReference< array1d< string > >( SolverBase::viewKeyStruct::targetRegionsString() ); arrayView1d< string const > const & flowTargetRegionNames = - flowSolver()->template getReference< array1d< string > >( SolverBase::viewKeyStruct::targetRegionsString() ); + this->flowSolver()->template getReference< array1d< string > >( SolverBase::viewKeyStruct::targetRegionsString() ); for( integer i = 0; i < poromechanicsTargetRegionNames.size(); ++i ) { GEOS_THROW_IF( std::find( flowTargetRegionNames.begin(), flowTargetRegionNames.end(), poromechanicsTargetRegionNames[i] ) == flowTargetRegionNames.end(), GEOS_FMT( "{} {}: region `{}` must be a target region of `{}`", - getCatalogName(), this->getDataContext(), poromechanicsTargetRegionNames[i], flowSolver()->getDataContext() ), + getCatalogName(), this->getDataContext(), poromechanicsTargetRegionNames[i], this->flowSolver()->getDataContext() ), InputError ); } - integer & isFlowThermal = flowSolver()->isThermal(); - GEOS_LOG_RANK_0_IF( m_isThermal && !isFlowThermal, + integer & isFlowThermal = this->flowSolver()->isThermal(); + GEOS_LOG_RANK_0_IF( this->m_isThermal && !isFlowThermal, GEOS_FMT( "{} {}: The attribute `{}` of the flow solver `{}` is set to 1 since the poromechanics solver is thermal", getCatalogName(), this->getName(), - FlowSolverBase::viewKeyStruct::isThermalString(), flowSolver()->getDataContext() ) ); - isFlowThermal = m_isThermal; + FlowSolverBase::viewKeyStruct::isThermalString(), this->flowSolver()->getDataContext() ) ); + isFlowThermal = this->m_isThermal; - if( m_isThermal ) + if( this->m_isThermal ) { this->m_linearSolverParameters.get().mgr.strategy = LinearSolverParameters::MGR::StrategyType::thermalSinglePhasePoromechanics; } else { - if( flowSolver()->getLinearSolverParameters().mgr.strategy == LinearSolverParameters::MGR::StrategyType::singlePhaseHybridFVM ) + if( this->flowSolver()->getLinearSolverParameters().mgr.strategy == LinearSolverParameters::MGR::StrategyType::singlePhaseHybridFVM ) { this->m_linearSolverParameters.get().mgr.strategy = LinearSolverParameters::MGR::StrategyType::hybridSinglePhasePoromechanics; } @@ -291,11 +182,11 @@ void SinglePhasePoromechanics< FLOW_SOLVER >::assembleSystem( real64 const time_ localRhs ); // Step 3: compute the fluxes (face-based contributions) - flowSolver()->assembleFluxTerms( dt, - domain, - dofManager, - localMatrix, - localRhs ); + this->flowSolver()->assembleFluxTerms( dt, + domain, + dofManager, + localMatrix, + localRhs ); } @@ -325,7 +216,7 @@ void SinglePhasePoromechanics< FLOW_SOLVER >::assembleElementBasedTerms( real64 string const flowDofKey = dofManager.getKey( SinglePhaseBase::viewKeyStruct::elemDofFieldString() ); - if( m_isThermal ) + if( this->m_isThermal ) { poromechanicsMaxForce = assemblyLaunch< constitutive::PorousSolid< ElasticIsotropic >, // TODO: change once there is a cmake solution @@ -357,9 +248,9 @@ void SinglePhasePoromechanics< FLOW_SOLVER >::assembleElementBasedTerms( real64 // step 2: apply mechanics solver on its target regions not included in the poromechanics solver target regions - solidMechanicsSolver()->forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&] ( string const &, - MeshLevel & mesh, - arrayView1d< string const > const & regionNames ) + this->solidMechanicsSolver()->forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&] ( string const &, + MeshLevel & mesh, + arrayView1d< string const > const & regionNames ) { // collect the target region of the mechanics solver not included in the poromechanics target regions array1d< string > filteredRegionNames; @@ -390,7 +281,7 @@ void SinglePhasePoromechanics< FLOW_SOLVER >::assembleElementBasedTerms( real64 dt ); } ); - solidMechanicsSolver()->getMaxForce() = LvArray::math::max( mechanicsMaxForce, poromechanicsMaxForce ); + this->solidMechanicsSolver()->getMaxForce() = LvArray::math::max( mechanicsMaxForce, poromechanicsMaxForce ); } template< typename FLOW_SOLVER > @@ -402,12 +293,12 @@ void SinglePhasePoromechanics< FLOW_SOLVER >::createPreconditioner() SchurComplementOption::RowsumDiagonalProbing, BlockScalingOption::FrobeniusNorm ); - auto mechPrecond = LAInterface::createPreconditioner( solidMechanicsSolver()->getLinearSolverParameters() ); + auto mechPrecond = LAInterface::createPreconditioner( this->solidMechanicsSolver()->getLinearSolverParameters() ); precond->setupBlock( 0, { { solidMechanics::totalDisplacement::key(), { 3, true } } }, std::make_unique< SeparateComponentPreconditioner< LAInterface > >( 3, std::move( mechPrecond ) ) ); - auto flowPrecond = LAInterface::createPreconditioner( flowSolver()->getLinearSolverParameters() ); + auto flowPrecond = LAInterface::createPreconditioner( this->flowSolver()->getLinearSolverParameters() ); precond->setupBlock( 1, { { flow::pressure::key(), { 1, true } } }, std::move( flowPrecond ) ); @@ -435,67 +326,15 @@ void SinglePhasePoromechanics< FLOW_SOLVER >::updateState( DomainPartition & dom [&]( localIndex const, CellElementSubRegion & subRegion ) { - flowSolver()->updateFluidState( subRegion ); - if( m_isThermal ) + this->flowSolver()->updateFluidState( subRegion ); + if( this->m_isThermal ) { - flowSolver()->updateSolidInternalEnergyModel( subRegion ); + this->flowSolver()->updateSolidInternalEnergyModel( subRegion ); } } ); } ); } -template< typename FLOW_SOLVER > -void SinglePhasePoromechanics< FLOW_SOLVER >::mapSolutionBetweenSolvers( DomainPartition & domain, integer const solverType ) -{ - GEOS_MARK_FUNCTION; - - /// After the flow solver - if( solverType == static_cast< integer >( Base::SolverType::Flow ) ) - { - // save pressure and temperature at the end of this iteration - flowSolver()->saveIterationState( domain ); - - this->template forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const &, - MeshLevel & mesh, - arrayView1d< string const > const & regionNames ) - { - - mesh.getElemManager().forElementSubRegions< CellElementSubRegion >( regionNames, [&]( localIndex const, - auto & subRegion ) - { - // update the bulk density - // TODO: ideally, we would not recompute the bulk density, but a more general "rhs" containing the body force and the - // pressure/temperature terms - updateBulkDensity( subRegion ); - - } ); - } ); - } - - /// After the solid mechanics solver - if( solverType == static_cast< integer >( Base::SolverType::SolidMechanics ) ) - { - // compute the average of the mean total stress increment over quadrature points - averageMeanTotalStressIncrement( domain ); - - this->template forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const &, - MeshLevel & mesh, - arrayView1d< string const > const & regionNames ) - { - - mesh.getElemManager().forElementSubRegions< CellElementSubRegion >( regionNames, [&]( localIndex const, - auto & subRegion ) - { - // update the porosity after a change in displacement (after mechanics solve) - // or a change in pressure/temperature (after a flow solve) - flowSolver()->updatePorosityAndPermeability( subRegion ); - } ); - } ); - } - // call base method (needed to perform nonlinear acceleration) - Base::mapSolutionBetweenSolvers( domain, solverType ); -} - template< typename FLOW_SOLVER > void SinglePhasePoromechanics< FLOW_SOLVER >::updateBulkDensity( ElementSubRegionBase & subRegion ) { @@ -515,48 +354,6 @@ void SinglePhasePoromechanics< FLOW_SOLVER >::updateBulkDensity( ElementSubRegio subRegion ); } -template< typename FLOW_SOLVER > -void SinglePhasePoromechanics< FLOW_SOLVER >::averageMeanTotalStressIncrement( DomainPartition & domain ) -{ - this->template forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const &, - MeshLevel & mesh, - arrayView1d< string const > const & regionNames ) - { - mesh.getElemManager().forElementSubRegions< CellElementSubRegion >( regionNames, [&]( localIndex const, - auto & subRegion ) - { - // get the solid model (to access stress increment) - string const solidName = subRegion.template getReference< string >( viewKeyStruct::porousMaterialNamesString() ); - CoupledSolidBase & solid = this->template getConstitutiveModel< CoupledSolidBase >( subRegion, solidName ); - - arrayView2d< real64 const > const meanTotalStressIncrement_k = solid.getMeanTotalStressIncrement_k(); - arrayView1d< real64 > const averageMeanTotalStressIncrement_k = solid.getAverageMeanTotalStressIncrement_k(); - - finiteElement::FiniteElementBase & subRegionFE = - subRegion.template getReference< finiteElement::FiniteElementBase >( solidMechanicsSolver()->getDiscretizationName() ); - - // determine the finite element type - finiteElement::FiniteElementDispatchHandler< BASE_FE_TYPES >:: - dispatch3D( subRegionFE, [&] ( auto const finiteElement ) - { - using FE_TYPE = decltype( finiteElement ); - - // call the factory and launch the kernel - AverageOverQuadraturePoints1DKernelFactory:: - createAndLaunch< CellElementSubRegion, - FE_TYPE, - parallelDevicePolicy<> >( mesh.getNodeManager(), - mesh.getEdgeManager(), - mesh.getFaceManager(), - subRegion, - finiteElement, - meanTotalStressIncrement_k, - averageMeanTotalStressIncrement_k ); - } ); - } ); - } ); -} - template class SinglePhasePoromechanics< SinglePhaseBase >; template class SinglePhasePoromechanics< SinglePhaseReservoirAndWells< SinglePhaseBase > >; diff --git a/src/coreComponents/physicsSolvers/multiphysics/SinglePhasePoromechanics.hpp b/src/coreComponents/physicsSolvers/multiphysics/SinglePhasePoromechanics.hpp index 5a8bbad8593..52097c53b33 100644 --- a/src/coreComponents/physicsSolvers/multiphysics/SinglePhasePoromechanics.hpp +++ b/src/coreComponents/physicsSolvers/multiphysics/SinglePhasePoromechanics.hpp @@ -58,24 +58,6 @@ class SinglePhasePoromechanics : public PoromechanicsSolver< FLOW_SOLVER > */ string getCatalogName() const override { return catalogName(); } - /** - * @brief accessor for the pointer to the solid mechanics solver - * @return a pointer to the solid mechanics solver - */ - SolidMechanicsLagrangianFEM * solidMechanicsSolver() const - { - return std::get< toUnderlying( Base::SolverType::SolidMechanics ) >( m_solvers ); - } - - /** - * @brief accessor for the pointer to the flow solver - * @return a pointer to the flow solver - */ - FLOW_SOLVER * flowSolver() const - { - return std::get< toUnderlying( Base::SolverType::Flow ) >( m_solvers ); - } - /** * @defgroup Solver Interface Functions * @@ -85,18 +67,9 @@ class SinglePhasePoromechanics : public PoromechanicsSolver< FLOW_SOLVER > virtual void postProcessInput() override; - virtual void registerDataOnMesh( dataRepository::Group & MeshBodies ) override; - virtual void setupCoupling( DomainPartition const & domain, DofManager & dofManager ) const override; - virtual void setupDofs( DomainPartition const & domain, - DofManager & dofManager ) const override; - - virtual void implicitStepSetup( real64 const & time_n, - real64 const & dt, - DomainPartition & domain ) override; - virtual void setupSystem( DomainPartition & domain, DofManager & dofManager, CRSMatrix< real64, globalIndex > & localMatrix, @@ -113,43 +86,23 @@ class SinglePhasePoromechanics : public PoromechanicsSolver< FLOW_SOLVER > virtual void updateState( DomainPartition & domain ) override; - /* - * @brief Utility function to set the stress initialization flag - * @param[in] performStressInitialization true if the solver has to initialize stress, false otherwise - */ - void setStressInitialization( integer const performStressInitialization ) - { m_performStressInitialization = performStressInitialization; } - /**@}*/ - virtual void mapSolutionBetweenSolvers( DomainPartition & Domain, integer const idx ) override final; - struct viewKeyStruct : Base::viewKeyStruct { - /// Names of the porous materials - constexpr static char const * porousMaterialNamesString() { return "porousMaterialNames"; } - - /// Flag to indicate that the simulation is thermal - constexpr static char const * isThermalString() { return "isThermal"; } - - /// Flag to indicate that the solver is going to perform stress initialization - constexpr static char const * performStressInitializationString() { return "performStressInitialization"; } + // nothing yet here }; protected: virtual void initializePostInitialConditionsPreSubGroups() override; - virtual void initializePreSubGroups() override; - void assembleElementBasedTerms( real64 const time_n, real64 const dt, DomainPartition & domain, DofManager const & dofManager, CRSMatrixView< real64, globalIndex const > const & localMatrix, arrayView1d< real64 > const & localRhs ); - /// flag to determine whether or not this is a thermal simulation - integer m_isThermal; private: @@ -157,13 +110,7 @@ class SinglePhasePoromechanics : public PoromechanicsSolver< FLOW_SOLVER > * @brief Helper function to recompute the bulk density * @param[in] subRegion the element subRegion */ - void updateBulkDensity( ElementSubRegionBase & subRegion ); - - /** - * @brief Helper function to average the mean stress increment - * @param[in] domain the domain partition - */ - void averageMeanTotalStressIncrement( DomainPartition & domain ); + virtual void updateBulkDensity( ElementSubRegionBase & subRegion ) override; void createPreconditioner(); @@ -179,8 +126,6 @@ class SinglePhasePoromechanics : public PoromechanicsSolver< FLOW_SOLVER > real64 const dt, PARAMS && ... params ); - /// Flag to indicate that the solver is going to perform stress initialization - integer m_performStressInitialization; }; template< typename FLOW_SOLVER > @@ -218,7 +163,7 @@ real64 SinglePhasePoromechanics< FLOW_SOLVER >::assemblyLaunch( MeshLevel & mesh CONSTITUTIVE_BASE, CellElementSubRegion >( mesh, regionNames, - solidMechanicsSolver()->getDiscretizationName(), + this->solidMechanicsSolver()->getDiscretizationName(), materialNamesString, kernelWrapper ); } From 9fe92d2f7bb5ec093eff3a72bc5f969c45d7a7de Mon Sep 17 00:00:00 2001 From: Jian Huang <53012159+jhuang2601@users.noreply.github.com> Date: Thu, 11 Jan 2024 11:36:18 -0600 Subject: [PATCH 23/40] fix multiple tutorial problems (#2920) * fix Sneddon problems * remove a redundant fig for pureThermalDiffusion problem * fix broken link for Mandel problem --- .../faultMechanics/sneddon/Example.rst | 4 ++-- .../displacementJump_contactMechanics.hdf5 | Bin 28680 -> 28680 bytes .../displacementJump_embeddedFrac.hdf5 | Bin 48216 -> 21640 bytes .../sneddon/displacementJump_hydroFrac.hdf5 | Bin 28496 -> 28496 bytes .../faultMechanics/sneddon/sneddonFigure.py | 8 ++++---- .../poromechanics/mandel/Example.rst | 2 +- .../mandel/Mandel_Verification.png | Bin 433266 -> 0 bytes .../pureThermalDiffusion/Example.rst | 7 ------- .../radialThermalDiffusionResults.png | Bin 101670 -> 0 bytes 9 files changed, 7 insertions(+), 14 deletions(-) delete mode 100644 src/docs/sphinx/advancedExamples/validationStudies/poromechanics/mandel/Mandel_Verification.png delete mode 100644 src/docs/sphinx/advancedExamples/validationStudies/wellboreProblems/pureThermalDiffusion/radialThermalDiffusionResults.png diff --git a/src/docs/sphinx/advancedExamples/validationStudies/faultMechanics/sneddon/Example.rst b/src/docs/sphinx/advancedExamples/validationStudies/faultMechanics/sneddon/Example.rst index 9d2515ba0e5..6e555c74745 100644 --- a/src/docs/sphinx/advancedExamples/validationStudies/faultMechanics/sneddon/Example.rst +++ b/src/docs/sphinx/advancedExamples/validationStudies/faultMechanics/sneddon/Example.rst @@ -84,7 +84,7 @@ verbosity levels, target regions, and other solver-specific attributes. Additionally, we need to specify another solver of type, ``EmbeddedSurfaceGenerator``, which is used to discretize the fracture planes. -.. literalinclude:: ../../../../../../../inputFiles/efemFractureMechanics/Sneddon_embeddedFrac_base.xml +.. literalinclude:: ../../../../../../../inputFiles/efemFractureMechanics/Sneddon_embeddedFrac_verification.xml :language: xml :start-after: :end-before: @@ -234,7 +234,7 @@ In this example, a task is specified to output fracture aperture (normal opening - The test case with EmbeddedFractures solver: -.. literalinclude:: ../../../../../../../inputFiles/efemFractureMechanics/Sneddon_embeddedFrac_verification.xml +.. literalinclude:: ../../../../../../../inputFiles/efemFractureMechanics/Sneddon_embeddedFrac_base.xml :language: xml :start-after: :end-before: diff --git a/src/docs/sphinx/advancedExamples/validationStudies/faultMechanics/sneddon/displacementJump_contactMechanics.hdf5 b/src/docs/sphinx/advancedExamples/validationStudies/faultMechanics/sneddon/displacementJump_contactMechanics.hdf5 index 1646430aa8bc906a9eab7df759c0e5361e6c5c3f..5a5aa97bf3cbf98cc6ad0f904424f676a5785b0c 100644 GIT binary patch delta 3038 zcmZvedr(y88He|*5LR*#+)lWR?2TPT&}9K5>x%5bfUL;2rP&}A=N@?8qv~* zpsBn5w52$0G0jX6n|2v&MDk8ut+kZ24p_B@u_>Aum9YxxWeBFGFT_me%y$1c&o^hE z@AjVW^0to)?c;*GM{Ww}>z$F$m+^0&Jm7jL-krB3;Od?u)o55~M?;8N-u{%QPX%AP zLf#zc>H8D|p>1F5lcH2tsJ|`m48r%+>L4#SA(Pd5`s&e8@f1{=MEOW4EJ{^>gXZph zm;yv}2PhDtjFE>g?zbo}JsmOl+m!3KCuUM-$HKqXx##E0=_(pyl~Ja&G>Uxp*2T}B zdEPqr&lAJz%%n0LXT$gA2HH3;OWeOmB{8~B&rEo*yTCpD6a=sOVN zlu^zD#Z><=FL%cgy~Hp*DxqE^u;EMFME^c>U~kZSizSBmk)4*If{oTl2c5N>vtC1t z#HelBLKE=BDRbI?SVoJ>z)8NP^PIZpmP?I)IcXZ&Wy+{uY^nlHgC*3G9riUsh6$NZu*tm^!Ke_+t z90H49D3x_@0YoQ-4tpZIT+zS6%c0M%l3c%|(uL zBctL`sj+C%Mdu+Hl~Movhl`9~91eW?1F2z~cF`&%u<^&+E|SMtBWpjG8aFPws2>$< zwEf;imHDS5exON`jMR4BMc;yl4fB6o6xw86a6*(CwJ|lc9Ou}WwAGMKRkGm5W~nh$ zRzr!HGAaYT->jkgxEarT;7|=MOiHbM@UqmE9jl@5qc2_=IP`iA#iM{rwyGMsfPGwA zq^Y6b%#{MpxF}-o-`yE5a?RDaGFnX^LCfB$tZLpTv;AD?uA*CS{mOQyQrb_*y-JR6 zs7V<$6jw>VD{l5iAUdZ=&Sc)e`8A+Q{*%NKi(_q?gcNFO8UAy)RvRBWH_g7d&i;`eGVheE4=r zm(-}uDdN?6jt%>{Lh9U;{aA068up$7>cSKo=0E4t*}5O~ZXA>DBk!N`$O3Jm(m2wT zM@Os1&#c-ZHO3Qj=*jsgD=y5)5M(dN?1`|HtduuIqbt!UZ;!?>qdOWijDcvxCK=@; z(Qq*CMx!Iih`kvK_>%Z&)?zO8HzGpDVR;X?m{YHHXVXtQa;M@kMRtz$_t(ag8SP^OAfLM{mxNAj=#fYDUDB!hlKtzo3Ta$?3)f^BJ z7KT$q9iv%97sD-Ln9(C*hT#(to5BGR`yDB{^7-M5zxp=ep1vjWa23W%9MqoqL>KM&hbTQmo3^RJP zm|^&|h+W4M)WX4-)S`o-&|#3F(P4sN)*&*JC#XXfqgIC&Myn3Jj7}ZK7+xKMZ9G98 zER1m->KM~HbTLAB@iSt0@iRnT{Ofsw(fy9~uL%*ZH2=G;_3{R)##9!coFA?y$1(r= zanhGbb|7C8-6JK&$hv|f&*2BfjD zeHkS`@Xv#MC$&6^#%#`V?x-np%0HKq?N{+BcbuEJproG){^^+PKnPvKXtpxDH8q0H zpY%`ZWIKZB@w3X9J!iA< zHg4vWV{&H@GIN&-1~le|32wZR8!xzUhMy_8ms>8}!}`2K!Zm!Dmnqnx&5suXP>>%c zjA48JA$W^|@MZo|AskB!4q>t|XyMtfcJcVv!;^Jlv>+4i!k~V2VT8cus-jGO?kj4F zDBV4p5mt8PiTb}PZ3r!n7j7V}IE;JST-+pF=^rf)ix4JouRil|UJ#OZhDM|shKly5 j-1e(|85KLjcn+`ZJS2RA`Ma8gOW3!oT+rd>t|k8kSI?I# delta 3044 zcmZvee^6A{70352B8U=!rShXLzXXB(y=WqW(S=~a-4z#H&4Qw={O}cjfo%neF{?2n zC2at|HB#zWXxcGBJAp7Eh-aF$rWvFi2bwfRVv{v$M8?sTI>urq_N+GTyl&nf?{nwv z=iGbGdH3zTO&+1iBecFLuMO+%o|exNWHMQRe1>5LejkuJU9Uja{FdUd~hVU9QKsNFx!9 zaSCPU+&uc~^cNpa&6&5t_X}Sx->r3}g)-n|BQ42B$pL25!TE6#!{aI@E85s-Xt0xM z>S)=ac}Wt(yJtOZ!Z|h!i4L;fSyQ;}7s(Q%^LF{a%^%FwK_7*`;jIwT$$z6kD_?R>b%WP@}6}ct;$wOjB7t~(k09GM^)cQpKsi} zg$8SSdN;nOmiWngw{T{?Jm$vT71Wv@P@eRZR$`d?E9ft{!p5Qbl~l2#;_Asfoy53x zppqgHo}f^M-KnJe$8SA6wMj2E%Bm;;Ic$7fTSb~&ndWDDgT&bPsESHam!JqV9jd1L zxRRm>)cn1g8qmd2BPCm@;Lf!R4?~TTsK^UjDHo$`+)5KE#c^}ZjU7@WFIS}P5EA*_ zxaW+iiX$>4qny4X(m6O26@m6YiS#AzCMyC5`b2sM zZ5*}lb&+zI1kV1m)Y$iPky3G&`)%OXT{yYl_LoKa z9TJli${Ecft+Txs^IF0p$!zW&7pWRWNs2%>&*l*Xj&gq?QWOqwl>4$sv*Xsyy<8%V zq7Qiv=;f%(?~62ot_6|v2Sl36SYB~^w^Tj-OOYy2w?Gj%VHYU`;oSOQYAc;Uj_+CB zR(jpnQrbVOrm4cWPUgNKjrZhN(=uPYQqxsM{ogUdy@^UX$Wu@#FK1NJz;E6zxKSbP zqhNmpbzzi^hLJ6F+}_ZmER-5ynOkU&FL~wIJ5IXr@~XB*qtrMZ@1%EOWFz>+a+)5k z4EQ`$YM9>LOgX-?E4|g5Xm3a-7F?CSB2Q=;ZABX!uHSB;e|oQOjOvvd1JfmR1n1cB z~tQ9DGH^ddM))|9Ld~wOS&wZ#)@eb zir8rQUNN0qc;x!J4yoZvC?XLD*zmk!qxzp%ze!&4f5uQDHKLb|o=Gd6s5#cXeq8$5 z^;|YnjPGO9xr)rRr_ys~={Bj+*`H6oKS|$ZFJ8G)kUcGHUyiJG&&Yf9C|#(N59!g& z@aQqfc%a85Bg}xERd8Ywci~$2`7$XLBFeVHb zPSfG-?BFBmY4T_zROvdo!H7~uff3D&aw7&A4Mt2dT8)TR>Ev%3VP?3EXk?5T(arE0 zG0q6iK!{o=Psl(PBQpavj4?Ah7+y1m8Nn7zGZHLNX;`tKlwq@=nIT#*$Z%OO$!NDA zR;!cuSYT!hSuMy(aYj3z6l8J$+B^jrcf zN*NrItAROray4pkUtG^sGn z=u|WCg>!h>NO5>|!pe8fHd;8jXx{HM$uMYK${l)d+c- zi>gKz!>vXQV@!<>hF6VYMz99cj06o-&v3;wC}r3*Xl95S3^H6AOfuRvh+V=J)4en z`EQZ7W6zNmSJ-gda_Plw{@bj(-9&Q{zCxi)2whI+Q~kGU&%8|9jU1n$UQG8z|LtBj zkwN*WW5aYZjW!JWA0S-EQYaiDD-*%Lx}r16}d6GB65Ce66Fo}pJQYT(y0)F zDRMrW(YO7NLT+aQZG_RJP|D84(br!85+=cfk+AjE?$YhIy=oH#a3acz$ zLBBrbf2x!1e{l9{s3L7ibJ(~0=abLI`&oAUAN|jXGSeq#VOVVwzQTdkn}s)Uh3{*S zQ>Yh`QCAoxwBV=QuEH64g9k*`kC?_@S*u_#FS@ zCoxDbYKh4?5pgX&=MV2cnStlO6FP>`qI_XRUrcelAVgv5+I$W;u(m#??8l=kqsl)1 zkNCfUR}gO33!fs#9>s%|*z1Ll`bOtMwgmGt7%(E#&YY#5F?ic(KoI9g9 bgS|UjgsYgfs~$b8gK&6P32e+8yAuBm!x58n diff --git a/src/docs/sphinx/advancedExamples/validationStudies/faultMechanics/sneddon/displacementJump_embeddedFrac.hdf5 b/src/docs/sphinx/advancedExamples/validationStudies/faultMechanics/sneddon/displacementJump_embeddedFrac.hdf5 index dabc71790ba046322232ab620f3ce7aea98e6b83..6ca11781b7fd1868f13cc84ebc5d2c3e105b82ce 100644 GIT binary patch delta 5659 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

8;(qbE8Os!@VCr9C!m%0C)=U62J=qUkCUR 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 OuK_$>5&!QRf&Tz7sZxjl diff --git a/src/docs/sphinx/advancedExamples/validationStudies/faultMechanics/sneddon/displacementJump_hydroFrac.hdf5 b/src/docs/sphinx/advancedExamples/validationStudies/faultMechanics/sneddon/displacementJump_hydroFrac.hdf5 index ae790cd8e0d6f8496e68d2c817869eeefacca16a..37424fc7b84ad138db5d2d78eab3e955d2aaa13b 100644 GIT binary patch delta 2808 zcmZY9eNa?Y6aaAEV*{39Y}cq|DcQy0y|>}Vei;%WmmN%m3|T%DX&b>0TCs>NoiVbO zH8WzGWIH;3lm^%oN;2#)=E{^VI+TJr6(j*`UAD^OnaH%nH*nZU2Ki+X?=WHJ5-)E zFcmDSd_I~7%_@-(&P4iN?fu};mfYEM?-b4WYV~D9t(FwbT{M0)Lr0=%4I6U@A0%IT zI*VF18A$)SoORK)*~H))dv(vmbn?htie**6-Z2()#Yha%;JjoXPwB@2CA%;>xVwo*c50=#G=s z$=9tUGq`bo>-}CEY27`iptjIP-Z=75(&r5};;P--b!L-|d~s=v^Y+?EfA7a7myX#; zM@Bd^@h2O3?&8tHYk%0tl%pT7*q3T2L+`8V?FKu^uNs=#YqjHs+xu@h>}0{_fsOV` zJ8@4*%5HGk$-5EnE8ZqMN#pl^v8}~U{+is@+3Q_rC!b_3=|8Z+P6|(Ut{rHzlRWzU z5%o*4M9jO!$qznH7qZcX)94SG=nvm<${SfvH+J?jd80Gv#^=))X~Gxt#@=ibebWs3 z=JRPKX3@%o564PnBR#ZqX|$BD($fAsO)j;QmVPO1#ZuZDA8l10ZCy5PW%y#cH{RRf zZ{%L5)84!2EPQ&8JR6Pju#ijt$ive`he<<+%SDH+k`CX5M;^v{I-KY8PlVfugAC6h21O_@5Y-`u)};pilnA>K!wmh1qas{I9A|hF zaYBTG$3%6Mp|j4Q{)^rD>Wt7@mdxQ+>kNE~1Y?-2k?!tayUf66NRV47s*Maih$acz z5G@SPA?8bP8nKw6wcfxllpu4SsIFw_N34^e8L@%kO+>c@!-%a6olhC~4HBdliRyNS z0mKdo>JhsbYF$`T2@W83GxQ+#NpKr+kl{JRpakUvOUlstG?r9?-9+TW68`6FNd!xY zMjf0<>);RILpjh~f)C}~txfg{d?*Ko5yK39h@&}>Iv@MMFoHOd1NDeehK`lk2X^OK zX#``u4?Qcf4+7lAWQ}n5x42c<2LZ|-$38G@L^KJo8_~jW1TkNLtBA!6vsYsu1SnX5 zePHNAtP`LUv4LR((JjCjVk<*O6ZS!X+*0fV!yd#A0oo9|7;?{G9|Sm!*v+sJu}^@^ zh1ds%BZxr(nh`?`v)x!y0frI741I{B0;HBi8wzQP)BK?nZU8(_>h!5U>}#EudgGYf`f(zECnPjM z4@QsQGP$-M|4k9if^$#mQrgl7N+WI8k3n~f4w5Uh`Z@O4bxo1p1=b23cOA+qO2OEY z3_B{c&`^_f-!F>HAdFyUI{a9%l`DcZi%YpG2rM?EL>B9~ui@{-W_@9N&tr;bC&5u^ z=G@?^Eakf3Nab?xYa1*2L z@iJ^yMB5>|N~aHHy?)IZ+)Msn;BIhYH0lguJfMhbz*nWi9qy_!>+|DPJ)y`s1rc1K zF1CR4lp;C-+G-uQ0UoX{<=z5MwV8VedQeuuk!rJkGS=5yql%{rfvYiNovk&oE|lTl zRq#?xDfVInKXXA{Tgp8!COj6h-uV j2w;@Xl_Hu25scEAR79IV`@GIX=UEYrO=|kU4d(v=LqIZ6 delta 2820 zcmZY9e^Aq990zc|-{D}y6(e(O1>fQ6Txf3J?_-DrJnXm&^b|K?rUWNw3W7(>ybej* zX4V;9vGvgl>9oMR0yJYsoyeWZ@-m{!pvx?}LsoZ0StpbhdHwoqdwBk6`(wNB>v_J< z_j%r*&v&B}!svwH>lW+o4)iV(#S$xw=nGy|9n}bNad9e9WsQduDk=Wzf+uTWNYw-$ zLyl-If@i}5#x<8Ns_Fai$-#z}o6c|;h(7IqRTPc1h1s156 z#eWrE$*SIhE2Z@p+RVu~&kTL)GBG71@~G$MA2pM$hLZ`3SYl?+@uj+Vy#GSh%GG}r zs6O5Kz2wwV_3HJLAEfE%d&$)Yvik0)M_jE1jfW)q!oDT@W*Rc-ACHeedt!PqOF--cU&-}$O}%5Lw)PV{RP{?zFqR@v8mJ*Zn{Wqxa7aGf?k*^@Y*~N(dHL(o_fo?k{YgjQMh$@B{fej z{BmNOg+6_&u;=tm3#}>|&%QU$N_)O^C0{*YrS9~tpBD{U>7nmadY-#wr3cLe@$WB} zX==vW#M|3sdM5kVKdbyQ%{NpWOBj;r*lU-|5`LEH-{%hWG~MS`XnDt~TxXs_>(4)+ z{-|1^)Opf(<&Z+VzZ)0aT?&1<>rByi7Zuu&Hfm6Pt5Ey(5AtsOrqG!SXSSb8BJ|iB z@fAcOG<{QlYL`rC$)xA>Z5yF04xc?h?1XN(Tb)^5NvJ2}KIq;>=&W$^;l_pI@|afc92Rum^*y3{p_S#cJcz2go!0H5{XKo9V|LEi?W$TJCPoZdOeH2oRz}K zO5a`Zg@?A6s@aKG9l)?=F78 zXGnulLyBfdi9#X-oRT=KfZ-vNW|X6Lvm}14Kt19lM;GF>0)vP%9LF|GnmK+YJZ6Lx znjj>C{Fo#r5~x^d(xj02XZaD+2=pQvInE-Q3EV-na&&Bw#2fB}Pi19&wVR3vrsjAmR+iu}X}TUkO!4@HIpt zt-?rGK?O01DXSuXfNzp&m1Zu7#p@5?eb)J`XKRcwR1m%|b35KAL(6I`g*-p(MNE@n z1kuPbglLw*kb|Y*Xxo9Mkf9#2kYguei422?c8zFvrSjzT>~sz6c!M#`}Uu}*;s#3qhG#AXG05j`9;8!%D@ z?jZU&dJ)?dSVu8Zjv>Tu1v(J>INJ6|nt}PNe%Br<2=9VxgHC_fwNp*`NjEP>yJ?a} zf8*k6p%X_h-8NUYefUevs03phbP3NaJX;)U`5D^mI#8FU>K`WI&yASbevnIb!Y{D4 zv{(?pU#inrwS9;y{}40tJOpv(eeg|bn?Par#$uri0vjzTp^Z8)I@R#c#;nBrr0}N4 zFM_9h5j<+o5_W^jUM%?GynUNK)Dqs+NX*g>kR295hMLH?-=WiMBS$|MGjkS#INEtU z{OO4q4ZL%uPCdFyr|fj)9Z(|Dkq*14m~a12`Ut8P$S!lMZ)ye3M1L zCKBbPm^q(72v^t|UcfONGx{TF%XGqic(ANkco|$}79kxvQFg-lG7I?1)%SiKb5A}9 z<(5da+2yGi^xAR@hQ7bN7z+@@@8zIz77Mz5hx3OteYjVyotlnWq!=8HIz65l_w{4U zXd8GNb%~J^ksB0dHt677qgKz_CuX_?AzX~LUd(7MXrI)XS=+{pzKadH@a#?Q{{iX< BHS+)f diff --git a/src/docs/sphinx/advancedExamples/validationStudies/faultMechanics/sneddon/sneddonFigure.py b/src/docs/sphinx/advancedExamples/validationStudies/faultMechanics/sneddon/sneddonFigure.py index c16c015da0c..5d3db7e4687 100644 --- a/src/docs/sphinx/advancedExamples/validationStudies/faultMechanics/sneddon/sneddonFigure.py +++ b/src/docs/sphinx/advancedExamples/validationStudies/faultMechanics/sneddon/sneddonFigure.py @@ -96,13 +96,13 @@ def main(): loc_HydroFrac = x[0, :, 1] #-------- Extract info from XML - xmlFilePath = "../../../../../../../inputFiles/efemFractureMechanics/Sneddon_embeddedFrac_base.xml" + xmlFilePath = "../../../../../../../inputFiles/efemFractureMechanics/Sneddon_embeddedFrac" - mechanicalParameters = getMechanicalParametersFromXML(xmlFilePath) - appliedPressure = getFracturePressureFromXML(xmlFilePath) + mechanicalParameters = getMechanicalParametersFromXML(xmlFilePath+"_base.xml") + appliedPressure = getFracturePressureFromXML(xmlFilePath+"_base.xml") # Get length of the fracture - length, origin = getFractureLengthFromXML(xmlFilePath) + length, origin = getFractureLengthFromXML(xmlFilePath+"_verification.xml") # Initialize Sneddon's analytical solution sneddonAnalyticalSolution = Sneddon(mechanicalParameters, length, appliedPressure) diff --git a/src/docs/sphinx/advancedExamples/validationStudies/poromechanics/mandel/Example.rst b/src/docs/sphinx/advancedExamples/validationStudies/poromechanics/mandel/Example.rst index 313f9944f5a..ea1107077f7 100644 --- a/src/docs/sphinx/advancedExamples/validationStudies/poromechanics/mandel/Example.rst +++ b/src/docs/sphinx/advancedExamples/validationStudies/poromechanics/mandel/Example.rst @@ -265,7 +265,7 @@ The next figure shows the distribution of vertical displacement (:math:`u_z(x,z, The figure below compares the results from GEOS (marks) and the corresponding analytical solution (lines) for the pore pressure along the x-direction and vertical displacement along the z-direction. GEOS reliably captures the short-term Mandel-Cryer effect and shows excellent agreement with the analytical solution at various times. -.. plot:: docs/sphinx/advancedExamples/validationStudies/faultMechanics/mandel/mandelFigure.py +.. plot:: docs/sphinx/advancedExamples/validationStudies/poromechanics/mandel/mandelFigure.py diff --git a/src/docs/sphinx/advancedExamples/validationStudies/poromechanics/mandel/Mandel_Verification.png b/src/docs/sphinx/advancedExamples/validationStudies/poromechanics/mandel/Mandel_Verification.png deleted file mode 100644 index d9ffdb71df3dbcda341bb70f2832e238c68a9b52..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 433266 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⪚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

uOhF+e`R?J(K1Sx|eu;^P@NgdNv%h}* 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�zCgtGxZkjbT zKhjqAN)5Ssb#5iN3MXY(n-(x+Z47l}*%Sx_LiG_upXgVNjn%(s(5z*lPE>yoA8!&? z!!oE$-7Y)4hCx}mz&HADuR$eZjpyVMWTB^W~$T>*11g{Msk9XrJ<( zC*L}av!dQ%fOG(KK}=UJF272@7OpHmg+df!sBoTg@QTz8*Ps-j-ctnt32AdQd-ocM zub&ExlyNs`!zU4? zsLx{KmD^uiReaBLSKaywuw|Xh`5n8@U%S*^*x1;Zd*$bC+MTPzs1NTJwmb?}C&8Kp z6@@NDcI`1$ERHV++E?Ym=wf1S{)xm}|3(5y}ndeV<%@iqg;skMxZ;+NqUuO$mo|Gp^FH zt04x21-HBzLyfbZlQNN)0nW0gX84ezpr>ta|&~-tgg~;IT6l zhn*!?yOej+kt1(pnVz6mjEahK!Uyv08UHAW9Tfr;2aGK8vV^Paf^6Xr6vEGMSnb>( zFnXkWGoLfW2EFlJ9l1ZQ#pOT>AlkU)z37?bG<3J9ONkSAd>p}#7IfCk?O+4%%)_aTNC$sPnith zmc6}i9~a>e@9`fwVgXDJ%wdnqxIQlQb6+l!P)qKYH@{H+_WZ2*%zxZl=y47Fs|mxH z;d`8xkuuW^@e9!Tf4~pu6M23LZCf0AN#|vVAd3JJtiq}( z{kF5Zx+jnH;`LG__`*$k+NL0pr)O=oj=&0o`z#T8BWu^Kqu;r>sx)o?ny_LiXmu$- z93EWUef`PNP@lCiT#&Y|G~#Ux5j&=l`aH`Aie>z zwuMMg_a-hvkx8pLO_Vx;#edUKs1{S4KZnrR*_kDZuGP?1* zbUfeF>}-*W-!B*R#FOGqJudjI=_u}QsLCN0nV*&-&vYjydSA{^S~MPL{~i3!grtBQ zlfo%mUxXxQG_7wrk?Xwfl8fGZtrUEsm6gCH!qB)9g0G=LuuS$t`Y{&0=h{?M*bZZX zKm{PU+NrdEH>knZ0&=dZ>kJu_js=E?bMf?Fx3h~SdK3v3@@w1fvYyCw33)5CTu#53 z>E5|-hmi1)%d=>C)4XW*M+x;nkS@12@y57yA z^L-~x%lpq3u(2^d+*Zs?PQ{tZzv2e`&qSr?+73vvDOgNy9x(n4^`%NH-jQn_!&fii z-eu)kN1+?J@bGSraM8G&Gj2CdbHaKB-12Hs0G@_0P1@n~7 zkaNEV+%zp27Q9#9Lkno1yC>}Q+8>KmRdDGi;`^h|2z|Alh*i3yi?Pj_78uQ1J{3^wYD`HDhN3DAv_6C?bSC z1wuB7cNT5eRO|fQT#xujmARQ&Df(hes?mvMfWo6Msr+&JryLo?Nu1zRt72HtaE{W; z-=tEjA#x*ACgk`7z1u5K+@GBc?CLC$_4-P+E5o4T3O5)A-ZAE38;5N#4E2}iHF?7_PI7OH`G=5p%w_WK0_ zym|u9TL5%FASw_*`}{H1EIm%cbWhJM_8Unbv!EpPV-%I)GYDwOgA?t@?um;_7*UY5mW|Qj)+vp=b zWO@4{6}Nb{jsIftDxgwfi^*3m9wz>9HR1x4PuxGhthIMk_paHj>X~FJ_ih0;k+3pX z`T&;|MylKh@y&W!)8C~&T~GGZU!GaV38C1)t2b}Te*B2S*taIkM-{4nX3`OYAtizN zXkr!8n#~uU8Zzuxf4piEulR>HZXot(UIGFG$^M}^1kbEWhA|qlM1x(mD&)kijWr7I zF^#&oWgk!y$u=i(iEf8K!jr}(V=t_ItoF`|Oa_Gp08kW=tSL8w3YtAE%Rt1sX@h6j z>PhWiF28H@L&P>(?CAgYEa|QGUo4#O0WlcIo(5aYk2k&gQ1q@{&N*plYC%q1K1!8vd#y61 zz9IqV_WS6k_#{qh>BDHP*W(--7Pe{|jP;lEP_z0L@}+C1u}&=@M>RsUn9!CMZAjw* zFSdrPqTm-4Y-p1-poBQ1yx0Dk!6}O(!wRTosrphWS1N9DiAVCyB<-M&EPka~cX1`b zat+7#JDr9x$C$5N@jNn>hg<(h^oV5mjqd%K=iZln_`F6dM4WQu$)8R}R5EhlT3_#% zZo9GfmV|%MMxPs}2g4JYIl+{~@CksKj?u{$A-GV2u<;f|+(eVIdKPc-7U+m61k-K>D^i?rae4PT(lEpm z@xe&-nLa#%_DmE73@AQ3KmRtI%zZ+l31~;>M{G<@<&~6_9>_GJl>&Zk2Zbc$p!nS< za*UF0)6Qi3czYY8jbjhDxzNL~VJ5D;{KRh|`~V&h%;eNQwHXuXelB%vXIlJ2^&ggNg zC3_n*@5f!S8cgEVbUnT$B0{)*hJWe#PCCrLcOkzaTSk?XykCzA+H{urxKH&*om!EZ z8IgEV`R-=tT;G+>mz2A>x{5rgPpj<-TXXzb?s5NcB|U%hM8C4E*HacRZs;fesKK8q!m8Wb@UAF+VwHFvW`SdF5I~vRI*L)(!!P8Vcl zAuv(`IAx293tcUPr>w_=0QL7&a}9k;BdXUPzSWh_YfSA2XVK`E0bA+^T$_`Ax-g^S zwxJ}n7bX2A5+GwKQwSKFdQVW>j6r-o@k?WJp70}Q3iI_E`vY_5bbBvnx&Z89<)~Ui zhI`~?>W!%twN4)onU6SxmD%AU!VCd8$nPpK>hoY zZXronYt;E$TN{jpYl_gXI>FfTUI0}Hq4;_k3c_T@K+!&0W5YgsRU=}AId|0NW_}Wk zuPHZ~A!~bAu0lg-1aU1<2?-ks3t&K6=Q<}7AQD-g&TtQ@Qp$m#zye_us>9C$f`dz7 zy-!(UBBCT`>FdM8!@EcZ3SMzu$7!-=3*q~#duRjl=YKtAuffN%w{;l$0eaD2KA2?Us~1d76PBv64XO{PtXg9zh#L$QKw-_iS~Rkb;wl4Yj9n9tK!D7Qw#!{J7Oq%T_TbyBA*-9y{q-`+9M&yQBoz3M`qKF2rlL53u6cT_jou~S@V4!b0XmvV-Kd0KXeBa1%pf3^k3?4z8VA>UB~A{3pS~gZh5~*Cb5JpqpXNv1SE|5y zlZ~th35W%ELll-cDt5CY=dVE;n{>qlGaQyI4*?BHz^_K|7U|m3Kj8 ztT8(b)CGw?z*Pinz8nI@5uEh396-3-$F3#aI{0SMYZc?clVA)y@}D^639yNYii-z^ zg@L8tfcAiFe1x(PhKE(Rpkji8>qAwQ55Tnl7Smb5_7N5qMzl+q5`bX6Xc;|!0iM`- zq!bdR4Z3GSVdi6}Es}~Z;77xcM5H#jKKHS*uD%5&t03`!WRk@gI32myNB6=Tasa_0 z*y?;A+#oUbp~oj|ZdjOD+7FU2MoE7|N*Qh-%Z7xF$jIm+OD*9leDlM_hv<;EZ})ie zgn?XkP?Yx|vxrOhA=)3ZnU?%e(3nCHvj@C3d<@wp_C6`z-DK|`JUvjBJ-|g>k41&j zi(_(tMG)523xW@*$LY2u6KoV`_co-P9_?U;TLm{N<|_Fl({1$8vx>Knk}4^lLPTiU(9Xjw7uv%-!vwc|u80Lj{d?`jl>)w^rc{ywd<|^Zy0ol_BCTxI~b!r zwYw%h+%Dii@31l#DPmEZ#V73{6BNj=wX@UG)a!xADl2P|pJBQQ)=de?%?Vb*BuflL z;|YHYb@6Wl7xPD*9k{w^8JBq#)soVtUH058BO&E8}+{YPCTHv1u3?Wf( zp!z*WXb`Utm;f%e&4B7#Z?*s*Q&OUm!5bzP<*}_+8iHI{4{M+lr~yd63%bmxK4ive zTbB8x8X;Q=ImVe-dWE5spx%>yDd*nsf)$E|K}($ZGME{%fGR&9T4#HY(hYD5ka?8I z!x_md1J)gReL1paK=xn3kwqNuHJJ}fKI)Ayy2)zlqeMZ0FGC_#0jUC>i76jP$uPkjOi920+t;t1aQ6_gHT<_= zJSp9@;S@&tMyv7l?0F7&dI;5uP47-X)jUS~0H8v=^CL;+UL>}JgSOwt&Fur#0e{CN zyRKh3nr3`JcQ6ClW2%SLH)3?<@R1dK12`1Xv=Y~}pt!)AN|*6EPFmeap^Hw3}>AigJwe#5c0YThUF4xyT&$Z2rL#Z2B|5;K|4NmpU}QdAIin{!a@K zoaKe6w*evGnwXFBQG-FryabnWNk{g-Fx_pki#**mOvdiZl+!Ep2f2B)3f`qJE?w6? zr?&%ka)^~4DLZMKzt2PrA&4s3r0(CRF6td}wH^NH*06M=*6fpMU}ThE!`YNcyR61n z6}}ZK1&GvSk^dHu7bWXu?*N~{J`34c+1O6le|WRz)`03EtK_30yC*K4Lp(g$3j6S| zxv$XKvfQ;LbrCjgr|D)LyDL}fuB|xH6SWFL+F+F;scP6~T81<-plncQ)?(LDDFn`- zUs|fhfp$ZVxV3Qk4>wWw!PJJFX=rw!A~6HCgkp^CNgriRNW2>u53*PlC{y+d$Fpb8 za0Kt8Nyuol2P7>#koGetBZ%L~wv^L}vyO4kbsasJXwTdVE4+{kRDL97KP$wuIA>rf!!J;tp20k#LU zd=o!hN9}O_aJ7Y76r!qkVY>wG1)^iY&lVaU-T>|mXwW=9gis-ihbp;rEr1`1t{?CD zM})NCs~B5Y+(&)@qLC5x24XM_3%>~g0!s!uKnBJP6b4eF(QbO+QK^Gd#QpaY+B$^X zk$`Yqy!bn=R>J(@hY^n-@jpL9q_)of2X14j=y1(69pnkl|4W*jU0;%A9{4q^*mgc8pxte3egr|Fl4a z!Nwz7Zi_Gc?)KakN0qG6?#r-SddZ?aXfKC_{i0%1<9aF<+m6v+~%cK)y(dajX=n_~QBr3EvfmSC`l|fP|;kM@&5U97g$Pz^d!vdyT zj9SlTS=XpJn^{j$=*M8>W{H#}yxsjHeGCdunI& zqNAgoO9=_e=svq3x&!Ev9M`zLD_1jM^y6!6QjvBEy4%#{F3q*Mb7^l)Q?m9(>2A-u zJ7~KupcVmW1|pEw?6v|G;iJz+7iTo!yJ^SFD95z7?W{W8y?bXjGkRI2X?s3Q@g4nGJ#_lvU8Z8b%8WVz zB)$#Zu;BQ@r`s)Bqt<6~@A4*&k<)uxc+aN^Q}TDs?`t{Xxvf@co?Cx|nytOgV{J}a z5{s&t=AZZW?HF1R@SnG3V@b8ejrdzai=HNTv-uQ5iv_4pyQqxY^!yqV1U@5pQz zm#jis!tueDLH)(p*C`K&OExOdyK5?{9aKx7H8eCGUk|wH8FCrYW ze`UY$u8^dK2lyi6l7F+BbL|`#^Q^yr_z>4*M;couGZU)^B(oZdMvuQ;q%ln#WS2rVEhlewT7CLs zvj;O9{Z1B-+Fsmcw*T%nRR;8iZ_Z|By1SE3SM{>p|F&^@rE>CcdWBvcEO*(NsIjN} zfbvV%g-gEH%?%pnW~%Y^oeM=zrgd6qA{o!8-)!}>KCUfGzxA5*J_(*Y&9cubRlkaB z)|;}Mf|SToFa2Bhf1e_UKTA4#JCCw=BWNCKqjm z_wwJzsC7>mi}{f(`n`DCq}zYeO5B-`y9DvlhldF z&*oaB7L>_Qh2 zwk__%*Tc7M8)H-?+D+J{?iM~i*1xgN($OE29$n14T<@sQHjgc%gMBY-f?a&>&1JaK}1Mw(%?q~TeV$mZe1wD4_l#=RdCO@eKc z@8&Z}Y3LT^BVQ=uKS8lS(cWA@>bHw18+rD@_rCw6l?~?hlr2V{D@mvZq7RGEeH`5< zTwmZ>d)=1E!}@QDaa^U~^DRsrp7eK$kCsg{HFR}ODQys6ZQZWzgDq1N+8tjaW;QG3 z$Qn8ti*sBq4OUac1A0k({Sk?w=a0R5mEf5h2Mmwr$z9Qad#_0W}} zDM}S-JWO+?Z|08Ndey`@WB9;m@zmx@ej!{ldoe_kv>42w+5X7nu*X^lkPFySz%C}X z3wdhrNbD57vX7pgo-Xu%n{_s*Neh1X;I(ANF_5r7vMYI@rOc(I@QikBVG?tqv~lfo zDf#`!`}d^1Fgt%wZ0pC$uS#3PHmbcRD?QNZ3y7?B6{7sbm8_v$YK{Q-3^q)v?AyfwLJ#c62gYM7wkWdat zJh&$QZg#+JvnN=w&fZDo$Rlo-vh- z(;rH_Rq&Z|-6iW7D$kF2=D5>)^hjTW0|;ZyC;UUz-T_b(FVO9k#XgzVoB#Dcw?0xE zoo86gDeB(FwAyN4|2E0@n=xUZJ#d_wh3g1vPt2EWUAHNEUG}#uH@>c{3UWPLK23?Y ztX2d76kbVmtxy9zii&!OAqRXH@*3zNYu0qr)?Nh!%oqo=l+os|-#5bGVOqT`w53xPXv*Py{@ga|u5-PH3kzFDmxake{ihVJa2SHe~7 zXcZODZr*#>s_L;zW%`hrsl^q}sckxO8U1^z)Xgd$eUh^@Vn6>#p*aEZcjlXhZMf%C zp9qX(uS88m1n|1+Y-EWtRL3@EP95B+;D}d&U>nxKk^epzhAg!#I;022d zzQ;9O9~$^`%}8$_x6;pLR>iqA3;nwuMa<01>}PLp>6hr79ap$-ZMwRSOM-44*P5nX z)`G@@XLcoDvAcGmI$3}OxXFD?z3Dn$!TJDP49sn6nH#n2{F!CULB>R2w5{!i9~sp9 zm7enL#Q2FJFIsP(nijb3Orzq}^n3>T^(kH!`mlWf#B_N-yZa~>iXg9-Xkh@Vtsbl7 z&cGiIm{8Fe1*Zf0Q-XqIK_H=v?&%{`V!CvpGFB zsk-vVou2hW?{a;PTlqUa?7cW#9rNi9{}I6!Idj?I!tY)FZZ-FApzEbJ`EA5@cUx}i z44ocU*mk4G1sFwQoMtisf?4_;R*a*?#q z8u!Q|(eu9~|mRmr&fYfa3B>rVHyZRunsUEhgh_Ei>$ z#svv_7u1ff+Ir>r`OR{P-;a`!XpyKdC&Qv0W(IVt!^3_G|JXRV?hB`1aI3(#ux}-v zpX>&wc%qn=gN#Cp)UUb}*C@qyxqdo&HIs#H_kLZ){u+f&#`mPY?bvKcMv_ zjSz-WA_s!Z6d%!Tm_$2=htKy?dr^@DaY1-C@Z#vyr%3Ajm*&7N?q`q88Bz=(tfL^7 z0<@Dz0)7a)5x6cmBbjkRNj5$HAh&3@_pAS}fx63?_&R1^*R^5Apr{uZf6SFL_lnc5 zp`np7)6M}jz_3@pb5^1-=y}DLgOAl?6Lgjg9Fv=Wh7FW(MDc+a_;@y~abAw}ajb6oBSea$)I}#tF>(|Ev=T(+9i=lRQjjRm<}K zTFzwOAx613sy`>LIVH=bIr=fKcK1BaQs*Kpu<;;?G=z-%hMb=-wy}A^{H$`n1U1!h zQ^>ka_6|J@L9wMjI1aN1ImG%qr)1u+Mc?KmO-P8J1sh8kOB;sT&CfQ!;<^&iaN@xw znsXPQe%;}IBx+KkM##8&!~1j7Oqfs(_N6PF#NH1Is1yaIKH($FE@jRrm|^^zp0dwE z{b6371QDn}#zdAWLL`!D+b#|WszC5_NV9|jz$4S3yKU=jJ?Y&SgS4c$ze`_Xt3MF% zU{o0iiwV9$bRl7gv4KpPf}rb&dcFjgEJU~XFa(5z))*DvrgR<|l%z&*gfd_q3bM%z zOBLuM0-Utbmr+xoorkgWywP}BQ23F{HE_ICv5Al$%wiyz*7%KWWA5C?q9)p>0`Zv; z2v^d&-z~kLIWwSFK-OLAUI}d{ z(BRbwup)sdklw>@YYfVKwzjWG&P zMbkF~QG6T=&RZLwxxJ8oaM!oF@4mcwck$J|lnCroYvHDnu6F3f<#)!G`dJTX?|r)W z_Wf_AbgsvI<~NT4VcTVv>W&u=1}o${W!y|hqr^$;Eoq2um+pxvojv5@koE)BTXXpE zVakWEGBYoLg{Qz3VJUHMQ3Hbl$$JJd2?gjD*aeWFXZ8K90MO-LV$LE=*VCYSvbpG1 zL|iBU(ex%eOi)QTnWX)Yn;4MM@?8`J#F~9@9uwsZ{vFiYiuk+`KIr~_)C%tm*qvCs zP_Q(Ue+7pPc}rj~DHZp})iQ=i`+69Y3HS>X8Fwp*+C=jo7%htA@DfCwYq_cz4Q!NfpN zfZt7GhmfI7$W{2xljc-iBY#mrGqx-PJ-BDY|b2p;fV2E|@qMI!U4hbp6 z6CrU$Brokhh##AM0T92Aw(|swUhc36sTY9NyQn~bl%Wj6)21dQ7p{08U+kByeN|n4 z!uZPzwk7w-7fMy-vGh4XKR$i^ri%%LCtJ~PR_e0brp15~U-?MmlueqOY$ZckYUKE} z83zx_v2Z;E-$Bns$#&kG*9FU+`@(|DHnOB1f-?nE5dqUL7fjPrfOAF4IN9Pxu3k(< zWC3JE%D)LQ#7m2*|Bt}j=-mYd?}DerMRaEw<&wme-Vd1R_j^k`<686X2!D+08o3*6 zS)p#ik(1jGg5QYIPMf}pIci+<8vUo&myJGtYb%Ymbl?)b_i*#2Ou>-dr|=#{DZ_dh zQQ-2}eDpB49SVY+iCn~SQ3vzsU7+clu*zvFA~Bf?tw?qoJ_}C!iLk?otka1}I6 z?IbRu{67O}^2hslKKc3k6BZAl7epBeHFmUkMIkG~*+Kw25YI9+g?G`!1Eo*vfBhQ9 zNgs_U%040mLLlZ6QAJO zEYp+kS^c4>`fn2&#%gx_IJnWKDx1JT$AbMv;}jok5yd7U@f0k_OUK_0H=JQe1EWl0 zkcglT+E!<8LYw(MeR|Hrjv31uh>`&IHGC_VNtPmC{@87mTzV|53563HvRUZJh+;d* z2|FUN4)IQOr(!~^M(kz%(`trcXmqN|M*VnLc?RKC;=-c!#E^~g;UOPR5 z{RsIvH`%3W zeEPf3t`~^!%ju-&p&~*bQ=>vuGSdfiFMMh8y(EzX_GHZX>Y-#yRI88J=L>Qy1mo1| zTaf>Oj>?nljtbdw4^|m5(o`!dV&@>iAE7ZKv?IFF<9&0W5L+)XFg#%`%K?*^w^XZ; zCnzLDLP*h6<0as?{Vq!UVTkO8d>z9bMsAWQM0hl@(JaCPlXQAqi~vTkGVVp3#_CS= zgT&B}UgQphfer6(x&pd#U!Lb6#fP2UlO!S|umIZ&szAYkqrQ%c$PWo4A@$)te_>ES ztl=a!jc8Ez+5oCtgn)(u^3@3PhhZ~D1A7cp0C7wrwG>GlQ6O^ce^u`y-to^j$2yCN zhvLW&r#oom$gTu(?7Y3X>Sv8`q@kOlqX3|Qu&Lx>Cn7LLe>jg3P7F!P?{B`!%0en4 z*>C{WV*|?B(9jTa+J9n*gX`%IG5+<{FyXn>f;~$Hp!;Bgn-_Lz^e_OifBGHyVFU^< zQCb`45_Oe>g=H1M-*x<;(ZSHv;`Jb-A_=29eE2cw(h$gh#(=I4Ogeg#+)tPmQO1fO z-r86M`xKPnXQJ%5LQ>`Jm}T5fLs|2>ln-|vSx6m==Yc@!%*!3p5E{$DasW3EtWdPN zL!poeLfuL7q(Kj|0C47l42Xoe;i-2I4=bawC&kkpaUUBCsZr@JHkg_PT$$T&kiom~ zgqBC}y0Zm;xSoxSXmwrQaVw_m*dpTucZzO`c-cT_orl4+%LU7Q8b?cCl~uG-XoQeK zU;aW*QsK6)Fbf#p>i^fZ&2!S8Sy6cTf|}vTF?&akk6R*gkLBnoCy7_?pmXoZ;y>J( zw|rpJyjV!kbq%sT1aET7$Bi2Rp6iD1tS|lcFuuh{=H!s<*+YCHS@DV@xl!2B-1QCe z0r{fm6cL{!u-$f8B|A6gHzTAk2~a4xJkWiN_5?Di(v}vTg|k3zu~MK}>>7Sz2%zf3 zwmiyMbIdAI`leo9WK@(20vE_)8C>mT>;Vk@6LBU3bJs@}3-rbtwO_kW%yZ1HAz_(s zv2KTgWJy7ARrzLxJ0t%Cc&=(_ipI{s1aAwX*lu~z8La@>T){2QL&8*WAh6Hu zY>z6x%m1_hQN|vq=yf>F2Cl;*c<;y(DNMseeK$4Gd`hAQq%B6^Mw~#hV;X`VD58$0 zX#NtbK?X`3&+=U1%7P5;cTbPvj+U(io5VRIC>R*=ZoGyhX5t2<_MX2;iWzZHN50#H zdE(@`MMT$C0&|`y7%{|Z;N_)JcZ>*ro}LO(eb}K~4V@B6I(h#UIEOi;>LfD-T(jNi z4=EUk6M6U>-w@q0X!liDz;D2XCI=M}`2jH6!fy5ME9FH9+3M)sNdN|}jvnXta8KdK zqDF_Bm6bI%HPwUOVt%=!DjWGI7Eofo(7T_G5Dh@%@heRDJ7n@u zY{}#A0PAanf`GSkH+R}?ACCC=ctnZ!A{+zOSR3Z+4i53|E6cl1_|gnwjawtKqF&;-*h*)9P3Gy%%Db8@w<%xZ zTIskT8~ims;)6SNe5zU#x*0kJr5==gQuh^Ls+EmB(+M1-K7}?VwMUn%P(?3`at9ji zMZe=j`DYsfJw$rfg`fu!q}!uMH#c=F!!7Av?(GbhT~FT5u|4t3TxB>d_zb^-8qpv4 z97y|F3k!)uzCJQ)C*wUjR__hI0Cb#1zH|c&JaUm9SJ&7Sl)SPvQpTEPRgMj60Zc3`*5CTqF|dih>-41XMwp>m-X$w5-JY);(eCy#w{ZOrUqe&k z<7V_g68;q@Zy;q4T z+(Oy~=ateq_#DQfR~FabLL@a5if2r}!*cWYN3*mn2Tk&N1^3by7FS@ki*QwE&-zpS zdFME`4-&!wkj?h?AFeedaA_=V4`Tedby=oAaqeSEI8BhAGDM~0Lnlw&I(6>n6ct6F zpa@+A@Fz`M(n+9W@WefV4fQzwdW9tJyCc4*V~3rt?4t5649729r;jz{{Basq;I= z(UM!e;ZP3O>=`Fu&Fa~-O2l}-DBy$SZcZ{OPgy?M~crT#l{vX?WKKTNNbs?%C}3QjNgOQjg@>kwjIUUG3OF2toXB1A9w@^3{*P` zb~77aOfJe!5i&7guE50lnww8c>p$fEXpO>{g%#b+^4y{cZ1#AGoIpwcq{PH=WOU+{ zF2kLL_fIuyw_aihc4GpBpM3s@j*dCXl5D`t zYkRxtthMUbhuN9819%l(#7a)+u+97^Cu2AUmICZ+_Uf|iD7x3kB2cnls(?bG<)_V7 zr#zdFt99;>J-n%L_-&gs|D4~gav@-)h>#JfoAMIQ1^c6hf+1&$9loLVh+}0L`p~M$@2B_*Q4FAq zs6)2o(*z@&3(jn#B6$1X~vF>v_4kbP_7Zcn$KC+p*-qHKiL4!#sB>Q77f$fW70b}m?xv)nIUVfI5S3(R;UL1}C--1u#0iyO_k9?2 zy9ZB|RO^*jyV>ZlRnpx=ffr?5xItC{o9%3u^RnSEoCjg3pF2;b)HA<6{LyV z;Y_=_<(izV)BB`Aq0WnM!$&ro`l0G`o@kCrB;vGS*toZu-w+5Adh+6e{u}l!87j~8 zhCgxoEgMuEk`TIc*UM;i#@yI=b?#7Wcj;|s2P@1!l;*tN6Hwq=`^YdJzDVv95)RGF z`w&xE7{;_KWQ>>@u7bh;9Qz@2jcLyw$I4tmjkM+)Gv6%s0BDNSa2|n>SId#A_ZNof z{Fzo8QwxjuGn~Y4ib>`f1_+1|cR;y}dtUVF04>p^L3mCVY3~`p8ennuDo6hx5Z6lP zheOk1;^Wm;A7|&&RKlb%rB`*UW zHOw_EklLGlN$*m<*z_ie4(su5&PSJudHO9_9QmG{oUCsCq>!yT2+*Ap6T?1nalJeM zhr{Zn!0}@s7;fFdQL5G?_B#ktd33dZ+si!YR`3^^LVrg=rcNTCgi6@5Oww8E6*^;u zy)XkKr|u($yy&pO7oI2c)g08`?$z++-T>S23!1O)y;qGreV4=Y)xs}Z z<3^LK51Z3ngw_c&-U?Hzm$`g5S-oLL=b!hIOfpflFJCf@816AUe)K^5?s8fSoMf*X z;zVq=!~pMs>2qfa+^Bc3$%!qLnJK3k`&`?LMRgKgAVFZaEq{x>7O&p?sgZnUCRZ5;^`9VrgHO0CPEO zmkz=?f{x6L*i$f)C3KV(&qfgjtV=Rct@@xx}%ZCK3f@9$qzYAma%#N}Wqvpw`~Q1+^4 zE_&==_04W+TwU{_tJnO^wz$RICdgU_o_+UM=9!T$q?Al>er~;U=Lv>wcqUqgxbDln zD)vbT?M@7s_$?u+a82>>E`eQ%;lV5E71h<){eFTk6QV%xfB?65{Pe#nY}paX$EW4% zGCe>d&nZwa&-Dz<0ye97)gdEt@#`a?9VFHP7k3%jWzwDM>gtjZc;qv^LSz8_p|g)H zSY6FTK%rr~NCieNKyn)aEg@SfRX{gW7-Q1wHUd8*_oej5n`3uwj}}sJhP=C}B9-fO z(kkzdvx^H#3#lOIE*B^O5GAS{sEb?oSit&=LNUqoCchanY6P*+gMUCLy`{&I&u!vn zy#MGat(pAjLJ$EOw&TZ-I8d9nq(5*LaKDxp8lDxG!}f|}2s8RWcw z+3e@;08iITVZRRH?Cl2;gj!AmT}0?ujj6Gp%}>F8sbH>d<6(>6breqm8gs`7xenw@ zgc;^s3Q*Y>QVn>-Q!mmXfT+bOU+g`MhlT5?23^88-0W~!fM0yAS@u-MUHXy3&p|Q< zX)FfVfTtlu82SK`0#5QgA(+)p?UouOYIYF!D4ayr_Ybq$Q5!=(ty$Ze)J}i}TrcOk zPbwt_!mBQG#Pe*I*i_+eDtg5~1(`-K`ka8-=|hP1{zdj#cfr?R7vwpfb-Qvrp4sep z?&ev{g~^)`(dJQkR6*ZzPmc1%s}C6%81Gje_4M)a*_)Nt3NsKi!ifgPp7-t{#V6s( zSW0)iKHKmcU>m%>(=2o13m)FUmGOg6TWBi6T$9?=%DXIW8aAcaVJ>#tmY6)$!z8cUHK>)|VG-*lQ#h{Rokffqy zRw-RalCjf8R!o)RM%q~9wVe%(>G4<-?sM0DacFI*k1p#*m|IvRwLkRrJv3pU4eK}% zigiU_a(ZNr*c*P#oi(S@h|1`5k8%fI=e>t{*RGAhBa5Efx^y9l6V}AeIpxzznu`Hd z`YEQ@K8K%vQ2v2WBqaC#R)JTeY0m2p(w?0e@^F5_s})DVDkcKn) zV3I0^E*{}i|15fD&_WXvWmDR62s=Jr=gYweUG69~6fKY&O-L1j z10S9Q7e+DEn*Dd*Fd{54iV@cV(2xUI%3k{(_O%;h#p{J_zHCCLh?qbc(7@_~7v)}Vt{7s1@+T^})6Ztc6}9MGyLK3KtQjCC!bc#QGgdtXiydPKrMp)u z8~_;opliGjG7N{Wp@L1(&%&oczE>CnjWMWWO(rU~8OS?aAjgxwf_{>=LhwOrrfQVU zNH?@dU!-ShYu)iU$cS3PJ>m-H?>2y=x|~br{vWwU=2U`5%ufo!InXh_X7%b&2uILM z0X|v_2MUBv#VEPlj-I6!y6Vr|3NtEPh!vB)zV63u^Xx;XUe`vqFb77lNBWTNUzs=zf$Vz6@F{0|ZUfrKwts(5%EDQ_E6Y zWx{m47B?Ihe^5Rw9ocOFE%?TlRSisT0F!jcp}{~6a&r9^cBr?Jk6g>WwF zPp@|dCC%juf46=s5Zk-<;PSTGhp*R;wXIIlq0O6HV^n{4Z-T|gd9RBH_MTunc>c`` zar~9(0jtSf+T{*hN7VH<<7NZ5_>xlQ++Z#f#{9)~2(86p&<2Nlaj%glmd$;lmp+H~ z8N6t%!{1LXvG;a&$G4B5?+h@Rw#)AdmvTLf8NmVrJG4E4!NKxyOCYfxNl-|tECt#} zysA~g{te+m7F!Oi*bFa#G15t8O~X)5c4*sV6#R`JLyLRj)TwTqMq)7{Q#8l-73}LG zJvJ7+V0Df>Ky$3TB8wFOPx=6Gq<1DnG}+onfLQ{Y!hb}tb%X^Wd6;bH{Tn1-6QP@4 zF^02C+%~vg>?VIH2L=WzO-m8b9hikFq>*H`AL!d1G{m`0+(KB|6G^m^NKfc#yN>xa ze{te(d?H@Y2|#H8f=^<;Lyj>KTH=HWVb=_T+E0ZT+U?xEvhH=!Ybw{Ap`qtyu=LzU zm*#K7qh#M9%EIH*SX}$7)*45Ec=3xv)l7(-cCp3Mn z1bSB&NantKN1g@+N{`%?D;3(^ZWDhT)R?$HJKh|kXW*e56Q;*1Wh8D$&bwh> z_4xohBHQS6D-CfS!e_mM*>J9wr2EnCIvyQOLZyx;s;?p%dTbe>VoDpFr^c|#g`5i$ zT1Sy1W&ek*_m0PU|NqA|lxS!uDak2YWEB#nAv>EwWQE8|R!9*NWoCzjh$wrM?2$bZ z$;vJ(NyP8|?40-Q^ZVm_yPZy_j&fbs>p32e`{O<;+EkM?d7eMF<2zn;Hm+&60=-BZ zK*MhO6DKAP=@f@UISCGBXGc2wkr4W>z4{&Xn;)p}CiA}P2CBrw!04$-L;Iwz{o0PN z&dxjxsoZRPpz_`MbxIs+Ms^^MAIfI1bS|4&q}46>5+=0N%f`&OwYTUI^GQ+(V*Y%+E|5 zr+o+z8Zk#xMfb#L?4-!!?uv@U>P!HGpe(F?+Lme?JmjF5v&Zez3_;`IZwCHxBFwOa zVFusdUkbEg_U4mcNo28OR#aP^H1Gdmj*Li1k9TLyn^7mnpt#@c#qp24F&* z+UK`&3@UAudJ33f5fBHyvLN;yA}qpc4*w@``zF7W-KkqvS?NV)!SK8z(ngfk)2ox8 z9W{fE7T(QFZQguzGIGH81pA|<5_2=l@WK$&h|7zuE=T6nx?L+EsK{mtE_}{< zPkBoqOs@S@HEoJu6hsQOsHNW+vZ6e7N9 zoMOmiH2L@);k+(06?@t;t}wY{VG`q_t0QX4A{ty`Xrb{dzH!GMkj8=g#J8K_tn0@K z#scvhc{;e3_o_#NNJftagy+Xq1FZ-kG_d*V_uQjBx98PB zH-LXAi8#Zk64qx#^h{WGJ$YVOP(;a%Ef)npx`5+&q-&L2-zk*V^j&Elx^_DG1 zYzA%v8pB<{3hy6xoCV4-CUU7VzIE@930w1Z)cP2lP-q1WesLJwVpq@;%Xm|i5$qvR zk^_m?&ZcUAG)p+EYrX~qyx9zex}rWuhvJ7?$TSp)=b>?t5_ z>VP+&98)^Pv5R^I8NL)+otI#Csyj6G1C~UXKWEp^Ibo%&fyh}8h2q(BbUkZ0K!HHe zR@nmZJFjjmAd8r)3*ZL&^+aAGrqU~;*LiNtduhAo&|!s!!bq3@)>W$nl}n9GnTNea@8iP##K z3}RA7WFZvL@DrTq{5K6STn?iq{rH4q?1ssWK!-n*lbPzAW7)PF$9EsG3K!mPn-myb z$YqXxsNxFG`LAj3GlLFgCv`mUXWNn@ktBZ8q$6=9>bKzXGiLm5CH^O;3EfC;tch#Y zVukOO)*M!q2!+zB_N43E&U#m|{Nxq>PIIU5bfmFr^5j2{cIt<3{v1&o{3m69@k^nu zftxtYw8Fz~ttHd2b5=-cO7Eq5?zC!pZX3^r+~@GQJZdiVCu+|oPU83nxv$MjWkgS$ zJoCWG08ux}88O!9gt{3UgWRL|sWaPIatQ!a(hpA2<7Vr5Jo+M(R}zl+29eK^m2+qWdx<}7a&%~@99&@AlSzo&jX<#{0CR65^a3UH*W~NQ+OKO} zfvlxm%I3rx^orfPv>f~B(L317P@vi91H=26wx(Cw)kut{`5hdQe{lMCJ+yEEJ!#|kotoPXYOpfYy5^)>zn8s4Gl?bbgkZ*MsEQt4l2j!|7t zPn4qg?|VuaegoqLslSSb@8%oLe3>wB&DPerMVGXhOxVZT8k`R0~uo4Dwk9hf4aE8=cmBeNJ!kuIKrR1bjG2xR)R{e%`G)6O6l*E?A z5o^T(DazKjZjr!tAkB~Ph#9aHfLLqB6~vLQ5CiyEaI=6WioAW|!UMblFls{U7}5WM zc(ReWoI!Ee54UP#0Om#!od6(+-zX{%!sx@y1lCQG7*NCJ<`Bq3sK8JXzkP)?77Xe( zk0}3_+mjEEVI;RahmwN??t(1v2>wTSrQ{lgp&J>VqCyB8%Oor#tR3TqC~4U76H+8d zZ+UrnPdg_N#MK8t8aXEo7l+zWCz8rnER!&6&~srr*!N|1g*Z3p3hU5j?!xI!LP~|Xv3P;&PLR}Q1gq8H7&`OhN1f0Rd z!X7U)4v)sG;UJ;=jNl&8tw*a!R93*hNbga(?*=ly&L9!vBNB!%#XgnLOuzDW|Ni|l zF3ae`4y$kl3w~&htD*mpmUfXV_!R5byH}E7wn9fuDFX|ylS1w1RnO?~uBNjYhZ=u8 zmRXVGa|Dw4{M&P9?aPtE78H2s6U)rtXE#@yv|oro%In=En}Jrm`J9 z`bv41yVw}|pe?!ICMSLO$J)vPrvl7iR3L%V6M3LE7GXg$iSdLd0$HI+j3em~U~frO zkg$LwG0QN{CXxeyS#vs>Ud#vPUoGywKWI-$SeT0a=@P9=pNA@V8(V2XY8vY zFlZj^UL1yA3&Y+GI2|ameC^TKg=tqXb>Ui8fG0jc|31tc9luK;xE;og>-9inb@Y|U z3J2XAUKdCdlLh((@b-AxyRcXQ|HGUs>?x=UcGA*Pyo1eHAwp zV}e(0Xl$Hi(s+OvIgUrs5aKwpJ%n9;9et$c@Dtkq8T?qIcjJa4zIHF}0ANt%z2d9w z4@t*XM)@9QdtzTaK-HxIKj9S6N%+s1-Vc!KS zpD2+0;8;+LtALP|Vcfk~`36Zw*i`v=M=+N?3WDgFjWJlGS5L0`#-l9?eBpVw^iqSA zsi{uFkEHGRkHy8%P*n?cZHn)IHYKglSg?BBtkJ#UN&B9DYX_(3F{}QMAKtU|f3-Vy zI8_{JgS2oOGps!yF zT#Yag=hi-&9PoocjKJ8qqiGnwz^uDub!Ew=F_Fv-&wHuPo0Brj&SklL_j_~`58PxXEgoVNdDM^Im0*6*zM zXBafoGgd1yQ14V3TzP&%$yMpFu0nk-Re5EPSLv3GtxYw8wyF@Ii89_haDU&%hu?oa zeh_lVVVl?Z2d%c#rSXYc6@{oH#V8~PALPD+5M9iNSSg_?HBYn;xx3GpvsG@0Pem)< z#tOJu`QaJP*Qis$4xkIF;CvWm(fvRm^Ha<0lv*{|9WvS!%AfuuK6^IgOk8?w=r)hq zi6YUOcwykm8G-|;9(bDAcqCO2G|kwgY4)X69$0AM!)V52bqAx$Iez2{5es~Waje)} zy#R*a%#G)2q6jaQpvMo@f}{3q_~JOi7!>tH9&ipydpvWyg147*MNgcV!&Una8y|fc zWD97a;8#=%7fTTkMAP?^Y;JgD5T6W3c-IS`sG;ka`0kGyvM|xd z%woEMBee{B1DAF2*tSW&I&oKbJk{cN)(}|8Jvn*@!~mFcm62N;UE&;Lcd7<2ASH+P zc>?$tRBc6n3S=3ma@voL2W`4=uas?+XzV#NHhCbiyd!IrM|d?jINjiToZWY}36ad{ z#hzQNXW#p>W2|NMSFl|Ls_I0Oy_<#k)1 zjrRWsx@^XXn|*_nOd6jC0b=g&NdySENO02`vXR)Rc?r2{96$_VVZ2);2a*j!g}@U5 z5AFb}ImG=YfjddqRKTREyRz_%;zk=g|lHx8Z7(&5HP31xhNJB*v{wt*-EDboRB79wHB=7@?ix&7f^!aCUZ%aU9-@ zxl|+gvdV_H?5agKUcsE6=#KIg3@QT9au9eGC)L(6^ECdfw9Ru3c9~_$x zzHQH}HFKl<#`V^6yGSv1t9GINLy%?yh3ZPmgm z!N*r|C8bah!>~i={qzvC06f_m6nb*kf+8*gE(j2J3j8h~oG@_8*xu~Le(S?4KBd0q zV#CP2WxMeHooz?>C4+l)Fh9GaUn)$wK_{ir3k( zE&GLO1!T<|&LtjQJjGzX;ZjI4)nN{us-bnJQ4ea% z-pi!xojUH)h+DoZ&+*WmiFGwiMtc2bcO(-}Wi$tE54z*ovX*HZ(|I@H2oP5FELRM* zb@xL7fycx(B`8;kG!o#>KKM~{ct$2Gq69b}jKG2aJuhDZNC3KtIO^4;^6tVvYbu5hC4!jCua_|E-t^~GTr%LMrIoc{#sgG>W-^M8i{7zjMo9Fz@G z%)yS>Q}`<$5TOeSRF;MmqVuxf!oe93IHW}nSO8)oM|55f{VBCN`Tv%cmUZw1!wDYu zj;E%Ju($DqXo}wExv3zy@flj+bN*c42dmMqcIrC6d}nEXjzc;(IZ(`mz$l@ATtWj4 zW&~9(2w01$;I=vUH7)*=w;2o1N;P`d^V!?msV*O=oQiZCSsZI@Z7pVFG`CB$`mAt| zJ@btN)u)Y*N96Z)w(pGDyEP}ob`1rFvDz_y0&lQ&?mdRwx!F|at@1_J9}$~Dgp*@X z307#RnT2!6Ie%Cq*52yQ>b+w6GsFV@s>||B6$O~f!i|4O4TsO4sF|?*;1e1Er@%)y zA19%S^kWe6#q1<_+!|%z6eP@EYp0ISS3Ja-F$;T3D-;cA*vj6n%=0oF<9@3)NOSon z@5s20c?}@)`OS(2H8k!Ri|#!z2h@Vgh6yLw@gDz&4~6EL zgC9%|9{oDAXJKmNL4nr}T=@?MWEB+7r>M>vyb5K$tfKX?F0ez$S3J>5T&50JEf)S^ zK1REx=X7G48FiPK*_o+}?t6qsK_DJnymgDV@Xj5w+A=e&L#8*K6h8aLWhl+08=Gxnsvq)0AT3h)t7iI5Sz8A_)`e_XPgp*dSi z#L>`2q3cI(?RnhXxR8jpf*|P_GT+B15+)i}bvi|r=$H`T;|)F(H4Gp1~JdJ1mVU0l|;upl}*5&}Wy&j`;&lXm7hFc4r; zbvLsGQ8WS!AO;{{1RTQcimL<%qmiZ~uZgST($JHnKfhuHMWeR@tyH*|c+}3E^AAJB zTzr7q9L^GB3t*z@T9SfQGW%K$Y&8spM|IM4zIXxTR(%g z`F^dO^bOwQIRNWV+QFtk>{+CgTHEf*w_$MG39O!aeetKWWwCA}7AUga))CncP@s1$ zEpI+sRs_jI#4tAIlayrQm_*p=SPRK|Z}|AcJ{3`ZTixv?+dlD2;K;)qPwqes?XcFC z3+)K8yYsFvq7u4C>kc>C&|&Hw@s|CnRdu5dn;z%B64ZdNfQ82-XD&)DQZNLS7vRpq zNwy;mO9Z=ePw4n_K~yX_sVi|}5c7|zqPeXo>dsjv!Ovx4e$l?GnnOgS zOEo3pERr|~A%7dVP@g|DA`fX+VUs%+p$)=D0Oacu~_LF=~PjPVT!*e7PW} zhKh}ql^zB{Kq1y2)QF3G{rYvkwf%x+GuvAme~?8uln#g#>Z(4gdV|^%Nqk1G%L6kw zBM41XY;}3=|KOGMpdI@{JPaSkG)ajD3K6tYjb>@&r@r3(N|x^CvbTNZdz$R|Kh|+B zfp*_s6ncowveJq-&LK4<(fnrACZ9MHgIxXwc<|V8O`>(V4URgAzyo8!8ntm%2z+hG zQ<3y$Q@wPN*1B-v>FSsHqBCV)0(v6~t}=2*rPoxKmakW~EBGg4P=cpml`a9p%H)ZC zoqrBR7TjULl(PQpm>w@@N}F(A&bEJepBzh1WfK$Fe?2wgSUQ?L%HVU`Kzp1GJE^r8 zdj(}Ivw({D!A*nUuMG@#d5_jr$?f3%-8d-L`f(jxh=a&B_1G;`B zR5J=%4@>U~TC-E?&1dy1n6L!t(%F-o=HId2dv^l_}m z61RkpO|`4o-I)MWm68phsO_recRb{)n5uV48T*QRT7^(+v#pp&_`5dM zgrD1=MAZ5W8MgdzDk}23lH984v)AYP=1)}awQ8Q>iKe0yky;UPHfrIALums-cy_Lc zQcFn)SI0v<4ewpi{~?>esdL&BG_-zuC;uSfN`TpxB|CS?%&A3uXQF_&Ca??& zT3|A0wlDR4MAt!Y89>h$uey|qqzTMJC<#mpF6GHD*3c6{ytz)#)U*aYi%hnJJD@oR zR#$X#3FY00B}V69@N1=X_0xj79sVfcAe~aTRkV zC@Pu&=%PqgeW85P&1aTeX7C#>$>;*7BTsye+b&{_*+|RO*~krVA-KhOP?ChIQ~u>RxJjwI~XZgFs*|+G}@CTHv#tiLT zg7Vq#j1VhD)v-9Gv84f;GuQfQ7Ct3*6;)NBl0I+ zwzSRMW4GnY?aSj`|Lu-kYdw>s{)R2irvn@>CWxqJiKyX%e_VZn299`vUxu>KY&1hu zBM#**wZL9=RR#Ey#NDQ0vRt}xP$0pMNta)sCI8#oJ6n&$>SF-P+VE=f>*W5KXD@e& zfW=sLcnl@=NTKoy8VC{RpWKh;9jbLpS7QrqP5Dd4K??xZ%cH3$L`tUBco~$l+T3;T z&`@vT8dQuHrQ5jizL)_fOPYqAcQFmUg9>~X1##b&j|`o90@!n{0YD={?I~OMJI=ig zGAJ|?>!2_4_@`=NvTJAcc*Fn#_@J0qz z2#af2N{YxdHW$YLZeWPCPcglD$#u)05T^9iQ&~T!CT0ulT4NRjZi+G2%9-ug{zzdG zdS*bUwc_Udz3;Y9Yv%MUO$+ic;RxpEomvke+2PrV=h(K**91x9K!ky)6~N1yu&Gv1 zwTDNB%kLQ}zx^k@CY&{zetE8z)yMy05+$pyk>mk_ zrAEBJa4GeVjgj%)7niv$V6d-wil(Iqi0@)0;Ve0hC5F*MvkxzYG z{P*Y~%(7?8!Oc0K@?0(sW`hLnL9Q*~svCr?HL9E`@w!Iakh(#v5~!9)OvzUv8OJG+EUi;EzWdW<^N8tN2~ zc+2s)NVqCW1%YL8u*k`FB;j!U{5#y-R{3v)FaJk~BSFaE50Lf${@x}MHig2L#2Nw% zBb^Bjc7WRqIg9|kAhte}(MbkzfEzAg5SOByNDcNpk@I66lQMuz5}$uIwG(9ownoAT zpmq$v3^)d->i~Qh;I0Nj9%0m@&3)yz>RM)xKb!ysQT|mSbp49ri}%122f8PVY4j5e zv_blie5t;Zp1zuRI0ECilQxn}>~IrnondhAwHD>N(=^lu%_X;;-&{JfheuoQzgP`G z?>4we)(+yM|6t{e-ANuGyfV6{orWhT{a)M7h>E}?<~0VM826oXauUUNyNb4!)NJW%;#X(PB18f0+-@XC^FHcXBqyaRzRs>dw{}yv0^0gHY z4(}Is*S&0aTB>b{>Ed7l<$hU(t4_h8xjLzRFV*+t>*$$r%3eKSgO`9@%E;|M;bD*f=i9=<4in1Pzo0CuW`|%pM8&Q zHs_auj}%Y2Rv^4k5YrKFg(Q;IfrEjG9D9zH#KRmW39y=__9HP3xCk()QMqFPZZZD4 zufYG=t>l!IcR-9v#&(qcXfn6pZ{(a_@f|PQCNYGe6j#<|YD2mnu)2Mbf)J0M8&e|k z!{#AzW(N%;EAH}zbN$1^l~{PB55?g9A22yFrr0gq3tslVeUbc)m?d8VM&*T5fWX+; zcEPuU-5LMK1-QY+P_1J~(FEog2>}6zTONZ)Y#0Nq#r#T`w|Rqz(uys}e_{iLl#~>) zr-0cLhDnaIzt}L;ft1u_^w*ps>1hiNEDox@C% z4a#V8;T8~6M$}uh5C&XZbAeoUtx4O6eLq;rjdQDi5J=4Lwz7P6LP?F~O&==mXr*Seh@_tD$m(J&H9xAeN6%MV#z*pV#!voVF zzOd?R9LO-UUmjj}snc3AC74D-(%ZKbE`<>HAd2bUtirkf%9pb&ah+z;zsA^<8?LZ7 z+jpHixJN}L`~aQwo_+f^eC%p%tEEoePhD1xH4r~v@Y0P8>;-KQ=;9S>nnB3qq@y-h#@RR zdPM2k!On%313!7Lhzi;mbU&Yh^212P$dUo)6p$=NLcsudv9G{QY!f&bnCAC^{y?Y` zqBqA;(fYuMnv|?ivq0F^Zm4q)7#bl%k^>O~z_v84oxmJO(m2Sngz12C^cl)c%1R<7 zg1HbP4ff-Sluey|sjh-_03d@^c>P$93UL)oY>F|4fZ6=^9rwo@VX%QHrtRhBFs#c= z(0pJFB@6gn-ex%<2ts?{y!M$`1rjj=CJjdsql%lw3uaZ|K9kWSaUg*g-i^)_-=`oo zpaI8R{2eHO)3dY4L|B933+t2w)DW~8cp11$E#Nj(1!~}+6voh<>^z`qgdn1xnwgzl zQC4;r%%iNU71V?-WYUd`hzxm=L*R?ogBu>=0%9)%7E3umZW7`d&U6Nw*KxXU&D-T! z8gd-~oh48gZX$fbpx}@L1XKL;aO1dU!yQeY&L4e&p8Cu^=iMPY&%7ff!pLrIZPs6|W& zaMzN00s0s#JG--q%1U6|kmrC>B@c0@s9J=~B;t3ZbZ-)sTSs$<=gHyTbB@DrHIu@0 zqz=LcCH1BbTiOxd)!%y}qe?@nfAQB}_+Lej6`B>amq~FlcS1&c`SFqQKii%(y`Woo zbLnRV+jPfg_Lyy3jI=*8>IP7m;HM3qUi=f5yOwtD$V+PHH|R*S;aaJ#uGgD-Sv*;g zo%(Jd;PDr~vtiESfsAS5qnm>PZ?9xfjjmLL!(f}+LoN3mK;Bt7LxiR4iLj`8tM~{aM zUy7d``#b7=m76P-66~bQTzhw!QQVQU#raW^WbK(>fA(GQ=58Mj7iGNlKEcXu<;#;! zQ}Y2Bc2vaSu!a(s+*&U+3K zHQZ{rZ81aYbYW8l^f-gMh(Y{tjUkP`3^oRtHKI7Mg&+a06`M9~Vm@+Y3k;9o6cUh| zdmOC1*^eaA=pOqfbCXfQbt@m&-{KA9yr&%NGY~Mve3B>i_M0N6osv&3EdA!$U%2RQ z_cC2`Fkxp)zANuz*7mP!gJ|QeRwpT5Tx1spgBh8s>>WSRtP&B0qiZilTR}rO9!@+K z^~E*DxqGS~TmxhEiYbPuBLYW)@EdXRO$6f)c5r5vxr!bO#Krhy_c=SLuX8_5PuOue z#N#kfGGWG=y-fw-$5Spjj5spwmLE5bi01kb=NgbUakFIxhLi_r{sk@|x1;P`faZd* z+VFXii~=`YXoIqHVsw~`0f9y`dgv|Azi&iwtF7!cp#$lwRJqHtOe2*6k6f4{N~nx& zBF<3hi)DN6{WfvJcG6f5!zrgh@rS8hH3Jlq-jdMy4UgNT0b4Z7uIOFC*vAfVRi~}I zw88(ju|vUFORLY9;`^{ZO&DP3=C-2er@zS@y@AtXmpt{-%BQM_QpaW9e0CpO_v`lD z)U#d%mReq4%sPWHIKwBy{+3<&MpAs|IDG1jrB(LCf5eLqHNM!bd;0D9;iA!(@weq! z%pSZge3NKAD!ebnK=!E@$UdQe?C8Gy(P*A>Zq190R$0W5HVr>4^Rk}TqD21L!R!JE!QsFuP?>^alPLr)#S8RQ#10I z6mz*sipm)cg;lx-teAl8_L7ieIm{ZqLG5~-umE#;&_R>!7fe;l+g1M;eWB_Sv9#dR zokeFMH?v2)CLlkR%b(X@QZ4(c;;)h*tNS{og%_$Xu($sNLya(q!9h8EU5oxNp-oX| z$o~ImY|V+|L$*ciDo|E9%U-)&=I2?=ZK`uXJkv2 zcQGZnQ5@Uk!@_6>QjAOzEMhCWW z@$zavmrHM|U=l6%H|VeWNShbe20ohj$t8{gRYs2EDww;SANqYEFrDRbc#QMu)cPNC zfgXY0+Dy_oFO)5xUzoYiZrfGCw2mSCwR3O*j&C&|+O_mUj#slKcBM+Ow7Df?4wHZ%(-G z=-;MN%fpOksmS{9f9sWw6iSM>+~~sTR5KHSwO=gr6=? z_5S0cCTwjo&?MG&)O?tGaX)Wu8!*L8!MgF~Oyj3?IiW0()Yr{Z3_qOW`qXi>tYx!t z#!2NL=hY%k<%%>pP&~OD%x{Z__nbt2{O`pk(_c?LHR5(hHld~Jf}*=hUEhjO>sOY0 z4xA!gnoUJH20l5P@;G!(!G)iVk1w-G<=J(a}e0{ZYVE6Xi{jFjKM$w$Uwg|>W1pbmRK$o~E6?bAw|&ayr0H@=~k;{y+I znIylcS6H!sKe&Oh$J@6{_jDXFtYo97lTv%ls=(|2zP_~dY(k{_>AjMM7c^kumyF;>n(lkl422ri{lTFU{$XQ|8^= zV*C4;pvhn?1MWJMhrgE;5=8_@H*Im2tpco^toJu&@4M@tc@1zL-=VE~58Pn0ga!il zvM*6UVn>2gV=Q#EwO?gsI$qBw(qFvn1M38Aul@Yzqq?X0QXD|{dC|P0y~<2?q^=~t zOAXw_%$fe;r@BMus}VE98Qx%~!Ws9+7G`1k>gYHoNq5)$3xuD(@$pNe#HbRlO%uj} zF(K+lxQ7M`|63K_J<1KSZ2d{GP38g*eW6>T`}t=n;b=yl%4P#)gOAp>8=Eaa^QS$PPMpZGZ2Cu&{j%$M`uednYwx zyfwSabSQXkf3j`{tQdPa>?bEJY%6nf!=yR>w}-Mug%}3hmLIo02=ntzIC{LyweM8g zTzHa-61FstL|(mOuA8LFr-_ZL2nfi%^m$Y{l}5(1i^QEr?Iuua!me{`?p|)=9;}C}o0k5A&%ws~4WlJK9Ec@? z_?xm(k^g(-Yym%;4-Mp4p4mxfq`F^V{|pnwPuKc z$RfZD*8V9C4gh(bwE+ny>>Oz(ALjM#vR$XZTgM4#)_ceJcdhKRgSZB0Lr z81R^O6WiN+_X?la$-=xhdE%-(X8!I*KXUFld#}(PRVA+m~L2{y4py56-j1f)Rqxst+GXF%9uHaY{wc zcYKkL;44seAOJA{>^&fzVB7J*@DX-ima~7<&f+nGhPT)C227JUr-I9O_F<}6ge<`~ z=AB#N4gfVfk?y1Ag^`K?tP^sscS(W+?U)4jq4@QnXiCE*6qiwR;&TSNx*YR8QI&qD zO$`jChjh48h48ZQt==E~3$H7NVVD71QQ{`!+qz~wVbta; z*iFnk`6H|PeAoXz_gx_jpA=5JneM#j8OC$|lD0bAqhmWu6YrSL?UWSDL>YQgEOp?P z=$nt-%LjgYn?&a6>+_gTQ_Tq0W*A7hj)>EYZ&uz9R(m|YU1G1UcTxxlQkdRuBuzI8 zEif*d zKVpJ{!A(sUOIBB$i7gPY-YW?UYL6Ee7voyTHh*zLb}qMe9&Lz>n!lFXb)2VP@$my8 zlP@^Fd>3>Oga8`?199wqfF^7Eo;^}n9}v4{BDMSx>;3%nsP%Rl89UWP82;?}RQ59O^7$&8OdI{YDytV=ssg8t zai?#yd>6v_$VH+v;LM=iGvm>Vm+8Tw6geX`dPt{v9a91h%e&#!b>jzwC5~?qkiF@c zgrbtpmk=rrq~eNfu{vQgBcT+C^;$DyFBW3EZHgc6U`OW37z zuqm>FSt$|TG<1c=VjvgtLKfVo;5{GyER|byc^qTyNERMed(ro!<-wD}f(UkjRvMpe z`nRSn0|lkeSD7hgQ{JBVS@FL9{d(jCRVp)`?z7Bu7Wq;bLvtxKI(4jJ@U#{WlL+eXAbqpiIF=yVzP=@2?#EBDu zfh#L|Nu3kjXSR;)ww4uO|N@^Vt$&DvJEIYp_^H!BGwtB;sL7($Ih=)9oLR<9-dk%YecA#IhjM zFEam6S-J27knZ-dw>1n(lrPoAt+!=ZXyy17_&V zVOp^UM*J>4BukJzt}XRW`Bc|k@k3?F!s@-f!khQ{wZvcDea3K{f0H*=pXw0D4|v_lyuPXQ>xV}%R9l?yz%f^HS80;hy7Zjp z^22mfYn@eH7;arvNuDtK>j=*2+tk#wc1neFF1^|T=?J)k>W&@)NJxB-QS!Ep3WMJb z)0VZ>oY$8ynV9t>PDkL20*Q+2oLC$RQk2o5Ap9kqo0O+8pCGa!(&s}{1p3mRXoV_Z zNeuvZWw7ZcF@2!ST5+rL+gpb71SDokyiWnpm4H3JjuO}vQiI9Z=3Gq5o)Gzy+6z^E zfSpFsV0(Ehw@la_{>}8yVjIWcS`NC8^WPuJSNYS~HwWgu5w_njb@(5S_oJFZ7UukK znlDDZd?uTby#7FsJJ*SWi>`5Huo1b?eqccFRVX_3OP%b01pDwGt|f^qYLRrg|G*&Z zyy-W0luuuyQNXBbRxY@K>~ctSyfGw}(Q4BlnNyOeV;|Kb*eYo9~YTK4}~prF=6cW;g6OZwZIY3gRLI12sP5zKP@Nc$Dxck z03{!kyg1=!=L2|)eR`8Nu3!HYW_l8?Mg1mF{Z!A1!Y=4m&}0Tb;B2O`F|Bhyaht&+ z;_HcY95|pTQ@TSJu;YO4vVy6I#g~%LR?Z+NQTk6{&H|y)8fZS(J$wFq_{4Xpx8SH7 z1iTp$LkLrkmkHq{$^(M7BNO?|tsvjSJUk5-sfgbP@*TDWbYh;3j_IPIAHLPz19cQ| z3IK&B0-JttV8A0JgbqYSLu}WxXLpIm@pEwaU~7|1U~GO*1SunD7~>=;Zz@4(hW(^d zNg{@gru%AHx^r!79Rcd#>P zx?KrhatM-0APRE0UcGr!-rgR9cQZGhnvn$+0j5W@*D<`Q0~o&dggrL~*Fs`j(bQc7 zhsbugo@4%ttn?7K-2C+PQE53e)p#3wK@TQ2k$AzvA|m~RgJob~Qf=LOjB`bTsmwEl z5Q!SQyCfQOaFDq$?GagZgX_uc?V_O(FhB9NUZ~4J+oZapuhXWk zYFh*j1o#Vy@;5j~?Q-+><(*ej@BICm>jC#l_0NVm@gBtvfrX2r(qje6-;ROl4#F>f z3rw3U!JOx{l#;nK)4pr{ZTvi07=c6t$TtM#c72wB<6Ojz(^C47cg`+zBYb!ijm+O8 zBqZ3tVsv@;at=e__Yj`SZw6Y?ry>G22#Dw)2uzdamXvFWQ-Gbt49^Rlhkk70YIpC7 z0tmmeYBE()7E!RW(f{pEsx|L-oZGc)jWx5`=MK+=n{CA}FHfYmXLKl~T4dL$S)_J6 z97yZX%I#3g9Z=Wy?mMqUYshx^t;8>DSwj}uHS&6XiOXd>!2fa7V$qqZD$M8c1=HvFyB-5HJx7y+cy> z0azbm2Av0MKjI0Aa}Nuw(pxIxDlk(biAF>;M*iUzUwv3)Ln#m~Cxwpnp?FLt2{!`^ z44Jzv-75nXG`d9&rJy4au_1#RVg_T3K_ua`K>D*$K;Sj^5+*fIaW~`foVl@t0)>QufL}`p@ngLs>rXqZ9zN+( zUAJZ51%KZLH3@1B4eFayURoQ>EveVu%!(+EbIzZhluTbz4o|%{)qWsDQkRuJ{9w`P z8iqXQ^&US@XX&dYseIVqc25iE;7@D*ySG?hJLAaQFpU_S3o}@*egB=QqBg@tFRp5)r9X zR!qzGonHN+YU*A0M^xgo_U99ow}&NH)^}{;Y8=+}C{0eat$ zVmeO_czCpdqzwC;d$2u)&Y2K@Kvx;S!3D=SE~!D6R^`-7!JKL`ytL2VR#(VbhjN=Z z-~(gaGM)iVFuHo8*204E2Im+)56B_QbeuDW?+m)FjhG?VS;ZBcPl!`alp)0+q$f)8 zXE&jX+jc;#4|f94P4DDn_LAjUSzJcvQ0@6S6=B&RpwAPd;n%p{xc5Zql(>YWSfs#F z%MzW01$_`+Qsm(OHDiFXv(%T4tL58Nf4_d)N4PR@swE&mix_Go!hV%Zf7{J4vFgVw zf?8gr>`zco5Hy=xaaBFTH{HdhzBymJ0SpK-t-(GdnK>A$AYcy4yPJLtv2o;kpuk*1 zQZB(959SJc&0P!03E_!p7ddQqM{y>AIR>;3kLtdXje5oHPsqoQnnZFvWFrEZv_O{u zUnO)R=rrQ-VNjq;_s2oNr*i_m`rW0rF+04~b55&PxFfJ}2!Ei*%ZrCx1aEQY-n}(=jG6^@9C)iFX#~~6gDF3c>6n%=yYko* zQf=QqKZ<%ixssA*-tjKoNY-$e(l({QYTqPiaf(Fs4D<~=9&dWT z@5dyocK3CwiU7aSoPe_(_B@3if_5Ki8{Y&3w^`M!h)FDHmX)vbiMkynXLq&qf@VBy z82$))eb$JjIH;jHJm47#*H+4IYTzz+9-02dM}?DV;paGIjgA^ z*Y~qU8y-hrq3u1BeUf_Leu}kL#{@l$#Vbzhdm8!p3JZ=u$kGho++BMlSTp2>pIS=o zVK2&=*=m`A*q-w|VF~TS-><$sNVUk7KtX$&cGe%XgQ}|g_>85y@etvqQFHSP@T>E@ zM;ql3x&pUe)-kRmocbHxv$KWJKsIDqd~$!-_Qwy&gw;@lw9Xq<&w!ft^wt+V2SC;o z;p#b{p{^sD+`PK^*9NC=d^q)F7-#S?mj%!Si5x!nx)#OAFOB960%aW={>teSzD zGuHK*4fIW7GdKCUFbw>j#BNed?A<^E5B_K@#yL1#AhEX&=7mo{6#~#Km?yf)9Y%`) zKL||>mBw9gLYhHh-rNmNoMu5Z!;>|ul=keqINE}7p-0xSyLfbzM;L=g&Y9&}P91Sv-Rb}d*^B;6*xGAbO>PCh835s8Bw zAB?Jrk#Y+O)#*r+hIo(t(wpSdw-Eqw#_3@W%u~>zi4qJ|p5B?mf%^g<_C7vRI`+ zoz`dRHODcq*{c6NC7IBG%myOOR@4iRcBfH$=siKxutv|^Jkp~3jfL_;T}`HqO%X^S z#Y-__(lxs-Ctr-)d{?5Smgf~qMxy#^^|kNyH8s-IO&^OK1>oDUVO|7!0=XbM0`9kb zgVx#bBWt0|c2I7}WQhWU5^JdZ?pB6$V7DAyU6mUm-d3;Vp-ILrT=x_gfoh6!#pobC z(GA0NXU$P?*6d1|OrhM3ijKxJV*hpMB|NRkXqN&=a&W>uM=y5uG3$L0m56c!%uy){4hW!Ldw|Ad|>f#_+ zwv?xM!T|R&6!!(Fvd9EC(Wt|$QX%v<*i522(^4<6_GKEajYpCXQcdxIKFzzlY{R&$ zKiqe!zfqj)RVmXygBzHN@FxzgU9HVrjxE3E1FE3M{^mvoU!Cfx#YrPLrIiiDO`JB_XqXuoGU@}Qu z+Yqe~_MPdNicv>x1Ilvp)KLV~PP?LSB$6r;Lh$&XbF+qW*p>KGz0Yb6y7wuwAES>GC5 z%L+b(FjLF3>i6h<{|)s6%vd6L4PeWUh!@hM;H-$!-v@Ies1B>a!sOR}!%3A-GMR8_ z%6DkE!qj}@yZ7(^>CS;KYXKu7+6E224mp@Ec&^42R##sD9pNm#>%#+zuAqJz;;5tm zx0XUtvC+gq(fs2+K;P_?+6GOwpKyqMTB1==z zvho4W33_%`#C-ss+WOc@#2*1vLFMF&%*DqDOO%t7`rXs2=zVy&xWO(MOf)Clm;SwF z+cx|kUw8+MTy|$%ec&-&U*GNeE}6)586G_ekC9Gy6Fd$U?b|Wc)Bm+n{@RtL7z1u2Q^oxScBd^Z zEaD%Bcih$3_HoA>&oG}cF4Mhrg|CjkJbBr`$g(FY^59U4$?^VA#@@G$3&g)tPyePW zitq^c0Q-<1KH^9^Qb75>X`^I9VC%Qt^z`0Hsu-($yl&j^NY#|s$d}9ght2p)(x0oa z1^yb^t)9p^GBb+9vUls%`3&r-=eU}yzuFX!>H}$m^9mffymdVvb)uUQfa5ee!&u8T zNs@5{Rljk)=TYD}GZ@|@#Z|n{9z!HdD;}W|MskV_oJm&BSJ6S+N>rvUzh0gG#tZ2_ zxN?O2iO1C#mQ^QNIy^O{2!8z3y=u>+SuCr%vPwTec8G zNic^gtN=@5-v%+A5lX=mn*lX)cp{$jv_opOA;-=yyK}6JPL5`6t-p9PBH-nYOCfwF z(&n<)gT3hFU-n;Z=&-ceFeRs_@#y@eQ-^RBW<!nYge6eC#?!k%cTjm`DMo6-JMurG}BE__%#Ai+!GVOMmp*_mOrVA{RHEt6XlhaJ8-m_3Pn%u z)_tzd$^T|N0L9z=HqWkbk*0i_UOLX9ar$Fh_UW}g+r*!*Z`?$Vg0%s=qgNjs?b-o2~^|25&@zm1LYNJ_gEtDmW(^d&mRc! znKkw|I+BH6-Tk(=Zn=q&GscmvAjGo z_KG>iU}5mAz3XWQ#p`JeN@&*_}|+&A+5eBR^rx~}VW zT_J?%Lp3Gd>g(X==Qo8>4HpO_Ka8Oc&`C4UjPg~N%JV{+5W%A{o4|`$1+(pjbU$dI z;>JQ7(OsxgRju`9YH-#vlo&f+zDF?UmV63|x*1dFHVrRo#2PPuy+&)M2Cs1wqsIb@j6cWa8oLHcq@ucKH^-Jv8DA3n01=T0M-s)U+ zNnMtR0&rGLp)h29uc01S6wRakY76eG7nlh>Q7w@JqzD6Br~b1sATAexLzI@@yHCj% znu5`}9IOdP(P1};0v^WI+}bZ5EIU@5Su-ZVx2d$t8mkA^uC$y&>|bl&JgRcieQJ3) zg5h<`v|{=`v)73&uhb5{v@{4}^J5#PNg>(C#nCiz( za_nECQTXxW$K|$zJACJ)ADovfh&5&gV95O7e|mKtaQFpY5}YX#S2bU%zdis$oUCq3ql0v5{+V&d`q$SbyyFj zh&No|!_d}RBegFH#==qds^|*9Q5BNyfd}7kNjsZI+G`#+;ay@^6c(&TNAnU;kubVm zjsdC)tfqf_+i_V1!gqz(0Z-$?AbGUd!D`Nb;MVhdsI;f(CyKGgnf7FDZr~tffDM~R zEwW#SZ^q+JX;(i0TPQ=eDHa@a1FL+gEh8qw6oaTN63z!vyDVek&O#2S-`U!Y$Jw`C zh`1%W^3q?dX;!;Yy&RCtnT@^)*_zdnv!Rb3?&>{F_EoTspzeL&4B68$(Nhu=;_q3V z6KO_iwyHTx>n_~=vN5K-rA1e8DF+)W7?T^{;DiS%o3j7-bJ>#YXufb|<;Ogs0=61v zjhAxXIrf+d&c`EZ2i394Om#A`FGLlvnO0?UC++6VsKmFi%lepD`7^0vB+doHX<>iE zoA~Ho_5c2T205uPC?G4ZT0SoH-QaUzlMNLJ6l8?GFuBhFXkG4lEo1h8p0n|L4>Vcu z&aJ_K^H%_I#L^lKF_04ccAqdOMf&;w$%BSLNzk6mWZX{9cwiK`plUvEXLrbSZxL*%f$wi{UXA1mt6~wd+60({6J~C}>j}6)flNLA z9N~l!C5J@sEfOnUsHg8TE^ESXi45WabmKA1K8VL7S+ae%e&m>p@2KA>P<)$kAs8yx^RDu%VSBGZ68=iDQj=W z*rAf*xw+Jf56jDYudPbc*2nZsiTAL%ZgJt}rt$9FJ>#X$^2pRLETg8-#+Z^X{ z3qpA|YPz>3rSRraJmTQQL{1V!OvwTC8#fdccEShtlH$P7SvF(!`ZBypDqh`6`~G8IhK-4rnT@XJ?)nrbLj)S6!ZmIsXsx4B(O>A$znR!{Q&Mi(>aXRk+Oudg@T8OiW+>4B zM?0LC1BmDBowiJenNt+VlpqZ1uk1UKf|6b(PfeHdU>@$4NQ6KjnEJV5=zbm?<6u@m z&)LFKgP4cH>0&{qb_*nHl)Q$$u=kV)?wm-wCwb1g4EZ#kE4v|xxq%UAguY9en3$a9 z3&*e;9EKwjgoE>B56~X@i(mXDY%5~PUK}&tx zL_h(b;8P#~7<`m0behSgX9B$7!~C*Me`FSTinWI-3?)&Qk&Xw$M6mG3Sp-y%icBlw zcV`taPzNl5^MkmSW0>)5g5nNF8ozfFP&Z(H{PL`ZmH_Ee^3ZhJW&6}4LjZRQuW>Fv zUX_8dao?|B#79#Cs=tiIx*^f%Nb=X_ha9@PRI;{8n_yK~J9HIO&w^vHZjBlq9k@zJ zO>C9<`FWV>lI;P?H319(d{a4VytQRz8~3c?(1MY{I?(%=1q8|mYj9-R%ZHjVF#f~j`VM!vf!4?d*lUgBG`ZEkpFEX1 zcA@7i-5si7^7wXYl^mb2{i9(v3;`T4Oz=#hcm+cE0B8lCOtfsqt7!UquMS}XHDzsh zXed+^9J>I^zcn@Cdw>1Z?ChU~#YN>j?Nn_mE@a|>7x*zW1#}tn^czkjYE2E~edi1A9UEb(3pdE|NyPv7zKWgJY@T6|+W59#qY zxxI>FaCLP}Kpk_U`u#F0B4S-FXkNTNJ9G7X%1r0?@1{n7d_fuFmksJ%!`!~K&oavm zNQIF1BxL`Lc_z7BSt=q_jPAf#P8r-(tveJDJp=U*g%%%M;)YokK6|EU^w+9ge)sMT z8cuJu+~eCRhRZi41l{}+uju?|ee2IA%=&yrtWQ1^_pWUWm`}N!y^Sig=8atg`#k~E zkdh}KMFgv?gnSnMF*81-VIPq=>+G)d-eFYj>oXZGB1}can%< zYM;&|txtzU{WyMBg-x)=U5eCskZ;m*VP4I!JMa*G<{s(dc=Y7 zg$cs9oo8iySEHfaetUh+;T7=eydE|PGw6P>q{{P#pQz)%8!>m5 z`*t)(itU}4HV*GGVg-#Fpd>4chFYVutP&jp z*KpJK? z#`&qQ9!6c;bdVd;)`;IHjB_r9#}`C_fmC7QGK+KhY6stmV4bN1Ks99Gu*(})M=6ZZ z71h+6GY8I^=)CDt)n>*A7XP;3!UJrCIm@iv&BcZVtltm2$* zx)Bo~c_6&7SYi;isjYO)xB^kN785Zg+$Hn-9VG(^K7>Z}vl|Mas$4-p8^F_?R|>&J z^6d4Rqy+xo3jr6bOQo9DpC+Hob&S{mK7AIzDS@}d29wmCLagEZha-Fc1dinR@9ofx zj67P_5h=LGE}w31{r2l_Gio(!88X$neC&m8a*@ARBxNhDg!=WI)1=dQS`%6H_OSZO z3TsE5*Z#EO8I9PNdtVrGj|R?%xZ`XoeH^AOG_jZUpXq2*lGTBJq0m7io*S{>Z`eIk zGoCt`aYJbD$zd)>-Lma~rP5z#wtI^PPU^o}FyEzh>%^;TDXOKPNAEZ;u&UHwO5&T3 zN*f`ye3$Za9`7dkV!(OpP-33LF)=J;dV%S0sHZP1BJiI-@7mpm3HbW~OO8|J0OX$9 zN=&u=gS!F^1!a4-b$}9cXvuL~6G${}&tWkn{Fnfo&&3)x>6U8vmW#j;0AHV&r4qFg z+HKFV>}oyTkVI3SSkp*%+=3_s{tCGDx89&!2lS#BfCuKXiQ)sM1PZ8y<|jXI7yho1 zuGfSf81D~nlmzhMDCmRb362It8AHzkl6LQ{+qcz(^t6?Ye;pV*-38@@Fb*78sI0E8 zZqPkKH4l}{F-RcPqD0Q)6vd`IlQJ#@qbJB(;7&=36IPp+#)~4Pbir-_fP4~Q3Q~ro zy%!%t-U|ZHi!_ET)(bD zt47@YabQMDdt@_S35~KBOve^r6V1gV(MIz)Jo6dBNJKs|HWs9n+>c5XWF}YBrS0$) z1C&I}en5*qxOeZ~yB^%Yh%lqX30y0o&&j)k{mrPm(>pI&TTKjJ#d2lYKZ2myvo(v@P(dHp;B-7$w*tepnK0 z;qh!Mpp<|%h2*_K!9!S;>c_m$&Yj}Vpr4NOqdC6JUbnRF=Qu}2b*Tt*u+C2BZrZBN zK|%s51iH(UjP(=VO3{$Qpz6u5J%=vq2buWBbH?WRu%My&fa3!A%#^w707Q=npOXv6 zlnW6{z)z!BY*ydRyuQ2M7q!nh;QXX8#@RWD zzy3`9X4aR;8v)-DiB;r+XdynGr03U4^il_42`H}g=!p)xtXBYsNnwYBXR5!7fg_Pk z*G>1JiK%IU#|#&GDY?{^5#7uqt#>vv7`(OFif0Y2JJt5>!K$*{m|DCKda^?zQ8^AZ z9JE{j^4{QnB(o{Q#X3X>1QbexDugUqaHuShx}pH9e-~qvZUZ<>>*pE#(oRqvjAIaz zi0BdD{t#*`7&(2!LE0o!55s}Rg5GAjZU&1A1W-JQHPXqk9y9O|C#S0`wd76LD= zcW@WGFW>ir@`jSb{}CrobIu`~+1Xd@*$rB^WiO|;_cqp33#)wI^E&(PMJcv#qyCeUs=NIqTkeJ<7fR);TJt9d!BBrH$q_^-KiXpGr@er<^><7yDiRbN&o} zdB3+l?|F?=s{5N>soS8g`LbaT*YP0{bo(}Lk!us&kltl5P;x*PNSmltMX2cG-jcXm z5aZ3pVR}R{rI~gUU${SX(8Rp@w!qLpId=I!AIIjT&`(Q1PbgemTzH%6@q|4V|JoKR z{fPGb(CsFF>sNrRnd37zDt&j~WF0;jZyiH~#JjJbKd;-nH_l`(W|qp_!S6rF_^%HN zqA-V|f&%NVVymi0M8dpuX}K=bKP%mQ zQN(q;+mL2;9`zrqgmZ$~Gi(bIh)Epyheab=AvyPlw?kox_K1&VdEg5W-h?xPel8S< zY(mkzH4P!=q0+#K6_t>15a`lL6h3gr;Uwna&<(nKvWac>LL&BQB73Hvx}lLDWmeou(_ocSD$m)t$&8?I#~P+IAX)S^#6Iw1gH>{pw`aBQcNU z7hymTt<&{ZeOba8eTSBW~QrrPx{c8)?0@Q3JR7Q2liu5H!If0jYQ9q6_FR zZCiiWBL2u&sY>{RC5H7cex?pt1jn>6yzCPl9&b7&bNboi``xAaG%leAbqz)|(J+a7ShU#b(Zt{^Vus-o{q07Fy;rYZ8VWP)1LqIwF>4Otz5 z2sMBF_hWUnvg5;__HU8J96tB!0yCHNOg#J?`7NFAH z0(W(o&%i6Ij7(1B(xMnb$Pqg8uPym-y7`Pd&I1ZXbFV3C*ah`Ncv<%2M=aPFLSrUN zF5+n{05wanQ%MG=rvh?$ekeZpV&^+vh$$Jt9fI;DA}+vZ9R)7jAwp|0&%W$0_y;L6 zFJ<7pdmHf%h1`4a3phcR!TYK1S(1eeWFG{Lg+d3eM8t3i-%UcTh*+6f;2^kTL^dhv zo>M)Kx(CbGfM%6~McVyJ?h=|}=gFUPv9i8(r~rrI#SL)l$#FYa?rqIr0Z74$4xt_> z*L|RWAhyxr-zG&W9O7_#W7Cth2fii%8$@Mu2pBowUjw+nIr;em0Zkn7S$4<0a?3?Vr0b8EYd=3~gzb_nS|AX*>1UI5NH)>% zViZy-0nYTHsY06=1As)x85r~T?akgz8#W%568R5YP< zK+%9a)?N77#Ge{F4o+fca7{e9DT1elCQpU(0HhwDkAJnWh0XNsL#_Z*jg?p7XKRR$ zP>Pcv8J9jNK|r9lfP^Z|tda_#t*vdSLJ;UNkw)-@Hk?g-X(5nWqyH}ZM zs3EA>JIk(v%J8^83#q1sTv+w?=$)bVO%Qr3@;Nb%c5d(o_qZE7FuIp5%;^bBAK2d$ zrFVGmnCe;H_n#lN(0Q@kn!T-OU+9w_a04{ate9z>$4}i`QJ^#ix<4qY%6u63eNin{ zxyL>tnio|>9_aqr#MA-#|wDjQ^2^OXKi4WKpqENr$VCB z;&}B1B8ZdK@r8bdyIWW-Tw7u-ZX$E;z2v$YROYOnf>&vHS?nYulKx9eiO);1qN@|-BINt@h2FgKC>9Ob<39K8WD&v&tv3I1FEcH zY|U34luc$|OtP(%?$SzOwg_u!D|SCramZGR5v1gBECr{nnX1jjd7<`c==l>5uqU9r zNI3@}`}ac}UTf} z_%qyjb?BR%^7^n{0Q1NK#dlBt{Irf#-!QFh%Grei4z+u=h`YeiqoL%s0`8_7JTNd& z254yOwoUE~XtUMeIe4P5?VKBqqAxh$AT1;bUN{&cmtFc$d=CC6gpb6iGn~faGSmQ> zl7O=xKVBIpw8krJ2XT3bdJa}dWF+MB+%A}wL89;*Tm|7gP~ePy_k~|Z{4HuEpWO#C ziZR{;Sj|}T#I5r7EHU*v22)RbY!1+~sE1$MHK7y1Jsdvv-q#_VN0SO7RGg;x{XDBu zgtT%m8-h(gjbfzn`)eIO5PZHv?O6vPZXZC?w1wpehp80mD^d_(-A42|Vf_*L7Ibjc zK)wmATj(^7v4yTdM=;mKa1{N_6qqS=oU%jT9aS5TMyi(MdvR!Qeix&lfDClTR324ktPzio}J4{skm>h;cpp-^p?TH^%=0By8>@wY5Y{aJS} zhgR1%2I60xe1CXTt!{aa6YNM>WJ7a0?^XY{9c#?A)b&lHSL}tq$cUT9Ia5DgU{MyQ zeA~^*^;L!Fx^BnLOpINj7c~l-HLWy~qm8rBan7lq^#0h@WxVJ+iDzu6cY_h6cf`Q^ z*EL8ZL_^XzfelG|ypHiXoIHTH_*XK{^2|d9t0pEOS9u>|J^VD$Dh+kvi82WJ z^0pQ{%~TDuJ&Ir|g$j1jON^MmK8I!uHCrSq3}QZqbxbsQn21Vo{WXH7kadGtg7Q$3G^Wx_c>IQ;}0YKf1Rm4nrA`)7leo~0U$;LsV zgh?MfRce5Wp!8#LIRT`7qRMcEB^BF=Bn)ALkn50r_ijzTaE$->qq=qOuiUYUC#2fL zjkjNkAH#w}0eXi7bSdyHc&Wq3tNC(091e+Mh5Qs6CcdXuwG!cC`H7Ikof(5fdmqY| z4GH|`>K!zF)q!1Pfk=h{*wnv&C>QrY9|w>E{XNm1VjQ8SUrq!te^da~%1D)=le6=F zlyqeL01pH(lkfR1GUjnzOcg^T}Osu<6*x!LM4snk_!FNnb3Nd?_K7D@ng}hFNi6PCU ze77++wC#28O7K8k@KA_s78{Cuc;!TTq)zhbrB&hz-n?Z?Kh%HKE%_?pd}UZJ#LgAB zE(&lOd=;5R_gNg~hCnvNzLfh0Q6U4#LHQvM*$Fa$>RLuitE>Gn+%XMakn|ZS`m)}= zBW{lk*}gV6&MIMMhV1Ek4vKYotG9_{p&M8V0hhXjsLtHbL#7VSZ(irMVe&=Iz1}s87v~-X+<2?<>yJg#24BY1^+6?fv%mI4{Uoqs z*-~ij58r1iZE;Z|^CxWuUi)brJ>zTgq?(ic%hC-@E{L6?#_$XcBfwQcF~V?p`YJml zrJjZh9DRX9h8`chFAZC1V(SxvEt5UwRuvnVk;=0Xro)BY+>=zRVuZt*09P!v!tj<3Y#DiHQ7ZGHlP$0F!X(` z@9-XnqL%0blnF|#lLT$VSsrB69TJKuEI2<1eJDBgfPQ#q5=$=@Lu2nWY6eS7%diYJ z03#P+%!|NVB2b2hsLfKeb%c^Y4vq39xBNg1;7D7vPfB5NtwXYCr&R^rzZsCIb98JN;t}tnz9->5z9kXU-C#)( z%h?Znl)(77T%8xDt;w(#{`JP=j}=%}Z?HRXgC88*OHaRFcY{zuDX}$8xDiT#MyR*M zCzGbtan)lyU_A*M>@Z8p)X$7%OuC8{q$)hh=J`;*+T#)ud1~KK{|dr0#oW}f6-5=x z;BNC%QZtGwX`OGLYokCuocn}^ub|Uq_>=F=ho}7+Db?twuGNRJ?7Wk0&>G&7{fK4f zsnYMXLn2E1Q!#<*2UXC%@2$hG!0Sd3lFTVgw`I<=ccbN?z4n~aQi5E8amXODC(B84|oA#@n;vKP@o{nnmMr zUg*j|MYth28IvSr*n|Ap5WG(7Hyc`^NNdsGf6C9-9($_=DbnS&hvgwvR*g%q^-?+i zLrQM1GXg1H3NSg_esE~0l2G!tx>t_;S;6`5^t`3sSAQ^d-#p#=m32l8vA%rV(4fjDA7|0st|i-$n8ZRm`$v6gM+_!IagSAp25N^;5ZB%uaQ z>`D$Oev1+~>{szCy<#s9;vi4-Ur0s7DcWRgV2{p*{1rbwNP&1kDR2*{gYuky(%G9> z%15j!>7-?3f{y!OqF<+j;{dv!BlEWujDY1|%;AmPligVY-6jbn1rh+$-x|o+v;T+} zor`X!RZXt&`3@NAG?_Q1^2&bHG$M$cKrh5AkYQ8oOQ-Zj)lHVx=xu!qUUTiu1IjYN@KNm(7>{FYac0rnn`>IdO3a zw_Kr?t2_y<28J`Jau!Rd*V*2dU;p{T@oUe*QW_jR$MrBDSxGzvkWWF8P`meadh1~_ zpzL_Nc~r2KE90+Ko}kh`_NXJzgYR#g?k{{{-R;>u{yU`$xjag58U8Zt0IZ zR>3CJxW=J%nZf1TgoNs_UH`jnTJ%Jc0Tfx9{uzjL^$J)j~i zDp>mxryzp%{UpN%sEAJd&{PzuC{jGn-h13!=Yp=RIf_--~&nKpH9~ z^+0Zm9xwX1_2<)dFb=sQe$*9*`VlCS@j=Pha^l3VyS18|hCdv3$s?2PQ>2UHuzu(W zc6LFUjSS!(c_?-~Ho`cUg6_gOEzy&lPxDKn{gqV>DKdCoepI!MqT#ktCOWI6IOaea z?$RRL|5XPca=DWIOl?VLs8MgSUS}&M*YmPYn+1KmnELr%#h2j>_T~S2S#o6SI543%MwyE=K8k zvQ6*R1NiqcGcsFA#_UuT`4u)Rp)n_3`2x~K6qAW2bL;5X*#nD;iW-(YuCM7!&Yb2KJPN!I8Hxa#6F*hhVF~3L z&Ifrf!wH8pnP;^Z(@}xIm@+mT(2;o4h-<-W97}n6UKEdZI6ncR*!6xv$JzAxKW(nM-(y# zscCmo@V4wiu2rC>&b}6{^iR8d|iA36hXx;b`ppz~9C@3DfZVi|38 z|K6YUxcq{4fLgQA)QSD|7FXF93Ph_a24AL&u77MXZR9Qqp$p$`ASvnoxWxn9If85Qm)W zU=a;ZP%&VVdC`3OY#&R1WbTspD(b*j*Hvx0_3;TY&vpzpb{*Qk{}yIwF};j)>o9Gpu0Lx-q4LA>sV^xLs|h)7G!KCR|zX+~ke3)*TDiSXepio_-LiYemdepPv2t z!Wo$>6dcqT_-Y_P>-$%%izlNl3endwn^u2{f{a*liB=vE{S5i;Nv<&5fuV7qg%s7Z zEeB7Z86)yB@HS)=$Z-oPJb|AP#~TpXNhlrVmVgli5%GJM58xeueL-w4Zb*E@q0EAC zL@f1{YxlS)Qx8s+$sCw!XgC-9*!$$Vgb(*IPRMOqS+`YtgKXKCFZ~x)(NdoNZ!QpR zI*z~UzwB_2i*Utl+sWmzEt?GJ4d>pzeH$AF?KZBBSNK0$*hDtn042rhWmduqEh^|n zxQR$f74dhKcr82vuR=uv$qD~>%~xLjma9}zn^mXpxVdA*qBJv~&VA^PZ941m(-P)% zg3iqN{`u}sc6^^ z&mC3cyI!qT$vMX;+SW}?A9|YZ^!g6g4R2lc>~}8 zo4XGpFR(^U#Vg;r*><|21>`6(Qp0(m9K_ATlT>?beCGvgLOMH1adTI=*8+ORe3vS+ zV1SH$u;YhFNz5S+?6KnY^x3$U4~PT|2(q?csn^ed?m;M+iJsn~TOip~uJp;LbNP0) ze&*M&N}6WP?v`}r09gUws7E0oXMf*GhI*ehuM0NoWIBz%&rK<~BuC(ZO|#4gQ?9tnZGL1!v{hp6ta`tvVm;n`(_ z>&Nhk|<6Fw`3$ zE6Fht9q$a~iHW|KbI)X5`m_E{)qi+|Wdna%dEQON>K(nZPwJW3PJ=D~yxhc$IM?Q? zM)JiTzwY1hXqSA#YTRjxt&%ldt4x9V#+lw#9tJM(rJ`ZpD*f|(*~@z(e|ZvV-p8b~ zm9$=3HZpcuEQ~E!a*c+|(Z8h3`$kil&U@RQXZOBR(RbjD(OmUkUtMzFMpv6LKa_gV zAyp}Fa)2g>MzWB%`$FQCLn60KUp!&e&j_X2H*WoiV*i~ap+rtkMl-*nSFriyb1s_f zSJ(dDF<5u>>p#qAO|C9YFn6>8H!$;AuqOI$&}f(!f)b0gnLuJNXGRfXPD#Wwh?{%} zy>`z(19avCaHtgdp{}A554s3D@;=s_!Cxoq3ncd_AS_I~p(@GKSSMF(r;MKI`MzGN z)&cY3HwPUY9Lntv9ZD@y{~>-FO~Hhb^~>yn{Ctw@L422yrg#k33)b1;tTEt6fn6Fh z{~n)S-H^B$>K8Bx6o6ZVG5dD;^7yK>KBFHDlQCG&DthVhQA(UViCTRho;`ldnCsZH ztzZA6`&dq!>c}7_QHj4Q;M^L)5`O1#<{xfXuJFuEF|Brmw+cu}?NU&v(9+K}D)IT? z)YaKp_{FLD($&a0=BGl|G$2O~NJvBy5*a`{oBpY#xso+g8}kc;zIp*|7e%;qJ#Hw>!WVXyqQeNf&oU^9(_Eope|_rq-Sb{Irj_# z`-fFXm3vV_iH4jIdaH5MH6K6OYZpbLEXKg9lW`hUm?Sn0TR;4mIsg)wwXG>b-lSHyN5$dt$%h z`5BcICVGXRVCxC~RqWeP`r`N3dxyRTHD7k~>po}{_rRFpO~0U#>9eBAZ{9&kgXgZ# zl@Aa(MzznxGb@Wx-m&O!=dVSkn}&u4 zQ8r;8V>oGp|I3hwhzhW8m^JurdT7IAv4!?>3=)}J%uG*%Ohzr-cW`w1cE{&QPO@!Z zWVhb%6G+Ru*if494dV+4gd9Sh>~3K+KO4#b#?!xY+RXYeCs0QJjYN!r;HQM9#5I}u z8_a3~AM66}LoE3!SHSmUPUkL^7vXFq446nioct2eAr-WHkSR96mIaU%`Tr42I)QhL ztf2@eUz7peGdBnymi&MKX7@$FG66g9>eVPlPNEwO@lE4fij_hRX+!Y}1LApwiim*z$UJSJ)Bn+So>KpKB znZze-#CzFqneh7c4BRu>>I=YpvR3eT?b*pQEa8b?stLJz(54z7e3Zl};|Aet+lx^akdJ{s+gtCtq26wE6?owH2BVjMfQV8#e(5um2~9OI$nx-&2?0 z*^8DqV1?&morF}t43SB&2AdGW|?o&t{SzeTFiC) z(vV~|p1+>zhJM^Wcl+WK5HOFf$}pbP2uA;sLpzr?qu)$k-CN;H+!A-AErJ`K3qKEP?sdgD;kkD^j4Y`` zIu}iz%)I&S0c}%*!GQj%!A#O_^?{_tZ9fw<-~QH=!tv;Qj4QZc^p?SN>CbE_iNceE zS>J|^*J=c%unl(<{Mq`UDmitbE&K37WQMi(#xp;99zWTtni*we=KN#x|95x8P>Kld z0ITfcrr`501-F_ik=;9PVk>y)Pyk#v`>MNMDJt?vdc>zGMGgFeNjsBL0z{`m5HEi+ zT~doX9s^{1r+~?({P9#(VOh-UMxQ1Pz9E8SItXMitz@g_f~4mE&?1S!KPqWpQ9jyJaqjH z*o+X-C^pcIWx-s*;&}@{%iKG+3@BpVdpCzIVRHS`{5&>)*Z$2MPlT+i836{}e95DV z&x!?4DhYC$c>d#q;DrH~{!3I75O6;OA7I#@53SzM0KsC!SA;!%da?VOI1qLbhZ2PK z;q(lKa*gFs1cJMI`e}?11-Lw2BM2}#0*~6v`=2h%Ros6^UceNSe0;+ds5MC;PwE!W z**_0TN=mZUTiAhkQo=!fZ?XsCU5%gV|Ur6ydcdwiA-15bii55PH?) zj|QGPu^1&PGi-cVjD|jWat1XE)Vd;Ewv@z=s_e(-jZj>of~m!14>X-bLO^_7@UShc zt;+yE8N$~M>>>q*pR$`rc3WrR!H4UconKvEfKNQJ0V78zH0_84f}Qal=(HZ8SR!IW zIE*A)GU-eRv24L~S`dEm7%(v`vihR14p98}H2a->*AtR#iBjenq;q?`Cgpy(_if$d zIkXQt4X8+e$*wJIhw0(QY4ejk9)mH?vOfDR#pA)D7OS%imMP!P?7aDXLPf&{>Ct4T zcbcJ*sdO=yIfR*ooEhJFwu{Z}*fXZ>*~@LHbfv#$`5+zlT*~z$^7k)k*Ec*_iYlb@ zlu2)fgM(OqOwj9~yg1J{oH5U;s@LS3tugg`nm(RZUxHt8!-Mm2FjZ~LnU#@0 z(`kRY__O}+9OM7u3Qm20!oYBHx;%)bQCQi2MyozHP0Bl}p20DzG@kXL-ay>8*vN>A z=`Y6Jbu~l;A*3s=jSMH6#lD^AdB?h5uSIq@bj6I0juToF z+c6XEU7^0r8e93PChyzu)}1mwG8=c@z3Nc9^3`ObYE%#2zq%=FyA?J$X^{7e1$6Rt z6YfYXF^tEModOwsij;IbDsVEFOh%}dP)GZx2bw;C*l1_LcZik)zU*XXzL0kI?8nFb z!Bk|@9sEzZ?zno~@vne5#|s}7=8GRYcI*f)3Y;P`@LDSe=i=Wji(H(X*@kr#0vCu*k4=&1G?<4?sApJ)#`u|B{Tg0(mzaVX3RD z!$`RPsD~F!iu%6%Lx0#eJZ$USgN`1@+II4b0HT65snF7d?`s?|SuiF*6MtG=eH)m7 zbAZ@Rcm6LKJq5Xj@t=J3r^N5Qyu~0UI$Blv$}s4{VCm^i&|Uw616-NKBJ_j?Z_eGU zaw6H(5LF2~bRPpuznO-H@1Myyn4r)uG~dUF4ZOL}0w+r)$uCJVQ z_O&&ZpT0g@f8jz{JkR^7@?F30IzAd@ZR>FLV5lnW*Xkl(;Qf47CY2|4C6*OmEjtu_ zG5(#EN$rU$O&qBylgH#4ce1hhe-Pc`=%}W#edblg?B6#w22yrnCY7AX0GXe8r^GzD z$MgW-M{@o{-o=<^uPK%PGP~j2SSL$P(C)k4m;WY9Ph@c{s2M%{scj>=}{0oIUi2h|@ z#CK5Hdq8OqZw<;`|8589*9y3lr6eUA0qyQzg4e+L(DqZ`CoyP3ieiks%i&9cT4YD4 zkcf&`{1w6@kCEip#L*tZ9k>W*KCavT23|%$90SyD2xek!WW$b?xJaN$xep=(o29+I z9sWz}AbKey3IVJ$k{g>G&i=z^$)1E4LdQ$w3y@}%b#$)ceJGPH-rmhX(rHJOdNI4|qAme-uKAZ~0D^!0^b_FGMvqFxHQjxt^=V z<;58LM-~$HutOrGg9KFfYjjivv}Vvm3{wld#!>r{JKFtER>j5EG*-5t4NyYkDIAAD z#1HZa=zLFS6%(~D?kQ{F-NYyidVdrdfymmx$@Cj#Kz@<_s{br;R)AIwvW_Emt^7F1 zPC*ppbma=sK|o*!q`d-mLk4K9F=&*?<$J>C>O-PL$K#zsVM3-dAVkS}`}Q;3@?OV! z`V(0UZpw3xXFMG6cakdzmnO-SC6BhFqX9F^Hg(Zec&G5q=r^sv_x6E0BI*|iS$aoC zF1fM(fNqwUhT-}9!@~gIi8BBS?K(75B=!UC4dlNYY<4T$?@YlY5peE4|8D=6&xeH0 z-g@$!_NlGsv;w86kq^CMXHfNKgH4+8@2*};2z@)e`Edk;kYdYkJ&RNF_oH4W+zBpK z+Dknb@o&Pc75L@;o=^sQ`aoShHIxB1X^oo~dbV;>p879nOLHeD_r2yD6pe0w^6jP- zZjR3U4m*G3$`Rq)k!^jO7S;k>f2iHptEnAzVy~^`_WUu!xvzZL^$JHFXD8+uZ-!F7l;c**|g|a)9W|x~hM3vnt&gq`H1u zrIuc=EB1AhEc|?1_<7qqw$NqVKn9Bo%mxxgef=^|?*^CLu5d}Uo$lYy7PNGOOGBWv zub>49>AR!fZ?qivQ`aH0W;tar!_2Ci`uy6n#rLA`4y=>PSDC(towy@hiz$xYkFIwc zF1Nnq*RevY-(K<104pLT*@ak-SIZ2^e4kLYkxn)+55-c=>o}iFT0N*Py1Tm(XHo7A zRkMi0$Y!Ilx-+~!zj4EYZUl;?6p8)kH#_fXm6>KuaH|3` z0%n#B+CYzH@=-7%n*Uf9o0bJ%Yft%(#rIn>z8>BcL8qJ4S^wFn_u{GjU3Jf5|B9x$ zFl=y3TK;0Bxx~rI*?`JfOIsTkLh|^PI}hLm;3UWH!W0;=Rm?!oh82jWhS>?{z|zWO)MF$|k#sLxR^sidM}@A*_M&7Nxi)rSuQUEN*;#O!wS zqwmV`@QALjv$^2OhQ_EMCPJZZcL24{liF@opQ7nq`dZa_JR?*+)JbD4$F5y1xAfnr z=k`j_CL$uH;_BLHLYzj$^YkdxxEzD$zb0rUPr7RFSbTI~HutyhnX1~O-#m1=8j@+M zCmiLL6rO+Ocq5cqcs~)-{5rYYE=IY1{OTwa#dXcRU!Hldy7IScLk=cqs;VsNe$z5Z zx>b~`2j-XX2AQM`y7-wKV3O~yxl~>hLf0EhqI&w=&~M~}H+E_%L}mNKbM!Fz16hGB z25^*8?NfF5pitW$P$3DcPgHQI9l7RY(5r;@2cy9^KDe;%ffDxLiIz_>Zq~k9uy8mj_ zJ3RbL)7Dzzj^)q4W=yiQ4SpSLI-oFrX>IRiHMPFrz%74tFGY12SiaT^vp+%?k*bi& zQYpkzsmBP3^j9!={PT*+J0sbu9DkLB#@c!6mQm3@6gYQtf~v9pHN)Hece(nWo$FZ~ zckz0sY1Hc*q_8V7y`^gJOPd*2?f?1s4SGQrWJDX1J${PkozYq5IUBNkz9*OY-@W2n z;lIZRIc~gCy3Y2{C%CJqM1I#4b3la~uRgt^CG*w;6OPJD<#kM3O>t619>jsAnTXHl zzw*)Wjh5-JmI-iYsz!%1o;z9-T_EHc7uS4Q>j+g3>Jus^*S2EnFmGA}QFmH6dAPeX z7VL)0pq}1Nn22x%JE4x+p9gDxi_mtY5itJqU7PdmF1$*611b_d7ZKzW@K={_^DJ4< zU2-#Fa(b@*>#e@jnTn8ubo)m5HyYc$m&W+c;g*EiyvD0vezZ1b-yH6l@}tWMP`#WL zh#K@Q1Oa_xdwu3_q9$ZHl$xy{{k5jX60+s8a{B)QRPL8Dp5AwW=YHVx+vk4I9pjdj z)$_`~5zfbi8F7pUuJiaZ>=KZ(752t8H8p8{k)_XBj?rDeallF8Y|?z+Aw{aH!syer z)hyBD*GhBp4$a#C$!c7lS>e}+xF;sBGV(x2NWkW~MpOJy>Z{TR`Sk6k=zz7PCEN1J@d2RDh6adEmfd%1&{wqF;0 zaUs<+sU`d41Dvtk{{tHaNR@nHQm5ig{~E)0JyCk&Mk150z_|Lk?zxT&aalkWg_eko zI;Ly;>`_yo2eit{FLWK2hpG${+#9A<{;i_;em~zE!1WJfd{Q{OPS2vPueX@myzk2g z)zlVx#8tVui4kJ^`?VhG=eUcdk^j=dPSWxM-nM?cEm&$k`m?oXU?Q_knI82gMck8r z*aeiwcDoqUpSY7058>nOk!>&PNNh4}pt<sv8&^>k?kHCS}=PyloU;{#-cZ+0y{I3m<{~^y`@VF*t31u>Ecb zMWr$o$APx&LBm&m6>sl=E43K-^YB?uzcaTli)Is z3AwquJL@g)^{K7c;JPRA3F%s1a(Ki~GIy1BUE{i~69LF}63r9h&P%3mp?1QQ`)~Nt z31)fYK|&t~lg^5KM~thX3(u|X>s~O}&VKAhT%7p>dpO1rzj@4c1UdDfCLDqf2oNrT znPcE$!70X976Tw4=%UaP&afk2;f%#1a`9}ax0sk1D4lI~b_+v?@6cU+6u$Lu)P~n9 zzih57EUDnNTs2!dqn_Vx9QLZ^S@WvbWxfLwb3g09HU6I#;A5+iX;tXveJN1~Z#>SB zZd2H|#x@N6t@3UCal7t0GR%kG5l64eABNq#zwUB;uW8i->O(Vi;2&bUNuWrmp!o6F z{x~15i8aAWL(Nt-Hf_h=Q)#1Z(U&MGUMRh|9MFEo%`CUWa=~+6D5;Dit)-RUF#8Th z8d(6iCk}=_Gw^0o{~~ixm{)eH?_tk{m)ct{x@>9hidu`26|Okpn*HgI6F)DS?KJx= z$EI~^VY4 z)$Mc%{KPnx2lch500&iESrf;2}k{W$@n_^_^xxI zQ|wY(PgvX4w|X!HEg6Yk`DtB}t2)%8^+*;UXu~7x`+nFSTsm_{TA*bCuyh{v9tFX$ zfl-kmbW~zj+}yZeDharcQAsr`CkM7{Z?sbEL>zxa6QT}8L|8<`#uFgG@g@B@GGE~( z!4v``vJZlT2Qi=n#FKH?Akh6#r7FB~IUgUN7PDMKJblru>^co9iKTTWvf8Vw)Jh!Q zW*hRJJPFp=q?zvdE~Nr6jjDt1;w7Q)M)|s^-#48v#IcFQ(C-Yo2>d{X%hd;SYO&I+ zkfjEcC+ej_FTJmLf zE5+HKhP&r~s10}5CZ755gw#380fI=N3XB+Bz=&A+3f>1)_o-iwgMjFT zBUNg1zdyM;Vc~*d=yKpCK}$JChlwLlr1$RKyNOFn=KE>o#IpZ#*}uprq~9H{@7G`d zKt^+Q)$=gBF{cuTe6!qm9UEB-m$ar*VmKek zRxy(VTL_gueUPQ#2D8Nkw5}iTe-TAVFQyxtlsfW5qn{eQE!UKi&~z+iXwSW5{i3q= zH4n`N`norVE-`zac5F4c)I6Uv8E^j9M`)jkPF(G&P1|nV$!0i;1h8)-b6LVB^~s~2 zo}M~h{(tZ7=e8JFndGGJZgmK%Qc?N&?;cVy5_2o_ z@ecWd4DYI7^>4^@N-u@4fxq8=Vt7+W&aHcQphke&>DRX=< ze?$tMr+d;zBBLo@@?7EKiu@E~<(y47M{`;;A5H<#fFC@3c-`gV4n~@1eN^i!e>!V<@mF^#S&)>A?D^T-_pegiwyjcqyLYtKfA^=_ z@2?MBxy8nOb8rLtWnDy>w{bFwMKEqsq94(Y_bpSx2UaP1NX1=zwBsLo(mREuRmN%AbgQ$S<2&LzY0<}XKd$YlIR3%Or+;Rgyi_@Qoj-GfnRMrG%|Gd5 zhv_2e1`>F#2p=mMxtBYCH8h*w_!Y$mt7u z>ew=1;4Y1860SnZLEIWJaB5_!QR==qKoE5ZBhsWc{5!_keU+)7)eJk4u3=49ZgyPm z9RV8)wZ-Y>tJeEllnnDO7%YUA_%Y~C0?Cnviw<697G5bz=?@*vohA!F31B4xAS}A1?OR zX!G0T;6Oojj?`1tLbU5|_*L+ECh<^3bW*s(F~u_O z|6L;_6V_`6QGM&IFkhw*k<&I81U?Yg^(QY?|K(hACU;+1Fhg7iY%<8!H?X)kUOHe& z+9~%}Cq6=Q1z63>!FWm-mJUc6u)jtXw;z}+xaBP{3kEoE2tRTB6R|_b|LJ!7vS`T! z#4+sI(*!hx*fAhJ`7HvI{`>`J7UmKd85;T)8!+%ejYm^MFtHpf6=FsU@v-2_cEHVs zuye=fNCLElK;kwyEkr{S1;)X0dO-KExA!W?Bqh#G*YWsIKN{Li?zTezg++`Vu$4g5 zUR_tW9<~zXQ2~bth0Zo;^+>wXOyqG|EuQb8A&P&8S#v{W)5ku=SN>wL*O@tWQk{l% zrT6vTgWivyEw$8^ZxO!i=CkL)b;X%GFWgZ|^44zJFHYv3oZq^wmoI{0J;<70TBO$C z!+W$VDsDu{`k~LVmV1&@n}~IM#M^`IkCuXMG>N7_?P@~lv>Ut%m-CTKlF#Swu*Sea- z8G}zrw&@xC5+dv;bP?2K4kuR}Kx<=aEuY*x5fnn?V({tQISF}EUY;oUjVdVoOcs3Z zNg^>Bd7ib^8(HttuBUvqX|wUhIesQ1g|FTck{@hse~iEPO@3~TB4A+rY2rD{^73+| zOKVZ80m)!29EgXZn*d=&6>V03&qatK(a)^gth9cc@wVv^i67 zQPB~k>cWF$7vxElX4r*C(Mk@8&nqRf3Kn;rVD-`MV-nj~^B+)iY>nHsjrs!3@rx9; zPR19`2y9gTBh$N5@l5b+dDPzhgN)ad0^>Jc>o`4N8_#h_pIx$3&U(M)Ag}Fj&?^oR zf`;bBM^s~Y>CPQ)>vTRCZ9HmKp?7p;y4hiQ$}nd|#@AC|Hnc@Vc(;6A3se*4a|XD( zsVB$F0 zzAKU;E|svNU}Lu_4IWUy^NjsP5H@FcLpXD=p^f31i5WIPOY^-0lkoMha%yf9GHR`B zX95x6f9F-H^!~)8oI`^56`bA=7ruYG@I6$+76KHpllzrV{}F=-wgrAt#3&8*uHmR2 z)Ga_A1|U44Acov%e_8<3zj^b<0J=nQtTrgVu*@Q>k-6r$xz^C=b8-%PtGiDWk6=$~ z4X{(d_z@VC7!dgd9s?foHn@q_yED0;VmidUaVkSk-<5YhEG8xfqjo(xNl)!@qsq{u ze+^Wdm>|H=1!}v{=xB9lT*z)ZnCcN!@b+EKzxnu%out(&;iXx-5jRBr8}<~vWc+Q^9?ZjQW+$CJGEucm&zZe zW~PB$4Y#u_J4LXgiJo|vnc>+J{dB|v(wdm)dhh8`Y|j}mm)2(#QMI4M2D_f;rmoty z@890Gh}xNcwfOPvZQVoZTXD@P5jFc!Zp{hm^ndj>F2|66{X19gBZspc9OUov54F|* zqGBKiIKcF~lHXvGhdhIPgPA)ib!@O_7lg1TV!8mw#F2|JA#qGszTjnfyzqwpV~OX} zs@;;Uy;X_tuI>~Nc-4`q@#*jH^%^P`RsLI5qYwBno$u%$7&u*}b*}_BYa^ELk}+|> z6qM+2;Q@eMQT}-Y%qH0f7Y0bUH(_5F3AsZochLWVgrKchnxV(XNH^3KK=);(x6ZlNA&DU-+6eBdC0YWIhN)7VTBpkXj-^!(21+#- zt^s9Z0j~mdt)N%aBu}vWX3-wkW9EA*9$(^8mG1m;?rPD)-S^Jk*+kyg!d$1V4146x zoQ&$U5OcL}`aEahoO>x(&QdEgQ7MZnPd7>PNP2YuMk^}Np-a*#qJCCkL1n|RBq%rc>L6H z9yP90+`cayRu`JR?|1u!%BnuQHdVVXaC;&B-(?5~aVGs7Z&=+|QRJ#{;>3YjKNj22 zi0YEFCFk^Cd^=^>RZ^6dcmA-<#`VMPqiqWp22Q3>X}^V=gdg~=$4Axe?fJ1)CKj~1 z*nnyDUOst`Ala^7uS2{i{kF;x2^e;4DKJv=Ig7aCYQu1YSRH_QnIf1PZZh5O%p`ZL z7?NQK6f+^2Fv^(Q*yw*b=P$_(X(JkciUp=>WywCe(zRda7;Pb;F zkD?qpj)<#0?H70E==eQm$SX2S%|nxW)J5u*^fSYO7n&`FhNeqT7SAP$k4yP5Fvs#T zcyEzkbL@Jz%Yczx%v(*{qYUZxb9tY4mHF(`XOy_Mz${r7D{ryE(~H^0)$7hgwbg~m zl>lIg1j~(9Zy5h#ic@lvTQT8rnF5aEjQkz+Dx17TqoYb_1BL!(Vh^BwIqQG*^o=M+ z<>+OB4%0``KpEeU9$n|jA)fqP?Av3;*4HNFCLRlufprg?6A`Iqd&KJ$$s-YAHN&DCy$PODSuzm`%F!Jh1bl+stSMWtn%vpy2*+_6Q@dDBLPFa zSFqXwGrDsgn{l+^%DRKkfreTP4N4+B;+B1g_KPT-u!$Im0TM7c zk>S`p_l$UZr@_SFYg5HB2M58)(%DV8tkv(|Zwyzif)(94hslwVJIaY7DLIWeP$FSb zhFVUnWt^Nepol^zXo16kOQr%EP81+Qv)$R_eiu~-OKle~UW|d%_Wo|E2;AXw2Xq|P z#H3(b;X(Iq!J?35Oe=2lUtGzaDE{_Td`th=ua($kH-viHaFWIKdNeblf1Dgxw{6pL zSOp$v+w%$6(R`B9Rj>Ep?=#Bk>chZYfR3@iO9%>COz#Je-`;Qz0*}OU6Q-isKj`U+ zzB~cU03<2>!^5{hUI|07RDL9W)3$x= zag|~}@r-Nl&i|p#^P-mod@Pd(d3=6(GW|SX9T+HocV>F-;<>c^uED*3n%*T48Lw8F z?!Q(dhnR@v<>hhs@5SSb+ZBwf9VvPnyAsGmgyk4(4J9q4cA(9}84F)r$4j!X_}Q7+ z^}FT-v^!Vz^h?!NSJ&pwO-c$$rmC~JM8(|Ced$%~PWcQ)*JgKZl8a9AXq0pF?L zHH~Kw|6q;U1lWs&gaLyxN_dZ>+-+g#5tvM&99hus*s3L@J`@o}*QVnR#>R3-ik&A} zS;JVMClRbD+|ll0maTlRr6%FL#HAAO^IkxJIqD%2QzJ2XlQkirL1Y2|P^8ecN5iD)4{7hkf;UIgBNx%pXKgwCIAN2tt5ZO6y9W z*b{Gl`rEf}8(B7DK{J}jjSWKHqKyN9KqGbaXFo7^8?<5ET%SLRL$z}byZW4c2V10J z0tqjrT<=vcX!)w~>w9n6;@O&RI0*x0;Ggkmhky-4? za@33$~;zV0PIpRVG33!M_JBV9*~&=RR8H2Vl# zXE&GKF8H|d`$wvYz3Vo18((NtW_M?g{&@#Sc&&lD$39m) z7V^{yevNzOG5h{7{Mfc(Gm~t(fNMMfik~RN$ub{|%V2)&kEJ1!%>gwGnWfj#YJ{{)dcqW0EMfIlOie{UeT$GQi3g~;Lz#NRk3*UoJ9 zR>n$9!&VJx%GcK?%W9AgbJ5wE9nc5LU+7+Md=9*rhsu)+QQ9E}{{X)d*y)388^(qQ z*<~goz8|JjrNqyRNZf!d)gbs7DolW&PuyAIElYY>JkEzC3TB|EdIO77d`i+9 z!ZRxpcBLI1Oiv{d)m;mf>be^b9&99HSny2AAhdn&+{r~;D3(H1p*zLT*pK~uB7_au zT*nDgop_`WlV;45YZv^FVJ%9bEBx>@(ePk?WGpiYR8IUBX%l@8fF@+TzIY#r&m;7K zIBlz7{ES8x4!hmjH6`#s#IlP3yk$xkCrV%-W$xtEOuTY%)o~&cPb&;AQ;;EsuWn{P zu{+{X#7Rgt2dhmEo!=$EV$;K|`no`Yo?gLv4}^yTE5dVUz8sGZvFR|-;}@VQ+@<eyTcmiw8b(Xhx$+3Fv3eTrGUs(OZ@#ld->v)Y~|F}=FgSJ|~Bp|shsrL9p21u`e z?-p}}Vo#88!hi6=U84-P^(VCY$DR*OK6j&kn6ai$Uv6LM8^0Hh?(Q9jBq*u+buQ-s z8rHf8S!Q?9S-ThVPwEy+vSl-!!Ri7_$KiAJC?dKMa``N)R+WQHtWE(3%XRP*Zr1m*8M=!0OeIo zV}ajRapfn!ybdFW57I$q)`qZws_JUyVkcnI-+wQpt)J{J%zPIOa#P-ZbR#xaz5f=d85Rmjj|o!^Y+3tK@DW2xa&qYu>LG{yf))!u63z zBXG6v_~xVURD{dk;h{3b?2N-?1hUNpSaS>ov}}VOkKiF_(HMct3 zv7N4Py@uJ)pe3k|(D0{K9d|Q)x$Zv}M zLurrK7{Vv5JDr78pN7`>WKlgl$r)l}#BLiZ=A3O|`G{NLC5!NXoGZi5o%X7O%_=Iq z9fvtN6BNzM0BLq9dCqhH@7eFNM%PPZJp4=u2Wa1|o?@>OH8k|E7fqd8&+7U_#42EV zQpooOyH4uT%wPupk(7P=_B_nIy=&I1XPdlES;X(%w%;wU&);4({r*5SPhvRZ7q9ON z89qL~syXb%r}hk3MjVLc6V@02vr`F)9y2s_c4ZBkwbDcJ# zj$PYG6lK_sMX1e;I2Ynep?D#h1qw9Gdoc!7g~-wg@I|ITP07XWy{!Nu(1AodiU&2-Ck;3YP?pW zCOluV&fPjVnpS}N&k8poN;4#DE8yGV$eF+2yofD_i_j8_)cl^G7tWSc!+CYTUGLk# zK=I|#5Q(q!T0Fb~^6>8KS(%UmCcDdXQPaDAaA)X}v)DNQYT1x zz3-;Rf2!n7KJ?}Pv;ckGU;E7)xV?{v`N(9-1=fj4F6?WLxhTpJY<2z3kzE%$2RGYD zov;2QYBAh@HE};PG|hf-yooEJgFyOUuqHr-9A~6s*?R74D!YiCmJXMK7VOSRXd71U zA-l$lin9Z5Yzola4M`X_>_TVhRK9Q<-R0K3djsR+i9spJ@GAY)s=~ee;o4!OB=ker zE1KenC29$nDRHqX1fI*npCI+mA-Q)W+}NEvF;YDp$Da&O_{d1^xaLJqx6_7BaOFDD=KqZc9@3=+n^9e{jEoGG zXOEpyp+woSb-(HL@qCWvw*mD{O~W{b2(7L(?1026xLyKMto~^cR^8S0gs}2_r<1av zup(U9g$pmIy)yI~P`Ba9$c9^GhW+f)*(RwfnOi@5%esq>LcXjWh<1wXD?UbrEG{kv zEO!IAiP4IW?!nW0!L@w@kUfm~wc0C@D+Rv$j|`s;;|D)*!L-<=s_0cA!#u}v^-U=i z%O|g9xz*M3v$CE!wM@UbI9aG^Ied_pW|OH@W99jES_dW%pJvV9xX&msChfX@WHVdp z?Krt5YTETwjtiM?VfD&uci%bo!zbotaeF2vW^E5iDdFCJy^{|WY6`NoTQbicr@4Hw zrYUK7Wh2lKo!$SmR>cA8KZX9t8wCXmp0H~B@f(!DOv9=;1Jl(lS4_k+deWVQRrgXE5cDw}_@M6FPLCdpK5>Q{>eO82!n2Nbl(GP40ZnSz%)J3F!K z3|04f;)V(I2^OjC+pNC8>-`NXPd#cyn$$KeG>KKjg`Di<|13u-aRyRItnffbP!I(k z7A#Z84hZe%<*hBs>4h>KagxfvX*hjf90{kJ@LsdQy=YESYMLK@R8l2g6l!wYojked;#K0$m67?OV3!qq$I4UHTS9U`?J5bKMG1d z+jN~?V=3qKkg0LWCxqbXEDn08?TPpAenu^Bz`!!=Nn0P0r>E@}g!JUKP zW%ya|@0Hzy#(j_HFRi~wCqUv@K)!G$mK}lYE0MR}Dw`$J9vNq9M86pBq3H)cpc2QZchWCchv+CX4cDBFH3wVuDR>x>}8& zPk^x#;~_GfwpOrBXJGS~OV5KZ9W71uIP%~=`BMdfK%iJyFXp%Pkh06jfbjb-(er+3 zEQyJUk&H>uW6ALOLmG9`w0bdzFMa37*j(^1+cjx{vCe^u6{;3U&R=-YF$& zeddm{Xr7a;mzn%yj(wCj*9sa>uYLV?UFnK$ynq)4F!Y&4Kh_gHIUf0Q6GA=L-jy7@ zuqjDDlo@4gzJ0jADvz~uG|Lj9E0Sw;$-;W3IhzU_yu&F1Ml6v+kb1m zZ)m17e#4B;p~dOHmmPaIJa{iFYxLb^V^l>%gjQ9sip zHMu=h7E}IPxa6sUKS6mohrW}{f@jLy!{N4f39CXd3bhIzK;8!H=D=2!u_udfE!5`G z?HxqR!`RS!uRhnTat)G+HNTwG@yF^vnZU5>5VUHsh}LklGnWTIAe)rbQ?#aJ(-Hn~ zc1!0bOa><>13?*IxdU;_1L!7rhIo9=$QrM#)U{l6*(iNvZTiq13U>Ro`s-M`4!eAt zdA}jFtERUjBq%~{AB)m2#<&8$(N}U{-5_c-$LPuZfNbP!Q{XFbIsa8TNz9lAq z>YH*WU7{WpL;j=O+(THs$2MeK)3Vq)@3PPQqo5qggps2vB*sR(j`aaZ`$ z^^;ab#*7NbT5G;A`q1&*>4~J!(z~fNi}KwRqIbq~F*Ew*1}u{_PJaP0N&{GNk*mxD`^ zBIr{Bi^B^Ho^)934dn@&suKMb2BR&m4b{Sf{0Vte&M|(0Yg^Wjs&+fy zJ$LKx^iYVC!tcAv-_bS_FOnO$JuV=7r4M@{SVy55^Ju#0o5Nu~|5#0V_CK*DnU9^YYX)Js<1t!HBulJY4&tggPtG|5Cm zUXL3(-GbNOqc0#02a~N~g0PByFz-fK5x`#U_{ozrc>5bdomZU6l3T8+5?{D7yv8e$ zl2r^kGkg5f-b~=o3(=E3#fQ+yeuXZ?0@Dy__&}@0x7$m=#R`r#!vwrTgS7O+bd`Vl z+VebY^X09B;g0$FduI~Rk=8YAb~l*{<8ZfoRR!iHA| z4TEN-L`IgLe_Gvlj5p=m&3zB|8~NI3?-1Zr;{5Eop|ap?36HxYw#0%VLgLU;4C-tM z;!osF6~JG&i)^$F&BtHU_vY@2?@*lm^rY>|nNN(nk8|m_~drTz_XGo+Q@e{x@s z487LVW3wMzmg~d%S?FZO<&5TusMqoaHZ#HH)Q=|*WY9dd#FS6 z{X5(H~>wM3{QoXE;8VbVE=siQogSg|PxqOCg z+ui}7cKh{x3sKzCgbd0=<7Dx11Zubg>v(t_DRR;d6SZQvfPN01M{Jdxf+^+Qd-s5% z?X(We<=~${4$KXBy%5v!i^$I-?rxYp?tKwXR+ejux^@oY8zB*O6=$fw9Rp=F(NVZbe}K)hJ=P=c z0rOz#)}Ihppblr^bZx&^|LY0TtR+db8TxNaYis5mSz$pzW?lP77wF&2vhf^|_xDW| z*}?C>ZL{Al)|9Xfw|tC^cH77M%nkH$Z{25c^VWjK_XaMRhdPOGM#IZ_(N*5b<&8SQ zag5_p%!}05FJpH<6xyxTuE&0Q<&bMy^T+t|IPM_>I=hk9BgHG6JRMPHU43nhlD8j~ zv6TIotJX4{;kUUNWbEu?fQwCaP47aXf*4{AuLnuK2=oAjr5rgX;`qWOUq)t^@21g%Q;z}B4NlvcqA z`P@UA`mhDZ?BcaZ__80j;jJ6VVGn;8J&N8-RD@-0J+>XSm}tE z^jw^#-ZyNb0+13SUl>RPz{y!TF$70#EAqbKyBjic+v=F)c)1Jf*0>N4>C5TSAP?$p+1&%#7|W@-e|Zl$~&RO zdEvr>cQqqbzLMG66t93(KDwH!0&An2~20UA_nd+aPFGhionS(!iZy}IbE_-%A z%liUy@46*2D?^STV#Mw`gphs(DoRS(24d_g(^FFi97i_2 zqfqs6g7jCg;AS&+10B0AAg;a zqu(lPYF>P+yQK2oK#ZM1;-;0NCs9oy%g*VtZf$Xud$re>8YJ(Wbe)TLJZrOMi5->U zvBFY5OhZ$)?%EYKa@z)r0Dg~-F-LC%49-2=*r!T=MZkIL)TZ$8`*y1%)roC)I!oPh z{Z8?Qb>L}IGkor{#+YulRYf?mU>+fey~hQ}zlU(r0$xHSMhAB~wrx~VO^pl5dW3cb zpp0$A{(wF9K7dCP+ziEBDo(N2^CLC2S92S?Z2a#xR}>qQrv~`5~Cf zJ+)2h@HdtsYD_GLg9ogoFg`WucEheuM5>4m-yp3W9)GX#5AaK% zMAJ(md)(%ZVj4u-L;m|E7#QIw#Y&_PoUj2E!K{RAy))uOL`_tdE(ocuOTRilarQKK zSq`M@uzm`3?RC3@F3TDJ7LH8d*~q8B9J1Ziv`_oXPtToX_(S=4w1^4juB+mVfW5T$!sIvsmfW?yn6msyX!vsy_fI!JkVk<=Ac{rPNq9vWJ%$n4W z{a;oJ)vay5GWI>9ifuhVW|(+lyt!A<_&_QfEfWlFdS6#oQvR`-J~v^{76>2NB)1Py z;>9DPU^!=~!fEB5@f@zD**xrP^mgduN0HxGB*enE+HYK5x6yrU4ZjO>>so7`-@Fe& z5sW?&`k|qqze<5q_)f|zO$fA%94qfVCBz$k5K3FvJVsNQa81_>WIiqcj$mS2f+gj* zVYDO=8J`(FwIr@Af$5iIC+f`OJ)_SgBI2Fhl$Ad=S zgE7Gs-IY{Im^DAsy`H0whsRFR;{>pBn`}33u*}QKw|bJsdhg3o+a&FCJmF7vQ^nc0 z`r7Y1$(E^|v+yXVe*H?8lV#w@H4Pn^Vb7~t3ooX2P5OK8TjaPU_cP`lhbA@)J_0=+F$K1LD=+6tm$lEji@Mb4 z@fF(k{hRUBJpOJ)L2$(^B5in@DpvIV$$dZcGCAIZh+a|rh7c3F)=kPumLk6)jze{|IU*|Xh2XL~;~k|af(xJX2Y zlg}2sA5iTc0ka&uXqRt9AS_IB+9@RPh9sysMI zFw3O(w=4~SjV0-N=q$)hNB=?0pcZhV;@qpW75(SfYHm)vlx&)!&H)G~Y!$$BfU-9A zQ@_DxM5aF5W;s|J>@i)%^8L3vQkO^yA4x}sHp(3LJSlYpa*}xfy5GNlpQEN1L^R;8 zYY?t=zPchn{t&=-P=d)PR@Y%y!Kx`r0$&Hl3naD;t;;&Nn~-t{XNC`%rWI?Pl73v4 zM6W7_MN*-#GBtoVg=?p=(x7EOUdB@RDp^VDf!ZgX z!Tl?Ex^2+CM@e1@K!w=K%zXO<$5;Fph`p-#{G+H875ySyQQIDzzJZocv~O_^fU{Pd z7k-;KxI<+^m2=1yUGEcYkLJK04v9vA^09KLs?5xK4v7hviO_4*V|2YX^2`-&n=A^L z88GjpF&@wd1AVJ2u5~MGslK{#=*!>J>eqSYr*lS%mc_g9=VPj4Sw&BDb&8JgzQ3^d zSx|tU6K~BH#7gafFVX@8IJ1o`Lk=u?H)LFYm)Fb(D{=ymr)On->ar()Dt#1*55G-4 zd=#=DwS>6@XP?Zp(Q*)^%rFt0K6An==mEE~YA=v6C)fWJV{7OtiJ`Ll!0xPho_NDv*BzSo^!e(E`!G+kP zJ}%g6+GrA~7AZo_ll;@6_K@|qiqHALQsw4$%|b6g?~seNpaO?Z^WtMhHxLV$qV0yr zbs}>Vxj~(i_5lNV(V3c|@o_@>2Aw@;4x-D_%8IFLW!jPlgLKmPn>Ac`hTr0e5jxNn z2g_e#rh#VX!1jO@b!Wg{kQLedb)7I1s*N_idbzPlTo&#_r{z8m5Ra3WXNuRn#-oA- zW4`I4OE*sfXf-iQWGgdi#_I&L8^N5tC|6MCNR^j&-qY33Fsi~~OYd+P$41+EQBF=@ z94uj@MQCVh*RR+*ov-0=-Mr@ghMyajOtTcP8<%{$C1h=q&1{&@XNrTx*@!rzj`lmZLb^JHg&<3WI3Q!}VjxOdO z^l`DAH4ci#JiI+OPjX4q6S+yd%E&^@DcyU&J{2nI_V&0ueEj&a^w!1jaCDmJ62ND* z{(20K2Y+uhv51QVVRBgU-r6Q*(>@B6PZs>iEE~+ zg`ft$CxV2svE)dAa`gB!UwBh#q1Z`+K8TFT{KXWrQezwjIJWO{n-gly$8^I`$dyQD8+0`2 z^Q${LO8d(X_g;G&W;lh_=!U{;>?pDL$+n=5sV7@t7CZx@qi?dTly4gyX8jpKdz>!3 zvx{@sFm|Hzf*&d;_Pf=ho6icD{bI!7PnHC6V=(HId?K~O?t34Fh54e7#QTyYx4X97 z3&UeI(LEr`_%rl$n5(O3M%TZ&q;hVf(5bh*fQO50z~0Q?DvyqKcJQmW$ERy9d5dG2O90r<{t6$u zWpU<%c5kVG(_)oKLI-Hx+bgjN7KbM)Fd>iorCPomzDypbh z;P?Qy4M4>*^%~q{X5I%&tBE^&hWE;o(LM;qL>+6Yt88iH7%xq$F88gc%wHbF5_FBlg~$Ks_OpPMn~^VF|Ey z9qa+gZGoFnfqxQ-!t{&S$*yi@s=d38G0}PY4Vy;`cGl!+BW)b@kP4BwoH#lXfMfORG+sXRtv=u!qSXAr3-Sp>pFjdUa?)DN8tBj-eS zc0xb~2&4w_;*gwtz%m%N25x~V2IdTmfOO}m9BNETSVMt+SuMJp2m*=v4AwKtZJWLS zPYY1j*q8ulfar$-MGQd7ho1wxUk7z*GBK7!-v|lW4xAf5@f%xFk_XJK5v_{OB3L;Q zxk@Bl299y$f7r)$-__wC0trV9CSbjUu&Y{Z z!xPI0pbqb9YI0F?h{pSLAGwI_z_xO&A@DR%;uOs)uzqrnzJO~<)lMMv;QJ~+=k<~t zAMLFtJ~w&Gn>Mwb95xGVyFqEto@!MW`_vJ@HE$~a#qklNZ~cM0M9%McxqQaobXT*# z7|(afmjWXG_nS2 zeKebPAYyuW_s8CiH%(Hq0``J)ROk)79}w`Sxk9GqWv@lEUx}rqVS(h+vivA->Y@*g zxpdkoMYnMT__l5(IV;j)IXOAou~3KT@49(~xu)H-%!;(~=b+IJ9upHywTooE{=mMA zy`9I}Tf&2Gja>85Y_sw8f1fw_Oga_~Ix5j;`?mj%hbA>zJB zGUHIgBbFT$GcYX;M;bF+t@&5T&J#W^2a7mh`jPADF)@iXS0;P^L0-_|;v*Yepk%`d zB#6Yc^;@|CdPC|#Lf7#Hw@^f?o+L`LPZcW)&|btIV}v|SjM{++i#{}lV{HD4I!@L* zU{;K66lSd3oZUY%hvR^28f?Sb&UWvefra&#U3NuvR zgNQOC;-!LZWZ;*dE8@><~06kDA_hYNoO? z_x}9v#Fw{@XD^;rSF=L%NXl)?%#eoeZ{Hq>Hy#r=SkGTu009xztXniF=%aYiGapwP zzQzIboQGZg0{E0BeLh0nzorO6Y0E-jD->|Y0I37qPIIM)t7dI;A}`*nl3 zoB~TVlH&2i0tYp=tP4N%7lRJ>T3vSaf?!DbE>c-waGdx$wJ^LBCeehGEF?`X|Imw?7V7SRGBzVK@{pPr7+ z9Kznj^1fC;G{p1(2;m!?I5Y3RD04F6U))AQb}%IyRxXHnFQy^v;1M6)S?N_^C?oV? z0?Gsp#pQGR-LCHjA_@r{(BF=n*GHlv$xBb3b4q42=)%nB9NyrT`RbDYEm@b(^_HqH zCK`);jRlXoiD*#V4KF;bzP!kOU9h+_lyUd&2PfDo0lo7-&++U<2b*=?tmf^$%}S*q zwI5ARJLg}?|DfnM(h1FjR(*_LN&1P(y%Imev?E+|lje=Y)(!ktJ(K-N!s)W0$GT#% zKYlk|yXjK?z&h{*!no;O1QNs_DsEFAe7IkcsbhQI&dYrM+w&u-#O>F-5`5xYuKsAt zR>7+teyZ}UePelPBlq*ajjALNR9;7hsbWl>!FMD;!Ybv`_6;;>@KBAu;YcQw9xi@l zPR?NY!a|-}D}A^IMm`gDm#}~k&lI8i`SJmeLZvlsCy#_9mjHW@g$AaHussLl8MzdI zG)c8?K){Kw*r(^Zomh%KSAaMNpL%P)_j(|DFap4kAsN+X_RG6^&vIhGi<8vD{!sD9 zRNZ?x_zz;>AS?JV@Z5gLTGj6%A207+d^DCnpfh6e2a=wq0MOf?_Z;{2Ndf#lW~f-; zy6^wu1uvVFZTt~pL5lK%yQ*HOMXe?Ne z>`1*dv7-~;zL`rVaq*9Bvj)3NLQ(-RynoLa<__kyzG#~A7xD~)_K#Ef5ZCO>Uoc{8pf za>YH&#rdr!L$o+Bc$n`Jw5|NlH~P~wi#4~7xeN-r6y)a*!L_nKK2XM;KW#s4xC#pS z!ntuHnT04(7S%n^BII^g9O&Fb`_-1B-&|CkaOGaEQokxeYLC*+lfUh2Tk2RKTIxjN>L zDroZqqUK+ugUpMBVLrr#htoXKTmlRrrqO2T{E3lrf4>~gk?rCR+sU&bYVabiv%8yQ z8lwcQB~JKgB<}_W@_<{0#uRSUSk5M03Z&2t=DM}_PM5h~Sy`IZAWn3M=C&aS5Ei(g zEWnP|S4`BlwzfV@St!y_t;hm}u)ycWH^#=`9W2WcGz8qM4unuW&BzrC#dC!f4X3%u zzGmzLLD9*G&VYU2zO{+kN-$5Do}W*~;jv-Yk*v@2OIU4N`JL%aT#T|&TC5SD;cj)BlXMc zFITlBc}#@T13PTyA;kG zc0YM7+k#~tuK9#l5{F#~^xBO>CsES+m@b#gEK+H3Z^7qLAX5;_B``_TnCYP)sas^t z1>F&Xv_FSYW@2Q8dN4vF%@B7AeU{L#1wh_UbcZU>0M8nRq-qE=05L$v&BaEYP=~kD z!r~&!g2zPPIW5B}m%;|aywCyM#-wo^XCiQD&aH99uqwY+4om0eS7oUsu8f7*NkP^9 zaTy>EiN+%t;Q---C*4rF&>t`xsJ+vMy@Mf^k^xtfOzEg2&Nne&ldx%ct2}72eP=70 zgC!E6hJSu!R%8_vh>v?u^vG_#1~im^#sJTXT&yO+F;GT|pS6YRgv@3d=5oVF00orA zZ3Pyq3M);Q^o8A*{%)a)W$4&6_3i1@JV$Mj{+nkHE?P6yhWGD0;o>3!QLfvXc^A;# z&;Ul)e%pGCVWlN2DZZ1E&Cq?{qY3G_b7~Eq)+|(C>j1c^MoUM6N11`VA8TZr(7fF? z$!u;@=MQgmToYf6*{>(fF1jkFqZ{s_C_hnH%!O?dzS$st3AV5F_V)Ve{ouU| za0Ieza|pA!WFk<+(B33_udZWdSw=3#K-hVVMbh?c1W3SIqJG;wWMLzd6n&Ry>v*!~pDTZt7hdb7=5-H^K|E_j9EuB#ZU z$%X!t8L5fJmpDkorGlHdwJoBv$HJ`5`FW<|V?2c<*|?csJuit3slwZs~?MMbHk7ewC2?WKm{BKJ7@xQ zCyn6v&_EMMj7$I%@1LI5h&rM*_-z$Xjfnn}+6;8H8|B z=zC?C6$;ou{P$v2QMWK%S*E?INfo}ebc%baj6sx-tNW9!S@$=k%c~wz{1QhDm9p}~ z!MRqcv$Lz6AM855cGOT(`b(774>Pp8m+{M^NrAyv`lj@*P8V^?Taj(tBNvkHQZO95 zR*$j%eV&14d<&wRAM+TOA)>jKStv}P_>4L>CCE0;UGP^3Il`_2^y?@LB*+alO&e!e za)C$&=@jwlieGSQ?=<=@ES(Huc@OSqgF6= zljdtNp31wq4}A643Wak0ZfB#eF!{LU&({n0jo0J|1TUh6H4f4{L){kDzghR$u+I1l zvk25}0?MCW=}azfnRoT{lbkr=e@ia4i=TF*%f1g^j(G$!Q=tudKudw@BX1B#cee%3 zy}=&<3SlKrP(u2vgbp-s9kL&TQ^aC)!pe4~pFH3H_xSG2aC5DdT#%?Pa5BC3i>$oJ zYt1OpLiN^m71ShTZC#MtM~q$LB76|gOrM|BV}WUBOT1O%4dV3uCCcf2c4%nw?Ov^ z!wIwc))lo)^z?_Qy?t-ro+0~?C~{xN#saVx{s7|G6s&U{>T`achofW$%>etO2C{%h zalRwH?$Q^7Z>vLgbEVHK*rcyGq*c;ee4XO>@I+sT;&s8K{`ZYEIBsb(1V-BBsD-a7 zY8fui?l5>})&E^+ZN&+@PG`d!fwEWoPIm=GUs!RbI>x)(I5A4H>FSyCr+OY1Y~0gZ zc4q83uTb?xk%_9(1b$tt*G<#RF8o5GQ)? z*I6TwXjGAF5{=6ksWF}N>5^xm?y0Q8r9)57_BACp++D9QVUZEk+EPIy-0|}E8Sa;c z_|CB&{&>0>_)i!z-xxLtvPH-S5IdoRoL?na7#MCGvwq+C)Glv|?|N}(?%}orwdrvB z%OlE181=-VZD$_+0lE&Fp-0y$Vq&~=%C_leu*t~u>QD|UEv+=3wcL%}*VGPpd{-0a z9W?R_SV9B;Z4Q_UhE`j!Z#9=K5T{cFF$h*Q&A_loyZ^ciwRz69>2K!q4QIJtn;jTj zm>=M8`=$dmNn3$>a>J}qx?=II3m^z|zby-6xPb(Pf>_1FUqmI=QNZ}h{lZ@NhHVrx z*De~NbyEZ2-v{*kO~V!&E30XAacoi+PD(?iw>Q!p4<3&m{ILM^<8)jx1^yh3vg^uC zHiNrm0;LCbKJ?fqsh0gd@hJ1TOkX`QtH!Ye!MwbZdS&Iifnf}LC3v=8R_%ROH|@J2 zLEA2S(imF5v++g`t=HYK{e5zOk9wlNO}NXR@c&-^|Nd(Ad7Ja|Own|q9#bW|jYe0` zc2}jmJDpUsAu{6Gsr<{{Z!W1mT7RSPbniP&t8?eMs!9UF&hxq3O1|UpjA+Na9NZ~2 z@bT+!#&!Sh^N}mtm0OdVCCUcwpw&rWv^6Z*DZKFMwH{x@039lh#%)@$ZU3;Y`Fo9C^Jd{kdDBsmYW0N6c&>a*=t|X<&5ms}ah0df~du)bTS4Ri) z4Kx5uJAMV_zSvFL{2`eF41Z4xj!H{6b^*h_9Tdbs_Q=6Es0E_nb+x_~U^#d0T$0>f z=)kwIu;~Ak!3K2F_~JZ$g?ea62=o~H7ukeJnnTh!2v7Zf#TmLyo5UUeZD=k&mzvk` z@5YS5+&PD{fNP>cdZ@f<&t^`0L_pdtLiVUIcL@-2Ue`|tZPL|DA8|2hQz(n3+<&G> zb8t4oqi=uRLf@+^4LU+8q9ekzH>oAujW{S9ZDY8 zC%UC8_fm9O3jI|rQ+OB)Euh-VKgb3?-DK-)yd`V$5XH}J5ocM=&wpL1=RmhGsE6MC z_^45#swdysALeIE_!x_IuT})6OjW{{=0csWBCu7xUKaOjz=ZgpDj<*{4Epzo+qdkmLf%$U4)Fy6()d+Y%uYspV&r%lMw$Me{OGCS)xVdkK zg}q^MBtG^Ea~5^;<9M{duv1_|mj|(m9|{Q0?)YeU2{N~=v2q6KU7XY!vkp=&Tgocs z=v{X-lo(nh3wB-(dZt}JbD<;e;-lE+Ml&!I5!>_GX#F$|>t2kQn|i!m^;h!nm2429 zr~CiE!S%Q1I)p+VS@qp1OKgvAF}B`+Jmb?Pl{A6!SU-r{I2iXCcwLZiZFikL>QpNl zQyy1PoBh$*UW@Vs@6%JR{ofhy{-G4@oabuG8}2g|JN3$MQQ#h&rW5q|9mWcC6}<~q z#)Aw}KF>VYR|_nbFd|&2Z`pUXjn{v0%#nlHcZQwBK3|I?qAGOSwteMgow6Xa6EGAJ zqZ(okjH{ImTvysKmAaCV7$%ifXFM(#xSGP!{37?4YPTC}1$PF>pg_M<1}iQ$)lk-? zybd<#1t@4sSG(izg)=^e^Pry^0Ui1L{B(<`dp*e>uTHovSd@imS??8T{Jx-zG!*Q~ z;Chn$et|bvh^>8Eya-6L%6hf=tnRnzs-NM8c$n0f^q|400Xh-j(1*;pwmFok7M25i zyI>fGAzMi5gGdNwl;<+LYolqs&?^NjKQk%47K|=4VzFpt_W3FW#9buVUJ+4Wd7c=5 z?0?IKe*DL=V*z^8amvVWY;x|9zLrIO>MV4XU6|hjIBscCFmB!I1~5}~m3-M5={~?5 zaGa$e-UQI2@?|~8Ha~`fofP0zVCZtnzb0z-Nr!zt*tLq+;*i_h0Gj>xpg9t);>Mj+ zuc2zbhHX}Qhe9CRAn@1S07V3#M7Yfv&m6-e3R@AX+Pyb=JUjM&%nCn{7MkdJcR8;oCcLUTkf(2YxCYUG2%OqB)1qOOy=&~Xg?6T@!^N>q7J2{!5R7mafgOC%Z#o%dT-ddmaj(e zkZGukAocMiAHa0?e(^}3%G-H&1z}xlLS}nUmCk**fk*q3x_gAr`OWf$$6JN+9@81;ycqYAE+k7-%T<}dmnSvGul1&;VeODhlJ{SU zw=};k-X#5KoE-40SGJy8852CZRs8J8!*_mDkNn&wAQSFrI?qUKqby zeLJdHVw!MCk4TJK2UcKAGtEN4Hr8Zhyc1eqWc9q?W4*~ zt{m)TL!p@k!bl~Dn@YsTv%hm)CruD6a|}QO@p^#>WIc9mzwLNQ-Tq9mW07+Rb0CI|Co2`ZcE2}`;ir|Cn6;&o z+!(dU6LdgAV#oh)<`&h=Dv4U14bL5miU1FuiaOb`h5n}I-M<$$@mezGxzCD>JS&Nm z&vI@5*N8=I+q<_JU^rMmVPMe5PS;myF#2<9grEG^iIW;!|K>$o@GDQL9XuM5d%C~O zGra;)ICm-qQb3{EgIA@Mx!z6<-rRRV`c-98!_#Yz7qUKxn1deWZfeaPpt2_(}S{^ks3oVSlu z%7>n1@4M#NuA8*{9J^Un8qx|p35Og<948dd{Bfx()X?QZYSjOWCu=OED8mDcKgF3;d`B5#LY&KEw<pj4+?%(iXZ8Qi)W-3BaQA9=&iiFI}%FHSun-md=l7#FEHxd$} zM0N>D$jq!{MD}{mPd&f?@ju@8J&x*mIO9k|1W}L6{wKpT4F?i zhQGg5*Ly0_(s`W?n);F<8M1Em>d*pv5I`Dlok^q743|EH$VrlKS6btwFlZRaia;4g2p(o#>gI|e5V zF{ppJs610QgoF3un&5U}NF_>vC%WL&2lGQjYuRNht_T?#P}N(NJa9C|qQ8Q9pAQNh$490(l80^J!AEtU!sh;pbI$$%Po zx8jpC#GVcpA7Qm(;*#>BL5#vFT|Np^co>bqFRj zv9%2bTMjpa8Mw0`no{0wPaD4sWE<;~;37zJ5WoQJFl>us2m?eIo<-y2JhYnQps|yu0H(|PgCn5b(4+ptC#GtpZPnJ+ z#tjd%5qhtM--_UvSyB4b?~}s{`d!+XKc2hlr+1$1Wd*tDeAO& z-XRw5pV}p@3#*T%Na8HRSnC2fo1pdLFA7PUZyi5F`CI<{#bIjrCTh0aD?a;R-^|X+ zhu!B!h9>u(IK9I#b6?YePm^u~yLX#SYkVx(eDUnX^jFUVJAN0|(k@XEii|?c=1HwX zGd(q8DXC{OPuTiT50K;Y*y_#auC zCzv;V|9%3MGR~?T%Pt(AFwsI)V)7-idHiwXkCq}=5%9fxemlw<`0oqx#pUr=E=+8QwHt_G?2hrxlFmu)C z$h_zASDo}bEp-YXDe`D;MQNObDI`X1-Ecx^c(?h(9}E!oU;Wjx@`jkwoU_)ByAQ7` z{&`}spXa{H#laKh&UD9~Z#W>flX{D)fxjx(Z}Gm?GusBu@z(wDUmj?P^ER(j-CE)~ zu;JI^Ghf@jg6OnHKXUf$WaFgUQy$)N9XHmCYvl?)YE($wHks(JeLVCj=#s&;iy2S3 zwN8Sm55iMmP4Z0O?w@H^Bk9FA<|wQ;UU}51@owXVX4T6NlFeAZzt*lTagoXy5OPWMQHqq1@)z3eu$}kTUe8Wz)2m%KtJtl( zr9Jr1q-FDbT(#fy;)xl%y>p)nd)(4Z%h*#fX%op((t;vc5VbA#C9%6erAd4+A@bjE zHp1*XzANJW516b+Dgx zAwlGOi?a!r_wEJ5r(RDNrGoK%?`{q|w$S79i?!w3%=MkXpG|ALuN(b|bVIj%=FXGQ ztI`_{DVvf$^1TtGm3Mbsw?k~J{_mvi4xQhtFw$gKtrxixeY3wKU72b1y$2W1fm&9{ z_{a38>xB|7g`~TUVw~A~er`(rS(+9Bl7?yL8{e9=^N}Huk(X(bgmOM$4uA`g=P$S! z%ZFSZ5 z75qB)W?68*N~7Ks?|F3ejH?qaZ?uw#;-I>4Q1){MlisZMzCm@+;aS1?<>lrs(Ja{M ze?nksLDdA{!3O}$Gt!`z@$@axyFpPD*-ZhrZq zJ4m5U{4yk-*6_SnU0_{fV*wJJ&Gr^|2ds-)N4h}$i+MHeIx2-xl4>&D6RB|`7z%SP zErxy$(0r`KANZ3Nf8IQ`3Q!dO#IK6IJGnf@R-g~gdfQ4}w<}|<(CGC&^Cgx>lik-@ z@@^}BGT98mUW0o^)Y+K(s|%TnAN?`Cey6%nL_9Wr_o0dEMbCnJ_ba9*@(L!^G)xOQ zMZC|y|Fa)MzSQW#4-W7I^S^w*yvB$9c5|aeMwFVdI7&K~5;;VN%c3VY$=k5NkZNX; zey8wksT6%|3Yx8f`LQnOMj^DFkQ6;%x;p;*1{Z7*5cW`w%yol>^ZWmL(!)%& zYcTb?(Z4;DhypW(uy*^fBXB+jFoHaL3p+KH9h@1Oo@8_-TYEu{alOHc3sMdRV&NewW# z-m4D2uz8d6PJ2bC*5ej|U5~AU4ctRLFKrimAD~ysn0h7&T7>By!#f5i6^u*SX;oy6 z2mf%Tx#lmgqf9SRN1oB@=^YYjl58(#@T`y{C2r3m7^$lhB>x>FDImZNRNFwo1+|ZU ztSA(TyF^7_R&8(*ue*4V6|~7R*cNMl+{yfZ4$?W4N8LXLKI~K1ll7>P-uOqkZ#l<# z@VC0wU~i?2GjCXREImV2=~nF=s%)B?O?s=(9zUjjJnISHmoxnLYSQ#(t!x*4v{tm~ zF+se1HasS~ib3u28Bg?t?)xY^XVp+eY*d(w4444|}PzDOdwtYu~ z8r+TGxx2970pray!__B=S$V03rWTKkOdNpB*?GDAT0Pz4BZ2)7KRzuybf=;@B6N2} z;-$v5mic`F%JLDqu5LWyH`((lt=K+2P`6ds+|AMLQ{_wbf#=F07oZF0OSgJTtS2h8Jo<_1Ej zImWY^^ z#pui43q`r7yM9StRYyKd{ubzaTDT$%C)f{KhQcfE38cwz7Ulu}5+GcpFNqNfka$87 zfOXl!T66p>n*_Ukw1{E;iIHaItbu+!E+K(Hh2GfXYz#dKRR+lvfptP$=Nu}`)2)Vi zBO@B(&tHCD($3{tyx_S{QcjC|i2kw1DuwBfd9+0y-(t_#XVY1-r`;4Y4QJKT5|1@} z`?SyI+199b&e7a(Mc3)yr&&Hth2_lE`EGa^Af0F!ym&j}bXcLufr?*^DNiq?e%U$L4bYk!LrG#y}y%e9)Waogyp0S?#u~<7$yS*0YO`Z&JPPRf&mWVzvFyZq=uBw`RmPdT6^6B14K^u=XTfZ;7*i zmpa94$5O?AkYQ_lW^a`*{j={VW*|i^bvkWqJg5`P0RN0*ZG2+lKy>Xv>n|H*WQuQYQfaRU zq+{Ri{cf#hkb5=58^c2Sg{qx=5;6We91ODx9y(bx+b_)7V_`nNRE7}D-4ffsuDVOJ zqR&EZoHqYzMNY5iC{5PRqg>xx?WMNwX?}atr1i0(;Q04^vy0d=igJld-vOOVvL2RrX?1m5^rG6yo9lSOO(0VR2{=#sFc&);86<;8n1undLues*410#!``L22A zX`r-{|KQh$_>1RR?H8;YJ^$=}D?$Haqw@{zfI^jI^ODoMuN}R(1wo8apRu>8b%>-$ zJxKxP{8sZT#c){Je1Cp?6)lq5&?KTSy&E0<9>5H%#4c{TJrv4?{Vedn3oqm6W(|1c zpSLbUTN##M=#}w;tW*v~SL+vZzDsy#7o6mBk%m=tL-9z56w}$^NAG1SYZ~`dixn7a zC79?ca$a1dcFWjX#x42QU}dtX?3|FAZ7iEu$~H$2c3FLQyHj-4A`hX2ij*6)>;7R7 zq#eBI@>pY+x0V)wd^FLUdbd+}S5eB#NTG)C zZi`hUtyJ;^iEWx3025`Z(rk+)6iNCCD(2?A;HJrjhk)#N06Z`%c&5n$9L8GNGWR4o zSp?b<3Ga>n9Rv191OxC()e{ZLaR;(il^LdGcl8afzouVR1s*kZ!@2{^}kG}uP zDn(w_K)5VnmI1EWInn5>R>{L9iUCI+wc42Dgu+CR&T!Q;PNA}nQy<^y$0;NoOKv=Q z{m$i}Lt+tZ%Ec9r!u!7*{@T4=HkRGrIp=2Dz4IEjy9`fVsGQ|U$ro!hNNzZ=*PJEh z-I4d=yzR-9%fIO#d*RSv4Ese64aIn~OZnv#r)`4T_f8K!ANs6yO1sY7%Xe;wC5rx} zhi2&F!XqhnQ7~_K4;>1hnQ@^?pVeWXd;0h>Ju~xQgUjE;b^G7dtZ~&OrzkWul(tSa zMxwv|DTrNDmEd)gC<##KwPzS&;zmpM9sS>vU6jj@+of32PZ0#klcM$0#xO^>E?MC(lOTNIw z?&0B%`t#KG`VCgOx_h^RrefMtK)><$M>*~hkBS!uuS5t|n(xm)bjNv!MYiI$JX`lD z&gFV| z4r34Lh+6)LDluJ2iI*X#-*vl3eM)oUGx}P?XatO3P0G-Ugtk~x{k2mTgKOcYwuQw# zUL~1(S3D-8_1HY<1Mv{Q9De!l1dxRuF))zW1n*S1-QA^Fn=XC6#CTl>>&3BVex^t* zVY?PSVY!5p<>!2IMZ8PK=11+>_j>N05xp+DYG~Py+MfC6=e=b=5C)UPJnpjpG`YZ< z$kYKa5y@G>;hCkEu>hKE|Lf__B%tPSK4`$S<&WFzMw6u-&X?`|`AJmq&GlbkpQ;)Y8s@U)*nP$l?aub4! zdPcS{S2D_ej;_)6;ujF0j4c4b+GGoqP3xPhnV8Iz$4~!zK8G!k2$VuW*1{&}iwp!s z@7}e@sIO>K{?>CnnPz?3`7MQ?FTLH(l34iVe3>luHJmim0(&**o7|O^@YWQ1I2CLfJ&a zg>lQ4Sh0(rR>Mz%w$1J|>k6>QC)Do$Z)!h8)yV`mH)9%ve{uN`s`O%iIwQ+fb-_eHfE`<&l;5>#7faY-^+Z z=bG$IWdov6^ZbTujaJlxy23q)|919dHECwX`yUEK-uOlv9LL42dRxBhRW7L+wa;(e zqng?0R_%nEu`5L?>Fe!M)!oe+zHI-$OZ?SC>vQiKY(5S!xqAE(5|m|OU_SOd?qjbB zS*Ce=h-2<&HkiB;KP0@9d)hx}GCldk77HIJB-5mnzC?d{Axl@_58b~S&51exzoV{Q z#X~NBFzC63-9z{0q_oe4-&N>^=NVA=)QfS~LDMPM-D{@XalKq*V z(kK6apP$J6P{?4{^w1OCiSZ6rMP?3_Bzx7wjhm*_OrNQ1g>&V2o3<6$>T0Fwn_S!? zNSEsoFu!9YY}6`I309gQ*Xk6=^e3+>yC}kKB93yxXN3ZLV#^t^}--TE7DWuYKGH>}Mr+ZCI z-0x`=+p5&Fvl~~hUiacFp*|D%wl0Gt@k%yO8*y1C* z$LsCwswG`)dVZGF_54gxTEbUsnIhw%>SEcu-{knp9B-+r#Z^NK-Lc>5d%Wr`+qp8= z9^%daTR-_XT;TcPxT)~8VY%s~&46M`@tp*kyJd=Nst>8)r%R;$TMp>pjW>sG5qyKB zuQHf2NFna%&bukhVh;at@Ag?I$(j7Wl`zSg$FI_JIG^S6@3kMxZ*()RwBI?jLzsKS z3VqxGiHD=7jhrZpxjPtjXm20fIG##+uBNP3FQdsEkDkhZt`d*IADWq|auxWu2U3nu zT2H|mnPn5lRcrQgW##&@Z!=EAKgls48}adHV;cW{W)wn$V;?sJZM&JLeebt?Ka>2i zOuvC+v~q2(2ZW3iGc7ktcgpNGz5mc=!KI<%6O#y=s{X?~?nrU#F!knB8H+yp?Z#0H z33Ov`(^8DiUSDt2X}NG+-O;V+dURsL>^YH$bJc~ntwb5R3fAkM`olgcxhgJZpn=81 zk#%iX<9(wHx!<3@cxDITG1wmo`+HO9BZCZ#2U7@^rfmB!xy_-{#4P!54`2*V1x{4T za{*4`uQ4QT`EM=w=CjFntmjGMHVYr}Y<%fs%=hAF?`*zr{(C1o&K}2=)c3Vlkn5LK z8|)JHmr%`uD4-2B2cVJvR#QjWmEN@l#{1s=SC*e+4R&WRbbNsWBUmLjt9N13Jt5wN zSFbEU$1*Ncsw=8&-6k8ltMs9^|) z#uk{=5N8L>*NIQS{rmSZr#lAXmn&>@ZUtQ4o92qvAN?4|%jq9G!){@je2&9x{4OGh z@lmkTQIzvvbuRPHWV=IM7HGORH$G*}Q-1Ue>KlKlZIh_VO+fG6$X34$c|JWO}tlr*q z@&i$lCYB+hp($JEZMT#;?c3+6%SJ$j_`E)DX1}SG8L&h&mhOzQy z?m54BF=*oCc8TIofn0(k>r@ss->NaKDQ~0mGDQ(ot%Y`+>?V^0~zH9Ee*{JN); z6MHy)q5#Y(9EZP#gQeA$w}px6;5pz8OkxZj?=Z zck;*ou9D@}y4WX9{$K34l6Al~&11P8q42c!76qqmV)Fy-rOQ0CNvEzEy}SNR`u5V- zw<8YP3^J`Be;LzJ5_8W){jsmx8Bcdv#$|UnkN3>WAIH9jMl}5NjQN=yVyW2jUFM4C zgd#w&W&xUA!Y%&iId)!bd%R?nNsf)(sd1X>@gVEJI=@mn5JF5)&I=l^RnY*de`Goi6* zs!yQi9#IHfIeo{!B0O_ROE7Kolpxi+m_@M=`bj}i;i@N7l{phbd<@*aIZF$JGHq$z zDU=!*K~&X6IB5KR^3>m4MU(EfKT+myc~zUyM<<_|+>pPU29tjY!7*RRO zmLkvm^xY_@%u0!M-OeFgul93%Ju0{yyKu=|YJbq=M04}c!(K*SlAd*q8!jF?eoy9j zFkX-xqqkqNbc(b1zjuZlUuDMUKYs12VOt~SFWneF5jGjMaQERuYCVp*swp8J@B4U9 zXgJ1&=c=y8H)YK>7K=CStEvn-(2?lsdQSi364}1(+r4Ya!PN6C2N@wy;;Z)~FJWlv zzz1`Y|IT$Pm|w+V?i@)DALNZIa(NP62{GA1Q+y6I0~gnhNbe{?gM4}_i1zWBKn4LM z3Sa6hT6^c$$Anh}R*l1bAUm7GT!YHR)%Evh4b=niKZVLO_;fOu7;L+C?+&|vzYG=G z=+|mZFzQ~VNizdVPMo|xQtUfAcQHrk9^N&QtL$B-+doL z`#9}}%I_taBU0s7P|%)^go%9_GYvgnDer0|vAe`#wJ;#&#Hu03$H#XLj!RUM5D7#1a|89x z7`Th1=!)##VQ>!sCEz|P66FXXClNv?bPx_Aa+xsS0*cuH(&F4EE$kkHPOtijH7Dli zm97{LMSE}F@9v+g7$*M$ledf37e;aG-yDZc3H;Y@FOs`n8$IZB0SCk2?Xls+ZD}IJ) zO>pMgE*9e*6$;&K8ne3`GmMmv+EA^?TEX&Zqk*0mjod4rvveOQ2WXDpxVfTsUHy?) zVyyDdPSOsds%9KX-Y;Y{c=tCzv6%s1znSKjiO+8BQ<=*jyz7=Rqi%#;Ru|XawpVO( z0eYH?LW*1so8rD0@}Hi4cHz3RxS^i!{#`=X&&kvk#gWsOw9jRNLYPJ1!DNBB{LHxc zG$Mx44w&M?0Ldvk;T@xFfD)R_PS0sWD(GBi!Ji$j<}!m{0Xe^L5GLp6KP+D8-$x>5 z_yRxCb2mqV)qO@&bM;$r{xR$@1Id%v1rZh-Od+y;b~5FKf$XoYt4sZM{k|eKV0c7L z3ultgik4U^&cAI}OBwr0Ih0(uoG}`%dBTW>qw+1slyeCEMs6X2Dj-y88&4Yv#5+uc zO59PRRu9J;(Z&_wxvZlV4ped+jufk&G>M?&_st6nOUHalP_TBQdH6?Nwf){>6Uca~a5qcC-`IMUgHi>n^d8BV+`VoU zcJ%t|8?Hqiy$9*6MoOy6J4(CQ+~nPPFQ!<$SnJJUu;#nSM%F{WJdy#79`q_f@ikH9 zvNZkY-Q_vOFGUCL_>wO?~j9+2oW61|7{sn7)@;xpstqw157$Nv1@<}c) zOibB=&b(K!K!3yny@DOrklk}Ir(lyq1zU#=FoX$zxqjnDLhU+wu~h$Td1!P8_Ea&1 z@Wdnxvo7!i1@&JNw2?^QK~0PS4d<2X0XsdSi6bKS`uJlAy1R!%QKv;G1{UaTGTv~7 zry}+b1Ukf&3tt2SZ(@8z5Z@yYnykzqn}l%x6O7G3S00<1;)O*q)TP1z8A0>X8)}{# zlv!@&90F6#$_9R{+rVB4Ol%2b0v`_vNghkHgHu<<;`{>>;f9({YZ7i7ci8?yZsiNz zy`=U5@eQRYGzv2CSpy&>43|mi#XlNw3uK>zB!hTQL1RF??Lo$dOOksRfPavr-$Zro z5;HS3ZQ+8btJ9>HQ_>|AOuI&SB_Ndz-nQiE!a99!+a{V<@$8p~K{%AHU)!`_5-|%E zT*I~J3UrHHvRz8h4Dw9J%S}&1T0sAAU=gD)gB{zT3tc0wh`q?$Ot^pGqHg>Da(F#~=e5vkK z#-Hgg^^}I?3ajqE5aGbnA=2456e~U{IvxG}sVz=yYTDeRZtKFUOV z@;nAr&k-9{W#zQ}$J{VXESl*F(z;_J9O;5e7jE ze$!lUJ$XosY4Yj9u2E11wc4&C0hO41PIzx9 zUZa~XTJ$1YD3pF<>cVu$_{r@fw=rlY3p8JZKk#~9BK?nkd1G(yn`_oKR9CL!%GkO^ z?HWT`coGkf4PTXoj{4^Hbew&0>o$c>-kfOeb!uoh$U432Yj?MVyCA8jZwP5BmbFkv zgvr!cdcHV*JGi@j!Qt0(ofqfq@J=chej}8szIG~wO&1x0@>lg6`kK7|rXWGJLZZ&g zYe7oV$Tr+id5qQ$m$t~nse_%;E`A5gqrCkT0aT?qr5Jbb@|}*qFBG-3CnAEW95@Md_W7Yqc4` zFl!KkS_DV(o_b}pMi5s}c_};-k3+If3{K>{gUV%LfQ5RS=*zIE z)&e~vZb6{;%wl~VcAp~E92mE^Vct{t#|@r!HE`)8mR2YiAjkvtAw{Vzs@B{+JOplq z?mczHlMz!mVyZh#!;B)`;d|HPlZ+7;jzC|6TjOa;3iY+6#d#4_#+Z%O4^0v~4lq## z(xfn>BMC|Yn|I=ytOMb3%1dVP52H6?Y>DR!VY6Y_GqoJzt_LdVa0qtSTR7TaTJDlj z8I8hld8Sf^3KJ0$ph*lXEOYfXaY|OfHj%ibq1t3(X3l|pY;D2WF*vr3{Y=Xo$3t}J ze!GGM8Nw7@t`PT9!NC!H%0n0(s?E(WhU*x8OYr9`?dpnyR_Hh0KV;gdBYCM>4%wSf zQU>JZIfwV+3Jt)6Z_LmaBmRo$^`7fx5b7)uKb4ls;_H^9er3Pc0X#TsKq~(5y3+-E z{fd5a%Qx4_+C^@u^UXIbS6C|^Lv`Ur%ZWPvM8n3ne`}(c?9nY|%_rX*?+d3<`tRr$ z4oswvZ>Vpm^zZWbV>B+8)sof|oSvO`h%1)iU%P5_(4b%UB#k+NV_6t_^VE5la@gy29?fjBd zzQA{7wRj>R&TaE&OXv?D-owv-{=XPJC-wq*XHhISJSRXG*F5!M1`ZL2T}aH`yT?wQ zy4}`Gg>eBHR~7Z7Sprq4tf0ybW=+X7tK=6kUNHQQb;ER8Qx zVPbUHd4!z`$TBB(?5elsId^d_z?}yIr+%!Be&{Q6@8k)Ar}JPhzv0FrGH_rJCaxj4 z{Kc_S<2zM9lQ4{V`~f~tP?T)@*Qt_&TJx(NH0lNgSJtHHE)t6( zBA5eFeqPkMP7WIuo9S0I7hGt}Aotnwl|V1xk&rXI4U$<;u=}Av`0}kQHN6WS2<}Ke zC*Z&gRFxl4ksk$XPTyXKSPsIRi8#e=4y%R(>`vG>kxr)_s}t52!6>_r9D?Yp5Z?=3 z@D5C$N?p5vQ9%$_hh_UW?YTK~uxoiq4EJbyrLm3*>=&6$}EP+?LdwinW9$1LHd$XqA+3n&X%ttfY88-(gtcVVq4%Nt+;8}4z zV6C=c|0atxw-bp0e=i?;=a)eGmX=n0u=>j6o#7BJ+~esP$-JdD{e491SxsGAhJc;8u}Gj4^()rnQ6J9}@|4%=G|S^3;mscwC2&fJpM`hka2>Si)V*#Igh zAAfm(achf>sDIONg5#F)aXjaL%(SKC+lhQYb8zSn30ZQNaXcXsju?q>h%o|5DT(!( zh0rMEWt`O2{h`DJLDOR_eS`|+eyQZ=o0%;|bl)UnTFvObE;{7GwJjl)p`__fW4^tK zj%>OEO6sg)e>iLuy<5t*h1o0MsoK;7N1T3^883}TC`~VpM-;!%x$zaK?h_b3UDQBf zxDCOTAk`4kM27a8*#WlX9)U~fI#7PEe6mH&=4H*kK0)2*R8$ye@t%6vJ#KG6%(m>Y z&*T4iz7n79H4oX0eN*@_$wK{TJYGpgNMhD&}K+U z1h|bJGD(=M)i4`-96NckiJ4UfW@Gcc(2Z(F{n^6K{sET%^_EJ-qKm_qyeGQTF6t83 zNAsL**OsT3myDm#@N{-|{_Q8aG%!f4G!@noHz&FgxK0V|^1eS?zR&A?X7`yUaRJ}_ zV18OuH?kaiE;m(aQoAo}y{d_#Pt;y0dL2OH@J7$X>gtzioi)_Wrv3WseH7M)KQ)ji=Bi_3S}xT zyDUT)d0>6xX0toE%Q6HcB%*L_x%O4YP}m8F6^sh(uZZMWw?L$#6U{59*UWh=hGOEn zJ0JLm?Bk{B;>^jc4Qp>KX5n(nwViZ(y$GN3@>?PO#-r$O-1JpGk;YL?&(Iu$t4;GT zc?mh441GQ?a-*NCmK8^8r#%7pn`+9`KZr7WkCBxgN9Eam#vOL&V=A5tK8kGsBmtQVQWn<5{QHW zm{TE_um%u^DJ0> zIJu~_$Oh;~ep&CfK)Vo~-pbYFK5@T2l>&;4)M;6{h1E0 z=KoJwT#HB_^lP}d3m^SkL~go=6Z8a^CDpR7WWG_cE;aI45yLiZ)RC~hF`=n>#Hr&{ zT65z1urj)DyEG24>#r#6yutQEOH|o)`WUT>fU<^USZ>zA)>8>5VdPrAv$v`2LT=XJ zsZFD{(&e!t%>o(h=~2NfIjU}bE*0rvDO>Ayg^}f4Dz3H9XD_D65B~p%il=rUN%{Xp zRD533fzohzPw{$gJ3)z?;9M{ zY2}rYih+nZ6yh4lLXC!fBN3xy6`~V980&rq*Y+6$gAc%EkQ8g_w;1O!erqv*U(uLs z#+`40VAiiBb1`UNt)xCe#uxEG#wKp+wL$UtPy#MluP`@ayH zUARcsz6FFr(k!8v9YkfHHw`6uo=$rKyyLAUjqn79X;68ybMM%ZxXDOs@jxWBd-f}@ z5x?r07_CUS$F?!%A_t=-?;h)X zFXJ#5W8=BG`5aN1xX+(8QIk`DZ;rTj?1mae*D*urN#Fg`Ui;EozVXL$s)^gSQ9;_g5*PRcQG8VC;))}{rLr_Y^> zU$h5uluNVS&rinqoIn@dTWEB#AP^RPnD|Lz84yaTr#C#j3D?e#*@)yI3RM;WMRhm3 zQA2{y@y;~#+rty%cCEJFro2~6#Ip((xA6BK2WCxBN}VHiPT^~)+?_(RO}YTLnn}+8 z%Gq+yj^f}Tztv*Rx^)wnJ!B!-gdoj`y~9L<3gst;Q5%k;ONvpFzT6D6iWTjY_z7gN zh=7(R(A5^*H1#?y7>(Q*+Ocu`UiqX+>I-ruag>qnj6xAMt6?GsH~`%hzi4(`)2L=Q zTlNy0er*T<@LjBLUv=~7FLBx@%cn7*yYH=gktdXEX_F}P9=Au!n^#61Z7XqNjHY*f zuCUa3e9e4lxXu{|lkJCJ@$b)L+SZNIZ?#ee)L^)SGovblOU*C2S>ATq({n9ZKO zeI?<0_7}wjs@kM0Ryg@pTV2qo;N4^_zTsN!_x43z3s*Nud;jV}wO2Fs!5bo8)w0}u zTePtnjB!&;T~}J&wbZnHalj$%?jCsoSQ)%|zi4QG0Nm@O)YXhMX$b78aI!hINR6 z0QH6(SdBfkCN*&=UtnT}xZI z7IVrTDAu!fd&B1o;l8IxyNL6EpP#?PJSTx7K%soXLY}uy&EVH~N(Hb+_l4zM&v|5l zzK06lXD)R~y|cC{=TR=24;1frYR!|dqDg@G0%WiTZ#*LJ-D8-a*Y=VvJ~A3p2n?T0 z%`hmLqGd!cpQ|%=E%0~KlxIOq$8e@GwK|=wzFdTbRrB~bQ(y~g4u8Jy zeP9DE+A#<5wHAHbKJ1mb99!(|T#*V8(eS3Uf9~Okc8)lcL#$TtV{14%a;IE3$YDsb zM4p^lTTZSaw8&JOLu1+D`R_F!P<=uS0&SiCOPgvOl-BI})b`f-F28qR;PIzQ7iYr@ ztEQTrCR*i;VF`>345a0v!ox?alTq^$t?pDM@J6kdY=Wjsz@F zR2wHxoH&OD>2#$y#={}1pO?|PV9TYyU-X*$u^W-&$5CiVv8qNQrJ!|>ce+8rrFR2+ z|HlQmhaND$<|PbtoabE-sesCh^H*1K9NY-*S;T5nW<&l?6N zc{Bw@38XM>5%*UA!hwOnl$Ev9;+!A7b5@Fwnn%XLgm*jw>~ms&mN-^7zi|tvcTU29 z|7A>mkK;V9ia)l@M*f>=NRdmPccN&blhKT2F6XxQI%zz5gXQwdi@Yb%srbBK*YYT9 zTbka1)_3MF&L6hkzFqX~a4*ZJ4QEyy*L+O5?8U`yyy~g!^i4gHEoOa^U*cZwpR-=& z?Wv|Zof4I-&smkdAKM)&hVfvh9GBRJ3ue=)>{nyn1h)R1oB97peuVX2XErWoD6DE0 z^Y%S3e^9s9>EDGBP(%1`iJd_F{cXim9n)<-jn#&|Mx83hcJ{R%bzP_f%^i+;*YY3V zzkgMc*lS#1iOoUIWUZHi#^LcE#Xrf;L=6Zw8o^~q9Q%qHbm<04RH^Ix(+oWpepgP> zZqOo0C`cu__xh3{&$auDg*R{B)Q(=bd+%OP*2x*<0-Oga21J+V&^><5yHtnceFY0V zmU2gfch{8zQFIt-7bh7u7(U?KxS_oGzOxWxY@O)GP=HA1^`x)wulqKBiv@5O@rwjM z6(x&W?_oq4qUHu1VHgNaR{c``)4oI~`*jMtG~(6+!yYLZssG%E#7nT%BOUDHif!#-qZO^ z=~2}Be5hD%n|MUWV7lZ~u1|vPYnF)kB%K#)xklsaGIoRozh<`Gt0J27hfu>YbiNj5l8__N6b0aIfM`46)QPk?DQX*f*kk8}BRL@@$kG+YncHQMRfA~WgQs{tGlzU zXhvXX(quc~w+zf@F9^+iF_u!$fanEd6vu^`RX&hKpT>mVVQLpF_;^U4b}JVgiixw<`ZA z-*=(Zz& zoP)Ot8GJvpoIU;QY1u;nugyY7nekfBFdUzKd>@T+3zv1S-dZ}&55Rim^z>L!ESaF| z55HrNgn7i6Y&wb%GWbhe<{ucZ>_F-XYKhbr9at#VMdIpH1!HBSgYdv6-VZ3UXYg~+ zHf4HgT`YX`h}~U2>G|{L63U+#WZM&%)=1o0@y;pVtu*%yPr>tL4VUh`cqfBXN2$M^ zP-nWZ^TrH!Z9$FAw&p5U?|Ck8+pVEch;$4n@+mZHxaQ!Pa&yyvta9~`biXG} zw1L(sd?OSFx)7ZhhuJ|Q8JvB(pfp`sUY-!8MH{m9!U?f%wqB2NpXhC$HZpPUIesV= z@`NTgcKdbvx4f&~7fGE1(vKN?vn6*hz(lvAAV`tAMRrFA$wQoz4fUEhLg-5dd}rCFJ4* zBsu|ZbmDJR)$c3m@YmHj7pajRRwCH&pyZ`svCNKXJu=i9|Kj3qlRlo-9|s=}*()W( zdqEV!_=@EE2(%d#UJVA><$im+0adI^4-HsEdu-b%T6y{xr*;lf?2L_cR=>pq(mpsFND3H-|}+2U={-BTq9k_rVUO z544GMa3}%!gX%I?{S|b^T5Z!<$^aX3@0ftmQM5SKMgr|{KicCX#@o7j%K@ zn&+3v3~V6!5*2Y*!C-9DpvSReE9xXCHoRQ!V2PCWeD(f7Wo2a}W)Fx5DovCRA|^w` z_p*KfCjlz@tct7}X=d_{iHS8YMN)zZ*QgjdekjZT;=ONI(H&lR2H_Yf|CjQbFM-)p z^jd0qnVq}LP&UcWj`4h*0bkYCj7xUB3xBpadcHgud7K(ah|CN}HMg8Ls!7&Yt~7Px zKE60hQiKNMyg!0xgzbORvoMiw%69=lLrQyS5$e)%zJcT4)mt`P?Kr|-u&f{YBe7NX zR#Twrmi18urRQ?5H2g9Or?P0#WIM?m)?)9q&T1p$n7d@tjaBdIE?=DG(K}`9^QCgK z`J!Of75sJY?AA{2<;AZz1?`+Ew1d;7zjMzC{AthcTtRq;+5VxQ;jR4sjX``IheN#35V{P<@w+`zmc%P)N7nsPA>yIt=#jNpEZU~ z)PX=#DO-0^s;^lcXgYbFuynZ}{Tl@|K8rDlJQ`hFfhmp4uPlUJnym*!u|<0dU=8r#MQVBmT!k)re2o0)|tn78q(#*`vOMbV0Tk<40_4|B)F2~qNy4l!tT%xPrVHu zCv@6Ln3VxgJK*$F6I04^a1_Dil!I49OiV52YG>bgh!fl$6ws+9Lgpb8W%R_v9v-dC zeiXBiCJ&$F0XRoSWSEk-`8iiQVa$jh<~sQemu^fElz`C#-Y0wx=)uVa2LqzvL1q%E zGrFIjVqEM+ctQ)kT+7|)i3m*>T!d6bj1(Jo6nx3okra0 z22{Q07{$fKCCA>bA`cE0-K@QaG#D@+uR#Sl3l6dElZAVPc*Kv8g#Y?~Ti6ZSi z^N!2FYf+YZ#kG&jBhed9j^1b}XVX0EzdfGc(IwyR0%Ol6M#h1V%+o<7zg#8?gj3Ig zpnqJh07G=t7bHNGs_EIo$OjL!FCKbBAS9epO=oZagN6fMLC;-2KNVJdai^{_^JC!@ z(K`?BUyfVH@FwK`{c~zVFIS#;!yng_tP^%O?wjVkqSrk2{BjN#NMh;bXOFAgzQRva zrgwqI+J5Uz$5j-0GrW%6+Xn4luGGED>VLAH#`X3}LHBo3@7@-UParG!;}S=wjVK9J z0kh`j>A_j0FXg=J+f)sRMOBNWf#psCP+((rTx8^=I?S6-r8|*%_)l8UYG!iIpqL`k zSzb}mt*?zjs!g*mX)u8}DYV~v$-})VWE>H$`riu5FJ!5f?cK-A`_%6G)*hkW!NE!p z#%J+{SK)&RTVc;H1dtYc$}hGxV5L)H3^qQ={EZxul{BaCfHYx_TIE(wF0_7BNGm;{Mt)gYwh`=HYSBBL1OF7WHTY%Dpfyc&czzdTZ z4DhPn@ALY-ehCA_Y}_vX4}puRU@qJ5$c`V+919+d!<(R;W#bYPsOv}duHzyN#*x=_ zVHZi#fxoQ_(u-X2d(R8_iLL{Q-Z91I+eK{ff0k1J0B)T&X{2)!4-OpqZ$2%O10k(R z7*ChnMffHHwqn4PrI!~R{EIanO(Ft}7-0ebK80{VIY}T^d-Y%5#)+mgf5Zv^I{{PK zrQGfluoLwx?YzwVFOg({TlYzd3ucK(U%YYs`bxq-A;0~vf^}>G*2nkBdjb5umv0#J z{du^$t8QI%(4I}PU#VB9Z2xNClSZGoBSYa^Y{1b?adps*{Mh67aPQ0Iy3UwNd96%3 z_T>*B-%wNctR1;t;HNjBaJBZ(^w6hT8x8%@VeQTrwc7I+o!WA2vZ5p14!!^I|#j7eaC5?nf#XPPhK&o^b4eN&HcX$$!16x+=fCyus4>jTOs+ z6|0}y|2lj#!=UMM{3Q3>jJ}KgL&c~en|PZ z%N0Cwo!)T1_ubg-Kk1G#z5F`|m)$w>AJL?Y(hF|(F#GrVGp;0Emo z*FqX8!rH-tFv0RCNIdz+`1trM(?|#91=*(?G?b?}zF*jNuwsM<>cCt|a;BAzg;L25F zyVWtON*+TQ4nPku@A_bc8<5^(ta%=uH6$9=qieFAnPBKR#1v-WV!Zt0>m3om0 z_}p{;YOME?E23dwyr-CPb$Dk-qpD0TMhC8_d;;#kE2WwFMhS7QC6rCHoFsl(XHlHT zmtKp3Wrbl{>yM#shN>%rN_Mga#fhbhk2tMg3hXjUxnRM`?W;_}zVKK+BKm50eb0R+ zZ_R@xiH{x3fBeQ>iXqpVfv~OruA=-Ho;^YEy>#MRy4F+O%**t$f6(ExrQ;74#bAY( zgM%6=e0IiJT^DkmOPfh`u`fTZDd*xDSP)$o-Jno%Yvsox=bH}3k9O{prc0}~FD+~` zmw@#g4nj*z5xBd>MT#g64=^? z`x!H;1k_NN(;37r2wHXGs*05HmZmC3LQ_ZK`=9;It6aH^y!$SN&OLeBMhRkl@O;y0 zO_OX&3GO4%~YN@n&>!xk#ptH{Wv z?3KNV2pQQcTSk%2_0jV?uQOifb^h=?X!(A=?{VMPeT|7kYRA__(+@KFrG63y>zI@7 z(E=pj&q2#3HWgoE@J__Nw&r`ZNUNYN9htPF3!~IS+hIw=%2G28C)*xnq18Xu#y@`! z^~}cFXz08=w=L%z<+MYhzl3a4`ggsdMa1l&bhW-U|4D36Oo1Wg6svHn#FDgNAA;m~qsPEj?t z4E+^R22OuxicEd*`Ip#^$0#P|zbd)ghoL{~2S*SXb(RqGZS6foXbN*`wK>wE=^%lq zgT8_eOLFU_{1S@ZvgY!gBFr-;;!8qvB?#Rc4?ilh|{tB*dU1 z>_Kdw%_dv7c_v=<$9!L&bGO&N+#&%VBrNh*Q4a1Zd%C`xnaoMt-TKphzwsf(se!%L zxrST|-yNN1OGZv*A9T7xYCreT&|sbBi>jwEUcdW~=UFKSC`7m}5(+yXf$2Vwtuswn zdiIrfb%kQs+=80b?GHidgJDUq{2ZDYrG;w*{@#{$w`4O12M37_&Z-~0NARrynzu5S z+XdeCY0lLe$-gwG-s7+uypd08qlZ+Ib2*hDV2TDjbp(diT$XXCI^k)1~6$HSt| zPR?aZActoUvrO<2=fd&z?dS5-nM7u`(9S#_5&W_CJEKNZXVv3I7NhQMZOfTu$28xF zlJHV@{ejWervyjFIo|0V30mhLpk@>K$2bABP`!W`3uOT!{tgl446Y?yZT_9?nCb#T z59sWL;oi5aD#Rau-K;kRJqLQm4Dh*in<92!0xA~F^+1O9J|kYQswfx#n5h$jkK$HCLvj)IsDE<4f8 zp|D_iz7IK+xCLx&ov3Zjc*1OcGy1~h=C%Ly>23FgoFbB9uBWTlCbT>{Ep~j>Tm0Bf zK?w~L6W3qAn>)+4?Deh`YSha#i%)xDnagL`)HM34O|to~kV%fHvN{dRe#M9HXJ6H& zf7_O&rTycj&GzsGiEV#nMM7&hA z>?gQ(O?CvSzx4jB-)ihli2L0QyzqMHC!8f?h{zQJ#~62n1E(0=*7eV^V11Qx6M?uF zE_}Tg3PBXKo1%&S!iB_(*?tUD=1nV$v}x4xM&I9g_Z`{OV4Q>}7C-7BXu-Aj=RkeT z=D13_>-qJckCbYQU82{&jd2|=qow6wu$ttUQR@>{?_+Yi{3XQ@g$FLGnN2YR1B2wI z|43TxOj@c(1B+LWRmc>2^uBKrY^wY5b-uaCF{vo5EVkg{MS1;@iH|n%ZIs^(!#GvW zF<&4vyg)|fLXcNbZ{&snN0}5V4)u=X(UebOt7wVmT8kOh7(4t`HbvNdo@%&kj;{Zw z$>Spenp?$7cMpCl4qnw%kTnxOW-!=twNQ2JYmPoucQ?PgiU1eV#FyXUg}b|6dNlZN z8YaDk2DAp0FV$eu9p6TDdgW~^JkN}+ONyG_&)+N*{r>pYj;$Z#U$#2O&k#2@8%geK zlM#^=kK4k&G>ww(t}H@AMfDfh+1bGfjbX0Xxn~dChbe+X(D*+{l#?>izkBzQ>hGCd znV0mbaIysMMLC68EBJMrSVqr|aEO9gEGZ}V+MsGRaZK}yH@3Ip2cx2&s~-5_`_A+%Yx`7sbjS)vj_d&u zhuy}$|5ivKu|kL=p`QVw#D^*ZM))eKM6cyA{~os$x`eFAhF$i;E=9K}tq<%I;l-w{ z`h9XV{Fu{OZV9qW&heKaIL#1d8((h*LEemq}tY zh6@eK9L@CI`^1UXSLnp%$2c`s9hp9K)&r3{j5dHjys_;y?K@Se{;qc>HLPZTK8Odt zVB6(=)Kfj$T2yx+&(D!`&)@8oV&mwi?n0j*F^v^=AIhE}^E_+lS^O!*?_hfFFKV-# z;_YSpJa6UQeeJgpZ)BAEi_MTnaf?b*!cYR)nT(8#+hG-f;hb6=s;`a9B`P-a*pA%s zhgP@RmRJf@y9I|vBrXm19_bNjwWcp36><2fJw(OFy{@FZC!Ln2{h@*gmnsHuI$4<* z_Sc%uFVv;D>Srn^`uqL+`%3?KHipp~baEKx+OkzNXX{CQ()jCH+0=A1SiUi~qvLm# zLRMs#k4&3!q2lMn#a#y6&l~B(dtQgf??p1eQZXC%@CWPnXKLA}S4?#o<)?S4z5H}F z=bozR=s3+lJzti=_a|zM>~nQ<;H7a4SkzwOZ6_A0WSiCtw zq1Jfx>oh9=<18$7MQZS&PFEtSuB$UwI-N>nR)RN|8Ibtz=N56Nc_VXqbm#4d``At^ z8huZCJs>}jcZ1pIq;r}V*|OFTj~j875e*z>xu2dF2U5!Jls@3#P@7VH=eKhF2atJd zZ_F~Kvi!HdqpWa5mP`u{C26=NUjJjiIN|YqDm%L6YwyRDy4X60&=uDEW@sn!N??M_nKSu{%`!0~q=Ef`J4C zc0NKZ$O2T_?MglyO9ubZ_YfyPzg+5*?MZ$)VZgViuWPGv)z=%wBCWF0H`l`1NS+L^ z9p}jos5vas>WoQaCtac(Gem6gVUau!OpOm$OYjuXPPS3vnj)?;p_q90I^ng)bGO5; zo60G(uAiLw^x^tJ9-Wb}6|cD@DfOFgGd~Y2@o?vQJYgk`lCOLH(NzR*>E}!4!c?N) z^n(v255~soUxe}7A+49*YNLi{1@(2+qFk(}^ARzJhqM67bXq9#3NShc!m21l$51*>z=$) zMt$ss%!l*i<)L`HQmQd^C_LUjx+m{32*sn^tQiiXN1GWZoiJJFq_!^ zI8Obh;ky7|H~aait+#$>eeSzY(ndR@@VD>D%g-~{unoNrI>rA~x8d?)(5^RpN-F&H zeqG~Vmh}}sWs^@IbnZXB^NqCfe_Q}kVUg;fPW)c+nB{@R^zcN-NrjT$i$}X$O?fW( z4Qv=1@DfMVmE?R-7{CD$fXS^T)7oRt{Kv$|nq<$QxtW;~A`KcC|MTpjh_YI7y}X?N zB*SZq+>IxHG(^%LW zv%}jCJ6Nr7F2&AR@+mlLZ2gjdBhKiq3Xe9!gx9M9-+M-xNg;jLZseG|2%qn-7<9{Q zQ%K!$WoC=oU()O~+O<&mljfuTlukBT)0bz*?w@n!TNX6lVQ?Lv&l>hbD~la35we@k zdw+U)+56pt$M-E|4&sL@x0J$FLy^lmE4gOBV+Q-$4s zo8ZYG;`+>~TE<)s>}Z9rxeZ?!7HS|K58PiwZxUeEwOPFcTQK zXSMjmLg_Ym!u?s$9e0AT7Za1^(X-{;@s{p;Ns|`>pWJ#k`t|Mw5iUdP9?QT#rZtw* zV$xTyUF!$L#P-1v093(jDLLx+PI93X)uW@}(4@pn{#PrLz_b9Z+>2~ma=DCZ9kJGk zV_SRq4u9|HP!1hZ(>tTBGa#wJm|AJbnNL}#G1JsK_MWia%9M9f834;y)I0Iyu?o3`%C$tR!=F49|@ zNpFY^Qhma4rfwOgye<3V&Vib)F4OCwcR$kD-XfsJD9!oyr(lHt0a%(#D}|(dZ(VJ&oS=dJbK;my-=?)S zPsIw>+^jo!*XR8uQ-TAI$=YcKMMg%7N_+f!Q=@c)n<5#_OR9pT!XBRSxp4ONoAEDm zdhLyl)b>&%4ErLRe0=d&!c2vP_65` zpnNg2!nE%5>ZSJL5>}_}e`DEb6PZznkSYsCBpa7geEk}ue{)Ga)2k~}%r68SZYdD8qR%DQkmq7!+QEYvRWU=>$QhM^d?88%kJ!Kp2aYT7=F!yWXxxA zwMcp2)p1SVmQ?>q!b82FK>z-*?7s_yhJWy_sOtJ7ZqJGVnWIf?;d6B7H~Xv4#MU#% zGLaR>tXHz%e^lby5Tm5*AJBEuc(n5K5rbp{kDMjK7knT_Q}bgZ>l}ks%Uq8sdGjrk zD|cuYi<6n%C&{N=Ta~MPTTs(W+y5GDOOI3;u(cj@J8yb0F*5XOYb`HH=^w#EUvqaR zi-c+9Y>?ifoQcw0|K`80O2^n38Po)y`SbfjNRmNR6qIbk4=_ht#5}Rx%+RDWfJiJ>o*xO2zycw3>QNK}0Oz%|9 z-^o^^ zx3#q;G)wTgXaR%Uf`9qO<^&(yF+@}q>}kZnc7+!wU^_scgs>0fHOqj|f7MPTB_^M{ zk1%xfWiJo6yrR(O zorPEwUoVT-xQZvg`I4hNwIY4YzwzbTtoeRjB0loT&!c{_gE$$RvyHCqzJK~k8F~Z`Viy4Ioa_-sEB8}+{x@>4K})=o zJY*Mx3Bc@E6_a$Ryv#IATI2UYM9~)$sLs61Z+!DMr3+L+b0yp9knQb5Rz>Jj4|D1g ztc$3&T2_vYl`DPTz?iMGEFdq{-BD9)Y{4>adFFMdey!UJH4pdNxf-N}kl{%5wx6dOppo@w zJANkkSJ2t7H1*={t_^q3r6I3f-EmPK8BWra73wR}G88g-=iUujqO-_o(Iinl13QYWx;v)^!pn4@p1+8;uP$ zCKPX#3mae*#iF9LBv~*W>%-g?(eD}CN;q{}mFL{rbae*Dz0dk+Oq$KPUDmp*+GYA& zPf)~D_)2;bC5eN@H0$SCHjnlKiEt{XumiS`SRKLu7x%v=pFUS(p0V;x(ScM*@PIXi+u?=1-ei+ws zK;8^|L-j|lazg(8;e!NsWNjy!8Do=(L101Y{a5fZS+mSin~5 z=uiUZX*x|U?n^U%Ka#&Zl#>Z@(Hhw6z~R1C5uZRZM$A(6)uR8+CfeD>q?tN0y|Cp! zjeO%iF%xQ1YW?}RT*$ZJ*>L%wirRAjusVTJv!qy|n}@rs&OSNz#JKuKt9`60a1E!) z&BvQ2dw<0JGWJoV3M8*L
}a{uTkS7*J8VrR5nH|>UP%kEGMqLHJaql@I|f<;Ec zQAXHk7X4Q9n9qAklyAE`A@7}Jsf5jOt0ljpyvBS|!lo^xxRr@irXO-gA;D|AclsW> zG3!?Su}3^MZX_>)gFWJ?T|c(%KQR4`pYeRZGHG`6zF;zW%B!VQh)% zN}$N3N8TNDM6Mt#y?~7k(Xk)^4M5un$h#22Wtfjj5#0;_nROC^cS`FijA1$U^D)%@ zgvScumjnK1I5^!8F)Z(@3ckTWxF#wqD|-(uLRubuV~$8IAtV6!YjvS)Mjii}`1jyU zfU%Re@}JSB5_Gb7lYi3G+}tQyTW@7WonT8y>X9Vsp7%UC!ef-++Nacx8 zl|3&tv`?O{6HHGi)lSctQ{p#S54#<^vgH%nZk3lctW_CQRGKwX*4NK&98)j$z^7js z)3}qXTKKPb7{)qyx#>U$}=0w%AVoQRF|baLxhb9%oGf!AvEs~JKpuPoEH!# zkI^W>P=36Ac^EmF!J_v5u$Q}<~O|piUU$Gu7kH{i{Zgk7npvZ zu;XEj5~%XOW6gJXKrADGt>V?Ak{I3)@h4lQ!ra-;*53>+`EOXp98LyLqs+_RY)Wq5 z-c;&l+-v00;_OtqXmsB5&fBVY$9CCUDEa#iMlam62yOmIrZ=3`EhcC+PI>gsrog5E z+cU*GtinI+J2L+J+n4;N&T5|gqm>UXgqA)L&R7}ONRMrugPDPCh)sq?XjJTXafASzb-djPX*eNrB?G6*7_eh!`EYG2MBIGoPAo+#fgqih4d}_vr54PY|1_?VoQj0s0f-5at3m&FA zy(T1tF^je~Vf+9Z?$%GlI2Kuh1_C#;yTELNF&+t_AHeC6X?$Qm5;g{okoL?}8iu=N zA2ZgUh+xS6uVA_SQe^-uV&&4ixTdR}TeM3CFEgT<}x17|S_oRfva>6XB_LN$+R!={d4EsR^-f5q}z>MD& zXDG`}%&%{{x;|&bBvz@+CqbMLT)P(S0}=q)66_lN%y0K}ti_~lP>O;&=q2j*g5^%K z0S(aX2-{#-IAbt z9``)#?~E<}2VzZJ%Lxi@ebTH$)eZeT*AISnZPb1CrC6j?`P@NOOC?y0h}VgKJ+0gk zs87#O@OwFDIYwD2QoBU&jE{;ehuO^}fmh-Me&o?O;nTm#IL{5u1T`{h?l4f9(h6;4 zl(8`r_Kb-aZ<5})yXzMD>Td5ay*a8CteZcg+((eqjHQOVvO1E&cA#f4BOxxIbL<8MrUq^v?5od{5GFwE@Apl3{ zYcOrx5B9Lruf=u^!duN2vUdZnxNp?^_Y*0U zky$0+B~T0~XMzMsMz$XI(;T$|;qe)_REPyPx$QU!GKG^>$S=X}pEiQW;A{Q3Bfy^N z*^8J1u<~ux=I*~M6mDNy^7Ic{w|07)2MlJ#m(~m6FO=zi(j9%ioG8?R-2r*JBTd2NPdfy@S2%##_$t7Hl?a3hQ%KTbs$=jV9x<#0k z-o7qytM4OH@3#}-j!6tBJ5{Lfu~w&~n-fkwSIUMWhWEKplwSLXH(g_-`2WP4B&{hn zWXlL>@cABwj8}McMl|38R^*#S(7gOMay|-9o(@xP)gQ^T7 z{GP|z**uVFq#bm6{Tbo7@H#-BL85LymB}IeeIg$yP&4R>Bt^d`SoY8fsa75K_#t28 z`B6DJ0PT5N_keE;u*0!g9w0UDzgbbU7QdbEIeN?8;HG@u<9FI4S{YX*j?#Az16_Oz zN}ta9ROg4d7Dwyp?rWe-)=lOkRD-6@`p@shJx$wp9qRiDvD=d6n1vmTYB0j;@uxu# zy{CzsoIJdDxi+23GWNgnn$cXDCYa7?03AxowL_OVb#n?=v~VQFe`LORQZP|&Oq}a`E|1`BMyh(vG_TvH7 zZ?i%Qe(92{DmTU1jBMy+l}{)&7z_V9<_$MyQ}TN+&XT|(f@EpwZA|_X)oap}_Wcqy zK`llSf7aHtH^)6Z0tXb%CPC2vHZQ4VD-nZu<><4?m#@d!3%rbJ8t5F21WC2^cI&&K@Vd(=*>g$`d6EB5)aQ7Usyg}#SSM>*aDT8ms3zsJO;}h9)m>hN{lHEVNA~K z_sXbDNiOrqb49=Zx((+Xg-mkGB9d}VS>E868ow^lJIkV$7MGOtEGx#@{~4{1^1hDR{Jh4#Ms<-a zcELkx>OOzu)sY0na||J`B7c?qHl5gqC)xD@0#(C@$PSd4!} zn9}deSB_Zp1{#7II}|}L^+e4yHhbHRnKrR1Z{B<=YS82Qwg5|@Te+Y*!`nn_sQTq(Pk#Ho zzi5Zf!L5!c)3K7ESsFf8rTgJL)=;m_pf`uuF&LJ>UEGy07(}cm_|eQruVblS1vz}z zv5P^3XmX+6izpls9f>3N4nryq1Q=m;A_U%~aFH7DkQoyLFEk6N$XIxc?dWC{+1ha?S4tnkBoJ-2J4@VcK814N5GW7glF_v1SO)s?*w>TiFu&PW%EPLV-al z17RVBpWR|ysXYpsbkizY>({}P@^W&7$@J>-%qS7846iJ!1PUG$uaM^j{(9u?>&qx4 z6aku8hUE_hWe|M7E)Z2QVj|W~kmomcLbdG}?$?F4K1b>M^=iuPH6Hs1pvJy$S;sdv zfSyrw+=)N=Jw6cf8q}vyK$G0X|DnB;|J}I&eicN{E$garU(P>hIrV>Tk6!R^L2oUZ z_)+}l`^6KgYB`e8l_s+k1$Ijh_4_jta1|axV;R{fWFu~uM_I?&av<`ELv?R(1MvsF zJ#-Aa(w%Kwa?Yqov#arMb5oRiKUmeqlt?FPS(R@ntff=WaZ>b~UU0{?s+%|4Y$pB| zsE2B3Yc2B~x$YZuCsMm#r08kQMDKv_hdbMn1j8!d*FUY@u2_;}G8iNf%jt68_vPG_ z0Wr9+u3E?ROd|R3LT_4fJcX?O=3iCW(x@)KJ~d1WiCIDQ?M%Tf@^8}ke+9pFb^h<* zx7aff2)&B$JXhS#zNWH*=~S_cu=-Ttirsko^CLVd;%sZVD&Xw!I;@-nj3Y=GPm)AB zt=k4uQ4}Ja;)Gzf{YdbAWEGxfW!;U#74niXIG7@KPZvT?wkr0*C>^nb!GtN@q^~cn zz{qR};=-TN=8z6jy^ataJW(;Ccc2^NT24IudK-6;x8B2te zJ?)bza35HlJk6ge3dJ2v+>+3dAooSYzlf9wv4>`f1d(B!M&G#XVXyskGu&W)VUkG9 z%7PtPsmU>cMD0ajB6u=8_Uws$^X4(|TO#@45pu;bA8Ek7%V#l0@q{~#jQSGVf#S7! za?hxTpl*CXR)Ibp3$_}tOCU}p1p6E})?WDHo&u*3du|@r+e_rsYW)4Jh$gR;HM@JD z@r$V>UMU~mOQe1be+C(S z0vnR0VLu~K5D0QR&fuUg=Lw^yzTsh)X1$Ll`3}E0;JF7KdT;VC^*oFBH?oTosEsuE zgw8kT`pbmBRY?4`w$PM|@&hUx7?=@4Q9xvJu?I9gpv1v-KAg6r9BV*j{KT#e_vlF|M+0tm*+WPR>EH?4DueqmF;@xiKQv4Oo6nLcSoiRwO%O>2o zW-UCB$jh1NqcZ#T@|8Co*l( z6O&1{q^mT_P!P+s6v_EZf=BB{w~M5~r6Uxc%A-#jRNYSt&{k-QoZ*O4^6a+zNBB{E zML<;HgqZ}fRdGQsP1BubX8z4&>xlURBs)n8W>mXHK?(&pwcqrXv&w+W@}AM^sFPUt zrBgh822ash5GfA0HJ6N_Fo*c9X1D_o!xeGyyrbKhzC3x82Mq_$s+M2P6=P#IIK=kB ze3V#Y4(JZKaAJR(0}5&6q}n;;-jW*FyO(gnM>Rbk2R zzF0#Wgv0Du%ZXMG*o~xtolQ^ykZwXa6{y8kJlV3HV6J1Yc!{Q80y$I>d+63Of4 z6`_D2If0l==M3(t^r5|uQRZKGi`nAzO9@#mdA1#L#oX&^t}t)ZW?vs`eSZMHqU;Nf zPIau=4?)86d-?K-udi>J0bX}MtTx}^+|bcn9{=J7xYUnu$Rl)(c+s#yr0XEB@17!F zE>50Jgs(2bL7zkD{uwU;vJAY!#4p-un_k0 z@G@gx_*)nYTSI=!i7OBrII7?TP+QOa11q_2Ucv~Tt%PsX(RFJ0haATpuU-9up3r3C z;3C~k<6P|Yizf;^Zj|nXW-Tb_(CU(LPu_Wlm50Re3E*Q#j_FAp+Q}dFq;}A!P{K~S zdGn9`tdrL>DGsRrxvsVQ3=}KEG!teV936 z*EaYnDggrTs5bt;`PZYHiP6oYKZ}ZP#a|2te#=^A4VE8fXHR8j-(@yDT7jb=KTPof z>UcnC+&3aF2BT^N?S(+fiL(ZJmRO7$LER-1Eb%G{>%5~WkvAfR{v?p|Z443Ie~ZS* zyj6U!7uD9%-WB?`=#jrh+czmEK5hQ2jT34 zD)0`n8ZqC_xHaE}OA>r_#*-(bzg5*V{4qb_Pe_-wt{1D@2|xb$k!y7j`%U2xBNJ0R z^O>)Cf$DYuz4wx!I`yv5d_VZy(-9!@u$3nfrhxZpOCQkbi}Ted_kg>Cxg2r(-!6Ei zhd9gN^Kd5Ff4lqIJtULkmt)`|5dviv$t<>kT+lMcoT|+07$7UE+iqi;rr6L9dPQKM zQM=Z=d&dr@?3L(`a4b59ElQ%0qIze_frV4Mnu554R?FYx2F5pQ2!jj6#t{7M17E|3j226I-uU5R#9NF?}ZOQUe&F{QVM=41(R8V~6;?tK+M0QezO zb12xb{+r%_NKq-ei=MEz6zvgKZ>gN5a$7tsMm-USrl^?lN%TS6_{DwH)C#&P*Y;%# z45P8VnA(`x!Lpn?pXVk(G`xskSb+F!|KY>eaKf4t|G7Xcd=xQ+6F+ejfXIwp&TldH zX@0n_jFrNkSnnbwxDB zRbk8Vr8|07Ctv?orJ|zJiTp0xD{M_E-eE|#D#JE6a*J_gcx78QwUf;R*YmM%=ip|3u!ElZz34B8yij(IoUJlOJ+=4Xi=CQ{=8Hj8)uRX%B5n)2-g#L1x} znd>)$IANlOEP=BHLZ7xn^STDR*&%GHgs}gR5CltoVRV8&%qwaNWwI- z7H77s2_plSNpBb-pM$lDUrrv4qhyjbIDO{phv5?G zq62ALz~U}|mb8OU2^)u&Fd|B?)}MvXd}J5;uk8(~Eg}W)#@^)tlDlDM#z6wxled6E z*ebTbi5X_XexadWorYN^dw1{7r2ArcA^J;O{Y}@ZWA@KcRL;RmA_FV~w>JG`1s1%* zAt;WCc!qA<(Qv}SREO;21;Rb!BK}%3-{sGudpq-A8g-;Sfx<6;&Tygp!+~C`@DFRFk$Ah$Tz!l$)`%a_OT+UV#;{Z!GyA{>un5dwYOqO7)E;?LolRG5HMUqY-O(-$PQ4GMBP26Yxn39LDz!Q8as2sZ z{^6bdyG}!^m7`~!<7rRN2-yDJDQ!LE$TSgxNJfd|*y8NH<|3kVRvEI^7T40R+VQI_ zEbMUlb-GbV_R)58e<`;a+LN8H?%1C8)SAAMf8{*ZC*Fg5sF$c{)fGmaS^9W{rK zL}F2I(jCsto4dbIZNG~&3J1*d;b=~nun^XK$Rg~aE>6H%a=ZUWjKDLC$&Tjtrl(WV*(B%gonV2S8D|bXL#7GV0zWo|K~7aH{T7$ z0GV$ayq!Thfb9dU7TP-a+F}#JoN$3*4z&%=b?;}_P{uN^IjQH_H~Y`ffePUX8;DkY zZea%HLxuI#-CeuNofRK-8fYZA+&OX+F!3vc&0Q02_h#;cA^o|%=ldCvR#&UHMN$VJ zht*fdRcnf-`n$G>$SGc19&AbY;>g9x`Gs~|?v5q(yLfN0v+z8s1{fms=uwgEpc5)! z6jB6lhpbmR7gg+|kW*{#(-ADegCnoswG( zg-MP+R583iL|O9oIaeO_^4@HN3CyE@AzT;u4oO;zc-32g;eN&h*G1 z=i$jNoq5)hsG;8JeJQamL$_fgBmhkNYm$J1_B$73N@H_{s(e-{11)CS((a5DyfT6@q2e83yz>5CL)Va`xHFVRkWN~_* z?(+c=-!-eZTVo(1-WS_8-_%DDC*Y)7qY~Zk(MJKvU=`v%>LX`I1uu)PTCModxa3h! zFUAhXr87UEV)pZ-{z&pE>_Me|M$~z^nQqAsAxS};UgVo#+%jQy(R6nmzsv7x$=fkc zO@^0k!=hxOuly8!AmF~kXsXGPi78{YZf=)PjP~1|Pe+W`#@e_(J);j7@z{~DE^=u; z|F(OcWnp(5V$df3sS^0?elo%PmU%f7?U%g$<^+oA&v}*xo*Z6jA<23c#yKw(v}2e) z#4G#Fr}E%Lfq_hRxjvqjH?7=PiH z-LTd_z{MBtWsRVwr74{yGP3CCnp8{*34+VWxtD*G7YPqkVqp>e9-Q;fX;tnb2yE>S z+75vi;b0VdhA7`=sTortXa^Ma-hJg#FA2xP& z-^>Zea9g+})E&*v0)+e=_&*&C&{3;w`Gh?(I9p^|)gB{cFgD8WoFfEZVH}b)7@?Oa zn(8XhX7?pObHBt5(+o#9i90$l&1k&A;dhOmxS00u5WK7ua)Opp+Vk<_H%w3SR?H7t z8l9c^CSYW3$=a9j#{KwlnQ{>cN~#?)`z4QiJlQI9EmCM^#@Dd&#&2zgZM>1AHG{A1 zo9s#NzY4Yn-Kj}M?4=m}uUFwG$SFgpb}4kMwcdQ5U!;0J?YRx_A6kpTT+?Ij6uaLz zk_&&Nq0&{E>WvPUWGCLk?%P=;@8nSJ!lzlg)Oly7XS(fzERk@i)1?WZU;j9 z*HTZnw;h}4`xbTJXVB&bWm|>_@}vs_!i*zn3)-ZlUc2S=TaVdYyYR*SJU>HNuhFxM zOZnR2327&fymm0PLgmaYA}+E<&(BYjtbE;AhK)5Zr`YHU zK2gpLK<~a++u9b6$-^NADRWno0EWWmapt9x{9O692ncItLDwNUU15SVBH7H)!J%N# z%(5vEO%-pR<$rqQEaRS9#HBq2hKqw5Gq`6e#%K+x5r%Q$Lk|AI<7YoDph!(KI^m zE6mTI_iYYFxFi!xE1drh^=V409p_knoF`RMcGz2OOi)9Dz1$2aEctwQK z-Q!nM&b}At-RpXkHs*3y20c7H=#`@H-MGZ*Tb_UV&0zsD@?6tD{LfR1T~o~O`<5q5 zrN;z$-{&#=-7*d^b^UwSoz*_&`A0PiS2tblWw-6PX%)zy=T+z2v!{6GnV(9@gkAF^ ze}~QVcAKyKd@#GG1|chf0ZrLe?61_cm-Gyi#VbCuz1=l^^l4+=pPQFA*fTVKZjz7i z*mM;Vsl=q2*WejeAH%-KC4Mh-;iqHGcRdh}u|1{!?bd^zV=d2sq}iNO7kx?ly&s*E zPaQ!+l6?;2Sw6Oz?%WUG(scw*`iCphLYqV!FQQ3LI>Y%Sin(?{WpVwS{VLv6EA#>7Jv!k1J$aI0_Zn+Z9CugyItIg|+~>jQR$b zGy5+8s>qNKgr1Py#uGtJgEMZcUya;+=ZE84hC_cbg&q^{{CO^xoi3qhWedqRP2nd4 z6HbccrhGrTGnQxtGra1xsX`{sAE8e%1edb3sw%E@3U48%hpi8uxz7XahrU25CNk2| z!2uKVYzcFhS0Xd>;bM})TUr%5Q{qU%1wX!EXGQHC#700Ou8|ZHzUh+7*@}&rS0%ZT~?iQliW=!Q92v<*77?k`#Nu+Y<)(dhr`l! zPYJk5FpnMV468DpsNLOD24*brFDq^(^rKusEhqN+OAad~#Y2e)a*|f;>8-ctRSSOj zU;liZlP&+LX9oGc8w^cnCo!sajH%GD{PlijnhyMMA1PAk zJTMn*Khub26d=*|y8vM1*=8GSY6yb@5||u&qIb=8NJ0}Tz;<6(_bYvuYn(!&EvD=w zxJ556)N^t-p2-g#2jrA+G}~-ga{0i|Hwb>>6>QA@CY1H zNmi&Dp5T{*VR7+gD{0FZznh&;U(e?c1vm<_LNEA@7hGlam<9brI6~iwWMEI7C>DV4 z-h)iji@)sc5if(_B@&_a6|MS_gj*@axGa3?EN$1oMC@A=eY@QA*lVVVwc_FI$+SBY zr&;M~RN8019en)hZyt)|iE-pDS^%a{j$29fmx0%_25}NW=DIJ~u8D6&rgx`Kr{WJPw-TG%$h{^E4 zH?NN8S^8B96}lfyO{c_?eks5rQzq_E)J~3(vVQ;BFDfr0CvtOG`ODIZ11;8Pmu{HK z8x2#btPFhO>+Nw27@E}dt(-nIQPsUKJg7T+_Y*r4-^vZ{j^2%bH6mZTanXA3@keh_U9C}FA_M`vBtD@|DMBte~#B$hvyzW&B)lE zGb@DfI=D6=K9!u{r%J&Gk(ss&yy>bF-k8mkEho0=V?49>^;sVRG*9oElG2*A68O99 z^jE^vwB}k8DqO`e@iZ7V(OCaw?~;#N(_D%*#O%?>hf%{}ZX0^C1gkaFy|jV2G~62$ z=CrM0xzr@M9CrVJrJrQXz($$}_2`XYj+n~7aiAY+qbOdCXvdt0{4rwAvbu~iBa&9Gg?Rff23fWdSw=AMyKQuy>=I`3VB%V;M#328 z%O9K{P(bV>AxzqdfEb$l7{(Bl90>+09w-{yhQqE$eu1qbc+-mf1os1@&iCJVBi0IO zX=rM}oqznwMcG&Aw_ePk)l6>#__ZWE$jAmzUSiOJg?hI%>g|t1?W4otES=YGxwG6( ziwwB!WRzSLDH%uZ#JEZla}NvV=Wp01 zxvP})wiYtA7QAokaI1_`qS|$hv?U-eS9Pl)zI=PUv3#;UoX=kRh3WZm6Ntv#w8aEtJrI<+78CiVU&6Bfb-{s#S zj2q{ss_sOwv)T^&_4^n>$G1(8Z@kv17#yZq4;X zyc>QOlx2jKC5|xgA0_bi+AmG^gZ`cx{Y)IBgJ1Bo9shd%FUFN=XbjNk-zT_4V80Q( zN{GIPP^c3|otl_rlc?fOBU~S;G$*@r{eY-@0FsAN?U3E_^pw(@7l0YjDgT0X{w8FU z5*BI?Sw>x#fcbI25R+2uz3E#q4D1i_nVrI{S{vS?&w->92p(D%WY^T76DWi6?hpO0KDH~CPxh0K`#l$AGw#^Ra0HvhcUPo=q`luJ29_<7^lrZ z9sRd9P$+OIUg2nipG?D+y%#@WL+vCdk46G72HFd7CnIv7a3#{J*${>;L}a_Sw+=>q zgefU1q82dIKqV%cQ3An+25;9<-WP=VsXE@YXAp($ajRP2magq4lyT<_r6Z?=z9K`4i4_sL8a2!xraC?yvUX4e36%Tq0 zH%l}pg7bJV1I=tSKyye;%CN)dFnrmM#t;6&S8>!-BXk&y10whU>3~(mib(XR^VQfv zPjhor`o*fiJ34D4_1Z^zy~n=ojyH{`2n=QG-=rP!z9%>MUDAoO7X*4~Iu)r`-XFfag#sxK|2J_&h80t$-__W@Jhfrp-&?Eu=2F8Z92oFYIc%1wV@Ul*1q zu>|TJ5_Els5X(7aQ5f4m!Yr$#G-lEB{rmUiuz$5^SsE6umFejtvbkkr{Wdz`^?Wsz zoJY=od(PHi-ye6-=3XB;<hjYiDjRh*+Tt@iJi7yvlKxr$cQb ziY+95^26yPtu`5hC_|*n-Y)3*TCE|ojgf&Nwsv)JU|{TPvOxNoz!f2(@XcC17eIpVl>@s0yn=zBF*=UyWK^Y8064Wf1?qwp z+WFR&zM5D>d=36&#m#QmaJ}Z_y_=gG8XE2_HD=T$91y(&$3kL^PE-?EKSiaRf1+f~ z{i)+5d*H~C>xPDgnOdM`Riej{(OLHWHovr_3&t4ih7f77L^xYYlt|pckYxlTg3_-1 z`G60eQM0(r=kcLnn*{Aj0K0}1HgZpG?prlbdx_a3gcaG*OBfWCgVO;qnl2VUuA#E= zbyU%_AX%G(OW7)sXH2-RyW>D0bQ5}986dgDiE#~cw?6$1ueL8Bbb=r6^b*2Az-kGT zcG3a3qi`2#;Q|3VwecfDsTy}4^j!k&1rTfZS4BfPOj=H^99+;l`Bp}mgC=?73`FoS z6o@R{?eAq7vC9ebU0$d4iyDrLRQQm0F$Ihq42UcUnM+Da2vh_YnLozxAFvuFoo0S4 z+Vwj9b&Qn?SN_p~E9z9d+-%(8*)mNgE7Q0cErYjrwre<}BFH+@NLm`dUy$%xSXeHQ?ZgK8BkCSfNvi)u z4XrsPRCqS)mQ`&zGJn|cf(&bfn)9B!=_(xo1>Rf6EoZ*oR^BS(kQekiTXwd%GV;@= zCu7k+^(>q&TEVuiKj}$<#yrzS1$XZ6o|^LSRtC-P6RrQnuDLD8-;w_enr!x=!I-2|FGa!0w0<76+1#x!u;STR+1ME+v1OpP)(*3XSx^EWPHZ z1eOXJ#~eOPVKWbA(E|YV_^vkW(7G+#)#4NNSOiUaFA~H85CK*|mm+v&Cr@sH*Cftk z1g3Ude7OmMmm5wtyR$Q{KMA*4TrP2HmlH7qtpVPK@R@zYBF5njj&d`u7A&$_=wF^j zMrv%)p5phHtdPAWSOJICn0V1WcB zEJLS`;9G#!!N|iC2g(@6CI1_ zUU=0vLwT-x;bYO~8TF3kTWKo#!cE35&*;iNGhXWA`fk2M`rwr4+Gl;aAfrqbO0x;g zCm0s=yTbf+0jJyo-g-WxSF)ilcXY_F5T0r%UME;@F?BL&!}p(&x;nsF%Q!(r%!iI2aZhZ`1H{_womiJr1dV3KvQD8oe)>1Gal3+SJ z&z^-m7xv3!Sw>szYgToiX^zG>S7#nuQ6F<}cE3Dlxcx=_z>TkrNsNWOJyS|`zU+)k z(Mr9_^VGS*!>{N6ES~!=uqozOc4`cj^UNj;MUg zXG4V#Cyu1$edUW)7J5P5$X>l|k(?Lxb>gVjr0#)O z@;D};^n;=U^n09&JT35OtVmc?V;;hT_6qLOh zg4W%p+b@LfX5zc|1-lD{x&#WZBYXnVM}GbKg(dR}f$IMUTi+edb=$@*Qlg?ll93dm zNF-Z!DMUh9MOl?X_G(EHS|YPZL}oToGAha5B!ujhk&O3q>3-hh{o{R(qx*QeNBDiO z?{%K%=ll%V%}TSjS&BY-@D3~pJ{!{JDIb$DE;;D5(%EgOE31xB_2T-*z?~hom8#) z39269?Fq175oOJVaxh#{UX~cHL&?^_5x3COj>eHdb9M`a@q(9{tqBg&XstQXv8!g5 zqo+NGS6YL5$E`IR5~>^0#RjL?UIXWv^whaq#+#y8!!T?v;$q_AJ>eYQlLfoFvSgF0 z+waW_7`}fQF_ZS1pbVRulDM`YjvL6PRGZP8``Q_CeJgLoG;5d4U}Ifb4fI5JlsZGNxMcsTB2|*-I8>z z$XdPET-M^(I5vEbTwEf2q}uuDiw)k-yJnx$jT!c^)s?Qcb4q!CtxME5H|Lx3j?1A# z+w9c^ByWp;HE!)}9t1KHNIqkbqD8!y+C64F z@DM~DLlr=qEQQzui+oywdSK0p5B$CgH`Yh!ZZ~b&q6ju9zx&buESfC4TW5TZeIC#E zqmvcUJPe)t^i$WP$L^L@@80{ok$T5zZ}W6I^^6BbOy2P#&=-Mfa};}em+1z*&y|(b zOW9XnJ+eO@tIP$bd(wFkImd>l7onyD3QB`xYu7G@JCtN};1O0^X9l!shKql|=6FnJ zfoxld9dTg)bh~|38gQTWYp5t-u1}_fCSx~$*!6D(${06+eGQ=2a1z3bA3p9ci8BxU z&yB3zqVh|3ZGU2#xw&Kq2b?dh7A0qxnKoJMXNYNP`om(ohDwf+>6{K@HXZMAu`LGQ zY`UChY_q#5R$Eg&Yj-(tIZWcp^KI z71M6>%|Bxg+xT9b((qa<|B!)4ir-g2v1yz_T>I}tTrC|7AN3A}oDuA&EI=(JihQ(B zlQ2S)%Hs(}Nkm@Zbu@oOCJKG}cIhg@*o07*Wb8ga|DpxOq68UVpxb%S(9rA<7Ji83 zVcbR@v~9T3s}q9$<{t=df_0kQxl4&Ha~(qKi8phCi4!4R%{ zcM3T#2SDlizmL@Heju;YM?F5Cld-dM%_*Fm9FirE-W>n#Yv~`Q(laUSdr6Gh;=&`d zw}jl5J*mF$_=UxRX~P3Xb^;p@&;A&_KPJxcPOpQ`K&kG9_G`;mw=Qyv7I?-4cy0<3 zYN)%|aR1kHbEUOH8(*DvvCh9|Qg2dB=bEyrK-_QP6SeMZf<#<6!%GF6N0! z9Ix|l!*sENn-lKRor_MwF=Ji@trC(#2Nm;g@6S2OcP1qFSktmCG2W40<+*Qgp9~kP z+6t7j@0oYs8GkvCwCgw6W8D%NB1yCiX>#xj>}awg2d2)b`~octd}q6Qdg|gOtVLM~ z3Nc!=>h8o-g%fnm%Jl#1Y7J`GkuDutr{wBYOWrj|QhX%+`Qq zzn>j#&z=6S;9iX|%|4l9&+F~x74O*oHv4|pyIX4|%ta>^TqT8Wd*|w`);|As0>3Hu zSNZB&jCkZT+3rl>e4I}4ikt5=5aQz_I)&C^{St*uy3^XmX>!Qhpt0MJUJ*U=yaTSgGlpW0u)S8s?? z?h8(_oeN_+#e6+pr#jonUZwPQ%~#dDjunuI;)ui>hG;E5-|mdc9}Ie~ zydUH**#lvUQEn7li}bS@KfVZjZ~i(sG_+j1Y=Ox!iEAejRn z<45mqFUtR(3FY-_!RpIgPw8tULy7 zdOxZVvQ~gkLC)eI9laCsD*$xo;r@o_9Nd%lwo9``kRQoU{n&>XVj~H0Jh)r?_dfu4 z3kaP$hKWIUHgaFnXnE~@{o3%%yEO*Jk;kTLBRvA{2Ak}h@ls~T@|b0N_5Da$*|{eJ zG^VL7;-;dL>hiLvKo8B{o^v$o`CQVjBo^Ls_(f?@Kzh(nT{?YCTcAp!?V?A9WgZAd zN7~H!jf=GU?a0B32yMDjZ`;O7MR$nP;zN|Gu52|gjVX^pqFhi$MZ5)=E%6>y6C<+q z+!G6V8%iIHi-bi+X#Ad!p}0MNF}F8V_Zl6&W$i_umCu>g@68gEf~aq(#>@IXrDJC9 zpV0ELLxWG!0fWAC;%b5J(s>{@7*WPnKZbWem_bzkXzL~uBqUxn4Uv!oqPc+-$ol>I z|9s`5?+sq#HdMRwyC=Ew@1iQrD9z>h#$#s`M4xQr@^rqaqkk#WV8KrE-Sb*X2E^X^ zsxynOdhsh}zPrtXFHhG<>*8{=ORbIAxxA~s-CNv*cX605l(daExSZ6{`I%PEeLLO+ zDO@Co&Obth8Wej%m-LUgvvSp5mAAdL_m4r=bN$N>p;0r#_5}--y5FUoAE>+h8Rpxo zl%ieN+D6Bj$r{t4foWep?c}9wGabfA_sOG>$p4s3yX3e%ziOnfJcCm!-ffe}b&VJ^ z_j+ej1=Dt4?$AgyjK7Vqq7Rr?&dC-{F}c07=5@1D zME2mvk3XW>DJOCyqIi&Ty>{!TMdle7JPfi#N;{KuFyIi&dg3<%`W4yXtz309d@bx_ znj$}ZN>7CMwzkJy+>v^4_3b828wF8{EB>MJ400N=O*XT&Wa%9@b=f+TwP1QK;~p1o z(_h6>dXjoc4KD(R@5!d~zVci5bJX}oSFsgpGd2btJ}a&q_@28DJl9Y9d8I>b;)9}1 zsg{eC{_pQ@1$Eg%kbUgn;aQXV&^l9xW&75x{RP**Q|Fc$V)W{F?{dkWq_!OC4E-Fz zTh|_!+m@zOw*}p2e2VXY@OO@Thu--dGKt98EaD~g<<+u`mtvj*>7n`_n$lP z#rVcKjWCZK#iZ#E0(|QYJlANp*E7A-Jvp_Dyn=TV!&l0zp;24B7*WLI65&oH{Q`-EhADqQuYYy?nKqXkJio!7A^A#z)J0WJ;8%4U%>*l-eoj3Sfj$a)&W0};yHI~JDo7Z6-eZ0e}Bjzujh3^#k z;Cbfx%c-^BU4OIx-6HKO$Qn_oYkRFLV%@Xf>m@oD*M4lF!psdb0NVzD41LHAxd1Q` zo9mn5;cSzWlc_D%MN?^j{Q#;>hs3U&Z-z*Vii)1v!GrqRCp&y&)?HGm&KR(4%UH7m zgb3qC?ve}TX}!bD_r+q`($*-iH&treWLcm7X;G~&&U;l-1IIlFql*VUzdu;_W6Nsc z$InltJlb%X^@z%ihgSkkewKJsQq+H!%VyXx2x-ifVMaqScv8l7 z(?W;*|2FU_g1MN`T8g~nD0m*smiF0W82%-a9FTIeH zNHNF0N&lT|=E2&Vtp!tS zcjRy@8Jh@Mt<&mMSa7&Kckwg7Xp{9q%hyMg51`zS`Tw;}!k!Cr70I{WFl1gh@GJyO zOu{t!?J}w+?^B?$a_~!fm9!XrJs0lXyxjJ_ayH%b)|yTG2UT>etM;a*99OYjGo+Ei zUH4$X@lC)D_GisjJD1LQkq>RJqF^}R>7uT&krq?%7@RlgES$@1X^9G{{PuVo%f1=k z+5T9wLt!T_c2t$yh?!QZJ4pqQgI?0`a!GdGI*c!5VN+t7CZ1dsdN8rJJ5U>PN+9H* zJGnZ0qG_S%5siuB*leI4W zGEspH8(!M$A5JQd{LHrtok&3js!ufC*l=T-Ls!Jay}?^wjvdq0eQJ<)+cF^%3;{I8 zcS$%v0dlFwR(PpdCZlB>~5Soap#F0oNSBlk-Krw z+UAlIw@6;q12kgKI@VY?eMoNFG0i~X&0NJiywyeA_!MG_oUJ*Q+dV^*$^~`LEUbF2 ze>_AmeDag9x>ekXH#~tQo5@$rIYYK03OXNPENIgq8P~Q1OopNz%SXVrQZSNg2+s`-d$(W(j z_N^4#c_v#WsiCLMBVILNtm!fze^MmgR<~V|4C+V!Q^7^}%3$+Y7x?cw3Z}bg&eKvM z`<{h!OqVU?0dq#kl*MmhVU90vcHU}u-&%L^kL19`H@*!?`to@@%x6O?pEurm+5A`S zsl%EX!P|2w?`KpuDcU8o-^!irUZ=CQ_!0D4u=>@WxY^`Am3f}m(W%!}9g6M7nlRLV7j*dw8W;^Au9G@U^cQ403C&pkp zGk59J_)tOzjCnUi{Tj`HHEw}H*6$PMOc51w<+a?$Bp%uqkLU~UGj)F6PnEIGvggKE zrN`$#c`FK>U2Z#Z^+j=qviY1ugNX9>qL;0&UvYfA>{7~HD1J4-$th~LM1D@$1OTeD z2hEO1-ySf%X;3a^bo_k?i=XMiiaT*DM^umyC~wCXqZuE%<}o+kl^H38ff@@NwkCQ= zO)%rdX`2E0)98?c$>;IIhp(4+{|k`dx;+;)&QU$Frm5dZobqU73$^CmJ3o7pUZ~qQ zNp?C@-+9qEC~fDS*|;|4`F46667ySH8tH2u;z07JIVIk}E)Ix7~Sf(hr*S6xbNPjL<#j)PfB=##8?D{{?Isb9y zQ>=N}eEF&)@38BJT2le`&9i0xAdu>oyS%bCo3^F%HZSdAKX9LUduO@P`H8HPY_h>a zwe8=t+ixWyl)XbB_1R$Kx)$>T5Axk4vbqibQC?O)8$E%PP~!UT2B8Zt5%L9c&UL zj5y(NqT2o6m2*2@Dcknyo^5vD^2CHZxkUqS_l*p33vdmpV~oP9rlea#ev&xa&ExaaU5^4WXCdaFrw`S+#}&I_#}pAm!xDUrWer}#g$8e zAzC}Dc&A-cQfYX|PhGam6e0POanFaEYZRZIdUEBPg<|oVM^leG0vc+wcb}Bh!U45 zh!~VHnHs_>oy+1rMW=5J6kFL9{-s|*wFWNAbmCK<>hRicbc7PTw6>xk11TdtGzW7n>xYDvF5ZG zc+_Abg;w?V5gUB|iFMPp-Hbpcu(w6$a1fzYFJog#Zrr+cFF?z<&w9{TYBWbN&-=d% z@3sJ(Fh#b^2(Gq2SJ`uHkC^zJ@cOW|XNu-4G->FE>T9En;tpJt`EDcJDE)~#GJsd< zcWZY!1hn)^3owNLdC(chdiT}D>&YRW*^;X>6sNZvqle#%?($-|MPA6^(b3)z!ClWo{9-?@G>%H_D=T$l z7Jn6Dnu_Bj;I1#Qc=PR(O7k9YfXsm5VoiOky1J}$6E`of4_eT;HUJp?+V>}WV0+#E zjSgy#U=a)Zkid!maRI)GCnMAwt_H*@6y`NNdp^7HGR4#!0dJO=f?`W14R;i2Fk_|R z@W=64^}pwmOgq2hmp%Q$g5Zzh;74VK;su+rR#_xFg zu}=CnV%Aov@+Nn{rhHTMYp{ruu7;__+VM}uf5W~{0bdS2f|L}*j1&kc?!30gR{!2a zEsN5%^pA=h1biF%blJdFv=lR6&p$T5=T&NJ((sX%4^>+|9i^TImiZV+eOH@h?vQ!# z%+hXH>_{tBTp@MX=h)Z4?jL!F1x{TI^_MWX4~(iud()8@pT7Z}@>G9&F1^dq=l*?n z?oPI%PuPXnldjr=NP@{u=z-m}B!#l;55J0EsjDtY4Qi{d5BQ)VjgjXDf* zT`SGi;HT+HQ(V^G5E-N+394Mf@q*WN+HCaB=-A+zyxuYY-NUy+d(!zT5XUDc*W`Hd za!Yz_-E={LboLZwj&n_Jge6S>NMH(HmNzFBw4MzQU*d52xe4I|Hg6j9GN*{g4!M;R zwJo>)a`hXUI?&zq)v>CdTS;c3Hr}c;+Oc0=hgFEtZ(W`DNe+puJtK_14Uo4^ACcw? z^}Tshj=AND;MIY~r0vikWCjRhdkc9RTmMN=(Txa2W5V!XYkk$*H0{CP2ECt`TZeTg ze7+o0{Bhf0UHJYN-+krMH?+84Ti_04^vc*^SwSVkNxGs_x|GAGPt^YTSc>lM%PXJH zhdwl|)8$DLzPdljaY$t2tDyVKk{{Fx*Jb~O!+Xqt9?S0VhRYR})@DN-P)1E02QGIElj zwLro-j7?XOq#8g$roc=f<$8cuORAE11i|Zn_K3#6Po2iL;g|cC+l^g*i;qndXHKtt z>;uiUMY=tsZnJK~=2WG;Gp;{#lzC^PLpB6C9%JaX(_LzRt`@tKpFg2hqIc`Zxw*Oh zun;MKCFL8P3ipw~{(k_!(&ZLA`_D_`-0RbP{!N7A?`FH&vQrFffiMbOQaL@-e1Oo@Tw6CLU0+3u*wf2$=k414v25fw@5BRpR5v1N1tnqJ zP~l%y=1M8kI80;POE3<$;EPk|GA;$fD9;)GfFw5ePOx6jqA$M*1%9j^yZe*WJ9^C; zPyL~M04%n=7k0%E!urydb0BIHj4YzcegiR_?O)hPkdXHK52*Gk zFpc&1K9!Vv`bB^5?#r?xj&6^Ct>IQWsWYFS`8yNHrt_p^!DRD@Bct*M)3oO`mRM$L z-Kw2^9_cB>uasmYvSGTg^z5~?x}Fb#Y4o0K8JPz9oliawjelt>%uUuEvj^<^!Mf~$ zGYvL-xS5CCkK_N`zn8|RD}wFVmv$UjNP@9fBNRA?R=%(Mi2MLn(GIjSmQt|KXmF(WF4B-+Ljf*dGsn$ zh>Bz2u=<5yF{Ny=YEtexaNavI5Q3tEG~ut{>T=4-g@dosjpyqY~{d4LyBaYHIZW9oFm0 zk%N18v8qB004gr!4@3^)k$*v(CtDWCN&g;f$B*!jYx%V8RPvtQfzM4{pmTfxIdUCI zDy zMc;2xa+)A(u$5kgw;cg1+SFPn~U={Vsseh~)>m z%}?;E`CL_1j>#3P2bk6$h1m^@s3ik&fB^V?`19BkxL)9Y(z?H2*A1`*FD@>IeJ%28 zDJlmEQk_Rg6F)G7te~dzkI4&CfniQefnl0n@UEARx5ioLs8+njrE4z6*AAIHiQhE+ zKskm$xJ9}TQOP4K3_gMAi$K$Vi6r5Dqf@m|1{R?kJj=%2uC8e`;S|_IN zM`s7;^5_!FqxFV{ZkxbE-MXgmgxRLMDzRNOD~v469PAG%Plo8^vnNrrQdK^*N~1~agn9%EmIQZMX$(z9csNCf$f}i;K|uz z*HR2=Oetgu-!KS*?o8U_v7+V3AOStoH2rXmCs-^p#t9qRQ12kTVB>ijN#W zeyu$VJW|4a>}8A4=+EHGe3G4=UA}d``BK)SEe_TW-bM_{iw1@666l`Ss7V&Ahq%sm z%j9#kBnOC!dQ8aUdxI|gm`Sz=?9|u-;mLi)sNbT^Lz|NHVUfDyX?s zB=EO22^VUuS%-a<459!ETGN6nrizWQVhB<@B{X(LY4PE*Npy`v;i^ebA3s&2u;B3x zw;k{N*f{-IkLpoVS5VSL=N-I)%njDVTcVb~gq?IfC+>Fr&4v^PQFmr+A`gBOF*?4( zY1Z!e*3IutpMTPE=|9CcCuYf+5G*9oZx6?~03+*+Q&(pKytBlk9D3H@_O{cNT;`i^ z&t7^kTBPFVFB2#7XW}?BbC`vnn!;jcyPdi~p0}F~x9G~v+$FKnddA~Ar!8fJw+R}6 z{a7?09ST8eAoL2s#?Ef=l1kaQ%sE|2{kdu#WAvnrIom6%2x!w6Z5-Gn?9ft5_bb!@5F)?mkMF= zWd}6r?D6gXRojigoO9>6Kv=)@z2F&aX_6kk%|C!s-QJVG@!$nwr_(cyU93GJBivcP zjaZfYMn8D?@B;`5W$2y%fXl@y(N-n`^Gxs=8=9@)pIrA+TRufQ!~_M0O)%R3PtEMbkJST$9;z;A$u*EynEs4rEzPO1b9q zJC-eGX-xgyiBnIz>1~w-MtEqv!l}SZeNz_#F_}a}9P}@QKSh4M=iEN>WGPz*WqwG) zZRM%iW{Q-QKFsbCfP-O@xNJ?$YK_plt(BE^a8bda<6U^`dn{%cR?ZEZ-tIi&BH-O! zSL(hv({$g%5?>*bOJ>`agKHwn7X7~~ZFxkh=dI#vRi!#^^5qY!tr(rHWVUA0I_4LD z=oCs@RFb4tNmMoa|1AyGgUG5&dJx>~fQncXCDQ4B;N5!$Z{n-tgH?^ zgOwM?hI+SeL4b^pc)wHUn?A+13Oj&@;Sq zIJ(_XT>SJCDkPzp_t~Yg60RO}MI~h~U6G01dI0>Z}B8}%H zNl@1KBy4pARVP|W7lunkRYPMVP}hg};e#)C7ItVY5E%*t;JA|$_uGI7N5F>j=H}^k zyE`TKV*-2ZaSNAJY{2-@w}7!%i(UOJriKPEw9zbdy<54=>iu=vs7yu7@A}EP5U3>p~vSaMctNl@^??*D7E%$8kU2!Me$Q+}z<9Umb z5e9)sz<%Ju#Uzxt3Y+3rPt3r!d;i3atc{IzskUv^X7BONskK@GuflCkGI~Q@B$?dl zK2^!ohp>UTsL2jF^izhT8&_gJxRfq8)B1fnJ#i1@8f!VZn`60amp|N+)?V1e$r%WW zt(T_PZ}#6drL|7gJzoBD{7Jr`3KV;M{9|7wcWk0OsKne`r0rbOP~^U)>a~i?f4f>G zHfS$Ae0b&jpTMK;IYKQxrJOQn4)xS*&)?q{s;j;5@4k7qg5s-gTYrZ(J80STZPsk? z$kiGaWIuFW^T6Ad8EJrH3+ij4e}BNF)ZhNTzC6*Qg*iAA=0u$IILSzz|9O{O&_caA z@260_x7*RkU?akw?av|{UA*%aCa`>uHx(UZ(O48kyxg+0onlwq5@9+)F^9FU1j6Pb z09uqJeg-Uu@w5iMzmtMc zjJTr)jPj_%vn)A=!5oPsus8fV;hx!Y2{91SWh~$b+ertndZR0VmP(d1v|yOv(yE47 z1$NIB7-o@|qK7v~T9QuELn>vbmMI-C{cVoG&CSrR<)T4$wIF@qYAs9Q)}5h@aV zi{VMW^gB+@77Ec+R6aGj^>5$4B>@Q}&=M8nnl!yZhUJIMCgTdM4u%$7ZIzMO&|?QO zt@P%TT8zZ_`N^J>mTg)4z_cYqf0%OZ0biFdxSGnjAxdi%wtv(I)NHV=_M(OFe)#<4*ps_O4w4%4f1 zo^oSVVfbu_N&ppGcXakJ4VO)Rwcv?tN)?ii-B`iB}4%K_$^3QY2iygT{Q63a{J zTlx8aM?APz@S*Nxy{mhk-FJyP`QXV<9k%QrXBVQ_1GBV3{0`g#gH(ru-`X*L;F0q| zsd}Z@QxhG{W6pV{b#rcCIuo4yGgddtP(g(rNbE=SBp-zTlXA_ zc%~+1lr&=dazq_HOf<_JE|zRzXVq?NOH)@}fnAguYIPha74GIo`S_IYixL{5j1^$c zl@@Y{<&XwgdxlTC^W zk4+{VZ@fvV-RV}8P#6F7=683h(hq|Bqq}XEjH)4KG6+Dj0xt}&%B(XdA$&l3%Uw08 z$Y?&ID#7akpVW_N!{7&VEKQhPxCGx)iAihjfdM!yF@3CI}XNCfmkn zZ~-a|Ab0v0DHjXo5%u`rV52a(mK_M9{Y z7u~GrWM&Y`_v+FRi8PsB(8ILA9ug)Aj-}fHhk*S^1fKxqLZCjj?ft?8PTzaLg`d8D z{Rw_Mub5a?z4ss}N~n+I#KVF{5!78$B-;>2Nm!xSt4&av;bxn~Of{80 z4r0)IU~8F*h5bR%Atewu4Y*uuAAc@^e%7uc1l(FPl-Kp}B}VHX4x9?cF8TmAi2DLA z1ro)GchR~bq4&KLY6kza{H+<=sNLk&BvSe9nmq7hZn8MnuI7}Eh%ATA>W1n^t{sID z2Q=e)t6ykMkBRator3YX(!Fnm6v+C>qI`fSnEGGAmY0`*ZA1b8OPC8{%Seyzq5Wz5 z%Dr9EQDrUq=$$S1e9X6fp?ad$*1z3Wr-;Q~N$;h#S+a@G%>Hiq$=EuH^^n)0`vYZ@ zYyAc*FWr1;^dPm=fnAP5dU1!{{?*F5bL}1IWXX?h*{%Rs=+KYZ*<2CyPtK6)-zc`>hAr6M8mm`m35mS`KvH}_nVW?J*d&TE zQRsC~KF~?;-yZf>!|GnRVMMFxa7)!O@D@N6H$vtV+*nj{`AZiLT#zB zY)GE2?!v@-DtZLqZnUnCKj&5mUIPK6kdx$%?cfxtT==6pCKvO7&JcM_q{mE)Y2Yj} zaiA_Ivgz8|T4F^^_FsKE;7NAb6>fO(hnu0OF%7we*Oxu2Q&0B(+cn#*oym49TtzLAQ9|7NADCMHgQ6ZxO42X`8;8Qo z6FtYt;pkzsMe4Y7uD~WRV!_u24j24~vY{&M8XdLyJC8kp1uA@Y_*Mp10H~;6Hfm%WGTG3w-!^$-DR|k5)?ZCuD$H-g?G$yCn z!e{5cGZ|;$ft@8mSStKor%w69JBEe=_di-kKPE56*LZW&Ok?rtbRIA-@?2D7^kPIJ z+@leceJ)UR|Dlp-^5UW^;Ra&NfJU;oSc1`Vl#y4i=58&I;=rvUWC1lw5vUb@QX& zkN0uO`<7_d`~2Ed{U)%kl+!nVE5|f94a>rsOiovhsyqAe15(7dXF7{rIy&ML$$NHu zyK{ZuVcS{L`3H}TcCpupA1vxBsE2URT;U-P^xhmMvbI_GBbtvzPH+T;PaCN&a9go zu^NPE`xBaFn4@1xD~&@3g7r9bxGzmh_kgf!0qiR1Q51tRHpUXR-M1@DPaS+`On+i{ z(S$i^W_F7cj8d3np9%!%{eD;R<%=oo8OTnl%RS-dhqI{;QiG7~BZ`Y3e!n5YWJv{H zNa5m)0k)bZHtq204EkdTS{ue%XjmXFzpNJtITKFUi>4tc2@X#zTfo=fAxE5BTs#Ei z$lJs^1?o$HcemgIpC*g}jT?Rd?~|KG)HBcP8gp7Gp4RZ4YY_fert5Hn0cyN-pqyBn@TiAg1&WQw6Y%+b zI+Mg=P15%mEW403@dQ0)erF8a^V{~&E)3YtS*`gQJbd>QkKd+%y?1OLACB4Ql*5pH zETm{-5G8Y>M^BYuC0EWyo%qTe)KJAVbjo~Ay_1oVk!X*Z1zz#{;k0(lPEICNr}>Jz6%Y@9 zd`IA75NvM%P%6>Up?S$bQ_-Bp-wn(MPe1c(e;7JkYrWf(#G$`&RrH&inFy}g3;@X5 znjaLB)I^4z($WQA`@z(}lC-_O-N2C%ml{2sh4z5mnaxD;WVejWyY_IkDIzQ;0w!1n zfniC+O};WF_1j*eYNLyIqfnmO1f!x$DKTi8;~SWY;?uEdBV9b{NxD1Zl~DV;c}z$PxOV$6u!>sMmWVC)jS5CcI^^}HNz2Hf zQf32lmzbMaPeQ?AnPNu zH&|cr`r?^o(j4I3-LJcM4c+`(*MhLN&b0Ks^CGST6x!;;0)wPJYT-!8l3cn>G%A_A9-rH$8S?CnmB(L2?`mvt1r$W0w z!YC+^V5*X9W^ZSw_BHWSnxSn0JP6(bdp6K&#Tf&veF&j3(O5;1t_^3ZE$^uKAi56{ zpL^l^qsNa07@a=cq^9_FE$=}7Q}nSa_xTKWY{R$=hoM~C3V}~ri&uR3-?kVIPJuh{ z632=}?zDH)$Lp_UxsJIK;f5zEaKijdt0EA%qq`S56-VOyoWIyl4ZXq7>TQb3PEhy% zFpJz$&Jz=3M#|)>0}M2YjafQcOwuX>pS;X|6d1}e#x4}()=_x=#I>8X8?v}gwM(5+ zQMofx7?ZNxm+SR&*iDCqD`7*CO7-x$@_r-V8-6~9P)2B2vYlOy4z=Hw8}c>XS>@mM zf-6ByYeMzOw=jw6n-zPP=3;0xHT&x6c+~p+v(=Q}SuOM1zv0ZRMWn=knL{b2tQW`nsccvzWO>l`X zy3tizz*W>b<;+6*K!rizDf#SIOUGSPw|UtZ8vMtv9JQ-mr9`7OFg#GLbw*y*!CG-( zWP}*3ut;9x0|4EP8!R?p3NDMtLn5YKt5>&2MhIq}*$0|}1X?COPWSOuFj?WV7es99ev~L`0#2hpwVQ*TtZ02em2YK*D-s*gQioM2d&qwU;mR_ zXa&w`?GwH1W61s?9@7LiL4*DlzDf|A3A6emKQNehoe??-KsZ>*EtGb-8;xN80|ZBP z9VI;9&ypY;U|Q=~xxm}9o>>mg&Cp%d!jllV1uVA$lit?iv|6?22%nfQvY&x7JGi7G zPZDk-zT%uyjVdfHb5>G1VPAb%XE~pzTg@UR;zH%X!JjPl zY96vcz!GcJzD=sq9hBZ}-^VP&e91o%H(x!swZUqVX@P<6htC+GKNW zy>8dQ(%;Ckc}lvqkM`~L6G`7wQxeW;`Ye}=d1!uXgj9~+<-08F9d<^zGnJN>m)|O5 zuTr6Zo>W)=TEA6NeZsQ-PC-rtZo}Ytd)94+w|sUEo@g@|s!v;C_m)H`5)EBYax~qc ziZDGUzxCh%eL;1A1%5YdOVAuW0JQ?8!Lm7(3y&phA9Py+%-JY7kKuyz=;_lk%*{E7 zJpi)n1QZh>uA9IsM%f`89umL%!jO>;LAW}MnYV1Tk^mn06|yq@MijoLels6Hm z1|^+F?;EG|15a4<92K5v>5pAWr5rue$gfFQ1+A(^e+~K-Y`Fy-!V?Rfk`I9Eif$MX zV3}ze>GU|Dp}B6Vd^T9fMDS2!r5TzJwl6X9L)6zxP4~=9a}xdsV}3NEhAz*tdn{)2 zKi`5X;v0QT2=7i(O0y5%f=MZ+T(vJ zOsX9Qe@+ffoBKU8+Uk^c=z@^0&0@AieoxbH{9)3)9Zwkn=*6JL_7{9~T{Cg&Cc{ko3y;FBnDCmL-@3 zD6zZvY(jg;##XC5Fbu1z=aUKd^rq5NxTO@Fybk@ow^n~SuWoaVG1vOcKoq; zuYsnve+RMSSTYA4ml8FMAxPFxgVhs$FtgC_zf7oOaN=uu_s$(yDxNO`->~-8M9K$Y zThC(elnDfR@FpxkHe%~zG$@V%0895(*g0jD4)m_0p-E-Bjm*_Ztm?3M1BYn4Gbly^ zPu>c^{IGB#JvGP{bjuL5aJlxx5mWZh&n>W_It{BM3{pu?+OdsT#V9zsCvlD{m5ElO z0xcZLw}I{g(r4XqAh&O^egxXG=1J$`$5UfNKo{|xk7{aKfUOi#Y3`GE6g!Luj~-FN zCYS2V-X)4Ak1@m7@f53lx0)ilqSg^Gz+$$0Ck~@M^c~MrV;=hW_$W%Ww^Q?qh`iOG zD6K^KJff-T#OJ8II58a@XL%oYp2H_#5N3?L@!|B;|&a}{9KI$d! z1zXx_l_vXmnv(D<>y~=&Ut<3&!Y5r~YYuk_QJTOW1{D=I2tS`^GZY*z_Qn*F9!g@Ral-;pzZRppw<|~Gi!5{+qbW>>rZk9 zW()*(P`pn)_mM@`-8na#h&OZ}q@Mk7Q>~)%{DD>3qp4)6)*)zk4iNwpARM8+GyQvd z97-(Mfxhc)kDkqZJ7$HgKLG3#&8vO_lC!n+teM?lv-mFGb1^SzF7ALttq~6|Z+YW6 z#9`qzy4;a>5a15Q8^hemryZC@Pb+9>blv6CC$)Y_4u*EI>}Cbvp`iTZhJd#eo9NYv~4?!f3MUiHcyI-{+2Ds8I0 zqc>jZGEmUpSt~ptG+QHCqSsjY52yZkzSN(gxcBGkd-3ai^QGjk6lyhC=^XpA?7PX3 zOI*C!QI3o2g#z7^F~>~n22(tih0f(gP8chnCEjiz%9fUqYab>jZDSt~edk-Wm__%J z<^2=;n+&F_?R%xA!%@=G@m3=ps0#n=aw&x}+Ey2YI9x9-9e~)*^`3a5Udk)9%NHEi zQ($C$JmPt3?5mgks&a_$Mnbo+upGfjYJoKxve&u*Xw|2fboS-~y0{PG!2}G9rXZ=W z0fuZ^Ww4idX(qd96Fdph%^VS!h6gwW>|q>?#V`a6QtbyoYnOk7q?uxQ3Wx-uc6%D% z6>AaQ8V*9~+5O{)LD|C0tb)R)#%_S6iLNfQyu3H>ODalA97b50JjS+cD#dHDV#e?x zkV?>D@3m=P7d=9y3Wl*xg+ep$k9A<@#Gc>60uu<)e;Wt_ILvk%#l_i-cK}Zt-}6+X!IfO;qPR#` zUt-Y;7!W*UXLz#>RKK7Cq)GPWXt#*#8$=S9jm{c&<@QIKk>l7+e;quRASoe!eqa2_ zk0T?$VWkh_5mC(1S^LyzH*U;xWv%^G1(JKmqU!yuz6lEnd0m{T&7O(V%xWeHZBtT-S=b|g%*YeKg zC8wP`cUBKg6L%CcvU@HUfKnT~WV1&Tf#k9?_XWSL2RLg4jwzpAMg@Ggy}ZAcop z5u6`dK>1hFE&ON&JYj6Ba*gOdy{uMRxsadiZdSil&M8Kpz^9oRdiKzU&pa{5x|woA zoq|rSOUy|q8oc%Az;lBl%_1F-=-lr*i(}zo<<}W!cM8=dZt>86Zw0 zr2zUWeyq^1eh}Cc;2RiNev--~)Y{UZD)++0%R^Sq@%Qe;mirTIhl9h|{qTuhO6!yB z12`#~CTZt{M`??Uzh=pp3-{&!X2C{irJRjkB9P~d=fFtuFgiJe$m69&odGWC%CH}IK1-Bu2B1OPfPY|!ru0Y*&D zL_QrQ|HntWkMZ&GQSeQO=tsihdp|B^OzzmO|ExUJdF@YhNYUcR6>@U1?SaE>8TP%$ z?&Ad010UdbiEdd(RKFPfyV1oQ?)p1{>0HR^I}DjCP+%ZOA{jXlV5Kxq!HOLs?bcCg zj8lF-K5M}sW5caOaBvtFHy?DuO+-*A{OopE=5Fq|M1h^wM`AFKREQ3VQ%ZQz0onPL z7Ng&20T{Wjyc1Lscf7`!^`cAc;Xj7g;s8_zghGH?X)WNM`%ER~9RB12XsMaLxnaJ8 z9}#hyCE-+HnjQO@i`Uz($cxxU!txET{4Icx2>;Dkf#x@{vRdnDKw1dLbmE3YB2@9} z99#6lG(=KuUMq_NQP-S!L+#~Cxv^5fYq+vj^TV?dZz*P4EoJO>Y_A{Uee|}1bA5Q> z;NcgK8JyPb`T7rnJXKk7Pvb}I#MN_10sQi?o_oQ0RQ0&-HF_R|GcVdy%dA}$DPT?!%Zu1(1UWx~?6{+Yb$N(F@*hUS+S zi>4DcJXGAlSG>EN?^t2YSIu=+!!i*8HHPjY6xhu_rKI9_>i<7{y?H#AYuGicImu9_ zh(=UGDP&ekGH0HLh(u%_Lz9G1$~=W+CYeJD5kjV9o+)Dp8RA=4_VYg9`~C6l->+?_ zd+WZh>%7kMIM!OnTIULLXoig51&`Tn_;pz?T21P^5%_4qU=*87b*=zgt4<0aX{Q4+$~f;P|+&T51AZ zf(g^hiH=-fuo;3_sX_S40DcU?u3j4EfLY9qfu)~)1(F~4opcB-iriH++JWE zU+Zg%ghUJ&g-(tG@#*9}g&q%qPy`8%=LJCm4lWm(u-fQ#1q?(uxWT)4UWUnI}@{wLj zQfD?Bj_#%*hESatx>t4q_;qBvV^4Gjhyo1&Qo*jA2`>1vVbr~sBh{5H>v@upRy zQC&MV6X-YFst*c%JwD01y z_6F_mYTFT)sA4vQ3UK(U5?gj&q+wff&R$@gYVn)Ohn2Ymq7M(#s6GG4`X7?ieIctx zbu&ORig)gR1ySh|evciwx}99SVq!6n6hR8AlV_#(xCrHIKXxjdyMuWgFX&q(5l#Vy z`eScz3^vl(R>4y9r%C&q5B9hXo&6m3$&_an5(gr39Oz=UawJuLRQpy}vHKG;{&jOH zJ;=#K2{E@7ublDe&L2mTbjqHRPj-lt1GeaoHkh= zS$|5O|NOgA>6Jaoytaw&DLo6cKMN=wt|*aJZYBK>{H}3C{e~!Zl6_}Qp3ry@-*pts z-40Bk`{4cdd(5j)FI6~xbz$LeZFFZJS>hCVev^)0 z9RKi5-cgWV^~0@)XN5NXaWNmgZP1a1n_)D6cc3VkTc`R9;@Ld7J&b$HLA> zC%sL_dD4GbldJEo7IURZO+No07XHxhmg>g9T?MlN&I^*HRx>z4T>aEee6BLq;w`bC z;{6+BL^p>}pa%3WD#E<^i-N+UBK&S@Ir5VTNO8s;PMsoazgw9S9lZa?y;M=qXiT+f zwBeN6YL><4t2T9X?giG}tX1Fb1_hnz$nZ#0G?XY>JxG|5X}7la0%3nI`rZ?B0e`*= zFq9mWf5Av8=0=jpxB01&-K6t<9r~&wP5+M-@ARK9Il0E^f<4k&WbhQ2W^8kC^KO5c z`8BIv~o?NJdjk?#$C z>wQ7BO;p#h_L|uj%$+w>J2K~Acl4)Apt1l-4!K$F_h2K^U%RXXgA3|r&m0~pwK6?0 z^t%0ksAJvz_a6%5ULGmjMptiK$o8%7i3yn)>#LdmfJA;PK5E|wylf>^?>CzqeN;<& zeT~1Z{|Hs~1qE=#+&c1Gv!4MXYR}rb0sraB{qLHV4u_V0x_C}Lt7@z*vjNhCl;cNn zLBv&dNt~G292m6KnPHfsjXcb^-7xk7xu>Wc@9qGrc%wJF6c&3Iog3*j_@CaorWyH6 z&YSHK#htCW-+aNk*4$woIMDR{-ev%ZoA>s`r@y+jfp<_1^jzbOD9kb@D_o&`Auv*y zXQ62|v@^T8IaK-7z9_B9U)DBtCMF4q&u;PwwK#vc&Yd+_MzNsZGyRKu{qx1i3+t7C zZ|W5lHfL2>nDCW_MLjLp#!JOQjg7v=-bAm8G>{eIQ;&Z?+Prlh^lp%w~#-hz|cL;SB-H_A`RpOKHRJ?EZpN(Q1 zqjIte{Ih=n*M@?~9qoInAS>~rg7AnPkS*4vE3_srH(ZE+`<3@n-PpD0FXN+9i>KwX z1}I4s9BI4DdGL*DeBX{-F`j!gR;fDqd_QB47P-B-%Xg7ASuJ@$LjPTw#qGB86k)!l z#l|x`K8P*}^Lqy0-ui;Cgg+=(|@lT*S^V;c+Bc<}tX z`QcCSV8X`3Q}nt#*)M_4qe5<4LjGQmv2{jrhX$QFOX3Hl4@5R+ahZ&8-(J*wFJSla z=Rm*_Tp0h{LjkKYGq2L!V}94IU+`Gt58iw+%R2b}o!_JW&)zSI5r2Xg{EvvIhJ_hC z2JvVmJpva{1MWI?!>_uF>pgk@npb~5d4g#A{xkFkIfc7a%2(YFIGInL95K~aC=b8J zUg;LoPt)471{tP7sW>;{gat(MAw5ryUebeGp}X z#@yUDIx30gnGngsNkDk^^HEbC8_j>H#DJcPRsUwq+7P3{Yx?)xYQ%b8_vCoYC|gpT zdKK#QN$l=Ug$A3^PvE(w6^`DZkvvW@ zC|mJSk_jF_2ijMaJvJK9%(yi3@v(;UM;gXil=!Us+46s?7-?qv{kp7x)}Ax-?!m!~ zU*n%9cA@qhBw;?$Hv8vCiN_h+5m}o-hd2e{yK#+i$+Z6|MVoU~(%80q+bp7{yy<^> zFlW(!Th+kSA@yDVt%rXXn1XTfVb0boVyYAU(~B_`x3az_zwd~}ee2Ppom+7}ZCV&D zLO=TlT?nSoV@{r>Uwm14PtaX7%Cb$Pq)}EnFM9dC-oGnKfj$Cijz6XAD`KD~H(xch z)&)HY6l(xD+LtJy5JCH~UxZc|{9&A0YU&Y-e@}yTgl4+hEX9F=on@~ihDjQ zmPFGl1^!HFo_V7{uI1v?XWcw1Rnq_J3kZW*dc54WM(nxxv`_qzAPOMKGC^5jp4P;A07Jh z{vXR;SZvQ2(F#LeWcl2tSw9nZ=_^LlKkc5Fv^jL_{MFc$FTotXZ3$vSG_nS0pEjfj zo9-?xx9*)o*AU5Wvau^KZvS6zRD7;D!K*Z;BAeZpNp`vSHqJfsxJ~SCfSc5Q+~=pE zyGKwkA8Y4{CFU^QcjA(!xcT_i6API@_k&Nun_#s8XALn>BGq&9D({HNJ+gS<{ z*YG&+`#0l~(Qf-M(EuZ(G_ia&Ln`d29xjM zD9A}4?Mg1ro97X3xm4aXco}RCgr$xWl>-Nc`(MAl7ehaKc6Nf%gOAfIX=O7zQhZWM zF@U_6mR17Z8)~V!mp)b<3{s;)4So^H01(2k@$lsA&EBXbR6o54lK7V2Yxpj3OogyC zJD90mn`l4YgM&f#ZSwWR?XE(Us2UA3D&7pQ(tS}IrE5r~CbSYm2yuZ392KnD=kY{|pncJOBHDraXUXWlBSS!%=ZM>~>Ou>b?X0 zBY|Jbk9KRgHeEuWxc!0oDLY?Rh36!*Xx_ zMCA{k`Yq0t!rx*v8o)Xrre}9k*6O9DIKo*_Js@6Xzmb;TD4&NcL^*h|L`Pn)qB=7#D3yCqGR zm7to;dhl&eC4 zRDX2Q*!Zc{8*6^%6_>w9TSBUbKrDzTueBYv+0B->9$XXqN|vL(Bwadbdc3-vb8PQe zrme-l%$A*pYYzY456U?+@*DdS~S`!)0_7m z*?uqN;91c-PZ`jh!$uF&`>!q}k7i(dgegw`$>PRa*LuTtJ46@ze7tUbbBtG{+v)gQ ziuAEIj_juhtHT?4T*LBze@4lkO`hx4wv(RMqkIRwT|*+YBp%1m-)Od-WQ=(cEz|b; zpYW`;8SEhB_PMl{G=)p!P$}v@o#U9L(gVM!sB>%#bRc8V@t+l+?PY0PwT=fcq2=#dwy!z7muADdVlzj z>vGYdvx+~zT;?xfmcPBlx6$6(eiN0z*3A(iPVA5cUQ#TM$HfLX)z0MVnI=p>LT<{- z9v8mtYl63!rCFPO+s08*rug60-+0h3@dg{&O_ePk9Dc_iU6Aj&G>(Y}fskl)>kV{-R?nA}} zT(4`$r^di7s9;h+Cqk>ZdtEwGH}pf=AHaYbe3Mc{wktw=(vm{K$4EGhv3WP9$}!;f2KgvN^kpOiam0cYnU+$(;vx z>bYn5t%zwBZd={w=xG%4R`)=W)XQ^j+AXn`asnY6wVbPrvkxc6a2mwt|I>GD~kRkJk|Wb-u_>e;?*`m-~R zYjK-%QCh)&_eY)ZfB%1-E6Fcj(1?$1f5TCKW%2Z=!Z4>VZ=kXP>yph|vbWTmyLj2h zR$Qz_k2Yk?Nc}9dvmDT`#(n$?+Ol(s-hTZ@SK-;{=mq*q(2F+~+TMVfmd>qBO?4aV z;?lgD)f#+hOUq}y;`Ag7y?9=Jdvh(x?T$`h2+kjk4sQ1=%bVgq{ZQf9=9#ptXqVng z8#J(LLKhdP>&-c_{4AvZU8tF(>GC0-F$%Mlh8vJV{U7}&EJlp7Gz9L5`lipQf94L= zeU{}{4_W80Lmghru}V_?UK^c5G~o!^e+K>lszJ;v7&J|LyCJh7)@#tG-rj>Hr2kuR z$J7f&8XC^sq;^rhrgK<_^^5-K)lF9#2OJMiu|hB<9^YudxBi~Lx~;9PpWh=gUM-cd z(4Vk#|5w1o*=&@sL(3y|eb*B=XHrZiHx`}qsm!w7I?S2jF;N<@=fHs}6-(YO^V0=C z4H&k9Pe9xME)ay*^%8puJNkvc@JUjTpLrp#sQa4au+7!FDIz>k4!(pys6>Lc-LXA4$b7N2JLr2^#zAKW3{*LIy)KDWbmnsdmzp0_E-oJ07a97z@ zl}|<^{@CWYk4>kgco&6QY7imNK_oF2aW!Er57t!^4!RiBE1r~4K*docAt?z%MhttZ zM%8e3pKumT`C;1tel}Gw$PlxqiZq@uaZ8bxfOdcs67{a*SHFjv8h*7A@lu}XjGeKP z?*D@-+j8HbbG3!rj)}jxoT{jt8JyTX_`J#XB=`QqDVbpIeg$}}Zb@zRi8~%D%LX}_ z9EP6?ZOL}++6TqL<@iPiE;FcE2+`Tw=8g7=_Jzk7&Cj_J0o~PDiw>Ig8&&E_e=ZIu zmyL^_64%>#*vam6PhH8sS)W|9;s{r2VZqR+^y0tXHIM}*`Xooa4t9OgaUhqW(~x6w zEa&<7VymX5AX6kIw|+t1pLolYFanTsRW0~ao^36J@d`~7$jxq!^L+C78sWre=`}F* z3745!zUX!0{_+l9193Z}|E7hg99f7mxFE9Ctc3I2VfKjX>oB0C6&AEQPU%rge@BSACXgboTxc>{w1VkR*7 zap3$$xJv85?$SWXHEZ;K7<%C+y)SRv2qbm8m+-xyYcrWppzM>*mp9N#xN?QA$CL;K z_@;CnahzVDHnYDE|A-mBDSn{}Gsc7-T;1PAM851Zs?$e8747@H(pbmT^8YIVdV zhpup$S>6oZb=>D}+)kfEJHPBcZR_sg6m~w3!Gt7adSyPmZTw5vsB`_|kJG0|g?mS3 zVyibbcc}M&S2)z0EWv9!c6;fecg1g}!n~tXT7;#L@@rY{_1#1qGrS-`UV7_go@&*D z7XS!-98w=arQX|5oj?qBLtXHf(GQ{F*nKrFE7D|4cS(#aioN_nu6poFLRpEVt zT|3f;r54Y<>$$S_%XIKUx%&P%3qws03u;bseT&DE`vat(pYQl~v$o*d;7+@2E^djI zLq?k_Ebb?1S_xFT558-?w?qHYO3W5@{nR3bXkmsHdDF3NgPkk1EsWzmy=H!Ny*-&P zliN18$lucZ{Y>I7k3WSlqgn`Qz;;{xEw`n#Hr&1QImeqaQDi+XssQfbyi#{gz`2iP zGFJ=16Hi@BioYhUYwf8r;IUyp=__Y#{Uw~=n>ST$X6j2AD6urUqxp5-Cc%K@ zyLh&*nHUa(V!Pd<7%6~6`YJ#l=?MjSc{Z5xwWg|{B?gY++0Mi0S63wj3KWa3ANnlT zh5yF4u&pv6YYB_9o!K?Z1`CczoCJ6J&Efa}#Uk{*XJAPJ`Hggm!_0sL7u*#fqOS&x zABJ2Bkm-hyi18jwijo6q>e%DFqhN-?F;P_sLQ5eD*vnyKPlQ;LLqzZVP_lSe>Z8ahY#KZ6TOd!rUbR@FiOIGewBNr zlresdeQxid>ZJrFLi4&3{p8>2ef;@}Qvv3wYDWCFm9MRta=4txnSEre-n6=54}DUj z=$Ypo{PMKkFqx<3~3=)nH>hzp|S3 zQ`;pV>Z+x7(5{Yi3$g08@0<$wRe5i1)De~pf#@FJ2${1Tpzkio$qC4v^i1*rNqPqJ z(lgbc`yTXBX*szBx68+?HCMKU@V$Q@SzT|^6MIqk>>d6&IxdzYN8%pE(*>OnRTNK9 z+QY;oizr@zGkGp)7|0@~o~A5k`H725edP&Ku_Z^weS%^ zxE&!2stvjQV&_3DKWsHbpboGNqUt7Ed@&DE$QTes|Mm4?cMSn2V-NCw@POuRsuZ~G zTxM}U0W3da_yX2scQKbum@VKO0u67JNGnF0k%tye1sEWdtlAP=gU-F((t z-G+73r7nBuj-++kRA{yZNgHQ4>1b$N0eoIqR~KaC)4{lVyKm+V8s>jaw&E(K_hmvp zuig}>{307sX}vqmx8d;~{$(wF%}tiF8IH5|M}AeGPkk=tlcd|SRg~c~tP=!Q2d~*~ zzT&pHy!$5iR{fBaPi(h{FmeqIjl#^TiVCWZ(|wh>F|}C>B2FcIRDAn#MES_^0y6)4 z(}=u_)ZfcqXYt(Qw7SW7LGKF}Y^q7g$vInJJ7bJGXVER@o-8x0bj1K&hgaMn`lL>SSe~dDCT%39<%M}s#@W0EEcq6>gCdR z*H+(LNX-kP9M}Ibn0i15_aJ4#jUgn6V_5`5BER-$RBXLk^59=pc9T^asW+P7Ccf%!OF2y<8xGsjL1MqP_ZOCZWS_stZ~J=SKvL1or=mcpz@S*5LGX zz=gyI`eOGc$bua?TFPT;Uk*Aw?KLWs*NJS|>Rf0w6W$ZI;EndllwIn|z?8~rhlwUB z<1pua<(%Fo^Z39&{~jkrTRV?6sc#+j$2~py{NgQ|88g@ZDhddxCwm}unf(~;RC;$K zUzSB#QJy#5qS};>kd+^TFDoGz4cIPXW%o4sGOq<{8Nwwa{pi+$6f4_Dl|2*%Ldl;` zT)pzn$Q@5!fWl*urQ{GsZ3s&8o_ClN4bbjmBhu@70pmSoZ{->+?CzxP z4ttR1X1+Bx)jwjfZRYFOL{*Xdj zr@EfAC$vJ?kMHYcLL3E+d>JmYGc=;s)=TE9SNtxQLthwBeZ9<-M~y0oD)=M^$I#@t z-il*KAKbjwPj}3N##%8ojE>HKv32*_-%Jlh)4^?e*Yxp2M`G4jkAao}?Hpe-h-u;( zd}GI5&^NSCuAKQFEx;^p_6xK4Rx`~U(`QSD1GpZkcZvawWT6@*5rjr~@6S(a3#jB+aDH?ZRsO4Kne~B11*b z{=LJ3qr_7jSDCI~N{H4BPVd7Q7o1NJ>Qe{V*^ky#dU)*e3keMl4PC-sjWG*Cz&Hvw z9-RNXgQPt_8W`Y-CA6ryZh`|8L~?rdmL1Ny&Sb>KRFKqK+)*r$wL(e7z;JVPXQ~3w zcziZ)qsq|5dDQ+b755-gZqs|3{}}iWjEo2k#BO?tFF}(K;jh?XKI?Q3 zsVODOGed|#avnBn0{sc>u!OqRxFT@kMD=P9hfhyW z4{{>$S4*P?f+$!mZ2Awh;kHnA`a>O%gy2Z|_TjP0ZA@m+UIIf9Q> z%*@1eE8kjKNhy@if)O20IkFv)On>z%6}C{qIFFBYx7vazr)6~@>qs8<-h_oxXlX{= z~izP=v4YzW52&fM-Pwh_zp>AJIu6M6=o1kW*? zh<7uyu<#m>ABV=9^MR*4sT@xQoZq?smRo6o-&13WVXDLIjTOCh19|Q5#Lw8wrE)VK zw$vR~<>%_~m@98`2%6uBI9f3NQqN{oy?54qr4H4QaMz6Mlt9;k34@U8F$%^VIdT@M zYl@dTWqlu9eofa(hn1MP{T+`=wo5_qsg;{Q8!Pst+g7hrr)O^w3Fn_R7UN9yGXL3h z)i3c;%CUP}b3G3z$2w>3_YL?S7O$?$6v-QOF6ZupY%>2I`%ORSCK@Vw4hw&8q1>@U zYIWmLcI1+(qZe;Uf}u1#R5WMjZg=%LT3Pi$RR3D)Ia|;IbdD#m3SL6(hSna^PePW7 zV~wbj-(944{89dGR0JO;In7f z-d8j1*>eF`hI7gTv+YEcMa0apBm9wFcT#Jo4D|Zed~A=Z#<#T_7gvpK+jEfFO-4=W zxC}Nx7=>(welyy+7Y;M!L?0&%pI#kb#U4g%gkL(*=+owrlauq_R<}Rn+&I(K-F*}D z%|QY5`}fz%oGQ5li~-V2*(kvvc*t81ecnUCELZ_GsWTj+3n6b$p}8T4eR32-$lEPh zE~va7+lpe#g`nGx(p2+2O=dJYrcKca0VTu3P> z5=!qQwPj#@ycS0q;rj69FL4yW!|F>GvGm}ds?8LVi{`@G-hNvjPEW$xnosqGMwa0= zV$uN}2)wZ2rqY1_kKbl!rx<473#G3DuOZa4M~{|sjb2MfUk%H4Qo<%??mdRyUMw@8 z#?G8t*$rboZ0W3L&r)NXzDX9Hly#Z!vD3S_DaOmy%E&NC)G31O@~qQho8G`InNKVe;;

i4M;{Hauco(Xs0#F8 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

8dU_w_wZg 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 @h1R-F26kR?0 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 diff --git a/src/docs/sphinx/advancedExamples/validationStudies/wellboreProblems/pureThermalDiffusion/Example.rst b/src/docs/sphinx/advancedExamples/validationStudies/wellboreProblems/pureThermalDiffusion/Example.rst index f99d3d2cb21..84752f872ea 100644 --- a/src/docs/sphinx/advancedExamples/validationStudies/wellboreProblems/pureThermalDiffusion/Example.rst +++ b/src/docs/sphinx/advancedExamples/validationStudies/wellboreProblems/pureThermalDiffusion/Example.rst @@ -102,13 +102,6 @@ A good agreement between the GEOS results and analytical results is shown in the .. plot:: docs/sphinx/advancedExamples/validationStudies/wellboreProblems/pureThermalDiffusion/pureThermalDiffusionAroundWellbore.py -.. _resultsThermalDiffusionWellboreFig: -.. figure:: radialThermalDiffusionResults.png - :align: center - :width: 500 - :figclass: align-center - - Radial thermal diffusion around a wellbore: a validation against analytical results ------------------------------------------------------------------ To go further diff --git a/src/docs/sphinx/advancedExamples/validationStudies/wellboreProblems/pureThermalDiffusion/radialThermalDiffusionResults.png b/src/docs/sphinx/advancedExamples/validationStudies/wellboreProblems/pureThermalDiffusion/radialThermalDiffusionResults.png deleted file mode 100644 index c6226e33579d750e2d36fa93dfd5c9f9d649cfd1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 101670 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

S$X1#_FYueQxyQR0Uxk_ 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%0D#(}I$iRLn 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 z z^HjN#+}5}vPG1TdGC-4p7?AIVzXpXZI!tC_TxYj1agjqJB3yOR$Ib64ozx@pns|fy z;^nDRfGeqKX`}G|Rdys-q(vX!svwIAwByD8S&gF5a!CtZfykd31^ZRowtO{5y|jH! zOI!%k^NrXhHSqL4dMZxq?~RZj40?{343RC9MO^%!V%$#^P^Xha0wi*JJ*n{S?QhoQ z1@o+J?Lk69dw*|}1vjigv5m5)igGd~}d&_w2+2E!VIL)A77@28SI~?{@Q<5eU z_EY38N%w#EfS8VT)O&~f!-qoRTJLr1Q#jbgOo$UvY9JPsrSF8zVH#16Irm_qAX^5@ z3NI8W6%`02R(%aqEG`lf@6@t7Gue@98RnzFjJ^|aLuie8O5-Rrn>FPoHG$|sztBVI zc>}kxpvYuP^L2yK%_~>Z_CbWQ;rT?G@sDotdU zcSmkH6OS2gS*je-6q`}#7(PQ;8Qi$KZI*S*7N75_?gEn4$}vPbR#8zA9FLd6BSV68 z@~_RBv=!?)>g6KCJ;l~N@Ykr+fZUl=!y&XHQ#%vKM!)DW^ZvLmlu$zRuBE?!Kl9h<{CuphJhiL=@w-1g!@O_*e+QWH!0*8W8X8J8CfaW| zV5*El`_gby_-wK!V8NorjWT|2DshntyDfO!WO)?XoLbP5Y+7;aD%-5xG3u5=S%5 zRHLxS<*%|DT-9lub>N^M`8wSJzPGHhCh{`kJ!6yI=L}7q8$k|Vl>l*RG{t(kEvb?QE>i#(qbi)abNO%hp)ndB2z(k zP!9Y@Oe8kWlX!M%@P5&fpB|D2zFxGnX_rKkgkvuy!ja=uzhtJ-=s*2?+;NV8{^f+! z^V8R_Pj?C!WA6MKwLk^N1q*rf0q#`N$-j07kd&toBz5z}z8vy@CZ8}L$BaE!-kE50 zudk@EC9eG-LzbBiA8ORciXc%be03T@D9NI(t}kswgDILx7cVAfIHE2aUbYcXOk#1G z--#Fp+qm)G7yb#DGOi=i?>e4M!tDjb=CHHm)vLtP4FC@j%VC|aTDL%EZrvnbrRgS`t=qEV#g74k-nj+;@e zBDo_Hu1*Ag^H`b9k`9_O3j3LhkYe9bGPe*Et7kfMgaZs(Iq+pZATH1I;Ma3`0(~%dL4kP82Si0c0S#c|l5(D&I z!~$~I`d(FXhWlg*((QbEFgr+Gt6Ivjm%LylT}Y><%(Fwsl^8IhKCLW%&&-J0z<0R$ zbu9=vzUv>UN5?F^U>Gr0)yUX5*8CvG-9eG7i<@n}LE7gu}14IhbDM z6O?0o7+>%SgR?K##a`pz5JNWdNNC~>z#e`E@!hG}4@)FZIi7!{zpx_499{>5qd{Gg9GH>zJwN)B-Z zW(8!QxF#-bmTvpAA;A`vbUf{~2`YRhj7Bd7~X8!?>N&syKOd zrFdQXtPavs(6xdPCQ@LDyfo}ei^Nk3R|GL3L#jgf3?N#+4lM;X?auz9eBxw^0khzP z{qb*DV@p;?`Zr@LrdJNBT)vFcvPCgMY-ea#?~gMhjTgs*7giVeFW^Oz(?{s9_CyOk z$u*3D)38cV*2p4R{)^oNi_7`5gXBd8ry{nB{t7Pl=DX{l9E!v@wXm68x_3nEgnHRf^n#6w*9 zv@Z>SNI@!abNu!5$uq+=FadZ=axgPbox4bE)a!N_=gFl$ya&&=NMrFIsqv>&5EvB9 zFl^XhqBG!`YD0;6b$~rJZjhXVYI@gjbHs8Hy9WI zIv33Cu(y9LB(c|;0BYog3e@5nv7q+BvuDo|r1;?ah}*lUj#t(Md(@GJNqD$nS4oG2 zU)N>P2qQJZMr5;{!Qv-Cz2s$t$$r3SWKkKr3o6CjkQ(_SayZpOaRYB(Mv&5st z(I+a+o8sMJi$aeJGIP7f7>hn{OQ>ljAuxQMbqy)xNjrEO`Nfe3Hka(J5z zZ+MND!ScSpe}H(=i@&?3T14?jm4QfV>#ra@R7^@&(ROtcf>_lAOO zs&(yqDWNkUFM&_NmtCWD2RI<~Qe;2#U3a^|sSO6`Gw?v98}k|2mu&!_nKFsF>gBLcbV;*^X6YC_9TO9-jptDWih{%p2 z5hK*c7fjqZ37R7K+z%W*YDU)06(J0=1>stF|K)au^X+A0L;L>bEI=Izi4LaYdM-5G z-5oakgvUc>@43s;&gR{^PV3Gta?(1pm;YU4OyimH7Dm=hP|c3rb0gfn zInhW|b+2^@$}g0s$oi3pf{3U9W-xX!`{xia6VY@adOn_-Eg0Ax}pwDr;NzzEr|GRzJ zeRyp|F*U0>Jau7&tU0l~;2!n_jUD#<%<0n}0gr2CRb}O&^0XTc#qbiPfB0~f2r9X5 z803xZiO~LRQUs7KlOcEd5N}kVx3_HGaT^dn1_yq3q#IQ?HC#>hBcHolm6nX{8>|?8 ziGPTdD|o1lhk9K7e=n$2wagc%-MNxzX3*N-M?BJ_Uqti_CYG)~U4G@AbVZg+QP0iV z+8SBsnwjy0g9~O6zr0*hj*~{huYP}P+tv3eJi6KmTq*}RefSjq!ulu7QA!9&1#Ag7 za&@8BB^(TxAOw^Rcgo7iMH7!t=LcV;h|YjqhV$WuI`$p++`(?`a7c;8RFt)_yxi}Z z#L^=GxMZ5|-O)iA=SG10$2KVXX@l$60ab;bm!o*zipebP@zXX*;6AD4B*ndxT+1O=QiD~oz6#Xh-tWwP?+HgDpNmlVJ=3uwbbht9 zwP0!|AqU6(flEj9MR}$_&ItTTw;<=qvuAbIT{Yo!>SY|^1qfH?2MI-O8}D>4Mp9lz zLL^b{p-L zbI*qq&Lal+MeL(Zm$p&IGmXlhQ8bg9elB(CQqaP)kgfyK&kt(78ou*2{6KciSB7e_ zyHjCrRjbr4xhrEg0{zanO{0x1U&9#ZZPj|TxwlvyYzh9l(BmBU)!yLZiH%&%G2Y66EdM_@G{vv{tgs9~(-C{E^gh1pTE5%zm*I+g2-zAD3j{eSe^}a8g{iUOl5``u z>f`Fe-vV-d4V`6KNA6a;1K=mxh7j94r?b~Kw&Ts&y(d)J8hJfp+M9Kp1yWTD{MF5` zA5}x(A7gT((gm(vIeDyA+0ZN!8U>uRNO!z;^oFYXOXXC9habEgWQKQ0hSb=$@mXwD z6BZsHR2a}Beg_8;QOoZqiM4*f=QG6cff9WX1Vi`La$iK&3XwlB1G7QEj<4_fYz4U_ zHgpmaT@u1Ss^-;!dn2TxvGCkGun&B{pPjakY*gA3Pn)D$$ck0>9obgLs`DPP-?sD8 zE&Nfx_s^tovVIB^WP)`Pi9GG|i^H0C#W7B(^LNW638qbTiKI04#xU*Q+g1AvRLxAI z!s+AI1D9~M>YkSaMbBUb@z1KGnh#cf>a*~XR#t(l+t;{QHn?_ebs0NXb1I;o9^_6p@5PJ6_ zi23GCZs--GARH5sXbqqGseT^^Gg6_Po(Y*xe2V6sCg*RF7t3vo)1T7Duvw%(H5lU? zwp_gaD(y}g|8Mg!t4aG1z3MJMq0Oux1@v6ynH0+#U(s^ip`xUuRD$dbnFXXATUz97 z7X2ZgJ$hJF)GRTTj%_1~BE93@nlCA1PTuozt)qmy4&FDfp2ni|jwuB4!TwkqUW^3= zt$h0@&~x4KZ-K-2ujen%VeXyMa7QBxjcon1vk}OHmvPVA@J_Kxt{6G4xZnXzRX=6K zF4oSc>^?;$Bfb!-%qD!q-_LN8{Ye4$>6XP0j8-!SxgpxhFY{jUmwpQM%aN;cBDQ@V zIdI_I^-MnONFIxe@H6Wcb52Uu(Mqr?QaU2k(jmfhFG#$3afkE$Zb*7;a&ykl=d}0S zFOnD44*PXhZ}TPR*9yM;V5aPbeY9}7F+ z-@tQ0Ov``>Z!OK5N^0?1qUVBPm(_o_;}W@sG6H*|_V2Uj_`_gtb36>=C*T&Ih~qCf z!$e+ntm+iYg~V~rPH%o+xKYqdUD$Qs^k)UzBA$xQ71gPs%)aGmQ~sMQYuT2^DOl+7 zQ@3JmDlR98PoEs>h)g>nPv1OewjI1peUqNF9YPl`j(pcJkI%lzwJm2;V``kJ9_vU6 zT9In(Nt;)Qv#SK1v;$hTv`gNXYt=PjOucpNvdw$T< z)ALtnO^{q?p8rEpj)Q)E4A?Lyhsj}(CXdhiHH&(lYJQ{ZZ^N;95J_H0!}&2~S-3*8 z92ha?r1d0wG2wfy<*EZbIrd>spUB*9t@g$Q1F!T)?9CyTy+Ql&!Yl8y^7XeuY2QOd zo_{F6x%j(7WsZy@^3s+>BZUDnd>^tDT^rjQnLqdWHMESKbnxpfxoP-CP=BXPDbk7t z15=IZfzWr-?(je>Ug_X})3)kRg-gJA(oik{p^?d~89ivkdqqK&m zpqkx|6-7c{$WCQOLa1sl zC%l^$TlNlGYKFwg@3)yyXLq|nyTcHUPZ*wN5VHqV`L(F+yN@^Y{cu;)$cO44KA=*` zR!d0|zcSskd(-xG-8B#Av^de8KKfar9U#;nF}s_Bd=ISdf9F&gUL+YXG(PVA#Ao1$ zBFSXKgnWkHR6VV%jLdx)F})wojb}*@NrsFVmyjmO-(EkTXspymmN1iiwyKK`l7xe@ zCx48|JosaeCW3xE3J`M$_SsiAnD;$q7mcpyfFfdfA$%_RKk_Mg9&+`!--ej zA`%GMFnlF+Zh%S9WKt5cF)E{j)ZUrSE}xun`P+d~hL5NO=#sQhT++<8;q zSxBvm*JQeM3-jz$3as>g&Sh!Nd(da#?!2X+#dvxLi(_2Y7H`1#axEb{>(u-#WqLsJ zbg{y?^sAiBs=sFktmWvSW5@BR1cwVM&>?zT{q2t*cWkwo8ODmjSl;HV(bTh}6vv8_ zbS_c9;j}Hn`pb|{c;XV)$KQuivfgvB7C5xg4MOJzI5tql<)pX(LVIT&y;|~ss>AN? zU5nT4)yK}x4DnMGTr$EteV%Ou``&>t_`&|AImR>jQry`+siO7MLmee0RjLbQ5hzZ1 zd9+$s2m#GXjv`xjidwM*cFcm=mFMUDE&T7s$X&nwh)ajH?(-$%dk*~kG44~NqtaM* zf(FqDy%_e-9=bCf{vmv6W%1UE{n&hoq(zPfl)2OUT>svo1%E6~mf51Q|IF)>Ky0x& zXjC=vf`Q>=?9KO@Sx4aW9;!Z8oT)qHacK)9=@q9r{alv$8(HRC)(wa9v5`^6uby>O z>qGR{O;`Yijm=+{g8V$}rBK?U5fXeZH(y0m zBxs7ObFRMwN5-4lKfE6Fx9q$aIIcAx$#L2#vA6-4*&q94^KOAU6K2M3hf3NrTD}r7 zN|$G_V^{H4Uk+)hb1;QVRtq%EF9c_3@>uPgZWxW!KRYSC)SQS~^PJJ5=YvHDUq)X= z?_8^XPf^oe6R9>kgK@N?)V&kOG;1msd;!^y#U0zmVe2oVm{~xi5IRxkPrZ{WOH%1Z;=IXyIU2@_jHN#F*+=?=yEk4dn6|H-<9kR z;EC#nKVWUmxg|fRzPI-*{9XvnCWG{H-;pCvpkJx>xLww_Z$GkEGb@n+lt7gKUE-N> zWAeeDeSZ~v3`g4}RFBlzFNhw3K>ICJcSuqtgPEB78Da?E#-tmo5H+1v$Z6&HWVeI2 zVWhr0IlGT9WhPBOJVv~-DKz=fOcURO9O6zJ?K32ewKVhYa+q&A6ufO-{_llmKQDhM zy@{dnxJVFL2{zhcBKuEqi>koyvSM!8Sc&MN~Q(DWM zBlFa9vX;5;+}E(u>gspxObVC&T~a8`<;!xHW%jYgQ-Lun@#MtI*+#?`_AwmFbivAc_}p5iXjn!)4J$E5Bu3EZUwMMp7xBGRlV# z5-TQP;STzy)0Ra}nVB{+-eM}y_%)0cITZb)A8xw4tVXIIxBqp%EoaNttw~`9aBf@~ z%cwg!Ht5Nm$~4)h64!X9st!Xd`X)^FGf6@6FX}`@%`%FPX>MqJZ?5y>ludoWMRDs5 z7kuwG#JrJbvpB5Tv9W+L*6JH08hP$Y3!*z=gp^lgD%Iw-z!mRM&Fx~3o#Zz9~^a>!| zF_Gp(W@$;P$h|B>!tp4V5MdbE#nI{qR{1x!TwY($Yb)OEMgQRK`Q9P-T(G-M=1}fbu6&w9KD=XK2UIVE;S@~z+ zi{?$|pCt4f^OJ83rCInle6VWGtD=3uhUR(~pl=M<@mTx{imarru1?v?LTzG{;aPIK zR@M=PFfnPC`dai_erp#on8tv8K-(AV#9IrdQ+5f24 zv1j|l{~a!y{Q6YOy;Q|+xv`0I*!uUyded&+E#W8K@1di#iGicx`s6}Sxi4G7GSGyn zt}Oe`_!l)Btd0^D%7kJSP!Zt*K%~k>41LHm4V)Am!TyF!)s~x2aN6CJm$$NFxNxzb zT#Vx8Jv`U8%!|@;bV)jizEPc-G`l3^y3{gaTNGV;DG%#)JKSSJ7w7Gl@Bgv#cVHZ6 zmf!W=_3V%822u$RBI{@wFU;65{wL`avNq@%2k<8IbRzhkf1sx0-}Q81+DZMo*#B8{ zWd0Xrx**!OlEJRKqYk~~H#j&Fg8e?8#m#$$C6KzVtF~CBW-)nllRohA7@?MIEO4gT zmDz5pXMZOD(P6C?GmV<;=&#ufTzAJcgzoWAa>|zpb3OLNG86x*Th50S8a%}v{xFu6 zWvb^J%P&11ASYL58=o7KHt1^jcQwvP>Ak?wQ|#(0F!=EU(a`STSsB=t_mu5oY0k;= zb0<=`##1)pltP@NYzgyXN7$(j&+;mN&AWdx7o;9M`1frYRjKoMFFyI#O&V!^cs^Sr zaFX1Lq~R+Tfi0_`06$!-^x*T&IoVdA;SC7etgvIYDnQ;BQMq z*r}POW5v%mpg_6PDKxCEto(s%d7v^c)f{Pr?~BdS>{gd%XUbLMpNkG8IoO&eg?OV| z&=}WSzBwyb|A?B`x2P_fxDpODTHouB_%g&^(5 zPOn6Y>nofJGEY!WNShyx+iPSb^~;0!lKyO)g+ILk+%2(I!u)+*ESY#UGbkE7XR#NfML(m-F#|eR4cT8icjjkRel)7x~*vCj;^)R2U!|@jS`elS=>Z}_`8J6lIhrGnjf1Z zrFJ}YL6k-V)_qC?J3l$Q+ohg$9EX+f?=sKcc?)bQ5w-K!cRusN%9HJk+l?l*Y44<( zFfRZ^2_$B!Y_tIcx!yR6a52J|M?~;?^#eo#`9oyO{+nocS>_}C<2dyfru(U9sRGco zAM%Cd<$Lk_zayHzYno$8`8w*3AyFfAw`E7M$Vca@ntnwz>%! zc6V_YhS|JuJ#fhEZ0*(LhQ7%d*@R_n?aXcqgmnsapC2L~g^_Ku!~$YSTw@g?!l$H% zqKL3Zgd=dj7I+a@xHQ-5qzhw!9?4(j!+7xn%1unq^M6T+k!{{=@iN4yIx~MYM5r|J zz0|k29eLx`0jA9KE-oEd5t$W7f?qP>r1U@aJc6YD!mx}X_$`Y;;+PQCib#{wv zC8x+5G`jlUix}gV8x_n2$|A&Q((B_G_l3PZ&t*Nq5?JN;hB1}iBz`nv0 zdS-9Z#kdDQu0hCwM8~y7Dy(A9v(379?Ss|BE&DTV5kvOyxWTmr%S$rpc3(Y@W8AoV z=gxjn(WSIc&{&eeUP3!?X@GfmU2w&d`N`aOjY`|qFN_81tsihkMBMKKI@H@BbFPeb z{Y?{kit6F!a)=*%roVjAR4atR9LMOK-o>XLJuVMCHkc1lXB{$nR%|joFRPpx&x0?>th4>Dh#8vhSXsH1+W)G@TguXeBM3L$;g3%E;Qs z$9!9v)4qL~2aT}!tjAU1bWrHC&0@>kHY21uaVpPE8pk364*VTuJKb|mUKTXJP(eXO zQl~B;GZdnV0kSz=w4B0SerUt3;ip0qH@ieF7C(Ic+~my5tEu*9elp9Rjm0EcTQlH4 zj1OvFY-g?wu_}_b-ZB1*pqCJz^BYjKI=}h8h>=q_7_1hT$oV$0ka_K8UW!0Q3-)+u zgvZkAZzLn*Gkj#%duu$vPS~}CWN|&@^CgS<4mbjeLj)f@leoXWmSLCT$#}JQ=t2c{ z+IM9gl`c72Vi$St(8F%zAfh^9C&mGV#`8sL@r;o6I;|H9U95pqv#_};u+`GXs-3*t z>x?2}Q#={Ar-h3*Z12AM8Yz>=6t~Cf(tE>UcS+#uqJWg6IGu)d_93yJ1#Nq{%aSQr zu#eESd!Sj~!7KJ^%`YYq&2M;Ll z&S2X?@A$YZLbKs@dBHg*75&D~uF@)pm5*$NS#wWEgwvyl0Sv3wV_9C zfh1QpUI*j@n4(lTi;;3D!&T05Ertp053!^IX(;)pgHIFFp>Fb@HOIGlu3=}Clnz7* zHZY$$`5bYge{>xj9q%BE46!2nvC=Iy_2^yzo4AH}n4x_ucVa|L?nRgosp{ z5@|^ZQA%c85>iA$c0yA2*3b}=l`UnD?2=WtX`~98A`JVCDd7Sf4 zKA(EyHJ;D=dEeK4-Pgr8I+rwR=zreA%*F3OhRXb4T4Tak1Mg?O2@=w*Y1BH(SKtT3s$+jxSo^z&&cIWZT^tcwDB+#=T}kZowv6n4%n%b z31p<7+`DbtHcVLUz^nv36v!b*JkziO61^aUF&uoBNig zu9$Q;rrXylCs8;3J+gJWfkYIje#zJ5|tOfy#K6 zdEYvWuyvxJKYm&LvGLx>109)WuZ0aNJht_5TUeNV_mll`q&{9bVonC@knR(3P?##$xx^=Fm;$R>q%(>bnS*U0kBj=st!5o`#w`izQpPPnfTLSAb8T4xf~yQs8UkwFGZ|acpU)=^k#?E0$Uzew68~|1}&<+@;@F5oREDc{7cpe zMa%5u{KCQ|(`QI9GF2iQFNN?kn{U;X2fd@SnuP*;$G|F42nmf_rL`gw(+agJ^TnKSyZF#r%D{F+4*BlQhN2n^z!a4&Qcl%l zoxuAbuE5$;B7vQ-mp|Z$DaR^|9!4tq(64jZa>J^XU7Vx% zKuu@thc#J1EsR@38?Q@R>|0oDFTA8;vA zra#{l0P0$rl$>5E^iO3FIzt}ala8W;aksVTe&w7i^cgbyxDHr}Wl3%GmR=vJyxFohOvGkKScf_u} z5WI7OL_0!ZbXu6&gbMKu4BZIB3n~w6xuMx8F2+-j+`nhNAMP_ePNtM1FN@ot-eMVo z)E+m6xdxKiLRoYlW<`?G3o_iadX8?stGsV!6CV>7SL~*>-Gi(7E%MA{H<(8Lnn>!a ziJoYIyB^#Z7LqihF9td|y*gZ%Pm`qQ?dmuj03HQ&hD@yEI9OQ=pt!ghI>czY3J>(G z`P@=iY-E%;P@EKY)XOnHJG<_FB6g!vTGZ?r&IwA!+4e=Snw+3cNzF4z^wX{?wYo~yv}jkwy+5=R%X-{o#o;xVV~w>x zWM^lS-a0jrsEoUs5l4L)eW4#O59$yxFrq-|%mxXr49V0r!b8=&BRBFZ$H{X>KHXHc z*~o4E#W(}`Nbo;nAFr&*dxv)n?aWB19`2X2v9*mui*!q7YmUt&C`q2ALius=Or#;% z&%{vKNi{l^UBSN&5@ZVm-;{5|CJk7J1e5o zZ@s@OG#?IRXEv-j9HqcD)$=-$TH&me!^*gWd!u>VZBa?r9R7xm=!S4KEQAg<7DMj{ zsPH~!E3VY%!3niv9WoAcv5G^DJYB!NQa)+Cx~F)bet*VIUq!2t?%@TTK>l>Le0}?S z2q~@p9kd=#7CL|u&mK+g^Y4jP1t&fM1qbH-GxE!9VFc zFn7cE``?=8M4O}=WL{XQrF!wAOrlzJ?dWeArNqE7>d(#UvBD-kfg+b;3m*B($O!T| z#7^8TsvT1<6L+dE{=t9NU2#IjS23G2Yc@%$nLcA%t+_&o)P~AkQJ85tS@7%AC$5vP z7rsXi8Ezf$6#hkkCfXQitcWz{r8hk+8EEhDZA>{CJJWJ0?&8i!2<8=ErKgW%JEU;) z*U?DFCH2_%M?0$DEq4I3Zi7#9^0WikiL#z!Gc#A5=b)ZvG?I| z<#`TqzUX<|>A_gak2I_*pR(zZ;%!Y}a?!?sy`?v2CFfB(7)+K0RX;SkN+(BxIf$Y- zZ48#kOIWO|Qn}$n+nmVC|Jsid8%{4Wj~BZBCJU-x?H)g_I!H;!}|-0NqFlZc@X_+kagQQ8U1WzU`Fjk{>PO-mc-r?0ZL)o~$zw$`uI}ZdpDhC$GVnHiY^z+5o9y zQIOSOQ~=H0(xWB=hk;|HQ^XaFn;x3im0y)B3u&%Up9=wi#peu|A3oiwo~{ z0NJb$2T@#bL6o~#*U}>|`u^Lurcq8klbaXv!yqwzE;B1Td;hnhGy3li#1GAlJdTea zVC9To3Oyz&8r^hjaBS|?b|e=47mGr@)vwOE>EoGs57kH>uIY=5S55P|*{Z=KU-j$< zgcuZQ==Ns7ifvKODHNMlQ3gRy;4FgNCfm875vV3#Vn!J7mC8-1iG3@z?Ps5$n3nwf z2gaK$F(cnY-{-H6Gm%N)XD-{lyJ%n_4d{o&MVe+jcWx*2P%1YV2{q|IQ3?7n{i~=k z!Q)+_N>H=JMA22Jj2m8xxpw;@YF4!5@sN?{M@IZI>0v(<^d-m}7yfL{^ z1{%3FEZcUZsvOMAvUG2QZm6{EuHPkV8);W4{AoN~R#<$Ad2eX<)Meqm)=N(Za2YFh zKrelKSUtiyhL!teTd#C>r*4FE$wbc+>Ky0ytlZLc)GqWrRo-oF&RcH}6fvjy-d~d` z8=YO@OeJvLxo(%FBSz-g9x5Ji>Hd|~0qywYo4%IzW9`p_bC^_^{~Bl5OERSKrTP7Y zDS$87dEX&K3nH&?-$76YbpZ1dJ6cpp2DY;H_F|AZb3fIV{&{Nc-+q(=JuW0XSTIam0#P+&< z;FUYV^WXZUmG^Z_p9P0IHBK?LiF?JHC6A+T?0aOrlp9->gy&O5`3oefmpG;8tr}Z9 z_I@-B9A!!T^GG>3nuGByWuL&ywAGrg(p!ubdkhNJk11;>3KWr1nDg-_%^!ThQU}YS z0=y#;7!iO;6!|`Wzm*3&*P28w^{?O8F1^|H*F{_E;>Y(7E$?=7H8u{JAwWJcXbdM2XQ%so|R^t}WNXIi9YRAkwjS6MMM-7ABiqFk& zD;=8Iot-Y5!e6ZIlI*kTSHYtYu}ahQc0skHA;orq4eOp{eBKOFA`zQ#%?HRL4fYds zbbf2eOJKBxtg2 zrFSt;Uc(?ObsGwARpfH4G|z`Z0#WRbb_bcVE!MUE*fh;!az*6hx9fmI4nx71cDqJ& z$FbX$%^~PM-y8T*0J&c@`+oQJ#hR>IEo^-E&Ycsx_q6wPyXstHAATcT%Q5RTl@G7(>+q;FO5NA=_v80ZE^mD3j2c_-2Kg;!NAowOw-%nw z_ZMlN`65qaesOOGhe{R0M-mzR*WqNi{j@^t1K@(tw4AqRu7*7H~2ldhy* z=8-#fMiIP=TU4Top*b|J(W6AF4ho1}prxTT{d;)$R{bUp4ml9cJkX5a_b^TIJrxK3 zi<`USfhE_-%kF5m`!pXqJI$LW^9PnJrQ8m$Hk8n@bQ-AsYd?9c&8(?9G|az#!k~<= zPJb#ZbeDwM&boVbYku~va}L>?ai29H*l40ctVmp5E!#4Y`Z7qrX<}z24J*}5bA3C^ zhYZ)zXax_}9k1%+9Tq$nEW3`|`%6RJF0;APmQZtKWu68e$jbOCxZ*clx+ChAz5D_3 z;rc<3s#V@!wrn-xY&Mm*aBK|H`z#Gw6jd7pd{pIRh&>`&r-?=>_6$vjp*lnO0qKUU zSW`sIYZo`S5N6Aey1PT$3cdET(KX+pkS6<%f$Nmx&N1bBHxOOe+UHT+*hBez%k&8JJrQ}^`}<{pl^+NZ z*D{!NpGkM0uE@-|)qG5yMq?>kA_jDR@q*0f&!5YBI!vD6%3gIpVOrewn%oR@HJP@b zmE^RNh38o6LV&l9peWgj4T}|h2XU?PGeYm0ZvIRP88%REN^OKS#gw^ZSZ6(v?3|x{ zQ+x$NrOj2pf^j^eh1usAs2%2bW~@Sgqf@oRj=Ko&x#ycy}i#YU8Phe^tV<{qXRls8~0d(i=zYCO2ko_q@o82%xs02ANsRg0H zvKx9+XlN6cQ|Qh^72V8D3*|P9wN4F7hnTWoDpcdKf)<11ati)|ITidM0QtZEL6sh2 zLYk1)_Bh%P&yMhdYC!_c+!D!9tYjXQDm& z)6~?I{A8GF5k5a?p86%ZT_k}ak|tC%bPBJ}Fk^fLiYw5D-+iSIeK=p7)9rw8va?0e zuhEC=020HOdwXo|f4SNrop9I#U(V&=OJi{9rW9p{G_b|rQ~N+O6mBB!{f!y%<8U{3 zzS)=_`_$e2@V;E=ZC}JZHmVWWv_tkr!}oxiGF2RKzkVoo=+Eu)(~29H0YBH7+6P(u z&4$#-{)V(@@&kzOt}_wf{a`nfWFz@JAYg9Zw+^Z-mr)!*3hI(GBLl-ht*?m%wOud; zd5h=$iqbVaoCGbdNYs6in3#wKcPtL_jK)#&+9FZBX*hsm(KcmU)XX%jIYUfA;&9il zOK-u@73zpS$nT&kJ{KrvGtL@4Tgn}_SE|qa7%W$Wen9Jpcu~;CU=%MF^L2rvto8g) zGK#!ZjM(wT+nRh4@#oY|JoK&d2F_B~C3XmN2pZ?xOhm<9eC%+UN!c6N!U%rfi{fo}~=hAX=!hJo->o56Tfiig9mA9c+v+hJb#casC5x`H%5IIqB zw(X1bmz~?fTUy)t_AHr~BFF_=mOW40=_fTQbWXDAvdq1RA;(H50r zCiP{cXF`K+9v;fzt}`lNz@*HgCGk^jmv0E>?%0MuOW6Mr_}Qm=U!>d!@X&I7nRyj* z&7tpa2w2!4+f|~9!>hHN-)75x$)EU~ZwOb0byD9V1SV)>S%@h2;t+TNo8^ixnoyLxKNs`HjCy-BOf`!h=fjc0}MjM*(9)+jRkC6xczK z=@lYp{rV7&-Vi-oAh|GGU+auaTih@+v4|a4kNyh<6&MZKLIab$mBC%0Hy$7~4Vvg8&%Ln7bbuu)~vY3-yo@!Xh$XGV{63BbBsIVyJp|b{PexQVNLO*P;y!yLsPYz1+pc!9e+ChfGUdz=s?d!4YW>3gC+l zw7e?<54+(*LSV|l_04#O)jFUxe2$0@o9ti+sSYeOuNsxu6h8@cn~Hz zzF}{3S7s`a>jh+#3RhPQ%^JVoKLiFOtW=IdITjCA3)$qKKYuzGY3fBB0_NuC+Pk_? z3Qk|^33~b;OXKQ5>JOFu^4}_VL7$nz!s3ZbpPXEQ!*5@4pPv7JT{xg97EK zK>KQAy?-;j$URtWRe{t7%V?}l&D0FdF5Jno10PP3z1Q9gfIbohGJjT;J;(LZW8Xtj zsF3K4ARYQe4PP?&)ZB5nZ<;Lwy^qjcKoV#}?-S?^W8K#mmDZ4U$%0i3H2w~w1)pqb zX&k6nHfo2V(t7w5AYm+qycA+-3WiD%k6?E4tXbn_U;%JfxVthy2u7>ShsTwBC)_lm zkxi0WU;+Xg?PA~>^!(D8uzW>o9p@4Tu)?jp&QBKW!_DR6=scX?5XH|V z{d3i4G4c9zlcv2FhKxxxe{Y?9C5n%Kp~44pmL<2p--dA?1yhSaY_WvlnRM$~RALDZ zt7Sj8lRT#ZxeJM($zK5TtcV1J4;6fxd9p}hDag&sl3UT;8E(#oiS$8q9*9xrvnq3r z2>u$hch_#)7SdOL)Gle_!s&mmAGa~KtA*T+MZ?VmU+!N02+gaV%Vo{J+y5$S=IbXh z_Qa!^Xa8a1j(2F{K?|G64A{p24m}6+5A;fOV91bFp1>?{#bz5uFOfJz+hLzOIV;=r zy@Oam*Ep++Uu=0fmIh8jD%~ve=Z@KqgIKQ?0bnP58nsildNsvbCw6SW*b&Qf_sN-` zIV%6VeT5T)=l@Uw-SyGOxK^qxKiV+Z2vpy|55xBU@$z*Jg)9r=mkP;uT(*$;{ZuO~ zB-B8_5GONkeBVJq>BZDEI8^E-lBYR$D@fHMwx;#dt#9F<{G->^ScEPDT!gktyED&v{m&txK_&h{17^jpQkB)zT!Q{@N%8_N zC=Sd;Mr2j)GFD#^iC~)S3rrYqhMphN^1%4KM1;($bGh$8Ry?9+7>-0aV;4Qo^sLCiZw0FRbkTGq4L=jcb!s zJ^-hdEL9n(65{B)#UaNk`}y+<74a9_2ak;Au{p+L+qZA8nZ!vQi#f0j;l$Q~4DJx; z2-+CPP{cyGTdnOPx3deA}Q9}!>@h_CjiF! zB5-=H565$kLjN-f2+(T^Z45+XRq%8CdCI|s(J6yHEt&SbfCX`a_~GHqu2j&++*V4H zYZU@1$fL~6{0jyLh*`}L%n%O-3qg^WHU=IP27%q&uw=@^=&kYKrJ62X#5HYXd2Co2D+~QGrF47U(@kfTy6{R=Q zYl)R2)T3&jbDgZk@~-&9!&|(*%VP1b&B@)9?|;6w+BD=Io3E)F;(djW4a)&leg9eq5+>SN(>NV0R{bYt5cU{DJS*X!N# z+8X&7Oa5;0wS$k%M{yr7uk7-hLwQG@JPa+4yQ7dw?4eam zjQ_5bKJU|garD3b=l@5k`yZv?zYDtm|26pkjSHhAawx|__|Cq?<`NgqMXIv~Ui1IG zx+I33-rVxtdnF-VQV}K@hJ#q~{cSDA^?Ridg@~ICI2wu`)z)b6W81&MoIhY}^*%pfK&?UpMl^cKv6v?$v!>7w5|N=Hx;X%`F{{+W9_AfF&%&V) zaf$(3TEuoT=3Mo9(DR_@@C2;hacIOHyxk^+HTVSUk82}akg~=TLxgkf&JwT7;8jr5 zGAiH-(QhsTrU-sZ*+n+w=biY*-Fx@0gy|TD4bGrMAiL>~IhtV+ITYmn55aBh@RSrj zm*=Ah4h%iS%n`FMyof3}>9c0XPe>`mP&TYtbA2eGvkZk(k~4cQ;zxkqF@QyJ7>!X4 z9q8_k$^$#Vcw*g#4Us^NUg)twuI45{w}vP)^d6P)$arh^Vu#Ja)FSOd2PwetcV&X^ z*mf}hvGjsm6=fD6!^@}uCmcoKvYz4+(HPH-r2GI;z&nx$g6zJU?b>o(kuOnG3lq;s zJvI~+O3=M%h)RKwNLP2apB@`3s!O=ChN!WlQG59#eHyc^-ePbz&U&n!1kWorGDb+t z8JE4KrVKXNxB*`haLKXe8n2REis|j`<<)H1g3dWt2q5Tq%-h6nJ#s4s5jh4Z7wj2D zMdLA^q#JMs)#&oHtu1tA@Z6$n5tV-65fl5V_2KsR_O+0G6BL)^;yV5#_EPbv28me2 zmlC4SHy8iT5=&94I3NZ6$VmdCh%Ac|Fdo{ySSUy(HDmEuV&R2!7(Icm!NCLyJVl$0 z?;F|M=P)udDmp_V)FM;QAOVYJb6E!xvBQRqM9Fhp`x@q)6G+vMSnl_oc`|2R4RodB zaY^gLNx*gd^PApcs$U5>EzcB^;Vos6xKCBTLL}TwBTDes%|JnG0d!>wnMypK!`5mL zDsFg`|77oqjLuIP#~_u2nPaLVTuy0Y!1fIgGttU_pVyWFAdqy;Ev>Shd_y5eLmhV4vF>Q z(47u4B%F5PYYHG2nr@kPB8=RbKd?m+ys&e~3>r^+9Bh+j03{o!g#v0bwAsz4k!P>E z0G;!G>fsoDAYc-WnINAnMQ|Feef zaA`lK`>@!Tg$?=v<0?_b(9I|ql>j(Znyk6>h8X#P z4j^(f?p(j9p3W9g;fQI=Wnc{`_fgG7^+;NeN^Rg(QJ{)LYiG+etl3HwED^LS4!HxT z*xF+7XY{vf_hHej1N^ywlq8zAa%aKEDH2mx8^R$|Zt6F)X8J5Z*&UlEz2i{tF<`*; zF_U*JEgjv}BiDD`;keeQ9Ws$-Zf0``) zb)YB2)C@Q&h#zC);}K9_Ib_-rjMPX^BufLbO@{T!Tog^nhGmZ6{OsOkb}%<*eGR#b zmlVGEL&$M@Yhzg{5N9YA=)2a)@I6)LHSDj@imj0+L%v-f5rBi3Cz0^0CD@6+M5?ZG6sHB@)u{}C+d6pDG;QLK6)(CvAvoIq%$`h!=HDs~2dsvcrwlzaio zIkHxS-SO*m-=`$Vllz1!M0biIs<^F(n`|3rq=V!vN2mgdM+m_j%r`t$LjXm7eSb%s zmR{qjLa{R#`x%*hF)sW4?cdZWmh`~taowsM@EzahwBSps(D{Z_iv*p&^Bb43=h!jQ zv!ei-*g~9Fb8**Nmjh*ZU?F;EN-*kTh%XR_j^SYs;LB())F!I=<0Ldg0nb}CiqZA)8zeo_XLA=WZCc;INga|F@&c8n_p?^K4yCQN5)~(y^?434JpR0dE2%(xdk9? z8-vdX4UY0MDX2H~vAD1A3+1KK>o;-X-S` zURbh1@J=0McwTepj;;H1RW?tZr{ni(|EL3tzIN@J;DruE`ez*CY|cokp*X|#HB7P! z(<2NlECyJb#8zEWGM{(im1ir?AT+Id4u;8f(v!Bqn(2z~-`C2^%bzSvgtEQKsV*$= zQy)Hp69=&(9E)M`hZg4s_zOmm8nBDpd4LgGqJWn-+r1U}c?UmAA&90_!mQfQEfnDa208?uK}GO6&017zAHAD7w#LBr>17fJc;>Iw(7=Yt&<;S zCZ?t$;Y|XCB9ZcmsXkDRd=&BcU|v>kV3)Jz#LJT!9&YkG_zMp$w2HWobjx&@$zIf; z0DD}K>e)(k(0F(l5FLqh8iEtkJW?8{EVX6zgq92EL!X&U=3#~W+xTy2Y{!TFo&_rE<$u0h-Rndd%_g!8I(k#V;Q*!Z?Ay&UYBKEuSZujDYr)>)1 z=K{Hj?FM5_1qB6b?z`hyVLB4N%W)M1XHL`yW2C}IQQ2k$Xwx^q0ZdImNlycXAg(;H zR{|gVpl-<;tX`MbuXUmf`?B5jy3FQHcDw%?Bslc%nRKfkoJ#Pqq9B9g1cpbZds4+E z_M?jE#Q7@W{MBiT6hy{@{XZ#E#(tA^5wJ{!8L ztB6@Ck8;9hyy@;{t5!X%#Vz`uDxa=^JXrDp8=j~JndPNiX889O1kO-kQla{tN<`6u;b0}nXFmw?saH5#gNx*VY1t5(a5CtITTHuza8~yY^ae5H{ zfMMA*S(@!=WfaV#>d8%;djB#6FK*JXbMo%X{mXB!>yYF9OxrRL0fEV$;3>pq2EbVu z8QyF3J94mF&==DWa{XfJ<%bhE`5-_B_ziURA4X3(t?Ml`id>(Vr-H28&t|Bij@}avC;W8%T1FDSpJ-XwU*&$NB@j{v4<# zC(7r&I2VQfY9wF(WIj(!ZC9?OTnAZEHOdTY8ykXJ3h-9-5Rj2TEk*yq>iT@Ae6D1P zK%#UHG}!-4W0$O~mup_o2aQIlpc;#77RR>Xcrzegp$PBD0cs-8Z(2rxz~4<3yF!gf zQ17?=HlTx}x4GR1xaY&yC^az6OzA-@6W>bHs;Mm+Cw-; z)paBiarUt}LdHkug9Co8>+Um9e)H#r7<~?fp`L4sjVt?5l1A=LmG;5!|0D-C;JipIc^8=?pdoVK z_#+zsC()-Jd5iNG(^p=2UwgtiN6dkt8D|SLq@p8fu9GJ?0Z&6h7p4$LRjBF-u?ryu zWM^kbDANV9vSk{4TW<`Zt#n4pa1qKgOvSz25!pzM;5Af|yaY=HJ)rCqF|^qFih{%g zK}RdbJpDP>1=O;Sv=+vQY-EA)Bsh6F(~U;U(!HcjDXjyxESYejxMp`6!m*RBPQE$c z$;oxW7SM;L-~4umO}Pn0?U5JqFb(f8-{~{pd%rz z`6}p+vT>~Tl020FKx^5boSbk%0;A*Tv14#}LJ}Ruid0hCJ+h$?_SEuQZ6$Q~aF5O7 z77jnV=>O*cMd8HbYogYp;neHHNqI@i1Cs5cPbVWQn{GLMrxUT0z)VwpHD{q7aw0^` zMiJZoJPdy?4YlN|5rAyD3yMEtaqQ~_)L}f2xofQYJbx@w;&@WCAsEK}?`k$o|3Co@ zKCFOt@EH&@|5dX=TR`aFHQQM#f*w}FV)gW?Q!&^k@jx48Z0zjzOZoC#Is<>aBjtk2 zeA@BpX=RLJEBNzkDkBKpM1DlzJ1Wu)-G@eB&7nQAFT+V0{k+S-q$MYdWc+B0rp}@A zuEjF_fi`R{%-_%(P4j^!B{k{E{y@ehc!3m3t~$u@8^=#vs}+R>Qe&Ed=kFg7L@Y2k zpY4FS212@E&7pA_Sy9k9KD{YlckyZ0nK;GBr;YE; zUVp#{z%j?a@7cildu5D*HW0y22kHm6GRLpJzy0d>UnCc@o_&DF7@3$ZBhV=$Z@x&Z znUQy1jFt~m#&kD7&0e=Bw@kuLw9YVdteew3asOVZLm zKirZVWVp!buz~NI3bDOxi1K~@JRHdZemfLW`T@0-3%g?~`v;C>-kQ;=smkd+E{ZaM z6weW;u>25^@<>SAju_VvlT2nR;j}AAJ!+95r;2Mj4ML_~P&^7{smQ(=oK6O_63h54 zpvm&-31kS7^>){4SFK!Ga?yT8Eue@{LpGb;Q9O~g02X|}g7q_mO3P)SbvWIS82N$) z?Pq9?7GHVL3K{V{ms?NAKj8Eyy;x_GRz(%VxpOI{l>rq(2DS!oYc!4oMg`>P72}(| z@(EFe^~uwxia0)$AaKkKNss5JnV$`75=ww3NY?7r^ZWcTP!(;;=5H;y0ntg&~5Q4*sBVe6IY3V9jjqX>(<%TU$6vSHt41-$Y&q^$(qzS)vvBQnfo2DOmG zz`D@ai9tc4|WUVX#zx_inY+m3=c^IKcR6W6&nk`>+JO>LxIGB1jkq7Aj#w zlwk_P#>OTVjpP_KGu5zzbpsrsO#S;zA#!)lAWkpIULV_vc1KW9?&x8+5%*Qdb0MQQ z9k-CfuX4ccoWe7mMew{3m|LCgASjkxE1au-K1$FB*jo;$HE&2zh>v2J$sra z@C^mdR5XOhhCvJBs|{kwW_{iDCpt}uEDo{|IpsJV3 z{s$#2kTWX6AR5uGLrfB|nZqCqUHX%;H)ecH2@5M*Yti@(HS~XtaARQCLLhmmpq$qT z{&85f1SbPHwogeBAVS4t4i4K`Ad6t%W5MJ?H-;<|#&ia*)Swk`ZE{MumZeCAXm)j< zh)9EO-`Bvy0JX5r_ME@;xOn+`_yNO2qYpxKAwM`R<&{>eV~U;nGLoaTZKnL=VPZd} zG?TBpEaPa{Za;8cN+|a^?Y`gsGpdD*-3MbdBwYPh2SqFO=~%L#4Ic?-(RDHa8k2v)fy-TA`_t6i=ooY z9vq?GDAoV+6N0-t$j2wgV;+kpb+m9EV*$I613m>8xWJ*!)d8SB6yOcmq% zm*;3Jz*%HFj{sB!+2`uwHZxE2oW{&5Dw&G3@v3~X&x(XIG> z3B3^P#iDKm6)tW086`hb2^!hjJC|hHVBzNWn}(2h$&v4OIS=bsY;Lii zxKPjv(M6(Y2|lO3UFdG?9zU4>u{vv|>j=00E;`U2LoJ5mVvq5!_uu1e0G!)FApBu| zzLTGN{si@t49{rKcj9|LRk3q#;ST2-<#+y~1{xwQ_E*zUTulxu}IuxN_Cpm|3 zjeQ=pLlFe8BX^234rH`M-l~a-iSkUfFn|`=n_Dtr|EjhS@f+a*sM#Xb$1$fg6^BWV zO+4r0l_zXw^$dW&>Q9M*@Tc+IKU?*Df`gbt&QzAQ*PDa+X-{wY@JjdwEY1~a7 zkYxZeUZ!Z98tE_WRS2h00NYex8e=IK7b$q+w;1~UA4<;jvT z48jRXv#ioRKk4)qN^PCs7|*}(_MSzXfYJ*FTIqzzIYb6MfNRDdY`l;2etc;CHFOG6 z8+lszJ4j{MHmc%@YZdIOlW2Loc4u!*G>gOBQ~{UGex;+w1w&;6)*T0xop{biBn{eD3@_0GqR#@B+rzoyz;I`i?u_UB9U z`K@}!!Q==(kDU?)WB}Fv-QC-ry*6+v!HR zw|{(u&P{wbxpw!&y4e6P=o=S*{`~o+pdKUluV0iK1)F-!$Jmcj?$aIc%AOqV@BB{&eW>rZO%Qj9PcZ9_kWB3$zQE= z0q2c?&AnUqjTcu}{fJjc-F42HrA>2Ib8+F_y0>|Z6Fq98cd-sMjq|=%y*AGJH6>oB zL7hisNhLqAT7c$Hkf7c%Jg!@nyf`4$Q@V~VT^wJbzWMDbh8+&}w^#JjTnHMby*{R$ zI0S||d~~8mQk1Par73Kb;fmt#2RFu)#=lL4_PmHVo#8tBch=FkVF!f!*6iSa z!8a^i;+K8BH@i8P!Ja9B?gRBGQ!+ycpWoIajBPyzg|fKRZMalP{)nm-_mFmMEqWzS*FqD{JIdMiWpQB*yKyc~#0TQZkB(`|W7od! zD=zfyucnpW_6X8pR(meQx#T-BSq*=LRwKVB$x(9u6~|v|m*wyuP|U;-l7G|%ehQ2@ zEXLug_E`*_$7dS>`a>b94@e!QmWJI0Rn0_;UB{n4I`R~W#m%>xQ#B-(^D1S)wO}!q zaq;s@WpfR$PEgPGi#Xl$yY2ax?n-E6E2W{={P_WXW@rg4SydfJx6g!xj8WofQ2UYt z?@Qr<=sR615wEq1n2dTZ;|h~C(adRIl34Ir<-F?KdeWweS6=Y!?3|^*OHc4R!oWD4 zVYnN&ASj@7lLN)W4^~KJ$LH&ot^>L8WWJ-r?K^=hKK6t`CQ!K|hP;d7$$~OIr6eY) zAfF-PAhR%i^4hf*^%<*#O;R;Da3e204t*h{ix>C>miXTu#u$M2;23X+@EO%Qgola*zH19K0M zm-W0V&ymq$gv!u8p`P6?Q17|LK)B9XIm!>yPXicr9rN!Qq8+61a&Y8c_w$~Q9^qUJo3XmO~ zX@ziYg10>G^K&SPo?u(Al`z_jv86$8Yzn=2fBa#HS)WNozp_LbW*Dv@!h&(g-1t%HiNhnu$m!MUau~LCPHyvzJf( z!ztv~q%Q;tAs$pso`W4~k}WVW1)M;7C4oS=hedd%0)W_NYX6qpjOHg|k?62{G>Bd0B*|Tc=VYrPu z#R>A?wz0GCMOy(}b77p01!%bx_rOld4d8^(-ELWuCn+kv$VxT53-v)3N~_~4{@gEo zZHCpJC+Uw`t%fV$BbE%VfuSa~{jag8?FN&+R_z>2{H2zDdMk_b!H(al`8Yi*=*GJA88Z0S^M3 zS^wMo&D1Msl62W0aUcpjUMp~S5By%lCYX+js__(%7_tpT@kTOoH-lJEwe#$a`*l)) z&_w~eMWLX5@aUvJ-{@HJXn(vy)H7e916Ur3>)^rmw2?nK*0E=#W)i9%%af#zd2tx%EftkpS^+lYy&n zHh_3a(hXmMdjyk=RtJMFCXB|o|0B~aT!INJto6aBcb-Xb@SO^C+Ds$MZK1EafI|d| z$Di)Mx_t%--N&F=RHvW!Ni{qNkH1!)_!u55>r@X{al}r-645(>20WRPkE^L zXziuC(s*pobwmo~*g1C4dFfTMEe`;_a~w)(nqK+Ff<*|0Ttuu64sP?7Pe0gux96sO&3>g+WvD`(SUDe&-{+7gvw?Bj zQ7O^3E7zA67f2e82tYOZ`_Zd}YYcWXpPz2HYK3XDHPfFG)PEUN1{Q*@T?)jc@FP24 z*=*&JRso<)(0#02T4-Gg*l#3U8T#S6vvAsA64vG_YpTO8I)>B0L4q-vR4{6lZJBJg zGu@CTNZ8Qm%_X}+NYG}RfLj5S6@pz>$Ws|e>+3A5E#{j|8YIhQK&;6Ov934+R zp^w*fW?8I>Zy3n*7x2(Qs_FR?oaZo)o^&YysmhlqLJ$kJ$5QpaTz;Z`I$wX{XcnA@`}zz+)Vpv>RDF(ks0s{7l(2;`Y`LS@$z<%4FTS-Nh&y zsGBi}S0M00b!%_2Cnp(PJi0X{$}cTV2o6|nXs&ols?5YC z$qk|E5*i}r7+|D;^|&CRz2eIk72bHVkJRBr-_^jU{$4e04`t|5rYT&TFn4?F*ZWU6 zE6wiS7rC46kfsmcCFRm2yr!K287_`+Kqh4ENgH4$9YBNhOS}a9;nL&h`iLfWxjikY zSGFEw445`l`Os1P%q4s~Y*qF=Bk0C6A~3v%Q-u1B@Hdo*0S9&HKsVKbuMkCSCODJN zR&gEEmL04`h^OKL1G~&nnInL=_D(>r#mvIu@JWvcS+I(H90VtJiVxjAXP{`w22~u} zEo6g3vqdXHnYSH7u5n^%v5{$n++s-!F!-bI?s7A2ExP2)d3zNs(y$L={q}@^4Wtb^ zQsA*I*?7z3kWr*8c5lgA3~uvdB+O}P;obM11=L;glbz}+RbUiv7P1liS?)s>u2(jl zem-9;&cUv?e(gHe-|k1Yi!|KZvJt;-@G^GaSlqRM_}wyXl=KS?d6*7Bp`eoQnER^d zYJgy)u35PI`TV=Z`&99Huao*F;Yi-Kaf@hxN_+0;^&7O%a*rp4|pM8?!{kmsl7D}wu&w~M6xdhTp)$E#lvCZ zLX0BqtzS_2#^o9NWBCAWIKxwj=7)@~Zl{iCJ|wHr2-$}@I*rsD(aj8~75XC2OTn@f zBO!viCC_DxU(u|f`|QKhI07TKN?`&qJsu<{bTxbqJ_UwAFXaZDX13prG3IH<=B5Y{WRs6!6+{Q0$xOP#Dot-qzt0w)}^)>qfRP=%R&poao5CIeBc3IwSv zn~6QdVBr-`PGbLwBIZKzh5@yyfDGtKsUrysD`W@bf9(fJp#U@RY6ECduJA$FMlha>nC;A%@tX<67w>I)Bipj#q9G z!ddjp5z;59f4J!A<_QnH{`Cu_jJCdLtugK1YT9EKsVc-r{bc|yD`LV+pXJcr$gz8Y`}xUBOtKxR)4xk^J*6bwcLQ&5ho`s7ye`aM$9f@CJ$x)g z3+5_Y(cCRHw^sGqA>foZJ%5(dWo`U#a<~W?hW8MEk@N771Z}K*QZ8MZAL)-tX{l8I z(MeJ)Ku49=U+uh6^{yD@3u(rEVX*q0hJ&vl{pxQurB7ZzdGfL2kw2`{ z|4wfW7NL247K&qj6&Z+e6)#a^adOJz*v_&XV#5wJz$u=7t2q$O=*G%pJs2150qqQJ z)oX>r<6ZP~+%Y|Q)eSeAyFLKtifJX2Qv&rn@ zTfoq6v6HuB-a=g&c)YJ#zlsb|?|-fmBnn`wTSa;$`%x)VE2}eS&(e$nLtL|Q;~A_x zK*UzAk#Y_WTE90RRA;B&Y2#3)g0WnyPIlIP%Ofa!-&?WFO+S3IOGr-zp;-xHDY={c z`Fpq&P;r&Lau*I}O6MC9oN>dB4G?^dV6mT`o_-3$q6L|o zG^~%K>qjnm50u(A>{WmTAx_3KVUp&(m}ez4U%;-so4hj_-7D;3_} zEGYi*RaQa`1W%7@5epd}7M4h zeGrpco+pCW+B2EICPraRi~{t=jB>#&*6ZwU>Y@*(%5elu5d3=Pw|ETQdzyr`00~}p znz$gP=Kn)f&rVu;C8?6|mm)IJCukF!w%urMo*6#uph1F~C!)tnX6DGx@69NaX4Pi| z`W5vpTEVgfpP}kbL>&bEXEm-}yWV{4rRdK)vn0m|#hH&&R*=MBG|FK76@?s(@p zGBW!0-Qg_`{@G|tLUhmmB3ihQu`;QlF&4?|35(@IG;o^BTVUrXIN9O(O8a!M^K+je zg7Kosyrt-qUbK@_Z^Na5_QiI$HCqh*NZ3k7hh|T0mXO)8Z4jQUr7pOza^X}W>mJ#X z)OZA|#ur8u(_C7oC6iZT__TC!0`nx+YbtcxFhB4GY;M9S3=p-t<@f26AITU{2jG;p zMprg;uRB=UJ1$-Io;7oAK-M|EY2OOx#efoQ!!3fk(jIC;^8oo7D6^SyKUE2LbBgKfCQUH%Y#5uXa% z)IRg%8*6J57+`KW`ulmoT0>ls>=#t-LrO5mMHr$<)%2V~tBzn0h&oL3s3ruU2@ol2<(rYK)6)8~HO&MPb?X_W8W{`q>jR3Ww1dDFi7r z{>s~S3~2P3p$d#d569ZpRxw&`8>k$GsOOL3JV!xFmmW=xk&?oi8lmD5&i_iDEG5qj z1GHE_(j3&zEhoVAk_LEyGs3ze6c^BBJAj9O}xvr9A$cK#UM^o;8f~mP|@XFy~sB+cDXgvwW zGP6O&#xMU(=v7#9H>g_`;+#i!># zF_wN$UM`qY{q~UrX1sy&6`(QpOs4A7@$59sC?*)FTio?mlf^@I!b6oWz4)49-Cw;s zF#6d!`oy4lB+|=avd3kMBuA(X9fufH-M4!l*b37f;c~p+0XS(uV~a=`haGjt=^_aG zKGNj=9NZub#rlb(c+OpAMega_j(E&VakX(>Ju(AKbnC(jb<-us8;$GNtwUXXy;GXi zfJCTW{6A)=cgg$PouGc#{A+J;uZ>j`v~UP?Mi5SwM3yXc=52RE$Ozf3CmfuG!sHLNV*s%6uN zJ}6ymknV+bH2kW$`Hdp6aCv?q#HtNs3qO)~f6fyTWo^nlKw&NS!=P+oaybl;N!fgZ zW3!|rY&+mc;Pm`ON=CIwgR7a7Rxn-1EM^Qp+UUPE!%aq>m z!xfKMezNszX?=@$xMc#!l0MOXJ-z2iHtdCffOKIPyBnO6#;pd`XJx{CaYhN;__n>p zx`P>deTKt>4eT(I>pF!oW~)sH+Gj`ddvfH?HuyLh=2Q zGxLozp#PI%(s6$12o}<346HwS_UsF`F2XyR^nv^b8tfNtTUusb&NTLbc;p@4J!jnd z$Hp$x%2AUe2mwAzZtj;zqM2ilhb2{tb;>g@))V!fVyZn&E`Jqa0H>dzeIs{s|1URFi4I*d+Bq6hGpRP_e2@-BHR(v7L#{D3f-zMejJNns(U;OGj z8d^E&!`W~1dH*-E{sXGV{{I8VOGrvZG(=`4O+_gUE80H#$bcEBUnYg%GkZzg>?M5-IufZ><1r*zjJ#Y`iRWz&(`caQ&?)H{iPyZ zEd2r2g*OVt-5yh?9zXG9W2lBXLt=z4O(!?<2IswfZ)qevQw^$Lo1#YM#=By4&{lsp z$T*QQ6*&nn>fULQO>EJJz(_b)e!`Oy1XF4o!B-e>AitM5E-X*o?de`|GIz+pk- z$qO&)gWbj38jLhBCP7C*=|kYwta`5dJZ_V+M}nj70Tgv_M?2{=daoDE-^#9r7^!wm z5av%mePv=30%ySdJDsGut5&s{^ehZdB2bz``LrE{)laKn1%*Gytt{!^-9 z2){VPOneLlu&ijuSws!Gc*H&+%&A}P!Kot-4D#;l&rsxX~;Cm*6T}s zNC?^y&i4>AuAEK}(mz~jB=8xwb0u7|X2J?wzjcH~4&NDsasI}}hPMu3n#?PxZjC^J8Q;rGb??XvQa2VCKrzCxz*xN;)o|c&(^`3~)w&qTx5%&;xJ$!0+ zj{RdXXSrYy$9L$j?xb&QKD^DAk{;|S(h?r!_Ty8vq=Qv;ZthcJ4i6vy#Wko^tdWtB zJbx^G`GQJ{5}7M{6oT3eZqR=0+XNxLqyWVl6o8fJYZ6|c(I-u8OYc+Y`$6J)MUp9F z3a%-Tumu{Hr+>8-NpxPdAz*_)mjyT148HWE0@<T6`hQNAX9LQY3&HIX>WrW+xQ%dWF6(r8S_aeFUpd^>OTo-o9pEF z+Ai9DE$<>^XDRZ$1yUbiboHQMV*|{d5*`Fsg#I(k%DQg)L878=_dD)K@3@b!U17fU z?#mrCa8Jro5F3SZ^fc}lpNsHhkUb5loW%Jiy4*=WF|(MrW&o@Od-~=(d@vTikx#R_ zgynf=Kex>SjZPWjbGdbO&(4pLpMUJb(6CD&&wW#z5MQ-r)(oGiJ2Wu3-L(09n26I8^L-XH|w!2t@>(XTg2rC+{O*=8bgjyWSFEhJZW zpyH38t%XHvw({7+#qI~cJ3*rGOgVVTo1|7g_T#1MVDD;Zojo#mR{Rp4Zk({N;BHn; z{Je{slH&EUo+oXu6~1O(%+QeK;W%yu07x%qURbiT@rx|Ofwr%tH&xS&z5lN}^7Qr& zo?jnpdL$oG)IgVUui@QIA77y{En-h-R{OZ!)3ZBruYs$pl|r`k?@zQOgk!hb4(Q>cf}>?S61F~X0H?RF7_HoG&TRf=Ekkhwzpj>SM|zc7P? z+(9nW9ikbk17~T$HiBulNqGVVrz8FrX_AVv@7IKlLgIMw`5FT1x%ApP>3^+VS1&5P z`P~Ji%`+uDUGqw%r&#{fo}hIAK)~z*d`~4&v+0cno$5@TP#3Lk z>QNGlfRje8=A)F4%I}C)7aMvN0Z1>~D33`y48g9NMVoACKZv%AspI;z9 z>o9$o@D@O~^Q-Ck zAFRl0G1ry7T^tQ2WaCA!n$q_kAu%mhm zh*9%6FIKrPi)LTmRT>m{re9I1WcO!^bg4>Ze@uL!nsfT{m3sQ6Gd?Adfg^M>yu5Rv zUm7kRV}rUMy#=r2-?{F`ispie7*oN6q?4$U;ff5hCvYHTxDuEemBtVmB70?)mn=n2 zKHGAh&vN?pdQ1VILuk5bIz``}J&`uIA=Vh%z>t0LRTmKl>Yc;AXwD}$>>d^MqG z3LUI6&>}#|hhNOPjQ1_i3STb}CCRrFDVmebXZ&bzb!i}=@zjV;?nUiT$+W|GI&ghn zKXmr&wSjup{gCZ~RY{c#Ot1tvi$;c7PlhzxjxA{Ai0dkQr5gn^k~p`F*x6a(_DTZ={-G>?fLE&jK4 z-!Dak68Pt6pNLP@#q=7ftb669BAMc3^C2aDOjxm)U0M8!=u|!TZ8S7HC?DOQ_}W8o z4lak!EVPl#*;sa$#>m%+)rVAn`$nj-f(8;FJ^ac(VpLh2ZS$w%NjvOxR*1EksH6KA!Zke>wyFH-1Cs*+^S>hXfWwKAZURwKQQs`O{fN0;U&Br zUY~O0tJzZnYyO6{H6kDT)t?YnC_5698h@H7ZmJc2~WSyxX5n=v;t6}BiqqVnRe9qJ`0?)RP z|H}ncl9v9?!2N72?3~;uyqg@EHQ+)FHzzW~5h)6@RBY9ntj_F;CJfX8Ja=Era%+8+ zHV1AbG;=S^!boY-_u9^fk9um46mV#~xzLhCaA_EWhbJc$V22H|W^b5%=c%@tSo^7q zkEy}e>K@#Vl+|6Q=-`F=Uo@yK`Qy;VPc4XiHxi#=Mh6)5r=_!#>iNsT+mX&}tJRJ} z;_N)yn9|`8Mw}Ppynna{wwKE@HenX>N|QlL%eRL+!<^4E9TxE^smfowYsMwIDQebA z^717hBaAOIGU+b%lcBLXr!iCQwz+OETM(9VscBH3Y4Apo$#l5!TZ;xufuqNmx%AbH@4D)Cnok%5Cm(5jQiwLJ@Bhu0k$CHpi z?hG9e5tIomkIz(a=ib^|W1PuFK-!ZJwdZU*b~SkZ9>HZ$pQFh6$wVaWQQKF%i>_V9 zOFmVj#lX9TD1Y&H-092{#@zRmR!dIkvzN_D1{wE|8>s$etFFVh&l-C!w#KdL5@C_f z^z_^h1N6LmOC6KpMdiA}fsGR2Km5I3D8zom(;VO?@ z*v>1qKS5(IDj-a4+YS?7d5PBK;L;~7I9DOf$n>Px5Q0{DiF>#2+xp(I*3>^;V$LRuwC(LxD;XD`h=MEP zl1Y#a zZ+UhWW3yyLZ_kCcSF}WZrTO(!&nxVx{E!s-d^ZTA{_z~)y4%>bfQ;J-P5*EqbHXnbQWkRofgA~w1+&EWNqe#NfIqQ3AEB^A z|G>Z8Ix%J|t7`?K+67hbNy*fQCx4-_Bruf!R|dMKS?h0$LM(UMfAp9iFEj-~UbZ6M zyljnjs7_@#d}?~dZx)fj2QhTw6gGq+Za?uGP@dfaL$5#pcfPWCHBkp`|J}42crsOsYvp`Rw_){6v0G03mHE~o zq^qLi&g|efW(&(3u^$tZ44djY>Pvnpgx0c|ym;bK?3LcymX%uCsk=izmbkd58ttfL zo<4a(aDWqFLI<%^ZHzoKLX{%{dHC1#-cNu4-20xy`Wzj=hNGuWy~cyW6qGpc39=Sp zT?@`c1;5zq7q!=V*VazBjM3|ag`VBJ_d9B7(zc+w_3H=E6)#NG7Kp8_3!OImbfzAZ z{rkVL8!t%ZrCTNEZ4ofO20niD3+CCkkI69^lH@Q7tkZ!bHt5(s#_=XMT65L3IM=%` z+-Z^t`82;g#2vf~0K*|8197y{1hbnUlGiOxK(Du@5v{DX%P)*${=fF9A4&EdIFJC2 z|BDJ)NTRl=;K%i$0bzq5C$^1Zs6yDNk?y<(V)qSTv#P`&421L3ZGtX1WGOX$=GZ3V z=x_pYD(N=IJyVSSe_#6gQ%v&dtDfz>F2SQ21y#^zl7WE%qbKEf?b2mLUp~G5S(%;N zIkGWIT85QX<22>P9p?z}rCNTE5Xf(ks}K{?#IUzz1YHaLC^0z$U#BUh17;Sec2E_S zpr>j5V&t>W=5E<`Dw4cUx-k(IBYa1cX;UJeoe2N>T3B> z={o%&Lz@uiz;8GG4KfCqIxIT-m&THre?&sU#!QX%?0KU%^)dO{n$0MN59}t2_|42a zf_OsxW=RjI2}5G2%Zw(>M4Lg^BXt17Kwx|pBWVoANU&@wJ}g%VoK)>Mj+QyQMSxRP zu4SO^jlfTF3O&80c{N5sxm7Lq9$GqsCNq##bly0avi|y|NkaDH?74;%nSHh*IyuXv zsA=m)Y*BKh0ekQesIXz)xElrT6!#gGxj?y208Aw&$2yB?k$_3# z30+v8fYe`tD}y_KjiojigQ!wSBZ)E~Cc4Y5|L5*mqqDx8{Q6r+TqnDR<{yylYJ;Q0 zFE}phrv@2Bc>2+~EHH6t2RilCMm^O&yW_}KEKFwJf*o%;`}WgaDzh27CbK)h_}<*o zl7qhzWDV^Im(_LL=C*n88GX~B2L*NWpeHWR*|1K!*P0k1j-yKluA(up9>AsXMqdju z+a(zJ6R7pBw)$Tzx*+|?H9)#W-VM*Hm}*0}`uuZNkI4J(FZ1W@Vx|g#B}6p}w!8oB z{;4D0E0w&L1}Zew?AN_Y6eTXq4mSS^`Zwj$I@dG2W2%rw%y}l`5!vF(;%%%H@5#v( ziaspF3Qzx#(PrG1;9Zc~ESFLQtIU2KNcr~K5-k!p3O)%qdU7pF!a5XVF2dac6K%2v zQSNk4&;$PhWCw@sVi3O(;rB&=C6JAZ6W%a*^su1uB0Bw<_#q7X?*_xq)B<5QXjSU~Z|8v%G=i4a0KEVFmKSny}8`=w-!IkiyW6ShGA ze7E?`HX&x(B@oDV{J5dhBvR)0M#7upi>ohMmHqWHt6v^#Z0FXWxE&D}<$qUGYh{)5 z{PN1me&dPs(xCP<>&*ga@}qx=w8i=g(j~&o{Yz|+QFVFyzk66;2%*n9aPWqhVvSs- z(&h|I=}lDq{N4r&%5#J%R9csK%Sc{UKO>82c3P}@yfV&;b_$YC=rBvdny+TVa`&?e z2Ob8Y%;A0;eP9{5IcNic^YG4q^l}Ij)irnL#e)ZL{1voZbG8?H;d%W0o%h~H4~~3_ zoI5EgvSa6yqXW@9K142+JoNC0{BZPBDp)8Donm3q5hX3rLBDZ$x| zQ@>edcT$*T9IqJ)6)W%j5+r9nm@Qy01FV|#$+KsO4m@7%2z)UT(<)F~9w)Nav_~`GVx2C4PY&t{&Q9+|abK=7OxY%&(t0xa$zKj%>>3+W) z@-`pdQ3Xc^o~(05*iOU)RBMkNJ<1509=ESEOd?MpGLTZq|4fFR7*wPOjRU6YqbW%+ z%vZ1>@;-nSY$w(ckCYM5Z9zIdlf5K#l=R0>pKdHb;b=hE_!5o|JbI7j754E`GYdVO z=(@FFcBh$2Asdf`*lsOGGBE;69+1U#e7%~L9_&Mmr=N_S|GqqXJbJX4 zk%>tf1~A~r*|zOZurI##V5C96bmtE0X72TL(=lfBhCk(xcMfim^eJBYOY(K4hx`YK zmvoAv7u3}Q*I(hNA>6bJTyw9U36)qmv9`jglCXna@nYW(CvSVEaSqSv6FgzBfAKW} zMYM3#PS(x$FDxviREi>eBS=4=f3W2ZJ0{M(;PQ6U!|A{2^u_I^~;OS)4*hSP%PCVrmB)nH0@L7*U0S0`a@7nqugLfU_Wpd`;;4 z>lJ)E+A2sun}d0^_)R=CmZn<~2f<_KEd+aDWxXoMY1jOv$L%3JM?5XJKh4a(T;RfI zcao%rjE=?eyS~)JiK!vP(?Tc$eqiC%(t%gBOEa<#tM{g7ADuDnp3I$pC4k?|JP=XZ zVJ6sCs3v2SRrRMj+;Na8CT8!3;|rl`{$YBID>LrNZP)$T4TPN*aECVX5LQ=A9mY>( z*-~sF>*=t!TNFiJ$WfyLXQ_g&BhlR(tMgf~@Y(^T4h$NpXcil@plWrJL{!rp*Hn!- zC2t{B+!YXrtVyw9*sM|H$S~QP$A`wcxzASKN`+QLgBT>p^5*+DNYizmG*WHdEiT@l zseS@h3a)DSim?lw00tkPt2A^v>|9BKt5e2eyxMzr_xx~Ex#zjckQp7V5ugebxazKx z<3Dihs#3bijqV>*YmftYgdcBJ?z65APBCSmVo7K_5F_7EL;CW7m3@ULt>cZCYPEQa zCU~RwLd|N~`fdeo7e(*}s04KP7v2^lQ4|*QwU@4Ca2Y!U+GMN~ZE zY{dehQg3?`^4et?qS&Qg${o9!PGzWaOWm#`!7FDa+*vA?j?l}e1OXgjeQN7c$^G&= zeC@^yW{d*f>*wv}^{1 z+{uSN4EhUZYh}Ln>U8VII9nEe*ecqtot>MAH{cnboSMob+eVoqq;v7fxjuQL0$KK_ zAutJ5`Q>Fpwg2+1^CUMO44E{2skukHckkBWq{;9oAL9tNhq}rA8`Otp&oBM+~x3}yV@#8q4bKd<9mUZ_%}M}dZVp1OOXvruCk zS(@<$8O4j}wu{qQ+_f_q%YMuZ{|Y1cZI4-;e!hRmg7X#%A~;#j{4}48)8NY4y#~&% z5-S86f6^QBmkkU$!bFZ-HXo{ox)64R`}AD?ZiAwmNI;!?bCYrV=EiDryx-vNyf{j4 ztuXOjyLVfzi}?C;SH_mTd@bSb2Aida`iz9PVeVv(4>^m&VKvoe#R%^UkIcW0N=%cu z%)S1#=ZNRxEB-FA<6U9nz7n=RR!V!sxabSyWGbewDNYTB#^mtFZ*=d_E`VD967XX-#c(ir(;f~Wo8%Lk{}AtdaO z1hq%G;Zb2>Cd8{F^XYDT)+w+p7lXIY9dh${KsGPR=S`|8kr-ewyJ$~5BaI$nVdQ4v z#aMNL{V5!r{9q$dhi#3jq#Ha97f`0>Mw=)-2-N&?b0)3EDf0sre8W5P>eJW>FNvuN zdD$eR?|j|m?DN)UFPO)Y>=i{x(R0TsPb!2F3unNKAyQlT;fD|EDj5lv`m*j_Swtc* z36X`cI(^!rHZNOOz_t5)$60ZM?TzuV0e7H3xQ(OHweExD((>|OZ1#1ZZ*Z=*Mq!t& z2Uah>xn?u?E36X3`5Cjt`>*Bqao)0>qCOv=@BCM>$^Re|Q#tt|mm>Um#raOVQf_(1 zNhyR$ps0nRIpM+Q8}kizOd*6Ijdf;#tZivV*kbkMcaWTgr^fzy}mH2=BG z_o^cI@vZb+9?6B?M9j=Ja zXt!(8AuLGWV$&OkMdYW|8j0`G!S$m2{BLmQ2;msMkvNxGY1XVWxiJ1<`>w-mS0r>S zIuB4Ew6-55CL9!rS|uNSFIw{_((@XxeYzef7jxS5qaLi#F#2Xw3?IMo*49HI45Xd+ ziibLO5MNvZMTlTF3ZWW{%u^xy zb9Xw5yS}h~FB^NgeHZg+dLzaJPYx(YlCyJf$8-hXALI(jofh|-`r_-VA^TcDS@Ua)={Ca)37?u?z9dTH$uTAYnw_;#s&EHY5>^&i|@gyQ5!qtnVZTd=E3z|iz0bEn}O@~FD)-T&8 zb7Yf4I-@DkUEZ$AIcIlqhecogPABk@2{qQL@(&6)4hO?LkKiMZwj?X!KeHA)ou%dE z`s=P9vNNi`Y|eLg4++_>N6%1gKHh#F)-*{Gbz;Z7L2oG9SYI*zX5w|X0hNLNx|4k@ z8rsWA^MfJRzJ8)U&zo0-p$3dI$%`OS$-bQWEUi@j#=houi!{bH1JpOm6^0`#}Gf75;v`#(|k{MRGeTjy0~+^fwx^|VN* zDAktwFAPRzZ4O+%Iz~-yGdQfe9(fpCP(<8t(c|lTk|$Z=viQoy{ylqm?V~i=w-3AA z%L>&`tr}X5lyh5&a7f#`y7z3$|MILns_e63IMw_2#LnmFgBp{^^`RRYC-A9*jGdtn zgly(Ld~F>Ita&Dpun?M{&2*?cEQN2?xa}D!EX;JfynZoVyf}9}H`l&IZ1KDq*K;N^ zyRlJ0YT{75Yh2?!Gtkp7y;Qhq02WYVk`8kT-#BMdYfcWx*na;0{Y{E>_qga8h4*Z1 zFtfO+3q2{ErB?a8HNtJ@r2Azy&*{rNiIFgQeTQx%cjoFd(N6#@NxtSfyv|^iArUfu zfq#rJI>wZmzou$9JV-O$)@$t%P2|lBOY8P`+8J4#sg$_0h(TX_LF8ebV`oBo(rOqr zBS{EeAEqm*66Ig)?0kKEN=(w723CfdICKw)uTlw`dse*dS#xyg-+iZ9j;01Nc8%J9 zN>M`iBd=i^=CwJaSDjcbMj!U1I?X|M*>BqYo(gfT6h~y}In#43FDRPLej{ITeXzxe zKl{R?IkC5pD@g@SvAmfwhsDoyy=)G1i$!6NUbIa3@M!p-`7u7zcPy&(3~=Z5W%`)= zE!LI|8!HLX*{*EO2kBwtz7<392%93?Z^I7)wLkfG$c{{!kIATE#fS=jbT%$7?p|N) z8H#|}VE}K5pCZ&x5K84LWA0KsCLj8#?1tpkOH)9|47P(Rv%Nmaml~Dzd6?ArwQKk)M~Lg#J41dG1ol)<@6HvpS>M1EY0b8Y<#bQ@=>z}P)0T%xDmia_ zkm@NedQnt}#Gg*bp@Z;HVAJWau%60yTKFy0^;M$3`vH06ZN;R&F` zyvi^jS7ecZ-MDkT&Y_M$%zJ0xSSp~L!G)1RyxJv*6IV{pO(h#l^A=_iTaG<(z+wuH z?e;n7qykU^QnQE#-fdw!GWjz~x_jvA>%z^Lst05wu)ZoSSdEQvyY)ZBONV1lJ>%&; z>Vx%_C4q9&r3_Bv?cbj|DWS-rJ=#{`{{TDWVsasMmy6%z%J4gXR!OL{0!-On$SOXj zUb(wsntWkn4T^u=GZ_|oVzJ@0N2jbt7;S9?w}091eF5f9)GEXS0ZmmU8I9;+KZefYslpOv#cwnc!;7UpC5>@TZ% zx<*ym)0Eo_F^g&1H=R_cU6?SK9Bm>RG(skCU~np=T0K#9wdH?gCx~x5!e$QGEF_q3 z)k`^FGtELvQX?L3VKg&fgUw~yyi;W`_v=IRioq}!YnFpg`cSNu`8<2Lb6Yi#c)ROj2Nk|e@I-Qi5?K^D)YEtVc{gBOQ6^?b!e0vM2Gne(? zFF{<*+;25&YwHZ%i|whk*Tf9CY6mjAviy~V$zhDKUxow9)DX_HOyLmyVTyYjE3%4r z{3ImC+vBE^S=<#8c=Q=s3981QX_4O_5H4cZ zU3SQB!Jp$}ZjQuSithFsKVm6guSyT8yj~SsJ|6IF-!UqOSnP$y|9>Y*$etnBwKZG& z+)IzU|CZU6uFuxYFU`e<*H@+!?jV^eNuj!j+xcK{!Kn97!6}5^ZHcmFwKAA1eJN15~Vg?w?5;dHUZ zaeeHf3j^2h`IQd5mF_dA$5WS}*(sL|+X< zeK{~29Dp*7uez{9~S7@!{sCMxiAMUc}=(YljU zX6Ytiu!Bk1n6&HEsTJc77q{s=s39wXyUoS+r%&nTZ3ihRYSTF{InUk1Xs+mmM#nwH zMsR@uFc9|O2Cz9Ym;Dm&^>{3BrR|ct!aLaxrhv&pKht)LrEXod$UVc!@vFh8kF4<49R|1f3Q-} zF?3=Kuy7z$K5cKtd2Ymy2IknsPfof`?9W^IJej{R7?Shi(vF@El!m~@&fzl8g2g2l zAs$A=cU3cfg!doqo;}65cAcQpuNhPlpTV7p^m+i zE&lwV@+kH%%c~Y09r@Reth)yAI9VfzxIK*MB3U?2t1NaWzN@0FrOo-+gZ{s-d~^qP{X!Y%`DL(&B8* ziuZ?0Y@Ev&YhOpM=ux-O3@5p~y#7VPzm_?^kZPx&Ponc~Gim z4de3!ygY}0-=;a#+|f~Ote&=gw7E&K$$hhAX+=WR# z3H(QDl>6!|j8lu~6o;E`=(_IvXSt-~Xi-a2u&Qu!^r8bD&{a5B!60#t8>hQZnqoJr zXM^1ob)sfZ?B1zBIds@tUWL{ls#>_weAPaD+A>f`k(Z$%Il_EiFMH2q0khM>))785 z7RBascXawxb~-H@-|71iq_98Mzc;-_x3b}_ZLMso*kY*LGjaWgZiS`{kd;6Sko3OW*wcP9BJM?x$3x+gNfwFG38m#^s&XJ>h@q3c|za_Wb0 z^5Nt{+tLHwKD@TcKit-B#|T=mD36hjZLrYeR`okw;eXdxqR-t8O}s94@+9GOb~9Cd z#ziv!=7s$Bk=Ms^=pGhItj$nQr!jEJ@$ zSiN?Y=F=D3U*}HNS63E2_uTqyb>$7Oo_P0<%co&FhJ!~tCuhh51m_W@j?#QthK>)L zzi&IO7Cl&rA(%ugV9jV*#;ZKo+yK2O@bB8}0`?1w*m0}RB@_L>FH2=*l}n@plH#zq{NQ1u2<)u&qxeDxW(L+WgU z)j1za#d#0PtpN*5)?2oGsS$3Y%T^;3&L-H5hp+htTxHdNG@AS2|bbgQ7W15oM z_c;>=eOL135Pgl~p4ddCs?mXAXvYU>9bw%L6ZJ|R`wlOCMp&V|#B7F87NH>go@LdS zH5BfwO>6?oE5#4D-Q=$m5)SGasL*Hhc6GHK8J&d1XwhgBK-k)tUB_9!txk=xT9H$G zJ|ge<_wJit&-t1ZMNU3eRtS6S(Mn#r{rg8P1IrD+C@$>G5z+<%GwWi%*eLVP)4r>z zq#SsYVtdYg?$c&F(0uz!R502d5q%)bIZOh+vmqQ(_j0N}2bS{DzZWddH3S|Iemx*= zOX;7~23J>%Y1j09gt+Oshk>KeeMOB>feRr240*0cbUJL*A8lVTCN`eM+^FO0m-A)z z4L8Zw$Plu7-?;0Sl9>j_C}EM27OU`gT%hKxsI;PjLWb|vOkwN(9moQ<}_+wx*jh5dIbLRyAwOctKnRTBm?m zcCaV@Cs-ny$~$*XV5&ge6jWiUVCLA?IAgMYit1QH3s?sey?4Tqt|#luKK@DwVe{Gx4snSmh`E zqTDHdaW?O$@0Za}lf&WrQ$BgLhaoVKMA z_Wt837WGlmJFh_!WNqSvhIU^heuS-L_tIZ^sj^eW%ra$vYwE|H$cjZ4@jp*%u=AXd z%-#`JFFMwJA$mKtO2w_~G7@TPqv6i}D}&6@Z~O?<)Lc>b@My7cf0vEwQC8$=D*e*`5!eXKnzPlt#{eFttq5M5c z4KWv*K5zZ;eY$(JX(4GcQmf~J@6`bF$w`|Q9VgwwlE?b3d>scZR_}|EVVq}OUrJcCGMIoZuDpQ4nQAmOJIo)NRLTUu6)YYmDCzL6o+Cn5@Ujus z2}HgQ=G4$E2Ntmgo`qH z1bqDPMCx~fdGN0}^pkJcM_3{qtl-MtC7QR!zn6!KU16B~WVFnPbEb0azxz=o?#GxB zv#aNf@16Bg+~qB@psP?w+ZuB3=(olxS^pE?aCQ#$UUrVffTZyRjCv6H{YhIx;zEQl z-O;1nbytnv?!&46c722y#@t-=vN;AdW{3^_jP-8iNM{#sVU6lq+lU`zqo_*Q+i_*udT_@(cT$JU*M9x$i)JraC zBsxt7u*sVno;%U>`K{7eb9pFVv4A7ieLefI`^GXm_E!b_@t_y?Ho;CCDfW-%MT{Jn zK%ha&W6tjYiR8frxI~15bv_LX1tO&lHe=u6ae5hSJR*(7eRIPFJe|xnVSpci)z0R5 zG6z*VaRhAH_Q@~sClvwPW&J%a20#moMH$BI8U>|~z-p#b1oW3;vQKo{gT-&-Z`rjp zwy*annFW8YBwcjaul8N*X?nK5eo=zPbd|u$`lsv97yll?)d9yN!vM$=fMv;DxM?6C zSu9YF&GAjRJR6(mB|o0whgu0k#|6j7RqsykjEOfuVbLOmtY3F=P^ z$MzB$i3B7ybwAu|gm}^mJFg)iLWywGZO(=*$Tg3X+F2=xNdGz4`aGP<;S!}{QifA{ zzDAZQ8~3zv+V2jIeL<}SI&myw{`^7aUuRlAM>obsoU~idAut#F^wFWc|J{VP^_Os` zcGX(N@PrtJAYx-ALfa9}_C?|hxyrR`uUxfo_H~3gCEL$^LP5c?kI|t|@!AJZrG{9} zeftI`8!38pQkzkUz^5(_twz^O7JkBNSTp;uBoni+HK=*WHgCl1@x^rmugXM6JpWjI zvcD4i6;6tX{Mit3oQZ1DKA+v0WhL|A*{L8E;cLYBO-7hQr=txdXk1gUh~4L0WlsLT zQ-v}w7~iWDxV#ji@nh$-n+(PM%Y#hdb4-i%+fF_b4-a@};|{>|2xm7-OiUi7MeE=fa4 zsq6u<{Tp2I7a$tZx~@0kK1f=$i!QC4oFD!uGw}ys+ta)gyaCIT&odo`yKM3`(#-cx zyRLq2(RGx>e#%z8WYgAQ+s1j~gjBP%{hK9+XnG!%&zfh-tVYp?Q$JG$c6MTBKLEI0 z(b_`$ZBU~SF({~@u0fE_;@H3Y=+QaR--Lb|E^|hRTYnC}ZM+>(!kZ7sS$_UKEe@jw z+}k6(`w%cIf;4I|W|N@GiI@TH-1{$31T+9~;fi3Uqq_um2DlM{qSDn|!=NzN<$f)m zUOVsSmYdhu9_LJVNw>fE+=}rmCd)3O(JftcplksUg;7_HcU&@sKr-afB7emVTM z%L)h8fgRG)PB-V&^e)_tEHRs8Iz{yez6S(k#&GjxFH%tSjE~34!9t*3`b2;-)A^ z7wG>GrRJEng-K7jKy#w9YRR>ojXzZsYqI@3z;*ulREFwC zZ>gPPdD?9p;IG>TO8L9!dhh90Es74cSy((bHEF#T_fzUdj_uuTCk`C~_rfmoUVzoe zuSH@}9*kGHGyFoGnekI&?R%Mtt|>g>_Fo|tE&<{f;Ej0qohFaKm%9yY>@h#;zCWdN z#CVX9Zi4t&Xe7IVc_8?plrT6c>+0&#@4|rt{VFYzF+b=Py5YP%p>_A~XZr9w!v(Ya zqKxv(@%@H^hSr~cn<2}p+(?@4m(>DSRS5Z1erzH6^n8;*<4Z=;%w-S7CvpSn{9Vep zjmkw)QylaIOVP4mLC>DEH*O8FfM(34QrBYUxY%v&{GAc5F%h33<$E!~tu@MGHqkb= z&Cb)3?H_;X!XlAcOqg1d5NS(*e50MUA?L=Iyp;&@0eT)18F|>GB}pCxN#qg{Bd7x+ zwmUpf4W~kQ2vp?DTNaBPkD0yEwt5M^5^R6WV#1qbRMZ)<5ukDe@R6#SNbtZ89jd`J z5{GGCt}?fI$^6*#KtRqa=+r_PLS~;hM>}#$Us(!{}sW^)lWLvf{iaC8%skiY^O1bOzGCf`u)}fs&yGctP zBdp-sr&n5jC#-klUZWpUyUf*Hg}7__T^Zg*Z&WIzmWTr~t_u7H!m2PRjL3fI2b=-c z+;xb`S=^TNQG0on`>#h2=8tTtu<7l4uOZM{5Ju+x?KzK*dcG0t*R_jorM~j_2fP6P zEKsnj03LOOnOKp7?G5(}gZRDn^n=` z+?d=#C=tdxoDH1QO>#bL{6y>TN&9;Mqj9a1!+6w0vyVOsU2a5=71Dw}Q7R7z=iysR z!}yk*#UoyAneDizLS5ZW#*vKT$KKvE5O~V4iUml?Je(?MFlZ`VU-GT*UDheK|k&Z)M3HHnV+Y(IWNHvOsAf1vbiRJDVfd0X)~nM2GAdhryqAeK}TTE-o& zE5jAgClb#=`#)%37x}9MlM64QBq-E6^9K1Z!NJ@T$iEHEpAqAn(lcIuVU_TxA-Ah9 zi&m#f9&U3|I9jVS{pah0GL}aQVTRisdZhjh3K=}YUys*awd3R*E@-eQkWS?{I%_|9 z0Gy=}@#{iVWavmB^a2TL2M%Z(tK!)V*#MY=*cz=*6QPSH4d; zURyX&WS>HKBljA&=CJA}LJ4jw?x1NI%iDmcM){;C?ZYkGy2p}x1_JC%NAnylyIh}- zwKJ9QVb`(EiRFlvl9D1Jh^~y@`cteYt#!XW9r;-2I9rL`lDp2vEamP`B7)ytvct_Cn7a@qk$rZ3H=y8pe}KBt?L zyxi7Nr*v32{W^f4mtx6hMBpI7$a*_72hzwX08As3lWYt;!>OZ`^hJl<+go+NM6tX$ zdy~YUcPgoo!hV+E<0#g)rJI%!-iC-H!7PqexW=qGJ()#}Pb-m&d{U9=fKhHI{4WtB zw51!c$XoUiN2$_hPRDT|I&B9F&{lS{GoR1I-+W}M?o!Q8-eVYz7R&$1XzHsEIrAu9 zZNfGBs_4uPZ+$>sfpUt9Qc^b&@s0vJ9I>+uteUN!UL&!is|(+fbXKIDFx|Y1v=gvu z@6=!JN;2X%dPNH^6ZB?YFgQ8t&$6&$gT=k`z@K@yz8Hy{V|BZHg-`yXtB@6OBKF znfH`eU(dKCG|TYzG{HcuPD8b z7nEJfL`EHwS4G_b@Fd9uU;T@(&aijIuH!(iEk}6~ZTGrXC`yhX1emEbZoB=f=!4|S z_M`KA_+fGrACQzFWOkxs#+vMZ*H;;QhR-1-e58Tz=G+~&pz1t?#A#>>=!C~O$F!=W z_C08tX`Y|j(Y1M{#34kmo-dsFe^&)WmT)5Yonyy|$@cvw-+7TvThgamDVpSCWk*!aA5!6 zg=wkm6`l8=JhscJIdyrKqcQ&+ipC1dcX&i8>Rgy3C`F!sAXk<`xhU+km!7w|q*_Vn zIm1cYkNRuj&Qbd9TK^hujdsOw(Q^2{^1iisZ68?8XU`KQy4#&YJiJAE)fY1b6gwmk z%V-;W?7QzxIzRet{I?UEX4}d04<1ZU6%vGjGNTf2C&|d!p^mUyfVZPBUYh^dY*E5+36Z?b&YBD!c;FZ)ga0kV#%Hnynd2Z?d1LzjsQ~XEa;C z(^aj8Qu+NBx!|pvYkiZA&)NyP5tw1G^^8UKIKI$FS}32Z2#JA*MU}BROS^uQ$(tK8 zr;s(P;VpkQMcq~{SJG$BZ>~lh0|47|wJ#4k1sXb>6_gjxfPBJ4F-(cL8WB@9JRuMe2ZPhd{pqu>JOgtkz?B4m`Iss zJmUS=Zmj$DRojxK*T<%3DzC-uIeiBXZ>+oeikC5xBlz(c9I%Rq@f2L_ck27`?G0=9 zl;XMgxA)GEsN`C(+}p#|kwVu~diuzoC0^+ck ze=zWRFHG2#Ed2^2c2q`-E?xT}pGlf`<86Uyn-r*dSem|7#vC))5O2;JLrxi7 z#N+qW+oSZBmBT~ooVOi7NaCAJEbcKJ6~mK>9fCI*IZg5A2Khk62>Y-P{Rs2pTcwyx zgB_Khkc>e%`zI|1)Ty1$G&}bmUTYV2+a7a3Lg9tK?niN^Dn8Uq1REUUpM`t%>m4@R zwvXOeY)+`N*;X+Bjz!lvebjsUD$9eTlF$-~-Ig;;$W`XbdkeO$^LT0*4z^n;We7AB zwq?dS)GLn`bFps}pD<9c^*=Z$~V2L7P&vCyt( z*dPxwyvia=P0cwp^Z5pei+SqwWu-lFk9T#gZ5|zq_G!~k$1uz(za_b#e({wczTpa| zWZl|NuT`2mF8o*j2oGjn0T)b(%UGOY zGw^kWP3GGn_hI#1+qm#cwQPFg!>!lL5WaroaFOds)xWa#?zVLVHvVe<3un)o_x=uo z=*hkxlu>B*E0Yi1p~^|_Fs70RaAP&y$aQ~#0c9go*Q<&k+4iBccf@UN7wt@}-i@qBDz-f+8jLf$eU0f4^bE*rn_NnK~CebaSQf)NTkhXWVX_76iT} z0bhgJ9ra~W(*I$;vt5Eth8%B?G2Py%bJrI}quJf6Z;D^&G5oKLv$3bdf;(F^z8(L*s=sAvDwv?G`# zdTEEcS-R42%tBgYM=uye2&)uhH;wUW!$dOo54U<`*{5XPME30R2t8hIh#J~$e07=c zS21?H^uKzQh2qHfiB$FOr|fg*2U>I=rSMhg7XCVVvm{6Wa`z(-9&kL? z>3>S*%ju9sJ}R-g&F4&(m-RE>OS6URiWA!x zw&E@kJ}_GYuNb97%`+%8ww{M%0}Fd;u!rq9fRfz`??Mw=uK$cC<;O#gU>e@u6=*k z*mml78atcc(;FNDT9mKZo01aAx$qtu3%Gg|dTcNyM4JePEJLe8x`ZFmZMl}g9wh5u z*ZdTbR%*cN^IiBU#-4(VeHG%Z4;fgV%ZG2eai`R>3xxN#RdQk~Iuz z_qiblVYhMZCKcN{(R#{0R-tz}BB7$|DLIg%dc6?Ni z0A}U>>qj*mJSoTjl)_B-L~wm^!M63&>%Ll{gF{1C!TMQ3(Lvl*PQM}W>dd)hb<(&) ze*&v(6M~XjCFW>7ALnuh>HcpWv9NMTFWR`R+v%Jmd-LE-QN;(zWgOGsM)ahO5e_ps*@YhgmpgtvD`D)ppBpo{=T{he5K!Q!07>U_V5GHIk za6WgIz3H@Xju0kc+$MVNQaakzV#z&wY-=&S>FrI~GFxpNo~HS?RE9J2|vBlw#$MBB0xNR}2&hLLqt@Ov*L?{>N$FFs?D=UzH}& z0i+a&&C5$dVRK6>E7sC0Qai04MMO-F%`JAVUC-t>zWJ%C;z-HO9k1NV?8i4v(GvSH zbxFQFMU}N`E|%FdRXA4uELO2)pG~dep9sh8CsonU)`)(z;QGRsM|eunm(;ojswLL@ zDnvN#+({qh*;HZJ9jJ8R|0(XP!>U^MbtfPaB4VIofCWehN+W60ASH-^v?8G(ohB+O zBHg&8Lj)uwU7~_WDJ9Y&B^?v#`wrZDKlki&?s@J%_uTb7E=8G>ImY*MTq{A|0(^O^dql%YwpVQCZz}hWzNzQ z<9uNBbjEuLAWp!csl{a2#-$DIPFnvV`g?MzH?La4Ky0^&Y!LTdu8)_xY`ZppTRFv< zD3oqc_uP?Mj@ts75+Q#|6SpKP=9H|gCg8%Vrs`e-RU(^BjI{`x9^!w+nUf9 z<1)V6QOnLSzr5{Q zO3qP&+cZZ3tYP5c^9Pj28-QB^y9H7mH$K`PicGo=4ht>V4i$3HoJNRKt9w!jI|bLL zp)_oF=Q`?-={gg9NeG`oX4ox1TwbFF%@}B(vO#F&rcveH34tRQ-<5cz*)%@NmKE>X z+T|2`ZG9s6x1{UP4+VvR_8mY;@mYL!nh&?Yi^WFc(PN0cL+29W&#D}AEf#f!c_p_H zQBP{>G{`*=%YF%3JK`%-2gnRhVZ@dBT_;rJNvfx(CRGWg zBgG~?-q3pSYEyw03wIf(vB*#2F%(0%T`~EHe zztfBqxP0gftE=_9;@^(}CmHK-4O0mIEfb65@WFBY9%eKcVc*mM&aI)}NRHb=H}aAMhVww1KLy4pxXrQX-{bzD z1G0qAX^@F%y~%&i`k1M1ko1hzq&X8_EH7iW^>O^8jUdct*c3FYD$_`P<&o)SNWUv? z-8#Q3e-~>F)O1IJ?8=VrDS=Z*zDKFG`r9q+3SvqEb9JXS5z4joZ|yChYd*BlsnzB` zJuq88)0jG&m7JL=N}gXl^(7cNfoIU+?vk7R>(BidLIs?ya8y3AZjgX>H^1;x5gA1SW0gwj+|s94z9AX!DV2~AH5 zKPmD~HqYNLHmLL6-Oe1}?5_&ofXo8HB9H{q`GE z^vHDY(qOn1ze!qdv^wG~kD11niKx#X7C(P>-(cEgFZLf2KyZcQB=vXjg#@l^&24us z@SKS4U3oxzrB%CAR_Iy+XZfVaKc3pGN>oo-O%zv-n`re$D#aANl@0GyDJaq&`I_tv z6YVH_LccgvzaXbatqx};T>65F zP~!O6aEKF97)wnVY&jaUX2+@A!_{?VZq{?*uoa1rS5P%>mS8S;@?p(mP*3kErWap-DO6vyfGI^@gtIhr6 zD&5kaofT857h32|&BW|5+QG=;wHH4#xvjTNyq$Ta)z)Zv;SEoEL-NaBD?CkIQQwbJ zLsg7eOlEX$`|TtG=#D$}`))p%L~nKV`!ip2!H>6Y07yt>MSYf(+kIAn>$&>&>_(aaP(sA-!+_s+aE`Lc~_IulQ z`dX<(o_>%p1|JT|(uxZ1kHAA!-UT`i6ztGWwlGy8K5> zOfqF(o)wLL)tojYuG$>G2D9Hk!#mo^uls&=tkCbgR?79^mwt!+Yre5~Q4}sM>tZi~v}@+OeV5j+_>Kd8}}Jrdcs$&s=kwOYhKv$K23c6Ukf1 zdBhD)l(tXy-8cFk)Ae(0bC1uxH}Tt?eSQBy-yNJiRV+R>!Vc9I5~Q|*td86x7qF zUok{rEZS|SK{m%Kug*LDp*BCm^`3K+-2)#^JH7~UQqDLplQY9TLPD6_2h1p37LZp& z_DjX3f7nn9&TLJTsPSk`)Kin@>49d!veq8!p>Jb~ssFeReQbm3m%g2@_0)&#LK2+X z&*Yr)8I0j)bI8!Qcg|wnXm$ERQz^~UczaK%N^q(~zO&3p&^w_DATEe*06f2dM!Y*( zl;X>&4<*Mc{CA5o$t-o{ccg-ORF`9w%<~Hw%;dOm9DgfW^UZ| z8grzi=)k*P$IH*|S}ze~%X8A}%Q|E>4MOP&fIF=(VQD5UHz& zh~=W1WcNjXe^=TcPMyH6FL+pLr3{L-X0Z!dr|?X?LL~D9-5O8!OLncao8Exv)o&j@ zb4@W4UBfo^S*6j|(v|RGPL(?LqtBiK>c4PwFIDb~!Vk8mXnOjTq!;@u-A}nRKhq*QNjJp_E4fP6cD@*eapFGK#8>IS{ z{zmakhw$@&S)EzT=_VZ)9gC|!8Gqf_ldIDjd!okCXD&s+@aI0D-ym~fKXuOeG2yxk zka`lHd=TKhTG{5xvp=q7BqJxcSj_$>9M67V{7KVl6F;d0(k#%oTtR_xu4hgs4V)OI zr&W7cKod~Lv%X<2)?i)ZX3eX`A1n*_13t$oaRg9m%F24HDE0mMY2|njPvFKeu;RWpr5;=N^6V3J?<^f&!mB<7;v3vTLYfpQ zz}yXkx!JNHiR12c2Vp%%orHQlX_r}LQgE!f&;1lODoV|p>nVP3Nm7nAzq5;!B9i2N z^R_jhjRFh@%jjLrgPh`R+VdK2fB>y0l+>EL)mFcRs3xCd({igecM!hgw!W2z zqMiN0*=OId8e*9`8!lOi_FBCgdb^y!@n78aQJPJ1opZtxB%l7Ed4TG##T=!iY7` z!wsTfDd%6_r))q!J$p8c*h58Jv-PuRHJDL0=ANdD@2}A^Tt}VUsZMEi_U}2dq}kXG z3SoLp3ALXmbk!+S)3a%AnIk~m(=$xFgZVH|Qb26(tp&;kz1V0fVJ1yIHK#k= zl$H5qkKE5P&&=c|=2h3$s!~)!kfwX-adcJUg%=sR*$I@I$boEK(nPZ_2K^}3OKvvM z0Ovb?X}Nx7C{;_ta^^~W$^6;m(9n@phJV~NpqMjA!Y)&NLW=+hTCSEqe)Onv%7O^4 zcTZh#j@LYW`YvUIzS)-)$L{Xo^J;ACXm}da!jNjjMywlNmlPv=Wc}=}f>yoqKySTm zf#O=1g&E2WbZc#>rFeo|TLZ-P=H5nh@&x55xV!2PjG5|b>x~Qkh(3QgWXwI!ap<|a zp+`J{oY6{pMiyYCS5G-b6W`%{3h4N%i#w#arn5h_OlnW*(>5z+9BbR3doex5_4;c| zddn-=;{3&N4JLlcY`ohYm^9mtj2%=j-Ev}?#&SnePOLQ5EJxA~*^ShVRGNZn!YA(C znfelUfQ^YMmXeaPp%}ZaoO$Lk_sY=T@!sB-q@TUL_FqhbD)Uc*`^ZcU4fT|``G~d} zahB0k$n;Wb0*3c3G9b$1#tjAQ@39TPCl%%w#xyQF{GPtZOW-6+BoVkT_U7Y|Qa@2j z1&&k9GTR&q*CAF7=qgbs0YGLkZx58nmLBtDo{=G~jt#emFBawRV&0X)ap&UtVSe8; z`d^IS<=j3_W69=PwRk{H@Wc};VOq@{YRVdvHu7q>hSpuwGizgEo=Mwd&)M6P^<$Jz z;>dE#+p85x0x|8{9v9k#Ov$=61y|+Lq3cConRN1Av{tbz7xVUD@^j&^16t!91-m<6 zoGN(#s`gaS5j|}#b~8`yzSBiP?mONKn#R9q-LALc?%l%<YDWTognTDe4{ZnM~wjzc6I(S)(*kFA$yH-J*-)Km_4+O!P4mk;5h_&0EGGkHzgFN zAY@TP4cP=O72SnP8R2GX53J;h85a-}_nP0}XJJ+O7ADxRaf3-UunqNvqTSj&@wdLo za>X9KF5xR`a4w_8mHI*Tr(fJmG>6(|9?>k_xg$^F`}E09SUjvtDcDEe*1SccB^c)w z(9E=O&%IdbYBXg1cdfy=aKFTl}o%851o1D`R>li+3uZDvba%tlMc=>E037zp8CGL8^bo8 zsDdI&2cxq6m|swwQR1NIIh3Nl zz)*Q1<-<4)BNK|B?HhLd_1DasL22pVzZZ@lX)@2PWzO7=hQV%;cLA!NbVAhc_tbEy z$!2_-nr%LK-cpLAUglDXd&{*-ci+OTUDXhDp_c`YK~OS5Gg+OnoZdgggRnRuT&~Gx z4Xn;x6r@0->x|!j{Q?4RTKDT;WO_;H3MX=D6^U_@M_y=Ehwt?F-$qsW^UJxqoNL=C z=t2?k8h;nc`f!cKkmCuaNHhInqv2;M^Aowl*v+{=hxHYTz*t;eZq@~p@80=&>*`@ z0u#n5qZ;bDGPbS3&)&spmP-d^Mdgce#HTq3aImX|2Ry$#thqS8__$GF%jMjQMn8lW z$(i!GS8KCilx+%~j(gO|mX_&o+;? zo=cRrXyWx5mG{O|H?J+3EI-C@HbHveiRefx#o4$HXqhp3HLohFG+l*_oVfiS^2Q7F z)H@a6cpmEgL)O|$?CRqK^7Gf9x+S&P9G6+rcy9QVxwlVVu#Rm|b+a&qzwj2@dlju# zK~oL8M64<>Hd~W<0I}Kx!exN+sa{Z047{AtqQuM8*ITxf7)^P(LC-e+>TgCy`u8_y zxx445GFJy;_8$*tO}|L!yBJAJYw~G7&KIi7lA)Jya6MfClb zOGuYf_pMHrKe3jVXWC}!wcGDi6(>9U4e_5V2R3`u-XM9%JdX^mFyxxsrrmX6ruddH z*TYe+fkQ9Hw>BmGn6pl?FL~_|+*qj|@bFOljQIV(mjXk>ta7GGKA*O$bYr-ar(SYF zo5wb>gQiMpK8#yDtXX4BcZ!B)y6@TkOXkNP?u>l7{^vy%v-(N9CFj3*L--f7?bzSx zZTorcz|imqhFX`Yb<2zdu4^F1E1K~A0Yo<5t%~Rw19x}@o}FkTD9(k#Oa{=Rk#Q4a zV;CC*UU)M>+szx6ey0T9X+DXg>^sK&Xx~0bFVE*0m-f@z%ufxReWtuz%qCWxqu*!KOnZ6vlkcYmJ!8esu>38WAaz)( zO{sE8N$g;ULK^dd@z2A@2X#l&gTFd1ig~9;!g%9AD>fCa*YAYMIqt6_R00S+<-FLENJ>E+sSh@a|c3@xW zSMaEGlWX;;A$J^QIC=8qQJypIH{YC!mvZ>o{(MSddAWMrHm7#G)sw)G?9x&fy@Hm( zKI^yLORZaJtYSHKC(%{WJP(LkpgPTQsEOWx?mKr^-jDu?8L2a%7D4*l5bZVXCa+i{ z?-;h<^OG?~&q%++l$Ms74auhjQhq0jp62B#`%IXnK|YlP75)$JGQW9FU+~IiDYJC= z-SN1D=IDun&c$%0q&`!h@p=iqSrN7}@4y#S!GYt}b9;gVE3+sM zQsxgRbK_YrL1)gnX>ylLVq~v!ZCw0qPZ~@qH@yQ2M^KVOL}dHE`rvR78VR1=qMKf7 z3De0hX;X}KN#_ritSnsHC2Scuw)i-=9c~*;i@&ERsp+qC#c%OC9s(_Wf(LxvxhJ$9 zQg$9AVSaw9;m2(A7Igv9m$n)0pUW$(H5KrWOmU1_@Nk@KiH`MsEWY;qPnsSoR)Eya zYGjT7(1o`8R&CH=;Fhb5of{Q(i3VucY%kl?IvzH*SN(PF{9&W^A9v0mtpzNT;u%Nu zkKFi`j!)!u?YsMC4VawbkO+_>C0zu{Osp2I$`i_KYyH5i^9f-(@C!}Dhw6`lT;H7! zpBu7P)DKe~=ef+*9Q?JpjeFldZ-C3t&KzZSzya=dMxE=PD%gA=sQZkzNOc&09sQ8I z&$bm!!&%;_fS_6h1%>xkO;lT~{$5p?(}gI&M(@#tZpXPZXU_brGZdJ+ezqt`hv)9~ z`)B~M#0Q?DmM*qNi|!=XN3>k`)Z}UP*m?@srdA}rs=JV^{;~U-d3irGp}C0SeYdLh zq)72HK>Nfq0@QfpJ7iOWk%6Dcs=9!^hr}z=-ngcl+yDmV_V_>#yYPt4Q%&5HxqSA? zg*EM;b7yY5ou!Z|9%Z2>KbAg4>lEP3bGPEix74ar3(O0I-Cm%bvqJRO%x5?5l>lqP zXgEF-`Iw9WpP0MDC+XI{*f8ZS;lIR8(sPT_aK+$_5qun5L<_PoJqW#7OJN;vRAQJ8 zI0ZP=+2$}VJejS+ksdg&+dHy$Ofz*H$<}e3_Cx|>wtY_GjonX$f}2Ap)zp|EzWlQI zCjjWaLTDA>t2>~$3)uHp@p>hsRRdn=;`6_~jf>A(e(7}k{L8bGn8OlI3os?3WneHx z>H{6auUMP}tRF^@!7{ZNqJzNgbhdIS&EA~I@}Q<&2FO`*ti)%y(mD@BIejm5;)lw3 zAm}u>vdIPwRk)f*q}KxQDhF$xD1QMNw@7AN_D)!0jAQ<4$UVqf@vrwK6kY!LrePlm zdNxKB`|nPW)K3s@0&8DMdTZ7Zo;GV=ZQ93t^B>>D^qxwN`hVgjI&D!T|6p}`Z?+7<~N+G(TBvuzPEp#bbfBw!s!w$h2P*Qr;Pk()U5P6 ztT$T@t~USkUk=U(Nv$pCv0F+UQo^!wau1fu%uc}zr0;@=oTph#pDr1{`K6ESc(p&1 zOs-gUAj?Oa`So2``)kn;7i(3CY0%oO^r!i^`#2{H9*t7C5FK`|6X!=B;+^oyf4#w5 z$et1J^Ut4xe{L9Qity0czvaY11e%&w0|85qi9(JfMuOJDqX+S3C@RX2wl9%7GT zl`6t+Cm+UzgrHJXsNLKR^Jx8I*B}f^)iVsFV4nxj`3s#)&Shaju_l?|k?%;;$1SwdGp2 zYL7KpF0DFTa`UyI3hcuFwi8ptU-J8;F7I~gB;e>$F(XXD`*O)LAGxTx7ov?mo?Dx* zYkRVDaB#~~aD(!Lv7yrJSi7FEO`A?wwK;VXuxClY%MFb12bM*8c*Hw`>c-5<`ih}U z&D-+pU-!0ZjIq|#WFTQ_e0gpYbrL){8bJ+hgb#6Jny#Q$oZM#Ed%oDSpnR!3%*7Gt zUM*-pF0Ps(bZON_OKD+bKeCzu*^jEfJ2@;`riS~Qc`}OZT4Tgv^GtmhJv(T`#DabO z0DqT@%M1gZ0uJS2z7h&nB{hoI*kwcwWjNN}3x*s=2VU_p!;Zmg% z1`!nC|MbG{#%amrRdB$(1J>Jp29}e6d)R#p<*Nu=3Kz6sBmqkVCA@N%hqFxL{uZ!s z(>>0|%gg(Vt~wrLs~SLoyl19Mk*5n;Hqhp8-aU2T&oU!EamL^Vau9DTC`VOO+x@)?T1)q!F(47?#2GViH8RRD&u*}8Kgr$0gG=Xi*Iuva)SsrF z8}}X$#Heus@H+Fi&*pB{)bOV^!g6#sg)`Kl#N5cdgbN=`K+O=!2!ka9nOhfBE}7se zsk%TI>;>hMaj|5XtcNb(X)s|%m}84Zklb{m-yl22I~{e#cr8*8dxUe6xZMRIiJva| ze(_XiwVxS0PlASYRQECB-iV0^;sIrQ#eB6eADEVWLM#Ubt_0N>Mp^J#kxCZ6thM)v ztjvH9JXlVK0?@d2^EI-;ia2i9_h9+{Wn_hfr*)Pzk`;Ya3xaYCO zHv61$x|@q-S`g=;cpq45FyLHo?yQ7$K!Ss;?n+UWw4lVFyB$Up7(`l6OHXeM2AtE3 z?%4CK^A6+@U05)yV97v=zA}pQ+Z{29(lsG$PY_)j()B{H?hqfDA-bf93xa`X9B5}` zPPq;a&R3aGoYID%KDKK$5_Hhu_d->hAUJM=i8Ho<`d_CXN~pT?byW&{jrjN2ba8H~ zL5GL=rhg(~uu7;>gFLXES4S4hxjpKT-!Zt4U*Ta`0^j3+g2wje$Wm6d z5~Sm6ghV&Okem849vRt#7{lRcZc%)*TU?^FGWdwE(ayv15F5W#Q2sqPM%GqYy-?3`7HVG-hMjiph zg*+~3c`sL4$9_63+vPkq{7Ac@y-CZ>L z3MsDUn|>`N9ul(m6|4K$LH(D6!-VX@yw}q28-x^A*IBCy-~S>$SfyzlC{H7_%ZY4e zw4=DSWEsan3VJQ{YJ~8Em<6_f3Ucfeoc~v)$I*`9h8jD)8b}{`wyFvW>VA?_4=6QJ zRcb+(t!7zWyrN}32b$qOfBi~D)`Q;Fm&uvJaD>Q%xE{^5EU5n57$bHE?02+4_@Yii zbHCQ86HT7AJn15)i#1Cp`MO3VQ{iJy34?pB27r_IlHu!K5p98kx6;3}yb)(+25GjE zC&Sda3WRD+JgA0)ev;44I}t2{{-guQHf#Q4I`GB+joa3L-!*O)b|z^-^sbC+nFpaC zV%eITPISPD$)LAoLy|u{1;fiGb14gO&JB5Xpc)HovC{S?)ESU%gm6Q4pHdUry^Tn4 zE=}nD++*>x8M`PD3ImjyVCHK9)GO!?MbLr?(VKi0hK56*l|?t=E+AwW%rC;+0x7Su z!+3{&<12kV7~=33P2Aq#nHu@CN^%i6a7`%GQxam)5Y7@vFD4NW5gtr0 zz^KuL2c9O*kw%1LyNSH(M=bhVTCyz~FmnJD^2ITa_G?$A3uEPTjKwD=HIt~byMFxook1K zEN`9oXSH-P|DMto!ewl&R{Kwd)IUnN{dZ}e(HH!)pzEfH`)_Km|GSqA96;HG`pG8* zVFP&sag-7=)O^adNR$cO&Ca9mj26K*1rZEalw;H*UMPWx`{Ps%pH^gwhLFk=C zLd?X<`t+mmXaCZ4Xw0k!K9bF2#=ndlx!|?sK;APQiLT&sbDs|v=bq{O+O_4{$I;jM z5)|*}k@TO&%!~vz4)7Q}O>k}!=2VH%u8)B3d@FvB@^$C4!|`pATfjy53vrb=I0)Kw`yeguNWK)DBmqWP306!C%37I62XI zaWoI|yCQ%-yg)%=qi6{3j@b1s62gtW-mb9^NbI2e8+ZEQmN6BDSN4cwun#k)RrkTDkA$~}HG#~|#m(c5o zN+ud#_Ts;>xW40nnvnJ#IB?QtC*=Lk7^mC+>&?_p)EGfj zHXKwoZ{KdtrrMl(f!}eg4Mr>+?Cg^enjubSyo68(M&gDIyHYU`S@r2-yUlT%W`b&o z<{9nAjqHaHKL7w^yMTXV`thKYV80f-1UaO02=<|?@e)sq? z_Kw=Hez~&P1|4l_DBmoQM-_46!T5y(eLv*;$H315hBV4%0cpk~qn(7|C(zY|PlKBQ z3XLFm?n>3aI!6H7k)FgNL}2=L9xZXyKeci40r}38#^JrI5GRXvpOS})kRN7bNHirU z-ch+P3?+v3n^+>{Ej!8(E_9Xh9g)}AM8~bPV+5^$zbCGmBjB@jqKMD@UcA{Hck3?G@$U;(6iCu(`5MoRYX!&wI*5Y}wq_5?x+n?ck>3>y5A z!vo)n>tdX#tF|I7)#Ygh0S9cK=|o6B%(*I`7Np}=c&MrWmQL#FSLYLySg4Z_kwB(L zk7F%ppA7ea@aqK}!YN2u2)iQ!@{xk~U(@69K=b2j4FnX3%2(ZcYIasDT`vz!<5_hC z04-EkI8u!0)j;}j60-$aj9v`^{D#8u?R=L)$7VaH$Y1f$~k5=TJT5IJd{SLmgF@FQQjy7lmH5Z1_7Il0TJ=h=T8EL zBNc!7-K!J1b$kRT`bV!532B)WK(|u#3msEXiuoI%V&!(6P@};_Xpd*f=&s?mx7o;G9&y8dgE-MG+Y55&X=&BG7fM$b zWgI{$z$tM|3+t~j+d`G-!nj4NM2H+wEKOBcKD0dR;u*^xp^Gl@DWbr6bR#JMr0581yNT=lm2Vsa1P$!{U zNffWuen2q{{D%GaJjlCYr4kB0pcUdXxc)lgX0#02K-_$rq1Un&te4)etbjgq0sYWQ zKzTf%zQS0}D&eTq2zMunVS*1g;Z&q+-u$V$w3XmEn|ryt7cGwy6_!uwN9cg1qBcn- z%|g&gJkp}@KB+O&by9oi%5d_7#KV|aDc zae7j_Ij+LvP!n0z-r($^CWSO*Wn=3fMbC8H(8SywcRSiQ&Z!=3#(31ViO4e3NWwRI zK3I9#K$(79EWDh1Ul&7>f?f^zg0j8JrAv{;!V(gq8}kC5wsQ5sH6e}UP%AYTSLg+S z^Ol0Eq0%0%1)}@H-fHc4EtDwoX7lf{pM=Nr7r(%dwZsorC+iaQJkdbFaXL4V~CFE3-^gsoi-A{;k4N;?q7PL{h}AB{^nZqQ!Ii+Hzgco^W;|Bc_@eu zf#6V9f{>!&U@wC`8$xXyY2-5i_37s$%qA=qNbhU0my@-4R z3ETzZ0B45hrPImBgsl+a+9~~3!4|6p3#5&0OLnVx3}DnN)3H=A9-m3JID;5{k0ZOqSz#l`yMyWt_ zu8B(-NZY4Z`fWt+p z2WB8h3QsYeF+aXLS=NxU-v&zV(6f;|&>B(o@TULRfl-TUF zl}(UEO;tUTJwl%py|9N*pXPRMdt=#1Unfd~(%%>*9x+lPOlS_Ac|@e$ix&BdoY+m+^3@x*9JSnn45MYK;sI#&GN0?dfPg7MEHfEwYfORwQwv2S&B(||Me$42YyFU1?cN#a>o;`kI6>h?=<2t-h!@#-eDL%1 zZm~+h(fsa7(;~L#Gt6kP-L8Cn-)-B=<>f={F3d+(Ru*l25*b-Cb0S488y=vplSmA3 zQjC7?_r6d*Otf=^C$v)A zU=sqS7KT8g0THhqiF`-&yqD42{SB$lcYCvpU$DTz4nO4(!JVetC1~ z+v#)XrjgFH=CyLTh01tIuR#O-{zz5g-x3#H(AQYsg^TY)2PfAy?_xS3JK7Tdp;zVG z4GApZ(`dF6=V16}2Y9>^61^emwR!+O!f)-yUT zMosZ&m^7h1@811E_K)QW5BZD7Ps$OJ8D$|8j zggP^{1>2IZRx5$I57BZV28R%-Ft6^7^&ATYy%>!<-IerY^7tVk1Hg35R1saicQ^ zHE_0{8qMoee|1T`z-e3swKl>Y15WYY%yL%6xoKhmf%+11i44TZ8m*mpbhZdr^-a>h z-$_%P(?36=0<7!>(Ee{bRZ_xM&F(s!_CRFjbErcsRiAgZ5s7{zWx@fHSmS zLWQqs=Sp9D=S}gLO@?UaWTS{FfwGS$6ycFed;~NQl-`(Pt*`w!UAx09qW8ma2e1BN z(NY9fTWko|JR-+cJV#7}jBsNkWxS%n9Q7x<#T`#7bQ#Jn!}Wz|Ab?pB2Asq+&Vx+* z0TqA^oDs~wJ+A%r^(75q%?v}ZZHPaF=6FWQ!Xso9ggY$ybDvT(N`@P5b|_%AwE(k& zys_L$gfQABdM3z~!X%vdaCe?~OtGmF$36-D&9~@;?bm~t)>{*ynWa{ATD%eVg*80*;* From decc28110acf58053a0d6911f5ea7a4d9d7f76d1 Mon Sep 17 00:00:00 2001 From: Pavel Tomin Date: Thu, 11 Jan 2024 13:51:14 -0600 Subject: [PATCH 24/40] Some random flow solver cleanup (#2921) --- .../fluidFlow/CompositionalMultiphaseBase.cpp | 114 +++++++++--------- .../fluidFlow/CompositionalMultiphaseBase.hpp | 4 +- .../fluidFlow/FlowSolverBase.cpp | 23 +--- ...rmalCompositionalMultiphaseBaseKernels.hpp | 16 +-- .../wells/CompositionalMultiphaseWell.cpp | 6 +- .../wells/CompositionalMultiphaseWell.hpp | 4 +- .../fluidFlowTests/testCompFlowUtils.hpp | 4 +- 7 files changed, 74 insertions(+), 97 deletions(-) diff --git a/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseBase.cpp b/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseBase.cpp index c2edc85b3c8..b841c70edb1 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseBase.cpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseBase.cpp @@ -256,67 +256,61 @@ void CompositionalMultiphaseBase::registerDataOnMesh( Group & meshBodies ) [&]( localIndex const, ElementSubRegionBase & subRegion ) { + if( m_hasCapPressure ) { + subRegion.registerWrapper< string >( viewKeyStruct::capPressureNamesString() ). + setPlotLevel( PlotLevel::NOPLOT ). + setRestartFlags( RestartFlags::NO_WRITE ). + setSizedFromParent( 0 ). + setDescription( "Name of the capillary pressure constitutive model to use" ). + reference(); + + string & capPresName = subRegion.getReference< string >( viewKeyStruct::capPressureNamesString() ); + capPresName = getConstitutiveName< CapillaryPressureBase >( subRegion ); + GEOS_THROW_IF( capPresName.empty(), + GEOS_FMT( "{}: Capillary pressure model not found on subregion {}", + getDataContext(), subRegion.getDataContext() ), + InputError ); + } - if( m_hasCapPressure ) - { - - subRegion.registerWrapper< string >( viewKeyStruct::capPressureNamesString() ). - setPlotLevel( PlotLevel::NOPLOT ). - setRestartFlags( RestartFlags::NO_WRITE ). - setSizedFromParent( 0 ). - setDescription( "Name of the capillary pressure constitutive model to use" ). - reference(); - - string & capPresName = subRegion.getReference< string >( viewKeyStruct::capPressureNamesString() ); - capPresName = getConstitutiveName< CapillaryPressureBase >( subRegion ); - GEOS_THROW_IF( capPresName.empty(), - GEOS_FMT( "{}: Capillary pressure model not found on subregion {}", - getDataContext(), subRegion.getDataContext() ), - InputError ); - } - if( m_hasDiffusion ) - { - subRegion.registerWrapper< string >( viewKeyStruct::diffusionNamesString() ). - setPlotLevel( PlotLevel::NOPLOT ). - setRestartFlags( RestartFlags::NO_WRITE ). - setSizedFromParent( 0 ). - setDescription( "Name of the diffusion constitutive model to use" ); - - string & diffusionName = subRegion.getReference< string >( viewKeyStruct::diffusionNamesString() ); - diffusionName = getConstitutiveName< DiffusionBase >( subRegion ); - GEOS_THROW_IF( diffusionName.empty(), - GEOS_FMT( "Diffusion model not found on subregion {}", subRegion.getName() ), - InputError ); - } - if( m_hasDispersion ) - { - subRegion.registerWrapper< string >( viewKeyStruct::dispersionNamesString() ). - setPlotLevel( PlotLevel::NOPLOT ). - setRestartFlags( RestartFlags::NO_WRITE ). - setSizedFromParent( 0 ). - setDescription( "Name of the dispersion constitutive model to use" ); - - string & dispersionName = subRegion.getReference< string >( viewKeyStruct::dispersionNamesString() ); - dispersionName = getConstitutiveName< DispersionBase >( subRegion ); - GEOS_THROW_IF( dispersionName.empty(), - GEOS_FMT( "Dispersion model not found on subregion {}", subRegion.getName() ), - InputError ); - } - - - if( m_targetFlowCFL > 0 ) - { - - subRegion.registerField< fields::flow::phaseOutflux >( getName() ). - reference().resizeDimension< 1 >( m_numPhases ); + if( m_hasDiffusion ) + { + subRegion.registerWrapper< string >( viewKeyStruct::diffusionNamesString() ). + setPlotLevel( PlotLevel::NOPLOT ). + setRestartFlags( RestartFlags::NO_WRITE ). + setSizedFromParent( 0 ). + setDescription( "Name of the diffusion constitutive model to use" ); + + string & diffusionName = subRegion.getReference< string >( viewKeyStruct::diffusionNamesString() ); + diffusionName = getConstitutiveName< DiffusionBase >( subRegion ); + GEOS_THROW_IF( diffusionName.empty(), + GEOS_FMT( "Diffusion model not found on subregion {}", subRegion.getName() ), + InputError ); + } - subRegion.registerField< fields::flow::componentOutflux >( getName() ). - reference().resizeDimension< 1 >( m_numComponents ); - subRegion.registerField< fields::flow::phaseCFLNumber >( getName() ); - subRegion.registerField< fields::flow::componentCFLNumber >( getName() ); - } + if( m_hasDispersion ) + { + subRegion.registerWrapper< string >( viewKeyStruct::dispersionNamesString() ). + setPlotLevel( PlotLevel::NOPLOT ). + setRestartFlags( RestartFlags::NO_WRITE ). + setSizedFromParent( 0 ). + setDescription( "Name of the dispersion constitutive model to use" ); + + string & dispersionName = subRegion.getReference< string >( viewKeyStruct::dispersionNamesString() ); + dispersionName = getConstitutiveName< DispersionBase >( subRegion ); + GEOS_THROW_IF( dispersionName.empty(), + GEOS_FMT( "Dispersion model not found on subregion {}", subRegion.getName() ), + InputError ); + } + if( m_targetFlowCFL > 0 ) + { + subRegion.registerField< fields::flow::phaseOutflux >( getName() ). + reference().resizeDimension< 1 >( m_numPhases ); + subRegion.registerField< fields::flow::componentOutflux >( getName() ). + reference().resizeDimension< 1 >( m_numComponents ); + subRegion.registerField< fields::flow::phaseCFLNumber >( getName() ); + subRegion.registerField< fields::flow::componentCFLNumber >( getName() ); } string const & fluidName = subRegion.getReference< string >( viewKeyStruct::fluidNamesString() ); @@ -600,12 +594,12 @@ void CompositionalMultiphaseBase::validateConstitutiveModels( DomainPartition co } ); } -void CompositionalMultiphaseBase::updateComponentFraction( ObjectManagerBase & dataGroup ) const +void CompositionalMultiphaseBase::updateGlobalComponentFraction( ObjectManagerBase & dataGroup ) const { GEOS_MARK_FUNCTION; isothermalCompositionalMultiphaseBaseKernels:: - ComponentFractionKernelFactory:: + GlobalComponentFractionKernelFactory:: createAndLaunch< parallelDevicePolicy<> >( m_numComponents, dataGroup ); @@ -735,7 +729,7 @@ real64 CompositionalMultiphaseBase::updateFluidState( ObjectManagerBase & subReg { GEOS_MARK_FUNCTION; - updateComponentFraction( subRegion ); + updateGlobalComponentFraction( subRegion ); updateFluidModel( subRegion ); real64 const maxDeltaPhaseVolFrac = updatePhaseVolumeFraction( subRegion ); updateRelPermModel( subRegion ); diff --git a/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseBase.hpp b/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseBase.hpp index 18a4b7e4481..e69cb487fff 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseBase.hpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseBase.hpp @@ -108,10 +108,10 @@ class CompositionalMultiphaseBase : public FlowSolverBase DomainPartition & domain ) override; /** - * @brief Recompute component fractions from primary variables (component densities) + * @brief Recompute global component fractions from primary variables (component densities) * @param dataGroup the group storing the required fields */ - void updateComponentFraction( ObjectManagerBase & dataGroup ) const; + void updateGlobalComponentFraction( ObjectManagerBase & dataGroup ) const; /** * @brief Recompute phase volume fractions (saturations) from constitutive and primary variables diff --git a/src/coreComponents/physicsSolvers/fluidFlow/FlowSolverBase.cpp b/src/coreComponents/physicsSolvers/fluidFlow/FlowSolverBase.cpp index 02bed4b1b0d..62e21cd33f3 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/FlowSolverBase.cpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/FlowSolverBase.cpp @@ -212,14 +212,7 @@ void FlowSolverBase::saveConvergedState( ElementSubRegionBase & subRegion ) cons arrayView1d< real64 > const temp_n = subRegion.template getField< fields::flow::temperature_n >(); temp_n.setValues< parallelDevicePolicy<> >( temp ); - GEOS_THROW_IF( subRegion.hasField< fields::flow::pressure_k >() != - subRegion.hasField< fields::flow::temperature_k >(), - GEOS_FMT( "`{}` and `{}` must be either both existing or both non-existing on subregion {}", - fields::flow::pressure_k::key(), fields::flow::temperature_k::key(), subRegion.getName() ), - std::runtime_error ); - - if( subRegion.hasField< fields::flow::pressure_k >() && - subRegion.hasField< fields::flow::temperature_k >() ) + if( m_isFixedStressPoromechanicsUpdate ) { arrayView1d< real64 > const pres_k = subRegion.template getField< fields::flow::pressure_k >(); arrayView1d< real64 > const temp_k = subRegion.template getField< fields::flow::temperature_k >(); @@ -230,11 +223,8 @@ void FlowSolverBase::saveConvergedState( ElementSubRegionBase & subRegion ) cons void FlowSolverBase::saveIterationState( ElementSubRegionBase & subRegion ) const { - if( !( subRegion.hasField< fields::flow::pressure_k >() && - subRegion.hasField< fields::flow::temperature_k >() ) ) - { + if( !m_isFixedStressPoromechanicsUpdate ) return; - } arrayView1d< real64 const > const pres = subRegion.template getField< fields::flow::pressure >(); arrayView1d< real64 const > const temp = subRegion.template getField< fields::flow::temperature >(); @@ -472,18 +462,11 @@ void FlowSolverBase::updatePorosityAndPermeability( CellElementSubRegion & subRe string const & solidName = subRegion.getReference< string >( viewKeyStruct::solidNamesString() ); CoupledSolidBase & porousSolid = subRegion.template getConstitutiveModel< CoupledSolidBase >( solidName ); - GEOS_THROW_IF( subRegion.hasField< fields::flow::pressure_k >() != - subRegion.hasField< fields::flow::temperature_k >(), - GEOS_FMT( "`{}` and `{}` must be either both existing or both non-existing on subregion {}", - fields::flow::pressure_k::key(), fields::flow::temperature_k::key(), subRegion.getName() ), - std::runtime_error ); - constitutive::ConstitutivePassThru< CoupledSolidBase >::execute( porousSolid, [=, &subRegion] ( auto & castedPorousSolid ) { typename TYPEOFREF( castedPorousSolid ) ::KernelWrapper porousWrapper = castedPorousSolid.createKernelUpdates(); - if( subRegion.hasField< fields::flow::pressure_k >() && // for sequential simulations - subRegion.hasField< fields::flow::temperature_k >() ) + if( m_isFixedStressPoromechanicsUpdate ) // for sequential simulations { arrayView1d< real64 const > const & pressure_k = subRegion.getField< fields::flow::pressure_k >(); arrayView1d< real64 const > const & temperature_k = subRegion.getField< fields::flow::temperature_k >(); diff --git a/src/coreComponents/physicsSolvers/fluidFlow/IsothermalCompositionalMultiphaseBaseKernels.hpp b/src/coreComponents/physicsSolvers/fluidFlow/IsothermalCompositionalMultiphaseBaseKernels.hpp index 2afbf350956..878878f3c78 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/IsothermalCompositionalMultiphaseBaseKernels.hpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/IsothermalCompositionalMultiphaseBaseKernels.hpp @@ -139,15 +139,15 @@ void kernelLaunchSelectorCompSwitch( T value, LAMBDA && lambda ) } // namespace internal -/******************************** ComponentFractionKernel ********************************/ +/******************************** GlobalComponentFractionKernel ********************************/ /** - * @class ComponentFractionKernel + * @class GlobalComponentFractionKernel * @tparam NUM_COMP number of fluid components * @brief Define the interface for the update kernel in charge of computing the phase volume fractions */ template< integer NUM_COMP > -class ComponentFractionKernel : public PropertyKernelBase< NUM_COMP > +class GlobalComponentFractionKernel : public PropertyKernelBase< NUM_COMP > { public: @@ -159,7 +159,7 @@ class ComponentFractionKernel : public PropertyKernelBase< NUM_COMP > * @param[in] subRegion the element subregion * @param[in] fluid the fluid model */ - ComponentFractionKernel( ObjectManagerBase & subRegion ) + GlobalComponentFractionKernel( ObjectManagerBase & subRegion ) : Base(), m_compDens( subRegion.getField< fields::flow::globalCompDensity >() ), m_compFrac( subRegion.getField< fields::flow::globalCompFraction >() ), @@ -219,9 +219,9 @@ class ComponentFractionKernel : public PropertyKernelBase< NUM_COMP > }; /** - * @class ComponentFractionKernelFactory + * @class GlobalComponentFractionKernelFactory */ -class ComponentFractionKernelFactory +class GlobalComponentFractionKernelFactory { public: @@ -240,8 +240,8 @@ class ComponentFractionKernelFactory internal::kernelLaunchSelectorCompSwitch( numComp, [&] ( auto NC ) { integer constexpr NUM_COMP = NC(); - ComponentFractionKernel< NUM_COMP > kernel( subRegion ); - ComponentFractionKernel< NUM_COMP >::template launch< POLICY >( subRegion.size(), kernel ); + GlobalComponentFractionKernel< NUM_COMP > kernel( subRegion ); + GlobalComponentFractionKernel< NUM_COMP >::template launch< POLICY >( subRegion.size(), kernel ); } ); } diff --git a/src/coreComponents/physicsSolvers/fluidFlow/wells/CompositionalMultiphaseWell.cpp b/src/coreComponents/physicsSolvers/fluidFlow/wells/CompositionalMultiphaseWell.cpp index ca537b2f6a6..46dc3c80861 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/wells/CompositionalMultiphaseWell.cpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/wells/CompositionalMultiphaseWell.cpp @@ -543,12 +543,12 @@ void CompositionalMultiphaseWell::initializePostInitialConditionsPreSubGroups() } ); } -void CompositionalMultiphaseWell::updateComponentFraction( WellElementSubRegion & subRegion ) const +void CompositionalMultiphaseWell::updateGlobalComponentFraction( WellElementSubRegion & subRegion ) const { GEOS_MARK_FUNCTION; isothermalCompositionalMultiphaseBaseKernels:: - ComponentFractionKernelFactory:: + GlobalComponentFractionKernelFactory:: createAndLaunch< parallelDevicePolicy<> >( m_numComponents, subRegion ); @@ -886,7 +886,7 @@ void CompositionalMultiphaseWell::updateTotalMassDensity( WellElementSubRegion & void CompositionalMultiphaseWell::updateSubRegionState( WellElementSubRegion & subRegion ) { // update properties - updateComponentFraction( subRegion ); + updateGlobalComponentFraction( subRegion ); // update volumetric rates for the well constraints // note: this must be called before updateFluidModel diff --git a/src/coreComponents/physicsSolvers/fluidFlow/wells/CompositionalMultiphaseWell.hpp b/src/coreComponents/physicsSolvers/fluidFlow/wells/CompositionalMultiphaseWell.hpp index da7f355c931..e0b95e4799b 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/wells/CompositionalMultiphaseWell.hpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/wells/CompositionalMultiphaseWell.hpp @@ -133,10 +133,10 @@ class CompositionalMultiphaseWell : public WellSolverBase /**@}*/ /** - * @brief Recompute component fractions from primary variables (component densities) + * @brief Recompute global component fractions from primary variables (component densities) * @param subRegion the well subregion containing all the primary and dependent fields */ - void updateComponentFraction( WellElementSubRegion & subRegion ) const; + void updateGlobalComponentFraction( WellElementSubRegion & subRegion ) const; /** * @brief Recompute the volumetric rates that are used in the well constraints diff --git a/src/coreComponents/unitTests/fluidFlowTests/testCompFlowUtils.hpp b/src/coreComponents/unitTests/fluidFlowTests/testCompFlowUtils.hpp index eebfa0a114c..5b336a0481a 100644 --- a/src/coreComponents/unitTests/fluidFlowTests/testCompFlowUtils.hpp +++ b/src/coreComponents/unitTests/fluidFlowTests/testCompFlowUtils.hpp @@ -273,8 +273,8 @@ void testCompositionNumericalDerivatives( CompositionalMultiphaseFVM & solver, compDens[ei][jc] += dRho; } ); - // recompute component fractions - solver.updateComponentFraction( subRegion ); + // recompute global component fractions + solver.updateGlobalComponentFraction( subRegion ); // check values in each cell forAll< serialPolicy >( subRegion.size(), [=] ( localIndex const ei ) From 9bb5962031d291cc7203f8e8ea001c0b4115ffeb Mon Sep 17 00:00:00 2001 From: Stefano Frambati Date: Fri, 12 Jan 2024 21:32:38 +0100 Subject: [PATCH 25/40] Correct errors in wave propagation xml files (#2911) * fixed issue #2836 * fixed issue #2835 --- .../wavePropagation/acous3D_Q5_abc_smoke.xml | 2 +- .../wavePropagation/acous3D_Q5_small_base.xml | 4 ++-- .../benchmarks/acous3D_benchmark_base.xml | 6 +++--- .../benchmarks/elas3D_benchmark_base.xml | 18 +++++++++--------- .../elas3D_Q5_firstOrder_small_base.xml | 6 +++--- .../wavePropagation/elas3D_Q5_small_base.xml | 6 +++--- 6 files changed, 21 insertions(+), 21 deletions(-) diff --git a/inputFiles/wavePropagation/acous3D_Q5_abc_smoke.xml b/inputFiles/wavePropagation/acous3D_Q5_abc_smoke.xml index b221f455741..bd39714adc3 100644 --- a/inputFiles/wavePropagation/acous3D_Q5_abc_smoke.xml +++ b/inputFiles/wavePropagation/acous3D_Q5_abc_smoke.xml @@ -2,7 +2,7 @@ - + diff --git a/inputFiles/wavePropagation/acous3D_Q5_small_base.xml b/inputFiles/wavePropagation/acous3D_Q5_small_base.xml index 99697f8ff77..0ad60b621bf 100644 --- a/inputFiles/wavePropagation/acous3D_Q5_small_base.xml +++ b/inputFiles/wavePropagation/acous3D_Q5_small_base.xml @@ -72,7 +72,7 @@ initialCondition="1" objectPath="mesh/FE2/ElementRegions/Region/cb" fieldName="acousticDensity" - scale="1500" + scale="1" setNames="{ all }"/> @@ -96,7 +96,7 @@ @@ -92,7 +92,7 @@ name="initialPressure_nm1" initialCondition="1" setNames="{ all }" - objectPath="nodeManager" + objectPath="mesh/FE1/nodeManager" fieldName="pressure_nm1" scale="0.0"/> @@ -100,7 +100,7 @@ diff --git a/inputFiles/wavePropagation/benchmarks/elas3D_benchmark_base.xml b/inputFiles/wavePropagation/benchmarks/elas3D_benchmark_base.xml index b3562deee89..fc3b967238b 100644 --- a/inputFiles/wavePropagation/benchmarks/elas3D_benchmark_base.xml +++ b/inputFiles/wavePropagation/benchmarks/elas3D_benchmark_base.xml @@ -118,7 +118,7 @@ name="initialdisplacementnx" initialCondition="1" setNames="{ all }" - objectPath="nodeManager" + objectPath="mesh/FE1/nodeManager" fieldName="displacementx_n" scale="0.0"/> @@ -126,7 +126,7 @@ name="initialdisplacementny" initialCondition="1" setNames="{ all }" - objectPath="nodeManager" + objectPath="mesh/FE1/nodeManager" fieldName="displacementy_n" scale="0.0"/> @@ -134,7 +134,7 @@ name="initialdisplacementnz" initialCondition="1" setNames="{ all }" - objectPath="nodeManager" + objectPath="mesh/FE1/nodeManager" fieldName="displacementz_n" scale="0.0"/> @@ -142,7 +142,7 @@ name="initialdisplacementnm1x" initialCondition="1" setNames="{ all }" - objectPath="nodeManager" + objectPath="mesh/FE1/nodeManager" fieldName="displacementx_nm1" scale="0.0"/> @@ -150,7 +150,7 @@ name="initialdisplacementnm1y" initialCondition="1" setNames="{ all }" - objectPath="nodeManager" + objectPath="mesh/FE1/nodeManager" fieldName="displacementy_nm1" scale="0.0"/> @@ -158,7 +158,7 @@ name="initialdisplacementnm1z" initialCondition="1" setNames="{ all }" - objectPath="nodeManager" + objectPath="mesh/FE1/nodeManager" fieldName="displacementz_nm1" scale="0.0"/> @@ -167,7 +167,7 @@ @@ -175,7 +175,7 @@ @@ -183,7 +183,7 @@ diff --git a/inputFiles/wavePropagation/elas3D_Q5_firstOrder_small_base.xml b/inputFiles/wavePropagation/elas3D_Q5_firstOrder_small_base.xml index c21bd3328ea..b360569bb0e 100644 --- a/inputFiles/wavePropagation/elas3D_Q5_firstOrder_small_base.xml +++ b/inputFiles/wavePropagation/elas3D_Q5_firstOrder_small_base.xml @@ -99,17 +99,17 @@ Date: Sat, 13 Jan 2024 03:34:31 +0700 Subject: [PATCH 26/40] Fix a typo in Example2.rst (#2931) --- .../wellboreProblems/deviatedPoroElasticWellbore/Example2.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/docs/sphinx/advancedExamples/validationStudies/wellboreProblems/deviatedPoroElasticWellbore/Example2.rst b/src/docs/sphinx/advancedExamples/validationStudies/wellboreProblems/deviatedPoroElasticWellbore/Example2.rst index 0e72064ba11..00bd5351de7 100644 --- a/src/docs/sphinx/advancedExamples/validationStudies/wellboreProblems/deviatedPoroElasticWellbore/Example2.rst +++ b/src/docs/sphinx/advancedExamples/validationStudies/wellboreProblems/deviatedPoroElasticWellbore/Example2.rst @@ -10,7 +10,7 @@ Problem description ------------------------------------------------------------------ -This example deals with the problem of drilling a deviated poro-elastic wellbore. This is an extension of the poroelastic wellbore example :ref:`AdvancedExampleDeviatedPoroElasticWellbore` with the consideration of in-situ stresses and in-situ pore pressure. Both pore pressure and mud pressure are supposed to be nil at the borehole wall following the consideration of `(Abousleiman and Cui, 1998) `__. Also, the in-situ horizontal stresses are anisotropic, i.e. :math:`\sigma_hmax` > :math:`\sigma_hmin`. The wellbore trajectory is deviated from the directions of the in-situ stresses. Analytical solutions of the pore pressure, the radial and hoop stresses in the near wellbore region are given by `(Abousleiman and Cui, 1998) `__. They are hereby used to verify the modeling predictions. +This example deals with the problem of drilling a deviated poro-elastic wellbore. This is an extension of the poroelastic wellbore example :ref:`AdvancedExampleDeviatedPoroElasticWellbore` with the consideration of in-situ stresses and in-situ pore pressure. Both pore pressure and mud pressure are supposed to be nil at the borehole wall following the consideration of `(Abousleiman and Cui, 1998) `__. Also, the in-situ horizontal stresses are anisotropic, i.e. :math:`\sigma_{hmax}` > :math:`\sigma_{hmin}`. The wellbore trajectory is deviated from the directions of the in-situ stresses. Analytical solutions of the pore pressure, the radial and hoop stresses in the near wellbore region are given by `(Abousleiman and Cui, 1998) `__. They are hereby used to verify the modeling predictions. **Input file** From 741b6ab459c01266627b518df67769dd0dbf6700 Mon Sep 17 00:00:00 2001 From: Stefano Frambati Date: Fri, 12 Jan 2024 22:05:52 +0100 Subject: [PATCH 27/40] Fix compilation issues and PVT Driver unit test on Pangea3 (#2910) * removed access to private class members from device code * modified host config for P3 * removed CUDA Hypre flag (does not completely work on P3) * Re-enabled cuda acceleration for hypre --------- Co-authored-by: Matteo Cusini <49037133+CusiniM@users.noreply.github.com> --- .../TOTAL/pangea3-gcc8.4.1-openmpi-4.1.2.cmake | 10 ++++------ .../fluid/multifluid/PVTDriverRunTest.hpp | 12 +++++++----- 2 files changed, 11 insertions(+), 11 deletions(-) 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 ee75f9079b7..c2b8e96c328 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 @@ -1,7 +1,5 @@ # hostconfig for pangea3 # -# export MODULEPATH=/workrd/SCR/NUM/geosx_num/module_files:$MODULEPATH -# module load cmake/3.21.4 gcc/8.4.1 cuda/11.0.3 ompi/4.1.2 openblas/0.3.18 python4geosx/p3/gcc8.4.1-ompi4.1.2 # set(CONFIG_NAME "pangea3-gcc8.4.1-ompi-4.1.2" CACHE PATH "") @@ -56,7 +54,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_FLAGS "-restrict -arch ${CUDA_ARCH} --expt-extended-lambda -Werror cross-execution-space-call,reorder,deprecated-declarations" 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 -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 "") @@ -64,7 +62,7 @@ if (DEFINED ENV{CUDA_ROOT}) # Uncomment this line to make nvcc output register usage for each kernel. # set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} --resource-usage" CACHE STRING "" FORCE) else() - message(FATAL_ERROR "You must have CUDA_ROOT environment variable set, we advise loading module cuda/11.0.3") + message(FATAL_ERROR "You must have CUDA_ROOT environment variable set, we advise loading module cuda/11.5.0") endif() # GTEST options @@ -108,7 +106,7 @@ set(ENABLE_PETSC OFF CACHE BOOL "") set(ENABLE_HYPRE ON CACHE BOOL "") set(ENABLE_HYPRE_DEVICE "CUDA" CACHE BOOL "") -# activate workaround for fmt formatter -set(ENABLE_FMT_CONST_FORMATTER_WORKAROUND ON CACHE BOOL "") +# disable benchmarks, they are incompatible with P3's nvcc version (cuda 11.5.0) +set(ENABLE_BENCHMARKS OFF CACHE BOOL "") include( ${CMAKE_CURRENT_LIST_DIR}/../tpls.cmake ) diff --git a/src/coreComponents/constitutive/fluid/multifluid/PVTDriverRunTest.hpp b/src/coreComponents/constitutive/fluid/multifluid/PVTDriverRunTest.hpp index ee9c5324a25..da522d27913 100644 --- a/src/coreComponents/constitutive/fluid/multifluid/PVTDriverRunTest.hpp +++ b/src/coreComponents/constitutive/fluid/multifluid/PVTDriverRunTest.hpp @@ -64,14 +64,16 @@ void PVTDriver::runTest( FLUID_TYPE & fluid, arrayView2d< real64 > const & table // note: column indexing should be kept consistent with output file header below. integer const numSteps = m_numSteps; + integer const outputCompressibility = m_outputCompressibility; + integer const outputPhaseComposition = m_outputPhaseComposition; using ExecPolicy = typename FLUID_TYPE::exec_policy; forAll< ExecPolicy >( composition.size( 0 ), - [this, numPhases, numComponents, numSteps, kernelWrapper, - table, composition] + [ outputCompressibility, outputPhaseComposition, numPhases, numComponents, numSteps, kernelWrapper, + table, composition] GEOS_HOST_DEVICE ( localIndex const i ) { // Index for start of phase properties - integer const PHASE = m_outputCompressibility != 0 ? TEMP + 3 : TEMP + 2; + integer const PHASE = outputCompressibility != 0 ? TEMP + 3 : TEMP + 2; // Temporary space for phase mole fractions stackArray1d< real64, constitutive::MultiFluidBase::MAX_NUM_COMPONENTS > phaseComposition( numComponents ); @@ -81,7 +83,7 @@ void PVTDriver::runTest( FLUID_TYPE & fluid, arrayView2d< real64 > const & table kernelWrapper.update( i, 0, table( n, PRES ), table( n, TEMP ), composition[i] ); table( n, TEMP + 1 ) = kernelWrapper.totalDensity()( i, 0 ); - if( m_outputCompressibility != 0 ) + if( outputCompressibility != 0 ) { table( n, TEMP + 2 ) = kernelWrapper.totalCompressibility( i, 0 ); } @@ -92,7 +94,7 @@ void PVTDriver::runTest( FLUID_TYPE & fluid, arrayView2d< real64 > const & table table( n, PHASE + p + numPhases ) = kernelWrapper.phaseDensity()( i, 0, p ); table( n, PHASE + p + 2 * numPhases ) = kernelWrapper.phaseViscosity()( i, 0, p ); } - if( m_outputPhaseComposition != 0 ) + if( outputPhaseComposition != 0 ) { for( integer p = 0; p < numPhases; ++p ) { From 7f43ca81c104530ed1323d390024e540b9d7c87b Mon Sep 17 00:00:00 2001 From: Nicola Castelletto <38361926+castelletto1@users.noreply.github.com> Date: Tue, 16 Jan 2024 13:55:03 -0800 Subject: [PATCH 28/40] Updating paths to GEOSDATA repo (#2934) --- .../benchmarks/Class09Pb3/class09_pb3_benchmark.xml | 2 +- .../benchmarks/Egg/deadOilEgg_benchmark.xml | 2 +- .../poromechanics/PoroElastic_Mandel_prism6_benchmark.xml | 2 +- .../nonlinearAcceleration/smallEggModel/smallEggModel.xml | 2 +- .../PoroElastic_efem-edfm_eggModel_large.xml | 2 +- .../PoroElastic_efem-edfm_eggModel_small.xml | 2 +- .../ThermoPoroElastic_efem-edfm_eggModel_small.xml | 2 +- .../carbonStorage/isothermalHystInjection/Example.rst | 2 +- .../basicExamples/multiphaseFlowWithWells/Example.rst | 6 +++--- 9 files changed, 11 insertions(+), 11 deletions(-) diff --git a/inputFiles/compositionalMultiphaseWell/benchmarks/Class09Pb3/class09_pb3_benchmark.xml b/inputFiles/compositionalMultiphaseWell/benchmarks/Class09Pb3/class09_pb3_benchmark.xml index 95b03293b68..d0c07591e87 100644 --- a/inputFiles/compositionalMultiphaseWell/benchmarks/Class09Pb3/class09_pb3_benchmark.xml +++ b/inputFiles/compositionalMultiphaseWell/benchmarks/Class09Pb3/class09_pb3_benchmark.xml @@ -14,7 +14,7 @@ diff --git a/inputFiles/poromechanics/PoroElastic_Mandel_prism6_benchmark.xml b/inputFiles/poromechanics/PoroElastic_Mandel_prism6_benchmark.xml index ce5162ea265..5235a06f984 100644 --- a/inputFiles/poromechanics/PoroElastic_Mandel_prism6_benchmark.xml +++ b/inputFiles/poromechanics/PoroElastic_Mandel_prism6_benchmark.xml @@ -9,7 +9,7 @@ + file="../../../GEOSDATA/DataSets/mandelMeshes/mandel_prism6_0430_cells.vtu"/> diff --git a/inputFiles/poromechanics/nonlinearAcceleration/smallEggModel/smallEggModel.xml b/inputFiles/poromechanics/nonlinearAcceleration/smallEggModel/smallEggModel.xml index 0a6d34d930b..a8259a6e66d 100755 --- a/inputFiles/poromechanics/nonlinearAcceleration/smallEggModel/smallEggModel.xml +++ b/inputFiles/poromechanics/nonlinearAcceleration/smallEggModel/smallEggModel.xml @@ -66,7 +66,7 @@ diff --git a/inputFiles/poromechanicsFractures/PoroElastic_efem-edfm_eggModel_large.xml b/inputFiles/poromechanicsFractures/PoroElastic_efem-edfm_eggModel_large.xml index 4c1baeb73dd..7b99a61756a 100644 --- a/inputFiles/poromechanicsFractures/PoroElastic_efem-edfm_eggModel_large.xml +++ b/inputFiles/poromechanicsFractures/PoroElastic_efem-edfm_eggModel_large.xml @@ -67,7 +67,7 @@ diff --git a/inputFiles/poromechanicsFractures/PoroElastic_efem-edfm_eggModel_small.xml b/inputFiles/poromechanicsFractures/PoroElastic_efem-edfm_eggModel_small.xml index a065fc3d739..a523346a32e 100644 --- a/inputFiles/poromechanicsFractures/PoroElastic_efem-edfm_eggModel_small.xml +++ b/inputFiles/poromechanicsFractures/PoroElastic_efem-edfm_eggModel_small.xml @@ -67,7 +67,7 @@ diff --git a/inputFiles/thermoPoromechanicsFractures/ThermoPoroElastic_efem-edfm_eggModel_small.xml b/inputFiles/thermoPoromechanicsFractures/ThermoPoroElastic_efem-edfm_eggModel_small.xml index 2e83bc82161..521fecdf020 100644 --- a/inputFiles/thermoPoromechanicsFractures/ThermoPoroElastic_efem-edfm_eggModel_small.xml +++ b/inputFiles/thermoPoromechanicsFractures/ThermoPoroElastic_efem-edfm_eggModel_small.xml @@ -70,7 +70,7 @@ diff --git a/src/docs/sphinx/advancedExamples/validationStudies/carbonStorage/isothermalHystInjection/Example.rst b/src/docs/sphinx/advancedExamples/validationStudies/carbonStorage/isothermalHystInjection/Example.rst index bc15312c9fe..03796a32495 100644 --- a/src/docs/sphinx/advancedExamples/validationStudies/carbonStorage/isothermalHystInjection/Example.rst +++ b/src/docs/sphinx/advancedExamples/validationStudies/carbonStorage/isothermalHystInjection/Example.rst @@ -37,7 +37,7 @@ presented in `(Class et al., 2009) `__. The setup is illustrated in the figure below. -The mesh can be found in `GEOSXDATA `__ and +The mesh can be found in `GEOSDATA `__ and was provided for the benchmark. It discretizes the widely-used `Johansen` reservoir, which consists in a tilted reservoir with a main fault. The model domain has the following dimensions: 9600 x 8900 x [90-140] m. diff --git a/src/docs/sphinx/basicExamples/multiphaseFlowWithWells/Example.rst b/src/docs/sphinx/basicExamples/multiphaseFlowWithWells/Example.rst index f0fdd28eece..0c1d7be2a45 100644 --- a/src/docs/sphinx/basicExamples/multiphaseFlowWithWells/Example.rst +++ b/src/docs/sphinx/basicExamples/multiphaseFlowWithWells/Example.rst @@ -29,12 +29,12 @@ This example is based on the XML file located at ../../../../../inputFiles/compositionalMultiphaseWell/benchmarks/Egg/deadOilEgg_benchmark.xml -The mesh file corresponding to the Egg model is stored in the GEOSXDATA repository. -Therefore, you must first download the GEOSXDATA repository in the same folder +The mesh file corresponding to the Egg model is stored in the GEOSDATA repository. +Therefore, you must first download the GEOSDATA repository in the same folder as the GEOS repository to run this test case. .. note:: - `GEOSXDATA `_ is a separate repository in which we store large mesh files in order to keep the main GEOS repository lightweight. + `GEOSDATA `_ is a separate repository in which we store large mesh files in order to keep the main GEOS repository lightweight. The XML file considered here follows the typical structure of the GEOS input files: From bafcb9b4212319d170a66c082a2373279e9c8cc1 Mon Sep 17 00:00:00 2001 From: Jian Huang <53012159+jhuang2601@users.noreply.github.com> Date: Wed, 17 Jan 2024 10:20:22 -0600 Subject: [PATCH 29/40] Fix function interface for poroVisoPlastic models (#2696) --- .../PoroViscoDruckerPrager_base.xml | 227 +++++++++++++++++ .../PoroViscoDruckerPrager_smoke.xml | 52 ++++ .../PoroViscoExtendedDruckerPrager_base.xml | 228 ++++++++++++++++++ .../PoroViscoExtendedDruckerPrager_smoke.xml | 52 ++++ .../PoroViscoModifiedCamClay_base.xml | 228 ++++++++++++++++++ .../PoroViscoModifiedCamClay_smoke.xml | 52 ++++ integratedTests | 2 +- .../constitutive/ConstitutivePassThru.hpp | 18 +- .../constitutive/solid/PorousSolid.cpp | 12 +- .../constitutive/solid/PorousSolid.hpp | 2 +- .../PoromechanicsKernels.cmake | 5 +- .../schema/docs/Constitutive.rst | 5 +- .../schema/docs/Constitutive_other.rst | 5 +- .../schema/docs/PorousViscoDruckerPrager.rst | 13 + .../docs/PorousViscoDruckerPrager_other.rst | 9 + .../docs/PorousViscoExtendedDruckerPrager.rst | 13 + ...PorousViscoExtendedDruckerPrager_other.rst | 9 + .../docs/PorousViscoModifiedCamClay.rst | 13 + .../docs/PorousViscoModifiedCamClay_other.rst | 9 + src/coreComponents/schema/schema.xsd | 51 ++++ src/coreComponents/schema/schema.xsd.other | 6 + src/docs/sphinx/CompleteXMLSchema.rst | 42 ++++ 22 files changed, 1033 insertions(+), 20 deletions(-) create mode 100644 inputFiles/poromechanics/PoroViscoDruckerPrager_base.xml create mode 100644 inputFiles/poromechanics/PoroViscoDruckerPrager_smoke.xml create mode 100644 inputFiles/poromechanics/PoroViscoExtendedDruckerPrager_base.xml create mode 100644 inputFiles/poromechanics/PoroViscoExtendedDruckerPrager_smoke.xml create mode 100644 inputFiles/poromechanics/PoroViscoModifiedCamClay_base.xml create mode 100644 inputFiles/poromechanics/PoroViscoModifiedCamClay_smoke.xml create mode 100644 src/coreComponents/schema/docs/PorousViscoDruckerPrager.rst create mode 100644 src/coreComponents/schema/docs/PorousViscoDruckerPrager_other.rst create mode 100644 src/coreComponents/schema/docs/PorousViscoExtendedDruckerPrager.rst create mode 100644 src/coreComponents/schema/docs/PorousViscoExtendedDruckerPrager_other.rst create mode 100644 src/coreComponents/schema/docs/PorousViscoModifiedCamClay.rst create mode 100644 src/coreComponents/schema/docs/PorousViscoModifiedCamClay_other.rst diff --git a/inputFiles/poromechanics/PoroViscoDruckerPrager_base.xml b/inputFiles/poromechanics/PoroViscoDruckerPrager_base.xml new file mode 100644 index 00000000000..d4d042e5ae2 --- /dev/null +++ b/inputFiles/poromechanics/PoroViscoDruckerPrager_base.xml @@ -0,0 +1,227 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inputFiles/poromechanics/PoroViscoDruckerPrager_smoke.xml b/inputFiles/poromechanics/PoroViscoDruckerPrager_smoke.xml new file mode 100644 index 00000000000..892d3a9fa74 --- /dev/null +++ b/inputFiles/poromechanics/PoroViscoDruckerPrager_smoke.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inputFiles/poromechanics/PoroViscoExtendedDruckerPrager_base.xml b/inputFiles/poromechanics/PoroViscoExtendedDruckerPrager_base.xml new file mode 100644 index 00000000000..072aa138f0a --- /dev/null +++ b/inputFiles/poromechanics/PoroViscoExtendedDruckerPrager_base.xml @@ -0,0 +1,228 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inputFiles/poromechanics/PoroViscoExtendedDruckerPrager_smoke.xml b/inputFiles/poromechanics/PoroViscoExtendedDruckerPrager_smoke.xml new file mode 100644 index 00000000000..4e81b81c03b --- /dev/null +++ b/inputFiles/poromechanics/PoroViscoExtendedDruckerPrager_smoke.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inputFiles/poromechanics/PoroViscoModifiedCamClay_base.xml b/inputFiles/poromechanics/PoroViscoModifiedCamClay_base.xml new file mode 100644 index 00000000000..b48274d0c7a --- /dev/null +++ b/inputFiles/poromechanics/PoroViscoModifiedCamClay_base.xml @@ -0,0 +1,228 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inputFiles/poromechanics/PoroViscoModifiedCamClay_smoke.xml b/inputFiles/poromechanics/PoroViscoModifiedCamClay_smoke.xml new file mode 100644 index 00000000000..8a0581c357c --- /dev/null +++ b/inputFiles/poromechanics/PoroViscoModifiedCamClay_smoke.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/integratedTests b/integratedTests index b5332575b73..7285cd2e28d 160000 --- a/integratedTests +++ b/integratedTests @@ -1 +1 @@ -Subproject commit b5332575b730ed88cef3f18475203d1d5ca1b207 +Subproject commit 7285cd2e28d97c43f648ab6d61b1c49de153ddb5 diff --git a/src/coreComponents/constitutive/ConstitutivePassThru.hpp b/src/coreComponents/constitutive/ConstitutivePassThru.hpp index ef6a51fc069..0f97f509309 100644 --- a/src/coreComponents/constitutive/ConstitutivePassThru.hpp +++ b/src/coreComponents/constitutive/ConstitutivePassThru.hpp @@ -269,9 +269,9 @@ struct ConstitutivePassThru< PorousSolidBase > PorousSolid< ModifiedCamClay >, PorousSolid< DelftEgg >, PorousSolid< DruckerPrager >, - //PorousSolid< DuvautLionsSolid< DruckerPrager > >, - // PorousSolid< DuvautLionsSolid< DruckerPragerExtended > >, - //PorousSolid< DuvautLionsSolid< ModifiedCamClay > >, + PorousSolid< DuvautLionsSolid< DruckerPrager > >, + PorousSolid< DuvautLionsSolid< DruckerPragerExtended > >, + PorousSolid< DuvautLionsSolid< ModifiedCamClay > >, PorousSolid< ElasticIsotropic >, PorousSolid< ElasticTransverseIsotropic >, PorousSolid< ElasticIsotropicPressureDependent >, @@ -359,9 +359,9 @@ struct ConstitutivePassThru< CoupledSolidBase > PorousSolid< ModifiedCamClay >, PorousSolid< DelftEgg >, PorousSolid< DruckerPrager >, - //PorousSolid< DuvautLionsSolid< DruckerPrager > >, - //PorousSolid< DuvautLionsSolid< DruckerPragerExtended > >, - //PorousSolid< DuvautLionsSolid< ModifiedCamClay > >, + PorousSolid< DuvautLionsSolid< DruckerPrager > >, + PorousSolid< DuvautLionsSolid< DruckerPragerExtended > >, + PorousSolid< DuvautLionsSolid< ModifiedCamClay > >, PorousSolid< ElasticIsotropic >, PorousSolid< ElasticTransverseIsotropic >, PorousSolid< ElasticIsotropicPressureDependent >, @@ -385,9 +385,9 @@ struct ConstitutivePassThru< CoupledSolidBase > PorousSolid< ModifiedCamClay >, PorousSolid< DelftEgg >, PorousSolid< DruckerPrager >, - //PorousSolid< DuvautLionsSolid< DruckerPrager > >, - //PorousSolid< DuvautLionsSolid< DruckerPragerExtended > >, - //PorousSolid< DuvautLionsSolid< ModifiedCamClay > >, + PorousSolid< DuvautLionsSolid< DruckerPrager > >, + PorousSolid< DuvautLionsSolid< DruckerPragerExtended > >, + PorousSolid< DuvautLionsSolid< ModifiedCamClay > >, PorousSolid< ElasticIsotropic >, PorousSolid< ElasticTransverseIsotropic >, PorousSolid< ElasticIsotropicPressureDependent >, diff --git a/src/coreComponents/constitutive/solid/PorousSolid.cpp b/src/coreComponents/constitutive/solid/PorousSolid.cpp index b4c8627a196..ed9cadb7bca 100644 --- a/src/coreComponents/constitutive/solid/PorousSolid.cpp +++ b/src/coreComponents/constitutive/solid/PorousSolid.cpp @@ -56,9 +56,9 @@ typedef PorousSolid< DruckerPragerExtended > PorousDruckerPragerExtended; typedef PorousSolid< Damage< ElasticIsotropic > > PorousDamageElasticIsotropic; typedef PorousSolid< DamageSpectral< ElasticIsotropic > > PorousDamageSpectralElasticIsotropic; typedef PorousSolid< DamageVolDev< ElasticIsotropic > > PorousDamageVolDevElasticIsotropic; -//typedef PorousSolid< DuvautLionsSolid< DruckerPrager > > PorousViscoDruckerPrager; -//typedef PorousSolid< DuvautLionsSolid< DruckerPragerExtended > > PorousViscoDruckerPragerExtended; -//typedef PorousSolid< DuvautLionsSolid< ModifiedCamClay > > PorousViscoModifiedCamClay; +typedef PorousSolid< DuvautLionsSolid< DruckerPrager > > PorousViscoDruckerPrager; +typedef PorousSolid< DuvautLionsSolid< DruckerPragerExtended > > PorousViscoDruckerPragerExtended; +typedef PorousSolid< DuvautLionsSolid< ModifiedCamClay > > PorousViscoModifiedCamClay; typedef PorousSolid< ModifiedCamClay > PorousModifiedCamClay; @@ -72,9 +72,9 @@ REGISTER_CATALOG_ENTRY( ConstitutiveBase, PorousDamageElasticIsotropic, string c REGISTER_CATALOG_ENTRY( ConstitutiveBase, PorousDamageSpectralElasticIsotropic, string const &, Group * const ) REGISTER_CATALOG_ENTRY( ConstitutiveBase, PorousDamageVolDevElasticIsotropic, string const &, Group * const ) REGISTER_CATALOG_ENTRY( ConstitutiveBase, PorousModifiedCamClay, string const &, Group * const ) -//REGISTER_CATALOG_ENTRY( ConstitutiveBase, PorousViscoDruckerPrager, string const &, Group * const ) -//REGISTER_CATALOG_ENTRY( ConstitutiveBase, PorousViscoDruckerPragerExtended, string const &, Group * const ) -//REGISTER_CATALOG_ENTRY( ConstitutiveBase, PorousViscoModifiedCamClay, string const &, Group * const ) +REGISTER_CATALOG_ENTRY( ConstitutiveBase, PorousViscoDruckerPrager, string const &, Group * const ) +REGISTER_CATALOG_ENTRY( ConstitutiveBase, PorousViscoDruckerPragerExtended, string const &, Group * const ) +REGISTER_CATALOG_ENTRY( ConstitutiveBase, PorousViscoModifiedCamClay, string const &, Group * const ) } diff --git a/src/coreComponents/constitutive/solid/PorousSolid.hpp b/src/coreComponents/constitutive/solid/PorousSolid.hpp index 4d6ca176a9b..67a16bef411 100644 --- a/src/coreComponents/constitutive/solid/PorousSolid.hpp +++ b/src/coreComponents/constitutive/solid/PorousSolid.hpp @@ -337,7 +337,7 @@ class PorousSolid : public CoupledSolid< SOLID_TYPE, BiotPorosity, ConstantPerme * @brief Catalog name * @return Static catalog string */ - static string catalogName() { return string( "Porous" ) + SOLID_TYPE::m_catalogNameString; } + static string catalogName() { return string( "Porous" ) + SOLID_TYPE::catalogName(); } /** * @brief Get catalog name diff --git a/src/coreComponents/physicsSolvers/multiphysics/poromechanicsKernels/PoromechanicsKernels.cmake b/src/coreComponents/physicsSolvers/multiphysics/poromechanicsKernels/PoromechanicsKernels.cmake index 790302aaca9..ff5ee1f49eb 100644 --- a/src/coreComponents/physicsSolvers/multiphysics/poromechanicsKernels/PoromechanicsKernels.cmake +++ b/src/coreComponents/physicsSolvers/multiphysics/poromechanicsKernels/PoromechanicsKernels.cmake @@ -23,7 +23,10 @@ set( porousSolidDispatch PorousSolid PorousSolid PorousSolid> PorousSolid> - PorousSolid> ) + PorousSolid> + PorousSolid> + PorousSolid> + PorousSolid> ) set( finiteElementDispatch H1_Hexahedron_Lagrange1_GaussLegendre2 H1_Wedge_Lagrange1_Gauss6 diff --git a/src/coreComponents/schema/docs/Constitutive.rst b/src/coreComponents/schema/docs/Constitutive.rst index 5f0b249cc69..2f7c4bad4e0 100644 --- a/src/coreComponents/schema/docs/Constitutive.rst +++ b/src/coreComponents/schema/docs/Constitutive.rst @@ -57,7 +57,10 @@ PorousElasticIsotropic node :ref:`XML_PorousElast PorousElasticOrthotropic node :ref:`XML_PorousElasticOrthotropic` PorousElasticTransverseIsotropic node :ref:`XML_PorousElasticTransverseIsotropic` PorousExtendedDruckerPrager node :ref:`XML_PorousExtendedDruckerPrager` -PorousModifiedCamClay node :ref:`XML_PorousModifiedCamClay` +PorousModifiedCamClay node :ref:`XML_PorousModifiedCamClay` +PorousViscoDruckerPrager node :ref:`XML_PorousViscoDruckerPrager` +PorousViscoExtendedDruckerPrager node :ref:`XML_PorousViscoExtendedDruckerPrager` +PorousViscoModifiedCamClay node :ref:`XML_PorousViscoModifiedCamClay` PressurePorosity node :ref:`XML_PressurePorosity` ProppantPermeability node :ref:`XML_ProppantPermeability` ProppantPorosity node :ref:`XML_ProppantPorosity` diff --git a/src/coreComponents/schema/docs/Constitutive_other.rst b/src/coreComponents/schema/docs/Constitutive_other.rst index 3c79a6b6906..06999731356 100644 --- a/src/coreComponents/schema/docs/Constitutive_other.rst +++ b/src/coreComponents/schema/docs/Constitutive_other.rst @@ -57,7 +57,10 @@ PorousElasticIsotropic node :ref:`DATASTRUCTURE_PorousEla PorousElasticOrthotropic node :ref:`DATASTRUCTURE_PorousElasticOrthotropic` PorousElasticTransverseIsotropic node :ref:`DATASTRUCTURE_PorousElasticTransverseIsotropic` PorousExtendedDruckerPrager node :ref:`DATASTRUCTURE_PorousExtendedDruckerPrager` -PorousModifiedCamClay node :ref:`DATASTRUCTURE_PorousModifiedCamClay` +PorousModifiedCamClay node :ref:`DATASTRUCTURE_PorousModifiedCamClay` +PorousViscoDruckerPrager node :ref:`DATASTRUCTURE_PorousViscoDruckerPrager` +PorousViscoExtendedDruckerPrager node :ref:`DATASTRUCTURE_PorousViscoExtendedDruckerPrager` +PorousViscoModifiedCamClay node :ref:`DATASTRUCTURE_PorousViscoModifiedCamClay` PressurePorosity node :ref:`DATASTRUCTURE_PressurePorosity` ProppantPermeability node :ref:`DATASTRUCTURE_ProppantPermeability` ProppantPorosity node :ref:`DATASTRUCTURE_ProppantPorosity` diff --git a/src/coreComponents/schema/docs/PorousViscoDruckerPrager.rst b/src/coreComponents/schema/docs/PorousViscoDruckerPrager.rst new file mode 100644 index 00000000000..e15edba5a2f --- /dev/null +++ b/src/coreComponents/schema/docs/PorousViscoDruckerPrager.rst @@ -0,0 +1,13 @@ + + +============================ ====== ======== =========================================== +Name Type Default Description +============================ ====== ======== =========================================== +name string required A name is required for any non-unique nodes +permeabilityModelName string required Name of the permeability model. +porosityModelName string required Name of the porosity model. +solidInternalEnergyModelName string Name of the solid internal energy model. +solidModelName string required Name of the solid model. +============================ ====== ======== =========================================== + + diff --git a/src/coreComponents/schema/docs/PorousViscoDruckerPrager_other.rst b/src/coreComponents/schema/docs/PorousViscoDruckerPrager_other.rst new file mode 100644 index 00000000000..adf1c1b8aec --- /dev/null +++ b/src/coreComponents/schema/docs/PorousViscoDruckerPrager_other.rst @@ -0,0 +1,9 @@ + + +==== ==== ============================ +Name Type Description +==== ==== ============================ + (no documentation available) +==== ==== ============================ + + diff --git a/src/coreComponents/schema/docs/PorousViscoExtendedDruckerPrager.rst b/src/coreComponents/schema/docs/PorousViscoExtendedDruckerPrager.rst new file mode 100644 index 00000000000..e15edba5a2f --- /dev/null +++ b/src/coreComponents/schema/docs/PorousViscoExtendedDruckerPrager.rst @@ -0,0 +1,13 @@ + + +============================ ====== ======== =========================================== +Name Type Default Description +============================ ====== ======== =========================================== +name string required A name is required for any non-unique nodes +permeabilityModelName string required Name of the permeability model. +porosityModelName string required Name of the porosity model. +solidInternalEnergyModelName string Name of the solid internal energy model. +solidModelName string required Name of the solid model. +============================ ====== ======== =========================================== + + diff --git a/src/coreComponents/schema/docs/PorousViscoExtendedDruckerPrager_other.rst b/src/coreComponents/schema/docs/PorousViscoExtendedDruckerPrager_other.rst new file mode 100644 index 00000000000..adf1c1b8aec --- /dev/null +++ b/src/coreComponents/schema/docs/PorousViscoExtendedDruckerPrager_other.rst @@ -0,0 +1,9 @@ + + +==== ==== ============================ +Name Type Description +==== ==== ============================ + (no documentation available) +==== ==== ============================ + + diff --git a/src/coreComponents/schema/docs/PorousViscoModifiedCamClay.rst b/src/coreComponents/schema/docs/PorousViscoModifiedCamClay.rst new file mode 100644 index 00000000000..e15edba5a2f --- /dev/null +++ b/src/coreComponents/schema/docs/PorousViscoModifiedCamClay.rst @@ -0,0 +1,13 @@ + + +============================ ====== ======== =========================================== +Name Type Default Description +============================ ====== ======== =========================================== +name string required A name is required for any non-unique nodes +permeabilityModelName string required Name of the permeability model. +porosityModelName string required Name of the porosity model. +solidInternalEnergyModelName string Name of the solid internal energy model. +solidModelName string required Name of the solid model. +============================ ====== ======== =========================================== + + diff --git a/src/coreComponents/schema/docs/PorousViscoModifiedCamClay_other.rst b/src/coreComponents/schema/docs/PorousViscoModifiedCamClay_other.rst new file mode 100644 index 00000000000..adf1c1b8aec --- /dev/null +++ b/src/coreComponents/schema/docs/PorousViscoModifiedCamClay_other.rst @@ -0,0 +1,9 @@ + + +==== ==== ============================ +Name Type Description +==== ==== ============================ + (no documentation available) +==== ==== ============================ + + diff --git a/src/coreComponents/schema/schema.xsd b/src/coreComponents/schema/schema.xsd index a081d6b6ada..faf2990dedb 100644 --- a/src/coreComponents/schema/schema.xsd +++ b/src/coreComponents/schema/schema.xsd @@ -729,6 +729,18 @@ + + + + + + + + + + + + @@ -3595,6 +3607,9 @@ Local - Add stabilization only to interiors of macro elements.--> + + + @@ -4617,6 +4632,42 @@ If you want to do a three-phase simulation, please use instead wettingIntermedia + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/coreComponents/schema/schema.xsd.other b/src/coreComponents/schema/schema.xsd.other index 88a655235d3..ca7aaa0f57c 100644 --- a/src/coreComponents/schema/schema.xsd.other +++ b/src/coreComponents/schema/schema.xsd.other @@ -1334,6 +1334,9 @@ + + + @@ -2300,6 +2303,9 @@ + + + diff --git a/src/docs/sphinx/CompleteXMLSchema.rst b/src/docs/sphinx/CompleteXMLSchema.rst index 0ed2e64afe2..ff88d7a1a73 100644 --- a/src/docs/sphinx/CompleteXMLSchema.rst +++ b/src/docs/sphinx/CompleteXMLSchema.rst @@ -864,6 +864,27 @@ Element: PorousModifiedCamClay .. include:: ../../coreComponents/schema/docs/PorousModifiedCamClay.rst +.. _XML_PorousViscoDruckerPrager: + +Element: PorousViscoDruckerPrager +============================== +.. include:: ../../coreComponents/schema/docs/PorousViscoDruckerPrager.rst + + +.. _XML_PorousViscoExtendedDruckerPrager: + +Element: PorousViscoExtendedDruckerPrager +============================== +.. include:: ../../coreComponents/schema/docs/PorousViscoExtendedDruckerPrager.rst + + +.. _XML_PorousViscoModifiedCamClay: + +Element: PorousViscoModifiedCamClay +============================== +.. include:: ../../coreComponents/schema/docs/PorousViscoModifiedCamClay.rst + + .. _XML_PressurePorosity: Element: PressurePorosity @@ -2233,6 +2254,27 @@ Datastructure: PorousModifiedCamClay .. include:: ../../coreComponents/schema/docs/PorousModifiedCamClay_other.rst +.. _DATASTRUCTURE_PorousViscoDruckerPrager: + +Datastructure: PorousViscoDruckerPrager +==================================== +.. include:: ../../coreComponents/schema/docs/PorousViscoDruckerPrager_other.rst + + +.. _DATASTRUCTURE_PorousViscoExtendedDruckerPrager: + +Datastructure: PorousViscoExtendedDruckerPrager +==================================== +.. include:: ../../coreComponents/schema/docs/PorousViscoExtendedDruckerPrager_other.rst + + +.. _DATASTRUCTURE_PorousViscoModifiedCamClay: + +Datastructure: PorousViscoModifiedCamClay +==================================== +.. include:: ../../coreComponents/schema/docs/PorousViscoModifiedCamClay_other.rst + + .. _DATASTRUCTURE_PressurePorosity: Datastructure: PressurePorosity From a6a01de751846671008e444a8d6203b121b9447d Mon Sep 17 00:00:00 2001 From: frankfeifan Date: Wed, 17 Jan 2024 11:22:29 -0800 Subject: [PATCH 30/40] Fix the naming of the fluid heat capacity (#2924) --- .../3D_10x10x10_thermalCompressible_base.xml | 2 +- .../singlePhaseFlow/thermalCompressible_2d_base.xml | 2 +- .../egsCollab_thermalFlow/egsCollab_thermalFlow_base.xml | 2 +- .../fractureMatrixThermalFlow_base.xml | 2 +- .../ThermoPoroElastic_consolidation_base.xml | 2 +- .../ThermoPoroElastic_base.xml | 2 +- .../ThermoPoroElastic_efem-edfm_eggModel_small.xml | 2 +- ...asedThermoElasticWellbore_ImperfectInterfaces_base.xml | 2 +- inputFiles/wellbore/CasedThermoElasticWellbore_base.xml | 2 +- inputFiles/wellbore/ThermoPoroElasticWellbore_base.xml | 2 +- integratedTests | 2 +- .../singlefluid/ThermalCompressibleSinglePhaseFluid.cpp | 8 ++++---- .../singlefluid/ThermalCompressibleSinglePhaseFluid.hpp | 4 ++-- .../schema/docs/ThermalCompressibleSinglePhaseFluid.rst | 2 +- src/coreComponents/schema/schema.xsd | 4 ++-- .../fluidFlowTests/testThermalSinglePhaseFlow.cpp | 2 +- 16 files changed, 21 insertions(+), 21 deletions(-) diff --git a/inputFiles/singlePhaseFlow/3D_10x10x10_thermalCompressible_base.xml b/inputFiles/singlePhaseFlow/3D_10x10x10_thermalCompressible_base.xml index 44920972449..33608531c56 100644 --- a/inputFiles/singlePhaseFlow/3D_10x10x10_thermalCompressible_base.xml +++ b/inputFiles/singlePhaseFlow/3D_10x10x10_thermalCompressible_base.xml @@ -80,7 +80,7 @@ compressibility="5e-10" thermalExpansionCoeff="3e-4" viscosibility="0.0" - volumetricHeatCapacity="4.0e3" + specificHeatCapacity="4.0e3" referenceInternalEnergy="1.1e6"/> diff --git a/inputFiles/thermalSinglePhaseFlowFractures/egsCollab_thermalFlow/egsCollab_thermalFlow_base.xml b/inputFiles/thermalSinglePhaseFlowFractures/egsCollab_thermalFlow/egsCollab_thermalFlow_base.xml index 23e4089f3f1..afba24559c9 100644 --- a/inputFiles/thermalSinglePhaseFlowFractures/egsCollab_thermalFlow/egsCollab_thermalFlow_base.xml +++ b/inputFiles/thermalSinglePhaseFlowFractures/egsCollab_thermalFlow/egsCollab_thermalFlow_base.xml @@ -57,7 +57,7 @@ compressibility="1.0e-10" thermalExpansionCoeff="0.0" viscosibility="0.0" - volumetricHeatCapacity="4.2e6" + specificHeatCapacity="4.2e6" referenceInternalEnergy="0.99"/> diff --git a/inputFiles/thermoPoromechanicsFractures/ThermoPoroElastic_base.xml b/inputFiles/thermoPoromechanicsFractures/ThermoPoroElastic_base.xml index fcdaa19a12c..dd727469509 100644 --- a/inputFiles/thermoPoromechanicsFractures/ThermoPoroElastic_base.xml +++ b/inputFiles/thermoPoromechanicsFractures/ThermoPoroElastic_base.xml @@ -28,7 +28,7 @@ compressibility="1.0e-10" thermalExpansionCoeff="0.0" viscosibility="0.0" - volumetricHeatCapacity="1.672e2" + specificHeatCapacity="1.672e2" referenceInternalEnergy="0.001"/> diff --git a/inputFiles/wellbore/CasedThermoElasticWellbore_base.xml b/inputFiles/wellbore/CasedThermoElasticWellbore_base.xml index 41f9e2ef493..133d834fdd3 100644 --- a/inputFiles/wellbore/CasedThermoElasticWellbore_base.xml +++ b/inputFiles/wellbore/CasedThermoElasticWellbore_base.xml @@ -165,7 +165,7 @@ compressibility="5e-10" thermalExpansionCoeff="1e-10" viscosibility="0.0" - volumetricHeatCapacity="1" + specificHeatCapacity="1" referenceInternalEnergy="1"/> diff --git a/inputFiles/wellbore/ThermoPoroElasticWellbore_base.xml b/inputFiles/wellbore/ThermoPoroElasticWellbore_base.xml index 1f04e96db15..7f2318765f8 100644 --- a/inputFiles/wellbore/ThermoPoroElasticWellbore_base.xml +++ b/inputFiles/wellbore/ThermoPoroElasticWellbore_base.xml @@ -100,7 +100,7 @@ compressibility="5e-10" thermalExpansionCoeff="3e-4" viscosibility="0.0" - volumetricHeatCapacity="1" + specificHeatCapacity="1" referenceInternalEnergy="1"/> diff --git a/integratedTests b/integratedTests index 7285cd2e28d..5c11fe9652e 160000 --- a/integratedTests +++ b/integratedTests @@ -1 +1 @@ -Subproject commit 7285cd2e28d97c43f648ab6d61b1c49de153ddb5 +Subproject commit 5c11fe9652ee08ed60fef9d72dde8fa08e57291b diff --git a/src/coreComponents/constitutive/fluid/singlefluid/ThermalCompressibleSinglePhaseFluid.cpp b/src/coreComponents/constitutive/fluid/singlefluid/ThermalCompressibleSinglePhaseFluid.cpp index ed7a92429e2..96ffa8bf5f7 100644 --- a/src/coreComponents/constitutive/fluid/singlefluid/ThermalCompressibleSinglePhaseFluid.cpp +++ b/src/coreComponents/constitutive/fluid/singlefluid/ThermalCompressibleSinglePhaseFluid.cpp @@ -39,10 +39,10 @@ ThermalCompressibleSinglePhaseFluid::ThermalCompressibleSinglePhaseFluid( string setInputFlag( InputFlags::OPTIONAL ). setDescription( "Fluid thermal expansion coefficient. Unit: 1/K" ); - registerWrapper( viewKeyStruct::volumetricHeatCapacityString(), &m_volumetricHeatCapacity ). + registerWrapper( viewKeyStruct::specificHeatCapacityString(), &m_specificHeatCapacity ). setApplyDefaultValue( 0.0 ). setInputFlag( InputFlags::OPTIONAL ). - setDescription( "Fluid volumetric heat capacity. Unit: J/kg/K" ); + setDescription( "Fluid heat capacity. Unit: J/kg/K" ); registerWrapper( viewKeyStruct::referenceTemperatureString(), &m_referenceTemperature ). setApplyDefaultValue( 0.0 ). @@ -83,7 +83,7 @@ void ThermalCompressibleSinglePhaseFluid::postProcessInput() }; checkNonnegative( m_thermalExpansionCoeff, viewKeyStruct::thermalExpansionCoeffString() ); - checkNonnegative( m_volumetricHeatCapacity, viewKeyStruct::volumetricHeatCapacityString() ); + checkNonnegative( m_specificHeatCapacity, viewKeyStruct::specificHeatCapacityString() ); checkNonnegative( m_referenceInternalEnergy, viewKeyStruct::referenceInternalEnergyString() ); // Due to the way update wrapper is currently implemented, we can only support one model type @@ -101,7 +101,7 @@ ThermalCompressibleSinglePhaseFluid::createKernelWrapper() { return KernelWrapper( KernelWrapper::DensRelationType( m_referencePressure, m_referenceTemperature, m_referenceDensity, m_compressibility, -m_thermalExpansionCoeff ), KernelWrapper::ViscRelationType( m_referencePressure, m_referenceViscosity, m_viscosibility ), - KernelWrapper::IntEnergyRelationType( m_referenceTemperature, m_referenceInternalEnergy, m_volumetricHeatCapacity/m_referenceInternalEnergy ), + KernelWrapper::IntEnergyRelationType( m_referenceTemperature, m_referenceInternalEnergy, m_specificHeatCapacity/m_referenceInternalEnergy ), m_density, m_dDensity_dPressure, m_dDensity_dTemperature, diff --git a/src/coreComponents/constitutive/fluid/singlefluid/ThermalCompressibleSinglePhaseFluid.hpp b/src/coreComponents/constitutive/fluid/singlefluid/ThermalCompressibleSinglePhaseFluid.hpp index f3940c72efb..e20130bf9fb 100644 --- a/src/coreComponents/constitutive/fluid/singlefluid/ThermalCompressibleSinglePhaseFluid.hpp +++ b/src/coreComponents/constitutive/fluid/singlefluid/ThermalCompressibleSinglePhaseFluid.hpp @@ -240,7 +240,7 @@ class ThermalCompressibleSinglePhaseFluid : public CompressibleSinglePhaseFluid struct viewKeyStruct : public CompressibleSinglePhaseFluid::viewKeyStruct { static constexpr char const * thermalExpansionCoeffString() { return "thermalExpansionCoeff"; } - static constexpr char const * volumetricHeatCapacityString() { return "volumetricHeatCapacity"; } + static constexpr char const * specificHeatCapacityString() { return "specificHeatCapacity"; } static constexpr char const * referenceTemperatureString() { return "referenceTemperature"; } static constexpr char const * referenceInternalEnergyString() { return "referenceInternalEnergy"; } static constexpr char const * internalEnergyModelTypeString() { return "internalEnergyModelType"; } @@ -256,7 +256,7 @@ class ThermalCompressibleSinglePhaseFluid : public CompressibleSinglePhaseFluid real64 m_thermalExpansionCoeff; /// scalar fluid volumetric heat capacity coefficient - real64 m_volumetricHeatCapacity; + real64 m_specificHeatCapacity; /// reference temperature parameter real64 m_referenceTemperature; diff --git a/src/coreComponents/schema/docs/ThermalCompressibleSinglePhaseFluid.rst b/src/coreComponents/schema/docs/ThermalCompressibleSinglePhaseFluid.rst index 1a465fa0ee0..469050d50fa 100644 --- a/src/coreComponents/schema/docs/ThermalCompressibleSinglePhaseFluid.rst +++ b/src/coreComponents/schema/docs/ThermalCompressibleSinglePhaseFluid.rst @@ -20,13 +20,13 @@ referenceInternalEnergy real64 0.001 Ref referencePressure real64 0 Reference pressure referenceTemperature real64 0 Reference temperature referenceViscosity real64 0.001 Reference fluid viscosity +specificHeatCapacity real64 0 Fluid heat capacity. Unit: J/kg/K thermalExpansionCoeff real64 0 Fluid thermal expansion coefficient. Unit: 1/K viscosibility real64 0 Fluid viscosity exponential coefficient viscosityModelType geos_constitutive_ExponentApproximationType linear | Type of viscosity model. Valid options: | * exponential | * linear | * quadratic -volumetricHeatCapacity real64 0 Fluid volumetric heat capacity. Unit: J/kg/K ======================= =========================================== ======== =================================================================================== diff --git a/src/coreComponents/schema/schema.xsd b/src/coreComponents/schema/schema.xsd index faf2990dedb..a12097730ed 100644 --- a/src/coreComponents/schema/schema.xsd +++ b/src/coreComponents/schema/schema.xsd @@ -4900,6 +4900,8 @@ To neglect hysteresis on this phase, just use the same table name for the draina + + @@ -4909,8 +4911,6 @@ To neglect hysteresis on this phase, just use the same table name for the draina * linear * quadratic--> - - diff --git a/src/coreComponents/unitTests/fluidFlowTests/testThermalSinglePhaseFlow.cpp b/src/coreComponents/unitTests/fluidFlowTests/testThermalSinglePhaseFlow.cpp index 04315a4c1fd..05a369aa969 100644 --- a/src/coreComponents/unitTests/fluidFlowTests/testThermalSinglePhaseFlow.cpp +++ b/src/coreComponents/unitTests/fluidFlowTests/testThermalSinglePhaseFlow.cpp @@ -107,7 +107,7 @@ char const * xmlInput = compressibility="5e-10" thermalExpansionCoeff="7e-4" viscosibility="0.0" - volumetricHeatCapacity="4.5e3" /> + specificHeatCapacity="4.5e3" /> From 1be5e1a747affa1016d1d0a9905273abd13a946c Mon Sep 17 00:00:00 2001 From: tbeltzun <129868353+tbeltzun@users.noreply.github.com> Date: Thu, 18 Jan 2024 04:29:29 +0100 Subject: [PATCH 31/40] generate code coverage reports (#2738) * use BLT code coverage * update submodules. update tpl tag --------- Co-authored-by: Randolph Settgast Co-authored-by: TotoGaz <49004943+TotoGaz@users.noreply.github.com> --- .devcontainer/devcontainer.json | 2 +- .github/workflows/build_and_test.yml | 18 ++++++++++- .github/workflows/ci_tests.yml | 18 ++++++++++- README.md | 2 ++ host-configs/LBL/cori-gcc@8.1.0.cmake | 1 - host-configs/LBL/cori-intel.cmake | 1 - scripts/ci_build_and_test_in_container.sh | 30 +++++++++++++++---- src/cmake/GeosxMacros.cmake | 6 ++++ src/cmake/GeosxOptions.cmake | 8 ++--- src/coreComponents/CMakeLists.txt | 3 +- src/coreComponents/LvArray | 2 +- .../codingUtilities/CMakeLists.txt | 2 -- src/coreComponents/common/CMakeLists.txt | 2 -- .../constitutive/CMakeLists.txt | 3 -- .../constitutive/unitTests/CMakeLists.txt | 2 +- .../dataRepository/CMakeLists.txt | 2 -- .../denseLinearAlgebra/CMakeLists.txt | 1 - .../discretizationMethods/CMakeLists.txt | 1 - src/coreComponents/events/CMakeLists.txt | 1 - .../fieldSpecification/CMakeLists.txt | 1 - src/coreComponents/fileIO/CMakeLists.txt | 1 - .../fileIO/coupling/hdf5_interface | 2 +- .../finiteElement/CMakeLists.txt | 1 - .../finiteVolume/CMakeLists.txt | 1 - src/coreComponents/functions/CMakeLists.txt | 1 - .../linearAlgebra/CMakeLists.txt | 1 - .../mainInterface/CMakeLists.txt | 1 - src/coreComponents/math/CMakeLists.txt | 1 - src/coreComponents/mesh/CMakeLists.txt | 1 - .../physicsSolvers/CMakeLists.txt | 1 - src/coreComponents/schema/CMakeLists.txt | 1 - src/docs/sphinx/buildGuide/BuildProcess.rst | 2 +- 32 files changed, 75 insertions(+), 45 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 9ef94fcc44b..e34ff40c1da 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -2,7 +2,7 @@ "build": { "dockerfile": "Dockerfile", "args": { - "GEOS_TPL_TAG": "251-99" + "GEOS_TPL_TAG": "255-118" } }, "runArgs": [ diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index dee5f516a81..f9b423b95a2 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -12,6 +12,10 @@ on: CMAKE_BUILD_TYPE: required: true type: string + CODE_COVERAGE: + required: false + type: boolean + default: false DOCKER_IMAGE_TAG: required: true type: string @@ -127,6 +131,10 @@ jobs: script_args+=(--cmake-build-type ${{ inputs.CMAKE_BUILD_TYPE }}) script_args+=(${{ inputs.BUILD_AND_TEST_CLI_ARGS }}) + if ${{ inputs.CODE_COVERAGE }} == 'true'; then + script_args+=(--code-coverage) + fi + # In case of integrated tests run, we still want to send the results to the cloud for inspection. # While for standard build (if even possible), pushing a failed build would be pointless. # GHA set `-e` to bash scripts by default to fail asap, @@ -148,5 +156,13 @@ jobs: echo "Download the bundle at https://storage.googleapis.com/${{ inputs.GCP_BUCKET }}/${DATA_BASENAME}" fi fi - exit ${EXIT_STATUS} + + - name: Upload coverage to Codecov + if: inputs.CODE_COVERAGE + uses: codecov/codecov-action@v3 + with: + files: geos_coverage.info.cleaned + fail_ci_if_error: true + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/ci_tests.yml b/.github/workflows/ci_tests.yml index 4863884a573..1b94079a583 100644 --- a/.github/workflows/ci_tests.yml +++ b/.github/workflows/ci_tests.yml @@ -189,7 +189,23 @@ jobs: ENABLE_TRILINOS: OFF GCP_BUCKET: geosx/integratedTests RUNS_ON: ubuntu-22.04 - + + code_coverage: + needs: + - is_pull_request_a_draft + uses: ./.github/workflows/build_and_test.yml + secrets: inherit + with: + BUILD_AND_TEST_CLI_ARGS: "--no-run-unit-tests" + CMAKE_BUILD_TYPE: Debug + CODE_COVERAGE: true + DOCKER_IMAGE_TAG: ${{ needs.is_pull_request_a_draft.outputs.DOCKER_IMAGE_TAG }} + DOCKER_REPOSITORY: geosx/ubuntu22.04-gcc11 + ENABLE_HYPRE: ON + ENABLE_TRILINOS: OFF + GCP_BUCKET: geosx/ubuntu22.04-gcc11 + RUNS_ON: ubuntu-22.04 + # If the 'ci: ready to be merged' PR label is found, the cuda jobs run immediately along side linux jobs. # Note: CUDA jobs should only be run if PR is ready to merge. cuda_builds: diff --git a/README.md b/README.md index f4561b7b60c..d634a08ef09 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ [![DOI](https://zenodo.org/badge/131810578.svg)](https://zenodo.org/badge/latestdoi/131810578) +[![codecov](https://codecov.io/github/GEOS-DEV/GEOS/graph/badge.svg?token=0VTEHPQG58)](https://codecov.io/github/GEOS-DEV/GEOS) + Welcome to the GEOS project! ----------------------------- GEOS is a simulation framework for modeling coupled flow, transport, and geomechanics diff --git a/host-configs/LBL/cori-gcc@8.1.0.cmake b/host-configs/LBL/cori-gcc@8.1.0.cmake index 8b46e4e6a08..8b6f5af6517 100644 --- a/host-configs/LBL/cori-gcc@8.1.0.cmake +++ b/host-configs/LBL/cori-gcc@8.1.0.cmake @@ -24,7 +24,6 @@ set(MPI_Fortran_COMPILER ${MPI_HOME}/bin/mpifort CACHE PATH "") set(MPIEXEC /usr/bin/srun CACHE PATH "") set(MPIEXEC_NUMPROC_FLAG "-n" CACHE STRING "") -set(GEOSX_ENABLE_FPE OFF CACHE BOOL "" FORCE) set(GEOSX_TPL_DIR "/global/project/projectdirs/m1411/GEOSX/tpls/install-cori-gcc\@8.1.0-release-24-07-20" CACHE PATH "" ) diff --git a/host-configs/LBL/cori-intel.cmake b/host-configs/LBL/cori-intel.cmake index 87b03329eed..85044651337 100644 --- a/host-configs/LBL/cori-intel.cmake +++ b/host-configs/LBL/cori-intel.cmake @@ -24,7 +24,6 @@ set(MPI_Fortran_COMPILER "ftn" CACHE PATH "" FORCE) set(MPIEXEC "/usr/bin/srun" CACHE PATH "") set(MPIEXEC_NUMPROC_FLAG "-n" CACHE STRING "") -set(GEOSX_ENABLE_FPE OFF CACHE BOOL "" FORCE) set(GEOSX_TPL_DIR "/global/project/projectdirs/m1411/GEOSX/tpls/install-cori-intel-release-22-07-20" CACHE PATH "" ) set(GEOSX_LINK_PREPEND_FLAG "-Wl,--whole-archive" CACHE STRING "" FORCE) diff --git a/scripts/ci_build_and_test_in_container.sh b/scripts/ci_build_and_test_in_container.sh index 0c504566933..a77e3fc0ae2 100755 --- a/scripts/ci_build_and_test_in_container.sh +++ b/scripts/ci_build_and_test_in_container.sh @@ -34,6 +34,8 @@ Usage: $0 Request for the build of geos only. --cmake-build-type ... One of Debug, Release, RelWithDebInfo and MinSizeRel. Forwarded to CMAKE_BUILD_TYPE. + --code-coverage + run a code build and test. --data-basename output.tar.gz If some data needs to be extracted from the build, the argument will define the tarball. Has to be a `tar.gz`. --exchange-dir /path/to/exchange @@ -64,7 +66,7 @@ exit 1 or_die cd $(dirname $0)/.. # Parsing using getopt -args=$(or_die getopt -a -o h --long build-exe-only,cmake-build-type:,data-basename:,exchange-dir:,host-config:,install-dir-basename:,no-install-schema,no-run-unit-tests,repository:,run-integrated-tests,sccache-credentials:,test-code-style,test-documentation,help -- "$@") +args=$(or_die getopt -a -o h --long build-exe-only,cmake-build-type:,code-coverage,data-basename:,exchange-dir:,host-config:,install-dir-basename:,no-install-schema,no-run-unit-tests,repository:,run-integrated-tests,sccache-credentials:,test-code-style,test-documentation,help -- "$@") # Variables with default values BUILD_EXE_ONLY=false @@ -74,6 +76,7 @@ RUN_UNIT_TESTS=true RUN_INTEGRATED_TESTS=false TEST_CODE_STYLE=false TEST_DOCUMENTATION=false +CODE_COVERAGE=false eval set -- ${args} while : @@ -101,6 +104,7 @@ do --no-run-unit-tests) RUN_UNIT_TESTS=false; shift;; --repository) GEOS_SRC_DIR=$2; shift 2;; --run-integrated-tests) RUN_INTEGRATED_TESTS=true; shift;; + --code-coverage) CODE_COVERAGE=true; shift;; --sccache-credentials) SCCACHE_CREDS=$2; shift 2;; --test-code-style) TEST_CODE_STYLE=true; shift;; --test-documentation) TEST_DOCUMENTATION=true; shift;; @@ -153,6 +157,14 @@ if [[ "${RUN_INTEGRATED_TESTS}" = true ]]; then ATS_CMAKE_ARGS="-DATS_ARGUMENTS=\"--machine openmpi --ats openmpi_mpirun=/usr/bin/mpirun --ats openmpi_args=--allow-run-as-root --ats openmpi_procspernode=2 --ats openmpi_maxprocs=2\" -DPython3_ROOT_DIR=${ATS_PYTHON_HOME}" fi + +if [[ "${CODE_COVERAGE}" = true ]]; then + or_die apt-get update + or_die apt-get install -y lcov +fi + + + # The -DBLT_MPI_COMMAND_APPEND="--allow-run-as-root;--oversubscribe" option is added for OpenMPI. # # OpenMPI prevents from running as `root` user by default. @@ -175,6 +187,7 @@ or_die python3 scripts/config-build.py \ --ninja \ -DBLT_MPI_COMMAND_APPEND='"--allow-run-as-root;--oversubscribe"' \ -DGEOSX_INSTALL_SCHEMA=${GEOSX_INSTALL_SCHEMA} \ + -DENABLE_COVERAGE=$([[ "${CODE_COVERAGE}" = true ]] && echo 1 || echo 0) \ ${SCCACHE_CMAKE_ARGS} \ ${ATS_CMAKE_ARGS} @@ -207,6 +220,16 @@ else fi fi +if [[ ! -z "${SCCACHE_CREDS}" ]]; then + echo "sccache post-build state" + or_die ${SCCACHE} --show-adv-stats +fi + +if [[ "${CODE_COVERAGE}" = true ]]; then + or_die ninja coreComponents_coverage + cp -r ${GEOSX_BUILD_DIR}/coreComponents_coverage.info.cleaned ${GEOS_SRC_DIR}/geos_coverage.info.cleaned +fi + # Run the unit tests (excluding previously ran checks). if [[ "${RUN_UNIT_TESTS}" = true ]]; then or_die ctest --output-on-failure -E "testUncrustifyCheck|testDoxygenCheck" @@ -235,11 +258,6 @@ if [[ "${RUN_INTEGRATED_TESTS}" = true ]]; then or_die gzip ${DATA_EXCHANGE_DIR}/${DATA_BASENAME_WE}.tar fi -if [[ ! -z "${SCCACHE_CREDS}" ]]; then - echo "sccache final state" - or_die ${SCCACHE} --show-adv-stats -fi - # If we're here, either everything went OK or we have to deal with the integrated tests manually. if [[ ! -z "${INTEGRATED_TEST_EXIT_STATUS+x}" ]]; then echo "Exiting the build process with exit status ${INTEGRATED_TEST_EXIT_STATUS} from the integrated tests." diff --git a/src/cmake/GeosxMacros.cmake b/src/cmake/GeosxMacros.cmake index 81264a61c57..af5105678ee 100644 --- a/src/cmake/GeosxMacros.cmake +++ b/src/cmake/GeosxMacros.cmake @@ -53,6 +53,12 @@ macro( geosx_add_code_checks ) UNCRUSTIFY_CFG_FILE ${PROJECT_SOURCE_DIR}/uncrustify.cfg ) endif() + if (ENABLE_COVERAGE) + blt_add_code_coverage_target( NAME ${arg_PREFIX}_coverage + RUNNER ctest -E 'blt_gtest_smoke|blt_mpi_smoke|testUncrustifyCheck|testDoxygenCheck' + SOURCE_DIRECTORIES ${PROJECT_SOURCE_DIR}/coreComponents ) + endif() + endmacro( geosx_add_code_checks ) ##------------------------------------------------------------------------------ diff --git a/src/cmake/GeosxOptions.cmake b/src/cmake/GeosxOptions.cmake index b1530b390cc..c0dd7d5075d 100644 --- a/src/cmake/GeosxOptions.cmake +++ b/src/cmake/GeosxOptions.cmake @@ -4,10 +4,8 @@ message( "CMAKE_SYSTEM_NAME = ${CMAKE_SYSTEM_NAME}" ) message( "CMAKE_HOST_APPLE = ${CMAKE_HOST_APPLE}" ) ### OPTIONS ### -option( GEOSX_ENABLE_FPE "" ON ) option( GEOS_ENABLE_TESTS "" ON ) - -option( ENABLE_CALIPER "" OFF ) +option( ENABLE_CALIPER "Enables Caliper instrumentation" OFF ) option( ENABLE_MATHPRESSO "" ON ) @@ -122,8 +120,8 @@ message( "CMAKE_CXX_COMPILER_ID = ${CMAKE_CXX_COMPILER_ID}" ) blt_append_custom_compiler_flag( FLAGS_VAR CMAKE_CXX_FLAGS DEFAULT "${OpenMP_CXX_FLAGS}" ) blt_append_custom_compiler_flag( FLAGS_VAR CMAKE_CXX_FLAGS - GNU "-Wall -Wextra -Wpedantic -pedantic-errors -Wshadow -Wfloat-equal -Wcast-align -Wcast-qual" - CLANG "-Wall -Wextra -Wpedantic -pedantic-errors -Wshadow -Wfloat-equal -Wno-cast-align -Wcast-qual" + GNU "-Wpedantic -pedantic-errors -Wshadow -Wfloat-equal -Wcast-align -Wcast-qual" + CLANG "-Wpedantic -pedantic-errors -Wshadow -Wfloat-equal -Wno-cast-align -Wcast-qual" ) blt_append_custom_compiler_flag( FLAGS_VAR CMAKE_CXX_FLAGS_DEBUG diff --git a/src/coreComponents/CMakeLists.txt b/src/coreComponents/CMakeLists.txt index 2de7ccd0068..e77afed5fc6 100644 --- a/src/coreComponents/CMakeLists.txt +++ b/src/coreComponents/CMakeLists.txt @@ -107,8 +107,7 @@ if ( TARGET geosx_core ) endif() geosx_add_code_checks( PREFIX coreComponents - EXCLUDES cmake - src/coreComponents/constitutive/PVTPackage ) + EXCLUDES cmake constitutive/PVTPackage ) if( ENABLE_UNCRUSTIFY ) add_test( NAME testUncrustifyCheck diff --git a/src/coreComponents/LvArray b/src/coreComponents/LvArray index e6b7f90c1aa..52aac7fd723 160000 --- a/src/coreComponents/LvArray +++ b/src/coreComponents/LvArray @@ -1 +1 @@ -Subproject commit e6b7f90c1aaa53992962ce6510dc000cc06c1943 +Subproject commit 52aac7fd7233365a01206634f4d8b1b2b5be8e15 diff --git a/src/coreComponents/codingUtilities/CMakeLists.txt b/src/coreComponents/codingUtilities/CMakeLists.txt index 784e4aa0455..c51ea1ca849 100644 --- a/src/coreComponents/codingUtilities/CMakeLists.txt +++ b/src/coreComponents/codingUtilities/CMakeLists.txt @@ -32,8 +32,6 @@ set_source_files_properties( Parsing.cpp PROPERTIES LANGUAGE CXX ) target_include_directories( codingUtilities PUBLIC ${CMAKE_SOURCE_DIR}/coreComponents ) -geosx_add_code_checks(PREFIX codingUtilities ) - if( GEOS_ENABLE_TESTS ) add_subdirectory(tests) diff --git a/src/coreComponents/common/CMakeLists.txt b/src/coreComponents/common/CMakeLists.txt index fd5edeee558..f8a0ca5345d 100644 --- a/src/coreComponents/common/CMakeLists.txt +++ b/src/coreComponents/common/CMakeLists.txt @@ -79,8 +79,6 @@ blt_add_library( NAME common target_include_directories( common PUBLIC ${CMAKE_BINARY_DIR}/include ) target_include_directories( common PUBLIC ${CMAKE_SOURCE_DIR}/coreComponents ) -geosx_add_code_checks(PREFIX common ) - if( GEOS_ENABLE_TESTS ) add_subdirectory( unitTests ) endif() diff --git a/src/coreComponents/constitutive/CMakeLists.txt b/src/coreComponents/constitutive/CMakeLists.txt index 9e2ddd521c9..47e573b6f13 100644 --- a/src/coreComponents/constitutive/CMakeLists.txt +++ b/src/coreComponents/constitutive/CMakeLists.txt @@ -315,6 +315,3 @@ target_include_directories( constitutive PUBLIC ${CMAKE_SOURCE_DIR}/coreComponen if( GEOS_ENABLE_TESTS ) add_subdirectory( unitTests ) endif( ) - -geosx_add_code_checks( PREFIX constitutive - EXCLUDES PVTPackage ) diff --git a/src/coreComponents/constitutive/unitTests/CMakeLists.txt b/src/coreComponents/constitutive/unitTests/CMakeLists.txt index 9c50765fa2c..add6c460a3f 100644 --- a/src/coreComponents/constitutive/unitTests/CMakeLists.txt +++ b/src/coreComponents/constitutive/unitTests/CMakeLists.txt @@ -12,7 +12,7 @@ set( gtest_geosx_tests testCubicEOS.cpp testRachfordRice.cpp ) -set( dependencyList gtest constitutive ${parallelDeps} ) +set( dependencyList gtest blas lapack constitutive ${parallelDeps} ) if( ENABLE_CUDA_NVTOOLSEXT ) list( APPEND dependencyList CUDA::nvToolsExt ) diff --git a/src/coreComponents/dataRepository/CMakeLists.txt b/src/coreComponents/dataRepository/CMakeLists.txt index bea120432a7..1b882eb42e5 100644 --- a/src/coreComponents/dataRepository/CMakeLists.txt +++ b/src/coreComponents/dataRepository/CMakeLists.txt @@ -59,8 +59,6 @@ blt_add_library( NAME dataRepository target_include_directories( dataRepository PUBLIC ${CMAKE_SOURCE_DIR}/coreComponents ) -geosx_add_code_checks( PREFIX dataRepository ) - if( GEOS_ENABLE_TESTS ) add_subdirectory( unitTests ) diff --git a/src/coreComponents/denseLinearAlgebra/CMakeLists.txt b/src/coreComponents/denseLinearAlgebra/CMakeLists.txt index 9077e6f68d2..3edb50f95d5 100644 --- a/src/coreComponents/denseLinearAlgebra/CMakeLists.txt +++ b/src/coreComponents/denseLinearAlgebra/CMakeLists.txt @@ -22,4 +22,3 @@ if( GEOS_ENABLE_TESTS ) add_subdirectory( unitTests ) endif( ) -geosx_add_code_checks( PREFIX denseLinearAlgebra ) diff --git a/src/coreComponents/discretizationMethods/CMakeLists.txt b/src/coreComponents/discretizationMethods/CMakeLists.txt index 515347cd410..e038df90710 100644 --- a/src/coreComponents/discretizationMethods/CMakeLists.txt +++ b/src/coreComponents/discretizationMethods/CMakeLists.txt @@ -22,4 +22,3 @@ blt_add_library( NAME discretizationMethods target_include_directories( discretizationMethods PUBLIC ${CMAKE_SOURCE_DIR}/coreComponents ) -geosx_add_code_checks( PREFIX discretizationMethods ) diff --git a/src/coreComponents/events/CMakeLists.txt b/src/coreComponents/events/CMakeLists.txt index 87311ad735c..8e87a2f62c6 100644 --- a/src/coreComponents/events/CMakeLists.txt +++ b/src/coreComponents/events/CMakeLists.txt @@ -35,4 +35,3 @@ blt_add_library( NAME events target_include_directories( events PUBLIC ${CMAKE_SOURCE_DIR}/coreComponents ) -geosx_add_code_checks( PREFIX events ) diff --git a/src/coreComponents/fieldSpecification/CMakeLists.txt b/src/coreComponents/fieldSpecification/CMakeLists.txt index a3c2e1d37e0..10ad2e8ec1a 100644 --- a/src/coreComponents/fieldSpecification/CMakeLists.txt +++ b/src/coreComponents/fieldSpecification/CMakeLists.txt @@ -37,4 +37,3 @@ blt_add_library( NAME fieldSpecification target_include_directories( fieldSpecification PUBLIC ${CMAKE_SOURCE_DIR}/coreComponents ) -geosx_add_code_checks( PREFIX fieldSpecification ) diff --git a/src/coreComponents/fileIO/CMakeLists.txt b/src/coreComponents/fileIO/CMakeLists.txt index 2d7fe04730c..c479846cc79 100644 --- a/src/coreComponents/fileIO/CMakeLists.txt +++ b/src/coreComponents/fileIO/CMakeLists.txt @@ -88,5 +88,4 @@ blt_add_library( NAME fileIO target_include_directories( fileIO PUBLIC ${CMAKE_SOURCE_DIR}/coreComponents ) -geosx_add_code_checks(PREFIX fileIO ) diff --git a/src/coreComponents/fileIO/coupling/hdf5_interface b/src/coreComponents/fileIO/coupling/hdf5_interface index 5136554439e..7ee534586a6 160000 --- a/src/coreComponents/fileIO/coupling/hdf5_interface +++ b/src/coreComponents/fileIO/coupling/hdf5_interface @@ -1 +1 @@ -Subproject commit 5136554439e791dc5e948f2a74ede31c4c697ef5 +Subproject commit 7ee534586a6ea995532eb4e3cfc7da4e5ccff64a diff --git a/src/coreComponents/finiteElement/CMakeLists.txt b/src/coreComponents/finiteElement/CMakeLists.txt index da3e81afabb..d8ade51e37e 100644 --- a/src/coreComponents/finiteElement/CMakeLists.txt +++ b/src/coreComponents/finiteElement/CMakeLists.txt @@ -50,7 +50,6 @@ blt_add_library( NAME finiteElement target_include_directories( finiteElement PUBLIC ${CMAKE_SOURCE_DIR}/coreComponents ) -geosx_add_code_checks( PREFIX finiteElement ) if( GEOS_ENABLE_TESTS ) add_subdirectory( unitTests ) diff --git a/src/coreComponents/finiteVolume/CMakeLists.txt b/src/coreComponents/finiteVolume/CMakeLists.txt index e5ba8874456..9fc47c9411b 100644 --- a/src/coreComponents/finiteVolume/CMakeLists.txt +++ b/src/coreComponents/finiteVolume/CMakeLists.txt @@ -51,4 +51,3 @@ blt_add_library( NAME finiteVolume target_include_directories( finiteVolume PUBLIC ${CMAKE_SOURCE_DIR}/coreComponents ) -geosx_add_code_checks( PREFIX finiteVolume ) diff --git a/src/coreComponents/functions/CMakeLists.txt b/src/coreComponents/functions/CMakeLists.txt index 8ad064d8df8..f28de10f61f 100644 --- a/src/coreComponents/functions/CMakeLists.txt +++ b/src/coreComponents/functions/CMakeLists.txt @@ -45,4 +45,3 @@ target_include_directories( functions PUBLIC ${CMAKE_SOURCE_DIR}/coreComponents if( GEOS_ENABLE_TESTS ) add_subdirectory( unitTests ) endif() -geosx_add_code_checks( PREFIX functions ) diff --git a/src/coreComponents/linearAlgebra/CMakeLists.txt b/src/coreComponents/linearAlgebra/CMakeLists.txt index 8e86dbfdd05..1a1bc7ca108 100644 --- a/src/coreComponents/linearAlgebra/CMakeLists.txt +++ b/src/coreComponents/linearAlgebra/CMakeLists.txt @@ -162,4 +162,3 @@ if( GEOS_ENABLE_TESTS ) add_subdirectory( unitTests ) endif( ) -geosx_add_code_checks( PREFIX linearAlgebra ) diff --git a/src/coreComponents/mainInterface/CMakeLists.txt b/src/coreComponents/mainInterface/CMakeLists.txt index bba7d519c99..70cbacd9e61 100644 --- a/src/coreComponents/mainInterface/CMakeLists.txt +++ b/src/coreComponents/mainInterface/CMakeLists.txt @@ -31,4 +31,3 @@ add_dependencies( mainInterface generate_version ) target_include_directories( mainInterface PUBLIC ${CMAKE_SOURCE_DIR}/coreComponents ) -geosx_add_code_checks( PREFIX mainInterface ) diff --git a/src/coreComponents/math/CMakeLists.txt b/src/coreComponents/math/CMakeLists.txt index 3dd93cfc12a..290f23808db 100644 --- a/src/coreComponents/math/CMakeLists.txt +++ b/src/coreComponents/math/CMakeLists.txt @@ -14,4 +14,3 @@ blt_add_library( NAME math target_include_directories( math INTERFACE ${CMAKE_SOURCE_DIR}/coreComponents ) -geosx_add_code_checks( PREFIX math ) diff --git a/src/coreComponents/mesh/CMakeLists.txt b/src/coreComponents/mesh/CMakeLists.txt index 32c815d0c34..52b2fd69b60 100644 --- a/src/coreComponents/mesh/CMakeLists.txt +++ b/src/coreComponents/mesh/CMakeLists.txt @@ -192,4 +192,3 @@ if( GEOS_ENABLE_TESTS ) add_subdirectory( unitTests ) endif( ) -geosx_add_code_checks( PREFIX mesh ) diff --git a/src/coreComponents/physicsSolvers/CMakeLists.txt b/src/coreComponents/physicsSolvers/CMakeLists.txt index 0d2514f94bd..6cc22d1a431 100644 --- a/src/coreComponents/physicsSolvers/CMakeLists.txt +++ b/src/coreComponents/physicsSolvers/CMakeLists.txt @@ -229,5 +229,4 @@ if( externalComponentDeps ) target_include_directories( physicsSolvers PUBLIC ${CMAKE_SOURCE_DIR}/externalComponents ) endif() -geosx_add_code_checks( PREFIX physicsSolvers ) diff --git a/src/coreComponents/schema/CMakeLists.txt b/src/coreComponents/schema/CMakeLists.txt index cebb95e9551..ccee0a436dd 100644 --- a/src/coreComponents/schema/CMakeLists.txt +++ b/src/coreComponents/schema/CMakeLists.txt @@ -24,4 +24,3 @@ blt_add_library( NAME schema target_include_directories( schema PUBLIC ${CMAKE_SOURCE_DIR}/coreComponents) -geosx_add_code_checks(PREFIX schema ) diff --git a/src/docs/sphinx/buildGuide/BuildProcess.rst b/src/docs/sphinx/buildGuide/BuildProcess.rst index e01da32d298..37a5d66d9d3 100644 --- a/src/docs/sphinx/buildGuide/BuildProcess.rst +++ b/src/docs/sphinx/buildGuide/BuildProcess.rst @@ -73,8 +73,8 @@ Option Default Explanation ``ENABLE_WARNINGS_AS_ERRORS`` ``ON`` Treat all warnings as errors ``ENABLE_PVTPackage`` ``ON`` Enable PVTPackage library (required for compositional flow runs) ``ENABLE_TOTALVIEW_OUTPUT`` ``OFF`` Enables TotalView debugger custom view of GEOS data structures +``ENABLE_COV`` ``OFF`` Enables code coverage ``GEOS_ENABLE_TESTS`` ``ON`` Enables unit testing targets -``GEOSX_ENABLE_FPE`` ``ON`` Enable floating point exception trapping ``GEOSX_LA_INTERFACE`` ``Hypre`` Choiсe of Linear Algebra backend (Hypre/Petsc/Trilinos) ``GEOSX_BUILD_OBJ_LIBS`` ``ON`` Use CMake Object Libraries build ``GEOSX_BUILD_SHARED_LIBS`` ``OFF`` Build ``geosx_core`` as a shared library instead of static From 4ad8f0b5847b877e7cd4db3e28e57f0a88a3367f Mon Sep 17 00:00:00 2001 From: Randolph Settgast Date: Wed, 17 Jan 2024 21:30:19 -0800 Subject: [PATCH 32/40] Add action trigger for push to develop (#2943) --- .github/workflows/ci_tests.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci_tests.yml b/.github/workflows/ci_tests.yml index 1b94079a583..0d6ecd14835 100644 --- a/.github/workflows/ci_tests.yml +++ b/.github/workflows/ci_tests.yml @@ -1,5 +1,10 @@ name: GEOS CI -on: pull_request + +on: + push: + branches: + - develop + pull_request: # Cancels in-progress workflows for a PR when updated concurrency: From 796a458daa72b16bd3196022020ce53689ecd5f9 Mon Sep 17 00:00:00 2001 From: Christopher Sherman Date: Thu, 18 Jan 2024 10:46:22 -0800 Subject: [PATCH 33/40] Migrate python packages (#2896) * Migrating python packages to a new repo --- integratedTests | 2 +- scripts/setupPythonEnvironment.bash | 60 ++- src/CMakeLists.txt | 59 +++ src/coreComponents/CMakeLists.txt | 3 +- .../fileIO/doc/InputXMLFiles.rst | 2 +- src/coreComponents/python/.gitignore | 3 - src/coreComponents/python/CMakeLists.txt | 78 --- .../geosx_mesh_doctor/checks/__init__.py | 1 - .../checks/check_fractures.py | 178 ------- .../checks/collocated_nodes.py | 78 --- .../checks/element_volumes.py | 82 --- .../checks/fix_elements_orderings.py | 66 --- .../geosx_mesh_doctor/checks/generate_cube.py | 160 ------ .../checks/generate_fractures.py | 482 ------------------ .../checks/generate_global_ids.py | 68 --- .../geosx_mesh_doctor/checks/non_conformal.py | 432 ---------------- .../geosx_mesh_doctor/checks/reorient_mesh.py | 178 ------- .../checks/self_intersecting_elements.py | 92 ---- .../checks/supported_elements.py | 163 ------ .../checks/triangle_distance.py | 186 ------- .../checks/vtk_polyhedron.py | 212 -------- .../geosx_mesh_doctor/checks/vtk_utils.py | 143 ------ .../modules/geosx_mesh_doctor/mesh_doctor.py | 35 -- .../geosx_mesh_doctor/parsing/__init__.py | 21 - .../parsing/check_fractures_parsing.py | 1 - .../geosx_mesh_doctor/parsing/cli_parsing.py | 73 --- .../parsing/collocated_nodes_parsing.py | 49 -- .../parsing/element_volumes_parsing.py | 34 -- .../parsing/fix_elements_orderings_parsing.py | 84 --- .../parsing/generate_cube_parsing.py | 87 ---- .../parsing/generate_fractures_parsing.py | 66 --- .../parsing/generate_global_ids_parsing.py | 57 --- .../parsing/non_conformal_parsing.py | 48 -- .../self_intersecting_elements_parsing.py | 36 -- .../parsing/supported_elements_parsing.py | 49 -- .../parsing/vtk_output_parsing.py | 45 -- .../modules/geosx_mesh_doctor/pyproject.toml | 15 - .../modules/geosx_mesh_doctor/register.py | 72 --- .../geosx_mesh_doctor/requirements.txt | 4 - .../python/modules/geosx_mesh_doctor/setup.py | 3 - .../tests/test_cli_parsing.py | 72 --- .../tests/test_collocated_nodes.py | 76 --- .../tests/test_element_volumes.py | 48 -- .../tests/test_generate_cube.py | 24 - .../tests/test_generate_fractures.py | 262 ---------- .../tests/test_generate_global_ids.py | 32 -- .../tests/test_non_conformal.py | 67 --- .../tests/test_reorient_mesh.py | 109 ---- .../tests/test_self_intersecting_elements.py | 50 -- .../tests/test_supported_elements.py | 125 ----- .../tests/test_triangle_distance.py | 178 ------- .../geosx_mesh_tools/__init__.py | 0 .../geosx_mesh_tools/abaqus_converter.py | 170 ------ .../geosx_mesh_tools/main.py | 51 -- .../geosx_mesh_tools/py.typed | 0 .../geosx_mesh_tools_package/pyproject.toml | 8 - .../geosx_mesh_tools_package/setup.cfg | 22 - .../geosx_xml_tools_package/.gitignore | 2 - .../geosx_xml_tools/__init__.py | 3 - .../geosx_xml_tools/attribute_coverage.py | 187 ------- .../geosx_xml_tools/command_line_parsers.py | 88 ---- .../geosx_xml_tools/main.py | 163 ------ .../geosx_xml_tools/py.typed | 0 .../geosx_xml_tools/regex_tools.py | 78 --- .../geosx_xml_tools/table_generator.py | 75 --- .../geosx_xml_tools/tests/__init__.py | 0 .../tests/generate_test_xml.py | 374 -------------- .../geosx_xml_tools/tests/test_manager.py | 173 ------- .../geosx_xml_tools/unit_manager.py | 151 ------ .../geosx_xml_tools/xml_formatter.py | 205 -------- .../geosx_xml_tools/xml_processor.py | 321 ------------ .../geosx_xml_tools/xml_redundancy_check.py | 98 ---- .../geosx_xml_tools_package/pyproject.toml | 8 - .../modules/geosx_xml_tools_package/setup.cfg | 28 - .../hdf5_wrapper/__init__.py | 1 - .../hdf5_wrapper/py.typed | 0 .../hdf5_wrapper/use_example.py | 89 ---- .../hdf5_wrapper/wrapper.py | 206 -------- .../hdf5_wrapper/wrapper_tests.py | 124 ----- .../hdf5_wrapper_package/pyproject.toml | 8 - .../modules/hdf5_wrapper_package/setup.cfg | 23 - .../pygeosx_tools/__init__.py | 0 .../pygeosx_tools/file_io.py | 86 ---- .../pygeosx_tools/mesh_interpolation.py | 110 ---- .../pygeosx_tools/py.typed | 0 .../pygeosx_tools/well_log.py | 96 ---- .../pygeosx_tools/wrapper.py | 406 --------------- .../pygeosx_tools_package/pyproject.toml | 8 - .../modules/pygeosx_tools_package/setup.cfg | 20 - .../timehistory_package/pyproject.toml | 8 - .../modules/timehistory_package/setup.cfg | 16 - .../timehistory/__init__.py | 1 - .../timehistory/plot_time_history.py | 157 ------ .../python/visitMacros/visitVTKConversion.py | 68 --- src/docs/sphinx/QuickStart.rst | 2 +- .../kgdToughnessDominated/Example.rst | 2 +- .../kgdValidation/Example.rst | 2 +- .../kgdViscosityDominated/Example.rst | 2 +- .../pennyFracToughnessDominated/Example.rst | 2 +- .../pennyFracViscosityDominated/Example.rst | 2 +- .../pknFracViscosityDominated/Example.rst | 2 +- .../sphinx/pythonTools/geosx_mesh_tools.rst | 51 -- .../sphinx/pythonTools/geosx_xml_tools.rst | 82 --- src/docs/sphinx/pythonTools/hdf5_wrapper.rst | 60 --- src/docs/sphinx/pythonTools/mesh_doctor.rst | 124 ----- src/docs/sphinx/pythonTools/pygeosx_tools.rst | 24 - src/docs/sphinx/pythonTools/pythonAPI.rst | 54 -- src/docs/sphinx/pythonTools/timehistory.rst | 6 - src/docs/sphinx/requirements.txt | 5 - src/docs/sphinx/userGuide/Index.rst | 2 + src/index.rst | 34 +- 111 files changed, 149 insertions(+), 8489 deletions(-) delete mode 100644 src/coreComponents/python/.gitignore delete mode 100644 src/coreComponents/python/CMakeLists.txt delete mode 100644 src/coreComponents/python/modules/geosx_mesh_doctor/checks/__init__.py delete mode 100644 src/coreComponents/python/modules/geosx_mesh_doctor/checks/check_fractures.py delete mode 100644 src/coreComponents/python/modules/geosx_mesh_doctor/checks/collocated_nodes.py delete mode 100644 src/coreComponents/python/modules/geosx_mesh_doctor/checks/element_volumes.py delete mode 100644 src/coreComponents/python/modules/geosx_mesh_doctor/checks/fix_elements_orderings.py delete mode 100644 src/coreComponents/python/modules/geosx_mesh_doctor/checks/generate_cube.py delete mode 100644 src/coreComponents/python/modules/geosx_mesh_doctor/checks/generate_fractures.py delete mode 100644 src/coreComponents/python/modules/geosx_mesh_doctor/checks/generate_global_ids.py delete mode 100644 src/coreComponents/python/modules/geosx_mesh_doctor/checks/non_conformal.py delete mode 100644 src/coreComponents/python/modules/geosx_mesh_doctor/checks/reorient_mesh.py delete mode 100644 src/coreComponents/python/modules/geosx_mesh_doctor/checks/self_intersecting_elements.py delete mode 100644 src/coreComponents/python/modules/geosx_mesh_doctor/checks/supported_elements.py delete mode 100644 src/coreComponents/python/modules/geosx_mesh_doctor/checks/triangle_distance.py delete mode 100644 src/coreComponents/python/modules/geosx_mesh_doctor/checks/vtk_polyhedron.py delete mode 100644 src/coreComponents/python/modules/geosx_mesh_doctor/checks/vtk_utils.py delete mode 100644 src/coreComponents/python/modules/geosx_mesh_doctor/mesh_doctor.py delete mode 100644 src/coreComponents/python/modules/geosx_mesh_doctor/parsing/__init__.py delete mode 100644 src/coreComponents/python/modules/geosx_mesh_doctor/parsing/check_fractures_parsing.py delete mode 100644 src/coreComponents/python/modules/geosx_mesh_doctor/parsing/cli_parsing.py delete mode 100644 src/coreComponents/python/modules/geosx_mesh_doctor/parsing/collocated_nodes_parsing.py delete mode 100644 src/coreComponents/python/modules/geosx_mesh_doctor/parsing/element_volumes_parsing.py delete mode 100644 src/coreComponents/python/modules/geosx_mesh_doctor/parsing/fix_elements_orderings_parsing.py delete mode 100644 src/coreComponents/python/modules/geosx_mesh_doctor/parsing/generate_cube_parsing.py delete mode 100644 src/coreComponents/python/modules/geosx_mesh_doctor/parsing/generate_fractures_parsing.py delete mode 100644 src/coreComponents/python/modules/geosx_mesh_doctor/parsing/generate_global_ids_parsing.py delete mode 100644 src/coreComponents/python/modules/geosx_mesh_doctor/parsing/non_conformal_parsing.py delete mode 100644 src/coreComponents/python/modules/geosx_mesh_doctor/parsing/self_intersecting_elements_parsing.py delete mode 100644 src/coreComponents/python/modules/geosx_mesh_doctor/parsing/supported_elements_parsing.py delete mode 100644 src/coreComponents/python/modules/geosx_mesh_doctor/parsing/vtk_output_parsing.py delete mode 100644 src/coreComponents/python/modules/geosx_mesh_doctor/pyproject.toml delete mode 100644 src/coreComponents/python/modules/geosx_mesh_doctor/register.py delete mode 100644 src/coreComponents/python/modules/geosx_mesh_doctor/requirements.txt delete mode 100644 src/coreComponents/python/modules/geosx_mesh_doctor/setup.py delete mode 100644 src/coreComponents/python/modules/geosx_mesh_doctor/tests/test_cli_parsing.py delete mode 100644 src/coreComponents/python/modules/geosx_mesh_doctor/tests/test_collocated_nodes.py delete mode 100644 src/coreComponents/python/modules/geosx_mesh_doctor/tests/test_element_volumes.py delete mode 100644 src/coreComponents/python/modules/geosx_mesh_doctor/tests/test_generate_cube.py delete mode 100644 src/coreComponents/python/modules/geosx_mesh_doctor/tests/test_generate_fractures.py delete mode 100644 src/coreComponents/python/modules/geosx_mesh_doctor/tests/test_generate_global_ids.py delete mode 100644 src/coreComponents/python/modules/geosx_mesh_doctor/tests/test_non_conformal.py delete mode 100644 src/coreComponents/python/modules/geosx_mesh_doctor/tests/test_reorient_mesh.py delete mode 100644 src/coreComponents/python/modules/geosx_mesh_doctor/tests/test_self_intersecting_elements.py delete mode 100644 src/coreComponents/python/modules/geosx_mesh_doctor/tests/test_supported_elements.py delete mode 100644 src/coreComponents/python/modules/geosx_mesh_doctor/tests/test_triangle_distance.py delete mode 100644 src/coreComponents/python/modules/geosx_mesh_tools_package/geosx_mesh_tools/__init__.py delete mode 100644 src/coreComponents/python/modules/geosx_mesh_tools_package/geosx_mesh_tools/abaqus_converter.py delete mode 100644 src/coreComponents/python/modules/geosx_mesh_tools_package/geosx_mesh_tools/main.py delete mode 100644 src/coreComponents/python/modules/geosx_mesh_tools_package/geosx_mesh_tools/py.typed delete mode 100644 src/coreComponents/python/modules/geosx_mesh_tools_package/pyproject.toml delete mode 100644 src/coreComponents/python/modules/geosx_mesh_tools_package/setup.cfg delete mode 100644 src/coreComponents/python/modules/geosx_xml_tools_package/.gitignore delete mode 100644 src/coreComponents/python/modules/geosx_xml_tools_package/geosx_xml_tools/__init__.py delete mode 100644 src/coreComponents/python/modules/geosx_xml_tools_package/geosx_xml_tools/attribute_coverage.py delete mode 100644 src/coreComponents/python/modules/geosx_xml_tools_package/geosx_xml_tools/command_line_parsers.py delete mode 100644 src/coreComponents/python/modules/geosx_xml_tools_package/geosx_xml_tools/main.py delete mode 100644 src/coreComponents/python/modules/geosx_xml_tools_package/geosx_xml_tools/py.typed delete mode 100644 src/coreComponents/python/modules/geosx_xml_tools_package/geosx_xml_tools/regex_tools.py delete mode 100644 src/coreComponents/python/modules/geosx_xml_tools_package/geosx_xml_tools/table_generator.py delete mode 100644 src/coreComponents/python/modules/geosx_xml_tools_package/geosx_xml_tools/tests/__init__.py delete mode 100644 src/coreComponents/python/modules/geosx_xml_tools_package/geosx_xml_tools/tests/generate_test_xml.py delete mode 100644 src/coreComponents/python/modules/geosx_xml_tools_package/geosx_xml_tools/tests/test_manager.py delete mode 100644 src/coreComponents/python/modules/geosx_xml_tools_package/geosx_xml_tools/unit_manager.py delete mode 100644 src/coreComponents/python/modules/geosx_xml_tools_package/geosx_xml_tools/xml_formatter.py delete mode 100644 src/coreComponents/python/modules/geosx_xml_tools_package/geosx_xml_tools/xml_processor.py delete mode 100644 src/coreComponents/python/modules/geosx_xml_tools_package/geosx_xml_tools/xml_redundancy_check.py delete mode 100644 src/coreComponents/python/modules/geosx_xml_tools_package/pyproject.toml delete mode 100644 src/coreComponents/python/modules/geosx_xml_tools_package/setup.cfg delete mode 100644 src/coreComponents/python/modules/hdf5_wrapper_package/hdf5_wrapper/__init__.py delete mode 100644 src/coreComponents/python/modules/hdf5_wrapper_package/hdf5_wrapper/py.typed delete mode 100644 src/coreComponents/python/modules/hdf5_wrapper_package/hdf5_wrapper/use_example.py delete mode 100644 src/coreComponents/python/modules/hdf5_wrapper_package/hdf5_wrapper/wrapper.py delete mode 100644 src/coreComponents/python/modules/hdf5_wrapper_package/hdf5_wrapper/wrapper_tests.py delete mode 100644 src/coreComponents/python/modules/hdf5_wrapper_package/pyproject.toml delete mode 100644 src/coreComponents/python/modules/hdf5_wrapper_package/setup.cfg delete mode 100644 src/coreComponents/python/modules/pygeosx_tools_package/pygeosx_tools/__init__.py delete mode 100644 src/coreComponents/python/modules/pygeosx_tools_package/pygeosx_tools/file_io.py delete mode 100644 src/coreComponents/python/modules/pygeosx_tools_package/pygeosx_tools/mesh_interpolation.py delete mode 100644 src/coreComponents/python/modules/pygeosx_tools_package/pygeosx_tools/py.typed delete mode 100644 src/coreComponents/python/modules/pygeosx_tools_package/pygeosx_tools/well_log.py delete mode 100644 src/coreComponents/python/modules/pygeosx_tools_package/pygeosx_tools/wrapper.py delete mode 100644 src/coreComponents/python/modules/pygeosx_tools_package/pyproject.toml delete mode 100644 src/coreComponents/python/modules/pygeosx_tools_package/setup.cfg delete mode 100644 src/coreComponents/python/modules/timehistory_package/pyproject.toml delete mode 100644 src/coreComponents/python/modules/timehistory_package/setup.cfg delete mode 100644 src/coreComponents/python/modules/timehistory_package/timehistory/__init__.py delete mode 100644 src/coreComponents/python/modules/timehistory_package/timehistory/plot_time_history.py delete mode 100644 src/coreComponents/python/visitMacros/visitVTKConversion.py delete mode 100644 src/docs/sphinx/pythonTools/geosx_mesh_tools.rst delete mode 100644 src/docs/sphinx/pythonTools/geosx_xml_tools.rst delete mode 100644 src/docs/sphinx/pythonTools/hdf5_wrapper.rst delete mode 100644 src/docs/sphinx/pythonTools/mesh_doctor.rst delete mode 100644 src/docs/sphinx/pythonTools/pygeosx_tools.rst delete mode 100644 src/docs/sphinx/pythonTools/pythonAPI.rst delete mode 100644 src/docs/sphinx/pythonTools/timehistory.rst diff --git a/integratedTests b/integratedTests index 5c11fe9652e..aff76fac6eb 160000 --- a/integratedTests +++ b/integratedTests @@ -1 +1 @@ -Subproject commit 5c11fe9652ee08ed60fef9d72dde8fa08e57291b +Subproject commit aff76fac6ebdf931162e68c535b69012111984c4 diff --git a/scripts/setupPythonEnvironment.bash b/scripts/setupPythonEnvironment.bash index e017b4f3cac..8732edf3130 100755 --- a/scripts/setupPythonEnvironment.bash +++ b/scripts/setupPythonEnvironment.bash @@ -3,12 +3,19 @@ # Configuration SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" -PACKAGE_DIR=$SCRIPT_DIR/../src/coreComponents/python/modules -declare -a TARGET_PACKAGES=("$PACKAGE_DIR/geosx_mesh_tools_package" - "$PACKAGE_DIR/geosx_xml_tools_package" - "$PACKAGE_DIR/hdf5_wrapper_package" - "$PACKAGE_DIR/pygeosx_tools_package" - "$SCRIPT_DIR/../integratedTests/scripts/geos_ats_package") +PYTHON_TARGET= +BIN_DIR= +PACKAGE_DIR= +TMP_CLONE_DIR= +PIP_CMD="pip --disable-pip-version-check" + + +declare -a TARGET_PACKAGES=("geosx_mesh_tools_package" + "geosx_mesh_doctor" + "geosx_xml_tools_package" + "hdf5_wrapper_package" + "pygeosx_tools_package" + "geos_ats_package") declare -a LINK_SCRIPTS=("preprocess_xml" "format_xml" "convert_abaqus" @@ -19,11 +26,6 @@ declare -a LINK_SCRIPTS=("preprocess_xml" # Read input arguments -PYTHON_TARGET= -BIN_DIR= -PIP_CMD="pip --disable-pip-version-check" - - if [[ -z "${VERBOSE}" ]] then VERBOSE=false @@ -45,6 +47,10 @@ case $key in BIN_DIR="$2" shift # past argument ;; + -d|--pkg-dir) + PACKAGE_DIR="$2" + shift # past argument + ;; -v|--verbose) VERBOSE=true shift # past argument @@ -54,6 +60,7 @@ case $key in echo "Python environment setup options:" echo "-p/--python-target \"Target parent python bin\"" echo "-b/--bin-dir \"Directory to link new scripts\"" + echo "-d/--pkg-dir \"Directory containing target python packages\"" echo "-v/--verbose \"Increase verbosity level\"" echo "" exit @@ -84,18 +91,36 @@ then fi +# Check for a predefined package directory +echo "Checking for python packages..." +if [[ -z "${PACKAGE_DIR}" ]] +then + echo "Cloning the GEOS python package repository..." + TMP_CLONE_DIR=$(mktemp -d) + PACKAGE_DIR=$TMP_CLONE_DIR/geosPythonPackages + git clone --depth 1 --branch main --single-branch https://github.com/GEOS-DEV/geosPythonPackages.git $PACKAGE_DIR +elif [ ! -d "${PACKAGE_DIR}/geosx_xml_tools_package" ] +then + echo "The specified package directory does not contain the expected targets." + echo "The path specified with -d/--pkg-dir should point to a copy of the geosPythonPackages repository." + exit 1 +fi + + # Install packages echo "Installing python packages..." for p in "${TARGET_PACKAGES[@]}" do - if [ -d "$p" ] + if [ -d "$PACKAGE_DIR/$p" ] then echo " $p" + + # Try installing the package if $VERBOSE - INSTALL_MSG=$($PYTHON_TARGET -m $PIP_CMD install $p) + INSTALL_MSG=$($PYTHON_TARGET -m $PIP_CMD install $PACKAGE_DIR/$p) INSTALL_RC=$? then - INSTALL_MSG=$($PYTHON_TARGET -m $PIP_CMD install $p 2>&1) + INSTALL_MSG=$($PYTHON_TARGET -m $PIP_CMD install $PACKAGE_DIR/$p 2>&1) INSTALL_RC=$? fi @@ -189,5 +214,12 @@ then fi fi + +if [[ ! -z "${TMP_CLONE_DIR}" ]] +then + rm -rf $TMP_CLONE_DIR +fi + + echo "Done!" diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 847adc5a11f..7041bf98ff1 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -229,6 +229,65 @@ install( FILES ${CMAKE_BINARY_DIR}/schema.xsd DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/${CMAKE_PROJECT_NAME}/schema OPTIONAL) +################################ +# Add python environment setup +################################ +if ( Python3_EXECUTABLE ) + message(STATUS "Found python version ${Python3_VERSION}") + if (${Python3_VERSION} VERSION_LESS "3.6.0") + message(STATUS "Note: try setting Python3_ROOT_DIR and/or Python3_EXECUTABLE in your host config to the appropriate version.") + message( FATAL_ERROR "Building the GEOSX python tools requires Python >= 3.6." ) + endif() + + # Select the version of python to target + if( ENABLE_PYGEOSX ) + set( PYTHON_POST_EXECUTABLE ${CMAKE_BINARY_DIR}/lib/PYGEOSX/bin/python CACHE PATH "" FORCE ) + else() + set( PYTHON_POST_EXECUTABLE ${Python3_EXECUTABLE} CACHE PATH "" FORCE ) + endif() + + # Check for the virtualenv package + execute_process( + COMMAND ${Python3_EXECUTABLE} -c "import virtualenv" + RESULT_VARIABLE VIRTUALENV_AVAILABLE + ) + + if (NOT ${VIRTUALENV_AVAILABLE} EQUAL 0) + message(WARNING "The \"virtualenv\" package was not found in the target python environment (${Python3_EXECUTABLE}). This package may be required to build PYGEOSX or the python development environment.") + endif() + + # Build targets + set( GEOSX_PYTHON_TOOLS_BINS + "${CMAKE_BINARY_DIR}/bin/preprocess_xml" + "${CMAKE_BINARY_DIR}/bin/format_xml" ) + + add_custom_command( OUTPUT ${GEOSX_PYTHON_TOOLS_BINS} + COMMAND bash ${CMAKE_SOURCE_DIR}/../scripts/setupPythonEnvironment.bash -p ${PYTHON_POST_EXECUTABLE} -b ${CMAKE_BINARY_DIR}/bin + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + ) + + add_custom_target( geosx_python_tools + DEPENDS ${GEOSX_PYTHON_TOOLS_BINS} ) + + add_custom_target( geosx_python_tools_test + COMMAND ${CMAKE_BINARY_DIR}/python/geosx/bin/test_geosx_xml_tools + COMMAND rm -r ${CMAKE_BINARY_DIR}/python/geosx_xml_tools_tests* + WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/python + DEPENDS geosx_python_tools + ) + + add_custom_target( geosx_format_all_xml_files + COMMAND bash ${CMAKE_SOURCE_DIR}/../scripts/formatXMLFiles.bash -g ${CMAKE_BINARY_DIR}/bin/format_xml ${CMAKE_SOURCE_DIR} ${CMAKE_SOURCE_DIR}/../examples + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + DEPENDS geosx_xml_tools + ) + +else() + message(WARNING "Building the GEOSX python tools requires Python >= 3.6.") + message(STATUS "If you need these, try setting Python3_ROOT_DIR and/or Python3_EXECUTABLE in your host config.") +endif() + + ################################ # Add integratedTests ################################ diff --git a/src/coreComponents/CMakeLists.txt b/src/coreComponents/CMakeLists.txt index e77afed5fc6..81653fb0b8f 100644 --- a/src/coreComponents/CMakeLists.txt +++ b/src/coreComponents/CMakeLists.txt @@ -16,8 +16,7 @@ set( subdirs fileIO physicsSolvers events - mainInterface - python ) + mainInterface ) unset( parallelDeps ) diff --git a/src/coreComponents/fileIO/doc/InputXMLFiles.rst b/src/coreComponents/fileIO/doc/InputXMLFiles.rst index 0a9267bf70c..fa5087e2cab 100644 --- a/src/coreComponents/fileIO/doc/InputXMLFiles.rst +++ b/src/coreComponents/fileIO/doc/InputXMLFiles.rst @@ -198,7 +198,7 @@ Advanced XML Features ================================= The `geosx_xml_tools` python package adds a set of advanced features to the GEOS xml format: units, parameters, and symbolic expressions. -See :ref:`PythonToolsSetup` for details on setup instructions, and :ref:`XMLToolsPackage` for package API details. +See`Python Tools Setup `_ for details on setup instructions, and `XML Parser Documentation `_ for package API details. Usage diff --git a/src/coreComponents/python/.gitignore b/src/coreComponents/python/.gitignore deleted file mode 100644 index 800e83a4bb7..00000000000 --- a/src/coreComponents/python/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -*.pyc -*.egg-info -build diff --git a/src/coreComponents/python/CMakeLists.txt b/src/coreComponents/python/CMakeLists.txt deleted file mode 100644 index fd387879b74..00000000000 --- a/src/coreComponents/python/CMakeLists.txt +++ /dev/null @@ -1,78 +0,0 @@ - - -if ( Python3_EXECUTABLE ) - # Select the version of python to target - if( ENABLE_PYGEOSX ) - set( PYTHON_POST_EXECUTABLE ${CMAKE_BINARY_DIR}/lib/PYGEOSX/bin/python CACHE PATH "" FORCE ) - - # Check for the virtualenv package - execute_process( - COMMAND ${Python3_EXECUTABLE} -c "import virtualenv" - RESULT_VARIABLE VIRTUALENV_AVAILABLE - ) - - if (NOT ${VIRTUALENV_AVAILABLE} EQUAL 0) - message(FATAL_ERROR "To build the PYGEOSX interface, the \"virtualenv\" package should be installed in the target python environment. Please install it (i.e.: \"${Python3_EXECUTABLE} -m pip install virtualenv\") or use a different python distribution.") - endif() - - else() - set( PYTHON_POST_EXECUTABLE ${Python3_EXECUTABLE} CACHE PATH "" FORCE ) - endif() - - # Build targets - set( GEOSX_PYTHON_TOOLS_BINS - "${CMAKE_BINARY_DIR}/bin/preprocess_xml" - "${CMAKE_BINARY_DIR}/bin/format_xml" - ) - - add_custom_command( OUTPUT ${GEOSX_PYTHON_TOOLS_BINS} - COMMAND bash ${CMAKE_SOURCE_DIR}/../scripts/setupPythonEnvironment.bash -p ${PYTHON_POST_EXECUTABLE} -b ${CMAKE_BINARY_DIR}/bin - WORKING_DIRECTORY ${CMAKE_BINARY_DIR} - ) - - if( ENABLE_PYGEOSX ) - add_custom_target( geosx_python_tools - DEPENDS pygeosx ${GEOSX_PYTHON_TOOLS_BINS} ) - else() - add_custom_target( geosx_python_tools - DEPENDS ${GEOSX_PYTHON_TOOLS_BINS} ) - endif() - - add_custom_target( geosx_python_tools_test - COMMAND ${CMAKE_BINARY_DIR}/python/geosx/bin/test_geosx_xml_tools - COMMAND rm -r ${CMAKE_BINARY_DIR}/python/geosx_xml_tools_tests* - WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/python - DEPENDS geosx_python_tools - ) - - add_custom_target( geosx_format_all_xml_files - COMMAND bash ${CMAKE_SOURCE_DIR}/../scripts/formatXMLFiles.bash -g ${CMAKE_BINARY_DIR}/bin/format_xml ${CMAKE_SOURCE_DIR} ${CMAKE_SOURCE_DIR}/../examples - WORKING_DIRECTORY ${CMAKE_BINARY_DIR} - DEPENDS geosx_xml_tools - ) - -else() - message(WARNING "Building the GEOSX python tools requires Python >= 3.7.") - message(STATUS "If you need these, try setting Python3_ROOT_DIR and/or Python3_EXECUTABLE in your host config.") -endif() - - -# Python formatting -if ( ENABLE_YAPF ) - set( python_module_sources ) - file( GLOB_RECURSE python_module_sources "*.py" ) - - # Note: blt throws an error if sources doesn't include a c-file, so include dummy.cpp - blt_add_code_checks( PREFIX python_modules_yapf_style - SOURCES ${python_module_sources} ${CMAKE_SOURCE_DIR}/coreComponents/dummy.cpp - YAPF_CFG_FILE ${PROJECT_SOURCE_DIR}/yapf.cfg ) - - set( python_script_sources ) - file( GLOB_RECURSE python_script_sources "${CMAKE_SOURCE_DIR}/../scripts/*.py" ) - - blt_add_code_checks( PREFIX python_scripts_yapf_style - SOURCES ${python_script_sources} ${CMAKE_SOURCE_DIR}/coreComponents/dummy.cpp - YAPF_CFG_FILE ${PROJECT_SOURCE_DIR}/yapf.cfg ) -endif() - - diff --git a/src/coreComponents/python/modules/geosx_mesh_doctor/checks/__init__.py b/src/coreComponents/python/modules/geosx_mesh_doctor/checks/__init__.py deleted file mode 100644 index b7db25411d0..00000000000 --- a/src/coreComponents/python/modules/geosx_mesh_doctor/checks/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# Empty diff --git a/src/coreComponents/python/modules/geosx_mesh_doctor/checks/check_fractures.py b/src/coreComponents/python/modules/geosx_mesh_doctor/checks/check_fractures.py deleted file mode 100644 index b2c241b1ae1..00000000000 --- a/src/coreComponents/python/modules/geosx_mesh_doctor/checks/check_fractures.py +++ /dev/null @@ -1,178 +0,0 @@ -from dataclasses import dataclass -import logging - -from typing import ( - Collection, - FrozenSet, - Iterable, - Sequence, - Set, - Tuple, -) - -from tqdm import tqdm -import numpy - -from vtkmodules.vtkCommonDataModel import ( - vtkUnstructuredGrid, - vtkCell, -) -from vtkmodules.vtkCommonCore import ( - vtkPoints, -) -from vtkmodules.vtkIOXML import ( - vtkXMLMultiBlockDataReader, -) -from vtkmodules.util.numpy_support import ( - vtk_to_numpy, -) -from vtk_utils import ( - vtk_iter, -) - - -@dataclass(frozen=True) -class Options: - tolerance: float - matrix_name: str - fracture_name: str - collocated_nodes_field_name: str - - -@dataclass(frozen=True) -class Result: - # First index is the local index of the fracture mesh. - # Second is the local index of the matrix mesh. - # Third is the global index in the matrix mesh. - errors: Sequence[tuple[int, int, int]] - - -def __read_multiblock(vtk_input_file: str, matrix_name: str, fracture_name: str) -> Tuple[vtkUnstructuredGrid, vtkUnstructuredGrid]: - reader = vtkXMLMultiBlockDataReader() - reader.SetFileName(vtk_input_file) - reader.Update() - multi_block = reader.GetOutput() - for b in range(multi_block.GetNumberOfBlocks()): - block_name: str = multi_block.GetMetaData(b).Get(multi_block.NAME()) - if block_name == matrix_name: - matrix: vtkUnstructuredGrid = multi_block.GetBlock(b) - if block_name == fracture_name: - fracture: vtkUnstructuredGrid = multi_block.GetBlock(b) - assert matrix and fracture - return matrix, fracture - - -def format_collocated_nodes(fracture_mesh: vtkUnstructuredGrid) -> Sequence[Iterable[int]]: - """ - Extract the collocated nodes information from the mesh and formats it in a python way. - :param fracture_mesh: The mesh of the fracture (with 2d cells). - :return: An iterable over all the buckets of collocated nodes. - """ - collocated_nodes: numpy.ndarray = vtk_to_numpy(fracture_mesh.GetPointData().GetArray("collocated_nodes")) - if len(collocated_nodes.shape) == 1: - collocated_nodes: numpy.ndarray = collocated_nodes.reshape((collocated_nodes.shape[0], 1)) - generator = (tuple(sorted(bucket[bucket > -1])) for bucket in collocated_nodes) - return tuple(generator) - - -def __check_collocated_nodes_positions(matrix_points: Sequence[Tuple[float, float, float]], - fracture_points: Sequence[Tuple[float, float, float]], - g2l: Sequence[int], - collocated_nodes: Iterable[Iterable[int]]) -> Collection[Tuple[int, Iterable[int], Iterable[Tuple[float, float, float]]]]: - issues = [] - for li, bucket in enumerate(collocated_nodes): - matrix_nodes = (fracture_points[li], ) + tuple(map(lambda gi: matrix_points[g2l[gi]], bucket)) - m = numpy.array(matrix_nodes) - rank: int = numpy.linalg.matrix_rank(m) - if rank > 1: - issues.append((li, bucket, tuple(map(lambda gi: matrix_points[g2l[gi]], bucket)))) - return issues - - -def my_iter(ccc): - car, cdr = ccc[0], ccc[1:] - for i in car: - if cdr: - for j in my_iter(cdr): - yield i, *j - else: - yield (i, ) - - -def __check_neighbors(matrix: vtkUnstructuredGrid, - fracture: vtkUnstructuredGrid, - g2l: Sequence[int], - collocated_nodes: Sequence[Iterable[int]]): - fracture_nodes: Set[int] = set() - for bucket in collocated_nodes: - for gi in bucket: - fracture_nodes.add(g2l[gi]) - # For each face of each cell, - # if all the points of the face are "made" of collocated nodes, - # then this is a fracture face. - fracture_faces: Set[FrozenSet[int]] = set() - for c in range(matrix.GetNumberOfCells()): - cell: vtkCell = matrix.GetCell(c) - for f in range(cell.GetNumberOfFaces()): - face: vtkCell = cell.GetFace(f) - point_ids = frozenset(vtk_iter(face.GetPointIds())) - if point_ids <= fracture_nodes: - fracture_faces.add(point_ids) - # Finding the cells - for c in tqdm(range(fracture.GetNumberOfCells()), desc="Finding neighbor cell pairs"): - cell: vtkCell = fracture.GetCell(c) - cns: Set[FrozenSet[int]] = set() # subset of collocated_nodes - point_ids = frozenset(vtk_iter(cell.GetPointIds())) - for point_id in point_ids: - bucket = collocated_nodes[point_id] - local_bucket = frozenset(map(g2l.__getitem__, bucket)) - cns.add(local_bucket) - found = 0 - tmp = tuple(map(tuple, cns)) - for node_combinations in my_iter(tmp): - f = frozenset(node_combinations) - if f in fracture_faces: - found += 1 - if found != 2: - logging.warning(f"Something went wrong since we should have found 2 fractures faces (we found {found}) for collocated nodes {cns}.") - - -def __check(vtk_input_file: str, options: Options) -> Result: - matrix, fracture = __read_multiblock(vtk_input_file, options.matrix_name, options.fracture_name) - matrix_points: vtkPoints = matrix.GetPoints() - fracture_points: vtkPoints = fracture.GetPoints() - - collocated_nodes: Sequence[Iterable[int]] = format_collocated_nodes(fracture) - assert matrix.GetPointData().GetGlobalIds() and matrix.GetCellData().GetGlobalIds() and \ - fracture.GetPointData().GetGlobalIds() and fracture.GetCellData().GetGlobalIds() - - point_ids = vtk_to_numpy(matrix.GetPointData().GetGlobalIds()) - g2l = numpy.ones(len(point_ids), dtype=int) * -1 - for loc, glo in enumerate(point_ids): - g2l[glo] = loc - g2l.flags.writeable = False - - issues = __check_collocated_nodes_positions(vtk_to_numpy(matrix.GetPoints().GetData()), - vtk_to_numpy(fracture.GetPoints().GetData()), - g2l, - collocated_nodes) - assert len(issues) == 0 - - __check_neighbors(matrix, fracture, g2l, collocated_nodes) - - errors = [] - for i, duplicates in enumerate(collocated_nodes): - for duplicate in filter(lambda i: i > -1, duplicates): - p0 = matrix_points.GetPoint(g2l[duplicate]) - p1 = fracture_points.GetPoint(i) - if numpy.linalg.norm(numpy.array(p1) - numpy.array(p0)) > options.tolerance: - errors.append((i, g2l[duplicate], duplicate)) - return Result(errors=errors) - - -def check(vtk_input_file: str, options: Options) -> Result: - try: - return __check(vtk_input_file, options) - except BaseException as e: - logging.error(e) - return Result(errors=()) diff --git a/src/coreComponents/python/modules/geosx_mesh_doctor/checks/collocated_nodes.py b/src/coreComponents/python/modules/geosx_mesh_doctor/checks/collocated_nodes.py deleted file mode 100644 index 7a5273ec448..00000000000 --- a/src/coreComponents/python/modules/geosx_mesh_doctor/checks/collocated_nodes.py +++ /dev/null @@ -1,78 +0,0 @@ -from collections import defaultdict -from dataclasses import dataclass -import logging -from typing import ( - Collection, - Iterable, -) -import numpy - -from vtkmodules.vtkCommonCore import ( - reference, - vtkPoints, -) -from vtkmodules.vtkCommonDataModel import ( - vtkIncrementalOctreePointLocator, ) - -from . import vtk_utils - - -@dataclass(frozen=True) -class Options: - tolerance: float - - -@dataclass(frozen=True) -class Result: - nodes_buckets: Iterable[Iterable[int]] # Each bucket contains the duplicated node indices. - wrong_support_elements: Collection[int] # Element indices with support node indices appearing more than once. - - -def __check(mesh, options: Options) -> Result: - points = mesh.GetPoints() - - locator = vtkIncrementalOctreePointLocator() - locator.SetTolerance(options.tolerance) - output = vtkPoints() - locator.InitPointInsertion(output, points.GetBounds()) - - # original ids to/from filtered ids. - filtered_to_original = numpy.ones(points.GetNumberOfPoints(), dtype=int) * -1 - - rejected_points = defaultdict(list) - point_id = reference(0) - for i in range(points.GetNumberOfPoints()): - is_inserted = locator.InsertUniquePoint(points.GetPoint(i), point_id) - if not is_inserted: - # If it's not inserted, `point_id` contains the node that was already at that location. - # But in that case, `point_id` is the new numbering in the destination points array. - # It's more useful for the user to get the old index in the original mesh, so he can look for it in his data. - logging.debug( - f"Point {i} at {points.GetPoint(i)} has been rejected, point {filtered_to_original[point_id.get()]} is already inserted." - ) - rejected_points[point_id.get()].append(i) - else: - # If it's inserted, `point_id` contains the new index in the destination array. - # We store this information to be able to connect the source and destination arrays. - # original_to_filtered[i] = point_id.get() - filtered_to_original[point_id.get()] = i - - tmp = [] - for n, ns in rejected_points.items(): - tmp.append((n, *ns)) - - # Checking that the support node indices appear only once per element. - wrong_support_elements = [] - for c in range(mesh.GetNumberOfCells()): - cell = mesh.GetCell(c) - num_points_per_cell = cell.GetNumberOfPoints() - if len({cell.GetPointId(i) for i in range(num_points_per_cell)}) != num_points_per_cell: - wrong_support_elements.append(c) - - return Result(nodes_buckets=tmp, - wrong_support_elements=wrong_support_elements) - - -def check(vtk_input_file: str, options: Options) -> Result: - mesh = vtk_utils.read_mesh(vtk_input_file) - return __check(mesh, options) diff --git a/src/coreComponents/python/modules/geosx_mesh_doctor/checks/element_volumes.py b/src/coreComponents/python/modules/geosx_mesh_doctor/checks/element_volumes.py deleted file mode 100644 index 4dfd917247a..00000000000 --- a/src/coreComponents/python/modules/geosx_mesh_doctor/checks/element_volumes.py +++ /dev/null @@ -1,82 +0,0 @@ -import logging -from dataclasses import dataclass -from typing import List, Tuple -import uuid - -from vtkmodules.vtkCommonDataModel import ( - VTK_HEXAHEDRON, - VTK_PYRAMID, - VTK_TETRA, - VTK_WEDGE, -) -from vtkmodules.vtkFiltersVerdict import ( - vtkCellSizeFilter, - vtkMeshQuality, -) -from vtkmodules.util.numpy_support import ( - vtk_to_numpy, -) - - -from . import vtk_utils - - -@dataclass(frozen=True) -class Options: - min_volume: float - - -@dataclass(frozen=True) -class Result: - element_volumes: List[Tuple[int, float]] - - -def __check(mesh, options: Options) -> Result: - cs = vtkCellSizeFilter() - - cs.ComputeAreaOff() - cs.ComputeLengthOff() - cs.ComputeSumOff() - cs.ComputeVertexCountOff() - cs.ComputeVolumeOn() - volume_array_name = "__MESH_DOCTOR_VOLUME-" + str(uuid.uuid4()) # Making the name unique - cs.SetVolumeArrayName(volume_array_name) - - cs.SetInputData(mesh) - cs.Update() - - mq = vtkMeshQuality() - SUPPORTED_TYPES = [VTK_HEXAHEDRON, VTK_TETRA] - - mq.SetTetQualityMeasureToVolume() - mq.SetHexQualityMeasureToVolume() - if hasattr(mq, "SetPyramidQualityMeasureToVolume"): # This feature is quite recent - mq.SetPyramidQualityMeasureToVolume() - SUPPORTED_TYPES.append(VTK_PYRAMID) - mq.SetWedgeQualityMeasureToVolume() - SUPPORTED_TYPES.append(VTK_WEDGE) - else: - logging.warning("Your \"pyvtk\" version does not bring pyramid nor wedge support with vtkMeshQuality. Using the fallback solution.") - - mq.SetInputData(mesh) - mq.Update() - - volume = cs.GetOutput().GetCellData().GetArray(volume_array_name) - quality = mq.GetOutput().GetCellData().GetArray("Quality") # Name is imposed by vtk. - - assert volume is not None - assert quality is not None - volume = vtk_to_numpy(volume) - quality = vtk_to_numpy(quality) - small_volumes: List[Tuple[int, float]] = [] - for i, pack in enumerate(zip(volume, quality)): - v, q = pack - vol = q if mesh.GetCellType(i) in SUPPORTED_TYPES else v - if vol < options.min_volume: - small_volumes.append((i, vol)) - return Result(element_volumes=small_volumes) - - -def check(vtk_input_file: str, options: Options) -> Result: - mesh = vtk_utils.read_mesh(vtk_input_file) - return __check(mesh, options) diff --git a/src/coreComponents/python/modules/geosx_mesh_doctor/checks/fix_elements_orderings.py b/src/coreComponents/python/modules/geosx_mesh_doctor/checks/fix_elements_orderings.py deleted file mode 100644 index 61dd034d4cd..00000000000 --- a/src/coreComponents/python/modules/geosx_mesh_doctor/checks/fix_elements_orderings.py +++ /dev/null @@ -1,66 +0,0 @@ -from dataclasses import dataclass -import logging -from typing import ( - List, - Dict, - Set, - FrozenSet, -) - -from vtkmodules.vtkCommonCore import ( - vtkIdList, -) - -from . import vtk_utils -from .vtk_utils import ( - to_vtk_id_list, - VtkOutput, -) - - -@dataclass(frozen=True) -class Options: - vtk_output: VtkOutput - cell_type_to_ordering: Dict[int, List[int]] - - -@dataclass(frozen=True) -class Result: - output: str - unchanged_cell_types: FrozenSet[int] - - -def __check(mesh, options: Options) -> Result: - # The vtk cell type is an int and will be the key of the following mapping, - # that will point to the relevant permutation. - cell_type_to_ordering: Dict[int, List[int]] = options.cell_type_to_ordering - unchanged_cell_types: Set[int] = set() # For logging purpose - - # Preparing the output mesh by first keeping the same instance type. - output_mesh = mesh.NewInstance() - output_mesh.CopyStructure(mesh) - output_mesh.CopyAttributes(mesh) - - # `output_mesh` now contains a full copy of the input mesh. - # We'll now modify the support nodes orderings in place if needed. - cells = output_mesh.GetCells() - for cell_idx in range(output_mesh.GetNumberOfCells()): - cell_type: int = output_mesh.GetCell(cell_idx).GetCellType() - new_ordering = cell_type_to_ordering.get(cell_type) - if new_ordering: - support_point_ids = vtkIdList() - cells.GetCellAtId(cell_idx, support_point_ids) - new_support_point_ids = [] - for i, v in enumerate(new_ordering): - new_support_point_ids.append(support_point_ids.GetId(new_ordering[i])) - cells.ReplaceCellAtId(cell_idx, to_vtk_id_list(new_support_point_ids)) - else: - unchanged_cell_types.add(cell_type) - is_written_error = vtk_utils.write_mesh(output_mesh, options.vtk_output) - return Result(output=options.vtk_output.output if not is_written_error else "", - unchanged_cell_types=frozenset(unchanged_cell_types)) - - -def check(vtk_input_file: str, options: Options) -> Result: - mesh = vtk_utils.read_mesh(vtk_input_file) - return __check(mesh, options) diff --git a/src/coreComponents/python/modules/geosx_mesh_doctor/checks/generate_cube.py b/src/coreComponents/python/modules/geosx_mesh_doctor/checks/generate_cube.py deleted file mode 100644 index f8625f50453..00000000000 --- a/src/coreComponents/python/modules/geosx_mesh_doctor/checks/generate_cube.py +++ /dev/null @@ -1,160 +0,0 @@ -from dataclasses import dataclass -import logging -from typing import Sequence, Iterable - -import numpy - -from vtkmodules.vtkCommonCore import ( - vtkPoints, -) -from vtkmodules.vtkCommonDataModel import ( - VTK_HEXAHEDRON, - vtkCellArray, - vtkHexahedron, - vtkRectilinearGrid, - vtkUnstructuredGrid, -) -from vtkmodules.util.numpy_support import ( - numpy_to_vtk, -) - -from . import vtk_utils -from .vtk_utils import ( - VtkOutput, -) - -from .generate_global_ids import __build_global_ids - - -@dataclass(frozen=True) -class Result: - info: str - - -@dataclass(frozen=True) -class FieldInfo: - name: str - dimension: int - support: str - - -@dataclass(frozen=True) -class Options: - vtk_output: VtkOutput - generate_cells_global_ids: bool - generate_points_global_ids: bool - xs: Sequence[float] - ys: Sequence[float] - zs: Sequence[float] - nxs: Sequence[int] - nys: Sequence[int] - nzs: Sequence[int] - fields: Iterable[FieldInfo] - - -@dataclass(frozen=True) -class XYZ: - x: numpy.ndarray - y: numpy.ndarray - z: numpy.ndarray - - -def build_rectilinear_blocks_mesh(xyzs: Iterable[XYZ]) -> vtkUnstructuredGrid: - """ - Builds an unstructured vtk grid from the `xyzs` blocks. Kind of InternalMeshGenerator. - :param xyzs: The blocks. - :return: The unstructured mesh, even if it's topologically structured. - """ - rgs = [] - for xyz in xyzs: - rg = vtkRectilinearGrid() - rg.SetDimensions(len(xyz.x), len(xyz.y), len(xyz.z)) - rg.SetXCoordinates(numpy_to_vtk(xyz.x)) - rg.SetYCoordinates(numpy_to_vtk(xyz.y)) - rg.SetZCoordinates(numpy_to_vtk(xyz.z)) - rgs.append(rg) - - num_points = sum(map(lambda r: r.GetNumberOfPoints(), rgs)) - num_cells = sum(map(lambda r: r.GetNumberOfCells(), rgs)) - - points = vtkPoints() - points.Allocate(num_points) - for rg in rgs: - for i in range(rg.GetNumberOfPoints()): - points.InsertNextPoint(rg.GetPoint(i)) - - cell_types = [VTK_HEXAHEDRON] * num_cells - cells = vtkCellArray() - cells.AllocateExact(num_cells, num_cells * 8) - - m = (0, 1, 3, 2, 4, 5, 7, 6) # VTK_VOXEL and VTK_HEXAHEDRON do not share the same ordering. - offset = 0 - for rg in rgs: - for i in range(rg.GetNumberOfCells()): - c = rg.GetCell(i) - new_cell = vtkHexahedron() - for j in range(8): - new_cell.GetPointIds().SetId(j, offset + c.GetPointId(m[j])) - cells.InsertNextCell(new_cell) - offset += rg.GetNumberOfPoints() - - mesh = vtkUnstructuredGrid() - mesh.SetPoints(points) - mesh.SetCells(cell_types, cells) - - return mesh - - -def __add_fields(mesh: vtkUnstructuredGrid, fields: Iterable[FieldInfo]) -> vtkUnstructuredGrid: - for field_info in fields: - if field_info.support == "CELLS": - data = mesh.GetCellData() - n = mesh.GetNumberOfCells() - elif field_info.support == "POINTS": - data = mesh.GetPointData() - n = mesh.GetNumberOfPoints() - array = numpy.ones((n, field_info.dimension), dtype=float) - vtk_array = numpy_to_vtk(array) - vtk_array.SetName(field_info.name) - data.AddArray(vtk_array) - return mesh - - -def __build(options: Options): - def build_coordinates(positions, num_elements): - result = [] - it = zip(zip(positions, positions[1:]), num_elements) - try: - coords, n = next(it) - while True: - start, stop = coords - end_point = False - tmp = numpy.linspace(start=start, stop=stop, num=n+end_point, endpoint=end_point) - coords, n = next(it) - result.append(tmp) - except StopIteration: - end_point = True - tmp = numpy.linspace(start=start, stop=stop, num=n+end_point, endpoint=end_point) - result.append(tmp) - return numpy.concatenate(result) - x = build_coordinates(options.xs, options.nxs) - y = build_coordinates(options.ys, options.nys) - z = build_coordinates(options.zs, options.nzs) - cube = build_rectilinear_blocks_mesh((XYZ(x, y, z),)) - cube = __add_fields(cube, options.fields) - __build_global_ids(cube, options.generate_cells_global_ids, options.generate_points_global_ids) - return cube - - -def __check(options: Options) -> Result: - output_mesh = __build(options) - vtk_utils.write_mesh(output_mesh, options.vtk_output) - return Result(info=f"Mesh was written to {options.vtk_output.output}") - - -def check(vtk_input_file: str, options: Options) -> Result: - try: - return __check(options) - except BaseException as e: - logging.error(e) - return Result(info="Something went wrong.") diff --git a/src/coreComponents/python/modules/geosx_mesh_doctor/checks/generate_fractures.py b/src/coreComponents/python/modules/geosx_mesh_doctor/checks/generate_fractures.py deleted file mode 100644 index 22fbadcb956..00000000000 --- a/src/coreComponents/python/modules/geosx_mesh_doctor/checks/generate_fractures.py +++ /dev/null @@ -1,482 +0,0 @@ -from collections import defaultdict -from dataclasses import dataclass -import logging -from typing import ( - Collection, - Dict, - FrozenSet, - Iterable, - List, - Mapping, - Optional, - Set, - Sequence, - Tuple, -) -from enum import Enum - -from tqdm import tqdm -import networkx -import numpy - -from vtkmodules.vtkCommonCore import ( - vtkIdList, - vtkPoints, -) -from vtkmodules.vtkCommonDataModel import ( - vtkCell, - vtkCellArray, - vtkPolygon, - vtkUnstructuredGrid, - VTK_POLYGON, - VTK_POLYHEDRON, -) -from vtkmodules.util.numpy_support import ( - vtk_to_numpy, - numpy_to_vtk, -) -from vtkmodules.util.vtkConstants import VTK_ID_TYPE - -from . import vtk_utils -from .vtk_utils import ( - vtk_iter, - VtkOutput, - to_vtk_id_list, -) -from .vtk_polyhedron import ( - FaceStream, -) - - -class FracturePolicy(Enum): - FIELD = 0 - INTERNAL_SURFACES = 1 - - -@dataclass(frozen=True) -class Options: - policy: FracturePolicy - field: str - field_values: FrozenSet[int] - vtk_output: VtkOutput - vtk_fracture_output: VtkOutput - - -@dataclass(frozen=True) -class Result: - info: str - - -@dataclass(frozen=True) -class FractureInfo: - node_to_cells: Mapping[int, Iterable[int]] # For each _fracture_ node, gives all the cells that use this node. - face_nodes: Iterable[Collection[int]] # For each fracture face, returns the nodes of this face - - -def build_node_to_cells(mesh: vtkUnstructuredGrid, - face_nodes: Iterable[Iterable[int]]) -> Mapping[int, Iterable[int]]: - node_to_cells: Dict[int, Set[int]] = defaultdict(set) # TODO normally, just a list and not a set should be enough. - - fracture_nodes: Set[int] = set() - for fns in face_nodes: - for n in fns: - fracture_nodes.add(n) - - for cell_id in tqdm(range(mesh.GetNumberOfCells()), desc="Computing the node to cells mapping"): - cell_points: FrozenSet[int] = frozenset(vtk_iter(mesh.GetCell(cell_id).GetPointIds())) - intersection: Iterable[int] = cell_points & fracture_nodes - for node in intersection: - node_to_cells[node].add(cell_id) - - return node_to_cells - - -def __build_fracture_info_from_fields(mesh: vtkUnstructuredGrid, - f: Sequence[int], - field_values: FrozenSet[int]) -> FractureInfo: - cells_to_faces: Dict[int, List[int]] = defaultdict(list) - # For each face of each cell, we search for the unique neighbor cell (if it exists). - # Then, if the 2 values of the two cells match the field requirements, - # we store the cell and its local face index: this is indeed part of the surface that we'll need to be split. - cell: vtkCell - for cell_id in tqdm(range(mesh.GetNumberOfCells()), desc="Computing the cell to faces mapping"): - if f[cell_id] not in field_values: # No need to consider a cell if its field value is not in the target range. - continue - cell = mesh.GetCell(cell_id) - for i in range(cell.GetNumberOfFaces()): - neighbor_cell_ids = vtkIdList() - mesh.GetCellNeighbors(cell_id, cell.GetFace(i).GetPointIds(), neighbor_cell_ids) - assert neighbor_cell_ids.GetNumberOfIds() < 2 - for j in range(neighbor_cell_ids.GetNumberOfIds()): # It's 0 or 1... - neighbor_cell_id = neighbor_cell_ids.GetId(j) - if f[neighbor_cell_id] != f[cell_id] and f[neighbor_cell_id] in field_values: - cells_to_faces[cell_id].append(i) # TODO add this (cell_is, face_id) information to the fracture_info? - face_nodes: List[Collection[int]] = list() - face_nodes_hashes: Set[FrozenSet[int]] = set() # A temporary not to add multiple times the same face. - for cell_id, faces_ids in tqdm(cells_to_faces.items(), desc="Extracting the faces of the fractures"): - cell = mesh.GetCell(cell_id) - for face_id in faces_ids: - fn: Collection[int] = tuple(vtk_iter(cell.GetFace(face_id).GetPointIds())) - fnh = frozenset(fn) - if fnh not in face_nodes_hashes: - face_nodes_hashes.add(fnh) - face_nodes.append(fn) - node_to_cells: Mapping[int, Iterable[int]] = build_node_to_cells(mesh, face_nodes) - - return FractureInfo(node_to_cells=node_to_cells, face_nodes=face_nodes) - - -def __build_fracture_info_from_internal_surfaces(mesh: vtkUnstructuredGrid, - f: Sequence[int], - field_values: FrozenSet[int]) -> FractureInfo: - node_to_cells: Dict[int, List[int]] = {} - face_nodes: List[Collection[int]] = [] - for cell_id in tqdm(range(mesh.GetNumberOfCells()), desc="Computing the face to nodes mapping"): - cell = mesh.GetCell(cell_id) - if cell.GetCellDimension() == 2: - if f[cell_id] in field_values: - nodes = [] - for v in range(cell.GetNumberOfPoints()): - point_id: int = cell.GetPointId(v) - node_to_cells[point_id] = [] - nodes.append(point_id) - face_nodes.append(tuple(nodes)) - - for cell_id in tqdm(range(mesh.GetNumberOfCells()), desc="Computing the node to cells mapping"): - cell = mesh.GetCell(cell_id) - if cell.GetCellDimension() == 3: - for v in range(cell.GetNumberOfPoints()): - if cell.GetPointId(v) in node_to_cells: - node_to_cells[cell.GetPointId(v)].append(cell_id) - - return FractureInfo(node_to_cells=node_to_cells, face_nodes=face_nodes) - - -def build_fracture_info(mesh: vtkUnstructuredGrid, - options: Options) -> FractureInfo: - field = options.field - field_values = options.field_values - cell_data = mesh.GetCellData() - if cell_data.HasArray(field): - f = vtk_to_numpy(cell_data.GetArray(field)) - else: - raise ValueError(f"Cell field {field} does not exist in mesh, nothing done") - - if options.policy == FracturePolicy.FIELD: - return __build_fracture_info_from_fields(mesh, f, field_values) - elif options.policy == FracturePolicy.INTERNAL_SURFACES: - return __build_fracture_info_from_internal_surfaces(mesh, f, field_values) - - -def build_cell_to_cell_graph(mesh: vtkUnstructuredGrid, - fracture: FractureInfo) -> networkx.Graph: - """ - Connects all the cells that touch the fracture by at least one node. - Two cells are connected when they share at least a face which is not a face of the fracture. - :param mesh: The input mesh. - :param fracture: The fracture info. - :return: The graph: each node of this graph is the index of the cell. - There's an edge between two nodes of the graph if the cells share a face. - """ - # Faces are identified by their nodes. But the order of those nodes may vary while referring to the same face. - # Therefore we compute some kinds of hashes of those face to easily detect if a face is part of the fracture. - tmp: List[FrozenSet[int]] = [] - for fn in fracture.face_nodes: - tmp.append(frozenset(fn)) - face_hashes: FrozenSet[FrozenSet[int]] = frozenset(tmp) - - # We extract the list of the cells that touch the fracture by at least one node. - cells: Set[int] = set() - for cell_ids in fracture.node_to_cells.values(): - for cell_id in cell_ids: - cells.add(cell_id) - - # Using the last precomputed containers, we're now building the dict which connects - # every face (hash) of the fracture to the cells that touch the face... - face_to_cells: Dict[FrozenSet[int], List[int]] = defaultdict(list) - for cell_id in tqdm(cells, desc="Computing the cell to cell graph"): - cell: vtkCell = mesh.GetCell(cell_id) - for face_id in range(cell.GetNumberOfFaces()): - face_hash: FrozenSet[int] = frozenset(vtk_iter(cell.GetFace(face_id).GetPointIds())) - if face_hash not in face_hashes: - face_to_cells[face_hash].append(cell_id) - - # ... eventually, when a face touches two cells, this means that those two cells share the same face - # and should be connected in the final cell to cell graph. - cell_to_cell = networkx.Graph() - cell_to_cell.add_nodes_from(cells) - cell_to_cell.add_edges_from(filter(lambda cs: len(cs) == 2, face_to_cells.values())) - - return cell_to_cell - - -def __identify_split(num_points: int, - cell_to_cell: networkx.Graph, - node_to_cells: Mapping[int, Iterable[int]]) -> Mapping[int, Mapping[int, int]]: - """ - For each cell, compute the node indices replacements. - :param num_points: Number of points in the whole mesh (not the fracture). - :param cell_to_cell: The cell to cell graph (connection through common faces). - :param node_to_cells: Maps the nodes of the fracture to the cells relying on this node. - :return: For each cell (first key), returns a mapping from the current index - and the new index that should replace the current index. - Note that the current index and the new index can be identical: no replacement should be done then. - """ - - class NewIndex: - """ - Returns the next available index. - Note that the first time an index is met, the index itself is returned: - we do not want to change an index if we do not have to. - """ - def __init__(self, num_nodes: int): - self.__current_last_index = num_nodes - 1 - self.__seen: Set[int] = set() - - def __call__(self, index: int) -> int: - if index in self.__seen: - self.__current_last_index += 1 - return self.__current_last_index - else: - self.__seen.add(index) - return index - - build_new_index = NewIndex(num_points) - result: Dict[int, Dict[int, int]] = defaultdict(dict) - for node, cells in tqdm(sorted(node_to_cells.items()), # Iteration over `sorted` nodes to have a predictable result for tests. - desc="Identifying the node splits"): - for connected_cells in networkx.connected_components(cell_to_cell.subgraph(cells)): - # Each group of connect cells need around `node` must consider the same `node`. - # Separate groups must have different (duplicated) nodes. - new_index: int = build_new_index(node) - for cell in connected_cells: - result[cell][node] = new_index - return result - - -def __copy_fields(old_mesh: vtkUnstructuredGrid, - new_mesh: vtkUnstructuredGrid, - collocated_nodes: Sequence[int]) -> None: - """ - Copies the fields from the old mesh to the new one. - Point data will be duplicated for collocated nodes. - :param old_mesh: The mesh before the split. - :param new_mesh: The mesh after the split. Will receive the fields in place. - :param collocated_nodes: New index to old index. - :return: None - """ - # Copying the cell data. - # The cells are the same, just their nodes support have changed. - input_cell_data = old_mesh.GetCellData() - for i in range(input_cell_data.GetNumberOfArrays()): - input_array = input_cell_data.GetArray(i) - logging.info(f"Copying cell field \"{input_array.GetName()}\".") - new_mesh.GetCellData().AddArray(input_array) - - # Copying field data. This data is a priori not related to geometry. - input_field_data = old_mesh.GetFieldData() - for i in range(input_field_data.GetNumberOfArrays()): - input_array = input_field_data.GetArray(i) - logging.info(f"Copying field data \"{input_array.GetName()}\".") - new_mesh.GetFieldData().AddArray(input_array) - - # Copying the point data. - input_point_data = old_mesh.GetPointData() - for i in range(input_point_data.GetNumberOfArrays()): - input_array = input_point_data.GetArray(i) - logging.info(f"Copying point field \"{input_array.GetName()}\"") - tmp = input_array.NewInstance() - tmp.SetName(input_array.GetName()) - tmp.SetNumberOfComponents(input_array.GetNumberOfComponents()) - tmp.SetNumberOfTuples(new_mesh.GetNumberOfPoints()) - for p in range(tmp.GetNumberOfTuples()): - tmp.SetTuple(p, input_array.GetTuple(collocated_nodes[p])) - new_mesh.GetPointData().AddArray(tmp) - - -def __perform_split(old_mesh: vtkUnstructuredGrid, - cell_to_node_mapping: Mapping[int, Mapping[int, int]]) -> vtkUnstructuredGrid: - """ - Split the main 3d mesh based on the node duplication information contained in @p cell_to_node_mapping - :param old_mesh: The main 3d mesh. - :param cell_to_node_mapping: For each cell, gives the nodes that must be duplicated and their new index. - :return: The main 3d mesh split at the fracture location. - """ - added_points: Set[int] = set() - for node_mapping in cell_to_node_mapping.values(): - for i, o in node_mapping.items(): - if i != o: - added_points.add(o) - num_new_points: int = old_mesh.GetNumberOfPoints() + len(added_points) - - # Creating the new points for the new mesh. - old_points: vtkPoints = old_mesh.GetPoints() - new_points = vtkPoints() - new_points.SetNumberOfPoints(num_new_points) - collocated_nodes = numpy.ones(num_new_points, dtype=int) * -1 - # Copying old points into the new container. - for p in range(old_points.GetNumberOfPoints()): - new_points.SetPoint(p, old_points.GetPoint(p)) - collocated_nodes[p] = p - # Creating the new collocated/duplicated points based on the old points positions. - for node_mapping in cell_to_node_mapping.values(): - for i, o in node_mapping.items(): - if i != o: - new_points.SetPoint(o, old_points.GetPoint(i)) - collocated_nodes[o] = i - collocated_nodes.flags.writeable = False - - # We are creating a new mesh. - # The cells will be the same, except that their nodes may be duplicated or renumbered nodes. - # In vtk, the polyhedron and the standard cells are managed differently. - # Also, it looks like the internal representation is being modified - # (see https://gitlab.kitware.com/vtk/vtk/-/merge_requests/9812) - # so we'll try nothing fancy for the moment. - # Maybe in the future using a `DeepCopy` of the vtkCellArray can be considered? - # The cell point ids could be modified in place then. - new_mesh = old_mesh.NewInstance() - new_mesh.SetPoints(new_points) - new_mesh.Allocate(old_mesh.GetNumberOfCells()) - - for c in tqdm(range(old_mesh.GetNumberOfCells()), desc="Performing the mesh split"): - node_mapping: Mapping[int, int] = cell_to_node_mapping.get(c, {}) - cell: vtkCell = old_mesh.GetCell(c) - cell_type: int = cell.GetCellType() - # For polyhedron, we'll manipulate the face stream directly. - if cell_type == VTK_POLYHEDRON: - face_stream = vtkIdList() - old_mesh.GetFaceStream(c, face_stream) - new_face_nodes: List[List[int]] = [] - for face_nodes in FaceStream.build_from_vtk_id_list(face_stream).face_nodes: - new_point_ids = [] - for current_point_id in face_nodes: - new_point_id: int = node_mapping.get(current_point_id, current_point_id) - new_point_ids.append(new_point_id) - new_face_nodes.append(new_point_ids) - new_mesh.InsertNextCell(cell_type, to_vtk_id_list(FaceStream(new_face_nodes).dump())) - else: - # For the standard cells, we extract the point ids of the cell directly. - # Then the values will be (potentially) overwritten in place, before being sent back into the cell. - cell_point_ids: vtkIdList = cell.GetPointIds() - for i in range(cell_point_ids.GetNumberOfIds()): - current_point_id: int = cell_point_ids.GetId(i) - new_point_id: int = node_mapping.get(current_point_id, current_point_id) - cell_point_ids.SetId(i, new_point_id) - new_mesh.InsertNextCell(cell_type, cell_point_ids) - - __copy_fields(old_mesh, new_mesh, collocated_nodes) - - return new_mesh - - -def __generate_fracture_mesh(mesh_points: vtkPoints, - fracture_info: FractureInfo, - cell_to_node_mapping: Mapping[int, Mapping[int, int]]) -> vtkUnstructuredGrid: - """ - Generates the mesh of the fracture. - :param mesh_points: The points of the main 3d mesh. - :param fracture_info: The fracture description. - :param cell_to_node_mapping: For each cell, gives the nodes that must be duplicated and their new index. - :return: The fracture mesh. - """ - logging.info("Generating the meshes") - - is_node_duplicated = numpy.zeros(mesh_points.GetNumberOfPoints(), dtype=bool) # defaults to False - for node_mapping in cell_to_node_mapping.values(): - for i, o in node_mapping.items(): - if not is_node_duplicated[i]: - is_node_duplicated[i] = i != o - - # Some elements can have all their nodes not duplicated. - # In this case, it's mandatory not get rid of this element - # because the neighboring 3d elements won't follow. - face_nodes: List[Collection[int]] = [] - discarded_face_nodes: Set[Iterable[int]] = set() - for ns in fracture_info.face_nodes: - if any(map(is_node_duplicated.__getitem__, ns)): - face_nodes.append(ns) - else: - discarded_face_nodes.add(ns) - - if discarded_face_nodes: - # tmp = [] - # for dfns in discarded_face_nodes: - # tmp.append(", ".join(map(str, dfns))) - msg: str = "(" + '), ('.join(map(lambda dfns: ", ".join(map(str, dfns)), discarded_face_nodes)) + ")" - # logging.info(f"The {len(tmp)} faces made of nodes ({'), ('.join(tmp)}) were/was discarded from the fracture mesh because none of their/its nodes were duplicated.") - # print(f"The {len(tmp)} faces made of nodes ({'), ('.join(tmp)}) were/was discarded from the fracture mesh because none of their/its nodes were duplicated.") - print(f"The faces made of nodes [{msg}] were/was discarded from the fracture mesh because none of their/its nodes were duplicated.") - - fracture_nodes_tmp = numpy.ones(mesh_points.GetNumberOfPoints(), dtype=int) * -1 - for ns in face_nodes: - for n in ns: - fracture_nodes_tmp[n] = n - fracture_nodes: Collection[int] = tuple(filter(lambda n: n > -1, fracture_nodes_tmp)) - num_points: int = len(fracture_nodes) - points = vtkPoints() - points.SetNumberOfPoints(num_points) - node_3d_to_node_2d: Dict[int, int] = {} # Building the node mapping, from 3d mesh nodes to 2d fracture nodes. - for i, n in enumerate(fracture_nodes): - coords: Tuple[float, float, float] = mesh_points.GetPoint(n) - points.SetPoint(i, coords) - node_3d_to_node_2d[n] = i - - polygons = vtkCellArray() - for ns in face_nodes: - polygon = vtkPolygon() - polygon.GetPointIds().SetNumberOfIds(len(ns)) - for i, n in enumerate(ns): - polygon.GetPointIds().SetId(i, node_3d_to_node_2d[n]) - polygons.InsertNextCell(polygon) - - buckets: Dict[int, Set[int]] = defaultdict(set) - for node_mapping in cell_to_node_mapping.values(): - for i, o in node_mapping.items(): - k: Optional[int] = node_3d_to_node_2d.get(min(i, o)) - if k is not None: - buckets[k].update((i, o)) - - assert set(buckets.keys()) == set(range(num_points)) - max_collocated_nodes: int = max(map(len, buckets.values())) if buckets.values() else 0 - collocated_nodes = numpy.ones((num_points, max_collocated_nodes), dtype=int) * -1 - for i, bucket in buckets.items(): - for j, val in enumerate(bucket): - collocated_nodes[i, j] = val - array = numpy_to_vtk(collocated_nodes, array_type=VTK_ID_TYPE) - array.SetName("collocated_nodes") - - fracture_mesh = vtkUnstructuredGrid() # We could be using vtkPolyData, but it's not supported by GEOS for now. - fracture_mesh.SetPoints(points) - if polygons.GetNumberOfCells() > 0: - fracture_mesh.SetCells([VTK_POLYGON] * polygons.GetNumberOfCells(), polygons) - fracture_mesh.GetPointData().AddArray(array) - return fracture_mesh - - -def __split_mesh_on_fracture(mesh: vtkUnstructuredGrid, - options: Options) -> Tuple[vtkUnstructuredGrid, vtkUnstructuredGrid]: - fracture: FractureInfo = build_fracture_info(mesh, options) - cell_to_cell: networkx.Graph = build_cell_to_cell_graph(mesh, fracture) - cell_to_node_mapping: Mapping[int, Mapping[int, int]] = __identify_split(mesh.GetNumberOfPoints(), - cell_to_cell, - fracture.node_to_cells) - output_mesh: vtkUnstructuredGrid = __perform_split(mesh, cell_to_node_mapping) - fractured_mesh: vtkUnstructuredGrid = __generate_fracture_mesh(mesh.GetPoints(), fracture, cell_to_node_mapping) - return output_mesh, fractured_mesh - - -def __check(mesh, options: Options) -> Result: - output_mesh, fracture_mesh = __split_mesh_on_fracture(mesh, options) - vtk_utils.write_mesh(output_mesh, options.vtk_output) - vtk_utils.write_mesh(fracture_mesh, options.vtk_fracture_output) - # TODO provide statistics about what was actually performed (size of the fracture, number of split nodes...). - return Result(info="OK") - - -def check(vtk_input_file: str, options: Options) -> Result: - try: - mesh = vtk_utils.read_mesh(vtk_input_file) - return __check(mesh, options) - except BaseException as e: - logging.error(e) - return Result(info="Something went wrong") diff --git a/src/coreComponents/python/modules/geosx_mesh_doctor/checks/generate_global_ids.py b/src/coreComponents/python/modules/geosx_mesh_doctor/checks/generate_global_ids.py deleted file mode 100644 index 80474e22358..00000000000 --- a/src/coreComponents/python/modules/geosx_mesh_doctor/checks/generate_global_ids.py +++ /dev/null @@ -1,68 +0,0 @@ -from dataclasses import dataclass -import logging - -from vtkmodules.vtkCommonCore import ( - vtkIdTypeArray, ) - -from . import vtk_utils -from .vtk_utils import ( - VtkOutput, -) - - -@dataclass(frozen=True) -class Options: - vtk_output: VtkOutput - generate_cells_global_ids: bool - generate_points_global_ids: bool - - -@dataclass(frozen=True) -class Result: - info: str - - -def __build_global_ids(mesh, - generate_cells_global_ids: bool, - generate_points_global_ids: bool) -> None: - """ - Adds the global ids for cells and points in place into the mesh instance. - :param mesh: - :return: None - """ - # Building GLOBAL_IDS for points and cells.g GLOBAL_IDS for points and cells. - # First for points... - if mesh.GetPointData().GetGlobalIds(): - logging.error("Mesh already has globals ids for points; nothing done.") - elif generate_points_global_ids: - point_global_ids = vtkIdTypeArray() - point_global_ids.SetName("GLOBAL_IDS_POINTS") - point_global_ids.Allocate(mesh.GetNumberOfPoints()) - for i in range(mesh.GetNumberOfPoints()): - point_global_ids.InsertNextValue(i) - mesh.GetPointData().SetGlobalIds(point_global_ids) - # ... then for cells. - if mesh.GetCellData().GetGlobalIds(): - logging.error("Mesh already has globals ids for cells; nothing done.") - elif generate_cells_global_ids: - cells_global_ids = vtkIdTypeArray() - cells_global_ids.SetName("GLOBAL_IDS_CELLS") - cells_global_ids.Allocate(mesh.GetNumberOfCells()) - for i in range(mesh.GetNumberOfCells()): - cells_global_ids.InsertNextValue(i) - mesh.GetCellData().SetGlobalIds(cells_global_ids) - - -def __check(mesh, options: Options) -> Result: - __build_global_ids(mesh, options.generate_cells_global_ids, options.generate_points_global_ids) - vtk_utils.write_mesh(mesh, options.vtk_output) - return Result(info=f"Mesh was written to {options.vtk_output.output}") - - -def check(vtk_input_file: str, options: Options) -> Result: - try: - mesh = vtk_utils.read_mesh(vtk_input_file) - return __check(mesh, options) - except BaseException as e: - logging.error(e) - return Result(info="Something went wrong.") diff --git a/src/coreComponents/python/modules/geosx_mesh_doctor/checks/non_conformal.py b/src/coreComponents/python/modules/geosx_mesh_doctor/checks/non_conformal.py deleted file mode 100644 index 43f26e2391d..00000000000 --- a/src/coreComponents/python/modules/geosx_mesh_doctor/checks/non_conformal.py +++ /dev/null @@ -1,432 +0,0 @@ -from dataclasses import dataclass -import math -from typing import List, Tuple, Any -import numpy - -from tqdm import tqdm - -from vtkmodules.vtkCommonCore import ( - vtkIdList, - vtkPoints, -) -from vtkmodules.vtkCommonDataModel import ( - VTK_POLYHEDRON, - vtkBoundingBox, - vtkCell, - vtkCellArray, - vtkPointSet, - vtkPolyData, - vtkStaticCellLocator, - vtkStaticPointLocator, - vtkUnstructuredGrid, -) -from vtkmodules.vtkCommonTransforms import ( - vtkTransform, -) -from vtkmodules.vtkFiltersCore import ( - vtkPolyDataNormals, -) -from vtkmodules.vtkFiltersGeometry import ( - vtkDataSetSurfaceFilter, -) -from vtkmodules.vtkFiltersModeling import ( - vtkCollisionDetectionFilter, - vtkLinearExtrusionFilter, -) -from vtk import reference as vtk_reference - -from .reorient_mesh import reorient_mesh - -from . import vtk_utils - -from .vtk_polyhedron import ( - vtk_iter, -) - -from . import triangle_distance - - -@dataclass(frozen=True) -class Options: - angle_tolerance: float - point_tolerance: float - face_tolerance: float - - -@dataclass(frozen=True) -class Result: - non_conformal_cells: List[Tuple[int, int]] - - -class BoundaryMesh: - """ - A BoundaryMesh is the envelope of the 3d mesh on which we want to perform the simulations. - It is computed by vtk. But we want to be sure that the normals of the envelope are directed outwards. - The `vtkDataSetSurfaceFilter` does not have the same behavior for standard vtk cells (like tets or hexs), - and for polyhedron meshes, for which the result is a bit brittle. - Therefore, we reorient the polyhedron cells ourselves, so we're sure that they point outwards. - And then we compute the boundary meshes for both meshes, given that the computing options are not identical. - """ - def __init__(self, mesh: vtkUnstructuredGrid): - """ - Builds a boundary mesh. - :param mesh: The 3d mesh. - """ - # Building the boundary meshes - boundary_mesh, __normals, self.__original_cells = BoundaryMesh.__build_boundary_mesh(mesh) - cells_to_reorient = filter(lambda c: mesh.GetCell(c).GetCellType() == VTK_POLYHEDRON, - map(self.__original_cells.GetValue, - range(self.__original_cells.GetNumberOfValues()))) - reoriented_mesh = reorient_mesh(mesh, cells_to_reorient) - self.re_boundary_mesh, re_normals, _ = BoundaryMesh.__build_boundary_mesh(reoriented_mesh, consistency=False) - num_cells = boundary_mesh.GetNumberOfCells() - # Precomputing the underlying cell type - self.__is_underlying_cell_type_a_polyhedron = numpy.zeros(num_cells, dtype=bool) - for ic in range(num_cells): - self.__is_underlying_cell_type_a_polyhedron[ic] = mesh.GetCell(self.__original_cells.GetValue(ic)).GetCellType() == VTK_POLYHEDRON - # Precomputing the normals - self.__normals: numpy.ndarray = numpy.empty((num_cells, 3), dtype=numpy.double, order='C') # Do not modify the storage layout - for ic in range(num_cells): - if self.__is_underlying_cell_type_a_polyhedron[ic]: - self.__normals[ic, :] = re_normals.GetTuple3(ic) - else: - self.__normals[ic, :] = __normals.GetTuple3(ic) - @staticmethod - def __build_boundary_mesh(mesh: vtkUnstructuredGrid, consistency=True) -> Tuple[vtkUnstructuredGrid, Any, Any]: - """ - From a 3d mesh, build the envelope meshes. - :param mesh: The input 3d mesh. - :param consistency: The vtk option passed to the `vtkDataSetSurfaceFilter`. - :return: A tuple containing the boundary mesh, the normal vectors array, - an array that maps the id of the boundary element to the id of the 3d cell it touches. - """ - f = vtkDataSetSurfaceFilter() - f.PassThroughCellIdsOn() - f.PassThroughPointIdsOff() - f.FastModeOff() - - # Note that we do not need the original points, but we could keep them as well if needed - original_cells_key = "ORIGINAL_CELLS" - f.SetOriginalCellIdsName(original_cells_key) - - boundary_mesh = vtkPolyData() - f.UnstructuredGridExecute(mesh, boundary_mesh) - - n = vtkPolyDataNormals() - n.SetConsistency(consistency) - n.SetAutoOrientNormals(consistency) - n.FlipNormalsOff() - n.ComputeCellNormalsOn() - n.SetInputData(boundary_mesh) - n.Update() - normals = n.GetOutput().GetCellData().GetArray("Normals") - assert normals - assert normals.GetNumberOfComponents() == 3 - assert normals.GetNumberOfTuples() == boundary_mesh.GetNumberOfCells() - original_cells = boundary_mesh.GetCellData().GetArray(original_cells_key) - assert original_cells - return boundary_mesh, normals, original_cells - - def GetNumberOfCells(self) -> int: - """ - The number of cells. - :return: An integer. - """ - return self.re_boundary_mesh.GetNumberOfCells() - - def GetNumberOfPoints(self) -> int: - """ - The number of points. - :return: An integer. - """ - return self.re_boundary_mesh.GetNumberOfPoints() - - def bounds(self, i) -> Tuple[float, float, float, float, float, float]: - """ - The boundrary box of cell `i`. - :param i: The boundary cell index. - :return: The vtk bounding box. - """ - return self.re_boundary_mesh.GetCell(i).GetBounds() - - def normals(self, i) -> numpy.ndarray: - """ - The normal of cell `i`. This normal will be directed outwards - :param i: The boundary cell index. - :return: The normal as a length-3 numpy array. - """ - return self.__normals[i] - - def GetCell(self, i) -> vtkCell: - """ - Cell i of the boundary mesh. This cell will have its normal directed outwards. - :param i: The boundary cell index. - :return: The cell instance. - :warning: This member function relies on the vtkUnstructuredGrid.GetCell member function which is not thread safe. - """ - return self.re_boundary_mesh.GetCell(i) - - def GetPoint(self, i) -> Tuple[float, float, float]: - """ - Point i of the boundary mesh. - :param i: The boundary point index. - :return: A length-3 tuple containing the coordinates of the point. - :warning: This member function relies on the vtkUnstructuredGrid.GetPoint member function which is not thread safe. - """ - return self.re_boundary_mesh.GetPoint(i) - - @property - def original_cells(self): - """ - Returns the 2d boundary cell to the 3d cell index of the original mesh. - :return: A 1d array. - """ - return self.__original_cells - - -def build_poly_data_for_extrusion(i: int, boundary_mesh: BoundaryMesh) -> vtkPolyData: - """ - Creates a vtkPolyData containing the unique cell `i` of the boundary mesh. - This operation is needed to use the vtk extrusion filter. - :param i: The boundary cell index that will eventually be extruded. - :param boundary_mesh: - :return: The created vtkPolyData. - """ - cell = boundary_mesh.GetCell(i) - copied_cell = cell.NewInstance() - copied_cell.DeepCopy(cell) - points_ids_mapping = [] - for i in range(copied_cell.GetNumberOfPoints()): - copied_cell.GetPointIds().SetId(i, i) - points_ids_mapping.append(cell.GetPointId(i)) - polygons = vtkCellArray() - polygons.InsertNextCell(copied_cell) - points = vtkPoints() - points.SetNumberOfPoints(len(points_ids_mapping)) - for i, v in enumerate(points_ids_mapping): - points.SetPoint(i, boundary_mesh.GetPoint(v)) - polygon_poly_data = vtkPolyData() - polygon_poly_data.SetPoints(points) - polygon_poly_data.SetPolys(polygons) - return polygon_poly_data - - -def are_points_conformal(point_tolerance: float, cell_i: vtkCell, cell_j: vtkCell) -> bool: - """ - Checks if points of cell `i` matches, one by one, the points of cell `j`. - :param point_tolerance: The point tolerance to consider that two points match. - :param cell_i: The first cell. - :param cell_j: The second cell. - :return: A boolean. - """ - # In this last step, we check that the nodes are (or not) matching each other. - if cell_i.GetNumberOfPoints() != cell_j.GetNumberOfPoints(): - return True - - point_locator = vtkStaticPointLocator() - points = vtkPointSet() - points.SetPoints(cell_i.GetPoints()) - point_locator.SetDataSet(points) - point_locator.BuildLocator() - found_points = set() - for ip in range(cell_j.GetNumberOfPoints()): - p = cell_j.GetPoints().GetPoint(ip) - squared_dist = vtk_reference(0.) # unused - found_point = point_locator.FindClosestPointWithinRadius(point_tolerance, p, squared_dist) - found_points.add(found_point) - return found_points == set(range(cell_i.GetNumberOfPoints())) - - -class Extruder: - """ - Computes and stores all the extrusions of the boundary faces. - The main reason for this class is to be lazy and cache the extrusions. - """ - def __init__(self, boundary_mesh: BoundaryMesh, face_tolerance: float): - self.__extrusions: List[vtkPolyData] = [None, ] * boundary_mesh.GetNumberOfCells() - self.__boundary_mesh = boundary_mesh - self.__face_tolerance = face_tolerance - - def __extrude(self, polygon_poly_data, normal) -> vtkPolyData: - """ - Extrude the polygon data to create a volume that will be used for intersection. - :param polygon_poly_data: The data to extrude - :param normal: The (uniform) direction of the extrusion. - :return: The extrusion. - """ - extruder = vtkLinearExtrusionFilter() - extruder.SetExtrusionTypeToVectorExtrusion() - extruder.SetVector(normal) - extruder.SetScaleFactor(self.__face_tolerance / 2.) - extruder.SetInputData(polygon_poly_data) - extruder.Update() - return extruder.GetOutput() - - def __getitem__(self, i) -> vtkPolyData: - """ - Returns the vtk extrusion for boundary element i. - :param i: The cell index. - :return: The vtk instance. - """ - extrusion = self.__extrusions[i] - if extrusion: - return extrusion - extrusion = self.__extrude(build_poly_data_for_extrusion(i, self.__boundary_mesh), - self.__boundary_mesh.normals(i)) - self.__extrusions[i] = extrusion - return extrusion - - -def are_faces_conformal_using_extrusions(extrusions: Extruder, - i: int, j: int, - boundary_mesh: vtkUnstructuredGrid, - point_tolerance: float) -> bool: - """ - Tests if two boundary faces are conformal, checking for intersection between their normal extruded volumes. - :param extrusions: The extrusions cache. - :param i: The cell index of the first cell. - :param j: The cell index of the second cell. - :param boundary_mesh: The boundary mesh. - :param point_tolerance: The point tolerance to consider that two points match. - :return: A boolean. - """ - collision = vtkCollisionDetectionFilter() - collision.SetCollisionModeToFirstContact() - collision.SetInputData(0, extrusions[i]) - collision.SetInputData(1, extrusions[j]) - m_i = vtkTransform() - m_j = vtkTransform() - collision.SetTransform(0, m_i) - collision.SetTransform(1, m_j) - collision.Update() - - if collision.GetNumberOfContacts() == 0: - return True - - # Duplicating data not to risk anything w.r.t. thread safety of the GetCell function. - cell_i = boundary_mesh.GetCell(i) - copied_cell_i = cell_i.NewInstance() - copied_cell_i.DeepCopy(cell_i) - - return are_points_conformal(point_tolerance, copied_cell_i, boundary_mesh.GetCell(j)) - - -def are_faces_conformal_using_distances(i: int, j: int, - boundary_mesh: vtkUnstructuredGrid, - face_tolerance: float, point_tolerance: float) -> bool: - """ - Tests if two boundary faces are conformal, checking the minimal distance between triangulated surfaces. - :param i: The cell index of the first cell. - :param j: The cell index of the second cell. - :param boundary_mesh: The boundary mesh. - :param face_tolerance: The tolerance under which we should consider the two faces "touching" each other. - :param point_tolerance: The point tolerance to consider that two points match. - :return: A boolean. - """ - cp_i = boundary_mesh.GetCell(i).NewInstance() - cp_i.DeepCopy(boundary_mesh.GetCell(i)) - cp_j = boundary_mesh.GetCell(j).NewInstance() - cp_j.DeepCopy(boundary_mesh.GetCell(j)) - - def triangulate(cell): - assert cell.GetCellDimension() == 2 - __points_ids = vtkIdList() - __points = vtkPoints() - cell.Triangulate(0, __points_ids, __points) - __points_ids = tuple(vtk_iter(__points_ids)) - assert len(__points_ids) % 3 == 0 - assert __points.GetNumberOfPoints() % 3 == 0 - return __points_ids, __points - - points_ids_i, points_i = triangulate(cp_i) - points_ids_j, points_j = triangulate(cp_j) - - def build_numpy_triangles(points_ids): - __triangles = [] - for __i in range(0, len(points_ids), 3): - __t = [] - for __pi in points_ids[__i: __i + 3]: - __t.append(boundary_mesh.GetPoint(__pi)) - __triangles.append(numpy.array(__t, dtype=float)) - return __triangles - - triangles_i = build_numpy_triangles(points_ids_i) - triangles_j = build_numpy_triangles(points_ids_j) - - min_dist = numpy.inf - for ti, tj in [(ti, tj) for ti in triangles_i for tj in triangles_j]: - # Note that here, we compute the exact distance to compare with the threshold. - # We could improve by exiting the iterative distance computation as soon as - # we're sure we're smaller than the threshold. No need of the exact solution. - dist, _, _ = triangle_distance.distance_between_two_triangles(ti, tj) - if dist < min_dist: - min_dist = dist - if min_dist < face_tolerance: - break - if min_dist > face_tolerance: - return True - - return are_points_conformal(point_tolerance, cp_i, cp_j) - - -def __check(mesh: vtkUnstructuredGrid, options: Options) -> Result: - """ - Checks if the mesh is "conformal" (i.e. if some of its boundary faces may not be too close to each other without matching nodes). - :param mesh: The vtk mesh - :param options: The check options. - :return: The Result instance. - """ - boundary_mesh = BoundaryMesh(mesh) - cos_theta = abs(math.cos(numpy.deg2rad(options.angle_tolerance))) - num_cells = boundary_mesh.GetNumberOfCells() - - # Computing the exact number of cells per node - num_cells_per_node = numpy.zeros(boundary_mesh.GetNumberOfPoints(), dtype=int) - for ic in range(boundary_mesh.GetNumberOfCells()): - c = boundary_mesh.GetCell(ic) - point_ids = c.GetPointIds() - for point_id in vtk_iter(point_ids): - num_cells_per_node[point_id] += 1 - - cell_locator = vtkStaticCellLocator() - cell_locator.Initialize() - cell_locator.SetNumberOfCellsPerNode(num_cells_per_node.max()) - cell_locator.SetDataSet(boundary_mesh.re_boundary_mesh) - cell_locator.BuildLocator() - - # Precomputing the bounding boxes. - # The options are important to directly interact with memory in C++. - bounding_boxes = numpy.empty((boundary_mesh.GetNumberOfCells(), 6), dtype=numpy.double, order="C") - for i in range(boundary_mesh.GetNumberOfCells()): - bb = vtkBoundingBox(boundary_mesh.bounds(i)) - bb.Inflate(2 * options.face_tolerance) - assert bounding_boxes[i, :].data.contiguous # Do not modify the storage layout since vtk deals with raw memory here. - bb.GetBounds(bounding_boxes[i, :]) - - non_conformal_cells = [] - extrusions = Extruder(boundary_mesh, options.face_tolerance) - close_cells = vtkIdList() - # Looping on all the pairs of boundary cells. We'll hopefully discard most of the pairs. - for i in tqdm(range(num_cells), desc="Non conformal elements"): - cell_locator.FindCellsWithinBounds(bounding_boxes[i], close_cells) - for j in vtk_iter(close_cells): - if j < i: - continue - # Discarding pairs that are not facing each others (with a threshold). - normal_i, normal_j = boundary_mesh.normals(i), boundary_mesh.normals(j) - if numpy.dot(normal_i, normal_j) > -cos_theta: # opposite directions only (can be facing or not) - continue - # At this point, back-to-back and face-to-face pairs of elements are considered. - if not are_faces_conformal_using_extrusions(extrusions, i, j, boundary_mesh, options.point_tolerance): - non_conformal_cells.append((i, j)) - # Extracting the original 3d element index (and not the index of the boundary mesh). - tmp = [] - for i, j in non_conformal_cells: - tmp.append((boundary_mesh.original_cells.GetValue(i), boundary_mesh.original_cells.GetValue(j))) - - return Result(non_conformal_cells=tmp) - - -def check(vtk_input_file: str, options: Options) -> Result: - mesh = vtk_utils.read_mesh(vtk_input_file) - return __check(mesh, options) diff --git a/src/coreComponents/python/modules/geosx_mesh_doctor/checks/reorient_mesh.py b/src/coreComponents/python/modules/geosx_mesh_doctor/checks/reorient_mesh.py deleted file mode 100644 index efb664bc06c..00000000000 --- a/src/coreComponents/python/modules/geosx_mesh_doctor/checks/reorient_mesh.py +++ /dev/null @@ -1,178 +0,0 @@ -import logging -from typing import ( - Dict, - FrozenSet, - Iterator, - List, - Tuple, -) - -import numpy - -from tqdm import tqdm - -import networkx - -from vtkmodules.vtkCommonCore import ( - vtkIdList, - vtkPoints, -) -from vtkmodules.vtkCommonDataModel import ( - VTK_POLYHEDRON, - VTK_TRIANGLE, - vtkCellArray, - vtkPolyData, - vtkPolygon, - vtkUnstructuredGrid, - vtkTetra, -) -from vtkmodules.vtkFiltersCore import ( - vtkTriangleFilter, -) -from .vtk_utils import ( - to_vtk_id_list, -) - -from .vtk_polyhedron import ( - FaceStream, - build_face_to_face_connectivity_through_edges, -) - - -def __compute_volume(mesh_points: vtkPoints, face_stream: FaceStream) -> float: - """ - Computes the volume of a polyhedron element (defined by its face_stream). - :param mesh_points: The mesh points, needed to compute the volume. - :param face_stream: The vtk face stream. - :return: The volume of the element. - :note: The faces of the polyhedron are triangulated and the volumes of the tetrahedra - from the barycenter to the triangular bases are summed. - The normal of each face plays critical role, - since the volume of each tetrahedron can be positive or negative. - """ - # Triangulating the envelope of the polyhedron for further volume computation. - polygons = vtkCellArray() - for face_nodes in face_stream.face_nodes: - polygon = vtkPolygon() - polygon.GetPointIds().SetNumberOfIds(len(face_nodes)) - # We use the same global points numbering for the polygons than for the input mesh. - # There will be a lot of points in the poly data that won't be used as a support for the polygons. - # But the algorithm deals with it, and it's actually faster (and easier) to do this - # than to renumber and allocate a new fit-for-purpose set of points just for the polygons. - for i, point_id in enumerate(face_nodes): - polygon.GetPointIds().SetId(i, point_id) - polygons.InsertNextCell(polygon) - polygon_poly_data = vtkPolyData() - polygon_poly_data.SetPoints(mesh_points) - polygon_poly_data.SetPolys(polygons) - - f = vtkTriangleFilter() - f.SetInputData(polygon_poly_data) - f.Update() - triangles = f.GetOutput() - # Computing the barycenter that will be used as the tip of all the tetra which mesh the polyhedron. - # (The basis of all the tetra being the triangles of the envelope). - # We could take any point, not only the barycenter. - # But in order to work with figure of the same magnitude, let's compute the barycenter. - tmp_barycenter = numpy.empty((face_stream.num_support_points, 3), dtype=float) - for i, point_id in enumerate(face_stream.support_point_ids): - tmp_barycenter[i, :] = mesh_points.GetPoint(point_id) - barycenter = tmp_barycenter[:, 0].mean(), tmp_barycenter[:, 1].mean(), tmp_barycenter[:, 2].mean() - # Looping on all the triangles of the envelope of the polyhedron, creating the matching tetra. - # Then the volume of all the tetra are added to get the final polyhedron volume. - cell_volume = 0. - for i in range(triangles.GetNumberOfCells()): - triangle = triangles.GetCell(i) - assert triangle.GetCellType() == VTK_TRIANGLE - p = triangle.GetPoints() - cell_volume += vtkTetra.ComputeVolume(barycenter, p.GetPoint(0), p.GetPoint(1), p.GetPoint(2)) - return cell_volume - - -def __select_and_flip_faces(mesh_points: vtkPoints, - colors: Dict[FrozenSet[int], int], - face_stream: FaceStream) -> FaceStream: - """ - Given a polyhedra, given that we were able to paint the faces in two colors, - we now need to select which faces/color to flip such that the volume of the element is positive. - :param mesh_points: The mesh points, needed to compute the volume. - :param colors: Maps the nodes of each connected component (defined as a frozenset) to its color. - :param face_stream: the polyhedron. - :return: The face stream that leads to a positive volume. - """ - # Flipping either color 0 or 1. - color_to_nodes: Dict[int, List[int]] = {0: [], 1: []} - for connected_components_indices, color in colors.items(): - color_to_nodes[color] += connected_components_indices - # This implementation works even if there is one unique color. - # Admittedly, there will be one face stream that won't be flipped. - fs: Tuple[FaceStream, FaceStream] = face_stream.flip_faces(color_to_nodes[0]), face_stream.flip_faces(color_to_nodes[1]) - volumes = __compute_volume(mesh_points, fs[0]), __compute_volume(mesh_points, fs[1]) - # We keep the flipped element for which the volume is largest - # (i.e. positive, since they should be the opposite of each other). - return fs[numpy.argmax(volumes)] - - -def __reorient_element(mesh_points: vtkPoints, face_stream_ids: vtkIdList) -> vtkIdList: - """ - Considers a vtk face stream and flips the appropriate faces to get an element with normals directed outwards. - :param mesh_points: The mesh points, needed to compute the volume. - :param face_stream_ids: The raw vtk face stream, not converted into a more practical python class. - :return: The raw vtk face stream with faces properly flipped. - """ - face_stream = FaceStream.build_from_vtk_id_list(face_stream_ids) - face_graph = build_face_to_face_connectivity_through_edges(face_stream, add_compatibility=True) - # Removing the non-compatible connections to build the non-connected components. - g = networkx.Graph() - g.add_nodes_from(face_graph.nodes) - g.add_edges_from(filter(lambda uvd: uvd[2]["compatible"] == "+", face_graph.edges(data=True))) - connected_components = tuple(networkx.connected_components(g)) - # Squashing all the connected nodes that need to receive the normal direction flip (or not) together. - quotient_graph = networkx.algorithms.quotient_graph(face_graph, connected_components) - # Coloring the new graph lets us know how which cluster of faces need to eventually receive the same flip. - # W.r.t. the nature of our problem (a normal can be directed inwards or outwards), - # two colors should be enough to color the face graph. - # `colors` maps the nodes of each connected component to its color. - colors: Dict[FrozenSet[int], int] = networkx.algorithms.greedy_color(quotient_graph) - assert len(colors) in (1, 2) - # We now compute the face stream which generates outwards normal vectors. - flipped_face_stream = __select_and_flip_faces(mesh_points, colors, face_stream) - return to_vtk_id_list(flipped_face_stream.dump()) - - -def reorient_mesh(mesh, cell_indices: Iterator[int]) -> vtkUnstructuredGrid: - """ - Reorient the polyhedron elements such that they all have their normals directed outwards. - :param mesh: The input vtk mesh. - :param cell_indices: We may need to only flip a limited number of polyhedron cells (only on the boundary for example). - :return: The vtk mesh with the desired polyhedron cells directed outwards. - """ - num_cells = mesh.GetNumberOfCells() - # Building an indicator/predicate from the list - needs_to_be_reoriented = numpy.zeros(num_cells, dtype=bool) - for ic in cell_indices: - needs_to_be_reoriented[ic] = True - - output_mesh = mesh.NewInstance() - # I did not manage to call `output_mesh.CopyStructure(mesh)` because I could not modify the polyhedron in place. - # Therefore, I insert the cells one by one... - output_mesh.SetPoints(mesh.GetPoints()) - logging.info("Reorienting the polyhedron cells to enforce normals directed outward.") - with tqdm(total=needs_to_be_reoriented.sum(), desc="Reorienting polyhedra") as progress_bar: # For smoother progress, we only update on reoriented elements. - for ic in range(num_cells): - cell = mesh.GetCell(ic) - cell_type = cell.GetCellType() - if cell_type == VTK_POLYHEDRON: - face_stream_ids = vtkIdList() - mesh.GetFaceStream(ic, face_stream_ids) - if needs_to_be_reoriented[ic]: - new_face_stream_ids = __reorient_element(mesh.GetPoints(), face_stream_ids) - else: - new_face_stream_ids = face_stream_ids - output_mesh.InsertNextCell(VTK_POLYHEDRON, new_face_stream_ids) - else: - output_mesh.InsertNextCell(cell_type, cell.GetPointIds()) - if needs_to_be_reoriented[ic]: - progress_bar.update(1) - assert output_mesh.GetNumberOfCells() == mesh.GetNumberOfCells() - return output_mesh diff --git a/src/coreComponents/python/modules/geosx_mesh_doctor/checks/self_intersecting_elements.py b/src/coreComponents/python/modules/geosx_mesh_doctor/checks/self_intersecting_elements.py deleted file mode 100644 index 0e98d4f5b49..00000000000 --- a/src/coreComponents/python/modules/geosx_mesh_doctor/checks/self_intersecting_elements.py +++ /dev/null @@ -1,92 +0,0 @@ -from dataclasses import dataclass -import logging -from typing import ( - Collection, - List, -) - -from vtkmodules.vtkFiltersGeneral import ( - vtkCellValidator -) -from vtkmodules.vtkCommonCore import ( - vtkOutputWindow, - vtkFileOutputWindow -) -from vtkmodules.util.numpy_support import ( - vtk_to_numpy, -) - -from . import vtk_utils - - -@dataclass(frozen=True) -class Options: - tolerance: float - - -@dataclass(frozen=True) -class Result: - wrong_number_of_points_elements: Collection[int] - intersecting_edges_elements: Collection[int] - intersecting_faces_elements: Collection[int] - non_contiguous_edges_elements: Collection[int] - non_convex_elements: Collection[int] - faces_are_oriented_incorrectly_elements: Collection[int] - - -def __check(mesh, options: Options) -> Result: - err_out = vtkFileOutputWindow() - err_out.SetFileName("/dev/null") # vtkCellValidator outputs loads for each cell... - vtk_std_err_out = vtkOutputWindow() - vtk_std_err_out.SetInstance(err_out) - - valid = 0x0 - wrong_number_of_points = 0x01 - intersecting_edges = 0x02 - intersecting_faces = 0x04 - non_contiguous_edges = 0x08 - non_convex = 0x10 - faces_are_oriented_incorrectly = 0x20 - - wrong_number_of_points_elements: List[int] = [] - intersecting_edges_elements: List[int] = [] - intersecting_faces_elements: List[int] = [] - non_contiguous_edges_elements: List[int] = [] - non_convex_elements: List[int] = [] - faces_are_oriented_incorrectly_elements: List[int] = [] - - f = vtkCellValidator() - f.SetTolerance(options.tolerance) - - f.SetInputData(mesh) - f.Update() - output = f.GetOutput() - - validity = output.GetCellData().GetArray("ValidityState") # Could not change name using the vtk interface. - assert validity is not None - validity = vtk_to_numpy(validity) - for i, v in enumerate(validity): - if not v & valid: - if v & wrong_number_of_points: - wrong_number_of_points_elements.append(i) - if v & intersecting_edges: - intersecting_edges_elements.append(i) - if v & intersecting_faces: - intersecting_faces_elements.append(i) - if v & non_contiguous_edges: - non_contiguous_edges_elements.append(i) - if v & non_convex: - non_convex_elements.append(i) - if v & faces_are_oriented_incorrectly: - faces_are_oriented_incorrectly_elements.append(i) - return Result(wrong_number_of_points_elements=wrong_number_of_points_elements, - intersecting_edges_elements=intersecting_edges_elements, - intersecting_faces_elements=intersecting_faces_elements, - non_contiguous_edges_elements=non_contiguous_edges_elements, - non_convex_elements=non_convex_elements, - faces_are_oriented_incorrectly_elements=faces_are_oriented_incorrectly_elements) - - -def check(vtk_input_file: str, options: Options) -> Result: - mesh = vtk_utils.read_mesh(vtk_input_file) - return __check(mesh, options) diff --git a/src/coreComponents/python/modules/geosx_mesh_doctor/checks/supported_elements.py b/src/coreComponents/python/modules/geosx_mesh_doctor/checks/supported_elements.py deleted file mode 100644 index 84c5fcbaf74..00000000000 --- a/src/coreComponents/python/modules/geosx_mesh_doctor/checks/supported_elements.py +++ /dev/null @@ -1,163 +0,0 @@ -from dataclasses import dataclass -import logging -import multiprocessing -from typing import ( - Collection, - FrozenSet, - Iterable, - Mapping, - Optional, - Sequence, - Set, -) - -from tqdm import tqdm - -import networkx -import numpy - -from vtkmodules.vtkCommonCore import ( - vtkIdList, -) -from vtkmodules.vtkCommonDataModel import ( - vtkCellTypes, - vtkUnstructuredGrid, - VTK_HEXAGONAL_PRISM, - VTK_HEXAHEDRON, - VTK_PENTAGONAL_PRISM, - VTK_POLYHEDRON, - VTK_PYRAMID, - VTK_TETRA, - VTK_VOXEL, - VTK_WEDGE, -) -from vtkmodules.util.numpy_support import ( - vtk_to_numpy, -) - -from . import vtk_utils -from .vtk_utils import vtk_iter -from .vtk_polyhedron import build_face_to_face_connectivity_through_edges, FaceStream - - -@dataclass(frozen=True) -class Options: - num_proc: int - chunk_size: int - - -@dataclass(frozen=True) -class Result: - unsupported_std_elements_types: FrozenSet[int] # list of unsupported types - unsupported_polyhedron_elements: FrozenSet[int] # list of polyhedron elements that could not be converted to supported std elements - - -MESH: Optional[vtkUnstructuredGrid] = None # for multiprocessing, vtkUnstructuredGrid cannot be pickled. Let's use a global variable instead. - - -class IsPolyhedronConvertible: - def __init__(self, mesh: vtkUnstructuredGrid): - global MESH # for multiprocessing, vtkUnstructuredGrid cannot be pickled. Let's use a global variable instead. - MESH = mesh - - def build_prism_graph(n: int, name: str) -> networkx.Graph: - """ - Builds the face to face connectivities (through edges) for prism graphs. - :param n: The number of nodes of the basis (i.e. the pentagonal prims gets n = 5) - :param name: A human-readable name for logging purpose. - :return: A graph instance. - """ - tmp = networkx.cycle_graph(n) - for node in range(n): - tmp.add_edge(node, n) - tmp.add_edge(node, n + 1) - tmp.name = name - return tmp - - # Building the reference graphs - tet_graph = networkx.complete_graph(4) - tet_graph.name = "Tetrahedron" - pyr_graph = build_prism_graph(4, "Pyramid") - pyr_graph.remove_node(5) # Removing a node also removes its associated edges. - self.__reference_graphs: Mapping[int, Iterable[networkx.Graph]] = { - 4: (tet_graph,), - 5: (pyr_graph, build_prism_graph(3, "Wedge")), - 6: (build_prism_graph(4, "Hexahedron"),), - 7: (build_prism_graph(5, "Prism5"),), - 8: (build_prism_graph(6, "Prism6"),), - 9: (build_prism_graph(7, "Prism7"),), - 10: (build_prism_graph(8, "Prism8"),), - 11: (build_prism_graph(9, "Prism9"),), - 12: (build_prism_graph(10, "Prism10"),), - 13: (build_prism_graph(11, "Prism11"),), - } - - def __is_polyhedron_supported(self, face_stream) -> str: - """ - Checks if a polyhedron can be converted into a supported cell. - If so, returns the name of the type. If not, the returned name will be empty. - :param face_stream: The polyhedron. - :return: The name of the supported type or an empty string. - """ - cell_graph = build_face_to_face_connectivity_through_edges(face_stream, add_compatibility=True) - for reference_graph in self.__reference_graphs[cell_graph.order()]: - if networkx.is_isomorphic(reference_graph, cell_graph): - return str(reference_graph.name) - return "" - - def __call__(self, ic: int) -> int: - """ - Checks if a vtk polyhedron cell can be converted into a supported GEOSX element. - :param ic: The index element. - :return: -1 if the polyhedron vtk element can be converted into a supported element type. The index otherwise. - """ - global MESH - assert MESH is not None - if MESH.GetCellType(ic) != VTK_POLYHEDRON: - return -1 - pt_ids = vtkIdList() - MESH.GetFaceStream(ic, pt_ids) - face_stream = FaceStream.build_from_vtk_id_list(pt_ids) - converted_type_name = self.__is_polyhedron_supported(face_stream) - if converted_type_name: - logging.debug(f"Polyhedron cell {ic} can be converted into \"{converted_type_name}\"") - return -1 - else: - logging.debug(f"Polyhedron cell {ic} cannot be converted into any supported element.") - return ic - - -def __check(mesh: vtkUnstructuredGrid, options: Options) -> Result: - if hasattr(mesh, "GetDistinctCellTypesArray"): # For more recent versions of vtk. - cell_types = set(vtk_to_numpy(mesh.GetDistinctCellTypesArray())) - else: - cell_types = vtkCellTypes() - mesh.GetCellTypes(cell_types) - cell_types = set(vtk_iter(cell_types)) - supported_cell_types = { - VTK_HEXAGONAL_PRISM, - VTK_HEXAHEDRON, - VTK_PENTAGONAL_PRISM, - VTK_POLYHEDRON, - VTK_PYRAMID, - VTK_TETRA, - VTK_VOXEL, - VTK_WEDGE - } - unsupported_std_elements_types = cell_types - supported_cell_types - - # Dealing with polyhedron elements. - num_cells = mesh.GetNumberOfCells() - result = numpy.ones(num_cells, dtype=int) * -1 - with multiprocessing.Pool(processes=options.num_proc) as pool: - generator = pool.imap_unordered(IsPolyhedronConvertible(mesh), range(num_cells), chunksize=options.chunk_size) - for i, val in enumerate(tqdm(generator, total=num_cells, desc="Testing support for elements")): - result[i] = val - unsupported_polyhedron_elements = [i for i in result if i > -1] - return Result(unsupported_std_elements_types=frozenset(unsupported_std_elements_types), - unsupported_polyhedron_elements=frozenset(unsupported_polyhedron_elements)) - - -def check(vtk_input_file: str, options: Options) -> Result: - mesh: vtkUnstructuredGrid = vtk_utils.read_mesh(vtk_input_file) - return __check(mesh, options) diff --git a/src/coreComponents/python/modules/geosx_mesh_doctor/checks/triangle_distance.py b/src/coreComponents/python/modules/geosx_mesh_doctor/checks/triangle_distance.py deleted file mode 100644 index ef1f3c98dac..00000000000 --- a/src/coreComponents/python/modules/geosx_mesh_doctor/checks/triangle_distance.py +++ /dev/null @@ -1,186 +0,0 @@ -import itertools -from math import sqrt -from typing import Tuple, Union - -import numpy -from numpy.linalg import norm - - -def __div_clamp(num: float, den :float) -> float: - """ - Computes the division `num / den`. and clamps the result between 0 and 1. - If `den` is zero, the result of the division is set to 0. - :param num: The numerator. - :param den: The denominator. - :return: The result between 0 and 1. - """ - if den == 0.: - return 0. - tmp: float = num / den - if tmp < 0: - return 0. - elif tmp > 1: - return 1. - else: - return tmp - - -def distance_between_two_segments(x0: numpy.ndarray, d0: numpy.ndarray, - x1: numpy.ndarray, d1: numpy.ndarray) -> Tuple[numpy.ndarray, numpy.ndarray]: - """ - Compute the minimum distance between two segments. - :param x0: First point of segment 0. - :param d0: Director vector such that x0 + d0 is the second point of segment 0. - :param x1: First point of segment 1. - :param d1: Director vector such that x1 + d1 is the second point of segment 1. - :return: A tuple containing the two points closest point for segments 0 and 1 respectively. - """ - # The reference paper is: - # "On fast computation of distance between line segments" by Vladimir J. Lumelsky. - # Information Processing Letters, Vol. 21, number 2, pages 55-61, 08/16/1985. - - # In the reference, the indices start at 1, while in this implementation, they start at 0. - tmp: numpy.ndarray = x1 - x0 - D0: float = numpy.dot(d0, d0) # As such, this is D1 in the reference paper. - D1: float = numpy.dot(d1, d1) - R: float = numpy.dot(d0, d1) - S0: float = numpy.dot(d0, tmp) - S1: float = numpy.dot(d1, tmp) - - # `t0` parameterizes line 0: - # - when t0 = 0 the point is p0. - # - when t0 = 1, the point is p0 + u0, the other side of the segment - # Same for `t1` and line 1. - - # Step 1 of the algorithm considers degenerate cases. - # They'll be considered along the line using `div_clamp`. - - # Step 2: Computing t0 using eq (11). - t0: float = __div_clamp(S0 * D1 - S1 * R, D0 * D1 - R * R) - - # Step 3: compute t1 for point on line 1 closest to point at t0. - t1: float = __div_clamp(t0 * R - S1, D1) # Eq (10, right) - sol_1: numpy.ndarray = x1 + t1 * d1 # Eq (3) - t0: float = __div_clamp(t1 * R + S0, D0) # Eq (10, left) - sol_0: numpy.ndarray = x0 + t0 * d0 # Eq (4) - - return sol_0, sol_1 - - -def __compute_nodes_to_triangle_distance(tri_0, edges_0, tri_1) -> Tuple[Union[float, None], Union[numpy.ndarray, None], Union[numpy.ndarray, None], bool]: - """ - Computes the distance from nodes of `tri_1` points onto `tri_0`. - :param tri_0: First triangle. - :param edges_0: The edges of triangle 0. First element being edge [0, 1], etc. - :param tri_1: Second triangle - :return: The distance, the closest point on triangle 0, the closest on triangle 1 - and a boolean indicating of the triangles are disjoint. If nothing was found, - then the first three arguments are None. The boolean being still defined. - """ - are_disjoint: bool = False - tri_0_normal: numpy.ndarray = numpy.cross(edges_0[0], edges_0[1]) - tri_0_normal_norm: float = numpy.dot(tri_0_normal, tri_0_normal) - - # Forget about degenerate cases. - if tri_0_normal_norm > numpy.finfo(float).eps: - # Build projection lengths of `tri_1` points. - tri_1_proj = numpy.empty(3, dtype=float) - for i in range(3): - tri_1_proj[i] = numpy.dot(tri_0[0] - tri_1[i], tri_0_normal) - - # Considering `tri_0` separates the space in 2, - # let's check if `tri_1` is on one side only. - # If so, let's take the closest point. - point: int = -1 - if numpy.all(tri_1_proj > 0): - point = numpy.argmin(tri_1_proj) - elif numpy.all(tri_1_proj < 0): - point = numpy.argmax(tri_1_proj) - - # So if `tri_1` is actually "on one side", - # point `tri_1[point]` is candidate to be the closest point. - if point > -1: - are_disjoint = True - # But we must check that its projection is inside `tri_0`. - if numpy.dot(tri_1[point] - tri_0[0], numpy.cross(tri_0_normal, edges_0[0])) > 0: - if numpy.dot(tri_1[point] - tri_0[1], numpy.cross(tri_0_normal, edges_0[1])) > 0: - if numpy.dot(tri_1[point] - tri_0[2], numpy.cross(tri_0_normal, edges_0[2])) > 0: - # It is! - sol_0 = tri_1[point] - sol_1 = tri_1[point] + (tri_1_proj[point] / tri_0_normal_norm) * tri_0_normal - return norm(sol_1 - sol_0), sol_0, sol_1, are_disjoint - return None, None, None, are_disjoint - - -def distance_between_two_triangles(tri_0: numpy.ndarray, - tri_1: numpy.ndarray) -> Tuple[float, numpy.ndarray, numpy.ndarray]: - """ - Returns the minimum distance between two triangles, and the two points where this minimum occurs. - If the two triangles touch, then distance is exactly 0. - But the two points are dummy and cannot be used as contact points (they are still though). - :param tri_0: The first 3x3 triangle points. - :param tri_1: The second 3x3 triangle points. - :return: The distance and the two points. - """ - # Compute vectors along the 6 sides - edges_0 = numpy.empty((3, 3), dtype=float) - edges_1 = numpy.empty((3, 3), dtype=float) - for i in range(3): - edges_0[i][:] = tri_0[(i + 1) % 3] - tri_0[i] - edges_1[i][:] = tri_1[(i + 1) % 3] - tri_1[i] - - min_sol_0 = numpy.empty(3, dtype=float) - min_sol_1 = numpy.empty(3, dtype=float) - are_disjoint: bool = False - - min_dist = numpy.inf - - # Looping over all the pair of edges. - for i, j in itertools.product(range(3), repeat=2): - # Find the closest points on edges i and j. - sol_0, sol_1 = distance_between_two_segments(tri_0[i], edges_0[i], tri_1[j], edges_1[j]) - # Computing the distance between the two solutions. - delta_sol = sol_1 - sol_0 - dist: float = numpy.dot(delta_sol, delta_sol) - # Update minimum if relevant and check if it's the closest pair of points. - if dist <= min_dist: - min_sol_0[:] = sol_0 - min_sol_1[:] = sol_1 - min_dist = dist - - # `tri_0[(i + 2) % 3]` is the points opposite to edges_0[i] where the closest point sol_0 lies. - # Computing those scalar products and checking the signs somehow let us determine - # if the triangles are getting closer to each other when approaching the sol_(0|1) nodes. - # If so, we have a minimum. - a: float = numpy.dot(tri_0[(i + 2) % 3] - sol_0, delta_sol) - b: float = numpy.dot(tri_1[(j + 2) % 3] - sol_1, delta_sol) - if a <= 0 <= b: - return sqrt(dist), sol_0, sol_1 - - if a < 0: - a = 0 - if b > 0: - b = 0 - # `dist - a + b` expands to `numpy.dot(tri_1[(j + 2) % 3] - tri_0[(i + 2) % 3], sol_1 - sol_0)`. - # If the "directions" of the (sol_1 - sol_0) vector and the vector joining the extra points of the triangles - # (i.e. not involved in the current edge check) re the "same", then the triangles do not intersect. - if dist - a + b > 0: - are_disjoint = True - # No edge pair contained the closest points. - # Checking the node/face situation. - distance, sol_0, sol_1, are_disjoint_tmp = __compute_nodes_to_triangle_distance(tri_0, edges_0, tri_1) - if distance: - return distance, sol_0, sol_1 - are_disjoint = are_disjoint or are_disjoint_tmp - - distance, sol_0, sol_1, are_disjoint_tmp = __compute_nodes_to_triangle_distance(tri_1, edges_1, tri_0) - if distance: - return distance, sol_0, sol_1 - are_disjoint = are_disjoint or are_disjoint_tmp - # It's not a node/face situation. - # If the triangles do not overlap, let's return the minimum found during the edges loop. - # (maybe an edge was parallel to the other face, and we could not decide for a unique closest point). - if are_disjoint: - return sqrt(min_dist), min_sol_0, min_sol_1 - else: # Surely overlapping or degenerate triangles. - return 0., numpy.zeros(3, dtype=float), numpy.zeros(3, dtype=float) diff --git a/src/coreComponents/python/modules/geosx_mesh_doctor/checks/vtk_polyhedron.py b/src/coreComponents/python/modules/geosx_mesh_doctor/checks/vtk_polyhedron.py deleted file mode 100644 index e246a573bde..00000000000 --- a/src/coreComponents/python/modules/geosx_mesh_doctor/checks/vtk_polyhedron.py +++ /dev/null @@ -1,212 +0,0 @@ -from collections import defaultdict -from dataclasses import dataclass -from typing import ( - Collection, - Dict, - FrozenSet, - Iterable, - List, - Sequence, - Tuple, -) - -from vtkmodules.vtkCommonCore import ( - vtkIdList, -) - -import networkx - -from .vtk_utils import ( - vtk_iter, -) - - -@dataclass(frozen=True) -class Options: - dummy: float - - -@dataclass(frozen=True) -class Result: - dummy: float - - -def parse_face_stream(ids: vtkIdList) -> Sequence[Sequence[int]]: - """ - Parses the face stream raw information and converts it into a tuple of tuple of integers, - each tuple of integer being the nodes of a face. - :param ids: The raw vtk face stream. - :return: The tuple of tuple of integers. - """ - result = [] - it = vtk_iter(ids) - num_faces = next(it) - try: - while True: - num_nodes = next(it) - tmp = [] - for i in range(num_nodes): - tmp.append(next(it)) - result.append(tuple(tmp)) - except StopIteration: - pass - assert len(result) == num_faces - assert sum(map(len, result)) + len(result) + 1 == ids.GetNumberOfIds() - - return tuple(result) - - -class FaceStream: - """ - Helper class to manipulate the vtk face streams. - """ - def __init__(self, data: Sequence[Sequence[int]]): - # self.__data contains the list of faces nodes, like it appears in vtk face streams. - # Except that the additional size information is removed - # in favor of the __len__ of the containers. - self.__data: Sequence[Sequence[int]] = data - - @staticmethod - def build_from_vtk_id_list(ids: vtkIdList): - """ - Builds a FaceStream from the raw vtk face stream. - :param ids: The vtk face stream. - :return: A new FaceStream instance. - """ - return FaceStream(parse_face_stream(ids)) - - @property - def face_nodes(self) -> Iterable[Sequence[int]]: - """ - Iterate on the nodes of all the faces. - :return: An iterator. - """ - return iter(self.__data) - - @property - def num_faces(self) -> int: - """ - Number of faces in the face stream - :return: An integer - """ - return len(self.__data) - - @property - def support_point_ids(self) -> Collection[int]: - """ - The list of all (unique) support points of the face stream, in no specific order. - :return: The set of all the point ids. - """ - tmp: List[int] = [] - for nodes in self.face_nodes: - tmp += nodes - return frozenset(tmp) - - @property - def num_support_points(self) -> int: - """ - The number of unique support nodes of the polyhedron. - :return: An integer. - """ - return len(self.support_point_ids) - - def __getitem__(self, face_index) -> Sequence[int]: - """ - The support point ids for the `face_index` face. - :param face_index: The face index (within the face stream). - :return: A tuple containing all the point ids. - """ - return self.__data[face_index] - - def flip_faces(self, face_indices): - """ - Returns a new FaceStream instance with the face indices defined in face_indices flipped., - :param face_indices: The faces (local) indices to flip. - :return: A newly created instance. - """ - result = [] - for face_index, face_nodes in enumerate(self.__data): - result.append(tuple(reversed(face_nodes)) if face_index in face_indices else face_nodes) - return FaceStream(tuple(result)) - - def dump(self) -> Sequence[int]: - """ - Returns the face stream awaited by vtk, but in a python container. - The content can be used, once converted to a vtkIdList, to define another polyhedron in vtk. - :return: The face stream in a python container. - """ - result = [len(self.__data)] - for face_nodes in self.__data: - result.append(len(face_nodes)) - result += face_nodes - return tuple(result) - - def __repr__(self): - result = [str(len(self.__data))] - for face_nodes in self.__data: - result.append(str(len(face_nodes))) - result.append(", ".join(map(str, face_nodes))) - return ",\n".join(result) - - -def build_face_to_face_connectivity_through_edges(face_stream: FaceStream, add_compatibility=False) -> networkx.Graph: - """ - Given a face stream/polyhedron, builds the connections between the faces. - Those connections happen when two faces share an edge. - :param face_stream: The face stream description of the polyhedron. - :param add_compatibility: Two faces are considered compatible if their normals point in the same direction (inwards or outwards). - If `add_compatibility=True`, we add a `compatible={"-", "+"}` flag on the edges - to indicate that the two connected faces are compatible or not. - If `add_compatibility=False`, non-compatible faces are simply not connected by any edge. - :return: A graph which nodes are actually the faces of the polyhedron. - Two nodes of the graph are connected if they share an edge. - """ - edges_to_face_indices: Dict[FrozenSet[int], List[int]] = defaultdict(list) - for face_index, face_nodes in enumerate(face_stream.face_nodes): - # Each edge is defined by two nodes. We do a small trick to loop on consecutive points. - face_indices: Tuple[int, int] - for face_indices in zip(face_nodes, face_nodes[1:] + (face_nodes[0], )): - edges_to_face_indices[frozenset(face_indices)].append(face_index) - # We are doing here some small validations w.r.t. the connections of the faces - # which may only make sense in the context of numerical simulations. - # As such, an error will be thrown in case the polyhedron is not closed. - # So there may be a lack of absolute genericity, and the code may evolve if needed. - for face_indices in edges_to_face_indices.values(): - assert len(face_indices) == 2 - # Computing the graph degree for validation - degrees: Dict[int, int] = defaultdict(int) - for face_indices in edges_to_face_indices.values(): - for face_index in face_indices: - degrees[face_index] += 1 - for face_index, degree in degrees.items(): - assert len(face_stream[face_index]) == degree - # Validation that there is one unique edge connecting two faces. - face_indices_to_edge_index = defaultdict(list) - for edge_index, face_indices in edges_to_face_indices.items(): - face_indices_to_edge_index[frozenset(face_indices)].append(edge_index) - for edge_indices in face_indices_to_edge_index.values(): - assert len(edge_indices) == 1 - # Connecting the faces. Neighbor faces with consistent normals (i.e. facing both inward or outward) - # will be connected. This will allow us to extract connected components with consistent orientations. - # Another step will then reconcile all the components such that all the normals of the cell - # will consistently point outward. - graph = networkx.Graph() - graph.add_nodes_from(range(face_stream.num_faces)) - for edge, face_indices in edges_to_face_indices.items(): - face_index_0, face_index_1 = face_indices - face_nodes_0 = face_stream[face_index_0] + (face_stream[face_index_0][0],) - face_nodes_1 = face_stream[face_index_1] + (face_stream[face_index_1][0],) - node_0, node_1 = edge - order_0 = 1 if face_nodes_0[face_nodes_0.index(node_0) + 1] == node_1 else -1 - order_1 = 1 if face_nodes_1[face_nodes_1.index(node_0) + 1] == node_1 else -1 - # Same order of nodes means that the normals of the faces - # are _not_ both in the same "direction" (inward or outward). - if order_0 * order_1 == 1: - if add_compatibility: - graph.add_edge(face_index_0, face_index_1, compatible="-") - else: - if add_compatibility: - graph.add_edge(face_index_0, face_index_1, compatible="+") - else: - graph.add_edge(face_index_0, face_index_1) - return graph diff --git a/src/coreComponents/python/modules/geosx_mesh_doctor/checks/vtk_utils.py b/src/coreComponents/python/modules/geosx_mesh_doctor/checks/vtk_utils.py deleted file mode 100644 index 2604609ef0c..00000000000 --- a/src/coreComponents/python/modules/geosx_mesh_doctor/checks/vtk_utils.py +++ /dev/null @@ -1,143 +0,0 @@ -from dataclasses import dataclass -import os.path -import logging -import sys -from typing import ( - Any, - Iterator, - Optional, -) - -from vtkmodules.vtkCommonCore import ( - vtkIdList, -) -from vtkmodules.vtkCommonDataModel import ( - vtkUnstructuredGrid, -) -from vtkmodules.vtkIOLegacy import ( - vtkUnstructuredGridWriter, - vtkUnstructuredGridReader, -) -from vtkmodules.vtkIOXML import ( - vtkXMLUnstructuredGridReader, - vtkXMLUnstructuredGridWriter, -) - - -@dataclass(frozen=True) -class VtkOutput: - output: str - is_data_mode_binary: bool - - -def to_vtk_id_list(data) -> vtkIdList: - result = vtkIdList() - result.Allocate(len(data)) - for d in data: - result.InsertNextId(d) - return result - - -def vtk_iter(l) -> Iterator[Any]: - """ - Utility function transforming a vtk "container" (e.g. vtkIdList) into an iterable to be used for building built-ins python containers. - :param l: A vtk container. - :return: The iterator. - """ - if hasattr(l, "GetNumberOfIds"): - for i in range(l.GetNumberOfIds()): - yield l.GetId(i) - elif hasattr(l, "GetNumberOfTypes"): - for i in range(l.GetNumberOfTypes()): - yield l.GetCellType(i) - - -def __read_vtk(vtk_input_file: str) -> Optional[vtkUnstructuredGrid]: - reader = vtkUnstructuredGridReader() - logging.info(f"Testing file format \"{vtk_input_file}\" using legacy format reader...") - reader.SetFileName(vtk_input_file) - if reader.IsFileUnstructuredGrid(): - logging.info(f"Reader matches. Reading file \"{vtk_input_file}\" using legacy format reader.") - reader.Update() - return reader.GetOutput() - else: - logging.info("Reader did not match the input file format.") - return None - - -def __read_vtu(vtk_input_file: str) -> Optional[vtkUnstructuredGrid]: - reader = vtkXMLUnstructuredGridReader() - logging.info(f"Testing file format \"{vtk_input_file}\" using XML format reader...") - if reader.CanReadFile(vtk_input_file): - reader.SetFileName(vtk_input_file) - logging.info(f"Reader matches. Reading file \"{vtk_input_file}\" using XML format reader.") - reader.Update() - return reader.GetOutput() - else: - logging.info("Reader did not match the input file format.") - return None - - -def read_mesh(vtk_input_file: str) -> vtkUnstructuredGrid: - """ - Read the vtk file and builds an unstructured grid from it. - :param vtk_input_file: The file name. The extension will be used to guess the file format. - If first guess does not work, eventually all the others reader available will be tested. - :return: A unstructured grid. - """ - file_extension = os.path.splitext(vtk_input_file)[-1] - extension_to_reader = {".vtk": __read_vtk, - ".vtu": __read_vtu} - # Testing first the reader that should match - if file_extension in extension_to_reader: - output_mesh = extension_to_reader.pop(file_extension)(vtk_input_file) - if output_mesh: - return output_mesh - # If it does not match, then test all the others. - for reader in extension_to_reader.values(): - output_mesh = reader(vtk_input_file) - if output_mesh: - return output_mesh - # No reader did work. Dying. - logging.critical(f"Could not find the appropriate VTK reader for file \"{vtk_input_file}\". Dying...") - sys.exit(1) - - -def __write_vtk(mesh: vtkUnstructuredGrid, output: str) -> int: - logging.info(f"Writing mesh into file \"{output}\" using legacy format.") - writer = vtkUnstructuredGridWriter() - writer.SetFileName(output) - writer.SetInputData(mesh) - return writer.Write() - - -def __write_vtu(mesh: vtkUnstructuredGrid, output: str, is_data_mode_binary: bool) -> int: - logging.info(f"Writing mesh into file \"{output}\" using XML format.") - writer = vtkXMLUnstructuredGridWriter() - writer.SetFileName(output) - writer.SetInputData(mesh) - writer.SetDataModeToBinary() if is_data_mode_binary else writer.SetDataModeToAscii() - return writer.Write() - - -def write_mesh(mesh: vtkUnstructuredGrid, vtk_output: VtkOutput) -> int: - """ - Writes the mesh to disk. - Nothing will be done if the file already exists. - :param mesh: The unstructured grid to write. - :param vtk_output: Where to write. The file extension will be used to select the VTK file format. - :return: 0 in case of success. - """ - if os.path.exists(vtk_output.output): - logging.error(f"File \"{vtk_output.output}\" already exists, nothing done.") - return 1 - file_extension = os.path.splitext(vtk_output.output)[-1] - if file_extension == ".vtk": - success_code = __write_vtk(mesh, vtk_output.output) - elif file_extension == ".vtu": - success_code = __write_vtu(mesh, vtk_output.output, vtk_output.is_data_mode_binary) - else: - # No writer found did work. Dying. - logging.critical(f"Could not find the appropriate VTK writer for extension \"{file_extension}\". Dying...") - sys.exit(1) - return 0 if success_code else 2 # the Write member function return 1 in case of success, 0 otherwise. diff --git a/src/coreComponents/python/modules/geosx_mesh_doctor/mesh_doctor.py b/src/coreComponents/python/modules/geosx_mesh_doctor/mesh_doctor.py deleted file mode 100644 index f28cc7e9747..00000000000 --- a/src/coreComponents/python/modules/geosx_mesh_doctor/mesh_doctor.py +++ /dev/null @@ -1,35 +0,0 @@ -import sys - -try: - min_python_version = (3, 7) - assert sys.version_info >= min_python_version -except AssertionError as e: - print(f"Please update python to at least version {'.'.join(map(str, min_python_version))}.") - sys.exit(1) - -import logging - -from parsing import CheckHelper -from parsing.cli_parsing import parse_and_set_verbosity -import register - - -def main(): - logging.basicConfig(format='[%(asctime)s][%(levelname)s] %(message)s') - parse_and_set_verbosity(sys.argv) - main_parser, all_checks, all_checks_helpers = register.register() - args = main_parser.parse_args(sys.argv[1:]) - logging.info(f"Checking mesh \"{args.vtk_input_file}\".") - check_options = all_checks_helpers[args.subparsers].convert(vars(args)) - try: - check = all_checks[args.subparsers] - except KeyError as e: - logging.critical(f"Check {args.subparsers} is not a valid check.") - sys.exit(1) - helper: CheckHelper = all_checks_helpers[args.subparsers] - result = check(args.vtk_input_file, check_options) - helper.display_results(check_options, result) - - -if __name__ == '__main__': - main() diff --git a/src/coreComponents/python/modules/geosx_mesh_doctor/parsing/__init__.py b/src/coreComponents/python/modules/geosx_mesh_doctor/parsing/__init__.py deleted file mode 100644 index 0d06f736cce..00000000000 --- a/src/coreComponents/python/modules/geosx_mesh_doctor/parsing/__init__.py +++ /dev/null @@ -1,21 +0,0 @@ -import argparse -from dataclasses import dataclass -from typing import Callable, Any - - -COLLOCATES_NODES = "collocated_nodes" -ELEMENT_VOLUMES = "element_volumes" -FIX_ELEMENTS_ORDERINGS = "fix_elements_orderings" -GENERATE_CUBE = "generate_cube" -GENERATE_FRACTURES = "generate_fractures" -GENERATE_GLOBAL_IDS = "generate_global_ids" -NON_CONFORMAL = "non_conformal" -SELF_INTERSECTING_ELEMENTS = "self_intersecting_elements" -SUPPORTED_ELEMENTS = "supported_elements" - - -@dataclass(frozen=True) -class CheckHelper: - fill_subparser: Callable[[Any], argparse.ArgumentParser] - convert: Callable[[Any], Any] - display_results: Callable[[Any, Any], None] diff --git a/src/coreComponents/python/modules/geosx_mesh_doctor/parsing/check_fractures_parsing.py b/src/coreComponents/python/modules/geosx_mesh_doctor/parsing/check_fractures_parsing.py deleted file mode 100644 index 3f43bca60a6..00000000000 --- a/src/coreComponents/python/modules/geosx_mesh_doctor/parsing/check_fractures_parsing.py +++ /dev/null @@ -1 +0,0 @@ -# empty: the check is not available yet! \ No newline at end of file diff --git a/src/coreComponents/python/modules/geosx_mesh_doctor/parsing/cli_parsing.py b/src/coreComponents/python/modules/geosx_mesh_doctor/parsing/cli_parsing.py deleted file mode 100644 index a2eb20ed7c9..00000000000 --- a/src/coreComponents/python/modules/geosx_mesh_doctor/parsing/cli_parsing.py +++ /dev/null @@ -1,73 +0,0 @@ -import argparse -import logging -import textwrap -from typing import List - -__VERBOSE_KEY = "verbose" -__QUIET_KEY = "quiet" - -__VERBOSITY_FLAG = "v" -__QUIET_FLAG = "q" - - -def parse_and_set_verbosity(cli_args: List[str]) -> None: - """ - Parse the verbosity flag only. And sets the logger's level accordingly. - :param cli_args: The list of arguments (as strings) - :return: None - """ - dummy_verbosity_parser = argparse.ArgumentParser(add_help=False) - dummy_verbosity_parser.add_argument('-' + __VERBOSITY_FLAG, - '--' + __VERBOSE_KEY, - action='count', - default=2, - dest=__VERBOSE_KEY) - dummy_verbosity_parser.add_argument('-' + __QUIET_FLAG, - '--' + __QUIET_KEY, - action='count', - default=0, - dest=__QUIET_KEY) - args = dummy_verbosity_parser.parse_known_args(cli_args[1:])[0] - d = vars(args) - v = d[__VERBOSE_KEY] - d[__QUIET_KEY] - verbosity = logging.CRITICAL - (10 * v) - if verbosity < logging.DEBUG: - verbosity = logging.DEBUG - elif verbosity > logging.CRITICAL: - verbosity = logging.CRITICAL - logging.getLogger().setLevel(verbosity) - logging.info(f"Logger level set to \"{logging.getLevelName(verbosity)}\"") - - -def init_parser() -> argparse.ArgumentParser: - vtk_input_file_key = "vtk_input_file" - - epilog_msg = f"""\ - Note that checks are dynamically loaded. - An option may be missing because of an unloaded module. - Increase verbosity (-{__VERBOSITY_FLAG}, -{__VERBOSITY_FLAG * 2}) to get full information. - """ - formatter = lambda prog: argparse.RawTextHelpFormatter(prog, max_help_position=8) - parser = argparse.ArgumentParser(description='Inspects meshes for GEOSX.', - epilog=textwrap.dedent(epilog_msg), - formatter_class=formatter) - # Nothing will be done with this verbosity/quiet input. - # It's only here for the `--help` message. - # `parse_verbosity` does the real parsing instead. - parser.add_argument('-' + __VERBOSITY_FLAG, - action='count', - default=2, - dest=__VERBOSE_KEY, - help=f"Use -{__VERBOSITY_FLAG} 'INFO', -{__VERBOSITY_FLAG * 2} for 'DEBUG'. Defaults to 'WARNING'.") - parser.add_argument('-' + __QUIET_FLAG, - action='count', - default=0, - dest=__QUIET_KEY, - help=f"Use -{__QUIET_FLAG} to reduce the verbosity of the output.") - parser.add_argument('-i', - '--vtk-input-file', - metavar='VTK_MESH_FILE', - type=str, - required=True, - dest=vtk_input_file_key) - return parser diff --git a/src/coreComponents/python/modules/geosx_mesh_doctor/parsing/collocated_nodes_parsing.py b/src/coreComponents/python/modules/geosx_mesh_doctor/parsing/collocated_nodes_parsing.py deleted file mode 100644 index 421ae95b993..00000000000 --- a/src/coreComponents/python/modules/geosx_mesh_doctor/parsing/collocated_nodes_parsing.py +++ /dev/null @@ -1,49 +0,0 @@ -import logging - -from typing import ( - FrozenSet, - List, -) - -from checks.collocated_nodes import Options, Result - -from . import COLLOCATES_NODES - -__TOLERANCE = "tolerance" - - -def convert(parsed_options) -> Options: - return Options(parsed_options[__TOLERANCE]) - - -def fill_subparser(subparsers) -> None: - p = subparsers.add_parser(COLLOCATES_NODES, - help="Checks if nodes are collocated.") - p.add_argument('--' + __TOLERANCE, - type=float, - required=True, - help="[float]: The absolute distance between two nodes for them to be considered collocated.") - - -def display_results(options: Options, result: Result): - all_collocated_nodes: List[int] = [] - for bucket in result.nodes_buckets: - for node in bucket: - all_collocated_nodes.append(node) - all_collocated_nodes: FrozenSet[int] = frozenset(all_collocated_nodes) # Surely useless - if all_collocated_nodes: - logging.error(f"You have {len(all_collocated_nodes)} collocated nodes (tolerance = {options.tolerance}).") - - logging.info("Here are all the buckets of collocated nodes.") - tmp: List[str] = [] - for bucket in result.nodes_buckets: - tmp.append(f"({', '.join(map(str, bucket))})") - logging.info(f"({', '.join(tmp)})") - else: - logging.error(f"You have no collocated node (tolerance = {options.tolerance}).") - - if result.wrong_support_elements: - tmp: str = ", ".join(map(str, result.wrong_support_elements)) - logging.error(f"You have {len(result.wrong_support_elements)} elements with duplicated support nodes.\n" + tmp) - else: - logging.error("You have no element with duplicated support nodes.") diff --git a/src/coreComponents/python/modules/geosx_mesh_doctor/parsing/element_volumes_parsing.py b/src/coreComponents/python/modules/geosx_mesh_doctor/parsing/element_volumes_parsing.py deleted file mode 100644 index 3b196822fbd..00000000000 --- a/src/coreComponents/python/modules/geosx_mesh_doctor/parsing/element_volumes_parsing.py +++ /dev/null @@ -1,34 +0,0 @@ -import logging - -from checks.element_volumes import Options, Result - -from . import ELEMENT_VOLUMES - -__MIN_VOLUME = "min" -__MIN_VOLUME_DEFAULT = 0. - - -def fill_subparser(subparsers) -> None: - p = subparsers.add_parser(ELEMENT_VOLUMES, - help=f"Checks if the volumes of the elements are greater than \"{__MIN_VOLUME}\".") - p.add_argument('--' + __MIN_VOLUME, - type=float, - metavar=__MIN_VOLUME_DEFAULT, - default=__MIN_VOLUME_DEFAULT, - required=True, - help=f"[float]: The minimum acceptable volume. Defaults to {__MIN_VOLUME_DEFAULT}.") - - -def convert(parsed_options) -> Options: - """ - From the parsed cli options, return the converted options for elements volumes check. - :param options_str: Parsed cli options. - :return: Options instance. - """ - return Options(min_volume=parsed_options[__MIN_VOLUME]) - - -def display_results(options: Options, result: Result): - logging.error(f"You have {len(result.element_volumes)} elements with volumes smaller than {options.min_volume}.") - if result.element_volumes: - logging.error("The elements indices and their volumes are:\n" + "\n".join(map(str, result.element_volumes))) diff --git a/src/coreComponents/python/modules/geosx_mesh_doctor/parsing/fix_elements_orderings_parsing.py b/src/coreComponents/python/modules/geosx_mesh_doctor/parsing/fix_elements_orderings_parsing.py deleted file mode 100644 index c105792560b..00000000000 --- a/src/coreComponents/python/modules/geosx_mesh_doctor/parsing/fix_elements_orderings_parsing.py +++ /dev/null @@ -1,84 +0,0 @@ -import logging -import random - -from vtkmodules.vtkCommonDataModel import ( - VTK_HEXAGONAL_PRISM, - VTK_HEXAHEDRON, - VTK_PENTAGONAL_PRISM, - VTK_PYRAMID, - VTK_TETRA, - VTK_VOXEL, - VTK_WEDGE, -) - -from checks.fix_elements_orderings import Options, Result - -from . import vtk_output_parsing, FIX_ELEMENTS_ORDERINGS - - -__CELL_TYPE_MAPPING = { - "Hexahedron": VTK_HEXAHEDRON, - "Prism5": VTK_PENTAGONAL_PRISM, - "Prism6": VTK_HEXAGONAL_PRISM, - "Pyramid": VTK_PYRAMID, - "Tetrahedron": VTK_TETRA, - "Voxel": VTK_VOXEL, - "Wedge": VTK_WEDGE, -} - -__CELL_TYPE_SUPPORT_SIZE = { - VTK_HEXAHEDRON: 8, - VTK_PENTAGONAL_PRISM: 10, - VTK_HEXAGONAL_PRISM: 12, - VTK_PYRAMID: 5, - VTK_TETRA: 4, - VTK_VOXEL: 8, - VTK_WEDGE: 6, -} - - -def fill_subparser(subparsers) -> None: - p = subparsers.add_parser(FIX_ELEMENTS_ORDERINGS, - help="Reorders the support nodes for the given cell types.") - for key, vtk_key in __CELL_TYPE_MAPPING.items(): - tmp = list(range(__CELL_TYPE_SUPPORT_SIZE[vtk_key])) - random.Random(4).shuffle(tmp) - p.add_argument('--' + key, - type=str, - metavar=",".join(map(str, tmp)), - default=None, - required=False, - help=f"[list of integers]: node permutation for \"{key}\".") - vtk_output_parsing.fill_vtk_output_subparser(p) - - -def convert(parsed_options) -> Options: - """ - From the parsed cli options, return the converted options for self intersecting elements check. - :param options_str: Parsed cli options. - :return: Options instance. - """ - cell_type_to_ordering = {} - for key, vtk_key in __CELL_TYPE_MAPPING.items(): - raw_mapping = parsed_options[key] - if raw_mapping: - tmp = tuple(map(int, raw_mapping.split(","))) - if not set(tmp) == set(range(__CELL_TYPE_SUPPORT_SIZE[vtk_key])): - err_msg = f"Permutation {raw_mapping} for type {key} is not valid." - logging.error(err_msg) - raise ValueError(err_msg) - cell_type_to_ordering[vtk_key] = tmp - vtk_output = vtk_output_parsing.convert(parsed_options) - return Options(vtk_output=vtk_output, - cell_type_to_ordering=cell_type_to_ordering) - - -def display_results(options: Options, result: Result): - if result.output: - logging.info(f"New mesh was written to file '{result.output}'") - if result.unchanged_cell_types: - logging.info(f"Those vtk types were not reordered: [{', '.join(map(str, result.unchanged_cell_types))}].") - else: - logging.info("All the cells of the mesh were reordered.") - else: - logging.info("No output file was written.") diff --git a/src/coreComponents/python/modules/geosx_mesh_doctor/parsing/generate_cube_parsing.py b/src/coreComponents/python/modules/geosx_mesh_doctor/parsing/generate_cube_parsing.py deleted file mode 100644 index 41c0e045127..00000000000 --- a/src/coreComponents/python/modules/geosx_mesh_doctor/parsing/generate_cube_parsing.py +++ /dev/null @@ -1,87 +0,0 @@ -import logging - -from checks.generate_cube import Options, Result, FieldInfo - -from . import vtk_output_parsing, generate_global_ids_parsing, GENERATE_CUBE -from .generate_global_ids_parsing import GlobalIdsInfo - - -__X, __Y, __Z, __NX, __NY, __NZ = "x", "y", "z", "nx", "ny", "nz" -__FIELDS = "fields" - - -def convert(parsed_options) -> Options: - def check_discretizations(x, nx, title): - if len(x) != len(nx) + 1: - raise ValueError(f"{title} information (\"{x}\" and \"{nx}\") does not have consistent size.") - check_discretizations(parsed_options[__X], parsed_options[__NX], __X) - check_discretizations(parsed_options[__Y], parsed_options[__NY], __Y) - check_discretizations(parsed_options[__Z], parsed_options[__NZ], __Z) - - def parse_fields(s): - name, support, dim = s.split(":") - if support not in ("CELLS", "POINTS"): - raise ValueError(f"Support {support} for field \"{name}\" must be one of \"CELLS\" or \"POINTS\".") - try: - dim = int(dim) - assert dim > 0 - except ValueError: - raise ValueError(f"Dimension {dim} cannot be converted to an integer.") - except AssertionError: - raise ValueError(f"Dimension {dim} must be a positive integer") - return FieldInfo(name=name, support=support, dimension=dim) - - gids: GlobalIdsInfo = generate_global_ids_parsing.convert_global_ids(parsed_options) - - return Options(vtk_output=vtk_output_parsing.convert(parsed_options), - generate_cells_global_ids=gids.cells, - generate_points_global_ids=gids.points, - xs=parsed_options[__X], - ys=parsed_options[__Y], - zs=parsed_options[__Z], - nxs=parsed_options[__NX], - nys=parsed_options[__NY], - nzs=parsed_options[__NZ], - fields=tuple(map(parse_fields, parsed_options[__FIELDS]))) - - -def fill_subparser(subparsers) -> None: - p = subparsers.add_parser(GENERATE_CUBE, - help="Generate a cube and its fields.") - p.add_argument('--' + __X, - type=lambda s: tuple(map(float, s.split(":"))), - metavar="0:1.5:3", - help="[list of floats]: X coordinates of the points.") - p.add_argument('--' + __Y, - type=lambda s: tuple(map(float, s.split(":"))), - metavar="0:5:10", - help="[list of floats]: Y coordinates of the points.") - p.add_argument('--' + __Z, - type=lambda s: tuple(map(float, s.split(":"))), - metavar="0:1", - help="[list of floats]: Z coordinates of the points.") - p.add_argument('--' + __NX, - type=lambda s: tuple(map(int, s.split(":"))), - metavar="2:2", - help="[list of integers]: Number of elements in the X direction.") - p.add_argument('--' + __NY, - type=lambda s: tuple(map(int, s.split(":"))), - metavar="1:1", - help="[list of integers]: Number of elements in the Y direction.") - p.add_argument('--' + __NZ, - type=lambda s: tuple(map(int, s.split(":"))), - metavar="4", - help="[list of integers]: Number of elements in the Z direction.") - p.add_argument('--' + __FIELDS, - type=str, - metavar="name:support:dim", - nargs="+", - required=False, - default=(), - help="Create fields on CELLS or POINTS, with given dimension (typically 1 or 3).") - generate_global_ids_parsing.fill_generate_global_ids_subparser(p) - vtk_output_parsing.fill_vtk_output_subparser(p) - - -def display_results(options: Options, result: Result): - logging.info(result.info) diff --git a/src/coreComponents/python/modules/geosx_mesh_doctor/parsing/generate_fractures_parsing.py b/src/coreComponents/python/modules/geosx_mesh_doctor/parsing/generate_fractures_parsing.py deleted file mode 100644 index 47897933908..00000000000 --- a/src/coreComponents/python/modules/geosx_mesh_doctor/parsing/generate_fractures_parsing.py +++ /dev/null @@ -1,66 +0,0 @@ -import logging - -from checks.generate_fractures import Options, Result, FracturePolicy - -from . import vtk_output_parsing, GENERATE_FRACTURES - -__POLICY = "policy" -__FIELD_POLICY = "field" -__INTERNAL_SURFACES_POLICY = "internal_surfaces" -__POLICIES = (__FIELD_POLICY, __INTERNAL_SURFACES_POLICY ) - -__FIELD_NAME = "name" -__FIELD_VALUES = "values" - -__FRACTURE_PREFIX = "fracture" - - -def convert_to_fracture_policy(s: str) -> FracturePolicy: - """ - Converts the user input to the proper enum chosen. - I do not want to use the auto conversion already available to force explicit conversion. - :param s: The user input - :return: The matching enum. - """ - if s == __FIELD_POLICY: - return FracturePolicy.FIELD - elif s == __INTERNAL_SURFACES_POLICY: - return FracturePolicy.INTERNAL_SURFACES - raise ValueError(f"Policy {s} is not valid. Please use one of \"{', '.join(map(str, __POLICIES))}\".") - - -def fill_subparser(subparsers) -> None: - p = subparsers.add_parser(GENERATE_FRACTURES, - help="Splits the mesh to generate the faults and fractures. [EXPERIMENTAL]") - p.add_argument('--' + __POLICY, - type=convert_to_fracture_policy, - metavar=", ".join(__POLICIES), - required=True, - help=f"[string]: The criterion to define the surfaces that will be changed into fracture zones. " - f"Possible values are \"{', '.join(__POLICIES)}\"") - p.add_argument('--' + __FIELD_NAME, - type=str, - help=f"[string]: If the \"{__FIELD_POLICY}\" {__POLICY} is selected, defines which field will be considered to define the fractures. " - f"If the \"{__INTERNAL_SURFACES_POLICY}\" {__POLICY} is selected, defines the name of the attribute will be considered to identify the fractures. ") - p.add_argument('--' + __FIELD_VALUES, - type=str, - help=f"[list of comma separated integers]: If the \"{__FIELD_POLICY}\" {__POLICY} is selected, which changes of the field will be considered as a fracture. If the \"{__INTERNAL_SURFACES_POLICY}\" {__POLICY} is selected, list of the fracture attributes.") - vtk_output_parsing.fill_vtk_output_subparser(p) - vtk_output_parsing.fill_vtk_output_subparser(p, prefix=__FRACTURE_PREFIX) - - -def convert(parsed_options) -> Options: - policy = parsed_options[__POLICY] - field = parsed_options[__FIELD_NAME] - field_values = frozenset(map(int, parsed_options[__FIELD_VALUES].split(","))) - vtk_output = vtk_output_parsing.convert(parsed_options) - vtk_fracture_output = vtk_output_parsing.convert(parsed_options, prefix=__FRACTURE_PREFIX) - return Options(policy=policy, - field=field, - field_values=field_values, - vtk_output=vtk_output, - vtk_fracture_output=vtk_fracture_output) - - -def display_results(options: Options, result: Result): - pass diff --git a/src/coreComponents/python/modules/geosx_mesh_doctor/parsing/generate_global_ids_parsing.py b/src/coreComponents/python/modules/geosx_mesh_doctor/parsing/generate_global_ids_parsing.py deleted file mode 100644 index 730599a41cc..00000000000 --- a/src/coreComponents/python/modules/geosx_mesh_doctor/parsing/generate_global_ids_parsing.py +++ /dev/null @@ -1,57 +0,0 @@ -from dataclasses import dataclass -import logging - -from checks.generate_global_ids import Options, Result - -from . import vtk_output_parsing, GENERATE_GLOBAL_IDS - - -__CELLS, __POINTS = "cells", "points" - - -@dataclass(frozen=True) -class GlobalIdsInfo: - cells: bool - points: bool - - -def convert_global_ids(parsed_options) -> GlobalIdsInfo: - return GlobalIdsInfo(cells=parsed_options[__CELLS], - points=parsed_options[__POINTS]) - - -def convert(parsed_options) -> Options: - gids: GlobalIdsInfo = convert_global_ids(parsed_options) - return Options(vtk_output=vtk_output_parsing.convert(parsed_options), - generate_cells_global_ids=gids.cells, - generate_points_global_ids=gids.points) - - -def fill_generate_global_ids_subparser(p): - p.add_argument('--' + __CELLS, - action="store_true", - help=f"[bool]: Generate global ids for cells. Defaults to true.") - p.add_argument('--no-' + __CELLS, - action="store_false", - dest=__CELLS, - help=f"[bool]: Don't generate global ids for cells.") - p.set_defaults(**{__CELLS: True}) - p.add_argument('--' + __POINTS, - action="store_true", - help=f"[bool]: Generate global ids for points. Defaults to true.") - p.add_argument('--no-' + __POINTS, - action="store_false", - dest=__POINTS, - help=f"[bool]: Don't generate global ids for points.") - p.set_defaults(**{__POINTS: True}) - - -def fill_subparser(subparsers) -> None: - p = subparsers.add_parser(GENERATE_GLOBAL_IDS, - help="Adds globals ids for points and cells.") - fill_generate_global_ids_subparser(p) - vtk_output_parsing.fill_vtk_output_subparser(p) - - -def display_results(options: Options, result: Result): - logging.info(result.info) diff --git a/src/coreComponents/python/modules/geosx_mesh_doctor/parsing/non_conformal_parsing.py b/src/coreComponents/python/modules/geosx_mesh_doctor/parsing/non_conformal_parsing.py deleted file mode 100644 index 33625f68f01..00000000000 --- a/src/coreComponents/python/modules/geosx_mesh_doctor/parsing/non_conformal_parsing.py +++ /dev/null @@ -1,48 +0,0 @@ -import logging - -from typing import ( - FrozenSet, - List, -) - -from checks.non_conformal import Options, Result - -from . import NON_CONFORMAL - -__ANGLE_TOLERANCE = "angle_tolerance" -__POINT_TOLERANCE = "point_tolerance" -__FACE_TOLERANCE = "face_tolerance" - -__ANGLE_TOLERANCE_DEFAULT = 10. - -__ALL_KEYWORDS = {__ANGLE_TOLERANCE, __POINT_TOLERANCE, __FACE_TOLERANCE} - - -def convert(parsed_options) -> Options: - return Options(angle_tolerance=parsed_options[__ANGLE_TOLERANCE], - point_tolerance=parsed_options[__POINT_TOLERANCE], - face_tolerance=parsed_options[__FACE_TOLERANCE]) - - -def fill_subparser(subparsers) -> None: - p = subparsers.add_parser(NON_CONFORMAL, - help="Detects non conformal elements. [EXPERIMENTAL]") - p.add_argument('--' + __ANGLE_TOLERANCE, - type=float, - metavar=__ANGLE_TOLERANCE_DEFAULT, - default=__ANGLE_TOLERANCE_DEFAULT, - help=f"[float]: angle tolerance in degrees. Defaults to {__ANGLE_TOLERANCE_DEFAULT}") - p.add_argument('--' + __POINT_TOLERANCE, - type=float, - help=f"[float]: tolerance for two points to be considered collocated.") - p.add_argument('--' + __FACE_TOLERANCE, - type=float, - help=f"[float]: tolerance for two faces to be considered \"touching\".") - - -def display_results(options: Options, result: Result): - non_conformal_cells: List[int] = [] - for i, j in result.non_conformal_cells: - non_conformal_cells += i, j - non_conformal_cells: FrozenSet[int] = frozenset(non_conformal_cells) - logging.error(f"You have {len(non_conformal_cells)} non conformal cells.\n{', '.join(map(str, sorted(non_conformal_cells)))}") diff --git a/src/coreComponents/python/modules/geosx_mesh_doctor/parsing/self_intersecting_elements_parsing.py b/src/coreComponents/python/modules/geosx_mesh_doctor/parsing/self_intersecting_elements_parsing.py deleted file mode 100644 index 70f5d6a9a37..00000000000 --- a/src/coreComponents/python/modules/geosx_mesh_doctor/parsing/self_intersecting_elements_parsing.py +++ /dev/null @@ -1,36 +0,0 @@ -import logging - -import numpy - -from checks.self_intersecting_elements import Options, Result - -from . import SELF_INTERSECTING_ELEMENTS - -__TOLERANCE = "min" -__TOLERANCE_DEFAULT = numpy.finfo(float).eps - - -def convert(parsed_options) -> Options: - tolerance = parsed_options[__TOLERANCE] - if tolerance == 0: - logging.warning("Having tolerance set to 0 can induce lots of false positive results (adjacent faces may be considered intersecting).") - elif tolerance < 0: - raise ValueError(f"Negative tolerance ({tolerance}) in the {SELF_INTERSECTING_ELEMENTS} check is not allowed.") - return Options(tolerance=tolerance) - - -def fill_subparser(subparsers) -> None: - p = subparsers.add_parser(SELF_INTERSECTING_ELEMENTS, - help="Checks if the faces of the elements are self intersecting.") - p.add_argument('--' + __TOLERANCE, - type=float, - required=False, - metavar=__TOLERANCE_DEFAULT, - default=__TOLERANCE_DEFAULT, - help=f"[float]: The tolerance in the computation. Defaults to your machine precision {__TOLERANCE_DEFAULT}.") - - -def display_results(options: Options, result: Result): - logging.error(f"You have {len(result.intersecting_faces_elements)} elements with self intersecting faces.") - if result.intersecting_faces_elements: - logging.error("The elements indices are:\n" + ", ".join(map(str, result.intersecting_faces_elements))) diff --git a/src/coreComponents/python/modules/geosx_mesh_doctor/parsing/supported_elements_parsing.py b/src/coreComponents/python/modules/geosx_mesh_doctor/parsing/supported_elements_parsing.py deleted file mode 100644 index c68905bea6b..00000000000 --- a/src/coreComponents/python/modules/geosx_mesh_doctor/parsing/supported_elements_parsing.py +++ /dev/null @@ -1,49 +0,0 @@ -import logging -import multiprocessing - -from checks.supported_elements import Options, Result - -from . import SUPPORTED_ELEMENTS - -__CHUNK_SIZE = "chunck_size" -__NUM_PROC = "nproc" - - -__ALL_KEYWORDS = {__CHUNK_SIZE, __NUM_PROC} - -__CHUNK_SIZE_DEFAULT = 1 -__NUM_PROC_DEFAULT = multiprocessing.cpu_count() - - -def convert(parsed_options) -> Options: - return Options(chunk_size=parsed_options[__CHUNK_SIZE], - num_proc=parsed_options[__NUM_PROC]) - - -def fill_subparser(subparsers) -> None: - p = subparsers.add_parser(SUPPORTED_ELEMENTS, - help="Check that all the elements of the mesh are supported by GEOSX.") - p.add_argument('--' + __CHUNK_SIZE, - type=int, - required=False, - metavar=__CHUNK_SIZE_DEFAULT, - default=__CHUNK_SIZE_DEFAULT, - help=f"[int]: Defaults chunk size for parallel processing to {__CHUNK_SIZE_DEFAULT}") - p.add_argument('--' + __NUM_PROC, - type=int, - required=False, - metavar=__NUM_PROC_DEFAULT, - default=__NUM_PROC_DEFAULT, - help=f"[int]: Number of threads used for parallel processing. Defaults to your CPU count {__NUM_PROC_DEFAULT}.") - - -def display_results(options: Options, result: Result): - if result.unsupported_polyhedron_elements: - logging.error(f"There is/are {len(result.unsupported_polyhedron_elements)} polyhedra that may not be converted to supported elements.") - logging.error(f"The list of the unsupported polyhedra is\n{tuple(sorted(result.unsupported_polyhedron_elements))}.") - else: - logging.info("All the polyhedra (if any) can be converted to supported elements.") - if result.unsupported_std_elements_types: - logging.error(f"There are unsupported vtk standard element types. The list of those vtk types is {tuple(sorted(result.unsupported_std_elements_types))}.") - else: - logging.info("All the standard vtk element types (if any) are supported.") \ No newline at end of file diff --git a/src/coreComponents/python/modules/geosx_mesh_doctor/parsing/vtk_output_parsing.py b/src/coreComponents/python/modules/geosx_mesh_doctor/parsing/vtk_output_parsing.py deleted file mode 100644 index 6e9b7d5d663..00000000000 --- a/src/coreComponents/python/modules/geosx_mesh_doctor/parsing/vtk_output_parsing.py +++ /dev/null @@ -1,45 +0,0 @@ -import os.path -import logging -import textwrap - -from checks.vtk_utils import VtkOutput - - -__OUTPUT_FILE = "output" -__OUTPUT_BINARY_MODE = "data-mode" -__OUTPUT_BINARY_MODE_VALUES = "binary", "ascii" -__OUTPUT_BINARY_MODE_DEFAULT = __OUTPUT_BINARY_MODE_VALUES[0] - - -def get_vtk_output_help(): - msg = \ - f"""{__OUTPUT_FILE} [string]: The vtk output file destination. - {__OUTPUT_BINARY_MODE} [string]: For ".vtu" output format, the data mode can be {" or ".join(__OUTPUT_BINARY_MODE_VALUES)}. Defaults to {__OUTPUT_BINARY_MODE_DEFAULT}.""" - return textwrap.dedent(msg) - - -def __build_arg(prefix, main): - return "-".join(filter(None, (prefix, main))) - - -def fill_vtk_output_subparser(parser, prefix="") -> None: - parser.add_argument('--' + __build_arg(prefix, __OUTPUT_FILE), - type=str, - required=True, - help=f"[string]: The vtk output file destination.") - parser.add_argument('--' + __build_arg(prefix, __OUTPUT_BINARY_MODE), - type=str, - metavar=", ".join(__OUTPUT_BINARY_MODE_VALUES), - default=__OUTPUT_BINARY_MODE_DEFAULT, - help=f"""[string]: For ".vtu" output format, the data mode can be {" or ".join(__OUTPUT_BINARY_MODE_VALUES)}. Defaults to {__OUTPUT_BINARY_MODE_DEFAULT}.""") - - -def convert(parsed_options, prefix="") -> VtkOutput: - output_key = __build_arg(prefix, __OUTPUT_FILE).replace("-", "_") - binary_mode_key = __build_arg(prefix, __OUTPUT_BINARY_MODE).replace("-", "_") - output = parsed_options[output_key] - if parsed_options[binary_mode_key] and os.path.splitext(output)[-1] == ".vtk": - logging.info("VTK data mode will be ignored for legacy file format \"vtk\".") - is_data_mode_binary: bool = parsed_options[binary_mode_key] == __OUTPUT_BINARY_MODE_DEFAULT - return VtkOutput(output=output, - is_data_mode_binary=is_data_mode_binary) diff --git a/src/coreComponents/python/modules/geosx_mesh_doctor/pyproject.toml b/src/coreComponents/python/modules/geosx_mesh_doctor/pyproject.toml deleted file mode 100644 index 20051350aef..00000000000 --- a/src/coreComponents/python/modules/geosx_mesh_doctor/pyproject.toml +++ /dev/null @@ -1,15 +0,0 @@ -[tool.pytest.ini_options] -addopts = [ - "--import-mode=importlib", -] -pythonpath = [ - "checks", "parsing", -] - -[tool.mypy] -python_version = "3.11" -warn_return_any = true -warn_unused_configs = true -ignore_missing_imports = true -allow_redefinition = true -plugins = "numpy.typing.mypy_plugin" diff --git a/src/coreComponents/python/modules/geosx_mesh_doctor/register.py b/src/coreComponents/python/modules/geosx_mesh_doctor/register.py deleted file mode 100644 index a36001e5fa7..00000000000 --- a/src/coreComponents/python/modules/geosx_mesh_doctor/register.py +++ /dev/null @@ -1,72 +0,0 @@ -import argparse -import importlib -import logging -from typing import Dict, Callable, Any, Tuple - -import parsing -from parsing import CheckHelper, cli_parsing - - -__HELPERS: Dict[str, Callable[[None], CheckHelper]] = dict() -__CHECKS: Dict[str, Callable[[None], Any]] = dict() - - -def __load_module_check(module_name: str, check_fct="check"): - module = importlib.import_module("checks." + module_name) - return getattr(module, check_fct) - - -def __load_module_check_helper(module_name: str, parsing_fct_suffix="_parsing"): - module = importlib.import_module("parsing." + module_name + parsing_fct_suffix) - return CheckHelper(fill_subparser=module.fill_subparser, - convert=module.convert, - display_results=module.display_results) - - -def __load_checks() -> Dict[str, Callable[[str, Any], Any]]: - """ - Loads all the checks. - This function acts like a protection layer if a module fails to load. - A check that fails to load won't stop the process. - :return: The checks. - """ - loaded_checks: Dict[str, Callable[[str, Any], Any]] = dict() - for check_name, check_provider in __CHECKS.items(): - try: - loaded_checks[check_name] = check_provider() - logging.debug(f"Check \"{check_name}\" is loaded.") - except Exception as e: - logging.warning(f"Could not load module \"{check_name}\": {e}") - return loaded_checks - - -def register() -> Tuple[argparse.ArgumentParser, Dict[str, Callable[[str, Any], Any]], Dict[str, CheckHelper]]: - """ - Register all the parsing checks. Eventually initiate the registration of all the checks too. - :return: The checks and the checks helpers. - """ - parser = cli_parsing.init_parser() - subparsers = parser.add_subparsers(help="Modules", dest="subparsers") - - def closure_trick(cn: str): - __HELPERS[check_name] = lambda: __load_module_check_helper(cn) - __CHECKS[check_name] = lambda: __load_module_check(cn) - # Register the modules to load here. - for check_name in (parsing.COLLOCATES_NODES, - parsing.ELEMENT_VOLUMES, - parsing.FIX_ELEMENTS_ORDERINGS, - parsing.GENERATE_CUBE, - parsing.GENERATE_FRACTURES, - parsing.GENERATE_GLOBAL_IDS, - parsing.NON_CONFORMAL, - parsing.SELF_INTERSECTING_ELEMENTS, - parsing.SUPPORTED_ELEMENTS): - closure_trick(check_name) - loaded_checks: Dict[str, Callable[[str, Any], Any]] = __load_checks() - loaded_checks_helpers: Dict[str, CheckHelper] = dict() - for check_name in loaded_checks.keys(): - h = __HELPERS[check_name]() - h.fill_subparser(subparsers) - loaded_checks_helpers[check_name] = h - logging.debug(f"Parsing for check \"{check_name}\" is loaded.") - return parser, loaded_checks, loaded_checks_helpers diff --git a/src/coreComponents/python/modules/geosx_mesh_doctor/requirements.txt b/src/coreComponents/python/modules/geosx_mesh_doctor/requirements.txt deleted file mode 100644 index 4c9b176327d..00000000000 --- a/src/coreComponents/python/modules/geosx_mesh_doctor/requirements.txt +++ /dev/null @@ -1,4 +0,0 @@ -vtk >= 9.1 -networkx >= 2.4 -tqdm -numpy \ No newline at end of file diff --git a/src/coreComponents/python/modules/geosx_mesh_doctor/setup.py b/src/coreComponents/python/modules/geosx_mesh_doctor/setup.py deleted file mode 100644 index 1d0b9915f33..00000000000 --- a/src/coreComponents/python/modules/geosx_mesh_doctor/setup.py +++ /dev/null @@ -1,3 +0,0 @@ -from setuptools import setup, find_packages - -setup(name='mesh_doctor', version='0.0.1', packages=find_packages()) diff --git a/src/coreComponents/python/modules/geosx_mesh_doctor/tests/test_cli_parsing.py b/src/coreComponents/python/modules/geosx_mesh_doctor/tests/test_cli_parsing.py deleted file mode 100644 index 445b7c92d1c..00000000000 --- a/src/coreComponents/python/modules/geosx_mesh_doctor/tests/test_cli_parsing.py +++ /dev/null @@ -1,72 +0,0 @@ -import argparse -from dataclasses import dataclass - -from typing import ( - Iterator, - Sequence, -) - -import pytest - -from checks.vtk_utils import ( - VtkOutput, -) - -from checks.generate_fractures import ( - FracturePolicy, - Options, -) -from parsing.generate_fractures_parsing import ( - convert, - display_results, - fill_subparser, -) - - -@dataclass(frozen=True) -class TestCase: - __test__ = False - cli_args: Sequence[str] - options: Options - exception: bool = False - - -def __generate_generate_fractures_parsing_test_data() -> Iterator[TestCase]: - field: str = "attribute" - main_mesh: str = "output.vtu" - fracture_mesh: str = "fracture.vtu" - - cli_gen: str = f"generate_fractures --policy {{}} --name {field} --values 0,1 --output {main_mesh} --fracture-output {fracture_mesh}" - all_cli_args = cli_gen.format("field").split(), cli_gen.format("internal_surfaces").split(), cli_gen.format("dummy").split() - policies = FracturePolicy.FIELD, FracturePolicy.INTERNAL_SURFACES, FracturePolicy.FIELD - exceptions = False, False, True - for cli_args, policy, exception in zip(all_cli_args, policies, exceptions): - options: Options = Options(policy=policy, field=field, field_values=frozenset((0, 1)), - vtk_output=VtkOutput(output=main_mesh, is_data_mode_binary=True), - vtk_fracture_output=VtkOutput(output=fracture_mesh, is_data_mode_binary=True)) - yield TestCase(cli_args, options, exception) - - -def __f(test_case: TestCase): - parser = argparse.ArgumentParser(description='Testing.') - subparsers = parser.add_subparsers() - fill_subparser(subparsers) - args = parser.parse_args(test_case.cli_args) - options = convert(vars(args)) - assert options.policy == test_case.options.policy - assert options.field == test_case.options.field - assert options.field_values == test_case.options.field_values - - -def test_display_results(): - # Dummy test for code coverage only. Shame on me! - display_results(None, None) - - -@pytest.mark.parametrize("test_case", __generate_generate_fractures_parsing_test_data()) -def test(test_case: TestCase): - if test_case.exception: - with pytest.raises(SystemExit): - __f(test_case) - else: - __f(test_case) diff --git a/src/coreComponents/python/modules/geosx_mesh_doctor/tests/test_collocated_nodes.py b/src/coreComponents/python/modules/geosx_mesh_doctor/tests/test_collocated_nodes.py deleted file mode 100644 index 6936331d14c..00000000000 --- a/src/coreComponents/python/modules/geosx_mesh_doctor/tests/test_collocated_nodes.py +++ /dev/null @@ -1,76 +0,0 @@ -from typing import Iterator, Tuple - -import pytest - -from vtkmodules.vtkCommonCore import ( - vtkPoints, -) -from vtkmodules.vtkCommonDataModel import ( - VTK_TETRA, - vtkCellArray, - vtkTetra, - vtkUnstructuredGrid, -) - -from checks.collocated_nodes import Options, __check - - -def get_points() -> Iterator[Tuple[vtkPoints, int]]: - """ - Generates the data for the cases. - One case has two nodes at the exact same position. - The other has two differente nodes - :return: Generator to (vtk points, number of expected duplicated locations) - """ - for p0, p1 in ((0, 0, 0), (1, 1, 1)), ((0, 0, 0), (0, 0, 0)): - points = vtkPoints() - points.SetNumberOfPoints(2) - points.SetPoint(0, p0) - points.SetPoint(1, p1) - num_nodes_bucket = 1 if p0 == p1 else 0 - yield points, num_nodes_bucket - - -@pytest.mark.parametrize("data", get_points()) -def test_simple_collocated_points(data: Tuple[vtkPoints, int]): - points, num_nodes_bucket = data - - mesh = vtkUnstructuredGrid() - mesh.SetPoints(points) - - result = __check(mesh, Options(tolerance=1.e-12)) - - assert len(result.wrong_support_elements) == 0 - assert len(result.nodes_buckets) == num_nodes_bucket - if num_nodes_bucket == 1: - assert len(result.nodes_buckets[0]) == points.GetNumberOfPoints() - - -def test_wrong_support_elements(): - points = vtkPoints() - points.SetNumberOfPoints(4) - points.SetPoint(0, (0, 0, 0)) - points.SetPoint(1, (1, 0, 0)) - points.SetPoint(2, (0, 1, 0)) - points.SetPoint(3, (0, 0, 1)) - - cell_types = [VTK_TETRA] - cells = vtkCellArray() - cells.AllocateExact(1, 4) - - tet = vtkTetra() - tet.GetPointIds().SetId(0, 0) - tet.GetPointIds().SetId(1, 1) - tet.GetPointIds().SetId(2, 2) - tet.GetPointIds().SetId(3, 0) # Intentionally wrong - cells.InsertNextCell(tet) - - mesh = vtkUnstructuredGrid() - mesh.SetPoints(points) - mesh.SetCells(cell_types, cells) - - result = __check(mesh, Options(tolerance=1.e-12)) - - assert len(result.nodes_buckets) == 0 - assert len(result.wrong_support_elements) == 1 - assert result.wrong_support_elements[0] == 0 diff --git a/src/coreComponents/python/modules/geosx_mesh_doctor/tests/test_element_volumes.py b/src/coreComponents/python/modules/geosx_mesh_doctor/tests/test_element_volumes.py deleted file mode 100644 index e37c22c60c0..00000000000 --- a/src/coreComponents/python/modules/geosx_mesh_doctor/tests/test_element_volumes.py +++ /dev/null @@ -1,48 +0,0 @@ -import numpy - -from vtkmodules.vtkCommonCore import ( - vtkPoints, -) -from vtkmodules.vtkCommonDataModel import ( - VTK_TETRA, - vtkCellArray, - vtkTetra, - vtkUnstructuredGrid, -) - -from checks.element_volumes import Options, __check - - -def test_simple_tet(): - # creating a simple tetrahedron - points = vtkPoints() - points.SetNumberOfPoints(4) - points.SetPoint(0, (0, 0, 0)) - points.SetPoint(1, (1, 0, 0)) - points.SetPoint(2, (0, 1, 0)) - points.SetPoint(3, (0, 0, 1)) - - cell_types = [VTK_TETRA] - cells = vtkCellArray() - cells.AllocateExact(1, 4) - - tet = vtkTetra() - tet.GetPointIds().SetId(0, 0) - tet.GetPointIds().SetId(1, 1) - tet.GetPointIds().SetId(2, 2) - tet.GetPointIds().SetId(3, 3) - cells.InsertNextCell(tet) - - mesh = vtkUnstructuredGrid() - mesh.SetPoints(points) - mesh.SetCells(cell_types, cells) - - result = __check(mesh, Options(min_volume=1.)) - - assert len(result.element_volumes) == 1 - assert result.element_volumes[0][0] == 0 - assert abs(result.element_volumes[0][1] - 1./6.) < 10 * numpy.finfo(float).eps - - result = __check(mesh, Options(min_volume=0.)) - - assert len(result.element_volumes) == 0 diff --git a/src/coreComponents/python/modules/geosx_mesh_doctor/tests/test_generate_cube.py b/src/coreComponents/python/modules/geosx_mesh_doctor/tests/test_generate_cube.py deleted file mode 100644 index 4d93abdd280..00000000000 --- a/src/coreComponents/python/modules/geosx_mesh_doctor/tests/test_generate_cube.py +++ /dev/null @@ -1,24 +0,0 @@ -from checks.generate_cube import __build, Options, FieldInfo - - -def test_generate_cube(): - options = Options( - vtk_output=None, - generate_cells_global_ids=True, - generate_points_global_ids=False, - xs=(0, 5, 10), - ys=(0, 4, 8), - zs=(0, 1), - nxs=(5, 2), - nys=(1, 1), - nzs=(1,), - fields=( - FieldInfo(name="test", dimension=2, support="CELLS"), - ) - ) - output = __build(options) - assert output.GetNumberOfCells() == 14 - assert output.GetNumberOfPoints() == 48 - assert output.GetCellData().GetArray("test").GetNumberOfComponents() == 2 - assert output.GetCellData().GetGlobalIds() - assert not output.GetPointData().GetGlobalIds() diff --git a/src/coreComponents/python/modules/geosx_mesh_doctor/tests/test_generate_fractures.py b/src/coreComponents/python/modules/geosx_mesh_doctor/tests/test_generate_fractures.py deleted file mode 100644 index f197731c1d9..00000000000 --- a/src/coreComponents/python/modules/geosx_mesh_doctor/tests/test_generate_fractures.py +++ /dev/null @@ -1,262 +0,0 @@ -from dataclasses import dataclass - -from typing import ( - Tuple, - Iterable, - Iterator, - Sequence, -) - -import numpy - -import pytest - -from vtkmodules.vtkCommonDataModel import ( - vtkUnstructuredGrid, - VTK_HEXAHEDRON, - VTK_POLYHEDRON, - VTK_QUAD, -) -from vtkmodules.util.numpy_support import ( - numpy_to_vtk, -) - -from checks.vtk_utils import ( - to_vtk_id_list, -) - -from checks.check_fractures import format_collocated_nodes -from checks.generate_cube import build_rectilinear_blocks_mesh, XYZ -from checks.generate_fractures import __split_mesh_on_fracture, Options, FracturePolicy - - -@dataclass(frozen=True) -class TestResult: - __test__ = False - main_mesh_num_points: int - main_mesh_num_cells: int - fracture_mesh_num_points: int - fracture_mesh_num_cells: int - - -@dataclass(frozen=True) -class TestCase: - __test__ = False - input_mesh: vtkUnstructuredGrid - options: Options - collocated_nodes: Sequence[Sequence[int]] - result: TestResult - - -def __build_test_case(xs: Tuple[numpy.ndarray, numpy.ndarray, numpy.ndarray], - attribute: Iterable[int], - field_values: Iterable[int] = None, - policy: FracturePolicy = FracturePolicy.FIELD): - xyz = XYZ(*xs) - - mesh: vtkUnstructuredGrid = build_rectilinear_blocks_mesh((xyz, )) - - ref = numpy.array(attribute, dtype=int) - if policy == FracturePolicy.FIELD: - assert len(ref) == mesh.GetNumberOfCells() - attr = numpy_to_vtk(ref) - attr.SetName("attribute") - mesh.GetCellData().AddArray(attr) - - if field_values is None: - fv = frozenset(attribute) - else: - fv = frozenset(field_values) - - options = Options(policy=policy, - field="attribute", - field_values=fv, - vtk_output=None, - vtk_fracture_output=None) - return mesh, options - - -# Utility class to generate the new indices of the newly created collocated nodes. -class Incrementor: - def __init__(self, start): - self.__val = start - - def next(self, num: int) -> Iterable[int]: - self.__val += num - return range(self.__val - num, self.__val) - - -def __generate_test_data() -> Iterator[TestCase]: - two_nodes = numpy.arange(2, dtype=float) - three_nodes = numpy.arange(3, dtype=float) - four_nodes = numpy.arange(4, dtype=float) - - # Split in 2 - mesh, options = __build_test_case((three_nodes, three_nodes, three_nodes), (0, 1, 0, 1, 0, 1, 0, 1)) - yield TestCase(input_mesh=mesh, options=options, - collocated_nodes=tuple(map(lambda i: (1 + 3 * i, 27 + i), range(9))), - result=TestResult(9 * 4, 8, 9, 4)) - - # Split in 3 - inc = Incrementor(27) - collocated_nodes: Sequence[Sequence[int]] = ( - (1, *inc.next(1)), - (3, *inc.next(1)), - (4, *inc.next(2)), - (7, *inc.next(1)), - (1 + 9, *inc.next(1)), - (3 + 9, *inc.next(1)), - (4 + 9, *inc.next(2)), - (7 + 9, *inc.next(1)), - (1 + 18, *inc.next(1)), - (3 + 18, *inc.next(1)), - (4 + 18, *inc.next(2)), - (7 + 18, *inc.next(1)), - ) - mesh, options = __build_test_case((three_nodes, three_nodes, three_nodes), (0, 1, 2, 1, 0, 1, 2, 1)) - yield TestCase(input_mesh=mesh, options=options, collocated_nodes=collocated_nodes, - result=TestResult(9 * 4 + 6, 8, 12, 6)) - - # Split in 8 - inc = Incrementor(27) - collocated_nodes: Sequence[Sequence[int]] = ( - (1, *inc.next(1)), - (3, *inc.next(1)), - (4, *inc.next(3)), - (5, *inc.next(1)), - (7, *inc.next(1)), - (0 + 9, *inc.next(1)), - (1 + 9, *inc.next(3)), - (2 + 9, *inc.next(1)), - (3 + 9, *inc.next(3)), - (4 + 9, *inc.next(7)), - (5 + 9, *inc.next(3)), - (6 + 9, *inc.next(1)), - (7 + 9, *inc.next(3)), - (8 + 9, *inc.next(1)), - (1 + 18, *inc.next(1)), - (3 + 18, *inc.next(1)), - (4 + 18, *inc.next(3)), - (5 + 18, *inc.next(1)), - (7 + 18, *inc.next(1)), - ) - mesh, options = __build_test_case((three_nodes, three_nodes, three_nodes), range(8)) - yield TestCase(input_mesh=mesh, options=options, collocated_nodes=collocated_nodes, - result=TestResult(8 * 8, 8, 3 * 3 * 3 - 8, 12)) - - # Straight notch - inc = Incrementor(27) - collocated_nodes: Sequence[Sequence[int]] = ( - (1, *inc.next(1)), - (4,), - (1 + 9, *inc.next(1)), - (4 + 9,), - (1 + 18, *inc.next(1)), - (4 + 18,), - ) - mesh, options = __build_test_case((three_nodes, three_nodes, three_nodes), (0, 1, 2, 2, 0, 1, 2, 2), field_values=(0, 1)) - yield TestCase(input_mesh=mesh, options=options, collocated_nodes=collocated_nodes, - result=TestResult(3 * 3 * 3 + 3, 8, 6, 2)) - - # L-shaped notch - inc = Incrementor(27) - collocated_nodes: Sequence[Sequence[int]] = ( - (1, *inc.next(1)), - (4, *inc.next(1)), - (7, *inc.next(1)), - (1 + 9, *inc.next(1)), - (4 + 9,), - (7 + 9,), - (1 + 18, *inc.next(1)), - (4 + 18,), - ) - mesh, options = __build_test_case((three_nodes, three_nodes, three_nodes), (0, 1, 0, 1, 0, 1, 2, 2), field_values=(0, 1)) - yield TestCase(input_mesh=mesh, options=options, collocated_nodes=collocated_nodes, - result=TestResult(3 * 3 * 3 + 5, 8, 8, 3)) - - # 3x1x1 split - inc = Incrementor(2 * 2 * 4) - collocated_nodes: Sequence[Sequence[int]] = ( - (1, *inc.next(1)), - (2, *inc.next(1)), - (5, *inc.next(1)), - (6, *inc.next(1)), - (1 + 8, *inc.next(1)), - (2 + 8, *inc.next(1)), - (5 + 8, *inc.next(1)), - (6 + 8, *inc.next(1)), - ) - mesh, options = __build_test_case((four_nodes, two_nodes, two_nodes), (0, 1, 2)) - yield TestCase(input_mesh=mesh, options=options, collocated_nodes=collocated_nodes, - result=TestResult(6 * 4, 3, 2 * 4, 2)) - - # Discarded fracture element if no node duplication. - collocated_nodes: Sequence[Sequence[int]] = () - mesh, options = __build_test_case((three_nodes, four_nodes, four_nodes), [0, ] * 8 + [1, 2] + [0, ] * 8, field_values=(1, 2)) - yield TestCase(input_mesh=mesh, options=options, collocated_nodes=collocated_nodes, - result=TestResult(3 * 4 * 4, 2 * 3 * 3, 0, 0)) - - # Fracture on a corner - inc = Incrementor(3 * 4 * 4) - collocated_nodes: Sequence[Sequence[int]] = ( - (1 + 12,), - (4 + 12,), - (7 + 12,), - (1 + 12 * 2, *inc.next(1)), - (4 + 12 * 2, *inc.next(1)), - (7 + 12 * 2,), - (1 + 12 * 3, *inc.next(1)), - (4 + 12 * 3, *inc.next(1)), - (7 + 12 * 3,), - ) - mesh, options = __build_test_case((three_nodes, four_nodes, four_nodes), [0, ] * 6 + [1, 2, 1, 2, 0, 0, 1, 2, 1, 2, 0, 0], field_values=(1, 2)) - yield TestCase(input_mesh=mesh, options=options, collocated_nodes=collocated_nodes, - result=TestResult(3 * 4 * 4 + 4, 2 * 3 * 3, 9, 4)) - - # Generate mesh with 2 hexs, one being a standard hex, the other a 42 hex. - inc = Incrementor(3 * 2 * 2) - collocated_nodes: Sequence[Sequence[int]] = ( - (1, *inc.next(1)), - (1 + 3, *inc.next(1)), - (1 + 6, *inc.next(1)), - (1 + 9, *inc.next(1)), - ) - mesh, options = __build_test_case((three_nodes, two_nodes, two_nodes), (0, 1)) - polyhedron_mesh = vtkUnstructuredGrid() - polyhedron_mesh.SetPoints(mesh.GetPoints()) - polyhedron_mesh.Allocate(2) - polyhedron_mesh.InsertNextCell(VTK_HEXAHEDRON, to_vtk_id_list((1, 2, 5, 4, 7, 8, 10, 11))) - poly = to_vtk_id_list([6] + [4, 0, 1, 7, 6] + [4, 1, 4, 10, 7] + [4, 4, 3, 9, 10] + [4, 3, 0, 6, 9] + [4, 6, 7, 10, 9] + [4, 1, 0, 3, 4]) - polyhedron_mesh.InsertNextCell(VTK_POLYHEDRON, poly) - polyhedron_mesh.GetCellData().AddArray(mesh.GetCellData().GetArray("attribute")) - - yield TestCase(input_mesh=polyhedron_mesh, options=options, collocated_nodes=collocated_nodes, - result=TestResult(4 * 4, 2, 4, 1)) - - # Split in 2 using the internal fracture description - inc = Incrementor(3 * 2 * 2) - collocated_nodes: Sequence[Sequence[int]] = ( - (1, *inc.next(1)), - (1 + 3, *inc.next(1)), - (1 + 6, *inc.next(1)), - (1 + 9, *inc.next(1)), - ) - mesh, options = __build_test_case((three_nodes, two_nodes, two_nodes), attribute=(0, 0, 0), field_values=(0,), - policy=FracturePolicy.INTERNAL_SURFACES) - mesh.InsertNextCell(VTK_QUAD, to_vtk_id_list((1, 4, 7, 10))) # Add a fracture on the fly - yield TestCase(input_mesh=mesh, options=options, - collocated_nodes=collocated_nodes, - result=TestResult(4 * 4, 3, 4, 1)) - - -@pytest.mark.parametrize("test_case", __generate_test_data()) -def test_generate_fracture(test_case: TestCase): - main_mesh, fracture_mesh = __split_mesh_on_fracture(test_case.input_mesh, test_case.options) - assert main_mesh.GetNumberOfPoints() == test_case.result.main_mesh_num_points - assert main_mesh.GetNumberOfCells() == test_case.result.main_mesh_num_cells - assert fracture_mesh.GetNumberOfPoints() == test_case.result.fracture_mesh_num_points - assert fracture_mesh.GetNumberOfCells() == test_case.result.fracture_mesh_num_cells - - res = format_collocated_nodes(fracture_mesh) - assert res == test_case.collocated_nodes - assert len(res) == test_case.result.fracture_mesh_num_points diff --git a/src/coreComponents/python/modules/geosx_mesh_doctor/tests/test_generate_global_ids.py b/src/coreComponents/python/modules/geosx_mesh_doctor/tests/test_generate_global_ids.py deleted file mode 100644 index 5dc7c1bad4a..00000000000 --- a/src/coreComponents/python/modules/geosx_mesh_doctor/tests/test_generate_global_ids.py +++ /dev/null @@ -1,32 +0,0 @@ -from vtkmodules.vtkCommonCore import ( - vtkPoints, ) -from vtkmodules.vtkCommonDataModel import ( - VTK_VERTEX, - vtkCellArray, - vtkUnstructuredGrid, - vtkVertex, -) - -from checks.generate_global_ids import __build_global_ids - - -def test_generate_global_ids(): - points = vtkPoints() - points.InsertNextPoint(0, 0, 0) - - vertex = vtkVertex() - vertex.GetPointIds().SetId(0, 0) - - vertices = vtkCellArray() - vertices.InsertNextCell(vertex) - - mesh = vtkUnstructuredGrid() - mesh.SetPoints(points) - mesh.SetCells([VTK_VERTEX], vertices) - - __build_global_ids(mesh, True, True) - - global_cell_ids = mesh.GetCellData().GetGlobalIds() - global_point_ids = mesh.GetPointData().GetGlobalIds() - assert global_cell_ids.GetNumberOfValues() == 1 - assert global_point_ids.GetNumberOfValues() == 1 diff --git a/src/coreComponents/python/modules/geosx_mesh_doctor/tests/test_non_conformal.py b/src/coreComponents/python/modules/geosx_mesh_doctor/tests/test_non_conformal.py deleted file mode 100644 index bcf60fe962b..00000000000 --- a/src/coreComponents/python/modules/geosx_mesh_doctor/tests/test_non_conformal.py +++ /dev/null @@ -1,67 +0,0 @@ -import numpy - -from checks.non_conformal import Options, __check -from checks.generate_cube import ( - build_rectilinear_blocks_mesh, - XYZ, -) - - -def test_two_close_hexs(): - delta = 1.e-6 - tmp = numpy.arange(2, dtype=float) - xyz0 = XYZ(tmp, tmp, tmp) - xyz1 = XYZ(tmp + 1 + delta, tmp, tmp) - mesh = build_rectilinear_blocks_mesh((xyz0, xyz1)) - - # Close enough, but points tolerance is too strict to consider the faces matching. - options = Options(angle_tolerance=1., point_tolerance=delta / 2, face_tolerance=delta * 2) - results = __check(mesh, options) - assert len(results.non_conformal_cells) == 1 - assert set(results.non_conformal_cells[0]) == {0, 1} - - # Close enough, and points tolerance is loose enough to consider the faces matching. - options = Options(angle_tolerance=1., point_tolerance=delta * 2, face_tolerance=delta * 2) - results = __check(mesh, options) - assert len(results.non_conformal_cells) == 0 - - -def test_two_distant_hexs(): - delta = 1 - tmp = numpy.arange(2, dtype=float) - xyz0 = XYZ(tmp, tmp, tmp) - xyz1 = XYZ(tmp + 1 + delta, tmp, tmp) - mesh = build_rectilinear_blocks_mesh((xyz0, xyz1)) - - options = Options(angle_tolerance=1., point_tolerance=delta / 2., face_tolerance=delta / 2.) - - results = __check(mesh, options) - assert len(results.non_conformal_cells) == 0 - - -def test_two_close_shifted_hexs(): - delta_x, delta_y = 1.e-6, 0.5 - tmp = numpy.arange(2, dtype=float) - xyz0 = XYZ(tmp, tmp, tmp) - xyz1 = XYZ(tmp + 1 + delta_x, tmp + delta_y, tmp + delta_y) - mesh = build_rectilinear_blocks_mesh((xyz0, xyz1)) - - options = Options(angle_tolerance=1., point_tolerance=delta_x * 2, face_tolerance=delta_x * 2) - - results = __check(mesh, options) - assert len(results.non_conformal_cells) == 1 - assert set(results.non_conformal_cells[0]) == {0, 1} - - -def test_big_elem_next_to_small_elem(): - delta = 1.e-6 - tmp = numpy.arange(2, dtype=float) - xyz0 = XYZ(tmp, tmp + 1, tmp + 1) - xyz1 = XYZ(3 * tmp + 1 + delta, 3 * tmp, 3 * tmp) - mesh = build_rectilinear_blocks_mesh((xyz0, xyz1)) - - options = Options(angle_tolerance=1., point_tolerance=delta * 2, face_tolerance=delta * 2) - - results = __check(mesh, options) - assert len(results.non_conformal_cells) == 1 - assert set(results.non_conformal_cells[0]) == {0, 1} diff --git a/src/coreComponents/python/modules/geosx_mesh_doctor/tests/test_reorient_mesh.py b/src/coreComponents/python/modules/geosx_mesh_doctor/tests/test_reorient_mesh.py deleted file mode 100644 index 1136bbb7704..00000000000 --- a/src/coreComponents/python/modules/geosx_mesh_doctor/tests/test_reorient_mesh.py +++ /dev/null @@ -1,109 +0,0 @@ -from dataclasses import dataclass -from typing import Generator - -import pytest - -from vtkmodules.vtkCommonCore import ( - vtkIdList, - vtkPoints, -) -from vtkmodules.vtkCommonDataModel import ( - VTK_POLYHEDRON, - vtkUnstructuredGrid, -) - -import numpy - -from checks.reorient_mesh import reorient_mesh -from checks.vtk_polyhedron import FaceStream -from checks.vtk_utils import ( - to_vtk_id_list, - vtk_iter, -) - - -@dataclass(frozen=True) -class Expected: - mesh: vtkUnstructuredGrid - face_stream: FaceStream - - -def __build_test_meshes() -> Generator[Expected, None, None]: - # Creating the support nodes for the polyhedron. - # It has a C shape and is actually non-convex, non star-shaped. - front_nodes = numpy.array(( - (0, 0, 0), - (3, 0, 0), - (3, 1, 0), - (1, 1, 0), - (1, 2, 0), - (3, 2, 0), - (3, 3, 0), - (0, 3, 0), - ), dtype=float) - front_nodes = numpy.array(front_nodes, dtype=float) - back_nodes = front_nodes - (0., 0., 1.) - - n = len(front_nodes) - - points = vtkPoints() - points.Allocate(2 * n) - for coords in front_nodes: - points.InsertNextPoint(coords) - for coords in back_nodes: - points.InsertNextPoint(coords) - - # Creating the polyhedron with faces all directed outward. - faces = [] - # Creating the side faces - for i in range(n): - faces.append( - (i % n + n, (i + 1) % n + n, (i + 1) % n, i % n) - ) - # Creating the front faces - faces.append(tuple(range(n))) - faces.append(tuple(reversed(range(n, 2 * n)))) - face_stream = FaceStream(faces) - - # Creating multiple meshes, each time with one unique polyhedron, - # but with different "face flip status". - # First case, no face is flipped. - mesh = vtkUnstructuredGrid() - mesh.Allocate(1) - mesh.SetPoints(points) - mesh.InsertNextCell(VTK_POLYHEDRON, to_vtk_id_list( - face_stream.dump() - )) - yield Expected(mesh=mesh, face_stream=face_stream) - - # Here, two faces are flipped. - mesh = vtkUnstructuredGrid() - mesh.Allocate(1) - mesh.SetPoints(points) - mesh.InsertNextCell(VTK_POLYHEDRON, to_vtk_id_list( - face_stream.flip_faces((1, 2)).dump() - )) - yield Expected(mesh=mesh, face_stream=face_stream) - - # Last, all faces are flipped. - mesh = vtkUnstructuredGrid() - mesh.Allocate(1) - mesh.SetPoints(points) - mesh.InsertNextCell(VTK_POLYHEDRON, to_vtk_id_list( - face_stream.flip_faces(range(len(faces))).dump() - )) - yield Expected(mesh=mesh, face_stream=face_stream) - - -@pytest.mark.parametrize("expected", __build_test_meshes()) -def test_reorient_polyhedron(expected: Expected): - output_mesh = reorient_mesh(expected.mesh, range(expected.mesh.GetNumberOfCells())) - assert output_mesh.GetNumberOfCells() == 1 - assert output_mesh.GetCell(0).GetCellType() == VTK_POLYHEDRON - face_stream_ids = vtkIdList() - output_mesh.GetFaceStream(0, face_stream_ids) - # Note that the following makes a raw (but simple) check. - # But one may need to be more precise some day, - # since triangular faces (0, 1, 2) and (1, 2, 0) should be considered as equivalent. - # And the current simpler check does not consider this case. - assert tuple(vtk_iter(face_stream_ids)) == expected.face_stream.dump() diff --git a/src/coreComponents/python/modules/geosx_mesh_doctor/tests/test_self_intersecting_elements.py b/src/coreComponents/python/modules/geosx_mesh_doctor/tests/test_self_intersecting_elements.py deleted file mode 100644 index 8993e68bf48..00000000000 --- a/src/coreComponents/python/modules/geosx_mesh_doctor/tests/test_self_intersecting_elements.py +++ /dev/null @@ -1,50 +0,0 @@ -from vtkmodules.vtkCommonCore import ( - vtkPoints, -) -from vtkmodules.vtkCommonDataModel import ( - VTK_HEXAHEDRON, - vtkCellArray, - vtkHexahedron, - vtkUnstructuredGrid, -) - - -from checks.self_intersecting_elements import Options, __check - - -def test_jumbled_hex(): - # creating a simple hexahedron - points = vtkPoints() - points.SetNumberOfPoints(8) - points.SetPoint(0, (0, 0, 0)) - points.SetPoint(1, (1, 0, 0)) - points.SetPoint(2, (1, 1, 0)) - points.SetPoint(3, (0, 1, 0)) - points.SetPoint(4, (0, 0, 1)) - points.SetPoint(5, (1, 0, 1)) - points.SetPoint(6, (1, 1, 1)) - points.SetPoint(7, (0, 1, 1)) - - cell_types = [VTK_HEXAHEDRON] - cells = vtkCellArray() - cells.AllocateExact(1, 8) - - hex = vtkHexahedron() - hex.GetPointIds().SetId(0, 0) - hex.GetPointIds().SetId(1, 1) - hex.GetPointIds().SetId(2, 3) # Intentionally wrong - hex.GetPointIds().SetId(3, 2) # Intentionally wrong - hex.GetPointIds().SetId(4, 4) - hex.GetPointIds().SetId(5, 5) - hex.GetPointIds().SetId(6, 6) - hex.GetPointIds().SetId(7, 7) - cells.InsertNextCell(hex) - - mesh = vtkUnstructuredGrid() - mesh.SetPoints(points) - mesh.SetCells(cell_types, cells) - - result = __check(mesh, Options(tolerance=0.)) - - assert len(result.intersecting_faces_elements) == 1 - assert result.intersecting_faces_elements[0] == 0 diff --git a/src/coreComponents/python/modules/geosx_mesh_doctor/tests/test_supported_elements.py b/src/coreComponents/python/modules/geosx_mesh_doctor/tests/test_supported_elements.py deleted file mode 100644 index 639d9043d8b..00000000000 --- a/src/coreComponents/python/modules/geosx_mesh_doctor/tests/test_supported_elements.py +++ /dev/null @@ -1,125 +0,0 @@ -import os -from typing import Tuple - -import pytest - -from vtkmodules.vtkCommonCore import ( - vtkIdList, - vtkPoints, -) -from vtkmodules.vtkCommonDataModel import ( - VTK_POLYHEDRON, - vtkUnstructuredGrid, -) - -from checks.supported_elements import Options, check, __check -from checks.vtk_polyhedron import parse_face_stream, build_face_to_face_connectivity_through_edges, FaceStream -from checks.vtk_utils import ( - to_vtk_id_list, -) - - -@pytest.mark.parametrize("base_name", - ("supportedElements.vtk", "supportedElementsAsVTKPolyhedra.vtk")) -def test_supported_elements(base_name) -> None: - """ - Testing that the supported elements are properly detected as supported! - :param base_name: Supported elements are provided as standard elements or polyhedron elements. - """ - directory = os.path.dirname(os.path.realpath(__file__)) - supported_elements_file_name = os.path.join(directory, "../../../../unitTests/meshTests", base_name) - options = Options(chunk_size=1, num_proc=4) - result = check(supported_elements_file_name, options) - assert not result.unsupported_std_elements_types - assert not result.unsupported_polyhedron_elements - - -def make_dodecahedron() -> Tuple[vtkPoints, vtkIdList]: - """ - Returns the points and faces for a dodecahedron. - This code was adapted from an official vtk example. - :return: The tuple of points and faces (as vtk instances). - """ - points = ( - (1.21412, 0, 1.58931), - (0.375185, 1.1547, 1.58931), - (-0.982247, 0.713644, 1.58931), - (-0.982247, -0.713644, 1.58931), - (0.375185, -1.1547, 1.58931), - (1.96449, 0, 0.375185), - (0.607062, 1.86835, 0.375185), - (-1.58931, 1.1547, 0.375185), - (-1.58931, -1.1547, 0.375185), - (0.607062, -1.86835, 0.375185), - (1.58931, 1.1547, -0.375185), - (-0.607062, 1.86835, -0.375185), - (-1.96449, 0, -0.375185), - (-0.607062, -1.86835, -0.375185), - (1.58931, -1.1547, -0.375185), - (0.982247, 0.713644, -1.58931), - (-0.375185, 1.1547, -1.58931), - (-1.21412, 0, -1.58931), - (-0.375185, -1.1547, -1.58931), - (0.982247, -0.713644, -1.58931) - ) - - faces = (12, # number of faces - 5, 0, 1, 2, 3, 4, # number of ids on face, ids - 5, 0, 5, 10, 6, 1, - 5, 1, 6, 11, 7, 2, - 5, 2, 7, 12, 8, 3, - 5, 3, 8, 13, 9, 4, - 5, 4, 9, 14, 5, 0, - 5, 15, 10, 5, 14, 19, - 5, 16, 11, 6, 10, 15, - 5, 17, 12, 7, 11, 16, - 5, 18, 13, 8, 12, 17, - 5, 19, 14, 9, 13, 18, - 5, 19, 18, 17, 16, 15) - - p = vtkPoints() - p.Allocate(len(points)) - for coords in points: - p.InsertNextPoint(coords) - - f = to_vtk_id_list(faces) - - return p, f - - -def test_dodecahedron() -> None: - """ - Tests that a dodecahedron is not supported by GEOSX. - """ - points, faces = make_dodecahedron() - mesh = vtkUnstructuredGrid() - mesh.Allocate(1) - mesh.SetPoints(points) - mesh.InsertNextCell(VTK_POLYHEDRON, faces) - - result = __check(mesh, Options(num_proc=1, chunk_size=1)) - assert set(result.unsupported_polyhedron_elements) == {0} - assert not result.unsupported_std_elements_types - - -def test_parse_face_stream() -> None: - _, faces = make_dodecahedron() - result = parse_face_stream(faces) - expected = ( - (0, 1, 2, 3, 4), - (0, 5, 10, 6, 1), - (1, 6, 11, 7, 2), - (2, 7, 12, 8, 3), - (3, 8, 13, 9, 4), - (4, 9, 14, 5, 0), - (15, 10, 5, 14, 19), - (16, 11, 6, 10, 15), - (17, 12, 7, 11, 16), - (18, 13, 8, 12, 17), - (19, 14, 9, 13, 18), - (19, 18, 17, 16, 15) - ) - assert result == expected - face_stream = FaceStream.build_from_vtk_id_list(faces) - assert face_stream.num_faces == 12 - assert face_stream.num_support_points == 20 diff --git a/src/coreComponents/python/modules/geosx_mesh_doctor/tests/test_triangle_distance.py b/src/coreComponents/python/modules/geosx_mesh_doctor/tests/test_triangle_distance.py deleted file mode 100644 index 605169b644f..00000000000 --- a/src/coreComponents/python/modules/geosx_mesh_doctor/tests/test_triangle_distance.py +++ /dev/null @@ -1,178 +0,0 @@ -from dataclasses import dataclass - -import numpy -from numpy.linalg import norm -import pytest - -from checks.triangle_distance import distance_between_two_segments, distance_between_two_triangles - - -@dataclass(frozen=True) -class ExpectedSeg: - p0: numpy.array - u0: numpy.array - p1: numpy.array - u1: numpy.array - x: numpy.array - y: numpy.array - - @classmethod - def from_tuples(cls, p0, u0, p1, u1, x, y): - return cls( - numpy.array(p0), - numpy.array(u0), - numpy.array(p1), - numpy.array(u1), - numpy.array(x), - numpy.array(y) - ) - - -def __get_segments_references(): - # Node to node configuration. - yield ExpectedSeg.from_tuples( - p0=(0., 0., 0.), - u0=(1., 0., 0.), - p1=(2., 0., 0.), - u1=(1., 0., 0.), - x=(1., 0., 0.), - y=(2., 0., 0.), - ) - # Node to edge configuration. - yield ExpectedSeg.from_tuples( - p0=(0., 0., 0.), - u0=(1., 0., 0.), - p1=(2., -1., -1.), - u1=(0., 1., 1.), - x=(1., 0., 0.), - y=(2., 0., 0.), - ) - # Edge to edge configuration. - yield ExpectedSeg.from_tuples( - p0=(0., 0., -1.), - u0=(0., 0., 2.), - p1=(1., -1., -1.), - u1=(0., 2., 2.), - x=(0., 0., 0.), - y=(1., 0., 0.), - ) - # Example from "On fast computation of distance between line segments" by Vladimir J. Lumelsky. - # Information Processing Letters, Vol. 21, number 2, pages 55-61, 08/16/1985. - # It's a node to edge configuration. - yield ExpectedSeg.from_tuples( - p0=(0., 0., 0.), - u0=(1., 2., 1.), - p1=(1., 0., 0.), - u1=(1., 1., 0.), - x=(1./6., 2./6., 1./6.), - y=(1., 0., 0.), - ) - # Overlapping edges. - yield ExpectedSeg.from_tuples( - p0=(0., 0., 0.), - u0=(2., 0., 0.), - p1=(1., 0., 0.), - u1=(2., 0., 0.), - x=(0., 0., 0.), - y=(0., 0., 0.), - ) - # Crossing edges. - yield ExpectedSeg.from_tuples( - p0=(0., 0., 0.), - u0=(2., 0., 0.), - p1=(1., -1., 0.), - u1=(0., 2., 0.), - x=(0., 0., 0.), - y=(0., 0., 0.), - ) - - -@pytest.mark.parametrize("expected", __get_segments_references()) -def test_segments(expected: ExpectedSeg): - eps = numpy.finfo(float).eps - x, y = distance_between_two_segments(expected.p0, expected.u0, expected.p1, expected.u1) - if norm(expected.x - expected.y) == 0: - assert norm(x - y) == 0. - else: - assert norm(expected.x - x) < eps - assert norm(expected.y - y) < eps - - -@dataclass(frozen=True) -class ExpectedTri: - t0: numpy.array - t1: numpy.array - d: float - p0: numpy.array - p1: numpy.array - - @classmethod - def from_tuples(cls, t0, t1, d, p0, p1): - return cls( - numpy.array(t0), - numpy.array(t1), - float(d), - numpy.array(p0), - numpy.array(p1) - ) - - -def __get_triangles_references(): - # Node to node configuration. - yield ExpectedTri.from_tuples( - t0=((0., 0., 0.), (1., 0., 0.), (0., 1., 1.)), - t1=((2., 0., 0.), (3., 0., 0.), (2., 1., 1.)), - d=1., - p0=(1., 0., 0.), - p1=(2., 0., 0.) - ) - # Node to edge configuration. - yield ExpectedTri.from_tuples( - t0=((0., 0., 0.), (1., 0., 0.), (0., 1., 1.)), - t1=((2., -1., 0.), (3., 0., 0.), (2., 1., 0.)), - d=1., - p0=(1., 0., 0.), - p1=(2., 0., 0.) - ) - # Edge to edge configuration. - yield ExpectedTri.from_tuples( - t0=((0., 0., 0.), (1., 1., 1.), (1., -1., -1.)), - t1=((2., -1., 0.), (2., 1., 0.), (3., 0., 0.)), - d=1., - p0=(1., 0., 0.), - p1=(2., 0., 0.) - ) - # Point to face configuration. - yield ExpectedTri.from_tuples( - t0=((0., 0., 0.), (1., 0., 0.), (0., 1., 1.)), - t1=((2., -1., 0.), (2., 1., -1.), (2, 1., 1.)), - d=1., - p0=(1., 0., 0.), - p1=(2., 0., 0.) - ) - # Same triangles configuration. - yield ExpectedTri.from_tuples( - t0=((0., 0., 0.), (1., 0., 0.), (0., 1., 1.)), - t1=((0., 0., 0.), (1., 0., 0.), (0., 1., 1.)), - d=0., - p0=(0., 0., 0.), - p1=(0., 0., 0.) - ) - # Crossing triangles configuration. - yield ExpectedTri.from_tuples( - t0=((0., 0., 0.), (2., 0., 0.), (2., 0., 1.)), - t1=((1., -1., 0.), (1., 1., 0.), (1., 1., 1.)), - d=0., - p0=(0., 0., 0.), - p1=(0., 0., 0.) - ) - - -@pytest.mark.parametrize("expected", __get_triangles_references()) -def test_triangles(expected: ExpectedTri): - eps = numpy.finfo(float).eps - d, p0, p1 = distance_between_two_triangles(expected.t0, expected.t1) - assert abs(d - expected.d) < eps - if d != 0: - assert norm(p0 - expected.p0) < eps - assert norm(p1 - expected.p1) < eps diff --git a/src/coreComponents/python/modules/geosx_mesh_tools_package/geosx_mesh_tools/__init__.py b/src/coreComponents/python/modules/geosx_mesh_tools_package/geosx_mesh_tools/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/src/coreComponents/python/modules/geosx_mesh_tools_package/geosx_mesh_tools/abaqus_converter.py b/src/coreComponents/python/modules/geosx_mesh_tools_package/geosx_mesh_tools/abaqus_converter.py deleted file mode 100644 index 14f62992d92..00000000000 --- a/src/coreComponents/python/modules/geosx_mesh_tools_package/geosx_mesh_tools/abaqus_converter.py +++ /dev/null @@ -1,170 +0,0 @@ -import meshio # type: ignore[import] -from meshio._mesh import CellBlock # type: ignore[import] -import numpy as np -import logging - - -def convert_abaqus_to_gmsh(input_mesh: str, output_mesh: str, logger: logging.Logger = None) -> int: - """ - Convert an abaqus mesh to gmsh 2 format, preserving nodeset information. - - If the code encounters any issues with region/element indices, - the conversion will attempt to continue, with errors - indicated by -1 values in the output file. - - Args: - input_mesh (str): path of the input abaqus file - output_mesh (str): path of the output gmsh file - logger (logging.Logger): an instance of logging.Logger - - Returns: - int: Number of potential warnings encountered during conversion - """ - # Initialize the logger if it is empty - if not logger: - logging.basicConfig(level=logging.WARNING) - logger = logging.getLogger(__name__) - - # Keep track of the number of warnings - n_warnings = 0 - - # Load the mesh - logger.info('Reading abaqus mesh...') - mesh = meshio.read(input_mesh, file_format="abaqus") - - # Convert the element regions to tags - logger.info('Converting region tags...') - region_list = list(mesh.cell_sets.keys()) - n_regions = len(region_list) - cell_ids = [] - for block_id, block in enumerate(mesh.cells): - cell_ids.append(np.zeros(len(block[1]), dtype=int) - 1) - for region_id, region in enumerate(region_list): - mesh.field_data[region] = [region_id + 1, 3] - cell_ids[block_id][mesh.cell_sets[region][block_id]] = region_id + 1 - - # Check for bad element region conversions - if (-1 in cell_ids[-1]): - logger.warning('Some element regions in block %i did not convert correctly to tags!' % (block_id)) - logger.warning('Note: These will be indicated by a -1 in the output file.') - n_warnings += 1 - - # Add to the meshio datastructure - # Note: the copy here is required, so that later appends - # do not break these dicts - mesh.cell_data['gmsh:physical'] = cell_ids.copy() - mesh.cell_data['gmsh:geometrical'] = cell_ids.copy() - - # Build the face elements - logger.info('Converting nodesets to face elements, tags...') - new_tris, tri_nodeset, tri_region = [], [], [] - new_quads, quad_nodeset, quad_region = [], [], [] - - for nodeset_id, nodeset_name in enumerate(mesh.point_sets): - logger.info(' %s' % (nodeset_name)) - mesh.field_data[nodeset_name] = [nodeset_id + n_regions + 1, 2] - nodeset = mesh.point_sets[nodeset_name] - - # Search by block, then element - for block_id, block in enumerate(mesh.cells): - for element_id, element in enumerate(block[1]): - # Find any matching nodes - matching_nodes = [x for x in element if x in nodeset] - - # Add a new face element if there are enough nodes - n_matching = len(matching_nodes) - if (n_matching >= 3): - # Find the region - region_id = -1 - for region in region_list: - if (element_id in mesh.cell_sets[region][block_id]): - region_id = mesh.field_data[region][block_id] - - # Test to see if the element is a quad or triangle - tag_id = mesh.field_data[nodeset_name][0] - if (n_matching == 3): - new_tris.append(matching_nodes) - tri_nodeset.append(tag_id) - tri_region.append(region_id) - - elif (n_matching == 4): - new_quads.append(matching_nodes) - quad_nodeset.append(tag_id) - quad_region.append(region_id) - - else: - logger.warning(' Discarding an element with an unexpected number of nodes') - logger.warning(' n_nodes=%i, element=%i, set=%s' % (n_matching, element_id, nodeset_name)) - n_warnings += 1 - - # Add new tris - if new_tris: - logger.info(' Adding %i new triangles...' % (len(new_tris))) - if (-1 in tri_region): - logger.warning('Triangles with empty region information found!') - logger.warning('Note: These will be indicated by a -1 in the output file.') - n_warnings += 1 - mesh.cells.append(CellBlock('triangle', np.array(new_tris))) - mesh.cell_data['gmsh:geometrical'].append(np.array(tri_region)) - mesh.cell_data['gmsh:physical'].append(np.array(tri_nodeset)) - - # Add new quads - if new_quads: - logger.info(' Adding %i new quads...' % (len(new_quads))) - if (-1 in quad_region): - logger.warning('Quads with empty region information found!') - logger.warning('Note: These will be indicated by a -1 in the output file.') - n_warnings += 1 - mesh.cells.append(CellBlock('quad', np.array(new_quads))) - mesh.cell_data['gmsh:geometrical'].append(np.array(quad_region)) - mesh.cell_data['gmsh:physical'].append(np.array(quad_nodeset)) - - # Write the final mesh - logger.info('Writing gmsh mesh...') - meshio.write(output_mesh, mesh, file_format="gmsh22", binary=False) - logger.info('Done!') - - return (n_warnings > 0) - - -def convert_abaqus_to_vtu(input_mesh: str, output_mesh: str, logger: logging.Logger = None) -> int: - """ - Convert an abaqus mesh to vtu format, preserving nodeset information. - - If the code encounters any issues with region/element indices, the conversion will - attempt to continue, with errors indicated by -1 values in the output file. - - Args: - input_mesh (str): path of the input abaqus file - output_mesh (str): path of the output vtu file - logger (logging.Logger): a logger instance - - Returns: - int: Number of potential warnings encountered during conversion - """ - # Initialize the logger if it is empty - if not logger: - logging.basicConfig(level=logging.WARNING) - logger = logging.getLogger(__name__) - - # Keep track of the number of warnings - n_warnings = 0 - - # Load the mesh - logger.info('Reading abaqus mesh...') - mesh = meshio.read(input_mesh, file_format="abaqus") - - # Converting nodesets to binary masks - for k, nodeset in mesh.point_sets.items(): - mesh.point_data[k] = np.zeros(len(mesh.points), dtype=int) - mesh.point_data[k][nodeset] = 1 - - # Overwrite point sets to suppress conversion warnings - mesh.point_sets = {} - - # Write the final mesh - logger.info('Writing vtu mesh...') - meshio.write(output_mesh, mesh, file_format="vtu") - logger.info('Done!') - - return (n_warnings > 0) diff --git a/src/coreComponents/python/modules/geosx_mesh_tools_package/geosx_mesh_tools/main.py b/src/coreComponents/python/modules/geosx_mesh_tools_package/geosx_mesh_tools/main.py deleted file mode 100644 index 1637d07fd8f..00000000000 --- a/src/coreComponents/python/modules/geosx_mesh_tools_package/geosx_mesh_tools/main.py +++ /dev/null @@ -1,51 +0,0 @@ -import argparse -import logging -import sys - - -def build_abaqus_converter_input_parser() -> argparse.ArgumentParser: - """Build the input argument parser - - Returns: - argparse.ArgumentParser: a parser instance - """ - parser = argparse.ArgumentParser() - parser.add_argument('input', type=str, help='Input abaqus mesh file name') - parser.add_argument('output', type=str, help='Output gmsh/vtu mesh file name') - parser.add_argument('-v', '--verbose', help='Increase verbosity level', action="store_true") - return parser - - -def main() -> None: - """ - Entry point for the abaqus convertor console script - - Args: - input (str): Input abaqus mesh file name - output (str): Output mesh file name - -v/--verbose (flag): Increase verbosity level - """ - from geosx_mesh_tools import abaqus_converter - - # Parse the user arguments - parser = build_abaqus_converter_input_parser() - args = parser.parse_args() - - # Set up a logger - logging.basicConfig(level=logging.WARNING) - logger = logging.getLogger(__name__) - if args.verbose: - logger.setLevel(logging.INFO) - - # Call the converter - err = 0 - if ('.msh' in args.output): - err = abaqus_converter.convert_abaqus_to_gmsh(args.input, args.output, logger) - else: - err = abaqus_converter.convert_abaqus_to_vtu(args.input, args.output, logger) - if err: - sys.exit('Warnings detected: check the output file for potential errors!') - - -if __name__ == '__main__': - main() diff --git a/src/coreComponents/python/modules/geosx_mesh_tools_package/geosx_mesh_tools/py.typed b/src/coreComponents/python/modules/geosx_mesh_tools_package/geosx_mesh_tools/py.typed deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/src/coreComponents/python/modules/geosx_mesh_tools_package/pyproject.toml b/src/coreComponents/python/modules/geosx_mesh_tools_package/pyproject.toml deleted file mode 100644 index c2f433afcb5..00000000000 --- a/src/coreComponents/python/modules/geosx_mesh_tools_package/pyproject.toml +++ /dev/null @@ -1,8 +0,0 @@ -[build-system] -requires = ["setuptools>=42", "wheel"] -build-backend = "setuptools.build_meta" - -[tool.mypy] -python_version = "3.8" -warn_return_any = true -warn_unused_configs = true diff --git a/src/coreComponents/python/modules/geosx_mesh_tools_package/setup.cfg b/src/coreComponents/python/modules/geosx_mesh_tools_package/setup.cfg deleted file mode 100644 index 06ca8c2c7df..00000000000 --- a/src/coreComponents/python/modules/geosx_mesh_tools_package/setup.cfg +++ /dev/null @@ -1,22 +0,0 @@ -[metadata] -name = geosx_mesh_tools -version = 0.2.0 -description = Tools for managing meshes in GEOSX -author = Christopher Sherman -author_email = sherman27@llnl.gov -license = LGPL-2.1 - -[options] -packages = - geosx_mesh_tools -install_requires = - meshio>=5.3.2 - numpy -python_requires = >=3.6 - -[options.package_data] -geosx_mesh_tools = py.typed - -[options.entry_points] -console_scripts = - convert_abaqus = geosx_mesh_tools.main:main diff --git a/src/coreComponents/python/modules/geosx_xml_tools_package/.gitignore b/src/coreComponents/python/modules/geosx_xml_tools_package/.gitignore deleted file mode 100644 index 59d52651e06..00000000000 --- a/src/coreComponents/python/modules/geosx_xml_tools_package/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -build -*.egg-info diff --git a/src/coreComponents/python/modules/geosx_xml_tools_package/geosx_xml_tools/__init__.py b/src/coreComponents/python/modules/geosx_xml_tools_package/geosx_xml_tools/__init__.py deleted file mode 100644 index c51fba5a2c9..00000000000 --- a/src/coreComponents/python/modules/geosx_xml_tools_package/geosx_xml_tools/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -""" -A python module that enables advanced xml features for GEOSX. -""" diff --git a/src/coreComponents/python/modules/geosx_xml_tools_package/geosx_xml_tools/attribute_coverage.py b/src/coreComponents/python/modules/geosx_xml_tools_package/geosx_xml_tools/attribute_coverage.py deleted file mode 100644 index 2b64df8c540..00000000000 --- a/src/coreComponents/python/modules/geosx_xml_tools_package/geosx_xml_tools/attribute_coverage.py +++ /dev/null @@ -1,187 +0,0 @@ -from lxml import etree as ElementTree # type: ignore[import] -import os -from pathlib import Path -from typing import Any, Iterable, Dict -from geosx_xml_tools import command_line_parsers - -record_type = Dict[str, Dict[str, Any]] - - -def parse_schema_element(root: ElementTree.Element, - node: ElementTree.Element, - xsd: str = '{http://www.w3.org/2001/XMLSchema}', - recursive_types: Iterable[str] = ['PeriodicEvent', 'SoloEvent', 'HaltEvent'], - folders: Iterable[str] = ['src', 'examples']) -> record_type: - """Parse the xml schema at the current level - - Args: - root (lxml.etree.Element): the root schema node - node (lxml.etree.Element): current schema node - xsd (str): the file namespace - recursive_types (list): node tags that allow recursive nesting - folders (list): folders to sort xml attribute usage into - - Returns: - dict: Dictionary of attributes and children for the current node - """ - - element_type = node.get('type') - element_name = node.get('name') - element_def = root.find("%scomplexType[@name='%s']" % (xsd, element_type)) - local_types: record_type = {'attributes': {}, 'children': {}} - - # Parse attributes - for attribute in element_def.findall('%sattribute' % (xsd)): - attribute_name = attribute.get('name') - local_types['attributes'][attribute_name] = {ka: [] for ka in folders} - if ('default' in attribute.attrib): - local_types['attributes'][attribute_name]['default'] = attribute.get('default') - - # Parse children - choice_node = element_def.findall('%schoice' % (xsd)) - if choice_node: - for child in choice_node[0].findall('%selement' % (xsd)): - child_name = child.get('name') - if not ((child_name in recursive_types) and (element_name in recursive_types)): - local_types['children'][child_name] = parse_schema_element(root, child) - - return local_types - - -def parse_schema(fname: str) -> record_type: - """Parse the schema file into the xml attribute usage dict - - Args: - fname (str): schema name - - Returns: - dict: Dictionary of attributes and children for the entire schema - """ - xml_tree = ElementTree.parse(fname) - xml_root = xml_tree.getroot() - problem_node = xml_root.find("{http://www.w3.org/2001/XMLSchema}element") - return {'Problem': parse_schema_element(xml_root, problem_node)} - - -def collect_xml_attributes_level(local_types: record_type, node: ElementTree.Element, folder: str) -> None: - """Collect xml attribute usage at the current level - - Args: - local_types (dict): dictionary containing attribute usage - node (lxml.etree.Element): current xml node - folder (str): the source folder for the current file - """ - for ka in node.attrib.keys(): - local_types['attributes'][ka][folder].append(node.get(ka)) - - for child in node: - if child.tag in local_types['children']: - collect_xml_attributes_level(local_types['children'][child.tag], child, folder) - - -def collect_xml_attributes(xml_types: record_type, fname: str, folder: str) -> None: - """Collect xml attribute usage in a file - - Args: - xml_types (dict): dictionary containing attribute usage - fname (str): name of the target file - folder (str): the source folder for the current file - """ - parser = ElementTree.XMLParser(remove_comments=True, remove_blank_text=True) - xml_tree = ElementTree.parse(fname, parser=parser) - xml_root = xml_tree.getroot() - - collect_xml_attributes_level(xml_types['Problem'], xml_root, folder) - - -def write_attribute_usage_xml_level(local_types: record_type, - node: ElementTree.Element, - folders: Iterable[str] = ['src', 'examples']) -> None: - """Write xml attribute usage file at a given level - - Args: - local_types (dict): dict containing attribute usage at the current level - node (lxml.etree.Element): current xml node - """ - - # Write attributes - for ka in local_types['attributes'].keys(): - attribute_node = ElementTree.Element(ka) - node.append(attribute_node) - - if ('default' in local_types['attributes'][ka]): - attribute_node.set('default', local_types['attributes'][ka]['default']) - - unique_values = [] - for f in folders: - sub_values = list(set(local_types['attributes'][ka][f])) - unique_values.extend(sub_values) - attribute_node.set(f, ' | '.join(sub_values)) - - unique_length = len(set(unique_values)) - attribute_node.set('unique_values', str(unique_length)) - - # Write children - for ka in sorted(local_types['children']): - child = ElementTree.Element(ka) - node.append(child) - write_attribute_usage_xml_level(local_types['children'][ka], child) - - -def write_attribute_usage_xml(xml_types: record_type, fname: str) -> None: - """Write xml attribute usage file - - Args: - xml_types (dict): dictionary containing attribute usage by xml type - fname (str): output file name - """ - xml_root = ElementTree.Element('Problem') - xml_tree = ElementTree.ElementTree(xml_root) - - write_attribute_usage_xml_level(xml_types['Problem'], xml_root) - xml_tree.write(fname, pretty_print=True) - - -def process_xml_files(geosx_root: str, output_name: str) -> None: - """Test for xml attribute usage - - Args: - geosx_root (str): GEOSX root directory - output_name (str): output file name - """ - - # Parse the schema - geosx_root = os.path.expanduser(geosx_root) - schema = '%ssrc/coreComponents/schema/schema.xsd' % (geosx_root) - xml_types = parse_schema(schema) - - # Find all xml files, collect their attributes - for folder in ['src', 'examples']: - print(folder) - xml_files = Path(os.path.join(geosx_root, folder)).rglob('*.xml') - for f in xml_files: - print(' %s' % (str(f))) - collect_xml_attributes(xml_types, str(f), folder) - - # Consolidate attributes - write_attribute_usage_xml(xml_types, output_name) - - -def main() -> None: - """Entry point for the xml attribute usage test script - - Args: - -r/--root (str): GEOSX root directory - -o/--output (str): output file name - """ - - # Parse the user arguments - parser = command_line_parsers.build_attribute_coverage_input_parser() - args = parser.parse_args() - - # Parse the xml files - process_xml_files(args.root, args.output) - - -if __name__ == "__main__": - main() diff --git a/src/coreComponents/python/modules/geosx_xml_tools_package/geosx_xml_tools/command_line_parsers.py b/src/coreComponents/python/modules/geosx_xml_tools_package/geosx_xml_tools/command_line_parsers.py deleted file mode 100644 index 4c07d11f4e5..00000000000 --- a/src/coreComponents/python/modules/geosx_xml_tools_package/geosx_xml_tools/command_line_parsers.py +++ /dev/null @@ -1,88 +0,0 @@ -import argparse -from typing import Tuple, Iterable - - -def build_preprocessor_input_parser() -> argparse.ArgumentParser: - """Build the argument parser - - Returns: - argparse.ArgumentParser: The parser - """ - # Parse the user arguments - parser = argparse.ArgumentParser() - parser.add_argument('-i', '--input', type=str, action='append', help='Input file name (multiple allowed)') - parser.add_argument('-c', - '--compiled-name', - type=str, - help='Compiled xml file name (otherwise, it is randomly genrated)', - default='') - parser.add_argument('-s', '--schema', type=str, help='GEOSX schema to use for validation', default='') - parser.add_argument('-v', '--verbose', type=int, help='Verbosity of outputs', default=0) - parser.add_argument('-p', - '--parameters', - nargs='+', - action='append', - help='Parameter overrides (name value, multiple allowed)', - default=[]) - - return parser - - -def parse_xml_preprocessor_arguments() -> Tuple[argparse.Namespace, Iterable[str]]: - """Parse user arguments - - Args: - -i/--input (str): Input file name (multiple allowed) - -c/--compiled-name (str): Compiled xml file name - -s/--schema (str): Path to schema to use for validation - -v/--verbose (int): Verbosity of outputs - -p/--parameters (str): Parameter overrides (name and value, multiple allowed) - - Returns: - list: The remaining unparsed argument strings - """ - parser = build_preprocessor_input_parser() - return parser.parse_known_args() - - -def build_xml_formatter_input_parser() -> argparse.ArgumentParser: - """Build the argument parser - - Returns: - argparse.ArgumentParser: the parser instance - """ - - parser = argparse.ArgumentParser() - parser.add_argument('input', type=str, help='Input file name') - parser.add_argument('-i', '--indent', type=int, help='Indent size', default=2) - parser.add_argument('-s', '--style', type=int, help='Indent style', default=0) - parser.add_argument('-d', '--depth', type=int, help='Block separation depth', default=2) - parser.add_argument('-a', '--alphebitize', type=int, help='Alphebetize attributes', default=0) - parser.add_argument('-c', '--close', type=int, help='Close tag style', default=0) - parser.add_argument('-n', '--namespace', type=int, help='Include namespace', default=0) - return parser - - -def build_attribute_coverage_input_parser() -> argparse.ArgumentParser: - """Build attribute coverage redundancy input parser - - Returns: - argparse.ArgumentParser: parser instance - """ - - parser = argparse.ArgumentParser() - parser.add_argument('-r', '--root', type=str, help='GEOSX root', default='') - parser.add_argument('-o', '--output', type=str, help='Output file name', default='attribute_test.xml') - return parser - - -def build_xml_redundancy_input_parser() -> argparse.ArgumentParser: - """Build xml redundancy input parser - - Returns: - argparse.ArgumentParser: parser instance - """ - - parser = argparse.ArgumentParser() - parser.add_argument('-r', '--root', type=str, help='GEOSX root', default='') - return parser diff --git a/src/coreComponents/python/modules/geosx_xml_tools_package/geosx_xml_tools/main.py b/src/coreComponents/python/modules/geosx_xml_tools_package/geosx_xml_tools/main.py deleted file mode 100644 index b5110288f93..00000000000 --- a/src/coreComponents/python/modules/geosx_xml_tools_package/geosx_xml_tools/main.py +++ /dev/null @@ -1,163 +0,0 @@ -"""Command line tools for geosx_xml_tools""" - -import sys -import argparse -import os -import time -from geosx_xml_tools import xml_processor, command_line_parsers -from typing import Callable, Any, Union, Tuple, Iterable - - -def check_mpi_rank() -> int: - """Check the MPI rank - - Returns: - int: MPI rank - """ - rank = 0 - mpi_rank_key_options = ['OMPI_COMM_WORLD_RANK', 'PMI_RANK'] - for k in mpi_rank_key_options: - if k in os.environ: - rank = int(os.environ[k]) - return rank - - -TFunc = Callable[..., Any] - - -def wait_for_file_write_rank_0(target_file_argument: Union[int, str] = 0, - max_wait_time: float = 100, - max_startup_delay: float = 1) -> Callable[[TFunc], TFunc]: - """Constructor for a function decorator that waits for a target file to be written on rank 0 - - Args: - target_file_argument (int, str): Index or keyword of the filename argument in the decorated function - max_wait_time (float): Maximum amount of time to wait (seconds) - max_startup_delay (float): Maximum delay allowed for thread startup (seconds) - - Returns: - Wrapped function - """ - - def wait_for_file_write_rank_0_inner(writer: TFunc) -> TFunc: - """Intermediate constructor for the function decorator - - Args: - writer (typing.Callable): A function that writes to a file - """ - - def wait_for_file_write_rank_0_decorator(*args, **kwargs) -> Any: - """Apply the writer on rank 0, and wait for completion on other ranks - """ - # Check the target file status - rank = check_mpi_rank() - fname = '' - if isinstance(target_file_argument, int): - fname = args[target_file_argument] - else: - fname = kwargs[target_file_argument] - - target_file_exists = os.path.isfile(fname) - target_file_edit_time = 0.0 - if target_file_exists: - target_file_edit_time = os.path.getmtime(fname) - - # Variations in thread startup times may mean the file has already been processed - # If the last edit was done within the specified time, then allow the thread to proceed - if (abs(target_file_edit_time - time.time()) < max_startup_delay): - target_file_edit_time = 0.0 - - # Go into the target process or wait for the expected file update - if (rank == 0): - return writer(*args, **kwargs) - else: - ta = time.time() - while (time.time() - ta < max_wait_time): - if target_file_exists: - if (os.path.getmtime(fname) > target_file_edit_time): - break - else: - if os.path.isfile(fname): - break - time.sleep(0.1) - - return wait_for_file_write_rank_0_decorator - - return wait_for_file_write_rank_0_inner - - -def preprocess_serial() -> None: - """ - Entry point for the geosx_xml_tools console script - """ - # Process the xml file - args, unknown_args = command_line_parsers.parse_xml_preprocessor_arguments() - - # Attempt to only process the file on rank 0 - # Note: The rank here is determined by inspecting the system environment variables - # While this is not the preferred way of doing so, it avoids mpi environment errors - # If the rank detection fails, then it will preprocess the file on all ranks, which - # sometimes cause a (seemingly harmless) file write conflict. - # processor = xml_processor.process - processor = wait_for_file_write_rank_0(target_file_argument='outputFile', max_wait_time=100)(xml_processor.process) - - compiled_name = processor(args.input, - outputFile=args.compiled_name, - schema=args.schema, - verbose=args.verbose, - parameter_override=args.parameters) - if not compiled_name: - if args.compiled_name: - compiled_name = args.compiled_name - else: - raise Exception( - 'When applying the preprocessor in parallel (outside of pygeosx), the --compiled_name argument is required' - ) - - # Note: the return value may be passed to sys.exit, and cause bash to report an error - # return format_geosx_arguments(compiled_name, unknown_args) - print(compiled_name) - - -def preprocess_parallel() -> Iterable[str]: - """ - MPI aware xml preprocesing - """ - # Process the xml file - from mpi4py import MPI # type: ignore[import] - comm = MPI.COMM_WORLD - rank = comm.Get_rank() - - args, unknown_args = command_line_parsers.parse_xml_preprocessor_arguments() - compiled_name = '' - if (rank == 0): - compiled_name = xml_processor.process(args.input, - outputFile=args.compiled_name, - schema=args.schema, - verbose=args.verbose, - parameter_override=args.parameters) - compiled_name = comm.bcast(compiled_name, root=0) - return format_geosx_arguments(compiled_name, unknown_args) - - -def format_geosx_arguments(compiled_name: str, unknown_args: Iterable[str]) -> Iterable[str]: - """Format GEOSX arguments - - Args: - compiled_name (str): Name of the compiled xml file - unknown_args (list): List of unprocessed arguments - - Returns: - list: List of arguments to pass to GEOSX - """ - geosx_args = [sys.argv[0], '-i', compiled_name] - if unknown_args: - geosx_args.extend(unknown_args) - - # Print the output name for use in bash scripts - print(compiled_name) - return geosx_args - - -if __name__ == "__main__": - preprocess_serial() diff --git a/src/coreComponents/python/modules/geosx_xml_tools_package/geosx_xml_tools/py.typed b/src/coreComponents/python/modules/geosx_xml_tools_package/geosx_xml_tools/py.typed deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/src/coreComponents/python/modules/geosx_xml_tools_package/geosx_xml_tools/regex_tools.py b/src/coreComponents/python/modules/geosx_xml_tools_package/geosx_xml_tools/regex_tools.py deleted file mode 100644 index ded5c1a8783..00000000000 --- a/src/coreComponents/python/modules/geosx_xml_tools_package/geosx_xml_tools/regex_tools.py +++ /dev/null @@ -1,78 +0,0 @@ -"""Tools for managing regular expressions in geosx_xml_tools""" - -import re -from typing import Union, Dict -""" -Define regex patterns used throughout the module: - -Pattern | Example targets | Notes ------------------------------------------------------------------------------------- -parameters | $Parameter, $Parameter$ | Matches entire parameter string -units | 9.81[m**2/s], 1.0 [bbl/day] | Matches entire unit string -units_b | m, bbl, day | Matches unit names -symbolic | `1 + 2.34e5*2` | Matches the entire symbolic string -sanitize | | Removes any residual characters before - | | evaluating symbolic expressions -strip_trailing | 3.0000, 5.150050 | Removes unnecessary float strings -strip_trailing_b| 3.0000e0, 1.23e0 | Removes unnecessary float strings -""" - -patterns: Dict[str, str] = { - 'parameters': r"\$:?([a-zA-Z_0-9]*)\$?", - 'units': r"([0-9]*?\.?[0-9]+(?:[eE][-+]?[0-9]*?)?)\ *?\[([-+.*/()a-zA-Z0-9]*)\]", - 'units_b': r"([a-zA-Z]*)", - 'symbolic': r"\`([-+.*/() 0-9eE]*)\`", - 'sanitize': r"[a-z-[e]A-Z-[E]]", - 'strip_trailing': r"\.?0+(?=e)", - 'strip_trailing_b': r"e\+00|\+0?|(?<=-)0" -} - -# String formatting for symbolic expressions -symbolic_format = '%1.6e' - - -def SymbolicMathRegexHandler(match: re.Match) -> str: - """Evaluate symbolic expressions that are identified using the regex_tools.patterns['symbolic']. - - Args: - match (re.match): A matching string identified by the regex. - """ - k = match.group(1) - if k: - # Sanitize the input - sanitized = re.sub(patterns['sanitize'], '', k).strip() - value = eval(sanitized, {'__builtins__': None}) - - # Format the string, removing any trailing zeros, decimals, etc. - str_value = re.sub(patterns['strip_trailing'], '', symbolic_format % (value)) - str_value = re.sub(patterns['strip_trailing_b'], '', str_value) - return str_value - else: - return '' - - -class DictRegexHandler(): - """This class is used to substitute matched values with those stored in a dict.""" - - def __init__(self) -> None: - """Initialize the handler with an empty target list. - The key/value pairs of self.target indicate which values - to look for and the values they will replace with. - """ - self.target: Dict[str, str] = {} - - def __call__(self, match: re.Match) -> str: - """Replace the matching strings with their target. - - Args: - match (re.match): A matching string identified by the regex. - """ - - k = match.group(1) - if k: - if (k not in self.target.keys()): - raise Exception('Error: Target (%s) is not defined in the regex handler' % k) - value = self.target[k] - return str(value) - else: - return '' diff --git a/src/coreComponents/python/modules/geosx_xml_tools_package/geosx_xml_tools/table_generator.py b/src/coreComponents/python/modules/geosx_xml_tools_package/geosx_xml_tools/table_generator.py deleted file mode 100644 index bb4a63c0959..00000000000 --- a/src/coreComponents/python/modules/geosx_xml_tools_package/geosx_xml_tools/table_generator.py +++ /dev/null @@ -1,75 +0,0 @@ -"""Tools for reading/writing GEOSX ascii tables""" - -import numpy as np -from typing import Tuple, Iterable, Dict - - -def write_GEOS_table(axes_values: Iterable[np.ndarray], - properties: Dict[str, np.ndarray], - axes_names: Iterable[str] = ['x', 'y', 'z', 't'], - string_format: str = '%1.5e') -> None: - """Write an GEOS-compatible ascii table. - - Args: - axes_values (list): List of arrays containing the coordinates for each axis of the table. - properties (dict): Dict of arrays with dimensionality/size defined by the axes_values - axes_names (list): Names for each axis (default = ['x', 'y', 'z', 't']) - string_format (str): Format for output values (default = %1.5e) - """ - - # Check to make sure the axes/property files have the correct shape - axes_shape = tuple([len(x) for x in axes_values]) - for k in properties.keys(): - if (np.shape(properties[k]) != axes_shape): - raise Exception("Shape of parameter %s is incompatible with given axes" % (k)) - - # Write axes files - for ka, x in zip(axes_names, axes_values): - np.savetxt('%s.geos' % (ka), x, fmt=string_format, delimiter=',') - - # Write property files - for k in properties.keys(): - tmp = np.reshape(properties[k], (-1), order='F') - np.savetxt('%s.geos' % (k), tmp, fmt=string_format, delimiter=',') - - -def read_GEOS_table(axes_files: Iterable[str], - property_files: Iterable[str]) -> Tuple[Iterable[np.ndarray], Dict[str, np.ndarray]]: - """Read an GEOS-compatible ascii table. - - Args: - axes_files (list): List of the axes file names in order. - property_files (list): List of property file names - - Returns: - tuple: List of axis definitions, dict of property values - """ - axes_values = [] - for f in axes_files: - axes_values.append(np.loadtxt('%s.geos' % (f), unpack=True, delimiter=',')) - axes_shape = tuple([len(x) for x in axes_values]) - - # Open property files - properties = {} - for f in property_files: - tmp = np.loadtxt('%s.geos' % (f), unpack=True, delimiter=',') - properties[f] = np.reshape(tmp, axes_shape, order='F') - - return axes_values, properties - - -def write_read_GEOS_table_example() -> None: - """Table read / write example.""" - - # Define table axes - a = np.array([0.0, 1.0]) - b = np.array([0.0, 0.5, 1.0]) - axes_values = [a, b] - - # Generate table values (note: the indexing argument is important) - A, B = np.meshgrid(a, b, indexing='ij') - properties = {'c': A + 2.0 * B} - - # Write, then read tables - write_GEOS_table(axes_values, properties, axes_names=['a', 'b']) - axes_b, properties_b = read_GEOS_table(['a', 'b'], ['c']) diff --git a/src/coreComponents/python/modules/geosx_xml_tools_package/geosx_xml_tools/tests/__init__.py b/src/coreComponents/python/modules/geosx_xml_tools_package/geosx_xml_tools/tests/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/src/coreComponents/python/modules/geosx_xml_tools_package/geosx_xml_tools/tests/generate_test_xml.py b/src/coreComponents/python/modules/geosx_xml_tools_package/geosx_xml_tools/tests/generate_test_xml.py deleted file mode 100644 index 3c1b7714d56..00000000000 --- a/src/coreComponents/python/modules/geosx_xml_tools_package/geosx_xml_tools/tests/generate_test_xml.py +++ /dev/null @@ -1,374 +0,0 @@ -"""Tool for generating test xml files for processing.""" - -from lxml import etree as ElementTree -import os -from geosx_xml_tools import xml_formatter - - -def generate_test_xml_files(root_dir): - """Build example input/output xml files, which can be used to test the parser. - These are derived from a GEOSX integrated test xml. - - @param root_dir The folder to write the example xml files. - """ - - # Build segments of an xml file that can be compiled to form a test - # File header/footer - xml_header = """""" - - xml_footer = """""" - - # Parameters - xml_parameters = """ - - - - - """ - - # Includes - xml_includes = """ - - - - -""" - - # Base segments - xml_base_a = """ - - - - - - - - - - - - - - - -""" - - xml_base_b = """ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -""" - - # Field specifications with parameters, symbolic math, and their compiled equivalents - field_string_with_parameters = """ - - - - - - - - - - - - - - - -""" - - field_string_with_symbolic = """ - - - - - - - - - - - - - - - -""" - - field_string_base = """ - - - - - - - - - - - - - - - -""" - - field_string_alt = """ - - - - - - - - - - - - - - - -""" - - # Write the files, and apply pretty_print to targets for easy matches - # No advanced features case - with open('%s/no_advanced_features_input.xml' % (root_dir), 'w') as f: - f.write(xml_header + xml_base_a + xml_base_b + field_string_base + xml_footer) - with open('%s/no_advanced_features_target.xml' % (root_dir), 'w') as f: - f.write(xml_header + xml_base_a + xml_base_b + field_string_base + xml_footer) - xml_formatter.format_file('%s/no_advanced_features_target.xml' % (root_dir)) - - # Parameters case - with open('%s/parameters_input.xml' % (root_dir), 'w') as f: - f.write(xml_header + xml_parameters + xml_base_a + xml_base_b + field_string_with_parameters + xml_footer) - with open('%s/parameters_target.xml' % (root_dir), 'w') as f: - f.write(xml_header + xml_base_a + xml_base_b + field_string_base + xml_footer) - xml_formatter.format_file('%s/parameters_target.xml' % (root_dir)) - - # Symbolic + parameters case - with open('%s/symbolic_parameters_input.xml' % (root_dir), 'w') as f: - f.write(xml_header + xml_parameters + xml_base_a + xml_base_b + field_string_with_symbolic + xml_footer) - with open('%s/symbolic_parameters_target.xml' % (root_dir), 'w') as f: - f.write(xml_header + xml_base_a + xml_base_b + field_string_alt + xml_footer) - xml_formatter.format_file('%s/symbolic_parameters_target.xml' % (root_dir)) - - # Included case - os.makedirs('%s/included' % (root_dir), exist_ok=True) - with open('%s/included_input.xml' % (root_dir), 'w') as f: - f.write(xml_header + xml_includes + xml_footer) - with open('%s/included/included_a.xml' % (root_dir), 'w') as f: - f.write(xml_header + xml_base_a + xml_footer) - with open('%s/included/included_b.xml' % (root_dir), 'w') as f: - f.write(xml_header + xml_base_b + xml_footer) - with open('%s/included/included_c.xml' % (root_dir), 'w') as f: - f.write(xml_header + field_string_base + xml_footer) - with open('%s/included_target.xml' % (root_dir), 'w') as f: - f.write(xml_header + xml_base_a + xml_base_b + field_string_base + xml_footer) - xml_formatter.format_file('%s/included_target.xml' % (root_dir)) diff --git a/src/coreComponents/python/modules/geosx_xml_tools_package/geosx_xml_tools/tests/test_manager.py b/src/coreComponents/python/modules/geosx_xml_tools_package/geosx_xml_tools/tests/test_manager.py deleted file mode 100644 index 54e60a7051d..00000000000 --- a/src/coreComponents/python/modules/geosx_xml_tools_package/geosx_xml_tools/tests/test_manager.py +++ /dev/null @@ -1,173 +0,0 @@ -import unittest -import re -import os -import filecmp -from geosx_xml_tools import regex_tools, unit_manager, xml_processor -from geosx_xml_tools.tests import generate_test_xml -import argparse -from parameterized import parameterized - -# Create an instance of the unit manager -unitManager = unit_manager.UnitManager() - - -# Test the unit manager definitions -class TestUnitManager(unittest.TestCase): - - @classmethod - def setUpClass(cls): - cls.tol = 1e-6 - - def test_unit_dict(self): - unitManager.buildUnits() - self.assertTrue(bool(unitManager.units)) - - # Scale value tests - @parameterized.expand([['meter', '2', 2.0], ['meter', '1.234', 1.234], ['meter', '1.234e1', 12.34], - ['meter', '1.234E1', 12.34], ['meter', '1.234e+1', 12.34], ['meter', '1.234e-1', 0.1234], - ['mumeter', '1', 1.0e-6], ['micrometer', '1', 1.0e-6], ['kilometer', '1', 1.0e3], - ['ms', '1', 1.0e-3], ['millisecond', '1', 1.0e-3], ['Ms', '1', 1.0e6], ['m/s', '1', 1.0], - ['micrometer/s', '1', 1.0e-6], ['micrometer/ms', '1', 1.0e-3], - ['micrometer/microsecond', '1', 1.0], ['m**2', '1', 1.0], ['km**2', '1', 1.0e6], - ['kilometer**2', '1', 1.0e6], ['(km*mm)', '1', 1.0], ['(km*mm)**2', '1', 1.0], - ['km^2', '1', 1.0e6, True], ['bbl/day', '1', 0.000001840130728333], ['cP', '1', 0.001]]) - def test_units(self, unit, scale, expected_value, expect_fail=False): - try: - val = float(unitManager([scale, unit])) - self.assertTrue((abs(val - expected_value) < self.tol) != expect_fail) - except TypeError: - self.assertTrue(expect_fail) - - -# Test the behavior of the parameter regex -class TestParameterRegex(unittest.TestCase): - - @classmethod - def setUpClass(cls): - cls.regexHandler = regex_tools.DictRegexHandler() - cls.regexHandler.target['foo'] = '1.23' - cls.regexHandler.target['bar'] = '4.56e7' - - @parameterized.expand([['$:foo*1.234', '1.23*1.234'], ['$:foo*1.234/$:bar', '1.23*1.234/4.56e7'], - ['$:foo*1.234/($:bar*$:foo)', '1.23*1.234/(4.56e7*1.23)'], - ['$foo*1.234/$bar', '1.23*1.234/4.56e7'], ['$foo$*1.234/$bar', '1.23*1.234/4.56e7'], - ['$foo$*1.234/$bar$', '1.23*1.234/4.56e7'], - ['$blah$*1.234/$bar$', '1.23*1.234/4.56e7', True], - ['$foo$*1.234/$bar$', '4.56e7*1.234/4.56e7', True]]) - def test_parameter_regex(self, parameterInput, expectedValue, expect_fail=False): - try: - result = re.sub(regex_tools.patterns['parameters'], self.regexHandler, parameterInput) - self.assertTrue((result == expectedValue) != expect_fail) - except Exception: - self.assertTrue(expect_fail) - - -# Test the behavior of the unit regex -class TestUnitsRegex(unittest.TestCase): - - @classmethod - def setUpClass(cls): - cls.tol = 1e-6 - - @parameterized.expand([['1.234[m**2/s]', '1.234'], ['1.234 [m**2/s]', '1.234'], ['1.234[m**2/s]*3.4', '1.234*3.4'], - ['1.234[m**2/s] + 5.678[mm/s]', '1.234 + 5.678e-3'], - ['1.234 [m**2/s] + 5.678 [mm/s]', '1.234 + 5.678e-3'], - ['(1.234[m**2/s])*5.678', '(1.234)*5.678']]) - def test_units_regex(self, unitInput, expectedValue, expect_fail=False): - try: - result = re.sub(regex_tools.patterns['units'], unitManager.regexHandler, unitInput) - self.assertTrue((result == expectedValue) != expect_fail) - except Exception: - self.assertTrue(expect_fail) - - -# Test the symbolic math regex -class TestSymbolicRegex(unittest.TestCase): - - @classmethod - def setUpClass(cls): - cls.tol = 1e-6 - - @parameterized.expand([['`1.234`', '1.234'], ['`1.234*2.0`', '2.468'], ['`10`', '1e1'], ['`10*2`', '2e1'], - ['`1.0/2.0`', '5e-1'], ['`2.0**2`', '4'], ['`1.0 + 2.0**2`', '5'], ['`(1.0 + 2.0)**2`', '9'], - ['`((1.0 + 2.0)**2)**(0.5)`', '3'], ['`(1.2e3)*2`', '2.4e3'], ['`1.2e3*2`', '2.4e3'], - ['`2.0^2`', '4', True], ['`sqrt(4.0)`', '2', True]]) - def test_symbolic_regex(self, symbolicInput, expectedValue, expect_fail=False): - try: - result = re.sub(regex_tools.patterns['symbolic'], regex_tools.SymbolicMathRegexHandler, symbolicInput) - self.assertTrue((result == expectedValue) != expect_fail) - except Exception: - self.assertTrue(expect_fail) - - -# Test the complete xml processor -class TestXMLProcessor(unittest.TestCase): - - @classmethod - def setUpClass(cls): - generate_test_xml.generate_test_xml_files('.') - - @parameterized.expand([['no_advanced_features_input.xml', 'no_advanced_features_target.xml'], - ['parameters_input.xml', 'parameters_target.xml'], - ['included_input.xml', 'included_target.xml'], - ['symbolic_parameters_input.xml', 'symbolic_parameters_target.xml']]) - def test_xml_processor(self, input_file, target_file, expect_fail=False): - try: - tmp = xml_processor.process(input_file, - outputFile=input_file + '.processed', - verbose=0, - keep_parameters=False, - keep_includes=False) - self.assertTrue(filecmp.cmp(tmp, target_file) != expect_fail) - except Exception: - self.assertTrue(expect_fail) - - -# Main entry point for the unit tests -def run_unit_tests(test_dir, verbose): - # Create and move to the test directory - pwd = os.getcwd() - os.makedirs(test_dir, exist_ok=True) - os.chdir(test_dir) - - # Unit manager tests - suite = unittest.TestLoader().loadTestsFromTestCase(TestUnitManager) - unittest.TextTestRunner(verbosity=verbose).run(suite) - - # Parameter regex handler tests - suite = unittest.TestLoader().loadTestsFromTestCase(TestParameterRegex) - unittest.TextTestRunner(verbosity=verbose).run(suite) - - # Regex handler tests - suite = unittest.TestLoader().loadTestsFromTestCase(TestUnitsRegex) - unittest.TextTestRunner(verbosity=verbose).run(suite) - - # Symbolic regex handler tests - suite = unittest.TestLoader().loadTestsFromTestCase(TestSymbolicRegex) - unittest.TextTestRunner(verbosity=verbose).run(suite) - - # xml processor tests - suite = unittest.TestLoader().loadTestsFromTestCase(TestXMLProcessor) - unittest.TextTestRunner(verbosity=verbose).run(suite) - - os.chdir(pwd) - - -def main(): - """Entry point for the geosx_xml_tools unit tests - - @arg -o/--output Output directory (default = ./test_results) - """ - - # Parse the user arguments - parser = argparse.ArgumentParser() - parser.add_argument('-t', '--test_dir', type=str, help='Test output directory', default='./test_results') - parser.add_argument('-v', '--verbose', type=int, help='Verbosity level', default=2) - args = parser.parse_args() - - # Process the xml file - run_unit_tests(args.test_dir, args.verbose) - - -if __name__ == "__main__": - main() diff --git a/src/coreComponents/python/modules/geosx_xml_tools_package/geosx_xml_tools/unit_manager.py b/src/coreComponents/python/modules/geosx_xml_tools_package/geosx_xml_tools/unit_manager.py deleted file mode 100644 index 44360b07939..00000000000 --- a/src/coreComponents/python/modules/geosx_xml_tools_package/geosx_xml_tools/unit_manager.py +++ /dev/null @@ -1,151 +0,0 @@ -"""Tools for managing units in GEOSX""" - -import re -from geosx_xml_tools import regex_tools -from typing import List, Any, Dict, Union - - -class UnitManager(): - """This class is used to manage unit definitions.""" - - def __init__(self) -> None: - """Initialize the class by creating an instance of the dict regex handler, building units.""" - self.units: Dict[str, str] = {} - self.unitMatcher = regex_tools.DictRegexHandler() - self.buildUnits() - - def __call__(self, unitStruct: List[Any]) -> str: - """Evaluate the symbolic expression for matched strings. - - Args: - unitStruct (list): A list containing the variable scale and the unit definition. - - Returns: - str: The string with evaluated unit definitions - """ - - # Replace all instances of units in the string with their scale defined in self.units - symbolicUnits = re.sub(regex_tools.patterns['units_b'], self.unitMatcher, unitStruct[1]) - - # Strip out any undesired characters and evaluate - # Note: the only allowed alpha characters are e and E. This could be relaxed to allow - # functions such as sin, cos, etc. - symbolicUnits_sanitized = re.sub(regex_tools.patterns['sanitize'], '', symbolicUnits).strip() - value = float(unitStruct[0]) * eval(symbolicUnits_sanitized, {'__builtins__': None}) - - # Format the string, removing any trailing zeros, decimals, extraneous exponential formats - str_value = re.sub(regex_tools.patterns['strip_trailing'], '', regex_tools.symbolic_format % (value)) - str_value = re.sub(regex_tools.patterns['strip_trailing_b'], '', str_value) - return str_value - - def regexHandler(self, match: re.Match) -> str: - """Split the matched string into a scale and unit definition. - - Args: - match (re.match): The matching string from the regex. - - Returns: - str: The string with evaluated unit definitions - """ - # The first matched group includes the scale of the value (e.g. 1.234) - # The second matches the string inside the unit definition (e.g. m/s**2) - return self.__call__([match.group(1), match.group(2)]) - - def buildUnits(self) -> None: - """Build the unit definitions.""" - - # yapf: disable - # Long, short names for SI prefixes - unit_dict_type = Dict[str, Dict[str, Any]] - - prefixes: unit_dict_type = { - 'giga': {'value': 1e9, 'alt': 'G'}, - 'mega': {'value': 1e6, 'alt': 'M'}, - 'kilo': {'value': 1e3, 'alt': 'k'}, - 'hecto': {'value': 1e2, 'alt': 'H'}, - 'deca': {'value': 1e1, 'alt': 'D'}, - '': {'value': 1.0, 'alt': ''}, - 'deci': {'value': 1e-1, 'alt': 'd'}, - 'centi': {'value': 1e-2, 'alt': 'c'}, - 'milli': {'value': 1e-3, 'alt': 'm'}, - 'micro': {'value': 1e-6, 'alt': 'mu'}, - 'nano': {'value': 1e-9, 'alt': 'n'} - } - - # Base units, and their abbreviations - # Note: setting (usePrefix = True) instructs the manager to expand using SI prefixes - unit_defs: unit_dict_type = { - 'gram': {'value': 1e-3, 'alt': ['g', 'grams'], 'usePrefix': True}, - 'meter': {'value': 1.0, 'alt': ['m', 'meters'], 'usePrefix': True}, - 'second': {'value': 1.0, 'alt': ['s', 'seconds'], 'usePrefix': True}, - 'minute': {'value': 60.0, 'alt': ['min', 'minutes'], 'usePrefix': True}, - 'hour': {'value': 3600.0, 'alt': ['hr', 'hours', 'hrs'], 'usePrefix': True}, - 'day': {'value': 3600.0*24.0, 'alt': ['d', 'dy'], 'usePrefix': True}, - 'year': {'value': 3600.0*24.0*365.25, 'alt': ['yr', 'years'], 'usePrefix': True}, - 'pascal': {'value': 1.0, 'alt': ['Pa'], 'usePrefix': True}, - 'newton': {'value': 1.0, 'alt': ['N'], 'usePrefix': True}, - 'joule': {'value': 1.0, 'alt': ['J'], 'usePrefix': True}, - 'watt': {'value': 1.0, 'alt': ['W'], 'usePrefix': True} - } - - # Imperial units, and their abbreviations - imp_defs: unit_dict_type = { - 'pound': {'value': 0.453592, 'alt': ['lb', 'pounds', 'lbs'], 'usePrefix': True}, - 'poundforce': {'value': 0.453592*9.81, 'alt': ['lbf'], 'usePrefix': True}, - 'stone': {'value': 6.35029, 'alt': ['st'], 'usePrefix': True}, - 'ton': {'value': 907.185, 'alt': ['tons'], 'usePrefix': True}, - 'inch': {'value': 1.0/(3.281*12), 'alt': ['in', 'inches'], 'usePrefix': False}, - 'foot': {'value': 1.0/3.281, 'alt': ['ft', 'feet'], 'usePrefix': True}, - 'yard': {'value': 3.0/3.281, 'alt': ['yd', 'yards'], 'usePrefix': True}, - 'rod': {'value': 16.5/3.281, 'alt': ['rd', 'rods'], 'usePrefix': True}, - 'mile': {'value': 5280.0/3.281, 'alt': ['mi', 'miles'], 'usePrefix': True}, - 'acre': {'value': 4046.86, 'alt': ['acres'], 'usePrefix': True}, - 'gallon': {'value': 0.00378541, 'alt': ['gal', 'gallons'], 'usePrefix': True}, - 'psi': {'value': 6894.76, 'alt': [], 'usePrefix': True}, - 'psf': {'value': 1853.184, 'alt': [], 'usePrefix': True} - } - - # Other commonly used units: - other_defs: unit_dict_type = { - 'dyne': {'value': 1.0e-5, 'alt': ['dynes'], 'usePrefix': True}, - 'bar': {'value': 1.0e5, 'alt': ['bars'], 'usePrefix': True}, - 'atmosphere': {'value': 101325.0, 'alt': ['atm', 'atmospheres'], 'usePrefix': True}, - 'poise': {'value': 0.1, 'alt': ['P'], 'usePrefix': True}, - 'barrel': {'value': 0.1589873, 'alt': ['bbl', 'barrels'], 'usePrefix': True}, - 'horsepower': {'value': 745.7, 'alt': ['hp', 'horsepowers'], 'usePrefix': True} - } - # yapf: enable - - # Combine the unit dicts - unit_defs.update(imp_defs) - unit_defs.update(other_defs) - - # Use brute-force to generate a list of potential units, rather than trying to parse - # unit strings on the fly. This is still quite fast, and allows us to do simple - # checks for overlapping definitions - - # Expand prefix and alternate names - for p in list(prefixes.keys()): - if prefixes[p]['alt']: - prefixes[prefixes[p]['alt']] = {'value': prefixes[p]['value']} - for u in list(unit_defs.keys()): - for alt in unit_defs[u]['alt']: - unit_defs[alt] = {'value': unit_defs[u]['value'], 'usePrefix': unit_defs[u]['usePrefix']} - - # Combine the results into the final dictionary - for u in unit_defs.keys(): - if (unit_defs[u]['usePrefix']): - for p in prefixes.keys(): - self.units[p + u] = prefixes[p]['value'] * unit_defs[u]['value'] - else: - self.units[u] = unit_defs[u]['value'] - - # Test to make sure that there are no overlapping unit definitions - from collections import Counter - tmp = list(self.units.keys()) - duplicates = [k for k, v in Counter(tmp).items() if v > 1] - if (duplicates): - print(duplicates) - raise Exception('Error: There are overlapping unit definitions in the UnitManager') - - self.unitMatcher.target = self.units diff --git a/src/coreComponents/python/modules/geosx_xml_tools_package/geosx_xml_tools/xml_formatter.py b/src/coreComponents/python/modules/geosx_xml_tools_package/geosx_xml_tools/xml_formatter.py deleted file mode 100644 index eb745abcbcb..00000000000 --- a/src/coreComponents/python/modules/geosx_xml_tools_package/geosx_xml_tools/xml_formatter.py +++ /dev/null @@ -1,205 +0,0 @@ -import os -from lxml import etree as ElementTree # type: ignore[import] -import re -from typing import List, Any, TextIO -from geosx_xml_tools import command_line_parsers - - -def format_attribute(attribute_indent: str, ka: str, attribute_value: str) -> str: - """Format xml attribute strings - - Args: - attribute_indent (str): Attribute indent string - ka (str): Attribute name - attribute_value (str): Attribute value - - Returns: - str: Formatted attribute value - """ - # Make sure that a space follows commas - attribute_value = re.sub(r",\s*", ", ", attribute_value) - - # Handle external brackets - attribute_value = re.sub(r"{\s*", "{ ", attribute_value) - attribute_value = re.sub(r"\s*}", " }", attribute_value) - - # Consolidate whitespace - attribute_value = re.sub(r"\s+", " ", attribute_value) - - # Identify and split multi-line attributes - if re.match(r"\s*{\s*({[-+.,0-9a-zA-Z\s]*},?\s*)*\s*}", attribute_value): - split_positions: List[Any] = [match.end() for match in re.finditer(r"}\s*,", attribute_value)] - newline_indent = '\n%s' % (' ' * (len(attribute_indent) + len(ka) + 4)) - new_values = [] - for a, b in zip([None] + split_positions, split_positions + [None]): - new_values.append(attribute_value[a:b].strip()) - if new_values: - attribute_value = newline_indent.join(new_values) - - return attribute_value - - -def format_xml_level(output: TextIO, - node: ElementTree.Element, - level: int, - indent: str = ' ' * 2, - block_separation_max_depth: int = 2, - modify_attribute_indent: bool = False, - sort_attributes: bool = False, - close_tag_newline: bool = False, - include_namespace: bool = False) -> None: - """Iteratively format the xml file - - Args: - output (file): the output text file handle - node (lxml.etree.Element): the current xml element - level (int): the xml depth - indent (str): the xml indent style - block_separation_max_depth (int): the maximum depth to separate adjacent elements - modify_attribute_indent (bool): option to have flexible attribute indentation - sort_attributes (bool): option to sort attributes alphabetically - close_tag_newline (bool): option to place close tag on a separate line - include_namespace (bool): option to include the xml namespace in the output - """ - - # Handle comments - if node.tag is ElementTree.Comment: - output.write('\n%s' % (indent * level, node.text)) - - else: - # Write opening line - opening_line = '\n%s<%s' % (indent * level, node.tag) - output.write(opening_line) - - # Write attributes - if (len(node.attrib) > 0): - # Choose indentation - attribute_indent = '%s' % (indent * (level + 1)) - if modify_attribute_indent: - attribute_indent = ' ' * (len(opening_line)) - - # Get a copy of the attributes - attribute_dict = {} - if ((level == 0) & include_namespace): - # Handle the optional namespace information at the root level - # Note: preferably, this would point to a schema we host online - attribute_dict['xmlns:xsi'] = 'http://www.w3.org/2001/XMLSchema-instance' - attribute_dict['xsi:noNamespaceSchemaLocation'] = '/usr/gapps/GEOS/schema/schema.xsd' - elif (level > 0): - attribute_dict = node.attrib - - # Sort attribute names - akeys = list(attribute_dict.keys()) - if sort_attributes: - akeys = sorted(akeys) - - # Format attributes - for ka in akeys: - # Avoid formatting mathpresso expressions - if not (node.tag in ["SymbolicFunction", "CompositeFunction"] and ka == "expression"): - attribute_dict[ka] = format_attribute(attribute_indent, ka, attribute_dict[ka]) - - for ii in range(0, len(akeys)): - k = akeys[ii] - if ((ii == 0) & modify_attribute_indent): - output.write(' %s=\"%s\"' % (k, attribute_dict[k])) - else: - output.write('\n%s%s=\"%s\"' % (attribute_indent, k, attribute_dict[k])) - - # Write children - if len(node): - output.write('>') - Nc = len(node) - for ii, child in zip(range(Nc), node): - format_xml_level(output, child, level + 1, indent, block_separation_max_depth, modify_attribute_indent, - sort_attributes, close_tag_newline, include_namespace) - - # Add space between blocks - if ((level < block_separation_max_depth) & (ii < Nc - 1) & (child.tag is not ElementTree.Comment)): - output.write('\n') - - # Write the end tag - output.write('\n%s' % (indent * level, node.tag)) - else: - if close_tag_newline: - output.write('\n%s/>' % (indent * level)) - else: - output.write('/>') - - -def format_file(input_fname: str, - indent_size: int = 2, - indent_style: bool = False, - block_separation_max_depth: int = 2, - alphebitize_attributes: bool = False, - close_style: bool = False, - namespace: bool = False) -> None: - """Script to format xml files - - Args: - input_fname (str): Input file name - indent_size (int): Indent size - indent_style (bool): Style of indentation (0=fixed, 1=hanging) - block_separation_max_depth (int): Max depth to separate xml blocks - alphebitize_attributes (bool): Alphebitize attributes - close_style (bool): Style of close tag (0=same line, 1=new line) - namespace (bool): Insert this namespace in the xml description - """ - fname = os.path.expanduser(input_fname) - try: - tree = ElementTree.parse(fname) - root = tree.getroot() - prologue_comments = [tmp.text for tmp in root.itersiblings(preceding=True)] - epilog_comments = [tmp.text for tmp in root.itersiblings()] - - with open(fname, 'w') as f: - f.write('\n') - - for comment in reversed(prologue_comments): - f.write('\n' % (comment)) - - format_xml_level(f, - root, - 0, - indent=' ' * indent_size, - block_separation_max_depth=block_separation_max_depth, - modify_attribute_indent=indent_style, - sort_attributes=alphebitize_attributes, - close_tag_newline=close_style, - include_namespace=namespace) - - for comment in epilog_comments: - f.write('\n' % (comment)) - f.write('\n') - - except ElementTree.ParseError as err: - print('\nCould not load file: %s' % (fname)) - print(err.msg) - raise Exception('\nCheck input file!') - - -def main() -> None: - """Script to format xml files - - Args: - input (str): Input file name - -i/--indent (int): Indent size - -s/--style (int): Indent style - -d/--depth (int): Block separation depth - -a/--alphebitize (int): Alphebitize attributes - -c/--close (int): Close tag style - -n/--namespace (int): Include namespace - """ - parser = command_line_parsers.build_xml_formatter_input_parser() - args = parser.parse_args() - format_file(args.input, - indent_size=args.indent, - indent_style=args.style, - block_separation_max_depth=args.depth, - alphebitize_attributes=args.alphebitize, - close_style=args.close, - namespace=args.namespace) - - -if __name__ == "__main__": - main() diff --git a/src/coreComponents/python/modules/geosx_xml_tools_package/geosx_xml_tools/xml_processor.py b/src/coreComponents/python/modules/geosx_xml_tools_package/geosx_xml_tools/xml_processor.py deleted file mode 100644 index 757d0257ce4..00000000000 --- a/src/coreComponents/python/modules/geosx_xml_tools_package/geosx_xml_tools/xml_processor.py +++ /dev/null @@ -1,321 +0,0 @@ -"""Tools for processing xml files in GEOSX""" - -from lxml import etree as ElementTree # type: ignore[import] -from lxml.etree import XMLSyntaxError # type: ignore[import] -import re -import os -from geosx_xml_tools import regex_tools, unit_manager -from geosx_xml_tools import xml_formatter -from typing import Iterable, Tuple, List - -# Create an instance of the unit, parameter regex handlers -unitManager = unit_manager.UnitManager() -parameterHandler = regex_tools.DictRegexHandler() - - -def merge_xml_nodes(existingNode: ElementTree.Element, targetNode: ElementTree.Element, level: int) -> None: - """Merge nodes in an included file into the current structure level by level. - - Args: - existingNode (lxml.etree.Element): The current node in the base xml structure. - targetNode (lxml.etree.Element): The node to insert. - level (int): The xml file depth. - """ - - # Copy attributes on the current level - for tk in targetNode.attrib.keys(): - existingNode.set(tk, targetNode.get(tk)) - - # Copy target children into the xml structure - currentTag = '' - matchingSubNodes = [] - - for target in targetNode.getchildren(): - insertCurrentLevel = True - - # Check to see if a node with the appropriate type - # exists at this level - if (currentTag != target.tag): - currentTag = target.tag - matchingSubNodes = existingNode.findall(target.tag) - - if (matchingSubNodes): - targetName = target.get('name') - - # Special case for the root Problem node (which may be unnamed) - if (level == 0): - insertCurrentLevel = False - merge_xml_nodes(matchingSubNodes[0], target, level + 1) - - # Handle named xml nodes - elif (targetName and (currentTag not in ['Nodeset'])): - for match in matchingSubNodes: - if (match.get('name') == targetName): - insertCurrentLevel = False - merge_xml_nodes(match, target, level + 1) - - # Insert any unnamed nodes or named nodes that aren't present - # in the current xml structure - if (insertCurrentLevel): - existingNode.insert(-1, target) - - -def merge_included_xml_files(root: ElementTree.Element, fname: str, includeCount: int, maxInclude: int = 100) -> None: - """Recursively merge included files into the current structure. - - Args: - root (lxml.etree.Element): The root node of the base xml structure. - fname (str): The name of the target xml file to merge. - includeCount (int): The current recursion depth. - maxInclude (int): The maximum number of xml files to include (default = 100) - """ - - # Expand the input path - pwd = os.getcwd() - includePath, fname = os.path.split(os.path.abspath(os.path.expanduser(fname))) - os.chdir(includePath) - - # Check to see if the code has fallen into a loop - includeCount += 1 - if (includeCount > maxInclude): - raise Exception('Reached maximum recursive includes... Is there an include loop?') - - # Check to make sure the file exists - if (not os.path.isfile(fname)): - print('Included file does not exist: %s' % (fname)) - raise Exception('Check included file path!') - - # Load target xml - try: - parser = ElementTree.XMLParser(remove_comments=True, remove_blank_text=True) - includeTree = ElementTree.parse(fname, parser) - includeRoot = includeTree.getroot() - except XMLSyntaxError as err: - print('\nCould not load included file: %s' % (fname)) - print(err.msg) - raise Exception('\nCheck included file!') - - # Recursively add the includes: - for includeNode in includeRoot.findall('Included'): - for f in includeNode.findall('File'): - merge_included_xml_files(root, f.get('name'), includeCount) - - # Merge the results into the xml tree - merge_xml_nodes(root, includeRoot, 0) - os.chdir(pwd) - - -def apply_regex_to_node(node: ElementTree.Element) -> None: - """Apply regexes that handle parameters, units, and symbolic math to each - xml attribute in the structure. - - Args: - node (lxml.etree.Element): The target node in the xml structure. - """ - - for k in node.attrib.keys(): - value = node.get(k) - - # Parameter format: $Parameter or $:Parameter - ii = 0 - while ('$' in value): - value = re.sub(regex_tools.patterns['parameters'], parameterHandler, value) - ii += 1 - if (ii > 100): - raise Exception('Reached maximum parameter expands (Node=%s, value=%s)' % (node.tag, value)) - - # Unit format: 9.81[m**2/s] or 1.0 [bbl/day] - if ('[' in value): - value = re.sub(regex_tools.patterns['units'], unitManager.regexHandler, value) - - # Symbolic format: `1 + 2.34e5*2 * ...` - ii = 0 - while ('`' in value): - value = re.sub(regex_tools.patterns['symbolic'], regex_tools.SymbolicMathRegexHandler, value) - ii += 1 - if (ii > 100): - raise Exception('Reached maximum symbolic expands (Node=%s, value=%s)' % (node.tag, value)) - - node.set(k, value) - - for subNode in node.getchildren(): - apply_regex_to_node(subNode) - - -def generate_random_name(prefix: str = '', suffix: str = '.xml') -> str: - """If the target name is not specified, generate a random name for the compiled xml - - Args: - prefix (str): The file prefix (default = ''). - suffix (str): The file suffix (default = '.xml') - - Returns: - str: Random file name - """ - from hashlib import md5 - from time import time - from os import getpid - - tmp = str(time()) + str(getpid()) - return '%s%s%s' % (prefix, md5(tmp.encode('utf-8')).hexdigest(), suffix) - - -def process(inputFiles: Iterable[str], - outputFile: str = '', - schema: str = '', - verbose: int = 0, - parameter_override: List[Tuple[str, str]] = [], - keep_parameters: bool = True, - keep_includes: bool = True) -> str: - """Process an xml file - - Args: - inputFiles (list): Input file names. - outputFile (str): Output file name (if not specified, then generate randomly). - schema (str): Schema file name to validate the final xml (if not specified, then do not validate). - verbose (int): Verbosity level. - parameter_override (list): Parameter value overrides - keep_parameters (bool): If True, then keep parameters in the compiled file (default = True) - keep_includes (bool): If True, then keep includes in the compiled file (default = True) - - Returns: - str: Output file name - """ - if verbose: - print('\nReading input xml parameters and parsing symbolic math...') - - # Check the type of inputFiles - if isinstance(inputFiles, str): - inputFiles = [inputFiles] - - # Expand the input path - pwd = os.getcwd() - expanded_files = [os.path.abspath(os.path.expanduser(f)) for f in inputFiles] - single_path, single_input = os.path.split(expanded_files[0]) - os.chdir(single_path) - - # Handle single vs. multiple command line inputs - root = ElementTree.Element("Problem") - tree = ElementTree.ElementTree() - if (len(expanded_files) == 1): - # Load single files directly - try: - parser = ElementTree.XMLParser(remove_comments=True, remove_blank_text=True) - tree = ElementTree.parse(single_input, parser=parser) - root = tree.getroot() - except XMLSyntaxError as err: - print('\nCould not load input file: %s' % (single_input)) - print(err.msg) - raise Exception('\nCheck input file!') - - else: - # For multiple inputs, create a simple xml structure to hold - # the included files. These will be saved as comments in the compiled file - root = ElementTree.Element('Problem') - tree = ElementTree.ElementTree(root) - included_node = ElementTree.Element("Included") - root.append(included_node) - for f in expanded_files: - included_file = ElementTree.Element("File") - included_file.set('name', f) - included_node.append(included_file) - - # Add the included files to the xml structure - # Note: doing this first assumes that parameters aren't used in Included block - includeCount = 0 - for includeNode in root.findall('Included'): - for f in includeNode.findall('File'): - merge_included_xml_files(root, f.get('name'), includeCount) # type: ignore[attr-defined] - os.chdir(pwd) - - # Build the parameter map - Pmap = {} - for parameters in root.findall('Parameters'): - for p in parameters.findall('Parameter'): - Pmap[p.get('name')] = p.get('value') - - # Apply any parameter overrides - if len(parameter_override): - # Save overriden values to a new xml element - command_override_node = ElementTree.Element("CommandLineOverride") - root.append(command_override_node) - for ii in range(len(parameter_override)): - pname = parameter_override[ii][0] - pval = ' '.join(parameter_override[ii][1:]) - Pmap[pname] = pval - override_parameter = ElementTree.Element("Parameter") - override_parameter.set('name', pname) - override_parameter.set('value', pval) - command_override_node.append(override_parameter) - - # Add the parameter map to the handler - parameterHandler.target = Pmap - - # Process any parameters, units, and symbolic math in the xml - apply_regex_to_node(root) - - # Comment out or remove the Parameter, Included nodes - for includeNode in root.findall('Included'): - if keep_includes: - root.insert(-1, ElementTree.Comment(ElementTree.tostring(includeNode))) - root.remove(includeNode) - for parameterNode in root.findall('Parameters'): - if keep_parameters: - root.insert(-1, ElementTree.Comment(ElementTree.tostring(parameterNode))) - root.remove(parameterNode) - for overrideNode in root.findall('CommandLineOverride'): - if keep_parameters: - root.insert(-1, ElementTree.Comment(ElementTree.tostring(overrideNode))) - root.remove(overrideNode) - - # Generate a random output name if not specified - if not outputFile: - outputFile = generate_random_name(prefix='prep_') - - # Write the output file - tree.write(outputFile, pretty_print=True) - - # Check for un-matched special characters - with open(outputFile, 'r') as ofile: - for line in ofile: - if any([sc in line for sc in ['$', '[', ']', '`']]): - raise Exception( - 'Found un-matched special characters in the pre-processed input file on line:\n%s\n Check your input xml for errors!' - % (line)) - - # Apply formatting to the file - xml_formatter.format_file(outputFile) - - if verbose: - print('Preprocessed xml file stored in %s' % (outputFile)) - - if schema: - validate_xml(outputFile, schema, verbose) - - return outputFile - - -def validate_xml(fname: str, schema: str, verbose: int) -> None: - """Validate an xml file, and parse the warnings. - - Args: - fname (str): Target xml file name. - schema (str): Schema file name. - verbose (int): Verbosity level. - """ - if verbose: - print('Validating the xml against the schema...') - try: - ofile = ElementTree.parse(fname) - sfile = ElementTree.XMLSchema(ElementTree.parse(os.path.expanduser(schema))) - sfile.assertValid(ofile) - except ElementTree.DocumentInvalid as err: - print(err) - print('\nWarning: input XML contains potentially invalid input parameters:') - print('-' * 20 + '\n') - print(sfile.error_log) - print('\n' + '-' * 20) - print('(Total schema warnings: %i)\n' % (len(sfile.error_log))) - - if verbose: - print('Done!') diff --git a/src/coreComponents/python/modules/geosx_xml_tools_package/geosx_xml_tools/xml_redundancy_check.py b/src/coreComponents/python/modules/geosx_xml_tools_package/geosx_xml_tools/xml_redundancy_check.py deleted file mode 100644 index 251e1df0ea7..00000000000 --- a/src/coreComponents/python/modules/geosx_xml_tools_package/geosx_xml_tools/xml_redundancy_check.py +++ /dev/null @@ -1,98 +0,0 @@ -from geosx_xml_tools.attribute_coverage import parse_schema -from geosx_xml_tools.xml_formatter import format_file -from lxml import etree as ElementTree # type: ignore[import] -import os -from pathlib import Path -from geosx_xml_tools import command_line_parsers -from typing import Iterable, Dict, Any - - -def check_redundancy_level(local_schema: Dict[str, Any], - node: ElementTree.Element, - whitelist: Iterable[str] = ['component']) -> int: - """Check xml redundancy at the current level - - Args: - local_schema (dict): Schema definitions - node (lxml.etree.Element): current xml node - whitelist (list): always match nodes containing these attributes - - Returns: - int: Number of required attributes in the node and its children - """ - node_is_required = 0 - for ka in node.attrib.keys(): - if (ka in whitelist): - node_is_required += 1 - elif (ka not in local_schema['attributes']): - node_is_required += 1 - elif ('default' not in local_schema['attributes'][ka]): - node_is_required += 1 - elif (node.get(ka) != local_schema['attributes'][ka]['default']): - node_is_required += 1 - else: - node.attrib.pop(ka) - - for child in node: - # Comments will not appear in the schema - if child.tag in local_schema['children']: - child_is_required = check_redundancy_level(local_schema['children'][child.tag], child) - node_is_required += child_is_required - if not child_is_required: - node.remove(child) - - return node_is_required - - -def check_xml_redundancy(schema: Dict[str, Any], fname: str) -> None: - """Check redundancy in an xml file - - Args: - schema (dict): Schema definitions - fname (str): Name of the target file - """ - xml_tree = ElementTree.parse(fname) - xml_root = xml_tree.getroot() - check_redundancy_level(schema['Problem'], xml_root) - xml_tree.write(fname) - format_file(fname) - - -def process_xml_files(geosx_root: str) -> None: - """Test for xml redundancy - - Args: - geosx_root (str): GEOSX root directory - """ - - # Parse the schema - geosx_root = os.path.expanduser(geosx_root) - schema_fname = '%ssrc/coreComponents/schema/schema.xsd' % (geosx_root) - schema = parse_schema(schema_fname) - - # Find all xml files, collect their attributes - for folder in ['src', 'examples']: - print(folder) - xml_files = Path(os.path.join(geosx_root, folder)).rglob('*.xml') - for f in xml_files: - print(' %s' % (str(f))) - check_xml_redundancy(schema, str(f)) - - -def main() -> None: - """Entry point for the xml attribute usage test script - - Args: - -r/--root (str): GEOSX root directory - """ - - # Parse the user arguments - parser = command_line_parsers.build_xml_redundancy_input_parser() - args = parser.parse_args() - - # Parse the xml files - process_xml_files(args.root) - - -if __name__ == "__main__": - main() diff --git a/src/coreComponents/python/modules/geosx_xml_tools_package/pyproject.toml b/src/coreComponents/python/modules/geosx_xml_tools_package/pyproject.toml deleted file mode 100644 index c2f433afcb5..00000000000 --- a/src/coreComponents/python/modules/geosx_xml_tools_package/pyproject.toml +++ /dev/null @@ -1,8 +0,0 @@ -[build-system] -requires = ["setuptools>=42", "wheel"] -build-backend = "setuptools.build_meta" - -[tool.mypy] -python_version = "3.8" -warn_return_any = true -warn_unused_configs = true diff --git a/src/coreComponents/python/modules/geosx_xml_tools_package/setup.cfg b/src/coreComponents/python/modules/geosx_xml_tools_package/setup.cfg deleted file mode 100644 index b369ca6f88b..00000000000 --- a/src/coreComponents/python/modules/geosx_xml_tools_package/setup.cfg +++ /dev/null @@ -1,28 +0,0 @@ -[metadata] -name = geosx_xml_tools -version = 0.6.0 -description = Tools for enabling advanced xml features in GEOSX -author = Christopher Sherman -author_email = sherman27@llnl.gov -license = LGPL-2.1 - -[options] -packages = - geosx_xml_tools - geosx_xml_tools.tests -install_requires = - lxml>=4.5.0 - parameterized - numpy -python_requires = >=3.6 - -[options.package_data] -geosx_xml_tools = py.typed - -[options.entry_points] -console_scripts = - preprocess_xml = geosx_xml_tools.main:preprocess_serial - format_xml = geosx_xml_tools.xml_formatter:main - test_geosx_xml_tools = geosx_xml_tools.tests.test_manager:main - check_xml_attribute_coverage = geosx_xml_tools.attribute_coverage:main - check_xml_redundancy = geosx_xml_tools.xml_redundancy_check:main diff --git a/src/coreComponents/python/modules/hdf5_wrapper_package/hdf5_wrapper/__init__.py b/src/coreComponents/python/modules/hdf5_wrapper_package/hdf5_wrapper/__init__.py deleted file mode 100644 index 0f724ef67a9..00000000000 --- a/src/coreComponents/python/modules/hdf5_wrapper_package/hdf5_wrapper/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .wrapper import hdf5_wrapper diff --git a/src/coreComponents/python/modules/hdf5_wrapper_package/hdf5_wrapper/py.typed b/src/coreComponents/python/modules/hdf5_wrapper_package/hdf5_wrapper/py.typed deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/src/coreComponents/python/modules/hdf5_wrapper_package/hdf5_wrapper/use_example.py b/src/coreComponents/python/modules/hdf5_wrapper_package/hdf5_wrapper/use_example.py deleted file mode 100644 index 5bbdbe3d27a..00000000000 --- a/src/coreComponents/python/modules/hdf5_wrapper_package/hdf5_wrapper/use_example.py +++ /dev/null @@ -1,89 +0,0 @@ -import numpy as np -import hdf5_wrapper -from typing import Union, Dict - - -def print_database_iterative(database: hdf5_wrapper.hdf5_wrapper, level: int = 0) -> None: - """ - Print the database targets iteratively by level - - Args: - database (hdf5_wrapper.hdf5_wrapper) the wrapper for the current database - level (int): the depth within the database - """ - # Note: you can also iterate over the hdf5_wrapper object directly - for k in database.keys(): - print('%s%s' % (' ' * level, k)) - - if isinstance(database[k], hdf5_wrapper.hdf5_wrapper): - # This is a group, so continue iterating downward - print_database_iterative(database[k], level + 1) - else: - # This is likely to be an array - print(database[k]) - print() - - -def read_write_hdf5_database_example() -> None: - """ - Simple demonstration of hdf5_wrapper - """ - - # ------------------------ - # Generate test data - # ------------------------ - nested_dict_type = Dict[str, Union[np.ndarray, Dict[str, np.ndarray]]] - source_a: nested_dict_type = { - '1D_double_array': np.random.randn(10), - 'string_array': np.array(['a', 'list', 'of', 'strings']), - 'child_a': { - '2D_double_array': np.random.randn(2, 3) - } - } - - source_b: nested_dict_type = { - '1D_integer_array': np.random.randint(0, 100, 5), - 'child_b': { - '3D_double_array': np.random.randn(4, 5, 2) - } - } - - # ------------------------ - # Write databases to file - # ------------------------ - # Write the first piece-by-piece to an hdf5_file - # Note: when you exit the following scope, the database is automatically closed - with hdf5_wrapper.hdf5_wrapper('database_a.hdf5', mode='a') as database_a: - # Assign the two array objects to this level - database_a['1D_double_array'] = source_a['1D_double_array'] - database_a['string_array'] = source_a['string_array'] - - # Create a child group and assign the final array - child_a = database_a['child_a'] - child_a['2D_double_array'] = source_a['child_a']['2D_double_array'] - - # Automatically write the second source to a second database - with hdf5_wrapper.hdf5_wrapper('database_b.hdf5', mode='a') as database_b: - database_b['/'] = source_b - - # Create a third database that links the either two - with hdf5_wrapper.hdf5_wrapper('database_c.hdf5', mode='a') as database_c: - database_c.link('database_a', 'database_a.hdf5') - database_c.link('database_b', 'database_b.hdf5') - - # --------------------------------------- - # Read the databases from the filesystem - # --------------------------------------- - print('Database contents:') - with hdf5_wrapper.hdf5_wrapper('database_c.hdf5') as database_c: - # Iteratively print the database contents - print_database_iterative(database_c, 1) - - # As a final note, you can also access low-level h5py functionality - # by interacting directly with the database target, e.g.: - print('Database attributes:') - print(' ', database_c.target.attrs) - - -if __name__ == "__main__": - read_write_hdf5_database_example() diff --git a/src/coreComponents/python/modules/hdf5_wrapper_package/hdf5_wrapper/wrapper.py b/src/coreComponents/python/modules/hdf5_wrapper_package/hdf5_wrapper/wrapper.py deleted file mode 100644 index a8a11b89930..00000000000 --- a/src/coreComponents/python/modules/hdf5_wrapper_package/hdf5_wrapper/wrapper.py +++ /dev/null @@ -1,206 +0,0 @@ -import h5py # type: ignore[import] -import numpy as np -from numpy.core.defchararray import encode, decode -from typing import Union, Dict, Any, Iterable, Optional, Tuple - -# Note: I would like to replace Any here with str, float, int, np.ndarray, etc. -# However, this heterogeneous pattern causes issues with mypy indexing -hdf5_get_types = Union['hdf5_wrapper', Any] -nested_dict_type = Dict[str, Any] -hdf5_set_types = Union['hdf5_wrapper', nested_dict_type, Any] - - -class hdf5_wrapper(): - """ - A class for reading/writing hdf5 files, which behaves similar to a native dict - """ - - def __init__(self, fname: str = '', target: Optional[h5py.File] = None, mode: str = 'r') -> None: - """ - Initialize the hdf5_wrapper class - - If the fname is supplied (either by a positional or keyword argument), - the wrapper will open a hdf5 database from the filesystem. - The recommended options for the mode flag include 'r' for read-only - and 'a' for read/write access. - If write mode is enabled, and the fname does not point - to an existing file, a new database will be created. - - If the target is supplied, then a new instance of the wrapper will - be created using an existing database handle. - - Args: - fname (str): the filename of a new or existing hdf5 database - target (hdf5_wrapper): the handle of an existing hdf5 dataset - mode (str): the read/write behavior of the database (default='r') - """ - self.mode: str = mode - self.target: h5py.File = target - if fname: - self.target = h5py.File(fname, self.mode) - - def __getitem__(self, k: str) -> hdf5_get_types: - """ - Get a target from the database - - If the target is not present in the datastructure and the - database is open in read/write mode, the wrapper will create a - new group and return an hdf5_wrapper. Otherwise it will throw an error - - Args: - k (str): name of target group or array - - Returns: - hdf5_wrapper/np.ndarray: The returned value - """ - if (k not in self.target): - if (self.mode in ['w', 'a']): - self.target.create_group(k) - else: - raise ValueError('Entry does not exist in database: %s' % (k)) - - tmp = self.target[k] - - if isinstance(tmp, h5py._hl.group.Group): - return hdf5_wrapper(target=tmp, mode=self.mode) - elif isinstance(tmp, h5py._hl.dataset.Dataset): - tmp = np.array(tmp) - - # Decode any string types - if (tmp.dtype.kind in ['S', 'U', 'O']): - tmp = decode(tmp) - - # Convert any 0-length arrays to native types - if not tmp.shape: - tmp = tmp[()] - - return tmp - else: - return tmp - - def __setitem__(self, k: str, value: hdf5_set_types): - """ - Write an object to the database if write-mode is enabled - - Args: - k (str): the name of the object - value (dict, np.ndarray, float, int, str): the object to be written - """ - if (self.mode in ['w', 'a']): - if isinstance(value, (dict, hdf5_wrapper)): - # Recursively add groups and their children - if (k not in self.target): - self.target.create_group(k) - new_group = self[k] - for kb, x in value.items(): - new_group[kb] = x - else: - # Delete the old copy if necessary - if (k in self.target): - del (self.target[k]) - - # Add everything else as an ndarray - tmp = np.array(value) - if (tmp.dtype.kind in ['S', 'U', 'O']): - tmp = encode(tmp) - self.target[k] = tmp - else: - raise ValueError( - 'Cannot write to an hdf5 opened in read-only mode! This can be changed by overriding the default mode argument for the wrapper.' - ) - - def link(self, k: str, target: str) -> None: - """ - Link an external hdf5 file to this location in the database - - Args: - k (str): the name of the new link in the database - target (str): the path to the external database - """ - self.target[k] = h5py.ExternalLink(target, '/') - - def keys(self) -> Iterable[str]: - """ - Get a list of groups and arrays located at the current level - - Returns: - list: a list of key names pointing to objects at the current level - """ - if isinstance(self.target, h5py._hl.group.Group): - return list(self.target) - else: - raise ValueError('Object not a group!') - - def values(self) -> Iterable[hdf5_get_types]: - """ - Get a list of values located on the current level - """ - return [self[k] for k in self.keys()] - - def items(self) -> Iterable[Tuple[str, hdf5_get_types]]: - return zip(self.keys(), self.values()) - - def __enter__(self): - """ - Entry point for an iterator - """ - return self - - def __exit__(self, type, value, traceback) -> None: - """ - End point for an iterator - """ - self.target.close() - - def __del__(self) -> None: - """ - Closes the database on wrapper deletion - """ - try: - if isinstance(self.target, h5py._hl.files.File): - self.target.close() - except: - pass - - def close(self) -> None: - """ - Closes the database - """ - if isinstance(self.target, h5py._hl.files.File): - self.target.close() - - def get_copy(self) -> nested_dict_type: - """ - Copy the entire database into memory - - Returns: - dict: a dictionary holding the database contents - """ - result: Dict[Union[str, int], Any] = {} - for k in self.keys(): - tmp = self[k] - if isinstance(tmp, hdf5_wrapper): - result[k] = tmp.get_copy() - else: - result[k] = tmp - - return result - - def copy(self) -> nested_dict_type: - """ - Copy the entire database into memory - - Returns: - dict: a dictionary holding the database contents - """ - return self.get_copy() - - def insert(self, x: Union[nested_dict_type, 'hdf5_wrapper']) -> None: - """ - Insert the contents of the target object to the current location - - Args: - x (dict, hdf5_wrapper): the dictionary to insert - """ - for k, v in x.items(): - self[k] = v diff --git a/src/coreComponents/python/modules/hdf5_wrapper_package/hdf5_wrapper/wrapper_tests.py b/src/coreComponents/python/modules/hdf5_wrapper_package/hdf5_wrapper/wrapper_tests.py deleted file mode 100644 index 7e496ee954e..00000000000 --- a/src/coreComponents/python/modules/hdf5_wrapper_package/hdf5_wrapper/wrapper_tests.py +++ /dev/null @@ -1,124 +0,0 @@ -import unittest -import os -import argparse -import numpy as np -import random -import string -import hdf5_wrapper - - -def random_string(N): - return ''.join(random.choices(string.ascii_uppercase + string.ascii_lowercase + string.digits, k=N)) - - -def build_test_dict(depth=0, max_depth=3): - r = [np.random.randint(2, 20) for x in range(5)] - test = { - 'int': np.random.randint(-1000000, 1000000), - 'float': np.random.random(), - '1d_array': np.random.randn(r[0]), - '3d_array': np.random.randn(r[1], r[2], r[3]), - 'string': random_string(10), - 'string_array': np.array([random_string(x + 10) for x in range(r[4])]) - } - if (depth < max_depth): - test['child_a'] = build_test_dict(depth + 1, max_depth) - test['child_b'] = build_test_dict(depth + 1, max_depth) - test['child_c'] = build_test_dict(depth + 1, max_depth) - - return test - - -# Test the unit manager definitions -class TestHDF5Wrapper(unittest.TestCase): - - @classmethod - def setUpClass(cls): - cls.test_dir = 'wrapper_tests' - os.makedirs(cls.test_dir, exist_ok=True) - cls.test_dict = build_test_dict() - - def compare_wrapper_dict(self, x, y): - kx = x.keys() - ky = y.keys() - - for k in kx: - if k not in ky: - raise Exception('y key not in x object (%s)' % (k)) - - for k in ky: - if k not in kx: - raise Exception('x key not in y object (%s)' % (k)) - - vx, vy = x[k], y[k] - tx, ty = type(vx), type(vy) - if ((tx != ty) and not (isinstance(vx, (dict, hdf5_wrapper.hdf5_wrapper)) - and isinstance(vy, (dict, hdf5_wrapper.hdf5_wrapper)))): - self.assertTrue(np.issubdtype(tx, ty)) - - if isinstance(vx, (dict, hdf5_wrapper.hdf5_wrapper)): - self.compare_wrapper_dict(vx, vy) - else: - if isinstance(vx, np.ndarray): - self.assertTrue(np.shape(vx) == np.shape(vy)) - self.assertTrue((vx == vy).all()) - else: - self.assertTrue(vx == vy) - - def test_a_insert_write(self): - data = hdf5_wrapper.hdf5_wrapper(os.path.join(self.test_dir, 'test_insert.hdf5'), mode='w') - data.insert(self.test_dict) - - def test_b_manual_write(self): - data = hdf5_wrapper.hdf5_wrapper(os.path.join(self.test_dir, 'test_manual.hdf5'), mode='w') - for k, v in self.test_dict.items(): - data[k] = v - - def test_c_link_write(self): - data = hdf5_wrapper.hdf5_wrapper(os.path.join(self.test_dir, 'test_linked.hdf5'), mode='w') - for k, v in self.test_dict.items(): - if ('child' in k): - child_path = os.path.join(self.test_dir, 'test_%s.hdf5' % (k)) - data_child = hdf5_wrapper.hdf5_wrapper(child_path, mode='w') - data_child.insert(v) - data.link(k, child_path) - else: - data[k] = v - - def test_d_compare_wrapper(self): - data = hdf5_wrapper.hdf5_wrapper(os.path.join(self.test_dir, 'test_insert.hdf5')) - self.compare_wrapper_dict(self.test_dict, data) - - def test_e_compare_wrapper_copy(self): - data = hdf5_wrapper.hdf5_wrapper(os.path.join(self.test_dir, 'test_insert.hdf5')) - tmp = data.copy() - self.compare_wrapper_dict(self.test_dict, tmp) - - def test_f_compare_wrapper(self): - data = hdf5_wrapper.hdf5_wrapper(os.path.join(self.test_dir, 'test_manual.hdf5')) - self.compare_wrapper_dict(self.test_dict, data) - - def test_g_compare_wrapper(self): - data = hdf5_wrapper.hdf5_wrapper(os.path.join(self.test_dir, 'test_linked.hdf5')) - self.compare_wrapper_dict(self.test_dict, data) - - -def main(): - """Entry point for the geosx_xml_tools unit tests - - Args: - -v/--verbose (int): Output verbosity - """ - - # Parse the user arguments - parser = argparse.ArgumentParser() - parser.add_argument('-v', '--verbose', type=int, help='Verbosity level', default=2) - args = parser.parse_args() - - # Unit manager tests - suite = unittest.TestLoader().loadTestsFromTestCase(TestHDF5Wrapper) - unittest.TextTestRunner(verbosity=args.verbose).run(suite) - - -if __name__ == "__main__": - main() diff --git a/src/coreComponents/python/modules/hdf5_wrapper_package/pyproject.toml b/src/coreComponents/python/modules/hdf5_wrapper_package/pyproject.toml deleted file mode 100644 index c2f433afcb5..00000000000 --- a/src/coreComponents/python/modules/hdf5_wrapper_package/pyproject.toml +++ /dev/null @@ -1,8 +0,0 @@ -[build-system] -requires = ["setuptools>=42", "wheel"] -build-backend = "setuptools.build_meta" - -[tool.mypy] -python_version = "3.8" -warn_return_any = true -warn_unused_configs = true diff --git a/src/coreComponents/python/modules/hdf5_wrapper_package/setup.cfg b/src/coreComponents/python/modules/hdf5_wrapper_package/setup.cfg deleted file mode 100644 index 13db52e1b82..00000000000 --- a/src/coreComponents/python/modules/hdf5_wrapper_package/setup.cfg +++ /dev/null @@ -1,23 +0,0 @@ -[metadata] -name = hdf5_wrapper -version = 0.2.0 -description = Simple wrapper for h5py objects -author = Christopher Sherman -author_email = sherman27@llnl.gov -license = LGPL-2.1 - -[options] -packages = - hdf5_wrapper -install_requires = - h5py>=2.10.0 - numpy>=1.16.2 -python_requires = >=3.6 - -[options.package_data] -hdf5_wrapper = py.typed - -[options.entry_points] -console_scripts = - hdf5_wrapper_tests = hdf5_wrapper.wrapper_tests:main - diff --git a/src/coreComponents/python/modules/pygeosx_tools_package/pygeosx_tools/__init__.py b/src/coreComponents/python/modules/pygeosx_tools_package/pygeosx_tools/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/src/coreComponents/python/modules/pygeosx_tools_package/pygeosx_tools/file_io.py b/src/coreComponents/python/modules/pygeosx_tools_package/pygeosx_tools/file_io.py deleted file mode 100644 index 12093e8d076..00000000000 --- a/src/coreComponents/python/modules/pygeosx_tools_package/pygeosx_tools/file_io.py +++ /dev/null @@ -1,86 +0,0 @@ -import os -import numpy as np -from typing import Dict, Iterable, List, Tuple - - -def save_tables(axes: Iterable[np.ndarray], - properties: Dict[str, np.ndarray], - table_root: str = './tables', - axes_names: List[str] = []) -> None: - """ - Saves a set of tables in GEOSX format - - The shape of these arrays should match the length of each axis in the specified order. - The output directory will be created if it does not exist yet. - If axes_names are not supplied, then they will be selected based - on the dimensionality of the grid: 1D=[t]; 3D=[x, y, z]; 4D=[x, y, z, t]. - - Args: - axes (list): A list of numpy ndarrays defining the table axes - properties (dict): A dict of numpy ndarrays defning the table values - table_root (str): The root path for the output directory - axes_names (list): A list of names for each potential axis (optional) - """ - # Check to see if the axes, properties have consistent shapes - axes_size = tuple([len(x) for x in axes]) - axes_dimension = len(axes_size) - for k, p in properties.items(): - property_size = np.shape(p) - if (property_size != axes_size): - print('Property:', k) - print('Grid size:', axes_size) - print('Property size', property_size) - raise Exception('Table dimensions do not match proprerties') - - # Check the axes names - if axes_names: - if (axes_dimension != len(axes_names)): - print('Axes dimensions:', axes_dimension) - print('Number of axis names provided:', len(axes_names)) - raise Exception('The grid dimensions and axes names do not match') - else: - if (axes_dimension == 1): - axes_names = ['t'] - elif (axes_dimension == 3): - axes_names = ['x', 'y', 'z'] - elif (axes_dimension == 4): - axes_names = ['x', 'y', 'z', 't'] - else: - axes_names = ['x%i' % (ii) for ii in range(axes_dimension)] - - # Write the axes - os.makedirs(table_root, exist_ok=True) - for g, a in zip(axes, axes_names): - np.savetxt('%s/%s.csv' % (table_root, a), g, fmt='%1.5f', delimiter=',') - - for k, p in properties.items(): - np.savetxt('%s/%s.csv' % (table_root, k), np.reshape(p, (-1), order='F'), fmt='%1.5e', delimiter=',') - - -def load_tables(axes_names: Iterable[str], - property_names: Iterable[str], - table_root: str = './tables', - extension: str = 'csv') -> Tuple[Iterable[np.ndarray], Dict[str, np.ndarray]]: - """ - Load a set of tables in GEOSX format - - Args: - axes_names (list): Axis file names in the target directory (with no extension) - property_names (list): Property file names in the target directory (with not extension) - table_root (str): Root path for the table directory - extension (str): Table file extension (default = 'csv') - - Returns: - tuple: List of axes values, and dictionary of table values - """ - # Load axes - axes = [np.loadtxt('%s/%s.%s' % (table_root, axis, extension), unpack=True) for axis in axes_names] - N = tuple([len(x) for x in axes]) - - # Load properties - properties = { - p: np.reshape(np.loadtxt('%s/%s.%s' % (table_root, p, extension)), N, order='F') - for p in property_names - } - - return axes, properties diff --git a/src/coreComponents/python/modules/pygeosx_tools_package/pygeosx_tools/mesh_interpolation.py b/src/coreComponents/python/modules/pygeosx_tools_package/pygeosx_tools/mesh_interpolation.py deleted file mode 100644 index 49b20112044..00000000000 --- a/src/coreComponents/python/modules/pygeosx_tools_package/pygeosx_tools/mesh_interpolation.py +++ /dev/null @@ -1,110 +0,0 @@ -import numpy as np -from scipy import stats # type: ignore[import] -from typing import Dict, Iterable, List, Tuple, Callable, Union - - -def apply_to_bins(fn: Callable[[Union[float, np.ndarray]], float], - position: np.ndarray, - value: np.ndarray, - bins: np.ndarray, - collapse_edges: bool = True): - """ - Apply a function to values that are located within a series of bins - Note: if a bin is empty, this function will fill a nan value - - Args: - fn (function): Function that takes a single scalar or array input - position (np.ndarray): A 1D list/array describing the location of each sample - value (np.ndarray): A 1D list/array of values at each location - bins (np.ndarray): The bin edges for the position data - collapse_edges (bool): Controls the behavior of edge-data (default=True) - - Returns: - np.ndarray: an array of function results for each bin - """ - # Sort values into bins - Nr = len(bins) + 1 - Ibin = np.digitize(position, bins) - - if collapse_edges: - Nr -= 2 - Ibin -= 1 - Ibin[Ibin == -1] = 0 - Ibin[Ibin == Nr] = Nr - 1 - - # Apply functions to bins - binned_values = np.zeros(Nr) - for ii in range(Nr): - tmp = (Ibin == ii) - if np.sum(tmp): - binned_values[ii] = fn(value[tmp]) - else: - # Empty bin - binned_values[ii] = np.NaN - - return binned_values - - -def extrapolate_nan_values(x, y, slope_scale=0.0): - """ - Fill in any nan values in two 1D arrays by extrapolating - - Args: - x (np.ndarray): 1D list/array of positions - y (np.ndarray): 1D list/array of values - slope_scale (float): value to scale the extrapolation slope (default=0.0) - - Returns: - np.ndarray: The input array with nan values replaced by extrapolated data - """ - Inan = np.isnan(y) - reg = stats.linregress(x[~Inan], y[~Inan]) - y[Inan] = reg[0] * x[Inan] * slope_scale + reg[1] - return y - - -def get_random_realization(x, bins, value, rand_fill=0, rand_scale=0, slope_scale=0): - """ - Get a random realization for a noisy signal with a set of bins - - Args: - x (np.ndarray): 1D list/array of positions - bins (np.ndarray): 1D list/array of bin edges - value (np.ndarray): 1D list/array of values - rand_fill (float): The standard deviation to use where data is not defined (default=0) - rand_scale (float): Value to scale the standard deviation for the realization (default=0) - slope_scale (float): Value to scale the extrapolation slope (default=0.0) - - Returns: - np.ndarray: An array containing the random realization - """ - y_mean = apply_to_bins(np.mean, x, value, bins) - y_std = apply_to_bins(np.std, x, value, bins) - - # Extrapolate to fill the upper/lower bounds - x_mid = bins[:-1] + 0.5 * (bins[1] - bins[0]) - y_mean = extrapolate_nan_values(x_mid, y_mean, slope_scale) - y_std[np.isnan(y_std)] = rand_fill - - # Add a random perturbation to the target value to match missing high/lows - y_final = y_mean + (rand_scale * y_std * np.random.randn(len(y_mean))) - return y_final - - -def get_realizations(x, bins, targets): - """ - Get random realizations for noisy signals on target bins - - Args: - x (np.ndarray): 1D list/array of positions - bins (np.ndarray): 1D list/array of bin edges - targets (dict): Dict of geosx target keys, inputs to get_random_realization - - Returns: - dict: Dictionary of random realizations - - """ - results = {} - for k, t in targets.items(): - results[k] = get_random_realization(x, bins, **t) - return results diff --git a/src/coreComponents/python/modules/pygeosx_tools_package/pygeosx_tools/py.typed b/src/coreComponents/python/modules/pygeosx_tools_package/pygeosx_tools/py.typed deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/src/coreComponents/python/modules/pygeosx_tools_package/pygeosx_tools/well_log.py b/src/coreComponents/python/modules/pygeosx_tools_package/pygeosx_tools/well_log.py deleted file mode 100644 index 58453f0f968..00000000000 --- a/src/coreComponents/python/modules/pygeosx_tools_package/pygeosx_tools/well_log.py +++ /dev/null @@ -1,96 +0,0 @@ -import numpy as np -import re - - -def parse_las(fname, variable_start='~C', body_start='~A'): - """ - Parse an las format log file - - Args: - fname (str): Path to the log file - variable_start (str): A string that indicates the start of variable header information (default = '~CURVE INFORMATION') - body_start (str): a string that indicates the start of the log body (default = '~A') - - Returns: - np.ndarray: a dict containing the values and unit definitions for each variable in the log - """ - results = {} - variable_order = [] - - # The expected format of the varible definition block is: - # name.units code:description - variable_regex = re.compile('\s*([^\.^\s]*)\s*(\.[^ ]*) ([^:]*):(.*)') - - with open(fname) as f: - file_location = 0 - for line in f: - line = line.split('#')[0] - if line: - # Preamble - if (file_location == 0): - if variable_start in line: - file_location += 1 - - # Variable definitions - elif (file_location == 1): - # This is not a comment line - if body_start in line: - file_location += 1 - else: - match = variable_regex.match(line) - if match: - variable_order.append(match[1]) - results[match[1]] = { - 'units': match[2][0:], - 'code': match[3], - 'description': match[4], - 'values': [] - } - else: - # As a fall-back use the full line - variable_order.append(line[:-1]) - results[line[:-1]] = {'units': '', 'code': '', 'description': '', 'values': []} - - # Body - else: - for k, v in zip(variable_order, line.split()): - results[k]['values'].append(float(v)) - - # Convert values to numpy arrays - for k in results: - results[k]['values'] = np.array(results[k]['values']) - - return results - - -def convert_E_nu_to_K_G(E, nu): - """ - Convert young's modulus and poisson's ratio to bulk and shear modulus - - Args: - E (float, np.ndarray): Young's modulus - nu (float, np.ndarray): Poisson's ratio - - Returns: - tuple: bulk modulus, shear modulus with same size as inputs - """ - K = E / (3.0 * (1 - 2.0 * nu)) - G = E / (2.0 * (1 + nu)) - return K, G - - -def estimate_shmin(z, rho, nu): - """ - Estimate the minimum horizontal stress using the poisson's ratio - - Args: - z (float, np.ndarray): Depth - rho (float, np.ndarray): Density - nu (float, np.ndarray): Poisson's ratio - - Returns: - float: minimum horizontal stress - """ - k = nu / (1.0 - nu) - sigma_h = k * rho * 9.81 * z - return sigma_h diff --git a/src/coreComponents/python/modules/pygeosx_tools_package/pygeosx_tools/wrapper.py b/src/coreComponents/python/modules/pygeosx_tools_package/pygeosx_tools/wrapper.py deleted file mode 100644 index 7c46e742bbf..00000000000 --- a/src/coreComponents/python/modules/pygeosx_tools_package/pygeosx_tools/wrapper.py +++ /dev/null @@ -1,406 +0,0 @@ -import sys -import numpy as np -from mpi4py import MPI -import matplotlib.pyplot as plt -import pylvarray -import pygeosx - -# Get the MPI rank -comm = MPI.COMM_WORLD -rank = comm.Get_rank() - - -def get_wrapper(problem, target_key, write_flag=False): - """ - Get a local copy of a wrapper as a numpy ndarray - - Args: - filename (str): Catalog file name - problem (pygeosx.Group): GEOSX problem handle - target_key (str): Key for the target wrapper - write_flag (bool): Sets write mode (default=False) - - Returns: - np.ndarray: The wrapper as a numpy ndarray - """ - local_values = problem.get_wrapper(target_key).value() - - if hasattr(local_values, "set_access_level"): - # Array types will have the set_access_level method - # These require additional manipulation before use - if write_flag: - local_values.set_access_level(pylvarray.MODIFIABLE, pylvarray.CPU) - else: - local_values.set_access_level(pylvarray.CPU) - - if hasattr(local_values, "to_numpy"): - local_values = local_values.to_numpy() - return local_values - - -def get_wrapper_par(problem, target_key, allgather=False, ghost_key=''): - """ - Get a global copy of a wrapper as a numpy ndarray. - Note: if ghost_key is set, it will try to remove any ghost elements - - Args: - problem (pygeosx.Group): GEOSX problem handle - target_key (str): Key for the target wrapper - allgather (bool): Flag to trigger allgather across ranks (False) - ghost_key (str): Key for the corresponding ghost wrapper (default='') - - Returns: - np.ndarray: The wrapper as a numpy ndarray - """ - if (comm.size == 1): - # This is a serial problem - return get_wrapper(problem, target_key) - - else: - # This is a parallel problem - # Get the local wrapper size, shape - local_values = get_wrapper(problem, target_key) - - # Filter out ghost ranks if requested - if ghost_key: - ghost_values = get_wrapper(problem, ghost_key) - local_values = local_values[ghost_values < -0.5] - - # Find buffer size - N = np.shape(local_values) - M = np.prod(N) - all_M = [] - max_M = 0 - if allgather: - all_M = comm.allgather(M) - max_M = np.amax(all_M) - else: - all_M = comm.gather(M, root=0) - if (rank == 0): - max_M = np.amax(all_M) - max_M = comm.bcast(max_M, root=0) - - # Pack the array into a buffer - send_buff = np.zeros(max_M) - send_buff[:M] = np.reshape(local_values, (-1)) - receive_buff = np.zeros((comm.size, max_M)) - - # Gather the buffers - if allgather: - comm.Allgather([send_buff, MPI.DOUBLE], [receive_buff, MPI.DOUBLE]) - else: - comm.Gather([send_buff, MPI.DOUBLE], [receive_buff, MPI.DOUBLE], root=0) - - # Unpack the buffers - all_values = [] - R = list(N) - R[0] = -1 - if ((rank == 0) | allgather): - # Reshape each rank's contribution - for ii in range(comm.size): - if (all_M[ii] > 0): - tmp = np.reshape(receive_buff[ii, :all_M[ii]], R) - all_values.append(tmp) - - # Concatenate into a single array - all_values = np.concatenate(all_values, axis=0) - return all_values - - -def gather_wrapper(problem, key, ghost_key=''): - """ - Get a global copy of a wrapper as a numpy ndarray on rank 0 - - Args: - problem (pygeosx.Group): GEOSX problem handle - target_key (str): Key for the target wrapper - - Returns: - np.ndarray: The wrapper as a numpy ndarray - """ - return get_wrapper_par(problem, key, ghost_key=ghost_key) - - -def allgather_wrapper(problem, key, ghost_key=''): - """ - Get a global copy of a wrapper as a numpy ndarray on all ranks - - Args: - problem (pygeosx.Group): GEOSX problem handle - target_key (str): Key for the target wrapper - - Returns: - np.ndarray: The wrapper as a numpy ndarray - """ - return get_wrapper_par(problem, key, allgather=True, ghost_key=ghost_key) - - -def get_global_value_range(problem, key): - """ - Get the range of a target value across all processes - - Args: - problem (pygeosx.Group): GEOSX problem handle - target_key (str): Key for the target wrapper - - Returns: - tuple: The global min/max of the target - """ - local_values = get_wrapper(problem, key) - - # 1D arrays will return a scalar, ND arrays an array - N = np.shape(local_values) - local_min = 1e100 - local_max = -1e100 - if (len(N) > 1): - local_min = np.zeros(N[1]) + 1e100 - local_max = np.zeros(N[1]) - 1e100 - - # For >1D arrays, keep the last dimension - query_axis = 0 - if (len(N) > 2): - query_axis = tuple([ii for ii in range(0, len(N) - 1)]) - - # Ignore zero-length results - if len(local_values): - local_min = np.amin(local_values, axis=query_axis) - local_max = np.amax(local_values, axis=query_axis) - - # Gather the results onto rank 0 - all_min = comm.gather(local_min, root=0) - all_max = comm.gather(local_max, root=0) - global_min = 1e100 - global_max = -1e100 - if (rank == 0): - global_min = np.amin(np.array(all_min), axis=0) - global_max = np.amax(np.array(all_max), axis=0) - return global_min, global_max - - -def print_global_value_range(problem, key, header, scale=1.0, precision='%1.4f'): - """ - Print the range of a target value across all processes - - Args: - problem (pygeosx.Group): GEOSX problem handle - target_key (str): Key for the target wrapper - header (str): Header to print with the range - scale (float): Multiply the range with this value before printing (default = 1.0) - precision (str): Format for printing the range (default = %1.4f) - - Returns: - tuple: The global min/max of the target - """ - global_min, global_max = get_global_value_range(problem, key) - global_min *= scale - global_max *= scale - - if (rank == 0): - if isinstance(global_min, np.ndarray): - min_str = ', '.join([precision % (x) for x in global_min]) - max_str = ', '.join([precision % (x) for x in global_max]) - print('%s: min=[%s], max=[%s]' % (header, min_str, max_str)) - else: - min_str = precision % (global_min) - max_str = precision % (global_max) - print('%s: min=%s, max=%s' % (header, min_str, max_str)) - - # Return a copy of the min/max in case we want to use them - return global_min, global_max - - -def set_wrapper_to_value(problem, key, value): - """ - Set the value of a wrapper - - Args: - problem (pygeosx.Group): GEOSX problem handle - target_key (str): Key for the target wrapper - value (float): Value to set the wrapper - """ - local_values = get_wrapper(problem, key, write_flag=True) - local_values[...] = value - - -def set_wrapper_with_function(problem, target_key, input_keys, fn, target_index=-1): - """ - Set the value of a wrapper using a function - - Args: - problem (pygeosx.Group): GEOSX problem handle - target_key (str): Key for the target wrapper - input_keys (str, list): The input key(s) - fn (function): Vectorized function used to calculate target values - target_index (int): Target index to write the output (default = all) - """ - if isinstance(input_keys, str): - input_keys = [input_keys] - local_target = get_wrapper(problem, target_key, write_flag=True) - local_inputs = [get_wrapper(problem, k) for k in input_keys] - - # Run the function, check the shape of outputs/target - fn_output = fn(*local_inputs) - N = np.shape(local_target) - M = np.shape(fn_output) - - if (target_index < 0): - if (N == M): - # Function output, target shapes are the same - local_target[...] = fn_output - - elif (len(M) == 1): - # Apply the function output across each of the target dims - local_target[...] = np.tile(np.expand_dims(fn_output, axis=1), (1, N[1])) - - else: - raise Exception('Shape of function output %s is not compatible with target %s' % (str(M), str(N))) - elif (len(M) == 1): - if (len(N) == 2): - # 2D target, with 1D output applied to a given index - local_target[:, target_index] = fn_output - - else: - # ND target, with 1D output tiled across intermediate indices - expand_axes = tuple([ii for ii in range(1, len(N) - 1)]) - tile_axes = tuple([1] + [ii for ii in N[1:-1]]) - local_target[..., target_index] = np.tile(np.expand_dims(fn_output, axis=expand_axes), tile_axes) - - else: - raise Exception('Shape of function output %s is not compatible with target %s (target axis=%i)' % - (str(M), str(N), target_index)) - - -def search_datastructure_wrappers_recursive(group, filters, matching_paths, level=0, group_path=[]): - """ - Recursively search the group and its children for wrappers that match the filters - - Args: - problem (pygeosx.Group): GEOSX problem handle - filters (list): a list of strings - matching_paths (list): a list of matching values - """ - for wrapper in group.wrappers(): - wrapper_path = str(wrapper).split()[0] - wrapper_test = group_path + [wrapper_path[wrapper_path.rfind('/') + 1:]] - if all(f in wrapper_test for f in filters): - matching_paths.append('/'.join(wrapper_test)) - - for sub_group in group.groups(): - sub_group_name = str(sub_group).split()[0].split('/')[-1] - search_datastructure_wrappers_recursive(sub_group, - filters, - matching_paths, - level=level + 1, - group_path=group_path + [sub_group_name]) - - -def get_matching_wrapper_path(problem, filters): - """ - Recursively search the group and its children for wrappers that match the filters - A successful match is identified if the wrapper path contains all of the - strings in the filter. - For example, if filters=['a', 'b', 'c'], the following could match any of the following: - 'a/b/c', 'c/b/a', 'd/e/c/f/b/a/a' - - Args: - problem (pygeosx.Group): GEOSX problem handle - filters (list): a list of strings - - Returns: - str: Key of the matching wrapper - """ - matching_paths = [] - search_datastructure_wrappers_recursive(problem, filters, matching_paths) - - if (len(matching_paths) == 1): - if (rank == 0): - print('Found matching wrapper: %s' % (matching_paths[0])) - return matching_paths[0] - - else: - if (rank == 0): - print('Error occured while looking for wrappers:') - print('Filters: [%s]' % (', '.join(filters))) - print('Matching wrappers: [%s]' % (', '.join(matching_paths))) - raise Exception('Search resulted in 0 or >1 wrappers mathching filters') - - -def run_queries(problem, records): - """ - Query the current GEOSX datastructure - Note: The expected record request format is as follows. - For now, the only supported query is to find the min/max values of the target - record = {'path/of/wrapper': {'label': 'aperture (m)', # A label to include with plots - 'scale': 1.0, # Value to scale results by - 'history: [], # A list to store values over time - 'fhandle': plt.figure() # A figure handle }} - - Args: - problem (pygeosx.Group): GEOSX problem handle - records (dict): A dict of dicts that specifies the queries to run - """ - for k in records.keys(): - if (k == 'time'): - current_time = get_wrapper(problem, "Events/time") - records[k]['history'].append(current_time * records[k]['scale']) - else: - tmp = print_global_value_range(problem, k, records[k]['label'], scale=records[k]['scale']) - records[k]['history'].append(tmp) - sys.stdout.flush() - - -def plot_history(records, output_root='.', save_figures=True, show_figures=True): - """ - Plot the time-histories for the records structure. - Note: If figures are shown, the GEOSX process will be blocked until they are closed - - Args: - records (dict): A dict of dicts containing the queries - output_root (str): Path to save figures (default = './') - save_figures (bool): Flag to indicate whether figures should be saved (default = True) - show_figures (bool): Flag to indicate whether figures should be drawn (default = False) - """ - if (rank == 0): - for k in records.keys(): - if (k != 'time'): - # Set the active figure - fa = plt.figure(records[k]['fhandle'].number) - - # Assemble values to plot - t = np.array(records['time']['history']) - x = np.array(records[k]['history']) - N = np.shape(x) # (time, min/max, dimensions) - - # Add plots - if (len(N) == 2): - # This is a 1D field - plt.gca().cla() - plt.plot(t, x[:, 0], label='min') - plt.plot(t, x[:, 1], label='max') - plt.xlabel(records['time']['label']) - plt.ylabel(records[k]['label']) - - else: - # This is a 2D field - columns = 2 - rows = int(np.ceil(N[2] / float(columns))) - - # Setup axes - if (('axes' not in records[k]) or (len(fa.axes) == 0)): - records[k]['axes'] = [plt.subplot(rows, columns, ii + 1) for ii in range(0, N[2])] - - for ii in range(0, N[2]): - ax = records[k]['axes'][ii] - ax.cla() - ax.plot(t, x[:, 0, ii], label='min') - ax.plot(t, x[:, 1, ii], label='max') - ax.set_xlabel(records['time']['label']) - ax.set_ylabel('%s (dim=%i)' % (records[k]['label'], ii)) - plt.legend(loc=2) - records[k]['fhandle'].tight_layout(pad=1.5) - - if save_figures: - fname = k[k.rfind('/') + 1:] - plt.savefig('%s/%s.png' % (output_root, fname), format='png') - if show_figures: - plt.show() diff --git a/src/coreComponents/python/modules/pygeosx_tools_package/pyproject.toml b/src/coreComponents/python/modules/pygeosx_tools_package/pyproject.toml deleted file mode 100644 index c2f433afcb5..00000000000 --- a/src/coreComponents/python/modules/pygeosx_tools_package/pyproject.toml +++ /dev/null @@ -1,8 +0,0 @@ -[build-system] -requires = ["setuptools>=42", "wheel"] -build-backend = "setuptools.build_meta" - -[tool.mypy] -python_version = "3.8" -warn_return_any = true -warn_unused_configs = true diff --git a/src/coreComponents/python/modules/pygeosx_tools_package/setup.cfg b/src/coreComponents/python/modules/pygeosx_tools_package/setup.cfg deleted file mode 100644 index 4df92e3dcfd..00000000000 --- a/src/coreComponents/python/modules/pygeosx_tools_package/setup.cfg +++ /dev/null @@ -1,20 +0,0 @@ -[metadata] -name = pygeosx_tools -version = 0.1.0 -description = Tools for interacting with pygeosx -author = Christopher Sherman -author_email = sherman27@llnl.gov -license = LGPL-2.1 - -[options] -packages = - pygeosx_tools -install_requires = - matplotlib - numpy - scipy - mpi4py -python_requires = >=3.6 - -[options.package_data] -pygeosx_tools = py.typed diff --git a/src/coreComponents/python/modules/timehistory_package/pyproject.toml b/src/coreComponents/python/modules/timehistory_package/pyproject.toml deleted file mode 100644 index c2f433afcb5..00000000000 --- a/src/coreComponents/python/modules/timehistory_package/pyproject.toml +++ /dev/null @@ -1,8 +0,0 @@ -[build-system] -requires = ["setuptools>=42", "wheel"] -build-backend = "setuptools.build_meta" - -[tool.mypy] -python_version = "3.8" -warn_return_any = true -warn_unused_configs = true diff --git a/src/coreComponents/python/modules/timehistory_package/setup.cfg b/src/coreComponents/python/modules/timehistory_package/setup.cfg deleted file mode 100644 index e6e34647a51..00000000000 --- a/src/coreComponents/python/modules/timehistory_package/setup.cfg +++ /dev/null @@ -1,16 +0,0 @@ -[metadata] -name = time_history_plotting -version = 0.1.0 -description = Scripts to plot time-series data from GEOSX time-history output files -author = William Tobin -author_email = tobin6@llnl.gov -license = LGPL-2.1 - -[options] -packages = - plot_time_history -install_requires = - matplotlib - hdf5_wrapper - numpy -python_requires = >=3.6 diff --git a/src/coreComponents/python/modules/timehistory_package/timehistory/__init__.py b/src/coreComponents/python/modules/timehistory_package/timehistory/__init__.py deleted file mode 100644 index b288976bbee..00000000000 --- a/src/coreComponents/python/modules/timehistory_package/timehistory/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .plot_time_history import getHistorySeries diff --git a/src/coreComponents/python/modules/timehistory_package/timehistory/plot_time_history.py b/src/coreComponents/python/modules/timehistory_package/timehistory/plot_time_history.py deleted file mode 100644 index 839288cebf9..00000000000 --- a/src/coreComponents/python/modules/timehistory_package/timehistory/plot_time_history.py +++ /dev/null @@ -1,157 +0,0 @@ -import numpy as np -from hdf5_wrapper import hdf5_wrapper as h5w -import matplotlib as mpl -import matplotlib.pyplot as plt -import os -import sys -import argparse - -import re - - -def isiterable(obj): - try: - it = iter(obj) - except TypeError: - return False - return True - - -def getHistorySeries(database, variable, setname, indices=None, components=None): - """ - Retrieve a series of time history structures suitable for plotting in addition to the specific set index and component for the time series - - Args: - database (hdf5_wrapper.hdf5_wrapper): database to retrieve time history data from - variable (str): the name of the time history variable for which to retrieve time-series data - setname (str): the name of the index set as specified in the geosx input xml for which to query time-series data - indices (int, list): the indices in the named set to query for, if None, defaults to all - components (int, list): the components in the flattened data types to retrieve, defaults to all - - Returns: - list: list of (time, data, idx, comp) timeseries tuples for each time history data component - """ - - set_regex = re.compile(variable + '(.*?)', re.IGNORECASE) - if setname is not None: - set_regex = re.compile(variable + '\s*' + str(setname), re.IGNORECASE) - time_regex = re.compile('Time', re.IGNORECASE) # need to make this per-set, thought that was in already? - - set_match = list(filter(set_regex.match, database.keys())) - time_match = list(filter(time_regex.match, database.keys())) - - if len(set_match) == 0: - print(f"Error: can't locate time history data for variable/set described by regex {set_regex.pattern}") - return None - if len(time_match) == 0: - print(f"Error: can't locate time history data for set time variable described by regex {time_regex.pattern}") - return None - - if len(set_match) > 1: - print(f"Warning: variable/set specification matches multiple datasets: {', '.join(set_match)}") - if len(time_match) > 1: - print(f"Warning: set specification matches multiple time datasets: {', '.join(time_match)}") - - set_match = set_match[0] - time_match = time_match[0] - - data_series = database[set_match] - time_series = database[time_match] - - if time_series.shape[0] != data_series.shape[0]: - print( - f"Error: The length of the time-series {time_match} and data-series {set_match} do not match: {time_series.shape} and {data_series.shape} !" - ) - - if indices is not None: - if type(indices) is int: - indices = list(indices) - if isiterable(indices): - oob_idxs = list(filter(lambda idx: not 0 <= idx < data_series.shape[1], indices)) - if len(oob_idxs) > 0: - print(f"Error: The specified indices: ({', '.join(oob_idxs)}) " + "\n\t" + - f" are out of the dataset index range: [0,{data_series.shape[1]})") - indices = list(set(indices) - set(oob_idxs)) - else: - print(f"Error: unsupported indices type: {type(indices)}") - else: - indices = range(data_series.shape[1]) - - if components is not None: - if type(components) is int: - components = list(components) - if isiterable(components): - oob_comps = list(filter(lambda comp: not 0 <= comp < data_series.shape[2], components)) - if len(oob_comps) > 0: - print(f"Error: The specified components: ({', '.join(oob_comps)}) " + "\n\t" + - " is out of the dataset component range: [0,{data_series.shape[1]})") - components = list(set(components) - set(oob_comps)) - else: - print(f"Error: unsupported components type: {type(components)}") - else: - components = range(data_series.shape[2]) - - return [(time_series[:, 0], data_series[:, idx, comp], idx, comp) for idx in indices for comp in components] - - -def commandLinePlotGen(): - parser = argparse.ArgumentParser( - description="A script that parses geosx HDF5 time-history files and produces time-history plots using matplotlib" - ) - parser.add_argument("filename", metavar="history_file", type=str, help="The time history file to parse") - - parser.add_argument("variable", - metavar="variable_name", - type=str, - help="Which time-history variable collected by GEOSX to generate a plot file for.") - - parser.add_argument( - "--sets", - metavar="name", - type=str, - action='append', - default=[None], - nargs="+", - help= - "Which index set of time-history data collected by GEOSX to generate a plot file for, may be specified multiple times with different indices/components for each set." - ) - - parser.add_argument("--indices", - metavar="index", - type=int, - default=[], - nargs="+", - help="An optional list of specific indices in the most-recently specified set.") - - parser.add_argument("--components", - metavar="int", - type=int, - default=[], - nargs="+", - help="An optional list of specific variable components") - - args = parser.parse_args() - result = 0 - - if not os.path.isfile(args.filename): - print(f"Error: file '{args.filename}' not found.") - result = -1 - else: - with h5w(args.filename, mode='r') as database: - for setname in args.sets: - ds = getHistorySeries(database, args.variable, setname, args.indices, args.components) - if ds is None: - result = -1 - break - figname = args.variable + ("_" + setname if setname is not None else "") - fig, ax = plt.subplots() - ax.set_title(figname) - for d in ds: - ax.plot(d[0], d[1]) - fig.savefig(figname + "_history.png") - - return result - - -if __name__ == "__main__": - commandLinePlotGen() diff --git a/src/coreComponents/python/visitMacros/visitVTKConversion.py b/src/coreComponents/python/visitMacros/visitVTKConversion.py deleted file mode 100644 index 8a135d18d5b..00000000000 --- a/src/coreComponents/python/visitMacros/visitVTKConversion.py +++ /dev/null @@ -1,68 +0,0 @@ -""" -visitVTKConversion.py - -VisIt macros designed to convert GEOSX vtk format files containing multiple -regions into separate files, which can then be normally read by the tool. -To install the macros, do the following: - 1) Select Controls/Command from the top menu in VisIt - 2) In the newly opened Commands window, select the Macros tab - 3) Copy the following scripts to the panel and click Update Macros - 4) The macro will appear under the name 'Convert pvd' in the macros window - -If the macros window is not visible, select Controls/Macros from the top menu in VisIt. -When you select 'Convert pvd', visit will attempt to convert any pvd files in the current -directory (where the visit command was run). The macro will create a new set of .vtm files -in the vtk output directory (named '[Region]_*.vtm'). - -For multi-region problems, you can load each set of .vtm files individual. -After adding plots to the interface, you may be prompted to 'Correlate databases'. -If you select 'Yes', VisIt will add a new time slider 'Correlation*' to the interface, which can -be used to change the visualization time, while keeping the datasets syncronized. - -Note: The VisIt python interpreter does not allow empty lines within functions or multi-line arguments -""" - - -def convert_vtm_file(vtm_file, output_dir, root_path): - from xml.etree import ElementTree - import os - vtm_dir = os.path.split(vtm_file)[0] - vtm_header = os.path.split(vtm_file)[1].split('.')[0] - vtm_tree = ElementTree.parse(vtm_file) - vtm_root = vtm_tree.getroot() - multiblock = vtm_root[0] - for block in multiblock: - for child_block in block: - # Setup a new tree and copy parent elements - visit_root = ElementTree.Element(vtm_root.tag, attrib=vtm_root.attrib) - visit_multiblock = ElementTree.Element(multiblock.tag, attrib=multiblock.attrib) - visit_root.append(visit_multiblock) - visit_block = ElementTree.Element(block.tag, attrib=block.attrib) - visit_multiblock.append(visit_block) - for dataset in child_block: - # Copy the dataset, then correct the relative path - visit_block.append(dataset) - dataset_path = os.path.relpath(os.path.join(root_path, vtm_dir, dataset.get('file')), start=output_dir) - dataset.set('file', dataset_path) - visit_tree = ElementTree.ElementTree(element=visit_root) - visit_tree.write(os.path.join(output_dir, '%s_%s.vtm' % (child_block.get('name'), vtm_header))) - - -def user_macro_convert_pvd_files(): - import glob - import os - from xml.etree import ElementTree - print('Converting pvd files in current directory:') - for file in glob.glob('*.pvd'): - print(' ' + file) - pvd_dir = os.path.split(file)[0] - output_dir = file[:file.rfind('.')] - pvd_tree = ElementTree.parse(file) - pvd_root = pvd_tree.getroot() - collection_root = pvd_root[0] - for dataset in collection_root: - convert_vtm_file(dataset.get('file'), output_dir, pvd_dir) - print('Done!') - - -RegisterMacro("Convert pvd", user_macro_convert_pvd_files) diff --git a/src/docs/sphinx/QuickStart.rst b/src/docs/sphinx/QuickStart.rst index 35f709ee90b..111918a8aa8 100644 --- a/src/docs/sphinx/QuickStart.rst +++ b/src/docs/sphinx/QuickStart.rst @@ -89,7 +89,7 @@ The main repository of interest is obviously GEOS itself: `GEOS `_ or the streamlined CMake-based foundation `BTL `_ or the streamlined CMake-based foundation `BLT `_ . These packages are handled as `Git Submodules `_, which provides a transparent way of coordinating multiple code development projects. Most users will never have to worry that these modules are in fact separate projects from GEOS. diff --git a/src/docs/sphinx/advancedExamples/validationStudies/hydraulicFracture/kgdToughnessDominated/Example.rst b/src/docs/sphinx/advancedExamples/validationStudies/hydraulicFracture/kgdToughnessDominated/Example.rst index 4081cb5f501..ff659fda37c 100644 --- a/src/docs/sphinx/advancedExamples/validationStudies/hydraulicFracture/kgdToughnessDominated/Example.rst +++ b/src/docs/sphinx/advancedExamples/validationStudies/hydraulicFracture/kgdToughnessDominated/Example.rst @@ -236,7 +236,7 @@ the HDF5 output is postprocessed and temporal evolution of fracture characterisc 8 2.446e+05 0.0001277 4.5 10 2.411e+05 0.0001409 5 -Note: GEOS python tools ``geosx_xml_tools`` should be installed to run the query script (See :ref:`PythonToolsSetup` for details). +Note: GEOS python tools ``geosx_xml_tools`` should be installed to run the query script (See `Python Tools Setup `_ for details). A good agreement between GEOS results and analytical solutions is shown in the comparison below, which is generated using the visualization script: diff --git a/src/docs/sphinx/advancedExamples/validationStudies/hydraulicFracture/kgdValidation/Example.rst b/src/docs/sphinx/advancedExamples/validationStudies/hydraulicFracture/kgdValidation/Example.rst index 8a4fdf8cfa0..ec4183a473c 100644 --- a/src/docs/sphinx/advancedExamples/validationStudies/hydraulicFracture/kgdValidation/Example.rst +++ b/src/docs/sphinx/advancedExamples/validationStudies/hydraulicFracture/kgdValidation/Example.rst @@ -245,7 +245,7 @@ By running the query script ``kgdValidationQueries.py``, the HDF5 output is post 0.4 1.183e+07 0 0 0 0.0005662 0.5 1.125e+07 0 0 0 0.0005662 -Note: GEOS python tools ``geosx_xml_tools`` should be installed to run the query script (See :ref:`PythonToolsSetup` for details). +Note: GEOS python tools ``geosx_xml_tools`` should be installed to run the query script (See `Python Tools Setup `_ for details). The figure below shows simulation results of the fracture extent at the end of the injection, which is generated using the visualization script ``kgdValidationFigure.py``. The temporal evolution of the fracture characteristics (length, aperture and pressure) from the GEOS simulation are extracted and compared with the experimental data gathered at specific locations. As observed, the time history plots of the modelling predictions (green curves) for the pressure at three gage locations, the fracture length, and the fracture aperture at LVDT location correlate well with the experimental data (blue circles). diff --git a/src/docs/sphinx/advancedExamples/validationStudies/hydraulicFracture/kgdViscosityDominated/Example.rst b/src/docs/sphinx/advancedExamples/validationStudies/hydraulicFracture/kgdViscosityDominated/Example.rst index 34dee2860d6..0d4f26b0df6 100644 --- a/src/docs/sphinx/advancedExamples/validationStudies/hydraulicFracture/kgdViscosityDominated/Example.rst +++ b/src/docs/sphinx/advancedExamples/validationStudies/hydraulicFracture/kgdViscosityDominated/Example.rst @@ -89,7 +89,7 @@ the HDF5 output is postprocessed and temporal evolution of fracture characterisc 8 7.28e+05 0.000209 3 10 6.512e+05 0.000222 3.5 -Note: GEOS python tools ``geosx_xml_tools`` should be installed to run the query script (See :ref:`PythonToolsSetup` for details). +Note: GEOS python tools ``geosx_xml_tools`` should be installed to run the query script (See `Python Tools Setup `_ for details). A good agreement between GEOS results and analytical solutions is shown in the comparison below, which is generated using the visualization script: diff --git a/src/docs/sphinx/advancedExamples/validationStudies/hydraulicFracture/pennyFracToughnessDominated/Example.rst b/src/docs/sphinx/advancedExamples/validationStudies/hydraulicFracture/pennyFracToughnessDominated/Example.rst index 89bf205a6bc..b453eaee88d 100644 --- a/src/docs/sphinx/advancedExamples/validationStudies/hydraulicFracture/pennyFracToughnessDominated/Example.rst +++ b/src/docs/sphinx/advancedExamples/validationStudies/hydraulicFracture/pennyFracToughnessDominated/Example.rst @@ -258,7 +258,7 @@ the HDF5 output is postprocessed and temporal evolution of fracture characterisc 8 6.07e+05 0.0006163 13.73 10 6.32e+05 0.0006827 14.45 -Note: GEOS python tools ``geosx_xml_tools`` should be installed to run the query script (See :ref:`PythonToolsSetup` for details). +Note: GEOS python tools ``geosx_xml_tools`` should be installed to run the query script (See `Python Tools Setup `_ for details). Next, the figure below compares the asymptotic solutions (curves) and the GEOS simulation results (markers) for this analysis, which is generated using the visualization script: diff --git a/src/docs/sphinx/advancedExamples/validationStudies/hydraulicFracture/pennyFracViscosityDominated/Example.rst b/src/docs/sphinx/advancedExamples/validationStudies/hydraulicFracture/pennyFracViscosityDominated/Example.rst index 1b394c51d7c..7f0bbfc9b49 100644 --- a/src/docs/sphinx/advancedExamples/validationStudies/hydraulicFracture/pennyFracViscosityDominated/Example.rst +++ b/src/docs/sphinx/advancedExamples/validationStudies/hydraulicFracture/pennyFracViscosityDominated/Example.rst @@ -263,7 +263,7 @@ the HDF5 output is postprocessed and temporal evolution of fracture characterisc 8 1.005e+06 0.0007918 13.73 10 9.482e+05 0.0008189 15.14 -Note: GEOS python tools ``geosx_xml_tools`` should be installed to run the query script (See :ref:`PythonToolsSetup` for details). +Note: GEOS python tools ``geosx_xml_tools`` should be installed to run the query script (See `Python Tools Setup `_ for details). Next, GEOS simulation results (markers) and asymptotic solutions (curves) for the case with viscosity-storage dominated assumptions are plotted together in the following figure, which is generated using the visualization script: diff --git a/src/docs/sphinx/advancedExamples/validationStudies/hydraulicFracture/pknFracViscosityDominated/Example.rst b/src/docs/sphinx/advancedExamples/validationStudies/hydraulicFracture/pknFracViscosityDominated/Example.rst index b01f651a189..8f4b2b2ef1a 100644 --- a/src/docs/sphinx/advancedExamples/validationStudies/hydraulicFracture/pknFracViscosityDominated/Example.rst +++ b/src/docs/sphinx/advancedExamples/validationStudies/hydraulicFracture/pknFracViscosityDominated/Example.rst @@ -253,7 +253,7 @@ the HDF5 output is postprocessed and temporal evolution of fracture characterisc 8 1.044e+06 0.0008482 12.8 10 1.047e+06 0.0009098 14.8 -Note: GEOS python tools ``geosx_xml_tools`` should be installed to run the query script (See :ref:`PythonToolsSetup` for details). +Note: GEOS python tools ``geosx_xml_tools`` should be installed to run the query script (See `Python Tools Setup `_ for details). Next, figure below shows the comparisons between the results from GEOS simulations (markers) and the corresponding analytical solutions (curves) for the example with viscosity-storage dominated assumptions, which is generated using the visualization script: diff --git a/src/docs/sphinx/pythonTools/geosx_mesh_tools.rst b/src/docs/sphinx/pythonTools/geosx_mesh_tools.rst deleted file mode 100644 index efb90d4c185..00000000000 --- a/src/docs/sphinx/pythonTools/geosx_mesh_tools.rst +++ /dev/null @@ -1,51 +0,0 @@ - -GEOS Mesh Tools --------------------------- - -The `geosx_mesh_tools` python package includes tools for converting meshes from common formats (abaqus, etc.) to those that can be read by GEOS (gmsh, vtk). -See :ref:`PythonToolsSetup` for details on setup instructions, and :ref:`ExternalMeshUsage` for a detailed description of how to use external meshes in GEOS. -The available console scripts for this package and its API are described below. - - -convert_abaqus -^^^^^^^^^^^^^^ - -Compile an xml file with advanced features into a single file that can be read by GEOS. - -.. argparse:: - :module: geosx_mesh_tools.main - :func: build_abaqus_converter_input_parser - :prog: convert_abaqus - - -.. note:: - For vtk format meshes, the user also needs to determine the region ID numbers and names of nodesets to import into GEOS. - The following shows how these could look in an input XML file for a mesh with three regions (*REGIONA*, *REGIONB*, and *REGIONC*) and six nodesets (*xneg*, *xpos*, *yneg*, *ypos*, *zneg*, and *zpos*): - - -.. code-block:: xml - - - - - - - - - - - - -API -^^^ - -.. automodule:: geosx_mesh_tools.abaqus_converter - :members: diff --git a/src/docs/sphinx/pythonTools/geosx_xml_tools.rst b/src/docs/sphinx/pythonTools/geosx_xml_tools.rst deleted file mode 100644 index 92be5ad6df0..00000000000 --- a/src/docs/sphinx/pythonTools/geosx_xml_tools.rst +++ /dev/null @@ -1,82 +0,0 @@ - -.. _XMLToolsPackage: - -GEOS XML Tools --------------------------- - -The `geosx_xml_tools` python package adds a set of advanced features to the GEOS xml format: units, parameters, and symbolic expressions. -See :ref:`PythonToolsSetup` for details on setup instructions, and :ref:`AdvancedXMLFeatures` for a detailed description of the input format. -The available console scripts for this package and its API are described below. - - -convert_abaqus -^^^^^^^^^^^^^^ - -Convert an abaqus format mesh file to gmsh or vtk format. - -.. argparse:: - :module: geosx_xml_tools.command_line_parsers - :func: build_preprocessor_input_parser - :prog: preprocess_xml - - -format_xml -^^^^^^^^^^^^^^ - -Formats an xml file. - -.. argparse:: - :module: geosx_xml_tools.command_line_parsers - :func: build_xml_formatter_input_parser - :prog: format_xml - - -check_xml_attribute_coverage -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Checks xml attribute coverage for files in the GEOS repository. - -.. argparse:: - :module: geosx_xml_tools.command_line_parsers - :func: build_attribute_coverage_input_parser - :prog: check_xml_attribute_coverage - - -check_xml_redundancy -^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Checks for redundant attribute definitions in an xml file, such as those that duplicate the default value. - -.. argparse:: - :module: geosx_xml_tools.command_line_parsers - :func: build_xml_redundancy_input_parser - :prog: check_xml_redundancy - - -API -^^^ - -.. automodule:: geosx_xml_tools.main - :members: - -.. automodule:: geosx_xml_tools.xml_processor - :members: - -.. automodule:: geosx_xml_tools.xml_formatter - :members: - -.. automodule:: geosx_xml_tools.unit_manager - :members: - -.. automodule:: geosx_xml_tools.regex_tools - :members: - -.. automodule:: geosx_xml_tools.xml_redundancy_check - :members: - -.. automodule:: geosx_xml_tools.attribute_coverage - :members: - -.. automodule:: geosx_xml_tools.table_generator - :members: - diff --git a/src/docs/sphinx/pythonTools/hdf5_wrapper.rst b/src/docs/sphinx/pythonTools/hdf5_wrapper.rst deleted file mode 100644 index 01f1ad9fe4f..00000000000 --- a/src/docs/sphinx/pythonTools/hdf5_wrapper.rst +++ /dev/null @@ -1,60 +0,0 @@ - -HDF5 Wrapper --------------------------- - -The `hdf5_wrapper` python package adds a wrapper to `h5py` that greatly simplifies reading/writing to/from hdf5-format files. - - -Usage -^^^^^^^ - -Once loaded, the contents of a file can be navigated in the same way as a native python dictionary. - -.. code-block:: python - - import hdf5_wrapper - - data = hdf5_wrapper.hdf5_wrapper('data.hdf5') - - test = data['test'] - for k, v in data.items(): - print('key: %s, value: %s' % (k, str(v))) - - -If the user indicates that a file should be opened in write-mode (`w`) or read/write-mode (`a`), then the file can be created or modified. -Note: for these changes to be written to the disk, the wrapper may need to be closed or deleted. - -.. code-block:: python - - import hdf5_wrapper - import numpy as np - - data = hdf5_wrapper.hdf5_wrapper('data.hdf5', mode='w') - data['string'] = 'string' - data['integer'] = 123 - data['array'] = np.random.randn(3, 4, 5) - data['child'] = {'float': 1.234} - - -Existing dictionaries can be placed on the current level: - -.. code-block:: python - - existing_dict = {'some': 'value'} - data.insert(existing_dict) - - -And external hdf5 format files can be linked together: - -.. code-block:: python - - for k in ['child_a', 'child_b']: - data.link(k, '%s.hdf5' % (k)) - - - -API -^^^^^ - -.. automodule:: hdf5_wrapper.wrapper - :members: diff --git a/src/docs/sphinx/pythonTools/mesh_doctor.rst b/src/docs/sphinx/pythonTools/mesh_doctor.rst deleted file mode 100644 index d9de402e15f..00000000000 --- a/src/docs/sphinx/pythonTools/mesh_doctor.rst +++ /dev/null @@ -1,124 +0,0 @@ -``mesh_doctor`` ---------------- - -``mesh_doctor`` is a ``python`` executable that can be used through the command line to perform various checks, validations, and tiny fixes to the ``vtk`` mesh that are meant to be used in ``geos``. -``mesh_doctor`` is organized as a collection of modules with their dedicated sets of options. -The current page will introduce those modules, but the details and all the arguments can be retrieved by using the ``--help`` option for each module. - -Modules -^^^^^^^ - -To list all the modules available through ``mesh_doctor``, you can simply use the ``--help`` option, which will list all available modules as well as a quick summary. - -.. command-output:: python mesh_doctor.py --help - :cwd: ../../../coreComponents/python/modules/geosx_mesh_doctor - -Then, if you are interested in a specific module, you can ask for its documentation using the ``mesh_doctor module_name --help`` pattern. -For example - -.. command-output:: python mesh_doctor.py collocated_nodes --help - :cwd: ../../../coreComponents/python/modules/geosx_mesh_doctor - -``mesh_doctor`` loads its module dynamically. -If a module can't be loaded, ``mesh_doctor`` will proceed and try to load other modules. -If you see a message like - -.. code-block:: bash - - [1970-04-14 03:07:15,625][WARNING] Could not load module "collocated_nodes": No module named 'vtkmodules' - -then most likely ``mesh_doctor`` could not load the ``collocated_nodes`` module, because the ``vtk`` python package was not found. -Thereafter, the documentation for module ``collocated_nodes`` will not be displayed. -You can solve this issue by installing the dependencies of ``mesh_doctor`` defined in its ``requirements.txt`` file (``python -m pip install -r requirements.txt``). - -Here is a list and brief description of all the modules available. - -``collocated_nodes`` -"""""""""""""""""""" - -Displays the neighboring nodes that are closer to each other than a prescribed threshold. -It is not uncommon to define multiple nodes for the exact same position, which will typically be an issue for ``geos`` and should be fixed. - -.. command-output:: python mesh_doctor.py collocated_nodes --help - :cwd: ../../../coreComponents/python/modules/geosx_mesh_doctor - -``element_volumes`` -""""""""""""""""""" - -Computes the volumes of all the cells and displays the ones that are below a prescribed threshold. -Cells with negative volumes will typically be an issue for ``geos`` and should be fixed. - -.. command-output:: python mesh_doctor.py element_volumes --help - :cwd: ../../../coreComponents/python/modules/geosx_mesh_doctor - -``fix_elements_orderings`` -"""""""""""""""""""""""""" - -It sometimes happens that an exported mesh does not abide by the ``vtk`` orderings. -The ``fix_elements_orderings`` module can rearrange the nodes of given types of elements. -This can be convenient if you cannot regenerate the mesh. - -.. command-output:: python mesh_doctor.py fix_elements_orderings --help - :cwd: ../../../coreComponents/python/modules/geosx_mesh_doctor - -``generate_cube`` -""""""""""""""""" - -This module conveniently generates cubic meshes in ``vtk``. -It can also generate fields with simple values. -This tool can also be useful to generate a trial mesh that will later be refined or customized. - -.. command-output:: python mesh_doctor.py generate_cube --help - :cwd: ../../../coreComponents/python/modules/geosx_mesh_doctor - -``generate_fractures`` -"""""""""""""""""""""" - -For a conformal fracture to be defined in a mesh, ``geos`` requires the mesh to be split at the faces where the fracture gets across the mesh. -The ``generate_fractures`` module will split the mesh and generate the multi-block ``vtk`` files. - -.. command-output:: python mesh_doctor.py generate_fractures --help - :cwd: ../../../coreComponents/python/modules/geosx_mesh_doctor - -``generate_global_ids`` -""""""""""""""""""""""" - -When running ``geos`` in parallel, `global ids` can be used to refer to data across multiple ranks. -The ``generate_global_ids`` can generate `global ids` for the imported ``vtk`` mesh. - -.. command-output:: python mesh_doctor.py generate_global_ids --help - :cwd: ../../../coreComponents/python/modules/geosx_mesh_doctor - -``non_conformal`` -""""""""""""""""" - -This module will detect elements which are close enough (there's a user defined threshold) but which are not in front of each other (another threshold can be defined). -`Close enough` can be defined in terms or proximity of the nodes and faces of the elements. -The angle between two faces can also be precribed. -This module can be a bit time consuming. - -.. command-output:: python mesh_doctor.py non_conformal --help - :cwd: ../../../coreComponents/python/modules/geosx_mesh_doctor - -``self_intersecting_elements`` -"""""""""""""""""""""""""""""" - -Some meshes can have cells that auto-intersect. -This module will display the elements that have faces intersecting. - -.. command-output:: python mesh_doctor.py self_intersecting_elements --help - :cwd: ../../../coreComponents/python/modules/geosx_mesh_doctor - -``supported_elements`` -"""""""""""""""""""""" - -``geos`` supports a specific set of elements. -Let's cite the standard elements like `tetrahedra`, `wedges`, `pyramids` or `hexahedra`. -But also prismes up to 11 faces. -``geos`` also supports the generic ``VTK_POLYHEDRON``/``42`` elements, which are converted on the fly into one of the elements just described. - -The ``supported_elements`` check will validate that no unsupported element is included in the input mesh. -It will also verify that the ``VTK_POLYHEDRON`` cells can effectively get converted into a supported type of element. - -.. command-output:: python mesh_doctor.py supported_elements --help - :cwd: ../../../coreComponents/python/modules/geosx_mesh_doctor diff --git a/src/docs/sphinx/pythonTools/pygeosx_tools.rst b/src/docs/sphinx/pythonTools/pygeosx_tools.rst deleted file mode 100644 index 9da66b74361..00000000000 --- a/src/docs/sphinx/pythonTools/pygeosx_tools.rst +++ /dev/null @@ -1,24 +0,0 @@ - -PyGEOSX Tools --------------------------- - -The `pygeosx_tools` python package adds a variety of tools for working with pygeosx objects. -These include common operations such as setting the value of geosx wrappers with python functions, parallel communication, and file IO. -Examples using these tools can be found here: :ref:`pygeosxExamples`. - - -API -^^^^^ - -.. automodule:: pygeosx_tools.wrapper - :members: - -.. automodule:: pygeosx_tools.file_io - :members: - -.. automodule:: pygeosx_tools.mesh_interpolation - :members: - -.. automodule:: pygeosx_tools.well_log - :members: - diff --git a/src/docs/sphinx/pythonTools/pythonAPI.rst b/src/docs/sphinx/pythonTools/pythonAPI.rst deleted file mode 100644 index b5300e8f607..00000000000 --- a/src/docs/sphinx/pythonTools/pythonAPI.rst +++ /dev/null @@ -1,54 +0,0 @@ - -Python Tools -========================== - - -.. _PythonToolsSetup: - -Python Tools Setup ---------------------------------- - -The preferred method to setup the GEOSX python tools is to run the following command in the build directory: - -.. code-block:: bash - - make geosx_python_tools - - -This will attempt to install the required packages into the python distribution indicated via the `Python3_EXECUTABLE` cmake variable (also used by pygeosx). - -If the user does not have write access for the target python distribution, the installation will attempt to create a new virtual python environment (Note: this requires that the virtualenv package be installed). -If any package dependencies are missing, then the install script will attempt to fetch them from the internet using pip. -After installation, these packages will be available for import within the associated python distribution, and a set of console scripts will be available within the GEOSX build bin directory. - -Alternatively, these packages can be installed manually into a python environment using pip: - -.. code-block:: bash - - cd GEOSX/src/coreComponents/python/modules/geosx_mesh_tools_package - pip install --upgrade . - - cd ../geosx_xml_tools_package - pip install --upgrade . - - # Etc. - - -Packages ------------------------ - - -.. toctree:: - :maxdepth: 1 - - hdf5_wrapper - - geosx_mesh_tools - - geosx_xml_tools - - pygeosx_tools - - timehistory - - mesh_doctor diff --git a/src/docs/sphinx/pythonTools/timehistory.rst b/src/docs/sphinx/pythonTools/timehistory.rst deleted file mode 100644 index cf3ac46eb8e..00000000000 --- a/src/docs/sphinx/pythonTools/timehistory.rst +++ /dev/null @@ -1,6 +0,0 @@ - -Time History Tools --------------------------- - -.. automodule:: timehistory.plot_time_history - :members: diff --git a/src/docs/sphinx/requirements.txt b/src/docs/sphinx/requirements.txt index 0fb4f2f3c11..7ddae753d66 100644 --- a/src/docs/sphinx/requirements.txt +++ b/src/docs/sphinx/requirements.txt @@ -5,14 +5,9 @@ h5py mpmath docutils>=0.18 pandas -# using plantuml for diagrams Sphinx>=7.0.0 -# pydata-sphinx-theme sphinx_rtd_theme sphinxcontrib-plantuml sphinx-argparse sphinx-design -# Running CLI programs and capture outputs sphinxcontrib-programoutput>=0.17 -# Installing the mesh_doctor requirements to be able to load all the modules and run the help. --r ../../coreComponents/python/modules/geosx_mesh_doctor/requirements.txt \ No newline at end of file diff --git a/src/docs/sphinx/userGuide/Index.rst b/src/docs/sphinx/userGuide/Index.rst index 46a76ccab80..1e7330a3f13 100644 --- a/src/docs/sphinx/userGuide/Index.rst +++ b/src/docs/sphinx/userGuide/Index.rst @@ -1,3 +1,5 @@ +.. _UserGuide: + ############################################################################### User Guide ############################################################################### diff --git a/src/index.rst b/src/index.rst index 6fe0dd5862e..9dedc1f4d75 100644 --- a/src/index.rst +++ b/src/index.rst @@ -94,6 +94,38 @@ you have suggestions for improving the guides below, please post an issue on our To the Advanced Examples + .. grid-item-card:: + + User Guide + ^^^^^^^^^^^^^^^^^^^ + + Detailed instructions on how to construct input files, configure problems, manage outputs, etc. + + +++ + + .. button-ref:: UserGuide + :expand: + :color: info + :click-parent: + + To the User Guide + + .. grid-item-card:: + + Python Tools + ^^^^^^^^^^^^^^^^^^^ + + Documentation for the python packages distributed alongside GEOS used to manage xml files, condition numerical meshes, read outputs, etc. + + +++ + + .. button-link:: https://geosx-geosx.readthedocs-hosted.com/projects/geosx-geospythonpackages/en/latest/ + :expand: + :color: info + :click-parent: + + To the Python Tools Documentation + ******************** Table of Contents @@ -116,8 +148,6 @@ Table of Contents docs/sphinx/Doxygen - docs/sphinx/pythonTools/pythonAPI - docs/sphinx/buildGuide/Index docs/sphinx/CompleteXMLSchema From 5dfbb3afc1e8963a08fb0f5f92bd794443ecbbc5 Mon Sep 17 00:00:00 2001 From: Pavel Tomin Date: Thu, 18 Jan 2024 16:52:34 -0600 Subject: [PATCH 34/40] apply min comp dens treatment for initial conditions (#2938) --- .../fluidFlow/CompositionalMultiphaseBase.cpp | 6 +++++- .../fluidFlow/CompositionalMultiphaseBase.hpp | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseBase.cpp b/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseBase.cpp index b841c70edb1..6db5fe77bfa 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseBase.cpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseBase.cpp @@ -742,6 +742,7 @@ real64 CompositionalMultiphaseBase::updateFluidState( ObjectManagerBase & subReg } void CompositionalMultiphaseBase::initializeFluidState( MeshLevel & mesh, + DomainPartition & domain, arrayView1d< string const > const & regionNames ) { GEOS_MARK_FUNCTION; @@ -780,6 +781,9 @@ void CompositionalMultiphaseBase::initializeFluidState( MeshLevel & mesh, } ); + // with initial component densities defined - check if they need to be corrected to avoid zero diags etc + chopNegativeDensities( domain ); + // for some reason CUDA does not want the host_device lambda to be defined inside the generic lambda // I need the exact type of the subRegion for updateSolidflowProperties to work well. mesh.getElemManager().forElementSubRegions< CellElementSubRegion, @@ -1211,7 +1215,7 @@ void CompositionalMultiphaseBase::initializePostInitialConditionsPreSubGroups() } ); // Initialize primary variables from applied initial conditions - initializeFluidState( mesh, regionNames ); + initializeFluidState( mesh, domain, regionNames ); mesh.getElemManager().forElementRegions< SurfaceElementRegion >( regionNames, [&]( localIndex const, diff --git a/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseBase.hpp b/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseBase.hpp index e69cb487fff..5491db5e2d4 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseBase.hpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseBase.hpp @@ -267,7 +267,7 @@ class CompositionalMultiphaseBase : public FlowSolverBase * from prescribed intermediate values (i.e. global densities from global fractions) * and any applicable hydrostatic equilibration of the domain */ - void initializeFluidState( MeshLevel & mesh, arrayView1d< string const > const & regionNames ); + void initializeFluidState( MeshLevel & mesh, DomainPartition & domain, arrayView1d< string const > const & regionNames ); /** * @brief Compute the hydrostatic equilibrium using the compositions and temperature input tables From 74ef8d31c421b6c9af94ac6108b653a2bf5dddcf Mon Sep 17 00:00:00 2001 From: MelReyCG <122801580+MelReyCG@users.noreply.github.com> Date: Fri, 19 Jan 2024 01:11:16 +0100 Subject: [PATCH 35/40] More single phase outputs (#2904) * added region mass stats * added region temperature stats * Warning instead when statistics could not be computed --- .../fluidFlow/SinglePhaseBaseKernels.hpp | 23 +++++++- .../fluidFlow/SinglePhaseStatistics.cpp | 56 +++++++++++++++++-- .../fluidFlow/SinglePhaseStatistics.hpp | 10 ++++ 3 files changed, 83 insertions(+), 6 deletions(-) diff --git a/src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseBaseKernels.hpp b/src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseBaseKernels.hpp index c275f3a6781..1a53a221166 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseBaseKernels.hpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseBaseKernels.hpp @@ -674,15 +674,21 @@ struct StatisticsKernel arrayView1d< real64 const > const & volume, arrayView1d< real64 const > const & pres, arrayView1d< real64 const > const & deltaPres, + arrayView1d< real64 const > const & temp, arrayView1d< real64 const > const & refPorosity, arrayView2d< real64 const > const & porosity, + arrayView2d< real64 const > const & density, real64 & minPres, real64 & avgPresNumerator, real64 & maxPres, real64 & minDeltaPres, real64 & maxDeltaPres, + real64 & minTemp, + real64 & avgTempNumerator, + real64 & maxTemp, real64 & totalUncompactedPoreVol, - real64 & totalPoreVol ) + real64 & totalPoreVol, + real64 & totalMass ) { RAJA::ReduceMin< parallelDeviceReduce, real64 > subRegionMinPres( LvArray::NumericLimits< real64 >::max ); RAJA::ReduceSum< parallelDeviceReduce, real64 > subRegionAvgPresNumerator( 0.0 ); @@ -691,8 +697,13 @@ struct StatisticsKernel RAJA::ReduceMin< parallelDeviceReduce, real64 > subRegionMinDeltaPres( LvArray::NumericLimits< real64 >::max ); RAJA::ReduceMax< parallelDeviceReduce, real64 > subRegionMaxDeltaPres( -LvArray::NumericLimits< real64 >::max ); + RAJA::ReduceMin< parallelDeviceReduce, real64 > subRegionMinTemp( LvArray::NumericLimits< real64 >::max ); + RAJA::ReduceSum< parallelDeviceReduce, real64 > subRegionAvgTempNumerator( 0.0 ); + RAJA::ReduceMax< parallelDeviceReduce, real64 > subRegionMaxTemp( -LvArray::NumericLimits< real64 >::max ); + RAJA::ReduceSum< parallelDeviceReduce, real64 > subRegionTotalUncompactedPoreVol( 0.0 ); RAJA::ReduceSum< parallelDeviceReduce, real64 > subRegionTotalPoreVol( 0.0 ); + RAJA::ReduceSum< parallelDeviceReduce, real64 > subRegionTotalMass( 0.0 ); forAll< parallelDevicePolicy<> >( size, [=] GEOS_HOST_DEVICE ( localIndex const ei ) { @@ -712,8 +723,13 @@ struct StatisticsKernel subRegionMinDeltaPres.min( deltaPres[ei] ); subRegionMaxDeltaPres.max( deltaPres[ei] ); + subRegionMinTemp.min( temp[ei] ); + subRegionAvgTempNumerator += uncompactedPoreVol * temp[ei]; + subRegionMaxTemp.max( temp[ei] ); + subRegionTotalUncompactedPoreVol += uncompactedPoreVol; subRegionTotalPoreVol += dynamicPoreVol; + subRegionTotalMass += dynamicPoreVol * density[ei][0]; } ); minPres = subRegionMinPres.get(); @@ -723,8 +739,13 @@ struct StatisticsKernel minDeltaPres = subRegionMinDeltaPres.get(); maxDeltaPres = subRegionMaxDeltaPres.get(); + minTemp = subRegionMinTemp.get(); + avgTempNumerator = subRegionAvgTempNumerator.get(); + maxTemp = subRegionMaxTemp.get(); + totalUncompactedPoreVol = subRegionTotalUncompactedPoreVol.get(); totalPoreVol = subRegionTotalPoreVol.get(); + totalMass = subRegionTotalMass.get(); } }; diff --git a/src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseStatistics.cpp b/src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseStatistics.cpp index e8a0bcd17dd..aa905f08310 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseStatistics.cpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseStatistics.cpp @@ -98,8 +98,13 @@ void SinglePhaseStatistics::computeRegionStatistics( MeshLevel & mesh, regionStatistics.maxDeltaPressure = -LvArray::NumericLimits< real64 >::max; regionStatistics.minDeltaPressure = LvArray::NumericLimits< real64 >::max; + regionStatistics.averageTemperature = 0.0; + regionStatistics.maxTemperature = -LvArray::NumericLimits< real64 >::max; + regionStatistics.minTemperature = LvArray::NumericLimits< real64 >::max; + regionStatistics.totalPoreVolume = 0.0; regionStatistics.totalUncompactedPoreVolume = 0.0; + regionStatistics.totalMass = 0.0; } // Step 2: increment the average/min/max quantities for all the subRegions @@ -111,6 +116,7 @@ void SinglePhaseStatistics::computeRegionStatistics( MeshLevel & mesh, arrayView1d< real64 const > const volume = subRegion.getElementVolume(); arrayView1d< real64 const > const pres = subRegion.getField< fields::flow::pressure >(); arrayView1d< real64 const > const deltaPres = subRegion.getField< fields::flow::deltaPressure >(); + arrayView1d< real64 const > const temp = subRegion.getField< fields::flow::temperature >(); string const & solidName = subRegion.getReference< string >( SinglePhaseBase::viewKeyStruct::solidNamesString() ); Group const & constitutiveModels = subRegion.getGroup( ElementSubRegionBase::groupKeyStruct::constitutiveModelsString() ); @@ -118,13 +124,21 @@ void SinglePhaseStatistics::computeRegionStatistics( MeshLevel & mesh, arrayView1d< real64 const > const refPorosity = solid.getReferencePorosity(); arrayView2d< real64 const > const porosity = solid.getPorosity(); + string const & fluidName = subRegion.template getReference< string >( FlowSolverBase::viewKeyStruct::fluidNamesString() ); + SingleFluidBase const & fluid = constitutiveModels.getGroup< SingleFluidBase >( fluidName ); + arrayView2d< real64 const > const densities = fluid.density(); + real64 subRegionAvgPresNumerator = 0.0; real64 subRegionMinPres = 0.0; real64 subRegionMaxPres = 0.0; real64 subRegionMinDeltaPres = 0.0; real64 subRegionMaxDeltaPres = 0.0; + real64 subRegionAvgTempNumerator = 0.0; + real64 subRegionMinTemp = 0.0; + real64 subRegionMaxTemp = 0.0; real64 subRegionTotalUncompactedPoreVol = 0.0; real64 subRegionTotalPoreVol = 0.0; + real64 subRegionTotalMass = 0.0; singlePhaseBaseKernels::StatisticsKernel:: launch( subRegion.size(), @@ -132,15 +146,21 @@ void SinglePhaseStatistics::computeRegionStatistics( MeshLevel & mesh, volume, pres, deltaPres, + temp, refPorosity, porosity, + densities, subRegionMinPres, subRegionAvgPresNumerator, subRegionMaxPres, subRegionMinDeltaPres, subRegionMaxDeltaPres, + subRegionMinTemp, + subRegionAvgTempNumerator, + subRegionMaxTemp, subRegionTotalUncompactedPoreVol, - subRegionTotalPoreVol ); + subRegionTotalPoreVol, + subRegionTotalMass ); ElementRegionBase & region = elemManager.getRegion( subRegion.getParent().getParent().getName() ); RegionStatistics & regionStatistics = region.getReference< RegionStatistics >( viewKeyStruct::regionStatisticsString() ); @@ -164,9 +184,19 @@ void SinglePhaseStatistics::computeRegionStatistics( MeshLevel & mesh, regionStatistics.maxDeltaPressure = subRegionMaxDeltaPres; } + regionStatistics.averageTemperature += subRegionAvgTempNumerator; + if( subRegionMinTemp < regionStatistics.minTemperature ) + { + regionStatistics.minTemperature = subRegionMinTemp; + } + if( subRegionMaxTemp > regionStatistics.maxTemperature ) + { + regionStatistics.maxTemperature = subRegionMaxTemp; + } regionStatistics.totalUncompactedPoreVolume += subRegionTotalUncompactedPoreVol; regionStatistics.totalPoreVolume += subRegionTotalPoreVol; + regionStatistics.totalMass += subRegionTotalMass; } ); // Step 3: synchronize the results over the MPI ranks @@ -176,21 +206,32 @@ void SinglePhaseStatistics::computeRegionStatistics( MeshLevel & mesh, RegionStatistics & regionStatistics = region.getReference< RegionStatistics >( viewKeyStruct::regionStatisticsString() ); regionStatistics.minPressure = MpiWrapper::min( regionStatistics.minPressure ); + regionStatistics.averagePressure = MpiWrapper::sum( regionStatistics.averagePressure ); regionStatistics.maxPressure = MpiWrapper::max( regionStatistics.maxPressure ); + regionStatistics.minDeltaPressure = MpiWrapper::min( regionStatistics.minDeltaPressure ); regionStatistics.maxDeltaPressure = MpiWrapper::max( regionStatistics.maxDeltaPressure ); + + regionStatistics.minTemperature = MpiWrapper::min( regionStatistics.minTemperature ); + regionStatistics.averageTemperature = MpiWrapper::sum( regionStatistics.averageTemperature ); + regionStatistics.maxTemperature = MpiWrapper::max( regionStatistics.maxTemperature ); + regionStatistics.totalUncompactedPoreVolume = MpiWrapper::sum( regionStatistics.totalUncompactedPoreVolume ); regionStatistics.totalPoreVolume = MpiWrapper::sum( regionStatistics.totalPoreVolume ); - regionStatistics.averagePressure = MpiWrapper::sum( regionStatistics.averagePressure ); + regionStatistics.totalMass = MpiWrapper::sum( regionStatistics.totalMass ); + if( regionStatistics.totalUncompactedPoreVolume > 0 ) { - regionStatistics.averagePressure /= regionStatistics.totalUncompactedPoreVolume; + float invTotalUncompactedPoreVolume = 1.0 / regionStatistics.totalUncompactedPoreVolume; + regionStatistics.averagePressure *= invTotalUncompactedPoreVolume; + regionStatistics.averageTemperature *= invTotalUncompactedPoreVolume; } else { regionStatistics.averagePressure = 0.0; - GEOS_LOG_LEVEL_RANK_0( 1, getName() << ", " << regionNames[i] - << ": Cannot compute average pressure because region pore volume is zero." ); + regionStatistics.averageTemperature = 0.0; + GEOS_WARNING( getName() << ", " << regionNames[i] << + ": Cannot compute average pressure, temperature and total mass because region pore volume is zero." ); } GEOS_LOG_LEVEL_RANK_0( 1, getName() << ", " << regionNames[i] @@ -199,8 +240,13 @@ void SinglePhaseStatistics::computeRegionStatistics( MeshLevel & mesh, GEOS_LOG_LEVEL_RANK_0( 1, getName() << ", " << regionNames[i] << ": Delta pressure (min, max): " << regionStatistics.minDeltaPressure << ", " << regionStatistics.maxDeltaPressure << " Pa" ); + GEOS_LOG_LEVEL_RANK_0( 1, getName() << ", " << regionNames[i] + << ": Temperature (min, average, max): " + << regionStatistics.minTemperature << ", " << regionStatistics.averageTemperature << ", " << regionStatistics.maxTemperature << " K" ); GEOS_LOG_LEVEL_RANK_0( 1, getName() << ", " << regionNames[i] << ": Total dynamic pore volume: " << regionStatistics.totalPoreVolume << " rm^3" ); + GEOS_LOG_LEVEL_RANK_0( 1, getName() << ", " << regionNames[i] + << ": Total fluid mass: " << regionStatistics.totalMass << " kg" ); } } diff --git a/src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseStatistics.hpp b/src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseStatistics.hpp index 04c0bc28ff7..8d826c5034e 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseStatistics.hpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseStatistics.hpp @@ -89,6 +89,16 @@ class SinglePhaseStatistics : public FieldStatisticsBase< SinglePhaseBase > /// maximum region delta pressure real64 maxDeltaPressure; + // fluid mass + real64 totalMass; + + /// average region temperature + real64 averageTemperature; + /// minimum region temperature + real64 minTemperature; + /// maximum region temperature + real64 maxTemperature; + /// total region pore volume real64 totalPoreVolume; /// total region uncompacted pore volume From 6cce14a78c64ff2f345ccd47565111d25492f51f Mon Sep 17 00:00:00 2001 From: Matteo Cusini <49037133+CusiniM@users.noreply.github.com> Date: Fri, 19 Jan 2024 10:58:30 -0800 Subject: [PATCH 36/40] Poroelastic frac propagation criterion: use total stress to compute force balance. (#2706) --- ...kgdViscosityDominated_poroelastic_base.xml | 3 +- ...ughnessDominated_poroelastic_benchmark.xml | 3 +- ...edToughnessDominated_poroelastic_smoke.xml | 3 +- ...edViscosityDominated_poroelastic_smoke.xml | 3 +- ...knViscosityDominated_poroelastic_smoke.xml | 5 +- integratedTests | 2 +- .../physicsSolvers/CMakeLists.txt | 2 + .../multiphysics/PoromechanicsSolver.hpp | 15 + .../surfaceGeneration/SurfaceGenerator.cpp | 499 +++++++++--------- .../surfaceGeneration/SurfaceGenerator.hpp | 6 +- .../kernels/surfaceGenerationKernels.hpp | 182 +++++++ .../surfaceGenerationKernelsHelpers.hpp | 62 +++ 12 files changed, 530 insertions(+), 255 deletions(-) create mode 100644 src/coreComponents/physicsSolvers/surfaceGeneration/kernels/surfaceGenerationKernels.hpp create mode 100644 src/coreComponents/physicsSolvers/surfaceGeneration/kernels/surfaceGenerationKernelsHelpers.hpp diff --git a/inputFiles/hydraulicFracturing/kgdViscosityDominated_poroelastic_base.xml b/inputFiles/hydraulicFracturing/kgdViscosityDominated_poroelastic_base.xml index 85e24c5015f..76c2d059736 100644 --- a/inputFiles/hydraulicFracturing/kgdViscosityDominated_poroelastic_base.xml +++ b/inputFiles/hydraulicFracturing/kgdViscosityDominated_poroelastic_base.xml @@ -39,7 +39,8 @@ targetRegions="{ Domain }" nodeBasedSIF="1" rockToughness="1e4" - mpiCommOrder="1"/> + mpiCommOrder="1" + isPoroelastic="1"/> diff --git a/inputFiles/hydraulicFracturing/pennyShapedToughnessDominated_poroelastic_benchmark.xml b/inputFiles/hydraulicFracturing/pennyShapedToughnessDominated_poroelastic_benchmark.xml index 166fa20cf2e..dff5dd1605c 100644 --- a/inputFiles/hydraulicFracturing/pennyShapedToughnessDominated_poroelastic_benchmark.xml +++ b/inputFiles/hydraulicFracturing/pennyShapedToughnessDominated_poroelastic_benchmark.xml @@ -69,7 +69,8 @@ targetRegions="{ Domain }" nodeBasedSIF="1" rockToughness="3.0e6" - mpiCommOrder="1"/> + mpiCommOrder="1" + isPoroelastic="1"/> diff --git a/inputFiles/hydraulicFracturing/pennyShapedToughnessDominated_poroelastic_smoke.xml b/inputFiles/hydraulicFracturing/pennyShapedToughnessDominated_poroelastic_smoke.xml index 9959607e44a..bab9396dd71 100644 --- a/inputFiles/hydraulicFracturing/pennyShapedToughnessDominated_poroelastic_smoke.xml +++ b/inputFiles/hydraulicFracturing/pennyShapedToughnessDominated_poroelastic_smoke.xml @@ -44,7 +44,8 @@ targetRegions="{ Domain }" nodeBasedSIF="1" rockToughness="3.0e6" - mpiCommOrder="1"/> + mpiCommOrder="1" + isPoroelastic="1"/> diff --git a/inputFiles/hydraulicFracturing/pennyShapedViscosityDominated_poroelastic_smoke.xml b/inputFiles/hydraulicFracturing/pennyShapedViscosityDominated_poroelastic_smoke.xml index a330b0c1102..f162d0da562 100644 --- a/inputFiles/hydraulicFracturing/pennyShapedViscosityDominated_poroelastic_smoke.xml +++ b/inputFiles/hydraulicFracturing/pennyShapedViscosityDominated_poroelastic_smoke.xml @@ -44,7 +44,8 @@ targetRegions="{ Domain }" nodeBasedSIF="1" rockToughness="0.3e6" - mpiCommOrder="1"/> + mpiCommOrder="1" + isPoroelastic="1"/> diff --git a/inputFiles/hydraulicFracturing/pknViscosityDominated_poroelastic_smoke.xml b/inputFiles/hydraulicFracturing/pknViscosityDominated_poroelastic_smoke.xml index e78204ea5d2..1d2262b7a46 100644 --- a/inputFiles/hydraulicFracturing/pknViscosityDominated_poroelastic_smoke.xml +++ b/inputFiles/hydraulicFracturing/pknViscosityDominated_poroelastic_smoke.xml @@ -14,7 +14,7 @@ flowSolverName="SinglePhaseFlow" surfaceGeneratorName="SurfaceGen" logLevel="1" - targetRegions="{ Fracture }" + targetRegions="{ Domain, Fracture }" contactRelationName="fractureContact" maxNumResolves="5" initialDt="0.1"> @@ -44,7 +44,8 @@ targetRegions="{ Domain }" nodeBasedSIF="1" rockToughness="0.1e6" - mpiCommOrder="1"/> + mpiCommOrder="1" + isPoroelastic="1"/> diff --git a/integratedTests b/integratedTests index aff76fac6eb..a5adb7edd18 160000 --- a/integratedTests +++ b/integratedTests @@ -1 +1 @@ -Subproject commit aff76fac6ebdf931162e68c535b69012111984c4 +Subproject commit a5adb7edd188392a5a31a71f2dda60e856a3f079 diff --git a/src/coreComponents/physicsSolvers/CMakeLists.txt b/src/coreComponents/physicsSolvers/CMakeLists.txt index 6cc22d1a431..def7de24ea1 100644 --- a/src/coreComponents/physicsSolvers/CMakeLists.txt +++ b/src/coreComponents/physicsSolvers/CMakeLists.txt @@ -126,6 +126,8 @@ set( physicsSolvers_headers surfaceGeneration/ParallelTopologyChange.hpp surfaceGeneration/SurfaceGenerator.hpp surfaceGeneration/SurfaceGeneratorFields.hpp + surfaceGeneration/kernels/surfaceGenerationKernels.hpp + surfaceGeneration/kernels/surfaceGenerationKernelsHelpers.hpp wavePropagation/WaveSolverBase.hpp wavePropagation/WaveSolverUtils.hpp wavePropagation/WaveSolverBaseFields.hpp diff --git a/src/coreComponents/physicsSolvers/multiphysics/PoromechanicsSolver.hpp b/src/coreComponents/physicsSolvers/multiphysics/PoromechanicsSolver.hpp index 94964512394..05eaca5fdef 100644 --- a/src/coreComponents/physicsSolvers/multiphysics/PoromechanicsSolver.hpp +++ b/src/coreComponents/physicsSolvers/multiphysics/PoromechanicsSolver.hpp @@ -97,6 +97,15 @@ class PoromechanicsSolver : public CoupledSolver< FLOW_SOLVER, this->getCatalogName(), this->getDataContext().toString(), subRegion.getName() ), InputError ); + string & porosityModelName = subRegion.getReference< string >( constitutive::CoupledSolidBase::viewKeyStruct::porosityModelNameString() ); + porosityModelName = this->template getConstitutiveName< constitutive::PorosityBase >( subRegion ); + GEOS_THROW_IF( porosityModelName.empty(), + GEOS_FMT( "{} {} : Porosity model not found on subregion {}", + this->catalogName(), this->getDataContext().toString(), subRegion.getName() ), + InputError ); + + + if( subRegion.hasField< fields::poromechanics::bulkDensity >() ) { // get the solid model to know the number of quadrature points and resize the bulk density @@ -134,6 +143,12 @@ class PoromechanicsSolver : public CoupledSolver< FLOW_SOLVER, setRestartFlags( dataRepository::RestartFlags::NO_WRITE ). setSizedFromParent( 0 ); + // This is needed by the way the surface generator currently does things. + subRegion.registerWrapper< string >( constitutive::CoupledSolidBase::viewKeyStruct::porosityModelNameString() ). + setPlotLevel( dataRepository::PlotLevel::NOPLOT ). + setRestartFlags( dataRepository::RestartFlags::NO_WRITE ). + setSizedFromParent( 0 ); + if( this->getNonlinearSolverParameters().m_couplingType == NonlinearSolverParameters::CouplingType::Sequential ) { // register the bulk density for use in the solid mechanics solver diff --git a/src/coreComponents/physicsSolvers/surfaceGeneration/SurfaceGenerator.cpp b/src/coreComponents/physicsSolvers/surfaceGeneration/SurfaceGenerator.cpp index b29940f75fb..42e8639eab7 100644 --- a/src/coreComponents/physicsSolvers/surfaceGeneration/SurfaceGenerator.cpp +++ b/src/coreComponents/physicsSolvers/surfaceGeneration/SurfaceGenerator.cpp @@ -19,7 +19,6 @@ #include "SurfaceGenerator.hpp" #include "ParallelTopologyChange.hpp" - #include "mesh/mpiCommunications/CommunicationTools.hpp" #include "mesh/mpiCommunications/NeighborCommunicator.hpp" #include "mesh/mpiCommunications/SpatialPartition.hpp" @@ -33,6 +32,7 @@ #include "physicsSolvers/solidMechanics/kernels/SolidMechanicsLagrangianFEMKernels.hpp" #include "physicsSolvers/surfaceGeneration/SurfaceGeneratorFields.hpp" #include "physicsSolvers/fluidFlow/FlowSolverBaseFields.hpp" +#include "kernels/surfaceGenerationKernels.hpp" #include @@ -176,7 +176,8 @@ SurfaceGenerator::SurfaceGenerator( const string & name, SolverBase( name, parent ), m_failCriterion( 1 ), // m_maxTurnAngle(91.0), - m_nodeBasedSIF( 0 ), + m_nodeBasedSIF( 1 ), + m_isPoroelastic( 0 ), m_rockToughness( 1.0e99 ), m_mpiCommOrder( 0 ) { @@ -191,6 +192,10 @@ SurfaceGenerator::SurfaceGenerator( const string & name, setInputFlag( InputFlags::OPTIONAL ). setDescription( "Flag for choosing between node or edge based criteria: 1 for node based criterion" ); + registerWrapper( viewKeyStruct::isPoroelasticString(), &m_isPoroelastic ). + setInputFlag( InputFlags::OPTIONAL ). + setDescription( "Flag that defines whether the material is poroelastic or not." ); + registerWrapper( viewKeyStruct::mpiCommOrderString(), &m_mpiCommOrder ). setInputFlag( InputFlags::OPTIONAL ). setDescription( "Flag to enable MPI consistent communication ordering" ); @@ -216,6 +221,23 @@ SurfaceGenerator::SurfaceGenerator( const string & name, setInputFlag( InputFlags::FALSE ); } +void SurfaceGenerator::postProcessInput() +{ + static const std::set< integer > binaryOptions = { 0, 1 }; + + GEOS_ERROR_IF( binaryOptions.count( m_isPoroelastic ) == 0, + getWrapperDataContext( viewKeyStruct::isPoroelasticString() ) << + ": option can be either 0 (false) or 1 (true)" ); + + GEOS_ERROR_IF( binaryOptions.count( m_nodeBasedSIF ) == 0, + getWrapperDataContext( viewKeyStruct::nodeBasedSIFString() ) << + ": option can be either 0 (false) or 1 (true)" ); + + GEOS_ERROR_IF( binaryOptions.count( m_mpiCommOrder ) == 0, + getWrapperDataContext( viewKeyStruct::mpiCommOrderString() ) << + ": option can be either 0 (false) or 1 (true)" ); +} + SurfaceGenerator::~SurfaceGenerator() { // TODO Auto-generated destructor stub @@ -2867,14 +2889,6 @@ void SurfaceGenerator::calculateNodeAndFaceSif( DomainPartition const & domain, elementManager.constructFullMaterialViewAccessor< array3d< real64, solid::STRESS_PERMUTATION >, arrayView3d< real64 const, solid::STRESS_USD > >( SolidBase::viewKeyStruct::stressString(), constitutiveManager ); - - - ElementRegionManager::ElementViewAccessor< arrayView4d< real64 const > > const - dNdX = elementManager.constructViewAccessor< array4d< real64 >, arrayView4d< real64 const > >( keys::dNdX ); - - ElementRegionManager::ElementViewAccessor< arrayView2d< real64 const > > const - detJ = elementManager.constructViewAccessor< array2d< real64 >, arrayView2d< real64 const > >( keys::detJ ); - nodeManager.getField< fields::solidMechanics::totalDisplacement >().move( hostMemorySpace, false ); forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const &, @@ -2894,322 +2908,313 @@ void SurfaceGenerator::calculateNodeAndFaceSif( DomainPartition const & domain, displacement.move( hostMemorySpace, false ); } ); - for( localIndex const trailingFaceIndex : m_trailingFaces ) -// RAJA::forall< parallelHostPolicy >( RAJA::TypedRangeSegment< localIndex >( 0, m_trailingFaces.size() ), -// [=] GEOS_HOST_DEVICE ( localIndex const trailingFacesCounter ) + // auto nodalForceKernel = surfaceGenerationKernels::createKernel( elementManager, constitutiveManager, + // viewKeyStruct::solidMaterialNameString(), false ); + + surfaceGenerationKernels::kernelSelector( elementManager, + constitutiveManager, + viewKeyStruct::solidMaterialNameString(), + m_isPoroelastic, [&] ( auto nodalForceKernel ) { -// localIndex const trailingFaceIndex = m_trailingFaces[ trailingFacesCounter ]; + for( localIndex const trailingFaceIndex : m_trailingFaces ) + { + // RAJA::forall< parallelHostPolicy >( RAJA::TypedRangeSegment< localIndex >( 0, m_trailingFaces.size() ), [=] GEOS_HOST_DEVICE ( + // localIndex const trailingFacesCounter ) + // localIndex const trailingFaceIndex = m_trailingFaces[ trailingFacesCounter ]; + /// TODO: check if a ghost face still has the correct attributes such as normal vector, face center, face index. - real64 const faceNormalVector[3] = LVARRAY_TENSOROPS_INIT_LOCAL_3( faceNormal[trailingFaceIndex] ); - //TODO: check if a ghost face still has the correct attributes such as normal vector, face center, face index. - localIndex_array unpinchedNodeID; - localIndex_array pinchedNodeID; - localIndex_array tipEdgesID; + real64 const faceNormalVector[3] = LVARRAY_TENSOROPS_INIT_LOCAL_3( faceNormal[trailingFaceIndex] ); + localIndex_array unpinchedNodeID; + localIndex_array pinchedNodeID; + localIndex_array tipEdgesID; - for( localIndex const nodeIndex : faceToNodeMap[ trailingFaceIndex ] ) - { - if( m_tipNodes.contains( nodeIndex )) - { - pinchedNodeID.emplace_back( nodeIndex ); - } - else + for( localIndex const nodeIndex : faceToNodeMap[ trailingFaceIndex ] ) { - unpinchedNodeID.emplace_back( nodeIndex ); + if( m_tipNodes.contains( nodeIndex )) + { + pinchedNodeID.emplace_back( nodeIndex ); + } + else + { + unpinchedNodeID.emplace_back( nodeIndex ); + } } - } - for( localIndex const edgeIndex : faceToEdgeMap[ trailingFaceIndex ] ) - { - if( m_tipEdges.contains( edgeIndex )) + for( localIndex const edgeIndex : faceToEdgeMap[ trailingFaceIndex ] ) { - tipEdgesID.emplace_back( edgeIndex ); + if( m_tipEdges.contains( edgeIndex )) + { + tipEdgesID.emplace_back( edgeIndex ); + } } - } - if( unpinchedNodeID.size() < 2 || (unpinchedNodeID.size() == 2 && tipEdgesID.size() < 2) ) - { - for( localIndex const nodeIndex : pinchedNodeID ) + if( unpinchedNodeID.size() < 2 || (unpinchedNodeID.size() == 2 && tipEdgesID.size() < 2) ) { - if( isNodeGhost[nodeIndex] < 0 ) + for( localIndex const nodeIndex : pinchedNodeID ) { - real64 nodeDisconnectForce[3] = { 0 }; - real64 const nodePosition[3] = LVARRAY_TENSOROPS_INIT_LOCAL_3( X[nodeIndex] ); - localIndex tralingNodeID = std::numeric_limits< localIndex >::max(); - localIndex nElemEachSide[2]; - nElemEachSide[0] = 0; - nElemEachSide[1] = 0; - - for( localIndex k=0; k( esr ); + real64 nodeDisconnectForce[3] = { 0 }; + real64 const nodePosition[3] = LVARRAY_TENSOROPS_INIT_LOCAL_3( X[nodeIndex] ); + localIndex tralingNodeID = std::numeric_limits< localIndex >::max(); + localIndex nElemEachSide[2]; + nElemEachSide[0] = 0; + nElemEachSide[1] = 0; + + for( localIndex k=0; k const & elementsToNodes = elementSubRegion.nodeList(); - arrayView2d< real64 const > const & elementCenter = elementSubRegion.getElementCenter().toViewConst(); - real64 K = bulkModulus[er][esr][m_solidMaterialFullIndex[er]][ei]; - real64 G = shearModulus[er][esr][m_solidMaterialFullIndex[er]][ei]; - real64 YoungModulus = 9 * K * G / ( 3 * K + G ); - real64 poissonRatio = ( 3 * K - 2 * G ) / ( 2 * ( 3 * K + G ) ); + CellElementSubRegion const & elementSubRegion = elementManager.getRegion( er ).getSubRegion< CellElementSubRegion >( esr ); - localIndex const numQuadraturePoints = detJ[er][esr].size( 1 ); + arrayView2d< localIndex const, cells::NODE_MAP_USD > const & elementsToNodes = elementSubRegion.nodeList(); + arrayView2d< real64 const > const & elementCenter = elementSubRegion.getElementCenter().toViewConst(); - for( localIndex n=0; n( temp, YoungModulus ); - LvArray::tensorOps::scale< 3 >( temp, 1.0 / (1 - poissonRatio * poissonRatio) ); - - LvArray::tensorOps::subtract< 3 >( xEle, nodePosition ); - if( LvArray::tensorOps::AiBi< 3 >( xEle, faceNormalVector ) > 0 ) //TODO: check the sign. + if( elementsToNodes( ei, n ) == nodeIndex ) { - nElemEachSide[0] += 1; - LvArray::tensorOps::add< 3 >( nodeDisconnectForce, temp ); - } - else - { - nElemEachSide[1] +=1; - LvArray::tensorOps::subtract< 3 >( nodeDisconnectForce, temp ); + real64 nodalForce[ 3 ] = {0}; + real64 xEle[ 3 ] = LVARRAY_TENSOROPS_INIT_LOCAL_3 ( elementCenter[ei] ); + + nodalForceKernel.calculateSingleNodalForce( er, esr, ei, n, nodalForce ); + + LvArray::tensorOps::subtract< 3 >( xEle, nodePosition ); + if( LvArray::tensorOps::AiBi< 3 >( xEle, faceNormalVector ) > 0 ) //TODO: check the sign. + { + nElemEachSide[0] += 1; + LvArray::tensorOps::add< 3 >( nodeDisconnectForce, nodalForce ); + } + else + { + nElemEachSide[1] +=1; + LvArray::tensorOps::subtract< 3 >( nodeDisconnectForce, nodalForce ); + } } } } - } - if( nElemEachSide[0]>=1 && nElemEachSide[1]>=1 ) - { - LvArray::tensorOps::scale< 3 >( nodeDisconnectForce, 0.5 ); - } + if( nElemEachSide[0]>=1 && nElemEachSide[1]>=1 ) + { + LvArray::tensorOps::scale< 3 >( nodeDisconnectForce, 0.5 ); + } - //Find the trailing node according to the node index and face index - if( unpinchedNodeID.size() == 0 ) //Tet mesh under three nodes pinched scenario. Need to find the other - // trailing face that containing the trailing node. - { - for( localIndex const edgeIndex: faceToEdgeMap[ trailingFaceIndex ] ) + //Find the trailing node according to the node index and face index + if( unpinchedNodeID.size() == 0 ) //Tet mesh under three nodes pinched scenario. Need to find the other + // trailing face that containing the trailing node. { - for( localIndex const faceIndex: edgeToFaceMap[ edgeIndex ] ) + for( localIndex const edgeIndex: faceToEdgeMap[ trailingFaceIndex ] ) { - if( faceIndex != trailingFaceIndex && m_tipFaces.contains( faceIndex )) + for( localIndex const faceIndex: edgeToFaceMap[ edgeIndex ] ) { - for( localIndex const iNode: faceToNodeMap[ faceIndex ] ) + if( faceIndex != trailingFaceIndex && m_tipFaces.contains( faceIndex )) { - if( !m_tipNodes.contains( iNode )) + for( localIndex const iNode: faceToNodeMap[ faceIndex ] ) { - tralingNodeID = iNode; + if( !m_tipNodes.contains( iNode )) + { + tralingNodeID = iNode; + } } } } } - } - if( tralingNodeID == std::numeric_limits< localIndex >::max()) + if( tralingNodeID == std::numeric_limits< localIndex >::max()) + { + GEOS_ERROR( getDataContext() << ": The triangular trailing face has three tip nodes but cannot find the other trailing face containing the trailing node." ); + } + } + else if( unpinchedNodeID.size() == 1 ) { - GEOS_ERROR( getDataContext() << ": The triangular trailing face has three tip nodes but cannot find the other trailing face containing the trailing node." ); + tralingNodeID = unpinchedNodeID[0]; } - } - else if( unpinchedNodeID.size() == 1 ) - { - tralingNodeID = unpinchedNodeID[0]; - } - else if( unpinchedNodeID.size() == 2 ) - { - for( localIndex const edgeIndex : nodeToEdgeMap[ nodeIndex ] ) + else if( unpinchedNodeID.size() == 2 ) { - auto const faceToEdgeMapIterator = faceToEdgeMap[ trailingFaceIndex ]; - if( std::find( faceToEdgeMapIterator.begin(), faceToEdgeMapIterator.end(), edgeIndex ) != faceToEdgeMapIterator.end() && - !m_tipEdges.contains( edgeIndex ) ) + for( localIndex const edgeIndex : nodeToEdgeMap[ nodeIndex ] ) { - tralingNodeID = edgeToNodeMap[edgeIndex][0] == nodeIndex ? edgeToNodeMap[edgeIndex][1] : edgeToNodeMap[edgeIndex][0]; + auto const faceToEdgeMapIterator = faceToEdgeMap[ trailingFaceIndex ]; + if( std::find( faceToEdgeMapIterator.begin(), faceToEdgeMapIterator.end(), edgeIndex ) != faceToEdgeMapIterator.end() && + !m_tipEdges.contains( edgeIndex ) ) + { + tralingNodeID = edgeToNodeMap[edgeIndex][0] == nodeIndex ? edgeToNodeMap[edgeIndex][1] : edgeToNodeMap[edgeIndex][0]; + } } } - } - //Calculate SIF for the node. - real64 tipNodeSIF; - real64 tipNodeForce[3]; - real64 trailingNodeDisp[3]; - localIndex theOtherTrailingNodeID; + //Calculate SIF for the node. + real64 tipNodeSIF; + real64 tipNodeForce[3]; + real64 trailingNodeDisp[3]; + localIndex theOtherTrailingNodeID; - if( childNodeIndices[tralingNodeID] == -1 ) - { - theOtherTrailingNodeID = parentNodeIndices[tralingNodeID]; - } - else - { - theOtherTrailingNodeID = childNodeIndices[tralingNodeID]; - } + if( childNodeIndices[tralingNodeID] == -1 ) + { + theOtherTrailingNodeID = parentNodeIndices[tralingNodeID]; + } + else + { + theOtherTrailingNodeID = childNodeIndices[tralingNodeID]; + } - LvArray::tensorOps::copy< 3 >( trailingNodeDisp, displacement[theOtherTrailingNodeID] ); - LvArray::tensorOps::subtract< 3 >( trailingNodeDisp, displacement[tralingNodeID] ); + LvArray::tensorOps::copy< 3 >( trailingNodeDisp, displacement[theOtherTrailingNodeID] ); + LvArray::tensorOps::subtract< 3 >( trailingNodeDisp, displacement[tralingNodeID] ); - //Calculate average young's modulus and poisson ratio for fext. - real64 fExternal[2][3]; - for( localIndex i=0; i<2; ++i ) - { - real64 averageYoungModulus( 0 ), averagePoissonRatio( 0 ); - localIndex nodeID = i == 0 ? tralingNodeID : theOtherTrailingNodeID; - for( localIndex k=0; k( fExternal[i], fext[nodeID] ); - LvArray::tensorOps::scale< 3 >( fExternal[i], averageYoungModulus / (1 - averagePoissonRatio * averagePoissonRatio) ); - } + LvArray::tensorOps::copy< 3 >( fExternal[i], fext[nodeID] ); + LvArray::tensorOps::scale< 3 >( fExternal[i], averageYoungModulus / (1 - averagePoissonRatio * averagePoissonRatio) ); + } - //TODO: The sign of fext here is opposite to the sign of fFaceA in function "CalculateEdgeSIF". - tipNodeForce[0] = nodeDisconnectForce[0] - ( fExternal[0][0] - fExternal[1][0] ) / 2.0; - tipNodeForce[1] = nodeDisconnectForce[1] - ( fExternal[0][1] - fExternal[1][1] ) / 2.0; - tipNodeForce[2] = nodeDisconnectForce[2] - ( fExternal[0][2] - fExternal[1][2] ) / 2.0; + //TODO: The sign of fext here is opposite to the sign of fFaceA in function "CalculateEdgeSIF". + tipNodeForce[0] = nodeDisconnectForce[0] - ( fExternal[0][0] - fExternal[1][0] ) / 2.0; + tipNodeForce[1] = nodeDisconnectForce[1] - ( fExternal[0][1] - fExternal[1][1] ) / 2.0; + tipNodeForce[2] = nodeDisconnectForce[2] - ( fExternal[0][2] - fExternal[1][2] ) / 2.0; // tipNodeForce[0] = nodeDisconnectForce[0]; // tipNodeForce[1] = nodeDisconnectForce[1]; // tipNodeForce[2] = nodeDisconnectForce[2]; - real64 tipArea; - tipArea = faceArea( trailingFaceIndex ); - if( faceToNodeMap.sizeOfArray( trailingFaceIndex ) == 3 ) - { - tipArea *= 2.0; - } + real64 tipArea = faceArea( trailingFaceIndex ); + if( faceToNodeMap.sizeOfArray( trailingFaceIndex ) == 3 ) + { + tipArea *= 2.0; + } - tipNodeSIF = pow( (fabs( tipNodeForce[0] * trailingNodeDisp[0] / 2.0 / tipArea ) + fabs( tipNodeForce[1] * trailingNodeDisp[1] / 2.0 / tipArea ) - + fabs( tipNodeForce[2] * trailingNodeDisp[2] / 2.0 / tipArea )), 0.5 ); + tipNodeSIF = pow( (fabs( tipNodeForce[0] * trailingNodeDisp[0] / 2.0 / tipArea ) + fabs( tipNodeForce[1] * trailingNodeDisp[1] / 2.0 / tipArea ) + + fabs( tipNodeForce[2] * trailingNodeDisp[2] / 2.0 / tipArea )), 0.5 ); - if( LvArray::tensorOps::AiBi< 3 >( trailingNodeDisp, faceNormalVector ) < 0.0 ) //In case the aperture is negative with the - // presence of confining stress. - { - tipNodeSIF *= -1; - } + if( LvArray::tensorOps::AiBi< 3 >( trailingNodeDisp, faceNormalVector ) < 0.0 ) //In case the aperture is negative with the + // presence of confining stress. + { + tipNodeSIF *= -1; + } - SIFNode_All[nodeIndex].emplace_back( tipNodeSIF ); + SIFNode_All[nodeIndex].emplace_back( tipNodeSIF ); - //Calculate SIF on tip faces connected to this trailing face and the tip node. - for( localIndex const edgeIndex: tipEdgesID ) - { - if( edgeToNodeMap[edgeIndex][0] == nodeIndex || edgeToNodeMap[edgeIndex][1] == nodeIndex ) + //Calculate SIF on tip faces connected to this trailing face and the tip node. + for( localIndex const edgeIndex: tipEdgesID ) { - real64 SIF_I = 0, SIF_II = 0, /*SIF_III,*/ SIF_Face; - real64 vecTipNorm[3], vecTip[3], tipForce[3], tipOpening[3]; + if( edgeToNodeMap[edgeIndex][0] == nodeIndex || edgeToNodeMap[edgeIndex][1] == nodeIndex ) + { + real64 SIF_I = 0, SIF_II = 0, /*SIF_III,*/ SIF_Face; + real64 vecTipNorm[3], vecTip[3], tipForce[3], tipOpening[3]; - LvArray::tensorOps::copy< 3 >( vecTipNorm, faceNormal[trailingFaceIndex] ); - LvArray::tensorOps::subtract< 3 >( vecTipNorm, faceNormal[childFaceIndices[trailingFaceIndex]] ); - LvArray::tensorOps::normalize< 3 >( vecTipNorm ); + LvArray::tensorOps::copy< 3 >( vecTipNorm, faceNormal[trailingFaceIndex] ); + LvArray::tensorOps::subtract< 3 >( vecTipNorm, faceNormal[childFaceIndices[trailingFaceIndex]] ); + LvArray::tensorOps::normalize< 3 >( vecTipNorm ); - real64 vecEdge[3]; - edgeManager.calculateLength( edgeIndex, X, vecEdge ); - LvArray::tensorOps::normalize< 3 >( vecEdge ); + real64 vecEdge[3]; + edgeManager.calculateLength( edgeIndex, X, vecEdge ); + LvArray::tensorOps::normalize< 3 >( vecEdge ); - LvArray::tensorOps::crossProduct( vecTip, vecTipNorm, vecEdge ); - LvArray::tensorOps::normalize< 3 >( vecTip ); - real64 v0[3]; - edgeManager.calculateCenter( edgeIndex, X, v0 ); - LvArray::tensorOps::subtract< 3 >( v0, faceCenter[ trailingFaceIndex ] ); + LvArray::tensorOps::crossProduct( vecTip, vecTipNorm, vecEdge ); + LvArray::tensorOps::normalize< 3 >( vecTip ); + real64 v0[3]; + edgeManager.calculateCenter( edgeIndex, X, v0 ); + LvArray::tensorOps::subtract< 3 >( v0, faceCenter[ trailingFaceIndex ] ); - if( LvArray::tensorOps::AiBi< 3 >( v0, vecTip ) < 0 ) - LvArray::tensorOps::scale< 3 >( vecTip, -1.0 ); + if( LvArray::tensorOps::AiBi< 3 >( v0, vecTip ) < 0 ) + LvArray::tensorOps::scale< 3 >( vecTip, -1.0 ); - tipForce[0] = LvArray::tensorOps::AiBi< 3 >( nodeDisconnectForce, vecTipNorm ) - - ( LvArray::tensorOps::AiBi< 3 >( fExternal[0], vecTipNorm ) - LvArray::tensorOps::AiBi< 3 >( fExternal[1], vecTipNorm ) ) / 2.0; - tipForce[1] = LvArray::tensorOps::AiBi< 3 >( nodeDisconnectForce, vecTip ) - - ( LvArray::tensorOps::AiBi< 3 >( fExternal[0], vecTip ) - LvArray::tensorOps::AiBi< 3 >( fExternal[1], vecTip ) ) / 2.0; - tipForce[2] = LvArray::tensorOps::AiBi< 3 >( nodeDisconnectForce, vecEdge ) - - ( LvArray::tensorOps::AiBi< 3 >( fExternal[0], vecEdge ) - LvArray::tensorOps::AiBi< 3 >( fExternal[1], vecEdge ) ) / 2.0; + tipForce[0] = LvArray::tensorOps::AiBi< 3 >( nodeDisconnectForce, vecTipNorm ) - + ( LvArray::tensorOps::AiBi< 3 >( fExternal[0], vecTipNorm ) - LvArray::tensorOps::AiBi< 3 >( fExternal[1], vecTipNorm ) ) / 2.0; + tipForce[1] = LvArray::tensorOps::AiBi< 3 >( nodeDisconnectForce, vecTip ) - + ( LvArray::tensorOps::AiBi< 3 >( fExternal[0], vecTip ) - LvArray::tensorOps::AiBi< 3 >( fExternal[1], vecTip ) ) / 2.0; + tipForce[2] = LvArray::tensorOps::AiBi< 3 >( nodeDisconnectForce, vecEdge ) - + ( LvArray::tensorOps::AiBi< 3 >( fExternal[0], vecEdge ) - LvArray::tensorOps::AiBi< 3 >( fExternal[1], vecEdge ) ) / 2.0; // tipForce[0] = LvArray::tensorOps::AiBi< 3 >( nodeDisconnectForce, vecTipNorm ); // tipForce[1] = LvArray::tensorOps::AiBi< 3 >( nodeDisconnectForce, vecTip ); // tipForce[2] = LvArray::tensorOps::AiBi< 3 >( nodeDisconnectForce, vecEdge ); - tipOpening[0] = LvArray::tensorOps::AiBi< 3 >( trailingNodeDisp, vecTipNorm ); - tipOpening[1] = LvArray::tensorOps::AiBi< 3 >( trailingNodeDisp, vecTip ); - tipOpening[2] = LvArray::tensorOps::AiBi< 3 >( trailingNodeDisp, vecEdge ); + tipOpening[0] = LvArray::tensorOps::AiBi< 3 >( trailingNodeDisp, vecTipNorm ); + tipOpening[1] = LvArray::tensorOps::AiBi< 3 >( trailingNodeDisp, vecTip ); + tipOpening[2] = LvArray::tensorOps::AiBi< 3 >( trailingNodeDisp, vecEdge ); // if( tipForce[0] > 0.0 ) - { - SIF_I = pow( fabs( tipForce[0] * tipOpening[0] / 2.0 / tipArea ), 0.5 ); - SIF_II = pow( fabs( tipForce[1] * tipOpening[1] / 2.0 / tipArea ), 0.5 ); + { + SIF_I = pow( fabs( tipForce[0] * tipOpening[0] / 2.0 / tipArea ), 0.5 ); + SIF_II = pow( fabs( tipForce[1] * tipOpening[1] / 2.0 / tipArea ), 0.5 ); // SIF_III = pow( fabs( tipForce[2] * tipOpening[2] / 2.0 / tipArea ), 0.5 ); - } + } - if( tipOpening[0] < 0 ) - { - SIF_I *= -1.0; - } + if( tipOpening[0] < 0 ) + { + SIF_I *= -1.0; + } - if( tipForce[1] < 0.0 ) - { - SIF_II *= -1.0; - } + if( tipForce[1] < 0.0 ) + { + SIF_II *= -1.0; + } - for( localIndex const faceIndex: edgeToFaceMap[ edgeIndex ] ) - { - if( m_tipFaces.contains( faceIndex )) + for( localIndex const faceIndex: edgeToFaceMap[ edgeIndex ] ) { - real64 vecFace[ 3 ]; - real64 fc[3] = LVARRAY_TENSOROPS_INIT_LOCAL_3 ( faceCenter[faceIndex] ); + if( m_tipFaces.contains( faceIndex )) + { + real64 vecFace[ 3 ]; + real64 fc[3] = LVARRAY_TENSOROPS_INIT_LOCAL_3 ( faceCenter[faceIndex] ); - //Get the vector in the face and normal to the edge. - real64 udist; + //Get the vector in the face and normal to the edge. + real64 udist; - real64 x0_x1[ 3 ] = LVARRAY_TENSOROPS_INIT_LOCAL_3( X[edgeToNodeMap[edgeIndex][0]] ); - real64 x0_fc[ 3 ] = LVARRAY_TENSOROPS_INIT_LOCAL_3( fc ); + real64 x0_x1[ 3 ] = LVARRAY_TENSOROPS_INIT_LOCAL_3( X[edgeToNodeMap[edgeIndex][0]] ); + real64 x0_fc[ 3 ] = LVARRAY_TENSOROPS_INIT_LOCAL_3( fc ); - LvArray::tensorOps::subtract< 3 >( x0_x1, X[edgeToNodeMap[edgeIndex][1]] ); - LvArray::tensorOps::normalize< 3 >( x0_x1 ); - LvArray::tensorOps::subtract< 3 >( x0_fc, X[edgeToNodeMap[edgeIndex][1]] ); - udist = LvArray::tensorOps::AiBi< 3 >( x0_x1, x0_fc ); + LvArray::tensorOps::subtract< 3 >( x0_x1, X[edgeToNodeMap[edgeIndex][1]] ); + LvArray::tensorOps::normalize< 3 >( x0_x1 ); + LvArray::tensorOps::subtract< 3 >( x0_fc, X[edgeToNodeMap[edgeIndex][1]] ); + udist = LvArray::tensorOps::AiBi< 3 >( x0_x1, x0_fc ); - real64 ptPrj[ 3 ] = LVARRAY_TENSOROPS_INIT_LOCAL_3 ( x0_x1 ); - LvArray::tensorOps::scale< 3 >( ptPrj, udist ); - LvArray::tensorOps::add< 3 >( ptPrj, X[edgeToNodeMap[edgeIndex][1]] ); - LvArray::tensorOps::copy< 3 >( vecFace, fc ); - LvArray::tensorOps::subtract< 3 >( vecFace, ptPrj ); - LvArray::tensorOps::normalize< 3 >( vecFace ); + real64 ptPrj[ 3 ] = LVARRAY_TENSOROPS_INIT_LOCAL_3 ( x0_x1 ); + LvArray::tensorOps::scale< 3 >( ptPrj, udist ); + LvArray::tensorOps::add< 3 >( ptPrj, X[edgeToNodeMap[edgeIndex][1]] ); + LvArray::tensorOps::copy< 3 >( vecFace, fc ); + LvArray::tensorOps::subtract< 3 >( vecFace, ptPrj ); + LvArray::tensorOps::normalize< 3 >( vecFace ); // if( LvArray::tensorOps::AiBi< 3 >( vecTip, vecFace ) > cos( m_maxTurnAngle )) - { - // We multiply this by 0.9999999 to avoid an exception caused by acos a number slightly larger than - // 1. - real64 thetaFace = acos( LvArray::tensorOps::AiBi< 3 >( vecTip, vecFace )*0.999999 ); + { + // We multiply this by 0.9999999 to avoid an exception caused by acos a number slightly larger than + // 1. + real64 thetaFace = acos( LvArray::tensorOps::AiBi< 3 >( vecTip, vecFace )*0.999999 ); - real64 tipCrossFace[ 3 ]; - LvArray::tensorOps::crossProduct( tipCrossFace, vecTip, vecEdge ); + real64 tipCrossFace[ 3 ]; + LvArray::tensorOps::crossProduct( tipCrossFace, vecTip, vecEdge ); - if( LvArray::tensorOps::AiBi< 3 >( tipCrossFace, vecEdge ) < 0.0 ) - { - thetaFace *= -1.0; - } + if( LvArray::tensorOps::AiBi< 3 >( tipCrossFace, vecEdge ) < 0.0 ) + { + thetaFace *= -1.0; + } - SIF_Face = cos( thetaFace / 2.0 ) * - ( SIF_I * cos( thetaFace / 2.0 ) * cos( thetaFace / 2.0 ) - 1.5 * SIF_II * sin( thetaFace ) ); + SIF_Face = cos( thetaFace / 2.0 ) * + ( SIF_I * cos( thetaFace / 2.0 ) * cos( thetaFace / 2.0 ) - 1.5 * SIF_II * sin( thetaFace ) ); - SIFonFace_All[faceIndex].emplace_back( SIF_Face ); + SIFonFace_All[faceIndex].emplace_back( SIF_Face ); + } } } } @@ -3218,7 +3223,7 @@ void SurfaceGenerator::calculateNodeAndFaceSif( DomainPartition const & domain, } } } - } + } ); //wu40: the tip node may be included in multiple trailing faces and SIF of the node/face will be calculated multiple // times. We chose the smaller node SIF and the larger face SIF. @@ -3779,10 +3784,10 @@ int SurfaceGenerator::calculateElementForcesOnEdge( DomainPartition const & doma if(( udist <= edgeLength && udist > 0.0 ) || threeNodesPinched ) { - real64 K = bulkModulus[er][esr][m_solidMaterialFullIndex[er]][ei]; - real64 G = shearModulus[er][esr][m_solidMaterialFullIndex[er]][ei]; - real64 YoungModulus = 9 * K * G / ( 3 * K + G ); - real64 poissonRatio = ( 3 * K - 2 * G ) / ( 2 * ( 3 * K + G ) ); + real64 const K = bulkModulus[er][esr][m_solidMaterialFullIndex[er]][ei]; + real64 const G = shearModulus[er][esr][m_solidMaterialFullIndex[er]][ei]; + real64 const YoungModulus = 9 * K * G / ( 3 * K + G ); + real64 const poissonRatio = ( 3 * K - 2 * G ) / ( 2 * ( 3 * K + G ) ); arrayView2d< localIndex const, cells::NODE_MAP_USD > const & elementsToNodes = elementSubRegion.nodeList(); for( localIndex n=0; n, arrayView4d< real64 const > >( dataRepository::keys::dNdX ) ), + m_detJ( elemManager.constructViewAccessor< array2d< real64 >, arrayView2d< real64 const > >( dataRepository::keys::detJ ) ), + m_bulkModulus( elemManager.constructFullMaterialViewAccessor< array1d< real64 >, arrayView1d< real64 const > >( "bulkModulus", constitutiveManager ) ), + m_shearModulus( elemManager.constructFullMaterialViewAccessor< array1d< real64 >, arrayView1d< real64 const > >( "shearModulus", constitutiveManager ) ), + m_stress( elemManager.constructFullMaterialViewAccessor< array3d< real64, solid::STRESS_PERMUTATION >, + arrayView3d< real64 const, solid::STRESS_USD > >( constitutive::SolidBase::viewKeyStruct::stressString(), + constitutiveManager ) ) + { + m_solidMaterialFullIndex.resize( elemManager.numRegions() ); + elemManager.forElementRegionsComplete< CellElementRegion >( [&]( localIndex regionIndex, + CellElementRegion const & region ) + { + string const & solidMaterialName = region.getSubRegion( 0 ).getReference< string >( solidMaterialKey ); + constitutive::ConstitutiveBase const & solid = constitutiveManager.getConstitutiveRelation< constitutive::ConstitutiveBase >( solidMaterialName ); + m_solidMaterialFullIndex[regionIndex] = solid.getIndexInParent(); + } ); + } + + virtual void + calculateSingleNodalForce( localIndex const er, + localIndex const esr, + localIndex const ei, + localIndex const targetNode, + real64 ( & force )[ 3 ] ) const + { + GEOS_MARK_FUNCTION; + + localIndex const numQuadraturePoints = m_detJ[er][esr].size( 1 ); + + // Loop over quadrature points + for( localIndex q = 0; q < numQuadraturePoints; ++q ) + { + real64 const quadratureStress[6] = LVARRAY_TENSOROPS_INIT_LOCAL_6 ( m_stress[er][esr][m_solidMaterialFullIndex[er]][ei][q] ); + real64 const dNdX[3] = LVARRAY_TENSOROPS_INIT_LOCAL_3 ( m_dNdX[er][esr][ei][q][targetNode] ); + computeNodalForce( quadratureStress, dNdX, m_detJ[er][esr][ei][q], force ); + } + + //wu40: the nodal force need to be weighted by Young's modulus and possion's ratio. + scaleNodalForce( m_bulkModulus[er][esr][m_solidMaterialFullIndex[er]][ei], m_shearModulus[er][esr][m_solidMaterialFullIndex[er]][ei], force ); + } + +protected: + + ElementRegionManager::ElementViewAccessor< arrayView4d< real64 const > > const m_dNdX; + + ElementRegionManager::ElementViewAccessor< arrayView2d< real64 const > > const m_detJ; + + ElementRegionManager::MaterialViewAccessor< arrayView1d< real64 const > > const m_bulkModulus; + + ElementRegionManager::MaterialViewAccessor< arrayView1d< real64 const > > const m_shearModulus; + + ElementRegionManager::MaterialViewAccessor< arrayView3d< real64 const, solid::STRESS_USD > > const m_stress; + + array1d< integer > m_solidMaterialFullIndex; +}; + + +class PoroElasticNodalForceKernel : public NodalForceKernel +{ + +public: + PoroElasticNodalForceKernel( ElementRegionManager const & elemManager, + constitutive::ConstitutiveManager const & constitutiveManager, + string const solidMaterialKey, + string const porosityModelKey ): + NodalForceKernel( elemManager, constitutiveManager, solidMaterialKey ), + m_pressure( elemManager.constructArrayViewAccessor< real64, 1 >( fields::flow::pressure::key() ) ), + m_biotCoefficient( elemManager.constructFullMaterialViewAccessor< array1d< real64 >, arrayView1d< real64 const > >( "biotCoefficient", constitutiveManager ) ) + { + m_porosityMaterialFullIndex.resize( elemManager.numRegions() ); + elemManager.forElementRegionsComplete< CellElementRegion >( [&]( localIndex regionIndex, + CellElementRegion const & region ) + { + string const & porosityModelName = region.getSubRegion( 0 ).getReference< string >( porosityModelKey ); + constitutive::ConstitutiveBase const & porosityModel = constitutiveManager.getConstitutiveRelation< constitutive::ConstitutiveBase >( porosityModelName ); + m_porosityMaterialFullIndex[regionIndex] = porosityModel.getIndexInParent(); + } ); + + } + + void + calculateSingleNodalForce( localIndex const er, + localIndex const esr, + localIndex const ei, + localIndex const targetNode, + real64 ( & force )[ 3 ] ) const override + + { + GEOS_MARK_FUNCTION; + + localIndex const numQuadraturePoints = m_detJ[er][esr].size( 1 ); + + // Loop over quadrature points + for( localIndex q = 0; q < numQuadraturePoints; ++q ) + { + real64 totalStress[6] = LVARRAY_TENSOROPS_INIT_LOCAL_6 ( m_stress[er][esr][m_solidMaterialFullIndex[er]][ei][q] ); + real64 const dNdX[3] = LVARRAY_TENSOROPS_INIT_LOCAL_3 ( m_dNdX[er][esr][ei][q][targetNode] ); + /// TODO: make it work for the thermal case as well + LvArray::tensorOps::symAddIdentity< 3 >( totalStress, -m_biotCoefficient[er][esr][m_porosityMaterialFullIndex[er]][ei] * m_pressure[er][esr][ei] ); + + computeNodalForce( totalStress, dNdX, m_detJ[er][esr][ei][q], force ); + } + + //wu40: the nodal force need to be weighted by Young's modulus and possion's ratio. + scaleNodalForce( m_bulkModulus[er][esr][m_solidMaterialFullIndex[er]][ei], m_shearModulus[er][esr][m_solidMaterialFullIndex[er]][ei], force ); + } + +private: + + ElementRegionManager::ElementViewAccessor< arrayView1d< real64 const > > const m_pressure; + + ElementRegionManager::MaterialViewAccessor< arrayView1d< real64 const > > const m_biotCoefficient; + + array1d< integer > m_porosityMaterialFullIndex; + +}; + +template< typename LAMBDA > +void kernelSelector( ElementRegionManager const & elemManager, + constitutive::ConstitutiveManager const & constitutiveManager, + string const solidMaterialKey, + integer const isPoroelastic, + LAMBDA && lambda ) +{ + if( isPoroelastic == 0 ) + { + lambda( NodalForceKernel( elemManager, constitutiveManager, solidMaterialKey ) ); + } + else + { + string const porosityModelKey = constitutive::CoupledSolidBase::viewKeyStruct::porosityModelNameString(); + lambda( PoroElasticNodalForceKernel( elemManager, constitutiveManager, solidMaterialKey, porosityModelKey ) ); + } + +} + +} // namespace solidMechanicsLagrangianFEMKernels + +} // namespace geos diff --git a/src/coreComponents/physicsSolvers/surfaceGeneration/kernels/surfaceGenerationKernelsHelpers.hpp b/src/coreComponents/physicsSolvers/surfaceGeneration/kernels/surfaceGenerationKernelsHelpers.hpp new file mode 100644 index 00000000000..917ce1f1a8a --- /dev/null +++ b/src/coreComponents/physicsSolvers/surfaceGeneration/kernels/surfaceGenerationKernelsHelpers.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 surfaceGenerationKernelsHelpers.hpp + */ + + +#include "common/DataTypes.hpp" +#include "common/TimingMacros.hpp" + +namespace geos +{ + +namespace surfaceGenerationKernelsHelpers +{ + +GEOS_HOST_DEVICE +inline void computeNodalForce( real64 const ( &stress) [ 6 ], + real64 const ( &dNdX) [ 3 ], + real64 const detJ, + real64 ( & force ) [ 3 ] ) +{ + + force[ 0 ] -= ( stress[ 0 ] * dNdX[ 0 ] + + stress[ 5 ] * dNdX[ 1 ] + + stress[ 4 ] * dNdX[ 2 ] ) * detJ; + force[ 1 ] -= ( stress[ 5 ] * dNdX[ 0 ] + + stress[ 1 ] * dNdX[ 1 ] + + stress[ 3 ] * dNdX[ 2 ] ) * detJ; + force[ 2 ] -= ( stress[ 4 ] * dNdX[ 0 ] + + stress[ 3 ] * dNdX[ 1 ] + + stress[ 2 ] * dNdX[ 2 ] ) * detJ; +} + +GEOS_HOST_DEVICE +inline void scaleNodalForce( real64 const bulkModulus, + real64 const shearModulus, + real64 ( & force ) [ 3 ] ) +{ + real64 const YoungModulus = 9 * bulkModulus * shearModulus / ( 3 * bulkModulus + shearModulus ); + real64 const poissonRatio = ( 3 * bulkModulus - 2 * shearModulus ) / ( 2 * ( 3 * bulkModulus + shearModulus ) ); + + LvArray::tensorOps::scale< 3 >( force, YoungModulus ); + LvArray::tensorOps::scale< 3 >( force, 1.0 / (1 - poissonRatio * poissonRatio) ); +} + + +} + +} From dc79a6c9bc264dbb8ad39e3ce446f34e8f84952b Mon Sep 17 00:00:00 2001 From: Pavel Tomin Date: Fri, 19 Jan 2024 17:09:36 -0600 Subject: [PATCH 37/40] Add csv output also for singe phase flow and unify a bit between different statistics outputs (#2914) --- integratedTests | 2 +- .../physicsSolvers/FieldStatisticsBase.hpp | 31 +++++++++- .../CompositionalMultiphaseStatistics.cpp | 19 +----- .../CompositionalMultiphaseStatistics.hpp | 3 - .../fluidFlow/SinglePhaseStatistics.cpp | 58 ++++++++++++------- .../fluidFlow/SinglePhaseStatistics.hpp | 3 +- .../wells/CompositionalMultiphaseWell.cpp | 39 ++++++++----- .../fluidFlow/wells/SinglePhaseWell.cpp | 28 ++++++--- .../fluidFlow/wells/WellSolverBase.cpp | 7 ++- .../fluidFlow/wells/WellSolverBase.hpp | 2 + .../SolidMechanicsStatistics.cpp | 23 +------- .../SolidMechanicsStatistics.hpp | 4 -- .../CompositionalMultiphaseStatistics.rst | 1 + .../docs/CompositionalMultiphaseWell.rst | 1 + .../schema/docs/SinglePhaseStatistics.rst | 1 + .../schema/docs/SinglePhaseWell.rst | 1 + .../schema/docs/SolidMechanicsStatistics.rst | 1 + src/coreComponents/schema/schema.xsd | 10 ++++ 18 files changed, 143 insertions(+), 91 deletions(-) diff --git a/integratedTests b/integratedTests index a5adb7edd18..3c5c18a23ff 160000 --- a/integratedTests +++ b/integratedTests @@ -1 +1 @@ -Subproject commit a5adb7edd188392a5a31a71f2dda60e856a3f079 +Subproject commit 3c5c18a23ff238de934b647a421c966742d7ff7c diff --git a/src/coreComponents/physicsSolvers/FieldStatisticsBase.hpp b/src/coreComponents/physicsSolvers/FieldStatisticsBase.hpp index a15ba7778aa..fc1f4a2c5a3 100644 --- a/src/coreComponents/physicsSolvers/FieldStatisticsBase.hpp +++ b/src/coreComponents/physicsSolvers/FieldStatisticsBase.hpp @@ -23,6 +23,7 @@ #include "physicsSolvers/PhysicsSolverManager.hpp" #include "mainInterface/ProblemManager.hpp" #include "mesh/MeshLevel.hpp" +#include "fileIO/Outputs/OutputBase.hpp" namespace geos { @@ -45,7 +46,8 @@ class FieldStatisticsBase : public TaskBase FieldStatisticsBase( const string & name, Group * const parent ) : TaskBase( name, parent ), - m_solver( nullptr ) + m_solver( nullptr ), + m_outputDir( joinPath( OutputBase::getOutputDirectory(), name ) ) { enableLogLevelInput(); @@ -54,6 +56,11 @@ class FieldStatisticsBase : public TaskBase setRTTypeName( rtTypes::CustomTypes::groupNameRef ). setInputFlag( dataRepository::InputFlags::REQUIRED ). setDescription( "Name of the " + SOLVER::coupledSolverAttributePrefix() + " solver" ); + + this->registerWrapper( viewKeyStruct::writeCSVFlagString(), &m_writeCSV ). + setApplyDefaultValue( 0 ). + setInputFlag( dataRepository::InputFlags::OPTIONAL ). + setDescription( "Write statistics into a CSV file" ); } /** @@ -85,11 +92,33 @@ class FieldStatisticsBase : public TaskBase getDataContext(), m_solverName, LvArray::system::demangleType< SOLVER >() ), InputError ); + + // create dir for output + if( m_writeCSV > 0 ) + { + if( MpiWrapper::commRank() == 0 ) + { + makeDirsForPath( m_outputDir ); + } + // wait till the dir is created by rank 0 + MPI_Barrier( MPI_COMM_WORLD ); + } } + struct viewKeyStruct + { + static constexpr char const * writeCSVFlagString() { return "writeCSV"; } + }; + /// Pointer to the physics solver SOLVER * m_solver; + // Output directory + string const m_outputDir; + + // Flag to enable writing CSV output + integer m_writeCSV; + private: /// Name of the solver diff --git a/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseStatistics.cpp b/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseStatistics.cpp index fd01f5d3898..5f7bf339c4b 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseStatistics.cpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseStatistics.cpp @@ -31,7 +31,6 @@ #include "physicsSolvers/fluidFlow/FlowSolverBaseFields.hpp" #include "physicsSolvers/fluidFlow/IsothermalCompositionalMultiphaseBaseKernels.hpp" #include "physicsSolvers/fluidFlow/IsothermalCompositionalMultiphaseFVMKernels.hpp" -#include "fileIO/Outputs/OutputBase.hpp" namespace geos @@ -44,8 +43,7 @@ CompositionalMultiphaseStatistics::CompositionalMultiphaseStatistics( const stri Group * const parent ): Base( name, parent ), m_computeCFLNumbers( 0 ), - m_computeRegionStatistics( 1 ), - m_outputDir( joinPath( OutputBase::getOutputDirectory(), name ) ) + m_computeRegionStatistics( 1 ) { registerWrapper( viewKeyStruct::computeCFLNumbersString(), &m_computeCFLNumbers ). setApplyDefaultValue( 0 ). @@ -73,17 +71,6 @@ void CompositionalMultiphaseStatistics::postProcessInput() catalogName(), getDataContext() ), InputError ); } - - // create dir for output - if( getLogLevel() > 0 ) - { - if( MpiWrapper::commRank() == 0 ) - { - makeDirsForPath( m_outputDir ); - } - // wait till the dir is created by rank 0 - MPI_Barrier( MPI_COMM_WORLD ); - } } void CompositionalMultiphaseStatistics::registerDataOnMesh( Group & meshBodies ) @@ -125,7 +112,7 @@ void CompositionalMultiphaseStatistics::registerDataOnMesh( Group & meshBodies ) regionStatistics.componentMass.resizeDimension< 0, 1 >( numPhases, numComps ); // write output header - if( getLogLevel() > 0 && MpiWrapper::commRank() == 0 ) + if( m_writeCSV > 0 && MpiWrapper::commRank() == 0 ) { std::ofstream outputFile( m_outputDir + "/" + regionNames[i] + ".csv" ); integer const useMass = m_solver->getReference< integer >( CompositionalMultiphaseBase::viewKeyStruct::useMassFlagString() ); @@ -449,7 +436,7 @@ void CompositionalMultiphaseStatistics::computeRegionStatistics( real64 const ti GEOS_LOG_LEVEL_RANK_0( 1, GEOS_FMT( "{}, {} (time {} s): Component mass: {} {}", getName(), regionNames[i], time, regionStatistics.componentMass, massUnit ) ); - if( getLogLevel() > 0 && MpiWrapper::commRank() == 0 ) + if( m_writeCSV > 0 && MpiWrapper::commRank() == 0 ) { std::ofstream outputFile( m_outputDir + "/" + regionNames[i] + ".csv", std::ios_base::app ); outputFile << time << "," << regionStatistics.minPressure << "," << regionStatistics.averagePressure << "," << regionStatistics.maxPressure << "," << diff --git a/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseStatistics.hpp b/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseStatistics.hpp index 8c448195e56..a4a46ad166a 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseStatistics.hpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseStatistics.hpp @@ -154,9 +154,6 @@ class CompositionalMultiphaseStatistics : public FieldStatisticsBase< Compositio /// Threshold to decide whether a phase is considered "mobile" or not real64 m_relpermThreshold; - // Output directory - string const m_outputDir; - }; diff --git a/src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseStatistics.cpp b/src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseStatistics.cpp index aa905f08310..c74a9e906b1 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseStatistics.cpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseStatistics.cpp @@ -59,12 +59,23 @@ void SinglePhaseStatistics::registerDataOnMesh( Group & meshBodies ) region.registerWrapper< RegionStatistics >( viewKeyStruct::regionStatisticsString() ). setRestartFlags( RestartFlags::NO_WRITE ); region.excludeWrappersFromPacking( { viewKeyStruct::regionStatisticsString() } ); + + // write output header + if( m_writeCSV > 0 && MpiWrapper::commRank() == 0 ) + { + std::ofstream outputFile( m_outputDir + "/" + regionNames[i] + ".csv" ); + outputFile << + "Time [s],Min pressure [Pa],Average pressure [Pa],Max pressure [Pa],Min delta pressure [Pa],Max delta pressure [Pa]," << + "Min temperature [Pa],Average temperature [Pa],Max temperature [Pa],Total dynamic pore volume [rm^3],Total fluid mass [kg]"; + outputFile << std::endl; + outputFile.close(); + } } } ); } -bool SinglePhaseStatistics::execute( real64 const GEOS_UNUSED_PARAM( time_n ), - real64 const GEOS_UNUSED_PARAM( dt ), +bool SinglePhaseStatistics::execute( real64 const time_n, + real64 const dt, integer const GEOS_UNUSED_PARAM( cycleNumber ), integer const GEOS_UNUSED_PARAM( eventCounter ), real64 const GEOS_UNUSED_PARAM( eventProgress ), @@ -74,12 +85,14 @@ bool SinglePhaseStatistics::execute( real64 const GEOS_UNUSED_PARAM( time_n ), MeshLevel & mesh, arrayView1d< string const > const & regionNames ) { - computeRegionStatistics( mesh, regionNames ); + // current time is time_n + dt + computeRegionStatistics( time_n + dt, mesh, regionNames ); } ); return false; } -void SinglePhaseStatistics::computeRegionStatistics( MeshLevel & mesh, +void SinglePhaseStatistics::computeRegionStatistics( real64 const time, + MeshLevel & mesh, arrayView1d< string const > const & regionNames ) const { GEOS_MARK_FUNCTION; @@ -230,24 +243,29 @@ void SinglePhaseStatistics::computeRegionStatistics( MeshLevel & mesh, { regionStatistics.averagePressure = 0.0; regionStatistics.averageTemperature = 0.0; - GEOS_WARNING( getName() << ", " << regionNames[i] << - ": Cannot compute average pressure, temperature and total mass because region pore volume is zero." ); + GEOS_WARNING( GEOS_FMT( "{}, {}: Cannot compute average pressure & temperature because region pore volume is zero.", getName(), regionNames[i] ) ); } - GEOS_LOG_LEVEL_RANK_0( 1, getName() << ", " << regionNames[i] - << ": Pressure (min, average, max): " - << regionStatistics.minPressure << ", " << regionStatistics.averagePressure << ", " << regionStatistics.maxPressure << " Pa" ); - GEOS_LOG_LEVEL_RANK_0( 1, getName() << ", " << regionNames[i] - << ": Delta pressure (min, max): " - << regionStatistics.minDeltaPressure << ", " << regionStatistics.maxDeltaPressure << " Pa" ); - GEOS_LOG_LEVEL_RANK_0( 1, getName() << ", " << regionNames[i] - << ": Temperature (min, average, max): " - << regionStatistics.minTemperature << ", " << regionStatistics.averageTemperature << ", " << regionStatistics.maxTemperature << " K" ); - GEOS_LOG_LEVEL_RANK_0( 1, getName() << ", " << regionNames[i] - << ": Total dynamic pore volume: " << regionStatistics.totalPoreVolume << " rm^3" ); - GEOS_LOG_LEVEL_RANK_0( 1, getName() << ", " << regionNames[i] - << ": Total fluid mass: " << regionStatistics.totalMass << " kg" ); - + GEOS_LOG_LEVEL_RANK_0( 1, GEOS_FMT( "{}, {} (time {} s): Pressure (min, average, max): {}, {}, {} Pa", + getName(), regionNames[i], time, regionStatistics.minPressure, regionStatistics.averagePressure, regionStatistics.maxPressure ) ); + GEOS_LOG_LEVEL_RANK_0( 1, GEOS_FMT( "{}, {} (time {} s): Delta pressure (min, max): {}, {} Pa", + getName(), regionNames[i], time, regionStatistics.minDeltaPressure, regionStatistics.maxDeltaPressure ) ); + GEOS_LOG_LEVEL_RANK_0( 1, GEOS_FMT( "{}, {} (time {} s): Temperature (min, average, max): {}, {}, {} K", + getName(), regionNames[i], time, regionStatistics.minTemperature, regionStatistics.averageTemperature, regionStatistics.maxTemperature ) ); + GEOS_LOG_LEVEL_RANK_0( 1, GEOS_FMT( "{}, {} (time {} s): Total dynamic pore volume: {} rm^3", + getName(), regionNames[i], time, regionStatistics.totalPoreVolume ) ); + GEOS_LOG_LEVEL_RANK_0( 1, GEOS_FMT( "{}, {} (time {} s): Total fluid mass: {} kg", + getName(), regionNames[i], time, regionStatistics.totalMass ) ); + + if( m_writeCSV > 0 && MpiWrapper::commRank() == 0 ) + { + std::ofstream outputFile( m_outputDir + "/" + regionNames[i] + ".csv", std::ios_base::app ); + outputFile << time << "," << regionStatistics.minPressure << "," << regionStatistics.averagePressure << "," << regionStatistics.maxPressure << "," << + regionStatistics.minDeltaPressure << "," << regionStatistics.maxDeltaPressure << "," << + regionStatistics.minTemperature << "," << regionStatistics.averageTemperature << "," << regionStatistics.maxTemperature << "," << + regionStatistics.totalPoreVolume << "," << regionStatistics.totalMass << std::endl; + outputFile.close(); + } } } diff --git a/src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseStatistics.hpp b/src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseStatistics.hpp index 8d826c5034e..bfd1360fd92 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseStatistics.hpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseStatistics.hpp @@ -110,7 +110,8 @@ class SinglePhaseStatistics : public FieldStatisticsBase< SinglePhaseBase > * @param[in] mesh the mesh level object * @param[in] regionNames the array of target region names */ - void computeRegionStatistics( MeshLevel & mesh, + void computeRegionStatistics( real64 const time, + MeshLevel & mesh, arrayView1d< string const > const & regionNames ) const; diff --git a/src/coreComponents/physicsSolvers/fluidFlow/wells/CompositionalMultiphaseWell.cpp b/src/coreComponents/physicsSolvers/fluidFlow/wells/CompositionalMultiphaseWell.cpp index 46dc3c80861..6620d93c277 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/wells/CompositionalMultiphaseWell.cpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/wells/CompositionalMultiphaseWell.cpp @@ -251,7 +251,7 @@ void CompositionalMultiphaseWell::registerDataOnMesh( Group & meshBodies ) // write rates output header // the rank that owns the reference well element is responsible - if( getLogLevel() > 0 && subRegion.isLocallyOwned() ) + if( m_writeCSV > 0 && subRegion.isLocallyOwned() ) { string const wellControlsName = wellControls.getName(); string const massUnit = m_useMass ? "kg" : "mol"; @@ -1874,18 +1874,25 @@ void CompositionalMultiphaseWell::printRates( real64 const & time_n, string const wellControlsName = wellControls.getName(); // format: time,total_rate,total_vol_rate,phase0_vol_rate,phase1_vol_rate,... - std::ofstream outputFile( m_ratesOutputDir + "/" + wellControlsName + ".csv", std::ios_base::app ); - - outputFile << time_n + dt; + std::ofstream outputFile; + if( m_writeCSV > 0 ) + { + outputFile.open( m_ratesOutputDir + "/" + wellControlsName + ".csv", std::ios_base::app ); + outputFile << time_n + dt; + } if( !wellControls.isWellOpen( time_n + dt ) ) { GEOS_LOG( GEOS_FMT( "{}: well is shut", wellControlsName ) ); - // print all zeros in the rates file - outputFile << ",0.0,0.0,0.0"; - for( integer ip = 0; ip < numPhase; ++ip ) - outputFile << ",0.0"; - outputFile << std::endl; + if( outputFile.is_open()) + { + // print all zeros in the rates file + outputFile << ",0.0,0.0,0.0"; + for( integer ip = 0; ip < numPhase; ++ip ) + outputFile << ",0.0"; + outputFile << std::endl; + outputFile.close(); + } return; } @@ -1924,19 +1931,21 @@ void CompositionalMultiphaseWell::printRates( real64 const & time_n, real64 const currentTotalRate = connRate[iwelemRef]; GEOS_LOG( GEOS_FMT( "{}: BHP (at the specified reference elevation): {} Pa", wellControlsName, currentBHP ) ); - outputFile << "," << currentBHP; GEOS_LOG( GEOS_FMT( "{}: Total rate: {} {}/s; total {} volumetric rate: {} {}m3/s", wellControlsName, currentTotalRate, massUnit, conditionKey, currentTotalVolRate, unitKey ) ); - outputFile << "," << currentTotalRate << "," << currentTotalVolRate; for( integer ip = 0; ip < numPhase; ++ip ) - { GEOS_LOG( GEOS_FMT( "{}: Phase {} {} volumetric rate: {} {}m3/s", wellControlsName, ip, conditionKey, currentPhaseVolRate[ip], unitKey ) ); - outputFile << "," << currentPhaseVolRate[ip]; + if( outputFile.is_open()) + { + outputFile << "," << currentBHP; + outputFile << "," << currentTotalRate << "," << currentTotalVolRate; + for( integer ip = 0; ip < numPhase; ++ip ) + outputFile << "," << currentPhaseVolRate[ip]; + outputFile << std::endl; + outputFile.close(); } - outputFile << std::endl; } ); - outputFile.close(); } ); } ); } diff --git a/src/coreComponents/physicsSolvers/fluidFlow/wells/SinglePhaseWell.cpp b/src/coreComponents/physicsSolvers/fluidFlow/wells/SinglePhaseWell.cpp index 9f61be73ac7..a1f1777c5e3 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/wells/SinglePhaseWell.cpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/wells/SinglePhaseWell.cpp @@ -90,7 +90,7 @@ void SinglePhaseWell::registerDataOnMesh( Group & meshBodies ) wellControls.registerWrapper< real64 >( viewKeyStruct::dCurrentVolRate_dRateString() ); // write rates output header - if( getLogLevel() > 0 ) + if( m_writeCSV > 0 && subRegion.isLocallyOwned()) { string const wellControlsName = wellControls.getName(); integer const useSurfaceConditions = wellControls.useSurfaceConditions(); @@ -1049,15 +1049,22 @@ void SinglePhaseWell::printRates( real64 const & time_n, string const wellControlsName = wellControls.getName(); // format: time,total_rate,total_vol_rate - std::ofstream outputFile( m_ratesOutputDir + "/" + wellControlsName + ".csv", std::ios_base::app ); - - outputFile << time_n + dt; + std::ofstream outputFile; + if( m_writeCSV > 0 ) + { + outputFile.open( m_ratesOutputDir + "/" + wellControlsName + ".csv", std::ios_base::app ); + outputFile << time_n + dt; + } if( !wellControls.isWellOpen( time_n + dt ) ) { GEOS_LOG( GEOS_FMT( "{}: well is shut", wellControlsName ) ); - // print all zeros in the rates file - outputFile << ",0.0,0.0,0.0" << std::endl; + if( outputFile.is_open()) + { + // print all zeros in the rates file + outputFile << ",0.0,0.0,0.0" << std::endl; + outputFile.close(); + } return; } @@ -1083,12 +1090,15 @@ void SinglePhaseWell::printRates( real64 const & time_n, real64 const currentTotalRate = connRate[iwelemRef]; GEOS_LOG( GEOS_FMT( "{}: BHP (at the specified reference elevation): {} Pa", wellControlsName, currentBHP ) ); - outputFile << "," << currentBHP; GEOS_LOG( GEOS_FMT( "{}: Total rate: {} kg/s; total {} volumetric rate: {} {}m3/s", wellControlsName, currentTotalRate, conditionKey, currentTotalVolRate, unitKey ) ); - outputFile << "," << currentTotalRate << "," << currentTotalVolRate << std::endl; + if( outputFile.is_open()) + { + outputFile << "," << currentBHP; + outputFile << "," << currentTotalRate << "," << currentTotalVolRate << std::endl; + outputFile.close(); + } } ); - outputFile.close(); } ); } ); } diff --git a/src/coreComponents/physicsSolvers/fluidFlow/wells/WellSolverBase.cpp b/src/coreComponents/physicsSolvers/fluidFlow/wells/WellSolverBase.cpp index 0699870a3df..5086f5f2d83 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/wells/WellSolverBase.cpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/wells/WellSolverBase.cpp @@ -43,6 +43,11 @@ WellSolverBase::WellSolverBase( string const & name, { this->getWrapper< string >( viewKeyStruct::discretizationString() ). setInputFlag( InputFlags::FALSE ); + + this->registerWrapper( viewKeyStruct::writeCSVFlagString(), &m_writeCSV ). + setApplyDefaultValue( 0 ). + setInputFlag( dataRepository::InputFlags::OPTIONAL ). + setDescription( "Write rates into a CSV file" ); } Group * WellSolverBase::createChild( string const & childKey, string const & childName ) @@ -73,7 +78,7 @@ void WellSolverBase::postProcessInput() SolverBase::postProcessInput(); // create dir for rates output - if( getLogLevel() > 0 ) + if( m_writeCSV > 0 ) { if( MpiWrapper::commRank() == 0 ) { diff --git a/src/coreComponents/physicsSolvers/fluidFlow/wells/WellSolverBase.hpp b/src/coreComponents/physicsSolvers/fluidFlow/wells/WellSolverBase.hpp index f07890a1363..6f53122b71e 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/wells/WellSolverBase.hpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/wells/WellSolverBase.hpp @@ -273,6 +273,7 @@ class WellSolverBase : public SolverBase struct viewKeyStruct : SolverBase::viewKeyStruct { static constexpr char const * fluidNamesString() { return "fluidNames"; } + static constexpr char const * writeCSVFlagString() { return "writeCSV"; } }; private: @@ -311,6 +312,7 @@ class WellSolverBase : public SolverBase /// the number of Degrees of Freedom per reservoir element integer m_numDofPerResElement; + integer m_writeCSV; string const m_ratesOutputDir; }; diff --git a/src/coreComponents/physicsSolvers/solidMechanics/SolidMechanicsStatistics.cpp b/src/coreComponents/physicsSolvers/solidMechanics/SolidMechanicsStatistics.cpp index d4c7bfa3152..ec97b4bc55d 100644 --- a/src/coreComponents/physicsSolvers/solidMechanics/SolidMechanicsStatistics.cpp +++ b/src/coreComponents/physicsSolvers/solidMechanics/SolidMechanicsStatistics.cpp @@ -33,26 +33,9 @@ using namespace fields; SolidMechanicsStatistics::SolidMechanicsStatistics( const string & name, Group * const parent ): - Base( name, parent ), - m_outputDir( joinPath( OutputBase::getOutputDirectory(), name ) ) + Base( name, parent ) {} -void SolidMechanicsStatistics::postProcessInput() -{ - Base::postProcessInput(); - - // create dir for output - if( getLogLevel() > 0 ) - { - if( MpiWrapper::commRank() == 0 ) - { - makeDirsForPath( m_outputDir ); - } - // wait till the dir is created by rank 0 - MPI_Barrier( MPI_COMM_WORLD ); - } -} - void SolidMechanicsStatistics::registerDataOnMesh( Group & meshBodies ) { // the fields have to be registered in "registerDataOnMesh" (and not later) @@ -78,7 +61,7 @@ void SolidMechanicsStatistics::registerDataOnMesh( Group & meshBodies ) nodeStatistics.maxDisplacement.resizeDimension< 0 >( 3 ); // write output header - if( getLogLevel() > 0 && MpiWrapper::commRank() == 0 ) + if( m_writeCSV > 0 && MpiWrapper::commRank() == 0 ) { std::ofstream outputFile( m_outputDir + "/" + mesh.getName() + "_node_statistics" + ".csv" ); outputFile << "Time [s],Min displacement X [m],Min displacement Y [m],Min displacement Z [m]," @@ -174,7 +157,7 @@ void SolidMechanicsStatistics::computeNodeStatistics( MeshLevel & mesh, real64 c getName(), time, nodeStatistics.maxDisplacement[0], nodeStatistics.maxDisplacement[1], nodeStatistics.maxDisplacement[2] ) ); - if( getLogLevel() > 0 && MpiWrapper::commRank() == 0 ) + if( m_writeCSV > 0 && MpiWrapper::commRank() == 0 ) { std::ofstream outputFile( m_outputDir + "/" + mesh.getName() + "_node_statistics" + ".csv", std::ios_base::app ); outputFile << time; diff --git a/src/coreComponents/physicsSolvers/solidMechanics/SolidMechanicsStatistics.hpp b/src/coreComponents/physicsSolvers/solidMechanics/SolidMechanicsStatistics.hpp index 31d4c45c74d..77c32e85e6b 100644 --- a/src/coreComponents/physicsSolvers/solidMechanics/SolidMechanicsStatistics.hpp +++ b/src/coreComponents/physicsSolvers/solidMechanics/SolidMechanicsStatistics.hpp @@ -89,12 +89,8 @@ class SolidMechanicsStatistics : public FieldStatisticsBase< SolidMechanicsLagra array1d< real64 > maxDisplacement; }; - void postProcessInput() override; - void registerDataOnMesh( Group & meshBodies ) override; - // Output directory - string const m_outputDir; }; diff --git a/src/coreComponents/schema/docs/CompositionalMultiphaseStatistics.rst b/src/coreComponents/schema/docs/CompositionalMultiphaseStatistics.rst index 6be91e9fb68..4a386d1d1c3 100644 --- a/src/coreComponents/schema/docs/CompositionalMultiphaseStatistics.rst +++ b/src/coreComponents/schema/docs/CompositionalMultiphaseStatistics.rst @@ -9,6 +9,7 @@ flowSolverName groupNameRef required Name of the flow solver logLevel integer 0 Log level name groupName required A name is required for any non-unique nodes relpermThreshold real64 1e-06 Flag to decide whether a phase is considered mobile (when the relperm is above the threshold) or immobile (when the relperm is below the threshold) in metric 2 +writeCSV integer 0 Write statistics into a CSV file ======================= ============ ======== =============================================================================================================================================================== diff --git a/src/coreComponents/schema/docs/CompositionalMultiphaseWell.rst b/src/coreComponents/schema/docs/CompositionalMultiphaseWell.rst index 30233ab1445..b072785ff00 100644 --- a/src/coreComponents/schema/docs/CompositionalMultiphaseWell.rst +++ b/src/coreComponents/schema/docs/CompositionalMultiphaseWell.rst @@ -13,6 +13,7 @@ maxRelativePressureChange real64 1 Maximum (relative) cha name groupName required A name is required for any non-unique nodes targetRegions groupNameRef_array required Allowable regions that the solver may be applied to. Note that this does not indicate that the solver will be applied to these regions, only that allocation will occur such that the solver may be applied to these regions. The decision about what regions this solver will beapplied to rests in the EventManager. useMass integer 0 Use total mass equation +writeCSV integer 0 Write rates into a CSV file LinearSolverParameters node unique :ref:`XML_LinearSolverParameters` NonlinearSolverParameters node unique :ref:`XML_NonlinearSolverParameters` WellControls node :ref:`XML_WellControls` diff --git a/src/coreComponents/schema/docs/SinglePhaseStatistics.rst b/src/coreComponents/schema/docs/SinglePhaseStatistics.rst index d3b6b815b17..ff3105519bd 100644 --- a/src/coreComponents/schema/docs/SinglePhaseStatistics.rst +++ b/src/coreComponents/schema/docs/SinglePhaseStatistics.rst @@ -6,6 +6,7 @@ Name Type Default Description flowSolverName groupNameRef required Name of the flow solver logLevel integer 0 Log level name groupName required A name is required for any non-unique nodes +writeCSV integer 0 Write statistics into a CSV file ============== ============ ======== =========================================== diff --git a/src/coreComponents/schema/docs/SinglePhaseWell.rst b/src/coreComponents/schema/docs/SinglePhaseWell.rst index b285015b443..ba2a66c6297 100644 --- a/src/coreComponents/schema/docs/SinglePhaseWell.rst +++ b/src/coreComponents/schema/docs/SinglePhaseWell.rst @@ -8,6 +8,7 @@ initialDt real64 1e+99 Initial time-step value re logLevel integer 0 Log level name groupName required A name is required for any non-unique nodes targetRegions groupNameRef_array required Allowable regions that the solver may be applied to. Note that this does not indicate that the solver will be applied to these regions, only that allocation will occur such that the solver may be applied to these regions. The decision about what regions this solver will beapplied to rests in the EventManager. +writeCSV integer 0 Write rates into a CSV file LinearSolverParameters node unique :ref:`XML_LinearSolverParameters` NonlinearSolverParameters node unique :ref:`XML_NonlinearSolverParameters` WellControls node :ref:`XML_WellControls` diff --git a/src/coreComponents/schema/docs/SolidMechanicsStatistics.rst b/src/coreComponents/schema/docs/SolidMechanicsStatistics.rst index c1caaa49863..2395830a112 100644 --- a/src/coreComponents/schema/docs/SolidMechanicsStatistics.rst +++ b/src/coreComponents/schema/docs/SolidMechanicsStatistics.rst @@ -6,6 +6,7 @@ Name Type Default Description logLevel integer 0 Log level name groupName required A name is required for any non-unique nodes solidSolverName groupNameRef required Name of the solid solver +writeCSV integer 0 Write statistics into a CSV file =============== ============ ======== =========================================== diff --git a/src/coreComponents/schema/schema.xsd b/src/coreComponents/schema/schema.xsd index a12097730ed..20d51a57d98 100644 --- a/src/coreComponents/schema/schema.xsd +++ b/src/coreComponents/schema/schema.xsd @@ -2437,6 +2437,8 @@ Local - Add stabilization only to interiors of macro elements.--> + + @@ -3146,6 +3148,8 @@ Local - Add stabilization only to interiors of macro elements.--> + + @@ -3390,6 +3394,8 @@ Local - Add stabilization only to interiors of macro elements.--> + + @@ -3500,6 +3506,8 @@ Local - Add stabilization only to interiors of macro elements.--> + + @@ -3520,6 +3528,8 @@ Local - Add stabilization only to interiors of macro elements.--> + + From 3ccaaf9a1d57d81dc305d03f86f6f1e124ecb582 Mon Sep 17 00:00:00 2001 From: Pavel Tomin Date: Mon, 22 Jan 2024 15:43:18 -0600 Subject: [PATCH 38/40] Fix for black oil (#2937) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix for black oil --------- Co-authored-by: Pavel Tomin <“paveltomin@users.noreply.github.com”> Co-authored-by: Matteo Cusini <49037133+CusiniM@users.noreply.github.com> --- integratedTests | 2 +- .../fluid/multifluid/blackOil/BlackOilFluid.hpp | 12 +++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/integratedTests b/integratedTests index 3c5c18a23ff..d92088aa880 160000 --- a/integratedTests +++ b/integratedTests @@ -1 +1 @@ -Subproject commit 3c5c18a23ff238de934b647a421c966742d7ff7c +Subproject commit d92088aa88028e976408dcfc75fd0e932974c90a diff --git a/src/coreComponents/constitutive/fluid/multifluid/blackOil/BlackOilFluid.hpp b/src/coreComponents/constitutive/fluid/multifluid/blackOil/BlackOilFluid.hpp index bfd8bfedce4..52716ec7a4e 100644 --- a/src/coreComponents/constitutive/fluid/multifluid/blackOil/BlackOilFluid.hpp +++ b/src/coreComponents/constitutive/fluid/multifluid/blackOil/BlackOilFluid.hpp @@ -340,7 +340,7 @@ BlackOilFluid::KernelWrapper:: real64 phaseMolecularWeight[NP_BO]{}; real64 dPhaseMolecularWeight[NP_BO][NC_BO+2]{}; - // 1. Convert to mass if necessary + // 1. Convert to moles if necessary if( m_useMass ) { @@ -529,8 +529,8 @@ BlackOilFluid::KernelWrapper:: phaseFraction.value[ipWater] = zw; // oil - phaseCompFraction.value[ipOil][icOil] = zo; - phaseCompFraction.value[ipOil][icGas] = zg; + phaseCompFraction.value[ipOil][icOil] = zo / ( 1 - zw ); + phaseCompFraction.value[ipOil][icGas] = zg / ( 1 - zw ); phaseCompFraction.value[ipOil][icWater] = 0.0; // gas @@ -542,8 +542,10 @@ BlackOilFluid::KernelWrapper:: { phaseFraction.derivs[ipOil][Deriv::dC+icWater] = -1.0; phaseFraction.derivs[ipWater][Deriv::dC+icWater] = 1.0; - phaseCompFraction.derivs[ipOil][icOil][Deriv::dC+icOil] = 1.0; - phaseCompFraction.derivs[ipOil][icGas][Deriv::dC+icGas] = 1.0; + phaseCompFraction.derivs[ipOil][icOil][Deriv::dC+icOil] = 1 / ( 1 - zw ); + phaseCompFraction.derivs[ipOil][icOil][Deriv::dC+icWater] = zo / (( 1 - zw )*( 1 - zw )); + phaseCompFraction.derivs[ipOil][icGas][Deriv::dC+icGas] = 1 / ( 1 - zw ); + phaseCompFraction.derivs[ipOil][icGas][Deriv::dC+icWater] = zg / (( 1 - zw )*( 1 - zw )); } } } From c446b53993b11add95be3563544f863dca4f27ee Mon Sep 17 00:00:00 2001 From: Randolph Settgast Date: Mon, 22 Jan 2024 15:09:28 -0800 Subject: [PATCH 39/40] Misc cleanup of CI and README (#2944) * Cleanup badges in README.md * Added CI badge and docs badge * rename if_pull_request_is_assigned to if_not_unassigned_pull_request, and try to evaluate NUM_ASSIGNEES in if_not_unassigned_pull_request job instead of is_not_draft_pull_request * remove NUM_ASSIGNEES variable from yml --------- Co-authored-by: TotoGaz <49004943+TotoGaz@users.noreply.github.com> --- .github/workflows/ci_tests.yml | 47 +++++++++++++++++----------------- README.md | 3 ++- 2 files changed, 26 insertions(+), 24 deletions(-) diff --git a/.github/workflows/ci_tests.yml b/.github/workflows/ci_tests.yml index 0d6ecd14835..11784f7afa2 100644 --- a/.github/workflows/ci_tests.yml +++ b/.github/workflows/ci_tests.yml @@ -17,12 +17,11 @@ jobs: # Jobs will be cancelled if PR is a draft. # PR status must be "Open" to run CI. - is_pull_request_a_draft: + is_not_draft_pull_request: # Everywhere in this workflow, we use the most recent ubuntu distribution available in Github Actions # to ensure maximum support of google cloud's sdk. runs-on: ubuntu-22.04 outputs: - NUM_ASSIGNEES: ${{ steps.extract_pr_info.outputs.NUM_ASSIGNEES }} DOCKER_IMAGE_TAG: ${{ steps.extract_docker_image_tag.outputs.DOCKER_IMAGE_TAG }} LABELS: ${{ steps.extract_pr_info.outputs.LABELS }} steps: @@ -39,7 +38,6 @@ jobs: if [[ $draft_status == true ]]; then exit 1 ; fi # If the workflow is meant to continue, we extract additional information for the json of the pr. - echo "NUM_ASSIGNEES=$(echo ${pr_json} | jq '.assignees | length')" >> "$GITHUB_OUTPUT" echo "LABELS=$(echo ${pr_json} | jq -crM '[.labels[].name]')" >> "$GITHUB_OUTPUT" # The TPL tag is contained in the codespaces configuration to avoid duplications. - name: Checkout .devcontainer/devcontainer.json @@ -58,19 +56,22 @@ jobs: # PR must be assigned to be merged. # This job will fail if this is not the case. - is_pull_request_assigned: - needs: [is_pull_request_a_draft] + if_not_unassigned_pull_request: + needs: [is_not_draft_pull_request] runs-on: ubuntu-22.04 steps: - - name: Check that the PR is assigned + - name: If this is a PR, Check that it is assigned run: | - echo "There are ${{ needs.is_pull_request_a_draft.outputs.NUM_ASSIGNEES }} on this PR." - ${{ needs.is_pull_request_a_draft.outputs.NUM_ASSIGNEES > 0 }} + if [[ ${{github.event_name}} != 'pull_request' ]]; then exit 0 ; fi + pr_json=$(curl -H "Accept: application/vnd.github+json" https://api.github.com/repos/${{ github.repository }}/pulls/${{ github.event.number }}) + NUM_ASSIGNEES=$(echo ${pr_json} | jq '.assignees | length') + echo "There are ${NUM_ASSIGNEES} assignees on this PR." + if [[ $NUM_ASSIGNEES == 0 ]]; then exit 1 ; fi # Validates that the PR is still pointing to the HEAD of the main branch of the submodules repositories. # (There are exceptions, read the script about those). are_submodules_in_sync: - needs: [is_pull_request_a_draft] + needs: [is_not_draft_pull_request] runs-on: ubuntu-22.04 steps: # The integrated test submodule repository contains large data (using git lfs). @@ -87,7 +88,7 @@ jobs: check_code_style_and_documentation: name: ${{ matrix.name }} - needs: [is_pull_request_a_draft] + needs: [is_not_draft_pull_request] strategy: fail-fast : false matrix: @@ -102,7 +103,7 @@ jobs: with: BUILD_AND_TEST_CLI_ARGS: ${{ matrix.BUILD_AND_TEST_ARGS }} CMAKE_BUILD_TYPE: Release - DOCKER_IMAGE_TAG: ${{ needs.is_pull_request_a_draft.outputs.DOCKER_IMAGE_TAG }} + DOCKER_IMAGE_TAG: ${{ needs.is_not_draft_pull_request.outputs.DOCKER_IMAGE_TAG }} DOCKER_REPOSITORY: geosx/ubuntu20.04-gcc9 RUNS_ON: ubuntu-22.04 USE_SCCACHE: false @@ -111,7 +112,7 @@ jobs: # Those are quite fast and can efficiently benefit from the `sccache' tool to make them even faster. cpu_builds: name: ${{ matrix.name }} - needs: [is_pull_request_a_draft] + needs: [is_not_draft_pull_request] strategy: # In-progress jobs will not be cancelled if there is a failure fail-fast : false @@ -166,7 +167,7 @@ jobs: uses: ./.github/workflows/build_and_test.yml with: CMAKE_BUILD_TYPE: ${{ matrix.CMAKE_BUILD_TYPE }} - DOCKER_IMAGE_TAG: ${{ needs.is_pull_request_a_draft.outputs.DOCKER_IMAGE_TAG }} + DOCKER_IMAGE_TAG: ${{ needs.is_not_draft_pull_request.outputs.DOCKER_IMAGE_TAG }} DOCKER_REPOSITORY: ${{ matrix.DOCKER_REPOSITORY }} ENABLE_HYPRE: ${{ matrix.ENABLE_HYPRE }} ENABLE_TRILINOS: ${{ matrix.ENABLE_TRILINOS }} @@ -179,16 +180,16 @@ jobs: # Note: The integrated tests are optional and are (for the moment) run for convenience only. run_integrated_tests: needs: - - is_pull_request_a_draft + - is_not_draft_pull_request - cpu_builds - if: "${{ contains( fromJSON( needs.is_pull_request_a_draft.outputs.LABELS ), 'ci: run integrated tests') }}" + if: "${{ contains( fromJSON( needs.is_not_draft_pull_request.outputs.LABELS ), 'ci: run integrated tests') }}" uses: ./.github/workflows/build_and_test.yml secrets: inherit with: BUILD_AND_TEST_CLI_ARGS: --build-exe-only BUILD_TYPE: integrated_tests CMAKE_BUILD_TYPE: Release - DOCKER_IMAGE_TAG: ${{ needs.is_pull_request_a_draft.outputs.DOCKER_IMAGE_TAG }} + DOCKER_IMAGE_TAG: ${{ needs.is_not_draft_pull_request.outputs.DOCKER_IMAGE_TAG }} DOCKER_REPOSITORY: geosx/ubuntu22.04-gcc11 ENABLE_HYPRE: ON ENABLE_TRILINOS: OFF @@ -197,14 +198,14 @@ jobs: code_coverage: needs: - - is_pull_request_a_draft + - is_not_draft_pull_request uses: ./.github/workflows/build_and_test.yml secrets: inherit with: BUILD_AND_TEST_CLI_ARGS: "--no-run-unit-tests" CMAKE_BUILD_TYPE: Debug CODE_COVERAGE: true - DOCKER_IMAGE_TAG: ${{ needs.is_pull_request_a_draft.outputs.DOCKER_IMAGE_TAG }} + DOCKER_IMAGE_TAG: ${{ needs.is_not_draft_pull_request.outputs.DOCKER_IMAGE_TAG }} DOCKER_REPOSITORY: geosx/ubuntu22.04-gcc11 ENABLE_HYPRE: ON ENABLE_TRILINOS: OFF @@ -216,8 +217,8 @@ jobs: cuda_builds: name: ${{ matrix.name }} needs: - - is_pull_request_a_draft - if: "${{ contains( fromJSON( needs.is_pull_request_a_draft.outputs.LABELS ), 'ci: ready to be merged') }}" + - is_not_draft_pull_request + if: "${{ contains( fromJSON( needs.is_not_draft_pull_request.outputs.LABELS ), 'ci: ready to be merged') }}" strategy: # In-progress jobs will not be cancelled if there is a failure fail-fast : false @@ -258,7 +259,7 @@ jobs: with: BUILD_AND_TEST_CLI_ARGS: ${{ matrix.BUILD_AND_TEST_CLI_ARGS }} CMAKE_BUILD_TYPE: ${{ matrix.CMAKE_BUILD_TYPE }} - DOCKER_IMAGE_TAG: ${{ needs.is_pull_request_a_draft.outputs.DOCKER_IMAGE_TAG }} + DOCKER_IMAGE_TAG: ${{ needs.is_not_draft_pull_request.outputs.DOCKER_IMAGE_TAG }} DOCKER_REPOSITORY: ${{ matrix.DOCKER_REPOSITORY }} ENABLE_HYPRE_DEVICE: ${{ matrix.ENABLE_HYPRE_DEVICE }} ENABLE_HYPRE: ${{ matrix.ENABLE_HYPRE }} @@ -271,7 +272,7 @@ jobs: check_that_all_jobs_succeeded: runs-on: ubuntu-22.04 needs: - - is_pull_request_assigned + - if_not_unassigned_pull_request - are_submodules_in_sync - check_code_style_and_documentation - cpu_builds @@ -280,7 +281,7 @@ jobs: steps: - run: | ${{ - needs.is_pull_request_assigned.result == 'success' && + needs.if_not_unassigned_pull_request.result == 'success' && needs.are_submodules_in_sync.result == 'success' && needs.check_code_style_and_documentation.result == 'success' && needs.cpu_builds.result == 'success' && diff --git a/README.md b/README.md index d634a08ef09..ee7671ba61e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ [![DOI](https://zenodo.org/badge/131810578.svg)](https://zenodo.org/badge/latestdoi/131810578) - [![codecov](https://codecov.io/github/GEOS-DEV/GEOS/graph/badge.svg?token=0VTEHPQG58)](https://codecov.io/github/GEOS-DEV/GEOS) +![CI](https://github.com/GEOS-DEV/GEOS/actions/workflows/ci_tests.yml/badge.svg) +[![docs](https://readthedocs.com/projects/geosx-geosx/badge/?version=latest)](https://geosx-geosx.readthedocs-hosted.com/en/latest/) Welcome to the GEOS project! ----------------------------- From e322e0a962f433a20eb3631d57f420c110039d40 Mon Sep 17 00:00:00 2001 From: Lionel Untereiner Date: Tue, 23 Jan 2024 00:23:59 +0100 Subject: [PATCH 40/40] use lvarray::math namespace (#2927) * use lvarray::math instead of std:: --- .../ElasticFirstOrderWaveEquationSEMKernel.hpp | 6 +++--- .../wavePropagation/ElasticWaveEquationSEMKernel.hpp | 6 +++--- .../physicsSolvers/wavePropagation/WaveSolverUtils.hpp | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/coreComponents/physicsSolvers/wavePropagation/ElasticFirstOrderWaveEquationSEMKernel.hpp b/src/coreComponents/physicsSolvers/wavePropagation/ElasticFirstOrderWaveEquationSEMKernel.hpp index 262fe06824e..80aa82c77b3 100644 --- a/src/coreComponents/physicsSolvers/wavePropagation/ElasticFirstOrderWaveEquationSEMKernel.hpp +++ b/src/coreComponents/physicsSolvers/wavePropagation/ElasticFirstOrderWaveEquationSEMKernel.hpp @@ -305,9 +305,9 @@ struct DampingMatrixKernel for( localIndex q = 0; q < numNodesPerFace; ++q ) { real32 const aux = density[e] * m_finiteElement.computeDampingTerm( q, xLocal ); - real32 const localIncrementx = density[e] * (velocityVp[e] * abs( nx ) + velocityVs[e] * sqrt( pow( ny, 2 ) + pow( nz, 2 ) ) ) * aux; - real32 const localIncrementy = density[e] * (velocityVp[e] * abs( ny ) + velocityVs[e] * sqrt( pow( nx, 2 ) + pow( nz, 2 ) ) ) * aux; - real32 const localIncrementz = density[e] * (velocityVp[e] * abs( nz ) + velocityVs[e] * sqrt( pow( nx, 2 ) + pow( ny, 2 ) ) ) * aux; + real32 const localIncrementx = density[e] * (velocityVp[e] * LvArray::math::abs( nx ) + velocityVs[e] * LvArray::math::sqrt( pow( ny, 2 ) + pow( nz, 2 ) ) ) * aux; + real32 const localIncrementy = density[e] * (velocityVp[e] * LvArray::math::abs( ny ) + velocityVs[e] * LvArray::math::sqrt( pow( nx, 2 ) + pow( nz, 2 ) ) ) * aux; + real32 const localIncrementz = density[e] * (velocityVp[e] * LvArray::math::abs( nz ) + velocityVs[e] * LvArray::math::sqrt( pow( nx, 2 ) + pow( ny, 2 ) ) ) * aux; RAJA::atomicAdd< ATOMIC_POLICY >( &dampingx[facesToNodes( f, q )], localIncrementx ); RAJA::atomicAdd< ATOMIC_POLICY >( &dampingy[facesToNodes( f, q )], localIncrementy ); diff --git a/src/coreComponents/physicsSolvers/wavePropagation/ElasticWaveEquationSEMKernel.hpp b/src/coreComponents/physicsSolvers/wavePropagation/ElasticWaveEquationSEMKernel.hpp index 636abda029a..00970fa61fd 100644 --- a/src/coreComponents/physicsSolvers/wavePropagation/ElasticWaveEquationSEMKernel.hpp +++ b/src/coreComponents/physicsSolvers/wavePropagation/ElasticWaveEquationSEMKernel.hpp @@ -328,9 +328,9 @@ struct DampingMatrixKernel for( localIndex q = 0; q < numNodesPerFace; ++q ) { real32 const aux = density[e] * m_finiteElement.computeDampingTerm( q, xLocal ); - real32 const localIncrementx = aux * ( velocityVp[e] * abs( nx ) + velocityVs[e] * sqrt( pow( ny, 2 ) + pow( nz, 2 ) ) ); - real32 const localIncrementy = aux * ( velocityVp[e] * abs( ny ) + velocityVs[e] * sqrt( pow( nx, 2 ) + pow( nz, 2 ) ) ); - real32 const localIncrementz = aux * ( velocityVp[e] * abs( nz ) + velocityVs[e] * sqrt( pow( nx, 2 ) + pow( ny, 2 ) ) ); + real32 const localIncrementx = aux * ( velocityVp[e] * LvArray::math::abs( nx ) + velocityVs[e] * LvArray::math::sqrt( pow( ny, 2 ) + pow( nz, 2 ) ) ); + real32 const localIncrementy = aux * ( velocityVp[e] * LvArray::math::abs( ny ) + velocityVs[e] * LvArray::math::sqrt( pow( nx, 2 ) + pow( nz, 2 ) ) ); + real32 const localIncrementz = aux * ( velocityVp[e] * LvArray::math::abs( nz ) + velocityVs[e] * LvArray::math::sqrt( pow( nx, 2 ) + pow( ny, 2 ) ) ); RAJA::atomicAdd< ATOMIC_POLICY >( &dampingx[facesToNodes( f, q )], localIncrementx ); RAJA::atomicAdd< ATOMIC_POLICY >( &dampingy[facesToNodes( f, q )], localIncrementy ); diff --git a/src/coreComponents/physicsSolvers/wavePropagation/WaveSolverUtils.hpp b/src/coreComponents/physicsSolvers/wavePropagation/WaveSolverUtils.hpp index 1e856597658..794df9fe54e 100644 --- a/src/coreComponents/physicsSolvers/wavePropagation/WaveSolverUtils.hpp +++ b/src/coreComponents/physicsSolvers/wavePropagation/WaveSolverUtils.hpp @@ -175,7 +175,7 @@ struct WaveSolverUtils { real64 const time_np1 = time_n + dt; - real32 const a1 = abs( dt ) < epsilonLoc ? 1.0 : (time_np1 - timeSeismo) / dt; + real32 const a1 = LvArray::math::abs( dt ) < epsilonLoc ? 1.0 : (time_np1 - timeSeismo) / dt; real32 const a2 = 1.0 - a1; localIndex const nReceivers = receiverConstants.size( 0 );