diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 539befdf706..60f40f7beb8 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -15,25 +15,25 @@ /inputFiles/hydraulicFracturing @rrsettgast @frankfeifan @cssherman @jhuang2601 @kimtaeho07 @OmarDuran /inputFiles/inducedSeismicity @jhuang2601 @frankfeifan @kimtaeho07 /inputFiles/initialization @rrsettgast @jhuang2601 @OmarDuran -/inputFiles/lagrangianContactMechanics @jhuang2601 @jafranc @joshua-white @castelletto1 +/inputFiles/lagrangianContactMechanics @jhuang2601 @jafranc @joshua-white /inputFiles/materialPointMethod @cmcrook5 @homel1 /inputFiles/meshGeneration @rrsettgast @cssherman @jhuang2601 @OmarDuran /inputFiles/multiphaseFlowFractures @jhuang2601 @tjb-ltk @joshua-white /inputFiles/multipleMeshBodies @rrsettgast @OmarDuran @bd713 /inputFiles/phaseField @rrsettgast @frankfeifan @jhuang2601 -/inputFiles/poromechanics @castelletto1 @jhuang2601 @joshua-white -/inputFiles/poromechanicsFractures @castelletto1 @jhuang2601 @joshua-white +/inputFiles/poromechanics @jhuang2601 @joshua-white +/inputFiles/poromechanicsFractures @jhuang2601 @joshua-white /inputFiles/proppant @rrsettgast @jhuang2601 @OmarDuran /inputFiles/relpermDriver @dkachuma @jafranc @joshua-white -/inputFiles/simplePDE @rrsettgast @castelletto1 @OmarDuran @bd713 -/inputFiles/singlePhaseFlow @castelletto1 @jhuang2601 @dkachuma +/inputFiles/simplePDE @rrsettgast @OmarDuran @bd713 +/inputFiles/singlePhaseFlow @jhuang2601 @dkachuma /inputFiles/singlePhaseFlowFractures @jhuang2601 @dkachuma @joshua-white /inputFiles/singlePhaseWell @tjb-ltk @dkachuma @joshua-white -/inputFiles/solidMechanics @castelletto1 @jhuang2601 @OmarDuran +/inputFiles/solidMechanics @jhuang2601 @OmarDuran /inputFiles/surfaceGeneration @rrsettgast @OmarDuran @bd713 /inputFiles/thermalMultiphaseFlow @dkachuma @tjb-ltk @joshua-white /inputFiles/thermalSinglePhaseFlowFractures @jhuang2601 @frankfeifan @dkachuma -/inputFiles/thermoPoromechanics @jhuang2601 @castelletto1 @frankfeifan +/inputFiles/thermoPoromechanics @jhuang2601 @frankfeifan /inputFiles/thermoPoromechanicsFractures @jhuang2601 @jafranc @joshua-white /inputFiles/triaxialDriver @rrsettgast @jhuang2601 @joshua-white @OmarDuran /inputFiles/wavePropagation @acitrain @sframba @bd713 @jhuang2601 @@ -57,15 +57,15 @@ /src/coreComponents/constitutive/unitTests @rrsettgast @dkachuma @jhuang2601 @joshua-white /src/coreComponents/constitutiveDrivers @rrsettgast @dkachuma @jhuang2601 @jafranc @joshua-white /src/coreComponents/dataRepository @rrsettgast @wrtobin @corbett5 @MelReyCG @OmarDuran @bd713 -/src/coreComponents/denseLinearAlgebra @rrsettgast @castelletto1 @OmarDuran @bd713 -/src/coreComponents/discretizationMethods @rrsettgast @castelletto1 @OmarDuran @bd713 +/src/coreComponents/denseLinearAlgebra @rrsettgast @OmarDuran @bd713 +/src/coreComponents/discretizationMethods @rrsettgast @OmarDuran @bd713 /src/coreComponents/events @rrsettgast @corbett5 @cssherman @MelReyCG @OmarDuran @bd713 /src/coreComponents/fieldSpecification @rrsettgast @corbett5 @cssherman @MelReyCG @OmarDuran @OmarDuran @bd713 /src/coreComponents/fileIO @rrsettgast @wrtobin @MelReyCG @OmarDuran @bd713 -/src/coreComponents/finiteElement @rrsettgast @castelletto1 @andrea-borio @OmarDuran @jafranc @bd713 -/src/coreComponents/finiteVolume @rrsettgast @castelletto1 @OmarDuran @jafranc @bd713 +/src/coreComponents/finiteElement @rrsettgast @andrea-borio @OmarDuran @jafranc @bd713 +/src/coreComponents/finiteVolume @rrsettgast @OmarDuran @jafranc @bd713 /src/coreComponents/functions @rrsettgast @cssherman @wrtobin @MelReyCG @OmarDuran @bd713 -/src/coreComponents/linearAlgebra @rrsettgast @castelletto1 @victorapm @OmarDuran @bd713 +/src/coreComponents/linearAlgebra @rrsettgast @victorapm @OmarDuran @bd713 /src/coreComponents/mainInterface @rrsettgast @corbett5 @wrtobin @MelReyCG @OmarDuran @bd713 /src/coreComponents/math @rrsettgast @corbett5 @wrtobin @OmarDuran @bd713 /src/coreComponents/mesh @rrsettgast @wrtobin @OmarDuran @jafranc @bd713 @@ -73,10 +73,10 @@ /src/coreComponents/physicsSolvers/contact @rrsettgast @jhuang2601 @jafranc @OmarDuran /src/coreComponents/physicsSolvers/fluidFlow @rrsettgast @tjb-ltk @dkachuma @OmarDuran @jhuang2601 /src/coreComponents/physicsSolvers/inducedSeismicity @rrsettgast @jhuang2601 @kimtaeho07 @frankfeifan -/src/coreComponents/physicsSolvers/multiphysics @rrsettgast @castelletto1 @frankfeifan @jhuang2601 @OmarDuran +/src/coreComponents/physicsSolvers/multiphysics @rrsettgast @frankfeifan @jhuang2601 @OmarDuran /src/coreComponents/physicsSolvers/python @cssherman @corbett5 @bd713 @jafranc -/src/coreComponents/physicsSolvers/simplePDE @rrsettgast @castelletto1 @frankfeifan @bd713 -/src/coreComponents/physicsSolvers/solidMechanics @rrsettgast @castelletto1 @jhuang2601 @OmarDuran +/src/coreComponents/physicsSolvers/simplePDE @rrsettgast @frankfeifan @bd713 +/src/coreComponents/physicsSolvers/solidMechanics @rrsettgast @jhuang2601 @OmarDuran /src/coreComponents/physicsSolvers/surfaceGeneration @rrsettgast @jhuang2601 @OmarDuran @bd713 /src/coreComponents/physicsSolvers/wavePropagation @sframba @acitrain @bd713 @MelReyCG /src/coreComponents/schema @rrsettgast @herve-gross @jhuang2601 @dkachuma @OmarDuran diff --git a/.integrated_tests.yaml b/.integrated_tests.yaml index 5076ede8bd3..94ec9a64586 100644 --- a/.integrated_tests.yaml +++ b/.integrated_tests.yaml @@ -1,6 +1,6 @@ baselines: bucket: geosx - baseline: integratedTests/baseline_integratedTests-pr4040-16993-1393f80 + baseline: integratedTests/baseline_integratedTests-pr4088-17188-4247846 allow_fail: all: '' diff --git a/BASELINE_NOTES.md b/BASELINE_NOTES.md index 024e6787153..b833684e8de 100644 --- a/BASELINE_NOTES.md +++ b/BASELINE_NOTES.md @@ -5,6 +5,15 @@ This file is designed to track changes to the integrated test baselines. Any developer who updates the baseline ID in the .integrated_tests.yaml file is expected to create an entry in this file with the pull request number, date, and their justification for rebaselining. These notes should be in reverse-chronological order, and use the following time format: (YYYY-MM-DD). +PR #4088 (2026-07-27) +Fluid reset after convergence failure + +PR #3972 (2026-07-28) +Well model refactor . Integrated test update due to schema changes + +PR #3836 (2026-05-20) +Added statistics `Group` objects for each statistics `Task` instance + PR #4040 (2026-06-16) Move relperm driver to use new constitutive driver framework diff --git a/inputFiles/compositionalMultiphaseFlow/soreideWhitson/1D_100cells/1D_benchmark.xml b/inputFiles/compositionalMultiphaseFlow/soreideWhitson/1D_100cells/1D_benchmark.xml index 89b9cc7dc86..1ec3648a5fb 100644 --- a/inputFiles/compositionalMultiphaseFlow/soreideWhitson/1D_100cells/1D_benchmark.xml +++ b/inputFiles/compositionalMultiphaseFlow/soreideWhitson/1D_100cells/1D_benchmark.xml @@ -1,294 +1,391 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inputFiles/compositionalMultiphaseFlow/soreideWhitson/1D_100cells/1D_smoke.xml b/inputFiles/compositionalMultiphaseFlow/soreideWhitson/1D_100cells/1D_smoke.xml index 19a577bde98..c415cbc8fb6 100644 --- a/inputFiles/compositionalMultiphaseFlow/soreideWhitson/1D_100cells/1D_smoke.xml +++ b/inputFiles/compositionalMultiphaseFlow/soreideWhitson/1D_100cells/1D_smoke.xml @@ -1,294 +1,391 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inputFiles/compositionalMultiphaseWell/benchmarks/Class09Pb3/class09_pb3_drainageOnly_direct_base.xml b/inputFiles/compositionalMultiphaseWell/benchmarks/Class09Pb3/class09_pb3_drainageOnly_direct_base.xml index f2c31503e8a..d6c553dc399 100644 --- a/inputFiles/compositionalMultiphaseWell/benchmarks/Class09Pb3/class09_pb3_drainageOnly_direct_base.xml +++ b/inputFiles/compositionalMultiphaseWell/benchmarks/Class09Pb3/class09_pb3_drainageOnly_direct_base.xml @@ -27,27 +27,32 @@ logLevel="1" useMass="1"/> - - - + useSurfaceConditions="1" + surfacePressure="101325" + surfaceTemperature="288.71" + control="totalVolRate"> + + + + - + - + + name="fluidTPFA"/> @@ -80,13 +85,16 @@ solidModelName="nullSolid" porosityModelName="rockPorosity" permeabilityModelName="rockPerm"/> + + + @@ -94,9 +102,8 @@ - + wettingNonWettingRelPermTableNames="{ waterRelativePermeabilityTable, gasRelativePermeabilityTable }"/> + - - + + + + + - @@ -165,37 +176,36 @@ name="waterRelativePermeabilityTable" coordinateFiles="{ tables/phaseVolumeFraction_water.txt }" voxelFile="tables/relPerm_water.txt"/> + + voxelFile="tables/relPerm_gas.txt"/> - + voxelFile="tables/capPres_water.txt"/> - + interpolation="linear"/> - + interpolation="linear"/> - - diff --git a/inputFiles/compositionalMultiphaseWell/benchmarks/Class09Pb3/class09_pb3_drainageOnly_iterative_base.xml b/inputFiles/compositionalMultiphaseWell/benchmarks/Class09Pb3/class09_pb3_drainageOnly_iterative_base.xml index 8e76b08905b..254a2c798a5 100644 --- a/inputFiles/compositionalMultiphaseWell/benchmarks/Class09Pb3/class09_pb3_drainageOnly_iterative_base.xml +++ b/inputFiles/compositionalMultiphaseWell/benchmarks/Class09Pb3/class09_pb3_drainageOnly_iterative_base.xml @@ -33,57 +33,71 @@ logLevel="1" useMass="1"/> - - - + - - + + + + - + + + + - - + control="massRate"> + + + + + @@ -98,11 +112,11 @@ materialList="{ fluid }"/> - + + name="fluidTPFA"/> @@ -123,13 +137,16 @@ solidModelName="nullSolid" porosityModelName="rockPorosity" permeabilityModelName="rockPerm"/> + + + @@ -139,9 +156,8 @@ - + wettingNonWettingRelPermTableNames="{ waterRelativePermeabilityTable, gasRelativePermeabilityTable }"/> + - @@ -171,28 +186,31 @@ + + + @@ -204,11 +222,13 @@ name="initCO2CompFracTable" coordinates="{ -3238.2, -2506.13 }" values="{ 0.000001, 0.000001 }"/> + - @@ -217,43 +237,43 @@ name="waterRelativePermeabilityTable" coordinateFiles="{ tables/phaseVolumeFraction_water.txt }" voxelFile="tables/relPerm_water.txt"/> + + voxelFile="tables/relPerm_gas.txt"/> - + voxelFile="tables/capPres_water.txt"/> - + interpolation="linear"/> - + interpolation="linear"/> - diff --git a/inputFiles/compositionalMultiphaseWell/benchmarks/Class09Pb3/class09_pb3_hystRelperm_direct_base.xml b/inputFiles/compositionalMultiphaseWell/benchmarks/Class09Pb3/class09_pb3_hystRelperm_direct_base.xml index c731abcf696..f1963b5631c 100644 --- a/inputFiles/compositionalMultiphaseWell/benchmarks/Class09Pb3/class09_pb3_hystRelperm_direct_base.xml +++ b/inputFiles/compositionalMultiphaseWell/benchmarks/Class09Pb3/class09_pb3_hystRelperm_direct_base.xml @@ -27,27 +27,32 @@ logLevel="1" useMass="1"/> - - - + useSurfaceConditions="1" + surfacePressure="101325" + surfaceTemperature="288.71" + control="totalVolRate"> + + + + - + - + + name="fluidTPFA"/> @@ -80,13 +85,16 @@ solidModelName="nullSolid" porosityModelName="rockPorosity" permeabilityModelName="rockPerm"/> + + + @@ -95,11 +103,10 @@ - + - - + @@ -128,6 +134,7 @@ fieldName="pressure" functionName="pressureFunction" scale="1"/> + + + + + - + voxelFile="tables/capPres_water.txt"/> - + interpolation="linear"/> - + interpolation="linear"/> - + + + - - diff --git a/inputFiles/compositionalMultiphaseWell/benchmarks/Class09Pb3/class09_pb3_hystRelperm_iterative_base.xml b/inputFiles/compositionalMultiphaseWell/benchmarks/Class09Pb3/class09_pb3_hystRelperm_iterative_base.xml index ee1af0ef88d..7e24e404aed 100644 --- a/inputFiles/compositionalMultiphaseWell/benchmarks/Class09Pb3/class09_pb3_hystRelperm_iterative_base.xml +++ b/inputFiles/compositionalMultiphaseWell/benchmarks/Class09Pb3/class09_pb3_hystRelperm_iterative_base.xml @@ -30,28 +30,33 @@ logLevel="1" useMass="1"/> - - - + control="totalVolRate"> + + + + - + - + + name="fluidTPFA"/> @@ -84,13 +89,16 @@ solidModelName="nullSolid" porosityModelName="rockPorosity" permeabilityModelName="rockPerm"/> + + + @@ -100,12 +108,11 @@ - + - + + + + @@ -161,10 +170,12 @@ name="initCO2CompFracTable" coordinates="{ -3238.2, -2506.13 }" values="{ 0.000001, 0.000001 }"/> + + + + voxelFile="tables/relPerm_gas.txt"/> + voxelFile="tables/capPres_water.txt"/> - + interpolation="linear"/> - + interpolation="linear"/> @@ -210,19 +222,20 @@ name="drainageWaterRelativePermeabilityTable" coordinateFiles="{ tables/drainagePhaseVolFraction_water.txt }" voxelFile="tables/drainageRelPerm_water.txt"/> + + + - - diff --git a/inputFiles/compositionalMultiphaseWell/benchmarks/Egg/deadOilEgg_base_direct.xml b/inputFiles/compositionalMultiphaseWell/benchmarks/Egg/deadOilEgg_base_direct.xml index 048e88999d7..bddccec015d 100644 --- a/inputFiles/compositionalMultiphaseWell/benchmarks/Egg/deadOilEgg_base_direct.xml +++ b/inputFiles/compositionalMultiphaseWell/benchmarks/Egg/deadOilEgg_base_direct.xml @@ -1,7 +1,6 @@ - - - - + + + + - - - - + + + + - + + + + - + + + + - + + + + - + + + + - + + + + - + + + + - + + + + - + + + + - + + + + - + maxCompFractionChange="0.5"> + + + + - - - - - - - - @@ -279,7 +338,6 @@ materialList="{ fluid }"/> - - - - - @@ -373,32 +427,27 @@ - - - + fieldName="wellElementConnectionRate"/> + fieldName="wellElementConnectionRate"/> + fieldName="wellElementConnectionRate"/> + fieldName="wellElementConnectionRate"/> - - diff --git a/inputFiles/compositionalMultiphaseWell/benchmarks/Egg/deadOilEgg_base_iterative.xml b/inputFiles/compositionalMultiphaseWell/benchmarks/Egg/deadOilEgg_base_iterative.xml index 615688bef78..682607e2575 100644 --- a/inputFiles/compositionalMultiphaseWell/benchmarks/Egg/deadOilEgg_base_iterative.xml +++ b/inputFiles/compositionalMultiphaseWell/benchmarks/Egg/deadOilEgg_base_iterative.xml @@ -36,120 +36,189 @@ logLevel="1" useMass="1"/> - - - + + + + - + maxCompFractionChange="0.5"> + + + - - - + + + + - + + + + - + + + + - + + + + - + + + + - + + + + - + + + + - + + + + - + + + + - + maxCompFractionChange="0.5"> + + + + @@ -210,7 +279,6 @@ timeFrequency="7.5e6" targetExactTimestep="0" target="/Outputs/restartOutput"/> - @@ -375,7 +443,6 @@ - @@ -384,22 +451,22 @@ + fieldName="wellElementConnectionRate"/> + fieldName="wellElementonnectionRate"/> + fieldName="wellElementConnectionRate"/> + fieldName="wellElementConnectionRate"/> diff --git a/inputFiles/compositionalMultiphaseWell/black_oil_wells_saturated_3d.xml b/inputFiles/compositionalMultiphaseWell/black_oil_wells_saturated_3d.xml index 7ebac080cbc..b18a6368e5e 100644 --- a/inputFiles/compositionalMultiphaseWell/black_oil_wells_saturated_3d.xml +++ b/inputFiles/compositionalMultiphaseWell/black_oil_wells_saturated_3d.xml @@ -2,7 +2,6 @@ - - + - - - + + + + - + maxRelativePressureChange="0.1" + maxCompFractionChange="0.1"> + + + + - - - - + - + - + - @@ -148,12 +155,14 @@ name="region" cellBlocks="{ * }" materialList="{ fluid, rock, relperm, cappres }"/> + + + materialList="{ fluid }"/> @@ -163,7 +172,7 @@ surfaceDensities="{ 800.907131537, 0.856234902739, 1020.3440 }" componentMolarWeight="{ 120e-3, 25e-3, 18e-3 }" tableFiles="{ pvto_bo.txt, pvtg_norv_bo.txt, pvtw_bo.txt }"/> - + - + - - + + + scale="0.2"/> + - + coordinates="{ 0.0, 0.05, 0.10, 0.15, 0.20, 0.25, 0.30, 0.35, 0.40, 0.45, 0.5, 0.55, 0.60, 0.65, 0.70, 0.75, 0.80, 0.85, 0.90, 0.95, 1.00 }" + values="{ 0, 0.0025, 0.0100, 0.0225, 0.0400, 0.0625, 0.0900, 0.1225, 0.1600, 0.2025, 0.2500, 0.3025, 0.3600, 0.4225, 0.4900, 0.5625, 0.6400, 0.7225, 0.8100, 0.9025, 1.0000 }"/> + + coordinates="{ 0.0, 0.05, 0.10, 0.15, 0.20, 0.25, 0.30, 0.35, 0.40, 0.45, 0.5, 0.55, 0.60, 0.65, 0.70, 0.75, 0.80, 0.85, 0.90, 0.95, 1.00 }" + values="{ 0, 0.0025, 0.0100, 0.0225, 0.0400, 0.0625, 0.0900, 0.1225, 0.1600, 0.2025, 0.2500, 0.3025, 0.3600, 0.4225, 0.4900, 0.5625, 0.6400, 0.7225, 0.8100, 0.9025, 1.0000 }"/> + + coordinates="{ 0.0, 0.05, 0.10, 0.15, 0.20, 0.25, 0.30, 0.35, 0.40, 0.45, 0.5, 0.55, 0.60, 0.65, 0.70, 0.75, 0.80, 0.85, 0.90, 0.95, 1.00 }" + values="{ 0, 0.0025, 0.0100, 0.0225, 0.0400, 0.0625, 0.0900, 0.1225, 0.1600, 0.2025, 0.2500, 0.3025, 0.3600, 0.4225, 0.4900, 0.5625, 0.6400, 0.7225, 0.8100, 0.9025, 1.0000 }"/> + + coordinates="{ 0.0, 0.05, 0.10, 0.15, 0.20, 0.25, 0.30, 0.35, 0.40, 0.45, 0.5, 0.55, 0.60, 0.65, 0.70, 0.75, 0.80, 0.85, 0.90, 0.95, 1.00 }" + values="{ 0, 0.0025, 0.0100, 0.0225, 0.0400, 0.0625, 0.0900, 0.1225, 0.1600, 0.2025, 0.2500, 0.3025, 0.3600, 0.4225, 0.4900, 0.5625, 0.6400, 0.7225, 0.8100, 0.9025, 1.0000 }"/> + coordinates="{ 0.0, 0.05, 0.10, 0.15, 0.20, 0.25, 0.30, 0.35, 0.40, 0.45, 0.5, 0.55, 0.60, 0.65, 0.70, 0.75, 0.80, 0.85, 0.90, 0.95, 1.00 }" + values="{ 10000, 9025, 8100, 7225, 6400, 5625, 4900, 4225, 3600, 3025, 2500, 2025, 1600, 1225, 900, 625, 400, 225, 100, 25, 0 }"/> + - + coordinates="{ 0.0, 0.05, 0.10, 0.15, 0.20, 0.25, 0.30, 0.35, 0.40, 0.45, 0.5, 0.55, 0.60, 0.65, 0.70, 0.75, 0.80, 0.85, 0.90, 0.95, 1.00 }" + values="{ 0, 50, 200, 450, 800, 1250, 1800, 2450, 3200, 4050, 5000, 6050, 7200, 8450, 9800, 11250, 12800, 14450, 16200, 18050, 20000 }"/> - + - + + fieldName="wellElementConnectionRate"/> - + - + - diff --git a/inputFiles/compositionalMultiphaseWell/black_oil_wells_saturated_3d_stone2.xml b/inputFiles/compositionalMultiphaseWell/black_oil_wells_saturated_3d_stone2.xml index ce669961545..a6bc0ce2660 100644 --- a/inputFiles/compositionalMultiphaseWell/black_oil_wells_saturated_3d_stone2.xml +++ b/inputFiles/compositionalMultiphaseWell/black_oil_wells_saturated_3d_stone2.xml @@ -2,7 +2,6 @@ - - + - - - + + + + - + maxRelativePressureChange="0.1" + maxCompFractionChange="0.1"> + + + + - - - - - - - + + + + + + - - + - + - + - @@ -147,12 +155,14 @@ name="region" cellBlocks="{ * }" materialList="{ fluid, rock, relperm, cappres }"/> + + + materialList="{ fluid }"/> @@ -162,7 +172,7 @@ surfaceDensities="{ 800.907131537, 0.856234902739, 1020.3440 }" componentMolarWeight="{ 120e-3, 25e-3, 18e-3 }" tableFiles="{ pvto_bo.txt, pvtg_norv_bo.txt, pvtw_bo.txt }"/> - + - + - - + + + scale="0.2"/> + - + coordinates="{ 0.0, 0.05, 0.10, 0.15, 0.20, 0.25, 0.30, 0.35, 0.40, 0.45, 0.5, 0.55, 0.60, 0.65, 0.70, 0.75, 0.80, 0.85, 0.90, 0.95, 1.00 }" + values="{ 0, 0.0025, 0.0100, 0.0225, 0.0400, 0.0625, 0.0900, 0.1225, 0.1600, 0.2025, 0.2500, 0.3025, 0.3600, 0.4225, 0.4900, 0.5625, 0.6400, 0.7225, 0.8100, 0.9025, 1.0000 }"/> + + coordinates="{ 0.0, 0.05, 0.10, 0.15, 0.20, 0.25, 0.30, 0.35, 0.40, 0.45, 0.5, 0.55, 0.60, 0.65, 0.70, 0.75, 0.80, 0.85, 0.90, 0.95, 1.00 }" + values="{ 0, 0.0025, 0.0100, 0.0225, 0.0400, 0.0625, 0.0900, 0.1225, 0.1600, 0.2025, 0.2500, 0.3025, 0.3600, 0.4225, 0.4900, 0.5625, 0.6400, 0.7225, 0.8100, 0.9025, 1.0000 }"/> + + coordinates="{ 0.0, 0.05, 0.10, 0.15, 0.20, 0.25, 0.30, 0.35, 0.40, 0.45, 0.5, 0.55, 0.60, 0.65, 0.70, 0.75, 0.80, 0.85, 0.90, 0.95, 1.00 }" + values="{ 0, 0.0025, 0.0100, 0.0225, 0.0400, 0.0625, 0.0900, 0.1225, 0.1600, 0.2025, 0.2500, 0.3025, 0.3600, 0.4225, 0.4900, 0.5625, 0.6400, 0.7225, 0.8100, 0.9025, 1.0000 }"/> + + coordinates="{ 0.0, 0.05, 0.10, 0.15, 0.20, 0.25, 0.30, 0.35, 0.40, 0.45, 0.5, 0.55, 0.60, 0.65, 0.70, 0.75, 0.80, 0.85, 0.90, 0.95, 1.00 }" + values="{ 0, 0.0025, 0.0100, 0.0225, 0.0400, 0.0625, 0.0900, 0.1225, 0.1600, 0.2025, 0.2500, 0.3025, 0.3600, 0.4225, 0.4900, 0.5625, 0.6400, 0.7225, 0.8100, 0.9025, 1.0000 }"/> + coordinates="{ 0.0, 0.05, 0.10, 0.15, 0.20, 0.25, 0.30, 0.35, 0.40, 0.45, 0.5, 0.55, 0.60, 0.65, 0.70, 0.75, 0.80, 0.85, 0.90, 0.95, 1.00 }" + values="{ 10000, 9025, 8100, 7225, 6400, 5625, 4900, 4225, 3600, 3025, 2500, 2025, 1600, 1225, 900, 625, 400, 225, 100, 25, 0 }"/> + - + coordinates="{ 0.0, 0.05, 0.10, 0.15, 0.20, 0.25, 0.30, 0.35, 0.40, 0.45, 0.5, 0.55, 0.60, 0.65, 0.70, 0.75, 0.80, 0.85, 0.90, 0.95, 1.00 }" + values="{ 0, 50, 200, 450, 800, 1250, 1800, 2450, 3200, 4050, 5000, 6050, 7200, 8450, 9800, 11250, 12800, 14450, 16200, 18050, 20000 }"/> - + - + + fieldName="wellElementConnectionRate"/> - + - + - diff --git a/inputFiles/compositionalMultiphaseWell/black_oil_wells_unsaturated_3d.xml b/inputFiles/compositionalMultiphaseWell/black_oil_wells_unsaturated_3d.xml index 323b9a144c6..01e678391cd 100644 --- a/inputFiles/compositionalMultiphaseWell/black_oil_wells_unsaturated_3d.xml +++ b/inputFiles/compositionalMultiphaseWell/black_oil_wells_unsaturated_3d.xml @@ -2,7 +2,6 @@ - - + - - - + + + + - + maxRelativePressureChange="0.1" + maxCompFractionChange="0.1"> + + + + - - - - - + - - + - + - @@ -148,12 +156,14 @@ name="region" cellBlocks="{ * }" materialList="{ fluid, rock, relperm }"/> + + + materialList="{ fluid }"/> @@ -163,7 +173,7 @@ surfaceDensities="{ 800.907131537, 0.856234902739, 1020.3440 }" componentMolarWeight="{ 120e-3, 25e-3, 18e-3 }" tableFiles="{ pvto_bo.txt, pvtg_norv_bo.txt, pvtw_bo.txt }"/> - + - - + + + scale="0.1"/> + - + - + + fieldName="wellElementConnectionRate"/> - + name="vtkOutput"/> + - - diff --git a/inputFiles/compositionalMultiphaseWell/black_oil_wells_unsaturated_3d_stone2.xml b/inputFiles/compositionalMultiphaseWell/black_oil_wells_unsaturated_3d_stone2.xml index ea41c8adf9c..9a59b7a21da 100644 --- a/inputFiles/compositionalMultiphaseWell/black_oil_wells_unsaturated_3d_stone2.xml +++ b/inputFiles/compositionalMultiphaseWell/black_oil_wells_unsaturated_3d_stone2.xml @@ -1,260 +1,269 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inputFiles/compositionalMultiphaseWell/bos.xml b/inputFiles/compositionalMultiphaseWell/bos.xml new file mode 100644 index 00000000000..07524638dec --- /dev/null +++ b/inputFiles/compositionalMultiphaseWell/bos.xml @@ -0,0 +1,305 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inputFiles/compositionalMultiphaseWell/compositional_multiphase_wells_1d.xml b/inputFiles/compositionalMultiphaseWell/compositional_multiphase_wells_1d.xml index 50dd2dac0e7..1cd60fe2ceb 100644 --- a/inputFiles/compositionalMultiphaseWell/compositional_multiphase_wells_1d.xml +++ b/inputFiles/compositionalMultiphaseWell/compositional_multiphase_wells_1d.xml @@ -23,28 +23,38 @@ targetRegions="{ Region1 }" temperature="297.15"/> - - - + + + + - + control="totalVolRate"> + + + + @@ -58,7 +68,6 @@ ny="{ 1 }" nz="{ 1 }" cellBlockNames="{ cb1 }"> - - + name="fluidTPFA"/> diff --git a/inputFiles/compositionalMultiphaseWell/compositional_multiphase_wells_2d.xml b/inputFiles/compositionalMultiphaseWell/compositional_multiphase_wells_2d.xml index 9c385c41364..32ba08239e6 100644 --- a/inputFiles/compositionalMultiphaseWell/compositional_multiphase_wells_2d.xml +++ b/inputFiles/compositionalMultiphaseWell/compositional_multiphase_wells_2d.xml @@ -22,39 +22,52 @@ discretization="fluidTPFA" targetRegions="{ Region1 }" temperature="297.15"/> - - - + - - + + + + - + + + + - - + control="totalVolRate"> + + + + @@ -69,7 +82,6 @@ ny="{ 20 }" nz="{ 1 }" cellBlockNames="{ cb1 }"> - - - + name="fluidTPFA"/> @@ -220,7 +229,6 @@ - - - + - + + + + - + + + + - + control="totalVolRate" + maxRelativePressureChange="0.1" + maxCompFractionChange="0.1"> + + + + @@ -78,7 +97,6 @@ ny="{ 20 }" nz="{ 1 }" cellBlockNames="{ cb1 }"> - - - + name="fluidTPFA"/> @@ -262,14 +277,13 @@ - - + - - + - + + + + - + + + + - + control="totalVolRate" + maxRelativePressureChange="0.1" + maxCompFractionChange="0.1"> + + + + @@ -77,7 +96,6 @@ ny="{ 20 }" nz="{ 1 }" cellBlockNames="{ cb1 }"> - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inputFiles/compositionalMultiphaseWell/dome_soreide_whitson_base.xml b/inputFiles/compositionalMultiphaseWell/dome_soreide_whitson_base.xml index 718e7894417..eb98b185e18 100644 --- a/inputFiles/compositionalMultiphaseWell/dome_soreide_whitson_base.xml +++ b/inputFiles/compositionalMultiphaseWell/dome_soreide_whitson_base.xml @@ -1,317 +1,377 @@ + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inputFiles/compositionalMultiphaseWell/isothm_mass_inj_table.xml b/inputFiles/compositionalMultiphaseWell/isothm_mass_inj_table.xml index 14076fab917..da6c1c11af3 100644 --- a/inputFiles/compositionalMultiphaseWell/isothm_mass_inj_table.xml +++ b/inputFiles/compositionalMultiphaseWell/isothm_mass_inj_table.xml @@ -10,7 +10,7 @@ initialDt="1e2" targetRegions="{ region, injwell }"> - + targetRegions="{ region }"/> - - - + writeCSV="1"> + + + + @@ -83,10 +86,8 @@ - - @@ -94,8 +95,6 @@ name="sink" xMin="{ 89.99, 89.99, -0.01 }" xMax="{ 101.01, 101.01, 1.01 }"/> - - - - @@ -143,25 +143,28 @@ name="region" cellBlocks="{ cb }" materialList="{ fluid, rock, relperm }"/> + - + + + @@ -181,11 +184,9 @@ phaseMinVolumeFraction="{ 0.0, 0.0 }" phaseRelPermExponent="{ 1.5, 1.5 }" phaseRelPermMaxValue="{ 0.9, 0.9 }"/> - - + + + - diff --git a/inputFiles/compositionalMultiphaseWell/isothm_vol_inj_table.xml b/inputFiles/compositionalMultiphaseWell/isothm_vol_inj_table.xml index d39421e36fc..15c9dcc7b89 100644 --- a/inputFiles/compositionalMultiphaseWell/isothm_vol_inj_table.xml +++ b/inputFiles/compositionalMultiphaseWell/isothm_vol_inj_table.xml @@ -10,7 +10,7 @@ initialDt="1e2" targetRegions="{ region, injwell }"> - + targetRegions="{ region }"/> - - - + writeCSV="1"> + + + + @@ -82,10 +86,8 @@ - - @@ -93,8 +95,6 @@ name="sink" xMin="{ 89.99, 89.99, -0.01 }" xMax="{ 101.01, 101.01, 1.01 }"/> - - - - - @@ -148,25 +150,28 @@ name="region" cellBlocks="{ cb }" materialList="{ fluid, rock, relperm }"/> + - + + + @@ -186,11 +191,9 @@ phaseMinVolumeFraction="{ 0.0, 0.0 }" phaseRelPermExponent="{ 1.5, 1.5 }" phaseRelPermMaxValue="{ 0.9, 0.9 }"/> - - + + + - diff --git a/inputFiles/compositionalMultiphaseWell/resvol_constraint.xml b/inputFiles/compositionalMultiphaseWell/resvol_constraint.xml index 620b7b74df0..549456ba08d 100644 --- a/inputFiles/compositionalMultiphaseWell/resvol_constraint.xml +++ b/inputFiles/compositionalMultiphaseWell/resvol_constraint.xml @@ -23,31 +23,42 @@ targetRegions="{ Region1 }" temperature="297.15"/> - - - + + + + - + writeCSV="1"> + + + + @@ -61,7 +72,6 @@ ny="{ 1 }" nz="{ 1 }" cellBlockNames="{ cb1 }"> - - + - - + + name="fluidTPFA"/> @@ -189,7 +198,6 @@ - - - - - + useSurfaceConditions="1" + surfacePressure="101325" + surfaceTemperature="288.71" + control="totalVolRate" + maxCompFractionChange="0.2"> + + + + - + - + @@ -111,13 +116,12 @@ - - - + sources="{ /Tasks/wellPressureCollection }" + filename="wellPressureHistory"/> @@ -196,31 +199,29 @@ - + fieldName="pressure"/> - - - - - + + + + + - diff --git a/inputFiles/compositionalMultiphaseWell/staged_perf_base.xml b/inputFiles/compositionalMultiphaseWell/staged_perf_base.xml index 302d728e9b4..cc2aee8bbbe 100644 --- a/inputFiles/compositionalMultiphaseWell/staged_perf_base.xml +++ b/inputFiles/compositionalMultiphaseWell/staged_perf_base.xml @@ -10,11 +10,11 @@ targetRegions="{ reservoir, wellRegion1 }"> + directParallel="0"/> - - - + writeCSV="1"> + + + + - - + + name="fluidTPFA"/> @@ -91,7 +94,6 @@ - @@ -158,34 +160,31 @@ scale="1.0"/> - + name="equil" + objectPath="ElementRegions" + datumElevation="0" + datumPressure="2.214e7" + initialPhaseName="water" + componentNames="{ co2, water }" + componentFractionVsElevationTableNames="{ initCO2CompFracTable, initWaterCompFracTable }" + temperatureVsElevationTableName="initTempTable"/> + name="initCO2CompFracTable" + coordinates="{ -3000.0, 0.0 }" + values="{ 0.0, 0.0 }"/> + name="initWaterCompFracTable" + coordinates="{ -3000.0, 0.0 }" + values="{ 1.0, 1.0 }"/> - + name="initTempTable" + coordinates="{ -3000.0, 0.0 }" + values="{ 368, 288 }"/> diff --git a/inputFiles/compositionalMultiphaseWell/staircase_co2_wells_3d.xml b/inputFiles/compositionalMultiphaseWell/staircase_co2_wells_3d.xml index 388b15ca605..6b436f5d9b8 100644 --- a/inputFiles/compositionalMultiphaseWell/staircase_co2_wells_3d.xml +++ b/inputFiles/compositionalMultiphaseWell/staircase_co2_wells_3d.xml @@ -25,36 +25,45 @@ maxCompFractionChange="0.2" useMass="1"/> - - - + + + + - + maxCompFractionChange="0.2"> + + + + - - - + cellBlockNames="{ cb-0_0_0, cb-1_0_0, cb-0_1_0, cb-1_1_0, cb-0_0_1, cb-1_0_1, cb-0_1_1, cb-1_1_1, cb-0_0_2, cb-1_0_2, cb-0_1_2, cb-1_1_2, cb-0_0_3, cb-1_0_3, cb-0_1_3, cb-1_1_3 }"> - + wettingNonWettingRelPermTableNames="{ waterRelativePermeabilityTable, gasRelativePermeabilityTable }"/> - @@ -222,35 +224,37 @@ scale="0.96"/> - + + voxelFile="relPerm_gas.txt"/> + + voxelFile="capPres_water.txt"/> + + - diff --git a/inputFiles/compositionalMultiphaseWell/staircase_co2_wells_hybrid_3d.xml b/inputFiles/compositionalMultiphaseWell/staircase_co2_wells_hybrid_3d.xml index b7e30e91dcb..e194d703d6c 100644 --- a/inputFiles/compositionalMultiphaseWell/staircase_co2_wells_hybrid_3d.xml +++ b/inputFiles/compositionalMultiphaseWell/staircase_co2_wells_hybrid_3d.xml @@ -28,35 +28,46 @@ maxRelativePressureChange="0.2" useMass="1"/> - - - + + + + - + maxRelativePressureChange="0.2" + maxCompFractionChange="0.2"> + + + + - - - - + diff --git a/inputFiles/poromechanics/PoroElastic_staircase_co2_3d_fim.xml b/inputFiles/poromechanics/PoroElastic_staircase_co2_3d_fim.xml index 0ca07929f8e..d71aa6ef337 100644 --- a/inputFiles/poromechanics/PoroElastic_staircase_co2_3d_fim.xml +++ b/inputFiles/poromechanics/PoroElastic_staircase_co2_3d_fim.xml @@ -1,13 +1,12 @@ - - - + + - - + + targetRegions="{ channel, barrier }"/> - - - + + + + - - + maxCompFractionChange="0.2"> + + + + - + + - + + - + - - - diff --git a/inputFiles/poromechanics/PoroElastic_staircase_co2_3d_sequential.xml b/inputFiles/poromechanics/PoroElastic_staircase_co2_3d_sequential.xml index e978595a49f..f61b41a4c67 100755 --- a/inputFiles/poromechanics/PoroElastic_staircase_co2_3d_sequential.xml +++ b/inputFiles/poromechanics/PoroElastic_staircase_co2_3d_sequential.xml @@ -1,13 +1,12 @@ - - - + + - - + + targetRegions="{ channel, barrier }"/> - - - + + + + - - + maxCompFractionChange="0.2"> + + + + - + + - + + - + - - diff --git a/inputFiles/poromechanics/PoroElastic_staircase_singlephase_3d_fim.xml b/inputFiles/poromechanics/PoroElastic_staircase_singlephase_3d_fim.xml index 95f2090ada1..2e78157ef6e 100644 --- a/inputFiles/poromechanics/PoroElastic_staircase_singlephase_3d_fim.xml +++ b/inputFiles/poromechanics/PoroElastic_staircase_singlephase_3d_fim.xml @@ -1,13 +1,12 @@ - - - + + - - + + targetRegions="{ channel, barrier }"/> - - - + + + + - - + surfacePressure="101325"> + + + + - + minTime="-1e11" + maxTime="1e7"> - + + - + + - - + + - diff --git a/inputFiles/poromechanics/PoroElastic_staircase_singlephase_3d_sequential.xml b/inputFiles/poromechanics/PoroElastic_staircase_singlephase_3d_sequential.xml index 0c952c6934f..331844d8b48 100755 --- a/inputFiles/poromechanics/PoroElastic_staircase_singlephase_3d_sequential.xml +++ b/inputFiles/poromechanics/PoroElastic_staircase_singlephase_3d_sequential.xml @@ -1,13 +1,12 @@ - - - + + - - + + targetRegions="{ channel, barrier }"/> - - - + + + + - - + surfacePressure="101325"> + + + + - + minTime="-1e11" + maxTime="1e7"> - + + - + - @@ -118,24 +125,25 @@ name="linearElasticityStatistics" solidSolverName="linearElasticity" logLevel="1"/> + - - + + - diff --git a/inputFiles/poromechanics/ReservoirThermoPoroElastic_Circulation_debug.xml b/inputFiles/poromechanics/ReservoirThermoPoroElastic_Circulation_debug.xml index b2a0d65d59d..feb3d2e9307 100644 --- a/inputFiles/poromechanics/ReservoirThermoPoroElastic_Circulation_debug.xml +++ b/inputFiles/poromechanics/ReservoirThermoPoroElastic_Circulation_debug.xml @@ -2,12 +2,12 @@ - + - - - - + + + + - - + writeCSV="1"> + + + + + - - + + @@ -162,7 +173,6 @@ - - + targetTime="-1e10"/> - + - - - + poromechanicsSolverName="reservoirPoromechanics"/> - diff --git a/inputFiles/poromechanicsFractures/Contact/LagrangianMultipliers/multiphasePoromechanics_FaultModel_well_fim_smoke.xml b/inputFiles/poromechanicsFractures/Contact/LagrangianMultipliers/multiphasePoromechanics_FaultModel_well_fim_smoke.xml index f072a262c59..66b6e0e5d9e 100644 --- a/inputFiles/poromechanicsFractures/Contact/LagrangianMultipliers/multiphasePoromechanics_FaultModel_well_fim_smoke.xml +++ b/inputFiles/poromechanicsFractures/Contact/LagrangianMultipliers/multiphasePoromechanics_FaultModel_well_fim_smoke.xml @@ -1,14 +1,16 @@ + + - + - + maxAllowedResidualNorm="1e+15"/> + directParallel="0"/> + targetRegions="{ Region, Fault }" + discretization="FE1"/> + logLevel="3"/> - + targetRegions="{ Region, Fault }" + temperature="368.15"/> - - + - + logLevel="2" + writeCSV="1"> + + + + @@ -88,47 +95,44 @@ numElementsPerSegment="1"> + distanceFromHead="250"/> - + + - - - + target="/Outputs/vtkOutput"/> + target="/Solvers/reservoirSolver"/> + target="/Outputs/vtkOutput"/> + target="/Outputs/restartOutput"/> diff --git a/inputFiles/poromechanicsFractures/Contact/LagrangianMultipliers/multiphasePoromechanics_FaultModel_well_seq_smoke.xml b/inputFiles/poromechanicsFractures/Contact/LagrangianMultipliers/multiphasePoromechanics_FaultModel_well_seq_smoke.xml index 6ca0216ac4e..d91d7f890ff 100644 --- a/inputFiles/poromechanicsFractures/Contact/LagrangianMultipliers/multiphasePoromechanics_FaultModel_well_seq_smoke.xml +++ b/inputFiles/poromechanicsFractures/Contact/LagrangianMultipliers/multiphasePoromechanics_FaultModel_well_seq_smoke.xml @@ -1,14 +1,16 @@ + + - + - + maxAllowedResidualNorm="1e+15"/> + directParallel="0"/> + targetRegions="{ Region, Fault }" + discretization="FE1"/> + newtonMaxIter="20"/> + preconditionerType="mgr"/> - + targetRegions="{ Region, Fault }" + temperature="368.15"/> - - + - + logLevel="2" + writeCSV="1"> + + + + @@ -97,47 +104,44 @@ numElementsPerSegment="1"> + distanceFromHead="250"/> - + + - - - + target="/Outputs/vtkOutput"/> + target="/Solvers/reservoirSolver"/> + target="/Outputs/vtkOutput"/> + target="/Outputs/restartOutput"/> diff --git a/inputFiles/poromechanicsFractures/Contact/LagrangianMultipliers/singlePhasePoromechanics_FaultModel_well_fim_new_smoke.xml b/inputFiles/poromechanicsFractures/Contact/LagrangianMultipliers/singlePhasePoromechanics_FaultModel_well_fim_new_smoke.xml index 07a6e1521ff..72aad6f89b6 100644 --- a/inputFiles/poromechanicsFractures/Contact/LagrangianMultipliers/singlePhasePoromechanics_FaultModel_well_fim_new_smoke.xml +++ b/inputFiles/poromechanicsFractures/Contact/LagrangianMultipliers/singlePhasePoromechanics_FaultModel_well_fim_new_smoke.xml @@ -1,14 +1,16 @@ + + - + - + solverType="direct"/> - + logLevel="1"/> + targetRegions="{ Region, Fault }" + discretization="FE1"/> - + targetRegions="{ Region, Fault }"/> - - - + + + + - + control="BHP"> + + + + - + + - - - + - + - + solverType="direct"/> + targetRegions="{ Region, Fault }" + discretization="FE1"/> - + targetRegions="{ Region, Fault }"/> - - - + + + + - + control="BHP"> + + + + - + + - - - + - + - + directParallel="1"/> - + logLevel="1"/> + targetRegions="{ Region, Fault }" + discretization="FE1"/> - + targetRegions="{ Region, Fault }"/> - - - + + + + - + control="BHP"> + + + + - + + - - - + - + - + directParallel="1"/> + targetRegions="{ Region, Fault }" + discretization="FE1"/> - + targetRegions="{ Region, Fault }"/> - - - + + + + - + control="BHP"> + + + + - + + - - - + targetRegions="{ region , wellRegion2 }"> - + targetRegions="{ region }"/> - - - + surfaceTemperature="300.15"> + + + + @@ -78,13 +82,13 @@ name="region" cellBlocks="{ * }" materialList="{ fluid, rock, thermalCond }"/> + - - - - diff --git a/inputFiles/singlePhaseWell/compressible_single_phase_wells_1d.xml b/inputFiles/singlePhaseWell/compressible_single_phase_wells_1d.xml index 4b5f64d0287..0fe903b3199 100644 --- a/inputFiles/singlePhaseWell/compressible_single_phase_wells_1d.xml +++ b/inputFiles/singlePhaseWell/compressible_single_phase_wells_1d.xml @@ -20,25 +20,35 @@ discretization="singlePhaseTPFA" targetRegions="{ Region1 }"/> - - - + + + + - + control="totalVolRate"> + + + + @@ -52,7 +62,6 @@ ny="{ 1 }" nz="{ 1 }" cellBlockNames="{ cb1 }"> - - - - - + + + + - + control="totalVolRate"> + + + + @@ -52,7 +62,6 @@ ny="{ 1 }" nz="{ 1 }" cellBlockNames="{ cb1 }"> - + distanceFromHead="1.45" + skinFactor="1"/> - - - + - + + + + - + control="totalVolRate"> + + + + @@ -53,7 +63,6 @@ ny="{ 1 }" nz="{ 1 }" cellBlockNames="{ cb1 }"> - - - - + - + + + + - + + + + - + initialPressureCoefficient="0.01" + control="totalVolRate"> + + + + @@ -60,7 +75,6 @@ ny="{ 20 }" nz="{ 1 }" cellBlockNames="{ cb1 }"> - - - + name="singlePhaseTPFA"/> diff --git a/inputFiles/singlePhaseWell/incompressible_single_phase_wells_hybrid_2d.xml b/inputFiles/singlePhaseWell/incompressible_single_phase_wells_hybrid_2d.xml index 743a1f7be70..28a95e82f30 100644 --- a/inputFiles/singlePhaseWell/incompressible_single_phase_wells_hybrid_2d.xml +++ b/inputFiles/singlePhaseWell/incompressible_single_phase_wells_hybrid_2d.xml @@ -21,32 +21,47 @@ discretization="singlePhaseHybridMimetic" targetRegions="{ Region1 }"/> - - - + + + + - + + + + - + control="totalVolRate"> + + + + @@ -60,7 +75,6 @@ ny="{ 20 }" nz="{ 1 }" cellBlockNames="{ cb1 }"> - - - - - + - + + + + - + + + + - + writeCSV="1"> + + + + @@ -62,7 +79,6 @@ ny="{ 20 }" nz="{ 1 }" cellBlockNames="{ cb1 }"> - - - + name="singlePhaseTPFA"/> diff --git a/inputFiles/singlePhaseWell/staircase_single_phase_wells_3d.xml b/inputFiles/singlePhaseWell/staircase_single_phase_wells_3d.xml index 6789ab90542..55da1048afe 100644 --- a/inputFiles/singlePhaseWell/staircase_single_phase_wells_3d.xml +++ b/inputFiles/singlePhaseWell/staircase_single_phase_wells_3d.xml @@ -16,29 +16,39 @@ - - - + + + + - + control="totalVolRate"> + + + + @@ -51,11 +61,7 @@ nx="{ 5, 5 }" ny="{ 5, 5 }" nz="{ 3, 3, 3, 3 }" - cellBlockNames="{ cb-0_0_0, cb-1_0_0, cb-0_1_0, cb-1_1_0, - cb-0_0_1, cb-1_0_1, cb-0_1_1, cb-1_1_1, - cb-0_0_2, cb-1_0_2, cb-0_1_2, cb-1_1_2, - cb-0_0_3, cb-1_0_3, cb-0_1_3, cb-1_1_3 }"> - + cellBlockNames="{ cb-0_0_0, cb-1_0_0, cb-0_1_0, cb-1_1_0, cb-0_0_1, cb-1_0_1, cb-0_1_1, cb-1_1_1, cb-0_0_2, cb-1_0_2, cb-0_1_2, cb-1_1_2, cb-0_0_3, cb-1_0_3, cb-0_1_3, cb-1_1_3 }"> - @@ -172,9 +177,9 @@ diff --git a/inputFiles/singlePhaseWell/staircase_single_phase_wells_hybrid_3d.xml b/inputFiles/singlePhaseWell/staircase_single_phase_wells_hybrid_3d.xml index 2298a32e483..40fd508d61b 100644 --- a/inputFiles/singlePhaseWell/staircase_single_phase_wells_hybrid_3d.xml +++ b/inputFiles/singlePhaseWell/staircase_single_phase_wells_hybrid_3d.xml @@ -21,27 +21,37 @@ discretization="singlePhaseHybridMimetic" targetRegions="{ Channel }"/> - - - + + + + - + surfacePressure="101325"> + + + + @@ -54,11 +64,7 @@ nx="{ 5, 5 }" ny="{ 5, 5 }" nz="{ 3, 3, 3, 3 }" - cellBlockNames="{ cb-0_0_0, cb-1_0_0, cb-0_1_0, cb-1_1_0, - cb-0_0_1, cb-1_0_1, cb-0_1_1, cb-1_1_1, - cb-0_0_2, cb-1_0_2, cb-0_1_2, cb-1_1_2, - cb-0_0_3, cb-1_0_3, cb-0_1_3, cb-1_1_3 }"> - + cellBlockNames="{ cb-0_0_0, cb-1_0_0, cb-0_1_0, cb-1_1_0, cb-0_0_1, cb-1_0_1, cb-0_1_1, cb-1_1_1, cb-0_0_2, cb-1_0_2, cb-0_1_2, cb-1_1_2, cb-0_0_3, cb-1_0_3, cb-0_1_3, cb-1_1_3 }"> - short form) + try: + if hasattr(tree, "write"): + tree.write(outpath, encoding='utf-8', xml_declaration=True, short_empty_elements=False) + else: + # last resort: convert to string and write + s = ET_local.tostring(tree, encoding='utf-8') + with open(outpath, "wb") as f: + f.write(s) + except Exception as e: + raise + xml_formatter.format_file(outpath) + # + +def validate_with_xsd(tree: ET.ElementTree, xsd_path: str) -> Tuple[bool, str]: + if not lxml_etree: + return False, "lxml not installed" + try: + xml_bytes = ET.tostring(tree.getroot(), encoding="utf-8") + xml_doc = lxml_etree.fromstring(xml_bytes) + xsd_doc = lxml_etree.parse(xsd_path) + schema = lxml_etree.XMLSchema(xsd_doc) + ok = schema.validate(xml_doc) + if ok: + return True, "" + else: + return False, str(schema.error_log) + except Exception as e: + return False, str(e) + +def update_constraint_element(xml_file,add_we,estimatorSolves): + delete_old_schema=True + with open(xml_file, 'rb') as xml_file: + tree = ET.parse(xml_file) + root = tree.getroot() + compoFluidModel = True + isThermal=False + for elem in root.iter(): + #print(f"Tag: {elem.tag}, Attributes: {elem.attrib}, Text: {elem.text}") + + if elem.tag == "CompositionalMultiphaseWell" or elem.tag == "SinglePhaseWell": + if elem.tag == "SinglePhaseWell": + compoFluidModel = False + #if "writeCSV" not in elem.attrib: + # esolves = '"'+str(estimatorSolves)+'"' + # elem.set("writeCSV","1") + if add_we: + + nlsTag="NonlinearSolverParameters" + nlsAttributes={} + nlsAttributes["newtonTol"]="1.0e-8" + nlsAttributes["lineSearchAction"]="None" + nlsAttributes["newtonMaxIter"]="20" + elem.insert(0,ET.Element(nlsTag,nlsAttributes)) + lsTag="LinearSolverParameters" + lsAttributes={} + lsAttributes["directParallel"]="0" + elem.insert(0,ET.Element(lsTag,lsAttributes)) + + if "isThermal" in elem.attrib: + if elem.attrib["isThermal"] == "1": + isThermal = True + if elem.tag == "WellControls": + #print(f"Tag: {elem.tag}, Attributes: {elem.attrib}, Text: {elem.text}") + #if "control" in elem.attrib: + # if delete_old_schema: + # elem.attrib.pop("control") + if add_we: + elem.set("estimateWellSolution",str(estimatorSolves)) + isProducer = elem.attrib['type'] == 'producer' + if isProducer: + constraintType="Production" + pressureType="Minimum" + else: + constraintType="Injection" + pressureType="Maximum" + # setup phase constraint + if 'targetPhaseName' in elem.attrib: + writePhaseConstraint=True + phaseConstraintTag= constraintType+"PhaseVolumeRateConstraint" + phaseConstraintAttributes={} + phaseConstraintAttributes["name"]="max"+elem.attrib['targetPhaseName'].lower() +"prod" if isProducer else "inj" + if 'targetPhaseName' in elem.attrib: + phaseConstraintAttributes["phaseName"] = elem.attrib['targetPhaseName'] + if delete_old_schema: + elem.attrib.pop("targetPhaseName") + if "targetPhaseRateTableName" in elem.attrib: + phaseConstraintAttributes["constraintScheduleTableName"]=elem.attrib["targetPhaseRateTableName"] + if delete_old_schema: + elem.attrib.pop("targetPhaseRateTableName") + elif "targetPhaseRate" in elem.attrib: + phaseConstraintAttributes["phaseRate"]=elem.attrib["targetPhaseRate"] + if delete_old_schema: + elem.attrib.pop("targetPhaseRate") + else: + writePhaseConstraint=False + print("error missing phase rate info") + if not isProducer: + if compoFluidModel: + if "injectionStream" in elem: + phaseConstraintAttributes["injectionStream"]=elem.attrib["injectionStream"] + phaseConstraintAttributes["injectionTemperature"]=elem.attrib["injectionTemperature"] + if delete_old_schema: + elem.attrib.pop("injectionStream") + elem.attrib.pop("injectionTemperature") + else: + print("error missinging injectionStream ",elem.attrib) + + if writePhaseConstraint: + elem.append(ET.Element(phaseConstraintTag,phaseConstraintAttributes)) + + # setup pressure constraint + pressureConstraintTag= pressureType+"BHPConstraint" + pressureConstraintAttributes={} + pressureConstraintAttributes["name"]="minbhp" if isProducer else "maxbhp" + if 'targetBHPTableName' in elem.attrib: + pressureConstraintAttributes["constraintScheduleTableName"]=elem.attrib["targetBHPTableName"] + if delete_old_schema: + elem.attrib.pop("targetBHPTableName") + elif 'targetBHP' in elem.attrib: + pressureConstraintAttributes["targetBHP"]=elem.attrib["targetBHP"] + if delete_old_schema: + elem.attrib.pop("targetBHP") + else: + print('error missing bhp info') + + if 'referenceElevation' in elem.attrib: + pressureConstraintAttributes["referenceElevation"]=elem.attrib["referenceElevation"] + if delete_old_schema: + elem.attrib.pop("referenceElevation") + else: + print('error missing bhp referenceElevation') + + elem.append(ET.Element(pressureConstraintTag,pressureConstraintAttributes)) + + totalVolRateTag= constraintType+"VolumeRateConstraint" + totalVolRateAttributes={} + totalVolRateAttributes["name"]="maxvolrateprod" if isProducer else "maxvolrateinj" + if 'targetTotalRate' in elem.attrib or 'targetTotalRateTableName' in elem.attrib: + if 'targetTotalRate' in elem.attrib: + totalVolRateAttributes["volumeRate"]=elem.attrib["targetTotalRate"] + if delete_old_schema: + elem.attrib.pop("targetTotalRate") + if 'targetTotalRateTableName' in elem.attrib: + totalVolRateAttributes["constraintScheduleTableName"]=elem.attrib["targetTotalRateTableName"] + if delete_old_schema: + elem.attrib.pop("targetTotalRateTableName") + if not isProducer: + if compoFluidModel: + totalVolRateAttributes["injectionStream"]=elem.attrib["injectionStream"] + totalVolRateAttributes["injectionTemperature"]=elem.attrib["injectionTemperature"] + if delete_old_schema: + elem.attrib.pop("injectionStream") + elem.attrib.pop("injectionTemperature") + else: + if isThermal: + if "injectionStream" in elem.attrib: + totalVolRateAttributes["injectionStream"]=elem.attrib["injectionStream"] + totalVolRateAttributes["injectionTemperature"]=elem.attrib["injectionTemperature"] + if delete_old_schema: + elem.attrib.pop("injectionStream") + elem.attrib.pop("injectionTemperature") + else: + totalVolRateAttributes["injectionStream"]="{1.0}" + totalVolRateAttributes["injectionTemperature"]="123" + + elem.append(ET.Element(totalVolRateTag,totalVolRateAttributes)) + + totalMassRateTag= constraintType+"MassRateConstraint" + totalMassRateAttributes={} + totalMassRateAttributes["name"]="maxmassrateprod" if isProducer else "maxmassrateinj" + if 'targetMassRate' in elem.attrib or 'targetMassRateTableName' in elem.attrib: + if 'targetMassRate' in elem.attrib: + totalMassRateAttributes["massRate"]=elem.attrib["targetMassRate"] + if delete_old_schema: + elem.attrib.pop("targetMassRate") + if 'targetMassRateTableName' in elem.attrib: + totalMassRateAttributes["constraintScheduleTableName"]=elem.attrib["targetMassRateTableName"] + if delete_old_schema: + elem.attrib.pop("targetMassRateTableName") + if not isProducer: + if True: #compoFluidModel: + totalMassRateAttributes["injectionStream"]=elem.attrib["injectionStream"] + totalMassRateAttributes["injectionTemperature"]=elem.attrib["injectionTemperature"] + if delete_old_schema: + elem.attrib.pop("injectionStream") + elem.attrib.pop("injectionTemperature") + elem.append(ET.Element(totalMassRateTag,totalMassRateAttributes)) + + wc={} + isCompositional=False + nlsparms={} + useMass=False + useMasVal="0" + for elem in root.iter(): + if elem.tag == "CompositionalMultiphaseWell": + isCompositional=True + if "maxRelativePressureChange" in elem.attrib: + nlsparms["maxRelativePressureChange"]=elem.attrib["maxRelativePressureChange"] + elem.attrib.pop("maxRelativePressureChange") + if "maxCompFractionChange" in elem.attrib: + nlsparms["maxCompFractionChange"]=elem.attrib["maxCompFractionChange"] + elem.attrib.pop("maxCompFractionChange") + if "useMass" in elem.attrib: + useMass=True + useMassVal=elem.attrib["useMass"] + elem.attrib.pop("useMass") + if "writeCSV" in elem.attrib: + nlsparms["writeCSV"]=elem.attrib["writeCSV"] + elem.attrib.pop("writeCSV") + + break + elif elem.tag == "SinglePhaseWell": + if "writeCSV" in elem.attrib: + nlsparms["writeCSV"]=elem.attrib["writeCSV"] + elem.attrib.pop("writeCSV") + for elem in root.iter(): + if elem.tag == "CompositionalMultiphaseWell" or elem.tag == "SinglePhaseWell": + elem.tag="WellManager" + if useMass: + elem.set("useMass",useMassVal) + + if elem.tag == "WellControls": + for k in nlsparms: + elem.attrib[k] = nlsparms[k] + if isCompositional: + elem.tag="CompositionalMultiphaseWell" + else: + elem.tag="SinglePhaseWell" + + return True ,tree + +def removeUseMass(xml_file,delete_old_schema,add_we,estimatorSolves): + with open(xml_file, 'rb') as xml_file: + tree = ET.parse(xml_file) + root = tree.getroot() + compoFluidModel = True + isThermal=False + + wc={} + isCompositional=False + nlsparms={} + useMass=False + useMasVal="0" + mod=False + for elem in root.iter(): + if elem.tag == "CompositionalMultiphaseWell": + isCompositional=True + if "useMass" in elem.attrib: + elem.attrib.pop("useMass") + mod=True + + return mod,tree + +def add_we(xml_file,estimatorSolves): + with open(xml_file, 'rb') as xml_file: + tree = ET.parse(xml_file) + root = tree.getroot() + compoFluidModel = True + isThermal=False + + wc={} + isCompositional=False + nlsparms={} + useMass=False + useMasVal="0" + mod=True + for elem in root.iter(): + if elem.tag == "CompositionalMultiphaseWell" or elem.tag == "SinglePhaseWell": + elem.attrib["estimateWellSolution"] = str(estimatorSolves) + + return mod,tree + +def main1(ifs,ofs,add_we,estimatorSolves,args): + try: + mod,tree= update_constraint_element(ifs, add_we,estimatorSolves) + #mod,tree= removeUseMass(ifs, delete_old_schema,add_we,estimatorSolves) + if mod: + wrap_width=80 + indent=" " + attr_per_line=True + pretty_print_file(tree, ofs, indent, wrap_width, attr_per_line,False) + # Optionally validate against XSD + if args.xsd: + ok, msg = validate_with_xsd(tree, args.xsd) + if not ok: + print("XSD validation failed:", msg, file=sys.stderr) + # still write a diagnostic copy + tree.write(ofs + ".failed.xml", encoding="utf-8", xml_declaration=True) + print("XSD failed for ",ofs ) + xml_formatter.format_file(ofs + ".failed.xml") + else: + print("XSD validation passed. Wrote to ",ofs) + except Exception as e: + print("Error occurred:", e,ifs) + +def add_estimator(ifs,ofs,estimatorSolves,args): + try: + #tree= updateconstraint_element(ifs, delete_old_schema,add_we,estimatorSolves) + mod,tree= add_we(ifs, estimatorSolves) + if mod: + wrap_width=80 + indent=" " + attr_per_line=True + pretty_print_file(tree, ofs, indent, wrap_width, attr_per_line,args.pretty) + # Optionally validate against XSD + if args.xsd: + ok, msg = validate_with_xsd(tree, args.xsd) + if not ok: + print("XSD validation failed:", msg, file=sys.stderr) + # still write a diagnostic copy + tree.write(ofs + ".failed.xml", encoding="utf-8", xml_declaration=True) + print("XSD failed for ",ofs ) + xml_formatter.format_file(ofs + ".failed.xml") + else: + print("XSD validation passed. Wrote to ",ofs) + except Exception as e: + print("Error occurred:", e,ifs) + +def main(): + # Two modes: single file or batch from list + # To create a list of files to process + # egrep -l -m1 -R "WellControl|SinglePhaseWell|CompositionalMultiphaseWell" ./ --include=\*.xml > convert_lst.txt + ## convert from file list + # cws_v3 -d --xsd=/Users/byer3/GEOS-DEV-1105/wc1014/src/coreComponents/schema/schema.xsd -r -f convert_lst.txt | tee convert.log + ## convert single file inplace + # cws_v3.py -scompositionalMultiphaseWell/compositional_multiphase_wells_1d.xml --xsd=/Users/byer3/GEOS-DEV/wmwe0603/src/coreComponents/schema/schema.xsd + + p = argparse.ArgumentParser(description="Add attributes and/or child elements to XML") + p.add_argument("-s", "--sourcefile", required=False, help="source XML file") + p.add_argument("-t", "--targetfile", required=False, help="output XML file") + p.add_argument("-a", "--add", action="store_true",default=False, help="add we strings") + p.add_argument("-e", "--estimatorSolves", type=int ,default=0, help="when to use estimator") + p.add_argument("-r", "--replace", action="store_true",default=True, help="in place substitution") + p.add_argument("-f", "--file", type=str ,default="", help="file with list of files to process") + p.add_argument("--xsd", required=True, help=" XSD file to validate output (requires lxml)") + args = p.parse_args() + + ofn_tag="" + if args.estimatorSolves >0 and not args.add: + ofn_tag+="we"+str(args.estimatorSolves) + if args.file: + if os.path.exists(args.file): + ifs = open(args.file,"r") + for f in ifs: + args.sourcefile=f.rstrip().lstrip() + if args.replace: + fn1=f.rstrip().lstrip() + fn2=fn1 + else: + fns = f.split() + fn1=fns[0].rstrip().lstrip() + if len(ofn_tag)>0: + fn2=fns[0].rstrip().lstrip() + fn2=fn2.replace(".xml",ofn_tag+".xml") + else: + fn2=fns[1].rstrip().lstrip() + print("Processing ",fn1,fn2,args.add,args.estimatorSolves) + if args.estimatorSolves >0 : + add_estimator(fn1,fn2,args.estimatorSolves,args) + else: + main1(fn1,fn2,args.add,args.estimatorSolves,args) + else: + print("File with list of files to process not found ",args.file) + + else: + if os.path.exists(args.sourcefile): + if not args.targetfile: + args.targetfile=args.sourcefile + main1(args.sourcefile,args.targetfile,args.add,args.estimatorSolves,args) + else: + print("Source file not found ",args.sourcefile) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/scripts/xml_formatter.py b/scripts/xml_formatter.py new file mode 100644 index 00000000000..1656aedf78f --- /dev/null +++ b/scripts/xml_formatter.py @@ -0,0 +1,205 @@ +import os +from lxml import etree as ElementTree # type: ignore[import] +import re +from typing import List, Any, TextIO + + + +def format_attribute( attribute_indent: str, ka: str, attribute_value: str ) -> str: + """Format xml attribute strings. + + Args: + attribute_indent (str): Attribute indent string + ka (str): Attribute name + attribute_value (str): Attribute value + + Returns: + str: Formatted attribute value + """ + # Make sure that a space follows commas + attribute_value = re.sub( r",\s*", ", ", attribute_value ) + + # Handle external brackets + attribute_value = re.sub( r"{\s*", "{ ", attribute_value ) + attribute_value = re.sub( r"\s*}", " }", attribute_value ) + + # Consolidate whitespace + attribute_value = re.sub( r"\s+", " ", attribute_value ) + + # Identify and split multi-line attributes + if re.match( r"\s*{\s*({[-+.,0-9a-zA-Z\s]*},?\s*)*\s*}", attribute_value ): + split_positions: List[ Any ] = [ match.end() for match in re.finditer( r"}\s*,", attribute_value ) ] + newline_indent = '\n%s' % ( ' ' * ( len( attribute_indent ) + len( ka ) + 4 ) ) + new_values = [] + for a, b in zip( [ None ] + split_positions, split_positions + [ None ], strict=False ): + new_values.append( attribute_value[ a:b ].strip() ) + if new_values: + attribute_value = newline_indent.join( new_values ) + + return attribute_value + + +def format_xml_level( output: TextIO, + node: ElementTree.Element, + level: int, + indent: str = ' ' * 2, + block_separation_max_depth: int = 2, + modify_attribute_indent: bool = False, + sort_attributes: bool = False, + close_tag_newline: bool = False, + include_namespace: bool = False ) -> None: + """Iteratively format the xml file. + + Args: + output (file): the output text file handle + node (lxml.etree.Element): the current xml element + level (int): the xml depth + indent (str): the xml indent style + block_separation_max_depth (int): the maximum depth to separate adjacent elements + modify_attribute_indent (bool): option to have flexible attribute indentation + sort_attributes (bool): option to sort attributes alphabetically + close_tag_newline (bool): option to place close tag on a separate line + include_namespace (bool): option to include the xml namespace in the output + """ + # Handle comments + if node.tag is ElementTree.Comment: + output.write( '\n%s' % ( indent * level, node.text ) ) + + else: + # Write opening line + opening_line = '\n%s<%s' % ( indent * level, node.tag ) + output.write( opening_line ) + + # Write attributes + if ( len( node.attrib ) > 0 ): + # Choose indentation + attribute_indent = '%s' % ( indent * ( level + 1 ) ) + if modify_attribute_indent: + attribute_indent = ' ' * ( len( opening_line ) ) + + # Get a copy of the attributes + attribute_dict = {} + if ( ( level == 0 ) & include_namespace ): + # Handle the optional namespace information at the root level + # Note: preferably, this would point to a schema we host online + attribute_dict[ 'xmlns:xsi' ] = 'http://www.w3.org/2001/XMLSchema-instance' + attribute_dict[ 'xsi:noNamespaceSchemaLocation' ] = '/usr/gapps/GEOS/schema/schema.xsd' + elif ( level > 0 ): + attribute_dict = node.attrib + + # Sort attribute names + akeys = list( attribute_dict.keys() ) + if sort_attributes: + akeys = sorted( akeys ) + + # Format attributes + for ka in akeys: + # Avoid formatting mathpresso expressions + if not ( node.tag in [ "SymbolicFunction", "CompositeFunction" ] and ka == "expression" ): + attribute_dict[ ka ] = format_attribute( attribute_indent, ka, attribute_dict[ ka ] ) + + for ii in range( 0, len( akeys ) ): + k = akeys[ ii ] + if ( ( ii == 0 ) & modify_attribute_indent ): + output.write( ' %s=\"%s\"' % ( k, attribute_dict[ k ] ) ) + else: + output.write( '\n%s%s=\"%s\"' % ( attribute_indent, k, attribute_dict[ k ] ) ) + + # Write children + if len( node ): + output.write( '>' ) + Nc = len( node ) + for ii, child in zip( range( Nc ), node, strict=False ): + format_xml_level( output, child, level + 1, indent, block_separation_max_depth, modify_attribute_indent, + sort_attributes, close_tag_newline, include_namespace ) + + # Add space between blocks + if ( ( level < block_separation_max_depth ) & ( ii < Nc - 1 ) & + ( child.tag is not ElementTree.Comment ) ): + output.write( '\n' ) + + # Write the end tag + output.write( '\n%s' % ( indent * level, node.tag ) ) + else: + if close_tag_newline: + output.write( '\n%s/>' % ( indent * level ) ) + else: + output.write( '/>' ) + + +def format_file( input_fname: str, + indent_size: int = 2, + indent_style: bool = False, + block_separation_max_depth: int = 2, + alphebitize_attributes: bool = False, + close_style: bool = False, + namespace: bool = False ) -> None: + """Script to format xml files. + + Args: + input_fname (str): Input file name + indent_size (int): Indent size + indent_style (bool): Style of indentation (0=fixed, 1=hanging) + block_separation_max_depth (int): Max depth to separate xml blocks + alphebitize_attributes (bool): Alphebitize attributes + close_style (bool): Style of close tag (0=same line, 1=new line) + namespace (bool): Insert this namespace in the xml description + """ + fname = os.path.expanduser( input_fname ) + try: + tree = ElementTree.parse( fname ) + root = tree.getroot() + prologue_comments = [ tmp.text for tmp in root.itersiblings( preceding=True ) ] + epilog_comments = [ tmp.text for tmp in root.itersiblings() ] + + with open( fname, 'w' ) as f: + f.write( '\n' ) + + for comment in reversed( prologue_comments ): + f.write( '\n' % ( comment ) ) + + format_xml_level( f, + root, + 0, + indent=' ' * indent_size, + block_separation_max_depth=block_separation_max_depth, + modify_attribute_indent=indent_style, + sort_attributes=alphebitize_attributes, + close_tag_newline=close_style, + include_namespace=namespace ) + + for comment in epilog_comments: + f.write( '\n' % ( comment ) ) + f.write( '\n' ) + + except ElementTree.ParseError as err: + print( '\nCould not load file: %s' % ( fname ) ) + print( err.msg ) + raise Exception( '\nCheck input file!' ) from err + + +def main() -> None: + """Script to format xml files. + + Args: + input (str): Input file name + -i/--indent (int): Indent size + -s/--style (int): Indent style + -d/--depth (int): Block separation depth + -a/--alphebitize (int): Alphebitize attributes + -c/--close (int): Close tag style + -n/--namespace (int): Include namespace + """ + parser = command_line_parsers.build_xml_formatter_input_parser() + args = parser.parse_args() + format_file( args.input, + indent_size=args.indent, + indent_style=args.style, + block_separation_max_depth=args.depth, + alphebitize_attributes=args.alphebitize, + close_style=args.close, + namespace=args.namespace ) + + +if __name__ == "__main__": + main() diff --git a/src/coreComponents/common/format/table/TableFormatter.hpp b/src/coreComponents/common/format/table/TableFormatter.hpp index eed907975c2..5100336e302 100644 --- a/src/coreComponents/common/format/table/TableFormatter.hpp +++ b/src/coreComponents/common/format/table/TableFormatter.hpp @@ -57,6 +57,12 @@ class TableFormatter TableErrorListing & getErrorsList() const { return *m_errors; } + /** + * @return The prepared table layout + */ + PreparedTableLayout const & getLayout() const + { return m_tableLayout; } + protected: /// Layout for a table diff --git a/src/coreComponents/common/format/table/TableTypes.hpp b/src/coreComponents/common/format/table/TableTypes.hpp index cfea392d21b..d42d2fd2a33 100644 --- a/src/coreComponents/common/format/table/TableTypes.hpp +++ b/src/coreComponents/common/format/table/TableTypes.hpp @@ -82,7 +82,7 @@ class TableErrorListing * @brief Append a vector of string to the errors vector. * @param errors A vector of string to append */ - void appendErrors( stdVector< string > & errors ) + void appendErrors( stdVector< string > const & errors ) { m_errorList.insert( m_errorList.end(), errors.begin(), errors.end() );} /** diff --git a/src/coreComponents/dataRepository/DataContext.hpp b/src/coreComponents/dataRepository/DataContext.hpp index 8a43381b438..f34ca3c8687 100644 --- a/src/coreComponents/dataRepository/DataContext.hpp +++ b/src/coreComponents/dataRepository/DataContext.hpp @@ -78,7 +78,7 @@ class DataContext /** * @return Get the target object name */ - string getTargetName() const + string const & getTargetName() const { return m_targetName; } /** * @brief Insert contextual information in the provided stream. @@ -179,13 +179,13 @@ class DataFileContext final : public DataContext /** * @return the type name in the source file (XML node tag name / attribute name). */ - string getTypeName() const + string const & getTypeName() const { return m_typeName; } /** * @return the source file path where the target object has been declared. */ - string getFilePath() const + string const & getFilePath() const { return m_filePath; } /** diff --git a/src/coreComponents/fileIO/doc/InputXMLFiles.rst b/src/coreComponents/fileIO/doc/InputXMLFiles.rst index 15fb463ca82..fb1b66ab32c 100644 --- a/src/coreComponents/fileIO/doc/InputXMLFiles.rst +++ b/src/coreComponents/fileIO/doc/InputXMLFiles.rst @@ -266,7 +266,7 @@ Advanced XML Features ================================= The `geosx_xml_tools` python package adds a set of advanced features to the GEOS xml format: units, parameters, and symbolic expressions. -See`Python Tools Setup `_ for details on setup instructions, and `XML Parser Documentation `_ for package API details. +See `Python Tools Setup `_ for details on setup instructions, and `XML Parser Documentation `_ for package API details. Usage diff --git a/src/coreComponents/fileIO/doc/OutputTasks.rst b/src/coreComponents/fileIO/doc/OutputTasks.rst index b8d91d92fed..9d79f9440bc 100644 --- a/src/coreComponents/fileIO/doc/OutputTasks.rst +++ b/src/coreComponents/fileIO/doc/OutputTasks.rst @@ -195,7 +195,7 @@ time history datat. .. _SILO: https://wci.llnl.gov/simulation/computer-codes/silo .. _VTK: https://vtk.org/wp-content/uploads/2015/04/file-formats.pdf -.. _HDF5: https://portal.hdfgroup.org/display/HDF5/HDF5 +.. _HDF5: https://www.hdfgroup.org/solutions/hdf5 .. _VisIT: https://wci.llnl.gov/simulation/computer-codes/visit/downloads .. _Paraview: https://www.paraview.org/ .. _MatPlotLib: https://matplotlib.org/ diff --git a/src/coreComponents/integrationTests/tableFunctionsFileTests/testTableFunctionsOutput.cpp b/src/coreComponents/integrationTests/tableFunctionsFileTests/testTableFunctionsOutput.cpp index bbb29ff0972..ae5394e2d26 100644 --- a/src/coreComponents/integrationTests/tableFunctionsFileTests/testTableFunctionsOutput.cpp +++ b/src/coreComponents/integrationTests/tableFunctionsFileTests/testTableFunctionsOutput.cpp @@ -84,26 +84,31 @@ char const * xmlInput = targetRegions="{ region }"> - - + - + referenceElevation="-0.01"/> + + + diff --git a/src/coreComponents/integrationTests/wellsTests/CMakeLists.txt b/src/coreComponents/integrationTests/wellsTests/CMakeLists.txt index badcac28453..0206cc917b9 100644 --- a/src/coreComponents/integrationTests/wellsTests/CMakeLists.txt +++ b/src/coreComponents/integrationTests/wellsTests/CMakeLists.txt @@ -3,6 +3,8 @@ set( gtest_geosx_tests testIsothermalReservoirCompositionalMultiphaseMSWells.cpp testIsothermalReservoirCompositionalMultiphaseSSWells.cpp testOpenClosePerf.cpp + testThermalEstimatorProdWell.cpp + testThermalEstimatorInjWell.cpp testReservoirCompositionalMultiphaseMSWells.cpp testReservoirSinglePhaseMSWells.cpp testReservoirThermalSinglePhaseMSWells.cpp diff --git a/src/coreComponents/integrationTests/wellsTests/testIsothermalReservoirCompositionalMultiphaseMSWells.cpp b/src/coreComponents/integrationTests/wellsTests/testIsothermalReservoirCompositionalMultiphaseMSWells.cpp index da7d3606fb2..0736e182dca 100644 --- a/src/coreComponents/integrationTests/wellsTests/testIsothermalReservoirCompositionalMultiphaseMSWells.cpp +++ b/src/coreComponents/integrationTests/wellsTests/testIsothermalReservoirCompositionalMultiphaseMSWells.cpp @@ -94,26 +94,31 @@ char const * xmlInput = targetRegions="{ region }"> - - + surfaceTemperature="300.15"> + + + @@ -300,7 +305,7 @@ void testNumericalJacobian( CompositionalMultiphaseReservoirAndWells< Compositio real64 const relTol, LAMBDA && assembleFunction ) { - CompositionalMultiphaseWell & wellSolver = *solver.wellSolver(); + WellManager & wellSolver = *solver.wellSolver(); CompositionalMultiphaseFVM & flowSolver = dynamicCast< CompositionalMultiphaseFVM & >( *solver.reservoirSolver() ); localIndex const NC = flowSolver.numFluidComponents(); @@ -463,7 +468,7 @@ void testNumericalJacobian( CompositionalMultiphaseReservoirAndWells< Compositio wellElemCompDens.move( hostMemorySpace, false ); arrayView1d< real64 > const & connRate = - subRegion.getField< fields::well::mixtureConnectionRate >(); + subRegion.getField< fields::well::connectionRate >(); connRate.move( hostMemorySpace, false ); // a) compute all the derivatives wrt to the pressure in WELL elem iwelem @@ -580,6 +585,23 @@ class CompositionalMultiphaseReservoirSolverTest : public ::testing::Test solver->getSystemSolution() ); solver->implicitStepSetup( time, dt, domain ); + WellManager & wellSolver = *solver->wellSolver(); + wellSolver.forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const & meshBodyName, + MeshLevel & meshLevel, + string_array const & regionNames ) + { + GEOS_UNUSED_VAR( meshBodyName ); + ElementRegionManager & elementRegionManager = meshLevel.getElemManager(); + elementRegionManager.forElementRegions< WellElementRegion >( regionNames, + [&]( localIndex const, + WellElementRegion & region ) + { + WellElementSubRegion & subRegion = region.getGroup( ElementRegionBase::viewKeyStruct::elementSubRegions() ) + .getGroup< WellElementSubRegion >( region.getSubRegionName() ); + WellControls & wellControls = wellSolver.getWellControls( subRegion ); + wellControls.initializeWell( domain, domain.getMeshBodies(), meshBodyName, meshLevel, subRegion, time ); + } ); + } ); } static real64 constexpr time = 0.0; @@ -641,7 +663,23 @@ TEST_F( CompositionalMultiphaseReservoirSolverTest, jacobianNumericalCheck_Accum [&] ( CRSMatrixView< real64, globalIndex const > const & localMatrix, arrayView1d< real64 > const & localRhs ) { - solver->wellSolver()->assembleAccumulationTerms( time, dt, domain, solver->getDofManager(), localMatrix, localRhs ); + WellManager & wellSolver = *solver->wellSolver(); + wellSolver.forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const & meshBodyName, + MeshLevel & meshLevel, + string_array const & regionNames ) + { + GEOS_UNUSED_VAR( meshBodyName ); + ElementRegionManager & elementRegionManager = meshLevel.getElemManager(); + elementRegionManager.forElementRegions< WellElementRegion >( regionNames, + [&]( localIndex const, + WellElementRegion & region ) + { + WellElementSubRegion & subRegion = region.getGroup( ElementRegionBase::viewKeyStruct::elementSubRegions() ) + .getGroup< WellElementSubRegion >( region.getSubRegionName() ); + WellControls & wellControls = wellSolver.getWellControls( subRegion ); + wellControls.assembleWellAccumulationTerms( time, dt, subRegion, solver->getDofManager(), localMatrix, localRhs ); + } ); + } ); } ); } diff --git a/src/coreComponents/integrationTests/wellsTests/testIsothermalReservoirCompositionalMultiphaseSSWells.cpp b/src/coreComponents/integrationTests/wellsTests/testIsothermalReservoirCompositionalMultiphaseSSWells.cpp index 277c7a2a38c..9bf476f7cb4 100644 --- a/src/coreComponents/integrationTests/wellsTests/testIsothermalReservoirCompositionalMultiphaseSSWells.cpp +++ b/src/coreComponents/integrationTests/wellsTests/testIsothermalReservoirCompositionalMultiphaseSSWells.cpp @@ -94,26 +94,31 @@ char const * xmlInput = targetRegions="{ region }"> - - - + surfaceTemperature="300.15"> + + + + @@ -301,7 +306,7 @@ void testNumericalJacobian( CompositionalMultiphaseReservoirAndWells< Compositio real64 const relTol, LAMBDA && assembleFunction ) { - CompositionalMultiphaseWell & wellSolver = *solver.wellSolver(); + WellManager & wellSolver = *solver.wellSolver(); CompositionalMultiphaseFVM & flowSolver = dynamicCast< CompositionalMultiphaseFVM & >( *solver.reservoirSolver() ); localIndex const NC = flowSolver.numFluidComponents(); @@ -510,7 +515,7 @@ void testNumericalJacobian( CompositionalMultiphaseReservoirAndWells< Compositio wellElemCompDens.move( hostMemorySpace, false ); arrayView1d< real64 > const & connRate = - subRegion.getField< fields::well::mixtureConnectionRate >(); + subRegion.getField< fields::well::connectionRate >(); connRate.move( hostMemorySpace, false ); // a) compute all the derivatives wrt to the pressure in WELL elem iwelem @@ -531,7 +536,6 @@ void testNumericalJacobian( CompositionalMultiphaseReservoirAndWells< Compositio real64 const dP = perturbParameter * ( wellElemPressure[iwelem] + perturbParameter ); wellElemPressure.move( hostMemorySpace, true ); wellElemPressure[iwelem] += dP; - // after perturbing, update the pressure-dependent quantities in the well wellSolver.updateState( domain ); @@ -652,6 +656,23 @@ class CompositionalMultiphaseReservoirSolverTest : public ::testing::Test solver->getSystemSolution() ); solver->implicitStepSetup( time, dt, domain ); + WellManager & wellSolver = *solver->wellSolver(); + wellSolver.forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const & meshBodyName, + MeshLevel & meshLevel, + string_array const & regionNames ) + { + GEOS_UNUSED_VAR( meshBodyName ); + ElementRegionManager & elementRegionManager = meshLevel.getElemManager(); + elementRegionManager.forElementRegions< WellElementRegion >( regionNames, + [&]( localIndex const, + WellElementRegion & region ) + { + WellElementSubRegion & subRegion = region.getGroup( ElementRegionBase::viewKeyStruct::elementSubRegions() ) + .getGroup< WellElementSubRegion >( region.getSubRegionName() ); + WellControls & wellControls = wellSolver.getWellControls( subRegion ); + wellControls.initializeWell( domain, domain.getMeshBodies(), meshBodyName, meshLevel, subRegion, time ); + } ); + } ); } static real64 constexpr time = 0.0; @@ -699,7 +720,23 @@ TEST_F( CompositionalMultiphaseReservoirSolverTest, jacobianNumericalCheck_Accum [&] ( CRSMatrixView< real64, globalIndex const > const & localMatrix, arrayView1d< real64 > const & localRhs ) { - solver->wellSolver()->assembleAccumulationTerms( time, dt, domain, solver->getDofManager(), localMatrix, localRhs ); + WellManager & wellSolver = *solver->wellSolver(); + wellSolver.forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const & meshBodyName, + MeshLevel & meshLevel, + string_array const & regionNames ) + { + GEOS_UNUSED_VAR( meshBodyName ); + ElementRegionManager & elementRegionManager = meshLevel.getElemManager(); + elementRegionManager.forElementRegions< WellElementRegion >( regionNames, + [&]( localIndex const, + WellElementRegion & region ) + { + WellElementSubRegion & subRegion = region.getGroup( ElementRegionBase::viewKeyStruct::elementSubRegions() ) + .getGroup< WellElementSubRegion >( region.getSubRegionName() ); + WellControls & wellControls = wellSolver.getWellControls( subRegion ); + wellControls.assembleWellAccumulationTerms( time, dt, subRegion, solver->getDofManager(), localMatrix, localRhs ); + } ); + } ); } ); } #endif diff --git a/src/coreComponents/integrationTests/wellsTests/testOpenClosePerf.cpp b/src/coreComponents/integrationTests/wellsTests/testOpenClosePerf.cpp index 36a32117b62..136aaf417cb 100644 --- a/src/coreComponents/integrationTests/wellsTests/testOpenClosePerf.cpp +++ b/src/coreComponents/integrationTests/wellsTests/testOpenClosePerf.cpp @@ -57,18 +57,24 @@ char const * PreXmlInput = temperature="297.15" useMass="0"> - - - + + + + + & solv LAMBDA && perfFunction ) { - CompositionalMultiphaseWell & wellSolver = *solver.wellSolver(); + WellManager & wellSolver = *solver.wellSolver(); typedef stdMap< real64, stdVector< int > > map_type; map_type refVal; @@ -214,7 +220,7 @@ void testPlugBottomUpPerfCheck( CompositionalMultiphaseReservoirAndWells<> & sol LAMBDA && perfFunction ) { - CompositionalMultiphaseWell & wellSolver = *solver.wellSolver(); + WellManager & wellSolver = *solver.wellSolver(); typedef stdMap< real64, stdVector< int > > map_type; map_type refVal; @@ -257,7 +263,7 @@ void testOpenTopDownPerfCheck( CompositionalMultiphaseReservoirAndWells<> & solv LAMBDA && perfFunction ) { - CompositionalMultiphaseWell & wellSolver = *solver.wellSolver(); + WellManager & wellSolver = *solver.wellSolver(); typedef stdMap< real64, stdVector< int > > map_type; map_type refPerfTable; @@ -299,7 +305,7 @@ void testOpenBottomUpPerfCheck( CompositionalMultiphaseReservoirAndWells<> & sol LAMBDA && perfFunction ) { - CompositionalMultiphaseWell & wellSolver = *solver.wellSolver(); + WellManager & wellSolver = *solver.wellSolver(); typedef stdMap< real64, stdVector< int > > map_type; map_type refVal; @@ -513,8 +519,23 @@ TEST_F( CompositionalMultiphaseReservoirSolverTest, plugTopDownPerfCheck ) testPlugTopDownPerfCheck( *solver, domain, [&] ( real64 time ) { - WellSolverBase * wellSolverBase = solver->wellSolver(); - wellSolverBase->setPerforationStatus( time, domain ); + WellManager & wellSolver = *solver->wellSolver(); + wellSolver.forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const & meshBodyName, + MeshLevel & meshLevel, + string_array const & regionNames ) + { + GEOS_UNUSED_VAR( meshBodyName ); + ElementRegionManager & elementRegionManager = meshLevel.getElemManager(); + elementRegionManager.forElementRegions< WellElementRegion >( regionNames, + [&]( localIndex const, + WellElementRegion & region ) + { + WellElementSubRegion & subRegion = region.getGroup( ElementRegionBase::viewKeyStruct::elementSubRegions() ) + .getGroup< WellElementSubRegion >( region.getSubRegionName() ); + WellControls & wellControls = wellSolver.getWellControls( subRegion ); + wellControls.setPerforationStatus( time, subRegion ); + } ); + } ); } ); } @@ -527,8 +548,23 @@ TEST_F( CompositionalMultiphaseReservoirSolverTest, plugBottomUpPerfCheck ) testPlugBottomUpPerfCheck( *solver, domain, [&] ( real64 time ) { - WellSolverBase * wellSolverBase = solver->wellSolver(); - wellSolverBase->setPerforationStatus( time, domain ); + WellManager & wellSolver = *solver->wellSolver(); + wellSolver.forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const & meshBodyName, + MeshLevel & meshLevel, + string_array const & regionNames ) + { + GEOS_UNUSED_VAR( meshBodyName ); + ElementRegionManager & elementRegionManager = meshLevel.getElemManager(); + elementRegionManager.forElementRegions< WellElementRegion >( regionNames, + [&]( localIndex const, + WellElementRegion & region ) + { + WellElementSubRegion & subRegion = region.getGroup( ElementRegionBase::viewKeyStruct::elementSubRegions() ) + .getGroup< WellElementSubRegion >( region.getSubRegionName() ); + WellControls & wellControls = wellSolver.getWellControls( subRegion ); + wellControls.setPerforationStatus( time, subRegion ); + } ); + } ); } ); } TEST_F( CompositionalMultiphaseReservoirSolverTest, openTopDownPerfCheck ) @@ -540,8 +576,23 @@ TEST_F( CompositionalMultiphaseReservoirSolverTest, openTopDownPerfCheck ) testOpenTopDownPerfCheck( *solver, domain, [&] ( real64 time ) { - WellSolverBase * wellSolverBase = solver->wellSolver(); - wellSolverBase->setPerforationStatus( time, domain ); + WellManager & wellSolver = *solver->wellSolver(); + wellSolver.forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const & meshBodyName, + MeshLevel & meshLevel, + string_array const & regionNames ) + { + GEOS_UNUSED_VAR( meshBodyName ); + ElementRegionManager & elementRegionManager = meshLevel.getElemManager(); + elementRegionManager.forElementRegions< WellElementRegion >( regionNames, + [&]( localIndex const, + WellElementRegion & region ) + { + WellElementSubRegion & subRegion = region.getGroup( ElementRegionBase::viewKeyStruct::elementSubRegions() ) + .getGroup< WellElementSubRegion >( region.getSubRegionName() ); + WellControls & wellControls = wellSolver.getWellControls( subRegion ); + wellControls.setPerforationStatus( time, subRegion ); + } ); + } ); } ); } @@ -554,8 +605,24 @@ TEST_F( CompositionalMultiphaseReservoirSolverTest, openBottomUpPerfCheck ) testOpenBottomUpPerfCheck( *solver, domain, [&] ( real64 time ) { - WellSolverBase * wellSolverBase = solver->wellSolver(); - wellSolverBase->setPerforationStatus( time, domain ); + + WellManager & wellSolver = *solver->wellSolver(); + wellSolver.forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const & meshBodyName, + MeshLevel & meshLevel, + string_array const & regionNames ) + { + GEOS_UNUSED_VAR( meshBodyName ); + ElementRegionManager & elementRegionManager = meshLevel.getElemManager(); + elementRegionManager.forElementRegions< WellElementRegion >( regionNames, + [&]( localIndex const, + WellElementRegion & region ) + { + WellElementSubRegion & subRegion = region.getGroup( ElementRegionBase::viewKeyStruct::elementSubRegions() ) + .getGroup< WellElementSubRegion >( region.getSubRegionName() ); + WellControls & wellControls = wellSolver.getWellControls( subRegion ); + wellControls.setPerforationStatus( time, subRegion ); + } ); + } ); } ); } diff --git a/src/coreComponents/integrationTests/wellsTests/testReservoirCompositionalMultiphaseMSWells.cpp b/src/coreComponents/integrationTests/wellsTests/testReservoirCompositionalMultiphaseMSWells.cpp index 74f624e5d2e..b336ce89d3c 100644 --- a/src/coreComponents/integrationTests/wellsTests/testReservoirCompositionalMultiphaseMSWells.cpp +++ b/src/coreComponents/integrationTests/wellsTests/testReservoirCompositionalMultiphaseMSWells.cpp @@ -57,26 +57,38 @@ char const * xmlInput = temperature="297.15" useMass="0"> - - - + + + + + + + + & solver, real64 const relTol, LAMBDA && assembleFunction ) { - CompositionalMultiphaseWell & wellSolver = *solver.wellSolver(); + WellManager & wellSolver = *solver.wellSolver(); CompositionalMultiphaseFVM & flowSolver = dynamicCast< CompositionalMultiphaseFVM & >( *solver.reservoirSolver() ); localIndex const NC = flowSolver.numFluidComponents(); @@ -363,7 +375,7 @@ void testNumericalJacobian( CompositionalMultiphaseReservoirAndWells<> & solver, wellElemCompDens.move( hostMemorySpace, false ); arrayView1d< real64 > const & connRate = - subRegion.getField< fields::well::mixtureConnectionRate >(); + subRegion.getField< fields::well::connectionRate >(); connRate.move( hostMemorySpace, false ); // a) compute all the derivatives wrt to the pressure in WELL elem iwelem @@ -480,6 +492,23 @@ class CompositionalMultiphaseReservoirSolverTest : public ::testing::Test solver->getSystemSolution() ); solver->implicitStepSetup( time, dt, domain ); + WellManager & wellSolver = *solver->wellSolver(); + wellSolver.forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const & meshBodyName, + MeshLevel & meshLevel, + string_array const & regionNames ) + { + GEOS_UNUSED_VAR( meshBodyName ); + ElementRegionManager & elementRegionManager = meshLevel.getElemManager(); + elementRegionManager.forElementRegions< WellElementRegion >( regionNames, + [&]( localIndex const, + WellElementRegion & region ) + { + WellElementSubRegion & subRegion = region.getGroup( ElementRegionBase::viewKeyStruct::elementSubRegions() ) + .getGroup< WellElementSubRegion >( region.getSubRegionName() ); + WellControls & wellControls = wellSolver.getWellControls( subRegion ); + wellControls.initializeWell( domain, domain.getMeshBodies(), meshBodyName, meshLevel, subRegion, time ); + } ); + } ); } static real64 constexpr time = 0.0; @@ -525,7 +554,23 @@ TEST_F( CompositionalMultiphaseReservoirSolverTest, jacobianNumericalCheck_Flux [&] ( CRSMatrixView< real64, globalIndex const > const & localMatrix, arrayView1d< real64 > const & localRhs ) { - solver->wellSolver()->assembleFluxTerms( time, dt, domain, solver->getDofManager(), localMatrix, localRhs ); + WellManager & wellSolver = *solver->wellSolver(); + wellSolver.forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const & meshBodyName, + MeshLevel & meshLevel, + string_array const & regionNames ) + { + GEOS_UNUSED_VAR( meshBodyName ); + ElementRegionManager & elementRegionManager = meshLevel.getElemManager(); + elementRegionManager.forElementRegions< WellElementRegion >( regionNames, + [&]( localIndex const, + WellElementRegion & region ) + { + WellElementSubRegion & subRegion = region.getGroup( ElementRegionBase::viewKeyStruct::elementSubRegions() ) + .getGroup< WellElementSubRegion >( region.getSubRegionName() ); + WellControls & wellControls = wellSolver.getWellControls( subRegion ); + wellControls.assembleWellFluxTerms( time, dt, subRegion, solver->getDofManager(), localMatrix, localRhs ); + } ); + } ); } ); } @@ -542,7 +587,23 @@ TEST_F( CompositionalMultiphaseReservoirSolverTest, jacobianNumericalCheck_Press [&] ( CRSMatrixView< real64, globalIndex const > const & localMatrix, arrayView1d< real64 > const & localRhs ) { - solver->wellSolver()->assemblePressureRelations( time, dt, domain, solver->getDofManager(), localMatrix, localRhs ); + WellManager & wellSolver = *solver->wellSolver(); + wellSolver.forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const & meshBodyName, + MeshLevel & meshLevel, + string_array const & regionNames ) + { + GEOS_UNUSED_VAR( meshBodyName ); + ElementRegionManager & elementRegionManager = meshLevel.getElemManager(); + elementRegionManager.forElementRegions< WellElementRegion >( regionNames, + [&]( localIndex const, + WellElementRegion & region ) + { + WellElementSubRegion & subRegion = region.getGroup( ElementRegionBase::viewKeyStruct::elementSubRegions() ) + .getGroup< WellElementSubRegion >( region.getSubRegionName() ); + WellControls & wellControls = wellSolver.getWellControls( subRegion ); + wellControls.assembleWellPressureRelations( time, dt, subRegion, solver->getDofManager(), localMatrix, localRhs ); + } ); + } ); } ); } diff --git a/src/coreComponents/integrationTests/wellsTests/testReservoirSinglePhaseMSWells.cpp b/src/coreComponents/integrationTests/wellsTests/testReservoirSinglePhaseMSWells.cpp index d75ad50a957..031bded773d 100644 --- a/src/coreComponents/integrationTests/wellsTests/testReservoirSinglePhaseMSWells.cpp +++ b/src/coreComponents/integrationTests/wellsTests/testReservoirSinglePhaseMSWells.cpp @@ -58,22 +58,35 @@ char const * PreXmlInput = discretization="singlePhaseTPFA" targetRegions="{Region1}"> - - - - + + + + + + + + + + & solver, real64 const relTol, LAMBDA && assembleFunction ) { - SinglePhaseWell & wellSolver = *solver.wellSolver(); + WellManager & wellSolver = *solver.wellSolver(); SinglePhaseFVM< SinglePhaseBase > & flowSolver = dynamicCast< SinglePhaseFVM< SinglePhaseBase > & >( *solver.reservoirSolver() ); CRSMatrix< real64, globalIndex > const & jacobian = solver.getLocalMatrix(); @@ -357,6 +370,23 @@ class SinglePhaseReservoirSolverTest : public ::testing::Test solver->getSystemSolution() ); solver->implicitStepSetup( TIME, DT, domain ); + WellManager & wellSolver = *solver->wellSolver(); + wellSolver.forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const & meshBodyName, + MeshLevel & meshLevel, + string_array const & regionNames ) + { + GEOS_UNUSED_VAR( meshBodyName ); + ElementRegionManager & elementRegionManager = meshLevel.getElemManager(); + elementRegionManager.forElementRegions< WellElementRegion >( regionNames, + [&]( localIndex const, + WellElementRegion & region ) + { + WellElementSubRegion & subRegion = region.getGroup( ElementRegionBase::viewKeyStruct::elementSubRegions() ) + .getGroup< WellElementSubRegion >( region.getSubRegionName() ); + WellControls & wellControls = wellSolver.getWellControls( subRegion ); + wellControls.initializeWell( domain, domain.getMeshBodies(), meshBodyName, meshLevel, subRegion, TIME ); + } ); + } ); } void TestAssembleCouplingTerms() @@ -385,7 +415,23 @@ class SinglePhaseReservoirSolverTest : public ::testing::Test [&] ( CRSMatrixView< real64, globalIndex const > const & localMatrix, arrayView1d< real64 > const & localRhs ) { - solver->wellSolver()->assembleFluxTerms( TIME, DT, domain, solver->getDofManager(), localMatrix, localRhs ); + WellManager & wellSolver = *solver->wellSolver(); + wellSolver.forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const & meshBodyName, + MeshLevel & meshLevel, + string_array const & regionNames ) + { + GEOS_UNUSED_VAR( meshBodyName ); + ElementRegionManager & elementRegionManager = meshLevel.getElemManager(); + elementRegionManager.forElementRegions< WellElementRegion >( regionNames, + [&]( localIndex const, + WellElementRegion & region ) + { + WellElementSubRegion & subRegion = region.getGroup( ElementRegionBase::viewKeyStruct::elementSubRegions() ) + .getGroup< WellElementSubRegion >( region.getSubRegionName() ); + WellControls & wellControls = wellSolver.getWellControls( subRegion ); + wellControls.assembleWellFluxTerms( TIME, DT, subRegion, solver->getDofManager(), localMatrix, localRhs ); + } ); + } ); } ); } @@ -400,7 +446,23 @@ class SinglePhaseReservoirSolverTest : public ::testing::Test [&] ( CRSMatrixView< real64, globalIndex const > const & localMatrix, arrayView1d< real64 > const & localRhs ) { - solver->wellSolver()->assemblePressureRelations( TIME, DT, domain, solver->getDofManager(), localMatrix, localRhs ); + WellManager & wellSolver = *solver->wellSolver(); + wellSolver.forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const & meshBodyName, + MeshLevel & meshLevel, + string_array const & regionNames ) + { + GEOS_UNUSED_VAR( meshBodyName ); + ElementRegionManager & elementRegionManager = meshLevel.getElemManager(); + elementRegionManager.forElementRegions< WellElementRegion >( regionNames, + [&]( localIndex const, + WellElementRegion & region ) + { + WellElementSubRegion & subRegion = region.getGroup( ElementRegionBase::viewKeyStruct::elementSubRegions() ) + .getGroup< WellElementSubRegion >( region.getSubRegionName() ); + WellControls & wellControls = wellSolver.getWellControls( subRegion ); + wellControls.assembleWellPressureRelations( TIME, DT, subRegion, solver->getDofManager(), localMatrix, localRhs ); + } ); + } ); } ); } @@ -415,7 +477,23 @@ class SinglePhaseReservoirSolverTest : public ::testing::Test [&] ( CRSMatrixView< real64, globalIndex const > const & localMatrix, arrayView1d< real64 > const & localRhs ) { - solver->wellSolver()->assembleAccumulationTerms( TIME, DT, domain, solver->getDofManager(), localMatrix, localRhs ); + WellManager & wellSolver = *solver->wellSolver(); + wellSolver.forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const & meshBodyName, + MeshLevel & meshLevel, + string_array const & regionNames ) + { + GEOS_UNUSED_VAR( meshBodyName ); + ElementRegionManager & elementRegionManager = meshLevel.getElemManager(); + elementRegionManager.forElementRegions< WellElementRegion >( regionNames, + [&]( localIndex const, + WellElementRegion & region ) + { + WellElementSubRegion & subRegion = region.getGroup( ElementRegionBase::viewKeyStruct::elementSubRegions() ) + .getGroup< WellElementSubRegion >( region.getSubRegionName() ); + WellControls & wellControls = wellSolver.getWellControls( subRegion ); + wellControls.assembleWellAccumulationTerms( TIME, DT, subRegion, solver->getDofManager(), localMatrix, localRhs ); + } ); + } ); } ); } @@ -497,7 +575,24 @@ TEST_F( SinglePhaseReservoirSolverInternalWellTest, jacobianNumericalCheck_Flux [&] ( CRSMatrixView< real64, globalIndex const > const & localMatrix, arrayView1d< real64 > const & localRhs ) { - solver->wellSolver()->assembleFluxTerms( TIME, DT, domain, solver->getDofManager(), localMatrix, localRhs ); + + WellManager & wellSolver = *solver->wellSolver(); + wellSolver.forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const & meshBodyName, + MeshLevel & meshLevel, + string_array const & regionNames ) + { + GEOS_UNUSED_VAR( meshBodyName ); + ElementRegionManager & elementRegionManager = meshLevel.getElemManager(); + elementRegionManager.forElementRegions< WellElementRegion >( regionNames, + [&]( localIndex const, + WellElementRegion & region ) + { + WellElementSubRegion & subRegion = region.getGroup( ElementRegionBase::viewKeyStruct::elementSubRegions() ) + .getGroup< WellElementSubRegion >( region.getSubRegionName() ); + WellControls & wellControls = wellSolver.getWellControls( subRegion ); + wellControls.assembleWellFluxTerms( TIME, DT, subRegion, solver->getDofManager(), localMatrix, localRhs ); + } ); + } ); } ); } diff --git a/src/coreComponents/integrationTests/wellsTests/testReservoirThermalSinglePhaseMSWells.cpp b/src/coreComponents/integrationTests/wellsTests/testReservoirThermalSinglePhaseMSWells.cpp index 16cd7e0855c..d473b995dee 100644 --- a/src/coreComponents/integrationTests/wellsTests/testReservoirThermalSinglePhaseMSWells.cpp +++ b/src/coreComponents/integrationTests/wellsTests/testReservoirThermalSinglePhaseMSWells.cpp @@ -60,27 +60,40 @@ char const * PreXmlInput = discretization="singlePhaseTPFA" targetRegions="{Region1}"> - - - - + + + + + + + + + + & solver, LAMBDA && assembleFunction ) { GEOS_UNUSED_VAR( testName ); - SinglePhaseWell & wellSolver = *solver.wellSolver(); + WellManager & wellSolver = *solver.wellSolver(); SinglePhaseFVM< SinglePhaseBase > & flowSolver = dynamicCast< SinglePhaseFVM< SinglePhaseBase > & >( *solver.reservoirSolver() ); CRSMatrix< real64, globalIndex > const & jacobian = solver.getLocalMatrix(); @@ -513,6 +526,23 @@ class SinglePhaseReservoirSolverTest : public ::testing::Test solver->getSystemSolution() ); solver->implicitStepSetup( TIME, DT, domain ); + WellManager & wellSolver = *solver->wellSolver(); + wellSolver.forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const & meshBodyName, + MeshLevel & meshLevel, + string_array const & regionNames ) + { + GEOS_UNUSED_VAR( meshBodyName ); + ElementRegionManager & elementRegionManager = meshLevel.getElemManager(); + elementRegionManager.forElementRegions< WellElementRegion >( regionNames, + [&]( localIndex const, + WellElementRegion & region ) + { + WellElementSubRegion & subRegion = region.getGroup( ElementRegionBase::viewKeyStruct::elementSubRegions() ) + .getGroup< WellElementSubRegion >( region.getSubRegionName() ); + WellControls & wellControls = wellSolver.getWellControls( subRegion ); + wellControls.initializeWell( domain, domain.getMeshBodies(), meshBodyName, meshLevel, subRegion, TIME ); + } ); + } ); } void TestAssembleCouplingTerms() @@ -545,7 +575,23 @@ class SinglePhaseReservoirSolverTest : public ::testing::Test [&] ( CRSMatrixView< real64, globalIndex const > const & localMatrix, arrayView1d< real64 > const & localRhs ) { - solver->wellSolver()->assembleFluxTerms( TIME, DT, domain, solver->getDofManager(), localMatrix, localRhs ); + WellManager & wellSolver = *solver->wellSolver(); + wellSolver.forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const & meshBodyName, + MeshLevel & meshLevel, + string_array const & regionNames ) + { + GEOS_UNUSED_VAR( meshBodyName ); + ElementRegionManager & elementRegionManager = meshLevel.getElemManager(); + elementRegionManager.forElementRegions< WellElementRegion >( regionNames, + [&]( localIndex const, + WellElementRegion & region ) + { + WellElementSubRegion & subRegion = region.getGroup( ElementRegionBase::viewKeyStruct::elementSubRegions() ) + .getGroup< WellElementSubRegion >( region.getSubRegionName() ); + WellControls & wellControls = wellSolver.getWellControls( subRegion ); + wellControls.assembleWellFluxTerms( TIME, DT, subRegion, solver->getDofManager(), localMatrix, localRhs ); + } ); + } ); } ); } @@ -559,7 +605,23 @@ class SinglePhaseReservoirSolverTest : public ::testing::Test [&] ( CRSMatrixView< real64, globalIndex const > const & localMatrix, arrayView1d< real64 > const & localRhs ) { - solver->wellSolver()->assemblePressureRelations( TIME, DT, domain, solver->getDofManager(), localMatrix, localRhs ); + WellManager & wellSolver = *solver->wellSolver(); + wellSolver.forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const & meshBodyName, + MeshLevel & meshLevel, + string_array const & regionNames ) + { + GEOS_UNUSED_VAR( meshBodyName ); + ElementRegionManager & elementRegionManager = meshLevel.getElemManager(); + elementRegionManager.forElementRegions< WellElementRegion >( regionNames, + [&]( localIndex const, + WellElementRegion & region ) + { + WellElementSubRegion & subRegion = region.getGroup( ElementRegionBase::viewKeyStruct::elementSubRegions() ) + .getGroup< WellElementSubRegion >( region.getSubRegionName() ); + WellControls & wellControls = wellSolver.getWellControls( subRegion ); + wellControls.assembleWellPressureRelations( TIME, DT, subRegion, solver->getDofManager(), localMatrix, localRhs ); + } ); + } ); } ); } @@ -574,7 +636,23 @@ class SinglePhaseReservoirSolverTest : public ::testing::Test [&] ( CRSMatrixView< real64, globalIndex const > const & localMatrix, arrayView1d< real64 > const & localRhs ) { - solver->wellSolver()->assembleAccumulationTerms( TIME, DT, domain, solver->getDofManager(), localMatrix, localRhs ); + WellManager & wellSolver = *solver->wellSolver(); + wellSolver.forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const & meshBodyName, + MeshLevel & meshLevel, + string_array const & regionNames ) + { + GEOS_UNUSED_VAR( meshBodyName ); + ElementRegionManager & elementRegionManager = meshLevel.getElemManager(); + elementRegionManager.forElementRegions< WellElementRegion >( regionNames, + [&]( localIndex const, + WellElementRegion & region ) + { + WellElementSubRegion & subRegion = region.getGroup( ElementRegionBase::viewKeyStruct::elementSubRegions() ) + .getGroup< WellElementSubRegion >( region.getSubRegionName() ); + WellControls & wellControls = wellSolver.getWellControls( subRegion ); + wellControls.assembleWellAccumulationTerms( TIME, DT, subRegion, solver->getDofManager(), localMatrix, localRhs ); + } ); + } ); } ); } @@ -656,7 +734,24 @@ TEST_F( SinglePhaseReservoirSolverInternalWellTest, jacobianNumericalCheck_Flux [&] ( CRSMatrixView< real64, globalIndex const > const & localMatrix, arrayView1d< real64 > const & localRhs ) { - solver->wellSolver()->assembleFluxTerms( TIME, DT, domain, solver->getDofManager(), localMatrix, localRhs ); + + WellManager & wellSolver = *solver->wellSolver(); + wellSolver.forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const & meshBodyName, + MeshLevel & meshLevel, + string_array const & regionNames ) + { + GEOS_UNUSED_VAR( meshBodyName ); + ElementRegionManager & elementRegionManager = meshLevel.getElemManager(); + elementRegionManager.forElementRegions< WellElementRegion >( regionNames, + [&]( localIndex const, + WellElementRegion & region ) + { + WellElementSubRegion & subRegion = region.getGroup( ElementRegionBase::viewKeyStruct::elementSubRegions() ) + .getGroup< WellElementSubRegion >( region.getSubRegionName() ); + WellControls & wellControls = wellSolver.getWellControls( subRegion ); + wellControls.assembleWellFluxTerms( TIME, DT, subRegion, solver->getDofManager(), localMatrix, localRhs ); + } ); + } ); } ); } diff --git a/src/coreComponents/integrationTests/wellsTests/testReservoirThermalSinglePhaseMSWells_RateInj.cpp b/src/coreComponents/integrationTests/wellsTests/testReservoirThermalSinglePhaseMSWells_RateInj.cpp index 9169eeb0206..8e4776c9730 100644 --- a/src/coreComponents/integrationTests/wellsTests/testReservoirThermalSinglePhaseMSWells_RateInj.cpp +++ b/src/coreComponents/integrationTests/wellsTests/testReservoirThermalSinglePhaseMSWells_RateInj.cpp @@ -73,26 +73,30 @@ char const * XmlInput = targetRegions="{ region }"> - - + + referenceElevation="-0.01"/> + + @@ -305,7 +309,7 @@ void testNumericalJacobian( SinglePhaseReservoirAndWells<> & solver, { GEOS_UNUSED_VAR( testName ); - SinglePhaseWell & wellSolver = *solver.wellSolver(); + WellManager & wellSolver = *solver.wellSolver(); SinglePhaseFVM< SinglePhaseBase > & flowSolver = dynamicCast< SinglePhaseFVM< SinglePhaseBase > & >( *solver.reservoirSolver() ); CRSMatrix< real64, globalIndex > const & jacobian = solver.getLocalMatrix(); @@ -596,6 +600,23 @@ class SinglePhaseReservoirSolverTest : public ::testing::Test solver->getSystemSolution() ); solver->implicitStepSetup( TIME, DT, domain ); + WellManager & wellSolver = *solver->wellSolver(); + wellSolver.forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const & meshBodyName, + MeshLevel & meshLevel, + string_array const & regionNames ) + { + GEOS_UNUSED_VAR( meshBodyName ); + ElementRegionManager & elementRegionManager = meshLevel.getElemManager(); + elementRegionManager.forElementRegions< WellElementRegion >( regionNames, + [&]( localIndex const, + WellElementRegion & region ) + { + WellElementSubRegion & subRegion = region.getGroup( ElementRegionBase::viewKeyStruct::elementSubRegions() ) + .getGroup< WellElementSubRegion >( region.getSubRegionName() ); + WellControls & wellControls = wellSolver.getWellControls( subRegion ); + wellControls.initializeWell( domain, domain.getMeshBodies(), meshBodyName, meshLevel, subRegion, TIME ); + } ); + } ); } void TestAssembleCouplingTerms() @@ -624,7 +645,23 @@ class SinglePhaseReservoirSolverTest : public ::testing::Test [&] ( CRSMatrixView< real64, globalIndex const > const & localMatrix, arrayView1d< real64 > const & localRhs ) { - solver->wellSolver()->assembleFluxTerms( TIME, DT, domain, solver->getDofManager(), localMatrix, localRhs ); + WellManager & wellSolver = *solver->wellSolver(); + wellSolver.forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const & meshBodyName, + MeshLevel & meshLevel, + string_array const & regionNames ) + { + GEOS_UNUSED_VAR( meshBodyName ); + ElementRegionManager & elementRegionManager = meshLevel.getElemManager(); + elementRegionManager.forElementRegions< WellElementRegion >( regionNames, + [&]( localIndex const, + WellElementRegion & region ) + { + WellElementSubRegion & subRegion = region.getGroup( ElementRegionBase::viewKeyStruct::elementSubRegions() ) + .getGroup< WellElementSubRegion >( region.getSubRegionName() ); + WellControls & wellControls = wellSolver.getWellControls( subRegion ); + wellControls.assembleWellFluxTerms( TIME, DT, subRegion, solver->getDofManager(), localMatrix, localRhs ); + } ); + } ); } ); } @@ -638,7 +675,23 @@ class SinglePhaseReservoirSolverTest : public ::testing::Test [&] ( CRSMatrixView< real64, globalIndex const > const & localMatrix, arrayView1d< real64 > const & localRhs ) { - solver->wellSolver()->assemblePressureRelations( TIME, DT, domain, solver->getDofManager(), localMatrix, localRhs ); + WellManager & wellSolver = *solver->wellSolver(); + wellSolver.forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const & meshBodyName, + MeshLevel & meshLevel, + string_array const & regionNames ) + { + GEOS_UNUSED_VAR( meshBodyName ); + ElementRegionManager & elementRegionManager = meshLevel.getElemManager(); + elementRegionManager.forElementRegions< WellElementRegion >( regionNames, + [&]( localIndex const, + WellElementRegion & region ) + { + WellElementSubRegion & subRegion = region.getGroup( ElementRegionBase::viewKeyStruct::elementSubRegions() ) + .getGroup< WellElementSubRegion >( region.getSubRegionName() ); + WellControls & wellControls = wellSolver.getWellControls( subRegion ); + wellControls.assembleWellPressureRelations( TIME, DT, subRegion, solver->getDofManager(), localMatrix, localRhs ); + } ); + } ); } ); } @@ -653,7 +706,23 @@ class SinglePhaseReservoirSolverTest : public ::testing::Test [&] ( CRSMatrixView< real64, globalIndex const > const & localMatrix, arrayView1d< real64 > const & localRhs ) { - solver->wellSolver()->assembleAccumulationTerms( TIME, DT, domain, solver->getDofManager(), localMatrix, localRhs ); + WellManager & wellSolver = *solver->wellSolver(); + wellSolver.forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const & meshBodyName, + MeshLevel & meshLevel, + string_array const & regionNames ) + { + GEOS_UNUSED_VAR( meshBodyName ); + ElementRegionManager & elementRegionManager = meshLevel.getElemManager(); + elementRegionManager.forElementRegions< WellElementRegion >( regionNames, + [&]( localIndex const, + WellElementRegion & region ) + { + WellElementSubRegion & subRegion = region.getGroup( ElementRegionBase::viewKeyStruct::elementSubRegions() ) + .getGroup< WellElementSubRegion >( region.getSubRegionName() ); + WellControls & wellControls = wellSolver.getWellControls( subRegion ); + wellControls.assembleWellAccumulationTerms( TIME, DT, subRegion, solver->getDofManager(), localMatrix, localRhs ); + } ); + } ); } ); } @@ -710,7 +779,24 @@ TEST_F( SinglePhaseReservoirSolverInternalWellTest, jacobianNumericalCheck_Flux [&] ( CRSMatrixView< real64, globalIndex const > const & localMatrix, arrayView1d< real64 > const & localRhs ) { - solver->wellSolver()->assembleFluxTerms( TIME, DT, domain, solver->getDofManager(), localMatrix, localRhs ); + + WellManager & wellSolver = *solver->wellSolver(); + wellSolver.forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const & meshBodyName, + MeshLevel & meshLevel, + string_array const & regionNames ) + { + GEOS_UNUSED_VAR( meshBodyName ); + ElementRegionManager & elementRegionManager = meshLevel.getElemManager(); + elementRegionManager.forElementRegions< WellElementRegion >( regionNames, + [&]( localIndex const, + WellElementRegion & region ) + { + WellElementSubRegion & subRegion = region.getGroup( ElementRegionBase::viewKeyStruct::elementSubRegions() ) + .getGroup< WellElementSubRegion >( region.getSubRegionName() ); + WellControls & wellControls = wellSolver.getWellControls( subRegion ); + wellControls.assembleWellFluxTerms( TIME, DT, subRegion, solver->getDofManager(), localMatrix, localRhs ); + } ); + } ); } ); } diff --git a/src/coreComponents/integrationTests/wellsTests/testThermalEstimatorInjWell.cpp b/src/coreComponents/integrationTests/wellsTests/testThermalEstimatorInjWell.cpp new file mode 100644 index 00000000000..7bd3df651c5 --- /dev/null +++ b/src/coreComponents/integrationTests/wellsTests/testThermalEstimatorInjWell.cpp @@ -0,0 +1,955 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2016-2024 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2024 TotalEnergies + * Copyright (c) 2018-2024 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2023-2024 Chevron + * Copyright (c) 2019- GEOS/GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +#include "integrationTests/fluidFlowTests/testCompFlowUtils.hpp" + +#include "common/DataTypes.hpp" +#include "mainInterface/initialization.hpp" +#include "constitutive/fluid/multifluid/MultiFluidBase.hpp" +#include "mainInterface/ProblemManager.hpp" +#include "mesh/DomainPartition.hpp" +#include "mainInterface/GeosxState.hpp" +#include "mesh/WellElementSubRegion.hpp" +#include "physicsSolvers/PhysicsSolverManager.hpp" +#include "physicsSolvers/multiphysics/CompositionalMultiphaseReservoirAndWells.hpp" +#include "physicsSolvers/fluidFlow/CompositionalMultiphaseFVM.hpp" +#include "physicsSolvers/fluidFlow/wells/CompositionalMultiphaseWell.hpp" +#include "physicsSolvers/fluidFlow/wells/CompositionalMultiphaseWellFields.hpp" +#include "physicsSolvers/fluidFlow/wells/WellSolverBaseFields.hpp" +#include "physicsSolvers/fluidFlow/wells/kernels/CompositionalMultiphaseWellKernels.hpp" + +using namespace geos; +using namespace geos::dataRepository; +using namespace geos::constitutive; +using namespace geos::testing; + +CommandLineOptions g_commandLineOptions; + +void writeTableToFile( string const & filename, char const * str ) +{ + std::ofstream os( filename ); + ASSERT_TRUE( os.is_open() ); + os << str; + os.close(); +} + +void removeFile( string const & filename ) +{ + int const ret = std::remove( filename.c_str() ); + ASSERT_TRUE( ret == 0 ); +} +char const * co2flash = "FlashModel CO2Solubility 1e5 7.5e7 5e5 283.15 414.15 10 0\n"; +char const * pvtLiquid = "DensityFun PhillipsBrineDensity 1e5 7.5e7 5e5 283.15 414.15 10 0\n" + "ViscosityFun PhillipsBrineViscosity 0\n" + "EnthalpyFun BrineEnthalpy 1e5 7.5e7 5e5 283.15 414.15 10 0\n"; + +char const * pvtGas = "DensityFun SpanWagnerCO2Density 1e5 7.5e7 5e5 283.15 414.15 10\n" + "ViscosityFun FenghourCO2Viscosity 1e5 7.5e7 5e5 283.15 414.15 10\n" + "EnthalpyFun CO2Enthalpy 1e5 7.5e7 5e5 283.15 414.15 10\n"; +char const * xmlInput = + R"xml( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +)xml"; + +template< typename T, typename COL_INDEX > +void printCompareLocalMatrices( CRSMatrixView< T const, COL_INDEX const > const & matrix1, + CRSMatrixView< T const, COL_INDEX const > const & matrix2, std::string const & testName ) +{ + std::ofstream omat1( testName+".csv" ); + + + std::vector< std::vector< double > > fmat1( matrix1.numRows(), std::vector< double >( matrix1.numRows(), 0.0 )); + std::vector< std::vector< double > > fmat2( matrix2.numRows(), std::vector< double >( matrix2.numRows(), 0.0 )); + + for( localIndex i = 0; i < matrix1.numRows(); ++i ) + { + arraySlice1d< globalIndex const > indices1 = matrix1.getColumns( i ); + arraySlice1d< globalIndex const > indices2 = matrix2.getColumns( i ); + arraySlice1d< double const > values1 = matrix1.getEntries( i ); + arraySlice1d< double const > values2 = matrix2.getEntries( i ); + for( integer j=0; j const & rsd1, + array1d< real64 > const & rsd2, std::string const & testName ) +{ + std::ofstream omat1( testName+".csv" ); + + for( integer i=0; i +void testWellEstimatorNumericalJacobian( CompositionalMultiphaseReservoirAndWells< CompositionalMultiphaseBase > & solver, + DomainPartition & domain, + real64 const perturbParameter, + real64 const time_n, + real64 const relTol, bool diag_check, std::string const & testName, + LAMBDA && assembleFunction ) +{ + GEOS_UNUSED_VAR( testName ); + WellManager & wellSolver = *solver.wellSolver(); + + wellSolver.forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&] ( string const & meshBodyName, + MeshLevel & mesh, + string_array const & regionNames ) + { + mesh.getElemManager().forElementSubRegions< WellElementSubRegion >( regionNames, + [&]( localIndex const, + WellElementSubRegion & subRegion ) + { + WellControls & wellControls = wellSolver.getWellControls( subRegion ); + CompositionalMultiphaseWell * compWell = dynamic_cast< CompositionalMultiphaseWell * >(&wellControls); + compWell->setWellState( 1 ); + compWell->initializeWell( domain, domain.getMeshBodies(), meshBodyName, mesh, subRegion, time_n ); + } ); + } ); + + CompositionalMultiphaseFVM & flowSolver = dynamicCast< CompositionalMultiphaseFVM & >( *solver.reservoirSolver() ); + + localIndex const NC = flowSolver.numFluidComponents(); + + CRSMatrix< real64, globalIndex > const & jacobian = solver.getLocalMatrix(); + array1d< real64 > residual( jacobian.numRows() ); + DofManager const & dofManager = solver.getDofManager(); + + // assemble the analytical residual + solver.resetStateToBeginningOfStep( domain ); + string const resDofKey = dofManager.getKey( wellSolver.resElementDofName() ); + string const wellDofKey = dofManager.getKey( wellSolver.wellElementDofName() ); + residual.zero(); + jacobian.zero(); + + assembleFunction( jacobian.toViewConstSizes(), residual.toView() ); + residual.move( hostMemorySpace, false ); + + // copy the analytical residual + array1d< real64 > residualOrig( residual ); + + // create the numerical jacobian + jacobian.move( hostMemorySpace ); + CRSMatrix< real64, globalIndex > jacobianFD( jacobian ); + jacobianFD.zero(); + //////////////////////////////////////////////// + // Step 1) Compute the terms in J_RR and J_WR // + //////////////////////////////////////////////// +#if 1 + domain.forMeshBodies( [&] ( MeshBody & meshBody ) + { + bool processMesh = true; + meshBody.forMeshLevels( [&] ( MeshLevel & mesh ) + { + if( !processMesh ) + return; + processMesh = false; + ElementRegionManager & elemManager = mesh.getElemManager(); + for( localIndex er = 0; er < elemManager.numRegions(); ++er ) + { + ElementRegionBase & elemRegion = elemManager.getRegion( er ); + elemRegion.forElementSubRegionsIndex< CellElementSubRegion >( [&]( localIndex const, CellElementSubRegion & subRegion ) + { + // get the degrees of freedom and ghosting information + arrayView1d< globalIndex const > const & dofNumber = + subRegion.getReference< array1d< globalIndex > >( resDofKey ); + + // get the primary variables on the reservoir elements + arrayView1d< real64 > const & pres = + subRegion.getField< fields::flow::pressure >(); + pres.move( hostMemorySpace, false ); + + arrayView1d< real64 > const & temperature = + subRegion.getField< fields::flow::temperature >(); + temperature.move( hostMemorySpace, false ); + + arrayView2d< real64, compflow::USD_COMP > const & compDens = + subRegion.getField< fields::flow::globalCompDensity >(); + compDens.move( hostMemorySpace, false ); + + // a) compute all the derivatives wrt to the pressure in RESERVOIR elem ei + for( localIndex ei = 0; ei < subRegion.size(); ++ei ) + { +#if 1 + { + solver.resetStateToBeginningOfStep( domain ); + + // here is the perturbation in the pressure of the element + real64 const dP = perturbParameter * (pres[ei] + perturbParameter); + pres.move( hostMemorySpace, true ); + pres[ei] += dP; + + // after perturbing, update the pressure-dependent quantities in the reservoir + flowSolver.forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&] ( string const &, + MeshLevel & mesh2, + string_array const & regionNames2 ) + { + mesh2.getElemManager().forElementSubRegions( regionNames2, + [&]( localIndex const, + ElementSubRegionBase & subRegion2 ) + { + flowSolver.updateFluidState( subRegion2 ); + } ); + } ); + + wellSolver.updateState( domain ); + + residual.zero(); + jacobian.zero(); + assembleFunction( jacobian.toViewConstSizes(), residual.toView() ); + + fillNumericalJacobian( residual.toViewConst(), + residualOrig.toViewConst(), + dofNumber[ei], + dP, + jacobianFD.toViewConstSizes() ); + } +#endif +#if 1 + { + solver.resetStateToBeginningOfStep( domain ); + + // here is the perturbation in the temperature of the element + real64 const dT = 1;//perturbParameter * (temperature[ei] + perturbParameter); + temperature.move( hostMemorySpace, true ); + temperature[ei] += dT; + + // after perturbing, update the temperature-dependent quantities in the reservoir + flowSolver.forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&] ( string const &, + MeshLevel & mesh2, + string_array const & regionNames2 ) + { + mesh2.getElemManager().forElementSubRegions( regionNames2, + [&]( localIndex const, + ElementSubRegionBase & subRegion2 ) + { + flowSolver.updateFluidState( subRegion2 ); + } ); + } ); + + wellSolver.updateState( domain ); + + residual.zero(); + jacobian.zero(); + assembleFunction( jacobian.toViewConstSizes(), residual.toView() ); + + fillNumericalJacobian( residual.toViewConst(), + residualOrig.toViewConst(), + dofNumber[ei]+NC+1, + dT, + jacobianFD.toViewConstSizes() ); + } +#endif +#if 1 + real64 totalDensity = 0.0; + for( localIndex ic = 0; ic < NC; ++ic ) + { + totalDensity += compDens[ei][ic]; + } + + for( localIndex jc = 0; jc < NC; ++jc ) + { + solver.resetStateToBeginningOfStep( domain ); + + real64 const dRho = perturbParameter * totalDensity; + compDens.move( hostMemorySpace, true ); + compDens[ei][jc] += dRho; + + flowSolver.forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&] ( string const &, + MeshLevel & mesh2, + string_array const & regionNames2 ) + { + mesh2.getElemManager().forElementSubRegions( regionNames2, + [&]( localIndex const, + ElementSubRegionBase & subRegion2 ) + { + flowSolver.updateFluidState( subRegion2 ); + } ); + } ); + + residual.zero(); + jacobian.zero(); + assembleFunction( jacobian.toViewConstSizes(), residual.toView() ); + + fillNumericalJacobian( residual.toViewConst(), + residualOrig.toViewConst(), + dofNumber[ei] + jc + 1, + dRho, + jacobianFD.toViewConstSizes() ); + } +#endif + } + + return; + } ); + } + return; + } ); + } ); + +#endif + // at this point we start assembling the finite-difference block by block + + + ///////////////////////////////////////////////// + // Step 2) Compute the terms in J_RW and J_WW // + ///////////////////////////////////////////////// + + // loop over the wells + if( 1 ) + wellSolver.forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&] ( string const &, + MeshLevel & mesh, + string_array const & regionNames ) + { + mesh.getElemManager().forElementSubRegions< WellElementSubRegion >( regionNames, + [&]( localIndex const, + WellElementSubRegion & subRegion ) + { + // get the degrees of freedom, ghosting info and next well elem index + arrayView1d< globalIndex const > const & wellElemDofNumber = + subRegion.getReference< array1d< globalIndex > >( wellDofKey ); + + // get the primary variables on the well elements + arrayView1d< real64 > const & wellElemPressure = + subRegion.getField< fields::well::pressure >(); + wellElemPressure.move( hostMemorySpace, false ); + + arrayView1d< real64 > const & wellElemTemperature = + subRegion.getField< fields::well::temperature >(); + wellElemTemperature.move( hostMemorySpace, false ); + + arrayView2d< real64, compflow::USD_COMP > const & wellElemCompDens = + subRegion.getField< fields::well::globalCompDensity >(); + wellElemCompDens.move( hostMemorySpace, false ); + + arrayView1d< real64 > const & connRate = + subRegion.getField< fields::well::connectionRate >(); + connRate.move( hostMemorySpace, false ); + + // a) compute all the derivatives wrt to the pressure in WELL elem iwelem + for( localIndex iwelem = 0; iwelem < subRegion.size(); ++iwelem ) + { + +#if 1 + { + solver.resetStateToBeginningOfStep( domain ); + + // here is the perturbation in the pressure of the well element + real64 const dP = perturbParameter * ( wellElemPressure[iwelem] + perturbParameter ); + wellElemPressure.move( hostMemorySpace, true ); + wellElemPressure[iwelem] += dP; + + // after perturbing, update the pressure-dependent quantities in the well + wellSolver.updateState( domain ); + + residual.zero(); + jacobian.zero(); + assembleFunction( jacobian.toViewConstSizes(), residual.toView() ); + + fillNumericalJacobian( residual.toViewConst(), + residualOrig.toViewConst(), + wellElemDofNumber[iwelem] + compositionalMultiphaseWellKernels::ColOffset::DPRES, + dP, + jacobianFD.toViewConstSizes() ); + } +#endif +#if 1 + real64 wellElemTotalDensity = 0.0; + for( localIndex ic = 0; ic < NC; ++ic ) + { + wellElemTotalDensity += wellElemCompDens[iwelem][ic]; + } + for( localIndex jc = 0; jc < NC; ++jc ) + { + solver.resetStateToBeginningOfStep( domain ); + + real64 const dRho = perturbParameter * wellElemTotalDensity; + wellElemCompDens.move( hostMemorySpace, true ); + wellElemCompDens[iwelem][jc] += dRho; + + wellSolver.updateState( domain ); + + residual.zero(); + jacobian.zero(); + assembleFunction( jacobian.toViewConstSizes(), residual.toView() ); + + fillNumericalJacobian( residual.toViewConst(), + residualOrig.toViewConst(), + wellElemDofNumber[iwelem] + compositionalMultiphaseWellKernels::ColOffset::DCOMP + jc, + dRho, + jacobianFD.toViewConstSizes() ); + } + { + solver.resetStateToBeginningOfStep( domain ); + residual.zero(); + jacobian.zero(); + if( diag_check || iwelem > 0 ) + { + // here is the perturbation in the temperature of the well element + real64 const dT = perturbParameter * ( wellElemTemperature[iwelem] + perturbParameter ); + wellElemTemperature.move( hostMemorySpace, true ); + wellElemTemperature[iwelem] += dT; + + // after perturbing, update the pressure-dependent quantities in the well + wellSolver.updateState( domain ); + + assembleFunction( jacobian.toViewConstSizes(), residual.toView() ); + fillNumericalJacobian( residual.toViewConst(), + residualOrig.toViewConst(), + wellElemDofNumber[iwelem] + compositionalMultiphaseWellKernels::ColOffset::DCOMP + NC+1, + dT, + jacobianFD.toViewConstSizes() ); + if( iwelem == 1 ) + { + real64 dRdX = 0.0; + localIndex rowIndex = wellElemDofNumber[0] + compositionalMultiphaseWellKernels::ColOffset::DCOMP + NC+1;; + for( integer ider=0; ider< 3; ider++ ) + { + globalIndex colIndex = wellElemDofNumber[0]+ ider; + setNumericalJacobianValue( rowIndex, colIndex, dRdX, jacobianFD.toViewConstSizes() ); + } + globalIndex colIndex = wellElemDofNumber[1]+3; + setNumericalJacobianValue( rowIndex, colIndex, dRdX, jacobianFD.toViewConstSizes() ); + + } + } + else + { + localIndex rowIndex = wellElemDofNumber[iwelem] + compositionalMultiphaseWellKernels::ColOffset::DCOMP + NC+1;; + globalIndex colIndex = wellElemDofNumber[iwelem] + compositionalMultiphaseWellKernels::ColOffset::DCOMP + NC+1;; + setNumericalJacobianValue( rowIndex, colIndex, 1.0, jacobianFD.toViewConstSizes() ); + } + } +#endif + } + +#if 1 + // b) compute all the derivatives wrt to the connection in WELL elem + // iwelem + for( localIndex iwelem = 0; iwelem < subRegion.size(); ++iwelem ) + { + { + solver.resetStateToBeginningOfStep( domain ); + + // here is the perturbation in the rate of the well element + real64 const dRate = perturbParameter * ( connRate[iwelem] + perturbParameter ); + connRate.move( hostMemorySpace, true ); + connRate[iwelem] += dRate; + + wellSolver.updateState( domain ); + + residual.zero(); + jacobian.zero(); + assembleFunction( jacobian.toViewConstSizes(), residual.toView() ); + std::cout << "tjb dq iwelem " << iwelem << " " << (residual[25] - residualOrig[25])/dRate << std::endl; + fillNumericalJacobian( residual.toViewConst(), + residualOrig.toViewConst(), + wellElemDofNumber[iwelem] + compositionalMultiphaseWellKernels::ColOffset::DCOMP + NC, + dRate, + jacobianFD.toViewConstSizes() ); + } + } +#endif + } ); + } ); + + // assemble the analytical jacobian + solver.resetStateToBeginningOfStep( domain ); + + residual.zero(); + jacobian.zero(); + assembleFunction( jacobian.toViewConstSizes(), residual.toView() ); + //printCompareLocalMatrices( jacobian.toViewConst(), jacobianFD.toViewConst(), testName ); + compareLocalMatrices( jacobian.toViewConst(), jacobianFD.toViewConst(), relTol ); +} + +class CompositionalMultiphaseReservoirSolverTest : public ::testing::Test +{ +public: + + CompositionalMultiphaseReservoirSolverTest(): + state( std::make_unique< CommandLineOptions >( g_commandLineOptions ) ) + {} + +protected: + + void SetUp() override + { + setupProblemFromXML( state.getProblemManager(), xmlInput ); + solver = &state.getProblemManager().getPhysicsSolverManager().getGroup< CompositionalMultiphaseReservoirAndWells< CompositionalMultiphaseBase > >( "reservoirSystem" ); + + DomainPartition & domain = state.getProblemManager().getDomainPartition(); + + solver->setupSystem( domain, + solver->getDofManager(), + solver->getLocalMatrix(), + solver->getSystemRhs(), + solver->getSystemSolution() ); + + solver->implicitStepSetup( time, dt, domain ); + WellManager & wellSolver = *solver->wellSolver(); + wellSolver.forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const & meshBodyName, + MeshLevel & meshLevel, + string_array const & regionNames ) + { + ElementRegionManager & elementRegionManager = meshLevel.getElemManager(); + elementRegionManager.forElementRegions< WellElementRegion >( regionNames, + [&]( localIndex const, + WellElementRegion & region ) + { + WellElementSubRegion & subRegion = region.getGroup( ElementRegionBase::viewKeyStruct::elementSubRegions() ) + .getGroup< WellElementSubRegion >( region.getSubRegionName() ); + WellControls & wellControls = wellSolver.getWellControls( subRegion ); + wellControls.initializeWell( domain, domain.getMeshBodies(), meshBodyName, meshLevel, subRegion, time ); + } ); + } ); + } + + static real64 constexpr time = 0.0; + static real64 constexpr dt = 1e4; + static real64 constexpr eps = std::numeric_limits< real64 >::epsilon(); + + GeosxState state; + CompositionalMultiphaseReservoirAndWells< CompositionalMultiphaseBase > * solver; +}; + +real64 constexpr CompositionalMultiphaseReservoirSolverTest::time; +real64 constexpr CompositionalMultiphaseReservoirSolverTest::dt; +real64 constexpr CompositionalMultiphaseReservoirSolverTest::eps; + + + +#if 0 +// There are a few terms that cause this test to fail, these are dt terms of cell connected to cell with boundary condtion. +// The could be zeroed out in the jacobian assembly and then this test should pass. +// Otherwise the test is good, uncomment out printCompareLocalMatrices and look at FD and computed derivatives +TEST_F( CompositionalMultiphaseReservoirSolverTest, jacobianNumericalCheck_System ) +{ + real64 const perturb = std::sqrt( eps ); + real64 const tol = 1e-1; // 10% error margin + + DomainPartition & domain = state.getProblemManager().getDomainPartition(); + + testWellEstimatorNumericalJacobian( *solver, domain, perturb, time, tol, false, "Check_System", + [&] ( CRSMatrixView< real64, globalIndex const > const & localMatrix, + arrayView1d< real64 > const & localRhs ) + { + + DofManager const & dofManager = solver->getDofManager(); + WellManager * wellSolver = solver->wellSolver(); + wellSolver->forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&] ( string const &, + MeshLevel & mesh, + string_array const & regionNames ) + { + ElementRegionManager & elemManager = mesh.getElemManager(); + elemManager.forElementSubRegions< WellElementSubRegion >( regionNames, + [&]( localIndex const, + WellElementSubRegion & subRegion ) + { + // call assemble to fill the matrix and the rhs + WellControls & wellControls = wellSolver->getWellControls( subRegion ); + CompositionalMultiphaseWell * compWell = dynamic_cast< CompositionalMultiphaseWell * >(&wellControls); + compWell->assembleSystem( time, + dt, + 0, + elemManager, + subRegion, + dofManager, + localMatrix, + localRhs ); + + } ); + } ); + + solver->assembleCouplingTerms( time, dt, domain, solver->getDofManager(), localMatrix, localRhs ); + + } ); +} +#endif +TEST_F( CompositionalMultiphaseReservoirSolverTest, jacobianNumericalCheck_Accum ) +{ + real64 const perturb = std::sqrt( eps ); + real64 const tol = 1e-1; // 10% error margin + + DomainPartition & domain = state.getProblemManager().getDomainPartition(); + + testWellEstimatorNumericalJacobian( *solver, domain, perturb, time, tol, false, "Check_Accum", + [&] ( CRSMatrixView< real64, globalIndex const > const & localMatrix, + arrayView1d< real64 > const & localRhs ) + { + + //DofManager const & dofManager = solver->wellSolver()->getDofManager(); + WellManager * wellSolver = solver->wellSolver(); + wellSolver->forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&] ( string const &, + MeshLevel & mesh, + string_array const & regionNames ) + { + ElementRegionManager & elemManager = mesh.getElemManager(); + elemManager.forElementSubRegions< WellElementSubRegion >( regionNames, + [&]( localIndex const, + WellElementSubRegion & subRegion ) + { + // call assemble to fill the matrix and the rhs + WellControls & wellControls = wellSolver->getWellControls( subRegion ); + wellControls.assembleWellAccumulationTerms( time, dt, subRegion, solver->getDofManager(), localMatrix, localRhs ); + } ); + } ); + } ); +} +#if 1 +TEST_F( CompositionalMultiphaseReservoirSolverTest, jacobianNumericalCheck_PressureRelation ) +{ + real64 const perturb = std::sqrt( eps ); + real64 const tol = 1e-1; // 10% error margin + + DomainPartition & domain = state.getProblemManager().getDomainPartition(); + + testWellEstimatorNumericalJacobian( *solver, domain, perturb, time, tol, true, "Check_PressureRelation", + [&] ( CRSMatrixView< real64, globalIndex const > const & localMatrix, + arrayView1d< real64 > const & localRhs ) + { + + //DofManager const & dofManager = solver->wellSolver()->getDofManager(); + DofManager const & dofManager = solver->getDofManager(); + WellManager * wellSolver = solver->wellSolver(); + wellSolver->forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&] ( string const &, + MeshLevel & mesh, + string_array const & regionNames ) + { + ElementRegionManager & elemManager = mesh.getElemManager(); + elemManager.forElementSubRegions< WellElementSubRegion >( regionNames, + [&]( localIndex const, + WellElementSubRegion & subRegion ) + { + // call assemble to fill the matrix and the rhs + WellControls & wellControls = wellSolver->getWellControls( subRegion ); + //wellControls.assembleWellConstraintTerms( time, dt, subRegion, dofManager, localMatrix.toViewConstSizes(), localRhs ); + wellControls.assembleWellPressureRelations( time, dt, subRegion, dofManager, localMatrix.toViewConstSizes(), localRhs ); + + } ); + } ); + } ); +} +#endif +#if 1 +TEST_F( CompositionalMultiphaseReservoirSolverTest, jacobianNumericalCheck_Flux ) +{ + real64 const perturb = std::sqrt( eps ); + real64 const tol = 1e-1; // 10% error margin + + DomainPartition & domain = state.getProblemManager().getDomainPartition(); + + testWellEstimatorNumericalJacobian( *solver, domain, perturb, time, tol, true, "Check_Flux", + [&] ( CRSMatrixView< real64, globalIndex const > const & localMatrix, + arrayView1d< real64 > const & localRhs ) + { + + //DofManager const & dofManager = solver->wellSolver()->getDofManager(); + WellManager * wellSolver = solver->wellSolver(); + wellSolver->forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&] ( string const &, + MeshLevel & mesh, + string_array const & regionNames ) + { + ElementRegionManager & elemManager = mesh.getElemManager(); + elemManager.forElementSubRegions< WellElementSubRegion >( regionNames, + [&]( localIndex const, + WellElementSubRegion & subRegion ) + { + // call assemble to fill the matrix and the rhs + WellControls & wellControls = wellSolver->getWellControls( subRegion ); + wellControls.assembleWellFluxTerms( time, dt, subRegion, solver->getDofManager(), localMatrix, localRhs ); + } ); + } ); + } ); +} +#endif +int main( int argc, char * * argv ) +{ + writeTableToFile( "co2flash.txt", co2flash ); + writeTableToFile( "pvtliquid.txt", pvtLiquid ); + writeTableToFile( "pvtgas.txt", pvtGas ); + ::testing::InitGoogleTest( &argc, argv ); + g_commandLineOptions = *geos::basicSetup( argc, argv ); + int const result = RUN_ALL_TESTS(); + geos::basicCleanup(); + removeFile( "co2flash.txt" ); + removeFile( "pvtliquid.txt" ); + removeFile( "pvtgas.txt" ); + + return result; +} diff --git a/src/coreComponents/integrationTests/wellsTests/testThermalEstimatorProdWell.cpp b/src/coreComponents/integrationTests/wellsTests/testThermalEstimatorProdWell.cpp new file mode 100644 index 00000000000..ed45f48f4ed --- /dev/null +++ b/src/coreComponents/integrationTests/wellsTests/testThermalEstimatorProdWell.cpp @@ -0,0 +1,969 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2016-2024 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2024 TotalEnergies + * Copyright (c) 2018-2024 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2023-2024 Chevron + * Copyright (c) 2019- GEOS/GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +#include "integrationTests/fluidFlowTests/testCompFlowUtils.hpp" + +#include "common/DataTypes.hpp" +#include "mainInterface/initialization.hpp" +#include "constitutive/fluid/multifluid/MultiFluidBase.hpp" +#include "mainInterface/ProblemManager.hpp" +#include "mesh/DomainPartition.hpp" +#include "mainInterface/GeosxState.hpp" +#include "mesh/WellElementSubRegion.hpp" +#include "physicsSolvers/PhysicsSolverManager.hpp" +#include "physicsSolvers/multiphysics/CompositionalMultiphaseReservoirAndWells.hpp" +#include "physicsSolvers/fluidFlow/CompositionalMultiphaseFVM.hpp" +#include "physicsSolvers/fluidFlow/wells/CompositionalMultiphaseWell.hpp" +#include "physicsSolvers/fluidFlow/wells/CompositionalMultiphaseWellFields.hpp" +#include "physicsSolvers/fluidFlow/wells/WellSolverBaseFields.hpp" +#include "physicsSolvers/fluidFlow/wells/kernels/CompositionalMultiphaseWellKernels.hpp" + +using namespace geos; +using namespace geos::dataRepository; +using namespace geos::constitutive; +using namespace geos::testing; + +CommandLineOptions g_commandLineOptions; + + + +void writeTableToFile( string const & filename, char const * str ) +{ + std::ofstream os( filename ); + ASSERT_TRUE( os.is_open() ); + os << str; + os.close(); +} + +void removeFile( string const & filename ) +{ + int const ret = std::remove( filename.c_str() ); + ASSERT_TRUE( ret == 0 ); +} +char const * co2flash = "FlashModel CO2Solubility 1e5 7.5e7 5e5 283.15 414.15 10 0\n"; +char const * pvtLiquid = "DensityFun PhillipsBrineDensity 1e5 7.5e7 5e5 283.15 414.15 10 0\n" + "ViscosityFun PhillipsBrineViscosity 0\n" + "EnthalpyFun BrineEnthalpy 1e5 7.5e7 5e5 283.15 414.15 10 0\n"; + +char const * pvtGas = "DensityFun SpanWagnerCO2Density 1e5 7.5e7 5e5 283.15 414.15 10\n" + "ViscosityFun FenghourCO2Viscosity 1e5 7.5e7 5e5 283.15 414.15 10\n" + "EnthalpyFun CO2Enthalpy 1e5 7.5e7 5e5 283.15 414.15 10\n"; +char const * xmlInput = + R"xml( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +)xml"; + +template< typename T, typename COL_INDEX > +void printCompareLocalMatrices( CRSMatrixView< T const, COL_INDEX const > const & matrix1, + CRSMatrixView< T const, COL_INDEX const > const & matrix2, std::string const & testName ) +{ + std::ofstream omat1( testName+".csv" ); + + + std::vector< std::vector< double > > fmat1( matrix1.numRows(), std::vector< double >( matrix1.numRows(), 0.0 )); + std::vector< std::vector< double > > fmat2( matrix2.numRows(), std::vector< double >( matrix2.numRows(), 0.0 )); + + for( localIndex i = 0; i < matrix1.numRows(); ++i ) + { + arraySlice1d< globalIndex const > indices1 = matrix1.getColumns( i ); + arraySlice1d< globalIndex const > indices2 = matrix2.getColumns( i ); + arraySlice1d< double const > values1 = matrix1.getEntries( i ); + arraySlice1d< double const > values2 = matrix2.getEntries( i ); + for( integer j=0; j const & rsd1, + array1d< real64 > const & rsd2, std::string const & testName ) +{ + std::ofstream omat1( testName+".csv" ); + + for( integer i=0; i +void testWellEstimatorNumericalJacobian( CompositionalMultiphaseReservoirAndWells< CompositionalMultiphaseBase > & solver, + DomainPartition & domain, + real64 const perturbParameter, + real64 const time_n, + real64 const relTol, std::string const & testName, + LAMBDA && assembleFunction ) +{ + WellManager & wellSolver = *solver.wellSolver(); + + wellSolver.forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&] ( string const & meshBodyName, + MeshLevel & mesh, + string_array const & regionNames ) + { + mesh.getElemManager().forElementSubRegions< WellElementSubRegion >( regionNames, + [&]( localIndex const, + WellElementSubRegion & subRegion ) + { + WellControls & wellControls = wellSolver.getWellControls( subRegion ); + CompositionalMultiphaseWell * compWell = dynamic_cast< CompositionalMultiphaseWell * >(&wellControls); + compWell->setWellState( 1 ); + compWell->initializeWell( domain, domain.getMeshBodies(), meshBodyName, mesh, subRegion, time_n ); + } ); + } ); + + CompositionalMultiphaseFVM & flowSolver = dynamicCast< CompositionalMultiphaseFVM & >( *solver.reservoirSolver() ); + + localIndex const NC = flowSolver.numFluidComponents(); + + //ßCRSMatrix< real64, globalIndex > const & jacobian = wellSolver.getLocalMatrix(); + CRSMatrix< real64, globalIndex > const & jacobian = solver.getLocalMatrix(); + array1d< real64 > residual( jacobian.numRows() ); + //DofManager const & dofManager = wellSolver.getDofManager(); + DofManager const & dofManager = solver.getDofManager(); + // assemble the analytical residual + solver.resetStateToBeginningOfStep( domain ); + string const resDofKey = dofManager.getKey( wellSolver.resElementDofName() ); + string const wellDofKey = dofManager.getKey( wellSolver.wellElementDofName() ); + residual.zero(); + jacobian.zero(); + + assembleFunction( jacobian.toViewConstSizes(), residual.toView() ); + residual.move( hostMemorySpace, false ); + + // copy the analytical residual + array1d< real64 > residualOrig( residual ); + + // create the numerical jacobian + jacobian.move( hostMemorySpace ); + CRSMatrix< real64, globalIndex > jacobianFD( jacobian ); + jacobianFD.zero(); + //////////////////////////////////////////////// + // Step 1) Compute the terms in J_RR and J_WR // + //////////////////////////////////////////////// +#if 1 + domain.forMeshBodies( [&] ( MeshBody & meshBody ) + { + bool processMesh = true; + meshBody.forMeshLevels( [&] ( MeshLevel & mesh ) + { + if( !processMesh ) + return; + processMesh = false; + ElementRegionManager & elemManager = mesh.getElemManager(); + for( localIndex er = 0; er < elemManager.numRegions(); ++er ) + { + //if ( er == 1) + // continue; + ElementRegionBase & elemRegion = elemManager.getRegion( er ); + elemRegion.forElementSubRegionsIndex< CellElementSubRegion >( [&]( localIndex const, CellElementSubRegion & subRegion ) + { + // get the degrees of freedom and ghosting information + arrayView1d< globalIndex const > const & dofNumber = + subRegion.getReference< array1d< globalIndex > >( resDofKey ); + + // get the primary variables on the reservoir elements + arrayView1d< real64 > const & pres = + subRegion.getField< fields::flow::pressure >(); + pres.move( hostMemorySpace, false ); + + arrayView1d< real64 > const & temperature = + subRegion.getField< fields::flow::temperature >(); + temperature.move( hostMemorySpace, false ); + + arrayView2d< real64, compflow::USD_COMP > const & compDens = + subRegion.getField< fields::flow::globalCompDensity >(); + compDens.move( hostMemorySpace, false ); + + // a) compute all the derivatives wrt to the pressure in RESERVOIR elem ei + for( localIndex ei = 0; ei < subRegion.size(); ++ei ) + { + //if ( ei > 0 ) + // continue; + + { + solver.resetStateToBeginningOfStep( domain ); + + // here is the perturbation in the pressure of the element + real64 const dP = perturbParameter * (pres[ei] + perturbParameter); + pres.move( hostMemorySpace, true ); + pres[ei] += dP; + + // after perturbing, update the pressure-dependent quantities in the reservoir + flowSolver.forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&] ( string const &, + MeshLevel & mesh2, + string_array const & regionNames2 ) + { + mesh2.getElemManager().forElementSubRegions( regionNames2, + [&]( localIndex const, + ElementSubRegionBase & subRegion2 ) + { + flowSolver.updateFluidState( subRegion2 ); + } ); + } ); + + wellSolver.updateState( domain ); + + residual.zero(); + jacobian.zero(); + assembleFunction( jacobian.toViewConstSizes(), residual.toView() ); + fillNumericalJacobian ( residual.toViewConst(), + residualOrig.toViewConst(), + dofNumber[ei], + dP, + jacobianFD.toViewConstSizes() ); + } +#if 1 + { + solver.resetStateToBeginningOfStep( domain ); + + // here is the perturbation in the temperature of the element + real64 const dT = 1;//perturbParameter * (temperature[ei] + perturbParameter); + temperature.move( hostMemorySpace, true ); + temperature[ei] += dT; + + // after perturbing, update the temperature-dependent quantities in the reservoir + flowSolver.forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&] ( string const &, + MeshLevel & mesh2, + string_array const & regionNames2 ) + { + mesh2.getElemManager().forElementSubRegions( regionNames2, + [&]( localIndex const, + ElementSubRegionBase & subRegion2 ) + { + flowSolver.updateFluidState( subRegion2 ); + } ); + } ); + + wellSolver.updateState( domain ); + + residual.zero(); + jacobian.zero(); + assembleFunction( jacobian.toViewConstSizes(), residual.toView() ); + + fillNumericalJacobian( residual.toViewConst(), + residualOrig.toViewConst(), + dofNumber[ei]+NC+1, + dT, + jacobianFD.toViewConstSizes() ); + } +#endif +#if 1 + real64 totalDensity = 0.0; + for( localIndex ic = 0; ic < NC; ++ic ) + { + totalDensity += compDens[ei][ic]; + } + + for( localIndex jc = 0; jc < NC; ++jc ) + { + solver.resetStateToBeginningOfStep( domain ); + + real64 const dRho = perturbParameter * totalDensity; + compDens.move( hostMemorySpace, true ); + compDens[ei][jc] += dRho; + + flowSolver.forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&] ( string const &, + MeshLevel & mesh2, + string_array const & regionNames2 ) + { + mesh2.getElemManager().forElementSubRegions( regionNames2, + [&]( localIndex const, + ElementSubRegionBase & subRegion2 ) + { + flowSolver.updateFluidState( subRegion2 ); + } ); + } ); + + residual.zero(); + jacobian.zero(); + assembleFunction( jacobian.toViewConstSizes(), residual.toView() ); + + fillNumericalJacobian( residual.toViewConst(), + residualOrig.toViewConst(), + dofNumber[ei] + jc + 1, + dRho, + jacobianFD.toViewConstSizes() ); + } +#endif + } + + return; + } ); + } + return; + } ); + } ); + +#endif + // at this point we start assembling the finite-difference block by block + + + ///////////////////////////////////////////////// + // Step 2) Compute the terms in J_RW and J_WW // + ///////////////////////////////////////////////// + + // loop over the wells + if( 1 ) + wellSolver.forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&] ( string const &, + MeshLevel & mesh, + string_array const & regionNames ) + { + mesh.getElemManager().forElementSubRegions< WellElementSubRegion >( regionNames, + [&]( localIndex const, + WellElementSubRegion & subRegion ) + { + // get the degrees of freedom, ghosting info and next well elem index + arrayView1d< globalIndex const > const & wellElemDofNumber = + subRegion.getReference< array1d< globalIndex > >( wellDofKey ); + + // get the primary variables on the well elements + arrayView1d< real64 > const & wellElemPressure = + subRegion.getField< fields::well::pressure >(); + wellElemPressure.move( hostMemorySpace, false ); + + arrayView1d< real64 > const & wellElemTemperature = + subRegion.getField< fields::well::temperature >(); + wellElemTemperature.move( hostMemorySpace, false ); + + arrayView2d< real64, compflow::USD_COMP > const & wellElemCompDens = + subRegion.getField< fields::well::globalCompDensity >(); + wellElemCompDens.move( hostMemorySpace, false ); + + arrayView1d< real64 > const & connRate = + subRegion.getField< fields::well::connectionRate >(); + connRate.move( hostMemorySpace, false ); + + // a) compute all the derivatives wrt to the pressure in WELL elem iwelem + for( localIndex iwelem = 0; iwelem < subRegion.size(); ++iwelem ) + { + +#if 1 + { + solver.resetStateToBeginningOfStep( domain ); + + // here is the perturbation in the pressure of the well element + real64 const dP = perturbParameter * ( wellElemPressure[iwelem] + perturbParameter ); + wellElemPressure.move( hostMemorySpace, true ); + wellElemPressure[iwelem] += dP; + + // after perturbing, update the pressure-dependent quantities in the well + wellSolver.updateState( domain ); + + residual.zero(); + jacobian.zero(); + assembleFunction( jacobian.toViewConstSizes(), residual.toView() ); + + fillNumericalJacobian( residual.toViewConst(), + residualOrig.toViewConst(), + wellElemDofNumber[iwelem] + compositionalMultiphaseWellKernels::ColOffset::DPRES, + dP, + jacobianFD.toViewConstSizes() ); + } +#endif +#if 1 + real64 wellElemTotalDensity = 0.0; + for( localIndex ic = 0; ic < NC; ++ic ) + { + wellElemTotalDensity += wellElemCompDens[iwelem][ic]; + } + for( localIndex jc = 0; jc < NC; ++jc ) + { + solver.resetStateToBeginningOfStep( domain ); + + real64 const dRho = perturbParameter * wellElemTotalDensity; + wellElemCompDens.move( hostMemorySpace, true ); + wellElemCompDens[iwelem][jc] += dRho; + + wellSolver.updateState( domain ); + + residual.zero(); + jacobian.zero(); + assembleFunction( jacobian.toViewConstSizes(), residual.toView() ); + + fillNumericalJacobian( residual.toViewConst(), + residualOrig.toViewConst(), + wellElemDofNumber[iwelem] + compositionalMultiphaseWellKernels::ColOffset::DCOMP + jc, + dRho, + jacobianFD.toViewConstSizes() ); + } + { + solver.resetStateToBeginningOfStep( domain ); + residual.zero(); + jacobian.zero(); + // here is the perturbation in the temperature of the well element + real64 const dT = 1.0; + wellElemTemperature.move( hostMemorySpace, true ); + wellElemTemperature[iwelem] += dT; + + // after perturbing, update the pressure-dependent quantities in the well + wellSolver.updateState( domain ); + + + assembleFunction( jacobian.toViewConstSizes(), residual.toView() ); + fillNumericalJacobian( residual.toViewConst(), + residualOrig.toViewConst(), + wellElemDofNumber[iwelem] + compositionalMultiphaseWellKernels::ColOffset::DCOMP + NC+1, + dT, + jacobianFD.toViewConstSizes() ); + + } +#endif + } + +#if 1 + // b) compute all the derivatives wrt to the connection in WELL elem + // iwelem + for( localIndex iwelem = 0; iwelem < subRegion.size(); ++iwelem ) + { + { + solver.resetStateToBeginningOfStep( domain ); + + // here is the perturbation in the rate of the well element + real64 const dRate = perturbParameter * ( connRate[iwelem] + perturbParameter ); + connRate.move( hostMemorySpace, true ); + connRate[iwelem] += dRate; + + wellSolver.updateState( domain ); + + residual.zero(); + jacobian.zero(); + assembleFunction( jacobian.toViewConstSizes(), residual.toView() ); + + fillNumericalJacobian( residual.toViewConst(), + residualOrig.toViewConst(), + wellElemDofNumber[iwelem] + compositionalMultiphaseWellKernels::ColOffset::DCOMP + NC, + dRate, + jacobianFD.toViewConstSizes() ); + } + } +#endif + } ); + } ); + + // assemble the analytical jacobian + solver.resetStateToBeginningOfStep( domain ); + + residual.zero(); + jacobian.zero(); + assembleFunction( jacobian.toViewConstSizes(), residual.toView() ); + printCompareLocalMatrices( jacobian.toViewConst(), jacobianFD.toViewConst(), testName ); + compareLocalMatrices( jacobian.toViewConst(), jacobianFD.toViewConst(), relTol ); +} + +class CompositionalMultiphaseReservoirSolverTest : public ::testing::Test +{ +public: + + CompositionalMultiphaseReservoirSolverTest(): + state( std::make_unique< CommandLineOptions >( g_commandLineOptions ) ) + {} + +protected: + + void SetUp() override + { + setupProblemFromXML( state.getProblemManager(), xmlInput ); + solver = &state.getProblemManager().getPhysicsSolverManager().getGroup< CompositionalMultiphaseReservoirAndWells< CompositionalMultiphaseBase > >( "reservoirSystem" ); + + DomainPartition & domain = state.getProblemManager().getDomainPartition(); + + solver->setupSystem( domain, + solver->getDofManager(), + solver->getLocalMatrix(), + solver->getSystemRhs(), + solver->getSystemSolution() ); + +#if 0 + solver->wellSolver()->setupSystem( domain, + solver->wellSolver()->getDofManager(), + solver->wellSolver()->getLocalMatrix(), + solver->wellSolver()->getSystemRhs(), + solver->wellSolver()->getSystemSolution() ); +#endif + solver->implicitStepSetup( time, dt, domain ); + WellManager & wellSolver = *solver->wellSolver(); + wellSolver.forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const & meshBodyName, + MeshLevel & meshLevel, + string_array const & regionNames ) + { + ElementRegionManager & elementRegionManager = meshLevel.getElemManager(); + elementRegionManager.forElementRegions< WellElementRegion >( regionNames, + [&]( localIndex const, + WellElementRegion & region ) + { + WellElementSubRegion & subRegion = region.getGroup( ElementRegionBase::viewKeyStruct::elementSubRegions() ) + .getGroup< WellElementSubRegion >( region.getSubRegionName() ); + WellControls & wellControls = wellSolver.getWellControls( subRegion ); + wellControls.initializeWell( domain, domain.getMeshBodies(), meshBodyName, meshLevel, subRegion, time ); + } ); + } ); + } + + static real64 constexpr time = 0.0; + static real64 constexpr dt = 1e4; + static real64 constexpr eps = std::numeric_limits< real64 >::epsilon(); + + GeosxState state; + CompositionalMultiphaseReservoirAndWells< CompositionalMultiphaseBase > * solver; +}; + +real64 constexpr CompositionalMultiphaseReservoirSolverTest::time; +real64 constexpr CompositionalMultiphaseReservoirSolverTest::dt; +real64 constexpr CompositionalMultiphaseReservoirSolverTest::eps; + +#if 0 +TEST_F( CompositionalMultiphaseReservoirSolverTest, jacobianNumericalCheck_FullSystem ) +{ + real64 const perturb = std::sqrt( eps ); + real64 const tol = 1e-1; // 10% error margin + + DomainPartition & domain = state.getProblemManager().getDomainPartition(); + + testWellEstimatorNumericalJacobian( *solver, domain, perturb, time, tol, "Check_FullSystem", + [&] ( CRSMatrixView< real64, globalIndex const > const & localMatrix, + arrayView1d< real64 > const & localRhs ) + { + solver->assembleSystem( time, dt, domain, solver->getDofManager(), localMatrix, localRhs ); + solver->assembleCouplingTerms( time, dt, domain, solver->getDofManager(), localMatrix, localRhs ); + } ); +} +#endif + +#if 1 +TEST_F( CompositionalMultiphaseReservoirSolverTest, jacobianNumericalCheck_System ) +{ + real64 const perturb = std::sqrt( eps ); + real64 const tol = 1e-1; // 10% error margin + + DomainPartition & domain = state.getProblemManager().getDomainPartition(); + + testWellEstimatorNumericalJacobian( *solver, domain, perturb, time, tol, "Check_System", + [&] ( CRSMatrixView< real64, globalIndex const > const & localMatrix, + arrayView1d< real64 > const & localRhs ) + { + + DofManager const & dofManager = solver->getDofManager(); + WellManager * wellSolver = solver->wellSolver(); + wellSolver->forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&] ( string const &, + MeshLevel & mesh, + string_array const & regionNames ) + { + ElementRegionManager & elemManager = mesh.getElemManager(); + elemManager.forElementSubRegions< WellElementSubRegion >( regionNames, + [&]( localIndex const, + WellElementSubRegion & subRegion ) + { + // call assemble to fill the matrix and the rhs + WellControls & wellControls = wellSolver->getWellControls( subRegion ); + CompositionalMultiphaseWell * compWell = dynamic_cast< CompositionalMultiphaseWell * >(&wellControls); + compWell->assembleSystem( time, + dt, + 0, + elemManager, + subRegion, + dofManager, + localMatrix, + localRhs ); + +// apply boundary conditions to system +#if 0 + compWell->applyWellBoundaryConditions( time, + dt, + elemManager, + subRegion, + dofManager, + localRhs, + localMatrix ); +#endif + } ); + } ); + + solver->assembleCouplingTerms( time, dt, domain, solver->getDofManager(), localMatrix, localRhs ); + + } ); +} +#endif +TEST_F( CompositionalMultiphaseReservoirSolverTest, jacobianNumericalCheck_Accum ) +{ + real64 const perturb = std::sqrt( eps ); + real64 const tol = 1e-1; // 10% error margin + + DomainPartition & domain = state.getProblemManager().getDomainPartition(); + + testWellEstimatorNumericalJacobian( *solver, domain, perturb, time, tol, "Check_Accum", + [&] ( CRSMatrixView< real64, globalIndex const > const & localMatrix, + arrayView1d< real64 > const & localRhs ) + { + + //DofManager const & dofManager = solver->wellSolver()->getDofManager(); + WellManager * wellSolver = solver->wellSolver(); + wellSolver->forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&] ( string const &, + MeshLevel & mesh, + string_array const & regionNames ) + { + ElementRegionManager & elemManager = mesh.getElemManager(); + elemManager.forElementSubRegions< WellElementSubRegion >( regionNames, + [&]( localIndex const, + WellElementSubRegion & subRegion ) + { + // call assemble to fill the matrix and the rhs + WellControls & wellControls = wellSolver->getWellControls( subRegion ); + wellControls.assembleWellAccumulationTerms( time, dt, subRegion, solver->getDofManager(), localMatrix, localRhs ); + } ); + } ); + } ); +} +#if 1 +TEST_F( CompositionalMultiphaseReservoirSolverTest, jacobianNumericalCheck_PressureRelation ) +{ + real64 const perturb = std::sqrt( eps ); + real64 const tol = 1e-1; // 10% error margin + + DomainPartition & domain = state.getProblemManager().getDomainPartition(); + + testWellEstimatorNumericalJacobian( *solver, domain, perturb, time, tol, "Check_PressureRelation", + [&] ( CRSMatrixView< real64, globalIndex const > const & localMatrix, + arrayView1d< real64 > const & localRhs ) + { + + //DofManager const & dofManager = solver->wellSolver()->getDofManager(); + DofManager const & dofManager = solver->getDofManager(); + WellManager * wellSolver = solver->wellSolver(); + wellSolver->forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&] ( string const &, + MeshLevel & mesh, + string_array const & regionNames ) + { + ElementRegionManager & elemManager = mesh.getElemManager(); + elemManager.forElementSubRegions< WellElementSubRegion >( regionNames, + [&]( localIndex const, + WellElementSubRegion & subRegion ) + { + // call assemble to fill the matrix and the rhs + WellControls & wellControls = wellSolver->getWellControls( subRegion ); + //wellControls.assembleWellConstraintTerms( time, dt, subRegion, dofManager, localMatrix.toViewConstSizes(), localRhs ); + wellControls.assembleWellPressureRelations( time, dt, subRegion, dofManager, localMatrix.toViewConstSizes(), localRhs ); + + } ); + } ); + } ); +} +#endif +#if 1 +TEST_F( CompositionalMultiphaseReservoirSolverTest, jacobianNumericalCheck_Flux ) +{ + real64 const perturb = std::sqrt( eps ); + real64 const tol = 1e-1; // 10% error margin + + DomainPartition & domain = state.getProblemManager().getDomainPartition(); + + testWellEstimatorNumericalJacobian( *solver, domain, perturb, time, tol, "Check_Flux", + [&] ( CRSMatrixView< real64, globalIndex const > const & localMatrix, + arrayView1d< real64 > const & localRhs ) + { + + //DofManager const & dofManager = solver->wellSolver()->getDofManager(); + WellManager * wellSolver = solver->wellSolver(); + wellSolver->forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&] ( string const &, + MeshLevel & mesh, + string_array const & regionNames ) + { + ElementRegionManager & elemManager = mesh.getElemManager(); + elemManager.forElementSubRegions< WellElementSubRegion >( regionNames, + [&]( localIndex const, + WellElementSubRegion & subRegion ) + { + // call assemble to fill the matrix and the rhs + WellControls & wellControls = wellSolver->getWellControls( subRegion ); + wellControls.assembleWellFluxTerms( time, dt, subRegion, solver->getDofManager(), localMatrix, localRhs ); + } ); + } ); + } ); +} +#endif +int main( int argc, char * * argv ) +{ + writeTableToFile( "co2flash.txt", co2flash ); + writeTableToFile( "pvtliquid.txt", pvtLiquid ); + writeTableToFile( "pvtgas.txt", pvtGas ); + ::testing::InitGoogleTest( &argc, argv ); + g_commandLineOptions = *geos::basicSetup( argc, argv ); + int const result = RUN_ALL_TESTS(); + geos::basicCleanup(); + removeFile( "co2flash.txt" ); + removeFile( "pvtliquid.txt" ); + removeFile( "pvtgas.txt" ); + + return result; +} diff --git a/src/coreComponents/integrationTests/wellsTests/testThermalInjWell.cpp b/src/coreComponents/integrationTests/wellsTests/testThermalInjWell.cpp index b7bfa1a4ce6..77b7b1d60ae 100644 --- a/src/coreComponents/integrationTests/wellsTests/testThermalInjWell.cpp +++ b/src/coreComponents/integrationTests/wellsTests/testThermalInjWell.cpp @@ -93,28 +93,32 @@ char const * xmlInput = maxCompFractionChange="0.5" targetRegions="{ region }"> - - - - + control="totalVolRate"> + + + + )xml"; +// CSV output file are generated for the jacobian, which can be used to compare the analytical and numerical results. +// Both matrices are reported to same file template< typename T, typename COL_INDEX > void printCompareLocalMatrices( CRSMatrixView< T const, COL_INDEX const > const & matrix1, CRSMatrixView< T const, COL_INDEX const > const & matrix2, std::string const & testName ) @@ -328,7 +334,9 @@ void printCompareLocalMatrices( CRSMatrixView< T const, COL_INDEX const > const omat1.close(); } - +// CSV output file are generated for the residuals, which can be used to compare the analytical and numerical results. +// Both residuals are reported to same file. This is used to determine how the perturbation affects the residuals. +// This is useful to determine if the perturbation is too small or too large. void printResiduals( array1d< real64 > const & rsd1, array1d< real64 > const & rsd2, std::string const & testName ) { @@ -351,7 +359,7 @@ void testWellNumericalJacobian( CompositionalMultiphaseReservoirAndWells< Compos { GEOS_UNUSED_VAR( time_n ); GEOS_UNUSED_VAR ( testName ); - CompositionalMultiphaseWell & wellSolver = *solver.wellSolver(); + WellManager & wellSolver = *solver.wellSolver(); CompositionalMultiphaseFVM & flowSolver = dynamicCast< CompositionalMultiphaseFVM & >( *solver.reservoirSolver() ); @@ -381,6 +389,8 @@ void testWellNumericalJacobian( CompositionalMultiphaseReservoirAndWells< Compos //////////////////////////////////////////////// // Step 1) Compute the terms in J_RR and J_WR // //////////////////////////////////////////////// +// Toggels to turn on/off the numerical jacobian computation of terms with respect to reservoir element dofs. +// This is useful to isolate the terms that are causing the test to fail. #if 1 domain.forMeshBodies( [&] ( MeshBody & meshBody ) { @@ -412,6 +422,7 @@ void testWellNumericalJacobian( CompositionalMultiphaseReservoirAndWells< Compos // a) compute all the derivatives wrt to the pressure in RESERVOIR elem ei for( localIndex ei = 0; ei < subRegion.size(); ++ei ) { +// Turn on/off the numerical jacobian computation of terms with respect to reservoir pressure. #if 1 { solver.resetStateToBeginningOfStep( domain ); @@ -447,6 +458,7 @@ void testWellNumericalJacobian( CompositionalMultiphaseReservoirAndWells< Compos jacobianFD.toViewConstSizes() ); } #endif +// Turn on/off the numerical jacobian computation of terms with respect to reservoir temperature. #if 1 { solver.resetStateToBeginningOfStep( domain ); @@ -482,6 +494,7 @@ void testWellNumericalJacobian( CompositionalMultiphaseReservoirAndWells< Compos jacobianFD.toViewConstSizes() ); } #endif +// Turn on/off the numerical jacobian computation of terms with respect to reservoir component density. #if 1 real64 totalDensity = 0.0; for( localIndex ic = 0; ic < NC; ++ic ) @@ -493,7 +506,7 @@ void testWellNumericalJacobian( CompositionalMultiphaseReservoirAndWells< Compos { solver.resetStateToBeginningOfStep( domain ); - real64 const dRho = perturbParameter * totalDensity; + real64 const dRho = perturbParameter * (totalDensity+ perturbParameter); compDens.move( hostMemorySpace, true ); compDens[ei][jc] += dRho; @@ -529,14 +542,13 @@ void testWellNumericalJacobian( CompositionalMultiphaseReservoirAndWells< Compos } ); #endif - // at this point we start assembling the finite-difference block by block - - ///////////////////////////////////////////////// // Step 2) Compute the terms in J_RW and J_WW // ///////////////////////////////////////////////// // loop over the wells +// Toggels to turn on/off the numerical jacobian computation of terms with respect to well element dofs. +// This is useful to isolate the terms that are causing the test to fail. if( 1 ) wellSolver.forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&] ( string const &, MeshLevel & mesh, @@ -564,13 +576,13 @@ void testWellNumericalJacobian( CompositionalMultiphaseReservoirAndWells< Compos wellElemCompDens.move( hostMemorySpace, false ); arrayView1d< real64 > const & connRate = - subRegion.getField< fields::well::mixtureConnectionRate >(); + subRegion.getField< fields::well::connectionRate >(); connRate.move( hostMemorySpace, false ); // a) compute all the derivatives wrt to the pressure in WELL elem iwelem for( localIndex iwelem = 0; iwelem < subRegion.size(); ++iwelem ) { - +// Turn on/off the numerical jacobian computation of terms with respect to well pressure. #if 1 { solver.resetStateToBeginningOfStep( domain ); @@ -594,6 +606,7 @@ void testWellNumericalJacobian( CompositionalMultiphaseReservoirAndWells< Compos jacobianFD.toViewConstSizes() ); } #endif +// Turn on/off the numerical jacobian computation of terms with respect to well component density. #if 1 real64 wellElemTotalDensity = 0.0; for( localIndex ic = 0; ic < NC; ++ic ) @@ -604,7 +617,7 @@ void testWellNumericalJacobian( CompositionalMultiphaseReservoirAndWells< Compos { solver.resetStateToBeginningOfStep( domain ); - real64 const dRho = perturbParameter * wellElemTotalDensity; + real64 const dRho = perturbParameter * (wellElemTotalDensity+ perturbParameter); wellElemCompDens.move( hostMemorySpace, true ); wellElemCompDens[iwelem][jc] += dRho; @@ -663,10 +676,9 @@ void testWellNumericalJacobian( CompositionalMultiphaseReservoirAndWells< Compos } #endif } - +// Turn on/off the numerical jacobian computation of terms with respect to well connection rate. #if 1 - // b) compute all the derivatives wrt to the connection in WELL elem - // iwelem + for( localIndex iwelem = 0; iwelem < subRegion.size(); ++iwelem ) { { @@ -741,12 +753,13 @@ real64 constexpr CompositionalMultiphaseReservoirSolverTest::time; real64 constexpr CompositionalMultiphaseReservoirSolverTest::dt; real64 constexpr CompositionalMultiphaseReservoirSolverTest::eps; - - -#if 0 -// There are a few terms that cause this test to fail, these are dt terms of cell connected to cell with boundary condtion. -// The could be zeroed out in the jacobian assembly and then this test should pass. +// Enable test for full well system jacobian, which includes the coupling terms. +// There are is a terms that cause this test to fail, these are dt terms of cell connected to cell with boundary condtion. +// The the well cell with boundary condition cannot have 2 constraints. The temperature equation is set so that +// in the energy balance dEnergyBalance/dT = 1 and the residual is set to zero. +// This causes the numerical jacobian to have a non-zero value, but the analytical jacobian has zero. // Otherwise the test is good, uncomment out printCompareLocalMatrices and look at FD and computed derivatives +#if 0 TEST_F( CompositionalMultiphaseReservoirSolverTest, jacobianNumericalCheck_System ) { real64 const perturb = std::sqrt( eps ); @@ -776,9 +789,26 @@ TEST_F( CompositionalMultiphaseReservoirSolverTest, jacobianNumericalCheck_Accum [&] ( CRSMatrixView< real64, globalIndex const > const & localMatrix, arrayView1d< real64 > const & localRhs ) { - solver->wellSolver()->assembleAccumulationTerms( time, dt, domain, solver->getDofManager(), localMatrix, localRhs ); + WellManager & wellSolver = *solver->wellSolver(); + wellSolver.forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const & meshBodyName, + MeshLevel & meshLevel, + string_array const & regionNames ) + { + GEOS_UNUSED_VAR( meshBodyName ); + ElementRegionManager & elementRegionManager = meshLevel.getElemManager(); + elementRegionManager.forElementRegions< WellElementRegion >( regionNames, + [&]( localIndex const, + WellElementRegion & region ) + { + WellElementSubRegion & subRegion = region.getGroup( ElementRegionBase::viewKeyStruct::elementSubRegions() ) + .getGroup< WellElementSubRegion >( region.getSubRegionName() ); + WellControls & wellControls = wellSolver.getWellControls( subRegion ); + wellControls.assembleWellAccumulationTerms( time, dt, subRegion, solver->getDofManager(), localMatrix, localRhs ); + } ); + } ); } ); } +// Enable test for well pressure relation derivatives #if 1 TEST_F( CompositionalMultiphaseReservoirSolverTest, jacobianNumericalCheck_PressureRelation ) { @@ -791,10 +821,27 @@ TEST_F( CompositionalMultiphaseReservoirSolverTest, jacobianNumericalCheck_Press [&] ( CRSMatrixView< real64, globalIndex const > const & localMatrix, arrayView1d< real64 > const & localRhs ) { - solver->wellSolver()->assemblePressureRelations( time, dt, domain, solver->getDofManager(), localMatrix, localRhs ); + WellManager & wellSolver = *solver->wellSolver(); + wellSolver.forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const & meshBodyName, + MeshLevel & meshLevel, + string_array const & regionNames ) + { + GEOS_UNUSED_VAR( meshBodyName ); + ElementRegionManager & elementRegionManager = meshLevel.getElemManager(); + elementRegionManager.forElementRegions< WellElementRegion >( regionNames, + [&]( localIndex const, + WellElementRegion & region ) + { + WellElementSubRegion & subRegion = region.getGroup( ElementRegionBase::viewKeyStruct::elementSubRegions() ) + .getGroup< WellElementSubRegion >( region.getSubRegionName() ); + WellControls & wellControls = wellSolver.getWellControls( subRegion ); + wellControls.assembleWellPressureRelations( time, dt, subRegion, solver->getDofManager(), localMatrix, localRhs ); + } ); + } ); } ); } #endif +// Enable test for well flux derivatives #if 1 TEST_F( CompositionalMultiphaseReservoirSolverTest, jacobianNumericalCheck_Flux ) { @@ -807,7 +854,24 @@ TEST_F( CompositionalMultiphaseReservoirSolverTest, jacobianNumericalCheck_Flux [&] ( CRSMatrixView< real64, globalIndex const > const & localMatrix, arrayView1d< real64 > const & localRhs ) { - solver->wellSolver()->assembleFluxTerms( time, dt, domain, solver->getDofManager(), localMatrix, localRhs ); + + WellManager & wellSolver = *solver->wellSolver(); + wellSolver.forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const & meshBodyName, + MeshLevel & meshLevel, + string_array const & regionNames ) + { + GEOS_UNUSED_VAR( meshBodyName ); + ElementRegionManager & elementRegionManager = meshLevel.getElemManager(); + elementRegionManager.forElementRegions< WellElementRegion >( regionNames, + [&]( localIndex const, + WellElementRegion & region ) + { + WellElementSubRegion & subRegion = region.getGroup( ElementRegionBase::viewKeyStruct::elementSubRegions() ) + .getGroup< WellElementSubRegion >( region.getSubRegionName() ); + WellControls & wellControls = wellSolver.getWellControls( subRegion ); + wellControls.assembleWellFluxTerms( time, dt, subRegion, solver->getDofManager(), localMatrix, localRhs ); + } ); + } ); } ); } #endif diff --git a/src/coreComponents/integrationTests/wellsTests/testThermalProdWell.cpp b/src/coreComponents/integrationTests/wellsTests/testThermalProdWell.cpp index a0bbc409c9e..fda57774f2b 100644 --- a/src/coreComponents/integrationTests/wellsTests/testThermalProdWell.cpp +++ b/src/coreComponents/integrationTests/wellsTests/testThermalProdWell.cpp @@ -37,8 +37,6 @@ using namespace geos::testing; CommandLineOptions g_commandLineOptions; - - void writeTableToFile( string const & filename, char const * str ) { std::ofstream os( filename ); @@ -95,14 +93,14 @@ char const * xmlInput = maxCompFractionChange="0.5" targetRegions="{ region }"> - - + surfaceTemperature="300.15"> + + + )xml"; +// CSV output file are generated for the jacobian, which can be used to compare the analytical and numerical results. +// Both matrices are reported to same file template< typename T, typename COL_INDEX > void printCompareLocalMatrices( CRSMatrixView< T const, COL_INDEX const > const & matrix1, CRSMatrixView< T const, COL_INDEX const > const & matrix2, std::string const & testName ) @@ -329,7 +334,9 @@ void printCompareLocalMatrices( CRSMatrixView< T const, COL_INDEX const > const omat1.close(); } - +// CSV output file are generated for the residuals, which can be used to compare the analytical and numerical results. +// Both residuals are reported to same file. This is used to determine how the perturbation affects the residuals. +// This is useful to determine if the perturbation is too small or too large. void printResiduals( array1d< real64 > const & rsd1, array1d< real64 > const & rsd2, std::string const & testName ) { @@ -352,7 +359,7 @@ void testWellNumericalJacobian( CompositionalMultiphaseReservoirAndWells< Compos { GEOS_UNUSED_VAR( time_n ); GEOS_UNUSED_VAR ( testName ); - CompositionalMultiphaseWell & wellSolver = *solver.wellSolver(); + WellManager & wellSolver = *solver.wellSolver(); CompositionalMultiphaseFVM & flowSolver = dynamicCast< CompositionalMultiphaseFVM & >( *solver.reservoirSolver() ); @@ -381,6 +388,8 @@ void testWellNumericalJacobian( CompositionalMultiphaseReservoirAndWells< Compos //////////////////////////////////////////////// // Step 1) Compute the terms in J_RR and J_WR // //////////////////////////////////////////////// +// Toggels to turn on/off the numerical jacobian computation of terms with respect to reservoir element dofs. +// This is useful to isolate the terms that are causing the test to fail. #if 1 domain.forMeshBodies( [&] ( MeshBody & meshBody ) { @@ -412,8 +421,8 @@ void testWellNumericalJacobian( CompositionalMultiphaseReservoirAndWells< Compos // a) compute all the derivatives wrt to the pressure in RESERVOIR elem ei for( localIndex ei = 0; ei < subRegion.size(); ++ei ) { - - +// Turn on/off the numerical jacobian computation of terms with respect to reservoir pressure. +#if 1 { solver.resetStateToBeginningOfStep( domain ); @@ -446,6 +455,8 @@ void testWellNumericalJacobian( CompositionalMultiphaseReservoirAndWells< Compos dP, jacobianFD.toViewConstSizes() ); } +#endif +// Turn on/off the numerical jacobian computation of terms with respect to reservoir temperature. #if 1 { solver.resetStateToBeginningOfStep( domain ); @@ -481,6 +492,7 @@ void testWellNumericalJacobian( CompositionalMultiphaseReservoirAndWells< Compos jacobianFD.toViewConstSizes() ); } #endif +// Turn on/off the numerical jacobian computation of terms with respect to reservoir component density. #if 1 real64 totalDensity = 0.0; for( localIndex ic = 0; ic < NC; ++ic ) @@ -492,7 +504,7 @@ void testWellNumericalJacobian( CompositionalMultiphaseReservoirAndWells< Compos { solver.resetStateToBeginningOfStep( domain ); - real64 const dRho = perturbParameter * totalDensity; + real64 const dRho = perturbParameter * (totalDensity+perturbParameter); compDens.move( hostMemorySpace, true ); compDens[ei][jc] += dRho; @@ -528,14 +540,13 @@ void testWellNumericalJacobian( CompositionalMultiphaseReservoirAndWells< Compos } ); #endif - // at this point we start assembling the finite-difference block by block - - ///////////////////////////////////////////////// // Step 2) Compute the terms in J_RW and J_WW // ///////////////////////////////////////////////// // loop over the wells +// Toggels to turn on/off the numerical jacobian computation of terms with respect to well element dofs. +// This is useful to isolate the terms that are causing the test to fail. if( 1 ) wellSolver.forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&] ( string const &, MeshLevel & mesh, @@ -563,13 +574,13 @@ void testWellNumericalJacobian( CompositionalMultiphaseReservoirAndWells< Compos wellElemCompDens.move( hostMemorySpace, false ); arrayView1d< real64 > const & connRate = - subRegion.getField< fields::well::mixtureConnectionRate >(); + subRegion.getField< fields::well::connectionRate >(); connRate.move( hostMemorySpace, false ); // a) compute all the derivatives wrt to the pressure in WELL elem iwelem for( localIndex iwelem = 0; iwelem < subRegion.size(); ++iwelem ) { - +// Turn on/off the numerical jacobian computation of terms with respect to well pressure. #if 1 { solver.resetStateToBeginningOfStep( domain ); @@ -593,6 +604,7 @@ void testWellNumericalJacobian( CompositionalMultiphaseReservoirAndWells< Compos jacobianFD.toViewConstSizes() ); } #endif +// Turn on/off the numerical jacobian computation of terms with respect to well component density. #if 1 real64 wellElemTotalDensity = 0.0; for( localIndex ic = 0; ic < NC; ++ic ) @@ -603,7 +615,7 @@ void testWellNumericalJacobian( CompositionalMultiphaseReservoirAndWells< Compos { solver.resetStateToBeginningOfStep( domain ); - real64 const dRho = perturbParameter * wellElemTotalDensity; + real64 const dRho = perturbParameter * (wellElemTotalDensity+perturbParameter);; wellElemCompDens.move( hostMemorySpace, true ); wellElemCompDens[iwelem][jc] += dRho; @@ -642,10 +654,9 @@ void testWellNumericalJacobian( CompositionalMultiphaseReservoirAndWells< Compos } #endif } - +// Turn on/off the numerical jacobian computation of terms with respect to well connection rate. #if 1 - // b) compute all the derivatives wrt to the connection in WELL elem - // iwelem + for( localIndex iwelem = 0; iwelem < subRegion.size(); ++iwelem ) { { @@ -737,7 +748,7 @@ TEST_F( CompositionalMultiphaseReservoirSolverTest, jacobianNumericalCheck_FullS } ); } #endif - +// Enable test for full well system jacobian, which includes the coupling terms. #if 1 TEST_F( CompositionalMultiphaseReservoirSolverTest, jacobianNumericalCheck_System ) { @@ -768,9 +779,27 @@ TEST_F( CompositionalMultiphaseReservoirSolverTest, jacobianNumericalCheck_Accum [&] ( CRSMatrixView< real64, globalIndex const > const & localMatrix, arrayView1d< real64 > const & localRhs ) { - solver->wellSolver()->assembleAccumulationTerms( time, dt, domain, solver->getDofManager(), localMatrix, localRhs ); + + WellManager & wellSolver = *solver->wellSolver(); + wellSolver.forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const & meshBodyName, + MeshLevel & meshLevel, + string_array const & regionNames ) + { + GEOS_UNUSED_VAR( meshBodyName ); + ElementRegionManager & elementRegionManager = meshLevel.getElemManager(); + elementRegionManager.forElementRegions< WellElementRegion >( regionNames, + [&]( localIndex const, + WellElementRegion & region ) + { + WellElementSubRegion & subRegion = region.getGroup( ElementRegionBase::viewKeyStruct::elementSubRegions() ) + .getGroup< WellElementSubRegion >( region.getSubRegionName() ); + WellControls & wellControls = wellSolver.getWellControls( subRegion ); + wellControls.assembleWellAccumulationTerms( time, dt, subRegion, solver->getDofManager(), localMatrix, localRhs ); + } ); + } ); } ); } +// Enable test for well pressure relation derivatives #if 1 TEST_F( CompositionalMultiphaseReservoirSolverTest, jacobianNumericalCheck_PressureRelation ) { @@ -783,10 +812,27 @@ TEST_F( CompositionalMultiphaseReservoirSolverTest, jacobianNumericalCheck_Press [&] ( CRSMatrixView< real64, globalIndex const > const & localMatrix, arrayView1d< real64 > const & localRhs ) { - solver->wellSolver()->assemblePressureRelations( time, dt, domain, solver->getDofManager(), localMatrix, localRhs ); + WellManager & wellSolver = *solver->wellSolver(); + wellSolver.forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const & meshBodyName, + MeshLevel & meshLevel, + string_array const & regionNames ) + { + GEOS_UNUSED_VAR( meshBodyName ); + ElementRegionManager & elementRegionManager = meshLevel.getElemManager(); + elementRegionManager.forElementRegions< WellElementRegion >( regionNames, + [&]( localIndex const, + WellElementRegion & region ) + { + WellElementSubRegion & subRegion = region.getGroup( ElementRegionBase::viewKeyStruct::elementSubRegions() ) + .getGroup< WellElementSubRegion >( region.getSubRegionName() ); + WellControls & wellControls = wellSolver.getWellControls( subRegion ); + wellControls.assembleWellPressureRelations( time, dt, subRegion, solver->getDofManager(), localMatrix, localRhs ); + } ); + } ); } ); } #endif +// Enable test for well flux derivatives #if 1 TEST_F( CompositionalMultiphaseReservoirSolverTest, jacobianNumericalCheck_Flux ) { @@ -799,7 +845,23 @@ TEST_F( CompositionalMultiphaseReservoirSolverTest, jacobianNumericalCheck_Flux [&] ( CRSMatrixView< real64, globalIndex const > const & localMatrix, arrayView1d< real64 > const & localRhs ) { - solver->wellSolver()->assembleFluxTerms( time, dt, domain, solver->getDofManager(), localMatrix, localRhs ); + WellManager & wellSolver = *solver->wellSolver(); + wellSolver.forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const & meshBodyName, + MeshLevel & meshLevel, + string_array const & regionNames ) + { + GEOS_UNUSED_VAR( meshBodyName ); + ElementRegionManager & elementRegionManager = meshLevel.getElemManager(); + elementRegionManager.forElementRegions< WellElementRegion >( regionNames, + [&]( localIndex const, + WellElementRegion & region ) + { + WellElementSubRegion & subRegion = region.getGroup( ElementRegionBase::viewKeyStruct::elementSubRegions() ) + .getGroup< WellElementSubRegion >( region.getSubRegionName() ); + WellControls & wellControls = wellSolver.getWellControls( subRegion ); + wellControls.assembleWellFluxTerms( time, dt, subRegion, solver->getDofManager(), localMatrix, localRhs ); + } ); + } ); } ); } #endif diff --git a/src/coreComponents/integrationTests/wellsTests/testThermalReservoirCompositionalMultiphaseMSWells.cpp b/src/coreComponents/integrationTests/wellsTests/testThermalReservoirCompositionalMultiphaseMSWells.cpp index 269b4e6fdeb..1ee3ad577c3 100644 --- a/src/coreComponents/integrationTests/wellsTests/testThermalReservoirCompositionalMultiphaseMSWells.cpp +++ b/src/coreComponents/integrationTests/wellsTests/testThermalReservoirCompositionalMultiphaseMSWells.cpp @@ -96,28 +96,33 @@ char const * xmlInput = targetRegions="{ region }"> - - - + surfaceTemperature="300.15"> + + + + @@ -375,7 +380,7 @@ void testNumericalJacobian( CompositionalMultiphaseReservoirAndWells< Compositio LAMBDA && assembleFunction ) { GEOS_UNUSED_VAR( testName ); - CompositionalMultiphaseWell & wellSolver = *solver.wellSolver(); + WellManager & wellSolver = *solver.wellSolver(); CompositionalMultiphaseFVM & flowSolver = dynamicCast< CompositionalMultiphaseFVM & >( *solver.reservoirSolver() ); localIndex const NC = flowSolver.numFluidComponents(); @@ -587,7 +592,7 @@ void testNumericalJacobian( CompositionalMultiphaseReservoirAndWells< Compositio wellElemCompDens.move( hostMemorySpace, false ); arrayView1d< real64 > const & connRate = - subRegion.getField< fields::well::mixtureConnectionRate >(); + subRegion.getField< fields::well::connectionRate >(); connRate.move( hostMemorySpace, false ); // a) compute all the derivatives wrt to the pressure in WELL elem iwelem @@ -748,6 +753,24 @@ class CompositionalMultiphaseReservoirSolverTest : public ::testing::Test solver->getSystemSolution() ); solver->implicitStepSetup( time, dt, domain ); + + WellManager & wellSolver = *solver->wellSolver(); + wellSolver.forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const & meshBodyName, + MeshLevel & meshLevel, + string_array const & regionNames ) + { + GEOS_UNUSED_VAR( meshBodyName ); + ElementRegionManager & elementRegionManager = meshLevel.getElemManager(); + elementRegionManager.forElementRegions< WellElementRegion >( regionNames, + [&]( localIndex const, + WellElementRegion & region ) + { + WellElementSubRegion & subRegion = region.getGroup( ElementRegionBase::viewKeyStruct::elementSubRegions() ) + .getGroup< WellElementSubRegion >( region.getSubRegionName() ); + WellControls & wellControls = wellSolver.getWellControls( subRegion ); + wellControls.initializeWell( domain, domain.getMeshBodies(), meshBodyName, meshLevel, subRegion, time ); + } ); + } ); } static real64 constexpr time = 0.0; @@ -779,13 +802,11 @@ TEST_F( CompositionalMultiphaseReservoirSolverTest, jacobianNumericalCheck_Perfo { solver->wellSolver()->assembleSystem( time, dt, domain, solver->getDofManager(), localMatrix, localRhs ); solver->assembleCouplingTerms( time, dt, domain, solver->getDofManager(), localMatrix, localRhs ); - } ); } #endif - #if 1 TEST_F( CompositionalMultiphaseReservoirSolverTest, jacobianNumericalCheck_Accum_Vol_Energy_Bal ) @@ -799,7 +820,23 @@ TEST_F( CompositionalMultiphaseReservoirSolverTest, jacobianNumericalCheck_Accum [&] ( CRSMatrixView< real64, globalIndex const > const & localMatrix, arrayView1d< real64 > const & localRhs ) { - solver->wellSolver()->assembleAccumulationTerms( time, dt, domain, solver->getDofManager(), localMatrix, localRhs ); + WellManager & wellSolver = *solver->wellSolver(); + wellSolver.forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const & meshBodyName, + MeshLevel & meshLevel, + string_array const & regionNames ) + { + GEOS_UNUSED_VAR( meshBodyName ); + ElementRegionManager & elementRegionManager = meshLevel.getElemManager(); + elementRegionManager.forElementRegions< WellElementRegion >( regionNames, + [&]( localIndex const, + WellElementRegion & region ) + { + WellElementSubRegion & subRegion = region.getGroup( ElementRegionBase::viewKeyStruct::elementSubRegions() ) + .getGroup< WellElementSubRegion >( region.getSubRegionName() ); + WellControls & wellControls = wellSolver.getWellControls( subRegion ); + wellControls.assembleWellAccumulationTerms( time, dt, subRegion, solver->getDofManager(), localMatrix, localRhs ); + } ); + } ); } ); } TEST_F( CompositionalMultiphaseReservoirSolverTest, jacobianNumericalCheck_PressureRel ) @@ -814,7 +851,23 @@ TEST_F( CompositionalMultiphaseReservoirSolverTest, jacobianNumericalCheck_Press [&] ( CRSMatrixView< real64, globalIndex const > const & localMatrix, arrayView1d< real64 > const & localRhs ) { - solver->wellSolver()->assemblePressureRelations( time, dt, domain, solver->getDofManager(), localMatrix, localRhs ); + WellManager & wellSolver = *solver->wellSolver(); + wellSolver.forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const & meshBodyName, + MeshLevel & meshLevel, + string_array const & regionNames ) + { + GEOS_UNUSED_VAR( meshBodyName ); + ElementRegionManager & elementRegionManager = meshLevel.getElemManager(); + elementRegionManager.forElementRegions< WellElementRegion >( regionNames, + [&]( localIndex const, + WellElementRegion & region ) + { + WellElementSubRegion & subRegion = region.getGroup( ElementRegionBase::viewKeyStruct::elementSubRegions() ) + .getGroup< WellElementSubRegion >( region.getSubRegionName() ); + WellControls & wellControls = wellSolver.getWellControls( subRegion ); + wellControls.assembleWellPressureRelations( time, dt, subRegion, solver->getDofManager(), localMatrix, localRhs ); + } ); + } ); } ); } #endif diff --git a/src/coreComponents/integrationTests/wellsTests/testThermalReservoirCompositionalMultiphaseSSWells.cpp b/src/coreComponents/integrationTests/wellsTests/testThermalReservoirCompositionalMultiphaseSSWells.cpp index 76fa20d4cea..5caf701f77e 100644 --- a/src/coreComponents/integrationTests/wellsTests/testThermalReservoirCompositionalMultiphaseSSWells.cpp +++ b/src/coreComponents/integrationTests/wellsTests/testThermalReservoirCompositionalMultiphaseSSWells.cpp @@ -97,28 +97,33 @@ char const * xmlInput = targetRegions="{ region }"> - - - + surfaceTemperature="300.15"> + + + + @@ -329,7 +334,7 @@ void testNumericalJacobian( CompositionalMultiphaseReservoirAndWells< Compositio real64 const relTol, bool diag_check, LAMBDA && assembleFunction ) { - CompositionalMultiphaseWell & wellSolver = *solver.wellSolver(); + WellManager & wellSolver = *solver.wellSolver(); CompositionalMultiphaseFVM & flowSolver = dynamicCast< CompositionalMultiphaseFVM & >( *solver.reservoirSolver() ); localIndex const NC = flowSolver.numFluidComponents(); @@ -539,7 +544,7 @@ void testNumericalJacobian( CompositionalMultiphaseReservoirAndWells< Compositio wellElemCompDens.move( hostMemorySpace, false ); arrayView1d< real64 > const & connRate = - subRegion.getField< fields::well::mixtureConnectionRate >(); + subRegion.getField< fields::well::connectionRate >(); connRate.move( hostMemorySpace, false ); // a) compute all the derivatives wrt to the pressure in WELL elem iwelem @@ -688,6 +693,23 @@ class CompositionalMultiphaseReservoirSolverTest : public ::testing::Test solver->getSystemSolution() ); solver->implicitStepSetup( time, dt, domain ); + WellManager & wellSolver = *solver->wellSolver(); + wellSolver.forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const & meshBodyName, + MeshLevel & meshLevel, + string_array const & regionNames ) + { + GEOS_UNUSED_VAR( meshBodyName ); + ElementRegionManager & elementRegionManager = meshLevel.getElemManager(); + elementRegionManager.forElementRegions< WellElementRegion >( regionNames, + [&]( localIndex const, + WellElementRegion & region ) + { + WellElementSubRegion & subRegion = region.getGroup( ElementRegionBase::viewKeyStruct::elementSubRegions() ) + .getGroup< WellElementSubRegion >( region.getSubRegionName() ); + WellControls & wellControls = wellSolver.getWellControls( subRegion ); + wellControls.initializeWell( domain, domain.getMeshBodies(), meshBodyName, meshLevel, subRegion, time ); + } ); + } ); } static real64 constexpr time = 0.0; @@ -773,7 +795,23 @@ TEST_F( CompositionalMultiphaseReservoirSolverTest, jacobianNumericalCheck_Accum [&] ( CRSMatrixView< real64, globalIndex const > const & localMatrix, arrayView1d< real64 > const & localRhs ) { - solver->wellSolver()->assembleAccumulationTerms( time, dt, domain, solver->getDofManager(), localMatrix, localRhs ); + WellManager & wellSolver = *solver->wellSolver(); + wellSolver.forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const & meshBodyName, + MeshLevel & meshLevel, + string_array const & regionNames ) + { + GEOS_UNUSED_VAR( meshBodyName ); + ElementRegionManager & elementRegionManager = meshLevel.getElemManager(); + elementRegionManager.forElementRegions< WellElementRegion >( regionNames, + [&]( localIndex const, + WellElementRegion & region ) + { + WellElementSubRegion & subRegion = region.getGroup( ElementRegionBase::viewKeyStruct::elementSubRegions() ) + .getGroup< WellElementSubRegion >( region.getSubRegionName() ); + WellControls & wellControls = wellSolver.getWellControls( subRegion ); + wellControls.assembleWellAccumulationTerms( time, dt, subRegion, solver->getDofManager(), localMatrix, localRhs ); + } ); + } ); } ); } TEST_F( CompositionalMultiphaseReservoirSolverTest, jacobianNumericalCheck_PressureRel ) @@ -787,7 +825,23 @@ TEST_F( CompositionalMultiphaseReservoirSolverTest, jacobianNumericalCheck_Press [&] ( CRSMatrixView< real64, globalIndex const > const & localMatrix, arrayView1d< real64 > const & localRhs ) { - solver->wellSolver()->assemblePressureRelations( time, dt, domain, solver->getDofManager(), localMatrix, localRhs ); + WellManager & wellSolver = *solver->wellSolver(); + wellSolver.forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const & meshBodyName, + MeshLevel & meshLevel, + string_array const & regionNames ) + { + GEOS_UNUSED_VAR( meshBodyName ); + ElementRegionManager & elementRegionManager = meshLevel.getElemManager(); + elementRegionManager.forElementRegions< WellElementRegion >( regionNames, + [&]( localIndex const, + WellElementRegion & region ) + { + WellElementSubRegion & subRegion = region.getGroup( ElementRegionBase::viewKeyStruct::elementSubRegions() ) + .getGroup< WellElementSubRegion >( region.getSubRegionName() ); + WellControls & wellControls = wellSolver.getWellControls( subRegion ); + wellControls.assembleWellPressureRelations( time, dt, subRegion, solver->getDofManager(), localMatrix, localRhs ); + } ); + } ); } ); } #endif diff --git a/src/coreComponents/linearAlgebra/docs/LinearSolvers.rst b/src/coreComponents/linearAlgebra/docs/LinearSolvers.rst index e7390348681..9e690b8abf3 100644 --- a/src/coreComponents/linearAlgebra/docs/LinearSolvers.rst +++ b/src/coreComponents/linearAlgebra/docs/LinearSolvers.rst @@ -305,14 +305,14 @@ This section provides a brief description of the available preconditioners. * **ILUK**: incomplete LU factorization with fill level k of the original matrix: :math:`\mathsf{M}^{-1} = \mathsf{U}^{-1} \mathsf{L}^{-1}`. Further details can be found in: - - `HYPRE documentation `__, + - `HYPRE documentation `__, - `PETSc documentation `__, - `Trilinos documentation `__. * **ILUT**: a dual threshold incomplete LU factorization: :math:`\mathsf{M}^{-1} = \mathsf{U}^{-1} \mathsf{L}^{-1}`. Further details can be found in: - - `HYPRE documentation `__, + - `HYPRE documentation `__, - not yet available through PETSc interface, - `Trilinos documentation `__. diff --git a/src/coreComponents/linearAlgebra/utilities/ComponentMask.hpp b/src/coreComponents/linearAlgebra/utilities/ComponentMask.hpp index a27d1a37a64..42eab7c568e 100644 --- a/src/coreComponents/linearAlgebra/utilities/ComponentMask.hpp +++ b/src/coreComponents/linearAlgebra/utilities/ComponentMask.hpp @@ -92,10 +92,10 @@ class ComponentMask private: /// Number of bits in mask storage - static constexpr int NUM_BITS = internal::roundToNextPowerOfTwo( MAX_COMP ); + static constexpr int NUM_BITS = geos::internal::roundToNextPowerOfTwo( MAX_COMP ); /// Type used to represent the bit mask - using mask_t = typename internal::ComponentMaskType< NUM_BITS >::type; + using mask_t = typename geos::internal::ComponentMaskType< NUM_BITS >::type; public: diff --git a/src/coreComponents/mesh/MeshBody.hpp b/src/coreComponents/mesh/MeshBody.hpp index c0f30715c0c..a8f8cbe70af 100644 --- a/src/coreComponents/mesh/MeshBody.hpp +++ b/src/coreComponents/mesh/MeshBody.hpp @@ -96,8 +96,7 @@ class MeshBody : public dataRepository::Group * @param[in] level The lookup key of the MeshLevel * @return const reference to the MeshLevel */ - template< typename T, std::enable_if_t< std::is_same< T, string >::value || - std::is_same< T, const char * >::value, bool > = false > + template< typename T > MeshLevel & getMeshLevel( T const & level ) const { return m_meshLevels.getGroup< MeshLevel >( level ); } @@ -108,8 +107,7 @@ class MeshBody : public dataRepository::Group * @param[in] level The lookup key of the MeshLevel * @return Reference to the MeshLevel */ - template< typename T, std::enable_if_t< std::is_same< T, string >::value || - std::is_same< T, const char * >::value, bool > = false > + template< typename T > MeshLevel & getMeshLevel( T const & level ) { return m_meshLevels.getGroup< MeshLevel >( level ); } diff --git a/src/coreComponents/mesh/generators/VTKUtilities.cpp b/src/coreComponents/mesh/generators/VTKUtilities.cpp index 2bf0d4f02e5..1dca5f69d5f 100644 --- a/src/coreComponents/mesh/generators/VTKUtilities.cpp +++ b/src/coreComponents/mesh/generators/VTKUtilities.cpp @@ -199,6 +199,10 @@ generateGlobalIDs( vtkSmartPointer< vtkDataSet > mesh ) { GEOS_MARK_FUNCTION; + // vtkGenerateGlobalIds may trigger floating-point exceptions internally + // Temporarily disable FPE trapping while invoking VTK. + LvArray::system::FloatingPointExceptionGuard guard; + vtkNew< vtkGenerateGlobalIds > generator; generator->SetInputDataObject( mesh ); generator->Update(); diff --git a/src/coreComponents/mesh/mpiCommunications/docs/SpatialPartition.rst b/src/coreComponents/mesh/mpiCommunications/docs/SpatialPartition.rst index ca141be0514..0c8a7567f81 100644 --- a/src/coreComponents/mesh/mpiCommunications/docs/SpatialPartition.rst +++ b/src/coreComponents/mesh/mpiCommunications/docs/SpatialPartition.rst @@ -81,6 +81,6 @@ Therefore, it is a good practice or habit to hide ghost objects using ghostRank If the visualization method involves interpolation, such as interpolating a zonal field into a nodal field or generating contours, the interpretation near partition boundaries is not accurate. -.. _METIS: http://glaros.dtc.umn.edu/gkhome/metis/metis/overview -.. _PARMETIS: http://glaros.dtc.umn.edu/gkhome/metis/parmetis/overview +.. _METIS: https://github.com/KarypisLab/METIS +.. _PARMETIS: https://github.com/KarypisLab/ParMETIS .. _PTSCOTCH: https://www.labri.fr/perso/pelegrin/scotch/ \ No newline at end of file diff --git a/src/coreComponents/physicsSolvers/CMakeLists.txt b/src/coreComponents/physicsSolvers/CMakeLists.txt index ee5c8ff2a0b..62748ae8208 100644 --- a/src/coreComponents/physicsSolvers/CMakeLists.txt +++ b/src/coreComponents/physicsSolvers/CMakeLists.txt @@ -29,7 +29,9 @@ set( physicsSolversBase_headers NonlinearSolverParameters.hpp PhysicsSolverBase.hpp PhysicsSolverBaseKernels.hpp - SolverStatistics.hpp ) + SolverStatistics.hpp + StatisticsAggregatorBase.hpp + StatisticsAggregatorBaseHelpers.hpp ) # # Specify solver sources # diff --git a/src/coreComponents/physicsSolvers/FieldStatisticsBase.hpp b/src/coreComponents/physicsSolvers/FieldStatisticsBase.hpp index 13390fe314c..1548ed4b6e6 100644 --- a/src/coreComponents/physicsSolvers/FieldStatisticsBase.hpp +++ b/src/coreComponents/physicsSolvers/FieldStatisticsBase.hpp @@ -50,8 +50,7 @@ class FieldStatisticsBase : public TaskBase m_outputDir( joinPath( OutputBase::getOutputDirectory(), name ) ) { - string const key = SOLVER::coupledSolverAttributePrefix() + "SolverName"; - registerWrapper( key, &m_solverName ). + registerWrapper( getSolverWrapperKey(), &m_solverName ). setRTTypeName( rtTypes::CustomTypes::groupNameRef ). setInputFlag( dataRepository::InputFlags::REQUIRED ). setDescription( "Name of the " + SOLVER::coupledSolverAttributePrefix() + " solver" ); @@ -80,6 +79,20 @@ class FieldStatisticsBase : public TaskBase protected: + struct viewKeyStruct + { + static constexpr char const * writeCSVFlagString() { return "writeCSV"; } + }; + + /// Pointer to the physics solver + SOLVER * m_solver; + + // Output directory + string const m_outputDir; + + // Flag to enable writing CSV output + integer m_writeCSV; + void postInputInitialization() override { Group & problemManager = this->getGroupByPath( "/Problem" ); @@ -103,19 +116,8 @@ class FieldStatisticsBase : public TaskBase } } - struct viewKeyStruct - { - static constexpr char const * writeCSVFlagString() { return "writeCSV"; } - }; - - /// Pointer to the physics solver - SOLVER * m_solver; - - // Output directory - string const m_outputDir; - - // Flag to enable writing CSV output - integer m_writeCSV; + string getSolverWrapperKey() const + { return SOLVER::coupledSolverAttributePrefix() + "SolverName"; } private: diff --git a/src/coreComponents/physicsSolvers/SolverStatistics.hpp b/src/coreComponents/physicsSolvers/SolverStatistics.hpp index 334f1672c98..0cef5c39d20 100644 --- a/src/coreComponents/physicsSolvers/SolverStatistics.hpp +++ b/src/coreComponents/physicsSolvers/SolverStatistics.hpp @@ -184,6 +184,11 @@ class IterationsStatistics : public dataRepository::Group void closeFile() { if( m_CSVOutputOpened ) { m_logStream.close(); m_CSVOutputOpened = false; } } + /** + * @brief Get the number of time steps + */ + integer const & getNumTimeSteps() const { return m_numTimeSteps; } + protected: /// Number of time steps diff --git a/src/coreComponents/physicsSolvers/StatisticsAggregatorBase.hpp b/src/coreComponents/physicsSolvers/StatisticsAggregatorBase.hpp new file mode 100644 index 00000000000..6f389885dc6 --- /dev/null +++ b/src/coreComponents/physicsSolvers/StatisticsAggregatorBase.hpp @@ -0,0 +1,283 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2016-2024 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2024 TotalEnergies + * Copyright (c) 2018-2024 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2023-2024 Chevron + * Copyright (c) 2019- GEOS/GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +/** + * @file StatisticsAggregatorBase.hpp + */ + +#ifndef SRC_CORECOMPONENTS_PHYSICSSOLVERS_STATISTICSAGGREGATOR_HPP_ +#define SRC_CORECOMPONENTS_PHYSICSSOLVERS_STATISTICSAGGREGATOR_HPP_ + +#include "common/DataTypes.hpp" +#include "dataRepository/DataContext.hpp" +#include "dataRepository/Group.hpp" +#include "mesh/CellElementRegion.hpp" +#include "mesh/MeshBody.hpp" +#include "mesh/MeshLevel.hpp" + +namespace geos +{ + +/** + * @brief Output data group to contain the result of a given stat aggregator on the dataRepository. + * Attributes are public since the class is a POD. + * @todo repair 1D HDF5 outputs to enable stats HDF5 outputs + */ +class RegionStatisticsBase : public dataRepository::Group +{ +public: + + /// Time of statistics computation (std::numeric_limits< double >::lowest() if not initialized) + real64 m_time; + + /** + * @brief Construct a new Region Statistics Base object + * @param targetName name of the data-repository object that is targeted by the statistics + * (mesh level / region / sub-region). + * @param parent the instance parent in data-repository + * @param statsOutputEnabled If true, the stats are saved in the output HDF5 + * (through dataRepository::RestartFlags, but not functional for this output for now). + */ + RegionStatisticsBase( string const & targetName, + dataRepository::Group * const parent, + bool statsOutputEnabled ); + + /** + * @return the name of the data-repository object that is targeted by the statistics + * (mesh level / region / sub-region). + */ + string_view getTargetName() const + { return getName(); } + +}; + +template< typename T > +struct StatsAggregatorTraits; + +/** + * @brief Reponsible of computing physical statistics over the grid, registering the result in the + * data repository, but not storing / outputing it by itself. It does not have mutable state + * except the encountered issues. + * @todo repair 1D HDF5 outputs to enable stats HDF5 outputs + * @tparam Impl the derived type of the statistics aggregator which contains all necessary implementations (CRTP) + */ +template< typename Impl > +class StatsAggregatorBase +{ +public: + + using SolverType = typename StatsAggregatorTraits< Impl >::SolverType; + + using StatsGroupType = typename StatsAggregatorTraits< Impl >::StatsGroupType; + + /** + * @brief Standard function signature for any functor that applies on statistics group instances (StatsGroupType) + * - param 0: OwnerType &, the group instance containing the data for which we want to aggregate the statistics (MeshLevel, + * CellElementRegion...) + * - param 1: StatsAggregateGroupType &, the statistics aggregate Group where to store the data + * @tparam OwnerType the concrete type of the OwnerType param + */ + template< typename OwnerType > + using RegionStatsFunc = std::function< void ( OwnerType &, + StatsGroupType & ) >; + + /** + * @brief A functor that can be used to register a statistics Group instance. Parameters: + * - dataRepository::Group &, the parent Group, + * - string const &, name of the statistics target (i.e. region name). + */ + using RegionStatsRegisterFunc = std::function< StatsGroupType & ( dataRepository::Group &, + string const & ) >; + + /** + * @brief the associated view keys + */ + struct ViewKeys + { + /// String for the discretization statistics group + constexpr static char const * statisticsString() { return "statistics"; } + /// String for the region statistics group + constexpr static char const * regionsStatisticsString() { return "regionsStatistics"; } + }; + + /** + * @brief Construct a new Stats Aggregator object + * @param ownerName the unique name of the entity requesting the statistics. + * An error is thrown if not unique in this context. + * @param meshBodies The Group containing the MeshBody objects + * @param statsOutputEnabled If true, the stats are saved in the output HDF5 + * (through dataRepository::RestartFlags, but not functional for this output for now). + */ + StatsAggregatorBase( dataRepository::DataContext const & ownerDataContext, + dataRepository::Group & meshBodies, + bool statsOutputEnabled ); + + /** + * @brief Enable the computation of any statistics, initialize data structure to collect them. + * Register the resulting data wrappers so they will be targeted by TimeHistory output + * @param solver flow solver object to retrieve: + - the simulated regions, + - fields for statistics computation. + */ + void initStatisticsAggregation( SolverType & solver ); + + void forRegionStatistics( RegionStatsFunc< MeshLevel > const & functor ) const; + + void forRegionStatistics( MeshLevel & mesh, + StatsGroupType & meshRegionsStatistics, + RegionStatsFunc< CellElementRegion > const & functor ) const; + + void forRegionStatistics( CellElementRegion & region, + StatsGroupType & regionStatistics, + RegionStatsFunc< CellElementSubRegion > const & functor ) const; + + /** + * @param[in] timeRequest The time for which we want to know if the statistics are computed. + * @param[in] stats the statistics data structure we want to know if it has been computed + * @return true if the statistics have been computed. + */ + bool isComputed( real64 const timeRequest, StatsGroupType const & stats ); + + /** + * @brief set the statistics as dirty, ensuring isComputed() will be false until the next computation. + */ + void setDirty(); + + /** + * @brief Compute statistics on the mesh discretizations (average field pressure, etc) + * Results are reduced on rank 0, and broadcasted over all ranks. + * @param[in] timeRequest The time for which we want to compute the statistics. + * @return false if there was a problem that prevented the statistics to be computed correctly. + */ + bool computeRegionsStatistics( real64 const timeRequest ); + + /** + * @return the name of the entity that needs the statistics. + */ + string const & getOwnerName() const + { return m_ownerDataContext.getTargetName(); } + + /** + * @return The encountered issues during the last computing method call. + */ + stdVector< string > const & getWarnings() const + { return m_warnings; } + + dataRepository::Group & getInstanceStatisticsGroup( MeshLevel & mesh ) const; + + StatsGroupType & getRegionsStatistics( MeshLevel & mesh ) const; + + /** + * @return a specific statistics Group instance. + * @param mesh The desired mesh-level + * @param regionName The name of the desired region + * @throw InputError if no statistics data is found for the given region name. + */ + StatsGroupType & getRegionStatistics( MeshLevel & mesh, string_view regionName ) const; + +protected: + + struct StatsState + { + bool m_isEnabled = false; + bool m_isDirty = false; + }; + + struct DiscretizationGroupPath + { + localIndex m_meshBody; + localIndex m_meshLevel; + string_array m_regionNames; + }; + + /// @see getOwnerName() + dataRepository::DataContext const & m_ownerDataContext; + + /// If true, the stats are to save in the output HDF5 + bool const m_statsOutputEnabled; + + dataRepository::Group & m_meshBodies; + + stdVector< DiscretizationGroupPath > m_discretizationsPaths; + + /// @see getWarnings() + stdVector< string > m_warnings; + + /// The current state of the region statistics + StatsState m_regionStatsState; + + /** + * @brief Enable the computation of region statistics, initialize data structure to collect them. + * Register the resulting data wrappers so they will be targeted by TimeHistory output + * @note Must be called in or after the "registerDataOnMesh" initialization phase + * @param registerStatsFunc The functor which register each statistics group whithin the regions hierarchy + */ + void enableRegionStatisticsAggregation( RegionStatsRegisterFunc && registerStatsFunc ); + + /** + * @param path the path of the discretization group in the data-repository. + * @return MeshLevel& the MeshLevel Group for the given discretisation + */ + MeshLevel & getMeshLevel( DiscretizationGroupPath const & path ) const; + + /** + * @brief Initialize all statistics values to aggregable default values, + * before any computation / reduction for the current timestep. + * @param stats the statistics instance + * @param time start time of the current timestep (s) + * @note Must be implemented for each type that implements this template (CRTP). + */ + void initStats( StatsGroupType & stats, real64 time ) const + { static_cast< Impl const * >(this)->initStats( stats, time ); } + + /** + * @brief Compute the rank-local stats for the given sub-region and store the results in the given stats group. + * @param subRegion + * @param subRegionStats the stats group instance for the subregion + * @note Must be implemented for each type that implements this template (CRTP). + */ + void computeSubRegionRankStats( CellElementSubRegion & subRegion, StatsGroupType & subRegionStats ) const + { static_cast< Impl const * >(this)->computeSubRegionRankStats( subRegion, subRegionStats ); } + + /** + * @brief Aggregate all instance statistics with those of another instance on the current rank. + * @param stats the statistics instance + * @param other the other instance to aggregate with. + * @note Must be implemented for each type that implements this template (CRTP). + */ + void aggregateStats( StatsGroupType & stats, StatsGroupType const & other ) const + { static_cast< Impl const * >(this)->aggregateStats( stats, other ); } + + /** + * @brief Aggregate all instance statistics with those of other ranks. + * @param stats the statistics instance + * @note Must be implemented for each type that implements this template (CRTP). + */ + void mpiAggregateStats( StatsGroupType & stats ) const + { static_cast< Impl const * >(this)->mpiAggregateStats( stats ); } + + /** + * @brief Do the final computations for the statistics. Must be called after computations & aggregations. + * @param stats the statistics instance + * @note Must be implemented for each type that implements this template (CRTP). + */ + void postAggregateStats( StatsGroupType & stats ) + { static_cast< Impl * >(this)->postAggregateStats( stats ); } + +}; + +} /* namespace geos */ + +#endif /* SRC_CORECOMPONENTS_PHYSICSSOLVERS_STATISTICSAGGREGATOR_HPP_ */ diff --git a/src/coreComponents/physicsSolvers/StatisticsAggregatorBaseHelpers.hpp b/src/coreComponents/physicsSolvers/StatisticsAggregatorBaseHelpers.hpp new file mode 100644 index 00000000000..a609b4033ed --- /dev/null +++ b/src/coreComponents/physicsSolvers/StatisticsAggregatorBaseHelpers.hpp @@ -0,0 +1,296 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2016-2024 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2024 TotalEnergies + * Copyright (c) 2018-2024 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2023-2024 Chevron + * Copyright (c) 2019- GEOS/GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +#include "mesh/MeshBody.hpp" +#include "physicsSolvers/StatisticsAggregatorBase.hpp" + +#include "LvArray/src/math.hpp" +#include "common/DataTypes.hpp" +#include "common/format/Format.hpp" +#include "common/format/StringUtilities.hpp" +#include "common/logger/Logger.hpp" +#include "dataRepository/DataContext.hpp" +#include "dataRepository/Group.hpp" +#include "mesh/CellElementRegion.hpp" +#include "mesh/CellElementSubRegion.hpp" +#include "mesh/ElementRegionManager.hpp" +#include "mesh/MeshLevel.hpp" + +namespace geos +{ + +inline RegionStatisticsBase::RegionStatisticsBase( string const & targetName, + dataRepository::Group * const parent, + bool const statsOutputEnabled ): + dataRepository::Group( targetName, parent ), + m_time( std::numeric_limits< double >::lowest() ) +{ + Group::setRestartFlags( statsOutputEnabled ? + dataRepository::RestartFlags::WRITE_AND_READ : + dataRepository::RestartFlags::NO_WRITE ); + + // TODO : registerWrappers to store results in HDF5 (but need repairing of 1D HDF5 outputs) +} + +template< typename Impl > +StatsAggregatorBase< Impl >::StatsAggregatorBase( dataRepository::DataContext const & ownerDataContext, + dataRepository::Group & meshBodies, + bool const statsOutputEnabled ): + m_ownerDataContext( ownerDataContext ), + m_statsOutputEnabled( statsOutputEnabled ), + m_meshBodies( meshBodies ) +{} + +template< typename Impl > +void +StatsAggregatorBase< Impl >::initStatisticsAggregation( SolverType & solver ) +{ + solver.forDiscretizationOnMeshTargets( m_meshBodies, [&] ( string const & meshBodyName, + MeshLevel & mesh, + string_array const & regionNames ) + { + // getting the container of all requesters statistics groups (can be already initialized) + dataRepository::Group * meshStatsGroup = mesh.getGroupPointer( ViewKeys::statisticsString() ); + if( meshStatsGroup == nullptr ) + meshStatsGroup = &mesh.registerGroup( ViewKeys::statisticsString() ); + + // registering the container of instance statistics groups (must be unique for this instance) + string const & ownerName = getOwnerName(); + GEOS_ERROR_IF_NE_MSG( meshStatsGroup->hasGroup( ownerName ), false, + GEOS_FMT( "A statistics aggregator have already been requested for '{}'.", + ownerName ), + m_ownerDataContext ); + meshStatsGroup->registerGroup( ownerName ); + + // remembering the path of this discretization + MeshBody const & body = m_meshBodies.getGroup< MeshBody >( meshBodyName ); + DiscretizationGroupPath const path { + /* .m_meshBody = */ body.getIndexInParent(), + /* .m_meshLevel = */ mesh.getIndexInParent(), + /* .m_regionNames = */ regionNames, + }; + m_discretizationsPaths.push_back( path ); + } ); +} + +template< typename Impl > +void +StatsAggregatorBase< Impl >::enableRegionStatisticsAggregation( RegionStatsRegisterFunc && registerStatsFunc ) +{ + integer regionCount = 0; + integer subRegionCount = 0; + + for( auto const & path : m_discretizationsPaths ) + { + MeshLevel & mesh = getMeshLevel( path ); + ElementRegionManager & elemManager = mesh.getElemManager(); + dataRepository::Group & statisticsGroup = getInstanceStatisticsGroup( mesh ); + StatsGroupType & meshRegionsStats = registerStatsFunc( statisticsGroup, + ViewKeys::regionsStatisticsString() ); + + for( size_t i = 0; i < path.m_regionNames.size(); ++i ) + { + CellElementRegion & region = elemManager.getRegion< CellElementRegion >( path.m_regionNames[i] ); + StatsGroupType & regionStats = registerStatsFunc( meshRegionsStats, + region.getName() ); + + region.forElementSubRegions< CellElementSubRegion >( [&] ( CellElementSubRegion & subRegion ) + { + registerStatsFunc( regionStats, + subRegion.getName() ); + ++subRegionCount; + } ); + ++regionCount; + } + } + + GEOS_ERROR_IF( regionCount == 0 || subRegionCount == 0, + GEOS_FMT( "Missing region for computing statistics:\n- {} regions,\n- {} sub-regions.", + getOwnerName(), regionCount, subRegionCount ), + m_ownerDataContext ); + + m_regionStatsState.m_isEnabled = true; + m_regionStatsState.m_isDirty = true; +} + +template< typename Impl > +void +StatsAggregatorBase< Impl >::forRegionStatistics( RegionStatsFunc< MeshLevel > const & func ) const +{ + for( auto const & path : m_discretizationsPaths ) + { + MeshLevel & mesh = getMeshLevel( path ); + StatsGroupType & meshRegionsStats = getRegionsStatistics( mesh ); + + func( mesh, meshRegionsStats ); + } +} + +template< typename Impl > +void +StatsAggregatorBase< Impl >::forRegionStatistics( MeshLevel & mesh, + StatsGroupType & meshRegionsStatistics, + RegionStatsFunc< CellElementRegion > const & func ) const +{ + ElementRegionManager & elemManager = mesh.getElemManager(); + meshRegionsStatistics.template forSubGroups< StatsGroupType >( [&] ( StatsGroupType & regionStatistics ) + { + string_view targetName = regionStatistics.getTargetName(); + CellElementRegion & region = elemManager.getRegion< CellElementRegion >( string( targetName ) ); + + func( region, regionStatistics ); + } ); +} + +template< typename Impl > +void +StatsAggregatorBase< Impl >::forRegionStatistics( CellElementRegion & region, + StatsGroupType & regionStatistics, + RegionStatsFunc< CellElementSubRegion > const & func ) const +{ + regionStatistics.template forSubGroups< StatsGroupType >( [&] ( StatsGroupType & subRegionStatistics ) + { + string_view targetName = subRegionStatistics.getTargetName(); + CellElementSubRegion & subRegion = region.getSubRegion< CellElementSubRegion >( string( targetName ) ); + func( subRegion, subRegionStatistics ); + } ); +} + +template< typename Impl > +bool +StatsAggregatorBase< Impl >::isComputed( real64 const timeRequest, StatsGroupType const & stats ) +{ + real64 const timePrecisionScale = LvArray::math::max( LvArray::math::abs( timeRequest ), + LvArray::math::abs( stats.m_time ) ); + static constexpr real64 timeRelTol = 1.0e-12; + + return + !m_regionStatsState.m_isDirty && + LvArray::math::abs( timeRequest - stats.m_time ) < timeRelTol * timePrecisionScale; +} + +template< typename Impl > +void +StatsAggregatorBase< Impl >::setDirty() +{ + m_regionStatsState.m_isDirty = true; +} + +template< typename Impl > +bool +StatsAggregatorBase< Impl >::computeRegionsStatistics( real64 const timeRequest ) +{ + GEOS_MARK_FUNCTION; + + m_warnings.clear(); + + // computation of sub region stats + forRegionStatistics( [&, timeRequest] ( MeshLevel & mesh, StatsGroupType & meshRegionsStats ) + { + forRegionStatistics( mesh, + meshRegionsStats, + [&, timeRequest] ( CellElementRegion & region, StatsGroupType & regionStats ) + { + forRegionStatistics( region, + regionStats, + [&, timeRequest] ( CellElementSubRegion & subRegion, StatsGroupType & subRegionStats ) + { + initStats( subRegionStats, timeRequest ); + computeSubRegionRankStats( subRegion, subRegionStats ); + } ); + } ); + } ); + + // aggregation of computations from the sub regions + forRegionStatistics( [&, timeRequest] ( MeshLevel & mesh, StatsGroupType & meshRegionsStats ) + { + initStats( meshRegionsStats, timeRequest ); + + forRegionStatistics( mesh, + meshRegionsStats, + [&, timeRequest] ( CellElementRegion & region, StatsGroupType & regionStats ) + { + initStats( regionStats, timeRequest ); + + forRegionStatistics( region, + regionStats, + [&] ( CellElementSubRegion &, StatsGroupType & subRegionStats ) + { + aggregateStats( regionStats, subRegionStats ); + + mpiAggregateStats( subRegionStats ); + postAggregateStats( subRegionStats ); + } ); + + aggregateStats( meshRegionsStats, regionStats ); + + mpiAggregateStats( regionStats ); + postAggregateStats( regionStats ); + } ); + + mpiAggregateStats( meshRegionsStats ); + postAggregateStats( meshRegionsStats ); + } ); + + m_regionStatsState.m_isDirty = false; + + return true; +} + +template< typename Impl > +MeshLevel & +StatsAggregatorBase< Impl >::getMeshLevel( DiscretizationGroupPath const & path ) const +{ + MeshBody & body = m_meshBodies.getGroup< MeshBody >( path.m_meshBody ); + MeshLevel & mesh = body.getMeshLevel( path.m_meshLevel ); + return mesh; +} + +template< typename Impl > +dataRepository::Group & +StatsAggregatorBase< Impl >::getInstanceStatisticsGroup( MeshLevel & mesh ) const +{ + // considering everything is initialized, or else, crash gracefully + dataRepository::Group & meshStatsGroup = mesh.getGroup( ViewKeys::statisticsString() ); + dataRepository::Group & instanceStatisticsGroup = meshStatsGroup.getGroup( getOwnerName() ); + return instanceStatisticsGroup; +} + +template< typename Impl > +typename StatsAggregatorBase< Impl >::StatsGroupType & +StatsAggregatorBase< Impl >:: +getRegionsStatistics( MeshLevel & mesh ) const +{ + // considering everything is initialized, or else, crash gracefully + dataRepository::Group & instanceStatisticsGroup = getInstanceStatisticsGroup( mesh ); + return instanceStatisticsGroup.getGroup< StatsGroupType >( ViewKeys::regionsStatisticsString() ); +} + +template< typename Impl > +typename StatsAggregatorBase< Impl >::StatsGroupType & +StatsAggregatorBase< Impl >::getRegionStatistics( MeshLevel & mesh, + string_view regionName ) const +{ + StatsGroupType & meshRegionsStats = getRegionsStatistics( mesh ); + StatsGroupType * const stats = meshRegionsStats.template getGroupPointer< StatsGroupType >( string( regionName ) ); + GEOS_THROW_IF( stats == nullptr, + GEOS_FMT( "Region '{}' not found to get region statistics, is it a target of the reservoir solver?\n" + "Available target regions:\n- {}", + regionName, stringutilities::join( meshRegionsStats.getSubGroupsNames(), "\n- " ) ), + InputError, m_ownerDataContext ); + return *stats; +} + +} /* namespace geos */ diff --git a/src/coreComponents/physicsSolvers/fluidFlow/CMakeLists.txt b/src/coreComponents/physicsSolvers/fluidFlow/CMakeLists.txt index f7f31869468..6a134ee798e 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/CMakeLists.txt +++ b/src/coreComponents/physicsSolvers/fluidFlow/CMakeLists.txt @@ -22,7 +22,8 @@ set( fluidFlowSolvers_headers FlowSolverBaseFields.hpp CompositionalMultiphaseBase.hpp CompositionalMultiphaseBaseFields.hpp - CompositionalMultiphaseStatistics.hpp + CompositionalMultiphaseStatisticsAggregator.hpp + CompositionalMultiphaseStatisticsTask.hpp CompositionalMultiphaseFVM.hpp CompositionalMultiphaseHybridFVM.hpp CompositionalMultiphaseUtilities.hpp @@ -34,7 +35,8 @@ set( fluidFlowSolvers_headers SourceFluxStatistics.hpp SinglePhaseBase.hpp SinglePhaseBaseFields.hpp - SinglePhaseStatistics.hpp + SinglePhaseStatisticsAggregator.hpp + SinglePhaseStatisticsTask.hpp SinglePhaseFVM.hpp SinglePhaseHybridFVM.hpp SinglePhaseProppantBase.hpp @@ -134,11 +136,20 @@ set( fluidFlowSolvers_headers wells/SinglePhaseWellFields.hpp wells/WellConstants.hpp wells/WellControls.hpp - wells/WellSolverBase.hpp - wells/WellSolverBaseFields.hpp + wells/WellConstraintsBase.hpp + wells/WellInjectionConstraint.hpp + wells/WellProductionConstraint.hpp + wells/WellBHPConstraints.hpp + wells/WellVolumeRateConstraint.hpp + wells/WellMassRateConstraint.hpp + wells/WellPhaseVolumeRateConstraint.hpp + wells/WellManager.hpp + wells/WellNewtonSolver.hpp wells/LogLevelsInfo.hpp wells/kernels/SinglePhaseWellKernels.hpp wells/kernels/CompositionalMultiphaseWellKernels.hpp + wells/kernels/CompositionalMultiphaseWellConstraintKernels.hpp + wells/kernels/SinglePhaseWellConstraintKernels.hpp proppantTransport/ProppantTransport.hpp proppantTransport/ProppantTransportFields.hpp proppantTransport/ProppantTransportKernels.hpp ) @@ -147,13 +158,15 @@ set( fluidFlowSolvers_headers set( fluidFlowSolvers_sources CompositionalMultiphaseBase.cpp CompositionalMultiphaseFVM.cpp - CompositionalMultiphaseStatistics.cpp + CompositionalMultiphaseStatisticsAggregator.cpp + CompositionalMultiphaseStatisticsTask.cpp CompositionalMultiphaseHybridFVM.cpp ImmiscibleMultiphaseFlow.cpp ReactiveCompositionalMultiphaseOBL.cpp FlowSolverBase.cpp SinglePhaseBase.cpp - SinglePhaseStatistics.cpp + SinglePhaseStatisticsAggregator.cpp + SinglePhaseStatisticsTask.cpp SinglePhaseFVM.cpp SinglePhaseHybridFVM.cpp SinglePhaseProppantBase.cpp @@ -172,7 +185,15 @@ set( fluidFlowSolvers_sources wells/SinglePhaseWell.cpp wells/kernels/SinglePhaseWellKernels.cpp wells/WellControls.cpp - wells/WellSolverBase.cpp + wells/WellConstraintsBase.cpp + wells/WellInjectionConstraint.cpp + wells/WellProductionConstraint.cpp + wells/WellBHPConstraints.cpp + wells/WellVolumeRateConstraint.cpp + wells/WellMassRateConstraint.cpp + wells/WellPhaseVolumeRateConstraint.cpp + wells/WellManager.cpp + wells/WellNewtonSolver.cpp proppantTransport/ProppantTransport.cpp proppantTransport/ProppantTransportKernels.cpp ) diff --git a/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseBase.cpp b/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseBase.cpp index 93c17c765ec..03ec8ffe48a 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseBase.cpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseBase.cpp @@ -2790,6 +2790,14 @@ void CompositionalMultiphaseBase::resetStateToBeginningOfStep( DomainPartition & // update porosity, permeability updatePorosityAndPermeability( subRegion ); + // discard any warm-started state held by the fluid model (e.g. K-values + // used to seed the flash) so the rolled-back step does not inherit + // iterate-dependent state from the failed Newton attempt. + { + string const & fluidName = subRegion.template getReference< string >( viewKeyStruct::fluidNamesString() ); + MultiFluidBase const & fluid = getConstitutiveModel< MultiFluidBase >( subRegion, fluidName ); + fluid.initializeState(); + } // update all fluid properties updateFluidState( subRegion ); // for thermal simulations, update solid internal energy diff --git a/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseStatistics.cpp b/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseStatistics.cpp deleted file mode 100644 index c83695e5a98..00000000000 --- a/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseStatistics.cpp +++ /dev/null @@ -1,555 +0,0 @@ -/* - * ------------------------------------------------------------------------------------------------------------ - * SPDX-License-Identifier: LGPL-2.1-only - * - * Copyright (c) 2016-2024 Lawrence Livermore National Security LLC - * Copyright (c) 2018-2024 TotalEnergies - * Copyright (c) 2018-2024 The Board of Trustees of the Leland Stanford Junior University - * Copyright (c) 2023-2024 Chevron - * Copyright (c) 2019- GEOS/GEOSX Contributors - * All rights reserved - * - * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. - * ------------------------------------------------------------------------------------------------------------ - */ - -/** - * @file CompositionalMultiphaseStatistics.cpp - */ - -#include "CompositionalMultiphaseStatistics.hpp" - -#include "mesh/DomainPartition.hpp" -#include "constitutive/fluid/multifluid/MultiFluidBase.hpp" -#include "constitutive/relativePermeability/RelativePermeabilityBase.hpp" -#include "constitutive/solid/CoupledSolidBase.hpp" -#include "physicsSolvers/LogLevelsInfo.hpp" -#include "physicsSolvers/fluidFlow/LogLevelsInfo.hpp" -#include "physicsSolvers/fluidFlow/CompositionalMultiphaseBase.hpp" -#include "physicsSolvers/fluidFlow/CompositionalMultiphaseBaseFields.hpp" -#include "physicsSolvers/fluidFlow/CompositionalMultiphaseHybridFVM.hpp" -#include "physicsSolvers/fluidFlow/FlowSolverBaseFields.hpp" -#include "physicsSolvers/fluidFlow/kernels/compositional/StatisticsKernel.hpp" -#include "common/format/table/TableData.hpp" -#include "common/format/table/TableFormatter.hpp" -#include "common/format/table/TableLayout.hpp" - - -namespace geos -{ - -using namespace constitutive; -using namespace fields; -using namespace dataRepository; - -CompositionalMultiphaseStatistics::CompositionalMultiphaseStatistics( const string & name, - Group * const parent ): - Base( name, parent ), - m_computeCFLNumbers( 0 ), - m_computeRegionStatistics( 1 ) -{ - registerWrapper( viewKeyStruct::computeCFLNumbersString(), &m_computeCFLNumbers ). - setApplyDefaultValue( 0 ). - setInputFlag( InputFlags::OPTIONAL ). - setDescription( "Flag to decide whether CFL numbers are computed or not" ); - - registerWrapper( viewKeyStruct::computeRegionStatisticsString(), &m_computeRegionStatistics ). - setApplyDefaultValue( 1 ). - setInputFlag( InputFlags::OPTIONAL ). - setDescription( "Flag to decide whether region statistics are computed or not" ); - - registerWrapper( viewKeyStruct::relpermThresholdString(), &m_relpermThreshold ). - setApplyDefaultValue( 1e-6 ). - setInputFlag( InputFlags::OPTIONAL ). - setDescription( "Flag to decide whether a phase is considered mobile (when the relperm is above the threshold) or immobile (when the relperm is below the threshold) in metric 2" ); - - addLogLevel< logInfo::CFL >(); - addLogLevel< logInfo::Statistics >(); -} - -void CompositionalMultiphaseStatistics::postInputInitialization() -{ - Base::postInputInitialization(); - - if( dynamicCast< CompositionalMultiphaseHybridFVM * >( m_solver ) && m_computeCFLNumbers != 0 ) - { - GEOS_THROW( "The option to compute CFL numbers is incompatible with CompositionalMultiphaseHybridFVM", - InputError, getDataContext() ); - } -} - -void CompositionalMultiphaseStatistics::registerDataOnMesh( Group & meshBodies ) -{ - // the fields have to be registered in "registerDataOnMesh" (and not later) - // otherwise they cannot be targeted by TimeHistory - - // for now, this guard is needed to avoid breaking the xml schema generation - if( m_solver == nullptr ) - { - return; - } - - m_solver->forDiscretizationOnMeshTargets( meshBodies, [&] ( string const &, - MeshLevel & mesh, - string_array const & regionNames ) - { - ElementRegionManager & elemManager = mesh.getElemManager(); - - integer const numPhases = m_solver->numFluidPhases(); - integer const numComps = m_solver->numFluidComponents(); - - // if we have to report region statistics, we have to register them first here - if( m_computeRegionStatistics ) - { - for( size_t i = 0; i < regionNames.size(); ++i ) - { - ElementRegionBase & region = elemManager.getRegion( regionNames[i] ); - - region.registerWrapper< RegionStatistics >( viewKeyStruct::regionStatisticsString() ). - setRestartFlags( RestartFlags::NO_WRITE ); - region.excludeWrappersFromPacking( { viewKeyStruct::regionStatisticsString() } ); - RegionStatistics & stats = region.getReference< RegionStatistics >( viewKeyStruct::regionStatisticsString() ); - - stats.phasePoreVolume.resizeDimension< 0 >( numPhases ); - stats.phaseMass.resizeDimension< 0 >( numPhases ); - stats.trappedPhaseMass.resizeDimension< 0 >( numPhases ); - stats.immobilePhaseMass.resizeDimension< 0 >( numPhases ); - stats.componentMass.resizeDimension< 0, 1 >( numPhases, numComps ); - - if( m_writeCSV > 0 && MpiWrapper::commRank() == 0 ) - { - auto addStatsValue = []( std::ostringstream & pstatsLayout, TableLayout & ptableLayout, - string const & description, string_view punit, - integer pnumPhases, integer pnumComps = 0 ) - { - for( int ip = 0; ip < pnumPhases; ++ip ) - { - if( pnumComps == 0 ) - { - pstatsLayout << description << " (phase " << ip << ") [" << punit << "]"; - } - else - { - for( int ic = 0; ic < pnumComps; ++ic ) - { - pstatsLayout << description << " (component " << ic << " / phase " << ip << ") [" << punit << "]"; - if( ic == 0 ) - { - pstatsLayout << ","; - } - } - } - if( ip == 0 ) - { - pstatsLayout << ","; - } - } - - ptableLayout.addColumn( pstatsLayout.str()); - pstatsLayout.str( "" ); - }; - - string_view massUnit = units::getSymbol( m_solver->getMassUnit() ); - - TableLayout tableLayout( { - TableLayout::Column().setName( GEOS_FMT( "Time [{}]", units::getSymbol( units::Unit::Time ))), - TableLayout::Column().setName( GEOS_FMT( "Min pressure [{}]", units::getSymbol( units::Unit::Pressure ))), - TableLayout::Column().setName( GEOS_FMT( "Average pressure [{}]", units::getSymbol( units::Unit::Pressure )) ), - TableLayout::Column().setName( GEOS_FMT( "Max pressure [{}]", units::getSymbol( units::Unit::Pressure ) ) ), - TableLayout::Column().setName( GEOS_FMT( "Min delta pressure [{}]", units::getSymbol( units::Unit::Pressure ))), - TableLayout::Column().setName( GEOS_FMT( "Max delta pressure [{}]", units::getSymbol( units::Unit::Pressure ))), - TableLayout::Column().setName( GEOS_FMT( "Min temperature [{}]", units::getSymbol( units::Unit::Temperature ) )), - TableLayout::Column().setName( GEOS_FMT( "Average temperature [{}]", units::getSymbol( units::Unit::Temperature ) )), - TableLayout::Column().setName( GEOS_FMT( "Max temperature [{}]", units::getSymbol( units::Unit::Temperature ) )), - TableLayout::Column().setName( GEOS_FMT( "Total dynamic pore volume [{}]", units::getSymbol( units::Unit::ReservoirVolume ) )), - } ); - - std::ostringstream statsLayout; - addStatsValue( statsLayout, tableLayout, "Phase dynamic pore volume", "rm^3", numPhases ); - addStatsValue( statsLayout, tableLayout, "Phase mass", massUnit, numPhases ); - addStatsValue( statsLayout, tableLayout, "Trapped phase mass (metric 1)", massUnit, numPhases ); - addStatsValue( statsLayout, tableLayout, "Non-trapped phase mass (metric 1)", massUnit, numPhases ); - addStatsValue( statsLayout, tableLayout, "Immobile phase mass (metric 2)", massUnit, numPhases ); - addStatsValue( statsLayout, tableLayout, "Mobile phase mass (metric 2)", massUnit, numPhases ); - addStatsValue( statsLayout, tableLayout, "Component mass", massUnit, numPhases, numComps ); - - std::ofstream outputFile( m_outputDir + "/" + regionNames[i] + ".csv" ); - TableCSVFormatter csvFormatter( tableLayout ); - outputFile << csvFormatter.headerToString(); - } - } - } - } ); - - // if we have to compute CFL numbers later, we need to register additional variables - if( m_computeCFLNumbers ) - { - m_solver->registerDataForCFL( meshBodies ); - } -} - -bool CompositionalMultiphaseStatistics::execute( real64 const time_n, - real64 const dt, - integer const GEOS_UNUSED_PARAM( cycleNumber ), - integer const GEOS_UNUSED_PARAM( eventCounter ), - real64 const GEOS_UNUSED_PARAM( eventProgress ), - DomainPartition & domain ) -{ - m_solver->forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&] ( string const &, - MeshLevel & mesh, - string_array const & regionNames ) - { - if( m_computeRegionStatistics ) - { - // current time is time_n + dt - computeRegionStatistics( time_n + dt, mesh, regionNames ); - } - } ); - - if( m_computeCFLNumbers ) - { - // current time is time_n + dt - computeCFLNumbers( time_n + dt, dt, domain ); - } - - return false; -} - -void CompositionalMultiphaseStatistics::computeRegionStatistics( real64 const time, - MeshLevel & mesh, - string_array const & regionNames ) const -{ - GEOS_MARK_FUNCTION; - - integer const numPhases = m_solver->numFluidPhases(); - integer const numComps = m_solver->numFluidComponents(); - - // Step 1: initialize the average/min/max quantities - ElementRegionManager & elemManager = mesh.getElemManager(); - for( size_t i = 0; i < regionNames.size(); ++i ) - { - ElementRegionBase & region = elemManager.getRegion( regionNames[i] ); - RegionStatistics & stats = region.getReference< RegionStatistics >( viewKeyStruct::regionStatisticsString() ); - - stats.averagePressure = 0.0; - stats.maxPressure = 0.0; - stats.minPressure = LvArray::NumericLimits< real64 >::max; - - stats.maxDeltaPressure = -LvArray::NumericLimits< real64 >::max; - stats.minDeltaPressure = LvArray::NumericLimits< real64 >::max; - - stats.averageTemperature = 0.0; - stats.maxTemperature = 0.0; - stats.minTemperature = LvArray::NumericLimits< real64 >::max; - - stats.totalPoreVolume = 0.0; - stats.totalUncompactedPoreVolume = 0.0; - stats.phasePoreVolume.setValues< serialPolicy >( 0.0 ); - - stats.phaseMass.setValues< serialPolicy >( 0.0 ); - stats.trappedPhaseMass.setValues< serialPolicy >( 0.0 ); - stats.immobilePhaseMass.setValues< serialPolicy >( 0.0 ); - stats.componentMass.setValues< serialPolicy >( 0.0 ); - } - - // Step 2: increment the average/min/max quantities for all the subRegions - elemManager.forElementSubRegions( regionNames, [&]( localIndex const, - ElementSubRegionBase & subRegion ) - { - - arrayView1d< integer const > const elemGhostRank = subRegion.ghostRank(); - arrayView1d< real64 const > const volume = subRegion.getElementVolume(); - arrayView1d< real64 const > const pres = subRegion.getField< flow::pressure >(); - arrayView1d< real64 const > const temp = subRegion.getField< flow::temperature >(); - arrayView2d< real64 const, compflow::USD_PHASE > const phaseVolFrac = - subRegion.getField< flow::phaseVolumeFraction >(); - arrayView1d< real64 const > const deltaPres = subRegion.getField< flow::deltaPressure >(); - - Group const & constitutiveModels = subRegion.getGroup( ElementSubRegionBase::groupKeyStruct::constitutiveModelsString() ); - - string const & solidName = subRegion.getReference< string >( CompositionalMultiphaseBase::viewKeyStruct::solidNamesString() ); - CoupledSolidBase const & solid = constitutiveModels.getGroup< CoupledSolidBase >( solidName ); - arrayView1d< real64 const > const refPorosity = solid.getReferencePorosity(); - arrayView2d< real64 const > const porosity = solid.getPorosity(); - - string const & fluidName = subRegion.getReference< string >( CompositionalMultiphaseBase::viewKeyStruct::fluidNamesString() ); - MultiFluidBase const & fluid = constitutiveModels.getGroup< MultiFluidBase >( fluidName ); - arrayView3d< real64 const, multifluid::USD_PHASE > const phaseDensity = fluid.phaseDensity(); - arrayView4d< real64 const, multifluid::USD_PHASE_COMP > const phaseCompFraction = fluid.phaseCompFraction(); - - - //get min vol fraction for each phase to dispactche immobile/mobile mass - string const & relpermName = subRegion.getReference< string >( CompositionalMultiphaseBase::viewKeyStruct::relPermNamesString() ); - RelativePermeabilityBase const & relperm = constitutiveModels.getGroup< RelativePermeabilityBase >( relpermName ); - arrayView3d< real64 const, relperm::USD_RELPERM > const phaseTrappedVolFrac = relperm.phaseTrappedVolFraction(); - arrayView3d< real64 const, relperm::USD_RELPERM > const phaseRelperm = relperm.phaseRelPerm(); - - real64 subRegionAvgPresNumerator = 0.0; - real64 subRegionMinPres = 0.0; - real64 subRegionMaxPres = 0.0; - real64 subRegionMinDeltaPres = 0.0; - real64 subRegionMaxDeltaPres = 0.0; - real64 subRegionAvgTempNumerator = 0.0; - real64 subRegionMinTemp = 0.0; - real64 subRegionMaxTemp = 0.0; - real64 subRegionTotalUncompactedPoreVol = 0.0; - array1d< real64 > subRegionPhaseDynamicPoreVol( numPhases ); - array1d< real64 > subRegionPhaseMass( numPhases ); - array1d< real64 > subRegionTrappedPhaseMass( numPhases ); - array1d< real64 > subRegionImmobilePhaseMass( numPhases ); - array1d< real64 > subRegionRelpermPhaseMass( numPhases ); - array2d< real64 > subRegionComponentMass( numPhases, numComps ); - - isothermalCompositionalMultiphaseBaseKernels:: - StatisticsKernel:: - launch< parallelDevicePolicy<> >( subRegion.size(), - numComps, - numPhases, - m_relpermThreshold, - elemGhostRank, - volume, - pres, - deltaPres, - temp, - refPorosity, - porosity, - phaseDensity, - phaseCompFraction, - phaseVolFrac, - phaseTrappedVolFrac, - phaseRelperm, - subRegionMinPres, - subRegionAvgPresNumerator, - subRegionMaxPres, - subRegionMinDeltaPres, - subRegionMaxDeltaPres, - subRegionMinTemp, - subRegionAvgTempNumerator, - subRegionMaxTemp, - subRegionTotalUncompactedPoreVol, - subRegionPhaseDynamicPoreVol.toView(), - subRegionPhaseMass.toView(), - subRegionTrappedPhaseMass.toView(), - subRegionImmobilePhaseMass.toView(), - subRegionComponentMass.toView() ); - - ElementRegionBase & region = elemManager.getRegion( ElementRegionBase::getParentRegion( subRegion ).getName() ); - RegionStatistics & stats = region.getReference< RegionStatistics >( viewKeyStruct::regionStatisticsString() ); - - stats.averagePressure += subRegionAvgPresNumerator; - if( subRegionMinPres < stats.minPressure ) - { - stats.minPressure = subRegionMinPres; - } - if( subRegionMaxPres > stats.maxPressure ) - { - stats.maxPressure = subRegionMaxPres; - } - - if( subRegionMinDeltaPres < stats.minDeltaPressure ) - { - stats.minDeltaPressure = subRegionMinDeltaPres; - } - if( subRegionMaxDeltaPres > stats.maxDeltaPressure ) - { - stats.maxDeltaPressure = subRegionMaxDeltaPres; - } - - stats.averageTemperature += subRegionAvgTempNumerator; - if( subRegionMinTemp < stats.minTemperature ) - { - stats.minTemperature = subRegionMinTemp; - } - if( subRegionMaxTemp > stats.maxTemperature ) - { - stats.maxTemperature = subRegionMaxTemp; - } - - stats.totalUncompactedPoreVolume += subRegionTotalUncompactedPoreVol; - for( integer ip = 0; ip < numPhases; ++ip ) - { - stats.phasePoreVolume[ip] += subRegionPhaseDynamicPoreVol[ip]; - stats.phaseMass[ip] += subRegionPhaseMass[ip]; - stats.trappedPhaseMass[ip] += subRegionTrappedPhaseMass[ip]; - stats.immobilePhaseMass[ip] += subRegionImmobilePhaseMass[ip]; - - for( integer ic = 0; ic < numComps; ++ic ) - { - stats.componentMass[ip][ic] += subRegionComponentMass[ip][ic]; - } - } - - } ); - - // Step 3: synchronize the results over the MPI ranks - for( size_t i = 0; i < regionNames.size(); ++i ) - { - ElementRegionBase & region = elemManager.getRegion( regionNames[i] ); - RegionStatistics & stats = region.getReference< RegionStatistics >( viewKeyStruct::regionStatisticsString() ); - - stats.minPressure = MpiWrapper::min( stats.minPressure ); - stats.maxPressure = MpiWrapper::max( stats.maxPressure ); - stats.minDeltaPressure = MpiWrapper::min( stats.minDeltaPressure ); - stats.maxDeltaPressure = MpiWrapper::max( stats.maxDeltaPressure ); - stats.minTemperature = MpiWrapper::min( stats.minTemperature ); - stats.maxTemperature = MpiWrapper::max( stats.maxTemperature ); - stats.totalUncompactedPoreVolume = MpiWrapper::sum( stats.totalUncompactedPoreVolume ); - stats.totalPoreVolume = 0.0; - for( integer ip = 0; ip < numPhases; ++ip ) - { - stats.phasePoreVolume[ip] = MpiWrapper::sum( stats.phasePoreVolume[ip] ); - stats.phaseMass[ip] = MpiWrapper::sum( stats.phaseMass[ip] ); - stats.trappedPhaseMass[ip] = MpiWrapper::sum( stats.trappedPhaseMass[ip] ); - stats.immobilePhaseMass[ip] = MpiWrapper::sum( stats.immobilePhaseMass[ip] ); - stats.totalPoreVolume += stats.phasePoreVolume[ip]; - for( integer ic = 0; ic < numComps; ++ic ) - { - stats.componentMass[ip][ic] = MpiWrapper::sum( stats.componentMass[ip][ic] ); - } - } - stats.averagePressure = MpiWrapper::sum( stats.averagePressure ); - stats.averageTemperature = MpiWrapper::sum( stats.averageTemperature ); - if( stats.totalUncompactedPoreVolume > 0 ) - { - float invTotalUncompactedPoreVolume = 1.0 / stats.totalUncompactedPoreVolume; - stats.averagePressure *= invTotalUncompactedPoreVolume; - stats.averageTemperature *= invTotalUncompactedPoreVolume; - } - else - { - stats.averagePressure = 0.0; - stats.averageTemperature = 0.0; - GEOS_LOG_LEVEL_RANK_0( logInfo::Statistics, - GEOS_FMT( "{}, {}: Cannot compute average pressure because region pore volume is zero.", - getName(), regionNames[i] ) ); - } - - - // helpers to report statistics - array1d< real64 > nonTrappedPhaseMass( numPhases ); - array1d< real64 > mobilePhaseMass( numPhases ); - for( integer ip = 0; ip < numPhases; ++ip ) - { - nonTrappedPhaseMass[ip] = stats.phaseMass[ip] - stats.trappedPhaseMass[ip]; - mobilePhaseMass[ip] = stats.phaseMass[ip] - stats.immobilePhaseMass[ip]; - } - - string_view massUnit = units::getSymbol( m_solver->getMassUnit() ); - - stdVector< string > phaseCompName; - phaseCompName.reserve( numPhases*numComps ); - stdVector< string > massValues; - phaseCompName.reserve( numPhases*numComps ); - - ConstitutiveManager const & constitutiveManager = this->getGroupByPath< ConstitutiveManager >( "/Problem/domain/Constitutive" ); - MultiFluidBase const & fluid = constitutiveManager.getGroup< MultiFluidBase >( m_solver->referenceFluidModelName() ); - auto const phaseNames = fluid.phaseNames(); - auto const componentNames = fluid.componentNames(); - for( integer ip = 0; ip < numPhases; ++ip ) - { - for( integer ic = 0; ic < numComps; ++ic ) - { - std::stringstream ss; - ss << phaseNames[ip] << "/" << componentNames[ic]; - phaseCompName.push_back( ss.str() ); - massValues.push_back( GEOS_FMT( "{}", stats.componentMass[ip][ic] ) ); - } - } - - if( isLogLevelActive< logInfo::Statistics >( this->getLogLevel() ) && MpiWrapper::commRank() == 0 ) - { - TableData compPhaseStatsData; - compPhaseStatsData.addRow( "Pressure [Pa]", stats.minPressure, stats.averagePressure, stats.maxPressure ); - compPhaseStatsData.addRow( "Delta pressure [Pa]", stats.minDeltaPressure, "/", stats.maxDeltaPressure ); - compPhaseStatsData.addRow( "Temperature [K]", stats.minTemperature, stats.averageTemperature, stats.maxTemperature ); - compPhaseStatsData.addSeparator(); - - compPhaseStatsData.addRow( "Total dynamic pore volume [rm^3]", CellType::MergeNext, CellType::MergeNext, stats.totalPoreVolume ); - compPhaseStatsData.addSeparator(); - compPhaseStatsData.addRow( "Phase dynamic pore volume [rm^3]", - stringutilities::joinLambda( phaseNames, "\n", []( auto data ) { return data[0]; } ), - CellType::MergeNext, - stringutilities::joinLambda( stats.phasePoreVolume, "\n", []( auto data ) { return data[0]; } ) ); - compPhaseStatsData.addSeparator(); - - compPhaseStatsData.addRow( GEOS_FMT( "Phase mass [{}]", massUnit ), - stringutilities::joinLambda( phaseNames, "\n", []( auto data ) { return data[0]; } ), - CellType::MergeNext, - stringutilities::joinLambda( stats.phaseMass, "\n", []( auto data ) { return data[0]; } ) ); - compPhaseStatsData.addSeparator(); - - compPhaseStatsData.addRow( GEOS_FMT( "Trapped phase mass (metric 1) [{}]", massUnit ), - stringutilities::joinLambda( phaseNames, "\n", []( auto value ) { return value[0]; } ), - CellType::MergeNext, - stringutilities::joinLambda( stats.trappedPhaseMass, "\n", []( auto value ) { return value[0]; } ) ); - compPhaseStatsData.addSeparator(); - compPhaseStatsData.addRow( GEOS_FMT( "Non-trapped phase mass (metric 1) [{}]", massUnit ), - stringutilities::joinLambda( phaseNames, "\n", []( auto value ) { return value[0]; } ), - CellType::MergeNext, - stringutilities::joinLambda( nonTrappedPhaseMass, "\n", []( auto value ) { return value[0]; } ) ); - compPhaseStatsData.addSeparator(); - - compPhaseStatsData.addRow( GEOS_FMT( "Immobile phase mass (metric 2) [{}]", massUnit ), - stringutilities::joinLambda( phaseNames, "\n", []( auto value ) { return value[0]; } ), - CellType::MergeNext, - stringutilities::joinLambda( stats.immobilePhaseMass, "\n", []( auto value ) { return value[0]; } ) ); - compPhaseStatsData.addSeparator(); - compPhaseStatsData.addRow( GEOS_FMT( "Mobile phase mass (metric 2) [{}]", massUnit ), - stringutilities::joinLambda( phaseNames, "\n", []( auto value ) { return value[0]; } ), - CellType::MergeNext, - stringutilities::joinLambda( mobilePhaseMass, "\n", []( auto value ) { return value[0]; } ) ); - compPhaseStatsData.addSeparator(); - - compPhaseStatsData.addRow( GEOS_FMT( "Component mass [{}]", massUnit ), - stringutilities::join( phaseCompName, '\n' ), - CellType::MergeNext, - stringutilities::join( massValues, '\n' ) ); - - string const title = GEOS_FMT( "{}, {} (time {} s):", getName(), regionNames[i], time ); - TableLayout const compPhaseStatsLayout( title, { "statistics", "min", "average", "max" } ); - TableTextFormatter tableFormatter( compPhaseStatsLayout ); - GEOS_LOG_RANK_0( tableFormatter.toString( compPhaseStatsData ) ); - } - - if( m_writeCSV > 0 && MpiWrapper::commRank() == 0 ) - { - TableData tableData; - tableData.addRow( time, stats.minPressure, stats.averagePressure, stats.maxPressure, stats.minDeltaPressure, stats.maxDeltaPressure, - stats.minTemperature, stats.averageTemperature, stats.maxTemperature, stats.totalPoreVolume, - stringutilities::joinLambda( stats.phasePoreVolume, "\n", []( auto data ) { return data[0]; } ), - stringutilities::joinLambda( stats.phaseMass, "\n", []( auto data ) { return data[0]; } ), - stringutilities::joinLambda( stats.trappedPhaseMass, "\n", []( auto value ) { return value[0]; } ), - stringutilities::joinLambda( nonTrappedPhaseMass, "\n", []( auto value ) { return value[0]; } ), - stringutilities::joinLambda( stats.immobilePhaseMass, "\n", []( auto value ) { return value[0]; } ), - stringutilities::joinLambda( mobilePhaseMass, "\n", []( auto value ) { return value[0]; } ), - stringutilities::join( massValues, '\n' ) ); - - std::ofstream outputFile( m_outputDir + "/" + regionNames[i] + ".csv", std::ios_base::app ); - TableCSVFormatter const csvOutput; - outputFile << csvOutput.dataToString( tableData ); - outputFile.close(); - } - } - -} - -void CompositionalMultiphaseStatistics::computeCFLNumbers( real64 const time, - real64 const dt, - DomainPartition & domain ) const -{ - GEOS_MARK_FUNCTION; - real64 maxPhaseCFL, maxCompCFL; - m_solver->computeCFLNumbers( domain, dt, maxPhaseCFL, maxCompCFL ); - - GEOS_LOG_LEVEL_RANK_0( logInfo::CFL, - GEOS_FMT( "{} (time {} s): Max phase CFL number: {}", getName(), time, maxPhaseCFL ) ); - GEOS_LOG_LEVEL_RANK_0( logInfo::CFL, - GEOS_FMT( "{} (time {} s): Max component CFL number: {}", getName(), time, maxCompCFL ) ); -} - - -REGISTER_CATALOG_ENTRY( TaskBase, - CompositionalMultiphaseStatistics, - string const &, dataRepository::Group * const ) - -} /* namespace geos */ diff --git a/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseStatisticsAggregator.cpp b/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseStatisticsAggregator.cpp new file mode 100644 index 00000000000..c9a0826da47 --- /dev/null +++ b/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseStatisticsAggregator.cpp @@ -0,0 +1,397 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2016-2024 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2024 TotalEnergies + * Copyright (c) 2018-2024 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2023-2024 Chevron + * Copyright (c) 2019- GEOS/GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +/** + * @file CompositionalMultiphaseStatistics.cpp + * @details Region statistics data is stored as follow: + + * Problem : ProblemManager + * |-> domain : DomainPartition + * |-> MeshBodies : Group + * |-> cartesianMesh : MeshBody + * |-> meshLevels : Group + * |-> Level0 : MeshLevel + * | |-> nodeManager : NodeManager + * | | |-> sets : Group + * | | | * all : Wrapper< index array > + * | | | * xneg : Wrapper< index array > + * | | [...] (other element sets) + * | | + * | |-> ElementRegions : ElementRegionManager + * | | |-> Channel : CellElementRegion + * | | | |-> cb-0_0_0 : CellElementSubRegion + * | | | | | * pressure : Wrapper< real64 array > + * | | | | | * temperature : Wrapper< real64 array > + * | | | | [...] (other fields) + * | | | | + * | | | |-> cb-0_0_1 : CellElementSubRegion + * | | | | | * pressure : Wrapper< real64 array > + * | | | | | * temperature : Wrapper< real64 array > + * | | | | [...] (other fields) + * | | | | + * | | | [...] (other sub-regions) + * | | | + * | | |-> Barrier : CellElementRegion + * | | |-> cb-1_0_0 : CellElementSubRegion + * | | |-> cb-1_0_1 : CellElementSubRegion + * | | [...] (other sub-regions) + * | | + * | [...] (other element managers) + * ____ | | + * | | |-> statistics : Group (storage for all stats) + * | | |-> compFlowStats : Group (storage for this instance stats) + * | | | |-> cflStatistics : CFLStatistics + * | | | |-> regionsStatistics : RegionStatistics (aggregate) + * | | | |-> Channel : RegionStatistics (aggregate, mpi reduced) + * | | | | |-> cb-0_0_0 : RegionStatistics (compute read-back) + * stats | | | | |-> cb-0_0_1 : RegionStatistics (compute read-back) + * data -> | | | | [...] (other sub-regions stats) + * | | | | + * | | | |-> Barrier : RegionStatistics (aggregate, mpi reduced) + * | | | |-> cb-1_0_0 : RegionStatistics (compute read-back) + * | | | |-> cb-1_0_1 : RegionStatistics (compute read-back) + * | | | [...] (other sub-regions stats) + * | | | + * |___ | [...] (other stats storages) + * | + * [...] (other discretizations) + */ + +#include "CompositionalMultiphaseStatisticsAggregator.hpp" + +#include "physicsSolvers/StatisticsAggregatorBaseHelpers.hpp" +#include "mesh/DomainPartition.hpp" +#include "physicsSolvers/fluidFlow/CompositionalMultiphaseBase.hpp" +#include "physicsSolvers/fluidFlow/kernels/compositional/StatisticsKernel.hpp" +#include "constitutive/relativePermeability/RelativePermeabilityBase.hpp" + +namespace geos +{ + +using namespace constitutive; +using namespace dataRepository; + +namespace compositionalMultiphaseStatistics +{ + +RegionStatistics::RegionStatistics( string const & name, + dataRepository::Group * const parent, + bool statsOutputEnabled, + integer const numPhases, + integer const numComponents ): + RegionStatisticsBase( name, parent, statsOutputEnabled ), + m_phaseDynamicPoreVolume( numPhases ), + m_phaseMass( numPhases ), + m_trappedPhaseMass( numPhases ), + m_nonTrappedPhaseMass( numPhases ), + m_immobilePhaseMass( numPhases ), + m_mobilePhaseMass( numPhases ), + m_componentMass( numPhases, numComponents ) +{ + // TODO : registerWrappers to store results in HDF5 (but need repairing of 1D HDF5 outputs) +} + +CFLStatistics::CFLStatistics( string const & name, + dataRepository::Group * const parent, + bool const statsOutputEnabled ): + RegionStatisticsBase( name, parent, statsOutputEnabled ) +{ + // TODO : registerWrappers to store results in HDF5 (but need repairing of 1D HDF5 outputs) +} + +StatsAggregator::StatsAggregator( DataContext const & ownerDataContext, + dataRepository::Group & meshBodies, + bool const statsOutputEnabled ): + Base( ownerDataContext, meshBodies, statsOutputEnabled ), + m_params() +{} + +void StatsAggregator::initStatisticsAggregation( CompositionalMultiphaseBase & solver ) +{ + m_solver = &solver; + m_numPhases = solver.numFluidPhases(); + m_numComponents = solver.numFluidComponents(); + + Base::initStatisticsAggregation( solver ); +} + +void StatsAggregator::enableRegionStatisticsAggregation() +{ + auto const registerStats = [=] ( Group & parent, + string const & targetName ) -> RegionStatistics & + { + return parent.registerGroup( targetName, + std::make_unique< RegionStatistics >( targetName, + &parent, + m_statsOutputEnabled, + m_numPhases, + m_numComponents ) ); + }; + + Base::enableRegionStatisticsAggregation( registerStats ); +} + +void StatsAggregator::enableCFLStatistics() +{ + if( m_solver == nullptr ) + return; + + m_solver->registerDataForCFL( m_meshBodies ); + for( auto const & path : m_discretizationsPaths ) + { + MeshLevel & mesh = getMeshLevel( path ); + Group & statisticsGroup = getInstanceStatisticsGroup( mesh ); + auto const cflStatsName = ViewKeys::cflStatisticsString(); + statisticsGroup.registerGroup< CFLStatistics >( cflStatsName, + std::make_unique< CFLStatistics >( cflStatsName, + &statisticsGroup, + m_statsOutputEnabled ) ); + } + + m_cflStatsState.m_isEnabled = true; + m_cflStatsState.m_isDirty = true; +} + +void StatsAggregator::setDirty() +{ + Base::setDirty(); + m_cflStatsState.m_isDirty = true; +} + +bool StatsAggregator::computeCFLNumbers( real64 const time, + real64 const dt, + DomainPartition & domain ) +{ + GEOS_MARK_FUNCTION; + real64 maxPhaseCFL, maxCompCFL; + CFLStatistics * stats = getCFLStatistics( domain ); + + m_warnings.clear(); + + if( !m_cflStatsState.m_isEnabled ) + { + m_warnings.emplace_back( "CFL numbers computation is not enabled." ); + return false; + } + if( stats == nullptr ) + { + m_warnings.emplace_back( GEOS_FMT( "No statistics structure to compute CFL numbers for domain '{}'.", domain.getName() )); + return false; + } + + m_solver->computeCFLNumbers( domain, dt, maxPhaseCFL, maxCompCFL ); + + stats->m_time = time; + stats->m_maxPhaseCFL = maxPhaseCFL; + stats->m_maxCompCFL = maxCompCFL; + + m_cflStatsState.m_isDirty = false; + + return true; +} + +CFLStatistics * StatsAggregator::getCFLStatistics( DomainPartition & domain ) const +{ + return domain.getGroupPointer< CFLStatistics >( ViewKeys::cflStatisticsString() ); +} + +CFLStatistics & StatsAggregator::getCflStatistics( MeshLevel & mesh ) const +{ + // considering everything is initialized, or else, crash gracefully + Group & statisticsGroup = getInstanceStatisticsGroup( mesh ); + return statisticsGroup.getGroup< CFLStatistics >( ViewKeys::cflStatisticsString() ); +} + +void StatsAggregator::initStats( RegionStatistics & stats, real64 const time ) const +{ + stats.m_time = time; + + stats.m_averagePressure = 0.0; + stats.m_maxPressure = 0.0; + stats.m_minPressure = LvArray::NumericLimits< real64 >::max; + + stats.m_maxDeltaPressure = -LvArray::NumericLimits< real64 >::max; + stats.m_minDeltaPressure = LvArray::NumericLimits< real64 >::max; + + stats.m_averageTemperature = 0.0; + stats.m_maxTemperature = 0.0; + stats.m_minTemperature = LvArray::NumericLimits< real64 >::max; + + stats.m_totalMass = 0.0; + + stats.m_totalPoreVolume = 0.0; + stats.m_totalUncompactedPoreVolume = 0.0; + stats.m_phaseDynamicPoreVolume.setValues< serialPolicy >( 0.0 ); + + stats.m_phaseMass.setValues< serialPolicy >( 0.0 ); + stats.m_trappedPhaseMass.setValues< serialPolicy >( 0.0 ); + stats.m_nonTrappedPhaseMass.setValues< serialPolicy >( 0.0 ); + stats.m_immobilePhaseMass.setValues< serialPolicy >( 0.0 ); + stats.m_mobilePhaseMass.setValues< serialPolicy >( 0.0 ); + stats.m_componentMass.setValues< serialPolicy >( 0.0 ); +} + +void StatsAggregator::computeSubRegionRankStats( CellElementSubRegion & subRegion, + RegionStatistics & subRegionStats ) const +{ + arrayView1d< integer const > const elemGhostRank = subRegion.ghostRank(); + arrayView1d< real64 const > const volume = subRegion.getElementVolume(); + arrayView1d< real64 const > const pres = subRegion.getField< fields::flow::pressure >(); + arrayView1d< real64 const > const temp = subRegion.getField< fields::flow::temperature >(); + arrayView2d< real64 const, compflow::USD_PHASE > const phaseVolFrac = + subRegion.getField< fields::flow::phaseVolumeFraction >(); + arrayView1d< real64 const > const deltaPres = subRegion.getField< fields::flow::deltaPressure >(); + + Group const & constitutiveModels = subRegion.getGroup( ElementSubRegionBase::groupKeyStruct::constitutiveModelsString() ); + + string const & solidName = subRegion.getReference< string >( CompositionalMultiphaseBase::viewKeyStruct::solidNamesString() ); + CoupledSolidBase const & solid = constitutiveModels.getGroup< CoupledSolidBase >( solidName ); + arrayView1d< real64 const > const refPorosity = solid.getReferencePorosity(); + arrayView2d< real64 const > const porosity = solid.getPorosity(); + + string const & fluidName = subRegion.getReference< string >( CompositionalMultiphaseBase::viewKeyStruct::fluidNamesString() ); + MultiFluidBase const & fluid = constitutiveModels.getGroup< MultiFluidBase >( fluidName ); + arrayView3d< real64 const, multifluid::USD_PHASE > const phaseDensity = fluid.phaseDensity(); + arrayView4d< real64 const, multifluid::USD_PHASE_COMP > const phaseCompFraction = fluid.phaseCompFraction(); + + //get min vol fraction for each phase to dispactche immobile/mobile mass + string const & relpermName = subRegion.getReference< string >( CompositionalMultiphaseBase::viewKeyStruct::relPermNamesString() ); + RelativePermeabilityBase const & relperm = constitutiveModels.getGroup< RelativePermeabilityBase >( relpermName ); + arrayView3d< real64 const, relperm::USD_RELPERM > const phaseTrappedVolFrac = relperm.phaseTrappedVolFraction(); + arrayView3d< real64 const, relperm::USD_RELPERM > const phaseRelperm = relperm.phaseRelPerm(); + + isothermalCompositionalMultiphaseBaseKernels:: + StatisticsKernel:: + launch< parallelDevicePolicy<> >( subRegion.size(), + m_numComponents, + m_numPhases, + m_params.m_relpermThreshold, + elemGhostRank, + volume, + pres, + deltaPres, + temp, + refPorosity, + porosity, + phaseDensity, + phaseCompFraction, + phaseVolFrac, + phaseTrappedVolFrac, + phaseRelperm, + subRegionStats.m_minPressure, + subRegionStats.m_averagePressure, + subRegionStats.m_maxPressure, + subRegionStats.m_minDeltaPressure, + subRegionStats.m_maxDeltaPressure, + subRegionStats.m_minTemperature, + subRegionStats.m_averageTemperature, + subRegionStats.m_maxTemperature, + subRegionStats.m_totalUncompactedPoreVolume, + subRegionStats.m_phaseDynamicPoreVolume.toView(), + subRegionStats.m_phaseMass.toView(), + subRegionStats.m_trappedPhaseMass.toView(), + subRegionStats.m_immobilePhaseMass.toView(), + subRegionStats.m_componentMass.toView() ); +} + +void StatsAggregator::aggregateStats( RegionStatistics & stats, + RegionStatistics const & other ) const +{ + stats.m_averagePressure += other.m_averagePressure; + stats.m_minPressure = LvArray::math::min( stats.m_minPressure, other.m_minPressure ); + stats.m_maxPressure = LvArray::math::max( stats.m_maxPressure, other.m_maxPressure ); + + stats.m_minDeltaPressure = LvArray::math::min( stats.m_minDeltaPressure, other.m_minDeltaPressure ); + stats.m_maxDeltaPressure = LvArray::math::max( stats.m_maxDeltaPressure, other.m_maxDeltaPressure ); + + stats.m_averageTemperature += other.m_averageTemperature; + stats.m_minTemperature = LvArray::math::min( stats.m_minTemperature, other.m_minTemperature ); + stats.m_maxTemperature = LvArray::math::max( stats.m_maxTemperature, other.m_maxTemperature ); + + stats.m_totalUncompactedPoreVolume += other.m_totalUncompactedPoreVolume; + + for( integer ip = 0; ip < m_numPhases; ++ip ) + { + stats.m_phaseDynamicPoreVolume[ip] += other.m_phaseDynamicPoreVolume[ip]; + stats.m_phaseMass[ip] += other.m_phaseMass[ip]; + stats.m_trappedPhaseMass[ip] += other.m_trappedPhaseMass[ip]; + stats.m_immobilePhaseMass[ip] += other.m_immobilePhaseMass[ip]; + + for( integer ic = 0; ic < m_numComponents; ++ic ) + { + stats.m_componentMass[ip][ic] += other.m_componentMass[ip][ic]; + } + } +} + +void StatsAggregator::mpiAggregateStats( RegionStatistics & stats ) const +{ + stats.m_averagePressure = MpiWrapper::sum( stats.m_averagePressure ); + stats.m_minPressure = MpiWrapper::min( stats.m_minPressure ); + stats.m_maxPressure = MpiWrapper::max( stats.m_maxPressure ); + + stats.m_minDeltaPressure = MpiWrapper::min( stats.m_minDeltaPressure ); + stats.m_maxDeltaPressure = MpiWrapper::max( stats.m_maxDeltaPressure ); + + stats.m_averageTemperature = MpiWrapper::sum( stats.m_averageTemperature ); + stats.m_minTemperature = MpiWrapper::min( stats.m_minTemperature ); + stats.m_maxTemperature = MpiWrapper::max( stats.m_maxTemperature ); + + stats.m_totalUncompactedPoreVolume = MpiWrapper::sum( stats.m_totalUncompactedPoreVolume ); + + for( integer ip = 0; ip < m_numPhases; ++ip ) + { + stats.m_phaseDynamicPoreVolume[ip] = MpiWrapper::sum( stats.m_phaseDynamicPoreVolume[ip] ); + stats.m_phaseMass[ip] = MpiWrapper::sum( stats.m_phaseMass[ip] ); + stats.m_trappedPhaseMass[ip] = MpiWrapper::sum( stats.m_trappedPhaseMass[ip] ); + stats.m_immobilePhaseMass[ip] = MpiWrapper::sum( stats.m_immobilePhaseMass[ip] ); + + for( integer ic = 0; ic < m_numComponents; ++ic ) + { + stats.m_componentMass[ip][ic] = MpiWrapper::sum( stats.m_componentMass[ip][ic] ); + } + } +} + +void StatsAggregator::postAggregateStats( RegionStatistics & stats ) +{ + if( stats.m_totalUncompactedPoreVolume > 0 ) + { + float invTotalUncompactedPoreVolume = 1.0 / stats.m_totalUncompactedPoreVolume; + stats.m_averagePressure *= invTotalUncompactedPoreVolume; + stats.m_averageTemperature *= invTotalUncompactedPoreVolume; + } + else + { + stats.m_averagePressure = 0.0; + stats.m_averageTemperature = 0.0; + m_warnings.emplace_back( GEOS_FMT( "Cannot compute average pressure for '{}' because pore volume is zero in '{}'.", + getOwnerName(), stats.getTargetName() ) ); + } + + for( integer ip = 0; ip < m_numPhases; ++ip ) + { + stats.m_totalMass += stats.m_phaseMass[ip]; + stats.m_totalPoreVolume += stats.m_phaseDynamicPoreVolume[ip]; + stats.m_nonTrappedPhaseMass[ip] = stats.m_phaseMass[ip] - stats.m_trappedPhaseMass[ip]; + stats.m_mobilePhaseMass[ip] = stats.m_phaseMass[ip] - stats.m_immobilePhaseMass[ip]; + } +} + +} /* namespace compositionalMultiphaseStatistics */ + +template class StatsAggregatorBase< compositionalMultiphaseStatistics::StatsAggregator >; + +} /* namespace geos */ diff --git a/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseStatisticsAggregator.hpp b/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseStatisticsAggregator.hpp new file mode 100644 index 00000000000..e027122ebcb --- /dev/null +++ b/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseStatisticsAggregator.hpp @@ -0,0 +1,314 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2016-2024 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2024 TotalEnergies + * Copyright (c) 2018-2024 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2023-2024 Chevron + * Copyright (c) 2019- GEOS/GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +/** + * @file CompositionalMultiphaseStatisticsAggregator.hpp + * @details Region statistics data is stored as follow: + + * Problem : ProblemManager + * |-> domain : DomainPartition + * |-> MeshBodies : Group + * |-> cartesianMesh : MeshBody + * |-> meshLevels : Group + * |-> Level0 : MeshLevel + * | |-> nodeManager : NodeManager + * | | |-> sets : Group + * | | | * all : Wrapper< index array > + * | | | * xneg : Wrapper< index array > + * | | [...] (other element sets) + * | | + * | |-> ElementRegions : ElementRegionManager + * | | |-> Channel : CellElementRegion + * | | | |-> cb-0_0_0 : CellElementSubRegion + * | | | | | * pressure : Wrapper< real64 array > + * | | | | | * temperature : Wrapper< real64 array > + * | | | | [...] (other fields) + * | | | | + * | | | |-> cb-0_0_1 : CellElementSubRegion + * | | | | | * pressure : Wrapper< real64 array > + * | | | | | * temperature : Wrapper< real64 array > + * | | | | [...] (other fields) + * | | | | + * | | | [...] (other sub-regions) + * | | | + * | | |-> Barrier : CellElementRegion + * | | |-> cb-1_0_0 : CellElementSubRegion + * | | |-> cb-1_0_1 : CellElementSubRegion + * | | [...] (other sub-regions) + * | | + * | [...] (other element managers) + * ____ | | + * | | |-> statistics : Group (storage for all stats) + * | | |-> compFlowStats : Group (storage for this instance stats) + * | | | |-> cflStatistics : CFLStatistics + * | | | |-> regionsStatistics : RegionStatistics (aggregate) + * | | | |-> Channel : RegionStatistics (aggregate, mpi reduced) + * | | | | |-> cb-0_0_0 : RegionStatistics (compute read-back) + * stats | | | | |-> cb-0_0_1 : RegionStatistics (compute read-back) + * data -> | | | | [...] (other sub-regions stats) + * | | | | + * | | | |-> Barrier : RegionStatistics (aggregate, mpi reduced) + * | | | |-> cb-1_0_0 : RegionStatistics (compute read-back) + * | | | |-> cb-1_0_1 : RegionStatistics (compute read-back) + * | | | [...] (other sub-regions stats) + * | | | + * |___ | [...] (other stats storages) + * | + * [...] (other discretizations) + */ + +#ifndef SRC_CORECOMPONENTS_PHYSICSSOLVERS_FLUIDFLOW_COMPOSITIONALMULTIPHASESTATISTICSAGGREGATOR_HPP_ +#define SRC_CORECOMPONENTS_PHYSICSSOLVERS_FLUIDFLOW_COMPOSITIONALMULTIPHASESTATISTICSAGGREGATOR_HPP_ + +#include "common/DataTypes.hpp" +#include "physicsSolvers/StatisticsAggregatorBase.hpp" + +namespace geos +{ + +class CompositionalMultiphaseBase; + +namespace compositionalMultiphaseStatistics +{ +class StatsAggregator; +class RegionStatistics; +} + +template<> +struct StatsAggregatorTraits< compositionalMultiphaseStatistics::StatsAggregator > +{ + using SolverType = CompositionalMultiphaseBase; + using StatsGroupType = compositionalMultiphaseStatistics::RegionStatistics; +}; + +namespace compositionalMultiphaseStatistics +{ + +struct AggregatorParameters +{ + /// Threshold to decide whether a phase is considered "mobile" or not + real64 m_relpermThreshold; + + // TODO: add other params like views and stuff +}; + +/** + * @brief Output data group to contain the result of a given stat aggregator on the dataRepository. + * Attributes are public since the class is a POD. + * @todo repair 1D HDF5 outputs to enable stats HDF5 outputs + */ +class RegionStatistics : public RegionStatisticsBase +{ +public: + + /// average region pressure (numerator value before postAggregateCompute()) + real64 m_averagePressure; + /// minimum region pressure + real64 m_minPressure; + /// maximum region pressure + real64 m_maxPressure; + + /// minimum region delta pressure + real64 m_minDeltaPressure; + /// maximum region delta pressure + real64 m_maxDeltaPressure; + + /// average region temperature (numerator value before postAggregateCompute()) + real64 m_averageTemperature; + /// minimum region temperature + real64 m_minTemperature; + /// maximum region temperature + real64 m_maxTemperature; + + /// fluid mass + real64 m_totalMass; + + /// total region pore volume + real64 m_totalPoreVolume; + /// total region uncompacted pore volume (not necessarily output, useful for weighting cell pressure data) + real64 m_totalUncompactedPoreVolume; + /// phase region dynamic pore volume + array1d< real64 > const m_phaseDynamicPoreVolume; + + /// region phase mass (trapped and non-trapped, immobile and mobile) + array1d< real64 > const m_phaseMass; + /// trapped region phase mass + array1d< real64 > const m_trappedPhaseMass; + /// non-trapped region phase mass (available after postAggregateCompute()) + array1d< real64 > const m_nonTrappedPhaseMass; + /// immobile region phase mass + array1d< real64 > const m_immobilePhaseMass; + /// mobile region phase mass (available after postAggregateCompute()) + array1d< real64 > const m_mobilePhaseMass; + /// region component mass + array2d< real64 > const m_componentMass; + + // TODO? -> split to struct PressureStats...MassStats: + // - optional computation of each stats + // - VKS for struct name ("pressureStats"..."massStats") + // - current RegionStatistics struct bits + + /** + * @brief Construct a new Region Statistics object + * @param targetName name of the data-repository object that is targeted by the statistics + * (mesh level / region / sub-region). + * @param parent the instance parent in data-repository + * @param numPhases Fluid phase count + * @param numComponents Fluid component count + */ + RegionStatistics( string const & targetName, + dataRepository::Group * const parent, + bool statsOutputEnabled, + integer numPhases, + integer numComponents ); + + RegionStatistics( RegionStatistics && ) = default; + +}; + +/** + * @brief Output data group to contain the result of a given stat aggregator on the dataRepository. + * Attributes are public since the class is a POD. Can it be replaced by a wrapped-struct? + */ +class CFLStatistics : public RegionStatisticsBase +{ +public: + + /// Maximum Courant Friedrichs Lewy number in the grid for each phase + real64 m_maxPhaseCFL; + + /// Maximum Courant-Friedrichs-Lewy number in the grid for each component + real64 m_maxCompCFL; + + /** + * @brief Construct a new CFLStatistics object + * @param name instance name in data-repository + * @param parent the instance parent in data-repository + */ + CFLStatistics( const string & name, + dataRepository::Group * const parent, + bool statsOutputEnabled ); +}; + +/** + * @brief Reponsible of computing physical statistics over the grid, registering the result in the + * data repository, but not storing / outputing it by itself. It does not have mutable state + * except the encountered issues. + */ +class StatsAggregator : public StatsAggregatorBase< StatsAggregator > +{ +public: + + using Base = StatsAggregatorBase< StatsAggregator >; + + /** + * @brief the associated view keys + */ + struct ViewKeys + { + /// String for the cfl statistics group + constexpr static char const * cflStatisticsString() { return "cflStatistics"; } + }; + + /** + * @brief Construct a new Stats Aggregator object + * @param ownerName the unique name of the entity requesting the statistics. + * An error is thrown if not unique in this context. + * @param meshBodies The Group containing the MeshBody objects + * @param statsOutputEnabled If true, the stats are saved in the output HDF5 + * (through dataRepository::RestartFlags, but not functional for this output for now). + */ + StatsAggregator( dataRepository::DataContext const & ownerDataContext, + dataRepository::Group & meshBodies, + bool m_statsOutputEnabled ); + + /** + * @brief Enable the computation of any statistics, initialize data structure to collect them. + * Register the resulting data wrappers so they will be targeted by TimeHistory output + * @param solver flow solver object to retrieve: + - the simulated regions, + - fields for statistics computation. + */ + void initStatisticsAggregation( CompositionalMultiphaseBase & solver ); + + /** + * @brief Enable the computation of region statistics, initialize data structure to collect them. + * Register the resulting data wrappers so they will be targeted by TimeHistory output + * @note Must be called in or after the "registerDataOnMesh" initialization phase + */ + void enableRegionStatisticsAggregation(); + + /** + * @brief Register the results structs & wrappers so they will be targeted by TimeHistory output + * @note Must be called in or after the "registerDataOnMesh" initialization phase + */ + void enableCFLStatistics(); + + /** + * @brief set the statistics as dirty, ensuring isComputed() will be false until the next computation. + */ + void setDirty(); + + /** + * @brief Compute CFL numbers + * @param[in] time current time + * @param[in] dt the time step size + * @param[in] domain the domain partition + * @return false if there was a problem that prevented the statistics to be computed correctly. + */ + bool computeCFLNumbers( real64 const time, + real64 const dt, + DomainPartition & domain ); + + integer getNumPhases() const + { return m_numPhases; } + + integer getNumComponents() const + { return m_numComponents; } + + CFLStatistics * getCFLStatistics( DomainPartition & domain ) const; + + CFLStatistics & getCflStatistics( MeshLevel & mesh ) const; + + // template implementations + /// @cond DO_NOT_DOCUMENT + + void initStats( RegionStatistics & stats, real64 time ) const; + void computeSubRegionRankStats( CellElementSubRegion & subRegion, RegionStatistics & subRegionStats ) const; + void aggregateStats( RegionStatistics & stats, RegionStatistics const & other ) const; + void mpiAggregateStats( RegionStatistics & stats ) const; + void postAggregateStats( RegionStatistics & stats ); + + /// @endcond + +private: + + SolverType * m_solver = nullptr; + + AggregatorParameters m_params; + + StatsState m_cflStatsState; + + integer m_numPhases; + + integer m_numComponents; + +}; + +} /* namespace compositionalMultiphaseStatistics */ + +} /* namespace geos */ + +#endif /* SRC_CORECOMPONENTS_PHYSICSSOLVERS_FLUIDFLOW_COMPOSITIONALMULTIPHASESTATISTICSAGGREGATOR_HPP_ */ diff --git a/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseStatisticsTask.cpp b/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseStatisticsTask.cpp new file mode 100644 index 00000000000..d80db9040c1 --- /dev/null +++ b/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseStatisticsTask.cpp @@ -0,0 +1,419 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2016-2024 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2024 TotalEnergies + * Copyright (c) 2018-2024 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2023-2024 Chevron + * Copyright (c) 2019- GEOS/GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +/** + * @file CompositionalMultiphaseStatistics.cpp + */ + +#include "CompositionalMultiphaseStatisticsTask.hpp" + +#include "constitutive/fluid/multifluid/MultiFluidBase.hpp" +#include "physicsSolvers/LogLevelsInfo.hpp" +#include "physicsSolvers/fluidFlow/LogLevelsInfo.hpp" +#include "physicsSolvers/fluidFlow/CompositionalMultiphaseHybridFVM.hpp" + + +namespace geos +{ + +using namespace dataRepository; + +namespace compositionalMultiphaseStatistics +{ + +StatsTask::StatsTask( string const & name, Group * const parent ): + Base( name, parent ), + m_aggregator(), + m_computeCFLNumbers( 0 ), + m_computeRegionStatistics( 1 ) +{ + registerWrapper( viewKeyStruct::computeCFLNumbersString(), &m_computeCFLNumbers ). + setApplyDefaultValue( 0 ). + setInputFlag( InputFlags::OPTIONAL ). + setDescription( "Flag to decide whether CFL numbers are computed or not" ); + + registerWrapper( viewKeyStruct::computeRegionStatisticsString(), &m_computeRegionStatistics ). + setApplyDefaultValue( 1 ). + setInputFlag( InputFlags::OPTIONAL ). + setDescription( "Flag to decide whether region statistics are computed or not" ); + + registerWrapper( viewKeyStruct::relpermThresholdString(), &m_relpermThreshold ). + setApplyDefaultValue( 1e-6 ). + setInputFlag( InputFlags::OPTIONAL ). + setDescription( "Flag to decide whether a phase is considered mobile (when the relperm is above the threshold) or immobile (when the relperm is below the threshold) in metric 2" ); + + addLogLevel< logInfo::CFL >(); + addLogLevel< logInfo::Statistics >(); +} + +void StatsTask::postInputInitialization() +{ + Base::postInputInitialization(); + + GEOS_THROW_IF_EQ_MSG( m_solver, nullptr, + "To identify simulated regions, a solver must be provided.", + InputError, getWrapperDataContext( getSolverWrapperKey() ) ); + + if( !dynamicCast< CompositionalMultiphaseBase * >( m_solver ) ) + { + GEOS_THROW( "Incompatible solver selected, a compositional multiphase solver is expected", + InputError, getDataContext() ); + } + else if( dynamicCast< CompositionalMultiphaseHybridFVM * >( m_solver ) && m_computeCFLNumbers != 0 ) + { + GEOS_THROW( "The option to compute CFL numbers is incompatible with CompositionalMultiphaseHybridFVM", + InputError, getDataContext() ); + } +} + +void StatsTask::registerDataOnMesh( Group & meshBodies ) +{ + // for now, this guard is needed to avoid breaking the xml schema generation + if( m_solver == nullptr ) + return; + + prepareFluidMetaData(); + + if( m_computeRegionStatistics || m_computeCFLNumbers ) + { + // expected to work as check is done in postInputInitialization() + CompositionalMultiphaseBase * castedSolver = dynamicCast< CompositionalMultiphaseBase * >( m_solver ); + GEOS_ERROR_IF_EQ_MSG( castedSolver, nullptr, + GEOS_FMT( "{} {}: Unexpected error (solver pointer changed?)", catalogName(), getDataContext() ) ); + m_aggregator = std::make_unique< StatsAggregator >( getDataContext(), meshBodies, true ); + m_aggregator->initStatisticsAggregation( *castedSolver ); + } + else + { + GEOS_WARNING( GEOS_FMT( "{} {}: No computing option enabled, no output is scheduled.", + catalogName(), getDataContext() ) ); + } + + if( m_computeRegionStatistics ) + m_aggregator->enableRegionStatisticsAggregation(); + + // if we have to compute CFL numbers later, we need to register additional variables + if( m_computeCFLNumbers ) + m_aggregator->enableCFLStatistics(); + + m_aggregator->forRegionStatistics( [&] ( MeshLevel & mesh, RegionStatistics & ) + { + prepareLogTableLayouts( mesh.getName() ); + prepareCsvTableLayouts( mesh.getName() ); + } ); +} + +void StatsTask::prepareFluidMetaData() +{ + using namespace constitutive; + + ConstitutiveManager const & constitutiveManager = this->getGroupByPath< ConstitutiveManager >( "/Problem/domain/Constitutive" ); + MultiFluidBase const & fluid = constitutiveManager.getGroup< MultiFluidBase >( m_solver->referenceFluidModelName() ); + + m_fluid.m_numPhases = fluid.numFluidPhases(); + m_fluid.m_numComps = fluid.numFluidComponents(); + + m_fluid.m_phaseNames = fluid.phaseNames(); + m_fluid.m_compNames = fluid.componentNames(); + + m_fluid.m_phaseCompNames.resize( m_fluid.m_numPhases * m_fluid.m_numComps, string() ); + + for( int ip = 0; ip < m_fluid.m_numPhases; ++ip ) + for( int ic = 0; ic < m_fluid.m_numComps; ++ic ) + m_fluid.phaseCompName( ip, ic ) = GEOS_FMT( "{} / {}", m_fluid.m_phaseNames[ip], m_fluid.m_compNames[ic] ); +} + +void StatsTask::prepareLogTableLayouts( string_view meshName ) +{ + // only output from rank 0 + if( MpiWrapper::commRank() != 0 ) + return; + + TableLayout const tableLayout = TableLayout() + .setTitle( GEOS_FMT( "{}: mesh {}", getName(), meshName ) ); + + m_logFormatters.emplace( meshName, std::make_unique< TableTextFormatter >( tableLayout ) ); +} + +void StatsTask::prepareCsvTableLayouts( string_view meshName ) +{ + // only output from rank 0 + if( MpiWrapper::commRank() != 0 || !m_writeCSV ) + return; + + integer const numPhases = m_solver->numFluidPhases(); + integer const numComps = m_solver->numFluidComponents(); + + auto addPhaseColumns = []( TableLayout & ptableLayout, string const & description, string_view punit, + integer pnumPhases ) + { + for( int ip = 0; ip < pnumPhases; ++ip ) + ptableLayout.addColumn( GEOS_FMT( "{} (phase {}) [{}]", + description, ip, punit ) ); + }; + auto addPhaseCompColumns = []( TableLayout & ptableLayout, string const & description, string_view punit, + integer pnumPhases, integer pnumComps ) + { + for( int ip = 0; ip < pnumPhases; ++ip ) + for( int ic = 0; ic < pnumComps; ++ic ) + ptableLayout.addColumn( GEOS_FMT( "{} (component {} / phase {}) [{}]", + description, ic, ip, punit ) ); + }; + + string_view massUnit = units::getSymbol( m_solver->getMassUnit() ); + + TableLayout tableLayout( { + TableLayout::Column( GEOS_FMT( "Time [{}]", units::getSymbol( units::Unit::Time ))), + TableLayout::Column( "Region" ), // TODO : mention this change in PR description + TableLayout::Column( GEOS_FMT( "Min pressure [{}]", units::getSymbol( units::Unit::Pressure ))), + TableLayout::Column( GEOS_FMT( "Average pressure [{}]", units::getSymbol( units::Unit::Pressure )) ), + TableLayout::Column( GEOS_FMT( "Max pressure [{}]", units::getSymbol( units::Unit::Pressure ) ) ), + TableLayout::Column( GEOS_FMT( "Min delta pressure [{}]", units::getSymbol( units::Unit::Pressure ))), + TableLayout::Column( GEOS_FMT( "Max delta pressure [{}]", units::getSymbol( units::Unit::Pressure ))), + TableLayout::Column( GEOS_FMT( "Min temperature [{}]", units::getSymbol( units::Unit::Temperature ) )), + TableLayout::Column( GEOS_FMT( "Average temperature [{}]", units::getSymbol( units::Unit::Temperature ) )), + TableLayout::Column( GEOS_FMT( "Max temperature [{}]", units::getSymbol( units::Unit::Temperature ) )), + TableLayout::Column( GEOS_FMT( "Total dynamic pore volume [{}]", units::getSymbol( units::Unit::ReservoirVolume ) )), + } ); + addPhaseColumns( tableLayout, "Phase dynamic pore volume", units::getSymbol( units::Unit::ReservoirVolume ), numPhases ); + addPhaseColumns( tableLayout, "Phase mass", massUnit, numPhases ); + addPhaseColumns( tableLayout, "Trapped phase mass (metric 1)", massUnit, numPhases ); + addPhaseColumns( tableLayout, "Non-trapped phase mass (metric 1)", massUnit, numPhases ); + addPhaseColumns( tableLayout, "Immobile phase mass (metric 2)", massUnit, numPhases ); + addPhaseColumns( tableLayout, "Mobile phase mass (metric 2)", massUnit, numPhases ); + addPhaseCompColumns( tableLayout, "Component mass", massUnit, numPhases, numComps ); + + auto & csvFormatter = m_csvFormatters.get_inserted( string( meshName ) ); + csvFormatter = std::make_unique< TableCSVFormatter >( tableLayout ); + + // output CSV header + std::ofstream outputFile( getCsvFileName( meshName ) ); + outputFile << csvFormatter->headerToString(); +} + +string StatsTask::getCsvFileName( string_view meshName ) const +{ return GEOS_FMT( "{}/{}.csv", m_outputDir, meshName ); } + +bool StatsTask::execute( real64 const time_n, + real64 const dt, + integer const GEOS_UNUSED_PARAM( cycleNumber ), + integer const GEOS_UNUSED_PARAM( eventCounter ), + real64 const GEOS_UNUSED_PARAM( eventProgress ), + DomainPartition & domain ) +{ + // current statistics time is after solver resolution: time_n (timestep start) + dt + real64 statsTime = time_n + dt; + + GEOS_ERROR_IF( !m_aggregator, + "No statistics aggregator initialized!", getDataContext() ); + + m_aggregator->computeRegionsStatistics( statsTime ); + + m_aggregator->forRegionStatistics( [&] ( MeshLevel & mesh, RegionStatistics & meshRegionsStatistics ) + { + if( m_computeRegionStatistics ) + { + outputLogStats( statsTime, mesh, meshRegionsStatistics ); + outputCsvStats( statsTime, mesh, meshRegionsStatistics ); + } + } ); + + if( m_computeCFLNumbers ) + m_aggregator->computeCFLNumbers( statsTime, dt, domain ); + + return false; +} + +void StatsTask::outputLogStats( real64 const statsTime, + MeshLevel & mesh, + RegionStatistics & meshRegionsStatistics ) +{ + if( MpiWrapper::commRank() > 0 || !isLogLevelActive< logInfo::Statistics >( this->getLogLevel() ) ) + return; + + auto const formatterIter = m_logFormatters.find( mesh.getName() ); + if( formatterIter==m_logFormatters.end()) + return; + + TableTextFormatter const & formatter = *formatterIter->second; + TableData tableData; + static constexpr auto merge = CellType::MergeNext; + + string_view massUnit = units::getSymbol( m_solver->getMassUnit() ); + string_view pressureUnit = units::getSymbol( units::Pressure ); + string_view tempUnit = units::getSymbol( units::Temperature ); + string_view resVolUnit = units::getSymbol( units::ReservoirVolume ); + + tableData.getErrorsList().appendErrors( m_aggregator->getWarnings() ); + + tableData.addRow( "Statistics time", merge, merge, statsTime ); + + // lamda to apply for each region statistics + auto const outputRegionStats = [&] ( string_view targetName, RegionStatistics & stats ) + { + tableData.addSeparator(); + tableData.addRow( merge, merge, merge, "" ); + tableData.addRow( merge, merge, merge, targetName ); + tableData.addSeparator(); + + tableData.addRow( "statistics", "min", "average", "max" ); + tableData.addSeparator(); + + tableData.addRow( GEOS_FMT( "Pressure [{}]", pressureUnit ), + stats.m_minPressure, stats.m_averagePressure, stats.m_maxPressure ); + tableData.addRow( GEOS_FMT( "Delta pressure [{}]", pressureUnit ), + stats.m_minDeltaPressure, "/", stats.m_maxDeltaPressure ); + tableData.addRow( GEOS_FMT( "Temperature [{}]", tempUnit ), + stats.m_minTemperature, stats.m_averageTemperature, stats.m_maxTemperature ); + + tableData.addSeparator(); + + tableData.addRow( GEOS_FMT( "Total dynamic pore volume [{}]", resVolUnit ), + "all", + CellType::MergeNext, + stats.m_totalPoreVolume ); + tableData.addRow( GEOS_FMT( "Phase dynamic pore volume [{}]", resVolUnit ), + stringutilities::joinLambda( m_fluid.m_phaseNames, "\n", []( auto data ) { return data[0]; } ), + CellType::MergeNext, + stringutilities::joinLambda( stats.m_phaseDynamicPoreVolume, "\n", []( auto data ) { return data[0]; } ) ); + + tableData.addSeparator(); + + tableData.addRow( GEOS_FMT( "Total fluid mass [{}]", massUnit ), + "all", + CellType::MergeNext, + stats.m_totalMass ); + + tableData.addRow( GEOS_FMT( "Phase mass [{}]", massUnit ), + stringutilities::joinLambda( m_fluid.m_phaseNames, "\n", []( auto data ) { return data[0]; } ), + CellType::MergeNext, + stringutilities::joinLambda( stats.m_phaseMass, "\n", []( auto data ) { return data[0]; } ) ); + + tableData.addSeparator(); + + tableData.addRow( GEOS_FMT( "Trapped phase mass (metric 1) [{}]", massUnit ), + stringutilities::joinLambda( m_fluid.m_phaseNames, "\n", []( auto value ) { return value[0]; } ), + CellType::MergeNext, + stringutilities::joinLambda( stats.m_trappedPhaseMass, "\n", []( auto value ) { return value[0]; } ) ); + tableData.addRow( GEOS_FMT( "Non-trapped phase mass (metric 1) [{}]", massUnit ), + stringutilities::joinLambda( m_fluid.m_phaseNames, "\n", []( auto value ) { return value[0]; } ), + CellType::MergeNext, + stringutilities::joinLambda( stats.m_nonTrappedPhaseMass, "\n", []( auto value ) { return value[0]; } ) ); + + tableData.addSeparator(); + + tableData.addRow( GEOS_FMT( "Immobile phase mass (metric 2) [{}]", massUnit ), + stringutilities::joinLambda( m_fluid.m_phaseNames, "\n", []( auto value ) { return value[0]; } ), + CellType::MergeNext, + stringutilities::joinLambda( stats.m_immobilePhaseMass, "\n", []( auto value ) { return value[0]; } ) ); + tableData.addRow( GEOS_FMT( "Mobile phase mass (metric 2) [{}]", massUnit ), + stringutilities::joinLambda( m_fluid.m_phaseNames, "\n", []( auto value ) { return value[0]; } ), + CellType::MergeNext, + stringutilities::joinLambda( stats.m_mobilePhaseMass, "\n", []( auto value ) { return value[0]; } ) ); + + tableData.addSeparator(); + + tableData.addRow( GEOS_FMT( "Component mass [{}]", massUnit ), + stringutilities::join( m_fluid.m_phaseCompNames, '\n' ), + CellType::MergeNext, + stringutilities::join( stats.m_componentMass, '\n' ) ); + }; + + // apply the lambda for each region and, finally, the mesh summary + outputRegionStats( GEOS_FMT( "Discretization '{}'", mesh.getName() ), meshRegionsStatistics ); + + m_aggregator->forRegionStatistics( mesh, meshRegionsStatistics, + [&] ( CellElementRegion & region, RegionStatistics & stats ) + { + outputRegionStats( GEOS_FMT( "Region '{}'", region.getName() ), stats ); + } ); + + // output to log + GEOS_LOG_RANK_0( formatter.toString( tableData ) ); +} + +void StatsTask::outputCsvStats( real64 statsTime, + MeshLevel & mesh, + RegionStatistics & meshRegionsStatistics ) +{ + if( MpiWrapper::commRank() > 0 || m_writeCSV == 0 ) + return; + + auto const formatterIter = m_csvFormatters.find( mesh.getName() ); + if( formatterIter==m_csvFormatters.end()) + return; + + TableCSVFormatter const & formatter = *formatterIter->second; + TableData tableData; + + stdVector< string > row; + row.reserve( formatter.getLayout().getTotalLowermostColumnCount() ); + + // lamda to apply for each region statistics + auto const outputRegionStats = [&] ( string_view targetName, RegionStatistics & stats ) + { + + auto addPhaseValues = []( auto & list, auto const & values ) + { + for( auto value : values ) + list.emplace_back( std::to_string( value ) ); + }; + + row.clear(); + row.insert( row.begin(), + { std::to_string( statsTime ), + string( targetName ), + std::to_string( stats.m_minPressure ), + std::to_string( stats.m_averagePressure ), + std::to_string( stats.m_maxPressure ), + std::to_string( stats.m_minDeltaPressure ), + std::to_string( stats.m_maxDeltaPressure ), + std::to_string( stats.m_minTemperature ), + std::to_string( stats.m_averageTemperature ), + std::to_string( stats.m_maxTemperature ), + std::to_string( stats.m_totalPoreVolume ), + } ); + addPhaseValues( row, stats.m_phaseDynamicPoreVolume ); + addPhaseValues( row, stats.m_phaseMass ); + addPhaseValues( row, stats.m_trappedPhaseMass ); + addPhaseValues( row, stats.m_nonTrappedPhaseMass ); + addPhaseValues( row, stats.m_immobilePhaseMass ); + addPhaseValues( row, stats.m_mobilePhaseMass ); + addPhaseValues( row, stats.m_componentMass ); // TODO verify phase / comp ordering + + tableData.addRow( row ); + }; + + // apply the lambda for each region and, finally, the mesh summary + m_aggregator->forRegionStatistics( mesh, meshRegionsStatistics, + [&] ( CellElementRegion & region, RegionStatistics & stats ) + { + outputRegionStats( region.getName(), stats ); + } ); + outputRegionStats( mesh.getName(), meshRegionsStatistics ); + + // append to csv file + std::ofstream outputFile( getCsvFileName( mesh.getName() ), std::ios_base::app ); + outputFile << formatter.dataToString( tableData ); + outputFile.close(); +} + +REGISTER_CATALOG_ENTRY( TaskBase, + StatsTask, + string const &, dataRepository::Group * const ) + +} /* namespace compositionalMultiphaseStatistics */ + +} /* namespace geos */ diff --git a/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseStatistics.hpp b/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseStatisticsTask.hpp similarity index 55% rename from src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseStatistics.hpp rename to src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseStatisticsTask.hpp index dbe275b7b6f..c74dd261ba3 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseStatistics.hpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseStatisticsTask.hpp @@ -14,25 +14,30 @@ */ /** - * @file CompositionalMultiphaseStatistics.hpp + * @file CompositionalMultiphaseStatisticsTask.hpp */ -#ifndef SRC_CORECOMPONENTS_PHYSICSSOLVERS_FLUIDFLOW_COMPOSITIONALMULTIPHASESTATISTICS_HPP_ -#define SRC_CORECOMPONENTS_PHYSICSSOLVERS_FLUIDFLOW_COMPOSITIONALMULTIPHASESTATISTICS_HPP_ +#ifndef SRC_CORECOMPONENTS_PHYSICSSOLVERS_FLUIDFLOW_COMPOSITIONALMULTIPHASESTATISTICSTASK_HPP_ +#define SRC_CORECOMPONENTS_PHYSICSSOLVERS_FLUIDFLOW_COMPOSITIONALMULTIPHASESTATISTICSTASK_HPP_ +#include "common/DataTypes.hpp" +#include "common/format/table/TableFormatter.hpp" #include "physicsSolvers/FieldStatisticsBase.hpp" +#include "physicsSolvers/fluidFlow/CompositionalMultiphaseStatisticsAggregator.hpp" +#include namespace geos { class CompositionalMultiphaseBase; +namespace compositionalMultiphaseStatistics +{ + /** - * @class CompositionalMultiphaseStatistics - * * Task class allowing for the computation of aggregate statistics in compositional multiphase simulations */ -class CompositionalMultiphaseStatistics : public FieldStatisticsBase< CompositionalMultiphaseBase > +class StatsTask : public FieldStatisticsBase< CompositionalMultiphaseBase > { public: @@ -41,16 +46,11 @@ class CompositionalMultiphaseStatistics : public FieldStatisticsBase< Compositio * @param[in] name the name of the task coming from the xml * @param[in] parent the parent group of the task */ - CompositionalMultiphaseStatistics( const string & name, - Group * const parent ); + StatsTask( const string & name, dataRepository::Group * const parent ); /// Accessor for the catalog name static string catalogName() { return "CompositionalMultiphaseStatistics"; } - /// Accessor for the region statistics catalog name - static string regionStatisticsName() { return "regionStatistics"; } - - /** * @defgroup Tasks Interface Functions * @@ -67,43 +67,12 @@ class CompositionalMultiphaseStatistics : public FieldStatisticsBase< Compositio /**@}*/ - struct RegionStatistics - { - /// average region pressure - real64 averagePressure; - /// minimum region pressure - real64 minPressure; - /// maximum region pressure - real64 maxPressure; - - /// minimum region delta pressure - real64 minDeltaPressure; - /// maximum region delta pressure - real64 maxDeltaPressure; - - /// average region temperature - real64 averageTemperature; - /// minimum region temperature - real64 minTemperature; - /// maximum region temperature - real64 maxTemperature; - - /// total region pore volume - real64 totalPoreVolume; - /// total region uncompacted pore volume - real64 totalUncompactedPoreVolume; - /// phase region phase pore volume - array1d< real64 > phasePoreVolume; - - /// region phase mass (trapped and non-trapped, immobile and mobile) - array1d< real64 > phaseMass; - /// trapped region phase mass - array1d< real64 > trappedPhaseMass; - /// immobile region phase mass - array1d< real64 > immobilePhaseMass; - /// region component mass - array2d< real64 > componentMass; - }; + StatsAggregator & getStatisticsAggregator() + { return *m_aggregator; } + + StatsAggregator const & getStatisticsAggregator() const + { return *m_aggregator; } + private: using Base = FieldStatisticsBase< CompositionalMultiphaseBase >; @@ -117,37 +86,38 @@ class CompositionalMultiphaseStatistics : public FieldStatisticsBase< Compositio constexpr static char const * computeCFLNumbersString() { return "computeCFLNumbers"; } /// String for the flag deciding the computation of the region statistics constexpr static char const * computeRegionStatisticsString() { return "computeRegionStatistics"; } - /// String for the region statistics - constexpr static char const * regionStatisticsString() { return "regionStatistics"; } /// String for the relperm threshold constexpr static char const * relpermThresholdString() { return "relpermThreshold"; } }; + void postInputInitialization() override; + void registerDataOnMesh( Group & meshBodies ) override; - /** - * @brief Compute some statistics on the reservoir (average field pressure, etc) - * @param[in] time current time - * @param[in] mesh the mesh level object - * @param[in] regionNames the array of target region names - */ - void computeRegionStatistics( real64 const time, - MeshLevel & mesh, - string_array const & regionNames ) const; + void prepareFluidMetaData(); - /** - * @brief Compute CFL numbers - * @param[in] time current time - * @param[in] dt the time step size - * @param[in] domain the domain partition - */ - void computeCFLNumbers( real64 const time, - real64 const dt, - DomainPartition & domain ) const; + void prepareLogTableLayouts( string_view tableName ); - void postInputInitialization() override; + void prepareCsvTableLayouts( string_view tableName ); - void registerDataOnMesh( Group & meshBodies ) override; + string getCsvFileName( string_view meshName ) const; + + void outputLogStats( real64 statsTime, + MeshLevel & mesh, + RegionStatistics & meshRegionsStatistics ); + + void outputCsvStats( real64 statsTime, + MeshLevel & mesh, + RegionStatistics & meshRegionsStatistics ); + + /// For each discretization (MeshLevel name), table formatter for log output. + stdMap< string, std::unique_ptr< TableTextFormatter > > m_logFormatters; + + /// For each discretization (MeshLevel name), table formatter for csv output. + stdMap< string, std::unique_ptr< TableCSVFormatter > > m_csvFormatters; + + // mesh statistics aggregator + std::unique_ptr< StatsAggregator > m_aggregator; /// Flag to decide whether CFL numbers are computed or not integer m_computeCFLNumbers; @@ -158,9 +128,28 @@ class CompositionalMultiphaseStatistics : public FieldStatisticsBase< Compositio /// Threshold to decide whether a phase is considered "mobile" or not real64 m_relpermThreshold; + struct FluidMetaData + { + integer m_numPhases; + integer m_numComps; + stdVector< string > m_phaseNames; + stdVector< string > m_compNames; + stdVector< string > m_phaseCompNames; + + /** + * @param phaseId index of the phase for which we want the name + * @param compId index of the component for which we want the name + * @return string& reference of the name string + */ + string & phaseCompName( integer phaseId, integer compId ) + { return m_phaseCompNames[phaseId * m_numComps + compId]; } + + } m_fluid; + }; +} /* namespace compositionalMultiphaseStatistics */ } /* namespace geos */ -#endif /* SRC_CORECOMPONENTS_PHYSICSSOLVERS_FLUIDFLOW_COMPOSITIONALMULTIPHASESTATISTICS_HPP_ */ +#endif /* SRC_CORECOMPONENTS_PHYSICSSOLVERS_FLUIDFLOW_COMPOSITIONALMULTIPHASESTATISTICSTASK_HPP_ */ diff --git a/src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseStatistics.cpp b/src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseStatistics.cpp deleted file mode 100644 index 31cfee04b70..00000000000 --- a/src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseStatistics.cpp +++ /dev/null @@ -1,303 +0,0 @@ -/* - * ------------------------------------------------------------------------------------------------------------ - * SPDX-License-Identifier: LGPL-2.1-only - * - * Copyright (c) 2016-2024 Lawrence Livermore National Security LLC - * Copyright (c) 2018-2024 TotalEnergies - * Copyright (c) 2018-2024 The Board of Trustees of the Leland Stanford Junior University - * Copyright (c) 2023-2024 Chevron - * Copyright (c) 2019- GEOS/GEOSX Contributors - * All rights reserved - * - * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. - * ------------------------------------------------------------------------------------------------------------ - */ - -/** - * @file SinglePhaseStatistics.cpp - */ - -#include "SinglePhaseStatistics.hpp" - -#include "mesh/DomainPartition.hpp" -#include "physicsSolvers/LogLevelsInfo.hpp" -#include "physicsSolvers/fluidFlow/SinglePhaseBase.hpp" -#include "physicsSolvers/fluidFlow/FlowSolverBaseFields.hpp" -#include "physicsSolvers/fluidFlow/SinglePhaseBaseFields.hpp" -#include "physicsSolvers/fluidFlow/kernels/singlePhase/StatisticsKernel.hpp" -#include "common/format/table/TableData.hpp" -#include "common/format/table/TableFormatter.hpp" -#include "common/format/table/TableLayout.hpp" - -namespace geos -{ - -using namespace constitutive; -using namespace fields; -using namespace dataRepository; - -SinglePhaseStatistics::SinglePhaseStatistics( const string & name, - Group * const parent ): - Base( name, parent ) -{ - addLogLevel< logInfo::Statistics >(); -} - -void SinglePhaseStatistics::registerDataOnMesh( Group & meshBodies ) -{ - // the fields have to be registered in "registerDataOnMesh" (and not later) - // otherwise they cannot be targeted by TimeHistory - - // for now, this guard is needed to avoid breaking the xml schema generation - if( m_solver == nullptr ) - { - return; - } - - m_solver->forDiscretizationOnMeshTargets( meshBodies, [&] ( string const &, - MeshLevel & mesh, - string_array const & regionNames ) - { - ElementRegionManager & elemManager = mesh.getElemManager(); - - for( size_t i = 0; i < regionNames.size(); ++i ) - { - ElementRegionBase & region = elemManager.getRegion( regionNames[i] ); - region.registerWrapper< RegionStatistics >( viewKeyStruct::regionStatisticsString() ). - setRestartFlags( RestartFlags::NO_WRITE ); - region.excludeWrappersFromPacking( { viewKeyStruct::regionStatisticsString() } ); - - if( m_writeCSV > 0 && MpiWrapper::commRank() == 0 ) - { - TableLayout tableLayout( { - TableLayout::Column().setName( GEOS_FMT( "Time [{}]", units::getSymbol( units::Unit::Time ))), - TableLayout::Column().setName( GEOS_FMT( "Min pressure [{}]", units::getSymbol( units::Unit::Pressure ))), - TableLayout::Column().setName( GEOS_FMT( "Average pressure [{}]", units::getSymbol( units::Unit::Pressure ))), - TableLayout::Column().setName( GEOS_FMT( "Max pressure [{}]", units::getSymbol( units::Unit::Pressure ))), - TableLayout::Column().setName( GEOS_FMT( "Min delta pressure [{}]", units::getSymbol( units::Unit::Pressure ))), - TableLayout::Column().setName( GEOS_FMT( "Max delta pressure [{}]", units::getSymbol( units::Unit::Pressure )) ), - TableLayout::Column().setName( GEOS_FMT( "Min temperature [{}]", units::getSymbol( units::Unit::Temperature ))), - TableLayout::Column().setName( GEOS_FMT( "Average temperature [{}]", units::getSymbol( units::Unit::Temperature ))), - TableLayout::Column().setName( GEOS_FMT( "Max temperature [{}]", units::getSymbol( units::Unit::Temperature ))), - TableLayout::Column().setName( GEOS_FMT( "Total dynamic pore volume [{}]", units::getSymbol( units::Unit::ReservoirVolume ) )), - TableLayout::Column().setName( GEOS_FMT( "Total fluid mass [{}]", units::getSymbol( units::Unit::Mass ))) - } ); - - TableCSVFormatter csvFormatter( tableLayout ); - std::ofstream outputFile( m_outputDir + "/" + regionNames[i] + ".csv" ); - outputFile << csvFormatter.headerToString(); - outputFile.close(); - } - } - } ); -} - -bool SinglePhaseStatistics::execute( real64 const time_n, - real64 const dt, - integer const GEOS_UNUSED_PARAM( cycleNumber ), - integer const GEOS_UNUSED_PARAM( eventCounter ), - real64 const GEOS_UNUSED_PARAM( eventProgress ), - DomainPartition & domain ) -{ - m_solver->forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&] ( string const &, - MeshLevel & mesh, - string_array const & regionNames ) - { - // current time is time_n + dt - computeRegionStatistics( time_n + dt, mesh, regionNames ); - } ); - return false; -} - -void SinglePhaseStatistics::computeRegionStatistics( real64 const time, - MeshLevel & mesh, - string_array const & regionNames ) const -{ - GEOS_MARK_FUNCTION; - // Step 1: initialize the average/min/max quantities - ElementRegionManager & elemManager = mesh.getElemManager(); - for( size_t i = 0; i < regionNames.size(); ++i ) - { - ElementRegionBase & region = elemManager.getRegion( regionNames[i] ); - RegionStatistics & stats = region.getReference< RegionStatistics >( viewKeyStruct::regionStatisticsString() ); - - stats.averagePressure = 0.0; - stats.maxPressure = -LvArray::NumericLimits< real64 >::max; - stats.minPressure = LvArray::NumericLimits< real64 >::max; - - stats.maxDeltaPressure = -LvArray::NumericLimits< real64 >::max; - stats.minDeltaPressure = LvArray::NumericLimits< real64 >::max; - - stats.averageTemperature = 0.0; - stats.maxTemperature = -LvArray::NumericLimits< real64 >::max; - stats.minTemperature = LvArray::NumericLimits< real64 >::max; - - stats.totalPoreVolume = 0.0; - stats.totalUncompactedPoreVolume = 0.0; - stats.totalMass = 0.0; - } - - // Step 2: increment the average/min/max quantities for all the subRegions - elemManager.forElementSubRegions( regionNames, [&]( localIndex const, - ElementSubRegionBase & subRegion ) - { - - arrayView1d< integer const > const elemGhostRank = subRegion.ghostRank(); - arrayView1d< real64 const > const volume = subRegion.getElementVolume(); - arrayView1d< real64 const > const pres = subRegion.getField< flow::pressure >(); - arrayView1d< real64 const > const deltaPres = subRegion.getField< flow::deltaPressure >(); - arrayView1d< real64 const > const temp = subRegion.getField< flow::temperature >(); - - string const & solidName = subRegion.getReference< string >( SinglePhaseBase::viewKeyStruct::solidNamesString() ); - Group const & constitutiveModels = subRegion.getGroup( ElementSubRegionBase::groupKeyStruct::constitutiveModelsString() ); - CoupledSolidBase const & solid = constitutiveModels.getGroup< CoupledSolidBase >( solidName ); - arrayView1d< real64 const > const refPorosity = solid.getReferencePorosity(); - arrayView2d< real64 const > const porosity = solid.getPorosity(); - - string const & fluidName = subRegion.template getReference< string >( FlowSolverBase::viewKeyStruct::fluidNamesString() ); - SingleFluidBase const & fluid = constitutiveModels.getGroup< SingleFluidBase >( fluidName ); - arrayView2d< real64 const, constitutive::singlefluid::USD_FLUID > const densities = fluid.density(); - - real64 subRegionAvgPresNumerator = 0.0; - real64 subRegionMinPres = 0.0; - real64 subRegionMaxPres = 0.0; - real64 subRegionMinDeltaPres = 0.0; - real64 subRegionMaxDeltaPres = 0.0; - real64 subRegionAvgTempNumerator = 0.0; - real64 subRegionMinTemp = 0.0; - real64 subRegionMaxTemp = 0.0; - real64 subRegionTotalUncompactedPoreVol = 0.0; - real64 subRegionTotalPoreVol = 0.0; - real64 subRegionTotalMass = 0.0; - - singlePhaseBaseKernels::StatisticsKernel:: - launch( subRegion.size(), - elemGhostRank, - volume, - pres, - deltaPres, - temp, - refPorosity, - porosity, - densities, - subRegionMinPres, - subRegionAvgPresNumerator, - subRegionMaxPres, - subRegionMinDeltaPres, - subRegionMaxDeltaPres, - subRegionMinTemp, - subRegionAvgTempNumerator, - subRegionMaxTemp, - subRegionTotalUncompactedPoreVol, - subRegionTotalPoreVol, - subRegionTotalMass ); - - ElementRegionBase & region = elemManager.getRegion( ElementRegionBase::getParentRegion( subRegion ).getName() ); - RegionStatistics & stats = region.getReference< RegionStatistics >( viewKeyStruct::regionStatisticsString() ); - - stats.averagePressure += subRegionAvgPresNumerator; - if( subRegionMinPres < stats.minPressure ) - { - stats.minPressure = subRegionMinPres; - } - if( subRegionMaxPres > stats.maxPressure ) - { - stats.maxPressure = subRegionMaxPres; - } - - if( subRegionMinDeltaPres < stats.minDeltaPressure ) - { - stats.minDeltaPressure = subRegionMinDeltaPres; - } - if( subRegionMaxDeltaPres > stats.maxDeltaPressure ) - { - stats.maxDeltaPressure = subRegionMaxDeltaPres; - } - - stats.averageTemperature += subRegionAvgTempNumerator; - if( subRegionMinTemp < stats.minTemperature ) - { - stats.minTemperature = subRegionMinTemp; - } - if( subRegionMaxTemp > stats.maxTemperature ) - { - stats.maxTemperature = subRegionMaxTemp; - } - - stats.totalUncompactedPoreVolume += subRegionTotalUncompactedPoreVol; - stats.totalPoreVolume += subRegionTotalPoreVol; - stats.totalMass += subRegionTotalMass; - } ); - - // Step 3: synchronize the results over the MPI ranks - for( size_t i = 0; i < regionNames.size(); ++i ) - { - ElementRegionBase & region = elemManager.getRegion( regionNames[i] ); - RegionStatistics & stats = region.getReference< RegionStatistics >( viewKeyStruct::regionStatisticsString() ); - - stats.minPressure = MpiWrapper::min( stats.minPressure ); - stats.averagePressure = MpiWrapper::sum( stats.averagePressure ); - stats.maxPressure = MpiWrapper::max( stats.maxPressure ); - - stats.minDeltaPressure = MpiWrapper::min( stats.minDeltaPressure ); - stats.maxDeltaPressure = MpiWrapper::max( stats.maxDeltaPressure ); - - stats.minTemperature = MpiWrapper::min( stats.minTemperature ); - stats.averageTemperature = MpiWrapper::sum( stats.averageTemperature ); - stats.maxTemperature = MpiWrapper::max( stats.maxTemperature ); - - stats.totalUncompactedPoreVolume = MpiWrapper::sum( stats.totalUncompactedPoreVolume ); - stats.totalPoreVolume = MpiWrapper::sum( stats.totalPoreVolume ); - stats.totalMass = MpiWrapper::sum( stats.totalMass ); - - if( stats.totalUncompactedPoreVolume > 0 ) - { - float invTotalUncompactedPoreVolume = 1.0 / stats.totalUncompactedPoreVolume; - stats.averagePressure *= invTotalUncompactedPoreVolume; - stats.averageTemperature *= invTotalUncompactedPoreVolume; - } - else - { - stats.averagePressure = 0.0; - stats.averageTemperature = 0.0; - GEOS_WARNING( GEOS_FMT( "{}: Cannot compute average pressure & temperature because region pore volume is zero.", regionNames[i] ), - getDataContext() ); - } - - string_view massUnit = units::getSymbol( m_solver->getMassUnit() ); - - if( isLogLevelActive< logInfo::Statistics >( this->getLogLevel())&& MpiWrapper::commRank() == 0 ) - { - TableData singPhaseStatsData; - singPhaseStatsData.addRow( "Pressure[Pa]", stats.minPressure, stats.averagePressure, stats.maxPressure ); - singPhaseStatsData.addRow( "Delta pressure [Pa]", stats.minDeltaPressure, "/", stats.maxDeltaPressure ); - singPhaseStatsData.addRow( "Temperature [K]", stats.minTemperature, stats.averageTemperature, stats.maxTemperature ); - singPhaseStatsData.addSeparator(); - - singPhaseStatsData.addRow( "Total dynamic pore volume [rm^3]", CellType::MergeNext, CellType::MergeNext, stats.totalPoreVolume ); - singPhaseStatsData.addSeparator(); - singPhaseStatsData.addRow( GEOS_FMT( "Total fluid mass [{}]", massUnit ), CellType::MergeNext, CellType::MergeNext, stats.totalMass ); - - string const title = GEOS_FMT( "{}, {} (time {} s):", getName(), regionNames[i], time ); - TableLayout const singPhaseStatsLayout( title, { "statistics", "min", "average", "max" } ); - TableTextFormatter tableFormatter( singPhaseStatsLayout ); - GEOS_LOG_RANK_0( tableFormatter.toString( singPhaseStatsData ) ); - - if( m_writeCSV > 0 && MpiWrapper::commRank() == 0 ) - { - std::ofstream outputFile( m_outputDir + "/" + regionNames[i] + ".csv", std::ios_base::app ); - outputFile << time << "," << stats.minPressure << "," << stats.averagePressure << "," << stats.maxPressure << "," << - stats.minDeltaPressure << "," << stats.maxDeltaPressure << "," << - stats.minTemperature << "," << stats.averageTemperature << "," << stats.maxTemperature << "," << - stats.totalPoreVolume << "," << stats.totalMass << std::endl; - outputFile.close(); - } - } - } -} - -REGISTER_CATALOG_ENTRY( TaskBase, - SinglePhaseStatistics, - string const &, dataRepository::Group * const ) - -} /* namespace geos */ diff --git a/src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseStatisticsAggregator.cpp b/src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseStatisticsAggregator.cpp new file mode 100644 index 00000000000..d5c0aad8412 --- /dev/null +++ b/src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseStatisticsAggregator.cpp @@ -0,0 +1,194 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2016-2024 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2024 TotalEnergies + * Copyright (c) 2018-2024 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2023-2024 Chevron + * Copyright (c) 2019- GEOS/GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +/** + * @file SinglePhaseStatistics.cpp + */ + +#include "SinglePhaseStatisticsAggregator.hpp" + +#include "mesh/DomainPartition.hpp" +#include "physicsSolvers/LogLevelsInfo.hpp" +#include "physicsSolvers/StatisticsAggregatorBaseHelpers.hpp" +#include "physicsSolvers/fluidFlow/SinglePhaseBase.hpp" +#include "physicsSolvers/fluidFlow/FlowSolverBaseFields.hpp" +#include "physicsSolvers/fluidFlow/SinglePhaseBaseFields.hpp" +#include "physicsSolvers/fluidFlow/kernels/singlePhase/StatisticsKernel.hpp" +#include "common/format/table/TableData.hpp" +#include "common/format/table/TableFormatter.hpp" +#include "common/format/table/TableLayout.hpp" + +namespace geos +{ + +namespace singlePhaseStatistics +{ + +using namespace constitutive; +using namespace dataRepository; + +RegionStatistics::RegionStatistics( string const & name, + dataRepository::Group * const parent, + bool const statsOutputEnabled ): + RegionStatisticsBase( name, parent, statsOutputEnabled ) +{} + +StatsAggregator::StatsAggregator( DataContext const & ownerDataContext, + dataRepository::Group & meshBodies, + bool const statsOutputEnabled ): + Base( ownerDataContext, meshBodies, statsOutputEnabled ) +{} + +void StatsAggregator::enableRegionStatisticsAggregation() +{ + auto const registerStats = [=] ( Group & parent, + string const & targetName ) -> RegionStatistics & + { + return parent.registerGroup( targetName, + std::make_unique< RegionStatistics >( targetName, + &parent, + m_statsOutputEnabled ) ); + }; + + Base::enableRegionStatisticsAggregation( registerStats ); +} + +void StatsAggregator::initStats( RegionStatistics & stats, real64 const time ) const +{ + stats.m_time = time; + + stats.m_averagePressure = 0.0; + stats.m_maxPressure = 0.0; + stats.m_minPressure = LvArray::NumericLimits< real64 >::max; + + stats.m_maxDeltaPressure = -LvArray::NumericLimits< real64 >::max; + stats.m_minDeltaPressure = LvArray::NumericLimits< real64 >::max; + + stats.m_averageTemperature = 0.0; + stats.m_maxTemperature = 0.0; + stats.m_minTemperature = LvArray::NumericLimits< real64 >::max; + + stats.m_totalDynamicPoreVolume = 0.0; + stats.m_totalUncompactedPoreVolume = 0.0; + + stats.m_totalMass = 0.0; +} + +void StatsAggregator::computeSubRegionRankStats( CellElementSubRegion & subRegion, + RegionStatistics & subRegionStats ) const +{ + static constexpr string_view solidNamesVK = SinglePhaseBase::viewKeyStruct::solidNamesString(); + static constexpr string_view fluidNamesVK = FlowSolverBase::viewKeyStruct::fluidNamesString(); + static constexpr string_view modelsVK = ElementSubRegionBase::groupKeyStruct::constitutiveModelsString(); + + arrayView1d< integer const > const elemGhostRank = subRegion.ghostRank(); + arrayView1d< real64 const > const volume = subRegion.getElementVolume(); + arrayView1d< real64 const > const pres = subRegion.getField< fields::flow::pressure >(); + arrayView1d< real64 const > const deltaPres = subRegion.getField< fields::flow::deltaPressure >(); + arrayView1d< real64 const > const temp = subRegion.getField< fields::flow::temperature >(); + + string const & solidName = subRegion.getReference< string >( string( solidNamesVK ) ); + Group const & constitutiveModels = subRegion.getGroup( string( modelsVK ) ); + CoupledSolidBase const & solid = constitutiveModels.getGroup< CoupledSolidBase >( solidName ); + arrayView1d< real64 const > const refPorosity = solid.getReferencePorosity(); + arrayView2d< real64 const > const porosity = solid.getPorosity(); + + string const & fluidName = subRegion.template getReference< string >( string( fluidNamesVK ) ); + SingleFluidBase const & fluid = constitutiveModels.getGroup< SingleFluidBase >( fluidName ); + arrayView2d< real64 const, constitutive::singlefluid::USD_FLUID > const densities = fluid.density(); + + singlePhaseBaseKernels::StatisticsKernel::launch( subRegion.size(), + elemGhostRank, + volume, + pres, + deltaPres, + temp, + refPorosity, + porosity, + densities, + subRegionStats.m_minPressure, + subRegionStats.m_averagePressure, + subRegionStats.m_maxPressure, + subRegionStats.m_minDeltaPressure, + subRegionStats.m_maxDeltaPressure, + subRegionStats.m_minTemperature, + subRegionStats.m_averageTemperature, + subRegionStats.m_maxTemperature, + subRegionStats.m_totalUncompactedPoreVolume, + subRegionStats.m_totalDynamicPoreVolume, + subRegionStats.m_totalMass ); +} + +void StatsAggregator::aggregateStats( RegionStatistics & stats, + RegionStatistics const & other ) const +{ + stats.m_averagePressure += other.m_averagePressure; + stats.m_minPressure = LvArray::math::min( stats.m_minPressure, other.m_minPressure ); + stats.m_maxPressure = LvArray::math::max( stats.m_maxPressure, other.m_maxPressure ); + + stats.m_minDeltaPressure = LvArray::math::min( stats.m_minDeltaPressure, other.m_minDeltaPressure ); + stats.m_maxDeltaPressure = LvArray::math::max( stats.m_maxDeltaPressure, other.m_maxDeltaPressure ); + + stats.m_averageTemperature += other.m_averageTemperature; + stats.m_minTemperature = LvArray::math::min( stats.m_minTemperature, other.m_minTemperature ); + stats.m_maxTemperature = LvArray::math::max( stats.m_maxTemperature, other.m_maxTemperature ); + + stats.m_totalUncompactedPoreVolume += other.m_totalUncompactedPoreVolume; + stats.m_totalDynamicPoreVolume += other.m_totalDynamicPoreVolume; + + stats.m_totalMass += other.m_totalMass; +} + +void StatsAggregator::mpiAggregateStats( RegionStatistics & stats ) const +{ + stats.m_averagePressure = MpiWrapper::sum( stats.m_averagePressure ); + stats.m_minPressure = MpiWrapper::min( stats.m_minPressure ); + stats.m_maxPressure = MpiWrapper::max( stats.m_maxPressure ); + + stats.m_minDeltaPressure = MpiWrapper::min( stats.m_minDeltaPressure ); + stats.m_maxDeltaPressure = MpiWrapper::max( stats.m_maxDeltaPressure ); + + stats.m_averageTemperature = MpiWrapper::sum( stats.m_averageTemperature ); + stats.m_minTemperature = MpiWrapper::min( stats.m_minTemperature ); + stats.m_maxTemperature = MpiWrapper::max( stats.m_maxTemperature ); + + stats.m_totalUncompactedPoreVolume = MpiWrapper::sum( stats.m_totalUncompactedPoreVolume ); + stats.m_totalDynamicPoreVolume = MpiWrapper::sum( stats.m_totalDynamicPoreVolume ); + + stats.m_totalMass = MpiWrapper::sum( stats.m_totalMass ); +} + +void StatsAggregator::postAggregateStats( RegionStatistics & stats ) +{ + if( stats.m_totalUncompactedPoreVolume > 0 ) + { + float invTotalUncompactedPoreVolume = 1.0 / stats.m_totalUncompactedPoreVolume; + stats.m_averagePressure *= invTotalUncompactedPoreVolume; + stats.m_averageTemperature *= invTotalUncompactedPoreVolume; + } + else + { + stats.m_averagePressure = 0.0; + stats.m_averageTemperature = 0.0; + m_warnings.emplace_back( GEOS_FMT( "Cannot compute average pressure for '{}' because pore volume is zero in '{}'.", + getOwnerName(), stats.getTargetName() ) ); + } +} + +} /* namespace singlePhaseStatistics */ + +template class StatsAggregatorBase< singlePhaseStatistics::StatsAggregator >; + +} /* namespace geos */ diff --git a/src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseStatisticsAggregator.hpp b/src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseStatisticsAggregator.hpp new file mode 100644 index 00000000000..630423d993a --- /dev/null +++ b/src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseStatisticsAggregator.hpp @@ -0,0 +1,203 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2016-2024 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2024 TotalEnergies + * Copyright (c) 2018-2024 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2023-2024 Chevron + * Copyright (c) 2019- GEOS/GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +/** + * @file SinglePhaseStatisticsAggregator.hpp + * @details Region statistics data is stored as follow: + + * Problem : ProblemManager + * |-> domain : DomainPartition + * |-> MeshBodies : Group + * |-> cartesianMesh : MeshBody + * |-> meshLevels : Group + * |-> Level0 : MeshLevel + * | |-> nodeManager : NodeManager + * | | |-> sets : Group + * | | | * all : Wrapper< index array > + * | | | * xneg : Wrapper< index array > + * | | [...] (other element sets) + * | | + * | |-> ElementRegions : ElementRegionManager + * | | |-> Channel : CellElementRegion + * | | | |-> cb-0_0_0 : CellElementSubRegion + * | | | | | * pressure : Wrapper< real64 array > + * | | | | | * temperature : Wrapper< real64 array > + * | | | | [...] (other fields) + * | | | | + * | | | |-> cb-0_0_1 : CellElementSubRegion + * | | | | | * pressure : Wrapper< real64 array > + * | | | | | * temperature : Wrapper< real64 array > + * | | | | [...] (other fields) + * | | | | + * | | | [...] (other sub-regions) + * | | | + * | | |-> Barrier : CellElementRegion + * | | |-> cb-1_0_0 : CellElementSubRegion + * | | |-> cb-1_0_1 : CellElementSubRegion + * | | [...] (other sub-regions) + * | | + * | [...] (other element managers) + * ____ | | + * | | |-> statistics : Group (storage for all stats) + * | | |-> flowStats : Group (storage for this instance stats) + * | | | |-> regionsStatistics : RegionStatistics (aggregate) + * | | | |-> Channel : RegionStatistics (aggregate, mpi reduced) + * | | | | |-> cb-0_0_0 : RegionStatistics (compute read-back) + * stats | | | | |-> cb-0_0_1 : RegionStatistics (compute read-back) + * data -> | | | | [...] (other sub-regions stats) + * | | | | + * | | | |-> Barrier : RegionStatistics (aggregate, mpi reduced) + * | | | |-> cb-1_0_0 : RegionStatistics (compute read-back) + * | | | |-> cb-1_0_1 : RegionStatistics (compute read-back) + * | | | [...] (other sub-regions stats) + * | | | + * |___ | [...] (other stats storages) + * | + * [...] (other discretizations) + */ + +#ifndef SRC_CORECOMPONENTS_PHYSICSSOLVERS_FLUIDFLOW_SINGLEPHASESTATISTICSAGGREGATOR_HPP_ +#define SRC_CORECOMPONENTS_PHYSICSSOLVERS_FLUIDFLOW_SINGLEPHASESTATISTICSAGGREGATOR_HPP_ + +#include "physicsSolvers/StatisticsAggregatorBase.hpp" + +namespace geos +{ + +class SinglePhaseBase; + +namespace singlePhaseStatistics +{ +class StatsAggregator; +class RegionStatistics; +} + +template<> +struct StatsAggregatorTraits< singlePhaseStatistics::StatsAggregator > +{ + using SolverType = SinglePhaseBase; + using StatsGroupType = singlePhaseStatistics::RegionStatistics; +}; + +namespace singlePhaseStatistics +{ + +/** + * @brief Output data group to contain the result of a given stat aggregator on the dataRepository. + * Attributes are public since the class is a POD. + * @todo repair 1D HDF5 outputs to enable stats HDF5 outputs + */ +class RegionStatistics : public RegionStatisticsBase +{ +public: + + /// Time of statistics computation + real64 m_time; + + /// average region pressure (numerator value before postAggregateCompute()) + real64 m_averagePressure; + /// minimum region pressure + real64 m_minPressure; + /// maximum region pressure + real64 m_maxPressure; + + /// minimum region delta pressure + real64 m_minDeltaPressure; + /// maximum region delta pressure + real64 m_maxDeltaPressure; + + /// average region temperature (numerator value before postAggregateCompute()) + real64 m_averageTemperature; + /// minimum region temperature + real64 m_minTemperature; + /// maximum region temperature + real64 m_maxTemperature; + + /// fluid mass + real64 m_totalMass; + + /// total region pore volume + real64 m_totalDynamicPoreVolume; + /// total region uncompacted pore volume (not necessarily output, useful for weighting cell pressure data) + real64 m_totalUncompactedPoreVolume; + + // TODO? -> split to struct PressureStats...MassStats: + // - optional computation of each stats + // - VKS for struct name ("pressureStats"..."massStats") + // - current RegionStatistics struct bits + + /** + * @brief Construct a new Region Statistics object + * @param targetName name of the data-repository object that is targeted by the statistics + * (mesh level / region / sub-region). + * @param parent the instance parent in data-repository + */ + RegionStatistics( string const & targetName, + dataRepository::Group * const parent, + bool statsOutputEnabled ); + + RegionStatistics( RegionStatistics && ) = default; + +}; + +/** + * @brief Reponsible of computing physical statistics over the grid, registering the result in the + * data repository, but not storing / outputing it by itself. It does not have mutable state + * except the encountered issues. + * @todo repair 1D HDF5 outputs to enable stats HDF5 outputs + */ +class StatsAggregator : public StatsAggregatorBase< StatsAggregator > +{ +public: + + using Base = StatsAggregatorBase< StatsAggregator >; + + /** + * @brief Construct a new Stats Aggregator object + * @param ownerName the unique name of the entity requesting the statistics. + * An error is thrown if not unique in this context. + * @param meshBodies The Group containing the MeshBody objects + * @param statsOutputEnabled If true, the stats are saved in the output HDF5 + * (through dataRepository::RestartFlags, but not functional for this output for now). + */ + StatsAggregator( dataRepository::DataContext const & ownerDataContext, + dataRepository::Group & meshBodies, + bool statsOutputEnabled ); + + /** + * @brief Enable the computation of region statistics, initialize data structure to collect them. + * Register the resulting data wrappers so they will be targeted by TimeHistory output + * @note Must be called in or after the "registerDataOnMesh" initialization phase + */ + void enableRegionStatisticsAggregation(); + + // template implementations + /// @cond DO_NOT_DOCUMENT + + void initStats( RegionStatistics & stats, real64 time ) const; + void computeSubRegionRankStats( CellElementSubRegion & subRegion, RegionStatistics & subRegionStats ) const; + void aggregateStats( RegionStatistics & stats, RegionStatistics const & other ) const; + void mpiAggregateStats( RegionStatistics & stats ) const; + void postAggregateStats( RegionStatistics & stats ); + + /// @endcond + +}; + +} /* namespace singlePhaseStatistics */ + +} /* namespace geos */ + +#endif /* SRC_CORECOMPONENTS_PHYSICSSOLVERS_FLUIDFLOW_SINGLEPHASESTATISTICSAGGREGATOR_HPP_ */ diff --git a/src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseStatisticsTask.cpp b/src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseStatisticsTask.cpp new file mode 100644 index 00000000000..22882918abf --- /dev/null +++ b/src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseStatisticsTask.cpp @@ -0,0 +1,278 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2016-2024 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2024 TotalEnergies + * Copyright (c) 2018-2024 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2023-2024 Chevron + * Copyright (c) 2019- GEOS/GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +/** + * @file SinglePhaseStatisticsTask.cpp + */ + +#include "SinglePhaseStatisticsTask.hpp" + +#include "physicsSolvers/LogLevelsInfo.hpp" +#include "physicsSolvers/fluidFlow/SinglePhaseBase.hpp" + +namespace geos +{ + +using namespace dataRepository; + +namespace singlePhaseStatistics +{ + +StatsTask::StatsTask( const string & name, Group * const parent ): + Base( name, parent ) +{ + addLogLevel< logInfo::Statistics >(); +} + +void StatsTask::postInputInitialization() +{ + Base::postInputInitialization(); + + GEOS_THROW_IF_EQ_MSG( m_solver, nullptr, + "To identify simulated regions, a solver must be provided.", + InputError, getWrapperDataContext( getSolverWrapperKey() ) ); + + if( !dynamicCast< SinglePhaseBase * >( m_solver ) ) + { + GEOS_THROW( "Incompatible solver selected, a single-phase solver is expected", + InputError, getDataContext() ); + } +} + +void StatsTask::registerDataOnMesh( Group & meshBodies ) +{ + // for now, this guard is needed to avoid breaking the xml schema generation + if( m_solver == nullptr ) + return; + + if( m_writeCSV || isLogLevelActive< logInfo::Statistics >( this->getLogLevel()) ) + { + // expected to work as check is done in postInputInitialization() + SinglePhaseBase * castedSolver = dynamicCast< SinglePhaseBase * >( m_solver ); + GEOS_ERROR_IF_EQ_MSG( castedSolver, nullptr, + GEOS_FMT( "{} {}: Unexpected error (solver pointer changed?)", catalogName(), getDataContext() ) ); + m_aggregator = std::make_unique< StatsAggregator >( getDataContext(), meshBodies, true ); + m_aggregator->initStatisticsAggregation( *castedSolver ); + } + else + { + GEOS_WARNING( GEOS_FMT( "{} {}: No computing option enabled, no output is scheduled.", + catalogName(), getDataContext() ) ); + } + + m_aggregator->enableRegionStatisticsAggregation(); + + m_aggregator->forRegionStatistics( [&] ( MeshLevel & mesh, RegionStatistics & ) + { + prepareLogTableLayouts( mesh.getName() ); + prepareCsvTableLayouts( mesh.getName() ); + } ); +} + +void StatsTask::prepareLogTableLayouts( string_view meshName ) +{ + // only output from rank 0 + if( MpiWrapper::commRank() != 0 ) + return; + + TableLayout const tableLayout = TableLayout() + .setTitle( GEOS_FMT( "{}: mesh {}", getName(), meshName ) ); + + m_logFormatters.emplace( meshName, std::make_unique< TableTextFormatter >( tableLayout ) ); +} + +void StatsTask::prepareCsvTableLayouts( string_view meshName ) +{ + // only output from rank 0 + if( MpiWrapper::commRank() != 0 || !m_writeCSV ) + return; + + string_view massUnit = units::getSymbol( m_solver->getMassUnit() ); + + TableLayout tableLayout( { + TableLayout::Column( GEOS_FMT( "Time [{}]", units::getSymbol( units::Unit::Time ))), + TableLayout::Column( "Region" ), // TODO : mention this change in PR description + TableLayout::Column( GEOS_FMT( "Min pressure [{}]", units::getSymbol( units::Unit::Pressure ))), + TableLayout::Column( GEOS_FMT( "Average pressure [{}]", units::getSymbol( units::Unit::Pressure )) ), + TableLayout::Column( GEOS_FMT( "Max pressure [{}]", units::getSymbol( units::Unit::Pressure ) ) ), + TableLayout::Column( GEOS_FMT( "Min delta pressure [{}]", units::getSymbol( units::Unit::Pressure ))), + TableLayout::Column( GEOS_FMT( "Max delta pressure [{}]", units::getSymbol( units::Unit::Pressure ))), + TableLayout::Column( GEOS_FMT( "Min temperature [{}]", units::getSymbol( units::Unit::Temperature ) )), + TableLayout::Column( GEOS_FMT( "Average temperature [{}]", units::getSymbol( units::Unit::Temperature ) )), + TableLayout::Column( GEOS_FMT( "Max temperature [{}]", units::getSymbol( units::Unit::Temperature ) )), + TableLayout::Column( GEOS_FMT( "Total dynamic pore volume [{}]", units::getSymbol( units::Unit::ReservoirVolume ) )), + TableLayout::Column( GEOS_FMT( "Total fluid mass [{}]", massUnit )), + } ); + + auto & csvFormatter = m_csvFormatters.get_inserted( string( meshName ) ); + csvFormatter = std::make_unique< TableCSVFormatter >( tableLayout ); + + // output CSV header + std::ofstream outputFile( getCsvFileName( meshName ) ); + outputFile << csvFormatter->headerToString(); +} + +string StatsTask::getCsvFileName( string_view meshName ) const +{ return GEOS_FMT( "{}/{}.csv", m_outputDir, meshName ); } + +bool StatsTask::execute( real64 const time_n, + real64 const dt, + integer const GEOS_UNUSED_PARAM( cycleNumber ), + integer const GEOS_UNUSED_PARAM( eventCounter ), + real64 const GEOS_UNUSED_PARAM( eventProgress ), + DomainPartition & ) +{ + // current statistics time is after solver resolution: time_n (timestep start) + dt + real64 statsTime = time_n + dt; + + GEOS_ERROR_IF( !m_aggregator, + "No statistics aggregator initialized!", getDataContext() ); + + m_aggregator->computeRegionsStatistics( statsTime ); + + m_aggregator->forRegionStatistics( [&] ( MeshLevel & mesh, RegionStatistics & meshRegionsStatistics ) + { + outputLogStats( statsTime, mesh, meshRegionsStatistics ); + outputCsvStats( statsTime, mesh, meshRegionsStatistics ); + } ); + + return false; +} + +void StatsTask::outputLogStats( real64 const statsTime, + MeshLevel & mesh, + RegionStatistics & meshRegionsStatistics ) +{ + if( MpiWrapper::commRank() > 0 || !isLogLevelActive< logInfo::Statistics >( this->getLogLevel() ) ) + return; + + auto const formatterIter = m_logFormatters.find( mesh.getName() ); + if( formatterIter==m_logFormatters.end()) + return; + + TableTextFormatter const & formatter = *formatterIter->second; + TableData tableData; + static constexpr auto merge = CellType::MergeNext; + + string_view massUnit = units::getSymbol( m_solver->getMassUnit() ); + string_view pressureUnit = units::getSymbol( units::Pressure ); + string_view tempUnit = units::getSymbol( units::Temperature ); + string_view resVolUnit = units::getSymbol( units::ReservoirVolume ); + + tableData.getErrorsList().appendErrors( m_aggregator->getWarnings() ); + + tableData.addRow( "Statistics time", merge, merge, statsTime ); + + // lamda to apply for each region statistics + auto const outputRegionStats = [&] ( string_view targetName, RegionStatistics & stats ) + { + tableData.addSeparator(); + tableData.addRow( merge, merge, merge, "" ); + tableData.addRow( merge, merge, merge, targetName ); + tableData.addSeparator(); + + tableData.addRow( "statistics", "min", "average", "max" ); + tableData.addSeparator(); + + tableData.addRow( GEOS_FMT( "Pressure [{}]", pressureUnit ), + stats.m_minPressure, stats.m_averagePressure, stats.m_maxPressure ); + tableData.addRow( GEOS_FMT( "Delta pressure [{}]", pressureUnit ), + stats.m_minDeltaPressure, "/", stats.m_maxDeltaPressure ); + tableData.addRow( GEOS_FMT( "Temperature [{}]", tempUnit ), + stats.m_minTemperature, stats.m_averageTemperature, stats.m_maxTemperature ); + + tableData.addSeparator(); + + tableData.addRow( GEOS_FMT( "Total dynamic pore volume [{}]", resVolUnit ), + "all", CellType::MergeNext, stats.m_totalDynamicPoreVolume ); + + tableData.addRow( GEOS_FMT( "Total fluid mass [{}]", massUnit ), + "all", CellType::MergeNext, stats.m_totalMass ); + }; + + // apply the output lambda for the mesh then each regions + outputRegionStats( GEOS_FMT( "Discretization '{}'", mesh.getName() ), meshRegionsStatistics ); + + m_aggregator->forRegionStatistics( mesh, meshRegionsStatistics, + [&] ( CellElementRegion & region, RegionStatistics & stats ) + { + outputRegionStats( GEOS_FMT( "Region '{}'", region.getName() ), stats ); + } ); + + // output to log + GEOS_LOG_RANK_0( formatter.toString( tableData ) ); +} + +void StatsTask::outputCsvStats( real64 statsTime, + MeshLevel & mesh, + RegionStatistics & meshRegionsStatistics ) +{ + if( MpiWrapper::commRank() > 0 || m_writeCSV == 0 ) + return; + + auto const formatterIter = m_csvFormatters.find( mesh.getName() ); + if( formatterIter==m_csvFormatters.end()) + return; + + TableCSVFormatter const & formatter = *formatterIter->second; + TableData tableData; + + stdVector< string > row; + row.reserve( formatter.getLayout().getTotalLowermostColumnCount() ); + + // lamda to apply for each region statistics + auto const outputRegionStats = [&] ( string_view targetName, RegionStatistics & stats ) + { + row.clear(); + row.insert( row.begin(), + { std::to_string( statsTime ), + string( targetName ), + std::to_string( stats.m_minPressure ), + std::to_string( stats.m_averagePressure ), + std::to_string( stats.m_maxPressure ), + std::to_string( stats.m_minDeltaPressure ), + std::to_string( stats.m_maxDeltaPressure ), + std::to_string( stats.m_minTemperature ), + std::to_string( stats.m_averageTemperature ), + std::to_string( stats.m_maxTemperature ), + std::to_string( stats.m_totalDynamicPoreVolume ), + std::to_string( stats.m_totalMass ), + } ); + + tableData.addRow( row ); + }; + + // apply the output lambda for the mesh then each regions + outputRegionStats( mesh.getName(), meshRegionsStatistics ); + + m_aggregator->forRegionStatistics( mesh, meshRegionsStatistics, + [&] ( CellElementRegion & region, RegionStatistics & stats ) + { + outputRegionStats( region.getName(), stats ); + } ); + + // append to csv file + std::ofstream outputFile( getCsvFileName( mesh.getName() ), std::ios_base::app ); + outputFile << formatter.dataToString( tableData ); + outputFile.close(); +} + +REGISTER_CATALOG_ENTRY( TaskBase, + StatsTask, + string const &, dataRepository::Group * const ) + +} /* namespace singlePhaseStatistics */ + +} /* namespace geos */ diff --git a/src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseStatistics.hpp b/src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseStatisticsTask.hpp similarity index 53% rename from src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseStatistics.hpp rename to src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseStatisticsTask.hpp index 4906790b026..c10a71a989c 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseStatistics.hpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/SinglePhaseStatisticsTask.hpp @@ -14,25 +14,27 @@ */ /** - * @file SinglePhaseStatistics.hpp + * @file SinglePhaseStatisticsTask.hpp */ #ifndef SRC_CORECOMPONENTS_PHYSICSSOLVERS_FLUIDFLOW_SINGLEPHASESTATISTICS_HPP_ #define SRC_CORECOMPONENTS_PHYSICSSOLVERS_FLUIDFLOW_SINGLEPHASESTATISTICS_HPP_ #include "physicsSolvers/FieldStatisticsBase.hpp" +#include "physicsSolvers/fluidFlow/SinglePhaseStatisticsAggregator.hpp" namespace geos { class SinglePhaseBase; +namespace singlePhaseStatistics +{ + /** - * @class SinglePhaseStatistics - * - * Task class allowing for the computation of aggregate statistics in single-phase simulations + * Task class allowing for the computation of aggregate statistics in single phase simulations */ -class SinglePhaseStatistics : public FieldStatisticsBase< SinglePhaseBase > +class StatsTask : public FieldStatisticsBase< SinglePhaseBase > { public: @@ -41,14 +43,11 @@ class SinglePhaseStatistics : public FieldStatisticsBase< SinglePhaseBase > * @param[in] name the name of the task coming from the xml * @param[in] parent the parent group of the task */ - SinglePhaseStatistics( const string & name, - Group * const parent ); + StatsTask( const string & name, dataRepository::Group * const parent ); /// Accessor for the catalog name static string catalogName() { return "SinglePhaseStatistics"; } - /// Accessor for the region statistics catalog name - static string regionStatisticsName() { return "regionStatistics"; } /** * @defgroup Tasks Interface Functions * @@ -65,64 +64,46 @@ class SinglePhaseStatistics : public FieldStatisticsBase< SinglePhaseBase > /**@}*/ + StatsAggregator & getStatisticsAggregator() + { return *m_aggregator; } - /** - * @struct viewKeyStruct holds char strings and viewKeys for fast lookup - */ - struct viewKeyStruct - { - /// String for the region statistics - constexpr static char const * regionStatisticsString() { return "regionStatistics"; } - }; - - struct RegionStatistics - { - /// average region pressure - real64 averagePressure; - /// minimum region pressure - real64 minPressure; - /// maximum region pressure - real64 maxPressure; - - /// minimum region delta pressure - real64 minDeltaPressure; - /// maximum region delta pressure - real64 maxDeltaPressure; - - // fluid mass - real64 totalMass; - - /// average region temperature - real64 averageTemperature; - /// minimum region temperature - real64 minTemperature; - /// maximum region temperature - real64 maxTemperature; - - /// total region pore volume - real64 totalPoreVolume; - /// total region uncompacted pore volume - real64 totalUncompactedPoreVolume; - }; + StatsAggregator const & getStatisticsAggregator() const + { return *m_aggregator; } private: using Base = FieldStatisticsBase< SinglePhaseBase >; - /** - * @brief Compute some statistics on the reservoir (average field pressure, etc) - * @param[in] mesh the mesh level object - * @param[in] regionNames the array of target region names - */ - void computeRegionStatistics( real64 const time, - MeshLevel & mesh, - string_array const & regionNames ) const; - + void postInputInitialization() override; void registerDataOnMesh( Group & meshBodies ) override; + void prepareLogTableLayouts( string_view tableName ); + + void prepareCsvTableLayouts( string_view tableName ); + + string getCsvFileName( string_view meshName ) const; + + void outputLogStats( real64 statsTime, + MeshLevel & mesh, + RegionStatistics & meshRegionsStatistics ); + + void outputCsvStats( real64 statsTime, + MeshLevel & mesh, + RegionStatistics & meshRegionsStatistics ); + + /// For each discretization (MeshLevel name), table formatter for log output. + stdMap< string, std::unique_ptr< TableTextFormatter > > m_logFormatters; + + /// For each discretization (MeshLevel name), table formatter for csv output. + stdMap< string, std::unique_ptr< TableCSVFormatter > > m_csvFormatters; + + // mesh statistics aggregator + std::unique_ptr< StatsAggregator > m_aggregator; + }; +} /* namespace singlePhaseStatistics */ } /* namespace geos */ diff --git a/src/coreComponents/physicsSolvers/fluidFlow/unitTests/testFlowStatistics.cpp b/src/coreComponents/physicsSolvers/fluidFlow/unitTests/testFlowStatistics.cpp index 356661e434e..e61551c70c3 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/unitTests/testFlowStatistics.cpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/unitTests/testFlowStatistics.cpp @@ -17,8 +17,13 @@ #include "integrationTests/testingUtilities/TestingTasks.hpp" #include "mainInterface/initialization.hpp" #include "mainInterface/GeosxState.hpp" +#include "physicsSolvers/fluidFlow/CompositionalMultiphaseStatisticsAggregator.hpp" +#include "physicsSolvers/fluidFlow/CompositionalMultiphaseStatisticsTask.hpp" +#include "physicsSolvers/fluidFlow/SinglePhaseBase.hpp" +#include "physicsSolvers/fluidFlow/CompositionalMultiphaseBase.hpp" +#include "physicsSolvers/fluidFlow/SinglePhaseStatisticsAggregator.hpp" #include "physicsSolvers/fluidFlow/SourceFluxStatistics.hpp" -#include "physicsSolvers/fluidFlow/SinglePhaseStatistics.hpp" +#include "physicsSolvers/fluidFlow/SinglePhaseStatisticsTask.hpp" #include @@ -46,6 +51,7 @@ struct TestInputs string timeStepFluxStatsPath; string wholeSimFluxStatsPath; string flowSolverPath; + string statsTaskPath; // rates for each timesteps, for each phases array2d< real64 > sourceRates; @@ -206,27 +212,19 @@ void setRateTable( array2d< real64 > & rateTable, std::initializer_list< std::in } } -real64 getTotalFluidMass( ProblemManager & problem, string_view flowSolverPath ) +template< typename SolverType, typename StatTaskType > +real64 getTotalFluidMass( ProblemManager & problem, + SolverType & solver, + StatTaskType const & statsTask ) { - real64 totalMass = 0.0; - PhysicsSolverBase const & solver = problem.getGroupByPath< PhysicsSolverBase >( string( flowSolverPath ) ); - solver.forDiscretizationOnMeshTargets( problem.getDomainPartition().getMeshBodies(), - [&] ( string const &, - MeshLevel & mesh, - string_array const & ) - { - mesh.getElemManager().forElementRegions( [&]( ElementRegionBase & region ) - { - SinglePhaseStatistics::RegionStatistics & regionStats = region.getReference< SinglePhaseStatistics::RegionStatistics >( - SinglePhaseStatistics::viewKeyStruct::regionStatisticsString() ); - - totalMass += regionStats.totalMass; - } ); - } ); - return totalMass; + MeshLevel & mesh = problem.getDomainPartition() + .getMeshBody( 0 ) + .getMeshLevel( solver.getDiscretizationName() ); + auto const & statsAggregator = statsTask.getStatisticsAggregator(); + auto const & stats = statsAggregator.getRegionsStatistics( mesh ); + return stats.m_totalMass; } - /** * @brief Verification that the source flux statistics are correct for the current timestep * @param expectedMasses the expected mass values per phase @@ -529,6 +527,7 @@ TestSet getTestSet() testInputs.timeStepFluxStatsPath = "/Tasks/timeStepFluxStats"; testInputs.wholeSimFluxStatsPath = "/Tasks/wholeSimFluxStats"; testInputs.flowSolverPath = "/Solvers/testSolver"; + testInputs.statsTaskPath = "/Tasks/timeStepReservoirStats"; testInputs.dt = 500.0; testInputs.sourceElementsCount = 2; @@ -565,8 +564,12 @@ TEST_F( FlowStatisticsTest, checkSinglePhaseFluxStatistics ) setupProblemFromXML( problem, testSet.inputs.xmlInput.data() ); - real64 firstMass; + SinglePhaseBase & flowSolver = + problem.getGroupByPath< SinglePhaseBase >( testSet.inputs.flowSolverPath ); + singlePhaseStatistics::StatsTask & statsTask = + problem.getGroupByPath< singlePhaseStatistics::StatsTask >( string( testSet.inputs.statsTaskPath ) ); + real64 firstMass; TimeStepChecker & timeStepChecker = problem.getGroupByPath< TimeStepChecker >( testSet.inputs.timeStepCheckerPath ); timeStepChecker.setTimeStepCheckingFunction( [&]( real64 const time_n ) { @@ -578,7 +581,7 @@ TEST_F( FlowStatisticsTest, checkSinglePhaseFluxStatistics ) if( !passedFirstTimeStep ) { passedFirstTimeStep = true; - firstMass = getTotalFluidMass( problem, testSet.inputs.flowSolverPath ); + firstMass = getTotalFluidMass( problem, flowSolver, statsTask ); } } ); @@ -589,12 +592,12 @@ TEST_F( FlowStatisticsTest, checkSinglePhaseFluxStatistics ) checkWholeSimTimeStepStats( problem, testSet, timeStepChecker ); // check singlephasestatistics results - real64 const lastMass = getTotalFluidMass( problem, testSet.inputs.flowSolverPath ); + real64 const lastMass = getTotalFluidMass( problem, flowSolver, statsTask ); real64 const massDiffTol = 1e-7; EXPECT_NEAR( lastMass - firstMass, -testSet.totalMassProd[0], massDiffTol * std::abs( testSet.totalMassProd[0] ) ) << GEOS_FMT( "{} total mass difference from start to end is not consistent with fluxes production.", - SinglePhaseStatistics::catalogName() ); + singlePhaseStatistics::StatsTask::catalogName() ); } @@ -806,6 +809,7 @@ TestSet getTestSet() testInputs.timeStepFluxStatsPath = "/Tasks/timeStepFluxStats"; testInputs.wholeSimFluxStatsPath = "/Tasks/wholeSimFluxStats"; testInputs.flowSolverPath = "/Solvers/testSolver"; + testInputs.statsTaskPath = "/Tasks/timeStepReservoirStats"; testInputs.dt = 500.0; testInputs.sourceElementsCount = 1; @@ -854,12 +858,25 @@ TEST_F( FlowStatisticsTest, checkMultiPhaseFluxStatisticsMass ) setupProblemFromXML( problem, testSet.inputs.xmlInput.data() ); + CompositionalMultiphaseBase & flowSolver = + problem.getGroupByPath< CompositionalMultiphaseBase >( testSet.inputs.flowSolverPath ); + compositionalMultiphaseStatistics::StatsTask & statsTask = + problem.getGroupByPath< compositionalMultiphaseStatistics::StatsTask >( string( testSet.inputs.statsTaskPath ) ); + + real64 firstMass; TimeStepChecker & timeStepChecker = problem.getGroupByPath< TimeStepChecker >( testSet.inputs.timeStepCheckerPath ); timeStepChecker.setTimeStepCheckingFunction( [&]( real64 const time_n ) { integer const timestepId = timeStepChecker.getTestedTimeStepCount(); checkTimeStepStats( testSet, time_n, timestepId ); checkTimeStepFluxStats( problem, testSet, time_n, timestepId ); + + static bool passedFirstTimeStep = false; + if( !passedFirstTimeStep ) + { + passedFirstTimeStep = true; + firstMass = getTotalFluidMass( problem, flowSolver, statsTask ); + } } ); // run simulation @@ -867,6 +884,14 @@ TEST_F( FlowStatisticsTest, checkMultiPhaseFluxStatisticsMass ) checkWholeSimFluxStats( problem, testSet ); checkWholeSimTimeStepStats( problem, testSet, timeStepChecker ); + + // check compositionalmultiphasestatistics results + real64 const lastMass = getTotalFluidMass( problem, flowSolver, statsTask ); + real64 const massDiffTol = 1e-5; + EXPECT_NEAR( lastMass - firstMass, + -( testSet.totalMassProd[0] + testSet.totalMassProd[1] ), + massDiffTol * std::abs( testSet.totalMassProd[0] + testSet.totalMassProd[1] ) ) << GEOS_FMT( "{} total mass difference from start to end is not consistent with fluxes production.", + singlePhaseStatistics::StatsTask::catalogName() ); } @@ -1078,6 +1103,7 @@ TestSet getTestSet() testInputs.timeStepFluxStatsPath = "/Tasks/timeStepFluxStats"; testInputs.wholeSimFluxStatsPath = "/Tasks/wholeSimFluxStats"; testInputs.flowSolverPath = "/Solvers/testSolver"; + testInputs.statsTaskPath = "/Tasks/timeStepReservoirStats"; testInputs.dt = 500.0; testInputs.sourceElementsCount = 1; @@ -1130,12 +1156,25 @@ TEST_F( FlowStatisticsTest, checkMultiPhaseFluxStatisticsMol ) setupProblemFromXML( problem, testSet.inputs.xmlInput.data() ); + CompositionalMultiphaseBase & flowSolver = + problem.getGroupByPath< CompositionalMultiphaseBase >( testSet.inputs.flowSolverPath ); + auto const & statsTask = + problem.getGroupByPath< compositionalMultiphaseStatistics::StatsTask >( string( testSet.inputs.statsTaskPath ) ); + + real64 firstMass; TimeStepChecker & timeStepChecker = problem.getGroupByPath< TimeStepChecker >( testSet.inputs.timeStepCheckerPath ); timeStepChecker.setTimeStepCheckingFunction( [&]( real64 const time_n ) { integer const timestepId = timeStepChecker.getTestedTimeStepCount(); checkTimeStepStats( testSet, time_n, timestepId ); checkTimeStepFluxStats( problem, testSet, time_n, timestepId ); + + static bool passedFirstTimeStep = false; + if( !passedFirstTimeStep ) + { + passedFirstTimeStep = true; + firstMass = getTotalFluidMass( problem, flowSolver, statsTask ); + } } ); // run simulation @@ -1143,6 +1182,14 @@ TEST_F( FlowStatisticsTest, checkMultiPhaseFluxStatisticsMol ) checkWholeSimFluxStats( problem, testSet ); checkWholeSimTimeStepStats( problem, testSet, timeStepChecker ); + + // check compositionalmultiphasestatistics results + real64 const lastMass = getTotalFluidMass( problem, flowSolver, statsTask ); + real64 const massDiffTol = 1e-5; + EXPECT_NEAR( lastMass - firstMass, + -( testSet.totalMassProd[0] + testSet.totalMassProd[1] ), + massDiffTol * std::abs( testSet.totalMassProd[0] + testSet.totalMassProd[1] ) ) << GEOS_FMT( "{} total mass difference from start to end is not consistent with fluxes production.", + singlePhaseStatistics::StatsTask::catalogName() ); } diff --git a/src/coreComponents/physicsSolvers/fluidFlow/wells/CompositionalMultiphaseWell.cpp b/src/coreComponents/physicsSolvers/fluidFlow/wells/CompositionalMultiphaseWell.cpp index 98953ad39cb..66ad4b387dd 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/wells/CompositionalMultiphaseWell.cpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/wells/CompositionalMultiphaseWell.cpp @@ -18,26 +18,38 @@ */ #include "CompositionalMultiphaseWell.hpp" - +#include "physicsSolvers/PhysicsSolverManager.hpp" +#include "LvArray/src/system.hpp" #include "codingUtilities/Utilities.hpp" #include "common/DataTypes.hpp" #include "common/TimingMacros.hpp" +#include "common/logger/Logger.hpp" #include "constitutive/ConstitutiveManager.hpp" #include "constitutive/fluid/multifluid/MultiFluidBase.hpp" #include "constitutive/fluid/multifluid/MultiFluidFields.hpp" #include "constitutive/fluid/multifluid/MultiFluidSelector.hpp" #include "dataRepository/Group.hpp" #include "mesh/DomainPartition.hpp" +#include "mesh/MeshBody.hpp" #include "mesh/PerforationFields.hpp" #include "mesh/WellElementSubRegion.hpp" #include "mesh/mpiCommunications/CommunicationTools.hpp" #include "physicsSolvers/LogLevelsInfo.hpp" +#include "physicsSolvers/fluidFlow/CompositionalMultiphaseBase.hpp" #include "physicsSolvers/fluidFlow/SolutionCheckHelpers.hpp" #include "physicsSolvers/fluidFlow/wells/LogLevelsInfo.hpp" #include "physicsSolvers/fluidFlow/wells/WellSolverBaseFields.hpp" #include "physicsSolvers/fluidFlow/wells/WellFields.hpp" #include "physicsSolvers/fluidFlow/wells/CompositionalMultiphaseWellFields.hpp" #include "physicsSolvers/fluidFlow/wells/WellControls.hpp" + +#include "physicsSolvers/fluidFlow/wells/WellInjectionConstraint.hpp" +#include "physicsSolvers/fluidFlow/wells/WellProductionConstraint.hpp" +#include "physicsSolvers/fluidFlow/wells/WellBHPConstraints.hpp" +#include "physicsSolvers/fluidFlow/wells/WellVolumeRateConstraint.hpp" +#include "physicsSolvers/fluidFlow/wells/WellPhaseVolumeRateConstraint.hpp" +#include "physicsSolvers/fluidFlow/wells/WellMassRateConstraint.hpp" + #include "physicsSolvers/fluidFlow/wells/kernels/CompositionalMultiphaseWellKernels.hpp" #include "physicsSolvers/fluidFlow/wells/kernels/ThermalCompositionalMultiphaseWellKernels.hpp" #include "physicsSolvers/fluidFlow/wells/kernels/PerforationFluxKernels.hpp" @@ -49,7 +61,16 @@ #include "physicsSolvers/fluidFlow/kernels/compositional/PhaseVolumeFractionKernel.hpp" #include "physicsSolvers/fluidFlow/kernels/compositional/ThermalPhaseVolumeFractionKernel.hpp" #include "physicsSolvers/fluidFlow/kernels/compositional/FluidUpdateKernel.hpp" -#include "physicsSolvers/fluidFlow/CompositionalMultiphaseStatistics.hpp" +#include "physicsSolvers/fluidFlow/CompositionalMultiphaseStatisticsAggregator.hpp" + +#include "physicsSolvers/fluidFlow/wells/WellBHPConstraints.hpp" + +#include "physicsSolvers/fluidFlow/wells/WellVolumeRateConstraint.hpp" +#include "physicsSolvers/fluidFlow/wells/WellPhaseVolumeRateConstraint.hpp" +#include "physicsSolvers/fluidFlow/wells/WellMassRateConstraint.hpp" +#include "physicsSolvers/fluidFlow/wells/kernels/CompositionalMultiphaseWellConstraintKernels.hpp" +#include "physicsSolvers/multiphysics/CoupledReservoirAndWellKernels.hpp" + #if defined( __INTEL_COMPILER ) #pragma GCC optimize "O0" @@ -61,25 +82,65 @@ namespace geos using namespace dataRepository; using namespace constitutive; using namespace fields; +using namespace compositionalMultiphaseStatistics; + +CompositionalMultiphaseBase & getFlowSolver( CompositionalMultiphaseWell & wellSolver ) +{ + return wellSolver.getParent().getParent().getGroup< CompositionalMultiphaseBase >( wellSolver.getFlowSolverName() ); +} + +CompositionalMultiphaseBase const & getFlowSolver( CompositionalMultiphaseWell const & wellSolver ) +{ + return wellSolver.getParent().getParent().getGroup< CompositionalMultiphaseBase >( wellSolver.getFlowSolverName() ); +} + +real64 getBHPReferenceGravityCoef( CompositionalMultiphaseWell const & wellSolver, + ConstraintSourceId const source ) +{ + real64 refGravCoef = 0.0; + bool foundConstraint = false; + + if( wellSolver.isProducer() ) + { + wellSolver.forSubGroups< MinimumBHPConstraint >( [&]( WellConstraintBase const & constraint ) + { + if( constraint.isConstraintActive() && constraint.getConstraintSource() == source ) + { + refGravCoef = static_cast< MinimumBHPConstraint const & >( constraint ).getReferenceGravityCoef(); + foundConstraint = true; + } + } ); + } + else + { + wellSolver.forSubGroups< MaximumBHPConstraint >( [&]( WellConstraintBase const & constraint ) + { + if( constraint.isConstraintActive() && constraint.getConstraintSource() == source ) + { + refGravCoef = static_cast< MaximumBHPConstraint const & >( constraint ).getReferenceGravityCoef(); + foundConstraint = true; + } + } ); + } + + GEOS_THROW_IF( !foundConstraint, + GEOS_FMT( "Could not find active BHP constraint for well {}", wellSolver.getName() ), + InputError ); + + return refGravCoef; +} CompositionalMultiphaseWell::CompositionalMultiphaseWell( const string & name, Group * const parent ) : - WellSolverBase( name, parent ), - m_useMass( false ), + WellControls( name, parent ), m_useTotalMassEquation( 1 ), m_maxCompFracChange( 1.0 ), m_maxRelativePresChange( 0.2 ), m_maxAbsolutePresChange( -1 ), // disabled by default m_minScalingFactor( 0.01 ), - m_allowCompDensChopping( 1 ), - m_targetPhaseIndex( -1 ) + m_allowCompDensChopping( 1 ) { - this->registerWrapper( viewKeyStruct::useMassFlagString(), &m_useMass ). - setApplyDefaultValue( 0 ). - setInputFlag( InputFlags::OPTIONAL ). - setDescription( "Use mass formulation instead of molar" ); - this->registerWrapper( viewKeyStruct::useTotalMassEquationString(), &m_useTotalMassEquation ). setApplyDefaultValue( 1 ). setInputFlag( InputFlags::OPTIONAL ). @@ -122,9 +183,17 @@ CompositionalMultiphaseWell::CompositionalMultiphaseWell( const string & name, setDescription( "Flag indicating whether local (cell-wise) chopping of negative compositions is allowed" ); } +CompositionalMultiphaseWell::~CompositionalMultiphaseWell() = default; + +void CompositionalMultiphaseWell::setReservoirStatsAggregator( + std::unique_ptr< compositionalMultiphaseStatistics::StatsAggregator > aggregator ) +{ + m_reservoirStatsAggregator = std::move( aggregator ); +} + void CompositionalMultiphaseWell::postInputInitialization() { - WellSolverBase::postInputInitialization(); + WellControls::postInputInitialization(); GEOS_ERROR_IF_GT_MSG( m_maxCompFracChange, 1.0, "The maximum absolute change in component fraction must smaller or equal to 1.0", @@ -136,29 +205,24 @@ void CompositionalMultiphaseWell::postInputInitialization() GEOS_ERROR_IF_LE_MSG( m_maxRelativeCompDensChange, 0.0, "The maximum relative change in component density must be larger than 0.0", getWrapperDataContext( viewKeyStruct::maxRelativeCompDensChangeString() ) ); + +} +void CompositionalMultiphaseWell::setConstitutiveNames( ElementSubRegionBase & subRegion ) const +{ + setConstitutiveName< MultiFluidBase >( subRegion, viewKeyStruct::fluidNamesString(), "multiphase fluid" ); } -void CompositionalMultiphaseWell::registerDataOnMesh( Group & meshBodies ) +void CompositionalMultiphaseWell::registerWellDataOnMesh( WellElementSubRegion & subRegion ) { - WellSolverBase::registerDataOnMesh( meshBodies ); + DomainPartition const & domain = this->getGroupByPath< DomainPartition >( "/Problem/domain" ); ConstitutiveManager const & cm = domain.getConstitutiveManager(); - - forDiscretizationOnMeshTargets( meshBodies, [&]( string const &, - MeshLevel & mesh, - string_array const & regionNames ) + setConstitutiveNames ( subRegion ); + if( m_referenceFluidModelName.empty() ) { - mesh.getElemManager().forElementSubRegions( regionNames, - [&]( localIndex const, - ElementSubRegionBase & subRegion ) - { - if( m_referenceFluidModelName.empty() ) - { - m_referenceFluidModelName = getConstitutiveName< MultiFluidBase >( subRegion ); - } - } ); - } ); + m_referenceFluidModelName = getConstitutiveName< MultiFluidBase >( subRegion ); + } // 1. Set key dimensions of the problem // Empty check needed to avoid errors when running in schema generation mode. @@ -173,131 +237,119 @@ void CompositionalMultiphaseWell::registerDataOnMesh( Group & meshBodies ) // 1 pressure + NC compositions + temp if thermal m_numDofPerResElement = isThermal() ? m_numComponents + 2 : m_numComponents + 1; - // loop over the wells - forDiscretizationOnMeshTargets( meshBodies, [&] ( string const &, - MeshLevel & mesh, - string_array const & regionNames ) + + WellControls::registerWellDataOnMesh( subRegion ); + + + + string const & fluidName = getConstitutiveName< MultiFluidBase >( subRegion ); + MultiFluidBase const & fluid = subRegion.getConstitutiveModel< MultiFluidBase >( fluidName ); + + // The resizing of the arrays needs to happen here, before the call to initializePreSubGroups, + // to make sure that the dimensions are properly set before the timeHistoryOutput starts its initialization. + subRegion.registerField< well::pressure >( getName() ); + subRegion.registerField< well::pressure_n >( getName() ); + + subRegion.registerField< well::temperature >( getName() ); + if( isThermal() ) { + subRegion.registerField< well::temperature_n >( getName() ); + } - ElementRegionManager & elemManager = mesh.getElemManager(); + subRegion.registerField< well::gravityCoefficient >( getName() ); - elemManager.forElementSubRegions< WellElementSubRegion >( regionNames, - [&]( localIndex const, - WellElementSubRegion & subRegion ) - { - string const & fluidName = getConstitutiveName< MultiFluidBase >( subRegion ); - MultiFluidBase const & fluid = subRegion.getConstitutiveModel< MultiFluidBase >( fluidName ); - - // The resizing of the arrays needs to happen here, before the call to initializePreSubGroups, - // to make sure that the dimensions are properly set before the timeHistoryOutput starts its initialization. - - subRegion.registerField< well::globalCompDensity >( getName() ). - reference().resizeDimension< 1 >( m_numComponents ); - subRegion.registerField< well::globalCompDensity_n >( getName() ). - reference().resizeDimension< 1 >( m_numComponents ); - - subRegion.registerField< well::mixtureConnectionRate >( getName() ); - subRegion.registerField< well::mixtureConnectionRate_n >( getName() ); - - subRegion.registerField< well::globalCompFraction >( getName() ). - setDimLabels( 1, fluid.componentNames() ). - reference().resizeDimension< 1 >( m_numComponents ); - subRegion.registerField< well::dGlobalCompFraction_dGlobalCompDensity >( getName() ). - reference().resizeDimension< 1, 2 >( m_numComponents, m_numComponents ); - - subRegion.registerField< well::phaseVolumeFraction >( getName() ). - setDimLabels( 1, fluid.phaseNames() ). - reference().resizeDimension< 1 >( m_numPhases ); - subRegion.registerField< well::dPhaseVolumeFraction >( getName() ). - reference().resizeDimension< 1, 2 >( m_numPhases, m_numComponents + 2 ); // dP, dT, dC - - subRegion.registerField< well::totalMassDensity >( getName() ); - subRegion.registerField< well::dTotalMassDensity >( getName() ). - reference().resizeDimension< 1 >( m_numComponents +2 ); // dP, dT, dC - - subRegion.registerField< well::phaseVolumeFraction_n >( getName() ). - reference().resizeDimension< 1 >( m_numPhases ); - - subRegion.registerField< well::pressureScalingFactor >( getName() ); - subRegion.registerField< well::temperatureScalingFactor >( getName() ); - subRegion.registerField< well::globalCompDensityScalingFactor >( getName() ); - - PerforationData & perforationData = *subRegion.getPerforationData(); - perforationData.registerField< well::compPerforationRate >( getName() ). - reference().resizeDimension< 1 >( m_numComponents ); - - perforationData.registerField< well::dCompPerforationRate >( getName() ). - reference().resizeDimension< 1, 2, 3 >( 2, m_numComponents, m_numComponents+ 2 ); - if( fluid.isThermal() ) - { - perforationData.registerField< well::energyPerforationFlux >( getName() ); - perforationData.registerField< well::dEnergyPerforationFlux >( getName() ). - reference().resizeDimension< 1, 2 >( 2, m_numComponents+2 ); - } + subRegion.registerField< well::globalCompDensity >( getName() ). + reference().resizeDimension< 1 >( m_numComponents ); + subRegion.registerField< well::globalCompDensity_n >( getName() ). + reference().resizeDimension< 1 >( m_numComponents ); - WellControls & wellControls = getWellControls( subRegion ); - wellControls.registerWrapper< real64 >( viewKeyStruct::currentBHPString() ); + subRegion.registerField< well::connectionRate >( getName() ); + subRegion.registerField< well::connectionRate_n >( getName() ); - wellControls.registerWrapper< array1d< real64 > >( viewKeyStruct::dCurrentBHPString() ). - setSizedFromParent( 0 ). - reference().resizeDimension< 0 >( m_numComponents + 2 ); // dP, dT, dC + subRegion.registerField< well::globalCompFraction >( getName() ). + setDimLabels( 1, fluid.componentNames() ). + reference().resizeDimension< 1 >( m_numComponents ); + subRegion.registerField< well::dGlobalCompFraction_dGlobalCompDensity >( getName() ). + reference().resizeDimension< 1, 2 >( m_numComponents, m_numComponents ); - wellControls.registerWrapper< array1d< real64 > >( viewKeyStruct::currentPhaseVolRateString() ). - setSizedFromParent( 0 ). - reference().resizeDimension< 0 >( m_numPhases ); + subRegion.registerField< well::phaseVolumeFraction >( getName() ). + setDimLabels( 1, fluid.phaseNames() ). + reference().resizeDimension< 1 >( m_numPhases ); + subRegion.registerField< well::dPhaseVolumeFraction >( getName() ). + reference().resizeDimension< 1, 2 >( m_numPhases, m_numComponents + 2 ); // dP, dT, dC - wellControls.registerWrapper< array2d< real64 > >( viewKeyStruct::dCurrentPhaseVolRateString() ). - setSizedFromParent( 0 ). - reference().resizeDimension< 0, 1 >( m_numPhases, m_numComponents + 3 ); // dP, dT, dC, dQ + subRegion.registerField< well::totalMassDensity >( getName() ); + subRegion.registerField< well::dTotalMassDensity >( getName() ). + reference().resizeDimension< 1 >( m_numComponents +2 ); // dP, dT, dC - wellControls.registerWrapper< real64 >( viewKeyStruct::massDensityString() ); + subRegion.registerField< well::phaseVolumeFraction_n >( getName() ). + reference().resizeDimension< 1 >( m_numPhases ); - wellControls.registerWrapper< real64 >( viewKeyStruct::currentTotalVolRateString() ); - wellControls.registerWrapper< array1d< real64 > >( viewKeyStruct::dCurrentTotalVolRateString() ). - setSizedFromParent( 0 ). - reference().resizeDimension< 0 >( m_numComponents + 3 ); // dP, dT, dC dQ + subRegion.registerField< well::pressureScalingFactor >( getName() ); + subRegion.registerField< well::temperatureScalingFactor >( getName() ); + subRegion.registerField< well::globalCompDensityScalingFactor >( getName() ); - wellControls.registerWrapper< real64 >( viewKeyStruct::massDensityString() ); + PerforationData & perforationData = *subRegion.getPerforationData(); - wellControls.registerWrapper< real64 >( viewKeyStruct::currentMassRateString() ); + perforationData.registerField< well::gravityCoefficient >( getName() ); + perforationData.registerField< well::compPerforationRate >( getName() ). + reference().resizeDimension< 1 >( m_numComponents ); - // write rates output header - // the rank that owns the reference well element is responsible - if( m_writeCSV > 0 && subRegion.isLocallyOwned() ) - { - string const fileName = GEOS_FMT( "{}/{}.csv", m_ratesOutputDir, wellControls.getName() ); - string const massUnit = m_useMass ? "kg" : "mol"; - integer const useSurfaceConditions = wellControls.useSurfaceConditions(); - string const conditionKey = useSurfaceConditions ? "surface" : "reservoir"; - string const unitKey = useSurfaceConditions ? "s" : "r"; - integer const numPhase = m_numPhases; - integer const numComp = m_numComponents; - // format: time,bhp,total_rate,total_vol_rate,phase0_vol_rate,phase1_vol_rate,... - makeDirsForPath( m_ratesOutputDir ); - GEOS_LOG( GEOS_FMT( "{}: Rates CSV generated at {}", getName(), fileName ) ); - std::ofstream outputFile( fileName ); - outputFile << "Time [s],dt[s],BHP [Pa],Total rate [" << massUnit << "/s],Total " << conditionKey << " volumetric rate [" << unitKey << "m3/s]"; - for( integer ip = 0; ip < numPhase; ++ip ) - { - outputFile << ",Phase" << ip << " " << conditionKey << " volumetric rate [" << unitKey << "m3/s]"; - } - for( integer ic = 0; ic < numComp; ++ic ) - { - outputFile << ",Component" << ic << " rate [" << massUnit << "/s]"; - } - outputFile << std::endl; - outputFile.close(); - } - } ); - } ); + perforationData.registerField< well::dCompPerforationRate >( getName() ). + reference().resizeDimension< 1, 2, 3 >( 2, m_numComponents, m_numComponents+ 2 ); + if( fluid.isThermal() ) + { + perforationData.registerField< well::energyPerforationFlux >( getName() ); + perforationData.registerField< well::dEnergyPerforationFlux >( getName() ). + reference().resizeDimension< 1, 2 >( 2, m_numComponents+2 ); + } + + registerWrapper< real64 >( viewKeyStruct::currentBHPString() ); + + registerWrapper< array1d< real64 > >( viewKeyStruct::currentPhaseVolRateString() ). + setSizedFromParent( 0 ). + reference().resizeDimension< 0 >( m_numPhases ); + + registerWrapper< real64 >( viewKeyStruct::massDensityString() ); + + registerWrapper< real64 >( viewKeyStruct::currentTotalVolRateString() ); + + registerWrapper< real64 >( viewKeyStruct::massDensityString() ); + + registerWrapper< real64 >( viewKeyStruct::currentMassRateString() ); + + // write rates output header + // the rank that owns the reference well element is responsible + if( m_writeCSV > 0 && subRegion.isLocallyOwned() ) + { + string const fileName = GEOS_FMT( "{}/{}.csv", m_ratesOutputDir, getName() ); + string const massUnit = m_useMass ? "kg" : "mol"; + integer const useSurfaceConditions = this->useSurfaceConditions(); + string const conditionKey = useSurfaceConditions ? "surface" : "reservoir"; + string const unitKey = useSurfaceConditions ? "s" : "r"; + integer const numPhase = m_numPhases; + integer const numComp = m_numComponents; + // format: time,bhp,total_rate,total_vol_rate,phase0_vol_rate,phase1_vol_rate,... + makeDirsForPath( m_ratesOutputDir ); + GEOS_LOG( GEOS_FMT( "{}: Rates CSV generated at {}", getName(), fileName ) ); + std::ofstream outputFile( fileName ); + outputFile << "Time [s],dt[s],BHP [Pa],Total rate [" << massUnit << "/s],Total " << conditionKey << " volumetric rate [" << unitKey << "m3/s]"; + for( integer ip = 0; ip < numPhase; ++ip ) + { + outputFile << ",Phase" << ip << " " << conditionKey << " volumetric rate [" << unitKey << "m3/s]"; + } + for( integer ic = 0; ic < numComp; ++ic ) + { + outputFile << ",Component" << ic << " rate [" << massUnit << "/s]"; + } + outputFile << std::endl; + outputFile.close(); + } -} -void CompositionalMultiphaseWell::setConstitutiveNames( ElementSubRegionBase & subRegion ) const -{ - setConstitutiveName< MultiFluidBase >( subRegion, viewKeyStruct::fluidNamesString(), "multiphase fluid" ); } + namespace { @@ -341,15 +393,17 @@ void compareMulticomponentModels( MODEL1_TYPE const & lhs, MODEL2_TYPE const & r * @brief Checks if the WellControls parameters are within the fluid tables ranges * @param fluid the fluid to check */ -void CompositionalMultiphaseWell::validateWellControlsForFluid( WellControls const & wellControls, - MultiFluidBase const & fluid ) const +void CompositionalMultiphaseWell::validateFluidModel( + constitutive::MultiFluidBase const & fluid, constitutive::MultiFluidBase const & referenceFluid ) const { - if( wellControls.useSurfaceConditions() ) + compareMultiphaseModels( fluid, referenceFluid ); + compareMulticomponentModels( fluid, referenceFluid ); + if( useSurfaceConditions() ) { try { - real64 const & surfaceTemp = wellControls.getSurfaceTemperature(); - real64 const & surfacePres = wellControls.getSurfacePressure(); + real64 const & surfaceTemp = getSurfaceTemperature(); + real64 const & surfacePres = getSurfacePressure(); fluid.checkTablesParameters( surfacePres, surfaceTemp ); } catch( SimulationError const & ex ) { @@ -362,223 +416,80 @@ void CompositionalMultiphaseWell::validateWellControlsForFluid( WellControls con } } -void CompositionalMultiphaseWell::validateConstitutiveModels( DomainPartition const & domain ) const -{ - GEOS_MARK_FUNCTION; - - ConstitutiveManager const & cm = domain.getConstitutiveManager(); - CompositionalMultiphaseBase const & flowSolver = getParent().getGroup< CompositionalMultiphaseBase >( getFlowSolverName() ); - string const referenceFluidName = flowSolver.referenceFluidModelName(); - MultiFluidBase const & referenceFluid = cm.getConstitutiveRelation< MultiFluidBase >( m_referenceFluidModelName ); - - forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const &, - MeshLevel const & mesh, - string_array const & regionNames ) - { - - mesh.getElemManager().forElementSubRegions< WellElementSubRegion >( regionNames, [&]( localIndex const, - WellElementSubRegion const & subRegion ) - { - string const & fluidName = subRegion.getReference< string >( viewKeyStruct::fluidNamesString() ); - MultiFluidBase const & fluid = getConstitutiveModel< MultiFluidBase >( subRegion, fluidName ); - compareMultiphaseModels( fluid, referenceFluid ); - compareMulticomponentModels( fluid, referenceFluid ); - - WellControls const & wellControls = getWellControls( subRegion ); - validateWellControlsForFluid( wellControls, fluid ); - } ); - - } ); -} - -void CompositionalMultiphaseWell::validateInjectionStreams( WellElementSubRegion const & subRegion ) const -{ - WellControls const & wellControls = getWellControls( subRegion ); - - // check well injection stream for injectors - if( wellControls.isInjector()) - { - arrayView1d< real64 const > const & injectionStream = wellControls.getInjectionStream(); - - integer const streamSize = injectionStream.size(); - GEOS_THROW_IF( ( streamSize == 0 ), - "Injection stream not specified for well ", - InputError, wellControls.getDataContext() ); - GEOS_THROW_IF( ( streamSize != m_numComponents ), - GEOS_FMT( "Injection stream for well should have {} components.", - m_numComponents ), - InputError, wellControls.getDataContext() ); - - real64 compFracSum = 0; - for( integer ic = 0; ic < m_numComponents; ++ic ) - { - real64 const compFrac = injectionStream[ic]; - GEOS_THROW_IF( ( compFrac < 0.0 ) || ( compFrac > 1.0 ), - "Invalid injection stream for well ", - InputError, wellControls.getDataContext() ); - compFracSum += compFrac; - } - GEOS_THROW_IF( ( compFracSum < 1.0 - std::numeric_limits< real64 >::epsilon() ) || - ( compFracSum > 1.0 + std::numeric_limits< real64 >::epsilon() ), - "Invalid injection stream for well ", - InputError, wellControls.getDataContext() ); - } -} void CompositionalMultiphaseWell::validateWellConstraints( real64 const & time_n, real64 const & GEOS_UNUSED_PARAM( dt ), WellElementSubRegion const & subRegion ) { - WellControls & wellControls = getWellControls( subRegion ); - if( !wellControls.useSurfaceConditions() ) - { - bool const useSeg =wellControls.referenceReservoirRegion().empty(); - GEOS_WARNING_IF( useSeg, - GEOS_FMT( "WellControls {} not set and well constraint fluid property calculations will use " - "top segement pressure and temp ", - WellControls::viewKeyStruct::referenceReservoirRegionString() ) ); - if( useSeg ) - { - wellControls.setRegionAveragePressure( -1 ); - wellControls.setRegionAverageTemperature( -1 ); - } - else - { - // Check if region name exists in list of Reservoir's target regions - string const regionName = wellControls.referenceReservoirRegion(); - CompositionalMultiphaseBase const & flowSolver = getParent().getGroup< CompositionalMultiphaseBase >( getFlowSolverName() ); - string_array const & targetRegionsNames = flowSolver.getTargetRegionNames(); - auto const pos = std::find( targetRegionsNames.begin(), targetRegionsNames.end(), regionName ); - GEOS_ERROR_IF( pos == targetRegionsNames.end(), - GEOS_FMT( "Region {} is not a target of the reservoir solver and cannot be used for referenceReservoirRegion in WellControl {}.", - regionName, wellControls.getName() ), - getDataContext() ); + GEOS_UNUSED_VAR( time_n ); + if( !useSurfaceConditions() ) + { + bool const useSegmentValues = referenceReservoirRegion().empty(); + static bool firstNoRefRegionMsg = true; + if( useSegmentValues && firstNoRefRegionMsg ) + { + GEOS_WARNING( WellControls::viewKeyStruct::referenceReservoirRegionString() << + " not set: well constraint fluid property calculations will use top segement pressure and temp ", + getDataContext() ); + firstNoRefRegionMsg = false; } } + string const & fluidName = subRegion.getReference< string >( viewKeyStruct::fluidNamesString()); MultiFluidBase const & fluid = subRegion.getConstitutiveModel< MultiFluidBase >( fluidName ); - WellControls::Control const currentControl = wellControls.getControl(); - real64 const & targetTotalRate = wellControls.getTargetTotalRate( time_n ); - real64 const & targetPhaseRate = wellControls.getTargetPhaseRate( time_n ); - real64 const & targetMassRate = wellControls.getTargetMassRate( time_n ); - - GEOS_THROW_IF( wellControls.isInjector() && currentControl == WellControls::Control::PHASEVOLRATE, - "Phase rate control is not available for injectors", - InputError, wellControls.getDataContext() ); - GEOS_THROW_IF( wellControls.isProducer() && currentControl == WellControls::Control::TOTALVOLRATE, - "Total rate control is not available for producers", - InputError, wellControls.getDataContext() ); - - GEOS_THROW_IF( wellControls.isInjector() && targetTotalRate < 0.0, - "Target total rate cannot be negative for injectors", - InputError, wellControls.getDataContext() ); - GEOS_THROW_IF( wellControls.isInjector() && !isZero( targetPhaseRate ), - "Target phase rate cannot be used for injectors", - InputError, wellControls.getDataContext() ); - GEOS_THROW_IF( wellControls.isProducer() && !isZero( targetTotalRate ), - "Target total rate cannot be used for producers", - InputError, wellControls.getDataContext() ); - GEOS_THROW_IF( wellControls.isProducer() && !isZero( targetMassRate ), - "Target mass rate cannot be used for producers", - InputError, wellControls.getDataContext() ); - GEOS_THROW_IF( !m_useMass && !isZero( targetMassRate ), - "Target mass rate cannot with useMass=0", - InputError, wellControls.getDataContext() ); - - // The user always provides positive rates, but these rates are later multiplied by -1 internally for producers - GEOS_THROW_IF( wellControls.isProducer() && targetPhaseRate > 0.0, - "Target phase rate cannot be negative for producers", - InputError, wellControls.getDataContext() ); - GEOS_THROW_IF( wellControls.isProducer() && !isZero( targetTotalRate ), - "Target total rate cannot be used for producers", - InputError, wellControls.getDataContext() ); - - // Find target phase index for phase rate constraint - for( integer ip = 0; ip < fluid.numFluidPhases(); ++ip ) - { - if( fluid.phaseNames()[ip] == wellControls.getTargetPhaseName() ) - { - m_targetPhaseIndex = ip; - } - } - GEOS_THROW_IF( wellControls.isProducer() && m_targetPhaseIndex == -1, - GEOS_FMT( "Phase {} not found", wellControls.getTargetPhaseName() ), - InputError, wellControls.getDataContext() ); + forSubGroups< InjectionConstraint< PhaseVolumeRateConstraint >, ProductionConstraint< PhaseVolumeRateConstraint > >( [&]( auto & constraint ) + { + constraint.validatePhaseType( fluid ); + } ); } void CompositionalMultiphaseWell::initializePostSubGroups() { - WellSolverBase::initializePostSubGroups(); - - DomainPartition & domain = this->getGroupByPath< DomainPartition >( "/Problem/domain" ); - - validateConstitutiveModels( domain ); - - forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const &, - MeshLevel & mesh, - string_array const & regionNames ) - { - mesh.getElemManager().forElementSubRegions< WellElementSubRegion >( regionNames, [&]( localIndex const, - WellElementSubRegion & subRegion ) - { - validateInjectionStreams( subRegion ); - } ); - } ); + WellControls::initializePostSubGroups(); } void CompositionalMultiphaseWell::initializePostInitialConditionsPreSubGroups() { - WellSolverBase::initializePostInitialConditionsPreSubGroups(); - createSeparator(); + WellControls::initializePostInitialConditionsPreSubGroups(); } -void CompositionalMultiphaseWell::postRestartInitialization() +void CompositionalMultiphaseWell::initializeWellPostInitialConditionsPreSubGroups( WellElementSubRegion & subRegion ) { - DomainPartition & domain = this->getGroupByPath< DomainPartition >( "/Problem/domain" ); - forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&] ( string const &, - MeshLevel & mesh, - string_array const & regionNames ) - { - // loop over the wells - mesh.getElemManager().forElementSubRegions< WellElementSubRegion >( regionNames, [&]( localIndex const, - WellElementSubRegion & subRegion ) - { - // setup fluid separator - WellControls & wellControls = getWellControls( subRegion ); - constitutive::MultiFluidBase & fluidSeparator = wellControls.getMultiFluidSeparator(); - fluidSeparator.allocateConstitutiveData( wellControls, 1 ); - fluidSeparator.resize( 1 ); - } ); - } ); + // set gravity coefficient + setGravCoef( subRegion, getParent().getParent().getReference< R1Tensor >( PhysicsSolverManager::viewKeyStruct::gravityVectorString() )); + + // setup fluid model + string const & fluidName = subRegion.getReference< string >( viewKeyStruct::fluidNamesString() ); + constitutive::MultiFluidBase & fluid = subRegion.getConstitutiveModel< constitutive::MultiFluidBase >( fluidName ); + fluid.setMassFlag( m_useMass ); + createSeparator( subRegion ); +} +void CompositionalMultiphaseWell::postRestartInitialization( ) +{ + + // setup fluid separator + constitutive::MultiFluidBase & fluidSeparator = getMultiFluidSeparator(); + fluidSeparator.allocateConstitutiveData( *this, 1 ); + fluidSeparator.resize( 1 ); + } -void CompositionalMultiphaseWell::createSeparator() +void CompositionalMultiphaseWell::createSeparator( WellElementSubRegion & subRegion ) { - DomainPartition & domain = this->getGroupByPath< DomainPartition >( "/Problem/domain" ); - forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&] ( string const &, - MeshLevel & mesh, - string_array const & regionNames ) - { - // loop over the wells - mesh.getElemManager().forElementSubRegions< WellElementSubRegion >( regionNames, [&]( localIndex const, - WellElementSubRegion & subRegion ) - { - string const & fluidName = subRegion.getReference< string >( viewKeyStruct::fluidNamesString() ); - MultiFluidBase & fluid = subRegion.getConstitutiveModel< MultiFluidBase >( fluidName ); - fluid.setMassFlag( m_useMass ); - // setup fluid separator - WellControls & wellControls = getWellControls( subRegion ); - string const fluidSeparatorName = wellControls.getName() + "Separator"; - std::unique_ptr< constitutive::ConstitutiveBase > fluidSeparatorPtr = fluid.deliverClone( fluidSeparatorName, &wellControls ); - fluidSeparatorPtr->allocateConstitutiveData( wellControls, 1 ); - fluidSeparatorPtr->resize( 1 ); - wellControls.setFluidSeparator( std::move( fluidSeparatorPtr )); - } ); - } ); + string const & fluidName = subRegion.getReference< string >( viewKeyStruct::fluidNamesString() ); + MultiFluidBase & fluid = subRegion.getConstitutiveModel< MultiFluidBase >( fluidName ); + fluid.setMassFlag( m_useMass ); + // setup fluid separator + string const fluidSeparatorName = getName() + "Separator"; + std::unique_ptr< constitutive::ConstitutiveBase > fluidSeparatorPtr = fluid.deliverClone( fluidSeparatorName, this ); + fluidSeparatorPtr->allocateConstitutiveData( *this, 1 ); + fluidSeparatorPtr->resize( 1 ); + setFluidSeparator( std::move( fluidSeparatorPtr )); + } void CompositionalMultiphaseWell::updateGlobalComponentFraction( WellElementSubRegion & subRegion ) const { @@ -600,70 +511,41 @@ void CompositionalMultiphaseWell::updateBHPForConstraint( WellElementSubRegion & { return; } - using Deriv = constitutive::multifluid::DerivativeOffset; - integer const numComp = m_numComponents; localIndex const iwelemRef = subRegion.getTopWellElementIndex(); - string const & fluidName = subRegion.getReference< string >( viewKeyStruct::fluidNamesString() ); - MultiFluidBase const & fluid = subRegion.getConstitutiveModel< MultiFluidBase >( fluidName ); - integer const isThermal = fluid.isThermal(); // subRegion data - arrayView1d< real64 const > const & pres = subRegion.getField< well::pressure >(); - arrayView1d< real64 > const & totalMassDens = subRegion.getField< well::totalMassDensity >(); - arrayView2d< real64, compflow::USD_FLUID_DC > const & dTotalMassDens = subRegion.getField< well::dTotalMassDensity >(); - arrayView1d< real64 const > const wellElemGravCoef = subRegion.getField< well::gravityCoefficient >(); // control data - WellControls & wellControls = getWellControls( subRegion ); - string const wellControlsName = wellControls.getName(); - real64 const & refGravCoef = wellControls.getReferenceGravityCoef(); + string const wellControlsName = getName(); + real64 const refGravCoef = getBHPReferenceGravityCoef( *this, ConstraintSourceId::USER ); real64 & currentBHP = - wellControls.getReference< real64 >( CompositionalMultiphaseWell::viewKeyStruct::currentBHPString() ); - arrayView1d< real64 > const & dCurrentBHP = - wellControls.getReference< array1d< real64 > >( CompositionalMultiphaseWell::viewKeyStruct::dCurrentBHPString() ); - - geos::internal::kernelLaunchSelectorCompThermSwitch( numComp, isThermal, [&] ( auto NC, auto ISTHERMAL ) + getReference< real64 >( CompositionalMultiphaseWell::viewKeyStruct::currentBHPString() ); + // bring everything back to host, capture the scalars by reference + forAll< serialPolicy >( 1, [ pres, + totalMassDens, + wellElemGravCoef, + ¤tBHP, + &iwelemRef, + &refGravCoef] ( localIndex const ) { - integer constexpr IS_THERMAL = ISTHERMAL(); - GEOS_UNUSED_VAR( NC ); - // bring everything back to host, capture the scalars by reference - forAll< serialPolicy >( 1, [&numComp, - pres, - totalMassDens, - dTotalMassDens, - wellElemGravCoef, - ¤tBHP, - &dCurrentBHP, - &iwelemRef, - &refGravCoef] ( localIndex const ) - { - real64 const diffGravCoef = refGravCoef - wellElemGravCoef[iwelemRef]; - currentBHP = pres[iwelemRef] + totalMassDens[iwelemRef] * diffGravCoef; - dCurrentBHP[Deriv::dP] = 1 + dTotalMassDens[iwelemRef][Deriv::dP] * diffGravCoef; - for( integer ic = 0; ic < numComp; ++ic ) - { - dCurrentBHP[Deriv::dC+ic] = dTotalMassDens[iwelemRef][Deriv::dC+ic] * diffGravCoef; - } - if constexpr ( IS_THERMAL ) - { - dCurrentBHP[Deriv::dT] = dTotalMassDens[iwelemRef][Deriv::dT] * diffGravCoef; - } - } ); + real64 const diffGravCoef = refGravCoef - wellElemGravCoef[iwelemRef]; + currentBHP = pres[iwelemRef] + totalMassDens[iwelemRef] * diffGravCoef; } ); + GEOS_LOG_LEVEL_BY_RANK( logInfo::BoundaryConditions, GEOS_FMT( "{}: BHP (at the specified reference elevation) = {} Pa", wellControlsName, currentBHP ) ); } -void CompositionalMultiphaseWell::updateVolRatesForConstraint( ElementRegionManager const & elemManager, WellElementSubRegion const & subRegion ) +void CompositionalMultiphaseWell::updateVolRatesForConstraint( WellElementSubRegion const & subRegion ) { GEOS_MARK_FUNCTION; @@ -673,251 +555,286 @@ void CompositionalMultiphaseWell::updateVolRatesForConstraint( ElementRegionMana return; } - integer constexpr maxNumComp = constitutive::MultiFluidBase::MAX_NUM_COMPONENTS; - integer const numComp = m_numComponents; integer const numPhase = m_numPhases; localIndex const iwelemRef = subRegion.getTopWellElementIndex(); - WellControls & wellControls = getWellControls( subRegion ); - // subRegion data - arrayView1d< real64 const > const & pres = subRegion.getField< well::pressure >(); - arrayView1d< real64 const > const & temp = subRegion.getField< well::temperature >(); - arrayView1d< real64 const > const & connRate = subRegion.getField< well::mixtureConnectionRate >(); - - arrayView2d< real64 const, compflow::USD_COMP > const & compFrac = subRegion.getField< well::globalCompFraction >(); - arrayView3d< real64 const, compflow::USD_COMP_DC > const & dCompFrac_dCompDens = subRegion.getField< well::dGlobalCompFraction_dGlobalCompDensity >(); + arrayView1d< real64 const > const & connRate = subRegion.getField< well::connectionRate >(); // fluid data - constitutive::MultiFluidBase & fluidSeparator = wellControls.getMultiFluidSeparator(); - integer isThermal = fluidSeparator.isThermal(); - arrayView3d< real64 const, constitutive::multifluid::USD_PHASE > const & phaseFrac = fluidSeparator.phaseFraction(); - arrayView4d< real64 const, constitutive::multifluid::USD_PHASE_DC > const & dPhaseFrac = fluidSeparator.dPhaseFraction(); + constitutive::MultiFluidBase & fluidSeparator = getMultiFluidSeparator(); + arrayView3d< real64 const, constitutive::multifluid::USD_PHASE > const & phaseFrac = fluidSeparator.phaseFraction(); arrayView2d< real64 const, constitutive::multifluid::USD_FLUID > const & totalDens = fluidSeparator.totalDensity(); - arrayView3d< real64 const, constitutive::multifluid::USD_FLUID_DC > const & dTotalDens = fluidSeparator.dTotalDensity(); - arrayView3d< real64 const, constitutive::multifluid::USD_PHASE > const & phaseDens = fluidSeparator.phaseDensity(); - arrayView4d< real64 const, constitutive::multifluid::USD_PHASE_DC > const & dPhaseDens = fluidSeparator.dPhaseDensity(); // control data - string const wellControlsName = wellControls.getName(); - bool const logSurfaceCondition = isLogLevelActive< logInfo::BoundaryConditions >( wellControls.getLogLevel()); - string const massUnit = m_useMass ? "kg" : "mol"; + string const wellControlsName = getName(); + integer const useMassCond = useMass(); + arrayView1d< real64 > const & currentPhaseVolRate = + getReference< array1d< real64 > >( viewKeyStruct::currentPhaseVolRateString() ); - integer const useSurfaceConditions = wellControls.useSurfaceConditions(); - real64 flashPressure; - real64 flashTemperature; - if( useSurfaceConditions ) - { - // use surface conditions - flashPressure = wellControls.getSurfacePressure(); - flashTemperature = wellControls.getSurfaceTemperature(); - } - else + real64 & currentTotalVolRate = + getReference< real64 >( viewKeyStruct::currentTotalVolRateString() ); + + real64 & currentMassRate = + getReference< real64 >( viewKeyStruct::currentMassRateString() ); + + real64 & massDensity = + getReference< real64 >( viewKeyStruct::massDensityString() ); + + // bring everything back to host, capture the scalars by reference + forAll< serialPolicy >( 1, [&numPhase, + connRate, + totalDens, + phaseDens, + phaseFrac, + &useMassCond, + ¤tTotalVolRate, + currentPhaseVolRate, + ¤tMassRate, + &iwelemRef, + &massDensity] ( localIndex const ) { - if( !wellControls.referenceReservoirRegion().empty() ) - { - ElementRegionBase const & region = elemManager.getRegion( wellControls.referenceReservoirRegion()); - GEOS_ERROR_IF ( !region.hasWrapper( CompositionalMultiphaseStatistics::regionStatisticsName() ), - GEOS_FMT( "WellControl {} referenceReservoirRegion field requires CompositionalMultiphaseStatistics to be configured for region {} ", - wellControls.getName(), wellControls.referenceReservoirRegion() ), - getDataContext() ); + // Step 1: update the total volume rate - CompositionalMultiphaseStatistics::RegionStatistics const & stats = region.getReference< CompositionalMultiphaseStatistics::RegionStatistics >( - CompositionalMultiphaseStatistics::regionStatisticsName() ); - wellControls.setRegionAveragePressure( stats.averagePressure ); - wellControls.setRegionAverageTemperature( stats.averageTemperature ); - GEOS_ERROR_IF( stats.averagePressure <= 0.0, - GEOS_FMT( "No region average quantities computed. WellControl {} referenceReservoirRegion field requires CompositionalMultiphaseStatistics to be configured for region {} ", - wellControls.getName(), wellControls.referenceReservoirRegion() ), - getDataContext()); - } - // If flashPressure is not set by region the value is defaulted to -1 and indicates to use top segment conditions - flashPressure = wellControls.getRegionAveragePressure(); - if( flashPressure < 0.0 ) - { - // region name not set, use segment conditions - flashPressure = pres[iwelemRef]; - flashTemperature = temp[iwelemRef]; - } - else + real64 const currentTotalRate = connRate[iwelemRef]; + if( useMassCond ) + currentMassRate = currentTotalRate; + // Step 1.1: compute the inverse of the total density and derivatives + massDensity = totalDens[iwelemRef][0]; + real64 const totalDensInv = 1.0 / totalDens[iwelemRef][0]; + + // Step 1.2: divide the total mass/molar rate by the total density to get the total volumetric rate + currentTotalVolRate = currentTotalRate * totalDensInv; + + // Step 2: update the phase volume rate + for( integer ip = 0; ip < numPhase; ++ip ) { - // use reservoir region averages - flashTemperature = wellControls.getRegionAverageTemperature(); + // Step 2.1: compute the inverse of the (phase density * phase fraction) and derivatives + + // skip the rest of this function if phase ip is absent + bool const phaseExists = (phaseFrac[iwelemRef][0][ip] > 0); + if( !phaseExists ) + { + continue; + } + + real64 const phaseDensInv = 1.0 / phaseDens[iwelemRef][0][ip]; + real64 const phaseFracTimesPhaseDensInv = phaseFrac[iwelemRef][0][ip] * phaseDensInv; + + // Step 2.2: divide the total mass/molar rate by the (phase density * phase fraction) to get the phase volumetric rate + currentPhaseVolRate[ip] = currentTotalRate * phaseFracTimesPhaseDensInv; } + } ); + +} + +void CompositionalMultiphaseWell::calculateReferenceElementRates( WellElementSubRegion & subRegion ) +{ + GEOS_MARK_FUNCTION; + + // the rank that owns the reference well element is responsible for the calculations below. + if( !subRegion.isLocallyOwned() ) + { + return; } + + integer const numPhase = m_numPhases; + localIndex const iwelemRef = subRegion.getTopWellElementIndex(); + + + + // subRegion data + arrayView1d< real64 const > const & connRate = subRegion.getField< fields::well::connectionRate >(); + + // fluid data + constitutive::MultiFluidBase & fluidSeparator = getMultiFluidSeparator(); + arrayView3d< real64 const, constitutive::multifluid::USD_PHASE > const & phaseFrac = fluidSeparator.phaseFraction(); + arrayView2d< real64 const, constitutive::multifluid::USD_FLUID > const & totalDens = fluidSeparator.totalDensity(); + arrayView3d< real64 const, constitutive::multifluid::USD_PHASE > const & phaseDens = fluidSeparator.phaseDensity(); + + // control data + string const wellControlsName = getName(); + bool const logSurfaceCondition = isLogLevelActive< logInfo::BoundaryConditions >( getLogLevel()); + string const massUnit = m_useMass ? "kg" : "mol"; + + integer const useSurfCond = useSurfaceConditions(); + integer const useMassCond = useMass(); arrayView1d< real64 > const & currentPhaseVolRate = - wellControls.getReference< array1d< real64 > >( CompositionalMultiphaseWell::viewKeyStruct::currentPhaseVolRateString() ); - arrayView2d< real64 > const & dCurrentPhaseVolRate = - wellControls.getReference< array2d< real64 > >( CompositionalMultiphaseWell::viewKeyStruct::dCurrentPhaseVolRateString() ); + getReference< array1d< real64 > >( CompositionalMultiphaseWell::viewKeyStruct::currentPhaseVolRateString() ); real64 & currentTotalVolRate = - wellControls.getReference< real64 >( CompositionalMultiphaseWell::viewKeyStruct::currentTotalVolRateString() ); + getReference< real64 >( CompositionalMultiphaseWell::viewKeyStruct::currentTotalVolRateString() ); real64 & currentMassRate = - wellControls.getReference< real64 >( CompositionalMultiphaseWell::viewKeyStruct::currentMassRateString() ); - - arrayView1d< real64 > const & dCurrentTotalVolRate = - wellControls.getReference< array1d< real64 > >( CompositionalMultiphaseWell::viewKeyStruct::dCurrentTotalVolRateString() ); + getReference< real64 >( CompositionalMultiphaseWell::viewKeyStruct::currentMassRateString() ); real64 & massDensity = - wellControls.getReference< real64 >( CompositionalMultiphaseWell::viewKeyStruct::massDensityString() ); + getReference< real64 >( CompositionalMultiphaseWell::viewKeyStruct::massDensityString() ); + constitutive::constitutiveUpdatePassThru( fluidSeparator, [&] ( auto & castedFluidSeparator ) { // typename TYPEOFREF( castedFluid ) ::KernelWrapper fluidWrapper = castedFluid.createKernelWrapper(); typename TYPEOFREF( castedFluidSeparator ) ::KernelWrapper fluidSeparatorWrapper = castedFluidSeparator.createKernelWrapper(); - geos::internal::kernelLaunchSelectorCompThermSwitch( numComp, isThermal, [&] ( auto NC, auto ISTHERMAL ) + + // bring everything back to host, capture the scalars by reference + forAll< serialPolicy >( 1, [fluidSeparatorWrapper, + &numPhase, + connRate, + totalDens, + phaseDens, + phaseFrac, + logSurfaceCondition, + &useSurfCond, + &useMassCond, + ¤tTotalVolRate, + currentPhaseVolRate, + ¤tMassRate, + &iwelemRef, + &wellControlsName, + &massUnit, + &massDensity] ( localIndex const ) { - integer constexpr NUM_COMP = NC(); - integer constexpr IS_THERMAL = ISTHERMAL(); - using COFFSET_WJ = compositionalMultiphaseWellKernels::ColOffset_WellJac< NUM_COMP, IS_THERMAL >; - // bring everything back to host, capture the scalars by reference - forAll< serialPolicy >( 1, [&numComp, - &numPhase, - fluidSeparatorWrapper, - pres, - temp, - compFrac, - dCompFrac_dCompDens, - connRate, - totalDens, - dTotalDens, - phaseDens, - dPhaseDens, - phaseFrac, - dPhaseFrac, - logSurfaceCondition, - &useSurfaceConditions, - &flashPressure, - &flashTemperature, - ¤tTotalVolRate, - dCurrentTotalVolRate, - currentPhaseVolRate, - dCurrentPhaseVolRate, - ¤tMassRate, - &iwelemRef, - &wellControlsName, - &massUnit, - &massDensity] ( localIndex const ) - { - GEOS_UNUSED_VAR( massUnit ); - using Deriv = constitutive::multifluid::DerivativeOffset; - stackArray1d< real64, maxNumComp > work( numComp ); - // Step 1: evaluate the phase and total density in the reference element - - // We need to evaluate the density as follows: - // - Surface conditions: using the surface pressure provided by the user - // - Segment conditions: using the pressure in the top element - // - Reservoir conditions: using the average region pressure - if( useSurfaceConditions ) - { - // we need to compute the surface density - fluidSeparatorWrapper.update( iwelemRef, 0, flashPressure, flashTemperature, compFrac[iwelemRef] ); - if( logSurfaceCondition ) - { - GEOS_LOG_RANK( GEOS_FMT( "{}: surface density computed with P_surface = {} Pa and T_surface = {} K", - wellControlsName, flashPressure, flashTemperature ) ); - } -#ifdef GEOS_USE_HIP - GEOS_UNUSED_VAR( wellControlsName ); -#endif + GEOS_UNUSED_VAR( massUnit ); - } - else - { - fluidSeparatorWrapper.update( iwelemRef, 0, flashPressure, flashTemperature, compFrac[iwelemRef] ); - } - // Step 2: update the total volume rate - real64 const currentTotalRate = connRate[iwelemRef]; - // Assumes useMass is true + // Step 2: update the total volume rate + + real64 const currentTotalRate = connRate[iwelemRef]; + if( useMassCond ) currentMassRate = currentTotalRate; - // Step 2.1: compute the inverse of the total density and derivatives - massDensity = totalDens[iwelemRef][0]; - real64 const totalDensInv = 1.0 / totalDens[iwelemRef][0]; + // Step 2.1: compute the inverse of the total density + massDensity = totalDens[iwelemRef][0]; + real64 const totalDensInv = 1.0 / totalDens[iwelemRef][0]; - stackArray1d< real64, maxNumComp > dTotalDensInv_dCompDens( numComp ); - for( integer ic = 0; ic < numComp; ++ic ) - { - dTotalDensInv_dCompDens[ic] = -dTotalDens[iwelemRef][0][Deriv::dC+ic] * totalDensInv * totalDensInv; - } - applyChainRuleInPlace( numComp, dCompFrac_dCompDens[iwelemRef], dTotalDensInv_dCompDens, work.data() ); - - // Step 2.2: divide the total mass/molar rate by the total density to get the total volumetric rate - currentTotalVolRate = currentTotalRate * totalDensInv; - // Compute derivatives dP dT - real64 const dTotalDensInv_dPres = -dTotalDens[iwelemRef][0][Deriv::dP] * totalDensInv * totalDensInv; - dCurrentTotalVolRate[COFFSET_WJ::dP] = ( useSurfaceConditions == 0 ) * currentTotalRate * dTotalDensInv_dPres; - if constexpr ( IS_THERMAL ) - { - dCurrentTotalVolRate[COFFSET_WJ::dT] = ( useSurfaceConditions == 0 ) * currentTotalRate * -dTotalDens[iwelemRef][0][Deriv::dT] * totalDensInv * totalDensInv; - } - if( logSurfaceCondition && useSurfaceConditions ) - { - GEOS_LOG_RANK( GEOS_FMT( "{}: total fluid density at surface conditions = {} {}/sm3, total rate = {} {}/s, total surface volumetric rate = {} sm3/s", - wellControlsName, totalDens[iwelemRef][0], massUnit, connRate[iwelemRef], massUnit, currentTotalVolRate ) ); - } + // Step 2.2: divide the total mass/molar rate by the total density to get the total volumetric rate + currentTotalVolRate = currentTotalRate * totalDensInv; - dCurrentTotalVolRate[COFFSET_WJ::dQ] = totalDensInv; - for( integer ic = 0; ic < numComp; ++ic ) - { - dCurrentTotalVolRate[COFFSET_WJ::dC+ic] = currentTotalRate * dTotalDensInv_dCompDens[ic]; - } - // Step 3: update the phase volume rate - for( integer ip = 0; ip < numPhase; ++ip ) - { - - // Step 3.1: compute the inverse of the (phase density * phase fraction) and derivatives + if( logSurfaceCondition && useSurfCond ) + { + GEOS_LOG_RANK( GEOS_FMT( "{}: total fluid density at surface conditions = {} {}/sm3, total rate = {} {}/s, total surface volumetric rate = {} sm3/s", + wellControlsName, totalDens[iwelemRef][0], massUnit, connRate[iwelemRef], massUnit, currentTotalVolRate ) ); + } - // skip the rest of this function if phase ip is absent - bool const phaseExists = (phaseFrac[iwelemRef][0][ip] > 0); - if( !phaseExists ) - { - continue; - } + // Step 3: update the phase volume rate + for( integer ip = 0; ip < numPhase; ++ip ) + { - real64 const phaseDensInv = 1.0 / phaseDens[iwelemRef][0][ip]; - real64 const phaseFracTimesPhaseDensInv = phaseFrac[iwelemRef][0][ip] * phaseDensInv; - real64 const dPhaseFracTimesPhaseDensInv_dPres = dPhaseFrac[iwelemRef][0][ip][Deriv::dP] * phaseDensInv - - dPhaseDens[iwelemRef][0][ip][Deriv::dP] * phaseFracTimesPhaseDensInv * phaseDensInv; + // Step 3.1: compute the inverse of the (phase density * phase fraction) + // skip the rest of this function if phase ip is absent + bool const phaseExists = (phaseFrac[iwelemRef][0][ip] > 0); + if( !phaseExists ) + { + continue; + } - // Step 3.2: divide the total mass/molar rate by the (phase density * phase fraction) to get the phase volumetric rate - currentPhaseVolRate[ip] = currentTotalRate * phaseFracTimesPhaseDensInv; - dCurrentPhaseVolRate[ip][COFFSET_WJ::dP] = ( useSurfaceConditions == 0 ) * currentTotalRate * dPhaseFracTimesPhaseDensInv_dPres; - dCurrentPhaseVolRate[ip][COFFSET_WJ::dQ] = phaseFracTimesPhaseDensInv; - if constexpr (IS_THERMAL ) - { - real64 const dPhaseFracTimesPhaseDensInv_dTemp = dPhaseFrac[iwelemRef][0][ip][Deriv::dT] * phaseDensInv - - dPhaseDens[iwelemRef][0][ip][Deriv::dT] * phaseFracTimesPhaseDensInv * phaseDensInv; - dCurrentPhaseVolRate[ip][COFFSET_WJ::dT] = ( useSurfaceConditions == 0 ) * currentTotalRate * dPhaseFracTimesPhaseDensInv_dTemp; - } + real64 const phaseDensInv = 1.0 / phaseDens[iwelemRef][0][ip]; + real64 const phaseFracTimesPhaseDensInv = phaseFrac[iwelemRef][0][ip] * phaseDensInv; - for( integer ic = 0; ic < numComp; ++ic ) - { - dCurrentPhaseVolRate[ip][COFFSET_WJ::dC+ic] = -phaseFracTimesPhaseDensInv * dPhaseDens[iwelemRef][0][ip][Deriv::dC+ic] * phaseDensInv; - dCurrentPhaseVolRate[ip][COFFSET_WJ::dC+ic] += dPhaseFrac[iwelemRef][0][ip][Deriv::dC+ic] * phaseDensInv; - dCurrentPhaseVolRate[ip][COFFSET_WJ::dC+ic] *= currentTotalRate; - } - applyChainRuleInPlace( numComp, dCompFrac_dCompDens[iwelemRef], &dCurrentPhaseVolRate[ip][COFFSET_WJ::dC], work.data() ); + // Step 3.2: divide the total mass/molar rate by the (phase density * phase fraction) to get the phase volumetric rate + currentPhaseVolRate[ip] = currentTotalRate * phaseFracTimesPhaseDensInv; - if( logSurfaceCondition && useSurfaceConditions ) - { - GEOS_LOG_RANK( GEOS_FMT( "{}: density of phase {} at surface conditions = {} {}/sm3, phase surface volumetric rate = {} sm3/s", - wellControlsName, ip, phaseDens[iwelemRef][0][ip], massUnit, currentPhaseVolRate[ip] ) ); - } + if( logSurfaceCondition && useSurfCond ) + { + GEOS_LOG_RANK( GEOS_FMT( "{}: density of phase {} at surface conditions = {} {}/sm3, phase surface volumetric rate = {} sm3/s", + wellControlsName, ip, phaseDens[iwelemRef][0][ip], massUnit, currentPhaseVolRate[ip] ) ); } - } ); + } } ); } ); } +void CompositionalMultiphaseWell::precomputeReferenceConditions( real64 const time_n, + Group & meshBodies, + MeshBody & meshBody, + WellElementSubRegion const & subRegion ) +{ + GEOS_UNUSED_VAR( meshBodies ); + GEOS_UNUSED_VAR( subRegion ); + if( !useSurfaceConditions() ) + { + string_view refRegionName = referenceReservoirRegion(); + bool const useSegmentValues = refRegionName.empty(); + if( useSegmentValues ) + { + setRegionAveragePressure( -1 ); + setRegionAverageTemperature( -1 ); + } + else + { + if( !m_reservoirStatsAggregator ) + { + GEOS_THROW_IF( !m_reservoirStatsAggregator, + GEOS_FMT( "Region {} stats aggregator not initialized in WellControl {}.", + refRegionName, getName() ), + InputError, getDataContext() ); + } + MeshLevel & flowMeshLevel = meshBody.getMeshLevel( getDiscretizationName() ); + RegionStatistics & stats = m_reservoirStatsAggregator->getRegionStatistics( flowMeshLevel, refRegionName ); + + // compute region stats only if needed (could have already been done for another subRegion) + if( !m_reservoirStatsAggregator->isComputed( time_n, stats ) ) + m_reservoirStatsAggregator->computeRegionsStatistics( time_n ); + GEOS_WARNING_IF( stats.m_averagePressure <= 0.0, + GEOS_FMT( "No region average quantities computed in reference region '{}'.", + referenceReservoirRegion() ), + getWrapperDataContext( WellControls::viewKeyStruct::referenceReservoirRegionString() ), + getDataContext() ); + + setRegionAveragePressure( stats.m_averagePressure ); + setRegionAverageTemperature( stats.m_averageTemperature ); + } + } +} + +CompositionalMultiphaseWell::ReferenceConditions +CompositionalMultiphaseWell::getReferenceConditions( WellElementSubRegion const & subRegion ) +{ + if( useSurfaceConditions() ) + { + // use surface conditions + return { + /* .pressure = */ getSurfacePressure(), + /* .temperature = */ getSurfaceTemperature(), + }; + } + else + { + if( getRegionAveragePressure() > 0.0 && getRegionAverageTemperature() > 0.0 ) + { // reference region condition properly computed, we can return them + return { + /* .pressure = */ getRegionAveragePressure(), + /* .temperature = */ getRegionAverageTemperature(), + }; + } + else + { // region average stats not initialized or initialized, fallback to top segment values + static bool firstNoRefRegionMsg = true; + if( firstNoRefRegionMsg ) + { + GEOS_WARNING( "CompositionalMultiphaseWell: region average statsistics of reference region not initialized," + " fallback to top segment values.", + getDataContext() ); + firstNoRefRegionMsg=false; + } + + arrayView1d< real64 const > const & pres = subRegion.getField< well::pressure >(); + arrayView1d< real64 const > const & temp = subRegion.getField< well::temperature >(); + localIndex const iwelemRef = subRegion.getTopWellElementIndex(); + return { + /* .pressure = */ pres[iwelemRef], + /* .temperature = */ temp[iwelemRef], + }; + } + } +} void CompositionalMultiphaseWell::updateFluidModel( WellElementSubRegion & subRegion ) { @@ -944,6 +861,110 @@ void CompositionalMultiphaseWell::updateFluidModel( WellElementSubRegion & subRe } +void CompositionalMultiphaseWell::updateSeparator( real64 const time_n, + MeshBody const & meshBody, + ElementRegionManager const & elemManager, + WellElementSubRegion & subRegion ) +{ + GEOS_MARK_FUNCTION; + GEOS_UNUSED_VAR( elemManager ); + + + // control data + // needs to be done because the aggregator requires all cores for mpi + string const wellControlsName = getName(); + bool const logSurfaceCondition = isLogLevelActive< logInfo::BoundaryConditions >( getLogLevel()); + integer const useSurfaceCond = useSurfaceConditions(); + + if( !useSurfaceCond ) + { + string_view refRegionName = referenceReservoirRegion(); + if( !refRegionName.empty() ) + { + MeshLevel & flowMeshLevel = meshBody.getMeshLevel( getDiscretizationName()); + RegionStatistics & stats = m_reservoirStatsAggregator->getRegionStatistics( flowMeshLevel, refRegionName ); + + // compute region stats only if needed (could have already been done for another subRegion) + if( time_n >= 0.0 && !m_reservoirStatsAggregator->isComputed( time_n, stats ) ) + { + m_reservoirStatsAggregator->computeRegionsStatistics( time_n ); + } + } + } + // the rank that owns the reference well element is responsible for the calculations below. + if( !subRegion.isLocallyOwned() ) + { + return; + } + + localIndex const iwelemRef = subRegion.getTopWellElementIndex(); + + // subRegion data + arrayView1d< real64 const > const & pres = subRegion.getField< fields::well::pressure >(); + arrayView1d< real64 const > const & temp = subRegion.getField< fields::well::temperature >(); + arrayView2d< real64 const, compflow::USD_COMP > const & compFrac = subRegion.getField< fields::well::globalCompFraction >(); + + + // fluid data + constitutive::MultiFluidBase & fluidSeparator = getMultiFluidSeparator(); + + real64 flashPressure; + real64 flashTemperature; + if( useSurfaceCond ) + { + // use surface conditions + flashPressure = getSurfacePressure(); + flashTemperature = getSurfaceTemperature(); + } + else + { + string_view refRegionName = referenceReservoirRegion(); + if( !refRegionName.empty() ) + { + MeshLevel & flowMeshLevel = meshBody.getMeshLevel( getDiscretizationName()); + RegionStatistics & stats = m_reservoirStatsAggregator->getRegionStatistics( flowMeshLevel, refRegionName ); + + setRegionAveragePressure( stats.m_averagePressure ); + setRegionAverageTemperature( stats.m_averageTemperature ); + flashPressure = stats.m_averagePressure; + flashTemperature = stats.m_averageTemperature; + } + else + { + // region name not set, use segment conditions + flashPressure = pres[iwelemRef]; + flashTemperature = temp[iwelemRef]; + } + } + + constitutive::constitutiveUpdatePassThru( fluidSeparator, [&] ( auto & castedFluidSeparator ) + { + // typename TYPEOFREF( castedFluid ) ::KernelWrapper fluidWrapper = castedFluid.createKernelWrapper(); + typename TYPEOFREF( castedFluidSeparator ) ::KernelWrapper fluidSeparatorWrapper = castedFluidSeparator.createKernelWrapper(); + // bring everything back to host, capture the scalars by reference + forAll< serialPolicy >( 1, [fluidSeparatorWrapper, + wellControlsName, + flashPressure, + flashTemperature, + logSurfaceCondition, + iwelemRef, + compFrac] ( localIndex const ) + { + fluidSeparatorWrapper.update( iwelemRef, 0, flashPressure, flashTemperature, compFrac[iwelemRef] ); + if( logSurfaceCondition ) + { + GEOS_LOG_RANK( GEOS_FMT( "{}: separator density computed with P = {} Pa and T = {} K", + wellControlsName, flashPressure, flashTemperature ) ); + } +#ifdef GEOS_USE_HIP + GEOS_UNUSED_VAR( wellControlsName ); +#endif + + } ); + } ); +} + + real64 CompositionalMultiphaseWell::updatePhaseVolumeFraction( WellElementSubRegion & subRegion ) const { GEOS_MARK_FUNCTION; @@ -992,184 +1013,285 @@ void CompositionalMultiphaseWell::updateTotalMassDensity( WellElementSubRegion & } -void CompositionalMultiphaseWell::updateState( DomainPartition & domain ) +real64 CompositionalMultiphaseWell::updateWellState( MeshBody const & meshBody, ElementRegionManager const & elemManager, WellElementSubRegion & subRegion ) { GEOS_MARK_FUNCTION; - real64 maxPhaseVolFrac = 0.0; - forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const &, - MeshLevel & mesh, - string_array const & regionNames ) - { - ElementRegionManager & elemManager = mesh.getElemManager(); - elemManager.forElementSubRegions< WellElementSubRegion >( regionNames, [&]( localIndex const, - WellElementSubRegion & subRegion ) - { - WellControls & wellControls = getWellControls( subRegion ); - if( wellControls.getWellStatus() == WellControls::Status::OPEN ) - { - real64 const maxRegionPhaseVolFrac = updateSubRegionState( elemManager, subRegion ); - maxPhaseVolFrac = LvArray::math::max( maxRegionPhaseVolFrac, maxPhaseVolFrac ); - } - } ); - } ); - maxPhaseVolFrac = MpiWrapper::max( maxPhaseVolFrac ); - GEOS_LOG_LEVEL_RANK_0( logInfo::Solution, - GEOS_FMT( " {}: Max well phase volume fraction change = {}", - getName(), fmt::format( "{:.{}f}", maxPhaseVolFrac, 4 ) ) ); + real64 maxPhaseVolFrac = updateSubRegionState( -1.0, meshBody, elemManager, subRegion ); + return maxPhaseVolFrac; } -real64 CompositionalMultiphaseWell::updateSubRegionState( ElementRegionManager const & elemManager, WellElementSubRegion & subRegion ) +real64 CompositionalMultiphaseWell::updateSubRegionState( real64 const time_n, + MeshBody const & meshBody, + ElementRegionManager const & elemManager, + WellElementSubRegion & subRegion ) { - // update properties - updateGlobalComponentFraction( subRegion ); + real64 maxPhaseVolChange=0.0; + + if( getWellState()) + { + + // update properties + updateGlobalComponentFraction( subRegion ); + + // update densities, phase fractions, phase volume fractions + + updateFluidModel( subRegion ); // Calculate fluid properties + + updateSeparator( time_n, meshBody, elemManager, subRegion ); // Calculate fluid properties at control conditions + + updateVolRatesForConstraint( subRegion ); // remove tjb ?? + + maxPhaseVolChange = updatePhaseVolumeFraction( subRegion ); + updateTotalMassDensity( subRegion ); - // update volumetric rates for the well constraints - // note: this must be called before updateFluidModel - updateVolRatesForConstraint( elemManager, subRegion ); + // Calculate the reference element rates + calculateReferenceElementRates( subRegion ); - // update densities, phase fractions, phase volume fractions + // update the current BHP + updateBHPForConstraint( subRegion ); - updateFluidModel( subRegion ); // Calculate fluid properties; - real64 maxPhaseVolChange = updatePhaseVolumeFraction( subRegion ); - updateTotalMassDensity( subRegion ); - // update the current BHP pressure - updateBHPForConstraint( subRegion ); + // Broad case the updated well state to other ranks + real64 & currentBHP = + getReference< real64 >( CompositionalMultiphaseWell::viewKeyStruct::currentBHPString() ); + array1d< real64 > currentPhaseVolRate = + getReference< array1d< real64 > >( CompositionalMultiphaseWell::viewKeyStruct::currentPhaseVolRateString() ); + real64 & currentTotalVolRate = + getReference< real64 >( CompositionalMultiphaseWell::viewKeyStruct::currentTotalVolRateString() ); + real64 & currentMassRate = + getReference< real64 >( CompositionalMultiphaseWell::viewKeyStruct::currentMassRateString() ); + integer topRank = + subRegion.getReference< integer >( WellElementSubRegion::viewKeyStruct::topRankString() ); + MpiWrapper::broadcast( currentBHP, topRank ); + MpiWrapper::bcast( currentPhaseVolRate.data(), LvArray::integerConversion< int >( currentPhaseVolRate.size() ), topRank ); + MpiWrapper::broadcast( currentTotalVolRate, topRank ); + MpiWrapper::broadcast( currentMassRate, topRank ); + if( !subRegion.isLocallyOwned() ) + { + getReference< array1d< real64 > >( + CompositionalMultiphaseWell::viewKeyStruct::currentPhaseVolRateString() ) =currentPhaseVolRate; + + } + + WellConstraintBase * constraint = getCurrentConstraint(); + if( constraint != nullptr ) + { + constraint->setBHP ( getReference< real64 >( CompositionalMultiphaseWell::viewKeyStruct::currentBHPString() )); + constraint->setPhaseVolumeRates ( getReference< array1d< real64 > >( + CompositionalMultiphaseWell::viewKeyStruct::currentPhaseVolRateString() ) ); + constraint->setTotalVolumeRate ( getReference< real64 >( + CompositionalMultiphaseWell::viewKeyStruct::currentTotalVolRateString() )); + constraint->setMassRate( getReference< real64 >( CompositionalMultiphaseWell::viewKeyStruct::currentMassRateString() )); + } + + } return maxPhaseVolChange; } -void CompositionalMultiphaseWell::initializeWells( DomainPartition & domain, real64 const & time_n ) + +void CompositionalMultiphaseWell::initializeWell( DomainPartition & domain, Group & meshBodies, string const & meshBodyName, MeshLevel & mesh, WellElementSubRegion & subRegion, real64 const & time_n ) { GEOS_MARK_FUNCTION; - + GEOS_UNUSED_VAR( domain ); integer const numComp = m_numComponents; integer const numPhase = m_numPhases; - // TODO: change the way we access the flowSolver here - CompositionalMultiphaseBase const & flowSolver = getParent().getGroup< CompositionalMultiphaseBase >( getFlowSolverName() ); - // loop over the wells - forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&] ( string const &, - MeshLevel & mesh, - string_array const & regionNames ) - { + // TODO: change the way we access the flowSolver here + ElementRegionManager const & elemManager = mesh.getElemManager(); - ElementRegionManager & elemManager = mesh.getElemManager(); - compositionalMultiphaseWellKernels::PresTempCompFracInitializationKernel::CompFlowAccessors - resCompFlowAccessors( mesh.getElemManager(), flowSolver.getName() ); - compositionalMultiphaseWellKernels::PresTempCompFracInitializationKernel::MultiFluidAccessors - resMultiFluidAccessors( mesh.getElemManager(), flowSolver.getName() ); + compositionalMultiphaseWellKernels::PresTempCompFracInitializationKernel::CompFlowAccessors + resCompFlowAccessors( elemManager, getFlowSolverName() ); + compositionalMultiphaseWellKernels::PresTempCompFracInitializationKernel::MultiFluidAccessors + resMultiFluidAccessors( elemManager, getFlowSolverName() ); - elemManager.forElementSubRegions< WellElementSubRegion >( regionNames, - [&]( localIndex const, - WellElementSubRegion & subRegion ) - { - WellControls & wellControls = getWellControls( subRegion ); - PerforationData const & perforationData = *subRegion.getPerforationData(); - arrayView2d< real64 const > const compPerfRate = perforationData.getField< fields::well::compPerforationRate >(); + PerforationData const & perforationData = *subRegion.getPerforationData(); + arrayView2d< real64 const > const compPerfRate = perforationData.getField< fields::well::compPerforationRate >(); - bool const hasNonZeroRate = MpiWrapper::max< integer >( hasNonZero( compPerfRate )); + bool const hasNonZeroRate = MpiWrapper::max< integer >( hasNonZero( compPerfRate )); - if( wellControls.isWellOpen() && !hasNonZeroRate ) + if( time_n <= 0.0 || ( isWellOpen( ) && !hasNonZeroRate ) ) + { + setWellState( true ); + if( getCurrentConstraint() == nullptr ) + { + // tjb needed for backward compatibility. and these 2 lists must be consistent + ConstraintTypeId inputControl = ConstraintTypeId( getInputControl()); + if( isProducer() ) { - // get well primary variables on well elements - arrayView1d< real64 > const & wellElemPressure = subRegion.getField< well::pressure >(); - arrayView1d< real64 > const & wellElemTemp = subRegion.getField< well::temperature >(); - arrayView2d< real64, compflow::USD_COMP > const & wellElemCompDens = subRegion.getField< well::globalCompDensity >(); - arrayView1d< real64 > const & connRate = subRegion.getField< well::mixtureConnectionRate >(); - - // get the info stored on well elements - arrayView2d< real64, compflow::USD_COMP > const & wellElemCompFrac = subRegion.getField< well::globalCompFraction >(); - arrayView1d< real64 const > const & wellElemGravCoef = subRegion.getField< well::gravityCoefficient >(); - - // get the element region, subregion, index - arrayView1d< localIndex const > const resElementRegion = perforationData.getField< perforation::reservoirElementRegion >(); - arrayView1d< localIndex const > const resElementSubRegion = perforationData.getField< perforation::reservoirElementSubRegion >(); - arrayView1d< localIndex const > const resElementIndex = perforationData.getField< perforation::reservoirElementIndex >(); - - arrayView1d< real64 const > const & perfGravCoef = perforationData.getField< fields::well::gravityCoefficient >(); - arrayView1d< integer const > const & perfStatus = perforationData.getField< fields::perforation::perforationStatus >(); - - // 1) Loop over all perforations to compute an average mixture density and component fraction - // 2) Initialize the reference pressure - // 3) Estimate the pressures in the well elements using the average density - compositionalMultiphaseWellKernels:: - PresTempCompFracInitializationKernel:: - launch( perforationData.size(), - subRegion.size(), - numComp, - numPhase, - wellControls, - 0.0, // initialization done at t = 0 - resCompFlowAccessors.get( flow::pressure{} ), - resCompFlowAccessors.get( flow::temperature{} ), - resCompFlowAccessors.get( flow::globalCompDensity{} ), - resCompFlowAccessors.get( flow::phaseVolumeFraction{} ), - resMultiFluidAccessors.get( fields::multifluid::phaseMassDensity{} ), - resElementRegion, - resElementSubRegion, - resElementIndex, - perfGravCoef, - perfStatus, - wellElemGravCoef, - wellElemPressure, - wellElemTemp, - wellElemCompFrac ); - - // get well secondary variables on well elements - string const & fluidName = subRegion.getReference< string >( viewKeyStruct::fluidNamesString() ); - MultiFluidBase & fluid = getConstitutiveModel< MultiFluidBase >( subRegion, fluidName ); - arrayView3d< real64 const, constitutive::multifluid::USD_PHASE > const & wellElemPhaseDens = fluid.phaseDensity(); - arrayView2d< real64 const, constitutive::multifluid::USD_FLUID > const & wellElemTotalDens = fluid.totalDensity(); - - // 4) Back calculate component densities - constitutive::constitutiveUpdatePassThru( fluid, [&] ( auto & castedFluid ) + forSubGroups< MinimumBHPConstraint, ProductionConstraint< VolumeRateConstraint >, ProductionConstraint< MassRateConstraint >, + ProductionConstraint< PhaseVolumeRateConstraint > >( [&]( auto & constraint ) { - using FluidType = TYPEOFREF( castedFluid ); - typename FluidType::KernelWrapper fluidWrapper = castedFluid.createKernelWrapper(); - using KernelType = thermalCompositionalMultiphaseBaseKernels::FluidUpdateKernel< serialPolicy, FluidType >; - KernelType::launch( subRegion.size(), - fluidWrapper, - wellElemPressure, - wellElemTemp, - wellElemCompFrac ); + if( constraint.getControl() == inputControl ) + { + setCurrentConstraint( &constraint ); + } } ); + } + else + { - compositionalMultiphaseWellKernels:: - CompDensInitializationKernel::launch( subRegion.size(), - numComp, - wellElemCompFrac, - wellElemTotalDens, - wellElemCompDens ); - - // 5) Recompute the pressure-dependent properties - updateSubRegionState( elemManager, subRegion ); - - // 6) Estimate the well rates - // TODO: initialize rates using perforation rates - compositionalMultiphaseWellKernels:: - RateInitializationKernel:: - launch( subRegion.size(), - m_targetPhaseIndex, - wellControls, - time_n, // initialization done at time_n - wellElemPhaseDens, - wellElemTotalDens, - connRate ); + forSubGroups< MaximumBHPConstraint, InjectionConstraint< VolumeRateConstraint >, InjectionConstraint< MassRateConstraint >, + InjectionConstraint< PhaseVolumeRateConstraint > >( [&]( auto & constraint ) + { + if( constraint.getControl() == inputControl ) + { + setCurrentConstraint( &constraint ); + } + } ); } + } + // get well primary variables on well elements + arrayView1d< real64 > const & wellElemPressure = subRegion.getField< well::pressure >(); + arrayView1d< real64 > const & wellElemTemp = subRegion.getField< well::temperature >(); + arrayView2d< real64, compflow::USD_COMP > const & wellElemCompDens = subRegion.getField< well::globalCompDensity >(); + arrayView1d< real64 > const & connRate = subRegion.getField< well::connectionRate >(); + + // get the info stored on well elements + arrayView2d< real64, compflow::USD_COMP > const & wellElemCompFrac = subRegion.getField< well::globalCompFraction >(); + arrayView1d< real64 const > const & wellElemGravCoef = subRegion.getField< well::gravityCoefficient >(); + + // get the element region, subregion, index + arrayView1d< localIndex const > const resElementRegion = perforationData.getField< perforation::reservoirElementRegion >(); + arrayView1d< localIndex const > const resElementSubRegion = perforationData.getField< perforation::reservoirElementSubRegion >(); + arrayView1d< localIndex const > const resElementIndex = perforationData.getField< perforation::reservoirElementIndex >(); + + arrayView1d< real64 const > const & perfGravCoef = perforationData.getField< fields::well::gravityCoefficient >(); + arrayView1d< integer const > const & perfStatus = perforationData.getField< fields::perforation::perforationStatus >(); + real64 const refWellElemGravCoef = getBHPReferenceGravityCoef( *this, ConstraintSourceId::USER ); + // 1) Loop over all perforations to compute an average mixture density and component fraction + // 2) Initialize the reference pressure + // 3) Estimate the pressures in the well elements using the average density + compositionalMultiphaseWellKernels:: + PresTempCompFracInitializationKernel:: + launch( perforationData.size(), + subRegion.size(), + numComp, + numPhase, + *this, + refWellElemGravCoef, + 0.0, // initialization done at t = 0 + resCompFlowAccessors.get( flow::pressure{} ), + resCompFlowAccessors.get( flow::temperature{} ), + resCompFlowAccessors.get( flow::globalCompDensity{} ), + resCompFlowAccessors.get( flow::phaseVolumeFraction{} ), + resMultiFluidAccessors.get( fields::multifluid::phaseMassDensity{} ), + resElementRegion, + resElementSubRegion, + resElementIndex, + perfGravCoef, + perfStatus, + wellElemGravCoef, + wellElemPressure, + wellElemTemp, + wellElemCompFrac ); + + // get well secondary variables on well elements + string const & fluidName = subRegion.getReference< string >( viewKeyStruct::fluidNamesString() ); + MultiFluidBase & fluid = getConstitutiveModel< MultiFluidBase >( subRegion, fluidName ); + arrayView3d< real64 const, constitutive::multifluid::USD_PHASE > const & wellElemPhaseDens = fluid.phaseDensity(); + arrayView2d< real64 const, constitutive::multifluid::USD_FLUID > const & wellElemTotalDens = fluid.totalDensity(); + + // 4) Back calculate component densities + constitutive::constitutiveUpdatePassThru( fluid, [&] ( auto & castedFluid ) + { + using FluidType = TYPEOFREF( castedFluid ); + typename FluidType::KernelWrapper fluidWrapper = castedFluid.createKernelWrapper(); + using KernelType = thermalCompositionalMultiphaseBaseKernels::FluidUpdateKernel< serialPolicy, FluidType >; + KernelType::launch( subRegion.size(), + fluidWrapper, + wellElemPressure, + wellElemTemp, + wellElemCompFrac ); } ); - } ); + compositionalMultiphaseWellKernels:: + CompDensInitializationKernel::launch( subRegion.size(), + numComp, + wellElemCompFrac, + wellElemTotalDens, + wellElemCompDens ); + + // 5) Recompute the pressure-dependent properties + MeshBody & meshBody = domain.getMeshBody( meshBodyName ); + precomputeReferenceConditions( time_n, meshBodies, meshBody, subRegion ); + updateSubRegionState( time_n, meshBody, elemManager, subRegion ); + + // 6) Estimate the well rates + // TODO: initialize rates using perforation rates + compositionalMultiphaseWellKernels:: + RateInitializationKernel:: + launch( subRegion.size(), + *this, + time_n, // initialization done at time_n + wellElemPhaseDens, + wellElemTotalDens, + connRate ); + + updateVolRatesForConstraint( subRegion ); + // Since this is a well manager class the rates need to be pushed into the WellControls class, which represnets the well + WellConstraintBase * constraint = getCurrentConstraint(); + constraint->setBHP ( getReference< real64 >( CompositionalMultiphaseWell::viewKeyStruct::currentBHPString() )); + constraint->setPhaseVolumeRates ( getReference< array1d< real64 > >( + CompositionalMultiphaseWell::viewKeyStruct::currentPhaseVolRateString() ) ); + constraint->setTotalVolumeRate ( getReference< real64 >( + CompositionalMultiphaseWell::viewKeyStruct::currentTotalVolRateString() )); + constraint->setMassRate( getReference< real64 >( CompositionalMultiphaseWell::viewKeyStruct::currentMassRateString() )); + // 7) Copy well / fluid dofs to "prop"_n variables + saveState( subRegion ); + } + else if( !hasNonZeroRate ) + { + setWellState( false ); + } + else + { + setWellState( true ); + // setup if restart + if( getCurrentConstraint() == nullptr ) + { + if( isProducer() ) + { + forSubGroups< MinimumBHPConstraint, + ProductionConstraint< VolumeRateConstraint >, + ProductionConstraint< MassRateConstraint >, + ProductionConstraint< PhaseVolumeRateConstraint > >( [&]( auto & constraint ) + { + if( ConstraintTypeId( getControl()) == constraint.getControl() ) + { + setCurrentConstraint( &constraint ); + } + } ); + } + else + { + forSubGroups< MaximumBHPConstraint, + InjectionConstraint< VolumeRateConstraint >, + InjectionConstraint< MassRateConstraint >, + InjectionConstraint< PhaseVolumeRateConstraint > >( [&]( auto & constraint ) + { + if( ConstraintTypeId( getControl()) == constraint.getControl() ) + { + setCurrentConstraint( &constraint ); + } + } ); + } + updateSubRegionState( time_n, domain.getMeshBody( meshBodyName ), elemManager, subRegion ); + } + + } } -void CompositionalMultiphaseWell::assembleFluxTerms( real64 const & time, - real64 const & dt, - DomainPartition & domain, - DofManager const & dofManager, - CRSMatrixView< real64, globalIndex const > const & localMatrix, - arrayView1d< real64 > const & localRhs ) + + +void CompositionalMultiphaseWell::assembleWellFluxTerms( real64 const & time, + real64 const & dt, + WellElementSubRegion const & subRegion, + DofManager const & dofManager, + CRSMatrixView< real64, globalIndex const > const & localMatrix, + arrayView1d< real64 > const & localRhs ) { GEOS_MARK_FUNCTION; GEOS_UNUSED_VAR( time ); @@ -1179,62 +1301,55 @@ void CompositionalMultiphaseWell::assembleFluxTerms( real64 const & time, kernelFlags.set( isothermalCompositionalMultiphaseBaseKernels::KernelFlags::TotalMassEquation ); string const wellDofKey = dofManager.getKey( wellElementDofName()); - forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const &, - MeshLevel & mesh, - string_array const & regionNames ) + + + if( isWellOpen( ) && !m_keepVariablesConstantDuringInitStep ) { - mesh.getElemManager().forElementSubRegions< WellElementSubRegion >( regionNames, - [&]( localIndex const, - WellElementSubRegion & subRegion ) + string const & fluidName = subRegion.getReference< string >( viewKeyStruct::fluidNamesString()); + MultiFluidBase const & fluid = getConstitutiveModel< MultiFluidBase >( subRegion, fluidName ); + int numComponents = fluid.numFluidComponents(); + + if( isThermal() ) { - WellControls const & well_controls = getWellControls( subRegion ); - if( well_controls.getWellStatus() == WellControls::Status::OPEN && !m_keepVariablesConstantDuringInitStep ) - { - string const & fluidName = subRegion.getReference< string >( viewKeyStruct::fluidNamesString()); - MultiFluidBase const & fluid = getConstitutiveModel< MultiFluidBase >( subRegion, fluidName ); - int const numComponents = fluid.numFluidComponents(); + thermalCompositionalMultiphaseWellKernels:: + FaceBasedAssemblyKernelFactory:: + createAndLaunch< parallelDevicePolicy<> >( numComponents, + dt, + dofManager.rankOffset(), + kernelFlags, + wellDofKey, + *this, + subRegion, + fluid, + localMatrix, + localRhs ); + } + else + { + compositionalMultiphaseWellKernels:: + FaceBasedAssemblyKernelFactory:: + createAndLaunch< parallelDevicePolicy<> >( numComponents, + dt, + dofManager.rankOffset(), + kernelFlags, + wellDofKey, + *this, + subRegion, + localMatrix, + localRhs ); + } + } - if( isThermal() ) - { - thermalCompositionalMultiphaseWellKernels:: - FaceBasedAssemblyKernelFactory:: - createAndLaunch< parallelDevicePolicy<> >( numComponents, - dt, - dofManager.rankOffset(), - kernelFlags, - wellDofKey, - well_controls, - subRegion, - fluid, - localMatrix, - localRhs ); - } - else - { - compositionalMultiphaseWellKernels:: - FaceBasedAssemblyKernelFactory:: - createAndLaunch< parallelDevicePolicy<> >( numComponents, - dt, - dofManager.rankOffset(), - kernelFlags, - wellDofKey, - well_controls, - subRegion, - localMatrix, - localRhs ); - } - } - } ); - } ); } -void CompositionalMultiphaseWell::assembleAccumulationTerms( real64 const & time, - real64 const & dt, - DomainPartition & domain, - DofManager const & dofManager, - CRSMatrixView< real64, globalIndex const > const & localMatrix, - arrayView1d< real64 > const & localRhs ) + +void CompositionalMultiphaseWell::assembleWellAccumulationTerms( real64 const & time, + real64 const & dt, + WellElementSubRegion & subRegion, + DofManager const & dofManager, + CRSMatrixView< real64, globalIndex const > const & localMatrix, + arrayView1d< real64 > const & localRhs ) { GEOS_MARK_FUNCTION; GEOS_UNUSED_VAR( time ); @@ -1245,123 +1360,126 @@ void CompositionalMultiphaseWell::assembleAccumulationTerms( real64 const & time kernelFlags.set( isothermalCompositionalMultiphaseBaseKernels::KernelFlags::TotalMassEquation ); string const wellDofKey = dofManager.getKey( wellElementDofName() ); - forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const &, - MeshLevel & mesh, - string_array const & regionNames ) + + string const & fluidName = subRegion.getReference< string >( viewKeyStruct::fluidNamesString()); + MultiFluidBase const & fluid = getConstitutiveModel< MultiFluidBase >( subRegion, fluidName ); + integer const numPhases = fluid.numFluidPhases(); + integer const numComponents = fluid.numFluidComponents(); + + if( getWellStatus() == WellControls::Status::OPEN && !m_keepVariablesConstantDuringInitStep ) { - mesh.getElemManager().forElementSubRegions< WellElementSubRegion >( regionNames, - [&]( localIndex const, - WellElementSubRegion & subRegion ) + if( isThermal() ) + { + + thermalCompositionalMultiphaseWellKernels:: + ElementBasedAssemblyKernelFactory:: + createAndLaunch< parallelDevicePolicy<> >( numComponents, + numPhases, + thermalEffectsEnabled(), + isProducer(), + dofManager.rankOffset(), + kernelFlags, + wellDofKey, + subRegion, + fluid, + localMatrix, + localRhs ); + } + else { - string const & fluidName = subRegion.getReference< string >( viewKeyStruct::fluidNamesString()); - MultiFluidBase const & fluid = getConstitutiveModel< MultiFluidBase >( subRegion, fluidName ); - integer const numPhases = fluid.numFluidPhases(); - integer const numComponents = fluid.numFluidComponents(); - WellControls const & wellControls = getWellControls( subRegion ); - if( wellControls.getWellStatus() == WellControls::Status::OPEN && !m_keepVariablesConstantDuringInitStep ) + compositionalMultiphaseWellKernels:: + ElementBasedAssemblyKernelFactory:: + createAndLaunch< parallelDevicePolicy<> >( numComponents, + numPhases, + thermalEffectsEnabled(), + isProducer(), + dofManager.rankOffset(), + kernelFlags, + wellDofKey, + subRegion, + fluid, + localMatrix, + localRhs ); + } + // get the degrees of freedom and ghosting info + arrayView1d< globalIndex const > const & wellElemDofNumber = + subRegion.getReference< array1d< globalIndex > >( wellDofKey ); + arrayView1d< integer const > const wellElemGhostRank = subRegion.ghostRank(); + arrayView1d< integer const > const elemStatus = subRegion.getLocalWellElementStatus(); + arrayView1d< real64 > const mixConnRate = subRegion.getField< fields::well::connectionRate >(); + localIndex rank_offset = dofManager.rankOffset(); + forAll< parallelDevicePolicy<> >( subRegion.size(), [=] GEOS_HOST_DEVICE ( localIndex const ei ) + { + if( wellElemGhostRank[ei] < 0 ) { - if( isThermal() ) + if( elemStatus[ei]==WellElementSubRegion::WellElemStatus::CLOSED ) { + mixConnRate[ei] = 0.0; + globalIndex const dofIndex = wellElemDofNumber[ei]; + localIndex const localRow = dofIndex - rank_offset; - thermalCompositionalMultiphaseWellKernels:: - ElementBasedAssemblyKernelFactory:: - createAndLaunch< parallelDevicePolicy<> >( numComponents, - numPhases, - wellControls.isProducer(), - dofManager.rankOffset(), - kernelFlags, - wellDofKey, - subRegion, - fluid, - localMatrix, - localRhs ); - } - else - { - compositionalMultiphaseWellKernels:: - ElementBasedAssemblyKernelFactory:: - createAndLaunch< parallelDevicePolicy<> >( numComponents, - numPhases, - wellControls.isProducer(), - dofManager.rankOffset(), - kernelFlags, - wellDofKey, - subRegion, - fluid, - localMatrix, - localRhs ); - } - // get the degrees of freedom and ghosting info - arrayView1d< globalIndex const > const & wellElemDofNumber = - subRegion.getReference< array1d< globalIndex > >( wellDofKey ); - arrayView1d< integer const > const wellElemGhostRank = subRegion.ghostRank(); - arrayView1d< integer const > const elemStatus = subRegion.getLocalWellElementStatus(); - arrayView1d< real64 > const mixConnRate = subRegion.getField< fields::well::mixtureConnectionRate >(); - localIndex rank_offset = dofManager.rankOffset(); - forAll< parallelDevicePolicy<> >( subRegion.size(), [=] GEOS_HOST_DEVICE ( localIndex const ei ) - { - if( wellElemGhostRank[ei] < 0 ) + real64 const unity = 1.0; + for( integer i=0; i < m_numDofPerWellElement; i++ ) { - if( elemStatus[ei]==WellElementSubRegion::WellElemStatus::CLOSED ) - { - mixConnRate[ei] = 0.0; - globalIndex const dofIndex = wellElemDofNumber[ei]; - localIndex const localRow = dofIndex - rank_offset; - - real64 const unity = 1.0; - for( integer i=0; i < m_numDofPerWellElement; i++ ) - { - globalIndex const rindex = localRow+i; - globalIndex const cindex =dofIndex + i; - localMatrix.template addToRow< serialAtomic >( rindex, - &cindex, - &unity, - 1 ); - localRhs[rindex] = 0.0; - } - } + globalIndex const rindex = localRow+i; + globalIndex const cindex =dofIndex + i; + localMatrix.template addToRow< serialAtomic >( rindex, + &cindex, + &unity, + 1 ); + localRhs[rindex] = 0.0; } - } ); + } } - else + } ); + + } + else + { + arrayView1d< globalIndex const > const & wellElemDofNumber = + subRegion.getReference< array1d< globalIndex > >( wellDofKey ); + arrayView1d< integer const > const wellElemGhostRank = subRegion.ghostRank(); + + arrayView1d< real64 > mixConnRate = subRegion.getField< fields::well::connectionRate >(); + localIndex rank_offset = dofManager.rankOffset(); + forAll< parallelDevicePolicy<> >( subRegion.size(), [=] GEOS_HOST_DEVICE ( localIndex const ei ) + { + if( wellElemGhostRank[ei] < 0 ) { - // get the degrees of freedom and ghosting info - arrayView1d< globalIndex const > const & wellElemDofNumber = - subRegion.getReference< array1d< globalIndex > >( wellDofKey ); - arrayView1d< integer const > const & wellElemGhostRank = subRegion.ghostRank(); - localIndex rank_offset = dofManager.rankOffset(); - forAll< parallelDevicePolicy<> >( subRegion.size(), [=] GEOS_HOST_DEVICE ( localIndex const ei ) + mixConnRate[ei] = 0.0; + globalIndex const dofIndex = wellElemDofNumber[ei]; + localIndex const localRow = dofIndex - rank_offset; + + real64 const unity = 1.0; + for( integer i=0; i < m_numDofPerWellElement; i++ ) { - if( wellElemGhostRank[ei] < 0 ) - { - globalIndex const dofIndex = wellElemDofNumber[ei]; - localIndex const localRow = dofIndex - rank_offset; - - real64 unity = 1.0; - for( integer i=0; i < m_numDofPerWellElement; i++ ) - { - globalIndex const rindex = localRow+i; - globalIndex const cindex =dofIndex + i; - localMatrix.template addToRow< serialAtomic >( rindex, - &cindex, - &unity, - 1 ); - localRhs[rindex] = 0.0; - } - } - } ); + globalIndex const rindex = localRow+i; + globalIndex const cindex =dofIndex + i; + localMatrix.template addToRow< serialAtomic >( rindex, + &cindex, + &unity, + 1 ); + localRhs[rindex] = 0.0; + } } - } ); // forElementSubRegions - } ); // forDiscretizationOnMeshTargets + } ); + // zero out current state constraint quantities + getReference< real64 >( CompositionalMultiphaseWell::viewKeyStruct::currentBHPString() )=0.0; + getReference< array1d< real64 > >( + CompositionalMultiphaseWell::viewKeyStruct::currentPhaseVolRateString() ).zero(); + getReference< real64 >( + CompositionalMultiphaseWell::viewKeyStruct::currentTotalVolRateString() )=0.0; + getReference< real64 >( CompositionalMultiphaseWell::viewKeyStruct::currentMassRateString() )=0.0; + } } - -real64 -CompositionalMultiphaseWell::calculateResidualNorm( real64 const & time_n, - real64 const & dt, - DomainPartition const & domain, - DofManager const & dofManager, - arrayView1d< real64 const > const & localRhs ) +array1d< real64 > +CompositionalMultiphaseWell::calculateLocalWellResidualNorm( real64 const & time_n, + real64 const & dt, + NonlinearSolverParameters const & nonlinearSolverParameters, + WellElementSubRegion const & subRegion, + DofManager const & dofManager, + arrayView1d< real64 const > const & localRhs ) { GEOS_MARK_FUNCTION; @@ -1372,93 +1490,126 @@ CompositionalMultiphaseWell::calculateResidualNorm( real64 const & time_n, globalIndex const rankOffset = dofManager.rankOffset(); string const wellDofKey = dofManager.getKey( wellElementDofName() ); - forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&] ( string const &, - MeshLevel const & mesh, - string_array const & regionNames ) + + + string const & fluidName = subRegion.getReference< string >( viewKeyStruct::fluidNamesString() ); + MultiFluidBase const & fluid = subRegion.getConstitutiveModel< MultiFluidBase >( fluidName ); + + + // step 1: compute the norm in the subRegion + + if( !isWellOpen() ) + { + for( integer i = 0; i < numNorm; ++i ) + { + localResidualNormalizer[i] = nonlinearSolverParameters.m_minNormalizer; + } + } + else if( isThermal() ) { + real64 subRegionResidualNorm[2]{}; + thermalCompositionalMultiphaseWellKernels::ResidualNormKernelFactory:: + createAndLaunch< parallelDevicePolicy<> >( m_numComponents, + rankOffset, + wellDofKey, + localRhs, + subRegion, + fluid, + *this, + time_n, + dt, + nonlinearSolverParameters.m_minNormalizer, + subRegionResidualNorm ); + // step 2: reduction across meshBodies/regions/subRegions + + for( integer i=0; i localResidualNorm[i] ) + { + localResidualNorm[i] = subRegionResidualNorm[i]; + } + } - ElementRegionManager const & elemManager = mesh.getElemManager(); + } + else + { + real64 subRegionResidualNorm[1]{}; + compositionalMultiphaseWellKernels::ResidualNormKernelFactory:: + createAndLaunch< parallelDevicePolicy<> >( m_numComponents, + m_numDofPerWellElement, + rankOffset, + wellDofKey, + localRhs, + subRegion, + fluid, + *this, + time_n, + dt, + nonlinearSolverParameters.m_minNormalizer, + subRegionResidualNorm ); - elemManager.forElementSubRegions< WellElementSubRegion >( regionNames, - [&]( localIndex const, - WellElementSubRegion const & subRegion ) + + + // step 2: reduction across meshBodies/regions/subRegions + + if( subRegionResidualNorm[0] > localResidualNorm[0] ) { + localResidualNorm[0] = subRegionResidualNorm[0]; + } + } + return localResidualNorm; - string const & fluidName = subRegion.getReference< string >( viewKeyStruct::fluidNamesString() ); - MultiFluidBase const & fluid = subRegion.getConstitutiveModel< MultiFluidBase >( fluidName ); +} - WellControls const & wellControls = getWellControls( subRegion ); +real64 +CompositionalMultiphaseWell::calculateWellResidualNorm( real64 const & time_n, + real64 const & dt, + NonlinearSolverParameters const & nonlinearSolverParameters, + WellElementSubRegion const & subRegion, + DofManager const & dofManager, + arrayView1d< real64 const > const & localRhs ) +{ + GEOS_MARK_FUNCTION; + integer numNorm = 1; // mass balance + array1d< real64 > localResidualNorm; + array1d< real64 > localResidualNormalizer; - // step 1: compute the norm in the subRegion + if( isThermal() ) + { + numNorm = 2; // mass balance and energy balance + } + localResidualNorm.resize( numNorm ); + localResidualNormalizer.resize( numNorm ); - if( !wellControls.isWellOpen() ) - { - for( integer i = 0; i < numNorm; ++i ) - { - localResidualNorm[i] = 0.0; - localResidualNormalizer[i] = m_nonlinearSolverParameters.m_minNormalizer; - } - } - else if( isThermal() ) - { - real64 subRegionResidualNorm[2]{}; - - thermalCompositionalMultiphaseWellKernels::ResidualNormKernelFactory:: - createAndLaunch< parallelDevicePolicy<> >( m_numComponents, - m_targetPhaseIndex, - rankOffset, - wellDofKey, - localRhs, - subRegion, - fluid, - wellControls, - time_n, - dt, - m_nonlinearSolverParameters.m_minNormalizer, - subRegionResidualNorm ); - // step 2: reduction across meshBodies/regions/subRegions - - for( integer i=0; i localResidualNorm[i] ) - { - localResidualNorm[i] = subRegionResidualNorm[i]; - } - } - } - else - { - real64 subRegionResidualNorm[1]{}; - compositionalMultiphaseWellKernels::ResidualNormKernelFactory:: - createAndLaunch< parallelDevicePolicy<> >( m_numComponents, - numDofPerWellElement(), - m_targetPhaseIndex, - rankOffset, - wellDofKey, - localRhs, - subRegion, - fluid, - wellControls, - time_n, - dt, - m_nonlinearSolverParameters.m_minNormalizer, - subRegionResidualNorm ); - - - - // step 2: reduction across meshBodies/regions/subRegions - - if( subRegionResidualNorm[0] > localResidualNorm[0] ) - { - localResidualNorm[0] = subRegionResidualNorm[0]; - } - } - } ); - } ); + //globalIndex const rankOffset = dofManager.rankOffset(); + string const wellDofKey = dofManager.getKey( wellElementDofName() ); + + + //string const & fluidName = subRegion.getReference< string >( viewKeyStruct::fluidNamesString() ); + //MultiFluidBase const & fluid = subRegion.getConstitutiveModel< MultiFluidBase >( fluidName ); + + if( isWellOpen( ) ) + { + localResidualNorm = calculateLocalWellResidualNorm( time_n, + dt, + nonlinearSolverParameters, + subRegion, + dofManager, + localRhs ); + + } + else + { + for( integer i=0; i const & localSolution ) +CompositionalMultiphaseWell::scalingForLocalSystemSolution( WellElementSubRegion & subRegion, + DofManager const & dofManager, + real64 & maxDeltaPres, + real64 & maxDeltaCompDens, + real64 & maxDeltaTemp, + real64 & minPresScalingFactor, + real64 & minCompDensScalingFactor, + real64 & minTempScalingFactor, + arrayView1d< real64 const > const & localSolution ) { GEOS_MARK_FUNCTION; string const wellDofKey = dofManager.getKey( wellElementDofName() ); real64 scalingFactor = 1.0; - real64 maxDeltaPres = 0.0, maxDeltaCompDens = 0.0, maxDeltaTemp = 0.0; - real64 minPresScalingFactor = 1.0, minCompDensScalingFactor = 1.0, minTempScalingFactor = 1.0; - - forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const &, - MeshLevel & mesh, - string_array const & regionNames ) - { - mesh.getElemManager().forElementSubRegions( regionNames, - [&]( localIndex const, - ElementSubRegionBase & subRegion ) - { - arrayView1d< real64 const > const pressure = subRegion.getField< well::pressure >(); - arrayView1d< real64 const > const temperature = subRegion.getField< well::temperature >(); - arrayView2d< real64 const, compflow::USD_COMP > const compDens = subRegion.getField< well::globalCompDensity >(); - arrayView1d< real64 > pressureScalingFactor = subRegion.getField< well::pressureScalingFactor >(); - arrayView1d< real64 > temperatureScalingFactor = subRegion.getField< well::temperatureScalingFactor >(); - arrayView1d< real64 > compDensScalingFactor = subRegion.getField< well::globalCompDensityScalingFactor >(); - const integer temperatureOffset = m_numComponents+2; - auto const subRegionData = - m_isThermal + maxDeltaPres = 0.0; + maxDeltaCompDens = 0.0; + maxDeltaTemp = 0.0; + minPresScalingFactor = 1.0; + minCompDensScalingFactor = 1.0; + minTempScalingFactor = 1.0; + + arrayView1d< real64 const > const pressure = subRegion.getField< fields::well::pressure >(); + arrayView1d< real64 const > const temperature = subRegion.getField< fields::well::temperature >(); + arrayView2d< real64 const, compflow::USD_COMP > const compDens = subRegion.getField< fields::well::globalCompDensity >(); + arrayView1d< real64 > pressureScalingFactor = subRegion.getField< fields::well::pressureScalingFactor >(); + arrayView1d< real64 > temperatureScalingFactor = subRegion.getField< fields::well::temperatureScalingFactor >(); + arrayView1d< real64 > compDensScalingFactor = subRegion.getField< fields::well::globalCompDensityScalingFactor >(); + const integer temperatureOffset = m_numComponents+2; + auto const subRegionData = + m_isThermal ? thermalCompositionalMultiphaseBaseKernels:: - SolutionScalingKernelFactory:: - createAndLaunch< parallelDevicePolicy<> >( m_maxRelativePresChange, - m_maxAbsolutePresChange, - m_maxRelativeTempChange, - m_maxCompFracChange, - m_maxRelativeCompDensChange, - pressure, - temperature, - compDens, - pressureScalingFactor, - compDensScalingFactor, - temperatureScalingFactor, - dofManager.rankOffset(), - m_numComponents, - wellDofKey, - subRegion, - localSolution, - temperatureOffset ) + SolutionScalingKernelFactory:: + createAndLaunch< parallelDevicePolicy<> >( m_maxRelativePresChange, + m_maxAbsolutePresChange, + m_maxRelativeTempChange, + m_maxCompFracChange, + m_maxRelativeCompDensChange, + pressure, + temperature, + compDens, + pressureScalingFactor, + compDensScalingFactor, + temperatureScalingFactor, + dofManager.rankOffset(), + m_numComponents, + wellDofKey, + subRegion, + localSolution, + temperatureOffset ) : isothermalCompositionalMultiphaseBaseKernels:: - SolutionScalingKernelFactory:: - createAndLaunch< parallelDevicePolicy<> >( m_maxRelativePresChange, - m_maxAbsolutePresChange, - m_maxCompFracChange, - m_maxRelativeCompDensChange, - pressure, - compDens, - pressureScalingFactor, - compDensScalingFactor, - dofManager.rankOffset(), - m_numComponents, - wellDofKey, - subRegion, - localSolution ); - - - scalingFactor = std::min( subRegionData.localMinVal, scalingFactor ); - - maxDeltaPres = std::max( maxDeltaPres, subRegionData.localMaxDeltaPres ); - maxDeltaCompDens = std::max( maxDeltaCompDens, subRegionData.localMaxDeltaCompDens ); - maxDeltaTemp = std::max( maxDeltaTemp, subRegionData.localMaxDeltaTemp ); - minPresScalingFactor = std::min( minPresScalingFactor, subRegionData.localMinPresScalingFactor ); - minCompDensScalingFactor = std::min( minCompDensScalingFactor, subRegionData.localMinCompDensScalingFactor ); - minTempScalingFactor = std::min( minTempScalingFactor, subRegionData.localMinTempScalingFactor ); - } ); - } ); + SolutionScalingKernelFactory:: + createAndLaunch< parallelDevicePolicy<> >( m_maxRelativePresChange, + m_maxAbsolutePresChange, + m_maxCompFracChange, + m_maxRelativeCompDensChange, + pressure, + compDens, + pressureScalingFactor, + compDensScalingFactor, + dofManager.rankOffset(), + m_numComponents, + wellDofKey, + subRegion, + localSolution ); + + + scalingFactor = std::min( subRegionData.localMinVal, scalingFactor ); + + maxDeltaPres = std::max( maxDeltaPres, subRegionData.localMaxDeltaPres ); + maxDeltaCompDens = std::max( maxDeltaCompDens, subRegionData.localMaxDeltaCompDens ); + maxDeltaTemp = std::max( maxDeltaTemp, subRegionData.localMaxDeltaTemp ); + minPresScalingFactor = std::min( minPresScalingFactor, subRegionData.localMinPresScalingFactor ); + minCompDensScalingFactor = std::min( minCompDensScalingFactor, subRegionData.localMinCompDensScalingFactor ); + minTempScalingFactor = std::min( minTempScalingFactor, subRegionData.localMinTempScalingFactor ); scalingFactor = MpiWrapper::min( scalingFactor ); maxDeltaPres = MpiWrapper::max( maxDeltaPres ); @@ -1585,7 +1734,6 @@ CompositionalMultiphaseWell::scalingForSystemSolution( DomainPartition & domain, getName(), GEOS_FMT( "{:.{}f}", maxDeltaTemp, 3 ) ) ); } - GEOS_LOG_LEVEL_RANK_0( logInfo::Solution, GEOS_FMT( " {}: Min well pressure scaling factor: {}", getName(), minPresScalingFactor ) ); @@ -1599,205 +1747,311 @@ CompositionalMultiphaseWell::scalingForSystemSolution( DomainPartition & domain, getName(), minTempScalingFactor ) ); } - + return LvArray::math::max( scalingFactor, m_minScalingFactor ); + +} +real64 +CompositionalMultiphaseWell::scalingForWellSystemSolution( WellElementSubRegion & subRegion, + DofManager const & dofManager, + arrayView1d< real64 const > const & localSolution ) +{ + GEOS_MARK_FUNCTION; + + string const wellDofKey = dofManager.getKey( wellElementDofName() ); + + real64 maxDeltaPres = 0.0, maxDeltaCompDens = 0.0, maxDeltaTemp = 0.0; + real64 minPresScalingFactor = 1.0, minCompDensScalingFactor = 1.0, minTempScalingFactor = 1.0; + + real64 scalingFactor =scalingForLocalSystemSolution( subRegion, + dofManager, + maxDeltaPres, + maxDeltaCompDens, + maxDeltaTemp, + minPresScalingFactor, + minCompDensScalingFactor, + minTempScalingFactor, + localSolution ); + string const massUnit = m_useMass ? "kg/m3" : "mol/m3"; + GEOS_LOG_LEVEL_RANK_0( logInfo::Solution, + GEOS_FMT( " {}: Max well pressure change: {} Pa (before scaling)", + getName(), GEOS_FMT( "{:.{}f}", maxDeltaPres, 3 ) ) ); + GEOS_LOG_LEVEL_RANK_0( logInfo::Solution, + GEOS_FMT( " {}: Max well component density change: {} {} (before scaling)", + getName(), GEOS_FMT( "{:.{}f}", maxDeltaCompDens, 3 ), massUnit ) ); + + if( m_isThermal ) + { + maxDeltaTemp = MpiWrapper::max( maxDeltaTemp ); + minTempScalingFactor = MpiWrapper::min( minTempScalingFactor ); + GEOS_LOG_LEVEL_RANK_0( logInfo::Solution, + GEOS_FMT( " {}: Max well temperature change: {} K (before scaling)", + getName(), GEOS_FMT( "{:.{}f}", maxDeltaTemp, 3 ) ) ); + } + + GEOS_LOG_LEVEL_RANK_0( logInfo::Solution, + GEOS_FMT( " {}: Min well pressure scaling factor: {}", + getName(), minPresScalingFactor ) ); + GEOS_LOG_LEVEL_RANK_0( logInfo::Solution, + GEOS_FMT( " {}: Min well component density scaling factor: {}", + getName(), minCompDensScalingFactor ) ); + if( m_isThermal ) + { + GEOS_LOG_LEVEL_RANK_0( logInfo::Solution, + GEOS_FMT( " {}: Min well temperature scaling factor: {}", + getName(), minTempScalingFactor ) ); + } + return LvArray::math::max( scalingFactor, m_minScalingFactor ); } + + bool -CompositionalMultiphaseWell::checkSystemSolution( DomainPartition & domain, - DofManager const & dofManager, - arrayView1d< real64 const > const & localSolution, - real64 const scalingFactor ) +CompositionalMultiphaseWell::checkWellSystemSolution( WellElementSubRegion & subRegion, + DofManager const & dofManager, + arrayView1d< real64 const > const & localSolution, + real64 const scalingFactor, + real64 & minPressure, + real64 & minDensity, + real64 & minTotalDensity, + ElementsReporterBuffer & negPressureIds, + ElementsReporterBuffer & negDensityIds, + ElementsReporterBuffer & negTotalDensityIds ) { GEOS_MARK_FUNCTION; string const wellDofKey = dofManager.getKey( wellElementDofName() ); integer localCheck = 1; - real64 minPres = 0.0, minDens = 0.0, minTotalDens = 0.0; - ElementsReporterBuffer rankNegPressureIds{ isLogLevelActive< logInfo::Solution >( getLogLevel() ), - isLogLevelActive< logInfo::SolutionDetails >( getLogLevel() ) ? 16 : 0 }; - ElementsReporterBuffer rankNegDensityIds{ isLogLevelActive< logInfo::Solution >( getLogLevel() ), - isLogLevelActive< logInfo::SolutionDetails >( this->getLogLevel() ) ? 16 : 0 }; - // output only total density sum, not cell details - ElementsReporterBuffer rankTotalNegDensityIds{ isLogLevelActive< logInfo::Solution >( getLogLevel() ), 0 }; - - forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const &, - MeshLevel & mesh, - string_array const & regionNames ) - { - mesh.getElemManager().forElementSubRegions( regionNames, - [&]( localIndex const, - ElementSubRegionBase & subRegion ) - { - //integer const m_allowCompDensChopping(true); - integer const m_allowNegativePressure( false ); - compositionalMultiphaseUtilities::ScalingType const m_scalingType( compositionalMultiphaseUtilities::ScalingType::Global ); - arrayView1d< real64 const > const pressure = - subRegion.getField< well::pressure >(); - arrayView1d< real64 const > const temperature = - subRegion.getField< well::temperature >(); - arrayView2d< real64 const, compflow::USD_COMP > const compDens = - subRegion.getField< well::globalCompDensity >(); - arrayView1d< real64 > pressureScalingFactor = subRegion.getField< well::pressureScalingFactor >(); - arrayView1d< real64 > temperatureScalingFactor = subRegion.getField< well::temperatureScalingFactor >(); - arrayView1d< real64 > compDensScalingFactor = subRegion.getField< well::globalCompDensityScalingFactor >(); - auto const & cellLocalToGlobalIds = subRegion.localToGlobalMap(); - auto const negPresCollector = rankNegPressureIds.createCollector( cellLocalToGlobalIds ); - auto const negDensCollector = rankNegDensityIds.createCollector( cellLocalToGlobalIds ); - auto const negTotalDensCollector = rankNegDensityIds.createCollector( cellLocalToGlobalIds ); - - // check that pressure and component densities are non-negative - // for thermal, check that temperature is above 273.15 K - const integer temperatureOffset = m_numComponents+2; - auto const subRegionData = m_isThermal ? - thermalCompositionalMultiphaseBaseKernels:: - SolutionCheckKernelFactory:: - createAndLaunch< parallelDevicePolicy<> >( m_allowCompDensChopping, - m_allowNegativePressure, - m_scalingType, - scalingFactor, - pressure, - temperature, - compDens, - pressureScalingFactor, - temperatureScalingFactor, - compDensScalingFactor, - dofManager.rankOffset(), - m_numComponents, - wellDofKey, - subRegion, - localSolution, - negPresCollector, - negDensCollector, - negTotalDensCollector, - temperatureOffset ) : - isothermalCompositionalMultiphaseBaseKernels:: - SolutionCheckKernelFactory:: - createAndLaunch< parallelDevicePolicy<> >( m_allowCompDensChopping, - m_allowNegativePressure, - m_scalingType, - scalingFactor, - pressure, - compDens, - pressureScalingFactor, - compDensScalingFactor, - dofManager.rankOffset(), - m_numComponents, - wellDofKey, - subRegion, - localSolution, - negPresCollector, - negDensCollector, - negTotalDensCollector ); - - localCheck = std::min( localCheck, subRegionData.localMinVal ); - - minPres = std::min( minPres, subRegionData.localMinNegPres ); - minDens = std::min( minDens, subRegionData.localMinNegDens ); - minTotalDens = std::min( minTotalDens, subRegionData.localMinNegTotalDens ); - } ); - } ); - minPres = MpiWrapper::min( minPres ); - minDens = MpiWrapper::min( minDens ); - minTotalDens = MpiWrapper::min( minTotalDens ); + integer constexpr allowNegativePressure = 0; + compositionalMultiphaseUtilities::ScalingType constexpr scalingType = compositionalMultiphaseUtilities::ScalingType::Global; + + arrayView1d< real64 const > const pressure = subRegion.getField< well::pressure >(); + arrayView1d< real64 const > const temperature = subRegion.getField< well::temperature >(); + arrayView2d< real64 const, compflow::USD_COMP > const compDens = subRegion.getField< well::globalCompDensity >(); + arrayView1d< real64 > pressureScalingFactor = subRegion.getField< well::pressureScalingFactor >(); + arrayView1d< real64 > temperatureScalingFactor = subRegion.getField< well::temperatureScalingFactor >(); + arrayView1d< real64 > compDensScalingFactor = subRegion.getField< well::globalCompDensityScalingFactor >(); + + auto const & cellLocalToGlobalIds = subRegion.localToGlobalMap(); + auto const negPresCollector = negPressureIds.createCollector( cellLocalToGlobalIds ); + auto const negDensCollector = negDensityIds.createCollector( cellLocalToGlobalIds ); + auto const negTotalDensCollector = negTotalDensityIds.createCollector( cellLocalToGlobalIds ); + + // check that pressure and component densities are non-negative + // for thermal, check that temperature is above 273.15 K + const integer temperatureOffset = m_numComponents+2; - rankNegPressureIds.createOutput().outputTooLowValues( GEOS_FMT( " {}: ", getName() ), - "negative pressure", minPres, units::Unit::Pressure ); + auto const subRegionData = [&](){ + if( m_isThermal ) + { + using Kernel = thermalCompositionalMultiphaseBaseKernels::SolutionCheckKernelFactory; + return Kernel::createAndLaunch< parallelDevicePolicy<> >( m_allowCompDensChopping, + allowNegativePressure, + scalingType, + scalingFactor, + pressure, + temperature, + compDens, + pressureScalingFactor, + temperatureScalingFactor, + compDensScalingFactor, + dofManager.rankOffset(), + m_numComponents, + wellDofKey, + subRegion, + localSolution, + negPresCollector, + negDensCollector, + negTotalDensCollector, + temperatureOffset ); + } + else + { + using Kernel = isothermalCompositionalMultiphaseBaseKernels::SolutionCheckKernelFactory; + return Kernel::createAndLaunch< parallelDevicePolicy<> >( m_allowCompDensChopping, + allowNegativePressure, + scalingType, + scalingFactor, + pressure, + compDens, + pressureScalingFactor, + compDensScalingFactor, + dofManager.rankOffset(), + m_numComponents, + wellDofKey, + subRegion, + localSolution, + negPresCollector, + negDensCollector, + negTotalDensCollector ); + } + }(); - units::Unit const massUnit = m_useMass ? units::Unit::Density : units::Unit::MolarDensity; - rankNegDensityIds.createOutput().outputTooLowValues( GEOS_FMT( " {}: ", getName() ), - "negative component density", minDens, massUnit ); - rankTotalNegDensityIds.createOutput().outputTooLowValues( GEOS_FMT( " {}: ", getName() ), - "negative components total density", minTotalDens, massUnit ); + minPressure = std::min( minPressure, subRegionData.localMinNegPres ); + minDensity = std::min( minDensity, subRegionData.localMinNegDens ); + minTotalDensity = std::min( minTotalDensity, subRegionData.localMinNegTotalDens ); + localCheck = std::min( localCheck, subRegionData.localMinVal ); return MpiWrapper::min( localCheck ); } -void CompositionalMultiphaseWell::computePerforationRates( real64 const & time_n, - real64 const & GEOS_UNUSED_PARAM( dt ), - DomainPartition & domain ) +void CompositionalMultiphaseWell::computeWellPerforationRates( real64 const & time_n, + real64 const & GEOS_UNUSED_PARAM( dt ), + ElementRegionManager & elemManager, + WellElementSubRegion & subRegion ) { GEOS_MARK_FUNCTION; GEOS_UNUSED_VAR( time_n ); - forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&] ( string const &, - MeshLevel & mesh, - string_array const & regionNames ) + //CompositionalMultiphaseBase const & flowSolver = getParent().getParent().getGroup< CompositionalMultiphaseBase >( getFlowSolverName() + // ); + + PerforationData * const perforationData = subRegion.getPerforationData(); + + if( isWellOpen() && !m_keepVariablesConstantDuringInitStep ) { - // TODO: change the way we access the flowSolver here - CompositionalMultiphaseBase const & flowSolver = getParent().getGroup< CompositionalMultiphaseBase >( getFlowSolverName() ); - ElementRegionManager & elemManager = mesh.getElemManager(); + string const & fluidName = subRegion.getReference< string >( viewKeyStruct::fluidNamesString() ); + MultiFluidBase const & fluid = getConstitutiveModel< MultiFluidBase >( subRegion, fluidName ); + bool isThermal = fluid.isThermal(); - elemManager.forElementSubRegions< WellElementSubRegion >( regionNames, [&]( localIndex const, - WellElementSubRegion & subRegion ) + if( isThermal ) { - PerforationData * const perforationData = subRegion.getPerforationData(); - WellControls const & wellControls = getWellControls( subRegion ); - if( wellControls.getWellStatus() == WellControls::Status::OPEN && !m_keepVariablesConstantDuringInitStep ) - { - string const & fluidName = subRegion.getReference< string >( viewKeyStruct::fluidNamesString() ); - MultiFluidBase const & fluid = getConstitutiveModel< MultiFluidBase >( subRegion, fluidName ); - bool const isThermal = fluid.isThermal(); - - if( isThermal ) - { - thermalPerforationFluxKernels:: - PerforationFluxKernelFactory:: - createAndLaunch< parallelDevicePolicy<> >( m_numComponents, - m_numPhases, - flowSolver.getName(), - perforationData, - subRegion, - fluid, - elemManager, - wellControls.isInjector(), - wellControls.isCrossflowEnabled() ); - } - else - { - isothermalPerforationFluxKernels:: - PerforationFluxKernelFactory:: - createAndLaunch< parallelDevicePolicy<> >( m_numComponents, - m_numPhases, - flowSolver.getName(), - perforationData, - subRegion, - elemManager, - wellControls.isInjector(), - wellControls.isCrossflowEnabled() ); - } - } - else + thermalPerforationFluxKernels:: + PerforationFluxKernelFactory:: + createAndLaunch< parallelDevicePolicy<> >( m_numComponents, + m_numPhases, + getFlowSolverName(), + perforationData, + subRegion, + fluid, + elemManager, + isInjector(), + isCrossflowEnabled()); + } + else + { + isothermalPerforationFluxKernels:: + PerforationFluxKernelFactory:: + createAndLaunch< parallelDevicePolicy<> >( m_numComponents, + m_numPhases, + getFlowSolverName(), + perforationData, + subRegion, + elemManager, + isInjector(), + isCrossflowEnabled() ); + } + } + else + { + // Zero completion flow rate + arrayView2d< real64 > const compPerfRate = perforationData->getField< fields::well::compPerforationRate >(); + for( integer iperf=0; iperfsize(); iperf++ ) + { + for( integer ic = 0; ic < m_numComponents; ++ic ) { - // Zero completion flow rate - arrayView2d< real64 > const compPerfRate = perforationData->getField< well::compPerforationRate >(); - for( integer iperf=0; iperfsize(); iperf++ ) - { - for( integer ic = 0; ic < m_numComponents; ++ic ) - { - compPerfRate[iperf][ic] = 0.0; - } - } + compPerfRate[iperf][ic] = 0.0; } - } ); + } + } - } ); } - void -CompositionalMultiphaseWell::applySystemSolution( DofManager const & dofManager, - arrayView1d< real64 const > const & localSolution, - real64 const scalingFactor, - real64 const dt, - DomainPartition & domain ) +CompositionalMultiphaseWell::applyWellBoundaryConditions( real64 const time_n, + real64 const dt, + ElementRegionManager & elemManager, + WellElementSubRegion & subRegion, + DofManager const & dofManager, + arrayView1d< real64 > const & localRhs, + CRSMatrixView< real64, globalIndex const > const & localMatrix ) { + GEOS_UNUSED_VAR( elemManager ); + GEOS_UNUSED_VAR( time_n ); + + using namespace compositionalMultiphaseUtilities; + + BitFlags< isothermalCompositionalMultiphaseBaseKernels::KernelFlags > kernelFlags; + if( useTotalMassEquation() ) + kernelFlags.set( isothermalCompositionalMultiphaseBaseKernels::KernelFlags::TotalMassEquation ); + + integer const numComps = numFluidComponents(); + + globalIndex const rankOffset = dofManager.rankOffset(); + + + string const & fluidName = subRegion.getReference< string >( viewKeyStruct::fluidNamesString() ); + MultiFluidBase const & fluid = subRegion.getConstitutiveModel< MultiFluidBase >( fluidName ); + + // if the well is shut, we neglect reservoir-well flow that may occur despite the zero rate + // therefore, we do not want to compute perforation rates and we simply assume they are zero + + //bool const detectCrossflow = + // ( wellControls.isInjector() ) && wellControls.isCrossflowEnabled() && + // getLogLevel() >= 1; // since detect crossflow requires communication, we detect it only if the logLevel is sufficiently high + + if( !isWellOpen( ) ) + { + return; + } + + PerforationData const * const perforationData = subRegion.getPerforationData(); + // get the degrees of freedom + string const wellDofKey = dofManager.getKey( wellElementDofName() ); + + if( isThermal ( ) ) + { + coupledReservoirAndWellKernels:: + ThermalCompositionalMultiPhaseWellFluxKernelFactory:: + createAndLaunch< parallelDevicePolicy<> >( numComps, + *this, + isProducer(), + dt, + rankOffset, + wellDofKey, + subRegion, + perforationData, + fluid, + kernelFlags, + localRhs, + localMatrix ); + + } + else + { + coupledReservoirAndWellKernels:: + IsothermalCompositionalMultiPhaseWellFluxKernelFactory:: + createAndLaunch< parallelDevicePolicy<> >( numComps, + dt, + rankOffset, + wellDofKey, + subRegion, + perforationData, + fluid, + localRhs, + localMatrix, + kernelFlags ); + } + + +} + +void +CompositionalMultiphaseWell::applyWellSystemSolution( DofManager const & dofManager, + arrayView1d< real64 const > const & localSolution, + real64 const scalingFactor, + real64 const dt, + DomainPartition & domain, + MeshLevel & mesh, + WellElementSubRegion & subRegion ) +{ + GEOS_UNUSED_VAR( domain ); DofManager::CompMask pressureMask( m_numDofPerWellElement, 0, 1 ); DofManager::CompMask componentMask( m_numDofPerWellElement, 1, numFluidComponents()+1 ); DofManager::CompMask connRateMask( m_numDofPerWellElement, numFluidComponents()+1, numFluidComponents()+2 ); @@ -1805,513 +2059,521 @@ CompositionalMultiphaseWell::applySystemSolution( DofManager const & dofManager, // update all the fields using the global damping coefficients dofManager.addVectorToField( localSolution, wellElementDofName(), - well::pressure::key(), + fields::well::pressure::key(), scalingFactor, pressureMask ); dofManager.addVectorToField( localSolution, wellElementDofName(), - well::globalCompDensity::key(), + fields::well::globalCompDensity::key(), scalingFactor, componentMask ); dofManager.addVectorToField( localSolution, wellElementDofName(), - well::mixtureConnectionRate::key(), + fields::well::connectionRate::key(), scalingFactor, connRateMask ); if( isThermal() ) { DofManager::CompMask temperatureMask( m_numDofPerWellElement, numFluidComponents()+2, numFluidComponents()+3 ); - dofManager.addVectorToField( localSolution, wellElementDofName(), - well::temperature::key(), + fields::well::temperature::key(), scalingFactor, temperatureMask ); } + // if component density chopping is allowed, some component densities may be negative after the update // these negative component densities are set to zero in this function if( m_allowCompDensChopping ) { - chopNegativeDensities( domain ); + chopNegativeDensities( subRegion ); } - forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const &, - MeshLevel & mesh, - string_array const & regionNames ) + // synchronize + FieldIdentifiers fieldsToBeSync; + if( isThermal() ) { - // synchronize - FieldIdentifiers fieldsToBeSync; - if( isThermal() ) - { - fieldsToBeSync.addElementFields( { well::pressure::key(), - well::globalCompDensity::key(), - well::mixtureConnectionRate::key(), - well::temperature::key() }, - regionNames ); - } - else - { - fieldsToBeSync.addElementFields( { well::pressure::key(), - well::globalCompDensity::key(), - well::mixtureConnectionRate::key() }, - regionNames ); - } - CommunicationTools::getInstance().synchronizeFields( fieldsToBeSync, - mesh, - domain.getNeighbors(), - true ); - } ); - + fieldsToBeSync.addElementFields( { fields::well::pressure::key(), + fields::well::globalCompDensity::key(), + fields::well::connectionRate::key(), + fields::well::temperature::key() }, + getTargetRegionNames() ); + } + else + { + fieldsToBeSync.addElementFields( { fields::well::pressure::key(), + fields::well::globalCompDensity::key(), + fields::well::connectionRate::key() }, + getTargetRegionNames() ); + } + CommunicationTools::getInstance().synchronizeFields( fieldsToBeSync, + mesh, + domain.getNeighbors(), + true ); } -void CompositionalMultiphaseWell::chopNegativeDensities( DomainPartition & domain ) + + +void CompositionalMultiphaseWell::chopNegativeDensities( WellElementSubRegion & subRegion ) { integer const numComp = m_numComponents; - forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&] ( string const &, - MeshLevel & mesh, - string_array const & regionNames ) - { - - ElementRegionManager & elemManager = mesh.getElemManager(); - elemManager.forElementSubRegions< WellElementSubRegion >( regionNames, - [&]( localIndex const, - WellElementSubRegion & subRegion ) - { - arrayView1d< integer const > const & wellElemGhostRank = subRegion.ghostRank(); + arrayView1d< integer const > const & wellElemGhostRank = subRegion.ghostRank(); - arrayView2d< real64, compflow::USD_COMP > const & wellElemCompDens = - subRegion.getField< well::globalCompDensity >(); + arrayView2d< real64, compflow::USD_COMP > const & wellElemCompDens = + subRegion.getField< fields::well::globalCompDensity >(); - forAll< parallelDevicePolicy<> >( subRegion.size(), [=] GEOS_HOST_DEVICE ( localIndex const iwelem ) + forAll< parallelDevicePolicy<> >( subRegion.size(), [=] GEOS_HOST_DEVICE ( localIndex const iwelem ) + { + if( wellElemGhostRank[iwelem] < 0 ) + { + for( integer ic = 0; ic < numComp; ++ic ) { - if( wellElemGhostRank[iwelem] < 0 ) + // we allowed for some densities to be slightly negative in CheckSystemSolution + // if the new density is negative, chop back to zero + if( wellElemCompDens[iwelem][ic] < 0 ) { - for( integer ic = 0; ic < numComp; ++ic ) - { - // we allowed for some densities to be slightly negative in CheckSystemSolution - // if the new density is negative, chop back to zero - if( wellElemCompDens[iwelem][ic] < 0 ) - { - wellElemCompDens[iwelem][ic] = 0; - } - } + wellElemCompDens[iwelem][ic] = 0.0; } - } ); - } ); - + } + } } ); + } -void CompositionalMultiphaseWell::resetStateToBeginningOfStep( DomainPartition & domain ) +void CompositionalMultiphaseWell::resetStateToBeginningOfStep( DomainPartition & domain, + string const & meshBodyName, ElementRegionManager const & elemManager, WellElementSubRegion & subRegion ) { - forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&] ( string const &, - MeshLevel & mesh, - string_array const & regionNames ) + + // get a reference to the primary variables on well elements + arrayView1d< real64 > const & wellElemPressure = + subRegion.getField< well::pressure >(); + arrayView1d< real64 const > const & wellElemPressure_n = + subRegion.getField< well::pressure_n >(); + wellElemPressure.setValues< parallelDevicePolicy<> >( wellElemPressure_n ); + + if( isThermal() ) + { + // get a reference to the primary variables on well elements + arrayView1d< real64 > const & wellElemTemperature = + subRegion.getField< well::temperature >(); + arrayView1d< real64 const > const & wellElemTemperature_n = + subRegion.getField< well::temperature_n >(); + wellElemTemperature.setValues< parallelDevicePolicy<> >( wellElemTemperature_n ); + } + arrayView2d< real64, compflow::USD_COMP > const & wellElemGlobalCompDensity = + subRegion.getField< well::globalCompDensity >(); + arrayView2d< real64 const, compflow::USD_COMP > const & wellElemGlobalCompDensity_n = + subRegion.getField< well::globalCompDensity_n >(); + wellElemGlobalCompDensity.setValues< parallelDevicePolicy<> >( wellElemGlobalCompDensity_n ); + + arrayView1d< real64 > const & connRate = + subRegion.getField< well::connectionRate >(); + arrayView1d< real64 const > const & connRate_n = + subRegion.getField< well::connectionRate_n >(); + connRate.setValues< parallelDevicePolicy<> >( connRate_n ); + + + if( isWellOpen( ) ) { + updateSubRegionState( -1.0, domain.getMeshBody( meshBodyName ), elemManager, subRegion ); + } + +} + +void CompositionalMultiphaseWell::assembleWellConstraintTerms( real64 const & time_n, + real64 const & GEOS_UNUSED_PARAM( dt ), + WellElementSubRegion const & subRegion, + DofManager const & dofManager, + CRSMatrixView< real64, globalIndex const > const & localMatrix, + arrayView1d< real64 > const & localRhs ) +{ + GEOS_MARK_FUNCTION; - ElementRegionManager & elemManager = mesh.getElemManager(); - elemManager.forElementSubRegions< WellElementSubRegion >( regionNames, - [&]( localIndex const, - WellElementSubRegion & subRegion ) + // the rank that owns the reference well element is responsible for the calculations below. + + if( !subRegion.isLocallyOwned() || !( getWellStatus() == WellControls::Status::OPEN )) + { + return; + } + + if( isProducer() ) + { + forSubGroups< MinimumBHPConstraint, ProductionConstraint< PhaseVolumeRateConstraint >, ProductionConstraint< MassRateConstraint >, ProductionConstraint< VolumeRateConstraint > + >( [&]( auto & constraint ) { - // get a reference to the primary variables on well elements - arrayView1d< real64 > const & wellElemPressure = - subRegion.getField< well::pressure >(); - arrayView1d< real64 const > const & wellElemPressure_n = - subRegion.getField< well::pressure_n >(); - wellElemPressure.setValues< parallelDevicePolicy<> >( wellElemPressure_n ); - - if( isThermal() ) + // Need to use name since there could be multiple constraints of the same type + if( constraint.getName() == getCurrentConstraint()->getName()) { - // get a reference to the primary variables on well elements - arrayView1d< real64 > const & wellElemTemperature = - subRegion.getField< well::temperature >(); - arrayView1d< real64 const > const & wellElemTemperature_n = - subRegion.getField< well::temperature_n >(); - wellElemTemperature.setValues< parallelDevicePolicy<> >( wellElemTemperature_n ); + // found limiting constraint + constitutive::MultiFluidBase & fluidSeparator = getMultiFluidSeparator(); + integer isThermal = fluidSeparator.isThermal(); + integer const numComp = fluidSeparator.numFluidComponents(); + geos::internal::kernelLaunchSelectorCompThermSwitch( numComp, isThermal, [&] ( auto NC, auto ISTHERMAL ) + { + integer constexpr NUM_COMP = NC(); + integer constexpr IS_THERMAL = ISTHERMAL(); + + wellConstraintKernels::ConstraintHelper< NUM_COMP, IS_THERMAL >::assembleConstraintEquation( time_n, + *this, + constraint, + subRegion, + dofManager.getKey( wellElementDofName() ), + dofManager.rankOffset(), + localMatrix, + localRhs ); + } ); } - arrayView2d< real64, compflow::USD_COMP > const & wellElemGlobalCompDensity = - subRegion.getField< well::globalCompDensity >(); - arrayView2d< real64 const, compflow::USD_COMP > const & wellElemGlobalCompDensity_n = - subRegion.getField< well::globalCompDensity_n >(); - wellElemGlobalCompDensity.setValues< parallelDevicePolicy<> >( wellElemGlobalCompDensity_n ); - - arrayView1d< real64 > const & connRate = - subRegion.getField< well::mixtureConnectionRate >(); - arrayView1d< real64 const > const & connRate_n = - subRegion.getField< well::mixtureConnectionRate_n >(); - connRate.setValues< parallelDevicePolicy<> >( connRate_n ); - WellControls & wellControls = getWellControls( subRegion ); - - if( wellControls.isWellOpen( ) ) + } ); + } + else + { + forSubGroups< MaximumBHPConstraint, InjectionConstraint< PhaseVolumeRateConstraint >, InjectionConstraint< MassRateConstraint >, + InjectionConstraint< VolumeRateConstraint > + >( [&]( auto & constraint ) + { + if( constraint.getName() == getCurrentConstraint()->getName()) { - updateSubRegionState( elemManager, subRegion ); + // found limiting constraint + constitutive::MultiFluidBase & fluidSeparator = getMultiFluidSeparator(); + integer isThermal = fluidSeparator.isThermal(); + integer const numComp = fluidSeparator.numFluidComponents(); + geos::internal::kernelLaunchSelectorCompThermSwitch( numComp, isThermal, [&] ( auto NC, auto ISTHERMAL ) + { + integer constexpr NUM_COMP = NC(); + integer constexpr IS_THERMAL = ISTHERMAL(); + + wellConstraintKernels::ConstraintHelper< NUM_COMP, IS_THERMAL >::assembleConstraintEquation( time_n, + *this, + constraint, + subRegion, + dofManager.getKey( wellElementDofName() ), + dofManager.rankOffset(), + localMatrix, + localRhs ); + } ); } } ); - } ); + } } -void CompositionalMultiphaseWell::assemblePressureRelations( real64 const & time_n, - real64 const & GEOS_UNUSED_PARAM( dt ), - DomainPartition const & domain, - DofManager const & dofManager, - CRSMatrixView< real64, globalIndex const > const & localMatrix, - arrayView1d< real64 > const & localRhs ) +void CompositionalMultiphaseWell::assembleWellPressureRelations( real64 const & GEOS_UNUSED_PARAM( time_n ), + real64 const & GEOS_UNUSED_PARAM( dt ), + WellElementSubRegion const & subRegion, + DofManager const & dofManager, + CRSMatrixView< real64, globalIndex const > const & localMatrix, + arrayView1d< real64 > const & localRhs ) { GEOS_MARK_FUNCTION; - forDiscretizationOnMeshTargets ( domain.getMeshBodies(), [&] ( string const &, - MeshLevel const & mesh, - string_array const & regionNames ) + if( isWellOpen( ) && !m_keepVariablesConstantDuringInitStep ) { + string const & fluidName = subRegion.getReference< string >( viewKeyStruct::fluidNamesString() ); + MultiFluidBase const & fluid = getConstitutiveModel< MultiFluidBase >( subRegion, fluidName ); + bool const isThermal = fluid.isThermal(); + // get the degrees of freedom, depth info, next welem index + string const wellDofKey = dofManager.getKey( wellElementDofName() ); + arrayView1d< globalIndex const > const & wellElemDofNumber = + subRegion.getReference< array1d< globalIndex > >( wellDofKey ); + arrayView1d< real64 const > const & wellElemGravCoef = + subRegion.getField< well::gravityCoefficient >(); + arrayView1d< localIndex const > const & nextWellElemIndex = + subRegion.getReference< array1d< localIndex > >( WellElementSubRegion::viewKeyStruct::nextWellElementIndexString() ); + + // get primary variables on well elements + arrayView1d< real64 const > const & wellElemPres = + subRegion.getField< well::pressure >(); + + // get total mass density on well elements (for potential calculations) + arrayView1d< real64 const > const & wellElemTotalMassDens = + subRegion.getField< well::totalMassDensity >(); + arrayView2d< real64 const, compflow::USD_FLUID_DC > const & dWellElemTotalMassDens = + subRegion.getField< well::dTotalMassDensity >(); + + // segment status + arrayView1d< integer const > const elemStatus =subRegion.getLocalWellElementStatus(); + + bool controlHasSwitched = false; + isothermalCompositionalMultiphaseBaseKernels:: + KernelLaunchSelectorCompTherm< compositionalMultiphaseWellKernels::PressureRelationKernel > + ( numFluidComponents(), + isThermal, + subRegion.size(), + dofManager.rankOffset(), + elemStatus, + wellElemDofNumber, + wellElemGravCoef, + nextWellElemIndex, + wellElemPres, + wellElemTotalMassDens, + dWellElemTotalMassDens, + controlHasSwitched, + localMatrix, + localRhs ); - ElementRegionManager const & elemManager = mesh.getElemManager(); - - elemManager.forElementSubRegions< WellElementSubRegion >( regionNames, - [&]( localIndex const, - WellElementSubRegion const & subRegion ) - { + } - WellControls & wellControls = getWellControls( subRegion ); +} - if( wellControls.isWellOpen( ) && !m_keepVariablesConstantDuringInitStep ) - { - string const & fluidName = subRegion.getReference< string >( viewKeyStruct::fluidNamesString() ); - MultiFluidBase const & fluid = getConstitutiveModel< MultiFluidBase >( subRegion, fluidName ); - bool const isThermal = fluid.isThermal(); - // get the degrees of freedom, depth info, next welem index - string const wellDofKey = dofManager.getKey( wellElementDofName() ); - arrayView1d< globalIndex const > const & wellElemDofNumber = - subRegion.getReference< array1d< globalIndex > >( wellDofKey ); - arrayView1d< real64 const > const & wellElemGravCoef = - subRegion.getField< well::gravityCoefficient >(); - arrayView1d< localIndex const > const & nextWellElemIndex = - subRegion.getReference< array1d< localIndex > >( WellElementSubRegion::viewKeyStruct::nextWellElementIndexString() ); - - // get primary variables on well elements - arrayView1d< real64 const > const & wellElemPres = - subRegion.getField< well::pressure >(); - - // get total mass density on well elements (for potential calculations) - arrayView1d< real64 const > const & wellElemTotalMassDens = - subRegion.getField< well::totalMassDensity >(); - arrayView2d< real64 const, compflow::USD_FLUID_DC > const & dWellElemTotalMassDens = - subRegion.getField< well::dTotalMassDensity >(); - - // segment status - arrayView1d< integer const > const elemStatus =subRegion.getLocalWellElementStatus(); - - bool controlHasSwitched = false; - isothermalCompositionalMultiphaseBaseKernels:: - KernelLaunchSelectorCompTherm< compositionalMultiphaseWellKernels::PressureRelationKernel > - ( numFluidComponents(), - isThermal, - subRegion.size(), - dofManager.rankOffset(), - subRegion.isLocallyOwned(), - subRegion.getTopWellElementIndex(), - m_targetPhaseIndex, - wellControls, - time_n, // controls evaluated with BHP/rate of the beginning of step - elemStatus, - wellElemDofNumber, - wellElemGravCoef, - nextWellElemIndex, - wellElemPres, - wellElemTotalMassDens, - dWellElemTotalMassDens, - controlHasSwitched, - localMatrix, - localRhs ); - - if( controlHasSwitched ) - { - // TODO: move the switch logic into wellControls - // TODO: implement a more general switch when more then two constraints per well type are allowed - if( wellControls.getControl() == WellControls::Control::BHP ) - { - if( wellControls.isProducer() ) - { - wellControls.switchToPhaseRateControl( wellControls.getTargetPhaseRate( time_n ) ); - GEOS_LOG_LEVEL_RANK_0( logInfo::WellControl, - GEOS_FMT( "Control switch for well {} from BHP constraint to phase volumetric rate constraint", subRegion.getName() ) ); - } - else if( wellControls.getInputControl() == WellControls::Control::MASSRATE ) - { - wellControls.switchToMassRateControl( wellControls.getTargetMassRate( time_n ) ); - GEOS_LOG_LEVEL_RANK_0( logInfo::WellControl, - GEOS_FMT( "Control switch for well {} from BHP constraint to mass rate constraint", subRegion.getName()) ); - } - else - { - wellControls.switchToTotalRateControl( wellControls.getTargetTotalRate( time_n ) ); - GEOS_LOG_LEVEL_RANK_0( logInfo::WellControl, - GEOS_FMT( "Control switch for well {} from BHP constraint to total volumetric rate constraint", subRegion.getName()) ); - } - } - else - { - wellControls.switchToBHPControl( wellControls.getTargetBHP( time_n ) ); - GEOS_LOG_LEVEL_RANK_0( logInfo::WellControl, - GEOS_FMT( "Control switch for well {} from rate constraint to BHP constraint", subRegion.getName() ) ); - } - } - // If a well is opened and then timestep is cut resulting in the well being shut, if the well is opened - // the well initialization code requires control type to by synced - integer owner = -1; - // Only subregion owner evaluates well control and control changes need to be broadcast to all ranks - if( subRegion.isLocallyOwned() ) - { - owner = MpiWrapper::commRank( MPI_COMM_GEOS ); - } - owner = MpiWrapper::max( owner ); - WellControls::Control wellControl = wellControls.getControl(); - MpiWrapper::broadcast( wellControl, owner ); - wellControls.setControl( wellControl ); +void CompositionalMultiphaseWell::saveState( WellElementSubRegion & subRegion ) +{ - } - } ); - } ); -} + // get a reference to the primary variables on well elements + arrayView1d< real64 const > const & wellElemPressure = + subRegion.getField< fields::well::pressure >(); + arrayView2d< real64 const, compflow::USD_COMP > const & wellElemGlobalCompDensity = + subRegion.getField< fields::well::globalCompDensity >(); + arrayView1d< real64 const > const & wellElemTemperature = + subRegion.getField< fields::well::temperature >(); -void CompositionalMultiphaseWell::implicitStepSetup( real64 const & time_n, - real64 const & dt, - DomainPartition & domain ) -{ - WellSolverBase::implicitStepSetup( time_n, dt, domain ); + arrayView1d< real64 > const & wellElemPressure_n = + subRegion.getField< fields::well::pressure_n >(); + wellElemPressure_n.setValues< parallelDevicePolicy<> >( wellElemPressure ); - forDiscretizationOnMeshTargets ( domain.getMeshBodies(), [&] ( string const &, - MeshLevel & mesh, - string_array const & regionNames ) + if( isThermal() ) { - ElementRegionManager & elemManager = mesh.getElemManager(); + arrayView1d< real64 > const & wellElemTemperature_n = + subRegion.getField< fields::well::temperature_n >(); + wellElemTemperature_n.setValues< parallelDevicePolicy<> >( wellElemTemperature ); + } - elemManager.forElementSubRegions< WellElementSubRegion >( regionNames, - [&]( localIndex const, - WellElementSubRegion & subRegion ) - { + arrayView2d< real64, compflow::USD_COMP > const & wellElemGlobalCompDensity_n = + subRegion.getField< fields::well::globalCompDensity_n >(); + wellElemGlobalCompDensity_n.setValues< parallelDevicePolicy<> >( wellElemGlobalCompDensity ); + + arrayView1d< real64 const > const & connRate = + subRegion.getField< fields::well::connectionRate >(); + arrayView1d< real64 > const & connRate_n = + subRegion.getField< fields::well::connectionRate_n >(); + connRate_n.setValues< parallelDevicePolicy<> >( connRate ); + + arrayView2d< real64 const, compflow::USD_PHASE > const wellElemPhaseVolFrac = + subRegion.getField< fields::well::phaseVolumeFraction >(); + arrayView2d< real64, compflow::USD_PHASE > const wellElemPhaseVolFrac_n = + subRegion.getField< fields::well::phaseVolumeFraction_n >(); + wellElemPhaseVolFrac_n.setValues< parallelDevicePolicy<> >( wellElemPhaseVolFrac ); + + string const & fluidName = subRegion.getReference< string >( viewKeyStruct::fluidNamesString() ); + MultiFluidBase const & fluid = getConstitutiveModel< MultiFluidBase >( subRegion, fluidName ); + fluid.saveConvergedState(); - WellControls & wellControls = getWellControls( subRegion ); - if( wellControls.isWellOpen() ) - { - // get a reference to the primary variables on well elements - arrayView1d< real64 const > const & wellElemPressure = - subRegion.getField< fields::well::pressure >(); - arrayView2d< real64 const, compflow::USD_COMP > const & wellElemGlobalCompDensity = - subRegion.getField< fields::well::globalCompDensity >(); - arrayView1d< real64 const > const & wellElemTemperature = - subRegion.getField< fields::well::temperature >(); - - arrayView1d< real64 > const & wellElemPressure_n = - subRegion.getField< fields::well::pressure_n >(); - wellElemPressure_n.setValues< parallelDevicePolicy<> >( wellElemPressure ); - - if( isThermal() ) - { - arrayView1d< real64 > const & wellElemTemperature_n = - subRegion.getField< fields::well::temperature_n >(); - wellElemTemperature_n.setValues< parallelDevicePolicy<> >( wellElemTemperature ); - } - arrayView2d< real64, compflow::USD_COMP > const & wellElemGlobalCompDensity_n = - subRegion.getField< fields::well::globalCompDensity_n >(); - wellElemGlobalCompDensity_n.setValues< parallelDevicePolicy<> >( wellElemGlobalCompDensity ); +} - arrayView1d< real64 const > const & connRate = - subRegion.getField< fields::well::mixtureConnectionRate >(); - arrayView1d< real64 > const & connRate_n = - subRegion.getField< fields::well::mixtureConnectionRate_n >(); - connRate_n.setValues< parallelDevicePolicy<> >( connRate ); +void CompositionalMultiphaseWell::implicitStepSetup( real64 const & time_n, + real64 const & dt, + DomainPartition & domain, + string const & meshBodyName, + ElementRegionManager & elemManager, + WellElementSubRegion & subRegion ) +{ + WellControls::implicitStepSetup( time_n, dt, domain, meshBodyName, elemManager, subRegion ); + if( m_reservoirStatsAggregator && !useSurfaceConditions() && !referenceReservoirRegion().empty() ) + { + m_reservoirStatsAggregator->setDirty(); + } - arrayView2d< real64 const, compflow::USD_PHASE > const wellElemPhaseVolFrac = - subRegion.getField< fields::well::phaseVolumeFraction >(); - arrayView2d< real64, compflow::USD_PHASE > const wellElemPhaseVolFrac_n = - subRegion.getField< fields::well::phaseVolumeFraction_n >(); - wellElemPhaseVolFrac_n.setValues< parallelDevicePolicy<> >( wellElemPhaseVolFrac ); - string const & fluidName = subRegion.getReference< string >( viewKeyStruct::fluidNamesString() ); - MultiFluidBase const & fluid = getConstitutiveModel< MultiFluidBase >( subRegion, fluidName ); - fluid.saveConvergedState(); + if( isWellOpen() ) + { + // get a reference to the primary variables on well elements + arrayView1d< real64 const > const & wellElemPressure = + subRegion.getField< fields::well::pressure >(); + arrayView2d< real64 const, compflow::USD_COMP > const & wellElemGlobalCompDensity = + subRegion.getField< fields::well::globalCompDensity >(); + arrayView1d< real64 const > const & wellElemTemperature = + subRegion.getField< fields::well::temperature >(); + + arrayView1d< real64 > const & wellElemPressure_n = + subRegion.getField< fields::well::pressure_n >(); + wellElemPressure_n.setValues< parallelDevicePolicy<> >( wellElemPressure ); - validateWellConstraints( time_n, dt, subRegion ); + if( isThermal() ) + { + arrayView1d< real64 > const & wellElemTemperature_n = + subRegion.getField< fields::well::temperature_n >(); + wellElemTemperature_n.setValues< parallelDevicePolicy<> >( wellElemTemperature ); + } - updateSubRegionState( elemManager, subRegion ); - } - } ) - ; - } ); + arrayView2d< real64, compflow::USD_COMP > const & wellElemGlobalCompDensity_n = + subRegion.getField< fields::well::globalCompDensity_n >(); + wellElemGlobalCompDensity_n.setValues< parallelDevicePolicy<> >( wellElemGlobalCompDensity ); + + arrayView1d< real64 const > const & connRate = + subRegion.getField< fields::well::connectionRate >(); + arrayView1d< real64 > const & connRate_n = + subRegion.getField< fields::well::connectionRate_n >(); + connRate_n.setValues< parallelDevicePolicy<> >( connRate ); + + arrayView2d< real64 const, compflow::USD_PHASE > const wellElemPhaseVolFrac = + subRegion.getField< fields::well::phaseVolumeFraction >(); + arrayView2d< real64, compflow::USD_PHASE > const wellElemPhaseVolFrac_n = + subRegion.getField< fields::well::phaseVolumeFraction_n >(); + wellElemPhaseVolFrac_n.setValues< parallelDevicePolicy<> >( wellElemPhaseVolFrac ); + + string const & fluidName = subRegion.getReference< string >( viewKeyStruct::fluidNamesString() ); + MultiFluidBase const & fluid = getConstitutiveModel< MultiFluidBase >( subRegion, fluidName ); + fluid.saveConvergedState(); + + validateWellConstraints( time_n, dt, subRegion ); + precomputeReferenceConditions( time_n, domain.getMeshBodies(), domain.getMeshBody( meshBodyName ), subRegion ); + updateSubRegionState( time_n, domain.getMeshBody( meshBodyName ), elemManager, subRegion ); + } } void CompositionalMultiphaseWell::implicitStepComplete( real64 const & time_n, real64 const & dt, - DomainPartition & domain ) + WellElementSubRegion const & subRegion ) { - WellSolverBase::implicitStepComplete( time_n, dt, domain ); - - if( getLogLevel() > 0 ) - { - printRates( time_n, dt, domain ); - } + printRates( time_n, dt, subRegion ); } void CompositionalMultiphaseWell::printRates( real64 const & time_n, real64 const & dt, - DomainPartition & domain ) + WellElementSubRegion const & subRegion ) { - forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&] ( string const &, - MeshLevel & mesh, - string_array const & regionNames ) - { - ElementRegionManager & elemManager = mesh.getElemManager(); + integer const numPhase = m_numPhases; + integer const numComp = m_numComponents; + integer const numPerf = subRegion.getPerforationData()->size(); - elemManager.forElementSubRegions< WellElementSubRegion >( regionNames, - [&]( localIndex const, - WellElementSubRegion & subRegion ) - { - integer const numPhase = m_numPhases; - integer const numComp = m_numComponents; - integer const numPerf = subRegion.getPerforationData()->size(); - // control data - WellControls const & wellControls = getWellControls( subRegion ); - stdVector< double > compRate( numComp, 0.0 ); - if( m_writeCSV > 0 && wellControls.isWellOpen( ) ) - { - arrayView2d< real64 > const compPerfRate = subRegion.getPerforationData()->getField< fields::well::compPerforationRate >(); + stdVector< double > compRate( numComp, 0.0 ); + if( m_writeCSV > 0 && isWellOpen( ) ) + { + arrayView2d< real64 const > const & compPerfRate = subRegion.getPerforationData()->getField< fields::well::compPerforationRate >(); - // bring everything back to host, capture the scalars by reference - forAll< serialPolicy >( 1, [&numComp, - &numPerf, - compPerfRate, - &compRate] ( localIndex const ) - { - for( integer ic = 0; ic < numComp; ++ic ) - { - for( integer iperf = 0; iperf < numPerf; iperf++ ) - { - compRate[ic] += compPerfRate[iperf][ic]; - } - } - } ); - for( integer ic = 0; ic < numComp; ++ic ) + // bring everything back to host, capture the scalars by reference + forAll< serialPolicy >( 1, [&numComp, + &numPerf, + compPerfRate, + &compRate] ( localIndex const ) + { + for( integer ic = 0; ic < numComp; ++ic ) + { + for( integer iperf = 0; iperf < numPerf; iperf++ ) { - compRate[ic] = MpiWrapper::sum( compRate[ic] ); + compRate[ic] += compPerfRate[iperf][ic]; } } + } ); + for( integer ic = 0; ic < numComp; ++ic ) + { + compRate[ic] = MpiWrapper::sum( compRate[ic] ); + } + } - // the rank that owns the reference well element is responsible for the calculations below. - if( !subRegion.isLocallyOwned() ) - { - return; - } + // the rank that owns the reference well element is responsible for the calculations below. + if( !subRegion.isLocallyOwned() ) + { + return; + } + + string const wellControlsName = getName(); - string const wellControlsName = wellControls.getName(); + // format: time,total_rate,total_vol_rate,phase0_vol_rate,phase1_vol_rate,... + std::ofstream outputFile; + if( m_writeCSV > 0 ) + { + outputFile.open( m_ratesOutputDir + "/" + wellControlsName + ".csv", std::ios_base::app ); + outputFile << time_n << "," << dt; + } - // format: time,total_rate,total_vol_rate,phase0_vol_rate,phase1_vol_rate,... - std::ofstream outputFile; - if( m_writeCSV > 0 ) + if( getWellStatus() == WellControls::Status::CLOSED ) + { + GEOS_LOG( GEOS_FMT( "{}: well is shut", wellControlsName ) ); + if( outputFile.is_open()) + { + // print all zeros in the rates file + outputFile << ",0.0,0.0,0.0"; + for( integer ip = 0; ip < numPhase; ++ip ) { - outputFile.open( m_ratesOutputDir + "/" + wellControlsName + ".csv", std::ios_base::app ); - outputFile << time_n << "," << dt; + outputFile << ",0.0"; } - - if( wellControls.getWellStatus() == WellControls::Status::CLOSED ) + for( integer ic = 0; ic < numComp; ++ic ) { - GEOS_LOG( GEOS_FMT( "{}: well is shut", wellControlsName ) ); - if( outputFile.is_open()) - { - // print all zeros in the rates file - outputFile << ",0.0,0.0,0.0"; - for( integer ip = 0; ip < numPhase; ++ip ) - { - outputFile << ",0.0"; - } - for( integer ic = 0; ic < numComp; ++ic ) - { - outputFile << ",0.0"; - } - outputFile << std::endl; - outputFile.close(); - } - return; + outputFile << ",0.0"; } + outputFile << std::endl; + outputFile.close(); + } + return; + } - localIndex const iwelemRef = subRegion.getTopWellElementIndex(); - string const massUnit = m_useMass ? "kg" : "mol"; - - // subRegion data - - arrayView1d< real64 const > const & connRate = - subRegion.getField< well::mixtureConnectionRate >(); - - integer const useSurfaceConditions = wellControls.useSurfaceConditions(); - - real64 const & currentBHP = - wellControls.getReference< real64 >( CompositionalMultiphaseWell::viewKeyStruct::currentBHPString() ); - arrayView1d< real64 const > const & currentPhaseVolRate = - wellControls.getReference< array1d< real64 > >( CompositionalMultiphaseWell::viewKeyStruct::currentPhaseVolRateString() ); - real64 const & currentTotalVolRate = - wellControls.getReference< real64 >( CompositionalMultiphaseWell::viewKeyStruct::currentTotalVolRateString() ); - - // bring everything back to host, capture the scalars by reference - forAll< serialPolicy >( 1, [&numPhase, - &numComp, - &useSurfaceConditions, - ¤tBHP, - connRate, - ¤tTotalVolRate, - currentPhaseVolRate, - &compRate, - &iwelemRef, - &wellControlsName, - &massUnit, - &outputFile] ( localIndex const ) + localIndex const iwelemRef = subRegion.getTopWellElementIndex(); + string const massUnit = m_useMass ? "kg" : "mol"; + + // subRegion data + + arrayView1d< real64 const > const & connRate = + subRegion.getField< well::connectionRate >(); + + integer const useSurfaceCond = useSurfaceConditions(); + + real64 const & currentBHP = + getReference< real64 >( CompositionalMultiphaseWell::viewKeyStruct::currentBHPString() ); + arrayView1d< real64 const > const & currentPhaseVolRate = + getReference< array1d< real64 > >( CompositionalMultiphaseWell::viewKeyStruct::currentPhaseVolRateString() ); + real64 const & currentTotalVolRate = + getReference< real64 >( CompositionalMultiphaseWell::viewKeyStruct::currentTotalVolRateString() ); + + // bring everything back to host, capture the scalars by reference + forAll< serialPolicy >( 1, [&numPhase, + &numComp, + &useSurfaceCond, + ¤tBHP, + connRate, + ¤tTotalVolRate, + currentPhaseVolRate, + &compRate, + &iwelemRef, + &wellControlsName, + &massUnit, + &outputFile] ( localIndex const ) + { + string const conditionKey = useSurfaceCond ? "surface" : "reservoir"; + string const unitKey = useSurfaceCond ? "s" : "r"; + + real64 const currentTotalRate = connRate[iwelemRef]; + GEOS_LOG( GEOS_FMT( "{}: BHP (at the specified reference elevation): {} Pa", + wellControlsName, currentBHP ) ); + GEOS_LOG( GEOS_FMT( "{}: Total rate: {} {}/s; total {} volumetric rate: {} {}m3/s", + wellControlsName, currentTotalRate, massUnit, conditionKey, currentTotalVolRate, unitKey ) ); + for( integer ip = 0; ip < numPhase; ++ip ) + GEOS_LOG( GEOS_FMT( "{}: Phase {} {} volumetric rate: {} {}m3/s", + wellControlsName, ip, conditionKey, currentPhaseVolRate[ip], unitKey ) ); + if( outputFile.is_open()) + { + outputFile << "," << currentBHP; + outputFile << "," << currentTotalRate << "," << currentTotalVolRate; + for( integer ip = 0; ip < numPhase; ++ip ) { - string const conditionKey = useSurfaceConditions ? "surface" : "reservoir"; - string const unitKey = useSurfaceConditions ? "s" : "r"; - - real64 const currentTotalRate = connRate[iwelemRef]; - GEOS_LOG( GEOS_FMT( "{}: BHP (at the specified reference elevation): {} Pa", - wellControlsName, currentBHP ) ); - GEOS_LOG( GEOS_FMT( "{}: Total rate: {} {}/s; total {} volumetric rate: {} {}m3/s", - wellControlsName, currentTotalRate, massUnit, conditionKey, currentTotalVolRate, unitKey ) ); - for( integer ip = 0; ip < numPhase; ++ip ) - GEOS_LOG( GEOS_FMT( "{}: Phase {} {} volumetric rate: {} {}m3/s", - wellControlsName, ip, conditionKey, currentPhaseVolRate[ip], unitKey ) ); - if( outputFile.is_open()) - { - outputFile << "," << currentBHP; - outputFile << "," << currentTotalRate << "," << currentTotalVolRate; - for( integer ip = 0; ip < numPhase; ++ip ) - { - outputFile << "," << currentPhaseVolRate[ip]; - } - for( integer ic = 0; ic < numComp; ++ic ) - { - outputFile << "," << compRate[ic]; - } - outputFile << std::endl; - outputFile.close(); - } - } ); - } ); + outputFile << "," << currentPhaseVolRate[ip]; + } + for( integer ic = 0; ic < numComp; ++ic ) + { + outputFile << "," << compRate[ic]; + } + outputFile << std::endl; + outputFile.close(); + } } ); + } -REGISTER_CATALOG_ENTRY( PhysicsSolverBase, CompositionalMultiphaseWell, string const &, Group * const ) -} // namespace geos + + +} // namespace geos diff --git a/src/coreComponents/physicsSolvers/fluidFlow/wells/CompositionalMultiphaseWell.hpp b/src/coreComponents/physicsSolvers/fluidFlow/wells/CompositionalMultiphaseWell.hpp index cb8ed9f1a6f..b40a11f0ee5 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/wells/CompositionalMultiphaseWell.hpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/wells/CompositionalMultiphaseWell.hpp @@ -20,11 +20,15 @@ #ifndef GEOS_PHYSICSSOLVERS_FLUIDFLOW_WELLS_COMPOSITIONALMULTIPHASEWELL_HPP_ #define GEOS_PHYSICSSOLVERS_FLUIDFLOW_WELLS_COMPOSITIONALMULTIPHASEWELL_HPP_ +#include "common/DataTypes.hpp" #include "constitutive/fluid/multifluid/Layouts.hpp" #include "constitutive/relativePermeability/Layouts.hpp" -#include "physicsSolvers/fluidFlow/wells/WellSolverBase.hpp" +#include "mesh/MeshLevel.hpp" + #include "physicsSolvers/fluidFlow/CompositionalMultiphaseBase.hpp" +#include "physicsSolvers/fluidFlow/wells/WellConstraintsBase.hpp" +#include "physicsSolvers/fluidFlow/wells/WellControls.hpp" namespace geos { @@ -34,12 +38,17 @@ class ConstitutiveManager; class MultiFluidBase; } +namespace compositionalMultiphaseStatistics +{ +class StatsAggregator; +} + /** * @class CompositionalMultiphaseWell * * A compositional multiphase well solver */ -class CompositionalMultiphaseWell : public WellSolverBase +class CompositionalMultiphaseWell : public WellControls { public: @@ -57,8 +66,8 @@ class CompositionalMultiphaseWell : public WellSolverBase /// deleted copy constructor CompositionalMultiphaseWell( CompositionalMultiphaseWell const & ) = delete; - /// default move constructor - CompositionalMultiphaseWell( CompositionalMultiphaseWell && ) = default; + /// deleted move constructor + CompositionalMultiphaseWell( CompositionalMultiphaseWell && ) = delete; /// deleted assignment operator CompositionalMultiphaseWell & operator=( CompositionalMultiphaseWell const & ) = delete; @@ -69,66 +78,180 @@ class CompositionalMultiphaseWell : public WellSolverBase /** * @brief default destructor */ - virtual ~CompositionalMultiphaseWell() override = default; + virtual ~CompositionalMultiphaseWell() override; + compositionalMultiphaseStatistics::StatsAggregator * getStatsAggregator() { return m_reservoirStatsAggregator.get(); } + void setReservoirStatsAggregator( std::unique_ptr< compositionalMultiphaseStatistics::StatsAggregator > aggregator ); + + + virtual void registerWellDataOnMesh( WellElementSubRegion & subRegion ) override; /** - * @brief name of the node manager in the object catalog - * @return string that contains the catalog name to generate a new NodeManager object through the object catalog. + * @defgroup WellManager Interface Functions + * + * These functions provide the primary interface that is required for derived classes + * The "Well" versions apply to individual well subRegions, whereas the others apply to all wells */ - static string catalogName() { return "CompositionalMultiphaseWell"; } + /**@{*/ /** - * @copydoc PhysicsSolverBase::getCatalogName() + * * @brief Initialize well for the beginning of a simulation or restart + * @param domain the domain + * @param mesh the mesh level + * @param subRegion the well subRegion */ - string getCatalogName() const override { return catalogName(); } + virtual void initializeWell( DomainPartition & domain, Group & meshBodies, string const & meshBodyName, MeshLevel & mesh, WellElementSubRegion & subRegion, real64 const & time_n ) override; + + virtual void initializeWellPostInitialConditionsPreSubGroups( WellElementSubRegion & subRegion ) override; - virtual void registerDataOnMesh( Group & meshBodies ) override; + virtual bool isCompositional() const override { return true; } + + + /** + * @copydoc WellControls::assembleWellAccumulationTerms() + */ + virtual void assembleWellAccumulationTerms( real64 const & time, + real64 const & dt, + WellElementSubRegion & subRegion, + DofManager const & dofManager, + CRSMatrixView< real64, globalIndex const > const & localMatrix, + arrayView1d< real64 > const & localRhs ) override; + /** + * @copydoc WellControls::assembleWellPressureRelations() + */ + virtual void assembleWellPressureRelations( real64 const & time_n, + real64 const & dt, + WellElementSubRegion const & subRegion, + DofManager const & dofManager, + CRSMatrixView< real64, globalIndex const > const & localMatrix, + arrayView1d< real64 > const & localRhs ) override; + /** + * @copydoc WellControls::assembleWellConstraintTerms() + */ + virtual void assembleWellConstraintTerms( real64 const & time_n, + real64 const & dt, + WellElementSubRegion const & subRegion, + DofManager const & dofManager, + CRSMatrixView< real64, globalIndex const > const & localMatrix, + arrayView1d< real64 > const & localRhs ) override; + /** + * @copydoc WellControls::computeWellPerforationRates() + */ + virtual void computeWellPerforationRates( real64 const & time_n, + real64 const & GEOS_UNUSED_PARAM( dt ), + ElementRegionManager & elemManager, + WellElementSubRegion & subRegion ) override; /** - * @defgroup Solver Interface Functions + * @copydoc WellControls::assembleFluxTerms() + */ + virtual void assembleWellFluxTerms( real64 const & time, + real64 const & dt, + WellElementSubRegion const & subRegion, + DofManager const & dofManager, + CRSMatrixView< real64, globalIndex const > const & localMatrix, + arrayView1d< real64 > const & localRhs ) override; + /**@}*/ + /** + * @defgroup Well Interface Functions - required by WellManager and WellNewtonSolver * * These functions provide the primary interface that is required for derived classes + * The "Well" versions apply to individual well subRegions */ /**@{*/ + /** + * @copydoc WellControls::calculateResidualNorm() + */ + + virtual array1d< real64 > + calculateLocalWellResidualNorm( real64 const & time_n, + real64 const & dt, + NonlinearSolverParameters const & nonlinearSolverParameters, + WellElementSubRegion const & subRegion, + DofManager const & dofManager, + arrayView1d< real64 const > const & localRhs ) override; - virtual real64 - calculateResidualNorm( real64 const & time_n, - real64 const & dt, - DomainPartition const & domain, - DofManager const & dofManager, - arrayView1d< real64 const > const & localRhs ) override; virtual real64 - scalingForSystemSolution( DomainPartition & domain, - DofManager const & dofManager, - arrayView1d< real64 const > const & localSolution ) override; + calculateWellResidualNorm( real64 const & time_n, + real64 const & dt, + NonlinearSolverParameters const & nonlinearSolverParameters, + WellElementSubRegion const & subRegion, + DofManager const & dofManager, + arrayView1d< real64 const > const & localRhs ) override; - virtual bool - checkSystemSolution( DomainPartition & domain, - DofManager const & dofManager, - arrayView1d< real64 const > const & localSolution, - real64 const scalingFactor ) override; + /** + * @copydoc WellControls::scalingForSystemSolution() + */ + real64 scalingForLocalSystemSolution ( WellElementSubRegion & subRegion, + DofManager const & dofManager, + real64 & maxDeltaPres, + real64 & maxDeltaCompDens, + real64 & maxDeltaTemp, + real64 & minPresScalingFactor, + real64 & minCompDensScalingFactor, + real64 & minTempScalingFactor, + arrayView1d< real64 const > const & localSolution ); + + virtual real64 scalingForWellSystemSolution( WellElementSubRegion & subRegion, + DofManager const & dofManager, + arrayView1d< real64 const > const & localSolution ) override; - virtual void - applySystemSolution( DofManager const & dofManager, - arrayView1d< real64 const > const & localSolution, - real64 const scalingFactor, - real64 const dt, - DomainPartition & domain ) override; + /** + * @copydoc WellControls::checkSystemSolution() + */ + virtual bool + checkWellSystemSolution( WellElementSubRegion & subRegion, + DofManager const & dofManager, + arrayView1d< real64 const > const & localSolution, + real64 const scalingFactor, + real64 & minPressure, + real64 & minDensity, + real64 & minTotalDensity, + ElementsReporterBuffer & negPressureIds, + ElementsReporterBuffer & negDensityIds, + ElementsReporterBuffer & negTotalDensityIds ) override; - virtual void - resetStateToBeginningOfStep( DomainPartition & domain ) override; + /** + * @copydoc WellControls::applyWellSystemSolution() + */ virtual void - implicitStepSetup( real64 const & time, - real64 const & dt, - DomainPartition & domain ) override; + applyWellSystemSolution( DofManager const & dofManager, + arrayView1d< real64 const > const & localSolution, + real64 const scalingFactor, + real64 const dt, + DomainPartition & domain, + MeshLevel & mesh, + WellElementSubRegion & subRegion ) override; + + virtual void applyWellBoundaryConditions( real64 const time_n, + real64 const dt, + ElementRegionManager & elemManager, + WellElementSubRegion & subRegion, + DofManager const & dofManager, + arrayView1d< real64 > const & localRhs, + CRSMatrixView< real64, globalIndex const > const & localMatrix ) override; + + + virtual void resetStateToBeginningOfStep( DomainPartition & domain, + string const & meshBodyName, ElementRegionManager const & elemManager, WellElementSubRegion & subRegion ) override; + + virtual void implicitStepSetup( real64 const & time_n, + real64 const & GEOS_UNUSED_PARAM( dt ), + DomainPartition & domain, + string const & meshBodyName, + ElementRegionManager & elemManager, + WellElementSubRegion & subRegion ) override; virtual void implicitStepComplete( real64 const & time, real64 const & dt, - DomainPartition & domain ) override; + WellElementSubRegion const & subRegion ) override; + + virtual void printRates( real64 const & time_n, + real64 const & dt, + WellElementSubRegion const & subRegion ) override; /**@}*/ @@ -140,10 +263,9 @@ class CompositionalMultiphaseWell : public WellSolverBase /** * @brief Recompute the volumetric rates that are used in the well constraints - * @param elemManager the well region manager containing the well * @param subRegion the well subregion containing all the primary and dependent fields */ - void updateVolRatesForConstraint( ElementRegionManager const & elemManager, WellElementSubRegion const & subRegion ); + void updateVolRatesForConstraint( WellElementSubRegion const & subRegion ); /** * @brief Recompute the current BHP pressure @@ -159,6 +281,25 @@ class CompositionalMultiphaseWell : public WellSolverBase */ void updateFluidModel( WellElementSubRegion & subRegion ); + /** + * @brief Update well separator using current values of pressure and composition at the reference + * element + * @param elemManager the element region manager + + */ + void updateSeparator( real64 time_n, + MeshBody const & meshBody, + ElementRegionManager const & elemManager, + WellElementSubRegion & subRegion ); + + /** + * @brief Calculate well rates at reference element + * @param subRegion the well subregion containing all the primary and dependent fields + * @param targetIndex the targetIndex of the subRegion + */ + + void calculateReferenceElementRates( WellElementSubRegion & subRegion ); + /** * @brief Recompute phase volume fractions (saturations) from constitutive and primary variables * @param subRegion the well subregion containing all the primary and dependent fields @@ -172,20 +313,18 @@ class CompositionalMultiphaseWell : public WellSolverBase */ void updateTotalMassDensity( WellElementSubRegion & subRegion ) const; - /** - * @brief Recompute the perforation rates for all the wells - * @param domain the domain containing the mesh and fields - */ - virtual void computePerforationRates( real64 const & time_n, - real64 const & dt, DomainPartition & domain ) override; - /** * @brief Recompute all dependent quantities from primary variables (including constitutive models) * @param subRegion the well subregion containing all the primary and dependent fields */ - virtual void updateState( DomainPartition & domain ) override; + virtual real64 updateWellState( MeshBody const & meshBody, + ElementRegionManager const & elemManager, + WellElementSubRegion & subRegion ) override; - virtual real64 updateSubRegionState( ElementRegionManager const & elemManager, WellElementSubRegion & subRegion ) override; + virtual real64 updateSubRegionState( real64 time_n, + MeshBody const & meshBody, + ElementRegionManager const & elemManager, + WellElementSubRegion & subRegion ) override; virtual string wellElementDofName() const override { return viewKeyStruct::dofFieldString(); } @@ -197,61 +336,16 @@ class CompositionalMultiphaseWell : public WellSolverBase integer useTotalMassEquation() const { return m_useTotalMassEquation; } - /** - * @brief assembles the flux terms for all connections between well elements - * @param time_n previous time value - * @param dt time step - * @param domain the physical domain object - * @param dofManager degree-of-freedom manager associated with the linear system - * @param matrix the system matrix - * @param rhs the system right-hand side vector - */ - - virtual void assembleFluxTerms( real64 const & time_n, - real64 const & dt, - DomainPartition & domain, - DofManager const & dofManager, - CRSMatrixView< real64, globalIndex const > const & localMatrix, - arrayView1d< real64 > const & localRhs )override; - /** - * @brief assembles the accumulation term for all the well elements - * @param domain the physical domain object - * @param dofManager degree-of-freedom manager associated with the linear system - * @param matrix the system matrix - * @param rhs the system right-hand side vector - */ - virtual void assembleAccumulationTerms( real64 const & time_n, - real64 const & dt, - DomainPartition & domain, - DofManager const & dofManager, - CRSMatrixView< real64, globalIndex const > const & localMatrix, - arrayView1d< real64 > const & localRhs ) override; - - /** - * @brief assembles the pressure relations at all connections between well elements except at the well head - * @param time_n time at the beginning of the time step - * @param dt the time step size - * @param domain the physical domain object - * @param dofManager degree-of-freedom manager associated with the linear system - * @param matrix the system matrix - * @param rhs the system right-hand side vector - */ - virtual void assemblePressureRelations( real64 const & time_n, - real64 const & dt, - DomainPartition const & domain, - DofManager const & dofManager, - CRSMatrixView< real64, globalIndex const > const & localMatrix, - arrayView1d< real64 > const & localRhs ) override; - /** * @brief Sets all the negative component densities (if any) to zero. - * @param domain the physical domain object + * @param subRegion the well subregion containing all the primary and dependent fields */ - void chopNegativeDensities( DomainPartition & domain ); + void chopNegativeDensities( WellElementSubRegion & subRegion ); - struct viewKeyStruct : WellSolverBase::viewKeyStruct + + struct viewKeyStruct : WellControls::viewKeyStruct { - static constexpr char const * dofFieldString() { return "compositionalWellVars"; } + static constexpr char const * dofFieldString() { return "wellVars"; } // inputs @@ -271,39 +365,21 @@ class CompositionalMultiphaseWell : public WellSolverBase static constexpr char const * allowLocalCompDensChoppingString() { return CompositionalMultiphaseBase::viewKeyStruct::allowLocalCompDensChoppingString(); } - // control data (not registered on the mesh) - - static constexpr char const * massDensityString() { return "massDensity";} - - static constexpr char const * currentBHPString() { return "currentBHP"; } - static constexpr char const * dCurrentBHPString() { return "dCurrentBHP"; } - - static constexpr char const * dCurrentBHP_dPresString() { return "dCurrentBHP_dPres"; } - static constexpr char const * dCurrentBHP_dCompDensString() { return "dCurrentBHP_dCompDens"; } - - static constexpr char const * currentPhaseVolRateString() { return "currentPhaseVolumetricRate"; } - static constexpr char const * dCurrentPhaseVolRateString() { return "dCurrentPhaseVolumetricRate"; } - - - static constexpr char const * dCurrentPhaseVolRate_dPresString() { return "dCurrentPhaseVolumetricRate_dPres"; } - - static constexpr char const * dCurrentPhaseVolRate_dCompDensString() { return "dCurrentPhaseVolumetricRate_dCompDens"; } - - static constexpr char const * dCurrentPhaseVolRate_dRateString() { return "dCurrentPhaseVolumetricRate_dRate"; } - - static constexpr char const * currentTotalVolRateString() { return "currentTotalVolumetricRate"; } - static constexpr char const * dCurrentTotalVolRateString() { return "dCurrentTotalVolumetricRate"; } - - static constexpr char const * currentMassRateString() { return "currentMassRate"; } - static constexpr char const * dCurrentTotalVolRate_dPresString() { return "dCurrentTotalVolumetricRate_dPres"; } - - static constexpr char const * dCurrentTotalVolRate_dCompDensString() { return "dCurrentTotalVolumetricRate_dCompDens"; } - - static constexpr char const * dCurrentTotalVolRate_dRateString() { return "dCurrentTotalVolumetricRate_dRate"; } } viewKeysCompMultiphaseWell; + /** + * @brief Checks fluild model compatibility and validity + * @param[in] fluid the fluid to check + * @param[in] referenceFluid the reference fluid model + * @detail + * This function will produce an error if one of the well constitutive models + * is incompatible with the corresponding models in reservoir + * regions connected to that particular well. + */ + void validateFluidModel( constitutive::MultiFluidBase const & fluid, constitutive::MultiFluidBase const & referenceFluid )const; + protected: virtual void postInputInitialization() override; @@ -312,29 +388,9 @@ class CompositionalMultiphaseWell : public WellSolverBase virtual void initializePostInitialConditionsPreSubGroups() override; - virtual void postRestartInitialization() override final; - /* - * @brief Utility function that checks the consistency of the constitutive models - * @param[in] domain the domain partition - * @detail - * This function will produce an error if one of the well constitutive models - * is incompatible with the corresponding models in reservoir - * regions connected to that particular well. - */ - void validateConstitutiveModels( DomainPartition const & domain ) const; - - /** - * @brief Checks if the WellControls parameters are within the fluid tables ranges - * @param fluid the fluid to check - */ - void validateWellControlsForFluid( WellControls const & wellControls, - constitutive::MultiFluidBase const & fluid ) const; + void saveState( WellElementSubRegion & subRegion ); + virtual void postRestartInitialization( ) override; - /** - * @brief Checks injection streams for validity (compositions sum to one) - * @param subRegion the well subRegion - */ - void validateInjectionStreams( WellElementSubRegion const & subRegion ) const; /** * @brief Make sure that the well constraints are compatible @@ -350,26 +406,29 @@ class CompositionalMultiphaseWell : public WellSolverBase /** * @brief Create well separator */ - void createSeparator(); + virtual void createSeparator( WellElementSubRegion & subRegion ) override; + /// optional statistics aggregator to get the average pressure of simulated region + std::unique_ptr< compositionalMultiphaseStatistics::StatsAggregator > m_reservoirStatsAggregator; - void printRates( real64 const & time_n, - real64 const & dt, - DomainPartition & domain ) override; private: - /** - * @brief Initialize all the primary and secondary variables in all the wells - * @param domain the domain containing the well manager to access individual wells - */ - void initializeWells( DomainPartition & domain, real64 const & time_n ) override; + struct ReferenceConditions + { + real64 pressure; + real64 temperature; + }; + virtual void setConstitutiveNames( ElementSubRegionBase & subRegion ) const override; + void precomputeReferenceConditions( real64 time_n, + Group & meshBodies, + MeshBody & meshBody, + WellElementSubRegion const & subRegion ); + ReferenceConditions getReferenceConditions( WellElementSubRegion const & subRegion ); - /// flag indicating whether mass or molar formulation should be used - integer m_useMass; /// flag indicating whether total mass equation should be used integer m_useTotalMassEquation; @@ -395,9 +454,6 @@ class CompositionalMultiphaseWell : public WellSolverBase /// flag indicating whether local (cell-wise) chopping of negative compositions is allowed integer m_allowCompDensChopping; - /// index of the target phase, used to impose the phase rate constraint - localIndex m_targetPhaseIndex; - }; diff --git a/src/coreComponents/physicsSolvers/fluidFlow/wells/CompositionalMultiphaseWellFields.hpp b/src/coreComponents/physicsSolvers/fluidFlow/wells/CompositionalMultiphaseWellFields.hpp index 3ca423cd7ac..4abd716ffc4 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/wells/CompositionalMultiphaseWellFields.hpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/wells/CompositionalMultiphaseWellFields.hpp @@ -58,21 +58,7 @@ DECLARE_FIELD( globalCompDensity_n, WRITE_AND_READ, "Global component density at the previous converged time step" ); -DECLARE_FIELD( mixtureConnectionRate, - "wellElementMixtureConnectionRate", - array1d< real64 >, - 0, - LEVEL_0, - WRITE_AND_READ, - "Mixture connection rate" ); -DECLARE_FIELD( mixtureConnectionRate_n, - "wellElementMixtureConnectionRate_n", - array1d< real64 >, - 0, - NOPLOT, - WRITE_AND_READ, - "Mixture connection rate at the previous converged time step" ); DECLARE_FIELD( globalCompFraction, "globalCompFraction", diff --git a/src/coreComponents/physicsSolvers/fluidFlow/wells/SinglePhaseWell.cpp b/src/coreComponents/physicsSolvers/fluidFlow/wells/SinglePhaseWell.cpp index 875ddfba049..7da4cd4d891 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/wells/SinglePhaseWell.cpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/wells/SinglePhaseWell.cpp @@ -27,10 +27,12 @@ #include "constitutive/fluid/singlefluid/SingleFluidSelector.hpp" #include "dataRepository/Group.hpp" #include "mesh/DomainPartition.hpp" +#include "mesh/ElementRegionManager.hpp" #include "mesh/WellElementSubRegion.hpp" #include "mesh/PerforationFields.hpp" #include "mesh/mpiCommunications/CommunicationTools.hpp" #include "physicsSolvers/LogLevelsInfo.hpp" +#include "physicsSolvers/PhysicsSolverManager.hpp" #include "physicsSolvers/fluidFlow/wells/LogLevelsInfo.hpp" #include "physicsSolvers/fluidFlow/SinglePhaseBase.hpp" #include "physicsSolvers/fluidFlow/SolutionCheckHelpers.hpp" @@ -38,14 +40,21 @@ #include "physicsSolvers/fluidFlow/wells/WellFields.hpp" #include "physicsSolvers/fluidFlow/wells/WellSolverBaseFields.hpp" #include "physicsSolvers/fluidFlow/wells/SinglePhaseWellFields.hpp" -#include "physicsSolvers/fluidFlow/wells/WellControls.hpp" + +#include "physicsSolvers/fluidFlow/wells/WellInjectionConstraint.hpp" +#include "physicsSolvers/fluidFlow/wells/WellProductionConstraint.hpp" +#include "physicsSolvers/fluidFlow/wells/WellBHPConstraints.hpp" +#include "physicsSolvers/fluidFlow/wells/WellVolumeRateConstraint.hpp" +#include "physicsSolvers/fluidFlow/wells/WellPhaseVolumeRateConstraint.hpp" +#include "physicsSolvers/fluidFlow/wells/WellMassRateConstraint.hpp" + #include "physicsSolvers/fluidFlow/wells/kernels/SinglePhaseWellKernels.hpp" #include "physicsSolvers/fluidFlow/wells/kernels/ThermalSinglePhaseWellKernels.hpp" #include "physicsSolvers/fluidFlow/wells/kernels/SinglePhasePerforationFluxKernels.hpp" #include "physicsSolvers/fluidFlow/kernels/singlePhase/FluidUpdateKernel.hpp" #include "physicsSolvers/fluidFlow/kernels/singlePhase/SolutionCheckKernel.hpp" -#include "physicsSolvers/fluidFlow/SinglePhaseStatistics.hpp" - +#include "physicsSolvers/fluidFlow/SinglePhaseStatisticsAggregator.hpp" +#include "physicsSolvers/fluidFlow/wells/kernels/SinglePhaseWellConstraintKernels.hpp" namespace geos { @@ -53,10 +62,59 @@ using namespace dataRepository; using namespace constitutive; using namespace fields; using namespace singlePhaseWellKernels; +using namespace singlePhaseStatistics; + +SinglePhaseBase & getFlowSolver( SinglePhaseWell & wellSolver ) +{ + // TODO: change the way we access the flowSolver here + return wellSolver.getParent().getGroup< SinglePhaseBase >( wellSolver.getFlowSolverName() ); +} + +SinglePhaseBase const & getFlowSolver( SinglePhaseWell const & wellSolver ) +{ + // TODO: change the way we access the flowSolver here + return wellSolver.getParent().getGroup< SinglePhaseBase >( wellSolver.getFlowSolverName() ); +} + +real64 getBHPReferenceGravityCoef( SinglePhaseWell const & wellSolver, + ConstraintSourceId const source ) +{ + real64 refGravCoef = 0.0; + bool foundConstraint = false; + + if( wellSolver.isProducer() ) + { + wellSolver.forSubGroups< MinimumBHPConstraint >( [&]( WellConstraintBase const & constraint ) + { + if( constraint.isConstraintActive() && constraint.getConstraintSource() == source ) + { + refGravCoef = static_cast< MinimumBHPConstraint const & >( constraint ).getReferenceGravityCoef(); + foundConstraint = true; + } + } ); + } + else + { + wellSolver.forSubGroups< MaximumBHPConstraint >( [&]( WellConstraintBase const & constraint ) + { + if( constraint.isConstraintActive() && constraint.getConstraintSource() == source ) + { + refGravCoef = static_cast< MaximumBHPConstraint const & >( constraint ).getReferenceGravityCoef(); + foundConstraint = true; + } + } ); + } + + GEOS_THROW_IF( !foundConstraint, + GEOS_FMT( "Could not find active BHP constraint for well {}", wellSolver.getName() ), + InputError ); + + return refGravCoef; +} SinglePhaseWell::SinglePhaseWell( const string & name, Group * const parent ): - WellSolverBase( name, parent ) + WellControls( name, parent ) { m_numDofPerWellElement = 2; m_numDofPerResElement = 1; @@ -69,64 +127,59 @@ SinglePhaseWell::SinglePhaseWell( const string & name, setDescription( "Flag indicating if negative pressure is allowed" ); } -void SinglePhaseWell::registerDataOnMesh( Group & meshBodies ) +SinglePhaseWell::~SinglePhaseWell() = default; + +void SinglePhaseWell::registerWellDataOnMesh( WellElementSubRegion & subRegion ) { - WellSolverBase::registerDataOnMesh( meshBodies ); + WellControls::registerDataOnMesh( subRegion ); + setConstitutiveNames ( subRegion ); - // loop over the wells - forDiscretizationOnMeshTargets( meshBodies, [&] ( string const &, - MeshLevel & mesh, - string_array const & regionNames ) + if( m_referenceFluidModelName.empty() ) { + m_referenceFluidModelName = getConstitutiveName< SingleFluidBase >( subRegion ); + } + subRegion.registerField< well::pressure >( getName() ); + subRegion.registerField< well::pressure_n >( getName() ); - ElementRegionManager & elemManager = mesh.getElemManager(); - - elemManager.forElementSubRegions< WellElementSubRegion >( regionNames, - [&]( localIndex const, - WellElementSubRegion & subRegion ) - { - subRegion.registerField< well::connectionRate_n >( getName() ); - subRegion.registerField< well::connectionRate >( getName() ); - - PerforationData & perforationData = *subRegion.getPerforationData(); - perforationData.registerField< well::perforationRate >( getName() ); - perforationData.registerField< well::dPerforationRate >( getName() ). - reference().resizeDimension< 1, 2 >( 2, 2 ); - if( isThermal() ) - { - perforationData.registerField< well::energyPerforationFlux >( getName() ); - perforationData.registerField< well::dEnergyPerforationFlux >( getName() ). - reference().resizeDimension< 1, 2 >( 2, 2 ); - } - - WellControls & wellControls = getWellControls( subRegion ); - wellControls.registerWrapper< real64 >( viewKeyStruct::currentBHPString() ); + subRegion.registerField< well::temperature >( getName() ); + if( isThermal() ) + { + subRegion.registerField< well::temperature_n >( getName() ); + } + subRegion.registerField< well::connectionRate_n >( getName() ); + subRegion.registerField< well::connectionRate >( getName() ); + subRegion.registerField< well::gravityCoefficient >( getName() ); + PerforationData & perforationData = *subRegion.getPerforationData(); + perforationData.registerField< well::gravityCoefficient >( getName() ); + + perforationData.registerField< well::perforationRate >( getName() ); + perforationData.registerField< well::dPerforationRate >( getName() ). + reference().resizeDimension< 1, 2 >( 2, 2 ); + if( isThermal() ) + { + perforationData.registerField< well::energyPerforationFlux >( getName() ); + perforationData.registerField< well::dEnergyPerforationFlux >( getName() ). + reference().resizeDimension< 1, 2 >( 2, 2 ); + perforationData.registerField< well::gravityCoefficient >( getName() ); + } - wellControls.registerWrapper< array1d< real64 > >( viewKeyStruct::dCurrentBHPString() ). - setSizedFromParent( 0 ). - reference().resizeDimension< 0 >( 2 + isThermal() ); // dP, dT , dQ + registerWrapper< real64 >( viewKeyStruct::currentBHPString() ); + registerWrapper< real64 >( viewKeyStruct::currentVolRateString() ); - wellControls.registerWrapper< real64 >( viewKeyStruct::currentVolRateString() ); - wellControls.registerWrapper< array1d< real64 > >( viewKeyStruct::dCurrentVolRateString() ). - setSizedFromParent( 0 ). - reference().resizeDimension< 0 >( 2 + isThermal() ); // dP, dT, dQ + // write rates output header + if( m_writeCSV > 0 && subRegion.isLocallyOwned()) + { + string const fileName = GEOS_FMT( "{}/{}.csv", m_ratesOutputDir, getName() ); + string const conditionKey = useSurfaceConditions() ? "surface" : "reservoir"; + string const unitKey = useSurfaceConditions() ? "s" : "r"; + // format: time,bhp,total_rate,total_vol_rate + makeDirsForPath( m_ratesOutputDir ); + GEOS_LOG( GEOS_FMT( "{}: Rates CSV generated at {}", getName(), fileName ) ); + std::ofstream outputFile( fileName ); + outputFile << "Time [s],BHP [Pa],Total rate [kg/s],Total " << conditionKey << " volumetric rate ["< 0 && subRegion.isLocallyOwned()) - { - string const fileName = GEOS_FMT( "{}/{}.csv", m_ratesOutputDir, wellControls.getName() ); - integer const useSurfaceConditions = wellControls.useSurfaceConditions(); - string const conditionKey = useSurfaceConditions ? "surface" : "reservoir"; - string const unitKey = useSurfaceConditions ? "s" : "r"; - // format: time,bhp,total_rate,total_vol_rate - makeDirsForPath( m_ratesOutputDir ); - GEOS_LOG( GEOS_FMT( "{}: Rates CSV generated at {}", getName(), fileName ) ); - std::ofstream outputFile( fileName ); - outputFile << "Time [s],BHP [Pa],Total rate [kg/s],Total " << conditionKey << " volumetric rate ["<( getFlowSolverName() ); - string_array const & targetRegionsNames = flowSolver.getTargetRegionNames(); - auto const pos = std::find( targetRegionsNames.begin(), targetRegionsNames.end(), regionName ); - GEOS_ERROR_IF( pos == targetRegionsNames.end(), - GEOS_FMT( "Region {} is not a target of the reservoir solver and cannot be used for referenceReservoirRegion in WellControl {}.", - regionName, wellControls.getName() ), - getDataContext() ); + bool const useSegmentValues = referenceReservoirRegion().empty(); + static bool firstNoRefRegionMsg = true; + if( useSegmentValues && firstNoRefRegionMsg ) + { + GEOS_WARNING( WellControls::viewKeyStruct::referenceReservoirRegionString() << + " not set: well constraint fluid property calculations will use top segement pressure and temp ", + getDataContext() ); + firstNoRefRegionMsg = false; } } - WellControls::Control currentControl = wellControls.getControl(); - real64 const targetTotalRate = wellControls.getTargetTotalRate( time_n ); - real64 const targetPhaseRate = wellControls.getTargetPhaseRate( time_n ); - GEOS_THROW_IF( currentControl == WellControls::Control::PHASEVOLRATE, - "Phase rate control is not available for SinglePhaseWell", - InputError, wellControls.getDataContext() ); - // The user always provides positive rates, but these rates are later multiplied by -1 internally for producers - GEOS_THROW_IF( ( ( wellControls.isInjector() && targetTotalRate < 0.0 ) || - ( wellControls.isProducer() && targetTotalRate > 0.0) ), - "Target total rate cannot be negative", - InputError, wellControls.getDataContext() ); - GEOS_THROW_IF( !isZero( targetPhaseRate ), - "Target phase rate cannot be used for SinglePhaseWell", - InputError, wellControls.getDataContext() ); +} + +void SinglePhaseWell::initializeWellPostInitialConditionsPreSubGroups( WellElementSubRegion & subRegion ) +{ + + // set gravity coefficient + setGravCoef( subRegion, getParent().getParent().getReference< R1Tensor >( PhysicsSolverManager::viewKeyStruct::gravityVectorString() )); + + // setup fluid model + createSeparator( subRegion ); +} +void SinglePhaseWell::initializePostInitialConditionsPreSubGroups() +{ + WellControls::initializePostInitialConditionsPreSubGroups(); +} +void SinglePhaseWell::postRestartInitialization( ) +{ + // setup fluid separator + constitutive::SingleFluidBase & fluidSeparator = getSingleFluidSeparator(); + fluidSeparator.allocateConstitutiveData( *this, 1 ); + fluidSeparator.resize( 1 ); + +} +void SinglePhaseWell::createSeparator( WellElementSubRegion & subRegion ) +{ + + string const & fluidName = subRegion.getReference< string >( viewKeyStruct::fluidNamesString() ); + SingleFluidBase & fluid = subRegion.getConstitutiveModel< SingleFluidBase >( fluidName ); + // setup fluid separator + + string const fluidSeparatorName = getName() + "Separator"; + std::unique_ptr< constitutive::ConstitutiveBase > fluidSeparatorPtr = fluid.deliverClone( fluidSeparatorName, this ); + fluidSeparatorPtr->allocateConstitutiveData( *this, 1 ); + fluidSeparatorPtr->resize( 1 ); + setFluidSeparator( std::move( fluidSeparatorPtr )); + } void SinglePhaseWell::updateBHPForConstraint( WellElementSubRegion & subRegion ) @@ -193,7 +260,6 @@ void SinglePhaseWell::updateBHPForConstraint( WellElementSubRegion & subRegion ) return; } - localIndex const iwelemRef = subRegion.getTopWellElementIndex(); // subRegion data @@ -210,47 +276,29 @@ void SinglePhaseWell::updateBHPForConstraint( WellElementSubRegion & subRegion ) arrayView2d< real64 const, constitutive::singlefluid::USD_FLUID > const & dens = fluid.density(); arrayView3d< real64 const, constitutive::singlefluid::USD_FLUID_DER > const & dDens = fluid.dDensity(); - // control data - - WellControls & wellControls = getWellControls( subRegion ); - string const wellControlsName = wellControls.getName(); - real64 const & refGravCoef = wellControls.getReferenceGravityCoef(); - - real64 & currentBHP = - wellControls.getReference< real64 >( SinglePhaseWell::viewKeyStruct::currentBHPString() ); - arrayView1d< real64 > const & dCurrentBHP = - wellControls.getReference< array1d< real64 > >( SinglePhaseWell::viewKeyStruct::dCurrentBHPString() ); - - geos::internal::kernelLaunchSelectorThermalSwitch( isThermal(), [&] ( auto ISTHERMAL ) + real64 const refGravCoef = getBHPReferenceGravityCoef( *this, ConstraintSourceId::USER ); + real64 & currentBHP = getReference< real64 >( SinglePhaseWell::viewKeyStruct::currentBHPString() ); + + // bring everything back to host, capture the scalars by reference + forAll< serialPolicy >( 1, [pres, + dens, + dDens, + wellElemGravCoef, + ¤tBHP, + &iwelemRef, + &refGravCoef] ( localIndex const ) { - integer constexpr IS_THERMAL = ISTHERMAL(); - // bring everything back to host, capture the scalars by reference - forAll< serialPolicy >( 1, [pres, - dens, - dDens, - wellElemGravCoef, - ¤tBHP, - &dCurrentBHP, - &iwelemRef, - &refGravCoef] ( localIndex const ) - { - real64 const diffGravCoef = refGravCoef - wellElemGravCoef[iwelemRef]; - currentBHP = pres[iwelemRef] + dens[iwelemRef][0] * diffGravCoef; - dCurrentBHP[DerivOffset::dP] = 1.0 + dDens[iwelemRef][0][DerivOffset::dP] *diffGravCoef; - if constexpr ( IS_THERMAL ) - { - dCurrentBHP[DerivOffset::dT] = dDens[iwelemRef][0][DerivOffset::dT] * diffGravCoef; - } - } ); + real64 const diffGravCoef = refGravCoef - wellElemGravCoef[iwelemRef]; + currentBHP = pres[iwelemRef] + dens[iwelemRef][0] * diffGravCoef; } ); GEOS_LOG_LEVEL_BY_RANK( logInfo::WellControl, GEOS_FMT( "{}: The BHP (at the specified reference elevation) = {} Pa", - wellControlsName, currentBHP ) ); + getName(), currentBHP ) ); } -void SinglePhaseWell::updateVolRateForConstraint( ElementRegionManager const & elemManager, WellElementSubRegion & subRegion ) +void SinglePhaseWell::calculateReferenceElementRates( WellElementSubRegion & subRegion ) { GEOS_MARK_FUNCTION; @@ -264,649 +312,719 @@ void SinglePhaseWell::updateVolRateForConstraint( ElementRegionManager const & e // subRegion data - arrayView1d< real64 const > const pres = - subRegion.getField< well::pressure >(); - arrayView1d< real64 const > const & connRate = subRegion.getField< well::connectionRate >(); // fluid data + constitutive::SingleFluidBase & fluidSeparator = getSingleFluidSeparator(); + arrayView2d< real64 const, constitutive::singlefluid::USD_FLUID > const & dens = fluidSeparator.density(); + + real64 & currentVolRate = + getReference< real64 >( WellControls::viewKeyStruct::currentVolRateString() ); + + // bring everything back to host, capture the scalars by reference + forAll< serialPolicy >( 1, [connRate, + dens, + ¤tVolRate, + &iwelemRef] ( localIndex const ) + { + real64 const densInv = 1.0 / dens[iwelemRef][0]; + currentVolRate = connRate[iwelemRef] * densInv; + // compute mass rate + } ); + + +} + +void SinglePhaseWell::updateFluidModel( WellElementSubRegion & subRegion ) const +{ + GEOS_MARK_FUNCTION; + + arrayView1d< real64 const > const pres = subRegion.getField< well::pressure >(); + arrayView1d< real64 const > const temp = subRegion.getField< well::temperature >(); string const & fluidName = subRegion.getReference< string >( viewKeyStruct::fluidNamesString() ); SingleFluidBase & fluid = subRegion.getConstitutiveModel< SingleFluidBase >( fluidName ); - arrayView2d< real64 const, constitutive::singlefluid::USD_FLUID > const & dens = fluid.density(); - arrayView3d< real64 const, constitutive::singlefluid::USD_FLUID_DER > const & dDens = fluid.dDensity(); - // control data + constitutiveUpdatePassThru( fluid, [&]( auto & castedFluid ) + { + typename TYPEOFREF( castedFluid ) ::KernelWrapper fluidWrapper = castedFluid.createKernelWrapper(); + singlePhaseBaseKernels::FluidUpdateKernel::launch( fluidWrapper, pres, temp ); + } ); +} +void SinglePhaseWell::updateSeparator( ElementRegionManager const & elemManager, WellElementSubRegion & subRegion ) +{ + GEOS_MARK_FUNCTION; + GEOS_UNUSED_VAR( elemManager ); - WellControls & wellControls = getWellControls( subRegion ); - string const wellControlsName = wellControls.getName(); - bool const logSurfaceCondition = isLogLevelActive< logInfo::WellControl >( wellControls.getLogLevel()); - integer const useSurfaceConditions = wellControls.useSurfaceConditions(); - real64 flashPressure; - if( useSurfaceConditions ) + // the rank that owns the reference well element is responsible for the calculations below. + if( !subRegion.isLocallyOwned() ) { - // use surface conditions - flashPressure = wellControls.getSurfacePressure(); + return; } - else - { - if( !wellControls.referenceReservoirRegion().empty() ) - { - ElementRegionBase const & region = elemManager.getRegion( wellControls.referenceReservoirRegion() ); - GEOS_ERROR_IF ( !region.hasWrapper( SinglePhaseStatistics::regionStatisticsName()), - GEOS_FMT( "WellControl {} referenceReservoirRegion field requires SinglePhaseStatistics to be configured for region {} ", - wellControls.getName(), wellControls.referenceReservoirRegion() ), - getDataContext() ); - SinglePhaseStatistics::RegionStatistics const & stats = region.getReference< SinglePhaseStatistics::RegionStatistics >( SinglePhaseStatistics::regionStatisticsName() ); - GEOS_ERROR_IF( stats.averagePressure <= 0.0, - GEOS_FMT( - "No region average quantities computed. WellControl {} referenceReservoirRegion field requires SinglePhaseStatistics to be configured for region {} ", - wellControls.getName(), wellControls.referenceReservoirRegion() ), - getDataContext()); - wellControls.setRegionAveragePressure( stats.averagePressure ); - wellControls.setRegionAverageTemperature( stats.averageTemperature ); - } - // use region conditions - flashPressure = wellControls.getRegionAveragePressure(); - if( flashPressure < 0.0 ) - { - // use segment conditions - flashPressure = pres[iwelemRef]; - } - } - real64 & currentVolRate = - wellControls.getReference< real64 >( SinglePhaseWell::viewKeyStruct::currentVolRateString() ); + localIndex const iwelemRef = subRegion.getTopWellElementIndex(); - arrayView1d< real64 > const & dCurrentVolRate = - wellControls.getReference< array1d< real64 > >( SinglePhaseWell::viewKeyStruct::dCurrentVolRateString() ); + constitutive::SingleFluidBase & fluidSeparator = getSingleFluidSeparator(); + string const wellControlsName = getName(); + bool const logSurfaceCondition = isLogLevelActive< logInfo::WellControl >( getLogLevel()); + integer const useSurfCond = useSurfaceConditions(); + ReferenceConditions const refConditions = getReferenceConditions( subRegion ); - constitutiveUpdatePassThru( fluid, [&]( auto & castedFluid ) + constitutiveUpdatePassThru( fluidSeparator, [&]( auto & castedFluid ) { - typename TYPEOFREF( castedFluid ) ::KernelWrapper fluidWrapper = castedFluid.createKernelWrapper(); + typename TYPEOFREF( castedFluid ) ::KernelWrapper fluidSeparatorWrapper = castedFluid.createKernelWrapper(); geos::internal::kernelLaunchSelectorThermalSwitch( isThermal(), [&] ( auto ISTHERMAL ) { integer constexpr IS_THERMAL = ISTHERMAL(); - using COFFSET_WJ = singlePhaseWellKernels::ColOffset_WellJac< IS_THERMAL >; + GEOS_UNUSED_VAR( IS_THERMAL ); // bring everything back to host, capture the scalars by reference - forAll< serialPolicy >( 1, [fluidWrapper, - pres, - connRate, - dens, - dDens, + forAll< serialPolicy >( 1, [fluidSeparatorWrapper, logSurfaceCondition, - &useSurfaceConditions, - &flashPressure, - ¤tVolRate, - dCurrentVolRate, + &useSurfCond, + refConditions, &iwelemRef, &wellControlsName] ( localIndex const ) { - // We need to evaluate the density as follows: - // - Surface conditions: using the surface pressure provided by the user - // - Reservoir conditions: using the pressure in the top element - - if( useSurfaceConditions ) + // Refresh separator properties at the selected control/reference conditions. + if constexpr ( IS_THERMAL ) { - // we need to compute the surface density - fluidWrapper.update( iwelemRef, 0, flashPressure ); - if( logSurfaceCondition ) - { - - GEOS_LOG_RANK( GEOS_FMT( "{}: surface density computed with P_surface = {} Pa", - wellControlsName, flashPressure ) ); - } - -#ifdef GEOS_USE_HIP - GEOS_UNUSED_VAR( wellControlsName ); -#endif - + fluidSeparatorWrapper.update( iwelemRef, 0, refConditions.pressure, refConditions.temperature ); } else { - real64 const refPres = pres[iwelemRef]; - fluidWrapper.update( iwelemRef, 0, refPres ); + fluidSeparatorWrapper.update( iwelemRef, 0, refConditions.pressure ); } - real64 const densInv = 1.0 / dens[iwelemRef][0]; - currentVolRate = connRate[iwelemRef] * densInv; - - dCurrentVolRate[COFFSET_WJ::dP] = -( useSurfaceConditions == 0 ) * dDens[iwelemRef][0][DerivOffset::dP] * currentVolRate * densInv; - dCurrentVolRate[COFFSET_WJ::dQ] = densInv; - if constexpr ( IS_THERMAL ) - { - dCurrentVolRate[COFFSET_WJ::dT] = -( useSurfaceConditions == 0 ) * dDens[iwelemRef][0][DerivOffset::dT] * currentVolRate * densInv; - } - if( logSurfaceCondition && useSurfaceConditions ) + if( useSurfCond && logSurfaceCondition ) { - GEOS_LOG_RANK( GEOS_FMT( "{}: total fluid density at surface conditions = {} kg/sm3, total rate = {} kg/s, total surface volumetric rate = {} sm3/s", - wellControlsName, dens[iwelemRef][0], connRate[iwelemRef], currentVolRate ) ); + GEOS_LOG_RANK( GEOS_FMT( "{}: surface density computed with P_surface = {} Pa and T_surface = {} K", + wellControlsName, refConditions.pressure, refConditions.temperature ) ); } + +#ifdef GEOS_USE_HIP + GEOS_UNUSED_VAR( wellControlsName ); +#endif } ); } ); } ); } -void SinglePhaseWell::updateFluidModel( WellElementSubRegion & subRegion ) const +void SinglePhaseWell::precomputeReferenceConditions( real64 const time_n, + Group & meshBodies, + MeshBody & meshBody, + WellElementSubRegion const & subRegion ) { - GEOS_MARK_FUNCTION; + GEOS_UNUSED_VAR( subRegion ); + if( !useSurfaceConditions() ) + { + string_view refRegionName = referenceReservoirRegion(); + bool const useSegmentValues = refRegionName.empty(); + if( useSegmentValues ) + { + setRegionAveragePressure( -1 ); + setRegionAverageTemperature( -1 ); + } + else + { + auto & flowSolver = getParent().getGroup< SinglePhaseBase >( getFlowSolverName() ); + MeshLevel & flowMeshLevel = meshBody.getMeshLevel( flowSolver.getDiscretizationName() ); + + if( !m_reservoirStatsAggregator ) + { // lazily initialize the region statistics aggregator + m_reservoirStatsAggregator = std::make_unique< StatsAggregator >( getDataContext(), + meshBodies, + false ); + m_reservoirStatsAggregator->initStatisticsAggregation( flowSolver ); + m_reservoirStatsAggregator->enableRegionStatisticsAggregation(); + } - arrayView1d< real64 const > const pres = subRegion.getField< well::pressure >(); - arrayView1d< real64 const > const temp = subRegion.getField< well::temperature >(); + RegionStatistics & stats = m_reservoirStatsAggregator->getRegionStatistics( flowMeshLevel, refRegionName ); - string const & fluidName = subRegion.getReference< string >( viewKeyStruct::fluidNamesString() ); - SingleFluidBase & fluid = subRegion.getConstitutiveModel< SingleFluidBase >( fluidName ); + // compute region stats only if needed (could have already been done for another subRegion) + if( !m_reservoirStatsAggregator->isComputed( time_n, stats ) ) + m_reservoirStatsAggregator->computeRegionsStatistics( time_n ); - constitutiveUpdatePassThru( fluid, [&]( auto & castedFluid ) - { - typename TYPEOFREF( castedFluid ) ::KernelWrapper fluidWrapper = castedFluid.createKernelWrapper(); - singlePhaseBaseKernels::FluidUpdateKernel::launch( fluidWrapper, pres, temp ); - } ); + GEOS_WARNING_IF( stats.m_averagePressure <= 0.0, + GEOS_FMT( "No region average quantities computed in reference region '{}'.", + referenceReservoirRegion() ), + getWrapperDataContext( WellControls::viewKeyStruct::referenceReservoirRegionString() ), + getDataContext() ); + + setRegionAveragePressure( stats.m_averagePressure ); + setRegionAverageTemperature( stats.m_averageTemperature ); + } + } } -real64 SinglePhaseWell::updateSubRegionState( ElementRegionManager const & elemManager, WellElementSubRegion & subRegion ) +SinglePhaseWell::ReferenceConditions +SinglePhaseWell::getReferenceConditions( WellElementSubRegion const & subRegion ) { - // update volumetric rates for the well constraints - // Warning! This must be called before updating the fluid model - updateVolRateForConstraint( elemManager, subRegion ); - - // update density in the well elements - updateFluidModel( subRegion ); + if( useSurfaceConditions() ) + { + // use surface conditions + return { + /* .pressure = */ getSurfacePressure(), + /* .temperature = */ getSurfaceTemperature(), + }; + } + else + { + if( getRegionAveragePressure() > 0.0 && getRegionAverageTemperature() > 0.0 ) + { // reference region condition properly computed, we can return them + return { + /* .pressure = */ getRegionAveragePressure(), + /* .temperature = */ getRegionAverageTemperature(), + }; + } + else + { // region average stats not initialized or initialized, fallback to top segment values + static bool firstNoRefRegionMsg = true; + if( firstNoRefRegionMsg ) + { + GEOS_WARNING( "SinglePhaseWell: region average statsistics of reference region not initialized," + " fallback to top segment values.", + getDataContext() ); + firstNoRefRegionMsg=false; + } - // update the current BHP - updateBHPForConstraint( subRegion ); - // note: the perforation rates are updated separately - return 0.0; // change in phasevolume fraction doesnt apply + arrayView1d< real64 const > const & pres = subRegion.getField< well::pressure >(); + arrayView1d< real64 const > const & temp = subRegion.getField< well::temperature >(); + localIndex const iwelemRef = subRegion.getTopWellElementIndex(); + return { + /* .pressure = */ pres[iwelemRef], + /* .temperature = */ temp[iwelemRef], + }; + } + } } -void SinglePhaseWell::initializeWells( DomainPartition & domain, real64 const & time_n ) +real64 SinglePhaseWell::updateSubRegionState( real64 const time_n, + MeshBody const & meshBody, + ElementRegionManager const & elemManager, + WellElementSubRegion & subRegion ) { - GEOS_MARK_FUNCTION; GEOS_UNUSED_VAR( time_n ); + GEOS_UNUSED_VAR( meshBody ); - // loop over the wells - forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&] ( string const &, - MeshLevel & meshLevel, - string_array const & regionNames ) + if( getWellState()) { - ElementRegionManager & elemManager = meshLevel.getElemManager(); - elemManager.forElementSubRegions< WellElementSubRegion >( regionNames, - [&]( localIndex const, - WellElementSubRegion & subRegion ) + // update volumetric rates for the well constraints + // Warning! This must be called before updating the fluid model + //calculateReferenceElementRates( subRegion ); + + // update density in the well elements + updateFluidModel( subRegion ); + updateSeparator( elemManager, subRegion ); // Calculate fluid properties at control conditions + + // Calculate the reference element rates + calculateReferenceElementRates( subRegion ); + // update the current BHP + updateBHPForConstraint( subRegion ); + + // Broad case the updated well state to other ranks + // TODO: add the missing getters on SinglePhaseWell & WellElementSubRegion because look-up is not useful here. + real64 & currentBHP = + getReference< real64 >( SinglePhaseWell::viewKeyStruct::currentBHPString() ); + real64 & currentTotalVolRate = + getReference< real64 >( SinglePhaseWell::viewKeyStruct::currentVolRateString() ); + integer topRank = + subRegion.getReference< integer >( WellElementSubRegion::viewKeyStruct::topRankString() ); + MpiWrapper::broadcast( currentBHP, topRank ); + MpiWrapper::broadcast( currentTotalVolRate, topRank ); + WellConstraintBase * constraint = getCurrentConstraint(); + if( constraint != nullptr ) { - WellControls const & wellControls = getWellControls( subRegion ); - PerforationData const & perforationData = *subRegion.getPerforationData(); - - // get the info stored on well elements - arrayView1d< real64 const > const wellElemGravCoef = - subRegion.getField< well::gravityCoefficient >(); - - // get well primary variables on well elements - arrayView1d< real64 > const wellElemPressure = - subRegion.getField< well::pressure >(); - arrayView1d< real64 > const connRate = - subRegion.getField< well::connectionRate >(); - arrayView1d< real64 > const wellElemTemperature = - subRegion.getField< well::temperature >(); - // get the element region, subregion, index - arrayView1d< localIndex const > const resElementRegion = - perforationData.getField< perforation::reservoirElementRegion >(); - arrayView1d< localIndex const > const resElementSubRegion = - perforationData.getField< perforation::reservoirElementSubRegion >(); - arrayView1d< localIndex const > const resElementIndex = - perforationData.getField< perforation::reservoirElementIndex >(); - - arrayView1d< real64 const > const & perfGravCoef = - perforationData.getField< well::gravityCoefficient >(); - - bool const hasNonZeroRate = MpiWrapper::max< integer >( hasNonZero( connRate )); - - if( wellControls.isWellOpen() && !hasNonZeroRate ) - { - // TODO: change the way we access the flowSolver here - SinglePhaseBase const & flowSolver = getParent().getGroup< SinglePhaseBase >( getFlowSolverName() ); - PresTempInitializationKernel::SinglePhaseFlowAccessors resSinglePhaseFlowAccessors( meshLevel.getElemManager(), flowSolver.getName() ); - PresTempInitializationKernel::SingleFluidAccessors resSingleFluidAccessors( meshLevel.getElemManager(), flowSolver.getName() ); - - // 1) Loop over all perforations to compute an average density - // 2) Initialize the reference pressure - // 3) Estimate the pressures in the well elements using the average density - PresTempInitializationKernel:: - launch( isThermal(), - perforationData.size(), - subRegion.size(), - perforationData.getNumPerforationsGlobal(), - wellControls, - 0.0, // initialization done at t = 0 - resSinglePhaseFlowAccessors.get( flow::pressure{} ), - resSinglePhaseFlowAccessors.get( flow::temperature{} ), - resSingleFluidAccessors.get( fields::singlefluid::density{} ), - resElementRegion, - resElementSubRegion, - resElementIndex, - perfGravCoef, - wellElemGravCoef, - wellElemPressure, - wellElemTemperature ); - - // 4) Recompute the pressure-dependent properties - // Note: I am leaving that here because I would like to use the perforationRates (computed in UpdateState) - // to better initialize the rates - updateSubRegionState( elemManager, subRegion ); - - string const & fluidName = subRegion.getReference< string >( viewKeyStruct::fluidNamesString() ); - SingleFluidBase & fluid = subRegion.getConstitutiveModel< SingleFluidBase >( fluidName ); - arrayView2d< real64 const, constitutive::singlefluid::USD_FLUID > const & wellElemDens = fluid.density(); - - // 5) Estimate the well rates - RateInitializationKernel::launch( subRegion.size(), - wellControls, - 0.0, // initialization done at t = 0 - wellElemDens, - connRate ); - } - - } ); - - } ); + constraint->setBHP ( getReference< real64 >( SinglePhaseWell::viewKeyStruct::currentBHPString() )); + constraint->setTotalVolumeRate ( getReference< real64 >( + SinglePhaseWell::viewKeyStruct::currentVolRateString() )); + } + } + return 0.0; // change in phasevolume fraction doesnt apply } -void SinglePhaseWell::shutDownWell( real64 const time_n, - DomainPartition const & domain, - DofManager const & dofManager, - CRSMatrixView< real64, globalIndex const > const & localMatrix, - arrayView1d< real64 > const & localRhs ) +void SinglePhaseWell::initializeWell( DomainPartition & domain, Group & meshBodies, string const & meshBodyName, MeshLevel & mesh, WellElementSubRegion & subRegion, real64 const & time_n ) { - GEOS_MARK_FUNCTION; - GEOS_UNUSED_VAR( time_n ); + GEOS_UNUSED_VAR( domain ); - string const wellDofKey = dofManager.getKey( wellElementDofName() ); + PerforationData const & perforationData = *subRegion.getPerforationData(); + ElementRegionManager const & elemManager = mesh.getElemManager(); + // get the info stored on well elements + arrayView1d< real64 const > const wellElemGravCoef = + subRegion.getField< well::gravityCoefficient >(); - forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&] ( string const &, - MeshLevel const & mesh, - string_array const & regionNames ) + // get well primary variables on well elements + arrayView1d< real64 > const wellElemPressure = + subRegion.getField< well::pressure >(); + arrayView1d< real64 > const connRate = + subRegion.getField< well::connectionRate >(); + arrayView1d< real64 > const wellElemTemperature = + subRegion.getField< well::temperature >(); + // get the element region, subregion, index + arrayView1d< localIndex const > const resElementRegion = + perforationData.getField< perforation::reservoirElementRegion >(); + arrayView1d< localIndex const > const resElementSubRegion = + perforationData.getField< perforation::reservoirElementSubRegion >(); + arrayView1d< localIndex const > const resElementIndex = + perforationData.getField< perforation::reservoirElementIndex >(); + + arrayView1d< real64 const > const & perfGravCoef = + perforationData.getField< well::gravityCoefficient >(); + + bool const hasNonZeroRate = MpiWrapper::max< integer >( hasNonZero( connRate )); + + if( time_n <= 0.0 || ( isWellOpen() && !hasNonZeroRate ) ) { - - ElementRegionManager const & elemManager = mesh.getElemManager(); - - elemManager.forElementSubRegions< WellElementSubRegion >( regionNames, - [&]( localIndex const, - WellElementSubRegion const & subRegion ) + setWellState( true ); + if( getCurrentConstraint() == nullptr ) { - - // if the well is open, we don't have to do anything, so we just return - WellControls const & wellControls = getWellControls( subRegion ); - if( wellControls.isWellOpen( ) ) + if( isProducer() ) { - return; + forSubGroups< MinimumBHPConstraint, ProductionConstraint< VolumeRateConstraint >, ProductionConstraint< MassRateConstraint >, + ProductionConstraint< PhaseVolumeRateConstraint > >( [&]( auto & constraint ) + { + if( ConstraintTypeId( getControl()) == constraint.getControl() ) + { + setCurrentConstraint( &constraint ); + } + } ); } + else + { + forSubGroups< MaximumBHPConstraint, InjectionConstraint< VolumeRateConstraint >, InjectionConstraint< MassRateConstraint >, + InjectionConstraint< PhaseVolumeRateConstraint > >( [&]( auto & constraint ) + { + if( ConstraintTypeId( getControl()) == constraint.getControl() ) + { + setCurrentConstraint( &constraint ); + } + } ); + } + } - globalIndex const rankOffset = dofManager.rankOffset(); - - arrayView1d< integer const > const ghostRank = - subRegion.getReference< array1d< integer > >( ObjectManagerBase::viewKeyStruct::ghostRankString() ); - arrayView1d< globalIndex const > const dofNumber = - subRegion.getReference< array1d< globalIndex > >( wellDofKey ); - - arrayView1d< real64 const > const pres = - subRegion.getField< fields::well::pressure >(); - arrayView1d< real64 const > const connRate = - subRegion.getField< fields::well::connectionRate >(); - - forAll< parallelDevicePolicy<> >( subRegion.size(), [=] GEOS_HOST_DEVICE ( localIndex const ei ) + PresTempInitializationKernel::SinglePhaseFlowAccessors resSinglePhaseFlowAccessors( elemManager, getFlowSolverName()); + PresTempInitializationKernel::SingleFluidAccessors resSingleFluidAccessors( elemManager, getFlowSolverName() ); + real64 const refWellElemGravCoef = getBHPReferenceGravityCoef( *this, ConstraintSourceId::USER ); + // 1) Loop over all perforations to compute an average density + // 2) Initialize the reference pressure + // 3) Estimate the pressures in the well elements using the average density + PresTempInitializationKernel:: + launch( isThermal(), + perforationData.size(), + subRegion.size(), + perforationData.getNumPerforationsGlobal(), + *this, + refWellElemGravCoef, + 0.0, // initialization done at t = 0 + resSinglePhaseFlowAccessors.get( flow::pressure{} ), + resSinglePhaseFlowAccessors.get( flow::temperature{} ), + resSingleFluidAccessors.get( fields::singlefluid::density{} ), + resElementRegion, + resElementSubRegion, + resElementIndex, + perfGravCoef, + wellElemGravCoef, + wellElemPressure, + wellElemTemperature ); + + // 4) Recompute the pressure-dependent properties + // Note: I am leaving that here because I would like to use the perforationRates (computed in UpdateState) + // to better initialize the rates + MeshBody & meshBody = domain.getMeshBody( meshBodyName ); + precomputeReferenceConditions( time_n, meshBodies, meshBody, subRegion ); + updateSubRegionState( time_n, meshBody, elemManager, subRegion ); + + string const & fluidName = subRegion.getReference< string >( viewKeyStruct::fluidNamesString() ); + SingleFluidBase & fluid = subRegion.getConstitutiveModel< SingleFluidBase >( fluidName ); + arrayView2d< real64 const, constitutive::singlefluid::USD_FLUID > const & wellElemDens = fluid.density(); + + // 5) Estimate the well rates + RateInitializationKernel::launch( subRegion.size(), + *this, + 0.0, // initialization done at t = 0 + wellElemDens, + connRate ); + + calculateReferenceElementRates( subRegion ); + WellConstraintBase * constraint = getCurrentConstraint(); + constraint->setBHP ( getReference< real64 >( WellControls::viewKeyStruct::currentBHPString() )); + constraint->setTotalVolumeRate ( getReference< real64 >( + SinglePhaseWell::viewKeyStruct::currentVolRateString() )); + // 7) Copy well / fluid dofs to "prop"_n variables + saveState( subRegion ); + } + else if( !hasNonZeroRate ) + { + setWellState( false ); + } + else + { + setWellState( true ); + // setup for restart + if( getCurrentConstraint() == nullptr ) + { + if( isProducer() ) { - if( ghostRank[ei] >= 0 ) + forSubGroups< MinimumBHPConstraint, ProductionConstraint< VolumeRateConstraint >, ProductionConstraint< MassRateConstraint >, + ProductionConstraint< PhaseVolumeRateConstraint > >( [&]( + auto + & constraint ) { - return; - } + if( ConstraintTypeId( getControl()) == constraint.getControl() ) + { + setCurrentConstraint( &constraint ); + } + } ); + } + else + { + forSubGroups< MaximumBHPConstraint, InjectionConstraint< VolumeRateConstraint >, InjectionConstraint< MassRateConstraint >, InjectionConstraint< PhaseVolumeRateConstraint > >( [&]( + auto + & + constraint ) + { + if( ConstraintTypeId( getControl()) == constraint.getControl() ) + { + setCurrentConstraint( &constraint ); + } + } ); + } + updateSubRegionState( time_n, domain.getMeshBody( meshBodyName ), elemManager, subRegion ); + } - globalIndex const dofIndex = dofNumber[ei]; - localIndex const localRow = dofIndex - rankOffset; - real64 rhsValue; - - // 4.1. Apply pressure value to the matrix/rhs - FieldSpecificationEqual::SpecifyFieldValue( dofIndex, - rankOffset, - localMatrix, - rhsValue, - pres[ei], // freeze the current pressure value - pres[ei] ); - localRhs[localRow] = rhsValue; - - // 4.2. Apply rate value to the matrix/rhs - FieldSpecificationEqual::SpecifyFieldValue( dofIndex + 1, - rankOffset, - localMatrix, - rhsValue, - connRate[ei], // freeze the current pressure value - connRate[ei] ); - localRhs[localRow + 1] = rhsValue; + } - } ); - } ); - } ); } -void SinglePhaseWell::assembleSystem( real64 const time, - real64 const dt, - DomainPartition & domain, - DofManager const & dofManager, - CRSMatrixView< real64, globalIndex const > const & localMatrix, - arrayView1d< real64 > const & localRhs ) +real64 SinglePhaseWell::updateWellState( MeshBody const & meshBody, + ElementRegionManager const & elemManager, + WellElementSubRegion & subRegion ) { - string const wellDofKey = dofManager.getKey( wellElementDofName()); + GEOS_MARK_FUNCTION; - // assemble the accumulation term in the mass balance equations - assembleAccumulationTerms( time, dt, domain, dofManager, localMatrix, localRhs ); + updateSubRegionState( -1.0, meshBody, elemManager, subRegion ); + return 0.0; +} - // then assemble the pressure relations between well elements - assemblePressureRelations( time, dt, domain, dofManager, localMatrix, localRhs ); - // then compute the perforation rates (later assembled by the coupled solver) - computePerforationRates( time, dt, domain ); +void SinglePhaseWell::assembleWellFluxTerms( real64 const & time, + real64 const & dt, + WellElementSubRegion const & subRegion, + DofManager const & dofManager, + CRSMatrixView< real64, globalIndex const > const & localMatrix, + arrayView1d< real64 > const & localRhs ) +{ + GEOS_MARK_FUNCTION; + GEOS_UNUSED_VAR( time ); - // then assemble the flux terms in the mass balance equations // get a reference to the degree-of-freedom numbers - // then assemble the flux terms in the mass balance equations - assembleFluxTerms( time, dt, domain, dofManager, localMatrix, localRhs ); + string const wellDofKey = dofManager.getKey( wellElementDofName() ); + + if( isThermal() ) + { + string const & fluidName = subRegion.getReference< string >( viewKeyStruct::fluidNamesString() ); + SingleFluidBase const & fluid = subRegion.getConstitutiveModel< SingleFluidBase >( fluidName ); + thermalSinglePhaseWellKernels:: + FaceBasedAssemblyKernelFactory:: + createAndLaunch< parallelDevicePolicy<> >( dt, + dofManager.rankOffset(), + wellDofKey, + *this, + subRegion, + fluid, + localMatrix, + localRhs ); + } + else + { + singlePhaseWellKernels:: + FaceBasedAssemblyKernelFactory:: + createAndLaunch< parallelDevicePolicy<> >( dt, + dofManager.rankOffset(), + wellDofKey, + *this, + subRegion, + localMatrix, + localRhs ); + } - // then apply a special treatment to the wells that are shut - shutDownWell( time, domain, dofManager, localMatrix, localRhs ); } -void SinglePhaseWell::assembleFluxTerms( real64 const & time_n, - real64 const & dt, - DomainPartition & domain, - DofManager const & dofManager, - CRSMatrixView< real64, globalIndex const > const & localMatrix, - arrayView1d< real64 > const & localRhs ) + +void SinglePhaseWell::assembleWellConstraintTerms( real64 const & time_n, + real64 const & GEOS_UNUSED_PARAM( dt ), + WellElementSubRegion const & subRegion, + DofManager const & dofManager, + CRSMatrixView< real64, globalIndex const > const & localMatrix, + arrayView1d< real64 > const & localRhs ) { GEOS_MARK_FUNCTION; - GEOS_UNUSED_VAR( time_n ); - GEOS_UNUSED_VAR( dt ); - // loop over the wells - forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&] ( string const &, - MeshLevel const & mesh, - string_array const & regionNames ) - { - ElementRegionManager const & elemManager = mesh.getElemManager(); + // the rank that owns the reference well element is responsible for the calculations below. - elemManager.forElementSubRegions< WellElementSubRegion >( regionNames, - [&]( localIndex const, - WellElementSubRegion const & subRegion ) + if( !subRegion.isLocallyOwned() || !( getWellStatus() == WellControls::Status::OPEN )) + { + return; + } + { + forSubGroups< MinimumBHPConstraint, MaximumBHPConstraint, InjectionConstraint< VolumeRateConstraint >, ProductionConstraint< VolumeRateConstraint > >( [&]( auto & constraint ) { + if( constraint.getName() == getCurrentConstraint()->getName()) + { + // found limiting constraint - WellControls const & wellControls = getWellControls( subRegion ); - // get a reference to the degree-of-freedom numbers - string const wellDofKey = dofManager.getKey( wellElementDofName() ); + // fluid data + constitutive::SingleFluidBase & fluidSeparator = getSingleFluidSeparator(); + integer isThermal = fluidSeparator.isThermal(); - if( isThermal() ) - { - string const & fluidName = subRegion.getReference< string >( viewKeyStruct::fluidNamesString() ); - SingleFluidBase const & fluid = subRegion.getConstitutiveModel< SingleFluidBase >( fluidName ); - thermalSinglePhaseWellKernels:: - FaceBasedAssemblyKernelFactory:: - createAndLaunch< parallelDevicePolicy<> >( dt, - dofManager.rankOffset(), - wellDofKey, - wellControls, - subRegion, - fluid, - localMatrix, - localRhs ); - } - else - { - singlePhaseWellKernels:: - FaceBasedAssemblyKernelFactory:: - createAndLaunch< parallelDevicePolicy<> >( dt, - dofManager.rankOffset(), - wellDofKey, - wellControls, - subRegion, - localMatrix, - localRhs ); + geos::internal::kernelLaunchSelectorThermalSwitch( isThermal, [&] ( auto ISTHERMAL ) + { + integer constexpr IS_THERMAL = ISTHERMAL(); + + singlePhaseWellConstraintKernels::ConstraintHelper< IS_THERMAL >::assembleConstraintEquation( time_n, + *this, + constraint, + subRegion, + dofManager.getKey( wellElementDofName() ), + dofManager.rankOffset(), + localMatrix, + localRhs ); + } ); } } ); + } - } ); } -void SinglePhaseWell::assemblePressureRelations( real64 const & time_n, - real64 const & GEOS_UNUSED_PARAM( dt ), - DomainPartition const & domain, - DofManager const & dofManager, - CRSMatrixView< real64, globalIndex const > const & localMatrix, - arrayView1d< real64 > const & localRhs ) +void SinglePhaseWell::assembleWellPressureRelations( real64 const & GEOS_UNUSED_PARAM( time_n ), + real64 const & GEOS_UNUSED_PARAM( dt ), + WellElementSubRegion const & subRegion, + DofManager const & dofManager, + CRSMatrixView< real64, globalIndex const > const & localMatrix, + arrayView1d< real64 > const & localRhs ) { - GEOS_MARK_FUNCTION; - - forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&] ( string const &, - MeshLevel const & mesh, - string_array const & regionNames ) - { - ElementRegionManager const & elemManager = mesh.getElemManager(); + // get the degrees of freedom numbers, depth, next well elem index + string const wellDofKey = dofManager.getKey( wellElementDofName() ); + arrayView1d< globalIndex const > const & wellElemDofNumber = + subRegion.getReference< array1d< globalIndex > >( wellDofKey ); + arrayView1d< real64 const > const & wellElemGravCoef = + subRegion.getField< well::gravityCoefficient >(); + arrayView1d< localIndex const > const & nextWellElemIndex = + subRegion.getReference< array1d< localIndex > >( WellElementSubRegion::viewKeyStruct::nextWellElementIndexString() ); + // get primary variables on well elements + arrayView1d< real64 const > const & wellElemPressure = + subRegion.getField< well::pressure >(); - elemManager.forElementSubRegions< WellElementSubRegion >( regionNames, - [&]( localIndex const, - WellElementSubRegion const & subRegion ) - { + // get well constitutive data + string const & fluidName = subRegion.getReference< string >( viewKeyStruct::fluidNamesString() ); + SingleFluidBase const & fluid = subRegion.getConstitutiveModel< SingleFluidBase >( fluidName ); + arrayView2d< real64 const, constitutive::singlefluid::USD_FLUID > const & wellElemDensity = fluid.density(); + arrayView3d< real64 const, constitutive::singlefluid::USD_FLUID_DER > const & dWellElemDensity = fluid.dDensity(); - WellControls & wellControls = getWellControls( subRegion ); + geos::internal::kernelLaunchSelectorThermalSwitch( isThermal(), [&] ( auto ISTHERMAL ) + { + PressureRelationKernel::launch< ISTHERMAL >( subRegion.size(), + dofManager.rankOffset(), + wellElemDofNumber, + wellElemGravCoef, + nextWellElemIndex, + wellElemPressure, + wellElemDensity, + dWellElemDensity, + localMatrix, + localRhs ); + } ); - // get the degrees of freedom numbers, depth, next well elem index - string const wellDofKey = dofManager.getKey( wellElementDofName() ); - arrayView1d< globalIndex const > const & wellElemDofNumber = - subRegion.getReference< array1d< globalIndex > >( wellDofKey ); - arrayView1d< real64 const > const & wellElemGravCoef = - subRegion.getField< well::gravityCoefficient >(); - arrayView1d< localIndex const > const & nextWellElemIndex = - subRegion.getReference< array1d< localIndex > >( WellElementSubRegion::viewKeyStruct::nextWellElementIndexString() ); +} - // get primary variables on well elements - arrayView1d< real64 const > const & wellElemPressure = - subRegion.getField< well::pressure >(); +void SinglePhaseWell::assembleWellAccumulationTerms( real64 const & time, + real64 const & dt, + WellElementSubRegion & subRegion, + DofManager const & dofManager, + CRSMatrixView< real64, globalIndex const > const & localMatrix, + arrayView1d< real64 > const & localRhs ) - // get well constitutive data - string const & fluidName = subRegion.getReference< string >( viewKeyStruct::fluidNamesString() ); - SingleFluidBase const & fluid = subRegion.getConstitutiveModel< SingleFluidBase >( fluidName ); - arrayView2d< real64 const, constitutive::singlefluid::USD_FLUID > const & wellElemDensity = fluid.density(); - arrayView3d< real64 const, constitutive::singlefluid::USD_FLUID_DER > const & dWellElemDensity = fluid.dDensity(); +{ + GEOS_UNUSED_VAR( time ); + GEOS_UNUSED_VAR( dt ); + // get a reference to the degree-of-freedom numbers + string const wellElemDofKey = dofManager.getKey( wellElementDofName() ); - geos::internal::kernelLaunchSelectorThermalSwitch( isThermal(), [&] ( auto ISTHERMAL ) + string const & fluidName = subRegion.getReference< string >( viewKeyStruct::fluidNamesString() ); + SingleFluidBase const & fluid = subRegion.getConstitutiveModel< SingleFluidBase >( fluidName ); + if( getWellStatus() == WellControls::Status::OPEN && !m_keepVariablesConstantDuringInitStep ) + { + if( isThermal() ) + { + thermalSinglePhaseWellKernels:: + ElementBasedAssemblyKernelFactory:: + createAndLaunch< parallelDevicePolicy<> >( isProducer(), + dofManager.rankOffset(), + wellElemDofKey, + subRegion, + fluid, + localMatrix, + localRhs ); + } + else + { + singlePhaseWellKernels:: + ElementBasedAssemblyKernelFactory:: + createAndLaunch< parallelDevicePolicy<> >( dofManager.rankOffset(), + wellElemDofKey, + subRegion, + fluid, + localMatrix, + localRhs ); + } + // get the degrees of freedom and ghosting info + arrayView1d< globalIndex const > const & wellElemDofNumber = + subRegion.getReference< array1d< globalIndex > >( wellElemDofKey ); + arrayView1d< integer const > const wellElemGhostRank = subRegion.ghostRank(); + arrayView1d< integer const > const elemStatus = subRegion.getLocalWellElementStatus(); + + arrayView1d< real64 > connRate = subRegion.getField< fields::well::connectionRate >(); + localIndex rank_offset = dofManager.rankOffset(); + forAll< parallelDevicePolicy<> >( subRegion.size(), [=] GEOS_HOST_DEVICE ( localIndex const ei ) + { + if( wellElemGhostRank[ei] < 0 ) { - localIndex controlHasSwitched=0; - controlHasSwitched = PressureRelationKernel::launch< ISTHERMAL >( subRegion.size(), - dofManager.rankOffset(), - subRegion.isLocallyOwned(), - subRegion.getTopWellElementIndex(), - wellControls, - time_n, - wellElemDofNumber, - wellElemGravCoef, - nextWellElemIndex, - wellElemPressure, - wellElemDensity, - dWellElemDensity, - localMatrix, - localRhs ); - - if( controlHasSwitched == 1 ) + if( elemStatus[ei]==WellElementSubRegion::WellElemStatus::CLOSED ) { - // Note: if BHP control is not viable, we switch to TOTALVOLRATE - // if TOTALVOLRATE is not viable, we switch to BHP + connRate[ei] = 0.0; + globalIndex const dofIndex = wellElemDofNumber[ei]; + localIndex const localRow = dofIndex - rank_offset; - if( wellControls.getControl() == WellControls::Control::BHP ) - { - wellControls.switchToTotalRateControl( wellControls.getTargetTotalRate( time_n ) ); - GEOS_LOG_LEVEL_RANK_0( logInfo::WellControl, - GEOS_FMT( "Control switch for well {} from BHP constraint to rate constraint", subRegion.getName()) ); - } - else + real64 const unity = 1.0; + for( integer i=0; i < m_numDofPerWellElement; i++ ) { - wellControls.switchToBHPControl( wellControls.getTargetBHP( time_n ) ); - GEOS_LOG_LEVEL_RANK_0( logInfo::WellControl, - GEOS_FMT( "Control switch for well {} from rate constraint to BHP constraint", subRegion.getName()) ); + globalIndex const rindex = localRow+i; + globalIndex const cindex =dofIndex + i; + localMatrix.template addToRow< serialAtomic >( rindex, + &cindex, + &unity, + 1 ); + localRhs[rindex] = 0.0; } } - } ); - + } } ); - } ); -} - -void SinglePhaseWell::assembleAccumulationTerms( real64 const & time_n, - real64 const & dt, - DomainPartition & domain, - DofManager const & dofManager, - CRSMatrixView< real64, globalIndex const > const & localMatrix, - arrayView1d< real64 > const & localRhs ) -{ - GEOS_MARK_FUNCTION; - GEOS_UNUSED_VAR( time_n ); - GEOS_UNUSED_VAR( dt ); - forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&] ( string const &, - MeshLevel const & mesh, - string_array const & regionNames ) + } + else { - - ElementRegionManager const & elemManager = mesh.getElemManager(); - - elemManager.forElementSubRegions< WellElementSubRegion >( regionNames, - [&]( localIndex const, - WellElementSubRegion const & subRegion ) + // Zero accumulation contribution + arrayView1d< globalIndex const > const & wellElemDofNumber = + subRegion.getReference< array1d< globalIndex > >( wellElemDofKey ); + arrayView1d< integer const > const wellElemGhostRank = subRegion.ghostRank(); + + arrayView1d< real64 > connRate = subRegion.getField< fields::well::connectionRate >(); + localIndex rank_offset = dofManager.rankOffset(); + forAll< parallelDevicePolicy<> >( subRegion.size(), [=] GEOS_HOST_DEVICE ( localIndex const ei ) { - - // get a reference to the degree-of-freedom numbers - string const wellElemDofKey = dofManager.getKey( wellElementDofName() ); - - WellControls const & wellControls = getWellControls( subRegion ); - string const & fluidName = subRegion.getReference< string >( viewKeyStruct::fluidNamesString() ); - SingleFluidBase const & fluid = subRegion.getConstitutiveModel< SingleFluidBase >( fluidName ); - - if( isThermal() ) + if( wellElemGhostRank[ei] < 0 ) { - thermalSinglePhaseWellKernels:: - ElementBasedAssemblyKernelFactory:: - createAndLaunch< parallelDevicePolicy<> >( wellControls.isProducer(), - dofManager.rankOffset(), - wellElemDofKey, - subRegion, - fluid, - localMatrix, - localRhs ); - } - else - { - singlePhaseWellKernels:: - ElementBasedAssemblyKernelFactory:: - createAndLaunch< parallelDevicePolicy<> >( dofManager.rankOffset(), - wellElemDofKey, - subRegion, - fluid, - localMatrix, - localRhs ); + connRate[ei] = 0.0; + globalIndex const dofIndex = wellElemDofNumber[ei]; + localIndex const localRow = dofIndex - rank_offset; + + real64 const unity = 1.0; + for( integer i=0; i < m_numDofPerWellElement; i++ ) + { + globalIndex const rindex = localRow+i; + globalIndex const cindex =dofIndex + i; + localMatrix.template addToRow< serialAtomic >( rindex, + &cindex, + &unity, + 1 ); + localRhs[rindex] = 0.0; + } } } ); - } ); - // then assemble the volume balance equations - assembleVolumeBalanceTerms( domain, dofManager, localMatrix, localRhs ); -} - -void SinglePhaseWell::assembleVolumeBalanceTerms( DomainPartition const & GEOS_UNUSED_PARAM( domain ), - DofManager const & GEOS_UNUSED_PARAM( dofManager ), - CRSMatrixView< real64, globalIndex const > const & GEOS_UNUSED_PARAM( localMatrix ), - arrayView1d< real64 > const & GEOS_UNUSED_PARAM( localRhs ) ) -{ - // not implemented for single phase flow + // zero out current state constraint quantities + getReference< real64 >( WellControls::viewKeyStruct::currentBHPString() ) = 0.0; + getReference< real64 >( WellControls::viewKeyStruct::currentVolRateString() )=0.0; + getReference< real64 >( WellControls::viewKeyStruct::currentVolRateString() )=0.0; + } } -void SinglePhaseWell::computePerforationRates( real64 const & time_n, - real64 const & dt, DomainPartition & domain ) +void SinglePhaseWell::computeWellPerforationRates( real64 const & time_n, + real64 const & GEOS_UNUSED_PARAM( dt ), + ElementRegionManager & elemManager, + WellElementSubRegion & subRegion ) { GEOS_MARK_FUNCTION; GEOS_UNUSED_VAR( time_n ); - GEOS_UNUSED_VAR( dt ); - forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&] ( string const &, - MeshLevel & mesh, - string_array const & regionNames ) + PerforationData * const perforationData = subRegion.getPerforationData(); + if( isWellOpen() && !m_keepVariablesConstantDuringInitStep ) { + string const & fluidName = subRegion.getReference< string >( viewKeyStruct::fluidNamesString() ); + SingleFluidBase const & fluid = getConstitutiveModel< SingleFluidBase >( subRegion, fluidName ); - // TODO: change the way we access the flowSolver here - SinglePhaseBase const & flowSolver = getParent().getGroup< SinglePhaseBase >( getFlowSolverName() ); - PerforationKernel::SinglePhaseFlowAccessors resSinglePhaseFlowAccessors( mesh.getElemManager(), flowSolver.getName() ); - PerforationKernel::SingleFluidAccessors resSingleFluidAccessors( mesh.getElemManager(), flowSolver.getName() ); - ElementRegionManager & elemManager = mesh.getElemManager(); - elemManager.forElementSubRegions< WellElementSubRegion >( regionNames, [&]( localIndex const, - WellElementSubRegion & subRegion ) + if( isThermal() ) { - - // get the well data - PerforationData * const perforationData = subRegion.getPerforationData(); - WellControls const & wellControls = getWellControls( subRegion ); - if( wellControls.isWellOpen() && !m_keepVariablesConstantDuringInitStep ) - { - - string const & fluidName = subRegion.getReference< string >( viewKeyStruct::fluidNamesString() ); - SingleFluidBase const & fluid = getConstitutiveModel< SingleFluidBase >( subRegion, fluidName ); - - if( isThermal() ) - { - thermalSinglePhasePerforationFluxKernels:: - PerforationFluxKernelFactory:: - createAndLaunch< parallelDevicePolicy<> >( flowSolver.getName(), - perforationData, - subRegion, - fluid, - elemManager ); - } - else - { - isothermalSinglePhasePerforationFluxKernels:: - PerforationFluxKernelFactory:: - createAndLaunch< parallelDevicePolicy<> >( flowSolver.getName(), - perforationData, - subRegion, - fluid, - elemManager ); - } - } - else - { - // Zero completion flow rate - arrayView1d< real64 > const perfRate = perforationData->getField< fields::well::perforationRate >(); - for( integer iperf=0; iperfsize(); iperf++ ) - { - perfRate[iperf] = 0.0; - } - } - } ); - } ); + thermalSinglePhasePerforationFluxKernels:: + PerforationFluxKernelFactory:: + createAndLaunch< parallelDevicePolicy<> >( getFlowSolverName(), + perforationData, + subRegion, + fluid, + elemManager ); + } + else + { + isothermalSinglePhasePerforationFluxKernels:: + PerforationFluxKernelFactory:: + createAndLaunch< parallelDevicePolicy<> >( getFlowSolverName(), + perforationData, + subRegion, + fluid, + elemManager ); + } + } + else + { + // Zero completion flow rate. + arrayView1d< real64 > const perfRate = perforationData->getField< fields::well::perforationRate >(); + for( integer iperf=0; iperfsize(); iperf++ ) + { + perfRate[iperf] = 0.0; + } + } } +real64 +SinglePhaseWell::scalingForWellSystemSolution( WellElementSubRegion & subRegion, + DofManager const & dofManager, + arrayView1d< real64 const > const & localSolution ) +{ + GEOS_MARK_FUNCTION; + GEOS_UNUSED_VAR( subRegion ); + GEOS_UNUSED_VAR( dofManager ); + GEOS_UNUSED_VAR( localSolution ); + return 1.0; +} -real64 -SinglePhaseWell::calculateResidualNorm( real64 const & time_n, - real64 const & dt, - DomainPartition const & domain, - DofManager const & dofManager, - arrayView1d< real64 const > const & localRhs ) +array1d< real64 > +SinglePhaseWell::calculateLocalWellResidualNorm( real64 const & time_n, + real64 const & dt, + NonlinearSolverParameters const & nonlinearSolverParameters, + WellElementSubRegion const & subRegion, + DofManager const & dofManager, + arrayView1d< real64 const > const & localRhs ) { GEOS_MARK_FUNCTION; - integer numNorm = 1; // mass balance + integer numNorm = 1; // mass balance array1d< real64 > localResidualNorm; array1d< real64 > localResidualNormalizer; if( isThermal() ) { - numNorm = 2; // mass balance and energy balance + numNorm = 2; // mass balance and energy balance } localResidualNorm.resize( numNorm ); localResidualNormalizer.resize( numNorm ); @@ -915,74 +1033,99 @@ SinglePhaseWell::calculateResidualNorm( real64 const & time_n, globalIndex const rankOffset = dofManager.rankOffset(); string const wellDofKey = dofManager.getKey( wellElementDofName() ); - forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&] ( string const &, - MeshLevel const & mesh, - string_array const & regionNames ) - { - - ElementRegionManager const & elemManager = mesh.getElemManager(); - elemManager.forElementSubRegions< WellElementSubRegion >( regionNames, - [&]( localIndex const, - WellElementSubRegion const & subRegion ) - { + string const & fluidName = subRegion.getReference< string >( viewKeyStruct::fluidNamesString() ); + SingleFluidBase const & fluid = subRegion.getConstitutiveModel< SingleFluidBase >( fluidName ); - string const & fluidName = subRegion.getReference< string >( viewKeyStruct::fluidNamesString() ); - SingleFluidBase const & fluid = subRegion.getConstitutiveModel< SingleFluidBase >( fluidName ); - WellControls const & wellControls = getWellControls( subRegion ); - // step 1: compute the norm in the subRegion - if( isThermal() ) + if( isWellOpen() ) + { + // step 1: compute the norm in the subRegion + if( isThermal() ) + { + real64 subRegionResidualNorm[2]{}; + thermalSinglePhaseWellKernels::ResidualNormKernelFactory:: + createAndLaunch< parallelDevicePolicy<> >( rankOffset, + wellDofKey, + localRhs, + subRegion, + fluid, + *this, + time_n, + dt, + nonlinearSolverParameters.m_minNormalizer, + subRegionResidualNorm ); + // step 2: reduction across meshBodies/regions/subRegions + + for( integer i=0; i >( rankOffset, - wellDofKey, - localRhs, - subRegion, - fluid, - wellControls, - time_n, - dt, - m_nonlinearSolverParameters.m_minNormalizer, - subRegionResidualNorm ); - // step 2: reduction across meshBodies/regions/subRegions - - for( integer i=0; i localResidualNorm[i] ) { - if( subRegionResidualNorm[i] > localResidualNorm[i] ) - { - localResidualNorm[i] = subRegionResidualNorm[i]; - } + localResidualNorm[i] = subRegionResidualNorm[i]; } } - else + } + else + { + real64 subRegionResidualNorm[1]{}; + ResidualNormKernelFactory:: + createAndLaunch< parallelDevicePolicy<> >( rankOffset, + wellDofKey, + localRhs, + subRegion, + fluid, + *this, + time_n, + dt, + nonlinearSolverParameters.m_minNormalizer, + subRegionResidualNorm ); + + // step 2: reduction across meshBodies/regions/subRegions + if( subRegionResidualNorm[0] > localResidualNorm[0] ) { - real64 subRegionResidualNorm[1]{}; - ResidualNormKernelFactory:: - createAndLaunch< parallelDevicePolicy<> >( rankOffset, - wellDofKey, - localRhs, - subRegion, - fluid, - wellControls, - time_n, - dt, - m_nonlinearSolverParameters.m_minNormalizer, - subRegionResidualNorm ); - - // step 2: reduction across meshBodies/regions/subRegions - - if( subRegionResidualNorm[0] > localResidualNorm[0] ) - { - localResidualNorm[0] = subRegionResidualNorm[0]; - } + localResidualNorm[0] = subRegionResidualNorm[0]; } - } ); - } ); + } + } + + return localResidualNorm; +} + +real64 +SinglePhaseWell::calculateWellResidualNorm( real64 const & time_n, + real64 const & dt, + NonlinearSolverParameters const & nonlinearSolverParameters, + WellElementSubRegion const & subRegion, + DofManager const & dofManager, + arrayView1d< real64 const > const & localRhs ) +{ + GEOS_MARK_FUNCTION; + integer numNorm = 1; // mass balance + array1d< real64 > localResidualNorm; + array1d< real64 > localResidualNormalizer; + if( isThermal() ) + { + numNorm = 2; // mass balance and energy balance + } + localResidualNorm.resize( numNorm ); + localResidualNormalizer.resize( numNorm ); + + + //globalIndex const rankOffset = dofManager.rankOffset(); + string const wellDofKey = dofManager.getKey( wellElementDofName() ); + + if( isWellOpen() ) + { + localResidualNorm = calculateLocalWellResidualNorm( time_n, + dt, + nonlinearSolverParameters, + subRegion, + dofManager, + localRhs ); + } real64 resNorm=localResidualNorm[0]; if( isThermal() ) { @@ -994,8 +1137,8 @@ SinglePhaseWell::calculateResidualNorm( real64 const & time_n, GEOS_LOG_LEVEL_RANK_0_NLR( logInfo::ResidualNorm, GEOS_FMT( " ( R{} ) = ( {:4.2e} ) ( Renergy ) = ( {:4.2e} )", coupledSolverAttributePrefix(), globalResidualNorm[0], globalResidualNorm[1] )); - getConvergenceStats().setResidualValue( GEOS_FMT( "R{}", coupledSolverAttributePrefix()), globalResidualNorm[0] ); - getConvergenceStats().setResidualValue( "Renergy", globalResidualNorm[1] ); + //getConvergenceStats().setResidualValue( GEOS_FMT( "R{}", coupledSolverAttributePrefix()), globalResidualNorm[0] ); + //getConvergenceStats().setResidualValue( "Renergy", globalResidualNorm[1] ); } else { @@ -1003,76 +1146,64 @@ SinglePhaseWell::calculateResidualNorm( real64 const & time_n, GEOS_LOG_LEVEL_RANK_0_NLR( logInfo::ResidualNorm, GEOS_FMT( " ( R{} ) = ( {:4.2e} )", coupledSolverAttributePrefix(), resNorm )); - getConvergenceStats().setResidualValue( GEOS_FMT( "R{}", coupledSolverAttributePrefix()), resNorm ); + //getConvergenceStats().setResidualValue( GEOS_FMT( "R{}", coupledSolverAttributePrefix()), resNorm ); } return resNorm; } - -bool SinglePhaseWell::checkSystemSolution( DomainPartition & domain, - DofManager const & dofManager, - arrayView1d< real64 const > const & localSolution, - real64 const scalingFactor ) +bool SinglePhaseWell::checkWellSystemSolution( WellElementSubRegion & subRegion, + DofManager const & dofManager, + arrayView1d< real64 const > const & localSolution, + real64 const scalingFactor, + real64 & minPressure, + real64 & minDensity, + real64 & minTotalDensity, + ElementsReporterBuffer & negPressureIds, + ElementsReporterBuffer & negDensityIds, + ElementsReporterBuffer & negTotalDensityIds ) { GEOS_MARK_FUNCTION; + GEOS_UNUSED_VAR( minDensity, minTotalDensity ); + GEOS_UNUSED_VAR( negDensityIds, negTotalDensityIds ); + string const wellDofKey = dofManager.getKey( wellElementDofName() ); - ElementsReporterBuffer rankNegPressureIds{ isLogLevelActive< logInfo::Solution >( getLogLevel() ), - isLogLevelActive< logInfo::SolutionDetails >( getLogLevel() ) ? 16 : 0 }; - real64 minNegPres = 0.0; - forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&] ( string const &, - MeshLevel const & mesh, - string_array const & regionNames ) - { + globalIndex const rankOffset = dofManager.rankOffset(); + // get the degree of freedom numbers on well elements + arrayView1d< globalIndex const > const & dofNumber = subRegion.getReference< array1d< globalIndex > >( wellDofKey ); + arrayView1d< integer const > const & ghostRank = subRegion.ghostRank(); - ElementRegionManager const & elemManager = mesh.getElemManager(); + // get a reference to the primary variables on well elements + arrayView1d< real64 const > const & pressure = subRegion.getField< well::pressure >(); + auto const negPresCollector = negPressureIds.createCollector( subRegion.localToGlobalMap().toViewConst() ); - elemManager.forElementSubRegions< WellElementSubRegion >( regionNames, - [&]( localIndex const, - WellElementSubRegion const & subRegion ) + using Kernel = singlePhaseBaseKernels::SolutionCheckKernel; - { - globalIndex const rankOffset = dofManager.rankOffset(); - // get the degree of freedom numbers on well elements - arrayView1d< globalIndex const > const & dofNumber = - subRegion.getReference< array1d< globalIndex > >( wellDofKey ); - arrayView1d< integer const > const & ghostRank = subRegion.ghostRank(); - - // get a reference to the primary variables on well elements - arrayView1d< real64 const > const & pres = - subRegion.getField< well::pressure >(); - - auto const negPresCollector = rankNegPressureIds.createCollector( subRegion.localToGlobalMap().toViewConst() ); - - auto const results = singlePhaseBaseKernels::SolutionCheckKernel:: - launch< parallelDevicePolicy<> >( localSolution, - rankOffset, - dofNumber, - ghostRank, - pres, - scalingFactor, - negPresCollector ); - - minNegPres = std::min( minNegPres, results.minNegPres ); - } ); - } ); + auto const results = Kernel::launch< parallelDevicePolicy<> >( localSolution, + rankOffset, + dofNumber, + ghostRank, + pressure, + scalingFactor, + negPresCollector ); - ElementsReporterOutput const rankNegPressureIdsOutput = rankNegPressureIds.createOutput(); - rankNegPressureIdsOutput.outputTooLowValues( GEOS_FMT( " {}: ", getName() ), - "negative pressure", minNegPres, units::Unit::Pressure ); + minPressure = std::min( minPressure, results.minNegPres ); - return (m_allowNegativePressure || rankNegPressureIdsOutput.getRanksSignaledIdsCount() == 0) ? 1 : 0; + return (m_allowNegativePressure || 0.0 < results.minNegPres) ? 1 : 0; } void -SinglePhaseWell::applySystemSolution( DofManager const & dofManager, - arrayView1d< real64 const > const & localSolution, - real64 const scalingFactor, - real64 const dt, - DomainPartition & domain ) +SinglePhaseWell::applyWellSystemSolution( DofManager const & dofManager, + arrayView1d< real64 const > const & localSolution, + real64 const scalingFactor, + real64 const dt, + DomainPartition & domain, + MeshLevel & mesh, + WellElementSubRegion & subRegion ) { GEOS_UNUSED_VAR( dt ); + GEOS_UNUSED_VAR( subRegion ); DofManager::CompMask pressureMask( m_numDofPerWellElement, 0, 1 ); DofManager::CompMask connRateMask( m_numDofPerWellElement, 1, 2 ); dofManager.addVectorToField( localSolution, @@ -1099,213 +1230,197 @@ SinglePhaseWell::applySystemSolution( DofManager const & dofManager, } - forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&] ( string const &, - MeshLevel & mesh, - string_array const & regionNames ) + + FieldIdentifiers fieldsToBeSync; + if( isThermal() ) { - FieldIdentifiers fieldsToBeSync; - if( isThermal() ) - { - fieldsToBeSync.addElementFields( { well::pressure::key(), - well::connectionRate::key(), - well::temperature::key() }, - regionNames ); - } - else - { - fieldsToBeSync.addElementFields( { well::pressure::key(), - well::connectionRate::key() }, - regionNames ); - } - CommunicationTools::getInstance().synchronizeFields( fieldsToBeSync, - mesh, - domain.getNeighbors(), - true ); - } ); + fieldsToBeSync.addElementFields( { well::pressure::key(), + well::connectionRate::key(), + well::temperature::key() }, + getTargetRegionNames() ); + } + else + { + fieldsToBeSync.addElementFields( { well::pressure::key(), + well::connectionRate::key() }, + getTargetRegionNames() ); + } + CommunicationTools::getInstance().synchronizeFields( fieldsToBeSync, + mesh, + domain.getNeighbors(), + true ); + } -void SinglePhaseWell::resetStateToBeginningOfStep( DomainPartition & domain ) + +void SinglePhaseWell::resetStateToBeginningOfStep( DomainPartition & domain, + string const & meshBodyName, ElementRegionManager const & elemManager, WellElementSubRegion & subRegion ) { - forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&] ( string const &, - MeshLevel & mesh, - string_array const & regionNames ) - { - ElementRegionManager & elemManager = mesh.getElemManager(); - elemManager.forElementSubRegions< WellElementSubRegion >( regionNames, - [&]( localIndex const, - WellElementSubRegion & subRegion ) - { - // get a reference to the primary variables on well elements - arrayView1d< real64 > const & wellElemPressure = - subRegion.getField< well::pressure >(); - arrayView1d< real64 const > const & wellElemPressure_n = - subRegion.getField< well::pressure_n >(); - wellElemPressure.setValues< parallelDevicePolicy<> >( wellElemPressure_n ); - - if( isThermal() ) - { - arrayView1d< real64 > const & wellElemTemperature = - subRegion.getField< fields::well::temperature >(); - arrayView1d< real64 const > const & wellElemTemperature_n = - subRegion.getField< fields::well::temperature_n >(); - wellElemTemperature.setValues< parallelDevicePolicy<> >( wellElemTemperature_n ); - } - arrayView1d< real64 > const & connRate = - subRegion.getField< well::connectionRate >(); - arrayView1d< real64 const > const & connRate_n = - subRegion.getField< well::connectionRate_n >(); - connRate.setValues< parallelDevicePolicy<> >( connRate_n ); + // get a reference to the primary variables on well elements + arrayView1d< real64 > const & wellElemPressure = + subRegion.getField< well::pressure >(); + arrayView1d< real64 const > const & wellElemPressure_n = + subRegion.getField< well::pressure_n >(); + wellElemPressure.setValues< parallelDevicePolicy<> >( wellElemPressure_n ); + + if( isThermal() ) + { + arrayView1d< real64 > const & wellElemTemperature = + subRegion.getField< fields::well::temperature >(); + arrayView1d< real64 const > const & wellElemTemperature_n = + subRegion.getField< fields::well::temperature_n >(); + wellElemTemperature.setValues< parallelDevicePolicy<> >( wellElemTemperature_n ); + } + arrayView1d< real64 > const & connRate = + subRegion.getField< well::connectionRate >(); + arrayView1d< real64 const > const & connRate_n = + subRegion.getField< well::connectionRate_n >(); + connRate.setValues< parallelDevicePolicy<> >( connRate_n ); + updateSubRegionState( -1.0, domain.getMeshBody( meshBodyName ), elemManager, subRegion ); - updateSubRegionState( elemManager, subRegion ); - } ); - } ); } +void SinglePhaseWell::saveState( WellElementSubRegion & subRegion ) +{ + arrayView1d< real64 const > const wellElemPressure = subRegion.getField< well::pressure >(); + arrayView1d< real64 > const wellElemPressure_n = subRegion.getField< well::pressure_n >(); + wellElemPressure_n.setValues< parallelDevicePolicy<> >( wellElemPressure ); + + if( isThermal() ) + { + arrayView1d< real64 const > const wellElemTemperature = subRegion.getField< well::temperature >(); + arrayView1d< real64 > const wellElemTemperature_n = subRegion.getField< well::temperature_n >(); + wellElemTemperature_n.setValues< parallelDevicePolicy<> >( wellElemTemperature ); + } + arrayView1d< real64 const > const connRate = subRegion.getField< well::connectionRate >(); + arrayView1d< real64 > const connRate_n = subRegion.getField< well::connectionRate_n >(); + connRate_n.setValues< parallelDevicePolicy<> >( connRate ); + + SingleFluidBase const & fluid = + getConstitutiveModel< SingleFluidBase >( subRegion, subRegion.getReference< string >( viewKeyStruct::fluidNamesString() ) ); + fluid.saveConvergedState(); +} -void SinglePhaseWell::implicitStepSetup( real64 const & time, +void SinglePhaseWell::implicitStepSetup( real64 const & time_n, real64 const & dt, - DomainPartition & domain ) + DomainPartition & domain, + string const & meshBodyName, + ElementRegionManager & elemManager, + WellElementSubRegion & subRegion ) { - WellSolverBase::implicitStepSetup( time, dt, domain ); + GEOS_MARK_FUNCTION; + WellControls::implicitStepSetup( time_n, dt, domain, meshBodyName, elemManager, subRegion ); + arrayView1d< real64 const > const wellElemPressure = subRegion.getField< well::pressure >(); + arrayView1d< real64 > const wellElemPressure_n = subRegion.getField< well::pressure_n >(); + wellElemPressure_n.setValues< parallelDevicePolicy<> >( wellElemPressure ); - forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&] ( string const &, - MeshLevel & mesh, - string_array const & regionNames ) + if( isThermal() ) { + arrayView1d< real64 const > const wellElemTemperature = subRegion.getField< well::temperature >(); + arrayView1d< real64 > const wellElemTemperature_n = subRegion.getField< well::temperature_n >(); + wellElemTemperature_n.setValues< parallelDevicePolicy<> >( wellElemTemperature ); + } + arrayView1d< real64 const > const connRate = subRegion.getField< well::connectionRate >(); + arrayView1d< real64 > const connRate_n = subRegion.getField< well::connectionRate_n >(); + connRate_n.setValues< parallelDevicePolicy<> >( connRate ); - ElementRegionManager & elemManager = mesh.getElemManager(); - elemManager.forElementSubRegions< WellElementSubRegion >( regionNames, - [&]( localIndex const, - WellElementSubRegion & subRegion ) - { - arrayView1d< real64 const > const wellElemPressure = subRegion.getField< well::pressure >(); - arrayView1d< real64 > const wellElemPressure_n = subRegion.getField< well::pressure_n >(); - wellElemPressure_n.setValues< parallelDevicePolicy<> >( wellElemPressure ); - - if( isThermal() ) - { - arrayView1d< real64 const > const wellElemTemperature = subRegion.getField< well::temperature >(); - arrayView1d< real64 > const wellElemTemperature_n = subRegion.getField< well::temperature_n >(); - wellElemTemperature_n.setValues< parallelDevicePolicy<> >( wellElemTemperature ); - } - arrayView1d< real64 const > const connRate = subRegion.getField< well::connectionRate >(); - arrayView1d< real64 > const connRate_n = subRegion.getField< well::connectionRate_n >(); - connRate_n.setValues< parallelDevicePolicy<> >( connRate ); + SingleFluidBase const & fluid = + getConstitutiveModel< SingleFluidBase >( subRegion, subRegion.getReference< string >( viewKeyStruct::fluidNamesString() ) ); + fluid.saveConvergedState(); - SingleFluidBase const & fluid = - getConstitutiveModel< SingleFluidBase >( subRegion, subRegion.getReference< string >( viewKeyStruct::fluidNamesString() ) ); - fluid.saveConvergedState(); + validateWellConstraints( time_n, dt, subRegion ); - validateWellConstraints( time, dt, subRegion ); + updateSubRegionState( time_n, domain.getMeshBody( meshBodyName ), elemManager, subRegion ); - updateSubRegionState( elemManager, subRegion ); - } ); - } ); } void SinglePhaseWell::implicitStepComplete( real64 const & time_n, real64 const & dt, - DomainPartition & domain ) + WellElementSubRegion const & subRegion ) { - WellSolverBase::implicitStepComplete( time_n, dt, domain ); - - if( getLogLevel() > 0 ) - { - printRates( time_n, dt, domain ); - } + printRates( time_n, dt, subRegion ); } void SinglePhaseWell::printRates( real64 const & time_n, - real64 const & GEOS_UNUSED_PARAM( dt ), - DomainPartition & domain ) + real64 const & dt, + WellElementSubRegion const & subRegion ) { - forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&] ( string const &, - MeshLevel & mesh, - string_array const & regionNames ) - { - - ElementRegionManager & elemManager = mesh.getElemManager(); - - elemManager.forElementSubRegions< WellElementSubRegion >( regionNames, - [&]( localIndex const, - WellElementSubRegion & subRegion ) - { - - // the rank that owns the reference well element is responsible for the calculations below. - if( !subRegion.isLocallyOwned() ) - { - return; - } - localIndex const iwelemRef = subRegion.getTopWellElementIndex(); + GEOS_UNUSED_VAR( dt ); + // the rank that owns the reference well element is responsible for the calculations below. + if( !subRegion.isLocallyOwned() ) + { + return; + } - // subRegion data + localIndex const iwelemRef = subRegion.getTopWellElementIndex(); - arrayView1d< real64 const > const & connRate = - subRegion.getField< well::connectionRate >(); + // subRegion data - // control data + arrayView1d< real64 const > const & connRate = + subRegion.getField< well::connectionRate >(); - WellControls const & wellControls = getWellControls( subRegion ); - string const wellControlsName = wellControls.getName(); + // control data - // format: time,total_rate,total_vol_rate - std::ofstream outputFile; - if( m_writeCSV > 0 ) - { - outputFile.open( m_ratesOutputDir + "/" + wellControlsName + ".csv", std::ios_base::app ); - outputFile << time_n; - } - if( !wellControls.isWellOpen() ) - { - GEOS_LOG( GEOS_FMT( "{}: well is shut", wellControlsName ) ); - if( outputFile.is_open()) - { - // print all zeros in the rates file - outputFile << ",0.0,0.0,0.0" << std::endl; - outputFile.close(); - } - return; - } + string const wellControlsName = getName(); - integer const useSurfaceConditions = wellControls.useSurfaceConditions(); + // format: time,total_rate,total_vol_rate + std::ofstream outputFile; + if( m_writeCSV > 0 ) + { + outputFile.open( m_ratesOutputDir + "/" + wellControlsName + ".csv", std::ios_base::app ); + outputFile << time_n; + } - real64 const & currentBHP = - wellControls.getReference< real64 >( SinglePhaseWell::viewKeyStruct::currentBHPString() ); - real64 const & currentTotalVolRate = - wellControls.getReference< real64 >( SinglePhaseWell::viewKeyStruct::currentVolRateString() ); + if( !isWellOpen() ) + { + GEOS_LOG( GEOS_FMT( "{}: well is shut", wellControlsName ) ); + if( outputFile.is_open()) + { + // print all zeros in the rates file + outputFile << ",0.0,0.0,0.0" << std::endl; + outputFile.close(); + } + return; + } - // bring everything back to host, capture the scalars by reference - forAll< serialPolicy >( 1, [&useSurfaceConditions, - ¤tBHP, - connRate, - ¤tTotalVolRate, - &iwelemRef, - &wellControlsName, - &outputFile] ( localIndex const ) - { - string const conditionKey = useSurfaceConditions ? "surface" : "reservoir"; - string const unitKey = useSurfaceConditions ? "s" : "r"; - - real64 const currentTotalRate = connRate[iwelemRef]; - GEOS_LOG( GEOS_FMT( "{}: BHP (at the specified reference elevation): {} Pa", - wellControlsName, currentBHP ) ); - GEOS_LOG( GEOS_FMT( "{}: Total rate: {} kg/s; total {} volumetric rate: {} {}m3/s", - wellControlsName, currentTotalRate, conditionKey, currentTotalVolRate, unitKey ) ); - if( outputFile.is_open()) - { - outputFile << "," << currentBHP; - outputFile << "," << currentTotalRate << "," << currentTotalVolRate << std::endl; - outputFile.close(); - } - } ); - } ); + integer const useSurfaceCond = useSurfaceConditions(); + + real64 const & currentBHP = + getReference< real64 >( WellControls::viewKeyStruct::currentBHPString() ); + real64 const & currentTotalVolRate = + getReference< real64 >( WellControls::viewKeyStruct::currentVolRateString() ); + + // bring everything back to host, capture the scalars by reference + forAll< serialPolicy >( 1, [&useSurfaceCond, + ¤tBHP, + connRate, + ¤tTotalVolRate, + &iwelemRef, + &wellControlsName, + &outputFile] ( localIndex const ) + { + string const conditionKey = useSurfaceCond ? "surface" : "reservoir"; + string const unitKey = useSurfaceCond ? "s" : "r"; + + real64 const currentTotalRate = connRate[iwelemRef]; + GEOS_LOG( GEOS_FMT( "{}: BHP (at the specified reference elevation): {} Pa", + wellControlsName, currentBHP ) ); + GEOS_LOG( GEOS_FMT( "{}: Total rate: {} kg/s; total {} volumetric rate: {} {}m3/s", + wellControlsName, currentTotalRate, conditionKey, currentTotalVolRate, unitKey ) ); + if( outputFile.is_open()) + { + outputFile << "," << currentBHP; + outputFile << "," << currentTotalRate << "," << currentTotalVolRate << std::endl; + outputFile.close(); + } } ); } -REGISTER_CATALOG_ENTRY( PhysicsSolverBase, SinglePhaseWell, string const &, Group * const ) + + }// namespace geos diff --git a/src/coreComponents/physicsSolvers/fluidFlow/wells/SinglePhaseWell.hpp b/src/coreComponents/physicsSolvers/fluidFlow/wells/SinglePhaseWell.hpp index 8c07e223fb7..84b2888594d 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/wells/SinglePhaseWell.hpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/wells/SinglePhaseWell.hpp @@ -20,7 +20,7 @@ #ifndef GEOS_PHYSICSSOLVERS_FLUIDFLOW_WELLS_SINGLEPHASEWELL_HPP_ #define GEOS_PHYSICSSOLVERS_FLUIDFLOW_WELLS_SINGLEPHASEWELL_HPP_ -#include "WellSolverBase.hpp" +#include "physicsSolvers/fluidFlow/wells/WellControls.hpp" #include "constitutive/fluid/singlefluid/SingleFluidLayouts.hpp" @@ -36,6 +36,12 @@ namespace constitutive { class SingleFluidBase; } + +namespace singlePhaseStatistics +{ +class StatsAggregator; +} + class WellElementSubRegion; /** @@ -43,7 +49,7 @@ class WellElementSubRegion; * * A single-phase well solver */ -class SinglePhaseWell : public WellSolverBase +class SinglePhaseWell : public WellControls { public: @@ -63,7 +69,7 @@ class SinglePhaseWell : public WellSolverBase SinglePhaseWell( SinglePhaseWell const & ) = delete; /// default move constructor - SinglePhaseWell( SinglePhaseWell && ) = default; + SinglePhaseWell( SinglePhaseWell && ) = delete; /// deleted assignment operator SinglePhaseWell & operator=( SinglePhaseWell const & ) = delete; @@ -74,59 +80,164 @@ class SinglePhaseWell : public WellSolverBase /** * @brief default destructor */ - virtual ~SinglePhaseWell() override = default; + virtual ~SinglePhaseWell() override; + + void registerWellDataOnMesh( WellElementSubRegion & subRegion ) override; + + /** + * @defgroup WellManager Interface Functions + * + * These functions provide the primary interface that is required for derived classes + * The "Well" versions apply to individual well subRegions, whereas the others apply to all wells + */ + /**@{*/ + /** + * * @brief Initialize well for the beginning of a simulation or restart + * @param domain the domain + * @param mesh the mesh level + * @param subRegion the well subRegion + * @param time_n the current time + */ + virtual void initializeWell( DomainPartition & domain, Group & meshBodies, string const & meshBodyName, MeshLevel & mesh, WellElementSubRegion & subRegion, real64 const & time_n )override; + + virtual void initializeWellPostInitialConditionsPreSubGroups( WellElementSubRegion & subRegion )override; + + virtual bool isCompositional() const override { return false; } + /** + * @copydoc WellControls::assembleWellAccumulationTerms() + */ + virtual void assembleWellAccumulationTerms( real64 const & time, + real64 const & dt, + WellElementSubRegion & subRegion, + DofManager const & dofManager, + CRSMatrixView< real64, globalIndex const > const & localMatrix, + arrayView1d< real64 > const & localRhs ) override; + /** + * @copydoc WellControls::assembleWellConstraintTerms() + */ + virtual void assembleWellPressureRelations( real64 const & time_n, + real64 const & dt, + WellElementSubRegion const & subRegion, + DofManager const & dofManager, + CRSMatrixView< real64, globalIndex const > const & localMatrix, + arrayView1d< real64 > const & localRhs ) override; /** - * @brief name of the node manager in the object catalog - * @return string that contains the catalog name to generate a new NodeManager object through the object catalog. + * @copydoc WellControls::assembleWellConstraintTerms() */ - static string catalogName() { return "SinglePhaseWell"; } + virtual void assembleWellConstraintTerms( real64 const & time_n, + real64 const & dt, + WellElementSubRegion const & subRegion, + DofManager const & dofManager, + CRSMatrixView< real64, globalIndex const > const & localMatrix, + arrayView1d< real64 > const & localRhs ) override; + /** - * @copydoc PhysicsSolverBase::getCatalogName() + * @copydoc WellControls::computeWellPerforationRates() */ - string getCatalogName() const override { return catalogName(); } + virtual void computeWellPerforationRates( real64 const & time_n, + real64 const & GEOS_UNUSED_PARAM( dt ), + ElementRegionManager & elemManager, + WellElementSubRegion & subRegion ) override; - virtual void registerDataOnMesh( Group & meshBodies ) override; + /** + * @copydoc WellControls::assembleFluxTerms() + */ + virtual void assembleWellFluxTerms( real64 const & time, + real64 const & dt, + WellElementSubRegion const & subRegion, + DofManager const & dofManager, + CRSMatrixView< real64, globalIndex const > const & localMatrix, + arrayView1d< real64 > const & localRhs ) override; + /**@}*/ /** * @defgroup Solver Interface Functions * * These functions provide the primary interface that is required for derived classes + * The "Well" versions apply to individual well subRegions, whereas the others apply to all wells */ /**@{*/ - virtual real64 - calculateResidualNorm( real64 const & time_n, - real64 const & dt, - DomainPartition const & domain, - DofManager const & dofManager, - arrayView1d< real64 const > const & localRhs ) override; + virtual array1d< real64 > + calculateLocalWellResidualNorm( real64 const & time_n, + real64 const & dt, + NonlinearSolverParameters const & nonlinearSolverParameters, + WellElementSubRegion const & subRegion, + DofManager const & dofManager, + arrayView1d< real64 const > const & localRhs )override; - virtual bool - checkSystemSolution( DomainPartition & domain, - DofManager const & dofManager, - arrayView1d< real64 const > const & localSolution, - real64 const scalingFactor ) override; - virtual void - applySystemSolution( DofManager const & dofManager, - arrayView1d< real64 const > const & localSolution, - real64 const scalingFactor, - real64 const dt, - DomainPartition & domain ) override; + virtual real64 + calculateWellResidualNorm( real64 const & time_n, + real64 const & dt, + NonlinearSolverParameters const & nonlinearSolverParameters, + WellElementSubRegion const & subRegion, + DofManager const & dofManager, + arrayView1d< real64 const > const & localRhs ) override; + + virtual real64 scalingForWellSystemSolution( WellElementSubRegion & subRegion, + DofManager const & dofManager, + arrayView1d< real64 const > const & localSolution ) override; + /** + * @copydoc WellControls::checkSystemSolution() + */ - virtual void - resetStateToBeginningOfStep( DomainPartition & domain ) override; + virtual bool + checkWellSystemSolution( WellElementSubRegion & subRegion, + DofManager const & dofManager, + arrayView1d< real64 const > const & localSolution, + real64 const scalingFactor, + real64 & minPressure, + real64 & minDensity, + real64 & minTotalDensity, + ElementsReporterBuffer & negPressureIds, + ElementsReporterBuffer & negDensityIds, + ElementsReporterBuffer & negTotalDensityIds ) override; + /** + * @copydoc WellControls::applyWellSystemSolution() + */ virtual void - implicitStepSetup( real64 const & time, - real64 const & dt, - DomainPartition & domain ) override; + applyWellSystemSolution( DofManager const & dofManager, + arrayView1d< real64 const > const & localSolution, + real64 const scalingFactor, + real64 const dt, + DomainPartition & domain, + MeshLevel & mesh, + WellElementSubRegion & subRegion ) override; + + virtual void applyWellBoundaryConditions ( real64 const GEOS_UNUSED_PARAM( time_n ), + real64 const GEOS_UNUSED_PARAM( dt ), + ElementRegionManager & GEOS_UNUSED_PARAM( elemManager ), + WellElementSubRegion & GEOS_UNUSED_PARAM( subRegion ), + DofManager const & GEOS_UNUSED_PARAM( dofManager ), + arrayView1d< real64 > const & GEOS_UNUSED_PARAM( localRhs ), + CRSMatrixView< real64, globalIndex const > const & GEOS_UNUSED_PARAM( localMatrix ) )override {}; + + virtual void resetStateToBeginningOfStep( DomainPartition & domain, + string const & meshBodyName, ElementRegionManager const & elemManager, WellElementSubRegion & subRegion ) override; + + virtual void implicitStepSetup( real64 const & time_n, + real64 const & GEOS_UNUSED_PARAM( dt ), + DomainPartition & domain, + string const & meshBodyName, + ElementRegionManager & elemManager, + WellElementSubRegion & subRegion )override; virtual void implicitStepComplete( real64 const & time, real64 const & dt, - DomainPartition & domain ) override; + WellElementSubRegion const & subRegion ) override; + + virtual void printRates( real64 const & time_n, + real64 const & dt, + WellElementSubRegion const & subRegion ) override; + + virtual real64 updateSubRegionState( real64 const time_n, + MeshBody const & meshBody, + ElementRegionManager const & elemManager, + WellElementSubRegion & subRegion ) override; /**@}*/ @@ -134,17 +245,10 @@ class SinglePhaseWell : public WellSolverBase virtual string resElementDofName() const override; - virtual localIndex numFluidComponents() const override { return 1; } + virtual localIndex numFluidComponents() const override { return 0; } virtual localIndex numFluidPhases() const override { return 1; } - /** - * @brief Recompute the volumetric rate that are used in the well constraints - * @param elemManager the well region manager - * @param subRegion the well subregion containing all the primary and dependent fields - */ - virtual void updateVolRateForConstraint( ElementRegionManager const & elemManager, WellElementSubRegion & subRegion ); - /** * @brief Recompute the BHP pressure that is used in the well constraints * @param subRegion the well subregion containing all the primary and dependent fields @@ -156,136 +260,90 @@ class SinglePhaseWell : public WellSolverBase * @param subRegion the well subRegion containing the well elements and their associated fields */ virtual void updateFluidModel( WellElementSubRegion & subRegion ) const; - /** - * @brief Recompute the perforation rates for all the wells - * @param domain the domain containing the mesh and fields + * @brief Update separator model state + * @param elemManager the element region manager + * @param subRegion the well subRegion containing the separator */ - virtual void computePerforationRates( real64 const & time_n, - real64 const & dt, DomainPartition & domain ) override; + void updateSeparator( ElementRegionManager const & elemManager, WellElementSubRegion & subRegion ); /** - * @brief Recompute all dependent quantities from primary variables (including constitutive models) - * @param elemManager the elemManager containing the well + * @brief Calculate well rates at the reference element * @param subRegion the well subRegion containing the well elements and their associated fields */ - virtual real64 updateSubRegionState( ElementRegionManager const & elemManager, WellElementSubRegion & subRegion ) override; + void calculateReferenceElementRates( WellElementSubRegion & subRegion ); /** - * @brief function to assemble the linear system matrix and rhs - * @param time the time at the beginning of the step - * @param dt the desired timestep - * @param domain the domain partition - * @param dofManager degree-of-freedom manager associated with the linear system - * @param matrix the system matrix - * @param rhs the system right-hand side vector - */ - virtual void assembleSystem( real64 const time, - real64 const dt, - DomainPartition & domain, - DofManager const & dofManager, - CRSMatrixView< real64, globalIndex const > const & localMatrix, - arrayView1d< real64 > const & localRhs ) override; + * @brief Recompute all dependent quantities from primary variables (including constitutive + * models) - /** - * @brief assembles the flux terms for all connections between well elements - * @param time_n previous time value - * @param dt time step - * @param domain the physical domain object - * @param dofManager degree-of-freedom manager associated with the linear system - * @param matrix the system matrix - * @param rhs the system right-hand side vector - */ - virtual void assembleFluxTerms( real64 const & time_n, - real64 const & dt, - DomainPartition & domain, - DofManager const & dofManager, - CRSMatrixView< real64, globalIndex const > const & localMatrix, - arrayView1d< real64 > const & localRhs ) override; - - /** - * @brief assembles the accumulation term for all the well elements - * @param domain the physical domain object - * @param dofManager degree-of-freedom manager associated with the linear system - * @param matrix the system matrix - * @param rhs the system right-hand side vector + * @param + * @param subRegion the well subRegion containing the well elements and their associated */ - virtual void assembleAccumulationTerms( real64 const & time_n, - real64 const & dt, DomainPartition & domain, - DofManager const & dofManager, - CRSMatrixView< real64, globalIndex const > const & localMatrix, - arrayView1d< real64 > const & localRhs ) override; + virtual real64 updateWellState( MeshBody const & meshBody, + ElementRegionManager const & elemManager, + WellElementSubRegion & subRegion ) override; - /** - * @brief assembles the volume balance terms for all well elements - * @param domain the physical domain object - * @param dofManager degree-of-freedom manager associated with the linear system - * @param matrix the system matrix - * @param rhs the system right-hand side vector - */ - void assembleVolumeBalanceTerms( DomainPartition const & domain, - DofManager const & dofManager, - CRSMatrixView< real64, globalIndex const > const & localMatrix, - arrayView1d< real64 > const & localRhs ); - - /** - * @brief assembles the pressure relations at all connections between well elements except at the well head - * @param time_n time at the beginning of the time step - * @param dt the time step size - * @param domain the physical domain object - * @param dofManager degree-of-freedom manager associated with the linear system - * @param matrix the system matrix - * @param rhs the system right-hand side vector - */ - virtual void assemblePressureRelations( real64 const & time_n, - real64 const & dt, - DomainPartition const & domain, - DofManager const & dofManager, - CRSMatrixView< real64, globalIndex const > const & localMatrix, - arrayView1d< real64 > const & localRhs ) override; /* * @brief apply a special treatment to the wells that are shut - * @param time_n the time at the previous converged time step - * @param domain the physical domain object + * @param dofManager degree-of-freedom manager associated with the linear system * @param matrix the system matrix * @param rhs the system right-hand side vector */ - void shutDownWell( real64 const time_n, - DomainPartition const & domain, + void shutDownWell( WellElementSubRegion & subRegion, DofManager const & dofManager, CRSMatrixView< real64, globalIndex const > const & localMatrix, arrayView1d< real64 > const & localRhs ); - struct viewKeyStruct : WellSolverBase::viewKeyStruct + + void assembleVolumeBalanceTerms( DomainPartition const & domain, + DofManager const & dofManager, + CRSMatrixView< real64, globalIndex const > const & localMatrix, + arrayView1d< real64 > const & localRhs ); + + struct viewKeyStruct : WellControls::viewKeyStruct { - static constexpr char const * dofFieldString() { return "singlePhaseWellVars"; } + static constexpr char const * dofFieldString() { return "wellVars"; } + - // control data (not registered on the mesh) - static constexpr char const * currentBHPString() { return "currentBHP"; } - static constexpr char const * dCurrentBHPString() { return "dCurrentBHP"; } - static constexpr char const * currentVolRateString() { return "currentVolumetricRate"; } - static constexpr char const * dCurrentVolRateString() { return "dCurrentVolRate"; } }; protected: - void printRates( real64 const & time_n, - real64 const & dt, - DomainPartition & domain ) override; + virtual void initializePostInitialConditionsPreSubGroups() override; + + void saveState( WellElementSubRegion & subRegion ); + virtual void postRestartInitialization( )override; /// flag if negative pressure is allowed integer m_allowNegativePressure; private: + struct ReferenceConditions + { + real64 pressure; + real64 temperature; + }; + + /// optional statistics aggregator to get the average pressure of simulated region + std::unique_ptr< singlePhaseStatistics::StatsAggregator > m_reservoirStatsAggregator; + virtual void setConstitutiveNames( ElementSubRegionBase & subRegion ) const override; /** * @brief Initialize all the primary and secondary variables in all the wells * @param domain the domain containing the well manager to access individual wells */ - void initializeWells( DomainPartition & domain, real64 const & time_n ) override; + void initializeWells( DomainPartition & domain, real64 const & time_n ); + + void precomputeReferenceConditions( real64 time_n, + Group & meshBodies, + MeshBody & meshBody, + WellElementSubRegion const & subRegion ); + + ReferenceConditions getReferenceConditions( WellElementSubRegion const & subRegion ); /** * @brief Make sure that the well constraints are compatible @@ -295,7 +353,15 @@ class SinglePhaseWell : public WellSolverBase */ virtual void validateWellConstraints( real64 const & time_n, real64 const & dt, - WellElementSubRegion const & subRegion ) override; + WellElementSubRegion const & subRegion + ) override; + + + + /** + * @brief Create well separator + */ + virtual void createSeparator( WellElementSubRegion & subRegion ) override; }; diff --git a/src/coreComponents/physicsSolvers/fluidFlow/wells/SinglePhaseWellFields.hpp b/src/coreComponents/physicsSolvers/fluidFlow/wells/SinglePhaseWellFields.hpp index 2cd21f32866..9e0d5472826 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/wells/SinglePhaseWellFields.hpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/wells/SinglePhaseWellFields.hpp @@ -33,21 +33,6 @@ namespace fields namespace well { -DECLARE_FIELD( connectionRate, - "connectionRate", - array1d< real64 >, - 0, - LEVEL_0, - WRITE_AND_READ, - "Connection rate" ); - -DECLARE_FIELD( connectionRate_n, - "connectionRate_n", - array1d< real64 >, - 0, - NOPLOT, - WRITE_AND_READ, - "Connection rate at the previous converged time step" ); DECLARE_FIELD( density_n, "density_n", diff --git a/src/coreComponents/physicsSolvers/fluidFlow/wells/WellBHPConstraints.cpp b/src/coreComponents/physicsSolvers/fluidFlow/wells/WellBHPConstraints.cpp new file mode 100644 index 00000000000..c6fbca6e40c --- /dev/null +++ b/src/coreComponents/physicsSolvers/fluidFlow/wells/WellBHPConstraints.cpp @@ -0,0 +1,97 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2016-2024 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2024 TotalEnergies + * Copyright (c) 2018-2024 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2023-2024 Chevron + * Copyright (c) 2019- GEOS/GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +/* + * @file WellBHPConstraints.cpp + */ + +#include "LogLevelsInfo.hpp" +#include "WellBHPConstraints.hpp" +#include "WellConstants.hpp" +#include "dataRepository/InputFlags.hpp" +#include "functions/FunctionManager.hpp" + + +namespace geos +{ + +using namespace dataRepository; + +template< BHPConstraintTypeId T > +BHPConstraint< T >::BHPConstraint( string const & name, Group * const parent ) + : WellConstraintBase( name, parent ), + m_refElevation( 0.0 ), + m_refGravCoef( 0.0 ) +{ + setInputFlags( InputFlags::OPTIONAL_NONUNIQUE ); + + registerWrapper( viewKeyStruct::refElevString(), &m_refElevation ). + setDefaultValue( -1 ). + setInputFlag( InputFlags::REQUIRED ). + setDescription( "Reference elevation where BHP control is enforced [m]" ); + if constexpr (T == BHPConstraintTypeId::MAX) + { + // override the description for minimum BHP constraint + registerWrapper( viewKeyStruct::targetBHPString(), &m_constraintValue ). + setDefaultValue( 0.0 ). + setInputFlag( InputFlags::OPTIONAL ). + setRestartFlags( RestartFlags::WRITE_AND_READ ). + setDescription( "Maximum bottom-hole production pressure [Pa]" ); + } + else + { + registerWrapper( viewKeyStruct::targetBHPString(), &m_constraintValue ). + setDefaultValue( 0.0 ). + setInputFlag( InputFlags::OPTIONAL ). + setRestartFlags( RestartFlags::WRITE_AND_READ ). + setDescription( "Minimum bottom-hole production pressure [Pa]" ); + } +} + +template< BHPConstraintTypeId T > +BHPConstraint< T >::~BHPConstraint() +{} + +template< BHPConstraintTypeId T > +void BHPConstraint< T >::postInputInitialization() +{ + WellConstraintBase::postInputInitialization(); +} + + +template< BHPConstraintTypeId T > +bool BHPConstraint< T >::checkViolation( WellConstraintBase const & currentConstraint, real64 const & currentTime ) const +{ + if constexpr (T == BHPConstraintTypeId::MAX) + { + return currentConstraint.bottomHolePressure() > getConstraintValue( currentTime ); + } + else + { + return currentConstraint.bottomHolePressure() < getConstraintValue( currentTime ); + } +} + + +template class BHPConstraint< BHPConstraintTypeId::MIN >; +template class BHPConstraint< BHPConstraintTypeId::MAX >; +namespace +{ +typedef BHPConstraint< BHPConstraintTypeId::MIN > MinimumBHPConstraint; +typedef BHPConstraint< BHPConstraintTypeId::MAX > MaximumBHPConstraint; +REGISTER_CATALOG_ENTRY( WellConstraintBase, MinimumBHPConstraint, string const &, Group * const ) +REGISTER_CATALOG_ENTRY( WellConstraintBase, MaximumBHPConstraint, string const &, Group * const ) +} +} //namespace geos diff --git a/src/coreComponents/physicsSolvers/fluidFlow/wells/WellBHPConstraints.hpp b/src/coreComponents/physicsSolvers/fluidFlow/wells/WellBHPConstraints.hpp new file mode 100644 index 00000000000..63a637e18c5 --- /dev/null +++ b/src/coreComponents/physicsSolvers/fluidFlow/wells/WellBHPConstraints.hpp @@ -0,0 +1,177 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2016-2024 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2024 TotalEnergies + * Copyright (c) 2018-2024 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2023-2024 Chevron + * Copyright (c) 2019- GEOS/GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +/* + * @file WellBHPConstraints.hpp + */ + +#ifndef GEOS_PHYSICSSOLVERS_FLUIDFLOW_WELLS_WELLBHPCONSTRAINTS_HPP +#define GEOS_PHYSICSSOLVERS_FLUIDFLOW_WELLS_WELLBHPCONSTRAINTS_HPP + +#include "common/format/EnumStrings.hpp" +#include "dataRepository/Group.hpp" +#include "functions/TableFunction.hpp" +#include "WellConstraintsBase.hpp" +namespace geos +{ + +enum class BHPConstraintTypeId : integer +{ + MIN, /**< The well operates at a specified minimum bottom hole pressure (BHP) */ + MAX, /**< The well operates at a specified maximum bottom hole pressure (BHP) */ + UNINITIALIZED, /**< This is the current well control before postInputInitialization (needed to restart from file properly) */ +}; + + +/** + * @class BHPConstraint + * @brief This class describes a minimum pressure constraint used to control a injection well. + */ +template< BHPConstraintTypeId T > +class BHPConstraint : public WellConstraintBase +{ +public: + /** + * @name Constructor / Destructor + */ + ///@{ + + /** + * @brief Constructor for WellControls Objects. + * @param[in] name the name of this instantiation of WellControls in the repository + * @param[in] parent the parent group of this instantiation of WellControls + */ + explicit BHPConstraint( string const & name, dataRepository::Group * const parent ); + + + /** + * @brief Default destructor. + */ + ~BHPConstraint() override; + + /** + * @brief Deleted default constructor. + */ + BHPConstraint() = delete; + + /** + * @brief Deleted copy constructor. + */ + BHPConstraint( BHPConstraint const & ) = delete; + + /** + * @brief Deleted move constructor. + */ + BHPConstraint( BHPConstraint && ) = delete; + + /** + * @brief Deleted assignment operator. + * @return a reference to a constraint object + */ + BHPConstraint & operator=( BHPConstraint const & ) = delete; + + /** + * @brief Deleted move operator. + * @return a reference to a constraint object + */ + BHPConstraint & operator=( BHPConstraint && ) = delete; + + ///@} + + /** + * @name Getters / Setters + */ + ///@{ + + // Temp interface - tjb + virtual ConstraintTypeId getControl() const override { return ConstraintTypeId::BHP; }; + + ///@} + /** + * @brief Struct to serve as a container for variable strings and keys. + * @struct viewKeyStruct + */ + struct viewKeyStruct + { + /// String key for the well target BHP + static constexpr char const * targetBHPString() { return "targetBHP"; } + /// String key for the well reference elevation (for BHP control) + static constexpr char const * refElevString() { return "referenceElevation"; } + } + viewKeysWellBHPConstraint; + + virtual bool checkViolation( WellConstraintBase const & currentConstraint, real64 const & currentTime ) const override; + + /** + * @brief Getter for the reference elevation where the BHP control is enforced + * @return the reference elevation + */ + real64 getReferenceElevation() const { return m_refElevation; } + + /** + * @brief Set the reference elevation where the BHP control is enforced + * @return the reference elevation + */ + void setReferenceElevation( real64 const & refElevation ) { m_refElevation=refElevation; } + + /** + * @brief Getter for the reference gravity coefficient + * @return the reference gravity coefficient + */ + real64 getReferenceGravityCoef() const { return m_refGravCoef; } + + /** + * @brief Setter for the reference gravity + */ + void setReferenceGravityCoef( real64 const & refGravCoef ) { m_refGravCoef = refGravCoef; } + + + /** + * @brief name of the node manager in the object catalog + * @return string that contains the catalog name to generate a new Constraint object through the object catalog. + */ + static string catalogName() + { + if constexpr (T == BHPConstraintTypeId::MAX) + { + return "MaximumBHPConstraint"; + } + else + { + return "MinimumBHPConstraint"; + } + + } + + + virtual string getCatalogName() const override { return catalogName(); } +protected: + + virtual void postInputInitialization() override; + + /// Reference elevation + real64 m_refElevation; + + /// Gravity coefficient of the reference elevation + real64 m_refGravCoef; + +}; + +using MinimumBHPConstraint = BHPConstraint< BHPConstraintTypeId::MIN >; +using MaximumBHPConstraint = BHPConstraint< BHPConstraintTypeId::MAX >; + +} //namespace geos + +#endif //GEOS_PHYSICSSOLVERS_FLUIDFLOW_WELLS_WELLBHPCONSTRAINTS_HPP diff --git a/src/coreComponents/physicsSolvers/fluidFlow/wells/WellConstants.hpp b/src/coreComponents/physicsSolvers/fluidFlow/wells/WellConstants.hpp index 58b8b421c3a..83eadb7c2ff 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/wells/WellConstants.hpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/wells/WellConstants.hpp @@ -36,6 +36,11 @@ struct WellConstants static constexpr real64 defaultInjectorBHP = 1.01325e8; }; +enum class WellTypes : integer +{ + PRODUCER, /**< A production well */ + INJECTOR /**< An injection well */ +}; } //namespace geos #endif //GEOS_PHYSICSSOLVERS_FLUIDFLOW_WELLS_WELLCONSTANTS_HPP diff --git a/src/coreComponents/physicsSolvers/fluidFlow/wells/WellConstraintsBase.cpp b/src/coreComponents/physicsSolvers/fluidFlow/wells/WellConstraintsBase.cpp new file mode 100644 index 00000000000..d4a54860f68 --- /dev/null +++ b/src/coreComponents/physicsSolvers/fluidFlow/wells/WellConstraintsBase.cpp @@ -0,0 +1,116 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2016-2024 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2024 TotalEnergies + * Copyright (c) 2018-2024 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2023-2024 Chevron + * Copyright (c) 2019- GEOS/GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +/* + * @file WellConstraintBase.cpp + */ + +#include "LogLevelsInfo.hpp" +#include "WellConstraintsBase.hpp" +#include "WellConstants.hpp" +#include "dataRepository/InputFlags.hpp" +#include "functions/FunctionManager.hpp" + + +namespace geos +{ + +using namespace dataRepository; + +// Provide a properly-typed static catalog for WellConstraintBase so that +// CatalogInterface< WellConstraintBase, ... >::getCatalog() can return +// a catalog of CatalogInterface objects instead of +// inheriting Group::getCatalog() which returns a catalog of Group entries. +WellConstraintBase::CatalogInterface::CatalogType & WellConstraintBase::getCatalog() +{ + static WellConstraintBase::CatalogInterface::CatalogType catalog; + return catalog; +} + + +WellConstraintBase::WellConstraintBase( string const & name, Group * const parent ) + : Group( name, parent ), + m_constraintSource( ConstraintSourceId::USER ), + m_isConstraintActive( 1 ), + m_useScheduleTable( false ), + m_constraintValue( 0 ), + m_constraintScheduleTable( nullptr ), + m_rateSign( 1.0 ) // Default to positive rate sign for injection, set to -1.0 for production wells + +{ + setInputFlags( InputFlags::OPTIONAL_NONUNIQUE ); + + registerWrapper( viewKeyStruct::constraintScheduleTableNameString(), &m_constraintScheduleTableName ). + setRTTypeName( rtTypes::CustomTypes::groupNameRef ). + setInputFlag( InputFlags::OPTIONAL ). + setDescription( "Name of the well constraint schedule table when the constraint value is a time dependent function. \n" ); + + registerWrapper( viewKeyStruct::constraintActiveString(), &m_isConstraintActive ). + setDefaultValue( 1 ). + setInputFlag( InputFlags::OPTIONAL ). + setDescription( "Flag to enable constraint. Currently only supported for injectors: \n" + " - If the flag is set to 1, constraint included in boundary condition selection. \n" + " - If the flag is set to 0, constraint excluded from boundary condition selection." ); + +} + + +WellConstraintBase::~WellConstraintBase() +{} + + +void WellConstraintBase::postInputInitialization() +{ + + GEOS_THROW_IF( ((m_constraintValue > 0.0 && !m_constraintScheduleTableName.empty())|| (!(m_constraintValue > 0.0) && m_constraintScheduleTableName.empty())), + this->getDataContext() << ": You have provided redundant information for well constraint value ." << + " A constraint value and table of constraint values cannot be specified together", + InputError ); + + // Create time-dependent constraint table + if( !m_constraintScheduleTableName.empty() ) + { + FunctionManager & functionManager = FunctionManager::getInstance(); + m_constraintScheduleTable = &(functionManager.getGroup< TableFunction const >( m_constraintScheduleTableName )); + + GEOS_THROW_IF( m_constraintScheduleTable->getInterpolationMethod() != TableFunction::InterpolationType::Lower, + this->getName() << " " << this->getDataContext() << ": The interpolation method for the schedule table " + << m_constraintScheduleTable->getName() << " should be TableFunction::InterpolationType::Lower", + InputError ); + } + +} + +void WellConstraintBase::setNextDtFromTables( real64 const currentTime, real64 & nextDt ) const +{ + setNextDtFromTable( m_constraintScheduleTable, currentTime, nextDt ); +} + +void WellConstraintBase::setNextDtFromTable( TableFunction const * table, real64 const currentTime, real64 & nextDt ) +{ + if( table ) + { + // small epsilon to make sure we land on the other side of table interval and pick up the right rate + real64 const eps = 1e-6; + real64 const dtLimit = (table->getCoord( ¤tTime, 0, TableFunction::InterpolationType::Upper ) - currentTime) * ( 1.0 + eps ); + if( dtLimit > eps && dtLimit < nextDt ) + { + nextDt = dtLimit; + } + } +} + + +} //namespace geos diff --git a/src/coreComponents/physicsSolvers/fluidFlow/wells/WellConstraintsBase.hpp b/src/coreComponents/physicsSolvers/fluidFlow/wells/WellConstraintsBase.hpp new file mode 100644 index 00000000000..445ed62a61d --- /dev/null +++ b/src/coreComponents/physicsSolvers/fluidFlow/wells/WellConstraintsBase.hpp @@ -0,0 +1,292 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2016-2024 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2024 TotalEnergies + * Copyright (c) 2018-2024 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2023-2024 Chevron + * Copyright (c) 2019- GEOS/GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +/* + * @file WellConstraintBase.hpp + */ + + +#ifndef GEOS_PHYSICSSOLVERS_FLUIDFLOW_WELLS_WELLCONSTRAINTBASE_HPP +#define GEOS_PHYSICSSOLVERS_FLUIDFLOW_WELLS_WELLCONSTRAINTBASE_HPP + +#include "common/format/EnumStrings.hpp" + +#include "functions/TableFunction.hpp" +#include "dataRepository/Group.hpp" +namespace geos +{ + +enum class ConstraintSourceId : integer +{ + USER, /**< The well operates at a specified minimum bottom hole pressure (BHP) */ + WHP, /**< The well operates at a specified maximum bottom hole pressure (BHP) */ + UNINITIALIZED, /**< This is the current well control before postInputInitialization (needed to restart from file properly) */ +}; + + +enum class ConstraintTypeId : integer +{ + BHP, /**< The well operates at a specified bottom hole pressure (BHP) */ + PHASEVOLRATE, /**< The well operates at a specified phase volumetric flow rate */ + TOTALVOLRATE, /**< The well operates at a specified total volumetric flow rate */ + MASSRATE, /**; + + /// Get the singleton catalog for WellConstraintBase + static CatalogInterface::CatalogType & getCatalog(); + + /** + * @brief function to return the catalog name of the derived class + * @return a string that contains the catalog name of the derived class + */ + virtual string getCatalogName() const = 0; + + /** + * @name Getters / Setters + */ + ///@{ + + /** + * @brief Constructor for WellControls Objects. + * @param[in] name the name of this instantiation of WellControls in the repository + * @param[in] parent the parent group of this instantiation of WellControls + */ + explicit WellConstraintBase( string const & name, dataRepository::Group * const parent ); + + + /** + * @brief Default destructor. + */ + ~WellConstraintBase() override; + + /** + * @brief Deleted default constructor. + */ + WellConstraintBase() = delete; + + /** + * @brief Deleted copy constructor. + */ + WellConstraintBase( WellConstraintBase const & ) = delete; + + /** + * @brief Deleted move constructor. + */ + WellConstraintBase( WellConstraintBase && ) = delete; + + /** + * @brief Deleted assignment operator. + * @return a reference to a constraint object + */ + WellConstraintBase & operator=( WellConstraintBase const & ) = delete; + + /** + * @brief Deleted move operator. + * @return a reference to a constraint object + */ + WellConstraintBase & operator=( WellConstraintBase && ) = delete; + + ///@} + + + /** + * @name Getters / Setters + */ + ///@{ + + // Temp interface - tjb + virtual ConstraintTypeId getControl() const = 0; + + /** + * @brief Provide source of constraint (user defined, or computed from WHP constraint) + * @return true if the constraint is active, false otherwise + */ + ConstraintSourceId getConstraintSource( ) const { return m_constraintSource; } + + /** + * @brief Defines whether the constraint should be evaluated or not + * @brief Some workflows require the well model to define a constraint + * @brief of similar type to user defined constraints. For example, + * @brief rate constraints to evaluated WHP constraints. + * @return true if the constraint is active, false otherwise + */ + bool isConstraintActive( ) const { return m_isConstraintActive; } + + /** + * @brief Sets constraint active status + * @param[in] constraintActive true if the constraint is active, false otherwise + */ + void setConstraintActive( bool const & constraintActive ) { m_isConstraintActive=constraintActive; } + + /** + * @brief Sets constraint value + * @param[in] constraint value + */ + void setConstraintValue( real64 const & constraintValue ) + { + m_constraintValue = constraintValue; + } + + /** + * @brief Get the target bottom hole pressure value. + * @return a value for the target bottom hole pressure + */ + real64 getConstraintValue( real64 const & currentTime ) const + { + if( m_constraintScheduleTableName.empty() ) + { + return m_rateSign*m_constraintValue; + } + + return m_rateSign*m_constraintScheduleTable->evaluate( ¤tTime ); + } + + ///@} + + /** + * @brief Struct to serve as a container for variable strings and keys. + * @struct viewKeyStruct + */ + struct viewKeyStruct + { + /// string key for schedule table name + static constexpr char const * constraintScheduleTableNameString() { return "constraintScheduleTableName"; } + + /// String key for the well constraint active flag + static constexpr char const * constraintActiveString() { return "constraintActive"; } + + } + /// ViewKey struct for the WellControls class + viewKeysWellConstraint; + + // Quantities computed from well constraint solve with this boundary condition + // Until we have a more general interface for constraints to return these quantities, + // we will store them in the constraint object itself. + // This is not ideal but it is a temporary solution to avoid having to solve the constraint multiple times in the well solver and in the + // test. + void setBHP( real64 bhp ){ m_BHP=bhp;}; + void setPhaseVolumeRates( array1d< real64 > const & phaseVolumeRates ) { m_phaseVolumeRates = phaseVolumeRates; }; + void setTotalVolumeRate( real64 totalVolumeRate ){ m_totalVolumeRate = totalVolumeRate; }; + void setMassRate( real64 massRate ){ m_massRate = massRate; }; + + /** + * @brief Getter for the bottom hole pressure + * @return bottom hole pressure + */ + real64 bottomHolePressure() const { return m_BHP; } + + /** + * @brief Getter for the phase volume rates + * @return an arrayView1d storing the phase volume rates + */ + arrayView1d< real64 const > phaseVolumeRates() const { return m_phaseVolumeRates; } + + /** + * @brief Getter for the total volume rate + * @return mass rate + */ + real64 totalVolumeRate() const { return m_totalVolumeRate; } + + /** + * @brief Getter for the liquid rate + * @return liquid rate + */ + real64 liquidRate() const { return m_liquidRate; } + + /** + * @brief Getter for the mass rate + * @return mass rate + */ + real64 massRate() const { return m_massRate; } + + // endof This needs to be somewhere else tjb + /** + * @brief Check if this constraint is violated + * @return true if limiting constraint, false otherwise + */ + virtual bool checkViolation( WellConstraintBase const & currentConstraint, real64 const & currentTime ) const = 0; + +protected: + + virtual void postInputInitialization() override; + + /** + * @brief set next time step based on tables intervals + * @param[in] currentTime the current time + * @param[inout] nextDt the time step + */ + void setNextDtFromTables( real64 const currentTime, real64 & nextDt ) const; + + +protected: + + /// Source of the constraint (user defined, or computed from WHP constraint) + ConstraintSourceId m_constraintSource; + + /// Constraint status + integer m_isConstraintActive; + + /// Flag to indicate whether a schedule table should be generated for constraint value; + bool m_useScheduleTable; + + /// Constraint value + real64 m_constraintValue; + + static void setNextDtFromTable( TableFunction const * table, real64 const currentTime, real64 & nextDt ); + + /// Constraint schedule table name + string m_constraintScheduleTableName; + + /// Constraint values versus time + TableFunction const * m_constraintScheduleTable; + + // Quantities computed from well constraint solve with this boundary condition + + // botton hole pressure + real64 m_BHP; + + // phase rates + array1d< real64 > m_phaseVolumeRates; + + // liquid rate + real64 m_liquidRate; + + // total volume rate + real64 m_totalVolumeRate; + + // mass rate + real64 m_massRate; + + /// Rate sign. +1 for injector, -1 for producer + real64 m_rateSign; +}; + + +} //namespace geos + +#endif //GEOS_PHYSICSSOLVERS_FLUIDFLOW_WELLS_WELLCONSTRAINTBASE_HPP diff --git a/src/coreComponents/physicsSolvers/fluidFlow/wells/WellControls.cpp b/src/coreComponents/physicsSolvers/fluidFlow/wells/WellControls.cpp index e04bd780afa..8213516d2f8 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/wells/WellControls.cpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/wells/WellControls.cpp @@ -17,12 +17,30 @@ * @file WellControls.cpp */ -#include "LogLevelsInfo.hpp" #include "WellControls.hpp" + +#include "physicsSolvers/fluidFlow/wells/WellInjectionConstraint.hpp" +#include "physicsSolvers/fluidFlow/wells/WellProductionConstraint.hpp" +#include "physicsSolvers/fluidFlow/wells/WellBHPConstraints.hpp" +#include "physicsSolvers/fluidFlow/wells/WellVolumeRateConstraint.hpp" +#include "physicsSolvers/fluidFlow/wells/WellPhaseVolumeRateConstraint.hpp" +#include "physicsSolvers/fluidFlow/wells/WellMassRateConstraint.hpp" + + +#include "physicsSolvers/fluidFlow/FlowSolverBase.hpp" +#include "physicsSolvers/fluidFlow/CompositionalMultiphaseStatisticsTask.hpp" +#include "physicsSolvers/fluidFlow/SinglePhaseStatisticsTask.hpp" + +#include "LogLevelsInfo.hpp" #include "WellConstants.hpp" #include "dataRepository/InputFlags.hpp" #include "functions/FunctionManager.hpp" +#include "mesh/DomainPartition.hpp" +#include "mesh/PerforationFields.hpp" +#include "fileIO/Outputs/OutputBase.hpp" +#include "physicsSolvers/fluidFlow/wells/WellFields.hpp" +#include "functions/FunctionManager.hpp" namespace geos { @@ -32,26 +50,31 @@ using namespace dataRepository; WellControls::WellControls( string const & name, Group * const parent ) : Group( name, parent ), m_type( Type::PRODUCER ), - m_refElevation( 0.0 ), - m_refGravCoef( 0.0 ), - m_inputControl( Control::UNINITIALIZED ), - m_currentControl( Control::UNINITIALIZED ), - m_targetBHP( 0.0 ), - m_targetTotalRate( 0.0 ), - m_targetPhaseRate( 0.0 ), - m_targetMassRate( 0.0 ), + m_numPhases( 0 ), + m_numComponents( 0 ), + m_numDofPerWellElement( 0 ), + m_numDofPerResElement( 0 ), + m_isThermal( 0 ), + m_keepVariablesConstantDuringInitStep( false ), + m_ratesOutputDir( joinPath( OutputBase::getOutputDirectory(), parent->getName() + "_rates" ) ), + m_inputControl( ConstraintTypeId::UNINITIALIZED ), + m_currentControl( ConstraintTypeId::UNINITIALIZED ), m_useSurfaceConditions( 0 ), - m_surfacePres( 0.0 ), - m_surfaceTemp( 0.0 ), + m_surfacePres( -1.0 ), + m_surfaceTemp( -1.0 ), m_isCrossflowEnabled( 1 ), - m_initialPressureCoefficient( 0.1 ), - m_rateSign( -1.0 ), - m_targetTotalRateTable( nullptr ), - m_targetPhaseRateTable( nullptr ), - m_targetBHPTable( nullptr ), - m_statusTable( nullptr ), + m_initialPressureCoefficient( 0.5 ), + m_currentConstraint( nullptr ), m_wellStatus( WellControls::Status::OPEN ), - m_regionAveragePressure( -1 ) + m_wellOpen( false ), + m_statusTable( nullptr ), + m_regionAveragePressure( -1 ), + m_estimateSolution( 0 ), + m_enableIsoThermalEstimator( 0 ), + /// Nonlinear solver parameters + m_wellNewtonSolver( groupKeyStruct::wellNewtonSolverString(), this ), + m_estimatorDoFManager( name ), + m_dofManagerInitialized( false ) { setInputFlags( InputFlags::OPTIONAL_NONUNIQUE ); @@ -59,61 +82,24 @@ WellControls::WellControls( string const & name, Group * const parent ) setInputFlag( InputFlags::REQUIRED ). setDescription( "Well type. Valid options:\n* " + EnumStrings< Type >::concat( "\n* " ) ); - registerWrapper( viewKeyStruct::inputControlString(), &m_inputControl ). - setInputFlag( InputFlags::REQUIRED ). - setDescription( "Well control. Valid options:\n* " + EnumStrings< Control >::concat( "\n* " ) ); + this->registerWrapper( viewKeyStruct::writeCSVFlagString(), &m_writeCSV ). + setApplyDefaultValue( 1 ). + setInputFlag( dataRepository::InputFlags::OPTIONAL ). + setDescription( "When set to 1, write the rates into a CSV file." ); + + this->registerWrapper( viewKeyStruct::timeStepFromTablesFlagString(), &m_timeStepFromTables ). + setApplyDefaultValue( 0 ). + setInputFlag( dataRepository::InputFlags::OPTIONAL ). + setDescription( "Choose time step to honor rates/bhp tables time intervals" ); registerWrapper( viewKeyStruct::currentControlString(), &m_currentControl ). - setDefaultValue( Control::UNINITIALIZED ). + setDefaultValue( ConstraintTypeId::UNINITIALIZED ). setInputFlag( InputFlags::FALSE ). setDescription( "Current well control" ); - registerWrapper( viewKeyStruct::targetBHPString(), &m_targetBHP ). - setDefaultValue( 0.0 ). - setInputFlag( InputFlags::OPTIONAL ). - setRestartFlags( RestartFlags::WRITE_AND_READ ). - setDescription( "The target bottom-hole pressure [Pa] for the well." ); - - registerWrapper( viewKeyStruct::targetTotalRateString(), &m_targetTotalRate ). - setDefaultValue( 0.0 ). - setInputFlag( InputFlags::OPTIONAL ). - setRestartFlags( RestartFlags::WRITE_AND_READ ). - setDescription( "Target total volumetric rate (if useSurfaceConditions: [surface m^3/s]; else [reservoir m^3/s])" ); - - registerWrapper( viewKeyStruct::targetPhaseRateString(), &m_targetPhaseRate ). - setDefaultValue( 0.0 ). - setInputFlag( InputFlags::OPTIONAL ). - setRestartFlags( RestartFlags::WRITE_AND_READ ). - setDescription( "Target phase volumetric rate (if useSurfaceConditions: [surface m^3/s]; else [reservoir m^3/s])" ); - - registerWrapper( viewKeyStruct::targetMassRateString(), &m_targetMassRate ). - setDefaultValue( 0.0 ). - setInputFlag( InputFlags::OPTIONAL ). - setRestartFlags( RestartFlags::WRITE_AND_READ ). - setDescription( "Target Mass Rate rate ( [kg^3/s])" ); - - registerWrapper( viewKeyStruct::targetPhaseNameString(), &m_targetPhaseName ). - setRTTypeName( rtTypes::CustomTypes::groupNameRef ). - setDefaultValue( "" ). - setInputFlag( InputFlags::OPTIONAL ). - setRestartFlags( RestartFlags::WRITE_AND_READ ). - setDescription( "Name of the target phase" ); - - registerWrapper( viewKeyStruct::refElevString(), &m_refElevation ). - setDefaultValue( -1 ). + registerWrapper( viewKeyStruct::inputControlString(), &m_inputControl ). setInputFlag( InputFlags::REQUIRED ). - setDescription( "Reference elevation where BHP control is enforced [m]" ); - - registerWrapper( viewKeyStruct::injectionStreamString(), &m_injectionStream ). - setDefaultValue( -1 ). - setSizedFromParent( 0 ). - setInputFlag( InputFlags::OPTIONAL ). - setDescription( "Defines the global component fractions of the injected fluid." ); - - registerWrapper( viewKeyStruct::injectionTemperatureString(), &m_injectionTemperature ). - setDefaultValue( -1 ). - setInputFlag( InputFlags::OPTIONAL ). - setDescription( "Temperature of the injection stream [K]" ); + setDescription( "Well control. Valid options:\n* " + EnumStrings< ConstraintTypeId >::concat( "\n* " ) ); registerWrapper( viewKeyStruct::useSurfaceConditionsString(), &m_useSurfaceConditions ). setDefaultValue( 0 ). @@ -155,25 +141,15 @@ WellControls::WellControls( string const & name, Group * const parent ) " - Injector pressure at reference depth initialized as: (1+initialPressureCoefficient)*reservoirPressureAtClosestPerforation + density*g*( zRef - zPerf ) \n" " - Producer pressure at reference depth initialized as: (1-initialPressureCoefficient)*reservoirPressureAtClosestPerforation + density*g*( zRef - zPerf ) " ); - registerWrapper( viewKeyStruct::targetBHPTableNameString(), &m_targetBHPTableName ). - setRTTypeName( rtTypes::CustomTypes::groupNameRef ). + this->registerWrapper( viewKeyStruct::estimateWellSolutionString(), &m_estimateSolution ). + setApplyDefaultValue( 0 ). setInputFlag( InputFlags::OPTIONAL ). - setDescription( "Name of the BHP table when the rate is a time dependent function" ); + setDescription( "Flag to esitmate well solution prior to coupled reservoir and well solve." ); - registerWrapper( viewKeyStruct::targetTotalRateTableNameString(), &m_targetTotalRateTableName ). - setRTTypeName( rtTypes::CustomTypes::groupNameRef ). - setInputFlag( InputFlags::OPTIONAL ). - setDescription( "Name of the total rate table when the rate is a time dependent function" ); - - registerWrapper( viewKeyStruct::targetPhaseRateTableNameString(), &m_targetPhaseRateTableName ). - setRTTypeName( rtTypes::CustomTypes::groupNameRef ). - setInputFlag( InputFlags::OPTIONAL ). - setDescription( "Name of the phase rate table when the rate is a time dependent function" ); - - registerWrapper( viewKeyStruct::targetMassRateTableNameString(), &m_targetMassRateTableName ). - setRTTypeName( rtTypes::CustomTypes::groupNameRef ). + this->registerWrapper( viewKeyStruct::enableIsoThermalEstimatorString(), &m_enableIsoThermalEstimator ). + setApplyDefaultValue( 0 ). setInputFlag( InputFlags::OPTIONAL ). - setDescription( "Name of the mass rate table when the rate is a time dependent function" ); + setDescription( "Flag to enable isothermal estimator prior to coupled reservoir and well solve." ); registerWrapper( viewKeyStruct::statusTableNameString(), &m_statusTableName ). setRTTypeName( rtTypes::CustomTypes::groupNameRef ). @@ -181,6 +157,8 @@ WellControls::WellControls( string const & name, Group * const parent ) setDescription( "Name of the well status table when the status of the well is a time dependent function. \n" "If the status function evaluates to a positive value at the current time, the well will be open otherwise the well will be shut." ); + registerGroup( groupKeyStruct::wellNewtonSolverString(), &m_wellNewtonSolver ); + addLogLevel< logInfo::WellControl >(); } @@ -188,28 +166,26 @@ WellControls::WellControls( string const & name, Group * const parent ) WellControls::~WellControls() {} -void WellControls::switchToBHPControl( real64 const & val ) -{ - m_currentControl = Control::BHP; - m_targetBHP = val; -} - -void WellControls::switchToTotalRateControl( real64 const & val ) +Group * WellControls::createChild( string const & childKey, string const & childName ) { - m_currentControl = Control::TOTALVOLRATE; - m_targetTotalRate = val; -} + GEOS_LOG_RANK_0( GEOS_FMT( "{}: adding {} {}", getName(), childKey, childName ) ); + if( childKey == groupKeyStruct::wellNewtonSolverString() ) + { + return &m_wellNewtonSolver; + } -void WellControls::switchToPhaseRateControl( real64 const & val ) -{ - m_currentControl = Control::PHASEVOLRATE; - m_targetPhaseRate = val; + std::unique_ptr< WellConstraintBase > constraint = + WellConstraintBase::CatalogInterface::factory( childKey, getDataContext(), childName, this ); + return ®isterGroup< WellConstraintBase >( childName, std::move( constraint ) ); } -void WellControls::switchToMassRateControl( real64 const & val ) +void WellControls::expandObjectCatalogs() { - m_currentControl = Control::MASSRATE; - m_targetMassRate = val; + // During schema generation, register one of each type derived from WellConstraintBase here + for( auto & catalogIter : WellConstraintBase::getCatalog()) + { + createChild( catalogIter.first, catalogIter.first ); + } } namespace @@ -235,305 +211,983 @@ TableFunction * createWellTable( string const & tableName, } +void WellControls::registerWellDataOnMesh( WellElementSubRegion & subRegion ) +{ + std::string const & regionName = subRegion.getName(); + std::string addrWithMask( regionName ); + std::size_t pos = addrWithMask.find( "UniqueSubRegion" ); + std::string addr = addrWithMask.substr( 0, pos ); + m_targetRegionNames.push_back( addr ); + + registerWrapper< real64 >( viewKeyStruct::currentBHPString() ); + registerWrapper< real64 >( viewKeyStruct::currentVolRateString() ); + + registerWrapper< array1d< real64 > >( viewKeyStruct::currentPhaseVolRateString() ). + setSizedFromParent( 0 ). + reference().resizeDimension< 0 >( m_numPhases ); + registerWrapper< real64 >( viewKeyStruct::massDensityString() ); + + registerWrapper< real64 >( viewKeyStruct::currentTotalVolRateString() ); + registerWrapper< real64 >( viewKeyStruct::currentMassRateString() ); + + // If estimator is used including thermal effects set during constraint evaluation + // otherwise they are always included + if( isThermal() ) + { + if( m_estimateSolution == 0 ) + { + enableThermalEffects( true ); + } + } +} + void WellControls::postInputInitialization() { + Group::postInputInitialization(); // 0) Assign the value of the current well control // When the simulation starts from a restart file, we don't want to use the inputControl, // because the control may have switched in the simulation that generated the restart - GEOS_THROW_IF( m_inputControl == Control::UNINITIALIZED, + GEOS_THROW_IF( m_inputControl == ConstraintTypeId::UNINITIALIZED, "Input well control cannot be uninitialized", InputError, getWrapperDataContext( viewKeyStruct::inputControlString() ) ); - if( m_currentControl == Control::UNINITIALIZED ) + if( m_currentControl == ConstraintTypeId::UNINITIALIZED ) { m_currentControl = m_inputControl; } - // 1.a) check target BHP - GEOS_THROW_IF( m_targetBHP < 0, - "Target bottom-hole pressure is negative", - InputError, getWrapperDataContext( viewKeyStruct::targetBHPString() ) ); - // 1.b) check target rates - GEOS_THROW_IF( m_targetTotalRate < 0, - "Target rate is negative", - InputError, getWrapperDataContext( viewKeyStruct::targetTotalRateString() ) ); + // 3) check the flag for surface / reservoir conditions + GEOS_THROW_IF( m_useSurfaceConditions != 0 && m_useSurfaceConditions != 1, + "The flag to select surface/reservoir conditions must be equal to 0 or 1", + InputError, getWrapperDataContext( viewKeyStruct::useSurfaceConditionsString() ) ); + - GEOS_THROW_IF( m_targetPhaseRate < 0, - "Target oil rate is negative", - InputError, getWrapperDataContext( viewKeyStruct::targetPhaseRateString() ) ); - GEOS_THROW_IF( m_targetMassRate < 0, - "Target mass rate is negative", - InputError, getWrapperDataContext( viewKeyStruct::targetMassRateString() ) ); + // 6.2) Check incoherent information - GEOS_THROW_IF( (m_injectionStream.empty() && m_injectionTemperature >= 0) || - (!m_injectionStream.empty() && m_injectionTemperature < 0), - GEOS_FMT( "Both {} and {} must be specified for multiphase simulations", - viewKeyStruct::injectionStreamString(), - viewKeyStruct::injectionTemperatureString() ), + // An injector must be controlled by TotalVolRate + GEOS_THROW_IF( (isInjector() && (m_inputControl == ConstraintTypeId::PHASEVOLRATE)), + GEOS_FMT( "You have to control an injector with {}", + EnumStrings< ConstraintTypeId >::toString( ConstraintTypeId::TOTALVOLRATE ) ), InputError, getDataContext() ); - // 1.c) Set the multiplier for the rates - if( isProducer() ) + // An injector must be controlled by TotalVolRate + GEOS_THROW_IF( (isProducer() && (m_inputControl == ConstraintTypeId::MASSRATE)), + GEOS_FMT( "You have to control an injector with {}", + EnumStrings< ConstraintTypeId >::toString( ConstraintTypeId::MASSRATE ) ), + InputError, getDataContext() ); + + // 8) Make sure that the initial pressure coefficient is positive + GEOS_THROW_IF( m_initialPressureCoefficient < 0, + GEOS_FMT( "{}This tuning coefficient is negative", + viewKeyStruct::initialPressureCoefficientString() ), + InputError, getWrapperDataContext( viewKeyStruct::initialPressureCoefficientString() ) ); + + + + // 12) Create the time-dependent well status table + if( m_statusTableName.empty()) { - m_rateSign = -1.0; + // All well controls without a specified status function will use the same "Open" status function. + m_statusTableName = GEOS_FMT( "{0}_OpenStatus_table", dataRepository::keys::wellControls ); + FunctionManager & functionManager = FunctionManager::getInstance(); + m_statusTable = functionManager.getGroupPointer< TableFunction const >( m_statusTableName ); + if( m_statusTable==nullptr ) + { + m_statusTable = createWellTable( m_statusTableName, 1.0 ); + } } else { - m_rateSign = 1.0; + FunctionManager & functionManager = FunctionManager::getInstance(); + m_statusTable = &(functionManager.getGroup< TableFunction const >( m_statusTableName )); + + GEOS_THROW_IF( m_statusTable->getInterpolationMethod() != TableFunction::InterpolationType::Lower, + GEOS_FMT( "The interpolation method for the time-dependent status table {} " + "should be TableFunction::InterpolationType::Lower", + m_statusTable->getName() ), + InputError, getDataContext() ); } - // 2) check injection stream - if( !m_injectionStream.empty()) + // 13) Validate constraints + bool const isProducerWell = isProducer(); + + forSubGroups< InjectionConstraint< MassRateConstraint >, + ProductionConstraint< MassRateConstraint > >( [&]( auto const & constraint ) { - real64 sum = 0.0; - for( localIndex ic = 0; ic < m_injectionStream.size(); ++ic ) + GEOS_THROW_IF( useMass(), + GEOS_FMT( "Constraint {} of type {} only allowed for {} if useMass is set to 1", + constraint.getName(), getName() ), + InputError, constraint.getDataContext() ); + } ); + + stdVector< std::tuple< string, string, WellConstraintBase const * > > constraints; + forSubGroups< MaximumBHPConstraint, + InjectionConstraint< MassRateConstraint >, + InjectionConstraint< VolumeRateConstraint >, + InjectionConstraint< PhaseVolumeRateConstraint >, + MinimumBHPConstraint, + ProductionConstraint< MassRateConstraint >, + ProductionConstraint< VolumeRateConstraint >, + ProductionConstraint< PhaseVolumeRateConstraint > >( [&]( auto const & constraint ) + { + using ConstraintType = std::decay_t< decltype(constraint) >; + constraints.emplace_back( constraint.getName(), ConstraintType::catalogName(), &constraint ); + } ); + + // 13.1) Make sure a producer does not have injector constraints and vice versa + const std::set< string > types = [isProducerWell]() -> std::set< string > + { + if( isProducerWell ) { - GEOS_ERROR_IF( m_injectionStream[ic] < 0.0 || m_injectionStream[ic] > 1.0, - "Invalid injection stream", - getWrapperDataContext( viewKeyStruct::injectionStreamString() ) ); - sum += m_injectionStream[ic]; + return { MaximumBHPConstraint::catalogName(), + InjectionConstraint< MassRateConstraint >::catalogName(), + InjectionConstraint< VolumeRateConstraint >::catalogName(), + InjectionConstraint< PhaseVolumeRateConstraint >::catalogName()}; } - GEOS_THROW_IF( LvArray::math::abs( 1.0 - sum ) > std::numeric_limits< real64 >::epsilon(), - "Invalid injection stream", - InputError, getWrapperDataContext( viewKeyStruct::injectionStreamString() ) ); + else + { + return { MinimumBHPConstraint::catalogName(), + ProductionConstraint< MassRateConstraint >::catalogName(), + ProductionConstraint< VolumeRateConstraint >::catalogName(), + ProductionConstraint< PhaseVolumeRateConstraint >::catalogName() }; + } + }(); + for( const auto & [name, type, constraint] : constraints ) + { + GEOS_THROW_IF( types.find( type ) != types.end(), + GEOS_FMT( "Constraint {} of type {} is not allowed for {} wells", + name, type, (isProducerWell ? "producer" : "injector")), + InputError, constraint->getDataContext() ); } - // 3) check the flag for surface / reservoir conditions - GEOS_THROW_IF( m_useSurfaceConditions != 0 && m_useSurfaceConditions != 1, - "The flag to select surface/reservoir conditions must be equal to 0 or 1", - InputError, getWrapperDataContext( viewKeyStruct::useSurfaceConditionsString() ) ); - - // 4) check that at least one rate constraint has been defined - GEOS_THROW_IF( ((m_targetPhaseRate <= 0.0 && m_targetPhaseRateTableName.empty()) && - (m_targetMassRate <= 0.0 && m_targetMassRateTableName.empty()) && - (m_targetTotalRate <= 0.0 && m_targetTotalRateTableName.empty())), - GEOS_FMT( "You need to specify a phase, mass, or total rate constraint. \n" - "The phase rate constraint can be specified using either {} or {}.\n" - "The total rate constraint can be specified using .\n" - "either {} or {}.\n" - "The mass rate constraint can be specified using either {} or {}", - viewKeyStruct::targetPhaseRateString(), - viewKeyStruct::targetPhaseRateTableNameString(), - viewKeyStruct::targetTotalRateString(), - viewKeyStruct::targetTotalRateTableNameString(), - viewKeyStruct::targetMassRateString(), - viewKeyStruct::targetMassRateTableNameString() ), - InputError, getDataContext() ); - - // 5) check whether redundant information has been provided - GEOS_THROW_IF( ((m_targetPhaseRate > 0.0 && !m_targetPhaseRateTableName.empty())), - GEOS_FMT( "You have provided redundant information for well phase rate." - " The keywords {} and {} cannot be specified together", - viewKeyStruct::targetPhaseRateString(), - viewKeyStruct::targetPhaseRateTableNameString() ), - InputError, getDataContext() ); + // 13.2) Make sure we don't have multiple constraints of the same type + // Track the types we have already seen + mapBase< string, WellConstraintBase const *, std::false_type > seen_types; + for( const auto & [name, type, constraint] : constraints ) + { + auto [it, inserted] = seen_types.insert( {type, constraint} ); + GEOS_THROW_IF( !inserted, + GEOS_FMT( "Constraint of type {} is duplicated by {} and {}", + type, name, it->second->getName() ), + InputError, constraint->getDataContext() ); + } - GEOS_THROW_IF( ((m_targetTotalRate > 0.0 && !m_targetTotalRateTableName.empty())), - GEOS_FMT( "You have provided redundant information for well total rate." - " The keywords {} and {} cannot be specified together", - viewKeyStruct::targetTotalRateString(), - viewKeyStruct::targetTotalRateTableNameString() ), + // 13.3) Make sure there is a BHP constraint + string const bhp_type = isProducerWell ? MinimumBHPConstraint::catalogName() : MaximumBHPConstraint::catalogName(); + bool const no_match_found = std::none_of( constraints.begin(), constraints.end(), [&bhp_type]( const auto & constraint_tuple ) + { + return std::get< 1 >( constraint_tuple ) == bhp_type; + } ); + GEOS_THROW_IF( no_match_found, + GEOS_FMT( "Constraint of type {} is missing and is required for a {} well", + bhp_type, (isProducerWell ? "producer" : "injector") ), InputError, getDataContext() ); - GEOS_THROW_IF( ((m_targetBHP > 0.0 && !m_targetBHPTableName.empty())), - GEOS_FMT( "You have provided redundant information for well BHP." - " The keywords {} and {} cannot be specified together", - viewKeyStruct::targetBHPString(), - viewKeyStruct::targetBHPTableNameString() ), + // 13.4) Make sure there is at least one non-BHP constraint + bool const rate_match_found = std::any_of( constraints.begin(), constraints.end(), [&bhp_type]( const auto & constraint_tuple ) + { + return std::get< 1 >( constraint_tuple ) != bhp_type; + } ); + GEOS_THROW_IF( !rate_match_found, + GEOS_FMT( "Missing rate constraint for {} well {}", + (isProducerWell ? "producer" : "injector"), getName() ), InputError, getDataContext() ); +} - GEOS_THROW_IF( ((m_targetMassRate > 0.0 && !m_targetMassRateTableName.empty())), - GEOS_FMT( "You have provided redundant information for well mass rate." - " The keywords {} and {} cannot be specified together", - viewKeyStruct::targetMassRateString(), - viewKeyStruct::targetMassRateTableNameString() ), - InputError, getDataContext() ); +void WellControls::initializePreSubGroups() +{ + // Validate the reference region + validateReferenceRegion(); +} - GEOS_THROW_IF( ((m_targetMassRate > 0.0 && m_useSurfaceConditions==0)), - "Option only valid if useSurfaceConditions set to 1", - InputError, getDataContext() ); +void WellControls::postRestartInitialization( ) +{} - // 6.1) If the well is under BHP control then the BHP must be specified. - // Otherwise the BHP will be set to a default value. - if( m_currentControl == Control::BHP ) +void WellControls::logConstraint( WellConstraintBase const * constraint, WellElementSubRegion const & region, real64 time, bool isLimiting ) const +{ + bool const needsLog = (constraint != nullptr) && (getLogLevel() > 4) && region.isLocallyOwned(); + if( isLimiting ) { - GEOS_THROW_IF( ((m_targetBHP <= 0.0 && m_targetBHPTableName.empty())), - GEOS_FMT( "You have to provide well BHP by specifying either {} or {}", - viewKeyStruct::targetBHPString(), - viewKeyStruct::targetBHPTableNameString() ), - InputError, getDataContext() ); + GEOS_LOG_RANK_IF ( needsLog, + GEOS_FMT( " Well {}: Limiting Constraint {} - BHP {}, Volume rates {}, Total rate {}, Mass rate {}", + region.getName(), constraint->getName(), constraint->bottomHolePressure(), + constraint->phaseVolumeRates(), constraint->totalVolumeRate(), constraint->massRate()) ); } - else if( m_targetBHP <= 0.0 && m_targetBHPTableName.empty() ) + else { - m_targetBHP = isProducer() ? WellConstants::defaultProducerBHP : WellConstants::defaultInjectorBHP; - GEOS_LOG_LEVEL_RANK_0( logInfo::WellControl, - GEOS_FMT( "WellControls {}: Setting {} to default value {}", getDataContext(), viewKeyStruct::targetBHPString(), m_targetBHP )); + GEOS_LOG_RANK_IF ( needsLog, + GEOS_FMT( " Well {}: Constraint {} - active {}, value {}", + region.getName(), constraint->getName(), constraint->isConstraintActive(), + constraint->getConstraintValue( time ) ) ); } +} - // 6.2) Check incoherent information +void WellControls::setWellStatus( real64 const & currentTime, WellControls::Status status ) +{ + m_wellStatus = status; + if( m_wellStatus == WellControls::Status::OPEN ) + { + bool hasZeroRate = false; - // An injector must be controlled by TotalVolRate - GEOS_THROW_IF( (isInjector() && (m_inputControl == Control::PHASEVOLRATE)), - GEOS_FMT( "You have to control an injector with {}", - EnumStrings< Control >::toString( Control::TOTALVOLRATE ) ), - InputError, getDataContext() ); + if( m_statusTable->evaluate( ¤tTime ) < LvArray::NumericLimits< real64 >::epsilon ) + { + hasZeroRate = true; + } - // An injector must be controlled by TotalVolRate - GEOS_THROW_IF( (isProducer() && (m_inputControl == Control::MASSRATE)), - GEOS_FMT( "You have to control an injector with {}", - EnumStrings< Control >::toString( Control::MASSRATE ) ), - InputError, getDataContext() ); + if( !hasZeroRate ) + { + for( auto const * constraint : getRateConstraints() ) + { + if( isZero( constraint->getConstraintValue( currentTime ) ) ) + { + hasZeroRate = true; + } + } + } - // 8) Make sure that the initial pressure coefficient is positive - GEOS_THROW_IF( m_initialPressureCoefficient < 0, - GEOS_FMT( "{}This tuning coefficient is negative", - viewKeyStruct::initialPressureCoefficientString() ), - InputError, getWrapperDataContext( viewKeyStruct::initialPressureCoefficientString() ) ); + if( hasZeroRate ) + { + m_wellStatus = WellControls::Status::CLOSED; + m_currentConstraint = nullptr; + } + } +} +real64 WellControls::setNextDt( real64 const & currentTime, + real64 const & currentDt, + WellElementSubRegion & subRegion ) +{ + real64 nextDt = currentDt; + real64 nextDt_perf=nextDt; - // 9) Create time-dependent BHP table - if( m_targetBHPTableName.empty() ) + // Find min dt from perf status tables + PerforationData & perforationData = *subRegion.getPerforationData(); + string_array const & perfStatusTableName = perforationData.getPerfStatusTableName(); + FunctionManager & functionManager = FunctionManager::getInstance(); + // Get dt for local perforations + for( integer i=0; i( perfStatusTableName[i] ); + setNextDtFromTable( tableFunction, currentTime, nextDt_perf ); + } + nextDt = MpiWrapper::min< real64 >( nextDt_perf ); + // Find min dt including rate and status tables + real64 const nextDt_orig = nextDt; + setNextDtFromTables( currentTime, nextDt ); + //if( m_nonlinearSolverParameters.getLogLevel() > 0 && nextDt < nextDt_orig ) + if( getLogLevel() > 0 && nextDt < nextDt_orig ) + GEOS_LOG_RANK_0( GEOS_FMT( "{}: next time step based on tables coordinates = {}", getName(), nextDt )); + return nextDt; +} + +bool WellControls::isWellOpen() const +{ + return getWellStatus() == WellControls::Status::OPEN; +} + +void WellControls::setWellState( bool open ) +{ + m_wellOpen = open; +} + +bool WellControls::getWellState() const +{ + return m_wellOpen; +} + +void WellControls::setNextDtFromTables( real64 const & currentTime, real64 & nextDt ) +{ + if( isProducer() ) + { + if( getBHPConstraint() != nullptr ) + { + getBHPConstraint()->setNextDtFromTables( currentTime, nextDt ); + } + for( auto const & constraint : getRateConstraints() ) + { + constraint->setNextDtFromTables( currentTime, nextDt ); + } } else { - FunctionManager & functionManager = FunctionManager::getInstance(); - m_targetBHPTable = &(functionManager.getGroup< TableFunction const >( m_targetBHPTableName )); + getBHPConstraint()->setNextDtFromTables( currentTime, nextDt ); + for( auto const & constraint : getRateConstraints() ) + { + constraint->setNextDtFromTables( currentTime, nextDt ); + } + } - GEOS_THROW_IF( m_targetBHPTable->getInterpolationMethod() != TableFunction::InterpolationType::Lower, - GEOS_FMT( "The interpolation method for the time-dependent BHP table {} " - "should be TableFunction::InterpolationType::Lower", - m_targetBHPTable->getName() ), - InputError, getDataContext() ); + WellControls::setNextDtFromTable( m_statusTable, currentTime, nextDt ); +} + +void WellControls::setNextDtFromTable( TableFunction const * table, real64 const currentTime, real64 & nextDt ) +{ + if( table ) + { + // small epsilon to make sure we land on the other side of table interval and pick up the right rate + real64 const eps = 1e-6; + real64 const dtLimit = (table->getCoord( ¤tTime, 0, TableFunction::InterpolationType::Upper ) - currentTime) * ( 1.0 + eps ); + if( dtLimit > eps && dtLimit < nextDt ) + { + nextDt = dtLimit; + } } +} - // 10) Create time-dependent total rate table - if( m_targetTotalRateTableName.empty() ) +namespace +{ +template< typename GROUP, + typename CONSTRAINT = std::conditional_t< + std::is_const_v< std::remove_reference_t< GROUP > >, + WellConstraintBase const, + WellConstraintBase > > +void populateConstraints( GROUP & group, bool isProducer, stdVector< CONSTRAINT * > & constraints, ConstraintSourceId source = ConstraintSourceId::USER ) +{ + if( isProducer ) { - m_targetTotalRateTableName = getName()+"_ConstantTotalRate_table"; - m_targetTotalRateTable = createWellTable( m_targetTotalRateTableName, m_targetTotalRate ); + group.template forSubGroups< ProductionConstraint< MassRateConstraint >, + ProductionConstraint< VolumeRateConstraint >, + ProductionConstraint< PhaseVolumeRateConstraint > >( [&]( CONSTRAINT & constraint ) + { + if( constraint.isConstraintActive() && constraint.getConstraintSource() == source ) + { + constraints.push_back( &constraint ); + } + } ); } else { - FunctionManager & functionManager = FunctionManager::getInstance(); - m_targetTotalRateTable = &(functionManager.getGroup< TableFunction const >( m_targetTotalRateTableName )); + group.template forSubGroups< InjectionConstraint< MassRateConstraint >, + InjectionConstraint< VolumeRateConstraint >, + InjectionConstraint< PhaseVolumeRateConstraint > >( [&]( CONSTRAINT & constraint ) + { + if( constraint.isConstraintActive() && constraint.getConstraintSource() == source ) + { + constraints.push_back( &constraint ); + } + } ); + } +} +} stdVector< WellConstraintBase const * > WellControls::getRateConstraints() const +{ + stdVector< WellConstraintBase const * > constraints; + populateConstraints( *this, isProducer(), constraints ); + return constraints; +} - GEOS_THROW_IF( m_targetTotalRateTable->getInterpolationMethod() != TableFunction::InterpolationType::Lower, - GEOS_FMT( "The interpolation method for the time-dependent total rate table {} " - "should be TableFunction::InterpolationType::Lower", - m_targetTotalRateTable->getName() ), - InputError, getDataContext() ); +stdVector< WellConstraintBase * > WellControls::getRateConstraints() +{ + stdVector< WellConstraintBase * > constraints; + populateConstraints( *this, isProducer(), constraints ); + return constraints; +} + +stdVector< WellConstraintBase const * > WellControls::getAllConstraints() const +{ + stdVector< WellConstraintBase const * > constraints = getRateConstraints(); + constraints.insert( constraints.begin(), getBHPConstraint() ); + return constraints; +} +stdVector< WellConstraintBase * > WellControls::getAllConstraints() +{ + stdVector< WellConstraintBase * > constraints = getRateConstraints(); + constraints.insert( constraints.begin(), getBHPConstraint() ); + return constraints; +} + +WellConstraintBase const * WellControls::getBHPConstraint( const ConstraintSourceId source ) const +{ + WellConstraintBase const * bhpConstraint = nullptr; + // Rely on validation here. We assume that there aren't both constraints listed + if( isProducer() ) + { + forSubGroups< MinimumBHPConstraint >( [&]( WellConstraintBase const & constraint ) + { + if( constraint.isConstraintActive() && constraint.getConstraintSource() == source ) + bhpConstraint = &constraint; + } ); + } + else + { + forSubGroups< MaximumBHPConstraint >( [&]( WellConstraintBase const & constraint ) + { + if( constraint.isConstraintActive() && constraint.getConstraintSource() == source ) + bhpConstraint = &constraint; + } ); } + return bhpConstraint; +} - // 11) Create time-dependent phase rate table - if( m_targetPhaseRateTableName.empty() ) +WellConstraintBase * WellControls::getBHPConstraint( const ConstraintSourceId source ) +{ + WellConstraintBase * bhpConstraint = nullptr; + // Rely on validation here. We assume that there aren't both constraints listed + if( isProducer() ) { - m_targetPhaseRateTableName = getName()+"_ConstantPhaseRate_table"; - m_targetPhaseRateTable = createWellTable( m_targetPhaseRateTableName, m_targetPhaseRate ); + forSubGroups< MinimumBHPConstraint >( [&]( WellConstraintBase & constraint ) + { + if( constraint.isConstraintActive() && constraint.getConstraintSource() == source ) + bhpConstraint = &constraint; + } ); } else { - FunctionManager & functionManager = FunctionManager::getInstance(); - m_targetPhaseRateTable = &(functionManager.getGroup< TableFunction const >( m_targetPhaseRateTableName )); + forSubGroups< MaximumBHPConstraint >( [&]( WellConstraintBase & constraint ) + { + if( constraint.isConstraintActive() && constraint.getConstraintSource() == source ) + bhpConstraint = &constraint; + } ); + } + return bhpConstraint; +} +real64 WellControls::getTargetBHP( real64 const & targetTime, const ConstraintSourceId source ) const +{ + return getBHPConstraint( source )->getConstraintValue( targetTime ); +} - GEOS_THROW_IF( m_targetPhaseRateTable->getInterpolationMethod() != TableFunction::InterpolationType::Lower, - GEOS_FMT( "The interpolation method for the time-dependent phase rate table {} " - "should be TableFunction::InterpolationType::Lower", - m_targetPhaseRateTable->getName() ), - InputError, getDataContext() ); +real64 WellControls::getInjectionTemperature() const +{ + real64 injectionTemperature = 0.0; + localIndex firstIndex = -1; // Used to "capture" the first one + forSubGroupsIndex< InjectionConstraint< MassRateConstraint >, + InjectionConstraint< VolumeRateConstraint >, + InjectionConstraint< PhaseVolumeRateConstraint > >( [&] ( localIndex index, auto const & constraint ) + { + if( firstIndex < 0 && constraint.isConstraintActive() ) + { + injectionTemperature = constraint.getInjectionTemperature(); + firstIndex = index; + } + } ); + return injectionTemperature; +} + +arrayView1d< real64 const > WellControls::getInjectionStream() const +{ + arrayView1d< real64 const > injectionStream; + localIndex firstIndex = -1; // Used to "capture" the first one + forSubGroupsIndex< InjectionConstraint< MassRateConstraint >, + InjectionConstraint< VolumeRateConstraint >, + InjectionConstraint< PhaseVolumeRateConstraint > >( [&] ( localIndex index, auto const & constraint ) + { + if( firstIndex < 0 && constraint.isConstraintActive() ) + { + injectionStream = constraint.getInjectionStream(); + firstIndex = index; + } + } ); + return injectionStream; +} + +integer WellControls::getConstraintPhaseIndex() const +{ + integer phaseIndex = -1; + // Validation should make sure we are not mixing constraints. + // Here we assume that we have zero or one or the other but not both + forSubGroups< ProductionConstraint< PhaseVolumeRateConstraint >, + InjectionConstraint< PhaseVolumeRateConstraint > >( [&] ( auto & constraint ) + { + if( constraint.isConstraintActive() ) + { + phaseIndex = constraint.getPhaseIndex(); + } + } ); + return phaseIndex; +} + +real64 WellControls::getReferenceElevation() const +{ + real64 referenceElevation = 0.0; + // Validation should make sure we are not mixing constraints. + // Here we assume that we have zero or one or the other but not both + forSubGroups< MinimumBHPConstraint, + MaximumBHPConstraint >( [&] ( auto & constraint ) + { + referenceElevation = constraint.getReferenceElevation(); + } ); + return referenceElevation; +} + +void WellControls::implicitStepSetup( real64 const & time_n, + real64 const & GEOS_UNUSED_PARAM( dt ), + DomainPartition & GEOS_UNUSED_PARAM( domain ), + string const & GEOS_UNUSED_PARAM( meshBodyName ), + ElementRegionManager & elemManager, + WellElementSubRegion & subRegion ) +{ + + GEOS_UNUSED_VAR( elemManager ); + // Set perforation status + setPerforationStatus( time_n, subRegion ); +} + +void WellControls::setPerforationStatus( real64 const & time_n, WellElementSubRegion & subRegion ) +{ + FunctionManager & functionManager = FunctionManager::getInstance(); + + // Set perforation status + + PerforationData & perforationData = *subRegion.getPerforationData(); + string_array const & perfStatusTableName = perforationData.getPerfStatusTableName(); + arrayView1d< integer > perfStatus = perforationData.getLocalPerfStatus(); + // for now set to open + for( integer i=0; i( perfStatusTableName[i] ); + perfStatus[i]=PerforationData::PerforationStatus::OPEN; + if( tableFunction->evaluate( &time_n ) < LvArray::NumericLimits< real64 >::epsilon ) + { + perfStatus[i]=PerforationData::PerforationStatus::CLOSED; + } } - // Create time-dependent mass rate table - if( m_targetMassRateTableName.empty() ) + + array1d< localIndex > const perfWellElemIndex = perforationData.getField< fields::perforation::wellElementIndex >(); + // global index local elements (size == subregion.size) + arrayView1d< globalIndex const > globalWellElementIndex = subRegion.getGlobalWellElementIndex(); + + arrayView1d< integer const > const elemGhostRank = subRegion.ghostRank(); + array1d< integer > & currentStatus = subRegion.getWellElementStatus(); + // Local elements + array1d< integer > & localElemStatus = subRegion.getWellLocalElementStatus(); + + integer numLocalElements = subRegion.getNumLocalElements(); + array1d< integer > segStatus( numLocalElements ); + + // Local perforations + for( integer j = 0; j < perforationData.size(); j++ ) { - m_targetMassRateTableName = getName()+"_ConstantMassRate_table"; - m_targetMassRateTable = createWellTable( m_targetMassRateTableName, m_targetMassRate ); + localIndex const iwelem = perfWellElemIndex[j]; + if( elemGhostRank[iwelem] < 0 ) + { + if( perfStatus[j] ) + { + segStatus[iwelem] +=1; + } + } } - else + // Broadcast segment status so all cores have same well status + subRegion.setElementStatus( segStatus ); + integer numOpenElements = 0; + array1d< integer > const & updatedStatus = subRegion.getWellElementStatus(); + for( integer i=0; i( m_targetMassRateTableName )); + numOpenElements += updatedStatus[i]; + } + numOpenElements>0 ? setWellStatus( time_n, WellControls::Status::OPEN ) : setWellStatus( time_n, WellControls::Status::CLOSED ); - GEOS_THROW_IF( m_targetMassRateTable->getInterpolationMethod() != TableFunction::InterpolationType::Lower, - GEOS_FMT( "The interpolation method for the time-dependent mass rate table {} " - "should be TableFunction::InterpolationType::Lower", - m_targetMassRateTable->getName() ), - InputError, getDataContext() ); + + // Set local well element status array + for( integer i=0; i const wellElemLocation = subRegion.getElementCenter(); + arrayView1d< real64 > const wellElemGravCoef = subRegion.getField< fields::well::gravityCoefficient >(); + + arrayView2d< real64 const > const perfLocation = perforationData.getField< fields::perforation::location >(); + arrayView1d< real64 > const perfGravCoef = perforationData.getField< fields::well::gravityCoefficient >(); + + forAll< serialPolicy >( perforationData.size(), [=]( localIndex const iperf ) { - // All well controls without a specified status function will use the same "Open" status function. - m_statusTableName = GEOS_FMT( "{0}_OpenStatus_table", dataRepository::keys::wellControls ); - FunctionManager & functionManager = FunctionManager::getInstance(); - m_statusTable = functionManager.getGroupPointer< TableFunction const >( m_statusTableName ); - if( m_statusTable==nullptr ) + // precompute the depth of the perforations + perfGravCoef[iperf] = LvArray::tensorOps::AiBi< 3 >( perfLocation[iperf], gravVector ); + } ); + + forAll< serialPolicy >( subRegion.size(), [=]( localIndex const iwelem ) + { + // precompute the depth of the well elements + wellElemGravCoef[iwelem] = LvArray::tensorOps::AiBi< 3 >( wellElemLocation[iwelem], gravVector ); + } ); + + forSubGroups< MinimumBHPConstraint, MaximumBHPConstraint >( [&]( auto & constraint ) + { + // set the reference well element where the BHP control is applied + real64 const refElev = constraint.getReferenceElevation(); + constraint.setReferenceGravityCoef( refElev * gravVector[2] ); + } ); +} + +void WellControls::selectWellConstraint( real64 const & time_n, + real64 const & dt, + integer const cycleNumber, + integer const coupledIterationNumber, + DomainPartition & domain, + string const & meshBodyName, + MeshLevel & meshLevel, + ElementRegionManager & elemManager, + WellElementSubRegion & subRegion, + DofManager const & dofManager ) + +{ + + // Well state estimated from reservoir conditions + if( isWellOpen() ) + { + if( !getWellState() ) { - m_statusTable = createWellTable( m_statusTableName, 1.0 ); + setWellState( 1 ); + + initializeWell( domain, domain.getMeshBodies(), meshBodyName, meshLevel, subRegion, time_n ); } } else { - FunctionManager & functionManager = FunctionManager::getInstance(); - m_statusTable = &(functionManager.getGroup< TableFunction const >( m_statusTableName )); - - GEOS_THROW_IF( m_statusTable->getInterpolationMethod() != TableFunction::InterpolationType::Lower, - GEOS_FMT( "The interpolation method for the time-dependent status table {} " - "should be TableFunction::InterpolationType::Lower", - m_statusTable->getName() ), - InputError, getDataContext() ); + setWellState( 0 ); } + bool useEstimator = coupledIterationNumber < estimateSolution(); + + if( getWellState()) + { + if( useEstimator ) + { + // Estimate well solution prior to coupled solve + evaluateConstraints( time_n, + dt, + cycleNumber, + coupledIterationNumber, + domain, + meshLevel, + elemManager, + subRegion, + dofManager ); + } + else + { + // Evaluate well constraints based on current solution + evaluateConstraints( time_n, + subRegion ); + } + + // If a well is opened and then timestep is cut resulting in the well being shut, if the well is opened + // the well initialization code requires control type to by synced + integer owner = -1; + // Only subregion owner evaluates well control and control changes need to be broadcast to all ranks + if( subRegion.isLocallyOwned() ) + { + owner = MpiWrapper::commRank( MPI_COMM_GEOS ); + } + owner = MpiWrapper::max( owner ); + ConstraintTypeId wellControl = getControl(); + MpiWrapper::broadcast( wellControl, owner ); + setControl( wellControl ); + } } -void WellControls::setWellStatus( real64 const & currentTime, WellControls::Status status ) +bool WellControls::evaluateConstraints( real64 const & time_n, + WellElementSubRegion & subRegion ) { - m_wellStatus = status; - if( m_wellStatus == WellControls::Status::OPEN ) + + // create list of all constraints to process + stdVector< WellConstraintBase * > constraintList = getAllConstraints(); + + // Get current constraint + WellConstraintBase * limitingConstraint = nullptr; + for( auto & constraint : constraintList ) { + if( constraint->getName() == getCurrentConstraint()->getName()) + { + limitingConstraint = constraint; + logConstraint( limitingConstraint, subRegion, time_n, true ); + } + } + constraintList.erase( std::find( constraintList.begin(), constraintList.end(), limitingConstraint ) ); - if( isZero( getTargetTotalRate( currentTime ) ) && isZero( getTargetPhaseRate( currentTime ) ) - && isZero( getTargetMassRate( currentTime ) ) ) + // Check current against other constraints + for( auto & constraint : constraintList ) + { + + if( limitingConstraint->getName() != constraint->getName()) { - m_wellStatus = WellControls::Status::CLOSED; + if( constraint->checkViolation( *limitingConstraint, time_n ) ) + { + limitingConstraint = constraint; + setCurrentConstraint( constraint ); + GEOS_LOG_RANK_IF ( subRegion.isLocallyOwned(), + " Well " << subRegion.getName() << " Control switch " << constraint->getName() << " " << constraint->getConstraintValue( time_n ) ); + } } - if( m_statusTable->evaluate( ¤tTime ) < LvArray::NumericLimits< real64 >::epsilon ) + } + logConstraint( limitingConstraint, subRegion, time_n, true ); + + return true; +} + +bool WellControls::evaluateConstraints( real64 const & time_n, + real64 const & dt, + integer const cycleNumber, + integer const coupledIterationNumber, + DomainPartition & domain, + MeshLevel & mesh, + ElementRegionManager & elemManager, + WellElementSubRegion & subRegion, + DofManager const & dofManager ) +{ + // create list of all constraints to solve + // note that initializeWells sets the initial constraint + stdVector< WellConstraintBase * > constraintList = getRateConstraints(); + WellConstraintBase * limitingConstraint = getCurrentConstraint(); + if( isProducer() ) + { + if( limitingConstraint->getControl() != ConstraintTypeId::BHP ) { - m_wellStatus = WellControls::Status::CLOSED; + { // remove from list and add BHP constraint + auto it = std::find( constraintList.begin(), constraintList.end(), limitingConstraint ); + if( it != constraintList.end() ) + { + constraintList.erase( it ); + } + forSubGroups< MinimumBHPConstraint >( [&] ( auto & constraint ) + { + if( constraint.isConstraintActive() ) + { + constraintList.emplace_back( &constraint ); + } + } ); + constraintList.insert( constraintList.begin(), limitingConstraint ); + } } } + else + { + if( limitingConstraint->getControl() != ConstraintTypeId::BHP ) + { + constraintList.emplace_back( getBHPConstraint() ); + } + } + if( isoThermalEstimatorEnabled() ) + { + enableThermalEffects( false ); + solveConstraint ( limitingConstraint, time_n, + dt, + cycleNumber, + coupledIterationNumber, + domain, + mesh, + elemManager, + subRegion, + dofManager ); + enableThermalEffects( true ); + } + solveConstraint ( limitingConstraint, time_n, + dt, + cycleNumber, + coupledIterationNumber, + domain, + mesh, + elemManager, + subRegion, + dofManager ); + + for( auto const & constraint : constraintList ) + { + GEOS_LOG_RANK_IF ( getLogLevel() > 4 && subRegion.isLocallyOwned(), + " Well " << subRegion.getName() << " Constraint " << constraint->getName() << " active " << constraint->isConstraintActive() << + " value " << constraint->getConstraintValue( time_n ) ); + if( constraint->isConstraintActive() && constraint->checkViolation( *limitingConstraint, time_n )) + { + limitingConstraint=constraint; + setCurrentConstraint( limitingConstraint ); + GEOS_LOG_RANK_IF ( getLogLevel() > 4 && subRegion.isLocallyOwned(), + " Well " << subRegion.getName() << " New Limiting Constraint " << constraint->getName() << " active " << constraint->isConstraintActive() << + " value " << constraint->getConstraintValue( time_n ) ); + solveConstraint ( constraint, time_n, + dt, + cycleNumber, + coupledIterationNumber, + domain, + mesh, + elemManager, + subRegion, + dofManager ); + } + } + solveConstraint ( limitingConstraint, time_n, + dt, + cycleNumber, + coupledIterationNumber, + domain, + mesh, + elemManager, + subRegion, + dofManager ); + + logConstraint( limitingConstraint, subRegion, time_n, true ); + + return true; } -bool WellControls::isWellOpen() const +void WellControls::setupWellDofs( DomainPartition & domain, WellElementRegion & wellElementRegion, + string const & meshBodyName, MeshLevel const & meshLevel ) { - return getWellStatus() == WellControls::Status::OPEN; + if( !m_dofManagerInitialized ) + { + m_dofManagerInitialized=true; + m_wellNewtonSolver.setupSystem( *this, domain, meshBodyName, meshLevel, wellElementRegion ); + } } -void WellControls::setNextDtFromTables( real64 const & currentTime, real64 & nextDt ) +void WellControls::solveConstraint( WellConstraintBase *constraint, + real64 const & time_n, + real64 const & dt, + integer const cycleNumber, + integer const coupledIterationNumber, + DomainPartition & domain, + MeshLevel & mesh, + ElementRegionManager & elemManager, + WellElementSubRegion & subRegion, + DofManager const & dofManager ) { - WellControls::setNextDtFromTable( m_targetBHPTable, currentTime, nextDt ); - WellControls::setNextDtFromTable( m_targetMassRateTable, currentTime, nextDt ); - WellControls::setNextDtFromTable( m_targetPhaseRateTable, currentTime, nextDt ); - WellControls::setNextDtFromTable( m_targetTotalRateTable, currentTime, nextDt ); - WellControls::setNextDtFromTable( m_statusTable, currentTime, nextDt ); + GEOS_UNUSED_VAR( dt ); + GEOS_UNUSED_VAR( cycleNumber ); + GEOS_UNUSED_VAR( domain ); + GEOS_UNUSED_VAR( mesh ); + GEOS_UNUSED_VAR( elemManager ); + GEOS_UNUSED_VAR( dofManager ); + + bool useEstimator = coupledIterationNumber < estimateSolution(); + if( useEstimator ) + { + if( getLogLevel() > 4 ) + { + GEOS_LOG_RANK_0( "Well " <getName() << " value " << constraint->getConstraintValue( time_n ) << " active " << + constraint->isConstraintActive() ); + } + if( constraint->isConstraintActive() ) + { + setCurrentConstraint( constraint ); + // If a well is opened and then timestep is cut resulting in the well being shut, if the well is opened +// the well initialization code requires control type to by synced + integer owner = -1; +// Only subregion owner evaluates well control and control changes need to be broadcast to all ranks + if( subRegion.isLocallyOwned() ) + { + owner = MpiWrapper::commRank( MPI_COMM_GEOS ); + } + owner = MpiWrapper::max( owner ); + ConstraintTypeId wellControl = getControl(); + MpiWrapper::broadcast( wellControl, owner ); + setControl( wellControl ); + + m_wellNewtonSolver.solveNonlinearSystem( *this, time_n, + dt, + cycleNumber, + domain, + mesh, + elemManager, + subRegion ); + + if( getLogLevel() > 4 ) + { + GEOS_LOG_RANK_0( "Well " <getName() << " bhp " << constraint->bottomHolePressure() << " phaseVolRate " << + constraint->phaseVolumeRates() << " totalVolRate " << constraint->totalVolumeRate() << " massRate " << constraint->massRate()); + } + } + + + } + } -void WellControls::setNextDtFromTable( TableFunction const * table, real64 const currentTime, real64 & nextDt ) +void +WellControls::assembleSystem( real64 const & time_n, + real64 const & dt, + integer const cycleNumber, + ElementRegionManager & elemManager, + WellElementSubRegion & subRegion, + DofManager const & dofManager, + CRSMatrixView< real64, globalIndex const > const & localMatrix, + arrayView1d< real64 > const & localRhs ) { - if( table ) + GEOS_UNUSED_VAR( cycleNumber ); + assembleWellAccumulationTerms( time_n, dt, subRegion, dofManager, localMatrix.toViewConstSizes(), localRhs ); + + assembleWellConstraintTerms( time_n, dt, subRegion, dofManager, localMatrix.toViewConstSizes(), localRhs ); + + assembleWellPressureRelations( time_n, dt, subRegion, dofManager, localMatrix.toViewConstSizes(), localRhs ); + + computeWellPerforationRates( time_n, dt, elemManager, subRegion ); + + assembleWellFluxTerms( time_n, dt, subRegion, dofManager, localMatrix.toViewConstSizes(), localRhs ); +} + +bool WellControls::validateReferenceRegion() const +{ + // If using surface conditions then there is nothing to validate + if( useSurfaceConditions()) { - // small epsilon to make sure we land on the other side of table interval and pick up the right rate - real64 const eps = 1e-6; - real64 const dtLimit = (table->getCoord( ¤tTime, 0, TableFunction::InterpolationType::Upper ) - currentTime) * ( 1.0 + eps ); - if( dtLimit > eps && dtLimit < nextDt ) + return true; + } + bool const isRoot = MpiWrapper::commRank() == 0; + string const regionName = referenceReservoirRegion(); + if( regionName.empty() ) + { + GEOS_WARNING_IF( isRoot, + GEOS_FMT( "WellControls {} referenceReservoirRegion not set and well constraint " + "fluid property calculations will use top segment pressure and temperature.", + getName()) ); + } + else + { + FlowSolverBase const * flowSolver = getParent().getParent().getGroupPointer< FlowSolverBase >( getFlowSolverName() ); + if( flowSolver == nullptr ) { - nextDt = dtLimit; + return true; } + string_array const & targetRegionsNames = flowSolver->getTargetRegionNames(); + auto const pos = std::find( targetRegionsNames.begin(), targetRegionsNames.end(), regionName ); + GEOS_THROW_IF( pos == targetRegionsNames.end(), + GEOS_FMT( "Region {} is not a target of the reservoir solver {} and cannot " + "be used for referenceReservoirRegion in WellControl {}.", + regionName, flowSolver->getName(), getName() ), + InputError, getDataContext() ); + + return pos != targetRegionsNames.end(); + } + return true; +} + +#if 0 +template< typename STATISTICS > +bool WellControls::validateReferenceRegionStatistics( ElementRegionManager const & elemManager, + real64 & averagePressure, + real64 & averageTemperature ) const +{ + averagePressure = 0.0; + averageTemperature = 0.0; + string const regionName = referenceReservoirRegion(); + if( !regionName.empty()) + { + ElementRegionBase const & region = elemManager.getRegion( regionName ); + GEOS_THROW_IF( !region.hasWrapper( STATISTICS::regionStatisticsName() ), + GEOS_FMT( "WellControl {} referenceReservoirRegion field requires {} to be configured for region {}", + getName(), STATISTICS::catalogName(), regionName ), + RuntimeError, getDataContext() ); + + auto const & stats = region.getReference< typename STATISTICS::RegionStatistics >( STATISTICS::regionStatisticsName() ); + GEOS_THROW_IF( stats.averagePressure <= 0.0, + GEOS_FMT( "No region average quantities computed. WellControl {} referenceReservoirRegion field requires " + "{} to be configured for region {} ", + getName(), STATISTICS::catalogName(), regionName ), + RuntimeError, getDataContext()); + averagePressure = stats.averagePressure; + averageTemperature = stats.averageTemperature; } + return true; } +template bool WellControls::validateReferenceRegionStatistics< singlePhaseStatistics::StatsTask >( ElementRegionManager const &, real64 &, real64 & ) const; +template bool WellControls::validateReferenceRegionStatistics< compositionalMultiphaseStatistics::StatsTask >( ElementRegionManager const &, real64 &, real64 & ) const; +#endif } //namespace geos diff --git a/src/coreComponents/physicsSolvers/fluidFlow/wells/WellControls.hpp b/src/coreComponents/physicsSolvers/fluidFlow/wells/WellControls.hpp index 301c3c42f7c..870e0b45fe0 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/wells/WellControls.hpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/wells/WellControls.hpp @@ -17,15 +17,20 @@ * @file WellControls.hpp */ - #ifndef GEOS_PHYSICSSOLVERS_FLUIDFLOW_WELLS_WELLCONTROLS_HPP #define GEOS_PHYSICSSOLVERS_FLUIDFLOW_WELLS_WELLCONTROLS_HPP +#include "physicsSolvers/PhysicsSolverBase.hpp" #include "common/format/EnumStrings.hpp" #include "dataRepository/Group.hpp" #include "functions/TableFunction.hpp" #include "constitutive/fluid/multifluid/MultiFluidBase.hpp" +#include "constitutive/fluid/multifluid/MultiFluidBase.hpp" +#include "constitutive/fluid/singlefluid/SingleFluidBase.hpp" +#include "physicsSolvers/fluidFlow/wells/WellConstraintsBase.hpp" +#include "physicsSolvers/fluidFlow/wells/WellNewtonSolver.hpp" + namespace geos { namespace dataRepository @@ -36,17 +41,19 @@ static constexpr auto wellControls = "WellControls"; } } +class ElementsReporterBuffer; + /** * @class WellControls * @brief This class describes the controls used to operate a well. */ -class WellControls : public dataRepository::Group +class WellControls : public dataRepository::Group { public: /** Type of wells - * Either producer or injector. + * Either producer or injector */ enum class Type : integer { @@ -63,18 +70,6 @@ class WellControls : public dataRepository::Group CLOSED /**< shutin well */ }; - /** Types of well controls - * Used to specifiy a well's operating conditions - */ - enum class Control : integer - { - BHP, /**< The well operates at a specified bottom hole pressure (BHP) */ - PHASEVOLRATE, /**< The well operates at a specified phase volumetric flow rate */ - TOTALVOLRATE, /**< The well operates at a specified total volumetric flow rate */ - MASSRATE, /** const & localMatrix, + arrayView1d< real64 > const & localRhs ); + /** + * @brief assembles the accumulation term for an individual well + * @param time_n time at the beginning of the time step + * @param dt the time step size + * @param subRegion the well subregion containing all the primary and dependent fields + * @param dofManager degree-of-freedom manager associated with the linear system + * @param matrix the system matrix + * @param rhs the system right-hand side vector + */ + virtual void assembleWellAccumulationTerms( real64 const & time, + real64 const & dt, + WellElementSubRegion & subRegion, + DofManager const & dofManager, + CRSMatrixView< real64, globalIndex const > const & localMatrix, + arrayView1d< real64 > const & localRhs ) = 0; + /** + * @brief assembles the well momentum terms for an individual well + * @param time_n time at the beginning of the time step + * @param dt the time step size + * @param subRegion the well subregion containing all the primary and dependent fields + * @param dofManager degree-of-freedom manager associated with the linear system + * @param matrix the system matrix + * @param rhs the system right-hand side vector + */ + virtual void assembleWellPressureRelations( real64 const & time_n, + real64 const & dt, + WellElementSubRegion const & subRegion, + DofManager const & dofManager, + CRSMatrixView< real64, globalIndex const > const & localMatrix, + arrayView1d< real64 > const & localRhs ) = 0; + /** + * @brief assembles the well constraint terms for an individual well + * @param time_n time at the beginning of the time step + * @param dt the time step size + * @param subRegion the well subregion containing all the primary and dependent fields + * @param dofManager degree-of-freedom manager associated with the linear system + * @param matrix the system matrix + * @param rhs the system right-hand side vector */ - Control getControl() const { return m_currentControl; } + virtual void assembleWellConstraintTerms( real64 const & time_n, + real64 const & dt, + WellElementSubRegion const & subRegion, + DofManager const & dofManager, + CRSMatrixView< real64, globalIndex const > const & localMatrix, + arrayView1d< real64 > const & localRhs ) = 0; /** - * @brief Set the control type for the well. - * @param[in] newControl type + * @brief Recompute the perforation rates for all the wells + * @param time_n the time at the beginning of the time step + * @param dt the time step size + * @param elemManager the element region manager + * @param subRegion the well subregion containing all the primary and dependent fields */ - void setControl( Control const & newControl ) { m_currentControl = newControl; } + virtual void computeWellPerforationRates( real64 const & time_n, + real64 const & GEOS_UNUSED_PARAM( dt ), + ElementRegionManager & elemManager, + WellElementSubRegion & subRegion ) = 0; /** - * @brief Get the input control type for the well. - * @return the Control enum enforced at the well + * @brief assembles the flux terms for individual well for all connections between well elements + * @param time_n previous time value + * @param dt time step + * @param subRegion the well subregion containing all the primary and dependent fields + * @param dofManager degree-of-freedom manager associated with the linear system + * @param matrix the system matrix + * @param rhs the system right-hand side vector */ - Control getInputControl() const { return m_inputControl; } + virtual void assembleWellFluxTerms( real64 const & time, + real64 const & dt, + WellElementSubRegion const & subRegion, + DofManager const & dofManager, + CRSMatrixView< real64, globalIndex const > const & localMatrix, + arrayView1d< real64 > const & localRhs ) = 0; + virtual real64 + calculateWellResidualNorm( real64 const & time_n, + real64 const & dt, + NonlinearSolverParameters const & nonlinearSolverParameters, + WellElementSubRegion const & subRegion, + DofManager const & dofManager, + arrayView1d< real64 const > const & localRhs ) = 0; + + virtual array1d< real64 > + calculateLocalWellResidualNorm( real64 const & time_n, + real64 const & dt, + NonlinearSolverParameters const & nonlinearSolverParameters, + WellElementSubRegion const & subRegion, + DofManager const & dofManager, + arrayView1d< real64 const > const & localRhs ) = 0; + + virtual real64 + scalingForWellSystemSolution( WellElementSubRegion & subRegion, + DofManager const & dofManager, + arrayView1d< real64 const > const & localSolution ) = 0; + + virtual bool + checkWellSystemSolution( WellElementSubRegion & subRegion, + DofManager const & dofManager, + arrayView1d< real64 const > const & localSolution, + real64 const scalingFactor, + real64 & minPressure, + real64 & minDensity, + real64 & minTotalDensity, + ElementsReporterBuffer & negPressureIds, + ElementsReporterBuffer & negDensityIds, + ElementsReporterBuffer & negTotalDensityIds ) = 0; + + virtual void + applyWellSystemSolution( DofManager const & dofManager, + arrayView1d< real64 const > const & localSolution, + real64 const scalingFactor, + real64 const dt, + DomainPartition & domain, + MeshLevel & mesh, + WellElementSubRegion & subRegion ) = 0; + + virtual void applyWellBoundaryConditions( real64 const time_n, + real64 const dt, + ElementRegionManager & elemManager, + WellElementSubRegion & subRegion, + DofManager const & dofManager, + arrayView1d< real64 > const & localRhs, + CRSMatrixView< real64, globalIndex const > const & localMatrix ) = 0; + /** + * @brief Recompute all dependent quantities from primary variables (including constitutive models) + * @param subRegion the well subregion containing all the primary and dependent fields + */ + virtual real64 updateWellState( MeshBody const & meshBody, + ElementRegionManager const & elemManager, + WellElementSubRegion & subRegion ) = 0; /** - * @brief Getter for the reference elevation where the BHP control is enforced - * @return the reference elevation + * @brief Reset the well state to the beginning of the time step + * @param subRegion the well subregion containing all the primary and dependent fields */ - real64 getReferenceElevation() const { return m_refElevation; } + virtual void resetStateToBeginningOfStep( DomainPartition & domain, + string const & meshBodyName, ElementRegionManager const & elemManager, WellElementSubRegion & subRegion ) = 0; + + virtual void postInputInitialization() override; + + virtual void initializePreSubGroups() override; + + virtual void initializeWellPostInitialConditionsPreSubGroups( WellElementSubRegion & subRegion ) = 0; + virtual void printRates( real64 const & time_n, + real64 const & dt, + WellElementSubRegion const & subRegion ) = 0; /** - * @brief Getter for the reference gravity coefficient - * @return the reference gravity coefficient + * @name Getters / Setters */ - real64 getReferenceGravityCoef() const { return m_refGravCoef; } + ///@{ /** - * @brief Setter for the reference gravity + * @brief Get the Constitutive Name object + * + * @tparam CONSTITUTIVE_BASE_TYPE the base type of the constitutive model. + * @param subRegion the element subregion on which the constitutive model is registered + * @return the name name of the constitutive model of type CONSTITUTIVE_BASE_TYPE registered on the subregion. */ - void setReferenceGravityCoef( real64 const & refGravCoef ) { m_refGravCoef = refGravCoef; } + template< typename CONSTITUTIVE_BASE_TYPE > + static string getConstitutiveName( ElementSubRegionBase const & subRegion ); + /** + * @brief Register wrapper with given name and store constitutive model name on the subregion + * + * @tparam CONSTITUTIVE the base type of the constitutive model. + * @param subRegion the subregion on which the constitutive model is registered. + * @param wrapperName the wrapper name to register. + * @param constitutiveType the type description of the constitutive model. + */ + template< typename CONSTITUTIVE > + void setConstitutiveName( ElementSubRegionBase & subRegion, string const & wrapperName, string const & constitutiveType ) const; /** - * @brief Get the target bottom hole pressure value. - * @return a value for the target bottom hole pressure + * @brief return the list of target regions + * @return the array of region names */ - real64 getTargetBHP( real64 const & currentTime ) const - { - return m_targetBHPTable->evaluate( ¤tTime ); - } + string_array const & getTargetRegionNames() const {return m_targetRegionNames;} + /** + * @brief Get the control type for the well. + * @return the Control enum enforced at the well + */ + std::string getFlowSolverName() const { return m_flowSolverName; } /** - * @brief Get the target total rate - * @return the target total rate + * @brief Set the control type for the well. + * @param[in] flowSolverName the name of the flow solver */ - real64 getTargetTotalRate( real64 const & currentTime ) const - { - return m_rateSign * m_targetTotalRateTable->evaluate( ¤tTime ); - } + void setFlowSolverName( const std::string & flowSolverName ) { m_flowSolverName = flowSolverName; } /** - * @brief Get the target phase rate - * @return the target phase rate + * @brief return the name of the discretization object + * @return the name of the discretization object */ - real64 getTargetPhaseRate( real64 const & currentTime ) const - { - return m_rateSign * m_targetPhaseRateTable->evaluate( ¤tTime ); - } + std::string getDiscretizationName() const { return m_discretizationName; } /** - * @brief Get the target mass rate - * @return the target mass rate + * @brief set the name of the discretization object + * @param[in] discretizationName name */ - real64 getTargetMassRate( real64 const & currentTime ) const - { - return m_rateSign * m_targetMassRateTable->evaluate( ¤tTime ); - } + void setDiscretizationName( const std::string & discretizationName ) { m_discretizationName = discretizationName; } + /** + * @brief Get the control type for the well. + * @return the Control enum enforced at the well + */ + ConstraintTypeId getControl() const { return m_currentControl; } + + /** + * @brief Set the control type for the well. + * @param[in] newControl type + */ + void setControl( ConstraintTypeId const & newControl ) { m_currentControl = newControl; } /** - * @brief Get the target phase name - * @return the target phase name + * @brief Get the input control type for the well. + * @return the Control enum enforced at the well */ - const string & getTargetPhaseName() const { return m_targetPhaseName; } + ConstraintTypeId getInputControl() const { return m_inputControl; } /** - * @brief Const accessor for the composition of the injection stream - * @return a global component fraction vector + * @brief getter for esitmator switch + * @return True if estimate well solution */ - arrayView1d< real64 const > getInjectionStream() const { return m_injectionStream; } + integer estimateSolution() const { return m_estimateSolution; } + + + /** + * @brief Returns the target bottom hole pressure value. + * @param[in] targetTime time at which to evaluate the constraint + * @return the injector maximum bottom hole pressure or producer minimum bottom hole pressure + */ + real64 getTargetBHP( real64 const & targetTime, const ConstraintSourceId source = ConstraintSourceId::USER ) const; /** * @brief Const accessor for the temperature of the injection stream * @return the temperature of the injection stream */ - real64 getInjectionTemperature() const { return m_injectionTemperature; } + real64 getInjectionTemperature() const; + + /** + * @brief Const accessor for the injection stream + * @return the injection stream + */ + arrayView1d< real64 const > getInjectionStream() const; + + /** + * @brief Const accessor for the phase constraint index + * @return phase index associated with phase constraint + */ + integer getConstraintPhaseIndex() const; + + /** + * @brief Return the reference elvation where pressure constraint is measured + * @return vertical location of constraint + */ + real64 getReferenceElevation() const; /** * @brief Getter for the flag specifying whether we check rates at surface or reservoir conditions @@ -252,7 +501,7 @@ class WellControls : public dataRepository::Group * @brief Getter for the reservoir region associated with reservoir volume constraint * @return name of reservoir region */ - string referenceReservoirRegion() const { return m_referenceReservoirRegion; } + string const & referenceReservoirRegion() const { return m_referenceReservoirRegion; } /** * @brief Getter for the surface pressure when m_useSurfaceConditions == 1 @@ -278,12 +527,66 @@ class WellControls : public dataRepository::Group */ bool isProducer() const { return ( m_type == Type::PRODUCER ); } + /** + * @brief getter for iso/thermal switch + * @return True if thermal + */ + integer isThermal() const { return m_isThermal; } + + /** + * @brief setter for iso/thermal switch + * @param[in] isThermal + */ + + void setThermal( bool isThermal ) { m_isThermal=isThermal; } + + /** + * @brief setter to activate mass formulation + * @param[in] useMass + */ + + void setUseMass( integer useMass ) { m_useMass=useMass; } + + /** + * @brief is useMass option active + * @return a boolean + */ + + integer useMass( ) { return m_useMass;} + /** * @brief Is the well open (or shut) at currentTime, status initalized in WellSolverBase::implicitStepSetup * @return a boolean */ bool isWellOpen() const; + /** + * @brief Set the well state + * @param[in] open boolean + */ + void setWellState( bool open ); + /** + * @brief Get the well state + * @return a boolean + */ + bool getWellState() const; + + /** + * @brief Set the current consrtaint + * @param[in] currentConstraint pointer to constraint + */ + void setCurrentConstraint( WellConstraintBase * currentConstraint ) + { + setControl( currentConstraint->getControl() ); + m_currentConstraint = currentConstraint; + } + /** + * @brief Get the current consrtaint + * @return pointer to constraint + */ + WellConstraintBase * getCurrentConstraint() { return m_currentConstraint; } + WellConstraintBase const * getCurrentConstraint() const { return m_currentConstraint; } + /** * @brief Getter for the flag to enable crossflow * @return the flag deciding whether crossflow is allowed or not @@ -302,6 +605,16 @@ class WellControls : public dataRepository::Group * @param[inout] nextDt the time step */ void setNextDtFromTables( real64 const & currentTime, real64 & nextDt ); + /** + * @brief Utility function to keep the well variables during a time step (used in + * poromechanics simulations) + * @param[in] keepVariablesConstantDuringInitStep flag to tell the solver to freeze its + * primary variables during a time step + * @detail This function is meant to be called by a specific task before/after the + * initialization step + */ + void setKeepVariablesConstantDuringInitStep( bool const keepVariablesConstantDuringInitStep ) + { m_keepVariablesConstantDuringInitStep = keepVariablesConstantDuringInitStep; } /** * @brief setter for multi fluid separator @@ -314,14 +627,22 @@ class WellControls : public dataRepository::Group */ constitutive::MultiFluidBase & getMultiFluidSeparator() { return dynamicCast< constitutive::MultiFluidBase & >( *m_fluidSeparatorPtr ); } + /** + * @brief Getter for single fluid separator + * @return reference to separator + */ + constitutive::SingleFluidBase & getSingleFluidSeparator() { return dynamicCast< constitutive::SingleFluidBase & >( *m_fluidSeparatorPtr ); } + /** * @brief Getter for the reservoir average pressure when m_useSurfaceConditions == 0 + * @note When not available, value is less or equal to 0.0. * @return the pressure */ real64 getRegionAveragePressure() const { return m_regionAveragePressure; } /** * @brief Set the reservoir average pressure when m_useSurfaceConditions == 0 + * @note When not available, value is less or equal to 0.0. * @param[in] regionAveragePressure value for pressure */ void setRegionAveragePressure( real64 regionAveragePressure ) { m_regionAveragePressure = regionAveragePressure; } @@ -352,34 +673,49 @@ class WellControls : public dataRepository::Group WellControls::Status getWellStatus () const { return m_wellStatus; } ///@} + virtual string wellElementDofName() const = 0; + + virtual string resElementDofName() const = 0; + + /** + * @brief getter for the number of degrees of freedom per well element + * @return the number of dofs + */ + localIndex numDofPerWellElement() const { return m_numDofPerWellElement; } + + /** + * @brief getter for the number of degrees of freedom per mesh element + * @return the number of dofs + */ + localIndex numDofPerResElement() const { return m_numDofPerResElement; } + + + virtual localIndex numFluidComponents() const = 0; + + virtual localIndex numFluidPhases() const = 0; + /** * @brief Struct to serve as a container for variable strings and keys. * @struct viewKeyStruct */ struct viewKeyStruct { + /// String key for the fluid model names + static constexpr char const * fluidNamesString() { return "fluidNames"; } + /// String key for the write CSV flag + static constexpr char const * writeCSVFlagString() { return "writeCSV"; } + static constexpr char const * timeStepFromTablesFlagString() { return "timeStepFromTables"; } + /// String for the targetRegions wrapper + static constexpr char const * targetRegionsString() { return "targetRegions"; } + /// String key for the well reference elevation (for BHP control) static constexpr char const * refElevString() { return "referenceElevation"; } /// String key for the well type static constexpr char const * typeString() { return "type"; } - /// String key for the well input control - static constexpr char const * inputControlString() { return "control"; } /// String key for the well current control static constexpr char const * currentControlString() { return "currentControl"; } - /// String key for the well target BHP - static constexpr char const * targetBHPString() { return "targetBHP"; } - /// String key for the well target rate - static constexpr char const * targetTotalRateString() { return "targetTotalRate"; } - /// String key for the well target phase rate - static constexpr char const * targetPhaseRateString() { return "targetPhaseRate"; } - /// String key for the well target phase name - static constexpr char const * targetPhaseNameString() { return "targetPhaseName"; } - /// String key for the well target phase name - static constexpr char const * targetMassRateString() { return "targetMassRate"; } - /// String key for the well injection stream - static constexpr char const * injectionStreamString() { return "injectionStream"; } - /// String key for the well injection temperature - static constexpr char const * injectionTemperatureString() { return "injectionTemperature"; } + /// String key for the well input control + static constexpr char const * inputControlString() { return "control"; } /// String key for checking the rates at surface conditions static constexpr char const * useSurfaceConditionsString() { return "useSurfaceConditions"; } /// String key for reference reservoir region @@ -388,14 +724,7 @@ class WellControls : public dataRepository::Group static constexpr char const * surfacePressureString() { return "surfacePressure"; } /// String key for the surface temperature static constexpr char const * surfaceTemperatureString() { return "surfaceTemperature"; } - /// string key for total rate table name - static constexpr char const * targetTotalRateTableNameString() { return "targetTotalRateTableName"; } - /// string key for phase rate table name - static constexpr char const * targetPhaseRateTableNameString() { return "targetPhaseRateTableName"; } - /// string key for mass rate table name - static constexpr char const * targetMassRateTableNameString() { return "targetMassRateTableName"; } - /// string key for BHP table name - static constexpr char const * targetBHPTableNameString() { return "targetBHPTableName"; } + /// string key for status table name static constexpr char const * statusTableNameString() { return "statusTableName"; } /// string key for perforation status table name @@ -405,55 +734,217 @@ class WellControls : public dataRepository::Group /// string key for the initial pressure coefficient static constexpr char const * initialPressureCoefficientString() { return "initialPressureCoefficient"; } - } - /// ViewKey struct for the WellControls class - viewKeysWellControls; + /// string key for the estimate well solution flag + static constexpr char const * estimateWellSolutionString() { return "estimateWellSolution"; } + /// string key for the enable iso thermal estimator flag + static constexpr char const * enableIsoThermalEstimatorString() { return "enableIsoThermalEstimator"; } + + // control data (not registered on the mesh) + static constexpr char const * massDensityString() { return "massDensity";} + + static constexpr char const * currentBHPString() { return "currentBHP"; } + + static constexpr char const * currentPhaseVolRateString() { return "currentPhaseVolumetricRate"; } + static constexpr char const * currentVolRateString() { return "currentVolRate"; } + + static constexpr char const * currentTotalVolRateString() { return "currentTotalVolumetricRate"; } + + static constexpr char const * currentMassRateString() { return "currentMassRate"; } + }; + + /** + * @brief Structure to hold scoped key names + */ + struct groupKeyStruct + { + /// string key for the well Newton solver + static constexpr char const * wellNewtonSolverString() { return "WellNewtonSolver"; } + }; + + void setPerforationStatus( real64 const & time_n, WellElementSubRegion & subRegion ); + void setGravCoef( WellElementSubRegion & subRegion, R1Tensor const & gravVector ); + /** + * @brief Set next time step based on a table function + * @param[in] table the table function + * @param[in] currentTime the current time + * @param[inout] nextDt the time step + */ static void setNextDtFromTable( TableFunction const * table, real64 const currentTime, real64 & nextDt ); + /** + * @brief Create a constraint + * @tparam ConstraintType the type of constraint to create + * @param[in] constraintName name to assign to the constraint + */ + template< typename ConstraintType > void createConstraint ( string const & constraintName ); + + + /** + * @brief Gets the defined BHP constraint + * @details Returns the BHP constraint if one is defined for the WellControl. For a producer + * well this will be a minimum BHP constraint and for an injector well this will be a maximum + * BHP constraint. This will possibly return null if no BHP constraint is set. Validation is + * in place to enforce the setting of at least one BHp constraint. + * @return A BHP constraint object of one is defined + */ + WellConstraintBase const * getBHPConstraint( const ConstraintSourceId source = ConstraintSourceId::USER ) const; + WellConstraintBase * getBHPConstraint( const ConstraintSourceId source = ConstraintSourceId::USER ); + + /** + * @brief Gets a list of rate constraints + * @details Returns a list of rate constraints for the WellControl. For a producer + * well these will be a production rate constraints `ProductionConstraint` and for an + * injector well these will be injection rate constraints `InjectionConstraint`. + */ + stdVector< WellConstraintBase const * > getRateConstraints() const; + stdVector< WellConstraintBase * > getRateConstraints(); + + /** + * @brief Gets a list of all constraints constraints + * @details Returns a list of all constraints for the WellControl including rate and BHP + * constraints. + */ + stdVector< WellConstraintBase const * > getAllConstraints() const; + stdVector< WellConstraintBase * > getAllConstraints(); + + /** + * @brief Set thermal effects enable + * @param[in] true/false + */ + void enableThermalEffects ( bool enable ) { m_thermalEffectsEnabled = enable; }; + + /** + * @brief Are thermal effects enabled + * @return true if thermal effects are enabled, false otherwise + */ + bool thermalEffectsEnabled() const { return m_thermalEffectsEnabled; } + + /** + * @brief Is isoThermalEstimator enabled + * @return true if isoThermalEstimator is enabled, false otherwise + */ + bool isoThermalEstimatorEnabled() const { return m_enableIsoThermalEstimator; } + + void setupWellDofs( DomainPartition & domain, WellElementRegion & wellElementRegion, string const & meshBodyName, MeshLevel const & meshLevel ); + WellNewtonSolver & getWellNewtonSolver() { return m_wellNewtonSolver; } + + void selectWellConstraint( real64 const & time_n, + real64 const & dt, + integer const cycleNumber, + integer const coupledIterationNumber, + DomainPartition & domain, + string const & meshBodyName, + MeshLevel & mesh, + ElementRegionManager & elemManager, + WellElementSubRegion & subRegion, + DofManager const & dofManager ); + protected: + virtual void postRestartInitialization( )override; - virtual void postInputInitialization() override; + /** + * @brief Logs the state and values of a specific well constraint. + * + * @details This method evaluates whether the provided constraint requires + * logging based on its validity, the current log level being strictly greater + * than 4, and the region being locally owned. When the constraint is flagged + * as the limiting constraint, it logs extensive operational data such as the + * bottom hole pressure, individual phase volume rates, the total volume rate, + * and the mass rate. Otherwise, it logs general information, specifically + * whether the constraint is active and its target value at the current time. + * + * @param constraint Pointer to the base constraint object to evaluate and log. + * @param region Reference to the well element sub-region associated with it. + * @param time The current simulation time used to get the constraint value. + * @param isLimiting Boolean indicating if this is the active limiting constraint. + */ + void logConstraint( WellConstraintBase const * constraint, + WellElementSubRegion const & region, + real64 time, + bool isLimiting = false ) const; + /** + * @brief Validates the reference region + * @details Validates the reference region by ensuring that it is defined for cases that do not + * use surface conditions. If the reference region is provided, it is checked against the flow + * solves regions. + * @return @c true if the region is valid + */ + bool validateReferenceRegion() const; + /** + * @brief Validates and retrieves the reference region statistics for average pressure and temperature. + * + * @details This template method checks if a reference reservoir region is configured for the well control. + * If a region is specified, it retrieves the region from the provided element manager and verifies + * that the required statistics wrapper exists. It then extracts the average pressure and temperature, + * throwing an exception if the average pressure has not been properly computed. + * + * @tparam STATISTICS Type providing static methods and types for region statistics (SinglePhaseStatistics or + * CompositionalMultiphaseStatistics). + * @param elementManager Reference to the ElementRegionManager used to look up the reservoir region. + * @param[out] averagePressure Reference to a real64 variable where the retrieved average pressure is stored. + * @param[out] averageTemperature Reference to a real64 variable where the retrieved average temperature is stored. + * @return Boolean value, always returning true upon successful validation. + */ + //template< typename STATISTICS > + //bool validateReferenceRegionStatistics( ElementRegionManager const & elementManager, + // real64 & averagePressure, + // real64 & averageTemperature ) const; private: + /// List of names of regions the solver will be applied to + string_array m_targetRegionNames; + +protected: /// Well type (as Type enum) Type m_type; - /// Reference elevation - real64 m_refElevation; + /// Name of the flow solver managing this well + std::string m_flowSolverName; - /// Gravity coefficient of the reference elevation - real64 m_refGravCoef; + /// Name of the discretization for the region + std::string m_discretizationName; - /// Input well controls as a Control enum - Control m_inputControl; + /// flag indicating whether mass or molar formulation should be used + integer m_useMass; - /// Well controls as a Control enum - Control m_currentControl; + /// the max number of fluid phases + integer m_numPhases; - /// Target bottom hole pressure value - real64 m_targetBHP; + /// the number of fluid components + integer m_numComponents; - /// Target rate value - real64 m_targetTotalRate; + /// the number of Degrees of Freedom per well element + integer m_numDofPerWellElement; - /// Target phase rate value - real64 m_targetPhaseRate; + /// the number of Degrees of Freedom per reservoir element + integer m_numDofPerResElement; - /// Name of the targeted phase - string m_targetPhaseName; + /// flag indicating whether thermal formulation is used + integer m_isThermal; + /// flag to freeze the initial state during initialization in coupled problems + bool m_keepVariablesConstantDuringInitStep; + /// rates output + integer m_writeCSV; + string const m_ratesOutputDir; + + // flag to enable time step selection base on rates/bhp tables coordinates + integer m_timeStepFromTables; + + /// Reference elevation + real64 m_refElevation; - /// Target MassRate - real64 m_targetMassRate; + /// Gravity coefficient of the reference elevation + real64 m_refGravCoef; - /// Vector with global component fractions at the injector - array1d< real64 > m_injectionStream; + /// Input well controls as a Control enum + ConstraintTypeId m_inputControl; - /// Temperature at the injector - real64 m_injectionTemperature; + /// Well controls as a Control enum + ConstraintTypeId m_currentControl; /// Flag to decide whether rates are controlled at rates or surface conditions integer m_useSurfaceConditions; @@ -461,6 +952,9 @@ class WellControls : public dataRepository::Group // Fuild model to compute properties for constraint equation user specified conditions std::unique_ptr< constitutive::ConstitutiveBase > m_fluidSeparatorPtr; + /// name of the fluid constitutive model used as a reference for component/phase description on subregion + string m_referenceFluidModelName; + /// Reservoir region associated with reservoir volume constraint string m_referenceReservoirRegion; @@ -470,21 +964,6 @@ class WellControls : public dataRepository::Group /// Surface temperature real64 m_surfaceTemp; - /// Total rate table name - string m_targetTotalRateTableName; - - /// Phase rate table name - string m_targetPhaseRateTableName; - - /// Mass rate table name - string m_targetMassRateTableName; - - /// BHP table name - string m_targetBHPTableName; - - /// Well status table name - string m_statusTableName; - /// Perforation status table name string m_perfStatusTableName; @@ -494,41 +973,50 @@ class WellControls : public dataRepository::Group /// Tuning coefficient for the initial well pressure real64 m_initialPressureCoefficient; - /// Rate sign. +1 for injector, -1 for producer - real64 m_rateSign; - - /// Total rate table - TableFunction const * m_targetTotalRateTable; + // Current constrint + WellConstraintBase * m_currentConstraint{}; - /// Phase rate table - TableFunction const * m_targetPhaseRateTable; + /// Well status + WellControls::Status m_wellStatus; - /// Mass rate table - TableFunction const * m_targetMassRateTable; + /// Well open flag + bool m_wellOpen; - /// BHP table - TableFunction const * m_targetBHPTable; + /// Well status table name + string m_statusTableName; /// Status table TableFunction const * m_statusTable; - /// Well status - WellControls::Status m_wellStatus; - - /// Region average pressure used in volume rate constraint calculations real64 m_regionAveragePressure; /// Region average temperature used in volume rate constraint calculations real64 m_regionAverageTemperature; + integer m_estimateSolution; + integer m_enableIsoThermalEstimator; + bool m_thermalEffectsEnabled; + + WellNewtonSolver m_wellNewtonSolver; + + + /// @brief Well DofManager + /// @details This DofManager is used to store the DOF numbers for the estimator + /// @note This DofManager is used in the assembly of the estimators linear system + DofManager m_estimatorDoFManager; + bool m_dofManagerInitialized; }; -ENUM_STRINGS( WellControls::Type, + +// Use local aliases to avoid accidental macro expansion of the tokens 'Type' or 'Control' +using WellControls_Type = WellControls::Type; +ENUM_STRINGS( WellControls_Type, "producer", "injector" ); -ENUM_STRINGS( WellControls::Control, +using WellControls_Control = ConstraintTypeId; +ENUM_STRINGS( WellControls_Control, "BHP", "phaseVolRate", "totalVolRate", @@ -536,6 +1024,62 @@ ENUM_STRINGS( WellControls::Control, "uninitialized" ); +template< typename CONSTITUTIVE > +void WellControls::setConstitutiveName( ElementSubRegionBase & subRegion, string const & wrapperName, string const & constitutiveType ) const +{ + subRegion.registerWrapper< string >( wrapperName ). + setPlotLevel( dataRepository::PlotLevel::NOPLOT ). + setRestartFlags( dataRepository::RestartFlags::NO_WRITE ). + setSizedFromParent( 0 ); + + string & constitutiveName = subRegion.getReference< string >( wrapperName ); + constitutiveName = getConstitutiveName< CONSTITUTIVE >( subRegion ); + GEOS_ERROR_IF( constitutiveName.empty(), GEOS_FMT( "{}: {} constitutive model not found on subregion {}", + getDataContext(), constitutiveType, subRegion.getName() ) ); +} +template< typename CONSTITUTIVE_BASE_TYPE > +string WellControls::getConstitutiveName( ElementSubRegionBase const & subRegion ) +{ + string validName; + dataRepository::Group const & constitutiveModels = subRegion.getConstitutiveModels(); + + constitutiveModels.forSubGroups< CONSTITUTIVE_BASE_TYPE >( [&]( dataRepository::Group const & model ) + { + GEOS_ERROR_IF( !validName.empty(), "A valid constitutive model was already found." ); + validName = model.getName(); + } ); + + return validName; +} + +/** + * @brief Get the Constitutive Model object + * @tparam BASETYPE the base type of the constitutive model. + * @tparam LOOKUP_TYPE the type of the key used to look up the constitutive model. + * @param dataGroup the data group containing the constitutive models. + * @param key the key used to look up the constitutive model. + * @return the constitutive model of type @p BASETYPE registered on the @p dataGroup with the key @p key. + */ +template< typename BASETYPE = constitutive::ConstitutiveBase, typename LOOKUP_TYPE > +static BASETYPE const & getConstitutiveModel( dataRepository::Group const & dataGroup, LOOKUP_TYPE const & key ) +{ + dataRepository::Group const & constitutiveModels = dataGroup.getGroup( ElementSubRegionBase::groupKeyStruct::constitutiveModelsString() ); + return constitutiveModels.getGroup< BASETYPE >( key ); +} +/** + * @brief Get the Constitutive Model object + * @tparam BASETYPE the base type of the constitutive model. + * @tparam LOOKUP_TYPE the type of the key used to look up the constitutive model. + * @param dataGroup the data group containing the constitutive models. + * @param key the key used to look up the constitutive model. + * @return the constitutive model of type @p BASETYPE registered on the @p dataGroup with the key @p key. + */ +template< typename BASETYPE = constitutive::ConstitutiveBase, typename LOOKUP_TYPE > +static BASETYPE & getConstitutiveModel( dataRepository::Group & dataGroup, LOOKUP_TYPE const & key ) +{ + dataRepository::Group & constitutiveModels = dataGroup.getGroup( ElementSubRegionBase::groupKeyStruct::constitutiveModelsString() ); + return constitutiveModels.getGroup< BASETYPE >( key ); +} } //namespace geos #endif //GEOS_PHYSICSSOLVERS_FLUIDFLOW_WELLS_WELLCONTROLS_HPP diff --git a/src/coreComponents/physicsSolvers/fluidFlow/wells/WellFields.hpp b/src/coreComponents/physicsSolvers/fluidFlow/wells/WellFields.hpp index 5f77922f4ee..3bb07399534 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/wells/WellFields.hpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/wells/WellFields.hpp @@ -34,6 +34,21 @@ namespace fields namespace well { +DECLARE_FIELD( connectionRate, + "wellElementConnectionRate", + array1d< real64 >, + 0, + LEVEL_0, + WRITE_AND_READ, + "Connection rate" ); + +DECLARE_FIELD( connectionRate_n, + "wellElementConnectionRate_n", + array1d< real64 >, + 0, + NOPLOT, + WRITE_AND_READ, + "Connection rate at the previous converged time step" ); DECLARE_FIELD( energyPerforationFlux, "energyPerforationFlux", array1d< real64 >, @@ -50,6 +65,61 @@ DECLARE_FIELD( dEnergyPerforationFlux, NO_WRITE, "Derivative of energy perforation flux with respect to pressure temperature and global component density (compositional only)" ); +DECLARE_FIELD( pressure, + "pressure", + array1d< real64 >, + 0, + LEVEL_0, + WRITE_AND_READ, + "Pressure" ); + +DECLARE_FIELD( pressure_n, + "pressure_n", + array1d< real64 >, + 0, + NOPLOT, + WRITE_AND_READ, + "Pressure at the previous converged time step" ); + +DECLARE_FIELD( temperature, + "temperature", + array1d< real64 >, + 0, + LEVEL_0, + WRITE_AND_READ, + "Temperature" ); + +DECLARE_FIELD( temperature_n, + "temperature_n", + array1d< real64 >, + 0, + NOPLOT, + WRITE_AND_READ, + "Temperature at the previous converged time step" ); + +DECLARE_FIELD( gravityCoefficient, + "gravityCoefficient", + array1d< real64 >, + 0, + NOPLOT, + WRITE_AND_READ, + "Gravity coefficient (dot product of gravity acceleration by gravity vector)" ); + +DECLARE_FIELD( pressureScalingFactor, + "pressureScalingFactor", + array1d< real64 >, + 1, + NOPLOT, + NO_WRITE, + "Scaling factors for pressure" ); + +DECLARE_FIELD( temperatureScalingFactor, + "temperatureScalingFactor", + array1d< real64 >, + 1, + NOPLOT, + NO_WRITE, + "Scaling factors for temperature" ); } diff --git a/src/coreComponents/physicsSolvers/fluidFlow/wells/WellInjectionConstraint.cpp b/src/coreComponents/physicsSolvers/fluidFlow/wells/WellInjectionConstraint.cpp new file mode 100644 index 00000000000..425a10147c8 --- /dev/null +++ b/src/coreComponents/physicsSolvers/fluidFlow/wells/WellInjectionConstraint.cpp @@ -0,0 +1,107 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2016-2024 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2024 TotalEnergies + * Copyright (c) 2018-2024 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2023-2024 Chevron + * Copyright (c) 2019- GEOS/GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +/* + * @file WellInjectionConstraint.cpp + */ + +#include "LogLevelsInfo.hpp" +#include "WellInjectionConstraint.hpp" +#include "WellConstants.hpp" +#include "dataRepository/InputFlags.hpp" +#include "functions/FunctionManager.hpp" + +#include "WellMassRateConstraint.hpp" +#include "WellPhaseVolumeRateConstraint.hpp" +#include "WellVolumeRateConstraint.hpp" + +namespace geos +{ +using namespace dataRepository; + +template< typename ConstraintRateType > +InjectionConstraint< ConstraintRateType >::InjectionConstraint( string const & name, Group * const parent ) + : ConstraintRateType( name, parent ) +{ + // set rate sign for injectors (base class member) + this->m_rateSign = 1.0; + classtype::registerWrapper( injectionStreamKey::injectionStreamString(), &m_injectionStream ). + setDefaultValue( -1 ). + setSizedFromParent( 0 ). + setInputFlag( InputFlags::OPTIONAL ). + setDescription( "Global component densities of the injection stream [moles/m^3 or kg/m^3]" ); + + InjectionConstraint< ConstraintRateType >::registerWrapper( injectionStreamKey::injectionTemperatureString(), &m_injectionTemperature ). + setDefaultValue( -1 ). + setInputFlag( InputFlags::OPTIONAL ). + setDescription( "Temperature of the injection stream [K]" ); +} +template< typename ConstraintRateType > +InjectionConstraint< ConstraintRateType >::~InjectionConstraint() +{} +template< typename ConstraintRateType > +void InjectionConstraint< ConstraintRateType >::postInputInitialization() +{ + // Validate value and table options + ConstraintRateType::postInputInitialization(); + +// Validate the injection stream and temperature + validateInjectionStream( ); + +} +template< typename ConstraintRateType > +void InjectionConstraint< ConstraintRateType >::validateInjectionStream( ) +{ + GEOS_THROW_IF( (m_injectionStream.empty() && m_injectionTemperature >= 0) || + (!m_injectionStream.empty() && m_injectionTemperature < 0), + this->getName() << " " << this->getDataContext() << ": Both " + << injectionStreamKey::injectionStreamString() << " and " << injectionStreamKey::injectionTemperatureString() + << " must be specified for multiphase simulations", + InputError ); + + if( !m_injectionStream.empty()) + { + real64 sum = 0.0; + for( localIndex ic = 0; ic < m_injectionStream.size(); ++ic ) + { + GEOS_ERROR_IF( m_injectionStream[ic] < 0.0 || m_injectionStream[ic] > 1.0, + classtype::getWrapperDataContext( injectionStreamKey::injectionStreamString() ) << ": Invalid injection stream" ); + sum += m_injectionStream[ic]; + } + GEOS_THROW_IF( LvArray::math::abs( 1.0 - sum ) > std::numeric_limits< real64 >::epsilon(), + classtype::getWrapperDataContext( injectionStreamKey::injectionStreamString() ) << ": Invalid injection stream", + InputError ); + } +} + +// Register concrete wrapper constraint types and instantiate templates. +//template class InjectionConstraint< LiquidRateConstraint >; +//using InjectionLiquidRateConstraint = InjectionConstraint< LiquidRateConstraint >; +//REGISTER_CATALOG_ENTRY( WellConstraintBase, InjectionLiquidRateConstraint, string const &, Group * const ) + +template class InjectionConstraint< MassRateConstraint >; +using InjectionMassRateConstraint = InjectionConstraint< MassRateConstraint >; +REGISTER_CATALOG_ENTRY( WellConstraintBase, InjectionMassRateConstraint, string const &, Group * const ) + +template class InjectionConstraint< PhaseVolumeRateConstraint >; +using InjectionPhaseVolumeRateConstraint = InjectionConstraint< PhaseVolumeRateConstraint >; +REGISTER_CATALOG_ENTRY( WellConstraintBase, InjectionPhaseVolumeRateConstraint, string const &, Group * const ) + +template class InjectionConstraint< VolumeRateConstraint >; +using InjectionVolumeRateConstraint = InjectionConstraint< VolumeRateConstraint >; +REGISTER_CATALOG_ENTRY( WellConstraintBase, InjectionVolumeRateConstraint, string const &, Group * const ) + + +} diff --git a/src/coreComponents/physicsSolvers/fluidFlow/wells/WellInjectionConstraint.hpp b/src/coreComponents/physicsSolvers/fluidFlow/wells/WellInjectionConstraint.hpp new file mode 100644 index 00000000000..7dfd4d21e53 --- /dev/null +++ b/src/coreComponents/physicsSolvers/fluidFlow/wells/WellInjectionConstraint.hpp @@ -0,0 +1,151 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2016-2024 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2024 TotalEnergies + * Copyright (c) 2018-2024 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2023-2024 Chevron + * Copyright (c) 2019- GEOS/GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +/* + * @file WellInjectionConstraint.hpp + */ + + +#ifndef GEOS_PHYSICSSOLVERS_FLUIDFLOW_WELLS_WELLINJECTIONCONSTRAINT_HPP +#define GEOS_PHYSICSSOLVERS_FLUIDFLOW_WELLS_WELLINJECTIONCONSTRAINT_HPP + +#include "common/format/EnumStrings.hpp" +#include "dataRepository/Group.hpp" +#include "functions/TableFunction.hpp" + +namespace geos +{ + +using namespace dataRepository; +/** + * @class InjectionConstraint + * @brief This class describes constraint used to control a injection well. + */ + +template< typename ConstraintType > +class InjectionConstraint : public ConstraintType +{ +public: + typedef InjectionConstraint< ConstraintType > classtype; + /** + * @name Constructor / Destructor + */ + ///@{ + + /** + * @brief Constructor for WellControls Objects. + * @param[in] name the name of this instantiation of WellControls in the repository + * @param[in] parent the parent group of this instantiation of WellControls + */ + explicit InjectionConstraint( string const & name, dataRepository::Group * const parent ); + + /** + * @brief Default destructor. + */ + ~InjectionConstraint() override; + + /** + * @brief Deleted default constructor. + */ + InjectionConstraint() = delete; + + /** + * @brief Deleted copy constructor. + */ + InjectionConstraint( InjectionConstraint const & ) = delete; + + /** + * @brief Deleted move constructor. + */ + InjectionConstraint( InjectionConstraint && ) = delete; + + /** + * @brief Deleted assignment operator. + * @return a reference to a constraint object + */ + InjectionConstraint & operator=( InjectionConstraint const & ) = delete; + + /** + * @brief Deleted move operator. + * @return a reference to a constraint object + */ + InjectionConstraint & operator=( InjectionConstraint && ) = delete; + + ///@} + + /** + * @brief name of the node manager in the object catalog + * @return string that contains the catalog name to generate a new Constraint object through the object catalog. + */ + static string catalogName() + { + return "Injection"+ConstraintType::catalogName(); + } + + virtual string getCatalogName() const override { return catalogName(); } + + struct injectionStreamKey + { + /// String key for the well injection stream + static constexpr char const * injectionStreamString() { return "injectionStream"; } + /// String key for the well injection temperature + static constexpr char const * injectionTemperatureString() { return "injectionTemperature"; } + }; + + /** + * @brief Const accessor for the composition of the injection stream + * @return a global component fraction vector + */ + arrayView1d< real64 const > getInjectionStream() const { return m_injectionStream; } + + /** + * @brief Const accessor for the temperature of the injection stream + * @return the temperature of the injection stream + */ + real64 getInjectionTemperature() const { return m_injectionTemperature; } + + /** + * @brief Set composition of the injection stream + * @param[in] injectionStream a global component fraction vector + */ + void setInjectionStream( arrayView1d< real64 const > const & injectionStream ) { m_injectionStream = injectionStream; } + + /** + * @brief Set temperature of the injection stream + * @param[in] temperature the temperature of the injection stream + */ + void setInjectionTemperature( real64 temperature ) { m_injectionTemperature = temperature; } + +protected: + + virtual void postInputInitialization() override; + static bool isViolated( const real64 & currentValue, const real64 & constraintValue ) + { return currentValue > constraintValue; } + + void validateInjectionStream(); +private: + + /// Vector with global component fractions at the injector + array1d< real64 > m_injectionStream; + + /// Temperature at the injector + real64 m_injectionTemperature; + +}; + + +} //namespace geos + +#endif //GEOS_PHYSICSSOLVERS_FLUIDFLOW_WELLS_WELLINJECTIONCONSTRAINT_HPP diff --git a/src/coreComponents/physicsSolvers/fluidFlow/wells/WellLiquidRateConstraint.cpp b/src/coreComponents/physicsSolvers/fluidFlow/wells/WellLiquidRateConstraint.cpp new file mode 100644 index 00000000000..3c84f70f1a7 --- /dev/null +++ b/src/coreComponents/physicsSolvers/fluidFlow/wells/WellLiquidRateConstraint.cpp @@ -0,0 +1,76 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2016-2024 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2024 TotalEnergies + * Copyright (c) 2018-2024 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2023-2024 Chevron + * Copyright (c) 2019- GEOS/GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +/* + * @file WellLiquidRateConstraint.cpp + */ + +#include "LogLevelsInfo.hpp" +#include "WellLiquidRateConstraint.hpp" +#include "WellConstants.hpp" +#include "dataRepository/InputFlags.hpp" +#include "functions/FunctionManager.hpp" + +namespace geos +{ + +using namespace dataRepository; + + +LiquidRateConstraint::LiquidRateConstraint( string const & name, Group * const parent ) + : WellConstraintBase( name, parent ) +{ + this->registerWrapper( viewKeyStruct::liquidRateString(), &this->m_constraintValue ). + setDefaultValue( 0.0 ). + setInputFlag( InputFlags::OPTIONAL ). + setRestartFlags( RestartFlags::WRITE_AND_READ ). + setDescription( "Phase rate, (if useSurfaceCondSitions: [surface m^3/s]; else [reservoir m^3/s]) " ); + + this->registerWrapper( viewKeyStruct::phaseNamesString(), &m_phaseNames ). + setRTTypeName( rtTypes::CustomTypes::groupNameRefArray ). + setInputFlag( InputFlags::REQUIRED ). + setDescription( "List of fluid phase names defining the liquid" ); +} + +LiquidRateConstraint::~LiquidRateConstraint() +{} + +void LiquidRateConstraint::postInputInitialization() +{ + // Validate table options + WellConstraintBase::postInputInitialization(); + + // check constraint value + GEOS_THROW_IF( m_constraintValue < 0, + getWrapperDataContext( viewKeyStruct::liquidRateString() ) << ": Target value is negative", + InputError ); + + GEOS_THROW_IF ((m_constraintValue <= 0.0 && m_constraintScheduleTableName.empty()), + getName() << " " << getDataContext() << ": You need to specify a liquid rate constraint. \n" << + "The rate constraint can be specified using " << + "either " << viewKeyStruct::liquidRateString() << + " or " << WellConstraintBase::viewKeyStruct::constraintScheduleTableNameString(), + InputError ); +} + + +bool LiquidRateConstraint::checkViolation( WellConstraintBase const & currentConstraint, real64 const & currentTime ) const +{ + real64 const currentValue = currentConstraint.liquidRate(); + real64 const constraintValue = this->getConstraintValue( currentTime ); + return ( LvArray::math::abs( currentValue ) <= LvArray::math::abs( constraintValue ) ); +} + +} //namespace geos diff --git a/src/coreComponents/physicsSolvers/fluidFlow/wells/WellLiquidRateConstraint.hpp b/src/coreComponents/physicsSolvers/fluidFlow/wells/WellLiquidRateConstraint.hpp new file mode 100644 index 00000000000..10ba7bffdf2 --- /dev/null +++ b/src/coreComponents/physicsSolvers/fluidFlow/wells/WellLiquidRateConstraint.hpp @@ -0,0 +1,175 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2016-2024 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2024 TotalEnergies + * Copyright (c) 2018-2024 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2023-2024 Chevron + * Copyright (c) 2019- GEOS/GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +/* + * @file WellLiquidRateConstraint.hpp + */ + + +#ifndef GEOS_PHYSICSSOLVERS_FLUIDFLOW_WELLS_WELLLIQUIDRATECONSTRAINT_HPP +#define GEOS_PHYSICSSOLVERS_FLUIDFLOW_WELLS_WELLLIQUIDRATECONSTRAINT_HPP + +#include "common/format/EnumStrings.hpp" + +#include "functions/TableFunction.hpp" +#include "WellConstraintsBase.hpp" + +namespace geos +{ + + +/** + * @class LiquidRateConstraint + * @brief This class describes a Liquid rate constraint used to control of type WellConstraintType + */ + + +class LiquidRateConstraint : public WellConstraintBase +{ +public: + + + /** + * @name Constructor / Destructor + */ + ///@{ + + /** + * @brief Constructor for WellControls Objects. + * @param[in] name the name of this instantiation of WellControls in the repository + * @param[in] parent the parent group of this instantiation of WellControls + */ + explicit LiquidRateConstraint( string const & name, dataRepository::Group * const parent ); + + + /** + * @brief Default destructor. + */ + ~LiquidRateConstraint() override; + + /** + * @brief Deleted default constructor. + */ + LiquidRateConstraint() = delete; + + /** + * @brief Deleted copy constructor. + */ + LiquidRateConstraint( LiquidRateConstraint const & ) = delete; + + /** + * @brief Deleted move constructor. + */ + LiquidRateConstraint( LiquidRateConstraint && ) = delete; + + /** + * @brief Deleted assignment operator. + * @return a reference to a constraint object + */ + LiquidRateConstraint & operator=( LiquidRateConstraint const & ) = delete; + + /** + * @brief Deleted move operator. + * @return a reference to a constraint object + */ + LiquidRateConstraint & operator=( LiquidRateConstraint && ) = delete; + + /** + * @brief name of the node manager in the object catalog + * @return string that contains the catalog name to generate a new Constraint object through the object catalog. + */ + static string catalogName() + { + return "LiquidRateConstraint"; + } + ///@} + + /** + * @name Getters / Setters + */ + ///@{ + /** + * @brief Get the target phase name + * @return the target phase name + */ + const string_array & getPhaseNames() const { return m_phaseNames; } + + /** + * @brief Set phases associated with liquid constraint + * @param array of phase names + */ + void setPhaseNames( const string_array & phaseNames ) { m_phaseNames=phaseNames; } + + /** + * @brief Get the phase indices + * @return array of phase indices + */ + const array1d< integer > & getPhaseIndices() const { return m_phaseIndices; } + + ///@} + /** + * @brief Struct to serve as a container for variable strings and keys. + * @struct viewKeyStruct + */ + struct viewKeyStruct + { + /// String key for the liquid rate + static constexpr char const * liquidRateString() { return "liquidRate"; } + /// String key for the phases names + static constexpr char const * phaseNamesString() { return "phaseNames"; } + }; + + // Temp interface - tjb + virtual ConstraintTypeId getControl() const override { return ConstraintTypeId::LIQUIDRATE; }; + + virtual bool checkViolation( WellConstraintBase const & currentConstraint, real64 const & currentTime ) const override; + + /** + * @brief Validate Liquid type is consistent with fluidmodel + */ + template< typename T > + void validateLiquidType( T const & fluidModel ); + +protected: + + virtual void postInputInitialization() override; + +protected: + + /// Name of the targeted phase + string_array m_phaseNames; + ///Indices of the phases defining the fluid + array1d< integer > m_phaseIndices; + +}; + +template< typename T > +void LiquidRateConstraint::validateLiquidType( T const & fluidModel ) +{ + m_phaseIndices.resize( m_phaseNames.size()); + for( size_t ip =0; ipgetWrapper< string >( viewKeyStruct::discretizationString() ). + setInputFlag( InputFlags::FALSE ); + + registerWrapper( viewKeyStruct::isThermalString(), &m_isThermal ). + setApplyDefaultValue( 0 ). + setInputFlag( InputFlags::OPTIONAL ). + setDescription( "Flag indicating whether the problem is thermal or not." ); + + + this->registerWrapper( viewKeyStruct::useMassFlagString(), &m_useMass ). + setApplyDefaultValue( 0 ). + setInputFlag( InputFlags::OPTIONAL ). + setDescription( "Use mass formulation instead of molar" ); + + this->registerWrapper( viewKeyStruct::useTotalMassEquationString(), &m_useTotalMassEquation ). + setApplyDefaultValue( 1 ). + setInputFlag( InputFlags::OPTIONAL ). + setDescription( "Use total mass equation" ); + + this->registerWrapper( viewKeyStruct::allowLocalCompDensChoppingString(), &m_allowCompDensChopping ). + setSizedFromParent( 0 ). + setInputFlag( InputFlags::OPTIONAL ). + setApplyDefaultValue( 1 ). + setDescription( "Flag indicating whether local (cell-wise) chopping of negative compositions is allowed" ); + + this->registerWrapper( viewKeyStruct::timeStepFromTablesFlagString(), &m_timeStepFromTables ). + setApplyDefaultValue( 0 ). + setInputFlag( dataRepository::InputFlags::OPTIONAL ). + setDescription ( "Choose time step to honor rates/bhp tables time intervals" ); + +} +Group * WellManager::createChild( string const & childKey, string const & childName ) +{ + static std::set< string > const childTypes = { + keys::compositionalMultiphaseWell, + keys::singlePhaseWell, + PhysicsSolverBase::groupKeyStruct::linearSolverParametersString(), + PhysicsSolverBase::groupKeyStruct::nonlinearSolverParametersString(), + }; + GEOS_ERROR_IF( childTypes.count( childKey ) == 0, + CatalogInterface::unknownTypeError( childKey, getDataContext(), childTypes ), + getDataContext() ); + if( childKey == keys::compositionalMultiphaseWell ) + { + setCompositional( true ); + return ®isterGroup< CompositionalMultiphaseWell >( childName ); + } + else if( childKey == keys::singlePhaseWell ) + { + setCompositional( false ); + return ®isterGroup< SinglePhaseWell >( childName ); + } + else + { + PhysicsSolverBase::createChild( childKey, childName ); + return nullptr; + } +} + +void WellManager::expandObjectCatalogs() +{ + createChild( keys::compositionalMultiphaseWell, keys::compositionalMultiphaseWell ); + createChild( keys::singlePhaseWell, keys::singlePhaseWell ); +} + +void WellManager::registerDataOnMesh( Group & meshBodies ) +{ + + //std::string const & flowSolverName = getParent().getName();//getGroup< CompositionalMultiphaseBase >().getName(); + // CompositionalMultiphaseBase const & flowSolver = getParent().getGroup< CompositionalMultiphaseBase >( getFlowSolverName() ); + + forDiscretizationOnMeshTargets( meshBodies, [&] ( string const &, + MeshLevel & mesh, + string_array const & regionNames ) + { + + ElementRegionManager & elemManager = mesh.getElemManager(); + + elemManager.forElementSubRegions< WellElementSubRegion >( regionNames, + [&]( localIndex const, + WellElementSubRegion & subRegion ) + { + + WellControls & well = getWellControls( subRegion ); + //well.setFlowSolverName( flowSolver.getName() ); + well.setThermal( isThermal() ); + well.setUseMass( m_useMass ); + well.registerWellDataOnMesh( subRegion ); + m_numFluidPhases = well.numFluidPhases(); + m_numFluidComponents = well.numFluidComponents(); + + } ); + } ); + // 1. Set key dimensions of the problem + // Empty check needed to avoid errors when running in schema generation mode. + + // 1 pressure + NC compositions + 1 connectionRate + temp if thermal + m_numDofPerWellElement = isThermal() ? m_numFluidComponents + 3 : m_numFluidComponents + 2; + // 1 pressure + NC compositions + temp if thermal + m_numDofPerResElement = isThermal() ? m_numFluidComponents + 2 : m_numFluidComponents + 1; + +} + +WellControls & WellManager::getWell( WellElementSubRegion const & subRegion ) +{ + return this->getGroup< WellControls >( subRegion.getWellControlsName()); +} +WellControls & WellManager::getWell( std::string const & wellControlsName ) +{ + return this->getGroup< WellControls >( wellControlsName ); +} + +WellControls const & WellManager::getWell( std::string const & wellControlsName ) const +{ + return this->getGroup< WellControls >( wellControlsName ); +} +void WellManager::implicitStepSetup( real64 const & time_n, + real64 const & dt, + DomainPartition & domain ) +{ + + forDiscretizationOnMeshTargets ( domain.getMeshBodies(), [&] ( string const & meshBodyName, + MeshLevel & mesh, + string_array const & regionNames ) + { + + ElementRegionManager & elemManager = mesh.getElemManager(); + + elemManager.forElementRegions< WellElementRegion >( regionNames, + [&]( localIndex const, + WellElementRegion & region ) + { + // TODO expose a getWellControlsName() on WellElementRegion because Wrapper look-up is not useful here. + WellControls & well = getWell( + region.getReference< string >( WellElementRegion::viewKeyStruct::wellControlsString() ) ); + if( well.estimateSolution() ) + { + well.setupWellDofs( domain, region, meshBodyName, mesh ); + } + + } ) + ; + } ); + + forDiscretizationOnMeshTargets ( domain.getMeshBodies(), [&] ( string const & meshBodyName, + MeshLevel & mesh, + string_array const & regionNames ) + { + + ElementRegionManager & elemManager = mesh.getElemManager(); + + elemManager.forElementSubRegions< WellElementSubRegion >( regionNames, + [&]( localIndex const, + WellElementSubRegion & subRegion ) + { + WellControls & well = getWell( subRegion ); + well.implicitStepSetup( time_n, dt, domain, meshBodyName, elemManager, subRegion ); + } ); + } ); +} +real64 +WellManager::setNextDt( real64 const & currentTime, const real64 & currentDt, geos::DomainPartition & domain ) +{ + + real64 nextDt = PhysicsSolverBase::setNextDt( currentTime, currentDt, domain ); + + if( m_timeStepFromTables ) + { + real64 nextDt_orig = nextDt; + real64 nextDtLocal = nextDt; + forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const &, + MeshLevel & mesh, + string_array const & regionNames ) + { + mesh.getElemManager().forElementSubRegions< WellElementSubRegion >( regionNames, [&]( localIndex const, + WellElementSubRegion & subRegion ) + { + + WellControls & wellControls = getWellControls( subRegion ); + real64 nextDtWell = wellControls.setNextDt( currentTime, nextDt, subRegion ); + if( nextDtWell < nextDtLocal ) + { + nextDtLocal = nextDtWell; + } + } ); + } ); + // get the minimum across all ranks + nextDt = MpiWrapper::min< real64 >( nextDtLocal ); + if( getLogLevel() > 0 && nextDt < nextDt_orig ) + GEOS_LOG_RANK_0( GEOS_FMT( "{}: next time step based on tables coordinates = {}", getName(), nextDt )); + } + + return nextDt; +} +localIndex WellManager::numDofPerWellElement() const +{ + return m_numDofPerWellElement; +} + +localIndex WellManager::numDofPerResElement() const +{ + return m_numDofPerResElement; +} +integer WellManager::isThermal() const +{ + return m_isThermal; +} + +string WellManager::wellElementDofName() const +{ + return viewKeyStruct::dofFieldString(); +} + +string WellManager::resElementDofName() const +{ + if( isCompositional() ) + return CompositionalMultiphaseBase::viewKeyStruct::elemDofFieldString(); + else + return SinglePhaseBase::viewKeyStruct::elemDofFieldString(); +} + +localIndex WellManager::numFluidComponents() const +{ + return m_numFluidComponents; +} + +localIndex WellManager::numFluidPhases() const +{ + return m_numFluidPhases; +} +WellControls & WellManager::getWellControls( WellElementSubRegion const & subRegion ) +{ + return this->getGroup< WellControls >( subRegion.getWellControlsName()); +} + +WellControls const & WellManager::getWellControls( WellElementSubRegion const & subRegion ) const +{ + return this->getGroup< WellControls >( subRegion.getWellControlsName()); +} + +CompositionalMultiphaseWell & WellManager::getCompositionalMultiphaseWell( WellElementSubRegion const & subRegion ) +{ + return this->getGroup< CompositionalMultiphaseWell >( subRegion.getWellControlsName()); +} + +CompositionalMultiphaseWell const & WellManager::getCompositionalMultiphaseWell( WellElementSubRegion const & subRegion ) const +{ + return this->getGroup< CompositionalMultiphaseWell >( subRegion.getWellControlsName()); +} +void WellManager::initializePostSubGroups() +{ + GEOS_MARK_FUNCTION; + // Validate constitutive models + if( isCompositional() ) + { + DomainPartition & domain = this->getGroupByPath< DomainPartition >( "/Problem/domain" ); + constitutive::ConstitutiveManager const & cm = domain.getConstitutiveManager(); + CompositionalMultiphaseBase const & flowSolver = getParent().getGroup< CompositionalMultiphaseBase >( getFlowSolverName() ); + string const referenceFluidName = flowSolver.referenceFluidModelName(); + constitutive::MultiFluidBase const & referenceFluid = cm.getConstitutiveRelation< constitutive::MultiFluidBase >( referenceFluidName ); + + forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const &, + MeshLevel const & mesh, + string_array const & regionNames ) + { + + mesh.getElemManager().forElementSubRegions< WellElementSubRegion >( regionNames, [&]( localIndex const, + WellElementSubRegion const & subRegion ) + { + string const & fluidName = subRegion.getReference< string >( CompositionalMultiphaseWell::viewKeyStruct::fluidNamesString() ); + constitutive::MultiFluidBase const & fluid = getConstitutiveModel< constitutive::MultiFluidBase >( subRegion, fluidName ); + CompositionalMultiphaseWell * wellControls = dynamic_cast< CompositionalMultiphaseWell * >(&getWellControls ( subRegion )); + wellControls->validateFluidModel( fluid, referenceFluid ); + } ); + + } ); + } + else + { + // Single phase validation can be added here in the future + } + +} + +void WellManager::setupDofs( DomainPartition const & domain, + DofManager & dofManager ) const +{ + map< std::pair< string, string >, string_array > meshTargets; + forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const & meshBodyName, + MeshLevel const & meshLevel, + string_array const & regionNames ) + { + string_array regions; + ElementRegionManager const & elementRegionManager = meshLevel.getElemManager(); + elementRegionManager.forElementRegions< WellElementRegion >( regionNames, + [&]( localIndex const, + WellElementRegion const & region ) + { + regions.emplace_back( region.getName() ); + } ); + auto const key = std::make_pair( meshBodyName, meshLevel.getName()); + meshTargets[key] = std::move( regions ); + } ); + + dofManager.addField( wellElementDofName(), + FieldLocation::Elem, + numDofPerWellElement(), + meshTargets ); + + dofManager.addCoupling( wellElementDofName(), + wellElementDofName(), + DofManager::Connector::Node ); +} + +void WellManager::assembleSystem( real64 const time, + real64 const dt, + DomainPartition & domain, + DofManager const & dofManager, + CRSMatrixView< real64, globalIndex const > const & localMatrix, + arrayView1d< real64 > const & localRhs ) +{ + + + // selects constraints one of 2 ways + // wellEstimator flag set to 0 => orginal logic rates are computed during update state and constraints are selected every newton + // iteration + // wellEstimator flag > 0 => well esitmator solved for each constraint and then selects the constraint + // => estimator solve only performed first "wellEstimator" iterations + NonlinearSolverParameters const & nonlinearParams = getNonlinearSolverParameters(); + IterationsStatistics const & iterationsStatistics = getIterationStats(); + //selectWellConstraint( time, dt, solverStatistics.m_numNewtonIterations, domain ); + + forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const & meshBodyName, + MeshLevel & meshLevel, + string_array const & regionNames ) + { + GEOS_UNUSED_VAR( meshBodyName ); + ElementRegionManager & elementRegionManager = meshLevel.getElemManager(); + elementRegionManager.forElementRegions< WellElementRegion >( regionNames, + [&]( localIndex const, + WellElementRegion & region ) + { + WellElementSubRegion & subRegion = region.getGroup( ElementRegionBase::viewKeyStruct::elementSubRegions() ) + .getGroup< WellElementSubRegion >( region.getSubRegionName() ); + WellControls & wellControls = getWellControls( subRegion ); + wellControls.selectWellConstraint( time, + dt, + iterationsStatistics.getNumTimeSteps(), + nonlinearParams.m_numNewtonIterations, + domain, + meshBodyName, + meshLevel, + elementRegionManager, + subRegion, + dofManager ); + + // assemble the accumulation term in the mass balance equations + wellControls.assembleWellAccumulationTerms( time, dt, subRegion, dofManager, localMatrix, localRhs ); + if( wellControls.isWellOpen() ) + { + // assemble the pressure relations between well elements + wellControls.assembleWellPressureRelations( time, dt, subRegion, dofManager, localMatrix, localRhs ); + // assemble well constraint terms + wellControls.assembleWellConstraintTerms( time, dt, subRegion, dofManager, localMatrix.toViewConstSizes(), localRhs ); + // compute the perforation rates (later assembled by the coupled solver) + wellControls.computeWellPerforationRates( time, dt, elementRegionManager, subRegion ); + // assemble the flux terms in the mass balance equations + wellControls.assembleWellFluxTerms( time, dt, subRegion, dofManager, localMatrix, localRhs ); + } + } ); + } ); + +} + + +void WellManager::resetStateToBeginningOfStep( DomainPartition & domain ) +{ + forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&] ( string const & meshBodyName, + MeshLevel & mesh, + string_array const & regionNames ) + { + + ElementRegionManager & elemManager = mesh.getElemManager(); + + elemManager.forElementSubRegions< WellElementSubRegion >( regionNames, + [&]( localIndex const, + WellElementSubRegion & subRegion ) + { + WellControls & wellControls = getWellControls( subRegion ); + wellControls.resetStateToBeginningOfStep( domain, meshBodyName, elemManager, subRegion ); + + + } ); + } ); +} + +void WellManager::implicitStepComplete( real64 const & time, + real64 const & dt, + DomainPartition & domain ) +{ + forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&] ( string const &, + MeshLevel & mesh, + string_array const & regionNames ) + { + + ElementRegionManager & elemManager = mesh.getElemManager(); + + elemManager.forElementSubRegions< WellElementSubRegion >( regionNames, + [&]( localIndex const, + WellElementSubRegion & subRegion ) + { + WellControls & wellControls = getWellControls( subRegion ); + wellControls.implicitStepComplete( time, dt, subRegion ); + } ); + } ); +} + +void WellManager::postRestartInitialization() +{} +void WellManager::initializePostInitialConditionsPreSubGroups() +{ + PhysicsSolverBase::initializePostInitialConditionsPreSubGroups(); + DomainPartition & domain = this->getGroupByPath< DomainPartition >( "/Problem/domain" ); + forDiscretizationOnMeshTargets ( domain.getMeshBodies(), [&] ( string const &, + MeshLevel & mesh, + string_array const & regionNames ) + { + + // loop over the wells + mesh.getElemManager().forElementSubRegions< WellElementSubRegion >( regionNames, [&]( localIndex const, + WellElementSubRegion & subRegion ) + { + // reconstruct local connectivity needed for flux calculations + subRegion.reconstructLocalConnectivity(); + WellControls & wellControls = getWellControls( subRegion ); + wellControls.initializeWellPostInitialConditionsPreSubGroups( subRegion ); + + + } ); + } ); +} +void WellManager::setKeepVariablesConstantDuringInitStep( bool const keepVariablesConstantDuringInitStep ) +{ + DomainPartition & domain = this->getGroupByPath< DomainPartition >( "/Problem/domain" ); + forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&] ( string const &, + MeshLevel & mesh, + string_array const & regionNames ) + { + // loop over the wells + mesh.getElemManager().forElementSubRegions< WellElementSubRegion >( regionNames, [&]( localIndex const, + WellElementSubRegion & subRegion ) + + { + WellControls & wellControls = getWellControls( subRegion ); + wellControls.setKeepVariablesConstantDuringInitStep( keepVariablesConstantDuringInitStep ); + + } ); + } ); +} +void WellManager::updateState( DomainPartition & domain ) +{ + GEOS_MARK_FUNCTION; + + real64 maxPhaseVolFrac = 0.0; + forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const & meshBodyName, + MeshLevel & mesh, + string_array const & regionNames ) + { + ElementRegionManager & elemManager = mesh.getElemManager(); + elemManager.forElementSubRegions< WellElementSubRegion >( regionNames, [&]( localIndex const, + WellElementSubRegion & subRegion ) + { + WellControls & wellControls = getWellControls( subRegion ); + if( wellControls.getWellState()) + { + + real64 const maxRegionPhaseVolFrac = wellControls.updateWellState( domain.getMeshBody( meshBodyName ), elemManager, subRegion ); + + maxPhaseVolFrac = LvArray::math::max( maxRegionPhaseVolFrac, maxPhaseVolFrac ); + } + } ); + } ); + maxPhaseVolFrac = MpiWrapper::max( maxPhaseVolFrac ); + + GEOS_LOG_LEVEL_RANK_0( logInfo::Solution, + GEOS_FMT( " {}: Max well phase volume fraction change = {}", + getName(), fmt::format( "{:.{}f}", maxPhaseVolFrac, 4 ) ) ); + +} + +real64 +WellManager::calculateResidualNorm( real64 const & time_n, + real64 const & dt, + DomainPartition const & domain, + DofManager const & dofManager, + arrayView1d< real64 const > const & localRhs ) +{ + GEOS_MARK_FUNCTION; + + integer numNorm = 1; // mass balance + array1d< real64 > localResidualNorm, wellResidualNorm; + array1d< real64 > localResidualNormalizer; + + if( isThermal() ) + { + numNorm = 2; // mass balance and energy balance + } + localResidualNorm.resize( numNorm ); + + localResidualNormalizer.resize( numNorm ); + + string const wellDofKey = dofManager.getKey( wellElementDofName() ); + + forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&] ( string const &, + MeshLevel const & mesh, + string_array const & regionNames ) + { + + + ElementRegionManager const & elemManager = mesh.getElemManager(); + + elemManager.forElementSubRegions< WellElementSubRegion >( regionNames, + [&]( localIndex const, + WellElementSubRegion const & subRegion ) + { + + WellControls & wellControls = getWellControls( subRegion ); + + // step 1: compute the norm in the subRegion + if( wellControls.isWellOpen( ) ) + { + wellResidualNorm = wellControls.calculateLocalWellResidualNorm( time_n, + dt, + m_nonlinearSolverParameters, + subRegion, + dofManager, + localRhs ); + for( integer i=0; i localResidualNorm[i] ) + { + localResidualNorm[i] = wellResidualNorm[i]; + } + } + } + else + { + for( integer i=0; i const & localSolution ) +{ + GEOS_MARK_FUNCTION; + + string const wellDofKey = dofManager.getKey( wellElementDofName() ); + + real64 scalingFactor = 1.0; + real64 localScalingFactor = 1.0; + if( isCompositional() ) + { + + + real64 maxDeltaPres = 0.0, maxDeltaCompDens = 0.0, maxDeltaTemp = 0.0; + real64 minPresScalingFactor = 1.0, minCompDensScalingFactor = 1.0, minTempScalingFactor = 1.0; + real64 localMaxDeltaPres = 0.0, localMaxDeltaCompDens = 0.0, localMaxDeltaTemp = 0.0; + real64 localMinPresScalingFactor = 1.0, localMinCompDensScalingFactor = 1.0, localMinTempScalingFactor = 1.0; + + forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const &, + MeshLevel & mesh, + string_array const & regionNames ) + { + + ElementRegionManager & elemManager = mesh.getElemManager(); + elemManager.forElementSubRegions< WellElementSubRegion >( regionNames, + [&]( localIndex const, + WellElementSubRegion & subRegion ) + + { + CompositionalMultiphaseWell * wellControls = dynamic_cast< CompositionalMultiphaseWell * >(&getWellControls ( subRegion )); + localScalingFactor = wellControls->scalingForLocalSystemSolution( subRegion, + dofManager, + localMaxDeltaPres, + localMaxDeltaCompDens, + localMaxDeltaTemp, + localMinPresScalingFactor, + localMinCompDensScalingFactor, + localMinTempScalingFactor, + localSolution ); + maxDeltaPres = LvArray::math::max( localMaxDeltaPres, maxDeltaPres ); + maxDeltaCompDens = LvArray::math::max( localMaxDeltaCompDens, maxDeltaCompDens ); + maxDeltaTemp = LvArray::math::max( localMaxDeltaTemp, maxDeltaTemp ); + minPresScalingFactor = LvArray::math::min( localMinPresScalingFactor, minPresScalingFactor ); + minCompDensScalingFactor = LvArray::math::min( localMinCompDensScalingFactor, minCompDensScalingFactor ); + minTempScalingFactor = LvArray::math::min( localMinTempScalingFactor, minTempScalingFactor ); + scalingFactor = LvArray::math::min( localScalingFactor, scalingFactor ); + + } ); + } ); + + scalingFactor = MpiWrapper::min( scalingFactor ); + maxDeltaPres = MpiWrapper::max( maxDeltaPres ); + maxDeltaCompDens = MpiWrapper::max( maxDeltaCompDens ); + minPresScalingFactor = MpiWrapper::min( minPresScalingFactor ); + minCompDensScalingFactor = MpiWrapper::min( minCompDensScalingFactor ); + + string const massUnit = m_useMass ? "kg/m3" : "mol/m3"; + GEOS_LOG_LEVEL_RANK_0( logInfo::Solution, + GEOS_FMT( " {}: Max well pressure change: {} Pa (before scaling)", + getName(), GEOS_FMT( "{:.{}f}", maxDeltaPres, 3 ) ) ); + GEOS_LOG_LEVEL_RANK_0( logInfo::Solution, + GEOS_FMT( " {}: Max well component density change: {} {} (before scaling)", + getName(), GEOS_FMT( "{:.{}f}", maxDeltaCompDens, 3 ), massUnit ) ); + + if( m_isThermal ) + { + maxDeltaTemp = MpiWrapper::max( maxDeltaTemp ); + minTempScalingFactor = MpiWrapper::min( minTempScalingFactor ); + GEOS_LOG_LEVEL_RANK_0( logInfo::Solution, + GEOS_FMT( " {}: Max well temperature change: {} K (before scaling)", + getName(), GEOS_FMT( "{:.{}f}", maxDeltaTemp, 3 ) ) ); + } + + + GEOS_LOG_LEVEL_RANK_0( logInfo::Solution, + GEOS_FMT( " {}: Min well pressure scaling factor: {}", + getName(), minPresScalingFactor ) ); + GEOS_LOG_LEVEL_RANK_0( logInfo::Solution, + GEOS_FMT( " {}: Min well component density scaling factor: {}", + getName(), minCompDensScalingFactor ) ); + if( m_isThermal ) + { + GEOS_LOG_LEVEL_RANK_0( logInfo::Solution, + GEOS_FMT( " {}: Min well temperature scaling factor: {}", + getName(), minTempScalingFactor ) ); + } + + } + else + { + // Single phase well scaling- not implemented yet + scalingFactor=1.0; + } + return LvArray::math::max( scalingFactor, m_minScalingFactor ); + +} + +bool +WellManager::checkSystemSolution( DomainPartition & domain, + DofManager const & dofManager, + arrayView1d< real64 const > const & localSolution, + real64 const scalingFactor ) +{ + GEOS_MARK_FUNCTION; + + integer globalCheck = 1; + + real64 minPressure = 0.0, minDensity = 0.0, minTotalDensity = 0.0; + + bool const solutionLogActive = isLogLevelActive< logInfo::Solution >( getLogLevel() ); + bool const solutionDetailsLogActive = isLogLevelActive< logInfo::SolutionDetails >( getLogLevel() ); + ElementsReporterBuffer rankNegPressureIds{ solutionLogActive, solutionDetailsLogActive ? 16 : 0 }; + ElementsReporterBuffer rankNegDensityIds{ solutionLogActive, solutionDetailsLogActive ? 16 : 0 }; + // output only total density sum, not cell details + ElementsReporterBuffer rankTotalNegDensityIds{ solutionLogActive, 0 }; + + forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const &, + MeshLevel & mesh, + string_array const & regionNames ) + { + + ElementRegionManager & elemManager = mesh.getElemManager(); + elemManager.forElementSubRegions< WellElementSubRegion >( regionNames, + [&]( localIndex const, + WellElementSubRegion & subRegion ) + + { + WellControls & wellControls = getWellControls( subRegion ); + integer localCheck = wellControls.checkWellSystemSolution( subRegion, + dofManager, + localSolution, + scalingFactor, + minPressure, + minDensity, + minTotalDensity, + rankNegPressureIds, + rankNegDensityIds, + rankTotalNegDensityIds ); + globalCheck = std::min( localCheck, globalCheck ); + } ); + } ); + globalCheck = MpiWrapper::min( globalCheck ); + minPressure = MpiWrapper::min( minPressure ); + minDensity = MpiWrapper::min( minDensity ); + minTotalDensity = MpiWrapper::min( minTotalDensity ); + + units::Unit const massUnit = m_useMass ? units::Unit::Density : units::Unit::MolarDensity; + rankNegPressureIds.createOutput() + .outputTooLowValues( GEOS_FMT( " {}: ", getName() ), + "negative pressure", minPressure, units::Unit::Pressure ); + rankNegDensityIds.createOutput() + .outputTooLowValues( GEOS_FMT( " {}: ", getName() ), + "negative component density", minDensity, massUnit ); + rankTotalNegDensityIds.createOutput() + .outputTooLowValues( GEOS_FMT( " {}: ", getName() ), + "negative components total density", minTotalDensity, massUnit ); + + return globalCheck; +} + +void +WellManager::applySystemSolution( DofManager const & dofManager, + arrayView1d< real64 const > const & localSolution, + real64 const scalingFactor, + real64 const dt, + DomainPartition & domain ) +{ + + + DofManager::CompMask pressureMask( m_numDofPerWellElement, 0, 1 ); + + DofManager::CompMask connRateMask( m_numDofPerWellElement, numFluidComponents()+1, numFluidComponents()+2 ); + GEOS_UNUSED_VAR( dt ); + // update all the fields using the global damping coefficients + dofManager.addVectorToField( localSolution, + wellElementDofName(), + well::pressure::key(), + scalingFactor, + pressureMask ); + + dofManager.addVectorToField( localSolution, + wellElementDofName(), + well::connectionRate::key(), + scalingFactor, + connRateMask ); + if( isCompositional()) + { + DofManager::CompMask componentMask( m_numDofPerWellElement, 1, numFluidComponents()+1 ); + dofManager.addVectorToField( localSolution, + wellElementDofName(), + well::globalCompDensity::key(), + scalingFactor, + componentMask ); + + } + if( isThermal() ) + { + DofManager::CompMask temperatureMask( m_numDofPerWellElement, numFluidComponents()+2, numFluidComponents()+3 ); + + dofManager.addVectorToField( localSolution, + wellElementDofName(), + well::temperature::key(), + scalingFactor, + temperatureMask ); + + } + // if component density chopping is allowed, some component densities may be negative after the update + // these negative component densities are set to zero in this function + if( isCompositional() && m_allowCompDensChopping ) + { + chopNegativeDensities( domain ); + } + + forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const &, + MeshLevel & mesh, + string_array const & regionNames ) + { + stdVector< string > propNames; + propNames.emplace_back( well::pressure::key() ); + + propNames.emplace_back( well::connectionRate::key() ); + if( isCompositional()) + { + propNames.emplace_back( well::globalCompDensity::key() ); + } + if( isThermal() ) + { + propNames.emplace_back( well::temperature::key() ); + } + // synchronize + FieldIdentifiers fieldsToBeSync; + + fieldsToBeSync.addElementFields( propNames, + regionNames ); + + + CommunicationTools::getInstance().synchronizeFields( fieldsToBeSync, + mesh, + domain.getNeighbors(), + true ); + } ); + +} + +void WellManager::chopNegativeDensities( DomainPartition & domain ) +{ + integer const numComp = m_numFluidComponents; + + forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&] ( string const &, + MeshLevel & mesh, + string_array const & regionNames ) + { + + ElementRegionManager & elemManager = mesh.getElemManager(); + + elemManager.forElementSubRegions< WellElementSubRegion >( regionNames, + [&]( localIndex const, + WellElementSubRegion & subRegion ) + { + arrayView1d< integer const > const & wellElemGhostRank = subRegion.ghostRank(); + + arrayView2d< real64, compflow::USD_COMP > const & wellElemCompDens = + subRegion.getField< well::globalCompDensity >(); + + forAll< parallelDevicePolicy<> >( subRegion.size(), [=] GEOS_HOST_DEVICE ( localIndex const iwelem ) + { + if( wellElemGhostRank[iwelem] < 0 ) + { + for( integer ic = 0; ic < numComp; ++ic ) + { + // we allowed for some densities to be slightly negative in CheckSystemSolution + // if the new density is negative, chop back to zero + if( wellElemCompDens[iwelem][ic] < 0 ) + { + wellElemCompDens[iwelem][ic] = 0; + } + } + } + } ); + } ); + + } ); +} + +REGISTER_CATALOG_ENTRY( PhysicsSolverBase, WellManager, string const &, Group * const ) +} // namespace geos diff --git a/src/coreComponents/physicsSolvers/fluidFlow/wells/WellManager.hpp b/src/coreComponents/physicsSolvers/fluidFlow/wells/WellManager.hpp new file mode 100644 index 00000000000..27acb8eb5c9 --- /dev/null +++ b/src/coreComponents/physicsSolvers/fluidFlow/wells/WellManager.hpp @@ -0,0 +1,474 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2016-2024 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2024 TotalEnergies + * Copyright (c) 2018-2024 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2023-2024 Chevron + * Copyright (c) 2019- GEOS/GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +/** + * @file WellManager.hpp + */ + +#ifndef GEOS_PHYSICSSOLVERS_FLUIDFLOW_WELLS_WELL_MANAGER_HPP_ +#define GEOS_PHYSICSSOLVERS_FLUIDFLOW_WELLS_WELL_MANAGER_HPP_ + +#include "physicsSolvers/PhysicsSolverBase.hpp" +#include "physicsSolvers/fluidFlow/SinglePhaseBase.hpp" +#include "physicsSolvers/fluidFlow/CompositionalMultiphaseBase.hpp" +#include "physicsSolvers/fluidFlow/wells/CompositionalMultiphaseWell.hpp" +#include "physicsSolvers/fluidFlow/wells/SinglePhaseWell.hpp" +namespace geos +{ + +class DomainPartition; +class WellControls; +class WellElementSubRegion; + +namespace dataRepository +{ +namespace keys +{ +static constexpr auto compositionalMultiphaseWell = "CompositionalMultiphaseWell"; +static constexpr auto singlePhaseWell = "SinglePhaseWell"; +} +} +/** + * @class WellManager + * + * Base class for well solvers. + * Provides some common features + */ +class WellManager : public PhysicsSolverBase +{ +public: + + /// String used to form the solverName used to register single-physics solvers in CoupledSolver + static string coupledSolverAttributePrefix() { return "well"; } + + /** + * @brief main constructor for Group Objects + * @param name the name of this instantiation of Group in the repository + * @param parent the parent group of this instantiation of Group + */ + WellManager( const string & name, + Group * const parent ); + + /// default destructor + virtual ~WellManager() override = default; + + /// deleted default constructor + WellManager() = delete; + + /// deleted copy constructor + WellManager( WellManager const & ) = delete; + + /// default move constructor + WellManager( WellManager && ) = default; + + /// deleted assignment operator + WellManager & operator=( WellManager const & ) = delete; + + /// deleted move operator + WellManager & operator=( WellManager && ) = delete; + + virtual Group * createChild( string const & childKey, string const & childName ) override; + + /// Expand catalog for schema generation + virtual void expandObjectCatalogs() override; + + /** + * @brief setter for the name of the flow solver (needed to use the flow kernels like UpdateFluid) + * @param name the name of the flow solver + */ + void setFlowSolverName( string const & name ) { m_flowSolverName = name; } + + /** + * @brief setter for compositional flag + * @param compositional the compositional flag + */ + void setCompositional( bool const & isCompositional ) { m_isCompositional = isCompositional; } + + /** + * @brief getter for compositional flag + * @return the compositional flag + */ + bool isCompositional() const { return m_isCompositional; } + + + /** + * @brief getter for the name of the flow solver (used in UpdateState) + * @return a string containing the name of the flow solver + */ + string const & getFlowSolverName() const { return m_flowSolverName; } + + + /** + * @brief name of the node manager in the object catalog + * @return string that contains the catalog name to generate a new NodeManager object through the object catalog. + */ + static string catalogName() { return "WellManager"; } + /** + * @copydoc PhysicsSolverBase::getCatalogName() + */ + string getCatalogName() const override { return catalogName(); } + + virtual void registerDataOnMesh( Group & meshBodies ) override; + /** + * @brief Get a well solver for a given well element sub-region + * @param subRegion the well subRegion whose well solver is requested + * @return a reference to the well solver + */ + WellControls & getWell( WellElementSubRegion const & subRegion ); + + /** + * @brief Get a well solver for a given well element sub-region + * @param wellControlsName name of well + * @return a reference to the well solver + */ + WellControls & getWell( std::string const & wellControlsName ); + + /** + * @brief Get a well solver for a given well element sub-region + * @param wellControlsName name of well + * @return a reference to the well solver + */ + WellControls const & getWell( std::string const & wellControlsName ) const; +/** + * @brief get the name of DOF defined on well elements + * @return name of the DOF field used by derived solver type + */ + string wellElementDofName() const; + + struct viewKeyStruct : PhysicsSolverBase::viewKeyStruct + { + static constexpr char const * dofFieldString() { return "wellVars"; } + static constexpr char const * isThermalString() { return "isThermal"; } + static constexpr char const * useMassFlagString() {return "useMass"; } + /// @return string for the nextDt targetRegions wrapper + static constexpr char const * targetRegionsString() { return "targetRegions"; } + static constexpr char const * timeStepFromTablesFlagString() { return "timeStepFromTables"; } + static constexpr char const * useTotalMassEquationString() { return "useTotalMassEquation"; } + static constexpr char const * allowLocalCompDensChoppingString() { return CompositionalMultiphaseBase::viewKeyStruct::allowLocalCompDensChoppingString(); } + + + }; + + /** + * @brief getter for the number of degrees of freedom per well element + * @return the number of dofs + */ + localIndex numDofPerWellElement() const; + + /** + * @brief getter for the number of degrees of freedom per mesh element + * @return the number of dofs + */ + localIndex numDofPerResElement() const; + + /** + * @brief getter for iso/thermal switch + * @return True if thermal + */ + integer isThermal() const; + + + /** + * @brief get the name of DOF defined on well elements + * @return name of the DOF field used by derived solver type + */ + virtual string resElementDofName() const; + + /** + * @brief const getter for the number of fluid components + * @return the number of fluid components + */ + virtual localIndex numFluidComponents() const; + + /** + * @brief const getter for the number of fluid phases + * @return the number of fluid phases + */ + virtual localIndex numFluidPhases() const; + + /** + * @brief const getter for well total mass equation usage + * @return true if total mass equation is used + */ + integer useTotalMassEquation() const { return m_useTotalMassEquation; } + + /** + * @brief getter for the well controls associated to this well subRegion + * @param subRegion the well subRegion whose controls are requested + * @return a reference to the controls + */ + WellControls & getWellControls( WellElementSubRegion const & subRegion ); + + /** + * @brief const getter for the well controls associated to this well subRegion + * @param subRegion the well subRegion whose controls are requested + * @return a reference to the const controls + */ + WellControls const & getWellControls( WellElementSubRegion const & subRegion ) const; + + /** + * @brief getter for the compositional multiphase well associated to this well subRegion + * @param subRegion the well subRegion whose controls are requested + * @return a reference to the well + */ + CompositionalMultiphaseWell & getCompositionalMultiphaseWell( WellElementSubRegion const & subRegion ); + + /** + * @brief const getter for the compositional multiphase well associated to this well subRegion + * @param subRegion the well subRegion whose controls are requested + * @return a reference to the const well + */ + CompositionalMultiphaseWell const & getCompositionalMultiphaseWell( WellElementSubRegion const & subRegion ) const; + + /** + * @brief Selects the active well constraint based on current conditions + * @param[in] currentTime the current time + * @param[in] currentDt the current time step size + * @param[in] coupledIterationNumber the current coupled iteration number + * @param[in] domain the domain object + * @return the prescribed time step size + */ + void selectWellConstraint( real64 const & time_n, + real64 const & dt, + integer const coupledIterationNumber, + DomainPartition & domain ); + /* PhysicsSolverBase interfaces */ + + /** + * @brief function to set the next time step size + * @param[in] currentTime the current time + * @param[in] currentDt the current time step size + * @param[in] domain the domain object + * @return the prescribed time step size + */ + virtual real64 setNextDt( real64 const & currentTime, + real64 const & currentDt, + DomainPartition & domain ) override; + + virtual void setupDofs( DomainPartition const & domain, + DofManager & dofManager ) const override; + + /** + * @brief function to perform setup for implicit timestep + * @param time_n the time at the beginning of the step + * @param dt the desired timestep + * @param domain the domain partition + * + * This function should contain any step level initialization required to perform an implicit + * step. + * + * @note This function must be overridden in the derived physics solver in order to use an implict + * solution method such as LinearImplicitStep() or NonlinearImplicitStep(). + */ + virtual void + implicitStepSetup( real64 const & time_n, + real64 const & dt, + DomainPartition & domain ) override; + + + /** + * @brief function to assemble the linear system matrix and rhs + * @param time the time at the beginning of the step + * @param dt the desired timestep + * @param domain the domain partition + * @param dofManager degree-of-freedom manager associated with the linear system + * @param localMatrix the system matrix + * @param localRhs the system right-hand side vector + * + * This function assembles the residual and the jacobian of the residual wrt the primary + * variables. In a stand alone physics solver, this function will fill a single block in the + * block system. However the capability to query the block system structure for any coupled blocks + * may be implemented to fill in off diagonal blocks of the system to enable coupling between + * solvers. + * + * @note This function must be overridden in the derived physics solver in order to use an implict + * solution method such as LinearImplicitStep() or NonlinearImplicitStep(). + */ + virtual void + assembleSystem( real64 const time, + real64 const dt, + DomainPartition & domain, + DofManager const & dofManager, + CRSMatrixView< real64, globalIndex const > const & localMatrix, + arrayView1d< real64 > const & localRhs ) override; + + + virtual void + resetStateToBeginningOfStep( DomainPartition & domain ) override; + + virtual void + implicitStepComplete( real64 const & time, + real64 const & dt, + DomainPartition & domain ) override; + + virtual void applyBoundaryConditions( real64 const GEOS_UNUSED_PARAM( time_n ), + real64 const GEOS_UNUSED_PARAM( dt ), + DomainPartition & GEOS_UNUSED_PARAM( domain ), + DofManager const & GEOS_UNUSED_PARAM( dofManager ), + CRSMatrixView< real64, globalIndex const > const & GEOS_UNUSED_PARAM( localMatrix ), + arrayView1d< real64 > const & GEOS_UNUSED_PARAM( localRhs ) ) override {} + + /** + * @brief calculate the norm of the global system residual + * @param time the time at the beginning of the step + * @param dt the desired timestep + * @param domain the domain partition + * @param dofManager degree-of-freedom manager associated with the linear system + * @param localRhs the system right-hand side vector + * @return norm of the residual + * + * This function returns the norm of global residual vector, which is suitable for comparison with + * a tolerance. + */ + virtual real64 + calculateResidualNorm( real64 const & time, + real64 const & dt, + DomainPartition const & domain, + DofManager const & dofManager, + arrayView1d< real64 const > const & localRhs ) override; + /** + * @brief Recompute all dependent quantities from primary variables (including constitutive models) + * @param domain the domain containing the mesh and fields + */ + virtual void updateState( DomainPartition & domain ) override; + + /** + * @brief Function to determine if the solution vector should be scaled back in order to maintain a known constraint. + * @param[in] domain The domain partition. + * @param[in] dofManager degree-of-freedom manager associated with the linear system + * @param[in] localSolution the solution vector + * @return The factor that should be used to scale the solution vector values when they are being applied. + */ + virtual real64 + scalingForSystemSolution( DomainPartition & domain, + DofManager const & dofManager, + arrayView1d< real64 const > const & localSolution )override; + + /** + * @brief Function to check system solution for physical consistency and constraint violation + * @param domain the domain partition + * @param dofManager degree-of-freedom manager associated with the linear system + * @param localSolution the solution vector + * @param scalingFactor factor to scale the solution prior to application + * @return true if solution can be safely applied without violating physical constraints, false otherwise + * + * @note This function must be overridden in the derived physics solver in order to use an implict + * solution method such as LinearImplicitStep() or NonlinearImplicitStep(). + * + */ + virtual bool + checkSystemSolution( DomainPartition & domain, + DofManager const & dofManager, + arrayView1d< real64 const > const & localSolution, + real64 const scalingFactor ) override; + + /** + * @brief Function to apply the solution vector to the state + * @param dofManager degree-of-freedom manager associated with the linear system + * @param localSolution the solution vector + * @param scalingFactor factor to scale the solution prior to application + * @param dt the timestep + * @param domain the domain partition + * + * This function performs 2 operations: + * 1) extract the solution vector for the "blockSystem" parameter, and applies the + * contents of the solution vector to the primary variable field data, + * 2) perform a synchronization of the primary field variable such that all ghosts are updated, + * + * The "scalingFactor" parameter allows for the scaled application of the solution vector. For + * instance, a line search may apply a negative scaling factor to remove part of the previously + * applied solution. + * + * @note This function must be overridden in the derived physics solver in order to use an implict + * solution method such as LinearImplicitStep() or NonlinearImplicitStep(). + * + */ + virtual void + applySystemSolution( DofManager const & dofManager, + arrayView1d< real64 const > const & localSolution, + real64 const scalingFactor, + real64 const dt, + DomainPartition & domain ) override; + + /** + * @brief Sets all the negative component densities (if any) to zero. + * @param domain the physical domain object + */ + void chopNegativeDensities( DomainPartition & domain ); + + /** + * @brief Utility function to keep the well variables during a time step (used in + * poromechanics simulations) + * @param[in] keepVariablesConstantDuringInitStep flag to tell the solver to freeze its + * primary variables during a time step + * @detail This function is meant to be called by a specific task before/after the + * initialization step + */ + void setKeepVariablesConstantDuringInitStep( bool const keepVariablesConstantDuringInitStep ); + + +protected: + //virtual void postInputInitialization() override; + + virtual void initializePostSubGroups() override; + + virtual void initializePostInitialConditionsPreSubGroups() override; + + virtual void postRestartInitialization() override final; + + +private: + + /// name of the flow solver + string m_flowSolverName; + + /// flag indicating whether mass or molar formulation should be used + integer m_useMass; + + /// flag indicating whether total mass equation should be used + integer m_useTotalMassEquation; + + /// flag indicating whether thermal formulation is used + integer m_isThermal; + + /// flag indicating whether compositional formulation is used + bool m_isCompositional; + + + /// number of phases + integer m_numFluidPhases; + + /// number of components + integer m_numFluidComponents; + + /// number of degrees of freedom per well element + integer m_numDofPerWellElement; + + /// number of degrees of freedom per reservoir element + integer m_numDofPerResElement; + + /// minimum value of the scaling factor obtained by enforcing maxCompFracChange + real64 m_minScalingFactor; + + /// flag indicating whether local (cell-wise) chopping of negative compositions is allowed + integer m_allowCompDensChopping; + + // flag to enable time step selection base on rates/bhp tables coordinates + integer m_timeStepFromTables; +}; + +} + +#endif //GEOS_PHYSICSSOLVERS_FLUIDFLOW_WELLS_WELL_MANAGER_HPP_ diff --git a/src/coreComponents/physicsSolvers/fluidFlow/wells/WellMassRateConstraint.cpp b/src/coreComponents/physicsSolvers/fluidFlow/wells/WellMassRateConstraint.cpp new file mode 100644 index 00000000000..0540818bb2d --- /dev/null +++ b/src/coreComponents/physicsSolvers/fluidFlow/wells/WellMassRateConstraint.cpp @@ -0,0 +1,75 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2016-2024 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2024 TotalEnergies + * Copyright (c) 2018-2024 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2023-2024 Chevron + * Copyright (c) 2019- GEOS/GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +/* + * @file WellMassRateConstraints.cpp + */ + +#include "LogLevelsInfo.hpp" +#include "WellMassRateConstraint.hpp" +#include "WellConstants.hpp" +#include "dataRepository/InputFlags.hpp" +#include "functions/FunctionManager.hpp" + +namespace geos +{ + +using namespace dataRepository; + +MassRateConstraint::MassRateConstraint( string const & name, Group * const parent ) + : WellConstraintBase( name, parent ) +{ + setInputFlags( InputFlags::OPTIONAL_NONUNIQUE ); + + registerWrapper( viewKeyStruct::massRateString(), &m_constraintValue ). + setDefaultValue( 0.0 ). + setInputFlag( InputFlags::OPTIONAL ). + setRestartFlags( RestartFlags::WRITE_AND_READ ). + setDescription( "Maximum mass rate (kg/s)" ); +} + +MassRateConstraint::~MassRateConstraint() +{} + + +void MassRateConstraint::postInputInitialization() +{ + // Validate table options + WellConstraintBase::postInputInitialization(); + + // check constraint value + GEOS_THROW_IF( m_constraintValue < 0, + getWrapperDataContext( viewKeyStruct::massRateString() ) << ": Target value is negative", + InputError ); + + GEOS_THROW_IF ((m_constraintValue <= 0.0 && m_constraintScheduleTableName.empty()), + getName() << " " << getDataContext() << ": You need to specify a mass rate constraint. \n" << + "The rate constraint can be specified using " << + "either " << viewKeyStruct::massRateString() << + " or " << WellConstraintBase::viewKeyStruct::constraintScheduleTableNameString(), + InputError ); +} + + +bool MassRateConstraint::checkViolation( WellConstraintBase const & currentConstraint, real64 const & currentTime )const +{ + // isViolated is defined as a static method on the specific WellConstraintType (Injection/Production) + // Evaluate violation according to the sign set for injectors/producers + real64 const currentValue = currentConstraint.massRate(); + real64 const constraintValue = this->getConstraintValue( currentTime ); + return ( LvArray::math::abs( currentValue ) > LvArray::math::abs( constraintValue ) ); + +} +} //namespace geos diff --git a/src/coreComponents/physicsSolvers/fluidFlow/wells/WellMassRateConstraint.hpp b/src/coreComponents/physicsSolvers/fluidFlow/wells/WellMassRateConstraint.hpp new file mode 100644 index 00000000000..6cd6703ba36 --- /dev/null +++ b/src/coreComponents/physicsSolvers/fluidFlow/wells/WellMassRateConstraint.hpp @@ -0,0 +1,120 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2016-2024 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2024 TotalEnergies + * Copyright (c) 2018-2024 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2023-2024 Chevron + * Copyright (c) 2019- GEOS/GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +/* + * @file WellMassRateConstraint.hpp + */ + + +#ifndef GEOS_PHYSICSSOLVERS_FLUIDFLOW_WELLS_WELLMASSRATECONSTRAINT_HPP +#define GEOS_PHYSICSSOLVERS_FLUIDFLOW_WELLS_WELLMASSRATECONSTRAINT_HPP + +#include "common/format/EnumStrings.hpp" +#include "dataRepository/Group.hpp" +#include "functions/TableFunction.hpp" +#include "WellConstraintsBase.hpp" +namespace geos +{ + +/** + * @class MassRateConstraint + * @brief This class describes a mass rate constraint used to control a well. + */ + +class MassRateConstraint : public WellConstraintBase +{ +public: + + /** + * @name Constructor / Destructor + */ + ///@{ + + /** + * @brief Constructor for WellControls Objects. + * @param[in] name the name of this instantiation of WellControls in the repository + * @param[in] parent the parent group of this instantiation of WellControls + */ + explicit MassRateConstraint( string const & name, dataRepository::Group * const parent ); + + + /** + * @brief Default destructor. + */ + ~MassRateConstraint() override; + + /** + * @brief Deleted default constructor. + */ + MassRateConstraint() = delete; + + /** + * @brief Deleted copy constructor. + */ + MassRateConstraint( MassRateConstraint const & ) = delete; + + /** + * @brief Deleted move constructor. + */ + MassRateConstraint( MassRateConstraint && ) = delete; + + /** + * @brief Deleted assignment operator. + * @return a reference to a constraint object + */ + MassRateConstraint & operator=( MassRateConstraint const & ) = delete; + + /** + * @brief Deleted move operator. + * @return a reference to a constraint object + */ + MassRateConstraint & operator=( MassRateConstraint && ) = delete; + + /** + * @brief name of the node manager in the object catalog + * @return string that contains the catalog name to generate a new Constraint object through the object catalog. + */ + static string catalogName() + { + return "MassRateConstraint"; + } + ///@} + + struct viewKeyStruct + { + /// String key for the well target rate + static constexpr char const * massRateString() { return "massRate"; } + }; + + /** + * @name Getters / Setters + */ + + // Temp interface - tjb + virtual ConstraintTypeId getControl() const override { return ConstraintTypeId::MASSRATE; }; + ///@} + + virtual bool checkViolation( WellConstraintBase const & currentConstraint, real64 const & currentTime ) const override; + +protected: + + virtual void postInputInitialization() override; + +}; + + +} //namespace geos + +#endif //GEOS_PHYSICSSOLVERS_FLUIDFLOW_WELLS_WELLMASSRATECONSTRAINT_HPP diff --git a/src/coreComponents/physicsSolvers/fluidFlow/wells/WellNewtonSolver.cpp b/src/coreComponents/physicsSolvers/fluidFlow/wells/WellNewtonSolver.cpp new file mode 100644 index 00000000000..06538248762 --- /dev/null +++ b/src/coreComponents/physicsSolvers/fluidFlow/wells/WellNewtonSolver.cpp @@ -0,0 +1,514 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2016-2024 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2024 TotalEnergies + * Copyright (c) 2018-2024 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2023-2024 Chevron + * Copyright (c) 2019- GEOS/GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +#include "WellNewtonSolver.hpp" + +#include "common/MpiWrapper.hpp" +#include "codingUtilities/RTTypes.hpp" +#include "common/format/EnumStrings.hpp" +#include "dataRepository/Group.hpp" +#include "physicsSolvers/LogLevelsInfo.hpp" +#include "common/format/LogPart.hpp" +#include "common/TimingMacros.hpp" +#include "linearAlgebra/solvers/KrylovSolver.hpp" +#include "mesh/DomainPartition.hpp" +#include "math/interpolation/Interpolation.hpp" +#include "common/Timer.hpp" +#include "common/Units.hpp" + + +namespace geos +{ + +using namespace dataRepository; + +WellNewtonSolver::WellNewtonSolver( string const & name, + Group * const parent ) + : + dataRepository::Group( name, parent ), + + m_dofManager( name ), + m_usePhysicsScaling( 1 ), + m_linearSolverParameters( groupKeyStruct::linearSolverParametersString(), this ), + m_nonlinearSolverParameters( groupKeyStruct::nonlinearSolverParametersString(), this ), + m_solverStatistics( groupKeyStruct::solverStatisticsString(), this ), + m_systemSetupTimestamp( 0 ), + m_activeCoupledIterations( 1 ), + m_enableIsoThermalEstimator( 0 ) +{ + setInputFlags( InputFlags::OPTIONAL_NONUNIQUE ); + + this->registerWrapper( viewKeyStruct::activeCoupledIterationsString(), &m_activeCoupledIterations ). + setApplyDefaultValue( 1 ). + setInputFlag( InputFlags::OPTIONAL ). + setDescription( "Number of coupled iterations to activate estimator solve." ); + + registerWrapper( viewKeyStruct::enableIsoThermalEstimatorString(), &m_enableIsoThermalEstimator ). + setDefaultValue( 0 ). + setInputFlag( InputFlags::OPTIONAL ). + setDescription( "Estimator configuration option to disable thermal effects on initial well constraint solve and then converge solution with thermal effects enabled: \n" + " - If the flag is set to 1, thermal effects are enabled during the initial constraint solve. \n" + " - If the flag is set to 0, thermal effects are disabled during the initial constraint solve." ); + + + + registerWrapper( viewKeyStruct::writeLinearSystemString(), &m_writeLinearSystem ). + setApplyDefaultValue( 0 ). + setInputFlag( InputFlags::OPTIONAL ). + setRestartFlags( RestartFlags::WRITE_AND_READ ). + setDescription( "Write matrix, rhs, solution to screen ( = 1) or file ( = 2)." ); + + registerWrapper( viewKeyStruct::allowNonConvergedLinearSolverSolutionString(), &m_allowNonConvergedLinearSolverSolution ). + setApplyDefaultValue( 1 ). + setInputFlag( InputFlags::OPTIONAL ). + setRestartFlags( RestartFlags::WRITE_AND_READ ). + setDescription( "Cut time step if linear solution fail without going until max nonlinear iterations." ); + + registerWrapper( viewKeyStruct::usePhysicsScalingString(), &m_usePhysicsScaling ). + setApplyDefaultValue( 1 ). + setInputFlag( InputFlags::OPTIONAL ). + setDescription( "Enable physics-based scaling of the linear system. Default: true." ); + + + registerWrapper( viewKeyStruct::writeStatisticsCSVString(), &m_writeStatisticsCSV ). + setApplyDefaultValue( StatsOutputType::none ). + setInputFlag( InputFlags::OPTIONAL ). + setRestartFlags( RestartFlags::NO_WRITE ). + setDescription( GEOS_FMT( "When set to `{}`, output iterations information to a csv\n" + "When set to `{}`, output convergence information to a csv\n" + "When set to `{}` output both convergence & iteration information to a csv.", + EnumStrings< StatsOutputType >::toString( StatsOutputType::iteration ), + EnumStrings< StatsOutputType >::toString( StatsOutputType::convergence ), + EnumStrings< StatsOutputType >::toString( StatsOutputType::all ) )); + + addLogLevel< logInfo::Convergence >(); + addLogLevel< logInfo::Fields >(); + addLogLevel< logInfo::LinearSolver >(); + addLogLevel< logInfo::ResidualNorm >(); + addLogLevel< logInfo::Solution >(); + addLogLevel< logInfo::TimeStep >(); + addLogLevel< logInfo::Timers >(); + + registerGroup( groupKeyStruct::linearSolverParametersString(), &m_linearSolverParameters ); + registerGroup( groupKeyStruct::nonlinearSolverParametersString(), &m_nonlinearSolverParameters ); + registerGroup( groupKeyStruct::solverStatisticsString(), &m_solverStatistics ); + + m_localMatrix.setName( this->getName() + "/localMatrix" ); + m_matrix.setDofManager( &m_dofManager ); +} + +void WellNewtonSolver::postInputInitialization() +{ + m_solverStatistics.setOutputFilesName( getName() ); + + m_solverStatistics.makeDir( m_writeStatisticsCSV != StatsOutputType::none ); + + getIterationStats().setTableName( getName() ); + getIterationStats().setLogOutputRequest( true ); + getIterationStats().setCSVOutputRequest( m_writeStatisticsCSV == StatsOutputType::iteration || + m_writeStatisticsCSV == StatsOutputType::all ); + getConvergenceStats().setCSVOutputRequest( m_writeStatisticsCSV == StatsOutputType::convergence || + m_writeStatisticsCSV == StatsOutputType::all ); +} + +WellNewtonSolver::~WellNewtonSolver() = default; + +void WellNewtonSolver::generateMeshTargetsFromTargetRegions( Group const & meshBodies ) +{ + for( auto const & target : m_targetRegionNames ) + { + + stdVector< string > targetTokens = stringutilities::tokenize( target, "/" ); + + if( targetTokens.size()==1 ) // no MeshBody or MeshLevel specified + { + GEOS_ERROR_IF( meshBodies.numSubGroups() != 1, + getDataContext() << ": No MeshBody information is specified in" << + " WellNewtonSolver::meshTargets, but there are multiple MeshBody objects", + getDataContext() ); + MeshBody const & meshBody = meshBodies.getGroup< MeshBody >( 0 ); + string const meshBodyName = meshBody.getName(); + + string const meshLevelName = ""; //tjbm_discretizationName; + + string const regionName = target; + auto const key = std::make_pair( meshBodyName, meshLevelName ); + m_meshTargets[key].emplace_back( regionName ); + } + else if( targetTokens.size()==2 ) + { + string const meshBodyName = targetTokens[0]; + GEOS_ERROR_IF( !meshBodies.hasGroup( meshBodyName ), + getWrapperDataContext( viewKeyStruct::targetRegionsString() ) << ": MeshBody (" << + meshBodyName << ") is specified in targetRegions, but does not exist.", + getWrapperDataContext( viewKeyStruct::targetRegionsString() ) ); + + string const meshLevelName = "";//tjbm_discretizationName; + + string const regionName = targetTokens[1]; + + + auto const key = std::make_pair( meshBodyName, meshLevelName ); + m_meshTargets[key].emplace_back( regionName ); + } + else + { + GEOS_ERROR( getDataContext() << ": Invalid specification of targetRegions" ); + } + } +} + + + +Group * WellNewtonSolver::createChild( string const & GEOS_UNUSED_PARAM( childKey ), string const & GEOS_UNUSED_PARAM( childName ) ) +{ + // Unused as all children are created within the constructor + return nullptr; +} + +WellNewtonSolver::CatalogInterface::CatalogType & WellNewtonSolver::getCatalog() +{ + static WellNewtonSolver::CatalogInterface::CatalogType catalog; + return catalog; +} + + +bool WellNewtonSolver::registerCallback( void * func, const std::type_info & funcType ) +{ + if( std::type_index( funcType ) == std::type_index( typeid( std::function< void( CRSMatrix< real64, globalIndex >, array1d< real64 > ) > ) ) ) + { + m_assemblyCallback = *reinterpret_cast< std::function< void( CRSMatrix< real64, globalIndex >, array1d< real64 > ) > * >( func ); + return true; + } + + return false; +} + + + +void WellNewtonSolver::logEndOfCycleInformation( integer const cycleNumber, + integer const numOfSubSteps, + stdVector< real64 > const & subStepDts ) const +{ + LogPart logpart( "TIMESTEP", MpiWrapper::commRank() == 0 ); + logpart.addEndDescription( "- Cycle ", cycleNumber ); + logpart.addEndDescription( "- N substeps ", numOfSubSteps ); + + std::stringstream logMessage; + for( integer i = 0; i < numOfSubSteps; ++i ) + { + if( i > 0 ) + { + logMessage << ", "; + } + logMessage << subStepDts[i] << " " << units::getSymbol( units::Unit::Time ); + } + + if( logMessage.rdbuf()->in_avail() == 0 ) + logMessage << "/"; + + logpart.addEndDescription( "- substep dts ", logMessage.str() ); + logpart.end(); + + if( isLogLevelActive< logInfo::SolverExecutionDetails >( getLogLevel())) + getIterationStats().outputStatistics(); +} + +void WellNewtonSolver::setupDofs( DomainPartition const & GEOS_UNUSED_PARAM( domain ), + DofManager & GEOS_UNUSED_PARAM( dofManager ) ) const +{ + GEOS_ERROR( "WellNewtonSolver::setupDofs called!. Should be overridden." ); +} + +void WellNewtonSolver::setSparsityPattern( DomainPartition & GEOS_UNUSED_PARAM( domain ), + DofManager & dofManager, + CRSMatrix< real64, globalIndex > & GEOS_UNUSED_PARAM( localMatrix ), + SparsityPattern< globalIndex > & pattern ) +{ + dofManager.setSparsityPattern( pattern ); +} + +void WellNewtonSolver::setSystemSetupTimestamp( Timestamp timestamp ) +{ + m_systemSetupTimestamp = timestamp; + + std::ostringstream oss; + m_dofManager.printFieldInfo( oss ); + GEOS_LOG_LEVEL( logInfo::Fields, oss.str()); +} + +std::unique_ptr< PreconditionerBase< LAInterface > > +WellNewtonSolver::createPreconditioner( DomainPartition & GEOS_UNUSED_PARAM( domain ) ) const +{ + // By default, do not create a preconditioner, one will be created internally inside LA backend + return {}; + + // TODO: refactor interfaces to always create preconditioner externally and pass to backends + // return LAInterface::createPreconditioner( m_linearSolverParameters.get() ); +} + + +namespace +{ + +/** + * @brief Helper for debug output of linear algebra objects (matrices and vectors) + * @tparam T type of LA object (must have stream insertion and .write() implemented) + * @param obj the object to output + * @param cycleNumber event cycle number + * @param nonlinearIteration nonlinear iteration number + * @param filePrefix short filename prefix (e.g. "mat") + * @param screenName long name for screen output (e.g. "System matrix") + * @param toScreen whether to print on screen + * @param toFile whether to write to file + */ +template< typename T > +void debugOutputLAObject( T const & obj, + real64 const & GEOS_UNUSED_PARAM( time ), + integer const cycleNumber, + integer const nonlinearIteration, + string const & filePrefix, + string const & screenName, + bool const toScreen, + bool const toFile ) +{ + if( toScreen ) + { + GEOS_LOG_RANK_0( GEOS_FMT( "{2:=>{1}}\n{0}:\n{2:=>{1}}", screenName, screenName.size() + 1, "" ) ); + GEOS_LOG( obj ); + } + + if( toFile ) + { + string const filename = GEOS_FMT( "{}_{:06}_{:02}.mtx", filePrefix.c_str(), cycleNumber, nonlinearIteration ); + obj.write( filename, LAIOutputFormat::MATRIX_MARKET ); + GEOS_LOG_RANK_0( GEOS_FMT( "{} written to {}", screenName, filename ) ); + } +} + +} + +void WellNewtonSolver::debugOutputSystem( real64 const & time, + integer const cycleNumber, + integer const nonlinearIteration, + ParallelMatrix const & matrix, + ParallelVector const & rhs ) const +{ + // special case when flag value > 2 + if( m_writeLinearSystem > 2 && cycleNumber < m_writeLinearSystem ) + return; + + debugOutputLAObject( matrix, + time, + cycleNumber, + nonlinearIteration, + getName() + "_mat", + "System matrix", + m_writeLinearSystem == 1, + m_writeLinearSystem >= 2 ); + + debugOutputLAObject( rhs, + time, + cycleNumber, + nonlinearIteration, + getName() + "_rhs", + "System right-hand side", + m_writeLinearSystem == 1, + m_writeLinearSystem >= 2 ); +} + +void WellNewtonSolver::debugOutputSolution( real64 const & time, + integer const cycleNumber, + integer const nonlinearIteration, + ParallelVector const & solution ) const +{ + // special case when flag value > 2 + if( m_writeLinearSystem > 2 && cycleNumber < m_writeLinearSystem ) + return; + + debugOutputLAObject( solution, + time, + cycleNumber, + nonlinearIteration, + getName() + "_sol", + "System solution", + m_writeLinearSystem == 1, + m_writeLinearSystem >= 2 ); +} + +void WellNewtonSolver::updateAndWriteConvergenceStep( real64 const & time_n, real64 const & dt, + integer const cycleNumber, integer const iteration ) +{ + getConvergenceStats().updateSolverStep( time_n, dt, cycleNumber, iteration ); + getConvergenceStats().writeConvergenceStatsToTable(); +} + + +void WellNewtonSolver::solveLinearSystem( DofManager const & dofManager, + ParallelMatrix & matrix, + ParallelVector & rhs, + ParallelVector & solution ) +{ + GEOS_MARK_FUNCTION; + + rhs.scale( -1.0 ); + solution.zero(); + + LinearSolverParameters const & params = m_linearSolverParameters.get(); + const bool isDirectSolver = (params.solverType == LinearSolverParameters::SolverType::direct); + const bool isSetupNeeded = !(isDirectSolver && params.direct.reuseFactorization); + + matrix.setDofManager( &dofManager ); + + GEOS_WARNING_IF( isDirectSolver && dofManager.numGlobalDofs() > 100000, + "Direct solver used for large system ( > 100,000 DOFs ). " + "This may lead to high memory consumption and long computation times. " + "Consider using an iterative solver for better performance." ); + + // Apply physics-based scaling to the linear system if enabled + if( m_usePhysicsScaling ) + { + Timer timer_setup( m_timers.get_inserted( "linear solver scaling" ) ); + + matrix.computeScalingVector( m_scaling ); + matrix.leftRightScale( m_scaling, m_scaling ); + rhs.pointwiseProduct( m_scaling ); + // Assume the solution is zeroed out, thus no need to scale it + } + + if( isDirectSolver || !m_precond ) + { + if( !m_linearSolver ) + { + m_linearSolver = LAInterface::createSolver( params ); + } + + if( isSetupNeeded ) + { + Timer timer_setup( m_timers.get_inserted( "linear solver setup" ) ); + m_linearSolver->setup( matrix ); + } + + { + Timer timer_setup( m_timers.get_inserted( "linear solver solve" ) ); + m_linearSolver->solve( rhs, solution ); + } + + m_linearSolverResult = m_linearSolver->result(); + } + else + { + { + Timer timer_setup( m_timers.get_inserted( "linear solver setup" ) ); + m_precond->setup( matrix ); + } + std::unique_ptr< KrylovSolver< ParallelVector > > solver = KrylovSolver< ParallelVector >::create( params, matrix, *m_precond ); + { + Timer timer_setup( m_timers.get_inserted( "linear solver solve" ) ); + solver->solve( rhs, solution ); + } + m_linearSolverResult = solver->result(); + } + + getIterationStats().accumulateSolverLinearTime( m_linearSolverResult.setupTime, m_linearSolverResult.solveTime ); + + GEOS_LOG_LEVEL_RANK_0( logInfo::LinearSolver, + GEOS_FMT( " Linear solve: ( iter, res ) = ( {:3}, {:4.2e} )", + m_linearSolverResult.numIterations, + m_linearSolverResult.residualReduction )); + + if( params.stopIfError ) + { + GEOS_ERROR_IF( m_linearSolverResult.breakdown(), + getDataContext() << ": Linear solution breakdown -> simulation STOP", + getDataContext() ); + } + else + { + GEOS_WARNING_IF( !m_linearSolverResult.success(), + getDataContext() << ": Linear solution failed", + getDataContext() ); + } + + // Unscale the solution vector if physics-based scaling was applied + if( m_usePhysicsScaling ) + { + Timer timer_setup( m_timers.get_inserted( "linear solver scaling" ) ); + + solution.pointwiseProduct( m_scaling ); + } +} + + + +// Detect oscillations for all dofs in the solution history +bool WellNewtonSolver::detectOscillations() const +{ + // grab the parameters + integer const oscillationCheckDepth = m_nonlinearSolverParameters.m_oscillationCheckDepth; + real64 const oscillationTolerance = m_nonlinearSolverParameters.m_oscillationTolerance; + real64 const oscillationFraction = m_nonlinearSolverParameters.m_oscillationFraction; + + if( m_solutionHistory.size() < oscillationCheckDepth ) + return false; // not enough history to check oscillations + + RAJA::ReduceSum< parallelDeviceReduce, localIndex > oscillationCount( 0 ); + + auto const solutionHistory = m_solutionHistory.toViewConst(); + localIndex const numDofs = m_solutionHistory[0].size(); + localIndex const historySize = m_solutionHistory.size(); + + RAJA::forall< parallelDevicePolicy<> >( RAJA::TypedRangeSegment< localIndex >( 0, numDofs ), + [=] GEOS_HOST_DEVICE ( localIndex const dof ) + { + bool oscillationDetected = true; + for( localIndex i = historySize - 1; i > historySize - oscillationCheckDepth; --i ) + { + real64 dxCur = solutionHistory[i][dof]; + real64 dxPrev = solutionHistory[i-1][dof]; + + if( LvArray::math::abs( dxCur ) < oscillationTolerance || LvArray::math::abs( dxPrev ) < oscillationTolerance ) + { + oscillationDetected = false; + break; // solution changes are too small + } + + real64 maxAbs = LvArray::math::max( LvArray::math::abs( dxCur ), LvArray::math::abs( dxPrev ) ); + if( LvArray::math::abs( dxCur + dxPrev ) / maxAbs > oscillationTolerance ) + { + oscillationDetected = false; + break; // solution changes are not oscillating + } + + if( dxCur * dxPrev > 0 ) + { + oscillationDetected = false; + break; // sign is not oscillating + } + } + + if( oscillationDetected ) + { + oscillationCount += 1; + } + } ); + + real64 const f = static_cast< real64 >( MpiWrapper::sum( oscillationCount.get() ) ) / MpiWrapper::sum( numDofs ); + + return f > oscillationFraction; +} + + +} // namespace geos diff --git a/src/coreComponents/physicsSolvers/fluidFlow/wells/WellNewtonSolver.hpp b/src/coreComponents/physicsSolvers/fluidFlow/wells/WellNewtonSolver.hpp new file mode 100644 index 00000000000..47a83e0f2f7 --- /dev/null +++ b/src/coreComponents/physicsSolvers/fluidFlow/wells/WellNewtonSolver.hpp @@ -0,0 +1,870 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2016-2024 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2024 TotalEnergies + * Copyright (c) 2018-2024 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2023-2024 Chevron + * Copyright (c) 2019- GEOS/GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +/** + * @file WellNewtonSolver.hpp + */ + +#ifndef GEOS_PHYSICSSOLVERS_WELLNEWTONSOLVER_HPP_ +#define GEOS_PHYSICSSOLVERS_WELLNEWTONSOLVER_HPP_ + +#include "codingUtilities/traits.hpp" +#include "common/DataTypes.hpp" +#include "common/format/LogPart.hpp" + +#include "dataRepository/RestartFlags.hpp" +#include "linearAlgebra/interfaces/InterfaceTypes.hpp" +#include "linearAlgebra/utilities/LinearSolverResult.hpp" +#include "linearAlgebra/DofManager.hpp" +#include "mesh/DomainPartition.hpp" +#include "mesh/MeshBody.hpp" +#include "physicsSolvers/NonlinearSolverParameters.hpp" +#include "physicsSolvers/LinearSolverParameters.hpp" +#include "physicsSolvers/SolverStatistics.hpp" +#include "physicsSolvers/LogLevelsInfo.hpp" + +#include "physicsSolvers/fluidFlow/SolutionCheckHelpers.hpp" +#include "common/Timer.hpp" +#include + +namespace geos +{ + +class DomainPartition; + +/** + * @class WellNewtonSolver + * @brief Base class for all physics solvers + * + * This class provides the base interface for all physics solvers. It provides the basic + * functionality for setting up and solving a linear system, as well as the interface for + * performing a timestep. + */ +class WellNewtonSolver : public dataRepository::Group +{ +public: + + /** + * @brief Type of the stat output + */ + enum class StatsOutputType : integer + { + none, iteration, convergence, all + }; + + /** + * @brief Constructor for WellNewtonSolver + * @param name the name of this instantiation of WellNewtonSolver + * @param parent the parent group of this instantiation of WellNewtonSolver + */ + explicit WellNewtonSolver( string const & name, + Group * const parent ); + + /** + * @brief Move constructor for WellNewtonSolver + */ + WellNewtonSolver( WellNewtonSolver && ) = default; + + /** + * @brief Destructor for WellNewtonSolver + */ + virtual ~WellNewtonSolver() override; + + /** + * @brief Deleted constructor + */ + WellNewtonSolver() = delete; + + /** + * @brief Deleted copy constructor + */ + WellNewtonSolver( WellNewtonSolver const & ) = delete; + + /** + * @brief Deleted copy assignment operator + */ + WellNewtonSolver & operator=( WellNewtonSolver const & ) = delete; + + /** + * @brief Deleted move assignment operator + */ + WellNewtonSolver & operator=( WellNewtonSolver && ) = delete; + + /** + * @brief name of the node manager in the object catalog + * @return string that contains the catalog name to generate a new NodeManager object through the object catalog. + */ + static string catalogName() { return "WellNewtonSolver"; } + + + + /** + * @brief Generate mesh targets from target regions + * @param meshBodies the group of mesh bodies + */ + void generateMeshTargetsFromTargetRegions( Group const & meshBodies ); + + + /** + * + * @brief Getter for system matrix + * @return a reference to linear system matrix of this solver + */ + ParallelMatrix & getSystemMatrix() { return m_matrix; } + + /** + * @brief Getter for system rhs vector + * @return a reference to linear system right-hand side of this solver + */ + ParallelMatrix const & getSystemMatrix() const { return m_matrix; } + + /** + * @brief Getter for system rhs vector + * @return a reference to linear system right-hand side of this solver + */ + ParallelVector & getSystemRhs() { return m_rhs; } + + /** + * @brief Getter for system rhs vector + * @return a reference to linear system right-hand side of this solver + */ + ParallelVector const & getSystemRhs() const { return m_rhs; } + + /** + * @brief Getter for system solution vector + * @return a reference to solution vector of this solver + */ + ParallelVector & getSystemSolution() { return m_solution; } + + /** + * @brief Getter for system solution vector + * @return a reference to solution vector of this solver + */ + ParallelVector const & getSystemSolution() const { return m_solution; } + + /** + * @brief Getter for degree-of-freedom manager + * @return a reference to degree-of-freedom manager of this solver + */ + DofManager & getDofManager() { return m_dofManager; } + + /** + * @brief Getter for degree-of-freedom manager + * @return a reference to degree-of-freedom manager of this solver + */ + DofManager const & getDofManager() const { return m_dofManager; } + + /** + * @brief Getter for local matrix + * @return a reference to linear system matrix of this solver + */ + CRSMatrix< real64, globalIndex > & getLocalMatrix() { return m_localMatrix; } + + /** + * @brief Getter for local matrix + * @return a reference to linear system matrix of this solver + */ + CRSMatrixView< real64 const, globalIndex const > getLocalMatrix() const { return m_localMatrix.toViewConst(); } + + + template< typename T > + void setupSystem( T & well, DomainPartition & domain, + std::string const & meshBodyName, + MeshLevel const & meshLevel, + WellElementRegion & wellElementRegion, + bool const setSparsity =true ); + + template< typename T > + bool + solveNonlinearSystem( T & well, real64 const & time_n, + real64 const & stepDt, + integer const cycleNumber, + DomainPartition & domain, + MeshLevel & mesh, + ElementRegionManager & elemManager, + WellElementSubRegion & subRegion ); + + + /** + * @brief Populate degree-of-freedom manager with fields relevant to this solver + * @param domain the domain containing the mesh and fields + * @param dofManager degree-of-freedom manager associated with the linear system + */ + virtual void + setupDofs( DomainPartition const & domain, + DofManager & dofManager ) const; + + /** + * @brief Set up the linear system (DOF indices and sparsity patterns) + * @param domain the domain containing the mesh and fields + * @param dofManager degree-of-freedom manager associated with the linear system + * @param localMatrix the system matrix + * @param rhs the system right-hand side vector + * @param solution the solution vector + * @param setSparsity flag to indicate if the sparsity pattern should be set + * + * @note While the function is virtual, the base class implementation should be + * sufficient for most single-physics solvers. + */ + + + /** + * @brief Set the sparsity pattern of the linear system matrix + * @param domain the domain containing the mesh and fields + * @param dofManager degree-of-freedom manager associated with the linear system + * @param localMatrix the system matrix + * @param pattern the sparsity pattern to be filled + */ + virtual void + setSparsityPattern( DomainPartition & domain, + DofManager & dofManager, + CRSMatrix< real64, globalIndex > & localMatrix, + SparsityPattern< globalIndex > & pattern ); + + /** + * @brief Create a preconditioner for this solver's linear system. + * @param domain the domain containing the mesh and fields + * @return the newly created preconditioner object + */ + virtual std::unique_ptr< PreconditionerBase< LAInterface > > + createPreconditioner( DomainPartition & domain ) const; + + + /** + * @brief Output the assembled linear system for debug purposes. + * @param time beginning-of-step time + * @param cycleNumber event cycle number + * @param nonlinearIteration current nonlinear iteration number + * @param matrix system matrix + * @param rhs system right-hand side vector + */ + void + debugOutputSystem( real64 const & time, + integer const cycleNumber, + integer const nonlinearIteration, + ParallelMatrix const & matrix, + ParallelVector const & rhs ) const; + + /** + * @brief Output the linear system solution for debug purposes. + * @param time beginning-of-step time + * @param cycleNumber event cycle number + * @param nonlinearIteration current nonlinear iteration number + * @param solution system solution vector + */ + void + debugOutputSolution( real64 const & time, + integer const cycleNumber, + integer const nonlinearIteration, + ParallelVector const & solution ) const; + + /** + * @brief Update the convergence information and write then into a CSV file + * @param time_n the time at the beginning of the step + * @param dt the desired timestep + * @param cycleNumber event cycle number + * @param iteration current iteration + */ + virtual void + updateAndWriteConvergenceStep( real64 const & time_n, + real64 const & dt, + integer const cycleNumber, + integer const iteration ); + + + + /** + * @brief function to apply a linear system solver to the assembled system. + * @param dofManager degree-of-freedom manager associated with the linear system + * @param matrix the system matrix + * @param rhs the system right-hand side vector + * @param solution the solution vector + * + * This function calls the linear solver package to perform a single linear solve on the block + * system. The derived physics solver is required to specify the call, as no default is provided. + * + * @note This function must be overridden in the derived physics solver in order to use an implict + * solution method such as LinearImplicitStep() or NonlinearImplicitStep(). + */ + virtual void + solveLinearSystem( DofManager const & dofManager, + ParallelMatrix & matrix, + ParallelVector & rhs, + ParallelVector & solution ); + + + + /** + * @brief creates a child group of of this WellNewtonSolver instantiation + * @param childKey the key of the child type + * @param childName the name of the child + * @return a pointer to the child group + */ + virtual Group * createChild( string const & childKey, string const & childName ) override; + + /** + * @brief Type alias for catalog interface used by this class. See CatalogInterface. + */ + using CatalogInterface = dataRepository::CatalogInterface< WellNewtonSolver, string const &, Group * const >; + + /** + * @brief Get the singleton catalog for WellNewtonSolver. + * @return reference to the catalog object + */ + static CatalogInterface::CatalogType & getCatalog(); + + /** + * @brief Structure to hold scoped key names + */ + struct viewKeyStruct + { + /// @return string for the cflFactor wrapper + static constexpr char const * cflFactorString() { return "cflFactor"; } + + /// @return string for the initialDt wrapper + static constexpr char const * initialDtString() { return "initialDt"; } + + /// @return string for the minDtIncreaseInterval wrapper + static constexpr char const * minDtIncreaseIntervalString() { return "minDtIncreaseInterval"; } + + /// @return string for the discretization wrapper + static constexpr char const * discretizationString() { return "discretization"; } + + /// @return string for the nextDt targetRegions wrapper + static constexpr char const * targetRegionsString() { return "targetRegions"; } + + /// @return string for the writeLinearSystem wrapper + static constexpr char const * writeLinearSystemString() { return "writeLinearSystem"; } + + /// @return string for the usePhysicsScaling wrapper + static constexpr char const * usePhysicsScalingString() { return "usePhysicsScaling"; } + + /// @return string for the allowNonConvergedLinearSolverSolution wrapper + static constexpr char const * allowNonConvergedLinearSolverSolutionString() { return "allowNonConvergedLinearSolverSolution"; } + + /// @return string for the writeStatistics wrapper + static constexpr char const * writeStatisticsCSVString() { return "writeStatistics"; } + + /// @return string for the numTimestepsSinceLastDtCut wrapper + static constexpr char const * numTimestepsSinceLastDtCutString() { return "numTimestepsSinceLastDtCut"; } + + /// string key for the esitmate well solution flag + static constexpr char const * activeCoupledIterationsString() { return "activeCoupledIterations"; } + static constexpr char const * estimateWellSolutionString() { return "estimateWellSolution"; } + /// string key for the enable iso thermal estimator flag + static constexpr char const * enableIsoThermalEstimatorString() { return "enableIsoThermalEstimator"; } + }; + + /** + * @brief Structure to hold scoped key names + */ + struct groupKeyStruct + { + /// @return string for the linearSolverParameters wrapper + static constexpr char const * linearSolverParametersString() { return "LinearSolverParameters"; } + + /// @return string for the nonlinearSolverParameters wrapper + static constexpr char const * nonlinearSolverParametersString() { return "NonlinearSolverParameters"; } + + /// @return string for the solverStatistics wrapper + static constexpr char const * solverStatisticsString() { return "SolverStatistics"; } + }; + + /** + * @brief getter for the timestamp of the system setup + * @return the timestamp of the last time systemSetup was called + */ + Timestamp getSystemSetupTimestamp() const { return m_systemSetupTimestamp; } + + + /** + * @brief set the timestamp of the system setup + * @param[in] timestamp the new timestamp of system setup + */ + void setSystemSetupTimestamp( Timestamp timestamp ); + + + /** + * @brief accessor for the linear solver parameters. + * @return the linear solver parameter list + */ + LinearSolverParameters & getLinearSolverParameters() + { + return m_linearSolverParameters.get(); + } + + /** + * @brief const accessor for the linear solver parameters. + * @return the linear solver parameter list + */ + LinearSolverParameters const & getLinearSolverParameters() const + { + return m_linearSolverParameters.get(); + } + + /** + * @brief accessor for the nonlinear solver parameters. + * @return the nonlinear solver parameter list + */ + NonlinearSolverParameters & getNonlinearSolverParameters() + { + return m_nonlinearSolverParameters; + } + + /** + * @brief const accessor for the nonlinear solver parameters. + * @return the nonlinear solver parameter list + */ + NonlinearSolverParameters const & getNonlinearSolverParameters() const + { + return m_nonlinearSolverParameters; + } + + /** + * @brief synchronize the nonlinear solver parameters. + */ + virtual void + synchronizeNonlinearSolverParameters() + { /* empty here, overriden in CoupledSolver */ } + + /** + * @brief Get position of a given region within solver's target region list + * @param regionName the region name to find + * @return index within target regions list + */ + localIndex targetRegionIndex( string const & regionName ) const; + + /** + * @brief return the list of target regions + * @return the array of region names + */ + string_array const & getTargetRegionNames() const {return m_targetRegionNames;} + + + + /** + * @brief function to set the value of m_assemblyCallback + * @param func the function to set m_assemblyCallback to + * @param funcType the type of the function + * @return true if the function was successfully set, false otherwise + * + * This is used to provide a callback function for to be called in the assembly step. + */ + virtual bool registerCallback( void * func, const std::type_info & funcType ) final override; + + /** + * @return An IterationsStatistics for the "root" solver. + * Otherwise return an empty IterationsStatistics + */ + IterationsStatistics & getIterationStats() + { + return m_solverStatistics.m_iterationsStats; + } + /** + * @return An IterationsStatistics for the "root" solver. + * Otherwise return an empty IterationsStatistics + * (const version) + */ + IterationsStatistics const & getIterationStats() const + { + return m_solverStatistics.m_iterationsStats; + } + /** + * @return A ConvergenceStatistics for all sub-solvers + */ + ConvergenceStatistics & getConvergenceStats() + { + return m_solverStatistics.m_convergenceStats; + } + /** + * @return A ConvergenceStatistics for all sub-solvers (const version) + */ + ConvergenceStatistics const & getConvergenceStats() const + { + return m_solverStatistics.m_convergenceStats; + } + + /** + * @brief accessor for the solver statistics. + * @return reference to m_solverStatistics + */ + SolverStatistics & getSolverStatistics() { return m_solverStatistics; } + + /** + * @brief const accessor for the solver statistics. + * @return reference to m_solverStatistics + */ + SolverStatistics const & getSolverStatistics() const { return m_solverStatistics; } + + + + /** + * @brief Detect oscillations in the solution + * @return true if oscillations are detected, false otherwise + */ + bool detectOscillations() const; + + + /** + * @brief Set thermal effects enable + * @param[in] true/false + */ + void enableThermalEffects ( bool enable ) { m_thermalEffectsEnabled = enable; }; + + /** + * @brief Are thermal effects enabled + * @return true if thermal effects are enabled, false otherwise + */ + bool thermalEffectsEnabled() const { return m_thermalEffectsEnabled; } + + /** + * @brief Is isoThermalEstimator enabled + * @return true if isoThermalEstimator is enabled, false otherwise + */ + bool isoThermalEstimatorEnabled() const { return m_enableIsoThermalEstimator; } + + bool getNumActiveCoupledIterations() const { return m_activeCoupledIterations; } + +protected: + + virtual void postInputInitialization() override; + + /// behavior in case of linear solver failure + integer m_allowNonConvergedLinearSolverSolution; + + + + /// Data structure to handle degrees of freedom + DofManager m_dofManager; + + /// System matrix + ParallelMatrix m_matrix; + + /// System right-hand side vector + ParallelVector m_rhs; + + /// System solution vector + ParallelVector m_solution; + + /// Diagonal scaling vector D (Ahat = D * A * D, bhat = D * b, x = D * xhat) + ParallelVector m_scaling; + + /// Flag to decide whether to apply physics-based scaling to the linear system + integer m_usePhysicsScaling; + + /// Local system matrix and rhs + CRSMatrix< real64, globalIndex > m_localMatrix; + + /// Custom linear solver for the "native" solver type + std::unique_ptr< LinearSolverBase< LAInterface > > m_linearSolver; + + /// Custom preconditioner for the "native" iterative solver + std::unique_ptr< PreconditionerBase< LAInterface > > m_precond; + + /// flag for debug output of matrix, rhs, and solution + integer m_writeLinearSystem; + + /// Parameter for outputing statistics information + StatsOutputType m_writeStatisticsCSV; + + /// Linear solver parameters + LinearSolverParametersInput m_linearSolverParameters; + + /// Result of the last linear solver + LinearSolverResult m_linearSolverResult; + + /// Nonlinear solver parameters + NonlinearSolverParameters m_nonlinearSolverParameters; + + /// Solver statistics + SolverStatistics m_solverStatistics; + + /// Timestamp of the last call to setup system + Timestamp m_systemSetupTimestamp; + + /// Callback function for assembly step + std::function< void( CRSMatrix< real64, globalIndex >, array1d< real64 > ) > m_assemblyCallback; + + /// Timers for the aggregate profiling of the solver + stdMap< std::string, std::chrono::system_clock::duration > m_timers; + + /// History of the solution vector, used for oscillation detection + ArrayOfArrays< real64 > m_solutionHistory; + +private: + /// List of names of regions the solver will be applied to + string_array m_targetRegionNames; + + /// Map containing the array of target regions (value) for each MeshBody (key). + map< std::pair< string, string >, string_array > m_meshTargets; + + /// Number of coupled iterations to activate estimator solve + integer m_activeCoupledIterations; + + /// Flag to enable thermal effects in wellbore calculations + bool m_thermalEffectsEnabled; + integer m_enableIsoThermalEstimator; + + + /** + * @brief output information about the cycle to the log + * @param cycleNumber the current cycle number + * @param numOfSubSteps the number of substeps taken + * @param subStepDts the time step size for each substep + */ + void logEndOfCycleInformation( integer const cycleNumber, + integer const numOfSubSteps, + stdVector< real64 > const & subStepDts ) const; +}; + +template< typename T > +void WellNewtonSolver::setupSystem( T & well, DomainPartition & domain, + std::string const & meshBodyName, + MeshLevel const & meshLevel, + WellElementRegion & wellElementRegion, + bool const setSparsity ) +{ + GEOS_MARK_FUNCTION; + + map< std::pair< string, string >, string_array > meshTargets; + string_array regions; + + meshTargets.clear(); + regions.clear(); + regions.emplace_back( wellElementRegion.getName() ); + auto const key = std::make_pair( meshBodyName, meshLevel.getName() ); + meshTargets[key] = std::move( regions ); + + m_dofManager.setDomain( domain ); + m_dofManager.addField( well.wellElementDofName(), + FieldLocation::Elem, + well.numDofPerWellElement(), + meshTargets ); + + m_dofManager.addCoupling( well.wellElementDofName(), + well.wellElementDofName(), + DofManager::Connector::Node ); + + m_dofManager.reorderByRank(); + if( setSparsity ) + { + SparsityPattern< globalIndex > pattern; + setSparsityPattern( domain, m_dofManager, m_localMatrix, pattern ); + m_localMatrix.assimilate< parallelDevicePolicy<> >( std::move( pattern ) ); + } + m_localMatrix.setName( this->getName() + "/matrix" ); + + m_rhs.setName( this->getName() + "/rhs" ); + m_rhs.create( m_dofManager.numLocalDofs(), MPI_COMM_GEOS ); + + m_solution.setName( this->getName() + "/solution" ); + m_solution.create( m_dofManager.numLocalDofs(), MPI_COMM_GEOS ); +} + + +template< typename T > +bool WellNewtonSolver::solveNonlinearSystem( T & well, real64 const & time_n, + real64 const & stepDt, + integer const cycleNumber, + DomainPartition & domain, + MeshLevel & mesh, + ElementRegionManager & elemManager, + WellElementSubRegion & subRegion ) +{ + integer const maxNewtonIter = m_nonlinearSolverParameters.m_maxIterNewton; + integer const minNewtonIter = m_nonlinearSolverParameters.m_minIterNewton; + real64 const newtonTol = m_nonlinearSolverParameters.m_newtonTol; + +// keep residual from previous iteration in case we need to do a line search + + integer newtonIter = 0; + real64 scaleFactor = 1.0; + + bool isNewtonConverged = false; + + for( newtonIter = 0; newtonIter < maxNewtonIter; ++newtonIter ) + { + if( m_nonlinearSolverParameters.getLogLevel() > 4 ) + GEOS_LOG_LEVEL_RANK_0( logInfo::NonlinearSolver, + GEOS_FMT( " Well: {} Est Attempt: NewtonIter: {:2}", subRegion.getName(), stepDt, newtonIter )); + + { + Timer timer( m_timers.get_inserted( "assemble" ) ); + +// We sync the nonlinear convergence history. The coupled solver parameters are the one being +// used. We want to propagate the info to subsolvers. It can be important for solvers that +// have special treatment for specific iterations. + synchronizeNonlinearSolverParameters(); + +// zero out matrix/rhs before assembly + m_localMatrix.zero(); + m_rhs.zero(); + + arrayView1d< real64 > const localRhs = m_rhs.open(); + +// call assemble to fill the matrix and the rhs + well.assembleSystem( time_n, + stepDt, + cycleNumber, + elemManager, + subRegion, + m_dofManager, + m_localMatrix.toViewConstSizes(), + localRhs ); + +// apply boundary conditions to system + well.applyWellBoundaryConditions( time_n, + stepDt, + elemManager, + subRegion, + m_dofManager, + localRhs, + m_localMatrix.toViewConstSizes() ); + + m_rhs.close(); + + if( m_assemblyCallback ) + { +// Make a copy of LA objects and ship off to the callback + array1d< real64 > localRhsCopy( m_rhs.localSize() ); + localRhsCopy.setValues< parallelDevicePolicy<> >( m_rhs.values() ); + m_assemblyCallback( m_localMatrix, std::move( localRhsCopy ) ); + } + } + + // well.outputSingleWellDebug( time_n, stepDt, 0, newtonIter, 0, + // mesh, subRegion, dofManager, m_localMatrix.toViewConstSizes(), m_rhs.values() ); + real64 residualNorm = 0; + { + Timer timer( m_timers.get_inserted( "convergence check" ) ); + +// get residual norm + residualNorm = well.calculateWellResidualNorm( time_n, stepDt, m_nonlinearSolverParameters, subRegion, m_dofManager, m_rhs.values() ); + if( m_nonlinearSolverParameters.getLogLevel() > 4 ) + GEOS_LOG_LEVEL_RANK_0( logInfo::Convergence, + GEOS_FMT( " ( R ) = ( {:4.2e} )", residualNorm ) ); + } + //auto iterInfo = currentIter( time_n, dt ); + //outputSingleWellDebug( time_n, stepDt, 0, newtonIter, 0, + // mesh, subRegion, dofManager, m_localMatrix.toViewConstSizes(), m_rhs.values() ); + // if the residual norm is less than the Newton tolerance we denote that we have + // converged and break from the Newton loop immediately. + if( residualNorm < newtonTol && newtonIter >= minNewtonIter ) + { + isNewtonConverged = true; + break; + } + +// if the residual norm is above the max allowed residual norm, we break from +// the Newton loop to avoid crashes due to Newton divergence + if( residualNorm > m_nonlinearSolverParameters.m_maxAllowedResidualNorm ) + { + string const maxAllowedResidualNormString = NonlinearSolverParameters::viewKeysStruct::maxAllowedResidualNormString(); + if( m_nonlinearSolverParameters.getLogLevel() > 4 ) + GEOS_LOG_LEVEL_RANK_0( logInfo::Convergence, + GEOS_FMT( " The residual norm is above the {} of {}. Newton loop terminated.", + maxAllowedResidualNormString, + m_nonlinearSolverParameters.m_maxAllowedResidualNorm ) ); + isNewtonConverged = false; + break; + } + + { + Timer timer( m_timers.get_inserted( "linear solver total" ) ); + +// TODO: Trilinos currently requires this, re-evaluate after moving to Tpetra-based solvers + if( m_precond ) + { + m_precond->clear(); + } + + { + Timer timer_setup( m_timers.get_inserted( "linear solver create" ) ); + +// Compose parallel LA matrix/rhs out of local LA matrix/rhs +// + m_matrix.create( m_localMatrix.toViewConst(), m_dofManager.numLocalDofs(), MPI_COMM_GEOS ); + } + +// Output the linear system matrix/rhs for debugging purposes + //string tag = "_"+std::to_string( my_ctime ); tjb + //debugOutputSystem( time_n, cycleNumber, newtonIter, m_matrix, m_rhs, tag ); + + debugOutputSystem( time_n, cycleNumber, newtonIter, m_matrix, m_rhs ); +// Solve the linear system + solveLinearSystem( m_dofManager, m_matrix, m_rhs, m_solution ); + +// Increment the solver statistics for reporting purposes + getIterationStats().updateNonlinearIteration( m_linearSolverResult.numIterations ); + +// Output the linear system solution for debugging purposes + debugOutputSolution( time_n, cycleNumber, newtonIter, m_solution ); + //debugOutputSolution( time_n, cycleNumber, newtonIter, m_solution, tag ); + } + + { + Timer timer( m_timers.get_inserted( "apply solution" ) ); + +// Compute the scaling factor for the Newton update + scaleFactor = well.scalingForWellSystemSolution( subRegion, m_dofManager, m_solution.values() ); + if( m_nonlinearSolverParameters.getLogLevel() > 4 ) + GEOS_LOG_LEVEL_RANK_0( logInfo::Solution, + GEOS_FMT( " {}: Global solution scaling factor = {}", getName(), scaleFactor ) ); + + real64 minPressure = 0.0, minDensity = 0.0, minTotalDensity = 0.0; + bool const solutionLogActive = isLogLevelActive< logInfo::Solution >( getLogLevel() ); + bool const solutionDetailsLogActive = isLogLevelActive< logInfo::SolutionDetails >( getLogLevel() ); + ElementsReporterBuffer rankNegPressureIds{ solutionLogActive, solutionDetailsLogActive ? 16 : 0 }; + ElementsReporterBuffer rankNegDensityIds{ solutionLogActive, solutionDetailsLogActive ? 16 : 0 }; + // output only total density sum, not cell details + ElementsReporterBuffer rankTotalNegDensityIds{ solutionLogActive, 0 }; + if( !well.checkWellSystemSolution( subRegion, m_dofManager, m_solution.values(), scaleFactor, minPressure, minDensity, minTotalDensity, rankNegPressureIds, rankNegDensityIds, + rankTotalNegDensityIds ) ) + { +// TODO try chopping (similar to line search) + if( m_nonlinearSolverParameters.getLogLevel() > 4 ) + GEOS_LOG_RANK_0( GEOS_FMT( " {}: Solution check failed. Newton loop terminated.", getName()) ); + break; + } + +// apply the system solution to the fields/variables + well.applyWellSystemSolution( m_dofManager, m_solution.values(), scaleFactor, stepDt, domain, mesh, subRegion ); + } + + { + Timer timer( m_timers.get_inserted( "update state" ) ); + + // update derived variables (constitutive models) + well.updateWellState( domain.getMeshBody( mesh.getParent().getParent().getName() ), elemManager, subRegion ); + } + + } + return isNewtonConverged; +} + + +/** + * @brief String for the stats output type + */ +ENUM_STRINGS( WellNewtonSolver::StatsOutputType, + "none", + "iteration", + "convergence", + "all" ); + +} // namespace geos + + +#endif /* GEOS_PHYSICSSOLVERS_WELLNEWTONSOLVER_HPP_ */ diff --git a/src/coreComponents/physicsSolvers/fluidFlow/wells/WellPhaseVolumeRateConstraint.cpp b/src/coreComponents/physicsSolvers/fluidFlow/wells/WellPhaseVolumeRateConstraint.cpp new file mode 100644 index 00000000000..63f7baa7053 --- /dev/null +++ b/src/coreComponents/physicsSolvers/fluidFlow/wells/WellPhaseVolumeRateConstraint.cpp @@ -0,0 +1,81 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2016-2024 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2024 TotalEnergies + * Copyright (c) 2018-2024 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2023-2024 Chevron + * Copyright (c) 2019- GEOS/GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +/* + * @file WellPhaseVolumeRateConstraint.cpp + */ + +#include "LogLevelsInfo.hpp" +#include "WellPhaseVolumeRateConstraint.hpp" +#include "WellConstants.hpp" +#include "dataRepository/InputFlags.hpp" +#include "functions/FunctionManager.hpp" + + +namespace geos +{ + +using namespace dataRepository; + + +PhaseVolumeRateConstraint::PhaseVolumeRateConstraint( string const & name, Group * const parent ) + : WellConstraintBase( name, parent ) +{ + this->setInputFlags( InputFlags::OPTIONAL_NONUNIQUE ); + + this->registerWrapper( viewKeyStruct::phaseRateString(), &this->m_constraintValue ). + setDefaultValue( 0.0 ). + setInputFlag( InputFlags::OPTIONAL ). + setRestartFlags( RestartFlags::WRITE_AND_READ ). + setDescription( "Phase rate, (if useSurfaceConditions: [surface m^3/s]; else [reservoir m^3/s]) " ); + + this->registerWrapper( viewKeyStruct::phaseNameString(), &this->m_phaseName ). + setRTTypeName( rtTypes::CustomTypes::groupNameRef ). + setDefaultValue( "" ). + setInputFlag( InputFlags::OPTIONAL ). + setRestartFlags( RestartFlags::WRITE_AND_READ ). + setDescription( "Name of the target phase" ); +} + +PhaseVolumeRateConstraint::~PhaseVolumeRateConstraint() +{} + +void PhaseVolumeRateConstraint::postInputInitialization() +{ + // Validate table options + WellConstraintBase::postInputInitialization(); + + // check constraint value + GEOS_THROW_IF( m_constraintValue < 0, + getWrapperDataContext( viewKeyStruct::phaseRateString() ) << ": Target value is negative", + InputError ); + + + GEOS_THROW_IF ((m_constraintValue <= 0.0 && m_constraintScheduleTableName.empty()), + getName() << " " << getDataContext() << ": You need to specify a phase rate constraint. \n" << + "The rate constraint can be specified using " << + "either " << viewKeyStruct::phaseRateString() << + " or " << WellConstraintBase::viewKeyStruct::constraintScheduleTableNameString(), + InputError ); +} + +bool PhaseVolumeRateConstraint::checkViolation( WellConstraintBase const & currentConstraint, real64 const & currentTime ) const +{ + real64 const currentValue = currentConstraint.phaseVolumeRates()[m_phaseIndex]; + real64 const constraintValue = getConstraintValue( currentTime ); + return ( LvArray::math::abs( currentValue ) > LvArray::math::abs( constraintValue ) ); +} + +} //namespace geos diff --git a/src/coreComponents/physicsSolvers/fluidFlow/wells/WellPhaseVolumeRateConstraint.hpp b/src/coreComponents/physicsSolvers/fluidFlow/wells/WellPhaseVolumeRateConstraint.hpp new file mode 100644 index 00000000000..3b1fd5b8028 --- /dev/null +++ b/src/coreComponents/physicsSolvers/fluidFlow/wells/WellPhaseVolumeRateConstraint.hpp @@ -0,0 +1,190 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2016-2024 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2024 TotalEnergies + * Copyright (c) 2018-2024 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2023-2024 Chevron + * Copyright (c) 2019- GEOS/GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +/* + * @file WellPhaseVolumeRateConstraint.hpp + */ + + +#ifndef GEOS_PHYSICSSOLVERS_FLUIDFLOW_WELLS_WELLPHASEVOLUMERATECONSTRAINT_HPP +#define GEOS_PHYSICSSOLVERS_FLUIDFLOW_WELLS_WELLPHASEVOLUMERATECONSTRAINT_HPP + +#include "common/format/EnumStrings.hpp" +#include "dataRepository/Group.hpp" +#include "functions/TableFunction.hpp" +#include "WellConstraintsBase.hpp" +#include "WellConstants.hpp" + +namespace geos +{ + + +template< typename T > +localIndex getPhaseIndexFromFluidModel( T const & fluidModel, std::string const & inputPhase ) +{ + localIndex phaseIndex=-1; + // Find target phase index for phase rate constraint + for( integer ip = 0; ip < fluidModel.numFluidPhases(); ++ip ) + { + if( fluidModel.phaseNames()[ip] == inputPhase ) + { + phaseIndex = ip; + } + } + return phaseIndex; +} + +/** + * @class PhaseVolumeRateConstraint + * @brief This class describes a phase rate constraint used to control a well of WellConstraintType type (Injection or Production). + */ + +class PhaseVolumeRateConstraint : public WellConstraintBase +{ +public: + + + /** + * @name Constructor / Destructor + */ + ///@{ + + /** + * @brief Constructor for WellControls Objects. + * @param[in] name the name of this instantiation of WellControls in the repository + * @param[in] parent the parent group of this instantiation of WellControls + */ + explicit PhaseVolumeRateConstraint( string const & name, dataRepository::Group * const parent ); + + /** + * @brief Default destructor. + */ + ~PhaseVolumeRateConstraint() override; + + /** + * @brief Deleted default constructor. + */ + PhaseVolumeRateConstraint() = delete; + + /** + * @brief Deleted copy constructor. + */ + PhaseVolumeRateConstraint( PhaseVolumeRateConstraint const & ) = delete; + + /** + * @brief Deleted move constructor. + */ + PhaseVolumeRateConstraint( PhaseVolumeRateConstraint && ) = delete; + + /** + * @brief Deleted assignment operator. + * @return a reference to a constraint object + */ + PhaseVolumeRateConstraint & operator=( PhaseVolumeRateConstraint const & ) = delete; + + /** + * @brief Deleted move operator. + * @return a reference to a constraint object + */ + PhaseVolumeRateConstraint & operator=( PhaseVolumeRateConstraint && ) = delete; + + ///@} + + /** + * @brief name of the node manager in the object catalog + * @return string that contains the catalog name to generate a new Constraint object through the object catalog. + */ + static string catalogName() + { + return "PhaseVolumeRateConstraint"; + } + + /** + * @name Getters / Setters + */ + ///@{ + + // Temp interface - tjb + virtual ConstraintTypeId getControl() const override { return ConstraintTypeId::PHASEVOLRATE; }; + + /** + * @brief Get the target phase name + * @return the target phase name + */ + const string & getPhaseName() const { return m_phaseName; } + + /** + * @brief Set the target phase name + * @param[in] phaseName the target phase name + */ + void setPhaseName( const string & phaseName ) { m_phaseName = phaseName; } + + /** + * @brief Get the target phase index + * @return the target phase index + */ + const localIndex & getPhaseIndex() const { return m_phaseIndex; } + + /** + * @brief Set the target phase index + * @param[in] phaseIndex the target phase index + */ + void setPhaseIndex( const localIndex & phaseIndex ) { m_phaseIndex = phaseIndex; } + ///@} + + struct viewKeyStruct + { + /// String key for the well target phase rate + static constexpr char const * phaseRateString() { return "phaseRate"; } + /// String key for the well target phase name + static constexpr char const * phaseNameString() { return "phaseName"; } + }; + + /** + * @brief Validate phase type is consistent with fluidmodel + */ + template< typename T > void validatePhaseType( T const & fluidModel ); + ///@} + + virtual bool checkViolation( WellConstraintBase const & currentConstraint, real64 const & currentTime ) const override; +protected: + + virtual void postInputInitialization() override; + +private: + + /// Name of the targeted phase + string m_phaseName; + + /// Index of the target phase, used to impose the phase rate constraint + localIndex m_phaseIndex; + +}; + +template< typename T > +void PhaseVolumeRateConstraint::validatePhaseType( T const & fluidModel ) +{ + // Find target phase index for phase rate constraint + m_phaseIndex = getPhaseIndexFromFluidModel( fluidModel, this->template getReference< string >( viewKeyStruct::phaseNameString())); + + GEOS_THROW_IF( m_phaseIndex == -1, + "PhaseVolumeRateConstraint " << this->template getReference< string >( viewKeyStruct::phaseNameString()) << + ": Invalid phase type for simulation fluid model", + InputError ); +} + +} //namespace geos + +#endif //GEOS_PHYSICSSOLVERS_FLUIDFLOW_WELLS_WELLPHASEVOLUMERATECONSTRAINT_HPP diff --git a/src/coreComponents/physicsSolvers/fluidFlow/wells/WellProductionConstraint.cpp b/src/coreComponents/physicsSolvers/fluidFlow/wells/WellProductionConstraint.cpp new file mode 100644 index 00000000000..0ede58882b5 --- /dev/null +++ b/src/coreComponents/physicsSolvers/fluidFlow/wells/WellProductionConstraint.cpp @@ -0,0 +1,69 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2016-2024 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2024 TotalEnergies + * Copyright (c) 2018-2024 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2023-2024 Chevron + * Copyright (c) 2019- GEOS/GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +/* + * @file WellProductionConstraint.cpp + */ + +#include "LogLevelsInfo.hpp" +#include "WellProductionConstraint.hpp" +#include "WellConstants.hpp" +#include "dataRepository/InputFlags.hpp" +#include "functions/FunctionManager.hpp" + +#include "WellMassRateConstraint.hpp" +#include "WellPhaseVolumeRateConstraint.hpp" +#include "WellVolumeRateConstraint.hpp" + +namespace geos +{ + +template< typename ConstraintRateType > +ProductionConstraint< ConstraintRateType >::ProductionConstraint( string const & name, Group * const parent ) + : ConstraintRateType( name, parent ) +{ + // set rate sign for producers (base class member) + this->m_rateSign = -1.0; +} +template< typename ConstraintRateType > +ProductionConstraint< ConstraintRateType >::~ProductionConstraint() +{} + +template< typename ConstraintRateType > +void ProductionConstraint< ConstraintRateType >::postInputInitialization() +{ + // Validate value and table options + ConstraintRateType::postInputInitialization(); + +} +// Register concrete wrapper constraint types and instantiate templates. + +//template class ProductionConstraint< LiquidRateConstraint >; +//using ProductionLiquidRateConstraint = ProductionConstraint< LiquidRateConstraint >; +//REGISTER_CATALOG_ENTRY( WellConstraintBase, ProductionLiquidRateConstraint, string const &, Group * const ) + +template class ProductionConstraint< MassRateConstraint >; +using ProductionMassRateConstraint = ProductionConstraint< MassRateConstraint >; +REGISTER_CATALOG_ENTRY( WellConstraintBase, ProductionMassRateConstraint, string const &, Group * const ) + +template class ProductionConstraint< PhaseVolumeRateConstraint >; +using ProductionPhaseVolumeRateConstraint = ProductionConstraint< PhaseVolumeRateConstraint >; +REGISTER_CATALOG_ENTRY( WellConstraintBase, ProductionPhaseVolumeRateConstraint, string const &, Group * const ) + +template class ProductionConstraint< VolumeRateConstraint >; +using ProductionVolumeRateConstraint = ProductionConstraint< VolumeRateConstraint >; +REGISTER_CATALOG_ENTRY( WellConstraintBase, ProductionVolumeRateConstraint, string const &, Group * const ) + +} //namespace geos diff --git a/src/coreComponents/physicsSolvers/fluidFlow/wells/WellProductionConstraint.hpp b/src/coreComponents/physicsSolvers/fluidFlow/wells/WellProductionConstraint.hpp new file mode 100644 index 00000000000..ce1ca16de14 --- /dev/null +++ b/src/coreComponents/physicsSolvers/fluidFlow/wells/WellProductionConstraint.hpp @@ -0,0 +1,106 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2016-2024 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2024 TotalEnergies + * Copyright (c) 2018-2024 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2023-2024 Chevron + * Copyright (c) 2019- GEOS/GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +/* + * @file WellProductionConstraints.hpp + */ + + +#ifndef GEOS_PHYSICSSOLVERS_FLUIDFLOW_WELLS_WELLPRODUCTIONCONSTRAINT_HPP +#define GEOS_PHYSICSSOLVERS_FLUIDFLOW_WELLS_WELLPRODUCTIONCONSTRAINT_HPP + +#include "common/format/EnumStrings.hpp" +#include "dataRepository/Group.hpp" +#include "functions/TableFunction.hpp" + +namespace geos +{ +using namespace dataRepository; +/** + * @class ProductionConstraint + * @brief This class describes constraint used to control a production well. + */ + +template< typename ConstraintType > +class ProductionConstraint : public ConstraintType +{ +public: + /** + * @name Constructor / Destructor + */ + ///@{ + + /** + * @brief Constructor for WellControls Objects. + * @param[in] name the name of this instantiation of WellControls in the repository + * @param[in] parent the parent group of this instantiation of WellControls + */ + explicit ProductionConstraint( string const & name, dataRepository::Group * const parent ); + + /** + * @brief Default destructor. + */ + ~ProductionConstraint() override; + + /** + * @brief Deleted default constructor. + */ + ProductionConstraint() = delete; + + /** + * @brief Deleted copy constructor. + */ + ProductionConstraint( ProductionConstraint const & ) = delete; + + /** + * @brief Deleted move constructor. + */ + ProductionConstraint( ProductionConstraint && ) = delete; + + /** + * @brief Deleted assignment operator. + * @return a reference to a constraint object + */ + ProductionConstraint & operator=( ProductionConstraint const & ) = delete; + + /** + * @brief Deleted move operator. + * @return a reference to a constraint object + */ + ProductionConstraint & operator=( ProductionConstraint && ) = delete; + + ///@} + + /** + * @brief name of the node manager in the object catalog + * @return string that contains the catalog name to generate a new Constraint object through the object catalog. + */ + static string catalogName() + { + return "Production"+ConstraintType::catalogName(); + } + virtual string getCatalogName() const override { return catalogName(); } +protected: + + virtual void postInputInitialization() override; + + static bool isViolated( const real64 & currentValue, const real64 & constraintValue ) + { return currentValue < constraintValue; } +}; + + +} //namespace geos + +#endif //GEOS_PHYSICSSOLVERS_FLUIDFLOW_WELLS_WELLPRODUCTIONCONSTRAINT_HPP diff --git a/src/coreComponents/physicsSolvers/fluidFlow/wells/WellSolverBase.cpp b/src/coreComponents/physicsSolvers/fluidFlow/wells/WellSolverBase.cpp index 060ff895e73..830833212b2 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/wells/WellSolverBase.cpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/wells/WellSolverBase.cpp @@ -19,7 +19,9 @@ #include "WellSolverBase.hpp" +#include "dataRepository/Group.hpp" #include "mesh/DomainPartition.hpp" +#include "mesh/MeshBody.hpp" #include "mesh/PerforationFields.hpp" #include "mesh/WellElementRegion.hpp" #include "mesh/WellElementSubRegion.hpp" @@ -36,7 +38,7 @@ using namespace fields; WellSolverBase::WellSolverBase( string const & name, Group * const parent ) - : PhysicsSolverBase( name, parent ), + : WellControls( name, parent ), m_numPhases( 0 ), m_numComponents( 0 ), m_numDofPerWellElement( 0 ), @@ -44,6 +46,7 @@ WellSolverBase::WellSolverBase( string const & name, m_isThermal( 0 ), m_ratesOutputDir( joinPath( OutputBase::getOutputDirectory(), name + "_rates" ) ), m_keepVariablesConstantDuringInitStep( false ) + { registerWrapper( viewKeyStruct::isThermalString(), &m_isThermal ). setApplyDefaultValue( 0 ). @@ -63,40 +66,41 @@ WellSolverBase::WellSolverBase( string const & name, setInputFlag( dataRepository::InputFlags::OPTIONAL ). setDescription( "Choose time step to honor rates/bhp tables time intervals" ); + addLogLevel< logInfo::WellControl >(); } Group * WellSolverBase::createChild( string const & childKey, string const & childName ) { + Group * baseChild = WellControls::createChild( childKey, childName ); + if( baseChild != nullptr ) + { + return baseChild; + } static std::set< string > const childTypes = { - keys::wellControls, + //keys::wellControls, PhysicsSolverBase::groupKeyStruct::linearSolverParametersString(), PhysicsSolverBase::groupKeyStruct::nonlinearSolverParametersString(), }; GEOS_ERROR_IF( childTypes.count( childKey ) == 0, CatalogInterface::unknownTypeError( childKey, getDataContext(), childTypes ), getDataContext() ); - if( childKey == keys::wellControls ) - { - return ®isterGroup< WellControls >( childName ); - } - else - { - PhysicsSolverBase::createChild( childKey, childName ); - return nullptr; - } + + PhysicsSolverBase::createChild( childKey, childName ); + return nullptr; + } void WellSolverBase::expandObjectCatalogs() { - createChild( keys::wellControls, keys::wellControls ); + //createChild( keys::wellControls, keys::wellControls ); } WellSolverBase::~WellSolverBase() = default; void WellSolverBase::postInputInitialization() { - PhysicsSolverBase::postInputInitialization(); + WellControls::postInputInitialization(); // 1. Set key dimensions of the problem m_numDofPerWellElement = m_isThermal ? m_numComponents + 2 : m_numComponents + 1; // 1 pressure connectionRate + temp if thermal @@ -151,9 +155,10 @@ void WellSolverBase::initializePostSubGroups() { DomainPartition & domain = this->getGroupByPath< DomainPartition >( "/Problem/domain" ); FunctionManager & functionManager = FunctionManager::getInstance(); - forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&] ( string const &, - MeshLevel & mesh, - string_array const & regionNames ) + Group & meshBodies = domain.getMeshBodies(); + forDiscretizationOnMeshTargets( meshBodies, [&] ( string const &, + MeshLevel & mesh, + string_array const & regionNames ) { ElementRegionManager & elemManager = mesh.getElemManager(); elemManager.forElementSubRegions< WellElementSubRegion >( regionNames, @@ -208,95 +213,67 @@ void WellSolverBase::setupDofs( DomainPartition const & domain, DofManager::Connector::Node ); } -void WellSolverBase::setPerforationStatus( real64 const & time_n, DomainPartition & domain ) + + +void WellSolverBase::selectWellConstraint( real64 const & time_n, + real64 const & dt, + const integer coupledIterationNumber, + DomainPartition & domain ) { - FunctionManager & functionManager = FunctionManager::getInstance(); + GEOS_MARK_FUNCTION; + GEOS_UNUSED_VAR( dt ); + GEOS_UNUSED_VAR( coupledIterationNumber ); - // Set well element/perf status - forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&] ( string const &, - MeshLevel & mesh, - string_array const & regionNames ) + forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const & meshBodyName, + MeshLevel & meshLevel, + string_array const & regionNames ) { - - ElementRegionManager & elemManager = mesh.getElemManager(); - elemManager.forElementSubRegions< WellElementSubRegion >( regionNames, - [&]( localIndex const, - WellElementSubRegion & subRegion ) + GEOS_UNUSED_VAR( meshBodyName ); + ElementRegionManager & elementRegionManager = meshLevel.getElemManager(); + elementRegionManager.forElementRegions< WellElementRegion >( regionNames, + [&]( localIndex const, + WellElementRegion & region ) { + WellElementSubRegion & subRegion = region.getGroup( ElementRegionBase::viewKeyStruct::elementSubRegions() ) + .getGroup< WellElementSubRegion >( region.getSubRegionName() ); WellControls & wellControls = getWellControls( subRegion ); - - // Set perforation status - - PerforationData & perforationData = *subRegion.getPerforationData(); - string_array const & perfStatusTableName = perforationData.getPerfStatusTableName(); - arrayView1d< integer > perfStatus = perforationData.getLocalPerfStatus(); - // for now set to open - for( integer i=0; i( perfStatusTableName[i] ); - perfStatus[i]=PerforationData::PerforationStatus::OPEN; - if( tableFunction->evaluate( &time_n ) < LvArray::NumericLimits< real64 >::epsilon ) + if( !wellControls.getWellState() ) { - perfStatus[i]=PerforationData::PerforationStatus::CLOSED; - } - } - - array1d< localIndex > const perfWellElemIndex = perforationData.getField< fields::perforation::wellElementIndex >(); - // global index local elements (size == subregion.size) - arrayView1d< globalIndex const > globalWellElementIndex = subRegion.getGlobalWellElementIndex(); - - arrayView1d< integer const > const elemGhostRank = subRegion.ghostRank(); - array1d< integer > & currentStatus = subRegion.getWellElementStatus(); - // Local elements - array1d< integer > & localElemStatus = subRegion.getWellLocalElementStatus(); - - integer numLocalElements = subRegion.getNumLocalElements(); - array1d< integer > segStatus( numLocalElements ); + wellControls.setWellState( 1 ); - // Local perforations - for( integer j = 0; j < perforationData.size(); j++ ) - { - localIndex const iwelem = perfWellElemIndex[j]; - if( elemGhostRank[iwelem] < 0 ) - { - if( perfStatus[j] ) - { - segStatus[iwelem] +=1; - } + initializeWell( domain, meshLevel, subRegion, time_n ); } } - // Broadcast segment status so all cores have same well status - subRegion.setElementStatus( segStatus ); - integer numOpenElements = 0; - array1d< integer > const & updatedStatus = subRegion.getWellElementStatus(); - for( integer i=0; i0 ? wellControls.setWellStatus( time_n, WellControls::Status::OPEN ) : wellControls.setWellStatus( time_n, WellControls::Status::CLOSED ); - - // Set local well element status array - for( integer i=0; i( regionNames, [&]( localIndex const, WellElementSubRegion & subRegion ) - { updateSubRegionState( elemManager, subRegion ); } ); + { updateSubRegionState( subRegion ); } ); } ); } @@ -321,13 +298,39 @@ void WellSolverBase::assembleSystem( real64 const time, CRSMatrixView< real64, globalIndex const > const & localMatrix, arrayView1d< real64 > const & localRhs ) { - string const wellDofKey = dofManager.getKey( wellElementDofName()); + + + // selects constraints one of 2 ways + // wellEstimator flag set to 0 => orginal logic rates are computed during update state and constraints are selected every newton + // iteration + // wellEstimator flag > 0 => well esitmator solved for each constraint and then selects the constraint + // => estimator solve only performed first "wellEstimator" iterations + NonlinearSolverParameters const & nonlinearParams = getNonlinearSolverParameters(); + selectWellConstraint( time, dt, nonlinearParams.m_numNewtonIterations, domain ); + + // assemble the accumulation term in the mass balance equations assembleAccumulationTerms( time, dt, domain, dofManager, localMatrix, localRhs ); // then assemble the pressure relations between well elements - assemblePressureRelations( time, dt, domain, dofManager, localMatrix, localRhs ); + //assemblePressureRelations( time, dt, domain, dofManager, localMatrix, localRhs ); + + forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const &, + MeshLevel & mesh, + string_array const & regionNames ) + { + ElementRegionManager & elementRegionManager = mesh.getElemManager(); + elementRegionManager.forElementRegions< WellElementRegion >( regionNames, + [&]( localIndex const, + WellElementRegion & region ) + { + WellElementSubRegion & subRegion = region.getGroup( ElementRegionBase::viewKeyStruct::elementSubRegions() ) + .getGroup< WellElementSubRegion >( region.getSubRegionName() ); + assembleWellConstraintTerms( time, dt, subRegion, dofManager, localMatrix.toViewConstSizes(), localRhs ); + } ); + } ); + // then compute the perforation rates (later assembled by the coupled solver) computePerforationRates( time, dt, domain ); @@ -389,8 +392,15 @@ void WellSolverBase::precomputeData( DomainPartition & domain ) wellElemGravCoef[iwelem] = LvArray::tensorOps::AiBi< 3 >( wellElemLocation[iwelem], gravVector ); } ); + wellControls.forSubGroups< MinimumBHPConstraint, MaximumBHPConstraint >( [&]( auto & constraint ) + { + // set the reference well element where the BHP control is applied + real64 const refElev1 = constraint.getReferenceElevation(); + constraint.setReferenceGravityCoef( refElev1 * gravVector[2] ); + } ); + // set the reference well element where the BHP control is applied - wellControls.setReferenceGravityCoef( refElev * gravVector[2] ); + wellControls.setReferenceGravityCoef( refElev * gravVector[2] ); // tjb remove } ); } ); } diff --git a/src/coreComponents/physicsSolvers/fluidFlow/wells/WellSolverBase.hpp b/src/coreComponents/physicsSolvers/fluidFlow/wells/WellSolverBase.hpp index 04fe58112b4..6df24834262 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/wells/WellSolverBase.hpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/wells/WellSolverBase.hpp @@ -20,13 +20,13 @@ #ifndef GEOS_PHYSICSSOLVERS_FLUIDFLOW_WELLS_WELLSOLVERBASE_HPP_ #define GEOS_PHYSICSSOLVERS_FLUIDFLOW_WELLS_WELLSOLVERBASE_HPP_ -#include "physicsSolvers/PhysicsSolverBase.hpp" - +#include "physicsSolvers/fluidFlow/wells/WellControls.hpp" +#include "dataRepository/Group.hpp" namespace geos { class DomainPartition; -class WellControls; + class WellElementSubRegion; /** @@ -35,7 +35,7 @@ class WellElementSubRegion; * Base class for well solvers. * Provides some common features */ -class WellSolverBase : public PhysicsSolverBase +class WellSolverBase : public WellControls { public: @@ -60,7 +60,7 @@ class WellSolverBase : public PhysicsSolverBase WellSolverBase( WellSolverBase const & ) = delete; /// default move constructor - WellSolverBase( WellSolverBase && ) = default; + WellSolverBase( WellSolverBase && ) = delete; /// deleted assignment operator WellSolverBase & operator=( WellSolverBase const & ) = delete; @@ -128,6 +128,12 @@ class WellSolverBase : public PhysicsSolverBase */ virtual localIndex numFluidPhases() const = 0; + /** + * @brief getter for the well associated to this subRegion + * @param subRegion the well subRegion whose controls are requested + * @return a reference to the well + */ + WellSolverBase & getWell( WellElementSubRegion const & subRegion ); /** * @brief getter for the well controls associated to this well subRegion * @param subRegion the well subRegion whose controls are requested @@ -149,27 +155,63 @@ class WellSolverBase : public PhysicsSolverBase * @param domain the domain */ void setPerforationStatus( real64 const & time_n, DomainPartition & domain ); - + void setPerforationStatus( real64 const & time_n, WellElementSubRegion & subRegion ); /** * @defgroup Solver Interface Functions * * These functions provide the primary interface that is required for derived classes + * The "Well" versions apply to individual well subRegions, whereas the others apply to all wells + */ /**@{*/ virtual void registerDataOnMesh( Group & meshBodies ) override; + virtual real64 + calculateWellResidualNorm( real64 const & GEOS_UNUSED_PARAM( time_n ), + real64 const & GEOS_UNUSED_PARAM( dt ), + WellElementSubRegion const & GEOS_UNUSED_PARAM( subRegion ), + DofManager const & GEOS_UNUSED_PARAM( dofManager ), + arrayView1d< real64 const > const & GEOS_UNUSED_PARAM( localRhs ) ) = 0; + + virtual real64 + scalingForWellSystemSolution( ElementSubRegionBase & GEOS_UNUSED_PARAM( subRegion ), + DofManager const & GEOS_UNUSED_PARAM( dofManager ), + arrayView1d< real64 const > const & GEOS_UNUSED_PARAM( localSolution ) ) = 0; + + virtual bool + checkWellSystemSolution( ElementSubRegionBase & GEOS_UNUSED_PARAM( subRegion ), + DofManager const & GEOS_UNUSED_PARAM( dofManager ), + arrayView1d< real64 const > const & GEOS_UNUSED_PARAM( localSolution ), + real64 const GEOS_UNUSED_PARAM( scalingFactor ) ) = 0; + virtual void + applyWellSystemSolution( DofManager const & GEOS_UNUSED_PARAM( dofManager ), + arrayView1d< real64 const > const & GEOS_UNUSED_PARAM( localSolution ), + real64 const GEOS_UNUSED_PARAM( scalingFactor ), + real64 const GEOS_UNUSED_PARAM( dt ), + DomainPartition & GEOS_UNUSED_PARAM( domain ), + MeshLevel & GEOS_UNUSED_PARAM( mesh ), + WellElementSubRegion & GEOS_UNUSED_PARAM( subRegion ) ) = 0; + + /** + * @brief function to set the next time step size + * @param[in] currentTime the current time + * @param[in] currentDt the current time step size + * @param[in] domain the domain object + * @return the prescribed time step size + */ + virtual real64 setNextDt( real64 const & currentTime, + real64 const & currentDt, + DomainPartition & domain ) override; virtual void setupDofs( DomainPartition const & domain, DofManager & dofManager ) const override; - virtual void implicitStepSetup( real64 const & time_n, - real64 const & dt, - DomainPartition & domain ) override; virtual void implicitStepComplete( real64 const & GEOS_UNUSED_PARAM( time_n ), real64 const & GEOS_UNUSED_PARAM( dt ), DomainPartition & GEOS_UNUSED_PARAM( domain ) ) override {} + virtual void applyBoundaryConditions( real64 const GEOS_UNUSED_PARAM( time_n ), real64 const GEOS_UNUSED_PARAM( dt ), DomainPartition & GEOS_UNUSED_PARAM( domain ), @@ -177,17 +219,23 @@ class WellSolverBase : public PhysicsSolverBase CRSMatrixView< real64, globalIndex const > const & GEOS_UNUSED_PARAM( localMatrix ), arrayView1d< real64 > const & GEOS_UNUSED_PARAM( localRhs ) ) override {} + /** + * @brief Selects the active well constraint based on current conditions + * @param[in] currentTime the current time + * @param[in] currentDt the current time step size + * @param[in] coupledIterationNumber the current coupled iteration number + * @param[in] domain the domain object + * @return the prescribed time step size + */ + void selectWellConstraint( real64 const & time_n, + real64 const & dt, + integer const coupledIterationNumber, + DomainPartition & domain ); /**@}*/ /** - * @brief function to assemble the linear system matrix and rhs - * @param time the time at the beginning of the step - * @param dt the desired timestep - * @param domain the domain partition - * @param dofManager degree-of-freedom manager associated with the linear system - * @param matrix the system matrix - * @param rhs the system right-hand side vector + * @copydoc PhysicsSolverBase::assembleSystem() */ virtual void assembleSystem( real64 const time, real64 const dt, @@ -197,14 +245,15 @@ class WellSolverBase : public PhysicsSolverBase arrayView1d< real64 > const & localRhs ) override; /** - * @brief assembles the flux terms for all connections between well elements + * @brief assembles the flux terms for individual well for all connections between well elements * @param time_n previous time value * @param dt time step - * @param domain the physical domain object + * @param subRegion the well subregion containing all the primary and dependent fields * @param dofManager degree-of-freedom manager associated with the linear system * @param matrix the system matrix * @param rhs the system right-hand side vector */ + virtual void assembleFluxTerms( real64 const & time_n, real64 const & dt, DomainPartition & domain, @@ -226,34 +275,38 @@ class WellSolverBase : public PhysicsSolverBase CRSMatrixView< real64, globalIndex const > const & localMatrix, arrayView1d< real64 > const & localRhs ) = 0; + + /** - * @brief assembles the pressure relations at all connections between well elements except at the well head - * @param time_n time at the beginning of the time step - * @param dt the time step size - * @param domain the physical domain object - * @param dofManager degree-of-freedom manager associated with the linear system - * @param matrix the system matrix - * @param rhs the system right-hand side vector + * @brief Recompute all dependent quantities from primary variables (including constitutive + * models) + * @param elemManager the element region manager + * @param subRegion the well subRegion containing the well elements and their associated fields */ - virtual void assemblePressureRelations( real64 const & time_n, - real64 const & dt, - DomainPartition const & domain, - DofManager const & dofManager, - CRSMatrixView< real64, globalIndex const > const & localMatrix, - arrayView1d< real64 > const & localRhs ) = 0; - + virtual real64 updateWellState( MeshBody const & meshBody, + ElementRegionManager const & elemManager, + WellElementSubRegion & subRegion ) = 0; /** - * @brief Recompute all dependent quantities from primary variables (including constitutive models) + * @brief Recompute all dependent quantities from primary variables (including constitutive + * models) * @param domain the domain containing the mesh and fields */ virtual void updateState( DomainPartition & domain ) override; /** - * @brief Recompute all dependent quantities from primary variables (including constitutive models) - * @param elemManager the elemManager containing the well - * @param subRegion the well subRegion containing the well elements and their associated fields + * @brief Initialize all the primary and secondary variables in all the wells + * @param domain the domain containing the well manager to access individual wells */ - virtual real64 updateSubRegionState( ElementRegionManager const & elemManager, WellElementSubRegion & subRegion ) = 0; + virtual void initializeWells( DomainPartition & domain, real64 const & time_n ) = 0; + + /** + * @brief Recompute all dependent quantities from primary variables (including constitutive + * models) + * @param elemManager the element region manager + * fields + */ + virtual real64 updateSubRegionState( WellElementSubRegion & subRegion ) = 0; + /** * @brief Recompute the perforation rates for all the wells @@ -263,21 +316,14 @@ class WellSolverBase : public PhysicsSolverBase real64 const & dt, DomainPartition & domain ) = 0; - /** - * @brief function to set the next time step size - * @param[in] currentTime the current time - * @param[in] currentDt the current time step size - * @param[in] domain the domain object - * @return the prescribed time step size - */ - virtual real64 setNextDt( real64 const & currentTime, - real64 const & currentDt, - DomainPartition & domain ) override; /** - * @brief Utility function to keep the well variables during a time step (used in poromechanics simulations) - * @param[in] keepVariablesConstantDuringInitStep flag to tell the solver to freeze its primary variables during a time step - * @detail This function is meant to be called by a specific task before/after the initialization step + * @brief Utility function to keep the well variables during a time step (used in + * poromechanics simulations) + * @param[in] keepVariablesConstantDuringInitStep flag to tell the solver to freeze its + * primary variables during a time step + * @detail This function is meant to be called by a specific task before/after the + * initialization step */ void setKeepVariablesConstantDuringInitStep( bool const keepVariablesConstantDuringInitStep ) { m_keepVariablesConstantDuringInitStep = keepVariablesConstantDuringInitStep; } @@ -287,6 +333,8 @@ class WellSolverBase : public PhysicsSolverBase static constexpr char const * isThermalString() { return "isThermal"; } static constexpr char const * writeCSVFlagString() { return "writeCSV"; } static constexpr char const * timeStepFromTablesFlagString() { return "timeStepFromTables"; } + /// @return string for the targetRegions wrapper + static constexpr char const * targetRegionsString() { return "targetRegions"; } static constexpr char const * fluidNamesString() { return "fluidNames"; } }; @@ -307,11 +355,6 @@ class WellSolverBase : public PhysicsSolverBase virtual void initializePostSubGroups() override; - /** - * @brief Initialize all the primary and secondary variables in all the wells - * @param domain the domain containing the well manager to access individual wells - */ - virtual void initializeWells( DomainPartition & domain, real64 const & time_n ) = 0; /** * @brief Make sure that the well constraints are compatible @@ -327,6 +370,8 @@ class WellSolverBase : public PhysicsSolverBase real64 const & dt, DomainPartition & domain ) = 0; + + /// name of the flow solver string m_flowSolverName; @@ -357,6 +402,10 @@ class WellSolverBase : public PhysicsSolverBase /// name of the fluid constitutive model used as a reference for component/phase description string m_referenceFluidModelName; + + /// flag to use the estimator + integer m_estimateSolution; + }; } diff --git a/src/coreComponents/physicsSolvers/fluidFlow/wells/WellSolverBaseFields.hpp b/src/coreComponents/physicsSolvers/fluidFlow/wells/WellSolverBaseFields.hpp index b9dfebc799b..1c4b639f4f7 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/wells/WellSolverBaseFields.hpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/wells/WellSolverBaseFields.hpp @@ -31,65 +31,7 @@ namespace fields { namespace well -{ - -DECLARE_FIELD( pressure, - "pressure", - array1d< real64 >, - 0, - LEVEL_0, - WRITE_AND_READ, - "Pressure" ); - -DECLARE_FIELD( pressure_n, - "pressure_n", - array1d< real64 >, - 0, - NOPLOT, - WRITE_AND_READ, - "Pressure at the previous converged time step" ); - -DECLARE_FIELD( temperature, - "temperature", - array1d< real64 >, - 0, - LEVEL_0, - WRITE_AND_READ, - "Temperature" ); - -DECLARE_FIELD( temperature_n, - "temperature_n", - array1d< real64 >, - 0, - NOPLOT, - WRITE_AND_READ, - "Temperature at the previous converged time step" ); - -DECLARE_FIELD( gravityCoefficient, - "gravityCoefficient", - array1d< real64 >, - 0, - NOPLOT, - WRITE_AND_READ, - "Gravity coefficient (dot product of gravity acceleration by gravity vector)" ); - -DECLARE_FIELD( pressureScalingFactor, - "pressureScalingFactor", - array1d< real64 >, - 1, - NOPLOT, - NO_WRITE, - "Scaling factors for pressure" ); - -DECLARE_FIELD( temperatureScalingFactor, - "temperatureScalingFactor", - array1d< real64 >, - 1, - NOPLOT, - NO_WRITE, - "Scaling factors for temperature" ); - -} +{} } diff --git a/src/coreComponents/physicsSolvers/fluidFlow/wells/WellVolumeRateConstraint.cpp b/src/coreComponents/physicsSolvers/fluidFlow/wells/WellVolumeRateConstraint.cpp new file mode 100644 index 00000000000..e675af7459e --- /dev/null +++ b/src/coreComponents/physicsSolvers/fluidFlow/wells/WellVolumeRateConstraint.cpp @@ -0,0 +1,73 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2016-2024 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2024 TotalEnergies + * Copyright (c) 2018-2024 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2023-2024 Chevron + * Copyright (c) 2019- GEOS/GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +/* + * @file WellVolumeRateConstraint.cpp + */ + +#include "LogLevelsInfo.hpp" +#include "WellVolumeRateConstraint.hpp" +#include "WellConstants.hpp" +#include "dataRepository/InputFlags.hpp" +#include "functions/FunctionManager.hpp" + +namespace geos +{ + +using namespace dataRepository; + +VolumeRateConstraint::VolumeRateConstraint( string const & name, Group * const parent ) + : WellConstraintBase( name, parent ) +{ + this->setInputFlags( InputFlags::OPTIONAL_NONUNIQUE ); + + this->registerWrapper( viewKeyStruct::volumeRateString(), &this->m_constraintValue ). + setDefaultValue( 0.0 ). + setInputFlag( InputFlags::OPTIONAL ). + setRestartFlags( RestartFlags::WRITE_AND_READ ). + setDescription( "Volumetric rate (if useSurfaceConditions: [surface m^3/s]; else [reservoir m^3/s])" ); + +} + +VolumeRateConstraint::~VolumeRateConstraint() +{} + + +void VolumeRateConstraint::postInputInitialization() +{ + // Validate table options + WellConstraintBase::postInputInitialization(); + + // check constraint value + GEOS_THROW_IF( m_constraintValue < 0, + getWrapperDataContext( viewKeyStruct::volumeRateString() ) << ": Target value is negative", + InputError ); + + GEOS_THROW_IF ((m_constraintValue <= 0.0 && m_constraintScheduleTableName.empty()), + getName() << " " << getDataContext() << ": You need to specify a volume rate constraint. \n" << + "The rate constraint can be specified using " << + "either " << viewKeyStruct::volumeRateString() << + " or " << WellConstraintBase::viewKeyStruct::constraintScheduleTableNameString(), + InputError ); +} + +bool VolumeRateConstraint::checkViolation( WellConstraintBase const & currentConstraint, real64 const & currentTime ) const +{ + real64 const currentValue = currentConstraint.totalVolumeRate(); + real64 const constraintValue = this->getConstraintValue( currentTime ); + return ( LvArray::math::abs( currentValue ) > LvArray::math::abs( constraintValue ) ); +} + +} //namespace geos diff --git a/src/coreComponents/physicsSolvers/fluidFlow/wells/WellVolumeRateConstraint.hpp b/src/coreComponents/physicsSolvers/fluidFlow/wells/WellVolumeRateConstraint.hpp new file mode 100644 index 00000000000..1ed14597ca3 --- /dev/null +++ b/src/coreComponents/physicsSolvers/fluidFlow/wells/WellVolumeRateConstraint.hpp @@ -0,0 +1,124 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2016-2024 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2024 TotalEnergies + * Copyright (c) 2018-2024 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2023-2024 Chevron + * Copyright (c) 2019- GEOS/GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +/* + * @file WellVolumeRateConstraints.hpp + */ + + +#ifndef GEOS_PHYSICSSOLVERS_FLUIDFLOW_WELLS_WELLTOTALVOLRATECONSTRAINTS_HPP +#define GEOS_PHYSICSSOLVERS_FLUIDFLOW_WELLS_WELLTOTALVOLRATECONSTRAINTS_HPP + +#include "common/format/EnumStrings.hpp" +#include "dataRepository/Group.hpp" +#include "functions/TableFunction.hpp" +#include "WellConstraintsBase.hpp" +namespace geos +{ + + + +/** + * @class VolumeRateConstraint + * @brief This class describes a volume rate constraint used to control a well. + */ + +class VolumeRateConstraint : public WellConstraintBase +{ +public: + + /** + * @name Constructor / Destructor + */ + ///@{ + + /** + * @brief Constructor for WellControls Objects. + * @param[in] name the name of this instantiation of WellControls in the repository + * @param[in] parent the parent group of this instantiation of WellControls + */ + explicit VolumeRateConstraint( string const & name, dataRepository::Group * const parent ); + + + /** + * @brief Default destructor. + */ + ~VolumeRateConstraint() override; + + /** + * @brief Deleted default constructor. + */ + VolumeRateConstraint() = delete; + + /** + * @brief Deleted copy constructor. + */ + VolumeRateConstraint( VolumeRateConstraint const & ) = delete; + + /** + * @brief Deleted move constructor. + */ + VolumeRateConstraint( VolumeRateConstraint && ) = delete; + + /** + * @brief Deleted assignment operator. + * @return a reference to a constraint object + */ + VolumeRateConstraint & operator=( VolumeRateConstraint const & ) = delete; + + /** + * @brief Deleted move operator. + * @return a reference to a constraint object + */ + VolumeRateConstraint & operator=( VolumeRateConstraint && ) = delete; + + /** + * @brief name of the node manager in the object catalog + * @return string that contains the catalog name to generate a new Constraint object through the object catalog. + */ + static string catalogName() + { + return "VolumeRateConstraint"; + } + ///@} + /** + * @brief Struct to serve as a container for variable strings and keys. + * @struct viewKeyStruct + */ + struct viewKeyStruct + { + /// String key for the volume rate + static constexpr char const * volumeRateString() { return "volumeRate"; } + }; + /** + * @name Getters / Setters + */ + ///@{ + + // Temp interface - tjb + virtual ConstraintTypeId getControl() const override { return ConstraintTypeId::TOTALVOLRATE; }; + ///@} + + virtual bool checkViolation( WellConstraintBase const & currentConstraint, real64 const & currentTime ) const override; +protected: + + virtual void postInputInitialization() override; + +}; + + +} //namespace geos + +#endif //GEOS_PHYSICSSOLVERS_FLUIDFLOW_WELLS_WELLCONSTRAINT_HPP diff --git a/src/coreComponents/physicsSolvers/fluidFlow/wells/kernels/CompositionalMultiphaseWellConstraintKernels.hpp b/src/coreComponents/physicsSolvers/fluidFlow/wells/kernels/CompositionalMultiphaseWellConstraintKernels.hpp new file mode 100644 index 00000000000..f972500e9a3 --- /dev/null +++ b/src/coreComponents/physicsSolvers/fluidFlow/wells/kernels/CompositionalMultiphaseWellConstraintKernels.hpp @@ -0,0 +1,459 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2016-2024 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2024 TotalEnergies + * Copyright (c) 2018-2024 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2023-2024 Chevron + * Copyright (c) 2019- GEOS/GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +/** + * @file CompositionalMultiphaseWellConstraintKernels.hpp + */ + +#ifndef GEOS_PHYSICSSOLVERS_FLUIDFLOW_WELLS_WELLCONSTRAINTKERNELS_HPP +#define GEOS_PHYSICSSOLVERS_FLUIDFLOW_WELLS_WELLCONSTRAINTKERNELS_HPP + +#include "codingUtilities/Utilities.hpp" +#include "common/DataLayouts.hpp" +#include "constitutive/fluid/multifluid/MultiFluidBase.hpp" +#include "constitutive/fluid/multifluid/MultiFluidFields.hpp" + + +#include "physicsSolvers/fluidFlow/wells/WellControls.hpp" +#include "physicsSolvers/fluidFlow/wells/WellBHPConstraints.hpp" +#include "physicsSolvers/fluidFlow/wells/WellVolumeRateConstraint.hpp" +#include "physicsSolvers/fluidFlow/wells/WellPhaseVolumeRateConstraint.hpp" +#include "physicsSolvers/fluidFlow/wells/WellMassRateConstraint.hpp" +namespace geos +{ + +namespace wellConstraintKernels +{ + +/******************************** ControlEquationHelper ********************************/ +//template< integer NC, integer IS_THERMAL, typname S, typename T > +//struct ConstraintHelper< NC, IS_THERMAL > {}; + +template< integer NC, integer IS_THERMAL, typename CONSTRAINT = BHPConstraint< BHPConstraintTypeId::MIN > > +struct ConstraintHelper +{ + template< BHPConstraintTypeId I > + static void assembleConstraintEquation( real64 const & time_n, + WellControls & wellControls, + BHPConstraint< I > & constraint, + WellElementSubRegion const & subRegion, + string const & wellDofKey, + localIndex const & rankOffset, + CRSMatrixView< real64, globalIndex const > const & localMatrix, + arrayView1d< real64 > const & localRhs ) + { + // subRegion data + localIndex const iwelemRef = subRegion.getTopWellElementIndex(); + arrayView1d< globalIndex const > const & wellElemDofNumber = subRegion.getReference< array1d< globalIndex > >( wellDofKey ); + arrayView1d< real64 const > const & pres = subRegion.getField< fields::well::pressure >(); + arrayView1d< real64 const > const & totalMassDens = subRegion.getField< fields::well::totalMassDensity >(); + arrayView2d< real64 const, constitutive::multifluid::USD_FLUID > const & dTotalMassDens = subRegion.getField< fields::well::dTotalMassDensity >(); + arrayView1d< real64 const > const wellElemGravCoef = subRegion.getField< fields::well::gravityCoefficient >(); + + // setup row/column indices for constraint equation + using COFFSET_WJ = compositionalMultiphaseWellKernels::ColOffset_WellJac< NC, IS_THERMAL >; + using WJ_ROFFSET = compositionalMultiphaseWellKernels::RowOffset_WellJac< NC, IS_THERMAL >; + using Deriv = constitutive::multifluid::DerivativeOffset; + + localIndex const eqnRowIndex = wellElemDofNumber[iwelemRef] + WJ_ROFFSET::CONTROL - rankOffset; + globalIndex dofColIndices[COFFSET_WJ::nDer]{}; + for( integer ic = 0; ic < COFFSET_WJ::nDer; ++ic ) + { + dofColIndices[ ic ] = wellElemDofNumber[iwelemRef] + ic; + } + + // constraint data + real64 const & targetBHP = constraint.getConstraintValue( time_n ); + real64 const & refGravCoef = constraint.getReferenceGravityCoef(); + + // current constraint value + real64 const & currentBHP = + wellControls.getReference< real64 >( CompositionalMultiphaseWell::viewKeyStruct::currentBHPString() ); + + // residual + real64 controlEqn = currentBHP - targetBHP; + + // setup Jacobian terms + real64 dControlEqn[NC+2+IS_THERMAL]{}; + + // bring everything back to host, capture the scalars by reference + forAll< serialPolicy >( 1, [pres, + totalMassDens, + dTotalMassDens, + wellElemGravCoef, + &dControlEqn, + &iwelemRef, + localRhs, + controlEqn, + eqnRowIndex, + dofColIndices, + localMatrix, + &refGravCoef] ( localIndex const ) + { + real64 const diffGravCoef = refGravCoef - wellElemGravCoef[iwelemRef]; + dControlEqn[COFFSET_WJ::dP] = 1 + dTotalMassDens[iwelemRef][Deriv::dP] * diffGravCoef; + for( integer ic = 0; ic < NC; ++ic ) + { + dControlEqn[COFFSET_WJ::dC+ic] = dTotalMassDens[iwelemRef][Deriv::dC+ic] * diffGravCoef; + } + if constexpr ( IS_THERMAL ) + { + dControlEqn[COFFSET_WJ::dT] = dTotalMassDens[iwelemRef][Deriv::dT] * diffGravCoef; + } + // add solver matrices + localRhs[eqnRowIndex] += controlEqn; + localMatrix.addToRowBinarySearchUnsorted< serialAtomic >( eqnRowIndex, + dofColIndices, + dControlEqn, + COFFSET_WJ::nDer ); + } ); + + + } + + + template< template< typename U > class T, typename U=PhaseVolumeRateConstraint > + static void assembleConstraintEquation( real64 const & time_n, + WellControls & wellControls, + T< PhaseVolumeRateConstraint > & constraint, + WellElementSubRegion const & subRegion, + string const & wellDofKey, + localIndex const & rankOffset, + CRSMatrixView< real64, globalIndex const > const & localMatrix, + arrayView1d< real64 > const & localRhs ) + { + // subRegion data + + localIndex const iwelemRef = subRegion.getTopWellElementIndex(); + arrayView1d< globalIndex const > const & wellElemDofNumber = subRegion.getReference< array1d< globalIndex > >( wellDofKey ); + arrayView1d< real64 const > const & connRate = subRegion.getField< fields::well::connectionRate >(); + arrayView2d< real64 const, compflow::USD_COMP > const & compFrac = subRegion.getField< fields::well::globalCompFraction >(); + arrayView3d< real64 const, compflow::USD_COMP_DC > const & dCompFrac_dCompDens = subRegion.getField< fields::well::dGlobalCompFraction_dGlobalCompDensity >(); + + // setup row/column indices for constraint equation + using COFFSET_WJ = compositionalMultiphaseWellKernels::ColOffset_WellJac< NC, IS_THERMAL >; + using WJ_ROFFSET = compositionalMultiphaseWellKernels::RowOffset_WellJac< NC, IS_THERMAL >; + using Deriv = constitutive::multifluid::DerivativeOffset; + + localIndex const eqnRowIndex = wellElemDofNumber[iwelemRef] + WJ_ROFFSET::CONTROL - rankOffset; + globalIndex dofColIndices[COFFSET_WJ::nDer]{}; + for( integer ic = 0; ic < COFFSET_WJ::nDer; ++ic ) + { + dofColIndices[ ic ] = wellElemDofNumber[iwelemRef] + ic; + } + + // fluid data + constitutive::MultiFluidBase & fluidSeparator = wellControls.getMultiFluidSeparator(); + + arrayView3d< real64 const, constitutive::multifluid::USD_PHASE > const & phaseFrac = fluidSeparator.phaseFraction(); + arrayView4d< real64 const, constitutive::multifluid::USD_PHASE_DC > const & dPhaseFrac = fluidSeparator.dPhaseFraction(); + arrayView3d< real64 const, constitutive::multifluid::USD_PHASE > const & phaseDens = fluidSeparator.phaseDensity(); + arrayView4d< real64 const, constitutive::multifluid::USD_PHASE_DC > const & dPhaseDens = fluidSeparator.dPhaseDensity(); + + // constraint data + integer ip = getPhaseIndexFromFluidModel( fluidSeparator, constraint.getPhaseName()); + real64 const & targetPhaseRate = constraint.getConstraintValue( time_n ); + + // current constraint value + arrayView1d< real64 > const & currentPhaseVolRate = + wellControls.getReference< array1d< real64 > >( CompositionalMultiphaseWell::viewKeyStruct::currentPhaseVolRateString() ); + integer const useSurfaceConditions = wellControls.useSurfaceConditions(); + + // residual + real64 controlEqn = currentPhaseVolRate[ip] - targetPhaseRate; + + // setup Jacobian terms + real64 dControlEqn[NC+2+IS_THERMAL]{}; + + // bring everything back to host, capture the scalars by reference + forAll< serialPolicy >( 1, [&ip, + connRate, + phaseDens, + dPhaseDens, + phaseFrac, + dPhaseFrac, + compFrac, + dCompFrac_dCompDens, + &dControlEqn, + &useSurfaceConditions, + localRhs, + controlEqn, + eqnRowIndex, + dofColIndices, + localMatrix, + &iwelemRef] ( localIndex const ) + { + // skip the rest of this function if phase ip is absent + bool const phaseExists = (phaseFrac[iwelemRef][0][ip] > 0); + if( phaseExists ) + { + stackArray1d< real64, NC > work( NC ); + real64 const currentTotalRate = connRate[iwelemRef]; + + real64 const phaseDensInv = 1.0 / phaseDens[iwelemRef][0][ip]; + real64 const phaseFracTimesPhaseDensInv = phaseFrac[iwelemRef][0][ip] * phaseDensInv; + real64 const dPhaseFracTimesPhaseDensInv_dPres = dPhaseFrac[iwelemRef][0][ip][Deriv::dP] * phaseDensInv + - dPhaseDens[iwelemRef][0][ip][Deriv::dP] * phaseFracTimesPhaseDensInv * phaseDensInv; + + // divide the total mass/molar rate by the (phase density * phase fraction) to get the phase volumetric rate + dControlEqn[COFFSET_WJ::dP] = ( useSurfaceConditions == 0 ) * currentTotalRate * dPhaseFracTimesPhaseDensInv_dPres; + dControlEqn[COFFSET_WJ::dQ] = phaseFracTimesPhaseDensInv; + if constexpr (IS_THERMAL ) + { + real64 const dPhaseFracTimesPhaseDensInv_dTemp = dPhaseFrac[iwelemRef][0][ip][Deriv::dT] * phaseDensInv + - dPhaseDens[iwelemRef][0][ip][Deriv::dT] * phaseFracTimesPhaseDensInv * phaseDensInv; + dControlEqn[COFFSET_WJ::dT] = ( useSurfaceConditions == 0 ) * currentTotalRate * dPhaseFracTimesPhaseDensInv_dTemp; + } + + for( integer ic = 0; ic < NC; ++ic ) + { + dControlEqn[COFFSET_WJ::dC+ic] = -phaseFracTimesPhaseDensInv * dPhaseDens[iwelemRef][0][ip][Deriv::dC+ic] * phaseDensInv; + dControlEqn[COFFSET_WJ::dC+ic] += dPhaseFrac[iwelemRef][0][ip][Deriv::dC+ic] * phaseDensInv; + dControlEqn[COFFSET_WJ::dC+ic] *= currentTotalRate; + } + applyChainRuleInPlace( NC, dCompFrac_dCompDens[iwelemRef], &dControlEqn[COFFSET_WJ::dC], work.data() ); + // add solver matrices + localRhs[eqnRowIndex] += controlEqn; + localMatrix.addToRowBinarySearchUnsorted< serialAtomic >( eqnRowIndex, + dofColIndices, + dControlEqn, + COFFSET_WJ::nDer ); + } + } ); + + + } + + template< template< typename U > class T, typename U=VolumeRateConstraint > + static void assembleConstraintEquation( real64 const & time_n, + WellControls & wellControls, + T< VolumeRateConstraint > & constraint, + WellElementSubRegion const & subRegion, + string const & wellDofKey, + localIndex const & rankOffset, + CRSMatrixView< real64, globalIndex const > const & localMatrix, + arrayView1d< real64 > const & localRhs ) + { + // subRegion data + + localIndex const iwelemRef = subRegion.getTopWellElementIndex(); + arrayView1d< globalIndex const > const & wellElemDofNumber = subRegion.getReference< array1d< globalIndex > >( wellDofKey ); + arrayView1d< real64 const > const & connRate = subRegion.getField< fields::well::connectionRate >(); + arrayView2d< real64 const, compflow::USD_COMP > const & compFrac = subRegion.getField< fields::well::globalCompFraction >(); + arrayView3d< real64 const, compflow::USD_COMP_DC > const & dCompFrac_dCompDens = subRegion.getField< fields::well::dGlobalCompFraction_dGlobalCompDensity >(); + + // setup row/column indices for constraint equation + using COFFSET_WJ = compositionalMultiphaseWellKernels::ColOffset_WellJac< NC, IS_THERMAL >; + using WJ_ROFFSET = compositionalMultiphaseWellKernels::RowOffset_WellJac< NC, IS_THERMAL >; + using Deriv = constitutive::multifluid::DerivativeOffset; + + localIndex const eqnRowIndex = wellElemDofNumber[iwelemRef] + WJ_ROFFSET::CONTROL - rankOffset; + globalIndex dofColIndices[COFFSET_WJ::nDer]{}; + for( integer ic = 0; ic < COFFSET_WJ::nDer; ++ic ) + { + dofColIndices[ ic ] = wellElemDofNumber[iwelemRef] + ic; + } + + // fluid data + constitutive::MultiFluidBase & fluidSeparator = wellControls.getMultiFluidSeparator(); + + arrayView2d< real64 const, constitutive::multifluid::USD_FLUID > const & totalDens = fluidSeparator.totalDensity(); + arrayView3d< real64 const, constitutive::multifluid::USD_FLUID_DC > const & dTotalDens = fluidSeparator.dTotalDensity(); + + // constraint data + real64 const & targetTotalVolRate = constraint.getConstraintValue( time_n ); + + // current constraint value + real64 const & currentTotalVolRate = + wellControls.getReference< real64 >( CompositionalMultiphaseWell::viewKeyStruct::currentTotalVolRateString() ); + integer const useSurfaceConditions = wellControls.useSurfaceConditions(); + + // residual + real64 controlEqn = currentTotalVolRate - targetTotalVolRate; + + // setup Jacobian terms + real64 dControlEqn[NC+2+IS_THERMAL]{}; + + // bring everything back to host, capture the scalars by reference + forAll< serialPolicy >( 1, [connRate, + totalDens, + dTotalDens, + compFrac, + dCompFrac_dCompDens, + &dControlEqn, + &useSurfaceConditions, + localRhs, + controlEqn, + eqnRowIndex, + dofColIndices, + localMatrix, + &iwelemRef] ( localIndex const ) + { + stackArray1d< real64, NC > work( NC ); + + real64 const currentTotalRate = connRate[iwelemRef]; + + // compute the inverse of the total density and derivatives + + real64 const totalDensInv = 1.0 / totalDens[iwelemRef][0]; + + stackArray1d< real64, NC > dTotalDensInv_dCompDens( NC ); + for( integer ic = 0; ic < NC; ++ic ) + { + dTotalDensInv_dCompDens[ic] = -dTotalDens[iwelemRef][0][Deriv::dC+ic] * totalDensInv * totalDensInv; + } + applyChainRuleInPlace( NC, dCompFrac_dCompDens[iwelemRef], dTotalDensInv_dCompDens, work.data() ); + + // Step 2.2: divide the total mass/molar rate by the total density to get the total volumetric rate + + // Compute derivatives dP dT + real64 const dTotalDensInv_dPres = -dTotalDens[iwelemRef][0][Deriv::dP] * totalDensInv * totalDensInv; + dControlEqn[COFFSET_WJ::dP] = ( useSurfaceConditions == 0 ) * currentTotalRate * dTotalDensInv_dPres; + if constexpr ( IS_THERMAL ) + { + dControlEqn[COFFSET_WJ::dT] = ( useSurfaceConditions == 0 ) * currentTotalRate * -dTotalDens[iwelemRef][0][Deriv::dT] * totalDensInv * totalDensInv; + } + + dControlEqn[COFFSET_WJ::dQ] = totalDensInv; + for( integer ic = 0; ic < NC; ++ic ) + { + dControlEqn[COFFSET_WJ::dC+ic] = currentTotalRate * dTotalDensInv_dCompDens[ic]; + } + localRhs[eqnRowIndex] += controlEqn; + localMatrix.addToRowBinarySearchUnsorted< serialAtomic >( eqnRowIndex, + dofColIndices, + dControlEqn, + COFFSET_WJ::nDer ); + } ); + + } + template< template< typename U > class T, typename U=MassRateConstraint > + static void assembleConstraintEquation( real64 const & time_n, + WellControls & wellControls, + T< MassRateConstraint > & constraint, + WellElementSubRegion const & subRegion, + string const & wellDofKey, + localIndex const & rankOffset, + CRSMatrixView< real64, globalIndex const > const & localMatrix, + arrayView1d< real64 > const & localRhs ) + { + // subRegion data + + localIndex const iwelemRef = subRegion.getTopWellElementIndex(); + arrayView1d< globalIndex const > const & wellElemDofNumber = subRegion.getReference< array1d< globalIndex > >( wellDofKey ); + arrayView1d< real64 const > const & connRate = subRegion.getField< fields::well::connectionRate >(); + arrayView2d< real64 const, compflow::USD_COMP > const & compFrac = subRegion.getField< fields::well::globalCompFraction >(); + arrayView3d< real64 const, compflow::USD_COMP_DC > const & dCompFrac_dCompDens = subRegion.getField< fields::well::dGlobalCompFraction_dGlobalCompDensity >(); + + // setup row/column indices for constraint equation + using COFFSET_WJ = compositionalMultiphaseWellKernels::ColOffset_WellJac< NC, IS_THERMAL >; + using WJ_ROFFSET = compositionalMultiphaseWellKernels::RowOffset_WellJac< NC, IS_THERMAL >; + using Deriv = constitutive::multifluid::DerivativeOffset; + + localIndex const eqnRowIndex = wellElemDofNumber[iwelemRef] + WJ_ROFFSET::CONTROL - rankOffset; + globalIndex dofColIndices[COFFSET_WJ::nDer]{}; + for( integer ic = 0; ic < COFFSET_WJ::nDer; ++ic ) + { + dofColIndices[ ic ] = wellElemDofNumber[iwelemRef] + ic; + } + + // fluid data + constitutive::MultiFluidBase & fluidSeparator = wellControls.getMultiFluidSeparator(); + arrayView2d< real64 const, constitutive::multifluid::USD_FLUID > const & totalDens = fluidSeparator.totalDensity(); + arrayView3d< real64 const, constitutive::multifluid::USD_FLUID_DC > const & dTotalDens = fluidSeparator.dTotalDensity(); + + // constraint data + real64 const & targetMassRate = constraint.getConstraintValue( time_n ); + + // current constraint value + real64 const & massDensity = + wellControls.getReference< real64 >( CompositionalMultiphaseWell::viewKeyStruct::massDensityString() ); + + // fix to use stored massrate + real64 const & currentTotalVolRate = + wellControls.getReference< real64 >( CompositionalMultiphaseWell::viewKeyStruct::currentTotalVolRateString() ); + integer const useSurfaceConditions = wellControls.useSurfaceConditions(); + + // residual + real64 controlEqn = massDensity*currentTotalVolRate - targetMassRate; + + // setup Jacobian terms + real64 dControlEqn[NC+2+IS_THERMAL]{}; + + // bring everything back to host, capture the scalars by reference + forAll< serialPolicy >( 1, [connRate, + massDensity, + totalDens, + dTotalDens, + compFrac, + dCompFrac_dCompDens, + &dControlEqn, + &useSurfaceConditions, + localRhs, + controlEqn, + eqnRowIndex, + dofColIndices, + localMatrix, + &iwelemRef] ( localIndex const ) + { + stackArray1d< real64, NC > work( NC ); + + real64 const currentTotalRate = connRate[iwelemRef]; + + // compute the inverse of the total density and derivatives + + real64 const totalDensInv = 1.0 / totalDens[iwelemRef][0]; + + stackArray1d< real64, NC > dTotalDensInv_dCompDens( NC ); + for( integer ic = 0; ic < NC; ++ic ) + { + dTotalDensInv_dCompDens[ic] = -dTotalDens[iwelemRef][0][Deriv::dC+ic] * totalDensInv * totalDensInv; + } + applyChainRuleInPlace( NC, dCompFrac_dCompDens[iwelemRef], dTotalDensInv_dCompDens, work.data() ); + + // Step 2.2: divide the total mass/molar rate by the total density to get the total volumetric rate + + // Compute derivatives dP dT + real64 const dTotalDensInv_dPres = -dTotalDens[iwelemRef][0][Deriv::dP] * totalDensInv * totalDensInv; + dControlEqn[COFFSET_WJ::dP] = ( useSurfaceConditions == 0 )*massDensity * currentTotalRate * dTotalDensInv_dPres; + if constexpr ( IS_THERMAL ) + { + dControlEqn[COFFSET_WJ::dT] = ( useSurfaceConditions == 0 ) * massDensity* currentTotalRate * -dTotalDens[iwelemRef][0][Deriv::dT] * totalDensInv * totalDensInv; + } + + dControlEqn[COFFSET_WJ::dQ] = massDensity*totalDensInv; + for( integer ic = 0; ic < NC; ++ic ) + { + dControlEqn[COFFSET_WJ::dC+ic] = massDensity* currentTotalRate * dTotalDensInv_dCompDens[ic]; + } + + // add solver matrices + localRhs[eqnRowIndex] += controlEqn; + localMatrix.addToRowBinarySearchUnsorted< serialAtomic >( eqnRowIndex, + dofColIndices, + dControlEqn, + COFFSET_WJ::nDer ); + + } ); + + } +}; + + +} // end namespace wellConstraintKernels + +} // end namespace geos + +#endif //GEOS_PHYSICSSOLVERS_FLUIDFLOW_WELLS_WELLCONSTRAINTKERNELS_HPP diff --git a/src/coreComponents/physicsSolvers/fluidFlow/wells/kernels/CompositionalMultiphaseWellKernels.cpp b/src/coreComponents/physicsSolvers/fluidFlow/wells/kernels/CompositionalMultiphaseWellKernels.cpp index ab153369374..2347657946d 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/wells/kernels/CompositionalMultiphaseWellKernels.cpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/wells/kernels/CompositionalMultiphaseWellKernels.cpp @@ -31,214 +31,6 @@ namespace compositionalMultiphaseWellKernels using namespace constitutive; -/******************************** ControlEquationHelper ********************************/ - -GEOS_HOST_DEVICE -inline -void -ControlEquationHelper:: - switchControl( bool const isProducer, - WellControls::Control const & inputControl, - WellControls::Control const & currentControl, - integer const phasePhaseIndex, - real64 const & targetBHP, - real64 const & targetPhaseRate, - real64 const & targetTotalRate, - real64 const & targetMassRate, - real64 const & currentBHP, - arrayView1d< real64 const > const & currentPhaseVolRate, - real64 const & currentTotalVolRate, - real64 const & currentMassRate, - WellControls::Control & newControl ) -{ - // if isViable is true at the end of the following checks, no need to switch - bool controlIsViable = false; - - // The limiting flow rates are treated as upper limits, while the pressure limits - // are treated as lower limits in production wells and upper limits in injectors. - // The well changes its mode of control whenever the existing control mode would - // violate one of these limits. - - // Currently, the available constraints are: - // - Producer: BHP, PHASEVOLRATE - // - Injector: BHP, TOTALVOLRATE, MASSRATE - - // TODO: support GRAT, WRAT, LIQUID for producers and check if any of the active constraint is violated - - // BHP control - if( currentControl == WellControls::Control::BHP ) - { - // the control is viable if the reference oil rate is below the max rate for producers - if( isProducer ) - { - controlIsViable = ( LvArray::math::abs( currentPhaseVolRate[phasePhaseIndex] ) <= LvArray::math::abs( targetPhaseRate ) ); - } - // the control is viable if the reference total rate is below the max rate for injectors - else if( inputControl == WellControls::Control::MASSRATE ) - { - controlIsViable = ( LvArray::math::abs( currentMassRate ) <= LvArray::math::abs( targetMassRate ) ); - } - else - { - controlIsViable = ( LvArray::math::abs( currentTotalVolRate ) <= LvArray::math::abs( targetTotalRate ) ); - } - } - else // rate control - { - // the control is viable if the reference pressure is below/above the max/min pressure - if( isProducer ) - { - // targetBHP specifies a min pressure here - controlIsViable = ( currentBHP >= targetBHP ); - } - else - { - // targetBHP specifies a max pressure here - controlIsViable = ( currentBHP <= targetBHP ); - } - } - - if( controlIsViable ) - { - newControl = currentControl; - } - else - { - if( isProducer ) - { - newControl = ( currentControl == WellControls::Control::BHP ) - ? WellControls::Control::PHASEVOLRATE - : WellControls::Control::BHP; - } - else - { - if( isZero( targetMassRate ) ) - { - newControl = ( currentControl == WellControls::Control::BHP ) - ? WellControls::Control::TOTALVOLRATE - : WellControls::Control::BHP; - } - else - { - newControl = ( currentControl == WellControls::Control::BHP ) - ? WellControls::Control::MASSRATE - : WellControls::Control::BHP; - } - } - } -} - -template< integer NC, integer IS_THERMAL > -GEOS_HOST_DEVICE -inline -void -ControlEquationHelper:: - compute( globalIndex const rankOffset, - WellControls::Control const currentControl, - integer const targetPhaseIndex, - real64 const & targetBHP, - real64 const & targetPhaseRate, - real64 const & targetTotalRate, - real64 const & targetMassRate, - real64 const & currentBHP, - arrayView1d< real64 const > const & dCurrentBHP, - arrayView1d< real64 const > const & currentPhaseVolRate, - arrayView2d< real64 const > const & dCurrentPhaseVolRate, - - real64 const & currentTotalVolRate, - arrayView1d< real64 const > const & dCurrentTotalVolRate, - real64 const & massDensity, - globalIndex const dofNumber, - CRSMatrixView< real64, globalIndex const > const & localMatrix, - arrayView1d< real64 > const & localRhs ) -{ - - using COFFSET_WJ = compositionalMultiphaseWellKernels::ColOffset_WellJac< NC, IS_THERMAL >; - using Deriv = multifluid::DerivativeOffset; - - localIndex const eqnRowIndex = dofNumber + ROFFSET::CONTROL - rankOffset; - globalIndex dofColIndices[COFFSET_WJ::nDer]{}; - for( integer ic = 0; ic < COFFSET_WJ::nDer; ++ic ) - { - dofColIndices[ ic ] = dofNumber + ic; - } - - real64 controlEqn = 0; - real64 dControlEqn[NC+2+IS_THERMAL]{}; - - // Note: We assume in the computation of currentBHP that the reference elevation - // is in the top well element. This is enforced by a check in the solver. - // If we wanted to allow the reference elevation to be outside the top - // well element, it would make more sense to check the BHP constraint in - // the well element that contains the reference elevation. - - // BHP control - if( currentControl == WellControls::Control::BHP ) - { - // control equation is a difference between current BHP and target BHP - controlEqn = currentBHP - targetBHP; - dControlEqn[COFFSET_WJ::dP] = dCurrentBHP[Deriv::dP]; - for( integer ic = 0; ic < NC; ++ic ) - { - dControlEqn[COFFSET_WJ::dC+ic] = dCurrentBHP[Deriv::dC+ic]; - } - if constexpr ( IS_THERMAL ) - - dControlEqn[COFFSET_WJ::dT] = dCurrentBHP[Deriv::dT]; - - } - // Oil volumetric rate control - else if( currentControl == WellControls::Control::PHASEVOLRATE ) - { - controlEqn = currentPhaseVolRate[targetPhaseIndex] - targetPhaseRate; - dControlEqn[COFFSET_WJ::dP] = dCurrentPhaseVolRate[targetPhaseIndex][COFFSET_WJ::dP]; - dControlEqn[COFFSET_WJ::dQ] = dCurrentPhaseVolRate[targetPhaseIndex][COFFSET_WJ::dQ]; - for( integer ic = 0; ic < NC; ++ic ) - { - dControlEqn[COFFSET_WJ::dC+ic] = dCurrentPhaseVolRate[targetPhaseIndex][COFFSET_WJ::dC+ic]; - } - if constexpr ( IS_THERMAL ) - dControlEqn[COFFSET_WJ::dT] = dCurrentPhaseVolRate[targetPhaseIndex][COFFSET_WJ::dT]; - } - // Total volumetric rate control - else if( currentControl == WellControls::Control::TOTALVOLRATE ) - { - controlEqn = currentTotalVolRate - targetTotalRate; - dControlEqn[COFFSET_WJ::dP] = dCurrentTotalVolRate[COFFSET_WJ::dP]; - dControlEqn[COFFSET_WJ::dQ] = dCurrentTotalVolRate[COFFSET_WJ::dQ]; - for( integer ic = 0; ic < NC; ++ic ) - { - dControlEqn[COFFSET_WJ::dC+ic] = dCurrentTotalVolRate[COFFSET_WJ::dC+ic]; - } - if constexpr ( IS_THERMAL ) - dControlEqn[COFFSET_WJ::dT] = dCurrentTotalVolRate[COFFSET_WJ::dT]; - } - // Total mass rate control - else if( currentControl == WellControls::Control::MASSRATE ) - { - controlEqn = massDensity*currentTotalVolRate - targetMassRate; - dControlEqn[COFFSET_WJ::dP] = massDensity*dCurrentTotalVolRate[COFFSET_WJ::dP]; - dControlEqn[COFFSET_WJ::dQ] = massDensity*dCurrentTotalVolRate[COFFSET_WJ::dQ]; - for( integer ic = 0; ic < NC; ++ic ) - { - dControlEqn[COFFSET_WJ::dC+ic] = massDensity*dCurrentTotalVolRate[COFFSET_WJ::dC+ic]; - } - if constexpr ( IS_THERMAL ) - dControlEqn[COFFSET_WJ::dT] = massDensity*dCurrentTotalVolRate[COFFSET_WJ::dT]; - } - else - { - GEOS_ERROR( "This constraint is not supported in CompositionalMultiphaseWell" ); - } - localRhs[eqnRowIndex] += controlEqn; - - localMatrix.addToRowBinarySearchUnsorted< serialAtomic >( eqnRowIndex, - dofColIndices, - dControlEqn, - COFFSET_WJ::nDer ); - - -} /******************************** PressureRelationKernel ********************************/ @@ -300,11 +92,6 @@ void PressureRelationKernel:: launch( localIndex const size, globalIndex const rankOffset, - bool const isLocallyOwned, - localIndex const iwelemControl, - integer const targetPhaseIndex, - WellControls const & wellControls, - real64 const & time, arrayView1d< integer const > const elemStatus, arrayView1d< globalIndex const > const & wellElemDofNumber, arrayView1d< real64 const > const & wellElemGravCoef, @@ -317,36 +104,6 @@ PressureRelationKernel:: arrayView1d< real64 > const & localRhs ) { using COFFSET_WJ = compositionalMultiphaseWellKernels::ColOffset_WellJac< NC, IS_THERMAL >; - // static well control data - bool const isProducer = wellControls.isProducer(); - WellControls::Control const currentControl = wellControls.getControl(); - WellControls::Control const inputControl = wellControls.getInputControl(); - real64 const targetBHP = wellControls.getTargetBHP( time ); - real64 const targetTotalRate = wellControls.getTargetTotalRate( time ); - real64 const targetPhaseRate = wellControls.getTargetPhaseRate( time ); - real64 const targetMassRate = wellControls.getTargetMassRate( time ); - - // dynamic well control data - real64 const & currentBHP = - wellControls.getReference< real64 >( CompositionalMultiphaseWell::viewKeyStruct::currentBHPString() ); - arrayView1d< real64 const > const & dCurrentBHP = - wellControls.getReference< array1d< real64 > >( CompositionalMultiphaseWell::viewKeyStruct::dCurrentBHPString() ); - - arrayView1d< real64 const > const & currentPhaseVolRate = - wellControls.getReference< array1d< real64 > >( CompositionalMultiphaseWell::viewKeyStruct::currentPhaseVolRateString() ); - arrayView2d< real64 const > const & dCurrentPhaseVolRate = - wellControls.getReference< array2d< real64 > >( CompositionalMultiphaseWell::viewKeyStruct::dCurrentPhaseVolRateString() ); - - real64 const & currentTotalVolRate = - wellControls.getReference< real64 >( CompositionalMultiphaseWell::viewKeyStruct::currentTotalVolRateString() ); - arrayView1d< real64 const > const & dCurrentTotalVolRate = - wellControls.getReference< array1d< real64 > >( CompositionalMultiphaseWell::viewKeyStruct::dCurrentTotalVolRateString() ); - - real64 const & currentMassRate = - wellControls.getReference< real64 >( CompositionalMultiphaseWell::viewKeyStruct::currentMassRateString() ); - - real64 const & massDensity = - wellControls.getReference< real64 >( CompositionalMultiphaseWell::viewKeyStruct::massDensityString() ); RAJA::ReduceMax< parallelDeviceReduce, localIndex > switchControl( 0 ); @@ -360,48 +117,7 @@ PressureRelationKernel:: localIndex const iwelemNext = nextWellElemIndex[iwelem]; - if( iwelemNext < 0 && isLocallyOwned ) // if iwelemNext < 0, form control equation - { - - WellControls::Control newControl = currentControl; - ControlEquationHelper::switchControl( isProducer, - inputControl, - currentControl, - targetPhaseIndex, - targetBHP, - targetPhaseRate, - targetTotalRate, - targetMassRate, - currentBHP, - currentPhaseVolRate, - currentTotalVolRate, - currentMassRate, - newControl ); - if( currentControl != newControl ) - { - switchControl.max( 1 ); - } - ControlEquationHelper::compute< NC, IS_THERMAL >( rankOffset, - newControl, - targetPhaseIndex, - targetBHP, - targetPhaseRate, - targetTotalRate, - targetMassRate, - currentBHP, - dCurrentBHP, - currentPhaseVolRate, - dCurrentPhaseVolRate, - currentTotalVolRate, - dCurrentTotalVolRate, - massDensity, - wellElemDofNumber[iwelemControl], - localMatrix, - localRhs ); - // TODO: for consistency, we should assemble here, not in compute... - - } - else if( iwelemNext >= 0 ) // if iwelemNext >= 0, form momentum equation + if( iwelemNext >= 0 ) // if iwelemNext >= 0, form momentum equation { real64 localPresRel = 0; @@ -455,11 +171,6 @@ PressureRelationKernel:: void PressureRelationKernel:: \ launch< NC, IS_THERMAL >( localIndex const size, \ globalIndex const rankOffset, \ - bool const isLocallyOwned, \ - localIndex const iwelemControl, \ - integer const targetPhaseIndex, \ - WellControls const & wellControls, \ - real64 const & time, \ arrayView1d< integer const > const elemStatus, \ arrayView1d< globalIndex const > const & wellElemDofNumber, \ arrayView1d< real64 const > const & wellElemGravCoef, \ @@ -489,6 +200,7 @@ PresTempCompFracInitializationKernel:: integer const numComps, integer const numPhases, WellControls const & wellControls, + real64 const & refWellElemGravCoef, real64 const & currentTime, ElementViewConst< arrayView1d< real64 const > > const & resPres, ElementViewConst< arrayView1d< real64 const > > const & resTemp, @@ -508,9 +220,8 @@ PresTempCompFracInitializationKernel:: integer constexpr MAX_NUM_COMP = constitutive::MultiFluidBase::MAX_NUM_COMPONENTS; real64 const targetBHP = wellControls.getTargetBHP( currentTime ); - real64 const refWellElemGravCoef = wellControls.getReferenceGravityCoef(); real64 const initialPresCoef = wellControls.getInitialPressureCoefficient(); - WellControls::Control const currentControl = wellControls.getControl(); + ConstraintTypeId const currentControl = wellControls.getControl(); bool const isProducer = wellControls.isProducer(); @@ -608,7 +319,7 @@ PresTempCompFracInitializationKernel:: real64 refPres = 0.0; // if the well is controlled by pressure, initialize the reference pressure at the target pressure - if( currentControl == WellControls::Control::BHP ) + if( currentControl == ConstraintTypeId::BHP ) { refPres = targetBHP; } @@ -716,62 +427,96 @@ CompDensInitializationKernel:: void RateInitializationKernel:: launch( localIndex const subRegionSize, - integer const targetPhaseIndex, WellControls const & wellControls, real64 const & time, arrayView3d< real64 const, multifluid::USD_PHASE > const & phaseDens, arrayView2d< real64 const, multifluid::USD_FLUID > const & totalDens, arrayView1d< real64 > const & connRate ) { - WellControls::Control const control = wellControls.getControl(); - bool const isProducer = wellControls.isProducer(); - real64 const targetTotalRate = wellControls.getTargetTotalRate( time ); - real64 const targetPhaseRate = wellControls.getTargetPhaseRate( time ); - real64 const targetMassRate = wellControls.getTargetMassRate( time ); - - // Estimate the connection rates - forAll< parallelDevicePolicy<> >( subRegionSize, [=] GEOS_HOST_DEVICE ( localIndex const iwelem ) + if( wellControls.isProducer() ) { - if( control == WellControls::Control::BHP ) + // Use use defined control type to set initial connection rates + WellConstraintBase const * constraint = wellControls.getCurrentConstraint(); + real64 const constraintVal = constraint->getConstraintValue( time ); + ConstraintTypeId const controlType = constraint->getControl(); + if( controlType == ConstraintTypeId::PHASEVOLRATE ) { - // if BHP constraint set rate below the absolute max rate - // with the appropriate sign (negative for prod, positive for inj) - if( isProducer ) + integer const targetPhaseIndex = wellControls.getConstraintPhaseIndex(); + + forAll< parallelDevicePolicy<> >( subRegionSize, [=] GEOS_HOST_DEVICE ( localIndex const iwelem ) { - connRate[iwelem] = LvArray::math::max( 0.1 * targetPhaseRate * phaseDens[iwelem][0][targetPhaseIndex], -1e3 ); - } - else + connRate[iwelem] = constraintVal * phaseDens[iwelem][0][targetPhaseIndex]; + } ); + } + else if( controlType == ConstraintTypeId::TOTALVOLRATE ) + { + forAll< parallelDevicePolicy<> >( subRegionSize, [=] GEOS_HOST_DEVICE ( localIndex const iwelem ) { - if( isZero( targetMassRate ) ) - { - connRate[iwelem] = LvArray::math::min( 0.1 * targetTotalRate * totalDens[iwelem][0], 1e3 ); - } - else - { - connRate[iwelem] = targetMassRate; - } + connRate[iwelem] = LvArray::math::max( 0.1 * constraintVal * totalDens[iwelem][0], -1e3 ); + } ); + } + else if( controlType == ConstraintTypeId::MASSRATE ) + { + forAll< parallelDevicePolicy<> >( subRegionSize, [=] GEOS_HOST_DEVICE ( localIndex const iwelem ) + { + connRate[iwelem] = constraintVal; + } ); + } + else if( controlType == ConstraintTypeId::BHP ) + { + // this assumes phase control present + integer const targetPhaseIndex = wellControls.getConstraintPhaseIndex(); + auto const * rateConstraint = wellControls.getRateConstraints().front(); + // Use first rate constraint to set initial connection rates + real64 const rateVal = rateConstraint->getConstraintValue( time ); + forAll< parallelDevicePolicy<> >( subRegionSize, [=] GEOS_HOST_DEVICE ( localIndex const iwelem ) + { + connRate[iwelem] = LvArray::math::max( 0.1 * rateVal * phaseDens[iwelem][0][targetPhaseIndex], -1e3 ); + } ); + } + } + else + { + // Use use defined control type to set initial connection rates + WellConstraintBase const * constraint = wellControls.getCurrentConstraint(); + real64 const constraintVal = constraint->getConstraintValue( time ); + ConstraintTypeId const controlType = constraint->getControl(); + if( controlType == ConstraintTypeId::PHASEVOLRATE ) + { + integer const targetPhaseIndex = wellControls.getConstraintPhaseIndex(); - } + forAll< parallelDevicePolicy<> >( subRegionSize, [=] GEOS_HOST_DEVICE ( localIndex const iwelem ) + { + connRate[iwelem] = LvArray::math::max( 0.1 * constraintVal * phaseDens[iwelem][0][targetPhaseIndex], 1e3 ); + } ); } - else if( control == WellControls::Control::MASSRATE ) + else if( controlType == ConstraintTypeId::TOTALVOLRATE ) { - connRate[iwelem] = targetMassRate; + forAll< parallelDevicePolicy<> >( subRegionSize, [=] GEOS_HOST_DEVICE ( localIndex const iwelem ) + { + connRate[iwelem] = constraintVal * totalDens[iwelem][0]; + } ); } - else + else if( controlType == ConstraintTypeId::MASSRATE ) { - if( isProducer ) + forAll< parallelDevicePolicy<> >( subRegionSize, [=] GEOS_HOST_DEVICE ( localIndex const iwelem ) { - connRate[iwelem] = targetPhaseRate * phaseDens[iwelem][0][targetPhaseIndex]; - } - else + connRate[iwelem] = constraintVal; + } ); + } + else if( controlType == ConstraintTypeId::BHP ) + { + auto const * rateConstraint = wellControls.getRateConstraints().front(); + // Use first rate constraint to set initial connection rates + real64 const rateVal = rateConstraint->getConstraintValue( time ); + forAll< parallelDevicePolicy<> >( subRegionSize, [=] GEOS_HOST_DEVICE ( localIndex const iwelem ) { - connRate[iwelem] = targetTotalRate * totalDens[iwelem][0]; - } + connRate[iwelem] = LvArray::math::min( 0.1 * rateVal * totalDens[iwelem][0], 1e3 ); + } ); } - } ); + } } - } // end namespace compositionalMultiphaseWellKernels } // end namespace geos diff --git a/src/coreComponents/physicsSolvers/fluidFlow/wells/kernels/CompositionalMultiphaseWellKernels.hpp b/src/coreComponents/physicsSolvers/fluidFlow/wells/kernels/CompositionalMultiphaseWellKernels.hpp index a8123abbb12..bc6dab29af6 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/wells/kernels/CompositionalMultiphaseWellKernels.hpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/wells/kernels/CompositionalMultiphaseWellKernels.hpp @@ -39,7 +39,8 @@ #include "physicsSolvers/fluidFlow/kernels/compositional/SolutionCheckKernel.hpp" #include "physicsSolvers/fluidFlow/wells/CompositionalMultiphaseWellFields.hpp" #include "physicsSolvers/fluidFlow/wells/WellControls.hpp" -#include "physicsSolvers/fluidFlow/wells/WellSolverBaseFields.hpp" +#include "physicsSolvers/fluidFlow/wells/WellFields.hpp" +#include "physicsSolvers/fluidFlow/wells/WellPhaseVolumeRateConstraint.hpp" namespace geos { @@ -134,32 +135,33 @@ struct ControlEquationHelper inline static void - switchControl( bool const isProducer, - WellControls::Control const & inputControl, - WellControls::Control const & currentControl, - integer const phasePhaseIndex, - real64 const & targetBHP, - real64 const & targetPhaseRate, - real64 const & targetTotalRate, - real64 const & targetMassRate, - real64 const & currentBHP, - arrayView1d< real64 const > const & currentPhaseVolRate, - real64 const & currentTotalVolRate, - real64 const & currentMassRate, - WellControls::Control & newControl ); + selectLimitingConstraint( bool const isProducer, + ConstraintTypeId const & inputControl, + ConstraintTypeId const & currentControl, + integer const phasePhaseIndex, + real64 const & targetBHP, + real64 const & targetPhaseRate, + real64 const & targetTotalRate, + real64 const & targetMassRate, + real64 const & currentBHP, + arrayView1d< real64 const > const & currentPhaseVolRate, + real64 const & currentTotalVolRate, + real64 const & currentMassRate, + ConstraintTypeId & newControl ); template< integer NC, integer IS_THERMAL > GEOS_HOST_DEVICE inline static void compute( globalIndex const rankOffset, - WellControls::Control const currentControl, + ConstraintTypeId const currentControl, integer const targetPhaseIndex, real64 const & targetBHP, real64 const & targetPhaseRate, real64 const & targetTotalRate, real64 const & targetMassRate, real64 const & currentBHP, + real64 const & targetValue, arrayView1d< real64 const > const & dCurrentBHP, arrayView1d< real64 const > const & currentPhaseVolRate, arrayView2d< real64 const > const & dCurrentPhaseVolRate, @@ -200,11 +202,6 @@ struct PressureRelationKernel static void launch( localIndex const size, globalIndex const rankOffset, - bool const isLocallyOwned, - localIndex const iwelemControl, - integer const targetPhaseIndex, - WellControls const & wellControls, - real64 const & time, arrayView1d< integer const > const elemStatus, arrayView1d< globalIndex const > const & wellElemDofNumber, arrayView1d< real64 const > const & wellElemGravCoef, @@ -284,6 +281,7 @@ struct PresTempCompFracInitializationKernel integer const numComponents, integer const numPhases, WellControls const & wellControls, + real64 const & refWellElemGravCoef, real64 const & currentTime, ElementViewConst< arrayView1d< real64 const > > const & resPres, ElementViewConst< arrayView1d< real64 const > > const & resTemp, @@ -323,7 +321,6 @@ struct RateInitializationKernel static void launch( localIndex const subRegionSize, - integer const targetPhaseIndex, WellControls const & wellControls, real64 const & currentTime, arrayView3d< real64 const, constitutive::multifluid::USD_PHASE > const & phaseDens, @@ -505,7 +502,6 @@ class ResidualNormKernel : public physicsSolverBaseKernels::ResidualNormKernelBa arrayView1d< localIndex const > const & ghostRank, integer const numComp, integer const numDof, - integer const targetPhaseIndex, WellElementSubRegion const & subRegion, constitutive::MultiFluidBase const & fluid, WellControls const & wellControls, @@ -519,20 +515,34 @@ class ResidualNormKernel : public physicsSolverBaseKernels::ResidualNormKernelBa minNormalizer ), m_numComp( numComp ), m_numDof( numDof ), - m_targetPhaseIndex( targetPhaseIndex ), m_dt( dt ), m_isLocallyOwned( subRegion.isLocallyOwned() ), m_iwelemControl( subRegion.getTopWellElementIndex() ), m_isProducer( wellControls.isProducer() ), m_currentControl( wellControls.getControl() ), m_targetBHP( wellControls.getTargetBHP( time ) ), - m_targetTotalRate( wellControls.getTargetTotalRate( time ) ), - m_targetPhaseRate( wellControls.getTargetPhaseRate( time ) ), - m_targetMassRate( wellControls.getTargetMassRate( time ) ), m_volume( subRegion.getElementVolume() ), m_phaseDens_n( fluid.phaseDensity_n() ), m_totalDens_n( fluid.totalDensity_n() ) - {} + { + // Note this assumes that there is only one rate constraint + // This is a normalizer for the balance equations. The normalizaer should be the current rate not the constraint value!! + // This is one of the reasons for restricting constraint type for a production well + // Another pr will remove fix this (so the cause for difference results is isolated to one change) + auto const * rateConstraint = wellControls.getRateConstraints().front(); + if( rateConstraint != nullptr ) + { + m_constraintValue = rateConstraint->getConstraintValue( time ); + } + if( m_isProducer ) + { + m_targetPhaseIndex = wellControls.getConstraintPhaseIndex(); + } + else + { + m_targetPhaseIndex = -1; + } + } GEOS_HOST_DEVICE virtual void computeLinf( localIndex const iwelem, @@ -553,25 +563,25 @@ class ResidualNormKernel : public physicsSolverBaseKernels::ResidualNormKernelBa // for the top well element, normalize using the current control if( m_isLocallyOwned && iwelem == m_iwelemControl ) { - if( m_currentControl == WellControls::Control::BHP ) + if( m_currentControl == ConstraintTypeId::BHP ) { // the residual entry is in pressure units normalizer = m_targetBHP; } - else if( m_currentControl == WellControls::Control::TOTALVOLRATE ) + else if( m_currentControl == ConstraintTypeId::TOTALVOLRATE ) { // the residual entry is in volume / time units - normalizer = LvArray::math::max( LvArray::math::abs( m_targetTotalRate ), m_minNormalizer ); + normalizer = LvArray::math::max( LvArray::math::abs( m_constraintValue ), m_minNormalizer ); } - else if( m_currentControl == WellControls::Control::PHASEVOLRATE ) + else if( m_currentControl == ConstraintTypeId::PHASEVOLRATE ) { // the residual entry is in volume / time units - normalizer = LvArray::math::max( LvArray::math::abs( m_targetPhaseRate ), m_minNormalizer ); + normalizer = LvArray::math::max( LvArray::math::abs( m_constraintValue ), m_minNormalizer ); } - else if( m_currentControl == WellControls::Control::MASSRATE ) + else if( m_currentControl == ConstraintTypeId::MASSRATE ) { // the residual entry is in volume / time units - normalizer = LvArray::math::max( LvArray::math::abs( m_targetMassRate ), m_minNormalizer ); + normalizer = LvArray::math::max( LvArray::math::abs( m_constraintValue ), m_minNormalizer ); } } // for the pressure difference equation, always normalize by the BHP @@ -586,18 +596,18 @@ class ResidualNormKernel : public physicsSolverBaseKernels::ResidualNormKernelBa if( m_isProducer ) // only PHASEVOLRATE is supported for now { // the residual is in mass units - normalizer = m_dt * LvArray::math::abs( m_targetPhaseRate ) * m_phaseDens_n[iwelem][0][m_targetPhaseIndex]; + normalizer = m_dt * LvArray::math::abs( m_constraintValue ) * m_phaseDens_n[iwelem][0][m_targetPhaseIndex]; } else // Type::INJECTOR, only TOTALVOLRATE is supported for now { - if( m_currentControl == WellControls::Control::MASSRATE ) + if( m_currentControl == ConstraintTypeId::MASSRATE ) { - normalizer = m_dt * LvArray::math::abs( m_targetMassRate ); + normalizer = m_dt * LvArray::math::abs( m_constraintValue ); } else { // the residual is in mass units - normalizer = m_dt * LvArray::math::abs( m_targetTotalRate ) * m_totalDens_n[iwelem][0]; + normalizer = m_dt * LvArray::math::abs( m_constraintValue ) * m_totalDens_n[iwelem][0]; } } @@ -611,17 +621,17 @@ class ResidualNormKernel : public physicsSolverBaseKernels::ResidualNormKernelBa if( m_isProducer ) // only PHASEVOLRATE is supported for now { // the residual is in volume units - normalizer = m_dt * LvArray::math::abs( m_targetPhaseRate ); + normalizer = m_dt * LvArray::math::abs( m_constraintValue ); } else // Type::INJECTOR, only TOTALVOLRATE is supported for now { - if( m_currentControl == WellControls::Control::MASSRATE ) + if( m_currentControl == ConstraintTypeId::MASSRATE ) { - normalizer = m_dt * LvArray::math::abs( m_targetMassRate/ m_totalDens_n[iwelem][0] ); + normalizer = m_dt * LvArray::math::abs( m_constraintValue/ m_totalDens_n[iwelem][0] ); } else { - normalizer = m_dt * LvArray::math::abs( m_targetTotalRate ); + normalizer = m_dt * LvArray::math::abs( m_constraintValue ); } } @@ -658,7 +668,7 @@ class ResidualNormKernel : public physicsSolverBaseKernels::ResidualNormKernelBa integer const m_numDof; /// Index of the target phase - integer const m_targetPhaseIndex; + integer m_targetPhaseIndex; /// Time step size real64 const m_dt; @@ -673,11 +683,10 @@ class ResidualNormKernel : public physicsSolverBaseKernels::ResidualNormKernelBa bool const m_isProducer; /// Controls - WellControls::Control const m_currentControl; - real64 const m_targetBHP; - real64 const m_targetTotalRate; - real64 const m_targetPhaseRate; - real64 const m_targetMassRate; + ConstraintTypeId const m_currentControl; + real64 m_constraintValue; + real64 m_targetBHP; + /// View on the volume arrayView1d< real64 const > const m_volume; @@ -700,7 +709,6 @@ class ResidualNormKernelFactory * @tparam POLICY the policy used in the RAJA kernel * @param[in] numComp number of fluid components * @param[in] numDof number of dofs per well element - * @param[in] targetPhaseIndex the index of the target phase (for phase volume control) * @param[in] rankOffset the offset of my MPI rank * @param[in] dofKey the string key to retrieve the degress of freedom numbers * @param[in] localResidual the residual vector on my MPI rank @@ -715,7 +723,6 @@ class ResidualNormKernelFactory static void createAndLaunch( integer const numComp, integer const numDof, - integer const targetPhaseIndex, globalIndex const rankOffset, string const & dofKey, arrayView1d< real64 const > const & localResidual, @@ -731,7 +738,7 @@ class ResidualNormKernelFactory arrayView1d< integer const > const ghostRank = subRegion.ghostRank(); ResidualNormKernel kernel( rankOffset, localResidual, dofNumber, ghostRank, - numComp, numDof, targetPhaseIndex, subRegion, fluid, wellControls, time, dt, minNormalizer ); + numComp, numDof, subRegion, fluid, wellControls, time, dt, minNormalizer ); ResidualNormKernel::launchLinf< POLICY >( subRegion.size(), kernel, residualNorm ); } @@ -830,6 +837,7 @@ class ElementBasedAssemblyKernel * @param[inout] localRhs the local right-hand side vector */ ElementBasedAssemblyKernel( localIndex const numPhases, + integer const thermalEffectsEnabled, integer const isProducer, globalIndex const rankOffset, string const dofKey, @@ -839,6 +847,7 @@ class ElementBasedAssemblyKernel arrayView1d< real64 > const & localRhs, BitFlags< isothermalCompositionalMultiphaseBaseKernels::KernelFlags > const kernelFlags ) : m_numPhases( numPhases ), + m_thermalEffectsEnabled( thermalEffectsEnabled ), m_isProducer( isProducer ), m_rankOffset( rankOffset ), m_iwelemControl( subRegion.getTopWellElementIndex() ), @@ -1120,7 +1129,20 @@ class ElementBasedAssemblyKernel if constexpr ( IS_THERMAL) { - if( ei == m_iwelemControl && !m_isProducer ) + if( !m_thermalEffectsEnabled ) + { + for( integer i=0; i < numComp+1+IS_THERMAL; i++ ) + { + stack.localJacobian[numRows-1][i] = 0.0; + } + // constant Temperature + for( integer i=0; i < numComp+1+IS_THERMAL; i++ ) + stack.localJacobian[i][numRows-1] = 0.0; + stack.localJacobian[numRows-1][numRows-1] = 1.0; + + stack.localResidual[numRows-1]=0.0; + } + else if( ei == m_iwelemControl && !m_isProducer ) { // For top segment energy balance eqn replaced with T(n+1) - T = 0 // No other energy balance derivatives @@ -1197,6 +1219,8 @@ class ElementBasedAssemblyKernel /// Number of fluid phases integer const m_numPhases; + /// Flag indicating whether thermal effects are enabled + bool const m_thermalEffectsEnabled; /// Well type integer const m_isProducer; @@ -1276,6 +1300,7 @@ class ElementBasedAssemblyKernelFactory static void createAndLaunch( localIndex const numComps, localIndex const numPhases, + integer const & thermalEffectsEnabled, integer const isProducer, globalIndex const rankOffset, BitFlags< isothermalCompositionalMultiphaseBaseKernels::KernelFlags > kernelFlags, @@ -1292,7 +1317,7 @@ class ElementBasedAssemblyKernelFactory integer constexpr istherm = IS_THERMAL(); ElementBasedAssemblyKernel< NUM_COMP, istherm > - kernel( numPhases, isProducer, rankOffset, dofKey, subRegion, fluid, localMatrix, localRhs, kernelFlags ); + kernel( numPhases, thermalEffectsEnabled, isProducer, rankOffset, dofKey, subRegion, fluid, localMatrix, localRhs, kernelFlags ); ElementBasedAssemblyKernel< NUM_COMP, istherm >::template launch< POLICY, ElementBasedAssemblyKernel< NUM_COMP, istherm > >( subRegion.size(), kernel ); } ); @@ -1359,7 +1384,7 @@ class FaceBasedAssemblyKernel m_wellElemDofNumber ( subRegion.getReference< array1d< globalIndex > >( wellDofKey ) ), m_nextWellElemIndex ( subRegion.getReference< array1d< localIndex > >( WellElementSubRegion::viewKeyStruct::nextWellElementIndexString()) ), m_elemStatus( subRegion.getLocalWellElementStatus() ), - m_connRate ( subRegion.getField< fields::well::mixtureConnectionRate >() ), + m_connRate ( subRegion.getField< fields::well::connectionRate >() ), m_wellElemCompFrac ( subRegion.getField< fields::well::globalCompFraction >() ), m_dWellElemCompFrac_dCompDens ( subRegion.getField< fields::well::dGlobalCompFraction_dGlobalCompDensity >() ), m_localMatrix( localMatrix ), diff --git a/src/coreComponents/physicsSolvers/fluidFlow/wells/kernels/SinglePhaseWellConstraintKernels.hpp b/src/coreComponents/physicsSolvers/fluidFlow/wells/kernels/SinglePhaseWellConstraintKernels.hpp new file mode 100644 index 00000000000..72d0e7878c5 --- /dev/null +++ b/src/coreComponents/physicsSolvers/fluidFlow/wells/kernels/SinglePhaseWellConstraintKernels.hpp @@ -0,0 +1,196 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2016-2024 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2024 TotalEnergies + * Copyright (c) 2018-2024 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2023-2024 Chevron + * Copyright (c) 2019- GEOS/GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +/** + * @file SinglePhaseWellConstraintKernels.hpp + */ + +#ifndef GEOS_PHYSICSSOLVERS_FLUIDFLOW_WELLS_SINGLEPHASEWELLCONSTRAINTKERNELS_HPP +#define GEOS_PHYSICSSOLVERS_FLUIDFLOW_WELLS_SINGLEPHASEWELLCONSTRAINTKERNELS_HPP + +#include "codingUtilities/Utilities.hpp" +#include "constitutive/fluid/singlefluid/SingleFluidBase.hpp" +#include "constitutive/fluid/singlefluid/SingleFluidFields.hpp" + + +#include "physicsSolvers/fluidFlow/wells/WellControls.hpp" +#include "physicsSolvers/fluidFlow/wells/WellBHPConstraints.hpp" +#include "physicsSolvers/fluidFlow/wells/WellVolumeRateConstraint.hpp" + + +namespace geos +{ + +namespace singlePhaseWellConstraintKernels +{ + +/******************************** ControlEquationHelper ********************************/ + + +template< integer IS_THERMAL > +struct ConstraintHelper +{ + template< BHPConstraintTypeId I > + static void assembleConstraintEquation( real64 const & time_n, + WellControls & wellControls, + BHPConstraint< I > & constraint, + WellElementSubRegion const & subRegion, + string const & wellDofKey, + localIndex const & rankOffset, + CRSMatrixView< real64, globalIndex const > const & localMatrix, + arrayView1d< real64 > const & localRhs ) + { + // subRegion data + localIndex const iwelemRef = subRegion.getTopWellElementIndex(); + arrayView1d< globalIndex const > const & wellElemDofNumber = subRegion.getReference< array1d< globalIndex > >( wellDofKey ); + arrayView1d< real64 const > const & pres = subRegion.getField< fields::well::pressure >(); + + constitutive::SingleFluidBase & fluidSeparator = wellControls.getSingleFluidSeparator(); + arrayView2d< real64 const, constitutive::singlefluid::USD_FLUID > const & density = fluidSeparator.density(); + arrayView3d< real64 const, constitutive::singlefluid::USD_FLUID_DER > const & dDensity = fluidSeparator.dDensity(); + + arrayView1d< real64 const > const wellElemGravCoef = subRegion.getField< fields::well::gravityCoefficient >(); + + // setup row/column indices for constraint equation + using ROFFSET_WJ = singlePhaseWellKernels::RowOffset_WellJac< IS_THERMAL >; + using COFFSET_WJ = singlePhaseWellKernels::ColOffset_WellJac< IS_THERMAL >; + using Deriv = constitutive::singlefluid::DerivativeOffsetC< IS_THERMAL >; + + localIndex const eqnRowIndex = wellElemDofNumber[iwelemRef] + ROFFSET_WJ::CONTROL - rankOffset; + globalIndex dofColIndices[COFFSET_WJ::nDer]{}; + for( integer i = 0; i < COFFSET_WJ::nDer; ++i ) + { + dofColIndices[ i ] = wellElemDofNumber[iwelemRef] + i; + } + // constraint data + real64 const & targetBHP = constraint.getConstraintValue( time_n ); + real64 const & refGravCoef = constraint.getReferenceGravityCoef(); + + // current constraint value + real64 const & currentBHP = + wellControls.getReference< real64 >( SinglePhaseWell::viewKeyStruct::currentBHPString() ); + + // residual + real64 controlEqn = currentBHP - targetBHP; + + // setup Jacobian terms + real64 dControlEqn[2+IS_THERMAL]{}; + + // bring everything back to host, capture the scalars by reference + forAll< serialPolicy >( 1, [pres, + density, + dDensity, + wellElemGravCoef, + &dControlEqn, + &iwelemRef, + &refGravCoef] ( localIndex const ) + { + real64 const diffGravCoef = refGravCoef - wellElemGravCoef[iwelemRef]; + dControlEqn[COFFSET_WJ::dP] = 1.0 + dDensity[iwelemRef][0][Deriv::dP] *diffGravCoef; + if constexpr ( IS_THERMAL ) + { + dControlEqn[COFFSET_WJ::dT] = dDensity[iwelemRef][0][Deriv::dT] * diffGravCoef; + } + } ); + + // add solver matrices + localRhs[eqnRowIndex] += controlEqn; + localMatrix.addToRowBinarySearchUnsorted< serialAtomic >( eqnRowIndex, + dofColIndices, + dControlEqn, + COFFSET_WJ::nDer ); + } + template< template< typename U > class T, typename U=VolumeRateConstraint > + static void assembleConstraintEquation( real64 const & time_n, + WellControls & wellControls, + T< VolumeRateConstraint > & constraint, + WellElementSubRegion const & subRegion, + string const & wellDofKey, + localIndex const & rankOffset, + CRSMatrixView< real64, globalIndex const > const & localMatrix, + arrayView1d< real64 > const & localRhs ) + { + // subRegion data + + localIndex const iwelemRef = subRegion.getTopWellElementIndex(); + arrayView1d< globalIndex const > const & wellElemDofNumber = subRegion.getReference< array1d< globalIndex > >( wellDofKey ); + + + // setup row/column indices for constraint equation + using ROFFSET_WJ = singlePhaseWellKernels::RowOffset_WellJac< IS_THERMAL >; + using COFFSET_WJ = singlePhaseWellKernels::ColOffset_WellJac< IS_THERMAL >; + using Deriv = constitutive::singlefluid::DerivativeOffsetC< IS_THERMAL >; + + localIndex const eqnRowIndex = wellElemDofNumber[iwelemRef] + ROFFSET_WJ::CONTROL - rankOffset; + globalIndex dofColIndices[COFFSET_WJ::nDer]{}; + for( integer i = 0; i < COFFSET_WJ::nDer; ++i ) + { + dofColIndices[ i ] = wellElemDofNumber[iwelemRef] + i; + } + + // fluid data + constitutive::SingleFluidBase & fluidSeparator = wellControls.getSingleFluidSeparator(); + arrayView2d< real64 const, constitutive::singlefluid::USD_FLUID > const & density = fluidSeparator.density(); + arrayView3d< real64 const, constitutive::singlefluid::USD_FLUID_DER > const & dDensity = fluidSeparator.dDensity(); + + // constraint data + real64 const & targetVolRate = constraint.getConstraintValue( time_n ); + + // current constraint value + real64 & currentVolRate = + wellControls.getReference< real64 >( WellControls::viewKeyStruct::currentVolRateString() ); + + integer const useSurfaceConditions = wellControls.useSurfaceConditions(); + + // residual + real64 controlEqn = currentVolRate - targetVolRate; + + // setup Jacobian terms + real64 dControlEqn[2+IS_THERMAL]{}; + + // bring everything back to host, capture the scalars by reference + forAll< serialPolicy >( 1, [currentVolRate, + density, + dDensity, + &dControlEqn, + &useSurfaceConditions, + &iwelemRef] ( localIndex const ) + { + // compute the inverse of the total density and derivatives + real64 const densInv = 1.0 / density[iwelemRef][0]; + + dControlEqn[COFFSET_WJ::dP] = -( useSurfaceConditions == 0 ) * dDensity[iwelemRef][0][Deriv::dP] * currentVolRate * densInv; + dControlEqn[COFFSET_WJ::dQ] = densInv; + if constexpr ( IS_THERMAL ) + { + dControlEqn[COFFSET_WJ::dT] = -( useSurfaceConditions == 0 ) * dDensity[iwelemRef][0][Deriv::dT] * currentVolRate * densInv; + } + + } ); + + // add solver matrices + localRhs[eqnRowIndex] += controlEqn; + localMatrix.addToRowBinarySearchUnsorted< serialAtomic >( eqnRowIndex, + dofColIndices, + dControlEqn, + COFFSET_WJ::nDer ); + } +}; + +} // end namespace wellConstraintKernels + +} // end namespace geos + +#endif //GEOS_PHYSICSSOLVERS_FLUIDFLOW_WELLS_WELLCONSTRAINTKERNELS_HPP diff --git a/src/coreComponents/physicsSolvers/fluidFlow/wells/kernels/SinglePhaseWellKernels.cpp b/src/coreComponents/physicsSolvers/fluidFlow/wells/kernels/SinglePhaseWellKernels.cpp index 5870c7c919c..d2846c9af53 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/wells/kernels/SinglePhaseWellKernels.cpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/wells/kernels/SinglePhaseWellKernels.cpp @@ -23,6 +23,7 @@ #include "physicsSolvers/fluidFlow/wells/SinglePhaseWell.hpp" #include "physicsSolvers/fluidFlow/wells/SinglePhaseWellFields.hpp" #include "constitutive/fluid/singlefluid/SingleFluidLayouts.hpp" + namespace geos { @@ -36,12 +37,12 @@ inline void ControlEquationHelper:: switchControl( bool const isProducer, - WellControls::Control const & currentControl, + ConstraintTypeId const & currentControl, real64 const & targetBHP, real64 const & targetRate, real64 const & currentBHP, real64 const & currentVolRate, - WellControls::Control & newControl ) + ConstraintTypeId & newControl ) { // if isViable is true at the end of the following checks, no need to switch bool controlIsViable = false; @@ -51,7 +52,7 @@ ControlEquationHelper:: // The well changes its mode of control whenever the existing control mode would // violate one of these limits. // BHP control - if( currentControl == WellControls::Control::BHP ) + if( currentControl == ConstraintTypeId::BHP ) { // the control is viable if the reference rate is below the max rate controlIsViable = ( LvArray::math::abs( currentVolRate ) <= LvArray::math::abs( targetRate ) + EPS ); @@ -79,9 +80,9 @@ ControlEquationHelper:: { // Note: if BHP control is not viable, we switch to TOTALVOLRATE // if TOTALVOLRATE are not viable, we switch to BHP - newControl = ( currentControl == WellControls::Control::BHP ) - ? WellControls::Control::TOTALVOLRATE - : WellControls::Control::BHP; + newControl = ( currentControl == ConstraintTypeId::BHP ) + ? ConstraintTypeId::TOTALVOLRATE + : ConstraintTypeId::BHP; } } @@ -91,7 +92,7 @@ inline void ControlEquationHelper:: compute( globalIndex const rankOffset, - WellControls::Control const currentControl, + ConstraintTypeId const currentControl, real64 const & targetBHP, real64 const & targetRate, real64 const & currentBHP, @@ -123,7 +124,7 @@ ControlEquationHelper:: // the well element that contains the reference elevation. // BHP control - if( currentControl == WellControls::Control::BHP ) + if( currentControl == ConstraintTypeId::BHP ) { // control equation is a difference between current BHP and target BHP controlEqn = currentBHP - targetBHP; @@ -133,7 +134,7 @@ ControlEquationHelper:: } // Total volumetric rate control - else if( currentControl == WellControls::Control::TOTALVOLRATE ) + else if( currentControl == ConstraintTypeId::TOTALVOLRATE ) { // control equation is the difference between volumetric current rate and target rate controlEqn = currentVolRate - targetRate; @@ -262,14 +263,10 @@ FluxKernel:: #define INST_PressureRelationKernel( IS_THERMAL ) \ template \ - localIndex \ + void \ PressureRelationKernel:: \ launch< IS_THERMAL >( localIndex const size, \ globalIndex const rankOffset, \ - bool const isLocallyOwned, \ - localIndex const iwelemControl, \ - WellControls const & wellControls, \ - real64 const & timeAtEndOfStep, \ arrayView1d< globalIndex const > const & wellElemDofNumber, \ arrayView1d< real64 const > const & wellElemGravCoef, \ arrayView1d< localIndex const > const & nextWellElemIndex, \ @@ -283,14 +280,10 @@ INST_PressureRelationKernel( 0 ); INST_PressureRelationKernel( 1 ); template< integer IS_THERMAL > -localIndex +void PressureRelationKernel:: launch( localIndex const size, globalIndex const rankOffset, - bool const isLocallyOwned, - localIndex const iwelemControl, - WellControls const & wellControls, - real64 const & time, arrayView1d< globalIndex const > const & wellElemDofNumber, arrayView1d< real64 const > const & wellElemGravCoef, arrayView1d< localIndex const > const & nextWellElemIndex, @@ -303,23 +296,6 @@ PressureRelationKernel:: using Deriv = constitutive::singlefluid::DerivativeOffset; using COFFSET_WJ = singlePhaseWellKernels::ColOffset_WellJac< IS_THERMAL >; // static well control data - bool const isProducer = wellControls.isProducer(); - WellControls::Control const currentControl = wellControls.getControl(); - real64 const targetBHP = wellControls.getTargetBHP( time ); - real64 const targetRate = wellControls.getTargetTotalRate( time ); - - // dynamic well control data - real64 const & currentBHP = - wellControls.getReference< real64 >( SinglePhaseWell::viewKeyStruct::currentBHPString() ); - arrayView1d< real64 const > const & dCurrentBHP = - wellControls.getReference< array1d< real64 > >( SinglePhaseWell::viewKeyStruct::dCurrentBHPString() ); - - real64 const & currentVolRate = - wellControls.getReference< real64 >( SinglePhaseWell::viewKeyStruct::currentVolRateString() ); - arrayView1d< real64 const > const & dCurrentVolRate = - wellControls.getReference< array1d< real64 > >( SinglePhaseWell::viewKeyStruct::dCurrentVolRateString() ); - - RAJA::ReduceMax< parallelDeviceReduce, localIndex > switchControl( 0 ); // loop over the well elements to compute the pressure relations between well elements forAll< parallelDevicePolicy<> >( size, [=] GEOS_HOST_DEVICE ( localIndex const iwelem ) @@ -327,34 +303,7 @@ PressureRelationKernel:: localIndex const iwelemNext = nextWellElemIndex[iwelem]; - if( iwelemNext < 0 && isLocallyOwned ) // if iwelemNext < 0, form control equation - { - WellControls::Control newControl = currentControl; - ControlEquationHelper::switchControl( isProducer, - currentControl, - targetBHP, - targetRate, - currentBHP, - currentVolRate, - newControl ); - if( currentControl != newControl ) - { - switchControl.max( 1 ); - } - - ControlEquationHelper::compute< IS_THERMAL >( rankOffset, - newControl, - targetBHP, - targetRate, - currentBHP, - dCurrentBHP, - currentVolRate, - dCurrentVolRate, - wellElemDofNumber[iwelemControl], - localMatrix, - localRhs ); - } - else if( iwelemNext >= 0 ) // if iwelemNext >= 0, form momentum equation + if( iwelemNext >= 0 ) // if iwelemNext >= 0, form momentum equation { // local working variables and arrays @@ -406,7 +355,6 @@ PressureRelationKernel:: } } } ); - return switchControl.get(); } /******************************** AccumulationKernel ********************************/ @@ -455,6 +403,7 @@ PresTempInitializationKernel:: localIndex const subRegionSize, localIndex const numPerforations, WellControls const & wellControls, + real64 const & refWellElemGravCoef, real64 const & currentTime, ElementViewConst< arrayView1d< real64 const > > const & resPressure, ElementViewConst< arrayView1d< real64 const > > const & resTemp, @@ -468,9 +417,8 @@ PresTempInitializationKernel:: arrayView1d< real64 > const & wellElemTemperature ) { real64 const targetBHP = wellControls.getTargetBHP( currentTime ); - real64 const refWellElemGravCoef = wellControls.getReferenceGravityCoef(); real64 const initialPressureCoef = wellControls.getInitialPressureCoefficient(); - WellControls::Control const currentControl = wellControls.getControl(); + ConstraintTypeId const currentControl = wellControls.getControl(); bool const isProducer = wellControls.isProducer(); @@ -529,7 +477,7 @@ PresTempInitializationKernel:: avgTemp = wellControls.getInjectionTemperature(); } // if the well is controlled by pressure, initialize the reference pressure at the target pressure - if( currentControl == WellControls::Control::BHP ) + if( currentControl == ConstraintTypeId::BHP ) { refPres = targetBHP; } @@ -581,7 +529,7 @@ PresTempInitializationKernel:: GEOS_THROW_IF( foundNegativePressure.get() == 1, "Invalid well initialization, negative pressure was found.", InputError, wellControls.getDataContext() ); - if( isThermal ) // tjb change temp in isothermal cases shouldnt be an issue (also what if temp in fluid prop calcs like compo) + if( isThermal ) { GEOS_THROW_IF( foundNegativeTemp.get() == 1, "Invalid well initialization, negative temperature was found.", @@ -599,29 +547,35 @@ RateInitializationKernel:: arrayView2d< real64 const, constitutive::singlefluid::USD_FLUID > const & wellElemDens, arrayView1d< real64 > const & connRate ) { - real64 const targetRate = wellControls.getTargetTotalRate( currentTime ); - WellControls::Control const control = wellControls.getControl(); + + ConstraintTypeId const control = wellControls.getControl(); bool const isProducer = wellControls.isProducer(); + auto const * rateConstraint = wellControls.getRateConstraints().front(); + real64 const constraintVal = rateConstraint->getConstraintValue( currentTime ); // Estimate the connection rates forAll< parallelDevicePolicy<> >( subRegionSize, [=] GEOS_HOST_DEVICE ( localIndex const iwelem ) { - if( control == WellControls::Control::BHP ) + if( control == ConstraintTypeId::BHP ) { // if BHP constraint set rate below the absolute max rate // with the appropriate sign (negative for prod, positive for inj) if( isProducer ) { - connRate[iwelem] = LvArray::math::max( 0.1 * targetRate * wellElemDens[iwelem][0], -1e3 ); + connRate[iwelem] = LvArray::math::max( 0.1 * constraintVal * wellElemDens[iwelem][0], -1e3 ); } else { - connRate[iwelem] = LvArray::math::min( 0.1 * targetRate * wellElemDens[iwelem][0], 1e3 ); + connRate[iwelem] = LvArray::math::min( 0.1 * constraintVal * wellElemDens[iwelem][0], 1e3 ); } } + else if( control == ConstraintTypeId::MASSRATE ) + { + connRate[iwelem] = constraintVal; + } else { - connRate[iwelem] = targetRate * wellElemDens[iwelem][0]; + connRate[iwelem] = constraintVal * wellElemDens[iwelem][0]; } } ); } diff --git a/src/coreComponents/physicsSolvers/fluidFlow/wells/kernels/SinglePhaseWellKernels.hpp b/src/coreComponents/physicsSolvers/fluidFlow/wells/kernels/SinglePhaseWellKernels.hpp index 5221162c73c..19d64b2d32b 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/wells/kernels/SinglePhaseWellKernels.hpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/wells/kernels/SinglePhaseWellKernels.hpp @@ -20,18 +20,23 @@ #ifndef GEOS_PHYSICSSOLVERS_FLUIDFLOW_WELLS_SINGLEPHASEWELLKERNELS_HPP #define GEOS_PHYSICSSOLVERS_FLUIDFLOW_WELLS_SINGLEPHASEWELLKERNELS_HPP +#include "common/DataTypes.hpp" +#include "common/GEOS_RAJA_Interface.hpp" +#include "physicsSolvers/PhysicsSolverBaseKernels.hpp" + #include "constitutive/fluid/singlefluid/SingleFluidFields.hpp" #include "constitutive/fluid/singlefluid/SingleFluidBase.hpp" #include "constitutive/fluid/singlefluid/SingleFluidLayouts.hpp" #include "constitutive/fluid/singlefluid/SingleFluidLayouts.hpp" -#include "common/DataTypes.hpp" -#include "common/GEOS_RAJA_Interface.hpp" + #include "mesh/ElementRegionManager.hpp" #include "physicsSolvers/fluidFlow/FlowSolverBaseFields.hpp" #include "physicsSolvers/fluidFlow/StencilAccessors.hpp" #include "physicsSolvers/fluidFlow/wells/WellControls.hpp" +#include "physicsSolvers/fluidFlow/wells/WellFields.hpp" #include "physicsSolvers/fluidFlow/wells/SinglePhaseWellFields.hpp" -#include "physicsSolvers/PhysicsSolverBaseKernels.hpp" +#include "physicsSolvers/fluidFlow/wells/WellConstraintsBase.hpp" + #include "physicsSolvers/KernelLaunchSelectors.hpp" namespace geos @@ -125,12 +130,12 @@ struct ControlEquationHelper static void switchControl( bool const isProducer, - WellControls::Control const & currentControl, + ConstraintTypeId const & currentControl, real64 const & targetBHP, real64 const & targetRate, real64 const & currentBHP, real64 const & currentVolRate, - WellControls::Control & newControl ); + ConstraintTypeId & newControl ); template< integer IS_THERMAL > GEOS_HOST_DEVICE @@ -138,7 +143,7 @@ struct ControlEquationHelper static void compute( globalIndex const rankOffset, - WellControls::Control const currentControl, + ConstraintTypeId const currentControl, real64 const & targetBHP, real64 const & targetRate, real64 const & currentBHP, @@ -185,13 +190,9 @@ struct PressureRelationKernel using TAG = singlePhaseWellKernels::ElemTag; template< integer IS_THERMAL > - static localIndex + static void launch( localIndex const size, globalIndex const rankOffset, - bool const isLocallyOwned, - localIndex const iwelemControl, - WellControls const & wellControls, - real64 const & time, arrayView1d< globalIndex const > const & wellElemDofNumber, arrayView1d< real64 const > const & wellElemGravCoef, arrayView1d< localIndex const > const & nextWellElemIndex, @@ -516,6 +517,7 @@ struct PresTempInitializationKernel localIndex const subRegionSize, localIndex const numPerforations, WellControls const & wellControls, + real64 const & refWellElemGravCoef, real64 const & currentTime, ElementViewConst< arrayView1d< real64 const > > const & resPressure, ElementViewConst< arrayView1d< real64 const > > const & resTemperature, @@ -811,9 +813,8 @@ class ResidualNormKernel : public physicsSolverBaseKernels::ResidualNormKernelBa m_isLocallyOwned( subRegion.isLocallyOwned() ), m_iwelemControl( subRegion.getTopWellElementIndex() ), m_currentControl( wellControls.getControl() ), - m_targetBHP( wellControls.getTargetBHP( time ) ), - m_targetRate( wellControls.getTargetTotalRate( time ) ), - m_targetMassRate( wellControls.getTargetMassRate( time ) ), + m_constraintValue ( wellControls.getCurrentConstraint()->getConstraintValue( time )), + m_targetBHP( wellControls.getTargetBHP( time ) ), // tjb fix for whp imposed bhp constraint m_volume( subRegion.getElementVolume() ), m_density_n( fluid.density_n() ) {} @@ -830,20 +831,20 @@ class ResidualNormKernel : public physicsSolverBaseKernels::ResidualNormKernelBa // for the top well element, normalize using the current control if( m_isLocallyOwned && iwelem == m_iwelemControl ) { - if( m_currentControl == WellControls::Control::BHP ) + if( m_currentControl == ConstraintTypeId::BHP ) { // this residual entry is in pressure units normalizer = m_targetBHP; } - else if( m_currentControl == WellControls::Control::TOTALVOLRATE ) + else if( m_currentControl == ConstraintTypeId::TOTALVOLRATE ) { // this residual entry is in volume / time units - normalizer = LvArray::math::max( LvArray::math::abs( m_targetRate ), m_minNormalizer ); + normalizer = LvArray::math::max( LvArray::math::abs( m_constraintValue ), m_minNormalizer ); } - else if( m_currentControl == WellControls::Control::MASSRATE ) + else if( m_currentControl == ConstraintTypeId::MASSRATE ) { // the residual entry is in volume / time units - normalizer = LvArray::math::max( LvArray::math::abs( m_targetMassRate ), m_minNormalizer ); + normalizer = LvArray::math::max( LvArray::math::abs( m_constraintValue ), m_minNormalizer ); } } // for the pressure difference equation, always normalize by the BHP @@ -856,7 +857,7 @@ class ResidualNormKernel : public physicsSolverBaseKernels::ResidualNormKernelBa else // SinglePhaseWell::RowOffset::MASSBAL { // this residual entry is in mass units - normalizer = m_dt * LvArray::math::abs( m_targetRate ) * m_density_n[iwelem][0]; + normalizer = m_dt * LvArray::math::abs( m_constraintValue ) * m_density_n[iwelem][0]; // to make sure that everything still works well if the rate is zero, we add this check normalizer = LvArray::math::max( normalizer, m_volume[iwelem] * m_density_n[iwelem][0] ); @@ -893,10 +894,10 @@ class ResidualNormKernel : public physicsSolverBaseKernels::ResidualNormKernelBa localIndex const m_iwelemControl; /// Controls - WellControls::Control const m_currentControl; - real64 const m_targetBHP; - real64 const m_targetRate; - real64 const m_targetMassRate; + ConstraintTypeId const m_currentControl; + real64 const m_constraintValue; + real64 m_targetBHP; + /// View on the volume arrayView1d< real64 const > const m_volume; diff --git a/src/coreComponents/physicsSolvers/fluidFlow/wells/kernels/ThermalCompositionalMultiphaseWellKernels.hpp b/src/coreComponents/physicsSolvers/fluidFlow/wells/kernels/ThermalCompositionalMultiphaseWellKernels.hpp index 54b7819e8bf..f3ec5b517a3 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/wells/kernels/ThermalCompositionalMultiphaseWellKernels.hpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/wells/kernels/ThermalCompositionalMultiphaseWellKernels.hpp @@ -22,6 +22,8 @@ #include "physicsSolvers/fluidFlow/wells/kernels/CompositionalMultiphaseWellKernels.hpp" #include "physicsSolvers/PhysicsSolverBaseKernels.hpp" +#include "physicsSolvers/fluidFlow/wells/WellConstraintsBase.hpp" +#include "physicsSolvers/fluidFlow/wells/WellPhaseVolumeRateConstraint.hpp" namespace geos { @@ -80,7 +82,7 @@ class TotalMassDensityKernel : public compositionalMultiphaseWellKernels::TotalM arraySlice2d< real64 const, multifluid::USD_PHASE_DC - 2 > dPhaseMassDens = m_dPhaseMassDens[ei][0]; real64 & dTotalMassDens_dT = m_dTotalMassDens[ei][Deriv::dT]; - dTotalMassDens_dT=0.0; + // Call the base compute the compute the total mass density and derivatives return Base::compute( ei, [&]( localIndex const ip ) { @@ -161,7 +163,7 @@ class ResidualNormKernel : public physicsSolverBaseKernels::ResidualNormKernelBa arrayView1d< real64 const > const & localResidual, arrayView1d< globalIndex const > const & dofNumber, arrayView1d< localIndex const > const & ghostRank, - integer const targetPhaseIndex, + WellElementSubRegion const & subRegion, MultiFluidBase const & fluid, WellControls const & wellControls, @@ -174,23 +176,33 @@ class ResidualNormKernel : public physicsSolverBaseKernels::ResidualNormKernelBa ghostRank, minNormalizer ), m_numPhases( fluid.numFluidPhases()), - m_targetPhaseIndex( targetPhaseIndex ), m_dt( dt ), m_isLocallyOwned( subRegion.isLocallyOwned() ), m_iwelemControl( subRegion.getTopWellElementIndex() ), m_isProducer( wellControls.isProducer() ), m_currentControl( wellControls.getControl() ), - m_targetBHP( wellControls.getTargetBHP( time ) ), - m_targetTotalRate( wellControls.getTargetTotalRate( time ) ), - m_targetPhaseRate( wellControls.getTargetPhaseRate( time ) ), - m_targetMassRate( wellControls.getTargetMassRate( time ) ), + m_targetBHP( std::numeric_limits< real64 >::max() ), m_volume( subRegion.getElementVolume() ), m_phaseDens_n( fluid.phaseDensity_n() ), m_totalDens_n( fluid.totalDensity_n() ), m_phaseVolFraction_n( subRegion.getField< fields::well::phaseVolumeFraction_n >()), m_phaseInternalEnergy_n( fluid.phaseInternalEnergy_n() ) - {} - + { + const WellConstraintBase * currentConstraint = wellControls.getCurrentConstraint(); + ConstraintTypeId currentControl = wellControls.getControl(); + if( currentControl == ConstraintTypeId::BHP ) + { + m_targetBHP = currentConstraint->getConstraintValue( time ); + } + else + { + m_constraintValue = currentConstraint->getConstraintValue( time ); + if( currentControl == ConstraintTypeId::PHASEVOLRATE ) + { + m_targetPhaseIndex = wellControls.getConstraintPhaseIndex(); + } + } + } GEOS_HOST_DEVICE void computeMassEnergyNormalizers( localIndex const iwelem, @@ -224,25 +236,25 @@ class ResidualNormKernel : public physicsSolverBaseKernels::ResidualNormKernelBa // for the top well element, normalize using the current control if( m_isLocallyOwned && iwelem == m_iwelemControl ) { - if( m_currentControl == WellControls::Control::BHP ) + if( m_currentControl == ConstraintTypeId::BHP ) { // the residual entry is in pressure units normalizer = m_targetBHP; } - else if( m_currentControl == WellControls::Control::TOTALVOLRATE ) + else if( m_currentControl == ConstraintTypeId::TOTALVOLRATE ) { // the residual entry is in volume / time units - normalizer = LvArray::math::max( LvArray::math::abs( m_targetTotalRate ), m_minNormalizer ); + normalizer = LvArray::math::max( LvArray::math::abs( m_constraintValue ), m_minNormalizer ); } - else if( m_currentControl == WellControls::Control::PHASEVOLRATE ) + else if( m_currentControl == ConstraintTypeId::PHASEVOLRATE ) { // the residual entry is in volume / time units - normalizer = LvArray::math::max( LvArray::math::abs( m_targetPhaseRate ), m_minNormalizer ); + normalizer = LvArray::math::max( LvArray::math::abs( m_constraintValue ), m_minNormalizer ); } - else if( m_currentControl == WellControls::Control::MASSRATE ) + else if( m_currentControl == ConstraintTypeId::MASSRATE ) { // the residual entry is in volume / time units - normalizer = LvArray::math::max( LvArray::math::abs( m_targetMassRate ), m_minNormalizer ); + normalizer = LvArray::math::max( LvArray::math::abs( m_constraintValue ), m_minNormalizer ); } } // for the pressure difference equation, always normalize by the BHP @@ -254,21 +266,21 @@ class ResidualNormKernel : public physicsSolverBaseKernels::ResidualNormKernelBa // Step 2: compute a normalizer for the mass balance equations else if( idof >= WJ_ROFFSET::MASSBAL && idof < WJ_ROFFSET::MASSBAL + numComp ) { - if( m_isProducer ) // only PHASEVOLRATE is supported for now + if( m_isProducer ) // only PHASEVOLRATE is supported for now { // the residual is in mass units - normalizer = m_dt * LvArray::math::abs( m_targetPhaseRate ) * m_phaseDens_n[iwelem][0][m_targetPhaseIndex]; + normalizer = m_dt * LvArray::math::abs( m_constraintValue ) * m_phaseDens_n[iwelem][0][m_targetPhaseIndex]; } - else // Type::INJECTOR, only TOTALVOLRATE is supported for now + else // Type::INJECTOR, only TOTALVOLRATE is supported for now { - if( m_currentControl == WellControls::Control::MASSRATE ) + if( m_currentControl == ConstraintTypeId::MASSRATE ) { - normalizer = m_dt * LvArray::math::abs( m_targetMassRate ); + normalizer = m_dt * LvArray::math::abs( m_constraintValue ); } else { // the residual is in mass units - normalizer = m_dt * LvArray::math::abs( m_targetTotalRate ) * m_totalDens_n[iwelem][0]; + normalizer = m_dt * LvArray::math::abs( m_constraintValue ) * m_totalDens_n[iwelem][0]; } } @@ -279,20 +291,20 @@ class ResidualNormKernel : public physicsSolverBaseKernels::ResidualNormKernelBa // Step 3: compute a normalizer for the volume balance equations else if( idof == WJ_ROFFSET::VOLBAL ) { - if( m_isProducer ) // only PHASEVOLRATE is supported for now + if( m_isProducer ) // only PHASEVOLRATE is supported for now { // the residual is in volume units - normalizer = m_dt * LvArray::math::abs( m_targetPhaseRate ); + normalizer = m_dt * LvArray::math::abs( m_constraintValue ); } - else // Type::INJECTOR, only TOTALVOLRATE is supported for now + else // Type::INJECTOR, only TOTALVOLRATE is supported for now { - if( m_currentControl == WellControls::Control::MASSRATE ) + if( m_currentControl == ConstraintTypeId::MASSRATE ) { - normalizer = m_dt * LvArray::math::abs( m_targetMassRate/ m_totalDens_n[iwelem][0] ); + normalizer = m_dt * LvArray::math::abs( m_constraintValue/ m_totalDens_n[iwelem][0] ); } else { - normalizer = m_dt * LvArray::math::abs( m_targetTotalRate ); + normalizer = m_dt * LvArray::math::abs( m_constraintValue ); } } @@ -339,7 +351,7 @@ class ResidualNormKernel : public physicsSolverBaseKernels::ResidualNormKernelBa integer const m_numPhases; /// Index of the target phase - integer const m_targetPhaseIndex; + integer m_targetPhaseIndex; /// Time step size real64 const m_dt; @@ -354,11 +366,10 @@ class ResidualNormKernel : public physicsSolverBaseKernels::ResidualNormKernelBa bool const m_isProducer; /// Controls - WellControls::Control const m_currentControl; - real64 const m_targetBHP; - real64 const m_targetTotalRate; - real64 const m_targetPhaseRate; - real64 const m_targetMassRate; + ConstraintTypeId const m_currentControl; + real64 m_constraintValue; + real64 m_targetBHP; + /// View on the volume arrayView1d< real64 const > const m_volume; @@ -383,7 +394,6 @@ class ResidualNormKernelFactory * @tparam POLICY the policy used in the RAJA kernel * @param[in] numComp number of fluid components * @param[in] numDof number of dofs per well element - * @param[in] targetPhaseIndex the index of the target phase (for phase volume control) * @param[in] rankOffset the offset of my MPI rank * @param[in] dofKey the string key to retrieve the degress of freedom numbers * @param[in] localResidual the residual vector on my MPI rank @@ -397,7 +407,6 @@ class ResidualNormKernelFactory template< typename POLICY > static void createAndLaunch( integer const numComp, - integer const targetPhaseIndex, globalIndex const rankOffset, string const & dofKey, arrayView1d< real64 const > const & localResidual, @@ -418,7 +427,7 @@ class ResidualNormKernelFactory arrayView1d< integer const > const ghostRank = subRegion.ghostRank(); kernelType kernel( rankOffset, localResidual, dofNumber, ghostRank, - targetPhaseIndex, subRegion, fluid, wellControls, time, dt, minNormalizer ); + subRegion, fluid, wellControls, time, dt, minNormalizer ); kernelType::template launchLinf< POLICY >( subRegion.size(), kernel, residualNorm ); } ); } @@ -476,6 +485,7 @@ class ElementBasedAssemblyKernel : public compositionalMultiphaseWellKernels::El * @param[inout] localRhs the local right-hand side vector */ ElementBasedAssemblyKernel( localIndex const numPhases, + bool const thermalEffectsEnabled, integer const isProducer, globalIndex const rankOffset, string const dofKey, @@ -484,7 +494,7 @@ class ElementBasedAssemblyKernel : public compositionalMultiphaseWellKernels::El CRSMatrixView< real64, globalIndex const > const & localMatrix, arrayView1d< real64 > const & localRhs, BitFlags< isothermalCompositionalMultiphaseBaseKernels::KernelFlags > const kernelFlags ) - : Base( numPhases, isProducer, rankOffset, dofKey, subRegion, fluid, localMatrix, localRhs, kernelFlags ), + : Base( numPhases, thermalEffectsEnabled, isProducer, rankOffset, dofKey, subRegion, fluid, localMatrix, localRhs, kernelFlags ), m_phaseInternalEnergy_n( fluid.phaseInternalEnergy_n()), m_phaseInternalEnergy( fluid.phaseInternalEnergy()), m_dPhaseInternalEnergy( fluid.dPhaseInternalEnergy()) @@ -636,6 +646,7 @@ class ElementBasedAssemblyKernelFactory static void createAndLaunch( localIndex const numComps, localIndex const numPhases, + integer const thermalEffectsEnabled, integer const isProducer, globalIndex const rankOffset, BitFlags< isothermalCompositionalMultiphaseBaseKernels::KernelFlags > kernelFlags, @@ -645,13 +656,12 @@ class ElementBasedAssemblyKernelFactory CRSMatrixView< real64, globalIndex const > const & localMatrix, arrayView1d< real64 > const & localRhs ) { - isothermalCompositionalMultiphaseBaseKernels:: - internal::kernelLaunchSelectorCompSwitch( numComps, [&]( auto NC ) + isothermalCompositionalMultiphaseBaseKernels::internal::kernelLaunchSelectorCompSwitch( numComps, [&]( auto NC ) { localIndex constexpr NUM_COMP = NC(); ElementBasedAssemblyKernel< NUM_COMP > - kernel( numPhases, isProducer, rankOffset, dofKey, subRegion, fluid, localMatrix, localRhs, kernelFlags ); + kernel( numPhases, thermalEffectsEnabled, isProducer, rankOffset, dofKey, subRegion, fluid, localMatrix, localRhs, kernelFlags ); ElementBasedAssemblyKernel< NUM_COMP >::template launch< POLICY, ElementBasedAssemblyKernel< NUM_COMP > >( subRegion.size(), kernel ); } ); @@ -731,6 +741,7 @@ class FaceBasedAssemblyKernel : public compositionalMultiphaseWellKernels::FaceB , localRhs , kernelFlags ), m_numPhases ( fluid.numFluidPhases()), + m_thermalEffectsEnabled( wellControls.thermalEffectsEnabled() ), m_globalWellElementIndex( subRegion.getGlobalWellElementIndex() ), m_phaseFraction( fluid.phaseFraction()), m_dPhaseFraction( fluid.dPhaseFraction()), @@ -777,7 +788,8 @@ class FaceBasedAssemblyKernel : public compositionalMultiphaseWellKernels::FaceB void complete( localIndex const iwelem, StackVariables & stack ) const { Base::complete ( iwelem, stack ); - + // tjb iso return; + if( !m_thermalEffectsEnabled ) return; using namespace compositionalMultiphaseUtilities; if( stack.numConnectedElems ==1 ) { @@ -939,7 +951,7 @@ class FaceBasedAssemblyKernel : public compositionalMultiphaseWellKernels::FaceB stack.localEnergyFlux[0] = -m_dt * eflux * currentConnRate; stack.localEnergyFluxJacobian_dQ[0][0] = -m_dt * eflux_dq; } - else if( ( iwelemNext < 0 && m_isProducer ) || currentConnRate < 0 ) // exit connection, producer + else if( ( iwelemNext < 0 && m_isProducer ) ) // exit connection, producer { real64 eflux=0; real64 eflux_dq=0; @@ -966,9 +978,9 @@ class FaceBasedAssemblyKernel : public compositionalMultiphaseWellKernels::FaceB for( integer dof=0; dof < CP_Deriv::nDer; dof++ ) { - stack.localEnergyFluxJacobian[0][dof] *= -m_dt*currentConnRate; + stack.localEnergyFluxJacobian[0][dof] *= -m_dt*currentConnRate; } - stack.localEnergyFlux[0] = -m_dt * eflux * currentConnRate; + stack.localEnergyFlux[0] = -m_dt * eflux * currentConnRate; stack.localEnergyFluxJacobian_dQ[0][0] = -m_dt*eflux_dq; } else @@ -1047,6 +1059,8 @@ class FaceBasedAssemblyKernel : public compositionalMultiphaseWellKernels::FaceB protected: /// Number of phases integer const m_numPhases; + /// Flag specifying whether thermal effects are enabled + bool const m_thermalEffectsEnabled; /// Global index of local element arrayView1d< globalIndex const > m_globalWellElementIndex; diff --git a/src/coreComponents/physicsSolvers/fluidFlow/wells/kernels/ThermalSinglePhaseWellKernels.hpp b/src/coreComponents/physicsSolvers/fluidFlow/wells/kernels/ThermalSinglePhaseWellKernels.hpp index f736826561c..317a0082b81 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/wells/kernels/ThermalSinglePhaseWellKernels.hpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/wells/kernels/ThermalSinglePhaseWellKernels.hpp @@ -522,14 +522,23 @@ class ResidualNormKernel : public physicsSolverBaseKernels::ResidualNormKernelBa m_iwelemControl( subRegion.getTopWellElementIndex() ), m_isProducer( wellControls.isProducer() ), m_currentControl( wellControls.getControl() ), - m_targetBHP( wellControls.getTargetBHP( time ) ), - m_targetRate( wellControls.getTargetTotalRate( time ) ), - m_targetMassRate( wellControls.getTargetMassRate( time ) ), + m_constraintValue ( wellControls.getCurrentConstraint()->getConstraintValue( time )), m_volume( subRegion.getElementVolume() ), m_density_n( fluid.density_n() ), m_internalEnergy_n( fluid.internalEnergy_n() ) - {} + { + const WellConstraintBase * currentConstraint = wellControls.getCurrentConstraint(); + if( currentConstraint->getControl() == ConstraintTypeId::BHP ) + { + m_targetBHP = currentConstraint->getConstraintValue( time ); + } + else + { + m_targetBHP = std::numeric_limits< real64 >::max(); + } + + } GEOS_HOST_DEVICE @@ -560,20 +569,20 @@ class ResidualNormKernel : public physicsSolverBaseKernels::ResidualNormKernelBa // for the top well element, normalize using the current control if( m_isLocallyOwned && iwelem == m_iwelemControl ) { - if( m_currentControl == WellControls::Control::BHP ) + if( m_currentControl == ConstraintTypeId::BHP ) { // the residual entry is in pressure units normalizer = m_targetBHP; } - else if( m_currentControl == WellControls::Control::TOTALVOLRATE ) + else if( m_currentControl == ConstraintTypeId::TOTALVOLRATE ) { // the residual entry is in volume / time units - normalizer = LvArray::math::max( LvArray::math::abs( m_targetRate ), m_minNormalizer ); + normalizer = LvArray::math::max( LvArray::math::abs( m_constraintValue ), m_minNormalizer ); } - else if( m_currentControl == WellControls::Control::MASSRATE ) + else if( m_currentControl == ConstraintTypeId::MASSRATE ) { // the residual entry is in volume / time units - normalizer = LvArray::math::max( LvArray::math::abs( m_targetMassRate ), m_minNormalizer ); + normalizer = LvArray::math::max( LvArray::math::abs( m_constraintValue ), m_minNormalizer ); } } // for the pressure difference equation, always normalize by the BHP @@ -587,7 +596,7 @@ class ResidualNormKernel : public physicsSolverBaseKernels::ResidualNormKernelBa { // this residual entry is in mass units - normalizer = m_dt * LvArray::math::abs( m_targetRate ) * m_density_n[iwelem][0]; + normalizer = m_dt * LvArray::math::abs( m_constraintValue ) * m_density_n[iwelem][0]; // to make sure that everything still works well if the rate is zero, we add this check normalizer = LvArray::math::max( normalizer, m_volume[iwelem] * m_density_n[iwelem][0] ); @@ -639,10 +648,9 @@ class ResidualNormKernel : public physicsSolverBaseKernels::ResidualNormKernelBa bool const m_isProducer; /// Controls - WellControls::Control const m_currentControl; - real64 const m_targetBHP; - real64 const m_targetRate; - real64 const m_targetMassRate; + ConstraintTypeId const m_currentControl; + real64 const m_constraintValue; + real64 m_targetBHP; /// View on the volume arrayView1d< real64 const > const m_volume; diff --git a/src/coreComponents/physicsSolvers/fluidFlow/wells/unitTests/testWellEnums.cpp b/src/coreComponents/physicsSolvers/fluidFlow/wells/unitTests/testWellEnums.cpp index 27b318e4511..e8fb82d1c93 100644 --- a/src/coreComponents/physicsSolvers/fluidFlow/wells/unitTests/testWellEnums.cpp +++ b/src/coreComponents/physicsSolvers/fluidFlow/wells/unitTests/testWellEnums.cpp @@ -14,7 +14,7 @@ */ #include "physicsSolvers/fluidFlow/wells/WellControls.hpp" - +#include "physicsSolvers/fluidFlow/wells/WellConstraintsBase.hpp" #include using namespace geos; @@ -34,7 +34,7 @@ TEST( WellControlsEnums, Type ) TEST( WellControlsEnums, Control ) { - using EnumType = WellControls::Control; + using EnumType = ConstraintTypeId; ASSERT_EQ( "BHP", toString( EnumType::BHP ) ); ASSERT_EQ( "phaseVolRate", toString( EnumType::PHASEVOLRATE ) ); diff --git a/src/coreComponents/physicsSolvers/multiphysics/CompositionalMultiphaseReservoirAndWells.cpp b/src/coreComponents/physicsSolvers/multiphysics/CompositionalMultiphaseReservoirAndWells.cpp index c0c49411f53..11a8ed584e4 100644 --- a/src/coreComponents/physicsSolvers/multiphysics/CompositionalMultiphaseReservoirAndWells.cpp +++ b/src/coreComponents/physicsSolvers/multiphysics/CompositionalMultiphaseReservoirAndWells.cpp @@ -27,6 +27,7 @@ #include "physicsSolvers/fluidFlow/CompositionalMultiphaseHybridFVM.hpp" #include "physicsSolvers/fluidFlow/CompositionalMultiphaseUtilities.hpp" #include "physicsSolvers/fluidFlow/LogLevelsInfo.hpp" +#include "physicsSolvers/fluidFlow/CompositionalMultiphaseStatisticsAggregator.hpp" #include "physicsSolvers/fluidFlow/wells/CompositionalMultiphaseWell.hpp" #include "physicsSolvers/fluidFlow/wells/WellControls.hpp" #include "physicsSolvers/fluidFlow/wells/kernels/CompositionalMultiphaseWellKernels.hpp" @@ -147,11 +148,10 @@ initializePreSubGroups() { Base::initializePreSubGroups(); - CompositionalMultiphaseBase const * const flowSolver = this->flowSolver(); + CompositionalMultiphaseBase * const flowSolver = this->flowSolver(); Base::wellSolver()->setFlowSolverName( flowSolver->getName() ); - bool const useMassFlow = flowSolver->getReference< integer >( CompositionalMultiphaseBase::viewKeyStruct::useMassFlagString() ); - bool const useMassWell = Base::wellSolver()->template getReference< integer >( CompositionalMultiphaseWell::viewKeyStruct::useMassFlagString() ); + bool const useMassWell = Base::wellSolver()->template getReference< integer >( WellManager::viewKeyStruct::useMassFlagString() ); GEOS_THROW_IF( useMassFlow != useMassWell, GEOS_FMT( "The input flag {} must be the same in the flow and well solvers, respectively '{}' and '{}'", CompositionalMultiphaseBase::viewKeyStruct::useMassFlagString(), @@ -159,12 +159,47 @@ initializePreSubGroups() InputError, this->getDataContext(), Base::reservoirSolver()->getDataContext(), Base::wellSolver()->getDataContext() ); bool const isThermalFlow = flowSolver->getReference< integer >( CompositionalMultiphaseBase::viewKeyStruct::isThermalString() ); - bool const isThermalWell = Base::wellSolver()->template getReference< integer >( CompositionalMultiphaseWell::viewKeyStruct::isThermalString() ); + bool const isThermalWell = Base::wellSolver()->template getReference< integer >( WellManager::viewKeyStruct::isThermalString() ); GEOS_THROW_IF( isThermalFlow != isThermalWell, GEOS_FMT( "The input flag {} must be the same in the flow and well solvers, respectively '{}' and '{}'", CompositionalMultiphaseBase::viewKeyStruct::isThermalString(), Base::reservoirSolver()->getName(), Base::wellSolver()->getName() ), InputError, this->getDataContext(), Base::reservoirSolver()->getDataContext(), Base::wellSolver()->getDataContext() ); + DomainPartition & domain = this->template getGroupByPath< DomainPartition >( "/Problem/domain" ); + + Group & meshBodies = domain.getMeshBodies(); + this->template forDiscretizationOnMeshTargets<>( meshBodies, [&] ( string const &, + MeshLevel & mesh, + string_array const & regionNames ) + { + ElementRegionManager & elemManager = mesh.getElemManager(); + elemManager.forElementSubRegions< WellElementSubRegion >( regionNames, [&]( localIndex const, + WellElementSubRegion const & subRegion ) + { + WellControls & wellControls = Base::wellSolver()->getWellControls( subRegion ); + CompositionalMultiphaseWell & compositionalMultiphaseWell = dynamic_cast< CompositionalMultiphaseWell & >( wellControls ); + wellControls.setFlowSolverName( flowSolver->getName() ); + wellControls.setDiscretizationName( flowSolver->getDiscretizationName() ); + + if( !wellControls.useSurfaceConditions() ) + { + string_view refRegionName = wellControls.referenceReservoirRegion(); + bool const useSegmentValues = refRegionName.empty(); + if( !useSegmentValues ) + { + if( !compositionalMultiphaseWell.getStatsAggregator() ) + { // lazily initialize the region statistics aggregator + auto aggregator = std::make_unique< compositionalMultiphaseStatistics::StatsAggregator >( compositionalMultiphaseWell.getDataContext(), + meshBodies, + false ); + aggregator->initStatisticsAggregation( *flowSolver ); + aggregator->enableRegionStatisticsAggregation(); + compositionalMultiphaseWell.setReservoirStatsAggregator( std::move( aggregator ) ); + } + } + } + } ); + } ); } template< typename RESERVOIR_SOLVER > @@ -345,6 +380,7 @@ assembleCouplingTerms( real64 const time_n, coupledReservoirAndWellKernels:: ThermalCompositionalMultiPhaseFluxKernelFactory:: createAndLaunch< parallelDevicePolicy<> >( numComps, + wellControls.thermalEffectsEnabled( ), wellControls.isProducer(), dt, rankOffset, diff --git a/src/coreComponents/physicsSolvers/multiphysics/CompositionalMultiphaseReservoirAndWells.hpp b/src/coreComponents/physicsSolvers/multiphysics/CompositionalMultiphaseReservoirAndWells.hpp index b2b1f7a4800..4a460846dba 100644 --- a/src/coreComponents/physicsSolvers/multiphysics/CompositionalMultiphaseReservoirAndWells.hpp +++ b/src/coreComponents/physicsSolvers/multiphysics/CompositionalMultiphaseReservoirAndWells.hpp @@ -23,20 +23,25 @@ #include "physicsSolvers/multiphysics/CoupledReservoirAndWellsBase.hpp" #include "physicsSolvers/fluidFlow/CompositionalMultiphaseBase.hpp" -#include "physicsSolvers/fluidFlow/wells/CompositionalMultiphaseWell.hpp" +#include "physicsSolvers/fluidFlow/wells/WellManager.hpp" namespace geos { +namespace compositionalMultiphaseStatistics +{ +class StatsAggregator; +} + /// @tparam RESERVOIR_SOLVER compositional flow or compositional poromechanics solver template< typename RESERVOIR_SOLVER = CompositionalMultiphaseBase > class CompositionalMultiphaseReservoirAndWells : public CoupledReservoirAndWellsBase< RESERVOIR_SOLVER, - CompositionalMultiphaseWell > + WellManager > { public: using Base = CoupledReservoirAndWellsBase< RESERVOIR_SOLVER, - CompositionalMultiphaseWell >; + WellManager >; using Base::getLogLevel; using Base::m_solvers; using Base::m_linearSolverParameters; diff --git a/src/coreComponents/physicsSolvers/multiphysics/CoupledReservoirAndWellKernels.hpp b/src/coreComponents/physicsSolvers/multiphysics/CoupledReservoirAndWellKernels.hpp index 5d106df3156..05592b5725a 100644 --- a/src/coreComponents/physicsSolvers/multiphysics/CoupledReservoirAndWellKernels.hpp +++ b/src/coreComponents/physicsSolvers/multiphysics/CoupledReservoirAndWellKernels.hpp @@ -329,7 +329,263 @@ class IsothermalCompositionalMultiPhaseFluxKernelFactory } }; +/** + * @class FaceBasedAssemblyKernel + * @tparam NUM_COMP number of fluid components + * @brief Define the interface for the assembly kernel in charge of flux terms + */ +template< integer NC, integer IS_THERMAL > +class IsothermalCompositionalMultiPhaseWellFluxKernel +{ +public: + + /// Compile time value for the number of components + static constexpr integer numComp = NC; + static constexpr integer resNumDOF = NC+1+IS_THERMAL; + + // Well jacobian column and row indicies + using WJ_COFFSET = compositionalMultiphaseWellKernels::ColOffset_WellJac< NC, IS_THERMAL >; + using WJ_ROFFSET = compositionalMultiphaseWellKernels::RowOffset_WellJac< NC, IS_THERMAL >; + + using ROFFSET = compositionalMultiphaseWellKernels::RowOffset; + using COFFSET = compositionalMultiphaseWellKernels::ColOffset; + + using CP_Deriv = multifluid::DerivativeOffsetC< NC, IS_THERMAL >; + + using TAG = compositionalMultiphaseWellKernels::SubRegionTag; + + + + /// Compute time value for the number of degrees of freedom + static constexpr integer numDof = WJ_COFFSET::nDer; + + /// Compile time value for the number of equations except volume and momentum + static constexpr integer numEqn = WJ_ROFFSET::nEqn - 2; + + /** + * @brief Constructor for the kernel interface + * @param[in] rankOffset the offset of my MPI rank + * @param[in] stencilWrapper reference to the stencil wrapper + * @param[in] dofNumberAccessor + * @param[in] compFlowAccessors + * @param[in] multiFluidAccessors + * @param[in] capPressureAccessors + * @param[in] permeabilityAccessors + * @param[in] dt time step size + * @param[inout] localMatrix the local CRS matrix + * @param[inout] localRhs the local right-hand side vector + * @param[in] kernelFlags flags packed together + */ + IsothermalCompositionalMultiPhaseWellFluxKernel( real64 const dt, + globalIndex const rankOffset, + string const wellDofKey, + WellElementSubRegion const & subRegion, + PerforationData const * const perforationData, + MultiFluidBase const & fluid, + arrayView1d< real64 > const & localRhs, + CRSMatrixView< real64, globalIndex const > const & localMatrix, + BitFlags< isothermalCompositionalMultiphaseBaseKernels::KernelFlags > kernelFlags ) + : + m_dt( dt ), + m_numPhases ( fluid.numFluidPhases()), + m_rankOffset( rankOffset ), + m_compPerfRate( perforationData->getField< fields::well::compPerforationRate >() ), + m_dCompPerfRate( perforationData->getField< fields::well::dCompPerforationRate >() ), + m_perfWellElemIndex( perforationData->getField< fields::perforation::wellElementIndex >() ), + m_wellElemDofNumber( subRegion.getReference< array1d< globalIndex > >( wellDofKey ) ), + m_localRhs( localRhs ), + m_localMatrix( localMatrix ), + m_useTotalMassEquation ( kernelFlags.isSet( isothermalCompositionalMultiphaseBaseKernels::KernelFlags::TotalMassEquation ) ) + { } + + + /** + * @brief Compute the local flux contributions to the residual and Jacobian + * @tparam FUNC the type of the function that can be used to customize the computation of the phase fluxes + * @param[in] ie the element index + * @param[inout] stack the stack variables + * @param[in] compFluxKernelOp the function used to customize the computation of the component fluxes + */ + + template< typename FUNC = NoOpFunc > + GEOS_HOST_DEVICE + inline + void computeFlux( localIndex const iperf, + FUNC && compFluxKernelOp = NoOpFunc{} ) const + { + + using namespace compositionalMultiphaseUtilities; + // local working variables and arrays + stackArray1d< localIndex, numComp > eqnRowIndices( numComp ); + stackArray1d< globalIndex, resNumDOF > dofColIndices( resNumDOF ); + + stackArray1d< real64, numComp > localPerf( numComp ); + stackArray2d< real64, numComp *resNumDOF > localPerfJacobian( numComp, resNumDOF ); + + // get the reservoir (sub)region and element indices + //localIndex const er = m_resElementRegion[iperf]; + //localIndex const esr = m_resElementSubRegion[iperf]; + //localIndex const ei = m_resElementIndex[iperf]; + + // get the well element index for this perforation + localIndex const iwelem = m_perfWellElemIndex[iperf]; + //globalIndex const resOffset = m_resElemDofNumber[er][esr][ei]; + globalIndex const wellElemOffset = m_wellElemDofNumber[iwelem]; + + for( integer ic = 0; ic < numComp; ++ic ) + { + eqnRowIndices[ ic] = LvArray::integerConversion< localIndex >( wellElemOffset - m_rankOffset ) + WJ_ROFFSET::MASSBAL + ic; + } + for( integer jdof = 0; jdof < NC+1; ++jdof ) + { + dofColIndices[ jdof] = wellElemOffset + WJ_COFFSET::dP + jdof; + } + // For temp its different + if constexpr ( IS_THERMAL ) + { + dofColIndices[ NC+1 ] = wellElemOffset + WJ_COFFSET::dT; + } + // populate local flux vector and derivatives + + for( integer ic = 0; ic < numComp; ++ic ) + { + localPerf[ic] = -m_dt * m_compPerfRate[iperf][ic]; + } + for( integer ic = 0; ic < numComp; ++ic ) + { + localIndex localDofIndexPres = 0; + + localPerfJacobian[ic][localDofIndexPres] = -m_dt * m_dCompPerfRate[iperf][TAG::WELL ][ic][CP_Deriv::dP]; + for( integer jc = 0; jc < numComp; ++jc ) + { + localIndex const localDofIndexComp = localDofIndexPres + jc + 1; + localPerfJacobian[ic][localDofIndexComp] = -m_dt * m_dCompPerfRate[iperf][TAG::WELL ][ic][CP_Deriv::dC+jc]; + } + if constexpr ( IS_THERMAL ) + { + localIndex localDofIndexTemp = localDofIndexPres + NC + 1; + localPerfJacobian[ic][localDofIndexTemp] = -m_dt * m_dCompPerfRate[iperf][TAG::WELL ][ic][CP_Deriv::dT]; + } + } + + if( m_useTotalMassEquation ) + { + stackArray1d< real64, resNumDOF > work( resNumDOF ); + shiftBlockRowsAheadByOneAndReplaceFirstRowWithColumnSum( numComp, numComp, resNumDOF, 1, localPerfJacobian, work ); + + // Apply equation/variable change transformation(s) + shiftBlockElementsAheadByOneAndReplaceFirstElementWithSum( numComp, numComp, 1, localPerf ); + } + + for( localIndex i = 0; i < localPerf.size(); ++i ) + { + if( eqnRowIndices[i] >= 0 && eqnRowIndices[i] < m_localMatrix.numRows() ) + { + m_localMatrix.addToRowBinarySearchUnsorted< parallelDeviceAtomic >( eqnRowIndices[i], + dofColIndices.data(), + localPerfJacobian[i].dataIfContiguous(), + resNumDOF ); + RAJA::atomicAdd( parallelDeviceAtomic{}, &m_localRhs[eqnRowIndices[i]], localPerf[i] ); + } + } + + compFluxKernelOp( wellElemOffset, iwelem, dofColIndices ); + + } + + +/** + * @brief Performs the kernel launch + * @tparam POLICY the policy used in the RAJA kernels + * @tparam KERNEL_TYPE the kernel type + * @param[in] numElements the number of elements + * @param[inout] kernelComponent the kernel component providing access to setup/compute/complete functions and stack + * variables + */ + template< typename POLICY, typename KERNEL_TYPE > + static void + launch( localIndex const numElements, + KERNEL_TYPE const & kernelComponent ) + { + GEOS_MARK_FUNCTION; + forAll< POLICY >( numElements, [=] GEOS_HOST_DEVICE ( localIndex const ie ) + { + kernelComponent.computeFlux( ie ); + + } ); + } + +protected: + +/// Time step size + real64 const m_dt; + +/// Number of phases + integer const m_numPhases; + + globalIndex const m_rankOffset; +// Perfoation variables + arrayView2d< real64 const > const m_compPerfRate; + arrayView4d< real64 const > const m_dCompPerfRate; + arrayView1d< localIndex const > const m_perfWellElemIndex; + +// Element region, subregion, index + arrayView1d< globalIndex const > const m_wellElemDofNumber; + +// RHS and Jacobian + arrayView1d< real64 > const m_localRhs; + CRSMatrixView< real64, globalIndex const > m_localMatrix; + + integer const m_useTotalMassEquation; +}; + +/** + * @class FaceBasedAssemblyKernelFactory + */ +class IsothermalCompositionalMultiPhaseWellFluxKernelFactory +{ +public: + + /** + * @brief Create a new kernel and launch + * @tparam POLICY the policy used in the RAJA kernel + * @param[in] numComps the number of fluid components + * @param[in] dt time step size + * @param[in] rankOffset the offset of my MPI rank + * @param[in] useTotalMassEquation flag specifying whether to replace one component bal eqn with total mass eqn + * @param[in] dofKey string to get the element degrees of freedom numbers + * @param[in] wellControls object holding well control/constraint information + * @param[in] subregion well subregion + * @param[inout] localMatrix the local CRS matrix + * @param[inout] localRhs the local right-hand side vector + */ + template< typename POLICY > + static void + createAndLaunch( integer const numComps, + real64 const dt, + globalIndex const rankOffset, + string const wellDofKey, + WellElementSubRegion const & subRegion, + PerforationData const * const perforationData, + MultiFluidBase const & fluid, + arrayView1d< real64 > const & localRhs, + CRSMatrixView< real64, globalIndex const > const & localMatrix, + BitFlags< isothermalCompositionalMultiphaseBaseKernels::KernelFlags > kernelFlags ) + { + isothermalCompositionalMultiphaseBaseKernels::internal::kernelLaunchSelectorCompSwitch( numComps, [&]( auto NC ) + { + integer constexpr NUM_COMP = NC(); + + using kernelType = IsothermalCompositionalMultiPhaseWellFluxKernel< NUM_COMP, 0 >; + kernelType kernel( dt, rankOffset, wellDofKey, subRegion, perforationData, + fluid, localRhs, localMatrix, kernelFlags ); + kernelType::template launch< POLICY >( perforationData->size(), kernel ); + } ); + + } +}; +/********************************************************/ /** * @class FaceBasedAssemblyKernel * @tparam NUM_COMP number of fluid components @@ -383,6 +639,7 @@ class ThermalCompositionalMultiPhaseFluxKernel : public IsothermalCompositionalM * @param[in] kernelFlags flags packed together */ ThermalCompositionalMultiPhaseFluxKernel( real64 const dt, + bool const thermalEffectsEnabled, integer const isProducer, globalIndex const rankOffset, string const wellDofKey, @@ -407,6 +664,7 @@ class ThermalCompositionalMultiPhaseFluxKernel : public IsothermalCompositionalM detectCrossflow, numCrossFlowPerforations, kernelFlags ), + m_thermalEffectsEnabled( thermalEffectsEnabled ), m_isProducer( isProducer ), m_globalWellElementIndex( subRegion.getGlobalWellElementIndex() ), m_energyPerfFlux( perforationData->getField< fields::well::energyPerforationFlux >()), @@ -432,6 +690,8 @@ class ThermalCompositionalMultiPhaseFluxKernel : public IsothermalCompositionalM stackArray1d< globalIndex, 2*resNumDOF > & dofColIndices, localIndex const iwelem ) { + if( !m_thermalEffectsEnabled ) + return; // No energy equation if top element and Injector // Top element defined by global index == 0 // Assumption is global index == 0 is top segment with fixed temp BC @@ -441,7 +701,7 @@ class ThermalCompositionalMultiPhaseFluxKernel : public IsothermalCompositionalM return; } // local working variables and arrays - stackArray1d< localIndex, 2* numComp > eqnRowIndices( 2 ); + stackArray1d< localIndex, 2* numComp > eqnRowIndices( 2* numComp ); stackArray1d< real64, 2 * numComp > localPerf( 2 ); stackArray2d< real64, 2 * resNumDOF * 2 * numComp > localPerfJacobian( 2, 2 * resNumDOF ); @@ -510,6 +770,8 @@ class ThermalCompositionalMultiPhaseFluxKernel : public IsothermalCompositionalM } protected: + /// Flag specifying whether thermal effects are enabled + bool const m_thermalEffectsEnabled; /// Well type integer const m_isProducer; @@ -545,6 +807,7 @@ class ThermalCompositionalMultiPhaseFluxKernelFactory template< typename POLICY > static void createAndLaunch( integer const numComps, + integer const thermalEffectsEnabled, integer const isProducer, real64 const dt, globalIndex const rankOffset, @@ -565,7 +828,7 @@ class ThermalCompositionalMultiPhaseFluxKernelFactory integer constexpr NUM_COMP = NC(); using kernelType = ThermalCompositionalMultiPhaseFluxKernel< NUM_COMP, 1 >; - kernelType kernel( dt, isProducer, rankOffset, wellDofKey, subRegion, resDofNumber, perforationData, + kernelType kernel( dt, thermalEffectsEnabled, isProducer, rankOffset, wellDofKey, subRegion, resDofNumber, perforationData, fluid, localRhs, localMatrix, detectCrossflow, numCrossFlowPerforations, kernelFlags ); kernelType::template launch< POLICY >( perforationData->size(), kernel ); } ); @@ -573,6 +836,234 @@ class ThermalCompositionalMultiPhaseFluxKernelFactory } }; +/********************************************************/ +/** + * @class FaceBasedAssemblyKernel + * @tparam NUM_COMP number of fluid components + * @brief Define the interface for the assembly kernel in charge of flux terms + */ +template< integer NC, integer IS_THERMAL > +class ThermalCompositionalMultiPhaseWellFluxKernel : public IsothermalCompositionalMultiPhaseWellFluxKernel< NC, IS_THERMAL > +{ +public: + using Base = IsothermalCompositionalMultiPhaseWellFluxKernel< NC, IS_THERMAL >; + /// Compile time value for the number of components + static constexpr integer numComp = NC; + static constexpr integer resNumDOF = NC+1+IS_THERMAL; + + // Well jacobian column and row indicies + using WJ_COFFSET = compositionalMultiphaseWellKernels::ColOffset_WellJac< NC, IS_THERMAL >; + using WJ_ROFFSET = compositionalMultiphaseWellKernels::RowOffset_WellJac< NC, IS_THERMAL >; + + using ROFFSET = compositionalMultiphaseWellKernels::RowOffset; + using COFFSET = compositionalMultiphaseWellKernels::ColOffset; + + using CP_Deriv = multifluid::DerivativeOffsetC< NC, IS_THERMAL >; + + using TAG = compositionalMultiphaseWellKernels::SubRegionTag; + + using Base::m_dt; + using Base::m_localRhs; + using Base::m_localMatrix; + using Base::m_rankOffset; + + + + /// Compute time value for the number of degrees of freedom + static constexpr integer numDof = WJ_COFFSET::nDer; + + /// Compile time value for the number of equations except volume and momentum + static constexpr integer numEqn = WJ_ROFFSET::nEqn - 2; + + /** + * @brief Constructor for the kernel interface + * @param[in] rankOffset the offset of my MPI rank + * @param[in] stencilWrapper reference to the stencil wrapper + * @param[in] dofNumberAccessor + * @param[in] compFlowAccessors + * @param[in] multiFluidAccessors + * @param[in] capPressureAccessors + * @param[in] permeabilityAccessors + * @param[in] dt time step size + * @param[inout] localMatrix the local CRS matrix + * @param[inout] localRhs the local right-hand side vector + * @param[in] kernelFlags flags packed together + */ + ThermalCompositionalMultiPhaseWellFluxKernel( real64 const dt, + bool const thermalEffectsEnabled, + integer const isProducer, + globalIndex const rankOffset, + string const wellDofKey, + WellElementSubRegion const & subRegion, + PerforationData const * const perforationData, + MultiFluidBase const & fluid, + arrayView1d< real64 > const & localRhs, + CRSMatrixView< real64, globalIndex const > const & localMatrix, + BitFlags< isothermalCompositionalMultiphaseBaseKernels::KernelFlags > kernelFlags ) + : Base( dt, + rankOffset, + wellDofKey, + subRegion, + perforationData, + fluid, + localRhs, + localMatrix, + kernelFlags ), + m_thermalEffectsEnabled( thermalEffectsEnabled ), + m_isProducer( isProducer ), + m_globalWellElementIndex( subRegion.getGlobalWellElementIndex() ), + m_energyPerfFlux( perforationData->getField< fields::well::energyPerforationFlux >()), + m_dEnergyPerfFlux( perforationData->getField< fields::well::dEnergyPerforationFlux >()) + + { } + + + /** + * @brief Compute the local flux contributions to the residual and Jacobian + * @tparam FUNC the type of the function that can be used to customize the computation of the phase fluxes + * @param[in] ie the element index + * @param[inout] stack the stack variables + * @param[in] compFluxKernelOp the function used to customize the computation of the component fluxes + */ + + GEOS_HOST_DEVICE + inline + void computeFlux( localIndex const iperf ) const + { + Base::computeFlux( iperf, [&] ( globalIndex const & wellElemOffset, + localIndex const iwelem, + stackArray1d< globalIndex, resNumDOF > & dofColIndices ) + { + GEOS_UNUSED_VAR( dofColIndices ); + if( !m_thermalEffectsEnabled ) // tjb iso + return; + // No energy equation if top element and Injector + // Top element defined by global index == 0 + // Assumption is global index == 0 is top segment with fixed temp BC + if( !m_isProducer ) + { + if( m_globalWellElementIndex[iwelem] == 0 ) + return; + } + // local working variables and arrays + localIndex eqnRowIndices = LvArray::integerConversion< localIndex >( wellElemOffset - m_rankOffset ) + WJ_ROFFSET::ENERGYBAL; + + stackArray2d< real64, resNumDOF > localPerfJacobian( 1, resNumDOF ); + // populate local flux vector and derivatives + + real64 localPerf = -m_dt * m_energyPerfFlux[iperf]; + + // std::cout << "Local perf: " << iperf << " " << localPerf << std::endl; + localIndex localDofIndexPres = 0; + localPerfJacobian [0][localDofIndexPres] = -m_dt * m_dEnergyPerfFlux[iperf][TAG::WELL][CP_Deriv::dP]; + + // populate local flux vector and derivatives + for( integer ic = 0; ic < numComp; ++ic ) + { + localIndex const localDofIndexComp = localDofIndexPres + ic + 1; + localPerfJacobian [0][localDofIndexComp] = -m_dt * m_dEnergyPerfFlux[iperf][TAG::WELL ][CP_Deriv::dC+ic]; + } + localPerfJacobian [0][localDofIndexPres+NC+1] = -m_dt * m_dEnergyPerfFlux[iperf][TAG::WELL ][CP_Deriv::dT]; + + if( eqnRowIndices >= 0 && eqnRowIndices < m_localMatrix.numRows() ) + { + // tjb iso + m_localMatrix.template addToRowBinarySearchUnsorted< parallelDeviceAtomic >( eqnRowIndices, + dofColIndices.data(), + localPerfJacobian[0].dataIfContiguous(), + resNumDOF ); + RAJA::atomicAdd( parallelDeviceAtomic{}, &m_localRhs[eqnRowIndices], localPerf ); + } + } ); + + + } + + + /** + * @brief Performs the kernel launch + * @tparam POLICY the policy used in the RAJA kernels + * @tparam KERNEL_TYPE the kernel type + * @param[in] numElements the number of elements + * @param[inout] kernelComponent the kernel component providing access to setup/compute/complete functions and stack + * variables + */ + template< typename POLICY, typename KERNEL_TYPE > + static void + launch( localIndex const numElements, + KERNEL_TYPE const & kernelComponent ) + { + GEOS_MARK_FUNCTION; + forAll< POLICY >( numElements, [=] GEOS_HOST_DEVICE ( localIndex const ie ) + { + kernelComponent.computeFlux( ie ); + + } ); + } + +protected: + /// Thermal effects enabled + bool const m_thermalEffectsEnabled; + /// Well type + integer const m_isProducer; + + /// Global index of local element + arrayView1d< globalIndex const > m_globalWellElementIndex; + + /// Views on energy flux + arrayView1d< real64 const > const m_energyPerfFlux; + arrayView3d< real64 const > const m_dEnergyPerfFlux; +}; + +/** + * @class ThermalCompositionalMultiPhaseFluxWellKernelFactory + */ +class ThermalCompositionalMultiPhaseWellFluxKernelFactory +{ +public: + + /** + * @brief Create a new kernel and launch + * @tparam POLICY the policy used in the RAJA kernel + * @param[in] numComps the number of fluid components + * @param[in] dt time step size + * @param[in] rankOffset the offset of my MPI rank + * @param[in] useTotalMassEquation flag specifying whether to replace one component bal eqn with total mass eqn + * @param[in] dofKey string to get the element degrees of freedom numbers + * @param[in] wellControls object holding well control/constraint information + * @param[in] subregion well subregion + * @param[inout] localMatrix the local CRS matrix + * @param[inout] localRhs the local right-hand side vector + */ + template< typename POLICY > + static void + createAndLaunch( integer const numComps, + WellControls const & wellControls, + integer const isProducer, + real64 const dt, + globalIndex const rankOffset, + string const wellDofKey, + WellElementSubRegion const & subRegion, + PerforationData const * const perforationData, + MultiFluidBase const & fluid, + BitFlags< isothermalCompositionalMultiphaseBaseKernels::KernelFlags > kernelFlags, + arrayView1d< real64 > const & localRhs, + CRSMatrixView< real64, globalIndex const > const & localMatrix + ) + { + isothermalCompositionalMultiphaseBaseKernels::internal::kernelLaunchSelectorCompSwitch( numComps, [&]( auto NC ) + { + integer constexpr NUM_COMP = NC(); + + using kernelType = ThermalCompositionalMultiPhaseWellFluxKernel< NUM_COMP, 1 >; + kernelType kernel( dt, wellControls.thermalEffectsEnabled(), isProducer, rankOffset, wellDofKey, subRegion, perforationData, + fluid, localRhs, localMatrix, kernelFlags ); + kernelType::template launch< POLICY >( perforationData->size(), kernel ); + } ); + + } +}; + } // end namespace coupledReservoirAndWellKernels } // end namespace geos diff --git a/src/coreComponents/physicsSolvers/multiphysics/CoupledReservoirAndWellsBase.cpp b/src/coreComponents/physicsSolvers/multiphysics/CoupledReservoirAndWellsBase.cpp index 1982739401f..3f544f2160d 100644 --- a/src/coreComponents/physicsSolvers/multiphysics/CoupledReservoirAndWellsBase.cpp +++ b/src/coreComponents/physicsSolvers/multiphysics/CoupledReservoirAndWellsBase.cpp @@ -116,7 +116,7 @@ addCouplingNumNonzeros( PhysicsSolverBase const * const solver, } bool validateWellPerforations( PhysicsSolverBase const * const reservoirSolver, - WellSolverBase const * const wellSolver, + WellManager const * const wellSolver, DomainPartition const & domain ) { std::pair< string, string > badPerforation; diff --git a/src/coreComponents/physicsSolvers/multiphysics/CoupledReservoirAndWellsBase.hpp b/src/coreComponents/physicsSolvers/multiphysics/CoupledReservoirAndWellsBase.hpp index 32a67843aec..c7a1f3ea030 100644 --- a/src/coreComponents/physicsSolvers/multiphysics/CoupledReservoirAndWellsBase.hpp +++ b/src/coreComponents/physicsSolvers/multiphysics/CoupledReservoirAndWellsBase.hpp @@ -29,7 +29,7 @@ #include "mesh/PerforationFields.hpp" #include "mesh/DomainPartition.hpp" #include "physicsSolvers/fluidFlow/wells/WellControls.hpp" -#include "physicsSolvers/fluidFlow/wells/WellSolverBase.hpp" +#include "physicsSolvers/fluidFlow/wells/WellManager.hpp" namespace geos { @@ -65,7 +65,7 @@ addCouplingNumNonzeros( PhysicsSolverBase const * const solver, * @param domain the physical domain object */ bool validateWellPerforations( PhysicsSolverBase const * const reservoirSolver, - WellSolverBase const * const wellSolver, + WellManager const * const wellSolver, DomainPartition const & domain ); } diff --git a/src/coreComponents/physicsSolvers/multiphysics/SinglePhaseReservoirAndWells.cpp b/src/coreComponents/physicsSolvers/multiphysics/SinglePhaseReservoirAndWells.cpp index ca582820992..898be9c3055 100644 --- a/src/coreComponents/physicsSolvers/multiphysics/SinglePhaseReservoirAndWells.cpp +++ b/src/coreComponents/physicsSolvers/multiphysics/SinglePhaseReservoirAndWells.cpp @@ -137,6 +137,29 @@ initializePreSubGroups() Base::initializePreSubGroups(); SinglePhaseBase const * const flowSolver = this->flowSolver(); Base::wellSolver()->setFlowSolverName( flowSolver->getName() ); + + bool const isThermalFlow = flowSolver->getReference< integer >( SinglePhaseBase::viewKeyStruct::isThermalString() ); + bool const isThermalWell = Base::wellSolver()->template getReference< integer >( WellManager::viewKeyStruct::isThermalString() ); + GEOS_THROW_IF( isThermalFlow != isThermalWell, + GEOS_FMT( "{}: the input flag {} must be the same in the flow and well solvers, respectively '{}' and '{}'", + this->getDataContext(), SinglePhaseBase::viewKeyStruct::isThermalString(), + Base::reservoirSolver()->getDataContext(), Base::wellSolver()->getDataContext() ), + InputError, this->getDataContext(), Base::reservoirSolver()->getDataContext(), Base::wellSolver()->getDataContext() ); + DomainPartition & domain = this->template getGroupByPath< DomainPartition >( "/Problem/domain" ); + + this->template forDiscretizationOnMeshTargets<>( domain.getMeshBodies(), [&] ( string const &, + MeshLevel & mesh, + string_array const & regionNames ) + { + ElementRegionManager & elemManager = mesh.getElemManager(); + elemManager.forElementSubRegions< WellElementSubRegion >( regionNames, [&]( localIndex const, + WellElementSubRegion const & subRegion ) + { + WellControls & wellControls = Base::wellSolver()->getWellControls( subRegion ); + wellControls.setFlowSolverName( flowSolver->getName() ); + + } ); + } ); } template< typename RESERVOIR_SOLVER > diff --git a/src/coreComponents/physicsSolvers/multiphysics/SinglePhaseReservoirAndWells.hpp b/src/coreComponents/physicsSolvers/multiphysics/SinglePhaseReservoirAndWells.hpp index 0adeee58026..98da0ab2737 100644 --- a/src/coreComponents/physicsSolvers/multiphysics/SinglePhaseReservoirAndWells.hpp +++ b/src/coreComponents/physicsSolvers/multiphysics/SinglePhaseReservoirAndWells.hpp @@ -23,7 +23,7 @@ #include "physicsSolvers/multiphysics/CoupledReservoirAndWellsBase.hpp" #include "physicsSolvers/fluidFlow/SinglePhaseBase.hpp" -#include "physicsSolvers/fluidFlow/wells/SinglePhaseWell.hpp" +#include "physicsSolvers/fluidFlow/wells/WellManager.hpp" namespace geos { @@ -31,12 +31,12 @@ namespace geos /// @tparam RESERVOIR_SOLVER single-phase flow or single-phase poromechanics solver template< typename RESERVOIR_SOLVER = SinglePhaseBase > class SinglePhaseReservoirAndWells : public CoupledReservoirAndWellsBase< RESERVOIR_SOLVER, - SinglePhaseWell > + WellManager > { public: using Base = CoupledReservoirAndWellsBase< RESERVOIR_SOLVER, - SinglePhaseWell >; + WellManager >; using Base::m_solvers; using Base::m_linearSolverParameters; diff --git a/src/coreComponents/physicsSolvers/solidMechanics/contact/SolidMechanicsAugmentedLagrangianContact.cpp b/src/coreComponents/physicsSolvers/solidMechanics/contact/SolidMechanicsAugmentedLagrangianContact.cpp index c4145ee9318..a928891a726 100644 --- a/src/coreComponents/physicsSolvers/solidMechanics/contact/SolidMechanicsAugmentedLagrangianContact.cpp +++ b/src/coreComponents/physicsSolvers/solidMechanics/contact/SolidMechanicsAugmentedLagrangianContact.cpp @@ -1257,7 +1257,7 @@ bool SolidMechanicsAugmentedLagrangianContact::updateConfiguration( DomainPartit { ElementRegionManager & elemManager = mesh.getElemManager(); - elemManager.forElementSubRegions< FaceElementSubRegion >( regionNames, [m_symmetric=m_symmetric]( localIndex const, + elemManager.forElementSubRegions< FaceElementSubRegion >( regionNames, [symmetric = m_symmetric]( localIndex const, FaceElementSubRegion & subRegion ) { @@ -1293,7 +1293,7 @@ bool SolidMechanicsAugmentedLagrangianContact::updateConfiguration( DomainPartit oldDispJump, dispJump, iterativePenalty, - m_symmetric, + symmetric, normalTractionTolerance, traction, fractureState ); diff --git a/src/coreComponents/physicsSolvers/solidMechanics/contact/docs/SolidMechanicsEmbeddedFractures.rst b/src/coreComponents/physicsSolvers/solidMechanics/contact/docs/SolidMechanicsEmbeddedFractures.rst index 5cf3a8ce3f9..5d39212805a 100644 --- a/src/coreComponents/physicsSolvers/solidMechanics/contact/docs/SolidMechanicsEmbeddedFractures.rst +++ b/src/coreComponents/physicsSolvers/solidMechanics/contact/docs/SolidMechanicsEmbeddedFractures.rst @@ -43,9 +43,9 @@ The following data are allocated and used by the solver: References ========== -1. Simo JC, Rifai MS. A class of mixed assumed strain methods and the method of incompatible modes. *Int J Numer Methods Eng.* 1990;29(8):1595-1638. Available at: http://arxiv.org/abs/https://onlinelibrary.wiley.com/doi/pdf/10.1002/nme.1620290802. +1. Simo JC, Rifai MS. A class of mixed assumed strain methods and the method of incompatible modes. *Int J Numer Methods Eng.* 1990;29(8):1595-1638. Available at: https://onlinelibrary.wiley.com/doi/pdf/10.1002/nme.1620290802. -2. Foster CD, Borja RI, Regueiro RA. Embedded strong discontinuity finite elements for fractured geomaterials with variable friction. *Int J Numer Methods Eng.* 2007;72(5):549-581. Available at: http://arxiv.org/abs/https://onlinelibrary.wiley.com/doi/pdf/10.1002/nme.2020. +2. Foster CD, Borja RI, Regueiro RA. Embedded strong discontinuity finite elements for fractured geomaterials with variable friction. *Int J Numer Methods Eng.* 2007;72(5):549-581. Available at: https://onlinelibrary.wiley.com/doi/pdf/10.1002/nme.2020. 3. Wells G, Sluys L. Three-dimensional embedded discontinuity model for brittle fracture. *Int J Solids Struct.* 2001;38(5):897-913. Available at: https://doi.org/10.1016/S0020-7683(00)00029-9. diff --git a/src/coreComponents/schema/schema.xsd b/src/coreComponents/schema/schema.xsd index e78160e9ce0..ebfc86d5b58 100644 --- a/src/coreComponents/schema/schema.xsd +++ b/src/coreComponents/schema/schema.xsd @@ -363,10 +363,6 @@ - - - - @@ -511,10 +507,6 @@ - - - - @@ -543,6 +535,10 @@ + + + + @@ -2788,12 +2784,6 @@ Information output from lower logLevels is added with the desired log level - - - - - - @@ -2830,12 +2820,6 @@ Information output from lower logLevels is added with the desired log level - - - - - - @@ -2843,6 +2827,16 @@ Information output from lower logLevels is added with the desired log level + + + + + + + + + + @@ -3682,144 +3676,6 @@ When set to `all` output both convergence & iteration information to a csv.--> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -5725,52 +5581,6 @@ Local- Add jump stabilization on interior of macro elements--> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -6256,6 +6066,445 @@ When set to `all` output both convergence & iteration information to a csv.--> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/coreComponents/schema/schema.xsd.other b/src/coreComponents/schema/schema.xsd.other index 12b9eae3e1d..acd3c24b757 100644 --- a/src/coreComponents/schema/schema.xsd.other +++ b/src/coreComponents/schema/schema.xsd.other @@ -570,7 +570,6 @@ A field can represent a physical variable. (pressure, temperature, global compos - @@ -607,7 +606,6 @@ A field can represent a physical variable. (pressure, temperature, global compos - @@ -615,6 +613,7 @@ A field can represent a physical variable. (pressure, temperature, global compos + @@ -867,27 +866,6 @@ A field can represent a physical variable. (pressure, temperature, global compos - - - - - - - - - - - - - - - - - - - - - @@ -1342,18 +1320,6 @@ A field can represent a physical variable. (pressure, temperature, global compos - - - - - - - - - - - - @@ -1497,6 +1463,69 @@ A field can represent a physical variable. (pressure, temperature, global compos + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -2443,11 +2472,11 @@ A field can represent a physical variable. (pressure, temperature, global compos - + - + diff --git a/src/docs/sphinx/Publications.rst b/src/docs/sphinx/Publications.rst index 9a81c0f20ff..baad1ae944f 100644 --- a/src/docs/sphinx/Publications.rst +++ b/src/docs/sphinx/Publications.rst @@ -2,7 +2,7 @@ Publications ############################################################################### -Last updated 10-October-2025 +Last updated 07-July-2026 Referencing GEOS ========================= @@ -20,25 +20,6 @@ Referencing GEOS Preprints and Early-Views ========================= -.. list-table:: - :widths: 100 - :header-rows: 0 - - * - | **A robust framework for frictional fault contact in geological formations using a stabilized augmented Lagrangian approach** - | M Frigo, N Castelletto, M Cusini, RR Settgast, HA Tchelepi - | arXiv preprint - | `arXiv:2509.20528 `_ - - * - | **Recurrent Transformer U-Net surrogate for flow modeling and data assimilation in subsurface formations with faults** - | Y Han, LJ Durlofsky - | arXiv preprint - | `arXiv:2508.16631 `_ - - * - | **Semi-Analytical Approaches for Addressing Thermal Debonding Issues in Cased Wellbores** - | T Nguyen-Sy, J Huang, H Gross, FP Hamon - | SSRN preprint - | `doi:10.2139/ssrn.5208966 `_ - 2026 ==== @@ -52,6 +33,31 @@ Preprints and Early-Views | Computer Methods in Applied Mechanics and Engineering | `doi:10.1016/j.cma.2025.118399 `_ + * - | **A robust framework for frictional fault contact in geological formations using a stabilized augmented Lagrangian approach** + | M Frigo, N Castelletto, M Cusini, RR Settgast, HA Tchelepi + | Journal of Computational Physics + | `doi:10.1016/j.jcp.2026.114988 `_ + + * - | **Recurrent Transformer U-Net surrogate for flow modeling and data assimilation in subsurface formations with faults** + | Y Han, LJ Durlofsky + | Journal of Computational Physics + | `doi:10.1016/j.jcp.2026.114801 `_ + + * - | **Simulation of Multiphase Flow and Poromechanical Effects Around Injection Wells in CO2 Storage Sites** + | J Huang, F Hamon, M Cusini, T Gazzola, RR Settgast, JA White, H Gross + | Rock Mechanics and Rock Engineering + | `doi:10.1007/s00603-024-04051-w `_ + + * - | **High-Resolution Simulations of Geological CO2 Injection: Application to the SPE11 Benchmark** + | D Kachuma, R Hasanzade, P Tomin, ME Thomadakis, J Franc, VAP Magri, TJ Byer, M Cusini, RR Settgast, H Gross, N Castelletto + | SPE Journal + | `doi:10.2118/231182-PA `_ + + * - | **Basin-scale analysis of Mokelumne River Formation for multi-well CO2 injection** + | A Zibitsker, B Schmidt, T Byer, N Castelletto, J Iyer + | International Journal of Greenhouse Gas Control + | `doi:10.1016/j.ijggc.2026.104703 `_ + 2025 ==== @@ -113,11 +119,6 @@ Preprints and Early-Views | Advances in Water Resources | `doi:10.1016/j.advwatres.2024.104678 `_ - * - | **Simulation of Multiphase Flow and Poromechanical Effects Around Injection Wells in CO2 Storage Sites** - | J Huang, F Hamon, M Cusini, T Gazzola, RR Settgast, JA White, H Gross - | Rock Mechanics and Rock Engineering - | `doi:10.1007/s00603-024-04051-w `_ - * - | **Learning CO2 plume migration in faulted reservoirs with Graph Neural Networks** | X Ju, FP Hamon, G Wen, R Kanfar, M Araya-Polo, HA Tchelepi | Computers & Geosciences diff --git a/src/docs/sphinx/advancedExamples/validationStudies/carbonStorage/buckleyLeverett/Example.rst b/src/docs/sphinx/advancedExamples/validationStudies/carbonStorage/buckleyLeverett/Example.rst index 9aedba305d5..12705ec12c8 100644 --- a/src/docs/sphinx/advancedExamples/validationStudies/carbonStorage/buckleyLeverett/Example.rst +++ b/src/docs/sphinx/advancedExamples/validationStudies/carbonStorage/buckleyLeverett/Example.rst @@ -9,7 +9,7 @@ **Context** In this example, we simulate a CO2 core flood experiment representing immiscible transport of two-phase -flow (CO2 and water) through porous media `(Ekechukwu et al., 2022) `__. This problem is solved using the multiphase flow solver in GEOS to obtain the temporal evolution of saturation along the flow direction, and verified against the Buckley-Leverett analytical solutions `(Buckley and Leverett, 1942; `__ `Arabzai and Honma, 2013) `__. +flow (CO2 and water) through porous media `(Ekechukwu et al., 2022) `__. This problem is solved using the multiphase flow solver in GEOS to obtain the temporal evolution of saturation along the flow direction, and verified against the Buckley-Leverett analytical solutions `(Buckley and Leverett, 1942; `__ `Arabzai and Honma, 2013) `__. **Input file** diff --git a/src/docs/sphinx/advancedExamples/validationStudies/hydraulicFracture/kgdValidation/Example.rst b/src/docs/sphinx/advancedExamples/validationStudies/hydraulicFracture/kgdValidation/Example.rst index 95d84bb7b4c..29c8ba27eb5 100644 --- a/src/docs/sphinx/advancedExamples/validationStudies/hydraulicFracture/kgdValidation/Example.rst +++ b/src/docs/sphinx/advancedExamples/validationStudies/hydraulicFracture/kgdValidation/Example.rst @@ -8,7 +8,7 @@ Validating KGD Hydraulic Fracture with Experiment **Context** -In this example, we use GEOS to model a planar hydraulic fracture propagating in a finite domain subject to traction-free external boundaries. Contrary to the classic KGD problems, we do not assume an infinite rock domain. Existing analytical solutions cannot model fracture behavior in this scenario, so this problem is solved using the hydrofracture solver in GEOS. We validate the simulation results against a benchmark experiment `(Rubin, 1983) `__. +In this example, we use GEOS to model a planar hydraulic fracture propagating in a finite domain subject to traction-free external boundaries. Contrary to the classic KGD problems, we do not assume an infinite rock domain. Existing analytical solutions cannot model fracture behavior in this scenario, so this problem is solved using the hydrofracture solver in GEOS. We validate the simulation results against a benchmark experiment `(Rubin, 1983) `__. **Input file** diff --git a/src/docs/sphinx/advancedExamples/validationStudies/thermoPoromechanics/thermalConsolidation/Example.rst b/src/docs/sphinx/advancedExamples/validationStudies/thermoPoromechanics/thermalConsolidation/Example.rst index 07922a086b9..3b887ca1ef5 100644 --- a/src/docs/sphinx/advancedExamples/validationStudies/thermoPoromechanics/thermalConsolidation/Example.rst +++ b/src/docs/sphinx/advancedExamples/validationStudies/thermoPoromechanics/thermalConsolidation/Example.rst @@ -8,7 +8,7 @@ Thermoporoelastic Consolidation **Context** Thermoporoelastic consolidation is a typical fully coupled problem which involves solid deformation, fluid flow and heat transfer in saturated porous media. -In this example, we use the GEOS coupled solvers to solve a one-dimensional thermoporoelastic consolidation problem with a non-isothermal boundary condition, and we verify the accuracy of the results using the analytical solution provided in `(Bai, 2005) `__ +In this example, we use the GEOS coupled solvers to solve a one-dimensional thermoporoelastic consolidation problem with a non-isothermal boundary condition, and we verify the accuracy of the results using the analytical solution provided in `(Bai, 2005) `__ **InputFile** @@ -187,19 +187,30 @@ On the top surface, we impose the traction boundary condition and the non-isothe Inspecting results --------------------------------- -We request an output of the displacements, pressure, and temperature using the **TimeHistory** feature of GEOS. -The figures below compare the results from GEOS (dashed line) and the corresponding analytical solution (solid line) as a function of time at different locations of the slab. -We obtain a very good match, confirming that GEOS can accurately capture the thermo-poromechanical coupling on this example. The first figure illustrates this good agreement for the pressure evolution. - -.. plot:: docs/sphinx/advancedExamples/validationStudies/thermoPoromechanics/thermalConsolidation/thermalConsolidationPressureFigure.py - -The second figure confirms the good match with the analytical solution for the temperature. - -.. plot:: docs/sphinx/advancedExamples/validationStudies/thermoPoromechanics/thermalConsolidation/thermalConsolidationTemperatureFigure.py - -The third figure shows that GEOS is also able to match the vertical displacement (settlement) analytical solution. - -.. plot:: docs/sphinx/advancedExamples/validationStudies/thermoPoromechanics/thermalConsolidation/thermalConsolidationDisplacementFigure.py + We request an output of the displacements, pressure, temperature, and stress using the + **TimeHistory** feature of GEOS. + The figure below compares the results from GEOS (open circles) with the corresponding + analytical solution (solid lines) as a function of time at different locations along the slab. + We obtain a very good match for all four quantities, confirming that GEOS accurately captures + the thermo-poromechanical coupling on this example. + + - The top-left panel shows the excellent agreement for the **pore pressure** evolution: + the initial thermally- and mechanically-induced overpressure gradually dissipates as the + fluid drains through the top surface. + - The top-right panel confirms the good match for the **temperature**, which diffuses from + the heated top boundary into the column. + - The bottom-left panel shows that GEOS also reproduces the **vertical displacement** + (settlement), including the characteristic non-monotonic response (thermal expansion + followed by consolidation). + - The bottom-right panel compares the **total stress**. By equilibrium, the total vertical + stress remains equal to the applied surface load at all times and depths + (:math:`\sigma_{yy} = -F`), which GEOS recovers exactly. The total horizontal stress + :math:`\sigma_{xx} = \sigma_{zz}` follows the uniaxial-strain thermo-poroelastic relation + :math:`\sigma_{xx} = -(\lambda/M)\,F - (2G/M)\,(3K\alpha_s\,\Delta T + b\,p)` + (with :math:`M = K + 4G/3` the oedometric modulus), exhibiting the transient overshoot + driven by the coupled pressure and thermal fields. + + .. plot:: docs/sphinx/advancedExamples/validationStudies/thermoPoromechanics/thermalConsolidation/plotTests_tutorial_updated.py ------------------------------------------------------------------ To go further diff --git a/src/docs/sphinx/advancedExamples/validationStudies/thermoPoromechanics/thermalConsolidation/displacementHistory.hdf5 b/src/docs/sphinx/advancedExamples/validationStudies/thermoPoromechanics/thermalConsolidation/displacementHistory.hdf5 deleted file mode 100644 index 7c6f53df872..00000000000 Binary files a/src/docs/sphinx/advancedExamples/validationStudies/thermoPoromechanics/thermalConsolidation/displacementHistory.hdf5 and /dev/null differ diff --git a/src/docs/sphinx/advancedExamples/validationStudies/thermoPoromechanics/thermalConsolidation/plotTests_tutorial_updated.py b/src/docs/sphinx/advancedExamples/validationStudies/thermoPoromechanics/thermalConsolidation/plotTests_tutorial_updated.py new file mode 100755 index 00000000000..d7fb3cf8c41 --- /dev/null +++ b/src/docs/sphinx/advancedExamples/validationStudies/thermoPoromechanics/thermalConsolidation/plotTests_tutorial_updated.py @@ -0,0 +1,540 @@ +import matplotlib +import matplotlib.pyplot as plt +import numpy as np +import h5py +import xml.etree.ElementTree as ElementTree +from mpmath import * +import math +import os +import glob +import vtk +from vtk.util.numpy_support import vtk_to_numpy + + +def readGeosTotalStress(depths=(0.0, 4.2, 5.6)): + """Contrainte TOTALE GEOS aux profondeurs demandees. + averageStress = contrainte effective thermo-poro (terme -3aKdT inclus) -> + contrainte totale = averageStress - biot * pression. + Lit stressHistory.hdf5 s'il existe, sinon retombe sur les VTU du .pvd.""" + if os.path.exists("stressHistory.hdf5"): + return _readStressFromHDF5(depths) + return _readStressFromVTU(depths) + + +def _readStressFromHDF5(depths, biot=1.0): + hs = h5py.File("stressHistory.hdf5", 'r') + ts = np.array(hs.get('averageStress Time')).ravel() + center = np.array(hs.get('averageStress elementCenter')) # (nt, ncell, 3) + S = np.array(hs.get('averageStress')) # (nt, ncell, 6) + hp = h5py.File("pressureHistory.hdf5", 'r') + P = np.array(hp.get('pressure')) # (nt, ncell) + # coupe au premier reset de temps (redemarrage/restart) + last = len(ts) + for j in range(1, len(ts)): + if ts[j] < 1e-12: + last = j; break + n = min(last, len(P)) + ts, S, P = ts[:n], S[:n], P[:n] + yc = center[0, :, 1] + cid = {d: int(np.argmin(np.abs(yc - d))) for d in depths} + out = {} + for d, i in cid.items(): + out[d] = dict(syy=S[:, i, 1] - biot * P[:, i], + sxx=0.5 * (S[:, i, 0] + S[:, i, 2]) - biot * P[:, i]) + return ts, out + + +def _readStressFromVTU(depths, + pvd="thermoPoro_consolidation_fim.pvd", + root="thermoPoro_consolidation_fim"): + seen = {} + for d in ElementTree.parse(pvd).getroot().iter('DataSet'): + t = float(d.get('timestep')) + stem = d.get('file').split('/')[-1].replace('.vtm', '') + vtus = glob.glob(f"{root}/{stem}/**/Domain/*.vtu", recursive=True) + if vtus: + seen[t] = vtus[0] + ts = np.array(sorted(seen)) + files = [seen[t] for t in ts] + + r = vtk.vtkXMLUnstructuredGridReader(); r.SetFileName(files[0]); r.Update() + cc = vtk.vtkCellCenters(); cc.SetInputData(r.GetOutput()); cc.Update() + yc = vtk_to_numpy(cc.GetOutput().GetPoints().GetData())[:, 1] + cid = {d: int(np.argmin(np.abs(yc - d))) for d in depths} + + out = {d: {'sxx': [], 'syy': []} for d in depths} + for f in files: + rr = vtk.vtkXMLUnstructuredGridReader(); rr.SetFileName(f); rr.Update() + cd = rr.GetOutput().GetCellData() + A = lambda n: vtk_to_numpy(cd.GetArray(n)) + S = A('averageStress'); p = A('pressure'); b = A('rockPorosity_biotCoefficient') + if b is None: + b = np.ones_like(p) + for d, i in cid.items(): + out[d]['syy'].append(S[i, 1] - b[i] * p[i]) + out[d]['sxx'].append(0.5 * (S[i, 0] + S[i, 2]) - b[i] * p[i]) + for d in out: + for k in out[d]: + out[d][k] = np.asarray(out[d][k]) + return ts, out + + +class thermalAnalytical: + + def __init__(self, hydromechanicalParameters, xMin, xMax, appliedLoad): + E = hydromechanicalParameters["YoungModulus"] + nu = hydromechanicalParameters["PoissonRatio"] + b = hydromechanicalParameters["biotCoefficient"] + mu = hydromechanicalParameters["fluidViscosity"] + cf = hydromechanicalParameters["fluidCompressibility"] + phi = hydromechanicalParameters["porosity"] + k = hydromechanicalParameters["permeability"] + cc = hydromechanicalParameters["consolidationCoefficient"] + + G = E / 2.0/ (1.0 + nu) + eta = b*(1.-2.*nu)/2./(1.-nu) + + + K = E / 3.0 / (1.0 - 2.0 * nu) # bulk modulus + Kv = E * (1.0 - nu) / ((1.0 + nu) * (1.0 - 2.0 * nu)) # uniaxial bulk modulus + Se = (b - phi) * (1.0 - b) / K + phi * cf # constrained specific storage + + print( abs((k / mu) * Kv / (Se * Kv + b**2)/cc-1.0) ) + + self.characteristicLength = xMax - xMin + self.appliedLoad = abs(appliedLoad) + self.loadingEfficiency = b / (Kv * Se + b**2) + self.consolidationCoefficient = (k / mu) * Kv / (Se * Kv + b**2) + #self.consolidationCoefficient = cc + self.initialPressure = 0.0 + self.initialDisplacement = 0.0 + self.term = self.appliedLoad * self.characteristicLength * eta/G + + print( self.term, eta, G ) + + def computePressure(self, x, t): + if t == 0.0: + return self.initialPressure + else: + cc = self.consolidationCoefficient + L = self.characteristicLength + load = self.appliedLoad + p = nsum( + lambda m: 1 / (2 * m + 1) * exp(-((2 * m + 1)**2) * (math.pi**2) * cc * t / 4 / L / L) * sin( + (2 * m + 1) * math.pi * x / 2 / L), [0, inf]) + return load - load* 4 / math.pi * p + + def computeDisplacement(self, x, t): + if t == 0.0: + return self.initialDisplacement + else: + cc = self.consolidationCoefficient + L = self.characteristicLength + term = self.term + p = nsum( + lambda m: 8 / ((2 * m + 1)**2) / (math.pi**2) * (exp(-((2 * m + 1)**2) * (math.pi**2) * cc * t / 4 / L / L) -1.0) * cos((2 * m + 1) * math.pi * x / 2 / L), [0, inf]) + return term * p + + +def getHydromechanicalParametersFromXML(xmlFilePath): + tree = ElementTree.parse(xmlFilePath) + + param1 = tree.find('Constitutive/ElasticIsotropic') + param2 = tree.find('Constitutive/BiotPorosity') + param3 = tree.find('Constitutive/CompressibleSinglePhaseFluid') + param4 = tree.find('Constitutive/ConstantPermeability') + + hydromechanicalParameters = dict.fromkeys(["YoungModulus", + "PoissonRatio", + "biotCoefficient", + "fluidViscosity", + "fluidCompressibility", + "porosity", + "permeability", + "skemptonCoefficient", + "poissonRatio", + "undrainedPoissonRatio", + "consolidationCoefficient"]) + + hydromechanicalParameters["YoungModulus"] = float(param1.get("defaultYoungModulus")) + hydromechanicalParameters["PoissonRatio"] = float(param1.get("defaultPoissonRatio")) + + E = hydromechanicalParameters["YoungModulus"] + nu = hydromechanicalParameters["PoissonRatio"] + K = E / 3.0/ (1.0 - 2.0 * nu) + G = E / 2.0/ (1.0 + nu) + Ks = float(param2.get("grainBulkModulus")) + + hydromechanicalParameters["biotCoefficient"] = 1.0 - K / Ks + hydromechanicalParameters["porosity"] = float(param2.get("defaultReferencePorosity")) + hydromechanicalParameters["fluidViscosity"] = float(param3.get("defaultViscosity")) + hydromechanicalParameters["fluidCompressibility"] = float(param3.get("compressibility")) + + perm = param4.get("permeabilityComponents") + perm = np.array(perm[1:-1].split(','),float) + hydromechanicalParameters["permeability"] = perm[0] + + phi = hydromechanicalParameters["porosity"] + cf = hydromechanicalParameters["fluidCompressibility"] + bBiot = hydromechanicalParameters["biotCoefficient"] + kp = hydromechanicalParameters["permeability"] + mu = hydromechanicalParameters["fluidViscosity"] + M = 1./(phi*cf + (bBiot - phi)/Ks) + Ku = K + bBiot**2*M + B = bBiot*M/Ku + nuu = (3.*nu + bBiot* B* (1-2.*nu))/(3.-bBiot*B*(1-2.*nu)) + cc = 2.*kp/mu*B**2*G*(1.-nu)*(1.+nuu)**2/9./(1.-nuu)/(nuu-nu) + hydromechanicalParameters["skemptonCoefficient"] = B + hydromechanicalParameters["poissonRatio"] = nu + hydromechanicalParameters["undrainedPoissonRatio"] = nuu + hydromechanicalParameters["consolidationCoefficient"] = cc + + return hydromechanicalParameters + + +def getAppliedTractionFromXML(xmlFilePath): + tree = ElementTree.parse(xmlFilePath) + param = tree.findall('FieldSpecifications/FieldSpecification') + load = np.empty(1) + for elem in param: + if elem.get("name") == "boundaryPressure" and elem.get("fieldName") == "pressure": + load = float(elem.get("scale")) + print(load) + + return load + + +def getDomainMaxMinXCoordFromXML(xmlFilePath): + tree = ElementTree.parse(xmlFilePath) + meshElement = tree.find('Mesh/InternalMesh') + nodeXCoords = meshElement.get("xCoords") + nodeXCoords = [float(i) for i in nodeXCoords[1:-1].split(",")] + xMin = nodeXCoords[0] + xMax = nodeXCoords[-1] + return xMin, xMax + + +def computePP(x, t, h, phi12, phi11, r11, r22): + p = nsum(lambda m: 1 / ((2 * m + 1) * math.pi / 2 / h) * ( phi12 * exp(-r11 * ((2 * m + 1) * math.pi / 2 / h)**2 * t) + phi11 * exp(-r22 * ((2 * m + 1) * math.pi / 2 / h)**2 * t) )* sin(((2 * m + 1) * math.pi / 2 / h) * ( h - x) ), [0, inf]) + return 2/h * p + +def computeTemp(x, t, h, phi22, phi21, r11, r22): + p = nsum(lambda m: 1 / ((2 * m + 1) * math.pi / 2 / h) * ( phi22 * exp(-r11 * ((2 * m + 1) * math.pi / 2 / h)**2 * t) + phi21 * exp(-r22 * ((2 * m + 1) * math.pi / 2 / h)**2 * t) )* sin(((2 * m + 1) * math.pi / 2 / h) * ( h - x) ), [0, inf]) + return 2/h * p + +def computeDisp(x, t, h, t1, t2, r11, r22, A, M): + p = nsum(lambda m: 1 / ((2 * m + 1) * math.pi / 2 / h)**2 * ( t1 * exp(-r11 * ((2 * m + 1) * math.pi / 2 / h)**2 * t) + t2 * exp(-r22 * ((2 * m + 1) * math.pi / 2 / h)**2 * t) )* cos(((2 * m + 1) * math.pi / 2 / h) * ( h - x) ), [0, inf]) + return -A*x+2/h/M * p + +def main(): + E = 6.0e3 + nu = 0.4 + Ks = 1.0e27 + Kw = 1.0e50 + a_s = 3.0e-7 + a_w = 0.0 + phi = 0.2 + cs = 1.672e5 + cw = 1.672e2 + rhow = 1.0e3 + rhos = 2.4e3 + Kt = 836 + + lamda = E*nu/(1.0+nu)/(1.0-2.0*nu) + G = E/2.0/(1.0+nu) + K = E/3.0/(1.0-2.0*nu) + alpha = 1.0 - K / Ks + M = lamda + 2.0*G + m = (1.0-phi)*cs+phi*cw + a_m = (1.0-phi)*a_s+phi*a_w + + a_m = 9.0e-7 + m = 1.672e5 + + beta = a_m*(3.0*lamda+2.0*G)/3. + #a_p = phi/Kw + (alpha- phi)/Ks + a_p = 0 + perm = 4.0e-9 + mu = 1.0e-3 + km = perm/mu + h = 7.0 + theta_0 =0. + theta_a = 50. + dT0 = theta_a - theta_0 + F = 1.0 + + nuu = 0.499 + M_biot = 2.0*G*(nuu-nu)/(alpha**2*(1.0-2.0*nuu)*(1.0-2.0*nu)) + a_p = 1.0/M_biot + p0 = alpha*M_biot/(lamda+2.0*G+alpha**2*M_biot)*F + A = (F-alpha*0-beta*theta_a)/M + print(p0, A) + + keppa = Kt/m + c = km*M + time = np.logspace(-2, 5, 100, endpoint=True) + T = keppa*time/h**2 + print(a_m, M) + print(K, alpha, a_p, keppa, M, c/keppa) + print(K,G,M,A) + + g11=1.0/km*(a_p + alpha**2/M) + g12=1.0/km*(alpha*beta/M-a_m) + g21=alpha*beta*(theta_0)/(Kt*M) + g22=(m+beta**2*(theta_0)/M)/Kt + gs = g11*g22 - g12*g21 + r11=g11/gs + r12=g12/gs + r21=g21/gs + r22=g22/gs + r1s = r12*(theta_0 - theta_a) - r22*p0 + r2s = r21*p0 - r11*(theta_0 - theta_a) + rs = r11*r22-r12*r21 + + phi12= -(r12*r2s)/rs + phi11= -(r11*r1s)/rs + phi22= -(r22*r2s)/rs + phi21= -(r21*r1s)/rs + t1 = alpha*phi12 + beta*phi22 + t2 = alpha*phi11 + beta*phi21 + + + # File paths + hdf5FilePathPressure = "pressureHistory.hdf5" + + + # Read simulation output from HDF5 file + hf = h5py.File(hdf5FilePathPressure, 'r') + timePressure = hf.get('pressure Time') + timePressure = np.array(timePressure) + centerPressure = hf.get('pressure elementCenter') + centerPressure = np.array(centerPressure) + pressure = hf.get('pressure') + pressure = np.array(pressure) + + posElement1 = -1 + posElement2 = -1 + posElement3 = -1 + last = -1 + for j in range(0, centerPressure.shape[1]): + if centerPressure[0,j,1] >= 0 and posElement1 == -1: + posElement1 = j + if centerPressure[0,j,1] >= 4.2 and posElement2 == -1: + posElement2 = j + if centerPressure[0,j,1] >= 5.6 and posElement3 == -1: + posElement3 = j + + + # File paths + hdf5FilePathTemperature = "temperatureHistory.hdf5" + + + # Read simulation output from HDF5 file + hf = h5py.File(hdf5FilePathTemperature, 'r') + timeTemperature = hf.get('temperature Time') + timeTemperature = np.array(timeTemperature) + centerTemperature = hf.get('temperature elementCenter') + centerTemperature = np.array(centerTemperature) + temperature = hf.get('temperature') + temperature = np.array(temperature) + + posElement1 = -1 + posElement2 = -1 + posElement3 = -1 + last = -1 + + for j in range(0, centerTemperature.shape[1]): + if centerTemperature[0,j,1] >= 0 and posElement1 == -1: + posElement1 = j + if centerTemperature[0,j,1] >= 4.2 and posElement2 == -1: + posElement2 = j + if centerTemperature[0,j,1] >= 5.6 and posElement3 == -1: + posElement3 = j + + for j in range(0, timeTemperature.shape[0]): + if j > 0 and timeTemperature[j] < 1e-12 and last == -1: + last = j + + + + + #Visulization + N1 = 4 + fsize = 32 + msize = 8 + lw = 4 + mew = 2 + malpha = 0.6 + lalpha = 0.8 + + fig, ax = plt.subplots(2, 2, figsize=(32, 18)) + cmap = plt.get_cmap("tab10") + + pressure_analytical = np.empty(len(time)) + temperature_analytical = np.empty(len(time)) + displacement_analytical = np.empty(len(time)) + x_analytical = [0.0, 0.6, 0.8] + x_analytical2 = [0.2, 0.6, 1.0] + iplt = -1 + for xCell in x_analytical: + iplt += 1 + i = 0 + for k in range(0, len(time)): + pressure_analytical[i] = computePP(xCell*h, time[i], h, phi12, phi11, r11, r22) + temperature_analytical[i] = theta_a + computeTemp(xCell*h, time[i], h, phi22, phi21, r11, r22) + displacement_analytical[i] = computeDisp(x_analytical2[iplt]*h, time[i], h, t1, t2, r11, r22, A, M) + i += 1 + #ax[0,0].plot(pressure[k, :]/1.0e6, x[k, :, 0], 'o', color=cmap(iplt), markersize=msize, alpha=malpha, mec=cmap(iplt), fillstyle='none', mew=mew, label='GEOS: t ='+ str(t) + ' s') + test= np.linspace(0, h, 10, endpoint=True) + for tt in range(0, len(test)): + print(computeDisp(test[tt], 0, h, t1, t2, r11, r22, A, M)) + ax[0,0].semilogx(time, pressure_analytical, color=cmap(iplt), lw=lw, alpha=lalpha, label='Analytical: z/h =' + str(xCell)) + #ax[0,1].plot(displacement[k, :, 0], xl_node[k, :, 0], 'o', color=cmap(iplt), markersize=msize, alpha=malpha, mec=cmap(iplt), fillstyle='none', mew=mew, label='GEOS: t ='+ str(t) + ' s') + ax[0,1].semilogx(time, temperature_analytical+273.15, color=cmap(iplt), lw=lw, alpha=lalpha, label='Analytical: z/h =' + str(xCell)) + ax[1,0].semilogx(time, -displacement_analytical, color=cmap(iplt), lw=lw, alpha=lalpha, label='Analytical: z =' + str(round(x_analytical2[iplt]*h,2))) + + + ax[0,0].plot( timePressure[0:last:N1], + pressure[0:last:N1,posElement1], + 'o', color=cmap(0), markersize=msize, alpha=malpha, mec=cmap(0), fillstyle='none', mew=mew, + label='GEOS: z = 0.0 m') + ax[0,0].plot( timePressure[0:last:N1], + pressure[0:last:N1,posElement2], + 'o', color=cmap(1), markersize=msize, alpha=malpha, mec=cmap(1), fillstyle='none', mew=mew, + label='GEOS: z = 4.2 m') + ax[0,0].plot( timePressure[0:last:N1], + pressure[0:last:N1,posElement3], + 'o', color=cmap(2), markersize=msize, alpha=malpha, mec=cmap(2), fillstyle='none', mew=mew, + label='GEOS: z = 5.6 m') + + ax[0,0].set_xlabel('Time [s]', size=fsize, weight="bold") + ax[0,0].set_ylabel('Pore Pressure [pa]', size=fsize, weight="bold") + ax[0,0].legend(loc='lower left', fontsize=fsize * 0.6) + ax[0,0].grid(True) + ax[0,0].xaxis.set_tick_params(labelsize=fsize) + ax[0,0].yaxis.set_tick_params(labelsize=fsize) + #ax[0,0].invert_yaxis() + + + ax[0,1].plot( timeTemperature[0:last:N1], + temperature[0:last:N1,posElement1], + 'o', color=cmap(0), markersize=msize, alpha=malpha, mec=cmap(0), fillstyle='none', mew=mew, + label='GEOS: z = 0.0 m') + ax[0,1].plot( timeTemperature[0:last:N1], + temperature[0:last:N1,posElement2], + 'o', color=cmap(1), markersize=msize, alpha=malpha, mec=cmap(1), fillstyle='none', mew=mew, + label='GEOS: z = 4.2 m') + ax[0,1].plot( timeTemperature[0:last:N1], + temperature[0:last:N1,posElement3], + 'o', color=cmap(2), markersize=msize, alpha=malpha, mec=cmap(2), fillstyle='none', mew=mew, + label='GEOS: z = 5.6 m') + + ax[0,1].set_xlabel('Time [s]', size=fsize, weight="bold") + ax[0,1].set_ylabel('Temperature [K]', size=fsize, weight="bold") + ax[0,1].legend(loc='upper left', fontsize=fsize * 0.6) + ax[0,1].grid(True) + ax[0,1].xaxis.set_tick_params(labelsize=fsize) + ax[0,1].yaxis.set_tick_params(labelsize=fsize) + + + # File paths + hdf5FilePathDisplacement = "displacementHistory.hdf5" + + + # Read simulation output from HDF5 file + hf = h5py.File(hdf5FilePathDisplacement, 'r') + timeDisplacement = hf.get('totalDisplacement Time') + timeDisplacement = np.array(timeDisplacement) + centerDisplacement = hf.get('totalDisplacement ReferencePosition') + centerDisplacement = np.array(centerDisplacement) + displacement = hf.get('totalDisplacement') + displacement = np.array(displacement) + + posVertex1 = -1 + posVertex2 = -1 + posVertex3 = -1 + last = -1 + + for j in range(0, 284): + if centerDisplacement[0,j,1] >= 1.4 and posVertex1 == -1: + posVertex1 = j + if centerDisplacement[0,j,1] >= 4.2 and posVertex2 == -1: + posVertex2 = j + if centerDisplacement[0,j,1] >= 7 and posVertex3 == -1: + posVertex3 = j + + for j in range(0, timeDisplacement.shape[0]): + if j > 0 and timeDisplacement[j] < 1e-12 and last == -1: + last = j + + ax[1,0].plot( timeDisplacement[0:last:N1], + -displacement[0:last:N1,posVertex1,1], + 'o', color=cmap(0), markersize=msize, alpha=malpha, mec=cmap(0), fillstyle='none', mew=mew, + label='GEOS: z = 1.4 m') + ax[1,0].plot( timeDisplacement[0:last:N1], + -displacement[0:last:N1,posVertex2,1], + 'o', color=cmap(1), markersize=msize, alpha=malpha, mec=cmap(1), fillstyle='none', mew=mew, + label='GEOS: z = 4.2 m') + ax[1,0].plot( timeDisplacement[0:last:N1], + -displacement[0:last:N1,posVertex3,1], + 'o', color=cmap(2), markersize=msize, alpha=malpha, mec=cmap(2), fillstyle='none', mew=mew, + label='GEOS: z = 7.0 m') + + ax[1,0].set_xlabel('Time [s]', size=fsize, weight="bold") + ax[1,0].set_ylabel('Displacement [m]', size=fsize, weight="bold") + ax[1,0].legend(loc='upper left', fontsize=fsize * 0.6) + ax[1,0].grid(True) + ax[1,0].xaxis.set_tick_params(labelsize=fsize) + ax[1,0].yaxis.set_tick_params(labelsize=fsize) + + + # ------------------------------------------------------------------ + # Contrainte TOTALE (bas-droite, ax[1,1]) + # deformation uniaxiale, equilibre : sigma_yy^tot = -F (constant) + # sigma_xx^tot = sigma_zz^tot = -(lamda/M) F - (2G/M)(beta*theta + alpha*p) + # avec beta = a_m (3 lamda + 2G)/3 = 3 K a_s (contrainte thermique), + # alpha = coefficient de Biot, M = lamda + 2G (module oedometrique). + # GEOS : sigma^tot = averageStress - biot * pression (lu depuis le .pvd). + # ------------------------------------------------------------------ + zdepths = [0.0, 4.2, 5.6] + ts_geos, geosStress = readGeosTotalStress(depths=tuple(zdepths)) + + sigyy_analytical = np.empty(len(time)) + sigxx_analytical = np.empty(len(time)) + iplt = -1 + for xCell in x_analytical: + iplt += 1 + z = xCell * h + for i in range(0, len(time)): + p_an = float(computePP(z, time[i], h, phi12, phi11, r11, r22)) + th_an = float(theta_a + computeTemp(z, time[i], h, phi22, phi21, r11, r22)) + sigxx_analytical[i] = -(lamda / M) * F - (2.0 * G / M) * (beta * th_an + alpha * p_an) + sigyy_analytical[i] = -F + ax[1, 1].semilogx(time, sigxx_analytical, color=cmap(iplt), lw=lw, alpha=lalpha, + label='Analytical $\\sigma_{xx}$: z =' + str(round(z, 2))) + g = geosStress[zdepths[iplt]] + ax[1, 1].plot(ts_geos, g['sxx'], 'o', color=cmap(iplt), markersize=msize, alpha=malpha, + mec=cmap(iplt), fillstyle='none', mew=mew, + label='GEOS $\\sigma_{xx}$: z =' + str(round(z, 2))) + + # contrainte verticale totale (= -F pour toutes les profondeurs) + ax[1, 1].semilogx(time, -F * np.ones(len(time)), 'k--', lw=lw * 0.6, alpha=0.7, + label='$\\sigma_{yy}$ total = -F (all z)') + ax[1, 1].plot(ts_geos, geosStress[0.0]['syy'], 'k+', markersize=msize, mew=mew, alpha=malpha, + label='GEOS $\\sigma_{yy}$ total') + + ax[1, 1].set_xlabel('Time [s]', size=fsize, weight="bold") + ax[1, 1].set_ylabel('Total stress [Pa]', size=fsize, weight="bold") + ax[1, 1].legend(loc='upper left', fontsize=fsize * 0.5) + ax[1, 1].grid(True) + ax[1, 1].xaxis.set_tick_params(labelsize=fsize) + ax[1, 1].yaxis.set_tick_params(labelsize=fsize) + + plt.subplots_adjust(left=0.1, bottom=0.1, right=0.9, top=0.9, wspace=0.4, hspace=0.4) + + fig.savefig('Verification_tutorial.png') + print('figure -> Verification_tutorial.png') + + +if __name__ == "__main__": + main() diff --git a/src/docs/sphinx/advancedExamples/validationStudies/thermoPoromechanics/thermalConsolidation/pressureHistory.hdf5 b/src/docs/sphinx/advancedExamples/validationStudies/thermoPoromechanics/thermalConsolidation/pressureHistory.hdf5 deleted file mode 100644 index 11f1523fcc6..00000000000 Binary files a/src/docs/sphinx/advancedExamples/validationStudies/thermoPoromechanics/thermalConsolidation/pressureHistory.hdf5 and /dev/null differ diff --git a/src/docs/sphinx/advancedExamples/validationStudies/thermoPoromechanics/thermalConsolidation/temperatureHistory.hdf5 b/src/docs/sphinx/advancedExamples/validationStudies/thermoPoromechanics/thermalConsolidation/temperatureHistory.hdf5 deleted file mode 100644 index 207a0237708..00000000000 Binary files a/src/docs/sphinx/advancedExamples/validationStudies/thermoPoromechanics/thermalConsolidation/temperatureHistory.hdf5 and /dev/null differ diff --git a/src/docs/sphinx/advancedExamples/validationStudies/thermoPoromechanics/thermalConsolidation/thermalConsolidationDisplacementFigure.py b/src/docs/sphinx/advancedExamples/validationStudies/thermoPoromechanics/thermalConsolidation/thermalConsolidationDisplacementFigure.py deleted file mode 100644 index 285a7b15706..00000000000 --- a/src/docs/sphinx/advancedExamples/validationStudies/thermoPoromechanics/thermalConsolidation/thermalConsolidationDisplacementFigure.py +++ /dev/null @@ -1,86 +0,0 @@ -import matplotlib -matplotlib.use('Agg') -import matplotlib.pyplot as plt -import xml.etree.ElementTree as ElementTree -import numpy as np -import h5py -from numpy import genfromtxt - -def main(): - - # File paths - hdf5FilePathDisplacement = "displacementHistory.hdf5" - - - # Read simulation output from HDF5 file - hf = h5py.File(hdf5FilePathDisplacement, 'r') - timeDisplacement = hf.get('totalDisplacement Time') - timeDisplacement = np.asarray(timeDisplacement) - centerDisplacement = hf.get('totalDisplacement ReferencePosition') - centerDisplacement = np.asarray(centerDisplacement) - displacement = hf.get('totalDisplacement') - displacement = np.asarray(displacement) - - time = 1 - posVertex1 = -1 - posVertex2 = -1 - posVertex3 = -1 - last = -1 - - for j in range(0, 284): - if centerDisplacement[0,j,1] >= 1.4 and posVertex1 == -1: - posVertex1 = j - if centerDisplacement[0,j,1] >= 4.2 and posVertex2 == -1: - posVertex2 = j - if centerDisplacement[0,j,1] >= 7 and posVertex3 == -1: - posVertex3 = j - - for j in range(0, timeDisplacement.shape[0]): - if j > 0 and timeDisplacement[j] < 1e-12 and last == -1: - last = j - - displacement_1p4 = genfromtxt('thermoConsolidationDisp_1p4m.csv', delimiter=',') - displacement_4p2 = genfromtxt('thermoConsolidationDisp_4p2m.csv', delimiter=',') - displacement_7 = genfromtxt('thermoConsolidationDisp_7m.csv', delimiter=',') - - plt.plot( timeDisplacement[0:last], - -displacement[0:last,posVertex1,1], - 'r--', - label='GEOSX: z = 1.4 m') - plt.plot( timeDisplacement[0:last], - -displacement[0:last,posVertex2,1], - 'b--', - label='GEOSX: z = 4.2 m') - plt.plot( timeDisplacement[0:last], - -displacement[0:last,posVertex3,1], - 'k--', - label='GEOSX: z = 5.6 m') - - plt.plot( displacement_1p4[:,0], - displacement_1p4[:,1], - 'r-', - label='Analytical: z = 1.4 m') - plt.plot( displacement_4p2[:,0], - displacement_4p2[:,1], - 'b-', - label='Analytical: z = 4.2 m') - plt.plot( displacement_7[:,0], - displacement_7[:,1], - 'k-', - label='Analytical: z = 7 m') - - plt.xscale("log") - - plt.grid() - plt.ylabel('Displacement [m]') - plt.xlabel('Time [s]') - plt.xlim(0.01,100000) - - plt.legend(loc='upper left') - plt.show() - # plt.savefig('displacement.png') - - - -if __name__ == "__main__": - main() diff --git a/src/docs/sphinx/advancedExamples/validationStudies/thermoPoromechanics/thermalConsolidation/thermalConsolidationPressureFigure.py b/src/docs/sphinx/advancedExamples/validationStudies/thermoPoromechanics/thermalConsolidation/thermalConsolidationPressureFigure.py deleted file mode 100644 index d72455e6020..00000000000 --- a/src/docs/sphinx/advancedExamples/validationStudies/thermoPoromechanics/thermalConsolidation/thermalConsolidationPressureFigure.py +++ /dev/null @@ -1,82 +0,0 @@ -import matplotlib -matplotlib.use('Agg') -import matplotlib.pyplot as plt -import xml.etree.ElementTree as ElementTree -import numpy as np -import h5py -from numpy import genfromtxt - -def main(): - - # File paths - hdf5FilePathPressure = "pressureHistory.hdf5" - - - # Read simulation output from HDF5 file - hf = h5py.File(hdf5FilePathPressure, 'r') - timePressure = hf.get('pressure Time') - timePressure = np.asarray(timePressure) - centerPressure = hf.get('pressure elementCenter') - centerPressure = np.asarray(centerPressure) - pressure = hf.get('pressure') - pressure = np.asarray(pressure) - - time = 1 - posElement1 = -1 - posElement2 = -1 - posElement3 = -1 - last = -1 - for j in range(0, centerPressure.shape[1]): - if centerPressure[0,j,1] >= 0 and posElement1 == -1: - posElement1 = j - if centerPressure[0,j,1] >= 4.2 and posElement2 == -1: - posElement2 = j - if centerPressure[0,j,1] >= 5.6 and posElement3 == -1: - posElement3 = j - - for j in range(0, timePressure.shape[0]): - if j > 0 and timePressure[j] < 1e-12 and last == -1: - last = j - - pressure_0 = genfromtxt('thermoConsolidationPressure_0m.csv', delimiter=',') - pressure_4p2 = genfromtxt('thermoConsolidationPressure_4p2m.csv', delimiter=',') - pressure_5p6 = genfromtxt('thermoConsolidationPressure_5p6m.csv', delimiter=',') - - plt.plot( timePressure[0:last], - pressure[0:last,posElement1], - 'r--', - label='GEOSX: z = 0.0 m') - plt.plot( timePressure[0:last], - pressure[0:last,posElement2], - 'b--', - label='GEOSX: z = 4.2 m') - plt.plot( timePressure[0:last], - pressure[0:last,posElement3], - 'k--', - label='GEOSX: z = 5.6 m') - - plt.plot( pressure_0[:,0], - pressure_0[:,1], - 'r-', - label='Analytical: z = 0.0 m') - plt.plot( pressure_4p2[:,0], - pressure_4p2[:,1], - 'b-', - label='Analytical: z = 4.2 m') - plt.plot( pressure_5p6[:,0], - pressure_5p6[:,1], - 'k-', - label='Analytical: z = 5.6 m') - - plt.xscale("log") - - plt.grid() - plt.ylabel('Pressure [Pa]') - plt.xlabel('Time [s]') - plt.xlim(0.01,100000) - - plt.legend(loc='lower left') - plt.show() - -if __name__ == "__main__": - main() diff --git a/src/docs/sphinx/advancedExamples/validationStudies/thermoPoromechanics/thermalConsolidation/thermalConsolidationTemperatureFigure.py b/src/docs/sphinx/advancedExamples/validationStudies/thermoPoromechanics/thermalConsolidation/thermalConsolidationTemperatureFigure.py deleted file mode 100644 index ee60cf94ab5..00000000000 --- a/src/docs/sphinx/advancedExamples/validationStudies/thermoPoromechanics/thermalConsolidation/thermalConsolidationTemperatureFigure.py +++ /dev/null @@ -1,85 +0,0 @@ -import matplotlib -matplotlib.use('Agg') -import matplotlib.pyplot as plt -import xml.etree.ElementTree as ElementTree -import numpy as np -import h5py -from numpy import genfromtxt - -def main(): - - # File paths - hdf5FilePathTemperature = "temperatureHistory.hdf5" - - - # Read simulation output from HDF5 file - hf = h5py.File(hdf5FilePathTemperature, 'r') - timeTemperature = hf.get('temperature Time') - timeTemperature = np.asarray(timeTemperature) - centerTemperature = hf.get('temperature elementCenter') - centerTemperature = np.asarray(centerTemperature) - temperature = hf.get('temperature') - temperature = np.asarray(temperature) - - time = 1 - posElement1 = -1 - posElement2 = -1 - posElement3 = -1 - last = -1 - - for j in range(0, centerTemperature.shape[1]): - if centerTemperature[0,j,1] >= 0 and posElement1 == -1: - posElement1 = j - if centerTemperature[0,j,1] >= 4.2 and posElement2 == -1: - posElement2 = j - if centerTemperature[0,j,1] >= 5.6 and posElement3 == -1: - posElement3 = j - - for j in range(0, timeTemperature.shape[0]): - if j > 0 and timeTemperature[j] < 1e-12 and last == -1: - last = j - - temperature_0 = genfromtxt('thermoConsolidationTemp_0m.csv', delimiter=',') - temperature_4p2 = genfromtxt('thermoConsolidationTemp_4p2m.csv', delimiter=',') - temperature_5p6 = genfromtxt('thermoConsolidationTemp_5p6m.csv', delimiter=',') - - plt.plot( timeTemperature[0:last], - temperature[0:last,posElement1], - 'r--', - label='GEOSX: z = 0.0 m') - plt.plot( timeTemperature[0:last], - temperature[0:last,posElement2], - 'b--', - label='GEOSX: z = 4.2 m') - plt.plot( timeTemperature[0:last], - temperature[0:last,posElement3], - 'k--', - label='GEOSX: z = 5.6 m') - - plt.plot( temperature_0[:,0], - 273+temperature_0[:,1], - 'r-', - label='Analytical: z = 0.0 m') - plt.plot( temperature_4p2[:,0], - 273+temperature_4p2[:,1], - 'b-', - label='Analytical: z = 4.2 m') - plt.plot( temperature_5p6[:,0], - 273+temperature_5p6[:,1], - 'k-', - label='Analytical: z = 5.6 m') - - plt.xscale("log") - - plt.grid() - plt.ylabel('Temperature [K]') - plt.xlabel('Time [s]') - plt.ylim(273,323) - plt.xlim(0.01,100000) - - plt.legend(loc='upper left') - plt.show() - #plt.savefig(' temperature.png') - -if __name__ == "__main__": - main() diff --git a/src/docs/sphinx/advancedExamples/validationStudies/thermoPoromechanics/thermalConsolidation/thermoConsolidationDisp_1p4m.csv b/src/docs/sphinx/advancedExamples/validationStudies/thermoPoromechanics/thermalConsolidation/thermoConsolidationDisp_1p4m.csv deleted file mode 100644 index fe4937dfb4d..00000000000 --- a/src/docs/sphinx/advancedExamples/validationStudies/thermoPoromechanics/thermalConsolidation/thermoConsolidationDisp_1p4m.csv +++ /dev/null @@ -1,64 +0,0 @@ -0.010153382770450296, 0.0000013599017727036408 -0.013363773135962386, 0.000001348306210454211 -0.017588889122929694, 0.0000020169836335079535 -0.022454281034521375, 0.00000200667646706404 -0.028665524766994802, 0.000001996369300620018 -0.03715929564047002, 6.24871965666839e-7 -0.049658839683139715, 0.00000197317817612105 -0.06947524531581625, 0.0000019590058222605875 -0.08869330361688064, 0.0000019486986558165656 -0.12990047589490736, 0.0000019325937082477745 -0.1608473432635658, 0.0000019235749376093772 -0.1961499697321569, 0.0000019152003648735755 -0.2786445563969257, 0.0000019003838131104091 -0.37811725071057417, 0.0000018874998550553546 -0.454121484123263, 0.0000018797694802223652 -0.5290052872535868, 0.0000018733275011948922 -0.6651076743801212, 0.0000018636645326536826 -0.8621294473729189, 0.000002532986153610129 -1.0197394947658354, 0.0000025258999766798436 -1.2435509780552996, 0.000002517525403944042 -1.7398272689135508, 0.000001823080064780407 -2.1874042970353043, 0.00000249369008154237 -2.9233188898260445, 0.0000024814503213901277 -3.7320371169840842, 0.0000017908701696429333 -4.912067090735175, 0.0000017792746073935035 -6.270830076016752, 0.0000017689674409494816 -8.128577892877692, 0.0000017580160766027557 -10.377083574864871, 0.0000017477089101587337 -13.868275049482747, 0.0000017354691500064915 -18.819079409319883, 0.0000017225851919515454 -25.930034799879476, 0.0000017090570359937869 -35.186739680618125, 0.0000016961730779388407 -47.747974844883, 0.0000016832891198837862 -65.7858703632878, 0.00000371057991983587 -92.0282264054625, 0.000007097772492491703 -113.93839291593221, 0.00001117039163367299 -147.662236140643, 0.000017962170122358856 -185.6103461800117, 0.00002543550999215352 -233.2963070659185, 0.00003494966881785808 -280.1206955348425, 0.00004310521426666435 -341.5020276966939, 0.000052620661488174316 -403.8330155958889, 0.0000607768511348834 -499.8741418664249, 0.00007165220012909717 -609.4341686323978, 0.00007980710138000058 -720.7280260248605, 0.00008524219908549654 -892.2269839498045, 0.00009271618315319401 -1121.6392214813618, 0.00009678815809647243 -1367.758754488643, 0.00009814032949434319 -1693.6422214882884, 0.0000974510377384014 -2263.6740781353897, 0.00009403743305173287 -3072.3521667520486, 0.00008790209222594845 -3983.205237157777, 0.00008244895697917553 -5406.171038138221, 0.00007631361615339117 -7008.7803886988195, 0.00007154075389192153 -8418.818380186558, 0.00006677111261996563 -11779.82325702495, 0.00006267530235428553 -15038.951357933107, 0.00006130444921723506 -18622.53255082284, 0.00005993488447599005 -25271.073403302787, 0.000059241727532631766 -33262.205588573604, 0.000058549858985079 -45831.57553825898, 0.00005785605784381796 -60324.27921334647, 0.0000571641892962652 -80617.6723282089, 0.000057832222521416235 -98313.606263342, 0.00005714357496337721 diff --git a/src/docs/sphinx/advancedExamples/validationStudies/thermoPoromechanics/thermalConsolidation/thermoConsolidationDisp_4p2m.csv b/src/docs/sphinx/advancedExamples/validationStudies/thermoPoromechanics/thermalConsolidation/thermoConsolidationDisp_4p2m.csv deleted file mode 100644 index 1692f9097fc..00000000000 --- a/src/docs/sphinx/advancedExamples/validationStudies/thermoPoromechanics/thermalConsolidation/thermoConsolidationDisp_4p2m.csv +++ /dev/null @@ -1,73 +0,0 @@ -0.009998754088411478, 0.000004081637911819468 -0.013362663107590534, 0.000004069398151667226 -0.017058653323225467, 0.000004739363970526485 -0.022797744140105793, 0.000004727124210374242 -0.029551628218007345, 0.0000047161728460275165 -0.038306365970614646, 0.000004705221481680791 -0.04890155591770684, 0.0000053751873005400495 -0.06339007058199968, 0.000004683962950890043 -0.08343332872231671, 0.000004672367388640613 -0.10489686247826191, 0.000005342977405402576 -0.14453607003290095, 0.000004649176264141645 -0.19023681993992214, 0.000004637580701892107 -0.2542387294495813, 0.000004625340941739864 -0.355700314756735, 0.000003930895602576121 -0.4976325182104469, 0.0000045969962340189395 -0.645043899575232, 0.000005266317854975386 -0.8234913350291972, 0.000004575737703228192 -1.1346312476178406, 0.000005242482532573714 -1.6618188048768607, 0.0000045461045997017505 -2.4713393026023933, 0.0000045293554542302555 -3.3535771692660803, 0.000004516471496175201 -4.413850519257689, 0.000005185148919229052 -5.721582692071965, 0.000004493924569579045 -8.127902712897296, 0.0000044791080178157704 -11.029235405122794, 0.000005146497045064105 -14.296968627598796, 0.000004455272695414098 -18.53170873696416, 0.000005804867301673826 -25.532487870755826, 0.000007832158101626018 -35.17431988860397, 0.000013260813828094398 -45.58721884623693, 0.00001869204634617381 -60.905280583255255, 0.00002888390136557067 -72.02171913747563, 0.00003704009101227954 -85.16536268344663, 0.0000458765536442919 -102.25446821781289, 0.00005539264506370463 -117.27276420768364, 0.00006627121504743219 -132.46228782962527, 0.00007647015624375928 -149.6160975249088, 0.00008734937042538955 -174.21514692485636, 0.00010094838815242742 -199.7816911518674, 0.00011522832306267133 -225.64396296383725, 0.0001274680832149082 -251.00420271602334, 0.00013834794159444123 -283.4855907790619, 0.00015194824771728462 -315.30739108928015, 0.00016690974400863728 -350.7522329441862, 0.00017710932940286707 -378.43483589839576, 0.0001886707491635089 -414.5735096208301, 0.0002009117977115513 -461.1772141359031, 0.00021111138310578103 -505.25934791346043, 0.0002206313397126103 -562.0457029828076, 0.00023151119809214338 -625.2143053779638, 0.0002423910564716764 -695.4824591216696, 0.00025327091485120954 -797.6292074144066, 0.0002641494848349371 -887.4041481726439, 0.00027026743231734726 -1049.351459962489, 0.0002791038949493595 -1339.5090086031735, 0.00028181467972412857 -1633.6049792431556, 0.00027976548619548303 -2023.2073126887071, 0.00027295373757181183 -2623.404183329673, 0.0002627386914279161 -3103.9680973001127, 0.0002525275104714368 -3616.858145407688, 0.00024299724669816353 -4279.40626425186, 0.0002327860657416843 -4986.418512926343, 0.0002239360749537143 -5990.713958814154, 0.00021304397681402902 -7307.371084458944, 0.00020487232641765401 -8778.391478736308, 0.00019670132021918182 -11557.383994177571, 0.00018716590286268657 -14532.676355490463, 0.00018307460198232567 -19721.89247021044, 0.00018102089906836088 -26764.03389796784, 0.00017896719615439615 -40413.7429276549, 0.0001789498028110219 -58292.29712146067, 0.0001796146150466592 -82808.22727093354, 0.00017959979849489598 -100982.90057642768, 0.00017959142392216023 diff --git a/src/docs/sphinx/advancedExamples/validationStudies/thermoPoromechanics/thermalConsolidation/thermoConsolidationDisp_7m.csv b/src/docs/sphinx/advancedExamples/validationStudies/thermoPoromechanics/thermalConsolidation/thermoConsolidationDisp_7m.csv deleted file mode 100644 index cf467af410f..00000000000 --- a/src/docs/sphinx/advancedExamples/validationStudies/thermoPoromechanics/thermalConsolidation/thermoConsolidationDisp_7m.csv +++ /dev/null @@ -1,70 +0,0 @@ -0.009997508332052525, 0.000008163275823639153 -0.014642076327913747, 0.000008827443861373642 -0.021444828476583546, 0.00000881133891380496 -0.03189056877732696, 0.000009474862753636746 -0.04599952839405873, 0.000009459402003970767 -0.06737104590376612, 0.000009443297056401976 -0.09717115108065555, 0.00001146865526264584 -0.13388534850727565, 0.000012135400091991362 -0.20842514486611707, 0.0000134772643234181 -0.3195496706161579, 0.000014819772752747758 -0.5050870011596059, 0.000017521538756878246 -0.7510360538100744, 0.000021586427523226435 -1.2427248786483838, 0.00002428626093364881 -1.738454866681777, 0.000027673453506304698 -2.469236035679274, 0.000032420547851664226 -3.5611526400792037, 0.00003716699799912116 -4.981302790847094, 0.00004327528251299017 -6.967649163227823, 0.0000500638400121623 -8.893359505790702, 0.000056175989713447803 -12.250740714758136, 0.0000643257373811292 -16.619272688510673, 0.00007383667521732007 -20.88943607464521, 0.0000826705610577213 -26.660064510881778, 0.0000921840756855231 -36.16085673700339, 0.00010713719740414 -43.41772108499518, 0.00011597301583824956 -52.12766613806268, 0.0001268496532282689 -63.54484090256706, 0.00013908619239099193 -75.13525041748713, 0.00015064374696421726 -86.1704985558791, 0.00016152231694794482 -100.34025795932699, 0.00017444106168967947 -118.63712596192376, 0.00018735916223351135 -138.13991587024188, 0.0002016384529458525 -153.65914921413193, 0.00021387885729599204 -170.92188009864876, 0.00022611926164613173 -187.25574825276573, 0.00023631949123826423 -214.7360360117358, 0.00025059942614850814 -238.85544727734836, 0.000263520103483951 -265.678462720635, 0.00027712105380469715 -291.04341093396084, 0.0002900423753380428 -328.6992769184907, 0.0003043229544461894 -360.08844830161087, 0.0003165640029942318 -388.50789829073506, 0.0003281254227548736 -419.17901626332485, 0.00033900656953021215 -473.44285592736765, 0.00035124632968244903 -526.631466256596, 0.0003634867340325886 -576.9102119716844, 0.0003764080555659342 -651.5792437063674, 0.00038932808870347434 -735.9126290692158, 0.00040224812184101445 -856.9784869877932, 0.00041312604762683926 -1013.3732508788464, 0.0004219625102588515 -1254.585997722622, 0.0004273956753706392 -1553.6016044563441, 0.0004246655646587876 -2014.4452252560188, 0.00041513079150019513 -2420.1166613447454, 0.0004049189663458131 -2863.501154352861, 0.00039402751240403053 -3601.867410039547, 0.00037905184375881736 -4197.377438863477, 0.00036680048804433105 -4743.890522157134, 0.00035727151266686327 -6059.026227402935, 0.0003416149268384441 -7505.148700187856, 0.00033004126731765005 -8879.782426667729, 0.000320510359346474 -11167.617583977653, 0.0003109768745836871 -14478.755753288755, 0.0003048434663516108 -18770.45904248312, 0.0003007508770754445 -23243.20866482086, 0.00029938131233419945 -30129.06263159623, 0.0002993703609698527 -42152.968135838084, 0.000298675915630689 -60803.45995643873, 0.0002979801818957197 -85067.13655571481, 0.00029796600954185926 -96115.22470876326, 0.00029796085595863725 diff --git a/src/docs/sphinx/advancedExamples/validationStudies/thermoPoromechanics/thermalConsolidation/thermoConsolidationPressure_0m.csv b/src/docs/sphinx/advancedExamples/validationStudies/thermoPoromechanics/thermalConsolidation/thermoConsolidationPressure_0m.csv deleted file mode 100644 index bcd2945855e..00000000000 --- a/src/docs/sphinx/advancedExamples/validationStudies/thermoPoromechanics/thermalConsolidation/thermoConsolidationPressure_0m.csv +++ /dev/null @@ -1,83 +0,0 @@ -0.010150923999646412, 0.987603305785124 -0.012900151049529792, 0.9876237877251735 -0.018206356747197474, 0.9876532305139943 -0.023840942487676983, 0.9890536831648694 -0.03169052018799551, 0.9877005950003585 -0.0408812497263315, 0.9877223570616609 -0.05434131097931401, 0.9877466793654696 -0.0690588482277122, 0.9877671613055189 -0.09043149333627863, 0.9864127930197549 -0.11665799236725406, 0.9878119655493769 -0.15740771780960836, 0.986460157506119 -0.19706507612585966, 0.9864793593249154 -0.2619483175442174, 0.9865036816287239 -0.3328931300843456, 0.9865241635687734 -0.46283640251960934, 0.9865523262363411 -0.5708284053867687, 0.9879476584022039 -0.7254288036348263, 0.9865907298739337 -0.9642746363079265, 0.9879924626460619 -1.2817599322880595, 0.9866393744815509 -1.6784448010661022, 0.9880398271324261 -2.2989216146542857, 0.9880667096787409 -3.3432093195047434, 0.988098712710068 -4.511023535244492, 0.9895017256034493 -5.819288499263777, 0.9895234876647517 -8.590429339305476, 0.9881793603490123 -11.41880924135124, 0.988203682652821 -15.64004211629874, 0.9882305651991358 -20.480393182283496, 0.9882536073816913 -28.051454854794255, 0.9882804899280061 -35.648774909408836, 0.9869235613997359 -45.98745810620617, 0.9855679129927187 -55.04353529985595, 0.9828284535111167 -65.88297990528858, 0.9745793521562364 -80.0471225377729, 0.9621992995176504 -92.9825574226782, 0.9470605855786661 -100.21422402961326, 0.9305380605650968 -112.97282209439288, 0.909887144510328 -131.22897594022814, 0.8795969154198287 -141.43523644191845, 0.8589421590013007 -154.73589743096068, 0.8314016303624281 -166.77039565689014, 0.8052372320706218 -179.7408702784364, 0.7832050651837743 -188.00247591190245, 0.7584155171177815 -202.6242637477963, 0.7405155816358926 -211.93768120406273, 0.7143486231015803 -238.92015515371875, 0.6813010128319356 -246.18633359727394, 0.6551327741763702 -269.33785542229856, 0.6220826036642191 -299.11378691303156, 0.5890337132733214 -317.58408897347647, 0.5546035720503447 -337.19493377427756, 0.5229282517640073 -352.6937547165059, 0.4940064722930557 -397.59634115030553, 0.4582040410867718 -428.5191750993205, 0.41964294858008966 -448.21562157565273, 0.3948534005140967 -468.8173951093013, 0.37144126291642354 -512.9052857579319, 0.3535426075557877 -544.577231254386, 0.32186728726945024 -569.6081678166662, 0.29707773920345737 -604.7815211557064, 0.27366688172703724 -632.5796864503114, 0.24749992319272496 -681.7782191808445, 0.2213355249009188 -702.512854131631, 0.1965446967136728 -791.9520453043104, 0.16762931784898671 -853.5456744651754, 0.13595527768390236 -933.8136610032172, 0.10979215951334931 -1037.048950938775, 0.07674326912245133 -1186.7232263265391, 0.050583991315657495 -2328.6293754671296, 0.012074103659098778 -3649.7881600453043, 0.007980275891732669 -4501.380496556548, 0.006620787120956262 -5806.848833698688, 0.005265138713939299 -7490.922707649899, 0.003909490306921892 -9809.247392683612, 0.0025551220211579295 -13435.467666382601, 0.0025820045674727066 -18679.939472771566, 0.0026101672350404836 -25585.42085334695, 0.0026370497813554827 -33005.57936739651, 0.001281401374338298 -45889.15251453425, 0.0026869745102255926 -58317.54813581721, 0.0013300459819556298 -77518.47216056062, 0.0013543682857641848 -98513.19939296534, 0.0013748502258135176 -1399.2998277410964, 0.028559505156328324 diff --git a/src/docs/sphinx/advancedExamples/validationStudies/thermoPoromechanics/thermalConsolidation/thermoConsolidationPressure_4p2m.csv b/src/docs/sphinx/advancedExamples/validationStudies/thermoPoromechanics/thermalConsolidation/thermoConsolidationPressure_4p2m.csv deleted file mode 100644 index 751dcd6ea76..00000000000 --- a/src/docs/sphinx/advancedExamples/validationStudies/thermoPoromechanics/thermalConsolidation/thermoConsolidationPressure_4p2m.csv +++ /dev/null @@ -1,78 +0,0 @@ -0.010150923999646412, 0.987603305785124 -0.013493093324807878, 0.9890050385572522 -0.01740629420154237, 0.9890268006185546 -0.02313727815405322, 0.9876737124540437 -0.030297907783064737, 0.9890741651049189 -0.03967464149927611, 0.9877197968191548 -0.052737429656322464, 0.9863667086546438 -0.0690588482277122, 0.9877671613055189 -0.09179632160310944, 0.9877914836093276 -0.11665799236725406, 0.9891893760176964 -0.1550673789056952, 0.9892136983215051 -0.2061229711975749, 0.9892380206253137 -0.2823212020209928, 0.9892649031716285 -0.3809386326441193, 0.9906679160650097 -0.5457438465435344, 0.9893212285067642 -0.7363772652851527, 0.989346830931826 -0.9358141142568936, 0.9893673128718753 -1.2254341045244705, 0.9893903550544308 -1.6046870010334717, 0.9880359867686668 -2.133026654795654, 0.9880603090724754 -2.8781127717460526, 0.9894633219658566 -3.7128089493104866, 0.9894850840271592 -4.7183681274551645, 0.9881281554988889 -6.0867661551458605, 0.9881499175601913 -7.852020280454728, 0.9867942691531743 -10.917019579814932, 0.9813127899474638 -13.873732243014787, 0.9716913986092763 -17.63122663137432, 0.9524281339928519 -19.875915308625075, 0.938664270279681 -24.149026509533922, 0.9180197548311777 -28.474818631052823, 0.8959952686718486 -30.68943178797696, 0.8794727436582794 -35.648774909408836, 0.8491825145677799 -40.18733612639283, 0.8257767775763721 -45.3037162999231, 0.7996162196483252 -51.07147943642877, 0.7762104826569174 -60.21986491247263, 0.7349022499411145 -69.95125957922657, 0.7004797894456564 -80.0471225377729, 0.6701882802339039 -90.23818146768905, 0.6426503118375373 -100.21422402961326, 0.6164884737882372 -114.67785311057571, 0.5875743750448044 -123.5968592541141, 0.5655422081579569 -137.26078186851976, 0.5393803701086568 -154.73589743096068, 0.5104649912439708 -169.28736117040538, 0.48430187307341754 -199.61164496439375, 0.45401292410417127 -221.679180387119, 0.41683180230831474 -246.18633359727394, 0.3906699642590147 -294.66656131348304, 0.3521178326011042 -337.19493377427756, 0.3121844501111146 -403.597024155423, 0.273632318453204 -448.21562157565273, 0.23507378618902797 -505.27940685576704, 0.21304545966593957 -604.7815211557064, 0.1689836861347508 -692.0678887519011, 0.13180512458140048 -803.9045023248603, 0.10151489549090109 -919.9297138228432, 0.07811043862074629 -1006.4404983000102, 0.0629666041967496 -1204.6337279055886, 0.031301524880436804 -1337.8088112866583, 0.013404149641053964 -1974.8723687289714, 0.009305201388675632 -2959.30406758484, 0.0065849437258698185 -3993.0165960405707, 0.005233135682612078 -4999.018665795859, 0.003874927033088671 -5983.45009110729, 0.0038902884881255595 -7379.547623360033, 0.0025307997173493746 -8701.438961584417, 0.002544881051133263 -11394.399699165459, 0.0011905127653693004 -16323.950862412092, 0.0025986461437628172 -20745.050584316636, 0.00261912808381215 -29719.9672924797, 0.002649850993886149 -36654.42356166941, 0.002667772691429482 -47284.76005789792, 0.0013121242844122971 -54925.87088594401, 0.0013249254969431856 -69801.72750582406, 0.0013454074369925184 -90045.28283432488, 0.0013671694982950733 -100000.0000000002, 0.0013761303470665176 diff --git a/src/docs/sphinx/advancedExamples/validationStudies/thermoPoromechanics/thermalConsolidation/thermoConsolidationPressure_5p6m.csv b/src/docs/sphinx/advancedExamples/validationStudies/thermoPoromechanics/thermalConsolidation/thermoConsolidationPressure_5p6m.csv deleted file mode 100644 index ec3f4cd6998..00000000000 --- a/src/docs/sphinx/advancedExamples/validationStudies/thermoPoromechanics/thermalConsolidation/thermoConsolidationPressure_5p6m.csv +++ /dev/null @@ -1,76 +0,0 @@ -0.010150923999646412, 0.987603305785124 -0.013292477931346833, 0.9890037584359991 -0.01793566452456119, 0.9890293608610609 -0.02313727815405322, 0.9890511229223633 -0.03169052018799551, 0.9890780054686781 -0.04340567041200643, 0.9877274775466733 -0.058567676169505485, 0.987753079971735 -0.07223306769529865, 0.9877710016692782 -0.08776241192181838, 0.9877876432455683 -0.11665799236725406, 0.9864345550810574 -0.18284445081830478, 0.9878503691869694 -0.2504372260790477, 0.9878772517332842 -0.3042785762390852, 0.9878938933095743 -0.37527483474153583, 0.9879118150071176 -0.46283640251960934, 0.9893071471729803 -0.633934824145139, 0.9893340297192951 -0.8056266589439807, 0.9879771011910249 -1.1034456053673303, 0.9880039837373397 -1.5341704235076514, 0.986654735936588 -1.8640008096307419, 0.9852939670445585 -2.4408802220263235, 0.9811847778221554 -3.1019563892948305, 0.9743182074206069 -4.1232686848701805, 0.96056842504122 -5.162086513789205, 0.9385490593669032 -6.0867661551458605, 0.9165245732075743 -7.070373869743989, 0.8931213964586726 -8.090820479054509, 0.8697169395885179 -9.120889089634584, 0.8490660235337492 -10.129224766183718, 0.8270364168894079 -11.766084696229441, 0.7953687773305889 -13.464249666614887, 0.7650772681188363 -14.952754039751238, 0.7402928405378558 -16.358920687741605, 0.7141297223673027 -17.897324155562217, 0.6934762460700279 -19.875915308625075, 0.6686918184890474 -21.745059222245825, 0.6439061107868138 -23.789978636797112, 0.6246300449578586 -26.027203593921122, 0.599844337255625 -29.783634604013276, 0.5764398803854702 -34.596602938686715, 0.5365077780167338 -40.18733612639283, 0.5103497803311932 -48.10122467677227, 0.46628800680000426 -58.44247833740541, 0.4291145657316662 -68.91122382717396, 0.4002030272307393 -78.8569814339672, 0.3767985703605846 -87.57480564424162, 0.3575237846528825 -97.25640576349953, 0.33962640941349975 -111.29314148970882, 0.31622195254334495 -125.46223248835038, 0.2955710364885763 -143.56983359939346, 0.2762988110233803 -174.43582666387255, 0.2528994746382379 -199.61164496439375, 0.23224983870472227 -238.92015515371875, 0.21298145360328535 -281.7176950225821, 0.1909569674439563 -312.8622468107721, 0.1730595922045738 -374.47262439575746, 0.14690415476153917 -422.1479494026821, 0.1317628805800487 -497.76690956741334, 0.11111580488903916 -586.931422054467, 0.08909131872971021 -671.6415364794294, 0.0711965037328337 -768.5776166923116, 0.05743392014091586 -906.2521932534311, 0.03816425491822595 -1068.5882856063554, 0.025781642037133734 -1298.323445043419, 0.007891947525269671 -1945.5099543625452, 0.0010394584575053045 -2959.30406758484, 0.0010753018525915259 -4434.4539440087965, -0.001644955810214288 -5983.45009110729, -0.001619353385152733 -8966.072174173372, -0.0029622005796388073 -13435.467666382601, 0.001204594099153189 -19538.54382325673, -0.00014081333783910743 -26761.428606782032, -0.00011393079152433039 -41321.01717198699, 0.0013006031931346307 -56596.3083539263, 0.0013274857394494077 -78688.41194705585, 0.0027330588753367024 -100000.0000000002, 0.0013761303470665176 diff --git a/src/docs/sphinx/advancedExamples/validationStudies/thermoPoromechanics/thermalConsolidation/thermoConsolidationTemp_0m.csv b/src/docs/sphinx/advancedExamples/validationStudies/thermoPoromechanics/thermalConsolidation/thermoConsolidationTemp_0m.csv deleted file mode 100644 index 0de5c5011e3..00000000000 --- a/src/docs/sphinx/advancedExamples/validationStudies/thermoPoromechanics/thermalConsolidation/thermoConsolidationTemp_0m.csv +++ /dev/null @@ -1,79 +0,0 @@ -0.009847522613947512, -0.00006538323730609363 -0.012984704504125002, 0.06969853096941847 -0.01738715297707653, 0.002353796543054898 -0.022926761479327634, 0.0035306948145858996 -0.03069940537329468, 0.004772976323422995 -0.041107135481112604, 0.006015257832252985 -0.05420287194642072, 0.07577917203898465 -0.07600262436389589, 0.07721760325974003 -0.10334485319892368, 0.07852526800588322 -0.13627088934753467, 0.07970216627741422 -0.18246952481410988, 0.08094444778625132 -0.2519553695461515, 0.0823174957697006 -0.3271634977522269, 0.08342901080392551 -0.4313989465438657, 0.0846059090754494 -0.6049024877789514, 0.08604434029621189 -0.888199860485518, 0.08767892122888554 -1.1893178892332754, 0.08892120273772264 -1.693504709248258, 0.02183800126058344 -2.2676376520806625, 0.023080282769420535 -3.1796546285384446, 0.024518713990183016 -4.458474019210867, 0.025957145210938393 -6.0624267499036755, 0.02726480995708158 -8.500662906977, 0.028703241177836958 -12.10383360127639, 0.0987940715711062 -17.234641266451327, 0.10029788602916767 -24.166718992123197, 0.03314930131472238 -36.034295475456396, 0.034849265484709235 -47.51595108902488, -0.03256085217896043 -67.65793371178145, -0.03105703772089896 -91.99618979378293, 0.038837642960444896 -119.4568517711176, 0.039949157994669804 -155.1177308394172, -0.02752634290631306 -210.9219156090737, -0.026218678160169873 -300.33158671082236, -0.024714863702101297 -414.6827600431619, 0.11383221615174932 -581.4264838157069, 0.3210316951781138 -743.3282497011799, 0.9393609703918457 -880.0194011992243, 1.625950345354255 -979.7215232722228, 2.3808652033026334 -1074.0431528439112, 3.272888709884114 -1214.0881612533528, 4.43939104668101 -1372.364780608189, 5.674480399413113 -1504.424295895417, 6.703677937864995 -1623.9766943305667, 7.869984124949973 -1726.3726233559503, 8.89905089692725 -1835.1475197500574, 10.065291700774921 -2011.2731934570777, 11.848946414514025 -2171.1949815654075, 12.878078569728608 -2343.5855923579347, 14.250145804619194 -2491.092184401604, 15.622147656272467 -2607.61861700225, 16.85691009281804 -2688.202299554259, 17.81725908238549 -2901.8873934524395, 18.914978253535267 -3037.245573084486, 20.561262785692044 -3328.811058957642, 22.276330483495958 -3704.388051751737, 24.40298566014839 -4059.3980338875435, 26.598162469498714 -4314.171321748316, 28.518860448633614 -4727.719521263503, 30.64545024204874 -5102.4501979037805, 32.429039572550536 -5591.797012988701, 34.418455334095256 -6318.6496999461115, 36.7509369417906 -6716.207497680589, 38.19152580937909 -7249.31455296477, 39.632180060204874 -8067.737044641319, 41.5530741890517 -8978.178124241254, 43.611142349768926 -10147.9915353643, 45.05199275030664 -11295.807152428426, 46.355603735736636 -12768.403810586182, 47.59069308846874 -15115.120130475732, 48.55163052717196 -17621.425778644534, 49.306741534832256 -19924.130714683673, 49.65019968040673 -23956.254936660134, 49.92533234299522 -30632.831165884967, 49.92637847479214 -39776.66444790172, 49.927489989826356 -51648.82340157383, 49.99718852079578 -65036.27075400391, 49.99816926935539 -83163.54530608773, 49.930628385217105 -98477.30173013602, 49.93134760082748 diff --git a/src/docs/sphinx/advancedExamples/validationStudies/thermoPoromechanics/thermalConsolidation/thermoConsolidationTemp_4p2m.csv b/src/docs/sphinx/advancedExamples/validationStudies/thermoPoromechanics/thermalConsolidation/thermoConsolidationTemp_4p2m.csv deleted file mode 100644 index 6587dbad78e..00000000000 --- a/src/docs/sphinx/advancedExamples/validationStudies/thermoPoromechanics/thermalConsolidation/thermoConsolidationTemp_4p2m.csv +++ /dev/null @@ -1,79 +0,0 @@ -0.010154838320286299, 0.00006538323730609363 -0.013808079447942344, 0.0013730479834492826 -0.018489302502721943, 0.002615329492286378 -0.024008824998343215, -0.06486017140869649 -0.029770349769560688, 0.004642209848803702 -0.03806730645718513, 0.005688341645722517 -0.05019568508168139, 0.006865239917246413 -0.06418515541884809, 0.007911371714165227 -0.07958968428283685, 0.008826737036464749 -0.10334703142811452, 0.009938252070682552 -0.13013472669692205, 0.010919000630295272 -0.16898322037911714, -0.05655650027068759 -0.2160786180238699, -0.05551036847376878 -0.27629361652116846, 0.014122779258343598 -0.3532888317617504, 0.08375592699045598 -0.45874477739519076, 0.08486744202468088 -0.5956790927636327, 0.08597895705889869 -0.7386429247941366, 0.08689432238119821 -0.9591262266046674, 0.08800583741542312 -1.2647072384377274, 0.08918273568695412 -1.6171794552933025, 0.09022886748386583 -2.1990121708282984, 0.022949516294808348 -3.0834287924135517, 0.024387947515563724 -4.257622973771922, 0.025760995499013006 -5.614116728441364, 0.026937893770544008 -7.069303646215659, 0.027918642330149623 -9.61251211588196, 0.029226307076292812 -11.558564609652718, 0.09859792185918081 -15.716801528530866, 0.099905586605324 -22.72565899768972, 0.10147478430069867 -30.430125012396537, 0.10271706580952866 -40.12527058714834, 0.10389396408105966 -54.56163698475552, 0.036614612892002185 -74.18882658893563, 0.10650929357334604 -99.33408413730689, 0.31351262288779225 -141.4178942814683, 0.8637125648274733 -183.5766496504274, 1.825042302954536 -231.06742154147815, 3.12917635428299 -273.51244862121325, 4.364461856727019 -313.956479536012, 5.599616592696428 -360.3885274683878, 6.766184312730637 -420.03105544083894, 8.41292652754857 -482.10000949068575, 9.92242932725879 -520.4440584795021, 10.882974466538158 -579.3224804765924, 12.117998436032963 -654.7640094544381, 13.764609884376277 -763.1724859787392, 15.205591051388609 -889.417536437656, 17.058094314012145 -989.9966098796707, 18.430292315377343 -1085.215965767925, 19.596663885699634 -1189.5936618416808, 20.763035456021917 -1344.6203326069424, 22.13529884062443 -1473.9479289919866, 23.301670410946716 -1717.8790585492452, 24.94841262576465 -2001.715442798373, 27.349612015869813 -2369.063183064449, 29.065006629860253 -2636.744496793452, 30.711552694966265 -2980.2996180407863, 32.152403095503985 -3266.880439608481, 33.38736168176147 -3580.7165156964043, 34.89666833175977 -4047.181345484366, 36.40610574823268 -4504.6637290568715, 37.91547778146829 -5014.070037651899, 39.28767578283349 -5496.214954253563, 40.52263436909098 -6024.595169045325, 41.82617997128367 -6809.570932258092, 43.267030371821384 -7579.945354707946, 44.50205434131618 -8970.990124537786, 46.21744895530662 -10949.807320316113, 47.59003925609567 -12764.636679653275, 48.55091131156158 -14881.82547508411, 49.16884828735147 -18450.373518659027, 49.71845978015539 -22528.21972490492, 49.925070810045995 -29705.1641920149, 49.994834724252726 -38571.27792798286, 50.06453325522215 -50860.22364751764, 50.065710153493676 -62105.12456086096, 50.06656013557867 -81892.03714535893, 50.0677370338502 -99997.89231566602, 50.068587015935194 diff --git a/src/docs/sphinx/advancedExamples/validationStudies/thermoPoromechanics/thermalConsolidation/thermoConsolidationTemp_5p6m.csv b/src/docs/sphinx/advancedExamples/validationStudies/thermoPoromechanics/thermalConsolidation/thermoConsolidationTemp_5p6m.csv deleted file mode 100644 index b0a5c8a2df0..00000000000 --- a/src/docs/sphinx/advancedExamples/validationStudies/thermoPoromechanics/thermalConsolidation/thermoConsolidationTemp_5p6m.csv +++ /dev/null @@ -1,81 +0,0 @@ -0.010312074131115496, 0.00013076647461218727 -0.016101391474977494, 0.0020268803565173243 -0.021560094495253666, 0.0032691618653544197 -0.0284292169405849, 0.004446060136885421 -0.03635240170142669, 0.00549219193379713 -0.046483767464426694, 0.006538323730715945 -0.06129236848963046, 0.07630223793744051 -0.08463298936573758, 0.07767528592088979 -0.10822009037553766, 0.0787214177178086 -0.14052355128648145, 0.07983293275202641 -0.20633573372439612, 0.08146751368470717 -0.28491002042159363, 0.08284056166815645 -0.40569175463007623, 0.01575736019102436 -0.5516408831558136, 0.01706502493716755 -0.761710114134855, 0.018438072920616833 -1.004372484084059, 0.0882019871273414 -1.4747558792731383, 0.08983656806002216 -2.0999039950600644, 0.09134038251809073 -3.0833638034679507, 0.09297496345076439 -4.390399835067726, 0.09447877790883297 -5.969862622996343, 0.09578644265497616 -7.517259374810218, 0.09676719121458177 -9.912075908769697, 0.16653110542132055 -14.114096308388454, 0.09944790394417424 -20.40823250091457, 0.10101710163954891 -27.748415975181043, 0.3080858141913012 -37.723855920645136, 0.9266766223542646 -47.487917451255115, 1.887875594006708 -55.36093450283487, 2.7115736176022125 -64.53513749551234, 3.741032689003326 -75.22331221585263, 5.044839824145235 -83.7404698922964, 6.00551572989923 -90.40080720133093, 6.966060869178605 -100.63643637440106, 7.926736774932586 -110.3227750905378, 8.887347297449267 -120.9337847435934, 10.053718867771558 -132.56817130753677, 11.151503422158633 -145.32490238426186, 12.180700960610523 -161.76567212667092, 13.41572493010532 -174.63178026735855, 14.376270069384681 -188.52120031663478, 15.336815208664056 -209.84881585339235, 16.571839178158854 -230.0226924789056, 17.875384780351546 -260.0154630833952, 19.04188711714845 -289.4191045522492, 20.414085118513647 -327.135916329084, 21.786348503116162 -353.154823930799, 22.74689364239553 -393.1241980306451, 23.844743580019923 -430.94455482528394, 24.942528134407006 -479.7079079876418, 26.1089650879666 -542.2343972238937, 27.412641456633907 -612.9753616051692, 28.3733827456252 -692.8723087084063, 29.67705911429251 -783.2162655995311, 30.84356145108941 -899.0297249815874, 32.078716187058816 -1048.013503299308, 33.10817525845994 -1166.6748064473145, 34.06885116421392 -1298.8262824664105, 34.8923530380975 -1514.0309103321279, 35.99039912543381 -1711.5543563700394, 36.9511404144251 -2026.0797531807355, 37.98066486906352 -2325.8703601749457, 38.941471541292124 -2670.0760150629, 39.83369119758552 -2972.457956261255, 40.7257800874043 -3464.896292992599, 41.89241319067582 -4164.959538113848, 43.059177060421945 -4708.526818887187, 43.88274431754284 -5488.574560273634, 45.04937742081435 -6398.2550652825785, 46.01024947628026 -7809.908666132243, 47.2456657451989 -9245.493608546547, 48.13801616796691 -11287.001535141568, 48.89332332533913 -13780.168450737357, 49.442869434905745 -17618.454765575774, 49.855437662313875 -22528.21972490492, 49.925070810045995 -30165.749763528773, 49.92631309155483 -40391.70829122072, 49.99614238899887 -49322.08108319346, 49.99699237108386 -62107.74260359248, 49.92938610370827 -78206.15645939638, 49.93036685226787 -98477.30173013602, 49.93134760082748 diff --git a/src/docs/sphinx/basicExamples/multiphaseFlowWithWells/Example.rst b/src/docs/sphinx/basicExamples/multiphaseFlowWithWells/Example.rst index 7fb95a4a927..640caa64300 100644 --- a/src/docs/sphinx/basicExamples/multiphaseFlowWithWells/Example.rst +++ b/src/docs/sphinx/basicExamples/multiphaseFlowWithWells/Example.rst @@ -67,7 +67,7 @@ It is illustrated in :ref:`TutorialPoroelasticity` for a poroelastic test case. The three solvers employed in this example are: - the single-physics reservoir flow solver, a solver of type **CompositionalMultiphaseFVM** named ``compositionalMultiphaseFlow`` (more information on this solver at :ref:`CompositionalMultiphaseFlow`), - - the single-physics well solver, a solver of type **CompositionalMultiphaseWell** named ``compositionalMultiphaseWell`` (more information on this solver at :ref:`CompositionalMultiphaseWell`), + - the single-physics well solver manager, a solver of type **WellManager** named ``compositionalMultiphaseWell`` (more information on this solver at :ref:`CompositionalMultiphaseWell`), - the coupling solver that binds the two single-physics solvers above, an object of type **CompositionalMultiphaseReservoir** named ``coupledFlowAndWells``. The **Solvers** XML block is shown below. @@ -146,7 +146,7 @@ needed in the XML file to define them. Each well is defined internally (i.e., not imported from a file) in a separate **InternalWell** XML sub-block. An **InternalWell** sub-block must point to the region corresponding to this well using the attribute -``wellRegionName``, and to the control of this well using the attribute ``wellControl``. +``wellRegionName``, and to the control of this well using the attribute ``control``. Each well is defined using a vertical polyline going through the seven layers of the mesh with a perforation in each layer. diff --git a/src/docs/sphinx/buildGuide/ContinuousIntegration.rst b/src/docs/sphinx/buildGuide/ContinuousIntegration.rst index 3740fe464a3..402c3c7ea11 100644 --- a/src/docs/sphinx/buildGuide/ContinuousIntegration.rst +++ b/src/docs/sphinx/buildGuide/ContinuousIntegration.rst @@ -6,9 +6,7 @@ Continuous Integration process To save building time, the third party libraries (that do not change so often) and GEOS are build separately. Everytime a pull is requested in the TPL repository, docker images are generated and deployed on `dockerhub `_. -The repository names (`ubuntu18.04-gcc8 `_, -`centos7.7.1908-clang9.0.0 `_, `centos7.6.1810-gcc8.3.1-cuda10.1.243 `_ etc.) -obviously reflect the OS and the compiler flavour used. +The repository names (e.g. `ubuntu24.04-gcc13-cuda12.9.1 `_) obviously reflect the OS and the compiler flavour used. For each image, the unique tag ``${PULL_REQUEST_NUMBER}-${BUILD_NUMBER}`` (defined as ``${{ github.event.number }}-${{ github.run_number }}`` in github actions) is used so we can connect the related code source in a rather convenient way. Each docker contains the ``org.opencontainers.image.created`` and ``org.opencontainers.image.revision`` labels to provide additional information. diff --git a/src/docs/sphinx/buildGuide/Dependencies.rst b/src/docs/sphinx/buildGuide/Dependencies.rst index 03824bb29a4..1ac14328374 100644 --- a/src/docs/sphinx/buildGuide/Dependencies.rst +++ b/src/docs/sphinx/buildGuide/Dependencies.rst @@ -57,10 +57,10 @@ uncrustify_ 401a409 :code:`ENABLE_UNCRUSTIFY` :code:`UNCRUSTIFY_EXECUTABL .. _conduit: https://github.com/LLNL/conduit .. _CHAI : https://github.com/LLNL/CHAI .. _RAJA : https://github.com/LLNL/RAJA -.. _hdf5 : https://portal.hdfgroup.org/display/HDF5/HDF5 +.. _hdf5 : https://www.hdfgroup.org/solutions/hdf5 .. _mathpresso : https://github.com/kobalicek/mathpresso .. _pugixml : https://pugixml.org -.. _parmetis : http://glaros.dtc.umn.edu/gkhome/metis/parmetis/overview +.. _parmetis : https://github.com/KarypisLab/ParMETIS .. _silo : https://wci.llnl.gov/simulation/computer-codes/silo .. _VTK : https://vtk.org/ .. _suitesparse : https://people.engr.tamu.edu/davis/suitesparse.html diff --git a/src/docs/sphinx/tutorials/step01/Tutorial.rst b/src/docs/sphinx/tutorials/step01/Tutorial.rst index 8c9b5697689..e67d33b3788 100644 --- a/src/docs/sphinx/tutorials/step01/Tutorial.rst +++ b/src/docs/sphinx/tutorials/step01/Tutorial.rst @@ -137,7 +137,7 @@ In production runs, you may want to suppress most console output. For solvers of the ``SinglePhaseFVM`` family, one required attribute is a discretization scheme. Here, we use a Two-Point Flux Approximation (TPFA) finite volume discretization scheme called ``singlePhaseTPFA``. -To know the list of admissible values of an attribute, please see `GEOS's XML schema `_. +To know the list of admissible values of an attribute, please see `GEOS's XML schema `_. This discretization type must know how to find permeability values that it uses internally to compute transmissibilities. The ``permeabilityNames`` attribute tells the solver the user-defined name (the *handle*) of the permeability values that will be defined elsewhere in the input file. diff --git a/src/index.rst b/src/index.rst index a912239cb52..1e52aa22f49 100644 --- a/src/index.rst +++ b/src/index.rst @@ -39,7 +39,7 @@ you have suggestions for improving the guides below, please post an issue on our +++ - .. button-ref:: QuickStart + .. button-link:: docs/sphinx/QuickStart.html :expand: :color: info :click-parent: @@ -55,7 +55,7 @@ you have suggestions for improving the guides below, please post an issue on our +++ - .. button-ref:: Tutorials + .. button-link:: docs/sphinx/tutorials/Index.html :expand: :color: info :click-parent: @@ -71,7 +71,7 @@ you have suggestions for improving the guides below, please post an issue on our +++ - .. button-ref:: BasicExamples + .. button-link:: docs/sphinx/basicExamples/Index.html :expand: :color: info :click-parent: @@ -87,7 +87,7 @@ you have suggestions for improving the guides below, please post an issue on our +++ - .. button-ref:: AdvancedExamples + .. button-link:: docs/sphinx/advancedExamples/Index.html :expand: :color: info :click-parent: @@ -103,7 +103,7 @@ you have suggestions for improving the guides below, please post an issue on our +++ - .. button-ref:: UserGuide + .. button-link:: docs/sphinx/userGuide/Index.html :expand: :color: info :click-parent: