diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index b07e2d5..36c7dd9 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -12,7 +12,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ['3.8'] + python-version: ['3.10'] os: [ubuntu-latest] runs-on: ${{ matrix.os }} diff --git a/examples/cemracs2023/ml/recovery.py b/examples/cemracs2023/ml/recovery.py index c980d36..e5a2772 100644 --- a/examples/cemracs2023/ml/recovery.py +++ b/examples/cemracs2023/ml/recovery.py @@ -6,10 +6,11 @@ """ import os + +import matplotlib.pyplot as plt import numpy as np from ecl.summary import EclSum from mako.template import Template -import matplotlib.pyplot as plt npoints, npruns = 20, 5 tmin, tmax = 0, 30 @@ -18,7 +19,7 @@ FLOW = "/Users/dmar/Github/opm/build/opm-simulators/bin/flow" FLAGS = ( " --linear-solver-reduction=1e-5 --relaxed-max-pv-fraction=0" - + " --ecl-enable-drift-compensation=0 --newton-max-iterations=50" + + " --enable-drift-compensation=0 --newton-max-iterations=50" + " --newton-min-iterations=5 --tolerance-mb=1e-7 --tolerance-wells=1e-5" + " --relaxed-well-flow-tol=1e-5 --use-multisegment-well=false --enable-tuning=true" + " --enable-opm-rst-file=true --linear-solver=cprw --enable-well-operability-check=false" diff --git a/examples/cemracs2023/ml_cyclic_h2/h2.mako b/examples/cemracs2023/ml_cyclic_h2/h2.mako index f6d0868..012baa6 100644 --- a/examples/cemracs2023/ml_cyclic_h2/h2.mako +++ b/examples/cemracs2023/ml_cyclic_h2/h2.mako @@ -1,5 +1,5 @@ """Set the full path to the flow executable and flags""" -${flow} --linear-solver-reduction=1e-5 --relaxed-max-pv-fraction=0 --ecl-enable-drift-compensation=0 --newton-max-iterations=50 --newton-min-iterations=5 --tolerance-mb=1e-7 --tolerance-wells=1e-5 --relaxed-well-flow-tol=1e-5 --use-multisegment-well=false --enable-tuning=true --enable-opm-rst-file=true --linear-solver=cprw --enable-well-operability-check=false --min-time-step-before-shutting-problematic-wells-in-days=1e-99 +${flow} --linear-solver-reduction=1e-5 --relaxed-max-pv-fraction=0 --enable-drift-compensation=0 --newton-max-iterations=50 --newton-min-iterations=5 --tolerance-mb=1e-7 --tolerance-wells=1e-5 --relaxed-well-flow-tol=1e-5 --use-multisegment-well=false --enable-tuning=true --enable-opm-rst-file=true --linear-solver=cprw --enable-well-operability-check=false --min-time-step-before-shutting-problematic-wells-in-days=1e-99 """Set the model parameters""" h2store okoroafor2023 #Model (co2store/h2store) diff --git a/examples/cemracs2023/ml_example_co2/co2.mako b/examples/cemracs2023/ml_example_co2/co2.mako index 7c2fc1d..8b7454e 100644 --- a/examples/cemracs2023/ml_example_co2/co2.mako +++ b/examples/cemracs2023/ml_example_co2/co2.mako @@ -1,5 +1,5 @@ """Set the full path to the flow executable and flags""" -${flow} --linear-solver-reduction=1e-5 --relaxed-max-pv-fraction=0 --ecl-enable-drift-compensation=0 --newton-max-iterations=50 --newton-min-iterations=5 --tolerance-mb=1e-7 --tolerance-wells=1e-5 --relaxed-well-flow-tol=1e-5 --use-multisegment-well=false --enable-tuning=true --enable-opm-rst-file=true --linear-solver=cprw --enable-well-operability-check=false --min-time-step-before-shutting-problematic-wells-in-days=1e-99 +${flow} --linear-solver-reduction=1e-5 --relaxed-max-pv-fraction=0 --enable-drift-compensation=0 --newton-max-iterations=50 --newton-min-iterations=5 --tolerance-mb=1e-7 --tolerance-wells=1e-5 --relaxed-well-flow-tol=1e-5 --use-multisegment-well=false --enable-tuning=true --enable-opm-rst-file=true --linear-solver=cprw --enable-well-operability-check=false --min-time-step-before-shutting-problematic-wells-in-days=1e-99 """Set the model parameters""" co2store base #Model (co2store/h2store) diff --git a/examples/cemracs2023/ml_example_h2/h2.mako b/examples/cemracs2023/ml_example_h2/h2.mako index 84d76e6..de832be 100644 --- a/examples/cemracs2023/ml_example_h2/h2.mako +++ b/examples/cemracs2023/ml_example_h2/h2.mako @@ -1,5 +1,5 @@ """Set the full path to the flow executable and flags""" -${flow} --linear-solver-reduction=1e-5 --relaxed-max-pv-fraction=0 --ecl-enable-drift-compensation=0 --newton-max-iterations=50 --newton-min-iterations=5 --tolerance-mb=1e-7 --tolerance-wells=1e-5 --relaxed-well-flow-tol=1e-5 --use-multisegment-well=false --enable-tuning=true --enable-opm-rst-file=true --linear-solver=cprw --enable-well-operability-check=false --min-time-step-before-shutting-problematic-wells-in-days=1e-99 +${flow} --linear-solver-reduction=1e-5 --relaxed-max-pv-fraction=0 --enable-drift-compensation=0 --newton-max-iterations=50 --newton-min-iterations=5 --tolerance-mb=1e-7 --tolerance-wells=1e-5 --relaxed-well-flow-tol=1e-5 --use-multisegment-well=false --enable-tuning=true --enable-opm-rst-file=true --linear-solver=cprw --enable-well-operability-check=false --min-time-step-before-shutting-problematic-wells-in-days=1e-99 """Set the model parameters""" h2store okoroafor2023 #Model (co2store/h2store) diff --git a/examples/cemracs2023/peaceman_well-model_nn/2023-08-10_ml_well_model_CO2_realistic_critical_point/co2_3d_finescale_wellmodel.mako b/examples/cemracs2023/peaceman_well-model_nn/2023-08-10_ml_well_model_CO2_realistic_critical_point/co2_3d_finescale_wellmodel.mako index cbe0f81..97eff29 100644 --- a/examples/cemracs2023/peaceman_well-model_nn/2023-08-10_ml_well_model_CO2_realistic_critical_point/co2_3d_finescale_wellmodel.mako +++ b/examples/cemracs2023/peaceman_well-model_nn/2023-08-10_ml_well_model_CO2_realistic_critical_point/co2_3d_finescale_wellmodel.mako @@ -1,5 +1,5 @@ """Set the full path to the flow executable and flags""" -${flow} --ml-wi-filename="" --linear-solver-reduction=1e-5 --relaxed-max-pv-fraction=0 --ecl-enable-drift-compensation=0 --newton-max-iterations=50 --newton-min-iterations=5 --tolerance-mb=1e-7 --tolerance-wells=1e-5 --relaxed-well-flow-tol=1e-5 --use-multisegment-well=false --enable-tuning=true --enable-opm-rst-file=true --linear-solver=cprw --enable-well-operability-check=false --min-time-step-before-shutting-problematic-wells-in-days=1e-99 +${flow} --ml-wi-filename="" --linear-solver-reduction=1e-5 --relaxed-max-pv-fraction=0 --enable-drift-compensation=0 --newton-max-iterations=50 --newton-min-iterations=5 --tolerance-mb=1e-7 --tolerance-wells=1e-5 --relaxed-well-flow-tol=1e-5 --use-multisegment-well=false --enable-tuning=true --enable-opm-rst-file=true --linear-solver=cprw --enable-well-operability-check=false --min-time-step-before-shutting-problematic-wells-in-days=1e-99 """Set the model parameters""" co2store base #Model (co2store/h2store) diff --git a/examples/cemracs2023/peaceman_well-model_nn/2023-08-10_ml_well_model_CO2_realistic_critical_point/co2_3d_finescale_wellmodel_large_reservoir.mako b/examples/cemracs2023/peaceman_well-model_nn/2023-08-10_ml_well_model_CO2_realistic_critical_point/co2_3d_finescale_wellmodel_large_reservoir.mako index d7f80aa..51555d4 100644 --- a/examples/cemracs2023/peaceman_well-model_nn/2023-08-10_ml_well_model_CO2_realistic_critical_point/co2_3d_finescale_wellmodel_large_reservoir.mako +++ b/examples/cemracs2023/peaceman_well-model_nn/2023-08-10_ml_well_model_CO2_realistic_critical_point/co2_3d_finescale_wellmodel_large_reservoir.mako @@ -1,5 +1,5 @@ """Set the full path to the flow executable and flags""" -${flow} --ml-wi-filename="" --linear-solver-reduction=1e-5 --relaxed-max-pv-fraction=0 --ecl-enable-drift-compensation=0 --newton-max-iterations=50 --newton-min-iterations=5 --tolerance-mb=1e-7 --tolerance-wells=1e-5 --relaxed-well-flow-tol=1e-5 --use-multisegment-well=false --enable-tuning=true --enable-opm-rst-file=true --linear-solver=cprw --enable-well-operability-check=false --min-time-step-before-shutting-problematic-wells-in-days=1e-99 +${flow} --ml-wi-filename="" --linear-solver-reduction=1e-5 --relaxed-max-pv-fraction=0 --enable-drift-compensation=0 --newton-max-iterations=50 --newton-min-iterations=5 --tolerance-mb=1e-7 --tolerance-wells=1e-5 --relaxed-well-flow-tol=1e-5 --use-multisegment-well=false --enable-tuning=true --enable-opm-rst-file=true --linear-solver=cprw --enable-well-operability-check=false --min-time-step-before-shutting-problematic-wells-in-days=1e-99 """Set the model parameters""" co2store base #Model (co2store/h2store) diff --git a/examples/cemracs2023/peaceman_well-model_nn/2023-08-10_ml_well_model_CO2_realistic_critical_point/co2_3d_flow_wellmodel.mako b/examples/cemracs2023/peaceman_well-model_nn/2023-08-10_ml_well_model_CO2_realistic_critical_point/co2_3d_flow_wellmodel.mako index 4169ad3..59db0ba 100644 --- a/examples/cemracs2023/peaceman_well-model_nn/2023-08-10_ml_well_model_CO2_realistic_critical_point/co2_3d_flow_wellmodel.mako +++ b/examples/cemracs2023/peaceman_well-model_nn/2023-08-10_ml_well_model_CO2_realistic_critical_point/co2_3d_flow_wellmodel.mako @@ -1,5 +1,5 @@ """Set the full path to the flow executable and flags""" -${flow} --ml-wi-filename="" --linear-solver-reduction=1e-5 --relaxed-max-pv-fraction=0 --ecl-enable-drift-compensation=0 --newton-max-iterations=50 --newton-min-iterations=5 --tolerance-mb=1e-7 --tolerance-wells=1e-5 --relaxed-well-flow-tol=1e-5 --use-multisegment-well=false --enable-tuning=true --enable-opm-rst-file=true --linear-solver=cprw --enable-well-operability-check=false --min-time-step-before-shutting-problematic-wells-in-days=1e-99 +${flow} --ml-wi-filename="" --linear-solver-reduction=1e-5 --relaxed-max-pv-fraction=0 --enable-drift-compensation=0 --newton-max-iterations=50 --newton-min-iterations=5 --tolerance-mb=1e-7 --tolerance-wells=1e-5 --relaxed-well-flow-tol=1e-5 --use-multisegment-well=false --enable-tuning=true --enable-opm-rst-file=true --linear-solver=cprw --enable-well-operability-check=false --min-time-step-before-shutting-problematic-wells-in-days=1e-99 """Set the model parameters""" co2store base #Model (co2store/h2store) diff --git a/examples/cemracs2023/peaceman_well-model_nn/2023-08-10_ml_well_model_CO2_realistic_critical_point/co2_3d_flow_wellmodel_large_reservoir.mako b/examples/cemracs2023/peaceman_well-model_nn/2023-08-10_ml_well_model_CO2_realistic_critical_point/co2_3d_flow_wellmodel_large_reservoir.mako index 13c456d..f6680f6 100644 --- a/examples/cemracs2023/peaceman_well-model_nn/2023-08-10_ml_well_model_CO2_realistic_critical_point/co2_3d_flow_wellmodel_large_reservoir.mako +++ b/examples/cemracs2023/peaceman_well-model_nn/2023-08-10_ml_well_model_CO2_realistic_critical_point/co2_3d_flow_wellmodel_large_reservoir.mako @@ -1,5 +1,5 @@ """Set the full path to the flow executable and flags""" -${flow} --ml-wi-filename="" --linear-solver-reduction=1e-5 --relaxed-max-pv-fraction=0 --ecl-enable-drift-compensation=0 --newton-max-iterations=50 --newton-min-iterations=5 --tolerance-mb=1e-7 --tolerance-wells=1e-5 --relaxed-well-flow-tol=1e-5 --use-multisegment-well=false --enable-tuning=true --enable-opm-rst-file=true --linear-solver=cprw --enable-well-operability-check=false --min-time-step-before-shutting-problematic-wells-in-days=1e-99 +${flow} --ml-wi-filename="" --linear-solver-reduction=1e-5 --relaxed-max-pv-fraction=0 --enable-drift-compensation=0 --newton-max-iterations=50 --newton-min-iterations=5 --tolerance-mb=1e-7 --tolerance-wells=1e-5 --relaxed-well-flow-tol=1e-5 --use-multisegment-well=false --enable-tuning=true --enable-opm-rst-file=true --linear-solver=cprw --enable-well-operability-check=false --min-time-step-before-shutting-problematic-wells-in-days=1e-99 """Set the model parameters""" co2store base #Model (co2store/h2store) diff --git a/examples/cemracs2023/peaceman_well-model_nn/2023-08-10_ml_well_model_CO2_realistic_critical_point/co2_3d_ml_wellmodel.mako b/examples/cemracs2023/peaceman_well-model_nn/2023-08-10_ml_well_model_CO2_realistic_critical_point/co2_3d_ml_wellmodel.mako index f6891df..738aac7 100644 --- a/examples/cemracs2023/peaceman_well-model_nn/2023-08-10_ml_well_model_CO2_realistic_critical_point/co2_3d_ml_wellmodel.mako +++ b/examples/cemracs2023/peaceman_well-model_nn/2023-08-10_ml_well_model_CO2_realistic_critical_point/co2_3d_ml_wellmodel.mako @@ -1,5 +1,5 @@ """Set the full path to the flow executable and flags""" -${flow} --ml-wi-filename="${pwd}/model_pressure_radius_WI/WI.model" --linear-solver-reduction=1e-5 --relaxed-max-pv-fraction=0 --ecl-enable-drift-compensation=0 --newton-max-iterations=50 --newton-min-iterations=5 --tolerance-mb=1e-7 --tolerance-wells=1e-5 --relaxed-well-flow-tol=1e-5 --use-multisegment-well=false --enable-tuning=true --enable-opm-rst-file=true --linear-solver=cprw --enable-well-operability-check=false --min-time-step-before-shutting-problematic-wells-in-days=1e-99 +${flow} --ml-wi-filename="${pwd}/model_pressure_radius_WI/WI.model" --linear-solver-reduction=1e-5 --relaxed-max-pv-fraction=0 --enable-drift-compensation=0 --newton-max-iterations=50 --newton-min-iterations=5 --tolerance-mb=1e-7 --tolerance-wells=1e-5 --relaxed-well-flow-tol=1e-5 --use-multisegment-well=false --enable-tuning=true --enable-opm-rst-file=true --linear-solver=cprw --enable-well-operability-check=false --min-time-step-before-shutting-problematic-wells-in-days=1e-99 """Set the model parameters""" co2store base #Model (co2store/h2store) diff --git a/examples/cemracs2023/peaceman_well-model_nn/2023-08-10_ml_well_model_CO2_realistic_critical_point/co2_3d_ml_wellmodel_large_reservoir.mako b/examples/cemracs2023/peaceman_well-model_nn/2023-08-10_ml_well_model_CO2_realistic_critical_point/co2_3d_ml_wellmodel_large_reservoir.mako index 21b440b..6afcbf1 100644 --- a/examples/cemracs2023/peaceman_well-model_nn/2023-08-10_ml_well_model_CO2_realistic_critical_point/co2_3d_ml_wellmodel_large_reservoir.mako +++ b/examples/cemracs2023/peaceman_well-model_nn/2023-08-10_ml_well_model_CO2_realistic_critical_point/co2_3d_ml_wellmodel_large_reservoir.mako @@ -1,5 +1,5 @@ """Set the full path to the flow executable and flags""" -${flow} --ml-wi-filename="${pwd}/model_pressure_radius_WI/WI.model" --linear-solver-reduction=1e-5 --relaxed-max-pv-fraction=0 --ecl-enable-drift-compensation=0 --newton-max-iterations=50 --newton-min-iterations=5 --tolerance-mb=1e-7 --tolerance-wells=1e-5 --relaxed-well-flow-tol=1e-5 --use-multisegment-well=false --enable-tuning=true --enable-opm-rst-file=true --linear-solver=cprw --enable-well-operability-check=false --min-time-step-before-shutting-problematic-wells-in-days=1e-99 +${flow} --ml-wi-filename="${pwd}/model_pressure_radius_WI/WI.model" --linear-solver-reduction=1e-5 --relaxed-max-pv-fraction=0 --enable-drift-compensation=0 --newton-max-iterations=50 --newton-min-iterations=5 --tolerance-mb=1e-7 --tolerance-wells=1e-5 --relaxed-well-flow-tol=1e-5 --use-multisegment-well=false --enable-tuning=true --enable-opm-rst-file=true --linear-solver=cprw --enable-well-operability-check=false --min-time-step-before-shutting-problematic-wells-in-days=1e-99 """Set the model parameters""" co2store base #Model (co2store/h2store) diff --git a/examples/cemracs2023/peaceman_well-model_nn/2023-08-10_ml_well_model_CO2_realistic_critical_point/run_ensemble.py b/examples/cemracs2023/peaceman_well-model_nn/2023-08-10_ml_well_model_CO2_realistic_critical_point/run_ensemble.py index 8601436..8ef6be6 100644 --- a/examples/cemracs2023/peaceman_well-model_nn/2023-08-10_ml_well_model_CO2_realistic_critical_point/run_ensemble.py +++ b/examples/cemracs2023/peaceman_well-model_nn/2023-08-10_ml_well_model_CO2_realistic_critical_point/run_ensemble.py @@ -10,6 +10,7 @@ 1. WI [m*s] """ + from __future__ import annotations import logging @@ -54,13 +55,15 @@ X: float = 2.500000e-01 # Outer coordinates of first cell. Y: float = -1.443376e-01 WELL_RADIUS: float = math.sqrt(X**2 + Y**2) # unit: [m]; Fixed during training. -DENSITY: float = 12.9788 # unit: kg/m^3; for 72 bar, 30.9780 °C. Is this at surface conditions or not? +DENSITY: float = ( + 12.9788 # unit: kg/m^3; for 72 bar, 30.9780 °C. Is this at surface conditions or not? +) VISCOSITY: float = 1.52786e-05 # unit: Pa*s; for 72 bar, 30.9780 °C FLOW = "flow" FLAGS = ( " --linear-solver-reduction=1e-5 --relaxed-max-pv-fraction=0" - + " --ecl-enable-drift-compensation=0 --newton-max-iterations=50" + + " --enable-drift-compensation=0 --newton-max-iterations=50" + " --newton-min-iterations=5 --tolerance-mb=1e-7 --tolerance-wells=1e-5" + " --relaxed-well-flow-tol=1e-5 --use-multisegment-well=false --enable-tuning=true" + " --enable-opm-rst-file=true --linear-solver=cprw" diff --git a/examples/cemracs2023/peaceman_well-model_nn/example_framework_peaceman_co2/co2_3d_flow_wellmodel.mako b/examples/cemracs2023/peaceman_well-model_nn/example_framework_peaceman_co2/co2_3d_flow_wellmodel.mako index 77a7d2a..a046624 100644 --- a/examples/cemracs2023/peaceman_well-model_nn/example_framework_peaceman_co2/co2_3d_flow_wellmodel.mako +++ b/examples/cemracs2023/peaceman_well-model_nn/example_framework_peaceman_co2/co2_3d_flow_wellmodel.mako @@ -1,5 +1,5 @@ """Set the full path to the flow executable and flags""" -${flow} --ml-wi-filename="" --linear-solver-reduction=1e-5 --relaxed-max-pv-fraction=0 --ecl-enable-drift-compensation=0 --newton-max-iterations=50 --newton-min-iterations=5 --tolerance-mb=1e-7 --tolerance-wells=1e-5 --relaxed-well-flow-tol=1e-5 --use-multisegment-well=false --enable-tuning=true --enable-opm-rst-file=true --linear-solver=cprw --enable-well-operability-check=false --min-time-step-before-shutting-problematic-wells-in-days=1e-99 +${flow} --ml-wi-filename="" --linear-solver-reduction=1e-5 --relaxed-max-pv-fraction=0 --enable-drift-compensation=0 --newton-max-iterations=50 --newton-min-iterations=5 --tolerance-mb=1e-7 --tolerance-wells=1e-5 --relaxed-well-flow-tol=1e-5 --use-multisegment-well=false --enable-tuning=true --enable-opm-rst-file=true --linear-solver=cprw --enable-well-operability-check=false --min-time-step-before-shutting-problematic-wells-in-days=1e-99 """Set the model parameters""" co2store base #Model (co2store/h2store) diff --git a/examples/cemracs2023/peaceman_well-model_nn/example_framework_peaceman_co2/co2_3d_ml_wellmodel.mako b/examples/cemracs2023/peaceman_well-model_nn/example_framework_peaceman_co2/co2_3d_ml_wellmodel.mako index 281cabc..6848622 100644 --- a/examples/cemracs2023/peaceman_well-model_nn/example_framework_peaceman_co2/co2_3d_ml_wellmodel.mako +++ b/examples/cemracs2023/peaceman_well-model_nn/example_framework_peaceman_co2/co2_3d_ml_wellmodel.mako @@ -1,5 +1,5 @@ """Set the full path to the flow executable and flags""" -${flow} --ml-wi-filename="${pwd}/water_re.modelPeaceman" --linear-solver-reduction=1e-5 --relaxed-max-pv-fraction=0 --ecl-enable-drift-compensation=0 --newton-max-iterations=50 --newton-min-iterations=5 --tolerance-mb=1e-7 --tolerance-wells=1e-5 --relaxed-well-flow-tol=1e-5 --use-multisegment-well=false --enable-tuning=true --enable-opm-rst-file=true --linear-solver=cprw --enable-well-operability-check=false --min-time-step-before-shutting-problematic-wells-in-days=1e-99 +${flow} --ml-wi-filename="${pwd}/water_re.modelPeaceman" --linear-solver-reduction=1e-5 --relaxed-max-pv-fraction=0 --enable-drift-compensation=0 --newton-max-iterations=50 --newton-min-iterations=5 --tolerance-mb=1e-7 --tolerance-wells=1e-5 --relaxed-well-flow-tol=1e-5 --use-multisegment-well=false --enable-tuning=true --enable-opm-rst-file=true --linear-solver=cprw --enable-well-operability-check=false --min-time-step-before-shutting-problematic-wells-in-days=1e-99 """Set the model parameters""" co2store base #Model (co2store/h2store) diff --git a/examples/cemracs2023/peaceman_well-model_nn/example_framework_peaceman_co2/co2_nearwell.mako b/examples/cemracs2023/peaceman_well-model_nn/example_framework_peaceman_co2/co2_nearwell.mako index 890b2c9..7cb5938 100644 --- a/examples/cemracs2023/peaceman_well-model_nn/example_framework_peaceman_co2/co2_nearwell.mako +++ b/examples/cemracs2023/peaceman_well-model_nn/example_framework_peaceman_co2/co2_nearwell.mako @@ -1,5 +1,5 @@ """Set the full path to the flow executable and flags""" -${flow} --linear-solver-reduction=1e-5 --relaxed-max-pv-fraction=0 --ecl-enable-drift-compensation=0 --newton-max-iterations=50 --newton-min-iterations=5 --use-multisegment-well=false --enable-tuning=true --enable-opm-rst-file=true --linear-solver=cprw --enable-well-operability-check=false --min-time-step-before-shutting-problematic-wells-in-days=1e-99 +${flow} --linear-solver-reduction=1e-5 --relaxed-max-pv-fraction=0 --enable-drift-compensation=0 --newton-max-iterations=50 --newton-min-iterations=5 --use-multisegment-well=false --enable-tuning=true --enable-opm-rst-file=true --linear-solver=cprw --enable-well-operability-check=false --min-time-step-before-shutting-problematic-wells-in-days=1e-99 """Set the model parameters""" co2store base #Model (co2store/h2store) diff --git a/examples/cemracs2023/peaceman_well-model_nn/example_framework_peaceman_co2/example.py b/examples/cemracs2023/peaceman_well-model_nn/example_framework_peaceman_co2/example.py index 2d2bab5..df740c5 100644 --- a/examples/cemracs2023/peaceman_well-model_nn/example_framework_peaceman_co2/example.py +++ b/examples/cemracs2023/peaceman_well-model_nn/example_framework_peaceman_co2/example.py @@ -24,12 +24,15 @@ def compute_peaceman(k_h: float, r_e: float, r_w: float) -> float: from the Peaceman well model. .. math:: WI\cdot\frac{\mu}{\rho} = \frac{2\pi hk}{\ln (r_e/r_w)} - Parameters: - k_h: Permeability times the cell thickness (thickness fix to 1 m). - r_e: Equivalent well-block radius. - r_w: Wellbore radius. + + Args: + k_h (float): Permeability times the cell thickness (thickness fix to 1 m). + r_e (float): Equivalent well-block radius. + r_w (float): Wellbore radius. + Returns: - :math:`WI\cdot\frac{\mu}{\rho}` + float: :math:`WI\cdot\frac{\mu}{\rho}` + """ w_i = (2 * math.pi * k_h) / (math.log(r_e / r_w)) return w_i diff --git a/examples/cemracs2023/peaceman_well-model_nn/example_framework_peaceman_water/example.py b/examples/cemracs2023/peaceman_well-model_nn/example_framework_peaceman_water/example.py index fb09a67..7f6453c 100644 --- a/examples/cemracs2023/peaceman_well-model_nn/example_framework_peaceman_water/example.py +++ b/examples/cemracs2023/peaceman_well-model_nn/example_framework_peaceman_water/example.py @@ -7,13 +7,14 @@ branches. This can be achieve by running the build_dune_and_opm-flow_ml-peaceman-branch.bash """ -import os import math +import os + +import matplotlib +import matplotlib.pyplot as plt import numpy as np from ecl.eclfile import EclFile from mako.template import Template -import matplotlib -import matplotlib.pyplot as plt np.random.seed(7) @@ -23,12 +24,14 @@ def compute_peaceman(k_h: float, r_e: float, r_w: float) -> float: from the Peaceman well model. .. math:: WI\cdot\frac{\mu}{\rho} = \frac{2\pi hk}{\ln (r_e/r_w)} - Parameters: - k_h: Permeability times the cell thickness (thickness fix to 1 m). - r_e: Equivalent well-block radius. - r_w: Wellbore radius. + + Args: + k_h (float): Permeability times the cell thickness (thickness fix to 1 m). + r_e (float): Equivalent well-block radius. + r_w (float): Wellbore radius. + Returns: - :math:`WI\cdot\frac{\mu}{\rho}` + float: :math:`WI\cdot\frac{\mu}{\rho}` """ w_i = (2 * math.pi * k_h) / (math.log(r_e / r_w)) return w_i @@ -37,19 +40,21 @@ def compute_peaceman(k_h: float, r_e: float, r_w: float) -> float: # Give the path to the OPM Flow ML repos PYOPM = "/Users/dmar/Github/pyopmnearwell/examples/cemracs2023/peaceman_well-model_nn" FLOW = f"{PYOPM}/build/opm-simulators/bin/flow_gaswater_dissolution_diffuse" -PERMEABILITY = 1e-12 # K between 1e-13 to 1e-12 m2 -WELLRADI = 0.1 # Between 0.025 to 0.125 m -RATE = 1.668e-05 # [sm3/s] Fix to 1.665e-2 kg/s, ref_dens = 998.108 kg/sm3 +PERMEABILITY = 1e-12 # K between 1e-13 to 1e-12 m2 +WELLRADI = 0.1 # Between 0.025 to 0.125 m +RATE = 1.668e-05 # [sm3/s] Fix to 1.665e-2 kg/s, ref_dens = 998.108 kg/sm3 BOFAC = 1.0 VISCOSCITY = 0.6532 * 0.001 -CLIP = 2 # Remove the distances with value less than this [m] +CLIP = 2 # Remove the distances with value less than this [m] -#Run the main routine +# Run the main routine NPOINTS, NPRUNS = 20, 5 -PERMEABILITYHS = np.linspace(1e-13, 1e-11, NPOINTS) # K between 1e-13 to 1e-12 m2 and H betweem 1 to 10, i,e, [1e-13,1e-11] -#WELLRADIS = [0.025, .05, .1, .125] -#WELLRADIS = np.linspace(.05, .125, NPOINTS) -WELLRADIS = [0.1, .125] +PERMEABILITYHS = np.linspace( + 1e-13, 1e-11, NPOINTS +) # K between 1e-13 to 1e-12 m2 and H betweem 1 to 10, i,e, [1e-13,1e-11] +# WELLRADIS = [0.025, .05, .1, .125] +# WELLRADIS = np.linspace(.05, .125, NPOINTS) +WELLRADIS = [0.1, 0.125] nradis = len(WELLRADIS) nperm = len(PERMEABILITYHS) wi_simulated = [] @@ -61,7 +66,13 @@ def compute_peaceman(k_h: float, r_e: float, r_w: float) -> float: for k, WELLRADI in enumerate(WELLRADIS): r_w.append(WELLRADI) for i, PERMEABILITYH in enumerate(PERMEABILITYHS): - var = {"flow": FLOW, "perm": PERMEABILITYH, "radius": WELLRADI, "rate": RATE, "pwd": os.getcwd()} + var = { + "flow": FLOW, + "perm": PERMEABILITYH, + "radius": WELLRADI, + "rate": RATE, + "pwd": os.getcwd(), + } filledtemplate = mytemplate.render(**var) with open( f"h2o_{i+k*nperm}.txt", @@ -89,17 +100,21 @@ def compute_peaceman(k_h: float, r_e: float, r_w: float) -> float: wi_analytical.append([]) for r in r_e[-1]: wi_analytical[-1].append( - compute_peaceman(PERMEABILITYH, r,r_wi[NPRUNS*i+j]) * BOFAC / VISCOSCITY + compute_peaceman(PERMEABILITYH, r, r_wi[NPRUNS * i + j]) + * BOFAC + / VISCOSCITY ) rst = EclFile(f"./h2o_{NPRUNS*i+j}/output/H2O_{NPRUNS*i+j}.UNRST") pressure = np.array(rst.iget_kw("PRESSURE")[-1]) pw = pressure[0] - cell_pressures = pressure[len(pressure)-len(r_e[-1]):] - wi_simulated.append(RATE / ((pw - cell_pressures) * 1e5)) # 1e5 to connvert from bar to Pascals + cell_pressures = pressure[len(pressure) - len(r_e[-1]) :] + wi_simulated.append( + RATE / ((pw - cell_pressures) * 1e5) + ) # 1e5 to connvert from bar to Pascals axis.plot( r_e[-1], wi_simulated[-1], - color=matplotlib.colormaps["tab20"].colors[(NPRUNS*i+j)%20], + color=matplotlib.colormaps["tab20"].colors[(NPRUNS * i + j) % 20], linestyle="", marker="*", markersize=5, @@ -108,7 +123,7 @@ def compute_peaceman(k_h: float, r_e: float, r_w: float) -> float: axis.plot( r_e[-1], wi_analytical[-1], - color=matplotlib.colormaps["tab20"].colors[(NPRUNS*i+j)%20], + color=matplotlib.colormaps["tab20"].colors[(NPRUNS * i + j) % 20], linestyle="", marker=".", markersize=5, @@ -117,7 +132,13 @@ def compute_peaceman(k_h: float, r_e: float, r_w: float) -> float: os.system(f"rm -rf h2o_{NPRUNS*i+j} h2o_{NPRUNS*i+j}.txt") # Write the configuration files for the comparison in the 3D reservoir -var = {"flow": FLOW, "perm": PERMEABILITY, "radius": .1, "rate": RATE, "pwd": os.getcwd()} +var = { + "flow": FLOW, + "perm": PERMEABILITY, + "radius": 0.1, + "rate": RATE, + "pwd": os.getcwd(), +} for name in ["3d_flow_wellmodel", "3d_ml_wellmodel"]: mytemplate = Template(filename=f"h2o_{name}.mako") filledtemplate = mytemplate.render(**var) diff --git a/examples/cemracs2023/peaceman_well-model_nn/example_framework_peaceman_water/h2o_3d_flow_wellmodel.mako b/examples/cemracs2023/peaceman_well-model_nn/example_framework_peaceman_water/h2o_3d_flow_wellmodel.mako index 00bb6f4..166be55 100644 --- a/examples/cemracs2023/peaceman_well-model_nn/example_framework_peaceman_water/h2o_3d_flow_wellmodel.mako +++ b/examples/cemracs2023/peaceman_well-model_nn/example_framework_peaceman_water/h2o_3d_flow_wellmodel.mako @@ -1,5 +1,5 @@ """Set the full path to the flow executable and flags""" -${flow} --ml-wi-filename="" --linear-solver-reduction=1e-5 --relaxed-max-pv-fraction=0 --ecl-enable-drift-compensation=0 --newton-max-iterations=50 --newton-min-iterations=5 --tolerance-mb=1e-7 --tolerance-wells=1e-5 --relaxed-well-flow-tol=1e-5 --use-multisegment-well=false --enable-tuning=true --enable-opm-rst-file=true --linear-solver=cprw --enable-well-operability-check=false --min-time-step-before-shutting-problematic-wells-in-days=1e-99 +${flow} --ml-wi-filename="" --linear-solver-reduction=1e-5 --relaxed-max-pv-fraction=0 --enable-drift-compensation=0 --newton-max-iterations=50 --newton-min-iterations=5 --tolerance-mb=1e-7 --tolerance-wells=1e-5 --relaxed-well-flow-tol=1e-5 --use-multisegment-well=false --enable-tuning=true --enable-opm-rst-file=true --linear-solver=cprw --enable-well-operability-check=false --min-time-step-before-shutting-problematic-wells-in-days=1e-99 """Set the model parameters""" co2store base #Model (co2store/h2store) diff --git a/examples/cemracs2023/peaceman_well-model_nn/example_framework_peaceman_water/h2o_3d_ml_wellmodel.mako b/examples/cemracs2023/peaceman_well-model_nn/example_framework_peaceman_water/h2o_3d_ml_wellmodel.mako index c51a7ce..562827b 100644 --- a/examples/cemracs2023/peaceman_well-model_nn/example_framework_peaceman_water/h2o_3d_ml_wellmodel.mako +++ b/examples/cemracs2023/peaceman_well-model_nn/example_framework_peaceman_water/h2o_3d_ml_wellmodel.mako @@ -1,5 +1,5 @@ """Set the full path to the flow executable and flags""" -${flow} --ml-wi-filename="${pwd}/water_re.modelPeaceman" --linear-solver-reduction=1e-5 --relaxed-max-pv-fraction=0 --ecl-enable-drift-compensation=0 --newton-max-iterations=50 --newton-min-iterations=5 --tolerance-mb=1e-7 --tolerance-wells=1e-5 --relaxed-well-flow-tol=1e-5 --use-multisegment-well=false --enable-tuning=true --enable-opm-rst-file=true --linear-solver=cprw --enable-well-operability-check=false --min-time-step-before-shutting-problematic-wells-in-days=1e-99 +${flow} --ml-wi-filename="${pwd}/water_re.modelPeaceman" --linear-solver-reduction=1e-5 --relaxed-max-pv-fraction=0 --enable-drift-compensation=0 --newton-max-iterations=50 --newton-min-iterations=5 --tolerance-mb=1e-7 --tolerance-wells=1e-5 --relaxed-well-flow-tol=1e-5 --use-multisegment-well=false --enable-tuning=true --enable-opm-rst-file=true --linear-solver=cprw --enable-well-operability-check=false --min-time-step-before-shutting-problematic-wells-in-days=1e-99 """Set the model parameters""" co2store base #Model (co2store/h2store) diff --git a/examples/cemracs2023/peaceman_well-model_nn/example_framework_peaceman_water/h2o_nearwell.mako b/examples/cemracs2023/peaceman_well-model_nn/example_framework_peaceman_water/h2o_nearwell.mako index 6ba7d3c..e00d045 100644 --- a/examples/cemracs2023/peaceman_well-model_nn/example_framework_peaceman_water/h2o_nearwell.mako +++ b/examples/cemracs2023/peaceman_well-model_nn/example_framework_peaceman_water/h2o_nearwell.mako @@ -1,5 +1,5 @@ """Set the full path to the flow executable and flags""" -${flow} --linear-solver-reduction=1e-5 --relaxed-max-pv-fraction=0 --ecl-enable-drift-compensation=0 --newton-max-iterations=50 --newton-min-iterations=5 --tolerance-mb=1e-7 --tolerance-wells=1e-5 --relaxed-well-flow-tol=1e-5 --use-multisegment-well=false --enable-tuning=true --enable-opm-rst-file=true --linear-solver=cprw --enable-well-operability-check=false --min-time-step-before-shutting-problematic-wells-in-days=1e-99 +${flow} --linear-solver-reduction=1e-5 --relaxed-max-pv-fraction=0 --enable-drift-compensation=0 --newton-max-iterations=50 --newton-min-iterations=5 --tolerance-mb=1e-7 --tolerance-wells=1e-5 --relaxed-well-flow-tol=1e-5 --use-multisegment-well=false --enable-tuning=true --enable-opm-rst-file=true --linear-solver=cprw --enable-well-operability-check=false --min-time-step-before-shutting-problematic-wells-in-days=1e-99 """Set the model parameters""" co2store base #Model (co2store/h2store) diff --git a/examples/h2.txt b/examples/h2.txt index 99084c9..5df95b7 100644 --- a/examples/h2.txt +++ b/examples/h2.txt @@ -1,5 +1,5 @@ """Set the full path to the flow executable and flags""" -flow --linear-solver-reduction=1e-5 --relaxed-max-pv-fraction=0 --ecl-enable-drift-compensation=0 --newton-max-iterations=50 --newton-min-iterations=5 --tolerance-mb=1e-7 --tolerance-wells=1e-5 --relaxed-well-flow-tol=1e-5 --use-multisegment-well=false --enable-tuning=true --enable-opm-rst-file=true --linear-solver=cprw --enable-well-operability-check=false --min-time-step-before-shutting-problematic-wells-in-days=1e-99 +flow --linear-solver-reduction=1e-5 --relaxed-max-pv-fraction=0 --enable-drift-compensation=0 --newton-max-iterations=50 --newton-min-iterations=5 --tolerance-mb=1e-7 --tolerance-wells=1e-5 --relaxed-well-flow-tol=1e-5 --use-multisegment-well=false --enable-tuning=true --enable-opm-rst-file=true --linear-solver=cprw --enable-well-operability-check=false --min-time-step-before-shutting-problematic-wells-in-days=1e-99 """Set the model parameters""" h2store base #Model (co2store/h2store) diff --git a/requirements.txt b/requirements.txt index 4db8f13..ce7d854 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,10 +1,9 @@ -ecl resdata opm mako matplotlib pandas scipy -tensorflow +tensorflow==2.13.0 keras_tuner scikit-learn \ No newline at end of file diff --git a/src/pyopmnearwell/core/pyopmnearwell.py b/src/pyopmnearwell/core/pyopmnearwell.py index 027e199..41c156b 100644 --- a/src/pyopmnearwell/core/pyopmnearwell.py +++ b/src/pyopmnearwell/core/pyopmnearwell.py @@ -5,6 +5,7 @@ import argparse import os import pathlib +from typing import Any from pyopmnearwell.utils.inputvalues import process_input from pyopmnearwell.utils.runs import plotting, simulations @@ -72,7 +73,7 @@ def pyopmnearwell(): help="Scale for the x axis in the figures: 'normal' or 'log' ('normal' by default)", ) cmdargs = vars(parser.parse_known_args()[0]) - dic = { + dic: dict[str, Any] = { "pat": os.path.split(os.path.dirname(__file__))[0] } # Path to the pyopmnearwell folder dic["exe"] = os.getcwd() # Path to the folder of the input.txt file diff --git a/src/pyopmnearwell/ml/analysis.py b/src/pyopmnearwell/ml/analysis.py new file mode 100644 index 0000000..fbe3287 --- /dev/null +++ b/src/pyopmnearwell/ml/analysis.py @@ -0,0 +1,226 @@ +# pylint: disable=fixme +"""Analyze the sensitivity of a neural network to its inputs. + +Inspiration taken from +https://f0nzie.github.io/machine_learning_compilation/sensitivity-analysis-for-a-neural-network.html + +""" + +from __future__ import annotations + +import math +import pathlib +from typing import Literal, Optional + +import numpy as np +from matplotlib import pyplot as plt +from tensorflow import keras + +from pyopmnearwell.utils import plotting + + +def sensitivity_analysis( + model: keras.Model, + resolution_1: int = 20, + resolution_2: int = 20, + mode: ( + Literal["homogeneous", "random_uniform", "random_normal"] | float + ) = "homogeneous", +) -> tuple[np.ndarray, np.ndarray]: + """Perform a sensitivity analysis of a neural network. + + For each input variable, vary from a min to a max value and measure how the output + of the network changes. The other input variables are kept constant meanwhile. + + Note: It is assumed that the network has a single output. + + Args: + model (keras.Model): Neural network. + resolution_1 (int): Number of different values that the fixed inputs take. + Equals the number of plotted lines in ``plot_analysis``. + resolution_2 (int): Number of different values the variable input takes. Equals + the number of datapoints along the x-axis ``plot_analysis``. + mode (Literal["homogeneous", "random_uniform", "random_normal"] | float): Sets + the sampling mode for the fixed inputs. + - "homogeneous": + - "random_uniform": + - "random_normal" + - float: All fixed inputs are set to this value. + Default is "homogeneous". + + Returns: + tuple[np.ndarray, np.ndarray]: Output and inputs from the sensitivity analysis. + First axis is the input variable that is varying, second axis is the variation, + third axis is the variation for the fixed variables. The input array contains an + additional axis in case of input dimension > 1. + + """ + # Get the number of input variables. + num_inputs = model.input_shape[1] + + # Get the minimum and maximum values for each input variable + # min_values = np.min(model.input, axis=0) + # max_values = np.max(model.input, axis=0) + + # We assume the model is normalized such that each input variable has min value -1 + # and max value 1. + # TODO: Add options for when this is not the case. + min_values: np.ndarray = np.full((num_inputs,), -1) + max_values: np.ndarray = np.full((num_inputs,), 1) + + # Initialize the inputs and outputs array. + inputs: np.ndarray = np.zeros((num_inputs, resolution_1, resolution_2, num_inputs)) + outputs: np.ndarray = np.zeros((num_inputs, resolution_1, resolution_2)) + + # Initialize random generators. + if isinstance(mode, str) and mode.startswith("random"): + rng: np.random.Generator = np.random.default_rng() + + # Iterate over all input variables. For each variable and constant input vector, we + # create a batch of input arrays and evaluate outside the innermost for loop, + # instead of evaluating on single-point batches. + for i in range(num_inputs): + # Create input arrays for the fixed variables. + if mode == "homogeneous": + # fixed_inputs: np.ndarray = np.repeat( + # np.linspace(min_values, max_values, resolution_1)[..., None], + # num_inputs, + # axis=-1, + # ) + fixed_inputs: np.ndarray = np.linspace(min_values, max_values, resolution_1) + elif mode == "random_uniform": + # Get uniform distribution on [0,1) and scale to (min_value, max_value). + # pylint: disable-next=possibly-used-before-assignment + fixed_inputs = rng.uniform( + min_values, max_values, size=(resolution_1, num_inputs) + ) + elif mode == "random_normal": + # Set standard deviation s.t. ~95% of the inputs are inside the [min_values, + # max_values] interval. + fixed_inputs = rng.normal( + max_values - min_values, + (max_values - min_values) / 4, + size=(resolution_1, num_inputs), + ) + elif isinstance(mode, float): + fixed_inputs = np.full((resolution_1, num_inputs), mode) + else: + raise ValueError("'mode' has invalid value") + for j, fixed_input in enumerate(fixed_inputs): + assert fixed_input.shape == (num_inputs,) + fixed_input = np.tile(fixed_input, (resolution_2, 1)) + + # Replace i-th input with varying value. + fixed_input[..., i] = np.linspace( + min_values[i], max_values[i], resolution_2 + ) + + predictions = model.predict(fixed_input) + + assert fixed_input.shape == (resolution_2, num_inputs) + assert predictions.shape == (resolution_2, 1) + + outputs[i, j] = predictions.flatten() + inputs[i, j] = fixed_input + + return outputs, inputs + + +def plot_analysis( + outputs: np.ndarray, + inputs: np.ndarray, + savepath: str | pathlib.Path, + feature_names: Optional[list[str]] = None, + main_plot: Optional[tuple[np.ndarray, np.ndarray]] = None, + **kwargs, +) -> None: + r""" + Plot the analysis of the model outputs against inputs. + + Args: + outputs (np.ndarray): The model outputs. + inputs (np.ndarray): The model inputs. Shape is ``(num_inputs, resolution_1, + resolution_2)``. + savepath (str | pathlib.Path): The path to save the plot. + feature_names (Optional[list[str]]): The names of the input features. If None, + default names (:math:`x_1,x_2,\dots`) will be used. Default is None. + main_plot (Optional[tuple[np.ndarray, np.ndarray]]): Add output and input for a + main plot that is specifically highlighted. Default is None. + **kwargs: + - legend (bool): Whether to plot a legend. Default + is True. + + Returns: + None + + """ + if feature_names is None: + feature_names = [rf"$x_{{{i}}}$" for i in range(inputs.shape[-1])] + elif len(feature_names) != inputs.shape[-1]: + raise ValueError + + # Max ``max_columns`` (default=3) columns and increase figure size. + num_rows: int = math.ceil(outputs.shape[0] / kwargs.get("max_columns", 3)) + num_columns: int = min(outputs.shape[0], kwargs.get("max_columns", 3)) + + fig, axes = plt.subplots( + num_rows, + num_columns, + sharex=True, + sharey=True, + # NOTE: Setting the figure size here does not work. + # figheight=max(num_rows * 3, 5), + # figwidth=max(num_columns * 3, 5) + # size_inches=(max(num_columns * 3, 5), max(num_rows * 3, 5)), + ) + fig.set_size_inches(w=max(num_columns * 3, 5), h=max(num_rows * 3, 5)) + + # mypy complains that ``axes`` might not have an attribute ``.flatten()``, but this + # shouldn't be a problem. + for i, ax in enumerate( + axes.flatten() # type: ignore # pylint: disable=invalid-name + ): + # If not all rows are full, there are more axes than features. Skip the last few + # axes. + if i >= len(feature_names): + break + for j, color in enumerate( + # Ignore mypy complaining about ``cm`` not being a module. + # pylint: disable-next=no-member + plt.cm.Blues(np.linspace(0.3, 0.7, outputs.shape[1])), # type: ignore + ): + ax.plot( + inputs[i, j, :, i], + outputs[i, j], + color=color, + linestyle=":", + label=f"{inputs[i, j, 0, i - 1]}", + ) + if main_plot is not None: + ax.plot( + main_plot[1][i, 0, :, i], + main_plot[0][i, 0], + color="blue", + label="0", + ) + ax.set_title(feature_names[i]) + + # Create common axes for all subplots. Add a big axis, hide frame. + fig.add_subplot(111, frameon=False) + # Set common x and y labels. + plt.tick_params( + labelcolor="none", + which="both", + top=False, + bottom=False, + left=False, + right=False, + ) + plt.xlabel("Explanatory") + plt.ylabel("Response") + + if kwargs.get("legend", True): + # pylint: disable-next=undefined-loop-variable + ax.legend(title="Splits", loc="center left", bbox_to_anchor=(1, 0.5)) + + plotting.save_fig_and_data(fig, savepath) diff --git a/src/pyopmnearwell/ml/ensemble.py b/src/pyopmnearwell/ml/ensemble.py index 67f4196..20412bc 100644 --- a/src/pyopmnearwell/ml/ensemble.py +++ b/src/pyopmnearwell/ml/ensemble.py @@ -17,12 +17,12 @@ import numpy as np import tensorflow as tf -from ecl.eclfile.ecl_file import open_ecl_file -from ecl.summary.ecl_sum import EclSum from mako import exceptions from mako.template import Template +from resdata import FileMode +from resdata.resfile import ResdataFile +from resdata.summary import Summary -from pyopmnearwell.utils import units from pyopmnearwell.utils.formulas import area_squaredcircle, pyopmnearwell_correction from pyopmnearwell.utils.inputvalues import readthefirstpart, readthesecondpart from pyopmnearwell.utils.writefile import reservoir_files @@ -34,7 +34,7 @@ FLAGS = ( " --linear-solver-reduction=1e-5 --relaxed-max-pv-fraction=0" - + " --ecl-enable-drift-compensation=0 --newton-max-iterations=50" + + " --enable-drift-compensation=0 --newton-max-iterations=50" + " --newton-min-iterations=5 --tolerance-mb=1e-7 --tolerance-wells=1e-1" + " --relaxed-well-flow-tol=1e-3 --use-multisegment-well=false --enable-tuning=true" + " --enable-opm-rst-file=true --linear-solver=cprw" @@ -44,12 +44,20 @@ def create_ensemble( - runspecs: dict[str, Any], efficient_sampling: Optional[list[str]] = None + runspecs: dict[str, Any], + efficient_sampling: Optional[list[str]] = None, + seed: Optional[int] = None, ) -> list[dict[str, Any]]: """Create an ensemble. - Note: It is assumed that the user provides the variables in the correct units for - pyopmnearwell. + Note: + - It is assumed that the user provides the variables in the correct units for + pyopmnearwell. + - If the variable name starts with ``"PERM"`` or ``"LOG"``, the distribution for + random sampling is log uniform. + - If the variable name starts with ``"INT"``, the distribution for random + sampling is uniform on integers. + - Else, the distribution for random sampling is uniformly distributed. Args: runspecs (dict[str, Any]): Dictionary with at least the following keys: @@ -70,6 +78,8 @@ def create_ensemble( vertical permeabilities. Only 10 layers with 10 samples generate a grid of 10^10 values. By sampling directly instead of generating the grid first, it is possible to deal with the complexity. + seed: (Optional[int]): Seed for the ``np.random.Generator``. Is passed to + ``memory_efficient_sample`` as well. Default is ``None``. Note: The ensemble is generated as the cartesian product of all variable ranges. The total number of ensemble members is thus the product of all individual @@ -90,17 +100,23 @@ def create_ensemble( variables: dict[str, np.ndarray] = OrderedDict() + rng: np.random.Generator = np.random.default_rng(seed=seed) + # Generate random value ranges for all variables. logger.info("Generating value ranges for all variables") for variable, (min_val, max_val, npoints) in runspecs["variables"].items(): - if variable.startswith("PERM"): - # Generate a log uniform distribution for permeabilities. + if variable.startswith("PERM") or variable.startswith("LOG"): + # Generate a log uniform distribution for permeabilities and other values + # that should be log uniform sampled. variables[variable] = np.exp( - np.random.uniform(math.log(min_val), math.log(max_val), npoints) + rng.uniform(math.log(min_val), math.log(max_val), npoints) ) + elif variable.startswith("INT"): + # Generate a uniform distribution on integers. + variables[variable] = rng.integers(min_val, max_val, npoints) else: # Generate a uniform distribution for all other variables. - variables[variable] = np.random.uniform(min_val, max_val, npoints) + variables[variable] = rng.uniform(min_val, max_val, npoints) constants: dict[str, float] = runspecs["constants"] # Differentiate between variables whose data ranges are sampled memory efficiently @@ -138,6 +154,7 @@ def create_ensemble( sampled_variables: np.ndarray | list = memory_efficient_sample( np.array(list(variables_to_sample.values())), num_members=runspecs["npoints"], + seed=seed, ) else: sampled_variables = [] @@ -156,15 +173,15 @@ def create_ensemble( ensemble.append(member) # Sample a subset of the ensemble if the ensemble size is larger than wanted. - ensemble_size: int = np.prod( - [npoints for _, __, npoints in runspecs["variables"].values()] + ensemble_size: int = int( + np.prod([npoints for _, __, npoints in runspecs["variables"].values()]) ) if runspecs["npoints"] < ensemble_size: logger.info( "Sample size is larger than ensemble size. Randomly selecting a subset" ) - idx = np.random.randint( + idx = rng.integers( len(ensemble), size=runspecs["npoints"], ) @@ -180,7 +197,9 @@ def create_ensemble( return ensemble -def memory_efficient_sample(variables: np.ndarray, num_members: int) -> np.ndarray: +def memory_efficient_sample( + variables: np.ndarray, num_members: int, seed: Optional[int] = None +) -> np.ndarray: """Sample all variables individually. Note: Requires that all variables arrays have the same length. @@ -188,40 +207,43 @@ def memory_efficient_sample(variables: np.ndarray, num_members: int) -> np.ndarr Args: variables (np.ndarray), (``shape=(num_variables, len_variables)``): _description_ num_members (int): _description_ + seed: (Optional[int]): Seed for the ``np.random.Generator``. Default is + ``None``. Returns: np.ndarray (``shape=()``): """ - indices: np.ndarray = np.random.randint( + rng: np.random.Generator = np.random.default_rng(seed=seed) + indices: np.ndarray = rng.integers( 0, variables.shape[-1], size=(variables.shape[0], num_members) ) return variables[np.arange(variables.shape[0])[..., None], indices] def setup_ensemble( - ensemble_path: pathlib.Path, + ensemble_path: str | pathlib.Path, ensemble: list[dict[str, Any]], makofile: str | pathlib.Path, - recalc_grid: bool = False, - recalc_tables: bool = False, - recalc_sections: bool = False, + **kwargs, ) -> None: """Create a deck file for each ensemble member. Args: - ensemble_path (pathlib.Path): The path to the ensemble directory. + ensemble_path (str | pathlib.Path): The path to the ensemble directory. ensemble (list[dict[str, Any]]): A list of dictionaries containing the parameters for each ensemble member. Usually generated by ``create_ensemble``. makofile (str | pathlib.Path): The path to the Mako template file for the pyopmnearwell deck for ensemble members. - recalc_grid (bool, optional): Whether to recalculate ``GRID.INC`` for each - ensemble member. Defaults to False. - recalc_tables (bool, optional): Whether to recalculate ``TABLES.INC`` for each - ensemble member. Defaults to False. - recalc_sections (bool, optional): Whether to recalculate ``GEOLOGY.INC`` and - ``REGIONS.INC`` for each ensemble member. Defaults to False. + **kwargs: kwargs are passed to ``reservoir_files``. Possible kwargs are: + - recalc_grid (bool, optional): Whether to recalculate ``GRID.INC`` for each + ensemble member. Defaults to False. + - recalc_tables (bool, optional): Whether to recalculate ``TABLES.INC`` for + each ensemble member. Defaults to False. + - recalc_sections (bool, optional): Whether to recalculate ``GEOLOGY.INC`` + and ``REGIONS.INC`` for each ensemble member. Defaults to False. + Raises: Exception: If there is an error rendering the Mako template. @@ -232,6 +254,12 @@ def setup_ensemble( # Ensure ``ensemble_path`` is a ``Path`` object. ensemble_path = pathlib.Path(ensemble_path) + # Update kwargs with the (future) relative path to the first first ensemble member + # from any other ensemble member. + kwargs.update( + {"inc_folder": pathlib.Path("..") / ".." / "runfiles_0" / "preprocessing"} + ) + logger.info(f"Filling templates for {len(ensemble)} members") mytemplate: Template = Template(filename=str(makofile)) for i, member in enumerate(ensemble): @@ -246,7 +274,8 @@ def setup_ensemble( (ensemble_path / f"runfiles_{i}" / "jobs").mkdir(exist_ok=True) lol = [] - for row in csv.reader(filledtemplate.split("\n"), delimiter="#"): + # Ignore Pylance complaining that the argument might be ``list[bool]`` + for row in csv.reader(filledtemplate.split("\n"), delimiter="#"): # type: ignore lol.append(row) dic, index = readthefirstpart( lol, @@ -264,10 +293,11 @@ def setup_ensemble( else: reservoir_files( dic, - recalc_grid=recalc_grid, - recalc_tables=recalc_tables, - recalc_sections=recalc_sections, - inc_folder=pathlib.Path("..") / ".." / "runfiles_0" / "preprocessing", + **kwargs, + # recalc_grid=recalc_grid, + # recalc_tables=recalc_tables, + # recalc_sections=recalc_sections, + # inc_folder=pathlib.Path("..") / ".." / "runfiles_0" / "preprocessing", ) # pyopmnearwell creates these unneeded folders, so we remove them. try: @@ -278,26 +308,63 @@ def setup_ensemble( logger.info(f"Filled templates for {len(ensemble)} members") +def get_flags( + makofile: str | pathlib.Path, +) -> str: + """Extract OPM Flow run flags from a makofile. + + + Args: + makofile (str | pathlib.Path): Path to the makofile. + + Returns: + str: All flags that are passed to OPM Flow. + + """ + # Ensure ``makofile`` is a ``Path`` object. + makofile = pathlib.Path(makofile) + with makofile.open("r") as f: + # Second line has command line arguments. + next(iter(f)) + line: str = next(iter(f)) + # Strip first argument, which is just the ``flow`` command. + command_line_args: list[str] = line.split(" ")[1:] + # Strip the newline at the end. + return (" ").join(command_line_args).rstrip() + + def run_ensemble( flow_path: str | pathlib.Path, - ensemble_path: pathlib.Path, + ensemble_path: str | pathlib.Path, runspecs: dict[str, Any], ecl_keywords: list[str], init_keywords: list[str], summary_keywords: list[str], num_report_steps: Optional[int] = None, + keep_result_files: bool = False, + **kwargs, ) -> dict[str, Any]: - """Run OPM flow for each ensemble member and store data. + """Run OPM Flow for each ensemble member and store data. + + Note: The initial time step (i.e., t=0) is always disregarded. Args: - flow_path (_type_): _description_ - ensemble_path (pathlib.Path): _description_ + flow_path (str | pathlib.Path): _description_ + ensemble_path (str | pathlib.Path): _description_ runspecs (dict[str, Any]): _description_ ecl_keywords (list[str]): _description_ init_keywords (list[str]): _description_ summary_keywords (list[str]): _description_ - num_report_steps (Optional[int], optional): Disregard an ensemble simulation, if + num_report_steps (Optional[int], optional): Disregard an ensemble simulation if it did not run to the last report step. Defaults to None. + keep_result_files (bool): Keep result files of all ensemble members, not + only the first one. Defaults to False. + **kwargs: Possible parameters are: + - step_size_time (int): Save data only for every ``step_size_time`` report + step. Default is 1. + - step_size_cell (int): Save data only for every ``step_size_cell`` grid + cell. Default is 1. + - flags (str): Flags to run OPM Flow with. Returns: dict[str, Any]: _description_ @@ -311,84 +378,105 @@ def run_ensemble( } num_disregarded_runs: int = 0 + # Get **kwargs that determine how many report steps and cells shall be skipped when + # extracting the data. + step_size_time: int = kwargs.get("step_size_time", 1) + step_size_cell: int = kwargs.get("step_size_cell", 1) + for i in range(round(runspecs["npoints"] / runspecs["npruns"])): command = " ".join( [ f"{flow_path}" + f" {ensemble_path / f'runfiles_{j}' / 'preprocessing' / f'RUN_{j}.DATA'}" + f" --output-dir={ensemble_path / f'results_{j}'}" - + f" {FLAGS} & " + + f" {kwargs.get('flags', '')} & " for j in range(runspecs["npruns"] * i, runspecs["npruns"] * (i + 1)) ] ) + # TODO: Possibly better to use subprocess? os.system(command + "wait") for j in range(runspecs["npruns"] * i, runspecs["npruns"] * (i + 1)): simulation_finished: bool = True - with open_ecl_file( - str(ensemble_path / f"results_{j}" / f"RUN_{j}.UNRST") - ) as ecl_file: - # Skip result, if the simulation did not run to the last time step. + resdata_file: ResdataFile = ResdataFile( + str(ensemble_path / f"results_{j}" / f"RUN_{j}.UNRST"), + flags=FileMode.CLOSE_STREAM, + ) + # Skip result, if the simulation did not run to the last time step. + if ( + num_report_steps is not None + and resdata_file.num_report_steps() < num_report_steps + ): + simulation_finished = False + + # Check again if the simulation data is available for all time steps. + # It seems that sometimes the keyword array has zero report steps, even + # though `resdata_file.num_report_steps()` is nonzero. + member_data: dict[str, np.ndarray] = {} + for keyword in ecl_keywords: + # Append the data corresponding to the keyword for all chosen report + # steps and cells. Disregard the zeroth time step. + member_data[keyword] = np.array(resdata_file.iget_kw(keyword))[ + 1::step_size_time, ::step_size_cell + ] if ( num_report_steps is not None - and ecl_file.num_report_steps() < num_report_steps + and member_data[keyword].shape[0] + < num_report_steps // step_size_time ): simulation_finished = False - # Check again if the simulation data is available for all time steps. - # It seems that sometimes the keyword array has zero report steps, even - # though `ecl_file.num_report_steps()` is nonzero. - member_data: dict[str, np.ndarray] = {} - for keyword in ecl_keywords: - # Append the data corresponding to the keyword for all report steps - # and cells. Disregard the zeroth time step. - member_data[keyword] = np.array(ecl_file.iget_kw(keyword))[1:, ...] - if ( - num_report_steps is not None - and member_data[keyword].shape[0] < num_report_steps - ): - simulation_finished = False - - # Disregard the result if an `inf` value is returned. - elif np.any(np.isinf(member_data[keyword])): - simulation_finished = False + # Disregard the result if an `inf` value is returned. + elif np.any(np.isinf(member_data[keyword])): + simulation_finished = False # Only append data if the simulation finished. if simulation_finished: for keyword in ecl_keywords: data[keyword].append(member_data[keyword]) - with open_ecl_file( - ensemble_path / f"results_{j}" / f"RUN_{j}.INIT" - ) as init_file: + # Get additional data from init and summary file. + if len(init_keywords) > 0: + init_file: ResdataFile = ResdataFile( + str(ensemble_path / f"results_{j}" / f"RUN_{j}.INIT"), + flags=FileMode.CLOSE_STREAM, + ) for keyword in init_keywords: - # Append the data corresponding to the keyword for all cells. - data[keyword].append(np.array(init_file.iget_kw(keyword))) - - summary_file: EclSum = EclSum( - ensemble_path / f"results_{j}" / f"RUN_{j}.SMSPEC" - ) - for keyword in summary_keywords: - # Append the data corresponding to the keyword for all report steps - # (not for all time steps). The ``*.SMSPEC`` file does not include - # the zeroth report step. Add a dimension to make it - # broadcastable to data from the ``*.UNRST`` and ``*.INIT`` files. - data[keyword].append( - np.array( - summary_file.get_values(keyword, report_only=True)[ - ..., None - ] + # Append the data corresponding to the keyword for all chosen + # cells. + # NOTE: The array has shape ``[1, num_cells]``, hence no axis + # needs to be added. + data[keyword].append( + np.array(init_file.iget_kw(keyword))[::step_size_cell] ) + + if len(summary_keywords): + # TODO: Check if lazyload option for ``Summary`` is faster or + # slower. + summary_file: Summary = Summary( + str(ensemble_path / f"results_{j}" / f"RUN_{j}.SMSPEC") ) - # NOTE: There does not seem to be a way to close an ``EclSum`` object. - # Also, there is no context manager. + for keyword in summary_keywords: + # Append the data corresponding to the keyword for all chosen report + # steps (not for all time steps). The ``*.SMSPEC`` file does not + # include the zeroth report step. Add a dimension to make the array + # broadcastable to data from the ``*.UNRST`` and ``*.INIT`` files. + data[keyword].append( + np.array( + summary_file.get_values(keyword, report_only=True) + )[::step_size_time, None] + ) + # NOTE: There does not seem to be a way to specify that a + # ``Summary`` object shall be closed after use. Also, there is no + # context manager for ``Summary`` and ``ResdataFile`` objects. else: num_disregarded_runs += 1 logger.info(f"Disregarded ensemble run {j}") + # Remove the run files and result folder (except for the first one that # remains to check if everything went right). - if j > 0: + if not keep_result_files and j > 0: shutil.rmtree(ensemble_path / f"results_{j}") shutil.rmtree(ensemble_path / f"runfiles_{j}") logger.info(f"Disregarded {num_disregarded_runs} of {runspecs['npoints']} runs") @@ -402,7 +490,7 @@ def calculate_radii( # num_dims: int = 1, return_outer_inner: bool = False, triangle_grid: bool = False, - theta: float = math.pi / 3, + angle: float = math.pi / 3, ) -> np.ndarray | tuple[np.ndarray, np.ndarray, np.ndarray]: """ Calculates the radii of the cells in a grid grom a given . @@ -416,8 +504,8 @@ def calculate_radii( triangle_grid (bool, optional): Whether the grid is a triangle grid. If True, transform altitudes of the triangle grid to radii of a raidal grid with equal solution. Defaults to False. - theta (float, optional): Angle between the horizontal axis and the edge of the - triangle grid. Defaults to ``math.pi/3``. + angle (float, optional): Angle between both sides of the triangle grid. Defaults + to ``math.pi/3``. Returns: @@ -447,7 +535,7 @@ def calculate_radii( ) ) if triangle_grid: - radii *= pyopmnearwell_correction(theta) + radii *= pyopmnearwell_correction(angle) inner_radii: np.ndarray = radii[:-1] outer_radii: np.ndarray = radii[1:] radii_t: np.ndarray = (inner_radii + outer_radii) / 2 # unit: [m] @@ -457,85 +545,69 @@ def calculate_radii( def calculate_WI( - data: dict[str, Any], - runspecs: dict[str, Any], - num_zcells: int, + pressures: np.ndarray, + injection_rates: float | np.ndarray, ) -> tuple[np.ndarray, list[int]]: r"""Calculate the well index (WI) for a given dataset. - The well index (WI) is calculated using the following formula: + The well index (WI) is calculated using the following formula: .. math:: WI = \frac{q}{{p_w - p_{gb}}} - Note: In 3D this will more probably than not fail. + Note: + - The unit of ``WI_array`` will depend on the units of ``pressures`` and + ``injection_rates``. + - In 3D this might fail. The user is responsible to fix the array shapes before + passing to this function. Args: - data (dict[str, Any]): Data generated by ``run_ensemble``. - runspecs (dict[str, Any]): Must contain a key "INJECTION_RATE_PER_SECOND". Unit - [m^3/s]. + pressures (np.ndarray): First axis are the ensemble members. Last axis is + assumed to be the x-axis. Must contain the well cells (i.e., well pressures + values) at ``pressures[...,0]``. + injection_rates (float | np.ndarray): Injection rate. If an ``np.ndarray``, it + must have shape broadcastable to ``pressures.shape``. + Returns: - WI_array (numpy.ndarray): ``shape=(,num_cells - 1)`` - An array of well index values for each data point in the dataset. In the - correct unit for OPM. Unit [m^4*s/kg]. + WI_array (numpy.ndarray): ``shape=(...,num_x_cells - 1)`` + An array of well index values for each data point in the dataset. failed_indices (list[int]): Indices for the ensemble members where WI could not - be computed. E.g., if the simmulation went wrong and the pressure difference is - zero. + be computed. E.g., if the simmulation went wrong and the pressure difference + is zero. Raises: ValueError: If no data is found for the 'pressure' keyword in the dataset. + """ - # Transform pressure values from [bar] (unit in *.UNRST files) to [Pa] (unit to - # calculate the WI and internally used by OPM). - pressure_data = ( - np.array(data["PRESSURE"]) * units.BAR_TO_PASCAL - ) # ``shape=(runspecs["npoints"], num_time_data - 1, num_grid_cell_data)``; - # unit [Pa] - if len(pressure_data) == 0: - raise ValueError("No data found for the 'pressure' keyword.") # Calculate WI for each ensemble member. WI_values: list[np.ndarray] = [] failed_indices: list[int] = [] - for i, member_pressure_data in enumerate(pressure_data): - p_w = member_pressure_data[ - ..., 0 :: int(member_pressure_data.shape[-1] / num_zcells) - ] # Bottom hole pressure extracted at the well blocks. + if isinstance(injection_rates, float): + injection_rates = np.full((pressures.shape[0],), injection_rates) + + for i, (member_pressure, member_injection_rate) in enumerate( + zip(pressures, injection_rates) # type: ignore + ): + p_w = member_pressure[ + ..., 0 + ] # Bottom hole/well pressure extracted at the well cells. p_gb = np.delete( - member_pressure_data, - np.arange( - 0, - member_pressure_data.shape[-1], - int(member_pressure_data.shape[-1] / num_zcells), - ), - axis=-1, + member_pressure, 0, axis=-1 ) # Cell pressures of all but the well blocks. try: - if "INJECTION_RATE_PER_SECOND" in runspecs["constants"]: - # Injection rate is constant for all ensemble members. - WI = runspecs["constants"]["INJECTION_RATE_PER_SECOND"] / ( - np.tile(p_w, int(member_pressure_data.shape[-1] / num_zcells) - 1) - - p_gb - ) # ``shape=(runspecs["npoints"], num_time_data - 1, num_grid_cell_data)``; - # unit [m^4*s/kg] - elif "INJECTION_RATE_PER_SECOND" in data: - # Get i-th injection rate in the ensemble. - WI = data["INJECTION_RATE_PER_SECOND"][i] / ( - np.tile(p_w, int(member_pressure_data.shape[-1] / num_zcells) - 1) - - p_gb - ) # ``shape=(runspecs["npoints"], num_time_data - 1, num_grid_cell_data)``; - # unit [m^4*s/kg] - if np.any(np.isnan(WI)): - raise ValueError("WI is nan.") + WI: np.ndarray = member_injection_rate / (p_w - p_gb) + if np.any(np.logical_or(np.isnan(WI), np.isinf(WI))): + raise ValueError("WI is nan/inf.") WI_values.append(WI) except ValueError: failed_indices.append(i) return ( - np.array(WI_values), + np.array(WI_values), # ``shape=(...,num_x_cells - 1)`` failed_indices, - ) # ``shape=(,num_cells - 1)``; ; unit [m^4*s/kg] + ) def extract_features( @@ -543,7 +615,7 @@ def extract_features( keywords: list[str], keyword_scalings: Optional[dict[str, float]] = None, ) -> np.ndarray: - r"""Extract features into a numpy array. + r"""Extract features from a ``run_ensemble`` run into a numpy array. Note: The features are in the units used in ``*.UNRST`` and ``*.SMSPEC`` files. The user is responsible for transforming them to the units needed. This may depend on @@ -562,11 +634,11 @@ def extract_features( keyword_scalings (Optional[dict[str, float]]): Scalings for the features. Returns: - feature_array: (numpy.ndarray): ``shape=(,num_cells - 1)`` - An array of input features each data point in the dataset. + feature_array: (numpy.ndarray): ``shape=(ensemble_size, num_report_steps, num_cells, num_features)`` + An array of input features for each data point in the dataset. Raises: - ValueError: If no data is found for the 'pressure' keyword in the dataset. + ValueError: If no data is found for one of the keywords. """ if keyword_scalings is None: @@ -594,34 +666,58 @@ def extract_features( def integrate_fine_scale_value( radial_values: np.ndarray, radii: np.ndarray, - block_sidelength: float, + block_sidelengths: float | np.ndarray, axis: int = -1, ) -> np.ndarray: - """Integrate a fine scale value across all radial cells lying in a square grid + """Integrate a fine scale value across all radial cells covering a square grid block. + This function correctly takes only fractions of the integrated values for radial + cells that are only partially inside the square block. + Args: radial_values (np.ndarray): Cell values for the radial cells. radii (np.ndarray): Array of radii for inner and outer radius of the radial - cells. - block_sidelength (float): The sidelength of the square grid block. + cells. Has to be ordered from low to high. + block_sidelengths (float | np.ndarray): The sidelengths of the square grid + blocks. The length of this array determines the new length of the integrated + axis. + # TODO: Update the tests for this new functionality, i.e., that + block_sidelengths determins the return shape. axis (int): Axis to integrate along. Returns: float: The integrated value of fine-scale data. + Raise: + ValueError: If the radial cells do not cover the square grid block. + """ + if isinstance(block_sidelengths, float): + block_sidelengths = np.array([block_sidelengths]) + + # Return 0 for empty radial_values. + # TODO: Should this be changed to raise an error as well, if block_sidelength > 0? if radial_values.shape[0] > 0: assert radial_values.shape[axis] == radii.shape[0] - 1 else: return np.zeros_like(radial_values) + if np.any(np.sqrt(2 * block_sidelengths**2) > 2 * radii[-1]): + raise ValueError( + "The disks defined by the radii do not cover all square blocks." + ) + + integrated_values_lst: list[np.ndarray] = [] # Ignore mypy complaining. ``area_squaredcircle`` returns an np.ndarray in this # case. This can be removed, once the typing in ``formulas.py`` is more strict. - cell_areas: np.ndarray = area_squaredcircle( # type: ignore - radii[1::], block_sidelength - ) - area_squaredcircle(radii[:-1:], block_sidelength) - return np.sum(radial_values * cell_areas, axis=axis) + # For some reason Pylance thinks that ``block_sidelengths`` is ``int``. Ignore this. + for block_sidelength in block_sidelengths: # type: ignore + cell_areas: np.ndarray = area_squaredcircle( # type: ignore + radii[1::], block_sidelength + ) - area_squaredcircle(radii[:-1:], block_sidelength) + integrated_values_lst.append(np.sum(radial_values * cell_areas, axis=axis)) + return np.stack(integrated_values_lst, axis=axis) def store_dataset( diff --git a/src/pyopmnearwell/ml/integration.py b/src/pyopmnearwell/ml/integration.py index a81a529..621d791 100644 --- a/src/pyopmnearwell/ml/integration.py +++ b/src/pyopmnearwell/ml/integration.py @@ -2,7 +2,16 @@ # SPDX-FileCopyrightText: 2023 UiB # SPDX-License-Identifier: GPL-3.0 # pylint: skip-file -""""Run simulations with machine learned well models.""" +""""Run simulations with machine learned well models. + +Check the https://github.com/cssr-tools/ML_near_well/ repo for examples on how to use +the ``recompile_flow`` and ``run_integration`` functions. + +``recompile_flow`` can only used for well models at the moment, however the +functionality could easily be extended to replace other parts of the OPM simulator +before recompiling. + +""" from __future__ import annotations @@ -21,16 +30,12 @@ logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) -dirname: pathlib.Path = pathlib.Path(__file__).parent - def recompile_flow( scalingsfile: pathlib.Path, opm_path: pathlib.Path, - StandardWell_impl_template: Literal[ - "co2_3_inputs", "co2_5_inputs", "h2o_2_inputs", "co2_local_stencil" - ], - StandardWell_template: Literal["base", "local_stencil"] = "base", + StandardWell_impl_template: pathlib.Path, + StandardWell_template: pathlib.Path, stencil_size: int = 3, local_feature_names: Optional[list[str]] = None, ) -> None: @@ -44,11 +49,9 @@ def recompile_flow( scalingsfile (pathlib.Path): Path to the csv file containing the input and output scalings for the model. opm_path (pathlib.Path): Path to a OPM installation with ml functionality. - StandardWell_impl_template (Literal["co2_3_inputs", "co2_5_inputs", - "h2o_2_inputs", "co2_local_stencil"]): Template for + StandardWell_impl_template (pathlib.Path): Template for ``StandardWell_impl.hpp``. Decides the neural network architecture. - StandardWell_template (Literal["base", "local_stencil"], optional): Template for - ``StandardWell.hpp``. Defaults to "base". + StandardWell_template (pathlib.Path): Template for ``StandardWell.hpp``. stencil_size (int, optional): The size of the vertical stencil of the model. Defaults to 3. local_feature_names (Optional[list[str]], optional): List of local feature names @@ -71,7 +74,6 @@ def recompile_flow( opm_well_path: pathlib.Path = ( opm_path / "opm-simulators" / "opm" / "simulators" / "wells" ) - template_path: pathlib.Path = dirname / ".." / "templates" # Get the scaling and write it to the C++ mako that integrates nn into OPM. feature_min: list[float] = [] @@ -112,19 +114,13 @@ def recompile_flow( "stencil_size": stencil_size, "cell_feature_names": local_feature_names, } - filledtemplate: str = fill_template( - var, - filename=str( - template_path / "standardwell_impl" / f"{StandardWell_impl_template}.mako" - ), - ) + + # Fill templates and copy into OPM installation. + filledtemplate: str = fill_template(var, filename=str(StandardWell_impl_template)) with (opm_well_path / "StandardWell_impl.hpp").open("w", encoding="utf-8") as file: file.write(filledtemplate) - shutil.copyfile( - template_path / "standardwell" / f"{StandardWell_template}.hpp", - opm_well_path / "StandardWell.hpp", - ) + shutil.copyfile(StandardWell_template, opm_well_path / "StandardWell.hpp") # Recompile flow. os.chdir(opm_path / "build" / "opm-simulators") @@ -136,7 +132,7 @@ def run_integration( ) -> None: """Runs ``pyopmnearwell`` simulations for the specified runspecs. - Note: All "variables" need to + Note: All "variables" in runspecs need to have the same number of values. Args: runspecs (dict[str, Any]): Contains at least two keys "variables" and @@ -176,7 +172,8 @@ def run_integration( print(exceptions.text_error_template().render()) raise error with (savepath / f"run_{i}.txt").open("w", encoding="utf-8") as file: - file.write(filledtemplate) + # We assume that filledtemplate is a string and ignore Pylance complaining. + file.write(filledtemplate) # type: ignore # Use our pyopmnearwell friend to run the 3D simulations and compare the # results. diff --git a/src/pyopmnearwell/ml/nn.py b/src/pyopmnearwell/ml/nn.py index f768df4..c8a0b23 100644 --- a/src/pyopmnearwell/ml/nn.py +++ b/src/pyopmnearwell/ml/nn.py @@ -7,20 +7,21 @@ import math import pathlib from functools import partial -from typing import Any, Literal, Optional +from typing import Any, Literal, Optional, TypeAlias import keras_tuner import numpy as np import pandas as pd import tensorflow as tf +from pyopmnearwell.ml.kerasify import export_model from sklearn.preprocessing import MinMaxScaler from tensorflow import keras -from pyopmnearwell.ml.kerasify import export_model - logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) +ArrayLike: TypeAlias = tf.Tensor | np.ndarray + def get_FCNN( ninputs: int, @@ -32,8 +33,7 @@ def get_FCNN( kernel_initializer: Literal["glorot_normal", "glorot_uniform"] = "glorot_normal", normalization: bool = False, ) -> keras.Model: - """ - Returns a fully connected neural network with the specified architecture. + """Return a fully connected neural network with the specified architecture. Args: ninputs (int): Number of inputs to the model. @@ -72,19 +72,164 @@ def get_FCNN( return model +def get_RNN( + ninputs: int, + noutputs: int, + units: int = 20, + saved_model: Optional[str] = None, + activation: Literal["sigmoid", "relu", "tanh"] = "tanh", + kernel_initializer: Literal["glorot_normal", "glorot_uniform"] = "glorot_uniform", +) -> keras.Model: + """Return a recurrent neural network with the specified architecture. + + Args: + ninputs (int): Number of inputs to the model. + noutputs (int): Number of outputs from the model. + units (int, optional): Size of internal model state. Defaults to 20. + hidden_dim (int, optional): Number of neurons in each hidden layer. Defaults to + 10. + saved_model (str, optional): Path to a saved model to load weights from. + Defaults to None. + activation (Literal["sigmoid", "relu", "tanh"], optional): Activation function + to use in the hidden layers. Defaults to "sigmoid". + kernel_initializer (Literal["glorot_normal", "glorot_uniform"], optional): + Weight initialization method to use in the hidden layers. Defaults to + "glorot_normal". + + Returns: + keras.Model: A fully connected neural network. + + """ + model: keras.Model = keras.Sequential() + # RNN as model head. + model.add( + keras.layers.SimpleRNN( + units, + input_shape=(None, ninputs), + return_sequences=True, + activation=activation, + kernel_initializer=kernel_initializer, + ) + ) + # FCNN to as model tail. + model.add(keras.layers.TimeDistributed(keras.layers.Dense(10, activation="relu"))) + model.add(keras.layers.TimeDistributed(keras.layers.Dense(10, activation="relu"))) + model.add(keras.layers.TimeDistributed(keras.layers.Dense(10, activation="relu"))) + model.add(keras.layers.Dense(noutputs)) + if saved_model is not None: + model.load_weights(saved_model) + return model + + +def get_GRU( + ninputs: int, + noutputs: int, + units: int = 20, + saved_model: Optional[str] = None, + activation: Literal["sigmoid", "relu", "tanh"] = "tanh", + kernel_initializer: Literal["glorot_normal", "glorot_uniform"] = "glorot_uniform", +) -> keras.Model: + """Return a recurrent neural network with the specified architecture. + + Args: + ninputs (int): Number of inputs to the model. + noutputs (int): Number of outputs from the model. + units (int, optional): Size of internal model state. Defaults to 20. + hidden_dim (int, optional): Number of neurons in each hidden layer. Defaults to + 10. + saved_model (str, optional): Path to a saved model to load weights from. + Defaults to None. + activation (Literal["sigmoid", "relu", "tanh"], optional): Activation function + to use in the hidden layers. Defaults to "sigmoid". + kernel_initializer (Literal["glorot_normal", "glorot_uniform"], optional): + Weight initialization method to use in the hidden layers. Defaults to + "glorot_normal". + + Returns: + keras.Model: A fully connected neural network. + + """ + model: keras.Model = keras.Sequential() + # RNN as model head. + model.add( + keras.layers.GRU( + units, + input_shape=(None, ninputs), + return_sequences=True, + activation=activation, + kernel_initializer=kernel_initializer, + ) + ) + # FCNN to as model tail. + model.add(keras.layers.TimeDistributed(keras.layers.Dense(10, activation="relu"))) + model.add(keras.layers.TimeDistributed(keras.layers.Dense(10, activation="relu"))) + model.add(keras.layers.TimeDistributed(keras.layers.Dense(10, activation="relu"))) + model.add(keras.layers.Dense(noutputs)) + if saved_model is not None: + model.load_weights(saved_model) + return model + + +def get_LSTM( + ninputs: int, + noutputs: int, + units: int = 20, + saved_model: Optional[str] = None, + activation: Literal["sigmoid", "relu", "tanh"] = "tanh", + kernel_initializer: Literal["glorot_normal", "glorot_uniform"] = "glorot_uniform", +) -> keras.Model: + """Return a recurrent neural network with the specified architecture. + + Args: + ninputs (int): Number of inputs to the model. + noutputs (int): Number of outputs from the model. + units (int, optional): Size of internal model state. Defaults to 20. + hidden_dim (int, optional): Number of neurons in each hidden layer. Defaults to + 10. + saved_model (str, optional): Path to a saved model to load weights from. + Defaults to None. + activation (Literal["sigmoid", "relu", "tanh"], optional): Activation function + to use in the hidden layers. Defaults to "sigmoid". + kernel_initializer (Literal["glorot_normal", "glorot_uniform"], optional): + Weight initialization method to use in the hidden layers. Defaults to + "glorot_normal". + + Returns: + keras.Model: A fully connected neural network. + + """ + model: keras.Model = keras.Sequential() + # RNN as model head. + model.add( + keras.layers.LSTM( + units, + input_shape=(None, ninputs), + return_sequences=True, + activation=activation, + kernel_initializer=kernel_initializer, + ) + ) + # FCNN to as model tail. + model.add(keras.layers.TimeDistributed(keras.layers.Dense(10, activation="relu"))) + model.add(keras.layers.TimeDistributed(keras.layers.Dense(10, activation="relu"))) + model.add(keras.layers.TimeDistributed(keras.layers.Dense(10, activation="relu"))) + model.add(keras.layers.Dense(noutputs)) + if saved_model is not None: + model.load_weights(saved_model) + return model + + def scale_and_prepare_dataset( dsfile: str | pathlib.Path, feature_names: list[str], - savepath: pathlib.Path, + savepath: str | pathlib.Path, train_split: float = 0.9, val_split: Optional[float] = 0.1, test_split: Optional[float] = None, - conv_input: bool = False, - conv_output: bool = False, shuffle: Literal["first", "last", "false"] = "first", feature_range: tuple[float, float] = (-1, 1), target_range: tuple[float, float] = (-1, 1), - scale: bool = False, + scale: bool = True, **kwargs, ) -> ( tuple[tuple[np.ndarray, np.ndarray], tuple[np.ndarray, np.ndarray]] @@ -103,10 +248,6 @@ def scale_and_prepare_dataset( train_split (float, optional): Train split. Defaults to 0.9. val_split (float, optional): Val split. Defaults to 0.1. test_split (float, optional): Test split. Defaults to None. - conv_input (bool, optional): Whether the input is for a convolutional neural - network. Defaults to False. - conv_output (bool, optional): Whether the output is for a convolutional neural - network. Defaults to False. shuffle (Literal["first", "last", "false"], optional): Options for shuffling the dataset: - "first": The dataset gets shuffled before the split. @@ -117,10 +258,10 @@ def scale_and_prepare_dataset( Defaults to (-1, 1). target_range (tuple[float, float], optional): Target range of target scaling. Defaults to (-1, 1) - scale (bool, optional): Whether to scale the dataset. Defaults to False. + scale (bool, optional): Whether to scale the dataset. Defaults to True. Returns: - tuple[tuple[np.ndarray, np.ndarray], np.ndarray] + tuple[tuple[np.ndarray, np.ndarray], tuple[np.ndarray, np.ndarray]] | tuple[ tuple[np.ndarray, np.ndarray], tuple[np.ndarray, np.ndarray], @@ -136,12 +277,12 @@ def scale_and_prepare_dataset( ds: tf.data.Dataset = tf.data.Dataset.load(str(dsfile)) features, targets = next(iter(ds.batch(batch_size=len(ds)).as_numpy_iterator())) - # Reshape features and targets for multidimensional input and output (e.g., for - # CNNs). - if conv_input: - features = features.reshape((-1, features.shape[-1])) - if conv_output: - targets = targets.reshape((-1, targets.shape[-1])) + # Save feature and targets shape, e.g., for multidimensional data. + features_shape: tuple = features.shape + targets_shape: tuple = targets.shape + # Reshape to one-dimensional data for the scalers to work. + features = features.reshape(-1, features.shape[-1]) + targets = targets.reshape(-1, targets.shape[-1]) if len(feature_names) > features.shape[-1]: raise ValueError("Too many feature names.") @@ -284,6 +425,12 @@ def scale_and_prepare_dataset( (1, train_targets.shape[-1]) ) + # Reshape to one-dimensional data. + train_features = train_features.reshape(-1, features_shape[-1]) + train_targets = train_targets.reshape(-1, targets_shape[-1]) + val_features = val_features.reshape(-1, features_shape[-1]) + val_targets = val_targets.reshape(-1, targets_shape[-1]) + # Scale the features and targets. logger.info("Scaling data") train_features = feature_scaler.transform(train_features) @@ -291,14 +438,27 @@ def scale_and_prepare_dataset( val_features = feature_scaler.transform(val_features) val_targets = target_scaler.transform(val_targets) + # Reshape to original shape + train_features = train_features.reshape(-1, *features_shape[1:]) + train_targets = train_targets.reshape(-1, *targets_shape[1:]) + val_features = val_features.reshape(-1, *features_shape[1:]) + val_targets = val_targets.reshape(-1, *targets_shape[1:]) + # Only return test ds if ``test_split > 0``. # Ignore mypy complaining that ``test_split`` can be None. if test_split > 0: # type: ignore test_features, test_targets = next( iter(test_ds.batch(batch_size=len(test_ds)).as_numpy_iterator()) ) + test_features = test_features.reshape(-1, features_shape[-1]) + test_targets = test_targets.reshape(-1, targets_shape[-1]) + test_features = feature_scaler.transform(test_features) test_targets = target_scaler.transform(test_targets) + + test_features = test_features.reshape(-1, *features_shape[1:]) + test_targets = test_targets.reshape(-1, *targets_shape[1:]) + return ( (train_features, train_targets), (val_features, val_targets), @@ -310,9 +470,9 @@ def scale_and_prepare_dataset( def train( model: keras.Model, - train_data: tuple[tf.Tensor, tf.Tensor], - val_data: tuple[tf.Tensor, tf.Tensor], - savepath: pathlib.Path, + train_data: tuple[ArrayLike, ArrayLike], + val_data: tuple[ArrayLike, ArrayLike], + savepath: str | pathlib.Path, lr: float = 0.1, epochs: int = 500, bs: int = 64, @@ -323,14 +483,14 @@ def train( "mse", "MeanAbsolutePercentageError", "MeanSquaredLogarithmicError" ] = "mse", recompile_model: bool = True, - **train_kwargs, + **kwargs, ) -> None: """Train a tensorflow model on the provided training data and save the best model. Args: model (tf.Module): Model to be trained. - train_data (tuple[tf.Tensor, tf.Tensor]): Training features and targets. - val_data (tuple[tf.Tensor, tf.Tensor]): Validation features and targets. + train_data (tuple[ArrayLike, ArrayLike]): Training features and targets. + val_data (tuple[ArrayLike, ArrayLike]): Validation features and targets. savepath (pathlib.Path): Savepath for models and logging. lr (float, optional): Initial learning rate. Defaults to 0.1. epochs (_type_, optional): Training epochs. Defaults to 500. @@ -346,8 +506,7 @@ def train( recompile_model (bool, optional): Whether to recompile the model before training. Can e.g., be set to false, if the model is built and compiled by a different function. Defaults to True. - **train_kwargs: Additional keyword arguments to be passed to the ``model.fit()`` - method. + **kwargs: Get passed to the ``model.fit()`` method. Returns: None @@ -412,12 +571,13 @@ def train( early_stopping_callback, tensorboard_callback, ], - **train_kwargs, + **kwargs, ) model.save_weights(savepath / "finalmodel") # Load the best model and save to OPM format. model.load_weights(savepath / "bestmodel") + model.save(savepath / "bestmodel.keras") if kerasify: export_model(model, savepath / "WI.model") @@ -426,6 +586,7 @@ def build_model( hp: keras_tuner.HyperParameters, ninputs: int, noutputs: int, + lr_tune: float = 0.1, ) -> tf.Module: """Build and compile a FCNN with the given hyperparameters. @@ -439,7 +600,8 @@ def build_model( """ # Get hyperparameters. - depth: int = hp.Int("depth", min_value=3, max_value=20, step=2) + # depth: int = hp.Int("depth", min_value=3, max_value=20, step=2) + depth: int = hp.Int("depth", min_value=3, max_value=6, step=2) hidden_size: int = hp.Int("hidden_size", min_value=5, max_value=50, step=5) activation: Literal["sigmoid", "relu", "tanh"] = hp.Choice( "activation", ["sigmoid", "relu", "tanh"] @@ -462,7 +624,7 @@ def build_model( ) model.compile( loss=loss, - optimizer=tf.keras.optimizers.Adam(learning_rate=0.1), + optimizer=tf.keras.optimizers.Adam(learning_rate=lr_tune), ) return model @@ -470,25 +632,32 @@ def build_model( def tune( ninputs: int, noutputs: int, - train_data: tuple[tf.Tensor, tf.Tensor], - val_data: tuple[tf.Tensor, tf.Tensor], + train_data: tuple[ArrayLike, ArrayLike], + val_data: tuple[ArrayLike, ArrayLike], + savepath: str | pathlib.Path, objective: Literal["loss", "val_loss"] = "val_loss", - **tune_kwargs, -) -> tuple[tf.Module, keras_tuner.Tuner]: + max_trials: int = 5, + executions_per_trial: int = 1, + sample_weight: ArrayLike = np.array([1.0]), + lr_tune: float = 0.1, + **kwargs, +) -> tuple[keras.Model, keras_tuner.Tuner]: """ Tune the hyperparameters of a neural network model using random search. Args: ninputs (int): Number of input features to the model. noutputs (int): Number of output features to the model. - train_data (tuple[tf.Tensor, tf.Tensor]): Tuple of training input and target + train_data (tuple[ArrayLike, ArrayLike]): Tuple of training input and target data. - val_data (tuple[tf.Tensor, tf.Tensor],): Tuple of validation input and target + val_data (tuple[ArrayLike, ArrayLike],): Tuple of validation input and target data. objective (Literal["loss", "val_loss"], optional): Objective for search. Defaults to ``"val_loss"``. - **tune_kwargs: Additional keyword arguments to pass to the tuner's search - method. + max_trials (int): Default is 5. + executions_per_trial (int): Default is 1. + sample_weight:(ArrayLike): Default is ``np.array([1.0])``. + **kwargs: Get passed to the tuner's search method. Returns: tf.Module: The model compiled with the best hyperparameters. @@ -500,13 +669,19 @@ def tune( """ # Define the tuner and start a search. tuner = keras_tuner.RandomSearch( - hypermodel=partial(build_model, ninputs=ninputs, noutputs=noutputs), + hypermodel=partial( + build_model, + ninputs=ninputs, + noutputs=noutputs, + lr_tune=lr_tune, + ), objective=objective, - max_trials=20, - executions_per_trial=2, + max_trials=max_trials, + executions_per_trial=executions_per_trial, overwrite=True, - directory="my_dir", - project_name="helloworld", + directory=savepath, + project_name="tuner", + **kwargs, ) tuner.search_space_summary() @@ -515,8 +690,14 @@ def tune( if not isinstance(val_data, tuple) or len(val_data) != 2: raise ValueError("val_data must be a tuple of two tensors.") + # If kwargs contains epochs, this will fail. tuner.search( - train_data[0], train_data[1], epochs=20, validation_data=val_data, **tune_kwargs + train_data[0], + train_data[1], + epochs=20, + validation_data=val_data, + sample_weight=sample_weight, + **kwargs, ) tuner.results_summary() @@ -527,7 +708,7 @@ def tune( return model, tuner -def save_tune_results(tuner: keras_tuner.Tuner, savepath: pathlib.Path) -> None: +def save_tune_results(tuner: keras_tuner.Tuner, savepath: str | pathlib.Path) -> None: # Ensure ``savepath`` is a ``Path`` object. savepath = pathlib.Path(savepath) @@ -546,19 +727,19 @@ def save_tune_results(tuner: keras_tuner.Tuner, savepath: pathlib.Path) -> None: def scale_and_evaluate( model: keras.Model, - model_input: np.ndarray, - scalingsfile: pathlib.Path, -) -> np.ndarray: + model_input: ArrayLike, + scalingsfile: str | pathlib.Path, +) -> tf.Tensor: """Scale the input, evaluate with the model and scale the output. Args: model (tf.keras.Model): A Keras model to evaluate the input with. - model_input (np.ndarray): Input tensor. Can be a batch. - scalingsfile (pathlib.Path): The path to the CSV file containing the + model_input (ArrayLike): Input tensor. Can be a batch. + scalingsfile (str | pathlib.Path): The path to the CSV file containing the scaling parameters for MinMaxScaling. Returns: - np.ndarray: The model's output, scaled back to the original range. + tf.Tensor: The model's output, scaled back to the original range. Raises: FileNotFoundError: If ``scalingsfile`` does not exist. @@ -601,23 +782,53 @@ def scale_and_evaluate( feature_scaler: MinMaxScaler = MinMaxScaler(feature_range) feature_scaler.data_min_ = np.array(feature_min) feature_scaler.data_max_ = np.array(feature_max) - feature_scaler.scale_ = (feature_range[1] - feature_range[0]) / ( - feature_scaler.data_max_ - feature_scaler.data_min_ - ) + feature_scaler.scale_ = ( + feature_range[1] - feature_range[0] + ) / handle_zeros_in_scale(feature_scaler.data_max_ - feature_scaler.data_min_) feature_scaler.min_ = ( feature_range[0] - feature_scaler.data_min_ * feature_scaler.scale_ ) target_scaler: MinMaxScaler = MinMaxScaler(target_range) target_scaler.data_min_ = np.array(target_min) target_scaler.data_max_ = np.array(target_max) - target_scaler.scale_ = (target_range[1] - target_range[0]) / ( + target_scaler.scale_ = (target_range[1] - target_range[0]) / handle_zeros_in_scale( target_scaler.data_max_ - target_scaler.data_min_ ) target_scaler.min_ = ( target_range[0] - target_scaler.data_min_ * target_scaler.scale_ ) + # Save feature and targets shape and reshape to one-dimensional data, e.g., for + # multidimensional data. The scaler accepts at most two dimensions. + input_shape: tuple = model_input.shape + model_input = model_input.reshape(-1, input_shape[-1]) + + scaled_input: np.ndarray = feature_scaler.transform(model_input) + scaled_input = scaled_input.reshape(input_shape) + # Run model. - scaled_input: tf.Tensor = feature_scaler.transform(model_input) output: tf.Tensor = model(scaled_input) - return target_scaler.inverse_transform(output) + + output_shape: tuple = output.shape + output = tf.reshape(output, (-1, output_shape[-1])) + + unscaled_output: tf.Tensor = target_scaler.inverse_transform(output) + unscaled_output = tf.reshape(unscaled_output, output_shape) + + return unscaled_output + + +def handle_zeros_in_scale(scale: ArrayLike) -> np.ndarray: + """Set scales of near constant features to 1. + + Note: This behavior is in line with `sklearn.preprocessing.MinMaxScaler`. + + Args: + scale (ArrayLike): The scale array. + + Returns: + np.ndarray: The modified scale array. + + """ + # ``atol`` must be very low s.t. this works for permeability in [m^2] for example. + return np.where(np.isclose(scale, 0.0, atol=1e-20), np.ones_like(scale), scale) diff --git a/src/pyopmnearwell/ml/data.py b/src/pyopmnearwell/ml/resdata_dataset.py similarity index 71% rename from src/pyopmnearwell/ml/data.py rename to src/pyopmnearwell/ml/resdata_dataset.py index 0009201..e67b238 100644 --- a/src/pyopmnearwell/ml/data.py +++ b/src/pyopmnearwell/ml/resdata_dataset.py @@ -1,3 +1,4 @@ +# pylint: disable=fixme """This module provides functionality to parse ``*.UNRST`` files for given keywords and transform the extracted values into a tensorflow dataset. @@ -5,7 +6,8 @@ different from the default one. The lines that need to be changed are marked with # MANUAL CHANGES. -Deprecated: This module is deprecated in favor of the ``ensemble`` module. +Deprecated: This module is deprecated in favor of the ``ensemble`` module which can run +an ensemble of pyopmnearwell decks AND extract data afterwards. """ @@ -18,20 +20,21 @@ import numpy as np import tensorflow as tf -from ecl.eclfile.ecl_file import EclFile, open_ecl_file +from resdata import FileMode +from resdata.resfile import ResdataFile logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) -class EclDataSet: # pylint: disable=R0902 - """Generate samples for a ``tf.data.Dataset`` from a folder of ``*.UNRST`` files. +class ResDataSet: # pylint: disable=R0902 + """Generate samples for a ``tf.data.Dataset`` from a folder of ``.UNRST`` files. Example: After instantiation of the class, it can be passed to ``tf.data.Dataset.from_generator()``, to create a dataset that ``tensorflow`` can work with. - >>> data = EclDataSet(path, input_kws, target_kws) + >>> data = ResDataSet(path, input_kws, target_kws) >>> data.read_data() >>> ds = tf.data.Dataset.from_generator( >>> data, @@ -40,10 +43,11 @@ class EclDataSet: # pylint: disable=R0902 To save the dataset, use >>> ds.save(path) - Afterwards, the ``*.UNRST`` files used to generated the dataset can be deleted. + Afterwards, the ``.UNRST`` files used to generated the dataset can be deleted. """ + # Typing for instance attributes. features: tf.Tensor """Stores all inputs of the dataset. @@ -62,7 +66,7 @@ def __init__( # pylint: disable=R0913 path: str, input_kws: list[str], target_kws: list[str], - file_format: Literal["ecl", "opm"] = "ecl", + file_format: Literal["resdata", "opm"] = "resdata", dtype=tf.float32, shuffle_on_epoch_end: bool = False, read_data_on_init: bool = True, @@ -71,17 +75,17 @@ def __init__( # pylint: disable=R0913 Parameters: path: _description_ - input_kws: Keywords for attributes of the ``EclFile`` that shall become + input_kws: Keywords for attributes of the ``.UNRST`` file that shall become model input. - target_kws: Keywords for attributes of the ``EclFile`` that shall become + target_kws: Keywords for attributes of the ``.UNRST`` file that shall become targets for model training. - type: _description_. Defaults to ``"ecl"``. - read_data_on_init: Reads data from ``*.UNRST`` files in ``path`` on + type: _description_. Defaults to ``"resdata"``. + read_data_on_init: Reads data from ``.UNRST`` files in ``path`` on instantiation. Disable for testing/debugging. Defaults to ``True``. Warning: - As of now, ``type`` is always assumed to be ``ecl``. ``opm`` is not + As of now, ``type`` is always assumed to be ``resdata``. ``opm`` is not implemented yet. Returns: @@ -95,28 +99,30 @@ def __init__( # pylint: disable=R0913 if read_data_on_init: self.read_data() self.shuffle_on_epoch_end: bool = shuffle_on_epoch_end - self.file_format: Literal["ecl", "opm"] = file_format + self.file_format: Literal["resdata", "opm"] = file_format def read_data(self): - """Create a ``tensorflow`` dataset from a folder of ``ecl`` or ``opm`` files.""" + """Create a ``tensorflow`` dataset from a folder of ``resdata`` or ``opm`` files.""" logger.info("Generating datapoints...") _features_lst: list[tf.Tensor] = [] _targets_lst: list[tf.Tensor] = [] for filename in os.listdir(self.path): if filename.endswith("UNRST"): - with open_ecl_file(os.path.join(self.path, filename)) as ecl_file: - try: - feature, target = self.EclFile_to_datapoint(ecl_file) - _features_lst.append(feature) - _targets_lst.append(target) - logger.info( # pylint: disable=W1203 - f"Generated a datapoint from {filename}." - ) - except KeyError as keyerror: - logger.info( # pylint: disable=W1203 - f"{filename} has no keyword {keyerror}." - ) - continue + resdata_file: ResdataFile = ResdataFile( + os.path.join(self.path, filename), flags=FileMode.CLOSE_STREAM + ) + try: + feature, target = self.ResdataFile_to_datapoint(resdata_file) + _features_lst.append(feature) + _targets_lst.append(target) + logger.info( # pylint: disable=W1203 + f"Generated a datapoint from {filename}." + ) + except KeyError as keyerror: + logger.info( # pylint: disable=W1203 + f"{filename} has no keyword {keyerror}." + ) + continue if len(_features_lst) > 0 and len(_targets_lst) > 0: # Transform the lists into a tensor @@ -138,25 +144,24 @@ def read_data(self): Generated an empty dataset for now.""" ) - def EclFile_to_datapoint( # pylint: disable= C0103 - self, ecl_file: EclFile + def ResdataFile_to_datapoint( # pylint: disable= C0103 + self, resdata_file: ResdataFile ) -> tuple[tf.Tensor, tf.Tensor]: - """Extract values from an ``EclFile`` to form an (input, target) tuple of - tensors. + """Extract values from an ``ResdataFile`` object to form an (input, target) + tuple of tensors. - - Parameters: - ecl_file: _description_ + Args: + ecl_file (EclFile): _description_ Raises: - KeyError: If ``ecl_file`` does not have either of the keywords in + KeyError: If ``resdata_file`` does not have either of the keywords in ``self.input_kws`` or ``self.target_kws`` Returns: - A tuple containing the input and target tensor. The former has shape - ``(ecl_file.num_report_steps(), num_cells, len(input_kws))``, while the - latter has shape - ``(ecl_file.num_report_steps(), num_cells, len(target_kws))``. + tuple[tf.Tensor, tf.Tensor]: A tuple containing the input and target tensor. + The former has shape ``(resdata_file.num_report_steps(), num_cells, + len(input_kws))``, while the latter has shape + ``(resdata_file.num_report_steps(), num_cells, len(target_kws))``. """ # Only add the datapoint if all input features and targets are @@ -167,13 +172,13 @@ def EclFile_to_datapoint( # pylint: disable= C0103 # corresponding to the keyword, with shape # ``(ecl_file.num_report_steps(), num_cells)``. for input_kw in self.input_kws: - if ecl_file.has_kw(input_kw): - feature[input_kw] = np.array(ecl_file.iget_kw(input_kw)) + if resdata_file.has_kw(input_kw): + feature[input_kw] = np.array(resdata_file.iget_kw(input_kw)) else: raise KeyError(input_kw) for target_kw in self.target_kws: - if ecl_file.has_kw(target_kw): - target[target_kw] = np.array(ecl_file.iget_kw(target_kw)) + if resdata_file.has_kw(target_kw): + target[target_kw] = np.array(resdata_file.iget_kw(target_kw)) else: raise KeyError(target_kw) # Stack the different input and target properties into one input @@ -220,13 +225,13 @@ def on_epoch_end(self): def main(args): # pylint: disable=W0621 """Create a dataset from the given arguments and store it""" - data = EclDataSet(args.path, args.input_kws, args.target_kws, args.file_format) + data = ResDataSet(args.path, args.input_kws, args.target_kws, args.file_format) assert len(data) > 0 dataset = tf.data.Dataset.from_generator( data, output_signature=( - tf.TensorSpec.from_tensor(data[0][0]), - tf.TensorSpec.from_tensor(data[0][1]), + tf.TensorSpec.from_tensor(data[0][0]), # type: ignore + tf.TensorSpec.from_tensor(data[0][1]), # type: ignore ), ) # Manually set the dataset cardinality. diff --git a/src/pyopmnearwell/ml/upscale.py b/src/pyopmnearwell/ml/upscale.py new file mode 100644 index 0000000..973e29e --- /dev/null +++ b/src/pyopmnearwell/ml/upscale.py @@ -0,0 +1,475 @@ +# pylint: disable=fixme,missing-function-docstring,no-member, pointless-string-statement +"""Functionality to upscale data from an ensemble run on a radial grid to a cartesian +grid. + +Note: pylint: no-member is disabled, because it complains about the ``BaseUpscaler`` +missing instance attributes, which are taken care of by the ``Upscaler`` protocol. +pylint: pointless-string-statement is disabled, as it complains an attribute docstring +in the ``Upscaler`` protocol. + +""" + +import math +import pathlib +from abc import ABC, abstractmethod +from typing import Protocol + +import numpy as np + +# ``tqdm`` is not a dependency. Up to the user to install it. +try: + # Avoid some mypy trouble. + import tqdm.autonotebook as tqdm # type: ignore +except ImportError: + _IS_TQDM_AVAILABLE: bool = False +else: + _IS_TQDM_AVAILABLE = True + +from pyopmnearwell.ml import ensemble +from pyopmnearwell.utils import formulas, units + + +class Upscaler(Protocol): + """Protocol class for upscalers. + + This class is used for typing of abstract attributes of ``BaseUpscaler``. MyPy will + check if subclasses of ``BaseUpscaler`` implement the following instance attributes: + - ``num_timesteps`` + - ``num_layers`` + - ``num_zcells`` + - ``num_xcells`` + - ``single_feature_shape`` + However, in comparison to missing abstract functions, no runtime error will be + raised if they are missing. + + See, e.g., https://stackoverflow.com/a/75253719 for an explanation. + + Note: As of now (Python 3.11), each instance method of ``BaseUpscaler`` or a + subclass making use of one of the attributes, needs to have its self argumented + annotated with ``Upscaler``. In future python versions it should be possible to + use ``class BaseUpscaler[Upscaler](ABC):...`` instead and remove these + annotations. + + """ + + @property + def num_timesteps(self) -> int: ... + + @property + def num_layers(self) -> int: ... + + @property + def num_zcells(self) -> int: ... + + @property + def num_xcells(self) -> int: ... + + @property + def single_feature_shape(self) -> tuple: ... + + @property + def angle(self) -> float: ... + + """Angle of the cake radial grid. Default is 60°.""" + + +class BaseUpscaler(ABC): + """Extract and upscale data from an array of ensemble data. + + This base class provides several methods to extract features from fine-scale radial + simulations and upscale to coarse cartesian cells. Depending on the type of data, + this is done by averaging/summing/etc. values along all cells that correspond to a + coarse cell. + + Additionally, the sparsity of the dataset can be increased by taking only some + timesteps/horizontal cells. + + The upscaled data is usually provided in form of two ``np.ndarrays``, one for + features and one for targets. + + Subclasses need to implement ``__init__`` and (if needed) ``create_ds`` methods. + + The feature array will have shape ``(num_ensemble_runs, num_timesteps/step_size_t, + num_layers, num_xcells/step_size_x, num_features)``. + The target array will have shape ``(num_ensemble_runs, num_timesteps/step_size_t, + num_layers, num_xcells/step_size_x, 1)`` + + Note: All methods assume that all cells have the same height. if this is not the + case, the methods must be overridden. + + """ + + @abstractmethod + def __init__(self: Upscaler): # pylint: disable=missing-function-docstring + return + + @abstractmethod + def create_ds(self: Upscaler): # pylint: disable=missing-function-docstring + return + + def reduce_data_size( + self: Upscaler, + feature: np.ndarray, + step_size_x: int = 1, + step_size_t: int = 1, + random: bool = False, + ) -> np.ndarray: + """Reduce the size of the input feature array by selecting elements with a + fixed step size. + + Args: + feature (np.ndarray): The input feature array. + step_size_x (int, optional): The step size for the x-axis. Defaults to 1. + step_size_t (int, optional): The step size for the t-axis. Defaults to 1. + random (bool, optional): If True, select elements randomly instead of using + a fixed step size. Defaults to False. Not implemented yet. + + Returns: + np.ndarray: The reduced feature array. + + """ + # TODO: Add option to have a random selection of elements for each member, + # instead of a fixed stepsize. This needs to be the same for each feature, hence + # the current idea does not work. + if random: + pass + return feature[:, ::step_size_t, ::, ::step_size_x] + + def get_vertically_averaged_values( + self: Upscaler, + features: np.ndarray, + feature_index, + disregard_first_xcell: bool = True, + ) -> np.ndarray: + """Average features vertically inside each layer. + + Args: + features (np.ndarray): _description_ + feature_index (int): _description_. + disregard_first_xcell (bool): __description__. Default is True. + + Returns: + np.ndarray (``shape = (num_ensemble_runs, num_timesteps, num_layers, + num_xcells)``): + + """ + # Innermost cells (well cells) get disregarded. + feature: np.ndarray = np.average(features[..., feature_index], axis=-2) + + if disregard_first_xcell: + feature = feature[..., 1:] + assert feature.shape == self.single_feature_shape + + else: + assert feature.shape[:-1] == self.single_feature_shape[:-1] + assert feature.shape[-1] == self.single_feature_shape[-1] + 1 + + return feature + + def get_radii( + self: Upscaler, radii_file: pathlib.Path + ) -> tuple[np.ndarray, np.ndarray]: + """Get full list of cell radii.""" + cell_center_radii, inner_radii, outer_radii = ensemble.calculate_radii( + radii_file, + num_cells=self.num_xcells + 1, + return_outer_inner=True, + triangle_grid=True, + angle=self.angle, + ) + # Innermost cells (well cells) get disregarded. If the cell_boundary_radii are + # used for integrating, this is actually needed, as the content of the well does + # not count towards the well block, hence the well radius should not be + # integrated. + cell_center_radii = cell_center_radii[1:] + inner_radii = inner_radii[1:] + outer_radii = outer_radii[1:] + + cell_boundary_radii: np.ndarray = np.append(inner_radii, outer_radii[-1]) + assert cell_center_radii.shape == (self.num_xcells,) + assert cell_boundary_radii.shape == (self.num_xcells + 1,) + return cell_center_radii, cell_boundary_radii + + def get_timesteps(self: Upscaler, simulation_length: float) -> np.ndarray: + """_summary_ + + Returns: + np.ndarray (``shape = (num_timesteps,) ``): Unit: [d]. + + """ + timesteps: np.ndarray = np.linspace(0, simulation_length, self.num_timesteps) + assert timesteps.shape == self.num_timesteps + return timesteps + + def get_horizontically_integrated_values( # pylint: disable=too-many-arguments + self: Upscaler, + features: np.ndarray, + cell_center_radii: np.ndarray, + cell_boundary_radii: np.ndarray, + feature_index: int, + disregard_first_xcell: bool = True, + ): + """Integrate feature horizontically along layers and divide by equivalent + cartesian block area. + + Note: + - Before integrating, the feature is averaged vertically inside each layer. + - As the feature is averaged and not summed, the integration takes place in + 2D with vertically averaged values. Hence it suffices to divide by area + and not by volume. + + Args: + features (np.ndarray): _description_ + cell_center_radii (np.ndarray): + cell_boundary_radii (np.ndarray): + feature_index (int): _description_. Default is 1. + disregard_first_xcell (bool): __description__. Default is True. + + Returns: + np.ndarray (``shape = (num_ensemble_runs, num_timesteps, num_layers, + num_xcells)``): Features values average for each cell. + + """ + # Average along vertical cells in a layer. + feature: np.ndarray = np.average(features[..., feature_index], axis=-2) + + if disregard_first_xcell: + feature = feature[..., 1:] + + # Integrate horizontically along layers and divide by equivalent cartesian block + # area. + block_sidelengths: np.ndarray = formulas.cell_size(cell_center_radii) # type: ignore + # feature_lst: list[np.ndarray] = [] + # for i in range(feature.shape[-1]): + # feature_lst.append( + # ensemble.integrate_fine_scale_value( + # feature[..., : i + 1], + # cell_boundary_radii[: i + 2], + # block_sidelengths[: i + 1], + # ) + # / (block_sidelengths[i] ** 2) + # ) + # averaged_feature: np.ndarray = np.concatenate(feature_lst, axis=-1) + # The horizontal dimension is the last axis of each feature, hence we pass + # ``axis=-1``. + integrated_feature: np.ndarray = ensemble.integrate_fine_scale_value( + feature, cell_boundary_radii, block_sidelengths, axis=-1 + ) / (block_sidelengths**2) + + assert integrated_feature.shape == self.single_feature_shape + return integrated_feature + + def get_homogeneous_values( + self: Upscaler, features, feature_index, disregard_first_xcell: bool = True + ): + """Get a feature that is homogeneous inside a layer. + + Note: Since the feature is equal inside a layer, this method takes the first + value for each layer. + + Args: + features (np.ndarray): _description_ + feature_index (int): _description_. + disregard_first_xcell (bool): __description__. Default is True. + + Returns: + np.ndarray (``shape = (num_ensemble_runs, num_timesteps, num_layers, + num_xcells)``): + + """ + # Innermost cells (well cells) get disregarded. + feature: np.ndarray = features[..., feature_index][..., 0, :] + + if disregard_first_xcell: + feature = feature[..., 1:] + + assert feature.shape == self.single_feature_shape + return feature + + def get_analytical_PI( # pylint: disable=invalid-name + self: Upscaler, + permeabilities: np.ndarray, + cell_heights: np.ndarray, + radii: np.ndarray, + well_radius: float, + ) -> np.ndarray: + """_summary_ + + _extended_summary_ + + Args: + permeabilities (np.ndarray): Unit has to be [m^2]! + cell_heights (np.ndarray): Unit [m]. + radii (np.ndarray): _description_ + well_radius (float): _description_ + + Returns: + np.ndarray: _description_ + """ + analytical_PI: np.ndarray = formulas.peaceman_matrix_WI( # type: ignore + k_h=permeabilities * cell_heights, + r_e=radii, + r_w=well_radius, + ) + assert analytical_PI.shape == self.single_feature_shape + return analytical_PI + + # pylint: disable-next=invalid-name, too-many-arguments, too-many-locals + def get_analytical_WI( + self: Upscaler, + pressures: np.ndarray, + saturations: np.ndarray, + permeabilities: np.ndarray, + temperature: float, + surface_density: float, + radii: np.ndarray, + well_radius: float, + # pylint: disable-next=invalid-name + OPM: pathlib.Path, + ) -> np.ndarray: + """_summary_ + + _extended_summary_ + + Args: + pressures (np.ndarray): _description_ + saturations (np.ndarray): _description_ + permeabilities (np.ndarray): Unit has to be [mD]! + temperature (float): _description_ + surface_density (float): _description_ + radii (np.ndarray): _description_ + well_radius (float): _description_ + OPM (pathlib.Path): _description_ + + Returns: + np.ndarray: _description_ + + """ + densities_lst: list[list[float]] = [] + viscosities_lst: list[list[float]] = [] + # ``formulas.co2brinepvt`` calls CO2BRINEPVT from OPM Flow for each pressure, + # which is quite inefficient. If tqdm is available, we show a progress bar. + if _IS_TQDM_AVAILABLE: + pressures_bar = tqdm.tqdm(pressures.flatten()) + else: + pressures_bar = pressures.flatten() + + for pressure in pressures_bar: + # Evaluate density and viscosity. + density_tuple: list[float] = [] + viscosity_tuple: list[float] = [] + + for phase in ["water", "CO2"]: + density_tuple.append( + formulas.co2brinepvt( + pressure=pressure, + temperature=temperature + units.CELSIUS_TO_KELVIN, + phase_property="density", + phase=phase, # type: ignore + OPM=OPM, + ) + ) + + viscosity_tuple.append( + formulas.co2brinepvt( + pressure=pressure, + temperature=temperature + units.CELSIUS_TO_KELVIN, + phase_property="viscosity", + phase=phase, # type: ignore + OPM=OPM, + ) + ) + densities_lst.append(density_tuple) + viscosities_lst.append(viscosity_tuple) + + densities_shape = list(pressures.shape) + densities_shape.extend([2]) + densities: np.ndarray = np.array(densities_lst).reshape(densities_shape) + viscosities: np.ndarray = np.array(viscosities_lst).reshape(densities_shape) + + # Calculate the well index from Peaceman. The analytical well index is in [m*s], + # hence we need to devide by surface density to transform to [m^4*s/kg]. + # pylint: disable-next=invalid-name + analytical_WI: np.ndarray = ( # type: ignore + # Ignore unsupported operand types for *. Fixing this would be quite + # complex. + formulas.two_phase_peaceman_WI( # type: ignore + k_h=permeabilities + * units.MILIDARCY_TO_M2 + * ( + self.num_zcells / self.num_layers + ), # TODO: This is FALSE, it must be height instead! + r_e=radii, + r_w=well_radius, + rho_1=densities[..., 0], + mu_1=viscosities[..., 0], + k_r1=(1 - saturations) ** 2, + rho_2=densities[..., 1], + mu_2=viscosities[..., 1], + k_r2=saturations**2, + ) + / surface_density + ) + + # TODO: Add this assert again. It's turned off s.t. the analytical WI can be + # computed after size reduction of features. + # assert analytical_WI.shape == self.single_feature_shape + return analytical_WI + + def get_data_WI( # pylint: disable=invalid-name + self: Upscaler, + features: np.ndarray, + pressure_index: int, + inj_rate_index: int, + angle: float = math.pi / 3, + ) -> np.ndarray: + """Calculate data-driven WI from pressure and flow rate. + + Similar functionality to ``ensemble.calculate_WI``, but can additionally treat + multiple vertical cells in a layer correctly. + + Note: + - Pressures get averaged over each layer, injection rates get summed over + each layer. + - The method automatically scales the near-well injection rate from the cake + grid with angle ``angle`` to a 360° well. Furthermore, the rate is scaled + from rate-per-day (which the results are in) to rate-per-second, which OPM + Flow uses internally for the WI. + + + Args: + features (np.ndarray): _description_ + pressure_index (int): _description_ + inj_rate_index (int): _description_ + + Returns: + np.ndarray: _description_ + + """ + # Take the pressure values of the well blocks as bhp. Average along each layer. + bhps: np.ndarray = np.average(features[..., pressure_index], axis=-2)[..., 0][ + ..., None + ] # ``shape = (num_completed_runs, num_timesteps, num_layers, 1)`` + + # Ge the pressure values of all other blocks. Average along each layer. + pressures: np.ndarray = np.average(features[..., pressure_index], axis=-2)[ + ..., 1: + ] # ``shape = (num_completed_runs, num_timesteps, num_layers, num_xcells)`` + + # Get the individual injection rates per second for each layer. Sum across a + # layer to get the rates for a full layer. Multiply by + # ``(math.pi * 2 / angle)`` to transform from a cake of given ``angle`` to a + # full radial model and convert to rate per second. + injection_rate_per_second_per_cell: np.ndarray = ( + np.sum(features[..., inj_rate_index], axis=-2)[..., 0] + * (math.pi * 2 / angle) + * units.Q_per_day_to_Q_per_seconds + )[ + ..., None + ] # ``shape = (num_completed_runs, num_timesteps, num_layers, 1)`` + + # Check that we do not divide by zero. + assert np.all(bhps - pressures) + WI_data: np.ndarray = injection_rate_per_second_per_cell / (bhps - pressures) + assert WI_data.shape == self.single_feature_shape + return WI_data diff --git a/src/pyopmnearwell/ml/utils.py b/src/pyopmnearwell/ml/utils.py new file mode 100644 index 0000000..2a24979 --- /dev/null +++ b/src/pyopmnearwell/ml/utils.py @@ -0,0 +1,24 @@ +"""Utilility functions for the ensemble and ML capabilities. + +Note: ``ml.ensemble`` makes use of ``np.random.default_rng``, which ignores the global +seed of ``numpy``. Make sure to set them locally for full determinism. + +""" + +from typing import Optional + +import tensorflow as tf + + +def enable_determinism(seed: Optional[int] = None): + """Set a seed for python, numpy, and tensorflow and enable deterministic behavior. + + Args: + seed: (Optional[int]): Seed for the ``np.random.Generator``. Default is + ``None``. + + """ + # ``tf.keras.utils.set_random_seed`` sets the python, numpy, and tensorflow seed + # simultaneously. + tf.keras.utils.set_random_seed(seed=seed) + tf.config.experimental.enable_op_determinism() diff --git a/src/pyopmnearwell/templates/co2store/base.mako b/src/pyopmnearwell/templates/co2store/base.mako index 2b6e7e1..5db7da0 100644 --- a/src/pyopmnearwell/templates/co2store/base.mako +++ b/src/pyopmnearwell/templates/co2store/base.mako @@ -242,6 +242,14 @@ WCONPROD %endif / %endif +-- Close the specified connections +% if len(dic['inj'][j]) >= 6: +WELOPEN +% for i in range(1, dic['noCells'][2] + 1): +'INJ0' ${'SHUT' if i in dic['inj'][j][5:] else 'OPEN'} 0 0 ${i} / +% endfor +/ +% endif TSTEP ${mt.floor(dic['inj'][j][0]/dic['inj'][j][1])}*${dic['inj'][j][1]} / diff --git a/src/pyopmnearwell/templates/co2store/no_disgas_no_diffusion.mako b/src/pyopmnearwell/templates/co2store/no_disgas_no_diffusion.mako index d43c8b0..32409e0 100644 --- a/src/pyopmnearwell/templates/co2store/no_disgas_no_diffusion.mako +++ b/src/pyopmnearwell/templates/co2store/no_disgas_no_diffusion.mako @@ -192,8 +192,7 @@ COMPDAT % if dic['grid'] == 'core': 'INJ0' 1 ${1+mt.floor(dic['noCells'][2]/2)} ${1+mt.floor(dic['noCells'][2]/2)} ${1+mt.floor(dic['noCells'][2]/2)} 'OPEN' 1* ${dic["jfactor"]} / % else: ---'INJ0' ${max(1, 1+mt.floor(dic['noCells'][1]/2))} ${max(1, 1+mt.floor(dic['noCells'][1]/2))} 1 ${dic['noCells'][2]} 'OPEN' 1* ${dic["jfactor"]*2*mt.pi*dic['rock'][0][0]*dic['dims'][2]/dic['noCells'][2]} / -'INJ0' ${max(1, 1+mt.floor(dic['noCells'][1]/2))} ${max(1, 1+mt.floor(dic['noCells'][1]/2))} 1 ${dic['noCells'][2]} 'OPEN' 1* ${dic["jfactor"]} / +'INJ0' ${max(1, 1+mt.floor(dic['noCells'][1]/2))} ${max(1, 1+mt.floor(dic['noCells'][1]/2))} 1 ${dic['noCells'][2]} 'OPEN' 1* ${dic["jfactor"]} ${dic['diameter']}/ % endif % endif @@ -222,7 +221,7 @@ WCONINJE % else: 'INJ0' 'WATER' ${'OPEN' if dic['inj'][j][4] > 0 else 'SHUT'} 'RATE' ${f"{dic['inj'][j][4] / 998.108 : E}"} 1* 400/ -%endif +% endif / % if dic["pvMult"] == 0 or dic['grid']== 'core': WCONPROD @@ -233,9 +232,17 @@ WCONPROD 'PRO3' ${'OPEN' if dic['inj'][j][4] > 0 else 'SHUT'} 'BHP' 5* ${dic['pressure']}/ % else: 'PRO0' ${'OPEN' if dic['inj'][j][4] > 0 else 'SHUT'} 'BHP' 5* ${dic['pressure']}/ -%endif +% endif / -%endif +% endif +-- Close the specified connections +% if len(dic['inj'][j]) >= 6: +WELOPEN +% for i in range(1, dic['noCells'][2] + 1): +'INJ0' ${'SHUT' if i in dic['inj'][j][5:] else 'OPEN'} 0 0 ${i} / +% endfor +/ +% endif TSTEP ${mt.floor(dic['inj'][j][0]/dic['inj'][j][1])}*${dic['inj'][j][1]} / diff --git a/src/pyopmnearwell/templates/standardwell/base.hpp b/src/pyopmnearwell/templates/standardwell/base.hpp deleted file mode 100644 index c15e536..0000000 --- a/src/pyopmnearwell/templates/standardwell/base.hpp +++ /dev/null @@ -1,513 +0,0 @@ -/* - Copyright 2017 SINTEF Digital, Mathematics and Cybernetics. - Copyright 2017 Statoil ASA. - Copyright 2016 - 2017 IRIS AS. - - This file is part of the Open Porous Media project (OPM). - - OPM is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - OPM is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with OPM. If not, see . -*/ - - -#ifndef OPM_STANDARDWELL_HEADER_INCLUDED -#define OPM_STANDARDWELL_HEADER_INCLUDED - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include -#include - -#include - -#include -#include - -#include -#include - -namespace Opm -{ - - template - class StandardWell : public WellInterface - , public StandardWellEval, - GetPropType, - GetPropType> - { - - public: - using Base = WellInterface; - using StdWellEval = StandardWellEval, - GetPropType, - GetPropType>; - - // TODO: some functions working with AD variables handles only with values (double) without - // dealing with derivatives. It can be beneficial to make functions can work with either AD or scalar value. - // And also, it can also be beneficial to make these functions hanle different types of AD variables. - using typename Base::Simulator; - using typename Base::IntensiveQuantities; - using typename Base::FluidSystem; - using typename Base::MaterialLaw; - using typename Base::ModelParameters; - using typename Base::Indices; - using typename Base::RateConverterType; - using typename Base::SparseMatrixAdapter; - using typename Base::FluidState; - using typename Base::RateVector; - - using Base::has_solvent; - using Base::has_zFraction; - using Base::has_polymer; - using Base::has_polymermw; - using Base::has_foam; - using Base::has_brine; - using Base::has_energy; - using Base::has_micp; - - using PolymerModule = BlackOilPolymerModule; - using FoamModule = BlackOilFoamModule; - using BrineModule = BlackOilBrineModule; - using typename Base::PressureMatrix; - - // number of the conservation equations - static constexpr int numWellConservationEq = Indices::numPhases + Indices::numSolvents; - // number of the well control equations - static constexpr int numWellControlEq = 1; - // number of the well equations that will always be used - // based on the solution strategy, there might be other well equations be introduced - static constexpr int numStaticWellEq = numWellConservationEq + numWellControlEq; - - // the index for Bhp in primary variables and also the index of well control equation - // they both will be the last one in their respective system. - // TODO: we should have indices for the well equations and well primary variables separately - static constexpr int Bhp = numStaticWellEq - numWellControlEq; - - using StdWellEval::WQTotal; - - using typename Base::Scalar; - - - using Base::name; - using Base::Water; - using Base::Oil; - using Base::Gas; - - using typename Base::BVector; - - using Eval = typename StdWellEval::Eval; - using EvalWell = typename StdWellEval::EvalWell; - using BVectorWell = typename StdWellEval::BVectorWell; - - StandardWell(const Well& well, - const ParallelWellInfo& pw_info, - const int time_step, - const ModelParameters& param, - const RateConverterType& rate_converter, - const int pvtRegionIdx, - const int num_components, - const int num_phases, - const int index_of_well, - const std::vector& perf_data); - - virtual void init(const PhaseUsage* phase_usage_arg, - const std::vector& depth_arg, - const double gravity_arg, - const int num_cells, - const std::vector< Scalar >& B_avg, - const bool changed_to_open_this_step) override; - - - void initPrimaryVariablesEvaluation() override; - - /// check whether the well equations get converged for this well - virtual ConvergenceReport getWellConvergence(const SummaryState& summary_state, - const WellState& well_state, - const std::vector& B_avg, - DeferredLogger& deferred_logger, - const bool relax_tolerance) const override; - - /// Ax = Ax - C D^-1 B x - virtual void apply(const BVector& x, BVector& Ax) const override; - /// r = r - C D^-1 Rw - virtual void apply(BVector& r) const override; - - /// using the solution x to recover the solution xw for wells and applying - /// xw to update Well State - void recoverWellSolutionAndUpdateWellState(const SummaryState& summary_state, - const BVector& x, - WellState& well_state, - DeferredLogger& deferred_logger) override; - - /// computing the well potentials for group control - virtual void computeWellPotentials(const Simulator& ebosSimulator, - const WellState& well_state, - std::vector& well_potentials, - DeferredLogger& deferred_logger) /* const */ override; - - void updatePrimaryVariables(const SummaryState& summary_state, - const WellState& well_state, - DeferredLogger& deferred_logger) override; - - virtual void solveEqAndUpdateWellState(const SummaryState& summary_state, - WellState& well_state, - DeferredLogger& deferred_logger) override; - - virtual void calculateExplicitQuantities(const Simulator& ebosSimulator, - const WellState& well_state, - DeferredLogger& deferred_logger) override; // should be const? - - virtual void updateProductivityIndex(const Simulator& ebosSimulator, - const WellProdIndexCalculator& wellPICalc, - WellState& well_state, - DeferredLogger& deferred_logger) const override; - - virtual double connectionDensity(const int globalConnIdx, - const int openConnIdx) const override; - - virtual void addWellContributions(SparseMatrixAdapter& mat) const override; - - virtual void addWellPressureEquations(PressureMatrix& mat, - const BVector& x, - const int pressureVarIndex, - const bool use_well_weights, - const WellState& well_state) const override; - - // iterate well equations with the specified control until converged - bool iterateWellEqWithControl(const Simulator& ebosSimulator, - const double dt, - const Well::InjectionControls& inj_controls, - const Well::ProductionControls& prod_controls, - WellState& well_state, - const GroupState& group_state, - DeferredLogger& deferred_logger) override; - - /// \brief Wether the Jacobian will also have well contributions in it. - virtual bool jacobianContainsWellContributions() const override - { - return this->param_.matrix_add_well_contributions_; - } - - /* returns BHP */ - double computeWellRatesAndBhpWithThpAlqProd(const Simulator &ebos_simulator, - const SummaryState &summary_state, - DeferredLogger &deferred_logger, - std::vector &potentials, - double alq) const; - - void computeWellRatesWithThpAlqProd( - const Simulator &ebos_simulator, - const SummaryState &summary_state, - DeferredLogger &deferred_logger, - std::vector &potentials, - double alq) const; - - std::optional computeBhpAtThpLimitProdWithAlq( - const Simulator& ebos_simulator, - const SummaryState& summary_state, - const double alq_value, - DeferredLogger& deferred_logger) const override; - - virtual void computeWellRatesWithBhp( - const Simulator& ebosSimulator, - const double& bhp, - std::vector& well_flux, - DeferredLogger& deferred_logger) const override; - - // NOTE: These cannot be protected since they are used by GasLiftRuntime - using Base::phaseUsage; - using Base::vfp_properties_; - - virtual std::vector computeCurrentWellRates(const Simulator& ebosSimulator, - DeferredLogger& deferred_logger) const override; - - std::vector getPrimaryVars() const override; - - int setPrimaryVars(std::vector::const_iterator it) override; - - protected: - bool regularize_; - - // updating the well_state based on well solution dwells - void updateWellState(const SummaryState& summary_state, - const BVectorWell& dwells, - WellState& well_state, - DeferredLogger& deferred_logger); - - // calculate the properties for the well connections - // to calulate the pressure difference between well connections. - using WellConnectionProps = typename StdWellEval::StdWellConnections::Properties; - void computePropertiesForWellConnectionPressures(const Simulator& ebosSimulator, - const WellState& well_state, - WellConnectionProps& props) const; - - void computeWellConnectionDensitesPressures(const Simulator& ebosSimulator, - const WellState& well_state, - const WellConnectionProps& props, - DeferredLogger& deferred_logger); - - void computeWellConnectionPressures(const Simulator& ebosSimulator, - const WellState& well_state, - DeferredLogger& deferred_logger); - - template - void computePerfRate(const IntensiveQuantities& intQuants, - const std::vector& mob, - const Value& bhp, - const double Tw, - const int perf, - const bool allow_cf, - const double elapsed_time, - std::vector& cq_s, - PerforationRates& perf_rates, - DeferredLogger& deferred_logger) const; - - template - void computePerfRate(const std::vector& mob, - const Value& pressure, - const Value& bhp, - const Value& rs, - const Value& rv, - const Value& rvw, - const Value& rsw, - std::vector& b_perfcells_dense, - const double Tw, - const int perf, - const bool allow_cf, - const Value& skin_pressure, - const std::vector& cmix_s, - const double elapsed_time, - std::vector& cq_s, - PerforationRates& perf_rates, - DeferredLogger& deferred_logger) const; - - void computeWellRatesWithBhpIterations(const Simulator& ebosSimulator, - const double& bhp, - std::vector& well_flux, - DeferredLogger& deferred_logger) const override; - - std::vector computeWellPotentialWithTHP( - const Simulator& ebosSimulator, - DeferredLogger& deferred_logger, - const WellState &well_state) const; - - virtual double getRefDensity() const override; - - // get the mobility for specific perforation - template - void getMobility(const Simulator& ebosSimulator, - const int perf, - std::vector& mob, - DeferredLogger& deferred_logger) const; - - void updateWaterMobilityWithPolymer(const Simulator& ebos_simulator, - const int perf, - std::vector& mob_water, - DeferredLogger& deferred_logger) const; - - void updatePrimaryVariablesNewton(const BVectorWell& dwells, - const bool stop_or_zero_rate_target, - DeferredLogger& deferred_logger); - - void updateWellStateFromPrimaryVariables(const bool stop_or_zero_rate_target, - WellState& well_state, - const SummaryState& summary_state, - DeferredLogger& deferred_logger) const; - - virtual void assembleWellEqWithoutIteration(const Simulator& ebosSimulator, - const double dt, - const Well::InjectionControls& inj_controls, - const Well::ProductionControls& prod_controls, - WellState& well_state, - const GroupState& group_state, - DeferredLogger& deferred_logger) override; - - void assembleWellEqWithoutIterationImpl(const Simulator& ebosSimulator, - const double dt, - const Well::InjectionControls& inj_controls, - const Well::ProductionControls& prod_controls, - WellState& well_state, - const GroupState& group_state, - DeferredLogger& deferred_logger); - - void calculateSinglePerf(const Simulator& ebosSimulator, - const int perf, - WellState& well_state, - std::vector& connectionRates, - std::vector& cq_s, - EvalWell& water_flux_s, - EvalWell& cq_s_zfrac_effective, - DeferredLogger& deferred_logger) const; - - // check whether the well is operable under BHP limit with current reservoir condition - virtual void checkOperabilityUnderBHPLimit(const WellState& well_state, const Simulator& ebos_simulator, DeferredLogger& deferred_logger) override; - - // check whether the well is operable under THP limit with current reservoir condition - virtual void checkOperabilityUnderTHPLimit(const Simulator& ebos_simulator, const WellState& well_state, DeferredLogger& deferred_logger) override; - - // updating the inflow based on the current reservoir condition - virtual void updateIPR(const Simulator& ebos_simulator, DeferredLogger& deferred_logger) const override; - - // for a well, when all drawdown are in the wrong direction, then this well will not - // be able to produce/inject . - bool allDrawDownWrongDirection(const Simulator& ebos_simulator) const; - - // whether the well can produce / inject based on the current well state (bhp) - bool canProduceInjectWithCurrentBhp(const Simulator& ebos_simulator, - const WellState& well_state, - DeferredLogger& deferred_logger); - - // turn on crossflow to avoid singular well equations - // when the well is banned from cross-flow and the BHP is not properly initialized, - // we turn on crossflow to avoid singular well equations. It can result in wrong-signed - // well rates, it can cause problem for THP calculation - // TODO: looking for better alternative to avoid wrong-signed well rates - bool openCrossFlowAvoidSingularity(const Simulator& ebos_simulator) const; - - // calculate the skin pressure based on water velocity, throughput and polymer concentration. - // throughput is used to describe the formation damage during water/polymer injection. - // calculated skin pressure will be applied to the drawdown during perforation rate calculation - // to handle the effect from formation damage. - EvalWell pskin(const double throuhgput, - const EvalWell& water_velocity, - const EvalWell& poly_inj_conc, - DeferredLogger& deferred_logger) const; - - // calculate the skin pressure based on water velocity, throughput during water injection. - EvalWell pskinwater(const double throughput, - const EvalWell& water_velocity, - DeferredLogger& deferred_logger) const; - - // calculate the injecting polymer molecular weight based on the througput and water velocity - EvalWell wpolymermw(const double throughput, - const EvalWell& water_velocity, - DeferredLogger& deferred_logger) const; - - // modify the water rate for polymer injectivity study - void handleInjectivityRate(const Simulator& ebosSimulator, - const int perf, - std::vector& cq_s) const; - - // handle the extra equations for polymer injectivity study - void handleInjectivityEquations(const Simulator& ebosSimulator, - const WellState& well_state, - const int perf, - const EvalWell& water_flux_s, - DeferredLogger& deferred_logger); - - virtual void updateWaterThroughput(const double dt, WellState& well_state) const override; - - // checking convergence of extra equations, if there are any - void checkConvergenceExtraEqs(const std::vector& res, - ConvergenceReport& report) const; - - // updating the connectionRates_ related polymer molecular weight - void updateConnectionRatePolyMW(const EvalWell& cq_s_poly, - const IntensiveQuantities& int_quants, - const WellState& well_state, - const int perf, - std::vector& connectionRates, - DeferredLogger& deferred_logger) const; - - - std::optional computeBhpAtThpLimitProd(const WellState& well_state, - const Simulator& ebos_simulator, - const SummaryState& summary_state, - DeferredLogger& deferred_logger) const; - - std::optional computeBhpAtThpLimitInj(const Simulator& ebos_simulator, - const SummaryState& summary_state, - DeferredLogger& deferred_logger) const; - template - Value wellIndexEval(const int perf, const double elapsed_time, const Value& pressure) const; - - private: - - template - Value scaleFunction(Value X, double min, double max) const; - - template - Value unscaleFunction(Value X, double min, double max) const; - - Eval connectionRateEnergy(const double maxOilSaturation, - const std::vector& cq_s, - const IntensiveQuantities& intQuants, - DeferredLogger& deferred_logger) const; - - template - void gasOilPerfRateInj(const std::vector& cq_s, - PerforationRates& perf_rates, - const Value& rv, - const Value& rs, - const Value& pressure, - const Value& rvw, - DeferredLogger& deferred_logger) const; - - template - void gasOilPerfRateProd(std::vector& cq_s, - PerforationRates& perf_rates, - const Value& rv, - const Value& rs, - const Value& rvw) const; - - template - void gasWaterPerfRateProd(std::vector& cq_s, - PerforationRates& perf_rates, - const Value& rvw, - const Value& rsw) const; - - template - void gasWaterPerfRateInj(const std::vector& cq_s, - PerforationRates& perf_rates, - const Value& rvw, - const Value& rsw, - const Value& pressure, - DeferredLogger& deferred_logger) const; - - template - void disOilVapWatVolumeRatio(Value& volumeRatio, - const Value& rvw, - const Value& rsw, - const Value& pressure, - const std::vector& cmix_s, - const std::vector& b_perfcells_dense, - DeferredLogger& deferred_logger) const; - - template - void gasOilVolumeRatio(Value& volumeRatio, - const Value& rv, - const Value& rs, - const Value& pressure, - const std::vector& cmix_s, - const std::vector& b_perfcells_dense, - DeferredLogger& deferred_logger) const; - }; - -} - -#include "StandardWell_impl.hpp" - -#endif // OPM_STANDARDWELL_HEADER_INCLUDED diff --git a/src/pyopmnearwell/templates/standardwell/local_stencil.hpp b/src/pyopmnearwell/templates/standardwell/local_stencil.hpp deleted file mode 100644 index 4b8bbfc..0000000 --- a/src/pyopmnearwell/templates/standardwell/local_stencil.hpp +++ /dev/null @@ -1,515 +0,0 @@ -/* - Copyright 2017 SINTEF Digital, Mathematics and Cybernetics. - Copyright 2017 Statoil ASA. - Copyright 2016 - 2017 IRIS AS. - - This file is part of the Open Porous Media project (OPM). - - OPM is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - OPM is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with OPM. If not, see . -*/ - - -#ifndef OPM_STANDARDWELL_HEADER_INCLUDED -#define OPM_STANDARDWELL_HEADER_INCLUDED - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include -#include - -#include - -#include -#include - -#include -#include - -namespace Opm -{ - - template - class StandardWell : public WellInterface - , public StandardWellEval, - GetPropType, - GetPropType> - { - - public: - using Base = WellInterface; - using StdWellEval = StandardWellEval, - GetPropType, - GetPropType>; - - // TODO: some functions working with AD variables handles only with values (double) without - // dealing with derivatives. It can be beneficial to make functions can work with either AD or scalar value. - // And also, it can also be beneficial to make these functions hanle different types of AD variables. - using typename Base::Simulator; - using typename Base::IntensiveQuantities; - using typename Base::FluidSystem; - using typename Base::MaterialLaw; - using typename Base::ModelParameters; - using typename Base::Indices; - using typename Base::RateConverterType; - using typename Base::SparseMatrixAdapter; - using typename Base::FluidState; - using typename Base::RateVector; - - using Base::has_solvent; - using Base::has_zFraction; - using Base::has_polymer; - using Base::has_polymermw; - using Base::has_foam; - using Base::has_brine; - using Base::has_energy; - using Base::has_micp; - - using PolymerModule = BlackOilPolymerModule; - using FoamModule = BlackOilFoamModule; - using BrineModule = BlackOilBrineModule; - using typename Base::PressureMatrix; - - // number of the conservation equations - static constexpr int numWellConservationEq = Indices::numPhases + Indices::numSolvents; - // number of the well control equations - static constexpr int numWellControlEq = 1; - // number of the well equations that will always be used - // based on the solution strategy, there might be other well equations be introduced - static constexpr int numStaticWellEq = numWellConservationEq + numWellControlEq; - - // the index for Bhp in primary variables and also the index of well control equation - // they both will be the last one in their respective system. - // TODO: we should have indices for the well equations and well primary variables separately - static constexpr int Bhp = numStaticWellEq - numWellControlEq; - - using StdWellEval::WQTotal; - - using typename Base::Scalar; - - - using Base::name; - using Base::Water; - using Base::Oil; - using Base::Gas; - - using typename Base::BVector; - - using Eval = typename StdWellEval::Eval; - using EvalWell = typename StdWellEval::EvalWell; - using BVectorWell = typename StdWellEval::BVectorWell; - - StandardWell(const Well& well, - const ParallelWellInfo& pw_info, - const int time_step, - const ModelParameters& param, - const RateConverterType& rate_converter, - const int pvtRegionIdx, - const int num_components, - const int num_phases, - const int index_of_well, - const std::vector& perf_data); - - virtual void init(const PhaseUsage* phase_usage_arg, - const std::vector& depth_arg, - const double gravity_arg, - const int num_cells, - const std::vector< Scalar >& B_avg, - const bool changed_to_open_this_step) override; - - - void initPrimaryVariablesEvaluation() override; - - /// check whether the well equations get converged for this well - virtual ConvergenceReport getWellConvergence(const SummaryState& summary_state, - const WellState& well_state, - const std::vector& B_avg, - DeferredLogger& deferred_logger, - const bool relax_tolerance) const override; - - /// Ax = Ax - C D^-1 B x - virtual void apply(const BVector& x, BVector& Ax) const override; - /// r = r - C D^-1 Rw - virtual void apply(BVector& r) const override; - - /// using the solution x to recover the solution xw for wells and applying - /// xw to update Well State - void recoverWellSolutionAndUpdateWellState(const SummaryState& summary_state, - const BVector& x, - WellState& well_state, - DeferredLogger& deferred_logger) override; - - /// computing the well potentials for group control - virtual void computeWellPotentials(const Simulator& ebosSimulator, - const WellState& well_state, - std::vector& well_potentials, - DeferredLogger& deferred_logger) /* const */ override; - - void updatePrimaryVariables(const SummaryState& summary_state, - const WellState& well_state, - DeferredLogger& deferred_logger) override; - - virtual void solveEqAndUpdateWellState(const SummaryState& summary_state, - WellState& well_state, - DeferredLogger& deferred_logger) override; - - virtual void calculateExplicitQuantities(const Simulator& ebosSimulator, - const WellState& well_state, - DeferredLogger& deferred_logger) override; // should be const? - - virtual void updateProductivityIndex(const Simulator& ebosSimulator, - const WellProdIndexCalculator& wellPICalc, - WellState& well_state, - DeferredLogger& deferred_logger) const override; - - virtual double connectionDensity(const int globalConnIdx, - const int openConnIdx) const override; - - virtual void addWellContributions(SparseMatrixAdapter& mat) const override; - - virtual void addWellPressureEquations(PressureMatrix& mat, - const BVector& x, - const int pressureVarIndex, - const bool use_well_weights, - const WellState& well_state) const override; - - // iterate well equations with the specified control until converged - bool iterateWellEqWithControl(const Simulator& ebosSimulator, - const double dt, - const Well::InjectionControls& inj_controls, - const Well::ProductionControls& prod_controls, - WellState& well_state, - const GroupState& group_state, - DeferredLogger& deferred_logger) override; - - /// \brief Wether the Jacobian will also have well contributions in it. - virtual bool jacobianContainsWellContributions() const override - { - return this->param_.matrix_add_well_contributions_; - } - - /* returns BHP */ - double computeWellRatesAndBhpWithThpAlqProd(const Simulator &ebos_simulator, - const SummaryState &summary_state, - DeferredLogger &deferred_logger, - std::vector &potentials, - double alq) const; - - void computeWellRatesWithThpAlqProd( - const Simulator &ebos_simulator, - const SummaryState &summary_state, - DeferredLogger &deferred_logger, - std::vector &potentials, - double alq) const; - - std::optional computeBhpAtThpLimitProdWithAlq( - const Simulator& ebos_simulator, - const SummaryState& summary_state, - const double alq_value, - DeferredLogger& deferred_logger) const override; - - virtual void computeWellRatesWithBhp( - const Simulator& ebosSimulator, - const double& bhp, - std::vector& well_flux, - DeferredLogger& deferred_logger) const override; - - // NOTE: These cannot be protected since they are used by GasLiftRuntime - using Base::phaseUsage; - using Base::vfp_properties_; - - virtual std::vector computeCurrentWellRates(const Simulator& ebosSimulator, - DeferredLogger& deferred_logger) const override; - - std::vector getPrimaryVars() const override; - - int setPrimaryVars(std::vector::const_iterator it) override; - - protected: - bool regularize_; - - // updating the well_state based on well solution dwells - void updateWellState(const SummaryState& summary_state, - const BVectorWell& dwells, - WellState& well_state, - DeferredLogger& deferred_logger); - - // calculate the properties for the well connections - // to calulate the pressure difference between well connections. - using WellConnectionProps = typename StdWellEval::StdWellConnections::Properties; - void computePropertiesForWellConnectionPressures(const Simulator& ebosSimulator, - const WellState& well_state, - WellConnectionProps& props) const; - - void computeWellConnectionDensitesPressures(const Simulator& ebosSimulator, - const WellState& well_state, - const WellConnectionProps& props, - DeferredLogger& deferred_logger); - - void computeWellConnectionPressures(const Simulator& ebosSimulator, - const WellState& well_state, - DeferredLogger& deferred_logger); - - template - void computePerfRate(const IntensiveQuantities& intQuants, - const std::vector& mob, - const Value& bhp, - const double Tw, - const int perf, - const bool allow_cf, - const double elapsed_time, - std::vector& cq_s, - PerforationRates& perf_rates, - DeferredLogger& deferred_logger, - const Simulator& ebosSimulator) const; - - template - void computePerfRate(const std::vector& mob, - const Value& pressure, - const Value& bhp, - const Value& rs, - const Value& rv, - const Value& rvw, - const Value& rsw, - std::vector& b_perfcells_dense, - const double Tw, - const int perf, - const bool allow_cf, - const Value& skin_pressure, - const std::vector& cmix_s, - const double elapsed_time, - std::vector& cq_s, - PerforationRates& perf_rates, - DeferredLogger& deferred_logger, - const Simulator& ebosSimulator) const; - - void computeWellRatesWithBhpIterations(const Simulator& ebosSimulator, - const double& bhp, - std::vector& well_flux, - DeferredLogger& deferred_logger) const override; - - std::vector computeWellPotentialWithTHP( - const Simulator& ebosSimulator, - DeferredLogger& deferred_logger, - const WellState &well_state) const; - - virtual double getRefDensity() const override; - - // get the mobility for specific perforation - template - void getMobility(const Simulator& ebosSimulator, - const int perf, - std::vector& mob, - DeferredLogger& deferred_logger) const; - - void updateWaterMobilityWithPolymer(const Simulator& ebos_simulator, - const int perf, - std::vector& mob_water, - DeferredLogger& deferred_logger) const; - - void updatePrimaryVariablesNewton(const BVectorWell& dwells, - const bool stop_or_zero_rate_target, - DeferredLogger& deferred_logger); - - void updateWellStateFromPrimaryVariables(const bool stop_or_zero_rate_target, - WellState& well_state, - const SummaryState& summary_state, - DeferredLogger& deferred_logger) const; - - virtual void assembleWellEqWithoutIteration(const Simulator& ebosSimulator, - const double dt, - const Well::InjectionControls& inj_controls, - const Well::ProductionControls& prod_controls, - WellState& well_state, - const GroupState& group_state, - DeferredLogger& deferred_logger) override; - - void assembleWellEqWithoutIterationImpl(const Simulator& ebosSimulator, - const double dt, - const Well::InjectionControls& inj_controls, - const Well::ProductionControls& prod_controls, - WellState& well_state, - const GroupState& group_state, - DeferredLogger& deferred_logger); - - void calculateSinglePerf(const Simulator& ebosSimulator, - const int perf, - WellState& well_state, - std::vector& connectionRates, - std::vector& cq_s, - EvalWell& water_flux_s, - EvalWell& cq_s_zfrac_effective, - DeferredLogger& deferred_logger) const; - - // check whether the well is operable under BHP limit with current reservoir condition - virtual void checkOperabilityUnderBHPLimit(const WellState& well_state, const Simulator& ebos_simulator, DeferredLogger& deferred_logger) override; - - // check whether the well is operable under THP limit with current reservoir condition - virtual void checkOperabilityUnderTHPLimit(const Simulator& ebos_simulator, const WellState& well_state, DeferredLogger& deferred_logger) override; - - // updating the inflow based on the current reservoir condition - virtual void updateIPR(const Simulator& ebos_simulator, DeferredLogger& deferred_logger) const override; - - // for a well, when all drawdown are in the wrong direction, then this well will not - // be able to produce/inject . - bool allDrawDownWrongDirection(const Simulator& ebos_simulator) const; - - // whether the well can produce / inject based on the current well state (bhp) - bool canProduceInjectWithCurrentBhp(const Simulator& ebos_simulator, - const WellState& well_state, - DeferredLogger& deferred_logger); - - // turn on crossflow to avoid singular well equations - // when the well is banned from cross-flow and the BHP is not properly initialized, - // we turn on crossflow to avoid singular well equations. It can result in wrong-signed - // well rates, it can cause problem for THP calculation - // TODO: looking for better alternative to avoid wrong-signed well rates - bool openCrossFlowAvoidSingularity(const Simulator& ebos_simulator) const; - - // calculate the skin pressure based on water velocity, throughput and polymer concentration. - // throughput is used to describe the formation damage during water/polymer injection. - // calculated skin pressure will be applied to the drawdown during perforation rate calculation - // to handle the effect from formation damage. - EvalWell pskin(const double throuhgput, - const EvalWell& water_velocity, - const EvalWell& poly_inj_conc, - DeferredLogger& deferred_logger) const; - - // calculate the skin pressure based on water velocity, throughput during water injection. - EvalWell pskinwater(const double throughput, - const EvalWell& water_velocity, - DeferredLogger& deferred_logger) const; - - // calculate the injecting polymer molecular weight based on the througput and water velocity - EvalWell wpolymermw(const double throughput, - const EvalWell& water_velocity, - DeferredLogger& deferred_logger) const; - - // modify the water rate for polymer injectivity study - void handleInjectivityRate(const Simulator& ebosSimulator, - const int perf, - std::vector& cq_s) const; - - // handle the extra equations for polymer injectivity study - void handleInjectivityEquations(const Simulator& ebosSimulator, - const WellState& well_state, - const int perf, - const EvalWell& water_flux_s, - DeferredLogger& deferred_logger); - - virtual void updateWaterThroughput(const double dt, WellState& well_state) const override; - - // checking convergence of extra equations, if there are any - void checkConvergenceExtraEqs(const std::vector& res, - ConvergenceReport& report) const; - - // updating the connectionRates_ related polymer molecular weight - void updateConnectionRatePolyMW(const EvalWell& cq_s_poly, - const IntensiveQuantities& int_quants, - const WellState& well_state, - const int perf, - std::vector& connectionRates, - DeferredLogger& deferred_logger) const; - - - std::optional computeBhpAtThpLimitProd(const WellState& well_state, - const Simulator& ebos_simulator, - const SummaryState& summary_state, - DeferredLogger& deferred_logger) const; - - std::optional computeBhpAtThpLimitInj(const Simulator& ebos_simulator, - const SummaryState& summary_state, - DeferredLogger& deferred_logger) const; - template - Value wellIndexEval(const int perf, const Simulator& ebosSimulator) const; - - private: - - template - Value scaleFunction(Value X, double min, double max, double range_min, double range_max) const; - - template - Value unscaleFunction(Value X, double min, double max, double range_min, double range_max) const; - - Eval connectionRateEnergy(const double maxOilSaturation, - const std::vector& cq_s, - const IntensiveQuantities& intQuants, - DeferredLogger& deferred_logger) const; - - template - void gasOilPerfRateInj(const std::vector& cq_s, - PerforationRates& perf_rates, - const Value& rv, - const Value& rs, - const Value& pressure, - const Value& rvw, - DeferredLogger& deferred_logger) const; - - template - void gasOilPerfRateProd(std::vector& cq_s, - PerforationRates& perf_rates, - const Value& rv, - const Value& rs, - const Value& rvw) const; - - template - void gasWaterPerfRateProd(std::vector& cq_s, - PerforationRates& perf_rates, - const Value& rvw, - const Value& rsw) const; - - template - void gasWaterPerfRateInj(const std::vector& cq_s, - PerforationRates& perf_rates, - const Value& rvw, - const Value& rsw, - const Value& pressure, - DeferredLogger& deferred_logger) const; - - template - void disOilVapWatVolumeRatio(Value& volumeRatio, - const Value& rvw, - const Value& rsw, - const Value& pressure, - const std::vector& cmix_s, - const std::vector& b_perfcells_dense, - DeferredLogger& deferred_logger) const; - - template - void gasOilVolumeRatio(Value& volumeRatio, - const Value& rv, - const Value& rs, - const Value& pressure, - const std::vector& cmix_s, - const std::vector& b_perfcells_dense, - DeferredLogger& deferred_logger) const; - }; - -} - -#include "StandardWell_impl.hpp" - -#endif // OPM_STANDARDWELL_HEADER_INCLUDED diff --git a/src/pyopmnearwell/templates/standardwell_impl/co2_3_inputs.mako b/src/pyopmnearwell/templates/standardwell_impl/co2_3_inputs.mako deleted file mode 100644 index 8bd3b0a..0000000 --- a/src/pyopmnearwell/templates/standardwell_impl/co2_3_inputs.mako +++ /dev/null @@ -1,2583 +0,0 @@ -/* - Copyright 2017 SINTEF Digital, Mathematics and Cybernetics. - Copyright 2017 Statoil ASA. - Copyright 2016 - 2017 IRIS AS. - - This file is part of the Open Porous Media project (OPM). - - OPM is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - OPM is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with OPM. If not, see . -*/ - -#include - -#include - -#include - -#include -#include -#include -#include -#include - -#include - -#include - -#include -#include -#include - - -namespace { - -template -auto dValueError(const dValue& d, - const std::string& name, - const std::string& methodName, - const Value& Rs, - const Value& Rv, - const Value& pressure) -{ - return fmt::format("Problematic d value {} obtained for well {}" - " during {} calculations with rs {}" - ", rv {} and pressure {}." - " Continue as if no dissolution (rs = 0) and vaporization (rv = 0) " - " for this connection.", d, name, methodName, Rs, Rv, pressure); -} - -} - -namespace Opm -{ - - template - StandardWell:: - StandardWell(const Well& well, - const ParallelWellInfo& pw_info, - const int time_step, - const ModelParameters& param, - const RateConverterType& rate_converter, - const int pvtRegionIdx, - const int num_components, - const int num_phases, - const int index_of_well, - const std::vector& perf_data) - : Base(well, pw_info, time_step, param, rate_converter, pvtRegionIdx, num_components, num_phases, index_of_well, perf_data) - , StdWellEval(static_cast&>(*this)) - , regularize_(false) - { - assert(this->num_components_ == numWellConservationEq); - } - - - - - - template - void - StandardWell:: - init(const PhaseUsage* phase_usage_arg, - const std::vector& depth_arg, - const double gravity_arg, - const int num_cells, - const std::vector< Scalar >& B_avg, - const bool changed_to_open_this_step) - { - Base::init(phase_usage_arg, depth_arg, gravity_arg, num_cells, B_avg, changed_to_open_this_step); - this->StdWellEval::init(this->perf_depth_, depth_arg, num_cells, Base::has_polymermw); - } - - - - - - template - void StandardWell:: - initPrimaryVariablesEvaluation() - { - this->primary_variables_.init(); - } - - - - - - template - template - void - StandardWell:: - computePerfRate(const IntensiveQuantities& intQuants, - const std::vector& mob, - const Value& bhp, - const double Tw, - const int perf, - const bool allow_cf, - const double elapsed_time, - std::vector& cq_s, - PerforationRates& perf_rates, - DeferredLogger& deferred_logger) const - { - auto obtain = [this](const Eval& value) - { - if constexpr (std::is_same_v) { - static_cast(this); // suppress clang warning - return getValue(value); - } else { - return this->extendEval(value); - } - }; - auto obtainN = [](const auto& value) - { - if constexpr (std::is_same_v) { - return getValue(value); - } else { - return value; - } - }; - auto zeroElem = [this]() - { - if constexpr (std::is_same_v) { - static_cast(this); // suppress clang warning - return 0.0; - } else { - return Value{this->primary_variables_.numWellEq() + Indices::numEq, 0.0}; - } - }; - - const auto& fs = intQuants.fluidState(); - const Value pressure = obtain(this->getPerfCellPressure(fs)); - const Value rs = obtain(fs.Rs()); - const Value rv = obtain(fs.Rv()); - const Value rvw = obtain(fs.Rvw()); - const Value rsw = obtain(fs.Rsw()); - - std::vector b_perfcells_dense(this->numComponents(), zeroElem()); - for (unsigned phaseIdx = 0; phaseIdx < FluidSystem::numPhases; ++phaseIdx) { - if (!FluidSystem::phaseIsActive(phaseIdx)) { - continue; - } - const unsigned compIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::solventComponentIndex(phaseIdx)); - b_perfcells_dense[compIdx] = obtain(fs.invB(phaseIdx)); - } - if constexpr (has_solvent) { - b_perfcells_dense[Indices::contiSolventEqIdx] = obtain(intQuants.solventInverseFormationVolumeFactor()); - } - - if constexpr (has_zFraction) { - if (this->isInjector()) { - const unsigned gasCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::gasCompIdx); - b_perfcells_dense[gasCompIdx] *= (1.0 - this->wsolvent()); - b_perfcells_dense[gasCompIdx] += this->wsolvent()*intQuants.zPureInvFormationVolumeFactor().value(); - } - } - - Value skin_pressure = zeroElem(); - if (has_polymermw) { - if (this->isInjector()) { - const int pskin_index = Bhp + 1 + this->numPerfs() + perf; - skin_pressure = obtainN(this->primary_variables_.eval(pskin_index)); - } - } - - // surface volume fraction of fluids within wellbore - std::vector cmix_s(this->numComponents(), zeroElem()); - for (int componentIdx = 0; componentIdx < this->numComponents(); ++componentIdx) { - cmix_s[componentIdx] = obtainN(this->primary_variables_.surfaceVolumeFraction(componentIdx)); - } - - computePerfRate(mob, - pressure, - bhp, - rs, - rv, - rvw, - rsw, - b_perfcells_dense, - Tw, - perf, - allow_cf, - skin_pressure, - cmix_s, - elapsed_time, - cq_s, - perf_rates, - deferred_logger); - } - - template - template - void - StandardWell:: - computePerfRate(const std::vector& mob, - const Value& pressure, - const Value& bhp, - const Value& rs, - const Value& rv, - const Value& rvw, - const Value& rsw, - std::vector& b_perfcells_dense, - const double Tw, - const int perf, - const bool allow_cf, - const Value& skin_pressure, - const std::vector& cmix_s, - const double elapsed_time, - std::vector& cq_s, - PerforationRates& perf_rates, - DeferredLogger& deferred_logger) const - { - // Pressure drawdown (also used to determine direction of flow) - const Value well_pressure = bhp + this->connections_.pressure_diff(perf); - Value drawdown = pressure - well_pressure; - if (this->isInjector()) { - drawdown += skin_pressure; - } - - // producing perforations - if (drawdown > 0) { - // Do nothing if crossflow is not allowed - if (!allow_cf && this->isInjector()) { - return; - } - - // compute component volumetric rates at standard conditions - for (int componentIdx = 0; componentIdx < this->numComponents(); ++componentIdx) { - const Value cq_p = - Tw * (mob[componentIdx] * drawdown); - cq_s[componentIdx] = b_perfcells_dense[componentIdx] * cq_p; - } - - if (FluidSystem::phaseIsActive(FluidSystem::oilPhaseIdx) && FluidSystem::phaseIsActive(FluidSystem::gasPhaseIdx)) { - gasOilPerfRateProd(cq_s, perf_rates, rv, rs, rvw); - } else if (FluidSystem::phaseIsActive(FluidSystem::waterPhaseIdx) && FluidSystem::phaseIsActive(FluidSystem::gasPhaseIdx)) { - gasWaterPerfRateProd(cq_s, perf_rates, rvw, rsw); - } - } else { - // Do nothing if crossflow is not allowed - if (!allow_cf && this->isProducer()) { - return; - } - - // Using total mobilities - Value total_mob_dense = mob[0]; - for (int componentIdx = 1; componentIdx < this->numComponents(); ++componentIdx) { - total_mob_dense += mob[componentIdx]; - } - - // Use well index from ML - if (!Base::ml_wi_filename_.empty()) { - Value WI; - if (allow_cf) { - OPM_DEFLOG_THROW(std::runtime_error, - fmt::format("ML well index only implemented with no cross flow. Well {}", name()), - deferred_logger); - } - if constexpr (std::is_same_v) { - WI = this->extendEval(wellIndexEval(perf, elapsed_time, Base::restrictEval(pressure))); - } else { - WI = wellIndexEval(perf, elapsed_time, pressure); - } - auto injectorType = this->well_ecl_.injectorType(); - if (injectorType == InjectorType::WATER) { - const unsigned waterCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::waterCompIdx); - //cq_s[waterCompIdx] = - WI * (total_mob_dense * drawdown) * b_perfcells_dense[waterCompIdx]; - cq_s[waterCompIdx] = - WI * drawdown; - std::cout << cq_s[waterCompIdx] << " " << - Tw * (total_mob_dense * drawdown)*b_perfcells_dense[waterCompIdx] << " " << cmix_s[waterCompIdx] << " " << b_perfcells_dense[waterCompIdx] << " " << total_mob_dense << std::endl; - std::cout << WI << " " << (Tw*total_mob_dense*b_perfcells_dense[waterCompIdx]) << std::endl; - } - else if (injectorType == InjectorType::GAS) { - const unsigned gasCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::gasCompIdx); - //cq_s[gasCompIdx] = - WI * (total_mob_dense * drawdown) * b_perfcells_dense[gasCompIdx]; - std::cout << "together" << (total_mob_dense * drawdown) / b_perfcells_dense[gasCompIdx] << std::endl; - std::cout << "total_mob_dense" << total_mob_dense << std::endl; - std::cout << "drawdown" << drawdown << std::endl; - std::cout << "b_perfcells_dense[gasCompIdx]" << - b_perfcells_dense[gasCompIdx] << std::endl; - - cq_s[gasCompIdx] = - WI * drawdown; - } - } else { - std::cout << "ML model not found"; - - const Value cqt_i = - Tw * (total_mob_dense * drawdown); - - // compute volume ratio between connection at standard conditions - Value volumeRatio = bhp * 0.0; // initialize it with the correct type -; - if (FluidSystem::enableVaporizedWater() && FluidSystem::enableDissolvedGasInWater()) { - disOilVapWatVolumeRatio(volumeRatio, rvw, rsw, pressure, - cmix_s, b_perfcells_dense, deferred_logger); - } else { - if (FluidSystem::phaseIsActive(FluidSystem::waterPhaseIdx)) { - const unsigned waterCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::waterCompIdx); - volumeRatio += cmix_s[waterCompIdx] / b_perfcells_dense[waterCompIdx]; - } - } - - if constexpr (Indices::enableSolvent) { - volumeRatio += cmix_s[Indices::contiSolventEqIdx] / b_perfcells_dense[Indices::contiSolventEqIdx]; - } - - if (FluidSystem::phaseIsActive(FluidSystem::oilPhaseIdx) && FluidSystem::phaseIsActive(FluidSystem::gasPhaseIdx)) { - gasOilVolumeRatio(volumeRatio, rv, rs, pressure, - cmix_s, b_perfcells_dense, deferred_logger); - } - else { - if (FluidSystem::phaseIsActive(FluidSystem::oilPhaseIdx)) { - const unsigned oilCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::oilCompIdx); - volumeRatio += cmix_s[oilCompIdx] / b_perfcells_dense[oilCompIdx]; - } - if (FluidSystem::phaseIsActive(FluidSystem::gasPhaseIdx)) { - const unsigned gasCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::gasCompIdx); - volumeRatio += cmix_s[gasCompIdx] / b_perfcells_dense[gasCompIdx]; - } - } - - // injecting connections total volumerates at standard conditions - Value cqt_is = cqt_i / volumeRatio; - for (int componentIdx = 0; componentIdx < this->numComponents(); ++componentIdx) { - cq_s[componentIdx] = cmix_s[componentIdx] * cqt_is; - } - } - - // calculating the perforation solution gas rate and solution oil rates - if (this->isProducer()) { - if (FluidSystem::phaseIsActive(FluidSystem::oilPhaseIdx) && FluidSystem::phaseIsActive(FluidSystem::gasPhaseIdx)) { - gasOilPerfRateInj(cq_s, perf_rates, - rv, rs, pressure, rvw, deferred_logger); - } - if (FluidSystem::phaseIsActive(FluidSystem::gasPhaseIdx) && FluidSystem::phaseIsActive(FluidSystem::waterPhaseIdx)) { - //no oil - gasWaterPerfRateInj(cq_s, perf_rates, rvw, rsw, - pressure, deferred_logger); - } - } - } - } - - - template - void - StandardWell:: - assembleWellEqWithoutIteration(const Simulator& ebosSimulator, - const double dt, - const Well::InjectionControls& inj_controls, - const Well::ProductionControls& prod_controls, - WellState& well_state, - const GroupState& group_state, - DeferredLogger& deferred_logger) - { - // TODO: only_wells should be put back to save some computation - // for example, the matrices B C does not need to update if only_wells - if (!this->isOperableAndSolvable() && !this->wellIsStopped()) return; - - // clear all entries - this->linSys_.clear(); - - assembleWellEqWithoutIterationImpl(ebosSimulator, dt, inj_controls, prod_controls, well_state, group_state, deferred_logger); - } - - - - - template - void - StandardWell:: - assembleWellEqWithoutIterationImpl(const Simulator& ebosSimulator, - const double dt, - const Well::InjectionControls& inj_controls, - const Well::ProductionControls& prod_controls, - WellState& well_state, - const GroupState& group_state, - DeferredLogger& deferred_logger) - { - // try to regularize equation if the well does not converge - const Scalar regularization_factor = this->regularize_? this->param_.regularization_factor_wells_ : 1.0; - const double volume = 0.1 * unit::cubic(unit::feet) * regularization_factor; - - auto& ws = well_state.well(this->index_of_well_); - ws.phase_mixing_rates.fill(0.0); - - const int np = this->number_of_phases_; - - std::vector connectionRates = this->connectionRates_; // Copy to get right size. - auto& perf_data = ws.perf_data; - auto& perf_rates = perf_data.phase_rates; - for (int perf = 0; perf < this->number_of_perforations_; ++perf) { - // Calculate perforation quantities. - std::vector cq_s(this->num_components_, {this->primary_variables_.numWellEq() + Indices::numEq, 0.0}); - EvalWell water_flux_s{this->primary_variables_.numWellEq() + Indices::numEq, 0.0}; - EvalWell cq_s_zfrac_effective{this->primary_variables_.numWellEq() + Indices::numEq, 0.0}; - calculateSinglePerf(ebosSimulator, perf, well_state, connectionRates, cq_s, water_flux_s, cq_s_zfrac_effective, deferred_logger); - - // Equation assembly for this perforation. - if constexpr (has_polymer && Base::has_polymermw) { - if (this->isInjector()) { - handleInjectivityEquations(ebosSimulator, well_state, perf, water_flux_s, deferred_logger); - } - } - const int cell_idx = this->well_cells_[perf]; - for (int componentIdx = 0; componentIdx < this->num_components_; ++componentIdx) { - // the cq_s entering mass balance equations need to consider the efficiency factors. - const EvalWell cq_s_effective = cq_s[componentIdx] * this->well_efficiency_factor_; - - connectionRates[perf][componentIdx] = Base::restrictEval(cq_s_effective); - - StandardWellAssemble(*this). - assemblePerforationEq(cq_s_effective, - componentIdx, - cell_idx, - this->primary_variables_.numWellEq(), - this->linSys_); - - // Store the perforation phase flux for later usage. - if (has_solvent && componentIdx == Indices::contiSolventEqIdx) { - auto& perf_rate_solvent = perf_data.solvent_rates; - perf_rate_solvent[perf] = cq_s[componentIdx].value(); - } else { - perf_rates[perf*np + this->ebosCompIdxToFlowCompIdx(componentIdx)] = cq_s[componentIdx].value(); - } - } - - if constexpr (has_zFraction) { - StandardWellAssemble(*this). - assembleZFracEq(cq_s_zfrac_effective, - cell_idx, - this->primary_variables_.numWellEq(), - this->linSys_); - } - } - // Update the connection - this->connectionRates_ = connectionRates; - - // Accumulate dissolved gas and vaporized oil flow rates across all - // ranks sharing this well (this->index_of_well_). - { - const auto& comm = this->parallel_well_info_.communication(); - comm.sum(ws.phase_mixing_rates.data(), ws.phase_mixing_rates.size()); - } - - // accumulate resWell_ and duneD_ in parallel to get effects of all perforations (might be distributed) - this->linSys_.sumDistributed(this->parallel_well_info_.communication()); - - // add vol * dF/dt + Q to the well equations; - for (int componentIdx = 0; componentIdx < numWellConservationEq; ++componentIdx) { - // TODO: following the development in MSW, we need to convert the volume of the wellbore to be surface volume - // since all the rates are under surface condition - EvalWell resWell_loc(this->primary_variables_.numWellEq() + Indices::numEq, 0.0); - if (FluidSystem::numActivePhases() > 1) { - assert(dt > 0); - resWell_loc += (this->primary_variables_.surfaceVolumeFraction(componentIdx) - - this->F0_[componentIdx]) * volume / dt; - } - resWell_loc -= this->primary_variables_.getQs(componentIdx) * this->well_efficiency_factor_; - StandardWellAssemble(*this). - assembleSourceEq(resWell_loc, - componentIdx, - this->primary_variables_.numWellEq(), - this->linSys_); - } - - const auto& summaryState = ebosSimulator.vanguard().summaryState(); - const Schedule& schedule = ebosSimulator.vanguard().schedule(); - StandardWellAssemble(*this). - assembleControlEq(well_state, group_state, - schedule, summaryState, - inj_controls, prod_controls, - this->primary_variables_, - this->connections_.rho(), - this->linSys_, - deferred_logger); - - - // do the local inversion of D. - try { - this->linSys_.invert(); - } catch( ... ) { - OPM_DEFLOG_THROW(NumericalProblem, "Error when inverting local well equations for well " + name(), deferred_logger); - } - } - - - - - template - void - StandardWell:: - calculateSinglePerf(const Simulator& ebosSimulator, - const int perf, - WellState& well_state, - std::vector& connectionRates, - std::vector& cq_s, - EvalWell& water_flux_s, - EvalWell& cq_s_zfrac_effective, - DeferredLogger& deferred_logger) const - { - const bool allow_cf = this->getAllowCrossFlow() || openCrossFlowAvoidSingularity(ebosSimulator); - const EvalWell& bhp = this->primary_variables_.eval(Bhp); - const int cell_idx = this->well_cells_[perf]; - const auto& intQuants = ebosSimulator.model().intensiveQuantities(cell_idx, /*timeIdx=*/ 0); - std::vector mob(this->num_components_, {this->primary_variables_.numWellEq() + Indices::numEq, 0.}); - getMobility(ebosSimulator, perf, mob, deferred_logger); - - PerforationRates perf_rates; - double trans_mult = ebosSimulator.problem().template rockCompTransMultiplier(intQuants, cell_idx); - const double Tw = this->wellIndex(perf) * trans_mult; - const double elapsed_time = ebosSimulator.time(); - - computePerfRate(intQuants, mob, bhp, Tw, perf, allow_cf, elapsed_time, - cq_s, perf_rates, deferred_logger); - - auto& ws = well_state.well(this->index_of_well_); - auto& perf_data = ws.perf_data; - if constexpr (has_polymer && Base::has_polymermw) { - if (this->isInjector()) { - // Store the original water flux computed from the reservoir quantities. - // It will be required to assemble the injectivity equations. - const unsigned water_comp_idx = Indices::canonicalToActiveComponentIndex(FluidSystem::waterCompIdx); - water_flux_s = cq_s[water_comp_idx]; - // Modify the water flux for the rest of this function to depend directly on the - // local water velocity primary variable. - handleInjectivityRate(ebosSimulator, perf, cq_s); - } - } - - // updating the solution gas rate and solution oil rate - if (this->isProducer()) { - ws.phase_mixing_rates[ws.dissolved_gas] += perf_rates.dis_gas; - ws.phase_mixing_rates[ws.dissolved_gas_in_water] += perf_rates.dis_gas_in_water; - ws.phase_mixing_rates[ws.vaporized_oil] += perf_rates.vap_oil; - ws.phase_mixing_rates[ws.vaporized_water] += perf_rates.vap_wat; - } - - if constexpr (has_energy) { - connectionRates[perf][Indices::contiEnergyEqIdx] = - connectionRateEnergy(ebosSimulator.problem().maxOilSaturation(cell_idx), - cq_s, intQuants, deferred_logger); - } - - if constexpr (has_polymer) { - std::variant polymerConcentration; - if (this->isInjector()) { - polymerConcentration = this->wpolymer(); - } else { - polymerConcentration = this->extendEval(intQuants.polymerConcentration() * - intQuants.polymerViscosityCorrection()); - } - - [[maybe_unused]] EvalWell cq_s_poly; - std::tie(connectionRates[perf][Indices::contiPolymerEqIdx], - cq_s_poly) = - this->connections_.connectionRatePolymer(perf_data.polymer_rates[perf], - cq_s, polymerConcentration); - - if constexpr (Base::has_polymermw) { - updateConnectionRatePolyMW(cq_s_poly, intQuants, well_state, - perf, connectionRates, deferred_logger); - } - } - - if constexpr (has_foam) { - std::variant foamConcentration; - if (this->isInjector()) { - foamConcentration = this->wfoam(); - } else { - foamConcentration = this->extendEval(intQuants.foamConcentration()); - } - connectionRates[perf][Indices::contiFoamEqIdx] = - this->connections_.connectionRateFoam(cq_s, foamConcentration, - FoamModule::transportPhase(), - deferred_logger); - } - - if constexpr (has_zFraction) { - std::variant> solventConcentration; - if (this->isInjector()) { - solventConcentration = this->wsolvent(); - } else { - solventConcentration = std::array{this->extendEval(intQuants.xVolume()), - this->extendEval(intQuants.yVolume())}; - } - std::tie(connectionRates[perf][Indices::contiZfracEqIdx], - cq_s_zfrac_effective) = - this->connections_.connectionRatezFraction(perf_data.solvent_rates[perf], - perf_rates.dis_gas, cq_s, - solventConcentration); - } - - if constexpr (has_brine) { - std::variant saltConcentration; - if (this->isInjector()) { - saltConcentration = this->wsalt(); - } else { - saltConcentration = this->extendEval(intQuants.fluidState().saltConcentration()); - } - - connectionRates[perf][Indices::contiBrineEqIdx] = - this->connections_.connectionRateBrine(perf_data.brine_rates[perf], - perf_rates.vap_wat, cq_s, - saltConcentration); - } - - if constexpr (has_micp) { - std::variant microbialConcentration; - std::variant oxygenConcentration; - std::variant ureaConcentration; - if (this->isInjector()) { - microbialConcentration = this->wmicrobes(); - oxygenConcentration = this->woxygen(); - ureaConcentration = this->wurea(); - } else { - microbialConcentration = this->extendEval(intQuants.microbialConcentration()); - oxygenConcentration = this->extendEval(intQuants.oxygenConcentration()); - ureaConcentration = this->extendEval(intQuants.ureaConcentration()); - } - std::tie(connectionRates[perf][Indices::contiMicrobialEqIdx], - connectionRates[perf][Indices::contiOxygenEqIdx], - connectionRates[perf][Indices::contiUreaEqIdx]) = - this->connections_.connectionRatesMICP(cq_s, - microbialConcentration, - oxygenConcentration, - ureaConcentration); - } - - // Store the perforation pressure for later usage. - perf_data.pressure[perf] = ws.bhp + this->connections_.pressure_diff(perf); - } - - - - template - template - void - StandardWell:: - getMobility(const Simulator& ebosSimulator, - const int perf, - std::vector& mob, - DeferredLogger& deferred_logger) const - { - auto obtain = [this](const Eval& value) - { - if constexpr (std::is_same_v) { - static_cast(this); // suppress clang warning - return getValue(value); - } else { - return this->extendEval(value); - } - }; - WellInterface::getMobility(ebosSimulator, perf, mob, - obtain, deferred_logger); - - // modify the water mobility if polymer is present - if constexpr (has_polymer) { - if (!FluidSystem::phaseIsActive(FluidSystem::waterPhaseIdx)) { - OPM_DEFLOG_THROW(std::runtime_error, "Water is required when polymer is active", deferred_logger); - } - - // for the cases related to polymer molecular weight, we assume fully mixing - // as a result, the polymer and water share the same viscosity - if constexpr (!Base::has_polymermw) { - if constexpr (std::is_same_v) { - std::vector mob_eval(this->num_components_, {this->primary_variables_.numWellEq() + Indices::numEq, 0.}); - for (size_t i = 0; i < mob.size(); ++i) - mob_eval[i].setValue(mob[i]); - updateWaterMobilityWithPolymer(ebosSimulator, perf, mob_eval, deferred_logger); - for (size_t i = 0; i < mob.size(); ++i) { - mob[i] = getValue(mob_eval[i]); - } - } else { - updateWaterMobilityWithPolymer(ebosSimulator, perf, mob, deferred_logger); - } - } - } - - // if the injecting well has WINJMULT setup, we update the mobility accordingly - if (this->isInjector() && this->well_ecl_.getInjMultMode() != Well::InjMultMode::NONE) { - const double bhp = this->primary_variables_.value(Bhp); - const double perf_press = bhp + this->connections_.pressure_diff(perf); - const double multiplier = this->getInjMult(perf, bhp, perf_press); - for (size_t i = 0; i < mob.size(); ++i) { - mob[i] *= multiplier; - } - } - } - - - template - void - StandardWell:: - updateWellState(const SummaryState& summary_state, - const BVectorWell& dwells, - WellState& well_state, - DeferredLogger& deferred_logger) - { - if (!this->isOperableAndSolvable() && !this->wellIsStopped()) return; - - const bool stop_or_zero_rate_target = this->stopppedOrZeroRateTarget(summary_state, well_state); - updatePrimaryVariablesNewton(dwells, stop_or_zero_rate_target, deferred_logger); - - updateWellStateFromPrimaryVariables(stop_or_zero_rate_target, well_state, summary_state, deferred_logger); - Base::calculateReservoirRates(well_state.well(this->index_of_well_)); - } - - - - - - template - void - StandardWell:: - updatePrimaryVariablesNewton(const BVectorWell& dwells, - const bool stop_or_zero_rate_target, - DeferredLogger& deferred_logger) - { - const double dFLimit = this->param_.dwell_fraction_max_; - const double dBHPLimit = this->param_.dbhp_max_rel_; - this->primary_variables_.updateNewton(dwells, stop_or_zero_rate_target, dFLimit, dBHPLimit); - - // for the water velocity and skin pressure - if constexpr (Base::has_polymermw) { - this->primary_variables_.updateNewtonPolyMW(dwells); - } - - this->primary_variables_.checkFinite(deferred_logger); - } - - - - - - template - void - StandardWell:: - updateWellStateFromPrimaryVariables(const bool stop_or_zero_rate_target, - WellState& well_state, - const SummaryState& summary_state, - DeferredLogger& deferred_logger) const - { - this->StdWellEval::updateWellStateFromPrimaryVariables(stop_or_zero_rate_target, well_state, summary_state, deferred_logger); - - // other primary variables related to polymer injectivity study - if constexpr (Base::has_polymermw) { - this->primary_variables_.copyToWellStatePolyMW(well_state); - } - } - - - - - - template - void - StandardWell:: - updateIPR(const Simulator& ebos_simulator, DeferredLogger& deferred_logger) const - { - // TODO: not handling solvent related here for now - - // initialize all the values to be zero to begin with - std::fill(this->ipr_a_.begin(), this->ipr_a_.end(), 0.); - std::fill(this->ipr_b_.begin(), this->ipr_b_.end(), 0.); - - for (int perf = 0; perf < this->number_of_perforations_; ++perf) { - std::vector mob(this->num_components_, 0.0); - getMobility(ebos_simulator, perf, mob, deferred_logger); - - const int cell_idx = this->well_cells_[perf]; - const auto& int_quantities = ebos_simulator.model().intensiveQuantities(cell_idx, /*timeIdx=*/ 0); - const auto& fs = int_quantities.fluidState(); - // the pressure of the reservoir grid block the well connection is in - double p_r = this->getPerfCellPressure(fs).value(); - - // calculating the b for the connection - std::vector b_perf(this->num_components_); - for (size_t phase = 0; phase < FluidSystem::numPhases; ++phase) { - if (!FluidSystem::phaseIsActive(phase)) { - continue; - } - const unsigned comp_idx = Indices::canonicalToActiveComponentIndex(FluidSystem::solventComponentIndex(phase)); - b_perf[comp_idx] = fs.invB(phase).value(); - } - if constexpr (has_solvent) { - b_perf[Indices::contiSolventEqIdx] = int_quantities.solventInverseFormationVolumeFactor().value(); - } - - // the pressure difference between the connection and BHP - const double h_perf = this->connections_.pressure_diff(perf); - const double pressure_diff = p_r - h_perf; - - // Let us add a check, since the pressure is calculated based on zero value BHP - // it should not be negative anyway. If it is negative, we might need to re-formulate - // to taking into consideration the crossflow here. - if ( (this->isProducer() && pressure_diff < 0.) || (this->isInjector() && pressure_diff > 0.) ) { - deferred_logger.debug("CROSSFLOW_IPR", - "cross flow found when updateIPR for well " + name() - + " . The connection is ignored in IPR calculations"); - // we ignore these connections for now - continue; - } - - // the well index associated with the connection - const double tw_perf = this->wellIndex(perf)*ebos_simulator.problem().template rockCompTransMultiplier(int_quantities, cell_idx); - - std::vector ipr_a_perf(this->ipr_a_.size()); - std::vector ipr_b_perf(this->ipr_b_.size()); - for (int comp_idx = 0; comp_idx < this->num_components_; ++comp_idx) { - const double tw_mob = tw_perf * mob[comp_idx] * b_perf[comp_idx]; - ipr_a_perf[comp_idx] += tw_mob * pressure_diff; - ipr_b_perf[comp_idx] += tw_mob; - } - - // we need to handle the rs and rv when both oil and gas are present - if (FluidSystem::phaseIsActive(FluidSystem::oilPhaseIdx) && FluidSystem::phaseIsActive(FluidSystem::gasPhaseIdx)) { - const unsigned oil_comp_idx = Indices::canonicalToActiveComponentIndex(FluidSystem::oilCompIdx); - const unsigned gas_comp_idx = Indices::canonicalToActiveComponentIndex(FluidSystem::gasCompIdx); - const double rs = (fs.Rs()).value(); - const double rv = (fs.Rv()).value(); - - const double dis_gas_a = rs * ipr_a_perf[oil_comp_idx]; - const double vap_oil_a = rv * ipr_a_perf[gas_comp_idx]; - - ipr_a_perf[gas_comp_idx] += dis_gas_a; - ipr_a_perf[oil_comp_idx] += vap_oil_a; - - const double dis_gas_b = rs * ipr_b_perf[oil_comp_idx]; - const double vap_oil_b = rv * ipr_b_perf[gas_comp_idx]; - - ipr_b_perf[gas_comp_idx] += dis_gas_b; - ipr_b_perf[oil_comp_idx] += vap_oil_b; - } - - for (size_t comp_idx = 0; comp_idx < ipr_a_perf.size(); ++comp_idx) { - this->ipr_a_[comp_idx] += ipr_a_perf[comp_idx]; - this->ipr_b_[comp_idx] += ipr_b_perf[comp_idx]; - } - } - this->parallel_well_info_.communication().sum(this->ipr_a_.data(), this->ipr_a_.size()); - this->parallel_well_info_.communication().sum(this->ipr_b_.data(), this->ipr_b_.size()); - } - - - template - void - StandardWell:: - checkOperabilityUnderBHPLimit(const WellState& well_state, const Simulator& ebos_simulator, DeferredLogger& deferred_logger) - { - const auto& summaryState = ebos_simulator.vanguard().summaryState(); - const double bhp_limit = WellBhpThpCalculator(*this).mostStrictBhpFromBhpLimits(summaryState); - // Crude but works: default is one atmosphere. - // TODO: a better way to detect whether the BHP is defaulted or not - const bool bhp_limit_not_defaulted = bhp_limit > 1.5 * unit::barsa; - if ( bhp_limit_not_defaulted || !this->wellHasTHPConstraints(summaryState) ) { - // if the BHP limit is not defaulted or the well does not have a THP limit - // we need to check the BHP limit - double total_ipr_mass_rate = 0.0; - for (unsigned phaseIdx = 0; phaseIdx < FluidSystem::numPhases; ++phaseIdx) - { - if (!FluidSystem::phaseIsActive(phaseIdx)) { - continue; - } - - const unsigned compIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::solventComponentIndex(phaseIdx)); - const double ipr_rate = this->ipr_a_[compIdx] - this->ipr_b_[compIdx] * bhp_limit; - - const double rho = FluidSystem::referenceDensity( phaseIdx, Base::pvtRegionIdx() ); - total_ipr_mass_rate += ipr_rate * rho; - } - if ( (this->isProducer() && total_ipr_mass_rate < 0.) || (this->isInjector() && total_ipr_mass_rate > 0.) ) { - this->operability_status_.operable_under_only_bhp_limit = false; - } - - // checking whether running under BHP limit will violate THP limit - if (this->operability_status_.operable_under_only_bhp_limit && this->wellHasTHPConstraints(summaryState)) { - // option 1: calculate well rates based on the BHP limit. - // option 2: stick with the above IPR curve - // we use IPR here - std::vector well_rates_bhp_limit; - computeWellRatesWithBhp(ebos_simulator, bhp_limit, well_rates_bhp_limit, deferred_logger); - - this->adaptRatesForVFP(well_rates_bhp_limit); - const double thp_limit = this->getTHPConstraint(summaryState); - const double thp = WellBhpThpCalculator(*this).calculateThpFromBhp(well_rates_bhp_limit, - bhp_limit, - this->connections_.rho(), - this->getALQ(well_state), - thp_limit, - deferred_logger); - if ( (this->isProducer() && thp < thp_limit) || (this->isInjector() && thp > thp_limit) ) { - this->operability_status_.obey_thp_limit_under_bhp_limit = false; - } - } - } else { - // defaulted BHP and there is a THP constraint - // default BHP limit is about 1 atm. - // when applied the hydrostatic pressure correction dp, - // most likely we get a negative value (bhp + dp)to search in the VFP table, - // which is not desirable. - // we assume we can operate under defaulted BHP limit and will violate the THP limit - // when operating under defaulted BHP limit. - this->operability_status_.operable_under_only_bhp_limit = true; - this->operability_status_.obey_thp_limit_under_bhp_limit = false; - } - } - - - - - - template - void - StandardWell:: - checkOperabilityUnderTHPLimit(const Simulator& ebos_simulator, const WellState& well_state, DeferredLogger& deferred_logger) - { - const auto& summaryState = ebos_simulator.vanguard().summaryState(); - const auto obtain_bhp = this->isProducer() ? computeBhpAtThpLimitProd(well_state, ebos_simulator, summaryState, deferred_logger) - : computeBhpAtThpLimitInj(ebos_simulator, summaryState, deferred_logger); - - if (obtain_bhp) { - this->operability_status_.can_obtain_bhp_with_thp_limit = true; - - const double bhp_limit = WellBhpThpCalculator(*this).mostStrictBhpFromBhpLimits(summaryState); - this->operability_status_.obey_bhp_limit_with_thp_limit = this->isProducer() ? - *obtain_bhp >= bhp_limit : *obtain_bhp <= bhp_limit ; - - const double thp_limit = this->getTHPConstraint(summaryState); - if (this->isProducer() && *obtain_bhp < thp_limit) { - const std::string msg = " obtained bhp " + std::to_string(unit::convert::to(*obtain_bhp, unit::barsa)) - + " bars is SMALLER than thp limit " - + std::to_string(unit::convert::to(thp_limit, unit::barsa)) - + " bars as a producer for well " + name(); - deferred_logger.debug(msg); - } - else if (this->isInjector() && *obtain_bhp > thp_limit) { - const std::string msg = " obtained bhp " + std::to_string(unit::convert::to(*obtain_bhp, unit::barsa)) - + " bars is LARGER than thp limit " - + std::to_string(unit::convert::to(thp_limit, unit::barsa)) - + " bars as a injector for well " + name(); - deferred_logger.debug(msg); - } - } else { - this->operability_status_.can_obtain_bhp_with_thp_limit = false; - this->operability_status_.obey_bhp_limit_with_thp_limit = false; - if (!this->wellIsStopped()) { - const double thp_limit = this->getTHPConstraint(summaryState); - deferred_logger.debug(" could not find bhp value at thp limit " - + std::to_string(unit::convert::to(thp_limit, unit::barsa)) - + " bar for well " + name() + ", the well might need to be closed "); - } - } - } - - - - - - template - bool - StandardWell:: - allDrawDownWrongDirection(const Simulator& ebos_simulator) const - { - bool all_drawdown_wrong_direction = true; - - for (int perf = 0; perf < this->number_of_perforations_; ++perf) { - const int cell_idx = this->well_cells_[perf]; - const auto& intQuants = ebos_simulator.model().intensiveQuantities(cell_idx, /*timeIdx=*/0); - const auto& fs = intQuants.fluidState(); - - const double pressure = this->getPerfCellPressure(fs).value(); - const double bhp = this->primary_variables_.eval(Bhp).value(); - - // Pressure drawdown (also used to determine direction of flow) - const double well_pressure = bhp + this->connections_.pressure_diff(perf); - const double drawdown = pressure - well_pressure; - - // for now, if there is one perforation can produce/inject in the correct - // direction, we consider this well can still produce/inject. - // TODO: it can be more complicated than this to cause wrong-signed rates - if ( (drawdown < 0. && this->isInjector()) || - (drawdown > 0. && this->isProducer()) ) { - all_drawdown_wrong_direction = false; - break; - } - } - - const auto& comm = this->parallel_well_info_.communication(); - if (comm.size() > 1) - { - all_drawdown_wrong_direction = - (comm.min(all_drawdown_wrong_direction ? 1 : 0) == 1); - } - - return all_drawdown_wrong_direction; - } - - - - - template - bool - StandardWell:: - canProduceInjectWithCurrentBhp(const Simulator& ebos_simulator, - const WellState& well_state, - DeferredLogger& deferred_logger) - { - const double bhp = well_state.well(this->index_of_well_).bhp; - std::vector well_rates; - computeWellRatesWithBhp(ebos_simulator, bhp, well_rates, deferred_logger); - - const double sign = (this->isProducer()) ? -1. : 1.; - const double threshold = sign * std::numeric_limits::min(); - - bool can_produce_inject = false; - for (const auto value : well_rates) { - if (this->isProducer() && value < threshold) { - can_produce_inject = true; - break; - } else if (this->isInjector() && value > threshold) { - can_produce_inject = true; - break; - } - } - - if (!can_produce_inject) { - deferred_logger.debug(" well " + name() + " CANNOT produce or inejct "); - } - - return can_produce_inject; - } - - - - - - template - bool - StandardWell:: - openCrossFlowAvoidSingularity(const Simulator& ebos_simulator) const - { - return !this->getAllowCrossFlow() && allDrawDownWrongDirection(ebos_simulator); - } - - - - - template - void - StandardWell:: - computePropertiesForWellConnectionPressures(const Simulator& ebosSimulator, - const WellState& well_state, - WellConnectionProps& props) const - { - std::function getTemperature = - [&ebosSimulator](int cell_idx, int phase_idx) - { - return ebosSimulator.model().intensiveQuantities(cell_idx, 0).fluidState().temperature(phase_idx).value(); - }; - std::function getSaltConcentration = - [&ebosSimulator](int cell_idx) - { - return ebosSimulator.model().intensiveQuantities(cell_idx, 0).fluidState().saltConcentration().value(); - }; - std::function getPvtRegionIdx = - [&ebosSimulator](int cell_idx) - { - return ebosSimulator.model().intensiveQuantities(cell_idx, 0).fluidState().pvtRegionIndex(); - }; - std::function getInvFac = - [&ebosSimulator](int cell_idx) - { - return ebosSimulator.model().intensiveQuantities(cell_idx, 0).solventInverseFormationVolumeFactor().value(); - }; - std::function getSolventDensity = - [&ebosSimulator](int cell_idx) - { - return ebosSimulator.model().intensiveQuantities(cell_idx, 0).solventRefDensity(); - }; - - this->connections_.computePropertiesForPressures(well_state, - getTemperature, - getSaltConcentration, - getPvtRegionIdx, - getInvFac, - getSolventDensity, - props); - } - - - - - - template - ConvergenceReport - StandardWell:: - getWellConvergence(const SummaryState& summary_state, - const WellState& well_state, - const std::vector& B_avg, - DeferredLogger& deferred_logger, - const bool relax_tolerance) const - { - // the following implementation assume that the polymer is always after the w-o-g phases - // For the polymer, energy and foam cases, there is one more mass balance equations of reservoir than wells - assert((int(B_avg.size()) == this->num_components_) || has_polymer || has_energy || has_foam || has_brine || has_zFraction || has_micp); - - // using sricter tolerance for stopped wells and wells under zero rate target control. - constexpr double stricter_factor = 1.e-4; - const double tol_wells = this->stopppedOrZeroRateTarget(summary_state, well_state) ? - this->param_.tolerance_wells_ * stricter_factor : this->param_.tolerance_wells_; - - std::vector res; - ConvergenceReport report = this->StdWellEval::getWellConvergence(well_state, - B_avg, - this->param_.max_residual_allowed_, - tol_wells, - this->param_.relaxed_tolerance_flow_well_, - relax_tolerance, - res, - deferred_logger); - - checkConvergenceExtraEqs(res, report); - - return report; - } - - - - - - template - void - StandardWell:: - updateProductivityIndex(const Simulator& ebosSimulator, - const WellProdIndexCalculator& wellPICalc, - WellState& well_state, - DeferredLogger& deferred_logger) const - { - auto fluidState = [&ebosSimulator, this](const int perf) - { - const auto cell_idx = this->well_cells_[perf]; - return ebosSimulator.model() - .intensiveQuantities(cell_idx, /*timeIdx=*/ 0).fluidState(); - }; - - const int np = this->number_of_phases_; - auto setToZero = [np](double* x) -> void - { - std::fill_n(x, np, 0.0); - }; - - auto addVector = [np](const double* src, double* dest) -> void - { - std::transform(src, src + np, dest, dest, std::plus<>{}); - }; - - auto& ws = well_state.well(this->index_of_well_); - auto& perf_data = ws.perf_data; - auto* wellPI = ws.productivity_index.data(); - auto* connPI = perf_data.prod_index.data(); - - setToZero(wellPI); - - const auto preferred_phase = this->well_ecl_.getPreferredPhase(); - auto subsetPerfID = 0; - - for (const auto& perf : *this->perf_data_) { - auto allPerfID = perf.ecl_index; - - auto connPICalc = [&wellPICalc, allPerfID](const double mobility) -> double - { - return wellPICalc.connectionProdIndStandard(allPerfID, mobility); - }; - - std::vector mob(this->num_components_, 0.0); - getMobility(ebosSimulator, static_cast(subsetPerfID), mob, deferred_logger); - - const auto& fs = fluidState(subsetPerfID); - setToZero(connPI); - - if (this->isInjector()) { - this->computeConnLevelInjInd(fs, preferred_phase, connPICalc, - mob, connPI, deferred_logger); - } - else { // Production or zero flow rate - this->computeConnLevelProdInd(fs, connPICalc, mob, connPI); - } - - addVector(connPI, wellPI); - - ++subsetPerfID; - connPI += np; - } - - // Sum with communication in case of distributed well. - const auto& comm = this->parallel_well_info_.communication(); - if (comm.size() > 1) { - comm.sum(wellPI, np); - } - - assert ((static_cast(subsetPerfID) == this->number_of_perforations_) && - "Internal logic error in processing connections for PI/II"); - } - - - - template - void - StandardWell:: - computeWellConnectionDensitesPressures(const Simulator& ebosSimulator, - const WellState& well_state, - const WellConnectionProps& props, - DeferredLogger& deferred_logger) - { - std::function invB = - [&ebosSimulator](int cell_idx, int phase_idx) - { - return ebosSimulator.model().intensiveQuantities(cell_idx, 0).fluidState().invB(phase_idx).value(); - }; - std::function mobility = - [&ebosSimulator](int cell_idx, int phase_idx) - { - return ebosSimulator.model().intensiveQuantities(cell_idx, 0).mobility(phase_idx).value(); - }; - std::function invFac = - [&ebosSimulator](int cell_idx) - { - return ebosSimulator.model().intensiveQuantities(cell_idx, 0).solventInverseFormationVolumeFactor().value(); - }; - std::function solventMobility = - [&ebosSimulator](int cell_idx) - { - return ebosSimulator.model().intensiveQuantities(cell_idx, 0).solventMobility().value(); - }; - - this->connections_.computeProperties(well_state, - invB, - mobility, - invFac, - solventMobility, - props, - deferred_logger); - } - - - - - - template - void - StandardWell:: - computeWellConnectionPressures(const Simulator& ebosSimulator, - const WellState& well_state, - DeferredLogger& deferred_logger) - { - // 1. Compute properties required by computePressureDelta(). - // Note that some of the complexity of this part is due to the function - // taking std::vector arguments, and not Eigen objects. - WellConnectionProps props; - computePropertiesForWellConnectionPressures(ebosSimulator, well_state, props); - computeWellConnectionDensitesPressures(ebosSimulator, well_state, - props, deferred_logger); - } - - - - - - template - void - StandardWell:: - solveEqAndUpdateWellState(const SummaryState& summary_state, - WellState& well_state, - DeferredLogger& deferred_logger) - { - if (!this->isOperableAndSolvable() && !this->wellIsStopped()) return; - - // We assemble the well equations, then we check the convergence, - // which is why we do not put the assembleWellEq here. - BVectorWell dx_well(1); - dx_well[0].resize(this->primary_variables_.numWellEq()); - this->linSys_.solve( dx_well); - - updateWellState(summary_state, dx_well, well_state, deferred_logger); - } - - - - - - template - void - StandardWell:: - calculateExplicitQuantities(const Simulator& ebosSimulator, - const WellState& well_state, - DeferredLogger& deferred_logger) - { - const auto& summary_state = ebosSimulator.vanguard().summaryState(); - updatePrimaryVariables(summary_state, well_state, deferred_logger); - initPrimaryVariablesEvaluation(); - computeWellConnectionPressures(ebosSimulator, well_state, deferred_logger); - this->computeAccumWell(); - } - - - - template - void - StandardWell:: - apply(const BVector& x, BVector& Ax) const - { - if (!this->isOperableAndSolvable() && !this->wellIsStopped()) return; - - if (this->param_.matrix_add_well_contributions_) - { - // Contributions are already in the matrix itself - return; - } - - this->linSys_.apply(x, Ax); - } - - - - - template - void - StandardWell:: - apply(BVector& r) const - { - if (!this->isOperableAndSolvable() && !this->wellIsStopped()) return; - - this->linSys_.apply(r); - } - - - - - template - void - StandardWell:: - recoverWellSolutionAndUpdateWellState(const SummaryState& summary_state, - const BVector& x, - WellState& well_state, - DeferredLogger& deferred_logger) - { - if (!this->isOperableAndSolvable() && !this->wellIsStopped()) return; - - BVectorWell xw(1); - xw[0].resize(this->primary_variables_.numWellEq()); - - this->linSys_.recoverSolutionWell(x, xw); - updateWellState(summary_state, xw, well_state, deferred_logger); - } - - - - - template - void - StandardWell:: - computeWellRatesWithBhp(const Simulator& ebosSimulator, - const double& bhp, - std::vector& well_flux, - DeferredLogger& deferred_logger) const - { - - const int np = this->number_of_phases_; - well_flux.resize(np, 0.0); - - const bool allow_cf = this->getAllowCrossFlow(); - - for (int perf = 0; perf < this->number_of_perforations_; ++perf) { - const int cell_idx = this->well_cells_[perf]; - const auto& intQuants = ebosSimulator.model().intensiveQuantities(cell_idx, /*timeIdx=*/ 0); - // flux for each perforation - std::vector mob(this->num_components_, 0.); - getMobility(ebosSimulator, perf, mob, deferred_logger); - double trans_mult = ebosSimulator.problem().template rockCompTransMultiplier(intQuants, cell_idx); - const double Tw = this->wellIndex(perf) * trans_mult; - const double elapsed_time = ebosSimulator.time(); - - std::vector cq_s(this->num_components_, 0.); - PerforationRates perf_rates; - computePerfRate(intQuants, mob, bhp, Tw, perf, allow_cf, elapsed_time, - cq_s, perf_rates, deferred_logger); - - for(int p = 0; p < np; ++p) { - well_flux[this->ebosCompIdxToFlowCompIdx(p)] += cq_s[p]; - } - } - this->parallel_well_info_.communication().sum(well_flux.data(), well_flux.size()); - } - - - - template - void - StandardWell:: - computeWellRatesWithBhpIterations(const Simulator& ebosSimulator, - const double& bhp, - std::vector& well_flux, - DeferredLogger& deferred_logger) const - { - // creating a copy of the well itself, to avoid messing up the explicit information - // during this copy, the only information not copied properly is the well controls - StandardWell well_copy(*this); - - // iterate to get a more accurate well density - // create a copy of the well_state to use. If the operability checking is sucessful, we use this one - // to replace the original one - WellState well_state_copy = ebosSimulator.problem().wellModel().wellState(); - const auto& group_state = ebosSimulator.problem().wellModel().groupState(); - - // Get the current controls. - const auto& summary_state = ebosSimulator.vanguard().summaryState(); - auto inj_controls = well_copy.well_ecl_.isInjector() - ? well_copy.well_ecl_.injectionControls(summary_state) - : Well::InjectionControls(0); - auto prod_controls = well_copy.well_ecl_.isProducer() - ? well_copy.well_ecl_.productionControls(summary_state) : - Well::ProductionControls(0); - - // Set current control to bhp, and bhp value in state, modify bhp limit in control object. - auto& ws = well_state_copy.well(this->index_of_well_); - if (well_copy.well_ecl_.isInjector()) { - inj_controls.bhp_limit = bhp; - ws.injection_cmode = Well::InjectorCMode::BHP; - } else { - prod_controls.bhp_limit = bhp; - ws.production_cmode = Well::ProducerCMode::BHP; - } - ws.bhp = bhp; - - // initialized the well rates with the potentials i.e. the well rates based on bhp - const int np = this->number_of_phases_; - const double sign = this->well_ecl_.isInjector() ? 1.0 : -1.0; - for (int phase = 0; phase < np; ++phase){ - well_state_copy.wellRates(this->index_of_well_)[phase] - = sign * ws.well_potentials[phase]; - } - well_copy.calculateExplicitQuantities(ebosSimulator, well_state_copy, deferred_logger); - - const double dt = ebosSimulator.timeStepSize(); - const bool converged = well_copy.iterateWellEqWithControl(ebosSimulator, dt, inj_controls, prod_controls, well_state_copy, group_state, deferred_logger); - if (!converged) { - const std::string msg = " well " + name() + " did not get converged during well potential calculations " - " potentials are computed based on unconverged solution"; - deferred_logger.debug(msg); - } - well_copy.updatePrimaryVariables(summary_state, well_state_copy, deferred_logger); - well_copy.computeWellConnectionPressures(ebosSimulator, well_state_copy, deferred_logger); - well_copy.initPrimaryVariablesEvaluation(); - well_copy.computeWellRatesWithBhp(ebosSimulator, bhp, well_flux, deferred_logger); - } - - - - - template - std::vector - StandardWell:: - computeWellPotentialWithTHP(const Simulator& ebos_simulator, - DeferredLogger& deferred_logger, - const WellState &well_state) const - { - std::vector potentials(this->number_of_phases_, 0.0); - const auto& summary_state = ebos_simulator.vanguard().summaryState(); - - const auto& well = this->well_ecl_; - if (well.isInjector()){ - const auto& controls = this->well_ecl_.injectionControls(summary_state); - auto bhp_at_thp_limit = computeBhpAtThpLimitInj(ebos_simulator, summary_state, deferred_logger); - if (bhp_at_thp_limit) { - const double bhp = std::min(*bhp_at_thp_limit, controls.bhp_limit); - computeWellRatesWithBhp(ebos_simulator, bhp, potentials, deferred_logger); - } else { - deferred_logger.warning("FAILURE_GETTING_CONVERGED_POTENTIAL", - "Failed in getting converged thp based potential calculation for well " - + name() + ". Instead the bhp based value is used"); - const double bhp = controls.bhp_limit; - computeWellRatesWithBhp(ebos_simulator, bhp, potentials, deferred_logger); - } - } else { - computeWellRatesWithThpAlqProd( - ebos_simulator, summary_state, - deferred_logger, potentials, this->getALQ(well_state) - ); - } - - return potentials; - } - - - - template - double - StandardWell:: - computeWellRatesAndBhpWithThpAlqProd(const Simulator &ebos_simulator, - const SummaryState &summary_state, - DeferredLogger &deferred_logger, - std::vector &potentials, - double alq) const - { - double bhp; - auto bhp_at_thp_limit = computeBhpAtThpLimitProdWithAlq( - ebos_simulator, summary_state, alq, deferred_logger); - if (bhp_at_thp_limit) { - const auto& controls = this->well_ecl_.productionControls(summary_state); - bhp = std::max(*bhp_at_thp_limit, controls.bhp_limit); - computeWellRatesWithBhp(ebos_simulator, bhp, potentials, deferred_logger); - } - else { - deferred_logger.warning("FAILURE_GETTING_CONVERGED_POTENTIAL", - "Failed in getting converged thp based potential calculation for well " - + name() + ". Instead the bhp based value is used"); - const auto& controls = this->well_ecl_.productionControls(summary_state); - bhp = controls.bhp_limit; - computeWellRatesWithBhp(ebos_simulator, bhp, potentials, deferred_logger); - } - return bhp; - } - - template - void - StandardWell:: - computeWellRatesWithThpAlqProd(const Simulator &ebos_simulator, - const SummaryState &summary_state, - DeferredLogger &deferred_logger, - std::vector &potentials, - double alq) const - { - /*double bhp =*/ - computeWellRatesAndBhpWithThpAlqProd(ebos_simulator, - summary_state, - deferred_logger, - potentials, - alq); - } - - template - void - StandardWell:: - computeWellPotentials(const Simulator& ebosSimulator, - const WellState& well_state, - std::vector& well_potentials, - DeferredLogger& deferred_logger) // const - { - const auto [compute_potential, bhp_controlled_well] = - this->WellInterfaceGeneric::computeWellPotentials(well_potentials, well_state); - - if (!compute_potential) { - return; - } - - // does the well have a THP related constraint? - const auto& summaryState = ebosSimulator.vanguard().summaryState(); - if (!Base::wellHasTHPConstraints(summaryState) || bhp_controlled_well) { - // get the bhp value based on the bhp constraints - double bhp = WellBhpThpCalculator(*this).mostStrictBhpFromBhpLimits(summaryState); - - // In some very special cases the bhp pressure target are - // temporary violated. This may lead to too small or negative potentials - // that could lead to premature shutting of wells. - // As a remedy the bhp that gives the largest potential is used. - // For converged cases, ws.bhp <=bhp for injectors and ws.bhp >= bhp, - // and the potentials will be computed using the limit as expected. - const auto& ws = well_state.well(this->index_of_well_); - if (this->isInjector()) - bhp = std::max(ws.bhp, bhp); - else - bhp = std::min(ws.bhp, bhp); - - assert(std::abs(bhp) != std::numeric_limits::max()); - computeWellRatesWithBhpIterations(ebosSimulator, bhp, well_potentials, deferred_logger); - } else { - // the well has a THP related constraint - well_potentials = computeWellPotentialWithTHP(ebosSimulator, deferred_logger, well_state); - } - - this->checkNegativeWellPotentials(well_potentials, - this->param_.check_well_operability_, - deferred_logger); - } - - - - - - - - template - double - StandardWell:: - connectionDensity([[maybe_unused]] const int globalConnIdx, - const int openConnIdx) const - { - return (openConnIdx < 0) - ? 0.0 - : this->connections_.rho(openConnIdx); - } - - - - - - template - void - StandardWell:: - updatePrimaryVariables(const SummaryState& summary_state, - const WellState& well_state, - DeferredLogger& deferred_logger) - { - if (!this->isOperableAndSolvable() && !this->wellIsStopped()) return; - - const bool stop_or_zero_rate_target = this->stopppedOrZeroRateTarget(summary_state, well_state); - this->primary_variables_.update(well_state, stop_or_zero_rate_target, deferred_logger); - - // other primary variables related to polymer injection - if constexpr (Base::has_polymermw) { - this->primary_variables_.updatePolyMW(well_state); - } - - this->primary_variables_.checkFinite(deferred_logger); - } - - - - - template - double - StandardWell:: - getRefDensity() const - { - return this->connections_.rho(); - } - - - - - template - void - StandardWell:: - updateWaterMobilityWithPolymer(const Simulator& ebos_simulator, - const int perf, - std::vector& mob, - DeferredLogger& deferred_logger) const - { - const int cell_idx = this->well_cells_[perf]; - const auto& int_quant = ebos_simulator.model().intensiveQuantities(cell_idx, /*timeIdx=*/ 0); - const EvalWell polymer_concentration = this->extendEval(int_quant.polymerConcentration()); - - // TODO: not sure should based on the well type or injecting/producing peforations - // it can be different for crossflow - if (this->isInjector()) { - // assume fully mixing within injecting wellbore - const auto& visc_mult_table = PolymerModule::plyviscViscosityMultiplierTable(int_quant.pvtRegionIndex()); - const unsigned waterCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::waterCompIdx); - mob[waterCompIdx] /= (this->extendEval(int_quant.waterViscosityCorrection()) * visc_mult_table.eval(polymer_concentration, /*extrapolate=*/true) ); - } - - if (PolymerModule::hasPlyshlog()) { - // we do not calculate the shear effects for injection wells when they do not - // inject polymer. - if (this->isInjector() && this->wpolymer() == 0.) { - return; - } - // compute the well water velocity with out shear effects. - // TODO: do we need to turn on crossflow here? - const bool allow_cf = this->getAllowCrossFlow() || openCrossFlowAvoidSingularity(ebos_simulator); - const EvalWell& bhp = this->primary_variables_.eval(Bhp); - - std::vector cq_s(this->num_components_, {this->primary_variables_.numWellEq() + Indices::numEq, 0.}); - PerforationRates perf_rates; - double trans_mult = ebos_simulator.problem().template rockCompTransMultiplier(int_quant, cell_idx); - const double elapsed_time = ebos_simulator.time(); - const double Tw = this->wellIndex(perf) * trans_mult; - computePerfRate(int_quant, mob, bhp, Tw, perf, allow_cf, elapsed_time, cq_s, - perf_rates, deferred_logger); - // TODO: make area a member - const double area = 2 * M_PI * this->perf_rep_radius_[perf] * this->perf_length_[perf]; - const auto& material_law_manager = ebos_simulator.problem().materialLawManager(); - const auto& scaled_drainage_info = - material_law_manager->oilWaterScaledEpsInfoDrainage(cell_idx); - const double swcr = scaled_drainage_info.Swcr; - const EvalWell poro = this->extendEval(int_quant.porosity()); - const EvalWell sw = this->extendEval(int_quant.fluidState().saturation(FluidSystem::waterPhaseIdx)); - // guard against zero porosity and no water - const EvalWell denom = max( (area * poro * (sw - swcr)), 1e-12); - const unsigned waterCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::waterCompIdx); - EvalWell water_velocity = cq_s[waterCompIdx] / denom * this->extendEval(int_quant.fluidState().invB(FluidSystem::waterPhaseIdx)); - - if (PolymerModule::hasShrate()) { - // the equation for the water velocity conversion for the wells and reservoir are from different version - // of implementation. It can be changed to be more consistent when possible. - water_velocity *= PolymerModule::shrate( int_quant.pvtRegionIndex() ) / this->bore_diameters_[perf]; - } - const EvalWell shear_factor = PolymerModule::computeShearFactor(polymer_concentration, - int_quant.pvtRegionIndex(), - water_velocity); - // modify the mobility with the shear factor. - mob[waterCompIdx] /= shear_factor; - } - } - - template - void - StandardWell::addWellContributions(SparseMatrixAdapter& jacobian) const - { - this->linSys_.extract(jacobian); - } - - - template - void - StandardWell::addWellPressureEquations(PressureMatrix& jacobian, - const BVector& weights, - const int pressureVarIndex, - const bool use_well_weights, - const WellState& well_state) const - { - this->linSys_.extractCPRPressureMatrix(jacobian, - weights, - pressureVarIndex, - use_well_weights, - *this, - Bhp, - well_state); - } - - - - template - typename StandardWell::EvalWell - StandardWell:: - pskinwater(const double throughput, - const EvalWell& water_velocity, - DeferredLogger& deferred_logger) const - { - if constexpr (Base::has_polymermw) { - const int water_table_id = this->polymerWaterTable_(); - if (water_table_id <= 0) { - OPM_DEFLOG_THROW(std::runtime_error, - fmt::format("Unused SKPRWAT table id used for well {}", name()), - deferred_logger); - } - const auto& water_table_func = PolymerModule::getSkprwatTable(water_table_id); - const EvalWell throughput_eval(this->primary_variables_.numWellEq() + Indices::numEq, throughput); - // the skin pressure when injecting water, which also means the polymer concentration is zero - EvalWell pskin_water(this->primary_variables_.numWellEq() + Indices::numEq, 0.0); - pskin_water = water_table_func.eval(throughput_eval, water_velocity); - return pskin_water; - } else { - OPM_DEFLOG_THROW(std::runtime_error, - fmt::format("Polymermw is not activated, while injecting " - "skin pressure is requested for well {}", name()), - deferred_logger); - } - } - - - - - - template - typename StandardWell::EvalWell - StandardWell:: - pskin(const double throughput, - const EvalWell& water_velocity, - const EvalWell& poly_inj_conc, - DeferredLogger& deferred_logger) const - { - if constexpr (Base::has_polymermw) { - const double sign = water_velocity >= 0. ? 1.0 : -1.0; - const EvalWell water_velocity_abs = abs(water_velocity); - if (poly_inj_conc == 0.) { - return sign * pskinwater(throughput, water_velocity_abs, deferred_logger); - } - const int polymer_table_id = this->polymerTable_(); - if (polymer_table_id <= 0) { - OPM_DEFLOG_THROW(std::runtime_error, - fmt::format("Unavailable SKPRPOLY table id used for well {}", name()), - deferred_logger); - } - const auto& skprpolytable = PolymerModule::getSkprpolyTable(polymer_table_id); - const double reference_concentration = skprpolytable.refConcentration; - const EvalWell throughput_eval(this->primary_variables_.numWellEq() + Indices::numEq, throughput); - // the skin pressure when injecting water, which also means the polymer concentration is zero - EvalWell pskin_poly(this->primary_variables_.numWellEq() + Indices::numEq, 0.0); - pskin_poly = skprpolytable.table_func.eval(throughput_eval, water_velocity_abs); - if (poly_inj_conc == reference_concentration) { - return sign * pskin_poly; - } - // poly_inj_conc != reference concentration of the table, then some interpolation will be required - const EvalWell pskin_water = pskinwater(throughput, water_velocity_abs, deferred_logger); - const EvalWell pskin = pskin_water + (pskin_poly - pskin_water) / reference_concentration * poly_inj_conc; - return sign * pskin; - } else { - OPM_DEFLOG_THROW(std::runtime_error, - fmt::format("Polymermw is not activated, while injecting " - "skin pressure is requested for well {}", name()), - deferred_logger); - } - } - - - - - - template - typename StandardWell::EvalWell - StandardWell:: - wpolymermw(const double throughput, - const EvalWell& water_velocity, - DeferredLogger& deferred_logger) const - { - if constexpr (Base::has_polymermw) { - const int table_id = this->polymerInjTable_(); - const auto& table_func = PolymerModule::getPlymwinjTable(table_id); - const EvalWell throughput_eval(this->primary_variables_.numWellEq() + Indices::numEq, throughput); - EvalWell molecular_weight(this->primary_variables_.numWellEq() + Indices::numEq, 0.); - if (this->wpolymer() == 0.) { // not injecting polymer - return molecular_weight; - } - molecular_weight = table_func.eval(throughput_eval, abs(water_velocity)); - return molecular_weight; - } else { - OPM_DEFLOG_THROW(std::runtime_error, - fmt::format("Polymermw is not activated, while injecting " - "polymer molecular weight is requested for well {}", name()), - deferred_logger); - } - } - - - - - - template - void - StandardWell:: - updateWaterThroughput(const double dt, WellState &well_state) const - { - if constexpr (Base::has_polymermw) { - if (this->isInjector()) { - auto& ws = well_state.well(this->index_of_well_); - auto& perf_water_throughput = ws.perf_data.water_throughput; - for (int perf = 0; perf < this->number_of_perforations_; ++perf) { - const double perf_water_vel = this->primary_variables_.value(Bhp + 1 + perf); - // we do not consider the formation damage due to water flowing from reservoir into wellbore - if (perf_water_vel > 0.) { - perf_water_throughput[perf] += perf_water_vel * dt; - } - } - } - } - } - - - - - - template - void - StandardWell:: - handleInjectivityRate(const Simulator& ebosSimulator, - const int perf, - std::vector& cq_s) const - { - const int cell_idx = this->well_cells_[perf]; - const auto& int_quants = ebosSimulator.model().intensiveQuantities(cell_idx, /*timeIdx=*/ 0); - const auto& fs = int_quants.fluidState(); - const EvalWell b_w = this->extendEval(fs.invB(FluidSystem::waterPhaseIdx)); - const double area = M_PI * this->bore_diameters_[perf] * this->perf_length_[perf]; - const int wat_vel_index = Bhp + 1 + perf; - const unsigned water_comp_idx = Indices::canonicalToActiveComponentIndex(FluidSystem::waterCompIdx); - - // water rate is update to use the form from water velocity, since water velocity is - // a primary variable now - cq_s[water_comp_idx] = area * this->primary_variables_.eval(wat_vel_index) * b_w; - } - - - - - template - void - StandardWell:: - handleInjectivityEquations(const Simulator& ebosSimulator, - const WellState& well_state, - const int perf, - const EvalWell& water_flux_s, - DeferredLogger& deferred_logger) - { - const int cell_idx = this->well_cells_[perf]; - const auto& int_quants = ebosSimulator.model().intensiveQuantities(cell_idx, /*timeIdx=*/ 0); - const auto& fs = int_quants.fluidState(); - const EvalWell b_w = this->extendEval(fs.invB(FluidSystem::waterPhaseIdx)); - const EvalWell water_flux_r = water_flux_s / b_w; - const double area = M_PI * this->bore_diameters_[perf] * this->perf_length_[perf]; - const EvalWell water_velocity = water_flux_r / area; - const int wat_vel_index = Bhp + 1 + perf; - - // equation for the water velocity - const EvalWell eq_wat_vel = this->primary_variables_.eval(wat_vel_index) - water_velocity; - - const auto& ws = well_state.well(this->index_of_well_); - const auto& perf_data = ws.perf_data; - const auto& perf_water_throughput = perf_data.water_throughput; - const double throughput = perf_water_throughput[perf]; - const int pskin_index = Bhp + 1 + this->number_of_perforations_ + perf; - - EvalWell poly_conc(this->primary_variables_.numWellEq() + Indices::numEq, 0.0); - poly_conc.setValue(this->wpolymer()); - - // equation for the skin pressure - const EvalWell eq_pskin = this->primary_variables_.eval(pskin_index) - - pskin(throughput, this->primary_variables_.eval(wat_vel_index), poly_conc, deferred_logger); - - StandardWellAssemble(*this). - assembleInjectivityEq(eq_pskin, - eq_wat_vel, - pskin_index, - wat_vel_index, - cell_idx, - this->primary_variables_.numWellEq(), - this->linSys_); - } - - - - - - template - void - StandardWell:: - checkConvergenceExtraEqs(const std::vector& res, - ConvergenceReport& report) const - { - // if different types of extra equations are involved, this function needs to be refactored further - - // checking the convergence of the extra equations related to polymer injectivity - if constexpr (Base::has_polymermw) { - WellConvergence(*this). - checkConvergencePolyMW(res, Bhp, this->param_.max_residual_allowed_, report); - } - } - - - - - - template - void - StandardWell:: - updateConnectionRatePolyMW(const EvalWell& cq_s_poly, - const IntensiveQuantities& int_quants, - const WellState& well_state, - const int perf, - std::vector& connectionRates, - DeferredLogger& deferred_logger) const - { - // the source term related to transport of molecular weight - EvalWell cq_s_polymw = cq_s_poly; - if (this->isInjector()) { - const int wat_vel_index = Bhp + 1 + perf; - const EvalWell water_velocity = this->primary_variables_.eval(wat_vel_index); - if (water_velocity > 0.) { // injecting - const auto& ws = well_state.well(this->index_of_well_); - const auto& perf_water_throughput = ws.perf_data.water_throughput; - const double throughput = perf_water_throughput[perf]; - const EvalWell molecular_weight = wpolymermw(throughput, water_velocity, deferred_logger); - cq_s_polymw *= molecular_weight; - } else { - // we do not consider the molecular weight from the polymer - // going-back to the wellbore through injector - cq_s_polymw *= 0.; - } - } else if (this->isProducer()) { - if (cq_s_polymw < 0.) { - cq_s_polymw *= this->extendEval(int_quants.polymerMoleWeight() ); - } else { - // we do not consider the molecular weight from the polymer - // re-injecting back through producer - cq_s_polymw *= 0.; - } - } - connectionRates[perf][Indices::contiPolymerMWEqIdx] = Base::restrictEval(cq_s_polymw); - } - - - - - - - template - std::optional - StandardWell:: - computeBhpAtThpLimitProd(const WellState& well_state, - const Simulator& ebos_simulator, - const SummaryState& summary_state, - DeferredLogger& deferred_logger) const - { - return computeBhpAtThpLimitProdWithAlq(ebos_simulator, - summary_state, - this->getALQ(well_state), - deferred_logger); - } - - template - std::optional - StandardWell:: - computeBhpAtThpLimitProdWithAlq(const Simulator& ebos_simulator, - const SummaryState& summary_state, - const double alq_value, - DeferredLogger& deferred_logger) const - { - // Make the frates() function. - auto frates = [this, &ebos_simulator, &deferred_logger](const double bhp) { - // Not solving the well equations here, which means we are - // calculating at the current Fg/Fw values of the - // well. This does not matter unless the well is - // crossflowing, and then it is likely still a good - // approximation. - std::vector rates(3); - computeWellRatesWithBhp(ebos_simulator, bhp, rates, deferred_logger); - this->adaptRatesForVFP(rates); - return rates; - }; - - double max_pressure = 0.0; - for (int perf = 0; perf < this->number_of_perforations_; ++perf) { - const int cell_idx = this->well_cells_[perf]; - const auto& int_quants = ebos_simulator.model().intensiveQuantities(cell_idx, /*timeIdx=*/ 0); - const auto& fs = int_quants.fluidState(); - double pressure_cell = this->getPerfCellPressure(fs).value(); - max_pressure = std::max(max_pressure, pressure_cell); - } - auto bhpAtLimit = WellBhpThpCalculator(*this).computeBhpAtThpLimitProd(frates, - summary_state, - max_pressure, - this->connections_.rho(), - alq_value, - this->getTHPConstraint(summary_state), - deferred_logger); - - if (bhpAtLimit) { - auto v = frates(*bhpAtLimit); - if (std::all_of(v.cbegin(), v.cend(), [](double i){ return i <= 0; }) ) { - return bhpAtLimit; - } - } - - - auto fratesIter = [this, &ebos_simulator, &deferred_logger](const double bhp) { - // Solver the well iterations to see if we are - // able to get a solution with an update - // solution - std::vector rates(3); - computeWellRatesWithBhpIterations(ebos_simulator, bhp, rates, deferred_logger); - this->adaptRatesForVFP(rates); - return rates; - }; - - bhpAtLimit = WellBhpThpCalculator(*this).computeBhpAtThpLimitProd(fratesIter, - summary_state, - max_pressure, - this->connections_.rho(), - alq_value, - this->getTHPConstraint(summary_state), - deferred_logger); - - - if (bhpAtLimit) { - // should we use fratesIter here since fratesIter is used in computeBhpAtThpLimitProd above? - auto v = frates(*bhpAtLimit); - if (std::all_of(v.cbegin(), v.cend(), [](double i){ return i <= 0; }) ) { - return bhpAtLimit; - } - } - - // we still don't get a valied solution. - return std::nullopt; - } - - - - template - std::optional - StandardWell:: - computeBhpAtThpLimitInj(const Simulator& ebos_simulator, - const SummaryState& summary_state, - DeferredLogger& deferred_logger) const - { - // Make the frates() function. - auto frates = [this, &ebos_simulator, &deferred_logger](const double bhp) { - // Not solving the well equations here, which means we are - // calculating at the current Fg/Fw values of the - // well. This does not matter unless the well is - // crossflowing, and then it is likely still a good - // approximation. - std::vector rates(3); - computeWellRatesWithBhp(ebos_simulator, bhp, rates, deferred_logger); - return rates; - }; - - return WellBhpThpCalculator(*this).computeBhpAtThpLimitInj(frates, - summary_state, - this->connections_.rho(), - 1e-6, - 50, - true, - deferred_logger); - } - - - - - - template - bool - StandardWell:: - iterateWellEqWithControl(const Simulator& ebosSimulator, - const double dt, - const Well::InjectionControls& inj_controls, - const Well::ProductionControls& prod_controls, - WellState& well_state, - const GroupState& group_state, - DeferredLogger& deferred_logger) - { - const int max_iter = this->param_.max_inner_iter_wells_; - int it = 0; - bool converged; - bool relax_convergence = false; - this->regularize_ = false; - const auto& summary_state = ebosSimulator.vanguard().summaryState(); - do { - assembleWellEqWithoutIteration(ebosSimulator, dt, inj_controls, prod_controls, well_state, group_state, deferred_logger); - - if (it > this->param_.strict_inner_iter_wells_) { - relax_convergence = true; - this->regularize_ = true; - } - - auto report = getWellConvergence(summary_state, well_state, Base::B_avg_, deferred_logger, relax_convergence); - - converged = report.converged(); - if (converged) { - break; - } - - ++it; - solveEqAndUpdateWellState(summary_state, well_state, deferred_logger); - - // TODO: when this function is used for well testing purposes, will need to check the controls, so that we will obtain convergence - // under the most restrictive control. Based on this converged results, we can check whether to re-open the well. Either we refactor - // this function or we use different functions for the well testing purposes. - // We don't allow for switching well controls while computing well potentials and testing wells - // updateWellControl(ebosSimulator, well_state, deferred_logger); - initPrimaryVariablesEvaluation(); - } while (it < max_iter); - - return converged; - } - - - template - std::vector - StandardWell:: - computeCurrentWellRates(const Simulator& ebosSimulator, - DeferredLogger& deferred_logger) const - { - // Calculate the rates that follow from the current primary variables. - std::vector well_q_s(this->num_components_, 0.); - const EvalWell& bhp = this->primary_variables_.eval(Bhp); - const bool allow_cf = this->getAllowCrossFlow() || openCrossFlowAvoidSingularity(ebosSimulator); - for (int perf = 0; perf < this->number_of_perforations_; ++perf) { - const int cell_idx = this->well_cells_[perf]; - const auto& intQuants = ebosSimulator.model().intensiveQuantities(cell_idx, /*timeIdx=*/ 0); - std::vector mob(this->num_components_, 0.); - getMobility(ebosSimulator, perf, mob, deferred_logger); - std::vector cq_s(this->num_components_, 0.); - double trans_mult = ebosSimulator.problem().template rockCompTransMultiplier(intQuants, cell_idx); - double elapsed_time = ebosSimulator.time(); - const double Tw = this->wellIndex(perf) * trans_mult; - PerforationRates perf_rates; - computePerfRate(intQuants, mob, bhp.value(), Tw, perf, allow_cf, elapsed_time, - cq_s, perf_rates, deferred_logger); - for (int comp = 0; comp < this->num_components_; ++comp) { - well_q_s[comp] += cq_s[comp]; - } - } - const auto& comm = this->parallel_well_info_.communication(); - if (comm.size() > 1) - { - comm.sum(well_q_s.data(), well_q_s.size()); - } - return well_q_s; - } - - - - template - std::vector - StandardWell:: - getPrimaryVars() const - { - const int num_pri_vars = this->primary_variables_.numWellEq(); - std::vector retval(num_pri_vars); - for (int ii = 0; ii < num_pri_vars; ++ii) { - retval[ii] = this->primary_variables_.value(ii); - } - return retval; - } - - - - - - template - int - StandardWell:: - setPrimaryVars(std::vector::const_iterator it) - { - const int num_pri_vars = this->primary_variables_.numWellEq(); - for (int ii = 0; ii < num_pri_vars; ++ii) { - this->primary_variables_.setValue(ii, it[ii]); - } - return num_pri_vars; - } - - - template - typename StandardWell::Eval - StandardWell:: - connectionRateEnergy(const double maxOilSaturation, - const std::vector& cq_s, - const IntensiveQuantities& intQuants, - DeferredLogger& deferred_logger) const - { - auto fs = intQuants.fluidState(); - Eval result = 0; - for (unsigned phaseIdx = 0; phaseIdx < FluidSystem::numPhases; ++phaseIdx) { - if (!FluidSystem::phaseIsActive(phaseIdx)) { - continue; - } - - // convert to reservoir conditions - EvalWell cq_r_thermal(this->primary_variables_.numWellEq() + Indices::numEq, 0.); - const unsigned activeCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::solventComponentIndex(phaseIdx)); - const bool both_oil_gas = FluidSystem::phaseIsActive(FluidSystem::oilPhaseIdx) && FluidSystem::phaseIsActive(FluidSystem::gasPhaseIdx); - if (!both_oil_gas || FluidSystem::waterPhaseIdx == phaseIdx) { - cq_r_thermal = cq_s[activeCompIdx] / this->extendEval(fs.invB(phaseIdx)); - } else { - // remove dissolved gas and vapporized oil - const unsigned oilCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::oilCompIdx); - const unsigned gasCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::gasCompIdx); - // q_os = q_or * b_o + rv * q_gr * b_g - // q_gs = q_gr * g_g + rs * q_or * b_o - // q_gr = 1 / (b_g * d) * (q_gs - rs * q_os) - // d = 1.0 - rs * rv - const EvalWell d = this->extendEval(1.0 - fs.Rv() * fs.Rs()); - if (d <= 0.0) { - deferred_logger.debug( - fmt::format("Problematic d value {} obtained for well {}" - " during calculateSinglePerf with rs {}" - ", rv {}. Continue as if no dissolution (rs = 0) and" - " vaporization (rv = 0) for this connection.", - d, this->name(), fs.Rs(), fs.Rv())); - cq_r_thermal = cq_s[activeCompIdx] / this->extendEval(fs.invB(phaseIdx)); - } else { - if (FluidSystem::gasPhaseIdx == phaseIdx) { - cq_r_thermal = (cq_s[gasCompIdx] - - this->extendEval(fs.Rs()) * cq_s[oilCompIdx]) / - (d * this->extendEval(fs.invB(phaseIdx)) ); - } else if (FluidSystem::oilPhaseIdx == phaseIdx) { - // q_or = 1 / (b_o * d) * (q_os - rv * q_gs) - cq_r_thermal = (cq_s[oilCompIdx] - this->extendEval(fs.Rv()) * - cq_s[gasCompIdx]) / - (d * this->extendEval(fs.invB(phaseIdx)) ); - } - } - } - - // change temperature for injecting fluids - if (this->isInjector() && cq_s[activeCompIdx] > 0.0){ - // only handles single phase injection now - assert(this->well_ecl_.injectorType() != InjectorType::MULTI); - fs.setTemperature(this->well_ecl_.temperature()); - typedef typename std::decay::type::Scalar FsScalar; - typename FluidSystem::template ParameterCache paramCache; - const unsigned pvtRegionIdx = intQuants.pvtRegionIndex(); - paramCache.setRegionIndex(pvtRegionIdx); - paramCache.setMaxOilSat(maxOilSaturation); - paramCache.updatePhase(fs, phaseIdx); - - const auto& rho = FluidSystem::density(fs, paramCache, phaseIdx); - fs.setDensity(phaseIdx, rho); - const auto& h = FluidSystem::enthalpy(fs, paramCache, phaseIdx); - fs.setEnthalpy(phaseIdx, h); - cq_r_thermal *= this->extendEval(fs.enthalpy(phaseIdx)) * this->extendEval(fs.density(phaseIdx)); - result += getValue(cq_r_thermal); - } else { - // compute the thermal flux - cq_r_thermal *= this->extendEval(fs.enthalpy(phaseIdx)) * this->extendEval(fs.density(phaseIdx)); - result += Base::restrictEval(cq_r_thermal); - } - } - - return result; - } - - - template - template - void - StandardWell:: - gasOilPerfRateInj(const std::vector& cq_s, - PerforationRates& perf_rates, - const Value& rv, - const Value& rs, - const Value& pressure, - const Value& rvw, - DeferredLogger& deferred_logger) const - { - const unsigned oilCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::oilCompIdx); - const unsigned gasCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::gasCompIdx); - // TODO: the formulations here remain to be tested with cases with strong crossflow through production wells - // s means standard condition, r means reservoir condition - // q_os = q_or * b_o + rv * q_gr * b_g - // q_gs = q_gr * b_g + rs * q_or * b_o - // d = 1.0 - rs * rv - // q_or = 1 / (b_o * d) * (q_os - rv * q_gs) - // q_gr = 1 / (b_g * d) * (q_gs - rs * q_os) - - const double d = 1.0 - getValue(rv) * getValue(rs); - - if (d <= 0.0) { - deferred_logger.debug(dValueError(d, this->name(), - "gasOilPerfRateInj", - rs, rv, pressure)); - } else { - // vaporized oil into gas - // rv * q_gr * b_g = rv * (q_gs - rs * q_os) / d - perf_rates.vap_oil = getValue(rv) * (getValue(cq_s[gasCompIdx]) - getValue(rs) * getValue(cq_s[oilCompIdx])) / d; - // dissolved of gas in oil - // rs * q_or * b_o = rs * (q_os - rv * q_gs) / d - perf_rates.dis_gas = getValue(rs) * (getValue(cq_s[oilCompIdx]) - getValue(rv) * getValue(cq_s[gasCompIdx])) / d; - } - - if (FluidSystem::phaseIsActive(FluidSystem::waterPhaseIdx)) { - // q_ws = q_wr * b_w + rvw * q_gr * b_g - // q_wr = 1 / b_w * (q_ws - rvw * q_gr * b_g) = 1 / b_w * (q_ws - rvw * 1 / d (q_gs - rs * q_os)) - // vaporized water in gas - // rvw * q_gr * b_g = q_ws -q_wr *b_w = rvw * (q_gs -rs *q_os) / d - perf_rates.vap_wat = getValue(rvw) * (getValue(cq_s[gasCompIdx]) - getValue(rs) * getValue(cq_s[oilCompIdx])) / d; - } - } - - - - template - template - void - StandardWell:: - gasOilPerfRateProd(std::vector& cq_s, - PerforationRates& perf_rates, - const Value& rv, - const Value& rs, - const Value& rvw) const - { - const unsigned oilCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::oilCompIdx); - const unsigned gasCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::gasCompIdx); - const Value cq_sOil = cq_s[oilCompIdx]; - const Value cq_sGas = cq_s[gasCompIdx]; - const Value dis_gas = rs * cq_sOil; - const Value vap_oil = rv * cq_sGas; - - cq_s[gasCompIdx] += dis_gas; - cq_s[oilCompIdx] += vap_oil; - - // recording the perforation solution gas rate and solution oil rates - if (this->isProducer()) { - perf_rates.dis_gas = getValue(dis_gas); - perf_rates.vap_oil = getValue(vap_oil); - } - - if (FluidSystem::phaseIsActive(FluidSystem::waterPhaseIdx)) { - const unsigned waterCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::waterCompIdx); - const Value vap_wat = rvw * cq_sGas; - cq_s[waterCompIdx] += vap_wat; - if (this->isProducer()) - perf_rates.vap_wat = getValue(vap_wat); - } - } - - - template - template - void - StandardWell:: - gasWaterPerfRateProd(std::vector& cq_s, - PerforationRates& perf_rates, - const Value& rvw, - const Value& rsw) const - { - const unsigned waterCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::waterCompIdx); - const unsigned gasCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::gasCompIdx); - const Value cq_sWat = cq_s[waterCompIdx]; - const Value cq_sGas = cq_s[gasCompIdx]; - const Value vap_wat = rvw * cq_sGas; - const Value dis_gas_wat = rsw * cq_sWat; - cq_s[waterCompIdx] += vap_wat; - cq_s[gasCompIdx] += dis_gas_wat; - if (this->isProducer()) { - perf_rates.vap_wat = getValue(vap_wat); - perf_rates.dis_gas_in_water = getValue(dis_gas_wat); - } - } - - - template - template - void - StandardWell:: - gasWaterPerfRateInj(const std::vector& cq_s, - PerforationRates& perf_rates, - const Value& rvw, - const Value& rsw, - const Value& pressure, - DeferredLogger& deferred_logger) const - - { - const unsigned gasCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::gasCompIdx); - const unsigned waterCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::waterCompIdx); - - const double dw = 1.0 - getValue(rvw) * getValue(rsw); - - if (dw <= 0.0) { - deferred_logger.debug(dValueError(dw, this->name(), - "gasWaterPerfRateInj", - rsw, rvw, pressure)); - } else { - // vaporized water into gas - // rvw * q_gr * b_g = rvw * (q_gs - rsw * q_ws) / dw - perf_rates.vap_wat = getValue(rvw) * (getValue(cq_s[gasCompIdx]) - getValue(rsw) * getValue(cq_s[waterCompIdx])) / dw; - // dissolved gas in water - // rsw * q_wr * b_w = rsw * (q_ws - rvw * q_gs) / dw - perf_rates.dis_gas_in_water = getValue(rsw) * (getValue(cq_s[waterCompIdx]) - getValue(rvw) * getValue(cq_s[gasCompIdx])) / dw; - } - } - - - template - template - void - StandardWell:: - disOilVapWatVolumeRatio(Value& volumeRatio, - const Value& rvw, - const Value& rsw, - const Value& pressure, - const std::vector& cmix_s, - const std::vector& b_perfcells_dense, - DeferredLogger& deferred_logger) const - { - const unsigned waterCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::waterCompIdx); - const unsigned gasCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::gasCompIdx); - // Incorporate RSW/RVW factors if both water and gas active - const Value d = 1.0 - rvw * rsw; - - if (d <= 0.0) { - deferred_logger.debug(dValueError(d, this->name(), - "disOilVapWatVolumeRatio", - rsw, rvw, pressure)); - } - const Value tmp_wat = d > 0.0 ? (cmix_s[waterCompIdx] - rvw * cmix_s[gasCompIdx]) / d - : cmix_s[waterCompIdx]; - volumeRatio += tmp_wat / b_perfcells_dense[waterCompIdx]; - - const Value tmp_gas = d > 0.0 ? (cmix_s[gasCompIdx] - rsw * cmix_s[waterCompIdx]) / d - : cmix_s[waterCompIdx]; - volumeRatio += tmp_gas / b_perfcells_dense[gasCompIdx]; - } - - - template - template - void - StandardWell:: - gasOilVolumeRatio(Value& volumeRatio, - const Value& rv, - const Value& rs, - const Value& pressure, - const std::vector& cmix_s, - const std::vector& b_perfcells_dense, - DeferredLogger& deferred_logger) const - { - const unsigned oilCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::oilCompIdx); - const unsigned gasCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::gasCompIdx); - // Incorporate RS/RV factors if both oil and gas active - const Value d = 1.0 - rv * rs; - - if (d <= 0.0) { - deferred_logger.debug(dValueError(d, this->name(), - "gasOilVolumeRatio", - rs, rv, pressure)); - } - const Value tmp_oil = d > 0.0? (cmix_s[oilCompIdx] - rv * cmix_s[gasCompIdx]) / d : cmix_s[oilCompIdx]; - volumeRatio += tmp_oil / b_perfcells_dense[oilCompIdx]; - - const Value tmp_gas = d > 0.0? (cmix_s[gasCompIdx] - rs * cmix_s[oilCompIdx]) / d : cmix_s[gasCompIdx]; - volumeRatio += tmp_gas / b_perfcells_dense[gasCompIdx]; - } - - template - template - Value - StandardWell::wellIndexEval(const int perf, const double elapsed_time, const Value& pressure) const { - KerasModel model; - model.LoadModel(Base::ml_wi_filename_); - const auto& connection = Base::well_ecl_.getConnections()[perf]; - - // Run prediction. - - // give number of input parameters - Tensor in{3}; - - const auto p = scaleFunction(pressure, ${xmin[0]}, ${xmax[0]}); - const auto t = Value(scaleFunction(elapsed_time / 3600, ${xmin[1]}, ${xmax[1]})); - //const auto t = Value(scaleFunction(240, ${xmin[1]}, ${xmax[1]})); - const auto re = Value(scaleFunction(connection.r0(), ${xmin[2]}, ${xmax[2]})); - // note order need to be the same as under training - in.data_ = {{p, t, re}}; - Tensor out; - model.Apply(&in, &out); - std::cout << "t: " << elapsed_time / 3600 << " re: " << connection.r0() << " WI: " << - unscaleFunction(out.data_[0],${ymin}, ${ymax}) << std::endl; - std::cout << "t_scaled: " << t << " re_scaled: " << re << " WI_scaled: " << - getValue(out.data_[0]) << std::endl; - return unscaleFunction(out.data_[0],${ymin}, ${ymax}); - } - - template - template - Value - StandardWell::scaleFunction(Value X, double min, double max) const { - return (X - min) / (max - min); -} - - template - template - Value - StandardWell::unscaleFunction(Value X, double min, double max) const { - return X * (max - min) + min; -} - -} // namespace Opm diff --git a/src/pyopmnearwell/templates/standardwell_impl/co2_5_inputs.mako b/src/pyopmnearwell/templates/standardwell_impl/co2_5_inputs.mako deleted file mode 100644 index c6471cf..0000000 --- a/src/pyopmnearwell/templates/standardwell_impl/co2_5_inputs.mako +++ /dev/null @@ -1,2583 +0,0 @@ -/* - Copyright 2017 SINTEF Digital, Mathematics and Cybernetics. - Copyright 2017 Statoil ASA. - Copyright 2016 - 2017 IRIS AS. - - This file is part of the Open Porous Media project (OPM). - - OPM is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - OPM is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with OPM. If not, see . -*/ - -#include - -#include - -#include - -#include -#include -#include -#include -#include - -#include - -#include - -#include -#include -#include - - -namespace { - -template -auto dValueError(const dValue& d, - const std::string& name, - const std::string& methodName, - const Value& Rs, - const Value& Rv, - const Value& pressure) -{ - return fmt::format("Problematic d value {} obtained for well {}" - " during {} calculations with rs {}" - ", rv {} and pressure {}." - " Continue as if no dissolution (rs = 0) and vaporization (rv = 0) " - " for this connection.", d, name, methodName, Rs, Rv, pressure); -} - -} - -namespace Opm -{ - - template - StandardWell:: - StandardWell(const Well& well, - const ParallelWellInfo& pw_info, - const int time_step, - const ModelParameters& param, - const RateConverterType& rate_converter, - const int pvtRegionIdx, - const int num_components, - const int num_phases, - const int index_of_well, - const std::vector& perf_data) - : Base(well, pw_info, time_step, param, rate_converter, pvtRegionIdx, num_components, num_phases, index_of_well, perf_data) - , StdWellEval(static_cast&>(*this)) - , regularize_(false) - { - assert(this->num_components_ == numWellConservationEq); - } - - - - - - template - void - StandardWell:: - init(const PhaseUsage* phase_usage_arg, - const std::vector& depth_arg, - const double gravity_arg, - const int num_cells, - const std::vector< Scalar >& B_avg, - const bool changed_to_open_this_step) - { - Base::init(phase_usage_arg, depth_arg, gravity_arg, num_cells, B_avg, changed_to_open_this_step); - this->StdWellEval::init(this->perf_depth_, depth_arg, num_cells, Base::has_polymermw); - } - - - - - - template - void StandardWell:: - initPrimaryVariablesEvaluation() - { - this->primary_variables_.init(); - } - - - - - - template - template - void - StandardWell:: - computePerfRate(const IntensiveQuantities& intQuants, - const std::vector& mob, - const Value& bhp, - const double Tw, - const int perf, - const bool allow_cf, - const double elapsed_time, - std::vector& cq_s, - PerforationRates& perf_rates, - DeferredLogger& deferred_logger) const - { - auto obtain = [this](const Eval& value) - { - if constexpr (std::is_same_v) { - static_cast(this); // suppress clang warning - return getValue(value); - } else { - return this->extendEval(value); - } - }; - auto obtainN = [](const auto& value) - { - if constexpr (std::is_same_v) { - return getValue(value); - } else { - return value; - } - }; - auto zeroElem = [this]() - { - if constexpr (std::is_same_v) { - static_cast(this); // suppress clang warning - return 0.0; - } else { - return Value{this->primary_variables_.numWellEq() + Indices::numEq, 0.0}; - } - }; - - const auto& fs = intQuants.fluidState(); - const Value pressure = obtain(this->getPerfCellPressure(fs)); - const Value rs = obtain(fs.Rs()); - const Value rv = obtain(fs.Rv()); - const Value rvw = obtain(fs.Rvw()); - const Value rsw = obtain(fs.Rsw()); - - std::vector b_perfcells_dense(this->numComponents(), zeroElem()); - for (unsigned phaseIdx = 0; phaseIdx < FluidSystem::numPhases; ++phaseIdx) { - if (!FluidSystem::phaseIsActive(phaseIdx)) { - continue; - } - const unsigned compIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::solventComponentIndex(phaseIdx)); - b_perfcells_dense[compIdx] = obtain(fs.invB(phaseIdx)); - } - if constexpr (has_solvent) { - b_perfcells_dense[Indices::contiSolventEqIdx] = obtain(intQuants.solventInverseFormationVolumeFactor()); - } - - if constexpr (has_zFraction) { - if (this->isInjector()) { - const unsigned gasCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::gasCompIdx); - b_perfcells_dense[gasCompIdx] *= (1.0 - this->wsolvent()); - b_perfcells_dense[gasCompIdx] += this->wsolvent()*intQuants.zPureInvFormationVolumeFactor().value(); - } - } - - Value skin_pressure = zeroElem(); - if (has_polymermw) { - if (this->isInjector()) { - const int pskin_index = Bhp + 1 + this->numPerfs() + perf; - skin_pressure = obtainN(this->primary_variables_.eval(pskin_index)); - } - } - - // surface volume fraction of fluids within wellbore - std::vector cmix_s(this->numComponents(), zeroElem()); - for (int componentIdx = 0; componentIdx < this->numComponents(); ++componentIdx) { - cmix_s[componentIdx] = obtainN(this->primary_variables_.surfaceVolumeFraction(componentIdx)); - } - - computePerfRate(mob, - pressure, - bhp, - rs, - rv, - rvw, - rsw, - b_perfcells_dense, - Tw, - perf, - allow_cf, - skin_pressure, - cmix_s, - elapsed_time, - cq_s, - perf_rates, - deferred_logger); - } - - template - template - void - StandardWell:: - computePerfRate(const std::vector& mob, - const Value& pressure, - const Value& bhp, - const Value& rs, - const Value& rv, - const Value& rvw, - const Value& rsw, - std::vector& b_perfcells_dense, - const double Tw, - const int perf, - const bool allow_cf, - const Value& skin_pressure, - const std::vector& cmix_s, - const double elapsed_time, - std::vector& cq_s, - PerforationRates& perf_rates, - DeferredLogger& deferred_logger) const - { - // Pressure drawdown (also used to determine direction of flow) - const Value well_pressure = bhp + this->connections_.pressure_diff(perf); - Value drawdown = pressure - well_pressure; - if (this->isInjector()) { - drawdown += skin_pressure; - } - - // producing perforations - if (drawdown > 0) { - // Do nothing if crossflow is not allowed - if (!allow_cf && this->isInjector()) { - return; - } - - // compute component volumetric rates at standard conditions - for (int componentIdx = 0; componentIdx < this->numComponents(); ++componentIdx) { - const Value cq_p = - Tw * (mob[componentIdx] * drawdown); - cq_s[componentIdx] = b_perfcells_dense[componentIdx] * cq_p; - } - - if (FluidSystem::phaseIsActive(FluidSystem::oilPhaseIdx) && FluidSystem::phaseIsActive(FluidSystem::gasPhaseIdx)) { - gasOilPerfRateProd(cq_s, perf_rates, rv, rs, rvw); - } else if (FluidSystem::phaseIsActive(FluidSystem::waterPhaseIdx) && FluidSystem::phaseIsActive(FluidSystem::gasPhaseIdx)) { - gasWaterPerfRateProd(cq_s, perf_rates, rvw, rsw); - } - } else { - // Do nothing if crossflow is not allowed - if (!allow_cf && this->isProducer()) { - return; - } - - // Using total mobilities - Value total_mob_dense = mob[0]; - for (int componentIdx = 1; componentIdx < this->numComponents(); ++componentIdx) { - total_mob_dense += mob[componentIdx]; - } - - // Use well index from ML - if (!Base::ml_wi_filename_.empty()) { - Value WI; - if (allow_cf) { - OPM_DEFLOG_THROW(std::runtime_error, - fmt::format("ML well index only implemented with no cross flow. Well {}", name()), - deferred_logger); - } - if constexpr (std::is_same_v) { - WI = this->extendEval(wellIndexEval(perf, elapsed_time, Base::restrictEval(pressure))); - } else { - WI = wellIndexEval(perf, elapsed_time, pressure); - } - auto injectorType = this->well_ecl_.injectorType(); - if (injectorType == InjectorType::WATER) { - const unsigned waterCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::waterCompIdx); - //cq_s[waterCompIdx] = - WI * (total_mob_dense * drawdown) * b_perfcells_dense[waterCompIdx]; - cq_s[waterCompIdx] = - WI * drawdown; - std::cout << cq_s[waterCompIdx] << " " << - Tw * (total_mob_dense * drawdown)*b_perfcells_dense[waterCompIdx] << " " << cmix_s[waterCompIdx] << " " << b_perfcells_dense[waterCompIdx] << " " << total_mob_dense << std::endl; - std::cout << WI << " " << (Tw*total_mob_dense*b_perfcells_dense[waterCompIdx]) << std::endl; - } - else if (injectorType == InjectorType::GAS) { - const unsigned gasCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::gasCompIdx); - //cq_s[gasCompIdx] = - WI * (total_mob_dense * drawdown) * b_perfcells_dense[gasCompIdx]; - cq_s[gasCompIdx] = - WI * drawdown; - } - } else { - std::cout << "ML model not found"; - - const Value cqt_i = - Tw * (total_mob_dense * drawdown); - - // compute volume ratio between connection at standard conditions - Value volumeRatio = bhp * 0.0; // initialize it with the correct type -; - if (FluidSystem::enableVaporizedWater() && FluidSystem::enableDissolvedGasInWater()) { - disOilVapWatVolumeRatio(volumeRatio, rvw, rsw, pressure, - cmix_s, b_perfcells_dense, deferred_logger); - } else { - if (FluidSystem::phaseIsActive(FluidSystem::waterPhaseIdx)) { - const unsigned waterCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::waterCompIdx); - volumeRatio += cmix_s[waterCompIdx] / b_perfcells_dense[waterCompIdx]; - } - } - - if constexpr (Indices::enableSolvent) { - volumeRatio += cmix_s[Indices::contiSolventEqIdx] / b_perfcells_dense[Indices::contiSolventEqIdx]; - } - - if (FluidSystem::phaseIsActive(FluidSystem::oilPhaseIdx) && FluidSystem::phaseIsActive(FluidSystem::gasPhaseIdx)) { - gasOilVolumeRatio(volumeRatio, rv, rs, pressure, - cmix_s, b_perfcells_dense, deferred_logger); - } - else { - if (FluidSystem::phaseIsActive(FluidSystem::oilPhaseIdx)) { - const unsigned oilCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::oilCompIdx); - volumeRatio += cmix_s[oilCompIdx] / b_perfcells_dense[oilCompIdx]; - } - if (FluidSystem::phaseIsActive(FluidSystem::gasPhaseIdx)) { - const unsigned gasCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::gasCompIdx); - volumeRatio += cmix_s[gasCompIdx] / b_perfcells_dense[gasCompIdx]; - } - } - - // injecting connections total volumerates at standard conditions - Value cqt_is = cqt_i / volumeRatio; - for (int componentIdx = 0; componentIdx < this->numComponents(); ++componentIdx) { - cq_s[componentIdx] = cmix_s[componentIdx] * cqt_is; - } - } - - // calculating the perforation solution gas rate and solution oil rates - if (this->isProducer()) { - if (FluidSystem::phaseIsActive(FluidSystem::oilPhaseIdx) && FluidSystem::phaseIsActive(FluidSystem::gasPhaseIdx)) { - gasOilPerfRateInj(cq_s, perf_rates, - rv, rs, pressure, rvw, deferred_logger); - } - if (FluidSystem::phaseIsActive(FluidSystem::gasPhaseIdx) && FluidSystem::phaseIsActive(FluidSystem::waterPhaseIdx)) { - //no oil - gasWaterPerfRateInj(cq_s, perf_rates, rvw, rsw, - pressure, deferred_logger); - } - } - } - } - - - template - void - StandardWell:: - assembleWellEqWithoutIteration(const Simulator& ebosSimulator, - const double dt, - const Well::InjectionControls& inj_controls, - const Well::ProductionControls& prod_controls, - WellState& well_state, - const GroupState& group_state, - DeferredLogger& deferred_logger) - { - // TODO: only_wells should be put back to save some computation - // for example, the matrices B C does not need to update if only_wells - if (!this->isOperableAndSolvable() && !this->wellIsStopped()) return; - - // clear all entries - this->linSys_.clear(); - - assembleWellEqWithoutIterationImpl(ebosSimulator, dt, inj_controls, prod_controls, well_state, group_state, deferred_logger); - } - - - - - template - void - StandardWell:: - assembleWellEqWithoutIterationImpl(const Simulator& ebosSimulator, - const double dt, - const Well::InjectionControls& inj_controls, - const Well::ProductionControls& prod_controls, - WellState& well_state, - const GroupState& group_state, - DeferredLogger& deferred_logger) - { - // try to regularize equation if the well does not converge - const Scalar regularization_factor = this->regularize_? this->param_.regularization_factor_wells_ : 1.0; - const double volume = 0.1 * unit::cubic(unit::feet) * regularization_factor; - - auto& ws = well_state.well(this->index_of_well_); - ws.phase_mixing_rates.fill(0.0); - - const int np = this->number_of_phases_; - - std::vector connectionRates = this->connectionRates_; // Copy to get right size. - auto& perf_data = ws.perf_data; - auto& perf_rates = perf_data.phase_rates; - for (int perf = 0; perf < this->number_of_perforations_; ++perf) { - // Calculate perforation quantities. - std::vector cq_s(this->num_components_, {this->primary_variables_.numWellEq() + Indices::numEq, 0.0}); - EvalWell water_flux_s{this->primary_variables_.numWellEq() + Indices::numEq, 0.0}; - EvalWell cq_s_zfrac_effective{this->primary_variables_.numWellEq() + Indices::numEq, 0.0}; - calculateSinglePerf(ebosSimulator, perf, well_state, connectionRates, cq_s, water_flux_s, cq_s_zfrac_effective, deferred_logger); - - // Equation assembly for this perforation. - if constexpr (has_polymer && Base::has_polymermw) { - if (this->isInjector()) { - handleInjectivityEquations(ebosSimulator, well_state, perf, water_flux_s, deferred_logger); - } - } - const int cell_idx = this->well_cells_[perf]; - for (int componentIdx = 0; componentIdx < this->num_components_; ++componentIdx) { - // the cq_s entering mass balance equations need to consider the efficiency factors. - const EvalWell cq_s_effective = cq_s[componentIdx] * this->well_efficiency_factor_; - - connectionRates[perf][componentIdx] = Base::restrictEval(cq_s_effective); - - StandardWellAssemble(*this). - assemblePerforationEq(cq_s_effective, - componentIdx, - cell_idx, - this->primary_variables_.numWellEq(), - this->linSys_); - - // Store the perforation phase flux for later usage. - if (has_solvent && componentIdx == Indices::contiSolventEqIdx) { - auto& perf_rate_solvent = perf_data.solvent_rates; - perf_rate_solvent[perf] = cq_s[componentIdx].value(); - } else { - perf_rates[perf*np + this->ebosCompIdxToFlowCompIdx(componentIdx)] = cq_s[componentIdx].value(); - } - } - - if constexpr (has_zFraction) { - StandardWellAssemble(*this). - assembleZFracEq(cq_s_zfrac_effective, - cell_idx, - this->primary_variables_.numWellEq(), - this->linSys_); - } - } - // Update the connection - this->connectionRates_ = connectionRates; - - // Accumulate dissolved gas and vaporized oil flow rates across all - // ranks sharing this well (this->index_of_well_). - { - const auto& comm = this->parallel_well_info_.communication(); - comm.sum(ws.phase_mixing_rates.data(), ws.phase_mixing_rates.size()); - } - - // accumulate resWell_ and duneD_ in parallel to get effects of all perforations (might be distributed) - this->linSys_.sumDistributed(this->parallel_well_info_.communication()); - - // add vol * dF/dt + Q to the well equations; - for (int componentIdx = 0; componentIdx < numWellConservationEq; ++componentIdx) { - // TODO: following the development in MSW, we need to convert the volume of the wellbore to be surface volume - // since all the rates are under surface condition - EvalWell resWell_loc(this->primary_variables_.numWellEq() + Indices::numEq, 0.0); - if (FluidSystem::numActivePhases() > 1) { - assert(dt > 0); - resWell_loc += (this->primary_variables_.surfaceVolumeFraction(componentIdx) - - this->F0_[componentIdx]) * volume / dt; - } - resWell_loc -= this->primary_variables_.getQs(componentIdx) * this->well_efficiency_factor_; - StandardWellAssemble(*this). - assembleSourceEq(resWell_loc, - componentIdx, - this->primary_variables_.numWellEq(), - this->linSys_); - } - - const auto& summaryState = ebosSimulator.vanguard().summaryState(); - const Schedule& schedule = ebosSimulator.vanguard().schedule(); - StandardWellAssemble(*this). - assembleControlEq(well_state, group_state, - schedule, summaryState, - inj_controls, prod_controls, - this->primary_variables_, - this->connections_.rho(), - this->linSys_, - deferred_logger); - - - // do the local inversion of D. - try { - this->linSys_.invert(); - } catch( ... ) { - OPM_DEFLOG_THROW(NumericalProblem, "Error when inverting local well equations for well " + name(), deferred_logger); - } - } - - - - - template - void - StandardWell:: - calculateSinglePerf(const Simulator& ebosSimulator, - const int perf, - WellState& well_state, - std::vector& connectionRates, - std::vector& cq_s, - EvalWell& water_flux_s, - EvalWell& cq_s_zfrac_effective, - DeferredLogger& deferred_logger) const - { - const bool allow_cf = this->getAllowCrossFlow() || openCrossFlowAvoidSingularity(ebosSimulator); - const EvalWell& bhp = this->primary_variables_.eval(Bhp); - const int cell_idx = this->well_cells_[perf]; - const auto& intQuants = ebosSimulator.model().intensiveQuantities(cell_idx, /*timeIdx=*/ 0); - std::vector mob(this->num_components_, {this->primary_variables_.numWellEq() + Indices::numEq, 0.}); - getMobility(ebosSimulator, perf, mob, deferred_logger); - - PerforationRates perf_rates; - double trans_mult = ebosSimulator.problem().template rockCompTransMultiplier(intQuants, cell_idx); - const double Tw = this->wellIndex(perf) * trans_mult; - const double elapsed_time = ebosSimulator.time(); - - computePerfRate(intQuants, mob, bhp, Tw, perf, allow_cf, elapsed_time, - cq_s, perf_rates, deferred_logger); - - auto& ws = well_state.well(this->index_of_well_); - auto& perf_data = ws.perf_data; - if constexpr (has_polymer && Base::has_polymermw) { - if (this->isInjector()) { - // Store the original water flux computed from the reservoir quantities. - // It will be required to assemble the injectivity equations. - const unsigned water_comp_idx = Indices::canonicalToActiveComponentIndex(FluidSystem::waterCompIdx); - water_flux_s = cq_s[water_comp_idx]; - // Modify the water flux for the rest of this function to depend directly on the - // local water velocity primary variable. - handleInjectivityRate(ebosSimulator, perf, cq_s); - } - } - - // updating the solution gas rate and solution oil rate - if (this->isProducer()) { - ws.phase_mixing_rates[ws.dissolved_gas] += perf_rates.dis_gas; - ws.phase_mixing_rates[ws.dissolved_gas_in_water] += perf_rates.dis_gas_in_water; - ws.phase_mixing_rates[ws.vaporized_oil] += perf_rates.vap_oil; - ws.phase_mixing_rates[ws.vaporized_water] += perf_rates.vap_wat; - } - - if constexpr (has_energy) { - connectionRates[perf][Indices::contiEnergyEqIdx] = - connectionRateEnergy(ebosSimulator.problem().maxOilSaturation(cell_idx), - cq_s, intQuants, deferred_logger); - } - - if constexpr (has_polymer) { - std::variant polymerConcentration; - if (this->isInjector()) { - polymerConcentration = this->wpolymer(); - } else { - polymerConcentration = this->extendEval(intQuants.polymerConcentration() * - intQuants.polymerViscosityCorrection()); - } - - [[maybe_unused]] EvalWell cq_s_poly; - std::tie(connectionRates[perf][Indices::contiPolymerEqIdx], - cq_s_poly) = - this->connections_.connectionRatePolymer(perf_data.polymer_rates[perf], - cq_s, polymerConcentration); - - if constexpr (Base::has_polymermw) { - updateConnectionRatePolyMW(cq_s_poly, intQuants, well_state, - perf, connectionRates, deferred_logger); - } - } - - if constexpr (has_foam) { - std::variant foamConcentration; - if (this->isInjector()) { - foamConcentration = this->wfoam(); - } else { - foamConcentration = this->extendEval(intQuants.foamConcentration()); - } - connectionRates[perf][Indices::contiFoamEqIdx] = - this->connections_.connectionRateFoam(cq_s, foamConcentration, - FoamModule::transportPhase(), - deferred_logger); - } - - if constexpr (has_zFraction) { - std::variant> solventConcentration; - if (this->isInjector()) { - solventConcentration = this->wsolvent(); - } else { - solventConcentration = std::array{this->extendEval(intQuants.xVolume()), - this->extendEval(intQuants.yVolume())}; - } - std::tie(connectionRates[perf][Indices::contiZfracEqIdx], - cq_s_zfrac_effective) = - this->connections_.connectionRatezFraction(perf_data.solvent_rates[perf], - perf_rates.dis_gas, cq_s, - solventConcentration); - } - - if constexpr (has_brine) { - std::variant saltConcentration; - if (this->isInjector()) { - saltConcentration = this->wsalt(); - } else { - saltConcentration = this->extendEval(intQuants.fluidState().saltConcentration()); - } - - connectionRates[perf][Indices::contiBrineEqIdx] = - this->connections_.connectionRateBrine(perf_data.brine_rates[perf], - perf_rates.vap_wat, cq_s, - saltConcentration); - } - - if constexpr (has_micp) { - std::variant microbialConcentration; - std::variant oxygenConcentration; - std::variant ureaConcentration; - if (this->isInjector()) { - microbialConcentration = this->wmicrobes(); - oxygenConcentration = this->woxygen(); - ureaConcentration = this->wurea(); - } else { - microbialConcentration = this->extendEval(intQuants.microbialConcentration()); - oxygenConcentration = this->extendEval(intQuants.oxygenConcentration()); - ureaConcentration = this->extendEval(intQuants.ureaConcentration()); - } - std::tie(connectionRates[perf][Indices::contiMicrobialEqIdx], - connectionRates[perf][Indices::contiOxygenEqIdx], - connectionRates[perf][Indices::contiUreaEqIdx]) = - this->connections_.connectionRatesMICP(cq_s, - microbialConcentration, - oxygenConcentration, - ureaConcentration); - } - - // Store the perforation pressure for later usage. - perf_data.pressure[perf] = ws.bhp + this->connections_.pressure_diff(perf); - } - - - - template - template - void - StandardWell:: - getMobility(const Simulator& ebosSimulator, - const int perf, - std::vector& mob, - DeferredLogger& deferred_logger) const - { - auto obtain = [this](const Eval& value) - { - if constexpr (std::is_same_v) { - static_cast(this); // suppress clang warning - return getValue(value); - } else { - return this->extendEval(value); - } - }; - WellInterface::getMobility(ebosSimulator, perf, mob, - obtain, deferred_logger); - - // modify the water mobility if polymer is present - if constexpr (has_polymer) { - if (!FluidSystem::phaseIsActive(FluidSystem::waterPhaseIdx)) { - OPM_DEFLOG_THROW(std::runtime_error, "Water is required when polymer is active", deferred_logger); - } - - // for the cases related to polymer molecular weight, we assume fully mixing - // as a result, the polymer and water share the same viscosity - if constexpr (!Base::has_polymermw) { - if constexpr (std::is_same_v) { - std::vector mob_eval(this->num_components_, {this->primary_variables_.numWellEq() + Indices::numEq, 0.}); - for (size_t i = 0; i < mob.size(); ++i) - mob_eval[i].setValue(mob[i]); - updateWaterMobilityWithPolymer(ebosSimulator, perf, mob_eval, deferred_logger); - for (size_t i = 0; i < mob.size(); ++i) { - mob[i] = getValue(mob_eval[i]); - } - } else { - updateWaterMobilityWithPolymer(ebosSimulator, perf, mob, deferred_logger); - } - } - } - - // if the injecting well has WINJMULT setup, we update the mobility accordingly - if (this->isInjector() && this->well_ecl_.getInjMultMode() != Well::InjMultMode::NONE) { - const double bhp = this->primary_variables_.value(Bhp); - const double perf_press = bhp + this->connections_.pressure_diff(perf); - const double multiplier = this->getInjMult(perf, bhp, perf_press); - for (size_t i = 0; i < mob.size(); ++i) { - mob[i] *= multiplier; - } - } - } - - - template - void - StandardWell:: - updateWellState(const SummaryState& summary_state, - const BVectorWell& dwells, - WellState& well_state, - DeferredLogger& deferred_logger) - { - if (!this->isOperableAndSolvable() && !this->wellIsStopped()) return; - - const bool stop_or_zero_rate_target = this->stopppedOrZeroRateTarget(summary_state, well_state); - updatePrimaryVariablesNewton(dwells, stop_or_zero_rate_target, deferred_logger); - - updateWellStateFromPrimaryVariables(stop_or_zero_rate_target, well_state, summary_state, deferred_logger); - Base::calculateReservoirRates(well_state.well(this->index_of_well_)); - } - - - - - - template - void - StandardWell:: - updatePrimaryVariablesNewton(const BVectorWell& dwells, - const bool stop_or_zero_rate_target, - DeferredLogger& deferred_logger) - { - const double dFLimit = this->param_.dwell_fraction_max_; - const double dBHPLimit = this->param_.dbhp_max_rel_; - this->primary_variables_.updateNewton(dwells, stop_or_zero_rate_target, dFLimit, dBHPLimit); - - // for the water velocity and skin pressure - if constexpr (Base::has_polymermw) { - this->primary_variables_.updateNewtonPolyMW(dwells); - } - - this->primary_variables_.checkFinite(deferred_logger); - } - - - - - - template - void - StandardWell:: - updateWellStateFromPrimaryVariables(const bool stop_or_zero_rate_target, - WellState& well_state, - const SummaryState& summary_state, - DeferredLogger& deferred_logger) const - { - this->StdWellEval::updateWellStateFromPrimaryVariables(stop_or_zero_rate_target, well_state, summary_state, deferred_logger); - - // other primary variables related to polymer injectivity study - if constexpr (Base::has_polymermw) { - this->primary_variables_.copyToWellStatePolyMW(well_state); - } - } - - - - - - template - void - StandardWell:: - updateIPR(const Simulator& ebos_simulator, DeferredLogger& deferred_logger) const - { - // TODO: not handling solvent related here for now - - // initialize all the values to be zero to begin with - std::fill(this->ipr_a_.begin(), this->ipr_a_.end(), 0.); - std::fill(this->ipr_b_.begin(), this->ipr_b_.end(), 0.); - - for (int perf = 0; perf < this->number_of_perforations_; ++perf) { - std::vector mob(this->num_components_, 0.0); - getMobility(ebos_simulator, perf, mob, deferred_logger); - - const int cell_idx = this->well_cells_[perf]; - const auto& int_quantities = ebos_simulator.model().intensiveQuantities(cell_idx, /*timeIdx=*/ 0); - const auto& fs = int_quantities.fluidState(); - // the pressure of the reservoir grid block the well connection is in - double p_r = this->getPerfCellPressure(fs).value(); - - // calculating the b for the connection - std::vector b_perf(this->num_components_); - for (size_t phase = 0; phase < FluidSystem::numPhases; ++phase) { - if (!FluidSystem::phaseIsActive(phase)) { - continue; - } - const unsigned comp_idx = Indices::canonicalToActiveComponentIndex(FluidSystem::solventComponentIndex(phase)); - b_perf[comp_idx] = fs.invB(phase).value(); - } - if constexpr (has_solvent) { - b_perf[Indices::contiSolventEqIdx] = int_quantities.solventInverseFormationVolumeFactor().value(); - } - - // the pressure difference between the connection and BHP - const double h_perf = this->connections_.pressure_diff(perf); - const double pressure_diff = p_r - h_perf; - - // Let us add a check, since the pressure is calculated based on zero value BHP - // it should not be negative anyway. If it is negative, we might need to re-formulate - // to taking into consideration the crossflow here. - if ( (this->isProducer() && pressure_diff < 0.) || (this->isInjector() && pressure_diff > 0.) ) { - deferred_logger.debug("CROSSFLOW_IPR", - "cross flow found when updateIPR for well " + name() - + " . The connection is ignored in IPR calculations"); - // we ignore these connections for now - continue; - } - - // the well index associated with the connection - const double tw_perf = this->wellIndex(perf)*ebos_simulator.problem().template rockCompTransMultiplier(int_quantities, cell_idx); - - std::vector ipr_a_perf(this->ipr_a_.size()); - std::vector ipr_b_perf(this->ipr_b_.size()); - for (int comp_idx = 0; comp_idx < this->num_components_; ++comp_idx) { - const double tw_mob = tw_perf * mob[comp_idx] * b_perf[comp_idx]; - ipr_a_perf[comp_idx] += tw_mob * pressure_diff; - ipr_b_perf[comp_idx] += tw_mob; - } - - // we need to handle the rs and rv when both oil and gas are present - if (FluidSystem::phaseIsActive(FluidSystem::oilPhaseIdx) && FluidSystem::phaseIsActive(FluidSystem::gasPhaseIdx)) { - const unsigned oil_comp_idx = Indices::canonicalToActiveComponentIndex(FluidSystem::oilCompIdx); - const unsigned gas_comp_idx = Indices::canonicalToActiveComponentIndex(FluidSystem::gasCompIdx); - const double rs = (fs.Rs()).value(); - const double rv = (fs.Rv()).value(); - - const double dis_gas_a = rs * ipr_a_perf[oil_comp_idx]; - const double vap_oil_a = rv * ipr_a_perf[gas_comp_idx]; - - ipr_a_perf[gas_comp_idx] += dis_gas_a; - ipr_a_perf[oil_comp_idx] += vap_oil_a; - - const double dis_gas_b = rs * ipr_b_perf[oil_comp_idx]; - const double vap_oil_b = rv * ipr_b_perf[gas_comp_idx]; - - ipr_b_perf[gas_comp_idx] += dis_gas_b; - ipr_b_perf[oil_comp_idx] += vap_oil_b; - } - - for (size_t comp_idx = 0; comp_idx < ipr_a_perf.size(); ++comp_idx) { - this->ipr_a_[comp_idx] += ipr_a_perf[comp_idx]; - this->ipr_b_[comp_idx] += ipr_b_perf[comp_idx]; - } - } - this->parallel_well_info_.communication().sum(this->ipr_a_.data(), this->ipr_a_.size()); - this->parallel_well_info_.communication().sum(this->ipr_b_.data(), this->ipr_b_.size()); - } - - - template - void - StandardWell:: - checkOperabilityUnderBHPLimit(const WellState& well_state, const Simulator& ebos_simulator, DeferredLogger& deferred_logger) - { - const auto& summaryState = ebos_simulator.vanguard().summaryState(); - const double bhp_limit = WellBhpThpCalculator(*this).mostStrictBhpFromBhpLimits(summaryState); - // Crude but works: default is one atmosphere. - // TODO: a better way to detect whether the BHP is defaulted or not - const bool bhp_limit_not_defaulted = bhp_limit > 1.5 * unit::barsa; - if ( bhp_limit_not_defaulted || !this->wellHasTHPConstraints(summaryState) ) { - // if the BHP limit is not defaulted or the well does not have a THP limit - // we need to check the BHP limit - double total_ipr_mass_rate = 0.0; - for (unsigned phaseIdx = 0; phaseIdx < FluidSystem::numPhases; ++phaseIdx) - { - if (!FluidSystem::phaseIsActive(phaseIdx)) { - continue; - } - - const unsigned compIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::solventComponentIndex(phaseIdx)); - const double ipr_rate = this->ipr_a_[compIdx] - this->ipr_b_[compIdx] * bhp_limit; - - const double rho = FluidSystem::referenceDensity( phaseIdx, Base::pvtRegionIdx() ); - total_ipr_mass_rate += ipr_rate * rho; - } - if ( (this->isProducer() && total_ipr_mass_rate < 0.) || (this->isInjector() && total_ipr_mass_rate > 0.) ) { - this->operability_status_.operable_under_only_bhp_limit = false; - } - - // checking whether running under BHP limit will violate THP limit - if (this->operability_status_.operable_under_only_bhp_limit && this->wellHasTHPConstraints(summaryState)) { - // option 1: calculate well rates based on the BHP limit. - // option 2: stick with the above IPR curve - // we use IPR here - std::vector well_rates_bhp_limit; - computeWellRatesWithBhp(ebos_simulator, bhp_limit, well_rates_bhp_limit, deferred_logger); - - this->adaptRatesForVFP(well_rates_bhp_limit); - const double thp_limit = this->getTHPConstraint(summaryState); - const double thp = WellBhpThpCalculator(*this).calculateThpFromBhp(well_rates_bhp_limit, - bhp_limit, - this->connections_.rho(), - this->getALQ(well_state), - thp_limit, - deferred_logger); - if ( (this->isProducer() && thp < thp_limit) || (this->isInjector() && thp > thp_limit) ) { - this->operability_status_.obey_thp_limit_under_bhp_limit = false; - } - } - } else { - // defaulted BHP and there is a THP constraint - // default BHP limit is about 1 atm. - // when applied the hydrostatic pressure correction dp, - // most likely we get a negative value (bhp + dp)to search in the VFP table, - // which is not desirable. - // we assume we can operate under defaulted BHP limit and will violate the THP limit - // when operating under defaulted BHP limit. - this->operability_status_.operable_under_only_bhp_limit = true; - this->operability_status_.obey_thp_limit_under_bhp_limit = false; - } - } - - - - - - template - void - StandardWell:: - checkOperabilityUnderTHPLimit(const Simulator& ebos_simulator, const WellState& well_state, DeferredLogger& deferred_logger) - { - const auto& summaryState = ebos_simulator.vanguard().summaryState(); - const auto obtain_bhp = this->isProducer() ? computeBhpAtThpLimitProd(well_state, ebos_simulator, summaryState, deferred_logger) - : computeBhpAtThpLimitInj(ebos_simulator, summaryState, deferred_logger); - - if (obtain_bhp) { - this->operability_status_.can_obtain_bhp_with_thp_limit = true; - - const double bhp_limit = WellBhpThpCalculator(*this).mostStrictBhpFromBhpLimits(summaryState); - this->operability_status_.obey_bhp_limit_with_thp_limit = this->isProducer() ? - *obtain_bhp >= bhp_limit : *obtain_bhp <= bhp_limit ; - - const double thp_limit = this->getTHPConstraint(summaryState); - if (this->isProducer() && *obtain_bhp < thp_limit) { - const std::string msg = " obtained bhp " + std::to_string(unit::convert::to(*obtain_bhp, unit::barsa)) - + " bars is SMALLER than thp limit " - + std::to_string(unit::convert::to(thp_limit, unit::barsa)) - + " bars as a producer for well " + name(); - deferred_logger.debug(msg); - } - else if (this->isInjector() && *obtain_bhp > thp_limit) { - const std::string msg = " obtained bhp " + std::to_string(unit::convert::to(*obtain_bhp, unit::barsa)) - + " bars is LARGER than thp limit " - + std::to_string(unit::convert::to(thp_limit, unit::barsa)) - + " bars as a injector for well " + name(); - deferred_logger.debug(msg); - } - } else { - this->operability_status_.can_obtain_bhp_with_thp_limit = false; - this->operability_status_.obey_bhp_limit_with_thp_limit = false; - if (!this->wellIsStopped()) { - const double thp_limit = this->getTHPConstraint(summaryState); - deferred_logger.debug(" could not find bhp value at thp limit " - + std::to_string(unit::convert::to(thp_limit, unit::barsa)) - + " bar for well " + name() + ", the well might need to be closed "); - } - } - } - - - - - - template - bool - StandardWell:: - allDrawDownWrongDirection(const Simulator& ebos_simulator) const - { - bool all_drawdown_wrong_direction = true; - - for (int perf = 0; perf < this->number_of_perforations_; ++perf) { - const int cell_idx = this->well_cells_[perf]; - const auto& intQuants = ebos_simulator.model().intensiveQuantities(cell_idx, /*timeIdx=*/0); - const auto& fs = intQuants.fluidState(); - - const double pressure = this->getPerfCellPressure(fs).value(); - const double bhp = this->primary_variables_.eval(Bhp).value(); - - // Pressure drawdown (also used to determine direction of flow) - const double well_pressure = bhp + this->connections_.pressure_diff(perf); - const double drawdown = pressure - well_pressure; - - // for now, if there is one perforation can produce/inject in the correct - // direction, we consider this well can still produce/inject. - // TODO: it can be more complicated than this to cause wrong-signed rates - if ( (drawdown < 0. && this->isInjector()) || - (drawdown > 0. && this->isProducer()) ) { - all_drawdown_wrong_direction = false; - break; - } - } - - const auto& comm = this->parallel_well_info_.communication(); - if (comm.size() > 1) - { - all_drawdown_wrong_direction = - (comm.min(all_drawdown_wrong_direction ? 1 : 0) == 1); - } - - return all_drawdown_wrong_direction; - } - - - - - template - bool - StandardWell:: - canProduceInjectWithCurrentBhp(const Simulator& ebos_simulator, - const WellState& well_state, - DeferredLogger& deferred_logger) - { - const double bhp = well_state.well(this->index_of_well_).bhp; - std::vector well_rates; - computeWellRatesWithBhp(ebos_simulator, bhp, well_rates, deferred_logger); - - const double sign = (this->isProducer()) ? -1. : 1.; - const double threshold = sign * std::numeric_limits::min(); - - bool can_produce_inject = false; - for (const auto value : well_rates) { - if (this->isProducer() && value < threshold) { - can_produce_inject = true; - break; - } else if (this->isInjector() && value > threshold) { - can_produce_inject = true; - break; - } - } - - if (!can_produce_inject) { - deferred_logger.debug(" well " + name() + " CANNOT produce or inejct "); - } - - return can_produce_inject; - } - - - - - - template - bool - StandardWell:: - openCrossFlowAvoidSingularity(const Simulator& ebos_simulator) const - { - return !this->getAllowCrossFlow() && allDrawDownWrongDirection(ebos_simulator); - } - - - - - template - void - StandardWell:: - computePropertiesForWellConnectionPressures(const Simulator& ebosSimulator, - const WellState& well_state, - WellConnectionProps& props) const - { - std::function getTemperature = - [&ebosSimulator](int cell_idx, int phase_idx) - { - return ebosSimulator.model().intensiveQuantities(cell_idx, 0).fluidState().temperature(phase_idx).value(); - }; - std::function getSaltConcentration = - [&ebosSimulator](int cell_idx) - { - return ebosSimulator.model().intensiveQuantities(cell_idx, 0).fluidState().saltConcentration().value(); - }; - std::function getPvtRegionIdx = - [&ebosSimulator](int cell_idx) - { - return ebosSimulator.model().intensiveQuantities(cell_idx, 0).fluidState().pvtRegionIndex(); - }; - std::function getInvFac = - [&ebosSimulator](int cell_idx) - { - return ebosSimulator.model().intensiveQuantities(cell_idx, 0).solventInverseFormationVolumeFactor().value(); - }; - std::function getSolventDensity = - [&ebosSimulator](int cell_idx) - { - return ebosSimulator.model().intensiveQuantities(cell_idx, 0).solventRefDensity(); - }; - - this->connections_.computePropertiesForPressures(well_state, - getTemperature, - getSaltConcentration, - getPvtRegionIdx, - getInvFac, - getSolventDensity, - props); - } - - - - - - template - ConvergenceReport - StandardWell:: - getWellConvergence(const SummaryState& summary_state, - const WellState& well_state, - const std::vector& B_avg, - DeferredLogger& deferred_logger, - const bool relax_tolerance) const - { - // the following implementation assume that the polymer is always after the w-o-g phases - // For the polymer, energy and foam cases, there is one more mass balance equations of reservoir than wells - assert((int(B_avg.size()) == this->num_components_) || has_polymer || has_energy || has_foam || has_brine || has_zFraction || has_micp); - - // using sricter tolerance for stopped wells and wells under zero rate target control. - constexpr double stricter_factor = 1.e-4; - const double tol_wells = this->stopppedOrZeroRateTarget(summary_state, well_state) ? - this->param_.tolerance_wells_ * stricter_factor : this->param_.tolerance_wells_; - - std::vector res; - ConvergenceReport report = this->StdWellEval::getWellConvergence(well_state, - B_avg, - this->param_.max_residual_allowed_, - tol_wells, - this->param_.relaxed_tolerance_flow_well_, - relax_tolerance, - res, - deferred_logger); - - checkConvergenceExtraEqs(res, report); - - return report; - } - - - - - - template - void - StandardWell:: - updateProductivityIndex(const Simulator& ebosSimulator, - const WellProdIndexCalculator& wellPICalc, - WellState& well_state, - DeferredLogger& deferred_logger) const - { - auto fluidState = [&ebosSimulator, this](const int perf) - { - const auto cell_idx = this->well_cells_[perf]; - return ebosSimulator.model() - .intensiveQuantities(cell_idx, /*timeIdx=*/ 0).fluidState(); - }; - - const int np = this->number_of_phases_; - auto setToZero = [np](double* x) -> void - { - std::fill_n(x, np, 0.0); - }; - - auto addVector = [np](const double* src, double* dest) -> void - { - std::transform(src, src + np, dest, dest, std::plus<>{}); - }; - - auto& ws = well_state.well(this->index_of_well_); - auto& perf_data = ws.perf_data; - auto* wellPI = ws.productivity_index.data(); - auto* connPI = perf_data.prod_index.data(); - - setToZero(wellPI); - - const auto preferred_phase = this->well_ecl_.getPreferredPhase(); - auto subsetPerfID = 0; - - for (const auto& perf : *this->perf_data_) { - auto allPerfID = perf.ecl_index; - - auto connPICalc = [&wellPICalc, allPerfID](const double mobility) -> double - { - return wellPICalc.connectionProdIndStandard(allPerfID, mobility); - }; - - std::vector mob(this->num_components_, 0.0); - getMobility(ebosSimulator, static_cast(subsetPerfID), mob, deferred_logger); - - const auto& fs = fluidState(subsetPerfID); - setToZero(connPI); - - if (this->isInjector()) { - this->computeConnLevelInjInd(fs, preferred_phase, connPICalc, - mob, connPI, deferred_logger); - } - else { // Production or zero flow rate - this->computeConnLevelProdInd(fs, connPICalc, mob, connPI); - } - - addVector(connPI, wellPI); - - ++subsetPerfID; - connPI += np; - } - - // Sum with communication in case of distributed well. - const auto& comm = this->parallel_well_info_.communication(); - if (comm.size() > 1) { - comm.sum(wellPI, np); - } - - assert ((static_cast(subsetPerfID) == this->number_of_perforations_) && - "Internal logic error in processing connections for PI/II"); - } - - - - template - void - StandardWell:: - computeWellConnectionDensitesPressures(const Simulator& ebosSimulator, - const WellState& well_state, - const WellConnectionProps& props, - DeferredLogger& deferred_logger) - { - std::function invB = - [&ebosSimulator](int cell_idx, int phase_idx) - { - return ebosSimulator.model().intensiveQuantities(cell_idx, 0).fluidState().invB(phase_idx).value(); - }; - std::function mobility = - [&ebosSimulator](int cell_idx, int phase_idx) - { - return ebosSimulator.model().intensiveQuantities(cell_idx, 0).mobility(phase_idx).value(); - }; - std::function invFac = - [&ebosSimulator](int cell_idx) - { - return ebosSimulator.model().intensiveQuantities(cell_idx, 0).solventInverseFormationVolumeFactor().value(); - }; - std::function solventMobility = - [&ebosSimulator](int cell_idx) - { - return ebosSimulator.model().intensiveQuantities(cell_idx, 0).solventMobility().value(); - }; - - this->connections_.computeProperties(well_state, - invB, - mobility, - invFac, - solventMobility, - props, - deferred_logger); - } - - - - - - template - void - StandardWell:: - computeWellConnectionPressures(const Simulator& ebosSimulator, - const WellState& well_state, - DeferredLogger& deferred_logger) - { - // 1. Compute properties required by computePressureDelta(). - // Note that some of the complexity of this part is due to the function - // taking std::vector arguments, and not Eigen objects. - WellConnectionProps props; - computePropertiesForWellConnectionPressures(ebosSimulator, well_state, props); - computeWellConnectionDensitesPressures(ebosSimulator, well_state, - props, deferred_logger); - } - - - - - - template - void - StandardWell:: - solveEqAndUpdateWellState(const SummaryState& summary_state, - WellState& well_state, - DeferredLogger& deferred_logger) - { - if (!this->isOperableAndSolvable() && !this->wellIsStopped()) return; - - // We assemble the well equations, then we check the convergence, - // which is why we do not put the assembleWellEq here. - BVectorWell dx_well(1); - dx_well[0].resize(this->primary_variables_.numWellEq()); - this->linSys_.solve( dx_well); - - updateWellState(summary_state, dx_well, well_state, deferred_logger); - } - - - - - - template - void - StandardWell:: - calculateExplicitQuantities(const Simulator& ebosSimulator, - const WellState& well_state, - DeferredLogger& deferred_logger) - { - const auto& summary_state = ebosSimulator.vanguard().summaryState(); - updatePrimaryVariables(summary_state, well_state, deferred_logger); - initPrimaryVariablesEvaluation(); - computeWellConnectionPressures(ebosSimulator, well_state, deferred_logger); - this->computeAccumWell(); - } - - - - template - void - StandardWell:: - apply(const BVector& x, BVector& Ax) const - { - if (!this->isOperableAndSolvable() && !this->wellIsStopped()) return; - - if (this->param_.matrix_add_well_contributions_) - { - // Contributions are already in the matrix itself - return; - } - - this->linSys_.apply(x, Ax); - } - - - - - template - void - StandardWell:: - apply(BVector& r) const - { - if (!this->isOperableAndSolvable() && !this->wellIsStopped()) return; - - this->linSys_.apply(r); - } - - - - - template - void - StandardWell:: - recoverWellSolutionAndUpdateWellState(const SummaryState& summary_state, - const BVector& x, - WellState& well_state, - DeferredLogger& deferred_logger) - { - if (!this->isOperableAndSolvable() && !this->wellIsStopped()) return; - - BVectorWell xw(1); - xw[0].resize(this->primary_variables_.numWellEq()); - - this->linSys_.recoverSolutionWell(x, xw); - updateWellState(summary_state, xw, well_state, deferred_logger); - } - - - - - template - void - StandardWell:: - computeWellRatesWithBhp(const Simulator& ebosSimulator, - const double& bhp, - std::vector& well_flux, - DeferredLogger& deferred_logger) const - { - - const int np = this->number_of_phases_; - well_flux.resize(np, 0.0); - - const bool allow_cf = this->getAllowCrossFlow(); - - for (int perf = 0; perf < this->number_of_perforations_; ++perf) { - const int cell_idx = this->well_cells_[perf]; - const auto& intQuants = ebosSimulator.model().intensiveQuantities(cell_idx, /*timeIdx=*/ 0); - // flux for each perforation - std::vector mob(this->num_components_, 0.); - getMobility(ebosSimulator, perf, mob, deferred_logger); - double trans_mult = ebosSimulator.problem().template rockCompTransMultiplier(intQuants, cell_idx); - const double Tw = this->wellIndex(perf) * trans_mult; - const double elapsed_time = ebosSimulator.time(); - - std::vector cq_s(this->num_components_, 0.); - PerforationRates perf_rates; - computePerfRate(intQuants, mob, bhp, Tw, perf, allow_cf, elapsed_time, - cq_s, perf_rates, deferred_logger); - - for(int p = 0; p < np; ++p) { - well_flux[this->ebosCompIdxToFlowCompIdx(p)] += cq_s[p]; - } - } - this->parallel_well_info_.communication().sum(well_flux.data(), well_flux.size()); - } - - - - template - void - StandardWell:: - computeWellRatesWithBhpIterations(const Simulator& ebosSimulator, - const double& bhp, - std::vector& well_flux, - DeferredLogger& deferred_logger) const - { - // creating a copy of the well itself, to avoid messing up the explicit information - // during this copy, the only information not copied properly is the well controls - StandardWell well_copy(*this); - - // iterate to get a more accurate well density - // create a copy of the well_state to use. If the operability checking is sucessful, we use this one - // to replace the original one - WellState well_state_copy = ebosSimulator.problem().wellModel().wellState(); - const auto& group_state = ebosSimulator.problem().wellModel().groupState(); - - // Get the current controls. - const auto& summary_state = ebosSimulator.vanguard().summaryState(); - auto inj_controls = well_copy.well_ecl_.isInjector() - ? well_copy.well_ecl_.injectionControls(summary_state) - : Well::InjectionControls(0); - auto prod_controls = well_copy.well_ecl_.isProducer() - ? well_copy.well_ecl_.productionControls(summary_state) : - Well::ProductionControls(0); - - // Set current control to bhp, and bhp value in state, modify bhp limit in control object. - auto& ws = well_state_copy.well(this->index_of_well_); - if (well_copy.well_ecl_.isInjector()) { - inj_controls.bhp_limit = bhp; - ws.injection_cmode = Well::InjectorCMode::BHP; - } else { - prod_controls.bhp_limit = bhp; - ws.production_cmode = Well::ProducerCMode::BHP; - } - ws.bhp = bhp; - - // initialized the well rates with the potentials i.e. the well rates based on bhp - const int np = this->number_of_phases_; - const double sign = this->well_ecl_.isInjector() ? 1.0 : -1.0; - for (int phase = 0; phase < np; ++phase){ - well_state_copy.wellRates(this->index_of_well_)[phase] - = sign * ws.well_potentials[phase]; - } - well_copy.calculateExplicitQuantities(ebosSimulator, well_state_copy, deferred_logger); - - const double dt = ebosSimulator.timeStepSize(); - const bool converged = well_copy.iterateWellEqWithControl(ebosSimulator, dt, inj_controls, prod_controls, well_state_copy, group_state, deferred_logger); - if (!converged) { - const std::string msg = " well " + name() + " did not get converged during well potential calculations " - " potentials are computed based on unconverged solution"; - deferred_logger.debug(msg); - } - well_copy.updatePrimaryVariables(summary_state, well_state_copy, deferred_logger); - well_copy.computeWellConnectionPressures(ebosSimulator, well_state_copy, deferred_logger); - well_copy.initPrimaryVariablesEvaluation(); - well_copy.computeWellRatesWithBhp(ebosSimulator, bhp, well_flux, deferred_logger); - } - - - - - template - std::vector - StandardWell:: - computeWellPotentialWithTHP(const Simulator& ebos_simulator, - DeferredLogger& deferred_logger, - const WellState &well_state) const - { - std::vector potentials(this->number_of_phases_, 0.0); - const auto& summary_state = ebos_simulator.vanguard().summaryState(); - - const auto& well = this->well_ecl_; - if (well.isInjector()){ - const auto& controls = this->well_ecl_.injectionControls(summary_state); - auto bhp_at_thp_limit = computeBhpAtThpLimitInj(ebos_simulator, summary_state, deferred_logger); - if (bhp_at_thp_limit) { - const double bhp = std::min(*bhp_at_thp_limit, controls.bhp_limit); - computeWellRatesWithBhp(ebos_simulator, bhp, potentials, deferred_logger); - } else { - deferred_logger.warning("FAILURE_GETTING_CONVERGED_POTENTIAL", - "Failed in getting converged thp based potential calculation for well " - + name() + ". Instead the bhp based value is used"); - const double bhp = controls.bhp_limit; - computeWellRatesWithBhp(ebos_simulator, bhp, potentials, deferred_logger); - } - } else { - computeWellRatesWithThpAlqProd( - ebos_simulator, summary_state, - deferred_logger, potentials, this->getALQ(well_state) - ); - } - - return potentials; - } - - - - template - double - StandardWell:: - computeWellRatesAndBhpWithThpAlqProd(const Simulator &ebos_simulator, - const SummaryState &summary_state, - DeferredLogger &deferred_logger, - std::vector &potentials, - double alq) const - { - double bhp; - auto bhp_at_thp_limit = computeBhpAtThpLimitProdWithAlq( - ebos_simulator, summary_state, alq, deferred_logger); - if (bhp_at_thp_limit) { - const auto& controls = this->well_ecl_.productionControls(summary_state); - bhp = std::max(*bhp_at_thp_limit, controls.bhp_limit); - computeWellRatesWithBhp(ebos_simulator, bhp, potentials, deferred_logger); - } - else { - deferred_logger.warning("FAILURE_GETTING_CONVERGED_POTENTIAL", - "Failed in getting converged thp based potential calculation for well " - + name() + ". Instead the bhp based value is used"); - const auto& controls = this->well_ecl_.productionControls(summary_state); - bhp = controls.bhp_limit; - computeWellRatesWithBhp(ebos_simulator, bhp, potentials, deferred_logger); - } - return bhp; - } - - template - void - StandardWell:: - computeWellRatesWithThpAlqProd(const Simulator &ebos_simulator, - const SummaryState &summary_state, - DeferredLogger &deferred_logger, - std::vector &potentials, - double alq) const - { - /*double bhp =*/ - computeWellRatesAndBhpWithThpAlqProd(ebos_simulator, - summary_state, - deferred_logger, - potentials, - alq); - } - - template - void - StandardWell:: - computeWellPotentials(const Simulator& ebosSimulator, - const WellState& well_state, - std::vector& well_potentials, - DeferredLogger& deferred_logger) // const - { - const auto [compute_potential, bhp_controlled_well] = - this->WellInterfaceGeneric::computeWellPotentials(well_potentials, well_state); - - if (!compute_potential) { - return; - } - - // does the well have a THP related constraint? - const auto& summaryState = ebosSimulator.vanguard().summaryState(); - if (!Base::wellHasTHPConstraints(summaryState) || bhp_controlled_well) { - // get the bhp value based on the bhp constraints - double bhp = WellBhpThpCalculator(*this).mostStrictBhpFromBhpLimits(summaryState); - - // In some very special cases the bhp pressure target are - // temporary violated. This may lead to too small or negative potentials - // that could lead to premature shutting of wells. - // As a remedy the bhp that gives the largest potential is used. - // For converged cases, ws.bhp <=bhp for injectors and ws.bhp >= bhp, - // and the potentials will be computed using the limit as expected. - const auto& ws = well_state.well(this->index_of_well_); - if (this->isInjector()) - bhp = std::max(ws.bhp, bhp); - else - bhp = std::min(ws.bhp, bhp); - - assert(std::abs(bhp) != std::numeric_limits::max()); - computeWellRatesWithBhpIterations(ebosSimulator, bhp, well_potentials, deferred_logger); - } else { - // the well has a THP related constraint - well_potentials = computeWellPotentialWithTHP(ebosSimulator, deferred_logger, well_state); - } - - this->checkNegativeWellPotentials(well_potentials, - this->param_.check_well_operability_, - deferred_logger); - } - - - - - - - - template - double - StandardWell:: - connectionDensity([[maybe_unused]] const int globalConnIdx, - const int openConnIdx) const - { - return (openConnIdx < 0) - ? 0.0 - : this->connections_.rho(openConnIdx); - } - - - - - - template - void - StandardWell:: - updatePrimaryVariables(const SummaryState& summary_state, - const WellState& well_state, - DeferredLogger& deferred_logger) - { - if (!this->isOperableAndSolvable() && !this->wellIsStopped()) return; - - const bool stop_or_zero_rate_target = this->stopppedOrZeroRateTarget(summary_state, well_state); - this->primary_variables_.update(well_state, stop_or_zero_rate_target, deferred_logger); - - // other primary variables related to polymer injection - if constexpr (Base::has_polymermw) { - this->primary_variables_.updatePolyMW(well_state); - } - - this->primary_variables_.checkFinite(deferred_logger); - } - - - - - template - double - StandardWell:: - getRefDensity() const - { - return this->connections_.rho(); - } - - - - - template - void - StandardWell:: - updateWaterMobilityWithPolymer(const Simulator& ebos_simulator, - const int perf, - std::vector& mob, - DeferredLogger& deferred_logger) const - { - const int cell_idx = this->well_cells_[perf]; - const auto& int_quant = ebos_simulator.model().intensiveQuantities(cell_idx, /*timeIdx=*/ 0); - const EvalWell polymer_concentration = this->extendEval(int_quant.polymerConcentration()); - - // TODO: not sure should based on the well type or injecting/producing peforations - // it can be different for crossflow - if (this->isInjector()) { - // assume fully mixing within injecting wellbore - const auto& visc_mult_table = PolymerModule::plyviscViscosityMultiplierTable(int_quant.pvtRegionIndex()); - const unsigned waterCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::waterCompIdx); - mob[waterCompIdx] /= (this->extendEval(int_quant.waterViscosityCorrection()) * visc_mult_table.eval(polymer_concentration, /*extrapolate=*/true) ); - } - - if (PolymerModule::hasPlyshlog()) { - // we do not calculate the shear effects for injection wells when they do not - // inject polymer. - if (this->isInjector() && this->wpolymer() == 0.) { - return; - } - // compute the well water velocity with out shear effects. - // TODO: do we need to turn on crossflow here? - const bool allow_cf = this->getAllowCrossFlow() || openCrossFlowAvoidSingularity(ebos_simulator); - const EvalWell& bhp = this->primary_variables_.eval(Bhp); - - std::vector cq_s(this->num_components_, {this->primary_variables_.numWellEq() + Indices::numEq, 0.}); - PerforationRates perf_rates; - double trans_mult = ebos_simulator.problem().template rockCompTransMultiplier(int_quant, cell_idx); - const double elapsed_time = ebos_simulator.time(); - const double Tw = this->wellIndex(perf) * trans_mult; - computePerfRate(int_quant, mob, bhp, Tw, perf, allow_cf, elapsed_time, cq_s, - perf_rates, deferred_logger); - // TODO: make area a member - const double area = 2 * M_PI * this->perf_rep_radius_[perf] * this->perf_length_[perf]; - const auto& material_law_manager = ebos_simulator.problem().materialLawManager(); - const auto& scaled_drainage_info = - material_law_manager->oilWaterScaledEpsInfoDrainage(cell_idx); - const double swcr = scaled_drainage_info.Swcr; - const EvalWell poro = this->extendEval(int_quant.porosity()); - const EvalWell sw = this->extendEval(int_quant.fluidState().saturation(FluidSystem::waterPhaseIdx)); - // guard against zero porosity and no water - const EvalWell denom = max( (area * poro * (sw - swcr)), 1e-12); - const unsigned waterCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::waterCompIdx); - EvalWell water_velocity = cq_s[waterCompIdx] / denom * this->extendEval(int_quant.fluidState().invB(FluidSystem::waterPhaseIdx)); - - if (PolymerModule::hasShrate()) { - // the equation for the water velocity conversion for the wells and reservoir are from different version - // of implementation. It can be changed to be more consistent when possible. - water_velocity *= PolymerModule::shrate( int_quant.pvtRegionIndex() ) / this->bore_diameters_[perf]; - } - const EvalWell shear_factor = PolymerModule::computeShearFactor(polymer_concentration, - int_quant.pvtRegionIndex(), - water_velocity); - // modify the mobility with the shear factor. - mob[waterCompIdx] /= shear_factor; - } - } - - template - void - StandardWell::addWellContributions(SparseMatrixAdapter& jacobian) const - { - this->linSys_.extract(jacobian); - } - - - template - void - StandardWell::addWellPressureEquations(PressureMatrix& jacobian, - const BVector& weights, - const int pressureVarIndex, - const bool use_well_weights, - const WellState& well_state) const - { - this->linSys_.extractCPRPressureMatrix(jacobian, - weights, - pressureVarIndex, - use_well_weights, - *this, - Bhp, - well_state); - } - - - - template - typename StandardWell::EvalWell - StandardWell:: - pskinwater(const double throughput, - const EvalWell& water_velocity, - DeferredLogger& deferred_logger) const - { - if constexpr (Base::has_polymermw) { - const int water_table_id = this->polymerWaterTable_(); - if (water_table_id <= 0) { - OPM_DEFLOG_THROW(std::runtime_error, - fmt::format("Unused SKPRWAT table id used for well {}", name()), - deferred_logger); - } - const auto& water_table_func = PolymerModule::getSkprwatTable(water_table_id); - const EvalWell throughput_eval(this->primary_variables_.numWellEq() + Indices::numEq, throughput); - // the skin pressure when injecting water, which also means the polymer concentration is zero - EvalWell pskin_water(this->primary_variables_.numWellEq() + Indices::numEq, 0.0); - pskin_water = water_table_func.eval(throughput_eval, water_velocity); - return pskin_water; - } else { - OPM_DEFLOG_THROW(std::runtime_error, - fmt::format("Polymermw is not activated, while injecting " - "skin pressure is requested for well {}", name()), - deferred_logger); - } - } - - - - - - template - typename StandardWell::EvalWell - StandardWell:: - pskin(const double throughput, - const EvalWell& water_velocity, - const EvalWell& poly_inj_conc, - DeferredLogger& deferred_logger) const - { - if constexpr (Base::has_polymermw) { - const double sign = water_velocity >= 0. ? 1.0 : -1.0; - const EvalWell water_velocity_abs = abs(water_velocity); - if (poly_inj_conc == 0.) { - return sign * pskinwater(throughput, water_velocity_abs, deferred_logger); - } - const int polymer_table_id = this->polymerTable_(); - if (polymer_table_id <= 0) { - OPM_DEFLOG_THROW(std::runtime_error, - fmt::format("Unavailable SKPRPOLY table id used for well {}", name()), - deferred_logger); - } - const auto& skprpolytable = PolymerModule::getSkprpolyTable(polymer_table_id); - const double reference_concentration = skprpolytable.refConcentration; - const EvalWell throughput_eval(this->primary_variables_.numWellEq() + Indices::numEq, throughput); - // the skin pressure when injecting water, which also means the polymer concentration is zero - EvalWell pskin_poly(this->primary_variables_.numWellEq() + Indices::numEq, 0.0); - pskin_poly = skprpolytable.table_func.eval(throughput_eval, water_velocity_abs); - if (poly_inj_conc == reference_concentration) { - return sign * pskin_poly; - } - // poly_inj_conc != reference concentration of the table, then some interpolation will be required - const EvalWell pskin_water = pskinwater(throughput, water_velocity_abs, deferred_logger); - const EvalWell pskin = pskin_water + (pskin_poly - pskin_water) / reference_concentration * poly_inj_conc; - return sign * pskin; - } else { - OPM_DEFLOG_THROW(std::runtime_error, - fmt::format("Polymermw is not activated, while injecting " - "skin pressure is requested for well {}", name()), - deferred_logger); - } - } - - - - - - template - typename StandardWell::EvalWell - StandardWell:: - wpolymermw(const double throughput, - const EvalWell& water_velocity, - DeferredLogger& deferred_logger) const - { - if constexpr (Base::has_polymermw) { - const int table_id = this->polymerInjTable_(); - const auto& table_func = PolymerModule::getPlymwinjTable(table_id); - const EvalWell throughput_eval(this->primary_variables_.numWellEq() + Indices::numEq, throughput); - EvalWell molecular_weight(this->primary_variables_.numWellEq() + Indices::numEq, 0.); - if (this->wpolymer() == 0.) { // not injecting polymer - return molecular_weight; - } - molecular_weight = table_func.eval(throughput_eval, abs(water_velocity)); - return molecular_weight; - } else { - OPM_DEFLOG_THROW(std::runtime_error, - fmt::format("Polymermw is not activated, while injecting " - "polymer molecular weight is requested for well {}", name()), - deferred_logger); - } - } - - - - - - template - void - StandardWell:: - updateWaterThroughput(const double dt, WellState &well_state) const - { - if constexpr (Base::has_polymermw) { - if (this->isInjector()) { - auto& ws = well_state.well(this->index_of_well_); - auto& perf_water_throughput = ws.perf_data.water_throughput; - for (int perf = 0; perf < this->number_of_perforations_; ++perf) { - const double perf_water_vel = this->primary_variables_.value(Bhp + 1 + perf); - // we do not consider the formation damage due to water flowing from reservoir into wellbore - if (perf_water_vel > 0.) { - perf_water_throughput[perf] += perf_water_vel * dt; - } - } - } - } - } - - - - - - template - void - StandardWell:: - handleInjectivityRate(const Simulator& ebosSimulator, - const int perf, - std::vector& cq_s) const - { - const int cell_idx = this->well_cells_[perf]; - const auto& int_quants = ebosSimulator.model().intensiveQuantities(cell_idx, /*timeIdx=*/ 0); - const auto& fs = int_quants.fluidState(); - const EvalWell b_w = this->extendEval(fs.invB(FluidSystem::waterPhaseIdx)); - const double area = M_PI * this->bore_diameters_[perf] * this->perf_length_[perf]; - const int wat_vel_index = Bhp + 1 + perf; - const unsigned water_comp_idx = Indices::canonicalToActiveComponentIndex(FluidSystem::waterCompIdx); - - // water rate is update to use the form from water velocity, since water velocity is - // a primary variable now - cq_s[water_comp_idx] = area * this->primary_variables_.eval(wat_vel_index) * b_w; - } - - - - - template - void - StandardWell:: - handleInjectivityEquations(const Simulator& ebosSimulator, - const WellState& well_state, - const int perf, - const EvalWell& water_flux_s, - DeferredLogger& deferred_logger) - { - const int cell_idx = this->well_cells_[perf]; - const auto& int_quants = ebosSimulator.model().intensiveQuantities(cell_idx, /*timeIdx=*/ 0); - const auto& fs = int_quants.fluidState(); - const EvalWell b_w = this->extendEval(fs.invB(FluidSystem::waterPhaseIdx)); - const EvalWell water_flux_r = water_flux_s / b_w; - const double area = M_PI * this->bore_diameters_[perf] * this->perf_length_[perf]; - const EvalWell water_velocity = water_flux_r / area; - const int wat_vel_index = Bhp + 1 + perf; - - // equation for the water velocity - const EvalWell eq_wat_vel = this->primary_variables_.eval(wat_vel_index) - water_velocity; - - const auto& ws = well_state.well(this->index_of_well_); - const auto& perf_data = ws.perf_data; - const auto& perf_water_throughput = perf_data.water_throughput; - const double throughput = perf_water_throughput[perf]; - const int pskin_index = Bhp + 1 + this->number_of_perforations_ + perf; - - EvalWell poly_conc(this->primary_variables_.numWellEq() + Indices::numEq, 0.0); - poly_conc.setValue(this->wpolymer()); - - // equation for the skin pressure - const EvalWell eq_pskin = this->primary_variables_.eval(pskin_index) - - pskin(throughput, this->primary_variables_.eval(wat_vel_index), poly_conc, deferred_logger); - - StandardWellAssemble(*this). - assembleInjectivityEq(eq_pskin, - eq_wat_vel, - pskin_index, - wat_vel_index, - cell_idx, - this->primary_variables_.numWellEq(), - this->linSys_); - } - - - - - - template - void - StandardWell:: - checkConvergenceExtraEqs(const std::vector& res, - ConvergenceReport& report) const - { - // if different types of extra equations are involved, this function needs to be refactored further - - // checking the convergence of the extra equations related to polymer injectivity - if constexpr (Base::has_polymermw) { - WellConvergence(*this). - checkConvergencePolyMW(res, Bhp, this->param_.max_residual_allowed_, report); - } - } - - - - - - template - void - StandardWell:: - updateConnectionRatePolyMW(const EvalWell& cq_s_poly, - const IntensiveQuantities& int_quants, - const WellState& well_state, - const int perf, - std::vector& connectionRates, - DeferredLogger& deferred_logger) const - { - // the source term related to transport of molecular weight - EvalWell cq_s_polymw = cq_s_poly; - if (this->isInjector()) { - const int wat_vel_index = Bhp + 1 + perf; - const EvalWell water_velocity = this->primary_variables_.eval(wat_vel_index); - if (water_velocity > 0.) { // injecting - const auto& ws = well_state.well(this->index_of_well_); - const auto& perf_water_throughput = ws.perf_data.water_throughput; - const double throughput = perf_water_throughput[perf]; - const EvalWell molecular_weight = wpolymermw(throughput, water_velocity, deferred_logger); - cq_s_polymw *= molecular_weight; - } else { - // we do not consider the molecular weight from the polymer - // going-back to the wellbore through injector - cq_s_polymw *= 0.; - } - } else if (this->isProducer()) { - if (cq_s_polymw < 0.) { - cq_s_polymw *= this->extendEval(int_quants.polymerMoleWeight() ); - } else { - // we do not consider the molecular weight from the polymer - // re-injecting back through producer - cq_s_polymw *= 0.; - } - } - connectionRates[perf][Indices::contiPolymerMWEqIdx] = Base::restrictEval(cq_s_polymw); - } - - - - - - - template - std::optional - StandardWell:: - computeBhpAtThpLimitProd(const WellState& well_state, - const Simulator& ebos_simulator, - const SummaryState& summary_state, - DeferredLogger& deferred_logger) const - { - return computeBhpAtThpLimitProdWithAlq(ebos_simulator, - summary_state, - this->getALQ(well_state), - deferred_logger); - } - - template - std::optional - StandardWell:: - computeBhpAtThpLimitProdWithAlq(const Simulator& ebos_simulator, - const SummaryState& summary_state, - const double alq_value, - DeferredLogger& deferred_logger) const - { - // Make the frates() function. - auto frates = [this, &ebos_simulator, &deferred_logger](const double bhp) { - // Not solving the well equations here, which means we are - // calculating at the current Fg/Fw values of the - // well. This does not matter unless the well is - // crossflowing, and then it is likely still a good - // approximation. - std::vector rates(3); - computeWellRatesWithBhp(ebos_simulator, bhp, rates, deferred_logger); - this->adaptRatesForVFP(rates); - return rates; - }; - - double max_pressure = 0.0; - for (int perf = 0; perf < this->number_of_perforations_; ++perf) { - const int cell_idx = this->well_cells_[perf]; - const auto& int_quants = ebos_simulator.model().intensiveQuantities(cell_idx, /*timeIdx=*/ 0); - const auto& fs = int_quants.fluidState(); - double pressure_cell = this->getPerfCellPressure(fs).value(); - max_pressure = std::max(max_pressure, pressure_cell); - } - auto bhpAtLimit = WellBhpThpCalculator(*this).computeBhpAtThpLimitProd(frates, - summary_state, - max_pressure, - this->connections_.rho(), - alq_value, - this->getTHPConstraint(summary_state), - deferred_logger); - - if (bhpAtLimit) { - auto v = frates(*bhpAtLimit); - if (std::all_of(v.cbegin(), v.cend(), [](double i){ return i <= 0; }) ) { - return bhpAtLimit; - } - } - - - auto fratesIter = [this, &ebos_simulator, &deferred_logger](const double bhp) { - // Solver the well iterations to see if we are - // able to get a solution with an update - // solution - std::vector rates(3); - computeWellRatesWithBhpIterations(ebos_simulator, bhp, rates, deferred_logger); - this->adaptRatesForVFP(rates); - return rates; - }; - - bhpAtLimit = WellBhpThpCalculator(*this).computeBhpAtThpLimitProd(fratesIter, - summary_state, - max_pressure, - this->connections_.rho(), - alq_value, - this->getTHPConstraint(summary_state), - deferred_logger); - - - if (bhpAtLimit) { - // should we use fratesIter here since fratesIter is used in computeBhpAtThpLimitProd above? - auto v = frates(*bhpAtLimit); - if (std::all_of(v.cbegin(), v.cend(), [](double i){ return i <= 0; }) ) { - return bhpAtLimit; - } - } - - // we still don't get a valied solution. - return std::nullopt; - } - - - - template - std::optional - StandardWell:: - computeBhpAtThpLimitInj(const Simulator& ebos_simulator, - const SummaryState& summary_state, - DeferredLogger& deferred_logger) const - { - // Make the frates() function. - auto frates = [this, &ebos_simulator, &deferred_logger](const double bhp) { - // Not solving the well equations here, which means we are - // calculating at the current Fg/Fw values of the - // well. This does not matter unless the well is - // crossflowing, and then it is likely still a good - // approximation. - std::vector rates(3); - computeWellRatesWithBhp(ebos_simulator, bhp, rates, deferred_logger); - return rates; - }; - - return WellBhpThpCalculator(*this).computeBhpAtThpLimitInj(frates, - summary_state, - this->connections_.rho(), - 1e-6, - 50, - true, - deferred_logger); - } - - - - - - template - bool - StandardWell:: - iterateWellEqWithControl(const Simulator& ebosSimulator, - const double dt, - const Well::InjectionControls& inj_controls, - const Well::ProductionControls& prod_controls, - WellState& well_state, - const GroupState& group_state, - DeferredLogger& deferred_logger) - { - const int max_iter = this->param_.max_inner_iter_wells_; - int it = 0; - bool converged; - bool relax_convergence = false; - this->regularize_ = false; - const auto& summary_state = ebosSimulator.vanguard().summaryState(); - do { - assembleWellEqWithoutIteration(ebosSimulator, dt, inj_controls, prod_controls, well_state, group_state, deferred_logger); - - if (it > this->param_.strict_inner_iter_wells_) { - relax_convergence = true; - this->regularize_ = true; - } - - auto report = getWellConvergence(summary_state, well_state, Base::B_avg_, deferred_logger, relax_convergence); - - converged = report.converged(); - if (converged) { - break; - } - - ++it; - solveEqAndUpdateWellState(summary_state, well_state, deferred_logger); - - // TODO: when this function is used for well testing purposes, will need to check the controls, so that we will obtain convergence - // under the most restrictive control. Based on this converged results, we can check whether to re-open the well. Either we refactor - // this function or we use different functions for the well testing purposes. - // We don't allow for switching well controls while computing well potentials and testing wells - // updateWellControl(ebosSimulator, well_state, deferred_logger); - initPrimaryVariablesEvaluation(); - } while (it < max_iter); - - return converged; - } - - - template - std::vector - StandardWell:: - computeCurrentWellRates(const Simulator& ebosSimulator, - DeferredLogger& deferred_logger) const - { - // Calculate the rates that follow from the current primary variables. - std::vector well_q_s(this->num_components_, 0.); - const EvalWell& bhp = this->primary_variables_.eval(Bhp); - const bool allow_cf = this->getAllowCrossFlow() || openCrossFlowAvoidSingularity(ebosSimulator); - for (int perf = 0; perf < this->number_of_perforations_; ++perf) { - const int cell_idx = this->well_cells_[perf]; - const auto& intQuants = ebosSimulator.model().intensiveQuantities(cell_idx, /*timeIdx=*/ 0); - std::vector mob(this->num_components_, 0.); - getMobility(ebosSimulator, perf, mob, deferred_logger); - std::vector cq_s(this->num_components_, 0.); - double trans_mult = ebosSimulator.problem().template rockCompTransMultiplier(intQuants, cell_idx); - double elapsed_time = ebosSimulator.time(); - const double Tw = this->wellIndex(perf) * trans_mult; - PerforationRates perf_rates; - computePerfRate(intQuants, mob, bhp.value(), Tw, perf, allow_cf, elapsed_time, - cq_s, perf_rates, deferred_logger); - for (int comp = 0; comp < this->num_components_; ++comp) { - well_q_s[comp] += cq_s[comp]; - } - } - const auto& comm = this->parallel_well_info_.communication(); - if (comm.size() > 1) - { - comm.sum(well_q_s.data(), well_q_s.size()); - } - return well_q_s; - } - - - - template - std::vector - StandardWell:: - getPrimaryVars() const - { - const int num_pri_vars = this->primary_variables_.numWellEq(); - std::vector retval(num_pri_vars); - for (int ii = 0; ii < num_pri_vars; ++ii) { - retval[ii] = this->primary_variables_.value(ii); - } - return retval; - } - - - - - - template - int - StandardWell:: - setPrimaryVars(std::vector::const_iterator it) - { - const int num_pri_vars = this->primary_variables_.numWellEq(); - for (int ii = 0; ii < num_pri_vars; ++ii) { - this->primary_variables_.setValue(ii, it[ii]); - } - return num_pri_vars; - } - - - template - typename StandardWell::Eval - StandardWell:: - connectionRateEnergy(const double maxOilSaturation, - const std::vector& cq_s, - const IntensiveQuantities& intQuants, - DeferredLogger& deferred_logger) const - { - auto fs = intQuants.fluidState(); - Eval result = 0; - for (unsigned phaseIdx = 0; phaseIdx < FluidSystem::numPhases; ++phaseIdx) { - if (!FluidSystem::phaseIsActive(phaseIdx)) { - continue; - } - - // convert to reservoir conditions - EvalWell cq_r_thermal(this->primary_variables_.numWellEq() + Indices::numEq, 0.); - const unsigned activeCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::solventComponentIndex(phaseIdx)); - const bool both_oil_gas = FluidSystem::phaseIsActive(FluidSystem::oilPhaseIdx) && FluidSystem::phaseIsActive(FluidSystem::gasPhaseIdx); - if (!both_oil_gas || FluidSystem::waterPhaseIdx == phaseIdx) { - cq_r_thermal = cq_s[activeCompIdx] / this->extendEval(fs.invB(phaseIdx)); - } else { - // remove dissolved gas and vapporized oil - const unsigned oilCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::oilCompIdx); - const unsigned gasCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::gasCompIdx); - // q_os = q_or * b_o + rv * q_gr * b_g - // q_gs = q_gr * g_g + rs * q_or * b_o - // q_gr = 1 / (b_g * d) * (q_gs - rs * q_os) - // d = 1.0 - rs * rv - const EvalWell d = this->extendEval(1.0 - fs.Rv() * fs.Rs()); - if (d <= 0.0) { - deferred_logger.debug( - fmt::format("Problematic d value {} obtained for well {}" - " during calculateSinglePerf with rs {}" - ", rv {}. Continue as if no dissolution (rs = 0) and" - " vaporization (rv = 0) for this connection.", - d, this->name(), fs.Rs(), fs.Rv())); - cq_r_thermal = cq_s[activeCompIdx] / this->extendEval(fs.invB(phaseIdx)); - } else { - if (FluidSystem::gasPhaseIdx == phaseIdx) { - cq_r_thermal = (cq_s[gasCompIdx] - - this->extendEval(fs.Rs()) * cq_s[oilCompIdx]) / - (d * this->extendEval(fs.invB(phaseIdx)) ); - } else if (FluidSystem::oilPhaseIdx == phaseIdx) { - // q_or = 1 / (b_o * d) * (q_os - rv * q_gs) - cq_r_thermal = (cq_s[oilCompIdx] - this->extendEval(fs.Rv()) * - cq_s[gasCompIdx]) / - (d * this->extendEval(fs.invB(phaseIdx)) ); - } - } - } - - // change temperature for injecting fluids - if (this->isInjector() && cq_s[activeCompIdx] > 0.0){ - // only handles single phase injection now - assert(this->well_ecl_.injectorType() != InjectorType::MULTI); - fs.setTemperature(this->well_ecl_.temperature()); - typedef typename std::decay::type::Scalar FsScalar; - typename FluidSystem::template ParameterCache paramCache; - const unsigned pvtRegionIdx = intQuants.pvtRegionIndex(); - paramCache.setRegionIndex(pvtRegionIdx); - paramCache.setMaxOilSat(maxOilSaturation); - paramCache.updatePhase(fs, phaseIdx); - - const auto& rho = FluidSystem::density(fs, paramCache, phaseIdx); - fs.setDensity(phaseIdx, rho); - const auto& h = FluidSystem::enthalpy(fs, paramCache, phaseIdx); - fs.setEnthalpy(phaseIdx, h); - cq_r_thermal *= this->extendEval(fs.enthalpy(phaseIdx)) * this->extendEval(fs.density(phaseIdx)); - result += getValue(cq_r_thermal); - } else { - // compute the thermal flux - cq_r_thermal *= this->extendEval(fs.enthalpy(phaseIdx)) * this->extendEval(fs.density(phaseIdx)); - result += Base::restrictEval(cq_r_thermal); - } - } - - return result; - } - - - template - template - void - StandardWell:: - gasOilPerfRateInj(const std::vector& cq_s, - PerforationRates& perf_rates, - const Value& rv, - const Value& rs, - const Value& pressure, - const Value& rvw, - DeferredLogger& deferred_logger) const - { - const unsigned oilCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::oilCompIdx); - const unsigned gasCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::gasCompIdx); - // TODO: the formulations here remain to be tested with cases with strong crossflow through production wells - // s means standard condition, r means reservoir condition - // q_os = q_or * b_o + rv * q_gr * b_g - // q_gs = q_gr * b_g + rs * q_or * b_o - // d = 1.0 - rs * rv - // q_or = 1 / (b_o * d) * (q_os - rv * q_gs) - // q_gr = 1 / (b_g * d) * (q_gs - rs * q_os) - - const double d = 1.0 - getValue(rv) * getValue(rs); - - if (d <= 0.0) { - deferred_logger.debug(dValueError(d, this->name(), - "gasOilPerfRateInj", - rs, rv, pressure)); - } else { - // vaporized oil into gas - // rv * q_gr * b_g = rv * (q_gs - rs * q_os) / d - perf_rates.vap_oil = getValue(rv) * (getValue(cq_s[gasCompIdx]) - getValue(rs) * getValue(cq_s[oilCompIdx])) / d; - // dissolved of gas in oil - // rs * q_or * b_o = rs * (q_os - rv * q_gs) / d - perf_rates.dis_gas = getValue(rs) * (getValue(cq_s[oilCompIdx]) - getValue(rv) * getValue(cq_s[gasCompIdx])) / d; - } - - if (FluidSystem::phaseIsActive(FluidSystem::waterPhaseIdx)) { - // q_ws = q_wr * b_w + rvw * q_gr * b_g - // q_wr = 1 / b_w * (q_ws - rvw * q_gr * b_g) = 1 / b_w * (q_ws - rvw * 1 / d (q_gs - rs * q_os)) - // vaporized water in gas - // rvw * q_gr * b_g = q_ws -q_wr *b_w = rvw * (q_gs -rs *q_os) / d - perf_rates.vap_wat = getValue(rvw) * (getValue(cq_s[gasCompIdx]) - getValue(rs) * getValue(cq_s[oilCompIdx])) / d; - } - } - - - - template - template - void - StandardWell:: - gasOilPerfRateProd(std::vector& cq_s, - PerforationRates& perf_rates, - const Value& rv, - const Value& rs, - const Value& rvw) const - { - const unsigned oilCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::oilCompIdx); - const unsigned gasCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::gasCompIdx); - const Value cq_sOil = cq_s[oilCompIdx]; - const Value cq_sGas = cq_s[gasCompIdx]; - const Value dis_gas = rs * cq_sOil; - const Value vap_oil = rv * cq_sGas; - - cq_s[gasCompIdx] += dis_gas; - cq_s[oilCompIdx] += vap_oil; - - // recording the perforation solution gas rate and solution oil rates - if (this->isProducer()) { - perf_rates.dis_gas = getValue(dis_gas); - perf_rates.vap_oil = getValue(vap_oil); - } - - if (FluidSystem::phaseIsActive(FluidSystem::waterPhaseIdx)) { - const unsigned waterCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::waterCompIdx); - const Value vap_wat = rvw * cq_sGas; - cq_s[waterCompIdx] += vap_wat; - if (this->isProducer()) - perf_rates.vap_wat = getValue(vap_wat); - } - } - - - template - template - void - StandardWell:: - gasWaterPerfRateProd(std::vector& cq_s, - PerforationRates& perf_rates, - const Value& rvw, - const Value& rsw) const - { - const unsigned waterCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::waterCompIdx); - const unsigned gasCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::gasCompIdx); - const Value cq_sWat = cq_s[waterCompIdx]; - const Value cq_sGas = cq_s[gasCompIdx]; - const Value vap_wat = rvw * cq_sGas; - const Value dis_gas_wat = rsw * cq_sWat; - cq_s[waterCompIdx] += vap_wat; - cq_s[gasCompIdx] += dis_gas_wat; - if (this->isProducer()) { - perf_rates.vap_wat = getValue(vap_wat); - perf_rates.dis_gas_in_water = getValue(dis_gas_wat); - } - } - - - template - template - void - StandardWell:: - gasWaterPerfRateInj(const std::vector& cq_s, - PerforationRates& perf_rates, - const Value& rvw, - const Value& rsw, - const Value& pressure, - DeferredLogger& deferred_logger) const - - { - const unsigned gasCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::gasCompIdx); - const unsigned waterCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::waterCompIdx); - - const double dw = 1.0 - getValue(rvw) * getValue(rsw); - - if (dw <= 0.0) { - deferred_logger.debug(dValueError(dw, this->name(), - "gasWaterPerfRateInj", - rsw, rvw, pressure)); - } else { - // vaporized water into gas - // rvw * q_gr * b_g = rvw * (q_gs - rsw * q_ws) / dw - perf_rates.vap_wat = getValue(rvw) * (getValue(cq_s[gasCompIdx]) - getValue(rsw) * getValue(cq_s[waterCompIdx])) / dw; - // dissolved gas in water - // rsw * q_wr * b_w = rsw * (q_ws - rvw * q_gs) / dw - perf_rates.dis_gas_in_water = getValue(rsw) * (getValue(cq_s[waterCompIdx]) - getValue(rvw) * getValue(cq_s[gasCompIdx])) / dw; - } - } - - - template - template - void - StandardWell:: - disOilVapWatVolumeRatio(Value& volumeRatio, - const Value& rvw, - const Value& rsw, - const Value& pressure, - const std::vector& cmix_s, - const std::vector& b_perfcells_dense, - DeferredLogger& deferred_logger) const - { - const unsigned waterCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::waterCompIdx); - const unsigned gasCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::gasCompIdx); - // Incorporate RSW/RVW factors if both water and gas active - const Value d = 1.0 - rvw * rsw; - - if (d <= 0.0) { - deferred_logger.debug(dValueError(d, this->name(), - "disOilVapWatVolumeRatio", - rsw, rvw, pressure)); - } - const Value tmp_wat = d > 0.0 ? (cmix_s[waterCompIdx] - rvw * cmix_s[gasCompIdx]) / d - : cmix_s[waterCompIdx]; - volumeRatio += tmp_wat / b_perfcells_dense[waterCompIdx]; - - const Value tmp_gas = d > 0.0 ? (cmix_s[gasCompIdx] - rsw * cmix_s[waterCompIdx]) / d - : cmix_s[waterCompIdx]; - volumeRatio += tmp_gas / b_perfcells_dense[gasCompIdx]; - } - - - template - template - void - StandardWell:: - gasOilVolumeRatio(Value& volumeRatio, - const Value& rv, - const Value& rs, - const Value& pressure, - const std::vector& cmix_s, - const std::vector& b_perfcells_dense, - DeferredLogger& deferred_logger) const - { - const unsigned oilCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::oilCompIdx); - const unsigned gasCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::gasCompIdx); - // Incorporate RS/RV factors if both oil and gas active - const Value d = 1.0 - rv * rs; - - if (d <= 0.0) { - deferred_logger.debug(dValueError(d, this->name(), - "gasOilVolumeRatio", - rs, rv, pressure)); - } - const Value tmp_oil = d > 0.0? (cmix_s[oilCompIdx] - rv * cmix_s[gasCompIdx]) / d : cmix_s[oilCompIdx]; - volumeRatio += tmp_oil / b_perfcells_dense[oilCompIdx]; - - const Value tmp_gas = d > 0.0? (cmix_s[gasCompIdx] - rs * cmix_s[oilCompIdx]) / d : cmix_s[gasCompIdx]; - volumeRatio += tmp_gas / b_perfcells_dense[gasCompIdx]; - } - - template - template - Value - StandardWell::wellIndexEval(const int perf, const double elapsed_time, const Value& pressure) const { - KerasModel model; - model.LoadModel(Base::ml_wi_filename_); - const auto& connection = Base::well_ecl_.getConnections()[perf]; - - // Run prediction. - - // give number of input parameters - Tensor in{5}; - - const auto p = scaleFunction(pressure, ${xmin[0]}, ${xmax[0]}); - const auto temp = Value(scaleFunction(${temperature}, ${xmin[1]}, ${xmax[1]})); - const auto k = Value(scaleFunction((connection.Kh()/connection.connectionLength())*1.01324997e15, ${xmin[2]}, ${xmax[2]})); - const auto t = Value(scaleFunction(elapsed_time / 3600, ${xmin[3]}, ${xmax[3]})); - //const auto t = Value(scaleFunction(240, ${xmin[3]}, ${xmax[3]})); - const auto re = Value(scaleFunction(connection.r0(), ${xmin[4]}, ${xmax[4]})); - - // note order need to be the same as under training - in.data_ = {{p, temp, k, t, re}}; - Tensor out; - model.Apply(&in, &out); - std::cout << "p: " << pressure << std::endl; - std::cout << "T: " << ${temperature} << std::endl; - std::cout << "k: " << (connection.Kh()/connection.connectionLength())*1.01324997e15 << std::endl; - std::cout << "t: " << elapsed_time / 3600 << " re: " << connection.r0() << " WI: " << - unscaleFunction(out.data_[0],${ymin}, ${ymax}) << std::endl; - std::cout << "t_scaled: " << t << " re_scaled: " << re << " WI_scaled: " << - getValue(out.data_[0]) << std::endl; - return unscaleFunction(out.data_[0],${ymin}, ${ymax}); - } - - template - template - Value - StandardWell::scaleFunction(Value X, double min, double max) const { - return (X - min) / (max - min); -} - - template - template - Value - StandardWell::unscaleFunction(Value X, double min, double max) const { - return X * (max - min) + min; -} - -} // namespace Opm diff --git a/src/pyopmnearwell/templates/standardwell_impl/co2_local_stencil.mako b/src/pyopmnearwell/templates/standardwell_impl/co2_local_stencil.mako deleted file mode 100644 index c6ee8e1..0000000 --- a/src/pyopmnearwell/templates/standardwell_impl/co2_local_stencil.mako +++ /dev/null @@ -1,2712 +0,0 @@ -/* - Copyright 2017 SINTEF Digital, Mathematics and Cybernetics. - Copyright 2017 Statoil ASA. - Copyright 2016 - 2017 IRIS AS. - - This file is part of the Open Porous Media project (OPM). - - OPM is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - OPM is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with OPM. If not, see . -*/ - -#include - -#include - -#include - -#include -#include -#include -#include -#include - -#include - -#include - -#include -#include -#include - - -namespace { - -template -auto dValueError(const dValue& d, - const std::string& name, - const std::string& methodName, - const Value& Rs, - const Value& Rv, - const Value& pressure) -{ - return fmt::format("Problematic d value {} obtained for well {}" - " during {} calculations with rs {}" - ", rv {} and pressure {}." - " Continue as if no dissolution (rs = 0) and vaporization (rv = 0) " - " for this connection.", d, name, methodName, Rs, Rv, pressure); -} - -} - -namespace Opm -{ - - template - StandardWell:: - StandardWell(const Well& well, - const ParallelWellInfo& pw_info, - const int time_step, - const ModelParameters& param, - const RateConverterType& rate_converter, - const int pvtRegionIdx, - const int num_components, - const int num_phases, - const int index_of_well, - const std::vector& perf_data) - : Base(well, pw_info, time_step, param, rate_converter, pvtRegionIdx, num_components, num_phases, index_of_well, perf_data) - , StdWellEval(static_cast&>(*this)) - , regularize_(false) - { - assert(this->num_components_ == numWellConservationEq); - } - - - - - - template - void - StandardWell:: - init(const PhaseUsage* phase_usage_arg, - const std::vector& depth_arg, - const double gravity_arg, - const int num_cells, - const std::vector< Scalar >& B_avg, - const bool changed_to_open_this_step) - { - Base::init(phase_usage_arg, depth_arg, gravity_arg, num_cells, B_avg, changed_to_open_this_step); - this->StdWellEval::init(this->perf_depth_, depth_arg, num_cells, Base::has_polymermw); - } - - - - - - template - void StandardWell:: - initPrimaryVariablesEvaluation() - { - this->primary_variables_.init(); - } - - - - - - template - template - void - StandardWell:: - computePerfRate(const IntensiveQuantities& intQuants, - const std::vector& mob, - const Value& bhp, - const double Tw, - const int perf, - const bool allow_cf, - const double elapsed_time, - std::vector& cq_s, - PerforationRates& perf_rates, - DeferredLogger& deferred_logger, - const Simulator& ebosSimulator) const - { - auto obtain = [this](const Eval& value) - { - if constexpr (std::is_same_v) { - static_cast(this); // suppress clang warning - return getValue(value); - } else { - return this->extendEval(value); - } - }; - auto obtainN = [](const auto& value) - { - if constexpr (std::is_same_v) { - return getValue(value); - } else { - return value; - } - }; - auto zeroElem = [this]() - { - if constexpr (std::is_same_v) { - static_cast(this); // suppress clang warning - return 0.0; - } else { - return Value{this->primary_variables_.numWellEq() + Indices::numEq, 0.0}; - } - }; - - const auto& fs = intQuants.fluidState(); - const Value pressure = obtain(this->getPerfCellPressure(fs)); - const Value rs = obtain(fs.Rs()); - const Value rv = obtain(fs.Rv()); - const Value rvw = obtain(fs.Rvw()); - const Value rsw = obtain(fs.Rsw()); - - std::vector b_perfcells_dense(this->numComponents(), zeroElem()); - for (unsigned phaseIdx = 0; phaseIdx < FluidSystem::numPhases; ++phaseIdx) { - if (!FluidSystem::phaseIsActive(phaseIdx)) { - continue; - } - const unsigned compIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::solventComponentIndex(phaseIdx)); - b_perfcells_dense[compIdx] = obtain(fs.invB(phaseIdx)); - } - if constexpr (has_solvent) { - b_perfcells_dense[Indices::contiSolventEqIdx] = obtain(intQuants.solventInverseFormationVolumeFactor()); - } - - if constexpr (has_zFraction) { - if (this->isInjector()) { - const unsigned gasCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::gasCompIdx); - b_perfcells_dense[gasCompIdx] *= (1.0 - this->wsolvent()); - b_perfcells_dense[gasCompIdx] += this->wsolvent()*intQuants.zPureInvFormationVolumeFactor().value(); - } - } - - Value skin_pressure = zeroElem(); - if (has_polymermw) { - if (this->isInjector()) { - const int pskin_index = Bhp + 1 + this->numPerfs() + perf; - skin_pressure = obtainN(this->primary_variables_.eval(pskin_index)); - } - } - - // surface volume fraction of fluids within wellbore - std::vector cmix_s(this->numComponents(), zeroElem()); - for (int componentIdx = 0; componentIdx < this->numComponents(); ++componentIdx) { - cmix_s[componentIdx] = obtainN(this->primary_variables_.surfaceVolumeFraction(componentIdx)); - } - - computePerfRate(mob, - pressure, - bhp, - rs, - rv, - rvw, - rsw, - b_perfcells_dense, - Tw, - perf, - allow_cf, - skin_pressure, - cmix_s, - elapsed_time, - cq_s, - perf_rates, - deferred_logger, - ebosSimulator); - } - - template - template - void - StandardWell:: - computePerfRate(const std::vector& mob, - const Value& pressure, - const Value& bhp, - const Value& rs, - const Value& rv, - const Value& rvw, - const Value& rsw, - std::vector& b_perfcells_dense, - const double Tw, - const int perf, - const bool allow_cf, - const Value& skin_pressure, - const std::vector& cmix_s, - const double elapsed_time, - std::vector& cq_s, - PerforationRates& perf_rates, - DeferredLogger& deferred_logger, - const Simulator& ebosSimulator) const - { - // Pressure drawdown (also used to determine direction of flow) - const Value well_pressure = bhp + this->connections_.pressure_diff(perf); - Value drawdown = pressure - well_pressure; - if (this->isInjector()) { - drawdown += skin_pressure; - } - - // producing perforations - if (drawdown > 0) { - // Do nothing if crossflow is not allowed - if (!allow_cf && this->isInjector()) { - return; - } - - // compute component volumetric rates at standard conditions - for (int componentIdx = 0; componentIdx < this->numComponents(); ++componentIdx) { - const Value cq_p = - Tw * (mob[componentIdx] * drawdown); - cq_s[componentIdx] = b_perfcells_dense[componentIdx] * cq_p; - } - - if (FluidSystem::phaseIsActive(FluidSystem::oilPhaseIdx) && FluidSystem::phaseIsActive(FluidSystem::gasPhaseIdx)) { - gasOilPerfRateProd(cq_s, perf_rates, rv, rs, rvw); - } else if (FluidSystem::phaseIsActive(FluidSystem::waterPhaseIdx) && FluidSystem::phaseIsActive(FluidSystem::gasPhaseIdx)) { - gasWaterPerfRateProd(cq_s, perf_rates, rvw, rsw); - } - } else { - // Do nothing if crossflow is not allowed - if (!allow_cf && this->isProducer()) { - return; - } - - // Using total mobilities - Value total_mob_dense = mob[0]; - for (int componentIdx = 1; componentIdx < this->numComponents(); ++componentIdx) { - total_mob_dense += mob[componentIdx]; - } - - // Use well index from ML - if (!Base::ml_wi_filename_.empty()) { - Value WI; - if (allow_cf) { - OPM_DEFLOG_THROW(std::runtime_error, - fmt::format("ML well index only implemented with no cross flow. Well {}", name()), - deferred_logger); - } - if constexpr (std::is_same_v) { - WI = this->extendEval(wellIndexEval(perf, ebosSimulator)); - } else { - WI = wellIndexEval(perf, ebosSimulator); - } - auto injectorType = this->well_ecl_.injectorType(); - if (injectorType == InjectorType::WATER) { - const unsigned waterCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::waterCompIdx); - //cq_s[waterCompIdx] = - WI * (total_mob_dense * drawdown) * b_perfcells_dense[waterCompIdx]; - cq_s[waterCompIdx] = - WI * drawdown; - std::cout << cq_s[waterCompIdx] << " " << - Tw * (total_mob_dense * drawdown)*b_perfcells_dense[waterCompIdx] << " " << cmix_s[waterCompIdx] << " " << b_perfcells_dense[waterCompIdx] << " " << total_mob_dense << std::endl; - std::cout << WI << " " << (Tw*total_mob_dense*b_perfcells_dense[waterCompIdx]) << std::endl; - } - else if (injectorType == InjectorType::GAS) { - const unsigned gasCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::gasCompIdx); - //cq_s[gasCompIdx] = - WI * (total_mob_dense * drawdown) * b_perfcells_dense[gasCompIdx]; - cq_s[gasCompIdx] = - WI * drawdown; - } - } else { - std::cout << "ML model not found"; - - const Value cqt_i = - Tw * (total_mob_dense * drawdown); - - // compute volume ratio between connection at standard conditions - Value volumeRatio = bhp * 0.0; // initialize it with the correct type -; - if (FluidSystem::enableVaporizedWater() && FluidSystem::enableDissolvedGasInWater()) { - disOilVapWatVolumeRatio(volumeRatio, rvw, rsw, pressure, - cmix_s, b_perfcells_dense, deferred_logger); - } else { - if (FluidSystem::phaseIsActive(FluidSystem::waterPhaseIdx)) { - const unsigned waterCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::waterCompIdx); - volumeRatio += cmix_s[waterCompIdx] / b_perfcells_dense[waterCompIdx]; - } - } - - if constexpr (Indices::enableSolvent) { - volumeRatio += cmix_s[Indices::contiSolventEqIdx] / b_perfcells_dense[Indices::contiSolventEqIdx]; - } - - if (FluidSystem::phaseIsActive(FluidSystem::oilPhaseIdx) && FluidSystem::phaseIsActive(FluidSystem::gasPhaseIdx)) { - gasOilVolumeRatio(volumeRatio, rv, rs, pressure, - cmix_s, b_perfcells_dense, deferred_logger); - } - else { - if (FluidSystem::phaseIsActive(FluidSystem::oilPhaseIdx)) { - const unsigned oilCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::oilCompIdx); - volumeRatio += cmix_s[oilCompIdx] / b_perfcells_dense[oilCompIdx]; - } - if (FluidSystem::phaseIsActive(FluidSystem::gasPhaseIdx)) { - const unsigned gasCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::gasCompIdx); - volumeRatio += cmix_s[gasCompIdx] / b_perfcells_dense[gasCompIdx]; - } - } - - // injecting connections total volumerates at standard conditions - Value cqt_is = cqt_i / volumeRatio; - for (int componentIdx = 0; componentIdx < this->numComponents(); ++componentIdx) { - cq_s[componentIdx] = cmix_s[componentIdx] * cqt_is; - } - } - - // calculating the perforation solution gas rate and solution oil rates - if (this->isProducer()) { - if (FluidSystem::phaseIsActive(FluidSystem::oilPhaseIdx) && FluidSystem::phaseIsActive(FluidSystem::gasPhaseIdx)) { - gasOilPerfRateInj(cq_s, perf_rates, - rv, rs, pressure, rvw, deferred_logger); - } - if (FluidSystem::phaseIsActive(FluidSystem::gasPhaseIdx) && FluidSystem::phaseIsActive(FluidSystem::waterPhaseIdx)) { - //no oil - gasWaterPerfRateInj(cq_s, perf_rates, rvw, rsw, - pressure, deferred_logger); - } - } - } - } - - - template - void - StandardWell:: - assembleWellEqWithoutIteration(const Simulator& ebosSimulator, - const double dt, - const Well::InjectionControls& inj_controls, - const Well::ProductionControls& prod_controls, - WellState& well_state, - const GroupState& group_state, - DeferredLogger& deferred_logger) - { - // TODO: only_wells should be put back to save some computation - // for example, the matrices B C does not need to update if only_wells - if (!this->isOperableAndSolvable() && !this->wellIsStopped()) return; - - // clear all entries - this->linSys_.clear(); - - assembleWellEqWithoutIterationImpl(ebosSimulator, dt, inj_controls, prod_controls, well_state, group_state, deferred_logger); - } - - - - - template - void - StandardWell:: - assembleWellEqWithoutIterationImpl(const Simulator& ebosSimulator, - const double dt, - const Well::InjectionControls& inj_controls, - const Well::ProductionControls& prod_controls, - WellState& well_state, - const GroupState& group_state, - DeferredLogger& deferred_logger) - { - // try to regularize equation if the well does not converge - const Scalar regularization_factor = this->regularize_? this->param_.regularization_factor_wells_ : 1.0; - const double volume = 0.1 * unit::cubic(unit::feet) * regularization_factor; - - auto& ws = well_state.well(this->index_of_well_); - ws.phase_mixing_rates.fill(0.0); - - const int np = this->number_of_phases_; - - std::vector connectionRates = this->connectionRates_; // Copy to get right size. - auto& perf_data = ws.perf_data; - auto& perf_rates = perf_data.phase_rates; - for (int perf = 0; perf < this->number_of_perforations_; ++perf) { - // Calculate perforation quantities. - std::vector cq_s(this->num_components_, {this->primary_variables_.numWellEq() + Indices::numEq, 0.0}); - EvalWell water_flux_s{this->primary_variables_.numWellEq() + Indices::numEq, 0.0}; - EvalWell cq_s_zfrac_effective{this->primary_variables_.numWellEq() + Indices::numEq, 0.0}; - calculateSinglePerf(ebosSimulator, perf, well_state, connectionRates, cq_s, water_flux_s, cq_s_zfrac_effective, deferred_logger); - - // Equation assembly for this perforation. - if constexpr (has_polymer && Base::has_polymermw) { - if (this->isInjector()) { - handleInjectivityEquations(ebosSimulator, well_state, perf, water_flux_s, deferred_logger); - } - } - const int cell_idx = this->well_cells_[perf]; - for (int componentIdx = 0; componentIdx < this->num_components_; ++componentIdx) { - // the cq_s entering mass balance equations need to consider the efficiency factors. - const EvalWell cq_s_effective = cq_s[componentIdx] * this->well_efficiency_factor_; - - connectionRates[perf][componentIdx] = Base::restrictEval(cq_s_effective); - - StandardWellAssemble(*this). - assemblePerforationEq(cq_s_effective, - componentIdx, - cell_idx, - this->primary_variables_.numWellEq(), - this->linSys_); - - // Store the perforation phase flux for later usage. - if (has_solvent && componentIdx == Indices::contiSolventEqIdx) { - auto& perf_rate_solvent = perf_data.solvent_rates; - perf_rate_solvent[perf] = cq_s[componentIdx].value(); - } else { - perf_rates[perf*np + this->ebosCompIdxToFlowCompIdx(componentIdx)] = cq_s[componentIdx].value(); - } - } - - if constexpr (has_zFraction) { - StandardWellAssemble(*this). - assembleZFracEq(cq_s_zfrac_effective, - cell_idx, - this->primary_variables_.numWellEq(), - this->linSys_); - } - } - // Update the connection - this->connectionRates_ = connectionRates; - - // Accumulate dissolved gas and vaporized oil flow rates across all - // ranks sharing this well (this->index_of_well_). - { - const auto& comm = this->parallel_well_info_.communication(); - comm.sum(ws.phase_mixing_rates.data(), ws.phase_mixing_rates.size()); - } - - // accumulate resWell_ and duneD_ in parallel to get effects of all perforations (might be distributed) - this->linSys_.sumDistributed(this->parallel_well_info_.communication()); - - // add vol * dF/dt + Q to the well equations; - for (int componentIdx = 0; componentIdx < numWellConservationEq; ++componentIdx) { - // TODO: following the development in MSW, we need to convert the volume of the wellbore to be surface volume - // since all the rates are under surface condition - EvalWell resWell_loc(this->primary_variables_.numWellEq() + Indices::numEq, 0.0); - if (FluidSystem::numActivePhases() > 1) { - assert(dt > 0); - resWell_loc += (this->primary_variables_.surfaceVolumeFraction(componentIdx) - - this->F0_[componentIdx]) * volume / dt; - } - resWell_loc -= this->primary_variables_.getQs(componentIdx) * this->well_efficiency_factor_; - StandardWellAssemble(*this). - assembleSourceEq(resWell_loc, - componentIdx, - this->primary_variables_.numWellEq(), - this->linSys_); - } - - const auto& summaryState = ebosSimulator.vanguard().summaryState(); - const Schedule& schedule = ebosSimulator.vanguard().schedule(); - StandardWellAssemble(*this). - assembleControlEq(well_state, group_state, - schedule, summaryState, - inj_controls, prod_controls, - this->primary_variables_, - this->connections_.rho(), - this->linSys_, - deferred_logger); - - - // do the local inversion of D. - try { - this->linSys_.invert(); - } catch( ... ) { - OPM_DEFLOG_THROW(NumericalProblem, "Error when inverting local well equations for well " + name(), deferred_logger); - } - } - - - - - template - void - StandardWell:: - calculateSinglePerf(const Simulator& ebosSimulator, - const int perf, - WellState& well_state, - std::vector& connectionRates, - std::vector& cq_s, - EvalWell& water_flux_s, - EvalWell& cq_s_zfrac_effective, - DeferredLogger& deferred_logger) const - { - const bool allow_cf = this->getAllowCrossFlow() || openCrossFlowAvoidSingularity(ebosSimulator); - const EvalWell& bhp = this->primary_variables_.eval(Bhp); - const int cell_idx = this->well_cells_[perf]; - const auto& intQuants = ebosSimulator.model().intensiveQuantities(cell_idx, /*timeIdx=*/ 0); - std::vector mob(this->num_components_, {this->primary_variables_.numWellEq() + Indices::numEq, 0.}); - getMobility(ebosSimulator, perf, mob, deferred_logger); - - PerforationRates perf_rates; - double trans_mult = ebosSimulator.problem().template rockCompTransMultiplier(intQuants, cell_idx); - const double Tw = this->wellIndex(perf) * trans_mult; - const double elapsed_time = ebosSimulator.time(); - - computePerfRate(intQuants, mob, bhp, Tw, perf, allow_cf, elapsed_time, - cq_s, perf_rates, deferred_logger, ebosSimulator); - - auto& ws = well_state.well(this->index_of_well_); - auto& perf_data = ws.perf_data; - if constexpr (has_polymer && Base::has_polymermw) { - if (this->isInjector()) { - // Store the original water flux computed from the reservoir quantities. - // It will be required to assemble the injectivity equations. - const unsigned water_comp_idx = Indices::canonicalToActiveComponentIndex(FluidSystem::waterCompIdx); - water_flux_s = cq_s[water_comp_idx]; - // Modify the water flux for the rest of this function to depend directly on the - // local water velocity primary variable. - handleInjectivityRate(ebosSimulator, perf, cq_s); - } - } - - // updating the solution gas rate and solution oil rate - if (this->isProducer()) { - ws.phase_mixing_rates[ws.dissolved_gas] += perf_rates.dis_gas; - ws.phase_mixing_rates[ws.dissolved_gas_in_water] += perf_rates.dis_gas_in_water; - ws.phase_mixing_rates[ws.vaporized_oil] += perf_rates.vap_oil; - ws.phase_mixing_rates[ws.vaporized_water] += perf_rates.vap_wat; - } - - if constexpr (has_energy) { - connectionRates[perf][Indices::contiEnergyEqIdx] = - connectionRateEnergy(ebosSimulator.problem().maxOilSaturation(cell_idx), - cq_s, intQuants, deferred_logger); - } - - if constexpr (has_polymer) { - std::variant polymerConcentration; - if (this->isInjector()) { - polymerConcentration = this->wpolymer(); - } else { - polymerConcentration = this->extendEval(intQuants.polymerConcentration() * - intQuants.polymerViscosityCorrection()); - } - - [[maybe_unused]] EvalWell cq_s_poly; - std::tie(connectionRates[perf][Indices::contiPolymerEqIdx], - cq_s_poly) = - this->connections_.connectionRatePolymer(perf_data.polymer_rates[perf], - cq_s, polymerConcentration); - - if constexpr (Base::has_polymermw) { - updateConnectionRatePolyMW(cq_s_poly, intQuants, well_state, - perf, connectionRates, deferred_logger); - } - } - - if constexpr (has_foam) { - std::variant foamConcentration; - if (this->isInjector()) { - foamConcentration = this->wfoam(); - } else { - foamConcentration = this->extendEval(intQuants.foamConcentration()); - } - connectionRates[perf][Indices::contiFoamEqIdx] = - this->connections_.connectionRateFoam(cq_s, foamConcentration, - FoamModule::transportPhase(), - deferred_logger); - } - - if constexpr (has_zFraction) { - std::variant> solventConcentration; - if (this->isInjector()) { - solventConcentration = this->wsolvent(); - } else { - solventConcentration = std::array{this->extendEval(intQuants.xVolume()), - this->extendEval(intQuants.yVolume())}; - } - std::tie(connectionRates[perf][Indices::contiZfracEqIdx], - cq_s_zfrac_effective) = - this->connections_.connectionRatezFraction(perf_data.solvent_rates[perf], - perf_rates.dis_gas, cq_s, - solventConcentration); - } - - if constexpr (has_brine) { - std::variant saltConcentration; - if (this->isInjector()) { - saltConcentration = this->wsalt(); - } else { - saltConcentration = this->extendEval(intQuants.fluidState().saltConcentration()); - } - - connectionRates[perf][Indices::contiBrineEqIdx] = - this->connections_.connectionRateBrine(perf_data.brine_rates[perf], - perf_rates.vap_wat, cq_s, - saltConcentration); - } - - if constexpr (has_micp) { - std::variant microbialConcentration; - std::variant oxygenConcentration; - std::variant ureaConcentration; - if (this->isInjector()) { - microbialConcentration = this->wmicrobes(); - oxygenConcentration = this->woxygen(); - ureaConcentration = this->wurea(); - } else { - microbialConcentration = this->extendEval(intQuants.microbialConcentration()); - oxygenConcentration = this->extendEval(intQuants.oxygenConcentration()); - ureaConcentration = this->extendEval(intQuants.ureaConcentration()); - } - std::tie(connectionRates[perf][Indices::contiMicrobialEqIdx], - connectionRates[perf][Indices::contiOxygenEqIdx], - connectionRates[perf][Indices::contiUreaEqIdx]) = - this->connections_.connectionRatesMICP(cq_s, - microbialConcentration, - oxygenConcentration, - ureaConcentration); - } - - // Store the perforation pressure for later usage. - perf_data.pressure[perf] = ws.bhp + this->connections_.pressure_diff(perf); - } - - - - template - template - void - StandardWell:: - getMobility(const Simulator& ebosSimulator, - const int perf, - std::vector& mob, - DeferredLogger& deferred_logger) const - { - auto obtain = [this](const Eval& value) - { - if constexpr (std::is_same_v) { - static_cast(this); // suppress clang warning - return getValue(value); - } else { - return this->extendEval(value); - } - }; - WellInterface::getMobility(ebosSimulator, perf, mob, - obtain, deferred_logger); - - // modify the water mobility if polymer is present - if constexpr (has_polymer) { - if (!FluidSystem::phaseIsActive(FluidSystem::waterPhaseIdx)) { - OPM_DEFLOG_THROW(std::runtime_error, "Water is required when polymer is active", deferred_logger); - } - - // for the cases related to polymer molecular weight, we assume fully mixing - // as a result, the polymer and water share the same viscosity - if constexpr (!Base::has_polymermw) { - if constexpr (std::is_same_v) { - std::vector mob_eval(this->num_components_, {this->primary_variables_.numWellEq() + Indices::numEq, 0.}); - for (size_t i = 0; i < mob.size(); ++i) - mob_eval[i].setValue(mob[i]); - updateWaterMobilityWithPolymer(ebosSimulator, perf, mob_eval, deferred_logger); - for (size_t i = 0; i < mob.size(); ++i) { - mob[i] = getValue(mob_eval[i]); - } - } else { - updateWaterMobilityWithPolymer(ebosSimulator, perf, mob, deferred_logger); - } - } - } - - // if the injecting well has WINJMULT setup, we update the mobility accordingly - if (this->isInjector() && this->well_ecl_.getInjMultMode() != Well::InjMultMode::NONE) { - const double bhp = this->primary_variables_.value(Bhp); - const double perf_press = bhp + this->connections_.pressure_diff(perf); - const double multiplier = this->getInjMult(perf, bhp, perf_press); - for (size_t i = 0; i < mob.size(); ++i) { - mob[i] *= multiplier; - } - } - } - - - template - void - StandardWell:: - updateWellState(const SummaryState& summary_state, - const BVectorWell& dwells, - WellState& well_state, - DeferredLogger& deferred_logger) - { - if (!this->isOperableAndSolvable() && !this->wellIsStopped()) return; - - const bool stop_or_zero_rate_target = this->stopppedOrZeroRateTarget(summary_state, well_state); - updatePrimaryVariablesNewton(dwells, stop_or_zero_rate_target, deferred_logger); - - updateWellStateFromPrimaryVariables(stop_or_zero_rate_target, well_state, summary_state, deferred_logger); - Base::calculateReservoirRates(well_state.well(this->index_of_well_)); - } - - - - - - template - void - StandardWell:: - updatePrimaryVariablesNewton(const BVectorWell& dwells, - const bool stop_or_zero_rate_target, - DeferredLogger& deferred_logger) - { - const double dFLimit = this->param_.dwell_fraction_max_; - const double dBHPLimit = this->param_.dbhp_max_rel_; - this->primary_variables_.updateNewton(dwells, stop_or_zero_rate_target, dFLimit, dBHPLimit); - - // for the water velocity and skin pressure - if constexpr (Base::has_polymermw) { - this->primary_variables_.updateNewtonPolyMW(dwells); - } - - this->primary_variables_.checkFinite(deferred_logger); - } - - - - - - template - void - StandardWell:: - updateWellStateFromPrimaryVariables(const bool stop_or_zero_rate_target, - WellState& well_state, - const SummaryState& summary_state, - DeferredLogger& deferred_logger) const - { - this->StdWellEval::updateWellStateFromPrimaryVariables(stop_or_zero_rate_target, well_state, summary_state, deferred_logger); - - // other primary variables related to polymer injectivity study - if constexpr (Base::has_polymermw) { - this->primary_variables_.copyToWellStatePolyMW(well_state); - } - } - - - - - - template - void - StandardWell:: - updateIPR(const Simulator& ebos_simulator, DeferredLogger& deferred_logger) const - { - // TODO: not handling solvent related here for now - - // initialize all the values to be zero to begin with - std::fill(this->ipr_a_.begin(), this->ipr_a_.end(), 0.); - std::fill(this->ipr_b_.begin(), this->ipr_b_.end(), 0.); - - for (int perf = 0; perf < this->number_of_perforations_; ++perf) { - std::vector mob(this->num_components_, 0.0); - getMobility(ebos_simulator, perf, mob, deferred_logger); - - const int cell_idx = this->well_cells_[perf]; - const auto& int_quantities = ebos_simulator.model().intensiveQuantities(cell_idx, /*timeIdx=*/ 0); - const auto& fs = int_quantities.fluidState(); - // the pressure of the reservoir grid block the well connection is in - double p_r = this->getPerfCellPressure(fs).value(); - - // calculating the b for the connection - std::vector b_perf(this->num_components_); - for (size_t phase = 0; phase < FluidSystem::numPhases; ++phase) { - if (!FluidSystem::phaseIsActive(phase)) { - continue; - } - const unsigned comp_idx = Indices::canonicalToActiveComponentIndex(FluidSystem::solventComponentIndex(phase)); - b_perf[comp_idx] = fs.invB(phase).value(); - } - if constexpr (has_solvent) { - b_perf[Indices::contiSolventEqIdx] = int_quantities.solventInverseFormationVolumeFactor().value(); - } - - // the pressure difference between the connection and BHP - const double h_perf = this->connections_.pressure_diff(perf); - const double pressure_diff = p_r - h_perf; - - // Let us add a check, since the pressure is calculated based on zero value BHP - // it should not be negative anyway. If it is negative, we might need to re-formulate - // to taking into consideration the crossflow here. - if ( (this->isProducer() && pressure_diff < 0.) || (this->isInjector() && pressure_diff > 0.) ) { - deferred_logger.debug("CROSSFLOW_IPR", - "cross flow found when updateIPR for well " + name() - + " . The connection is ignored in IPR calculations"); - // we ignore these connections for now - continue; - } - - // the well index associated with the connection - const double tw_perf = this->wellIndex(perf)*ebos_simulator.problem().template rockCompTransMultiplier(int_quantities, cell_idx); - - std::vector ipr_a_perf(this->ipr_a_.size()); - std::vector ipr_b_perf(this->ipr_b_.size()); - for (int comp_idx = 0; comp_idx < this->num_components_; ++comp_idx) { - const double tw_mob = tw_perf * mob[comp_idx] * b_perf[comp_idx]; - ipr_a_perf[comp_idx] += tw_mob * pressure_diff; - ipr_b_perf[comp_idx] += tw_mob; - } - - // we need to handle the rs and rv when both oil and gas are present - if (FluidSystem::phaseIsActive(FluidSystem::oilPhaseIdx) && FluidSystem::phaseIsActive(FluidSystem::gasPhaseIdx)) { - const unsigned oil_comp_idx = Indices::canonicalToActiveComponentIndex(FluidSystem::oilCompIdx); - const unsigned gas_comp_idx = Indices::canonicalToActiveComponentIndex(FluidSystem::gasCompIdx); - const double rs = (fs.Rs()).value(); - const double rv = (fs.Rv()).value(); - - const double dis_gas_a = rs * ipr_a_perf[oil_comp_idx]; - const double vap_oil_a = rv * ipr_a_perf[gas_comp_idx]; - - ipr_a_perf[gas_comp_idx] += dis_gas_a; - ipr_a_perf[oil_comp_idx] += vap_oil_a; - - const double dis_gas_b = rs * ipr_b_perf[oil_comp_idx]; - const double vap_oil_b = rv * ipr_b_perf[gas_comp_idx]; - - ipr_b_perf[gas_comp_idx] += dis_gas_b; - ipr_b_perf[oil_comp_idx] += vap_oil_b; - } - - for (size_t comp_idx = 0; comp_idx < ipr_a_perf.size(); ++comp_idx) { - this->ipr_a_[comp_idx] += ipr_a_perf[comp_idx]; - this->ipr_b_[comp_idx] += ipr_b_perf[comp_idx]; - } - } - this->parallel_well_info_.communication().sum(this->ipr_a_.data(), this->ipr_a_.size()); - this->parallel_well_info_.communication().sum(this->ipr_b_.data(), this->ipr_b_.size()); - } - - - template - void - StandardWell:: - checkOperabilityUnderBHPLimit(const WellState& well_state, const Simulator& ebos_simulator, DeferredLogger& deferred_logger) - { - const auto& summaryState = ebos_simulator.vanguard().summaryState(); - const double bhp_limit = WellBhpThpCalculator(*this).mostStrictBhpFromBhpLimits(summaryState); - // Crude but works: default is one atmosphere. - // TODO: a better way to detect whether the BHP is defaulted or not - const bool bhp_limit_not_defaulted = bhp_limit > 1.5 * unit::barsa; - if ( bhp_limit_not_defaulted || !this->wellHasTHPConstraints(summaryState) ) { - // if the BHP limit is not defaulted or the well does not have a THP limit - // we need to check the BHP limit - double total_ipr_mass_rate = 0.0; - for (unsigned phaseIdx = 0; phaseIdx < FluidSystem::numPhases; ++phaseIdx) - { - if (!FluidSystem::phaseIsActive(phaseIdx)) { - continue; - } - - const unsigned compIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::solventComponentIndex(phaseIdx)); - const double ipr_rate = this->ipr_a_[compIdx] - this->ipr_b_[compIdx] * bhp_limit; - - const double rho = FluidSystem::referenceDensity( phaseIdx, Base::pvtRegionIdx() ); - total_ipr_mass_rate += ipr_rate * rho; - } - if ( (this->isProducer() && total_ipr_mass_rate < 0.) || (this->isInjector() && total_ipr_mass_rate > 0.) ) { - this->operability_status_.operable_under_only_bhp_limit = false; - } - - // checking whether running under BHP limit will violate THP limit - if (this->operability_status_.operable_under_only_bhp_limit && this->wellHasTHPConstraints(summaryState)) { - // option 1: calculate well rates based on the BHP limit. - // option 2: stick with the above IPR curve - // we use IPR here - std::vector well_rates_bhp_limit; - computeWellRatesWithBhp(ebos_simulator, bhp_limit, well_rates_bhp_limit, deferred_logger); - - this->adaptRatesForVFP(well_rates_bhp_limit); - const double thp_limit = this->getTHPConstraint(summaryState); - const double thp = WellBhpThpCalculator(*this).calculateThpFromBhp(well_rates_bhp_limit, - bhp_limit, - this->connections_.rho(), - this->getALQ(well_state), - thp_limit, - deferred_logger); - if ( (this->isProducer() && thp < thp_limit) || (this->isInjector() && thp > thp_limit) ) { - this->operability_status_.obey_thp_limit_under_bhp_limit = false; - } - } - } else { - // defaulted BHP and there is a THP constraint - // default BHP limit is about 1 atm. - // when applied the hydrostatic pressure correction dp, - // most likely we get a negative value (bhp + dp)to search in the VFP table, - // which is not desirable. - // we assume we can operate under defaulted BHP limit and will violate the THP limit - // when operating under defaulted BHP limit. - this->operability_status_.operable_under_only_bhp_limit = true; - this->operability_status_.obey_thp_limit_under_bhp_limit = false; - } - } - - - - - - template - void - StandardWell:: - checkOperabilityUnderTHPLimit(const Simulator& ebos_simulator, const WellState& well_state, DeferredLogger& deferred_logger) - { - const auto& summaryState = ebos_simulator.vanguard().summaryState(); - const auto obtain_bhp = this->isProducer() ? computeBhpAtThpLimitProd(well_state, ebos_simulator, summaryState, deferred_logger) - : computeBhpAtThpLimitInj(ebos_simulator, summaryState, deferred_logger); - - if (obtain_bhp) { - this->operability_status_.can_obtain_bhp_with_thp_limit = true; - - const double bhp_limit = WellBhpThpCalculator(*this).mostStrictBhpFromBhpLimits(summaryState); - this->operability_status_.obey_bhp_limit_with_thp_limit = this->isProducer() ? - *obtain_bhp >= bhp_limit : *obtain_bhp <= bhp_limit ; - - const double thp_limit = this->getTHPConstraint(summaryState); - if (this->isProducer() && *obtain_bhp < thp_limit) { - const std::string msg = " obtained bhp " + std::to_string(unit::convert::to(*obtain_bhp, unit::barsa)) - + " bars is SMALLER than thp limit " - + std::to_string(unit::convert::to(thp_limit, unit::barsa)) - + " bars as a producer for well " + name(); - deferred_logger.debug(msg); - } - else if (this->isInjector() && *obtain_bhp > thp_limit) { - const std::string msg = " obtained bhp " + std::to_string(unit::convert::to(*obtain_bhp, unit::barsa)) - + " bars is LARGER than thp limit " - + std::to_string(unit::convert::to(thp_limit, unit::barsa)) - + " bars as a injector for well " + name(); - deferred_logger.debug(msg); - } - } else { - this->operability_status_.can_obtain_bhp_with_thp_limit = false; - this->operability_status_.obey_bhp_limit_with_thp_limit = false; - if (!this->wellIsStopped()) { - const double thp_limit = this->getTHPConstraint(summaryState); - deferred_logger.debug(" could not find bhp value at thp limit " - + std::to_string(unit::convert::to(thp_limit, unit::barsa)) - + " bar for well " + name() + ", the well might need to be closed "); - } - } - } - - - - - - template - bool - StandardWell:: - allDrawDownWrongDirection(const Simulator& ebos_simulator) const - { - bool all_drawdown_wrong_direction = true; - - for (int perf = 0; perf < this->number_of_perforations_; ++perf) { - const int cell_idx = this->well_cells_[perf]; - const auto& intQuants = ebos_simulator.model().intensiveQuantities(cell_idx, /*timeIdx=*/0); - const auto& fs = intQuants.fluidState(); - - const double pressure = this->getPerfCellPressure(fs).value(); - const double bhp = this->primary_variables_.eval(Bhp).value(); - - // Pressure drawdown (also used to determine direction of flow) - const double well_pressure = bhp + this->connections_.pressure_diff(perf); - const double drawdown = pressure - well_pressure; - - // for now, if there is one perforation can produce/inject in the correct - // direction, we consider this well can still produce/inject. - // TODO: it can be more complicated than this to cause wrong-signed rates - if ( (drawdown < 0. && this->isInjector()) || - (drawdown > 0. && this->isProducer()) ) { - all_drawdown_wrong_direction = false; - break; - } - } - - const auto& comm = this->parallel_well_info_.communication(); - if (comm.size() > 1) - { - all_drawdown_wrong_direction = - (comm.min(all_drawdown_wrong_direction ? 1 : 0) == 1); - } - - return all_drawdown_wrong_direction; - } - - - - - template - bool - StandardWell:: - canProduceInjectWithCurrentBhp(const Simulator& ebos_simulator, - const WellState& well_state, - DeferredLogger& deferred_logger) - { - const double bhp = well_state.well(this->index_of_well_).bhp; - std::vector well_rates; - computeWellRatesWithBhp(ebos_simulator, bhp, well_rates, deferred_logger); - - const double sign = (this->isProducer()) ? -1. : 1.; - const double threshold = sign * std::numeric_limits::min(); - - bool can_produce_inject = false; - for (const auto value : well_rates) { - if (this->isProducer() && value < threshold) { - can_produce_inject = true; - break; - } else if (this->isInjector() && value > threshold) { - can_produce_inject = true; - break; - } - } - - if (!can_produce_inject) { - deferred_logger.debug(" well " + name() + " CANNOT produce or inejct "); - } - - return can_produce_inject; - } - - - - - - template - bool - StandardWell:: - openCrossFlowAvoidSingularity(const Simulator& ebos_simulator) const - { - return !this->getAllowCrossFlow() && allDrawDownWrongDirection(ebos_simulator); - } - - - - - template - void - StandardWell:: - computePropertiesForWellConnectionPressures(const Simulator& ebosSimulator, - const WellState& well_state, - WellConnectionProps& props) const - { - std::function getTemperature = - [&ebosSimulator](int cell_idx, int phase_idx) - { - return ebosSimulator.model().intensiveQuantities(cell_idx, 0).fluidState().temperature(phase_idx).value(); - }; - std::function getSaltConcentration = - [&ebosSimulator](int cell_idx) - { - return ebosSimulator.model().intensiveQuantities(cell_idx, 0).fluidState().saltConcentration().value(); - }; - std::function getPvtRegionIdx = - [&ebosSimulator](int cell_idx) - { - return ebosSimulator.model().intensiveQuantities(cell_idx, 0).fluidState().pvtRegionIndex(); - }; - std::function getInvFac = - [&ebosSimulator](int cell_idx) - { - return ebosSimulator.model().intensiveQuantities(cell_idx, 0).solventInverseFormationVolumeFactor().value(); - }; - std::function getSolventDensity = - [&ebosSimulator](int cell_idx) - { - return ebosSimulator.model().intensiveQuantities(cell_idx, 0).solventRefDensity(); - }; - - this->connections_.computePropertiesForPressures(well_state, - getTemperature, - getSaltConcentration, - getPvtRegionIdx, - getInvFac, - getSolventDensity, - props); - } - - - - - - template - ConvergenceReport - StandardWell:: - getWellConvergence(const SummaryState& summary_state, - const WellState& well_state, - const std::vector& B_avg, - DeferredLogger& deferred_logger, - const bool relax_tolerance) const - { - // the following implementation assume that the polymer is always after the w-o-g phases - // For the polymer, energy and foam cases, there is one more mass balance equations of reservoir than wells - assert((int(B_avg.size()) == this->num_components_) || has_polymer || has_energy || has_foam || has_brine || has_zFraction || has_micp); - - // using sricter tolerance for stopped wells and wells under zero rate target control. - constexpr double stricter_factor = 1.e-4; - const double tol_wells = this->stopppedOrZeroRateTarget(summary_state, well_state) ? - this->param_.tolerance_wells_ * stricter_factor : this->param_.tolerance_wells_; - - std::vector res; - ConvergenceReport report = this->StdWellEval::getWellConvergence(well_state, - B_avg, - this->param_.max_residual_allowed_, - tol_wells, - this->param_.relaxed_tolerance_flow_well_, - relax_tolerance, - res, - deferred_logger); - - checkConvergenceExtraEqs(res, report); - - return report; - } - - - - - - template - void - StandardWell:: - updateProductivityIndex(const Simulator& ebosSimulator, - const WellProdIndexCalculator& wellPICalc, - WellState& well_state, - DeferredLogger& deferred_logger) const - { - auto fluidState = [&ebosSimulator, this](const int perf) - { - const auto cell_idx = this->well_cells_[perf]; - return ebosSimulator.model() - .intensiveQuantities(cell_idx, /*timeIdx=*/ 0).fluidState(); - }; - - const int np = this->number_of_phases_; - auto setToZero = [np](double* x) -> void - { - std::fill_n(x, np, 0.0); - }; - - auto addVector = [np](const double* src, double* dest) -> void - { - std::transform(src, src + np, dest, dest, std::plus<>{}); - }; - - auto& ws = well_state.well(this->index_of_well_); - auto& perf_data = ws.perf_data; - auto* wellPI = ws.productivity_index.data(); - auto* connPI = perf_data.prod_index.data(); - - setToZero(wellPI); - - const auto preferred_phase = this->well_ecl_.getPreferredPhase(); - auto subsetPerfID = 0; - - for (const auto& perf : *this->perf_data_) { - auto allPerfID = perf.ecl_index; - - auto connPICalc = [&wellPICalc, allPerfID](const double mobility) -> double - { - return wellPICalc.connectionProdIndStandard(allPerfID, mobility); - }; - - std::vector mob(this->num_components_, 0.0); - getMobility(ebosSimulator, static_cast(subsetPerfID), mob, deferred_logger); - - const auto& fs = fluidState(subsetPerfID); - setToZero(connPI); - - if (this->isInjector()) { - this->computeConnLevelInjInd(fs, preferred_phase, connPICalc, - mob, connPI, deferred_logger); - } - else { // Production or zero flow rate - this->computeConnLevelProdInd(fs, connPICalc, mob, connPI); - } - - addVector(connPI, wellPI); - - ++subsetPerfID; - connPI += np; - } - - // Sum with communication in case of distributed well. - const auto& comm = this->parallel_well_info_.communication(); - if (comm.size() > 1) { - comm.sum(wellPI, np); - } - - assert ((static_cast(subsetPerfID) == this->number_of_perforations_) && - "Internal logic error in processing connections for PI/II"); - } - - - - template - void - StandardWell:: - computeWellConnectionDensitesPressures(const Simulator& ebosSimulator, - const WellState& well_state, - const WellConnectionProps& props, - DeferredLogger& deferred_logger) - { - std::function invB = - [&ebosSimulator](int cell_idx, int phase_idx) - { - return ebosSimulator.model().intensiveQuantities(cell_idx, 0).fluidState().invB(phase_idx).value(); - }; - std::function mobility = - [&ebosSimulator](int cell_idx, int phase_idx) - { - return ebosSimulator.model().intensiveQuantities(cell_idx, 0).mobility(phase_idx).value(); - }; - std::function invFac = - [&ebosSimulator](int cell_idx) - { - return ebosSimulator.model().intensiveQuantities(cell_idx, 0).solventInverseFormationVolumeFactor().value(); - }; - std::function solventMobility = - [&ebosSimulator](int cell_idx) - { - return ebosSimulator.model().intensiveQuantities(cell_idx, 0).solventMobility().value(); - }; - - this->connections_.computeProperties(well_state, - invB, - mobility, - invFac, - solventMobility, - props, - deferred_logger); - } - - - - - - template - void - StandardWell:: - computeWellConnectionPressures(const Simulator& ebosSimulator, - const WellState& well_state, - DeferredLogger& deferred_logger) - { - // 1. Compute properties required by computePressureDelta(). - // Note that some of the complexity of this part is due to the function - // taking std::vector arguments, and not Eigen objects. - WellConnectionProps props; - computePropertiesForWellConnectionPressures(ebosSimulator, well_state, props); - computeWellConnectionDensitesPressures(ebosSimulator, well_state, - props, deferred_logger); - } - - - - - - template - void - StandardWell:: - solveEqAndUpdateWellState(const SummaryState& summary_state, - WellState& well_state, - DeferredLogger& deferred_logger) - { - if (!this->isOperableAndSolvable() && !this->wellIsStopped()) return; - - // We assemble the well equations, then we check the convergence, - // which is why we do not put the assembleWellEq here. - BVectorWell dx_well(1); - dx_well[0].resize(this->primary_variables_.numWellEq()); - this->linSys_.solve( dx_well); - - updateWellState(summary_state, dx_well, well_state, deferred_logger); - } - - - - - - template - void - StandardWell:: - calculateExplicitQuantities(const Simulator& ebosSimulator, - const WellState& well_state, - DeferredLogger& deferred_logger) - { - const auto& summary_state = ebosSimulator.vanguard().summaryState(); - updatePrimaryVariables(summary_state, well_state, deferred_logger); - initPrimaryVariablesEvaluation(); - computeWellConnectionPressures(ebosSimulator, well_state, deferred_logger); - this->computeAccumWell(); - } - - - - template - void - StandardWell:: - apply(const BVector& x, BVector& Ax) const - { - if (!this->isOperableAndSolvable() && !this->wellIsStopped()) return; - - if (this->param_.matrix_add_well_contributions_) - { - // Contributions are already in the matrix itself - return; - } - - this->linSys_.apply(x, Ax); - } - - - - - template - void - StandardWell:: - apply(BVector& r) const - { - if (!this->isOperableAndSolvable() && !this->wellIsStopped()) return; - - this->linSys_.apply(r); - } - - - - - template - void - StandardWell:: - recoverWellSolutionAndUpdateWellState(const SummaryState& summary_state, - const BVector& x, - WellState& well_state, - DeferredLogger& deferred_logger) - { - if (!this->isOperableAndSolvable() && !this->wellIsStopped()) return; - - BVectorWell xw(1); - xw[0].resize(this->primary_variables_.numWellEq()); - - this->linSys_.recoverSolutionWell(x, xw); - updateWellState(summary_state, xw, well_state, deferred_logger); - } - - - - - template - void - StandardWell:: - computeWellRatesWithBhp(const Simulator& ebosSimulator, - const double& bhp, - std::vector& well_flux, - DeferredLogger& deferred_logger) const - { - - const int np = this->number_of_phases_; - well_flux.resize(np, 0.0); - - const bool allow_cf = this->getAllowCrossFlow(); - - for (int perf = 0; perf < this->number_of_perforations_; ++perf) { - const int cell_idx = this->well_cells_[perf]; - const auto& intQuants = ebosSimulator.model().intensiveQuantities(cell_idx, /*timeIdx=*/ 0); - // flux for each perforation - std::vector mob(this->num_components_, 0.); - getMobility(ebosSimulator, perf, mob, deferred_logger); - double trans_mult = ebosSimulator.problem().template rockCompTransMultiplier(intQuants, cell_idx); - const double Tw = this->wellIndex(perf) * trans_mult; - const double elapsed_time = ebosSimulator.time(); - - std::vector cq_s(this->num_components_, 0.); - PerforationRates perf_rates; - computePerfRate(intQuants, mob, bhp, Tw, perf, allow_cf, elapsed_time, - cq_s, perf_rates, deferred_logger, ebosSimulator); - - for(int p = 0; p < np; ++p) { - well_flux[this->ebosCompIdxToFlowCompIdx(p)] += cq_s[p]; - } - } - this->parallel_well_info_.communication().sum(well_flux.data(), well_flux.size()); - } - - - - template - void - StandardWell:: - computeWellRatesWithBhpIterations(const Simulator& ebosSimulator, - const double& bhp, - std::vector& well_flux, - DeferredLogger& deferred_logger) const - { - // creating a copy of the well itself, to avoid messing up the explicit information - // during this copy, the only information not copied properly is the well controls - StandardWell well_copy(*this); - - // iterate to get a more accurate well density - // create a copy of the well_state to use. If the operability checking is sucessful, we use this one - // to replace the original one - WellState well_state_copy = ebosSimulator.problem().wellModel().wellState(); - const auto& group_state = ebosSimulator.problem().wellModel().groupState(); - - // Get the current controls. - const auto& summary_state = ebosSimulator.vanguard().summaryState(); - auto inj_controls = well_copy.well_ecl_.isInjector() - ? well_copy.well_ecl_.injectionControls(summary_state) - : Well::InjectionControls(0); - auto prod_controls = well_copy.well_ecl_.isProducer() - ? well_copy.well_ecl_.productionControls(summary_state) : - Well::ProductionControls(0); - - // Set current control to bhp, and bhp value in state, modify bhp limit in control object. - auto& ws = well_state_copy.well(this->index_of_well_); - if (well_copy.well_ecl_.isInjector()) { - inj_controls.bhp_limit = bhp; - ws.injection_cmode = Well::InjectorCMode::BHP; - } else { - prod_controls.bhp_limit = bhp; - ws.production_cmode = Well::ProducerCMode::BHP; - } - ws.bhp = bhp; - - // initialized the well rates with the potentials i.e. the well rates based on bhp - const int np = this->number_of_phases_; - const double sign = this->well_ecl_.isInjector() ? 1.0 : -1.0; - for (int phase = 0; phase < np; ++phase){ - well_state_copy.wellRates(this->index_of_well_)[phase] - = sign * ws.well_potentials[phase]; - } - well_copy.calculateExplicitQuantities(ebosSimulator, well_state_copy, deferred_logger); - - const double dt = ebosSimulator.timeStepSize(); - const bool converged = well_copy.iterateWellEqWithControl(ebosSimulator, dt, inj_controls, prod_controls, well_state_copy, group_state, deferred_logger); - if (!converged) { - const std::string msg = " well " + name() + " did not get converged during well potential calculations " - " potentials are computed based on unconverged solution"; - deferred_logger.debug(msg); - } - well_copy.updatePrimaryVariables(summary_state, well_state_copy, deferred_logger); - well_copy.computeWellConnectionPressures(ebosSimulator, well_state_copy, deferred_logger); - well_copy.initPrimaryVariablesEvaluation(); - well_copy.computeWellRatesWithBhp(ebosSimulator, bhp, well_flux, deferred_logger); - } - - - - - template - std::vector - StandardWell:: - computeWellPotentialWithTHP(const Simulator& ebos_simulator, - DeferredLogger& deferred_logger, - const WellState &well_state) const - { - std::vector potentials(this->number_of_phases_, 0.0); - const auto& summary_state = ebos_simulator.vanguard().summaryState(); - - const auto& well = this->well_ecl_; - if (well.isInjector()){ - const auto& controls = this->well_ecl_.injectionControls(summary_state); - auto bhp_at_thp_limit = computeBhpAtThpLimitInj(ebos_simulator, summary_state, deferred_logger); - if (bhp_at_thp_limit) { - const double bhp = std::min(*bhp_at_thp_limit, controls.bhp_limit); - computeWellRatesWithBhp(ebos_simulator, bhp, potentials, deferred_logger); - } else { - deferred_logger.warning("FAILURE_GETTING_CONVERGED_POTENTIAL", - "Failed in getting converged thp based potential calculation for well " - + name() + ". Instead the bhp based value is used"); - const double bhp = controls.bhp_limit; - computeWellRatesWithBhp(ebos_simulator, bhp, potentials, deferred_logger); - } - } else { - computeWellRatesWithThpAlqProd( - ebos_simulator, summary_state, - deferred_logger, potentials, this->getALQ(well_state) - ); - } - - return potentials; - } - - - - template - double - StandardWell:: - computeWellRatesAndBhpWithThpAlqProd(const Simulator &ebos_simulator, - const SummaryState &summary_state, - DeferredLogger &deferred_logger, - std::vector &potentials, - double alq) const - { - double bhp; - auto bhp_at_thp_limit = computeBhpAtThpLimitProdWithAlq( - ebos_simulator, summary_state, alq, deferred_logger); - if (bhp_at_thp_limit) { - const auto& controls = this->well_ecl_.productionControls(summary_state); - bhp = std::max(*bhp_at_thp_limit, controls.bhp_limit); - computeWellRatesWithBhp(ebos_simulator, bhp, potentials, deferred_logger); - } - else { - deferred_logger.warning("FAILURE_GETTING_CONVERGED_POTENTIAL", - "Failed in getting converged thp based potential calculation for well " - + name() + ". Instead the bhp based value is used"); - const auto& controls = this->well_ecl_.productionControls(summary_state); - bhp = controls.bhp_limit; - computeWellRatesWithBhp(ebos_simulator, bhp, potentials, deferred_logger); - } - return bhp; - } - - template - void - StandardWell:: - computeWellRatesWithThpAlqProd(const Simulator &ebos_simulator, - const SummaryState &summary_state, - DeferredLogger &deferred_logger, - std::vector &potentials, - double alq) const - { - /*double bhp =*/ - computeWellRatesAndBhpWithThpAlqProd(ebos_simulator, - summary_state, - deferred_logger, - potentials, - alq); - } - - template - void - StandardWell:: - computeWellPotentials(const Simulator& ebosSimulator, - const WellState& well_state, - std::vector& well_potentials, - DeferredLogger& deferred_logger) // const - { - const auto [compute_potential, bhp_controlled_well] = - this->WellInterfaceGeneric::computeWellPotentials(well_potentials, well_state); - - if (!compute_potential) { - return; - } - - // does the well have a THP related constraint? - const auto& summaryState = ebosSimulator.vanguard().summaryState(); - if (!Base::wellHasTHPConstraints(summaryState) || bhp_controlled_well) { - // get the bhp value based on the bhp constraints - double bhp = WellBhpThpCalculator(*this).mostStrictBhpFromBhpLimits(summaryState); - - // In some very special cases the bhp pressure target are - // temporary violated. This may lead to too small or negative potentials - // that could lead to premature shutting of wells. - // As a remedy the bhp that gives the largest potential is used. - // For converged cases, ws.bhp <=bhp for injectors and ws.bhp >= bhp, - // and the potentials will be computed using the limit as expected. - const auto& ws = well_state.well(this->index_of_well_); - if (this->isInjector()) - bhp = std::max(ws.bhp, bhp); - else - bhp = std::min(ws.bhp, bhp); - - assert(std::abs(bhp) != std::numeric_limits::max()); - computeWellRatesWithBhpIterations(ebosSimulator, bhp, well_potentials, deferred_logger); - } else { - // the well has a THP related constraint - well_potentials = computeWellPotentialWithTHP(ebosSimulator, deferred_logger, well_state); - } - - this->checkNegativeWellPotentials(well_potentials, - this->param_.check_well_operability_, - deferred_logger); - } - - - - - - - - template - double - StandardWell:: - connectionDensity([[maybe_unused]] const int globalConnIdx, - const int openConnIdx) const - { - return (openConnIdx < 0) - ? 0.0 - : this->connections_.rho(openConnIdx); - } - - - - - - template - void - StandardWell:: - updatePrimaryVariables(const SummaryState& summary_state, - const WellState& well_state, - DeferredLogger& deferred_logger) - { - if (!this->isOperableAndSolvable() && !this->wellIsStopped()) return; - - const bool stop_or_zero_rate_target = this->stopppedOrZeroRateTarget(summary_state, well_state); - this->primary_variables_.update(well_state, stop_or_zero_rate_target, deferred_logger); - - // other primary variables related to polymer injection - if constexpr (Base::has_polymermw) { - this->primary_variables_.updatePolyMW(well_state); - } - - this->primary_variables_.checkFinite(deferred_logger); - } - - - - - template - double - StandardWell:: - getRefDensity() const - { - return this->connections_.rho(); - } - - - - - template - void - StandardWell:: - updateWaterMobilityWithPolymer(const Simulator& ebos_simulator, - const int perf, - std::vector& mob, - DeferredLogger& deferred_logger) const - { - const int cell_idx = this->well_cells_[perf]; - const auto& int_quant = ebos_simulator.model().intensiveQuantities(cell_idx, /*timeIdx=*/ 0); - const EvalWell polymer_concentration = this->extendEval(int_quant.polymerConcentration()); - - // TODO: not sure should based on the well type or injecting/producing peforations - // it can be different for crossflow - if (this->isInjector()) { - // assume fully mixing within injecting wellbore - const auto& visc_mult_table = PolymerModule::plyviscViscosityMultiplierTable(int_quant.pvtRegionIndex()); - const unsigned waterCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::waterCompIdx); - mob[waterCompIdx] /= (this->extendEval(int_quant.waterViscosityCorrection()) * visc_mult_table.eval(polymer_concentration, /*extrapolate=*/true) ); - } - - if (PolymerModule::hasPlyshlog()) { - // we do not calculate the shear effects for injection wells when they do not - // inject polymer. - if (this->isInjector() && this->wpolymer() == 0.) { - return; - } - // compute the well water velocity with out shear effects. - // TODO: do we need to turn on crossflow here? - const bool allow_cf = this->getAllowCrossFlow() || openCrossFlowAvoidSingularity(ebos_simulator); - const EvalWell& bhp = this->primary_variables_.eval(Bhp); - - std::vector cq_s(this->num_components_, {this->primary_variables_.numWellEq() + Indices::numEq, 0.}); - PerforationRates perf_rates; - double trans_mult = ebos_simulator.problem().template rockCompTransMultiplier(int_quant, cell_idx); - const double elapsed_time = ebos_simulator.time(); - const double Tw = this->wellIndex(perf) * trans_mult; - computePerfRate(int_quant, mob, bhp, Tw, perf, allow_cf, elapsed_time, cq_s, - perf_rates, deferred_logger, ebos_simulator); - // TODO: make area a member - const double area = 2 * M_PI * this->perf_rep_radius_[perf] * this->perf_length_[perf]; - const auto& material_law_manager = ebos_simulator.problem().materialLawManager(); - const auto& scaled_drainage_info = - material_law_manager->oilWaterScaledEpsInfoDrainage(cell_idx); - const double swcr = scaled_drainage_info.Swcr; - const EvalWell poro = this->extendEval(int_quant.porosity()); - const EvalWell sw = this->extendEval(int_quant.fluidState().saturation(FluidSystem::waterPhaseIdx)); - // guard against zero porosity and no water - const EvalWell denom = max( (area * poro * (sw - swcr)), 1e-12); - const unsigned waterCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::waterCompIdx); - EvalWell water_velocity = cq_s[waterCompIdx] / denom * this->extendEval(int_quant.fluidState().invB(FluidSystem::waterPhaseIdx)); - - if (PolymerModule::hasShrate()) { - // the equation for the water velocity conversion for the wells and reservoir are from different version - // of implementation. It can be changed to be more consistent when possible. - water_velocity *= PolymerModule::shrate( int_quant.pvtRegionIndex() ) / this->bore_diameters_[perf]; - } - const EvalWell shear_factor = PolymerModule::computeShearFactor(polymer_concentration, - int_quant.pvtRegionIndex(), - water_velocity); - // modify the mobility with the shear factor. - mob[waterCompIdx] /= shear_factor; - } - } - - template - void - StandardWell::addWellContributions(SparseMatrixAdapter& jacobian) const - { - this->linSys_.extract(jacobian); - } - - - template - void - StandardWell::addWellPressureEquations(PressureMatrix& jacobian, - const BVector& weights, - const int pressureVarIndex, - const bool use_well_weights, - const WellState& well_state) const - { - this->linSys_.extractCPRPressureMatrix(jacobian, - weights, - pressureVarIndex, - use_well_weights, - *this, - Bhp, - well_state); - } - - - - template - typename StandardWell::EvalWell - StandardWell:: - pskinwater(const double throughput, - const EvalWell& water_velocity, - DeferredLogger& deferred_logger) const - { - if constexpr (Base::has_polymermw) { - const int water_table_id = this->polymerWaterTable_(); - if (water_table_id <= 0) { - OPM_DEFLOG_THROW(std::runtime_error, - fmt::format("Unused SKPRWAT table id used for well {}", name()), - deferred_logger); - } - const auto& water_table_func = PolymerModule::getSkprwatTable(water_table_id); - const EvalWell throughput_eval(this->primary_variables_.numWellEq() + Indices::numEq, throughput); - // the skin pressure when injecting water, which also means the polymer concentration is zero - EvalWell pskin_water(this->primary_variables_.numWellEq() + Indices::numEq, 0.0); - pskin_water = water_table_func.eval(throughput_eval, water_velocity); - return pskin_water; - } else { - OPM_DEFLOG_THROW(std::runtime_error, - fmt::format("Polymermw is not activated, while injecting " - "skin pressure is requested for well {}", name()), - deferred_logger); - } - } - - - - - - template - typename StandardWell::EvalWell - StandardWell:: - pskin(const double throughput, - const EvalWell& water_velocity, - const EvalWell& poly_inj_conc, - DeferredLogger& deferred_logger) const - { - if constexpr (Base::has_polymermw) { - const double sign = water_velocity >= 0. ? 1.0 : -1.0; - const EvalWell water_velocity_abs = abs(water_velocity); - if (poly_inj_conc == 0.) { - return sign * pskinwater(throughput, water_velocity_abs, deferred_logger); - } - const int polymer_table_id = this->polymerTable_(); - if (polymer_table_id <= 0) { - OPM_DEFLOG_THROW(std::runtime_error, - fmt::format("Unavailable SKPRPOLY table id used for well {}", name()), - deferred_logger); - } - const auto& skprpolytable = PolymerModule::getSkprpolyTable(polymer_table_id); - const double reference_concentration = skprpolytable.refConcentration; - const EvalWell throughput_eval(this->primary_variables_.numWellEq() + Indices::numEq, throughput); - // the skin pressure when injecting water, which also means the polymer concentration is zero - EvalWell pskin_poly(this->primary_variables_.numWellEq() + Indices::numEq, 0.0); - pskin_poly = skprpolytable.table_func.eval(throughput_eval, water_velocity_abs); - if (poly_inj_conc == reference_concentration) { - return sign * pskin_poly; - } - // poly_inj_conc != reference concentration of the table, then some interpolation will be required - const EvalWell pskin_water = pskinwater(throughput, water_velocity_abs, deferred_logger); - const EvalWell pskin = pskin_water + (pskin_poly - pskin_water) / reference_concentration * poly_inj_conc; - return sign * pskin; - } else { - OPM_DEFLOG_THROW(std::runtime_error, - fmt::format("Polymermw is not activated, while injecting " - "skin pressure is requested for well {}", name()), - deferred_logger); - } - } - - - - - - template - typename StandardWell::EvalWell - StandardWell:: - wpolymermw(const double throughput, - const EvalWell& water_velocity, - DeferredLogger& deferred_logger) const - { - if constexpr (Base::has_polymermw) { - const int table_id = this->polymerInjTable_(); - const auto& table_func = PolymerModule::getPlymwinjTable(table_id); - const EvalWell throughput_eval(this->primary_variables_.numWellEq() + Indices::numEq, throughput); - EvalWell molecular_weight(this->primary_variables_.numWellEq() + Indices::numEq, 0.); - if (this->wpolymer() == 0.) { // not injecting polymer - return molecular_weight; - } - molecular_weight = table_func.eval(throughput_eval, abs(water_velocity)); - return molecular_weight; - } else { - OPM_DEFLOG_THROW(std::runtime_error, - fmt::format("Polymermw is not activated, while injecting " - "polymer molecular weight is requested for well {}", name()), - deferred_logger); - } - } - - - - - - template - void - StandardWell:: - updateWaterThroughput(const double dt, WellState &well_state) const - { - if constexpr (Base::has_polymermw) { - if (this->isInjector()) { - auto& ws = well_state.well(this->index_of_well_); - auto& perf_water_throughput = ws.perf_data.water_throughput; - for (int perf = 0; perf < this->number_of_perforations_; ++perf) { - const double perf_water_vel = this->primary_variables_.value(Bhp + 1 + perf); - // we do not consider the formation damage due to water flowing from reservoir into wellbore - if (perf_water_vel > 0.) { - perf_water_throughput[perf] += perf_water_vel * dt; - } - } - } - } - } - - - - - - template - void - StandardWell:: - handleInjectivityRate(const Simulator& ebosSimulator, - const int perf, - std::vector& cq_s) const - { - const int cell_idx = this->well_cells_[perf]; - const auto& int_quants = ebosSimulator.model().intensiveQuantities(cell_idx, /*timeIdx=*/ 0); - const auto& fs = int_quants.fluidState(); - const EvalWell b_w = this->extendEval(fs.invB(FluidSystem::waterPhaseIdx)); - const double area = M_PI * this->bore_diameters_[perf] * this->perf_length_[perf]; - const int wat_vel_index = Bhp + 1 + perf; - const unsigned water_comp_idx = Indices::canonicalToActiveComponentIndex(FluidSystem::waterCompIdx); - - // water rate is update to use the form from water velocity, since water velocity is - // a primary variable now - cq_s[water_comp_idx] = area * this->primary_variables_.eval(wat_vel_index) * b_w; - } - - - - - template - void - StandardWell:: - handleInjectivityEquations(const Simulator& ebosSimulator, - const WellState& well_state, - const int perf, - const EvalWell& water_flux_s, - DeferredLogger& deferred_logger) - { - const int cell_idx = this->well_cells_[perf]; - const auto& int_quants = ebosSimulator.model().intensiveQuantities(cell_idx, /*timeIdx=*/ 0); - const auto& fs = int_quants.fluidState(); - const EvalWell b_w = this->extendEval(fs.invB(FluidSystem::waterPhaseIdx)); - const EvalWell water_flux_r = water_flux_s / b_w; - const double area = M_PI * this->bore_diameters_[perf] * this->perf_length_[perf]; - const EvalWell water_velocity = water_flux_r / area; - const int wat_vel_index = Bhp + 1 + perf; - - // equation for the water velocity - const EvalWell eq_wat_vel = this->primary_variables_.eval(wat_vel_index) - water_velocity; - - const auto& ws = well_state.well(this->index_of_well_); - const auto& perf_data = ws.perf_data; - const auto& perf_water_throughput = perf_data.water_throughput; - const double throughput = perf_water_throughput[perf]; - const int pskin_index = Bhp + 1 + this->number_of_perforations_ + perf; - - EvalWell poly_conc(this->primary_variables_.numWellEq() + Indices::numEq, 0.0); - poly_conc.setValue(this->wpolymer()); - - // equation for the skin pressure - const EvalWell eq_pskin = this->primary_variables_.eval(pskin_index) - - pskin(throughput, this->primary_variables_.eval(wat_vel_index), poly_conc, deferred_logger); - - StandardWellAssemble(*this). - assembleInjectivityEq(eq_pskin, - eq_wat_vel, - pskin_index, - wat_vel_index, - cell_idx, - this->primary_variables_.numWellEq(), - this->linSys_); - } - - - - - - template - void - StandardWell:: - checkConvergenceExtraEqs(const std::vector& res, - ConvergenceReport& report) const - { - // if different types of extra equations are involved, this function needs to be refactored further - - // checking the convergence of the extra equations related to polymer injectivity - if constexpr (Base::has_polymermw) { - WellConvergence(*this). - checkConvergencePolyMW(res, Bhp, this->param_.max_residual_allowed_, report); - } - } - - - - - - template - void - StandardWell:: - updateConnectionRatePolyMW(const EvalWell& cq_s_poly, - const IntensiveQuantities& int_quants, - const WellState& well_state, - const int perf, - std::vector& connectionRates, - DeferredLogger& deferred_logger) const - { - // the source term related to transport of molecular weight - EvalWell cq_s_polymw = cq_s_poly; - if (this->isInjector()) { - const int wat_vel_index = Bhp + 1 + perf; - const EvalWell water_velocity = this->primary_variables_.eval(wat_vel_index); - if (water_velocity > 0.) { // injecting - const auto& ws = well_state.well(this->index_of_well_); - const auto& perf_water_throughput = ws.perf_data.water_throughput; - const double throughput = perf_water_throughput[perf]; - const EvalWell molecular_weight = wpolymermw(throughput, water_velocity, deferred_logger); - cq_s_polymw *= molecular_weight; - } else { - // we do not consider the molecular weight from the polymer - // going-back to the wellbore through injector - cq_s_polymw *= 0.; - } - } else if (this->isProducer()) { - if (cq_s_polymw < 0.) { - cq_s_polymw *= this->extendEval(int_quants.polymerMoleWeight() ); - } else { - // we do not consider the molecular weight from the polymer - // re-injecting back through producer - cq_s_polymw *= 0.; - } - } - connectionRates[perf][Indices::contiPolymerMWEqIdx] = Base::restrictEval(cq_s_polymw); - } - - - - - - - template - std::optional - StandardWell:: - computeBhpAtThpLimitProd(const WellState& well_state, - const Simulator& ebos_simulator, - const SummaryState& summary_state, - DeferredLogger& deferred_logger) const - { - return computeBhpAtThpLimitProdWithAlq(ebos_simulator, - summary_state, - this->getALQ(well_state), - deferred_logger); - } - - template - std::optional - StandardWell:: - computeBhpAtThpLimitProdWithAlq(const Simulator& ebos_simulator, - const SummaryState& summary_state, - const double alq_value, - DeferredLogger& deferred_logger) const - { - // Make the frates() function. - auto frates = [this, &ebos_simulator, &deferred_logger](const double bhp) { - // Not solving the well equations here, which means we are - // calculating at the current Fg/Fw values of the - // well. This does not matter unless the well is - // crossflowing, and then it is likely still a good - // approximation. - std::vector rates(3); - computeWellRatesWithBhp(ebos_simulator, bhp, rates, deferred_logger); - this->adaptRatesForVFP(rates); - return rates; - }; - - double max_pressure = 0.0; - for (int perf = 0; perf < this->number_of_perforations_; ++perf) { - const int cell_idx = this->well_cells_[perf]; - const auto& int_quants = ebos_simulator.model().intensiveQuantities(cell_idx, /*timeIdx=*/ 0); - const auto& fs = int_quants.fluidState(); - double pressure_cell = this->getPerfCellPressure(fs).value(); - max_pressure = std::max(max_pressure, pressure_cell); - } - auto bhpAtLimit = WellBhpThpCalculator(*this).computeBhpAtThpLimitProd(frates, - summary_state, - max_pressure, - this->connections_.rho(), - alq_value, - this->getTHPConstraint(summary_state), - deferred_logger); - - if (bhpAtLimit) { - auto v = frates(*bhpAtLimit); - if (std::all_of(v.cbegin(), v.cend(), [](double i){ return i <= 0; }) ) { - return bhpAtLimit; - } - } - - - auto fratesIter = [this, &ebos_simulator, &deferred_logger](const double bhp) { - // Solver the well iterations to see if we are - // able to get a solution with an update - // solution - std::vector rates(3); - computeWellRatesWithBhpIterations(ebos_simulator, bhp, rates, deferred_logger); - this->adaptRatesForVFP(rates); - return rates; - }; - - bhpAtLimit = WellBhpThpCalculator(*this).computeBhpAtThpLimitProd(fratesIter, - summary_state, - max_pressure, - this->connections_.rho(), - alq_value, - this->getTHPConstraint(summary_state), - deferred_logger); - - - if (bhpAtLimit) { - // should we use fratesIter here since fratesIter is used in computeBhpAtThpLimitProd above? - auto v = frates(*bhpAtLimit); - if (std::all_of(v.cbegin(), v.cend(), [](double i){ return i <= 0; }) ) { - return bhpAtLimit; - } - } - - // we still don't get a valied solution. - return std::nullopt; - } - - - - template - std::optional - StandardWell:: - computeBhpAtThpLimitInj(const Simulator& ebos_simulator, - const SummaryState& summary_state, - DeferredLogger& deferred_logger) const - { - // Make the frates() function. - auto frates = [this, &ebos_simulator, &deferred_logger](const double bhp) { - // Not solving the well equations here, which means we are - // calculating at the current Fg/Fw values of the - // well. This does not matter unless the well is - // crossflowing, and then it is likely still a good - // approximation. - std::vector rates(3); - computeWellRatesWithBhp(ebos_simulator, bhp, rates, deferred_logger); - return rates; - }; - - return WellBhpThpCalculator(*this).computeBhpAtThpLimitInj(frates, - summary_state, - this->connections_.rho(), - 1e-6, - 50, - true, - deferred_logger); - } - - - - - - template - bool - StandardWell:: - iterateWellEqWithControl(const Simulator& ebosSimulator, - const double dt, - const Well::InjectionControls& inj_controls, - const Well::ProductionControls& prod_controls, - WellState& well_state, - const GroupState& group_state, - DeferredLogger& deferred_logger) - { - const int max_iter = this->param_.max_inner_iter_wells_; - int it = 0; - bool converged; - bool relax_convergence = false; - this->regularize_ = false; - const auto& summary_state = ebosSimulator.vanguard().summaryState(); - do { - assembleWellEqWithoutIteration(ebosSimulator, dt, inj_controls, prod_controls, well_state, group_state, deferred_logger); - - if (it > this->param_.strict_inner_iter_wells_) { - relax_convergence = true; - this->regularize_ = true; - } - - auto report = getWellConvergence(summary_state, well_state, Base::B_avg_, deferred_logger, relax_convergence); - - converged = report.converged(); - if (converged) { - break; - } - - ++it; - solveEqAndUpdateWellState(summary_state, well_state, deferred_logger); - - // TODO: when this function is used for well testing purposes, will need to check the controls, so that we will obtain convergence - // under the most restrictive control. Based on this converged results, we can check whether to re-open the well. Either we refactor - // this function or we use different functions for the well testing purposes. - // We don't allow for switching well controls while computing well potentials and testing wells - // updateWellControl(ebosSimulator, well_state, deferred_logger); - initPrimaryVariablesEvaluation(); - } while (it < max_iter); - - return converged; - } - - - template - std::vector - StandardWell:: - computeCurrentWellRates(const Simulator& ebosSimulator, - DeferredLogger& deferred_logger) const - { - // Calculate the rates that follow from the current primary variables. - std::vector well_q_s(this->num_components_, 0.); - const EvalWell& bhp = this->primary_variables_.eval(Bhp); - const bool allow_cf = this->getAllowCrossFlow() || openCrossFlowAvoidSingularity(ebosSimulator); - for (int perf = 0; perf < this->number_of_perforations_; ++perf) { - const int cell_idx = this->well_cells_[perf]; - const auto& intQuants = ebosSimulator.model().intensiveQuantities(cell_idx, /*timeIdx=*/ 0); - std::vector mob(this->num_components_, 0.); - getMobility(ebosSimulator, perf, mob, deferred_logger); - std::vector cq_s(this->num_components_, 0.); - double trans_mult = ebosSimulator.problem().template rockCompTransMultiplier(intQuants, cell_idx); - double elapsed_time = ebosSimulator.time(); - const double Tw = this->wellIndex(perf) * trans_mult; - PerforationRates perf_rates; - computePerfRate(intQuants, mob, bhp.value(), Tw, perf, allow_cf, elapsed_time, - cq_s, perf_rates, deferred_logger, ebosSimulator); - for (int comp = 0; comp < this->num_components_; ++comp) { - well_q_s[comp] += cq_s[comp]; - } - } - const auto& comm = this->parallel_well_info_.communication(); - if (comm.size() > 1) - { - comm.sum(well_q_s.data(), well_q_s.size()); - } - return well_q_s; - } - - - - template - std::vector - StandardWell:: - getPrimaryVars() const - { - const int num_pri_vars = this->primary_variables_.numWellEq(); - std::vector retval(num_pri_vars); - for (int ii = 0; ii < num_pri_vars; ++ii) { - retval[ii] = this->primary_variables_.value(ii); - } - return retval; - } - - - - - - template - int - StandardWell:: - setPrimaryVars(std::vector::const_iterator it) - { - const int num_pri_vars = this->primary_variables_.numWellEq(); - for (int ii = 0; ii < num_pri_vars; ++ii) { - this->primary_variables_.setValue(ii, it[ii]); - } - return num_pri_vars; - } - - - template - typename StandardWell::Eval - StandardWell:: - connectionRateEnergy(const double maxOilSaturation, - const std::vector& cq_s, - const IntensiveQuantities& intQuants, - DeferredLogger& deferred_logger) const - { - auto fs = intQuants.fluidState(); - Eval result = 0; - for (unsigned phaseIdx = 0; phaseIdx < FluidSystem::numPhases; ++phaseIdx) { - if (!FluidSystem::phaseIsActive(phaseIdx)) { - continue; - } - - // convert to reservoir conditions - EvalWell cq_r_thermal(this->primary_variables_.numWellEq() + Indices::numEq, 0.); - const unsigned activeCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::solventComponentIndex(phaseIdx)); - const bool both_oil_gas = FluidSystem::phaseIsActive(FluidSystem::oilPhaseIdx) && FluidSystem::phaseIsActive(FluidSystem::gasPhaseIdx); - if (!both_oil_gas || FluidSystem::waterPhaseIdx == phaseIdx) { - cq_r_thermal = cq_s[activeCompIdx] / this->extendEval(fs.invB(phaseIdx)); - } else { - // remove dissolved gas and vapporized oil - const unsigned oilCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::oilCompIdx); - const unsigned gasCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::gasCompIdx); - // q_os = q_or * b_o + rv * q_gr * b_g - // q_gs = q_gr * g_g + rs * q_or * b_o - // q_gr = 1 / (b_g * d) * (q_gs - rs * q_os) - // d = 1.0 - rs * rv - const EvalWell d = this->extendEval(1.0 - fs.Rv() * fs.Rs()); - if (d <= 0.0) { - deferred_logger.debug( - fmt::format("Problematic d value {} obtained for well {}" - " during calculateSinglePerf with rs {}" - ", rv {}. Continue as if no dissolution (rs = 0) and" - " vaporization (rv = 0) for this connection.", - d, this->name(), fs.Rs(), fs.Rv())); - cq_r_thermal = cq_s[activeCompIdx] / this->extendEval(fs.invB(phaseIdx)); - } else { - if (FluidSystem::gasPhaseIdx == phaseIdx) { - cq_r_thermal = (cq_s[gasCompIdx] - - this->extendEval(fs.Rs()) * cq_s[oilCompIdx]) / - (d * this->extendEval(fs.invB(phaseIdx)) ); - } else if (FluidSystem::oilPhaseIdx == phaseIdx) { - // q_or = 1 / (b_o * d) * (q_os - rv * q_gs) - cq_r_thermal = (cq_s[oilCompIdx] - this->extendEval(fs.Rv()) * - cq_s[gasCompIdx]) / - (d * this->extendEval(fs.invB(phaseIdx)) ); - } - } - } - - // change temperature for injecting fluids - if (this->isInjector() && cq_s[activeCompIdx] > 0.0){ - // only handles single phase injection now - assert(this->well_ecl_.injectorType() != InjectorType::MULTI); - fs.setTemperature(this->well_ecl_.temperature()); - typedef typename std::decay::type::Scalar FsScalar; - typename FluidSystem::template ParameterCache paramCache; - const unsigned pvtRegionIdx = intQuants.pvtRegionIndex(); - paramCache.setRegionIndex(pvtRegionIdx); - paramCache.setMaxOilSat(maxOilSaturation); - paramCache.updatePhase(fs, phaseIdx); - - const auto& rho = FluidSystem::density(fs, paramCache, phaseIdx); - fs.setDensity(phaseIdx, rho); - const auto& h = FluidSystem::enthalpy(fs, paramCache, phaseIdx); - fs.setEnthalpy(phaseIdx, h); - cq_r_thermal *= this->extendEval(fs.enthalpy(phaseIdx)) * this->extendEval(fs.density(phaseIdx)); - result += getValue(cq_r_thermal); - } else { - // compute the thermal flux - cq_r_thermal *= this->extendEval(fs.enthalpy(phaseIdx)) * this->extendEval(fs.density(phaseIdx)); - result += Base::restrictEval(cq_r_thermal); - } - } - - return result; - } - - - template - template - void - StandardWell:: - gasOilPerfRateInj(const std::vector& cq_s, - PerforationRates& perf_rates, - const Value& rv, - const Value& rs, - const Value& pressure, - const Value& rvw, - DeferredLogger& deferred_logger) const - { - const unsigned oilCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::oilCompIdx); - const unsigned gasCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::gasCompIdx); - // TODO: the formulations here remain to be tested with cases with strong crossflow through production wells - // s means standard condition, r means reservoir condition - // q_os = q_or * b_o + rv * q_gr * b_g - // q_gs = q_gr * b_g + rs * q_or * b_o - // d = 1.0 - rs * rv - // q_or = 1 / (b_o * d) * (q_os - rv * q_gs) - // q_gr = 1 / (b_g * d) * (q_gs - rs * q_os) - - const double d = 1.0 - getValue(rv) * getValue(rs); - - if (d <= 0.0) { - deferred_logger.debug(dValueError(d, this->name(), - "gasOilPerfRateInj", - rs, rv, pressure)); - } else { - // vaporized oil into gas - // rv * q_gr * b_g = rv * (q_gs - rs * q_os) / d - perf_rates.vap_oil = getValue(rv) * (getValue(cq_s[gasCompIdx]) - getValue(rs) * getValue(cq_s[oilCompIdx])) / d; - // dissolved of gas in oil - // rs * q_or * b_o = rs * (q_os - rv * q_gs) / d - perf_rates.dis_gas = getValue(rs) * (getValue(cq_s[oilCompIdx]) - getValue(rv) * getValue(cq_s[gasCompIdx])) / d; - } - - if (FluidSystem::phaseIsActive(FluidSystem::waterPhaseIdx)) { - // q_ws = q_wr * b_w + rvw * q_gr * b_g - // q_wr = 1 / b_w * (q_ws - rvw * q_gr * b_g) = 1 / b_w * (q_ws - rvw * 1 / d (q_gs - rs * q_os)) - // vaporized water in gas - // rvw * q_gr * b_g = q_ws -q_wr *b_w = rvw * (q_gs -rs *q_os) / d - perf_rates.vap_wat = getValue(rvw) * (getValue(cq_s[gasCompIdx]) - getValue(rs) * getValue(cq_s[oilCompIdx])) / d; - } - } - - - - template - template - void - StandardWell:: - gasOilPerfRateProd(std::vector& cq_s, - PerforationRates& perf_rates, - const Value& rv, - const Value& rs, - const Value& rvw) const - { - const unsigned oilCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::oilCompIdx); - const unsigned gasCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::gasCompIdx); - const Value cq_sOil = cq_s[oilCompIdx]; - const Value cq_sGas = cq_s[gasCompIdx]; - const Value dis_gas = rs * cq_sOil; - const Value vap_oil = rv * cq_sGas; - - cq_s[gasCompIdx] += dis_gas; - cq_s[oilCompIdx] += vap_oil; - - // recording the perforation solution gas rate and solution oil rates - if (this->isProducer()) { - perf_rates.dis_gas = getValue(dis_gas); - perf_rates.vap_oil = getValue(vap_oil); - } - - if (FluidSystem::phaseIsActive(FluidSystem::waterPhaseIdx)) { - const unsigned waterCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::waterCompIdx); - const Value vap_wat = rvw * cq_sGas; - cq_s[waterCompIdx] += vap_wat; - if (this->isProducer()) - perf_rates.vap_wat = getValue(vap_wat); - } - } - - - template - template - void - StandardWell:: - gasWaterPerfRateProd(std::vector& cq_s, - PerforationRates& perf_rates, - const Value& rvw, - const Value& rsw) const - { - const unsigned waterCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::waterCompIdx); - const unsigned gasCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::gasCompIdx); - const Value cq_sWat = cq_s[waterCompIdx]; - const Value cq_sGas = cq_s[gasCompIdx]; - const Value vap_wat = rvw * cq_sGas; - const Value dis_gas_wat = rsw * cq_sWat; - cq_s[waterCompIdx] += vap_wat; - cq_s[gasCompIdx] += dis_gas_wat; - if (this->isProducer()) { - perf_rates.vap_wat = getValue(vap_wat); - perf_rates.dis_gas_in_water = getValue(dis_gas_wat); - } - } - - - template - template - void - StandardWell:: - gasWaterPerfRateInj(const std::vector& cq_s, - PerforationRates& perf_rates, - const Value& rvw, - const Value& rsw, - const Value& pressure, - DeferredLogger& deferred_logger) const - - { - const unsigned gasCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::gasCompIdx); - const unsigned waterCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::waterCompIdx); - - const double dw = 1.0 - getValue(rvw) * getValue(rsw); - - if (dw <= 0.0) { - deferred_logger.debug(dValueError(dw, this->name(), - "gasWaterPerfRateInj", - rsw, rvw, pressure)); - } else { - // vaporized water into gas - // rvw * q_gr * b_g = rvw * (q_gs - rsw * q_ws) / dw - perf_rates.vap_wat = getValue(rvw) * (getValue(cq_s[gasCompIdx]) - getValue(rsw) * getValue(cq_s[waterCompIdx])) / dw; - // dissolved gas in water - // rsw * q_wr * b_w = rsw * (q_ws - rvw * q_gs) / dw - perf_rates.dis_gas_in_water = getValue(rsw) * (getValue(cq_s[waterCompIdx]) - getValue(rvw) * getValue(cq_s[gasCompIdx])) / dw; - } - } - - - template - template - void - StandardWell:: - disOilVapWatVolumeRatio(Value& volumeRatio, - const Value& rvw, - const Value& rsw, - const Value& pressure, - const std::vector& cmix_s, - const std::vector& b_perfcells_dense, - DeferredLogger& deferred_logger) const - { - const unsigned waterCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::waterCompIdx); - const unsigned gasCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::gasCompIdx); - // Incorporate RSW/RVW factors if both water and gas active - const Value d = 1.0 - rvw * rsw; - - if (d <= 0.0) { - deferred_logger.debug(dValueError(d, this->name(), - "disOilVapWatVolumeRatio", - rsw, rvw, pressure)); - } - const Value tmp_wat = d > 0.0 ? (cmix_s[waterCompIdx] - rvw * cmix_s[gasCompIdx]) / d - : cmix_s[waterCompIdx]; - volumeRatio += tmp_wat / b_perfcells_dense[waterCompIdx]; - - const Value tmp_gas = d > 0.0 ? (cmix_s[gasCompIdx] - rsw * cmix_s[waterCompIdx]) / d - : cmix_s[waterCompIdx]; - volumeRatio += tmp_gas / b_perfcells_dense[gasCompIdx]; - } - - - template - template - void - StandardWell:: - gasOilVolumeRatio(Value& volumeRatio, - const Value& rv, - const Value& rs, - const Value& pressure, - const std::vector& cmix_s, - const std::vector& b_perfcells_dense, - DeferredLogger& deferred_logger) const - { - const unsigned oilCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::oilCompIdx); - const unsigned gasCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::gasCompIdx); - // Incorporate RS/RV factors if both oil and gas active - const Value d = 1.0 - rv * rs; - - if (d <= 0.0) { - deferred_logger.debug(dValueError(d, this->name(), - "gasOilVolumeRatio", - rs, rv, pressure)); - } - const Value tmp_oil = d > 0.0? (cmix_s[oilCompIdx] - rv * cmix_s[gasCompIdx]) / d : cmix_s[oilCompIdx]; - volumeRatio += tmp_oil / b_perfcells_dense[oilCompIdx]; - - const Value tmp_gas = d > 0.0? (cmix_s[gasCompIdx] - rs * cmix_s[oilCompIdx]) / d : cmix_s[gasCompIdx]; - volumeRatio += tmp_gas / b_perfcells_dense[gasCompIdx]; - } - - template - template - Value - StandardWell:: - wellIndexEval(const int perf, - const Simulator& ebosSimulator - ) const - { - // Define some helper functions in this scope - auto obtain = [this](const Eval& value) - { - if constexpr (std::is_same_v) { - static_cast(this); // suppress clang warning - return getValue(value); - } else { - return value; - } - }; - - // Some constants; as of now they are set by filling out this *.hpp mako - // TODO: Find some smarter way to do this - // TODO: Move the scaling into a model layer - - float xmin[${len(xmin)}]; - float xmax[${len(xmax)}]; - std::string cell_feature_names[${len(cell_feature_names)}]; - const int stencil_size{ ${stencil_size} }; - const int num_cell_features{ ${len(cell_feature_names)} }; - - % for min_value, max_value in zip(xmin, xmax): - xmin[${loop.index}] = ${min_value}; - xmax[${loop.index}] = ${max_value}; - % endfor - auto feature_min { ${x_range_min} }; - auto feature_max { ${x_range_max} }; - auto target_min { ${y_range_min} }; - auto target_max { ${y_range_max} }; - - - % for cell_feature_name in cell_feature_names: - cell_feature_names[${loop.index}] = "${cell_feature_name}"; - % endfor - - // For now, total injected gas and analytical WI are the global features - const int num_global_features { 2 }; - const double injection_rate_per_second { 5000000 / 86000 }; - // TODO: This needs to be passed to the function - const double analytical_WI { 0.000005 }; - - // Load ML model - KerasModel model; - model.LoadModel(Base::ml_wi_filename_); - - // Get simulator values - - // Get values for local features - std::array, num_cell_features> features; - - for (int i = 0; i < stencil_size; ++i) { - // Perforation index - int perf_i = perf - 1 + i; - - // TODO: Add neighbor padding for pressure - - // Upper boundary: Set zero padding - if (perf_i < 0 ) { - for (int j = 0; j < num_cell_features; ++j) { - features[i][j] = Value(0.0); - } - } - // Lower boundary: Set zero padding - else if (perf_i >= this->number_of_perforations_) { - for (int j = 0; j < num_cell_features; ++j) { - features[i][j] = Value(0.0); - } - } - - // Inside the domain - else { - const int cell_idx = this->well_cells_[perf_i]; - const auto& intQuants = ebosSimulator.model().intensiveQuantities(cell_idx, /*timeIdx=*/ 0); - - auto fs = intQuants.fluidState(); - - for (int j = 0; j < num_cell_features; ++j) { - std::string feature_name = cell_feature_names[j]; - if (feature_name == "pressure") { - features[i][j] = obtain(this->getPerfCellPressure(fs)); - } - // TODO: Are saturation and permeability Values or will this create - // a bug? - else if (feature_name == "saturation") { - features[i][j] = obtain(fs.saturation(FluidSystem::gasPhaseIdx)); - } - else if (feature_name == "permeability") { - const auto& connection = Base::well_ecl_.getConnections()[perf_i]; - features[i][j] = Value(connection.Kh()/connection.connectionLength()); - } - std::cout << feature_name << " cell_" << i << ": " << features[i][j] << std::endl; - } - } - } - - // Give number of input parameters - Tensor in{stencil_size * num_cell_features + num_global_features}; - - // Scale local features and order them as input tensor - // Note: order needs to be the same as during training - for (int j = 0; j < num_cell_features; ++j) { - for (int i = 0; i < stencil_size; ++i) { - features[i][j] = scaleFunction(features[i][j], - xmin[j * stencil_size + i], - xmax[j * stencil_size + i], - feature_min, - feature_max - ); - in.data_[j * stencil_size + i] = features[i][j]; - std::cout << "feature_" << i << " cell_" << j << " scaled: " << features[i][j] << std::endl; - } - } - - // Add local features - // Note: order needs to be the same as during training - // TODO: Needs more functionality for num_global_features > 1 - const auto total_inj_gas = Value(scaleFunction(ebosSimulator.time() * injection_rate_per_second, - xmin[${len(xmin)} - 2], - xmax[${len(xmin)} - 2], - feature_min, - feature_max - )); - const auto analytical_WI_scaled = Value(scaleFunction(analytical_WI, - xmin[${len(xmin)} - 1], - xmax[${len(xmin)} - 1], - feature_min, - feature_max - )); - in.data_[stencil_size * num_cell_features + num_global_features - 2] = total_inj_gas; - in.data_[stencil_size * num_cell_features + num_global_features - 1] = analytical_WI_scaled; - - // Run prediction - Tensor out; - model.Apply(&in, &out); - - // Some help for debugging - std::cout << "total_inj_gas_scaled: " << total_inj_gas << std::endl; - std::cout << "total_inj_gas: " << ebosSimulator.time() * injection_rate_per_second << std::endl; - std::cout << "time: " << ebosSimulator.time() << std::endl; - - std::cout << "WI: " << unscaleFunction(out.data_[0],${ymin}, ${ymax}, target_min, target_max) << std::endl; - std::cout << "WI_scaled: " << getValue(out.data_[0]) << std::endl; - - return unscaleFunction(out.data_[0],${ymin}, ${ymax}, target_min, target_max); - } - - template - template - Value - StandardWell:: - scaleFunction(Value X, double min, double max, double range_min, double range_max) const - { - Value X_std { (X - min) / (max - min) }; - return X_std * (range_max - range_min) + range_min; - } - - template - template - Value - StandardWell:: - unscaleFunction(Value X, double min, double max, double range_min, double range_max) const - { - Value X_std { (X - range_min) / (range_max - range_min) }; - return X_std * (max - min) + min; - } - -} // namespace Opm diff --git a/src/pyopmnearwell/templates/standardwell_impl/h2o_2_inputs.mako b/src/pyopmnearwell/templates/standardwell_impl/h2o_2_inputs.mako deleted file mode 100644 index 7a031f3..0000000 --- a/src/pyopmnearwell/templates/standardwell_impl/h2o_2_inputs.mako +++ /dev/null @@ -1,2576 +0,0 @@ -/* - Copyright 2017 SINTEF Digital, Mathematics and Cybernetics. - Copyright 2017 Statoil ASA. - Copyright 2016 - 2017 IRIS AS. - - This file is part of the Open Porous Media project (OPM). - - OPM is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - OPM is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with OPM. If not, see . -*/ - -#include - -#include - -#include - -#include -#include -#include -#include -#include - -#include - -#include - -#include -#include -#include - - -namespace { - -template -auto dValueError(const dValue& d, - const std::string& name, - const std::string& methodName, - const Value& Rs, - const Value& Rv, - const Value& pressure) -{ - return fmt::format("Problematic d value {} obtained for well {}" - " during {} calculations with rs {}" - ", rv {} and pressure {}." - " Continue as if no dissolution (rs = 0) and vaporization (rv = 0) " - " for this connection.", d, name, methodName, Rs, Rv, pressure); -} - -} - -namespace Opm -{ - - template - StandardWell:: - StandardWell(const Well& well, - const ParallelWellInfo& pw_info, - const int time_step, - const ModelParameters& param, - const RateConverterType& rate_converter, - const int pvtRegionIdx, - const int num_components, - const int num_phases, - const int index_of_well, - const std::vector& perf_data) - : Base(well, pw_info, time_step, param, rate_converter, pvtRegionIdx, num_components, num_phases, index_of_well, perf_data) - , StdWellEval(static_cast&>(*this)) - , regularize_(false) - { - assert(this->num_components_ == numWellConservationEq); - } - - - - - - template - void - StandardWell:: - init(const PhaseUsage* phase_usage_arg, - const std::vector& depth_arg, - const double gravity_arg, - const int num_cells, - const std::vector< Scalar >& B_avg, - const bool changed_to_open_this_step) - { - Base::init(phase_usage_arg, depth_arg, gravity_arg, num_cells, B_avg, changed_to_open_this_step); - this->StdWellEval::init(this->perf_depth_, depth_arg, num_cells, Base::has_polymermw); - } - - - - - - template - void StandardWell:: - initPrimaryVariablesEvaluation() - { - this->primary_variables_.init(); - } - - - - - - template - template - void - StandardWell:: - computePerfRate(const IntensiveQuantities& intQuants, - const std::vector& mob, - const Value& bhp, - const double Tw, - const int perf, - const bool allow_cf, - const double elapsed_time, - std::vector& cq_s, - PerforationRates& perf_rates, - DeferredLogger& deferred_logger) const - { - auto obtain = [this](const Eval& value) - { - if constexpr (std::is_same_v) { - static_cast(this); // suppress clang warning - return getValue(value); - } else { - return this->extendEval(value); - } - }; - auto obtainN = [](const auto& value) - { - if constexpr (std::is_same_v) { - return getValue(value); - } else { - return value; - } - }; - auto zeroElem = [this]() - { - if constexpr (std::is_same_v) { - static_cast(this); // suppress clang warning - return 0.0; - } else { - return Value{this->primary_variables_.numWellEq() + Indices::numEq, 0.0}; - } - }; - - const auto& fs = intQuants.fluidState(); - const Value pressure = obtain(this->getPerfCellPressure(fs)); - const Value rs = obtain(fs.Rs()); - const Value rv = obtain(fs.Rv()); - const Value rvw = obtain(fs.Rvw()); - const Value rsw = obtain(fs.Rsw()); - - std::vector b_perfcells_dense(this->numComponents(), zeroElem()); - for (unsigned phaseIdx = 0; phaseIdx < FluidSystem::numPhases; ++phaseIdx) { - if (!FluidSystem::phaseIsActive(phaseIdx)) { - continue; - } - const unsigned compIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::solventComponentIndex(phaseIdx)); - b_perfcells_dense[compIdx] = obtain(fs.invB(phaseIdx)); - } - if constexpr (has_solvent) { - b_perfcells_dense[Indices::contiSolventEqIdx] = obtain(intQuants.solventInverseFormationVolumeFactor()); - } - - if constexpr (has_zFraction) { - if (this->isInjector()) { - const unsigned gasCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::gasCompIdx); - b_perfcells_dense[gasCompIdx] *= (1.0 - this->wsolvent()); - b_perfcells_dense[gasCompIdx] += this->wsolvent()*intQuants.zPureInvFormationVolumeFactor().value(); - } - } - - Value skin_pressure = zeroElem(); - if (has_polymermw) { - if (this->isInjector()) { - const int pskin_index = Bhp + 1 + this->numPerfs() + perf; - skin_pressure = obtainN(this->primary_variables_.eval(pskin_index)); - } - } - - // surface volume fraction of fluids within wellbore - std::vector cmix_s(this->numComponents(), zeroElem()); - for (int componentIdx = 0; componentIdx < this->numComponents(); ++componentIdx) { - cmix_s[componentIdx] = obtainN(this->primary_variables_.surfaceVolumeFraction(componentIdx)); - } - - computePerfRate(mob, - pressure, - bhp, - rs, - rv, - rvw, - rsw, - b_perfcells_dense, - Tw, - perf, - allow_cf, - skin_pressure, - cmix_s, - elapsed_time, - cq_s, - perf_rates, - deferred_logger); - } - - template - template - void - StandardWell:: - computePerfRate(const std::vector& mob, - const Value& pressure, - const Value& bhp, - const Value& rs, - const Value& rv, - const Value& rvw, - const Value& rsw, - std::vector& b_perfcells_dense, - const double Tw, - const int perf, - const bool allow_cf, - const Value& skin_pressure, - const std::vector& cmix_s, - const double elapsed_time, - std::vector& cq_s, - PerforationRates& perf_rates, - DeferredLogger& deferred_logger) const - { - // Pressure drawdown (also used to determine direction of flow) - const Value well_pressure = bhp + this->connections_.pressure_diff(perf); - Value drawdown = pressure - well_pressure; - if (this->isInjector()) { - drawdown += skin_pressure; - } - - // producing perforations - if (drawdown > 0) { - // Do nothing if crossflow is not allowed - if (!allow_cf && this->isInjector()) { - return; - } - - // compute component volumetric rates at standard conditions - for (int componentIdx = 0; componentIdx < this->numComponents(); ++componentIdx) { - const Value cq_p = - Tw * (mob[componentIdx] * drawdown); - cq_s[componentIdx] = b_perfcells_dense[componentIdx] * cq_p; - } - - if (FluidSystem::phaseIsActive(FluidSystem::oilPhaseIdx) && FluidSystem::phaseIsActive(FluidSystem::gasPhaseIdx)) { - gasOilPerfRateProd(cq_s, perf_rates, rv, rs, rvw); - } else if (FluidSystem::phaseIsActive(FluidSystem::waterPhaseIdx) && FluidSystem::phaseIsActive(FluidSystem::gasPhaseIdx)) { - gasWaterPerfRateProd(cq_s, perf_rates, rvw, rsw); - } - } else { - // Do nothing if crossflow is not allowed - if (!allow_cf && this->isProducer()) { - return; - } - - // Using total mobilities - Value total_mob_dense = mob[0]; - for (int componentIdx = 1; componentIdx < this->numComponents(); ++componentIdx) { - total_mob_dense += mob[componentIdx]; - } - - // Use well index from ML - if (!Base::ml_wi_filename_.empty()) { - Value WI; - if (allow_cf) { - OPM_DEFLOG_THROW(std::runtime_error, - fmt::format("ML well index only implemented with no cross flow. Well {}", name()), - deferred_logger); - } - if constexpr (std::is_same_v) { - WI = this->extendEval(wellIndexEval(perf, elapsed_time, Base::restrictEval(pressure))); - } else { - WI = wellIndexEval(perf, elapsed_time, pressure); - } - auto injectorType = this->well_ecl_.injectorType(); - if (injectorType == InjectorType::WATER) { - const unsigned waterCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::waterCompIdx); - //cq_s[waterCompIdx] = - WI * (total_mob_dense * drawdown) * b_perfcells_dense[waterCompIdx]; - cq_s[waterCompIdx] = - WI * drawdown; - std::cout << cq_s[waterCompIdx] << " " << - Tw * (total_mob_dense * drawdown)*b_perfcells_dense[waterCompIdx] << " " << cmix_s[waterCompIdx] << " " << b_perfcells_dense[waterCompIdx] << " " << total_mob_dense << std::endl; - std::cout << WI << " " << (Tw*total_mob_dense*b_perfcells_dense[waterCompIdx]) << std::endl; - } - else if (injectorType == InjectorType::GAS) { - const unsigned gasCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::gasCompIdx); - //cq_s[gasCompIdx] = - WI * (total_mob_dense * drawdown) * b_perfcells_dense[gasCompIdx]; - cq_s[gasCompIdx] = - WI * drawdown; - } - } else { - std::cout << "ML model not found"; - - const Value cqt_i = - Tw * (total_mob_dense * drawdown); - - // compute volume ratio between connection at standard conditions - Value volumeRatio = bhp * 0.0; // initialize it with the correct type -; - if (FluidSystem::enableVaporizedWater() && FluidSystem::enableDissolvedGasInWater()) { - disOilVapWatVolumeRatio(volumeRatio, rvw, rsw, pressure, - cmix_s, b_perfcells_dense, deferred_logger); - } else { - if (FluidSystem::phaseIsActive(FluidSystem::waterPhaseIdx)) { - const unsigned waterCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::waterCompIdx); - volumeRatio += cmix_s[waterCompIdx] / b_perfcells_dense[waterCompIdx]; - } - } - - if constexpr (Indices::enableSolvent) { - volumeRatio += cmix_s[Indices::contiSolventEqIdx] / b_perfcells_dense[Indices::contiSolventEqIdx]; - } - - if (FluidSystem::phaseIsActive(FluidSystem::oilPhaseIdx) && FluidSystem::phaseIsActive(FluidSystem::gasPhaseIdx)) { - gasOilVolumeRatio(volumeRatio, rv, rs, pressure, - cmix_s, b_perfcells_dense, deferred_logger); - } - else { - if (FluidSystem::phaseIsActive(FluidSystem::oilPhaseIdx)) { - const unsigned oilCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::oilCompIdx); - volumeRatio += cmix_s[oilCompIdx] / b_perfcells_dense[oilCompIdx]; - } - if (FluidSystem::phaseIsActive(FluidSystem::gasPhaseIdx)) { - const unsigned gasCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::gasCompIdx); - volumeRatio += cmix_s[gasCompIdx] / b_perfcells_dense[gasCompIdx]; - } - } - - // injecting connections total volumerates at standard conditions - Value cqt_is = cqt_i / volumeRatio; - for (int componentIdx = 0; componentIdx < this->numComponents(); ++componentIdx) { - cq_s[componentIdx] = cmix_s[componentIdx] * cqt_is; - } - } - - // calculating the perforation solution gas rate and solution oil rates - if (this->isProducer()) { - if (FluidSystem::phaseIsActive(FluidSystem::oilPhaseIdx) && FluidSystem::phaseIsActive(FluidSystem::gasPhaseIdx)) { - gasOilPerfRateInj(cq_s, perf_rates, - rv, rs, pressure, rvw, deferred_logger); - } - if (FluidSystem::phaseIsActive(FluidSystem::gasPhaseIdx) && FluidSystem::phaseIsActive(FluidSystem::waterPhaseIdx)) { - //no oil - gasWaterPerfRateInj(cq_s, perf_rates, rvw, rsw, - pressure, deferred_logger); - } - } - } - } - - - template - void - StandardWell:: - assembleWellEqWithoutIteration(const Simulator& ebosSimulator, - const double dt, - const Well::InjectionControls& inj_controls, - const Well::ProductionControls& prod_controls, - WellState& well_state, - const GroupState& group_state, - DeferredLogger& deferred_logger) - { - // TODO: only_wells should be put back to save some computation - // for example, the matrices B C does not need to update if only_wells - if (!this->isOperableAndSolvable() && !this->wellIsStopped()) return; - - // clear all entries - this->linSys_.clear(); - - assembleWellEqWithoutIterationImpl(ebosSimulator, dt, inj_controls, prod_controls, well_state, group_state, deferred_logger); - } - - - - - template - void - StandardWell:: - assembleWellEqWithoutIterationImpl(const Simulator& ebosSimulator, - const double dt, - const Well::InjectionControls& inj_controls, - const Well::ProductionControls& prod_controls, - WellState& well_state, - const GroupState& group_state, - DeferredLogger& deferred_logger) - { - // try to regularize equation if the well does not converge - const Scalar regularization_factor = this->regularize_? this->param_.regularization_factor_wells_ : 1.0; - const double volume = 0.1 * unit::cubic(unit::feet) * regularization_factor; - - auto& ws = well_state.well(this->index_of_well_); - ws.phase_mixing_rates.fill(0.0); - - const int np = this->number_of_phases_; - - std::vector connectionRates = this->connectionRates_; // Copy to get right size. - auto& perf_data = ws.perf_data; - auto& perf_rates = perf_data.phase_rates; - for (int perf = 0; perf < this->number_of_perforations_; ++perf) { - // Calculate perforation quantities. - std::vector cq_s(this->num_components_, {this->primary_variables_.numWellEq() + Indices::numEq, 0.0}); - EvalWell water_flux_s{this->primary_variables_.numWellEq() + Indices::numEq, 0.0}; - EvalWell cq_s_zfrac_effective{this->primary_variables_.numWellEq() + Indices::numEq, 0.0}; - calculateSinglePerf(ebosSimulator, perf, well_state, connectionRates, cq_s, water_flux_s, cq_s_zfrac_effective, deferred_logger); - - // Equation assembly for this perforation. - if constexpr (has_polymer && Base::has_polymermw) { - if (this->isInjector()) { - handleInjectivityEquations(ebosSimulator, well_state, perf, water_flux_s, deferred_logger); - } - } - const int cell_idx = this->well_cells_[perf]; - for (int componentIdx = 0; componentIdx < this->num_components_; ++componentIdx) { - // the cq_s entering mass balance equations need to consider the efficiency factors. - const EvalWell cq_s_effective = cq_s[componentIdx] * this->well_efficiency_factor_; - - connectionRates[perf][componentIdx] = Base::restrictEval(cq_s_effective); - - StandardWellAssemble(*this). - assemblePerforationEq(cq_s_effective, - componentIdx, - cell_idx, - this->primary_variables_.numWellEq(), - this->linSys_); - - // Store the perforation phase flux for later usage. - if (has_solvent && componentIdx == Indices::contiSolventEqIdx) { - auto& perf_rate_solvent = perf_data.solvent_rates; - perf_rate_solvent[perf] = cq_s[componentIdx].value(); - } else { - perf_rates[perf*np + this->ebosCompIdxToFlowCompIdx(componentIdx)] = cq_s[componentIdx].value(); - } - } - - if constexpr (has_zFraction) { - StandardWellAssemble(*this). - assembleZFracEq(cq_s_zfrac_effective, - cell_idx, - this->primary_variables_.numWellEq(), - this->linSys_); - } - } - // Update the connection - this->connectionRates_ = connectionRates; - - // Accumulate dissolved gas and vaporized oil flow rates across all - // ranks sharing this well (this->index_of_well_). - { - const auto& comm = this->parallel_well_info_.communication(); - comm.sum(ws.phase_mixing_rates.data(), ws.phase_mixing_rates.size()); - } - - // accumulate resWell_ and duneD_ in parallel to get effects of all perforations (might be distributed) - this->linSys_.sumDistributed(this->parallel_well_info_.communication()); - - // add vol * dF/dt + Q to the well equations; - for (int componentIdx = 0; componentIdx < numWellConservationEq; ++componentIdx) { - // TODO: following the development in MSW, we need to convert the volume of the wellbore to be surface volume - // since all the rates are under surface condition - EvalWell resWell_loc(this->primary_variables_.numWellEq() + Indices::numEq, 0.0); - if (FluidSystem::numActivePhases() > 1) { - assert(dt > 0); - resWell_loc += (this->primary_variables_.surfaceVolumeFraction(componentIdx) - - this->F0_[componentIdx]) * volume / dt; - } - resWell_loc -= this->primary_variables_.getQs(componentIdx) * this->well_efficiency_factor_; - StandardWellAssemble(*this). - assembleSourceEq(resWell_loc, - componentIdx, - this->primary_variables_.numWellEq(), - this->linSys_); - } - - const auto& summaryState = ebosSimulator.vanguard().summaryState(); - const Schedule& schedule = ebosSimulator.vanguard().schedule(); - StandardWellAssemble(*this). - assembleControlEq(well_state, group_state, - schedule, summaryState, - inj_controls, prod_controls, - this->primary_variables_, - this->connections_.rho(), - this->linSys_, - deferred_logger); - - - // do the local inversion of D. - try { - this->linSys_.invert(); - } catch( ... ) { - OPM_DEFLOG_THROW(NumericalProblem, "Error when inverting local well equations for well " + name(), deferred_logger); - } - } - - - - - template - void - StandardWell:: - calculateSinglePerf(const Simulator& ebosSimulator, - const int perf, - WellState& well_state, - std::vector& connectionRates, - std::vector& cq_s, - EvalWell& water_flux_s, - EvalWell& cq_s_zfrac_effective, - DeferredLogger& deferred_logger) const - { - const bool allow_cf = this->getAllowCrossFlow() || openCrossFlowAvoidSingularity(ebosSimulator); - const EvalWell& bhp = this->primary_variables_.eval(Bhp); - const int cell_idx = this->well_cells_[perf]; - const auto& intQuants = ebosSimulator.model().intensiveQuantities(cell_idx, /*timeIdx=*/ 0); - std::vector mob(this->num_components_, {this->primary_variables_.numWellEq() + Indices::numEq, 0.}); - getMobility(ebosSimulator, perf, mob, deferred_logger); - - PerforationRates perf_rates; - double trans_mult = ebosSimulator.problem().template rockCompTransMultiplier(intQuants, cell_idx); - const double Tw = this->wellIndex(perf) * trans_mult; - const double elapsed_time = ebosSimulator.time(); - - computePerfRate(intQuants, mob, bhp, Tw, perf, allow_cf, elapsed_time, - cq_s, perf_rates, deferred_logger); - - auto& ws = well_state.well(this->index_of_well_); - auto& perf_data = ws.perf_data; - if constexpr (has_polymer && Base::has_polymermw) { - if (this->isInjector()) { - // Store the original water flux computed from the reservoir quantities. - // It will be required to assemble the injectivity equations. - const unsigned water_comp_idx = Indices::canonicalToActiveComponentIndex(FluidSystem::waterCompIdx); - water_flux_s = cq_s[water_comp_idx]; - // Modify the water flux for the rest of this function to depend directly on the - // local water velocity primary variable. - handleInjectivityRate(ebosSimulator, perf, cq_s); - } - } - - // updating the solution gas rate and solution oil rate - if (this->isProducer()) { - ws.phase_mixing_rates[ws.dissolved_gas] += perf_rates.dis_gas; - ws.phase_mixing_rates[ws.dissolved_gas_in_water] += perf_rates.dis_gas_in_water; - ws.phase_mixing_rates[ws.vaporized_oil] += perf_rates.vap_oil; - ws.phase_mixing_rates[ws.vaporized_water] += perf_rates.vap_wat; - } - - if constexpr (has_energy) { - connectionRates[perf][Indices::contiEnergyEqIdx] = - connectionRateEnergy(ebosSimulator.problem().maxOilSaturation(cell_idx), - cq_s, intQuants, deferred_logger); - } - - if constexpr (has_polymer) { - std::variant polymerConcentration; - if (this->isInjector()) { - polymerConcentration = this->wpolymer(); - } else { - polymerConcentration = this->extendEval(intQuants.polymerConcentration() * - intQuants.polymerViscosityCorrection()); - } - - [[maybe_unused]] EvalWell cq_s_poly; - std::tie(connectionRates[perf][Indices::contiPolymerEqIdx], - cq_s_poly) = - this->connections_.connectionRatePolymer(perf_data.polymer_rates[perf], - cq_s, polymerConcentration); - - if constexpr (Base::has_polymermw) { - updateConnectionRatePolyMW(cq_s_poly, intQuants, well_state, - perf, connectionRates, deferred_logger); - } - } - - if constexpr (has_foam) { - std::variant foamConcentration; - if (this->isInjector()) { - foamConcentration = this->wfoam(); - } else { - foamConcentration = this->extendEval(intQuants.foamConcentration()); - } - connectionRates[perf][Indices::contiFoamEqIdx] = - this->connections_.connectionRateFoam(cq_s, foamConcentration, - FoamModule::transportPhase(), - deferred_logger); - } - - if constexpr (has_zFraction) { - std::variant> solventConcentration; - if (this->isInjector()) { - solventConcentration = this->wsolvent(); - } else { - solventConcentration = std::array{this->extendEval(intQuants.xVolume()), - this->extendEval(intQuants.yVolume())}; - } - std::tie(connectionRates[perf][Indices::contiZfracEqIdx], - cq_s_zfrac_effective) = - this->connections_.connectionRatezFraction(perf_data.solvent_rates[perf], - perf_rates.dis_gas, cq_s, - solventConcentration); - } - - if constexpr (has_brine) { - std::variant saltConcentration; - if (this->isInjector()) { - saltConcentration = this->wsalt(); - } else { - saltConcentration = this->extendEval(intQuants.fluidState().saltConcentration()); - } - - connectionRates[perf][Indices::contiBrineEqIdx] = - this->connections_.connectionRateBrine(perf_data.brine_rates[perf], - perf_rates.vap_wat, cq_s, - saltConcentration); - } - - if constexpr (has_micp) { - std::variant microbialConcentration; - std::variant oxygenConcentration; - std::variant ureaConcentration; - if (this->isInjector()) { - microbialConcentration = this->wmicrobes(); - oxygenConcentration = this->woxygen(); - ureaConcentration = this->wurea(); - } else { - microbialConcentration = this->extendEval(intQuants.microbialConcentration()); - oxygenConcentration = this->extendEval(intQuants.oxygenConcentration()); - ureaConcentration = this->extendEval(intQuants.ureaConcentration()); - } - std::tie(connectionRates[perf][Indices::contiMicrobialEqIdx], - connectionRates[perf][Indices::contiOxygenEqIdx], - connectionRates[perf][Indices::contiUreaEqIdx]) = - this->connections_.connectionRatesMICP(cq_s, - microbialConcentration, - oxygenConcentration, - ureaConcentration); - } - - // Store the perforation pressure for later usage. - perf_data.pressure[perf] = ws.bhp + this->connections_.pressure_diff(perf); - } - - - - template - template - void - StandardWell:: - getMobility(const Simulator& ebosSimulator, - const int perf, - std::vector& mob, - DeferredLogger& deferred_logger) const - { - auto obtain = [this](const Eval& value) - { - if constexpr (std::is_same_v) { - static_cast(this); // suppress clang warning - return getValue(value); - } else { - return this->extendEval(value); - } - }; - WellInterface::getMobility(ebosSimulator, perf, mob, - obtain, deferred_logger); - - // modify the water mobility if polymer is present - if constexpr (has_polymer) { - if (!FluidSystem::phaseIsActive(FluidSystem::waterPhaseIdx)) { - OPM_DEFLOG_THROW(std::runtime_error, "Water is required when polymer is active", deferred_logger); - } - - // for the cases related to polymer molecular weight, we assume fully mixing - // as a result, the polymer and water share the same viscosity - if constexpr (!Base::has_polymermw) { - if constexpr (std::is_same_v) { - std::vector mob_eval(this->num_components_, {this->primary_variables_.numWellEq() + Indices::numEq, 0.}); - for (size_t i = 0; i < mob.size(); ++i) - mob_eval[i].setValue(mob[i]); - updateWaterMobilityWithPolymer(ebosSimulator, perf, mob_eval, deferred_logger); - for (size_t i = 0; i < mob.size(); ++i) { - mob[i] = getValue(mob_eval[i]); - } - } else { - updateWaterMobilityWithPolymer(ebosSimulator, perf, mob, deferred_logger); - } - } - } - - // if the injecting well has WINJMULT setup, we update the mobility accordingly - if (this->isInjector() && this->well_ecl_.getInjMultMode() != Well::InjMultMode::NONE) { - const double bhp = this->primary_variables_.value(Bhp); - const double perf_press = bhp + this->connections_.pressure_diff(perf); - const double multiplier = this->getInjMult(perf, bhp, perf_press); - for (size_t i = 0; i < mob.size(); ++i) { - mob[i] *= multiplier; - } - } - } - - - template - void - StandardWell:: - updateWellState(const SummaryState& summary_state, - const BVectorWell& dwells, - WellState& well_state, - DeferredLogger& deferred_logger) - { - if (!this->isOperableAndSolvable() && !this->wellIsStopped()) return; - - const bool stop_or_zero_rate_target = this->stopppedOrZeroRateTarget(summary_state, well_state); - updatePrimaryVariablesNewton(dwells, stop_or_zero_rate_target, deferred_logger); - - updateWellStateFromPrimaryVariables(stop_or_zero_rate_target, well_state, summary_state, deferred_logger); - Base::calculateReservoirRates(well_state.well(this->index_of_well_)); - } - - - - - - template - void - StandardWell:: - updatePrimaryVariablesNewton(const BVectorWell& dwells, - const bool stop_or_zero_rate_target, - DeferredLogger& deferred_logger) - { - const double dFLimit = this->param_.dwell_fraction_max_; - const double dBHPLimit = this->param_.dbhp_max_rel_; - this->primary_variables_.updateNewton(dwells, stop_or_zero_rate_target, dFLimit, dBHPLimit); - - // for the water velocity and skin pressure - if constexpr (Base::has_polymermw) { - this->primary_variables_.updateNewtonPolyMW(dwells); - } - - this->primary_variables_.checkFinite(deferred_logger); - } - - - - - - template - void - StandardWell:: - updateWellStateFromPrimaryVariables(const bool stop_or_zero_rate_target, - WellState& well_state, - const SummaryState& summary_state, - DeferredLogger& deferred_logger) const - { - this->StdWellEval::updateWellStateFromPrimaryVariables(stop_or_zero_rate_target, well_state, summary_state, deferred_logger); - - // other primary variables related to polymer injectivity study - if constexpr (Base::has_polymermw) { - this->primary_variables_.copyToWellStatePolyMW(well_state); - } - } - - - - - - template - void - StandardWell:: - updateIPR(const Simulator& ebos_simulator, DeferredLogger& deferred_logger) const - { - // TODO: not handling solvent related here for now - - // initialize all the values to be zero to begin with - std::fill(this->ipr_a_.begin(), this->ipr_a_.end(), 0.); - std::fill(this->ipr_b_.begin(), this->ipr_b_.end(), 0.); - - for (int perf = 0; perf < this->number_of_perforations_; ++perf) { - std::vector mob(this->num_components_, 0.0); - getMobility(ebos_simulator, perf, mob, deferred_logger); - - const int cell_idx = this->well_cells_[perf]; - const auto& int_quantities = ebos_simulator.model().intensiveQuantities(cell_idx, /*timeIdx=*/ 0); - const auto& fs = int_quantities.fluidState(); - // the pressure of the reservoir grid block the well connection is in - double p_r = this->getPerfCellPressure(fs).value(); - - // calculating the b for the connection - std::vector b_perf(this->num_components_); - for (size_t phase = 0; phase < FluidSystem::numPhases; ++phase) { - if (!FluidSystem::phaseIsActive(phase)) { - continue; - } - const unsigned comp_idx = Indices::canonicalToActiveComponentIndex(FluidSystem::solventComponentIndex(phase)); - b_perf[comp_idx] = fs.invB(phase).value(); - } - if constexpr (has_solvent) { - b_perf[Indices::contiSolventEqIdx] = int_quantities.solventInverseFormationVolumeFactor().value(); - } - - // the pressure difference between the connection and BHP - const double h_perf = this->connections_.pressure_diff(perf); - const double pressure_diff = p_r - h_perf; - - // Let us add a check, since the pressure is calculated based on zero value BHP - // it should not be negative anyway. If it is negative, we might need to re-formulate - // to taking into consideration the crossflow here. - if ( (this->isProducer() && pressure_diff < 0.) || (this->isInjector() && pressure_diff > 0.) ) { - deferred_logger.debug("CROSSFLOW_IPR", - "cross flow found when updateIPR for well " + name() - + " . The connection is ignored in IPR calculations"); - // we ignore these connections for now - continue; - } - - // the well index associated with the connection - const double tw_perf = this->wellIndex(perf)*ebos_simulator.problem().template rockCompTransMultiplier(int_quantities, cell_idx); - - std::vector ipr_a_perf(this->ipr_a_.size()); - std::vector ipr_b_perf(this->ipr_b_.size()); - for (int comp_idx = 0; comp_idx < this->num_components_; ++comp_idx) { - const double tw_mob = tw_perf * mob[comp_idx] * b_perf[comp_idx]; - ipr_a_perf[comp_idx] += tw_mob * pressure_diff; - ipr_b_perf[comp_idx] += tw_mob; - } - - // we need to handle the rs and rv when both oil and gas are present - if (FluidSystem::phaseIsActive(FluidSystem::oilPhaseIdx) && FluidSystem::phaseIsActive(FluidSystem::gasPhaseIdx)) { - const unsigned oil_comp_idx = Indices::canonicalToActiveComponentIndex(FluidSystem::oilCompIdx); - const unsigned gas_comp_idx = Indices::canonicalToActiveComponentIndex(FluidSystem::gasCompIdx); - const double rs = (fs.Rs()).value(); - const double rv = (fs.Rv()).value(); - - const double dis_gas_a = rs * ipr_a_perf[oil_comp_idx]; - const double vap_oil_a = rv * ipr_a_perf[gas_comp_idx]; - - ipr_a_perf[gas_comp_idx] += dis_gas_a; - ipr_a_perf[oil_comp_idx] += vap_oil_a; - - const double dis_gas_b = rs * ipr_b_perf[oil_comp_idx]; - const double vap_oil_b = rv * ipr_b_perf[gas_comp_idx]; - - ipr_b_perf[gas_comp_idx] += dis_gas_b; - ipr_b_perf[oil_comp_idx] += vap_oil_b; - } - - for (size_t comp_idx = 0; comp_idx < ipr_a_perf.size(); ++comp_idx) { - this->ipr_a_[comp_idx] += ipr_a_perf[comp_idx]; - this->ipr_b_[comp_idx] += ipr_b_perf[comp_idx]; - } - } - this->parallel_well_info_.communication().sum(this->ipr_a_.data(), this->ipr_a_.size()); - this->parallel_well_info_.communication().sum(this->ipr_b_.data(), this->ipr_b_.size()); - } - - - template - void - StandardWell:: - checkOperabilityUnderBHPLimit(const WellState& well_state, const Simulator& ebos_simulator, DeferredLogger& deferred_logger) - { - const auto& summaryState = ebos_simulator.vanguard().summaryState(); - const double bhp_limit = WellBhpThpCalculator(*this).mostStrictBhpFromBhpLimits(summaryState); - // Crude but works: default is one atmosphere. - // TODO: a better way to detect whether the BHP is defaulted or not - const bool bhp_limit_not_defaulted = bhp_limit > 1.5 * unit::barsa; - if ( bhp_limit_not_defaulted || !this->wellHasTHPConstraints(summaryState) ) { - // if the BHP limit is not defaulted or the well does not have a THP limit - // we need to check the BHP limit - double total_ipr_mass_rate = 0.0; - for (unsigned phaseIdx = 0; phaseIdx < FluidSystem::numPhases; ++phaseIdx) - { - if (!FluidSystem::phaseIsActive(phaseIdx)) { - continue; - } - - const unsigned compIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::solventComponentIndex(phaseIdx)); - const double ipr_rate = this->ipr_a_[compIdx] - this->ipr_b_[compIdx] * bhp_limit; - - const double rho = FluidSystem::referenceDensity( phaseIdx, Base::pvtRegionIdx() ); - total_ipr_mass_rate += ipr_rate * rho; - } - if ( (this->isProducer() && total_ipr_mass_rate < 0.) || (this->isInjector() && total_ipr_mass_rate > 0.) ) { - this->operability_status_.operable_under_only_bhp_limit = false; - } - - // checking whether running under BHP limit will violate THP limit - if (this->operability_status_.operable_under_only_bhp_limit && this->wellHasTHPConstraints(summaryState)) { - // option 1: calculate well rates based on the BHP limit. - // option 2: stick with the above IPR curve - // we use IPR here - std::vector well_rates_bhp_limit; - computeWellRatesWithBhp(ebos_simulator, bhp_limit, well_rates_bhp_limit, deferred_logger); - - this->adaptRatesForVFP(well_rates_bhp_limit); - const double thp_limit = this->getTHPConstraint(summaryState); - const double thp = WellBhpThpCalculator(*this).calculateThpFromBhp(well_rates_bhp_limit, - bhp_limit, - this->connections_.rho(), - this->getALQ(well_state), - thp_limit, - deferred_logger); - if ( (this->isProducer() && thp < thp_limit) || (this->isInjector() && thp > thp_limit) ) { - this->operability_status_.obey_thp_limit_under_bhp_limit = false; - } - } - } else { - // defaulted BHP and there is a THP constraint - // default BHP limit is about 1 atm. - // when applied the hydrostatic pressure correction dp, - // most likely we get a negative value (bhp + dp)to search in the VFP table, - // which is not desirable. - // we assume we can operate under defaulted BHP limit and will violate the THP limit - // when operating under defaulted BHP limit. - this->operability_status_.operable_under_only_bhp_limit = true; - this->operability_status_.obey_thp_limit_under_bhp_limit = false; - } - } - - - - - - template - void - StandardWell:: - checkOperabilityUnderTHPLimit(const Simulator& ebos_simulator, const WellState& well_state, DeferredLogger& deferred_logger) - { - const auto& summaryState = ebos_simulator.vanguard().summaryState(); - const auto obtain_bhp = this->isProducer() ? computeBhpAtThpLimitProd(well_state, ebos_simulator, summaryState, deferred_logger) - : computeBhpAtThpLimitInj(ebos_simulator, summaryState, deferred_logger); - - if (obtain_bhp) { - this->operability_status_.can_obtain_bhp_with_thp_limit = true; - - const double bhp_limit = WellBhpThpCalculator(*this).mostStrictBhpFromBhpLimits(summaryState); - this->operability_status_.obey_bhp_limit_with_thp_limit = this->isProducer() ? - *obtain_bhp >= bhp_limit : *obtain_bhp <= bhp_limit ; - - const double thp_limit = this->getTHPConstraint(summaryState); - if (this->isProducer() && *obtain_bhp < thp_limit) { - const std::string msg = " obtained bhp " + std::to_string(unit::convert::to(*obtain_bhp, unit::barsa)) - + " bars is SMALLER than thp limit " - + std::to_string(unit::convert::to(thp_limit, unit::barsa)) - + " bars as a producer for well " + name(); - deferred_logger.debug(msg); - } - else if (this->isInjector() && *obtain_bhp > thp_limit) { - const std::string msg = " obtained bhp " + std::to_string(unit::convert::to(*obtain_bhp, unit::barsa)) - + " bars is LARGER than thp limit " - + std::to_string(unit::convert::to(thp_limit, unit::barsa)) - + " bars as a injector for well " + name(); - deferred_logger.debug(msg); - } - } else { - this->operability_status_.can_obtain_bhp_with_thp_limit = false; - this->operability_status_.obey_bhp_limit_with_thp_limit = false; - if (!this->wellIsStopped()) { - const double thp_limit = this->getTHPConstraint(summaryState); - deferred_logger.debug(" could not find bhp value at thp limit " - + std::to_string(unit::convert::to(thp_limit, unit::barsa)) - + " bar for well " + name() + ", the well might need to be closed "); - } - } - } - - - - - - template - bool - StandardWell:: - allDrawDownWrongDirection(const Simulator& ebos_simulator) const - { - bool all_drawdown_wrong_direction = true; - - for (int perf = 0; perf < this->number_of_perforations_; ++perf) { - const int cell_idx = this->well_cells_[perf]; - const auto& intQuants = ebos_simulator.model().intensiveQuantities(cell_idx, /*timeIdx=*/0); - const auto& fs = intQuants.fluidState(); - - const double pressure = this->getPerfCellPressure(fs).value(); - const double bhp = this->primary_variables_.eval(Bhp).value(); - - // Pressure drawdown (also used to determine direction of flow) - const double well_pressure = bhp + this->connections_.pressure_diff(perf); - const double drawdown = pressure - well_pressure; - - // for now, if there is one perforation can produce/inject in the correct - // direction, we consider this well can still produce/inject. - // TODO: it can be more complicated than this to cause wrong-signed rates - if ( (drawdown < 0. && this->isInjector()) || - (drawdown > 0. && this->isProducer()) ) { - all_drawdown_wrong_direction = false; - break; - } - } - - const auto& comm = this->parallel_well_info_.communication(); - if (comm.size() > 1) - { - all_drawdown_wrong_direction = - (comm.min(all_drawdown_wrong_direction ? 1 : 0) == 1); - } - - return all_drawdown_wrong_direction; - } - - - - - template - bool - StandardWell:: - canProduceInjectWithCurrentBhp(const Simulator& ebos_simulator, - const WellState& well_state, - DeferredLogger& deferred_logger) - { - const double bhp = well_state.well(this->index_of_well_).bhp; - std::vector well_rates; - computeWellRatesWithBhp(ebos_simulator, bhp, well_rates, deferred_logger); - - const double sign = (this->isProducer()) ? -1. : 1.; - const double threshold = sign * std::numeric_limits::min(); - - bool can_produce_inject = false; - for (const auto value : well_rates) { - if (this->isProducer() && value < threshold) { - can_produce_inject = true; - break; - } else if (this->isInjector() && value > threshold) { - can_produce_inject = true; - break; - } - } - - if (!can_produce_inject) { - deferred_logger.debug(" well " + name() + " CANNOT produce or inejct "); - } - - return can_produce_inject; - } - - - - - - template - bool - StandardWell:: - openCrossFlowAvoidSingularity(const Simulator& ebos_simulator) const - { - return !this->getAllowCrossFlow() && allDrawDownWrongDirection(ebos_simulator); - } - - - - - template - void - StandardWell:: - computePropertiesForWellConnectionPressures(const Simulator& ebosSimulator, - const WellState& well_state, - WellConnectionProps& props) const - { - std::function getTemperature = - [&ebosSimulator](int cell_idx, int phase_idx) - { - return ebosSimulator.model().intensiveQuantities(cell_idx, 0).fluidState().temperature(phase_idx).value(); - }; - std::function getSaltConcentration = - [&ebosSimulator](int cell_idx) - { - return ebosSimulator.model().intensiveQuantities(cell_idx, 0).fluidState().saltConcentration().value(); - }; - std::function getPvtRegionIdx = - [&ebosSimulator](int cell_idx) - { - return ebosSimulator.model().intensiveQuantities(cell_idx, 0).fluidState().pvtRegionIndex(); - }; - std::function getInvFac = - [&ebosSimulator](int cell_idx) - { - return ebosSimulator.model().intensiveQuantities(cell_idx, 0).solventInverseFormationVolumeFactor().value(); - }; - std::function getSolventDensity = - [&ebosSimulator](int cell_idx) - { - return ebosSimulator.model().intensiveQuantities(cell_idx, 0).solventRefDensity(); - }; - - this->connections_.computePropertiesForPressures(well_state, - getTemperature, - getSaltConcentration, - getPvtRegionIdx, - getInvFac, - getSolventDensity, - props); - } - - - - - - template - ConvergenceReport - StandardWell:: - getWellConvergence(const SummaryState& summary_state, - const WellState& well_state, - const std::vector& B_avg, - DeferredLogger& deferred_logger, - const bool relax_tolerance) const - { - // the following implementation assume that the polymer is always after the w-o-g phases - // For the polymer, energy and foam cases, there is one more mass balance equations of reservoir than wells - assert((int(B_avg.size()) == this->num_components_) || has_polymer || has_energy || has_foam || has_brine || has_zFraction || has_micp); - - // using sricter tolerance for stopped wells and wells under zero rate target control. - constexpr double stricter_factor = 1.e-4; - const double tol_wells = this->stopppedOrZeroRateTarget(summary_state, well_state) ? - this->param_.tolerance_wells_ * stricter_factor : this->param_.tolerance_wells_; - - std::vector res; - ConvergenceReport report = this->StdWellEval::getWellConvergence(well_state, - B_avg, - this->param_.max_residual_allowed_, - tol_wells, - this->param_.relaxed_tolerance_flow_well_, - relax_tolerance, - res, - deferred_logger); - - checkConvergenceExtraEqs(res, report); - - return report; - } - - - - - - template - void - StandardWell:: - updateProductivityIndex(const Simulator& ebosSimulator, - const WellProdIndexCalculator& wellPICalc, - WellState& well_state, - DeferredLogger& deferred_logger) const - { - auto fluidState = [&ebosSimulator, this](const int perf) - { - const auto cell_idx = this->well_cells_[perf]; - return ebosSimulator.model() - .intensiveQuantities(cell_idx, /*timeIdx=*/ 0).fluidState(); - }; - - const int np = this->number_of_phases_; - auto setToZero = [np](double* x) -> void - { - std::fill_n(x, np, 0.0); - }; - - auto addVector = [np](const double* src, double* dest) -> void - { - std::transform(src, src + np, dest, dest, std::plus<>{}); - }; - - auto& ws = well_state.well(this->index_of_well_); - auto& perf_data = ws.perf_data; - auto* wellPI = ws.productivity_index.data(); - auto* connPI = perf_data.prod_index.data(); - - setToZero(wellPI); - - const auto preferred_phase = this->well_ecl_.getPreferredPhase(); - auto subsetPerfID = 0; - - for (const auto& perf : *this->perf_data_) { - auto allPerfID = perf.ecl_index; - - auto connPICalc = [&wellPICalc, allPerfID](const double mobility) -> double - { - return wellPICalc.connectionProdIndStandard(allPerfID, mobility); - }; - - std::vector mob(this->num_components_, 0.0); - getMobility(ebosSimulator, static_cast(subsetPerfID), mob, deferred_logger); - - const auto& fs = fluidState(subsetPerfID); - setToZero(connPI); - - if (this->isInjector()) { - this->computeConnLevelInjInd(fs, preferred_phase, connPICalc, - mob, connPI, deferred_logger); - } - else { // Production or zero flow rate - this->computeConnLevelProdInd(fs, connPICalc, mob, connPI); - } - - addVector(connPI, wellPI); - - ++subsetPerfID; - connPI += np; - } - - // Sum with communication in case of distributed well. - const auto& comm = this->parallel_well_info_.communication(); - if (comm.size() > 1) { - comm.sum(wellPI, np); - } - - assert ((static_cast(subsetPerfID) == this->number_of_perforations_) && - "Internal logic error in processing connections for PI/II"); - } - - - - template - void - StandardWell:: - computeWellConnectionDensitesPressures(const Simulator& ebosSimulator, - const WellState& well_state, - const WellConnectionProps& props, - DeferredLogger& deferred_logger) - { - std::function invB = - [&ebosSimulator](int cell_idx, int phase_idx) - { - return ebosSimulator.model().intensiveQuantities(cell_idx, 0).fluidState().invB(phase_idx).value(); - }; - std::function mobility = - [&ebosSimulator](int cell_idx, int phase_idx) - { - return ebosSimulator.model().intensiveQuantities(cell_idx, 0).mobility(phase_idx).value(); - }; - std::function invFac = - [&ebosSimulator](int cell_idx) - { - return ebosSimulator.model().intensiveQuantities(cell_idx, 0).solventInverseFormationVolumeFactor().value(); - }; - std::function solventMobility = - [&ebosSimulator](int cell_idx) - { - return ebosSimulator.model().intensiveQuantities(cell_idx, 0).solventMobility().value(); - }; - - this->connections_.computeProperties(well_state, - invB, - mobility, - invFac, - solventMobility, - props, - deferred_logger); - } - - - - - - template - void - StandardWell:: - computeWellConnectionPressures(const Simulator& ebosSimulator, - const WellState& well_state, - DeferredLogger& deferred_logger) - { - // 1. Compute properties required by computePressureDelta(). - // Note that some of the complexity of this part is due to the function - // taking std::vector arguments, and not Eigen objects. - WellConnectionProps props; - computePropertiesForWellConnectionPressures(ebosSimulator, well_state, props); - computeWellConnectionDensitesPressures(ebosSimulator, well_state, - props, deferred_logger); - } - - - - - - template - void - StandardWell:: - solveEqAndUpdateWellState(const SummaryState& summary_state, - WellState& well_state, - DeferredLogger& deferred_logger) - { - if (!this->isOperableAndSolvable() && !this->wellIsStopped()) return; - - // We assemble the well equations, then we check the convergence, - // which is why we do not put the assembleWellEq here. - BVectorWell dx_well(1); - dx_well[0].resize(this->primary_variables_.numWellEq()); - this->linSys_.solve( dx_well); - - updateWellState(summary_state, dx_well, well_state, deferred_logger); - } - - - - - - template - void - StandardWell:: - calculateExplicitQuantities(const Simulator& ebosSimulator, - const WellState& well_state, - DeferredLogger& deferred_logger) - { - const auto& summary_state = ebosSimulator.vanguard().summaryState(); - updatePrimaryVariables(summary_state, well_state, deferred_logger); - initPrimaryVariablesEvaluation(); - computeWellConnectionPressures(ebosSimulator, well_state, deferred_logger); - this->computeAccumWell(); - } - - - - template - void - StandardWell:: - apply(const BVector& x, BVector& Ax) const - { - if (!this->isOperableAndSolvable() && !this->wellIsStopped()) return; - - if (this->param_.matrix_add_well_contributions_) - { - // Contributions are already in the matrix itself - return; - } - - this->linSys_.apply(x, Ax); - } - - - - - template - void - StandardWell:: - apply(BVector& r) const - { - if (!this->isOperableAndSolvable() && !this->wellIsStopped()) return; - - this->linSys_.apply(r); - } - - - - - template - void - StandardWell:: - recoverWellSolutionAndUpdateWellState(const SummaryState& summary_state, - const BVector& x, - WellState& well_state, - DeferredLogger& deferred_logger) - { - if (!this->isOperableAndSolvable() && !this->wellIsStopped()) return; - - BVectorWell xw(1); - xw[0].resize(this->primary_variables_.numWellEq()); - - this->linSys_.recoverSolutionWell(x, xw); - updateWellState(summary_state, xw, well_state, deferred_logger); - } - - - - - template - void - StandardWell:: - computeWellRatesWithBhp(const Simulator& ebosSimulator, - const double& bhp, - std::vector& well_flux, - DeferredLogger& deferred_logger) const - { - - const int np = this->number_of_phases_; - well_flux.resize(np, 0.0); - - const bool allow_cf = this->getAllowCrossFlow(); - - for (int perf = 0; perf < this->number_of_perforations_; ++perf) { - const int cell_idx = this->well_cells_[perf]; - const auto& intQuants = ebosSimulator.model().intensiveQuantities(cell_idx, /*timeIdx=*/ 0); - // flux for each perforation - std::vector mob(this->num_components_, 0.); - getMobility(ebosSimulator, perf, mob, deferred_logger); - double trans_mult = ebosSimulator.problem().template rockCompTransMultiplier(intQuants, cell_idx); - const double Tw = this->wellIndex(perf) * trans_mult; - const double elapsed_time = ebosSimulator.time(); - - std::vector cq_s(this->num_components_, 0.); - PerforationRates perf_rates; - computePerfRate(intQuants, mob, bhp, Tw, perf, allow_cf, elapsed_time, - cq_s, perf_rates, deferred_logger); - - for(int p = 0; p < np; ++p) { - well_flux[this->ebosCompIdxToFlowCompIdx(p)] += cq_s[p]; - } - } - this->parallel_well_info_.communication().sum(well_flux.data(), well_flux.size()); - } - - - - template - void - StandardWell:: - computeWellRatesWithBhpIterations(const Simulator& ebosSimulator, - const double& bhp, - std::vector& well_flux, - DeferredLogger& deferred_logger) const - { - // creating a copy of the well itself, to avoid messing up the explicit information - // during this copy, the only information not copied properly is the well controls - StandardWell well_copy(*this); - - // iterate to get a more accurate well density - // create a copy of the well_state to use. If the operability checking is sucessful, we use this one - // to replace the original one - WellState well_state_copy = ebosSimulator.problem().wellModel().wellState(); - const auto& group_state = ebosSimulator.problem().wellModel().groupState(); - - // Get the current controls. - const auto& summary_state = ebosSimulator.vanguard().summaryState(); - auto inj_controls = well_copy.well_ecl_.isInjector() - ? well_copy.well_ecl_.injectionControls(summary_state) - : Well::InjectionControls(0); - auto prod_controls = well_copy.well_ecl_.isProducer() - ? well_copy.well_ecl_.productionControls(summary_state) : - Well::ProductionControls(0); - - // Set current control to bhp, and bhp value in state, modify bhp limit in control object. - auto& ws = well_state_copy.well(this->index_of_well_); - if (well_copy.well_ecl_.isInjector()) { - inj_controls.bhp_limit = bhp; - ws.injection_cmode = Well::InjectorCMode::BHP; - } else { - prod_controls.bhp_limit = bhp; - ws.production_cmode = Well::ProducerCMode::BHP; - } - ws.bhp = bhp; - - // initialized the well rates with the potentials i.e. the well rates based on bhp - const int np = this->number_of_phases_; - const double sign = this->well_ecl_.isInjector() ? 1.0 : -1.0; - for (int phase = 0; phase < np; ++phase){ - well_state_copy.wellRates(this->index_of_well_)[phase] - = sign * ws.well_potentials[phase]; - } - well_copy.calculateExplicitQuantities(ebosSimulator, well_state_copy, deferred_logger); - - const double dt = ebosSimulator.timeStepSize(); - const bool converged = well_copy.iterateWellEqWithControl(ebosSimulator, dt, inj_controls, prod_controls, well_state_copy, group_state, deferred_logger); - if (!converged) { - const std::string msg = " well " + name() + " did not get converged during well potential calculations " - " potentials are computed based on unconverged solution"; - deferred_logger.debug(msg); - } - well_copy.updatePrimaryVariables(summary_state, well_state_copy, deferred_logger); - well_copy.computeWellConnectionPressures(ebosSimulator, well_state_copy, deferred_logger); - well_copy.initPrimaryVariablesEvaluation(); - well_copy.computeWellRatesWithBhp(ebosSimulator, bhp, well_flux, deferred_logger); - } - - - - - template - std::vector - StandardWell:: - computeWellPotentialWithTHP(const Simulator& ebos_simulator, - DeferredLogger& deferred_logger, - const WellState &well_state) const - { - std::vector potentials(this->number_of_phases_, 0.0); - const auto& summary_state = ebos_simulator.vanguard().summaryState(); - - const auto& well = this->well_ecl_; - if (well.isInjector()){ - const auto& controls = this->well_ecl_.injectionControls(summary_state); - auto bhp_at_thp_limit = computeBhpAtThpLimitInj(ebos_simulator, summary_state, deferred_logger); - if (bhp_at_thp_limit) { - const double bhp = std::min(*bhp_at_thp_limit, controls.bhp_limit); - computeWellRatesWithBhp(ebos_simulator, bhp, potentials, deferred_logger); - } else { - deferred_logger.warning("FAILURE_GETTING_CONVERGED_POTENTIAL", - "Failed in getting converged thp based potential calculation for well " - + name() + ". Instead the bhp based value is used"); - const double bhp = controls.bhp_limit; - computeWellRatesWithBhp(ebos_simulator, bhp, potentials, deferred_logger); - } - } else { - computeWellRatesWithThpAlqProd( - ebos_simulator, summary_state, - deferred_logger, potentials, this->getALQ(well_state) - ); - } - - return potentials; - } - - - - template - double - StandardWell:: - computeWellRatesAndBhpWithThpAlqProd(const Simulator &ebos_simulator, - const SummaryState &summary_state, - DeferredLogger &deferred_logger, - std::vector &potentials, - double alq) const - { - double bhp; - auto bhp_at_thp_limit = computeBhpAtThpLimitProdWithAlq( - ebos_simulator, summary_state, alq, deferred_logger); - if (bhp_at_thp_limit) { - const auto& controls = this->well_ecl_.productionControls(summary_state); - bhp = std::max(*bhp_at_thp_limit, controls.bhp_limit); - computeWellRatesWithBhp(ebos_simulator, bhp, potentials, deferred_logger); - } - else { - deferred_logger.warning("FAILURE_GETTING_CONVERGED_POTENTIAL", - "Failed in getting converged thp based potential calculation for well " - + name() + ". Instead the bhp based value is used"); - const auto& controls = this->well_ecl_.productionControls(summary_state); - bhp = controls.bhp_limit; - computeWellRatesWithBhp(ebos_simulator, bhp, potentials, deferred_logger); - } - return bhp; - } - - template - void - StandardWell:: - computeWellRatesWithThpAlqProd(const Simulator &ebos_simulator, - const SummaryState &summary_state, - DeferredLogger &deferred_logger, - std::vector &potentials, - double alq) const - { - /*double bhp =*/ - computeWellRatesAndBhpWithThpAlqProd(ebos_simulator, - summary_state, - deferred_logger, - potentials, - alq); - } - - template - void - StandardWell:: - computeWellPotentials(const Simulator& ebosSimulator, - const WellState& well_state, - std::vector& well_potentials, - DeferredLogger& deferred_logger) // const - { - const auto [compute_potential, bhp_controlled_well] = - this->WellInterfaceGeneric::computeWellPotentials(well_potentials, well_state); - - if (!compute_potential) { - return; - } - - // does the well have a THP related constraint? - const auto& summaryState = ebosSimulator.vanguard().summaryState(); - if (!Base::wellHasTHPConstraints(summaryState) || bhp_controlled_well) { - // get the bhp value based on the bhp constraints - double bhp = WellBhpThpCalculator(*this).mostStrictBhpFromBhpLimits(summaryState); - - // In some very special cases the bhp pressure target are - // temporary violated. This may lead to too small or negative potentials - // that could lead to premature shutting of wells. - // As a remedy the bhp that gives the largest potential is used. - // For converged cases, ws.bhp <=bhp for injectors and ws.bhp >= bhp, - // and the potentials will be computed using the limit as expected. - const auto& ws = well_state.well(this->index_of_well_); - if (this->isInjector()) - bhp = std::max(ws.bhp, bhp); - else - bhp = std::min(ws.bhp, bhp); - - assert(std::abs(bhp) != std::numeric_limits::max()); - computeWellRatesWithBhpIterations(ebosSimulator, bhp, well_potentials, deferred_logger); - } else { - // the well has a THP related constraint - well_potentials = computeWellPotentialWithTHP(ebosSimulator, deferred_logger, well_state); - } - - this->checkNegativeWellPotentials(well_potentials, - this->param_.check_well_operability_, - deferred_logger); - } - - - - - - - - template - double - StandardWell:: - connectionDensity([[maybe_unused]] const int globalConnIdx, - const int openConnIdx) const - { - return (openConnIdx < 0) - ? 0.0 - : this->connections_.rho(openConnIdx); - } - - - - - - template - void - StandardWell:: - updatePrimaryVariables(const SummaryState& summary_state, - const WellState& well_state, - DeferredLogger& deferred_logger) - { - if (!this->isOperableAndSolvable() && !this->wellIsStopped()) return; - - const bool stop_or_zero_rate_target = this->stopppedOrZeroRateTarget(summary_state, well_state); - this->primary_variables_.update(well_state, stop_or_zero_rate_target, deferred_logger); - - // other primary variables related to polymer injection - if constexpr (Base::has_polymermw) { - this->primary_variables_.updatePolyMW(well_state); - } - - this->primary_variables_.checkFinite(deferred_logger); - } - - - - - template - double - StandardWell:: - getRefDensity() const - { - return this->connections_.rho(); - } - - - - - template - void - StandardWell:: - updateWaterMobilityWithPolymer(const Simulator& ebos_simulator, - const int perf, - std::vector& mob, - DeferredLogger& deferred_logger) const - { - const int cell_idx = this->well_cells_[perf]; - const auto& int_quant = ebos_simulator.model().intensiveQuantities(cell_idx, /*timeIdx=*/ 0); - const EvalWell polymer_concentration = this->extendEval(int_quant.polymerConcentration()); - - // TODO: not sure should based on the well type or injecting/producing peforations - // it can be different for crossflow - if (this->isInjector()) { - // assume fully mixing within injecting wellbore - const auto& visc_mult_table = PolymerModule::plyviscViscosityMultiplierTable(int_quant.pvtRegionIndex()); - const unsigned waterCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::waterCompIdx); - mob[waterCompIdx] /= (this->extendEval(int_quant.waterViscosityCorrection()) * visc_mult_table.eval(polymer_concentration, /*extrapolate=*/true) ); - } - - if (PolymerModule::hasPlyshlog()) { - // we do not calculate the shear effects for injection wells when they do not - // inject polymer. - if (this->isInjector() && this->wpolymer() == 0.) { - return; - } - // compute the well water velocity with out shear effects. - // TODO: do we need to turn on crossflow here? - const bool allow_cf = this->getAllowCrossFlow() || openCrossFlowAvoidSingularity(ebos_simulator); - const EvalWell& bhp = this->primary_variables_.eval(Bhp); - - std::vector cq_s(this->num_components_, {this->primary_variables_.numWellEq() + Indices::numEq, 0.}); - PerforationRates perf_rates; - double trans_mult = ebos_simulator.problem().template rockCompTransMultiplier(int_quant, cell_idx); - const double elapsed_time = ebos_simulator.time(); - const double Tw = this->wellIndex(perf) * trans_mult; - computePerfRate(int_quant, mob, bhp, Tw, perf, allow_cf, elapsed_time, cq_s, - perf_rates, deferred_logger); - // TODO: make area a member - const double area = 2 * M_PI * this->perf_rep_radius_[perf] * this->perf_length_[perf]; - const auto& material_law_manager = ebos_simulator.problem().materialLawManager(); - const auto& scaled_drainage_info = - material_law_manager->oilWaterScaledEpsInfoDrainage(cell_idx); - const double swcr = scaled_drainage_info.Swcr; - const EvalWell poro = this->extendEval(int_quant.porosity()); - const EvalWell sw = this->extendEval(int_quant.fluidState().saturation(FluidSystem::waterPhaseIdx)); - // guard against zero porosity and no water - const EvalWell denom = max( (area * poro * (sw - swcr)), 1e-12); - const unsigned waterCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::waterCompIdx); - EvalWell water_velocity = cq_s[waterCompIdx] / denom * this->extendEval(int_quant.fluidState().invB(FluidSystem::waterPhaseIdx)); - - if (PolymerModule::hasShrate()) { - // the equation for the water velocity conversion for the wells and reservoir are from different version - // of implementation. It can be changed to be more consistent when possible. - water_velocity *= PolymerModule::shrate( int_quant.pvtRegionIndex() ) / this->bore_diameters_[perf]; - } - const EvalWell shear_factor = PolymerModule::computeShearFactor(polymer_concentration, - int_quant.pvtRegionIndex(), - water_velocity); - // modify the mobility with the shear factor. - mob[waterCompIdx] /= shear_factor; - } - } - - template - void - StandardWell::addWellContributions(SparseMatrixAdapter& jacobian) const - { - this->linSys_.extract(jacobian); - } - - - template - void - StandardWell::addWellPressureEquations(PressureMatrix& jacobian, - const BVector& weights, - const int pressureVarIndex, - const bool use_well_weights, - const WellState& well_state) const - { - this->linSys_.extractCPRPressureMatrix(jacobian, - weights, - pressureVarIndex, - use_well_weights, - *this, - Bhp, - well_state); - } - - - - template - typename StandardWell::EvalWell - StandardWell:: - pskinwater(const double throughput, - const EvalWell& water_velocity, - DeferredLogger& deferred_logger) const - { - if constexpr (Base::has_polymermw) { - const int water_table_id = this->polymerWaterTable_(); - if (water_table_id <= 0) { - OPM_DEFLOG_THROW(std::runtime_error, - fmt::format("Unused SKPRWAT table id used for well {}", name()), - deferred_logger); - } - const auto& water_table_func = PolymerModule::getSkprwatTable(water_table_id); - const EvalWell throughput_eval(this->primary_variables_.numWellEq() + Indices::numEq, throughput); - // the skin pressure when injecting water, which also means the polymer concentration is zero - EvalWell pskin_water(this->primary_variables_.numWellEq() + Indices::numEq, 0.0); - pskin_water = water_table_func.eval(throughput_eval, water_velocity); - return pskin_water; - } else { - OPM_DEFLOG_THROW(std::runtime_error, - fmt::format("Polymermw is not activated, while injecting " - "skin pressure is requested for well {}", name()), - deferred_logger); - } - } - - - - - - template - typename StandardWell::EvalWell - StandardWell:: - pskin(const double throughput, - const EvalWell& water_velocity, - const EvalWell& poly_inj_conc, - DeferredLogger& deferred_logger) const - { - if constexpr (Base::has_polymermw) { - const double sign = water_velocity >= 0. ? 1.0 : -1.0; - const EvalWell water_velocity_abs = abs(water_velocity); - if (poly_inj_conc == 0.) { - return sign * pskinwater(throughput, water_velocity_abs, deferred_logger); - } - const int polymer_table_id = this->polymerTable_(); - if (polymer_table_id <= 0) { - OPM_DEFLOG_THROW(std::runtime_error, - fmt::format("Unavailable SKPRPOLY table id used for well {}", name()), - deferred_logger); - } - const auto& skprpolytable = PolymerModule::getSkprpolyTable(polymer_table_id); - const double reference_concentration = skprpolytable.refConcentration; - const EvalWell throughput_eval(this->primary_variables_.numWellEq() + Indices::numEq, throughput); - // the skin pressure when injecting water, which also means the polymer concentration is zero - EvalWell pskin_poly(this->primary_variables_.numWellEq() + Indices::numEq, 0.0); - pskin_poly = skprpolytable.table_func.eval(throughput_eval, water_velocity_abs); - if (poly_inj_conc == reference_concentration) { - return sign * pskin_poly; - } - // poly_inj_conc != reference concentration of the table, then some interpolation will be required - const EvalWell pskin_water = pskinwater(throughput, water_velocity_abs, deferred_logger); - const EvalWell pskin = pskin_water + (pskin_poly - pskin_water) / reference_concentration * poly_inj_conc; - return sign * pskin; - } else { - OPM_DEFLOG_THROW(std::runtime_error, - fmt::format("Polymermw is not activated, while injecting " - "skin pressure is requested for well {}", name()), - deferred_logger); - } - } - - - - - - template - typename StandardWell::EvalWell - StandardWell:: - wpolymermw(const double throughput, - const EvalWell& water_velocity, - DeferredLogger& deferred_logger) const - { - if constexpr (Base::has_polymermw) { - const int table_id = this->polymerInjTable_(); - const auto& table_func = PolymerModule::getPlymwinjTable(table_id); - const EvalWell throughput_eval(this->primary_variables_.numWellEq() + Indices::numEq, throughput); - EvalWell molecular_weight(this->primary_variables_.numWellEq() + Indices::numEq, 0.); - if (this->wpolymer() == 0.) { // not injecting polymer - return molecular_weight; - } - molecular_weight = table_func.eval(throughput_eval, abs(water_velocity)); - return molecular_weight; - } else { - OPM_DEFLOG_THROW(std::runtime_error, - fmt::format("Polymermw is not activated, while injecting " - "polymer molecular weight is requested for well {}", name()), - deferred_logger); - } - } - - - - - - template - void - StandardWell:: - updateWaterThroughput(const double dt, WellState &well_state) const - { - if constexpr (Base::has_polymermw) { - if (this->isInjector()) { - auto& ws = well_state.well(this->index_of_well_); - auto& perf_water_throughput = ws.perf_data.water_throughput; - for (int perf = 0; perf < this->number_of_perforations_; ++perf) { - const double perf_water_vel = this->primary_variables_.value(Bhp + 1 + perf); - // we do not consider the formation damage due to water flowing from reservoir into wellbore - if (perf_water_vel > 0.) { - perf_water_throughput[perf] += perf_water_vel * dt; - } - } - } - } - } - - - - - - template - void - StandardWell:: - handleInjectivityRate(const Simulator& ebosSimulator, - const int perf, - std::vector& cq_s) const - { - const int cell_idx = this->well_cells_[perf]; - const auto& int_quants = ebosSimulator.model().intensiveQuantities(cell_idx, /*timeIdx=*/ 0); - const auto& fs = int_quants.fluidState(); - const EvalWell b_w = this->extendEval(fs.invB(FluidSystem::waterPhaseIdx)); - const double area = M_PI * this->bore_diameters_[perf] * this->perf_length_[perf]; - const int wat_vel_index = Bhp + 1 + perf; - const unsigned water_comp_idx = Indices::canonicalToActiveComponentIndex(FluidSystem::waterCompIdx); - - // water rate is update to use the form from water velocity, since water velocity is - // a primary variable now - cq_s[water_comp_idx] = area * this->primary_variables_.eval(wat_vel_index) * b_w; - } - - - - - template - void - StandardWell:: - handleInjectivityEquations(const Simulator& ebosSimulator, - const WellState& well_state, - const int perf, - const EvalWell& water_flux_s, - DeferredLogger& deferred_logger) - { - const int cell_idx = this->well_cells_[perf]; - const auto& int_quants = ebosSimulator.model().intensiveQuantities(cell_idx, /*timeIdx=*/ 0); - const auto& fs = int_quants.fluidState(); - const EvalWell b_w = this->extendEval(fs.invB(FluidSystem::waterPhaseIdx)); - const EvalWell water_flux_r = water_flux_s / b_w; - const double area = M_PI * this->bore_diameters_[perf] * this->perf_length_[perf]; - const EvalWell water_velocity = water_flux_r / area; - const int wat_vel_index = Bhp + 1 + perf; - - // equation for the water velocity - const EvalWell eq_wat_vel = this->primary_variables_.eval(wat_vel_index) - water_velocity; - - const auto& ws = well_state.well(this->index_of_well_); - const auto& perf_data = ws.perf_data; - const auto& perf_water_throughput = perf_data.water_throughput; - const double throughput = perf_water_throughput[perf]; - const int pskin_index = Bhp + 1 + this->number_of_perforations_ + perf; - - EvalWell poly_conc(this->primary_variables_.numWellEq() + Indices::numEq, 0.0); - poly_conc.setValue(this->wpolymer()); - - // equation for the skin pressure - const EvalWell eq_pskin = this->primary_variables_.eval(pskin_index) - - pskin(throughput, this->primary_variables_.eval(wat_vel_index), poly_conc, deferred_logger); - - StandardWellAssemble(*this). - assembleInjectivityEq(eq_pskin, - eq_wat_vel, - pskin_index, - wat_vel_index, - cell_idx, - this->primary_variables_.numWellEq(), - this->linSys_); - } - - - - - - template - void - StandardWell:: - checkConvergenceExtraEqs(const std::vector& res, - ConvergenceReport& report) const - { - // if different types of extra equations are involved, this function needs to be refactored further - - // checking the convergence of the extra equations related to polymer injectivity - if constexpr (Base::has_polymermw) { - WellConvergence(*this). - checkConvergencePolyMW(res, Bhp, this->param_.max_residual_allowed_, report); - } - } - - - - - - template - void - StandardWell:: - updateConnectionRatePolyMW(const EvalWell& cq_s_poly, - const IntensiveQuantities& int_quants, - const WellState& well_state, - const int perf, - std::vector& connectionRates, - DeferredLogger& deferred_logger) const - { - // the source term related to transport of molecular weight - EvalWell cq_s_polymw = cq_s_poly; - if (this->isInjector()) { - const int wat_vel_index = Bhp + 1 + perf; - const EvalWell water_velocity = this->primary_variables_.eval(wat_vel_index); - if (water_velocity > 0.) { // injecting - const auto& ws = well_state.well(this->index_of_well_); - const auto& perf_water_throughput = ws.perf_data.water_throughput; - const double throughput = perf_water_throughput[perf]; - const EvalWell molecular_weight = wpolymermw(throughput, water_velocity, deferred_logger); - cq_s_polymw *= molecular_weight; - } else { - // we do not consider the molecular weight from the polymer - // going-back to the wellbore through injector - cq_s_polymw *= 0.; - } - } else if (this->isProducer()) { - if (cq_s_polymw < 0.) { - cq_s_polymw *= this->extendEval(int_quants.polymerMoleWeight() ); - } else { - // we do not consider the molecular weight from the polymer - // re-injecting back through producer - cq_s_polymw *= 0.; - } - } - connectionRates[perf][Indices::contiPolymerMWEqIdx] = Base::restrictEval(cq_s_polymw); - } - - - - - - - template - std::optional - StandardWell:: - computeBhpAtThpLimitProd(const WellState& well_state, - const Simulator& ebos_simulator, - const SummaryState& summary_state, - DeferredLogger& deferred_logger) const - { - return computeBhpAtThpLimitProdWithAlq(ebos_simulator, - summary_state, - this->getALQ(well_state), - deferred_logger); - } - - template - std::optional - StandardWell:: - computeBhpAtThpLimitProdWithAlq(const Simulator& ebos_simulator, - const SummaryState& summary_state, - const double alq_value, - DeferredLogger& deferred_logger) const - { - // Make the frates() function. - auto frates = [this, &ebos_simulator, &deferred_logger](const double bhp) { - // Not solving the well equations here, which means we are - // calculating at the current Fg/Fw values of the - // well. This does not matter unless the well is - // crossflowing, and then it is likely still a good - // approximation. - std::vector rates(3); - computeWellRatesWithBhp(ebos_simulator, bhp, rates, deferred_logger); - this->adaptRatesForVFP(rates); - return rates; - }; - - double max_pressure = 0.0; - for (int perf = 0; perf < this->number_of_perforations_; ++perf) { - const int cell_idx = this->well_cells_[perf]; - const auto& int_quants = ebos_simulator.model().intensiveQuantities(cell_idx, /*timeIdx=*/ 0); - const auto& fs = int_quants.fluidState(); - double pressure_cell = this->getPerfCellPressure(fs).value(); - max_pressure = std::max(max_pressure, pressure_cell); - } - auto bhpAtLimit = WellBhpThpCalculator(*this).computeBhpAtThpLimitProd(frates, - summary_state, - max_pressure, - this->connections_.rho(), - alq_value, - this->getTHPConstraint(summary_state), - deferred_logger); - - if (bhpAtLimit) { - auto v = frates(*bhpAtLimit); - if (std::all_of(v.cbegin(), v.cend(), [](double i){ return i <= 0; }) ) { - return bhpAtLimit; - } - } - - - auto fratesIter = [this, &ebos_simulator, &deferred_logger](const double bhp) { - // Solver the well iterations to see if we are - // able to get a solution with an update - // solution - std::vector rates(3); - computeWellRatesWithBhpIterations(ebos_simulator, bhp, rates, deferred_logger); - this->adaptRatesForVFP(rates); - return rates; - }; - - bhpAtLimit = WellBhpThpCalculator(*this).computeBhpAtThpLimitProd(fratesIter, - summary_state, - max_pressure, - this->connections_.rho(), - alq_value, - this->getTHPConstraint(summary_state), - deferred_logger); - - - if (bhpAtLimit) { - // should we use fratesIter here since fratesIter is used in computeBhpAtThpLimitProd above? - auto v = frates(*bhpAtLimit); - if (std::all_of(v.cbegin(), v.cend(), [](double i){ return i <= 0; }) ) { - return bhpAtLimit; - } - } - - // we still don't get a valied solution. - return std::nullopt; - } - - - - template - std::optional - StandardWell:: - computeBhpAtThpLimitInj(const Simulator& ebos_simulator, - const SummaryState& summary_state, - DeferredLogger& deferred_logger) const - { - // Make the frates() function. - auto frates = [this, &ebos_simulator, &deferred_logger](const double bhp) { - // Not solving the well equations here, which means we are - // calculating at the current Fg/Fw values of the - // well. This does not matter unless the well is - // crossflowing, and then it is likely still a good - // approximation. - std::vector rates(3); - computeWellRatesWithBhp(ebos_simulator, bhp, rates, deferred_logger); - return rates; - }; - - return WellBhpThpCalculator(*this).computeBhpAtThpLimitInj(frates, - summary_state, - this->connections_.rho(), - 1e-6, - 50, - true, - deferred_logger); - } - - - - - - template - bool - StandardWell:: - iterateWellEqWithControl(const Simulator& ebosSimulator, - const double dt, - const Well::InjectionControls& inj_controls, - const Well::ProductionControls& prod_controls, - WellState& well_state, - const GroupState& group_state, - DeferredLogger& deferred_logger) - { - const int max_iter = this->param_.max_inner_iter_wells_; - int it = 0; - bool converged; - bool relax_convergence = false; - this->regularize_ = false; - const auto& summary_state = ebosSimulator.vanguard().summaryState(); - do { - assembleWellEqWithoutIteration(ebosSimulator, dt, inj_controls, prod_controls, well_state, group_state, deferred_logger); - - if (it > this->param_.strict_inner_iter_wells_) { - relax_convergence = true; - this->regularize_ = true; - } - - auto report = getWellConvergence(summary_state, well_state, Base::B_avg_, deferred_logger, relax_convergence); - - converged = report.converged(); - if (converged) { - break; - } - - ++it; - solveEqAndUpdateWellState(summary_state, well_state, deferred_logger); - - // TODO: when this function is used for well testing purposes, will need to check the controls, so that we will obtain convergence - // under the most restrictive control. Based on this converged results, we can check whether to re-open the well. Either we refactor - // this function or we use different functions for the well testing purposes. - // We don't allow for switching well controls while computing well potentials and testing wells - // updateWellControl(ebosSimulator, well_state, deferred_logger); - initPrimaryVariablesEvaluation(); - } while (it < max_iter); - - return converged; - } - - - template - std::vector - StandardWell:: - computeCurrentWellRates(const Simulator& ebosSimulator, - DeferredLogger& deferred_logger) const - { - // Calculate the rates that follow from the current primary variables. - std::vector well_q_s(this->num_components_, 0.); - const EvalWell& bhp = this->primary_variables_.eval(Bhp); - const bool allow_cf = this->getAllowCrossFlow() || openCrossFlowAvoidSingularity(ebosSimulator); - for (int perf = 0; perf < this->number_of_perforations_; ++perf) { - const int cell_idx = this->well_cells_[perf]; - const auto& intQuants = ebosSimulator.model().intensiveQuantities(cell_idx, /*timeIdx=*/ 0); - std::vector mob(this->num_components_, 0.); - getMobility(ebosSimulator, perf, mob, deferred_logger); - std::vector cq_s(this->num_components_, 0.); - double trans_mult = ebosSimulator.problem().template rockCompTransMultiplier(intQuants, cell_idx); - double elapsed_time = ebosSimulator.time(); - const double Tw = this->wellIndex(perf) * trans_mult; - PerforationRates perf_rates; - computePerfRate(intQuants, mob, bhp.value(), Tw, perf, allow_cf, elapsed_time, - cq_s, perf_rates, deferred_logger); - for (int comp = 0; comp < this->num_components_; ++comp) { - well_q_s[comp] += cq_s[comp]; - } - } - const auto& comm = this->parallel_well_info_.communication(); - if (comm.size() > 1) - { - comm.sum(well_q_s.data(), well_q_s.size()); - } - return well_q_s; - } - - - - template - std::vector - StandardWell:: - getPrimaryVars() const - { - const int num_pri_vars = this->primary_variables_.numWellEq(); - std::vector retval(num_pri_vars); - for (int ii = 0; ii < num_pri_vars; ++ii) { - retval[ii] = this->primary_variables_.value(ii); - } - return retval; - } - - - - - - template - int - StandardWell:: - setPrimaryVars(std::vector::const_iterator it) - { - const int num_pri_vars = this->primary_variables_.numWellEq(); - for (int ii = 0; ii < num_pri_vars; ++ii) { - this->primary_variables_.setValue(ii, it[ii]); - } - return num_pri_vars; - } - - - template - typename StandardWell::Eval - StandardWell:: - connectionRateEnergy(const double maxOilSaturation, - const std::vector& cq_s, - const IntensiveQuantities& intQuants, - DeferredLogger& deferred_logger) const - { - auto fs = intQuants.fluidState(); - Eval result = 0; - for (unsigned phaseIdx = 0; phaseIdx < FluidSystem::numPhases; ++phaseIdx) { - if (!FluidSystem::phaseIsActive(phaseIdx)) { - continue; - } - - // convert to reservoir conditions - EvalWell cq_r_thermal(this->primary_variables_.numWellEq() + Indices::numEq, 0.); - const unsigned activeCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::solventComponentIndex(phaseIdx)); - const bool both_oil_gas = FluidSystem::phaseIsActive(FluidSystem::oilPhaseIdx) && FluidSystem::phaseIsActive(FluidSystem::gasPhaseIdx); - if (!both_oil_gas || FluidSystem::waterPhaseIdx == phaseIdx) { - cq_r_thermal = cq_s[activeCompIdx] / this->extendEval(fs.invB(phaseIdx)); - } else { - // remove dissolved gas and vapporized oil - const unsigned oilCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::oilCompIdx); - const unsigned gasCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::gasCompIdx); - // q_os = q_or * b_o + rv * q_gr * b_g - // q_gs = q_gr * g_g + rs * q_or * b_o - // q_gr = 1 / (b_g * d) * (q_gs - rs * q_os) - // d = 1.0 - rs * rv - const EvalWell d = this->extendEval(1.0 - fs.Rv() * fs.Rs()); - if (d <= 0.0) { - deferred_logger.debug( - fmt::format("Problematic d value {} obtained for well {}" - " during calculateSinglePerf with rs {}" - ", rv {}. Continue as if no dissolution (rs = 0) and" - " vaporization (rv = 0) for this connection.", - d, this->name(), fs.Rs(), fs.Rv())); - cq_r_thermal = cq_s[activeCompIdx] / this->extendEval(fs.invB(phaseIdx)); - } else { - if (FluidSystem::gasPhaseIdx == phaseIdx) { - cq_r_thermal = (cq_s[gasCompIdx] - - this->extendEval(fs.Rs()) * cq_s[oilCompIdx]) / - (d * this->extendEval(fs.invB(phaseIdx)) ); - } else if (FluidSystem::oilPhaseIdx == phaseIdx) { - // q_or = 1 / (b_o * d) * (q_os - rv * q_gs) - cq_r_thermal = (cq_s[oilCompIdx] - this->extendEval(fs.Rv()) * - cq_s[gasCompIdx]) / - (d * this->extendEval(fs.invB(phaseIdx)) ); - } - } - } - - // change temperature for injecting fluids - if (this->isInjector() && cq_s[activeCompIdx] > 0.0){ - // only handles single phase injection now - assert(this->well_ecl_.injectorType() != InjectorType::MULTI); - fs.setTemperature(this->well_ecl_.temperature()); - typedef typename std::decay::type::Scalar FsScalar; - typename FluidSystem::template ParameterCache paramCache; - const unsigned pvtRegionIdx = intQuants.pvtRegionIndex(); - paramCache.setRegionIndex(pvtRegionIdx); - paramCache.setMaxOilSat(maxOilSaturation); - paramCache.updatePhase(fs, phaseIdx); - - const auto& rho = FluidSystem::density(fs, paramCache, phaseIdx); - fs.setDensity(phaseIdx, rho); - const auto& h = FluidSystem::enthalpy(fs, paramCache, phaseIdx); - fs.setEnthalpy(phaseIdx, h); - cq_r_thermal *= this->extendEval(fs.enthalpy(phaseIdx)) * this->extendEval(fs.density(phaseIdx)); - result += getValue(cq_r_thermal); - } else { - // compute the thermal flux - cq_r_thermal *= this->extendEval(fs.enthalpy(phaseIdx)) * this->extendEval(fs.density(phaseIdx)); - result += Base::restrictEval(cq_r_thermal); - } - } - - return result; - } - - - template - template - void - StandardWell:: - gasOilPerfRateInj(const std::vector& cq_s, - PerforationRates& perf_rates, - const Value& rv, - const Value& rs, - const Value& pressure, - const Value& rvw, - DeferredLogger& deferred_logger) const - { - const unsigned oilCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::oilCompIdx); - const unsigned gasCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::gasCompIdx); - // TODO: the formulations here remain to be tested with cases with strong crossflow through production wells - // s means standard condition, r means reservoir condition - // q_os = q_or * b_o + rv * q_gr * b_g - // q_gs = q_gr * b_g + rs * q_or * b_o - // d = 1.0 - rs * rv - // q_or = 1 / (b_o * d) * (q_os - rv * q_gs) - // q_gr = 1 / (b_g * d) * (q_gs - rs * q_os) - - const double d = 1.0 - getValue(rv) * getValue(rs); - - if (d <= 0.0) { - deferred_logger.debug(dValueError(d, this->name(), - "gasOilPerfRateInj", - rs, rv, pressure)); - } else { - // vaporized oil into gas - // rv * q_gr * b_g = rv * (q_gs - rs * q_os) / d - perf_rates.vap_oil = getValue(rv) * (getValue(cq_s[gasCompIdx]) - getValue(rs) * getValue(cq_s[oilCompIdx])) / d; - // dissolved of gas in oil - // rs * q_or * b_o = rs * (q_os - rv * q_gs) / d - perf_rates.dis_gas = getValue(rs) * (getValue(cq_s[oilCompIdx]) - getValue(rv) * getValue(cq_s[gasCompIdx])) / d; - } - - if (FluidSystem::phaseIsActive(FluidSystem::waterPhaseIdx)) { - // q_ws = q_wr * b_w + rvw * q_gr * b_g - // q_wr = 1 / b_w * (q_ws - rvw * q_gr * b_g) = 1 / b_w * (q_ws - rvw * 1 / d (q_gs - rs * q_os)) - // vaporized water in gas - // rvw * q_gr * b_g = q_ws -q_wr *b_w = rvw * (q_gs -rs *q_os) / d - perf_rates.vap_wat = getValue(rvw) * (getValue(cq_s[gasCompIdx]) - getValue(rs) * getValue(cq_s[oilCompIdx])) / d; - } - } - - - - template - template - void - StandardWell:: - gasOilPerfRateProd(std::vector& cq_s, - PerforationRates& perf_rates, - const Value& rv, - const Value& rs, - const Value& rvw) const - { - const unsigned oilCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::oilCompIdx); - const unsigned gasCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::gasCompIdx); - const Value cq_sOil = cq_s[oilCompIdx]; - const Value cq_sGas = cq_s[gasCompIdx]; - const Value dis_gas = rs * cq_sOil; - const Value vap_oil = rv * cq_sGas; - - cq_s[gasCompIdx] += dis_gas; - cq_s[oilCompIdx] += vap_oil; - - // recording the perforation solution gas rate and solution oil rates - if (this->isProducer()) { - perf_rates.dis_gas = getValue(dis_gas); - perf_rates.vap_oil = getValue(vap_oil); - } - - if (FluidSystem::phaseIsActive(FluidSystem::waterPhaseIdx)) { - const unsigned waterCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::waterCompIdx); - const Value vap_wat = rvw * cq_sGas; - cq_s[waterCompIdx] += vap_wat; - if (this->isProducer()) - perf_rates.vap_wat = getValue(vap_wat); - } - } - - - template - template - void - StandardWell:: - gasWaterPerfRateProd(std::vector& cq_s, - PerforationRates& perf_rates, - const Value& rvw, - const Value& rsw) const - { - const unsigned waterCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::waterCompIdx); - const unsigned gasCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::gasCompIdx); - const Value cq_sWat = cq_s[waterCompIdx]; - const Value cq_sGas = cq_s[gasCompIdx]; - const Value vap_wat = rvw * cq_sGas; - const Value dis_gas_wat = rsw * cq_sWat; - cq_s[waterCompIdx] += vap_wat; - cq_s[gasCompIdx] += dis_gas_wat; - if (this->isProducer()) { - perf_rates.vap_wat = getValue(vap_wat); - perf_rates.dis_gas_in_water = getValue(dis_gas_wat); - } - } - - - template - template - void - StandardWell:: - gasWaterPerfRateInj(const std::vector& cq_s, - PerforationRates& perf_rates, - const Value& rvw, - const Value& rsw, - const Value& pressure, - DeferredLogger& deferred_logger) const - - { - const unsigned gasCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::gasCompIdx); - const unsigned waterCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::waterCompIdx); - - const double dw = 1.0 - getValue(rvw) * getValue(rsw); - - if (dw <= 0.0) { - deferred_logger.debug(dValueError(dw, this->name(), - "gasWaterPerfRateInj", - rsw, rvw, pressure)); - } else { - // vaporized water into gas - // rvw * q_gr * b_g = rvw * (q_gs - rsw * q_ws) / dw - perf_rates.vap_wat = getValue(rvw) * (getValue(cq_s[gasCompIdx]) - getValue(rsw) * getValue(cq_s[waterCompIdx])) / dw; - // dissolved gas in water - // rsw * q_wr * b_w = rsw * (q_ws - rvw * q_gs) / dw - perf_rates.dis_gas_in_water = getValue(rsw) * (getValue(cq_s[waterCompIdx]) - getValue(rvw) * getValue(cq_s[gasCompIdx])) / dw; - } - } - - - template - template - void - StandardWell:: - disOilVapWatVolumeRatio(Value& volumeRatio, - const Value& rvw, - const Value& rsw, - const Value& pressure, - const std::vector& cmix_s, - const std::vector& b_perfcells_dense, - DeferredLogger& deferred_logger) const - { - const unsigned waterCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::waterCompIdx); - const unsigned gasCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::gasCompIdx); - // Incorporate RSW/RVW factors if both water and gas active - const Value d = 1.0 - rvw * rsw; - - if (d <= 0.0) { - deferred_logger.debug(dValueError(d, this->name(), - "disOilVapWatVolumeRatio", - rsw, rvw, pressure)); - } - const Value tmp_wat = d > 0.0 ? (cmix_s[waterCompIdx] - rvw * cmix_s[gasCompIdx]) / d - : cmix_s[waterCompIdx]; - volumeRatio += tmp_wat / b_perfcells_dense[waterCompIdx]; - - const Value tmp_gas = d > 0.0 ? (cmix_s[gasCompIdx] - rsw * cmix_s[waterCompIdx]) / d - : cmix_s[waterCompIdx]; - volumeRatio += tmp_gas / b_perfcells_dense[gasCompIdx]; - } - - - template - template - void - StandardWell:: - gasOilVolumeRatio(Value& volumeRatio, - const Value& rv, - const Value& rs, - const Value& pressure, - const std::vector& cmix_s, - const std::vector& b_perfcells_dense, - DeferredLogger& deferred_logger) const - { - const unsigned oilCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::oilCompIdx); - const unsigned gasCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::gasCompIdx); - // Incorporate RS/RV factors if both oil and gas active - const Value d = 1.0 - rv * rs; - - if (d <= 0.0) { - deferred_logger.debug(dValueError(d, this->name(), - "gasOilVolumeRatio", - rs, rv, pressure)); - } - const Value tmp_oil = d > 0.0? (cmix_s[oilCompIdx] - rv * cmix_s[gasCompIdx]) / d : cmix_s[oilCompIdx]; - volumeRatio += tmp_oil / b_perfcells_dense[oilCompIdx]; - - const Value tmp_gas = d > 0.0? (cmix_s[gasCompIdx] - rs * cmix_s[oilCompIdx]) / d : cmix_s[gasCompIdx]; - volumeRatio += tmp_gas / b_perfcells_dense[gasCompIdx]; - } - - template - template - Value - StandardWell::wellIndexEval(const int perf, const double elapsed_time, const Value& pressure) const { - KerasModel model; - model.LoadModel(Base::ml_wi_filename_); - const auto& connection = Base::well_ecl_.getConnections()[perf]; - - // Run prediction. - - // give number of input parameters - Tensor in{2}; - - const auto p = scaleFunction(pressure, ${xmin[0]}, ${xmax[0]}); - const auto re = Value(scaleFunction(connection.r0(), ${xmin[1]}, ${xmax[1]})); - // note order need to be the same as under training - in.data_ = {{p, re}}; - - Tensor out; - model.Apply(&in, &out); - std::cout << "p: " << pressure << " re: " << connection.r0() << " WI: " << - unscaleFunction(out.data_[0],${ymin}, ${ymax}) << std::endl; - std::cout << "p_scaled: " << p << " re_scaled: " << re << " WI_scaled: " << - getValue(out.data_[0]) << std::endl; - return unscaleFunction(out.data_[0],${ymin}, ${ymax}); - } - - template - template - Value - StandardWell::scaleFunction(Value X, double min, double max) const { - return (X - min) / (max - min); -} - - template - template - Value - StandardWell::unscaleFunction(Value X, double min, double max) const { - return X * (max - min) + min; -} - -} // namespace Opm diff --git a/src/pyopmnearwell/utils/formulas.py b/src/pyopmnearwell/utils/formulas.py index ce748be..19d7b09 100644 --- a/src/pyopmnearwell/utils/formulas.py +++ b/src/pyopmnearwell/utils/formulas.py @@ -1,5 +1,11 @@ # pylint: skip-file -"""Provide useful mathematical formulas for reservoir modelling.""" +"""Provide useful mathematical formulas for reservoir modelling. + +TODO: The typing is off in this module. Instead of returning an ArrayLike, most +functions should return an np.ndarray, however that does not work when a float is passed +as then an np.float64 or similar is returned. Not sure how to fix this. + +""" import math import os @@ -9,7 +15,6 @@ import numpy as np from numpy.typing import ArrayLike - from pyopmnearwell.utils import units # TODO: Change the typing to typevars. There needs to be some logic, e.g., in case some @@ -19,16 +24,15 @@ R_E_RATIO: float = math.exp(-math.pi / 2) -def pyopmnearwell_correction(theta: ArrayLike = math.pi / 3) -> ArrayLike: +def pyopmnearwell_correction(angle: ArrayLike = math.pi / 3) -> ArrayLike: r"""Calculate a correction factor that scales cell volume, equivalent radius etc. from a 2D triangle grid to a radial grid. In pyopmnearwell, when using the `cake` grid, a 2D triangle grid is used. As the - scaling for cell volumes between a raidal and a triangular grid is linear, the + scaling for cell volumes between a radial and a triangular grid is linear, the radial solution can be recovered. This function calculates the ratio .. math:: - r_{radial_grid} / x_{triangle_grid}, where :math:`x_{triangle_grid}` is the altitude of the triangle, s.t. the solution @@ -36,17 +40,18 @@ def pyopmnearwell_correction(theta: ArrayLike = math.pi / 3) -> ArrayLike: radii. Args: - theta (ArrayLike): Angle of the triangle grid. Default is :math:`\pi/3`. + angle (ArrayLike): Angle between both sides of the triangle grid. Default is + :math:`\pi/3`. Returns: ArrayLike: :math:`r_{radial_grid} / x_{triangle_grid}` """ - theta = np.asarray(theta) - return 2 * np.tan(theta / 2) / theta + angle = np.asarray(angle) + return 2 * np.tan(angle / 2) / angle -def equivalent_well_block_radius(delta_x: ArrayLike) -> ArrayLike: +def equivalent_well_radius(delta_x: ArrayLike) -> np.ndarray: """Calculate the equivalent well block radius for a given quadratic cell size. Args: @@ -61,7 +66,7 @@ def equivalent_well_block_radius(delta_x: ArrayLike) -> ArrayLike: def cell_size(radii: ArrayLike) -> ArrayLike: - """Calculate the cell size for a given equivalent well block radius. + """Calculate the size of a quadratic cell for a given equivalent well block radius. Args: delta_x (ArrayLike): _description_ @@ -74,6 +79,41 @@ def cell_size(radii: ArrayLike) -> ArrayLike: return radii / R_E_RATIO +def peaceman_matrix_WI( # pylint: disable=C0103 + k_h: ArrayLike, r_e: ArrayLike, r_w: ArrayLike +) -> ArrayLike: + r"""Compute the well productivity index (without taking into account density and + viscosity) from the Peaceman well model. + + .. math:: + + WI = \frac{2 \pi h \mathbf{k}}{\ln(r_e/r_w)} + + Args: + k_h (ArrayLike): Permeability times the cell thickness. Unit: [m^3]. + r_e (ArrayLike): Equivalent well-block radius. Needs to have the same unit as + ``r_w``. + r_w (ArrayLike): Wellbore radius. Needs to have the same unit as ``r_e``. + + Returns: + WI (ArrayLike): :math:`WI`. Unit: [m^3]. + + Raises: + ValueError: If r_w is zero for any point. + ValueError: If either r_e or r_w contains a negative value + + """ + k_h = np.asarray(k_h) + r_e = np.asarray(r_e) + r_w = np.asarray(r_w) + if np.any(r_w <= 0): + raise ValueError("r_w must be positive.") + if np.any(r_e <= r_w): + raise ValueError("r_e must greater than r_w.") + WI = (2 * math.pi * k_h) / (np.log(r_e / r_w)) + return WI + + def peaceman_WI( # pylint: disable=C0103 k_h: ArrayLike, r_e: ArrayLike, r_w: ArrayLike, rho: ArrayLike, mu: ArrayLike ) -> ArrayLike: @@ -82,11 +122,12 @@ def peaceman_WI( # pylint: disable=C0103 .. math:: - WI = \frac{2\pi hk\frac{\mu}{\rho}}{\ln(r_e/r_w)} + WI = \frac{2 \pi h \mathbf{k} \frac{\mu}{\rho}}{\ln(r_e/r_w)} Args: k_h (ArrayLike): Permeability times the cell thickness. Unit: [m^3]. - r_e (ArrayLike): Equivalent well-block radius. Needs to have the same unit as ``r_w``. + r_e (ArrayLike): Equivalent well-block radius. Needs to have the same unit as + ``r_w``. r_w (ArrayLike): Wellbore radius. Needs to have the same unit as ``r_e``. rho (ArrayLike): Density. Unit: [kg/m^3]. mu (ArrayLike): Viscosity. Unit: [Pa*s]. @@ -94,19 +135,10 @@ def peaceman_WI( # pylint: disable=C0103 Returns: WI (ArrayLike): :math:`WI`. Unit: [m*s]. - Raises: - ValueError: If r_w is zero for any point. - """ - k_h = np.asarray(k_h) - r_e = np.asarray(r_e) - r_w = np.asarray(r_w) rho = np.asarray(rho) mu = np.asarray(mu) - - if np.any(r_w == 0): - raise ValueError("r_w cannot be zero") - WI = (2 * math.pi * k_h) / (np.log(r_e / r_w)) + WI = peaceman_matrix_WI(k_h, r_e, r_w) return WI * rho / mu @@ -132,8 +164,8 @@ def two_phase_peaceman_WI( # pylint: disable=C0103 .. math:: - WI = \frac{2\pi h\mathbf{k}}{\ln(r_e/r_w)}\left(\frac{k_{r,1}}{\mu_1} + - \frac{k_{r,2}}{\mu_2}) + WI = \frac{2 \pi h \mathbf{k}}{\ln(r_e/r_w)} \left(\frac{k_{r,1}}{\mu_1} + + \frac{k_{r,2}}{\mu_2})\right) Note: @@ -154,13 +186,7 @@ def two_phase_peaceman_WI( # pylint: disable=C0103 Returns: WI (ArrayLike): :math:`WI`. Unit: [m*s]. - Raises: - ValueError: If ``r_w`` is zero for any point. - """ - k_h = np.asarray(k_h) - r_e = np.asarray(r_e) - r_w = np.asarray(r_w) rho_1 = np.asarray(rho_1) mu_1 = np.asarray(mu_1) k_r1 = np.asarray(k_r1) @@ -168,11 +194,10 @@ def two_phase_peaceman_WI( # pylint: disable=C0103 mu_2 = np.asarray(mu_2) k_r2 = np.asarray(k_r2) - if np.any(r_w == 0): - raise ValueError("r_w cannot be zero") total_mobility: ArrayLike = (k_r1 * rho_1) / mu_1 + (k_r2 * rho_2) / mu_2 - WI = (2 * math.pi * k_h) / (np.log(r_e / r_w)) - return WI * total_mobility + WI = peaceman_matrix_WI(k_h, r_e, r_w) + # Ignore unsupported operand types for *. Fixing this would be quite complex. + return WI * total_mobility # type: ignore def data_WI( @@ -220,7 +245,7 @@ def co2brinepvt( temperature: float, phase_property: Literal["density", "viscosity"], phase: Literal["CO2", "water"], - OPM: pathlib.Path, + OPM: str | pathlib.Path, ) -> float: """Call OPM's ``co2brinepvt`` to calculate density/viscosity. @@ -229,13 +254,13 @@ def co2brinepvt( temperature (float): Unit: [K]. property (Literal["density", "viscosity"]): Phase property to return. phase (Literal["CO2", "water"]): Phase of interest. - OPM: (pathlib.Path): Path to OPM installation. + OPM: (str | pathlib.Path): Path to OPM installation. Returns: quantity (float): Density (unit: [kg/m^3]) or viscosity (unit: [Pa*s]) """ - CO2BRINEPVT: pathlib.Path = OPM / "build/opm-common/bin/co2brinepvt" + CO2BRINEPVT: pathlib.Path = pathlib.Path(OPM) / "build/opm-common/bin/co2brinepvt" with subprocess.Popen( [ str(CO2BRINEPVT), diff --git a/src/pyopmnearwell/utils/inputvalues.py b/src/pyopmnearwell/utils/inputvalues.py index 06a3f55..88149f7 100644 --- a/src/pyopmnearwell/utils/inputvalues.py +++ b/src/pyopmnearwell/utils/inputvalues.py @@ -88,15 +88,6 @@ def readthefirstpart(lol, dic): (lol[8][0].strip()).split()[1] ) # Well connection transmissibility factor [mD m] dic["removecells"] = int((lol[8][0].strip()).split()[2]) # Remove small cells - - # Empty well, the permeability is set high and the injection takes part at the upper - # boundary of the well cells. - if len(lol[8][0].strip().split()) >= 4 and not lol[8][0].strip().split()[ - 3 - ].startswith("#"): - dic["empty_well"] = float((lol[8][0].strip()).split()[3]) - else: - dic["empty_well"] = 0.0 dic["pressure"] = float((lol[9][0].strip()).split()[0]) / 1.0e5 # Convert to bar dic["temperature"] = float((lol[9][0].strip()).split()[1]) dic["initialphase"] = int((lol[9][0].strip()).split()[2]) diff --git a/src/pyopmnearwell/utils/plotting.py b/src/pyopmnearwell/utils/plotting.py index 027c92b..1b01259 100644 --- a/src/pyopmnearwell/utils/plotting.py +++ b/src/pyopmnearwell/utils/plotting.py @@ -5,15 +5,46 @@ import pathlib import pickle +import matplotlib +import matplotlib.pyplot as plt from matplotlib.figure import Figure -def save_fig_and_data(fig: Figure, path: pathlib.Path) -> None: - """Save a pyplot figure to a png file and save the data to a pickle file. +def set_latex_params() -> None: + """Set the LaTeX parameters for matplotlib. + + This function sets the font to be sans-serif and the text to be normal weight. + + Returns: + None + + """ + font = {"family": "normal", "weight": "normal", "size": 16} + matplotlib.rc("font", **font) + plt.rcParams.update( + { + "text.usetex": True, + "font.family": "sans-serif", + "svg.fonttype": "path", + "legend.columnspacing": 0.9, + "legend.handlelength": 1.5, + "legend.fontsize": 14, + "lines.linewidth": 4, + "axes.titlesize": 16, + "axes.grid": True, + } + ) + + +set_latex_params() + + +def save_fig_and_data(fig: Figure, path: str | pathlib.Path) -> None: + """Save a pyplot figure to an ``.svg`` file and save the data to a ``.pickle`` file. Args: fig (matplotlib.figure.Figure): The figure to save. - path (pathlib.Path): The path to save the figure and data to. + path (str | pathlib.Path): The path to save the figure and data to. Returns: None @@ -25,8 +56,12 @@ def save_fig_and_data(fig: Figure, path: pathlib.Path) -> None: # Save the figure to a png file # NOTE: Convert filename to str to ensure this works. With fig, ax = plt.subplots() # the conversion is not necessary, but with fig = plt.figure() it is. - fig.savefig(str(path.with_suffix(".png"))) + fig.savefig( + str(path.with_suffix(".svg")), bbox_inches="tight", format="svg", dpi=300 + ) # Save the data to a pickle file with path.with_suffix(".pickle").open("wb") as f: # pylint: disable=C0103 pickle.dump(fig, f) + + plt.close(fig) diff --git a/src/pyopmnearwell/utils/writefile.py b/src/pyopmnearwell/utils/writefile.py index 624eb79..0b8abdf 100644 --- a/src/pyopmnearwell/utils/writefile.py +++ b/src/pyopmnearwell/utils/writefile.py @@ -20,25 +20,24 @@ def reservoir_files( dic, - recalc_grid: bool = True, - recalc_tables: bool = True, - recalc_sections: bool = True, - inc_folder: pathlib.Path = pathlib.Path(""), + **kwargs, ): """ Function to write opm-related files by running mako templates Args: dic (dict): Global dictionary with required parameters - recalc_grid (bool): Whether to recalculate the ``GRID.INC``file. Intended for - ensemble runs, where the saturation functions/geography/etc. do not need to - be recalculated for each ensemble member. Defaults to True. - recalc_tables (bool): Whether to recalculate the ``TABLES.INC``file. Defaults to - True. - recalc_sections (bool): Whether to recalculate the ``GEOLOGY.INC`` and - ``REGIONS.INC`` files. Defaults to True. - inc_folder (pathlib.Path): If any of the mentioned files is not recalculated, - they are taken from this folder. Defaults to ``pathlib.Path("")``. + **kwargs: Possible kwargs are: + - recalc_grid (bool): Whether to recalculate the ``GRID.INC``file. Intended + for ensemble runs, where the saturation functions/geography/etc. do not + need to be recalculated for each ensemble member. Defaults to True. + - recalc_tables (bool): Whether to recalculate the ``TABLES.INC``file. + Defaults to True. + - recalc_sections (bool): Whether to recalculate the ``GEOLOGY.INC`` and + ``REGIONS.INC`` files. Defaults to True. + - inc_folder (pathlib.Path): If any of the mentioned files is not + recalculated, they are taken from this folder. Defaults to + ``pathlib.Path("")``. Note: - All of the ``recalc_*`` options only work for @@ -49,8 +48,8 @@ def reservoir_files( dic (dict): Global dictionary with new added parameters """ - # Ensure ``inc_folder`` is a ``Path`` objects. - inc_folder = pathlib.Path(inc_folder) + # Get ``inc_folder`` and ensure it is a ``Path`` objects. + inc_folder = pathlib.Path(kwargs.get("inc_folder", pathlib.Path(""))) # default values dic.update( @@ -90,11 +89,10 @@ def reservoir_files( dic["xcor"] = dic["xcor"][ (0.5 * dic["diameter"] < dic["xcor"]) | (0 == dic["xcor"]) ] - # dic["xcor"] = np.insert(dic["xcor"], 1, 0.5 * dic["diameter"]) dic["noCells"][0] = len(dic["xcor"]) - 1 # Either calculate the grid or update the links to all grid files. - if recalc_grid: + if kwargs.get("recalc_grid", True): if dic["grid"] == "core": dic = handle_core(dic) else: @@ -110,11 +108,14 @@ def reservoir_files( ) # If the tables are not recalculated, update the link to the tables file. - if not recalc_tables: + # TODO: Hiding the default value in ``.get`` is dangerous, as this is accessed + # multiple times. Possibly its better to introduce a local variable or update the + # dictionary at the start of the funciton. + if not kwargs.get("recalc_tables", True): dic.update({"tables_file": f"'{inc_folder / 'TABLES.INC'}'"}) # If the sections are not recalculated, update the link to all section files. - if not recalc_sections: + if not kwargs.get("recalc_sections", True): dic.update( { "geology_file": f"'{inc_folder / 'GEOLOGY.INC'}'", @@ -131,7 +132,7 @@ def reservoir_files( dic["layers"] += dic["z_centers"] > sum(dic["thickness"][: i + 1]) var = {"dic": dic} - filledtemplate: Template = fill_template( + filledtemplate: str = fill_template( var, filename=os.path.join( dic["pat"], "templates", dic["model"], f"{dic['template']}.mako" @@ -147,9 +148,9 @@ def reservoir_files( ) as file: file.write(filledtemplate) if dic["model"] != "co2eor": - if recalc_tables: + if kwargs.get("recalc_tables", True): manage_tables(dic) - if recalc_sections: + if kwargs.get("recalc_sections", True): manage_sections(dic) @@ -170,7 +171,7 @@ def manage_sections(dic): sections.append("pcfact") for section in sections: var = {"dic": dic} - filledtemplate: Template = fill_template( + filledtemplate: str = fill_template( var, filename=os.path.join(dic["pat"], "templates", "common", f"{section}.mako"), ) @@ -202,7 +203,7 @@ def manage_tables(dic): else: filename = f"{dic['pat']}/templates/common/saturation_functions_format_1.mako" var = {"dic": dic} - filledtemplate: Template = fill_template(var, filename=filename) + filledtemplate: str = fill_template(var, filename=filename) with open( os.path.join(dic["exe"], dic["fol"], "jobs", "saturation_functions.py"), "w", @@ -274,7 +275,7 @@ def manage_grid(dic): else: lol.append(row[0]) var = {"dic": dic} - filledtemplate: Template = fill_template(var, text="\n".join(lol)) + filledtemplate: str = fill_template(var, text="\n".join(lol)) with open( os.path.join(dic["exe"], dic["fol"], "preprocessing", "GRID.INC"), "w", @@ -285,7 +286,7 @@ def manage_grid(dic): if dic["grid"] == "coord3d": dic["xcorc"] = dic["x_n"] else: - dic = crete_3dgrid(dic) + dic = create_3dgrid(dic) if dic["model"] != "co2eor": for cord in dic["xcorc"]: dic["xcorc"] = np.insert(dic["xcorc"], 0, -cord) @@ -372,7 +373,7 @@ def d3_grids(dic, dxarray): else: lol.append(row[0]) var = {"dic": dic} - filledtemplate: Template = fill_template(var, text="\n".join(lol)) + filledtemplate: str = fill_template(var, text="\n".join(lol)) with open( f"{dic['exe']}/{dic['fol']}/preprocessing/GRID.INC", "w", @@ -409,7 +410,7 @@ def d3_grids(dic, dxarray): return dic -def crete_3dgrid(dic): +def create_3dgrid(dic): """ Function to handle the first part of the 3d grids diff --git a/src/pyopmnearwell/visualization/getpressure.py b/src/pyopmnearwell/visualization/getpressure.py index 350accd..8346806 100644 --- a/src/pyopmnearwell/visualization/getpressure.py +++ b/src/pyopmnearwell/visualization/getpressure.py @@ -6,8 +6,9 @@ """ import argparse -import os import csv +import os + import numpy as np from scipy.interpolate import interp1d @@ -47,6 +48,7 @@ def main(): interp_func = interp1d(xvalue, yvalue, fill_value="extrapolate") print(f"Distance : {distance} [m]") print(f"Pressure : {interp_func(distance)} [bar]") + # pylint: disable-next=possibly-used-before-assignment print(f"WI computed : {q_rate/(p_well-interp_func(distance))} [kg/(Bar day)]") diff --git a/src/pyopmnearwell/visualization/plotting.py b/src/pyopmnearwell/visualization/plotting.py index b9cf3b7..37e1c5a 100644 --- a/src/pyopmnearwell/visualization/plotting.py +++ b/src/pyopmnearwell/visualization/plotting.py @@ -1,39 +1,25 @@ # SPDX-FileCopyrightText: 2023 NORCE # SPDX-License-Identifier: GPL-3.0 -"""" -Script to plot OPM Flow results -""" +""""Script to plot OPM Flow results.""" import argparse -import os import csv -import numpy as np -import matplotlib +import os + import matplotlib.pyplot as plt +import numpy as np from scipy.interpolate import interp1d -from pyopmnearwell.visualization.reading import read_simulations + +from pyopmnearwell.utils.plotting import set_latex_params from pyopmnearwell.visualization.additional_plots import ( final_time_maps, - saltprec_plots, over_time_saltprec, + saltprec_plots, ) +from pyopmnearwell.visualization.reading import read_simulations -font = {"family": "normal", "weight": "normal", "size": 16} -matplotlib.rc("font", **font) -plt.rcParams.update( - { - "text.usetex": True, - "font.family": "monospace", - "legend.columnspacing": 0.9, - "legend.handlelength": 3.0, - "legend.fontsize": 12, - "lines.linewidth": 4, - "axes.titlesize": 16, - "axes.grid": True, - "figure.figsize": (10, 5), - } -) +set_latex_params() KMOL_TO_KG = 1e3 * 0.044 diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..b4759d3 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,103 @@ +"""Provide fixtures that are used in multiple test modules.""" + +import os +import pathlib +import shutil +from typing import Any + +import pytest + +from pyopmnearwell.core.pyopmnearwell import main +from pyopmnearwell.utils.inputvalues import process_input + +dirname: pathlib.Path = pathlib.Path(__file__).parent + + +@pytest.fixture(name="input_dict") +def fixture_input_dict(tmp_path: pathlib.Path) -> dict[str, Any]: + """Manually do what ``pyopmnearwell.py`` does. + + This fixture is used by ``test_runs.py`` and ``test_writefile.py``. + + """ + # Create run folders. + for name in ["preprocessing", "jobs", "output", "postprocessing"]: + (tmp_path / "output" / name).mkdir(parents=True, exist_ok=True) + # Read input deck. + base_dict: dict[str, Any] = { + "pat": dirname / ".." / "src" / "pyopmnearwell", + "exe": tmp_path, + "fol": "output", + "runname": "test_run", + "model": "co2store", + "plot": "ecl", + } + return process_input(base_dict, dirname / "models" / "co2store.txt") + + +@pytest.fixture(scope="session", name="run_path") +def fixture_create_path(tmp_path_factory: Any) -> pathlib.Path: + """Create a temporary path for the run. + + This fixture is used by ``test_runs.py``. + + """ + return tmp_path_factory.mktemp("run") + + +@pytest.fixture(scope="session", name="run_main") +def fixture_run_main(tmp_path_factory) -> pathlib.Path: + """Run pyopmnearwell on the ``tests/models/input.txt`` deck. + + Note: In comparison to the ``fixture_run_model`` fixture, the ``main`` function of + pyopmnearwell is called directly. + + This fixture is used by ``test_main.py``. + + """ + shared_dir: pathlib.Path = tmp_path_factory.mktemp("shared") + shutil.copy((dirname / "models" / "input").with_suffix(".txt"), shared_dir) + os.chdir(shared_dir) + main() + return shared_dir + + +@pytest.fixture(scope="session", name="run_model") +def fixture_run_model(request, tmp_path_factory) -> tuple[str, pathlib.Path]: + """Run pyopmnearwell on the requested deck in ``tests/models``. + + Note: In comparison to the ``fixture_run_main`` fixture, pyopmnearwell is invoked + via the command line. + + This fixture is used by ``test_models.py``. + + """ + shared_dir: pathlib.Path = tmp_path_factory.mktemp("shared") + model: str = request.param + + shutil.copy((dirname / "models" / model).with_suffix(".txt"), shared_dir) + os.chdir(shared_dir) + os.system(f"pyopmnearwell -i {model}.txt -o {model}") + return model, shared_dir + + +@pytest.fixture(scope="session", name="run_all_models") +def fixture_run_all_models(tmp_path_factory) -> pathlib.Path: + """Run pyopmnearwell on the all decks in ``tests/models``. + + Note: ``tests/models/co2eor.txt`` fails convergence, so it is excluded. + + This fixture is used by ``test_plot_comparison.py``. + + """ + # This is a bit hacky, as all models are run twice, once when ``fixture_run_model`` + # is called by ``test_models`` and once here. However, I didn't find any other easy + # way to ensure that multiple model files are available in the temporary dir s.t. + # ``pyopmnearwell -c compare`` can be called. + shared_dir: pathlib.Path = tmp_path_factory.mktemp("shared") + models: list[str] = ["co2core", "co2store", "h2store", "saltprec"] + for model in models: + shutil.copy((dirname / "models" / model).with_suffix(".txt"), shared_dir) + os.chdir(shared_dir) + os.system(f"pyopmnearwell -i {model}.txt -o {model}") + return shared_dir diff --git a/tests/models/co2store.txt b/tests/models/co2store.txt index 89dbdab..95017b1 100644 --- a/tests/models/co2store.txt +++ b/tests/models/co2store.txt @@ -1,5 +1,5 @@ """Set the full path to the flow executable and flags""" -flow --linear-solver-reduction=1e-5 --relaxed-max-pv-fraction=0 --ecl-enable-drift-compensation=0 --newton-max-iterations=50 --newton-min-iterations=5 --tolerance-mb=1e-7 --tolerance-wells=1e-5 --relaxed-well-flow-tol=1e-5 --use-multisegment-well=false --enable-tuning=true --enable-opm-rst-file=true --linear-solver=cprw --enable-well-operability-check=false --min-time-step-before-shutting-problematic-wells-in-days=1e-99 +flow --linear-solver-reduction=1e-5 --relaxed-max-pv-fraction=0 --enable-drift-compensation=0 --newton-max-iterations=50 --newton-min-iterations=5 --tolerance-mb=1e-7 --tolerance-wells=1e-5 --relaxed-well-flow-tol=1e-5 --use-multisegment-well=false --enable-tuning=true --enable-opm-rst-file=true --linear-solver=cprw --enable-well-operability-check=false --min-time-step-before-shutting-problematic-wells-in-days=1e-99 """Set the model parameters""" co2store topinjection #Model (co2store/h2store) diff --git a/tests/models/h2store.txt b/tests/models/h2store.txt index 8ac9348..a53e13e 100644 --- a/tests/models/h2store.txt +++ b/tests/models/h2store.txt @@ -1,5 +1,5 @@ """Set the full path to the flow executable and flags""" -flow --linear-solver-reduction=1e-5 --relaxed-max-pv-fraction=0 --ecl-enable-drift-compensation=0 --newton-max-iterations=50 --newton-min-iterations=5 --tolerance-mb=1e-7 --tolerance-wells=1e-5 --relaxed-well-flow-tol=1e-5 --use-multisegment-well=false --enable-tuning=true --enable-opm-rst-file=true --linear-solver=cprw --enable-well-operability-check=false --min-time-step-before-shutting-problematic-wells-in-days=1e-99 +flow --linear-solver-reduction=1e-5 --relaxed-max-pv-fraction=0 --enable-drift-compensation=0 --newton-max-iterations=50 --newton-min-iterations=5 --tolerance-mb=1e-7 --tolerance-wells=1e-5 --relaxed-well-flow-tol=1e-5 --use-multisegment-well=false --enable-tuning=true --enable-opm-rst-file=true --linear-solver=cprw --enable-well-operability-check=false --min-time-step-before-shutting-problematic-wells-in-days=1e-99 """Set the model parameters""" co2store base #Model (co2store/h2store) [***SINCE H2STORE is nor part of the latest stable release used for the CI, here we set the co2store ***] diff --git a/tests/models/input.txt b/tests/models/input.txt index 16b31a3..1a6883f 100644 --- a/tests/models/input.txt +++ b/tests/models/input.txt @@ -1,5 +1,5 @@ """Set the full path to the flow executable and flags""" -flow --linear-solver-reduction=1e-5 --relaxed-max-pv-fraction=0 --ecl-enable-drift-compensation=0 --newton-max-iterations=50 --newton-min-iterations=5 --tolerance-mb=1e-7 --tolerance-wells=1e-5 --relaxed-well-flow-tol=1e-5 --use-multisegment-well=false --enable-tuning=true --enable-opm-rst-file=true --linear-solver=cprw --enable-well-operability-check=false --min-time-step-before-shutting-problematic-wells-in-days=1e-99 +flow --linear-solver-reduction=1e-5 --relaxed-max-pv-fraction=0 --enable-drift-compensation=0 --newton-max-iterations=50 --newton-min-iterations=5 --tolerance-mb=1e-7 --tolerance-wells=1e-5 --relaxed-well-flow-tol=1e-5 --use-multisegment-well=false --enable-tuning=true --enable-opm-rst-file=true --linear-solver=cprw --enable-well-operability-check=false --min-time-step-before-shutting-problematic-wells-in-days=1e-99 """Set the model parameters""" co2store base #Model (co2store/h2store) diff --git a/tests/test_analysis.py b/tests/test_analysis.py new file mode 100644 index 0000000..64c8a26 --- /dev/null +++ b/tests/test_analysis.py @@ -0,0 +1,96 @@ +# pylint: disable=missing-function-docstring, fixme +"""Tests for the ``pyopmnearwell.ml.analysis`` module. + + +TODO: Set ``mock_model`` to a linear model and check that the relations betweens outputs +and inputs of ``sensitivity_analysis`` are also linear. + +""" + +import numpy as np +import pytest +from tensorflow import keras + +from pyopmnearwell.ml.analysis import plot_analysis, sensitivity_analysis + + +@pytest.fixture(name="mock_model") +def fixture_mock_model() -> keras.Model: + # Create a mock model for testing + model = keras.Sequential() + model.add(keras.layers.Dense(1, input_shape=(2,))) + model.compile(optimizer="adam", loss="mse") + return model + + +@pytest.fixture(params=[1, 10], name="resolution_1") +def fixture_resolution_1(request) -> int: + return request.param + + +@pytest.fixture(params=[1, 10], name="resolution_2") +def fixture_resolution_2(request) -> int: + return request.param + + +@pytest.fixture(name="run_sensitivity_analysis") +def fixture_run_sensitivity_analysis( + mock_model: keras.Model, resolution_1: int, resolution_2: int +) -> tuple[np.ndarray, np.ndarray]: + return sensitivity_analysis(mock_model, resolution_1, resolution_2) + + +def test_sensitivity_analysis( + run_sensitivity_analysis: tuple[np.ndarray, np.ndarray], + resolution_1: int, + resolution_2: int, +) -> None: + """Test the sensitivity_analysis function. + + Args: + run_sensitivity_analysis (tuple[np.ndarray, np.ndarray]): Output from + ``run_sensitivity_analysis``. + resolution_1 (int): The resolution for the first dimension. + resolution_2 (int): The resolution for the second dimension. + + Returns: + None + + """ + outputs, inputs = run_sensitivity_analysis + + expected_output_shape: tuple = (2, resolution_1, resolution_2) + expected_input_shape: tuple = expected_output_shape + (2,) + + # Check shapes. + assert outputs.shape == expected_output_shape + assert inputs.shape == expected_input_shape + + # Check that the outputs are not all zeros. + assert not np.allclose(outputs, 0) + + # Check ranges. + assert np.all(inputs >= -1) + assert np.all(inputs <= 1) + + +def test_plot_analysis( + run_sensitivity_analysis: tuple[np.ndarray, np.ndarray], + tmp_path, +) -> None: + """Test the plot_analysis function. + + Args: + sensitivity_analysis_results (tuple[np.ndarray, np.ndarray]): Output from + ``run_sensitivity_analysis``. + tmp_path (pathlib.Path): + + Returns: + None + + """ + outputs, inputs = run_sensitivity_analysis + plot_analysis(outputs, inputs, tmp_path / "save") + + assert (tmp_path / "save.pickle").is_file() + assert (tmp_path / "save.svg").is_file() diff --git a/tests/test_co2core.py b/tests/test_co2core.py deleted file mode 100644 index 274a0f2..0000000 --- a/tests/test_co2core.py +++ /dev/null @@ -1,17 +0,0 @@ -# SPDX-FileCopyrightText: 2023 NORCE -# SPDX-License-Identifier: GPL-3.0 - -"""co2core (core grid, hysteresis, cyclic injection)""" - -import os - - -def test_co2core(): - """See models/co2core.txt""" - cwd = os.getcwd() - os.chdir(f"{os.getcwd()}/tests/models") - os.system("rm -rf co2core") - os.system("pyopmnearwell -i co2core.txt -o co2core -p '' ") - assert os.path.exists("./co2core/output/CO2CORE.UNRST") - os.system("rm -rf co2core") - os.chdir(cwd) diff --git a/tests/test_co2eor.py b/tests/test_co2eor.py deleted file mode 100644 index d5925a9..0000000 --- a/tests/test_co2eor.py +++ /dev/null @@ -1,17 +0,0 @@ -# SPDX-FileCopyrightText: 2023 NORCE -# SPDX-License-Identifier: GPL-3.0 - -"""Test the co2eor model""" - -import os - - -def test_co2eor(): - """See models/co2eor.txt""" - cwd = os.getcwd() - os.chdir(f"{os.getcwd()}/tests/models") - os.system("rm -rf co2eor") - os.system("pyopmnearwell -i co2eor.txt -o co2eor -p ''") - assert os.path.exists("./co2eor/output/CO2EOR.UNRST") - os.system("rm -rf co2eor") - os.chdir(cwd) diff --git a/tests/test_co2store.py b/tests/test_co2store.py deleted file mode 100644 index 6ff6556..0000000 --- a/tests/test_co2store.py +++ /dev/null @@ -1,16 +0,0 @@ -# SPDX-FileCopyrightText: 2023 NORCE -# SPDX-License-Identifier: GPL-3.0 - -"""co2store (cake grid, hysteresis, cyclic injection, perforations)""" - -import os - - -def test_co2store(): - """See models/co2store.txt""" - cwd = os.getcwd() - os.chdir(f"{os.getcwd()}/tests/models") - os.system("rm -rf co2store") - os.system("pyopmnearwell -i co2store.txt -o co2store") - assert os.path.exists("./co2store/postprocessing/concentration_2D.png") - os.chdir(cwd) diff --git a/tests/test_csaltprec.py b/tests/test_csaltprec.py deleted file mode 100644 index daacceb..0000000 --- a/tests/test_csaltprec.py +++ /dev/null @@ -1,16 +0,0 @@ -# SPDX-FileCopyrightText: 2023 NORCE -# SPDX-License-Identifier: GPL-3.0 - -"""salt precipitation""" - -import os - - -def test_saltprec(): - """See models/saltprec.txt""" - cwd = os.getcwd() - os.chdir(f"{os.getcwd()}/tests/models") - os.system("rm -rf saltprec") - os.system("pyopmnearwell -i saltprec.txt -o saltprec") - assert os.path.exists("./saltprec/postprocessing/cumulative_saltprec.png") - os.chdir(cwd) diff --git a/tests/test_data.py b/tests/test_data.py deleted file mode 100644 index bcfe228..0000000 --- a/tests/test_data.py +++ /dev/null @@ -1,128 +0,0 @@ -"""Test the data module. - -We disable quite a lot of pylint errors, as it struggles with the pytest fictures and so -on. - -""" - -import os - -import numpy as np -import pytest -import tensorflow as tf -from ecl.eclfile.ecl_file import EclFile, open_ecl_file - -from pyopmnearwell.ml.data import EclDataSet - -dir_path = os.path.dirname(os.path.realpath(__file__)) - - -@pytest.fixture -def EclKW_saturation() -> np.ndarray: # pylint: disable=C0116, C0103 - with open_ecl_file(os.path.join(dir_path, "data/DUMMY.UNRST")) as ecl_file: - saturation_array = np.array(ecl_file.iget_kw("SGAS")) - # Transform to model input shape (no batch). - return np.expand_dims(saturation_array, axis=-1) - - -@pytest.fixture -def EclKW_pressure() -> np.ndarray: # pylint: disable=C0116, C0103 - with open_ecl_file(os.path.join(dir_path, "data/DUMMY.UNRST")) as ecl_file: - pressure_array = np.array(ecl_file.iget_kw("PRESSURE")) - # Transform to model output shape (no batch). - return np.expand_dims(pressure_array, axis=-1) - - -@pytest.fixture -def Ecl_dummy_file() -> EclFile: # pylint: disable=C0116, C0103 - """Return a dummy ``EclFile``.""" - ecl_file = EclFile(os.path.join(dir_path, "data/DUMMY.UNRST")) - return ecl_file - - -@pytest.fixture -def Ecl_data_set() -> EclDataSet: # pylint: disable=C0116, C0103 - dataset = EclDataSet( - path=dir_path, - input_kws=["PRESSURE"], - target_kws=["SGAS"], - read_data_on_init=False, - ) - return dataset - - -# def test_ECLDataSet(Ecl_dummy_file: EclFile, Ecl_data_set: EclDataSet) -> None: -# """_summary_ - -# Parameters: -# ECL_dummy_file: _description_ -# """ -# pass - - -def test_EclFile_to_datapoint( # pylint: disable=C0116, C0103 - Ecl_data_set: EclDataSet, # pylint: disable=W0621 - Ecl_dummy_file: EclFile, # pylint: disable=W0621 - EclKW_pressure: np.ndarray, # pylint: disable=W0621 - EclKW_saturation: np.ndarray, # pylint: disable=W0621 -) -> None: - """Test the ``EclFile_to_datapoint`` method. - - Parameters: - ECL_dummmy_file: _description_ - Ecl_data_set: _description_ - """ - feature, target = Ecl_data_set.EclFile_to_datapoint(Ecl_dummy_file) - assert np.allclose(feature, EclKW_pressure) - assert np.allclose(target, EclKW_saturation) - - -def test_ECLDataSet_read_data( # pylint: disable=C0116, C0103 - Ecl_data_set: EclDataSet, # pylint: disable=W0621 -) -> None: - """Test that ``EclDataSet.read_data`` runs without any error. - - Parameters: - Ecl_data_set: _description_ - - """ - Ecl_data_set.read_data() - - -def test_ECLDataSet_on_epoch_end( # pylint: disable=C0116, C0103 - Ecl_data_set: EclDataSet, # pylint: disable=W0621 -) -> None: - """Test that ``EclDataSet.on_epoch_end`` runs without any error. - - Parameters: - Ecl_data_set: _description_ - - """ - Ecl_data_set.read_data() - Ecl_data_set.on_epoch_end() - - -def test_ECLDataSet_len( # pylint: disable=C0116, C0103 - Ecl_data_set: EclDataSet, # pylint: disable=W0621 -) -> None: - """Test that ``EclDataSet.__len__`` is greater than zero.""" - Ecl_data_set.read_data() - assert len(Ecl_data_set) > 0 - - -def test_tfDataset_from_ECLDataSet_len( # pylint: disable=C0116, C0103 - Ecl_data_set: EclDataSet, # pylint: disable=W0621 -) -> None: - """Test that a ``tf.data.Dataset`` generated from ``EclDataSet`` has length greater - than zero.""" - Ecl_data_set.read_data() - ds = tf.data.Dataset.from_generator( - Ecl_data_set, - output_signature=( - tf.TensorSpec.from_tensor(Ecl_data_set[0][0]), - tf.TensorSpec.from_tensor(Ecl_data_set[0][1]), - ), - ) - - ds = ds.apply(tf.data.experimental.assert_cardinality(len(Ecl_data_set))) - assert tf.data.experimental.cardinality(ds).numpy() == len(Ecl_data_set) diff --git a/tests/test_ensemble.py b/tests/test_ensemble.py index 91f27cd..95148e5 100644 --- a/tests/test_ensemble.py +++ b/tests/test_ensemble.py @@ -1,5 +1,6 @@ -# pylint: skip-file +# pylint: disable=missing-function-docstring, fixme """Test the ``ml.ensemble`` module.""" + from __future__ import annotations import itertools @@ -12,7 +13,9 @@ import pytest from pyopmnearwell.ml.ensemble import ( + calculate_WI, create_ensemble, + get_flags, integrate_fine_scale_value, memory_efficient_sample, run_ensemble, @@ -25,8 +28,15 @@ REPORT_STEPS: int = 20 NUM_CELLS: int = 100 +rng: np.random.Generator = np.random.default_rng() + + +# Disable some pylint warnings for the ``TestEnsemble`` class. Some functions need +# unused arguments, because they rely on the connected fixtures being run. +# pylint: disable=unused-argument, invalid-name + -# Test cases for create_ensemble, setup_ensemble, run_ensemble +# Test cases for create_ensemble, setup_ensemble, run_ensemble: @pytest.mark.parametrize( "runspecs", [ @@ -77,11 +87,11 @@ ], indirect=True, ) -# NOTE: The ``create_ensemble_fixture`` parametrization is only needed for +# NOTE: The ``fixture_create_ensemble`` parametrization is only needed for # ``test_create_ensemble`` and ``test_setup_ensemble``. It is ignored by # ``test_run_ensemble``. @pytest.mark.parametrize( - "create_ensemble_fixture", + "fixture_create_ensemble", [ (does_not_raise()), (does_not_raise()), @@ -89,11 +99,11 @@ ], indirect=True, ) -# NOTE: The ``setup_assemble_fixture`` parametrization is only needed for +# NOTE: The ``fixture_setup_ensemble`` parametrization is only needed for # ``test_setup_ensemble`` and ``test_run_ensemble``. It is ignored by # ``test_create_ensemble``. @pytest.mark.parametrize( - "setup_assemble_fixture", + "fixture_setup_ensemble", itertools.product(*([[True, False]] * 3)), indirect=True, ) @@ -106,12 +116,12 @@ class TestEnsemble: """ - @pytest.fixture(scope="class") - def runspecs(self, request) -> dict[str, Any]: + @pytest.fixture(scope="class", name="runspecs") + def fixture_runspecs(self, request) -> dict[str, Any]: return request.param - @pytest.fixture(scope="class") - def create_ensemble_fixture( + @pytest.fixture(scope="class", name="fixture_create_ensemble") + def fixture_create_ensemble( self, request, runspecs: dict[str, Any] ) -> Optional[list[dict[str, Any]]]: """Create and return ensemble. @@ -137,12 +147,11 @@ def create_ensemble_fixture( with expected_exception: return create_ensemble(runspecs) - @pytest.fixture(scope="class") - def setup_assemble_fixture( + @pytest.fixture(scope="class", name="fixture_setup_ensemble") + def fixture_setup_ensemble( self, request, - runspecs: dict[str, Any], - create_ensemble_fixture: Optional[list[dict[str, Any]]], + fixture_create_ensemble: Optional[list[dict[str, Any]]], tmp_path_factory, ) -> Optional[pathlib.Path]: """Setup ensemble and return the folder path. @@ -150,14 +159,14 @@ def setup_assemble_fixture( Args: request (_type_): _description_ runspecs (dict[str, Any]): - create_ensemble_fixture (_type_): _description_ + fixture_create_ensemble (_type_): _description_ tmp_path_factory (_type_): _description_ Returns: _type_: _description_ """ - if create_ensemble_fixture is not None: + if fixture_create_ensemble is not None: recalc_grid: bool = request.param[-3] recalc_tables: bool = request.param[-2] recalc_sections: bool = request.param[-1] @@ -169,7 +178,7 @@ def setup_assemble_fixture( setup_ensemble( dirname, - create_ensemble_fixture, + fixture_create_ensemble, TEST_ENSEMBLE_MAKO, recalc_grid=recalc_grid, recalc_tables=recalc_tables, @@ -181,17 +190,17 @@ def setup_assemble_fixture( def test_create_ensemble( self, runspecs: dict[str, Any], - create_ensemble_fixture: Optional[list[dict[str, Any]]], - setup_assemble_fixture: Optional[pathlib.Path], + fixture_create_ensemble: Optional[list[dict[str, Any]]], + fixture_setup_ensemble: Optional[pathlib.Path], ) -> None: - if create_ensemble_fixture is not None: + if fixture_create_ensemble is not None: max_values: dict[str, np.ndarray] = {} min_values: dict[str, np.ndarray] = {} for variable, (min_value, max_value, _) in runspecs["variables"].items(): max_values[variable] = max_value min_values[variable] = min_value - assert len(create_ensemble_fixture) == runspecs["npoints"] - for member in create_ensemble_fixture: + assert len(fixture_create_ensemble) == runspecs["npoints"] + for member in fixture_create_ensemble: for variable, value in member.items(): if variable in max_values: assert value <= max_values[variable] @@ -201,25 +210,25 @@ def test_create_ensemble( def test_setup_assemble( self, runspecs: dict[str, Any], - create_ensemble_fixture: list[dict[str, Any]], - setup_assemble_fixture: pathlib.Path, + fixture_create_ensemble: list[dict[str, Any]], + fixture_setup_ensemble: pathlib.Path, ) -> None: # Skip invalid test cases. if runspecs["npoints"] == 200: pytest.skip("Invalid case.") - # if create_ensemble_fixture is None: + # if fixture_create_ensemble is None: # pytest.skip("Invalid case.") ensemble_folders: list[str] = [ folder.name - for folder in setup_assemble_fixture.iterdir() + for folder in fixture_setup_ensemble.iterdir() if folder.name.startswith("runfiles") ] # Check that all ensemble files were generated. - assert len(create_ensemble_fixture) == len(ensemble_folders) + assert len(fixture_create_ensemble) == len(ensemble_folders) for member_folder in ensemble_folders: subfolders: list[str] = [ - file.name for file in (setup_assemble_fixture / member_folder).iterdir() + file.name for file in (fixture_setup_ensemble / member_folder).iterdir() ] assert "preprocessing" in subfolders assert "jobs" in subfolders @@ -228,7 +237,7 @@ def test_setup_assemble( preprocessing_files: list[str] = [ file.name for file in ( - setup_assemble_fixture / member_folder / "preprocessing" + fixture_setup_ensemble / member_folder / "preprocessing" ).iterdir() ] for file in [ @@ -243,13 +252,13 @@ def test_setup_assemble( assert "saturation_functions.py" in [ file.name for file in ( - setup_assemble_fixture / member_folder / "jobs" + fixture_setup_ensemble / member_folder / "jobs" ).iterdir() ] else: runfiles: list[pathlib.Path] = list( - (setup_assemble_fixture / member_folder / "preprocessing").iterdir() + (fixture_setup_ensemble / member_folder / "preprocessing").iterdir() ) assert len(runfiles) == 1 runfile: pathlib.Path = runfiles[0] @@ -260,16 +269,16 @@ def test_setup_assemble( def test_run_ensemble( self, runspecs: dict[str, Any], - create_ensemble_fixture: list[dict[str, Any]], - setup_assemble_fixture: pathlib.Path, + fixture_create_ensemble: list[dict[str, Any]], + fixture_setup_ensemble: pathlib.Path, ) -> None: """ Test the `run_ensemble` for various inputs. Args: runspecs (dict[str, Any]): - create_ensemble_fixture (list[dict[str, Any]]): - setup_assemble_fixture (pathlib.Path): + fixture_create_ensemble (list[dict[str, Any]]): + fixture_setup_ensemble (pathlib.Path): Asserts: - The lists in the returned dictionary have the correct length (npoints) @@ -280,7 +289,7 @@ def test_run_ensemble( # Skip invalid test cases. if runspecs["npoints"] == 200: pytest.skip("Invalid case.") - # if create_ensemble_fixture is None: + # if fixture_create_ensemble is None: # pytest.skip("Invalid case.") ecl_keywords: list[str] = ["PRESSURE", "SGAS"] @@ -292,7 +301,7 @@ def test_run_ensemble( with patch("builtins.open", mock_open()): data = run_ensemble( FLOW, - setup_assemble_fixture, + fixture_setup_ensemble, runspecs, ecl_keywords, init_keywords, @@ -317,12 +326,15 @@ def test_run_ensemble( assert array.shape == expected_shape +# pylint: enable=unused-argument, invalid-name + + @pytest.mark.parametrize("num_members", [1, 5, 10]) def test_memory_efficient_sample(num_members: int): # Create sample input data. num_samples = 100 num_variables = 5 - variables = np.random.rand(num_variables, num_samples) + variables = rng.random((num_variables, num_samples)) # Call the function. result = memory_efficient_sample(variables, num_members) @@ -337,36 +349,47 @@ def test_memory_efficient_sample(num_members: int): assert member in variables[i] -# def test_calculate_radii(): -# # TODO -# pass - - -# def test_calculate_WI(): -# # TODO -# pass - - +# TODO: Implement tests for the following functions. # def test_extract_features(): -# # TODO # pass +# def test_calculate_radii(): +# pass -# Define some example data for testing -radial_values = np.array([1.0, 2.0, 3.0, 4.0]) -radii = np.array([1.0, 2.0, 3.0, 4.0]) -block_sidelength = 4.0 -# Test data and expected results for the parametrized tests -test_data = [ - ( - np.array([1.0, 2.0, 3.0, 4.0]), - np.array([1.0, 2.0, 3.0, 4.0]), - 4.0, - 30.849556708632265, - ), # Expected result calculated separately - (np.array([]), np.array([]), 4.0, 0.0), # Empty data should result in 0 -] +@pytest.mark.parametrize( + "pressures,injection_rates,expected_WI,expected_failed_indices", + [ + ( + np.array([[100, 90, 80, 60]], dtype=np.float32), + 10.0, + np.array([[1.0, 0.5, 0.25]]), + [], + ), + ( + np.array([[100, 90, 80, 60], [110, 100, 90, 70]]), + np.array([10.0, 20.0]), + np.array([[1.0, 0.5, 0.25], [2.0, 1.0, 0.5]]), + [], + ), + ( + np.array([[100, 90, 80, 60], [100, 100, 90, 80]]), + np.array([10.0, 20.0]), + np.array([[1.0, 0.5, 0.25]]), + [1], + ), + ], +) +def test_calculate_WI( # pylint: disable=invalid-name + pressures: np.ndarray, + injection_rates: np.ndarray, + expected_WI: np.ndarray, # pylint: disable=invalid-name + expected_failed_indices: list[int], +): + # pylint: disable-next=invalid-name + WI_array, failed_indices = calculate_WI(pressures, injection_rates) + assert np.allclose(WI_array, expected_WI) + assert failed_indices == expected_failed_indices @pytest.mark.parametrize( @@ -377,8 +400,8 @@ def test_memory_efficient_sample(num_members: int): np.array([0.0, 1.0, 2.0, 3.0, 4.0]), 4.0, 32.0, - ), # Expected result calculated separately - (np.array([]), np.array([]), 4.0, 0.0), # Empty data should result in 0 + ), # Expected result calculated by hand. + (np.array([]), np.array([]), 4.0, 0.0), # Empty data should result in 0. ], ) def test_integrate_fine_scale_value(radial_values, radii, block_sidelength, expected): @@ -386,5 +409,17 @@ def test_integrate_fine_scale_value(radial_values, radii, block_sidelength, expe assert pytest.approx(result, rel=1e-7) == expected -if __name__ == "__main__": - pytest.main() +@pytest.mark.parametrize( + "flags, expected_value", + [ + ("flow --flag1 --flag2=value --flag3\n", "--flag1 --flag2=value --flag3"), + ("${FLOW} --flag1=2 --flag3=test --flag", "--flag1=2 --flag3=test --flag"), + ], +) +def test_get_flags(flags: str, expected_value: str, tmp_path): + makofile = tmp_path / "test.mako" + with makofile.open("w") as f: # pylint: disable=invalid-name + f.write("\n") + f.write(flags) + flags = get_flags(makofile) + assert flags == expected_value diff --git a/tests/test_formulas.py b/tests/test_formulas.py index db0a1a5..446ebab 100644 --- a/tests/test_formulas.py +++ b/tests/test_formulas.py @@ -14,11 +14,69 @@ data_WI, hydrostatic_fluid, hydrostatic_gas, + peaceman_matrix_WI, peaceman_WI, two_phase_peaceman_WI, ) +@pytest.mark.parametrize( + "k_h, r_e, r_w, expected", + [ + (1e-12, 100.0, 1.0, 2 * math.pi * 1e-12 / math.log(100.0)), + (1e-11, 200.0, 0.5, 2 * math.pi * 1e-11 / math.log(400.0)), + (1e-11, 250.0, 0.5, 2 * math.pi * 1e-11 / math.log(500.0)), + (1e-13, 200.0, 0.5, 2 * math.pi * 1e-13 / math.log(400.0)), + ( + np.array([1e-12, 1e-11]), + np.array([100.0, 200.0]), + np.array([1.0, 0.5]), + np.array( + [ + 2 * math.pi * 1e-12 / math.log(100.0), + 2 * math.pi * 1e-11 / math.log(400.0), + ] + ), + ), + ( + np.array([1e-11, 1e-13]), + np.array([250.0, 200.0]), + np.array([0.5, 0.5]), + np.array( + [ + 2 * math.pi * 1e-11 / math.log(500.0), + 2 * math.pi * 1e-13 / math.log(400.0), + ] + ), + ), + (1e-12, 0.0, 1.0, ValueError), + (1e-12, 1.0, 0.0, ValueError), + (1e-12, 1.0, -5.5, ValueError), + ( + np.array([1e-12, 1e-11, 1e-10]), + np.array([100.0, 200.0, 300.0]), + np.array([1.0, 0.5, 0.0]), + ValueError, + ), + ( + np.array([1e-12, 1e-11, 1e-10]), + np.array([100.0, 0.5, 300.0]), + np.array([1.0, 0.5, 1.0]), + ValueError, + ), + ], +) +def test_peaceman_matrix_WI( + k_h: ArrayLike, r_e: ArrayLike, r_w: ArrayLike, expected: ArrayLike | ValueError +): + if expected is ValueError: + with pytest.raises(ValueError): + result = peaceman_matrix_WI(k_h, r_e, r_w) + else: + result = peaceman_matrix_WI(k_h, r_e, r_w) + assert np.allclose(result, expected, rtol=1e-7) # type: ignore + + @pytest.mark.parametrize( "k_h, r_e, r_w, rho, mu, expected", [ diff --git a/tests/test_geometries.py b/tests/test_geometries.py index 0cf2db3..4ab0c7f 100644 --- a/tests/test_geometries.py +++ b/tests/test_geometries.py @@ -1,20 +1,36 @@ # SPDX-FileCopyrightText: 2023 NORCE # SPDX-License-Identifier: GPL-3.0 -"""Test the different type of grids""" +"""Test the different type of grids.""" import os +import pathlib +import shutil +import pytest -def test_geometries(): +dirname: pathlib.Path = pathlib.Path(__file__).parent + + +@pytest.mark.parametrize( + "file", + [ + "cake", + "cartesian", + "cartesian2d", + "coord2d", + "coord3d", + "cpg3d", + "radial", + "tensor2d", + "tensor3d", + ], +) +def test_geometries(file: str, tmp_path): """See geometries/""" - cwd = os.getcwd() - os.chdir(f"{os.getcwd()}/tests/geometries") - conf_fil = sorted([name[:-4] for name in os.listdir(".") if name.endswith(".txt")]) - for file in conf_fil: - command = f"pyopmnearwell -i {file}.txt -o {file} & wait" - print(command) - os.system(command) - assert os.path.exists(f"./{file}/postprocessing/saturation_2D.png") - os.system(f"rm -rf {file}") - os.chdir(cwd) + shutil.copy((dirname / "geometries" / file).with_suffix(".txt"), tmp_path) + os.chdir(tmp_path) + command = f"pyopmnearwell -i {file}.txt -o {file} & wait" + print(command) + os.system(command) + assert (tmp_path / file / "postprocessing" / "saturation_2D.png").exists() diff --git a/tests/test_h2store.py b/tests/test_h2store.py deleted file mode 100644 index 30f1c69..0000000 --- a/tests/test_h2store.py +++ /dev/null @@ -1,16 +0,0 @@ -# SPDX-FileCopyrightText: 2023 NORCE -# SPDX-License-Identifier: GPL-3.0 - -"""Test the h2store model with the 3d cartesian grid""" - -import os - - -def test_h2store(): - """See models/h2store.txt""" - cwd = os.getcwd() - os.chdir(f"{os.getcwd()}/tests/models") - os.system("rm -rf h2store") - os.system("pyopmnearwell -i h2store.txt -o h2store") - assert os.path.exists("./h2store/postprocessing/pressure_2D.png") - os.chdir(cwd) diff --git a/tests/test_main.py b/tests/test_main.py index d51c979..814210c 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -1,15 +1,11 @@ # SPDX-FileCopyrightText: 2023 NORCE # SPDX-License-Identifier: GPL-3.0 +# pylint: disable=missing-function-docstring +"""Test the main framework.""" -"""Test the main framework""" -import os -from pyopmnearwell.core.pyopmnearwell import main +import pathlib -def test(): - """See models/input.txt""" - cwd = os.getcwd() - os.chdir(f"{os.getcwd()}/tests/models") - main() - os.chdir(cwd) +def test_main(run_main: pathlib.Path) -> None: + assert (run_main / "output" / "output" / "INPUT.UNRST").exists() diff --git a/tests/test_models.py b/tests/test_models.py new file mode 100644 index 0000000..8591ec3 --- /dev/null +++ b/tests/test_models.py @@ -0,0 +1,39 @@ +"""Test ``pyopmnearwell`` for different models.""" + +import os +import pathlib + +import pytest + +dirname: pathlib.Path = pathlib.Path(__file__).parent + + +@pytest.mark.parametrize( + "run_model,expected", + [ + ("input", "output/INPUT.UNRST"), + ("co2core", "output/CO2CORE.UNRST"), + ("co2eor", "output/CO2EOR.UNRST"), + ("co2store", "postprocessing/pressure_2D.png"), + ("h2store", "postprocessing/pressure_2D.png"), + ("saltprec", "postprocessing/cumulative_saltprec.png"), + ], + indirect=["run_model"], +) +def test_models(run_model: tuple[str, pathlib.Path], expected: str) -> None: + """Check that pyopmnearwell runs for the input, co2core, co2eor, co2store, and + h2store decks in ```models``. + + The fixture runs pyopmnearwell via the command line. + + Args: + run_model (tuple[str, pathlib.Path]): Output from the + ``conftest.fixture_run_model`` fixture. Tuple containing the model name and + the run path. + expected (str): A file that should exist after pyopmnearwell was successfully + run. + + """ + model, run_path = run_model + os.chdir(run_path) + assert (run_path / model / expected).exists() diff --git a/tests/test_nn.py b/tests/test_nn.py index a7cb60d..ee5efe6 100644 --- a/tests/test_nn.py +++ b/tests/test_nn.py @@ -1,4 +1,5 @@ -# pylint: skip-file +# pylint: disable=missing-function-docstring,fixme +"""Tests for the ``pyopmnearwell.ml.nn`` module.""" from __future__ import annotations import csv @@ -15,15 +16,20 @@ from pyopmnearwell.ml.ensemble import store_dataset from pyopmnearwell.ml.nn import scale_and_evaluate, scale_and_prepare_dataset +rng: np.random.Generator = np.random.default_rng() -@pytest.fixture -def identity_nn() -> keras.Model: + +@pytest.fixture(name="identity_nn") +def fixture_identity_nn() -> keras.Model: model: keras.Model = keras.Sequential([keras.layers.Identity()]) return model -@pytest.fixture(params=[[[1, 2]], [[3.2, -5.8]], [[-12.3, 14.58]], [[-5, -20.5]]]) -def feature_batch(request) -> np.ndarray: +@pytest.fixture( + params=[[[1, 2]], [[3.2, -5.8]], [[-12.3, 14.58]], [[-5, -20.5]]], + name="feature_batch", +) +def fixture_feature_batch(request) -> np.ndarray: return np.array(request.param) @@ -56,14 +62,15 @@ def feature_batch(request) -> np.ndarray: "output_1": (-40559.5, 1.0), "output_2": (-600.5, 2292), }, - ] + ], + name="scalings", ) -def scalings(request) -> dict[str, tuple[float, float]]: +def fixture_scalings(request) -> dict[str, tuple[float, float]]: return request.param -@pytest.fixture -def write_scalings( +@pytest.fixture(name="write_scalings") +def fixture_write_scalings( scalings: dict[str, tuple[float, float]], tmp_path_factory, ) -> pathlib.Path: @@ -78,6 +85,8 @@ def write_scalings( return dirname +# TODO: Add tests with multidimensional input and output. +# pylint: disable-next=too-many-locals def test_scale_and_evaluate( identity_nn: keras.Model, feature_batch: tf.Tensor, @@ -98,7 +107,7 @@ def test_scale_and_evaluate( target_range: tuple[float, float] = scalings.get("target_range", (-1, 1)) for i, (feature_min, feature_max), (target_min, target_max) in zip( - range(output_batch.shape[-1]), feature_scalings, output_scalings + range(output_batch.shape[-1]), feature_scalings, output_scalings # type: ignore ): std_feature: np.ndarray = (feature_batch[..., i] - feature_min) / ( feature_max - feature_min @@ -119,36 +128,39 @@ def test_scale_and_evaluate( assert_allclose(output_batch[..., i], unscaled_output, rtol=1e-4) -@pytest.fixture(params=[1, 5, 20]) -def feature_names(request) -> list[str]: +@pytest.fixture(params=[1, 5, 20], name="feature_names") +def fixture_feature_names(request) -> list[str]: return [f"feat_{i}" for i in range(request.param)] -@pytest.fixture(params=[100, 1000]) -def data(request, feature_names: list[str]) -> tuple[np.ndarray, np.ndarray]: +@pytest.fixture(params=[100, 1000], name="data") +def fixture_data(request, feature_names: list[str]) -> tuple[np.ndarray, np.ndarray]: """Create data with sorted targets. This allows for comparing the different shuffle options.""" - features: np.ndarray = np.random.rand(request.param, len(feature_names)) - targets: np.ndarray = np.sort(np.random.rand(request.param, 1), axis=0) + features: np.ndarray = rng.random((request.param, len(feature_names))) + targets: np.ndarray = np.sort(rng.random((request.param, 1)), axis=0) return features, targets -@pytest.fixture -def dataset( +@pytest.fixture(name="dataset") +def fixture_dataset( data: tuple[np.ndarray, np.ndarray], tmp_path: pathlib.Path ) -> pathlib.Path: features, targets = data return store_dataset(features, targets, tmp_path / "dataset") -@pytest.fixture -def target_scaler(data: tuple[np.ndarray, np.ndarray]) -> MinMaxScaler: +@pytest.fixture(name="target_scaler") +def fixture_target_scaler(data: tuple[np.ndarray, np.ndarray]) -> MinMaxScaler: scaler = MinMaxScaler(feature_range=(0, 1)) scaler.fit(data[1]) return scaler +# TODO: Add tests with multidimensional input and output. Make sure this works correctly +# with all shuffling. @pytest.mark.parametrize("shuffle", ["first", "last", "false"]) +# pylint: disable-next=too-many-locals,too-many-statements def test_scale_and_prepare_dataset( shuffle: Literal["first", "last", "false"], dataset: pathlib.Path, @@ -227,6 +239,8 @@ def test_scale_and_prepare_dataset( unshuffled_test_targets = target_scaler.transform(unshuffled_test_targets) # Check that the dataset is shuffled correctly. + # There is a small chance this will fail for shuffle == "first" and shuffle == + # "last", when shuffling returns the original order by accident. if shuffle == "first": assert_raises( AssertionError, assert_allclose, train[1], unshuffled_train_targets @@ -252,8 +266,6 @@ def test_scale_and_prepare_dataset( np.sort(test[1], axis=0), unshuffled_test_targets, ) - # In general, there is a small chance this test (and also suffle == "first") will - # fail, when the shuffling returns the original order. elif shuffle == "last": assert_raises( AssertionError, assert_allclose, train[1], unshuffled_train_targets diff --git a/tests/test_plot_all.py b/tests/test_plot_all.py index 6a64ca0..e0b402b 100644 --- a/tests/test_plot_all.py +++ b/tests/test_plot_all.py @@ -1,17 +1,16 @@ # SPDX-FileCopyrightText: 2023 NORCE # SPDX-License-Identifier: GPL-3.0 -"""Test the plotting script""" +"""Test the plotting script.""" import os +import pathlib + from pyopmnearwell.visualization.plotting import main -def test_plot_all(): +def test_plot_all(run_main: pathlib.Path) -> None: """See visualization/plotting.py""" - cwd = os.getcwd() - os.chdir(f"{os.getcwd()}/tests/models") + os.chdir(run_main) main() - assert os.path.exists("pressure.csv") - os.system("rm -rf *.csv *.png") - os.chdir(cwd) + assert (run_main / "pressure.csv").exists() diff --git a/tests/test_plot_comparison.py b/tests/test_plot_comparison.py index 9422f75..6e674d6 100644 --- a/tests/test_plot_comparison.py +++ b/tests/test_plot_comparison.py @@ -1,15 +1,14 @@ # SPDX-FileCopyrightText: 2023 NORCE # SPDX-License-Identifier: GPL-3.0 -"""Test the comparison script""" +"""Test the comparison script.""" import os +import pathlib -def test_plot_comparison(): +def test_plot_comparison(run_all_models: pathlib.Path) -> None: """See visualization/plotting.py""" - cwd = os.getcwd() - os.chdir(f"{os.getcwd()}/tests/models") - os.system("pyopmnearwell -c compare") - assert os.path.exists("compare/well_pressure.png") - os.chdir(cwd) + os.chdir(run_all_models) + os.system("pyopmnearwell -c compare -p resdata") + assert (run_all_models / "compare" / "well_pressure.png").exists() diff --git a/tests/test_plotting.py b/tests/test_plotting.py index 578cc24..046e0ab 100644 --- a/tests/test_plotting.py +++ b/tests/test_plotting.py @@ -20,7 +20,8 @@ def test_save_fig_and_data(tmp_path: pathlib.Path): save_fig_and_data(fig, tmp_path / "test") # Check that the figure was saved correctly - assert (tmp_path / "test.png").is_file() + assert (tmp_path / "test.svg").is_file() + assert (tmp_path / "test.pickle").is_file() # Check that the data was saved correctly # # TODO: Fix this. diff --git a/tests/test_pressure_reader.py b/tests/test_pressure_reader.py index 0ae4fee..6f71c60 100644 --- a/tests/test_pressure_reader.py +++ b/tests/test_pressure_reader.py @@ -8,10 +8,8 @@ from pyopmnearwell.visualization.getpressure import main -dirname: pathlib.Path = pathlib.Path(__file__).parent - -def test_pressure_reader(): +def test_pressure_reader(run_main: pathlib.Path) -> None: """See visualization/getpressure.py""" - os.chdir(dirname / "models") + os.chdir(run_main) main() diff --git a/tests/test_resdata_dataset.py b/tests/test_resdata_dataset.py new file mode 100644 index 0000000..8b4a219 --- /dev/null +++ b/tests/test_resdata_dataset.py @@ -0,0 +1,129 @@ +# pylint: disable=missing-function-docstring, invalid-name +"""Test the ``resdata_dataset`` module.""" + + +import pathlib + +import numpy as np +import pytest +import tensorflow as tf +from resdata import FileMode +from resdata.resfile import ResdataFile + +from pyopmnearwell.ml.resdata_dataset import ResDataSet + +dirname: pathlib.Path = pathlib.Path(__file__).parent + + +@pytest.fixture(name="ResdataKW_saturation") +def fixture_ResdataKW_saturation() -> np.ndarray: + file = ResdataFile( + str(dirname / "data" / "DUMMY.UNRST"), flags=FileMode.CLOSE_STREAM + ) + saturation_array = np.array(file.iget_kw("SGAS")) + # Transform to model input shape (no batch). + return np.expand_dims(saturation_array, axis=-1) + + +@pytest.fixture(name="ResdataKW_pressure") +def fixture_ResdataKW_pressure() -> np.ndarray: + file = ResdataFile( + str(dirname / "data" / "DUMMY.UNRST"), flags=FileMode.CLOSE_STREAM + ) + pressure_array = np.array(file.iget_kw("PRESSURE")) + # Transform to model output shape (no batch). + return np.expand_dims(pressure_array, axis=-1) + + +@pytest.fixture(name="dummy_ResdataFile") +def fixture_dummy_ResdataFile() -> ResdataFile: + """Return a dummy ``EclFile``.""" + file = ResdataFile( + str(dirname / "data" / "DUMMY.UNRST"), flags=FileMode.CLOSE_STREAM + ) + return file + + +@pytest.fixture(name="dataset") +def fixture_dataset() -> ResDataSet: + dataset = ResDataSet( + path=str(dirname), + input_kws=["PRESSURE"], + target_kws=["SGAS"], + read_data_on_init=False, + ) + return dataset + + +def test_EclFile_to_datapoint( + dataset: ResDataSet, + dummy_ResdataFile: ResdataFile, + ResdataKW_pressure: np.ndarray, + ResdataKW_saturation: np.ndarray, +) -> None: + """Test the ``EclFile_to_datapoint`` method. + + This function tests the functionality of the ``EclFile_to_datapoint`` method in the + ``ResDataSet`` class. It verifies whether the method correctly converts an EclFile + object into a feature and target datapoint. + + + Args: + Ecl_data_set (ResDataSet): An instance of the ResDataSet class. + Ecl_dummy_file (EclFile): An EclFile object to be converted. + Resdata + KW_pressure (np.ndarray): The expected pressure values of the converted + datapoint. + ResdataKW_saturation (np.ndarray): The expected saturation values of the converted + datapoint. + + Returns: + None + + """ + feature, target = dataset.ResdataFile_to_datapoint(dummy_ResdataFile) + assert np.allclose(feature, ResdataKW_pressure) + assert np.allclose(target, ResdataKW_saturation) + + +def test_ResDataSet_read_data(dataset: ResDataSet) -> None: + """Test that ``ResDataSet.read_data`` runs without any error. + + Args: + Ecl_data_set (ResDataSet): _description_ + + """ + dataset.read_data() + + +def test_ResDataSet_on_epoch_end(dataset: ResDataSet) -> None: + """Test that ``ResDataSet.on_epoch_end`` runs without any error. + + Args: + Ecl_data_set (ResDataSet): _description_ + + """ + dataset.read_data() + dataset.on_epoch_end() + + +def test_ResDataSet_len(dataset: ResDataSet) -> None: + """Test that ``ResDataSet.__len__`` is greater than zero.""" + dataset.read_data() + assert len(dataset) > 0 + + +def test_tfDataset_from_ResDataSet_len(dataset: ResDataSet) -> None: + """Test that a ``tf.data.Dataset`` generated from ``ResDataSet`` has length greater + than zero.""" + dataset.read_data() + ds = tf.data.Dataset.from_generator( + dataset, + output_signature=( + tf.TensorSpec.from_tensor(dataset[0][0]), # type: ignore + tf.TensorSpec.from_tensor(dataset[0][1]), # type: ignore + ), + ) + + ds = ds.apply(tf.data.experimental.assert_cardinality(len(dataset))) + assert tf.data.experimental.cardinality(ds).numpy() == len(dataset) diff --git a/tests/test_runs.py b/tests/test_runs.py index d3aa6b8..cd43f81 100644 --- a/tests/test_runs.py +++ b/tests/test_runs.py @@ -1,4 +1,5 @@ -# pylint: skip-file +# pylint: disable=missing-function-docstring +"""Test the ``pyopmnearwell.utils.runs`` module.""" from __future__ import annotations import pathlib @@ -6,36 +7,16 @@ import pytest -from pyopmnearwell.utils.inputvalues import process_input from pyopmnearwell.utils.runs import simulations from pyopmnearwell.utils.writefile import reservoir_files -dirname: pathlib.Path = pathlib.Path(__file__).parent - - -@pytest.fixture -def input_dict(tmp_path: pathlib.Path) -> dict[str, Any]: - """Manually do what ``pyopmnearwell.py`` does.""" - # Create run folders. - for name in ["preprocessing", "jobs", "output", "postprocessing"]: - (tmp_path / "output" / name).mkdir(parents=True, exist_ok=True) - # Read input deck. - base_dict: dict[str, Any] = { - "pat": dirname / ".." / "src" / "pyopmnearwell", - "exe": tmp_path, - "fol": "output", - "runname": "test_run", - "model": "co2store", - "plot": "ecl", - } - return process_input(base_dict, dirname / "models" / "co2store.txt") - - -@pytest.fixture -def prepare_runfiles(input_dict: dict[str, Any]) -> None: + +@pytest.fixture(name="prepare_runfiles") +def fixture_prepare_runfiles(input_dict: dict[str, Any]) -> None: reservoir_files(input_dict) +# pylint: disable=unused-argument def test_simulations(input_dict: dict[str, Any], prepare_runfiles: None): simulations(input_dict) diff --git a/tests/test_scaler_layers.py b/tests/test_scaler_layers.py index 882571e..1d3583e 100644 --- a/tests/test_scaler_layers.py +++ b/tests/test_scaler_layers.py @@ -1,5 +1,6 @@ -# pylint: skip-file +# pylint: disable=missing-function-docstring """Test the ``ml.scaler_layers`` module.""" + from __future__ import annotations import itertools @@ -12,28 +13,35 @@ from pyopmnearwell.ml.scaler_layers import MinMaxScalerLayer, MinMaxUnScalerLayer +rng: np.random.Generator = np.random.default_rng() + + layers: list[str] = ["scaler", "unscaler"] feature_ranges: list[tuple[float, float]] = [(0.0, 1.0), (-3.7, 0.0), (-5.0, 4.0)] -@pytest.fixture(params=itertools.product(layers, feature_ranges)) -def layers_and_feature_ranges(request) -> tuple[str, tuple[float, float]]: +@pytest.fixture( + params=itertools.product(layers, feature_ranges), name="layers_and_feature_ranges" +) +def fixture_layers_and_feature_ranges(request) -> tuple[str, tuple[float, float]]: return request.param @pytest.fixture( - params=[np.random.uniform(-500, 500, (5, 1)) for _ in range(5)] - + [np.random.uniform(-500, 500, (5, 10)) for _ in range(5)] + params=[rng.uniform(-500, 500, (5, 1)) for _ in range(5)] + + [rng.uniform(-500, 500, (5, 10)) for _ in range(5)], + name="data", ) -def data(request) -> np.ndarray: +def fixture_data(request) -> np.ndarray: return request.param -@pytest.fixture -def fitted_model( +@pytest.fixture(name="fitted_model") +def fixture_fitted_model( layers_and_feature_ranges: tuple[str, tuple[float, float]], data: np.ndarray ) -> keras.Model: layer: str = layers_and_feature_ranges[0] + # pylint: disable=redefined-outer-name feature_ranges: tuple[float, float] = layers_and_feature_ranges[1] if layer == "scaler": model: keras.Model = keras.Sequential( @@ -49,15 +57,17 @@ def fitted_model( MinMaxUnScalerLayer(feature_range=feature_ranges), ] ) - model.get_layer(model.layers[0].name).adapt(data=data) + + # pylint: disable-next=possibly-used-before-assignment + model.get_layer(model.layers[0].name).adapt(data=data) # type: ignore return model -@pytest.fixture -def fitted_scaler( +@pytest.fixture(name="fitted_scaler") +def fixture_fitted_scaler( layers_and_feature_ranges: tuple[str, tuple[float, float]], data: np.ndarray ) -> MinMaxScaler: - scaler: MinMaxScaler = MinMaxScaler(feature_range=layers_and_feature_ranges[1]) + scaler: MinMaxScaler = MinMaxScaler(feature_range=layers_and_feature_ranges[1]) # type: ignore scaler.fit(data) return scaler @@ -67,7 +77,7 @@ def test_save_scaler_layer(fitted_model: keras.Model, tmp_path: pathlib.Path) -> assert (tmp_path / "model.pb").exists() # Load the saved model - loaded_model: keras.Model = keras.models.load_model( + loaded_model: keras.Model = keras.models.load_model( # type: ignore tmp_path / "model.pb", compile=False ) @@ -80,8 +90,8 @@ def test_save_scaler_layer(fitted_model: keras.Model, tmp_path: pathlib.Path) -> @pytest.mark.parametrize( "model_input", - [np.random.uniform(-500, 500, (5, 1)) for _ in range(5)] - + [np.random.uniform(-500, 500, (5, 10)) for _ in range(5)], + [rng.uniform(-500, 500, (5, 1)) for _ in range(5)] + + [rng.uniform(-500, 500, (5, 10)) for _ in range(5)], ) def test_minmax_scaler_layer( model_input: np.ndarray, diff --git a/tests/test_upscale.py b/tests/test_upscale.py new file mode 100644 index 0000000..9ab8464 --- /dev/null +++ b/tests/test_upscale.py @@ -0,0 +1,161 @@ +# pylint: disable=missing-function-docstring, fixme +"""Tests for the ``pyopmnearwell.ml.upscale`` module. + +Some of the test are not fixed yet. These get skipped. + +""" + +import math +import pathlib + +import numpy as np +import pytest + +from pyopmnearwell.ml.upscale import BaseUpscaler + +rng: np.random.Generator = np.random.default_rng() + +dirname: pathlib.Path = pathlib.Path(__file__).parent + + +# pylint: disable-next=missing-class-docstring +class MockUpscaler(BaseUpscaler): + def __init__(self) -> None: + self.num_timesteps = 10 + self.num_layers = 10 + self.num_zcells = 10 + self.num_xcells = 10 + self.single_feature_shape = (10, 10, 10, 10) + self.angle = math.pi / 3 + + def create_ds(self) -> None: + return None + + +@pytest.fixture(name="test_upscaler") +def fixture_test_upscaler() -> MockUpscaler: + return MockUpscaler() + + +@pytest.fixture( + params=[ + rng.random((10, 20, 30, 40, 5)), + rng.random((5, 1, 15, 20, 5)), + rng.random((5, 10, 15, 1, 5)), + rng.random((3, 1, 15, 1, 20)), + ], + name="feature", +) +def fixture_feature(request) -> np.ndarray: + return request.param + + +@pytest.mark.parametrize("step_size_x", [2, 4, 8]) +@pytest.mark.parametrize("step_size_t", [1, 2, 3]) +def test_reduce_data_size( + test_upscaler: MockUpscaler, + feature: np.ndarray, + step_size_x: int, + step_size_t: int, +) -> None: + feature_shape: list[int] = list(feature.shape) + feature_shape[-2] = math.ceil(feature_shape[-2] / step_size_x) + feature_shape[1] = math.ceil(feature_shape[1] / step_size_t) + + reduced_feature = test_upscaler.reduce_data_size(feature, step_size_x, step_size_t) + + assert reduced_feature.shape == tuple(feature_shape) + + +def test_get_vertically_averaged_values(test_upscaler: MockUpscaler) -> None: + # TOOD: Fix this test. + pytest.skip("Not implemented yet.") + features: np.ndarray = rng.random((10, 20, 30, 40, 5)) + feature_index: int = 2 + averaged_values: np.ndarray = test_upscaler.get_vertically_averaged_values( + features, feature_index + ) + assert averaged_values.shape == (10, 20, 1, 40, 5) + + +def test_get_radii(test_upscaler: MockUpscaler) -> None: + radii_file: pathlib.Path = dirname / "upscale" / "radii.txt" + cell_center_radii, cell_boundary_radii = test_upscaler.get_radii(radii_file) + assert cell_center_radii.shape == (test_upscaler.num_xcells,) + assert cell_boundary_radii.shape == (test_upscaler.num_xcells + 1,) + + +# TODO: Implement this test. +def test_create_ds() -> None: + pass + + +def test_get_timesteps(test_upscaler) -> None: + # TOOD: Fix this test. + pytest.skip("Not implemented yet.") + simulation_length: float = 10.0 + timesteps: np.ndarray = test_upscaler.get_timesteps(simulation_length) + assert len(timesteps) == test_upscaler.num_timesteps + assert timesteps[-1] == simulation_length + + +def test_get_horizontically_integrated_values(test_upscaler: MockUpscaler) -> None: + # TOOD: Fix this test. + pytest.skip("Not implemented yet.") + features = rng.random((10, 20, 30, 40, 5)) + radii_file: pathlib.Path = pathlib.Path("path/to/radii_file.txt") + cell_center_radii, cell_boundary_radii = test_upscaler.get_radii(radii_file) + feature_index: int = 2 + integrated_values = test_upscaler.get_horizontically_integrated_values( + features, cell_center_radii, cell_boundary_radii, feature_index + ) + assert integrated_values.shape == (10, 20, 30, 1) + + +def test_get_homogeneous_values(test_upscaler: MockUpscaler) -> None: + # TOOD: Fix this test. + pytest.skip("Not implemented yet.") + features: np.ndarray = rng.random((10, 20, 30, 40, 5)) + feature_index: int = 2 + homogeneous_values = test_upscaler.get_homogeneous_values(features, feature_index) + assert homogeneous_values.shape == (10, 20, 30, 1) + + +# pylint: disable-next=invalid-name +def test_get_analytical_WI(test_upscaler: MockUpscaler) -> None: + # TOOD: Fix this test. + pytest.skip("Not implemented yet.") + pressures: np.ndarray = rng.random((10, 20, 30, 40)) + saturations: np.ndarray = rng.random((10, 20, 30, 40)) + permeabilities: np.ndarray = rng.random((10, 20, 30, 40)) + temperature: float = 300.0 + surface_density: float = 0.1 + radii: np.ndarray = rng.random((40)) + OPM: pathlib.Path = pathlib.Path("path/to/OPM.txt") # pylint: disable=invalid-name + analytical_WI = test_upscaler.get_analytical_WI( # pylint: disable=invalid-name + pressures, + saturations, + permeabilities, + temperature, + surface_density, + radii, + well_radius=0.1, + OPM=OPM, + ) + assert analytical_WI.shape == (10, 20, 30, 40) + + +# pylint: disable-next=invalid-name +def test_get_data_WI(test_upscaler: MockUpscaler) -> None: + # TOOD: Fix this test. + pytest.skip("Not implemented yet.") + features: np.ndarray = rng.random((10, 20, 30, 40, 5)) + # pressures = rng.random((10, 20, 30, 40)) + pressure_index: int = 2 + inj_rate_index: int = 3 + angle: float = math.pi / 3 + # pylint: disable-next=invalid-name + data_WI: np.ndarray = test_upscaler.get_data_WI( + features, pressure_index, inj_rate_index, angle + ) + assert data_WI.shape == (10, 20, 30, 40) diff --git a/tests/test_writefile.py b/tests/test_writefile.py index 47c1948..a642a94 100644 --- a/tests/test_writefile.py +++ b/tests/test_writefile.py @@ -1,4 +1,6 @@ -# pylint: skip-file +# pylint: disable=missing-function-docstring +"""Test the ``pyopmnearwell.utils.writefile`` module.""" + from __future__ import annotations import pathlib @@ -6,29 +8,8 @@ import pytest -from pyopmnearwell.utils.inputvalues import process_input from pyopmnearwell.utils.writefile import reservoir_files -dirname: pathlib.Path = pathlib.Path(__file__).parent - - -@pytest.fixture -def input_dict(tmp_path: pathlib.Path) -> dict[str, Any]: - """Manually do what ``pyopmnearwell.py`` does.""" - # Create run folders. - for name in ["preprocessing", "jobs", "output", "postprocessing"]: - (tmp_path / "output" / name).mkdir(parents=True, exist_ok=True) - # Read input deck. - base_dict: dict[str, Any] = { - "pat": dirname / ".." / "src" / "pyopmnearwell", - "exe": tmp_path, - "fol": "output", - "runname": "test_run", - "model": "co2store", - "plot": "ecl", - } - return process_input(base_dict, dirname / "models" / "co2store.txt") - @pytest.mark.parametrize("recalc_grid", [True, False]) @pytest.mark.parametrize("recalc_tables", [True, False]) @@ -39,7 +20,12 @@ def test_reservoir_files( recalc_tables: bool, recalc_sections: bool, ) -> None: - reservoir_files(input_dict, recalc_grid, recalc_tables, recalc_sections) + reservoir_files( + input_dict, + recalc_grid=recalc_grid, + recalc_tables=recalc_tables, + recalc_sections=recalc_sections, + ) # Check that the expected files were created. preprocessing_fol: pathlib.Path = ( diff --git a/tests/upscale/radii.txt b/tests/upscale/radii.txt new file mode 100644 index 0000000..1e7f572 --- /dev/null +++ b/tests/upscale/radii.txt @@ -0,0 +1,100 @@ + 0.000000E+00 0.000000E+00 0 0.000000E+00 0.000000E+00 6.000000E+00 + 3.237255E-01 -1.869030E-01 0 3.237255E-01 -1.869030E-01 6.000000E+00 + 5.060793E-01 -2.921850E-01 0 5.060793E-01 -2.921850E-01 6.000000E+00 + 7.036209E-01 -4.062357E-01 0 7.036209E-01 -4.062357E-01 6.000000E+00 + 9.176151E-01 -5.297853E-01 0 9.176151E-01 -5.297853E-01 6.000000E+00 + 1.149432E+00 -6.636250E-01 0 1.149432E+00 -6.636250E-01 6.000000E+00 + 1.400557E+00 -8.086118E-01 0 1.400557E+00 -8.086118E-01 6.000000E+00 + 1.672597E+00 -9.656741E-01 0 1.672597E+00 -9.656741E-01 6.000000E+00 + 1.967294E+00 -1.135818E+00 0 1.967294E+00 -1.135818E+00 6.000000E+00 + 2.286536E+00 -1.320132E+00 0 2.286536E+00 -1.320132E+00 6.000000E+00 + 2.632366E+00 -1.519797E+00 0 2.632366E+00 -1.519797E+00 6.000000E+00 + 3.007000E+00 -1.736092E+00 0 3.007000E+00 -1.736092E+00 6.000000E+00 + 3.412836E+00 -1.970402E+00 0 3.412836E+00 -1.970402E+00 6.000000E+00 + 3.852473E+00 -2.224226E+00 0 3.852473E+00 -2.224226E+00 6.000000E+00 + 4.328726E+00 -2.499191E+00 0 4.328726E+00 -2.499191E+00 6.000000E+00 + 4.844644E+00 -2.797057E+00 0 4.844644E+00 -2.797057E+00 6.000000E+00 + 5.403532E+00 -3.119731E+00 0 5.403532E+00 -3.119731E+00 6.000000E+00 + 6.008968E+00 -3.469279E+00 0 6.008968E+00 -3.469279E+00 6.000000E+00 + 6.664829E+00 -3.847941E+00 0 6.664829E+00 -3.847941E+00 6.000000E+00 + 7.375315E+00 -4.258140E+00 0 7.375315E+00 -4.258140E+00 6.000000E+00 + 8.144975E+00 -4.702504E+00 0 8.144975E+00 -4.702504E+00 6.000000E+00 + 8.978738E+00 -5.183877E+00 0 8.978738E+00 -5.183877E+00 6.000000E+00 + 9.881942E+00 -5.705342E+00 0 9.881942E+00 -5.705342E+00 6.000000E+00 + 1.086037E+01 -6.270239E+00 0 1.086037E+01 -6.270239E+00 6.000000E+00 + 1.192029E+01 -6.882184E+00 0 1.192029E+01 -6.882184E+00 6.000000E+00 + 1.306849E+01 -7.545096E+00 0 1.306849E+01 -7.545096E+00 6.000000E+00 + 1.431232E+01 -8.263221E+00 0 1.431232E+01 -8.263221E+00 6.000000E+00 + 1.565974E+01 -9.041155E+00 0 1.565974E+01 -9.041155E+00 6.000000E+00 + 1.711939E+01 -9.883882E+00 0 1.711939E+01 -9.883882E+00 6.000000E+00 + 1.870060E+01 -1.079680E+01 0 1.870060E+01 -1.079680E+01 6.000000E+00 + 2.041351E+01 -1.178575E+01 0 2.041351E+01 -1.178575E+01 6.000000E+00 + 2.226908E+01 -1.285706E+01 0 2.226908E+01 -1.285706E+01 6.000000E+00 + 2.427920E+01 -1.401760E+01 0 2.427920E+01 -1.401760E+01 6.000000E+00 + 2.645674E+01 -1.527480E+01 0 2.645674E+01 -1.527480E+01 6.000000E+00 + 2.881563E+01 -1.663671E+01 0 2.881563E+01 -1.663671E+01 6.000000E+00 + 3.137100E+01 -1.811205E+01 0 3.137100E+01 -1.811205E+01 6.000000E+00 + 3.413919E+01 -1.971027E+01 0 3.413919E+01 -1.971027E+01 6.000000E+00 + 3.713793E+01 -2.144159E+01 0 3.713793E+01 -2.144159E+01 6.000000E+00 + 4.038643E+01 -2.331712E+01 0 4.038643E+01 -2.331712E+01 6.000000E+00 + 4.390549E+01 -2.534885E+01 0 4.390549E+01 -2.534885E+01 6.000000E+00 + 4.771764E+01 -2.754979E+01 0 4.771764E+01 -2.754979E+01 6.000000E+00 + 5.184729E+01 -2.993405E+01 0 5.184729E+01 -2.993405E+01 6.000000E+00 + 5.632090E+01 -3.251688E+01 0 5.632090E+01 -3.251688E+01 6.000000E+00 + 6.116709E+01 -3.531484E+01 0 6.116709E+01 -3.531484E+01 6.000000E+00 + 6.641691E+01 -3.834582E+01 0 6.641691E+01 -3.834582E+01 6.000000E+00 + 7.210397E+01 -4.162925E+01 0 7.210397E+01 -4.162925E+01 6.000000E+00 + 7.826469E+01 -4.518614E+01 0 7.826469E+01 -4.518614E+01 6.000000E+00 + 8.493852E+01 -4.903928E+01 0 8.493852E+01 -4.903928E+01 6.000000E+00 + 9.216819E+01 -5.321333E+01 0 9.216819E+01 -5.321333E+01 6.000000E+00 + 1.000000E+02 -5.773503E+01 0 1.000000E+02 -5.773503E+01 6.000000E+00 + 0.000000E+00 0.000000E+00 0 0.000000E+00 0.000000E+00 6.000000E+00 + 3.237255E-01 1.869030E-01 0 3.237255E-01 1.869030E-01 6.000000E+00 + 5.060793E-01 2.921850E-01 0 5.060793E-01 2.921850E-01 6.000000E+00 + 7.036209E-01 4.062357E-01 0 7.036209E-01 4.062357E-01 6.000000E+00 + 9.176151E-01 5.297853E-01 0 9.176151E-01 5.297853E-01 6.000000E+00 + 1.149432E+00 6.636250E-01 0 1.149432E+00 6.636250E-01 6.000000E+00 + 1.400557E+00 8.086118E-01 0 1.400557E+00 8.086118E-01 6.000000E+00 + 1.672597E+00 9.656741E-01 0 1.672597E+00 9.656741E-01 6.000000E+00 + 1.967294E+00 1.135818E+00 0 1.967294E+00 1.135818E+00 6.000000E+00 + 2.286536E+00 1.320132E+00 0 2.286536E+00 1.320132E+00 6.000000E+00 + 2.632366E+00 1.519797E+00 0 2.632366E+00 1.519797E+00 6.000000E+00 + 3.007000E+00 1.736092E+00 0 3.007000E+00 1.736092E+00 6.000000E+00 + 3.412836E+00 1.970402E+00 0 3.412836E+00 1.970402E+00 6.000000E+00 + 3.852473E+00 2.224226E+00 0 3.852473E+00 2.224226E+00 6.000000E+00 + 4.328726E+00 2.499191E+00 0 4.328726E+00 2.499191E+00 6.000000E+00 + 4.844644E+00 2.797057E+00 0 4.844644E+00 2.797057E+00 6.000000E+00 + 5.403532E+00 3.119731E+00 0 5.403532E+00 3.119731E+00 6.000000E+00 + 6.008968E+00 3.469279E+00 0 6.008968E+00 3.469279E+00 6.000000E+00 + 6.664829E+00 3.847941E+00 0 6.664829E+00 3.847941E+00 6.000000E+00 + 7.375315E+00 4.258140E+00 0 7.375315E+00 4.258140E+00 6.000000E+00 + 8.144975E+00 4.702504E+00 0 8.144975E+00 4.702504E+00 6.000000E+00 + 8.978738E+00 5.183877E+00 0 8.978738E+00 5.183877E+00 6.000000E+00 + 9.881942E+00 5.705342E+00 0 9.881942E+00 5.705342E+00 6.000000E+00 + 1.086037E+01 6.270239E+00 0 1.086037E+01 6.270239E+00 6.000000E+00 + 1.192029E+01 6.882184E+00 0 1.192029E+01 6.882184E+00 6.000000E+00 + 1.306849E+01 7.545096E+00 0 1.306849E+01 7.545096E+00 6.000000E+00 + 1.431232E+01 8.263221E+00 0 1.431232E+01 8.263221E+00 6.000000E+00 + 1.565974E+01 9.041155E+00 0 1.565974E+01 9.041155E+00 6.000000E+00 + 1.711939E+01 9.883882E+00 0 1.711939E+01 9.883882E+00 6.000000E+00 + 1.870060E+01 1.079680E+01 0 1.870060E+01 1.079680E+01 6.000000E+00 + 2.041351E+01 1.178575E+01 0 2.041351E+01 1.178575E+01 6.000000E+00 + 2.226908E+01 1.285706E+01 0 2.226908E+01 1.285706E+01 6.000000E+00 + 2.427920E+01 1.401760E+01 0 2.427920E+01 1.401760E+01 6.000000E+00 + 2.645674E+01 1.527480E+01 0 2.645674E+01 1.527480E+01 6.000000E+00 + 2.881563E+01 1.663671E+01 0 2.881563E+01 1.663671E+01 6.000000E+00 + 3.137100E+01 1.811205E+01 0 3.137100E+01 1.811205E+01 6.000000E+00 + 3.413919E+01 1.971027E+01 0 3.413919E+01 1.971027E+01 6.000000E+00 + 3.713793E+01 2.144159E+01 0 3.713793E+01 2.144159E+01 6.000000E+00 + 4.038643E+01 2.331712E+01 0 4.038643E+01 2.331712E+01 6.000000E+00 + 4.390549E+01 2.534885E+01 0 4.390549E+01 2.534885E+01 6.000000E+00 + 4.771764E+01 2.754979E+01 0 4.771764E+01 2.754979E+01 6.000000E+00 + 5.184729E+01 2.993405E+01 0 5.184729E+01 2.993405E+01 6.000000E+00 + 5.632090E+01 3.251688E+01 0 5.632090E+01 3.251688E+01 6.000000E+00 + 6.116709E+01 3.531484E+01 0 6.116709E+01 3.531484E+01 6.000000E+00 + 6.641691E+01 3.834582E+01 0 6.641691E+01 3.834582E+01 6.000000E+00 + 7.210397E+01 4.162925E+01 0 7.210397E+01 4.162925E+01 6.000000E+00 + 7.826469E+01 4.518614E+01 0 7.826469E+01 4.518614E+01 6.000000E+00 + 8.493852E+01 4.903928E+01 0 8.493852E+01 4.903928E+01 6.000000E+00 + 9.216819E+01 5.321333E+01 0 9.216819E+01 5.321333E+01 6.000000E+00 + 1.000000E+02 5.773503E+01 0 1.000000E+02 5.773503E+01 6.000000E+00