diff --git a/.integrated_tests.yaml b/.integrated_tests.yaml index de37c7e6147..9a2d47f3c6a 100644 --- a/.integrated_tests.yaml +++ b/.integrated_tests.yaml @@ -1,6 +1,6 @@ baselines: bucket: geosx - baseline: integratedTests/baseline_integratedTests-pr4057-16739-5dde641 + baseline: integratedTests/baseline_integratedTests-pr4040-16777-2d505f0 allow_fail: all: '' diff --git a/BASELINE_NOTES.md b/BASELINE_NOTES.md index 5909bd5d0bd..3a895daeabb 100644 --- a/BASELINE_NOTES.md +++ b/BASELINE_NOTES.md @@ -5,6 +5,9 @@ This file is designed to track changes to the integrated test baselines. Any developer who updates the baseline ID in the .integrated_tests.yaml file is expected to create an entry in this file with the pull request number, date, and their justification for rebaselining. These notes should be in reverse-chronological order, and use the following time format: (YYYY-MM-DD). +PR #4040 (2026-05-24) +Move relperm driver to use bew constitutive driver framework + PR #4057 (2026-05-21) Remove dependency on PVT package diff --git a/inputFiles/constitutiveDriver/constitutiveDriver.ats b/inputFiles/constitutiveDriver/constitutiveDriver.ats index 4ba8f0cc79b..0cb289572ed 100644 --- a/inputFiles/constitutiveDriver/constitutiveDriver.ats +++ b/inputFiles/constitutiveDriver/constitutiveDriver.ats @@ -12,13 +12,30 @@ def create_pvt_test(name, description=None): check_step=0, restartcheck_params=RestartcheckParameters(**restartcheck_params)) +def create_relperm_test(name, description=None): + if description is None: description = name + return TestDeck( + name=name, + description=description, + partitions=[(1, 1, 1)], + restart_step=0, + check_step=0, + restartcheck_params=RestartcheckParameters(**restartcheck_params)) + decks = [ create_pvt_test("testPVT"), create_pvt_test("testPVT_CO2Brine", "PVT Driver test for CO2 fluid models"), create_pvt_test("testPVT_CO2BrineTables", "PVT Driver test for CO2 fluid models with solubility tables"), create_pvt_test("testPVT_PhaseComposition", "PVT Driver test with composition output"), create_pvt_test("testPVT_ThreePhaseCompositional", "PVT Driver test for three phase compositional models"), - create_pvt_test("testPVT_docExample", "PVT Driver documentation example") + create_pvt_test("testPVT_docExample", "PVT Driver documentation example"), + + create_relperm_test("testRelperm_Table_2_phase", "Relperm driver for 2 phase tables"), + create_relperm_test("testRelperm_Table_3_phase", "Relperm driver for 3 phase tables"), + create_relperm_test("testRelperm_TableHyst2ph", "Two-phase hysteresis with large tables"), + create_relperm_test("testRelperm_BrooksCorey", "Three-phase Brooks-Corey with Baker and Stone II mixing rule"), + create_relperm_test("testRelperm_VanGenuchten", "Three-phase van Genuchten-Mualem models"), + create_relperm_test("testRelperm_docExample", "Relperm Driver documentation example") ] generate_geos_tests(decks) diff --git a/inputFiles/constitutiveDriver/testRelperm_BrooksCorey.xml b/inputFiles/constitutiveDriver/testRelperm_BrooksCorey.xml new file mode 100644 index 00000000000..a6fec333c09 --- /dev/null +++ b/inputFiles/constitutiveDriver/testRelperm_BrooksCorey.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inputFiles/constitutiveDriver/testRelperm_TableHyst2ph.xml b/inputFiles/constitutiveDriver/testRelperm_TableHyst2ph.xml new file mode 100644 index 00000000000..db7744e3860 --- /dev/null +++ b/inputFiles/constitutiveDriver/testRelperm_TableHyst2ph.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inputFiles/constitutiveDriver/testRelperm_Table_2_phase.xml b/inputFiles/constitutiveDriver/testRelperm_Table_2_phase.xml new file mode 100644 index 00000000000..47d1d1e4fe6 --- /dev/null +++ b/inputFiles/constitutiveDriver/testRelperm_Table_2_phase.xml @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inputFiles/constitutiveDriver/testRelperm_Table_3_phase.xml b/inputFiles/constitutiveDriver/testRelperm_Table_3_phase.xml new file mode 100644 index 00000000000..88784e04ac9 --- /dev/null +++ b/inputFiles/constitutiveDriver/testRelperm_Table_3_phase.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inputFiles/constitutiveDriver/testRelperm_VanGenuchten.xml b/inputFiles/constitutiveDriver/testRelperm_VanGenuchten.xml new file mode 100644 index 00000000000..cb6183319e5 --- /dev/null +++ b/inputFiles/constitutiveDriver/testRelperm_VanGenuchten.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inputFiles/relpermDriver/drainagePhaseVolFraction_gas.txt b/inputFiles/constitutiveDriver/testRelperm_data/drainagePhaseVolFraction_gas.txt similarity index 100% rename from inputFiles/relpermDriver/drainagePhaseVolFraction_gas.txt rename to inputFiles/constitutiveDriver/testRelperm_data/drainagePhaseVolFraction_gas.txt diff --git a/inputFiles/relpermDriver/drainagePhaseVolFraction_water.txt b/inputFiles/constitutiveDriver/testRelperm_data/drainagePhaseVolFraction_water.txt similarity index 100% rename from inputFiles/relpermDriver/drainagePhaseVolFraction_water.txt rename to inputFiles/constitutiveDriver/testRelperm_data/drainagePhaseVolFraction_water.txt diff --git a/inputFiles/relpermDriver/drainageRelPerm_gas.txt b/inputFiles/constitutiveDriver/testRelperm_data/drainageRelPerm_gas.txt similarity index 100% rename from inputFiles/relpermDriver/drainageRelPerm_gas.txt rename to inputFiles/constitutiveDriver/testRelperm_data/drainageRelPerm_gas.txt diff --git a/inputFiles/relpermDriver/drainageRelPerm_water.txt b/inputFiles/constitutiveDriver/testRelperm_data/drainageRelPerm_water.txt similarity index 100% rename from inputFiles/relpermDriver/drainageRelPerm_water.txt rename to inputFiles/constitutiveDriver/testRelperm_data/drainageRelPerm_water.txt diff --git a/inputFiles/relpermDriver/imbibitionPhaseVolFraction_gas.txt b/inputFiles/constitutiveDriver/testRelperm_data/imbibitionPhaseVolFraction_gas.txt similarity index 100% rename from inputFiles/relpermDriver/imbibitionPhaseVolFraction_gas.txt rename to inputFiles/constitutiveDriver/testRelperm_data/imbibitionPhaseVolFraction_gas.txt diff --git a/inputFiles/relpermDriver/imbibitionPhaseVolFraction_water.txt b/inputFiles/constitutiveDriver/testRelperm_data/imbibitionPhaseVolFraction_water.txt similarity index 100% rename from inputFiles/relpermDriver/imbibitionPhaseVolFraction_water.txt rename to inputFiles/constitutiveDriver/testRelperm_data/imbibitionPhaseVolFraction_water.txt diff --git a/inputFiles/relpermDriver/imbibitionRelPerm_gas.txt b/inputFiles/constitutiveDriver/testRelperm_data/imbibitionRelPerm_gas.txt similarity index 100% rename from inputFiles/relpermDriver/imbibitionRelPerm_gas.txt rename to inputFiles/constitutiveDriver/testRelperm_data/imbibitionRelPerm_gas.txt diff --git a/inputFiles/relpermDriver/imbibitionRelPerm_water.txt b/inputFiles/constitutiveDriver/testRelperm_data/imbibitionRelPerm_water.txt similarity index 100% rename from inputFiles/relpermDriver/imbibitionRelPerm_water.txt rename to inputFiles/constitutiveDriver/testRelperm_data/imbibitionRelPerm_water.txt diff --git a/inputFiles/constitutiveDriver/testRelperm_data/plot_relperm.py b/inputFiles/constitutiveDriver/testRelperm_data/plot_relperm.py new file mode 100644 index 00000000000..5c7c6b67e25 --- /dev/null +++ b/inputFiles/constitutiveDriver/testRelperm_data/plot_relperm.py @@ -0,0 +1,51 @@ +import numpy as np +import matplotlib.pyplot as plt +from matplotlib.collections import LineCollection +import os + +# Set global font size to 12 +plt.rcParams.update({'font.size': 12}) + +# Load data (ignore comment lines starting with '#') +data_dir = "." +data_dir = os.path.normpath(os.path.abspath(data_dir)) +file_name = "test_relperm_sandstone_hysteresis.txt" +data_file = os.path.join(data_dir, "..", file_name) +if not os.path.exists(data_file): + data_file = os.path.join(data_dir, file_name) +data = np.loadtxt(data_file) + +# Columns: +# col 2 = gas saturation (Sg) +# col 4 = historical maximum gas saturation (Sghy) +# col 6 = gas relperm (krg) +Sg = data[:, 1] +krg = data[:, 5] +Sghy = data[:, 3] + +# Create segments for the colored line +points = np.array([Sg, krg]).T.reshape(-1, 1, 2) +segments = np.concatenate([points[:-1], points[1:]], axis=1) + +fig, ax = plt.subplots() + +# Create the LineCollection +norm = plt.Normalize(Sghy.min(), Sghy.max()) +lc = LineCollection(segments, cmap='jet', norm=norm) + +# Set the colors based on the average Sghy of the two points making up each segment +lc.set_array(0.5 * (Sghy[:-1] + Sghy[1:])) +lc.set_linewidth(2) # 2pt line width + +line = ax.add_collection(lc) + +# Autoscale the axes since add_collection doesn't do it automatically +ax.autoscale() + +ax.set_xlabel('Gas saturation (Sg)') +ax.set_ylabel('Gas relative permeability (krg)') +ax.set_title('Gas RelPerm vs Gas Saturation') +fig.colorbar(line, ax=ax, label='Sghy') +ax.grid(True, alpha=0.3) + +plt.show() diff --git a/inputFiles/constitutiveDriver/testRelperm_data/relperm_tables.xml b/inputFiles/constitutiveDriver/testRelperm_data/relperm_tables.xml new file mode 100644 index 00000000000..20f40c23458 --- /dev/null +++ b/inputFiles/constitutiveDriver/testRelperm_data/relperm_tables.xml @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/inputFiles/constitutiveDriver/testRelperm_data/test_relperm_sandstone_hysteresis.txt b/inputFiles/constitutiveDriver/testRelperm_data/test_relperm_sandstone_hysteresis.txt new file mode 100644 index 00000000000..826611c60fb --- /dev/null +++ b/inputFiles/constitutiveDriver/testRelperm_data/test_relperm_sandstone_hysteresis.txt @@ -0,0 +1,88 @@ +# column 1 = index +# column 2 = saturation,gas +# column 3 = saturation,water +# column 4 = historical saturation,gas +# column 5 = historical saturation,water +# column 6 = relperm,gas +# column 7 = relperm,water + 0.0000e+00 0.0000e+00 1.0000e+00 0.0000e+00 1.0000e+00 0.0000e+00 1.0000e+00 + 5.0000e-02 2.0000e-02 9.8000e-01 2.0000e-02 9.8000e-01 1.1765e-03 9.1062e-01 + 1.0000e-01 4.0000e-02 9.6000e-01 4.0000e-02 9.6000e-01 2.3529e-03 8.2124e-01 + 1.5000e-01 6.0000e-02 9.4000e-01 6.0000e-02 9.4000e-01 3.5899e-03 7.3234e-01 + 2.0000e-01 8.0000e-02 9.2000e-01 8.0000e-02 9.2000e-01 7.1866e-03 6.6209e-01 + 2.5000e-01 1.0000e-01 9.0000e-01 1.0000e-01 9.0000e-01 1.0783e-02 5.9184e-01 + 3.0000e-01 1.2000e-01 8.8000e-01 1.2000e-01 8.8000e-01 1.4497e-02 5.2240e-01 + 3.5000e-01 1.4000e-01 8.6000e-01 1.4000e-01 8.6000e-01 2.0447e-02 4.6835e-01 + 4.0000e-01 1.6000e-01 8.4000e-01 1.6000e-01 8.4000e-01 2.6397e-02 4.1430e-01 + 4.5000e-01 1.8000e-01 8.2000e-01 1.8000e-01 8.2000e-01 3.2537e-02 3.6132e-01 + 5.0000e-01 2.0000e-01 8.0000e-01 2.0000e-01 8.0000e-01 4.1038e-02 3.2155e-01 + 5.5000e-01 2.2000e-01 7.8000e-01 2.2000e-01 7.8000e-01 4.9538e-02 2.8179e-01 + 6.0000e-01 2.4000e-01 7.6000e-01 2.4000e-01 7.6000e-01 5.8038e-02 2.4202e-01 + 6.5000e-01 2.6000e-01 7.4000e-01 2.6000e-01 7.4000e-01 6.8242e-02 2.1011e-01 + 7.0000e-01 2.8000e-01 7.2000e-01 2.8000e-01 7.2000e-01 7.9267e-02 1.8197e-01 + 7.5000e-01 3.0000e-01 7.0000e-01 3.0000e-01 7.0000e-01 9.0292e-02 1.5384e-01 + 8.0000e-01 3.2000e-01 6.8000e-01 3.2000e-01 6.8000e-01 1.0301e-01 1.3166e-01 + 8.5000e-01 3.4000e-01 6.6000e-01 3.4000e-01 6.6000e-01 1.1646e-01 1.1203e-01 + 9.0000e-01 3.6000e-01 6.4000e-01 3.6000e-01 6.4000e-01 1.2990e-01 9.2398e-02 + 9.5000e-01 3.8000e-01 6.2000e-01 3.8000e-01 6.2000e-01 1.4505e-01 7.7569e-02 + 1.0000e+00 4.0000e-01 6.0000e-01 4.0000e-01 6.0000e-01 1.6085e-01 6.4561e-02 + 1.0500e+00 3.9500e-01 6.0500e-01 4.0000e-01 6.0000e-01 1.5193e-01 6.7813e-02 + 1.1000e+00 3.9000e-01 6.1000e-01 4.0000e-01 6.0000e-01 1.4301e-01 7.1065e-02 + 1.1500e+00 3.8500e-01 6.1500e-01 4.0000e-01 6.0000e-01 1.3409e-01 7.4317e-02 + 1.2000e+00 3.8000e-01 6.2000e-01 4.0000e-01 6.0000e-01 1.2517e-01 7.7569e-02 + 1.2500e+00 3.7500e-01 6.2500e-01 4.0000e-01 6.0000e-01 1.1724e-01 8.0821e-02 + 1.3000e+00 3.7000e-01 6.3000e-01 4.0000e-01 6.0000e-01 1.0977e-01 8.4073e-02 + 1.3500e+00 3.6500e-01 6.3500e-01 4.0000e-01 6.0000e-01 1.0230e-01 8.7491e-02 + 1.4000e+00 3.6000e-01 6.4000e-01 4.0000e-01 6.0000e-01 9.4837e-02 9.2398e-02 + 1.4500e+00 3.5500e-01 6.4500e-01 4.0000e-01 6.0000e-01 8.7855e-02 9.7306e-02 + 1.5000e+00 3.5000e-01 6.5000e-01 4.0000e-01 6.0000e-01 8.1742e-02 1.0221e-01 + 1.5500e+00 3.4500e-01 6.5500e-01 4.0000e-01 6.0000e-01 7.5629e-02 1.0712e-01 + 1.6000e+00 3.4000e-01 6.6000e-01 4.0000e-01 6.0000e-01 6.9517e-02 1.1203e-01 + 1.6500e+00 3.3500e-01 6.6500e-01 4.0000e-01 6.0000e-01 6.3451e-02 1.1694e-01 + 1.7000e+00 3.3000e-01 6.7000e-01 4.0000e-01 6.0000e-01 5.8588e-02 1.2184e-01 + 1.7500e+00 3.2500e-01 6.7500e-01 4.0000e-01 6.0000e-01 5.3725e-02 1.2675e-01 + 1.8000e+00 3.2000e-01 6.8000e-01 4.0000e-01 6.0000e-01 4.8862e-02 1.3166e-01 + 1.8500e+00 3.1500e-01 6.8500e-01 4.0000e-01 6.0000e-01 4.3999e-02 1.3657e-01 + 1.9000e+00 3.1000e-01 6.9000e-01 4.0000e-01 6.0000e-01 3.9952e-02 1.4147e-01 + 1.9500e+00 3.0500e-01 6.9500e-01 4.0000e-01 6.0000e-01 3.6228e-02 1.4681e-01 + 2.0000e+00 3.0000e-01 7.0000e-01 4.0000e-01 6.0000e-01 3.2504e-02 1.5384e-01 + 2.0500e+00 3.1500e-01 6.8500e-01 4.0000e-01 6.0000e-01 4.3999e-02 1.3657e-01 + 2.1000e+00 3.3000e-01 6.7000e-01 4.0000e-01 6.0000e-01 5.8588e-02 1.2184e-01 + 2.1500e+00 3.4500e-01 6.5500e-01 4.0000e-01 6.0000e-01 7.5629e-02 1.0712e-01 + 2.2000e+00 3.6000e-01 6.4000e-01 4.0000e-01 6.0000e-01 9.4837e-02 9.2398e-02 + 2.2500e+00 3.7500e-01 6.2500e-01 4.0000e-01 6.0000e-01 1.1724e-01 8.0821e-02 + 2.3000e+00 3.9000e-01 6.1000e-01 4.0000e-01 6.0000e-01 1.4301e-01 7.1065e-02 + 2.3500e+00 4.0500e-01 5.9500e-01 4.0500e-01 5.9500e-01 1.6480e-01 6.1308e-02 + 2.4000e+00 4.2000e-01 5.8000e-01 4.2000e-01 5.8000e-01 1.7665e-01 5.1552e-02 + 2.4500e+00 4.3500e-01 5.6500e-01 4.3500e-01 5.6500e-01 1.8969e-01 4.4250e-02 + 2.5000e+00 4.5000e-01 5.5000e-01 4.5000e-01 5.5000e-01 2.0333e-01 3.8174e-02 + 2.5500e+00 4.6500e-01 5.3500e-01 4.6500e-01 5.3500e-01 2.1697e-01 3.2098e-02 + 2.6000e+00 4.8000e-01 5.2000e-01 4.8000e-01 5.2000e-01 2.3061e-01 2.6023e-02 + 2.6500e+00 4.9500e-01 5.0500e-01 4.9500e-01 5.0500e-01 2.4550e-01 2.1782e-02 + 2.7000e+00 5.1000e-01 4.9000e-01 5.1000e-01 4.9000e-01 2.6093e-01 1.8329e-02 + 2.7500e+00 5.2500e-01 4.7500e-01 5.2500e-01 4.7500e-01 2.7636e-01 1.4875e-02 + 2.8000e+00 5.4000e-01 4.6000e-01 5.4000e-01 4.6000e-01 2.9179e-01 1.1421e-02 + 2.8500e+00 5.5500e-01 4.4500e-01 5.5500e-01 4.4500e-01 3.0853e-01 9.2244e-03 + 2.9000e+00 5.7000e-01 4.3000e-01 5.7000e-01 4.3000e-01 3.2575e-01 7.4849e-03 + 2.9500e+00 5.8500e-01 4.1500e-01 5.8500e-01 4.1500e-01 3.4296e-01 5.7454e-03 + 3.0000e+00 6.0000e-01 4.0000e-01 6.0000e-01 4.0000e-01 3.6018e-01 4.0059e-03 + 3.0500e+00 5.9000e-01 4.1000e-01 6.0000e-01 4.0000e-01 3.3743e-01 5.1655e-03 + 3.1000e+00 5.8000e-01 4.2000e-01 6.0000e-01 4.0000e-01 3.1468e-01 6.3252e-03 + 3.1500e+00 5.7000e-01 4.3000e-01 6.0000e-01 4.0000e-01 2.9193e-01 7.4849e-03 + 3.2000e+00 5.6000e-01 4.4000e-01 6.0000e-01 4.0000e-01 2.6994e-01 8.6445e-03 + 3.2500e+00 5.5000e-01 4.5000e-01 6.0000e-01 4.0000e-01 2.5090e-01 9.8042e-03 + 3.3000e+00 5.4000e-01 4.6000e-01 6.0000e-01 4.0000e-01 2.3185e-01 1.1421e-02 + 3.3500e+00 5.3000e-01 4.7000e-01 6.0000e-01 4.0000e-01 2.1281e-01 1.3724e-02 + 3.4000e+00 5.2000e-01 4.8000e-01 6.0000e-01 4.0000e-01 1.9519e-01 1.6026e-02 + 3.4500e+00 5.1000e-01 4.9000e-01 6.0000e-01 4.0000e-01 1.7960e-01 1.8329e-02 + 3.5000e+00 5.0000e-01 5.0000e-01 6.0000e-01 4.0000e-01 1.6401e-01 2.0631e-02 + 3.5500e+00 4.9000e-01 5.1000e-01 6.0000e-01 4.0000e-01 1.4842e-01 2.2934e-02 + 3.6000e+00 4.8000e-01 5.2000e-01 6.0000e-01 4.0000e-01 1.3480e-01 2.6023e-02 + 3.6500e+00 4.7000e-01 5.3000e-01 6.0000e-01 4.0000e-01 1.2240e-01 3.0073e-02 + 3.7000e+00 4.6000e-01 5.4000e-01 6.0000e-01 4.0000e-01 1.0999e-01 3.4124e-02 + 3.7500e+00 4.5000e-01 5.5000e-01 6.0000e-01 4.0000e-01 9.7590e-02 3.8174e-02 + 3.8000e+00 4.4000e-01 5.6000e-01 6.0000e-01 4.0000e-01 8.7588e-02 4.2224e-02 + 3.8500e+00 4.3000e-01 5.7000e-01 6.0000e-01 4.0000e-01 7.8090e-02 4.6275e-02 + 3.9000e+00 4.2000e-01 5.8000e-01 6.0000e-01 4.0000e-01 6.8592e-02 5.1552e-02 + 3.9500e+00 4.1000e-01 5.9000e-01 6.0000e-01 4.0000e-01 5.9180e-02 5.8056e-02 + 4.0000e+00 4.0000e-01 6.0000e-01 6.0000e-01 4.0000e-01 5.2287e-02 6.4561e-02 diff --git a/inputFiles/constitutiveDriver/testRelperm_docExample.xml b/inputFiles/constitutiveDriver/testRelperm_docExample.xml new file mode 100644 index 00000000000..249efbacd06 --- /dev/null +++ b/inputFiles/constitutiveDriver/testRelperm_docExample.xml @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inputFiles/relpermDriver/testRelpermDriverBCBaker.xml b/inputFiles/relpermDriver/testRelpermDriverBCBaker.xml deleted file mode 100644 index 0a7c0f3018e..00000000000 --- a/inputFiles/relpermDriver/testRelpermDriverBCBaker.xml +++ /dev/null @@ -1,52 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/inputFiles/relpermDriver/testRelpermDriverBCStoneII.xml b/inputFiles/relpermDriver/testRelpermDriverBCStoneII.xml deleted file mode 100644 index 80c5ab16abb..00000000000 --- a/inputFiles/relpermDriver/testRelpermDriverBCStoneII.xml +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/inputFiles/relpermDriver/testRelpermDriverTableHyst2ph.xml b/inputFiles/relpermDriver/testRelpermDriverTableHyst2ph.xml deleted file mode 100644 index 60614391abf..00000000000 --- a/inputFiles/relpermDriver/testRelpermDriverTableHyst2ph.xml +++ /dev/null @@ -1,74 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/coreComponents/constitutive/relativePermeability/RelativePermeabilitySelector.hpp b/src/coreComponents/constitutive/relativePermeability/RelativePermeabilitySelector.hpp index ee20a4c4f6e..fbba10578b8 100644 --- a/src/coreComponents/constitutive/relativePermeability/RelativePermeabilitySelector.hpp +++ b/src/coreComponents/constitutive/relativePermeability/RelativePermeabilitySelector.hpp @@ -51,7 +51,7 @@ void constitutiveUpdatePassThru( RelativePermeabilityBase const & relPerm, BrooksCoreyBakerRelativePermeability, BrooksCoreyStone2RelativePermeability, TableRelativePermeability, - constitutive::TableRelativePermeabilityHysteresis, + TableRelativePermeabilityHysteresis, VanGenuchtenBakerRelativePermeability, VanGenuchtenStone2RelativePermeability >::execute( relPerm, std::forward< LAMBDA >( lambda ) ); } @@ -64,7 +64,7 @@ void constitutiveUpdatePassThru( RelativePermeabilityBase & relPerm, BrooksCoreyBakerRelativePermeability, BrooksCoreyStone2RelativePermeability, TableRelativePermeability, - constitutive::TableRelativePermeabilityHysteresis, + TableRelativePermeabilityHysteresis, VanGenuchtenBakerRelativePermeability, VanGenuchtenStone2RelativePermeability >::execute( relPerm, std::forward< LAMBDA >( lambda ) ); } diff --git a/src/coreComponents/constitutiveDrivers/docs/ConstitutiveDrivers.rst b/src/coreComponents/constitutiveDrivers/docs/ConstitutiveDrivers.rst index 8ad6a17ca83..2520aa8c568 100644 --- a/src/coreComponents/constitutiveDrivers/docs/ConstitutiveDrivers.rst +++ b/src/coreComponents/constitutiveDrivers/docs/ConstitutiveDrivers.rst @@ -11,3 +11,5 @@ These drivers are designed to facilitate the exploration of various constitutive PVTDriver TriaxialDriver + + RelpermDriver diff --git a/src/coreComponents/constitutiveDrivers/docs/RelpermDriver.rst b/src/coreComponents/constitutiveDrivers/docs/RelpermDriver.rst new file mode 100644 index 00000000000..53067ac7361 --- /dev/null +++ b/src/coreComponents/constitutiveDrivers/docs/RelpermDriver.rst @@ -0,0 +1,134 @@ +Relperm Driver +=============== + +.. contents:: Table of Contents + :depth: 3 + +Introduction +------------ + +Fitting relative permeability parameters to experimental or benchmark data often should not require a full flow simulation, when the only goal is to check that the relative permeability curves respond correctly to changes in phase saturation. As such, GEOS provides a ``RelpermDriver`` allowing the user to test relative permeability models for a well-defined sweep of saturation conditions. The driver itself is launched like any other GEOS simulation, but with a particular XML structure: + +.. code-block:: sh + + ./bin/geosx -i myRelpermTest.xml + +This driver will work for any 2-phase or 3-phase relative permeability model enabled within GEOS. + +XML Structure +------------- +A typical XML file to run the driver will have several key elements. Here, we will walk through an example file included in the source tree at: + +.. code-block:: sh + + inputFiles/constitutiveDriver/testRelperm_docExample.xml + +A key point is that the XML file follows the same structure as a typical GEOS input deck. As with the other constitutive drivers, once the constitutive block has been calibrated, solver and discretization sections can be added directly to transform it into a full field simulation setup. This continuity allows for smooth transitions between calibration and simulation workflows. + +The first step is to define a parameterized relative permeability model to test. Here, we create particular types of table-based two-phase relative permeabilities for sandstone, one standard and one with hysteresis: + +.. literalinclude:: ../../../../inputFiles/constitutiveDriver/testRelperm_docExample.xml + :language: xml + :start-after: + :end-before: + +We also define the underlying table functions used by the model. + +.. literalinclude:: ../../../../inputFiles/constitutiveDriver/testRelperm_docExample.xml + :language: xml + :start-after: + :end-before: + +A ``RelpermDriver`` is then added as a ``Task``, a particular type of executable event often used for simple actions. + +.. literalinclude:: ../../../../inputFiles/constitutiveDriver/testRelperm_docExample.xml + :language: xml + :start-after: + :end-before: + +The driver itself takes as input the relative permeability model (referenced by the ``relperm`` parameter). The model is evaluated over time increments defined by the ``steps`` parameter, while the saturations at each step are evaluated using functions provided in the ``saturationControls`` array. Results will be written in a simple ASCII table format to a specified file if an ``output`` parameter is provided; otherwise, it is written to the standard log. + +The driver task is added as a ``SoloEvent`` to the event queue. + +.. literalinclude:: ../../../../inputFiles/constitutiveDriver/testRelperm_docExample.xml + :language: xml + :start-after: + :end-before: + +Parameters +---------- +The key XML parameters for the RelpermDriver are summarized in the following table: + +.. include:: /docs/sphinx/datastructure/RelpermDriver.rst + +Saturation Assignment +--------------------- +The phase saturations are explicitly driven by user-defined functions over time. + +To configure the driver, the user must specify a list of target phases via ``phaseNames`` and corresponding time-history functions via ``saturationControls``. For an $N$-phase model, exactly $N-1$ phases and saturation functions must be provided. The saturation of the remaining implicit phase is calculated automatically by the driver to ensure the phase volume fractions (saturations) always sum to exactly 1.0. + +During execution, the driver evaluates the provided functions at each step. To ensure physical validity: +* If a saturation function evaluates to a negative value at a given step, the saturation is clamped to zero. +* If the evaluated saturations sum to a value greater than 1.0, they are proportionally scaled down so that their collective sum equals 1.0, and the implicit phase is assigned a saturation of zero. + +Examples and Hysteresis +----------------------- +The documentation example contains two distinct driver tests that showcase different saturation trajectories: + +1. **Standard Relperm Test (``test_sandstone``):** This test evaluates a standard relative permeability model. The gas saturation is driven by the ``gasSaturation`` function, which simply increases linearly from 0.0 to 1.0 over the evaluation time. +2. **Hysteresis Relperm Test (``test_sandstone_hysteresis``):** This test evaluates a hysteresis-enabled model and requires a non-monotonic saturation path to test historical tracking. It uses the ``gasSaturationHysteresis`` function, which dynamically varies the gas saturation: rising from 0.0 to 0.4 (primary drainage), dropping to 0.3 (imbibition), rising again to 0.6 (secondary drainage), and finally dropping to 0.4 (secondary imbibition). + +These saturation functions are explicitly defined in the XML as shown below: + +.. literalinclude:: ../../../../inputFiles/constitutiveDriver/testRelperm_docExample.xml + :language: xml + :start-after: + :end-before: + +.. plot:: ../inputFiles/constitutiveDriver/testRelperm_data/plot_relperm.py + +Output of the ``test_sandstone_hysteresis`` task. The plotted trajectory follows the defined ``gasSaturationHysteresis`` values ``{ 0.0, 0.4, 0.3, 0.6, 0.4 }``. The color scale indicates the internally tracked maximum historical gas saturation (Sghy), dictating the departure curves: + +* **Primary Drainage (0.0 to 0.4):** Sghy updates alongside the current saturation (dark blue to yellow). +* **Primary Imbibition (0.4 to 0.3):** The curve drops from the main envelope; Sghy remains constant at 0.4 (yellow). +* **Secondary Drainage (0.3 to 0.6):** The curve climbs back up, with Sghy resuming updates past 0.4 until it hits 0.6 (dark red). +* **Secondary Imbibition (0.6 to 0.4):** The relative permeability drops along a new, lower path, while Sghy stays fixed at 0.6 (dark red). + +Output Format +------------- +The ``output`` key is used to identify a file to which the results of the simulation are written. If this key is omitted, file output will be suppressed and instead the resulting table will be output to the screen. When written to standard output, the data is written in a table format similar to the one below. + +.. code:: sh + + --------------------------------------------------------------------------------- + | Output for test_sandstone | + |-------------------------------------------------------------------------------| + | index | saturation | relperm | + |---------------|-------------------------------|-------------------------------| + | | gas | water | gas | water | + |---------------|---------------|---------------|---------------|---------------| + | 0.0000e+00 | 0.0000e+00 | 1.0000e+00 | 0.0000e+00 | 1.0000e+00 | + | 1.0000e+00 | 3.9525e-02 | 9.6047e-01 | 2.3250e-03 | 8.2337e-01 | + | 2.0000e+00 | 7.9050e-02 | 9.2095e-01 | 7.0157e-03 | 6.6543e-01 | + | 3.0000e+00 | 1.1857e-01 | 8.8143e-01 | 1.4124e-02 | 5.2659e-01 | + --------------------------------------------------------------------------------- + +When written to a file, the file is a simple ASCII format with a brief header followed by the actual saturation data. An example is shown below. + +.. code:: sh + + # column 1 = index + # column 2 = saturation,gas + # column 3 = saturation,water + # column 4 = relperm,gas + # column 5 = relperm,water + 0.0000e+00 1.0000e+00 0.0000e+00 1.0000e+00 0.0000e+00 + 2.0000e-01 8.0000e-01 2.0000e-01 7.0006e-01 7.1429e-04 + 4.0000e-01 6.0000e-01 4.0000e-01 4.4233e-01 1.5671e-02 + 6.0000e-01 4.0000e-01 6.0000e-01 2.3163e-01 9.8057e-02 + 8.0000e-01 2.0000e-01 8.0000e-01 7.6686e-02 3.6119e-01 + 1.0000e+00 0.0000e+00 1.0000e+00 0.0000e+00 1.0000e+00 + +The exact number of columns will depend on the phase count configured in the chosen model. If hysteresis is activated on the model, additional columns tracking historical extremum saturations will automatically be present between the instantaneous saturations and the calculated relative permeabilities. This file can be readily plotted using any number of plotting tools. Each row corresponds to one timestep of the driver, starting from initial conditions in the first row. + + diff --git a/src/coreComponents/constitutiveDrivers/relativePermeability/RelpermDriver.cpp b/src/coreComponents/constitutiveDrivers/relativePermeability/RelpermDriver.cpp index ca17f97223f..8a136a5e5e2 100644 --- a/src/coreComponents/constitutiveDrivers/relativePermeability/RelpermDriver.cpp +++ b/src/coreComponents/constitutiveDrivers/relativePermeability/RelpermDriver.cpp @@ -13,15 +13,15 @@ * ------------------------------------------------------------------------------------------------------------ */ -#include "common/MpiWrapper.hpp" -#include "functions/FunctionManager.hpp" -#include "functions/TableFunction.hpp" +#include "RelpermDriver.hpp" + #include "constitutive/ConstitutiveManager.hpp" #include "constitutiveDrivers/LogLevelsInfo.hpp" #include "constitutive/relativePermeability/RelativePermeabilityBase.hpp" #include "constitutive/relativePermeability/RelativePermeabilitySelector.hpp" - -#include "RelpermDriver.hpp" +#include "functions/FunctionManager.hpp" +#include "functions/TableFunction.hpp" +#include "common/format/StringUtilities.hpp" namespace geos { @@ -29,306 +29,221 @@ namespace geos using namespace dataRepository; using namespace constitutive; -RelpermDriver::RelpermDriver( const geos::string & name, - geos::dataRepository::Group * const parent ) - : - TaskBase( name, parent ) +RelpermDriver::RelpermDriver( const string & name, + Group * const parent ) + : ConstitutiveDriver( name, parent ) { registerWrapper( viewKeyStruct::relpermNameString(), &m_relpermName ). setRTTypeName( rtTypes::CustomTypes::groupNameRef ). setInputFlag( InputFlags::REQUIRED ). - setDescription( "Relperm model to test" ); + setDescription( "Relative permeability model to test" ); - registerWrapper( viewKeyStruct::numStepsString(), &m_numSteps ). + registerWrapper( viewKeyStruct::phaseNamesString(), &m_phaseNames ). setInputFlag( InputFlags::REQUIRED ). - setDescription( "Number of saturation steps to take" ); + setDescription( "The names of the phases for which saturations are defined" ); - registerWrapper( viewKeyStruct::outputString(), &m_outputFile ). - setInputFlag( InputFlags::OPTIONAL ). - setApplyDefaultValue( "none" ). - setDescription( "Output file" ); + registerWrapper( viewKeyStruct::saturationFunctionsString(), &m_saturationFunctionNames ). + setInputFlag( InputFlags::REQUIRED ). + setDescription( "Functions controlling saturation time history of the selected phases" ); +} - registerWrapper( viewKeyStruct::baselineString(), &m_baselineFile ). - setInputFlag( InputFlags::OPTIONAL ). - setApplyDefaultValue( "none" ). - setDescription( "Baseline file" ); +void RelpermDriver::postInputInitialization() +{ + ConstitutiveDriver::postInputInitialization(); - addLogLevel< logInfo::LogOutput >(); -} + RelativePermeabilityBase const & baseRelperm = getRelperm(); + integer const numPhases = baseRelperm.numFluidPhases(); -void RelpermDriver::outputResults() -{ - // TODO: improve file path output to grab command line -o directory - // for the moment, we just use the specified m_outputFile directly + // Must be 2-phase or 3-phase + GEOS_ERROR_IF( numPhases < 2 || 3 < numPhases, + "Number of phases for relative permeability model must be 2 or 3", + getWrapperDataContext( viewKeyStruct::relpermNameString() ) ); - FILE * fp = fopen( m_outputFile.c_str(), "w" ); + // Table functions should be provided for np-1 phases + integer const numSelectedPhases = m_phaseNames.size(); + GEOS_ERROR_IF( numSelectedPhases != numPhases - 1, + GEOS_FMT( "Number of selected phases should be {} not {}", numPhases - 1, numSelectedPhases ), + getWrapperDataContext( viewKeyStruct::phaseNamesString() ) ); - fprintf( fp, "# column 1 = time\n" ); - fprintf( fp, "# columns %d-%d = phase vol fractions\n", 2, 1 + m_numPhases ); - fprintf( fp, "# columns %d-%d = phase relperm\n", 2 + m_numPhases, 1 + 2 * m_numPhases ); + integer const numFunctions = m_saturationFunctionNames.size(); + GEOS_ERROR_IF( numFunctions != numSelectedPhases, + "Number of saturations functions should match the number of selected phases", + getWrapperDataContext( viewKeyStruct::saturationFunctionsString() ) ); - if( ( m_numPhases == 2 && m_table.size( 1 ) > 5 ) || m_table.size( 1 ) > 7 ) + // Check that the phase names are valid + std::set< string > const relpermPhases( baseRelperm.phaseNames().begin(), baseRelperm.phaseNames().end()); + std::set< string > seenPhases; + for( auto const & phaseName : m_phaseNames ) { - fprintf( fp, "# columns %d-%d = phase relperm (hyst)\n", 1 + 2 * m_numPhases, 1 + 3 * m_numPhases ); - } + GEOS_ERROR_IF ( relpermPhases.find( phaseName ) == relpermPhases.end(), + GEOS_FMT( "Phase {} is not in the list of allowed phases for the relative permeability model", phaseName ), + getWrapperDataContext( viewKeyStruct::phaseNamesString() ) ); + GEOS_ERROR_IF ( seenPhases.find( phaseName ) != seenPhases.end(), + GEOS_FMT( "Phase {} is repeated in the list of selected phases", phaseName ), + getWrapperDataContext( viewKeyStruct::phaseNamesString() ) ); - for( integer n = 0; n < m_table.size( 0 ); ++n ) - { - for( integer col = 0; col < m_table.size( 1 ); ++col ) - { - fprintf( fp, "%.4e ", m_table( n, col ) ); - } - fprintf( fp, "\n" ); + seenPhases.insert( phaseName ); } - fclose( fp ); + // Check that the functions exist + FunctionManager & functionManager = FunctionManager::getInstance(); + for( auto const & functionName : m_saturationFunctionNames ) + { + GEOS_ERROR_IF( !functionManager.hasGroup< TableFunction >( functionName ), + GEOS_FMT( "Saturation function with name '{}' not found", functionName ), + getWrapperDataContext( viewKeyStruct::saturationFunctionsString() ) ); + } -} - + string_array columnNames; + getColumnNames( columnNames ); + integer const numCols = static_cast< integer >(columnNames.size()); -void RelpermDriver::postInputInitialization() -{ - ConstitutiveManager - & constitutiveManager = this->getGroupByPath< ConstitutiveManager >( "/Problem/domain/Constitutive" ); - RelativePermeabilityBase - & baseRelperm = constitutiveManager.getGroup< RelativePermeabilityBase >( m_relpermName ); + // Initialize functions and extract limits + real64 minTime = LvArray::NumericLimits< real64 >::max; + real64 maxTime = -LvArray::NumericLimits< real64 >::max; + for( auto const & functionName : m_saturationFunctionNames ) + { + TableFunction & function = functionManager.getGroup< TableFunction >( functionName ); + function.initializeFunction(); + ArrayOfArraysView< real64 > coordinates = function.getCoordinates(); + minTime = LvArray::math::min( minTime, coordinates[0][0] ); + maxTime = LvArray::math::max( maxTime, coordinates[0][coordinates.sizeOfArray( 0 )-1] ); + } - m_numPhases = baseRelperm.numFluidPhases(); + // Allocate the data + allocateTable( numCols, minTime, maxTime ); + // Populate the data + initializeTable( baseRelperm ); } - -bool RelpermDriver::execute( const geos::real64 GEOS_UNUSED_PARAM( time_n ), - const geos::real64 GEOS_UNUSED_PARAM( dt ), - const geos::integer GEOS_UNUSED_PARAM( cycleNumber ), - const geos::integer GEOS_UNUSED_PARAM( eventCounter ), - const geos::real64 GEOS_UNUSED_PARAM( eventProgress ), - geos::DomainPartition & - GEOS_UNUSED_PARAM( domain ) ) +bool RelpermDriver::execute() { - // this code only makes sense in serial - - GEOS_THROW_IF( MpiWrapper::commRank() > 0, "RelpermDriver should only be run in serial", geos::RuntimeError ); - + RelativePermeabilityBase & baseRelperm = getRelperm(); - ConstitutiveManager - & constitutiveManager = this->getGroupByPath< ConstitutiveManager >( "/Problem/domain/Constitutive" ); - RelativePermeabilityBase - & baseRelperm = constitutiveManager.getGroup< RelativePermeabilityBase >( m_relpermName ); + integer const numPhases = baseRelperm.numFluidPhases(); GEOS_LOG_LEVEL_RANK_0( logInfo::LogOutput, "Launching Relperm Driver" ); - GEOS_LOG_LEVEL_RANK_0( logInfo::LogOutput, " Relperm .................. " << m_relpermName ); + GEOS_LOG_LEVEL_RANK_0( logInfo::LogOutput, " Relperm ................ " << m_relpermName ); GEOS_LOG_LEVEL_RANK_0( logInfo::LogOutput, " Type ................... " << baseRelperm.getCatalogName() ); - GEOS_LOG_LEVEL_RANK_0( logInfo::LogOutput, " No. of Phases .......... " << m_numPhases ); + GEOS_LOG_LEVEL_RANK_0( logInfo::LogOutput, " No. of Phases .......... " << numPhases ); GEOS_LOG_LEVEL_RANK_0( logInfo::LogOutput, " Steps .................. " << m_numSteps ); - GEOS_LOG_LEVEL_RANK_0( logInfo::LogOutput, " Output ................. " << m_outputFile ); - GEOS_LOG_LEVEL_RANK_0( logInfo::LogOutput, " Baseline ............... " << m_baselineFile ); + GEOS_LOG_LEVEL_RANK_0( logInfo::LogOutput, " Selected phases ........ " << stringutilities::join( m_phaseNames, ", " ) ); + GEOS_LOG_LEVEL_RANK_0( logInfo::LogOutput, " Saturation functions ... " << stringutilities::join( m_saturationFunctionNames, ", " ) ); + if( !m_outputFile.empty()) + { + GEOS_LOG_LEVEL_RANK_0( logInfo::LogOutput, " Output ................. " << m_outputFile ); + } // create a dummy discretization with one quadrature point for // storing constitutive data - conduit::Node node; dataRepository::Group rootGroup( "root", node ); dataRepository::Group discretization( "discretization", &rootGroup ); - discretization.resize( 1 ); // one element + // Allocate as many elements as the number of rows + integer const numRows = m_table.size( 0 ); + discretization.resize( numRows ); // numRows elements baseRelperm.allocateConstitutiveData( discretization, 1 ); // one quadrature point constitutiveUpdatePassThru( baseRelperm, [&]( auto & selectedRelpermModel ) { using RELPERM_TYPE = TYPEOFREF( selectedRelpermModel ); - resizeTables< RELPERM_TYPE >(); runTest< RELPERM_TYPE >( selectedRelpermModel, m_table ); } ); // move table back to host for output m_table.move( LvArray::MemorySpace::host ); - if( m_outputFile != "none" ) - { - outputResults(); - } - - if( m_baselineFile != "none" ) - { - compareWithBaseline(); - } - return false; } - -template< typename RELPERM_TYPE > -void RelpermDriver::resizeTables() +void RelpermDriver::getColumnNames( string_array & columnNames ) const { - ConstitutiveManager - & constitutiveManager = this->getGroupByPath< ConstitutiveManager >( "/Problem/domain/Constitutive" ); - RelativePermeabilityBase - & baseRelperm = constitutiveManager.getGroup< RelativePermeabilityBase >( m_relpermName ); - - using PT = RelativePermeabilityBase::PhaseType; - integer const ipWater = baseRelperm.getPhaseOrder()[PT::WATER]; - integer const ipOil = baseRelperm.getPhaseOrder()[PT::OIL]; - integer const ipGas = baseRelperm.getPhaseOrder()[PT::GAS]; - - real64 minSw = 0., minSnw = 0.; - if( baseRelperm.numFluidPhases() > 2 ) - { - minSw = baseRelperm.getWettingPhaseMinVolumeFraction(); - minSnw = baseRelperm.getNonWettingMinVolumeFraction(); - } - else - { - if( ipWater < 0 )// a.k.a o/g - { - minSw = 0; - minSnw = baseRelperm.getNonWettingMinVolumeFraction(); - } - else if( ipGas < 0 || ipOil < 0 )// a.k.a w/o or w/g - { - minSnw = 0; - minSw = baseRelperm.getWettingPhaseMinVolumeFraction(); - } - } + RelativePermeabilityBase const & baseRelperm = getRelperm(); + bool const has_hysteresis = (dynamic_cast< constitutive::TableRelativePermeabilityHysteresis const * >(&baseRelperm) != nullptr); - real64 const dSw = ( 1 - minSw - minSnw ) / m_numSteps; - // set input columns + integer const numPhases = baseRelperm.numFluidPhases(); + string_array const & phaseNames = baseRelperm.phaseNames(); - resizeTable< RELPERM_TYPE >(); - // 3-phase branch - if( m_numPhases > 2 ) + columnNames.emplace_back( "index" ); + for( integer ip = 0; ip < numPhases; ip++ ) { - for( integer ni = 0; ni < m_numSteps + 1; ++ni ) - { - for( integer nj = 0; nj < m_numSteps + 1; ++nj ) - { - - integer index = ni * ( m_numSteps + 1 ) + nj; - m_table( index, TIME ) = minSw + index * dSw; - m_table( index, ipWater + 1 ) = minSw + nj * dSw; - m_table( index, ipGas + 1 ) = minSnw + ni * dSw; - m_table( index, ipOil + 1 ) = - 1. - m_table( index, ipWater + 1 ) - m_table( index, ipOil + 1 ); - } - } + columnNames.emplace_back( GEOS_FMT( "saturation,{}", phaseNames[ip] )); } - else // 2-phase branch + if( has_hysteresis ) { - for( integer ni = 0; ni < m_numSteps + 1; ++ni ) + for( integer ip = 0; ip < numPhases; ip++ ) { - integer index = ni; - m_table( index, TIME ) = minSw + index * dSw; - if( ipWater < 0 ) - { - m_table( index, ipGas + 1 ) = minSnw + ni * dSw; - m_table( index, ipOil + 1 ) = 1. - m_table( index, ipGas + 1 ); - } - else if( ipGas < 0 ) - { - m_table( index, ipWater + 1 ) = minSw + ni * dSw; - m_table( index, ipOil + 1 ) = 1. - m_table( index, ipWater + 1 ); - } - else if( ipOil < 0 ) - { - m_table( index, ipWater + 1 ) = minSw + ni * dSw; - m_table( index, ipGas + 1 ) = 1. - m_table( index, ipWater + 1 ); - } + columnNames.emplace_back( GEOS_FMT( "historical saturation,{}", phaseNames[ip] )); } - } - - -} - - -template< typename RELPERM_TYPE > -std::enable_if_t< std::is_same< TableRelativePermeabilityHysteresis, RELPERM_TYPE >::value, void > -RelpermDriver::resizeTable() -{ - if( m_numPhases > 2 ) + for( integer ip = 0; ip < numPhases; ip++ ) { - m_table.resize( ( m_numSteps + 1 ) * ( m_numSteps + 1 ), 1 + 3 * m_numPhases ); + columnNames.emplace_back( GEOS_FMT( "relperm,{}", phaseNames[ip] )); } - else - { - m_table.resize( m_numSteps + 1, 1 + 3 * m_numPhases ); - } - } -template< typename RELPERM_TYPE > -std::enable_if_t< !std::is_same< TableRelativePermeabilityHysteresis, RELPERM_TYPE >::value, void > -RelpermDriver::resizeTable() +void RelpermDriver::initializeTable( RelativePermeabilityBase const & baseRelperm ) { - if( m_numPhases > 2 ) - { - m_table.resize( ( m_numSteps + 1 ) * ( m_numSteps + 1 ), 1 + 2 * m_numPhases ); - } - else + integer const numRows = m_table.size( 0 ); + integer const numPhases = baseRelperm.numFluidPhases(); + + string_array const & phaseNames = baseRelperm.phaseNames(); + array1d< integer > phaseOrder( numPhases ); + phaseOrder[0] = numPhases * (numPhases - 1) / 2; + for( integer ip = 0; ip < numPhases-1; ++ip ) { - m_table.resize( m_numSteps + 1, 1 + 2 * m_numPhases ); + integer const index = static_cast< integer >(std::distance( phaseNames.begin(), std::find( phaseNames.begin(), phaseNames.end(), m_phaseNames[ip] ))); + phaseOrder[ip+1] = index; + phaseOrder[0] -= index; } -} - -//TODO refactor - duplication -void RelpermDriver::compareWithBaseline() -{ - // open baseline file - - std::ifstream file( m_baselineFile.c_str() ); - GEOS_THROW_IF( !file.is_open(), - GEOS_FMT( "Can't seem to open the baseline file {}", m_baselineFile ), - InputError ); - - // discard file header - - string line; - for( integer row = 0; row < 7; ++row ) + FunctionManager const & functionManager = FunctionManager::getInstance(); + stdVector< TableFunction const * > tableFunctions; + for( auto const & functionName : m_saturationFunctionNames ) { - getline( file, line ); + TableFunction const * function = functionManager.getGroupPointer< TableFunction >( functionName ); + tableFunctions.emplace_back( function ); } - // read data block. we assume the file size is consistent with m_table, - // but check for a premature end-of-file. we then compare results value by value. - // we ignore the newton iteration and residual columns, as those may be platform - // specific. + // Offset for saturations in table + constexpr integer SATURATION = 1; - real64 value; - //table is redim to fit the layout of relperm so the second dimension is numGaussPt - // and always of size 1 - for( integer row = 0; row < m_table.size( 0 ); ++row ) + for( integer step = 0; step < numRows; ++step ) { - for( integer col = 0; col < m_table.size( 1 ); ++col ) + real64 const time = m_table( step, TIME ); + real64 sumSaturation = 0.0; + for( integer ip = 1; ip < numPhases; ip++ ) + { + real64 const saturation = LvArray::math::max( tableFunctions[ip-1]->evaluate( &time ), 0.0 ); + m_table( step, phaseOrder[ip] + SATURATION ) = saturation; + sumSaturation += saturation; + } + if( 1.0 - sumSaturation < -LvArray::NumericLimits< real64 >::epsilon ) { - GEOS_THROW_IF( file.eof(), "Baseline file appears shorter than internal results", geos::RuntimeError ); - file >> value; - - real64 const error = fabs( m_table[row][col] - value ) / ( fabs( value ) + 1 ); - GEOS_THROW_IF( error > m_baselineTol, - GEOS_FMT( "Results do not match baseline at data row {} (row {} with header) and column {}", - row + 1, - row + m_numColumns, - col + 1 ), - geos::RuntimeError ); + real64 const scale = 1.0 / sumSaturation; + for( integer ip = 1; ip < numPhases; ip++ ) + { + m_table( step, phaseOrder[ip] + SATURATION ) *= scale; + } + sumSaturation = 1.0; } + m_table( step, phaseOrder[0] + SATURATION ) = 1.0 - sumSaturation; } - - // check we actually reached the end of the baseline file - - file >> value; - GEOS_THROW_IF( !file.eof(), "Baseline file appears longer than internal results", geos::RuntimeError ); - - // success - - GEOS_LOG_LEVEL_RANK_0( logInfo::LogOutput, " Comparison ............. Internal results consistent with baseline." ); - - file.close(); } +RelativePermeabilityBase & RelpermDriver::getRelperm() +{ + return getConstitutiveManager().getGroup< RelativePermeabilityBase >( m_relpermName ); +} +RelativePermeabilityBase const & RelpermDriver::getRelperm() const +{ + return getConstitutiveManager().getGroup< RelativePermeabilityBase >( m_relpermName ); +} REGISTER_CATALOG_ENTRY( TaskBase, RelpermDriver, diff --git a/src/coreComponents/constitutiveDrivers/relativePermeability/RelpermDriver.hpp b/src/coreComponents/constitutiveDrivers/relativePermeability/RelpermDriver.hpp index 426c66bf130..c652447c109 100644 --- a/src/coreComponents/constitutiveDrivers/relativePermeability/RelpermDriver.hpp +++ b/src/coreComponents/constitutiveDrivers/relativePermeability/RelpermDriver.hpp @@ -13,111 +13,75 @@ * ------------------------------------------------------------------------------------------------------------ */ -#ifndef GEOS_RELPERMDRIVER_HPP_ -#define GEOS_RELPERMDRIVER_HPP_ +#ifndef GEOS_CONSTITUTIVEDRIVERS_RELATIVEPERMEABILITY_RELPERMDRIVER_HPP +#define GEOS_CONSTITUTIVEDRIVERS_RELATIVEPERMEABILITY_RELPERMDRIVER_HPP -#include "events/tasks/TaskBase.hpp" -#include "constitutive/relativePermeability/TableRelativePermeabilityHysteresis.hpp" +#include "constitutiveDrivers/ConstitutiveDriver.hpp" namespace geos { -class RelpermDriver : public TaskBase +namespace constitutive { +class RelativePermeabilityBase; +} +/** + * @class RelpermDriver + * + * Class to allow for testing Relative permeability models without the + * complexity of setting up a full simulation. + */ +class RelpermDriver : public ConstitutiveDriver +{ public: - RelpermDriver( const string & name, - Group * const parent ); + RelpermDriver( const string & name, Group * const parent ); - static string catalogName() - { return "RelpermDriver"; } + static string catalogName() { return "RelpermDriver"; } void postInputInitialization() override; - virtual bool execute( real64 const GEOS_UNUSED_PARAM( time_n ), - real64 const GEOS_UNUSED_PARAM( dt ), - integer const GEOS_UNUSED_PARAM( cycleNumber ), - integer const GEOS_UNUSED_PARAM( eventCounter ), - real64 const GEOS_UNUSED_PARAM( eventProgress ), - DomainPartition & - GEOS_UNUSED_PARAM( domain ) ) override; + using ConstitutiveDriver::execute; + + bool execute() override; + + void getColumnNames( string_array & columnNames ) const override; /** * @brief Run test using loading protocol in table - * @param i Relperm constitutive model + * @param relperm Relperm constitutive model * @param table Table with input/output time history */ template< typename RELPERM_TYPE > - std::enable_if_t< std::is_same< constitutive::TableRelativePermeabilityHysteresis, RELPERM_TYPE >::value, void > - runTest( RELPERM_TYPE & relperm, - const arrayView2d< real64, 1 > & table ); - - template< typename RELPERM_TYPE > - std::enable_if_t< !std::is_same< constitutive::TableRelativePermeabilityHysteresis, RELPERM_TYPE >::value, void > - runTest( RELPERM_TYPE & relperm, - const arrayView2d< real64, 1 > & table ); + void runTest( RELPERM_TYPE & relperm, const arrayView2d< real64, 1 > & table ); +private: /** - * @brief Ouput table to file for easy plotting + * @brief Get the relative permeability model from the catalog */ - void outputResults(); + constitutive::RelativePermeabilityBase & getRelperm(); + constitutive::RelativePermeabilityBase const & getRelperm() const; /** - * @brief Read in a baseline table from file and compare with computed one (for unit testing purposes) + * @brief Initialises the table by filling in primary variables */ - void compareWithBaseline(); - -private: - - template< typename RELPERM_TYPE > - void resizeTables(); - - template< typename RELPERM_TYPE > - std::enable_if_t< std::is_same< constitutive::TableRelativePermeabilityHysteresis, RELPERM_TYPE >::value, void > - resizeTable(); - - template< typename RELPERM_TYPE > - std::enable_if_t< !std::is_same< constitutive::TableRelativePermeabilityHysteresis, RELPERM_TYPE >::value, void > - resizeTable(); + void initializeTable( constitutive::RelativePermeabilityBase const & baseRelperm ); /** * @struct viewKeyStruct holds char strings and viewKeys for fast lookup */ struct viewKeyStruct { - constexpr static char const * relpermNameString() - { return "relperm"; } - - constexpr static char const * numStepsString() - { return "steps"; } - - constexpr static char const * outputString() - { return "output"; } - - constexpr static char const * baselineString() - { return "baseline"; } + constexpr static char const * relpermNameString() { return "relperm"; } + constexpr static char const * phaseNamesString() { return "phaseNames"; } + constexpr static char const * saturationFunctionsString() { return "saturationControls"; } }; - integer m_numSteps; ///< Number of load steps - integer m_numColumns; ///< Number of columns in data table (depends on number of fluid phases) - integer m_numPhases; ///< Number of fluid phases - string m_relpermName; ///< relPermType identifier - string m_outputFile; ///< Output file (optional, no output if not specified) - - array2d< real64 > m_table; ///< Table storing time-history of input/output - - Path m_baselineFile; ///< Baseline file (optional, for unit testing of solid models) - - enum columnKeys - { - TIME - }; ///< Enumeration of "input" column keys for readability - - static constexpr real64 m_baselineTol = 1e-3; ///< Comparison tolerance for baseline results + string_array m_phaseNames; + string_array m_saturationFunctionNames; }; - } -#endif //GEOS_RELPERMDRIVER_HPP_ +#endif //GEOS_CONSTITUTIVEDRIVERS_RELATIVEPERMEABILITY_RELPERMDRIVER_HPP diff --git a/src/coreComponents/constitutiveDrivers/relativePermeability/RelpermDriverRunTest.hpp b/src/coreComponents/constitutiveDrivers/relativePermeability/RelpermDriverRunTest.hpp index eabe5616eee..04655ec956c 100644 --- a/src/coreComponents/constitutiveDrivers/relativePermeability/RelpermDriverRunTest.hpp +++ b/src/coreComponents/constitutiveDrivers/relativePermeability/RelpermDriverRunTest.hpp @@ -13,239 +13,95 @@ * ------------------------------------------------------------------------------------------------------------ */ -#ifndef GEOS_RELPERMDRIVERRUNTEST_HPP_ -#define GEOS_RELPERMDRIVERRUNTEST_HPP_ +#ifndef GEOS_CONSTITUTIVEDRIVERS_RELATIVEPERMEABILITY_RELPERMDRIVERRUNTEST_HPP +#define GEOS_CONSTITUTIVEDRIVERS_RELATIVEPERMEABILITY_RELPERMDRIVERRUNTEST_HPP #include "constitutiveDrivers/relativePermeability/RelpermDriver.hpp" +#include "constitutive/relativePermeability/RelativePermeabilityBase.hpp" #include "constitutive/relativePermeability/RelativePermeabilityFields.hpp" -#include "constitutive/relativePermeability/Layouts.hpp" #include "constitutive/relativePermeability/KilloughHysteresis.hpp" - +#include "common/DataLayouts.hpp" namespace geos { -//specific to Hysteresis +// Hysteresis traits +template< typename RELPERM_TYPE > +struct HasHysteresis : std::false_type {}; + template< typename RELPERM_TYPE > -std::enable_if_t< std::is_same< constitutive::TableRelativePermeabilityHysteresis, - RELPERM_TYPE >::value, void > -RelpermDriver::runTest( RELPERM_TYPE & relperm, - const arrayView2d< real64 > & table ) +void +RelpermDriver::runTest( RELPERM_TYPE & relperm, const arrayView2d< real64 > & table ) { - // get number of phases and components + // Get the number of phases integer const numPhases = relperm.numFluidPhases(); - // create kernel wrapper - - typename constitutive::TableRelativePermeabilityHysteresis::KernelWrapper const kernelWrapper = relperm.createKernelWrapper(); + // Create the kernel wrapper + typename RELPERM_TYPE::KernelWrapper const kernelWrapper = relperm.createKernelWrapper(); - // set saturation to user specified feed - // it is more convenient to provide input in molar, so perform molar to mass conversion here + // Offset for saturations in table + constexpr integer SATURATION = 1; - array2d< real64, compflow::LAYOUT_PHASE > saturationValues; - if( numPhases > 2 ) - { - saturationValues.resize( ( m_numSteps + 1 ) * ( m_numSteps + 1 ), numPhases ); - } - else - { - saturationValues.resize( m_numSteps + 1, numPhases ); - } - using PT = typename RELPERM_TYPE::PhaseType; - integer const ipWater = relperm.getPhaseOrder()[PT::WATER]; - integer const ipOil = relperm.getPhaseOrder()[PT::OIL]; - integer const ipGas = relperm.getPhaseOrder()[PT::GAS]; - localIndex offset = std::max( std::max( ipOil, ipWater ), std::max( ipOil, ipGas ) ) + 1; + // Offset for relative permeability data + integer RELPERM = SATURATION + numPhases; - integer ipWetting = -1, ipNonWetting = -1; - std::tie( ipWetting, ipNonWetting ) = relperm.wettingAndNonWettingPhaseIndices(); + // Number of "cells" + integer const numRows = m_table.size( 0 ); - for( integer n = 0; n < table.size( 0 ); ++n ) + // If we have hysteresis, we need to populate the historical saturations + if constexpr (HasHysteresis< RELPERM_TYPE >::value) { - if( m_numPhases > 2 ) - { - saturationValues[n][ipWater] = table( n, ipWater + 1 ); - saturationValues[n][ipOil] = table( n, ipOil + 1 ); - saturationValues[n][ipGas] = table( n, ipGas + 1 ); - } - else//two-phase - { - if( ipWater < 0 ) - { - saturationValues[n][ipOil] = table( n, ipOil + 1 ); - saturationValues[n][ipGas] = table( n, ipGas + 1 ); - } - else if( ipGas < 0 ) - { - saturationValues[n][ipWater] = table( n, ipWater + 1 ); - saturationValues[n][ipOil] = table( n, ipOil + 1 ); - } - else if( ipOil < 0 ) - { - saturationValues[n][ipWater] = table( n, ipWater + 1 ); - saturationValues[n][ipGas] = table( n, ipGas + 1 ); - } - } - } + // Shift the relative permeability offset + RELPERM += numPhases; + // Offset for the historical saturations + integer const OFFSET = SATURATION + numPhases; - arrayView2d< real64 const, compflow::USD_PHASE > const saturation = saturationValues.toViewConst(); + integer ipWetting, ipNonWetting; + std::tie( ipWetting, ipNonWetting ) = relperm.wettingAndNonWettingPhaseIndices(); - auto const & phaseHasHysteresis = relperm.template getReference< array1d< integer > >( constitutive::TableRelativePermeabilityHysteresis::viewKeyStruct::phaseHasHysteresisString()); + // For the wetting phase, we need the minimum saturation + // For the non-wetting phase, we need the maximum saturation + real64 maxNonWetting = 0.0; + real64 minWetting = 1.0; - arrayView2d< real64, compflow::USD_PHASE > phaseMaxHistoricalVolFraction = relperm.template getField< fields::relperm::phaseMaxHistoricalVolFraction >().reference(); - arrayView2d< real64, compflow::USD_PHASE > phaseMinHistoricalVolFraction = relperm.template getField< fields::relperm::phaseMinHistoricalVolFraction >().reference(); + arrayView2d< real64, compflow::USD_PHASE > phaseMaxHistoricalVolFraction = relperm.template getField< fields::relperm::phaseMaxHistoricalVolFraction >().reference(); + arrayView2d< real64, compflow::USD_PHASE > phaseMinHistoricalVolFraction = relperm.template getField< fields::relperm::phaseMinHistoricalVolFraction >().reference(); -// arrayView1d< real64 > const drainagePhaseMinVolFraction = relperm.template getReference< array1d< real64 > >( -// constitutive::TableRelativePermeabilityHysteresis::viewKeyStruct::drainagePhaseMinVolumeFractionString()); -// arrayView1d< real64 > const drainagePhaseMaxVolFraction = relperm.template getReference< array1d< real64 > >( -// constitutive::TableRelativePermeabilityHysteresis::viewKeyStruct::drainagePhaseMaxVolumeFractionString()); - constitutive::KilloughHysteresis::HysteresisCurve const wettingCurve = relperm.template getReference< constitutive::KilloughHysteresis::HysteresisCurve >( - constitutive::TableRelativePermeabilityHysteresis::viewKeyStruct::wettingCurveString()); - - constitutive::KilloughHysteresis::HysteresisCurve const nonWettingCurve = relperm.template getReference< constitutive::KilloughHysteresis::HysteresisCurve >( - constitutive::TableRelativePermeabilityHysteresis::viewKeyStruct::nonWettingCurveString()); - //setting for drainage - { - if( phaseHasHysteresis[ipNonWetting] ) - { - phaseMaxHistoricalVolFraction[0][ipNonWetting] = nonWettingCurve.m_extremumPhaseVolFraction; - GEOS_LOG( GEOS_FMT( "New max non-wetting phase historical phase volume fraction: {}", phaseMaxHistoricalVolFraction[0][ipNonWetting] ) ); - } - if( phaseHasHysteresis[ipWetting] ) + for( integer step = 0; step < numRows; ++step ) { - phaseMinHistoricalVolFraction[0][ipWetting] = wettingCurve.m_extremumPhaseVolFraction; - GEOS_LOG( GEOS_FMT( "New min wetting phase historical phase volume fraction: {}", phaseMinHistoricalVolFraction[0][ipWetting] ) ); - } - } + real64 const sw = table( step, SATURATION + ipWetting ); + real64 const snw = table( step, SATURATION + ipNonWetting ); + minWetting = LvArray::math::min( minWetting, sw ); + maxNonWetting = LvArray::math::max( maxNonWetting, snw ); + phaseMinHistoricalVolFraction( step, ipWetting ) = minWetting; + phaseMaxHistoricalVolFraction( step, ipNonWetting ) = maxNonWetting; - forAll< parallelDevicePolicy<> >( saturation.size( 0 ), - [numPhases, kernelWrapper, saturation, table, - offset] GEOS_HOST_DEVICE ( integer const n ) - { - // nw phase set max to snw_max to get the imbibition bounding curve - kernelWrapper.update( 0, 0, saturation[n] ); - for( integer p = 0; p < numPhases; ++p ) - { - table( n, offset + 1 + p ) = kernelWrapper.relperm()( 0, 0, p ); - } - } ); - - //loop in charge of hysteresis values - offset += numPhases; - -//setting for imbibition - { - if( phaseHasHysteresis[ipNonWetting] ) - { - - phaseMaxHistoricalVolFraction[0][ipNonWetting] = nonWettingCurve.m_criticalDrainagePhaseVolFraction; - GEOS_LOG( GEOS_FMT( "New max non-wetting phase historical phase volume fraction: {}", phaseMaxHistoricalVolFraction[0][ipNonWetting] ) ); - } - if( phaseHasHysteresis[ipWetting] ) - { - phaseMinHistoricalVolFraction[0][ipWetting] = wettingCurve.m_criticalDrainagePhaseVolFraction; - GEOS_LOG( GEOS_FMT( "New min wetting phase historical phase volume fraction: {}", phaseMinHistoricalVolFraction[0][ipWetting] ) ); + table( step, OFFSET + ipWetting ) = minWetting; + table( step, OFFSET + ipNonWetting ) = maxNonWetting; } } - - - forAll< parallelDevicePolicy<> >( saturation.size( 0 ), - [numPhases, kernelWrapper, saturation, table, - offset] GEOS_HOST_DEVICE ( integer const n ) + forAll< parallelDevicePolicy<> >( numRows, + [numPhases, kernelWrapper, table, + RELPERM] GEOS_HOST_DEVICE ( integer const n ) { - // nw phase set max to snw_max to get the imbibition bounding curve + StackArray< real64, 2, constitutive::RelativePermeabilityBase::MAX_NUM_PHASES, compflow::LAYOUT_PHASE > saturation( 1, numPhases ); - kernelWrapper.update( 0, 0, saturation[n] ); for( integer p = 0; p < numPhases; ++p ) { - table( n, offset + 1 + p ) = kernelWrapper.relperm()( 0, 0, p ); + saturation[0][p] = table( n, SATURATION + p ); } - } ); - - -} - -template< typename RELPERM_TYPE > -std::enable_if_t< !std::is_same< constitutive::TableRelativePermeabilityHysteresis, RELPERM_TYPE >::value, void > -RelpermDriver::runTest( RELPERM_TYPE & relperm, - const arrayView2d< real64 > & table ) -{ - // get number of phases and components - - integer const numPhases = relperm.numFluidPhases(); - - // create kernel wrapper - - typename RELPERM_TYPE::KernelWrapper const kernelWrapper = relperm.createKernelWrapper(); - - // set saturation to user specified feed - // it is more convenient to provide input in molar, so perform molar to mass conversion here - - array2d< real64, compflow::LAYOUT_PHASE > saturationValues; - if( numPhases > 2 ) - { - saturationValues.resize(( m_numSteps + 1 ) * ( m_numSteps + 1 ), numPhases ); - } - else - { - saturationValues.resize( m_numSteps + 1, numPhases ); - } - using PT = typename RELPERM_TYPE::PhaseType; - integer const ipWater = relperm.getPhaseOrder()[PT::WATER]; - integer const ipOil = relperm.getPhaseOrder()[PT::OIL]; - integer const ipGas = relperm.getPhaseOrder()[PT::GAS]; - const localIndex offset = std::max( std::max( ipOil, ipWater ), std::max( ipOil, ipGas ) ) + 1; - - for( integer n = 0; n < table.size( 0 ); ++n ) - { - - - if( m_numPhases > 2 ) - { - saturationValues[n][ipWater] = table( n, ipWater + 1 ); - saturationValues[n][ipOil] = table( n, ipOil + 1 ); - saturationValues[n][ipGas] = table( n, ipGas + 1 ); - } - else//two-phase - { - if( ipWater < 0 ) - { - saturationValues[n][ipOil] = table( n, ipOil + 1 ); - saturationValues[n][ipGas] = table( n, ipGas + 1 ); - } - else if( ipGas < 0 ) - { - saturationValues[n][ipWater] = table( n, ipWater + 1 ); - saturationValues[n][ipOil] = table( n, ipOil + 1 ); - } - } - - } - - arrayView2d< real64 const, compflow::USD_PHASE > const saturation = saturationValues.toViewConst(); - - // perform relperm update using table (Swet,Snonwet) and save resulting total density, etc. - // note: column indexing should be kept consistent with output file header below. - - forAll< parallelDevicePolicy<> >( saturation.size( 0 ), - [numPhases, kernelWrapper, saturation, table, - offset] GEOS_HOST_DEVICE ( integer const n ) - { - kernelWrapper.update( 0, 0, saturation[n] ); + kernelWrapper.update( n, 0, saturation[0] ); for( integer p = 0; p < numPhases; ++p ) { - table( n, offset + 1 + p ) = kernelWrapper.relperm()( 0, 0, p ); + table( n, RELPERM + p ) = kernelWrapper.relperm()( n, 0, p ); } } ); - } - } - -#endif //GEOS_RELPERMDRIVERRUNTEST_HPP_ +#endif //GEOS_CONSTITUTIVEDRIVERS_RELATIVEPERMEABILITY_RELPERMDRIVERRUNTEST_HPP diff --git a/src/coreComponents/constitutiveDrivers/relativePermeability/RelpermDriverTableRelativeHysteresisRunTest.cpp b/src/coreComponents/constitutiveDrivers/relativePermeability/RelpermDriverTableRelativeHysteresisRunTest.cpp index 4ed6420b673..d50bd1bdf19 100644 --- a/src/coreComponents/constitutiveDrivers/relativePermeability/RelpermDriverTableRelativeHysteresisRunTest.cpp +++ b/src/coreComponents/constitutiveDrivers/relativePermeability/RelpermDriverTableRelativeHysteresisRunTest.cpp @@ -16,8 +16,12 @@ #include "RelpermDriverRunTest.hpp" #include "constitutive/relativePermeability/TableRelativePermeabilityHysteresis.hpp" - namespace geos { + +template<> +struct HasHysteresis< constitutive::TableRelativePermeabilityHysteresis > : std::true_type +{}; + template void RelpermDriver::runTest< geos::constitutive::TableRelativePermeabilityHysteresis >( geos::constitutive::TableRelativePermeabilityHysteresis &, arrayView2d< real64 > const & ); } diff --git a/src/coreComponents/schema/schema.xsd b/src/coreComponents/schema/schema.xsd index bd776c2bac0..06fe5b92f7a 100644 --- a/src/coreComponents/schema/schema.xsd +++ b/src/coreComponents/schema/schema.xsd @@ -6482,18 +6482,22 @@ Information output from lower logLevels is added with the desired log level - - - - + + + + + + - + + + diff --git a/src/coreComponents/schema/schema.xsd.other b/src/coreComponents/schema/schema.xsd.other index 355f061e7f5..27627fd6635 100644 --- a/src/coreComponents/schema/schema.xsd.other +++ b/src/coreComponents/schema/schema.xsd.other @@ -1554,7 +1554,10 @@ A field can represent a physical variable. (pressure, temperature, global compos - + + + +