From 837a333093bc2df67b7080487ad9791352c16cc3 Mon Sep 17 00:00:00 2001 From: Svenn Tveit Date: Thu, 6 Nov 2025 15:31:27 +0100 Subject: [PATCH 01/19] TPSA implementation with coupling to Flow. - TPSA-Flow coupling done at the nonlinear solver step level: - -- Lagged coupling -> TPSA lags behind one time step - -- Fixed-stress -> Iterate until Flow and TPSA converge - Generic TPSA solver using Newton with associated linearizer. - Linear elasticity equations implemented - Specialized version of ISTL linear solver for TPSA - Output to restart files and VTK --- CMakeLists.txt | 4 +- CMakeLists_files.cmake | 25 + flow/flow_blackoil_tpsa.cpp | 104 +++ flow/flow_blackoil_tpsa.hpp | 35 + flow/flow_blackoil_tpsa_main.cpp | 28 + flow/flow_gaswater_dissolution_tpsa.cpp | 136 ++++ flow/flow_gaswater_dissolution_tpsa.hpp | 34 + flow/flow_gaswater_dissolution_tpsa_main.cpp | 28 + flow/flow_onephase_tpsa.cpp | 127 +++ flow/flow_onephase_tpsa.hpp | 34 + flow/flow_onephase_tpsa_main.cpp | 28 + .../blackoil/blackoilintensivequantities.hh | 26 + opm/models/blackoil/blackoilproblem.hh | 60 ++ .../discretization/common/tpsalinearizer.hpp | 692 +++++++++++++++++ opm/models/io/vtktpsamodule.hpp | 198 +++++ opm/models/io/vtktpsaparams.cpp | 51 ++ opm/models/io/vtktpsaparams.hpp | 55 ++ opm/models/tpsa/elasticityindices.hpp | 64 ++ .../tpsa/elasticitylocalresidualtpsa.hpp | 395 ++++++++++ .../tpsa/elasticityprimaryvariables.hpp | 142 ++++ opm/models/tpsa/tpsabaseproperties.hpp | 86 +++ opm/models/tpsa/tpsamodel.hpp | 518 +++++++++++++ .../tpsa/tpsanewtonconvergencewriter.hpp | 88 +++ opm/models/tpsa/tpsanewtonmethod.hpp | 731 ++++++++++++++++++ opm/models/tpsa/tpsanewtonmethodparams.cpp | 82 ++ opm/models/tpsa/tpsanewtonmethodparams.hpp | 78 ++ opm/simulators/flow/BlackoilModelTPSA.hpp | 220 ++++++ opm/simulators/flow/FacePropertiesTPSA.cpp | 76 ++ opm/simulators/flow/FacePropertiesTPSA.hpp | 137 ++++ .../flow/FacePropertiesTPSA_impl.hpp | 539 +++++++++++++ opm/simulators/flow/FlowGenericProblem.hpp | 15 + .../flow/FlowGenericProblem_impl.hpp | 63 ++ opm/simulators/flow/FlowProblem.hpp | 33 + opm/simulators/flow/FlowProblemTPSA.hpp | 507 ++++++++++++ opm/simulators/flow/MainDispatchDynamic.cpp | 19 + opm/simulators/flow/TTagFlowProblemTPSA.hpp | 160 ++++ opm/simulators/linalg/ISTLSolverTPSA.hpp | 388 ++++++++++ .../linalg/TPSALinearSolverParameters.cpp | 108 +++ .../linalg/TPSALinearSolverParameters.hpp | 61 ++ .../linalg/setupPropertyTreeTPSA.cpp | 116 +++ .../linalg/setupPropertyTreeTPSA.hpp | 39 + tests/test_face_properties.cpp | 192 +++++ 42 files changed, 6520 insertions(+), 2 deletions(-) create mode 100644 flow/flow_blackoil_tpsa.cpp create mode 100644 flow/flow_blackoil_tpsa.hpp create mode 100644 flow/flow_blackoil_tpsa_main.cpp create mode 100644 flow/flow_gaswater_dissolution_tpsa.cpp create mode 100644 flow/flow_gaswater_dissolution_tpsa.hpp create mode 100644 flow/flow_gaswater_dissolution_tpsa_main.cpp create mode 100644 flow/flow_onephase_tpsa.cpp create mode 100644 flow/flow_onephase_tpsa.hpp create mode 100644 flow/flow_onephase_tpsa_main.cpp create mode 100644 opm/models/discretization/common/tpsalinearizer.hpp create mode 100644 opm/models/io/vtktpsamodule.hpp create mode 100644 opm/models/io/vtktpsaparams.cpp create mode 100644 opm/models/io/vtktpsaparams.hpp create mode 100644 opm/models/tpsa/elasticityindices.hpp create mode 100644 opm/models/tpsa/elasticitylocalresidualtpsa.hpp create mode 100644 opm/models/tpsa/elasticityprimaryvariables.hpp create mode 100644 opm/models/tpsa/tpsabaseproperties.hpp create mode 100644 opm/models/tpsa/tpsamodel.hpp create mode 100644 opm/models/tpsa/tpsanewtonconvergencewriter.hpp create mode 100644 opm/models/tpsa/tpsanewtonmethod.hpp create mode 100644 opm/models/tpsa/tpsanewtonmethodparams.cpp create mode 100644 opm/models/tpsa/tpsanewtonmethodparams.hpp create mode 100644 opm/simulators/flow/BlackoilModelTPSA.hpp create mode 100644 opm/simulators/flow/FacePropertiesTPSA.cpp create mode 100644 opm/simulators/flow/FacePropertiesTPSA.hpp create mode 100644 opm/simulators/flow/FacePropertiesTPSA_impl.hpp create mode 100644 opm/simulators/flow/FlowProblemTPSA.hpp create mode 100644 opm/simulators/flow/TTagFlowProblemTPSA.hpp create mode 100644 opm/simulators/linalg/ISTLSolverTPSA.hpp create mode 100644 opm/simulators/linalg/TPSALinearSolverParameters.cpp create mode 100644 opm/simulators/linalg/TPSALinearSolverParameters.hpp create mode 100644 opm/simulators/linalg/setupPropertyTreeTPSA.cpp create mode 100644 opm/simulators/linalg/setupPropertyTreeTPSA.hpp create mode 100644 tests/test_face_properties.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index c1eb0b17371..f78248935b2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -599,8 +599,8 @@ set(FLOW_MODELS blackoil blackoil_temp brine energy extbo foam gasoil gaswater gaswater_saltprec_vapwat gaswater_saltprec_energy brine_precsalt_vapwat blackoil_legacyassembly gasoildiffuse gaswater_dissolution gaswater_dissolution_diffuse gaswater_energy gaswater_solvent biofilm - blackoil_nohyst) -set(FLOW_VARIANT_MODELS brine_energy onephase onephase_energy) + blackoil_nohyst blackoil_tpsa gaswater_dissolution_tpsa) +set(FLOW_VARIANT_MODELS brine_energy onephase onephase_energy onephase_tpsa) set(FLOW_TGTS) foreach(OBJ ${COMMON_MODELS} ${FLOW_MODELS} ${FLOW_VARIANT_MODELS}) diff --git a/CMakeLists_files.cmake b/CMakeLists_files.cmake index 53773a6f61b..07ab6256301 100644 --- a/CMakeLists_files.cmake +++ b/CMakeLists_files.cmake @@ -106,10 +106,12 @@ list (APPEND MAIN_SOURCE_FILES opm/models/io/vtkprimaryvarsparams.cpp opm/models/io/vtkptflashparams.cpp opm/models/io/vtktemperatureparams.cpp + opm/models/io/vtktpsaparams.cpp opm/models/io/restart.cpp opm/models/nonlinear/newtonmethodparams.cpp opm/models/parallel/tasklets.cpp opm/models/parallel/threadmanager.cpp + opm/models/tpsa/tpsanewtonmethodparams.cpp opm/models/utils/parametersystem.cpp opm/models/utils/simulatorutils.cpp opm/models/utils/terminal.cpp @@ -126,6 +128,7 @@ list (APPEND MAIN_SOURCE_FILES opm/simulators/flow/EclGenericWriter.cpp opm/simulators/flow/ExtboContainer.cpp opm/simulators/flow/ExtraConvergenceOutputThread.cpp + opm/simulators/flow/FacePropertiesTPSA.cpp opm/simulators/flow/FIPContainer.cpp opm/simulators/flow/FlowGenericProblem.cpp opm/simulators/flow/FlowGenericVanguard.cpp @@ -182,6 +185,8 @@ list (APPEND MAIN_SOURCE_FILES opm/simulators/linalg/PreconditionerFactory7.cpp opm/simulators/linalg/PropertyTree.cpp opm/simulators/linalg/setupPropertyTree.cpp + opm/simulators/linalg/setupPropertyTreeTPSA.cpp + opm/simulators/linalg/TPSALinearSolverParameters.cpp opm/simulators/timestepping/AdaptiveSimulatorTimer.cpp opm/simulators/timestepping/AdaptiveTimeStepping.cpp opm/simulators/timestepping/ConvergenceReport.cpp @@ -478,6 +483,7 @@ list (APPEND TEST_SOURCE_FILES tests/test_dilu.cpp tests/test_equil.cpp tests/test_extractMatrix.cpp + tests/test_face_properties.cpp tests/test_flexiblesolver.cpp tests/test_glift1.cpp tests/test_graphcoloring.cpp @@ -793,6 +799,7 @@ list (APPEND PUBLIC_HEADER_FILES opm/models/discretization/common/linearizationtype.hh opm/models/discretization/common/restrictprolong.hh opm/models/discretization/common/tpfalinearizer.hh + opm/models/discretization/common/tpsalinearizer.hpp opm/models/discretization/ecfv/ecfvbaseoutputmodule.hh opm/models/discretization/ecfv/ecfvdiscretization.hh opm/models/discretization/ecfv/ecfvgridcommhandlefactory.hh @@ -863,6 +870,8 @@ list (APPEND PUBLIC_HEADER_FILES opm/models/io/vtktemperaturemodule.hpp opm/models/io/vtktemperatureparams.hpp opm/models/io/vtktensorfunction.hh + opm/models/io/vtktpsamodule.hpp + opm/models/io/vtktpsaparams.hpp opm/models/io/vtkvectorfunction.hh opm/models/ncp/ncpboundaryratevector.hh opm/models/ncp/ncpextensivequantities.hh @@ -910,6 +919,14 @@ list (APPEND PUBLIC_HEADER_FILES opm/models/richards/richardsprimaryvariables.hh opm/models/richards/richardsproperties.hh opm/models/richards/richardsratevector.hh + opm/models/tpsa/elasticityindices.hpp + opm/models/tpsa/elasticitylocalresidualtpsa.hpp + opm/models/tpsa/elasticityprimaryvariables.hpp + opm/models/tpsa/tpsabaseproperties.hpp + opm/models/tpsa/tpsamodel.hpp + opm/models/tpsa/tpsanewtonconvergencewriter.hpp + opm/models/tpsa/tpsanewtonmethod.hpp + opm/models/tpsa/tpsanewtonmethodparams.hpp opm/models/utils/alignedallocator.hh opm/models/utils/basicparameters.hh opm/models/utils/basicproperties.hh @@ -939,6 +956,7 @@ list (APPEND PUBLIC_HEADER_FILES opm/simulators/flow/BlackoilModelNldd.hpp opm/simulators/flow/BlackoilModelParameters.hpp opm/simulators/flow/BlackoilModelProperties.hpp + opm/simulators/flow/BlackoilModelTPSA.hpp opm/simulators/flow/CO2H2Container.hpp opm/simulators/flow/CollectDataOnIORank.hpp opm/simulators/flow/CollectDataOnIORank_impl.hpp @@ -953,6 +971,8 @@ list (APPEND PUBLIC_HEADER_FILES opm/simulators/flow/EquilInitializer.hpp opm/simulators/flow/ExtboContainer.hpp opm/simulators/flow/ExtraConvergenceOutputThread.hpp + opm/simulators/flow/FacePropertiesTPSA.hpp + opm/simulators/flow/FacePropertiesTPSA_impl.hpp opm/simulators/flow/FemCpGridCompat.hpp opm/simulators/flow/FIBlackoilModel.hpp opm/simulators/flow/FIPContainer.hpp @@ -968,6 +988,7 @@ list (APPEND PUBLIC_HEADER_FILES opm/simulators/flow/FlowProblemComp.hpp opm/simulators/flow/FlowProblemCompProperties.hpp opm/simulators/flow/FlowProblemParameters.hpp + opm/simulators/flow/FlowProblemTPSA.hpp opm/simulators/flow/FlowsContainer.hpp opm/simulators/flow/FlowUtils.hpp opm/simulators/flow/FlowsData.hpp @@ -1007,6 +1028,7 @@ list (APPEND PUBLIC_HEADER_FILES opm/simulators/flow/SolutionContainers.hpp opm/simulators/flow/SubDomain.hpp opm/simulators/flow/TTagFlowProblemTPFA.hpp + opm/simulators/flow/TTagFlowProblemTPSA.hpp opm/simulators/flow/TTagFlowProblemGasWater.hpp opm/simulators/flow/TTagFlowProblemOnePhase.hpp opm/simulators/flow/TracerContainer.hpp @@ -1057,6 +1079,7 @@ list (APPEND PUBLIC_HEADER_FILES opm/simulators/linalg/is_gpu_operator.hpp opm/simulators/linalg/ISTLSolver.hpp opm/simulators/linalg/ISTLSolverRuntimeOptionProxy.hpp + opm/simulators/linalg/ISTLSolverTPSA.hpp opm/simulators/linalg/istlpreconditionerwrappers.hh opm/simulators/linalg/istlsolverwrappers.hh opm/simulators/linalg/istlsparsematrixadapter.hh @@ -1100,7 +1123,9 @@ list (APPEND PUBLIC_HEADER_FILES opm/simulators/linalg/residreductioncriterion.hh opm/simulators/linalg/SmallDenseMatrixUtils.hpp opm/simulators/linalg/setupPropertyTree.hpp + opm/simulators/linalg/setupPropertyTreeTPSA.hpp opm/simulators/linalg/superlubackend.hh + opm/simulators/linalg/TPSALinearSolverParameters.hpp opm/simulators/linalg/twolevelmethodcpr.hh opm/simulators/linalg/vertexborderlistfromgrid.hh opm/simulators/linalg/weightedresidreductioncriterion.hh diff --git a/flow/flow_blackoil_tpsa.cpp b/flow/flow_blackoil_tpsa.cpp new file mode 100644 index 00000000000..ac8ca6a8aa6 --- /dev/null +++ b/flow/flow_blackoil_tpsa.cpp @@ -0,0 +1,104 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + Copyright 2025, NORCE AS + + This file is part of the Open Porous Media project (OPM). + + OPM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . +*/ +#include "config.h" + +#include +#include + +#include +#include +#include + +#include + + +namespace Opm::Properties { + +namespace TTag { + +struct FlowBlackOilProblemTPSA +{ + using InheritsFrom = std::tuple; +}; + +} // namespace Opm::Properties::TTag + +// /// +// Flow related properties +// /// +template +struct Linearizer +{ using type = TpfaLinearizer; }; + +template +struct LocalResidual +{ using type = BlackOilLocalResidualTPFA; }; + +template +struct EnableDiffusion +{ static constexpr bool value = false; }; + +template +struct AvoidElementContext +{ static constexpr bool value = true; }; + +// /// +// TPSA related properties +// /// +template +struct EnableMech +{ static constexpr bool value = true; }; + +template +struct Problem +{ using type = FlowProblemTPSA; }; + +template +struct NonlinearSystem +{ using type = BlackoilModelTPSA; }; + +} // namespace Opm::Properties + +namespace Opm { + +// ----------------- Main program ----------------- +int flowBlackoilTpsaMain(int argc, char** argv, bool outputCout, bool outputFiles) +{ + // we always want to use the default locale, and thus spare us the trouble + // with incorrect locale settings. + resetLocale(); + + FlowMain + mainfunc {argc, argv, outputCout, outputFiles}; + return mainfunc.execute(); +} + +int flowBlackoilTpsaMainStandalone(int argc, char** argv) +{ + using TypeTag = Properties::TTag::FlowBlackOilProblemTPSA; + auto mainObject = std::make_unique(argc, argv); + auto ret = mainObject->runStatic(); + // Destruct mainObject as the destructor calls MPI_Finalize! + mainObject.reset(); + return ret; +} + +} // namespace Opm \ No newline at end of file diff --git a/flow/flow_blackoil_tpsa.hpp b/flow/flow_blackoil_tpsa.hpp new file mode 100644 index 00000000000..4d4e1c94a91 --- /dev/null +++ b/flow/flow_blackoil_tpsa.hpp @@ -0,0 +1,35 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + Copyright 2025, NORCE AS + + This file is part of the Open Porous Media project (OPM). + + OPM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . +*/ +#ifndef FLOW_BLACKOIL_TPSA_HPP +#define FLOW_BLACKOIL_TPSA_HPP + + +namespace Opm { + +//! \brief Main function used in flow binary. +int flowBlackoilTpsaMain(int argc, char** argv, bool outputCout, bool outputFiles); + +//! \brief Main function used in flow_blackoil_tpsa binary. +int flowBlackoilTpsaMainStandalone(int argc, char** argv); + +} // namespace Opm + +#endif \ No newline at end of file diff --git a/flow/flow_blackoil_tpsa_main.cpp b/flow/flow_blackoil_tpsa_main.cpp new file mode 100644 index 00000000000..c8847b97cc8 --- /dev/null +++ b/flow/flow_blackoil_tpsa_main.cpp @@ -0,0 +1,28 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + Copyright 2025, NORCE AS + + This file is part of the Open Porous Media project (OPM). + + OPM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . +*/ +#include "config.h" +#include + + +int main(int argc, char** argv) +{ + return Opm::flowBlackoilTpsaMainStandalone(argc, argv); +} \ No newline at end of file diff --git a/flow/flow_gaswater_dissolution_tpsa.cpp b/flow/flow_gaswater_dissolution_tpsa.cpp new file mode 100644 index 00000000000..2c41cff241c --- /dev/null +++ b/flow/flow_gaswater_dissolution_tpsa.cpp @@ -0,0 +1,136 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + Copyright 2025, NORCE AS + + This file is part of the Open Porous Media project (OPM). + + OPM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . +*/ +#include "config.h" + +#include +#include +#include + +#include +#include +#include + +#include + + +namespace Opm::Properties { + +namespace TTag { + +struct FlowGasWaterDissolutionProblemTPSA +{ + using InheritsFrom = std::tuple; +}; + +} // namespace Opm::Properties::TTag + +// /// +// Flow related properties +// /// +template +struct Linearizer +{ using type = TpfaLinearizer; }; + +template +struct LocalResidual +{ using type = BlackOilLocalResidualTPFA; }; + +template +struct EnableDiffusion +{ static constexpr bool value = false; }; + +template +struct EnableDisgasInWater +{ static constexpr bool value = true; }; + +template +struct EnableVapwat +{ static constexpr bool value = true; }; + +//! The indices required by the model +template +struct Indices +{ +private: + // it is unfortunately not possible to simply use 'TypeTag' here because this leads + // to cyclic definitions of some properties. if this happens the compiler error + // messages unfortunately are *really* confusing and not really helpful. + using BaseTypeTag = TTag::FlowProblem; + using FluidSystem = GetPropType; + static constexpr EnergyModules energyModuleType = getPropValue(); + static constexpr int numEnergyVars = energyModuleType == EnergyModules::FullyImplicitThermal; + static constexpr bool enableSeqImpEnergy = energyModuleType == EnergyModules::SequentialImplicitThermal; + +public: + using type = BlackOilTwoPhaseIndices(), + getPropValue(), + getPropValue(), + numEnergyVars, + enableSeqImpEnergy, + getPropValue(), + getPropValue(), + /*PVOffset=*/0, + /*disabledCompIdx=*/FluidSystem::oilCompIdx, + getPropValue()>; +}; + +// /// +// TPSA related properties +// /// +template +struct EnableMech +{ static constexpr bool value = true; }; + +template +struct Problem +{ using type = FlowProblemTPSA; }; + +template +struct NonlinearSystem +{ using type = BlackoilModelTPSA; }; + +} // namespace Opm::Properties + +namespace Opm { + +// ----------------- Main program ----------------- +int flowGasWaterDissolutionTpsaMain(int argc, char** argv, bool outputCout, bool outputFiles) +{ + // we always want to use the default locale, and thus spare us the trouble + // with incorrect locale settings. + resetLocale(); + + FlowMain + mainfunc {argc, argv, outputCout, outputFiles}; + return mainfunc.execute(); +} + +int flowGasWaterDissolutionTpsaMainStandalone(int argc, char** argv) +{ + using TypeTag = Properties::TTag::FlowGasWaterDissolutionProblemTPSA; + auto mainObject = std::make_unique(argc, argv); + auto ret = mainObject->runStatic(); + // Destruct mainObject as the destructor calls MPI_Finalize! + mainObject.reset(); + return ret; +} + +} // namespace Opm \ No newline at end of file diff --git a/flow/flow_gaswater_dissolution_tpsa.hpp b/flow/flow_gaswater_dissolution_tpsa.hpp new file mode 100644 index 00000000000..bf75cea854d --- /dev/null +++ b/flow/flow_gaswater_dissolution_tpsa.hpp @@ -0,0 +1,34 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + Copyright 2025, NORCE AS + + This file is part of the Open Porous Media project (OPM). + + OPM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . +*/ +#ifndef FLOW_GAS_WATER_DISSOLUTION_TPSA_HPP +#define FLOW_GAS_WATER_DISSOLUTION_TPSA_HPP + +namespace Opm { + +//! \brief Main function used in flow binary. +int flowGasWaterDissolutionTpsaMain(int argc, char** argv, bool outputCout, bool outputFiles); + +//! \brief Main function used in flow_gaswater_dissolution_tpsa binary. +int flowGasWaterDissolutionTpsaMainStandalone(int argc, char** argv); + +} // namespace Opm + +#endif \ No newline at end of file diff --git a/flow/flow_gaswater_dissolution_tpsa_main.cpp b/flow/flow_gaswater_dissolution_tpsa_main.cpp new file mode 100644 index 00000000000..352345c39ba --- /dev/null +++ b/flow/flow_gaswater_dissolution_tpsa_main.cpp @@ -0,0 +1,28 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + Copyright 2025, NORCE AS + + This file is part of the Open Porous Media project (OPM). + + OPM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . +*/ +#include "config.h" +#include + + +int main(int argc, char** argv) +{ + return Opm::flowGasWaterDissolutionTpsaMainStandalone(argc, argv); +} \ No newline at end of file diff --git a/flow/flow_onephase_tpsa.cpp b/flow/flow_onephase_tpsa.cpp new file mode 100644 index 00000000000..1ecff7dfffc --- /dev/null +++ b/flow/flow_onephase_tpsa.cpp @@ -0,0 +1,127 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + Copyright 2025, NORCE AS + + This file is part of the Open Porous Media project (OPM). + + OPM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . +*/ +#include "config.h" + +#include +#include +#include + +#include +#include +#include + +#include + + +namespace Opm::Properties { + +namespace TTag { + +struct FlowWaterOnlyProblemTPSA +{ + using InheritsFrom = std::tuple; +}; + +} // namespace Opm::Properties::TTag + +// /// +// Flow related properties +// /// +template +struct Linearizer +{ using type = TpfaLinearizer; }; + +template +struct LocalResidual +{ using type = BlackOilLocalResidualTPFA; }; + +template +struct EnableDiffusion +{ static constexpr bool value = false; }; + +template +struct Indices +{ +private: + // it is unfortunately not possible to simply use 'TypeTag' here because this leads + // to cyclic definitions of some properties. if this happens the compiler error + // messages unfortunately are *really* confusing and not really helpful. + using BaseTypeTag = TTag::FlowProblem; + using FluidSystem = GetPropType; + static constexpr EnergyModules energyModuleType = getPropValue(); + static constexpr int numEnergyVars = energyModuleType == EnergyModules::FullyImplicitThermal; + static constexpr bool enableSeqImpEnergy = energyModuleType == EnergyModules::SequentialImplicitThermal; + +public: + using type = BlackOilOnePhaseIndices(), + getPropValue(), + getPropValue(), + numEnergyVars, + enableSeqImpEnergy, + getPropValue(), + getPropValue(), + /*PVOffset=*/0, + /*enabledCompIdx=*/FluidSystem::waterCompIdx, + getPropValue()>; +}; + +// /// +// TPSA related properties +// /// +template +struct EnableMech +{ static constexpr bool value = true; }; + +template +struct Problem +{ using type = FlowProblemTPSA; }; + +template +struct NonlinearSystem +{ using type = BlackoilModelTPSA; }; + +} // namespace Opm::Properties + +namespace Opm { + +// ----------------- Main program ----------------- +int flowWaterOnlyTpsaMain(int argc, char** argv, bool outputCout, bool outputFiles) +{ + // we always want to use the default locale, and thus spare us the trouble + // with incorrect locale settings. + resetLocale(); + + FlowMain + mainfunc {argc, argv, outputCout, outputFiles}; + return mainfunc.execute(); +} + +int flowWaterOnlyTpsaMainStandalone(int argc, char** argv) +{ + using TypeTag = Opm::Properties::TTag::FlowWaterOnlyProblemTPSA; + auto mainObject = std::make_unique(argc, argv); + auto ret = mainObject->runStatic(); + // Destruct mainObject as the destructor calls MPI_Finalize! + mainObject.reset(); + return ret; +} + +} \ No newline at end of file diff --git a/flow/flow_onephase_tpsa.hpp b/flow/flow_onephase_tpsa.hpp new file mode 100644 index 00000000000..8673c836525 --- /dev/null +++ b/flow/flow_onephase_tpsa.hpp @@ -0,0 +1,34 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + Copyright 2025, NORCE AS + + This file is part of the Open Porous Media project (OPM). + + OPM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . +*/ +#ifndef FLOW_ONEPHASE_TPSA_HPP +#define FLOW_ONEPHASE_TPSA_HPP + +namespace Opm { + +//! \brief Main functon used in main flow binary. +int flowWaterOnlyTpsaMain(int argc, char** argv, bool outputCout, bool outputFiles); + +//! \brief Main function used in flow_onephase_tpsa binary. +int flowWaterOnlyTpsaMainStandalone(int argc, char** argv); + +} // namespace Opm + +#endif \ No newline at end of file diff --git a/flow/flow_onephase_tpsa_main.cpp b/flow/flow_onephase_tpsa_main.cpp new file mode 100644 index 00000000000..ad8f3a55f67 --- /dev/null +++ b/flow/flow_onephase_tpsa_main.cpp @@ -0,0 +1,28 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + Copyright 2025, NORCE AS + + This file is part of the Open Porous Media project (OPM). + + OPM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . +*/ +#include "config.h" +#include + + +int main(int argc, char** argv) +{ + return Opm::flowWaterOnlyTpsaMainStandalone(argc, argv); +} diff --git a/opm/models/blackoil/blackoilintensivequantities.hh b/opm/models/blackoil/blackoilintensivequantities.hh index 52f7893df24..465d7bb62da 100644 --- a/opm/models/blackoil/blackoilintensivequantities.hh +++ b/opm/models/blackoil/blackoilintensivequantities.hh @@ -108,6 +108,7 @@ class BlackOilIntensiveQuantities enum { enableDispersion = getPropValue() }; enum { enableConvectiveMixing = getPropValue() }; enum { enableBioeffects = getPropValue() }; + enum { enableMech = getPropValue() }; enum { enableMICP = Indices::enableMICP }; enum { numPhases = getPropValue() }; enum { waterCompIdx = FluidSystem::waterCompIdx }; @@ -587,6 +588,31 @@ public: const Evaluation Sp = priVars.makeEvaluation(Indices::saltConcentrationIdx, timeIdx); porosity_ *= (1.0 - Sp); } + + + // Geomechanical updates to porosity/pore volume + if constexpr (enableMech) { + // TPSA calculations + if (problem.simulator().vanguard().eclState().runspec().tpsa().active()) { + // TPSA compressibility term + Scalar rockBiot = problem.rockBiotComp(globalSpaceIdx); + if (rockBiot > 0.0) { + Scalar rockRefPressure = problem.rockReferencePressure(globalSpaceIdx); + Evaluation active_pressure; + if (FluidSystem::phaseIsActive(oilPhaseIdx)) { + active_pressure = fluidState_.pressure(oilPhaseIdx) - rockRefPressure; + } else if (FluidSystem::phaseIsActive(waterPhaseIdx)){ + active_pressure = fluidState_.pressure(waterPhaseIdx) - rockRefPressure; + } else { + active_pressure = fluidState_.pressure(gasPhaseIdx) - rockRefPressure; + } + porosity_ += rockBiot * active_pressure; + } + + // TPSA coupling term, pore volume changes due to mechanics + porosity_ += problem.rockMechPoroChange(globalSpaceIdx, /*timeIdx=*/timeIdx); + } + } } void assertFiniteMembers() diff --git a/opm/models/blackoil/blackoilproblem.hh b/opm/models/blackoil/blackoilproblem.hh index c6c7ce0838d..87a0ff488ad 100644 --- a/opm/models/blackoil/blackoilproblem.hh +++ b/opm/models/blackoil/blackoilproblem.hh @@ -139,6 +139,66 @@ public: Scalar rockCompressibility(unsigned) const { return 0.0; } + /*! + * \brief Returns the porosity (i.e., pore volume) change due to geomechanics + */ + template + Scalar rockMechPoroChange(const Context&, + unsigned, + unsigned) const + { return 0.0; } + + /*! + * \brief Returns the porosity (i.e., pore volume) change due to geomechanics + */ + Scalar rockMechPoroChange(unsigned) const + { return 0.0; } + + /*! + * \brief Returns the additional compressibility of a cell due to poroelasticity + */ + template + Scalar rockBiotComp(const Context&, + unsigned, + unsigned) const + { return 0.0; } + + /*! + * \brief Returns the additional compressibility of a cell due to poroelasticity + */ + Scalar rockBiotComp(unsigned) const + { return 0.0; } + + /*! + * \brief Returns Lame's first parameter of a cell + */ + template + Scalar lame(const Context&, + unsigned, + unsigned) const + { return 0.0; } + + /*! + * \brief Returns Lame's first parameter of a cell + */ + Scalar lame(unsigned) const + { return 0.0; } + + /*! + * \brief Returns Biot coefficient of a cell + */ + template + Scalar biotCoeff(const Context&, + unsigned, + unsigned) const + { return 0.0; } + + /*! + * \brief Returns Biot coefficient of a cell + */ + Scalar biotCoeff(unsigned) const + { return 0.0; } + /*! * \brief Returns the reference pressure for rock the compressibility of a cell */ diff --git a/opm/models/discretization/common/tpsalinearizer.hpp b/opm/models/discretization/common/tpsalinearizer.hpp new file mode 100644 index 00000000000..4dac185ac0d --- /dev/null +++ b/opm/models/discretization/common/tpsalinearizer.hpp @@ -0,0 +1,692 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + Copyright 2025 NORCE AS + + This file is part of the Open Porous Media project (OPM). + + OPM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +#ifndef TPSA_LINEARIZER_HPP +#define TPSA_LINEARIZER_HPP + +#include + +#include + +#include + +#include + +#include + +#include +#include + +#include +#include +#include + + +namespace Opm { + +template +class TpsaLinearizer +{ + using Constraints = GetPropType; // TODO: make TPSA constraints + using Evaluation = GetPropType; + using FlowModel = GetPropType; + using GeomechModel = GetPropType; + using GlobalEqVector = GetPropType; + using GridView = GetPropType; + using LocalResidual = GetPropType; + using Problem = GetPropType; + using Scalar = GetPropType; + using Simulator = GetPropType; + using SparseMatrixAdapter = GetPropType; + using Stencil = GetPropType; + + using MaterialState = MaterialStateTPSA; + + enum { numEq = getPropValue() }; + + using ADVectorBlock = Dune::FieldVector; + using MatrixBlock = typename SparseMatrixAdapter::MatrixBlock; + using VectorBlock = Dune::FieldVector; + +public: + // /// + // Public functions + // /// + /*! + * \brief Constructor + */ + TpsaLinearizer() + { + simulatorPtr_ = nullptr; + } + + /*! + * \brief Initialize the linearizer + * + * \param simulator Simulator object + * + * \note At this point we can assume that all objects in the simulator have been allocated. We cannot assume that + * they are fully initialized, though. + */ + void init(Simulator& simulator) + { + simulatorPtr_ = &simulator; + eraseMatrix(); + } + + /*! + * \brief Causes the Jacobian matrix to be recreated from scratch before the next iteration. + * + * \note This method is usally called if the sparsity pattern has changed for some reason. (e.g. by modifications of + * the grid or changes of the auxiliary equations.) + */ + void eraseMatrix() + { + jacobian_.reset(); + } + + /*! + * \brief Finalize creation of Jacobian matrix and make ready for linear solver + */ + void finalize() + { + jacobian_->finalize(); + } + + /*! + * \brief Initializing and/or reset residual and Jacobian + * + * \param domain (Sub-)domain to reset + */ + template + void resetSystem_(const SubDomainType& domain) + { + if (!jacobian_) { + initFirstIteration_(); + } + for (int globI : domain.cells) { + residual_[globI] = 0.0; + jacobian_->clearRow(globI, 0.0); + } + } + + /*! + * \brief Linearize the non-linear system + * + * \note The linearizationType() controls the scheme used and the focus time index. The default is fully implicit + * scheme, and focus index equal to 0, i.e. current time (end of step). + */ + void linearize() + { + linearizeDomain(); + linearizeAuxiliaryEquations(); + } + + /*! + * \brief Linearize the non-linear system for the spatial domain + * + * \note That means that the global Jacobian of the residual is assembled and the residual is evaluated for the + * current solution. The current state of affairs (esp. the previous and the current solutions) is represented by the + * model object. + */ + void linearizeDomain() + { + int succeeded; + try { + linearizeDomain(fullDomain_); + succeeded = 1; + } + catch (const std::exception& e) { + std::cout << "rank " << simulator_().gridView().comm().rank() + << " caught an exception while linearizing TPSA system:" << e.what() + << "\n" << std::flush; + succeeded = 0; + } + catch (...) { + std::cout << "rank " << simulator_().gridView().comm().rank() + << " caught an exception while linearizing TPSA system" + << "\n" << std::flush; + succeeded = 0; + } + succeeded = simulator_().gridView().comm().min(succeeded); + + if (!succeeded) { + throw NumericalProblem("A process did not succeed in linearizing the TPSA system"); + } + } + + /*! + * \brief Linearize the non-linear system for the spatial domain + * + * \param domain (Sub-)domain to linearize + * + * \note That means that the global Jacobian of the residual is assembled and the residual is evaluated for the + * current solution. The current state of affairs (esp. the previous and the current solutions) is represented by + * the model object. + */ + template + void linearizeDomain(const SubDomainType& domain) + { + OPM_TIMEBLOCK(linearizeDomain); + + // We defer the initialization of the Jacobian matrix until here because the auxiliary modules usually assume + // the problem, model and grid to be fully initialized' + if (!jacobian_) { + initFirstIteration_(); + } + + if (domain.cells.size() == flowModel_().numTotalDof()) { + // We are on the full domain. + resetSystem_(); + } + else { + resetSystem_(domain); + } + + linearize_(domain); + } + + /*! + * \brief Linearize auxillary equation + */ + void linearizeAuxiliaryEquations() + { } + + /*! + * \brief Extract local residuals and sub-block Jacobians from locally computed AD residual + * + * \param res Residual to set + * \param bMat Block matrix from Jacobian to set + * \param resid Computed AD residual + */ + void setResAndJacobi(VectorBlock& res, MatrixBlock& bMat, const ADVectorBlock& resid) const + { + // Scalar local residual + for (unsigned eqIdx = 0; eqIdx < numEq; ++eqIdx) { + res[eqIdx] = resid[eqIdx].value(); + } + + // A[dofIdx][focusDofIdx][eqIdx][pvIdx] is the partial derivative of the residual function 'eqIdx' for the + // degree of freedom 'dofIdx' with regard to the focus variable 'pvIdx' of the degree of freedom 'focusDofIdx' + for (unsigned eqIdx = 0; eqIdx < numEq; ++eqIdx) { + for (unsigned pvIdx = 0; pvIdx < numEq; ++pvIdx) { + bMat[eqIdx][pvIdx] = resid[eqIdx].derivative(pvIdx); + } + } + } + + /*! + * \brief Update boundary condition information + */ + void updateBoundaryConditionData() + { + for (auto& bdyInfo : boundaryInfo_) { + // Get boundary information from problem + const auto [type, displacementAD] = problem_().mechBoundaryCondition(bdyInfo.cell, bdyInfo.dir); + + // Strip the unnecessary (and zero anyway) derivatives off displacement + std::vector displacement(3, 0.0); + for (std::size_t ii = 0; ii < displacement.size(); ++ii) { + displacement[ii] = displacementAD[ii].value(); + } + + // Update boundary information + bdyInfo.bcdata.type = type; + bdyInfo.bcdata.displacement = displacement; + } + } + + // /// + // Public get and set functions + // /// + /*! + * \brief Get Jacobian matrix + */ + const SparseMatrixAdapter& jacobian() const + { return *jacobian_; } + + /*! + * \brief Get Jacobian matrix + */ + SparseMatrixAdapter& jacobian() + { return *jacobian_; } + + /*! + * \brief Get residual vector + */ + const GlobalEqVector& residual() const + { return residual_; } + + /*! + * \brief Get residual vector + */ + GlobalEqVector& residual() + { return residual_; } + + /*! + * \brief Get linearization type + */ + const LinearizationType& getLinearizationType() const + { return linearizationType_; } + + /*! + * \brief Get constraints map + */ + std::map constraintsMap() const + { return {}; } + + /*! + * \brief Set linearization type + */ + void setLinearizationType(LinearizationType linearizationType) + { linearizationType_ = linearizationType; } + +private: + // /// + // Private functions + // /// + /*! + * \brief Construct the Jacobian matrix and create cell-neighbor information + */ + void createMatrix_() + { + OPM_TIMEBLOCK(createMatrixTPSA); + + // If the Jacobian has been initialize before, we jump out + if (!neighborInfo_.empty()) { + return; + } + + // Init. the stencil + const auto& flowModel = flowModel_(); + Stencil stencil(gridView_(), flowModel.dofMapper()); + + // Build up sparsity patterns and neighboring information for Jacobian and linearization + std::vector> sparsityPattern(flowModel.numTotalDof()); + unsigned numCells = flowModel.numTotalDof(); + neighborInfo_.reserve(numCells, 6 * numCells); + std::vector loc_nbinfo; + for (const auto& elem : elements(gridView_())) { + // Loop over primary dofs in the element + stencil.update(elem); + + for (unsigned primaryDofIdx = 0; primaryDofIdx < stencil.numPrimaryDof(); ++primaryDofIdx) { + // Build up neighboring information for curret primary dof + const unsigned myIdx = stencil.globalSpaceIndex(primaryDofIdx); + loc_nbinfo.resize(stencil.numDof() - 1); + + for (unsigned dofIdx = 0; dofIdx < stencil.numDof(); ++dofIdx) { + // NOTE: NeighborInfo could/should be expanded with cell face parameters located in problem_() + // needed when computing face terms in LocalResidual + const unsigned neighborIdx = stencil.globalSpaceIndex(dofIdx); + sparsityPattern[myIdx].insert(neighborIdx); + if (dofIdx > 0) { + const auto scvfIdx = dofIdx - 1; + const auto& scvf = stencil.interiorFace(scvfIdx); + const Scalar area = scvf.area(); + loc_nbinfo[dofIdx - 1] = NeighborInfo{ neighborIdx, area, nullptr }; + } + } + + // Insert local neighbor info + neighborInfo_.appendRow(loc_nbinfo.begin(), loc_nbinfo.end()); + + // Boundary condition information + unsigned bfIndex = 0; + for (const auto& intersection : intersections(gridView_(), elem)) { + if (intersection.boundary()) { + // Get boundary face direction + const auto& bf = stencil.boundaryFace(bfIndex); + const int dir_id = bf.dirId(); + + // Skip NNCs + if (dir_id < 0) { + continue; + } + + // Get boundary information from problem() + const auto [type, displacementAD] = problem_().mechBoundaryCondition(myIdx, dir_id); + + // Strip the unnecessary (and zero anyway) derivatives off displacement + std::vector displacement(3, 0.0); + for (std::size_t ii = 0; ii < displacement.size(); ++ii) { + displacement[ii] = displacementAD[ii].value(); + } + + // Insert boundary condition data in container + BoundaryConditionData bcdata { type, displacement, bfIndex, bf.area() }; + boundaryInfo_.push_back( { myIdx, dir_id, bfIndex, bcdata } ); + ++bfIndex; + continue; + } + if (!intersection.neighbor()) { + ++bfIndex; + continue; + } + } + } + } + + // Allocate Jacobian matrix and pointers to its sub-blocks + jacobian_ = std::make_unique(simulator_()); + diagMatAddress_.resize(numCells); + jacobian_->reserve(sparsityPattern); + for (unsigned globI = 0; globI < numCells; globI++) { + const auto& nbInfos = neighborInfo_[globI]; + diagMatAddress_[globI] = jacobian_->blockAddress(globI, globI); + for (auto& nbInfo : nbInfos) { + nbInfo.matBlockAddress = jacobian_->blockAddress(nbInfo.neighbor, globI); + } + } + + // Create full domain + fullDomain_.cells.resize(numCells); + std::iota(fullDomain_.cells.begin(), fullDomain_.cells.end(), 0); + } + + /*! + * \brief Linearize the non-linear system of equation, computing residual and its derivatives + */ + template + void linearize_(const SubDomainType& domain) + { + OPM_TIMEBLOCK(linearizeTPSA); + + // Extract misc. variables used in linearization + const auto& flowModel = flowModel_(); + const auto& geoMechModel = geoMechModel_(); + auto& problem = problem_(); + const unsigned int numCells = domain.cells.size(); + const bool on_full_domain = (numCells == flowModel.numTotalDof()); + +#ifdef _OPENMP +#pragma omp parallel for +#endif + // Loop over cells in the domain and compute local residual and jacobian + for (unsigned ii = 0; ii < numCells; ++ii) { + OPM_TIMEBLOCK_LOCAL(linearizationForEachCellTPSA, Subsystem::Assembly); + + const unsigned globI = domain.cells[ii]; + const auto& nbInfos = neighborInfo_[globI]; + VectorBlock res(0.0); + MatrixBlock bMat(0.0); + ADVectorBlock adres(0.0); + const MaterialState& materialStateIn = geoMechModel.materialState(globI, /*timeIdx=*/0); + + // /// + // Face term + // /// + { + OPM_TIMEBLOCK_LOCAL(faceCalculationForEachCellTPSA, Subsystem::Assembly); + + // Loop over neighboring cells + for (const auto& nbInfo : nbInfos) { + OPM_TIMEBLOCK_LOCAL(calculationForEachFaceTPSA, Subsystem::Assembly); + + // Reset local residual and Jacobian + res = 0.0; + bMat = 0.0; + adres = 0.0; + + // Neighbor information + const unsigned globJ = nbInfo.neighbor; + assert(globJ != globI); + const MaterialState& materialStateEx = geoMechModel.materialState(globJ, /*timeIdx=*/0); + + // Compute local face term + LocalResidual::computeFaceTerm(adres, + materialStateIn, + materialStateEx, + problem, + globI, + globJ); + adres *= nbInfo.faceArea; + + // Extract residual and sub-block Jacobian entries from computed AD residual + setResAndJacobi(res, bMat, adres); + + // Insert into global residual + residual_[globI] += res; + + // Insert contribution to (globI, globI) sub-block + // SparseAdapter syntax: jacobian_->addToBlock(globI, globI, bMat); + *diagMatAddress_[globI] += bMat; + + // Insert contribution to (globJ, globI) sub-block + // Note: since LocalResidual::computeFaceTerm have Evaluation on globI primary variables, it is + // natural to insert Jacobian entries for (globJ, globI) here, since we only need to flip the signs + // in the calculated face terms + // SparseAdapter syntax: jacobian_->addToBlock(globJ, globI, bMat); + bMat *= -1.0; + *nbInfo.matBlockAddress += bMat; + } + } + + // /// + // Volume term + // // + adres = 0.0; + { + OPM_TIMEBLOCK_LOCAL(computeVolumeTerm, Subsystem::Assembly); + + // Compute local volume term + LocalResidual::computeVolumeTerm(adres, + materialStateIn, + problem, + globI); + } + const double volume = flowModel.dofTotalVolume(globI); + adres *= volume; + + // Extract residual and sub-block Jacobian entries from computed AD residual + setResAndJacobi(res, bMat, adres); + + // Insert in global residual + residual_[globI] += res; + + // Insert contribution to (globI, globI) sub-block + // SparseAdapter syntax: jacobian_->addToBlock(globI, globI, bMat); + *diagMatAddress_[globI] += bMat; + + // /// + // Source term + // /// + res = 0.0; + bMat = 0.0; + adres = 0.0; + + // Compute local source term + LocalResidual::computeSourceTerm(adres, + problem, + globI, + 0); + adres *= -volume; + + // Extract residual and sub-block Jacobian entries from computed AD residual + setResAndJacobi(res, bMat, adres); + + // Insert into global residual + residual_[globI] += res; + + // Insert contribution to (globI, globI) sub-block + // SparseAdapter syntax: jacobian_->addToBlock(globI, globI, bMat); + *diagMatAddress_[globI] += bMat; + } // globI loop + + // /// + // Boundary term + // /// + for (const auto& bdyInfo : boundaryInfo_) { + VectorBlock res(0.0); + MatrixBlock bMat(0.0); + ADVectorBlock adres(0.0); + const unsigned globI = bdyInfo.cell; + const MaterialState& materialStateIn = geoMechModel.materialState(globI, /*timeIdx=*/0); + + // Compute local boundary condition + LocalResidual::computeBoundaryTerm(adres, + materialStateIn, + bdyInfo.bcdata, + problem, + globI); + adres *= bdyInfo.bcdata.faceArea; + + // Extract residual and sub-block Jacobian entries from computed AD residual + setResAndJacobi(res, bMat, adres); + + // Insert in global residual + residual_[globI] += res; + + // Insert contribution to (globI, globI) sub-block + // SparseAdapter syntax: jacobian_->addToBlock(globI, globI, bMat); + *diagMatAddress_[globI] += bMat; + } + } + + /*! + * \brief Create residual and Jacobian, and reset both + */ + void initFirstIteration_() + { + // initialize the BCRS matrix for the Jacobian of the residual function + createMatrix_(); + + // initialize the Jacobian matrix and the vector for the residual function + residual_.resize(flowModel_().numTotalDof()); + resetSystem_(); + } + + /*! + * \brief Set Jacobian matrix and residuals to zero + */ + void resetSystem_() + { + // Set residual vector entries to zero + residual_ = 0.0; + + // Set all Jacobian matrix entries to zero + jacobian_->clear(); + } + + // /// + // Private get functions + // /// + /*! + * \brief Return simulator object + */ + Simulator& simulator_() + { return *simulatorPtr_; } + + /*! + * \brief Return simulator object + */ + const Simulator& simulator_() const + { return *simulatorPtr_; } + + /*! + * \brief Return problem object + */ + Problem& problem_() + { return simulator_().problem(); } + + /*! + * \brief Return problem object + */ + const Problem& problem_() const + { return simulator_().problem(); } + + /*! + * \brief Return Flow model object + */ + FlowModel& flowModel_() + { return simulator_().model(); } + + /*! + * \brief Return Flow model object + */ + const FlowModel& flowModel_() const + { return simulator_().model(); } + + /*! + * \brief Return TPSA model object + */ + GeomechModel& geoMechModel_() + { return problem_().geoMechModel(); } + + /*! + * \brief Return TPSA model object + */ + const GeomechModel& geoMechModel_() const + { return problem_().geoMechModel(); } + + /*! + * \brief Return grid view + */ + const GridView& gridView_() const + { return problem_().gridView(); } + + Simulator* simulatorPtr_{}; + LinearizationType linearizationType_{}; + + std::vector diagMatAddress_{}; + std::unique_ptr jacobian_{}; + GlobalEqVector residual_; + + // Helper structs + struct NeighborInfo + { + unsigned int neighbor; + double faceArea; + MatrixBlock* matBlockAddress; + }; + SparseTable neighborInfo_{}; + + struct BoundaryConditionData + { + BCMECHType type; + std::vector displacement; + unsigned boundaryFaceIndex; + double faceArea; + }; + + struct BoundaryInfo + { + unsigned int cell; + int dir; + unsigned int bfIndex; + BoundaryConditionData bcdata; + }; + std::vector boundaryInfo_; + + struct FullDomain + { + std::vector cells; + std::vector interior; + }; + FullDomain fullDomain_; +}; // class TpsaLinearizer + +} // namespace Opm + +#endif \ No newline at end of file diff --git a/opm/models/io/vtktpsamodule.hpp b/opm/models/io/vtktpsamodule.hpp new file mode 100644 index 00000000000..167db00eb31 --- /dev/null +++ b/opm/models/io/vtktpsamodule.hpp @@ -0,0 +1,198 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + Copyright 2025 NORCE AS + + This file is part of the Open Porous Media project (OPM). + + OPM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +#ifndef VTK_TPSA_MODULE_HPP +#define VTK_TPSA_MODULE_HPP + +#include + +#include +#include +#include +#include +#include +#include + + +namespace Opm { + +/*! +* \ingroup Vtk +* +* \brief VTK output module for TPSA quantities +*/ +template +class VtkTpsaModule : public BaseOutputModule +{ + using ParentType = BaseOutputModule; + + using GridView = GetPropType; + using Simulator = GetPropType; + using ElementContext = GetPropType; + + static constexpr auto vtkFormat = getPropValue(); + using VtkMultiWriter = Opm::VtkMultiWriter; + + enum { enableMech = getPropValue() }; + + using BufferType = typename ParentType::BufferType; + using ScalarBuffer = typename ParentType::ScalarBuffer; + using VectorBuffer = typename ParentType::VectorBuffer; + +public: + /*! + * \brief Constructor + * + * \param simulator Simulator object + */ + explicit VtkTpsaModule(const Simulator& simulator) + : ParentType(simulator) + { + // Read runtime parameters + if constexpr(enableMech) { + params_.read(); + } + } + + /*! + * \brief Register runtime parameters + */ + static void registerParameters() + { + if constexpr(enableMech) { + VtkTpsaParams::registerParameters(); + } + } + + /*! + * \brief Allocate memory for output + */ + void allocBuffers() override + { + // Check if vtk output is enabled + if (!Parameters::Get()) { + return; + } + + // Allocate buffers + if constexpr(enableMech) { + // Displacement + if (params_.displacementOutput_) { + this->resizeVectorBuffer_(displacement_, BufferType::Dof); + } + + // Rotation + if (params_.rotationOutput_) { + this->resizeVectorBuffer_(rotation_, BufferType::Dof); + } + + // Solid pressure + if (params_.solidPressureOutput_) { + this->resizeScalarBuffer_(solidPres_, BufferType::Dof); + } + } + } + + /*! + * \brief Assign quantities to output buffers + * + * \param elemCtx Element context object + */ + void processElement(const ElementContext& elemCtx) override + { + // Check if vtk output is enabled + if (!Parameters::Get()) { + return; + } + + if constexpr(enableMech) { + // Assign quantities from material state + const auto& problem = elemCtx.problem(); + const auto& geoMechModel = problem.geoMechModel(); + for (unsigned dofIdx = 0; dofIdx < elemCtx.numPrimaryDof(/*timeIdx=*/0); ++dofIdx) { + const unsigned globalDofIdx = elemCtx.globalSpaceIndex(dofIdx, /*timeIdx=*/0); + const auto& materialState = geoMechModel.materialState(globalDofIdx, /*timeIdx=*/0); + + // Loop over x-, y- and z-dir (corresponding to dirIdx = 0, 1, 2) + for (int dirIdx = 0; dirIdx < 3; ++dirIdx) { + // Displacement + if (params_.displacementOutput_) { + displacement_[globalDofIdx][dirIdx] = scalarValue(materialState.displacement(dirIdx)); + } + + // Rotation + if (params_.rotationOutput_) { + rotation_[globalDofIdx][dirIdx] = scalarValue(materialState.rotation(dirIdx)); + } + } + + // Solid pressure + if (params_.solidPressureOutput_) { + solidPres_[globalDofIdx] = scalarValue(materialState.solidPressure()); + } + } + } + } + + /*! + * \brief Add buffers to VTK writer + * + * \param baseWriter VTK output writer object + */ + void commitBuffers(BaseOutputWriter& baseWriter) override + { + // Check if writer exists or mechanics output is enabled + if (!dynamic_cast(&baseWriter)) { + return; + } + + if constexpr(enableMech) { + // Displacement + if (params_.displacementOutput_) { + this->commitVectorBuffer_(baseWriter, "displacement", displacement_, BufferType::Dof); + } + + // Rotation + if (params_.rotationOutput_) { + this->commitVectorBuffer_(baseWriter, "rotation", rotation_, BufferType::Dof); + } + + // Solid pressure + if (params_.solidPressureOutput_) { + this->commitScalarBuffer_(baseWriter, "solid_pressure", solidPres_, BufferType::Dof); + } + } + } + +private: + VtkTpsaParams params_{}; + + VectorBuffer displacement_{}; + VectorBuffer rotation_{}; + ScalarBuffer solidPres_{}; +}; // class VtkTpsaModule + +} // namespace Opm + +#endif \ No newline at end of file diff --git a/opm/models/io/vtktpsaparams.cpp b/opm/models/io/vtktpsaparams.cpp new file mode 100644 index 00000000000..6acfba5d937 --- /dev/null +++ b/opm/models/io/vtktpsaparams.cpp @@ -0,0 +1,51 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + Copyright 2025 NORCE AS + + This file is part of the Open Porous Media project (OPM). + + OPM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +#include +#include + + +namespace Opm { + +/*! +* \brief Register runtime parameters +*/ +void VtkTpsaParams::registerParameters() +{ + Parameters::Register + ("Include displacement in VTK output files"); + Parameters::Register + ("Include rotation in VTK output files"); + Parameters::Register + ("Include solid pressure in VTK output files"); +} + +void VtkTpsaParams::read() +{ + displacementOutput_ = Parameters::Get(); + rotationOutput_ = Parameters::Get(); + solidPressureOutput_ = Parameters::Get(); +} + +} // namespace Opm \ No newline at end of file diff --git a/opm/models/io/vtktpsaparams.hpp b/opm/models/io/vtktpsaparams.hpp new file mode 100644 index 00000000000..3b69b9f3b32 --- /dev/null +++ b/opm/models/io/vtktpsaparams.hpp @@ -0,0 +1,55 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + Copyright 2025 NORCE AS + + This file is part of the Open Porous Media project (OPM). + + OPM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +#ifndef VTK_TPSA_PARAMS_HPP +#define VTK_TPSA_PARAMS_HPP + + +namespace Opm::Parameters { + +// Default output quantities +struct VtkWriteDisplacement { static constexpr bool value = true; }; +struct VtkWriteRotation { static constexpr bool value = true; }; +struct VtkWriteSolidPressure { static constexpr bool value = true; }; + +} // namespace Opm::Parameters + +namespace Opm { + +/*! +* \brief Parameters for VtkTpsaOutputModule +*/ +struct VtkTpsaParams +{ + static void registerParameters(); + void read(); + + bool displacementOutput_; + bool rotationOutput_; + bool solidPressureOutput_; +}; + +} // namespace Opm + +#endif \ No newline at end of file diff --git a/opm/models/tpsa/elasticityindices.hpp b/opm/models/tpsa/elasticityindices.hpp new file mode 100644 index 00000000000..c774e876c45 --- /dev/null +++ b/opm/models/tpsa/elasticityindices.hpp @@ -0,0 +1,64 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + Copyright 2025 NORCE AS + + This file is part of the Open Porous Media project (OPM). + + OPM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +#ifndef ELASTICITY_INDICES_HPP +#define ELASTICITY_INDICES_HPP + + +namespace Opm { + +template +struct ElasticityIndices +{ + // /// + // Primary variable indices + // /// + // Starting index of displacement vector + static constexpr int disp0Idx = PVOffset + 0; + + // Starting index of rotation vector + static constexpr int rot0Idx = disp0Idx + 3; + + // Index of solid pressure + static constexpr int solidPres0Idx = rot0Idx + 3; + + // /// + // Equation indices + // /// + // Starting index of all equations, in particular the stress equations + static constexpr int conti0EqIdx = PVOffset + 0; + + // Starting index of rotation equations + static constexpr int contiRotEqIdx = conti0EqIdx + 3; + + // Starting index of solid pressure equation + static constexpr int contiSolidPresEqIdx = contiRotEqIdx + 3; + + // Total number of equations/primary variables + static constexpr int numEq = 7; +}; // struct ElasticityIndices + +} // namespace Opm + +#endif \ No newline at end of file diff --git a/opm/models/tpsa/elasticitylocalresidualtpsa.hpp b/opm/models/tpsa/elasticitylocalresidualtpsa.hpp new file mode 100644 index 00000000000..c788d48b482 --- /dev/null +++ b/opm/models/tpsa/elasticitylocalresidualtpsa.hpp @@ -0,0 +1,395 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + Copyright 2025 NORCE AS + + This file is part of the Open Porous Media project (OPM). + + OPM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +#ifndef ELASTICITY_LOCAL_RESIDUAL_TPSA_HPP +#define ELASTICITY_LOCAL_RESIDUAL_TPSA_HPP + +#include + +#include + +#include +#include + +#include + +#include + +#include + + +namespace Opm { + +template +class ElasticityLocalResidual +{ + using Indices = GetPropType; + using Scalar = GetPropType; + using Evaluation = GetPropType; + using Problem = GetPropType; + + using MaterialState = MaterialStateTPSA; + using Toolbox = MathToolbox; + + enum { conti0EqIdx = Indices::conti0EqIdx }; + enum { contiRotEqIdx = Indices::contiRotEqIdx }; + enum { contiSolidPresEqIdx = Indices::contiSolidPresEqIdx }; + enum { numEq = getPropValue() }; + +public: + /*! + * \brief Calculate volume terms in TPSA formulation + * + * \param volTerm Volume term vector + * \param materialState Material state container + * \param problem Flow problem + * \param globalIndex Cell index + * + * \note Material state, problem input and global index here might/should be merged in an "IntensiveQuantity" + * container as in BlackOilLocalResidualTPFA + */ + template + static void computeVolumeTerm(Dune::FieldVector& volTerm, + const MaterialState& materialState, + const Problem& problem, + const unsigned globalIndex) + { + // Reset volume terms + volTerm = 0.0; + + // Rotation equations (one per direction) + const Scalar sModulus = problem.shearModulus(globalIndex); + for (unsigned dirIdx = 0; dirIdx < 3; ++dirIdx) { + volTerm[contiRotEqIdx + dirIdx] += + Toolbox::template decay(materialState.rotation(dirIdx)) + / sModulus; + } + + // Solid pressure equation + const Scalar lame = problem.lame(globalIndex); + volTerm[contiSolidPresEqIdx] += + Toolbox::template decay(materialState.solidPressure()) + / lame; + } + + /*! + * \brief Calculate terms across cell faces in TPSA formulation + * + * \param faceTerm Face term vector + * \param materialStateIn Material state container of inside cell + * \param materialStateEx Material state container of outside cel + * \param problem Flow problem + * \param globalIndexIn Inside cell index + * \param globalIndexEx Outside cell index + * + * \note Material state, problem input and global index here might/should be merged in "IntensiveQuantity" and + * "NeighborInfo" containers as in BlackOilLocalResidualTPFA + */ + static void computeFaceTerm(Dune::FieldVector& faceTerm, + const MaterialState& materialStateIn, + const MaterialState& materialStateEx, + Problem& problem, + const unsigned globalIndexIn, + const unsigned globalIndexEx) + { + // Reset face terms + faceTerm = 0.0; + + // Extract some face properties + const Scalar weightAvgIn = problem.weightAverage(globalIndexIn, globalIndexEx); + const Scalar weightAvgEx = problem.weightAverage(globalIndexEx, globalIndexIn); + const Scalar weightProd = problem.weightProduct(globalIndexIn, globalIndexEx); + const Scalar normDist = problem.normalDistance(globalIndexIn, globalIndexEx); + const auto& faceNormal = problem.cellFaceNormal(globalIndexIn, globalIndexEx); + + // Effective shear modulus + const Scalar sModulusIn = problem.shearModulus(globalIndexIn); + const Scalar sModulusEx = problem.shearModulus(globalIndexEx); + const Scalar eff_sModulus = weightAvgIn * sModulusIn + weightAvgEx * sModulusEx; + + // Distance ratio + const Scalar distRatio = 0.5 * weightProd / normDist; + + // Solid pressures + const Evaluation& solidPIn = materialStateIn.solidPressure(); + const Scalar solidPEx = decay(materialStateEx.solidPressure()); + + // /// + // Solid pressure equation (direction-independent equation) + // /// + faceTerm[contiSolidPresEqIdx] += + distRatio * eff_sModulus * (solidPIn - solidPEx); + + // /// + // Displacement, rotation and solid pressure (directional-dependent) equations + // /// + // Lambda function for computing modulo 3 of possibly negative integers to get indices in cross product. + // E.g. if i = x-dir(=0), we want y-dir(=1) and z-dir(=2), hence -1 mod 3 must equal 2 and not -1 + auto modNeg = [](int i) { return ((i % 3) + 3) % 3; }; + + // Loop over x-, y- and z-dir (corresponding to dirIdx = 0, 1, 2) + for (int dirIdx = 0; dirIdx < 3; ++dirIdx) { + // Direction indices in cross-product + unsigned dirIdxNeg = modNeg(dirIdx - 1); + unsigned dirIdxPos = modNeg(dirIdx + 1); + + // Displacement equation + const Scalar faceNormalDir = faceNormal[dirIdx]; + const Scalar faceNormalNeg = faceNormal[dirIdxNeg]; + const Scalar faceNormalPos = faceNormal[dirIdxPos]; + + const Evaluation& dispIn = materialStateIn.displacement(dirIdx); + const Scalar dispEx = decay(materialStateEx.displacement(dirIdx)); + + const Evaluation& rotInNeg = materialStateIn.rotation(dirIdxNeg); + const Evaluation& rotInPos = materialStateIn.rotation(dirIdxPos); + const Scalar rotExNeg = decay(materialStateEx.rotation(dirIdxNeg)); + const Scalar rotExPos = decay(materialStateEx.rotation(dirIdxPos)); + + faceTerm[conti0EqIdx + dirIdx] += + 2.0 * (eff_sModulus / normDist) * (dispIn - dispEx) + - weightAvgIn * (faceNormalNeg * rotInPos - faceNormalPos * rotInNeg) + - weightAvgEx * (faceNormalNeg * rotExPos - faceNormalPos * rotExNeg) + - faceNormalDir * (weightAvgIn * solidPIn + weightAvgEx * solidPEx); + + // Rotation equation + const Evaluation& dispInNeg = materialStateIn.displacement(dirIdxNeg); + const Evaluation& dispInPos = materialStateIn.displacement(dirIdxPos); + const Scalar dispExNeg = decay(materialStateEx.displacement(dirIdxNeg)); + const Scalar dispExPos = decay(materialStateEx.displacement(dirIdxPos)); + + faceTerm[contiRotEqIdx + dirIdx] += + - weightAvgEx * (faceNormalNeg * dispInPos - faceNormalPos * dispInNeg) + - weightAvgIn * (faceNormalNeg * dispExPos - faceNormalPos * dispExNeg); + + // Solid pressure equation + faceTerm[contiSolidPresEqIdx] += + - faceNormalDir * (weightAvgEx * dispIn + weightAvgIn * dispEx); + } + } + + /*! + * \brief Calculate boundary conditions in TPSA formulation given by BCCON/BCPROP + * + * \param bndryTerm Boundary term vector + * \param materialState Material state container + * \param bdyInfo Boundary condition info container + * \param problem Flow problem + * \param globalIndex Cell index + */ + template + static void computeBoundaryTerm(Dune::FieldVector& bndryTerm, + const MaterialState& materialState, + const BoundaryConditionData& bdyInfo, + Problem& problem, + unsigned globalIndex) + { + // Switch between possible boundary conditions + switch (bdyInfo.type) { + // OBS: NONE is interpreted as FIXED with zero displacement + case BCMECHType::NONE: + computeBoundaryTermFixed(bndryTerm, + materialState, + bdyInfo, + problem, + globalIndex); + break; + case BCMECHType::FIXED: + throw std::runtime_error("BCTYPE FIXED has not been implemented in TPSA"); + case BCMECHType::FREE: + computeBoundaryTermFree(bndryTerm, + materialState, + bdyInfo, + problem, + globalIndex); + break; + default: + throw std::logic_error("Unknown boundary condition type " + + std::to_string(static_cast(bdyInfo.type)) + + " in computeBoundaryFlux()." ); + } + } + + /*! + * \brief Calculate fixed displacement boundary condition in TPSA formulation + * + * \param bndryTerm Boundary term vector + * \param materialState Material state container + * \param bdyInfo Boundary condition info container + * \param problem Flow problem + * \param globalIndex Cell index + * + * \note BCMECHType::NONE is a implemented as a specialization of BCMECHType::FIXED with displacement equal + * to zero on the boundary face + */ + template + static void computeBoundaryTermFixed(Dune::FieldVector& bndryTerm, + const MaterialState& materialState, + const BoundaryConditionData& bdyInfo, + Problem& problem, + unsigned globalIndex) + { + // !!!! + // Only BCMECHType::NONE, where we have zero displacement on boundary face, have been implemented! + // !!!! + + // Reset bondary term + bndryTerm = 0.0; + + // Extract some face properties + const unsigned bfIdx = bdyInfo.boundaryFaceIndex; + const Scalar weightAvg = problem.weightAverageBoundary(globalIndex, bfIdx); + const Scalar normDist = problem.normalDistanceBoundary(globalIndex, bfIdx); + const auto& faceNormal = problem.cellFaceNormalBoundary(globalIndex, bfIdx); + + // Effective shear modulus (= cell shear modulus) + const Scalar eff_sModulus = problem.shearModulus(globalIndex); + + // Solid pressure + const Evaluation& solidP = materialState.solidPressure(); + + // /// + // Displacement equation + // /// + // Lambda function for computing modulo 3 of possibly negative integers to get indices in cross product. + // E.g. if i = x-dir(=0), we want y-dir(=1) and z-dir(=2), hence -1 mod 3 must equal 2 and not -1 + auto modNeg = [](int i) { return ((i % 3) + 3) % 3; }; + + // Loop over x-, y- and z-dir (corresponding to dirIdx = 0, 1, 2) + for (int dirIdx = 0; dirIdx < 3; ++dirIdx) { + // Direction indices in cross-product + unsigned dirIdxNeg = modNeg(dirIdx - 1); + unsigned dirIdxPos = modNeg(dirIdx + 1); + + // Displacement equation + const Scalar faceNormalDir = faceNormal[dirIdx]; + const Scalar faceNormalNeg = faceNormal[dirIdxNeg]; + const Scalar faceNormalPos = faceNormal[dirIdxPos]; + + const Evaluation& disp = materialState.displacement(dirIdx); + + const Evaluation& rotNeg = materialState.rotation(dirIdxNeg); + const Evaluation& rotPos = materialState.rotation(dirIdxPos); + + bndryTerm[conti0EqIdx + dirIdx] += + 2.0 * (eff_sModulus / normDist) * disp + - weightAvg * (faceNormalNeg * rotPos - faceNormalPos * rotNeg) + - faceNormalDir * weightAvg * solidP; + } + } + + /*! + * \brief Calculate free (or zero traction) boundary condition in TPSA formulation + * + * \param bndryTerm Boundary term vector + * \param materialState Material state container + * \param bdyInfo Boundary condition info container + * \param problem Flow problem + * \param globalIndex Cell index + * + * \note Free, or zero traction, BC is equivalent of having a spring at infinity where we have assumed all (primary) + * variables and parameters (e.g., shear modulus) are zero. + */ + template + static void computeBoundaryTermFree(Dune::FieldVector& bndryTerm, + const MaterialState& materialState, + const BoundaryConditionData& bdyInfo, + Problem& problem, + unsigned globalIndex) + { + // Reset bondary term + bndryTerm = 0.0; + + // Face properties + const unsigned bfIdx = bdyInfo.boundaryFaceIndex; + const Scalar weightAvg = 1.0; + const Scalar normDist = problem.normalDistanceBoundary(globalIndex, bfIdx); + const auto& faceNormal = problem.cellFaceNormalBoundary(globalIndex, bfIdx); + + const Scalar sModulus = problem.shearModulus(globalIndex); + + // /// + // Solid pressure equation (direction-independent equation) + // /// + const Evaluation& solidP = materialState.solidPressure(); + bndryTerm[contiSolidPresEqIdx] += + 0.5 * (normDist / sModulus) * solidP; + + // /// + // Rotation and solid pressure (directional-dependent) equations + // /// + // Lambda function for computing modulo 3 of possibly negative integers to get indices in cross product. + // E.g. if i = x-dir(=0), we want y-dir(=1) and z-dir(=2), hence -1 mod 3 must equal 2 and not -1 + auto modNeg = [](int i) { return ((i % 3) + 3) % 3; }; + + // Loop over x-, y- and z-dir (corresponding to dirIdx = 0, 1, 2) + for (int dirIdx = 0; dirIdx < 3; ++dirIdx) { + // Direction indices in cross-product + unsigned dirIdxNeg = modNeg(dirIdx - 1); + unsigned dirIdxPos = modNeg(dirIdx + 1); + + // Rotation equation + const Scalar faceNormalNeg = faceNormal[dirIdxNeg]; + const Scalar faceNormalPos = faceNormal[dirIdxPos]; + + const Evaluation& dispNeg = materialState.displacement(dirIdxNeg); + const Evaluation& dispPos = materialState.displacement(dirIdxPos); + + bndryTerm[contiRotEqIdx + dirIdx] += + - weightAvg * (faceNormalNeg * dispPos - faceNormalPos * dispNeg); + + // Solid pressure (directional-dependent) equation + const Scalar faceNormalDir = faceNormal[dirIdx]; + const Evaluation& disp = materialState.displacement(dirIdx); + + bndryTerm[contiSolidPresEqIdx] += + - faceNormalDir * weightAvg * disp; + } + } + + /*! + * \brief Calculate source term in TPSA formulation + * + * \param sourceTerm Source term vector + * \param problem Flow problem + * \param globalIndex Cell index + * \param timeIdx Time index + */ + static void computeSourceTerm(Dune::FieldVector& sourceTerm, + Problem& problem, + unsigned globalSpaceIdex, + unsigned timeIdx) + { + // Reset source terms + sourceTerm = 0.0; + + // Get source term from problem + // NOTE: separate source function than Flow source(...)! + problem.tpsaSource(sourceTerm, globalSpaceIdex, timeIdx); + } +}; // class ElasticityLocalResidual + +} // namespace Opm + +#endif \ No newline at end of file diff --git a/opm/models/tpsa/elasticityprimaryvariables.hpp b/opm/models/tpsa/elasticityprimaryvariables.hpp new file mode 100644 index 00000000000..385e711638d --- /dev/null +++ b/opm/models/tpsa/elasticityprimaryvariables.hpp @@ -0,0 +1,142 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + Copyright 2025 NORCE AS + + This file is part of the Open Porous Media project (OPM). + + OPM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +#ifndef ELASTICITY_PRIMARY_VARIABLES_HPP +#define ELASTICITY_PRIMARY_VARIABLES_HPP + +#include + +#include +#include + +#include +#include + +#include + + +namespace Opm { + +template +class ElasticityPrimaryVariables + : public Dune::FieldVector, + getPropValue()> +{ + using Evaluation = GetPropType; + using Indices = GetPropType; + using Scalar = GetPropType; + + using Toolbox = Opm::MathToolbox; + + enum { disp0Idx = Indices::disp0Idx }; + enum { rot0Idx = Indices::rot0Idx }; + enum { solidPres0Idx = Indices::solidPres0Idx }; + + enum { numEq = getPropValue() }; + + using ParentType = Dune::FieldVector; + +public: + /*! + * \brief Constructor + */ + ElasticityPrimaryVariables() : ParentType() + { + Valgrind::SetUndefined(*this); + } + + /*! + * \brief Default copy constructor + */ + ElasticityPrimaryVariables(const ElasticityPrimaryVariables& value) = default; + + /*! + * \brief Default assignment constructor + */ + ElasticityPrimaryVariables& operator=(const ElasticityPrimaryVariables& value) = default; + + using ParentType::operator=; //!< Import base class assignment operators. + + /*! + * \brief Return primary variable in Evaluation type + * + * \param varIdx Primary variable index + * \param timeIdx Time index + * \param linearizationType Type of linearization + * + * Automatic differentiation: returns value + derivative + * Finite differences: returns value only + */ + Evaluation makeEvaluation(unsigned varIdx, + unsigned timeIdx, + Opm::LinearizationType linearizationType = LinearizationType()) const + { + // Finite difference + if constexpr (std::is_same_v) { + return (*this)[varIdx]; + } + // Automatic differentiation + else { + if (timeIdx == linearizationType.time) { + return Toolbox::createVariable((*this)[varIdx], varIdx); + } + else { + return Toolbox::createConstant((*this)[varIdx]); + } + } + } + + /*! + * \brief Assign primary variables from a material state container + * + * \param materialState Material state container + */ + template + void assignNaive(const MaterialState& materialState) + { + // Reset primary variables vector + (*this) = 0.0; + + // Assign displacement and rotation vectors + for (unsigned dirIdx = 0; dirIdx < 3; ++dirIdx) { + (*this)[disp0Idx + dirIdx] = Opm::getValue(materialState.displacement(dirIdx)); + (*this)[rot0Idx + dirIdx] = Opm::getValue(materialState.rotation(dirIdx)); + } + + // Assign solid pressure + (*this)[solidPres0Idx] = Opm::getValue(materialState.solidPressure()); + } + + /*! + * \brief Instruct Valgrind to check the definedness of all attributes of this class + */ + void checkDefined() const + { + Valgrind::CheckDefined(*static_cast(this)); + } +}; // class ElasticityPrimaryVariables + +} // namespace Opm + +#endif \ No newline at end of file diff --git a/opm/models/tpsa/tpsabaseproperties.hpp b/opm/models/tpsa/tpsabaseproperties.hpp new file mode 100644 index 00000000000..ba7169eb6cd --- /dev/null +++ b/opm/models/tpsa/tpsabaseproperties.hpp @@ -0,0 +1,86 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + Copyright 2025 NORCE AS + + This file is part of the Open Porous Media project (OPM). + + OPM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +#ifndef TPSA_BASE_PROPERTIES_HPP +#define TPSA_BASE_PROPERTIES_HPP + +#include + + +namespace Opm::Properties { + +template +struct IndicesTPSA { using type = UndefinedProperty; }; + +template +struct NumEqTPSA { using type = UndefinedProperty; }; + +template +struct LinearizerTPSA { using type = UndefinedProperty; }; + +template +struct EvaluationTPSA { using type = UndefinedProperty; }; + +template +struct EqVectorTPSA { using type = UndefinedProperty; }; + +template +struct GlobalEqVectorTPSA { using type = UndefinedProperty; }; + +template +struct PrimaryVariablesTPSA { using type = UndefinedProperty; }; + +template +struct SolutionVectorTPSA { using type = UndefinedProperty; }; + +template +struct SolutionHistorySizeTPSA { using type = UndefinedProperty; }; + +template +struct ModelTPSA { using type = UndefinedProperty; }; + +template +struct LocalResidualTPSA { using type = UndefinedProperty; }; + +template +struct SparseMatrixAdapterTPSA { using type = UndefinedProperty; }; + +template +struct NewtonMethodTPSA { using type = UndefinedProperty; }; + +template +struct NewtonConvergenceWriterTPSA { using type = UndefinedProperty; }; + +template +struct ConvergenceWriterTPSA { using type = UndefinedProperty; }; + +template +struct EnableConstraintsTPSA { using type = UndefinedProperty; }; + +template +struct LinearSolverBackendTPSA { using type = UndefinedProperty; }; + +} // namespace Opm::Properties + +#endif \ No newline at end of file diff --git a/opm/models/tpsa/tpsamodel.hpp b/opm/models/tpsa/tpsamodel.hpp new file mode 100644 index 00000000000..6afdafe4043 --- /dev/null +++ b/opm/models/tpsa/tpsamodel.hpp @@ -0,0 +1,518 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + Copyright 2025 NORCE AS + + This file is part of the Open Porous Media project (OPM). + + OPM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +#ifndef TPSA_MODEL_HPP +#define TPSA_MODEL_HPP + +#include + +#include + +#include + +#include +#include +#include + +#include +#include + + +namespace Opm { + +template +class TpsaModel +{ + using DofMapper = GetPropType; + using Evaluation = GetPropType; + using GlobalEqVector = GetPropType; + using GridView = GetPropType; + using Indices = GetPropType; + using Linearizer = GetPropType; + using NewtonMethod = GetPropType; + using PrimaryVariables = GetPropType; + using Simulator = GetPropType; + using SolutionVector = GetPropType; + using Scalar = GetPropType; + + enum { dimWorld = GridView::dimensionworld }; + enum { historySize = getPropValue() }; + enum { numEq = getPropValue() }; + + enum { disp0Idx = Indices::disp0Idx }; + enum { rot0Idx = Indices::rot0Idx }; + enum { solidPres0Idx = Indices::solidPres0Idx }; + + using MaterialState = MaterialStateTPSA; + + using DimVector = Dune::FieldVector; + using SymTensor = Dune::FieldVector; + +public: + class TpsaBlockVectorWrapper + { + protected: + SolutionVector blockVector_; + public: + TpsaBlockVectorWrapper(const std::string&, const std::size_t size) + : blockVector_(size) + {} + + TpsaBlockVectorWrapper() = default; + + static TpsaBlockVectorWrapper serializationTestObject() + { + TpsaBlockVectorWrapper result("dummy", 3); + result.blockVector_[0] = 1.0; + result.blockVector_[1] = 2.0; + result.blockVector_[2] = 3.0; + + return result; + } + + SolutionVector& blockVector() + { return blockVector_; } + + const SolutionVector& blockVector() const + { return blockVector_; } + + bool operator==(const TpsaBlockVectorWrapper& wrapper) const + { + return std::equal(this->blockVector_.begin(), this->blockVector_.end(), + wrapper.blockVector_.begin(), wrapper.blockVector_.end()); + } + + template + void serializeOp(Serializer& serializer) + { + serializer(blockVector_); + } + }; + + // /// + // Public functions + // /// + /*! + * \brief Constructor + * + * \param simulator Simulator object + */ + explicit TpsaModel(Simulator& simulator) + : linearizer_(std::make_unique()) + , newtonMethod_(simulator) + , simulator_(simulator) + , element_chunks_(simulator.gridView(), Dune::Partitions::all, ThreadManager::maxThreads()) + { + // Initialize equation weights to 1.0 + eqWeights_.resize(numEq, 1.0); + + // Initialize historic solution vectors + // OBS: need at least history size = 2, due to time-derivative of solid-pressure in Flow coupling term + const std::size_t numDof = simulator_.model().numGridDof(); + for (unsigned timeIdx = 0; timeIdx < historySize; ++timeIdx) { + solution_[timeIdx] = std::make_unique("solution", numDof); + } + } + + /*! + * \brief Initialize TPSA model + */ + void finishInit() + { + // Initialize the linearizer + linearizer_->init(simulator_); + + // Initialize the newton method + newtonMethod_.finishInit(); + + // Resize material state vector + resizeMaterialState_(); + } + + /*! + * \brief Register runtime parameters + */ + static void registerParameters() + { + // Newton method parameters + NewtonMethod::registerParameters(); + } + + /*! + * \brief Prepare TPSA model for coupled Flow-TPSA scheme + */ + void prepareTPSA() + { + // Update historic solution + solution(/*timeIdx=*/1) = solution(/*timeIdx=*/0); + } + + /*! + * \brief Sync primary variables in overlapping cells + * + * \note Copied code from EcfvDiscretization::syncOverlap() to sync TPSA primary variables + */ + void syncOverlap() + { + // Syncronize the solution on the ghost and overlap elements + using GhostSyncHandle = GridCommHandleGhostSync; + + auto ghostSync = GhostSyncHandle(solution(/*timeIdx=*/0), + simulator_.model().dofMapper()); + + simulator_.gridView().communicate(ghostSync, + Dune::InteriorBorder_All_Interface, + Dune::ForwardCommunication); + } + + /*! + * \brief Update material state for all cells + * + * \param timeIdx Time index + * + * \note Cached material state not implemented yet! + */ + void updateMaterialState(const unsigned /*timeIdx*/) + { + // Loop over all elements chuncks and update material state from current solution + const auto& elementMapper = simulator_.model().elementMapper(); +#ifdef _OPENMP +#pragma omp parallel for +#endif + for (const auto& chunk : element_chunks_) { + for (const auto& elem : chunk) { + const unsigned globalIdx = elementMapper.index(elem); + auto& currSol = solution(/*timeIdx=*/0)[globalIdx]; + setMaterialState_(globalIdx, /*timeIdx=*/0, currSol); + } + } + } + + // /// + // Public get and set functions + // /// + /*! + * \brief Return the linearizer + */ + const Linearizer& linearizer() const + { + return *linearizer_; + } + + /*! + * \brief Return the linearizer + */ + Linearizer& linearizer() + { + return *linearizer_; + } + + /*! + * \brief Return the Newton method + */ + const NewtonMethod& newtonMethod() const + { + return newtonMethod_; + } + + /*! + * \brief Return the Newton method + */ + NewtonMethod& newtonMethod() + { + return newtonMethod_; + } + + /*! + * \brief Get reference to history solution vector + * + * \param timeIdx Time index + */ + const SolutionVector& solution(unsigned timeIdx) const + { + return solution_[timeIdx]->blockVector(); + } + + /*! + * \brief Get reference to history solution vector + * + * \param timeIdx Time index + */ + SolutionVector& solution(unsigned timeIdx) + { + return solution_[timeIdx]->blockVector(); + } + + /*! + * \brief Return number of degrees of freedom in the grid from the Flow model + */ + std::size_t numGridDof() const + { + return simulator_.model().numGridDof(); + } + + /*! + * \brief Return the total number of degrees of freedom + */ + std::size_t numTotalDof() const + { + return numGridDof() + numAuxiliaryDof(); + } + + /*! + * \brief Return the total grid volume from the Flow model + * + * \param globalIdx Cell index + */ + Scalar dofTotalVolume(unsigned globalIdx) const + { + return simulator_.model().dofTotalVolume(globalIdx); + } + + /*! + * \brief Return equation weights + * + * \param dofIdx Degree-of-freedom index + * \param eqIdx Equation index + */ + Scalar eqWeight(unsigned /*dofIdx*/, unsigned eqIdx) const + { + return eqWeights_[eqIdx]; + } + + /*! + * \brief Set weights for equation + * + * \param eqIdx Equation index + * \param value Weight value to set + */ + void setEqWeight(unsigned eqIdx, Scalar value) + { + eqWeights_[eqIdx] = value; + } + + /*! + * \brief Number of auxillary modules + */ + std::size_t numAuxiliaryModules() const + { + return 0; + } + + /*! + * \brief Number of auxillary degrees of freedom + */ + std::size_t numAuxiliaryDof() const + { + return 0; + } + + /*! + * \brief Return current material state + * + * \param globalIdx Cell index + * \param timeIdx Time index + * + * \note Cached material state not implemented yet! + */ + const MaterialState& materialState(const unsigned globalIdx, unsigned /*timeIdx*/) const + { + return materialState_[globalIdx]; + } + + /*! + * \brief Output displacement vector + * + * \param globalIdx Cell index + * \param with_fracture Boolean to activate fracture output + * + * \note Used in OutputBlackOilModule! + */ + DimVector disp(const unsigned globalIdx, const bool /*with_fracture*/) const + { + DimVector d; + for (std::size_t i = 0; i < 3; ++i) { + d[i] = decay(materialState_[globalIdx].displacement(i)); + } + return d; + } + + /*! + * \brief Output (del?)stress tensor + * + * \param globalIdx Cell index + * + * \note Needed in OutputBlackOilModule, but zero for now! + */ + SymTensor delstress(const unsigned /*globalIdx*/) const + { + SymTensor val; + return val; + } + + /*! + * \brief Output fracture stress tensor + * + * \param globalIdx Cell index + * + * \note Needed in OutputBlackOilModule, but zero for now! + */ + SymTensor fractureStress(const unsigned /*globalIdx*/) const + { + SymTensor val; + return val; + } + + /*! + * \brief Output linear stress tensor + * + * \param globalIdx Cell index + * + * \note Needed in OutputBlackOilModule, but zero for now! + */ + SymTensor linstress(const unsigned /*globalIdx*/) const + { + SymTensor val; + return val; + } + + /*! + * \brief Output stress tensor + * + * \param globalIdx Cell index + * \param with_fracture Boolean to activate fracture output + * + * \note Needed in OutputBlackOilModule, but zero for now! + */ + SymTensor stress(const unsigned /*globalIdx*/, const bool /*with_fracture*/) const + { + SymTensor val; + return val; + } + + /*! + * \brief Output strain tensor + * + * \param globalIdx Cell index + * \param with_fracture Boolean to activate fracture output + * + * \note Needed in OutputBlackOilModule, but zero for now! + */ + SymTensor strain(const unsigned /*globalIdx*/, const bool /*with_fracture*/) const + { + SymTensor val; + return val; + } + + /*! + * \brief Output potential forces + * + * \param globalIdx Cell index + * + * \note Needed in OutputBlackOilModule, but zero for now! + */ + Scalar mechPotentialForce(unsigned /*globalIdx*/) const + { + return Scalar(0.0); + } + + /*! + * \brief Output potential pressure forces + * + * \param globalIdx Cell index + * + * \note Needed in OutputBlackOilModule, but zero for now! + */ + Scalar mechPotentialPressForce(unsigned /*globalIdx*/) const + { + return Scalar(0.0); + } + + /*! + * \brief Output potential temparature forces + * + * \param globalIdx Cell index + * + * \note Needed in OutputBlackOilModule, but zero for now! + */ + Scalar mechPotentialTempForce(unsigned /*globalIdx*/) const + { + return Scalar(0.0); + } + +protected: + // /// + // Protected functions + // /// + /*! + * \brief Resize material state vector + * + * \note Cached material state not implemented yet! + */ + void resizeMaterialState_() + { + const std::size_t numDof = simulator_.model().numGridDof(); + materialState_.resize(numDof); + } + +private: + // /// + // Private functions + // /// + /*! + * \brief Set material from another + * + * \param globalIdx Cell index + * \param timeIdx Time index + * \param values Primary variable to insert into material state + * + * \note Cached material state not implemented yet! + */ + void setMaterialState_(const unsigned globalIdx, const unsigned /*timeIdx*/, PrimaryVariables& values) + { + auto& dofMaterialState = materialState_[globalIdx]; + for (unsigned dirIdx = 0; dirIdx < 3; ++dirIdx) { + dofMaterialState.setDisplacement(dirIdx, values.makeEvaluation(disp0Idx + dirIdx, 0)); + dofMaterialState.setRotation(dirIdx, values.makeEvaluation(rot0Idx + dirIdx, 0)); + } + dofMaterialState.setSolidPressure(values.makeEvaluation(solidPres0Idx, 0)); + } + + std::unique_ptr linearizer_; + NewtonMethod newtonMethod_; + Simulator& simulator_; + ElementChunks element_chunks_; + + std::array, historySize> solution_; + std::vector eqWeights_; + std::vector materialState_; +}; // class TpsaModel + +} // namespace Opm + + +#endif \ No newline at end of file diff --git a/opm/models/tpsa/tpsanewtonconvergencewriter.hpp b/opm/models/tpsa/tpsanewtonconvergencewriter.hpp new file mode 100644 index 00000000000..4dd1af26e4f --- /dev/null +++ b/opm/models/tpsa/tpsanewtonconvergencewriter.hpp @@ -0,0 +1,88 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + Copyright 2025 NORCE AS + + This file is part of the Open Porous Media project (OPM). + + OPM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +#ifndef TPSA_NEWTON_CONVERGENCE_WRITER_HPP +#define TPSA_NEWTON_CONVERGENCE_WRITER_HPP + +#include + +namespace Opm { + +template +class TpsaNewtonConvergenceWriter +{ + using NewtonMethod = GetPropType; + + using SolutionVector = GetPropType; + using GlobalEqVector = GetPropType; + +public: + /*! + * \brief Constructor + */ + explicit TpsaNewtonConvergenceWriter() + { } + + /*! + * \brief Called by the Newton method before the actual algorithm is started for any given timestep. + */ + void beginTimeStep() + { } + + /*! + * \brief Called by the Newton method before an iteration of the Newton algorithm is started. + */ + void beginIteration() + { } + + /*! + * \brief Write the Newton update to disk. + * + * \note Called after the linear solution is found for an iteration. + * + * \param uLastIter The solution vector of the previous iteration. + * \param deltaU The negative difference between the solution vectors of the previous and the current iteration. + */ + void writeFields(const SolutionVector& /*uLastIter*/, + const GlobalEqVector& /*deltaU*/) + { } + + /*! + * \brief Called by the Newton method after an iteration of the Newton algorithm has been completed. + */ + void endIteration() + { } + + /*! + * \brief Called by the Newton method after Newton algorithm has been completed for any given timestep. + * + * \note This method is called regardless of whether the Newton method converged or not. + */ + void endTimeStep() + { } +}; // class TpsaNewtonConvergenceWriter + +} // namespace Opm + +#endif \ No newline at end of file diff --git a/opm/models/tpsa/tpsanewtonmethod.hpp b/opm/models/tpsa/tpsanewtonmethod.hpp new file mode 100644 index 00000000000..efb1f3cd647 --- /dev/null +++ b/opm/models/tpsa/tpsanewtonmethod.hpp @@ -0,0 +1,731 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + Copyright 2025 NORCE AS + + This file is part of the Open Porous Media project (OPM). + + OPM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +#ifndef TPSA_NEWTON_METHOD_HPP +#define TPSA_NEWTON_METHOD_HPP + +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace Opm { + +template +class TpsaNewtonMethod +{ + using Scalar = GetPropType; + using Simulator = GetPropType; + using Problem = GetPropType; + using Model = GetPropType; + + using SolutionVector = GetPropType; + using GlobalEqVector = GetPropType; + using PrimaryVariables = GetPropType; + using Constraints = GetPropType; + using EqVector = GetPropType; + using Linearizer = GetPropType; + using LinearSolverBackend = GetPropType; + using ConvergenceWriter = GetPropType; + using SparseMatrixAdapter = GetPropType; + + using IstlMatrix = typename SparseMatrixAdapter::IstlMatrix; + +public: + /*! + * \brief Constructor + * + * \param simulator Simulator object + */ + explicit TpsaNewtonMethod(Simulator& simulator) + : simulator_(simulator) + , linearSolver_(simulator) + , error_(1e100) + , lastError_(1e100) + , numIterations_(0) + , numLinearizations_(0) + , convergenceWriter_() + { + // Read runtime/default Newton parameters + params_.read(); + } + + /*! + * \brief Register all run-time parameters for the Newton method. + */ + static void registerParameters() + { + LinearSolverBackend::registerParameters(); + TpsaNewtonMethodParams::registerParameters(); + } + + /*! + * \brief Finialize the construction of the object. + * + * \note At this point, it can be assumed that all objects featured by the simulator have been allocated. (But not + * that they have been fully initialized yet.) + */ + void finishInit() + { } + + /*! + * \brief Run the Newton method. + */ + bool apply() + { + // Make sure all timers are prestine + prePostProcessTimer_.halt(); + linearizeTimer_.halt(); + solveTimer_.halt(); + updateTimer_.halt(); + + // Get vectors and linearizer + SolutionVector& nextSolution = model().solution(/*historyIdx=*/0); + SolutionVector currentSolution(nextSolution); + GlobalEqVector solutionUpdate(nextSolution.size()); + + Linearizer& linearizer = model().linearizer(); + + TimerGuard prePostProcessTimerGuard(prePostProcessTimer_); + + // Tell the implementation that we begin solving + prePostProcessTimer_.start(); + begin_(nextSolution); + prePostProcessTimer_.stop(); + + try { + TimerGuard innerPrePostProcessTimerGuard(prePostProcessTimer_); + TimerGuard linearizeTimerGuard(linearizeTimer_); + TimerGuard updateTimerGuard(updateTimer_); + TimerGuard solveTimerGuard(solveTimer_); + + // Execute the method as long as the implementation thinks that we should do another iteration + while (proceed_()) { + // Notify the implementation that we're about to start a new iteration + prePostProcessTimer_.start(); + beginIteration_(); + prePostProcessTimer_.stop(); + + // Make the current solution to the old one + currentSolution = nextSolution; + + // Do the actual linearization + linearizeTimer_.start(); + linearizeDomain_(); + linearizeAuxiliaryEquations_(); + linearizeTimer_.stop(); + + // Get residual and Jacobian for convergence check and preparation of linear solver + solveTimer_.start(); + auto& residual = linearizer.residual(); + const auto& jacobian = linearizer.jacobian(); + linearSolver_.prepare(jacobian, residual); + linearSolver_.getResidual(residual); + solveTimer_.stop(); + + // The preSolve_() method usually computes the errors, but it can do something else in addition. + // TODO: should its costs be counted to the linearization or to the update? + updateTimer_.start(); + preSolve_(currentSolution, residual); + updateTimer_.stop(); + + // Check convergence criteria + if (converged()) { + // Tell the implementation that we're done with this iteration + prePostProcessTimer_.start(); + endIteration_(nextSolution, currentSolution); + prePostProcessTimer_.stop(); + + break; + } + + // Solve A x = b, where b is the residual, A is its Jacobian and x is the update of the solution + solveTimer_.start(); + solutionUpdate = 0.0; + const bool conv = linearSolver_.solve(solutionUpdate); + solveTimer_.stop(); + + if (!conv) { + solveTimer_.stop(); + if (verbose_()) { + std::cout << "TPSA: Linear solver did not converge!" << std::endl; + } + + prePostProcessTimer_.start(); + failed_(); + prePostProcessTimer_.stop(); + + return false; + } + + // Update the current solution with the delta + updateTimer_.start(); + postSolve_(currentSolution, residual, solutionUpdate); + update_(nextSolution, currentSolution, solutionUpdate, residual); + updateTimer_.stop(); + + // End of iteration calculations + prePostProcessTimer_.start(); + endIteration_(nextSolution, currentSolution); + prePostProcessTimer_.stop(); + } + } + catch (const Dune::Exception& e) + { + if (verbose_()) { + std::cout << "TPSA: Newton method caught exception: \"" + << e.what() << "\"\n" << std::flush; + } + + prePostProcessTimer_.start(); + failed_(); + prePostProcessTimer_.stop(); + + return false; + } + catch (const NumericalProblem& e) + { + if (verbose_()) { + std::cout << "TPSA: Newton method caught exception: \"" + << e.what() << "\"\n" << std::flush; + } + + prePostProcessTimer_.start(); + failed_(); + prePostProcessTimer_.stop(); + + return false; + } + + // Tell the implementation that we're done + prePostProcessTimer_.start(); + end_(); + prePostProcessTimer_.stop(); + + // print the timing summary of the time step + if (verbose_()) { + Scalar elapsedTot = + linearizeTimer_.realTimeElapsed() + + solveTimer_.realTimeElapsed() + + updateTimer_.realTimeElapsed(); + const auto default_precision{std::cout.precision()}; + std::cout << std::setprecision(2) + << "TPSA: " + << "Newton iter = " << numIterations() << " (error=" + << error_ << ") | " + << "linearization = " + << linearizeTimer_.realTimeElapsed() << "s (" + << 100 * linearizeTimer_.realTimeElapsed() / elapsedTot << "%) | " + << "solve = " + << solveTimer_.realTimeElapsed() << "s (" + << 100 * solveTimer_.realTimeElapsed() / elapsedTot << "%) | " + << "update = " + << updateTimer_.realTimeElapsed() << "s (" + << 100 * updateTimer_.realTimeElapsed() / elapsedTot << "%)" + << "\n" << std::flush; + std::cout << std::setprecision(default_precision); // restore default output width + } + + // if we're not converged, tell the implementation that we've failed + if (!converged()) { + prePostProcessTimer_.start(); + failed_(); + std::cout << "TPSA: Newton iterations did not converge!" << std::endl; + prePostProcessTimer_.stop(); + return false; + } + + // if we converged, tell the implementation that we've succeeded + prePostProcessTimer_.start(); + succeeded_(); + prePostProcessTimer_.stop(); + + return true; + } + + /*! + * \brief Set the index of current iteration. + * + * \param value New iteration index + * + * \note Normally this does not need to be called, but if the non-linear solver is implemented externally, it needs + * to be set in order for the model to do the Right Thing (TM) while linearizing. + */ + void setIterationIndex(int value) + { + numIterations_ = value; + } + + /*! + * \brief Set the current tolerance at which the Newton method considers itself to be converged. + * + * \param value New tolerance value + */ + void setTolerance(Scalar value) + { + params_.tolerance_ = value; + } + + /*! + * \brief Returns true if the error of the solution is below the tolerance. + */ + bool converged() const + { return error_ <= tolerance(); } + + /*! + * \brief Returns a reference to the object describing the current physical problem. + */ + Problem& problem() + { return simulator_.problem(); } + + /*! + * \brief Returns a reference to the object describing the current physical problem. + */ + const Problem& problem() const + { return simulator_.problem(); } + + /*! + * \brief Returns a reference to the numeric model. + */ + Model& model() + { return simulator_.problem().geoMechModel(); } + + /*! + * \brief Returns a reference to the numeric model. + */ + const Model& model() const + { return simulator_.problem().geoMechModel(); } + + /*! + * \brief Returns the linear solver backend object for external use. + */ + LinearSolverBackend& linearSolver() + { return linearSolver_; } + + /*! + * \brief Returns the linear solver backend object for external use. + */ + const LinearSolverBackend& linearSolver() const + { return linearSolver_; } + + /*! + * \brief Returns the number of iterations done since the Newton method was invoked. + */ + int numIterations() const + { return numIterations_; } + + /*! + * \brief Returns the number of linearizations that has done since the Newton method was invoked. + */ + int numLinearizations() const + { return numLinearizations_; } + + /*! + * \brief Return the current tolerance at which the Newton method considers itself to be converged. + */ + Scalar tolerance() const + { return params_.tolerance_; } + + /*! + * \brief Returns minimum number of Newton iterations used + */ + Scalar minIterations() const + { return params_.minIterations_; } + + /*! + * \brief Return post-process timer + */ + const Timer& prePostProcessTimer() const + { return prePostProcessTimer_; } + + /*! + * \brief Return linearization timer + */ + const Timer& linearizeTimer() const + { return linearizeTimer_; } + + /*! + * \brief Return linear solver timer + */ + const Timer& solveTimer() const + { return solveTimer_; } + + /*! + * \brief Return update solution timer + */ + const Timer& updateTimer() const + { return updateTimer_; } + +protected: + /*! + * \brief Returns true if the Newton method ought to be chatty. + */ + bool verbose_() const + { return params_.verbose_ && (simulator_.gridView().comm().rank() == 0); } + + /*! + * \brief Called before the Newton method is applied to an non-linear system of equations. + * + * \param nextSolution The initial solution vector + */ + void begin_(const SolutionVector& /*nextSolution*/) + { + numIterations_ = 0; + numLinearizations_ = 0; + error_ = 1e100; + + if (params_.writeConvergence_) { + convergenceWriter_.beginTimeStep(); + } + } + + /*! + * \brief Indicates the beginning of a Newton iteration. + */ + void beginIteration_() + { + // start with a clean message stream + const auto& comm = simulator_.gridView().comm(); + bool succeeded = true; + succeeded = comm.min(succeeded); + + if (!succeeded) { + throw NumericalProblem("TPSA: Pre-processing of the problem failed!"); + } + + lastError_ = error_; + } + + /*! + * \brief Linearize the global non-linear system of equations associated with the spatial domain. + */ + void linearizeDomain_() + { + model().linearizer().linearizeDomain(); + ++numLinearizations_; + } + + /*! + * \brief Linearize auxillary equations + */ + void linearizeAuxiliaryEquations_() + { + model().linearizer().linearizeAuxiliaryEquations(); + model().linearizer().finalize(); + } + + /*! + * \brief Compute error before a Newton iteration + * + * \param currentSolution Current solution vector + * \param currentResidual Current residual vector + */ + void preSolve_(const SolutionVector& /*currentSolution*/, + const GlobalEqVector& currentResidual) + { + const auto& constraintsMap = model().linearizer().constraintsMap(); + lastError_ = error_; + Scalar newtonMaxError = params_.maxError_; + + // Calculate the error as the maximum weighted tolerance of the solution's residual + error_ = 0; + const auto& elemMapper = simulator_.model().elementMapper(); + for (const auto& elem : elements(simulator_.gridView(), Dune::Partitions::interior)) { + unsigned dofIdx = elemMapper.index(elem); + + // Do not consider auxiliary DOFs for the error + if (dofIdx >= model().numGridDof() || model().dofTotalVolume(dofIdx) <= 0.0) { + continue; + } + + // Also do not consider DOFs which are constraint + if (enableConstraints_()) { + if (constraintsMap.count(dofIdx) > 0) { + continue; + } + } + + const auto& r = currentResidual[dofIdx]; + for (unsigned eqIdx = 0; eqIdx < r.size(); ++eqIdx) { + error_ = max(std::abs(r[eqIdx] * model().eqWeight(dofIdx, eqIdx)), error_); + } + } + + // Take the other processes into account + error_ = simulator_.gridView().comm().max(error_); + + // Make sure that the error never grows beyond the maximum allowed one + if (error_ > newtonMaxError) { + throw NumericalProblem("TPSA: Newton error " + std::to_string(double(error_)) + + " is larger than maximum allowed error of " + + std::to_string(double(newtonMaxError))); + } + } + + /*! + * \brief Update the error of the solution given the previous iteration. + * + * \note For our purposes, the error of a solution is defined as the maximum of the weighted residual of a given + * solution. + * + * \param currentSolution The solution at the beginning the current iteration + * \param currentResidual The residual (i.e., right-hand-side) of the current iteration's solution. + * \param solutionUpdate The difference between the current and the next solution + */ + void postSolve_(const SolutionVector& /*currentSolution*/, + const GlobalEqVector& /*currentResidual*/, + GlobalEqVector& /*solutionUpdate*/) + { } + + /*! + * \brief Update the current solution with a delta vector. + * + * \note Different update strategies, such as chopped updates can be implemented by overriding this method. The + * default behavior is use the standard Newton-Raphson update strategy, i.e. \f[ u^{k+1} = u^k - \Delta u^k \f] + * + * \param nextSolution The solution vector after the current iteration + * \param currentSolution The solution vector after the last iteration + * \param solutionUpdate The delta vector as calculated by solving the linear system of equations + * \param currentResidual The residual vector of the current Newton-Raphson iteraton + */ + void update_(SolutionVector& nextSolution, + const SolutionVector& currentSolution, + const GlobalEqVector& solutionUpdate, + const GlobalEqVector& currentResidual) + { + const auto& constraintsMap = model().linearizer().constraintsMap(); + + // first, write out the current solution to make convergence + // analysis possible + writeConvergence_(currentSolution, solutionUpdate); + + // make sure not to swallow non-finite values at this point + if (!std::isfinite(solutionUpdate.one_norm())) { + throw NumericalProblem("TPSA: Non-finite update in Newton!"); + } + + std::size_t numGridDof = model().numGridDof(); + for (unsigned dofIdx = 0; dofIdx < numGridDof; ++dofIdx) { + if (enableConstraints_()) { + if (constraintsMap.count(dofIdx) > 0) { + const auto& constraints = constraintsMap.at(dofIdx); + updateConstraintDof_(dofIdx, + nextSolution[dofIdx], + constraints); + } + else { + updatePrimaryVariables_(dofIdx, + nextSolution[dofIdx], + currentSolution[dofIdx], + solutionUpdate[dofIdx], + currentResidual[dofIdx]); + } + } + else { + updatePrimaryVariables_(dofIdx, + nextSolution[dofIdx], + currentSolution[dofIdx], + solutionUpdate[dofIdx], + currentResidual[dofIdx]); + } + } + + // update the DOFs of the auxiliary equations + // std::size_t numDof = model().numTotalDof(); + // for (std::size_t dofIdx = numGridDof; dofIdx < numDof; ++dofIdx) { + // nextSolution[dofIdx] = currentSolution[dofIdx]; + // nextSolution[dofIdx] -= solutionUpdate[dofIdx]; + // } + + // Update material state + model().updateMaterialState(/*timeIdx=*/0); + } + + /*! + * \brief Update the primary variables for a degree of freedom which is constraint. + * + * \param dofIdx Degree-of-freedom index + * \param nextValue The solution vector after the current iteration + * \param constraints Constraints for solution + * + */ + void updateConstraintDof_(unsigned /*dofIdx*/, + PrimaryVariables& /*nextValue*/, + const Constraints& /*constraints*/) + { } + + /*! + * \brief Update a single primary variables object. + * + * \param nextValue The solution vector after the current iteration + * \param currentValue The solution vector after the last iteration + * \param update The delta vector as calculated by solving the linear system of equations + * \param currentResidual The residual vector of the current Newton-Raphson iteraton + */ + void updatePrimaryVariables_(unsigned /*dofIdx*/, + PrimaryVariables& nextValue, + const PrimaryVariables& currentValue, + const EqVector& update, + const EqVector& /*currentResidual*/) + { + nextValue = currentValue; + nextValue -= update; + } + + /*! + * \brief Write the convergence behaviour of the newton method to disk. + * + * \param currentSolution The solution vector after the last iteration + * \param solutionUpdate The delta vector as calculated by solving the linear system of equations + * + * \note This method is called as part of the update proceedure. + */ + void writeConvergence_(const SolutionVector& currentSolution, + const GlobalEqVector& solutionUpdate) + { + if (params_.writeConvergence_) { + convergenceWriter_.beginIteration(); + convergenceWriter_.writeFields(currentSolution, solutionUpdate); + convergenceWriter_.endIteration(); + } + } + + /*! + * \brief Indicates that one Newton iteration was finished. + * + * \param nextSolution The solution after the current Newton iteration + * \param currentSolution The solution at the beginning of the current Newton iteration + */ + void endIteration_(const SolutionVector& nextSolution, + const SolutionVector& /*currentSolution*/) + { + ++numIterations_; + + const auto& comm = simulator_.gridView().comm(); + bool succeeded = true; + succeeded = comm.min(succeeded); + + if (!succeeded) { + throw NumericalProblem("TPSA: Post-processing of the problem failed!"); + } + + // if (verbose_()) { + // std::cout << "TPSA: End Newton iteration " << numIterations_ << "" + // << " with error = " << error_ + // << std::endl; + // } + } + + /*! + * \brief Returns true iff another Newton iteration should be done. + */ + bool proceed_() const + { + if (numIterations() < params_.minIterations_) { + return true; + } + else if (converged()) { + // we are below the specified tolerance, so we don't have to + // do more iterations + return false; + } + else if (numIterations() >= params_.maxIterations_) { + // we have exceeded the allowed number of steps. If the + // error was reduced by a factor of at least 4, + // in the last iterations we proceed even if we are above + // the maximum number of steps + return error_ * 4.0 < lastError_; + } + + return true; + } + + /*! + * \brief Indicates that we're done solving the non-linear system of equations. + */ + void end_() + { + if (params_.writeConvergence_) { + convergenceWriter_.endTimeStep(); + } + } + + /*! + * \brief Called if the Newton method broke down. + * + * \note This method is called _after_ end_() + */ + void failed_() + { numIterations_ = params_.targetIterations_ * 2; } + + /*! + * \brief Called if the Newton method was successful. + * + * \note This method is called _after_ end_() + */ + void succeeded_() + { } + + /*! + * \brief Get constraints from properties + */ + static bool enableConstraints_() + { return getPropValue(); } + + Simulator& simulator_; + LinearSolverBackend linearSolver_; + + Timer prePostProcessTimer_; + Timer linearizeTimer_; + Timer solveTimer_; + Timer updateTimer_; + + Scalar error_; + Scalar lastError_; + TpsaNewtonMethodParams params_; + + int numIterations_; + int numLinearizations_; + + ConvergenceWriter convergenceWriter_; +}; // class TpsaNewtonMethod + +} // namespace Opm + +#endif \ No newline at end of file diff --git a/opm/models/tpsa/tpsanewtonmethodparams.cpp b/opm/models/tpsa/tpsanewtonmethodparams.cpp new file mode 100644 index 00000000000..a74758fbd42 --- /dev/null +++ b/opm/models/tpsa/tpsanewtonmethodparams.cpp @@ -0,0 +1,82 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + This file is part of the Open Porous Media project (OPM). + + OPM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +#include "config.h" + +#include +#include + +#if HAVE_QUAD +#include +#endif + + +namespace Opm { + +/*! +* \brief Register runtime parameters for TPSA Newton method +*/ +template +void TpsaNewtonMethodParams::registerParameters() +{ + Parameters::Register + ("Specify whether the TPSA Newton method should inform the user about its progress or not"); + Parameters::Register + ("Write the convergence behaviour of the TPSA Newton method to a VTK file"); + Parameters::Register + ("The 'optimum' number of TPSA Newton iterations"); + Parameters::Register + ("The maximum number of TPSA Newton iterations"); + Parameters::Register + ("The minimum number of TPSA Newton iterations"); + Parameters::Register> + ("The maximum raw error tolerated by the TPSA Newton method for considering a solution to be converged"); + Parameters::Register> + ("The maximum error tolerated by the TPSA Newton method to which does not cause an abort"); +} + +/*! +* \brief Read and internalize runtime parameters for TPSA Newton method +*/ +template +void TpsaNewtonMethodParams::read() +{ + verbose_ = Parameters::Get(); + writeConvergence_ = Parameters::Get(); + targetIterations_ = Parameters::Get(); + minIterations_ = Parameters::Get(); + maxIterations_ = Parameters::Get(); + tolerance_ = Parameters::Get>(); + maxError_ = Parameters::Get>(); +} + +template struct TpsaNewtonMethodParams; + +#if FLOW_INSTANTIATE_FLOAT +template struct TpsaNewtonMethodParams; +#endif + +#if HAVE_QUAD +template struct TpsaNewtonMethodParams; +#endif + +} // namespace Opm diff --git a/opm/models/tpsa/tpsanewtonmethodparams.hpp b/opm/models/tpsa/tpsanewtonmethodparams.hpp new file mode 100644 index 00000000000..ea4638a423f --- /dev/null +++ b/opm/models/tpsa/tpsanewtonmethodparams.hpp @@ -0,0 +1,78 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + Copyright 2025 NORCE AS + + This file is part of the Open Porous Media project (OPM). + + OPM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +#ifndef TPSA_NEWTON_METHOD_PARAMS_HPP +#define TPSA_NEWTON_METHOD_PARAMS_HPP + + +namespace Opm::Parameters { + +// The maximum error which may occur in a simulation before the Newton method for the time step is aborted +template +struct TpsaNewtonMaxError { static constexpr Scalar value = 1e100; }; + +// Number of maximum iterations for the Newton method +struct TpsaNewtonMaxIterations { static constexpr int value = 20; }; + +// Number of minimum iterations for the Newton method +struct TpsaNewtonMinIterations { static constexpr int value = 1; }; + +// Target number of iterations +struct TpsaNewtonTargetIterations { static constexpr int value = 10; }; + +// Convergence tolerance +template +struct TpsaNewtonTolerance { static constexpr Scalar value = 1e-3; }; + +// Specifies whether the Newton method should print messages or not +struct TpsaNewtonVerbose { static constexpr bool value = true; }; + +// Specifies whether the convergence rate and the global residual gets written out to disk for every Newton iteration +struct TpsaNewtonWriteConvergence { static constexpr bool value = false; }; + +} // namespace Opm::Parameters + +namespace Opm { + +/*! +* \brief Struct holding the parameters for TpsaNewtonMethod. +*/ +template +struct TpsaNewtonMethodParams +{ + static void registerParameters(); + void read(); + + bool verbose_; + bool writeConvergence_; + int targetIterations_; + int minIterations_; + int maxIterations_; + Scalar tolerance_; + Scalar maxError_; +}; // struct TpsaNewtonMethodParams + +} // namespace Opm + +#endif diff --git a/opm/simulators/flow/BlackoilModelTPSA.hpp b/opm/simulators/flow/BlackoilModelTPSA.hpp new file mode 100644 index 00000000000..0a0b489c208 --- /dev/null +++ b/opm/simulators/flow/BlackoilModelTPSA.hpp @@ -0,0 +1,220 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + Copyright 2025 NORCE AS + + This file is part of the Open Porous Media project (OPM). + + OPM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +#ifndef BLACK_OIL_MODEL_TPSA_HPP +#define BLACK_OIL_MODEL_TPSA_HPP + +#include +#include + +#include + +#include +#include + +#include + + +namespace Opm { + +template +class BlackoilModelTPSA : public BlackoilModel +{ + using ParentType = BlackoilModel; + + using Scalar = GetPropType; + using Simulator = GetPropType; + +public: + using ModelParameters = typename ParentType::ModelParameters; + + /*! + * \brief Constructor + */ + explicit BlackoilModelTPSA(Simulator& simulator, + const ModelParameters& param, + BlackoilWellModel& well_model, + const bool terminal_output) + : ParentType(simulator, param, well_model, terminal_output) + {} + + /*! + * \brief Perform a nonlinear iteration updating Flow and TPSA geomechanics + * + * \param iteration Flow nonlinear iteration + * \param timer Simulation timer + * \param nonlinear_solver Nonlinear solver type + * + * \note Several strategies of updating flow and geomechanics may be implemented: + * fixed-stress: fixed-stress algorithm, i.e. iteratively solving Flow and TPSA equations in sequence + * lagged: one-way coupling where Flow is solved with TPSA info from previous time step + */ + template + SimulatorReportSingle nonlinearIteration(const int iteration, + const SimulatorTimerInterface& timer, + NonlinearSolverType& nonlinear_solver) + { + SimulatorReportSingle report {}; + const auto& problem = this->simulator_.problem(); + if (problem.fixedStressScheme()) { + report = nonlinearIterationFixedStressTPSA(iteration, timer, nonlinear_solver); + } + else if (problem.laggedScheme()) { + report = nonlinearIterationLaggedTPSA(iteration, timer, nonlinear_solver); + } + else { + std::string msg = "Unknown Flow-TPSA coupling scheme!"; + OpmLog::error(msg); + throw std::runtime_error(msg); + } + return report; + } + + /*! + * \brief Perform a nonlinear iteration updating Flow and TPSA geomechanics in a fixed-stress, iterative loop + * + * \param iteration Flow nonlinear iteration + * \param timer Simulation timer + * \param nonlinear_solver Nonlinear solver type + * + */ + template + SimulatorReportSingle nonlinearIterationFixedStressTPSA(const int iteration, + const SimulatorTimerInterface& timer, + NonlinearSolverType& nonlinear_solver) + { + // Runtime parameters + const auto& [minSeqIter, maxSeqIter] = this->simulator_.problem().fixedStressParameters(); + SimulatorReportSingle reportFlow; + + // Max. no. of fixed-stress iterations reached: warn and move on + if (seqIter_ >= maxSeqIter) { + // Warning + std::string msg = fmt::format("TPSA: Fixed-stress scheme reached max iterations (={})!", maxSeqIter); + OpmLog::warning(msg); + + // Return true Flow convergence to move to next time step and reset other variables + reportFlow.converged = true; + seqIter_ = 0; + + return reportFlow; + } + + // Prepare before first iteration + if (seqIter_ == 0) { + this->simulator_.problem().geoMechModel().prepareTPSA(); + } + + // Run Flow nonlinear iteration + reportFlow = ParentType::nonlinearIteration(iteration, timer, nonlinear_solver); + + // Solve TPSA equations if: + // (i) Flow has converged and run more than min number of Newton iterations + // (ii) we have run at least min. number of fixed-stress iterations + if (reportFlow.converged && iteration >= this->param_.newton_min_iter_) { + // Solve TPSA equations + bool tpsaConv = solveTpsaEquations(); + ++seqIter_; + + // Fixed-stress convergence check: + // If the initial residual error, hence check for no. linearizations == 1, was small enough, we have + // convergence in the fixed-stress iterations + if (tpsaConv + && this->simulator_.problem().geoMechModel().newtonMethod().numLinearizations() == 1 + && seqIter_ >= minSeqIter) { + // Info + std::string msg = fmt::format("TPSA: Fixed-stress scheme converged in {} iterations", seqIter_); + OpmLog::info(msg); + + // Reset + seqIter_ = 0; + + return reportFlow; + } + // Throw error if TPSA did not converge. Will force time step cuts in the outer Flow loop. + else if (!tpsaConv) { + // Reset + seqIter_ = 0; + + throw std::runtime_error("TPSA: Fixed stress scheme update failed!"); + } + + // Return Flow convergence false to do another fixed-stress iteration + reportFlow.converged = false; + } + + return reportFlow; + } + + /*! + * \brief Perform a nonlinear iteration updating Flow and TPSA geomechanics in a lagged scheme + * + * \param iteration Flow nonlinear iteration + * \param timer Simulation timer + * \param nonlinear_solver Nonlinear solver type + * + */ + template + SimulatorReportSingle nonlinearIterationLaggedTPSA(const int iteration, + const SimulatorTimerInterface& timer, + NonlinearSolverType& nonlinear_solver) + { + // Run Flow nonlinear iteration + auto reportFlow = ParentType::nonlinearIteration(iteration, timer, nonlinear_solver); + + // Update TPSA geomechanics from successful Flow run + if (reportFlow.converged && iteration >= this->param_.newton_min_iter_) { + // Prepare before TPSA solve + this->simulator_.problem().geoMechModel().prepareTPSA(); + + // Solve TPSA equations + bool tpsaConv = solveTpsaEquations(); + + // Throw error if TPSA did not converge. Will force time step cuts in the outer Flow loop. + if (!tpsaConv) { + throw std::runtime_error("TPSA: Lagged scheme update failed!"); + } + } + + return reportFlow; + } + + /*! + * \brief Solve TPSA geomechanics equations + * + * \note Calls Newton method for TPSA + */ + bool solveTpsaEquations() + { + // Run Newthon method for TPSA equations + return this->simulator_.problem().geoMechModel().newtonMethod().apply(); + } + +private: + unsigned seqIter_{0}; +}; // class BlackoilModelTPSA + +} // namespace Opm + +#endif \ No newline at end of file diff --git a/opm/simulators/flow/FacePropertiesTPSA.cpp b/opm/simulators/flow/FacePropertiesTPSA.cpp new file mode 100644 index 00000000000..65ccc0ffd93 --- /dev/null +++ b/opm/simulators/flow/FacePropertiesTPSA.cpp @@ -0,0 +1,76 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + Copyright 2025 NORCE AS + + This file is part of the Open Porous Media project (OPM). + + OPM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +#include + +#include + +#include + +#if HAVE_DUNE_FEM +#include +#include +#include +#endif + + +namespace Opm { + +#define INSTANTIATE_TYPE(T) \ + template class FacePropertiesTPSA>, \ + Dune::MultipleCodimMultipleGeomTypeMapper< \ + Dune::GridView< \ + Dune::DefaultLeafGridViewTraits>>, \ + Dune::CartesianIndexMapper, \ + T>; + +INSTANTIATE_TYPE(double) + +#if FLOW_INSTANTIATE_FLOAT +INSTANTIATE_TYPE(float) +#endif + +#ifdef HAVE_DUNE_FEM +using GV = Dune::Fem::AdaptiveLeafGridPart; +template class FacePropertiesTPSA, + Dune::CartesianIndexMapper, + double>; + +#if FLOW_INSTANTIATE_FLOAT +template class FacePropertiesTPSA, + Dune::CartesianIndexMapper, + float>; +#endif + +#endif // HAVE_DUNE_FEM + +} // namespace Opm \ No newline at end of file diff --git a/opm/simulators/flow/FacePropertiesTPSA.hpp b/opm/simulators/flow/FacePropertiesTPSA.hpp new file mode 100644 index 00000000000..6c58cc13f86 --- /dev/null +++ b/opm/simulators/flow/FacePropertiesTPSA.hpp @@ -0,0 +1,137 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + Copyright 2025 NORCE AS + + This file is part of the Open Porous Media project (OPM). + + OPM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +#ifndef TPSA_FACE_PROPERTIES_HPP +#define TPSA_FACE_PROPERTIES_HPP + +#include + +#include + +#include +#include +#include +#include + + +namespace Opm { + +class EclipseState; + + +template +class FacePropertiesTPSA { + + enum { dimWorld = GridView::dimensionworld }; + +public: + using DimVector = Dune::FieldVector; + + FacePropertiesTPSA(const EclipseState& eclState, + const GridView& gridView, + const CartesianIndexMapper& cartMapper, + const Grid& grid, + std::function(int)> centroids); + + void finishInit(); + + void update(); + + Scalar weightAverage(unsigned elemIdx1, unsigned elemIdx2) const; + Scalar weightAverageBoundary(unsigned elemIdx1, unsigned boundaryFaceIdx) const; + Scalar weightProduct(unsigned elemIdx1, unsigned elemIdx2) const; + Scalar weightProductBoundary(unsigned elemIdx1, unsigned boundaryFaceIdx) const; + Scalar normalDistance(unsigned elemIdx1, unsigned elemIdx2) const; + Scalar normalDistanceBoundary(unsigned elemIdx1, unsigned boundaryFaceIdx) const; + DimVector cellFaceNormal(unsigned elemIdx1, unsigned elemIdx2); + const DimVector& cellFaceNormalBoundary(unsigned elemIdx1, unsigned boundaryFaceIdx) const; + + /*! + * \brief Return shear modulus of an element + */ + const Scalar shearModulus(unsigned elemIdx) const + { return sModulus_[elemIdx]; } + + +protected: + struct FaceInfo + { + DimVector faceCenter; + int faceIdx; + unsigned elemIdx; + unsigned cartElemIdx; + }; + + template + void computeCellProperties(const Intersection& intersection, + FaceInfo& inside, + FaceInfo& outside, + DimVector& faceNormal, + /*isCpGrid=*/std::false_type) const; + + template + void computeCellProperties(const Intersection& intersection, + FaceInfo& inside, + FaceInfo& outside, + DimVector& faceNormal, + /*isCpGrid=*/std::true_type) const; + + Scalar computeDistance_(const DimVector& distVec, const DimVector& faceNormal); + + Scalar computeWeight_(const Scalar distance, const Scalar smod); + + DimVector distanceVector_(const DimVector& faceCenter, const unsigned& cellIdx) const; + + void extractSModulus_(); + + std::vector sModulus_; + std::unordered_map weightsAvg_; + std::unordered_map weightsProd_; + std::unordered_map distance_; + std::unordered_map faceNormal_; + + std::map, Scalar> weightsAvgBoundary_; + std::map, Scalar> weightsProdBoundary_; + std::map, Scalar> distanceBoundary_; + std::map, DimVector> faceNormalBoundary_; + + const EclipseState& eclState_; + const GridView& gridView_; + const CartesianIndexMapper& cartMapper_; + const Grid& grid_; + std::function(int)> centroids_; + std::vector> centroids_cache_; + + const LookUpData lookUpData_; + const LookUpCartesianData lookUpCartesianData_; + +}; // FacePropertiesTPSA + +namespace details { + std::uint64_t isIdTPSA(std::uint32_t elemIdx1, std::uint32_t elemIdx2); +} // namespace details + +} // namespace Opm + +#endif \ No newline at end of file diff --git a/opm/simulators/flow/FacePropertiesTPSA_impl.hpp b/opm/simulators/flow/FacePropertiesTPSA_impl.hpp new file mode 100644 index 00000000000..6405872e1cc --- /dev/null +++ b/opm/simulators/flow/FacePropertiesTPSA_impl.hpp @@ -0,0 +1,539 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + Copyright 2025 NORCE AS + + This file is part of the Open Porous Media project (OPM). + + OPM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +#ifndef TPSA_FACE_PROPERTIES_IMPL_HPP +#define TPSA_FACE_PROPERTIES_IMPL_HPP + +#ifndef TPSA_FACE_PROPERTIES_HPP +#include +#include +#endif + +#include + +#include + +#include + +#include + +#include + +#include +#include +#include + + +namespace Opm { + +// Copied from Opm::Transmissibility class +namespace details { + constexpr unsigned elemIdxShift = 32; // bits + + std::uint64_t isIdTPSA(std::uint32_t elemIdx1, std::uint32_t elemIdx2) + { + const std::uint32_t elemAIdx = std::min(elemIdx1, elemIdx2); + const std::uint64_t elemBIdx = std::max(elemIdx1, elemIdx2); + + return (elemBIdx << elemIdxShift) + elemAIdx; + } +} // namespace Opm::details + +// ///// +// Public functions +// //// +/*! +* \brief Constructor +* +* \param eclState Eclipse state made from a deck +* \param gridView The view on the DUNE grid which ought to be used (normally the leaf grid view) +* \param cartMapper The cartesian index mapper for lookup of cartesian indices +* \param grid The grid to lookup cell properties +* \param centroids Function to lookup cell centroids +*/ +template +FacePropertiesTPSA:: +FacePropertiesTPSA(const EclipseState& eclState, + const GridView& gridView, + const CartesianIndexMapper& cartMapper, + const Grid& grid, + std::function(int)> centroids) + : eclState_(eclState) + , gridView_(gridView) + , cartMapper_(cartMapper) + , grid_(grid) + , centroids_(centroids) + , lookUpData_(gridView) + , lookUpCartesianData_(gridView, cartMapper) +{ } + +/*! +* \brief Compute TPSA face properties +*/ +template +void FacePropertiesTPSA:: +finishInit() +{ + update(); +} + +/*! +* \brief Compute TPSA face properties +*/ +template +void FacePropertiesTPSA:: +update() +{ + // Number of elements + ElementMapper elemMapper(gridView_, Dune::mcmgElementLayout()); + unsigned numElements = elemMapper.size(); + + // Extract shear modulus (Lame's sec. params) + extractSModulus_(); + + // Init. containers + // Note (from Transmissibility::update): Reserving some space in the hashmap upfront saves quite a bit of time + // because resizes are costly for hashmaps and there would be quite a few of them if we would not have a rough idea + // of how large the final map will be (the rough idea is a conforming Cartesian grid). + const int num_threads = ThreadManager::maxThreads(); + weightsAvg_.clear(); + if (num_threads == 1) { + weightsAvg_.reserve(numElements * 3 * 1.05); + weightsProd_.reserve(numElements * 3 * 1.05); + distance_.reserve(numElements * 3 * 1.05); + faceNormal_.reserve(numElements * 3 * 1.05); + } + weightsAvgBoundary_.clear(); + weightsProdBoundary_.clear(); + distanceBoundary_.clear(); + faceNormalBoundary_.clear(); + + // Initialize thread safe insert_or_assign for face properties in the grid and separate for boundaries + ThreadSafeMapBuilder weightsAvgMap(weightsAvg_, num_threads, MapBuilderInsertionMode::Insert_Or_Assign); + ThreadSafeMapBuilder weightsProdMap(weightsProd_, num_threads, MapBuilderInsertionMode::Insert_Or_Assign); + ThreadSafeMapBuilder distanceMap(distance_, num_threads, MapBuilderInsertionMode::Insert_Or_Assign); + ThreadSafeMapBuilder faceNormalMap(faceNormal_, num_threads, MapBuilderInsertionMode::Insert_Or_Assign); + + ThreadSafeMapBuilder weightsAvgBoundaryMap(weightsAvgBoundary_, num_threads, MapBuilderInsertionMode::Insert_Or_Assign); + ThreadSafeMapBuilder weightsProdBoundaryMap(weightsProdBoundary_, num_threads, MapBuilderInsertionMode::Insert_Or_Assign); + ThreadSafeMapBuilder distanceBoundaryMap(distanceBoundary_, num_threads, MapBuilderInsertionMode::Insert_Or_Assign); + ThreadSafeMapBuilder faceNormalBoundaryMap(faceNormalBoundary_, num_threads, MapBuilderInsertionMode::Insert_Or_Assign); + +#ifdef _OPENMP +#pragma omp parallel for +#endif + // Loop over grid element an compute face properties + for (const auto& chunk : ElementChunks(gridView_, Dune::Partitions::all, num_threads)) { + for (const auto& elem : chunk) { + // Init. face info for inside/outside cells + FaceInfo inside; + FaceInfo outside; + DimVector faceNormal; + + // Set inside info + inside.elemIdx = elemMapper.index(elem); + inside.cartElemIdx = lookUpCartesianData_. + template getFieldPropCartesianIdx(inside.elemIdx); + + // Loop over intersection to neighboring cells + unsigned boundaryIsIdx = 0; + for (const auto& intersection : intersections(gridView_, elem)) { + // Handle grid boundary + if (intersection.boundary()) { + // One-sided cell properties + const auto& geometry = intersection.geometry(); + inside.faceCenter = geometry.center(); + faceNormal = intersection.centerUnitOuterNormal(); + + // Face properties on boundary + const auto index_pair = std::make_pair(inside.elemIdx, boundaryIsIdx); + Scalar distBound = computeDistance_(distanceVector_(inside.faceCenter, inside.elemIdx), faceNormal); + distanceBoundaryMap.insert_or_assign(index_pair, distBound); + + Scalar weightsAvgBound = 1.0; // w_j = 0 -> w_avg = wi / (wi + 0) + Scalar weightsProdBound = 0.0; // w_j = 0 -> w_prod = wi * 0 + weightsAvgBoundaryMap.insert_or_assign(index_pair, weightsAvgBound); + weightsProdBoundaryMap.insert_or_assign(index_pair, weightsProdBound); + + faceNormalBoundaryMap.insert_or_assign(index_pair, faceNormal); + + ++boundaryIsIdx; + continue; + } + + // Handle intersection on process boundary (i.e., neighbor on different rank) + if (!intersection.neighbor()) { + ++boundaryIsIdx; + continue; + } + + // Set outside info + const auto& outsideElem = intersection.outside(); + outside.elemIdx = elemMapper.index(outsideElem); + outside.cartElemIdx = lookUpCartesianData_. + template getFieldPropCartesianIdx(outside.elemIdx); + + // Skip intersections that have already been processed + if (std::tie(inside.cartElemIdx, inside.elemIdx) > std::tie(outside.cartElemIdx, outside.elemIdx)) { + continue; + } + + // Face indices for this intersection + inside.faceIdx = intersection.indexInInside(); + outside.faceIdx = intersection.indexInOutside(); + + // Set NNC face properties to zero + if (inside.faceIdx == -1) { + const auto id = details::isIdTPSA(inside.elemIdx, outside.elemIdx); + weightsAvgMap.insert_or_assign(id, 0.0); + distanceMap.insert_or_assign(id, 0.0); + faceNormalMap.insert_or_assign(id, DimVector{0.0, 0.0, 0.0}); + + continue; + } + + // Compute cell properties from grid + typename std::is_same::type isCpGrid; + computeCellProperties(intersection, + inside, + outside, + faceNormal, + isCpGrid); + + // Compute face properties + const auto id = details::isIdTPSA(inside.elemIdx, outside.elemIdx); + Scalar dist_in = computeDistance_(distanceVector_(inside.faceCenter, inside.elemIdx), faceNormal); + Scalar dist_out = computeDistance_(distanceVector_(outside.faceCenter, outside.elemIdx), faceNormal); + distanceMap.insert_or_assign(id, dist_in + dist_out); + + Scalar weight_in = computeWeight_(dist_in, sModulus_[inside.elemIdx]); + Scalar weight_out = computeWeight_(dist_out, sModulus_[outside.elemIdx]); + Scalar weightsAvg = weight_in / (weight_in + weight_out); + Scalar weightsProd = weight_in * weight_out; + weightsAvgMap.insert_or_assign(id, weightsAvg); + weightsProdMap.insert_or_assign(id, weightsProd); + + faceNormalMap.insert_or_assign(id, faceNormal); + } + } + } +} + +/*! +* \brief Average (half-)weight at interface between two elements +* +* \param elemIdx1 Cell index 1 +* \param elemIdx1 Cell index 2 +*/ +template +Scalar FacePropertiesTPSA:: +weightAverage(unsigned elemIdx1, unsigned elemIdx2) const +{ + auto tmp_whgt = weightsAvg_.at(details::isIdTPSA(elemIdx1, elemIdx2)); + + auto cartIdx1 = lookUpCartesianData_. + template getFieldPropCartesianIdx(elemIdx1); + auto cartIdx2 = lookUpCartesianData_. + template getFieldPropCartesianIdx(elemIdx2); + if (cartIdx1 < cartIdx2) { + return tmp_whgt; + } + else { + return 1.0 - tmp_whgt; + } +} + +/*! +* \brief Average (half-)weight at boundary interface +* +* \param elemIdx Cell index +* \param boundaryFaceIdx Face index +*/ +template +Scalar FacePropertiesTPSA:: +weightAverageBoundary(unsigned elemIdx, unsigned boundaryFaceIdx) const +{ + return weightsAvgBoundary_.at(std::make_pair(elemIdx, boundaryFaceIdx)); +} + +/*! +* \brief Product of weights at interface between two elements +* +* \param elemIdx1 Cell index 1 +* \param elemIdx1 Cell index 2 +*/ +template +Scalar FacePropertiesTPSA:: +weightProduct(unsigned elemIdx1, unsigned elemIdx2) const +{ + return weightsProd_.at(details::isIdTPSA(elemIdx1, elemIdx2)); +} + +/*! +* \brief Product of weights at boundary interface +* +* \param elemIdx Cell index +* \param boundaryFaceIdx Face index +*/ +template +Scalar FacePropertiesTPSA:: +weightProductBoundary(unsigned elemIdx, unsigned boundaryFaceIdx) const +{ + return weightsProdBoundary_.at(std::make_pair(elemIdx, boundaryFaceIdx)); +} + +/*! +* \brief Distance between two elements +* +* \param elemIdx1 Cell index 1 +* \param elemIdx1 Cell index 2 +*/ +template +Scalar FacePropertiesTPSA:: +normalDistance(unsigned elemIdx1, unsigned elemIdx2) const +{ + return distance_.at(details::isIdTPSA(elemIdx1, elemIdx2)); +} + +/*! +* \brief Distance to boundary interface +* +* \param elemIdx Cell index +* \param boundaryFaceIdx Face index +*/ +template +Scalar FacePropertiesTPSA:: +normalDistanceBoundary(unsigned elemIdx, unsigned boundaryFaceIdx) const +{ + return distanceBoundary_.at(std::make_pair(elemIdx, boundaryFaceIdx)); +} + +/*! +* \brief Cell face normal at interface between two elements +* +* \param elemIdx1 Cell index 1 +* \param elemIdx1 Cell index 2 +*/ +template +typename FacePropertiesTPSA::DimVector +FacePropertiesTPSA:: +cellFaceNormal(unsigned elemIdx1, unsigned elemIdx2) +{ + auto cartIdx1 = lookUpCartesianData_. + template getFieldPropCartesianIdx(elemIdx1); + auto cartIdx2 = lookUpCartesianData_. + template getFieldPropCartesianIdx(elemIdx2); + int sign = (cartIdx1 < cartIdx2) ? 1 : -1; + return sign * faceNormal_.at(details::isIdTPSA(elemIdx1, elemIdx2)); +} + +/*! +* \brief Cell face normal of boundary interface +* +* \param elemIdx Cell index +* \param boundaryFaceIdx Face index +*/ +template +const typename FacePropertiesTPSA::DimVector& +FacePropertiesTPSA:: +cellFaceNormalBoundary(unsigned elemIdx, unsigned boundaryFaceIdx) const +{ + return faceNormalBoundary_.at(std::make_pair(elemIdx, boundaryFaceIdx)); +} + +// ///// +// Protected functions +// //// +/*! +* \brief Compute normal distance from cell center to face center +* +* \param distVec Distance vector from cell center to face center +* \param faceNormal Face (unit) normal vector +*/ +template +Scalar FacePropertiesTPSA:: +computeDistance_(const DimVector& distVec, const DimVector& faceNormal) +{ + return std::abs(Dune::dot(faceNormal, distVec)); +} + +/*! +* \brief Compute face properties from general DUNE grid +* +* \param intersection Info on cell intersection +* \param inside Info describing inside face +* \param outside Info describing outside face +* \param faceNormal Face (unit) normal vector +*/ +template +template +void FacePropertiesTPSA:: +computeCellProperties(const Intersection& intersection, + FaceInfo& inside, + FaceInfo& outside, + DimVector& faceNormal, + /*isCpGrid=*/std::false_type) const +{ + // default implementation for DUNE grids + const auto& geometry = intersection.geometry(); + outside.faceCenter = inside.faceCenter = geometry.center(); + + // OBS: Have not checked if this points from cell with lower to higher index! + faceNormal = intersection.centerUnitOuterNormal(); +} + +/*! +* \brief Compute face properties from DUNE CpGrid +* +* \param intersection Info on cell intersection +* \param inside Info describing inside face +* \param outside Info describing outside face +* \param faceNormal Face (unit) normal +*/ +template +template +void FacePropertiesTPSA:: +computeCellProperties(const Intersection& intersection, + FaceInfo& inside, + FaceInfo& outside, + DimVector& faceNormal, + /*isCpGrid=*/std::true_type) const +{ + int faceIdx = intersection.id(); + if (grid_.maxLevel() == 0) { + // Face center coordinates + inside.faceCenter = grid_.faceCenterEcl(inside.elemIdx, inside.faceIdx, intersection); + outside.faceCenter = grid_.faceCenterEcl(outside.elemIdx, outside.faceIdx, intersection); + + // Face normal, ensuring it points from cell with lower to higher (global grid) index + faceNormal = grid_.faceNormal(faceIdx); + auto cartFaceCell0 = lookUpCartesianData_. + template getFieldPropCartesianIdx(grid_.faceCell(faceIdx, 0)); + auto cartFaceCell1 = lookUpCartesianData_. + template getFieldPropCartesianIdx(grid_.faceCell(faceIdx, 1)); + if (cartFaceCell0 > cartFaceCell1) { + faceNormal *= -1; + } + } + else { + throw std::runtime_error("TPSA not implemented with LGR"); + } + +} + +/*! +* \brief Compute weight ratio between distance and shear modulus +* +* \param distance Normal distance from cell to face center +* \param smod Shear modulus +*/ +template +Scalar FacePropertiesTPSA:: +computeWeight_(const Scalar distance, const Scalar smod) +{ + return distance / smod; +} + +/*! +* \brief Distance vector from cell center to face center +* +* \param faceCenter Face center coordinates +* \param cellIdx Cell index +*/ +template +typename FacePropertiesTPSA::DimVector +FacePropertiesTPSA:: +distanceVector_(const DimVector& faceCenter, const unsigned& cellIdx) const +{ + const auto& cellCenter = centroids_cache_.empty() ? centroids_(cellIdx) + : centroids_cache_[cellIdx]; + DimVector x = faceCenter; + for (unsigned dimIdx = 0; dimIdx < dimWorld; ++dimIdx) { + x[dimIdx] -= cellCenter[dimIdx]; + } + + return x; +} + +/*! +* \brief Extract shear modulus from eclState +* +* \note (from Transmissibility::extractPorosity()): +* All arrays provided by eclState are one-per-cell of "uncompressed" grid, whereas the simulation grid might remove a +* few elements (e.g. because it is distributed over several processes). +*/ +template +void FacePropertiesTPSA:: +extractSModulus_() +{ + unsigned numElem = gridView_.size(/*codim=*/0); + sModulus_.resize(numElem); + + const auto& fp = eclState_.fieldProps(); + std::vector sModulusData; + if (fp.has_double("SMODULUS")) { + sModulusData = this->lookUpData_.assignFieldPropsDoubleOnLeaf(fp, "SMODULUS"); + } + else if (fp.has_double("YMODULE") && fp.has_double("LAME")) { + // Convert from Young's modulus and Lame's first parameter + const std::vector& ymodulus = this->lookUpData_.assignFieldPropsDoubleOnLeaf(fp, "YMODULE"); + const std::vector& lameParam = this->lookUpData_.assignFieldPropsDoubleOnLeaf(fp, "LAME"); + sModulusData.resize(numElem); + for (std::size_t i = 0; i < sModulusData.size(); ++i) { + const double r = std::sqrt(ymodulus[i] * ymodulus[i] + 9 * lameParam[i] * lameParam[i] + + 2 * ymodulus[i] * lameParam[i]); + sModulusData[i] = (ymodulus[i] - 3 * lameParam[i] + r) / 4.0; + } + } + else if (fp.has_double("YMODULE") && fp.has_double("PRATIO")) { + const std::vector& ymodulus = this->lookUpData_.assignFieldPropsDoubleOnLeaf(fp, "YMODULE"); + const std::vector& pratio = this->lookUpData_.assignFieldPropsDoubleOnLeaf(fp, "PRATIO"); + sModulusData.resize(numElem); + for (std::size_t i = 0; i < sModulusData.size(); ++i) { + sModulusData[i] = ymodulus[i] / (2 * (1 + pratio[i])); + } + } + else if (fp.has_double("LAME") && fp.has_double("PRATIO")) { + const std::vector& lameParam = this->lookUpData_.assignFieldPropsDoubleOnLeaf(fp, "LAME"); + const std::vector& pratio = this->lookUpData_.assignFieldPropsDoubleOnLeaf(fp, "PRATIO"); + sModulusData.resize(numElem); + for (std::size_t i = 0; i < sModulusData.size(); ++i) { + sModulusData[i] = lameParam[i] * (1 - 2 * pratio[i]) / (2 * pratio[i]); + } + } + else { + throw std::logic_error("Cannot read shear modulus data from ecl state, SMODULUS keyword missing, " + "and one of the following keyword pairs are missing for conversion: " + "(YMODULE, LAME), (YMODULE, PRATIO) and (LAME, PRATIO)!"); + } + + // Assign shear modulus + for (std::size_t dofIdx = 0; dofIdx < numElem; ++ dofIdx) { + sModulus_[dofIdx] = sModulusData[dofIdx]; + } +} + +} // namespace Opm + +#endif \ No newline at end of file diff --git a/opm/simulators/flow/FlowGenericProblem.hpp b/opm/simulators/flow/FlowGenericProblem.hpp index cbcac99471c..47e13e57732 100644 --- a/opm/simulators/flow/FlowGenericProblem.hpp +++ b/opm/simulators/flow/FlowGenericProblem.hpp @@ -148,6 +148,21 @@ class FlowGenericProblem */ Scalar rockFraction(unsigned elementIdx, unsigned timeIdx) const; + /*! + * \brief Returns the rock compressibility of an element due to poroelasticity + */ + Scalar rockBiotComp(unsigned elementIdx) const; + + /*! + * \brief Direct access to Lame's second parameter in an element + */ + Scalar lame(unsigned elementIdx) const; + + /*! + * \brief Direct access to Biot coefficient in an element + */ + Scalar biotCoeff(unsigned elementIdx) const; + /*! * \brief Sets the porosity of an element * diff --git a/opm/simulators/flow/FlowGenericProblem_impl.hpp b/opm/simulators/flow/FlowGenericProblem_impl.hpp index 9312f63885e..4ae01fe56bb 100644 --- a/opm/simulators/flow/FlowGenericProblem_impl.hpp +++ b/opm/simulators/flow/FlowGenericProblem_impl.hpp @@ -353,6 +353,69 @@ rockFraction(unsigned elementIdx, unsigned timeIdx) const return (1 - poro_eff) * referencePorosity(elementIdx, timeIdx) / poro_eff; } +template +typename FlowGenericProblem::Scalar +FlowGenericProblem:: +rockBiotComp(unsigned elementIdx) const +{ + // Additional compressibility of the rock due to Biot poroelasticity + auto biot = biotCoeff(elementIdx); + auto lameParam = lame(elementIdx); + return biot * biot / lameParam; +} + +template +typename FlowGenericProblem::Scalar +FlowGenericProblem:: +lame(unsigned elementIdx) const +{ + Scalar lameParam; + const auto& fp = eclState_.fieldProps(); + if (fp.has_double("LAME")) { + lameParam = this->lookUpData_.fieldPropDouble(this->eclState_.fieldProps(), "LAME", elementIdx); + } + else if (fp.has_double("YMODULE") && fp.has_double("SMODULUS")) { + const auto& yModulus = this->lookUpData_.fieldPropDouble(this->eclState_.fieldProps(), "YMODULE", elementIdx); + const auto& sModulus = this->lookUpData_.fieldPropDouble(this->eclState_.fieldProps(), "SMODULUS", elementIdx); + lameParam = sModulus * (yModulus - 2 * sModulus) / (3 * sModulus - yModulus); + } + else if (fp.has_double("YMODULE") && fp.has_double("PRATIO")) { + const auto& yModulus = this->lookUpData_.fieldPropDouble(this->eclState_.fieldProps(), "YMODULE", elementIdx); + const auto& pRatio = this->lookUpData_.fieldPropDouble(this->eclState_.fieldProps(), "PRATIO", elementIdx); + lameParam = yModulus * pRatio / ((1 + pRatio) * (1 - 2 * pRatio)); + } + else if (fp.has_double("SMODULUS") && fp.has_double("PRATIO")) { + const auto& sModulus = this->lookUpData_.fieldPropDouble(this->eclState_.fieldProps(), "SMODULUS", elementIdx); + const auto& pRatio = this->lookUpData_.fieldPropDouble(this->eclState_.fieldProps(), "PRATIO", elementIdx); + lameParam = 2 * sModulus * pRatio / (1 - 2 * pRatio); + } + else { + return 0.0; + } + return lameParam; +} + +template +typename FlowGenericProblem::Scalar +FlowGenericProblem:: +biotCoeff(unsigned elementIdx) const +{ + Scalar biotC; + const auto& fp = eclState_.fieldProps(); + if (fp.has_double("BIOTCOEF")) { + biotC = this->lookUpData_.fieldPropDouble(this->eclState_.fieldProps(), "BIOTCOEF", elementIdx); + } + else if (fp.has_double("POELCOEF") && fp.has_double("PRATIO")) { + const auto& poelC = this->lookUpData_.fieldPropDouble(this->eclState_.fieldProps(), "POELCOEF", elementIdx); + const auto& pRatio = this->lookUpData_.fieldPropDouble(this->eclState_.fieldProps(), "PRATIO", elementIdx); + biotC = poelC * (1 - pRatio) / (1 - 2 * pRatio); + } + else { + return 0.0; + } + return biotC; +} + template template void FlowGenericProblem:: diff --git a/opm/simulators/flow/FlowProblem.hpp b/opm/simulators/flow/FlowProblem.hpp index d0aafc775cc..336efb5c14a 100644 --- a/opm/simulators/flow/FlowProblem.hpp +++ b/opm/simulators/flow/FlowProblem.hpp @@ -173,6 +173,9 @@ class FlowProblem : public GetPropType using BaseType::helpPreamble; using BaseType::shouldWriteOutput; using BaseType::shouldWriteRestartFile; + using BaseType::rockBiotComp; + using BaseType::lame; + using BaseType::biotCoeff; using BaseType::rockCompressibility; using BaseType::porosity; @@ -706,6 +709,36 @@ class FlowProblem : public GetPropType return this->rockCompressibility(globalSpaceIdx); } + /*! + * \copydoc BlackoilProblem::rockBiotComp + */ + template + Scalar rockBiotComp(const Context& context, unsigned spaceIdx, unsigned timeIdx) const + { + unsigned globalSpaceIdx = context.globalSpaceIndex(spaceIdx, timeIdx); + return this->rockBiotComp(globalSpaceIdx); + } + + /*! + * \copydoc BlackoilProblem::lame + */ + template + Scalar lame(const Context& context, unsigned spaceIdx, unsigned timeIdx) const + { + unsigned globalSpaceIdx = context.globalSpaceIndex(spaceIdx, timeIdx); + return this->lame(globalSpaceIdx); + } + + /*! + * \copydoc BlackoilProblem::biotCoeff + */ + template + Scalar biotCoeff(const Context& context, unsigned spaceIdx, unsigned timeIdx) const + { + unsigned globalSpaceIdx = context.globalSpaceIndex(spaceIdx, timeIdx); + return this->biotCoeff(globalSpaceIdx); + } + /*! * \copydoc BlackoilProblem::rockReferencePressure */ diff --git a/opm/simulators/flow/FlowProblemTPSA.hpp b/opm/simulators/flow/FlowProblemTPSA.hpp new file mode 100644 index 00000000000..12fb33c5b48 --- /dev/null +++ b/opm/simulators/flow/FlowProblemTPSA.hpp @@ -0,0 +1,507 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + Copyright 2025 NORCE AS + + This file is part of the Open Porous Media project (OPM). + + OPM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +#ifndef FLOW_PROBLEM_TPSA_HPP +#define FLOW_PROBLEM_TPSA_HPP + +#include + +#include + +#include + +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include + + +namespace Opm { + +template +class FlowProblemTPSA : public FlowProblemBlackoil +{ +public: + using ParentType = FlowProblemBlackoil; + + using ElementContext = GetPropType; + using ElementMapper = GetPropType; + using Evaluation = GetPropType; + using FluidSystem = GetPropType; + using GeomechModel = GetPropType; + using Grid = GetPropType; + using GridView = GetPropType; + using Indices = GetPropType; + using RateVector = GetPropType; + using Scalar = GetPropType; + using Simulator = GetPropType; + + enum { dimWorld = GridView::dimensionworld }; + enum { enableMech = getPropValue() }; + enum { historySize = getPropValue() }; + enum { numEq = getPropValue() }; + enum { numPhases = FluidSystem::numPhases }; + + enum { contiRotEqIdx = Indices::contiRotEqIdx }; + enum { contiSolidPresEqIdx = Indices::contiSolidPresEqIdx }; + enum { solidPres0Idx = Indices::solidPres0Idx }; + + using CartesianIndexMapper = Dune::CartesianIndexMapper; + using DimVector = Dune::FieldVector; + using FaceProperties = FacePropertiesTPSA; + using InitialMaterialState = MaterialStateTPSA; + using Toolbox = MathToolbox; + + // /// + // Public functions + // /// + /*! + * \brief Constructor + */ + FlowProblemTPSA(Simulator& simulator) + : ParentType(simulator) + , faceProps_(simulator.vanguard().eclState(), + simulator.vanguard().gridView(), + simulator.vanguard().cartesianIndexMapper(), + simulator.vanguard().grid(), + simulator.vanguard().cellCentroids()) + , geoMechModel_(simulator) + { + if constexpr(enableMech) { + // Add VTK TPSA to output module + this->model().addOutputModule(std::make_unique>(simulator)); + + // Sanity check + const auto& tpsa = simulator.vanguard().eclState().runspec().tpsa(); + if (!tpsa.active()) { + std::string msg = "Simulator with Tpsa-geomechanics enabled compile time, but deck does not contain " + "TPSA keyword!"; + OpmLog::error(msg); + throw std::runtime_error(msg); + } + } + else { + // Sanity check + const auto& tpsa = simulator.vanguard().eclState().runspec().tpsa(); + if (tpsa.active()) { + std::string msg = "TPSA keyword in deck, but Tpsa-geomechanics disabled compile-time!"; + OpmLog::error(msg); + throw std::runtime_error(msg); + } + } + } + + /*! + * \brief Register runtime parameters + */ + static void registerParameters() + { + // Register parameters for parent class + ParentType::registerParameters(); + + // Geomech model parameters + GeomechModel::registerParameters(); + + // VTK output parameters + VtkTpsaModule::registerParameters(); + } + + /*! + * \brief Initialize the problem + */ + void finishInit() + { + // FlowProblemBlackoil::finishInit() + ParentType::finishInit(); + + // Read initial conditions and set material state + readInitalConditionsTPSA_(); + + // Set equation weights + computeAndSetEqWeights_(); + + // Calculate face properties + faceProps_.finishInit(); + + // Initialize the TPSA model + geoMechModel_.finishInit(); + } + + /*! + * \brief Set initial solution for the problem + * + * \note Mostly copy of FvBaseDiscretization::applyInitialSolution and FlowProblemBlackoil::initial() + */ + void initialSolutionApplied() + { + // Set up initial solution for the Flow model + ParentType::initialSolutionApplied(); + + // Initialize soultions as zero + auto& uCur = geoMechModel_.solution(/*timeIdx=*/0); + uCur = Scalar(0.0); + + // Loop through grid and set initial solution from material state + ElementContext elemCtx(this->simulator()); + for (const auto& elem : elements(this->gridView())) { + // Ignore everything which is not in the interior if the current process' piece of the grid + if (elem.partitionType() != Dune::InteriorEntity) { + continue; + } + + // Loop over sub control volumes and set initial solutions + elemCtx.updateStencil(elem); + for (unsigned dofIdx = 0; dofIdx < elemCtx.numPrimaryDof(/*timeIdx=*/0); ++dofIdx) { + const unsigned globalIdx = elemCtx.globalSpaceIndex(dofIdx, /*timeIdx=*/0); + auto& priVars = uCur[globalIdx]; + priVars.assignNaive(initialMaterialState_[globalIdx]); + priVars.checkDefined(); + } + } + + // synchronize the ghost/overlapping DOFs (if necessary) + geoMechModel_.syncOverlap(); + + // Set history solutions to the initial solution. + for (unsigned timeIdx = 1; timeIdx < historySize; ++timeIdx) { + geoMechModel_.solution(timeIdx) = geoMechModel_.solution(/*timeIdx=*/0); + } + + // Set material state + geoMechModel_.updateMaterialState(/*timeIdx=*/0); + } + + /*! + * \brief Compute weights to rescale the TPSA equations + */ + void computeAndSetEqWeights_() + { + // Average shear modulus over domain + Scalar avgSmodulus = 0.0; + const auto& gridView = this->gridView(); + ElementContext elemCtx(this->simulator()); + for(const auto& elem: elements(gridView, Dune::Partitions::interior)) { + elemCtx.updatePrimaryStencil(elem); + int elemIdx = elemCtx.globalSpaceIndex(/*spaceIdx=*/0, /*timeIdx=*/0); + avgSmodulus += this->lame(elemIdx); + } + std::size_t numDof = this->model().numGridDof(); + const auto& comm = this->simulator().vanguard().grid().comm(); + comm.sum(avgSmodulus); + Scalar numTotalDof = comm.sum(numDof); + avgSmodulus /= numTotalDof; + avgSmodulus = std::sqrt(avgSmodulus); + + for (unsigned eqIdx = 0; eqIdx < numEq; ++eqIdx) { + if (eqIdx < contiRotEqIdx) { + geoMechModel_.setEqWeight(eqIdx, 1 / avgSmodulus); + } + else { + geoMechModel_.setEqWeight(eqIdx, avgSmodulus); + } + } + } + + /*! + * \brief Called by the simulator before each time integration. + */ + void beginTimeStep() override + { + // Call parent class beginTimeStep() + ParentType::beginTimeStep(); + + // Update mechanics boundary conditions. + // NOTE: Flow boundary conditions should be updated in ParentType::beginTimeStep() + if (this->nonTrivialBoundaryConditions()) { + geoMechModel_.linearizer().updateBoundaryConditionData(); + } + } + + /*! + * \brief Organize mechanics boundary conditions + * + * \param globalSpaceIdx Cell index + * \param directionId Direction id + * + * \note Only BCMECHTYPE = FREE and NONE implemented. FIXED will/should throw an error when computed in local + * residual! + */ + std::pair> + mechBoundaryCondition(const unsigned int globalSpaceIdx, const int directionId) + { + // Default boundary conditions if BCCON/BCPROP not defined + if (!this->nonTrivialBoundaryConditions()) { + return { BCMECHType::NONE, Dune::FieldVector{0.0, 0.0, 0.0} }; + } + + // Default for BCPROP index = 0 or no BCPROP defined at current episode + FaceDir::DirEnum dir = FaceDir::FromIntersectionIndex(directionId); + const auto& schedule = this->simulator().vanguard().schedule(); + if (this->bcindex_(dir)[globalSpaceIdx] == 0 || schedule[this->episodeIndex()].bcprop.size() == 0) { + return { BCMECHType::NONE, Dune::FieldVector{0.0, 0.0, 0.0} }; + } + + // Get current BC + const auto& bc = schedule[this->episodeIndex()].bcprop[this->bcindex_(dir)[globalSpaceIdx]]; + if (bc.bcmechtype == BCMECHType::FREE) { + return { BCMECHType::FREE, Dune::FieldVector{0.0, 0.0, 0.0} }; + } + else { + return { bc.bcmechtype, Dune::FieldVector{0.0, 0.0, 0.0} }; + } + } + + /*! + * \brief Set mechanics source term, in particular coupling terms + * + * \param sourceTerm Source term vector + * \param globalSpaceIdx Cell index + * \param timeIdx Time index + */ + void tpsaSource(Dune::FieldVector& sourceTerm, + unsigned globalSpaceIdx, + unsigned timeIdx) + { + sourceTerm = 0.0; + + // Coupling term Flow -> TPSA + const auto biot = this->biotCoeff(globalSpaceIdx); + const auto lameParam = this->lame(globalSpaceIdx); + + const auto& iq = this->model().intensiveQuantities(globalSpaceIdx, 0); + const auto& fs = iq.fluidState(); + const auto pres = decay(fs.pressure(this->refPressurePhaseIdx_())); + const auto initPres = this->initialFluidState(globalSpaceIdx).pressure(this->refPressurePhaseIdx_()); + + auto sourceFromFlow = -biot / lameParam * (pres - initPres); + sourceTerm[contiSolidPresEqIdx] += sourceFromFlow; + } + + /*! + * \brief Pore volume change due to geomechanics + * + * \param globalDofIdx Cell index + * \param timeIdx Time index + * + * \note This is the coupling term to Flow + */ + Scalar rockMechPoroChange(unsigned elementIdx, unsigned timeIdx) const + { + // TODO: get timeIdx=1 solid pressure from a cached materialState (or intensiveQuantities) if/when implemented + assert (timeIdx <= historySize); + const auto solidPres = (timeIdx == 0) ? + decay( geoMechModel_.materialState(elementIdx, /*timeIdx=*/timeIdx).solidPressure()) : + geoMechModel_.solution(/*timeIdx=*/timeIdx)[elementIdx][solidPres0Idx]; + const auto biot = this->biotCoeff(elementIdx); + const auto lameParam = this->lame(elementIdx); + + return biot / lameParam * solidPres; + } + + // /// + // Public get functions + // /// + /*! + * \brief Direct access to average (half-)weight at interface between two elements + * + * \param globalElemIdxIn Inside cell index + * \param globalElemIdxOut Outside cell index + */ + Scalar weightAverage(unsigned globalElemIdxIn, unsigned globalElemIdxOut) + { + return faceProps_.weightAverage(globalElemIdxIn, globalElemIdxOut); + } + + /*! + * \brief Direct access to normal distance at the boundary + * + * \param globalElemIdxIn Inside cell index + * \param boundaryFaceIdx Boundary (local) face index + */ + Scalar weightAverageBoundary(unsigned globalElemIdxIn, unsigned boundaryFaceIdx) const + { + return faceProps_.weightAverageBoundary(globalElemIdxIn, boundaryFaceIdx); + } + + /*! + * \brief Direct access to product of weights at interface between two elements + * + * \param globalElemIdxIn Inside cell index + * \param globalElemIdxOut Outside cell index + */ + Scalar weightProduct(unsigned globalElemIdxIn, unsigned globalElemIdxOut) const + { + return faceProps_.weightProduct(globalElemIdxIn, globalElemIdxOut); + } + + /*! + * \brief Direct access to normal distance between two elements + * + * \param globalElemIdxIn Inside cell index + * \param globalElemIdxOut Outside cell index + */ + Scalar normalDistance(unsigned globalElemIdxIn, unsigned globalElemIdxOut) const + { + return faceProps_.normalDistance(globalElemIdxIn, globalElemIdxOut); + } + + /*! + * \brief Direct access to normal distance at the boundary + * + * \param globalElemIdxIn Inside cell index + * \param boundaryFaceIdx Boundary (local) face index + */ + Scalar normalDistanceBoundary(unsigned globalElemIdxIn, unsigned boundaryFaceIdx) const + { + return faceProps_.normalDistanceBoundary(globalElemIdxIn, boundaryFaceIdx); + } + + /*! + * \brief Direct access to face normal between two elements + * + * \param globalElemIdxIn Inside cell index + * \param globalElemIdxOut Outside cell index + */ + DimVector cellFaceNormal(unsigned globalElemIdxIn, unsigned globalElemIdxOut) + { + return faceProps_.cellFaceNormal(globalElemIdxIn, globalElemIdxOut); + } + + /*! + * \brief Direct access to face normal at the boundary + * + * \param globalElemIdxIn Inside cell index + * \param boundaryFaceIdx Boundary (local) face index + */ + const DimVector& cellFaceNormalBoundary(unsigned globalElemIdxIn, unsigned boundaryFaceIdx) const + { + return faceProps_.cellFaceNormalBoundary(globalElemIdxIn, boundaryFaceIdx); + } + + /*! + * \brief Direct access to shear modulus in an element + * + * \param globalElemIdx Cell index + */ + Scalar shearModulus(unsigned globalElemIdx) const + { + return faceProps_.shearModulus(globalElemIdx); + } + + /*! + * \brief Flow-TPSA lagged coupling scheme activated? + */ + bool laggedScheme() const + { + const auto& tpsa = this->simulator().vanguard().eclState().runspec().tpsa(); + return tpsa.laggedScheme(); + } + + /*! + * \brief Flow-TPSA fixed-stress coupling scheme activated? + */ + bool fixedStressScheme() const + { + const auto& tpsa = this->simulator().vanguard().eclState().runspec().tpsa(); + return tpsa.fixedStressScheme(); + } + + /*! + * \brief Get TPSA model + */ + const GeomechModel& geoMechModel() const + { + return geoMechModel_; + } + + /*! + * \brief Get TPSA model + */ + GeomechModel& geoMechModel() + { + return geoMechModel_; + } + + /*! + * \brief Get fixed-stress iteration parameters + */ + std::pair fixedStressParameters() const + { + const auto& tpsa = this->simulator().vanguard().eclState().runspec().tpsa(); + return std::make_pair(tpsa.fixedStressMinIter(), tpsa.fixedStressMaxIter()); + } + +protected: + // /// + // Protected functions + // /// + /*! + * \brief Read initial conditions and generate material state for TPSA model + */ + void readInitalConditionsTPSA_() + { + // /// + // OBS: No equilibration keywords (e.g., STREQUIL) implemented yet! + // /// + + // Set all initial material state variables to zero + std::size_t numDof = this->model().numGridDof(); + initialMaterialState_.resize(numDof); + for (std::size_t dofIdx = 0; dofIdx < numDof; ++dofIdx) { + auto& dofMaterialState = initialMaterialState_[dofIdx]; + for (unsigned dirIdx = 0; dirIdx < 3; ++dirIdx) { + dofMaterialState.setDisplacement(dirIdx, 0.0); + dofMaterialState.setRotation(dirIdx, 0.0); + } + dofMaterialState.setSolidPressure(0.0); + } + } + +private: + FaceProperties faceProps_; + GeomechModel geoMechModel_; + + std::vector biotcoeff_; + std::vector initialMaterialState_; +}; // class FlowProblemTPSA + +} // namespace Opm + +#endif \ No newline at end of file diff --git a/opm/simulators/flow/MainDispatchDynamic.cpp b/opm/simulators/flow/MainDispatchDynamic.cpp index a476587d075..2eee136d76b 100644 --- a/opm/simulators/flow/MainDispatchDynamic.cpp +++ b/opm/simulators/flow/MainDispatchDynamic.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -42,6 +43,7 @@ #include #include #include +#include #include #include #include @@ -53,6 +55,7 @@ #include #include #include +#include #include #include #include @@ -177,6 +180,8 @@ int Opm::Main::runTwoPhase(const Phases& phases) const bool disgasw = eclipseState_->getSimulationConfig().hasDISGASW(); const bool vapwat = eclipseState_->getSimulationConfig().hasVAPWAT(); + const auto& rspec = this->eclipseState_->runspec(); + // oil-gas if (phases.active(Phase::OIL) && phases.active(Phase::GAS)) { if (diffusive) { @@ -208,6 +213,9 @@ int Opm::Main::runTwoPhase(const Phases& phases) outputCout_, outputFiles_); } + else if (rspec.tpsa().active()) { + return flowGasWaterDissolutionTpsaMain(argc_, argv_, outputCout_, outputFiles_); + } return flowGasWaterDissolutionMain(argc_, argv_, outputCout_, outputFiles_); } @@ -285,6 +293,8 @@ int Opm::Main::runFoam() int Opm::Main::runWaterOnly(const Phases& phases) { + const auto& rspec = this->eclipseState_->runspec(); + if (!phases.active(Phase::WATER) || phases.size() != 1) { if (outputCout_) { std::cerr << "No valid configuration is found for " @@ -295,6 +305,10 @@ int Opm::Main::runWaterOnly(const Phases& phases) return EXIT_FAILURE; } + if (rspec.tpsa().active()) { + return flowWaterOnlyTpsaMain(argc_, argv_, outputCout_, outputFiles_); + } + return flowWaterOnlyMain(argc_, argv_, outputCout_, outputFiles_); } @@ -432,6 +446,11 @@ int Opm::Main::runBlackOil() return flowBlackoilMain(argc_, argv_, outputCout_, outputFiles_); } if (this->eclipseState_->runspec().hysterPar().active()) { + if (this->eclipseState_->runspec().tpsa().active()) { + // Blackoil + TPSA geomechanics + return flowBlackoilTpsaMain(argc_, argv_, outputCout_, outputFiles_); + } + return flowBlackoilTpfaMain(argc_, argv_, outputCout_, outputFiles_); } else { // Use variant without hysteresis support to save memory. diff --git a/opm/simulators/flow/TTagFlowProblemTPSA.hpp b/opm/simulators/flow/TTagFlowProblemTPSA.hpp new file mode 100644 index 00000000000..055db0ae481 --- /dev/null +++ b/opm/simulators/flow/TTagFlowProblemTPSA.hpp @@ -0,0 +1,160 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + Copyright 2025 NORCE AS + + This file is part of the Open Porous Media project (OPM). + + OPM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +#ifndef TTAG_FLOW_PROBLEM_TPSA_HPP +#define TTAG_FLOW_PROBLEM_TPSA_HPP + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + + +namespace Opm::Properties { + +namespace TTag { + +struct FlowProblemTpsa {}; + +} // Opm::Properties::TTag + +// TPSA indices for primary variables and equations +template +struct IndicesTPSA +{ + using type = ElasticityIndices; +}; + +// Number of TPSA equations +template +struct NumEqTPSA +{ static constexpr int value = GetPropType::numEq; }; + +// TPSA linearizer +template +struct LinearizerTPSA +{ using type = TpsaLinearizer; }; + +// Set the function evaluation w.r.t. the TPSA primary variables +template +struct EvaluationTPSA +{ +private: + static constexpr unsigned numEq = getPropValue(); + + using Scalar = GetPropType; + +public: + using type = DenseAd::Evaluation; +}; + +// TPSA equation vector +template +struct EqVectorTPSA +{ + using type = Dune::FieldVector, + getPropValue()>; +}; + +// Global TPSA equation vector +template +struct GlobalEqVectorTPSA +{ using type = Dune::BlockVector>; }; + +// TPSA Newton method +template +struct NewtonMethodTPSA +{ using type = TpsaNewtonMethod; }; + +template +struct NewtonConvergenceWriterTPSA +{ using type = TpsaNewtonConvergenceWriter; }; + +// TPSA primary variables +template +struct PrimaryVariablesTPSA +{ using type = ElasticityPrimaryVariables; }; + +// TPSA solution vector +template +struct SolutionVectorTPSA +{ using type = Dune::BlockVector>; }; + +// TPSA number of historic solutions to save +template +struct SolutionHistorySizeTPSA +{ static constexpr int value = 2; }; + +// TPSA model +template +struct ModelTPSA +{ using type = TpsaModel; }; + +// TPSA local residual +template +struct LocalResidualTPSA +{ using type = ElasticityLocalResidual; }; + +// TPSA sparse matrix adapter for Jacobian +template +struct SparseMatrixAdapterTPSA +{ +private: + using Scalar = GetPropType; + enum { numEq = getPropValue() }; + using Block = MatrixBlock; + +public: + using type = typename Linear::IstlSparseMatrixAdapter; + +}; + +// Disable constraints in Newton method +template +struct EnableConstraintsTPSA +{ static constexpr bool value = false; }; + +// Set linear solver backend +template +struct LinearSolverBackendTPSA +{ using type = ISTLSolverTPSA; }; + +} // namespace Opm::Properties + +#endif \ No newline at end of file diff --git a/opm/simulators/linalg/ISTLSolverTPSA.hpp b/opm/simulators/linalg/ISTLSolverTPSA.hpp new file mode 100644 index 00000000000..aca2229bc90 --- /dev/null +++ b/opm/simulators/linalg/ISTLSolverTPSA.hpp @@ -0,0 +1,388 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + Copyright 2025 NORCE AS + + This file is part of the Open Porous Media project (OPM). + + OPM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +#ifndef ISTL_SOLVER_TPSA_HPP +#define ISTL_SOLVER_TPSA_HPP + +#include + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + + +namespace Opm { + +/*! +* \brief Class for setting up ISTL linear solvers for TPSA +* +* \note Most of the code is copied from ISTLSolver class +*/ +template +class ISTLSolverTPSA +{ + using ElementMapper = GetPropType; + using Simulator = GetPropType; + using SparseMatrixAdapter = GetPropType; + using Vector = GetPropType; + + using Matrix = typename SparseMatrixAdapter::IstlMatrix; + + +#if HAVE_MPI + using CommunicationType = Dune::OwnerOverlapCopyCommunication; +#else + using CommunicationType = Dune::Communication; +#endif + +public: + /*! + * \brief Constructor + * + * \param simulator Simulator object + */ + ISTLSolverTPSA(const Simulator& simulator) + : simulator_(simulator) + , solveCount_(0) + , iterations_(0) + , matrix_(nullptr) + , rhs_(nullptr) + { + // Init parameters + parameters_.init(); + + // Initialize linear solver + initialize(); + } + + /*! + * \brief Register runtime/default parameters for linear solver + */ + static void registerParameters() + { + TpsaLinearSolverParameters::registerParameters(); + } + + /*! + * \brief Setup linear solver object based on runtime/default parameters + */ + void initialize() + { + // Setup property tree for FlexibleSolver + prm_ = setupPropertyTreeTPSA(parameters_); + + // Reset comm_ pointer +#if HAVE_MPI + comm_.reset( new CommunicationType( simulator_.vanguard().grid().comm() ) ); +#endif + + // Extract and copy parallel grid information + extractParallelGridInformationToISTL(simulator_.vanguard().grid(), parallelInformation_); +#if HAVE_MPI + if (isParallel()) { + const std::size_t size = simulator_.vanguard().grid().leafGridView().size(0); + detail::copyParValues(parallelInformation_, size, *comm_); + } +#endif + + // Get info on overlapping rows + ElementMapper elemMapper(simulator_.vanguard().gridView(), Dune::mcmgElementLayout()); + std::vector dummyInteriorRows; + detail::findOverlapAndInterior(simulator_.vanguard().grid(), elemMapper, overlapRows_, dummyInteriorRows); + + // Set number of interior cells in FlexibleSolverInfo + flexibleSolver_.interiorCellNum_ = detail::numMatrixRowsToUseInSolver(simulator_.vanguard().grid(), true); + + // Print parameters to PRT/DBG logs. + detail::printLinearSolverParameters(parameters_, prm_, simulator_.gridView().comm()); + } + + /*! + * \brief Prepare matix and rhs vector for linear solve + * + * \param M System matrix + * \param b Right-hand side vector + */ + void initPrepare(const Matrix& M, Vector& b) + { + // Update matrix entries if it has not been initialized yet + const bool firstcall = (matrix_ == nullptr); + if (firstcall) { + // Model will not change the matrix object. Hence simply store a pointer to the original one with a deleter + // that does nothing. + // OBS: We need to be able to scale the linear system, hence const_cast + matrix_ = const_cast(&M); + } + else { + // Pointers should not change; throw if the case + if (&M != matrix_) { + OPM_THROW(std::logic_error, "TPSA: Matrix objects are expected to be reused when reassembling!"); + } + + } + + // Set right-hand side vector + rhs_ = &b; + + // Zero out the overlapping cells in matrix (not in ilu0 case) + std::string type = prm_.template get("preconditioner.type", "paroverilu0"); + std::transform(type.begin(), type.end(), type.begin(), ::tolower); + if (isParallel() && type != "paroverilu0") { + detail::makeOverlapRowsInvalid(getMatrix(), overlapRows_); + } + } + + /*! + * \brief Prepare matix and rhs vector for linear solve + * + * \param M System matrix + * \param b Right-hand side vector + * + * \note No setResidual() or setMatrix() functions in this class. Must be handled here! + */ + void prepare(const Matrix& M, Vector& b) + { + try { + initPrepare(M, b); + + prepareFlexibleSolver(); + } + OPM_CATCH_AND_RETHROW_AS_CRITICAL_ERROR + ("TPSA: Failure likely due to a faulty linear solver JSON specification. " + "Check for errors related to missing nodes."); + } + + /*! + * \brief Prepare matix and rhs vector for linear solve + * + * \param M System matrix + * \param b Right-hand side vector + * + * \note No setResidual() or setMatrix() functions in this class. Must be handled here! + */ + void prepare(const SparseMatrixAdapter& M, Vector& b) + { + prepare(M.istlMatrix(), b); + } + + /*! + * \brief Prepare linear solver + */ + void prepareFlexibleSolver() + { + // Create solver or just update preconditioner + if (!flexibleSolver_.solver_) { + // Dummy weights calculator + // TODO: TPSA specific weights calculation for AMG preconditioner + std::function weightCalculator; + + // Create FlexibleSolver + flexibleSolver_.create(getMatrix(), + isParallel(), + prm_, + /*pressureIndex=*/0, + weightCalculator, + /*forceSerial_=*/false, + comm_.get()); + } + else { + // Update preconditioner + flexibleSolver_.pre_->update(); + } + } + + /*! + * \brief Solve the linear system and store result in the input Vector x + * + * \param x Linear system solution will be stored here + */ + bool solve(Vector& x) + { + // Increase solver count + ++solveCount_; + + // Write system matrix if verbosity level is high + const int verbosity = prm_.get("verbosity", 0); + if (verbosity > 10) { + // simulator_ is only used to get names + Helper::writeSystem(simulator_, + getMatrix(), + *rhs_, + comm_.get()); + } + + // Solve linear system + Dune::InverseOperatorResult result; + assert(flexibleSolver_.solver_); + flexibleSolver_.solver_->apply(x, *rhs_, result); + + // Store no. linear iterations + iterations_ = result.iterations; + + // Return result for convergence check (boolean) + return checkConvergence(result); + } + + /*! + * \brief Reset number of solver calls to zero + */ + void resetSolveCount() { + solveCount_ = 0; + } + + // /// + // Public get functions + // /// + /*! + * \brief Copy right-hand side vector (rhs_) to incomming vector (b) + * + * \param b Vector to copy rhs_ to + */ + void getResidual(Vector& b) const + { + b = *rhs_; + } + + /*! + * \brief Get number of solver calls + */ + int getSolveCount() const + { + return solveCount_; + } + + /*! + * \brief Get number of linear solver iterations + */ + int iterations () const + { + return iterations_; + } + +protected: + /*! + * \brief Check for parallel session + * + * \warning comm_ must be set before using this function + */ + bool isParallel() const + { +#if HAVE_MPI + return comm_->communicator().size() > 1; +#else + return false; +#endif + } + + /*! + * \brief Check for linear solver convergence + * + * \param result Linear solver result container + */ + bool checkConvergence(const Dune::InverseOperatorResult& result) const + { + // Check relaxed linear solver tolerance + if (!result.converged && result.reduction < parameters_.relaxed_linear_solver_reduction_) { + std::string msg = fmt::format("Full linear solver tolerance not achieved. The reduction is {} " + "after {} iterations."); + OpmLog::warning(msg); + return true; + } + + // If we have failed and we don't ignore failures, throw error + if (!parameters_.ignoreConvergenceFailure_ && !result.converged) { + const std::string msg("Convergence failure for linear solver."); + OPM_THROW_NOLOG(NumericalProblem, msg); + } + + // Return convergence bool from linear solver result + return result.converged; + } + + // /// + // Protected get functions + // /// + /*! + * \brief Get reference to system matrix object + */ + Matrix& getMatrix() + { + if (!matrix_) { + OPM_THROW(std::runtime_error, "TPSA: System matrix \"M\" not defined!"); + } + return *matrix_; + } + + /*! + * \brief Get reference to system matrix object + */ + const Matrix& getMatrix() const + { + if (!matrix_) { + OPM_THROW(std::runtime_error, "TPSA: System matrix \"M\" not defined!"); + } + return *matrix_; + } + + const Simulator& simulator_; + std::any parallelInformation_; + int solveCount_; + int iterations_; + + detail::FlexibleSolverInfo flexibleSolver_; + TpsaLinearSolverParameters parameters_; + PropertyTree prm_; + + Matrix* matrix_; + Vector* rhs_; + + std::shared_ptr comm_; + std::vector overlapRows_; +}; + +} // namespace Opm + +#endif \ No newline at end of file diff --git a/opm/simulators/linalg/TPSALinearSolverParameters.cpp b/opm/simulators/linalg/TPSALinearSolverParameters.cpp new file mode 100644 index 00000000000..347fdbb2e9a --- /dev/null +++ b/opm/simulators/linalg/TPSALinearSolverParameters.cpp @@ -0,0 +1,108 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + Copyright 2025 NORCE AS + + This file is part of the Open Porous Media project (OPM). + + OPM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +#include + +#include + + +namespace Opm { + +/*! +* \brief Internalize runtime parameters +* +* \note Overloading FlowLinearSolverParameters::init() to read TPSA specific runtime/default parameters +*/ +void TpsaLinearSolverParameters::init() +{ + // Runtime parameters + linear_solver_reduction_ = Parameters::Get(); + relaxed_linear_solver_reduction_ = Parameters::Get(); + linear_solver_maxiter_ = Parameters::Get(); + linear_solver_restart_ = Parameters::Get(); + linear_solver_verbosity_ = Parameters::Get(); + ilu_relaxation_ = Parameters::Get(); + ilu_fillin_level_ = Parameters::Get(); + newton_use_gmres_ = Parameters::Get(); + ignoreConvergenceFailure_ = Parameters::Get(); + linsolver_ = Parameters::Get(); + linear_solver_print_json_definition_ = Parameters::Get(); + + // Hardcode use of CPU linear solvers (?) + linear_solver_accelerator_ = Parameters::LinearSolverAcceleratorType::CPU; +} + +/*! +* \brief Register TPSA linear solver runtime parameters +*/ +void TpsaLinearSolverParameters::registerParameters() +{ + Parameters::Register + ("Minimum residual reduction in TPSA linear solver for convergenc"); + Parameters::Register + ("A relaxed version of --tpsa-linear-solver-reduction (use with care!)"); + Parameters::Register + ("Maximum TPSA linear iterations"); + Parameters::Register + ("Number of iterations before restarting GMRES if --tpsa-use-gmres=true"); + Parameters::Register + ("Level of verbosity in TPSA linear solver: 0 = off, 2 = all"); + Parameters::Register + ("Relaxation factor for TPSA linear solver ILU preconditioner"); + Parameters::Register + ("Fill-in level of TPSA linear solver ILU preconditioner"); + Parameters::Register + ("Use GMRES linear solver. If false, BiCGStab is used."); + Parameters::Register + ("Continue simulation even if TPSA linear solver did not converge"); + Parameters::Register + ("Configuration for linear solver. Valid preset options are: ilu0, dilu, amg or umfpack. " + "Alternatively, you can request a configuration to be read from a JSON file by giving the filename here, " + "ending with '.json.'"); + Parameters::Register + ("Print JSON formatted configuration of the TPSA linear solver. Can be used to make configuration JSON file " + "for --tpsa-linear-solver"); +} + +/*! +* \brief Reset TPSA linear solver parameters +* +* \warning Should be the same as the corresponding Parameters defaults! +*/ +void TpsaLinearSolverParameters::reset() +{ + linear_solver_reduction_ = 1e-3; + relaxed_linear_solver_reduction_ = 1e-3; + linear_solver_maxiter_ = 200; + linear_solver_restart_ = 40; + linear_solver_verbosity_ = 0; + ilu_relaxation_ = 9; + ilu_fillin_level_ = 0; + newton_use_gmres_ = false; + ignoreConvergenceFailure_ = false; + linsolver_ = "ilu0"; + linear_solver_print_json_definition_ = false; +} + +} // namespace Opm \ No newline at end of file diff --git a/opm/simulators/linalg/TPSALinearSolverParameters.hpp b/opm/simulators/linalg/TPSALinearSolverParameters.hpp new file mode 100644 index 00000000000..be5e7ff7a96 --- /dev/null +++ b/opm/simulators/linalg/TPSALinearSolverParameters.hpp @@ -0,0 +1,61 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + Copyright 2025 NORCE AS + + This file is part of the Open Porous Media project (OPM). + + OPM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +#ifndef TPSA_LINEAR_SOLVER_PARAMETERS_HPP +#define TPSA_LINEAR_SOLVER_PARAMETERS_HPP + +#include + +#include + + +// Default runtime parameters +namespace Opm::Parameters { + +struct TpsaLinearSolverReduction { static constexpr double value = 1e-3; }; +struct TpsaRelaxedLinearSolverReduction { static constexpr double value = 1e-3; }; +struct TpsaLinearSolverMaxIter { static constexpr int value = 200; }; +struct TpsaLinearSolverRestart { static constexpr int value = 40; }; +struct TpsaLinearSolverVerbosity { static constexpr int value = 0; }; +struct TpsaIluRelaxation { static constexpr double value = 0.9; }; +struct TpsaIluFillinLevel { static constexpr int value = 0; }; +struct TpsaUseGmres { static constexpr bool value = false; }; +struct TpsaLinearSolverIgnoreConvergenceFailure { static constexpr bool value = false; }; +struct TpsaLinearSolver { static constexpr auto value = "ilu0"; }; +struct TpsaLinearSolverPrintJsonDefinition { static constexpr auto value = false; }; + +} // namespace Opm::Parameters + +namespace Opm { + +struct TpsaLinearSolverParameters : public FlowLinearSolverParameters +{ + void init(); + static void registerParameters(); + void reset(); +}; + +} // namespace Opm + +#endif \ No newline at end of file diff --git a/opm/simulators/linalg/setupPropertyTreeTPSA.cpp b/opm/simulators/linalg/setupPropertyTreeTPSA.cpp new file mode 100644 index 00000000000..c9201b8d358 --- /dev/null +++ b/opm/simulators/linalg/setupPropertyTreeTPSA.cpp @@ -0,0 +1,116 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + Copyright 2025 NORCE AS + + This file is part of the Open Porous Media project (OPM). + + OPM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +#include "config.h" + +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include + + +namespace Opm { + +/*! +* \brief Setup linear solver property tree based on runtime/default parameters +* +* \param p Runtime/default parameters +* +* \note Same as Opm::setupPropertyTree() for Flow, but limit remove Flow specific linear solver variants like cpr_***. +* Moreover, TPSA specific presets should be done here! +*/ +PropertyTree setupPropertyTreeTPSA(TpsaLinearSolverParameters p) +{ + // Get linear solver + std::string conf = p.linsolver_; + + // Get configuration from JSON file + if (conf.size() > 5 && conf.substr(conf.size() - 5, 5) == ".json") { +#if BOOST_VERSION / 100 % 1000 > 48 + if ( !std::filesystem::exists(conf) ) { + std::string msg = fmt::format("TPSA: JSON file {} in --tpsa-linear-solver does not exist!", conf); + OpmLog::error(msg); + OPM_THROW(std::invalid_argument, msg); + } + try { + return PropertyTree(conf); + } + catch (...) { + std::string msg = fmt::format("TPSA: Failed reading linear solver configuration from JSON file {}", conf); + OpmLog::error(msg); + OPM_THROW(std::invalid_argument, msg); + } +#else + std::string msg = fmt::format("TPSA: --tpsa-linear-solver with JSON file (={}) not supported with boost " + "version <= 1.48!", conf); + OpmLog::error(msg); + OPM_THROW(std::invalid_argument, msg); +#endif + } + + // We use lower case as the internal canonical representation of solver names + std::transform(conf.begin(), conf.end(), conf.begin(), ::tolower); + + // Standard AMG + if (conf == "amg") { + return setupAMG(conf, p); + } + + // ILU setups + if (conf == "ilu0") { + return setupILU(conf, p); + } + if (conf == "dilu") { + return setupDILU(conf, p); + } + + // UMFPACK direct solver + if (conf == "umfpack") { + return setupUMFPack(conf, p); + } + + // At this point, the only separate ISAI implementation is with the OpenCL code, and + // it will check this argument to see if it should be using ISAI. The parameter tree + // will be ignored, so this is just a dummy configuration to avoid the throw below. + // If we are using CPU dune-istl solvers, this will just make "isai" an alias of "ilu". + if (conf == "isai") { + return setupILU(conf, p); + } + + // No valid configuration option found. + std::string msg = fmt::format("No valid settings found for --tpsa-linear-solver={}! " + "Valid preset options are: ilu0, dilu, amg, or umfpack.", conf); + OpmLog::error(msg); + OPM_THROW(std::invalid_argument, msg); +} + +} // namespace Opm \ No newline at end of file diff --git a/opm/simulators/linalg/setupPropertyTreeTPSA.hpp b/opm/simulators/linalg/setupPropertyTreeTPSA.hpp new file mode 100644 index 00000000000..3c7c3d1eae0 --- /dev/null +++ b/opm/simulators/linalg/setupPropertyTreeTPSA.hpp @@ -0,0 +1,39 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + Copyright 2025 NORCE AS + + This file is part of the Open Porous Media project (OPM). + + OPM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +#ifndef SETUP_PROPERTY_TREE_TSA_HPP +#define SETUP_PROPERTY_TREE_TSA_HPP + +#include + + +namespace Opm { + +struct TpsaLinearSolverParameters; + +PropertyTree setupPropertyTreeTPSA(TpsaLinearSolverParameters p); + +} // namespace Opm + +#endif \ No newline at end of file diff --git a/tests/test_face_properties.cpp b/tests/test_face_properties.cpp new file mode 100644 index 00000000000..84e48f53ce4 --- /dev/null +++ b/tests/test_face_properties.cpp @@ -0,0 +1,192 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + Copyright 2025 NORCE AS + + This file is part of the Open Porous Media project (OPM). + + OPM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +#include + +#define BOOST_TEST_MODULE TestFacePropertiesTPSA +#define BOOST_TEST_NO_MAIN + +#include + +#include +#include + +#include +#include +#include +#include + +#include + +#include + +#include +#include + +#if HAVE_MPI +struct MPIError +{ + MPIError(std::string s, int e) : errorstring(std::move(s)), errorcode(e){} + std::string errorstring; + int errorcode; +}; + +void MPI_err_handler(MPI_Comm*, int* err_code, ...) +{ + std::vector err_string(MPI_MAX_ERROR_STRING); + int err_length; + MPI_Error_string(*err_code, err_string.data(), &err_length); + std::string s(err_string.data(), err_length); + std::cerr << "An MPI Error ocurred:" << std::endl << s << std::endl; + throw MPIError(s, *err_code); +} +#endif + +bool init_unit_test_func() +{ + return true; +} + +static Opm::Deck createDeck() +{ + // Deck to test + std::string deck_string = R"( + RUNSPEC + DIMENS + 2 2 2 / + + GRID + DX + 8*50.0 / + DY + 8*50.0 / + DZ + 4*30.0 4*50.0 / + TOPS + 3*1000.0 1*1020 / + + PORO + 8*0.3 / + SMODULUS + 8*1.0 / + + END + )"; + Opm::Parser parser; + return parser.parseString(deck_string); +} + +BOOST_AUTO_TEST_CASE(SimpleGridWithNNC) +{ + // using declarations + using Grid = Dune::CpGrid; + using GridView = Grid::LeafGridView; + using ElementMapper = Dune::MultipleCodimMultipleGeomTypeMapper; + using CartesianIndexMapper = Dune::CartesianIndexMapper; + using FacePropertiesTPSA = Opm::FacePropertiesTPSA; + using DimVector = Dune::FieldVector; + + // Test simple 2x2x2 grid with one column shifted downwards + Opm::Deck deck = createDeck(); + Opm::EclipseState eclState(deck); + + Grid grid; + grid.processEclipseFormat(&eclState.getInputGrid(), &eclState, false, false, false); + const auto& gridView = grid.leafGridView(); + + CartesianIndexMapper cartMapper = Dune::CartesianIndexMapper(grid); + auto centroids = [&eclState, &cartMapper](int index) + { return eclState.getInputGrid().getCellCenter(cartMapper.cartesianIndex(index)); }; + + // Init. FacePropertiesTPSA and calculate all properties + FacePropertiesTPSA faceProps(eclState, + gridView, + cartMapper, + grid, + centroids); + faceProps.update(); + + // Check face properties + // Natural neighbor checks between cells 0 and 4 + const unsigned elem1 = 0; + const unsigned elem2 = 4; + const double normDist = 40.0; + const double weightAvg_04 = 0.375; + const double weightAvg_40 = 0.625; // = 1 - weightAvg_04 + const double weightProd = 3.75e-16; + const DimVector normal_04 = {0.0, 0.0, 1.0}; + const DimVector normal_40 = {0.0, 0.0, -1.0}; // = -1 * normal_04 + BOOST_CHECK_CLOSE(faceProps.weightAverage(elem1, elem2), weightAvg_04, 1.0e-8); + BOOST_CHECK_EQUAL(faceProps.weightAverage(elem2, elem1), weightAvg_40); + BOOST_CHECK_CLOSE(faceProps.weightProduct(elem1, elem2), weightProd, 1.0e-8); + BOOST_CHECK_EQUAL(faceProps.normalDistance(elem1, elem2), normDist); + BOOST_CHECK_EQUAL(faceProps.normalDistance(elem2, elem1), normDist); + BOOST_CHECK_EQUAL(faceProps.cellFaceNormal(elem1, elem2), normal_04); + BOOST_CHECK_EQUAL(faceProps.cellFaceNormal(elem2, elem1), normal_40); + + // NNC checks between cell 3 and 5 + const unsigned elemNNC1 = 3; + const unsigned elemNNC2 = 5; + const double normDistNNC = 50.0; + const double weightAvgNNC = 0.5; + const double weightProdNNC = 6.25e-16; + const DimVector normalNNC_35 = {0.0, -1.0, 0.0}; + const DimVector normalNNC_53 = {0.0, 1.0, 0.0}; + BOOST_CHECK_EQUAL(faceProps.weightAverage(elemNNC1, elemNNC2), weightAvgNNC); + BOOST_CHECK_EQUAL(faceProps.weightAverage(elemNNC2, elemNNC1), weightAvgNNC); + BOOST_CHECK_CLOSE(faceProps.weightProduct(elemNNC1, elemNNC2), weightProdNNC, 1.0e-8); + BOOST_CHECK_CLOSE(faceProps.weightProduct(elemNNC2, elemNNC1), weightProdNNC, 1.0e-8); + BOOST_CHECK_EQUAL(faceProps.normalDistance(elemNNC1, elemNNC2), normDistNNC); + BOOST_CHECK_EQUAL(faceProps.normalDistance(elemNNC2, elemNNC1), normDistNNC); + BOOST_CHECK_EQUAL(faceProps.cellFaceNormal(elemNNC1, elemNNC2), normalNNC_35); + BOOST_CHECK_EQUAL(faceProps.cellFaceNormal(elemNNC2, elemNNC1), normalNNC_53); + + // Boundary interfaces in cell 7 + const unsigned elemBnd = 7; + const double normDistBnd = 25.0; + const double weightAvgBnd = 1.0; + const double weightProdBnd = 0.0; + const std::vector normalBnd = { {-1.0, 0.0, 0.0}, {1.0, 0.0, 0.0}, // x-dir + {0.0, -1.0, 0.0}, {0.0, 1.0, 0.0}, // y-dir + {0.0, 0.0, 1.0} }; // z-dir + for (std::size_t bndIdx = 0; bndIdx < 5; ++bndIdx) { + BOOST_CHECK_EQUAL(faceProps.weightAverageBoundary(elemBnd, bndIdx), weightAvgBnd); + BOOST_CHECK_CLOSE(faceProps.weightProductBoundary(elemBnd, bndIdx), weightProdBnd, 1.0e-8); + BOOST_CHECK_EQUAL(faceProps.normalDistanceBoundary(elemBnd, bndIdx), normDistBnd); + BOOST_CHECK_EQUAL(faceProps.cellFaceNormalBoundary(elemBnd, bndIdx), normalBnd[bndIdx]); + } +} + +int main(int argc, char** argv) +{ + Dune::MPIHelper::instance(argc, argv); +#if HAVE_MPI + // register a throwing error handler to allow for + // debugging with "catch throw" in gdb + MPI_Errhandler handler; + MPI_Comm_create_errhandler(MPI_err_handler, &handler); + MPI_Comm_set_errhandler(MPI_COMM_WORLD, handler); +#endif + return boost::unit_test::unit_test_main(&init_unit_test_func, argc, argv); +} \ No newline at end of file From c605586001e59edd5164c2042079eef36afcb8ed Mon Sep 17 00:00:00 2001 From: Svenn Tveit Date: Tue, 2 Dec 2025 19:44:17 +0100 Subject: [PATCH 02/19] Fix for simulators using properties from blackoilmodel.hh --- opm/models/blackoil/blackoilmodel.hh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/opm/models/blackoil/blackoilmodel.hh b/opm/models/blackoil/blackoilmodel.hh index 4901709738a..6fa3e5f459f 100644 --- a/opm/models/blackoil/blackoilmodel.hh +++ b/opm/models/blackoil/blackoilmodel.hh @@ -221,6 +221,10 @@ template struct EnableConvectiveMixing { static constexpr bool value = false; }; +template +struct EnableMech +{ static constexpr bool value = false; }; + //! by default, scale the energy equation by the inverse of the energy required to heat //! up one kg of water by 30 Kelvin. If we conserve surface volumes, this must be divided //! by the weight of one cubic meter of water. This is required to make the "dumb" linear From 7f635ce435eb1f277d438e23ac235bc607938eb3 Mon Sep 17 00:00:00 2001 From: Svenn Tveit Date: Wed, 3 Dec 2025 10:21:53 +0100 Subject: [PATCH 03/19] Fix unused parameter warnings --- opm/models/discretization/common/tpsalinearizer.hpp | 1 - opm/models/tpsa/tpsanewtonmethod.hpp | 2 +- opm/simulators/flow/FlowProblemTPSA.hpp | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/opm/models/discretization/common/tpsalinearizer.hpp b/opm/models/discretization/common/tpsalinearizer.hpp index 4dac185ac0d..110f7cced23 100644 --- a/opm/models/discretization/common/tpsalinearizer.hpp +++ b/opm/models/discretization/common/tpsalinearizer.hpp @@ -418,7 +418,6 @@ class TpsaLinearizer const auto& geoMechModel = geoMechModel_(); auto& problem = problem_(); const unsigned int numCells = domain.cells.size(); - const bool on_full_domain = (numCells == flowModel.numTotalDof()); #ifdef _OPENMP #pragma omp parallel for diff --git a/opm/models/tpsa/tpsanewtonmethod.hpp b/opm/models/tpsa/tpsanewtonmethod.hpp index efb1f3cd647..766dffb4cdf 100644 --- a/opm/models/tpsa/tpsanewtonmethod.hpp +++ b/opm/models/tpsa/tpsanewtonmethod.hpp @@ -632,7 +632,7 @@ class TpsaNewtonMethod * \param nextSolution The solution after the current Newton iteration * \param currentSolution The solution at the beginning of the current Newton iteration */ - void endIteration_(const SolutionVector& nextSolution, + void endIteration_(const SolutionVector& /*nextSolution*/, const SolutionVector& /*currentSolution*/) { ++numIterations_; diff --git a/opm/simulators/flow/FlowProblemTPSA.hpp b/opm/simulators/flow/FlowProblemTPSA.hpp index 12fb33c5b48..d75ae61ff14 100644 --- a/opm/simulators/flow/FlowProblemTPSA.hpp +++ b/opm/simulators/flow/FlowProblemTPSA.hpp @@ -305,7 +305,7 @@ class FlowProblemTPSA : public FlowProblemBlackoil const auto biot = this->biotCoeff(globalSpaceIdx); const auto lameParam = this->lame(globalSpaceIdx); - const auto& iq = this->model().intensiveQuantities(globalSpaceIdx, 0); + const auto& iq = this->model().intensiveQuantities(globalSpaceIdx, timeIdx); const auto& fs = iq.fluidState(); const auto pres = decay(fs.pressure(this->refPressurePhaseIdx_())); const auto initPres = this->initialFluidState(globalSpaceIdx).pressure(this->refPressurePhaseIdx_()); From bf02ada1f5193d160eb4b7e5a2a31a5fef631f63 Mon Sep 17 00:00:00 2001 From: Svenn Tveit Date: Wed, 3 Dec 2025 10:59:46 +0100 Subject: [PATCH 04/19] Fix EOL at EOF --- flow/flow_blackoil_tpsa.cpp | 2 +- flow/flow_blackoil_tpsa.hpp | 2 +- flow/flow_blackoil_tpsa_main.cpp | 2 +- flow/flow_gaswater_dissolution_tpsa.cpp | 2 +- flow/flow_gaswater_dissolution_tpsa.hpp | 2 +- flow/flow_gaswater_dissolution_tpsa_main.cpp | 2 +- flow/flow_onephase_tpsa.cpp | 2 +- flow/flow_onephase_tpsa.hpp | 2 +- opm/models/discretization/common/tpsalinearizer.hpp | 2 +- opm/models/io/vtktpsamodule.hpp | 2 +- opm/models/io/vtktpsaparams.cpp | 2 +- opm/models/io/vtktpsaparams.hpp | 2 +- opm/models/tpsa/elasticityindices.hpp | 2 +- opm/models/tpsa/elasticitylocalresidualtpsa.hpp | 2 +- opm/models/tpsa/elasticityprimaryvariables.hpp | 2 +- opm/models/tpsa/tpsabaseproperties.hpp | 2 +- opm/models/tpsa/tpsamodel.hpp | 2 +- opm/models/tpsa/tpsanewtonconvergencewriter.hpp | 2 +- opm/models/tpsa/tpsanewtonmethod.hpp | 2 +- opm/simulators/flow/BlackoilModelTPSA.hpp | 2 +- opm/simulators/flow/FacePropertiesTPSA.cpp | 2 +- opm/simulators/flow/FacePropertiesTPSA.hpp | 2 +- opm/simulators/flow/FacePropertiesTPSA_impl.hpp | 2 +- opm/simulators/flow/FlowProblemTPSA.hpp | 2 +- opm/simulators/flow/TTagFlowProblemTPSA.hpp | 2 +- opm/simulators/linalg/ISTLSolverTPSA.hpp | 2 +- opm/simulators/linalg/TPSALinearSolverParameters.cpp | 2 +- opm/simulators/linalg/TPSALinearSolverParameters.hpp | 2 +- opm/simulators/linalg/setupPropertyTreeTPSA.cpp | 2 +- opm/simulators/linalg/setupPropertyTreeTPSA.hpp | 2 +- tests/test_face_properties.cpp | 2 +- 31 files changed, 31 insertions(+), 31 deletions(-) diff --git a/flow/flow_blackoil_tpsa.cpp b/flow/flow_blackoil_tpsa.cpp index ac8ca6a8aa6..9f3a2015be3 100644 --- a/flow/flow_blackoil_tpsa.cpp +++ b/flow/flow_blackoil_tpsa.cpp @@ -101,4 +101,4 @@ int flowBlackoilTpsaMainStandalone(int argc, char** argv) return ret; } -} // namespace Opm \ No newline at end of file +} // namespace Opm diff --git a/flow/flow_blackoil_tpsa.hpp b/flow/flow_blackoil_tpsa.hpp index 4d4e1c94a91..61984013627 100644 --- a/flow/flow_blackoil_tpsa.hpp +++ b/flow/flow_blackoil_tpsa.hpp @@ -32,4 +32,4 @@ int flowBlackoilTpsaMainStandalone(int argc, char** argv); } // namespace Opm -#endif \ No newline at end of file +#endif diff --git a/flow/flow_blackoil_tpsa_main.cpp b/flow/flow_blackoil_tpsa_main.cpp index c8847b97cc8..68006036783 100644 --- a/flow/flow_blackoil_tpsa_main.cpp +++ b/flow/flow_blackoil_tpsa_main.cpp @@ -25,4 +25,4 @@ int main(int argc, char** argv) { return Opm::flowBlackoilTpsaMainStandalone(argc, argv); -} \ No newline at end of file +} diff --git a/flow/flow_gaswater_dissolution_tpsa.cpp b/flow/flow_gaswater_dissolution_tpsa.cpp index 2c41cff241c..b5137bb93c5 100644 --- a/flow/flow_gaswater_dissolution_tpsa.cpp +++ b/flow/flow_gaswater_dissolution_tpsa.cpp @@ -133,4 +133,4 @@ int flowGasWaterDissolutionTpsaMainStandalone(int argc, char** argv) return ret; } -} // namespace Opm \ No newline at end of file +} // namespace Opm diff --git a/flow/flow_gaswater_dissolution_tpsa.hpp b/flow/flow_gaswater_dissolution_tpsa.hpp index bf75cea854d..8db759716a3 100644 --- a/flow/flow_gaswater_dissolution_tpsa.hpp +++ b/flow/flow_gaswater_dissolution_tpsa.hpp @@ -31,4 +31,4 @@ int flowGasWaterDissolutionTpsaMainStandalone(int argc, char** argv); } // namespace Opm -#endif \ No newline at end of file +#endif diff --git a/flow/flow_gaswater_dissolution_tpsa_main.cpp b/flow/flow_gaswater_dissolution_tpsa_main.cpp index 352345c39ba..af7663d1d5b 100644 --- a/flow/flow_gaswater_dissolution_tpsa_main.cpp +++ b/flow/flow_gaswater_dissolution_tpsa_main.cpp @@ -25,4 +25,4 @@ int main(int argc, char** argv) { return Opm::flowGasWaterDissolutionTpsaMainStandalone(argc, argv); -} \ No newline at end of file +} diff --git a/flow/flow_onephase_tpsa.cpp b/flow/flow_onephase_tpsa.cpp index 1ecff7dfffc..02c52b9373e 100644 --- a/flow/flow_onephase_tpsa.cpp +++ b/flow/flow_onephase_tpsa.cpp @@ -124,4 +124,4 @@ int flowWaterOnlyTpsaMainStandalone(int argc, char** argv) return ret; } -} \ No newline at end of file +} // namespace Opm diff --git a/flow/flow_onephase_tpsa.hpp b/flow/flow_onephase_tpsa.hpp index 8673c836525..31876f736a1 100644 --- a/flow/flow_onephase_tpsa.hpp +++ b/flow/flow_onephase_tpsa.hpp @@ -31,4 +31,4 @@ int flowWaterOnlyTpsaMainStandalone(int argc, char** argv); } // namespace Opm -#endif \ No newline at end of file +#endif diff --git a/opm/models/discretization/common/tpsalinearizer.hpp b/opm/models/discretization/common/tpsalinearizer.hpp index 110f7cced23..f823c1b8827 100644 --- a/opm/models/discretization/common/tpsalinearizer.hpp +++ b/opm/models/discretization/common/tpsalinearizer.hpp @@ -688,4 +688,4 @@ class TpsaLinearizer } // namespace Opm -#endif \ No newline at end of file +#endif diff --git a/opm/models/io/vtktpsamodule.hpp b/opm/models/io/vtktpsamodule.hpp index 167db00eb31..10046794744 100644 --- a/opm/models/io/vtktpsamodule.hpp +++ b/opm/models/io/vtktpsamodule.hpp @@ -195,4 +195,4 @@ class VtkTpsaModule : public BaseOutputModule } // namespace Opm -#endif \ No newline at end of file +#endif diff --git a/opm/models/io/vtktpsaparams.cpp b/opm/models/io/vtktpsaparams.cpp index 6acfba5d937..fe0f60fbf8b 100644 --- a/opm/models/io/vtktpsaparams.cpp +++ b/opm/models/io/vtktpsaparams.cpp @@ -48,4 +48,4 @@ void VtkTpsaParams::read() solidPressureOutput_ = Parameters::Get(); } -} // namespace Opm \ No newline at end of file +} // namespace Opm diff --git a/opm/models/io/vtktpsaparams.hpp b/opm/models/io/vtktpsaparams.hpp index 3b69b9f3b32..344dadfd860 100644 --- a/opm/models/io/vtktpsaparams.hpp +++ b/opm/models/io/vtktpsaparams.hpp @@ -52,4 +52,4 @@ struct VtkTpsaParams } // namespace Opm -#endif \ No newline at end of file +#endif diff --git a/opm/models/tpsa/elasticityindices.hpp b/opm/models/tpsa/elasticityindices.hpp index c774e876c45..8ced845c51a 100644 --- a/opm/models/tpsa/elasticityindices.hpp +++ b/opm/models/tpsa/elasticityindices.hpp @@ -61,4 +61,4 @@ struct ElasticityIndices } // namespace Opm -#endif \ No newline at end of file +#endif diff --git a/opm/models/tpsa/elasticitylocalresidualtpsa.hpp b/opm/models/tpsa/elasticitylocalresidualtpsa.hpp index c788d48b482..1711e1725b1 100644 --- a/opm/models/tpsa/elasticitylocalresidualtpsa.hpp +++ b/opm/models/tpsa/elasticitylocalresidualtpsa.hpp @@ -392,4 +392,4 @@ class ElasticityLocalResidual } // namespace Opm -#endif \ No newline at end of file +#endif diff --git a/opm/models/tpsa/elasticityprimaryvariables.hpp b/opm/models/tpsa/elasticityprimaryvariables.hpp index 385e711638d..ade27587d61 100644 --- a/opm/models/tpsa/elasticityprimaryvariables.hpp +++ b/opm/models/tpsa/elasticityprimaryvariables.hpp @@ -139,4 +139,4 @@ class ElasticityPrimaryVariables } // namespace Opm -#endif \ No newline at end of file +#endif diff --git a/opm/models/tpsa/tpsabaseproperties.hpp b/opm/models/tpsa/tpsabaseproperties.hpp index ba7169eb6cd..af81450df4d 100644 --- a/opm/models/tpsa/tpsabaseproperties.hpp +++ b/opm/models/tpsa/tpsabaseproperties.hpp @@ -83,4 +83,4 @@ struct LinearSolverBackendTPSA { using type = UndefinedProperty; }; } // namespace Opm::Properties -#endif \ No newline at end of file +#endif diff --git a/opm/models/tpsa/tpsamodel.hpp b/opm/models/tpsa/tpsamodel.hpp index 6afdafe4043..56acff7856b 100644 --- a/opm/models/tpsa/tpsamodel.hpp +++ b/opm/models/tpsa/tpsamodel.hpp @@ -515,4 +515,4 @@ class TpsaModel } // namespace Opm -#endif \ No newline at end of file +#endif diff --git a/opm/models/tpsa/tpsanewtonconvergencewriter.hpp b/opm/models/tpsa/tpsanewtonconvergencewriter.hpp index 4dd1af26e4f..23d46d3914f 100644 --- a/opm/models/tpsa/tpsanewtonconvergencewriter.hpp +++ b/opm/models/tpsa/tpsanewtonconvergencewriter.hpp @@ -85,4 +85,4 @@ class TpsaNewtonConvergenceWriter } // namespace Opm -#endif \ No newline at end of file +#endif diff --git a/opm/models/tpsa/tpsanewtonmethod.hpp b/opm/models/tpsa/tpsanewtonmethod.hpp index 766dffb4cdf..c976d06880a 100644 --- a/opm/models/tpsa/tpsanewtonmethod.hpp +++ b/opm/models/tpsa/tpsanewtonmethod.hpp @@ -728,4 +728,4 @@ class TpsaNewtonMethod } // namespace Opm -#endif \ No newline at end of file +#endif diff --git a/opm/simulators/flow/BlackoilModelTPSA.hpp b/opm/simulators/flow/BlackoilModelTPSA.hpp index 0a0b489c208..d3477e790dd 100644 --- a/opm/simulators/flow/BlackoilModelTPSA.hpp +++ b/opm/simulators/flow/BlackoilModelTPSA.hpp @@ -217,4 +217,4 @@ class BlackoilModelTPSA : public BlackoilModel } // namespace Opm -#endif \ No newline at end of file +#endif diff --git a/opm/simulators/flow/FacePropertiesTPSA.cpp b/opm/simulators/flow/FacePropertiesTPSA.cpp index 65ccc0ffd93..d2356cf5a7c 100644 --- a/opm/simulators/flow/FacePropertiesTPSA.cpp +++ b/opm/simulators/flow/FacePropertiesTPSA.cpp @@ -73,4 +73,4 @@ template class FacePropertiesTPSA } // namespace Opm -#endif \ No newline at end of file +#endif diff --git a/opm/simulators/flow/TTagFlowProblemTPSA.hpp b/opm/simulators/flow/TTagFlowProblemTPSA.hpp index 055db0ae481..77086e583c4 100644 --- a/opm/simulators/flow/TTagFlowProblemTPSA.hpp +++ b/opm/simulators/flow/TTagFlowProblemTPSA.hpp @@ -157,4 +157,4 @@ struct LinearSolverBackendTPSA } // namespace Opm::Properties -#endif \ No newline at end of file +#endif diff --git a/opm/simulators/linalg/ISTLSolverTPSA.hpp b/opm/simulators/linalg/ISTLSolverTPSA.hpp index aca2229bc90..72c40f999e2 100644 --- a/opm/simulators/linalg/ISTLSolverTPSA.hpp +++ b/opm/simulators/linalg/ISTLSolverTPSA.hpp @@ -385,4 +385,4 @@ class ISTLSolverTPSA } // namespace Opm -#endif \ No newline at end of file +#endif diff --git a/opm/simulators/linalg/TPSALinearSolverParameters.cpp b/opm/simulators/linalg/TPSALinearSolverParameters.cpp index 347fdbb2e9a..e4288e48b6b 100644 --- a/opm/simulators/linalg/TPSALinearSolverParameters.cpp +++ b/opm/simulators/linalg/TPSALinearSolverParameters.cpp @@ -105,4 +105,4 @@ void TpsaLinearSolverParameters::reset() linear_solver_print_json_definition_ = false; } -} // namespace Opm \ No newline at end of file +} // namespace Opm diff --git a/opm/simulators/linalg/TPSALinearSolverParameters.hpp b/opm/simulators/linalg/TPSALinearSolverParameters.hpp index be5e7ff7a96..360661b8c6c 100644 --- a/opm/simulators/linalg/TPSALinearSolverParameters.hpp +++ b/opm/simulators/linalg/TPSALinearSolverParameters.hpp @@ -58,4 +58,4 @@ struct TpsaLinearSolverParameters : public FlowLinearSolverParameters } // namespace Opm -#endif \ No newline at end of file +#endif diff --git a/opm/simulators/linalg/setupPropertyTreeTPSA.cpp b/opm/simulators/linalg/setupPropertyTreeTPSA.cpp index c9201b8d358..92e5ae29e1f 100644 --- a/opm/simulators/linalg/setupPropertyTreeTPSA.cpp +++ b/opm/simulators/linalg/setupPropertyTreeTPSA.cpp @@ -113,4 +113,4 @@ PropertyTree setupPropertyTreeTPSA(TpsaLinearSolverParameters p) OPM_THROW(std::invalid_argument, msg); } -} // namespace Opm \ No newline at end of file +} // namespace Opm diff --git a/opm/simulators/linalg/setupPropertyTreeTPSA.hpp b/opm/simulators/linalg/setupPropertyTreeTPSA.hpp index 3c7c3d1eae0..c470d6e9e9c 100644 --- a/opm/simulators/linalg/setupPropertyTreeTPSA.hpp +++ b/opm/simulators/linalg/setupPropertyTreeTPSA.hpp @@ -36,4 +36,4 @@ PropertyTree setupPropertyTreeTPSA(TpsaLinearSolverParameters p); } // namespace Opm -#endif \ No newline at end of file +#endif diff --git a/tests/test_face_properties.cpp b/tests/test_face_properties.cpp index 84e48f53ce4..48b75291c28 100644 --- a/tests/test_face_properties.cpp +++ b/tests/test_face_properties.cpp @@ -189,4 +189,4 @@ int main(int argc, char** argv) MPI_Comm_set_errhandler(MPI_COMM_WORLD, handler); #endif return boost::unit_test::unit_test_main(&init_unit_test_func, argc, argv); -} \ No newline at end of file +} From 1d1da043d0d38e3f445c4adc9b5061231d3fcd34 Mon Sep 17 00:00:00 2001 From: Svenn Tveit Date: Wed, 3 Dec 2025 13:53:17 +0100 Subject: [PATCH 05/19] Fix sign compare warnings --- opm/simulators/flow/BlackoilModelTPSA.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opm/simulators/flow/BlackoilModelTPSA.hpp b/opm/simulators/flow/BlackoilModelTPSA.hpp index d3477e790dd..b0637b28f26 100644 --- a/opm/simulators/flow/BlackoilModelTPSA.hpp +++ b/opm/simulators/flow/BlackoilModelTPSA.hpp @@ -212,7 +212,7 @@ class BlackoilModelTPSA : public BlackoilModel } private: - unsigned seqIter_{0}; + int seqIter_{0}; }; // class BlackoilModelTPSA } // namespace Opm From 3f3b051d2fc719bcbb8d640bc7eba5247a8703b8 Mon Sep 17 00:00:00 2001 From: Svenn Tveit Date: Wed, 3 Dec 2025 17:40:58 +0100 Subject: [PATCH 06/19] Tests for TPSA primary variables --- CMakeLists_files.cmake | 1 + .../tpsa/elasticityprimaryvariables.hpp | 1 + tests/test_tpsa_primaryvariables.cpp | 81 +++++++++++++++++++ 3 files changed, 83 insertions(+) create mode 100644 tests/test_tpsa_primaryvariables.cpp diff --git a/CMakeLists_files.cmake b/CMakeLists_files.cmake index 07ab6256301..65e6bace9c2 100644 --- a/CMakeLists_files.cmake +++ b/CMakeLists_files.cmake @@ -511,6 +511,7 @@ list (APPEND TEST_SOURCE_FILES tests/test_rstconv.cpp tests/test_stoppedwells.cpp tests/test_timer.cpp + tests/test_tpsa_primaryvariables.cpp tests/test_vfpproperties.cpp tests/test_wellmodel.cpp tests/test_wellprodindexcalculator.cpp diff --git a/opm/models/tpsa/elasticityprimaryvariables.hpp b/opm/models/tpsa/elasticityprimaryvariables.hpp index ade27587d61..97a43f84a02 100644 --- a/opm/models/tpsa/elasticityprimaryvariables.hpp +++ b/opm/models/tpsa/elasticityprimaryvariables.hpp @@ -32,6 +32,7 @@ #include #include +#include #include diff --git a/tests/test_tpsa_primaryvariables.cpp b/tests/test_tpsa_primaryvariables.cpp new file mode 100644 index 00000000000..9df03839569 --- /dev/null +++ b/tests/test_tpsa_primaryvariables.cpp @@ -0,0 +1,81 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + Copyright 2025, NORCE AS + + This file is part of the Open Porous Media project (OPM). + + OPM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . +*/ +#include + +#define BOOST_TEST_MODULE TpsaPrimaryVariablesTests + +#include + +#include + +#include +#include + +#include +#include + +#include + + +namespace Opm::Properties::TTag { + struct TpsaTestTypeTag { + using InheritsFrom = std::tuple; + }; +} + +BOOST_AUTO_TEST_CASE(ElasticityPrimVarTest) { + using TypeTag = Opm::Properties::TTag::TpsaTestTypeTag; + using Scalar = Opm::GetPropType; + + // Check straightforward assignment + Opm::ElasticityPrimaryVariables priVars; + for (std::size_t i = 0; i < priVars.size(); ++i) { + priVars[i] = static_cast(i); + } + BOOST_CHECK_EQUAL(priVars.size(), 7U); + for (std::size_t i = 0; i < priVars.size(); ++i) { + const auto eval_var = priVars.makeEvaluation(i, 0); + BOOST_CHECK_CLOSE(eval_var.value(), static_cast(i), 1.0e-6); + BOOST_CHECK_CLOSE(eval_var.derivative(i), 1.0, 1.0e-6); + } + + // Assign from material state container + Opm::MaterialStateTPSA ms; + for (int i = 0; i < 3; ++i) { + ms.setDisplacement(i, static_cast(i) + 10.0); + ms.setRotation(i, static_cast(i) + 20.0); + } + ms.setSolidPressure(30.0); + Opm::ElasticityPrimaryVariables priVarsFromMs; + priVarsFromMs.assignNaive(ms); + for (std::size_t i = 0; i < 3; ++i) { + const auto eval_disp = priVarsFromMs.makeEvaluation(i, 0); + BOOST_CHECK_CLOSE(eval_disp.value(), ms.displacement(i), 1.0e-6); + BOOST_CHECK_CLOSE(eval_disp.derivative(i), 1.0, 1.0e-6); + + const auto eval_rot = priVarsFromMs.makeEvaluation(i + 3, 0); + BOOST_CHECK_CLOSE(eval_rot.value(), ms.rotation(i), 1.0e-6); + BOOST_CHECK_CLOSE(eval_rot.derivative(i + 3), 1.0, 1.0e-6); + } + const auto eval_sp = priVarsFromMs.makeEvaluation(6, 0); + BOOST_CHECK_CLOSE(eval_sp.value(), ms.solidPressure(), 1.0e-6); + BOOST_CHECK_CLOSE(eval_sp.derivative(6), 1.0, 1.0e-6); +} From e7e5304629213ffc421d2246fe5f75ee7fcc3a95 Mon Sep 17 00:00:00 2001 From: Svenn Tveit Date: Thu, 4 Dec 2025 11:25:25 +0100 Subject: [PATCH 07/19] Tests for TPSA local residual computations --- CMakeLists.txt | 1 + CMakeLists_files.cmake | 2 + tests/test_tpsa_localresidual.cpp | 286 ++++++++++++++++++++++++++++++ tests/tpsa_ex.data | 130 ++++++++++++++ 4 files changed, 419 insertions(+) create mode 100644 tests/test_tpsa_localresidual.cpp create mode 100644 tests/tpsa_ex.data diff --git a/CMakeLists.txt b/CMakeLists.txt index f78248935b2..0b8f48c33b0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -511,6 +511,7 @@ target_sources(test_outputdir PRIVATE $) target_sources(test_equil PRIVATE $) target_sources(test_RestartSerialization PRIVATE $) target_sources(test_glift1 PRIVATE $) +target_sources(test_tpsa_localresidual PRIVATE $) if(MPI_FOUND) target_sources(test_chopstep PRIVATE $) endif() diff --git a/CMakeLists_files.cmake b/CMakeLists_files.cmake index 65e6bace9c2..98da6118111 100644 --- a/CMakeLists_files.cmake +++ b/CMakeLists_files.cmake @@ -511,6 +511,7 @@ list (APPEND TEST_SOURCE_FILES tests/test_rstconv.cpp tests/test_stoppedwells.cpp tests/test_timer.cpp + tests/test_tpsa_localresidual.cpp tests/test_tpsa_primaryvariables.cpp tests/test_vfpproperties.cpp tests/test_wellmodel.cpp @@ -657,6 +658,7 @@ list (APPEND TEST_DATA_FILES tests/equil_humidwetgas.DATA tests/equil_rsvd_and_rvvd.DATA tests/equil_rsvd_and_rvvd_and_rvwvd.DATA + tests/tpsa_ex.data tests/wetgas.DATA tests/satfuncEPS_B.DATA tests/wells_manager_data.data diff --git a/tests/test_tpsa_localresidual.cpp b/tests/test_tpsa_localresidual.cpp new file mode 100644 index 00000000000..47aadf73e41 --- /dev/null +++ b/tests/test_tpsa_localresidual.cpp @@ -0,0 +1,286 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + Copyright 2025, NORCE AS + + This file is part of the Open Porous Media project (OPM). + + OPM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . +*/ +#include + +#define BOOST_TEST_MODULE TpsaLocalResidualTests + +#include + +#include + +#include + +#include + +#include +#include +#include +#include + +#include +#include +#include + +#if HAVE_DUNE_FEM +#include +#else +#include +#endif + +#include +#include + +namespace Opm::Properties { + namespace TTag { + struct TpsaTestTypeTag { + using InheritsFrom = std::tuple; + }; + } + + template + struct Problem + { using type = FlowProblemTPSA; }; + + template + struct EnableMech + { static constexpr bool value = true; }; + + template + struct Indices + { + private: + using BaseTypeTag = TTag::FlowProblem; + using FluidSystem = GetPropType; + static constexpr EnergyModules energyModuleType = getPropValue(); + static constexpr int numEnergyVars = energyModuleType == EnergyModules::FullyImplicitThermal; + static constexpr bool enableSeqImpEnergy = energyModuleType == EnergyModules::SequentialImplicitThermal; + + public: + using type = BlackOilOnePhaseIndices(), + getPropValue(), + getPropValue(), + numEnergyVars, + enableSeqImpEnergy, + getPropValue(), + getPropValue(), + /*PVOffset=*/0, + /*enabledCompIdx=*/FluidSystem::waterCompIdx, + getPropValue()>; + }; +} + +namespace { + +template +std::unique_ptr> +initSimulator(const char *filename) +{ + using Simulator = Opm::GetPropType; + const auto filenameArg = std::string {"--ecl-deck-file-name="} + filename; + const char* argv[] = { + "test_tpsa", + filenameArg.c_str() + }; + + Opm::Parameters::reset(); + Opm::registerAllParameters_(false); + Opm::BlackoilModelParameters::registerParameters(); + Opm::registerEclTimeSteppingParameters(); + Opm::Parameters::Register("Do *NOT* use!"); + Opm::Parameters::SetDefault(1); + Opm::Parameters::endRegistration(); + Opm::setupParameters_(/*argc=*/sizeof(argv) / sizeof(argv[0]), + argv, + /*registerParams=*/false, + /*allowUnused*/false, + /*handleHelp*/true, + /*myRank*/0); + + Opm::FlowGenericVanguard::readDeck(filename); + + return std::make_unique(); +} + +struct TpsaLocalResidualFixture +{ + // Constructor + TpsaLocalResidualFixture() + { + int argc = boost::unit_test::framework::master_test_suite().argc; + char** argv = boost::unit_test::framework::master_test_suite().argv; + #if HAVE_DUNE_FEM + Dune::Fem::MPIManager::initialize(argc, argv); + #else + Dune::MPIHelper::instance(argc, argv); + #endif + Opm::FlowGenericVanguard::setCommunication(std::make_unique()); + } +}; + +struct BoundaryConditionData +{ + Opm::BCMECHType type; + std::vector displacement; + unsigned boundaryFaceIndex; + double faceArea; +}; + +} // namespace Anonymous + +BOOST_GLOBAL_FIXTURE(TpsaLocalResidualFixture); + +BOOST_AUTO_TEST_CASE(TestElasticityResidual) { + using TypeTag = Opm::Properties::TTag::TpsaTestTypeTag; + using LocalResidual = Opm::ElasticityLocalResidual; + using Evaluation = Opm::GetPropType; + using MaterialState = Opm::MaterialStateTPSA; + using FluidSystem = Opm::GetPropType; + using FVector = Dune::FieldVector; + + enum { waterPhaseIdx = FluidSystem::waterPhaseIdx }; + + // Setup + auto simulator = initSimulator("tpsa_ex.data"); + simulator->model().applyInitialSolution(); + // simulator->model().invalidateAndUpdateIntensiveQuantities(/*timeIdx=*/0); + auto& problem = simulator->problem(); + MaterialState materialStateIn; + MaterialState materialStateEx; + unsigned globI = 0; + unsigned globJ = 1; + + // Set TPSA quantities + materialStateIn.setDisplacement(0, 1.0); + materialStateIn.setDisplacement(1, 1.0); + materialStateIn.setDisplacement(2, 1.0); + materialStateIn.setRotation(0, 1.0); + materialStateIn.setRotation(1, 1.0); + materialStateIn.setRotation(2, 1.0); + materialStateIn.setSolidPressure(1.0); + + materialStateEx.setDisplacement(0, 2.0); + materialStateEx.setDisplacement(1, 2.0); + materialStateEx.setDisplacement(2, 2.0); + materialStateEx.setRotation(0, 2.0); + materialStateEx.setRotation(1, 2.0); + materialStateEx.setRotation(2, 2.0); + materialStateEx.setSolidPressure(2.0); + + // Set pressure s.t. dP in TPSA source is nonzero + auto& sol = simulator->model().solution(/*timeIdx=*/0)[globI]; + sol[0] = 12.5e5; + simulator->model().invalidateAndUpdateIntensiveQuantities(/*timeIdx=*/0); + + // Boundary info + // TODO: FIXED and FREE tests + BoundaryConditionData bcdata { Opm::BCMECHType::NONE, std::vector {0.0}, 0, 0.0 }; + + // + // Volume term + // + FVector volTerm; + LocalResidual::computeVolumeTerm(volTerm, + materialStateIn, + problem, + globI); + + for (std::size_t i = 0; i < volTerm.size(); ++i) { + if (i < 3) { + BOOST_CHECK_CLOSE(volTerm[i].value(), 0.0, 1.0e-3); + } + else if (i >= 3 && i < volTerm.size() - 1) { + // = rotation / SMODULUS + BOOST_CHECK_CLOSE(volTerm[i].value(), 1.0 / 3.0e9, 1.0e-3); + } + else { + // = solidPressure / LAME + BOOST_CHECK_CLOSE(volTerm[i].value(), 1.0 / 2.0e9, 1.0e-3); + } + } + + // + // Face term + // + FVector faceTerm; + LocalResidual::computeFaceTerm(faceTerm, + materialStateIn, + materialStateEx, + problem, + globI, + globJ); + + for (std::size_t i = 0; i < faceTerm.size(); ++i) { + if (i < 2) { + BOOST_CHECK_CLOSE(faceTerm[i].value(), -40000001.66666, 1.0e-3); + } + else if (i == 2) { + BOOST_CHECK_CLOSE(faceTerm[i].value(), -39999998.33333, 1.0e-3); + } + else if (i == 3) { + BOOST_CHECK_CLOSE(faceTerm[i].value(), 0.0, 1.0e-3); + } + else if (i == 4 || i == 6) { + BOOST_CHECK_CLOSE(faceTerm[i].value(), -1.33333, 1.0e-3); + } + else { + BOOST_CHECK_CLOSE(faceTerm[i].value(), 1.33333, 1.0e-3); + } + } + + // + // Source term + // + FVector srcTerm; + LocalResidual::computeSourceTerm(srcTerm, + problem, + globI, + /*timeIdx=*/0); + + for (std::size_t i = 0; i < srcTerm.size(); ++i) { + if (i == 6) { + BOOST_CHECK_CLOSE(srcTerm[i].value(), -0.000125, 1.0e-2); + } + else { + BOOST_CHECK_CLOSE(srcTerm[i].value(), 0.0, 1.0e-3); + } + } + + // + // Boundary term + // + FVector bndryTerm; + LocalResidual::computeBoundaryTerm(bndryTerm, + materialStateIn, + bcdata, + problem, + globI); + for (std::size_t i = 0; i < bndryTerm.size(); ++i) { + if (i < 2) { + BOOST_CHECK_CLOSE(bndryTerm[i].value(), 120000001.0, 1.0e-6); + } + else if (i == 2) { + BOOST_CHECK_CLOSE(bndryTerm[i].value(), 119999999.0, 1.0e-6); + } + else { + BOOST_CHECK_CLOSE(bndryTerm[i].value(), 0.0, 1.0e-6); + } + } +} diff --git a/tests/tpsa_ex.data b/tests/tpsa_ex.data new file mode 100644 index 00000000000..4fbb76d092a --- /dev/null +++ b/tests/tpsa_ex.data @@ -0,0 +1,130 @@ +---------------- +RUNSPEC +---------------- + +TITLE + TWO CELLS X-DIR TPSA / + +DIMENS + 2 1 1 / + +EQLDIMS +/ + +TABDIMS +/ + +WELLDIMS +1 1 1 1 / + +WATER + +MECH + +TPSA +/ + +METRIC + +START + 1 JAN 2025 / + +UNIFIN +UNIFOUT + +---------------- +GRID +---------------- + +DX + 100 200 / +DY + 2*100 / +DZ + 2*100 / + +TOPS + 50 50 / + +PORO + 2*0.25 / +PERMX + 2*1000 / +COPY + PERMX PERMY / + PERMX PERMZ / +/ + +BIOTCOEF + 2*1.0 / + +LAME + 2*2.0 / + +SMODULUS + 2*3.0 / + +INIT + +---------------- +PROPS +---------------- + +ROCKOPTS + 1* STORE / + +ROCK + 1.0 1E-05 / + +DENSITY + 860.04 1033.0 0.853 / + +PVTW + 10 1.0 4E-5 0.5 / + +---------------- +SOLUTION +---------------- + +EQUIL + 100.0 10.0 / + +RTEMPVD + 100.0 25.0 + 200.0 25.0 / + +RPTRST + BASIC=2 / + +---------------- +SUMMARY +---------------- +FPR + +---------------- +SCHEDULE +---------------- + +RPTRST + BASIC=2 / + +WELSPECS + W1 INJ 1 1 1* WAT/ +/ + +COMPDAT + W1 1 1 1 1 OPEN 2* 0.2 / +/ + +WCONINJE + W1 WAT OPEN RATE 100.0 1* 900 / +/ + +--SOURCE +-- 1 1 1 WATER 100.0 / +--/ + +TSTEP + 2 / + +END From ea55602ce15f252ceaa6c2e7c66175d6167e4dc4 Mon Sep 17 00:00:00 2001 From: Svenn Tveit Date: Thu, 4 Dec 2025 12:23:05 +0100 Subject: [PATCH 08/19] Rename and use check with tolerance for some quantities --- CMakeLists_files.cmake | 2 +- ...rties.cpp => test_tpsa_face_properties.cpp} | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) rename tests/{test_face_properties.cpp => test_tpsa_face_properties.cpp} (88%) diff --git a/CMakeLists_files.cmake b/CMakeLists_files.cmake index 98da6118111..f5466183db9 100644 --- a/CMakeLists_files.cmake +++ b/CMakeLists_files.cmake @@ -483,7 +483,6 @@ list (APPEND TEST_SOURCE_FILES tests/test_dilu.cpp tests/test_equil.cpp tests/test_extractMatrix.cpp - tests/test_face_properties.cpp tests/test_flexiblesolver.cpp tests/test_glift1.cpp tests/test_graphcoloring.cpp @@ -511,6 +510,7 @@ list (APPEND TEST_SOURCE_FILES tests/test_rstconv.cpp tests/test_stoppedwells.cpp tests/test_timer.cpp + tests/test_tpsa_face_properties.cpp tests/test_tpsa_localresidual.cpp tests/test_tpsa_primaryvariables.cpp tests/test_vfpproperties.cpp diff --git a/tests/test_face_properties.cpp b/tests/test_tpsa_face_properties.cpp similarity index 88% rename from tests/test_face_properties.cpp rename to tests/test_tpsa_face_properties.cpp index 48b75291c28..86056602d79 100644 --- a/tests/test_face_properties.cpp +++ b/tests/test_tpsa_face_properties.cpp @@ -138,10 +138,10 @@ BOOST_AUTO_TEST_CASE(SimpleGridWithNNC) const DimVector normal_04 = {0.0, 0.0, 1.0}; const DimVector normal_40 = {0.0, 0.0, -1.0}; // = -1 * normal_04 BOOST_CHECK_CLOSE(faceProps.weightAverage(elem1, elem2), weightAvg_04, 1.0e-8); - BOOST_CHECK_EQUAL(faceProps.weightAverage(elem2, elem1), weightAvg_40); + BOOST_CHECK_CLOSE(faceProps.weightAverage(elem2, elem1), weightAvg_40, 1.0e-8); BOOST_CHECK_CLOSE(faceProps.weightProduct(elem1, elem2), weightProd, 1.0e-8); - BOOST_CHECK_EQUAL(faceProps.normalDistance(elem1, elem2), normDist); - BOOST_CHECK_EQUAL(faceProps.normalDistance(elem2, elem1), normDist); + BOOST_CHECK_CLOSE(faceProps.normalDistance(elem1, elem2), normDist, 1.0e-8); + BOOST_CHECK_CLOSE(faceProps.normalDistance(elem2, elem1), normDist, 1.0e-8); BOOST_CHECK_EQUAL(faceProps.cellFaceNormal(elem1, elem2), normal_04); BOOST_CHECK_EQUAL(faceProps.cellFaceNormal(elem2, elem1), normal_40); @@ -153,12 +153,12 @@ BOOST_AUTO_TEST_CASE(SimpleGridWithNNC) const double weightProdNNC = 6.25e-16; const DimVector normalNNC_35 = {0.0, -1.0, 0.0}; const DimVector normalNNC_53 = {0.0, 1.0, 0.0}; - BOOST_CHECK_EQUAL(faceProps.weightAverage(elemNNC1, elemNNC2), weightAvgNNC); - BOOST_CHECK_EQUAL(faceProps.weightAverage(elemNNC2, elemNNC1), weightAvgNNC); + BOOST_CHECK_CLOSE(faceProps.weightAverage(elemNNC1, elemNNC2), weightAvgNNC, 1.0e-8); + BOOST_CHECK_CLOSE(faceProps.weightAverage(elemNNC2, elemNNC1), weightAvgNNC, 1.0e-8); BOOST_CHECK_CLOSE(faceProps.weightProduct(elemNNC1, elemNNC2), weightProdNNC, 1.0e-8); BOOST_CHECK_CLOSE(faceProps.weightProduct(elemNNC2, elemNNC1), weightProdNNC, 1.0e-8); - BOOST_CHECK_EQUAL(faceProps.normalDistance(elemNNC1, elemNNC2), normDistNNC); - BOOST_CHECK_EQUAL(faceProps.normalDistance(elemNNC2, elemNNC1), normDistNNC); + BOOST_CHECK_CLOSE(faceProps.normalDistance(elemNNC1, elemNNC2), normDistNNC, 1.0e-8); + BOOST_CHECK_CLOSE(faceProps.normalDistance(elemNNC2, elemNNC1), normDistNNC, 1.0e-8); BOOST_CHECK_EQUAL(faceProps.cellFaceNormal(elemNNC1, elemNNC2), normalNNC_35); BOOST_CHECK_EQUAL(faceProps.cellFaceNormal(elemNNC2, elemNNC1), normalNNC_53); @@ -171,9 +171,9 @@ BOOST_AUTO_TEST_CASE(SimpleGridWithNNC) {0.0, -1.0, 0.0}, {0.0, 1.0, 0.0}, // y-dir {0.0, 0.0, 1.0} }; // z-dir for (std::size_t bndIdx = 0; bndIdx < 5; ++bndIdx) { - BOOST_CHECK_EQUAL(faceProps.weightAverageBoundary(elemBnd, bndIdx), weightAvgBnd); + BOOST_CHECK_CLOSE(faceProps.weightAverageBoundary(elemBnd, bndIdx), weightAvgBnd, 1.0e-8); BOOST_CHECK_CLOSE(faceProps.weightProductBoundary(elemBnd, bndIdx), weightProdBnd, 1.0e-8); - BOOST_CHECK_EQUAL(faceProps.normalDistanceBoundary(elemBnd, bndIdx), normDistBnd); + BOOST_CHECK_CLOSE(faceProps.normalDistanceBoundary(elemBnd, bndIdx), normDistBnd, 1.0e-8); BOOST_CHECK_EQUAL(faceProps.cellFaceNormalBoundary(elemBnd, bndIdx), normalBnd[bndIdx]); } } From 0d9bcfc177ffdadb2f173ba4d7ceb71aac223a99 Mon Sep 17 00:00:00 2001 From: Svenn Tveit Date: Thu, 4 Dec 2025 13:43:25 +0100 Subject: [PATCH 09/19] Added regression tests for TPSA --- regressionTests.cmake | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/regressionTests.cmake b/regressionTests.cmake index 20548013e05..8fdab0ba8fe 100644 --- a/regressionTests.cmake +++ b/regressionTests.cmake @@ -617,6 +617,21 @@ add_multiple_tests( TEST_ARGS --tolerance-cnv-relaxed=0.01 ) +set(_tpsa_cases + TPSA_LAGGED + TPSA_FIXEDSTRESS +) + +add_multiple_tests( + _tpsa_cases + "" + SIMULATOR flow + ABS_TOL ${abs_tol} + REL_TOL ${rel_tol} + DIR tpsa + TEST_ARGS --enable-opm-rst-file=1 +) + add_test_compareECLFiles(CASENAME ppcwmax FILENAME PPCWMAX-01 SIMULATOR flow From b0089eac5480e94ab456e23a3170875681e31433 Mon Sep 17 00:00:00 2001 From: Svenn Tveit Date: Sat, 6 Dec 2025 17:52:18 +0100 Subject: [PATCH 10/19] Doxygen updates --- .../discretization/common/tpsalinearizer.hpp | 82 +++++++++++++++---- opm/models/io/vtktpsaparams.cpp | 3 + .../tpsa/elasticitylocalresidualtpsa.hpp | 13 +-- .../tpsa/elasticityprimaryvariables.hpp | 9 +- opm/models/tpsa/tpsamodel.hpp | 75 ++++++++++++++++- .../tpsa/tpsanewtonconvergencewriter.hpp | 9 +- opm/models/tpsa/tpsanewtonmethod.hpp | 73 +++++++++++++---- opm/simulators/flow/BlackoilModelTPSA.hpp | 19 ++++- opm/simulators/flow/FacePropertiesTPSA.hpp | 6 +- .../flow/FacePropertiesTPSA_impl.hpp | 25 +++++- opm/simulators/flow/FlowProblemTPSA.hpp | 33 +++++++- opm/simulators/linalg/ISTLSolverTPSA.hpp | 14 +++- .../linalg/TPSALinearSolverParameters.cpp | 2 +- .../linalg/TPSALinearSolverParameters.hpp | 3 + .../linalg/setupPropertyTreeTPSA.cpp | 3 +- 15 files changed, 312 insertions(+), 57 deletions(-) diff --git a/opm/models/discretization/common/tpsalinearizer.hpp b/opm/models/discretization/common/tpsalinearizer.hpp index f823c1b8827..74651b3d679 100644 --- a/opm/models/discretization/common/tpsalinearizer.hpp +++ b/opm/models/discretization/common/tpsalinearizer.hpp @@ -45,6 +45,9 @@ namespace Opm { +/*! +* \brief Linearizes TPSA equations and generates system matrix and residual for linear solver +*/ template class TpsaLinearizer { @@ -86,8 +89,8 @@ class TpsaLinearizer * * \param simulator Simulator object * - * \note At this point we can assume that all objects in the simulator have been allocated. We cannot assume that - * they are fully initialized, though. + * At this point we can assume that all objects in the simulator have been allocated. We cannot assume that they are + * fully initialized, though. */ void init(Simulator& simulator) { @@ -98,8 +101,8 @@ class TpsaLinearizer /*! * \brief Causes the Jacobian matrix to be recreated from scratch before the next iteration. * - * \note This method is usally called if the sparsity pattern has changed for some reason. (e.g. by modifications of - * the grid or changes of the auxiliary equations.) + * This method is usally called if the sparsity pattern has changed for some reason. (e.g. by modifications of the + * grid or changes of the auxiliary equations.) */ void eraseMatrix() { @@ -133,9 +136,6 @@ class TpsaLinearizer /*! * \brief Linearize the non-linear system - * - * \note The linearizationType() controls the scheme used and the focus time index. The default is fully implicit - * scheme, and focus index equal to 0, i.e. current time (end of step). */ void linearize() { @@ -146,9 +146,8 @@ class TpsaLinearizer /*! * \brief Linearize the non-linear system for the spatial domain * - * \note That means that the global Jacobian of the residual is assembled and the residual is evaluated for the - * current solution. The current state of affairs (esp. the previous and the current solutions) is represented by the - * model object. + * The global Jacobian of the residual is assembled and the residual is evaluated for the current solution. The + * current state of affairs (esp. the previous and the current solutions) is represented by the model object. */ void linearizeDomain() { @@ -181,9 +180,8 @@ class TpsaLinearizer * * \param domain (Sub-)domain to linearize * - * \note That means that the global Jacobian of the residual is assembled and the residual is evaluated for the - * current solution. The current state of affairs (esp. the previous and the current solutions) is represented by - * the model object. + * The global Jacobian of the residual is assembled and the residual is evaluated for the current solution. The + * current state of affairs (esp. the previous and the current solutions) is represented by the model object. */ template void linearizeDomain(const SubDomainType& domain) @@ -262,42 +260,61 @@ class TpsaLinearizer // /// /*! * \brief Get Jacobian matrix + * + * \returns Reference to (sparse) Jacobian matrix object */ const SparseMatrixAdapter& jacobian() const { return *jacobian_; } /*! * \brief Get Jacobian matrix + * + * \returns Reference to (sparse) Jacobian matrix object */ SparseMatrixAdapter& jacobian() { return *jacobian_; } /*! * \brief Get residual vector + * + * \returns Reference to the residual */ const GlobalEqVector& residual() const { return residual_; } /*! * \brief Get residual vector + * + * \returns Reference to the residual */ GlobalEqVector& residual() { return residual_; } /*! * \brief Get linearization type + * + * \returns Reference to the linearization object + * + * The LinearizationType controls the scheme used and the focus time index. The default is fully implicit scheme, and + * focus index equal to 0, i.e. current time (end of step). */ const LinearizationType& getLinearizationType() const { return linearizationType_; } /*! * \brief Get constraints map + * + * \returns Constraints map + * + * \note No constraints implemented in TPSA */ std::map constraintsMap() const { return {}; } /*! * \brief Set linearization type + * + * \param linearizationType Linearization object */ void setLinearizationType(LinearizationType linearizationType) { linearizationType_ = linearizationType; } @@ -308,6 +325,9 @@ class TpsaLinearizer // /// /*! * \brief Construct the Jacobian matrix and create cell-neighbor information + * + * Sets up cell-neigbor and boundary information structs for easy calculation in linearize_(). In addition, sparsity + * patterns for Jacobian matrix is set up, */ void createMatrix_() { @@ -407,6 +427,8 @@ class TpsaLinearizer /*! * \brief Linearize the non-linear system of equation, computing residual and its derivatives + * + * Calculates TPSA equation terms for the AD residual, which in turn is used to get the derivates for the Jacobian */ template void linearize_(const SubDomainType& domain) @@ -577,7 +599,7 @@ class TpsaLinearizer } /*! - * \brief Set Jacobian matrix and residuals to zero + * \brief Reset Jacobian matrix and residuals to zero */ void resetSystem_() { @@ -593,54 +615,72 @@ class TpsaLinearizer // /// /*! * \brief Return simulator object + * + * \returns Reference to simulator */ Simulator& simulator_() { return *simulatorPtr_; } /*! * \brief Return simulator object + * + * \returns Reference to simulator */ const Simulator& simulator_() const { return *simulatorPtr_; } /*! * \brief Return problem object + * + * \returns Reference to problem */ Problem& problem_() { return simulator_().problem(); } /*! * \brief Return problem object + * + * \returns Reference to problem */ const Problem& problem_() const { return simulator_().problem(); } /*! * \brief Return Flow model object + * + * \returns Reference to Flow model */ FlowModel& flowModel_() { return simulator_().model(); } /*! * \brief Return Flow model object + * + * \returns Reference to Flow model */ const FlowModel& flowModel_() const { return simulator_().model(); } /*! * \brief Return TPSA model object + * + * \returns Reference to geomechanics model */ GeomechModel& geoMechModel_() { return problem_().geoMechModel(); } /*! * \brief Return TPSA model object + * + * \returns Reference to geomechanics model */ const GeomechModel& geoMechModel_() const { return problem_().geoMechModel(); } /*! * \brief Return grid view + * + * \returns Reference to grid view */ const GridView& gridView_() const { return problem_().gridView(); } @@ -652,7 +692,12 @@ class TpsaLinearizer std::unique_ptr jacobian_{}; GlobalEqVector residual_; + // // Helper structs + // + /*! + * \brief Neighbor information for flux terms + */ struct NeighborInfo { unsigned int neighbor; @@ -661,6 +706,9 @@ class TpsaLinearizer }; SparseTable neighborInfo_{}; + /*! + * \brief Information for boundary conditions + */ struct BoundaryConditionData { BCMECHType type; @@ -669,6 +717,9 @@ class TpsaLinearizer double faceArea; }; + /*! + * \brief Boundary information + */ struct BoundaryInfo { unsigned int cell; @@ -678,6 +729,9 @@ class TpsaLinearizer }; std::vector boundaryInfo_; + /*! + * \brief Inforamtion of the domain to linearize + */ struct FullDomain { std::vector cells; diff --git a/opm/models/io/vtktpsaparams.cpp b/opm/models/io/vtktpsaparams.cpp index fe0f60fbf8b..de1a5a54cff 100644 --- a/opm/models/io/vtktpsaparams.cpp +++ b/opm/models/io/vtktpsaparams.cpp @@ -41,6 +41,9 @@ void VtkTpsaParams::registerParameters() ("Include solid pressure in VTK output files"); } +/*! +* \brief Read runtime parameters +*/ void VtkTpsaParams::read() { displacementOutput_ = Parameters::Get(); diff --git a/opm/models/tpsa/elasticitylocalresidualtpsa.hpp b/opm/models/tpsa/elasticitylocalresidualtpsa.hpp index 1711e1725b1..ff61f3c2716 100644 --- a/opm/models/tpsa/elasticitylocalresidualtpsa.hpp +++ b/opm/models/tpsa/elasticitylocalresidualtpsa.hpp @@ -41,6 +41,9 @@ namespace Opm { +/*! +* \brief Calculation of (linear) elasticity model terms for the residual +*/ template class ElasticityLocalResidual { @@ -66,8 +69,8 @@ class ElasticityLocalResidual * \param problem Flow problem * \param globalIndex Cell index * - * \note Material state, problem input and global index here might/should be merged in an "IntensiveQuantity" - * container as in BlackOilLocalResidualTPFA + * Material state, problem input and global index here might/should be merged in an "IntensiveQuantity" container as + * in BlackOilLocalResidualTPFA */ template static void computeVolumeTerm(Dune::FieldVector& volTerm, @@ -103,8 +106,8 @@ class ElasticityLocalResidual * \param globalIndexIn Inside cell index * \param globalIndexEx Outside cell index * - * \note Material state, problem input and global index here might/should be merged in "IntensiveQuantity" and - * "NeighborInfo" containers as in BlackOilLocalResidualTPFA + * Material state, problem input and global index here might/should be merged in "IntensiveQuantity" and + * "NeighborInfo" containers as in BlackOilLocalResidualTPFA */ static void computeFaceTerm(Dune::FieldVector& faceTerm, const MaterialState& materialStateIn, @@ -373,7 +376,7 @@ class ElasticityLocalResidual * * \param sourceTerm Source term vector * \param problem Flow problem - * \param globalIndex Cell index + * \param globalSpaceIdex Cell index * \param timeIdx Time index */ static void computeSourceTerm(Dune::FieldVector& sourceTerm, diff --git a/opm/models/tpsa/elasticityprimaryvariables.hpp b/opm/models/tpsa/elasticityprimaryvariables.hpp index 97a43f84a02..4aee5e8a8c4 100644 --- a/opm/models/tpsa/elasticityprimaryvariables.hpp +++ b/opm/models/tpsa/elasticityprimaryvariables.hpp @@ -39,6 +39,9 @@ namespace Opm { +/*! +* \brief Primary variables in (linear) elasticity equations +*/ template class ElasticityPrimaryVariables : public Dune::FieldVector, @@ -85,9 +88,11 @@ class ElasticityPrimaryVariables * \param varIdx Primary variable index * \param timeIdx Time index * \param linearizationType Type of linearization + * \returns Primary variable as Evalutation type * - * Automatic differentiation: returns value + derivative - * Finite differences: returns value only + * \note + * \li Automatic differentiation: returns value + derivative + * \li Finite differences: returns value only */ Evaluation makeEvaluation(unsigned varIdx, unsigned timeIdx, diff --git a/opm/models/tpsa/tpsamodel.hpp b/opm/models/tpsa/tpsamodel.hpp index 56acff7856b..9cd32280f46 100644 --- a/opm/models/tpsa/tpsamodel.hpp +++ b/opm/models/tpsa/tpsamodel.hpp @@ -41,6 +41,11 @@ namespace Opm { +/*! +* \brief TPSA geomechanics model +* +* Solves the (linear) elasticity equations using Newton method. +*/ template class TpsaModel { @@ -70,17 +75,32 @@ class TpsaModel using SymTensor = Dune::FieldVector; public: + /*! + * \brief Small block vector wrapper class for model solutions + */ class TpsaBlockVectorWrapper { protected: SolutionVector blockVector_; public: + /*! + * \brief Constructor + * + * \param + * \param size Size of block vector + */ TpsaBlockVectorWrapper(const std::string&, const std::size_t size) : blockVector_(size) {} + /*! + * \brief Default constructor + */ TpsaBlockVectorWrapper() = default; + /*! + * \brief Test function for serialization + */ static TpsaBlockVectorWrapper serializationTestObject() { TpsaBlockVectorWrapper result("dummy", 3); @@ -91,18 +111,39 @@ class TpsaModel return result; } + /*! + * \brief Get reference of block vector + * + * \returns Block vector + */ SolutionVector& blockVector() { return blockVector_; } + /*! + * \brief Get const reference of block vector + * + * \returns Block vector + */ const SolutionVector& blockVector() const { return blockVector_; } + /*! + * \brief Check if incoming block vector is the same as current + * + * \param wrapper Block vector to check + * \returns Boolean indicating if wrapper is equal to current block vector + */ bool operator==(const TpsaBlockVectorWrapper& wrapper) const { return std::equal(this->blockVector_.begin(), this->blockVector_.end(), wrapper.blockVector_.begin(), wrapper.blockVector_.end()); } + /*! + * \brief Serializing operation + * + * \param serializer Reference to serializer object + */ template void serializeOp(Serializer& serializer) { @@ -171,7 +212,7 @@ class TpsaModel /*! * \brief Sync primary variables in overlapping cells * - * \note Copied code from EcfvDiscretization::syncOverlap() to sync TPSA primary variables + * Copied code from EcfvDiscretization::syncOverlap() to sync TPSA primary variables */ void syncOverlap() { @@ -217,6 +258,8 @@ class TpsaModel // /// /*! * \brief Return the linearizer + * + * \returns Reference to linearizer */ const Linearizer& linearizer() const { @@ -225,6 +268,8 @@ class TpsaModel /*! * \brief Return the linearizer + * + * \returns Reference to linearizer */ Linearizer& linearizer() { @@ -233,6 +278,8 @@ class TpsaModel /*! * \brief Return the Newton method + * + * \returns Reference to Newton method object */ const NewtonMethod& newtonMethod() const { @@ -241,6 +288,8 @@ class TpsaModel /*! * \brief Return the Newton method + * + * \returns Reference to Newton method object */ NewtonMethod& newtonMethod() { @@ -251,6 +300,7 @@ class TpsaModel * \brief Get reference to history solution vector * * \param timeIdx Time index + * \returns Reference to solution vector at time step timeIdx */ const SolutionVector& solution(unsigned timeIdx) const { @@ -261,6 +311,7 @@ class TpsaModel * \brief Get reference to history solution vector * * \param timeIdx Time index + * \returns Reference to solution vector at time step timeIdx */ SolutionVector& solution(unsigned timeIdx) { @@ -269,6 +320,7 @@ class TpsaModel /*! * \brief Return number of degrees of freedom in the grid from the Flow model + * \returns Number of grid DOFs */ std::size_t numGridDof() const { @@ -277,6 +329,7 @@ class TpsaModel /*! * \brief Return the total number of degrees of freedom + * \returns Number of grid DOFs + auxillary DOFs */ std::size_t numTotalDof() const { @@ -287,6 +340,7 @@ class TpsaModel * \brief Return the total grid volume from the Flow model * * \param globalIdx Cell index + * \returns Grid volume of grid cell */ Scalar dofTotalVolume(unsigned globalIdx) const { @@ -298,6 +352,7 @@ class TpsaModel * * \param dofIdx Degree-of-freedom index * \param eqIdx Equation index + * \returns Weight for equation */ Scalar eqWeight(unsigned /*dofIdx*/, unsigned eqIdx) const { @@ -316,7 +371,8 @@ class TpsaModel } /*! - * \brief Number of auxillary modules + * \brief Return number of auxillary modules + * \returns No. of auxillary modules */ std::size_t numAuxiliaryModules() const { @@ -324,7 +380,8 @@ class TpsaModel } /*! - * \brief Number of auxillary degrees of freedom + * \brief Return number of auxillary degrees of freedom + * \returns No. of auxillary DOFs */ std::size_t numAuxiliaryDof() const { @@ -336,6 +393,7 @@ class TpsaModel * * \param globalIdx Cell index * \param timeIdx Time index + * \returns Material state container for grid cell * * \note Cached material state not implemented yet! */ @@ -349,8 +407,9 @@ class TpsaModel * * \param globalIdx Cell index * \param with_fracture Boolean to activate fracture output + * \returns Displacement vector at grid cell * - * \note Used in OutputBlackOilModule! + * \note Needed in OutputBlackOilModule! */ DimVector disp(const unsigned globalIdx, const bool /*with_fracture*/) const { @@ -365,6 +424,7 @@ class TpsaModel * \brief Output (del?)stress tensor * * \param globalIdx Cell index + * \returns (Del?)stress tensor (Voigt notation) at grid cell * * \note Needed in OutputBlackOilModule, but zero for now! */ @@ -378,6 +438,7 @@ class TpsaModel * \brief Output fracture stress tensor * * \param globalIdx Cell index + * \returns Fracture stress tensor (Voigt notation) at grid cell * * \note Needed in OutputBlackOilModule, but zero for now! */ @@ -391,6 +452,7 @@ class TpsaModel * \brief Output linear stress tensor * * \param globalIdx Cell index + * \returns Linear stress tensor (Voigt notation) at grid cell * * \note Needed in OutputBlackOilModule, but zero for now! */ @@ -405,6 +467,7 @@ class TpsaModel * * \param globalIdx Cell index * \param with_fracture Boolean to activate fracture output + * \returns Stress tensor (Voigt notation) at grid cell * * \note Needed in OutputBlackOilModule, but zero for now! */ @@ -419,6 +482,7 @@ class TpsaModel * * \param globalIdx Cell index * \param with_fracture Boolean to activate fracture output + * \returns Strain tensor (Voigt notation) at grid cell * * \note Needed in OutputBlackOilModule, but zero for now! */ @@ -432,6 +496,7 @@ class TpsaModel * \brief Output potential forces * * \param globalIdx Cell index + * \returns Potential forces at grid cell * * \note Needed in OutputBlackOilModule, but zero for now! */ @@ -444,6 +509,7 @@ class TpsaModel * \brief Output potential pressure forces * * \param globalIdx Cell index + * \returns Potential pressure forces at grid cell * * \note Needed in OutputBlackOilModule, but zero for now! */ @@ -456,6 +522,7 @@ class TpsaModel * \brief Output potential temparature forces * * \param globalIdx Cell index + * \returns Potential temperature forces at grid cell * * \note Needed in OutputBlackOilModule, but zero for now! */ diff --git a/opm/models/tpsa/tpsanewtonconvergencewriter.hpp b/opm/models/tpsa/tpsanewtonconvergencewriter.hpp index 23d46d3914f..6ce7a130c51 100644 --- a/opm/models/tpsa/tpsanewtonconvergencewriter.hpp +++ b/opm/models/tpsa/tpsanewtonconvergencewriter.hpp @@ -29,6 +29,9 @@ namespace Opm { +/*! +* \brief Write convergence info for TPSA Newton method +*/ template class TpsaNewtonConvergenceWriter { @@ -59,10 +62,10 @@ class TpsaNewtonConvergenceWriter /*! * \brief Write the Newton update to disk. * - * \note Called after the linear solution is found for an iteration. - * * \param uLastIter The solution vector of the previous iteration. * \param deltaU The negative difference between the solution vectors of the previous and the current iteration. + * + * Called after the linear solution is found for an iteration. */ void writeFields(const SolutionVector& /*uLastIter*/, const GlobalEqVector& /*deltaU*/) @@ -77,7 +80,7 @@ class TpsaNewtonConvergenceWriter /*! * \brief Called by the Newton method after Newton algorithm has been completed for any given timestep. * - * \note This method is called regardless of whether the Newton method converged or not. + * This method is called regardless of whether the Newton method converged or not. */ void endTimeStep() { } diff --git a/opm/models/tpsa/tpsanewtonmethod.hpp b/opm/models/tpsa/tpsanewtonmethod.hpp index c976d06880a..5587a306bd4 100644 --- a/opm/models/tpsa/tpsanewtonmethod.hpp +++ b/opm/models/tpsa/tpsanewtonmethod.hpp @@ -47,6 +47,12 @@ namespace Opm { +/*! +* \brief Newton method solving for generic TPSA model. +* +* Generates the Jacobian matrix, J(u^n) and residual vector, R(u^n), with a solution vector, u^n, at iteration n. +* Subsequently the linear system J(u^n)\delta u^n = -R(u^n) is solved to get u^{n+1} = u^n + \Delta u^n. +*/ template class TpsaNewtonMethod { @@ -98,14 +104,16 @@ class TpsaNewtonMethod /*! * \brief Finialize the construction of the object. * - * \note At this point, it can be assumed that all objects featured by the simulator have been allocated. (But not - * that they have been fully initialized yet.) + * At this point, it can be assumed that all objects featured by the simulator have been allocated. (But not that + * they have been fully initialized yet.) */ void finishInit() { } /*! * \brief Run the Newton method. + * + * \returns Bool indicating if Newton converged */ bool apply() { @@ -284,8 +292,8 @@ class TpsaNewtonMethod * * \param value New iteration index * - * \note Normally this does not need to be called, but if the non-linear solver is implemented externally, it needs - * to be set in order for the model to do the Right Thing (TM) while linearizing. + * Normally this does not need to be called, but if the non-linear solver is implemented externally, it needs to be + * set in order for the model to do the Right Thing (TM) while linearizing. */ void setIterationIndex(int value) { @@ -304,90 +312,120 @@ class TpsaNewtonMethod /*! * \brief Returns true if the error of the solution is below the tolerance. + * + * \returns Bool indicating if convergence has been achived */ bool converged() const { return error_ <= tolerance(); } /*! * \brief Returns a reference to the object describing the current physical problem. + * + * \returns Reference to problem object */ Problem& problem() { return simulator_.problem(); } /*! * \brief Returns a reference to the object describing the current physical problem. + * + * \returns Reference to problem object */ const Problem& problem() const { return simulator_.problem(); } /*! - * \brief Returns a reference to the numeric model. + * \brief Returns a reference to the geomechanics model. + * + * \returns Reference to geomechanics model object */ Model& model() { return simulator_.problem().geoMechModel(); } /*! - * \brief Returns a reference to the numeric model. + * \brief Returns a reference to the geomechanics model. + * + * \returns Reference to geomechanics model object */ const Model& model() const { return simulator_.problem().geoMechModel(); } /*! * \brief Returns the linear solver backend object for external use. + * + * \returns Reference to linear solver object */ LinearSolverBackend& linearSolver() { return linearSolver_; } /*! * \brief Returns the linear solver backend object for external use. + * + * \returns Reference to linear solver object */ const LinearSolverBackend& linearSolver() const { return linearSolver_; } /*! * \brief Returns the number of iterations done since the Newton method was invoked. + * + * \returns Number of Newton iteratinos */ int numIterations() const { return numIterations_; } /*! * \brief Returns the number of linearizations that has done since the Newton method was invoked. + * + * \returns Number of linearizations */ int numLinearizations() const { return numLinearizations_; } /*! * \brief Return the current tolerance at which the Newton method considers itself to be converged. + * + * \returns Tolerance for Newton error */ Scalar tolerance() const { return params_.tolerance_; } /*! * \brief Returns minimum number of Newton iterations used + * + * \returns Minimum number of Newton iterations */ Scalar minIterations() const { return params_.minIterations_; } /*! * \brief Return post-process timer + * + * \returns Reference to post-process timer object */ const Timer& prePostProcessTimer() const { return prePostProcessTimer_; } /*! * \brief Return linearization timer + * + * \returns Reference to linearization timer object */ const Timer& linearizeTimer() const { return linearizeTimer_; } /*! * \brief Return linear solver timer + * + * \returns Reference to linear solver timer object */ const Timer& solveTimer() const { return solveTimer_; } /*! - * \brief Return update solution timer + * \brief Return solution update timer + * + * \returns Reference to solution update timer object */ const Timer& updateTimer() const { return updateTimer_; } @@ -395,6 +433,8 @@ class TpsaNewtonMethod protected: /*! * \brief Returns true if the Newton method ought to be chatty. + * + * \returns Bool indicating if the Newton procedure should be verbose */ bool verbose_() const { return params_.verbose_ && (simulator_.gridView().comm().rank() == 0); } @@ -501,12 +541,11 @@ class TpsaNewtonMethod /*! * \brief Update the error of the solution given the previous iteration. * - * \note For our purposes, the error of a solution is defined as the maximum of the weighted residual of a given - * solution. - * * \param currentSolution The solution at the beginning the current iteration * \param currentResidual The residual (i.e., right-hand-side) of the current iteration's solution. * \param solutionUpdate The difference between the current and the next solution + * + * For our purposes, the error of a solution is defined as the maximum of the weighted residual of a given solution. */ void postSolve_(const SolutionVector& /*currentSolution*/, const GlobalEqVector& /*currentResidual*/, @@ -516,13 +555,13 @@ class TpsaNewtonMethod /*! * \brief Update the current solution with a delta vector. * - * \note Different update strategies, such as chopped updates can be implemented by overriding this method. The - * default behavior is use the standard Newton-Raphson update strategy, i.e. \f[ u^{k+1} = u^k - \Delta u^k \f] - * * \param nextSolution The solution vector after the current iteration * \param currentSolution The solution vector after the last iteration * \param solutionUpdate The delta vector as calculated by solving the linear system of equations * \param currentResidual The residual vector of the current Newton-Raphson iteraton + * + * Different update strategies, such as chopped updates can be implemented by overriding this method. The default + * behavior is use the standard Newton-Raphson update strategy, i.e. u^{n+1} = u^n - \Delta u^n */ void update_(SolutionVector& nextSolution, const SolutionVector& currentSolution, @@ -614,7 +653,7 @@ class TpsaNewtonMethod * \param currentSolution The solution vector after the last iteration * \param solutionUpdate The delta vector as calculated by solving the linear system of equations * - * \note This method is called as part of the update proceedure. + * This method is called as part of the update proceedure. */ void writeConvergence_(const SolutionVector& currentSolution, const GlobalEqVector& solutionUpdate) @@ -654,6 +693,8 @@ class TpsaNewtonMethod /*! * \brief Returns true iff another Newton iteration should be done. + * + * \returns Bool indicating if Newton iterations should continue */ bool proceed_() const { @@ -703,7 +744,9 @@ class TpsaNewtonMethod { } /*! - * \brief Get constraints from properties + * \brief Check if constraints are enabled + * + * \returns Bool indicating if constraints are enabled */ static bool enableConstraints_() { return getPropValue(); } diff --git a/opm/simulators/flow/BlackoilModelTPSA.hpp b/opm/simulators/flow/BlackoilModelTPSA.hpp index b0637b28f26..fb1d964cd7b 100644 --- a/opm/simulators/flow/BlackoilModelTPSA.hpp +++ b/opm/simulators/flow/BlackoilModelTPSA.hpp @@ -38,6 +38,9 @@ namespace Opm { +/*! +* \brief Black oil model for coupling Flow simulations with TPSA geomechanics +*/ template class BlackoilModelTPSA : public BlackoilModel { @@ -51,6 +54,11 @@ class BlackoilModelTPSA : public BlackoilModel /*! * \brief Constructor + * + * \param simulator Reference to simulator object + * \param param Reference to parameters for model + * \param well_model Refenerence to well model + * \param terminal_output Bool for terminal output */ explicit BlackoilModelTPSA(Simulator& simulator, const ModelParameters& param, @@ -65,10 +73,11 @@ class BlackoilModelTPSA : public BlackoilModel * \param iteration Flow nonlinear iteration * \param timer Simulation timer * \param nonlinear_solver Nonlinear solver type + * \returns Report for simulator performance * - * \note Several strategies of updating flow and geomechanics may be implemented: - * fixed-stress: fixed-stress algorithm, i.e. iteratively solving Flow and TPSA equations in sequence - * lagged: one-way coupling where Flow is solved with TPSA info from previous time step + * \note Strategies of coupling Flow and TPSA currently implemented: + * \li fixed-stress: fixed-stress algorithm, i.e. iteratively solving Flow and TPSA equations in sequence + * \li lagged: one-way coupling where Flow is solved with TPSA info from previous time step */ template SimulatorReportSingle nonlinearIteration(const int iteration, @@ -97,6 +106,7 @@ class BlackoilModelTPSA : public BlackoilModel * \param iteration Flow nonlinear iteration * \param timer Simulation timer * \param nonlinear_solver Nonlinear solver type + * \returns Report for simulator performance * */ template @@ -173,6 +183,7 @@ class BlackoilModelTPSA : public BlackoilModel * \param iteration Flow nonlinear iteration * \param timer Simulation timer * \param nonlinear_solver Nonlinear solver type + * \returns Report for simulator performance * */ template @@ -203,6 +214,8 @@ class BlackoilModelTPSA : public BlackoilModel /*! * \brief Solve TPSA geomechanics equations * + * \returns Bool indicating TPSA convergence + * * \note Calls Newton method for TPSA */ bool solveTpsaEquations() diff --git a/opm/simulators/flow/FacePropertiesTPSA.hpp b/opm/simulators/flow/FacePropertiesTPSA.hpp index bf8ff9844b8..fb718675935 100644 --- a/opm/simulators/flow/FacePropertiesTPSA.hpp +++ b/opm/simulators/flow/FacePropertiesTPSA.hpp @@ -39,7 +39,11 @@ namespace Opm { class EclipseState; - +/*! +* \brief Cell face properties needed in TPSA equation calculations +* +* Similar calculations as done in Transmissibility class for TPFA +*/ template class FacePropertiesTPSA { diff --git a/opm/simulators/flow/FacePropertiesTPSA_impl.hpp b/opm/simulators/flow/FacePropertiesTPSA_impl.hpp index a26c6bec999..fd9871b7e4c 100644 --- a/opm/simulators/flow/FacePropertiesTPSA_impl.hpp +++ b/opm/simulators/flow/FacePropertiesTPSA_impl.hpp @@ -244,7 +244,8 @@ update() * \brief Average (half-)weight at interface between two elements * * \param elemIdx1 Cell index 1 -* \param elemIdx1 Cell index 2 +* \param elemIdx2 Cell index 2 +* \returns Average weight */ template Scalar FacePropertiesTPSA:: @@ -269,6 +270,7 @@ weightAverage(unsigned elemIdx1, unsigned elemIdx2) const * * \param elemIdx Cell index * \param boundaryFaceIdx Face index +* \returns Average weight at boundary */ template Scalar FacePropertiesTPSA:: @@ -281,7 +283,8 @@ weightAverageBoundary(unsigned elemIdx, unsigned boundaryFaceIdx) const * \brief Product of weights at interface between two elements * * \param elemIdx1 Cell index 1 -* \param elemIdx1 Cell index 2 +* \param elemIdx2 Cell index 2 +* \returns Weight product */ template Scalar FacePropertiesTPSA:: @@ -295,6 +298,7 @@ weightProduct(unsigned elemIdx1, unsigned elemIdx2) const * * \param elemIdx Cell index * \param boundaryFaceIdx Face index +* \returns Weight product at boundary */ template Scalar FacePropertiesTPSA:: @@ -307,7 +311,8 @@ weightProductBoundary(unsigned elemIdx, unsigned boundaryFaceIdx) const * \brief Distance between two elements * * \param elemIdx1 Cell index 1 -* \param elemIdx1 Cell index 2 +* \param elemIdx2 Cell index 2 +* \returns Distance */ template Scalar FacePropertiesTPSA:: @@ -321,6 +326,7 @@ normalDistance(unsigned elemIdx1, unsigned elemIdx2) const * * \param elemIdx Cell index * \param boundaryFaceIdx Face index +* \returns Distance to boundary */ template Scalar FacePropertiesTPSA:: @@ -333,7 +339,10 @@ normalDistanceBoundary(unsigned elemIdx, unsigned boundaryFaceIdx) const * \brief Cell face normal at interface between two elements * * \param elemIdx1 Cell index 1 -* \param elemIdx1 Cell index 2 +* \param elemIdx2 Cell index 2 +* \returns Face normals +* +* \note It is assumed that normals point from lower index to higher index! */ template typename FacePropertiesTPSA::DimVector @@ -353,6 +362,9 @@ cellFaceNormal(unsigned elemIdx1, unsigned elemIdx2) * * \param elemIdx Cell index * \param boundaryFaceIdx Face index +* \returns Face normals +* +* \note Boundary normals points outwards */ template const typename FacePropertiesTPSA::DimVector& @@ -370,6 +382,7 @@ cellFaceNormalBoundary(unsigned elemIdx, unsigned boundaryFaceIdx) const * * \param distVec Distance vector from cell center to face center * \param faceNormal Face (unit) normal vector +* \returns Distance cell center -> face center */ template Scalar FacePropertiesTPSA:: @@ -410,6 +423,8 @@ computeCellProperties(const Intersection& intersection, * \param inside Info describing inside face * \param outside Info describing outside face * \param faceNormal Face (unit) normal +* +* \warning LGR computations not implemented! */ template template @@ -447,6 +462,7 @@ computeCellProperties(const Intersection& intersection, * * \param distance Normal distance from cell to face center * \param smod Shear modulus +* \returns Weight ratio */ template Scalar FacePropertiesTPSA:: @@ -460,6 +476,7 @@ computeWeight_(const Scalar distance, const Scalar smod) * * \param faceCenter Face center coordinates * \param cellIdx Cell index +* \returns Distance vector cell center -> face center */ template typename FacePropertiesTPSA::DimVector diff --git a/opm/simulators/flow/FlowProblemTPSA.hpp b/opm/simulators/flow/FlowProblemTPSA.hpp index 43a626b3dd0..eea2062ae5c 100644 --- a/opm/simulators/flow/FlowProblemTPSA.hpp +++ b/opm/simulators/flow/FlowProblemTPSA.hpp @@ -55,6 +55,9 @@ namespace Opm { +/*! +* \brief Problem for Flow-TPSA coupled simulations +*/ template class FlowProblemTPSA : public FlowProblemBlackoil { @@ -94,6 +97,8 @@ class FlowProblemTPSA : public FlowProblemBlackoil // /// /*! * \brief Constructor + * + * \param simulator Reference to simulator object */ FlowProblemTPSA(Simulator& simulator) : ParentType(simulator) @@ -167,7 +172,7 @@ class FlowProblemTPSA : public FlowProblemBlackoil /*! * \brief Set initial solution for the problem * - * \note Mostly copy of FvBaseDiscretization::applyInitialSolution and FlowProblemBlackoil::initial() + * This function is a combination FvBaseDiscretization::applyInitialSolution and FlowProblemBlackoil::initial() */ void initialSolutionApplied() { @@ -259,6 +264,9 @@ class FlowProblemTPSA : public FlowProblemBlackoil * * \param globalSpaceIdx Cell index * \param directionId Direction id + * \returns A pair of BCMECHType and displacement vector + * + * Output from this function is used in LocalResidual::computeBoundaryTerm * * \note Only BCMECHTYPE = FREE and NONE implemented. FIXED will/should throw an error when computed in local * residual! @@ -294,6 +302,8 @@ class FlowProblemTPSA : public FlowProblemBlackoil * \param sourceTerm Source term vector * \param globalSpaceIdx Cell index * \param timeIdx Time index + * + * This term requires that intensive quantities are updated! */ void tpsaSource(Dune::FieldVector& sourceTerm, unsigned globalSpaceIdx, @@ -317,8 +327,9 @@ class FlowProblemTPSA : public FlowProblemBlackoil /*! * \brief Pore volume change due to geomechanics * - * \param globalDofIdx Cell index + * \param elementIdx Cell index * \param timeIdx Time index + * \returns Pore volume change (dimensionless) * * \note This is the coupling term to Flow */ @@ -343,6 +354,7 @@ class FlowProblemTPSA : public FlowProblemBlackoil * * \param globalElemIdxIn Inside cell index * \param globalElemIdxOut Outside cell index + * \returns Weight average */ Scalar weightAverage(unsigned globalElemIdxIn, unsigned globalElemIdxOut) { @@ -354,6 +366,7 @@ class FlowProblemTPSA : public FlowProblemBlackoil * * \param globalElemIdxIn Inside cell index * \param boundaryFaceIdx Boundary (local) face index + * \returns Weight average at boundary */ Scalar weightAverageBoundary(unsigned globalElemIdxIn, unsigned boundaryFaceIdx) const { @@ -365,6 +378,7 @@ class FlowProblemTPSA : public FlowProblemBlackoil * * \param globalElemIdxIn Inside cell index * \param globalElemIdxOut Outside cell index + * \returns Weight product */ Scalar weightProduct(unsigned globalElemIdxIn, unsigned globalElemIdxOut) const { @@ -376,6 +390,7 @@ class FlowProblemTPSA : public FlowProblemBlackoil * * \param globalElemIdxIn Inside cell index * \param globalElemIdxOut Outside cell index + * \returns Normal distance */ Scalar normalDistance(unsigned globalElemIdxIn, unsigned globalElemIdxOut) const { @@ -387,6 +402,7 @@ class FlowProblemTPSA : public FlowProblemBlackoil * * \param globalElemIdxIn Inside cell index * \param boundaryFaceIdx Boundary (local) face index + * \returns Normal distance to boundary */ Scalar normalDistanceBoundary(unsigned globalElemIdxIn, unsigned boundaryFaceIdx) const { @@ -398,6 +414,7 @@ class FlowProblemTPSA : public FlowProblemBlackoil * * \param globalElemIdxIn Inside cell index * \param globalElemIdxOut Outside cell index + * \returns Face normal */ DimVector cellFaceNormal(unsigned globalElemIdxIn, unsigned globalElemIdxOut) { @@ -409,6 +426,7 @@ class FlowProblemTPSA : public FlowProblemBlackoil * * \param globalElemIdxIn Inside cell index * \param boundaryFaceIdx Boundary (local) face index + * \returns Face normal on boundary */ const DimVector& cellFaceNormalBoundary(unsigned globalElemIdxIn, unsigned boundaryFaceIdx) const { @@ -419,6 +437,7 @@ class FlowProblemTPSA : public FlowProblemBlackoil * \brief Direct access to shear modulus in an element * * \param globalElemIdx Cell index + * \returns Shear modulus */ Scalar shearModulus(unsigned globalElemIdx) const { @@ -427,6 +446,8 @@ class FlowProblemTPSA : public FlowProblemBlackoil /*! * \brief Flow-TPSA lagged coupling scheme activated? + * + * \returns Bool indicating lagged coupling */ bool laggedScheme() const { @@ -436,6 +457,8 @@ class FlowProblemTPSA : public FlowProblemBlackoil /*! * \brief Flow-TPSA fixed-stress coupling scheme activated? + * + * \returns Bool indicating fixed-stress coupling */ bool fixedStressScheme() const { @@ -445,6 +468,8 @@ class FlowProblemTPSA : public FlowProblemBlackoil /*! * \brief Get TPSA model + * + * \returns Reference to geomechanics model */ const GeomechModel& geoMechModel() const { @@ -453,6 +478,8 @@ class FlowProblemTPSA : public FlowProblemBlackoil /*! * \brief Get TPSA model + * + * \returns Reference to geomechanics model */ GeomechModel& geoMechModel() { @@ -461,6 +488,8 @@ class FlowProblemTPSA : public FlowProblemBlackoil /*! * \brief Get fixed-stress iteration parameters + * + * \returns Pair with min/max fixed-stress iterations */ std::pair fixedStressParameters() const { diff --git a/opm/simulators/linalg/ISTLSolverTPSA.hpp b/opm/simulators/linalg/ISTLSolverTPSA.hpp index 72c40f999e2..bff05ad76c9 100644 --- a/opm/simulators/linalg/ISTLSolverTPSA.hpp +++ b/opm/simulators/linalg/ISTLSolverTPSA.hpp @@ -59,7 +59,7 @@ namespace Opm { /*! * \brief Class for setting up ISTL linear solvers for TPSA * -* \note Most of the code is copied from ISTLSolver class +* Most of the code is copied from ISTLSolver class */ template class ISTLSolverTPSA @@ -238,6 +238,7 @@ class ISTLSolverTPSA * \brief Solve the linear system and store result in the input Vector x * * \param x Linear system solution will be stored here + * \returns Bool indicating convergence */ bool solve(Vector& x) { @@ -288,6 +289,8 @@ class ISTLSolverTPSA /*! * \brief Get number of solver calls + * + * \returns Number of calls to linear solver */ int getSolveCount() const { @@ -296,6 +299,8 @@ class ISTLSolverTPSA /*! * \brief Get number of linear solver iterations + * + * \returns Number of iterations */ int iterations () const { @@ -306,6 +311,8 @@ class ISTLSolverTPSA /*! * \brief Check for parallel session * + * \returns Bool indicating if this is a parallel session + * * \warning comm_ must be set before using this function */ bool isParallel() const @@ -321,6 +328,7 @@ class ISTLSolverTPSA * \brief Check for linear solver convergence * * \param result Linear solver result container + * \returns Bool indicating convergence */ bool checkConvergence(const Dune::InverseOperatorResult& result) const { @@ -347,6 +355,8 @@ class ISTLSolverTPSA // /// /*! * \brief Get reference to system matrix object + * + * \returns Reference to matrix */ Matrix& getMatrix() { @@ -358,6 +368,8 @@ class ISTLSolverTPSA /*! * \brief Get reference to system matrix object + * + * \returns Reference to matrix */ const Matrix& getMatrix() const { diff --git a/opm/simulators/linalg/TPSALinearSolverParameters.cpp b/opm/simulators/linalg/TPSALinearSolverParameters.cpp index e4288e48b6b..5734924608d 100644 --- a/opm/simulators/linalg/TPSALinearSolverParameters.cpp +++ b/opm/simulators/linalg/TPSALinearSolverParameters.cpp @@ -32,7 +32,7 @@ namespace Opm { /*! * \brief Internalize runtime parameters * -* \note Overloading FlowLinearSolverParameters::init() to read TPSA specific runtime/default parameters +* Overloading FlowLinearSolverParameters::init() to read TPSA specific runtime/default parameters */ void TpsaLinearSolverParameters::init() { diff --git a/opm/simulators/linalg/TPSALinearSolverParameters.hpp b/opm/simulators/linalg/TPSALinearSolverParameters.hpp index 360661b8c6c..fb43adea32b 100644 --- a/opm/simulators/linalg/TPSALinearSolverParameters.hpp +++ b/opm/simulators/linalg/TPSALinearSolverParameters.hpp @@ -49,6 +49,9 @@ struct TpsaLinearSolverPrintJsonDefinition { static constexpr auto value = false namespace Opm { +/*! +* \brief Parametern for linear solver and preconditioner +*/ struct TpsaLinearSolverParameters : public FlowLinearSolverParameters { void init(); diff --git a/opm/simulators/linalg/setupPropertyTreeTPSA.cpp b/opm/simulators/linalg/setupPropertyTreeTPSA.cpp index 92e5ae29e1f..9cd94baf4f9 100644 --- a/opm/simulators/linalg/setupPropertyTreeTPSA.cpp +++ b/opm/simulators/linalg/setupPropertyTreeTPSA.cpp @@ -45,8 +45,7 @@ namespace Opm { * * \param p Runtime/default parameters * -* \note Same as Opm::setupPropertyTree() for Flow, but limit remove Flow specific linear solver variants like cpr_***. -* Moreover, TPSA specific presets should be done here! +* \note Same as setupPropertyTree() for Flow, but Flow specific linear solver variants like cpr_*** have been removed. */ PropertyTree setupPropertyTreeTPSA(TpsaLinearSolverParameters p) { From 573ad35b7d1ef742c726030e94162b8ae6a1d00c Mon Sep 17 00:00:00 2001 From: Svenn Tveit Date: Mon, 8 Dec 2025 15:05:54 +0100 Subject: [PATCH 11/19] const fixes --- opm/models/blackoil/blackoilintensivequantities.hh | 4 ++-- opm/models/discretization/common/tpsalinearizer.hpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/opm/models/blackoil/blackoilintensivequantities.hh b/opm/models/blackoil/blackoilintensivequantities.hh index 465d7bb62da..423d29fd12a 100644 --- a/opm/models/blackoil/blackoilintensivequantities.hh +++ b/opm/models/blackoil/blackoilintensivequantities.hh @@ -595,9 +595,9 @@ public: // TPSA calculations if (problem.simulator().vanguard().eclState().runspec().tpsa().active()) { // TPSA compressibility term - Scalar rockBiot = problem.rockBiotComp(globalSpaceIdx); + const Scalar rockBiot = problem.rockBiotComp(globalSpaceIdx); if (rockBiot > 0.0) { - Scalar rockRefPressure = problem.rockReferencePressure(globalSpaceIdx); + const Scalar rockRefPressure = problem.rockReferencePressure(globalSpaceIdx); Evaluation active_pressure; if (FluidSystem::phaseIsActive(oilPhaseIdx)) { active_pressure = fluidState_.pressure(oilPhaseIdx) - rockRefPressure; diff --git a/opm/models/discretization/common/tpsalinearizer.hpp b/opm/models/discretization/common/tpsalinearizer.hpp index 74651b3d679..f6d10f10112 100644 --- a/opm/models/discretization/common/tpsalinearizer.hpp +++ b/opm/models/discretization/common/tpsalinearizer.hpp @@ -101,7 +101,7 @@ class TpsaLinearizer /*! * \brief Causes the Jacobian matrix to be recreated from scratch before the next iteration. * - * This method is usally called if the sparsity pattern has changed for some reason. (e.g. by modifications of the + * This method is usually called if the sparsity pattern has changed for some reason. (e.g. by modifications of the * grid or changes of the auxiliary equations.) */ void eraseMatrix() From e7fd3cba4b0c1108b8f35b5b74bc8a10e7242dac Mon Sep 17 00:00:00 2001 From: Svenn Tveit Date: Wed, 10 Dec 2025 15:48:36 +0100 Subject: [PATCH 12/19] Fix for FREE boundary conditions. Addition of a cross product term for rotation equations. --- opm/models/tpsa/elasticitylocalresidualtpsa.hpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/opm/models/tpsa/elasticitylocalresidualtpsa.hpp b/opm/models/tpsa/elasticitylocalresidualtpsa.hpp index ff61f3c2716..f383be94b86 100644 --- a/opm/models/tpsa/elasticitylocalresidualtpsa.hpp +++ b/opm/models/tpsa/elasticitylocalresidualtpsa.hpp @@ -346,6 +346,12 @@ class ElasticityLocalResidual // E.g. if i = x-dir(=0), we want y-dir(=1) and z-dir(=2), hence -1 mod 3 must equal 2 and not -1 auto modNeg = [](int i) { return ((i % 3) + 3) % 3; }; + // Pre-compute dot product for rotation equation + Evaluation dotProd = 0; + for (int dirIdx = 0; dirIdx < 3; ++dirIdx) { + dotProd += faceNormal[dirIdx] * materialState.rotation(dirIdx); + } + // Loop over x-, y- and z-dir (corresponding to dirIdx = 0, 1, 2) for (int dirIdx = 0; dirIdx < 3; ++dirIdx) { // Direction indices in cross-product @@ -353,17 +359,20 @@ class ElasticityLocalResidual unsigned dirIdxPos = modNeg(dirIdx + 1); // Rotation equation + const Scalar faceNormalDir = faceNormal[dirIdx]; const Scalar faceNormalNeg = faceNormal[dirIdxNeg]; const Scalar faceNormalPos = faceNormal[dirIdxPos]; const Evaluation& dispNeg = materialState.displacement(dirIdxNeg); const Evaluation& dispPos = materialState.displacement(dirIdxPos); + const Evaluation& rot = materialState.rotation(dirIdx); + bndryTerm[contiRotEqIdx + dirIdx] += - - weightAvg * (faceNormalNeg * dispPos - faceNormalPos * dispNeg); + - weightAvg * (faceNormalNeg * dispPos - faceNormalPos * dispNeg) + + 0.5 * (normDist / sModulus) * (dotProd * faceNormalDir - rot); // Solid pressure (directional-dependent) equation - const Scalar faceNormalDir = faceNormal[dirIdx]; const Evaluation& disp = materialState.displacement(dirIdx); bndryTerm[contiSolidPresEqIdx] += From 605d76992c9a6851d44aa196015f37013d062fe4 Mon Sep 17 00:00:00 2001 From: Svenn Tveit Date: Mon, 15 Dec 2025 12:14:31 +0100 Subject: [PATCH 13/19] Register TPSA runtime parameters for flow executable --- opm/simulators/flow/SimulatorFullyImplicitBlackoil.hpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/opm/simulators/flow/SimulatorFullyImplicitBlackoil.hpp b/opm/simulators/flow/SimulatorFullyImplicitBlackoil.hpp index 7c8fe7b6384..6c51970f597 100644 --- a/opm/simulators/flow/SimulatorFullyImplicitBlackoil.hpp +++ b/opm/simulators/flow/SimulatorFullyImplicitBlackoil.hpp @@ -38,6 +38,8 @@ #include +#include + #include #include #include @@ -47,6 +49,7 @@ #include #include #include +#include #include #include #include @@ -159,6 +162,9 @@ class SimulatorFullyImplicitBlackoil : private SerializableSim SolverParameters::registerParameters(); TimeStepper::registerParameters(); detail::registerSimulatorParameters(); + + TpsaNewtonMethodParams::registerParameters(); + TpsaLinearSolverParameters::registerParameters(); } /// Run the simulation. From 9db6d23d6d535e22867db2b4d6b8c3e16dfd9314 Mon Sep 17 00:00:00 2001 From: Svenn Tveit Date: Thu, 18 Dec 2025 16:27:36 +0100 Subject: [PATCH 14/19] Add docstring explanations --- .../tpsa/elasticitylocalresidualtpsa.hpp | 19 +++++++++++++++++++ .../tpsa/elasticityprimaryvariables.hpp | 5 +++++ 2 files changed, 24 insertions(+) diff --git a/opm/models/tpsa/elasticitylocalresidualtpsa.hpp b/opm/models/tpsa/elasticitylocalresidualtpsa.hpp index f383be94b86..bb408a0fdc7 100644 --- a/opm/models/tpsa/elasticitylocalresidualtpsa.hpp +++ b/opm/models/tpsa/elasticitylocalresidualtpsa.hpp @@ -43,6 +43,25 @@ namespace Opm { /*! * \brief Calculation of (linear) elasticity model terms for the residual +* +* The linearized Biot model is solved where it is assumed that solid mechanics are governed by Hooke's law and +* conservation of linear momentum: +* +* \f{eqnarray*}{ +* \sigma &=& 2\mu\epsilon(u) + \lambda(\nabla\cdot u)I, +* -\nabla\cdot(\sigma - \alpha(p_f - p_0)I) &=& f_u, +* \f} +* +* where \f&\sigma\f& is Cauchy stress tensor, \f&u\f& is displacement, \f&\epsilon(\cdot)\f& is thesymmetric gradient, +* \f&\mu\f& and \f&\lambda\f& are Lame's first (aka shear modulus) and second parameters, \f&\alpha\f& is the +* Biot-Willis parameter, \f&(p_f-p_0)\f& is fluid pressure difference wrt hydrostatic, and \f&f_u\f& are body forces. +* +* The equations are discretized using two-point stress approximation following Boon et al. (2025), Solving Biot +* poroelasticity by coupling OPM Flow with the two-point stress approximation finite volume method, arXiv:2510.23432v1. +* +* The resulting equations contain a volume term where only single-cell variables are used; face terms where variables +* across cell faces are calculated; boundary terms, similar to face terms, but cell faces are at the boundary; and +* source terms where coupling and potential body forces are calculated. */ template class ElasticityLocalResidual diff --git a/opm/models/tpsa/elasticityprimaryvariables.hpp b/opm/models/tpsa/elasticityprimaryvariables.hpp index 4aee5e8a8c4..944b45d3221 100644 --- a/opm/models/tpsa/elasticityprimaryvariables.hpp +++ b/opm/models/tpsa/elasticityprimaryvariables.hpp @@ -41,6 +41,11 @@ namespace Opm { /*! * \brief Primary variables in (linear) elasticity equations +* +* Primary variables are: +* \li Displacement (3D vector) +* \li Rotation (3D vector) -> variable to express rotations in stress tensor +* \li Solid pressure (single scalar) -> volumetric change from mechanics */ template class ElasticityPrimaryVariables From 276f80bab5d9bf87bac7de99148ec68a61b04c6d Mon Sep 17 00:00:00 2001 From: Svenn Tveit Date: Thu, 18 Dec 2025 16:32:03 +0100 Subject: [PATCH 15/19] Inherit AbstractISTLSolver in ISTLSolverTPSA. Template change in AbstractISTLSolver necessary to generalize class. --- opm/simulators/linalg/AbstractISTLSolver.hpp | 4 +- opm/simulators/linalg/ISTLSolver.hpp | 5 +- .../linalg/ISTLSolverRuntimeOptionProxy.hpp | 5 +- opm/simulators/linalg/ISTLSolverTPSA.hpp | 115 ++++++++++-------- .../linalg/gpuistl/ISTLSolverGPUISTL.hpp | 5 +- 5 files changed, 75 insertions(+), 59 deletions(-) diff --git a/opm/simulators/linalg/AbstractISTLSolver.hpp b/opm/simulators/linalg/AbstractISTLSolver.hpp index ba6dbee98a3..caf1ce29a6f 100644 --- a/opm/simulators/linalg/AbstractISTLSolver.hpp +++ b/opm/simulators/linalg/AbstractISTLSolver.hpp @@ -40,7 +40,7 @@ namespace Opm * directly. This would simplify the interface and reduce the number of * required method calls before solving the system. */ -template +template class AbstractISTLSolver { public: @@ -50,8 +50,6 @@ class AbstractISTLSolver using CommunicationType = Dune::Communication; #endif - using SparseMatrixAdapter = GetPropType; - using Vector = GetPropType; using Matrix = typename SparseMatrixAdapter::IstlMatrix; virtual ~AbstractISTLSolver() = default; diff --git a/opm/simulators/linalg/ISTLSolver.hpp b/opm/simulators/linalg/ISTLSolver.hpp index 7a5223f7ba8..9dc2b46b103 100644 --- a/opm/simulators/linalg/ISTLSolver.hpp +++ b/opm/simulators/linalg/ISTLSolver.hpp @@ -145,7 +145,8 @@ std::unique_ptr blockJacobiAdjacency(const Grid& grid, /// as a block-structured matrix (one block for all cell variables) for a fixed /// number of cell variables np . template - class ISTLSolver : public AbstractISTLSolver + class ISTLSolver : public AbstractISTLSolver, + GetPropType> { protected: using GridView = GetPropType; @@ -464,7 +465,7 @@ std::unique_ptr blockJacobiAdjacency(const Grid& grid, bool checkConvergence(const Dune::InverseOperatorResult& result) const { - return AbstractISTLSolver::checkConvergence(result, parameters_[activeSolverNum_]); + return AbstractISTLSolver::checkConvergence(result, parameters_[activeSolverNum_]); } bool isParallel() const { diff --git a/opm/simulators/linalg/ISTLSolverRuntimeOptionProxy.hpp b/opm/simulators/linalg/ISTLSolverRuntimeOptionProxy.hpp index 72bb0900257..898eb85fb6c 100644 --- a/opm/simulators/linalg/ISTLSolverRuntimeOptionProxy.hpp +++ b/opm/simulators/linalg/ISTLSolverRuntimeOptionProxy.hpp @@ -36,7 +36,8 @@ namespace Opm * \brief ISTLSolverRuntimeOptionProxy selects the appropriate ISTLSolver runtime based on CLI options */ template -class ISTLSolverRuntimeOptionProxy : public AbstractISTLSolver +class ISTLSolverRuntimeOptionProxy : public AbstractISTLSolver, + GetPropType> { public: using SparseMatrixAdapter = GetPropType; @@ -139,7 +140,7 @@ class ISTLSolverRuntimeOptionProxy : public AbstractISTLSolver } private: - std::unique_ptr> istlSolver_; + std::unique_ptr> istlSolver_; template diff --git a/opm/simulators/linalg/ISTLSolverTPSA.hpp b/opm/simulators/linalg/ISTLSolverTPSA.hpp index bff05ad76c9..6a945940308 100644 --- a/opm/simulators/linalg/ISTLSolverTPSA.hpp +++ b/opm/simulators/linalg/ISTLSolverTPSA.hpp @@ -35,6 +35,7 @@ #include #include +#include #include #include #include @@ -59,10 +60,11 @@ namespace Opm { /*! * \brief Class for setting up ISTL linear solvers for TPSA * -* Most of the code is copied from ISTLSolver class +* Implements AbstractISTLSolver interface for TPSA linear solvers. */ template -class ISTLSolverTPSA +class ISTLSolverTPSA : public AbstractISTLSolver, + GetPropType> { using ElementMapper = GetPropType; using Simulator = GetPropType; @@ -176,14 +178,9 @@ class ISTLSolverTPSA } /*! - * \brief Prepare matix and rhs vector for linear solve - * - * \param M System matrix - * \param b Right-hand side vector - * - * \note No setResidual() or setMatrix() functions in this class. Must be handled here! + * \copydoc AbstractISTLSolver::prepare */ - void prepare(const Matrix& M, Vector& b) + void prepare(const Matrix& M, Vector& b) override { try { initPrepare(M, b); @@ -196,20 +193,18 @@ class ISTLSolverTPSA } /*! - * \brief Prepare matix and rhs vector for linear solve - * - * \param M System matrix - * \param b Right-hand side vector - * - * \note No setResidual() or setMatrix() functions in this class. Must be handled here! + * \copydoc AbstractISTLSolver::prepare */ - void prepare(const SparseMatrixAdapter& M, Vector& b) + void prepare(const SparseMatrixAdapter& M, Vector& b) override { prepare(M.istlMatrix(), b); } /*! * \brief Prepare linear solver + * + * Create linear solver using FlexibleSolverInfo struct from ISTLSolver.hpp, or update preconditioner if solver has + * been created */ void prepareFlexibleSolver() { @@ -235,12 +230,9 @@ class ISTLSolverTPSA } /*! - * \brief Solve the linear system and store result in the input Vector x - * - * \param x Linear system solution will be stored here - * \returns Bool indicating convergence + * \copydoc AbstractISTLSolver::solve */ - bool solve(Vector& x) + bool solve(Vector& x) override { // Increase solver count ++solveCount_; @@ -270,43 +262,81 @@ class ISTLSolverTPSA /*! * \brief Reset number of solver calls to zero */ - void resetSolveCount() { + void resetSolveCount() + { solveCount_ = 0; } + // //// + // Unused AbstractISTLSolver functions + // //// + /*! + * \copydoc AbstractISTLSolver::eraseMatrix + */ + void eraseMatrix() override + { } + + /*! + * \copydoc AbstractISTLSolver::setActiveSolver + */ + void setActiveSolver(int /*num*/) override + { } + + /*! + * \copydoc AbstractISTLSolver::numAvailableSolvers + */ + int numAvailableSolvers() const override + { + return 1; + } + + /*! + * \copydoc AbstractISTLSolver::setResidual + */ + void setResidual(Vector& b) override + { } + + /*! + * \copydoc AbstractISTLSolver::setMatrix + */ + void setMatrix(const SparseMatrixAdapter& /*M*/) override + { } + // /// // Public get functions // /// /*! - * \brief Copy right-hand side vector (rhs_) to incomming vector (b) - * - * \param b Vector to copy rhs_ to + * \copydoc AbstractISTLSolver::getResidual */ - void getResidual(Vector& b) const + void getResidual(Vector& b) const override { b = *rhs_; } /*! - * \brief Get number of solver calls - * - * \returns Number of calls to linear solver + * \copydoc AbstractISTLSolver::getSolveCount */ - int getSolveCount() const + int getSolveCount() const override { return solveCount_; } /*! - * \brief Get number of linear solver iterations - * - * \returns Number of iterations + * \copydoc AbstractISTLSolver::iterations */ - int iterations () const + int iterations() const override { return iterations_; } + /*! + * \copydoc AbstractISTLSolver::comm + */ + const CommunicationType* comm() const override + { + return comm_.get(); + } + protected: /*! * \brief Check for parallel session @@ -332,22 +362,7 @@ class ISTLSolverTPSA */ bool checkConvergence(const Dune::InverseOperatorResult& result) const { - // Check relaxed linear solver tolerance - if (!result.converged && result.reduction < parameters_.relaxed_linear_solver_reduction_) { - std::string msg = fmt::format("Full linear solver tolerance not achieved. The reduction is {} " - "after {} iterations."); - OpmLog::warning(msg); - return true; - } - - // If we have failed and we don't ignore failures, throw error - if (!parameters_.ignoreConvergenceFailure_ && !result.converged) { - const std::string msg("Convergence failure for linear solver."); - OPM_THROW_NOLOG(NumericalProblem, msg); - } - - // Return convergence bool from linear solver result - return result.converged; + return AbstractISTLSolver::checkConvergence(result, parameters_); } // /// diff --git a/opm/simulators/linalg/gpuistl/ISTLSolverGPUISTL.hpp b/opm/simulators/linalg/gpuistl/ISTLSolverGPUISTL.hpp index 68c89c86e33..d4f4d012cbf 100644 --- a/opm/simulators/linalg/gpuistl/ISTLSolverGPUISTL.hpp +++ b/opm/simulators/linalg/gpuistl/ISTLSolverGPUISTL.hpp @@ -57,7 +57,8 @@ namespace Opm::gpuistl * matrices and vectors internally for computations. */ template -class ISTLSolverGPUISTL : public AbstractISTLSolver +class ISTLSolverGPUISTL : public AbstractISTLSolver, + GetPropType> { public: using SparseMatrixAdapter = GetPropType; @@ -332,7 +333,7 @@ class ISTLSolverGPUISTL : public AbstractISTLSolver private: bool checkConvergence(const Dune::InverseOperatorResult& result) const { - return AbstractISTLSolver::checkConvergence(result, m_parameters); + return AbstractISTLSolver::checkConvergence(result, m_parameters); } // Weights to make approximate pressure equations. From f33c26f688682a909035227f0c55273a5c51dc01 Mon Sep 17 00:00:00 2001 From: Svenn Tveit Date: Thu, 18 Dec 2025 16:36:30 +0100 Subject: [PATCH 16/19] Remove setupPropertyTreeTPSA. Linear solver setup used setupPropertyTree, so new function unecesary. New boolean to avoid Flow-specific linear solver presets. --- CMakeLists_files.cmake | 2 - opm/simulators/linalg/ISTLSolverTPSA.hpp | 4 +- opm/simulators/linalg/setupPropertyTree.cpp | 57 +++++---- opm/simulators/linalg/setupPropertyTree.hpp | 3 +- .../linalg/setupPropertyTreeTPSA.cpp | 115 ------------------ .../linalg/setupPropertyTreeTPSA.hpp | 39 ------ 6 files changed, 38 insertions(+), 182 deletions(-) delete mode 100644 opm/simulators/linalg/setupPropertyTreeTPSA.cpp delete mode 100644 opm/simulators/linalg/setupPropertyTreeTPSA.hpp diff --git a/CMakeLists_files.cmake b/CMakeLists_files.cmake index f5466183db9..e92acc240c3 100644 --- a/CMakeLists_files.cmake +++ b/CMakeLists_files.cmake @@ -185,7 +185,6 @@ list (APPEND MAIN_SOURCE_FILES opm/simulators/linalg/PreconditionerFactory7.cpp opm/simulators/linalg/PropertyTree.cpp opm/simulators/linalg/setupPropertyTree.cpp - opm/simulators/linalg/setupPropertyTreeTPSA.cpp opm/simulators/linalg/TPSALinearSolverParameters.cpp opm/simulators/timestepping/AdaptiveSimulatorTimer.cpp opm/simulators/timestepping/AdaptiveTimeStepping.cpp @@ -1126,7 +1125,6 @@ list (APPEND PUBLIC_HEADER_FILES opm/simulators/linalg/residreductioncriterion.hh opm/simulators/linalg/SmallDenseMatrixUtils.hpp opm/simulators/linalg/setupPropertyTree.hpp - opm/simulators/linalg/setupPropertyTreeTPSA.hpp opm/simulators/linalg/superlubackend.hh opm/simulators/linalg/TPSALinearSolverParameters.hpp opm/simulators/linalg/twolevelmethodcpr.hh diff --git a/opm/simulators/linalg/ISTLSolverTPSA.hpp b/opm/simulators/linalg/ISTLSolverTPSA.hpp index 6a945940308..b720a570268 100644 --- a/opm/simulators/linalg/ISTLSolverTPSA.hpp +++ b/opm/simulators/linalg/ISTLSolverTPSA.hpp @@ -39,7 +39,7 @@ #include #include #include -#include +#include #include #include @@ -114,7 +114,7 @@ class ISTLSolverTPSA : public AbstractISTLSolver #include +#include namespace Opm { @@ -181,7 +182,8 @@ namespace PropertyTree setupPropertyTree(FlowLinearSolverParameters p, // Note: copying the parameters to potentially override. bool linearSolverMaxIterSet, - bool linearSolverReductionSet) + bool linearSolverReductionSet, + bool tpsaSetup) { std::string conf = p.linsolver_; @@ -208,28 +210,30 @@ setupPropertyTree(FlowLinearSolverParameters p, // Note: copying the parameters std::transform(conf.begin(), conf.end(), conf.begin(), ::tolower); // Use CPR configuration. - if ((conf == "cpr_trueimpes") || (conf == "cpr_quasiimpes") || (conf == "cpr_trueimpesanalytic")) { - if (!linearSolverMaxIterSet) { - // Use our own default unless it was explicitly overridden by user. - p.linear_solver_maxiter_ = 20; + if (!tpsaSetup) { + if ((conf == "cpr_trueimpes") || (conf == "cpr_quasiimpes") || (conf == "cpr_trueimpesanalytic")) { + if (!linearSolverMaxIterSet) { + // Use our own default unless it was explicitly overridden by user. + p.linear_solver_maxiter_ = 20; + } + if (!linearSolverReductionSet) { + // Use our own default unless it was explicitly overridden by user. + p.linear_solver_reduction_ = 0.005; + } + return setupCPR(conf, p); } - if (!linearSolverReductionSet) { - // Use our own default unless it was explicitly overridden by user. - p.linear_solver_reduction_ = 0.005; - } - return setupCPR(conf, p); - } - if ((conf == "cpr") || (conf == "cprw")) { - if (!linearSolverMaxIterSet) { - // Use our own default unless it was explicitly overridden by user. - p.linear_solver_maxiter_ = 20; + if ((conf == "cpr") || (conf == "cprw")) { + if (!linearSolverMaxIterSet) { + // Use our own default unless it was explicitly overridden by user. + p.linear_solver_maxiter_ = 20; + } + if (!linearSolverReductionSet) { + // Use our own default unless it was explicitly overridden by user. + p.linear_solver_reduction_ = 0.005; + } + return setupCPRW(conf, p); } - if (!linearSolverReductionSet) { - // Use our own default unless it was explicitly overridden by user. - p.linear_solver_reduction_ = 0.005; - } - return setupCPRW(conf, p); } if (conf == "amg") { @@ -268,9 +272,16 @@ setupPropertyTree(FlowLinearSolverParameters p, // Note: copying the parameters } // No valid configuration option found. - OPM_THROW(std::invalid_argument, - conf + " is not a valid setting for --linear-solver-configuration." - " Please use ilu0, dilu, cpr, cprw, cpr_trueimpes, cpr_quasiimpes, cpr_trueimpesanalytic or isai"); + if (tpsaSetup) { + OPM_THROW(std::invalid_argument, + fmt::format("No valid settings found for --tpsa-linear-solver={}! " + "Valid preset options are: ilu0, dilu, amg, or umfpack.", conf)); + } + else { + OPM_THROW(std::invalid_argument, + conf + " is not a valid setting for --linear-solver-configuration." + " Please use ilu0, dilu, cpr, cprw, cpr_trueimpes, cpr_quasiimpes, cpr_trueimpesanalytic or isai"); + } } std::string getSolverString(const FlowLinearSolverParameters& p) diff --git a/opm/simulators/linalg/setupPropertyTree.hpp b/opm/simulators/linalg/setupPropertyTree.hpp index 2cb1cc20f22..a424a573259 100644 --- a/opm/simulators/linalg/setupPropertyTree.hpp +++ b/opm/simulators/linalg/setupPropertyTree.hpp @@ -31,7 +31,8 @@ struct FlowLinearSolverParameters; PropertyTree setupPropertyTree(FlowLinearSolverParameters p, bool linearSolverMaxIterSet, - bool linearSolverReductionSet); + bool linearSolverReductionSet, + bool tpsaSetup = false); PropertyTree setupCPRW(const std::string& conf, const FlowLinearSolverParameters& p); PropertyTree setupCPR(const std::string& conf, const FlowLinearSolverParameters& p); diff --git a/opm/simulators/linalg/setupPropertyTreeTPSA.cpp b/opm/simulators/linalg/setupPropertyTreeTPSA.cpp deleted file mode 100644 index 9cd94baf4f9..00000000000 --- a/opm/simulators/linalg/setupPropertyTreeTPSA.cpp +++ /dev/null @@ -1,115 +0,0 @@ -// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- -// vi: set et ts=4 sw=4 sts=4: -/* - Copyright 2025 NORCE AS - - This file is part of the Open Porous Media project (OPM). - - OPM is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 2 of the License, or - (at your option) any later version. - - OPM is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with OPM. If not, see . - - Consult the COPYING file in the top-level source directory of this - module for the precise wording of the license and the list of - copyright holders. -*/ -#include "config.h" - -#include -#include - -#include -#include -#include - -#include -#include - -#include -#include - - -namespace Opm { - -/*! -* \brief Setup linear solver property tree based on runtime/default parameters -* -* \param p Runtime/default parameters -* -* \note Same as setupPropertyTree() for Flow, but Flow specific linear solver variants like cpr_*** have been removed. -*/ -PropertyTree setupPropertyTreeTPSA(TpsaLinearSolverParameters p) -{ - // Get linear solver - std::string conf = p.linsolver_; - - // Get configuration from JSON file - if (conf.size() > 5 && conf.substr(conf.size() - 5, 5) == ".json") { -#if BOOST_VERSION / 100 % 1000 > 48 - if ( !std::filesystem::exists(conf) ) { - std::string msg = fmt::format("TPSA: JSON file {} in --tpsa-linear-solver does not exist!", conf); - OpmLog::error(msg); - OPM_THROW(std::invalid_argument, msg); - } - try { - return PropertyTree(conf); - } - catch (...) { - std::string msg = fmt::format("TPSA: Failed reading linear solver configuration from JSON file {}", conf); - OpmLog::error(msg); - OPM_THROW(std::invalid_argument, msg); - } -#else - std::string msg = fmt::format("TPSA: --tpsa-linear-solver with JSON file (={}) not supported with boost " - "version <= 1.48!", conf); - OpmLog::error(msg); - OPM_THROW(std::invalid_argument, msg); -#endif - } - - // We use lower case as the internal canonical representation of solver names - std::transform(conf.begin(), conf.end(), conf.begin(), ::tolower); - - // Standard AMG - if (conf == "amg") { - return setupAMG(conf, p); - } - - // ILU setups - if (conf == "ilu0") { - return setupILU(conf, p); - } - if (conf == "dilu") { - return setupDILU(conf, p); - } - - // UMFPACK direct solver - if (conf == "umfpack") { - return setupUMFPack(conf, p); - } - - // At this point, the only separate ISAI implementation is with the OpenCL code, and - // it will check this argument to see if it should be using ISAI. The parameter tree - // will be ignored, so this is just a dummy configuration to avoid the throw below. - // If we are using CPU dune-istl solvers, this will just make "isai" an alias of "ilu". - if (conf == "isai") { - return setupILU(conf, p); - } - - // No valid configuration option found. - std::string msg = fmt::format("No valid settings found for --tpsa-linear-solver={}! " - "Valid preset options are: ilu0, dilu, amg, or umfpack.", conf); - OpmLog::error(msg); - OPM_THROW(std::invalid_argument, msg); -} - -} // namespace Opm diff --git a/opm/simulators/linalg/setupPropertyTreeTPSA.hpp b/opm/simulators/linalg/setupPropertyTreeTPSA.hpp deleted file mode 100644 index c470d6e9e9c..00000000000 --- a/opm/simulators/linalg/setupPropertyTreeTPSA.hpp +++ /dev/null @@ -1,39 +0,0 @@ -// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- -// vi: set et ts=4 sw=4 sts=4: -/* - Copyright 2025 NORCE AS - - This file is part of the Open Porous Media project (OPM). - - OPM is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 2 of the License, or - (at your option) any later version. - - OPM is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with OPM. If not, see . - - Consult the COPYING file in the top-level source directory of this - module for the precise wording of the license and the list of - copyright holders. -*/ -#ifndef SETUP_PROPERTY_TREE_TSA_HPP -#define SETUP_PROPERTY_TREE_TSA_HPP - -#include - - -namespace Opm { - -struct TpsaLinearSolverParameters; - -PropertyTree setupPropertyTreeTPSA(TpsaLinearSolverParameters p); - -} // namespace Opm - -#endif From fe7b24aed1f7268bf6d2c57fe482d7a9adf48e8e Mon Sep 17 00:00:00 2001 From: Svenn Tveit Date: Thu, 18 Dec 2025 17:07:59 +0100 Subject: [PATCH 17/19] Major clean up of TPSA Newton method. - Removed unnecessary (null) convergence writer - Removed constraints - Removed unused/empty functions - Removed auxiliary module calculations - Added verbosity level with runtime parameter --- CMakeLists_files.cmake | 1 - opm/models/tpsa/tpsabaseproperties.hpp | 9 - opm/models/tpsa/tpsamodel.hpp | 3 - .../tpsa/tpsanewtonconvergencewriter.hpp | 91 ------ opm/models/tpsa/tpsanewtonmethod.hpp | 258 +++--------------- opm/models/tpsa/tpsanewtonmethodparams.cpp | 9 +- opm/models/tpsa/tpsanewtonmethodparams.hpp | 9 +- opm/simulators/flow/TTagFlowProblemTPSA.hpp | 10 - 8 files changed, 37 insertions(+), 353 deletions(-) delete mode 100644 opm/models/tpsa/tpsanewtonconvergencewriter.hpp diff --git a/CMakeLists_files.cmake b/CMakeLists_files.cmake index e92acc240c3..7b8ab1140ae 100644 --- a/CMakeLists_files.cmake +++ b/CMakeLists_files.cmake @@ -926,7 +926,6 @@ list (APPEND PUBLIC_HEADER_FILES opm/models/tpsa/elasticityprimaryvariables.hpp opm/models/tpsa/tpsabaseproperties.hpp opm/models/tpsa/tpsamodel.hpp - opm/models/tpsa/tpsanewtonconvergencewriter.hpp opm/models/tpsa/tpsanewtonmethod.hpp opm/models/tpsa/tpsanewtonmethodparams.hpp opm/models/utils/alignedallocator.hh diff --git a/opm/models/tpsa/tpsabaseproperties.hpp b/opm/models/tpsa/tpsabaseproperties.hpp index af81450df4d..12abd3e3fbc 100644 --- a/opm/models/tpsa/tpsabaseproperties.hpp +++ b/opm/models/tpsa/tpsabaseproperties.hpp @@ -69,15 +69,6 @@ struct SparseMatrixAdapterTPSA { using type = UndefinedProperty; }; template struct NewtonMethodTPSA { using type = UndefinedProperty; }; -template -struct NewtonConvergenceWriterTPSA { using type = UndefinedProperty; }; - -template -struct ConvergenceWriterTPSA { using type = UndefinedProperty; }; - -template -struct EnableConstraintsTPSA { using type = UndefinedProperty; }; - template struct LinearSolverBackendTPSA { using type = UndefinedProperty; }; diff --git a/opm/models/tpsa/tpsamodel.hpp b/opm/models/tpsa/tpsamodel.hpp index 9cd32280f46..c2272927cc9 100644 --- a/opm/models/tpsa/tpsamodel.hpp +++ b/opm/models/tpsa/tpsamodel.hpp @@ -184,9 +184,6 @@ class TpsaModel // Initialize the linearizer linearizer_->init(simulator_); - // Initialize the newton method - newtonMethod_.finishInit(); - // Resize material state vector resizeMaterialState_(); } diff --git a/opm/models/tpsa/tpsanewtonconvergencewriter.hpp b/opm/models/tpsa/tpsanewtonconvergencewriter.hpp deleted file mode 100644 index 6ce7a130c51..00000000000 --- a/opm/models/tpsa/tpsanewtonconvergencewriter.hpp +++ /dev/null @@ -1,91 +0,0 @@ -// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- -// vi: set et ts=4 sw=4 sts=4: -/* - Copyright 2025 NORCE AS - - This file is part of the Open Porous Media project (OPM). - - OPM is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 2 of the License, or - (at your option) any later version. - - OPM is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with OPM. If not, see . - - Consult the COPYING file in the top-level source directory of this - module for the precise wording of the license and the list of - copyright holders. -*/ -#ifndef TPSA_NEWTON_CONVERGENCE_WRITER_HPP -#define TPSA_NEWTON_CONVERGENCE_WRITER_HPP - -#include - -namespace Opm { - -/*! -* \brief Write convergence info for TPSA Newton method -*/ -template -class TpsaNewtonConvergenceWriter -{ - using NewtonMethod = GetPropType; - - using SolutionVector = GetPropType; - using GlobalEqVector = GetPropType; - -public: - /*! - * \brief Constructor - */ - explicit TpsaNewtonConvergenceWriter() - { } - - /*! - * \brief Called by the Newton method before the actual algorithm is started for any given timestep. - */ - void beginTimeStep() - { } - - /*! - * \brief Called by the Newton method before an iteration of the Newton algorithm is started. - */ - void beginIteration() - { } - - /*! - * \brief Write the Newton update to disk. - * - * \param uLastIter The solution vector of the previous iteration. - * \param deltaU The negative difference between the solution vectors of the previous and the current iteration. - * - * Called after the linear solution is found for an iteration. - */ - void writeFields(const SolutionVector& /*uLastIter*/, - const GlobalEqVector& /*deltaU*/) - { } - - /*! - * \brief Called by the Newton method after an iteration of the Newton algorithm has been completed. - */ - void endIteration() - { } - - /*! - * \brief Called by the Newton method after Newton algorithm has been completed for any given timestep. - * - * This method is called regardless of whether the Newton method converged or not. - */ - void endTimeStep() - { } -}; // class TpsaNewtonConvergenceWriter - -} // namespace Opm - -#endif diff --git a/opm/models/tpsa/tpsanewtonmethod.hpp b/opm/models/tpsa/tpsanewtonmethod.hpp index 5587a306bd4..387838a5c21 100644 --- a/opm/models/tpsa/tpsanewtonmethod.hpp +++ b/opm/models/tpsa/tpsanewtonmethod.hpp @@ -64,11 +64,9 @@ class TpsaNewtonMethod using SolutionVector = GetPropType; using GlobalEqVector = GetPropType; using PrimaryVariables = GetPropType; - using Constraints = GetPropType; using EqVector = GetPropType; using Linearizer = GetPropType; using LinearSolverBackend = GetPropType; - using ConvergenceWriter = GetPropType; using SparseMatrixAdapter = GetPropType; using IstlMatrix = typename SparseMatrixAdapter::IstlMatrix; @@ -86,7 +84,6 @@ class TpsaNewtonMethod , lastError_(1e100) , numIterations_(0) , numLinearizations_(0) - , convergenceWriter_() { // Read runtime/default Newton parameters params_.read(); @@ -101,15 +98,6 @@ class TpsaNewtonMethod TpsaNewtonMethodParams::registerParameters(); } - /*! - * \brief Finialize the construction of the object. - * - * At this point, it can be assumed that all objects featured by the simulator have been allocated. (But not that - * they have been fully initialized yet.) - */ - void finishInit() - { } - /*! * \brief Run the Newton method. * @@ -134,7 +122,7 @@ class TpsaNewtonMethod // Tell the implementation that we begin solving prePostProcessTimer_.start(); - begin_(nextSolution); + begin_(); prePostProcessTimer_.stop(); try { @@ -156,7 +144,6 @@ class TpsaNewtonMethod // Do the actual linearization linearizeTimer_.start(); linearizeDomain_(); - linearizeAuxiliaryEquations_(); linearizeTimer_.stop(); // Get residual and Jacobian for convergence check and preparation of linear solver @@ -177,7 +164,7 @@ class TpsaNewtonMethod if (converged()) { // Tell the implementation that we're done with this iteration prePostProcessTimer_.start(); - endIteration_(nextSolution, currentSolution); + endIteration_(); prePostProcessTimer_.stop(); break; @@ -191,7 +178,7 @@ class TpsaNewtonMethod if (!conv) { solveTimer_.stop(); - if (verbose_()) { + if (verbosity_() > 0) { std::cout << "TPSA: Linear solver did not converge!" << std::endl; } @@ -204,19 +191,18 @@ class TpsaNewtonMethod // Update the current solution with the delta updateTimer_.start(); - postSolve_(currentSolution, residual, solutionUpdate); update_(nextSolution, currentSolution, solutionUpdate, residual); updateTimer_.stop(); // End of iteration calculations prePostProcessTimer_.start(); - endIteration_(nextSolution, currentSolution); + endIteration_(); prePostProcessTimer_.stop(); } } catch (const Dune::Exception& e) { - if (verbose_()) { + if (verbosity_() > 0) { std::cout << "TPSA: Newton method caught exception: \"" << e.what() << "\"\n" << std::flush; } @@ -229,7 +215,7 @@ class TpsaNewtonMethod } catch (const NumericalProblem& e) { - if (verbose_()) { + if (verbosity_() > 0) { std::cout << "TPSA: Newton method caught exception: \"" << e.what() << "\"\n" << std::flush; } @@ -241,13 +227,8 @@ class TpsaNewtonMethod return false; } - // Tell the implementation that we're done - prePostProcessTimer_.start(); - end_(); - prePostProcessTimer_.stop(); - // print the timing summary of the time step - if (verbose_()) { + if (verbosity_() > 0) { Scalar elapsedTot = linearizeTimer_.realTimeElapsed() + solveTimer_.realTimeElapsed() + @@ -274,42 +255,15 @@ class TpsaNewtonMethod if (!converged()) { prePostProcessTimer_.start(); failed_(); - std::cout << "TPSA: Newton iterations did not converge!" << std::endl; + if (verbosity_() > 0) { + std::cout << "TPSA: Newton iterations did not converge!" << std::endl; + } prePostProcessTimer_.stop(); return false; } - - // if we converged, tell the implementation that we've succeeded - prePostProcessTimer_.start(); - succeeded_(); - prePostProcessTimer_.stop(); - return true; } - /*! - * \brief Set the index of current iteration. - * - * \param value New iteration index - * - * Normally this does not need to be called, but if the non-linear solver is implemented externally, it needs to be - * set in order for the model to do the Right Thing (TM) while linearizing. - */ - void setIterationIndex(int value) - { - numIterations_ = value; - } - - /*! - * \brief Set the current tolerance at which the Newton method considers itself to be converged. - * - * \param value New tolerance value - */ - void setTolerance(Scalar value) - { - params_.tolerance_ = value; - } - /*! * \brief Returns true if the error of the solution is below the tolerance. * @@ -432,43 +386,31 @@ class TpsaNewtonMethod protected: /*! - * \brief Returns true if the Newton method ought to be chatty. + * \brief Verbosity level of Newton print messages + * + * \returns Level indicating verbosity * - * \returns Bool indicating if the Newton procedure should be verbose + * Newton procedure verbosity: 0=none, 1=basic, 2=all */ - bool verbose_() const - { return params_.verbose_ && (simulator_.gridView().comm().rank() == 0); } + int verbosity_() const + { return simulator_.gridView().comm().rank() == 0 ? params_.verbosity_ : 0; } /*! * \brief Called before the Newton method is applied to an non-linear system of equations. - * - * \param nextSolution The initial solution vector */ - void begin_(const SolutionVector& /*nextSolution*/) + void begin_() { numIterations_ = 0; numLinearizations_ = 0; error_ = 1e100; - - if (params_.writeConvergence_) { - convergenceWriter_.beginTimeStep(); - } } /*! - * \brief Indicates the beginning of a Newton iteration. + * \brief Calculations at the beginning of a Newton iteration */ void beginIteration_() { - // start with a clean message stream - const auto& comm = simulator_.gridView().comm(); - bool succeeded = true; - succeeded = comm.min(succeeded); - - if (!succeeded) { - throw NumericalProblem("TPSA: Pre-processing of the problem failed!"); - } - + // Reset last Newton error to previous lastError_ = error_; } @@ -481,15 +423,6 @@ class TpsaNewtonMethod ++numLinearizations_; } - /*! - * \brief Linearize auxillary equations - */ - void linearizeAuxiliaryEquations_() - { - model().linearizer().linearizeAuxiliaryEquations(); - model().linearizer().finalize(); - } - /*! * \brief Compute error before a Newton iteration * @@ -499,7 +432,6 @@ class TpsaNewtonMethod void preSolve_(const SolutionVector& /*currentSolution*/, const GlobalEqVector& currentResidual) { - const auto& constraintsMap = model().linearizer().constraintsMap(); lastError_ = error_; Scalar newtonMaxError = params_.maxError_; @@ -508,19 +440,6 @@ class TpsaNewtonMethod const auto& elemMapper = simulator_.model().elementMapper(); for (const auto& elem : elements(simulator_.gridView(), Dune::Partitions::interior)) { unsigned dofIdx = elemMapper.index(elem); - - // Do not consider auxiliary DOFs for the error - if (dofIdx >= model().numGridDof() || model().dofTotalVolume(dofIdx) <= 0.0) { - continue; - } - - // Also do not consider DOFs which are constraint - if (enableConstraints_()) { - if (constraintsMap.count(dofIdx) > 0) { - continue; - } - } - const auto& r = currentResidual[dofIdx]; for (unsigned eqIdx = 0; eqIdx < r.size(); ++eqIdx) { error_ = max(std::abs(r[eqIdx] * model().eqWeight(dofIdx, eqIdx)), error_); @@ -538,20 +457,6 @@ class TpsaNewtonMethod } } - /*! - * \brief Update the error of the solution given the previous iteration. - * - * \param currentSolution The solution at the beginning the current iteration - * \param currentResidual The residual (i.e., right-hand-side) of the current iteration's solution. - * \param solutionUpdate The difference between the current and the next solution - * - * For our purposes, the error of a solution is defined as the maximum of the weighted residual of a given solution. - */ - void postSolve_(const SolutionVector& /*currentSolution*/, - const GlobalEqVector& /*currentResidual*/, - GlobalEqVector& /*solutionUpdate*/) - { } - /*! * \brief Update the current solution with a delta vector. * @@ -568,12 +473,6 @@ class TpsaNewtonMethod const GlobalEqVector& solutionUpdate, const GlobalEqVector& currentResidual) { - const auto& constraintsMap = model().linearizer().constraintsMap(); - - // first, write out the current solution to make convergence - // analysis possible - writeConvergence_(currentSolution, solutionUpdate); - // make sure not to swallow non-finite values at this point if (!std::isfinite(solutionUpdate.one_norm())) { throw NumericalProblem("TPSA: Non-finite update in Newton!"); @@ -581,54 +480,17 @@ class TpsaNewtonMethod std::size_t numGridDof = model().numGridDof(); for (unsigned dofIdx = 0; dofIdx < numGridDof; ++dofIdx) { - if (enableConstraints_()) { - if (constraintsMap.count(dofIdx) > 0) { - const auto& constraints = constraintsMap.at(dofIdx); - updateConstraintDof_(dofIdx, - nextSolution[dofIdx], - constraints); - } - else { - updatePrimaryVariables_(dofIdx, - nextSolution[dofIdx], - currentSolution[dofIdx], - solutionUpdate[dofIdx], - currentResidual[dofIdx]); - } - } - else { - updatePrimaryVariables_(dofIdx, - nextSolution[dofIdx], - currentSolution[dofIdx], - solutionUpdate[dofIdx], - currentResidual[dofIdx]); - } + updatePrimaryVariables_(dofIdx, + nextSolution[dofIdx], + currentSolution[dofIdx], + solutionUpdate[dofIdx], + currentResidual[dofIdx]); } - // update the DOFs of the auxiliary equations - // std::size_t numDof = model().numTotalDof(); - // for (std::size_t dofIdx = numGridDof; dofIdx < numDof; ++dofIdx) { - // nextSolution[dofIdx] = currentSolution[dofIdx]; - // nextSolution[dofIdx] -= solutionUpdate[dofIdx]; - // } - // Update material state model().updateMaterialState(/*timeIdx=*/0); } - /*! - * \brief Update the primary variables for a degree of freedom which is constraint. - * - * \param dofIdx Degree-of-freedom index - * \param nextValue The solution vector after the current iteration - * \param constraints Constraints for solution - * - */ - void updateConstraintDof_(unsigned /*dofIdx*/, - PrimaryVariables& /*nextValue*/, - const Constraints& /*constraints*/) - { } - /*! * \brief Update a single primary variables object. * @@ -647,48 +509,20 @@ class TpsaNewtonMethod nextValue -= update; } - /*! - * \brief Write the convergence behaviour of the newton method to disk. - * - * \param currentSolution The solution vector after the last iteration - * \param solutionUpdate The delta vector as calculated by solving the linear system of equations - * - * This method is called as part of the update proceedure. - */ - void writeConvergence_(const SolutionVector& currentSolution, - const GlobalEqVector& solutionUpdate) - { - if (params_.writeConvergence_) { - convergenceWriter_.beginIteration(); - convergenceWriter_.writeFields(currentSolution, solutionUpdate); - convergenceWriter_.endIteration(); - } - } - /*! * \brief Indicates that one Newton iteration was finished. - * - * \param nextSolution The solution after the current Newton iteration - * \param currentSolution The solution at the beginning of the current Newton iteration */ - void endIteration_(const SolutionVector& /*nextSolution*/, - const SolutionVector& /*currentSolution*/) + void endIteration_() { + // Increase Newton iterations ++numIterations_; - const auto& comm = simulator_.gridView().comm(); - bool succeeded = true; - succeeded = comm.min(succeeded); - - if (!succeeded) { - throw NumericalProblem("TPSA: Post-processing of the problem failed!"); + // Output error info + if (verbosity_() > 1) { + std::cout << "TPSA: End Newton iteration " << numIterations_ << "" + << " with error = " << error_ + << std::endl; } - - // if (verbose_()) { - // std::cout << "TPSA: End Newton iteration " << numIterations_ << "" - // << " with error = " << error_ - // << std::endl; - // } } /*! @@ -717,40 +551,12 @@ class TpsaNewtonMethod return true; } - /*! - * \brief Indicates that we're done solving the non-linear system of equations. - */ - void end_() - { - if (params_.writeConvergence_) { - convergenceWriter_.endTimeStep(); - } - } - /*! * \brief Called if the Newton method broke down. - * - * \note This method is called _after_ end_() */ void failed_() { numIterations_ = params_.targetIterations_ * 2; } - /*! - * \brief Called if the Newton method was successful. - * - * \note This method is called _after_ end_() - */ - void succeeded_() - { } - - /*! - * \brief Check if constraints are enabled - * - * \returns Bool indicating if constraints are enabled - */ - static bool enableConstraints_() - { return getPropValue(); } - Simulator& simulator_; LinearSolverBackend linearSolver_; @@ -765,8 +571,6 @@ class TpsaNewtonMethod int numIterations_; int numLinearizations_; - - ConvergenceWriter convergenceWriter_; }; // class TpsaNewtonMethod } // namespace Opm diff --git a/opm/models/tpsa/tpsanewtonmethodparams.cpp b/opm/models/tpsa/tpsanewtonmethodparams.cpp index a74758fbd42..789d796d605 100644 --- a/opm/models/tpsa/tpsanewtonmethodparams.cpp +++ b/opm/models/tpsa/tpsanewtonmethodparams.cpp @@ -38,10 +38,8 @@ namespace Opm { template void TpsaNewtonMethodParams::registerParameters() { - Parameters::Register - ("Specify whether the TPSA Newton method should inform the user about its progress or not"); - Parameters::Register - ("Write the convergence behaviour of the TPSA Newton method to a VTK file"); + Parameters::Register + ("Verbosity level of TPSA Newton solver: 0 (=none), 1 (=basic), 2 (=all)"); Parameters::Register ("The 'optimum' number of TPSA Newton iterations"); Parameters::Register @@ -60,8 +58,7 @@ void TpsaNewtonMethodParams::registerParameters() template void TpsaNewtonMethodParams::read() { - verbose_ = Parameters::Get(); - writeConvergence_ = Parameters::Get(); + verbosity_ = Parameters::Get(); targetIterations_ = Parameters::Get(); minIterations_ = Parameters::Get(); maxIterations_ = Parameters::Get(); diff --git a/opm/models/tpsa/tpsanewtonmethodparams.hpp b/opm/models/tpsa/tpsanewtonmethodparams.hpp index ea4638a423f..9885b86bd05 100644 --- a/opm/models/tpsa/tpsanewtonmethodparams.hpp +++ b/opm/models/tpsa/tpsanewtonmethodparams.hpp @@ -45,11 +45,8 @@ struct TpsaNewtonTargetIterations { static constexpr int value = 10; }; template struct TpsaNewtonTolerance { static constexpr Scalar value = 1e-3; }; -// Specifies whether the Newton method should print messages or not -struct TpsaNewtonVerbose { static constexpr bool value = true; }; - -// Specifies whether the convergence rate and the global residual gets written out to disk for every Newton iteration -struct TpsaNewtonWriteConvergence { static constexpr bool value = false; }; +// Specifies verbosity of print messages +struct TpsaNewtonVerbosity { static constexpr int value = 1; }; } // namespace Opm::Parameters @@ -64,7 +61,7 @@ struct TpsaNewtonMethodParams static void registerParameters(); void read(); - bool verbose_; + int verbosity_; bool writeConvergence_; int targetIterations_; int minIterations_; diff --git a/opm/simulators/flow/TTagFlowProblemTPSA.hpp b/opm/simulators/flow/TTagFlowProblemTPSA.hpp index 77086e583c4..45eed41f99e 100644 --- a/opm/simulators/flow/TTagFlowProblemTPSA.hpp +++ b/opm/simulators/flow/TTagFlowProblemTPSA.hpp @@ -36,7 +36,6 @@ #include #include #include -#include #include #include @@ -102,10 +101,6 @@ template struct NewtonMethodTPSA { using type = TpsaNewtonMethod; }; -template -struct NewtonConvergenceWriterTPSA -{ using type = TpsaNewtonConvergenceWriter; }; - // TPSA primary variables template struct PrimaryVariablesTPSA @@ -145,11 +140,6 @@ struct SparseMatrixAdapterTPSA }; -// Disable constraints in Newton method -template -struct EnableConstraintsTPSA -{ static constexpr bool value = false; }; - // Set linear solver backend template struct LinearSolverBackendTPSA From d95ec010eac303f9d01617d7dc3917f219a53b78 Mon Sep 17 00:00:00 2001 From: Svenn Tveit Date: Fri, 19 Dec 2025 11:11:21 +0100 Subject: [PATCH 18/19] Throw for DUNE grids other than CpGrid --- opm/simulators/flow/FacePropertiesTPSA_impl.hpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/opm/simulators/flow/FacePropertiesTPSA_impl.hpp b/opm/simulators/flow/FacePropertiesTPSA_impl.hpp index fd9871b7e4c..2fb905adcbf 100644 --- a/opm/simulators/flow/FacePropertiesTPSA_impl.hpp +++ b/opm/simulators/flow/FacePropertiesTPSA_impl.hpp @@ -398,6 +398,8 @@ computeDistance_(const DimVector& distVec, const DimVector& faceNormal) * \param inside Info describing inside face * \param outside Info describing outside face * \param faceNormal Face (unit) normal vector +* +* \warning Not implemented! */ template template @@ -408,12 +410,7 @@ computeCellProperties(const Intersection& intersection, DimVector& faceNormal, /*isCpGrid=*/std::false_type) const { - // default implementation for DUNE grids - const auto& geometry = intersection.geometry(); - outside.faceCenter = inside.faceCenter = geometry.center(); - - // OBS: Have not checked if this points from cell with lower to higher index! - faceNormal = intersection.centerUnitOuterNormal(); + throw std::runtime_error("TPSA not implemented for DUNE grid types other than CpGrid!"); } /*! From 3556b43e06d6c5aecfa1e6c58c341b1c9aa28c4f Mon Sep 17 00:00:00 2001 From: Svenn Tveit Date: Fri, 19 Dec 2025 13:55:39 +0100 Subject: [PATCH 19/19] Fix unused parameter warning --- opm/simulators/linalg/ISTLSolverTPSA.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opm/simulators/linalg/ISTLSolverTPSA.hpp b/opm/simulators/linalg/ISTLSolverTPSA.hpp index b720a570268..e3e86094a0b 100644 --- a/opm/simulators/linalg/ISTLSolverTPSA.hpp +++ b/opm/simulators/linalg/ISTLSolverTPSA.hpp @@ -293,7 +293,7 @@ class ISTLSolverTPSA : public AbstractISTLSolver