diff --git a/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_GravitySegregation_1d/immiscibleTwoPhase_GravitySegregation_1d.ats b/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_GravitySegregation_1d/immiscibleTwoPhase_GravitySegregation_1d.ats
new file mode 100644
index 00000000000..b7f470b932f
--- /dev/null
+++ b/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_GravitySegregation_1d/immiscibleTwoPhase_GravitySegregation_1d.ats
@@ -0,0 +1,18 @@
+from geos.ats.test_builder import TestDeck, RestartcheckParameters, generate_geos_tests
+
+restartcheck_params = {}
+restartcheck_params['atol'] = 1.0E-8
+restartcheck_params['rtol'] = 1.0E-8
+
+decks = [
+ TestDeck(
+ name="immiscibleTwoPhase_GravitySegregation_1d",
+ description=
+ 'Test that 2 fluids can be seperated based on gravity.',
+ partitions=((1, 1, 1), ),
+ restart_step=0,
+ check_step=45,
+ restartcheck_params=RestartcheckParameters(**restartcheck_params))
+]
+
+generate_geos_tests(decks)
diff --git a/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_GravitySegregation_1d/immiscibleTwoPhase_GravitySegregation_1d.xml b/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_GravitySegregation_1d/immiscibleTwoPhase_GravitySegregation_1d.xml
new file mode 100644
index 00000000000..62d4f6cf3bb
--- /dev/null
+++ b/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_GravitySegregation_1d/immiscibleTwoPhase_GravitySegregation_1d.xml
@@ -0,0 +1,203 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_GravitySegregation_1d/initialPressure.txt b/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_GravitySegregation_1d/initialPressure.txt
new file mode 100644
index 00000000000..85a30e13876
--- /dev/null
+++ b/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_GravitySegregation_1d/initialPressure.txt
@@ -0,0 +1,10 @@
+495.0
+485.0
+475.0
+465.0
+455.0
+400.0
+300.0
+200.0
+100.0
+0.0
diff --git a/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_GravitySegregation_1d/initialSaturation1.txt b/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_GravitySegregation_1d/initialSaturation1.txt
new file mode 100644
index 00000000000..99f11f6b2e7
--- /dev/null
+++ b/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_GravitySegregation_1d/initialSaturation1.txt
@@ -0,0 +1,10 @@
+0
+0
+0
+0
+0
+1
+1
+1
+1
+1
diff --git a/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_GravitySegregation_1d/initialSaturation2.txt b/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_GravitySegregation_1d/initialSaturation2.txt
new file mode 100644
index 00000000000..1c03b9c1871
--- /dev/null
+++ b/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_GravitySegregation_1d/initialSaturation2.txt
@@ -0,0 +1,10 @@
+1
+1
+1
+1
+1
+0
+0
+0
+0
+0
diff --git a/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_GravitySegregation_1d/x.txt b/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_GravitySegregation_1d/x.txt
new file mode 100644
index 00000000000..2eb3c4fe4ee
--- /dev/null
+++ b/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_GravitySegregation_1d/x.txt
@@ -0,0 +1 @@
+0.5
diff --git a/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_GravitySegregation_1d/y.txt b/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_GravitySegregation_1d/y.txt
new file mode 100644
index 00000000000..2eb3c4fe4ee
--- /dev/null
+++ b/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_GravitySegregation_1d/y.txt
@@ -0,0 +1 @@
+0.5
diff --git a/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_GravitySegregation_1d/z.txt b/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_GravitySegregation_1d/z.txt
new file mode 100644
index 00000000000..f43a1f14d26
--- /dev/null
+++ b/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_GravitySegregation_1d/z.txt
@@ -0,0 +1,10 @@
+0.5
+1.5
+2.5
+3.5
+4.5
+5.5
+6.5
+7.5
+8.5
+9.5
diff --git a/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/1D_case/initialPressure.txt b/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/1D_case/initialPressure.txt
new file mode 100644
index 00000000000..63943bfa00d
--- /dev/null
+++ b/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/1D_case/initialPressure.txt
@@ -0,0 +1,10 @@
+1e7
+1e7
+1e7
+1e7
+1e7
+1e7
+1e7
+1e7
+1e7
+1e7
diff --git a/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/1D_case/initialSaturation1.txt b/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/1D_case/initialSaturation1.txt
new file mode 100644
index 00000000000..1859fabae78
--- /dev/null
+++ b/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/1D_case/initialSaturation1.txt
@@ -0,0 +1,10 @@
+0.95
+0.95
+0.95
+0.95
+0.95
+0.95
+0.95
+0.05
+0.05
+0.05
diff --git a/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/1D_case/initialSaturation2.txt b/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/1D_case/initialSaturation2.txt
new file mode 100644
index 00000000000..d113536f2fd
--- /dev/null
+++ b/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/1D_case/initialSaturation2.txt
@@ -0,0 +1,10 @@
+0.05
+0.05
+0.05
+0.05
+0.05
+0.05
+0.05
+0.95
+0.95
+0.95
diff --git a/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/1D_case/jFunction_linear.txt b/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/1D_case/jFunction_linear.txt
new file mode 100644
index 00000000000..c3dfd50073f
--- /dev/null
+++ b/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/1D_case/jFunction_linear.txt
@@ -0,0 +1,2 @@
+4.33172935918785
+1.66604975353379
diff --git a/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/1D_case/permx.geos b/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/1D_case/permx.geos
new file mode 100644
index 00000000000..4d47d8fa568
--- /dev/null
+++ b/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/1D_case/permx.geos
@@ -0,0 +1,10 @@
+50
+50
+50
+50
+50
+200
+200
+200
+200
+200
diff --git a/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/1D_case/permy.geos b/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/1D_case/permy.geos
new file mode 100644
index 00000000000..4d47d8fa568
--- /dev/null
+++ b/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/1D_case/permy.geos
@@ -0,0 +1,10 @@
+50
+50
+50
+50
+50
+200
+200
+200
+200
+200
diff --git a/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/1D_case/permz.geos b/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/1D_case/permz.geos
new file mode 100644
index 00000000000..4d47d8fa568
--- /dev/null
+++ b/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/1D_case/permz.geos
@@ -0,0 +1,10 @@
+50
+50
+50
+50
+50
+200
+200
+200
+200
+200
diff --git a/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/1D_case/phaseVolumeFraction_water_linear.txt b/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/1D_case/phaseVolumeFraction_water_linear.txt
new file mode 100644
index 00000000000..0d66ea1aee9
--- /dev/null
+++ b/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/1D_case/phaseVolumeFraction_water_linear.txt
@@ -0,0 +1,2 @@
+0
+1
diff --git a/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/1D_case/tables/jFunction b/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/1D_case/tables/jFunction
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/1D_case/tables/jFunction.txt b/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/1D_case/tables/jFunction.txt
new file mode 100644
index 00000000000..72a78a75b85
--- /dev/null
+++ b/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/1D_case/tables/jFunction.txt
@@ -0,0 +1,76 @@
+99.9980934
+17.78445609
+11.20350909
+8.549878542
+7.057769576
+6.082201546
+5.386086427
+4.86006559
+4.446116454
+4.110353325
+3.831547061
+3.595663494
+3.393021946
+3.216710174
+3.061649542
+2.924017691
+2.800877928
+2.68993357
+2.589360389
+2.497689435
+2.413723433
+2.336475912
+2.265126101
+2.198985069
+2.137469915
+2.080083807
+2.02640045
+1.976051821
+1.928718377
+1.884121218
+1.842015737
+1.802186411
+1.764442543
+1.728614745
+1.694551981
+1.662119113
+1.631194842
+1.601669938
+1.573445757
+1.546432977
+1.520550493
+1.495724483
+1.471887601
+1.448978263
+1.426940034
+1.405721104
+1.385273798
+1.365554178
+1.346521677
+1.328138769
+1.310370692
+1.293185194
+1.276552299
+1.260444114
+1.244834648
+1.229699646
+1.215016445
+1.200763848
+1.186921998
+1.173472275
+1.160397205
+1.147680364
+1.135306301
+1.123260469
+1.111529155
+1.100099423
+1.088959056
+1.078096509
+1.067500858
+1.057161765
+1.047069433
+1.03721457
+1.027588362
+1.018182437
+1.008988837
+0.999999998
diff --git a/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/1D_case/tables/jFunction_linear.txt b/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/1D_case/tables/jFunction_linear.txt
new file mode 100644
index 00000000000..13036f8fd03
--- /dev/null
+++ b/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/1D_case/tables/jFunction_linear.txt
@@ -0,0 +1,2 @@
+100.00
+1.00
diff --git a/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/1D_case/tables/phaseVolumeFraction_water.txt b/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/1D_case/tables/phaseVolumeFraction_water.txt
new file mode 100644
index 00000000000..f38627d4e85
--- /dev/null
+++ b/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/1D_case/tables/phaseVolumeFraction_water.txt
@@ -0,0 +1,76 @@
+0.001
+0.013333333
+0.026666667
+0.04
+0.053333333
+0.066666667
+0.08
+0.093333333
+0.106666667
+0.12
+0.133333333
+0.146666667
+0.16
+0.173333333
+0.186666667
+0.2
+0.213333333
+0.226666667
+0.24
+0.253333333
+0.266666667
+0.28
+0.293333333
+0.306666667
+0.32
+0.333333333
+0.346666667
+0.36
+0.373333333
+0.386666667
+0.4
+0.413333333
+0.426666667
+0.44
+0.453333333
+0.466666667
+0.48
+0.493333333
+0.506666667
+0.52
+0.533333333
+0.546666667
+0.56
+0.573333333
+0.586666667
+0.6
+0.613333333
+0.626666667
+0.64
+0.653333333
+0.666666667
+0.68
+0.693333333
+0.706666667
+0.72
+0.733333333
+0.746666667
+0.76
+0.773333333
+0.786666667
+0.8
+0.813333333
+0.826666667
+0.84
+0.853333333
+0.866666667
+0.88
+0.893333333
+0.906666667
+0.92
+0.933333333
+0.946666667
+0.96
+0.973333333
+0.986666667
+1
diff --git a/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/1D_case/tables/phaseVolumeFraction_water_linear.txt b/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/1D_case/tables/phaseVolumeFraction_water_linear.txt
new file mode 100644
index 00000000000..0a269ee3741
--- /dev/null
+++ b/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/1D_case/tables/phaseVolumeFraction_water_linear.txt
@@ -0,0 +1,2 @@
+0.0
+1.0
diff --git a/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/1D_case/uni_directional_flow_base.xml b/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/1D_case/uni_directional_flow_base.xml
new file mode 100644
index 00000000000..efbd5b34421
--- /dev/null
+++ b/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/1D_case/uni_directional_flow_base.xml
@@ -0,0 +1,293 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/1D_case/uni_directional_flow_interface_condition.xml b/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/1D_case/uni_directional_flow_interface_condition.xml
new file mode 100644
index 00000000000..41ba4e3ed92
--- /dev/null
+++ b/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/1D_case/uni_directional_flow_interface_condition.xml
@@ -0,0 +1,66 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/1D_case/x.txt b/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/1D_case/x.txt
new file mode 100644
index 00000000000..2eb3c4fe4ee
--- /dev/null
+++ b/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/1D_case/x.txt
@@ -0,0 +1 @@
+0.5
diff --git a/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/1D_case/y.txt b/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/1D_case/y.txt
new file mode 100644
index 00000000000..2eb3c4fe4ee
--- /dev/null
+++ b/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/1D_case/y.txt
@@ -0,0 +1 @@
+0.5
diff --git a/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/1D_case/z.txt b/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/1D_case/z.txt
new file mode 100644
index 00000000000..f43a1f14d26
--- /dev/null
+++ b/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/1D_case/z.txt
@@ -0,0 +1,10 @@
+0.5
+1.5
+2.5
+3.5
+4.5
+5.5
+6.5
+7.5
+8.5
+9.5
diff --git a/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/3D_case/input/initialPressure.txt b/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/3D_case/input/initialPressure.txt
new file mode 100644
index 00000000000..6963bb6148c
--- /dev/null
+++ b/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/3D_case/input/initialPressure.txt
@@ -0,0 +1,1200 @@
+1.00022072e+07
+1.00022072e+07
+1.00022072e+07
+1.00022072e+07
+1.00022072e+07
+1.00022072e+07
+1.00022072e+07
+1.00022072e+07
+1.00022072e+07
+1.00022072e+07
+1.00022072e+07
+1.00022072e+07
+1.00022072e+07
+1.00022072e+07
+1.00022072e+07
+1.00022072e+07
+1.00022072e+07
+1.00022072e+07
+1.00022072e+07
+1.00022072e+07
+1.00022072e+07
+1.00022072e+07
+1.00022072e+07
+1.00022072e+07
+1.00022072e+07
+1.00022072e+07
+1.00022072e+07
+1.00022072e+07
+1.00022072e+07
+1.00022072e+07
+1.00022072e+07
+1.00022072e+07
+1.00022072e+07
+1.00022072e+07
+1.00022072e+07
+1.00022072e+07
+1.00022072e+07
+1.00022072e+07
+1.00022072e+07
+1.00022072e+07
+1.00022072e+07
+1.00022072e+07
+1.00022072e+07
+1.00022072e+07
+1.00022072e+07
+1.00022072e+07
+1.00022072e+07
+1.00022072e+07
+1.00022072e+07
+1.00022072e+07
+1.00022072e+07
+1.00022072e+07
+1.00022072e+07
+1.00022072e+07
+1.00022072e+07
+1.00022072e+07
+1.00022072e+07
+1.00022072e+07
+1.00022072e+07
+1.00022072e+07
+1.00066218e+07
+1.00066218e+07
+1.00066218e+07
+1.00066218e+07
+1.00066218e+07
+1.00066218e+07
+1.00066218e+07
+1.00066218e+07
+1.00066218e+07
+1.00066218e+07
+1.00066218e+07
+1.00066218e+07
+1.00066218e+07
+1.00066218e+07
+1.00066218e+07
+1.00066218e+07
+1.00066218e+07
+1.00066218e+07
+1.00066218e+07
+1.00066218e+07
+1.00066218e+07
+1.00066218e+07
+1.00066218e+07
+1.00066218e+07
+1.00066218e+07
+1.00066218e+07
+1.00066218e+07
+1.00066218e+07
+1.00066218e+07
+1.00066218e+07
+1.00066218e+07
+1.00066218e+07
+1.00066218e+07
+1.00066218e+07
+1.00066218e+07
+1.00066218e+07
+1.00066218e+07
+1.00066218e+07
+1.00066218e+07
+1.00066218e+07
+1.00066218e+07
+1.00066218e+07
+1.00066218e+07
+1.00066218e+07
+1.00066218e+07
+1.00066218e+07
+1.00066218e+07
+1.00066218e+07
+1.00066218e+07
+1.00066218e+07
+1.00066218e+07
+1.00066218e+07
+1.00066218e+07
+1.00066218e+07
+1.00066218e+07
+1.00066218e+07
+1.00066218e+07
+1.00066218e+07
+1.00066218e+07
+1.00066218e+07
+1.00110362e+07
+1.00110362e+07
+1.00110362e+07
+1.00110362e+07
+1.00110362e+07
+1.00110362e+07
+1.00110362e+07
+1.00110362e+07
+1.00110362e+07
+1.00110362e+07
+1.00110362e+07
+1.00110362e+07
+1.00110362e+07
+1.00110362e+07
+1.00110362e+07
+1.00110362e+07
+1.00110362e+07
+1.00110362e+07
+1.00110362e+07
+1.00110362e+07
+1.00110362e+07
+1.00110362e+07
+1.00110362e+07
+1.00110362e+07
+1.00110362e+07
+1.00110362e+07
+1.00110362e+07
+1.00110362e+07
+1.00110362e+07
+1.00110362e+07
+1.00110362e+07
+1.00110362e+07
+1.00110362e+07
+1.00110362e+07
+1.00110362e+07
+1.00110362e+07
+1.00110362e+07
+1.00110362e+07
+1.00110362e+07
+1.00110362e+07
+1.00110362e+07
+1.00110362e+07
+1.00110362e+07
+1.00110362e+07
+1.00110362e+07
+1.00110362e+07
+1.00110362e+07
+1.00110362e+07
+1.00110362e+07
+1.00110362e+07
+1.00110362e+07
+1.00110362e+07
+1.00110362e+07
+1.00110362e+07
+1.00110362e+07
+1.00110362e+07
+1.00110362e+07
+1.00110362e+07
+1.00110362e+07
+1.00110362e+07
+1.00154508e+07
+1.00154508e+07
+1.00154508e+07
+1.00154508e+07
+1.00154508e+07
+1.00154508e+07
+1.00154508e+07
+1.00154508e+07
+1.00154508e+07
+1.00154508e+07
+1.00154508e+07
+1.00154508e+07
+1.00154508e+07
+1.00154508e+07
+1.00154508e+07
+1.00154508e+07
+1.00154508e+07
+1.00154508e+07
+1.00154508e+07
+1.00154508e+07
+1.00154508e+07
+1.00154508e+07
+1.00154508e+07
+1.00154508e+07
+1.00154508e+07
+1.00154508e+07
+1.00154508e+07
+1.00154508e+07
+1.00154508e+07
+1.00154508e+07
+1.00154508e+07
+1.00154508e+07
+1.00154508e+07
+1.00154508e+07
+1.00154508e+07
+1.00154508e+07
+1.00154508e+07
+1.00154508e+07
+1.00154508e+07
+1.00154508e+07
+1.00154508e+07
+1.00154508e+07
+1.00154508e+07
+1.00154508e+07
+1.00154508e+07
+1.00154508e+07
+1.00154508e+07
+1.00154508e+07
+1.00154508e+07
+1.00154508e+07
+1.00154508e+07
+1.00154508e+07
+1.00154508e+07
+1.00154508e+07
+1.00154508e+07
+1.00154508e+07
+1.00154508e+07
+1.00154508e+07
+1.00154508e+07
+1.00154508e+07
+1.00198652e+07
+1.00198652e+07
+1.00198652e+07
+1.00198652e+07
+1.00198652e+07
+1.00198652e+07
+1.00198652e+07
+1.00198652e+07
+1.00198652e+07
+1.00198652e+07
+1.00198652e+07
+1.00198652e+07
+1.00198652e+07
+1.00198652e+07
+1.00198652e+07
+1.00198652e+07
+1.00198652e+07
+1.00198652e+07
+1.00198652e+07
+1.00198652e+07
+1.00198652e+07
+1.00198652e+07
+1.00198652e+07
+1.00198652e+07
+1.00198652e+07
+1.00198652e+07
+1.00198652e+07
+1.00198652e+07
+1.00198652e+07
+1.00198652e+07
+1.00198652e+07
+1.00198652e+07
+1.00198652e+07
+1.00198652e+07
+1.00198652e+07
+1.00198652e+07
+1.00198652e+07
+1.00198652e+07
+1.00198652e+07
+1.00198652e+07
+1.00198652e+07
+1.00198652e+07
+1.00198652e+07
+1.00198652e+07
+1.00198652e+07
+1.00198652e+07
+1.00198652e+07
+1.00198652e+07
+1.00198652e+07
+1.00198652e+07
+1.00198652e+07
+1.00198652e+07
+1.00198652e+07
+1.00198652e+07
+1.00198652e+07
+1.00198652e+07
+1.00198652e+07
+1.00198652e+07
+1.00198652e+07
+1.00198652e+07
+1.00242798e+07
+1.00242798e+07
+1.00242798e+07
+1.00242798e+07
+1.00242798e+07
+1.00242798e+07
+1.00242798e+07
+1.00242798e+07
+1.00242798e+07
+1.00242798e+07
+1.00242798e+07
+1.00242798e+07
+1.00242798e+07
+1.00242798e+07
+1.00242798e+07
+1.00242798e+07
+1.00242798e+07
+1.00242798e+07
+1.00242798e+07
+1.00242798e+07
+1.00242798e+07
+1.00242798e+07
+1.00242798e+07
+1.00242798e+07
+1.00242798e+07
+1.00242798e+07
+1.00242798e+07
+1.00242798e+07
+1.00242798e+07
+1.00242798e+07
+1.00242798e+07
+1.00242798e+07
+1.00242798e+07
+1.00242798e+07
+1.00242798e+07
+1.00242798e+07
+1.00242798e+07
+1.00242798e+07
+1.00242798e+07
+1.00242798e+07
+1.00242798e+07
+1.00242798e+07
+1.00242798e+07
+1.00242798e+07
+1.00242798e+07
+1.00242798e+07
+1.00242798e+07
+1.00242798e+07
+1.00242798e+07
+1.00242798e+07
+1.00242798e+07
+1.00242798e+07
+1.00242798e+07
+1.00242798e+07
+1.00242798e+07
+1.00242798e+07
+1.00242798e+07
+1.00242798e+07
+1.00242798e+07
+1.00242798e+07
+1.00286942e+07
+1.00286942e+07
+1.00286942e+07
+1.00286942e+07
+1.00286942e+07
+1.00286942e+07
+1.00286942e+07
+1.00286942e+07
+1.00286942e+07
+1.00286942e+07
+1.00286942e+07
+1.00286942e+07
+1.00286942e+07
+1.00286942e+07
+1.00286942e+07
+1.00286942e+07
+1.00286942e+07
+1.00286942e+07
+1.00286942e+07
+1.00286942e+07
+1.00286942e+07
+1.00286942e+07
+1.00286942e+07
+1.00286942e+07
+1.00286942e+07
+1.00286942e+07
+1.00286942e+07
+1.00286942e+07
+1.00286942e+07
+1.00286942e+07
+1.00286942e+07
+1.00286942e+07
+1.00286942e+07
+1.00286942e+07
+1.00286942e+07
+1.00286942e+07
+1.00286942e+07
+1.00286942e+07
+1.00286942e+07
+1.00286942e+07
+1.00286942e+07
+1.00286942e+07
+1.00286942e+07
+1.00286942e+07
+1.00286942e+07
+1.00286942e+07
+1.00286942e+07
+1.00286942e+07
+1.00286942e+07
+1.00286942e+07
+1.00286942e+07
+1.00286942e+07
+1.00286942e+07
+1.00286942e+07
+1.00286942e+07
+1.00286942e+07
+1.00286942e+07
+1.00286942e+07
+1.00286942e+07
+1.00286942e+07
+1.00331088e+07
+1.00331088e+07
+1.00331088e+07
+1.00331088e+07
+1.00331088e+07
+1.00331088e+07
+1.00331088e+07
+1.00331088e+07
+1.00331088e+07
+1.00331088e+07
+1.00331088e+07
+1.00331088e+07
+1.00331088e+07
+1.00331088e+07
+1.00331088e+07
+1.00331088e+07
+1.00331088e+07
+1.00331088e+07
+1.00331088e+07
+1.00331088e+07
+1.00331088e+07
+1.00331088e+07
+1.00331088e+07
+1.00331088e+07
+1.00331088e+07
+1.00331088e+07
+1.00331088e+07
+1.00331088e+07
+1.00331088e+07
+1.00331088e+07
+1.00331088e+07
+1.00331088e+07
+1.00331088e+07
+1.00331088e+07
+1.00331088e+07
+1.00331088e+07
+1.00331088e+07
+1.00331088e+07
+1.00331088e+07
+1.00331088e+07
+1.00331088e+07
+1.00331088e+07
+1.00331088e+07
+1.00331088e+07
+1.00331088e+07
+1.00331088e+07
+1.00331088e+07
+1.00331088e+07
+1.00331088e+07
+1.00331088e+07
+1.00331088e+07
+1.00331088e+07
+1.00331088e+07
+1.00331088e+07
+1.00331088e+07
+1.00331088e+07
+1.00331088e+07
+1.00331088e+07
+1.00331088e+07
+1.00331088e+07
+1.00375232e+07
+1.00375232e+07
+1.00375232e+07
+1.00375232e+07
+1.00375232e+07
+1.00375232e+07
+1.00375232e+07
+1.00375232e+07
+1.00375232e+07
+1.00375232e+07
+1.00375232e+07
+1.00375232e+07
+1.00375232e+07
+1.00375232e+07
+1.00375232e+07
+1.00375232e+07
+1.00375232e+07
+1.00375232e+07
+1.00375232e+07
+1.00375232e+07
+1.00375232e+07
+1.00375232e+07
+1.00375232e+07
+1.00375232e+07
+1.00375232e+07
+1.00375232e+07
+1.00375232e+07
+1.00375232e+07
+1.00375232e+07
+1.00375232e+07
+1.00375232e+07
+1.00375232e+07
+1.00375232e+07
+1.00375232e+07
+1.00375232e+07
+1.00375232e+07
+1.00375232e+07
+1.00375232e+07
+1.00375232e+07
+1.00375232e+07
+1.00375232e+07
+1.00375232e+07
+1.00375232e+07
+1.00375232e+07
+1.00375232e+07
+1.00375232e+07
+1.00375232e+07
+1.00375232e+07
+1.00375232e+07
+1.00375232e+07
+1.00375232e+07
+1.00375232e+07
+1.00375232e+07
+1.00375232e+07
+1.00375232e+07
+1.00375232e+07
+1.00375232e+07
+1.00375232e+07
+1.00375232e+07
+1.00375232e+07
+1.00419378e+07
+1.00419378e+07
+1.00419378e+07
+1.00419378e+07
+1.00419378e+07
+1.00419378e+07
+1.00419378e+07
+1.00419378e+07
+1.00419378e+07
+1.00419378e+07
+1.00419378e+07
+1.00419378e+07
+1.00419378e+07
+1.00419378e+07
+1.00419378e+07
+1.00419378e+07
+1.00419378e+07
+1.00419378e+07
+1.00419378e+07
+1.00419378e+07
+1.00419378e+07
+1.00419378e+07
+1.00419378e+07
+1.00419378e+07
+1.00419378e+07
+1.00419378e+07
+1.00419378e+07
+1.00419378e+07
+1.00419378e+07
+1.00419378e+07
+1.00419378e+07
+1.00419378e+07
+1.00419378e+07
+1.00419378e+07
+1.00419378e+07
+1.00419378e+07
+1.00419378e+07
+1.00419378e+07
+1.00419378e+07
+1.00419378e+07
+1.00419378e+07
+1.00419378e+07
+1.00419378e+07
+1.00419378e+07
+1.00419378e+07
+1.00419378e+07
+1.00419378e+07
+1.00419378e+07
+1.00419378e+07
+1.00419378e+07
+1.00419378e+07
+1.00419378e+07
+1.00419378e+07
+1.00419378e+07
+1.00419378e+07
+1.00419378e+07
+1.00419378e+07
+1.00419378e+07
+1.00419378e+07
+1.00419378e+07
+1.00463522e+07
+1.00463522e+07
+1.00463522e+07
+1.00463522e+07
+1.00463522e+07
+1.00463522e+07
+1.00463522e+07
+1.00463522e+07
+1.00463522e+07
+1.00463522e+07
+1.00463522e+07
+1.00463522e+07
+1.00463522e+07
+1.00463522e+07
+1.00463522e+07
+1.00463522e+07
+1.00463522e+07
+1.00463522e+07
+1.00463522e+07
+1.00463522e+07
+1.00463522e+07
+1.00463522e+07
+1.00463522e+07
+1.00463522e+07
+1.00463522e+07
+1.00463522e+07
+1.00463522e+07
+1.00463522e+07
+1.00463522e+07
+1.00463522e+07
+1.00463522e+07
+1.00463522e+07
+1.00463522e+07
+1.00463522e+07
+1.00463522e+07
+1.00463522e+07
+1.00463522e+07
+1.00463522e+07
+1.00463522e+07
+1.00463522e+07
+1.00463522e+07
+1.00463522e+07
+1.00463522e+07
+1.00463522e+07
+1.00463522e+07
+1.00463522e+07
+1.00463522e+07
+1.00463522e+07
+1.00463522e+07
+1.00463522e+07
+1.00463522e+07
+1.00463522e+07
+1.00463522e+07
+1.00463522e+07
+1.00463522e+07
+1.00463522e+07
+1.00463522e+07
+1.00463522e+07
+1.00463522e+07
+1.00463522e+07
+1.00507668e+07
+1.00507668e+07
+1.00507668e+07
+1.00507668e+07
+1.00507668e+07
+1.00507668e+07
+1.00507668e+07
+1.00507668e+07
+1.00507668e+07
+1.00507668e+07
+1.00507668e+07
+1.00507668e+07
+1.00507668e+07
+1.00507668e+07
+1.00507668e+07
+1.00507668e+07
+1.00507668e+07
+1.00507668e+07
+1.00507668e+07
+1.00507668e+07
+1.00507668e+07
+1.00507668e+07
+1.00507668e+07
+1.00507668e+07
+1.00507668e+07
+1.00507668e+07
+1.00507668e+07
+1.00507668e+07
+1.00507668e+07
+1.00507668e+07
+1.00507668e+07
+1.00507668e+07
+1.00507668e+07
+1.00507668e+07
+1.00507668e+07
+1.00507668e+07
+1.00507668e+07
+1.00507668e+07
+1.00507668e+07
+1.00507668e+07
+1.00507668e+07
+1.00507668e+07
+1.00507668e+07
+1.00507668e+07
+1.00507668e+07
+1.00507668e+07
+1.00507668e+07
+1.00507668e+07
+1.00507668e+07
+1.00507668e+07
+1.00507668e+07
+1.00507668e+07
+1.00507668e+07
+1.00507668e+07
+1.00507668e+07
+1.00507668e+07
+1.00507668e+07
+1.00507668e+07
+1.00507668e+07
+1.00507668e+07
+1.00551812e+07
+1.00551812e+07
+1.00551812e+07
+1.00551812e+07
+1.00551812e+07
+1.00551812e+07
+1.00551812e+07
+1.00551812e+07
+1.00551812e+07
+1.00551812e+07
+1.00551812e+07
+1.00551812e+07
+1.00551812e+07
+1.00551812e+07
+1.00551812e+07
+1.00551812e+07
+1.00551812e+07
+1.00551812e+07
+1.00551812e+07
+1.00551812e+07
+1.00551812e+07
+1.00551812e+07
+1.00551812e+07
+1.00551812e+07
+1.00551812e+07
+1.00551812e+07
+1.00551812e+07
+1.00551812e+07
+1.00551812e+07
+1.00551812e+07
+1.00551812e+07
+1.00551812e+07
+1.00551812e+07
+1.00551812e+07
+1.00551812e+07
+1.00551812e+07
+1.00551812e+07
+1.00551812e+07
+1.00551812e+07
+1.00551812e+07
+1.00551812e+07
+1.00551812e+07
+1.00551812e+07
+1.00551812e+07
+1.00551812e+07
+1.00551812e+07
+1.00551812e+07
+1.00551812e+07
+1.00551812e+07
+1.00551812e+07
+1.00551812e+07
+1.00551812e+07
+1.00551812e+07
+1.00551812e+07
+1.00551812e+07
+1.00551812e+07
+1.00551812e+07
+1.00551812e+07
+1.00551812e+07
+1.00551812e+07
+1.00595958e+07
+1.00595958e+07
+1.00595958e+07
+1.00595958e+07
+1.00595958e+07
+1.00595958e+07
+1.00595958e+07
+1.00595958e+07
+1.00595958e+07
+1.00595958e+07
+1.00595958e+07
+1.00595958e+07
+1.00595958e+07
+1.00595958e+07
+1.00595958e+07
+1.00595958e+07
+1.00595958e+07
+1.00595958e+07
+1.00595958e+07
+1.00595958e+07
+1.00595958e+07
+1.00595958e+07
+1.00595958e+07
+1.00595958e+07
+1.00595958e+07
+1.00595958e+07
+1.00595958e+07
+1.00595958e+07
+1.00595958e+07
+1.00595958e+07
+1.00595958e+07
+1.00595958e+07
+1.00595958e+07
+1.00595958e+07
+1.00595958e+07
+1.00595958e+07
+1.00595958e+07
+1.00595958e+07
+1.00595958e+07
+1.00595958e+07
+1.00595958e+07
+1.00595958e+07
+1.00595958e+07
+1.00595958e+07
+1.00595958e+07
+1.00595958e+07
+1.00595958e+07
+1.00595958e+07
+1.00595958e+07
+1.00595958e+07
+1.00595958e+07
+1.00595958e+07
+1.00595958e+07
+1.00595958e+07
+1.00595958e+07
+1.00595958e+07
+1.00595958e+07
+1.00595958e+07
+1.00595958e+07
+1.00595958e+07
+1.00640102e+07
+1.00640102e+07
+1.00640102e+07
+1.00640102e+07
+1.00640102e+07
+1.00640102e+07
+1.00640102e+07
+1.00640102e+07
+1.00640102e+07
+1.00640102e+07
+1.00640102e+07
+1.00640102e+07
+1.00640102e+07
+1.00640102e+07
+1.00640102e+07
+1.00640102e+07
+1.00640102e+07
+1.00640102e+07
+1.00640102e+07
+1.00640102e+07
+1.00640102e+07
+1.00640102e+07
+1.00640102e+07
+1.00640102e+07
+1.00640102e+07
+1.00640102e+07
+1.00640102e+07
+1.00640102e+07
+1.00640102e+07
+1.00640102e+07
+1.00640102e+07
+1.00640102e+07
+1.00640102e+07
+1.00640102e+07
+1.00640102e+07
+1.00640102e+07
+1.00640102e+07
+1.00640102e+07
+1.00640102e+07
+1.00640102e+07
+1.00640102e+07
+1.00640102e+07
+1.00640102e+07
+1.00640102e+07
+1.00640102e+07
+1.00640102e+07
+1.00640102e+07
+1.00640102e+07
+1.00640102e+07
+1.00640102e+07
+1.00640102e+07
+1.00640102e+07
+1.00640102e+07
+1.00640102e+07
+1.00640102e+07
+1.00640102e+07
+1.00640102e+07
+1.00640102e+07
+1.00640102e+07
+1.00640102e+07
+1.00684248e+07
+1.00684248e+07
+1.00684248e+07
+1.00684248e+07
+1.00684248e+07
+1.00684248e+07
+1.00684248e+07
+1.00684248e+07
+1.00684248e+07
+1.00684248e+07
+1.00684248e+07
+1.00684248e+07
+1.00684248e+07
+1.00684248e+07
+1.00684248e+07
+1.00684248e+07
+1.00684248e+07
+1.00684248e+07
+1.00684248e+07
+1.00684248e+07
+1.00684248e+07
+1.00684248e+07
+1.00684248e+07
+1.00684248e+07
+1.00684248e+07
+1.00684248e+07
+1.00684248e+07
+1.00684248e+07
+1.00684248e+07
+1.00684248e+07
+1.00684248e+07
+1.00684248e+07
+1.00684248e+07
+1.00684248e+07
+1.00684248e+07
+1.00684248e+07
+1.00684248e+07
+1.00684248e+07
+1.00684248e+07
+1.00684248e+07
+1.00684248e+07
+1.00684248e+07
+1.00684248e+07
+1.00684248e+07
+1.00684248e+07
+1.00684248e+07
+1.00684248e+07
+1.00684248e+07
+1.00684248e+07
+1.00684248e+07
+1.00684248e+07
+1.00684248e+07
+1.00684248e+07
+1.00684248e+07
+1.00684248e+07
+1.00684248e+07
+1.00684248e+07
+1.00684248e+07
+1.00684248e+07
+1.00684248e+07
+1.00728392e+07
+1.00728392e+07
+1.00728392e+07
+1.00728392e+07
+1.00728392e+07
+1.00728392e+07
+1.00728392e+07
+1.00728392e+07
+1.00728392e+07
+1.00728392e+07
+1.00728392e+07
+1.00728392e+07
+1.00728392e+07
+1.00728392e+07
+1.00728392e+07
+1.00728392e+07
+1.00728392e+07
+1.00728392e+07
+1.00728392e+07
+1.00728392e+07
+1.00728392e+07
+1.00728392e+07
+1.00728392e+07
+1.00728392e+07
+1.00728392e+07
+1.00728392e+07
+1.00728392e+07
+1.00728392e+07
+1.00728392e+07
+1.00728392e+07
+1.00728392e+07
+1.00728392e+07
+1.00728392e+07
+1.00728392e+07
+1.00728392e+07
+1.00728392e+07
+1.00728392e+07
+1.00728392e+07
+1.00728392e+07
+1.00728392e+07
+1.00728392e+07
+1.00728392e+07
+1.00728392e+07
+1.00728392e+07
+1.00728392e+07
+1.00728392e+07
+1.00728392e+07
+1.00728392e+07
+1.00728392e+07
+1.00728392e+07
+1.00728392e+07
+1.00728392e+07
+1.00728392e+07
+1.00728392e+07
+1.00728392e+07
+1.00728392e+07
+1.00728392e+07
+1.00728392e+07
+1.00728392e+07
+1.00728392e+07
+1.00772538e+07
+1.00772538e+07
+1.00772538e+07
+1.00772538e+07
+1.00772538e+07
+1.00772538e+07
+1.00772538e+07
+1.00772538e+07
+1.00772538e+07
+1.00772538e+07
+1.00772538e+07
+1.00772538e+07
+1.00772538e+07
+1.00772538e+07
+1.00772538e+07
+1.00772538e+07
+1.00772538e+07
+1.00772538e+07
+1.00772538e+07
+1.00772538e+07
+1.00772538e+07
+1.00772538e+07
+1.00772538e+07
+1.00772538e+07
+1.00772538e+07
+1.00772538e+07
+1.00772538e+07
+1.00772538e+07
+1.00772538e+07
+1.00772538e+07
+1.00772538e+07
+1.00772538e+07
+1.00772538e+07
+1.00772538e+07
+1.00772538e+07
+1.00772538e+07
+1.00772538e+07
+1.00772538e+07
+1.00772538e+07
+1.00772538e+07
+1.00772538e+07
+1.00772538e+07
+1.00772538e+07
+1.00772538e+07
+1.00772538e+07
+1.00772538e+07
+1.00772538e+07
+1.00772538e+07
+1.00772538e+07
+1.00772538e+07
+1.00772538e+07
+1.00772538e+07
+1.00772538e+07
+1.00772538e+07
+1.00772538e+07
+1.00772538e+07
+1.00772538e+07
+1.00772538e+07
+1.00772538e+07
+1.00772538e+07
+1.00816682e+07
+1.00816682e+07
+1.00816682e+07
+1.00816682e+07
+1.00816682e+07
+1.00816682e+07
+1.00816682e+07
+1.00816682e+07
+1.00816682e+07
+1.00816682e+07
+1.00816682e+07
+1.00816682e+07
+1.00816682e+07
+1.00816682e+07
+1.00816682e+07
+1.00816682e+07
+1.00816682e+07
+1.00816682e+07
+1.00816682e+07
+1.00816682e+07
+1.00816682e+07
+1.00816682e+07
+1.00816682e+07
+1.00816682e+07
+1.00816682e+07
+1.00816682e+07
+1.00816682e+07
+1.00816682e+07
+1.00816682e+07
+1.00816682e+07
+1.00816682e+07
+1.00816682e+07
+1.00816682e+07
+1.00816682e+07
+1.00816682e+07
+1.00816682e+07
+1.00816682e+07
+1.00816682e+07
+1.00816682e+07
+1.00816682e+07
+1.00816682e+07
+1.00816682e+07
+1.00816682e+07
+1.00816682e+07
+1.00816682e+07
+1.00816682e+07
+1.00816682e+07
+1.00816682e+07
+1.00816682e+07
+1.00816682e+07
+1.00816682e+07
+1.00816682e+07
+1.00816682e+07
+1.00816682e+07
+1.00816682e+07
+1.00816682e+07
+1.00816682e+07
+1.00816682e+07
+1.00816682e+07
+1.00816682e+07
+1.00860828e+07
+1.00860828e+07
+1.00860828e+07
+1.00860828e+07
+1.00860828e+07
+1.00860828e+07
+1.00860828e+07
+1.00860828e+07
+1.00860828e+07
+1.00860828e+07
+1.00860828e+07
+1.00860828e+07
+1.00860828e+07
+1.00860828e+07
+1.00860828e+07
+1.00860828e+07
+1.00860828e+07
+1.00860828e+07
+1.00860828e+07
+1.00860828e+07
+1.00860828e+07
+1.00860828e+07
+1.00860828e+07
+1.00860828e+07
+1.00860828e+07
+1.00860828e+07
+1.00860828e+07
+1.00860828e+07
+1.00860828e+07
+1.00860828e+07
+1.00860828e+07
+1.00860828e+07
+1.00860828e+07
+1.00860828e+07
+1.00860828e+07
+1.00860828e+07
+1.00860828e+07
+1.00860828e+07
+1.00860828e+07
+1.00860828e+07
+1.00860828e+07
+1.00860828e+07
+1.00860828e+07
+1.00860828e+07
+1.00860828e+07
+1.00860828e+07
+1.00860828e+07
+1.00860828e+07
+1.00860828e+07
+1.00860828e+07
+1.00860828e+07
+1.00860828e+07
+1.00860828e+07
+1.00860828e+07
+1.00860828e+07
+1.00860828e+07
+1.00860828e+07
+1.00860828e+07
+1.00860828e+07
+1.00860828e+07
diff --git a/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/3D_case/input/initialSaturation1.txt b/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/3D_case/input/initialSaturation1.txt
new file mode 100644
index 00000000000..90ddc12151e
--- /dev/null
+++ b/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/3D_case/input/initialSaturation1.txt
@@ -0,0 +1,1200 @@
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e+00
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
+1.00000000e-02
diff --git a/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/3D_case/input/initialSaturation2.txt b/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/3D_case/input/initialSaturation2.txt
new file mode 100644
index 00000000000..d5ef52f254b
--- /dev/null
+++ b/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/3D_case/input/initialSaturation2.txt
@@ -0,0 +1,1200 @@
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+0.00000000e+00
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
+9.90000000e-01
diff --git a/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/3D_case/input/permx.geos b/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/3D_case/input/permx.geos
new file mode 100644
index 00000000000..439bd535a86
--- /dev/null
+++ b/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/3D_case/input/permx.geos
@@ -0,0 +1,1200 @@
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
diff --git a/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/3D_case/input/permy.geos b/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/3D_case/input/permy.geos
new file mode 100644
index 00000000000..439bd535a86
--- /dev/null
+++ b/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/3D_case/input/permy.geos
@@ -0,0 +1,1200 @@
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
diff --git a/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/3D_case/input/permz.geos b/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/3D_case/input/permz.geos
new file mode 100644
index 00000000000..439bd535a86
--- /dev/null
+++ b/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/3D_case/input/permz.geos
@@ -0,0 +1,1200 @@
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+5.00000000e+01
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
+2.00000000e+02
diff --git a/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/3D_case/input/x.txt b/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/3D_case/input/x.txt
new file mode 100644
index 00000000000..283fc50e7fb
--- /dev/null
+++ b/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/3D_case/input/x.txt
@@ -0,0 +1,10 @@
+5.00000000e-01
+1.50000000e+00
+2.50000000e+00
+3.50000000e+00
+4.50000000e+00
+5.50000000e+00
+6.50000000e+00
+7.50000000e+00
+8.50000000e+00
+9.50000000e+00
diff --git a/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/3D_case/input/y.txt b/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/3D_case/input/y.txt
new file mode 100644
index 00000000000..a01763bc5d1
--- /dev/null
+++ b/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/3D_case/input/y.txt
@@ -0,0 +1,6 @@
+5.00000000e-01
+1.50000000e+00
+2.50000000e+00
+3.50000000e+00
+4.50000000e+00
+5.50000000e+00
diff --git a/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/3D_case/input/z.txt b/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/3D_case/input/z.txt
new file mode 100644
index 00000000000..d122a370abe
--- /dev/null
+++ b/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/3D_case/input/z.txt
@@ -0,0 +1,20 @@
+2.50000000e-01
+7.50000000e-01
+1.25000000e+00
+1.75000000e+00
+2.25000000e+00
+2.75000000e+00
+3.25000000e+00
+3.75000000e+00
+4.25000000e+00
+4.75000000e+00
+5.25000000e+00
+5.75000000e+00
+6.25000000e+00
+6.75000000e+00
+7.25000000e+00
+7.75000000e+00
+8.25000000e+00
+8.75000000e+00
+9.25000000e+00
+9.75000000e+00
diff --git a/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/3D_case/uni_directional_flow_base.xml b/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/3D_case/uni_directional_flow_base.xml
new file mode 100644
index 00000000000..3982a2b12c6
--- /dev/null
+++ b/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/3D_case/uni_directional_flow_base.xml
@@ -0,0 +1,299 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/3D_case/uni_directional_flow_interface_condition.xml b/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/3D_case/uni_directional_flow_interface_condition.xml
new file mode 100644
index 00000000000..9bac844ae7b
--- /dev/null
+++ b/inputFiles/immiscibleMultiphaseFlow/immiscibleTwoPhase_interface_conditions/3D_case/uni_directional_flow_interface_condition.xml
@@ -0,0 +1,66 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/coreComponents/common/DataLayouts.hpp b/src/coreComponents/common/DataLayouts.hpp
index cd09af18ef0..0f23039b745 100644
--- a/src/coreComponents/common/DataLayouts.hpp
+++ b/src/coreComponents/common/DataLayouts.hpp
@@ -21,6 +21,7 @@
#define GEOS_COMMON_DATALAYOUTS_HPP_
#include "common/GeosxConfig.hpp"
+#include "common/DataTypes.hpp"
#include "LvArray/src/Array.hpp"
#include "RAJA/RAJA.hpp"
diff --git a/src/coreComponents/common/logger/Logger.hpp b/src/coreComponents/common/logger/Logger.hpp
index e59cfa05256..1224ec6ee66 100644
--- a/src/coreComponents/common/logger/Logger.hpp
+++ b/src/coreComponents/common/logger/Logger.hpp
@@ -978,6 +978,7 @@ struct InputError : public std::runtime_error
InputError( std::exception const & subException, std::string const & msgToInsert );
};
+
/**
* @brief Exception class used to report errors in user input.
*/
diff --git a/src/coreComponents/constitutive/CMakeLists.txt b/src/coreComponents/constitutive/CMakeLists.txt
index 6af439fe23f..bfb182dfe89 100644
--- a/src/coreComponents/constitutive/CMakeLists.txt
+++ b/src/coreComponents/constitutive/CMakeLists.txt
@@ -27,12 +27,14 @@ set( constitutive_headers
ConstitutivePassThruHandler.hpp
ExponentialRelation.hpp
NullModel.hpp
+ KilloughHysteresis.hpp
capillaryPressure/BrooksCoreyCapillaryPressure.hpp
capillaryPressure/CapillaryPressureBase.hpp
capillaryPressure/CapillaryPressureFields.hpp
capillaryPressure/InverseCapillaryPressure.hpp
capillaryPressure/JFunctionCapillaryPressure.hpp
capillaryPressure/TableCapillaryPressure.hpp
+ capillaryPressure/TableCapillaryPressureHysteresis.hpp
capillaryPressure/TableCapillaryPressureHelpers.hpp
capillaryPressure/VanGenuchtenCapillaryPressure.hpp
capillaryPressure/CapillaryPressureSelector.hpp
@@ -226,11 +228,13 @@ set( constitutive_sources
ConstitutiveBase.cpp
ConstitutiveManager.cpp
NullModel.cpp
+ KilloughHysteresis.cpp
capillaryPressure/BrooksCoreyCapillaryPressure.cpp
capillaryPressure/CapillaryPressureBase.cpp
capillaryPressure/InverseCapillaryPressure.cpp
capillaryPressure/JFunctionCapillaryPressure.cpp
capillaryPressure/TableCapillaryPressure.cpp
+ capillaryPressure/TableCapillaryPressureHysteresis.cpp
capillaryPressure/TableCapillaryPressureHelpers.cpp
capillaryPressure/VanGenuchtenCapillaryPressure.cpp
contact/BartonBandis.cpp
@@ -369,6 +373,7 @@ if( ENABLE_PVTPackage )
endif()
if (ENABLE_HPCREACT)
+ if( EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/HPCReact/CMakeLists.txt" )
set( constitutive_headers
${constitutive_headers}
fluid/reactivefluid/ReactiveFluidSelector.hpp
@@ -383,8 +388,9 @@ if (ENABLE_HPCREACT)
${constitutive_sources}
fluid/reactivefluid/ReactiveSinglePhaseFluid.cpp
)
- add_subdirectory( HPCReact )
- list( APPEND dependencyList hpcReact )
+ add_subdirectory( HPCReact )
+ list( APPEND dependencyList hpcReact )
+ endif()
endif()
geos_decorate_link_dependencies( LIST decoratedDependencies
diff --git a/src/coreComponents/constitutive/KilloughHysteresis.cpp b/src/coreComponents/constitutive/KilloughHysteresis.cpp
new file mode 100644
index 00000000000..453a8b0585b
--- /dev/null
+++ b/src/coreComponents/constitutive/KilloughHysteresis.cpp
@@ -0,0 +1,144 @@
+/*
+ * ------------------------------------------------------------------------------------------------------------
+ * SPDX-License-Identifier: LGPL-2.1-only
+ *
+ * Copyright (c) 2018-2020 Lawrence Livermore National Security LLC
+ * Copyright (c) 2018-2020 The Board of Trustees of the Leland Stanford Junior University
+ * Copyright (c) 2018-2020 TotalEnergies
+ * Copyright (c) 2019- GEOSX Contributors
+ * All rights reserved
+ *
+ * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details.
+ * ------------------------------------------------------------------------------------------------------------
+ */
+
+/***
+ * @file KilloughHysteresis.cpp
+ */
+
+#include "KilloughHysteresis.hpp"
+
+
+namespace geos
+{
+
+using namespace dataRepository;
+
+namespace constitutive
+{
+
+
+void KilloughHysteresis::postProcessInput(real64 const &jerauldParam_a, real64 const &jerauldParam_b,
+ real64 const &killoughCurvatureParamRelPerm,
+ real64 const &killoughCurvatureParamPc)
+{
+ GEOS_THROW_IF( jerauldParam_a < 0,
+ GEOS_FMT( "{}: the parameter {} must be positive",
+ catalogName(),
+ viewKeyStruct::jerauldParameterAString() ),
+ InputError );
+
+ GEOS_THROW_IF( jerauldParam_b < 0,
+ GEOS_FMT( "{}: the paramater {} must be postitive",
+ catalogName(),
+ viewKeyStruct::jerauldParameterBString() ),
+ InputError );
+
+ GEOS_THROW_IF( killoughCurvatureParamRelPerm < 0,
+ GEOS_FMT( "{}: the paramater {} must be postitive",
+ catalogName(),
+ viewKeyStruct::killoughCurvatureParameterRelPermString() ),
+ InputError );
+
+ GEOS_THROW_IF( killoughCurvatureParamPc < 0,
+ GEOS_FMT( "{}: the paramater {} must be postitive",
+ catalogName(),
+ viewKeyStruct::killoughCurvatureParameterPcString() ),
+ InputError );
+
+}
+
+
+
+//TODO
+void KilloughHysteresis::computeLandCoefficient( KilloughHysteresis::HysteresisCurve const & hcurve,
+ real64 & landParam )
+{
+
+ // Note: for simplicity, the notations are taken from IX documentation (although this breaks our phaseVolFrac naming convention)
+
+ // Step 1: Land parameter for the wetting phase
+ if( hcurve.isWetting() )
+ {
+ real64 const Scrd = hcurve.oppositeBoundPhaseVolFraction;
+ real64 const Smxd = hcurve.drainageExtremaPhaseVolFraction;
+ real64 const Smxi = hcurve.imbibitionExtremaPhaseVolFraction;
+ real64 const Swc = Scrd;
+ GEOS_THROW_IF( (Smxi - Smxd) > 0,
+ GEOS_FMT( "{}: For wetting phase hysteresis, imbibition end-point saturation Smxi( {} ) must be smaller than the drainage saturation end-point Smxd( {} ).\n"
+ "Crossing relative permeability curves.\n",
+ catalogName(),
+ Smxi,
+ Smxd ),
+ InputError );
+
+ landParam = ( Smxd - Swc ) / LvArray::math::max( KilloughHysteresis::minScriMinusScrd, ( Smxd - Smxi ) ) - 1.0;
+ }
+ else
+ // Step 2: Land parameter for the non-wetting phase
+
+ {
+ real64 const Smx = hcurve.oppositeBoundPhaseVolFraction;
+ real64 const Scrd = hcurve.drainageExtremaPhaseVolFraction;
+ real64 const Scri = hcurve.imbibitionExtremaPhaseVolFraction;
+ GEOS_THROW_IF( (Scrd - Scri) > 0,
+ GEOS_FMT( "{}: For non-wetting phase hysteresis, drainage trapped saturation Scrd( {} ) must be smaller than the imbibition saturation Scri( {} ).\n"
+ "Crossing relative permeability curves.\n",
+ catalogName(),
+ Scrd,
+ Scri ),
+ InputError );
+
+ landParam = ( Smx - Scrd ) / LvArray::math::max( KilloughHysteresis::minScriMinusScrd, ( Scri - Scrd ) ) - 1.0;
+ }
+}
+
+GEOS_HOST_DEVICE
+void
+KilloughHysteresis::
+ computeTrappedCriticalPhaseVolFraction( HysteresisCurve const & hcurve,
+ real64 const & Shy,
+ real64 const & landParam,
+ real64 const & jerauldParam_a,
+ real64 const & jerauldParam_b,
+ real64 & Scrt )
+{
+
+ if( hcurve.isWetting())
+ {
+ //unpack values
+ real64 const Smxd = hcurve.drainageExtremaPhaseVolFraction;
+ real64 const Swc = hcurve.oppositeBoundPhaseVolFraction;
+
+ real64 const A = 1 + jerauldParam_a * (Shy - Swc);
+ real64 const numerator = Shy - Smxd;
+ real64 const denom = A + landParam * pow((Smxd - Shy) / (Smxd - Swc), 1 + jerauldParam_b / landParam );
+ Scrt = Smxd + numerator / denom;
+ }
+ else
+ {
+ //unpack values
+ real64 const Scrd = hcurve.drainageExtremaPhaseVolFraction;
+ real64 const Smx = hcurve.oppositeBoundPhaseVolFraction;
+
+ real64 const A = 1 + jerauldParam_a * (Smx - Shy);
+ real64 const numerator = Shy - Scrd;
+ real64 const denom = A + landParam * pow((Shy - Scrd) / (Smx - Scrd), 1 + jerauldParam_b / landParam );
+ Scrt = LvArray::math::max( 0.0,
+ Scrd + numerator / denom ); // trapped critical saturation from equation 2.162
+ }
+
+}
+
+}//end namespace
+}//end namespace
diff --git a/src/coreComponents/constitutive/KilloughHysteresis.hpp b/src/coreComponents/constitutive/KilloughHysteresis.hpp
new file mode 100644
index 00000000000..ce151f3aeae
--- /dev/null
+++ b/src/coreComponents/constitutive/KilloughHysteresis.hpp
@@ -0,0 +1,179 @@
+/*
+ * ------------------------------------------------------------------------------------------------------------
+ * SPDX-License-Identifier: LGPL-2.1-only
+ *
+ * Copyright (c) 2018-2020 Lawrence Livermore National Security LLC
+ * Copyright (c) 2018-2020 The Board of Trustees of the Leland Stanford Junior University
+ * Copyright (c) 2018-2020 TotalEnergies
+ * Copyright (c) 2019- GEOSX Contributors
+ * All rights reserved
+ *
+ * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details.
+ * ------------------------------------------------------------------------------------------------------------
+ */
+
+/**
+ * @file TableRelativePermeabilityHysteresis.hpp
+ */
+
+#ifndef GEOS_KILLOUGHHYSTERESIS_HPP
+#define GEOS_KILLOUGHHYSTERESIS_HPP
+
+#include "constitutive/ConstitutiveBase.hpp"
+#include "functions/TableFunction.hpp"
+
+#include "relativePermeability/Layouts.hpp"
+#include "capillaryPressure/Layouts.hpp"
+
+namespace geos
+{
+
+using namespace dataRepository;
+
+namespace constitutive
+{
+
+/***
+ * @brief KilloughHysteresis is designed to hold Killough hystereis model parameters and
+ * be in charge of all compuration related to this model (trapped Saturation,Land Coefficient?)
+ */
+
+
+
+//should be up to constitutiveBase or some new SCALConstitutiveBase but for now let's POC on relativePermeabilityBase
+class KilloughHysteresis
+{
+public:
+
+ static constexpr real64 minScriMinusScrd = 1e-12;
+ /// To avoid frequent changes from drainage to imbibition and vice versa, we use this buffer
+ static constexpr real64 flowReversalBuffer = 1e-12;
+
+ struct PhaseWettability
+ {
+ enum : integer
+ {
+ WETTING = 0,
+ NONWETTING = 1
+ };
+ };
+
+ /**
+ * @brief struct to represent hysteresis curves (relperm or capillary pressure)
+ * whether they are wetting or non wetting, storing key point as pairs of
+ * saturations and value, being either the relperm value (S,kr) or capillary pressure value (S,pc).
+ * this way we can tell wetting curve from non wetting by the ordering of drainage/imbibition key values.
+ * Indeed if imbibition comes at lower saturation than drainage then it is wetting curve, on the opposite
+ * if drainage comes before imbibition this is a non-wetting hysteresis. This is completed by an opposite
+ * point that is either the connate wetting saturation Swc or the maximum non wetting saturation Sgmax.
+ * @param oppositeBoundPhaseVolFraction represents either Swc or Sgmax depending if a wetting curve or nonwetting is described
+ * @param imbibitionExtremaPhaseVolFraction represents in wetting case the imibibition max and in non-wetting the imbibition residual
+ * @param drainageExtremaPhaseVolFraction represents in wetting case the drainage max and in non-wetting the drainage residual
+ * @param oppositeBoundSCALValue represents the associate relperm or capillary pressure value
+ * @param imbibitionExtremaSCALValue represents the associate relperm or capillary pressure value
+ * @param drainageExtremaSCALValue represents the associate relperm or capillary pressure value
+ */
+
+ struct HysteresisCurve
+ {
+ real64 oppositeBoundPhaseVolFraction = -1.;
+ real64 imbibitionExtremaPhaseVolFraction = -1.;
+ real64 drainageExtremaPhaseVolFraction = -1.;
+
+ real64 oppositeBoundSCALValue = -1.;
+ real64 imbibitionExtremaSCALValue = -1.;
+ real64 drainageExtremaSCALValue = -1.;
+
+ HysteresisCurve() = default;
+
+ HysteresisCurve( std::pair< real64, real64 > const & opp, std::pair< real64, real64 > const & imbE, std::pair< real64, real64 > const & drainE )
+ {
+ setPoints( opp, imbE, drainE );
+ }
+
+ void setPoints( std::pair< real64, real64 > const & opp, std::pair< real64, real64 > const & imbE, std::pair< real64, real64 > const & drainE )
+ {
+ oppositeBoundPhaseVolFraction = opp.first;
+ imbibitionExtremaPhaseVolFraction = imbE.first;
+ drainageExtremaPhaseVolFraction = drainE.first;
+
+ oppositeBoundSCALValue = opp.second;
+ imbibitionExtremaSCALValue = imbE.second;
+ drainageExtremaSCALValue = drainE.second;
+ }
+
+ //tODO (jacques) check if relevant to invert relation with same SCAL value // might be misleading for kr
+ HysteresisCurve toWetting() const
+ {
+ if(!isWetting())
+ return HysteresisCurve({1.-oppositeBoundPhaseVolFraction,oppositeBoundSCALValue},
+ {1.- imbibitionExtremaPhaseVolFraction,imbibitionExtremaSCALValue},
+ {1.-drainageExtremaPhaseVolFraction,drainageExtremaSCALValue});
+ else
+ return *this;
+ }
+
+ HysteresisCurve toNonWetting() const
+ {
+ if(isWetting())
+ return HysteresisCurve({1.-oppositeBoundPhaseVolFraction,oppositeBoundSCALValue},
+ {1.-imbibitionExtremaPhaseVolFraction,imbibitionExtremaSCALValue},
+ {1.-drainageExtremaPhaseVolFraction,drainageExtremaSCALValue});
+ else
+ return *this;
+ }
+
+ bool isWetting() const
+ {
+ return ((drainageExtremaPhaseVolFraction > oppositeBoundPhaseVolFraction) ? PhaseWettability::WETTING : PhaseWettability::NONWETTING) == PhaseWettability::WETTING;
+ }
+ bool isZero() const
+ {
+ return (oppositeBoundPhaseVolFraction <= 0.0) && (imbibitionExtremaPhaseVolFraction <= 0.0) && (drainageExtremaPhaseVolFraction <= 0.0);
+ }
+
+ };
+
+// void setRelPermParameters( real64 const & jerauldA, real64 const & jerauldB, real64 const & relpermCurv );
+
+ static std::string catalogName() { return "KilloughHysteresis"; }
+
+ static void postProcessInput(real64 const &jerauldParam_a, real64 const &jerauldParam_b,
+ real64 const &killoughCurvatureParamRelPerm,
+ real64 const &killoughCurvatureParamPc);
+
+ GEOS_HOST_DEVICE
+ static void computeLandCoefficient( HysteresisCurve const & hcruve, real64 & landParam );
+ /**
+ * @brief Function computing the trapped critical phase volume fraction
+ * @param[in] hcurve the hysteresis curve to be used and dispatched on
+ * @param[in] Shy the max historical phase volume fraction
+ * @param[in] landParam Land trapping parameter
+ * @param[in] jerauldParam_a jerauld expononent
+ * @param[in] jerauldParam_b jerauld expononent
+ * @param[out] Scrt the trapped critical phase volume fraction
+ */
+ GEOS_HOST_DEVICE
+ static void computeTrappedCriticalPhaseVolFraction( HysteresisCurve const & hcurve,
+ real64 const & Shy,
+ real64 const & landParam,
+ real64 const & jerauldParam_a,
+ real64 const & jerauldParam_b,
+ real64 & Scrt );
+
+ struct viewKeyStruct
+ {
+ static constexpr char const * jerauldParameterAString() { return "jerauldParameterA"; }
+ static constexpr char const * jerauldParameterBString() { return "jerauldParameterB"; }
+
+ static constexpr char const * killoughCurvatureParameterRelPermString() { return "killoughCurvatureParameterRelPerm"; }
+ static constexpr char const * killoughCurvatureParameterPcString() { return "killoughCurvatureParameterPc"; }
+ };
+
+};
+
+}
+
+}
+
+#endif //GEOS_KILLOUGHHYSTERESIS_HPP
diff --git a/src/coreComponents/constitutive/capillaryPressure/BrooksCoreyCapillaryPressure.cpp b/src/coreComponents/constitutive/capillaryPressure/BrooksCoreyCapillaryPressure.cpp
index f068365eb35..d7cc96a1dd1 100644
--- a/src/coreComponents/constitutive/capillaryPressure/BrooksCoreyCapillaryPressure.cpp
+++ b/src/coreComponents/constitutive/capillaryPressure/BrooksCoreyCapillaryPressure.cpp
@@ -114,6 +114,7 @@ BrooksCoreyCapillaryPressure::createKernelWrapper()
m_volFracScale,
m_phaseTypes,
m_phaseOrder,
+ m_phaseTrappedVolFrac,
m_phaseCapPressure,
m_dPhaseCapPressure_dPhaseVolFrac );
}
diff --git a/src/coreComponents/constitutive/capillaryPressure/BrooksCoreyCapillaryPressure.hpp b/src/coreComponents/constitutive/capillaryPressure/BrooksCoreyCapillaryPressure.hpp
index 39d93e48858..e0c971492b2 100644
--- a/src/coreComponents/constitutive/capillaryPressure/BrooksCoreyCapillaryPressure.hpp
+++ b/src/coreComponents/constitutive/capillaryPressure/BrooksCoreyCapillaryPressure.hpp
@@ -39,10 +39,12 @@ class BrooksCoreyCapillaryPressureUpdate final : public CapillaryPressureBaseUpd
real64 const volFracScale,
arrayView1d< integer const > const & phaseTypes,
arrayView1d< integer const > const & phaseOrder,
+ arrayView3d< real64, cappres::USD_CAPPRES > const & phaseTrapped,
arrayView3d< real64, cappres::USD_CAPPRES > const & phaseCapPressure,
arrayView4d< real64, cappres::USD_CAPPRES_DS > const & dPhaseCapPressure_dPhaseVolFrac )
: CapillaryPressureBaseUpdate( phaseTypes,
phaseOrder,
+ phaseTrapped,
phaseCapPressure,
dPhaseCapPressure_dPhaseVolFrac ),
m_phaseMinVolumeFraction( phaseMinVolumeFraction ),
@@ -57,6 +59,11 @@ class BrooksCoreyCapillaryPressureUpdate final : public CapillaryPressureBaseUpd
arraySlice1d< real64, cappres::USD_CAPPRES - 2 > const & phaseCapPres,
arraySlice2d< real64, cappres::USD_CAPPRES_DS - 2 > const & dPhaseCapPres_dPhaseVolFrac ) const;
+ GEOS_HOST_DEVICE
+ void computeInv( arraySlice1d< real64, compflow::USD_PHASE - 1 > const & phaseVolFraction,
+ arraySlice1d< real64 const, cappres::USD_CAPPRES - 2 > const & phaseCapPres,
+ arraySlice2d< real64, cappres::USD_CAPPRES_DS - 2 > const & dPhaseCapPres_dPhaseVolFrac ) const;
+
GEOS_HOST_DEVICE
virtual void update( localIndex const k,
localIndex const q,
@@ -80,14 +87,32 @@ class BrooksCoreyCapillaryPressureUpdate final : public CapillaryPressureBaseUpd
real64 & phaseCapPressure,
real64 & dPhaseCapPressure_dVolFrac );
+
+ GEOS_HOST_DEVICE
+ GEOS_FORCE_INLINE
+ static void
+ evaluateBrooksCoreyFunctionInv( real64 const phaseCapPressure,
+ int const ip,
+ real64 const volFracScaleInv,
+ real64 const exponentInv,
+ real64 const entryPressure,
+ real64 const maxCapPres_eps,
+ real64 const phaseMinVolumeFraction,
+ arrayView1d< integer const > const phaseOrder,
+ real64 & phaseVolFraction,
+ real64 & dPhaseCapPressure_dVolFrac );
+
arrayView1d< real64 const > m_phaseMinVolumeFraction;
arrayView1d< real64 const > m_phaseCapPressureExponentInv;
arrayView1d< real64 const > m_phaseEntryPressure;
real64 m_capPressureEpsilon;
real64 m_volFracScale;
+
};
+
+
class BrooksCoreyCapillaryPressure : public CapillaryPressureBase
{
public:
@@ -169,11 +194,14 @@ BrooksCoreyCapillaryPressureUpdate::
// compute first gas-oil capillary pressure as a function of gas-phase vol fraction
integer const ip_gas = m_phaseOrder[CapillaryPressureBase::PhaseType::GAS];
- if( ip_gas >= 0 )
+ integer const ip_oil = m_phaseOrder[CapillaryPressureBase::PhaseType::OIL];
+
+ GEOS_UNUSED_VAR( ip_gas );
+ if( ip_oil >= 0 )
{
- real64 const volFracScaled = (phaseVolFraction[ip_gas] - m_phaseMinVolumeFraction[ip_gas]) * volFracScaleInv;
- real64 const exponentInv = m_phaseCapPressureExponentInv[ip_gas];
- real64 const entryPressure = -m_phaseEntryPressure[ip_gas]; // for gas capillary pressure, take the opposite of the
+ real64 const volFracScaled = (phaseVolFraction[ip_oil] - m_phaseMinVolumeFraction[ip_oil]) * volFracScaleInv;
+ real64 const exponentInv = m_phaseCapPressureExponentInv[ip_oil];
+ real64 const entryPressure = -m_phaseEntryPressure[ip_oil]; // for gas capillary pressure, take the opposite of the
// BC function
real64 const wettingVolFracScaled = 1-volFracScaled;
@@ -184,11 +212,103 @@ BrooksCoreyCapillaryPressureUpdate::
exponentInv,
entryPressure,
eps,
- phaseCapPres[ip_gas],
- dPhaseCapPres_dPhaseVolFrac[ip_gas][ip_gas] );
+ phaseCapPres[ip_oil],
+ dPhaseCapPres_dPhaseVolFrac[ip_oil][ip_oil] );
}
}
+GEOS_HOST_DEVICE
+inline void
+BrooksCoreyCapillaryPressureUpdate::
+ computeInv( arraySlice1d< real64, compflow::USD_PHASE - 1 > const & phaseVolFraction,
+ arraySlice1d< real64 const, cappres::USD_CAPPRES - 2 > const & phaseCapPres,
+ arraySlice2d< real64, cappres::USD_CAPPRES_DS - 2 > const & dPhaseCapPres_dPhaseVolFrac ) const
+{
+ LvArray::forValuesInSlice( dPhaseCapPres_dPhaseVolFrac, []( real64 & val ){ val = 0.0; } );
+
+ real64 const volFracScaleInv = 1.0 / m_volFracScale;
+
+ // the Brooks-Corey model does not support volFracScaled = 0,
+ // hence we need an epsilon value to avoid a division by zero
+ // TODO: for S < epsilon, replace the original unbounded BC curve with a bounded power-law extension
+ real64 const eps = m_capPressureEpsilon;
+
+
+ // compute first water-oil capillary pressure as a function of water-phase vol fraction
+ integer const ip_water = m_phaseOrder[CapillaryPressureBase::PhaseType::WATER];
+ integer const ip_gas = m_phaseOrder[CapillaryPressureBase::PhaseType::GAS];
+ if( ip_water >= 0 )
+ {
+ real64 const volFracScaled_eps = (eps - m_phaseMinVolumeFraction[ip_water]) * volFracScaleInv;
+ real64 const exponentInv = m_phaseCapPressureExponentInv[ip_water];
+ real64 const entryPressure = m_phaseEntryPressure[ip_water];
+
+ real64 const wettingVolFracScaled_eps = volFracScaled_eps;
+ real64 const dWettingVolFracScaled_dVolFrac = volFracScaleInv;
+
+ real64 maxCapPres_eps = 0.0;
+ real64 max_dpc_eps = 0.0;
+
+ evaluateBrooksCoreyFunction( wettingVolFracScaled_eps,
+ dWettingVolFracScaled_dVolFrac,
+ exponentInv,
+ entryPressure,
+ eps,
+ maxCapPres_eps,
+ max_dpc_eps );
+
+ evaluateBrooksCoreyFunctionInv( phaseCapPres[ip_water],
+ ip_water,
+ volFracScaleInv,
+ exponentInv,
+ entryPressure,
+ maxCapPres_eps,
+ m_phaseMinVolumeFraction[ip_water],
+ m_phaseOrder,
+ phaseVolFraction[ip_water],
+ dPhaseCapPres_dPhaseVolFrac[ip_water][ip_water] );
+ phaseVolFraction[ip_gas] = 1.0 - phaseVolFraction[ip_water];
+ }
+
+
+ // compute first gas-oil capillary pressure as a function of gas-phase vol fraction
+
+
+ // if( ip_gas >= 0 )
+ // {
+ // real64 const volFracScaled_eps = (eps - m_phaseMinVolumeFraction[ip_gas]) * volFracScaleInv;
+ // real64 const exponentInv = m_phaseCapPressureExponentInv[ip_gas];
+ // real64 const entryPressure = -m_phaseEntryPressure[ip_gas]; // for gas capillary pressure, take the opposite of the
+ // // BC function
+
+ // real64 const wettingVolFracScaled_eps = 1-volFracScaled_eps;
+ // real64 const dWettingVolFracScaled_dVolFrac = -volFracScaleInv;
+
+ // real64 maxCapPres_eps = 0.0;
+ // real64 max_dpc_eps = 0.0;
+
+ // evaluateBrooksCoreyFunction( wettingVolFracScaled_eps,
+ // dWettingVolFracScaled_dVolFrac,
+ // exponentInv,
+ // entryPressure,
+ // eps,
+ // maxCapPres_eps,
+ // max_dpc_eps );
+
+ // evaluateBrooksCoreyFunctionInv( phaseCapPres[ip_gas],
+ // ip_gas,
+ // volFracScaleInv,
+ // exponentInv,
+ // entryPressure,
+ // maxCapPres_eps,
+ // m_phaseMinVolumeFraction[ip_gas],
+ // m_phaseOrder,
+ // phaseVolFraction[ip_gas],
+ // dPhaseCapPres_dPhaseVolFrac[ip_gas][ip_gas] );
+ // phaseVolFraction[ip_water] = 1.0 - phaseVolFraction[ip_gas];
+ // }
+}
+
GEOS_HOST_DEVICE
inline void
BrooksCoreyCapillaryPressureUpdate::
@@ -222,6 +342,49 @@ BrooksCoreyCapillaryPressureUpdate::
}
+GEOS_HOST_DEVICE
+inline void
+BrooksCoreyCapillaryPressureUpdate::
+ evaluateBrooksCoreyFunctionInv( real64 const phaseCapPressure,
+ int const ip,
+ real64 const volFracScaleInv,
+ real64 const exponentInv,
+ real64 const entryPressure,
+ real64 const maxCapPres_eps,
+ real64 const phaseMinVolumeFraction,
+ arrayView1d< integer const > const phaseOrder,
+ real64 & phaseVolFraction,
+ real64 & dPhaseCapPressure_dVolFrac )
+{
+
+
+ phaseVolFraction = 0.0;
+ real64 value = 0.0;
+ dPhaseCapPressure_dVolFrac = 0.0;
+ integer const ip_oil = phaseOrder[CapillaryPressureBase::PhaseType::OIL];
+
+ real64 const dScaledWettingPhaseVolFrac_dVolFrac = (ip == ip_oil)
+ ? -volFracScaleInv : volFracScaleInv;
+
+ if( phaseCapPressure <= maxCapPres_eps && phaseCapPressure >= entryPressure )
+ {
+ // intermediate value
+ real64 const val = pow( entryPressure, exponentInv ) / pow( phaseCapPressure, exponentInv + 1 );
+
+ value = (phaseCapPressure * val) * volFracScaleInv + phaseMinVolumeFraction; // entryPressure * (S_w)^( - 1 / exponentInv )
+ dPhaseCapPressure_dVolFrac = -dScaledWettingPhaseVolFrac_dVolFrac * val * exponentInv;
+ phaseVolFraction = (ip == ip_oil) ? 1.0 - value : value;
+ }
+ else // enforce a constant and bounded capillary pressure
+ {
+ real64 const val = (phaseCapPressure > maxCapPres_eps)
+ ? pow( entryPressure, exponentInv ) / pow( maxCapPres_eps, exponentInv ) : 1.0;
+ value = val * volFracScaleInv + phaseMinVolumeFraction;
+ phaseVolFraction = (ip == ip_oil) ? 1.0 - value : value;
+ }
+
+}
+
} // namespace constitutive
diff --git a/src/coreComponents/constitutive/capillaryPressure/CapillaryPressureBase.cpp b/src/coreComponents/constitutive/capillaryPressure/CapillaryPressureBase.cpp
index 9f1db0231ed..69d4280fb77 100644
--- a/src/coreComponents/constitutive/capillaryPressure/CapillaryPressureBase.cpp
+++ b/src/coreComponents/constitutive/capillaryPressure/CapillaryPressureBase.cpp
@@ -50,8 +50,9 @@ CapillaryPressureBase::CapillaryPressureBase( string const & name,
setSizedFromParent( 0 ).
setDescription( "Minimum volume fraction value for each phase" );
- registerField< fields::cappres::phaseCapPressure >( &m_phaseCapPressure );
- registerField< fields::cappres::dPhaseCapPressure_dPhaseVolFraction >( &m_dPhaseCapPressure_dPhaseVolFrac );
+ registerField< fields::cappres::phaseCapPressure >( &m_phaseCapPressure );
+ registerField< fields::cappres::dPhaseCapPressure_dPhaseVolFraction >( &m_dPhaseCapPressure_dPhaseVolFrac );
+ registerField< fields::cappres::phaseTrappedVolFraction >( &m_phaseTrappedVolFrac );
}
void CapillaryPressureBase::postInputInitialization()
@@ -93,19 +94,31 @@ void CapillaryPressureBase::postInputInitialization()
void CapillaryPressureBase::allocateConstitutiveData( Group & parent, localIndex const numPts )
{
integer const NP = numFluidPhases();
-
+ //phase trapped for stats
+ m_phaseTrappedVolFrac.resize( 0, numPts, NP );
+ m_phaseTrappedVolFrac.zero();
m_phaseCapPressure.resize( 0, numPts, NP );
m_dPhaseCapPressure_dPhaseVolFrac.resize( 0, numPts, NP, NP );
-
+
ConstitutiveBase::allocateConstitutiveData( parent, numPts );
}
void CapillaryPressureBase::setLabels()
{
+ getField< fields::cappres::phaseTrappedVolFraction >().
+ setDimLabels( 2, m_phaseNames );
getField< fields::cappres::phaseCapPressure >().
setDimLabels( 2, m_phaseNames );
}
+void CapillaryPressureBase::resizeFields( localIndex const size, localIndex const numPts )
+{
+ integer const NP = numFluidPhases();
+ m_phaseTrappedVolFrac.resize( size, numPts, NP );
+ m_phaseCapPressure.resize( size, numPts, NP );
+ m_dPhaseCapPressure_dPhaseVolFrac.resize( size, numPts, NP, NP );
+}
+
} // namespace constitutive
} // namespace geos
diff --git a/src/coreComponents/constitutive/capillaryPressure/CapillaryPressureBase.hpp b/src/coreComponents/constitutive/capillaryPressure/CapillaryPressureBase.hpp
index b0617841488..34a4701b957 100644
--- a/src/coreComponents/constitutive/capillaryPressure/CapillaryPressureBase.hpp
+++ b/src/coreComponents/constitutive/capillaryPressure/CapillaryPressureBase.hpp
@@ -60,16 +60,19 @@ class CapillaryPressureBaseUpdate
CapillaryPressureBaseUpdate( arrayView1d< integer const > const & phaseTypes,
arrayView1d< integer const > const & phaseOrder,
+ arrayView3d< real64, cappres::USD_CAPPRES > const & phaseTrapped,
arrayView3d< real64, cappres::USD_CAPPRES > const & phaseCapPressure,
arrayView4d< real64, cappres::USD_CAPPRES_DS > const & dPhaseCapPressure_dPhaseVolFrac )
: m_phaseTypes( phaseTypes ),
m_phaseOrder( phaseOrder ),
+ m_phaseTrappedVolFrac( phaseTrapped ),
m_phaseCapPressure( phaseCapPressure ),
m_dPhaseCapPressure_dPhaseVolFrac( dPhaseCapPressure_dPhaseVolFrac )
{}
arrayView1d< integer const > m_phaseTypes;
arrayView1d< integer const > m_phaseOrder;
+ arrayView3d< real64, cappres::USD_CAPPRES > m_phaseTrappedVolFrac;
arrayView3d< real64, cappres::USD_CAPPRES > m_phaseCapPressure;
arrayView4d< real64, cappres::USD_CAPPRES_DS > m_dPhaseCapPressure_dPhaseVolFrac;
@@ -130,6 +133,14 @@ class CapillaryPressureBase : public ConstitutiveBase
arrayView3d< real64 const > const & convergedPermeability ) const
{ GEOS_UNUSED_VAR( convergedPorosity, convergedPermeability ); }
+
+ /**
+ * @brief Save converged phase volume fraction at the end of a time step (needed for hysteresis)
+ * @param[in] phaseVolFraction an array containing the phase volume fractions at the end of a converged time step
+ */
+ virtual void saveConvergedPhaseVolFractionState( arrayView2d< real64 const, compflow::USD_PHASE > const & phaseVolFraction ) const
+ { GEOS_UNUSED_VAR( phaseVolFraction ); }
+
/*
* @brief Getter for the number of fluid phases
* @return the number of fluid phases
@@ -182,9 +193,17 @@ class CapillaryPressureBase : public ConstitutiveBase
void setLabels();
protected:
+/**
+ * @brief Function called internally to resize member arrays
+ * @param size primary dimension (e.g. number of cells)
+ * @param numPts secondary dimension (e.g. number of gauss points per cell)
+ */
+ virtual void resizeFields( localIndex const size, localIndex const numPts );
virtual void postInputInitialization() override;
+ std::tuple phaseIndex(const arrayView1d &phaseOrder);
+
// phase names read from input
string_array m_phaseNames;
@@ -198,8 +217,45 @@ class CapillaryPressureBase : public ConstitutiveBase
// output quantities
array3d< real64, cappres::LAYOUT_CAPPRES > m_phaseCapPressure;
array4d< real64, cappres::LAYOUT_CAPPRES_DS > m_dPhaseCapPressure_dPhaseVolFrac;
+
+ // trapped fraction
+ array3d< real64, cappres::LAYOUT_CAPPRES > m_phaseTrappedVolFrac;
};
+ inline std::tuple< integer, integer > CapillaryPressureBase::phaseIndex( arrayView1d< integer const > const & phaseOrder )
+ {
+ using PT = PhaseType;
+ integer const ipWater = phaseOrder[PT::WATER];
+ integer const ipOil = phaseOrder[PT::OIL];
+ integer const ipGas = phaseOrder[PT::GAS];
+
+ integer ipWetting = -1, ipNonWetting = -1;
+
+ if( ipWater >= 0 && ipOil >= 0 && ipGas >= 0 )
+ {
+ ipWetting = ipWater;
+ ipNonWetting = ipGas;
+ }
+ else if( ipWater < 0 )
+ {
+ ipWetting = ipOil;
+ ipNonWetting = ipGas;
+ }
+ else if( ipOil < 0 )
+ {
+ ipWetting = ipWater;
+ ipNonWetting = ipGas;
+ }
+ else if( ipGas < 0 )
+ {
+ ipWetting = ipWater;
+ ipNonWetting = ipOil;
+ }
+
+ //maybe a bit too pythonic
+ return std::make_tuple( ipWetting, ipNonWetting );
+ }
+
} // namespace constitutive
} // namespace geos
diff --git a/src/coreComponents/constitutive/capillaryPressure/CapillaryPressureFields.hpp b/src/coreComponents/constitutive/capillaryPressure/CapillaryPressureFields.hpp
index 653b7faeda8..a91ea22c1c6 100644
--- a/src/coreComponents/constitutive/capillaryPressure/CapillaryPressureFields.hpp
+++ b/src/coreComponents/constitutive/capillaryPressure/CapillaryPressureFields.hpp
@@ -23,45 +23,95 @@
#include "constitutive/capillaryPressure/Layouts.hpp"
#include "mesh/MeshFields.hpp"
-namespace geos
-{
-
-namespace fields
-{
-
-namespace cappres
-{
-
-using array3dLayoutCapPressure = array3d< real64, constitutive::cappres::LAYOUT_CAPPRES >;
-using array4dLayoutCapPressure_dS = array4d< real64, constitutive::cappres::LAYOUT_CAPPRES_DS >;
-
-DECLARE_FIELD( phaseCapPressure,
- "phaseCapPressure",
- array3dLayoutCapPressure,
- 0,
- LEVEL_0,
- WRITE_AND_READ,
- "Phase capillary pressure" );
-
-DECLARE_FIELD( dPhaseCapPressure_dPhaseVolFraction,
- "dPhaseCapPressure_dPhaseVolFraction",
- array4dLayoutCapPressure_dS,
- 0,
- NOPLOT,
- WRITE_AND_READ,
- "Derivative of phase capillary pressure with respect to phase volume fraction" );
-
-DECLARE_FIELD( jFuncMultiplier,
- "jFuncMultiplier",
- array2d< real64 >,
- 0,
- NOPLOT,
- WRITE_AND_READ,
- "Multiplier for the Leverett J-function" );
+namespace geos {
-}
+ namespace fields {
-}
+ namespace cappres {
+
+ using array2dLayoutPhase = array2d;
+ using array3dLayoutCapPressure = array3d;
+ using array4dLayoutCapPressure_dS = array4d;
+
+
+
+ enum ModeIndexType : integer {
+ DRAINAGE = 0,//to be used in array of Kernels
+ IMBIBITION = 1,
+ DRAINAGE_TO_IMBIBITION = 2,
+ IMBIBITION_TO_DRAINAGE = 3,
+ IMBIBITION_TO_DRAINAGE_FROM_SCANNING = 4
+ };
+
+ DECLARE_FIELD(phaseCapPressure,
+ "phaseCapPressure",
+ array3dLayoutCapPressure,
+ 0,
+ LEVEL_0,
+ WRITE_AND_READ,
+ "Phase capillary pressure");
+
+ DECLARE_FIELD(dPhaseCapPressure_dPhaseVolFraction,
+ "dPhaseCapPressure_dPhaseVolFraction",
+ array4dLayoutCapPressure_dS,
+ 0,
+ NOPLOT,
+ WRITE_AND_READ,
+ "Derivative of phase capillary pressure with respect to phase volume fraction");
+
+ DECLARE_FIELD(jFuncMultiplier,
+ "jFuncMultiplier",
+ array2d,
+ 0,
+ NOPLOT,
+ WRITE_AND_READ,
+ "Multiplier for the Leverett J-function");
+
+ DECLARE_FIELD(phaseTrappedVolFraction,
+ "phaseTrappedVolumeFraction",
+ array3dLayoutCapPressure,
+ 0,
+ LEVEL_0,
+ WRITE_AND_READ,
+ "Phase Trapped Volume Fraction");
+
+ DECLARE_FIELD(mode,
+ "Hysteresis Mode",
+ array1d,
+ 0,
+ LEVEL_0,
+ WRITE_AND_READ,
+ "Hysteresis mode");
+
+
+ DECLARE_FIELD(phaseMaxHistoricalVolFraction,
+ "phaseMaxHistoricalVolFraction",
+ array2dLayoutPhase,
+ 0,
+ LEVEL_0,
+ WRITE_AND_READ,
+ "Phase max historical phase volume fraction");
+
+ DECLARE_FIELD(phaseMinHistoricalVolFraction,
+ "phaseMinHistoricalVolFraction",
+ array2dLayoutPhase,
+ 0,
+ LEVEL_0,
+ WRITE_AND_READ,
+ "Phase min historical phase volume fraction");
+
+ DECLARE_FIELD(phaseMode2PeakVolFraction,
+ "phaseMode2PeakVolFraction",
+ array2dLayoutPhase,
+ 0,
+ LEVEL_0,
+ WRITE_AND_READ,
+ "Peak saturation reached during Mode 2 (DRAINAGE_TO_IMBIBITION)");
+
+
+ }
+
+ }
}
diff --git a/src/coreComponents/constitutive/capillaryPressure/CapillaryPressureSelector.hpp b/src/coreComponents/constitutive/capillaryPressure/CapillaryPressureSelector.hpp
index b164f55aa16..a8382183ba4 100644
--- a/src/coreComponents/constitutive/capillaryPressure/CapillaryPressureSelector.hpp
+++ b/src/coreComponents/constitutive/capillaryPressure/CapillaryPressureSelector.hpp
@@ -24,6 +24,7 @@
#include "constitutive/capillaryPressure/BrooksCoreyCapillaryPressure.hpp"
#include "constitutive/capillaryPressure/JFunctionCapillaryPressure.hpp"
#include "constitutive/capillaryPressure/TableCapillaryPressure.hpp"
+#include "constitutive/capillaryPressure/TableCapillaryPressureHysteresis.hpp"
#include "constitutive/capillaryPressure/VanGenuchtenCapillaryPressure.hpp"
namespace geos
@@ -39,6 +40,7 @@ void constitutiveUpdatePassThru( CapillaryPressureBase const & capPres,
ConstitutivePassThruHandler< BrooksCoreyCapillaryPressure,
JFunctionCapillaryPressure,
TableCapillaryPressure,
+ TableCapillaryPressureHysteresis,
VanGenuchtenCapillaryPressure >::execute( capPres, std::forward< LAMBDA >( lambda ) );
}
@@ -49,6 +51,7 @@ void constitutiveUpdatePassThru( CapillaryPressureBase & capPres,
ConstitutivePassThruHandler< BrooksCoreyCapillaryPressure,
JFunctionCapillaryPressure,
TableCapillaryPressure,
+ TableCapillaryPressureHysteresis,
VanGenuchtenCapillaryPressure >::execute( capPres, std::forward< LAMBDA >( lambda ) );
}
diff --git a/src/coreComponents/constitutive/capillaryPressure/InverseCapillaryPressure.cpp b/src/coreComponents/constitutive/capillaryPressure/InverseCapillaryPressure.cpp
index 534037c55f4..0156d06ab67 100644
--- a/src/coreComponents/constitutive/capillaryPressure/InverseCapillaryPressure.cpp
+++ b/src/coreComponents/constitutive/capillaryPressure/InverseCapillaryPressure.cpp
@@ -20,6 +20,7 @@
#include "InverseCapillaryPressure.hpp"
#include "constitutive/capillaryPressure/BrooksCoreyCapillaryPressure.hpp"
#include "constitutive/capillaryPressure/TableCapillaryPressure.hpp"
+#include "constitutive/capillaryPressure/TableCapillaryPressureHysteresis.hpp"
#include "constitutive/capillaryPressure/VanGenuchtenCapillaryPressure.hpp"
#include "constitutive/capillaryPressure/JFunctionCapillaryPressure.hpp"
@@ -317,6 +318,7 @@ void InverseCapillaryPressure< CAP_PRESSURE >::calculateJFunctionIndex( integer
template class InverseCapillaryPressure< BrooksCoreyCapillaryPressure >;
template class InverseCapillaryPressure< TableCapillaryPressure >;
+template class InverseCapillaryPressure< TableCapillaryPressureHysteresis >;
template class InverseCapillaryPressure< JFunctionCapillaryPressure >;
template class InverseCapillaryPressure< VanGenuchtenCapillaryPressure >;
template class InverseCapillaryPressure< NoOpCapillaryPressure >;
diff --git a/src/coreComponents/constitutive/capillaryPressure/JFunctionCapillaryPressure.cpp b/src/coreComponents/constitutive/capillaryPressure/JFunctionCapillaryPressure.cpp
index e4eb76d02ba..fbb31266692 100644
--- a/src/coreComponents/constitutive/capillaryPressure/JFunctionCapillaryPressure.cpp
+++ b/src/coreComponents/constitutive/capillaryPressure/JFunctionCapillaryPressure.cpp
@@ -119,6 +119,14 @@ JFunctionCapillaryPressure::JFunctionCapillaryPressure( std::string const & name
.setInputFlag( InputFlags::FALSE );
registerField< fields::cappres::jFuncMultiplier >( &m_jFuncMultiplier );
+
+ registerWrapper( viewKeyStruct::jFunctionWrappersString(), &m_jFuncKernelWrappers ).
+ setSizedFromParent( 0 ).
+ setRestartFlags( RestartFlags::NO_WRITE );
+
+ registerWrapper( viewKeyStruct::inverseJFunctionWrappersString(), &m_inverseJFuncKernelWrappers ).
+ setSizedFromParent( 0 ).
+ setRestartFlags( RestartFlags::NO_WRITE );
}
void JFunctionCapillaryPressure::postInputInitialization()
@@ -187,6 +195,7 @@ void JFunctionCapillaryPressure::initializePreSubGroups()
? true // pc on the gas phase, function must be increasing
: false; // pc on the water phase, function must be decreasing
TableCapillaryPressureHelpers::validateCapillaryPressureTable( jFuncTable, getFullName(), jFuncMustBeIncreasing );
+
}
else if( numPhases == 3 )
{
@@ -205,6 +214,7 @@ void JFunctionCapillaryPressure::initializePreSubGroups()
InputError, getDataContext() );
TableFunction const & jFuncTableNWI = functionManager.getGroup< TableFunction >( m_nonWettingIntermediateJFuncTableName );
TableCapillaryPressureHelpers::validateCapillaryPressureTable( jFuncTableNWI, getFullName(), true );
+
}
}
@@ -300,11 +310,48 @@ void JFunctionCapillaryPressure::createAllTableKernelWrappers()
// we want to make sure that the wrappers are always up-to-date, so we recreate them everytime
m_jFuncKernelWrappers.clear();
+ m_inverseJFuncKernelWrappers.clear();
+
if( numPhases == 2 )
{
+
TableFunction const & jFuncTable = functionManager.getGroup< TableFunction >( m_wettingNonWettingJFuncTableName );
m_jFuncKernelWrappers.emplace_back( jFuncTable.createKernelWrapper() );
+ auto const & satArrayView = jFuncTable.getCoordinates()[0];
+ auto const & jArrayView = jFuncTable.getValues();
+
+ std::vector< real64 > satVec( satArrayView.size() );
+ std::vector< real64 > jVec( jArrayView.size() );
+
+ std::copy( satArrayView.begin(), satArrayView.end(), satVec.begin() );
+ std::copy( jArrayView.begin(), jArrayView.end(), jVec.begin() );
+
+ // Reverse both arrays (if original J is decreasing in S)
+ std::reverse( jVec.begin(), jVec.end() );
+ std::reverse( satVec.begin(), satVec.end() );
+
+
+ auto inverseTable = std::make_shared< TableFunction >( "inverseJFunc", this );
+
+ real64_array invJVec( jVec.size() );
+ real64_array invSatVec( satVec.size() );
+ std::copy( jVec.begin(), jVec.end(), invJVec.data() );
+ std::copy( satVec.begin(), satVec.end(), invSatVec.data() );
+
+ array1d< real64_array > coordinates;
+ coordinates.emplace_back( std::move( invJVec ) );
+
+
+ std::vector< units::Unit > dimUnits = { units::Unknown }; // or actual unit if available
+
+ inverseTable->setTableCoordinates( coordinates, dimUnits );
+ inverseTable->setTableValues( std::move( invSatVec ), units::Unknown );
+ inverseTable->setInterpolationMethod( TableFunction::InterpolationType::Linear );
+
+ m_inverseJFuncKernelWrappers.emplace_back( inverseTable->createKernelWrapper() );
+ m_inverseTables.emplace_back( std::move( inverseTable ) );
+
// Populate the end-points from the tables
TableCapillaryPressureHelpers::populateMinPhaseVolumeFraction( m_phaseOrder.toSliceConst(), jFuncTable, m_phaseMinVolumeFraction );
}
@@ -313,8 +360,10 @@ void JFunctionCapillaryPressure::createAllTableKernelWrappers()
// the assumption used everywhere in this class is that the WI information comes before the NWI information
TableFunction const & jFuncTableWI = functionManager.getGroup< TableFunction >( m_wettingIntermediateJFuncTableName );
m_jFuncKernelWrappers.emplace_back( jFuncTableWI.createKernelWrapper() );
+ m_inverseJFuncKernelWrappers.emplace_back( jFuncTableWI.createKernelWrapper() );
TableFunction const & jFuncTableNWI = functionManager.getGroup< TableFunction >( m_nonWettingIntermediateJFuncTableName );
m_jFuncKernelWrappers.emplace_back( jFuncTableNWI.createKernelWrapper() );
+ m_inverseJFuncKernelWrappers.emplace_back( jFuncTableNWI.createKernelWrapper() );
// Populate the end-points from the tables
TableCapillaryPressureHelpers::populateMinPhaseVolumeFraction( m_phaseOrder.toSliceConst(), jFuncTableWI, jFuncTableNWI, m_phaseMinVolumeFraction );
@@ -323,16 +372,20 @@ void JFunctionCapillaryPressure::createAllTableKernelWrappers()
JFunctionCapillaryPressure::KernelWrapper::
KernelWrapper( arrayView1d< TableFunction::KernelWrapper const > const & jFuncKernelWrappers,
+ arrayView1d< TableFunction::KernelWrapper const > const & inverseJFuncKernelWrappers,
arrayView2d< real64 const > const & jFuncMultiplier,
arrayView1d< integer const > const & phaseTypes,
arrayView1d< integer const > const & phaseOrder,
+ arrayView3d< real64, cappres::USD_CAPPRES > const & phaseTrapped,
arrayView3d< real64, cappres::USD_CAPPRES > const & phaseCapPres,
arrayView4d< real64, cappres::USD_CAPPRES_DS > const & dPhaseCapPres_dPhaseVolFrac )
: CapillaryPressureBaseUpdate( phaseTypes,
phaseOrder,
+ phaseTrapped,
phaseCapPres,
dPhaseCapPres_dPhaseVolFrac ),
m_jFuncKernelWrappers( jFuncKernelWrappers ),
+ m_inverseJFuncKernelWrappers( inverseJFuncKernelWrappers ),
m_jFuncMultiplier( jFuncMultiplier )
{}
@@ -341,9 +394,11 @@ JFunctionCapillaryPressure::createKernelWrapper()
{
createAllTableKernelWrappers();
return KernelWrapper( m_jFuncKernelWrappers,
+ m_inverseJFuncKernelWrappers,
m_jFuncMultiplier,
m_phaseTypes,
m_phaseOrder,
+ m_phaseTrappedVolFrac,
m_phaseCapPressure,
m_dPhaseCapPressure_dPhaseVolFrac );
}
diff --git a/src/coreComponents/constitutive/capillaryPressure/JFunctionCapillaryPressure.hpp b/src/coreComponents/constitutive/capillaryPressure/JFunctionCapillaryPressure.hpp
index 4e42189d92e..29e7692bd95 100644
--- a/src/coreComponents/constitutive/capillaryPressure/JFunctionCapillaryPressure.hpp
+++ b/src/coreComponents/constitutive/capillaryPressure/JFunctionCapillaryPressure.hpp
@@ -65,9 +65,11 @@ class JFunctionCapillaryPressure : public CapillaryPressureBase
public:
KernelWrapper( arrayView1d< TableFunction::KernelWrapper const > const & jFuncKernelWrappers,
+ arrayView1d< TableFunction::KernelWrapper const > const & inverseJFuncKernelWrappers,
arrayView2d< real64 const > const & jFuncMultiplier,
arrayView1d< integer const > const & phaseTypes,
arrayView1d< integer const > const & phaseOrder,
+ arrayView3d< real64, cappres::USD_CAPPRES > const & phaseTrapped,
arrayView3d< real64, cappres::USD_CAPPRES > const & phaseCapPres,
arrayView4d< real64, cappres::USD_CAPPRES_DS > const & dPhaseCapPres_dPhaseVolFrac );
@@ -77,6 +79,13 @@ class JFunctionCapillaryPressure : public CapillaryPressureBase
arraySlice1d< real64, cappres::USD_CAPPRES - 2 > const & phaseCapPres,
arraySlice2d< real64, cappres::USD_CAPPRES_DS - 2 > const & dPhaseCapPres_dPhaseVolFrac ) const;
+ GEOS_HOST_DEVICE
+ void computeInv( arraySlice1d< real64, compflow::USD_PHASE - 1 > const & phaseVolFraction,
+ arraySlice1d< real64 const > const & jFuncMultiplier,
+ arraySlice1d< real64 const, cappres::USD_CAPPRES - 2 > const & phaseCapPres,
+ arraySlice2d< real64, cappres::USD_CAPPRES_DS - 2 > const & dPhaseCapPres_dPhaseVolFrac ) const;
+
+
GEOS_HOST_DEVICE
virtual void update( localIndex const k,
localIndex const q,
@@ -87,6 +96,7 @@ class JFunctionCapillaryPressure : public CapillaryPressureBase
/// Array of kernel wrappers for the J-function
/// Is of size 1 for two-phase flow, and of size 2 for three-phase flow
arrayView1d< TableFunction::KernelWrapper const > const m_jFuncKernelWrappers;
+ arrayView1d< TableFunction::KernelWrapper const > const m_inverseJFuncKernelWrappers;
/// Array of cell-wise J-function multipliers
/// The second dimension is of size 1 for two-phase flow, and of size 2 for three-phase flow
@@ -112,6 +122,8 @@ class JFunctionCapillaryPressure : public CapillaryPressureBase
static constexpr char const * porosityExponentString() { return "porosityExponent"; }
static constexpr char const * permeabilityExponentString() { return "permeabilityExponent"; }
static constexpr char const * permeabilityDirectionString() { return "permeabilityDirection"; }
+ static constexpr char const * jFunctionWrappersString() { return "jFunctionWrappers"; }
+ static constexpr char const * inverseJFunctionWrappersString() { return "inverseJFunctionWrappers"; }
};
/**
@@ -168,6 +180,9 @@ class JFunctionCapillaryPressure : public CapillaryPressureBase
/// J-function kernel wrapper for the first pair (wetting-intermediate if NP=3, wetting-non-wetting otherwise)
array1d< TableFunction::KernelWrapper > m_jFuncKernelWrappers;
+ array1d< TableFunction::KernelWrapper > m_inverseJFuncKernelWrappers;
+
+ std::vector< std::shared_ptr< TableFunction > > m_inverseTables;
};
@@ -242,6 +257,42 @@ JFunctionCapillaryPressure::KernelWrapper::
}
}
+GEOS_HOST_DEVICE
+inline void
+JFunctionCapillaryPressure::KernelWrapper::
+ computeInv( arraySlice1d< real64, compflow::USD_PHASE - 1 > const & phaseVolFraction,
+ arraySlice1d< real64 const > const & jFuncMultiplier,
+ arraySlice1d< real64 const, cappres::USD_CAPPRES - 2 > const & phaseCapPres,
+ arraySlice2d< real64, cappres::USD_CAPPRES_DS - 2 > const & dPhaseCapPres_dPhaseVolFrac ) const
+{
+ LvArray::forValuesInSlice( dPhaseCapPres_dPhaseVolFrac, []( real64 & val ){ val = 0.0; } );
+
+ using PT = CapillaryPressureBase::PhaseType;
+ integer const ipWater = m_phaseOrder[PT::WATER];
+ integer const ipOil = m_phaseOrder[PT::OIL];
+ integer const ipGas = m_phaseOrder[PT::GAS];
+
+ GEOS_UNUSED_VAR( ipOil );
+ // apply multiplier
+ real64 capPresWater_J = phaseCapPres[ipWater] / jFuncMultiplier[0];
+ // std::cout << GEOS_FMT( " JM_2 = ( {:4.2e} )", jFuncMultiplier[0] );
+ array1d< real64 > input( 1 );
+ input[0] = capPresWater_J;
+ // std::cout << GEOS_FMT( " J_int2 = ( {:4.2e} )", input[0] );
+ // std::cout << GEOS_FMT( " Pc_int2 = ( {:4.2e} )", phaseCapPres[ipWater] );
+ auto inputSlice = input.toSliceConst();
+
+
+
+ phaseVolFraction[ipWater] =
+ m_inverseJFuncKernelWrappers[0].compute( inputSlice,
+ &(dPhaseCapPres_dPhaseVolFrac)[ipWater][ipWater] );
+ dPhaseCapPres_dPhaseVolFrac[ipWater][ipWater] /= jFuncMultiplier[0];
+ // std::cout << GEOS_FMT( " S_int2 = ( {:4.2e} )", phaseVolFraction[ipWater] );
+ // std::cout << GEOS_FMT( " dS/dP = ( {:4.2e} )", dPhaseCapPres_dPhaseVolFrac[ipWater][ipWater] );
+ phaseVolFraction[ipGas] = 1.0 - phaseVolFraction[ipWater];
+}
+
GEOS_HOST_DEVICE
inline void
JFunctionCapillaryPressure::KernelWrapper::
diff --git a/src/coreComponents/constitutive/capillaryPressure/TableCapillaryPressure.cpp b/src/coreComponents/constitutive/capillaryPressure/TableCapillaryPressure.cpp
index 17b94f4730f..ded882f76d0 100644
--- a/src/coreComponents/constitutive/capillaryPressure/TableCapillaryPressure.cpp
+++ b/src/coreComponents/constitutive/capillaryPressure/TableCapillaryPressure.cpp
@@ -69,6 +69,10 @@ TableCapillaryPressure::TableCapillaryPressure( std::string const & name,
registerWrapper( viewKeyStruct::capPresWrappersString(), &m_capPresKernelWrappers ).
setSizedFromParent( 0 ).
setRestartFlags( RestartFlags::NO_WRITE );
+
+ registerWrapper( viewKeyStruct::inverseCapPresWrappersString(), &m_inverseCapPresWrappers ).
+ setSizedFromParent( 0 ).
+ setRestartFlags( RestartFlags::NO_WRITE );
}
void TableCapillaryPressure::postInputInitialization()
@@ -154,11 +158,46 @@ void TableCapillaryPressure::createAllTableKernelWrappers()
// we want to make sure that the wrappers are always up-to-date, so we recreate them everytime
m_capPresKernelWrappers.clear();
+ m_inverseCapPresWrappers.clear();
+
if( numPhases == 2 )
{
TableFunction const & capPresTable = functionManager.getGroup< TableFunction >( m_wettingNonWettingCapPresTableName );
m_capPresKernelWrappers.emplace_back( capPresTable.createKernelWrapper() );
+ auto const & satArrayView = capPresTable.getCoordinates()[0];
+ auto const & capPresArrayView = capPresTable.getValues();
+
+ std::vector< real64 > satVec( satArrayView.size() );
+ std::vector< real64 > pcVec( capPresArrayView.size() );
+
+ std::copy( satArrayView.begin(), satArrayView.end(), satVec.begin() );
+ std::copy( capPresArrayView.begin(), capPresArrayView.end(), pcVec.begin() );
+
+ // Reverse both arrays (if original J is decreasing in S)
+ std::reverse( pcVec.begin(), pcVec.end() );
+ std::reverse( satVec.begin(), satVec.end() );
+
+ auto inverseTable = std::make_shared< TableFunction >( "inverseCapPres", this );
+
+ real64_array invPcVec( pcVec.size() );
+ real64_array invSatVec( satVec.size() );
+ std::copy( pcVec.begin(), pcVec.end(), invPcVec.data() );
+ std::copy( satVec.begin(), satVec.end(), invSatVec.data() );
+
+ array1d< real64_array > coordinates;
+ coordinates.emplace_back( std::move( invPcVec ) );
+
+
+ std::vector< units::Unit > dimUnits = { units::Unknown }; // or actual unit if available
+
+ inverseTable->setTableCoordinates( coordinates, dimUnits );
+ inverseTable->setTableValues( std::move( invSatVec ), units::Unknown );
+ inverseTable->setInterpolationMethod( TableFunction::InterpolationType::Linear );
+
+ m_inverseCapPresWrappers.emplace_back( inverseTable->createKernelWrapper() );
+ m_inverseTables.emplace_back( std::move( inverseTable ) );
+
// Populate the end-points from the tables
TableCapillaryPressureHelpers::populateMinPhaseVolumeFraction( m_phaseOrder.toSliceConst(), capPresTable, m_phaseMinVolumeFraction );
}
@@ -166,8 +205,10 @@ void TableCapillaryPressure::createAllTableKernelWrappers()
{
TableFunction const & capPresTableWI = functionManager.getGroup< TableFunction >( m_wettingIntermediateCapPresTableName );
m_capPresKernelWrappers.emplace_back( capPresTableWI.createKernelWrapper() );
+ m_inverseCapPresWrappers.emplace_back( capPresTableWI.createKernelWrapper() );
TableFunction const & capPresTableNWI = functionManager.getGroup< TableFunction >( m_nonWettingIntermediateCapPresTableName );
m_capPresKernelWrappers.emplace_back( capPresTableNWI.createKernelWrapper() );
+ m_inverseCapPresWrappers.emplace_back( capPresTableNWI.createKernelWrapper() );
// Populate the end-points from the tables
TableCapillaryPressureHelpers::populateMinPhaseVolumeFraction( m_phaseOrder.toSliceConst(), capPresTableWI, capPresTableNWI, m_phaseMinVolumeFraction );
@@ -177,15 +218,19 @@ void TableCapillaryPressure::createAllTableKernelWrappers()
TableCapillaryPressure::KernelWrapper::
KernelWrapper( arrayView1d< TableFunction::KernelWrapper const > const & capPresKernelWrappers,
+ arrayView1d< TableFunction::KernelWrapper const > const & inverseCapPresWrappers,
arrayView1d< integer const > const & phaseTypes,
arrayView1d< integer const > const & phaseOrder,
+ arrayView3d< real64, cappres::USD_CAPPRES > const & phaseTrapped,
arrayView3d< real64, cappres::USD_CAPPRES > const & phaseCapPres,
arrayView4d< real64, cappres::USD_CAPPRES_DS > const & dPhaseCapPres_dPhaseVolFrac )
: CapillaryPressureBaseUpdate( phaseTypes,
phaseOrder,
+ phaseTrapped,
phaseCapPres,
dPhaseCapPres_dPhaseVolFrac ),
- m_capPresKernelWrappers( capPresKernelWrappers )
+ m_capPresKernelWrappers( capPresKernelWrappers ),
+ m_inverseCapPresWrappers( inverseCapPresWrappers )
{}
TableCapillaryPressure::KernelWrapper
@@ -193,8 +238,10 @@ TableCapillaryPressure::createKernelWrapper()
{
createAllTableKernelWrappers();
return KernelWrapper( m_capPresKernelWrappers,
+ m_inverseCapPresWrappers,
m_phaseTypes,
m_phaseOrder,
+ m_phaseTrappedVolFrac,
m_phaseCapPressure,
m_dPhaseCapPressure_dPhaseVolFrac );
}
diff --git a/src/coreComponents/constitutive/capillaryPressure/TableCapillaryPressure.hpp b/src/coreComponents/constitutive/capillaryPressure/TableCapillaryPressure.hpp
index 739b9993083..f7b661a6090 100644
--- a/src/coreComponents/constitutive/capillaryPressure/TableCapillaryPressure.hpp
+++ b/src/coreComponents/constitutive/capillaryPressure/TableCapillaryPressure.hpp
@@ -55,8 +55,10 @@ class TableCapillaryPressure : public CapillaryPressureBase
public:
KernelWrapper( arrayView1d< TableFunction::KernelWrapper const > const & capPresKernelWrappers,
+ arrayView1d< TableFunction::KernelWrapper const > const & inverseCapPresWrappers,
arrayView1d< integer const > const & phaseTypes,
arrayView1d< integer const > const & phaseOrder,
+ arrayView3d< real64, cappres::USD_CAPPRES > const & phaseTrapped,
arrayView3d< real64, cappres::USD_CAPPRES > const & phaseCapPres,
arrayView4d< real64, cappres::USD_CAPPRES_DS > const & dPhaseCapPres_dPhaseVolFrac );
@@ -65,6 +67,12 @@ class TableCapillaryPressure : public CapillaryPressureBase
arraySlice1d< real64, cappres::USD_CAPPRES - 2 > const & phaseCapPres,
arraySlice2d< real64, cappres::USD_CAPPRES_DS - 2 > const & dPhaseCapPres_dPhaseVolFrac ) const;
+ GEOS_HOST_DEVICE
+ void computeInv( arraySlice1d< real64, compflow::USD_PHASE - 1 > const & phaseVolFraction,
+ arraySlice1d< real64 const, cappres::USD_CAPPRES - 2 > const & phaseCapPres,
+ arraySlice2d< real64, cappres::USD_CAPPRES_DS - 2 > const & dPhaseCapPres_dPhaseVolFrac ) const;
+
+
GEOS_HOST_DEVICE
virtual void update( localIndex const k,
localIndex const q,
@@ -75,6 +83,7 @@ class TableCapillaryPressure : public CapillaryPressureBase
/// Array of kernel wrappers for the capillary pressures
/// Is of size 1 for two-phase flow, and of size 2 for three-phase flow
arrayView1d< TableFunction::KernelWrapper const > const m_capPresKernelWrappers;
+ arrayView1d< TableFunction::KernelWrapper const > const m_inverseCapPresWrappers;
};
@@ -91,6 +100,7 @@ class TableCapillaryPressure : public CapillaryPressureBase
static constexpr char const * wettingIntermediateCapPresTableNameString() { return "wettingIntermediateCapPressureTableName"; }
static constexpr char const * nonWettingIntermediateCapPresTableNameString() { return "nonWettingIntermediateCapPressureTableName"; }
static constexpr char const * capPresWrappersString() { return "capPresWrappers"; }
+ static constexpr char const * inverseCapPresWrappersString() { return "inverseCapPresWrappers"; }
};
@@ -116,6 +126,9 @@ class TableCapillaryPressure : public CapillaryPressureBase
/// Capillary pressure kernel wrapper for the first pair (wetting-intermediate if NP=3, wetting-non-wetting otherwise)
array1d< TableFunction::KernelWrapper > m_capPresKernelWrappers;
+ array1d< TableFunction::KernelWrapper > m_inverseCapPresWrappers;
+
+ std::vector< std::shared_ptr< TableFunction > > m_inverseTables;
};
@@ -173,6 +186,37 @@ TableCapillaryPressure::KernelWrapper::
}
}
+GEOS_HOST_DEVICE
+inline void
+TableCapillaryPressure::KernelWrapper::
+ computeInv( arraySlice1d< real64, compflow::USD_PHASE - 1 > const & phaseVolFraction,
+ arraySlice1d< real64 const, cappres::USD_CAPPRES - 2 > const & phaseCapPres,
+ arraySlice2d< real64, cappres::USD_CAPPRES_DS - 2 > const & dPhaseCapPres_dPhaseVolFrac ) const
+{
+ LvArray::forValuesInSlice( dPhaseCapPres_dPhaseVolFrac, []( real64 & val ){ val = 0.0; } );
+
+ using PT = CapillaryPressureBase::PhaseType;
+ integer const ipWater = m_phaseOrder[PT::WATER];
+ integer const ipOil = m_phaseOrder[PT::OIL];
+ integer const ipGas = m_phaseOrder[PT::GAS];
+
+ GEOS_UNUSED_VAR( ipOil );
+
+ // put capillary pressure on the wetting phase
+ real64 capPresWater = phaseCapPres[ipWater];
+ array1d< real64 > input( 1 );
+ input[0] = capPresWater;
+ auto inputSlice = input.toSliceConst();
+
+ phaseVolFraction[ipWater] =
+ m_inverseCapPresWrappers[0].compute( inputSlice,
+ &(dPhaseCapPres_dPhaseVolFrac)[ipWater][ipWater] );
+
+ phaseVolFraction[ipGas] = 1.0 - phaseVolFraction[ipWater];
+
+}
+
+
GEOS_HOST_DEVICE
inline void
TableCapillaryPressure::KernelWrapper::
diff --git a/src/coreComponents/constitutive/capillaryPressure/TableCapillaryPressureHelpers.cpp b/src/coreComponents/constitutive/capillaryPressure/TableCapillaryPressureHelpers.cpp
index 619e69a22db..d412260e7c5 100644
--- a/src/coreComponents/constitutive/capillaryPressure/TableCapillaryPressureHelpers.cpp
+++ b/src/coreComponents/constitutive/capillaryPressure/TableCapillaryPressureHelpers.cpp
@@ -130,18 +130,46 @@ void TableCapillaryPressureHelpers::populateMinPhaseVolumeFraction(
}
void
-TableCapillaryPressureHelpers::validateCapillaryPressureTable( geos::TableFunction const & capPresTable,
- geos::string const & fullConstitutiveName,
- bool const capPresMustBeIncreasing,
- geos::real64 & phaseMax, geos::real64 & phaseMin )
+TableCapillaryPressureHelpers::validateCapillaryPressureTable( const geos::TableFunction & capPresTable,
+ const geos::string & fullConstitutiveName,
+ const bool capPresMustBeIncreasing,
+ geos::real64 & phaseMax,
+ geos::real64 & phaseMin,
+ geos::real64 & phaseCapPresMinEndPoint,
+ geos::real64 & phaseCapPresMaxEndPoint )
{
TableCapillaryPressureHelpers::validateCapillaryPressureTable( capPresTable, fullConstitutiveName, capPresMustBeIncreasing );
ArrayOfArraysView< real64 const > coords = capPresTable.getCoordinates();
arraySlice1d< real64 const > phaseVolFrac = coords[0];
+ arrayView1d< real64 const > const capPres = capPresTable.getValues();
+
phaseMin = phaseVolFrac[0];
+ phaseCapPresMinEndPoint = capPres[0];
phaseMax = phaseVolFrac[phaseVolFrac.size()-1];
-}
+ phaseCapPresMaxEndPoint = capPres[phaseVolFrac.size()-1];
+
+
+ if(capPresMustBeIncreasing) {
+
+ for( localIndex i = 1; i < coords.sizeOfArray( 0 ); ++i ) {
+ if (isZero(capPres[i - 1]) && !isZero(capPres[i])) {
+ phaseMin = phaseVolFrac[i - 1];
+ phaseCapPresMinEndPoint = capPres[i - 1];
+ }
+ }
+ }
+ else
+ {
+ for( localIndex i = coords.sizeOfArray( 0 )-2; i>0; --i ) {
+ if (isZero(capPres[i + 1]) && !isZero(capPres[i])) {
+ phaseMax = phaseVolFrac[i + 1];
+ phaseCapPresMaxEndPoint = capPres[i + 1];
+ }
+ }
+
+ }
+ }
} // namespace constitutive
diff --git a/src/coreComponents/constitutive/capillaryPressure/TableCapillaryPressureHelpers.hpp b/src/coreComponents/constitutive/capillaryPressure/TableCapillaryPressureHelpers.hpp
index a691637002c..276d8c3eb92 100644
--- a/src/coreComponents/constitutive/capillaryPressure/TableCapillaryPressureHelpers.hpp
+++ b/src/coreComponents/constitutive/capillaryPressure/TableCapillaryPressureHelpers.hpp
@@ -47,7 +47,9 @@ struct TableCapillaryPressureHelpers
string const & fullConstitutiveName,
bool const capPresMustBeIncreasing,
real64 & phaseMax,
- real64 & phaseMin );
+ real64 & phaseMin,
+ real64 & phaseCapPresMinEndPoint,
+ real64 & phaseCapPresMaxEndPoint );
/**
* @brief Populates the minimum phase volume fraction for each phase from the ends of the provided tables
diff --git a/src/coreComponents/constitutive/capillaryPressure/TableCapillaryPressureHysteresis.cpp b/src/coreComponents/constitutive/capillaryPressure/TableCapillaryPressureHysteresis.cpp
new file mode 100644
index 00000000000..3485f743c00
--- /dev/null
+++ b/src/coreComponents/constitutive/capillaryPressure/TableCapillaryPressureHysteresis.cpp
@@ -0,0 +1,4004 @@
+/*
+ * ------------------------------------------------------------------------------------------------------------
+ * SPDX-License-Identifier: LGPL-2.1-only
+ *
+ * Copyright (c) 2018-2020 Lawrence Livermore National Security LLC
+ * Copyright (c) 2018-2020 The Board of Trustees of the Leland Stanford Junior University
+ * Copyright (c) 2018-2020 TotalEnergies
+ * Copyright (c) 2019- GEOSX Contributors
+ * All rights reserved
+ *
+ * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details.
+ * ------------------------------------------------------------------------------------------------------------
+ */
+
+
+#include "TableCapillaryPressureHysteresis.hpp"
+
+#include "constitutive/capillaryPressure/TableCapillaryPressureHelpers.hpp"
+#include "functions/FunctionManager.hpp"
+#include "constitutive/ConstitutiveManager.hpp"
+
+namespace geos {
+
+ using namespace dataRepository;
+
+ namespace constitutive {
+
+ TableCapillaryPressureHysteresis::TableCapillaryPressureHysteresis(const std::string &name,
+ dataRepository::Group *const parent)
+ : CapillaryPressureBase(name, parent) {
+
+ registerWrapper(viewKeyStruct::phaseHasHysteresisString(), &m_phaseHasHysteresis).
+ setInputFlag(InputFlags::FALSE)
+ . // will be deduced from tables
+ setSizedFromParent(0);
+
+ registerWrapper(viewKeyStruct::landParameterString(), &m_landParam).
+ setInputFlag(InputFlags::FALSE). // will be deduced from tables
+ setSizedFromParent(0);
+
+ //2phase
+ registerWrapper(viewKeyStruct::drainageWettingNonWettingCapPresTableNameString(),
+ &m_drainageWettingNonWettingCapPresTableName).
+ setInputFlag(InputFlags::OPTIONAL).
+ setDescription("Name of the drainage two-phase table for capillary pressure curve. \n"
+ "If you want to use 3-phase flow please use instead " +
+ string(viewKeyStruct::drainageWettingIntermediateCapPresTableNameString()) +
+ " and " +
+ string(viewKeyStruct::drainageNonWettingIntermediateCapPresTableNameString()) +
+ "to specify the tables names");
+ registerWrapper(viewKeyStruct::imbibitionWettingNonWettingCapPresTableNameString(),
+ &m_imbibitionWettingNonWettingCapPresTableName).
+ setInputFlag(InputFlags::OPTIONAL).
+ setDescription("Name of the drainage two-phase table for capillary pressure curve. \n"
+ "If you want to use 3-phase flow please use instead " +
+ string(viewKeyStruct::imbibitionWettingIntermediateCapPresTableNameString()) +
+ " and " +
+ string(viewKeyStruct::imbibitionNonWettingIntermediateCapPresTableNameString()) +
+ "to specify the tables names");
+ //3phase
+ registerWrapper(viewKeyStruct::drainageWettingIntermediateCapPresTableNameString(),
+ &m_drainageWettingIntermediateCapPresTableName).
+ setInputFlag(InputFlags::OPTIONAL).
+ setDescription(
+ "Drainage wetting/intermediate (e.g. w/o) capillary pressure table name for the wetting phase.\n"
+ "To neglect hysteresis on this phase, just use the same table name for the drainage and imbibition curves");
+ registerWrapper(viewKeyStruct::drainageNonWettingIntermediateCapPresTableNameString(),
+ &m_drainageNonWettingIntermediateCapPresTableName).
+ setInputFlag(InputFlags::OPTIONAL).
+ setDescription(
+ "Drainage non-wetting/intermediate (e.g. o/g) capillary pressure table name for the non-wetting phase.\n"
+ "To neglect hysteresis on this phase, just use the same table name for the drainage and imbibition curves");
+ registerWrapper(viewKeyStruct::imbibitionWettingIntermediateCapPresTableNameString(),
+ &m_imbibitionWettingIntermediateCapPresTableName).
+ setInputFlag(InputFlags::OPTIONAL).
+ setDescription("Imbibition wetting/intermediate (e.g. w/o) table name for the wetting phase.\n"
+ "To neglect hysteresis on this phase, just use the same table name for the drainage and imbibition curves");
+ registerWrapper(viewKeyStruct::imbibitionNonWettingIntermediateCapPresTableNameString(),
+ &m_imbibitionNonWettingIntermediateCapPresTableName).
+ setInputFlag(InputFlags::OPTIONAL).
+ setDescription("Imbibition non-wetting/intermediate (e.g. o/g) table name for the wetting phase.\n"
+ "To neglect hysteresis on this phase, just use the same table name for the drainage and imbibition curves");
+
+ // kernels
+ //2p
+ registerWrapper(viewKeyStruct::wettingNonWettingCapillaryPressureKernelWrappersString(),
+ &m_wettingNonWettingCapillaryPressureKernelWrappers)
+ .setSizedFromParent(0).setRestartFlags(RestartFlags::NO_WRITE);
+ //3p
+ registerWrapper(viewKeyStruct::wettingIntermediateCapillaryPressureKernelWrappersString(),
+ &m_wettingIntermediateCapillaryPressureKernelWrappers)
+ .setSizedFromParent(0).setRestartFlags(RestartFlags::NO_WRITE);
+ registerWrapper(viewKeyStruct::nonWettingIntermediateCapillaryPressureKernelWrappersString(),
+ &m_nonWettingIntermediateCapillaryPressureKernelWrappers)
+ .setSizedFromParent(0).setRestartFlags(RestartFlags::NO_WRITE);
+
+
+ registerWrapper(viewKeyStruct::wettingCurveString(), &m_wettingCurve).
+ setInputFlag(
+ InputFlags::FALSE). // will be deduced from tables
+ setSizedFromParent(
+ 0)
+ .setRestartFlags(RestartFlags::NO_WRITE);
+
+ registerWrapper(viewKeyStruct::nonWettingCurveString(), &m_nonWettingCurve).
+ setInputFlag(
+ InputFlags::FALSE). // will be deduced from tables
+ setSizedFromParent(
+ 0)
+ .setRestartFlags(RestartFlags::NO_WRITE);
+
+ //Forwarded to KilloughHysteresis
+ registerWrapper(KilloughHysteresis::viewKeyStruct::jerauldParameterAString(), &m_jerauldParam_a).
+ setInputFlag(InputFlags::OPTIONAL).
+ setApplyDefaultValue(0.1).
+ setDescription(
+ "First parameter (modification parameter) introduced by Jerauld in the Land trapping model (see RTD documentation).");
+
+ registerWrapper(KilloughHysteresis::viewKeyStruct::jerauldParameterBString(), &m_jerauldParam_b).
+ setInputFlag(InputFlags::OPTIONAL).
+ setApplyDefaultValue(0.0).
+ setDescription(
+ "Second parameter (modification parameter) introduced by Jerauld in the Land trapping model (see RTD documentation).");
+
+
+ registerWrapper(KilloughHysteresis::viewKeyStruct::killoughCurvatureParameterPcString(),
+ &m_killoughCurvatureParamCapPres).
+ setInputFlag(
+ InputFlags::OPTIONAL).
+ setApplyDefaultValue(
+ .1).
+ setDescription(
+ "Curvature parameter introduced by Killough for wetting-phase hysteresis (see RTD documentation).");
+
+ //misc
+ registerWrapper(viewKeyStruct::phaseIntermediateMinVolFractionString(), &m_phaseIntermediateMinVolFraction).
+ setInputFlag(InputFlags::FALSE).setDescription("min vol fraction of intermediate if exist").
+ // will be deduced from tables
+ setSizedFromParent(0);
+
+ registerField< fields::cappres::mode >( &m_mode );
+
+
+ registerField< fields::cappres::phaseMaxHistoricalVolFraction >(
+ &m_phaseMaxHistoricalVolFraction );
+ registerField< fields::cappres::phaseMinHistoricalVolFraction >(
+ &m_phaseMinHistoricalVolFraction );
+ registerField< fields::cappres::phaseMode2PeakVolFraction >(
+ &m_phaseMode2PeakVolFraction );
+
+ }
+
+/// usual utils
+
+ void TableCapillaryPressureHysteresis::postProcessInput() {
+
+ using TPP = ThreePhasePairPhaseType;
+
+ integer const numPhases = m_phaseNames.size();
+ GEOS_THROW_IF(numPhases != 2 && numPhases != 3,
+ GEOS_FMT("{}: the expected number of fluid phases is either two, or three",
+ getFullName()),
+ InputError);
+
+ m_phaseHasHysteresis.resize(2);
+
+ if (numPhases == 2) {
+ GEOS_THROW_IF(m_drainageWettingNonWettingCapPresTableName.empty(),
+ GEOS_FMT(
+ "{}: for a two-phase flow simulation, we must use {} to specify the capillary pressure table for the drainage pair (wetting phase, non-wetting phase)",
+ getFullName(),
+ viewKeyStruct::drainageWettingNonWettingCapPresTableNameString()),
+ InputError);
+
+
+ m_phaseHasHysteresis[TPP::INTERMEDIATE_WETTING] = (m_imbibitionWettingNonWettingCapPresTableName.empty() ||
+ m_imbibitionWettingNonWettingCapPresTableName ==
+ m_drainageWettingNonWettingCapPresTableName)
+ ? 0 : 1;
+ m_phaseHasHysteresis[TPP::INTERMEDIATE_NONWETTING] = m_phaseHasHysteresis[TPP::INTERMEDIATE_WETTING];
+
+
+ } else if (numPhases == 3) {
+
+
+ GEOS_THROW_IF(m_drainageWettingIntermediateCapPresTableName.empty() ||
+ m_drainageNonWettingIntermediateCapPresTableName.empty(),
+ GEOS_FMT(
+ "{}: for a three-phase flow simulation, we must use {} to specify the capillary pressure table "
+ "for the pair (wetting phase, intermediate phase), and {} to specify the capillary pressure table "
+ "for the pair (non-wetting phase, intermediate phase)",
+ getFullName(),
+ viewKeyStruct::drainageWettingIntermediateCapPresTableNameString(),
+ viewKeyStruct::drainageNonWettingIntermediateCapPresTableNameString()),
+ InputError);
+
+ m_phaseHasHysteresis[TPP::INTERMEDIATE_WETTING] = (m_imbibitionWettingIntermediateCapPresTableName.empty() ||
+ m_imbibitionWettingIntermediateCapPresTableName ==
+ m_drainageWettingIntermediateCapPresTableName)
+ ? 0 : 1;
+
+ m_phaseHasHysteresis[TPP::INTERMEDIATE_NONWETTING] = (m_imbibitionNonWettingIntermediateCapPresTableName.empty() ||
+ m_imbibitionNonWettingIntermediateCapPresTableName ==
+ m_drainageNonWettingIntermediateCapPresTableName)
+ ? 0 : 1;
+ }
+ //Killough section
+ //TODO improve hard coded default
+ KilloughHysteresis::postProcessInput(m_jerauldParam_a, m_jerauldParam_b, 0,
+ m_killoughCurvatureParamCapPres);
+
+ GEOS_THROW_IF(m_phaseHasHysteresis[TPP::INTERMEDIATE_WETTING] == 0 &&
+ m_phaseHasHysteresis[TPP::INTERMEDIATE_NONWETTING] == 0,
+ GEOS_FMT(
+ "{}: we must use {} (2-phase) / {} or {} (3-phase) to specify at least one imbibition relative permeability table",
+ getFullName(),
+ viewKeyStruct::imbibitionWettingNonWettingCapPresTableNameString(),
+ viewKeyStruct::imbibitionWettingIntermediateCapPresTableNameString(),
+ viewKeyStruct::imbibitionNonWettingIntermediateCapPresTableNameString()),
+ InputError);
+
+ }
+
+ void TableCapillaryPressureHysteresis::initializePreSubGroups() {
+ CapillaryPressureBase::initializePreSubGroups();
+
+ integer const numPhases = m_phaseNames.size();
+ FunctionManager const &functionManager = FunctionManager::getInstance();
+
+ //equivalent to oil/gas - a.k.a two phase flow ordered by non wetting
+ bool const capPresMustBeIncreasing = (m_phaseOrder[PhaseType::WATER] < 0)
+ ? true // pc on the gas phase, function must be increasing
+ : false; // pc on the water phase, function must be decreasing
+
+
+ // Step 1: check sanity of drainage tables
+ if (numPhases == 2) {
+
+ real64 drainageWettingPhaseMaxVolumeFraction, drainageWettingMinCapPres,
+ drainageNonWettingPhaseMinVolumeFraction, drainageNonWettingMinCapPres,
+ imbibitionWettingPhaseMaxVolumeFraction, imbibitionWettingMinCapPres,
+ imbibitionNonWettingPhaseMinVolumeFraction, imbibitionNonWettingMinCapPres,
+ wettingPhaseMinVolumeFraction, wettingMaxCapPres,
+ nonWettingPhaseMaxVolumeFraction, nonWettingMaxCapPres;
+
+ {
+
+ imbibitionNonWettingMinCapPres = 0.0;
+
+ GEOS_THROW_IF(!functionManager.hasGroup(m_drainageWettingNonWettingCapPresTableName),
+ GEOS_FMT("{}: the table function named {} could not be found",
+ getFullName(),
+ m_drainageWettingNonWettingCapPresTableName),
+ InputError);
+ TableFunction const
+ &capPresTable = functionManager.getGroup(
+ m_drainageWettingNonWettingCapPresTableName);
+
+ //w/o or w/g pair
+ if (!capPresMustBeIncreasing) {
+ TableCapillaryPressureHelpers::validateCapillaryPressureTable(capPresTable, getFullName(),
+ capPresMustBeIncreasing,
+ drainageWettingPhaseMaxVolumeFraction,
+ wettingPhaseMinVolumeFraction,
+ drainageWettingMinCapPres,
+ wettingMaxCapPres);
+
+ drainageNonWettingPhaseMinVolumeFraction = 1. - drainageWettingPhaseMaxVolumeFraction;
+ nonWettingPhaseMaxVolumeFraction = 1. - wettingPhaseMinVolumeFraction;
+
+ } else { // o/g pair
+ TableCapillaryPressureHelpers::validateCapillaryPressureTable(capPresTable, getFullName(),
+ capPresMustBeIncreasing,
+ nonWettingPhaseMaxVolumeFraction,
+ drainageNonWettingPhaseMinVolumeFraction,
+ nonWettingMaxCapPres,
+ drainageNonWettingMinCapPres );
+
+ drainageWettingPhaseMaxVolumeFraction = 1. - drainageNonWettingPhaseMinVolumeFraction;
+ wettingPhaseMinVolumeFraction = 1. - nonWettingPhaseMaxVolumeFraction;
+ }
+
+ }
+
+ {
+ GEOS_THROW_IF(!functionManager.hasGroup(m_imbibitionWettingNonWettingCapPresTableName),
+ GEOS_FMT("{}: the table function named {} could not be found",
+ getFullName(),
+ m_imbibitionWettingNonWettingCapPresTableName),
+ InputError);
+ TableFunction const
+ &capPresTable = functionManager.getGroup(
+ m_imbibitionWettingNonWettingCapPresTableName);
+
+ //w/o or w/g pair
+ if (!capPresMustBeIncreasing) {
+ TableCapillaryPressureHelpers::validateCapillaryPressureTable(capPresTable, getFullName(),
+ capPresMustBeIncreasing,
+ imbibitionWettingPhaseMaxVolumeFraction,
+ wettingPhaseMinVolumeFraction,
+ imbibitionWettingMinCapPres,
+ wettingMaxCapPres);
+
+ imbibitionNonWettingPhaseMinVolumeFraction = 1. - imbibitionWettingPhaseMaxVolumeFraction;
+ nonWettingPhaseMaxVolumeFraction = 1. - wettingPhaseMinVolumeFraction;
+
+ } else { // o/g pair
+ TableCapillaryPressureHelpers::validateCapillaryPressureTable(capPresTable, getFullName(),
+ capPresMustBeIncreasing,
+ nonWettingPhaseMaxVolumeFraction,
+ imbibitionNonWettingPhaseMinVolumeFraction,
+ nonWettingMaxCapPres,
+ imbibitionWettingMinCapPres );
+
+ imbibitionWettingPhaseMaxVolumeFraction = 1. - imbibitionNonWettingPhaseMinVolumeFraction;
+ wettingPhaseMinVolumeFraction = 1. - nonWettingPhaseMaxVolumeFraction;
+ }
+ }
+
+ //constructing wetting/nonwetting curves
+
+ if(!capPresMustBeIncreasing) {
+ m_wettingCurve.setPoints(
+ {wettingPhaseMinVolumeFraction, wettingMaxCapPres}, // same as imbibition min
+ {imbibitionWettingPhaseMaxVolumeFraction, imbibitionWettingMinCapPres},
+ {drainageWettingPhaseMaxVolumeFraction, drainageWettingMinCapPres});
+ }
+ else {
+ m_nonWettingCurve.setPoints(
+ {nonWettingPhaseMaxVolumeFraction,nonWettingMaxCapPres},
+ {imbibitionNonWettingPhaseMinVolumeFraction,imbibitionNonWettingMinCapPres},
+ {drainageNonWettingPhaseMinVolumeFraction,drainageNonWettingMinCapPres}
+ );
+ }
+
+ } else if (numPhases == 3) {
+
+ real64 drainageWettingPhaseMaxVolumeFraction, drainageWettingMinCapPres,
+ drainageNonWettingPhaseMinVolumeFraction, drainageNonWettingMinCapPres,
+ imbibitionWettingPhaseMaxVolumeFraction, imbibitionWettingMinCapPres,
+ imbibitionNonWettingPhaseMinVolumeFraction, imbibitionNonWettingMinCapPres,
+ wettingPhaseMinVolumeFraction, wettingMaxCapPres,
+ nonWettingPhaseMaxVolumeFraction, nonWettingMaxCapPres;
+
+ GEOS_UNUSED_VAR( drainageWettingMinCapPres );
+//define scope to avoid differentiate temp var (lazy)
+ {
+ GEOS_THROW_IF(!functionManager.hasGroup(m_drainageWettingIntermediateCapPresTableName),
+ GEOS_FMT("{}: the table function named {} could not be found",
+ getFullName(),
+ m_drainageWettingIntermediateCapPresTableName),
+ InputError);
+ TableFunction const
+ &capPresTableWI = functionManager.getGroup(
+ m_drainageWettingIntermediateCapPresTableName);
+ TableCapillaryPressureHelpers::validateCapillaryPressureTable(capPresTableWI, getFullName(), false,
+ drainageWettingPhaseMaxVolumeFraction,
+ wettingPhaseMinVolumeFraction,
+ drainageNonWettingMinCapPres,
+ wettingMaxCapPres);
+
+ GEOS_THROW_IF(!functionManager.hasGroup(m_drainageNonWettingIntermediateCapPresTableName),
+ GEOS_FMT("{}: the table function named {} could not be found",
+ getFullName(),
+ m_drainageNonWettingIntermediateCapPresTableName),
+ InputError);
+ TableFunction const &capPresTableNWI =
+ functionManager.getGroup(m_drainageNonWettingIntermediateCapPresTableName);
+ TableCapillaryPressureHelpers::validateCapillaryPressureTable(capPresTableNWI, getFullName(), true,
+ nonWettingPhaseMaxVolumeFraction,
+ drainageNonWettingPhaseMinVolumeFraction,
+ nonWettingMaxCapPres,
+ drainageWettingPhaseMaxVolumeFraction
+ );
+
+ m_phaseIntermediateMinVolFraction =
+ 1.0 - drainageWettingPhaseMaxVolumeFraction - drainageWettingPhaseMaxVolumeFraction;
+ }
+
+ if (!m_imbibitionWettingIntermediateCapPresTableName.empty()) {
+
+ GEOS_THROW_IF(!functionManager.hasGroup(m_imbibitionWettingIntermediateCapPresTableName),
+ GEOS_FMT("{}: the table function named {} could not be found",
+ getFullName(),
+ m_imbibitionWettingIntermediateCapPresTableName),
+ InputError);
+ TableFunction const
+ &capPresTableWI = functionManager.getGroup(
+ m_imbibitionWettingIntermediateCapPresTableName);
+ TableCapillaryPressureHelpers::validateCapillaryPressureTable(capPresTableWI, getFullName(), false,
+ imbibitionWettingPhaseMaxVolumeFraction,
+ wettingPhaseMinVolumeFraction,
+ imbibitionWettingMinCapPres,
+ wettingMaxCapPres
+ );
+
+
+ }
+
+ if (!m_imbibitionNonWettingIntermediateCapPresTableName.empty()) {
+
+ GEOS_THROW_IF(!functionManager.hasGroup(m_imbibitionNonWettingIntermediateCapPresTableName),
+ GEOS_FMT("{}: the table function named {} could not be found",
+ getFullName(),
+ m_imbibitionNonWettingIntermediateCapPresTableName),
+ InputError);
+ TableFunction const &capPresTableNWI =
+ functionManager.getGroup(m_imbibitionNonWettingIntermediateCapPresTableName);
+ TableCapillaryPressureHelpers::validateCapillaryPressureTable(capPresTableNWI, getFullName(), true,
+ nonWettingPhaseMaxVolumeFraction,
+ imbibitionNonWettingPhaseMinVolumeFraction,
+ nonWettingMaxCapPres,
+ imbibitionNonWettingMinCapPres);
+
+
+ }
+ }
+
+ // Step 2: check the sanity btw drainage and imbibition
+ auto const eps = 1e-15;
+ if (numPhases == 2) {
+ //TODO weak make stronger
+ GEOS_THROW_IF(
+ m_wettingCurve.isZero() && m_nonWettingCurve.isZero(),
+ GEOS_FMT(
+ "{}: Inconsistent data for capillary pressure hysteresis. No hysteresis curve is defined.",
+ getFullName()),
+ InputError);
+
+ GEOS_THROW_IF(
+ !m_wettingCurve.isZero() && !m_nonWettingCurve.isZero(),
+ GEOS_FMT(
+ "{}: Inconsistent data for capillary pressure hysteresis. Both non wetting and wetting hysteresis curve are defined in two phase flow setting.",
+ getFullName()),
+ InputError);
+
+
+ } else if (numPhases == 3) {
+
+ GEOS_THROW_IF(std::fabs(m_wettingCurve.oppositeBoundPhaseVolFraction - (1. - m_nonWettingCurve.oppositeBoundPhaseVolFraction - m_phaseIntermediateMinVolFraction)) > eps,
+ GEOS_FMT(
+ "{}: Inconsistent data for capillary pressure hysteresis. {}, {} and {} should sum up to 1.",
+ getFullName(), "Sw_min", "Snw_max", "Sinter_min"),
+ InputError);
+ GEOS_THROW_IF(std::fabs(m_wettingCurve.drainageExtremaPhaseVolFraction - (1. - m_nonWettingCurve.drainageExtremaPhaseVolFraction - m_phaseIntermediateMinVolFraction)) > eps,
+ GEOS_FMT(
+ "{}: Inconsistent data for capillary pressure hysteresis. {}, {} and {} should sum up to 1.",
+ getFullName(), "Sw_min", "Snw_max", "Sinter_min"),
+ InputError);
+ GEOS_THROW_IF(std::fabs(m_wettingCurve.imbibitionExtremaPhaseVolFraction - (1. - m_nonWettingCurve.imbibitionExtremaPhaseVolFraction - m_phaseIntermediateMinVolFraction)) > eps,
+ GEOS_FMT(
+ "{}: Inconsistent data for capillary pressure hysteresis. {}, {} and {} should sum up to 1.",
+ getFullName(), "Sw_min", "Snw_max", "Sinter_min"),
+ InputError);
+
+ }
+
+
+ // Step 3: compute the Land coefficient
+ computeLandCoefficient();
+
+
+
+ if (m_phaseMaxHistoricalVolFraction.size(1) == 0 && numPhases > 0) {
+ localIndex const currentSize = m_phaseMaxHistoricalVolFraction.size(0);
+ if (currentSize > 0) {
+ m_phaseMaxHistoricalVolFraction.resize(currentSize, numPhases);
+ m_phaseMinHistoricalVolFraction.resize(currentSize, numPhases);
+ m_phaseMode2PeakVolFraction.resize(currentSize, numPhases);
+ m_phaseMaxHistoricalVolFraction.setValues >(0.0);
+ m_phaseMinHistoricalVolFraction.setValues >(1.0);
+ m_phaseMode2PeakVolFraction.setValues >(0.0);
+ }
+ }
+ }
+
+/// Land coeff (tb refactored out in KilloughHysteresis) and saved cvgd
+
+ void TableCapillaryPressureHysteresis::computeLandCoefficient() {
+ // For now, we keep two separate Land parameters for the wetting and non-wetting phases
+ // For two-phase flow, we make sure that they are equal
+ m_landParam.resize(2);
+
+ // Note: for simplicity, the notations are taken from IX documentation (although this breaks our phaseVolFrac naming convention)
+
+ // Step 1: Land parameter for the wetting phase
+
+ integer ipWetting, ipNonWetting;
+ std::tie(ipWetting, ipNonWetting) = phaseIndex(m_phaseOrder);
+
+ KilloughHysteresis::computeLandCoefficient( m_wettingCurve, m_landParam[ipWetting] );
+ KilloughHysteresis::computeLandCoefficient( m_nonWettingCurve, m_landParam[ipNonWetting] );
+
+ }
+
+/// common utils
+ void TableCapillaryPressureHysteresis::resizeFields(localIndex const size, localIndex const numPts) {
+ CapillaryPressureBase::resizeFields(size, numPts);
+
+ integer const numPhases = numFluidPhases();
+
+
+
+ m_mode.resize(size);
+
+ if (numPhases > 0) {
+ m_phaseMaxHistoricalVolFraction.resize(size, numPhases);
+ m_phaseMinHistoricalVolFraction.resize(size, numPhases);
+ m_phaseMode2PeakVolFraction.resize(size, numPhases);
+ m_phaseMaxHistoricalVolFraction.setValues >(0.0);
+ m_phaseMinHistoricalVolFraction.setValues >(1.0);
+ m_phaseMode2PeakVolFraction.setValues >(0.0);
+ }
+
+
+ }
+
+ void TableCapillaryPressureHysteresis::saveConvergedPhaseVolFractionState(
+ arrayView2d const &phaseVolFraction) const {
+ CapillaryPressureBase::saveConvergedState();
+
+ arrayView2d phaseMaxHistoricalVolFraction = m_phaseMaxHistoricalVolFraction.toView();
+ arrayView2d phaseMinHistoricalVolFraction = m_phaseMinHistoricalVolFraction.toView();
+ arrayView2d phaseMode2PeakVolFraction = m_phaseMode2PeakVolFraction.toView();
+ arrayView1d mode_int = m_mode.toView();
+
+ localIndex const numElems = phaseVolFraction.size(0);
+ integer const numPhases = numFluidPhases();
+
+
+
+ using PT = CapillaryPressureBase::PhaseType;
+ arrayView1d const phaseOrderView = phaseOrder();
+ integer const ipWater = phaseOrderView[PT::WATER];
+ integer const ipOil = phaseOrderView[PT::OIL];
+ integer const ipGas = phaseOrderView[PT::GAS];
+
+ integer ipWetting = -1;
+ if (ipWater >= 0 && ipOil >= 0 && ipGas >= 0) {
+ ipWetting = ipWater;
+ } else if (ipWater < 0) {
+ ipWetting = ipOil;
+ } else if (ipOil < 0) {
+ ipWetting = ipWater;
+ } else if (ipGas < 0) {
+ ipWetting = ipWater;
+ }
+
+ forAll >(numElems, [=] GEOS_HOST_DEVICE(localIndex const ei) {
+ for (integer ip = 0; ip < numPhases; ++ip) {
+ phaseMaxHistoricalVolFraction[ei][ip] = LvArray::math::max(phaseVolFraction[ei][ip],
+ phaseMaxHistoricalVolFraction[ei][ip]);
+ phaseMinHistoricalVolFraction[ei][ip] = LvArray::math::min(phaseVolFraction[ei][ip],
+ phaseMinHistoricalVolFraction[ei][ip]);
+ }
+
+ if (ipWetting >= 0 && ipWetting < numPhases) {
+ ModeIndexType const currentMode = static_cast(mode_int[ei]);
+ if (currentMode == ModeIndexType::DRAINAGE_TO_IMBIBITION) {
+ real64 const currentS = phaseVolFraction[ei][ipWetting];
+ if (currentS > phaseMode2PeakVolFraction[ei][ipWetting]) {
+ phaseMode2PeakVolFraction[ei][ipWetting] = currentS;
+ }
+ } else if (currentMode != ModeIndexType::IMBIBITION_TO_DRAINAGE_FROM_SCANNING) {
+ phaseMode2PeakVolFraction[ei][ipWetting] = 0.0;
+ }
+ }
+ });
+
+ }
+
+ void
+ TableCapillaryPressureHysteresis::KernelWrapper::computeImbibitionWettingCapillaryPressure(
+ const arrayView1d &wettingKernelWapper,
+ const KilloughHysteresis::HysteresisCurve &wettingCurve,
+ const KilloughHysteresis::HysteresisCurve &nonWettingCurve, //discard if not needed
+ const geos::real64 &landParam,
+ const geos::real64 &phaseVolFraction,
+ const geos::real64 &phaseMinHistoricalVolFraction,
+ const geos::real64 &phaseMaxHistoricalVolFraction,
+ const geos::real64 &phaseMode2PeakVolFraction,
+ geos::real64 &phaseTrappedVolFrac,
+ geos::real64 &phaseCapPressure,
+ geos::real64 &dPhaseCapPressure_dPhaseVolFrac,
+ const ModeIndexType &mode) const {
+ GEOS_ASSERT(wettingCurve.isWetting());
+ real64 const S = phaseVolFraction;
+ real64 const Smxi = wettingCurve.imbibitionExtremaPhaseVolFraction;
+ real64 const Smxd = wettingCurve.drainageExtremaPhaseVolFraction;
+ real64 const Smin = wettingCurve.oppositeBoundPhaseVolFraction;
+ real64 const Smax = wettingCurve.drainageExtremaPhaseVolFraction;
+
+ GEOS_UNUSED_VAR( Smxi, Smxd, phaseTrappedVolFrac );
+
+// if( S <= Smin )
+// {
+// //below accessible range
+// phaseCapPressure = CAP_INF;
+// dPhaseCapPressure_dPhaseVolFrac = -CAP_INF_DERIV;
+// }
+// else if( S >= Smxd )
+// {
+// //above accessible range
+// phaseCapPressure = -CAP_INF;
+// dPhaseCapPressure_dPhaseVolFrac = -CAP_INF_DERIV;
+// }
+// else
+ {
+ //drainage to imbibition
+ real64 dpci_dS, dpcd_dS;
+ real64 const pci = wettingKernelWapper[ModeIndexType::IMBIBITION].compute(&S, &dpci_dS);
+ real64 const pcd = wettingKernelWapper[ModeIndexType::DRAINAGE].compute(&S, &dpcd_dS);
+ real64 const Somin = m_phaseIntermediateMinVolFraction;
+
+ real64 const E = m_killoughCurvatureParamCapPres;
+
+ //Step 2. compute F as in (EQ 34.15) F = (1/(Sw-Shy+E)-1/E) / (1/(Swma-Shy+E)-1/E)
+ //drainage to imbibition branch
+ if (mode == ModeIndexType::DRAINAGE_TO_IMBIBITION) {
+ // DRAINAGE_TO_IMBIBITION: Shy should be minimum historical (where drainage started)
+ real64 const Shy = (phaseMinHistoricalVolFraction > Smin) ? phaseMinHistoricalVolFraction : Smin;
+
+ real64 Scrt = 0.0;
+ KilloughHysteresis::computeTrappedCriticalPhaseVolFraction(nonWettingCurve,
+ Shy,
+ landParam,
+ m_jerauldParam_a,
+ m_jerauldParam_b,
+ Scrt);
+ real64 const Swma = 1 - Scrt - Somin;
+ real64 F = (1. / (S - Shy + E) - 1. / E) / (1. / (Swma - Shy + E) - 1. / E);
+ //force bound
+ F = LvArray::math::max(F, 0.0);
+ F = LvArray::math::min(F, 1.0);
+
+ //Step 3. Eventually assemble everything following (EQ. 34.14)
+ phaseCapPressure = pcd + F * (pci - pcd);
+ dPhaseCapPressure_dPhaseVolFrac = dpcd_dS + F * (dpci_dS - dpcd_dS);
+ }
+ //imbibition to drainage
+ else if (mode == ModeIndexType::IMBIBITION_TO_DRAINAGE) {
+ // IMBIBITION_TO_DRAINAGE: Shy should be maximum historical (where imbibition started)
+ real64 const Shy = (phaseMaxHistoricalVolFraction < Smax) ? phaseMaxHistoricalVolFraction : Smax;
+
+ // For IMBIBITION_TO_DRAINAGE, use the same formula structure as non-wetting phase
+ // F = (1. / (S - Shy + E) - 1. / E) / (1. / (Swmin - Shy + E) - 1. / E)
+ // This ensures F = 0 when S = Shy (high saturation) and F = 1 when S = Swmin (low saturation)
+ // Minimum accessible wetting saturation = Somin (irreducible wetting saturation)
+ // Use actual Somin (may be 0.0), the formula will handle negative values correctly
+ real64 const Swmin = Somin;
+
+ real64 const F_num = (1. / (S - Shy + E) - 1. / E);
+ real64 const F_denom = (1. / (Swmin - Shy + E) - 1. / E);
+ // Both numerator and denominator can be negative when Swmin < Shy - E
+ // This is okay - negative/negative = positive F
+ real64 F = F_num / F_denom;
+ //force bound
+ F = LvArray::math::max(F, 0.0);
+ F = LvArray::math::min(F, 1.0);
+
+ //Step 3. Eventually assemble everything following (EQ. 34.14)
+ phaseCapPressure = pci + F * (pcd - pci);
+ dPhaseCapPressure_dPhaseVolFrac = dpci_dS + F * (dpcd_dS - dpci_dS);
+ }
+ //imbibition to drainage from scanning curve (departing from DRAINAGE_TO_IMBIBITION)
+ else if (mode == ModeIndexType::IMBIBITION_TO_DRAINAGE_FROM_SCANNING) {
+ // IMBIBITION_TO_DRAINAGE_FROM_SCANNING: Secondary drainage scanning curve
+ // Based on Killough (1976) - solve quadratic for ghost departure point
+ //
+ // Nomenclature:
+ // - Sw_star (S_star): second reversal saturation (turnaround point, peak reached during Mode 2)
+ // - Sw_Hyst_imb (H): first reversal point (where imbibition started, departure from drainage)
+ // - Sw_wr (Sw_wr): residual/connate wetting saturation
+ // - x (Sw_star_Hyst_ghost): ghost departure point on imbibition curve (unknown, to be solved)
+ //
+ // Step 1: Identify reversal points
+ // S_star is the turnaround point (peak reached during Mode 2)
+ real64 S_star = phaseMode2PeakVolFraction;
+ if (S_star < 1e-12 || S_star >= Smax - 1e-12) {
+ // Use phaseMaxHistoricalVolFraction, but ensure it's between H and Smax
+ real64 const H_temp = (phaseMinHistoricalVolFraction > Smin) ? phaseMinHistoricalVolFraction : Smin;
+ if (phaseMaxHistoricalVolFraction > H_temp + 1e-12 && phaseMaxHistoricalVolFraction < Smax - 1e-12) {
+ S_star = phaseMaxHistoricalVolFraction;
+ } else {
+ S_star = LvArray::math::max(H_temp + 0.1, LvArray::math::min(S, Smax - 0.01));
+ }
+ }
+
+ real64 const H = (phaseMinHistoricalVolFraction > Smin) ? phaseMinHistoricalVolFraction : Smin;
+ // Sw_wr: residual/connate wetting saturation - minimum saturation on drainage curve
+ // For wetting phase, oppositeBoundPhaseVolFraction should be the minimum, but if it's 0,
+ // we need to get the actual minimum from the drainage curve table
+ // Since we can't easily access table coordinates from KernelWrapper, use Smin if > 0, otherwise try Somin
+ real64 Sw_wr = Smin;
+ if (Sw_wr < 1e-12) {
+ Sw_wr = (Somin > 1e-12) ? Somin : 1e-6; // Small default to avoid division issues
+ }
+
+ // Step 2: Compute F_known from Mode 2 (imbibition scanning curve) at S_star
+ // Mode 2 formula: F = (1 / (S - H + E) - 1 / E) / (1 / (Swma - H + E) - 1 / E)
+ real64 Scrt = 0.0;
+ KilloughHysteresis::computeTrappedCriticalPhaseVolFraction(nonWettingCurve,
+ H,
+ landParam,
+ m_jerauldParam_a,
+ m_jerauldParam_b,
+ Scrt);
+ real64 const Swma = 1 - Scrt - Somin;
+ real64 F_known = 0.0;
+ if (S_star <= Swma + 1e-12) {
+ real64 const F_num = (1. / (S_star - H + E) - 1. / E);
+ real64 const F_denom = (1. / (Swma - H + E) - 1. / E);
+ if (LvArray::math::abs(F_denom) > 1e-12) {
+ F_known = F_num / F_denom;
+ } else {
+ F_known = (S_star > H) ? 1.0 : 0.0;
+ }
+ } else {
+ // S_star > Swma: beyond Mode 2 range, use F_known = 0 (pure imbibition)
+ F_known = 0.0;
+ }
+ F_known = LvArray::math::max(0.0, LvArray::math::min(1.0, F_known));
+
+ // Step 3: Compute F_star_target = 1 - F_known (from Eq. A-6)
+ real64 const F_star_target = 1.0 - F_known;
+
+ // Step 4: Solve quadratic for x = Sw_star_Hyst_ghost
+ // F* = [1/(x - S_star + E) - 1/E] / [1/(x - H + E) - 1/E] = F_star_target
+ // Note: Denominator uses H (first reversal point), not Sw_wr, so curve rejoins drainage at H
+ // Rearranging gives: (1-T)*x^2 + B*x + C = 0
+ // where T = F_star_target, a = S_star, b = H, e = E
+ real64 const a = S_star;
+ real64 const b = H; // Use H instead of Sw_wr so curve rejoins drainage at H
+ real64 const e = E;
+ real64 const T = F_star_target;
+
+ real64 const coeff_A = (1.0 - T);
+ real64 const coeff_B = -(1.0 - T) * (a + b - 2.0 * e) - (1.0 - T) * e;
+ real64 const coeff_C = (1.0 - T) * (a - e) * (b - e)
+ - e * e * (1.0 + T)
+ - e * (T * a - b);
+
+ real64 x = S_star;
+ real64 discriminant = coeff_B * coeff_B - 4.0 * coeff_A * coeff_C;
+
+ if (discriminant >= 0.0 && LvArray::math::abs(coeff_A) > 1e-14) {
+ real64 sqrt_disc = LvArray::math::sqrt(discriminant);
+ real64 x1 = (-coeff_B + sqrt_disc) / (2.0 * coeff_A);
+ real64 x2 = (-coeff_B - sqrt_disc) / (2.0 * coeff_A);
+
+ // Pick the physically meaningful root: x must be > S_star
+ if (x1 > S_star - 1e-8 && x2 > S_star - 1e-8) {
+ x = LvArray::math::min(x1, x2); // Take the closer one
+ } else if (x1 > S_star - 1e-8) {
+ x = x1;
+ } else if (x2 > S_star - 1e-8) {
+ x = x2;
+ }
+ }
+
+ // Step 5: Compute F_star for current saturation S
+ // F_star = [1/(x - S + E) - 1/E] / [1/(x - H + E) - 1/E]
+ // When S = H, F_star = 1.0 (pure drainage), so curve rejoins drainage at H
+ real64 F_star = 1.0;
+ real64 F_star_num = (1. / (x - S + E) - 1. / E);
+ real64 F_star_denom = (1. / (x - H + E) - 1. / E);
+ if (LvArray::math::abs(F_star_denom) > 1e-12) {
+ F_star = F_star_num / F_star_denom;
+ F_star = LvArray::math::max(0.0, LvArray::math::min(1.0, F_star));
+ }
+
+ // Step 6: Compute Pc using Eq. A-5: P_c = P_c^Im + F* [P_c^Dr - P_c^Im]
+ phaseCapPressure = pci + F_star * (pcd - pci);
+ dPhaseCapPressure_dPhaseVolFrac = dpci_dS + F_star * (dpcd_dS - dpci_dS);
+ } else {
+ GEOS_THROW(GEOS_FMT("{}: State is {}.Shouldnt be used in pure DRAINAGE or IMBIBITION.",
+ "TableCapillaryPressureHysteresis",
+ (mode == ModeIndexType::DRAINAGE) ? "DRAINAGE" : ((mode ==
+ ModeIndexType::IMBIBITION)
+ ? "IMBIBITION"
+ : "UNKNOWN")),
+ InputError);
+ }
+
+
+ }
+
+ }
+
+ void
+ TableCapillaryPressureHysteresis::KernelWrapper::computeImbibitionWettingCapillaryPressure(
+ const arrayView1d &wettingKernelWapper,
+ const KilloughHysteresis::HysteresisCurve &wettingCurve,
+ const geos::real64 &landParam,
+ const geos::real64 &phaseVolFraction,
+ const geos::real64 &phaseMinHistoricalVolFraction,
+ const geos::real64 &phaseMaxHistoricalVolFraction,
+ const geos::real64 &phaseMode2PeakVolFraction,
+ geos::real64 &phaseTrappedVolFrac,
+ geos::real64 &phaseCapPressure,
+ geos::real64 &dPhaseCapPressure_dPhaseVolFrac,
+ const ModeIndexType &mode) const {
+ GEOS_ASSERT(wettingCurve.isWetting());
+ real64 const S = phaseVolFraction;
+ real64 const Smxi = wettingCurve.imbibitionExtremaPhaseVolFraction;
+ real64 const Smxd = wettingCurve.drainageExtremaPhaseVolFraction;
+ real64 const Smin = wettingCurve.oppositeBoundPhaseVolFraction;
+ real64 const Smax = wettingCurve.drainageExtremaPhaseVolFraction;
+
+ GEOS_UNUSED_VAR( Smxi, Smxd, phaseTrappedVolFrac );
+
+// if( S <= Smin )
+// {
+// //below accessible range
+// phaseCapPressure = CAP_INF;
+// dPhaseCapPressure_dPhaseVolFrac = -CAP_INF_DERIV;
+// }
+// else if( S >= Smxd )
+// {
+// //above accessible range
+// phaseCapPressure = -CAP_INF;
+// dPhaseCapPressure_dPhaseVolFrac = -CAP_INF_DERIV;
+// }
+// else
+ {
+ //drainage to imbibition
+ real64 dpci_dS, dpcd_dS;
+ real64 const pci = wettingKernelWapper[ModeIndexType::IMBIBITION].compute(&S, &dpci_dS);
+ real64 const pcd = wettingKernelWapper[ModeIndexType::DRAINAGE].compute(&S, &dpcd_dS);
+
+ real64 const Somin = m_phaseIntermediateMinVolFraction;
+ real64 const E = m_killoughCurvatureParamCapPres;
+
+ //Step 2. compute F as in (EQ 34.15) F = (1/(Sw-Shy+E)-1/E) / (1/(Swma-Shy+E)-1/E)
+ //drainage to imbibition branch
+ if (mode == ModeIndexType::DRAINAGE_TO_IMBIBITION) {
+ // DRAINAGE_TO_IMBIBITION: Shy should be minimum historical (where drainage started)
+ real64 const Shy = (phaseMinHistoricalVolFraction > Smin) ? phaseMinHistoricalVolFraction : Smin;
+
+ real64 Scrt = 0.0;
+ KilloughHysteresis::computeTrappedCriticalPhaseVolFraction(wettingCurve,
+ Shy,
+ landParam,
+ m_jerauldParam_a,
+ m_jerauldParam_b,
+ Scrt);
+
+
+
+ //should be the pore space accessible to the two wetting phase
+ real64 const Swma = 1 - (1 - Scrt);
+ real64 F = (1. / (S - Shy + E) - 1. / E) / (1. / (Swma - Shy + E) - 1. / E);
+ //force bound
+ F = LvArray::math::max(F, 0.0);
+ F = LvArray::math::min(F, 1.0);
+
+ //Step 3. Eventually assemble everything following (EQ. 34.14)
+ phaseCapPressure = pcd + F * (pci - pcd);
+ dPhaseCapPressure_dPhaseVolFrac = dpcd_dS + F * (dpci_dS - dpcd_dS);
+ }
+ //imbibition to drainage
+ else if (mode == ModeIndexType::IMBIBITION_TO_DRAINAGE) {
+ // IMBIBITION_TO_DRAINAGE: Shy should be maximum historical (where imbibition started)
+ real64 const Shy = (phaseMaxHistoricalVolFraction < Smax) ? phaseMaxHistoricalVolFraction : Smax;
+
+ // For IMBIBITION_TO_DRAINAGE, use the same formula structure as non-wetting phase
+ // F = (1. / (S - Shy + E) - 1. / E) / (1. / (Swmin - Shy + E) - 1. / E)
+ // This ensures F = 0 when S = Shy (high saturation) and F = 1 when S = Swmin (low saturation)
+ // For two-phase, use Smin as the minimum accessible wetting saturation
+ // Use actual Smin (may be 0.0), the formula will handle negative values correctly
+ real64 const Swmin = Smin;
+
+ real64 const F_num = (1. / (S - Shy + E) - 1. / E);
+ real64 const F_denom = (1. / (Swmin - Shy + E) - 1. / E);
+ // Both numerator and denominator can be negative when Swmin < Shy - E
+ // This is okay - negative/negative = positive F
+ real64 F = F_num / F_denom;
+ //force bound
+ F = LvArray::math::max(F, 0.0);
+ F = LvArray::math::min(F, 1.0);
+
+ //Step 3. Eventually assemble everything following (EQ. 34.14)
+ phaseCapPressure = pci + F * (pcd - pci);
+ dPhaseCapPressure_dPhaseVolFrac = dpci_dS + F * (dpcd_dS - dpci_dS);
+ }
+ //imbibition to drainage from scanning curve (departing from DRAINAGE_TO_IMBIBITION)
+ else if (mode == ModeIndexType::IMBIBITION_TO_DRAINAGE_FROM_SCANNING) {
+ // IMBIBITION_TO_DRAINAGE_FROM_SCANNING: Secondary drainage scanning curve
+ // Based on Killough (1976) - solve quadratic for ghost departure point
+ //
+ // Nomenclature:
+ // - Sw_star (S_star): second reversal saturation (turnaround point, peak reached during Mode 2)
+ // - Sw_Hyst_imb (H): first reversal point (where imbibition started, departure from drainage)
+ // - Sw_wr (Sw_wr): residual/connate wetting saturation
+ // - x (Sw_star_Hyst_ghost): ghost departure point on imbibition curve (unknown, to be solved)
+ //
+ // Step 1: Identify reversal points
+ // S_star is the turnaround point (peak reached during Mode 2)
+ real64 S_star = phaseMode2PeakVolFraction;
+ if (S_star < 1e-12 || S_star >= Smax - 1e-12) {
+ // Use phaseMaxHistoricalVolFraction, but ensure it's between H and Smax
+ real64 const H_temp = (phaseMinHistoricalVolFraction > Smin) ? phaseMinHistoricalVolFraction : Smin;
+ if (phaseMaxHistoricalVolFraction > H_temp + 1e-12 && phaseMaxHistoricalVolFraction < Smax - 1e-12) {
+ S_star = phaseMaxHistoricalVolFraction;
+ } else {
+ S_star = LvArray::math::max(H_temp + 0.1, LvArray::math::min(S, Smax - 0.01));
+ }
+ }
+
+ real64 const H = (phaseMinHistoricalVolFraction > Smin) ? phaseMinHistoricalVolFraction : Smin;
+ // Sw_wr: residual/connate wetting saturation - minimum saturation on drainage curve
+ // For wetting phase, oppositeBoundPhaseVolFraction should be the minimum, but if it's 0,
+ // we need to get the actual minimum from the drainage curve table
+ // Since we can't easily access table coordinates from KernelWrapper, use Smin if > 0, otherwise try Somin
+ real64 Sw_wr = Smin;
+ if (Sw_wr < 1e-12) {
+ Sw_wr = (Somin > 1e-12) ? Somin : 1e-6; // Small default to avoid division issues
+ }
+
+ // Step 2: Compute F_known from Mode 2 (imbibition scanning curve) at S_star
+ // Mode 2 formula: F = (1 / (S - H + E) - 1 / E) / (1 / (Swma - H + E) - 1 / E)
+ real64 Scrt = 0.0;
+ KilloughHysteresis::computeTrappedCriticalPhaseVolFraction(wettingCurve,
+ H,
+ landParam,
+ m_jerauldParam_a,
+ m_jerauldParam_b,
+ Scrt);
+ real64 const Swma = 1 - (1 - Scrt); // For two-phase, following existing Mode 2 pattern
+ real64 F_known = 0.0;
+ if (S_star <= Swma + 1e-12) {
+ real64 const F_num = (1. / (S_star - H + E) - 1. / E);
+ real64 const F_denom = (1. / (Swma - H + E) - 1. / E);
+ if (LvArray::math::abs(F_denom) > 1e-12) {
+ F_known = F_num / F_denom;
+ } else {
+ F_known = (S_star > H) ? 1.0 : 0.0;
+ }
+ } else {
+ // S_star > Swma: beyond Mode 2 range, use F_known = 0 (pure imbibition)
+ F_known = 0.0;
+ }
+ F_known = LvArray::math::max(0.0, LvArray::math::min(1.0, F_known));
+
+ // Step 3: Compute F_star_target = 1 - F_known (from Eq. A-6)
+ real64 const F_star_target = 1.0 - F_known;
+
+ // Step 4: Solve quadratic for x = Sw_star_Hyst_ghost
+ // F* = [1/(x - S_star + E) - 1/E] / [1/(x - H + E) - 1/E] = F_star_target
+ // Note: Denominator uses H (first reversal point), not Sw_wr, so curve rejoins drainage at H
+ // Rearranging gives: (1-T)*x^2 + B*x + C = 0
+ // where T = F_star_target, a = S_star, b = H, e = E
+ real64 const a = S_star;
+ real64 const b = H; // Use H instead of Sw_wr so curve rejoins drainage at H
+ real64 const e = E;
+ real64 const T = F_star_target;
+
+ real64 const coeff_A = (1.0 - T);
+ real64 const coeff_B = -(1.0 - T) * (a + b - 2.0 * e) - (1.0 - T) * e;
+ real64 const coeff_C = (1.0 - T) * (a - e) * (b - e)
+ - e * e * (1.0 + T)
+ - e * (T * a - b);
+
+ real64 x = S_star;
+ real64 discriminant = coeff_B * coeff_B - 4.0 * coeff_A * coeff_C;
+
+ if (discriminant >= 0.0 && LvArray::math::abs(coeff_A) > 1e-14) {
+ real64 sqrt_disc = LvArray::math::sqrt(discriminant);
+ real64 x1 = (-coeff_B + sqrt_disc) / (2.0 * coeff_A);
+ real64 x2 = (-coeff_B - sqrt_disc) / (2.0 * coeff_A);
+
+ // Pick the physically meaningful root: x must be > S_star
+ if (x1 > S_star - 1e-8 && x2 > S_star - 1e-8) {
+ x = LvArray::math::min(x1, x2); // Take the closer one
+ } else if (x1 > S_star - 1e-8) {
+ x = x1;
+ } else if (x2 > S_star - 1e-8) {
+ x = x2;
+ }
+ }
+
+ // Step 5: Compute F_star for current saturation S
+ // F_star = [1/(x - S + E) - 1/E] / [1/(x - H + E) - 1/E]
+ // When S = H, F_star = 1.0 (pure drainage), so curve rejoins drainage at H
+ real64 F_star = 1.0;
+ real64 F_star_num = (1. / (x - S + E) - 1. / E);
+ real64 F_star_denom = (1. / (x - H + E) - 1. / E);
+ if (LvArray::math::abs(F_star_denom) > 1e-12) {
+ F_star = F_star_num / F_star_denom;
+ F_star = LvArray::math::max(0.0, LvArray::math::min(1.0, F_star));
+ }
+
+ // Step 6: Compute Pc using Eq. A-5: P_c = P_c^Im + F* [P_c^Dr - P_c^Im]
+ phaseCapPressure = pci + F_star * (pcd - pci);
+ dPhaseCapPressure_dPhaseVolFrac = dpci_dS + F_star * (dpcd_dS - dpci_dS);
+ } else {
+ GEOS_THROW(GEOS_FMT("{}: State is {}.Shouldnt be used in pure DRAINAGE or IMBIBITION.",
+ "TableCapillaryPressureHysteresis",
+ (mode == ModeIndexType::DRAINAGE) ? "DRAINAGE" : ((mode ==
+ ModeIndexType::IMBIBITION)
+ ? "IMBIBITION"
+ : "UNKNOWN")),
+ InputError);
+ }
+
+
+ }
+
+ }
+ void TableCapillaryPressureHysteresis::KernelWrapper::computeTwoPhaseWetting(const geos::integer ipWetting,
+ const geos::integer GEOS_UNUSED_PARAM( ipNonWetting ),
+ const arraySlice1d &phaseVolFraction,
+ const arraySlice1d &phaseMaxHistoricalVolFraction,
+ const arraySlice1d &phaseMinHistoricalVolFraction,
+ const arraySlice1d &phaseTrappedVolFrac,
+ arraySlice1d const &phaseCapPressure,
+ arraySlice2d const &dPhaseCapPressure_dPhaseVolFrac,
+ ModeIndexType &mode,
+ arraySlice1d &phaseMode2PeakVolFraction) const {
+ using TTP = ThreePhasePairPhaseType;
+
+ // Validate array sizes and indices before accessing
+ GEOS_ASSERT_MSG(ipWetting >= 0, "ipWetting must be non-negative");
+ GEOS_ASSERT_MSG(static_cast(phaseVolFraction.size()) > ipWetting,
+ GEOS_FMT("phaseVolFraction array too small: size={}, ipWetting={}. "
+ "This usually means the arrays haven't been properly resized. "
+ "Ensure resizeFields() has been called before using the KernelWrapper.",
+ phaseVolFraction.size(), ipWetting));
+ GEOS_ASSERT_MSG(static_cast(phaseMaxHistoricalVolFraction.size()) > ipWetting,
+ GEOS_FMT("phaseMaxHistoricalVolFraction array too small: size={}, ipWetting={}. "
+ "This usually means the arrays haven't been properly resized. "
+ "Ensure resizeFields() has been called before using the KernelWrapper.",
+ phaseMaxHistoricalVolFraction.size(), ipWetting));
+ GEOS_ASSERT_MSG(static_cast(phaseMinHistoricalVolFraction.size()) > ipWetting,
+ GEOS_FMT("phaseMinHistoricalVolFraction array too small: size={}, ipWetting={}. "
+ "This usually means the arrays haven't been properly resized. "
+ "Ensure resizeFields() has been called before using the KernelWrapper.",
+ phaseMinHistoricalVolFraction.size(), ipWetting));
+ GEOS_ASSERT_MSG(static_cast(m_wettingNonWettingCapillaryPressureKernelWrappers.size()) >= 2,
+ GEOS_FMT("m_wettingNonWettingCapillaryPressureKernelWrappers must have at least 2 elements, but got {}. "
+ "This usually means createAllTableKernelWrappers() failed to populate the arrays.",
+ m_wettingNonWettingCapillaryPressureKernelWrappers.size()));
+
+ // TEMPORARY: Disable scanning curves for testing convergence
+ // Define at function scope so it's accessible to both wetting and non-wetting phase code
+ constexpr bool ENABLE_SCANNING_CURVES = true;
+
+ // TEMPORARY: Force IMBIBITION mode for testing (overrides normal mode determination)
+ constexpr bool FORCE_IMBIBITION_MODE = false;
+
+ // TEMPORARY: Enable/disable Mode 4 (IMBIBITION_TO_DRAINAGE_FROM_SCANNING)
+ // When false, the code stays in Mode 2 and does not transition to Mode 4
+ constexpr bool ENABLE_MODE4 = false;
+
+ // Determine mode based on saturation condition and flow direction
+ // Use DRAINAGE when saturation is at or below minimum historical
+ // Use scanning curves when above minimum, detecting direction of change
+ bool const useDrainage = FORCE_IMBIBITION_MODE ? false :
+ (!m_phaseHasHysteresis[TTP::INTERMEDIATE_WETTING] ||
+ phaseVolFraction[ipWetting] <= phaseMinHistoricalVolFraction[ipWetting] + flowReversalBuffer);
+
+ //--- wetting cap pressure -- W/O or W/G two phase flow
+ // Use drainage curve when S <= S_min
+ // Use scanning curves when S > S_min, but detect if we're in secondary drainage
+ // DEBUG: Print mode for capillary pressure
+ // printf("CapPressure: mode=%d, S_w=%.6e, S_min=%.6e, S_max=%.6e, hasHyst=%d\n",
+ // static_cast(mode),
+ // phaseVolFraction[ipWetting],
+ // phaseMinHistoricalVolFraction[ipWetting],
+ // phaseMaxHistoricalVolFraction[ipWetting],
+ // static_cast(m_phaseHasHysteresis[TTP::INTERMEDIATE_WETTING]));
+
+ if (useDrainage) {
+ // Use simple drainage curve (matching relative permeability)
+ mode = ModeIndexType::DRAINAGE;
+ phaseTrappedVolFrac[ipWetting] = LvArray::math::min(phaseVolFraction[ipWetting],
+ m_wettingCurve.oppositeBoundPhaseVolFraction);
+ // printf("CapPressure: Using DRAINAGE curve (arrayIndex=0)\n");
+ GEOS_ASSERT_MSG(static_cast(m_wettingNonWettingCapillaryPressureKernelWrappers.size()) > ModeIndexType::DRAINAGE,
+ "Invalid array index for kernel wrapper access");
+ computeBoundCapillaryPressure(
+ m_wettingNonWettingCapillaryPressureKernelWrappers[ModeIndexType::DRAINAGE],
+ phaseVolFraction[ipWetting],
+ phaseCapPressure[ipWetting],
+ dPhaseCapPressure_dPhaseVolFrac[ipWetting][ipWetting]);
+ }
+ else {
+ // Use scanning curves - original conservative approach: only switch from pure states to scanning curves
+ // Scanning curve modes persist once set (no switching between DRAINAGE_TO_IMBIBITION and IMBIBITION_TO_DRAINAGE)
+
+ // If mode is already set to pure IMBIBITION, use it directly (for Pc bounds computation)
+ if (mode == ModeIndexType::IMBIBITION) {
+ // Force pure imbibition mode - use the imbibition table directly
+ phaseTrappedVolFrac[ipWetting] = LvArray::math::min(phaseVolFraction[ipWetting],
+ m_wettingCurve.oppositeBoundPhaseVolFraction);
+ GEOS_ASSERT_MSG(static_cast(m_wettingNonWettingCapillaryPressureKernelWrappers.size()) > ModeIndexType::IMBIBITION,
+ "Invalid array index for kernel wrapper access");
+ computeBoundCapillaryPressure(
+ m_wettingNonWettingCapillaryPressureKernelWrappers[ModeIndexType::IMBIBITION],
+ phaseVolFraction[ipWetting],
+ phaseCapPressure[ipWetting],
+ dPhaseCapPressure_dPhaseVolFrac[ipWetting][ipWetting]);
+ return; // Exit early, don't use scanning curves
+ }
+
+ // If FORCE_IMBIBITION_MODE is enabled, override mode to IMBIBITION
+ if (FORCE_IMBIBITION_MODE) {
+ mode = ModeIndexType::IMBIBITION;
+ }
+
+ // If scanning curves are disabled, reset any scanning curve modes back to pure modes
+ if (!ENABLE_SCANNING_CURVES) {
+ if (mode == ModeIndexType::DRAINAGE_TO_IMBIBITION ||
+ mode == ModeIndexType::IMBIBITION_TO_DRAINAGE ||
+ mode == ModeIndexType::IMBIBITION_TO_DRAINAGE_FROM_SCANNING) {
+ // Reset to pure drainage or imbibition based on saturation
+ if (FORCE_IMBIBITION_MODE) {
+ mode = ModeIndexType::IMBIBITION;
+ } else {
+ real64 const Smin = m_wettingCurve.oppositeBoundPhaseVolFraction;
+ real64 const Smax = m_wettingCurve.drainageExtremaPhaseVolFraction;
+ real64 const Shy_min = (phaseMinHistoricalVolFraction[ipWetting] > Smin) ?
+ phaseMinHistoricalVolFraction[ipWetting] : Smin;
+ real64 const Shy_max = (phaseMaxHistoricalVolFraction[ipWetting] < Smax) ?
+ phaseMaxHistoricalVolFraction[ipWetting] : Smax;
+ real64 const currentS = phaseVolFraction[ipWetting];
+
+ if (currentS <= Shy_min + flowReversalBuffer) {
+ mode = ModeIndexType::DRAINAGE;
+ } else if (currentS >= Shy_max - flowReversalBuffer) {
+ mode = ModeIndexType::IMBIBITION;
+ } else {
+ // Default to drainage if in between
+ mode = ModeIndexType::DRAINAGE;
+ }
+ }
+ }
+ }
+
+ bool justSwitchedToMode2 = false;
+ if (ENABLE_SCANNING_CURVES && (mode == ModeIndexType::DRAINAGE || mode == ModeIndexType::IMBIBITION)) {
+ // Transitioning from a pure state to a scanning curve.
+ // The previous mode tells us the direction of the reversal:
+ // - From DRAINAGE: saturation is now increasing → DRAINAGE_TO_IMBIBITION (Mode 2)
+ // - From IMBIBITION: saturation is now decreasing → IMBIBITION_TO_DRAINAGE (Mode 3)
+ if (mode == ModeIndexType::DRAINAGE) {
+ mode = ModeIndexType::DRAINAGE_TO_IMBIBITION;
+ justSwitchedToMode2 = true;
+ } else {
+ mode = ModeIndexType::IMBIBITION_TO_DRAINAGE;
+ // Reset Mode 2 peak when leaving imbibition
+ if (phaseMode2PeakVolFraction[ipWetting] > 0.0) {
+ phaseMode2PeakVolFraction[ipWetting] = 0.0;
+ }
+ }
+ }
+ // Mode 2 peak is now tracked explicitly at end of timestep in saveConvergedPhaseVolFractionState()
+ // No need to update it during Newton iterations - use constant value from previous timestep
+ // This prevents oscillations during Newton solve from affecting the peak value
+ // Check for transition from DRAINAGE_TO_IMBIBITION to drainage (departing from scanning curve)
+ // Mode 4: Switch when saturation decreases from the peak reached during Mode 2
+ // In Mode 2, saturation increases from Shy_min (reversal point) towards Mode2_peak
+ // Once saturation starts decreasing from Mode2_peak by at least 1e-4, switch to Mode 4
+ // IMPORTANT: Only check for Mode 4 if we were already in Mode 2 (not just switched from Mode 0)
+ // This prevents immediate Mode 0 -> Mode 2 -> Mode 4 transition within a single Newton iteration
+ // TEMPORARY: Disable scanning curves for testing convergence
+ if (ENABLE_MODE4 && ENABLE_SCANNING_CURVES && mode == ModeIndexType::DRAINAGE_TO_IMBIBITION && !justSwitchedToMode2) {
+ real64 const currentS = phaseVolFraction[ipWetting];
+ real64 const Mode2_peak = phaseMode2PeakVolFraction[ipWetting];
+ // Use same approach as other mode switches: small buffer + mode persistence
+ // Similar to Mode 0 -> Mode 2: use flowReversalBuffer (1e-12) for consistency
+ // Mode 4 will persist once set, preventing oscillation-induced switching
+ // However, we need a slightly larger buffer than 1e-12 because:
+ // 1. The peak is a dynamic value that was just reached
+ // 2. Saturation can oscillate around the peak during Newton iterations
+ // Use a relative buffer: 0.01% of the peak value, with a minimum of 1e-4
+ // This is still much smaller than before but provides some protection against numerical noise
+ real64 const relativeBuffer = 0.0001 * Mode2_peak; // 0.01% of peak
+ real64 const absoluteBuffer = 1e-4; // Minimum absolute buffer (same as original)
+ real64 const decreaseBuffer = LvArray::math::max(relativeBuffer, absoluteBuffer);
+ // Valid range for Mode 4 switching: between min and max historical saturations
+ real64 const Smin = m_wettingCurve.oppositeBoundPhaseVolFraction;
+ real64 const Smax = m_wettingCurve.drainageExtremaPhaseVolFraction;
+ real64 const S_test_min = (phaseMinHistoricalVolFraction[ipWetting] > Smin) ?
+ phaseMinHistoricalVolFraction[ipWetting] : Smin;
+ real64 const S_test_max = (phaseMaxHistoricalVolFraction[ipWetting] < Smax) ?
+ phaseMaxHistoricalVolFraction[ipWetting] : Smax;
+
+ // Check: saturation must have decreased from the Mode 2 peak
+ // Only switch to Mode 4 if currentS < Mode2_peak - decreaseBuffer
+ // Also ensure Mode2_peak is valid (non-zero, meaning we've tracked a peak)
+ // And current saturation is within valid historical range
+ // Once Mode 4 is set, it persists (like Mode 2), preventing oscillation-induced switching
+ bool saturationHasDecreased = (Mode2_peak > 0.0 && currentS < Mode2_peak - decreaseBuffer);
+
+ if (saturationHasDecreased &&
+ currentS >= S_test_min &&
+ currentS <= S_test_max) {
+ mode = ModeIndexType::IMBIBITION_TO_DRAINAGE_FROM_SCANNING;
+ }
+ // Otherwise, keep Mode 2 (saturation still increasing or hasn't decreased enough from peak)
+ // Mode 2 persists once set, similar to how Mode 0 persists when S <= S_min + flowReversalBuffer
+ }
+ // IMBIBITION_TO_DRAINAGE_FROM_SCANNING mode persists once set
+
+ // If scanning curves are disabled, use pure drainage/imbibition curves
+ if (!ENABLE_SCANNING_CURVES &&
+ (mode == ModeIndexType::DRAINAGE || mode == ModeIndexType::IMBIBITION)) {
+ // Use pure drainage or imbibition curve
+ phaseTrappedVolFrac[ipWetting] = LvArray::math::min(phaseVolFraction[ipWetting],
+ m_wettingCurve.oppositeBoundPhaseVolFraction);
+ GEOS_ASSERT_MSG(static_cast(m_wettingNonWettingCapillaryPressureKernelWrappers.size()) > mode,
+ "Invalid array index for kernel wrapper access");
+ computeBoundCapillaryPressure(
+ m_wettingNonWettingCapillaryPressureKernelWrappers[mode],
+ phaseVolFraction[ipWetting],
+ phaseCapPressure[ipWetting],
+ dPhaseCapPressure_dPhaseVolFrac[ipWetting][ipWetting]);
+ } else {
+ // Use scanning curves
+ // printf("CapPressure: Using scanning curve (mode=%d)\n", static_cast(mode));
+ computeImbibitionWettingCapillaryPressure(m_wettingNonWettingCapillaryPressureKernelWrappers,
+ m_wettingCurve,
+ m_landParam[ipWetting],
+ phaseVolFraction[ipWetting],
+ phaseMinHistoricalVolFraction[ipWetting],
+ phaseMaxHistoricalVolFraction[ipWetting],
+ phaseMode2PeakVolFraction[ipWetting],
+ phaseTrappedVolFrac[ipWetting],
+ phaseCapPressure[ipWetting],
+ dPhaseCapPressure_dPhaseVolFrac[ipWetting][ipWetting],
+ mode);
+ }
+ }
+
+// trapped vol fraction
+ if (mode == ModeIndexType::DRAINAGE || mode == ModeIndexType::DRAINAGE_TO_IMBIBITION ||
+ mode == ModeIndexType::IMBIBITION_TO_DRAINAGE_FROM_SCANNING) {
+
+
+
+ real64 const Smin = m_wettingCurve.oppositeBoundPhaseVolFraction;
+ real64 const Shy = (phaseMinHistoricalVolFraction[ipWetting] < Smin)
+ ? phaseMinHistoricalVolFraction[ipWetting]
+ : Smin;
+ real64 Scrt = 0.0;
+ KilloughHysteresis::computeTrappedCriticalPhaseVolFraction(m_wettingCurve, Shy,
+ m_landParam[ipWetting],
+ m_jerauldParam_a,
+ m_jerauldParam_b,
+ Scrt);
+ phaseTrappedVolFrac[ipWetting] = LvArray::math::min(Scrt, phaseVolFraction[ipWetting]);
+
+
+ //keep the same Land coeff as two phase only
+ KilloughHysteresis::computeTrappedCriticalPhaseVolFraction(m_wettingCurve.toNonWetting(), Shy,
+ m_landParam[ipWetting],
+ m_jerauldParam_a,
+ m_jerauldParam_b,
+ Scrt);
+ phaseTrappedVolFrac[ipWetting] = LvArray::math::min(Scrt, phaseVolFraction[ipWetting]);
+
+
+
+
+ }
+ else if (mode == ModeIndexType::IMBIBITION || mode == ModeIndexType::IMBIBITION_TO_DRAINAGE) {
+
+ real64 const Smax = m_wettingCurve.imbibitionExtremaPhaseVolFraction;
+ real64 const Shy = (phaseMaxHistoricalVolFraction[ipWetting] < Smax)
+ ? phaseMaxHistoricalVolFraction[ipWetting]
+ : Smax;
+ real64 Scrt = 0.0;
+ //TODO (jacques) check if still accurate
+ KilloughHysteresis::computeTrappedCriticalPhaseVolFraction(m_wettingCurve,
+ Shy,
+ m_landParam[ipWetting],
+ m_jerauldParam_a,
+ m_jerauldParam_b,
+ Scrt);
+
+ phaseTrappedVolFrac[ipWetting] = LvArray::math::min(Scrt, phaseVolFraction[ipWetting]);
+
+ //keep the same Land coeff as two phase only
+ KilloughHysteresis::computeTrappedCriticalPhaseVolFraction(m_wettingCurve.toNonWetting(), Shy,
+ m_landParam[ipWetting],
+ m_jerauldParam_a,
+ m_jerauldParam_b,
+ Scrt);
+ phaseTrappedVolFrac[ipWetting] = LvArray::math::min(Scrt, phaseVolFraction[ipWetting]);
+
+ }
+
+ }
+
+ void TableCapillaryPressureHysteresis::KernelWrapper::computeTwoPhaseNonWetting(const geos::integer ipWetting,
+ const geos::integer ipNonWetting,
+ const arraySlice1d &phaseVolFraction,
+ const arraySlice1d &phaseMaxHistoricalVolFraction,
+ const arraySlice1d &phaseMinHistoricalVolFraction,
+ const arraySlice1d &phaseTrappedVolFrac,
+ arraySlice1d const &phaseCapPressure,
+ arraySlice2d const &dPhaseCapPressure_dPhaseVolFrac,
+ ModeIndexType &mode) const {
+ using TTP = ThreePhasePairPhaseType;
+
+ // TEMPORARY: Disable scanning curves for testing convergence
+ // Define at function scope so it's accessible throughout this function
+ constexpr bool ENABLE_SCANNING_CURVES = true;
+
+ // TEMPORARY: Force IMBIBITION mode for testing (overrides normal mode determination)
+ constexpr bool FORCE_IMBIBITION_MODE = false;
+
+ //update state
+ // TODO check if we can get rid of DRAINAGE_TO_IMBIBITION && IMBIBITION_TO_DRAINAGE
+ if (mode == ModeIndexType::DRAINAGE_TO_IMBIBITION &&
+ phaseVolFraction[ipNonWetting] >= phaseMaxHistoricalVolFraction[ipNonWetting] + flowReversalBuffer)
+ mode = ModeIndexType::DRAINAGE;
+ if (mode == ModeIndexType::IMBIBITION_TO_DRAINAGE &&
+ phaseVolFraction[ipWetting] <= phaseMinHistoricalVolFraction[ipNonWetting] + flowReversalBuffer)
+ mode = ModeIndexType::IMBIBITION;
+
+
+
+
+ // If scanning curves are disabled, reset any scanning curve modes back to pure modes
+ if (!ENABLE_SCANNING_CURVES) {
+ if (mode == ModeIndexType::DRAINAGE_TO_IMBIBITION ||
+ mode == ModeIndexType::IMBIBITION_TO_DRAINAGE ||
+ mode == ModeIndexType::IMBIBITION_TO_DRAINAGE_FROM_SCANNING) {
+ // Reset to pure drainage or imbibition based on saturation
+ if (FORCE_IMBIBITION_MODE) {
+ mode = ModeIndexType::IMBIBITION;
+ } else {
+ if (phaseVolFraction[ipNonWetting] <= phaseMinHistoricalVolFraction[ipNonWetting] + flowReversalBuffer) {
+ mode = ModeIndexType::DRAINAGE;
+ } else if (phaseVolFraction[ipNonWetting] >= phaseMaxHistoricalVolFraction[ipNonWetting] - flowReversalBuffer) {
+ mode = ModeIndexType::IMBIBITION;
+ } else {
+ // Default to drainage if in between
+ mode = ModeIndexType::DRAINAGE;
+ }
+ }
+ }
+ }
+
+ // Force IMBIBITION mode if flag is set (overrides all other logic)
+ if (FORCE_IMBIBITION_MODE) {
+ mode = ModeIndexType::IMBIBITION;
+ }
+
+ if (ENABLE_SCANNING_CURVES && (mode == ModeIndexType::DRAINAGE || mode == ModeIndexType::IMBIBITION)) {
+ // Handle transitions from pure states to scanning curves
+ // If current saturation is significantly above min historical, we're in imbibition
+ if (phaseVolFraction[ipNonWetting] > phaseMinHistoricalVolFraction[ipNonWetting] + flowReversalBuffer) {
+ if (mode == ModeIndexType::DRAINAGE) {
+ mode = ModeIndexType::DRAINAGE_TO_IMBIBITION;
+ }
+ }
+ // If current saturation is significantly below max historical, we're in drainage
+ else if (phaseVolFraction[ipNonWetting] < phaseMaxHistoricalVolFraction[ipNonWetting] - flowReversalBuffer) {
+ if (mode == ModeIndexType::IMBIBITION) {
+ mode = ModeIndexType::IMBIBITION_TO_DRAINAGE;
+ }
+ }
+ }
+ // Mode 4 switching is only implemented for wetting phase, not for non-wetting phase
+ // Scanning curve modes persist - no switching between DRAINAGE_TO_IMBIBITION and IMBIBITION_TO_DRAINAGE
+
+ // Use simple drainage/imbibition curves when:
+ // 1. No hysteresis enabled, OR
+ // 2. We're in pure DRAINAGE mode (use drainage curve), OR
+ // 3. We're in pure IMBIBITION mode (use imbibition curve)
+ // Use scanning curves only during transitions (DRAINAGE_TO_IMBIBITION or IMBIBITION_TO_DRAINAGE)
+ if (!m_phaseHasHysteresis[TTP::INTERMEDIATE_NONWETTING] ||
+ mode == ModeIndexType::DRAINAGE ||
+ mode == ModeIndexType::IMBIBITION) {
+ phaseTrappedVolFrac[ipNonWetting] = LvArray::math::min(phaseVolFraction[ipNonWetting],
+ (mode == ModeIndexType::DRAINAGE)
+ ? m_nonWettingCurve.drainageExtremaPhaseVolFraction
+ : m_nonWettingCurve.imbibitionExtremaPhaseVolFraction);
+ // Ensure mode is a valid array index (0 or 1)
+ integer const arrayIndex = (mode == ModeIndexType::DRAINAGE) ? ModeIndexType::DRAINAGE : ModeIndexType::IMBIBITION;
+ GEOS_ASSERT_MSG(arrayIndex >= 0 && arrayIndex < static_cast(m_wettingNonWettingCapillaryPressureKernelWrappers.size()),
+ "Invalid array index for kernel wrapper access");
+ computeBoundCapillaryPressure(
+ m_wettingNonWettingCapillaryPressureKernelWrappers[arrayIndex],
+ phaseVolFraction[ipNonWetting],
+ phaseCapPressure[ipNonWetting],
+ dPhaseCapPressure_dPhaseVolFrac[ipNonWetting][ipNonWetting]);
+ // when pc is on the gas phase, we need to multiply user input by -1
+ // because CompositionalMultiphaseFVM does: pres_gas = pres_oil - pc_og, so we need a negative pc_og
+ phaseCapPressure[ipNonWetting] *= -1;
+ dPhaseCapPressure_dPhaseVolFrac[ipNonWetting][ipNonWetting] *= -1;
+
+ } else {
+ // We're in a transition state (DRAINAGE_TO_IMBIBITION or IMBIBITION_TO_DRAINAGE)
+ // Use scanning curves (unless disabled)
+ if (!ENABLE_SCANNING_CURVES) {
+ // Scanning curves disabled - use pure drainage/imbibition based on current mode
+ // This should not happen if reset logic worked correctly, but handle it anyway
+ if (mode == ModeIndexType::DRAINAGE || mode == ModeIndexType::IMBIBITION) {
+ phaseTrappedVolFrac[ipNonWetting] = LvArray::math::min(phaseVolFraction[ipNonWetting],
+ (mode == ModeIndexType::DRAINAGE)
+ ? m_nonWettingCurve.drainageExtremaPhaseVolFraction
+ : m_nonWettingCurve.imbibitionExtremaPhaseVolFraction);
+ integer const arrayIndex = (mode == ModeIndexType::DRAINAGE) ? ModeIndexType::DRAINAGE : ModeIndexType::IMBIBITION;
+ GEOS_ASSERT_MSG(arrayIndex >= 0 && arrayIndex < static_cast(m_wettingNonWettingCapillaryPressureKernelWrappers.size()),
+ "Invalid array index for kernel wrapper access");
+ computeBoundCapillaryPressure(
+ m_wettingNonWettingCapillaryPressureKernelWrappers[arrayIndex],
+ phaseVolFraction[ipNonWetting],
+ phaseCapPressure[ipNonWetting],
+ dPhaseCapPressure_dPhaseVolFrac[ipNonWetting][ipNonWetting]);
+ phaseCapPressure[ipNonWetting] *= -1;
+ dPhaseCapPressure_dPhaseVolFrac[ipNonWetting][ipNonWetting] *= -1;
+ } else {
+ mode = ModeIndexType::DRAINAGE;
+ phaseTrappedVolFrac[ipNonWetting] = LvArray::math::min(phaseVolFraction[ipNonWetting],
+ m_nonWettingCurve.drainageExtremaPhaseVolFraction);
+ computeBoundCapillaryPressure(
+ m_wettingNonWettingCapillaryPressureKernelWrappers[ModeIndexType::DRAINAGE],
+ phaseVolFraction[ipNonWetting],
+ phaseCapPressure[ipNonWetting],
+ dPhaseCapPressure_dPhaseVolFrac[ipNonWetting][ipNonWetting]);
+ phaseCapPressure[ipNonWetting] *= -1;
+ dPhaseCapPressure_dPhaseVolFrac[ipNonWetting][ipNonWetting] *= -1;
+ }
+ } else {
+ // Use scanning curves
+
+ computeImbibitionNonWettingCapillaryPressure(m_wettingNonWettingCapillaryPressureKernelWrappers,
+ m_nonWettingCurve,
+ m_landParam[ipNonWetting],
+ phaseVolFraction[ipNonWetting],
+ phaseMaxHistoricalVolFraction[ipNonWetting],
+ phaseTrappedVolFrac[ipNonWetting],
+ phaseCapPressure[ipNonWetting],
+ dPhaseCapPressure_dPhaseVolFrac[ipNonWetting][ipNonWetting],
+ mode);
+
+ // when pc is on the gas phase, we need to multiply user input by -1
+ // because CompositionalMultiphaseFVM does: pres_gas = pres_oil - pc_og, so we need a negative pc_og
+ phaseCapPressure[ipNonWetting] *= -1;
+ dPhaseCapPressure_dPhaseVolFrac[ipNonWetting][ipNonWetting] *= -1;
+ }
+ }
+
+// trapped vol fraction
+ if (mode == ModeIndexType::DRAINAGE || mode == ModeIndexType::DRAINAGE_TO_IMBIBITION ||
+ mode == ModeIndexType::IMBIBITION_TO_DRAINAGE_FROM_SCANNING) {
+
+ {
+ real64 const Smax = m_nonWettingCurve.oppositeBoundPhaseVolFraction;
+ real64 const Shy = (phaseMaxHistoricalVolFraction[ipNonWetting] > Smax)
+ ? phaseMaxHistoricalVolFraction[ipNonWetting]
+ : Smax;
+ real64 Scrt = 0.0;
+ KilloughHysteresis::computeTrappedCriticalPhaseVolFraction(m_nonWettingCurve, Shy,
+ m_landParam[ipNonWetting],
+ m_jerauldParam_a, m_jerauldParam_b,
+ Scrt);
+ phaseTrappedVolFrac[ipNonWetting] = LvArray::math::min(Scrt, phaseVolFraction[ipNonWetting]);
+
+ //keep the same Land coeff as two phase only
+ KilloughHysteresis::computeTrappedCriticalPhaseVolFraction(m_nonWettingCurve.toWetting(), Shy,
+ m_landParam[ipNonWetting],
+ m_jerauldParam_a, m_jerauldParam_b,
+ Scrt);
+ phaseTrappedVolFrac[ipNonWetting] = LvArray::math::min(Scrt, phaseVolFraction[ipNonWetting]);
+ }
+ } else if (mode == ModeIndexType::IMBIBITION || mode == ModeIndexType::IMBIBITION_TO_DRAINAGE) {
+
+ {
+ real64 const Smin = m_nonWettingCurve.imbibitionExtremaPhaseVolFraction;;
+ real64 const Shy = (phaseMinHistoricalVolFraction[ipNonWetting] > Smin)
+ ? phaseMinHistoricalVolFraction[ipNonWetting]
+ : Smin;
+ real64 Scrt = 0.0;
+ KilloughHysteresis::computeTrappedCriticalPhaseVolFraction(m_nonWettingCurve, Shy,
+ m_landParam[ipNonWetting],
+ m_jerauldParam_a, m_jerauldParam_b,
+ Scrt);
+ phaseTrappedVolFrac[ipNonWetting] = LvArray::math::min(Scrt, phaseVolFraction[ipNonWetting]);
+
+ //keep the same Land coeff as two phase only
+ KilloughHysteresis::computeTrappedCriticalPhaseVolFraction(m_nonWettingCurve.toWetting(), Shy,
+ m_landParam[ipNonWetting],
+ m_jerauldParam_a, m_jerauldParam_b,
+ Scrt);
+ phaseTrappedVolFrac[ipNonWetting] = LvArray::math::min(Scrt, phaseVolFraction[ipNonWetting]);
+ }
+ }
+
+
+ }
+
+ void TableCapillaryPressureHysteresis::KernelWrapper::computeThreePhase(const geos::integer ipWetting,
+ const geos::integer GEOS_UNUSED_PARAM( ipInter ),
+ const geos::integer ipNonWetting,
+ const arraySlice1d &phaseVolFraction,
+ const arraySlice1d &phaseMaxHistoricalVolFraction,
+ const arraySlice1d &phaseMinHistoricalVolFraction,
+ const arraySlice1d &phaseTrappedVolFrac,
+ const arraySlice1d &phaseCapPressure,
+ const arraySlice2d &dPhaseCapPressure_dPhaseVolFrac,
+ ModeIndexType &mode,
+ arraySlice1d &phaseMode2PeakVolFraction) const {
+
+
+ LvArray::forValuesInSlice(dPhaseCapPressure_dPhaseVolFrac, [](real64 &val) { val = 0.0; });
+ using TTP = ThreePhasePairPhaseType;
+
+ // -- wetting curve if drainage only
+ constexpr bool ENABLE_COMPUTE_BRANCH_DEBUG = false;
+ bool usePureCurve = !m_phaseHasHysteresis[TTP::INTERMEDIATE_WETTING] ||
+ (mode == ModeIndexType::DRAINAGE &&
+ phaseVolFraction[ipWetting] <= phaseMinHistoricalVolFraction[ipWetting] + flowReversalBuffer) ||
+ (mode == ModeIndexType::IMBIBITION &&
+ phaseVolFraction[ipWetting] >= phaseMaxHistoricalVolFraction[ipWetting] + flowReversalBuffer);
+
+ if constexpr (ENABLE_COMPUTE_BRANCH_DEBUG) {
+ if (mode == ModeIndexType::DRAINAGE || mode == ModeIndexType::IMBIBITION) {
+ std::cout << "[COMPUTE_BRANCH_DEBUG] Two-phase wetting, mode=" << (mode == ModeIndexType::DRAINAGE ? "DRAINAGE" : "IMBIBITION")
+ << ", S=" << phaseVolFraction[ipWetting]
+ << ", S_min=" << phaseMinHistoricalVolFraction[ipWetting]
+ << ", S_max=" << phaseMaxHistoricalVolFraction[ipWetting]
+ << ", usePureCurve=" << (usePureCurve ? "true" : "false")
+ << ", hasHysteresis=" << m_phaseHasHysteresis[TTP::INTERMEDIATE_WETTING] << std::endl;
+ }
+ }
+
+ if (usePureCurve) {
+ // water-oil capillary pressure
+ phaseTrappedVolFrac[ipWetting] = LvArray::math::min(phaseVolFraction[ipWetting],
+ m_wettingCurve.oppositeBoundPhaseVolFraction);
+ constexpr bool ENABLE_COMPUTE_TABLE_DEBUG = false;
+ real64 S_before = phaseVolFraction[ipWetting];
+ real64 dPc_dS_before = dPhaseCapPressure_dPhaseVolFrac[ipWetting][ipWetting];
+ integer const tableIndex = static_cast(mode);
+ if constexpr (ENABLE_COMPUTE_TABLE_DEBUG) {
+ std::cout << "[COMPUTE_TABLE_DEBUG_TCH] Forward table lookup: mode=" << (mode == ModeIndexType::DRAINAGE ? "DRAINAGE" : "IMBIBITION")
+ << ", tableIndex=" << tableIndex
+ << ", S=" << S_before
+ << ", wrapper.size()=" << m_wettingIntermediateCapillaryPressureKernelWrappers.size() << std::endl;
+ }
+ phaseCapPressure[ipWetting] =
+ m_wettingIntermediateCapillaryPressureKernelWrappers[mode].compute(
+ &(phaseVolFraction)[ipWetting],
+ &(dPhaseCapPressure_dPhaseVolFrac)[ipWetting][ipWetting]);
+ real64 dPc_dS_after = dPhaseCapPressure_dPhaseVolFrac[ipWetting][ipWetting];
+ if constexpr (ENABLE_COMPUTE_TABLE_DEBUG) {
+ std::cout << "[COMPUTE_TABLE_DEBUG_TCH] After table.compute: Pc=" << phaseCapPressure[ipWetting]
+ << ", dPc_dS_before=" << dPc_dS_before
+ << ", dPc_dS_after=" << dPc_dS_after << std::endl;
+ }
+ } else {
+ // We're in a scanning curve state - preserve the mode if already set, otherwise determine from position
+ if (mode != ModeIndexType::DRAINAGE_TO_IMBIBITION &&
+ mode != ModeIndexType::IMBIBITION_TO_DRAINAGE) {
+ // Starting from a pure state - determine initial scanning curve direction
+ mode = (mode == ModeIndexType::DRAINAGE) ? ModeIndexType::DRAINAGE_TO_IMBIBITION
+ : ModeIndexType::IMBIBITION_TO_DRAINAGE;
+ }
+ computeImbibitionWettingCapillaryPressure(m_wettingIntermediateCapillaryPressureKernelWrappers,
+ m_wettingCurve,
+ m_nonWettingCurve,
+ m_landParam[ipWetting],
+ phaseVolFraction[ipWetting],
+ phaseMinHistoricalVolFraction[ipWetting],
+ phaseMaxHistoricalVolFraction[ipWetting],
+ phaseMode2PeakVolFraction[ipWetting],
+ phaseTrappedVolFrac[ipWetting],
+ phaseCapPressure[ipWetting],
+ dPhaseCapPressure_dPhaseVolFrac[ipWetting][ipWetting],
+ mode);
+
+
+ }
+
+
+ // -- non-wetting cure if drainage only
+ // gas-oil capillary pressure
+ if (!m_phaseHasHysteresis[TTP::INTERMEDIATE_NONWETTING] ||
+ (mode == ModeIndexType::DRAINAGE &&
+ phaseVolFraction[ipNonWetting] >= phaseMaxHistoricalVolFraction[ipNonWetting] + flowReversalBuffer) ||
+ (mode == ModeIndexType::IMBIBITION &&
+ phaseVolFraction[ipNonWetting] <= phaseMinHistoricalVolFraction[ipNonWetting] + flowReversalBuffer)) {
+ phaseTrappedVolFrac[ipNonWetting] = LvArray::math::min(phaseVolFraction[ipNonWetting],
+ (mode == ModeIndexType::DRAINAGE)
+ ? m_nonWettingCurve.drainageExtremaPhaseVolFraction
+ : m_nonWettingCurve.imbibitionExtremaPhaseVolFraction);
+ phaseCapPressure[ipNonWetting] =
+ m_nonWettingIntermediateCapillaryPressureKernelWrappers[mode].compute(
+ &(phaseVolFraction)[ipNonWetting],
+ &(dPhaseCapPressure_dPhaseVolFrac)[ipNonWetting][ipNonWetting]);
+
+
+ // when pc is on the gas phase, we need to multiply user input by -1
+ // because CompositionalMultiphaseFVM does: pres_gas = pres_oil - pc_og, so we need a negative pc_og
+ phaseCapPressure[ipNonWetting] *= -1;
+ dPhaseCapPressure_dPhaseVolFrac[ipNonWetting][ipNonWetting] *= -1;
+ } else {
+ // We're in a scanning curve state - preserve the mode if already set, otherwise determine from position
+ if (mode != ModeIndexType::DRAINAGE_TO_IMBIBITION &&
+ mode != ModeIndexType::IMBIBITION_TO_DRAINAGE) {
+ // Starting from a pure state - determine initial scanning curve direction
+ mode = (mode == ModeIndexType::DRAINAGE) ? ModeIndexType::DRAINAGE_TO_IMBIBITION
+ : ModeIndexType::IMBIBITION_TO_DRAINAGE;
+ }
+
+ computeImbibitionNonWettingCapillaryPressure(m_nonWettingIntermediateCapillaryPressureKernelWrappers,
+ m_nonWettingCurve,
+ m_wettingCurve,
+ m_landParam[ipNonWetting],
+ phaseVolFraction[ipNonWetting],
+ phaseMinHistoricalVolFraction[ipNonWetting],
+ phaseTrappedVolFrac[ipNonWetting],
+ phaseCapPressure[ipNonWetting],
+ dPhaseCapPressure_dPhaseVolFrac[ipNonWetting][ipNonWetting],
+ mode);
+ // when pc is on the gas phase, we need to multiply user input by -1
+ // because CompositionalMultiphaseFVM does: pres_gas = pres_oil - pc_og, so we need a negative pc_og
+ phaseCapPressure[ipNonWetting] *= -1;
+ dPhaseCapPressure_dPhaseVolFrac[ipNonWetting][ipNonWetting] *= -1;
+
+ //update trapped fraction
+ if (mode == ModeIndexType::DRAINAGE || mode == ModeIndexType::DRAINAGE_TO_IMBIBITION ||
+ mode == ModeIndexType::IMBIBITION_TO_DRAINAGE_FROM_SCANNING) {
+
+
+ {
+ real64 const Smin = m_wettingCurve.oppositeBoundPhaseVolFraction;
+ real64 const Shy = (phaseMinHistoricalVolFraction[ipWetting] < Smin)
+ ? phaseMinHistoricalVolFraction[ipWetting]
+ : Smin;
+ real64 Scrt = 0.0;
+ KilloughHysteresis::computeTrappedCriticalPhaseVolFraction(m_wettingCurve, Shy,
+ m_landParam[ipWetting],
+ m_jerauldParam_a, m_jerauldParam_b,
+ Scrt);
+ phaseTrappedVolFrac[ipWetting] = LvArray::math::min(Scrt, phaseVolFraction[ipWetting]);
+ }
+
+ {
+ real64 const Smax = m_nonWettingCurve.oppositeBoundPhaseVolFraction;
+ real64 const Shy = (phaseMaxHistoricalVolFraction[ipNonWetting] > Smax)
+ ? phaseMaxHistoricalVolFraction[ipNonWetting]
+ : Smax;
+ real64 Scrt = 0.0;
+ KilloughHysteresis::computeTrappedCriticalPhaseVolFraction(m_nonWettingCurve, Shy,
+ m_landParam[ipNonWetting],
+ m_jerauldParam_a, m_jerauldParam_b,
+ Scrt);
+ phaseTrappedVolFrac[ipNonWetting] = LvArray::math::min(Scrt, phaseVolFraction[ipNonWetting]);
+ }
+ } else if (mode == ModeIndexType::IMBIBITION || mode == ModeIndexType::IMBIBITION_TO_DRAINAGE) {
+ {
+ real64 const Smax = m_wettingCurve.imbibitionExtremaPhaseVolFraction;
+ real64 const Shy = (phaseMaxHistoricalVolFraction[ipWetting] < Smax)
+ ? phaseMaxHistoricalVolFraction[ipWetting]
+ : Smax;
+ real64 Scrt = 0.0;
+ KilloughHysteresis::computeTrappedCriticalPhaseVolFraction(m_wettingCurve, Shy,
+ m_landParam[ipWetting],
+ m_jerauldParam_a, m_jerauldParam_b,
+ Scrt);
+ phaseTrappedVolFrac[ipWetting] = LvArray::math::min(Scrt, phaseVolFraction[ipWetting]);
+ }
+
+ {
+ real64 const Smin = m_nonWettingCurve.imbibitionExtremaPhaseVolFraction;;
+ real64 const Shy = (phaseMinHistoricalVolFraction[ipNonWetting] > Smin)
+ ? phaseMinHistoricalVolFraction[ipNonWetting]
+ : Smin;
+ real64 Scrt = 0.0;
+ KilloughHysteresis::computeTrappedCriticalPhaseVolFraction(m_nonWettingCurve, Shy,
+ m_landParam[ipNonWetting],
+ m_jerauldParam_a, m_jerauldParam_b,
+ Scrt);
+ phaseTrappedVolFrac[ipNonWetting] = LvArray::math::min(Scrt, phaseVolFraction[ipNonWetting]);
+ }
+ }
+
+
+ }
+
+
+ }
+
+
+ void
+ TableCapillaryPressureHysteresis::KernelWrapper::computeImbibitionNonWettingCapillaryPressure(
+ const arrayView1d &nonWettingKernelWrapper,
+ const KilloughHysteresis::HysteresisCurve &nonWettingCurve,
+ const KilloughHysteresis::HysteresisCurve &wettingCurve,
+ const geos::real64 &landParam,
+ const geos::real64 &phaseVolFraction,
+ const geos::real64 &phaseMaxHistoricalVolFraction,
+ geos::real64 &phaseTrappedVolFrac,
+ geos::real64 &phaseCapPressure,
+ geos::real64 &dPhaseCapPressure_dPhaseVolFrac,
+ const ModeIndexType &mode) const {
+ GEOS_ASSERT(!nonWettingCurve.isWetting());
+ real64 const S = phaseVolFraction;
+ real64 const Smii = nonWettingCurve.imbibitionExtremaPhaseVolFraction;
+ real64 const Smid = nonWettingCurve.drainageExtremaPhaseVolFraction;
+ real64 const Smax = nonWettingCurve.oppositeBoundPhaseVolFraction;
+
+ GEOS_UNUSED_VAR( Smii, Smid );
+
+// if( S >= Smax )
+// {
+// //above accessible range
+// phaseCapPressure = CAP_INF;
+// dPhaseCapPressure_dPhaseVolFrac = CAP_INF_DERIV;
+// }
+// else if( S <= Smid )
+// {
+// //below accessible range
+// phaseCapPressure = -CAP_INF;
+// dPhaseCapPressure_dPhaseVolFrac = CAP_INF_DERIV;
+// }
+// else
+ {
+ //drainage to imbibition
+ real64 dpci_dS, dpcd_dS;
+ real64 const pci = nonWettingKernelWrapper[ModeIndexType::IMBIBITION].compute(&S, &dpci_dS);
+ real64 const pcd = nonWettingKernelWrapper[ModeIndexType::DRAINAGE].compute(&S, &dpcd_dS);
+
+ // Step 1: get the trapped from wetting data
+ real64 const Shy = (phaseMaxHistoricalVolFraction < Smax) ? phaseMaxHistoricalVolFraction : Smax;
+
+ //drainage to imbibition
+ if (mode == ModeIndexType::DRAINAGE_TO_IMBIBITION) {
+ real64 Scrt = 0.0;
+ KilloughHysteresis::computeTrappedCriticalPhaseVolFraction(nonWettingCurve, Shy, landParam,
+ m_jerauldParam_a, m_jerauldParam_b,
+ Scrt);
+
+ real64 const E = m_killoughCurvatureParamCapPres;
+
+ //Set 2. compute F as in (EQ 34.21) F = (1/(Shy-S+E)-1/E) / (1/(Shy - Sgcr +E)-1/E)
+ real64 F = (1. / (Shy - S + E) - 1. / E) / (1. / (Shy - Scrt + E) - 1. / E);
+ //force bound
+ F = LvArray::math::max(F, 0.0);
+ F = LvArray::math::min(F, 1.0);
+
+ //Step 3. compute dF_dS
+ real64 dF_dS = (1. / (S * S)) / (1. / (Shy - Scrt + E) - 1. / E);
+
+ //Step 4. Eventually assemble everything following (EQ. 34.20)
+ phaseCapPressure = pcd + F * (pci - pcd);
+ dPhaseCapPressure_dPhaseVolFrac = dpci_dS + F * (dpci_dS - dpcd_dS);
+ dPhaseCapPressure_dPhaseVolFrac += dF_dS * (pci - pcd);
+
+ //update trapped fraction
+ phaseTrappedVolFrac = LvArray::math::min(Scrt, S);
+
+ }
+ //imbibition to drainage
+ else if (mode == ModeIndexType::IMBIBITION_TO_DRAINAGE) {
+ real64 Scrt = 0.0;
+ KilloughHysteresis::computeTrappedCriticalPhaseVolFraction(wettingCurve, Shy, landParam,
+ m_jerauldParam_a, m_jerauldParam_b,
+ Scrt);
+ real64 Sgma = 1. - Scrt - m_phaseIntermediateMinVolFraction;
+
+ real64 const E = m_killoughCurvatureParamCapPres;
+
+ //Set 2. compute F as in (EQ 34.21) F = (1/(Shy-S+E)-1/E) / (1/(Shy - Sgcr +E)-1/E)
+ real64 F = (1. / (S - Shy + E) - 1. / E) / (1. / (Sgma - Shy + E) - 1. / E);
+ //force bound
+ F = LvArray::math::max(F, 0.0);
+ F = LvArray::math::min(F, 1.0);
+
+ //Step 3. compute dF_dS
+ real64 dF_dS = (-1. / (S * S)) / (1. / (Shy - Scrt + E) - 1. / E);
+
+ //Step 4. Eventually assemble everything following (EQ. 34.20)
+ phaseCapPressure = pci + F * (pcd - pci);
+ dPhaseCapPressure_dPhaseVolFrac = dpcd_dS + F * (dpcd_dS - dpci_dS);
+ dPhaseCapPressure_dPhaseVolFrac += dF_dS * (pcd - pci);
+ } else {
+ GEOS_THROW(GEOS_FMT("{}: State is {}.Shouldnt be used in pure DRAINAGE or IMBIBITION.",
+ "TableCapillaryPressureHysteresis",
+ (mode == ModeIndexType::DRAINAGE) ? "DRAINAGE" : ((mode ==
+ ModeIndexType::IMBIBITION)
+ ? "IMBIBITION"
+ : "UNKNOWN")),
+ InputError);
+ }
+
+
+ }
+ }
+
+
+ void
+ TableCapillaryPressureHysteresis::KernelWrapper::computeImbibitionNonWettingCapillaryPressure(
+ const arrayView1d &nonWettingKernelWrapper,
+ const KilloughHysteresis::HysteresisCurve &nonWettingCurve,
+ const geos::real64 &landParam,
+ const geos::real64 &phaseVolFraction,
+ const geos::real64 &phaseMaxHistoricalVolFraction,
+ geos::real64 &phaseTrappedVolFrac,
+ geos::real64 &phaseCapPressure,
+ geos::real64 &dPhaseCapPressure_dPhaseVolFrac,
+ const ModeIndexType &mode) const {
+
+ GEOS_ASSERT(!nonWettingCurve.isWetting());
+ real64 const S = phaseVolFraction;
+ real64 const Smii = nonWettingCurve.imbibitionExtremaPhaseVolFraction;
+ real64 const Smid = nonWettingCurve.drainageExtremaPhaseVolFraction;
+ real64 const Smax = nonWettingCurve.oppositeBoundPhaseVolFraction;
+
+ GEOS_UNUSED_VAR( Smii, Smid );
+
+// if( S >= Smax )
+// {
+// //above accessible range
+// phaseCapPressure = CAP_INF;
+// dPhaseCapPressure_dPhaseVolFrac = CAP_INF_DERIV;
+// }
+// else if( S <= Smid )
+// {
+// //below accessible range
+// phaseCapPressure = -CAP_INF;
+// dPhaseCapPressure_dPhaseVolFrac = CAP_INF_DERIV;
+// }
+// else
+ {
+ //drainage to imbibition
+ real64 dpci_dS, dpcd_dS;
+ real64 const pci = nonWettingKernelWrapper[ModeIndexType::IMBIBITION].compute(&S, &dpci_dS);
+ real64 const pcd = nonWettingKernelWrapper[ModeIndexType::DRAINAGE].compute(&S, &dpcd_dS);
+
+ // Step 1: get the trapped from wetting data
+ real64 const Shy = (phaseMaxHistoricalVolFraction < Smax) ? phaseMaxHistoricalVolFraction : Smax;
+
+ //drainage to imbibition
+ if (mode == ModeIndexType::DRAINAGE_TO_IMBIBITION) {
+ real64 Scrt = 0.0;
+ KilloughHysteresis::computeTrappedCriticalPhaseVolFraction(nonWettingCurve, Shy, landParam,
+ m_jerauldParam_a, m_jerauldParam_b,
+ Scrt);
+
+ real64 const E = m_killoughCurvatureParamCapPres;
+
+ //Set 2. compute F as in (EQ 34.21) F = (1/(Shy-S+E)-1/E) / (1/(Shy - Sgcr +E)-1/E)
+ real64 F = (1. / (Shy - S + E) - 1. / E) / (1. / (Shy - Scrt + E) - 1. / E);
+ //force bound
+ F = LvArray::math::max(F, 0.0);
+ F = LvArray::math::min(F, 1.0);
+
+ //Step 3. compute dF_dS
+ real64 dF_dS = (1. / (S * S)) / (1. / (Shy - Scrt + E) - 1. / E);
+
+ //Step 4. Eventually assemble everything following (EQ. 34.20)
+ phaseCapPressure = pcd + F * (pci - pcd);
+ dPhaseCapPressure_dPhaseVolFrac = dpci_dS + F * (dpci_dS - dpcd_dS);
+ dPhaseCapPressure_dPhaseVolFrac += dF_dS * (pci - pcd);
+
+ //update trapped fraction
+ phaseTrappedVolFrac = LvArray::math::min(Scrt, S);
+
+ }
+ //imbibition to drainage
+ else if (mode == ModeIndexType::IMBIBITION_TO_DRAINAGE) {
+ real64 Scrt = 0.0;
+ KilloughHysteresis::computeTrappedCriticalPhaseVolFraction(nonWettingCurve, Shy, landParam,
+ m_jerauldParam_a, m_jerauldParam_b,
+ Scrt);
+ real64 Sgma = 1. - (1. - Scrt);
+
+ real64 const E = m_killoughCurvatureParamCapPres;
+
+ //Set 2. compute F as in (EQ 34.21) F = (1/(Shy-S+E)-1/E) / (1/(Shy - Sgcr +E)-1/E)
+ real64 F = (1. / (S - Shy + E) - 1. / E) / (1. / (Sgma - Shy + E) - 1. / E);
+ //force bound
+ F = LvArray::math::max(F, 0.0);
+ F = LvArray::math::min(F, 1.0);
+
+ //Step 3. compute dF_dS
+ real64 dF_dS = (-1. / (S * S)) / (1. / (Shy - Scrt + E) - 1. / E);
+
+ //Step 4. Eventually assemble everything following (EQ. 34.20)
+ phaseCapPressure = pci + F * (pcd - pci);
+ dPhaseCapPressure_dPhaseVolFrac = dpcd_dS + F * (dpcd_dS - dpci_dS);
+ dPhaseCapPressure_dPhaseVolFrac += dF_dS * (pcd - pci);
+ } else {
+ GEOS_THROW(GEOS_FMT("{}: State is {}.Shouldnt be used in pure DRAINAGE or IMBIBITION.",
+ "TableCapillaryPressureHysteresis",
+ (mode == ModeIndexType::DRAINAGE) ? "DRAINAGE" : ((mode ==
+ ModeIndexType::IMBIBITION)
+ ? "IMBIBITION"
+ : "UNKNOWN")),
+ InputError);
+ }
+
+
+ }
+ }
+
+
+ void TableCapillaryPressureHysteresis::KernelWrapper::computeBoundCapillaryPressure(
+ const TableFunction::KernelWrapper &drainageRelpermWrapper,
+ const geos::real64 &phaseVolFraction,
+ geos::real64 &phaseCapPressure,
+ geos::real64 &dPhaseCapPressure_dPhaseVolFrac) const {
+ phaseCapPressure = drainageRelpermWrapper.compute(&phaseVolFraction,
+ &dPhaseCapPressure_dPhaseVolFrac);
+ }
+
+ /// Helper function to compute Pc(S) for given S, mode, and historical values
+ /// Used for Newton-Raphson inversion in computeInv
+ GEOS_HOST_DEVICE
+ inline real64 TableCapillaryPressureHysteresis::KernelWrapper::computeCapillaryPressureForSaturation(
+ real64 const S,
+ fields::cappres::ModeIndexType const &mode,
+ integer const ipPhase,
+ real64 const &phaseMinHistoricalVolFraction,
+ real64 const &phaseMaxHistoricalVolFraction,
+ real64 const &phaseMode2PeakVolFraction,
+ arrayView1d const &capPresKernelWrappers,
+ KilloughHysteresis::HysteresisCurve const &wettingCurve,
+ KilloughHysteresis::HysteresisCurve const &nonWettingCurve,
+ real64 const &landParam,
+ real64 const &phaseIntermediateMinVolFraction,
+ real64 const &killoughCurvatureParam,
+ real64 const &jerauldParam_a,
+ real64 const &jerauldParam_b,
+ bool const isWettingPhase,
+ real64 const precomputedScrt,
+ real64 const precomputedDenomF,
+ real64 const precomputedShy) const
+ {
+ GEOS_UNUSED_VAR( ipPhase, landParam, jerauldParam_a, jerauldParam_b );
+ real64 pc = 0.0;
+ real64 dpc_dS = 0.0;
+
+ // For pure drainage or imbibition modes, use the table directly
+ if (mode == fields::cappres::ModeIndexType::DRAINAGE || mode == fields::cappres::ModeIndexType::IMBIBITION) {
+ integer const arrayIndex = (mode == fields::cappres::ModeIndexType::DRAINAGE) ? fields::cappres::ModeIndexType::DRAINAGE : fields::cappres::ModeIndexType::IMBIBITION;
+ if (arrayIndex < static_cast(capPresKernelWrappers.size())) {
+ constexpr bool ENABLE_COMPUTE_PC_DEBUG = false;
+ if constexpr (ENABLE_COMPUTE_PC_DEBUG) {
+ std::cout << "[COMPUTE_PC_DEBUG] computeCapillaryPressureForSaturation: mode="
+ << (mode == fields::cappres::ModeIndexType::DRAINAGE ? "DRAINAGE" : "IMBIBITION")
+ << ", arrayIndex=" << arrayIndex
+ << ", S=" << S
+ << ", capPresKernelWrappers.size()=" << capPresKernelWrappers.size() << std::endl;
+ }
+ pc = capPresKernelWrappers[arrayIndex].compute(&S, &dpc_dS);
+ if constexpr (ENABLE_COMPUTE_PC_DEBUG) {
+ std::cout << "[COMPUTE_PC_DEBUG] After table.compute: pc=" << pc << ", dpc_dS=" << dpc_dS << std::endl;
+ }
+ }
+ }
+ // For scanning curves (DRAINAGE_TO_IMBIBITION or IMBIBITION_TO_DRAINAGE)
+ else {
+ // Precomputed values should be provided by caller (from computeFlux before local_solver)
+ // If not provided, fall back to drainage curve to avoid calling computeTrappedCriticalPhaseVolFraction
+ if (precomputedScrt < 0.0) {
+ // Precomputed values not provided - return drainage curve value
+ real64 const arrayIndex = fields::cappres::ModeIndexType::DRAINAGE;
+ if (arrayIndex < static_cast(capPresKernelWrappers.size())) {
+ pc = capPresKernelWrappers[arrayIndex].compute(&S, &dpc_dS);
+ }
+ return pc;
+ }
+
+ real64 dpci_dS, dpcd_dS;
+ real64 const pci = capPresKernelWrappers[fields::cappres::ModeIndexType::IMBIBITION].compute(&S, &dpci_dS);
+ real64 const pcd = capPresKernelWrappers[fields::cappres::ModeIndexType::DRAINAGE].compute(&S, &dpcd_dS);
+
+ real64 const E = killoughCurvatureParam;
+
+ if (isWettingPhase) {
+ real64 const Smin = wettingCurve.oppositeBoundPhaseVolFraction;
+ real64 const Smax = wettingCurve.drainageExtremaPhaseVolFraction;
+
+ if (mode == fields::cappres::ModeIndexType::DRAINAGE_TO_IMBIBITION) {
+ // DRAINAGE_TO_IMBIBITION: transitioning from drainage (low S) to imbibition (high S)
+ // Shy should be the minimum historical saturation (where drainage started)
+ real64 const Shy = (precomputedShy >= 0.0) ? precomputedShy :
+ ((phaseMinHistoricalVolFraction > Smin) ? phaseMinHistoricalVolFraction : Smin);
+
+ // Use precomputed value - should have been computed before local_solver
+ real64 const Scrt = precomputedScrt;
+ // For wetting curve, Scrt IS the max wetting saturation (Swma = Scrt)
+ // This matches the forward compute: Swma = 1 - (1 - Scrt) = Scrt
+ real64 const Swma = 1 - (1 - Scrt);
+ real64 denomF = precomputedDenomF;
+ if (LvArray::math::abs(precomputedDenomF) < 1e-15) {
+ denomF = (1. / (Swma - Shy + E) - 1. / E);
+ }
+ // Guard against division by zero
+ real64 F = 0.0;
+ if (LvArray::math::abs(denomF) >= 1e-15) {
+ real64 const F_num = (1. / (S - Shy + E) - 1. / E);
+ F = F_num / denomF;
+ }
+ F = LvArray::math::max(F, 0.0);
+ F = LvArray::math::min(F, 1.0);
+
+ pc = pcd + F * (pci - pcd);
+ dpc_dS = dpcd_dS + F * (dpci_dS - dpcd_dS);
+ }
+ else if (mode == fields::cappres::ModeIndexType::IMBIBITION_TO_DRAINAGE) {
+ // IMBIBITION_TO_DRAINAGE: transitioning from imbibition (high S) to drainage (low S)
+ // Shy should be the maximum historical saturation (where imbibition started)
+ real64 const Shy = (precomputedShy >= 0.0) ? precomputedShy :
+ ((phaseMaxHistoricalVolFraction < Smax) ? phaseMaxHistoricalVolFraction : Smax);
+
+ // Use precomputed value - should have been computed before local_solver
+ real64 const Scrt = precomputedScrt;
+
+ real64 denomF = precomputedDenomF;
+ if (LvArray::math::abs(precomputedDenomF) < 1e-15) {
+ denomF = (1. / (Shy - Scrt + E) - 1. / E);
+ }
+ // Guard against division by zero
+ real64 F = 0.0;
+ if (LvArray::math::abs(denomF) >= 1e-15) {
+ real64 const F_num = (1. / (Shy - S + E) - 1. / E);
+ F = F_num / denomF;
+ }
+ F = LvArray::math::max(F, 0.0);
+ F = LvArray::math::min(F, 1.0);
+
+ pc = pci + F * (pcd - pci);
+ dpc_dS = dpci_dS + F * (dpcd_dS - dpci_dS);
+ }
+ else if (mode == fields::cappres::ModeIndexType::IMBIBITION_TO_DRAINAGE_FROM_SCANNING) {
+ // IMBIBITION_TO_DRAINAGE_FROM_SCANNING: Secondary drainage scanning curve (Mode 4)
+ // Based on Killough (1976) - solve quadratic for ghost departure point
+ real64 const H = (phaseMinHistoricalVolFraction > Smin) ? phaseMinHistoricalVolFraction : Smin;
+ real64 S_star = phaseMode2PeakVolFraction;
+ if (S_star < 1e-12 || S_star >= Smax - 1e-12) {
+ if (phaseMaxHistoricalVolFraction > H + 1e-12 && phaseMaxHistoricalVolFraction < Smax - 1e-12) {
+ S_star = phaseMaxHistoricalVolFraction;
+ } else {
+ S_star = LvArray::math::max(H + 0.1, LvArray::math::min(S, Smax - 0.01));
+ }
+ }
+
+ // Compute F_known from Mode 2 at S_star
+ real64 const Scrt = precomputedScrt;
+ // For wetting curve, Scrt IS the max wetting saturation (Swma = Scrt)
+ real64 const Swma = 1 - (1 - Scrt);
+ real64 F_known = 0.0;
+ if (S_star <= Swma + 1e-12) {
+ real64 const F_num = (1. / (S_star - H + E) - 1. / E);
+ real64 const F_denom = (1. / (Swma - H + E) - 1. / E);
+ if (LvArray::math::abs(F_denom) > 1e-12) {
+ F_known = F_num / F_denom;
+ }
+ }
+ F_known = LvArray::math::max(0.0, LvArray::math::min(1.0, F_known));
+
+ // F_star_target = 1 - F_known
+ real64 const F_star_target = 1.0 - F_known;
+
+ // Solve quadratic for x (ghost departure point)
+ real64 const a = S_star;
+ real64 const b = H; // Use H so curve rejoins drainage at H
+ real64 const T = F_star_target;
+ real64 const coeff_A = (1.0 - T);
+ real64 const coeff_B = -(1.0 - T) * (a + b - 2.0 * E) - (1.0 - T) * E;
+ real64 const coeff_C = (1.0 - T) * (a - E) * (b - E)
+ - E * E * (1.0 + T)
+ - E * (T * a - b);
+
+ real64 x = S_star;
+ real64 discriminant = coeff_B * coeff_B - 4.0 * coeff_A * coeff_C;
+ if (discriminant >= 0.0 && LvArray::math::abs(coeff_A) > 1e-14) {
+ real64 sqrt_disc = LvArray::math::sqrt(discriminant);
+ real64 x1 = (-coeff_B + sqrt_disc) / (2.0 * coeff_A);
+ real64 x2 = (-coeff_B - sqrt_disc) / (2.0 * coeff_A);
+ if (x1 > S_star - 1e-8 && x2 > S_star - 1e-8) {
+ x = LvArray::math::min(x1, x2);
+ } else if (x1 > S_star - 1e-8) {
+ x = x1;
+ } else if (x2 > S_star - 1e-8) {
+ x = x2;
+ }
+ }
+
+ // Compute F_star: F_star = [1/(x - S + E) - 1/E] / [1/(x - H + E) - 1/E]
+ real64 F_star = 1.0;
+ real64 const F_star_num = (1. / (x - S + E) - 1. / E);
+ real64 const F_star_denom = (1. / (x - H + E) - 1. / E);
+ if (LvArray::math::abs(F_star_denom) > 1e-12) {
+ F_star = F_star_num / F_star_denom;
+ F_star = LvArray::math::max(0.0, LvArray::math::min(1.0, F_star));
+ }
+
+ // Pc = Pc_Im + F_star * (Pc_Dr - Pc_Im)
+ pc = pci + F_star * (pcd - pci);
+ dpc_dS = dpci_dS + F_star * (dpcd_dS - dpci_dS);
+ }
+ }
+ else {
+ // Non-wetting phase
+ real64 const Smax = nonWettingCurve.oppositeBoundPhaseVolFraction;
+ real64 const Shy = (precomputedShy >= 0.0) ? precomputedShy :
+ ((phaseMaxHistoricalVolFraction < Smax) ? phaseMaxHistoricalVolFraction : Smax);
+
+ if (mode == fields::cappres::ModeIndexType::DRAINAGE_TO_IMBIBITION) {
+ // Use precomputed value - should have been computed before local_solver
+ real64 const Scrt = precomputedScrt;
+
+ real64 denomF = precomputedDenomF;
+ if (LvArray::math::abs(precomputedDenomF) < 1e-15) {
+ denomF = (1. / (Shy - Scrt + E) - 1. / E);
+ }
+ // Guard against division by zero
+ real64 F = 0.0;
+ if (LvArray::math::abs(denomF) >= 1e-15) {
+ real64 const F_num = (1. / (Shy - S + E) - 1. / E);
+ F = F_num / denomF;
+ }
+ F = LvArray::math::max(F, 0.0);
+ F = LvArray::math::min(F, 1.0);
+
+ pc = pcd + F * (pci - pcd);
+ dpc_dS = dpci_dS + F * (dpci_dS - dpcd_dS);
+ // Note: there's also a dF/dS term in the non-wetting case, but for inversion we approximate
+ }
+ else if (mode == fields::cappres::ModeIndexType::IMBIBITION_TO_DRAINAGE) {
+ // Use precomputed value - should have been computed before local_solver
+ real64 const Scrt = precomputedScrt;
+ real64 const Sgma = 1. - Scrt - phaseIntermediateMinVolFraction;
+
+ real64 denomF = precomputedDenomF;
+ if (LvArray::math::abs(precomputedDenomF) < 1e-15) {
+ denomF = (1. / (Sgma - Shy + E) - 1. / E);
+ }
+ // Guard against division by zero
+ real64 F = 0.0;
+ if (LvArray::math::abs(denomF) >= 1e-15) {
+ real64 const F_num = (1. / (S - Shy + E) - 1. / E);
+ F = F_num / denomF;
+ }
+ F = LvArray::math::max(F, 0.0);
+ F = LvArray::math::min(F, 1.0);
+
+ pc = pci + F * (pcd - pci);
+ dpc_dS = dpcd_dS + F * (dpcd_dS - dpci_dS);
+ // Note: there's also a dF/dS term in the non-wetting case, but for inversion we approximate
+ }
+ }
+ }
+
+ return pc;
+ }
+
+ void
+ TableCapillaryPressureHysteresis::KernelWrapper::computeInv(
+ arraySlice1d const &phaseVolFraction,
+ arraySlice1d const &phaseMaxHistoricalVolFraction,
+ arraySlice1d const &phaseMinHistoricalVolFraction,
+ arraySlice1d const &phaseMode2PeakVolFraction,
+ arraySlice1d const &phaseTrappedVolFrac,
+ arraySlice1d const &phaseCapPressure,
+ arraySlice2d const &dPhaseCapPressure_dPhaseVolFrac,
+ fields::cappres::ModeIndexType const &mode) const
+ {
+ GEOS_UNUSED_VAR( phaseTrappedVolFrac );
+ LvArray::forValuesInSlice(dPhaseCapPressure_dPhaseVolFrac, [](real64 &val) { val = 0.0; });
+
+ constexpr bool ENABLE_COMPUTEINV_PATH_DEBUG = false;
+ if constexpr (ENABLE_COMPUTEINV_PATH_DEBUG) {
+ std::cout << "[COMPUTEINV_PATH] Entry to computeInv(), mode="
+ << (mode == fields::cappres::ModeIndexType::DRAINAGE ? "DRAINAGE" :
+ mode == fields::cappres::ModeIndexType::IMBIBITION ? "IMBIBITION" : "SCANNING") << std::endl;
+ }
+
+ using PT = CapillaryPressureBase::PhaseType;
+ integer const ipWater = (PT::WATER < m_phaseOrder.size()) ? m_phaseOrder[PT::WATER] : -1;
+ integer const ipOil = (PT::OIL < m_phaseOrder.size()) ? m_phaseOrder[PT::OIL] : -1;
+ integer const ipGas = (PT::GAS < m_phaseOrder.size()) ? m_phaseOrder[PT::GAS] : -1;
+
+ if constexpr (ENABLE_COMPUTEINV_PATH_DEBUG) {
+ std::cout << "[COMPUTEINV_PATH] Phase indices: ipWater=" << ipWater
+ << ", ipOil=" << ipOil << ", ipGas=" << ipGas << std::endl;
+ std::cout << "[COMPUTEINV_PATH] After phase indices calculation, continuing..." << std::endl;
+ }
+
+ constexpr real64 tol = 1e-9;
+ constexpr integer maxIter = 20;
+ constexpr real64 minS = 0.0;
+ constexpr real64 maxS = 1.0;
+
+ // Determine which phase pairs need inversion
+ if constexpr (ENABLE_COMPUTEINV_PATH_DEBUG) {
+ std::cout << "[COMPUTEINV_PATH] Checking phase combination..." << std::endl;
+ std::cout << "[COMPUTEINV_PATH] ipWater=" << ipWater << ", ipOil=" << ipOil << ", ipGas=" << ipGas << std::endl;
+ bool isThreePhase = (ipWater >= 0 && ipOil >= 0 && ipGas >= 0);
+ std::cout << "[COMPUTEINV_PATH] Three-phase condition: " << (isThreePhase ? "TRUE" : "FALSE") << std::endl;
+ std::cout << "[COMPUTEINV_PATH] About to check three-phase if statement..." << std::endl;
+ }
+ if (ipWater >= 0 && ipOil >= 0 && ipGas >= 0) {
+ if constexpr (ENABLE_COMPUTEINV_PATH_DEBUG) {
+ std::cout << "[COMPUTEINV_PATH] Taking THREE-PHASE path" << std::endl;
+ }
+ // Three-phase: invert wetting and non-wetting capillary pressures
+
+ // 1. Invert wetting phase (water-oil capillary pressure)
+ constexpr real64 pcEpsilon = 1e-10;
+ constexpr bool ENABLE_INVERSE_TABLE_DEBUG = false;
+ if (ipWater >= 0 && LvArray::math::abs(phaseCapPressure[ipWater]) > pcEpsilon) {
+ // For pure DRAINAGE or IMBIBITION modes, use direct table lookup (analytical inverse)
+ if (mode == fields::cappres::ModeIndexType::DRAINAGE || mode == fields::cappres::ModeIndexType::IMBIBITION) {
+ integer const arrayIndex = (mode == fields::cappres::ModeIndexType::DRAINAGE) ? 0 : 1;
+ array1d input(1);
+ input[0] = phaseCapPressure[ipWater];
+ auto inputSlice = input.toSliceConst();
+
+ if constexpr (ENABLE_INVERSE_TABLE_DEBUG) {
+ real64 S_before = phaseVolFraction[ipWater];
+ real64 dS_dPc_before = dPhaseCapPressure_dPhaseVolFrac[ipWater][ipWater];
+ std::cout << "[INVERSE_TABLE_DEBUG] Cell1 (water), mode=" << (mode == fields::cappres::ModeIndexType::DRAINAGE ? "DRAINAGE" : "IMBIBITION")
+ << ", arrayIndex=" << arrayIndex << std::endl;
+ std::cout << " Input Pc=" << input[0] << std::endl;
+ std::cout << " S before inverse table=" << S_before << std::endl;
+ std::cout << " dS_dPc before inverse table=" << dS_dPc_before << std::endl;
+ }
+
+ // Match TableCapillaryPressure exactly: pass derivative pointer directly to inverse table
+ // Note: The inverse table returns dS/dPc, but we store it directly like TableCapillaryPressure does
+ // The local solver will call compute() afterwards to get the correct dPc/dS derivative
+ phaseVolFraction[ipWater] = m_inverseWettingIntermediateCapillaryPressureKernelWrappers[arrayIndex].compute(
+ inputSlice, &dPhaseCapPressure_dPhaseVolFrac[ipWater][ipWater]);
+
+ if constexpr (ENABLE_INVERSE_TABLE_DEBUG) {
+ real64 S_after = phaseVolFraction[ipWater];
+ real64 dS_dPc_after = dPhaseCapPressure_dPhaseVolFrac[ipWater][ipWater];
+ std::cout << " S after inverse table=" << S_after << std::endl;
+ std::cout << " dS_dPc after inverse table=" << dS_dPc_after << std::endl;
+ }
+
+ phaseVolFraction[ipOil] = 1.0 - phaseVolFraction[ipWater] - phaseVolFraction[ipGas];
+ // Direct lookup complete, skip Newton-Raphson
+ } else {
+ // For scanning curves, use analytical inverse if possible, otherwise Newton-Raphson
+ // TEMPORARY: Flag to enable analytical inverse for scanning curves
+ constexpr bool USE_ANALYTICAL_INVERSE_SCANNING = true;
+
+ real64 S_guess = phaseMaxHistoricalVolFraction[ipWater];
+ if (S_guess <= phaseMinHistoricalVolFraction[ipWater] || S_guess < minS || S_guess > maxS) {
+ // Fall back to drainage/imbibition curve evaluation if available
+ real64 const Smin = m_wettingCurve.oppositeBoundPhaseVolFraction;
+ real64 const Smax = m_wettingCurve.drainageExtremaPhaseVolFraction;
+ S_guess = 0.5 * (Smin + Smax);
+ S_guess = LvArray::math::max(S_guess, minS);
+ S_guess = LvArray::math::min(S_guess, maxS);
+ }
+
+ // Precompute fixed parameters for scanning curves (these don't change during Newton-Raphson)
+ real64 precomputedScrt_water = -1.0;
+ real64 precomputedDenomF_water = 0.0;
+ real64 precomputedShy_water = -1.0;
+
+ // Precompute for scanning curves (Mode 2, Mode 3, or Mode 4)
+ if (mode == fields::cappres::ModeIndexType::DRAINAGE_TO_IMBIBITION ||
+ mode == fields::cappres::ModeIndexType::IMBIBITION_TO_DRAINAGE ||
+ mode == fields::cappres::ModeIndexType::IMBIBITION_TO_DRAINAGE_FROM_SCANNING) {
+ real64 const Smin = m_wettingCurve.oppositeBoundPhaseVolFraction;
+ real64 const Smax = m_wettingCurve.drainageExtremaPhaseVolFraction;
+ real64 const E = m_killoughCurvatureParamCapPres;
+
+ if (mode == fields::cappres::ModeIndexType::DRAINAGE_TO_IMBIBITION) {
+ // DRAINAGE_TO_IMBIBITION: Shy should be minimum historical (where drainage started)
+ precomputedShy_water = (phaseMinHistoricalVolFraction[ipWater] > Smin) ?
+ phaseMinHistoricalVolFraction[ipWater] : Smin;
+
+ // Compute Scrt for DRAINAGE_TO_IMBIBITION scanning curve (wetting phase)
+ real64 Scrt = 0.0;
+ KilloughHysteresis::computeTrappedCriticalPhaseVolFraction(m_wettingCurve, precomputedShy_water,
+ m_landParam[ipWater],
+ m_jerauldParam_a, m_jerauldParam_b,
+ Scrt);
+ precomputedScrt_water = Scrt;
+ // For wetting curve, Scrt IS the max wetting saturation (Swma = Scrt)
+ real64 const Swma = 1. - (1. - Scrt);
+ // Compute denomF for DRAINAGE_TO_IMBIBITION: denomF = (1. / (Swma - Shy + E) - 1. / E)
+ precomputedDenomF_water = (1. / (Swma - precomputedShy_water + E) - 1. / E);
+ }
+ else if (mode == fields::cappres::ModeIndexType::IMBIBITION_TO_DRAINAGE) {
+ // IMBIBITION_TO_DRAINAGE: Shy should be maximum historical (where imbibition started)
+ precomputedShy_water = (phaseMaxHistoricalVolFraction[ipWater] < Smax) ?
+ phaseMaxHistoricalVolFraction[ipWater] : Smax;
+
+ // Compute Scrt for IMBIBITION_TO_DRAINAGE scanning curve (wetting phase)
+ real64 Scrt = 0.0;
+ KilloughHysteresis::computeTrappedCriticalPhaseVolFraction(m_wettingCurve, precomputedShy_water,
+ m_landParam[ipWater],
+ m_jerauldParam_a, m_jerauldParam_b,
+ Scrt);
+ precomputedScrt_water = Scrt;
+ // Compute denomF for IMBIBITION_TO_DRAINAGE: denomF = (1. / (Shy - Scrt + E) - 1. / E)
+ precomputedDenomF_water = (1. / (precomputedShy_water - Scrt + E) - 1. / E);
+ }
+ else if (mode == fields::cappres::ModeIndexType::IMBIBITION_TO_DRAINAGE_FROM_SCANNING) {
+ // Mode 4: IMBIBITION_TO_DRAINAGE_FROM_SCANNING
+ // H (first reversal point) should be minimum historical (where imbibition started)
+ // This is used to compute Scrt for Mode 4
+ real64 const H = (phaseMinHistoricalVolFraction[ipWater] > Smin) ?
+ phaseMinHistoricalVolFraction[ipWater] : Smin;
+
+ // Compute Scrt for Mode 4 based on H (first reversal point)
+ real64 Scrt = 0.0;
+ KilloughHysteresis::computeTrappedCriticalPhaseVolFraction(m_wettingCurve, H,
+ m_landParam[ipWater],
+ m_jerauldParam_a, m_jerauldParam_b,
+ Scrt);
+ precomputedScrt_water = Scrt;
+ // For Mode 4, precomputedShy is not used in the same way, but set it to H for consistency
+ precomputedShy_water = H;
+ // denomF is not used for Mode 4 (it solves quadratic instead), but set to 0 to indicate it's not applicable
+ precomputedDenomF_water = 0.0;
+ }
+ }
+
+ real64 S = S_guess;
+
+ // Try analytical inverse for scanning curves if enabled
+ if (USE_ANALYTICAL_INVERSE_SCANNING) {
+ // Use inverse tables to get bounds: S_dr(Pc) and S_im(Pc)
+ array1d input_dr(1), input_im(1);
+ input_dr[0] = phaseCapPressure[ipWater];
+ input_im[0] = phaseCapPressure[ipWater];
+ auto inputSlice_dr = input_dr.toSliceConst();
+ auto inputSlice_im = input_im.toSliceConst();
+
+ real64 dS_dPc_dr = 0.0, dS_dPc_im = 0.0;
+ real64 S_dr = m_inverseWettingIntermediateCapillaryPressureKernelWrappers[ModeIndexType::DRAINAGE].compute(
+ inputSlice_dr, &dS_dPc_dr);
+ real64 S_im = m_inverseWettingIntermediateCapillaryPressureKernelWrappers[ModeIndexType::IMBIBITION].compute(
+ inputSlice_im, &dS_dPc_im);
+
+ // Clamp to valid range
+ S_dr = LvArray::math::max(minS, LvArray::math::min(maxS, S_dr));
+ S_im = LvArray::math::max(minS, LvArray::math::min(maxS, S_im));
+
+ // For scanning curves, S is between S_dr and S_im (order depends on mode)
+ real64 S_low = LvArray::math::min(S_dr, S_im);
+ real64 S_high = LvArray::math::max(S_dr, S_im);
+
+ // Use analytical inverse based on F(S) formula
+ real64 const E = m_killoughCurvatureParamCapPres;
+ real64 const Pc_target = phaseCapPressure[ipWater];
+
+ if (mode == fields::cappres::ModeIndexType::DRAINAGE_TO_IMBIBITION) {
+ // DRAINAGE_TO_IMBIBITION: Pc = Pc_dr(S) + F(S) * [Pc_im(S) - Pc_dr(S)]
+ // F(S) = (1. / (S - Shy + E) - 1. / E) / denomF
+ // Rearranging: S = Shy - E + 1 / (F(S) * denomF + 1/E)
+ real64 const Shy = precomputedShy_water;
+ real64 const denomF = precomputedDenomF_water;
+
+ // Initial guess: interpolate between S_dr and S_im
+ S = 0.5 * (S_low + S_high);
+
+ // Fixed-point iteration using analytical F(S) formula
+ for (integer iter = 0; iter < 10; ++iter) {
+ // Compute F(S) from current S
+ real64 F_S = (1. / (S - Shy + E) - 1. / E) / denomF;
+ F_S = LvArray::math::max(0.0, LvArray::math::min(1.0, F_S));
+
+ // Compute Pc(S) to check convergence
+ real64 pc_computed = this->computeCapillaryPressureForSaturation(
+ S, mode, ipWater,
+ phaseMinHistoricalVolFraction[ipWater],
+ phaseMaxHistoricalVolFraction[ipWater],
+ phaseMode2PeakVolFraction[ipWater],
+ m_wettingIntermediateCapillaryPressureKernelWrappers,
+ m_wettingCurve,
+ m_nonWettingCurve,
+ m_landParam[ipWater],
+ m_phaseIntermediateMinVolFraction,
+ m_killoughCurvatureParamCapPres,
+ m_jerauldParam_a,
+ m_jerauldParam_b,
+ true, // isWettingPhase
+ precomputedScrt_water,
+ precomputedDenomF_water,
+ precomputedShy_water);
+
+ if (LvArray::math::abs(pc_computed - Pc_target) < tol) {
+ break;
+ }
+
+ real64 denom_F = F_S * denomF + 1. / E;
+ if (LvArray::math::abs(denom_F) > 1e-12) {
+ real64 S_new = Shy - E + 1. / denom_F;
+ S_new = LvArray::math::max(S_low, LvArray::math::min(S_high, S_new));
+ S = S_new;
+ } else {
+ // Fall back to interpolation if denominator is too small
+ S = 0.5 * (S + 0.5 * (S_low + S_high));
+ }
+ }
+ }
+ else if (mode == fields::cappres::ModeIndexType::IMBIBITION_TO_DRAINAGE) {
+ // IMBIBITION_TO_DRAINAGE: Pc = Pc_im(S) + F(S) * [Pc_dr(S) - Pc_im(S)]
+ // F(S) = (1. / (S - Shy + E) - 1. / E) / denomF
+ real64 const Shy = precomputedShy_water;
+ real64 const denomF = precomputedDenomF_water;
+
+ S = 0.5 * (S_low + S_high);
+
+ for (integer iter = 0; iter < 10; ++iter) {
+ real64 F_S = (1. / (S - Shy + E) - 1. / E) / denomF;
+ F_S = LvArray::math::max(0.0, LvArray::math::min(1.0, F_S));
+
+ real64 pc_computed = this->computeCapillaryPressureForSaturation(
+ S, mode, ipWater,
+ phaseMinHistoricalVolFraction[ipWater],
+ phaseMaxHistoricalVolFraction[ipWater],
+ phaseMode2PeakVolFraction[ipWater],
+ m_wettingIntermediateCapillaryPressureKernelWrappers,
+ m_wettingCurve,
+ m_nonWettingCurve,
+ m_landParam[ipWater],
+ m_phaseIntermediateMinVolFraction,
+ m_killoughCurvatureParamCapPres,
+ m_jerauldParam_a,
+ m_jerauldParam_b,
+ true, // isWettingPhase
+ precomputedScrt_water,
+ precomputedDenomF_water,
+ precomputedShy_water);
+
+ if (LvArray::math::abs(pc_computed - Pc_target) < tol) {
+ break;
+ }
+
+ real64 denom_F = F_S * denomF + 1. / E;
+ if (LvArray::math::abs(denom_F) > 1e-12) {
+ real64 S_new = Shy - E + 1. / denom_F;
+ S_new = LvArray::math::max(S_low, LvArray::math::min(S_high, S_new));
+ S = S_new;
+ } else {
+ S = 0.5 * (S + 0.5 * (S_low + S_high));
+ }
+ }
+ }
+ // For IMBIBITION_TO_DRAINAGE_FROM_SCANNING, fall through to Newton-Raphson (more complex F_star formula)
+ }
+
+ real64 pc_check = this->computeCapillaryPressureForSaturation(
+ S, mode, ipWater,
+ phaseMinHistoricalVolFraction[ipWater],
+ phaseMaxHistoricalVolFraction[ipWater],
+ phaseMode2PeakVolFraction[ipWater],
+ m_wettingIntermediateCapillaryPressureKernelWrappers,
+ m_wettingCurve,
+ m_nonWettingCurve,
+ m_landParam[ipWater],
+ m_phaseIntermediateMinVolFraction,
+ m_killoughCurvatureParamCapPres,
+ m_jerauldParam_a,
+ m_jerauldParam_b,
+ true, // isWettingPhase
+ precomputedScrt_water,
+ precomputedDenomF_water,
+ precomputedShy_water);
+
+ bool needs_newton = (mode == fields::cappres::ModeIndexType::IMBIBITION_TO_DRAINAGE_FROM_SCANNING) ||
+ (LvArray::math::abs(pc_check - phaseCapPressure[ipWater]) >= tol);
+
+ if (needs_newton) {
+ real64 residual_prev = 1e30;
+
+ for (integer iter = 0; iter < maxIter; ++iter) {
+ // Compute Pc(S) using the helper function
+ real64 pc_computed = this->computeCapillaryPressureForSaturation(
+ S, mode, ipWater,
+ phaseMinHistoricalVolFraction[ipWater],
+ phaseMaxHistoricalVolFraction[ipWater],
+ phaseMode2PeakVolFraction[ipWater],
+ m_wettingIntermediateCapillaryPressureKernelWrappers,
+ m_wettingCurve,
+ m_nonWettingCurve,
+ m_landParam[ipWater],
+ m_phaseIntermediateMinVolFraction,
+ m_killoughCurvatureParamCapPres,
+ m_jerauldParam_a,
+ m_jerauldParam_b,
+ true, // isWettingPhase
+ precomputedScrt_water,
+ precomputedDenomF_water,
+ precomputedShy_water);
+
+ real64 residual = pc_computed - phaseCapPressure[ipWater];
+
+ if (LvArray::math::abs(residual) < tol) {
+ break;
+ }
+
+ // Compute derivative dPc/dS using adaptive finite difference
+ // Use relative step size to handle different scales
+ real64 const dS = LvArray::math::max(1e-8, 1e-6 * LvArray::math::max(LvArray::math::abs(S), 1e-6));
+ real64 S_pert = S + dS;
+ if (S_pert > maxS) {
+ S_pert = LvArray::math::max(S - dS, minS);
+ }
+
+ real64 const pc_pert = this->computeCapillaryPressureForSaturation(
+ S_pert, mode, ipWater,
+ phaseMinHistoricalVolFraction[ipWater],
+ phaseMaxHistoricalVolFraction[ipWater],
+ phaseMode2PeakVolFraction[ipWater],
+ m_wettingIntermediateCapillaryPressureKernelWrappers,
+ m_wettingCurve,
+ m_nonWettingCurve,
+ m_landParam[ipWater],
+ m_phaseIntermediateMinVolFraction,
+ m_killoughCurvatureParamCapPres,
+ m_jerauldParam_a,
+ m_jerauldParam_b,
+ true, // isWettingPhase
+ precomputedScrt_water,
+ precomputedDenomF_water,
+ precomputedShy_water);
+
+ real64 const dpc_dS = (pc_pert - pc_computed) / (S_pert - S);
+
+ if (LvArray::math::abs(dpc_dS) > 1e-12) {
+ real64 const newtonStep = -residual / dpc_dS;
+ real64 const maxStep = 0.1; // Limit step size to prevent overshooting
+ real64 const step = LvArray::math::max(-maxStep, LvArray::math::min(maxStep, newtonStep));
+
+ real64 S_new = S + step;
+
+ // Line search: if residual increases, reduce step
+ if (iter > 0 && LvArray::math::abs(residual) > LvArray::math::abs(residual_prev)) {
+ // Backtrack: use smaller step
+ S_new = S + 0.5 * step;
+ }
+
+ // Clamp to valid range
+ S_new = LvArray::math::max(S_new, minS);
+ S_new = LvArray::math::min(S_new, maxS);
+
+ if (LvArray::math::abs(S_new - S) < 1e-10 &&
+ (S_new <= minS + 1e-10 || S_new >= maxS - 1e-10)) {
+ // Use bisection between current guess and boundary
+ S_new = 0.5 * (S + (S_new <= minS + 1e-10 ? minS : maxS));
+ }
+
+ residual_prev = residual;
+ S = S_new;
+ } else {
+ // If derivative is zero or very small, use bisection
+ real64 const S_low = LvArray::math::max(S - 0.1, minS);
+ real64 const S_high = LvArray::math::min(S + 0.1, maxS);
+ S = 0.5 * (S_low + S_high);
+ S = LvArray::math::max(S, minS);
+ S = LvArray::math::min(S, maxS);
+ }
+ }
+
+ phaseVolFraction[ipWater] = S;
+ phaseVolFraction[ipOil] = 1.0 - S - phaseVolFraction[ipGas];
+
+ // Compute derivative at final S
+ real64 dpc_dS_final = 0.0;
+ if (mode == fields::cappres::ModeIndexType::DRAINAGE || mode == fields::cappres::ModeIndexType::IMBIBITION) {
+ integer const arrayIndex = (mode == fields::cappres::ModeIndexType::DRAINAGE) ? fields::cappres::ModeIndexType::DRAINAGE : fields::cappres::ModeIndexType::IMBIBITION;
+ m_wettingIntermediateCapillaryPressureKernelWrappers[arrayIndex].compute(&S, &dpc_dS_final);
+ } else {
+ // For scanning curves, compute derivative using finite difference
+ real64 const dS_final = 1e-6;
+ real64 const S_pert_final = LvArray::math::min(S + dS_final, maxS);
+ real64 const pc_final = this->computeCapillaryPressureForSaturation(
+ S, mode, ipWater,
+ phaseMinHistoricalVolFraction[ipWater],
+ phaseMaxHistoricalVolFraction[ipWater],
+ phaseMode2PeakVolFraction[ipWater],
+ m_wettingIntermediateCapillaryPressureKernelWrappers,
+ m_wettingCurve,
+ m_nonWettingCurve,
+ m_landParam[ipWater],
+ m_phaseIntermediateMinVolFraction,
+ m_killoughCurvatureParamCapPres,
+ m_jerauldParam_a,
+ m_jerauldParam_b,
+ true, // isWettingPhase
+ precomputedScrt_water,
+ precomputedDenomF_water,
+ precomputedShy_water);
+ real64 const pc_pert_final = this->computeCapillaryPressureForSaturation(
+ S_pert_final, mode, ipWater,
+ phaseMinHistoricalVolFraction[ipWater],
+ phaseMaxHistoricalVolFraction[ipWater],
+ phaseMode2PeakVolFraction[ipWater],
+ m_wettingIntermediateCapillaryPressureKernelWrappers,
+ m_wettingCurve,
+ m_nonWettingCurve,
+ m_landParam[ipWater],
+ m_phaseIntermediateMinVolFraction,
+ m_killoughCurvatureParamCapPres,
+ m_jerauldParam_a,
+ m_jerauldParam_b,
+ true, // isWettingPhase
+ precomputedScrt_water,
+ precomputedDenomF_water,
+ precomputedShy_water);
+ dpc_dS_final = (pc_pert_final - pc_final) / dS_final;
+ }
+ dPhaseCapPressure_dPhaseVolFrac[ipWater][ipWater] = dpc_dS_final;
+ } // End else block for scanning curves
+ }
+ } // Close if (ipWater >= 0) block
+
+ // 2. Invert non-wetting phase (gas-oil capillary pressure)
+ if (ipGas >= 0 && LvArray::math::abs(phaseCapPressure[ipGas]) > pcEpsilon) {
+ // For pure DRAINAGE or IMBIBITION modes, use direct table lookup (analytical inverse)
+ if (mode == fields::cappres::ModeIndexType::DRAINAGE || mode == fields::cappres::ModeIndexType::IMBIBITION) {
+ integer const arrayIndex = (mode == fields::cappres::ModeIndexType::DRAINAGE) ? 0 : 1;
+ array1d input(1);
+ input[0] = phaseCapPressure[ipGas];
+ auto inputSlice = input.toSliceConst();
+
+ if constexpr (ENABLE_INVERSE_TABLE_DEBUG) {
+ real64 S_before = phaseVolFraction[ipGas];
+ real64 dS_dPc_before = dPhaseCapPressure_dPhaseVolFrac[ipGas][ipGas];
+ std::cout << "[INVERSE_TABLE_DEBUG] Cell2 (gas), mode=" << (mode == fields::cappres::ModeIndexType::DRAINAGE ? "DRAINAGE" : "IMBIBITION")
+ << ", arrayIndex=" << arrayIndex << std::endl;
+ std::cout << " Input Pc=" << input[0] << std::endl;
+ std::cout << " S before inverse table=" << S_before << std::endl;
+ std::cout << " dS_dPc before inverse table=" << dS_dPc_before << std::endl;
+ }
+
+ // Match TableCapillaryPressure exactly: pass derivative pointer directly to inverse table
+ // Note: The inverse table returns dS/dPc, but we store it directly like TableCapillaryPressure does
+ // The local solver will call compute() afterwards to get the correct dPc/dS derivative
+ phaseVolFraction[ipGas] = m_inverseNonWettingIntermediateCapillaryPressureKernelWrappers[arrayIndex].compute(
+ inputSlice, &dPhaseCapPressure_dPhaseVolFrac[ipGas][ipGas]);
+
+ if constexpr (ENABLE_INVERSE_TABLE_DEBUG) {
+ real64 S_after = phaseVolFraction[ipGas];
+ real64 dS_dPc_after = dPhaseCapPressure_dPhaseVolFrac[ipGas][ipGas];
+ std::cout << " S after inverse table=" << S_after << std::endl;
+ std::cout << " dS_dPc after inverse table=" << dS_dPc_after << std::endl;
+ }
+
+ if (ipWater >= 0 && ipOil >= 0) {
+ phaseVolFraction[ipOil] = 1.0 - phaseVolFraction[ipWater] - phaseVolFraction[ipGas];
+ }
+ // Direct lookup complete, skip Newton-Raphson
+ } else {
+ // For scanning curves, use Newton-Raphson
+ // Note: For non-wetting phase, the capillary pressure is typically negative
+ // and the relationship may be inverted (increasing Pc with decreasing S)
+ real64 S_guess = phaseMaxHistoricalVolFraction[ipGas];
+ if (S_guess <= phaseMinHistoricalVolFraction[ipGas] || S_guess < minS || S_guess > maxS) {
+ real64 const Smin = m_nonWettingCurve.imbibitionExtremaPhaseVolFraction;
+ real64 const Smax = m_nonWettingCurve.drainageExtremaPhaseVolFraction;
+ S_guess = 0.5 * (Smin + Smax);
+ S_guess = LvArray::math::max(S_guess, minS);
+ S_guess = LvArray::math::min(S_guess, maxS);
+ }
+
+ // Precompute fixed parameters for scanning curves (these don't change during Newton-Raphson)
+ real64 precomputedScrt_gas = -1.0;
+ real64 precomputedDenomF_gas = 0.0;
+ real64 precomputedShy_gas = -1.0;
+
+ // Only precompute for scanning curves (DRAINAGE_TO_IMBIBITION or IMBIBITION_TO_DRAINAGE)
+ if (mode == fields::cappres::ModeIndexType::DRAINAGE_TO_IMBIBITION ||
+ mode == fields::cappres::ModeIndexType::IMBIBITION_TO_DRAINAGE) {
+ real64 const Smax = m_nonWettingCurve.oppositeBoundPhaseVolFraction;
+ precomputedShy_gas = (phaseMaxHistoricalVolFraction[ipGas] < Smax) ?
+ phaseMaxHistoricalVolFraction[ipGas] : Smax;
+ real64 const E = m_killoughCurvatureParamCapPres;
+
+ if (mode == fields::cappres::ModeIndexType::DRAINAGE_TO_IMBIBITION) {
+ // Compute Scrt for DRAINAGE_TO_IMBIBITION scanning curve
+ real64 Scrt = 0.0;
+ KilloughHysteresis::computeTrappedCriticalPhaseVolFraction(m_nonWettingCurve, precomputedShy_gas,
+ m_landParam[ipGas],
+ m_jerauldParam_a, m_jerauldParam_b,
+ Scrt);
+ precomputedScrt_gas = Scrt;
+ // Compute denomF for DRAINAGE_TO_IMBIBITION: denomF = (1. / (Shy - Scrt + E) - 1. / E)
+ precomputedDenomF_gas = (1. / (precomputedShy_gas - Scrt + E) - 1. / E);
+ }
+ else if (mode == fields::cappres::ModeIndexType::IMBIBITION_TO_DRAINAGE) {
+ // Compute Scrt for IMBIBITION_TO_DRAINAGE scanning curve
+ real64 Scrt = 0.0;
+ KilloughHysteresis::computeTrappedCriticalPhaseVolFraction(m_wettingCurve, precomputedShy_gas,
+ m_landParam[ipGas],
+ m_jerauldParam_a, m_jerauldParam_b,
+ Scrt);
+ precomputedScrt_gas = Scrt;
+ real64 const Sgma = 1. - Scrt - m_phaseIntermediateMinVolFraction;
+ // Compute denomF for IMBIBITION_TO_DRAINAGE: denomF = (1. / (Sgma - Shy + E) - 1. / E)
+ precomputedDenomF_gas = (1. / (Sgma - precomputedShy_gas + E) - 1. / E);
+ }
+ }
+
+ real64 S = S_guess;
+ real64 residual_prev = 1e30;
+
+ for (integer iter = 0; iter < maxIter; ++iter) {
+ real64 pc_computed = this->computeCapillaryPressureForSaturation(
+ S, mode, ipGas,
+ phaseMinHistoricalVolFraction[ipGas],
+ phaseMaxHistoricalVolFraction[ipGas],
+ phaseMode2PeakVolFraction[ipGas],
+ m_nonWettingIntermediateCapillaryPressureKernelWrappers,
+ m_wettingCurve,
+ m_nonWettingCurve,
+ m_landParam[ipGas],
+ m_phaseIntermediateMinVolFraction,
+ m_killoughCurvatureParamCapPres,
+ m_jerauldParam_a,
+ m_jerauldParam_b,
+ false, // isWettingPhase = false
+ precomputedScrt_gas,
+ precomputedDenomF_gas,
+ precomputedShy_gas);
+
+ // For non-wetting phase, capillary pressure is typically multiplied by -1
+ // Check which sign to use based on the input
+ real64 residual = pc_computed - phaseCapPressure[ipGas];
+
+ if (LvArray::math::abs(residual) < tol) {
+ break;
+ }
+
+ // Compute derivative dPc/dS using adaptive finite difference
+ real64 const dS = LvArray::math::max(1e-8, 1e-6 * LvArray::math::max(LvArray::math::abs(S), 1e-6));
+ real64 S_pert = S + dS;
+ if (S_pert > maxS) {
+ S_pert = LvArray::math::max(S - dS, minS);
+ }
+
+ real64 const pc_pert = this->computeCapillaryPressureForSaturation(
+ S_pert, mode, ipGas,
+ phaseMinHistoricalVolFraction[ipGas],
+ phaseMaxHistoricalVolFraction[ipGas],
+ phaseMode2PeakVolFraction[ipGas],
+ m_nonWettingIntermediateCapillaryPressureKernelWrappers,
+ m_wettingCurve,
+ m_nonWettingCurve,
+ m_landParam[ipGas],
+ m_phaseIntermediateMinVolFraction,
+ m_killoughCurvatureParamCapPres,
+ m_jerauldParam_a,
+ m_jerauldParam_b,
+ false, // isWettingPhase = false
+ precomputedScrt_gas,
+ precomputedDenomF_gas,
+ precomputedShy_gas);
+
+ real64 const dpc_dS = (pc_pert - pc_computed) / (S_pert - S);
+
+ if (LvArray::math::abs(dpc_dS) > 1e-12) {
+ real64 const newtonStep = -residual / dpc_dS;
+ real64 const maxStep = 0.1; // Limit step size to prevent overshooting
+ real64 const step = LvArray::math::max(-maxStep, LvArray::math::min(maxStep, newtonStep));
+
+ real64 S_new = S + step;
+
+ // Line search: if residual increases, reduce step
+ if (iter > 0 && LvArray::math::abs(residual) > LvArray::math::abs(residual_prev)) {
+ // Backtrack: use smaller step
+ S_new = S + 0.5 * step;
+ }
+
+ // Clamp to valid range
+ S_new = LvArray::math::max(S_new, minS);
+ S_new = LvArray::math::min(S_new, maxS);
+
+ if (LvArray::math::abs(S_new - S) < 1e-10 &&
+ (S_new <= minS + 1e-10 || S_new >= maxS - 1e-10)) {
+ // Use bisection between current guess and boundary
+ S_new = 0.5 * (S + (S_new <= minS + 1e-10 ? minS : maxS));
+ }
+
+ residual_prev = residual;
+ S = S_new;
+ } else {
+ // If derivative is zero or very small, use bisection
+ real64 const S_low = LvArray::math::max(S - 0.1, minS);
+ real64 const S_high = LvArray::math::min(S + 0.1, maxS);
+ S = 0.5 * (S_low + S_high);
+ S = LvArray::math::max(S, minS);
+ S = LvArray::math::min(S, maxS);
+ }
+ }
+
+ phaseVolFraction[ipGas] = S;
+
+ if (ipWater >= 0 && ipOil >= 0) {
+ phaseVolFraction[ipOil] = 1.0 - phaseVolFraction[ipWater] - phaseVolFraction[ipGas];
+ phaseVolFraction[ipOil] = LvArray::math::max(phaseVolFraction[ipOil], 0.0);
+ phaseVolFraction[ipOil] = LvArray::math::min(phaseVolFraction[ipOil], 1.0);
+ }
+
+ // Compute derivative at final S
+ real64 dpc_dS_final = 0.0;
+ if (mode == fields::cappres::ModeIndexType::DRAINAGE || mode == fields::cappres::ModeIndexType::IMBIBITION) {
+ integer const arrayIndex = (mode == fields::cappres::ModeIndexType::DRAINAGE) ? fields::cappres::ModeIndexType::DRAINAGE : fields::cappres::ModeIndexType::IMBIBITION;
+ m_nonWettingIntermediateCapillaryPressureKernelWrappers[arrayIndex].compute(&S, &dpc_dS_final);
+ } else {
+ // For scanning curves, compute derivative using finite difference
+ real64 const dS_final = 1e-6;
+ real64 const S_pert_final = LvArray::math::min(S + dS_final, maxS);
+ real64 const pc_final = this->computeCapillaryPressureForSaturation(
+ S, mode, ipGas,
+ phaseMinHistoricalVolFraction[ipGas],
+ phaseMaxHistoricalVolFraction[ipGas],
+ phaseMode2PeakVolFraction[ipGas],
+ m_nonWettingIntermediateCapillaryPressureKernelWrappers,
+ m_wettingCurve,
+ m_nonWettingCurve,
+ m_landParam[ipGas],
+ m_phaseIntermediateMinVolFraction,
+ m_killoughCurvatureParamCapPres,
+ m_jerauldParam_a,
+ m_jerauldParam_b,
+ false, // isWettingPhase = false
+ precomputedScrt_gas,
+ precomputedDenomF_gas,
+ precomputedShy_gas);
+ real64 const pc_pert_final = this->computeCapillaryPressureForSaturation(
+ S_pert_final, mode, ipGas,
+ phaseMinHistoricalVolFraction[ipGas],
+ phaseMaxHistoricalVolFraction[ipGas],
+ phaseMode2PeakVolFraction[ipGas],
+ m_nonWettingIntermediateCapillaryPressureKernelWrappers,
+ m_wettingCurve,
+ m_nonWettingCurve,
+ m_landParam[ipGas],
+ m_phaseIntermediateMinVolFraction,
+ m_killoughCurvatureParamCapPres,
+ m_jerauldParam_a,
+ m_jerauldParam_b,
+ false, // isWettingPhase = false
+ precomputedScrt_gas,
+ precomputedDenomF_gas,
+ precomputedShy_gas);
+ dpc_dS_final = (pc_pert_final - pc_final) / dS_final;
+ }
+ dPhaseCapPressure_dPhaseVolFrac[ipGas][ipGas] = dpc_dS_final;
+ } // End else block for scanning curves
+ }
+ }
+ // CRITICAL: Add debug right after three-phase block to see if we reach here
+ if constexpr (ENABLE_COMPUTEINV_PATH_DEBUG) {
+ std::cout << "[COMPUTEINV_PATH] *** AFTER three-phase block, before else-if checks ***" << std::endl;
+ std::cout << "[COMPUTEINV_PATH] ipWater=" << ipWater << ", ipOil=" << ipOil << ", ipGas=" << ipGas << std::endl;
+ std::cout << "[COMPUTEINV_PATH] Three-phase block was SKIPPED (condition was false)" << std::endl;
+ std::cout << "[COMPUTEINV_PATH] About to check if (ipWater < 0)..." << std::endl;
+ }
+ if (ipWater < 0) {
+ if constexpr (ENABLE_COMPUTEINV_PATH_DEBUG) {
+ std::cout << "[COMPUTEINV_PATH] *** TAKING ipWater < 0 path (oil-gas) ***" << std::endl;
+ std::cout << "[COMPUTEINV_PATH] This should NOT happen with ipWater=0!" << std::endl;
+ }
+ if constexpr (ENABLE_COMPUTEINV_PATH_DEBUG) {
+ std::cout << "[COMPUTEINV_PATH] After three-phase block, checking else-if conditions..." << std::endl;
+ std::cout << "[COMPUTEINV_PATH] ipWater=" << ipWater << ", condition (ipWater < 0)=" << (ipWater < 0 ? "TRUE" : "FALSE") << std::endl;
+ std::cout << "[COMPUTEINV_PATH] *** TAKING ipWater < 0 path (oil-gas) ***" << std::endl;
+ std::cout << "[COMPUTEINV_PATH] This should NOT happen with ipWater=0!" << std::endl;
+ }
+ if constexpr (ENABLE_COMPUTEINV_PATH_DEBUG) {
+ std::cout << "[COMPUTEINV_PATH] *** TAKING ipWater < 0 path (oil-gas) ***" << std::endl;
+ std::cout << "[COMPUTEINV_PATH] This should NOT happen with ipWater=0!" << std::endl;
+ }
+ // Two-phase: oil-gas (non-wetting phase)
+ // Similar to above but simpler
+ constexpr real64 pcEpsilon_gas = 1e-10;
+ if (ipGas >= 0 && LvArray::math::abs(phaseCapPressure[ipGas]) > pcEpsilon_gas) {
+ // For pure DRAINAGE or IMBIBITION modes, use direct table lookup (analytical inverse)
+ if (mode == fields::cappres::ModeIndexType::DRAINAGE || mode == fields::cappres::ModeIndexType::IMBIBITION) {
+ integer const arrayIndex = (mode == fields::cappres::ModeIndexType::DRAINAGE) ? 0 : 1;
+ array1d input(1);
+ input[0] = phaseCapPressure[ipGas];
+ auto inputSlice = input.toSliceConst();
+ // Match TableCapillaryPressure: no clamping, let the table handle bounds
+ phaseVolFraction[ipGas] = m_inverseNonWettingIntermediateCapillaryPressureKernelWrappers[arrayIndex].compute(
+ inputSlice, &dPhaseCapPressure_dPhaseVolFrac[ipGas][ipGas]);
+ if (ipOil >= 0) {
+ phaseVolFraction[ipOil] = 1.0 - phaseVolFraction[ipGas];
+ }
+ // Direct lookup complete, skip Newton-Raphson
+ } else {
+ // For scanning curves, use Newton-Raphson
+ real64 S_guess = phaseMaxHistoricalVolFraction[ipGas];
+ if (S_guess < minS || S_guess > maxS) {
+ S_guess = 0.5;
+ }
+
+ // Precompute fixed parameters for scanning curves (these don't change during Newton-Raphson)
+ real64 precomputedScrt_gas = -1.0;
+ real64 precomputedDenomF_gas = 0.0;
+ real64 precomputedShy_gas = -1.0;
+
+ // Only precompute for scanning curves (DRAINAGE_TO_IMBIBITION or IMBIBITION_TO_DRAINAGE)
+ if (mode == fields::cappres::ModeIndexType::DRAINAGE_TO_IMBIBITION ||
+ mode == fields::cappres::ModeIndexType::IMBIBITION_TO_DRAINAGE) {
+ real64 const Smax = m_nonWettingCurve.oppositeBoundPhaseVolFraction;
+ precomputedShy_gas = (phaseMaxHistoricalVolFraction[ipGas] < Smax) ?
+ phaseMaxHistoricalVolFraction[ipGas] : Smax;
+ real64 const E = m_killoughCurvatureParamCapPres;
+
+ if (mode == fields::cappres::ModeIndexType::DRAINAGE_TO_IMBIBITION) {
+ // Compute Scrt for DRAINAGE_TO_IMBIBITION scanning curve
+ real64 Scrt = 0.0;
+ KilloughHysteresis::computeTrappedCriticalPhaseVolFraction(m_nonWettingCurve, precomputedShy_gas,
+ m_landParam[ipGas],
+ m_jerauldParam_a, m_jerauldParam_b,
+ Scrt);
+ precomputedScrt_gas = Scrt;
+ // Compute denomF for DRAINAGE_TO_IMBIBITION: denomF = (1. / (Shy - Scrt + E) - 1. / E)
+ precomputedDenomF_gas = (1. / (precomputedShy_gas - Scrt + E) - 1. / E);
+ }
+ else if (mode == fields::cappres::ModeIndexType::IMBIBITION_TO_DRAINAGE) {
+ // Compute Scrt for IMBIBITION_TO_DRAINAGE scanning curve
+ real64 Scrt = 0.0;
+ KilloughHysteresis::computeTrappedCriticalPhaseVolFraction(m_wettingCurve, precomputedShy_gas,
+ m_landParam[ipGas],
+ m_jerauldParam_a, m_jerauldParam_b,
+ Scrt);
+ precomputedScrt_gas = Scrt;
+ real64 const Sgma = 1. - Scrt - m_phaseIntermediateMinVolFraction;
+ // Compute denomF for IMBIBITION_TO_DRAINAGE: denomF = (1. / (Sgma - Shy + E) - 1. / E)
+ precomputedDenomF_gas = (1. / (Sgma - precomputedShy_gas + E) - 1. / E);
+ }
+ }
+
+ real64 S = S_guess;
+ for (integer iter = 0; iter < maxIter; ++iter) {
+ real64 pc_computed = this->computeCapillaryPressureForSaturation(
+ S, mode, ipGas,
+ phaseMinHistoricalVolFraction[ipGas],
+ phaseMaxHistoricalVolFraction[ipGas],
+ phaseMode2PeakVolFraction[ipGas],
+ m_wettingNonWettingCapillaryPressureKernelWrappers,
+ m_wettingCurve,
+ m_nonWettingCurve,
+ m_landParam[ipGas],
+ m_phaseIntermediateMinVolFraction,
+ m_killoughCurvatureParamCapPres,
+ m_jerauldParam_a,
+ m_jerauldParam_b,
+ false, // isWettingPhase = false
+ precomputedScrt_gas,
+ precomputedDenomF_gas,
+ precomputedShy_gas);
+
+ real64 residual = pc_computed - phaseCapPressure[ipGas];
+
+ if (LvArray::math::abs(residual) < tol) {
+ break;
+ }
+
+ real64 const dS = 1e-6;
+ real64 const S_pert = LvArray::math::min(S + dS, maxS);
+ real64 const pc_pert = this->computeCapillaryPressureForSaturation(
+ S_pert, mode, ipGas,
+ phaseMinHistoricalVolFraction[ipGas],
+ phaseMaxHistoricalVolFraction[ipGas],
+ phaseMode2PeakVolFraction[ipGas],
+ m_wettingNonWettingCapillaryPressureKernelWrappers,
+ m_wettingCurve,
+ m_nonWettingCurve,
+ m_landParam[ipGas],
+ m_phaseIntermediateMinVolFraction,
+ m_killoughCurvatureParamCapPres,
+ m_jerauldParam_a,
+ m_jerauldParam_b,
+ false, // isWettingPhase = false
+ precomputedScrt_gas,
+ precomputedDenomF_gas,
+ precomputedShy_gas);
+
+ real64 const dpc_dS = (pc_pert - pc_computed) / dS;
+
+ if (LvArray::math::abs(dpc_dS) > 1e-12) {
+ S = S - residual / dpc_dS;
+ S = LvArray::math::max(S, minS);
+ S = LvArray::math::min(S, maxS);
+ } else {
+ S = S - residual * 1e-6;
+ S = LvArray::math::max(S, minS);
+ S = LvArray::math::min(S, maxS);
+ }
+ }
+
+ phaseVolFraction[ipGas] = S;
+ if (ipOil >= 0) {
+ phaseVolFraction[ipOil] = 1.0 - S;
+ }
+ } // End else block for scanning curves
+ }
+ }
+ else {
+ if constexpr (ENABLE_COMPUTEINV_PATH_DEBUG) {
+ std::cout << "[COMPUTEINV_PATH] *** REACHED TWO-PHASE path (else block) ***" << std::endl;
+ std::cout << "[COMPUTEINV_PATH] ipWater=" << ipWater << ", ipOil=" << ipOil << ", ipGas=" << ipGas << std::endl;
+ std::cout << "[COMPUTEINV_PATH] About to check if (ipWater >= 0)" << std::endl;
+ std::cout << "[COMPUTEINV_PATH] This is the path that should work for Mode 0 (DRAINAGE)!" << std::endl;
+ }
+ // Two-phase: water-oil or water-gas (wetting phase)
+ // Copy exact pattern from TableCapillaryPressure::computeInv
+ if (ipWater >= 0) {
+ if constexpr (ENABLE_COMPUTEINV_PATH_DEBUG) {
+ std::cout << "[COMPUTEINV_PATH] Two-phase: ipWater >= 0, entering water phase block" << std::endl;
+ std::cout << "[COMPUTEINV_PATH] About to check mode: mode=" << static_cast(mode)
+ << ", DRAINAGE=" << static_cast(fields::cappres::ModeIndexType::DRAINAGE)
+ << ", IMBIBITION=" << static_cast(fields::cappres::ModeIndexType::IMBIBITION) << std::endl;
+ }
+ // For pure DRAINAGE or IMBIBITION modes, use direct table lookup (analytical inverse)
+ // CRITICAL DEBUG: Check if mode condition is actually true
+ bool isDrainage = (mode == fields::cappres::ModeIndexType::DRAINAGE);
+ bool isImbibition = (mode == fields::cappres::ModeIndexType::IMBIBITION);
+ bool isPureMode = (isDrainage || isImbibition);
+ if constexpr (ENABLE_COMPUTEINV_PATH_DEBUG) {
+ std::cout << "[COMPUTEINV_PATH] Mode condition check:" << std::endl;
+ std::cout << "[COMPUTEINV_PATH] mode (as int)=" << static_cast(mode) << std::endl;
+ std::cout << "[COMPUTEINV_PATH] DRAINAGE (as int)=" << static_cast(fields::cappres::ModeIndexType::DRAINAGE) << std::endl;
+ std::cout << "[COMPUTEINV_PATH] IMBIBITION (as int)=" << static_cast(fields::cappres::ModeIndexType::IMBIBITION) << std::endl;
+ std::cout << "[COMPUTEINV_PATH] isDrainage=" << (isDrainage ? "TRUE" : "FALSE") << std::endl;
+ std::cout << "[COMPUTEINV_PATH] isImbibition=" << (isImbibition ? "TRUE" : "FALSE") << std::endl;
+ std::cout << "[COMPUTEINV_PATH] isPureMode (DRAINAGE || IMBIBITION)=" << (isPureMode ? "TRUE" : "FALSE") << std::endl;
+ }
+ if (isPureMode) {
+ if constexpr (ENABLE_COMPUTEINV_PATH_DEBUG) {
+ std::cout << "[COMPUTEINV_PATH] Using pure DRAINAGE/IMBIBITION path (same as working case)" << std::endl;
+ std::cout << "[COMPUTEINV_PATH] mode=" << (mode == fields::cappres::ModeIndexType::DRAINAGE ? "DRAINAGE" : "IMBIBITION") << std::endl;
+ }
+ integer const arrayIndex = (mode == fields::cappres::ModeIndexType::DRAINAGE) ? 0 : 1;
+ if constexpr (ENABLE_COMPUTEINV_PATH_DEBUG) {
+ std::cout << "[COMPUTEINV_PATH] arrayIndex=" << arrayIndex << std::endl;
+ }
+
+ // Match TableCapillaryPressure exactly (but skip the forward table call which passes Pc instead of S)
+ real64 capPresWater = phaseCapPressure[ipWater];
+ if constexpr (ENABLE_COMPUTEINV_PATH_DEBUG) {
+ std::cout << "[COMPUTEINV_PATH] Input Pc=" << capPresWater << std::endl;
+ std::cout << "[COMPUTEINV_PATH] phaseVolFraction[ipWater] before=" << phaseVolFraction[ipWater] << std::endl;
+ }
+ array1d input(1);
+ input[0] = capPresWater;
+ auto inputSlice = input.toSliceConst();
+
+ constexpr bool ENABLE_COMPUTEINV_DEBUG = false;
+ if (ENABLE_COMPUTEINV_DEBUG) {
+ std::cout << "[COMPUTEINV_DEBUG] Two-phase, mode=" << (mode == fields::cappres::ModeIndexType::DRAINAGE ? "DRAINAGE" : "IMBIBITION")
+ << ", arrayIndex=" << arrayIndex << std::endl;
+ std::cout << "[COMPUTEINV_DEBUG] Input Pc=" << capPresWater << std::endl;
+ std::cout << "[COMPUTEINV_DEBUG] Derivative before call=" << dPhaseCapPressure_dPhaseVolFrac[ipWater][ipWater] << std::endl;
+ }
+
+ // Use only the inverse table call (the forward table call in TableCapillaryPressure passes Pc instead of S, which is incorrect)
+ // The derivative array is already zeroed at the start of computeInv
+ // real64 dS_dPc_before = dPhaseCapPressure_dPhaseVolFrac[ipWater][ipWater];
+ real64 S_before_inv = phaseVolFraction[ipWater];
+ if (ENABLE_COMPUTEINV_DEBUG) {
+ std::cout << "[COMPUTEINV_DEBUG] Two-phase DRAINAGE/IMBIBITION: Before inverse table call" << std::endl;
+ std::cout << " S_before=" << S_before_inv << std::endl;
+ std::cout << " Pc_input=" << capPresWater << std::endl;
+ std::cout << " arrayIndex=" << arrayIndex << std::endl;
+ std::cout << " inputSlice[0]=" << inputSlice[0] << std::endl;
+ std::cout << " wrapper.size()=" << m_inverseWettingNonWettingCapillaryPressureKernelWrappers.size() << std::endl;
+ }
+ real64 S_returned = m_inverseWettingNonWettingCapillaryPressureKernelWrappers[arrayIndex].compute(
+ inputSlice,
+ &(dPhaseCapPressure_dPhaseVolFrac)[ipWater][ipWater]);
+ phaseVolFraction[ipWater] = S_returned;
+ real64 dS_dPc_after = dPhaseCapPressure_dPhaseVolFrac[ipWater][ipWater];
+ real64 S_after_inv = phaseVolFraction[ipWater];
+
+ if (ENABLE_COMPUTEINV_DEBUG) {
+ std::cout << "[COMPUTEINV_DEBUG] After inverse table call:" << std::endl;
+ std::cout << " S_returned=" << S_returned << std::endl;
+ std::cout << " S_after=" << S_after_inv << std::endl;
+ std::cout << " dS_dPc_after=" << dS_dPc_after << std::endl;
+ if (std::abs(S_after_inv) < 1e-10 && capPresWater > 3000 && capPresWater < 100000) {
+ std::cout << "[COMPUTEINV_DEBUG] ERROR: Pc=" << capPresWater
+ << " is within valid range but inverse table returned S=0!" << std::endl;
+ std::cout << "[COMPUTEINV_DEBUG] This will cause zero Jacobian and Pc_int won't update!" << std::endl;
+ }
+ }
+
+ // Set non-wetting phase - match TableCapillaryPressure
+ if (ipGas >= 0) {
+ phaseVolFraction[ipGas] = 1.0 - phaseVolFraction[ipWater];
+ } else if (ipOil >= 0) {
+ phaseVolFraction[ipOil] = 1.0 - phaseVolFraction[ipWater];
+ }
+ // Direct lookup complete, skip Newton-Raphson
+ } else {
+ constexpr bool ENABLE_TWOPHASE_SCANNINGINV_DEBUG = false;
+
+ // For scanning curves, use Newton-Raphson
+ real64 S_guess = phaseMaxHistoricalVolFraction[ipWater];
+ if (S_guess < minS || S_guess > maxS) {
+ real64 const Smin = m_wettingCurve.oppositeBoundPhaseVolFraction;
+ real64 const Smax = m_wettingCurve.drainageExtremaPhaseVolFraction;
+ S_guess = 0.5 * (Smin + Smax);
+ S_guess = LvArray::math::max(S_guess, minS);
+ S_guess = LvArray::math::min(S_guess, maxS);
+ }
+ if constexpr (ENABLE_TWOPHASE_SCANNINGINV_DEBUG) {
+ std::cout << "[2P_SCAN_INV] Mode=" << static_cast(mode)
+ << ", Pc_target=" << phaseCapPressure[ipWater]
+ << ", S_guess=" << S_guess
+ << ", S_minHist=" << phaseMinHistoricalVolFraction[ipWater]
+ << ", S_maxHist=" << phaseMaxHistoricalVolFraction[ipWater]
+ << ", Smin_curve=" << m_wettingCurve.oppositeBoundPhaseVolFraction
+ << ", Smax_curve=" << m_wettingCurve.drainageExtremaPhaseVolFraction
+ << std::endl;
+ }
+
+ // Precompute fixed parameters for scanning curves (these don't change during Newton-Raphson)
+ real64 precomputedScrt_water = -1.0;
+ real64 precomputedDenomF_water = 0.0;
+ real64 precomputedShy_water = -1.0;
+
+ // Precompute for scanning curves (Mode 2, Mode 3, or Mode 4)
+ if (mode == fields::cappres::ModeIndexType::DRAINAGE_TO_IMBIBITION ||
+ mode == fields::cappres::ModeIndexType::IMBIBITION_TO_DRAINAGE ||
+ mode == fields::cappres::ModeIndexType::IMBIBITION_TO_DRAINAGE_FROM_SCANNING) {
+ real64 const Smin = m_wettingCurve.oppositeBoundPhaseVolFraction;
+ real64 const Smax = m_wettingCurve.drainageExtremaPhaseVolFraction;
+ real64 const E = m_killoughCurvatureParamCapPres;
+
+ if (mode == fields::cappres::ModeIndexType::DRAINAGE_TO_IMBIBITION) {
+ // DRAINAGE_TO_IMBIBITION: Shy should be minimum historical (where drainage started)
+ precomputedShy_water = (phaseMinHistoricalVolFraction[ipWater] > Smin) ?
+ phaseMinHistoricalVolFraction[ipWater] : Smin;
+
+ // Compute Scrt for DRAINAGE_TO_IMBIBITION scanning curve (wetting phase)
+ real64 Scrt = 0.0;
+ KilloughHysteresis::computeTrappedCriticalPhaseVolFraction(m_wettingCurve, precomputedShy_water,
+ m_landParam[ipWater],
+ m_jerauldParam_a, m_jerauldParam_b,
+ Scrt);
+ precomputedScrt_water = Scrt;
+ // For wetting curve, Scrt IS the max wetting saturation (Swma = Scrt)
+ real64 const Swma = 1. - (1. - Scrt);
+ // Compute denomF for DRAINAGE_TO_IMBIBITION: denomF = (1. / (Swma - Shy + E) - 1. / E)
+ precomputedDenomF_water = (1. / (Swma - precomputedShy_water + E) - 1. / E);
+ }
+ else if (mode == fields::cappres::ModeIndexType::IMBIBITION_TO_DRAINAGE) {
+ // IMBIBITION_TO_DRAINAGE: Shy should be maximum historical (where imbibition started)
+ precomputedShy_water = (phaseMaxHistoricalVolFraction[ipWater] < Smax) ?
+ phaseMaxHistoricalVolFraction[ipWater] : Smax;
+
+ // Compute Scrt for IMBIBITION_TO_DRAINAGE scanning curve (wetting phase)
+ real64 Scrt = 0.0;
+ KilloughHysteresis::computeTrappedCriticalPhaseVolFraction(m_wettingCurve, precomputedShy_water,
+ m_landParam[ipWater],
+ m_jerauldParam_a, m_jerauldParam_b,
+ Scrt);
+ precomputedScrt_water = Scrt;
+ // Compute denomF for IMBIBITION_TO_DRAINAGE: denomF = (1. / (Shy - Scrt + E) - 1. / E)
+ precomputedDenomF_water = (1. / (precomputedShy_water - Scrt + E) - 1. / E);
+ }
+ else if (mode == fields::cappres::ModeIndexType::IMBIBITION_TO_DRAINAGE_FROM_SCANNING) {
+ // Mode 4: IMBIBITION_TO_DRAINAGE_FROM_SCANNING
+ // H (first reversal point) should be minimum historical (where imbibition started)
+ // This is used to compute Scrt for Mode 4
+ real64 const H = (phaseMinHistoricalVolFraction[ipWater] > Smin) ?
+ phaseMinHistoricalVolFraction[ipWater] : Smin;
+
+ // Compute Scrt for Mode 4 based on H (first reversal point)
+ real64 Scrt = 0.0;
+ KilloughHysteresis::computeTrappedCriticalPhaseVolFraction(m_wettingCurve, H,
+ m_landParam[ipWater],
+ m_jerauldParam_a, m_jerauldParam_b,
+ Scrt);
+ precomputedScrt_water = Scrt;
+ // For Mode 4, precomputedShy is not used in the same way, but set it to H for consistency
+ precomputedShy_water = H;
+ // denomF is not used for Mode 4 (it solves quadratic instead), but set to 0 to indicate it's not applicable
+ precomputedDenomF_water = 0.0;
+ }
+ }
+
+ if constexpr (ENABLE_TWOPHASE_SCANNINGINV_DEBUG) {
+ std::cout << "[2P_SCAN_INV] Precomputed: Shy=" << precomputedShy_water
+ << ", Scrt=" << precomputedScrt_water
+ << ", denomF=" << precomputedDenomF_water
+ << ", E=" << m_killoughCurvatureParamCapPres
+ << ", Somin=" << m_phaseIntermediateMinVolFraction
+ << std::endl;
+ // Evaluate Pc at a few points to see the curve shape
+ real64 const S_lo = precomputedShy_water;
+ // For wetting curve, Scrt IS the max wetting saturation (Swma = Scrt)
+ real64 const Swma = 1. - (1. - precomputedScrt_water);
+ std::cout << "[2P_SCAN_INV] Shy=" << S_lo << ", Swma=" << Swma << std::endl;
+ for (int probe = 0; probe <= 5; ++probe) {
+ real64 S_probe = S_lo + (Swma - S_lo) * probe / 5.0;
+ S_probe = LvArray::math::max(minS, LvArray::math::min(maxS, S_probe));
+ real64 pc_probe = this->computeCapillaryPressureForSaturation(
+ S_probe, mode, ipWater,
+ phaseMinHistoricalVolFraction[ipWater],
+ phaseMaxHistoricalVolFraction[ipWater],
+ phaseMode2PeakVolFraction[ipWater],
+ m_wettingNonWettingCapillaryPressureKernelWrappers,
+ m_wettingCurve, m_nonWettingCurve,
+ m_landParam[ipWater],
+ m_phaseIntermediateMinVolFraction,
+ m_killoughCurvatureParamCapPres,
+ m_jerauldParam_a, m_jerauldParam_b,
+ true, precomputedScrt_water, precomputedDenomF_water, precomputedShy_water);
+ std::cout << "[2P_SCAN_INV] S=" << S_probe << " -> Pc=" << pc_probe << std::endl;
+ }
+ }
+
+ real64 S_lo_bracket, S_hi_bracket;
+ real64 Swma_debug = -1.0;
+ bool degenerateBracket = false;
+ if (mode == fields::cappres::ModeIndexType::DRAINAGE_TO_IMBIBITION) {
+ // Mode 2: scanning curve valid in [Shy, Swma]
+ // For wetting curve, Scrt IS the max wetting saturation (Swma = Scrt)
+ real64 const Swma = 1. - (1. - precomputedScrt_water);
+ Swma_debug = Swma;
+ S_lo_bracket = precomputedShy_water;
+ S_hi_bracket = Swma;
+ // Guard: if Swma <= Shy, the scanning curve has no valid range (degenerate case).
+ // Fall back to returning S = Shy directly.
+ if (Swma <= precomputedShy_water + 1e-12) {
+ degenerateBracket = true;
+ }
+ }
+ else if (mode == fields::cappres::ModeIndexType::IMBIBITION_TO_DRAINAGE) {
+ // Mode 3: scanning curve valid in [Scrt_end, Shy]
+ S_lo_bracket = precomputedScrt_water;
+ S_hi_bracket = precomputedShy_water;
+ // Guard: if Shy <= Scrt, degenerate case
+ if (precomputedShy_water <= precomputedScrt_water + 1e-12) {
+ degenerateBracket = true;
+ }
+ }
+ else {
+ array1d input_inv(1);
+ input_inv[0] = phaseCapPressure[ipWater];
+ auto inputSlice_inv = input_inv.toSliceConst();
+
+ real64 dS_dPc_dr_unused = 0.0, dS_dPc_im_unused = 0.0;
+ real64 S_dr = m_inverseWettingNonWettingCapillaryPressureKernelWrappers[ModeIndexType::DRAINAGE].compute(
+ inputSlice_inv, &dS_dPc_dr_unused);
+ real64 S_im = m_inverseWettingNonWettingCapillaryPressureKernelWrappers[ModeIndexType::IMBIBITION].compute(
+ inputSlice_inv, &dS_dPc_im_unused);
+ S_dr = LvArray::math::max(minS, LvArray::math::min(maxS, S_dr));
+ S_im = LvArray::math::max(minS, LvArray::math::min(maxS, S_im));
+ S_lo_bracket = LvArray::math::min(S_dr, S_im);
+ S_hi_bracket = LvArray::math::max(S_dr, S_im);
+ }
+
+ S_lo_bracket = LvArray::math::max(minS, S_lo_bracket);
+ S_hi_bracket = LvArray::math::min(maxS, S_hi_bracket);
+
+ // Ensure S_lo <= S_hi (safety: swap if somehow still inverted after clamping)
+ if (S_lo_bracket > S_hi_bracket) {
+ real64 tmp = S_lo_bracket;
+ S_lo_bracket = S_hi_bracket;
+ S_hi_bracket = tmp;
+ }
+
+ if constexpr (ENABLE_TWOPHASE_SCANNINGINV_DEBUG) {
+ std::cout << "[2P_SCAN_INV] Bracket: S_lo=" << S_lo_bracket
+ << ", S_hi=" << S_hi_bracket
+ << ", Shy=" << precomputedShy_water
+ << ", Swma=" << Swma_debug
+ << ", Scrt=" << precomputedScrt_water
+ << (degenerateBracket ? " DEGENERATE" : "")
+ << std::endl;
+ }
+
+ // Handle degenerate bracket: if Swma <= Shy (Mode 2) or Shy <= Scrt (Mode 3),
+ // the scanning curve has no valid range. Return S = Shy (the departure point).
+ // The Killough formula at S=Shy gives F=0, so Pc = Pc_dr(Shy), which is consistent
+ // with the drainage curve at that point.
+ real64 const Pc_target = phaseCapPressure[ipWater];
+
+ if (degenerateBracket) {
+ // Return S = Shy (departure point).
+ // The Killough formula at Shy gives F=0, so Pc = Pc_dr(Shy), consistent with drainage.
+ phaseVolFraction[ipWater] = precomputedShy_water;
+ if (ipGas >= 0) {
+ phaseVolFraction[ipGas] = 1.0 - precomputedShy_water;
+ } else if (ipOil >= 0) {
+ phaseVolFraction[ipOil] = 1.0 - precomputedShy_water;
+ }
+ // Use drainage table derivative at Shy → dS/dPc = 1/(dPc/dS)
+ real64 dpc_dS_shy = 0.0;
+ m_wettingNonWettingCapillaryPressureKernelWrappers[ModeIndexType::DRAINAGE].compute(
+ &precomputedShy_water, &dpc_dS_shy );
+ if (LvArray::math::abs(dpc_dS_shy) > 1e-14) {
+ dPhaseCapPressure_dPhaseVolFrac[ipWater][ipWater] = 1.0 / dpc_dS_shy;
+ } else {
+ dPhaseCapPressure_dPhaseVolFrac[ipWater][ipWater] = 0.0;
+ }
+ constexpr bool ENABLE_SCANINV_SUMMARY_DEGEN = false;
+ if constexpr (ENABLE_SCANINV_SUMMARY_DEGEN) {
+ std::cout << "[SCAN_INV] mode=" << static_cast(mode)
+ << " Pc_tgt=" << Pc_target
+ << " Shy=" << precomputedShy_water
+ << " S=" << precomputedShy_water
+ << " dS/dPc=" << dPhaseCapPressure_dPhaseVolFrac[ipWater][ipWater]
+ << " DEGENERATE_BRACKET"
+ << std::endl;
+ }
+ }
+
+ if (!degenerateBracket) {
+ // Step 2: Evaluate Pc at bracket endpoints to determine sign of residual
+
+ // Helper macro-like inline evaluation (avoids lambda for GEOS_HOST_DEVICE compatibility)
+ #define EVAL_SCANNING_PC(S_eval) \
+ this->computeCapillaryPressureForSaturation( \
+ (S_eval), mode, ipWater, \
+ phaseMinHistoricalVolFraction[ipWater], \
+ phaseMaxHistoricalVolFraction[ipWater], \
+ phaseMode2PeakVolFraction[ipWater], \
+ m_wettingNonWettingCapillaryPressureKernelWrappers, \
+ m_wettingCurve, m_nonWettingCurve, \
+ m_landParam[ipWater], \
+ m_phaseIntermediateMinVolFraction, \
+ m_killoughCurvatureParamCapPres, \
+ m_jerauldParam_a, m_jerauldParam_b, \
+ true, \
+ precomputedScrt_water, \
+ precomputedDenomF_water, \
+ precomputedShy_water)
+
+ real64 Pc_lo = EVAL_SCANNING_PC(S_lo_bracket);
+ real64 Pc_hi = EVAL_SCANNING_PC(S_hi_bracket);
+ real64 res_lo = Pc_lo - Pc_target;
+ real64 res_hi = Pc_hi - Pc_target;
+
+ if constexpr (ENABLE_TWOPHASE_SCANNINGINV_DEBUG) {
+ std::cout << "[2P_SCAN_INV] Pc_target=" << Pc_target
+ << ", Pc_lo=" << Pc_lo << " (res=" << res_lo << ")"
+ << ", Pc_hi=" << Pc_hi << " (res=" << res_hi << ")" << std::endl;
+ }
+
+ // Early return: if the root is already at a bracket endpoint
+ // (i.e., Pc_target ≈ Pc_scan(Shy) or Pc_target ≈ Pc_scan(Swma)),
+ // return that endpoint directly. This avoids the bisection sign-test
+ // bug where res_lo ≈ 0 causes the update rule `residual * res_lo < 0`
+ // to take the wrong branch (0 * negative = 0, NOT < 0), pushing the
+ // bracket away from the root and converging to garbage.
+ bool skipBisection = false;
+ real64 S = 0.5 * (S_lo_bracket + S_hi_bracket); // default midpoint
+
+ if (LvArray::math::abs(res_lo) < tol) {
+ S = S_lo_bracket;
+ skipBisection = true;
+ if constexpr (ENABLE_TWOPHASE_SCANNINGINV_DEBUG) {
+ std::cout << "[2P_SCAN_INV] Root at lower bracket endpoint: S=" << S
+ << ", res_lo=" << res_lo << std::endl;
+ }
+ } else if (LvArray::math::abs(res_hi) < tol) {
+ S = S_hi_bracket;
+ skipBisection = true;
+ if constexpr (ENABLE_TWOPHASE_SCANNINGINV_DEBUG) {
+ std::cout << "[2P_SCAN_INV] Root at upper bracket endpoint: S=" << S
+ << ", res_hi=" << res_hi << std::endl;
+ }
+ }
+
+ if (!skipBisection) {
+ // Ensure bracket contains the root (res_lo and res_hi should have opposite signs)
+ // If not, Pc_target is at the boundary of the scanning curve.
+ // Clamp to the nearest endpoint (don't expand outside valid range).
+ if (res_lo * res_hi > 0) {
+ // Root not bracketed — Pc_target is at or beyond scanning curve boundary
+ // Return the endpoint with the smallest residual
+ if constexpr (ENABLE_TWOPHASE_SCANNINGINV_DEBUG) {
+ std::cout << "[2P_SCAN_INV] Root not bracketed: res_lo=" << res_lo
+ << ", res_hi=" << res_hi << " — using nearest endpoint" << std::endl;
+ }
+ // Pick the S with smallest |residual| and skip bisection
+ if (LvArray::math::abs(res_lo) < LvArray::math::abs(res_hi)) {
+ S_hi_bracket = S_lo_bracket; // collapse bracket to lo
+ } else {
+ S_lo_bracket = S_hi_bracket; // collapse bracket to hi
+ }
+ }
+
+ // Step 3: Bisection (guaranteed convergence within bracket)
+ S = 0.5 * (S_lo_bracket + S_hi_bracket); // start at midpoint
+ constexpr integer maxBisectIter = 50; // 50 bisections give ~15 digits of precision
+
+ for (integer iter = 0; iter < maxBisectIter; ++iter) {
+ real64 const pc_computed = EVAL_SCANNING_PC(S);
+ real64 const residual = pc_computed - Pc_target;
+
+ if constexpr (ENABLE_TWOPHASE_SCANNINGINV_DEBUG) {
+ if (iter <= 3 || iter == maxBisectIter - 1 || LvArray::math::abs(residual) < tol) {
+ std::cout << "[2P_SCAN_INV] Bisect iter=" << iter
+ << ", S=" << S << ", Pc(S)=" << pc_computed
+ << ", residual=" << residual
+ << ", bracket=[" << S_lo_bracket << "," << S_hi_bracket << "]" << std::endl;
+ }
+ }
+
+ if (LvArray::math::abs(residual) < tol) {
+ if constexpr (ENABLE_TWOPHASE_SCANNINGINV_DEBUG) {
+ std::cout << "[2P_SCAN_INV] Converged at iter=" << iter << ", S=" << S << std::endl;
+ }
+ break;
+ }
+
+ // Update bracket — use <= to correctly handle res_lo ≈ 0
+ // (when the root is very near S_lo, residual * res_lo ≈ 0
+ // and the < test would take the wrong branch)
+ if (residual * res_lo <= 0) {
+ S_hi_bracket = S;
+ res_hi = residual;
+ } else {
+ S_lo_bracket = S;
+ res_lo = residual;
+ }
+
+ // Bisection step
+ S = 0.5 * (S_lo_bracket + S_hi_bracket);
+
+ // Check if bracket is too small
+ if (S_hi_bracket - S_lo_bracket < 1e-12) {
+ break;
+ }
+ }
+ } // end if (!skipBisection)
+
+ #undef EVAL_SCANNING_PC
+
+ if constexpr (ENABLE_TWOPHASE_SCANNINGINV_DEBUG) {
+ real64 pc_final_check = this->computeCapillaryPressureForSaturation(
+ S, mode, ipWater,
+ phaseMinHistoricalVolFraction[ipWater],
+ phaseMaxHistoricalVolFraction[ipWater],
+ phaseMode2PeakVolFraction[ipWater],
+ m_wettingNonWettingCapillaryPressureKernelWrappers,
+ m_wettingCurve, m_nonWettingCurve,
+ m_landParam[ipWater],
+ m_phaseIntermediateMinVolFraction,
+ m_killoughCurvatureParamCapPres,
+ m_jerauldParam_a, m_jerauldParam_b,
+ true, precomputedScrt_water, precomputedDenomF_water, precomputedShy_water);
+ std::cout << "[2P_SCAN_INV] FINAL: S=" << S
+ << ", Pc(S)=" << pc_final_check
+ << ", Pc_target=" << phaseCapPressure[ipWater]
+ << ", error=" << (pc_final_check - phaseCapPressure[ipWater]) << std::endl;
+ }
+
+ phaseVolFraction[ipWater] = S;
+ // Set non-wetting phase
+ if (ipGas >= 0) {
+ phaseVolFraction[ipGas] = 1.0 - S;
+ } else if (ipOil >= 0) {
+ phaseVolFraction[ipOil] = 1.0 - S;
+ }
+
+ // Compute derivative dPc/dS at final S using finite difference
+ // (matching the three-phase scanning curve path)
+ {
+ real64 const dS_final_requested = 1e-6;
+ real64 S_pert_final = S + dS_final_requested;
+ if (S_pert_final > maxS) {
+ S_pert_final = S - dS_final_requested; // backward difference if at upper bound
+ S_pert_final = LvArray::math::max(S_pert_final, minS);
+ }
+ real64 const dS_final = S_pert_final - S;
+ real64 const pc_final = this->computeCapillaryPressureForSaturation(
+ S, mode, ipWater,
+ phaseMinHistoricalVolFraction[ipWater],
+ phaseMaxHistoricalVolFraction[ipWater],
+ phaseMode2PeakVolFraction[ipWater],
+ m_wettingNonWettingCapillaryPressureKernelWrappers,
+ m_wettingCurve,
+ m_nonWettingCurve,
+ m_landParam[ipWater],
+ m_phaseIntermediateMinVolFraction,
+ m_killoughCurvatureParamCapPres,
+ m_jerauldParam_a,
+ m_jerauldParam_b,
+ true, // isWettingPhase
+ precomputedScrt_water,
+ precomputedDenomF_water,
+ precomputedShy_water);
+ real64 const pc_pert_final = this->computeCapillaryPressureForSaturation(
+ S_pert_final, mode, ipWater,
+ phaseMinHistoricalVolFraction[ipWater],
+ phaseMaxHistoricalVolFraction[ipWater],
+ phaseMode2PeakVolFraction[ipWater],
+ m_wettingNonWettingCapillaryPressureKernelWrappers,
+ m_wettingCurve,
+ m_nonWettingCurve,
+ m_landParam[ipWater],
+ m_phaseIntermediateMinVolFraction,
+ m_killoughCurvatureParamCapPres,
+ m_jerauldParam_a,
+ m_jerauldParam_b,
+ true, // isWettingPhase
+ precomputedScrt_water,
+ precomputedDenomF_water,
+ precomputedShy_water);
+ real64 dpc_dS_final = 0.0;
+ if (LvArray::math::abs(dS_final) > 1e-14) {
+ dpc_dS_final = (pc_pert_final - pc_final) / dS_final;
+ }
+ // computeInv must return dS/dPc (not dPc/dS), matching the inverse table convention.
+ // The local solver uses this as: dV/dPc = dV/dS * dS/dPc.
+ real64 const dS_dPc_final = (LvArray::math::abs(dpc_dS_final) > 1e-14) ? (1.0 / dpc_dS_final) : 0.0;
+ dPhaseCapPressure_dPhaseVolFrac[ipWater][ipWater] = dS_dPc_final;
+ if constexpr (ENABLE_TWOPHASE_SCANNINGINV_DEBUG) {
+ std::cout << "[2P_SCAN_INV] dPc/dS=" << dpc_dS_final
+ << ", dS/dPc=" << dS_dPc_final
+ << ", Pc(S)=" << pc_final << ", Pc(S+dS)=" << pc_pert_final << std::endl;
+ }
+ // --- Concise one-line debug for scanning curve computeInv ---
+ constexpr bool ENABLE_SCANINV_SUMMARY = false;
+ if constexpr (ENABLE_SCANINV_SUMMARY) {
+ std::cout << "[SCAN_INV] mode=" << static_cast(mode)
+ << " Pc_tgt=" << phaseCapPressure[ipWater]
+ << " Shy=" << precomputedShy_water
+ << " Swma=" << Swma_debug
+ << " S=" << S
+ << " dS/dPc=" << dS_dPc_final
+ << (skipBisection ? " SKIP_BISECT" : " BISECT")
+ << std::endl;
+ }
+ }
+ } // end if (!degenerateBracket)
+ } // End else block for scanning curves
+ }
+ }
+ }
+
+/// kernel creation
+
+ TableCapillaryPressureHysteresis::KernelWrapper TableCapillaryPressureHysteresis::createKernelWrapper() {
+
+ // we want to make sure that the wrappers are always up-to-date, so we recreate them everytime
+ createAllTableKernelWrappers();
+
+ // Validate that the arrays are properly populated
+ integer const numPhases = m_phaseNames.size();
+ if (numPhases == 2) {
+ GEOS_THROW_IF(m_wettingNonWettingCapillaryPressureKernelWrappers.size() != 2,
+ GEOS_FMT("{}: Expected 2 kernel wrappers for two-phase flow, but got {}. "
+ "This usually means createAllTableKernelWrappers() failed to populate the arrays. "
+ "Check that table functions '{}' and '{}' exist and are properly defined.",
+ getFullName(),
+ m_wettingNonWettingCapillaryPressureKernelWrappers.size(),
+ m_drainageWettingNonWettingCapPresTableName,
+ m_imbibitionWettingNonWettingCapPresTableName.empty() ? m_drainageWettingNonWettingCapPresTableName : m_imbibitionWettingNonWettingCapPresTableName),
+ InputError);
+ } else if (numPhases == 3) {
+ GEOS_THROW_IF(m_wettingIntermediateCapillaryPressureKernelWrappers.size() != 2,
+ GEOS_FMT("{}: Expected 2 wetting-intermediate kernel wrappers for three-phase flow, but got {}",
+ getFullName(),
+ m_wettingIntermediateCapillaryPressureKernelWrappers.size()),
+ InputError);
+ GEOS_THROW_IF(m_nonWettingIntermediateCapillaryPressureKernelWrappers.size() != 2,
+ GEOS_FMT("{}: Expected 2 non-wetting-intermediate kernel wrappers for three-phase flow, but got {}",
+ getFullName(),
+ m_nonWettingIntermediateCapillaryPressureKernelWrappers.size()),
+ InputError);
+ }
+
+ // Validate that m_phaseHasHysteresis is properly initialized
+ GEOS_THROW_IF(m_phaseHasHysteresis.size() != 2,
+ GEOS_FMT("{}: m_phaseHasHysteresis must have size 2, but got {}",
+ getFullName(),
+ m_phaseHasHysteresis.size()),
+ InputError);
+
+ // Validate that the historical volume fraction arrays have been resized
+ // These arrays should be resized by resizeFields() before the KernelWrapper is used
+ // If they're empty, it means resizeFields() hasn't been called yet
+ GEOS_THROW_IF(m_phaseMaxHistoricalVolFraction.size(0) == 0 || m_phaseMaxHistoricalVolFraction.size(1) == 0,
+ GEOS_FMT("{}: phaseMaxHistoricalVolFraction array has not been resized (size=[{}, {}]). "
+ "This usually means resizeFields() has not been called yet. "
+ "The arrays must be resized before the KernelWrapper can be used.",
+ getFullName(),
+ m_phaseMaxHistoricalVolFraction.size(0),
+ m_phaseMaxHistoricalVolFraction.size(1)),
+ InputError);
+ GEOS_THROW_IF(m_phaseMinHistoricalVolFraction.size(0) == 0 || m_phaseMinHistoricalVolFraction.size(1) == 0,
+ GEOS_FMT("{}: phaseMinHistoricalVolFraction array has not been resized (size=[{}, {}]). "
+ "This usually means resizeFields() has not been called yet. "
+ "The arrays must be resized before the KernelWrapper can be used.",
+ getFullName(),
+ m_phaseMinHistoricalVolFraction.size(0),
+ m_phaseMinHistoricalVolFraction.size(1)),
+ InputError);
+ GEOS_THROW_IF(m_phaseMode2PeakVolFraction.size(0) == 0 || m_phaseMode2PeakVolFraction.size(1) == 0,
+ GEOS_FMT("{}: phaseMode2PeakVolFraction array has not been resized (size=[{}, {}]). "
+ "This usually means resizeFields() has not been called yet. "
+ "The arrays must be resized before the KernelWrapper can be used.",
+ getFullName(),
+ m_phaseMode2PeakVolFraction.size(0),
+ m_phaseMode2PeakVolFraction.size(1)),
+ InputError);
+
+ // then we create the actual TableRelativePermeabilityHysteresis::KernelWrapper
+ return KernelWrapper(m_wettingNonWettingCapillaryPressureKernelWrappers,
+ m_inverseWettingNonWettingCapillaryPressureKernelWrappers,
+ m_wettingIntermediateCapillaryPressureKernelWrappers,
+ m_inverseWettingIntermediateCapillaryPressureKernelWrappers,
+ m_nonWettingIntermediateCapillaryPressureKernelWrappers,
+ m_inverseNonWettingIntermediateCapillaryPressureKernelWrappers,
+ m_phaseHasHysteresis,
+ m_landParam,
+ m_jerauldParam_a,
+ m_jerauldParam_b,
+ m_killoughCurvatureParamCapPres,
+ m_phaseIntermediateMinVolFraction,
+ m_wettingCurve,
+ m_nonWettingCurve,
+ m_phaseMinHistoricalVolFraction,
+ m_phaseMaxHistoricalVolFraction,
+ m_phaseMode2PeakVolFraction,
+ m_phaseTypes,
+ m_phaseOrder,
+ m_mode,
+ m_phaseTrappedVolFrac,
+ m_phaseCapPressure,
+ m_dPhaseCapPressure_dPhaseVolFrac);
+ }
+
+ void TableCapillaryPressureHysteresis::createAllTableKernelWrappers() {
+ using TPP = ThreePhasePairPhaseType;
+
+ FunctionManager const &functionManager = FunctionManager::getInstance();
+
+ integer const numPhases = m_phaseNames.size();
+
+ // Ensure m_phaseHasHysteresis is initialized before accessing it
+ // This can happen if createKernelWrapper is called before postProcessInput
+ if (m_phaseHasHysteresis.size() == 0) {
+ m_phaseHasHysteresis.resize(2);
+ if (numPhases == 2) {
+ m_phaseHasHysteresis[TPP::INTERMEDIATE_WETTING] =
+ (m_imbibitionWettingNonWettingCapPresTableName.empty() ||
+ m_imbibitionWettingNonWettingCapPresTableName == m_drainageWettingNonWettingCapPresTableName)
+ ? 0 : 1;
+ m_phaseHasHysteresis[TPP::INTERMEDIATE_NONWETTING] = m_phaseHasHysteresis[TPP::INTERMEDIATE_WETTING];
+ } else if (numPhases == 3) {
+ m_phaseHasHysteresis[TPP::INTERMEDIATE_WETTING] =
+ (m_imbibitionWettingIntermediateCapPresTableName.empty() ||
+ m_imbibitionWettingIntermediateCapPresTableName == m_drainageWettingIntermediateCapPresTableName)
+ ? 0 : 1;
+ m_phaseHasHysteresis[TPP::INTERMEDIATE_NONWETTING] =
+ (m_imbibitionNonWettingIntermediateCapPresTableName.empty() ||
+ m_imbibitionNonWettingIntermediateCapPresTableName == m_drainageNonWettingIntermediateCapPresTableName)
+ ? 0 : 1;
+ }
+ }
+
+ // we want to make sure that the wrappers are always up-to-date, so we recreate them everytime
+
+ m_wettingNonWettingCapillaryPressureKernelWrappers.clear();
+ m_inverseWettingNonWettingCapillaryPressureKernelWrappers.clear();
+ m_wettingIntermediateCapillaryPressureKernelWrappers.clear();
+ m_inverseWettingIntermediateCapillaryPressureKernelWrappers.clear();
+ m_nonWettingIntermediateCapillaryPressureKernelWrappers.clear();
+ m_inverseNonWettingIntermediateCapillaryPressureKernelWrappers.clear();
+ m_inverseTables.clear();
+
+ if (numPhases == 2) {
+ GEOS_THROW_IF(m_drainageWettingNonWettingCapPresTableName.empty(),
+ GEOS_FMT("{}: drainageWettingNonWettingCapPressureTableName is empty for two-phase flow",
+ getFullName()),
+ InputError);
+
+ GEOS_THROW_IF(!functionManager.hasGroup(m_drainageWettingNonWettingCapPresTableName),
+ GEOS_FMT("{}: the table function named '{}' could not be found",
+ getFullName(),
+ m_drainageWettingNonWettingCapPresTableName),
+ InputError);
+ TableFunction const &drainageCapPresTable = functionManager.getGroup(
+ m_drainageWettingNonWettingCapPresTableName);
+ m_wettingNonWettingCapillaryPressureKernelWrappers.emplace_back(
+ drainageCapPresTable.createKernelWrapper());
+
+
+ string const &imbibitionTableName = m_phaseHasHysteresis[TPP::INTERMEDIATE_WETTING]
+ ? m_imbibitionWettingNonWettingCapPresTableName
+ : m_drainageWettingNonWettingCapPresTableName;
+ GEOS_THROW_IF(imbibitionTableName.empty(),
+ GEOS_FMT("{}: imbibition table name is empty for two-phase flow",
+ getFullName()),
+ InputError);
+ GEOS_THROW_IF(!functionManager.hasGroup(imbibitionTableName),
+ GEOS_FMT("{}: the table function named '{}' could not be found",
+ getFullName(),
+ imbibitionTableName),
+ InputError);
+ TableFunction const &imbibitionWettingCapPresTable = functionManager.getGroup(
+ imbibitionTableName);
+ m_wettingNonWettingCapillaryPressureKernelWrappers.emplace_back(
+ imbibitionWettingCapPresTable.createKernelWrapper());
+
+ GEOS_THROW_IF(m_wettingNonWettingCapillaryPressureKernelWrappers.size() != 2,
+ GEOS_FMT("{}: Expected 2 kernel wrappers after creation, but got {}",
+ getFullName(),
+ m_wettingNonWettingCapillaryPressureKernelWrappers.size()),
+ InputError);
+
+ // Create inverse tables for drainage and imbibition (for direct lookup in computeInv)
+ // Inverse for drainage (index 0)
+ auto const &drainageSatArrayView = drainageCapPresTable.getCoordinates()[0];
+ auto const &drainagePcArrayView = drainageCapPresTable.getValues();
+ std::vector drainageSatVec(drainageSatArrayView.size());
+ std::vector drainagePcVec(drainagePcArrayView.size());
+ std::copy(drainageSatArrayView.begin(), drainageSatArrayView.end(), drainageSatVec.begin());
+ std::copy(drainagePcArrayView.begin(), drainagePcArrayView.end(), drainagePcVec.begin());
+ // Reverse both arrays (if original Pc is decreasing in S)
+ std::reverse(drainagePcVec.begin(), drainagePcVec.end());
+ std::reverse(drainageSatVec.begin(), drainageSatVec.end());
+
+
+ auto inverseDrainageTable = std::make_shared("inverseDrainageCapPres", this);
+ real64_array invDrainagePcVec(drainagePcVec.size());
+ real64_array invDrainageSatVec(drainageSatVec.size());
+ std::copy(drainagePcVec.begin(), drainagePcVec.end(), invDrainagePcVec.data());
+ std::copy(drainageSatVec.begin(), drainageSatVec.end(), invDrainageSatVec.data());
+ array1d drainageCoordinates;
+ drainageCoordinates.emplace_back(std::move(invDrainagePcVec));
+ std::vector dimUnits = {units::Unknown};
+ inverseDrainageTable->setTableCoordinates(drainageCoordinates, dimUnits);
+ inverseDrainageTable->setTableValues(std::move(invDrainageSatVec), units::Unknown);
+ inverseDrainageTable->setInterpolationMethod(TableFunction::InterpolationType::Linear);
+ m_inverseWettingNonWettingCapillaryPressureKernelWrappers.emplace_back(
+ inverseDrainageTable->createKernelWrapper());
+ m_inverseTables.emplace_back(std::move(inverseDrainageTable));
+
+ // Inverse for imbibition (index 1)
+ auto const &imbibitionSatArrayView = imbibitionWettingCapPresTable.getCoordinates()[0];
+ auto const &imbibitionPcArrayView = imbibitionWettingCapPresTable.getValues();
+ std::vector imbibitionSatVec(imbibitionSatArrayView.size());
+ std::vector imbibitionPcVec(imbibitionPcArrayView.size());
+ std::copy(imbibitionSatArrayView.begin(), imbibitionSatArrayView.end(), imbibitionSatVec.begin());
+ std::copy(imbibitionPcArrayView.begin(), imbibitionPcArrayView.end(), imbibitionPcVec.begin());
+ // Reverse both arrays (if original Pc is decreasing in S)
+ std::reverse(imbibitionPcVec.begin(), imbibitionPcVec.end());
+ std::reverse(imbibitionSatVec.begin(), imbibitionSatVec.end());
+
+ auto inverseImbibitionTable = std::make_shared("inverseImbibitionCapPres", this);
+ real64_array invImbibitionPcVec(imbibitionPcVec.size());
+ real64_array invImbibitionSatVec(imbibitionSatVec.size());
+ std::copy(imbibitionPcVec.begin(), imbibitionPcVec.end(), invImbibitionPcVec.data());
+ std::copy(imbibitionSatVec.begin(), imbibitionSatVec.end(), invImbibitionSatVec.data());
+ array1d imbibitionCoordinates;
+ imbibitionCoordinates.emplace_back(std::move(invImbibitionPcVec));
+ inverseImbibitionTable->setTableCoordinates(imbibitionCoordinates, dimUnits);
+ inverseImbibitionTable->setTableValues(std::move(invImbibitionSatVec), units::Unknown);
+ inverseImbibitionTable->setInterpolationMethod(TableFunction::InterpolationType::Linear);
+ m_inverseWettingNonWettingCapillaryPressureKernelWrappers.emplace_back(
+ inverseImbibitionTable->createKernelWrapper());
+ m_inverseTables.emplace_back(std::move(inverseImbibitionTable));
+
+ } else if (numPhases == 3) {
+ TableFunction const &drainageWICapPres = functionManager.getGroup(
+ m_drainageWettingIntermediateCapPresTableName);
+ m_wettingIntermediateCapillaryPressureKernelWrappers.emplace_back(
+ drainageWICapPres.createKernelWrapper());
+
+ TableFunction const &drainageNWICapPres = functionManager.getGroup(
+ m_drainageNonWettingIntermediateCapPresTableName);
+ m_nonWettingIntermediateCapillaryPressureKernelWrappers.emplace_back(
+ drainageNWICapPres.createKernelWrapper());
+
+ TableFunction const &imbibitionWICapPres = m_phaseHasHysteresis[TPP::INTERMEDIATE_WETTING]
+ ? functionManager.getGroup(
+ m_imbibitionWettingIntermediateCapPresTableName)
+ : functionManager.getGroup(
+ m_drainageWettingIntermediateCapPresTableName);
+ m_wettingIntermediateCapillaryPressureKernelWrappers.emplace_back(
+ imbibitionWICapPres.createKernelWrapper());
+
+ TableFunction const &imbibitionNWICapPres = m_phaseHasHysteresis[TPP::INTERMEDIATE_NONWETTING]
+ ? functionManager.getGroup(
+ m_imbibitionNonWettingIntermediateCapPresTableName)
+ : functionManager.getGroup(
+ m_drainageNonWettingIntermediateCapPresTableName);
+ m_nonWettingIntermediateCapillaryPressureKernelWrappers.emplace_back(
+ imbibitionNWICapPres.createKernelWrapper());
+
+ // Create inverse tables for 3-phase (wetting-intermediate and non-wetting-intermediate)
+ // Inverse for wetting-intermediate drainage (index 0)
+ auto const &wiDrainageSatArrayView = drainageWICapPres.getCoordinates()[0];
+ auto const &wiDrainagePcArrayView = drainageWICapPres.getValues();
+ std::vector wiDrainageSatVec(wiDrainageSatArrayView.size());
+ std::vector wiDrainagePcVec(wiDrainagePcArrayView.size());
+ std::copy(wiDrainageSatArrayView.begin(), wiDrainageSatArrayView.end(), wiDrainageSatVec.begin());
+ std::copy(wiDrainagePcArrayView.begin(), wiDrainagePcArrayView.end(), wiDrainagePcVec.begin());
+ std::reverse(wiDrainagePcVec.begin(), wiDrainagePcVec.end());
+ std::reverse(wiDrainageSatVec.begin(), wiDrainageSatVec.end());
+
+ auto inverseWIDrainageTable = std::make_shared("inverseWIDrainageCapPres", this);
+ real64_array invWIDrainagePcVec(wiDrainagePcVec.size());
+ real64_array invWIDrainageSatVec(wiDrainageSatVec.size());
+ std::copy(wiDrainagePcVec.begin(), wiDrainagePcVec.end(), invWIDrainagePcVec.data());
+ std::copy(wiDrainageSatVec.begin(), wiDrainageSatVec.end(), invWIDrainageSatVec.data());
+ array1d wiDrainageCoordinates;
+ wiDrainageCoordinates.emplace_back(std::move(invWIDrainagePcVec));
+ std::vector dimUnits = {units::Unknown};
+ inverseWIDrainageTable->setTableCoordinates(wiDrainageCoordinates, dimUnits);
+ inverseWIDrainageTable->setTableValues(std::move(invWIDrainageSatVec), units::Unknown);
+ inverseWIDrainageTable->setInterpolationMethod(TableFunction::InterpolationType::Linear);
+ m_inverseWettingIntermediateCapillaryPressureKernelWrappers.emplace_back(
+ inverseWIDrainageTable->createKernelWrapper());
+ m_inverseTables.emplace_back(std::move(inverseWIDrainageTable));
+
+ // Inverse for wetting-intermediate imbibition (index 1)
+ auto const &wiImbibitionSatArrayView = imbibitionWICapPres.getCoordinates()[0];
+ auto const &wiImbibitionPcArrayView = imbibitionWICapPres.getValues();
+ std::vector wiImbibitionSatVec(wiImbibitionSatArrayView.size());
+ std::vector wiImbibitionPcVec(wiImbibitionPcArrayView.size());
+ std::copy(wiImbibitionSatArrayView.begin(), wiImbibitionSatArrayView.end(), wiImbibitionSatVec.begin());
+ std::copy(wiImbibitionPcArrayView.begin(), wiImbibitionPcArrayView.end(), wiImbibitionPcVec.begin());
+ std::reverse(wiImbibitionPcVec.begin(), wiImbibitionPcVec.end());
+ std::reverse(wiImbibitionSatVec.begin(), wiImbibitionSatVec.end());
+
+ auto inverseWIImbibitionTable = std::make_shared("inverseWIImbibitionCapPres", this);
+ real64_array invWIImbibitionPcVec(wiImbibitionPcVec.size());
+ real64_array invWIImbibitionSatVec(wiImbibitionSatVec.size());
+ std::copy(wiImbibitionPcVec.begin(), wiImbibitionPcVec.end(), invWIImbibitionPcVec.data());
+ std::copy(wiImbibitionSatVec.begin(), wiImbibitionSatVec.end(), invWIImbibitionSatVec.data());
+ array1d wiImbibitionCoordinates;
+ wiImbibitionCoordinates.emplace_back(std::move(invWIImbibitionPcVec));
+ inverseWIImbibitionTable->setTableCoordinates(wiImbibitionCoordinates, dimUnits);
+ inverseWIImbibitionTable->setTableValues(std::move(invWIImbibitionSatVec), units::Unknown);
+ inverseWIImbibitionTable->setInterpolationMethod(TableFunction::InterpolationType::Linear);
+ m_inverseWettingIntermediateCapillaryPressureKernelWrappers.emplace_back(
+ inverseWIImbibitionTable->createKernelWrapper());
+ m_inverseTables.emplace_back(std::move(inverseWIImbibitionTable));
+
+ // Inverse for non-wetting-intermediate drainage (index 0)
+ auto const &nwiDrainageSatArrayView = drainageNWICapPres.getCoordinates()[0];
+ auto const &nwiDrainagePcArrayView = drainageNWICapPres.getValues();
+ std::vector nwiDrainageSatVec(nwiDrainageSatArrayView.size());
+ std::vector nwiDrainagePcVec(nwiDrainagePcArrayView.size());
+ std::copy(nwiDrainageSatArrayView.begin(), nwiDrainageSatArrayView.end(), nwiDrainageSatVec.begin());
+ std::copy(nwiDrainagePcArrayView.begin(), nwiDrainagePcArrayView.end(), nwiDrainagePcVec.begin());
+ std::reverse(nwiDrainagePcVec.begin(), nwiDrainagePcVec.end());
+ std::reverse(nwiDrainageSatVec.begin(), nwiDrainageSatVec.end());
+
+ auto inverseNWIDrainageTable = std::make_shared("inverseNWIDrainageCapPres", this);
+ real64_array invNWIDrainagePcVec(nwiDrainagePcVec.size());
+ real64_array invNWIDrainageSatVec(nwiDrainageSatVec.size());
+ std::copy(nwiDrainagePcVec.begin(), nwiDrainagePcVec.end(), invNWIDrainagePcVec.data());
+ std::copy(nwiDrainageSatVec.begin(), nwiDrainageSatVec.end(), invNWIDrainageSatVec.data());
+ array1d nwiDrainageCoordinates;
+ nwiDrainageCoordinates.emplace_back(std::move(invNWIDrainagePcVec));
+ inverseNWIDrainageTable->setTableCoordinates(nwiDrainageCoordinates, dimUnits);
+ inverseNWIDrainageTable->setTableValues(std::move(invNWIDrainageSatVec), units::Unknown);
+ inverseNWIDrainageTable->setInterpolationMethod(TableFunction::InterpolationType::Linear);
+ m_inverseNonWettingIntermediateCapillaryPressureKernelWrappers.emplace_back(
+ inverseNWIDrainageTable->createKernelWrapper());
+ m_inverseTables.emplace_back(std::move(inverseNWIDrainageTable));
+
+ // Inverse for non-wetting-intermediate imbibition (index 1)
+ auto const &nwiImbibitionSatArrayView = imbibitionNWICapPres.getCoordinates()[0];
+ auto const &nwiImbibitionPcArrayView = imbibitionNWICapPres.getValues();
+ std::vector nwiImbibitionSatVec(nwiImbibitionSatArrayView.size());
+ std::vector nwiImbibitionPcVec(nwiImbibitionPcArrayView.size());
+ std::copy(nwiImbibitionSatArrayView.begin(), nwiImbibitionSatArrayView.end(), nwiImbibitionSatVec.begin());
+ std::copy(nwiImbibitionPcArrayView.begin(), nwiImbibitionPcArrayView.end(), nwiImbibitionPcVec.begin());
+ std::reverse(nwiImbibitionPcVec.begin(), nwiImbibitionPcVec.end());
+ std::reverse(nwiImbibitionSatVec.begin(), nwiImbibitionSatVec.end());
+
+ auto inverseNWIImbibitionTable = std::make_shared("inverseNWIImbibitionCapPres", this);
+ real64_array invNWIImbibitionPcVec(nwiImbibitionPcVec.size());
+ real64_array invNWIImbibitionSatVec(nwiImbibitionSatVec.size());
+ std::copy(nwiImbibitionPcVec.begin(), nwiImbibitionPcVec.end(), invNWIImbibitionPcVec.data());
+ std::copy(nwiImbibitionSatVec.begin(), nwiImbibitionSatVec.end(), invNWIImbibitionSatVec.data());
+ array1d nwiImbibitionCoordinates;
+ nwiImbibitionCoordinates.emplace_back(std::move(invNWIImbibitionPcVec));
+ inverseNWIImbibitionTable->setTableCoordinates(nwiImbibitionCoordinates, dimUnits);
+ inverseNWIImbibitionTable->setTableValues(std::move(invNWIImbibitionSatVec), units::Unknown);
+ inverseNWIImbibitionTable->setInterpolationMethod(TableFunction::InterpolationType::Linear);
+ m_inverseNonWettingIntermediateCapillaryPressureKernelWrappers.emplace_back(
+ inverseNWIImbibitionTable->createKernelWrapper());
+ m_inverseTables.emplace_back(std::move(inverseNWIImbibitionTable));
+ }
+
+ }
+
+///kernel ctor
+ TableCapillaryPressureHysteresis::KernelWrapper::KernelWrapper(
+ arrayView1d const &wettingNonWettingCapillaryPressureKernelWrappers,
+ arrayView1d const &inverseWettingNonWettingCapillaryPressureKernelWrappers,
+ arrayView1d const &wettingIntermediateCapillaryPressureKernelWrappers,
+ arrayView1d const &inverseWettingIntermediateCapillaryPressureKernelWrappers,
+ arrayView1d const &nonWettingIntermediateCapillaryPressureKernelWrappers,
+ arrayView1d const &inverseNonWettingIntermediateCapillaryPressureKernelWrappers,
+ const arrayView1d &phaseHasHysteresis,
+ const arrayView1d &landParam,
+ const real64 & jerauldParam_a,
+ const real64 & jerauldParam_b,
+ const real64 & killoughCurvaturePcParam,
+ const geos::real64 &phaseIntermediateMinVolFraction,
+ const KilloughHysteresis::HysteresisCurve &wettingCurve,
+ const KilloughHysteresis::HysteresisCurve &nonWettingCurve,
+ const arrayView2d &phaseMinHistoricalVolFraction,
+ const arrayView2d &phaseMaxHistoricalVolFraction,
+ arrayView2d &phaseMode2PeakVolFraction,
+ arrayView1d const &phaseTypes,
+ arrayView1d const &phaseOrder,
+ arrayView1d const &mode,
+ arrayView3d const &phaseTrapped,
+ const arrayView3d &phaseCapPressure,
+ const arrayView4d &dPhaseCapPressure_dPhaseVolFrac)
+ :
+ CapillaryPressureBaseUpdate(phaseTypes,
+ phaseOrder,
+ phaseTrapped,
+ phaseCapPressure,
+ dPhaseCapPressure_dPhaseVolFrac),
+ m_wettingNonWettingCapillaryPressureKernelWrappers(wettingNonWettingCapillaryPressureKernelWrappers),
+ m_inverseWettingNonWettingCapillaryPressureKernelWrappers(inverseWettingNonWettingCapillaryPressureKernelWrappers),
+ m_wettingIntermediateCapillaryPressureKernelWrappers(
+ wettingIntermediateCapillaryPressureKernelWrappers),
+ m_inverseWettingIntermediateCapillaryPressureKernelWrappers(inverseWettingIntermediateCapillaryPressureKernelWrappers),
+ m_nonWettingIntermediateCapillaryPressureKernelWrappers(
+ nonWettingIntermediateCapillaryPressureKernelWrappers),
+ m_inverseNonWettingIntermediateCapillaryPressureKernelWrappers(inverseNonWettingIntermediateCapillaryPressureKernelWrappers),
+ m_phaseHasHysteresis(phaseHasHysteresis),
+ m_landParam(landParam),
+ m_jerauldParam_a(jerauldParam_a),
+ m_jerauldParam_b(jerauldParam_b),
+ m_killoughCurvatureParamCapPres(killoughCurvaturePcParam),
+ m_phaseIntermediateMinVolFraction(phaseIntermediateMinVolFraction),
+ m_wettingCurve(wettingCurve),
+ m_nonWettingCurve(nonWettingCurve),
+ m_phaseMinHistoricalVolFraction(phaseMinHistoricalVolFraction),
+ m_phaseMaxHistoricalVolFraction(phaseMaxHistoricalVolFraction),
+ m_phaseMode2PeakVolFraction(phaseMode2PeakVolFraction),
+ m_mode(mode) {}
+
+ REGISTER_CATALOG_ENTRY(ConstitutiveBase, TableCapillaryPressureHysteresis, std::string const &, Group * const)
+
+ } // namespace constitutive
+} // namespace geos
diff --git a/src/coreComponents/constitutive/capillaryPressure/TableCapillaryPressureHysteresis.hpp b/src/coreComponents/constitutive/capillaryPressure/TableCapillaryPressureHysteresis.hpp
new file mode 100644
index 00000000000..c68c46c4194
--- /dev/null
+++ b/src/coreComponents/constitutive/capillaryPressure/TableCapillaryPressureHysteresis.hpp
@@ -0,0 +1,739 @@
+/*
+ * ------------------------------------------------------------------------------------------------------------
+ * SPDX-License-Identifier: LGPL-2.1-only
+ *
+ * Copyright (c) 2018-2020 Lawrence Livermore National Security LLC
+ * Copyright (c) 2018-2020 The Board of Trustees of the Leland Stanford Junior University
+ * Copyright (c) 2018-2020 TotalEnergies
+ * Copyright (c) 2019- GEOSX Contributors
+ * All rights reserved
+ *
+ * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details.
+ * ------------------------------------------------------------------------------------------------------------
+ */
+
+
+#ifndef GEOS_CONSTITUTIVE_TABLECAPILLARYPRESSUREHYSTERESIS_HPP
+#define GEOS_CONSTITUTIVE_TABLECAPILLARYPRESSUREHYSTERESIS_HPP
+
+#include "constitutive/capillaryPressure/CapillaryPressureBase.hpp"
+#include "functions/TableFunction.hpp"
+
+#include "constitutive/KilloughHysteresis.hpp"
+#include "CapillaryPressureFields.hpp"
+#include "common/DataLayouts.hpp"
+
+namespace geos {
+
+
+ namespace constitutive {
+
+ class TableCapillaryPressureHysteresis : public CapillaryPressureBase {
+// /// useful constant
+// static constexpr real64 CAP_INF = 1e9;
+//// std::numeric_limits< real64 >::max();
+// static constexpr real64 CAP_INF_DERIV = 1e9;
+//// std::numeric_limits< real64 >::max();
+
+ typedef fields::cappres::ModeIndexType ModeIndexType;
+
+ public:
+
+ /// order of the phase properties for three-phase flow
+ struct ThreePhasePairPhaseType {
+ enum : integer {
+ INTERMEDIATE_WETTING = 0, ///< index for intermediate-wetting
+ INTERMEDIATE_NONWETTING = 1 ///< index for intermediate-non-wetting
+ };
+ };
+
+
+ TableCapillaryPressureHysteresis(std::string const &name,
+ dataRepository::Group *const parent);
+
+ static std::string catalogName() { return "TableCapillaryPressureHysteresis"; }
+
+ virtual string getCatalogName() const override { return catalogName(); }
+
+ ///Kernel
+ class KernelWrapper final : public CapillaryPressureBaseUpdate {
+ public:
+
+ KernelWrapper(
+ arrayView1d const &wettingNonWettingCapillaryPressureKernelWrappers,
+ arrayView1d const &inverseWettingNonWettingCapillaryPressureKernelWrappers,
+ arrayView1d const &wettingIntermediateCapillaryPressureKernelWrappers,
+ arrayView1d const &inverseWettingIntermediateCapillaryPressureKernelWrappers,
+ arrayView1d const &nonWettingIntermediateCapillaryPressureKernelWrappers,
+ arrayView1d const &inverseNonWettingIntermediateCapillaryPressureKernelWrappers,
+ arrayView1d const &phaseHasHysteresis,
+ arrayView1d const &landParam,
+ real64 const &jerauldParam_a,
+ real64 const &jerauldParam_b,
+ real64 const &killoughCurvaturePcParameter,
+ real64 const &phaseIntermediateMinVolFraction,
+ KilloughHysteresis::HysteresisCurve const &wettingCurve,
+ KilloughHysteresis::HysteresisCurve const &nonWettingCurve,
+ arrayView2d const &phaseMinHistoricalVolFraction,
+ arrayView2d const &phaseMaxHistoricalVolFraction,
+ arrayView2d &phaseMode2PeakVolFraction,
+ arrayView1d const &phaseTypes,
+ arrayView1d const &phaseOrder,
+ arrayView1d const &mode,
+ arrayView3d const &phaseTrappedVolFrac,
+ arrayView3d const &phaseCapPressure,
+ arrayView4d const &dPhaseCapPressure_dPhaseVolFrac);
+
+ //actual workers
+ GEOS_HOST_DEVICE
+ void computeBoundCapillaryPressure(TableFunction::KernelWrapper const &drainageCapPressureWrapper,
+ real64 const &phaseVolFraction,
+ real64 &phaseCapPressure,
+ real64 &dPhaseCapPressure_dPhaseVolFrac) const;
+
+ GEOS_HOST_DEVICE
+ void
+ computeImbibitionWettingCapillaryPressure(
+ const arrayView1d &wettingKernelWapper,
+ const KilloughHysteresis::HysteresisCurve &wettingCurve,
+ const KilloughHysteresis::HysteresisCurve &nonWettingCurve,
+ const geos::real64 &landParam,
+ const geos::real64 &phaseVolFraction,
+ const geos::real64 &phaseMinHistoricalVolFraction,
+ const geos::real64 &phaseMaxHistoricalVolFraction,
+ const geos::real64 &phaseMode2PeakVolFraction,
+ geos::real64 &phaseTrappedVolFrac,
+ geos::real64 &phaseCapPressure,
+ geos::real64 &dPhaseCapPressure_dPhaseVolFrac,
+ const ModeIndexType &mode) const;
+
+ //two phase flow overload
+ GEOS_HOST_DEVICE
+ void
+ computeImbibitionWettingCapillaryPressure(
+ const arrayView1d &wettingKernelWapper,
+ const KilloughHysteresis::HysteresisCurve &wettingCurve,
+ const geos::real64 &landParam,
+ const geos::real64 &phaseVolFraction,
+ const geos::real64 &phaseMinHistoricalVolFraction,
+ const geos::real64 &phaseMaxHistoricalVolFraction,
+ const geos::real64 &phaseMode2PeakVolFraction,
+ geos::real64 &phaseTrappedVolFrac,
+ geos::real64 &phaseCapPressure,
+ geos::real64 &dPhaseCapPressure_dPhaseVolFrac,
+ const ModeIndexType &mode) const;
+
+ GEOS_HOST_DEVICE
+ void
+ computeImbibitionNonWettingCapillaryPressure(
+ const arrayView1d &nonWettingKernelWrapper,
+ const KilloughHysteresis::HysteresisCurve &nonWettingCurve,
+ const KilloughHysteresis::HysteresisCurve &wettingCurve,
+ const geos::real64 &landParam,
+ const geos::real64 &phaseVolFraction,
+ const geos::real64 &phaseMaxHistoricalVolFraction,
+ geos::real64 &phaseTrappedVolFrac,
+ geos::real64 &phaseCapPressure,
+ geos::real64 &dPhaseCapPressure_dPhaseVolFrac,
+ const ModeIndexType &mode) const;
+
+ //2phase flow overload
+ GEOS_HOST_DEVICE
+ void
+ computeImbibitionNonWettingCapillaryPressure(
+ const arrayView1d &nonWettingKernelWrapper,
+ const KilloughHysteresis::HysteresisCurve &nonWettingCurve,
+ const geos::real64 &landParam,
+ const geos::real64 &phaseVolFraction,
+ const geos::real64 &phaseMaxHistoricalVolFraction,
+ geos::real64 &phaseTrappedVolFrac,
+ geos::real64 &phaseCapPressure,
+ geos::real64 &dPhaseCapPressure_dPhaseVolFrac,
+ const ModeIndexType &mode) const;
+
+
+
+ //wrapper call wrt number of phase
+ GEOS_HOST_DEVICE
+ void computeTwoPhaseWetting(integer const ipWetting,
+ integer const ipNonWetting,
+ arraySlice1d const &phaseVolFraction,
+ arraySlice1d const &phaseMaxHistoricalVolFraction,
+ arraySlice1d const &phaseMinHistoricalVolFraction,
+ arraySlice1d const &phaseTrappedVolFrac,
+ arraySlice1d const &phaseCapPressure,
+ arraySlice2d const &dPhaseCapPressure_dPhaseVolFrac,
+ ModeIndexType &mode,
+ arraySlice1d &phaseMode2PeakVolFraction) const;
+
+
+ GEOS_HOST_DEVICE
+ void computeTwoPhaseNonWetting(integer const ipWetting,
+ integer const ipNonWetting,
+ arraySlice1d const &phaseVolFraction,
+ arraySlice1d const &phaseMaxHistoricalVolFraction,
+ arraySlice1d const &phaseMinHistoricalVolFraction,
+ arraySlice1d const &phaseTrappedVolFrac,
+ arraySlice1d const &phaseCapPressure,
+ arraySlice2d const &dPhaseCapPressure_dPhaseVolFrac,
+ ModeIndexType &mode) const;
+
+ GEOS_HOST_DEVICE
+ void computeThreePhase(integer const ipWetting,
+ integer const ipInter,
+ integer const ipNonWetting,
+ arraySlice1d const &phaseVolFraction,
+ arraySlice1d const &phaseMaxHistoricalVolFraction,
+ arraySlice1d const &phaseMinHistoricalVolFraction,
+ arraySlice1d const &phaseTrappedVolFrac,
+ arraySlice1d const &phaseCapPressure,
+ arraySlice2d const &dPhaseCapPressure_dPhaseVolFrac,
+ ModeIndexType &mode,
+ arraySlice1d &phaseMode2PeakVolFraction) const;
+
+ //uppermost call-wrappers
+ // Standard 3-argument compute method for compatibility with InverseCapillaryPressure
+ GEOS_HOST_DEVICE
+ void compute(arraySlice1d const &phaseVolFraction,
+ arraySlice1d const &phaseCapPressure,
+ arraySlice2d const &dPhaseCapPressure_dPhaseVolFrac) const;
+
+ GEOS_HOST_DEVICE
+ virtual void compute(arraySlice1d const &phaseVolFraction,
+ arraySlice1d const &phaseMaxHistoricalVolFraction,
+ arraySlice1d const &phaseMinHistoricalVolFraction,
+ arraySlice1d const &phaseTrappedVolFrac,
+ arraySlice1d const &phaseCapPressure,
+ arraySlice2d const &dPhaseCapPressure_dPhaseVolFrac,
+ ModeIndexType &mode,
+ arraySlice1d &phaseMode2PeakVolFraction) const;
+
+ GEOS_HOST_DEVICE
+ virtual void update(localIndex const k,
+ localIndex const q,
+ arraySlice1d const &phaseVolFraction) const override;
+
+ /**
+ * @brief Compute phase volume fraction from capillary pressure (inverse operation).
+ * @param phaseVolFraction [out] Computed phase volume fractions
+ * @param phaseMaxHistoricalVolFraction [in] Maximum historical phase volume fractions
+ * @param phaseMinHistoricalVolFraction [in] Minimum historical phase volume fractions
+ * @param phaseTrappedVolFrac [in] Trapped phase volume fractions
+ * @param phaseCapPressure [in] Target capillary pressures (input)
+ * @param dPhaseCapPressure_dPhaseVolFrac [out] Derivatives of capillary pressure w.r.t. phase volume fraction
+ * @param mode [in] Hysteresis mode (DRAINAGE, IMBIBITION, DRAINAGE_TO_IMBIBITION, IMBIBITION_TO_DRAINAGE)
+ *
+ * Uses Newton-Raphson iteration to invert the capillary pressure function.
+ */
+ GEOS_HOST_DEVICE
+ void computeInv(arraySlice1d const &phaseVolFraction,
+ arraySlice1d const &phaseMaxHistoricalVolFraction,
+ arraySlice1d const &phaseMinHistoricalVolFraction,
+ arraySlice1d const &phaseMode2PeakVolFraction,
+ arraySlice1d const &phaseTrappedVolFrac,
+ arraySlice1d const &phaseCapPressure,
+ arraySlice2d const &dPhaseCapPressure_dPhaseVolFrac,
+ fields::cappres::ModeIndexType const &mode) const;
+
+ /**
+ * @brief Evaluate the raw drainage or imbibition table at a given saturation,
+ * bypassing the hysteresis mode-transition logic in compute().
+ * @param tableIdx Table index (0 = DRAINAGE, 1 = IMBIBITION)
+ * @param phaseVolFraction Phase volume fraction to evaluate at
+ * @param phaseCapPressure [out] Capillary pressure from the table
+ * @param dPhaseCapPressure_dPhaseVolFrac [out] Derivative of Pc w.r.t. S
+ */
+ GEOS_HOST_DEVICE
+ void computeRawTablePc( integer const tableIdx,
+ real64 const & phaseVolFraction,
+ real64 & phaseCapPressure,
+ real64 & dPhaseCapPressure_dPhaseVolFrac ) const
+ {
+ computeBoundCapillaryPressure(
+ m_wettingNonWettingCapillaryPressureKernelWrappers[tableIdx],
+ phaseVolFraction, phaseCapPressure, dPhaseCapPressure_dPhaseVolFrac );
+ }
+
+ /**
+ * @brief Compute the actual Pc range [Pc_lo, Pc_hi] for a scanning curve.
+ *
+ * For Mode 2 (DRAINAGE_TO_IMBIBITION):
+ * Pc_hi = Pc_drainage(Shy) (departure point)
+ * Pc_lo = Pc_imbibition(Swma) (endpoint, where F=1)
+ * For Mode 3 (IMBIBITION_TO_DRAINAGE):
+ * Pc_lo = Pc_imbibition(Shy) (departure point)
+ * Pc_hi = Pc_drainage(Scrt) (endpoint, where F=1)
+ *
+ * @param phaseMinHistVolFrac Minimum historical volume fraction (Shy for Mode 2)
+ * @param phaseMaxHistVolFrac Maximum historical volume fraction (Shy for Mode 3)
+ * @param mode Hysteresis mode
+ * @param Pc_lo [out] Lower bound of scanning curve Pc range
+ * @param Pc_hi [out] Upper bound of scanning curve Pc range
+ */
+ GEOS_HOST_DEVICE
+ void computeScanningCurvePcRange( real64 const phaseMinHistVolFrac,
+ real64 const phaseMaxHistVolFrac,
+ ModeIndexType const mode,
+ real64 & Pc_lo,
+ real64 & Pc_hi ) const
+ {
+ integer const ipWater = 0;
+ real64 dPc_dummy;
+
+ if( mode == ModeIndexType::DRAINAGE_TO_IMBIBITION )
+ {
+ // Shy = min historical saturation (departure from drainage)
+ real64 const Smin_curve = m_wettingCurve.oppositeBoundPhaseVolFraction;
+ real64 const Shy = LvArray::math::max( phaseMinHistVolFrac, Smin_curve );
+
+ // Compute Scrt (trapped saturation) from Land model
+ real64 Scrt = 0.0;
+ KilloughHysteresis::computeTrappedCriticalPhaseVolFraction(
+ m_wettingCurve, Shy, m_landParam[ipWater],
+ m_jerauldParam_a, m_jerauldParam_b, Scrt );
+
+ // For wetting phase, Swma = Scrt (= 1 - (1 - Scrt))
+ real64 const Swma = Scrt;
+
+ // Pc_hi = Pc_drainage(Shy) — departure point, highest Pc on scanning curve
+ computeRawTablePc( 0 /* drainage */, Shy, Pc_hi, dPc_dummy );
+
+ // Pc_lo = Pc_imbibition(Swma) — endpoint where F=1, lowest Pc on scanning curve
+ computeRawTablePc( 1 /* imbibition */, Swma, Pc_lo, dPc_dummy );
+ }
+ else if( mode == ModeIndexType::IMBIBITION_TO_DRAINAGE ||
+ mode == ModeIndexType::IMBIBITION_TO_DRAINAGE_FROM_SCANNING )
+ {
+ // Shy = max historical saturation (departure from imbibition)
+ real64 const Smax_curve = m_wettingCurve.drainageExtremaPhaseVolFraction;
+ real64 const Shy = LvArray::math::min( phaseMaxHistVolFrac, Smax_curve );
+
+ // Compute Scrt
+ real64 Scrt = 0.0;
+ KilloughHysteresis::computeTrappedCriticalPhaseVolFraction(
+ m_wettingCurve, Shy, m_landParam[ipWater],
+ m_jerauldParam_a, m_jerauldParam_b, Scrt );
+
+ // Pc_lo = Pc_imbibition(Shy) — departure point, lowest Pc on scanning curve
+ computeRawTablePc( 1 /* imbibition */, Shy, Pc_lo, dPc_dummy );
+
+ // Pc_hi = Pc_drainage(Scrt) — endpoint where F=1, highest Pc on scanning curve
+ computeRawTablePc( 0 /* drainage */, Scrt, Pc_hi, dPc_dummy );
+ }
+ else
+ {
+ // Fallback: use raw table full range
+ computeRawTablePc( 0 /* drainage */, 0.0, Pc_hi, dPc_dummy );
+ computeRawTablePc( 1 /* imbibition */, 1.0, Pc_lo, dPc_dummy );
+ }
+
+ // Ensure Pc_lo <= Pc_hi
+ if( Pc_lo > Pc_hi )
+ {
+ real64 tmp = Pc_lo;
+ Pc_lo = Pc_hi;
+ Pc_hi = tmp;
+ }
+ }
+
+ private:
+
+ /**
+ * @brief Helper function to compute Pc(S) for given S, mode, and historical values.
+ * @param S Phase volume fraction
+ * @param mode Hysteresis mode
+ * @param ipPhase Phase index
+ * @param phaseMinHistoricalVolFraction Minimum historical volume fraction
+ * @param phaseMaxHistoricalVolFraction Maximum historical volume fraction
+ * @param capPresKernelWrappers Capillary pressure kernel wrappers
+ * @param wettingCurve Wetting curve data
+ * @param nonWettingCurve Non-wetting curve data
+ * @param landParam Land parameter
+ * @param phaseIntermediateMinVolFraction Intermediate phase minimum volume fraction
+ * @param killoughCurvatureParam Killough curvature parameter
+ * @param jerauldParam_a Jerauld parameter a
+ * @param jerauldParam_b Jerauld parameter b
+ * @param isWettingPhase Whether this is the wetting phase
+ * @param precomputedScrt Precomputed trapped critical saturation (optional, use -1.0 to compute)
+ * @param precomputedDenomF Precomputed denominator for F calculation (optional, use 0.0 to compute)
+ * @param precomputedShy Precomputed historical saturation Shy (optional, use -1.0 to compute)
+ * @return Computed capillary pressure
+ *
+ * Used for Newton-Raphson inversion in computeInv.
+ * If precomputed values are provided (precomputedScrt >= 0, precomputedDenomF != 0, precomputedShy >= 0),
+ * they will be used instead of recomputing them.
+ */
+ GEOS_HOST_DEVICE
+ real64 computeCapillaryPressureForSaturation(
+ real64 const S,
+ fields::cappres::ModeIndexType const &mode,
+ integer const ipPhase,
+ real64 const &phaseMinHistoricalVolFraction,
+ real64 const &phaseMaxHistoricalVolFraction,
+ real64 const &phaseMode2PeakVolFraction,
+ arrayView1d const &capPresKernelWrappers,
+ KilloughHysteresis::HysteresisCurve const &wettingCurve,
+ KilloughHysteresis::HysteresisCurve const &nonWettingCurve,
+ real64 const &landParam,
+ real64 const &phaseIntermediateMinVolFraction,
+ real64 const &killoughCurvatureParam,
+ real64 const &jerauldParam_a,
+ real64 const &jerauldParam_b,
+ bool const isWettingPhase,
+ real64 const precomputedScrt = -1.0,
+ real64 const precomputedDenomF = 0.0,
+ real64 const precomputedShy = -1.0) const;
+
+ static constexpr real64 flowReversalBuffer = KilloughHysteresis::flowReversalBuffer;
+// ModeIndexType& m_mode;
+
+ //2p
+ arrayView1d const m_wettingNonWettingCapillaryPressureKernelWrappers;
+ arrayView1d const m_inverseWettingNonWettingCapillaryPressureKernelWrappers;
+ //3p
+ arrayView1d const m_wettingIntermediateCapillaryPressureKernelWrappers;
+ arrayView1d const m_inverseWettingIntermediateCapillaryPressureKernelWrappers;
+ arrayView1d const m_nonWettingIntermediateCapillaryPressureKernelWrappers;
+ arrayView1d const m_inverseNonWettingIntermediateCapillaryPressureKernelWrappers;
+
+ ///Land Coeff
+ arrayView1d m_phaseHasHysteresis;
+ arrayView1d m_landParam;
+
+ /// Parameter a introduced by Jerauld in the Land model
+ const real64 m_jerauldParam_a;
+
+ /// Parameter b introduced by Jerauld in the Land model
+ const real64 m_jerauldParam_b;
+
+ /// Curvature parameter in Killough wetting phase hysteresis (enpoints curvatures)
+ const real64 m_killoughCurvatureParamCapPres;
+
+ /// needed in 3p-wetting hysteresis as we need to get the max accessible pore space
+ real64 const m_phaseIntermediateMinVolFraction;
+
+ KilloughHysteresis::HysteresisCurve const m_wettingCurve;
+ KilloughHysteresis::HysteresisCurve const m_nonWettingCurve;
+
+ /// Minimum historical phase volume fraction for each phase
+ arrayView2d m_phaseMinHistoricalVolFraction;
+
+ /// Maximum historical phase volume fraction for each phase
+ arrayView2d m_phaseMaxHistoricalVolFraction;
+
+ /// Peak saturation reached during Mode 2 (DRAINAGE_TO_IMBIBITION) for each phase (mutable for updates)
+ arrayView2d m_phaseMode2PeakVolFraction;
+
+ // Drainage / Imbibition flags cellwise
+ arrayView1d m_mode;
+
+ };
+
+ /**
+ * @brief Create an update kernel wrapper.
+ * @return the wrapper
+ */
+ KernelWrapper createKernelWrapper();
+
+ //might need it to be virtual one level higher --> from Killough/Hysteresis common class
+ virtual void saveConvergedPhaseVolFractionState(
+ arrayView2d const &phaseVolFraction) const override;
+
+
+ struct viewKeyStruct : CapillaryPressureBase::viewKeyStruct {
+
+
+ ///Land Coeff
+ static constexpr char const *landParameterString() { return "landParameter"; }
+
+ ///flag
+ static constexpr char const *phaseHasHysteresisString() { return "phaseHasHysteresis"; }
+
+ ///and packed curves data struct
+ static constexpr char const *wettingCurveString() { return "wettingCurve"; };
+
+ static constexpr char const *nonWettingCurveString() { return "nonWettingCurve"; };
+
+
+ ///tables and assoc. wrappers
+ //2phase
+ static constexpr char const *
+ drainageWettingNonWettingCapPresTableNameString() { return "drainageWettingNonWettingCapPressureTableName"; }
+
+ static constexpr char const *
+ imbibitionWettingNonWettingCapPresTableNameString() { return "imbibitionWettingNonWettingCapPressureTableName"; }
+
+ //3phase
+ static constexpr char const *
+ drainageWettingIntermediateCapPresTableNameString() { return "drainageWettingIntermediateCapPressureTableName"; }
+
+ static constexpr char const *
+ drainageNonWettingIntermediateCapPresTableNameString() { return "drainageNonWettingIntermediateCapPressureTableName"; }
+
+ static constexpr char const *
+ imbibitionWettingIntermediateCapPresTableNameString() { return "imbibitionWettingIntermediateCapPressureTableName"; }
+
+ static constexpr char const *
+ imbibitionNonWettingIntermediateCapPresTableNameString() { return "imbibitionNonWettingIntermediateCapPressureTableName"; }
+
+ static constexpr char const *
+ wettingNonWettingCapillaryPressureKernelWrappersString() { return "wettingNonWettingCapillaryPressureKernelWrappers"; }
+
+ static constexpr char const *
+ wettingIntermediateCapillaryPressureKernelWrappersString() { return "wettingIntermediateCapillaryPressureKernelWrappers"; }
+
+ static constexpr char const *
+ nonWettingIntermediateCapillaryPressureKernelWrappersString() { return "nonWettingIntermediateCapillaryPressureKernelWrappers"; }
+
+ //misc
+ static constexpr char const *
+ phaseIntermediateMinVolFractionString() { return "phaseIntermediateMinVolFraction"; }
+ //to decide wheter drainage/drainage to imbibition or imbibition/imbibition to drainage
+ };
+
+
+ private:
+ virtual void postProcessInput();
+
+ virtual void initializePreSubGroups() override;
+
+ void resizeFields(localIndex const size,
+ localIndex const numPts) override;
+
+
+ /**
+ * @brief Create all the table kernel wrappers needed for the simulation (for all the phases present)
+ */
+ void createAllTableKernelWrappers();
+
+ /**
+ * @brief Compute the Land coefficient for the wetting and non-wetting phases
+ */
+ void computeLandCoefficient();
+
+ ///data members
+
+
+ //TODO impl
+// array1d< integer > m_tCurveOption;
+
+ KilloughHysteresis::HysteresisCurve m_wettingCurve;
+ KilloughHysteresis::HysteresisCurve m_nonWettingCurve;
+
+ ///tables
+ //2p
+ string m_drainageWettingNonWettingCapPresTableName;
+ string m_imbibitionWettingNonWettingCapPresTableName;
+ //3p
+ string m_drainageWettingIntermediateCapPresTableName;
+ string m_drainageNonWettingIntermediateCapPresTableName;
+ string m_imbibitionWettingIntermediateCapPresTableName;
+ string m_imbibitionNonWettingIntermediateCapPresTableName;
+ // kernel wrappers
+ /// Imbibition kernel wrappers for relative permeabilities in the following order:
+ /// 0- drainage
+ /// 1- imbibition (cf. struct ModeIndexType)
+ //2p
+ array1d m_wettingNonWettingCapillaryPressureKernelWrappers;
+ array1d m_inverseWettingNonWettingCapillaryPressureKernelWrappers;
+ //3p
+ array1d m_wettingIntermediateCapillaryPressureKernelWrappers;
+ array1d m_inverseWettingIntermediateCapillaryPressureKernelWrappers;
+ array1d m_nonWettingIntermediateCapillaryPressureKernelWrappers;
+ array1d m_inverseNonWettingIntermediateCapillaryPressureKernelWrappers;
+
+ // Store inverse tables to keep them alive
+ std::vector> m_inverseTables;
+
+
+ /// Flag to specify whether the phase has hysteresis or not (deduced from table input)
+ array1d m_phaseHasHysteresis;
+
+ /// Trapping parameter from the Land model (typically called C)
+ array1d m_landParam;
+
+ /// Parameter a introduced by Jerauld in the Land model
+ real64 m_jerauldParam_a;
+
+ /// Parameter b introduced by Jerauld in the Land model
+ real64 m_jerauldParam_b;
+
+ /// Curvature parameter in Killough wetting phase hysteresis (Scanning curves curvatures)
+ real64 m_killoughCurvatureParamCapPres;
+
+ /// Cell-wise status imbibition, imbibitioon_to_drainage, ... etc
+ array1d m_mode;
+
+ // Max historical saturations
+ /// Minimum historical phase volume fraction for each phase
+ array2d m_phaseMinHistoricalVolFraction;
+
+ /// Maximum historical phase volume fraction for each phase
+ array2d m_phaseMaxHistoricalVolFraction;
+
+ /// Peak saturation reached during Mode 2 (DRAINAGE_TO_IMBIBITION) for each phase
+ array2d m_phaseMode2PeakVolFraction;
+
+ //needed in hysteresis of wetting phase
+ real64 m_phaseIntermediateMinVolFraction;
+
+ };
+
+ // Standard 3-argument compute method for compatibility with InverseCapillaryPressure
+ // Uses zero/default values for historical fractions (no hysteresis in inverse computation)
+ GEOS_HOST_DEVICE
+ inline void TableCapillaryPressureHysteresis::KernelWrapper::compute(
+ arraySlice1d const &phaseVolFraction,
+ arraySlice1d const &phaseCapPressure,
+ arraySlice2d const &dPhaseCapPressure_dPhaseVolFrac
+ ) const {
+ // Create temporary arrays for historical fractions and trapped fraction
+ // Initialize to zero/default values since inverse computation doesn't use hysteresis
+ constexpr integer MAX_NUM_PHASES = CapillaryPressureBase::MAX_NUM_PHASES;
+ real64 phaseMaxHistoricalVolFraction[MAX_NUM_PHASES]{};
+ real64 phaseMinHistoricalVolFraction[MAX_NUM_PHASES]{};
+ real64 phaseTrappedVolFrac[MAX_NUM_PHASES]{};
+ real64 phaseMode2PeakVolFraction[MAX_NUM_PHASES]{};
+ ModeIndexType mode = ModeIndexType::DRAINAGE; // Default to drainage mode
+
+ // Create ArrayView from stack arrays, then get slices
+ integer const numPhases = LvArray::integerConversion< integer >( phaseVolFraction.size() );
+ localIndex dims[1] = { numPhases };
+ localIndex strides[1] = { 1 };
+
+ arraySlice1d< real64 const, compflow::USD_PHASE - 1 > const phaseMaxHistSlice(
+ phaseMaxHistoricalVolFraction, dims, strides );
+ arraySlice1d< real64 const, compflow::USD_PHASE - 1 > const phaseMinHistSlice(
+ phaseMinHistoricalVolFraction, dims, strides );
+ arraySlice1d< real64, cappres::USD_CAPPRES - 2 > phaseTrappedSlice(
+ phaseTrappedVolFrac, dims, strides );
+ arraySlice1d< real64, compflow::USD_PHASE - 1 > phaseMode2PeakSlice(
+ phaseMode2PeakVolFraction, dims, strides );
+
+ // Call the full compute method
+ compute( phaseVolFraction,
+ phaseMaxHistSlice,
+ phaseMinHistSlice,
+ phaseTrappedSlice,
+ phaseCapPressure,
+ dPhaseCapPressure_dPhaseVolFrac,
+ mode,
+ phaseMode2PeakSlice );
+ }
+
+ GEOS_HOST_DEVICE
+ inline void TableCapillaryPressureHysteresis::KernelWrapper::compute(
+ arraySlice1d const &phaseVolFraction,
+ arraySlice1d const &phaseMaxHistoricalVolFraction,
+ arraySlice1d const &phaseMinHistoricalVolFraction,
+ arraySlice1d const &phaseTrappedVolFrac,
+ arraySlice1d const &phaseCapPressure,
+ arraySlice2d const &dPhaseCapPressure_dPhaseVolFrac,
+ ModeIndexType &mode,
+ arraySlice1d &phaseMode2PeakVolFraction
+ ) const {
+ // Early return if m_phaseOrder is empty or input arrays are empty
+ if( m_phaseOrder.size() == 0 ||
+ phaseVolFraction.size() == 0 ||
+ phaseCapPressure.size() == 0 )
+ {
+ return;
+ }
+
+ LvArray::forValuesInSlice(dPhaseCapPressure_dPhaseVolFrac, [](real64 &val) { val = 0.0; });
+
+ using PT = CapillaryPressureBase::PhaseType;
+ // Check bounds before accessing m_phaseOrder
+ integer const ipWater = ( PT::WATER < m_phaseOrder.size() ) ? m_phaseOrder[PT::WATER] : -1;
+ integer const ipOil = ( PT::OIL < m_phaseOrder.size() ) ? m_phaseOrder[PT::OIL] : -1;
+ integer const ipGas = ( PT::GAS < m_phaseOrder.size() ) ? m_phaseOrder[PT::GAS] : -1;
+
+ if (ipWater >= 0 && ipOil >= 0 && ipGas >= 0) {
+ computeThreePhase(ipWater, // wetting
+ ipOil, // intermediate
+ ipGas, // non-wetting
+ phaseVolFraction,
+ phaseMaxHistoricalVolFraction,
+ phaseMinHistoricalVolFraction,
+ phaseTrappedVolFrac,
+ phaseCapPressure,
+ dPhaseCapPressure_dPhaseVolFrac,
+ mode,
+ phaseMode2PeakVolFraction);
+
+ } else if (ipWater < 0) {
+ computeTwoPhaseNonWetting(ipOil, // leading
+ ipGas, // deduced
+ phaseVolFraction,
+ phaseMaxHistoricalVolFraction,
+ phaseMinHistoricalVolFraction,
+ phaseTrappedVolFrac,
+ phaseCapPressure,
+ dPhaseCapPressure_dPhaseVolFrac,
+ mode);
+ } else if (ipOil < 0) {
+ computeTwoPhaseWetting(ipWater, // leading
+ ipGas, // deduced
+ phaseVolFraction,
+ phaseMaxHistoricalVolFraction,
+ phaseMinHistoricalVolFraction,
+ phaseTrappedVolFrac,
+ phaseCapPressure,
+ dPhaseCapPressure_dPhaseVolFrac,
+ mode,
+ phaseMode2PeakVolFraction);
+ } else if (ipGas < 0) {
+ computeTwoPhaseWetting(ipWater, //leading
+ ipOil, //deduced
+ phaseVolFraction,
+ phaseMaxHistoricalVolFraction,
+ phaseMinHistoricalVolFraction,
+ phaseTrappedVolFrac,
+ phaseCapPressure,
+ dPhaseCapPressure_dPhaseVolFrac,
+ mode,
+ phaseMode2PeakVolFraction);
+ }
+
+
+ }
+
+ GEOS_HOST_DEVICE
+ inline void TableCapillaryPressureHysteresis::KernelWrapper::update(const geos::localIndex k,
+ const geos::localIndex q,
+ const arraySlice1d &phaseVolFraction) const {
+ // Create a reference to the Mode 2 peak slice for this element
+ // m_phaseMode2PeakVolFraction uses compflow::LAYOUT_PHASE, so use compflow::USD_PHASE - 1
+ arraySlice1d phaseMode2PeakSlice = m_phaseMode2PeakVolFraction[k];
+ compute(phaseVolFraction,
+ m_phaseMaxHistoricalVolFraction[k],
+ m_phaseMinHistoricalVolFraction[k],
+ m_phaseTrappedVolFrac[k][q],
+ m_phaseCapPressure[k][q],
+ m_dPhaseCapPressure_dPhaseVolFrac[k][q],
+ m_mode[k],
+ phaseMode2PeakSlice);
+ }
+
+
+ } //constitutive
+} // geos
+
+#endif //GEOS_CONSTITUTIVE_TABLECAPILLARYPRESSUREHYSTERESIS_HPP
diff --git a/src/coreComponents/constitutive/capillaryPressure/VanGenuchtenCapillaryPressure.cpp b/src/coreComponents/constitutive/capillaryPressure/VanGenuchtenCapillaryPressure.cpp
index b324366f4f2..17a0a74e426 100644
--- a/src/coreComponents/constitutive/capillaryPressure/VanGenuchtenCapillaryPressure.cpp
+++ b/src/coreComponents/constitutive/capillaryPressure/VanGenuchtenCapillaryPressure.cpp
@@ -122,6 +122,7 @@ VanGenuchtenCapillaryPressure::createKernelWrapper()
m_volFracScale,
m_phaseTypes,
m_phaseOrder,
+ m_phaseTrappedVolFrac,
m_phaseCapPressure,
m_dPhaseCapPressure_dPhaseVolFrac );
}
diff --git a/src/coreComponents/constitutive/capillaryPressure/VanGenuchtenCapillaryPressure.hpp b/src/coreComponents/constitutive/capillaryPressure/VanGenuchtenCapillaryPressure.hpp
index f3af8198d60..2234b405037 100644
--- a/src/coreComponents/constitutive/capillaryPressure/VanGenuchtenCapillaryPressure.hpp
+++ b/src/coreComponents/constitutive/capillaryPressure/VanGenuchtenCapillaryPressure.hpp
@@ -39,10 +39,12 @@ class VanGenuchtenCapillaryPressureUpdate final : public CapillaryPressureBaseUp
real64 const volFracScale,
arrayView1d< integer const > const & phaseTypes,
arrayView1d< integer const > const & phaseOrder,
+ arrayView3d< geos::real64, cappres::USD_CAPPRES > const & phaseTrapped,
arrayView3d< real64, cappres::USD_CAPPRES > const & phaseCapPressure,
arrayView4d< real64, cappres::USD_CAPPRES_DS > const & dPhaseCapPressure_dPhaseVolFrac )
: CapillaryPressureBaseUpdate( phaseTypes,
phaseOrder,
+ phaseTrapped,
phaseCapPressure,
dPhaseCapPressure_dPhaseVolFrac ),
m_phaseMinVolumeFraction( phaseMinVolumeFraction ),
@@ -57,6 +59,11 @@ class VanGenuchtenCapillaryPressureUpdate final : public CapillaryPressureBaseUp
arraySlice1d< real64, cappres::USD_CAPPRES - 2 > const & phaseCapPres,
arraySlice2d< real64, cappres::USD_CAPPRES_DS - 2 > const & dPhaseCapPres_dPhaseVolFrac ) const;
+ GEOS_HOST_DEVICE
+ void computeInv( arraySlice1d< real64 const, compflow::USD_PHASE - 1 > const & phaseVolFraction,
+ arraySlice1d< real64, cappres::USD_CAPPRES - 2 > const & phaseCapPres,
+ arraySlice2d< real64, cappres::USD_CAPPRES_DS - 2 > const & dPhaseCapPres_dPhaseVolFrac ) const;
+
GEOS_HOST_DEVICE
virtual void update( localIndex const k,
localIndex const q,
@@ -189,6 +196,69 @@ VanGenuchtenCapillaryPressureUpdate::
}
}
+GEOS_HOST_DEVICE
+inline void
+VanGenuchtenCapillaryPressureUpdate::
+ computeInv( arraySlice1d< real64 const, compflow::USD_PHASE - 1 > const & phaseVolFraction,
+ arraySlice1d< real64, cappres::USD_CAPPRES - 2 > const & phaseCapPres,
+ arraySlice2d< real64, cappres::USD_CAPPRES_DS - 2 > const & dPhaseCapPres_dPhaseVolFrac ) const
+{
+ LvArray::forValuesInSlice( dPhaseCapPres_dPhaseVolFrac, []( real64 & val ){ val = 0.0; } );
+
+ // the VanGenuchten model does not support volFracScaled = 0 and = 1
+ // hence we need an epsilon value to avoid a division by zero
+ // TODO: for S < epsilon and S > 1 - epsilon, replace the original unbounded VG curve with a bounded power-law
+ // extension
+ real64 const eps = m_capPressureEpsilon;
+ real64 const volFracScaleInv = 1.0 / m_volFracScale;
+
+ // compute first water-oil capillary pressure as a function of water-phase vol fraction
+ integer const ip_water = m_phaseOrder[CapillaryPressureBase::PhaseType::WATER];
+ if( ip_water >= 0 )
+ {
+
+ real64 const volFracScaled = (phaseVolFraction[ip_water] - m_phaseMinVolumeFraction[ip_water]) * volFracScaleInv;
+ real64 const exponentInv = m_phaseCapPressureExponentInv[ip_water]; // div by 0 taken care of by initialization
+ // check
+ real64 const multiplier = m_phaseCapPressureMultiplier[ip_water];
+
+ real64 const scaledWettingVolFrac = volFracScaled;
+ real64 const dScaledWettingPhaseVolFrac_dVolFrac = volFracScaleInv;
+
+ evaluateVanGenuchtenFunction( scaledWettingVolFrac,
+ dScaledWettingPhaseVolFrac_dVolFrac,
+ exponentInv,
+ multiplier,
+ eps,
+ phaseCapPres[ip_water],
+ dPhaseCapPres_dPhaseVolFrac[ip_water][ip_water] );
+
+ }
+
+
+ // then compute the oil-gas capillary pressure as a function of gas-phase vol fraction
+ integer const ip_gas = m_phaseOrder[CapillaryPressureBase::PhaseType::GAS];
+ if( ip_gas >= 0 )
+ {
+ real64 const volFracScaled = (phaseVolFraction[ip_gas] - m_phaseMinVolumeFraction[ip_gas]) * volFracScaleInv;
+ real64 const exponentInv = m_phaseCapPressureExponentInv[ip_gas]; // div by 0 taken care of by initialization
+ // check
+ real64 const multiplier = -m_phaseCapPressureMultiplier[ip_gas]; // for gas capillary pressure, take the opposite
+ // of the VG function
+
+ real64 const scaledWettingVolFrac = 1-volFracScaled;
+ real64 const dScaledWettingPhaseVolFrac_dVolFrac = -volFracScaleInv;
+
+ evaluateVanGenuchtenFunction( scaledWettingVolFrac,
+ dScaledWettingPhaseVolFrac_dVolFrac,
+ exponentInv,
+ multiplier,
+ eps,
+ phaseCapPres[ip_gas],
+ dPhaseCapPres_dPhaseVolFrac[ip_gas][ip_gas] );
+ }
+}
+
GEOS_HOST_DEVICE
GEOS_FORCE_INLINE
void
diff --git a/src/coreComponents/constitutive/docs/TwoPhaseFluid.rst b/src/coreComponents/constitutive/docs/TwoPhaseFluid.rst
new file mode 100644
index 00000000000..39b35510c63
--- /dev/null
+++ b/src/coreComponents/constitutive/docs/TwoPhaseFluid.rst
@@ -0,0 +1,90 @@
+.. _TwoPhaseFluid:
+
+############################################
+Two-phase fluid model
+############################################
+
+Overview
+=========================
+
+This model represents a two-phase fluid with pressure-dependent density and viscosity.
+
+For each phase, both density and viscosity are described as tabulated data, either in the form of ``TableFunction`` or text files.
+
+In the case of text files, one file is expected per phase and should consist of three columns: pressure, density and viscosity.
+
+Note that currently, there is no temperature dependence in the model.
+
+
+Parameters
+=========================
+
+The model is represented by ```` node in the input.
+
+The following attributes are supported:
+
+.. include:: /docs/sphinx/datastructure/TwoPhaseFluid.rst
+
+
+Example using TableFunctions
+============================
+
+.. code-block:: xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Example using text files
+=========================
+
+.. code-block:: xml
+
+
+
+
+
+
+with, for example, ``water.txt`` being set as:
+
+.. code-block:: text
+
+ # P(Pa) Dens(kg/m3) Visc(Pa.s)
+ 2068000 980.683 0.0003
+ 5516000 982.07 0.0003
+ 30600000 992.233 0.0003
+ 55160000 1002.265 0.0003
diff --git a/src/coreComponents/constitutive/fluid/twophasefluid/TwoPhaseFluid.cpp b/src/coreComponents/constitutive/fluid/twophasefluid/TwoPhaseFluid.cpp
new file mode 100644
index 00000000000..ad6379ea129
--- /dev/null
+++ b/src/coreComponents/constitutive/fluid/twophasefluid/TwoPhaseFluid.cpp
@@ -0,0 +1,268 @@
+/*
+ * ------------------------------------------------------------------------------------------------------------
+ * SPDX-License-Identifier: LGPL-2.1-only
+ *
+ * Copyright (c) 2016-2024 Lawrence Livermore National Security LLC
+ * Copyright (c) 2018-2024 Total, S.A
+ * Copyright (c) 2018-2024 The Board of Trustees of the Leland Stanford Junior University
+ * Copyright (c) 2023-2024 Chevron
+ * Copyright (c) 2019- GEOS/GEOSX Contributors
+ * All rights reserved
+ *
+ * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details.
+ * ------------------------------------------------------------------------------------------------------------
+ */
+
+/**
+ * @file TwoPhaseFluid.cpp
+ */
+
+#include "constitutive/fluid/multifluid/CO2Brine/functions/PVTFunctionHelpers.hpp" // for readTable
+#include "TwoPhaseFluid.hpp"
+#include "TwoPhaseFluidFields.hpp"
+
+#include "functions/FunctionManager.hpp"
+
+
+namespace geos
+{
+
+using namespace dataRepository;
+
+namespace constitutive
+{
+
+
+TwoPhaseFluid::TwoPhaseFluid( string const & name, Group * const parent )
+ : ConstitutiveBase( name, parent )
+{
+ registerWrapper( viewKeyStruct::phaseNamesString(), &m_phaseNames ).
+ setRTTypeName( rtTypes::CustomTypes::groupNameRefArray ).
+ setInputFlag( InputFlags::OPTIONAL ).
+ setDescription( "List of fluid phases" );
+
+ // 1) First option: specify PVT tables from one file per phase, read the files line by line, and populate the internal TableFunctions
+ registerWrapper( viewKeyStruct::tableFilesString(), &m_tableFiles ).
+ setInputFlag( InputFlags::OPTIONAL ).
+ setRestartFlags( RestartFlags::NO_WRITE ).
+ setDescription( "List of filenames with input PVT tables (one per phase)" );
+
+ // 2) Second option: specify TableFunction names for each phase,
+ registerWrapper( viewKeyStruct::densityTableNamesString(), &m_densityTableNames ).
+ setRTTypeName( rtTypes::CustomTypes::groupNameRefArray ).
+ setInputFlag( InputFlags::OPTIONAL ).
+ setDescription( "List of density TableFuncion names from the Function block. \n"
+ "The user must provide one TableFunction per phase, respecting the order provided in \"phaseNames\"." );
+
+ registerWrapper( viewKeyStruct::viscosityTableNamesString(), &m_viscosityTableNames ).
+ setRTTypeName( rtTypes::CustomTypes::groupNameRefArray ).
+ setInputFlag( InputFlags::OPTIONAL ).
+ setDescription( "List of viscosity TableFuncion names from the Function block. \n"
+ "The user must provide one TableFunction per phase, respecting the order provided in \"phaseNames\"." );
+
+ registerField( fields::twophasefluid::phaseDensity{}, &m_phaseDensity.value );
+ registerField( fields::twophasefluid::dPhaseDensity{}, &m_phaseDensity.derivs );
+ registerField( fields::twophasefluid::phaseDensity_n{}, &m_phaseDensity_n );
+
+ registerField( fields::twophasefluid::phaseViscosity{}, &m_phaseViscosity.value );
+ registerField( fields::twophasefluid::dPhaseViscosity{}, &m_phaseViscosity.derivs );
+}
+
+
+std::unique_ptr< ConstitutiveBase >
+TwoPhaseFluid::deliverClone( string const & name, Group * const parent ) const
+{
+ return ConstitutiveBase::deliverClone( name, parent );
+}
+
+
+void TwoPhaseFluid::resizeFields( localIndex const size, localIndex const numPts )
+{
+ // Assume sole dependency on pressure, i.e. one derivative
+ m_phaseDensity.value.resize( size, numPts, 2 );
+ m_phaseDensity.derivs.resize( size, numPts, 2, 1 );
+
+ m_phaseDensity_n.resize( size, numPts, 2 );
+
+ m_phaseViscosity.value.resize( size, numPts, 2 );
+ m_phaseViscosity.derivs.resize( size, numPts, 2, 1 );
+}
+
+
+void TwoPhaseFluid::allocateConstitutiveData( dataRepository::Group & parent,
+ localIndex const numConstitutivePointsPerParentIndex )
+{
+ ConstitutiveBase::allocateConstitutiveData( parent, numConstitutivePointsPerParentIndex );
+ resizeFields( parent.size(), numConstitutivePointsPerParentIndex );
+}
+
+
+void TwoPhaseFluid::postInputInitialization()
+{
+ ConstitutiveBase::postInputInitialization();
+
+ // Input relationships can be provided either as text files or TableFunctions.
+ m_tableFiles.empty() ? readInputDataFromTableFunctions() : readInputDataFromFileTableFunctions();
+
+ checkTableConsistency();
+}
+
+
+void TwoPhaseFluid::fillData( integer const ip,
+ array1d< array1d< real64 > > const & tableValues )
+{
+ array1d< array1d< real64 > > pressureCoords( 1 );
+ pressureCoords[0].resize( tableValues.size() );
+ array1d< real64 > density( tableValues.size() );
+ array1d< real64 > viscosity( tableValues.size() );
+
+ for( localIndex i = 0; i < tableValues.size(); ++i )
+ {
+ GEOS_THROW_IF_NE_MSG( tableValues[i].size(), 3,
+ GEOS_FMT( "{}: three columns (pressure, density, and viscosity) are expected", getFullName() ),
+ InputError );
+
+ pressureCoords[0][i] = tableValues[i][0];
+ density[i] = tableValues[i][1];
+ viscosity[i] = tableValues[i][2];
+ }
+
+ string const densityTableName = getName() + "DensityPhase" + GEOS_FMT( "{}", ip );
+ string const viscosityTableName = getName() + "ViscosityPhase" + GEOS_FMT( "{}", ip );
+ m_densityTableNames.emplace_back( densityTableName );
+ m_viscosityTableNames.emplace_back( viscosityTableName );
+
+ FunctionManager & functionManager = FunctionManager::getInstance();
+
+ TableFunction & tableDensity =
+ dynamicCast< TableFunction & >( *functionManager.createChild( "TableFunction", densityTableName ) );
+ tableDensity.setTableCoordinates( pressureCoords, { units::Pressure } );
+ tableDensity.setTableValues( density, units::Density );
+ tableDensity.setInterpolationMethod( TableFunction::InterpolationType::Linear );
+
+ TableFunction & tableViscosity =
+ dynamicCast< TableFunction & >( *functionManager.createChild( "TableFunction", viscosityTableName ) );
+ tableViscosity.setTableCoordinates( pressureCoords, { units::Pressure } );
+ tableViscosity.setTableValues( viscosity, units::Viscosity );
+ tableViscosity.setInterpolationMethod( TableFunction::InterpolationType::Linear );
+}
+
+
+void TwoPhaseFluid::readInputDataFromFileTableFunctions()
+{
+ // Check for ambiguous definition
+ GEOS_THROW_IF( !(m_densityTableNames.empty() && m_viscosityTableNames.empty()),
+ GEOS_FMT( "{}: input is redundant (both TableFunction names and text files)", getFullName() ),
+ InputError );
+
+
+ // Check that we have exactly two table files (one per phase)
+ GEOS_THROW_IF_NE_MSG( m_tableFiles.size(), 2,
+ GEOS_FMT( "{}: expecting two table files (one per phase)", getFullName() ),
+ InputError );
+
+ array1d< array1d< real64 > > tableValues;
+ for( integer ip = 0; ip < 2; ++ip )
+ {
+ tableValues.clear();
+ geos::constitutive::PVTProps::BlackOilTables::readTable( m_tableFiles[ip], 3, tableValues );
+ fillData( ip, tableValues );
+ }
+}
+
+
+void TwoPhaseFluid::readInputDataFromTableFunctions()
+{
+ // Check for ambiguous definition
+ GEOS_THROW_IF( !m_tableFiles.empty(),
+ GEOS_FMT( "{}: input is redundant (both TableFunction names and text files)", getFullName() ),
+ InputError );
+
+ // Since we are considering a two phase fluid, we should have exactly 2 tables per property
+ GEOS_THROW_IF_NE_MSG( m_densityTableNames.size(), 2,
+ GEOS_FMT( "{}: one density table must be provided for each phase", getFullName() ),
+ InputError );
+
+ GEOS_THROW_IF_NE_MSG( m_viscosityTableNames.size(), 2,
+ GEOS_FMT( "{}: one viscosity table must be provided for each phase", getFullName() ),
+ InputError );
+
+
+ FunctionManager const & functionManager = FunctionManager::getInstance();
+
+ for( integer iph = 0; iph < 2; ++iph )
+ {
+ GEOS_THROW_IF( !functionManager.hasGroup( m_densityTableNames[iph] ),
+ GEOS_FMT( "{}: density table '{}' not found", getFullName(), m_densityTableNames[iph] ),
+ InputError );
+
+ GEOS_THROW_IF( !functionManager.hasGroup( m_viscosityTableNames[iph] ),
+ GEOS_FMT( "{}: viscosity table '{}' not found", getFullName(), m_viscosityTableNames[iph] ),
+ InputError );
+ }
+}
+
+
+void TwoPhaseFluid::initializePostSubGroups()
+{
+ ConstitutiveBase::initializePostSubGroups();
+
+ FunctionManager const & functionManager = FunctionManager::getInstance();
+ for( integer iph = 0; iph < 2; ++iph )
+ {
+ // Grab the tables by name from the function manager,
+ // then add them in a list to create their table wrappers when needed
+ TableFunction const & densityTable = functionManager.getGroup< TableFunction const >( m_densityTableNames[iph] );
+ m_densityTables.emplace_back( &densityTable );
+ m_densityTableKernels.emplace_back( m_densityTables[iph]->createKernelWrapper() );
+
+ TableFunction const & viscosityTable = functionManager.getGroup< TableFunction const >( m_viscosityTableNames[iph] );
+ m_viscosityTables.emplace_back( &viscosityTable );
+ m_viscosityTableKernels.emplace_back( m_viscosityTables[iph]->createKernelWrapper() );
+ }
+}
+
+
+void TwoPhaseFluid::checkTableConsistency() const
+{
+ FunctionManager const & functionManager = FunctionManager::getInstance();
+ for( integer iph = 0; iph < 2; ++iph )
+ {
+ TableFunction const & densityTable = functionManager.getGroup< TableFunction const >( m_densityTableNames[iph] );
+ arrayView1d< real64 const > const density = densityTable.getValues();
+
+ for( localIndex i = 1; i < density.size(); ++i )
+ {
+ GEOS_THROW_IF( density[i] - density[i-1] < 0,
+ GEOS_FMT( "{}: in table '{}' density values must be increasing", getFullName(), densityTable.getName() ),
+ InputError );
+ }
+ }
+}
+
+
+TwoPhaseFluid::KernelWrapper
+TwoPhaseFluid::createKernelWrapper()
+{
+ return KernelWrapper( m_densityTableKernels,
+ m_viscosityTableKernels,
+ m_phaseDensity.toView(),
+ m_phaseViscosity.toView());
+}
+
+
+TwoPhaseFluid::KernelWrapper::KernelWrapper(
+ arrayView1d< TableFunction::KernelWrapper const > densityTables,
+ arrayView1d< TableFunction::KernelWrapper const > viscosityTables,
+ PhaseProp::ViewType phaseDensity,
+ PhaseProp::ViewType phaseViscosity )
+ : m_densityTables( std::move( densityTables )),
+ m_viscosityTables( std::move( viscosityTables )),
+ m_phaseDensity( std::move( phaseDensity )),
+ m_phaseViscosity( std::move( phaseViscosity )) {}
+
+
+REGISTER_CATALOG_ENTRY( ConstitutiveBase, TwoPhaseFluid, string const &, Group * const )
+
+} // namespace constitutive
+} // namespace geos
diff --git a/src/coreComponents/constitutive/fluid/twophasefluid/TwoPhaseFluid.hpp b/src/coreComponents/constitutive/fluid/twophasefluid/TwoPhaseFluid.hpp
new file mode 100644
index 00000000000..18cdc357bb0
--- /dev/null
+++ b/src/coreComponents/constitutive/fluid/twophasefluid/TwoPhaseFluid.hpp
@@ -0,0 +1,344 @@
+/*
+ * ------------------------------------------------------------------------------------------------------------
+ * SPDX-License-Identifier: LGPL-2.1-only
+ *
+ * Copyright (c) 2016-2024 Lawrence Livermore National Security LLC
+ * Copyright (c) 2018-2024 Total, S.A
+ * Copyright (c) 2018-2024 The Board of Trustees of the Leland Stanford Junior University
+ * Copyright (c) 2023-2024 Chevron
+ * Copyright (c) 2019- GEOS/GEOSX Contributors
+ * All rights reserved
+ *
+ * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details.
+ * ------------------------------------------------------------------------------------------------------------
+ */
+
+/**
+ * @file TwoPhaseFluid.hpp
+ */
+
+#ifndef GEOS_CONSTITUTIVE_FLUID_TWOPHASEFLUID_TWOPHASEFLUID_HPP_
+#define GEOS_CONSTITUTIVE_FLUID_TWOPHASEFLUID_TWOPHASEFLUID_HPP_
+
+#include "common/DataLayouts.hpp"
+#include "functions/TableFunction.hpp"
+#include "constitutive/ConstitutiveBase.hpp"
+
+#include "constitutive/fluid/multifluid/Layouts.hpp"
+#include "constitutive/fluid/multifluid/MultiFluidUtils.hpp"
+
+#include "constitutive/ConstitutivePassThruHandler.hpp"
+
+
+namespace geos
+{
+namespace constitutive
+{
+
+class TwoPhaseFluid : public ConstitutiveBase
+{
+public:
+
+ TwoPhaseFluid( string const & name,
+ Group * const parent );
+
+ virtual std::unique_ptr< ConstitutiveBase >
+ deliverClone( string const & name,
+ Group * const parent ) const override;
+
+ virtual void allocateConstitutiveData( dataRepository::Group & parent,
+ localIndex const numConstitutivePointsPerParentIndex ) override;
+
+ /**
+ * @name Static Factory Catalog members and functions
+ */
+ ///@{
+
+ /**
+ * @brief Static catalog string
+ * @return A string that is used to register/lookup this class in the registry
+ */
+ static std::string catalogName() { return "TwoPhaseFluid"; }
+
+ /**
+ * @brief Get catalog name
+ * @return Name string
+ */
+ virtual string getCatalogName() const override { return catalogName(); }
+
+ ///@}
+
+
+
+ /**
+ * @brief Getter for the fluid phase names
+ * @return an array storing the phase names
+ */
+ string_array const & phaseNames() const { return m_phaseNames; }
+
+ struct viewKeyStruct : ConstitutiveBase::viewKeyStruct
+ {
+ static constexpr char const * tableFilesString() { return "tableFiles"; }
+ static constexpr char const * phaseNamesString() { return "phaseNames"; }
+ static constexpr char const * densityTableNamesString() { return "densityTableNames"; }
+ static constexpr char const * viscosityTableNamesString() { return "viscosityTableNames"; }
+ };
+
+ arrayView3d< real64 const, multifluid::USD_PHASE > phaseDensity_n() const
+ { return m_phaseDensity_n; }
+
+ arrayView3d< real64 const, multifluid::USD_PHASE > phaseDensity() const
+ { return m_phaseDensity.value; }
+
+ arrayView4d< real64 const, multifluid::USD_PHASE_DC > dPhaseDensity() const
+ { return m_phaseDensity.derivs; }
+
+ arrayView3d< real64 const, multifluid::USD_PHASE > phaseViscosity() const
+ { return m_phaseViscosity.value; }
+
+ arrayView4d< real64 const, multifluid::USD_PHASE_DC > dPhaseViscosity() const
+ { return m_phaseViscosity.derivs; }
+
+ using PhaseProp = MultiFluidVar< real64, 3, constitutive::multifluid::LAYOUT_PHASE, constitutive::multifluid::LAYOUT_PHASE_DC >;
+
+
+ class KernelWrapper
+ {
+public:
+
+ /// @cond DO_NOT_DOCUMENT
+ /// We need these SMFs to avoid host-device errors with CUDA.
+ KernelWrapper() = default;
+ KernelWrapper( KernelWrapper const & ) = default;
+ KernelWrapper & operator=( KernelWrapper const & ) = default;
+ KernelWrapper & operator=( KernelWrapper && ) = default;
+ /// @endcond
+
+ /**
+ * @brief Get number of elements in this wrapper.
+ * @return number of elements
+ */
+ GEOS_HOST_DEVICE
+ GEOS_FORCE_INLINE
+ localIndex numElems() const { return m_phaseDensity.value.size( 0 ); }
+
+ /**
+ * @brief Get number of gauss points per element.
+ * @return number of gauss points per element
+ */
+ GEOS_HOST_DEVICE
+ GEOS_FORCE_INLINE
+ localIndex numGauss() const { return m_phaseDensity.value.size( 1 ); }
+
+
+ GEOS_HOST_DEVICE
+ void compute( real64 const pressure,
+ PhaseProp::SliceType const phaseDensity,
+ PhaseProp::SliceType const phaseViscosity ) const;
+
+ GEOS_HOST_DEVICE
+ void update( localIndex const k,
+ localIndex const q,
+ real64 const pressure ) const;
+
+private:
+
+ friend class TwoPhaseFluid;
+
+ /**
+ * @brief Constructor for the class doing in-kernel two-phase fluid updates
+ * @param[in] densityTables density tables
+ * @param[in] viscosityTables viscosity tables
+ * @param[in] phaseDensity phase densities (+ derivatives) in the cell
+ * @param[in] phaseViscosity phase viscosities (+ derivatives) in the cell
+ */
+ KernelWrapper(
+ arrayView1d< TableFunction::KernelWrapper const > densityTables,
+ arrayView1d< TableFunction::KernelWrapper const > viscosityTables,
+ PhaseProp::ViewType phaseDensity,
+ PhaseProp::ViewType phaseViscosity );
+
+
+protected:
+
+ KernelWrapper(
+ arrayView1d< TableFunction::KernelWrapper const > densityTables,
+ arrayView1d< TableFunction::KernelWrapper const > viscosityTables
+ );
+
+ /// Table kernel wrappers to interpolate in the two phase (\rho vs p) tables
+ arrayView1d< TableFunction::KernelWrapper const > m_densityTables;
+
+ /// Table kernel wrappers to interpolate in the two phase (\mu vs p) tables
+ arrayView1d< TableFunction::KernelWrapper const > m_viscosityTables;
+
+ /**
+ * @brief Utility function to compute densities as a function of pressure (keeping derivatives)
+ * @param[in] pressure pressure in the cell
+ * @param[out] phaseDensity the phase density in the cell (+ derivatives)
+ */
+ GEOS_HOST_DEVICE
+ void computeDensities( real64 const pressure,
+ PhaseProp::SliceType const & phaseDensity ) const;
+
+ /**
+ * @brief Utility function to compute viscosities as a function of pressure (keeping derivatives)
+ * @param[in] pressure pressure in the cell
+ * @param[out] phaseViscosity the phase viscosities in the cell (+ derivatives)
+ */
+ GEOS_HOST_DEVICE
+ void computeViscosities( real64 const pressure,
+ PhaseProp::SliceType const & phaseViscosity ) const;
+
+ /// Views on the phase properties
+ PhaseProp::ViewType m_phaseDensity;
+ PhaseProp::ViewType m_phaseViscosity;
+
+ }; //class KernelWrapper
+
+
+ string_array m_phaseNames;
+
+
+ path_array m_tableFiles;
+
+ /// Names of the density tables (one per phase)
+ string_array m_densityTableNames;
+
+ /// Names of the viscosity tables (one per phase)
+ string_array m_viscosityTableNames;
+
+ PhaseProp m_phaseDensity;
+ PhaseProp m_phaseViscosity;
+
+ /// Backup data
+ array3d< real64, multifluid::LAYOUT_PHASE > m_phaseDensity_n;
+
+
+ virtual void resizeFields( localIndex const size, localIndex const numPts );
+
+ virtual void postInputInitialization() override;
+
+ virtual void initializePostSubGroups() override;
+
+ /// Table kernel wrappers to interpolate (\rho vs p) tables
+ array1d< TableFunction const * > m_densityTables;
+ /// Table kernel wrappers of m_densityTables
+ array1d< TableFunction::KernelWrapper > m_densityTableKernels;
+
+ /// Table kernel wrappers to interpolate (\mu vs p) tables
+ array1d< TableFunction const * > m_viscosityTables;
+ /// Table kernel wrappers of m_viscosityTables
+ array1d< TableFunction::KernelWrapper > m_viscosityTableKernels;
+
+ KernelWrapper createKernelWrapper();
+
+
+private:
+ void readInputDataFromTableFunctions();
+ void readInputDataFromFileTableFunctions();
+
+ /**
+ * @brief Fill the fluid data (pressure, density, viscosity)
+ * @param[in] ip the index of the phase
+ * @param[in] tableValues the values in the fluid table
+ */
+ void fillData( integer const ip,
+ array1d< array1d< real64 > > const & tableValues );
+
+ /**
+ * @brief Check the monotonicity of the PVT relationship
+ */
+ void checkTableConsistency() const;
+};
+
+
+GEOS_HOST_DEVICE
+GEOS_FORCE_INLINE
+void TwoPhaseFluid::KernelWrapper::
+ computeDensities( real64 const pressure,
+ PhaseProp::SliceType const & phaseDensity ) const
+{
+ using Deriv = constitutive::multifluid::DerivativeOffset;
+
+ LvArray::forValuesInSlice( phaseDensity.derivs, []( real64 & val ) { val = 0.0; } );
+
+ for( integer iph = 0; iph < 2; ++iph )
+ {
+ // interpolate in the table to get the phase density and derivatives
+ real64 dPhaseDens_dPres = 0.0;
+
+ phaseDensity.value[iph] = m_densityTables[iph].compute( &pressure, &dPhaseDens_dPres );
+ phaseDensity.derivs[iph][Deriv::dP] = dPhaseDens_dPres;
+ }
+}
+
+
+GEOS_HOST_DEVICE
+GEOS_FORCE_INLINE
+void TwoPhaseFluid::KernelWrapper::
+ computeViscosities( real64 const pressure,
+ PhaseProp::SliceType const & phaseViscosity ) const
+{
+ using Deriv = constitutive::multifluid::DerivativeOffset;
+
+ LvArray::forValuesInSlice( phaseViscosity.derivs, []( real64 & val ) { val = 0.0; } );
+
+ for( integer iph = 0; iph < 2; ++iph )
+ {
+ // interpolate in the table to get the phase viscosity and derivatives
+ real64 dPhaseVisc_dPres = 0.0;
+ phaseViscosity.value[iph] = m_viscosityTables[iph].compute( &pressure, &dPhaseVisc_dPres );
+ phaseViscosity.derivs[iph][Deriv::dP] = dPhaseVisc_dPres;
+ }
+}
+
+
+GEOS_HOST_DEVICE
+GEOS_FORCE_INLINE
+void TwoPhaseFluid::KernelWrapper::
+ compute( real64 const pressure,
+ PhaseProp::SliceType const phaseDensity,
+ PhaseProp::SliceType const phaseViscosity ) const
+{
+ computeDensities( pressure,
+ phaseDensity );
+
+ computeViscosities( pressure,
+ phaseViscosity );
+}
+
+
+GEOS_HOST_DEVICE
+GEOS_FORCE_INLINE
+void TwoPhaseFluid::KernelWrapper::
+ update( localIndex const k,
+ localIndex const q,
+ real64 const pressure
+ ) const
+{
+ compute( pressure,
+ m_phaseDensity( k, q ),
+ m_phaseViscosity( k, q ) );
+}
+
+
+template< typename LAMBDA >
+void constitutiveUpdatePassThru( TwoPhaseFluid const & fluid,
+ LAMBDA && lambda )
+{
+ ConstitutivePassThruHandler< TwoPhaseFluid >::execute( fluid, std::forward< LAMBDA >( lambda ) );
+}
+
+
+template< typename LAMBDA >
+void constitutiveUpdatePassThru( TwoPhaseFluid & fluid,
+ LAMBDA && lambda )
+{
+ ConstitutivePassThruHandler< TwoPhaseFluid >::execute( fluid, std::forward< LAMBDA >( lambda ) );
+}
+
+} // namespace constitutive
+} // namespace geos
+
+#endif // GEOS_CONSTITUTIVE_FLUID_TWOPHASEFLUID_TWOPHASEFLUID_HPP_
diff --git a/src/coreComponents/constitutive/fluid/twophasefluid/TwoPhaseFluidFields.hpp b/src/coreComponents/constitutive/fluid/twophasefluid/TwoPhaseFluidFields.hpp
new file mode 100644
index 00000000000..9ce0dc5c33d
--- /dev/null
+++ b/src/coreComponents/constitutive/fluid/twophasefluid/TwoPhaseFluidFields.hpp
@@ -0,0 +1,84 @@
+/*
+ * ------------------------------------------------------------------------------------------------------------
+ * SPDX-License-Identifier: LGPL-2.1-only
+ *
+ * Copyright (c) 2016-2024 Lawrence Livermore National Security LLC
+ * Copyright (c) 2018-2024 Total, S.A
+ * Copyright (c) 2018-2024 The Board of Trustees of the Leland Stanford Junior University
+ * Copyright (c) 2023-2024 Chevron
+ * Copyright (c) 2019- GEOS/GEOSX Contributors
+ * All rights reserved
+ *
+ * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details.
+ * ------------------------------------------------------------------------------------------------------------
+ */
+
+/**
+ * @file TwoPhaseFluidFields.hpp
+ */
+
+#ifndef GEOS_CONSTITUTIVE_FLUID_TWOPHASEFLUIDFIELDS_HPP_
+#define GEOS_CONSTITUTIVE_FLUID_TWOPHASEFLUIDFIELDS_HPP_
+
+#include "constitutive/fluid/multifluid/Layouts.hpp"
+#include "mesh/MeshFields.hpp"
+
+
+namespace geos
+{
+
+namespace fields
+{
+
+namespace twophasefluid
+{
+
+using array3dLayoutPhase = array3d< real64, constitutive::multifluid::LAYOUT_PHASE >;
+using array4dLayoutPhase_d = array4d< real64, constitutive::multifluid::LAYOUT_PHASE_DC >;
+
+DECLARE_FIELD( phaseDensity,
+ "phaseDensity",
+ array3dLayoutPhase,
+ 0,
+ LEVEL_0,
+ WRITE_AND_READ,
+ "Phase density" );
+
+DECLARE_FIELD( phaseDensity_n,
+ "phaseDensity_n",
+ array3dLayoutPhase,
+ 0,
+ NOPLOT,
+ WRITE_AND_READ,
+ "Phase density at the previous converged time step" );
+
+DECLARE_FIELD( dPhaseDensity,
+ "dPhaseDensity",
+ array4dLayoutPhase_d,
+ 0,
+ NOPLOT,
+ NO_WRITE,
+ "Derivative of phase density with respect to pressure" );
+
+DECLARE_FIELD( phaseViscosity,
+ "phaseViscosity",
+ array3dLayoutPhase,
+ 0,
+ LEVEL_0,
+ WRITE_AND_READ,
+ "Phase viscosity" );
+
+DECLARE_FIELD( dPhaseViscosity,
+ "dPhaseViscosity",
+ array4dLayoutPhase_d,
+ 0,
+ NOPLOT,
+ NO_WRITE,
+ "Derivative of phase viscosity with respect to pressure" );
+
+} // namespace twophasefluid
+
+} // namespace constitutive
+} // namespace geos
+
+#endif // GEOS_CONSTITUTIVE_FLUID_TWOPHASEFLUIDFIELDS_HPP_
diff --git a/src/coreComponents/constitutive/relativePermeability/BrooksCoreyBakerRelativePermeability.hpp b/src/coreComponents/constitutive/relativePermeability/BrooksCoreyBakerRelativePermeability.hpp
index 476def9f455..21fdbe4a8a6 100644
--- a/src/coreComponents/constitutive/relativePermeability/BrooksCoreyBakerRelativePermeability.hpp
+++ b/src/coreComponents/constitutive/relativePermeability/BrooksCoreyBakerRelativePermeability.hpp
@@ -139,19 +139,17 @@ class BrooksCoreyBakerRelativePermeability : public RelativePermeabilityBase
static constexpr char const * volFracScaleString() { return "volFracScale"; }
};
- arrayView1d< real64 const > getPhaseMinVolumeFraction() const override { return m_phaseMinVolumeFraction; };
-
real64 getWettingPhaseMinVolumeFraction() const override
{
integer ipWetting;
- std::tie( ipWetting, std::ignore ) = wettingAndNonWettingPhaseIndices();
+ std::tie( ipWetting, std::ignore ) = phaseIndex( getPhaseOrder());
return m_phaseMinVolumeFraction[ipWetting];
}
real64 getNonWettingMinVolumeFraction() const override
{
integer ipNonWetting;
- std::tie( std::ignore, ipNonWetting ) = wettingAndNonWettingPhaseIndices();
+ std::tie( std::ignore, ipNonWetting ) = phaseIndex( getPhaseOrder());
return m_phaseMinVolumeFraction[ipNonWetting];
};
diff --git a/src/coreComponents/constitutive/relativePermeability/BrooksCoreyRelativePermeability.hpp b/src/coreComponents/constitutive/relativePermeability/BrooksCoreyRelativePermeability.hpp
index 30ee2ff3692..2de3998a78e 100644
--- a/src/coreComponents/constitutive/relativePermeability/BrooksCoreyRelativePermeability.hpp
+++ b/src/coreComponents/constitutive/relativePermeability/BrooksCoreyRelativePermeability.hpp
@@ -105,19 +105,18 @@ class BrooksCoreyRelativePermeability : public RelativePermeabilityBase
static constexpr char const * volFracScaleString() { return "volFracScale"; }
};
//END_SPHINX_INCLUDE_01
- arrayView1d< real64 const > getPhaseMinVolumeFraction() const override { return m_phaseMinVolumeFraction; };
real64 getWettingPhaseMinVolumeFraction() const override
{
integer ipWetting;
- std::tie( ipWetting, std::ignore ) = wettingAndNonWettingPhaseIndices();
+ std::tie( ipWetting, std::ignore ) = phaseIndex( getPhaseOrder());
return m_phaseMinVolumeFraction[ipWetting];
}
real64 getNonWettingMinVolumeFraction() const override
{
integer ipNonWetting;
- std::tie( std::ignore, ipNonWetting ) = wettingAndNonWettingPhaseIndices();
+ std::tie( std::ignore, ipNonWetting ) = phaseIndex( getPhaseOrder());
return m_phaseMinVolumeFraction[ipNonWetting];
};
diff --git a/src/coreComponents/constitutive/relativePermeability/BrooksCoreyStone2RelativePermeability.hpp b/src/coreComponents/constitutive/relativePermeability/BrooksCoreyStone2RelativePermeability.hpp
index 47eefdd512a..2d83dcda597 100644
--- a/src/coreComponents/constitutive/relativePermeability/BrooksCoreyStone2RelativePermeability.hpp
+++ b/src/coreComponents/constitutive/relativePermeability/BrooksCoreyStone2RelativePermeability.hpp
@@ -138,7 +138,7 @@ class BrooksCoreyStone2RelativePermeability : public RelativePermeabilityBase
static constexpr char const * volFracScaleString() { return "volFracScale"; }
};
- arrayView1d< real64 const > getPhaseMinVolumeFraction() const override { return m_phaseMinVolumeFraction; };
+ arrayView1d< real64 const > getPhaseMinVolumeFraction() const { return m_phaseMinVolumeFraction; };
real64 getWettingPhaseMinVolumeFraction() const override
{
diff --git a/src/coreComponents/constitutive/relativePermeability/KilloughHysteresis.hpp b/src/coreComponents/constitutive/relativePermeability/KilloughHysteresis.hpp
index 58499b94b64..48d2d3deaa2 100644
--- a/src/coreComponents/constitutive/relativePermeability/KilloughHysteresis.hpp
+++ b/src/coreComponents/constitutive/relativePermeability/KilloughHysteresis.hpp
@@ -137,18 +137,8 @@ class KilloughHysteresis
m_extremumPhaseVolFraction ),
InputError );
- GEOS_THROW_IF( m_criticalImbibitionValue < 0 || m_criticalImbibitionValue > 1,
- GEOS_FMT( "KilloughHysteresis: the critical imbibition relative permeability is equal to {} but must be between 0 an 1",
- m_criticalImbibitionValue ),
- InputError );
- GEOS_THROW_IF( m_criticalDrainageValue < 0 || m_criticalDrainageValue > 1,
- GEOS_FMT( "KilloughHysteresis: the critical drainage relative permeability is equal to {} but must be between 0 an 1",
- m_criticalDrainageValue ),
- InputError );
- GEOS_THROW_IF( m_extremumValue < 0 || m_extremumValue > 1,
- GEOS_FMT( "KilloughHysteresis: the extremum relative permeability is equal to {} but must be between 0 an 1",
- m_extremumValue ),
- InputError );
+ // Note: value validation removed because this struct is used for both relative permeability (0-1 range)
+ // and capillary pressure (can be in Pa or other units, typically >> 1). The value range depends on the application.
m_isWetting = m_criticalDrainagePhaseVolFraction > m_extremumPhaseVolFraction;
diff --git a/src/coreComponents/constitutive/relativePermeability/RelativePermeabilityBase.cpp b/src/coreComponents/constitutive/relativePermeability/RelativePermeabilityBase.cpp
index d93d335873e..57890850fab 100644
--- a/src/coreComponents/constitutive/relativePermeability/RelativePermeabilityBase.cpp
+++ b/src/coreComponents/constitutive/relativePermeability/RelativePermeabilityBase.cpp
@@ -153,6 +153,7 @@ std::tuple< integer, integer > RelativePermeabilityBase::wettingAndNonWettingPha
return std::make_tuple( ipWetting, ipNonWetting );
}
+
} // namespace constitutive
} // namespace geos
diff --git a/src/coreComponents/constitutive/relativePermeability/RelativePermeabilityBase.hpp b/src/coreComponents/constitutive/relativePermeability/RelativePermeabilityBase.hpp
index 0bc67b26a39..0600a41643a 100644
--- a/src/coreComponents/constitutive/relativePermeability/RelativePermeabilityBase.hpp
+++ b/src/coreComponents/constitutive/relativePermeability/RelativePermeabilityBase.hpp
@@ -165,8 +165,8 @@ class RelativePermeabilityBase : public ConstitutiveBase
static std::tuple< integer, integer > phaseIndex( arrayView1d< integer const > const & phaseOrder );
arrayView1d< integer const > getPhaseOrder() const { return m_phaseOrder; }
- virtual arrayView1d< real64 const > getPhaseMinVolumeFraction() const = 0;
virtual real64 getWettingPhaseMinVolumeFraction() const = 0;
+
virtual real64 getNonWettingMinVolumeFraction() const = 0;
std::tuple< integer, integer > wettingAndNonWettingPhaseIndices() const;
@@ -217,6 +217,45 @@ class RelativePermeabilityBase : public ConstitutiveBase
};
+
+/// for use in RelpermDriver to browse the drainage curves
+/// by setting the MaxHistoricalNonWettingSat to Snwmin and MinWettingSat to Sw
+inline std::tuple< integer, integer > RelativePermeabilityBase::phaseIndex( arrayView1d< integer const > const & phaseOrder )
+{
+ using PT = PhaseType;
+ integer const ipWater = phaseOrder[PT::WATER];
+ integer const ipOil = phaseOrder[PT::OIL];
+ integer const ipGas = phaseOrder[PT::GAS];
+
+ integer ipWetting = -1, ipNonWetting = -1;
+
+ if( ipWater >= 0 && ipOil >= 0 && ipGas >= 0 )
+ {
+ ipWetting = ipWater;
+ ipNonWetting = ipGas;
+ }
+ else if( ipWater < 0 )
+ {
+ ipWetting = ipOil;
+ ipNonWetting = ipGas;
+ }
+ else if( ipOil < 0 )
+ {
+ ipWetting = ipWater;
+ ipNonWetting = ipGas;
+ }
+ else if( ipGas < 0 )
+ {
+ ipWetting = ipWater;
+ ipNonWetting = ipOil;
+ }
+
+ //maybe a bit too pythonic
+ return std::make_tuple( ipWetting, ipNonWetting );
+}
+
+
+
} // namespace constitutive
} // namespace geos
diff --git a/src/coreComponents/constitutive/relativePermeability/TableRelativePermeability.hpp b/src/coreComponents/constitutive/relativePermeability/TableRelativePermeability.hpp
index 7b2fb4d7bcd..255fa33aca3 100644
--- a/src/coreComponents/constitutive/relativePermeability/TableRelativePermeability.hpp
+++ b/src/coreComponents/constitutive/relativePermeability/TableRelativePermeability.hpp
@@ -142,19 +142,17 @@ class TableRelativePermeability : public RelativePermeabilityBase
static constexpr char const * threePhaseInterpolatorString() { return "threePhaseInterpolator"; }
};
- arrayView1d< real64 const > getPhaseMinVolumeFraction() const override { return m_phaseMinVolumeFraction; };
-
real64 getWettingPhaseMinVolumeFraction() const override
{
integer ipWetting;
- std::tie( ipWetting, std::ignore ) = wettingAndNonWettingPhaseIndices();
+ std::tie( ipWetting, std::ignore ) = phaseIndex( getPhaseOrder());
return m_phaseMinVolumeFraction[ipWetting];
}
real64 getNonWettingMinVolumeFraction() const override
{
integer ipNonWetting;
- std::tie( std::ignore, ipNonWetting ) = wettingAndNonWettingPhaseIndices();
+ std::tie( std::ignore, ipNonWetting ) = phaseIndex( getPhaseOrder());
return m_phaseMinVolumeFraction[ipNonWetting];
};
diff --git a/src/coreComponents/constitutive/relativePermeability/TableRelativePermeabilityHelpers.cpp b/src/coreComponents/constitutive/relativePermeability/TableRelativePermeabilityHelpers.cpp
index 965c9278fc1..3d360fc6362 100644
--- a/src/coreComponents/constitutive/relativePermeability/TableRelativePermeabilityHelpers.cpp
+++ b/src/coreComponents/constitutive/relativePermeability/TableRelativePermeabilityHelpers.cpp
@@ -81,7 +81,7 @@ TableRelativePermeabilityHelpers::validateRelativePermeabilityTable( TableFuncti
if( isZero( relPerm[i-1] ) && !isZero( relPerm[i] ) )
{
phaseMinVolFrac = phaseVolFrac[i-1];
- phaseRelPermMinEndPoint = 0.;
+ phaseRelPermMinEndPoint = relPerm[i-1];
}
}
diff --git a/src/coreComponents/constitutive/relativePermeability/TableRelativePermeabilityHysteresis.cpp b/src/coreComponents/constitutive/relativePermeability/TableRelativePermeabilityHysteresis.cpp
index 910c42cce41..08fc87ea6a0 100644
--- a/src/coreComponents/constitutive/relativePermeability/TableRelativePermeabilityHysteresis.cpp
+++ b/src/coreComponents/constitutive/relativePermeability/TableRelativePermeabilityHysteresis.cpp
@@ -22,7 +22,8 @@
#include "constitutive/relativePermeability/RelativePermeabilityFields.hpp"
#include "constitutive/relativePermeability/TableRelativePermeabilityHelpers.hpp"
#include "functions/FunctionManager.hpp"
-#include "LogLevelsInfo.hpp"
+#include "constitutiveDrivers/relativePermeability/RelpermDriver.hpp"
+#include "constitutive/ConstitutiveManager.hpp"
namespace geos
{
@@ -89,48 +90,68 @@ TableRelativePermeabilityHysteresis::TableRelativePermeabilityHysteresis( std::s
"To neglect hysteresis on this phase, just use the same table name for the drainage and imbibition curves" );
// hysteresis input parameters
-
- registerWrapper( viewKeyStruct::landParameterString(), &m_landParam ).
- setInputFlag( InputFlags::FALSE ). // will be deduced from tables
+ registerWrapper( viewKeyStruct::phaseHasHysteresisString(), &m_phaseHasHysteresis ).
+ setInputFlag( InputFlags::FALSE )
+ . // will be deduced from tables
setSizedFromParent( 0 );
- // forwarded to KilloughHysteresis
- registerWrapper( KilloughHysteresis::viewKeyStruct::jerauldParameterAString(), &m_jerauldParam_a ).
- setInputFlag( InputFlags::OPTIONAL ).
- setApplyDefaultValue( 0.1 ).
- setDescription( "First parameter (modification parameter) introduced by Jerauld in the Land trapping model (see RTD documentation)." );
-
- registerWrapper( KilloughHysteresis::viewKeyStruct::jerauldParameterBString(), &m_jerauldParam_b ).
- setInputFlag( InputFlags::OPTIONAL ).
- setApplyDefaultValue( 0.0 ).
- setDescription( "Second parameter (modification parameter) introduced by Jerauld in the Land trapping model (see RTD documentation)." );
+ registerField< fields::relperm::phaseMaxHistoricalVolFraction >(
+ &m_phaseMaxHistoricalVolFraction );
+ registerField< fields::relperm::phaseMinHistoricalVolFraction >(
+ &m_phaseMinHistoricalVolFraction );
- registerWrapper( KilloughHysteresis::viewKeyStruct::killoughCurvatureParameterString(), &m_killoughCurvatureParamRelPerm ).
- setInputFlag( InputFlags::OPTIONAL ).
- setApplyDefaultValue( 1.0 ).
- setDescription( "Curvature parameter introduced by Killough for wetting-phase hysteresis (see RTD documentation)." );
-
- // structs
+ /// Killough data
registerWrapper( viewKeyStruct::drainageRelPermKernelWrappersString(),
&m_drainageRelPermKernelWrappers ).
setSizedFromParent( 0 ).
- setRestartFlags( RestartFlags::NO_WRITE );
+ setRestartFlags(
+ RestartFlags::NO_WRITE );
registerWrapper( viewKeyStruct::imbibitionRelPermKernelWrappersString(),
&m_imbibitionRelPermKernelWrappers ).
setSizedFromParent( 0 ).
- setRestartFlags( RestartFlags::NO_WRITE );
+ setRestartFlags(
+ RestartFlags::NO_WRITE );
- // Killough data
- registerWrapper( viewKeyStruct::wettingCurveString(), &m_wettingCurve ).
+ registerWrapper( viewKeyStruct::landParameterString(), &m_landParam ).
setInputFlag( InputFlags::FALSE ). // will be deduced from tables
- setSizedFromParent( 0 ).
- setRestartFlags( RestartFlags::NO_WRITE );
+ setSizedFromParent( 0 );
+
+
+ registerWrapper( viewKeyStruct::wettingCurveString(), &m_wettingCurve ).
+ setInputFlag(
+ InputFlags::FALSE ). // will be deduced from tables
+ setSizedFromParent(
+ 0 )
+ .setRestartFlags( RestartFlags::NO_WRITE );
registerWrapper( viewKeyStruct::nonWettingCurveString(), &m_nonWettingCurve ).
- setInputFlag( InputFlags::FALSE ). // will be deduced from tables
- setSizedFromParent( 0 ).
- setRestartFlags( RestartFlags::NO_WRITE );
+ setInputFlag(
+ InputFlags::FALSE ). // will be deduced from tables
+ setSizedFromParent(
+ 0 )
+ .setRestartFlags( RestartFlags::NO_WRITE );
+
+ //Forwarded to KilloughHysteresis
+ registerWrapper( KilloughHysteresis::viewKeyStruct::jerauldParameterAString(), &m_jerauldParam_a ).
+ setInputFlag( InputFlags::OPTIONAL ).
+ setApplyDefaultValue( 0.1 ).
+ setDescription(
+ "First parameter (modification parameter) introduced by Jerauld in the Land trapping model (see RTD documentation)." );
+
+ registerWrapper( KilloughHysteresis::viewKeyStruct::jerauldParameterBString(), &m_jerauldParam_b ).
+ setInputFlag( InputFlags::OPTIONAL ).
+ setApplyDefaultValue( 0.0 ).
+ setDescription(
+ "Second parameter (modification parameter) introduced by Jerauld in the Land trapping model (see RTD documentation)." );
+
+ registerWrapper(KilloughHysteresis::viewKeyStruct::killoughCurvatureParameterRelPermString(), &m_killoughCurvatureParamRelPerm ).
+ setInputFlag(
+ InputFlags::OPTIONAL ).
+ setApplyDefaultValue(
+ 1.0 ).
+ setDescription(
+ "Curvature parameter introduced by Killough for wetting-phase hysteresis (see RTD documentation)." );
registerWrapper( viewKeyStruct::waterOilMaxRelPermString(), &m_waterOilMaxRelPerm ).
setInputFlag( InputFlags::FALSE ). // will be deduced from tables
@@ -165,7 +186,6 @@ void TableRelativePermeabilityHysteresis::postInputInitialization()
getFullName() ),
InputError, getDataContext() );
- m_phaseMinVolumeFraction.resize( numPhases );
m_phaseHasHysteresis.resize( numPhases );
//initialize STONE-II only used var to avoid discrepancies in baselines
@@ -228,14 +248,14 @@ void TableRelativePermeabilityHysteresis::postInputInitialization()
}
GEOS_THROW_IF( m_phaseHasHysteresis[IPT::WETTING] == 0 && m_phaseHasHysteresis[IPT::NONWETTING] == 0,
- GEOS_FMT( "{}: we must use {} or {} to specify at least one imbibition relative permeability table",
- getFullName(),
- viewKeyStruct::imbibitionWettingRelPermTableNameString(),
- viewKeyStruct::imbibitionNonWettingRelPermTableNameString() ),
+ GEOS_FMT( "{}: we must use {} or {} to specify at least one imbibition relative permeability table",
+ getFullName(),
+ viewKeyStruct::imbibitionWettingRelPermTableNameString(),
+ viewKeyStruct::imbibitionNonWettingRelPermTableNameString() ),
InputError, getDataContext() );
//Killough section
- KilloughHysteresis::postProcessInput( m_jerauldParam_a, m_jerauldParam_b, m_killoughCurvatureParamRelPerm );
+ KilloughHysteresis::postProcessInput(m_jerauldParam_a, m_jerauldParam_b, m_killoughCurvatureParamRelPerm, 0.0);
}
void TableRelativePermeabilityHysteresis::initializePreSubGroups()
@@ -262,73 +282,65 @@ void TableRelativePermeabilityHysteresis::checkExistenceAndValidateWettingRelPer
using IPT = TableRelativePermeabilityHysteresis::ImbibitionPhasePairPhaseType;
integer const numPhases = m_phaseNames.size();
integer ipWetting = -1, ipNonWetting = -1;
- std::tie( ipWetting, ipNonWetting ) = RelativePermeabilityBase::wettingAndNonWettingPhaseIndices();
+ std::tie( ipWetting, ipNonWetting ) = RelativePermeabilityBase::phaseIndex( m_phaseOrder );
// Step 1.a: take care of the two-phase case
- real64 drainagePhaseMinVolFraction = -1; // output
- real64 drainagePhaseMaxVolFraction = -1;
- real64 drainagePhaseRelPermMinEndPoint = -1;
- real64 drainagePhaseRelPermMaxEndPoint = -1;
-
- string const tableName = ( numPhases == 2 ) ?
- m_drainageWettingNonWettingRelPermTableNames[0] : m_drainageWettingIntermediateRelPermTableNames[0];
-
- checkExistenceAndValidateRelPermTable( tableName, // input
- drainagePhaseMinVolFraction, // output
+ real64 drainagePhaseMinVolFraction, // output
+ drainagePhaseMaxVolFraction,
+ drainagePhaseRelPermMinEndPoint,
+ drainagePhaseRelPermMaxEndPoint;
+ GEOS_ASSERT( m_drainageWettingNonWettingRelPermTableNames.size() == 2 );
+ auto tableName = ( numPhases == 2 ) ? m_drainageWettingNonWettingRelPermTableNames[0] : m_drainageWettingIntermediateRelPermTableNames[0];
+// integer const ipWetting = ( m_phaseOrder[PhaseType::WATER] >= 0 ) ? m_phaseOrder[PhaseType::WATER] : m_phaseOrder[PhaseType::OIL];
+ checkExistenceAndValidateRelPermTable( tableName, // input
+ drainagePhaseMinVolFraction, // output
drainagePhaseMaxVolFraction,
drainagePhaseRelPermMinEndPoint,
drainagePhaseRelPermMaxEndPoint );
- // imbibition if provided
- real64 imbibitionPhaseMinVolFraction = drainagePhaseMinVolFraction; // output
- real64 imbibitionPhaseMaxVolFraction = drainagePhaseMaxVolFraction;
- real64 imbibitionPhaseRelPermMinEndPoint = drainagePhaseRelPermMinEndPoint;
- real64 imbibitionPhaseRelPermMaxEndPoint = drainagePhaseRelPermMaxEndPoint;
+
+ //imbibition if provided
+ real64 imbibitionPhaseMinVolFraction, // output
+ imbibitionPhaseMaxVolFraction,
+ imbibitionPhaseRelPermMinEndPoint,
+ imbibitionPhaseRelPermMaxEndPoint;
if( m_phaseHasHysteresis[IPT::WETTING] )
{
- checkExistenceAndValidateRelPermTable( m_imbibitionWettingRelPermTableName, // input
- imbibitionPhaseMinVolFraction, // output
+ checkExistenceAndValidateRelPermTable( m_imbibitionWettingRelPermTableName, // input
+ imbibitionPhaseMinVolFraction, // output
imbibitionPhaseMaxVolFraction,
imbibitionPhaseRelPermMinEndPoint,
imbibitionPhaseRelPermMaxEndPoint );
GEOS_THROW_IF( !isZero( imbibitionPhaseMinVolFraction - drainagePhaseMinVolFraction ),
- GEOS_FMT( "{}: the critical wetting-phase volume fraction (saturation) must be the same in drainage and imbibition.\n"
- "However, we found that the drainage critical wetting-phase volume fraction is {}, "
- "whereas the imbibition critical wetting-phase volume fraction is {}",
- getFullName(),
- drainagePhaseMinVolFraction, imbibitionPhaseMinVolFraction ),
+ GEOS_FMT( "{}: the critical wetting-phase volume fraction (saturation) must be the same in drainage and imbibition.\n"
+ "However, we found that the drainage critical wetting-phase volume fraction is {}, "
+ "whereas the imbibition critical wetting-phase volume fraction is {}",
+ getFullName(),
+ drainagePhaseMinVolFraction, imbibitionPhaseMinVolFraction ),
InputError, getDataContext() );
GEOS_THROW_IF( imbibitionPhaseMaxVolFraction > drainagePhaseMaxVolFraction,
- GEOS_FMT( "{}: the maximum wetting-phase volume fraction (saturation) must be smaller in imbibition (compared to the drainage value).\n"
- "However, we found that the drainage maximum wetting-phase volume fraction is {}, "
- "whereas the imbibition maximum wetting-phase volume fraction is {}",
- getFullName(),
- drainagePhaseMaxVolFraction, imbibitionPhaseMaxVolFraction ),
+ GEOS_FMT( "{}: the maximum wetting-phase volume fraction (saturation) must be smaller in imbibition (compared to the drainage value).\n"
+ "However, we found that the drainage maximum wetting-phase volume fraction is {}, "
+ "whereas the imbibition maximum wetting-phase volume fraction is {}",
+ getFullName(),
+ drainagePhaseMaxVolFraction, imbibitionPhaseMaxVolFraction ),
InputError, getDataContext() );
GEOS_THROW_IF( imbibitionPhaseRelPermMaxEndPoint > drainagePhaseRelPermMaxEndPoint,
- GEOS_FMT( "{}: the maximum wetting-phase relperm must be smaller in imbibition (compared to the drainage value).\n"
- "However, we found that the drainage maximum wetting-phase relperm is {}, "
- "whereas the imbibition maximum wetting-phase relperm is {}",
- getFullName(),
- drainagePhaseRelPermMaxEndPoint, imbibitionPhaseRelPermMaxEndPoint ),
+ GEOS_FMT( "{}: the maximum wetting-phase relperm must be smaller in imbibition (compared to the drainage value).\n"
+ "However, we found that the drainage maximum wetting-phase relperm is {}, "
+ "whereas the imbibition maximum wetting-phase relperm is {}",
+ getFullName(),
+ drainagePhaseRelPermMaxEndPoint, imbibitionPhaseRelPermMaxEndPoint ),
InputError, getDataContext() );
}
- m_wettingCurve.setPoints( drainagePhaseMinVolFraction, drainagePhaseRelPermMinEndPoint, // same as imbibition min
- imbibitionPhaseMaxVolFraction, imbibitionPhaseRelPermMaxEndPoint,
- drainagePhaseMaxVolFraction, drainagePhaseRelPermMaxEndPoint );
-
- m_phaseMinVolumeFraction[ipWetting] = drainagePhaseMinVolFraction;
-
- GEOS_LOG_LEVEL_RANK_0( logInfo::Init, GEOS_FMT( "Initializing wetting relperm curve with {(smin,krmin), (simax,krimax), (sdmax,krdmax)} : {({},{}),({},{}),({},{})}",
- m_wettingCurve.m_extremumPhaseVolFraction, m_wettingCurve.m_extremumValue,
- m_wettingCurve.m_criticalImbibitionPhaseVolFraction, m_wettingCurve.m_criticalImbibitionValue,
- m_wettingCurve.m_criticalDrainagePhaseVolFraction, m_wettingCurve.m_criticalDrainageValue
- ));
+ m_wettingCurve.setPoints( {drainagePhaseMinVolFraction, drainagePhaseRelPermMinEndPoint}, // extremum
+ {imbibitionPhaseMaxVolFraction, imbibitionPhaseRelPermMaxEndPoint}, // imbibition critical
+ {drainagePhaseMaxVolFraction, drainagePhaseRelPermMaxEndPoint} ); // drainage critical
}
void TableRelativePermeabilityHysteresis::checkExistenceAndValidateNonWettingRelPermTables()
@@ -337,17 +349,17 @@ void TableRelativePermeabilityHysteresis::checkExistenceAndValidateNonWettingRel
integer const numPhases = m_phaseNames.size();
integer ipWetting = -1, ipNonWetting = -1;
- std::tie( ipWetting, ipNonWetting ) = RelativePermeabilityBase::wettingAndNonWettingPhaseIndices();
-
- // treat drainage
- real64 drainagePhaseMinVolFraction = -1; // output
- real64 drainagePhaseMaxVolFraction = -1;
- real64 drainagePhaseRelPermMinEndPoint = -1;
- real64 drainagePhaseRelPermMaxEndPoint = -1;
+ std::tie( ipWetting, ipNonWetting ) = RelativePermeabilityBase::phaseIndex( m_phaseOrder );
+ //treat drainage
+ real64 drainagePhaseMinVolFraction, // output
+ drainagePhaseMaxVolFraction,
+ drainagePhaseRelPermMinEndPoint,
+ drainagePhaseRelPermMaxEndPoint;
// Step 1: Read the drainage for the non wetting phase
- string const tableName = ( numPhases == 2 ) ? m_drainageWettingNonWettingRelPermTableNames[1] :
- m_drainageNonWettingIntermediateRelPermTableNames[0];
+ GEOS_ASSERT( m_drainageWettingNonWettingRelPermTableNames.size() == 2 );
+ auto tableName = ( numPhases == 2 ) ? m_drainageWettingNonWettingRelPermTableNames[1] :
+ m_drainageNonWettingIntermediateRelPermTableNames[0];
checkExistenceAndValidateRelPermTable( tableName, // input
drainagePhaseMinVolFraction, // output
drainagePhaseMaxVolFraction,
@@ -355,10 +367,10 @@ void TableRelativePermeabilityHysteresis::checkExistenceAndValidateNonWettingRel
drainagePhaseRelPermMaxEndPoint );
// Step 2: validate non-wetting-phase imbibition relative permeability table
- real64 imbibitionPhaseMinVolFraction = drainagePhaseMinVolFraction; // output
- real64 imbibitionPhaseMaxVolFraction = drainagePhaseMaxVolFraction;
- real64 imbibitionPhaseRelPermMinEndPoint = drainagePhaseRelPermMinEndPoint;
- real64 imbibitionPhaseRelPermMaxEndPoint = drainagePhaseRelPermMaxEndPoint;
+ real64 imbibitionPhaseMinVolFraction, // output
+ imbibitionPhaseMaxVolFraction,
+ imbibitionPhaseRelPermMinEndPoint,
+ imbibitionPhaseRelPermMaxEndPoint;
if( m_phaseHasHysteresis[IPT::NONWETTING] )
{
@@ -370,42 +382,35 @@ void TableRelativePermeabilityHysteresis::checkExistenceAndValidateNonWettingRel
imbibitionPhaseRelPermMaxEndPoint );
GEOS_THROW_IF( !isZero ( imbibitionPhaseMaxVolFraction - drainagePhaseMaxVolFraction ),
- GEOS_FMT( string( "{}: the maximum non-wetting-phase volume fraction (saturation) must be the same in drainage and imbibition.\n" )
- + string( "However, we found that the drainage maximum wetting-phase volume fraction is {}, " )
- + string( "whereas the imbibition maximum wetting-phase volume fraction is {}" ),
- getFullName(),
- drainagePhaseMaxVolFraction, imbibitionPhaseMaxVolFraction ),
+ GEOS_FMT( string( "{}: the maximum non-wetting-phase volume fraction (saturation) must be the same in drainage and imbibition.\n" )
+ + string( "However, we found that the drainage maximum wetting-phase volume fraction is {}, " )
+ + string( "whereas the imbibition maximum wetting-phase volume fraction is {}" ),
+ getFullName(),
+ drainagePhaseMaxVolFraction, imbibitionPhaseMaxVolFraction ),
InputError, getDataContext() );
GEOS_THROW_IF( !isZero ( imbibitionPhaseRelPermMaxEndPoint - drainagePhaseRelPermMaxEndPoint ),
- GEOS_FMT( string( "{}: the non-wetting-phase relperm endpoint must be the same in drainage and imbibition.\n" )
- + string( "However, we found that the drainage endpoint wetting-phase relperm is {}, " )
- + string( "whereas the imbibition endpoint wetting-phase relperm is {}" ),
- getFullName(),
- drainagePhaseRelPermMaxEndPoint, imbibitionPhaseRelPermMaxEndPoint ),
+ GEOS_FMT( string( "{}: the non-wetting-phase relperm endpoint must be the same in drainage and imbibition.\n" )
+ + string( "However, we found that the drainage endpoint wetting-phase relperm is {}, " )
+ + string( "whereas the imbibition endpoint wetting-phase relperm is {}" ),
+ getFullName(),
+ drainagePhaseRelPermMaxEndPoint, imbibitionPhaseRelPermMaxEndPoint ),
InputError, getDataContext() );
GEOS_THROW_IF( imbibitionPhaseMinVolFraction < drainagePhaseMinVolFraction,
- GEOS_FMT( string( "{}: the critical wetting-phase volume fraction (saturation) must be larger in imbibition (compared to the drainage value).\n" )
- + string( "However, we found that the drainage critical wetting-phase volume fraction is {}, " )
- + string( "whereas the imbibition critical wetting-phase volume fraction is {}" ),
- getFullName(),
- drainagePhaseMinVolFraction, imbibitionPhaseMinVolFraction ),
+ GEOS_FMT( string( "{}: the critical wetting-phase volume fraction (saturation) must be larger in imbibition (compared to the drainage value).\n" )
+ + string( "However, we found that the drainage critical wetting-phase volume fraction is {}, " )
+ + string( "whereas the imbibition critical wetting-phase volume fraction is {}" ),
+ getFullName(),
+ drainagePhaseMinVolFraction, imbibitionPhaseMinVolFraction ),
InputError, getDataContext() );
}
- m_nonWettingCurve.setPoints( drainagePhaseMaxVolFraction, drainagePhaseRelPermMaxEndPoint, // same as imbibition max
- imbibitionPhaseMinVolFraction, imbibitionPhaseRelPermMinEndPoint,
- drainagePhaseMinVolFraction, drainagePhaseRelPermMinEndPoint );
-
- m_phaseMinVolumeFraction[ipNonWetting] = drainagePhaseMinVolFraction;
- GEOS_LOG_LEVEL_RANK_0( logInfo::Init, GEOS_FMT( "Initializing non-wetting relperm curve with {(sdmin,krdmin), (simin,krimin), (smax,krmax)} : {({},{}),({},{}),({},{})}",
- m_nonWettingCurve.m_criticalDrainagePhaseVolFraction, m_nonWettingCurve.m_criticalDrainageValue,
- m_nonWettingCurve.m_criticalImbibitionPhaseVolFraction, m_nonWettingCurve.m_criticalImbibitionValue,
- m_nonWettingCurve.m_extremumPhaseVolFraction, m_nonWettingCurve.m_extremumValue
- ));
+ m_nonWettingCurve.setPoints( {drainagePhaseMaxVolFraction, drainagePhaseRelPermMaxEndPoint}, // extremum
+ {imbibitionPhaseMinVolFraction, imbibitionPhaseRelPermMinEndPoint}, // imbibition critical
+ {drainagePhaseMinVolFraction, drainagePhaseRelPermMinEndPoint} ); // drainage critical
}
@@ -414,19 +419,21 @@ void TableRelativePermeabilityHysteresis::checkExistenceAndValidateIntermediateR
if( m_phaseNames.size() == 3 )
{
- real64 drainagePhaseMinVolFraction,
+
+ real64 drainagePhaseMinVolFraction, //
drainagePhaseMaxVolFraction,
drainagePhaseRelPermMinEndPoint,
drainagePhaseRelPermMaxEndPoint;
- // intermediate drainage from wetting
+ //intermediate drainage from wetting
checkExistenceAndValidateRelPermTable( m_drainageWettingIntermediateRelPermTableNames[1], // input
drainagePhaseMinVolFraction, // output
drainagePhaseMaxVolFraction,
drainagePhaseRelPermMinEndPoint,
drainagePhaseRelPermMaxEndPoint );
+ //??
checkExistenceAndValidateRelPermTable( m_drainageNonWettingIntermediateRelPermTableNames[1], // input
drainagePhaseMinVolFraction,
drainagePhaseMaxVolFraction,
@@ -468,9 +475,8 @@ void TableRelativePermeabilityHysteresis::computeLandCoefficient()
// For two-phase flow, we make sure that they are equal
m_landParam.resize( 2 );
- // Note: for simplicity, the notations are taken classical reservoir notations (although this breaks our phaseVolFrac naming convention)
+ // Note: for simplicity, the notations are taken reservoir simulation literature (although this breaks our phaseVolFrac naming convention)
using IPT = TableRelativePermeabilityHysteresis::ImbibitionPhasePairPhaseType;
-
KilloughHysteresis::computeLandCoefficient( m_wettingCurve, m_landParam[IPT::WETTING] );
KilloughHysteresis::computeLandCoefficient( m_nonWettingCurve, m_landParam[IPT::NONWETTING] );
}
@@ -566,12 +572,14 @@ void TableRelativePermeabilityHysteresis::allocateConstitutiveData( Group & pare
{
integer const numPhases = numFluidPhases();
- m_phaseMinVolumeFraction.resize( numPhases );
- m_phaseMaxHistoricalVolFraction.resize( 0, numPhases );
- m_phaseMinHistoricalVolFraction.resize( 0, numPhases );
-
RelativePermeabilityBase::allocateConstitutiveData( parent, numPts );
+ m_phaseMaxHistoricalVolFraction.resize( parent.size(), numPhases );
+ m_phaseMinHistoricalVolFraction.resize( parent.size(), numPhases );
+
+ m_phaseMaxHistoricalVolFraction.setValues< parallelDevicePolicy<> >( 0.0 );
+ m_phaseMinHistoricalVolFraction.setValues< parallelDevicePolicy<> >( 1.0 );
+
m_phaseMaxHistoricalVolFraction.setValues< parallelDevicePolicy<> >( 0.0 );
m_phaseMinHistoricalVolFraction.setValues< parallelDevicePolicy<> >( 1.0 );
}
@@ -597,24 +605,25 @@ void TableRelativePermeabilityHysteresis::saveConvergedPhaseVolFractionState( ar
}
-TableRelativePermeabilityHysteresis::KernelWrapper::KernelWrapper( arrayView1d< TableFunction::KernelWrapper const > const & drainageRelPermKernelWrappers,
- arrayView1d< TableFunction::KernelWrapper const > const & imbibitionRelPermKernelWrappers,
- arrayView1d< integer const > const & phaseHasHysteresis,
- arrayView1d< real64 const > const & landParam,
+TableRelativePermeabilityHysteresis::KernelWrapper::
+ KernelWrapper( arrayView1d< TableFunction::KernelWrapper const > const & drainageRelPermKernelWrappers,
+ arrayView1d< TableFunction::KernelWrapper const > const & imbibitionRelPermKernelWrappers,
+ arrayView1d< integer const > const & phaseHasHysteresis,
+ arrayView1d< real64 const > const & landParam,
real64 const & jerauldParam_a,
real64 const & jerauldParam_b,
real64 const & killoughCurvatureParamRelPerm,
KilloughHysteresis::HysteresisCurve const & wettingCurve,
KilloughHysteresis::HysteresisCurve const & nonWettingCurve,
- arrayView1d< integer const > const & phaseTypes,
- arrayView1d< integer const > const & phaseOrder,
- ThreePhaseInterpolator const & threePhaseInterpolator,
- real64 const & waterOilRelPermMaxValue,
- arrayView2d< real64 const, compflow::USD_PHASE > const & phaseMinHistoricalVolFraction,
- arrayView2d< real64 const, compflow::USD_PHASE > const & phaseMaxHistoricalVolFraction,
- arrayView3d< real64, relperm::USD_RELPERM > const & phaseTrappedVolFrac,
- arrayView3d< real64, relperm::USD_RELPERM > const & phaseRelPerm,
- arrayView4d< real64, relperm::USD_RELPERM_DS > const & dPhaseRelPerm_dPhaseVolFrac )
+ arrayView1d< integer const > const & phaseTypes,
+ arrayView1d< integer const > const & phaseOrder,
+ ThreePhaseInterpolator const & threePhaseInterpolator,
+ real64 const & waterOilRelPermMaxValue,
+ arrayView2d< real64 const, compflow::USD_PHASE > const & phaseMinHistoricalVolFraction,
+ arrayView2d< real64 const, compflow::USD_PHASE > const & phaseMaxHistoricalVolFraction,
+ arrayView3d< real64, relperm::USD_RELPERM > const & phaseTrappedVolFrac,
+ arrayView3d< real64, relperm::USD_RELPERM > const & phaseRelPerm,
+ arrayView4d< real64, relperm::USD_RELPERM_DS > const & dPhaseRelPerm_dPhaseVolFrac )
:
RelativePermeabilityBaseUpdate( phaseTypes,
phaseOrder,
diff --git a/src/coreComponents/constitutive/relativePermeability/TableRelativePermeabilityHysteresis.hpp b/src/coreComponents/constitutive/relativePermeability/TableRelativePermeabilityHysteresis.hpp
index 3f0d5611f4c..df63094083c 100644
--- a/src/coreComponents/constitutive/relativePermeability/TableRelativePermeabilityHysteresis.hpp
+++ b/src/coreComponents/constitutive/relativePermeability/TableRelativePermeabilityHysteresis.hpp
@@ -21,12 +21,11 @@
#define GEOS_CONSTITUTIVE_TABLERELATIVEPERMEABILITYHYSTERESIS_HPP
-#include "constitutive/relativePermeability/KilloughHysteresis.hpp"
+#include "constitutive/KilloughHysteresis.hpp"
#include "constitutive/relativePermeability/RelativePermeabilityBase.hpp"
#include "constitutive/relativePermeability/RelativePermeabilityInterpolators.hpp"
#include "functions/TableFunction.hpp"
-
-
+///helper model class with data struct for curves and computing recipe for trapped and Land Coeff
namespace geos
{
@@ -79,8 +78,6 @@ class TableRelativePermeabilityHysteresis : public RelativePermeabilityBase
virtual string getCatalogName() const override { return catalogName(); }
- virtual void allocateConstitutiveData( dataRepository::Group & parent, localIndex const numPts ) override;
-
/// Type of kernel wrapper for in-kernel update
class KernelWrapper final : public RelativePermeabilityBaseUpdate
{
@@ -140,7 +137,24 @@ class TableRelativePermeabilityHysteresis : public RelativePermeabilityBase
real64 & phaseRelPerm,
real64 & dPhaseRelPerm_dPhaseVolFrac ) const;
-
+ /**
+ * @brief Function computing the trapped critical phase volume fraction (Sgcrt)
+ * @param[in] Scrd the drainage critical phase volume fraction
+ * @param[in] Shy the max historical phase volume fraction
+ * @param[in] Smx the max phase volume fraction (= end-point phase volume fraction)
+ * @param[in] jerauldParam_a first (modification) parameter proposed by Jerauld
+ * @param[in] jerauldParam_b second (exponent) parameter proposed by Jerauld
+ * @param[in] landParam Land trapping parameter
+ * @param[out] Scrt the trapped critical phase volume fraction
+ */
+ GEOS_HOST_DEVICE
+ void computeTrappedCriticalPhaseVolFraction( real64 const & Scrd,
+ real64 const & Shy,
+ real64 const & Smx,
+ real64 const & jerauldParam_a,
+ real64 const & jerauldParam_b,
+ real64 const & landParam,
+ real64 & Scrt ) const;
/**
* @brief Function updating the relperm (and derivative) for the wetting phase in imbibition using Killough's method
* @param[in] drainageRelPermKernelWrapper kernel wrapper storing the drainage relperm table for the wetting phase
@@ -270,19 +284,13 @@ class TableRelativePermeabilityHysteresis : public RelativePermeabilityBase
/// Trapping parameter from the Land model (typically called C)
arrayView1d< real64 const > m_landParam;
- /// Parameter a introduced by Jerauld in the Land model
real64 const & m_jerauldParam_a;
- /// Parameter b introduced by Jerauld in the Land model
real64 const & m_jerauldParam_b;
- /// Curvature parameter introduced for wetting phase hysteresis in Killough
real64 const & m_killoughCurvatureParamRelPerm;
- /// The wetting phase hysteretic curve
KilloughHysteresis::HysteresisCurve const & m_wettingCurve;
-
- /// The non-wetting phase hysteretic curve
KilloughHysteresis::HysteresisCurve const & m_nonWettingCurve;
/// Minimum historical phase volume fraction for each phase
@@ -307,17 +315,17 @@ class TableRelativePermeabilityHysteresis : public RelativePermeabilityBase
struct viewKeyStruct : RelativePermeabilityBase::viewKeyStruct
{
- /// Land coefficient
+ ///Land Coeff
static constexpr char const * landParameterString() { return "landParameter"; }
- /// Hysteretic curves
+ ///and packed curves data struct
static constexpr char const * wettingCurveString() { return "wettingCurve"; };
static constexpr char const * nonWettingCurveString() { return "nonWettingCurve"; };
- /// Flag to determine whether a phase has hysteresis or not
+ ///flag
static constexpr char const * phaseHasHysteresisString() { return "phaseHasHysteresis"; }
- /// Tables and associated wrappers
+ ///tables and assoc. wrappers
static constexpr char const * drainageRelPermKernelWrappersString() { return "drainageRelPermWrappers"; }
static constexpr char const * imbibitionRelPermKernelWrappersString() { return "imbibitionRelPermWrappers"; }
@@ -334,18 +342,18 @@ class TableRelativePermeabilityHysteresis : public RelativePermeabilityBase
};
- arrayView1d< real64 const > getPhaseMinVolumeFraction() const override { return m_phaseMinVolumeFraction; };
-
real64 getWettingPhaseMinVolumeFraction() const override
{
- return m_wettingCurve.m_extremumPhaseVolFraction;
+ return m_wettingCurve.oppositeBoundPhaseVolFraction;
}
real64 getNonWettingMinVolumeFraction() const override
{
- return m_nonWettingCurve.m_criticalDrainagePhaseVolFraction;
+ return m_nonWettingCurve.oppositeBoundPhaseVolFraction;
}
+ virtual void allocateConstitutiveData( Group & parent, localIndex const numPts ) override;
+
private:
virtual void postInputInitialization() override;
@@ -453,17 +461,10 @@ class TableRelativePermeabilityHysteresis : public RelativePermeabilityBase
/// Maximum historical phase volume fraction for each phase
array2d< real64, compflow::LAYOUT_PHASE > m_phaseMaxHistoricalVolFraction;
- /// The wetting phase hysteretic curve
KilloughHysteresis::HysteresisCurve m_wettingCurve;
-
- /// The non-wetting phase hysteretic curve
KilloughHysteresis::HysteresisCurve m_nonWettingCurve;
- /// Min phase volume fractions (deduced from the tables). With Baker, only the water phase entry is used
- array1d< real64 > m_phaseMinVolumeFraction;
-
real64 m_waterOilMaxRelPerm;
-
ThreePhaseInterpolator m_threePhaseInterpolator;
};
@@ -498,13 +499,13 @@ TableRelativePermeabilityHysteresis::KernelWrapper::
// if consistent, S should be equal to 1 - imbibitionPhaseMinVolNonWettingFraction for two-phase flow
// (but wetting and nonwetting phase hysteresis are implemented in a decoupled fashion)
real64 const S = phaseVolFraction;
- real64 const Smxi = m_wettingCurve.m_criticalImbibitionPhaseVolFraction;
- real64 const Smxd = m_wettingCurve.m_criticalDrainagePhaseVolFraction;
+ real64 const Smxi = m_wettingCurve.imbibitionExtremaPhaseVolFraction;
+ real64 const Smxd = m_wettingCurve.drainageExtremaPhaseVolFraction;
// Swc is the common end min endpoint saturation for wetting curves
- real64 const Swc = m_wettingCurve.m_extremumPhaseVolFraction;
+ real64 const Swc = m_wettingCurve.oppositeBoundPhaseVolFraction;
- using IPT = ImbibitionPhasePairPhaseType;
+ using IPT = TableRelativePermeabilityHysteresis::ImbibitionPhasePairPhaseType;
if( S <= Swc )
{
phaseRelPerm = 0.0;
@@ -512,12 +513,12 @@ TableRelativePermeabilityHysteresis::KernelWrapper::
}
else if( S >= Smxd )
{
- phaseRelPerm = m_wettingCurve.m_criticalDrainageValue;
+ phaseRelPerm = m_wettingCurve.drainageExtremaSCALValue;
dPhaseRelPerm_dPhaseVolFrac = 0.0;
}
else
{
- real64 const krwei = m_wettingCurve.m_criticalImbibitionValue;
+ real64 const krwei = m_wettingCurve.imbibitionExtremaSCALValue;
real64 const krwedAtSmxi = drainageRelPermKernelWrapper.compute( &Smxi );
// Step 1: Compute the new end point
@@ -578,12 +579,12 @@ TableRelativePermeabilityHysteresis::KernelWrapper::
// Step 1: for a given value of the max historical saturation, Shy, compute the trapped critical saturation, Scrt,
// using Land's method. The calculation includes the modifications from Jerauld.
real64 const S = phaseVolFraction;
- real64 const Scri = m_nonWettingCurve.m_criticalImbibitionPhaseVolFraction;
- real64 const Smx = m_nonWettingCurve.m_extremumPhaseVolFraction;
+ real64 const Scri = m_nonWettingCurve.imbibitionExtremaPhaseVolFraction;
+ real64 const Smx = m_nonWettingCurve.oppositeBoundPhaseVolFraction;
real64 const Shy = (phaseMaxHistoricalVolFraction < Smx) ? phaseMaxHistoricalVolFraction : Smx; // to make sure that Shy < Smax
real64 Scrt = 0;
- using IPT = ImbibitionPhasePairPhaseType;
+ using IPT = TableRelativePermeabilityHysteresis::ImbibitionPhasePairPhaseType;
KilloughHysteresis::computeTrappedCriticalPhaseVolFraction( m_nonWettingCurve,
Shy,
m_landParam[IPT::NONWETTING],
@@ -598,13 +599,13 @@ TableRelativePermeabilityHysteresis::KernelWrapper::
}
else if( S >= Smx ) // S is above the max saturation, so we just skip the rest and set the relperm to the endpoint
{
- phaseRelPerm = m_nonWettingCurve.m_extremumValue;
+ phaseRelPerm = m_nonWettingCurve.oppositeBoundSCALValue;
dPhaseRelPerm_dPhaseVolFrac = 0.0;
}
else
{
// Step 2: compute the normalized saturation, S_norm, at which the imbibition relperm curve will be evaluated.
- real64 const ratio = ( Smx - Scri ) / ( Shy - Scrt );
+ real64 const ratio = ( Smx - Scri ) / ( Shy - Scrt ); // non S-deps part (isolated for derivatives calculations)
real64 const Snorm = Scri + ( S - Scrt ) * ratio; // normalized saturation
real64 const dSnorm_dS = ratio;
@@ -618,7 +619,7 @@ TableRelativePermeabilityHysteresis::KernelWrapper::
real64 const krdAtShy = drainageRelPermKernelWrapper.compute( &Shy );
// Step 5: evaluate the drainage relperm, krd(Smx), at the max drainage saturation, Smx.
- real64 const krdAtSmx = m_nonWettingCurve.m_extremumValue;
+ real64 const krdAtSmx = m_nonWettingCurve.oppositeBoundSCALValue;
// Step 6: apply the formula blending drainage and imbibition relperms from the Killough model.
real64 const drainageRelPermRatio = krdAtShy / krdAtSmx;
@@ -646,11 +647,29 @@ TableRelativePermeabilityHysteresis::KernelWrapper::
using TPT = constitutive::TableRelativePermeabilityHysteresis::TwoPhasePairPhaseType;
using IPT = constitutive::TableRelativePermeabilityHysteresis::ImbibitionPhasePairPhaseType;
+ constexpr bool FORCE_IMBIBITION_MODE = false;
+ constexpr bool FORCE_DRAINAGE_MODE = false;
+
// ---------- wetting rel perm
- if( !m_phaseHasHysteresis[IPT::WETTING] ||
+ if( FORCE_DRAINAGE_MODE )
+ {
+ phaseTrappedVolFrac[ipWetting] = LvArray::math::min( phaseVolFraction[ipWetting], m_wettingCurve.oppositeBoundPhaseVolFraction );
+ computeDrainageRelPerm( m_drainageRelPermKernelWrappers[TPT::WETTING],
+ phaseVolFraction[ipWetting],
+ phaseRelPerm[ipWetting],
+ dPhaseRelPerm_dPhaseVolFrac[ipWetting][ipWetting] );
+ }
+ else if( FORCE_IMBIBITION_MODE )
+ {
+ phaseTrappedVolFrac[ipWetting] = LvArray::math::min( phaseVolFraction[ipWetting], m_wettingCurve.oppositeBoundPhaseVolFraction );
+ auto const & imbibitionRelPermKernelWrapper = m_imbibitionRelPermKernelWrappers[IPT::WETTING];
+ phaseRelPerm[ipWetting] = imbibitionRelPermKernelWrapper.compute( &phaseVolFraction[ipWetting],
+ &dPhaseRelPerm_dPhaseVolFrac[ipWetting][ipWetting] );
+ }
+ else if( !m_phaseHasHysteresis[IPT::WETTING] ||
phaseVolFraction[ipWetting] <= phaseMinHistoricalVolFraction[ipWetting] + flowReversalBuffer )
{
- phaseTrappedVolFrac[ipWetting] = LvArray::math::min( phaseVolFraction[ipWetting], m_wettingCurve.m_extremumPhaseVolFraction );
+ phaseTrappedVolFrac[ipWetting] = LvArray::math::min( phaseVolFraction[ipWetting], m_wettingCurve.oppositeBoundPhaseVolFraction );
computeDrainageRelPerm( m_drainageRelPermKernelWrappers[TPT::WETTING],
phaseVolFraction[ipWetting],
phaseRelPerm[ipWetting],
@@ -667,12 +686,37 @@ TableRelativePermeabilityHysteresis::KernelWrapper::
}
// --------- non-wetting rel perm
- if( !m_phaseHasHysteresis[IPT::NONWETTING] ||
+ if( FORCE_DRAINAGE_MODE )
+ {
+ phaseTrappedVolFrac[ipNonWetting] = LvArray::math::min( phaseVolFraction[ipNonWetting], m_nonWettingCurve.drainageExtremaPhaseVolFraction );
+ computeDrainageRelPerm( m_drainageRelPermKernelWrappers[TPT::NONWETTING],
+ phaseVolFraction[ipNonWetting],
+ phaseRelPerm[ipNonWetting],
+ dPhaseRelPerm_dPhaseVolFrac[ipNonWetting][ipNonWetting] );
+ }
+ else if( FORCE_IMBIBITION_MODE )
+ {
+ real64 const Shy = ( phaseVolFraction[ipNonWetting] < m_nonWettingCurve.oppositeBoundPhaseVolFraction )
+ ? phaseVolFraction[ipNonWetting] : m_nonWettingCurve.oppositeBoundPhaseVolFraction;
+ real64 Scrt = 0;
+ KilloughHysteresis::computeTrappedCriticalPhaseVolFraction( m_nonWettingCurve,
+ Shy,
+ m_landParam[IPT::NONWETTING],
+ m_jerauldParam_a,
+ m_jerauldParam_b,
+ Scrt );
+ phaseTrappedVolFrac[ipNonWetting] = LvArray::math::min( Scrt, phaseVolFraction[ipNonWetting] );
+
+ auto const & imbibitionRelPermKernelWrapper = m_imbibitionRelPermKernelWrappers[IPT::NONWETTING];
+ phaseRelPerm[ipNonWetting] = imbibitionRelPermKernelWrapper.compute( &phaseVolFraction[ipNonWetting],
+ &dPhaseRelPerm_dPhaseVolFrac[ipNonWetting][ipNonWetting] );
+ }
+ else if( !m_phaseHasHysteresis[IPT::NONWETTING] ||
phaseVolFraction[ipNonWetting] >= phaseMaxHistoricalVolFraction[ipNonWetting] - flowReversalBuffer )
{
// for reporting purposes, compute Sgcrt first
- real64 const Shy = ( phaseVolFraction[ipNonWetting] < m_nonWettingCurve.m_extremumPhaseVolFraction )
- ? phaseVolFraction[ipNonWetting] : m_nonWettingCurve.m_extremumPhaseVolFraction; // to make sure that Shy < Smax
+ real64 const Shy = ( phaseVolFraction[ipNonWetting] < m_nonWettingCurve.oppositeBoundPhaseVolFraction )
+ ? phaseVolFraction[ipNonWetting] : m_nonWettingCurve.oppositeBoundPhaseVolFraction; // to make sure that Shy < Smax
real64 Scrt = 0;
KilloughHysteresis::computeTrappedCriticalPhaseVolFraction( m_nonWettingCurve,
Shy,
@@ -721,13 +765,31 @@ TableRelativePermeabilityHysteresis::KernelWrapper::
using TPT = constitutive::TableRelativePermeabilityHysteresis::ThreePhasePairPhaseType;
using IPT = constitutive::TableRelativePermeabilityHysteresis::ImbibitionPhasePairPhaseType;
+ constexpr bool FORCE_IMBIBITION_MODE = false;
+ constexpr bool FORCE_DRAINAGE_MODE = false;
+
// 1) Wetting and intermediate phase relative permeabilities using two-phase wetting-intermediate data
// ---------- wetting rel perm
- if( !m_phaseHasHysteresis[IPT::WETTING] ||
+ if( FORCE_DRAINAGE_MODE )
+ {
+ phaseTrappedVolFrac[ipWetting] = LvArray::math::min( m_wettingCurve.oppositeBoundPhaseVolFraction, phaseVolFraction[ipWetting] );
+ computeDrainageRelPerm( m_drainageRelPermKernelWrappers[TPT::WETTING],
+ phaseVolFraction[ipWetting],
+ phaseRelPerm[ipWetting],
+ dPhaseRelPerm_dPhaseVolFrac[ipWetting][ipWetting] );
+ }
+ else if( FORCE_IMBIBITION_MODE )
+ {
+ phaseTrappedVolFrac[ipWetting] = LvArray::math::min( m_wettingCurve.oppositeBoundPhaseVolFraction, phaseVolFraction[ipWetting] );
+ auto const & imbibitionRelPermKernelWrapper = m_imbibitionRelPermKernelWrappers[IPT::WETTING];
+ phaseRelPerm[ipWetting] = imbibitionRelPermKernelWrapper.compute( &phaseVolFraction[ipWetting],
+ &dPhaseRelPerm_dPhaseVolFrac[ipWetting][ipWetting] );
+ }
+ else if( !m_phaseHasHysteresis[IPT::WETTING] ||
phaseVolFraction[ipWetting] <= phaseMinHistoricalVolFraction[ipWetting] + flowReversalBuffer )
{
- phaseTrappedVolFrac[ipWetting] = LvArray::math::min( m_wettingCurve.m_extremumPhaseVolFraction, phaseVolFraction[ipWetting] );
+ phaseTrappedVolFrac[ipWetting] = LvArray::math::min( m_wettingCurve.oppositeBoundPhaseVolFraction, phaseVolFraction[ipWetting] );
computeDrainageRelPerm( m_drainageRelPermKernelWrappers[TPT::WETTING],
phaseVolFraction[ipWetting],
phaseRelPerm[ipWetting],
@@ -752,12 +814,37 @@ TableRelativePermeabilityHysteresis::KernelWrapper::
// 2) Non-wetting and intermediate phase relative permeabilities using two-phase non-wetting-intermediate data
// ---------- non-wetting rel perm
- if( !m_phaseHasHysteresis[IPT::NONWETTING] ||
+ if( FORCE_DRAINAGE_MODE )
+ {
+ phaseTrappedVolFrac[ipNonWetting] = LvArray::math::min( m_nonWettingCurve.drainageExtremaPhaseVolFraction, phaseVolFraction[ipNonWetting] );
+ computeDrainageRelPerm( m_drainageRelPermKernelWrappers[TPT::NONWETTING],
+ phaseVolFraction[ipNonWetting],
+ phaseRelPerm[ipNonWetting],
+ dPhaseRelPerm_dPhaseVolFrac[ipNonWetting][ipNonWetting] );
+ }
+ else if( FORCE_IMBIBITION_MODE )
+ {
+ real64 const Shy = ( phaseVolFraction[ipNonWetting] < m_nonWettingCurve.oppositeBoundPhaseVolFraction)
+ ? phaseVolFraction[ipNonWetting] : m_nonWettingCurve.oppositeBoundPhaseVolFraction;
+ real64 Scrt = 0;
+ KilloughHysteresis::computeTrappedCriticalPhaseVolFraction( m_nonWettingCurve,
+ Shy,
+ m_landParam[IPT::NONWETTING],
+ m_jerauldParam_a,
+ m_jerauldParam_b,
+ Scrt );
+ phaseTrappedVolFrac[ipNonWetting] = LvArray::math::min( Scrt, phaseVolFraction[ipNonWetting] );
+
+ auto const & imbibitionRelPermKernelWrapper = m_imbibitionRelPermKernelWrappers[IPT::NONWETTING];
+ phaseRelPerm[ipNonWetting] = imbibitionRelPermKernelWrapper.compute( &phaseVolFraction[ipNonWetting],
+ &dPhaseRelPerm_dPhaseVolFrac[ipNonWetting][ipNonWetting] );
+ }
+ else if( !m_phaseHasHysteresis[IPT::NONWETTING] ||
phaseVolFraction[ipNonWetting] >= phaseMaxHistoricalVolFraction[ipNonWetting] - flowReversalBuffer )
{
// 2.a) compute Sgcrt for reporting purposes
- real64 const Shy = ( phaseVolFraction[ipNonWetting] < m_nonWettingCurve.m_extremumPhaseVolFraction)
- ? phaseVolFraction[ipNonWetting] : m_nonWettingCurve.m_extremumPhaseVolFraction; // to make sure that Shy < Smax
+ real64 const Shy = ( phaseVolFraction[ipNonWetting] < m_nonWettingCurve.oppositeBoundPhaseVolFraction)
+ ? phaseVolFraction[ipNonWetting] : m_nonWettingCurve.oppositeBoundPhaseVolFraction; // to make sure that Shy < Smax
real64 Scrt = 0;
KilloughHysteresis::computeTrappedCriticalPhaseVolFraction( m_nonWettingCurve,
Shy,
@@ -792,7 +879,7 @@ TableRelativePermeabilityHysteresis::KernelWrapper::
// 3) Compute the "three-phase" oil relperm
// use saturation-weighted interpolation
- real64 const shiftedWettingVolFrac = (phaseVolFraction[ipWetting] - m_wettingCurve.m_extremumPhaseVolFraction);
+ real64 const shiftedWettingVolFrac = (phaseVolFraction[ipWetting] - m_wettingCurve.oppositeBoundPhaseVolFraction);
if( m_threePhaseInterpolator == ThreePhaseInterpolator::BAKER )
{
diff --git a/src/coreComponents/constitutive/relativePermeability/VanGenuchtenBakerRelativePermeability.hpp b/src/coreComponents/constitutive/relativePermeability/VanGenuchtenBakerRelativePermeability.hpp
index 931f7d35b63..11bd206d906 100644
--- a/src/coreComponents/constitutive/relativePermeability/VanGenuchtenBakerRelativePermeability.hpp
+++ b/src/coreComponents/constitutive/relativePermeability/VanGenuchtenBakerRelativePermeability.hpp
@@ -140,19 +140,17 @@ class VanGenuchtenBakerRelativePermeability : public RelativePermeabilityBase
};
- arrayView1d< real64 const > getPhaseMinVolumeFraction() const override { return m_phaseMinVolumeFraction; };
-
real64 getWettingPhaseMinVolumeFraction() const override
{
integer ipWetting;
- std::tie( ipWetting, std::ignore ) = wettingAndNonWettingPhaseIndices();
+ std::tie( ipWetting, std::ignore ) = phaseIndex( getPhaseOrder());
return m_phaseMinVolumeFraction[ipWetting];
}
real64 getNonWettingMinVolumeFraction() const override
{
integer ipNonWetting;
- std::tie( std::ignore, ipNonWetting ) = wettingAndNonWettingPhaseIndices();
+ std::tie( std::ignore, ipNonWetting ) = phaseIndex( getPhaseOrder());
return m_phaseMinVolumeFraction[ipNonWetting];
};
diff --git a/src/coreComponents/constitutive/relativePermeability/VanGenuchtenStone2RelativePermeability.hpp b/src/coreComponents/constitutive/relativePermeability/VanGenuchtenStone2RelativePermeability.hpp
index 3927764f236..e91e4fb39bd 100644
--- a/src/coreComponents/constitutive/relativePermeability/VanGenuchtenStone2RelativePermeability.hpp
+++ b/src/coreComponents/constitutive/relativePermeability/VanGenuchtenStone2RelativePermeability.hpp
@@ -139,7 +139,7 @@ class VanGenuchtenStone2RelativePermeability : public RelativePermeabilityBase
static constexpr char const * volFracScaleString() { return "volFracScale"; }
};
- arrayView1d< real64 const > getPhaseMinVolumeFraction() const override { return m_phaseMinVolumeFraction; };
+ arrayView1d< real64 const > getPhaseMinVolumeFraction() const { return m_phaseMinVolumeFraction; };
real64 getWettingPhaseMinVolumeFraction() const override
{
diff --git a/src/coreComponents/constitutive/relativePermeability/unitTests/constitutiveTestHelpers.hpp b/src/coreComponents/constitutive/relativePermeability/unitTests/constitutiveTestHelpers.hpp
index e2ddfa05933..c5463cca49f 100644
--- a/src/coreComponents/constitutive/relativePermeability/unitTests/constitutiveTestHelpers.hpp
+++ b/src/coreComponents/constitutive/relativePermeability/unitTests/constitutiveTestHelpers.hpp
@@ -32,7 +32,7 @@ namespace geos
{
namespace testing
{
-void fillArray( array1d< real64_array > & arr, std::initializer_list< real64 > const & input_list )
+void fill_array( array1d< real64_array > & arr, std::initializer_list< real64 > const & input_list )
{
arr.resize( 1 );
arr[0].resize( input_list.size());
@@ -42,7 +42,7 @@ void fillArray( array1d< real64_array > & arr, std::initializer_list< real64 > c
}
-void fillArray( real64_array & arr, std::initializer_list< real64 > const & input_list )
+void fill_array( real64_array & arr, std::initializer_list< real64 > const & input_list )
{
arr.resize( input_list.size());
int j = 0;
diff --git a/src/coreComponents/constitutive/unitTests/FluidModelTest_impl.hpp b/src/coreComponents/constitutive/unitTests/FluidModelTest_impl.hpp
index 72cfec6501c..ce731e6b775 100644
--- a/src/coreComponents/constitutive/unitTests/FluidModelTest_impl.hpp
+++ b/src/coreComponents/constitutive/unitTests/FluidModelTest_impl.hpp
@@ -35,7 +35,7 @@ template< typename FLUID_TYPE, integer NUM_COMP, integer NUM_PHASE >
FluidModelTest< FLUID_TYPE, NUM_COMP, NUM_PHASE >::FluidModelTest():
m_parent( "parent", m_node )
{
- createFunctionManager();
+ // createFunctionManager();
}
template< typename FLUID_TYPE, integer NUM_COMP, integer NUM_PHASE >
diff --git a/src/coreComponents/constitutive/unitTests/testCO2SpycherPruessModels.cpp.uncrustify b/src/coreComponents/constitutive/unitTests/testCO2SpycherPruessModels.cpp.uncrustify
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/src/coreComponents/constitutive/unitTests/testTwoPhaseFluid.cpp b/src/coreComponents/constitutive/unitTests/testTwoPhaseFluid.cpp
new file mode 100644
index 00000000000..207668e29cb
--- /dev/null
+++ b/src/coreComponents/constitutive/unitTests/testTwoPhaseFluid.cpp
@@ -0,0 +1,283 @@
+/*
+ * ------------------------------------------------------------------------------------------------------------
+ * SPDX-License-Identifier: LGPL-2.1-only
+ *
+ * Copyright (c) 2016-2024 Lawrence Livermore National Security LLC
+ * Copyright (c) 2018-2024 TotalEnergies
+ * Copyright (c) 2018-2024 The Board of Trustees of the Leland Stanford Junior University
+ * Copyright (c) 2023-2024 Chevron
+ * Copyright (c) 2019- GEOS/GEOSX Contributors
+ * All rights reserved
+ *
+ * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details.
+ * ------------------------------------------------------------------------------------------------------------
+ */
+
+#include "constitutive/fluid/twophasefluid/TwoPhaseFluid.hpp"
+#include "constitutive/fluid/twophasefluid/TwoPhaseFluidFields.hpp"
+
+// Only for fill
+#include "unitTests/constitutiveTests/MultiFluidTest.hpp"
+
+// Only for initializeTable
+#include "unitTests/constitutiveTests/constitutiveTestHelpers.hpp"
+
+
+using namespace geos;
+using namespace geos::testing;
+using namespace geos::constitutive;
+using namespace geos::dataRepository; /// Only for group definition
+
+
+static constexpr char const * tableContentPhase0 = "# Pg(Pa) Dens(kg/m3) Visc(Pa.s)\n"
+ "0.22 0.00603 40203\n"
+ "0.3 0.04224 31311\n"
+ "0.5 0.15011 22423\n"
+ "0.6 0.22423 15011\n"
+ "0.8 0.31311 4224\n"
+ "1.0 0.40203 603";
+
+static constexpr char const * tableContentPhase1 = "# Pg(Pa) Dens(kg/m3) Visc(Pa.s)\n"
+ "1.22 0.00603 0.22\n"
+ "1.3 0.04224 0.22\n"
+ "1.5 0.15011 0.22\n"
+ "1.6 0.22423 0.22\n"
+ "1.8 0.31311 0.22\n"
+ "2.0 0.40203 0.22";
+
+template< bool FROM_TABLE >
+class TwoPhaseFluidTest : public ConstitutiveTestBase< TwoPhaseFluid >
+{
+public:
+
+ TwoPhaseFluidTest()
+ {
+ if constexpr (!FROM_TABLE)
+ {
+ writeTableToFile( "phase0.txt", tableContentPhase0 );
+ writeTableToFile( "phase1.txt", tableContentPhase1 );
+ }
+
+ m_parent.resize( 1 );
+ string const fluidName = GEOS_FMT( "fluid{}", (FROM_TABLE ? "Tables" : "Files"));
+ m_model = makeTwoPhaseFluid( fluidName, m_parent );
+
+ m_parent.initialize();
+ m_parent.initializePostInitialConditions();
+ }
+
+ ~TwoPhaseFluidTest()
+ {
+ if constexpr (!FROM_TABLE)
+ {
+ removeFile( "phase0.txt" );
+ removeFile( "phase1.txt" );
+ }
+ }
+
+ constitutive::TwoPhaseFluid & getFluid() const { return *m_model; }
+
+ dataRepository::Group & getParent() { return m_parent; }
+
+
+ void testDerivatives( constitutive::TwoPhaseFluid & fluid,
+ dataRepository::Group * parent,
+ real64 const pressure,
+ real64 const perturbParameter,
+ real64 const relTol,
+ real64 const absTol = std::numeric_limits< real64 >::max() )
+ {
+ auto const & phaseNames = fluid.getReference< string_array >( TwoPhaseFluid::viewKeyStruct::phaseNamesString() );
+
+ // create a clone of the fluid to run updates on
+ string const fluidCopyName = fluid.getName() + "Copy";
+ std::unique_ptr< constitutive::ConstitutiveBase > fluidCopyPtr = fluid.deliverClone( fluidCopyName, parent );
+ constitutive::TwoPhaseFluid & fluidCopy = dynamicCast< constitutive::TwoPhaseFluid & >( *fluidCopyPtr );
+ fluidCopy.initializePostSubGroups();
+
+ fluid.allocateConstitutiveData( fluid.getParent(), 1 );
+ fluidCopy.allocateConstitutiveData( fluid.getParent(), 1 );
+
+ // extract data views from both fluids
+ #define GET_FLUID_DATA( FLUID, TRAIT ) \
+ FLUID.getReference< TRAIT::type >( TRAIT::key() )[0][0]
+
+ constitutive::MultiFluidVarSlice< real64, 1, constitutive::multifluid::USD_PHASE - 2, constitutive::multifluid::USD_PHASE_DC - 2 > phaseVisc {
+ GET_FLUID_DATA( fluid, fields::twophasefluid::phaseViscosity ),
+ GET_FLUID_DATA( fluid, fields::twophasefluid::dPhaseViscosity )
+ };
+
+ constitutive::MultiFluidVarSlice< real64, 1, constitutive::multifluid::USD_PHASE - 2, constitutive::multifluid::USD_PHASE_DC - 2 > phaseDens {
+ GET_FLUID_DATA( fluid, fields::twophasefluid::phaseDensity ),
+ GET_FLUID_DATA( fluid, fields::twophasefluid::dPhaseDensity )
+ };
+
+ auto const & phaseDensCopy = GET_FLUID_DATA( fluidCopy, fields::twophasefluid::phaseDensity );
+ auto const & phaseViscCopy = GET_FLUID_DATA( fluidCopy, fields::twophasefluid::phaseViscosity );
+#undef GET_FLUID_DATA
+
+
+ // set the original fluid state to current
+ constitutive::constitutiveUpdatePassThru( fluid, [&] ( auto & castedFluid )
+ {
+ typename TYPEOFREF( castedFluid ) ::KernelWrapper fluidWrapper = castedFluid.createKernelWrapper();
+ fluidWrapper.update( 0, 0, pressure );
+ } );
+
+ // now perturb variables and update the copied fluid's state
+ constitutive::constitutiveUpdatePassThru( fluidCopy, [&] ( auto & castedFluid )
+ {
+ using Deriv = constitutive::multifluid::DerivativeOffset;
+
+ typename TYPEOFREF( castedFluid ) ::KernelWrapper fluidWrapper = castedFluid.createKernelWrapper();
+
+ // to be able to use the checkDerivative utility function, we have to invert the layout
+ auto dPhaseDens = invertLayout( phaseDens.derivs.toSliceConst(), 2, 1 );
+ auto dPhaseVisc = invertLayout( phaseVisc.derivs.toSliceConst(), 2, 1 );
+
+ // update pressure and check derivatives
+ real64 const dP = perturbParameter * (pressure + perturbParameter);
+ fluidWrapper.update( 0, 0, pressure + dP );
+
+ checkDerivative( phaseDensCopy.toSliceConst(), phaseDens.value.toSliceConst(), dPhaseDens[Deriv::dP].toSliceConst(),
+ dP, relTol, absTol, "phaseDens", "Pressure", phaseNames );
+ checkDerivative( phaseViscCopy.toSliceConst(), phaseVisc.value.toSliceConst(), dPhaseVisc[Deriv::dP].toSliceConst(),
+ dP, relTol, absTol, "phaseVisc", "Pressure", phaseNames );
+ } );
+ } // void testDerivatives
+
+
+protected:
+ static void writeTableToFile( string const & fileName, char const * content )
+ {
+ std::ofstream os( fileName );
+ ASSERT_TRUE( os.is_open() );
+ os << content;
+ os.close();
+ }
+
+ static void removeFile( string const & fileName )
+ {
+ int const ret = std::remove( fileName.c_str() );
+ ASSERT_TRUE( ret == 0 );
+ }
+
+
+private:
+ static TwoPhaseFluid * makeTwoPhaseFluid( string const & name, Group & parent );
+
+}; // class TwoPhaseFluidTest
+
+
+template<>
+TwoPhaseFluid * TwoPhaseFluidTest< true >::makeTwoPhaseFluid( string const & name, Group & parent )
+{
+ // 1D table with linear interpolation
+ localIndex constexpr Naxis = 6;
+ localIndex constexpr NaxisSingle = 1;
+
+ array1d< real64_array > densityCoordPhase0( 1 );
+ fill< Naxis >( densityCoordPhase0[0], { 0.22, 0.3, 0.5, 0.6, 0.8, 1.0 } );
+ real64_array densityValuesPhase0;
+ fill< Naxis >( densityValuesPhase0, { 0.00603, 0.04224, 0.04224, 0.22423, 0.31311, 0.40203 } );
+
+ array1d< real64_array > densityCoordPhase1( 1 );
+ fill< Naxis >( densityCoordPhase1[0], { 1.22, 1.3, 1.5, 1.6, 1.8, 2.0 } );
+ real64_array densityValuesPhase1;
+ fill< Naxis >( densityValuesPhase1, { 0.00603, 0.04224, 0.04224, 0.22423, 0.31311, 0.40203 } );
+
+
+ array1d< real64_array > viscosityCoordPhase0( 1 );
+ fill< Naxis >( viscosityCoordPhase0[0], { 0.22, 0.3, 0.5, 0.6, 0.8, 1.0 } );
+ real64_array viscosityValuesPhase0;
+ fill< Naxis >( viscosityValuesPhase0, { 40203, 31311, 22423, 15011, 4224, 603 } );
+
+ array1d< real64_array > viscosityCoordPhase1( 1 );
+ fill< NaxisSingle >( viscosityCoordPhase1[0], { 0.22 } );
+ real64_array viscosityValuesPhase1;
+ fill< NaxisSingle >( viscosityValuesPhase1, { 45 } );
+
+ initializeTable( "densityTablePhase0", densityCoordPhase0, densityValuesPhase0 );
+ initializeTable( "densityTablePhase1", densityCoordPhase1, densityValuesPhase1 );
+ initializeTable( "viscosityTablePhase0", viscosityCoordPhase0, viscosityValuesPhase0 );
+ initializeTable( "viscosityTablePhase1", viscosityCoordPhase1, viscosityValuesPhase1 );
+
+
+ // 2) Set up the constitutive model
+ TwoPhaseFluid & fluid = parent.registerGroup< TwoPhaseFluid >( name );
+
+ string_array & phaseNames = fluid.getReference< string_array >( TwoPhaseFluid::viewKeyStruct::phaseNamesString() );
+ phaseNames.emplace_back( "oil" );
+ phaseNames.emplace_back( "water" );
+
+ string_array & densityTableNames = fluid.getReference< string_array >( TwoPhaseFluid::viewKeyStruct::densityTableNamesString() );
+ densityTableNames.emplace_back( "densityTablePhase0" );
+ densityTableNames.emplace_back( "densityTablePhase1" );
+
+ string_array & viscosityTableNames = fluid.getReference< string_array >( TwoPhaseFluid::viewKeyStruct::viscosityTableNamesString() );
+ viscosityTableNames.emplace_back( "viscosityTablePhase0" );
+ viscosityTableNames.emplace_back( "viscosityTablePhase1" );
+
+ fluid.postInputInitializationRecursive();
+ return &fluid;
+}
+
+
+template<>
+TwoPhaseFluid * TwoPhaseFluidTest< false >::makeTwoPhaseFluid( string const & name, Group & parent )
+{
+ TwoPhaseFluid & fluid = parent.registerGroup< TwoPhaseFluid >( name );
+
+ path_array & tableNames = fluid.getReference< path_array >( TwoPhaseFluid::viewKeyStruct::tableFilesString() );
+ fill< 2 >( tableNames, {"phase0.txt", "phase1.txt"} );
+
+ fluid.postInputInitializationRecursive();
+ return &fluid;
+}
+
+
+
+using TwoPhaseFluidTestFromFiles = TwoPhaseFluidTest< false >;
+using TwoPhaseFluidTestFromTables = TwoPhaseFluidTest< true >;
+
+
+TEST_F( TwoPhaseFluidTestFromTables, testNumericalDerivative_initFromTables )
+{
+ auto & fluid = getFluid();
+ real64 const eps = std::sqrt( std::numeric_limits< real64 >::epsilon());
+ real64 constexpr relTol = 1.0e-8;
+ real64 constexpr absTol = 1.0e-8;
+
+ for( real64 const pressure : { 0.55, 1.0, 10.0 } )
+ {
+ testDerivatives( fluid, &getParent(), pressure, eps, relTol, absTol );
+ }
+}
+
+
+TEST_F( TwoPhaseFluidTestFromFiles, testNumericalDerivative_initFromFiles )
+{
+ auto & fluid = getFluid();
+ real64 const eps = std::sqrt( std::numeric_limits< real64 >::epsilon());
+ real64 constexpr relTol = 1.0e-8;
+ real64 constexpr absTol = 1.0e-8;
+
+ for( real64 const pressure : { 0.55, 1.0, 10.0 } )
+ {
+ testDerivatives( fluid, &getParent(), pressure, eps, relTol, absTol );
+ }
+}
+
+
+int main( int argc, char * * argv )
+{
+ ::testing::InitGoogleTest( &argc, argv );
+
+ conduit::Node conduitNode;
+ dataRepository::Group rootNode( "root", conduitNode );
+ FunctionManager functionManager( "FunctionManager", &rootNode );
+
+ int const result = RUN_ALL_TESTS();
+
+ return result;
+}
diff --git a/src/coreComponents/constitutiveDrivers/relativePermeability/RelpermDriver.cpp b/src/coreComponents/constitutiveDrivers/relativePermeability/RelpermDriver.cpp
index 49dca10b482..6bd500b6dfa 100644
--- a/src/coreComponents/constitutiveDrivers/relativePermeability/RelpermDriver.cpp
+++ b/src/coreComponents/constitutiveDrivers/relativePermeability/RelpermDriver.cpp
@@ -13,7 +13,9 @@
* ------------------------------------------------------------------------------------------------------------
*/
-#include "common/MpiWrapper.hpp"
+#include "RelpermDriver.hpp"
+
+//#include "common/MpiWrapper.hpp"
#include "functions/FunctionManager.hpp"
#include "functions/TableFunction.hpp"
#include "constitutive/ConstitutiveManager.hpp"
@@ -110,7 +112,7 @@ bool RelpermDriver::execute( const geos::real64 GEOS_UNUSED_PARAM( time_n ),
{
// this code only makes sense in serial
- GEOS_THROW_IF( MpiWrapper::commRank() > 0, "RelpermDriver should only be run in serial", std::runtime_error );
+ //GEOS_THROW_IF( MpiWrapper::commRank() > 0, "RelpermDriver should only be run in serial", std::runtime_error );
ConstitutiveManager
diff --git a/src/coreComponents/constitutiveDrivers/relativePermeability/RelpermDriverRunTest.hpp b/src/coreComponents/constitutiveDrivers/relativePermeability/RelpermDriverRunTest.hpp
index eabe5616eee..750587bab88 100644
--- a/src/coreComponents/constitutiveDrivers/relativePermeability/RelpermDriverRunTest.hpp
+++ b/src/coreComponents/constitutiveDrivers/relativePermeability/RelpermDriverRunTest.hpp
@@ -19,7 +19,7 @@
#include "constitutiveDrivers/relativePermeability/RelpermDriver.hpp"
#include "constitutive/relativePermeability/RelativePermeabilityFields.hpp"
#include "constitutive/relativePermeability/Layouts.hpp"
-#include "constitutive/relativePermeability/KilloughHysteresis.hpp"
+#include "constitutive/KilloughHysteresis.hpp"
namespace geos
@@ -96,10 +96,6 @@ RelpermDriver::runTest( RELPERM_TYPE & relperm,
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());
@@ -109,14 +105,15 @@ RelpermDriver::runTest( RELPERM_TYPE & relperm,
{
if( phaseHasHysteresis[ipNonWetting] )
{
- phaseMaxHistoricalVolFraction[0][ipNonWetting] = nonWettingCurve.m_extremumPhaseVolFraction;
+ phaseMaxHistoricalVolFraction[0][ipNonWetting] = nonWettingCurve.oppositeBoundPhaseVolFraction;
GEOS_LOG( GEOS_FMT( "New max non-wetting phase historical phase volume fraction: {}", phaseMaxHistoricalVolFraction[0][ipNonWetting] ) );
}
if( phaseHasHysteresis[ipWetting] )
{
- phaseMinHistoricalVolFraction[0][ipWetting] = wettingCurve.m_extremumPhaseVolFraction;
+ phaseMinHistoricalVolFraction[0][ipWetting] = wettingCurve.oppositeBoundPhaseVolFraction;
GEOS_LOG( GEOS_FMT( "New min wetting phase historical phase volume fraction: {}", phaseMinHistoricalVolFraction[0][ipWetting] ) );
}
+
}
@@ -140,13 +137,12 @@ RelpermDriver::runTest( RELPERM_TYPE & relperm,
{
if( phaseHasHysteresis[ipNonWetting] )
{
-
- phaseMaxHistoricalVolFraction[0][ipNonWetting] = nonWettingCurve.m_criticalDrainagePhaseVolFraction;
+ phaseMaxHistoricalVolFraction[0][ipNonWetting] = nonWettingCurve.drainageExtremaPhaseVolFraction;
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;
+ phaseMinHistoricalVolFraction[0][ipWetting] = wettingCurve.drainageExtremaPhaseVolFraction;
GEOS_LOG( GEOS_FMT( "New min wetting phase historical phase volume fraction: {}", phaseMinHistoricalVolFraction[0][ipWetting] ) );
}
}
diff --git a/src/coreComponents/finiteVolume/CellElementStencilTPFA.hpp b/src/coreComponents/finiteVolume/CellElementStencilTPFA.hpp
index 88fa3bb2b27..5c468d293cf 100644
--- a/src/coreComponents/finiteVolume/CellElementStencilTPFA.hpp
+++ b/src/coreComponents/finiteVolume/CellElementStencilTPFA.hpp
@@ -70,6 +70,20 @@ class CellElementStencilTPFAWrapper : public StencilWrapperBase< TwoPointStencil
CoefficientAccessor< arrayView3d< real64 const > > const & dCoeff_dVar,
real64 ( &weight )[1][2],
real64 ( &dWeight_dVar )[1][2] ) const;
+/**
+ * @brief Compute half weights and derivatives w.r.t to one variable.
+ * @param[in] iconn connection index
+ * @param[in] coefficient view accessor to the coefficient used to compute the weights
+ * @param[in] dCoeff_dVar view accessor to the derivative of the coefficient w.r.t to the variable
+ * @param[out] weight view weights
+ * @param[out] dWeight_dVar derivative of the weights w.r.t to the variable
+ */
+ GEOS_HOST_DEVICE
+ void computeHalfWeights( localIndex const iconn,
+ CoefficientAccessor< arrayView3d< real64 const > > const & coefficient,
+ CoefficientAccessor< arrayView3d< real64 const > > const & dCoeff_dVar,
+ real64 ( &weight )[1][2],
+ real64 ( &dWeight_dVar )[1][2] ) const;
/**
* @brief Compute weights and derivatives w.r.t to one variable without coefficient
@@ -293,6 +307,92 @@ CellElementStencilTPFAWrapper::
}
}
+GEOS_HOST_DEVICE
+inline void
+CellElementStencilTPFAWrapper::
+ computeHalfWeights( localIndex const iconn,
+ CoefficientAccessor< arrayView3d< real64 const > > const & coefficient,
+ CoefficientAccessor< arrayView3d< real64 const > > const & dCoeff_dVar,
+ real64 (& weight)[1][2],
+ real64 (& dWeight_dVar )[1][2] ) const
+{
+ real64 halfWeight[2];
+ real64 dHalfWeight_dVar[2];
+
+ // real64 const tolerance = 1e-30 * lengthTolerance; // TODO: choice of constant based on physics?
+
+ for( localIndex i = 0; i < 2; ++i )
+ {
+ localIndex const er = m_elementRegionIndices[iconn][i];
+ localIndex const esr = m_elementSubRegionIndices[iconn][i];
+ localIndex const ei = m_elementIndices[iconn][i];
+
+ halfWeight[i] = m_weights[iconn][i];
+ dHalfWeight_dVar[i] = m_weights[iconn][i];
+
+ // Proper computation
+ real64 faceNormal[3];
+ LvArray::tensorOps::copy< 3 >( faceNormal, m_faceNormal[iconn] );
+ if( LvArray::tensorOps::AiBi< 3 >( m_cellToFaceVec[iconn][i], faceNormal ) < 0.0 )
+ {
+ LvArray::tensorOps::scale< 3 >( faceNormal, -1 );
+ }
+
+ real64 faceConormal[3];
+ real64 dFaceConormal_dVar[3];
+ LvArray::tensorOps::hadamardProduct< 3 >( faceConormal, coefficient[er][esr][ei][0], faceNormal );
+ LvArray::tensorOps::hadamardProduct< 3 >( dFaceConormal_dVar, dCoeff_dVar[er][esr][ei][0], faceNormal );
+ halfWeight[i] *= LvArray::tensorOps::AiBi< 3 >( m_cellToFaceVec[iconn][i], faceConormal );
+ dHalfWeight_dVar[i] *= LvArray::tensorOps::AiBi< 3 >( m_cellToFaceVec[iconn][i], dFaceConormal_dVar );
+
+ // correct negative weight issue arising from non-K-orthogonal grids
+ // if( halfWeight[i] < 0.0 )
+ // {
+ // LvArray::tensorOps::hadamardProduct< 3 >( faceConormal,
+ // coefficient[er][esr][ei][0],
+ // m_cellToFaceVec[iconn][i] );
+ // LvArray::tensorOps::hadamardProduct< 3 >( dFaceConormal_dVar,
+ // dCoeff_dVar[er][esr][ei][0],
+ // m_cellToFaceVec[iconn][i] );
+ // halfWeight[i] = m_weights[iconn][i];
+ // dHalfWeight_dVar[i] = m_weights[iconn][i];
+ // halfWeight[i] *= LvArray::tensorOps::AiBi< 3 >( m_cellToFaceVec[iconn][i], faceConormal );
+ // dHalfWeight_dVar[i] *= LvArray::tensorOps::AiBi< 3 >( m_cellToFaceVec[iconn][i], dFaceConormal_dVar );
+ // }
+ }
+
+ // // Do harmonic and arithmetic averaging
+ // real64 const product = halfWeight[0]*halfWeight[1];
+ // real64 const sum = halfWeight[0]+halfWeight[1];
+
+ // real64 const harmonicWeight = sum > 0 ? product / sum : 0.0;
+ // real64 const arithmeticWeight = sum / 2;
+
+ // real64 dHarmonicWeight_dVar[2];
+ // real64 dArithmeticWeight_dVar[2];
+
+ // dHarmonicWeight_dVar[0] = sum > 0 ? (dHalfWeight_dVar[0]*sum*halfWeight[1] - dHalfWeight_dVar[0]*halfWeight[0]*halfWeight[1]) / (
+ // sum*sum ) : 0.0;
+ // dHarmonicWeight_dVar[1] = sum > 0 ? (dHalfWeight_dVar[1]*sum*halfWeight[0] - dHalfWeight_dVar[1]*halfWeight[1]*halfWeight[0]) / (
+ // sum*sum ) : 0.0;
+
+ // dArithmeticWeight_dVar[0] = dHalfWeight_dVar[0] / 2;
+ // dArithmeticWeight_dVar[1] = dHalfWeight_dVar[1] / 2;
+
+ // real64 const meanPermCoeff = 1.0; //TODO make it a member if it is really necessary
+
+ // real64 const value = meanPermCoeff * harmonicWeight + (1 - meanPermCoeff) * arithmeticWeight;
+ for( localIndex ke = 0; ke < 2; ++ke )
+ {
+ // weight[0][ke] = m_transMultiplier[iconn] * value * (ke == 0 ? 1 : -1);
+ weight[0][ke] = m_transMultiplier[iconn] * halfWeight[ke] * (ke == 0 ? 1 : -1);
+
+ // real64 const dValue_dVar = meanPermCoeff * dHarmonicWeight_dVar[ke] + (1 - meanPermCoeff) * dArithmeticWeight_dVar[ke];
+ // dWeight_dVar[0][ke] = m_transMultiplier[iconn] * dValue_dVar;
+ dWeight_dVar[0][ke] = m_transMultiplier[iconn] * dHalfWeight_dVar[ke];
+ }
+}
+
GEOS_HOST_DEVICE
inline void
CellElementStencilTPFAWrapper::
diff --git a/src/coreComponents/finiteVolume/EmbeddedSurfaceToCellStencil.hpp b/src/coreComponents/finiteVolume/EmbeddedSurfaceToCellStencil.hpp
index 7f9c1562366..4a191ca7614 100644
--- a/src/coreComponents/finiteVolume/EmbeddedSurfaceToCellStencil.hpp
+++ b/src/coreComponents/finiteVolume/EmbeddedSurfaceToCellStencil.hpp
@@ -98,6 +98,22 @@ class EmbeddedSurfaceToCellStencilWrapper : public StencilWrapperBase< TwoPointS
real64 ( &weight )[1][2],
real64 ( &dWeight_dVar )[1][2] ) const;
+ /**
+ * @brief Compute half weigths and derivatives w.r.t to one variable.
+ * @param[in] iconn connection index
+ * @param[in] coefficient view accessor to the coefficient used to compute the weights
+ * @param[in] dCoeff_dVar view accessor to the derivative of the coefficient w.r.t to the variable
+ * @param[out] weight view weights
+ * @param[out] dWeight_dVar derivative of the weigths w.r.t to the variable
+ */
+
+ GEOS_HOST_DEVICE
+ void computeHalfWeights( localIndex const iconn,
+ CoefficientAccessor< arrayView3d< real64 const > > const & coefficient,
+ CoefficientAccessor< arrayView3d< real64 const > > const & dCoeff_dVar,
+ real64 ( &weight )[1][2],
+ real64 ( &dWeight_dVar )[1][2] ) const;
+
/**
* @brief Compute weigths and derivatives w.r.t to one variable without coefficient
* Used in ReactiveCompositionalMultiphaseOBL solver for thermal transmissibility computation:
@@ -243,6 +259,42 @@ EmbeddedSurfaceToCellStencilWrapper::
dWeight_dVar[0][1] = ( t0 * dt1 * sumOfTrans - dt1 * t0 * t1 ) / ( sumOfTrans * sumOfTrans );
}
+GEOS_HOST_DEVICE
+inline void
+EmbeddedSurfaceToCellStencilWrapper::
+ computeHalfWeights( localIndex iconn,
+ CoefficientAccessor< arrayView3d< real64 const > > const & coefficient,
+ CoefficientAccessor< arrayView3d< real64 const > > const & dCoeff_dVar,
+ real64 ( & weight )[1][2],
+ real64 ( & dWeight_dVar )[1][2] ) const
+{
+ localIndex const er0 = m_elementRegionIndices[iconn][0];
+ localIndex const esr0 = m_elementSubRegionIndices[iconn][0];
+ localIndex const ei0 = m_elementIndices[iconn][0];
+
+ localIndex const er1 = m_elementRegionIndices[iconn][1];
+ localIndex const esr1 = m_elementSubRegionIndices[iconn][1];
+ localIndex const ei1 = m_elementIndices[iconn][1];
+
+ // Will change when implementing collocation points. Will use fracture normal to project the permeability
+ real64 const t0 = m_weights[iconn][0] * LvArray::tensorOps::l2Norm< 3 >( coefficient[er0][esr0][ei0][0] );
+ // We consider the 3rd component of the permeability which is the normal one.
+ real64 const t1 = m_weights[iconn][1] * coefficient[er1][esr1][ei1][0][2];
+
+ real64 const sumOfTrans = t0+t1;
+ real64 const value = t0*t1/sumOfTrans;
+
+ weight[0][0] = value;
+ weight[0][1] = -value;
+
+ // We consider the 3rd component of the permeability which is the normal one.
+ real64 const dt0 = m_weights[iconn][0] * dCoeff_dVar[er0][esr0][ei0][0][0];
+ real64 const dt1 = m_weights[iconn][1] * dCoeff_dVar[er1][esr1][ei1][0][2];
+
+ dWeight_dVar[0][0] = ( dt0 * t1 * sumOfTrans - dt0 * t0 * t1 ) / ( sumOfTrans * sumOfTrans );
+ dWeight_dVar[0][1] = ( t0 * dt1 * sumOfTrans - dt1 * t0 * t1 ) / ( sumOfTrans * sumOfTrans );
+}
+
GEOS_HOST_DEVICE
inline void
EmbeddedSurfaceToCellStencilWrapper::
diff --git a/src/coreComponents/finiteVolume/FaceElementToCellStencil.hpp b/src/coreComponents/finiteVolume/FaceElementToCellStencil.hpp
index 6920e52b3bf..e86e6724bed 100644
--- a/src/coreComponents/finiteVolume/FaceElementToCellStencil.hpp
+++ b/src/coreComponents/finiteVolume/FaceElementToCellStencil.hpp
@@ -106,6 +106,21 @@ class FaceElementToCellStencilWrapper : public StencilWrapperBase< TwoPointStenc
real64 ( &weight )[1][2],
real64 ( &dWeight_dVar )[1][2] ) const;
+/**
+ * @brief Compute half weigths and derivatives w.r.t to one variable.
+ * @param[in] iconn connection index
+ * @param[in] coefficient view accessor to the coefficient used to compute the weights
+ * @param[in] dCoeff_dVar view accessor to the derivative of the coefficient w.r.t to the variable
+ * @param[out] weight view weights
+ * @param[out] dWeight_dVar derivative of the weigths w.r.t to the variable
+ */
+ GEOS_HOST_DEVICE
+ void computeHalfWeights( localIndex iconn,
+ CoefficientAccessor< arrayView3d< real64 const > > const & coefficient,
+ CoefficientAccessor< arrayView3d< real64 const > > const & dCoeff_dVar,
+ real64 ( &weight )[1][2],
+ real64 ( &dWeight_dVar )[1][2] ) const;
+
/**
* @brief Compute weigths and derivatives w.r.t to one variable without coefficient
* Used in ReactiveCompositionalMultiphaseOBL solver for thermal transmissibility computation:
@@ -297,6 +312,44 @@ inline void FaceElementToCellStencilWrapper::
dWeight_dVar[0][1] = m_transMultiplier[iconn] * ( t0 * dt1 * sumOfTrans - dt1 * t0 * t1 ) / ( sumOfTrans * sumOfTrans );
}
+GEOS_HOST_DEVICE
+inline void FaceElementToCellStencilWrapper::
+ computeHalfWeights( localIndex const iconn,
+ CoefficientAccessor< arrayView3d< real64 const > > const & coefficient,
+ CoefficientAccessor< arrayView3d< real64 const > > const & dCoeff_dVar,
+ real64 ( & weight )[1][2],
+ real64 ( & dWeight_dVar )[1][2] ) const
+{
+ localIndex const er0 = m_elementRegionIndices[iconn][0];
+ localIndex const esr0 = m_elementSubRegionIndices[iconn][0];
+ localIndex const ei0 = m_elementIndices[iconn][0];
+
+ localIndex const er1 = m_elementRegionIndices[iconn][1];
+ localIndex const esr1 = m_elementSubRegionIndices[iconn][1];
+ localIndex const ei1 = m_elementIndices[iconn][1];
+
+ real64 faceConormal[3];
+
+ // Will change when implementing collocation points.
+ LvArray::tensorOps::hadamardProduct< 3 >( faceConormal, coefficient[er0][esr0][ei0][0], m_faceNormal[iconn] );
+ real64 const t0 = m_weights[iconn][0] * LvArray::tensorOps::AiBi< 3 >( m_cellToFaceVec[iconn], faceConormal );
+ // We consider the 3rd component of the permeability which is the normal one.
+ real64 const t1 = m_weights[iconn][1] * coefficient[er1][esr1][ei1][0][2];
+
+ real64 const sumOfTrans = t0+t1;
+ real64 const value = m_transMultiplier[iconn]*t0*t1/sumOfTrans;
+
+ weight[0][0] = value;
+ weight[0][1] = -value;
+
+ // We consider the 3rd component of the permeability which is the normal one.
+ real64 const dt0 = m_weights[iconn][0] * dCoeff_dVar[er0][esr0][ei0][0][0];
+ real64 const dt1 = m_weights[iconn][1] * dCoeff_dVar[er1][esr1][ei1][0][2];
+
+ dWeight_dVar[0][0] = ( dt0 * t1 * sumOfTrans - dt0 * t0 * t1 ) / ( sumOfTrans * sumOfTrans );
+ dWeight_dVar[0][1] = ( t0 * dt1 * sumOfTrans - dt1 * t0 * t1 ) / ( sumOfTrans * sumOfTrans );
+}
+
GEOS_HOST_DEVICE
inline void
FaceElementToCellStencilWrapper
diff --git a/src/coreComponents/finiteVolume/SurfaceElementStencil.hpp b/src/coreComponents/finiteVolume/SurfaceElementStencil.hpp
index 5de67fe2f11..22942a1a648 100644
--- a/src/coreComponents/finiteVolume/SurfaceElementStencil.hpp
+++ b/src/coreComponents/finiteVolume/SurfaceElementStencil.hpp
@@ -110,6 +110,21 @@ class SurfaceElementStencilWrapper : public StencilWrapperBase< SurfaceElementSt
real64 ( &weight )[maxNumConnections][2],
real64 ( &dWeight_dVar )[maxNumConnections][2] ) const;
+ /**
+ * @brief Compute weights and derivatives w.r.t to one variable.
+ * @param[in] iconn connection index
+ * @param[in] coefficient view accessor to the coefficient used to compute the weights
+ * @param[in] dCoeff_dVar view accessor to the derivative of the coefficient w.r.t to the variable
+ * @param[out] weight view weights
+ * @param[out] dWeight_dVar derivative of the weights w.r.t to the variable
+ */
+ GEOS_HOST_DEVICE
+ void computeHalfWeights( localIndex iconn,
+ CoefficientAccessor< arrayView3d< real64 const > > const & coefficient,
+ CoefficientAccessor< arrayView3d< real64 const > > const & dCoeff_dVar,
+ real64 ( &weight )[maxNumConnections][2],
+ real64 ( &dWeight_dVar )[maxNumConnections][2] ) const;
+
/**
* @brief Compute weights and derivatives w.r.t to one variable without coefficient
* Used in ReactiveCompositionalMultiphaseOBL solver for thermal transmissibility computation:
@@ -364,6 +379,70 @@ SurfaceElementStencilWrapper::
}
}
+GEOS_HOST_DEVICE
+inline void
+SurfaceElementStencilWrapper::
+ computeHalfWeights( localIndex iconn,
+ CoefficientAccessor< arrayView3d< real64 const > > const & coefficient,
+ CoefficientAccessor< arrayView3d< real64 const > > const & dCoeff_dVar,
+ real64 ( & weight )[maxNumConnections][2],
+ real64 ( & dWeight_dVar )[maxNumConnections][2] ) const
+{
+
+ real64 sumOfTrans = 0.0;
+ for( localIndex k=0; k coordinates_dw;
// Swc = 0.22
// consistent with Swmaxd = 1-Sgcrd = 1-0 = 1
- geos::testing::fillArray( coordinates_dw,
- {.22, .25, .3, .35, .4, .45, .5, .55, .6, .65, .66, .68, .72, .82, .91, 1.} );
+ geosx::testing::fill_array( coordinates_dw, {.22, .25, .3, .35, .4, .45, .5, .55, .6, .65, .66, .68, .72, .82, .91, 1.} );
array1d< real64_array > coordinates_iw;
// Swc = 0.22
// consistent with Swmaxi = 1-Sgcri = 1-0.3 = 0.7
- geos::testing::fillArray( coordinates_iw, {.22, .25, .3, .35, .4, .45, .5, .55, .6, .65, .66, .7} );
+ geosx::testing::fill_array( coordinates_iw, {.22, .25, .3, .35, .4, .45, .5, .55, .6, .65, .66, .7} );
// Gas phase saturation, fifth column of Table 2
array1d< real64_array > coordinates_dg;
// Sgcrd = 0.0
// consistent with Swc = 0.22
- geos::testing::fillArray( coordinates_dg,
- {0.000, 0.010, 0.030, 0.050, 0.100, 0.150, 0.200, 0.250, 0.300, 0.350, 0.400, 0.450,
- 0.500,
- 0.550, 0.600, 0.650, 0.700, 0.750, 0.780} );
+ geosx::testing::fill_array( coordinates_dg,
+ {0.000, 0.010, 0.030, 0.050, 0.100, 0.150, 0.200, 0.250, 0.300, 0.350, 0.400, 0.450, 0.500,
+ 0.550, 0.600, 0.650, 0.700, 0.750, 0.780} );
array1d< real64_array > coordinates_ig;
// Sgcri = 0.30;
// consistent with Swc = 0.22
- geos::testing::fillArray( coordinates_ig,
- {0.300, 0.350, 0.400, 0.450, 0.500, 0.550, 0.600, 0.650, 0.700, 0.750, 0.78} );
+ geosx::testing::fill_array( coordinates_ig, {0.300, 0.350, 0.400, 0.450, 0.500, 0.550, 0.600, 0.650, 0.700, 0.750, 0.78} );
// then define the bounding drainage and imbibibition relative permeability
// Water phase drainage relperm
real64_array drainageValues_w;
- geos::testing::fillArray( drainageValues_w, {0.00000, 0.00100, 0.00300, 0.01000, 0.01800, 0.03500, 0.04000, 0.05700,
- 0.08800, 0.14500, 0.16000, 0.19000, 0.26300, 0.45500, 0.69200, 1.} );
+ geosx::testing::fill_array( drainageValues_w, {0.00000, 0.00100, 0.00300, 0.01000, 0.01800, 0.03500, 0.04000, 0.05700,
+ 0.08800, 0.14500, 0.16000, 0.19000, 0.26300, 0.45500, 0.69200, 1.} );
// Gas phase drainage relperm, seventh column of Table 2
real64_array drainageValues_g;
- geos::testing::fillArray( drainageValues_g, {0.00000, 0.00200, 0.00700, 0.01000, 0.02000, 0.04000, 0.07500,
- 0.12700, 0.18000, 0.24000, 0.31000, 0.37300, 0.46000, 0.55000,
- 0.64000, 0.73000, 0.82500, 0.92000, 1.00000} );
+ geosx::testing::fill_array( drainageValues_g, {0.00000, 0.00200, 0.00700, 0.01000, 0.02000, 0.04000, 0.07500,
+ 0.12700, 0.18000, 0.24000, 0.31000, 0.37300, 0.46000, 0.55000,
+ 0.64000, 0.73000, 0.82500, 0.92000, 1.00000} );
real64_array imbibitionValues_w;
- geos::testing::fillArray( imbibitionValues_w, {0, 0.0156, 0.0680, 0.1409, 0.2296, 0.3317, 0.4455, 0.5700,
- 0.7044, 0.8479, 0.8776, 0.9382} );
+ geosx::testing::fill_array( imbibitionValues_w, {0, 0.0156, 0.0680, 0.1409, 0.2296, 0.3317, 0.4455, 0.5700,
+ 0.7044, 0.8479, 0.8776, 0.9382} );
real64_array imbibitionValues_g;
- geos::testing::fillArray( imbibitionValues_g, {0.0000, 0.03361965, 0.09509072, 0.17469281, 0.26895718,
- 0.37587908, 0.49410588, 0.62264458, 0.76072577, 0.90773047, 1.} );
+ geosx::testing::fill_array( imbibitionValues_g, {0.0000, 0.03361965, 0.09509072, 0.17469281, 0.26895718,
+ 0.37587908, 0.49410588, 0.62264458, 0.76072577, 0.90773047, 1.} );
initializeTable( "drainageWater_swg",
coordinates_dw,
diff --git a/src/coreComponents/integrationTests/fluidFlowTests/CMakeLists.txt b/src/coreComponents/integrationTests/fluidFlowTests/CMakeLists.txt
index 951cac7c502..9a89d938e2e 100644
--- a/src/coreComponents/integrationTests/fluidFlowTests/CMakeLists.txt
+++ b/src/coreComponents/integrationTests/fluidFlowTests/CMakeLists.txt
@@ -4,6 +4,7 @@ set( gtest_geosx_tests
testThermalSinglePhaseFlow.cpp
testTransmissibility.cpp
testImmiscibleMultiphaseFlow.cpp
+ testImmiscibleInterfaceConditions.cpp
testSinglePhaseMFDPolyhedral.cpp
testSinglePhaseReactiveTransport.cpp )
diff --git a/src/coreComponents/integrationTests/fluidFlowTests/testImmiscibleInterfaceConditions.cpp b/src/coreComponents/integrationTests/fluidFlowTests/testImmiscibleInterfaceConditions.cpp
new file mode 100644
index 00000000000..b1816d04204
--- /dev/null
+++ b/src/coreComponents/integrationTests/fluidFlowTests/testImmiscibleInterfaceConditions.cpp
@@ -0,0 +1,764 @@
+/*
+ * ------------------------------------------------------------------------------------------------------------
+ * SPDX-License-Identifier: LGPL-2.1-only
+ *
+ * Copyright (c) 2016-2024 Lawrence Livermore National Security LLC
+ * Copyright (c) 2018-2024 TotalEnergies
+ * Copyright (c) 2018-2024 The Board of Trustees of the Leland Stanford Junior University
+ * Copyright (c) 2023-2024 Chevron
+ * Copyright (c) 2019- GEOS/GEOSX Contributors
+ * All rights reserved
+ *
+ * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details.
+ * ------------------------------------------------------------------------------------------------------------
+ */
+#define phase0MinSat1 0.0
+#define phase1MinSat1 0.0
+#define phase0MinSat2 0.0
+#define phase1MinSat2 0.0
+
+
+#include
+#include
+
+#include "mainInterface/initialization.hpp"
+#include "mainInterface/GeosxState.hpp"
+#include "constitutive/fluid/twophaseimmisciblefluid/TwoPhaseImmiscibleFluid.hpp"
+#include "physicsSolvers/PhysicsSolverManager.hpp"
+#include "physicsSolvers/fluidFlow/ImmiscibleMultiphaseFlow.hpp"
+#include "integrationTests/fluidFlowTests/testCompFlowUtils.hpp"
+#include "constitutive/unitTests/FluidModelTest.hpp"
+#include "constitutive/unitTests/FluidModelTest_impl.hpp"
+#include "common/initializeEnvironment.hpp"
+#include "constitutive/relativePermeability/unitTests/constitutiveTestHelpers.hpp"
+#include "functions/FunctionManager.hpp"
+#include "constitutive/capillaryPressure/CapillaryPressureFields.hpp"
+
+#include
+
+
+
+using namespace geos;
+using namespace geos::dataRepository;
+using namespace geos::constitutive;
+using namespace geos::testing;
+
+CommandLineOptions g_commandLineOptions;
+
+TwoPhaseImmiscibleFluid * makeTwoPhaseImmiscibleFluid( TwoPhaseImmiscibleFluid & fluid )
+{
+
+ FunctionManager & functionManager = FunctionManager::getInstance();
+
+ // 1D table with linear interpolation
+ localIndex constexpr Naxis = 6;
+ localIndex constexpr NaxisSingle = 1;
+
+ real64_array densityCoordPhase0;
+ // fill( densityCoordPhase0, Feed< Naxis >{ 0.22, 0.3, 0.5, 0.6, 0.8, 1.0 } );
+ for (auto v : {0.0})
+ densityCoordPhase0.emplace_back(v);
+ real64_array densityValuesPhase0;
+ // fill( densityValuesPhase0, Feed< Naxis >{ 0.00603, 0.04224, 0.04224, 0.22423, 0.31311, 0.40203 } );
+ for (auto v : {1000.0})
+ densityValuesPhase0.emplace_back(v);
+
+ real64_array densityCoordPhase1;
+ // fill( densityCoordPhase1, Feed< Naxis >{ 1.22, 1.3, 1.5, 1.6, 1.8, 2.0 } );
+ for (auto v : {0.0})
+ densityCoordPhase1.emplace_back(v);
+ real64_array densityValuesPhase1;
+ // fill( densityValuesPhase1, Feed< Naxis >{ 0.00603, 0.04224, 0.04224, 0.22423, 0.31311, 0.40203 } );
+ for (auto v : {100.0})
+ densityValuesPhase1.emplace_back(v);
+
+ real64_array viscosityCoordPhase0;
+ // fill( viscosityCoordPhase0, Feed< Naxis >{ 0.22, 0.3, 0.5, 0.6, 0.8, 1.0 } );
+ for (auto v : {0.0})
+ viscosityCoordPhase0.emplace_back(v);
+ real64_array viscosityValuesPhase0;
+ // fill( viscosityValuesPhase0, Feed< Naxis >{ 40203, 31311, 22423, 15011, 4224, 603 } );
+ for (auto v : {0.001})
+ viscosityValuesPhase0.emplace_back(v);
+
+ real64_array viscosityCoordPhase1;
+ // fill( viscosityCoordPhase1, Feed< NaxisSingle >{ 0.22 } );
+ for (auto v : {0.0})
+ viscosityCoordPhase1.emplace_back(v);
+
+ real64_array viscosityValuesPhase1;
+ // fill( viscosityValuesPhase1, Feed< NaxisSingle >{ 45 } );
+ for (auto v : {0.001})
+ viscosityValuesPhase1.emplace_back(v);
+
+ TableFunction & table_density0 = dynamicCast< TableFunction & >( *functionManager.createChild( "TableFunction", "densityTablePhase0" ) );
+ array1d coords_density0;
+ coords_density0.emplace_back(densityCoordPhase0);
+ table_density0.setTableCoordinates( coords_density0, { units::Dimensionless } );
+ table_density0.setTableValues( densityValuesPhase0, units::Dimensionless );
+ table_density0.reInitializeFunction();
+
+ table_density0.setInterpolationMethod( TableFunction::InterpolationType::Linear );
+
+ TableFunction & table_density1 = dynamicCast< TableFunction & >( *functionManager.createChild( "TableFunction", "densityTablePhase1" ) );
+ array1d coords_density1;
+ coords_density1.emplace_back(densityCoordPhase1);
+ table_density1.setTableCoordinates( coords_density1, { units::Dimensionless } );
+ table_density1.setTableValues( densityValuesPhase1, units::Dimensionless );
+ table_density1.reInitializeFunction();
+
+ table_density1.setInterpolationMethod( TableFunction::InterpolationType::Linear );
+
+ TableFunction & table_viscosity0 = dynamicCast< TableFunction & >( *functionManager.createChild( "TableFunction", "viscosityTablePhase0" ) );
+ array1d coords_viscosity0;
+ coords_viscosity0.emplace_back(viscosityCoordPhase0);
+ table_viscosity0.setTableCoordinates( coords_viscosity0, { units::Dimensionless } );
+ table_viscosity0.setTableValues( viscosityValuesPhase0, units::Dimensionless );
+ table_viscosity0.reInitializeFunction();
+
+ table_viscosity0.setInterpolationMethod( TableFunction::InterpolationType::Linear );
+
+ TableFunction & table_viscosity1 = dynamicCast< TableFunction & >( *functionManager.createChild( "TableFunction", "viscosityTablePhase1" ) );
+ array1d coords_viscosity1;
+ coords_viscosity1.emplace_back(viscosityCoordPhase1);
+ table_viscosity1.setTableCoordinates( coords_viscosity1, { units::Dimensionless } );
+ table_viscosity1.setTableValues( viscosityValuesPhase1, units::Dimensionless );
+ table_viscosity1.reInitializeFunction();
+
+ table_viscosity1.setInterpolationMethod( TableFunction::InterpolationType::Linear );
+
+
+ // createTable( "densityTablePhase0", densityCoordPhase0, densityValuesPhase0 );
+ // createTable( "densityTablePhase1", densityCoordPhase1, densityValuesPhase1 );
+ // createTable( "viscosityTablePhase0", viscosityCoordPhase0, viscosityValuesPhase0 );
+ // createTable( "viscosityTablePhase1", viscosityCoordPhase1, viscosityValuesPhase1 );
+
+ // 2) Set up the constitutive model
+
+ string_array & phaseNames = fluid.getReference< string_array >( TwoPhaseImmiscibleFluid::viewKeyStruct::phaseNamesString() );
+ phaseNames.emplace_back( "water" );
+ phaseNames.emplace_back( "gas" );
+
+ string_array & densityTableNames = fluid.getReference< string_array >( TwoPhaseImmiscibleFluid::viewKeyStruct::densityTableNamesString() );
+ densityTableNames.emplace_back( "densityTablePhase0" );
+ densityTableNames.emplace_back( "densityTablePhase1" );
+
+ string_array & viscosityTableNames = fluid.getReference< string_array >( TwoPhaseImmiscibleFluid::viewKeyStruct::viscosityTableNamesString() );
+ viscosityTableNames.emplace_back( "viscosityTablePhase0" );
+ viscosityTableNames.emplace_back( "viscosityTablePhase1" );
+
+ fluid.postInputInitializationRecursive();
+ fluid.initialize(); // to test all the checks
+
+ return &fluid;
+}
+
+CapillaryPressureBase & makeJFunctionCapPressureTwoPhase( string const & name, Group & parent )
+{
+ FunctionManager & functionManager = FunctionManager::getInstance();
+
+ // 1) First, define the tables
+
+ // // 1D table, various interpolation methods
+ // localIndex const Naxis = 12;
+
+ // // Setup table
+ // array1d< real64_array > coordinates;
+ // coordinates.resize( 1 );
+ // coordinates[0].resize( Naxis );
+
+ // coordinates[0][0] = 0.0;
+ // coordinates[0][1] = 0.05;
+ // coordinates[0][2] = 0.15;
+ // coordinates[0][3] = 0.25;
+ // coordinates[0][4] = 0.35;
+ // coordinates[0][5] = 0.45;
+ // coordinates[0][6] = 0.55;
+ // coordinates[0][7] = 0.65;
+ // coordinates[0][8] = 0.75;
+ // coordinates[0][9] = 0.85;
+ // coordinates[0][10] = 0.95;
+ // coordinates[0][11] = 1.0;
+
+ // real64_array values( Naxis );
+ // values[0] = 4.331729359;
+ // values[1] = 3.523266264;
+ // values[2] = 2.677103439;
+ // values[3] = 2.356150157;
+ // values[4] = 2.166062360;
+ // values[5] = 2.034158727;
+ // values[6] = 1.934627222;
+ // values[7] = 1.855494313;
+ // values[8] = 1.790286970;
+ // values[9] = 1.735134860;
+ // values[10] = 1.687551617;
+ // values[11] = 1.666049754;
+
+
+ localIndex const Naxis = 2;
+
+ // Setup table
+ array1d< real64_array > coordinates;
+ coordinates.resize( 1 );
+ coordinates[0].resize( Naxis );
+
+ coordinates[0][0] = 0.0;
+ coordinates[0][1] = 1.0;
+
+ real64_array values( Naxis );
+ values[0] = 4.331729359;
+ values[1] = 1.666049754;
+
+
+ TableFunction & table_w = dynamicCast< TableFunction & >( *functionManager.createChild( "TableFunction", "water_jFunction" ) );
+ table_w.setTableCoordinates( coordinates, { units::Dimensionless } );
+ table_w.setTableValues( values, units::Dimensionless );
+ table_w.reInitializeFunction();
+
+ table_w.setInterpolationMethod( TableFunction::InterpolationType::Linear );
+
+ // 2) Then set up the constitutive model
+
+ JFunctionCapillaryPressure & capPressure = parent.registerGroup< JFunctionCapillaryPressure >( name );
+
+ string_array & phaseNames = capPressure.getReference< string_array >( CapillaryPressureBase::viewKeyStruct::phaseNamesString() );
+ phaseNames.resize( 2 );
+ phaseNames[0] = "water"; phaseNames[1] = "gas";
+
+ auto & waterTableName = capPressure.getReference< string >( JFunctionCapillaryPressure::viewKeyStruct::wettingNonWettingJFuncTableNameString() );
+ waterTableName = "water_jFunction";
+
+ auto & surfaceTension = capPressure.getReference< real64 >( JFunctionCapillaryPressure::viewKeyStruct::wettingNonWettingSurfaceTensionString() );
+ //surfaceTension = 23.86955676433857e-3;
+ surfaceTension = 0.02;
+
+ auto & permeabilityDirection =
+ capPressure.getReference< JFunctionCapillaryPressure::PermeabilityDirection >( JFunctionCapillaryPressure::viewKeyStruct::permeabilityDirectionString() );
+ permeabilityDirection = JFunctionCapillaryPressure::PermeabilityDirection::XY;
+
+ capPressure.postInputInitializationRecursive();
+ capPressure.initialize(); // to test all the checks
+
+ return capPressure;
+}
+
+CapillaryPressureBase & makeTableCapPressureTwoPhase1( string const & name, Group & parent )
+{
+ FunctionManager & functionManager = FunctionManager::getInstance();
+
+ // 1) First, define the tables
+
+ // 1D table, various interpolation methods
+ localIndex Naxis = 12;
+
+ // Setup table
+ array1d< real64_array > coordinates;
+ coordinates.resize( 1 );
+ coordinates[0].resize( Naxis );
+ coordinates[0][0] = 0.0;
+ coordinates[0][1] = 0.05;
+ coordinates[0][2] = 0.15;
+ coordinates[0][3] = 0.25;
+ coordinates[0][4] = 0.35;
+ coordinates[0][5] = 0.45;
+ coordinates[0][6] = 0.55;
+ coordinates[0][7] = 0.65;
+ coordinates[0][8] = 0.75;
+ coordinates[0][9] = 0.85;
+ coordinates[0][10] = 0.95;
+ coordinates[0][11] = 1.0;
+
+ real64_array values( Naxis );
+ values[0] = 130000.0;
+ values[1] = 90572.79;
+ values[2] = 49307.11;
+ values[3] = 33654.85;
+ values[4] = 24384.64;
+ values[5] = 17951.96;
+ values[6] = 13098;
+ values[7] = 9238.84;
+ values[8] = 6058.81;
+ values[9] = 3369.14;
+ values[10] = 1048.6;
+ values[11] = 0.0;
+
+ // localIndex const Naxis = 2;
+
+ // // Setup table
+ // array1d< real64_array > coordinates;
+ // coordinates.resize( 1 );
+ // coordinates[0].resize( Naxis );
+
+ // coordinates[0][0] = 0.0;
+ // coordinates[0][1] = 1.0;
+
+ // real64_array values( Naxis );
+ // values[0] = 129999.999994362;
+ // values[1] = 50000.0000139914;
+
+
+ TableFunction & table_w = dynamicCast< TableFunction & >( *functionManager.createChild( "TableFunction", "water_pc" ) );
+ table_w.setTableCoordinates( coordinates, { units::Dimensionless } );
+ table_w.setTableValues( values, units::Pressure );
+ table_w.reInitializeFunction();
+
+ table_w.setInterpolationMethod( TableFunction::InterpolationType::Linear );
+
+ // 2) Then set up the constitutive model
+
+ TableCapillaryPressure & capPressure = parent.registerGroup< TableCapillaryPressure >( name );
+
+ string_array & phaseNames = capPressure.getReference< string_array >( CapillaryPressureBase::viewKeyStruct::phaseNamesString() );
+ phaseNames.resize( 2 );
+ phaseNames[0] = "water"; phaseNames[1] = "gas";
+
+ auto & waterTableName = capPressure.getReference< string >( TableCapillaryPressure::viewKeyStruct::wettingNonWettingCapPresTableNameString() );
+ waterTableName = "water_pc";
+
+ capPressure.postInputInitializationRecursive();
+ capPressure.initialize(); // to test all the checks
+ return capPressure;
+}
+
+CapillaryPressureBase & makeTableCapPressureTwoPhase2( string const & name, Group & parent )
+{
+ FunctionManager & functionManager = FunctionManager::getInstance();
+
+ // 1) First, define the tables
+
+ // 1D table, various interpolation methods
+ localIndex Naxis = 12;
+
+ // Setup table
+ array1d< real64_array > coordinates;
+ coordinates.resize( 1 );
+ coordinates[0].resize( Naxis );
+ coordinates[0][0] = 0.0;
+ coordinates[0][1] = 0.05;
+ coordinates[0][2] = 0.15;
+ coordinates[0][3] = 0.25;
+ coordinates[0][4] = 0.35;
+ coordinates[0][5] = 0.45;
+ coordinates[0][6] = 0.55;
+ coordinates[0][7] = 0.65;
+ coordinates[0][8] = 0.75;
+ coordinates[0][9] = 0.85;
+ coordinates[0][10] = 0.95;
+ coordinates[0][11] = 1.0;
+
+ real64_array values( Naxis );
+ values[0] = 195000.0;
+ values[1] = 135859.25;
+ values[2] = 73960.67;
+ values[3] = 50482.28;
+ values[4] = 36576.96;
+ values[5] = 26927.94;
+ values[6] = 19647;
+ values[7] = 13858.26;
+ values[8] = 9088.2;
+ values[9] = 5053.72;
+ values[10] = 1572.9;
+ values[11] = 0.0;
+
+ // localIndex const Naxis = 2;
+
+ // // Setup table
+ // array1d< real64_array > coordinates;
+ // coordinates.resize( 1 );
+ // coordinates[0].resize( Naxis );
+
+ // coordinates[0][0] = 0.0;
+ // coordinates[0][1] = 1.0;
+
+ // real64_array values( Naxis );
+ // values[0] = 129999.999994362;
+ // values[1] = 50000.0000139914;
+
+
+ TableFunction & table_w = dynamicCast< TableFunction & >( *functionManager.createChild( "TableFunction", "water_pc" ) );
+ table_w.setTableCoordinates( coordinates, { units::Dimensionless } );
+ table_w.setTableValues( values, units::Pressure );
+ table_w.reInitializeFunction();
+
+ table_w.setInterpolationMethod( TableFunction::InterpolationType::Linear );
+
+ // 2) Then set up the constitutive model
+
+ TableCapillaryPressure & capPressure = parent.registerGroup< TableCapillaryPressure >( name );
+
+ string_array & phaseNames = capPressure.getReference< string_array >( CapillaryPressureBase::viewKeyStruct::phaseNamesString() );
+ phaseNames.resize( 2 );
+ phaseNames[0] = "water"; phaseNames[1] = "gas";
+
+ auto & waterTableName = capPressure.getReference< string >( TableCapillaryPressure::viewKeyStruct::wettingNonWettingCapPresTableNameString() );
+ waterTableName = "water_pc";
+
+ capPressure.postInputInitializationRecursive();
+ capPressure.initialize(); // to test all the checks
+ return capPressure;
+}
+
+CapillaryPressureBase & makeBrooksCoreyCapPressureTwoPhase1( string const & name, Group & parent )
+{
+ BrooksCoreyCapillaryPressure & capPressure = parent.registerGroup< BrooksCoreyCapillaryPressure >( name );
+
+ string_array & phaseNames = capPressure.getReference< string_array >( CapillaryPressureBase::viewKeyStruct::phaseNamesString() );
+ phaseNames.resize( 2 );
+ phaseNames[0] = "water"; phaseNames[1] = "gas";
+
+ array1d< real64 > & phaseMinSat = capPressure.getReference< array1d< real64 > >( BrooksCoreyCapillaryPressure::viewKeyStruct::phaseMinVolumeFractionString() );
+ phaseMinSat.resize( 2 );
+ phaseMinSat[0] = phase0MinSat1; phaseMinSat[1] = phase1MinSat1;
+
+ array1d< real64 > & phaseCapPressureExpInv =
+ capPressure.getReference< array1d< real64 > >( BrooksCoreyCapillaryPressure::viewKeyStruct::phaseCapPressureExponentInvString() );
+ phaseCapPressureExpInv.resize( 2 );
+ phaseCapPressureExpInv[0] = 4; phaseCapPressureExpInv[1] = 1;
+
+ array1d< real64 > & phaseEntryPressure = capPressure.getReference< array1d< real64 > >( BrooksCoreyCapillaryPressure::viewKeyStruct::phaseEntryPressureString() );
+ phaseEntryPressure.resize( 2 );
+ phaseEntryPressure[0] = 4.0e3; phaseEntryPressure[1] = 0;
+
+ real64 & capPressureEpsilon = capPressure.getReference< real64 >( BrooksCoreyCapillaryPressure::viewKeyStruct::capPressureEpsilonString() );
+ capPressureEpsilon = 1.0e-8;
+
+ capPressure.postInputInitializationRecursive();
+ return capPressure;
+}
+
+CapillaryPressureBase & makeBrooksCoreyCapPressureTwoPhase2( string const & name, Group & parent )
+{
+ BrooksCoreyCapillaryPressure & capPressure = parent.registerGroup< BrooksCoreyCapillaryPressure >( name );
+
+ string_array & phaseNames = capPressure.getReference< string_array >( CapillaryPressureBase::viewKeyStruct::phaseNamesString() );
+ phaseNames.resize( 2 );
+ phaseNames[0] = "water"; phaseNames[1] = "gas";
+
+ array1d< real64 > & phaseMinSat = capPressure.getReference< array1d< real64 > >( BrooksCoreyCapillaryPressure::viewKeyStruct::phaseMinVolumeFractionString() );
+ phaseMinSat.resize( 2 );
+ phaseMinSat[0] = phase0MinSat2; phaseMinSat[1] = phase1MinSat2;
+
+ array1d< real64 > & phaseCapPressureExpInv =
+ capPressure.getReference< array1d< real64 > >( BrooksCoreyCapillaryPressure::viewKeyStruct::phaseCapPressureExponentInvString() );
+ phaseCapPressureExpInv.resize( 2 );
+ phaseCapPressureExpInv[0] = 4; phaseCapPressureExpInv[1] = 1;
+
+ array1d< real64 > & phaseEntryPressure = capPressure.getReference< array1d< real64 > >( BrooksCoreyCapillaryPressure::viewKeyStruct::phaseEntryPressureString() );
+ phaseEntryPressure.resize( 2 );
+ phaseEntryPressure[0] = 2.0e3; phaseEntryPressure[1] = 0;
+
+ real64 & capPressureEpsilon = capPressure.getReference< real64 >( BrooksCoreyCapillaryPressure::viewKeyStruct::capPressureEpsilonString() );
+ capPressureEpsilon = 1e-8;
+
+ capPressure.postInputInitializationRecursive();
+ return capPressure;
+}
+
+RelativePermeabilityBase & makeBrooksCoreyRelPerm( string const & name, Group & parent )
+{
+ BrooksCoreyRelativePermeability & relPerm = parent.registerGroup< BrooksCoreyRelativePermeability >( name );
+
+ string_array & phaseNames = relPerm.getReference< string_array >( RelativePermeabilityBase::viewKeyStruct::phaseNamesString() );
+ phaseNames.resize( 2 );
+ phaseNames[0] = "water"; phaseNames[1] = "gas";
+
+ array1d< real64 > & phaseMinSat = relPerm.getReference< array1d< real64 > >( BrooksCoreyRelativePermeability::viewKeyStruct::phaseMinVolumeFractionString() );
+ phaseMinSat.resize( 2 );
+ phaseMinSat[0] = phase0MinSat1; phaseMinSat[1] = phase1MinSat1;
+
+ array1d< real64 > & phaseRelPermExp = relPerm.getReference< array1d< real64 > >( BrooksCoreyRelativePermeability::viewKeyStruct::phaseRelPermExponentString() );
+ phaseRelPermExp.resize( 2 );
+ phaseRelPermExp[0] = 2.0; phaseRelPermExp[1] = 2.0;
+
+ array1d< real64 > & phaseRelPermMaxVal = relPerm.getReference< array1d< real64 > >( BrooksCoreyRelativePermeability::viewKeyStruct::phaseRelPermMaxValueString() );
+ phaseRelPermMaxVal.resize( 2 );
+ phaseRelPermMaxVal[0] = 1.0; phaseRelPermMaxVal[1] = 1.0;
+
+ relPerm.postInputInitializationRecursive();
+ return relPerm;
+}
+
+class ImmiscibleInterfaceConditionsTest : public FluidModelTest< TwoPhaseImmiscibleFluid, 2 >
+{
+public:
+ ImmiscibleInterfaceConditionsTest(): state( std::make_unique< CommandLineOptions >( g_commandLineOptions )),
+ m_parent( "TestParentGroup", m_node )
+
+ {}
+
+protected:
+
+ static real64 constexpr time = 0.0;
+ static real64 constexpr dt = 1e4;
+ static real64 constexpr eps = std::numeric_limits< real64 >::epsilon();
+
+ GeosxState state;
+ ImmiscibleMultiphaseFlow *solver;
+ conduit::Node m_node;
+ dataRepository::Group m_parent;
+};
+
+real64 constexpr ImmiscibleInterfaceConditionsTest::time;
+real64 constexpr ImmiscibleInterfaceConditionsTest::dt;
+real64 constexpr ImmiscibleInterfaceConditionsTest::eps;
+
+
+
+TEST_F( ImmiscibleInterfaceConditionsTest, LocalNonlinearSolverConvergence )
+{
+
+ // using Base = FluidModelTest< TwoPhaseImmiscibleFluid, 2 >;
+ createFluid( "fluid", [this]( TwoPhaseImmiscibleFluid & fluid ){
+ makeTwoPhaseImmiscibleFluid( fluid );
+
+ // getting constitutive models:
+ RelativePermeabilityBase & relPerm = makeBrooksCoreyRelPerm( "relPerm" , this->m_parent);
+ RelativePermeabilityBase * relPermPtr = &relPerm;
+ // CapillaryPressureBase & capPressure0 = makeJFunctionCapPressureTwoPhase( "capPressure0", this->m_parent );
+ // CapillaryPressureBase * capPressurePtr0 = &capPressure0;
+
+ CapillaryPressureBase & capPressure0 = makeTableCapPressureTwoPhase1( "capPressure0", this->m_parent );
+ CapillaryPressureBase * capPressurePtr0 = &capPressure0;
+
+ CapillaryPressureBase & capPressure1 = makeTableCapPressureTwoPhase2( "capPressure1", this->m_parent );
+ CapillaryPressureBase * capPressurePtr1 = &capPressure1;
+ // CapillaryPressureBase & capPressure0 = makeBrooksCoreyCapPressureTwoPhase1( "capPressure0", this->m_parent );
+ // CapillaryPressureBase * capPressurePtr0 = &capPressure0;
+
+ // CapillaryPressureBase & capPressure1 = makeBrooksCoreyCapPressureTwoPhase2( "capPressure1", this->m_parent );
+ // CapillaryPressureBase * capPressurePtr1 = &capPressure1;
+
+ std::vector< RelativePermeabilityBase * > relPerms = {relPermPtr, relPermPtr};
+ std::vector< CapillaryPressureBase * > capPressures = {capPressurePtr0, capPressurePtr1};
+ std::vector< TwoPhaseImmiscibleFluid * > fluids = { &fluid, &fluid };
+ // real64 uT = 3.2864545889999906e-05;
+
+ real64 uT = 3.3e-2;
+// real64 uT = 1e-7;
+ stdVector< real64 > saturations = {0.2, 0.4};
+ stdVector< real64 > trappedSats1 = {phase0MinSat1, phase1MinSat1};
+ stdVector< real64 > trappedSats2 = {phase0MinSat2, phase1MinSat2};
+ stdVector< real64 > pressures = {1e7, 1e7};
+ stdVector< real64 > JFMultipliers = {45016.662822296035, 30011.108548197357};
+ stdVector< fields::cappres::ModeIndexType > modes = {static_cast(0), static_cast(0)};
+ stdVector< real64 > transHats = {1.9738466000000002e-12, 4.4411548500000007e-12};
+ stdVector< real64 > dTransHats_dP = {0.0, 0.0};
+ stdVector< real64 > gravCoefHats = {490.5, 490.5};
+ stdVector< real64 > gravCoefs = {465.97500000000002, 515.02499999999998};
+ stdVector< real64 > cellCenterDuT = {0.0, 0.0, 0.0, 0.0}; // duT_dP[0], duT_dP[1], duT_dS[0], duT_dS[1]
+ stdVector< real64 > cellCenterDens = {1000.0, 800.0}; // density for each phase
+ stdVector< real64 > cellCenterDens_dP = {0.0, 0.0, 0.0, 0.0}; // dDens_dP[0][0], dDens_dP[0][1], dDens_dP[1][0], dDens_dP[1][1]
+ stdVector< real64 > phaseMaxHistVolFrac1 = {0.0, 0.0};
+ stdVector< real64 > phaseMinHistVolFrac1 = {0.0, 0.0};
+ stdVector< real64 > phaseMaxHistVolFrac2 = {0.0, 0.0};
+ stdVector< real64 > phaseMinHistVolFrac2 = {0.0, 0.0};
+
+ stdVector< real64 > phi = {0.0, 0.0};
+ stdVector< real64 > grad_phi_P = {0.0, 0.0, 0.0, 0.0};
+ stdVector< real64 > grad_phi_S = {0.0, 0.0, 0.0, 0.0};
+ bool converged = false;
+
+ std::ofstream outFile( "local_solver_results.csv" );
+
+
+ // Write data to the file
+ outFile << "Si";
+ outFile << ",";
+ outFile << "Sj";
+ outFile << ",";
+ outFile << "Fw_alpha";
+ outFile << ",";
+ outFile << "Fn_alpha";
+ outFile << ",";
+ outFile << "Residual_initial";
+ outFile << ",";
+ outFile << "Pc_int";
+ outFile << ",";
+ outFile << "Residual";
+ outFile << ",";
+ outFile << "newton";
+ outFile << std::endl;
+
+ real64 const start_sat = 0.0;
+ real64 const end_sat = 1.0;
+ real64 const dS = 1e-2;
+ // real64 Si = 0.0;
+ // real64 Sj = 0.9;
+
+ for( real64 Si = start_sat; Si <= end_sat + 1e-8; Si += dS )
+ {
+ for( real64 Sj = start_sat; Sj <= end_sat + 1e-8; Sj += dS )
+ {
+ saturations[0] = Si;
+ saturations[1] = Sj;
+
+ auto t0 = std::chrono::high_resolution_clock::now();
+
+// Call the GEOS local solver
+ geos::immiscibleMultiphaseKernels::local_solver( uT, saturations, pressures, JFMultipliers, trappedSats1, trappedSats2, modes, transHats, dTransHats_dP, gravCoefHats, gravCoefs,
+ cellCenterDuT, cellCenterDens, cellCenterDens_dP, relPerms, capPressures, fluids, phi, grad_phi_P, grad_phi_S, converged,
+ phaseMaxHistVolFrac1, phaseMinHistVolFrac1, phaseMaxHistVolFrac2, phaseMinHistVolFrac2 );
+
+auto t1 = std::chrono::high_resolution_clock::now();
+std::chrono::duration elapsed = t1 - t0;
+// std::cout << "Local solver time: " << elapsed.count() << " s" << std::endl;
+EXPECT_TRUE( converged ) << "Local solver diverged for saturations Si=" << Si << ", Sj=" << Sj;
+
+ // Write data to the file
+ outFile << GEOS_FMT( "{:10.10e}", saturations[0] );
+ outFile << GEOS_FMT( ",{:10.10e}", saturations[1] );
+ outFile << GEOS_FMT( ",{:10.10e}", phi[0] );
+ outFile << GEOS_FMT( ",{:10.10e}", phi[1] );
+ outFile << GEOS_FMT( ",{:10.10e}", grad_phi_P[0] );
+ outFile << GEOS_FMT( ",{:10.10e}", grad_phi_P[1] );
+ outFile << GEOS_FMT( ",{:10.10e}", grad_phi_P[2] );
+ outFile << GEOS_FMT( ",{:10.10e}", grad_phi_P[3] );
+ outFile << std::endl;
+ phi[0] = 0;
+ phi[1] = 0;
+ grad_phi_P[0] = 0;
+ grad_phi_P[1] = 0;
+ grad_phi_P[2] = 0;
+ grad_phi_P[3] = 0;
+ grad_phi_S[0] = 0;
+ grad_phi_S[1] = 0;
+ grad_phi_S[2] = 0;
+ grad_phi_S[3] = 0;
+
+ }
+}
+
+ outFile.close();
+
+} );
+}
+
+TEST_F( ImmiscibleInterfaceConditionsTest, LocalSolverResults )
+{
+
+ // using Base = FluidModelTest< TwoPhaseImmiscibleFluid, 2 >;
+ createFluid( "fluid", [this]( TwoPhaseImmiscibleFluid & fluid ){
+ makeTwoPhaseImmiscibleFluid( fluid );
+
+ // getting constitutive models:
+ RelativePermeabilityBase & relPerm = makeBrooksCoreyRelPerm( "relPerm" , this->m_parent);
+ RelativePermeabilityBase * relPermPtr = &relPerm;
+
+ CapillaryPressureBase & capPressure0 = makeBrooksCoreyCapPressureTwoPhase1( "capPressure0", this->m_parent );
+ CapillaryPressureBase * capPressurePtr0 = &capPressure0;
+
+ CapillaryPressureBase & capPressure1 = makeBrooksCoreyCapPressureTwoPhase2( "capPressure1", this->m_parent );
+ CapillaryPressureBase * capPressurePtr1 = &capPressure1;
+
+ std::vector< RelativePermeabilityBase * > relPerms = {relPermPtr, relPermPtr};
+ std::vector< CapillaryPressureBase * > capPressures = {capPressurePtr0, capPressurePtr1};
+ std::vector< TwoPhaseImmiscibleFluid * > fluids = { &fluid, &fluid };
+
+ real64 uT = 0.000001889581158;
+
+ stdVector< real64 > saturations = {0.6, 0.3};
+ stdVector< real64 > trappedSats1 = {phase0MinSat1, phase1MinSat1};
+ stdVector< real64 > trappedSats2 = {phase0MinSat2, phase1MinSat2};
+ stdVector< real64 > pressures = {1e7, 1e7};
+ stdVector< real64 > JFMultipliers = {45016.662822296035, 30011.108548197357};
+ stdVector< fields::cappres::ModeIndexType > modes = {static_cast(0), static_cast(0)};
+ stdVector< real64 > transHats = {2.0e-12, 8.0e-12};
+ stdVector< real64 > dTransHats_dP = {0.0, 0.0};
+ stdVector< real64 > gravCoefHats = {49.05, 49.05};
+ stdVector< real64 > gravCoefs = {46.5975, 51.5025};
+ stdVector< real64 > cellCenterDuT = {8.32E-10, -8.32E-10, 0.0000063429744518, -0.0000012971521486}; // duT_dP[0], duT_dP[1], duT_dS[0], duT_dS[1]
+ stdVector< real64 > cellCenterDens = {1000.0, 100.0}; // density for each phase
+ stdVector< real64 > cellCenterDens_dP = {0.0, 0.0, 0.0, 0.0}; // dDens_dP[0][0], dDens_dP[0][1], dDens_dP[1][0], dDens_dP[1][1]
+ stdVector< real64 > phaseMaxHistVolFrac1 = {0.0, 0.0};
+ stdVector< real64 > phaseMinHistVolFrac1 = {0.0, 0.0};
+ stdVector< real64 > phaseMaxHistVolFrac2 = {0.0, 0.0};
+ stdVector< real64 > phaseMinHistVolFrac2 = {0.0, 0.0};
+
+ stdVector< real64 > phi = {0.0, 0.0};
+ stdVector< real64 > grad_phi_P = {0.0, 0.0, 0.0, 0.0};
+ stdVector< real64 > grad_phi_S = {0.0, 0.0, 0.0, 0.0};
+ bool converged = false;
+
+
+
+// Call the GEOS local solver
+ geos::immiscibleMultiphaseKernels::local_solver( uT, saturations, pressures, JFMultipliers, trappedSats1, trappedSats2, modes, transHats, dTransHats_dP, gravCoefHats, gravCoefs,
+ cellCenterDuT, cellCenterDens, cellCenterDens_dP, relPerms, capPressures, fluids, phi, grad_phi_P, grad_phi_S, converged,
+ phaseMaxHistVolFrac1, phaseMinHistVolFrac1, phaseMaxHistVolFrac2, phaseMinHistVolFrac2 );
+
+ real64 const tolerance_eps = std::sqrt( std::numeric_limits< real64 >::epsilon() );
+ real64 const tol = 1e-4;
+ real64 const abs_tolerance = tolerance_eps * tol;
+
+ EXPECT_NEAR( phi[0], 1.676451024635667e-03, abs_tolerance );
+ EXPECT_NEAR( phi[1], 2.131301333609180e-05, abs_tolerance );
+ EXPECT_NEAR( grad_phi_P[0], 5.760000000000000e-07, abs_tolerance );
+ EXPECT_NEAR( grad_phi_P[1], -5.760000000000000e-07, abs_tolerance );
+ EXPECT_NEAR( grad_phi_P[2], 2.560000000000001e-08, abs_tolerance );
+ EXPECT_NEAR( grad_phi_P[3], -2.560000000000001e-08, abs_tolerance );
+ EXPECT_NEAR( grad_phi_S[0], 7.268012258072125e-03, abs_tolerance );
+ EXPECT_NEAR( grad_phi_S[1], -8.980284105794445e-04, abs_tolerance );
+ EXPECT_NEAR( grad_phi_S[2], -9.250378062747074e-05, abs_tolerance );
+ EXPECT_NEAR( grad_phi_S[3], -3.991237380353088e-05, abs_tolerance );
+
+
+} );
+}
+
+
+
+int main( int argc, char * *argv )
+{
+ ::testing::InitGoogleTest( &argc, argv );
+ g_commandLineOptions = *geos::basicSetup( argc, argv );
+ int const result = RUN_ALL_TESTS();
+ geos::basicCleanup();
+ return result;
+}
+
+// maybe needed later on
+// TEST_F( CapillaryPressureTest, numericalDerivatives_jFunctionCapPressureTwoPhase )
+// {
+// initialize( makeJFunctionCapPressureTwoPhase( "capPressure", m_parent ) );
+
+// // here, we have to apply a special treatment to this test
+// // to make sure that the J-function multiplier is initialized using initializeRockState
+// // this requires calling allocateConstitutiveData in advance (it will be called again later, in the "test" function)
+
+// // setup some values for porosity and permeability
+// array2d< real64 > porosity;
+// porosity.resize( 1, 1 );
+// porosity[0][0] = 0.13496794266569806;
+// array3d< real64 > permeability;
+// permeability.resize( 1, 1, 3 );
+// permeability[0][0][0] = 0.1722194e-15;
+// permeability[0][0][1] = 0.3423156e-15;
+// permeability[0][0][2] = 0.2324191e-15;
+
+// // initialize the J-function multiplier (done on GPU if GPU is available)
+// m_model->allocateConstitutiveData( m_parent, 1 );
+// m_model->initializeRockState( porosity.toViewConst(), permeability.toViewConst() );
+
+// // move the multiplier back to the CPU since the test is performed on the CPU
+// auto & jFuncMultiplier =
+// m_model->getReference< array2d< real64 > >( fields::cappres::jFuncMultiplier::key() );
+// jFuncMultiplier.move( hostMemorySpace, false );
+
+// // we are ready to proceed to the test
+
+// real64 const eps = std::sqrt( std::numeric_limits< real64 >::epsilon() );
+// real64 const tol = 1e-4;
+
+// real64 const start_sat = 0.3;
+// real64 const end_sat = 0.9;
+// real64 const dS = 1e-1;
+// array1d< real64 > sat( 2 );
+// sat[0] = start_sat; sat[1] = 1-sat[0];
+// while( sat[0] <= end_sat )
+// {
+// test( sat, eps, tol );
+// sat[0] += dS;
+// sat[1] = 1 - sat[0];
+// }
+
+// }
\ No newline at end of file
diff --git a/src/coreComponents/physicsSolvers/fluidFlow/CMakeLists.txt b/src/coreComponents/physicsSolvers/fluidFlow/CMakeLists.txt
index 6eb55e9775a..df14131a19a 100644
--- a/src/coreComponents/physicsSolvers/fluidFlow/CMakeLists.txt
+++ b/src/coreComponents/physicsSolvers/fluidFlow/CMakeLists.txt
@@ -29,8 +29,6 @@ set( fluidFlowSolvers_headers
ImmiscibleMultiphaseFlow.hpp
ImmiscibleMultiphaseFlowFields.hpp
LogLevelsInfo.hpp
- ReactiveCompositionalMultiphaseOBL.hpp
- ReactiveCompositionalMultiphaseOBLFields.hpp
SourceFluxStatistics.hpp
SinglePhaseBase.hpp
SinglePhaseBaseFields.hpp
@@ -65,18 +63,6 @@ set( fluidFlowSolvers_headers
kernels/singlePhase/ThermalFluxComputeKernel.hpp
kernels/singlePhase/proppant/ProppantBaseKernels.hpp
kernels/singlePhase/proppant/ProppantFluxKernels.hpp
- kernels/singlePhase/reactive/AccumulationKernels.hpp
- kernels/singlePhase/reactive/DirichletFluxComputeKernel.hpp
- kernels/singlePhase/reactive/FluidUpdateKernel.hpp
- kernels/singlePhase/reactive/FluxComputeKernel.hpp
- kernels/singlePhase/reactive/KernelLaunchSelectors.hpp
- kernels/singlePhase/reactive/ReactionUpdateKernel.hpp
- kernels/singlePhase/reactive/ResidualNormKernel.hpp
- kernels/singlePhase/reactive/SourceFluxComputeKernel.hpp
- kernels/singlePhase/reactive/ThermalAccumulationKernels.hpp
- kernels/singlePhase/reactive/ThermalDirichletFluxComputeKernel.hpp
- kernels/singlePhase/reactive/ThermalFluxComputeKernel.hpp
- kernels/singlePhase/reactive/ThermalSourceFluxComputeKernel.hpp
kernels/compositional/AccumulationKernel.hpp
kernels/compositional/AquiferBCKernel.hpp
kernels/compositional/PPUPhaseFlux.hpp
@@ -104,7 +90,6 @@ set( fluidFlowSolvers_headers
kernels/compositional/PotGrad.hpp
kernels/compositional/PPUPhaseFlux.hpp
kernels/compositional/PropertyKernelBase.hpp
- kernels/compositional/ReactiveCompositionalMultiphaseOBLKernels.hpp
kernels/compositional/RelativePermeabilityUpdateKernel.hpp
kernels/compositional/ResidualNormKernel.hpp
kernels/compositional/SolidInternalEnergyUpdateKernel.hpp
@@ -145,14 +130,12 @@ set( fluidFlowSolvers_sources
CompositionalMultiphaseStatistics.cpp
CompositionalMultiphaseHybridFVM.cpp
ImmiscibleMultiphaseFlow.cpp
- ReactiveCompositionalMultiphaseOBL.cpp
FlowSolverBase.cpp
SinglePhaseBase.cpp
SinglePhaseStatistics.cpp
SinglePhaseFVM.cpp
SinglePhaseHybridFVM.cpp
SinglePhaseProppantBase.cpp
- SinglePhaseReactiveTransport.cpp
SourceFluxStatistics.cpp
StencilDataCollection.cpp
kernels/singlePhase/proppant/ProppantFluxKernels.cpp
@@ -174,8 +157,10 @@ file( READ "${CMAKE_CURRENT_LIST_DIR}/kernelSpecs.json" kernelSpecs )
set( kernelTemplateFileList "" )
# Keep only the templates that are unrelated to hybrid flux/dirichlet
+if( ENABLE_HPCREACT AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/../constitutive/HPCReact/CMakeLists.txt" )
list( APPEND kernelTemplateFileList
ReactiveCompositionalMultiphaseOBLKernels.cpp.template )
+endif()
foreach( kernelTemplateFile ${kernelTemplateFileList} )
get_filename_component( jsonKey ${kernelTemplateFile} NAME_WE )
@@ -188,6 +173,31 @@ foreach( kernelTemplateFile ${kernelTemplateFileList} )
list(APPEND fluidFlowSolvers_sources ${sourceFiles})
endforeach()
+# Conditionally add reactive transport files when HPCReact is available
+if( ENABLE_HPCREACT AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/../constitutive/HPCReact/CMakeLists.txt" )
+ list( APPEND fluidFlowSolvers_headers
+ ReactiveCompositionalMultiphaseOBL.hpp
+ ReactiveCompositionalMultiphaseOBLFields.hpp
+ kernels/singlePhase/reactive/AccumulationKernels.hpp
+ kernels/singlePhase/reactive/DirichletFluxComputeKernel.hpp
+ kernels/singlePhase/reactive/FluidUpdateKernel.hpp
+ kernels/singlePhase/reactive/FluxComputeKernel.hpp
+ kernels/singlePhase/reactive/KernelLaunchSelectors.hpp
+ kernels/singlePhase/reactive/ReactionUpdateKernel.hpp
+ kernels/singlePhase/reactive/ResidualNormKernel.hpp
+ kernels/singlePhase/reactive/SourceFluxComputeKernel.hpp
+ kernels/singlePhase/reactive/ThermalAccumulationKernels.hpp
+ kernels/singlePhase/reactive/ThermalDirichletFluxComputeKernel.hpp
+ kernels/singlePhase/reactive/ThermalFluxComputeKernel.hpp
+ kernels/singlePhase/reactive/ThermalSourceFluxComputeKernel.hpp
+ kernels/compositional/ReactiveCompositionalMultiphaseOBLKernels.hpp
+ SinglePhaseReactiveTransport.hpp )
+
+ list( APPEND fluidFlowSolvers_sources
+ ReactiveCompositionalMultiphaseOBL.cpp
+ SinglePhaseReactiveTransport.cpp )
+endif()
+
# TODO: The two kernels below have non-matching file names and JSON keys.
# Either fix them to follow pattern, or come up with another mechanism.
diff --git a/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseBase.cpp b/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseBase.cpp
index ec8268e0079..d71740eb5fe 100644
--- a/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseBase.cpp
+++ b/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseBase.cpp
@@ -998,12 +998,24 @@ void CompositionalMultiphaseBase::initializeFluidState( MeshLevel & mesh,
// We postpone the other constitutive models for now
// Now, we initialize and update each constitutive model one by one
-
// initialized phase volume fraction
arrayView2d< real64 const, compflow::USD_PHASE > const phaseVolFrac =
- subRegion.template getField< flow::phaseVolumeFraction >();
+ subRegion.template getField< fields::flow::phaseVolumeFraction >();
- // Initialize/update the relative permeability model using the initial phase volume fraction
+ // 4.2 Save the computed porosity into the old porosity
+ //
+ // Note:
+ // - This must be called after updatePorosityAndPermeability
+ // - This step depends on porosity
+ string const & solidName = subRegion.template getReference< string >( viewKeyStruct::solidNamesString() );
+ CoupledSolidBase const & porousMaterial = getConstitutiveModel< CoupledSolidBase >( subRegion, solidName );
+ porousMaterial.initializeState();
+
+ // 4.3 Initialize/update the relative permeability model using the initial phase volume fraction
+ // This is needed to handle relative permeability hysteresis
+ // Also, initialize the fluid model (to compute the initial total mass density, needed to compute the body force increment in
+ // coupled simulations)
+ //
// Note:
// - This must be called after updatePhaseVolumeFraction
// - This step depends on phaseVolFraction
@@ -2858,6 +2870,7 @@ void CompositionalMultiphaseBase::implicitStepComplete( real64 const & time,
CapillaryPressureBase const & capPressureMaterial =
getConstitutiveModel< CapillaryPressureBase >( subRegion, capPressName );
capPressureMaterial.saveConvergedRockState( porosity, permeability );
+ capPressureMaterial.saveConvergedPhaseVolFractionState(phaseVolFrac);
}
// Step 6: if the thermal option is on, send the converged porosity and phase volume fraction to the thermal conductivity model
diff --git a/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseBase.hpp b/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseBase.hpp
index 227055bf3ed..a03fbf63a99 100644
--- a/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseBase.hpp
+++ b/src/coreComponents/physicsSolvers/fluidFlow/CompositionalMultiphaseBase.hpp
@@ -20,6 +20,11 @@
#ifndef GEOS_PHYSICSSOLVERS_FLUIDFLOW_COMPOSITIONALMULTIPHASEBASE_HPP_
#define GEOS_PHYSICSSOLVERS_FLUIDFLOW_COMPOSITIONALMULTIPHASEBASE_HPP_
+#include "common/DataLayouts.hpp"
+#include "constitutive/fluid/multifluid/Layouts.hpp"
+#include "constitutive/relativePermeability/Layouts.hpp"
+#include "constitutive/capillaryPressure/Layouts.hpp"
+#include "fieldSpecification/FieldSpecificationManager.hpp"
#include "physicsSolvers/fluidFlow/FlowSolverBase.hpp"
#include "common/DataLayouts.hpp"
#include "constitutive/fluid/multifluid/Layouts.hpp"
diff --git a/src/coreComponents/physicsSolvers/fluidFlow/ImmiscibleMultiphaseFlow.cpp b/src/coreComponents/physicsSolvers/fluidFlow/ImmiscibleMultiphaseFlow.cpp
index a198a78cb17..5fc1155a274 100644
--- a/src/coreComponents/physicsSolvers/fluidFlow/ImmiscibleMultiphaseFlow.cpp
+++ b/src/coreComponents/physicsSolvers/fluidFlow/ImmiscibleMultiphaseFlow.cpp
@@ -17,402 +17,585 @@
* @file ImmiscibleMultiphaseFlow.cpp
*/
-#include "ImmiscibleMultiphaseFlow.hpp"
-
-#include "FlowSolverBaseFields.hpp"
-#include "physicsSolvers/fluidFlow/ImmiscibleMultiphaseFlowFields.hpp"
-#include "physicsSolvers/PhysicsSolverBaseKernels.hpp"
-#include "physicsSolvers/fluidFlow/CompositionalMultiphaseUtilities.hpp"
-#include "physicsSolvers/fluidFlow/kernels/immiscibleMultiphase/ImmiscibleMultiphaseKernels.hpp"
-#include "physicsSolvers/fluidFlow/kernels/compositional/RelativePermeabilityUpdateKernel.hpp"
-#include "physicsSolvers/fluidFlow/kernels/compositional/CapillaryPressureUpdateKernel.hpp"
-#include "constitutive/capillaryPressure/CapillaryPressureSelector.hpp"
-#include "constitutive/relativePermeability/RelativePermeabilitySelector.hpp"
-
-#include "fieldSpecification/EquilibriumInitialCondition.hpp"
-#include "fieldSpecification/SourceFluxBoundaryCondition.hpp"
-#include "physicsSolvers/fluidFlow/SourceFluxStatistics.hpp"
-#include "physicsSolvers/LogLevelsInfo.hpp"
-
-#include "constitutive/ConstitutivePassThru.hpp"
-#include "constitutive/fluid/twophaseimmisciblefluid/TwoPhaseImmiscibleFluid.hpp"
-
-#include
-
-#if defined( __INTEL_COMPILER )
-#pragma GCC optimize "O0"
-#endif
-
-namespace geos
-{
-
-using namespace dataRepository;
-using namespace constitutive;
-using namespace fields::immiscibleMultiphaseFlow;
-using namespace immiscibleMultiphaseKernels;
-
-
-ImmiscibleMultiphaseFlow::ImmiscibleMultiphaseFlow( const string & name,
- Group * const parent )
- :
- FlowSolverBase( name, parent ),
- m_numPhases( 2 ),
- m_hasCapPressure( false ),
- m_useTotalMassEquation ( 1 )
-{
- this->registerWrapper( viewKeyStruct::inputTemperatureString(), &m_inputTemperature ).
- setInputFlag( InputFlags::REQUIRED ).
- setDescription( "Temperature" );
-
- this->registerWrapper( viewKeyStruct::useTotalMassEquationString(), &m_useTotalMassEquation ).
- setSizedFromParent( 0 ).
- setInputFlag( InputFlags::OPTIONAL ).
- setApplyDefaultValue( 1 ).
- setDescription( "Flag indicating whether total mass equation is used" );
-
- this->registerWrapper( viewKeyStruct::gravityDensitySchemeString(), &m_gravityDensityScheme ).
- setSizedFromParent( 0 ).
- setInputFlag( InputFlags::OPTIONAL ).
- setApplyDefaultValue( GravityDensityScheme::ArithmeticAverage ).
- setDescription( "Scheme for density treatment in gravity" );
-
- this->registerWrapper( viewKeyStruct::solutionChangeScalingFactorString(), &m_solutionChangeScalingFactor ).
- setSizedFromParent( 0 ).
- setInputFlag( InputFlags::OPTIONAL ).
- setApplyDefaultValue( 0.5 ).
- setDescription( "Damping factor for solution change targets" );
- this->registerWrapper( viewKeyStruct::targetRelativePresChangeString(), &m_targetRelativePresChange ).
- setSizedFromParent( 0 ).
- setInputFlag( InputFlags::OPTIONAL ).
- setApplyDefaultValue( 0.2 ).
- setDescription( "Target (relative) change in pressure in a time step (expected value between 0 and 1)" );
- this->registerWrapper( viewKeyStruct::targetPhaseVolFracChangeString(), &m_targetPhaseVolFracChange ).
- setSizedFromParent( 0 ).
- setInputFlag( InputFlags::OPTIONAL ).
- setApplyDefaultValue( 0.2 ).
- setDescription( "Target (absolute) change in the phase volume fraction within a single time step." );
-}
-
-void ImmiscibleMultiphaseFlow::postInputInitialization()
-{
- FlowSolverBase::postInputInitialization();
-}
-
-void ImmiscibleMultiphaseFlow::registerDataOnMesh( Group & meshBodies )
-{
- FlowSolverBase::registerDataOnMesh( meshBodies );
-
- // 0. Find a "reference" fluid model name (at this point, models are already attached to subregions)
- forDiscretizationOnMeshTargets( meshBodies, [&]( string const &,
- MeshLevel & mesh,
- string_array const & regionNames )
- {
- mesh.getElemManager().forElementSubRegions( regionNames,
- [&]( localIndex const,
- ElementSubRegionBase & subRegion )
- {
- // If at least one region has a capillary pressure model, consider it enabled for all
- string const capPresName = getConstitutiveName< CapillaryPressureBase >( subRegion );
- if( !capPresName.empty() )
- {
- m_hasCapPressure = true;
- }
- } );
- } );
-
- m_numDofPerCell = m_numPhases;
-
- // 2. Register and resize all fields as necessary
- forDiscretizationOnMeshTargets( meshBodies, [&]( string const &,
- MeshLevel & mesh,
- string_array const & regionNames )
- {
- mesh.getElemManager().forElementSubRegions( regionNames,
- [&]( localIndex const,
- ElementSubRegionBase & subRegion )
- {
- if( m_hasCapPressure )
+ #include "ImmiscibleMultiphaseFlow.hpp"
+
+ #include "FlowSolverBaseFields.hpp"
+ #include "physicsSolvers/fluidFlow/ImmiscibleMultiphaseFlowFields.hpp"
+ #include "physicsSolvers/PhysicsSolverBaseKernels.hpp"
+ #include "physicsSolvers/fluidFlow/CompositionalMultiphaseUtilities.hpp"
+ #include "physicsSolvers/fluidFlow/kernels/immiscibleMultiphase/ImmiscibleMultiphaseKernels.hpp"
+ #include "physicsSolvers/fluidFlow/kernels/immiscibleMultiphase/CapillaryPressureUpdateKernel.hpp"
+ #include "physicsSolvers/fluidFlow/kernels/compositional/ThermalAccumulationKernel.hpp"
+ #include "physicsSolvers/fluidFlow/kernels/compositional/RelativePermeabilityUpdateKernel.hpp"
+ #include "constitutive/ConstitutiveManager.hpp"
+ #include "constitutive/capillaryPressure/CapillaryPressureFields.hpp"
+ #include "constitutive/capillaryPressure/CapillaryPressureSelector.hpp"
+ #include "constitutive/relativePermeability/RelativePermeabilitySelector.hpp"
+
+ #include "fieldSpecification/EquilibriumInitialCondition.hpp"
+ #include "fieldSpecification/SourceFluxBoundaryCondition.hpp"
+ #include "physicsSolvers/fluidFlow/SourceFluxStatistics.hpp"
+ #include "physicsSolvers/LogLevelsInfo.hpp"
+
+ #include "constitutive/ConstitutivePassThru.hpp"
+ #include "constitutive/fluid/twophaseimmisciblefluid/TwoPhaseImmiscibleFluid.hpp"
+
+ #include
+
+ #if defined( __INTEL_COMPILER )
+ #pragma GCC optimize "O0"
+ #endif
+
+ namespace geos
+ {
+
+ using namespace dataRepository;
+ using namespace constitutive;
+ using namespace fields::immiscibleMultiphaseFlow;
+ using namespace immiscibleMultiphaseKernels;
+
+
+ ImmiscibleMultiphaseFlow::ImmiscibleMultiphaseFlow( const string & name,
+ Group * const parent )
+ :
+ FlowSolverBase( name, parent ),
+ m_numPhases( 2 ),
+ m_hasCapPressure( false ),
+ m_useTotalMassEquation ( 1 )
+ {
+ this->registerWrapper( viewKeyStruct::inputTemperatureString(), &m_inputTemperature ).
+ setInputFlag( InputFlags::REQUIRED ).
+ setDescription( "Temperature" );
+
+ this->registerWrapper( viewKeyStruct::useTotalMassEquationString(), &m_useTotalMassEquation ).
+ setSizedFromParent( 0 ).
+ setInputFlag( InputFlags::OPTIONAL ).
+ setApplyDefaultValue( 1 ).
+ setDescription( "Flag indicating whether total mass equation is used" );
+
+ this->registerWrapper( viewKeyStruct::gravityDensitySchemeString(), &m_gravityDensityScheme ).
+ setSizedFromParent( 0 ).
+ setInputFlag( InputFlags::OPTIONAL ).
+ setApplyDefaultValue( GravityDensityScheme::ArithmeticAverage ).
+ setDescription( "Scheme for density treatment in gravity" );
+
+ this->registerWrapper( viewKeyStruct::solutionChangeScalingFactorString(), &m_solutionChangeScalingFactor ).
+ setSizedFromParent( 0 ).
+ setInputFlag( InputFlags::OPTIONAL ).
+ setApplyDefaultValue( 0.5 ).
+ setDescription( "Damping factor for solution change targets" );
+ this->registerWrapper( viewKeyStruct::targetRelativePresChangeString(), &m_targetRelativePresChange ).
+ setSizedFromParent( 0 ).
+ setInputFlag( InputFlags::OPTIONAL ).
+ setApplyDefaultValue( 0.2 ).
+ setDescription( "Target (relative) change in pressure in a time step (expected value between 0 and 1)" );
+ this->registerWrapper( viewKeyStruct::targetPhaseVolFracChangeString(), &m_targetPhaseVolFracChange ).
+ setSizedFromParent( 0 ).
+ setInputFlag( InputFlags::OPTIONAL ).
+ setApplyDefaultValue( 0.2 ).
+ setDescription( "Target (absolute) change in phase volume fraction in a time step" );
+
+ this->registerWrapper( viewKeyStruct::interfaceFaceSetNamesString(),
+ &m_interfaceFaceSetNames ).
+ setInputFlag( InputFlags::OPTIONAL ).
+ setDescription( "Names of the interface face sets" );
+ }
+
+ void ImmiscibleMultiphaseFlow::postInputInitialization()
+ {
+ FlowSolverBase::postInputInitialization();
+ }
+
+ void ImmiscibleMultiphaseFlow::registerDataOnMesh( Group & meshBodies )
+ {
+ FlowSolverBase::registerDataOnMesh( meshBodies );
+
+ // 0. Find a "reference" fluid model name (at this point, models are already attached to subregions)
+ forDiscretizationOnMeshTargets( meshBodies, [&]( string const &,
+ MeshLevel & mesh,
+ string_array const & regionNames )
+ {
+ mesh.getElemManager().forElementSubRegions( regionNames,
+ [&]( localIndex const,
+ ElementSubRegionBase & subRegion )
+ {
+ // If at least one region has a capillary pressure model, consider it enabled for all
+ string const capPresName = getConstitutiveName< CapillaryPressureBase >( subRegion );
+ if( !capPresName.empty() )
+ {
+ m_hasCapPressure = true;
+ }
+ } );
+ } );
+
+ m_numDofPerCell = m_numPhases;
+
+ // 2. Register and resize all fields as necessary
+ forDiscretizationOnMeshTargets( meshBodies, [&]( string const &,
+ MeshLevel & mesh,
+ string_array const & regionNames )
+ {
+ mesh.getElemManager().forElementSubRegions( regionNames,
+ [&]( localIndex const,
+ ElementSubRegionBase & subRegion )
+ {
+ if( m_hasCapPressure )
+ {
+ subRegion.registerWrapper< string >( viewKeyStruct::capPressureNamesString() ).
+ setPlotLevel( PlotLevel::NOPLOT ).
+ setRestartFlags( RestartFlags::NO_WRITE ).
+ setSizedFromParent( 0 ).
+ setDescription( "Name of the capillary pressure constitutive model to use" ).
+ reference();
+
+ string & capPresName = subRegion.getReference< string >( viewKeyStruct::capPressureNamesString() );
+ capPresName = getConstitutiveName< CapillaryPressureBase >( subRegion );
+ GEOS_THROW_IF( capPresName.empty(),
+ GEOS_FMT( "{}: Capillary pressure model not found on subregion {}",
+ getDataContext(), subRegion.getDataContext() ),
+ InputError );
+ }
+
+ // The resizing of the arrays needs to happen here, before the call to initializePreSubGroups,
+ // to make sure that the dimensions are properly set before the timeHistoryOutput starts its initialization.
+ subRegion.registerField< phaseVolumeFraction >( getName() ).
+ reference().resizeDimension< 1 >( m_numPhases );
+
+ subRegion.registerField< phaseVolumeFraction_n >( getName() ).
+ reference().resizeDimension< 1 >( m_numPhases );
+
+ subRegion.registerField< bcPhaseVolumeFraction >( getName() ).
+ reference().resizeDimension< 1 >( m_numPhases );
+
+ subRegion.registerField< phaseMass >( getName() ).
+ reference().resizeDimension< 1 >( m_numPhases );
+
+ subRegion.registerField< phaseMass_n >( getName() ).
+ reference().resizeDimension< 1 >( m_numPhases );
+
+ subRegion.registerField< phaseMobility >( getName() ).
+ reference().resizeDimension< 1 >( m_numPhases );
+
+ subRegion.registerField< dPhaseMobility >( getName() ).
+ reference().resizeDimension< 1, 2 >( m_numPhases, m_numPhases ); // dP, dS
+
+ } );
+
+ } );
+ }
+
+ void ImmiscibleMultiphaseFlow::setConstitutiveNames( ElementSubRegionBase & subRegion ) const
+ {
+ setConstitutiveName< TwoPhaseImmiscibleFluid >( subRegion, viewKeyStruct::fluidNamesString(), "two phase immiscible fluid" );
+
+ setConstitutiveName< RelativePermeabilityBase >( subRegion, viewKeyStruct::relPermNamesString(), "relative permeability" );
+ }
+
+ void ImmiscibleMultiphaseFlow::initializePreSubGroups()
+ {
+ m_linearSolverParameters.get().mgr.strategy = LinearSolverParameters::MGR::StrategyType::immiscibleMultiphaseFVM;
+
+ FlowSolverBase::initializePreSubGroups();
+
+ DomainPartition & domain = this->getGroupByPath< DomainPartition >( "/Problem/domain" );
+
+ forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const &,
+ MeshLevel & mesh,
+ string_array const & regionNames )
+ {
+ mesh.getElemManager().forElementSubRegions( regionNames,
+ [&]( localIndex const,
+ ElementSubRegionBase & subRegion )
+ {
+ arrayView1d< real64 > const temp = subRegion.getField< fields::flow::temperature >();
+ temp.setValues< parallelHostPolicy >( m_inputTemperature );
+ } );
+ } );
+
+ // ***** Create FaceElements *****
+ forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const &,
+ MeshLevel & meshLevel,
+ string_array const & GEOS_UNUSED_PARAM( regionNames ))
+ {
+
+ FaceManager const & faceManager = meshLevel.getFaceManager();
+ Group const & faceSetGroup = faceManager.sets();
+ ElementRegionManager & elemManager = meshLevel.getElemManager();
+ m_interfaceConstitutivePairs.resize( m_interfaceFaceSetNames.size() );
+
+ // this is the FaceElement Level
+ for( size_t surfaceRegionIndex=0; surfaceRegionIndex < m_interfaceFaceSetNames.size(); ++surfaceRegionIndex )
+ {
+ string const & faceSetName = m_interfaceFaceSetNames[surfaceRegionIndex];
+ SortedArrayView< localIndex const > const & faceSet = faceSetGroup.getReference< SortedArray< localIndex > >( faceSetName );
+ SurfaceElementRegion & faceRegion = elemManager.getRegion< SurfaceElementRegion >( faceSetName );
+
+ for( localIndex const faceIndex : faceSet )
+ {
+ localIndex const faceIndices[2] = { faceIndex, faceIndex };
+ faceRegion.addToSurfaceMesh( &faceManager, faceIndices );
+ }
+
+ FaceElementSubRegion const & faceSubRegion = faceRegion.getUniqueSubRegion< FaceElementSubRegion >();
+ FixedToManyElementRelation const & faceElementsToCells = faceSubRegion.getToCellRelation();
+
+ // Precompute numRegions once (it's constant for all face elements)
+ localIndex const numRegions = elemManager.numRegions();
+ constexpr int MAX_REASONABLE_REGION_INDEX = 100000;
+
+ std::function< std::tuple< CellElementSubRegion *, CellElementSubRegion * >(localIndex) > getSubregions = [&]( localIndex surfaceSubRegionIndex ) -> std::tuple< CellElementSubRegion *,
+ CellElementSubRegion * >
{
- subRegion.registerWrapper< string >( viewKeyStruct::capPressureNamesString() ).
- setPlotLevel( PlotLevel::NOPLOT ).
- setRestartFlags( RestartFlags::NO_WRITE ).
- setSizedFromParent( 0 ).
- setDescription( "Name of the capillary pressure constitutive model to use" ).
- reference();
-
- string & capPresName = subRegion.getReference< string >( viewKeyStruct::capPressureNamesString() );
- capPresName = getConstitutiveName< CapillaryPressureBase >( subRegion );
- GEOS_THROW_IF( capPresName.empty(),
- GEOS_FMT( "{}: Capillary pressure model not found on subregion {}",
- getDataContext(), subRegion.getDataContext() ),
- InputError );
- }
-
- // The resizing of the arrays needs to happen here, before the call to initializePreSubGroups,
- // to make sure that the dimensions are properly set before the timeHistoryOutput starts its initialization.
- subRegion.registerField< phaseVolumeFraction >( getName() ).
- reference().resizeDimension< 1 >( m_numPhases );
-
- subRegion.registerField< phaseVolumeFraction_n >( getName() ).
- reference().resizeDimension< 1 >( m_numPhases );
-
- subRegion.registerField< bcPhaseVolumeFraction >( getName() ).
- reference().resizeDimension< 1 >( m_numPhases );
-
- subRegion.registerField< phaseMass >( getName() ).
- reference().resizeDimension< 1 >( m_numPhases );
-
- subRegion.registerField< phaseMass_n >( getName() ).
- reference().resizeDimension< 1 >( m_numPhases );
- subRegion.registerField< phaseMobility >( getName() ).
- reference().resizeDimension< 1 >( m_numPhases );
-
- subRegion.registerField< dPhaseMobility >( getName() ).
- reference().resizeDimension< 1, 2 >( m_numPhases, m_numPhases ); // dP, dS
-
- } );
-
- } );
-}
-
-void ImmiscibleMultiphaseFlow::setConstitutiveNames( ElementSubRegionBase & subRegion ) const
-{
- setConstitutiveName< TwoPhaseImmiscibleFluid >( subRegion, viewKeyStruct::fluidNamesString(), "two phase immiscible fluid" );
-
- setConstitutiveName< RelativePermeabilityBase >( subRegion, viewKeyStruct::relPermNamesString(), "relative permeability" );
-}
-
-void ImmiscibleMultiphaseFlow::initializePreSubGroups()
-{
- m_linearSolverParameters.get().mgr.strategy = LinearSolverParameters::MGR::StrategyType::immiscibleMultiphaseFVM;
-
- FlowSolverBase::initializePreSubGroups();
-
- DomainPartition & domain = this->getGroupByPath< DomainPartition >( "/Problem/domain" );
-
- forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const &,
- MeshLevel & mesh,
- string_array const & regionNames )
- {
- mesh.getElemManager().forElementSubRegions( regionNames,
- [&]( localIndex const,
- ElementSubRegionBase & subRegion )
- {
- arrayView1d< real64 > const temp = subRegion.getField< fields::flow::temperature >();
- temp.setValues< parallelHostPolicy >( m_inputTemperature );
- } );
- } );
-}
-
-
-void ImmiscibleMultiphaseFlow::updateFluidModel( ObjectManagerBase & dataGroup ) const
-{
- GEOS_MARK_FUNCTION;
+ int regionIdx0 = faceElementsToCells.m_toElementRegion[surfaceSubRegionIndex][0];
+ int regionIdx1 = faceElementsToCells.m_toElementRegion[surfaceSubRegionIndex][1];
+ int subRegionIdx0 = faceElementsToCells.m_toElementSubRegion[surfaceSubRegionIndex][0];
+ int subRegionIdx1 = faceElementsToCells.m_toElementSubRegion[surfaceSubRegionIndex][1];
- arrayView1d< real64 const > const pres = dataGroup.getField< fields::flow::pressure >();
-
- TwoPhaseImmiscibleFluid & fluid = getConstitutiveModel< TwoPhaseImmiscibleFluid >( dataGroup, dataGroup.getReference< string >( viewKeyStruct::fluidNamesString() ) );
-
- constitutiveUpdatePassThru( fluid, [&] ( auto & castedFluid )
- {
- using FluidType = TYPEOFREF( castedFluid );
- typename FluidType::KernelWrapper fluidWrapper = castedFluid.createKernelWrapper();
-
- FluidUpdateKernel::launch< parallelDevicePolicy<> >( dataGroup.size(), fluidWrapper, pres );
- } );
-}
-
-
-void ImmiscibleMultiphaseFlow::updateRelPermModel( ObjectManagerBase & dataGroup ) const
-{
- GEOS_MARK_FUNCTION;
-
-
- GEOS_UNUSED_VAR( dataGroup );
-
- arrayView2d< real64 const, immiscibleFlow::USD_PHASE > const phaseVolFrac =
- dataGroup.getField< fields::immiscibleMultiphaseFlow::phaseVolumeFraction >();
-
- string const & relPermName = dataGroup.getReference< string >( viewKeyStruct::relPermNamesString() );
- RelativePermeabilityBase & relPerm = getConstitutiveModel< RelativePermeabilityBase >( dataGroup, relPermName );
+ // Fast validation checks (ordered from cheapest to most expensive)
+ if( regionIdx0 < 0 || regionIdx1 < 0 ||
+ subRegionIdx0 < 0 || subRegionIdx1 < 0 )
+ {
+ return std::make_tuple( nullptr, nullptr );
+ }
+
+ if( regionIdx0 > MAX_REASONABLE_REGION_INDEX || regionIdx1 > MAX_REASONABLE_REGION_INDEX )
+ {
+ return std::make_tuple( nullptr, nullptr );
+ }
+
+ if( static_cast< localIndex >( regionIdx0 ) >= numRegions ||
+ static_cast< localIndex >( regionIdx1 ) >= numRegions )
+ {
+ return std::make_tuple( nullptr, nullptr );
+ }
- constitutive::constitutiveUpdatePassThru( relPerm, [&] ( auto & castedRelPerm )
- {
- typename TYPEOFREF( castedRelPerm ) ::KernelWrapper relPermWrapper = castedRelPerm.createKernelWrapper();
+ // Try to get regions - they might not exist on this MPI rank even if index is in range
+ ElementRegionBase * region0BasePtr = nullptr;
+ ElementRegionBase * region1BasePtr = nullptr;
+
+ try
+ {
+ region0BasePtr = &elemManager.getRegion< ElementRegionBase >( regionIdx0 );
+ region1BasePtr = &elemManager.getRegion< ElementRegionBase >( regionIdx1 );
+ }
+ catch( std::exception const & )
+ {
+ return std::make_tuple( nullptr, nullptr );
+ }
+
+ // Check if they are CellElementRegion (interface conditions only apply to cell-to-cell interfaces)
+ CellElementRegion * cellRegion0 = dynamic_cast< CellElementRegion * >( region0BasePtr );
+ CellElementRegion * cellRegion1 = dynamic_cast< CellElementRegion * >( region1BasePtr );
+
+ if( cellRegion0 == nullptr || cellRegion1 == nullptr )
+ {
+ return std::make_tuple( nullptr, nullptr );
+ }
- isothermalCompositionalMultiphaseBaseKernels::
- RelativePermeabilityUpdateKernel::
- launch< parallelDevicePolicy<> >( dataGroup.size(),
- relPermWrapper,
- phaseVolFrac );
- } );
-}
+ // Validate subregion indices before accessing
+ if( static_cast< localIndex >( subRegionIdx0 ) >= cellRegion0->numSubRegions() ||
+ static_cast< localIndex >( subRegionIdx1 ) >= cellRegion1->numSubRegions() )
+ {
+ return std::make_tuple( nullptr, nullptr );
+ }
-void ImmiscibleMultiphaseFlow::updateCapPressureModel( ObjectManagerBase & dataGroup ) const
+ CellElementSubRegion * subRegion0 = &cellRegion0->getSubRegion< CellElementSubRegion >( subRegionIdx0 );
+ CellElementSubRegion * subRegion1 = &cellRegion1->getSubRegion< CellElementSubRegion >( subRegionIdx1 );
+ return std::make_tuple( subRegion0, subRegion1 );
+ };
+
+ // std::tuple< CellElementSubRegion *, CellElementSubRegion * > subRegionPair = getSubregions( surfaceRegionIndex );
+ // CellElementSubRegion * subRegion0 = std::get< 0 >( subRegionPair );
+ // CellElementSubRegion * subRegion1 = std::get< 1 >( subRegionPair );
+
+ // // get constitutives by type and name: relPerms, capPressures, Fluids (three pointers)
+ // std::string & relPermName0 = subRegion0->getReference< std::string >( viewKeyStruct::relPermNamesString());
+ // std::string & relPermName1 = subRegion1->getReference< std::string >( viewKeyStruct::relPermNamesString());
+ // RelativePermeabilityBase * relPerm0 = &getConstitutiveModel< RelativePermeabilityBase >( *subRegion0, relPermName0 );
+ // RelativePermeabilityBase * relPerm1 = &getConstitutiveModel< RelativePermeabilityBase >( *subRegion1, relPermName1 );
+
+ // std::string & cappresName0 = subRegion0->getReference< std::string >( viewKeyStruct::capPressureNamesString());
+ // std::string & cappresName1 = subRegion1->getReference< std::string >( viewKeyStruct::capPressureNamesString());
+ // CapillaryPressureBase * capPressure0 = &getConstitutiveModel< CapillaryPressureBase >( *subRegion0, cappresName0 );
+ // CapillaryPressureBase * capPressure1 = &getConstitutiveModel< CapillaryPressureBase >( *subRegion1, cappresName1 );
+
+ // std::string & fluidName0 = subRegion0->getReference< std::string >( viewKeyStruct::fluidNamesString() );
+ // std::string & fluidName1 = subRegion1->getReference< std::string >( viewKeyStruct::fluidNamesString() );
+
+ // TwoPhaseImmiscibleFluid * fluid0 = &getConstitutiveModel< TwoPhaseImmiscibleFluid >( *subRegion0, fluidName0 );
+ // TwoPhaseImmiscibleFluid * fluid1 = &getConstitutiveModel< TwoPhaseImmiscibleFluid >( *subRegion1, fluidName1 );
+
+ // m_interfaceConstitutivePairs[surfaceRegionIndex][0] = std::make_tuple( relPerm0, capPressure0, fluid0 );
+ // m_interfaceConstitutivePairs[surfaceRegionIndex][1] = std::make_tuple( relPerm1, capPressure1, fluid1 );
+ // Find a representative face element that connects two CellElementRegion objects
+// (not SurfaceElementRegion, which we don't handle for interface conditions)
+CellElementSubRegion * subRegion0 = nullptr;
+CellElementSubRegion * subRegion1 = nullptr;
+bool foundValidFei = false;
+
+// Single loop to find a valid face element (avoids redundant scanning)
+for( localIndex i = 0; i < faceElementsToCells.size(); ++i )
{
- GEOS_MARK_FUNCTION;
-
- if( m_hasCapPressure )
+ // Quick check: must have two adjacent cells with non-negative region indices
+ if( faceElementsToCells.m_toElementRegion[i].size() >= 2 &&
+ faceElementsToCells.m_toElementRegion[i][0] >= 0 &&
+ faceElementsToCells.m_toElementRegion[i][1] >= 0 )
{
- arrayView2d< real64 const, immiscibleFlow::USD_PHASE > const phaseVolFrac =
- dataGroup.getField< fields::immiscibleMultiphaseFlow::phaseVolumeFraction >();
-
- string const & cappresName = dataGroup.getReference< string >( viewKeyStruct::capPressureNamesString() );
- CapillaryPressureBase & capPressure = getConstitutiveModel< CapillaryPressureBase >( dataGroup, cappresName );
-
- constitutive::constitutiveUpdatePassThru( capPressure, [&] ( auto & castedCapPres )
+ std::tuple< CellElementSubRegion *, CellElementSubRegion * > subRegionPair = getSubregions( i );
+ CellElementSubRegion * testSubRegion0 = std::get< 0 >( subRegionPair );
+ CellElementSubRegion * testSubRegion1 = std::get< 1 >( subRegionPair );
+
+ if( testSubRegion0 != nullptr && testSubRegion1 != nullptr )
{
- typename TYPEOFREF( castedCapPres ) ::KernelWrapper capPresWrapper = castedCapPres.createKernelWrapper();
-
- isothermalCompositionalMultiphaseBaseKernels::
- CapillaryPressureUpdateKernel::
- launch< parallelDevicePolicy<> >( dataGroup.size(),
- capPresWrapper,
- phaseVolFrac );
- } );
+ subRegion0 = testSubRegion0;
+ subRegion1 = testSubRegion1;
+ foundValidFei = true;
+ break;
+ }
}
}
-
-void ImmiscibleMultiphaseFlow::updateFluidState( ElementSubRegionBase & subRegion ) const
-{
- GEOS_MARK_FUNCTION;
-
- updateFluidModel( subRegion );
- updateVolumeConstraint( subRegion );
- updatePhaseMass( subRegion );
- updateRelPermModel( subRegion );
- updatePhaseMobility( subRegion );
- updateCapPressureModel( subRegion );
-}
-
-
-void ImmiscibleMultiphaseFlow::updatePhaseMass( ElementSubRegionBase & subRegion ) const
+// If no valid face element connecting two CellElementRegion objects, skip this surface region
+if( !foundValidFei )
{
- GEOS_MARK_FUNCTION;
-
- string const & solidName = subRegion.getReference< string >( viewKeyStruct::solidNamesString() );
- string const & fluidName = subRegion.getReference< string >( viewKeyStruct::fluidNamesString() );
-
- TwoPhaseImmiscibleFluid const & fluid = getConstitutiveModel< TwoPhaseImmiscibleFluid >( subRegion, fluidName );
- CoupledSolidBase const & solid = getConstitutiveModel< CoupledSolidBase >( subRegion, solidName );
-
- arrayView1d< real64 const > const volume = subRegion.getElementVolume();
- arrayView2d< real64 const > const porosity = solid.getPorosity();
-
- arrayView2d< real64 const, immiscibleFlow::USD_PHASE > const phaseVolFrac= subRegion.getField< fields::immiscibleMultiphaseFlow::phaseVolumeFraction >();
- arrayView3d< real64 const, multifluid::USD_PHASE > phaseDens = fluid.phaseDensity();
- arrayView2d< real64, immiscibleFlow::USD_PHASE > const phaseMass = subRegion.getField< fields::immiscibleMultiphaseFlow::phaseMass >();
-
- // Might be needed for geomechanics????? if so, need to change the accumulation as well?
- //arrayView1d< real64 > const deltaVolume = subRegion.getField< fields::flow::deltaVolume >();
-
- forAll< parallelDevicePolicy<> >( subRegion.size(), [=] GEOS_HOST_DEVICE ( localIndex const ei )
- {
- real64 const poreVolume = volume[ei] * porosity[ei][0];
- for( integer ip = 0; ip < 2; ++ip )
- {
- phaseMass[ei][ip] = poreVolume * phaseVolFrac[ei][ip] * phaseDens[ei][0][ip];
- }
- } );
+ continue;
}
-
-void ImmiscibleMultiphaseFlow::updatePhaseMobility( ObjectManagerBase & dataGroup ) const
-{
- GEOS_MARK_FUNCTION;
-
- // note that the phase mobility computed here also includes phase density
- string const & fluidName = dataGroup.getReference< string >( viewKeyStruct::fluidNamesString() );
- TwoPhaseImmiscibleFluid const & fluid = getConstitutiveModel< TwoPhaseImmiscibleFluid >( dataGroup, fluidName );
-
- string const & relpermName = dataGroup.getReference< string >( viewKeyStruct::relPermNamesString() );
- RelativePermeabilityBase const & relperm = getConstitutiveModel< RelativePermeabilityBase >( dataGroup, relpermName );
-
- immiscibleMultiphaseKernels::
- PhaseMobilityKernelFactory::
- createAndLaunch< parallelDevicePolicy<> >( m_numPhases,
- dataGroup,
- fluid,
- relperm );
-}
-
-void ImmiscibleMultiphaseFlow::initializeFluidState( MeshLevel & mesh,
- string_array const & regionNames )
-{
- GEOS_MARK_FUNCTION;
-
- mesh.getElemManager().forElementSubRegions( regionNames,
- [&]( localIndex const,
- ElementSubRegionBase & subRegion )
- {
- // 2. Assume global component fractions have been prescribed.
- // Initialize constitutive state to get fluid density.
- updateFluidModel( subRegion );
-
- } );
-
- // for some reason CUDA does not want the host_device lambda to be defined inside the generic lambda
- // I need the exact type of the subRegion for updateSolidflowProperties to work well.
- mesh.getElemManager().forElementSubRegions< CellElementSubRegion,
- SurfaceElementSubRegion >( regionNames, [&]( localIndex const,
- auto & subRegion )
- {
- // 4. Initialize/update dependent state quantities
-
- // 4.1 Update the constitutive models that only depend on
- // - the primary variables
- // - the fluid constitutive quantities (as they have already been updated)
- // We postpone the other constitutive models for now
- // In addition, to avoid multiplying permeability/porosity bay netToGross in the assembly kernel, we do it once and for all here
- arrayView1d< real64 const > const netToGross = subRegion.template getField< fields::flow::netToGross >();
- CoupledSolidBase const & porousSolid =
- getConstitutiveModel< CoupledSolidBase >( subRegion, subRegion.template getReference< string >( viewKeyStruct::solidNamesString() ) );
- PermeabilityBase const & permeabilityModel =
- getConstitutiveModel< PermeabilityBase >( subRegion, subRegion.template getReference< string >( viewKeyStruct::permeabilityNamesString() ) );
- permeabilityModel.scaleHorizontalPermeability( netToGross );
- porousSolid.scaleReferencePorosity( netToGross );
- saveConvergedState( subRegion ); // necessary for a meaningful porosity update in sequential schemes
- updatePorosityAndPermeability( subRegion );
-
- // Now, we initialize and update each constitutive model one by one
-
- // 4.2 Save the computed porosity into the old porosity
- //
- // Note:
- // - This must be called after updatePorosityAndPermeability
- // - This step depends on porosity
- string const & solidName = subRegion.template getReference< string >( viewKeyStruct::solidNamesString() );
- CoupledSolidBase const & porousMaterial = getConstitutiveModel< CoupledSolidBase >( subRegion, solidName );
- porousMaterial.initializeState();
-
- // 4.3 Initialize/update the relative permeability model using the initial phase volume fraction
- // This is needed to handle relative permeability hysteresis
- // Also, initialize the fluid model
- //
- // Note:
- // - This must be called after updateVolumeConstraint
- // - This step depends on phaseVolFraction
-
- // initialized phase volume fraction
- arrayView2d< real64 const, immiscibleFlow::USD_PHASE > const phaseVolFrac =
- subRegion.template getField< fields::immiscibleMultiphaseFlow::phaseVolumeFraction >();
-
- string const & relpermName = subRegion.template getReference< string >( viewKeyStruct::relPermNamesString() );
- RelativePermeabilityBase & relPermMaterial =
- getConstitutiveModel< RelativePermeabilityBase >( subRegion, relpermName );
- relPermMaterial.saveConvergedPhaseVolFractionState( phaseVolFrac ); // this needs to happen before calling updateRelPermModel
- updateRelPermModel( subRegion );
- relPermMaterial.saveConvergedState(); // this needs to happen after calling updateRelPermModel
-
- // 4.4 Then, we initialize/update the capillary pressure model
- //
- // Note:
- // - This must be called after updatePorosityAndPermeability
- // - This step depends on porosity and permeability
- if( m_hasCapPressure )
- {
+// get constitutives by type and name: relPerms, capPressures, Fluids (three pointers)
+std::string & relPermName0 = subRegion0->getReference< std::string >( viewKeyStruct::relPermNamesString());
+std::string & relPermName1 = subRegion1->getReference< std::string >( viewKeyStruct::relPermNamesString());
+RelativePermeabilityBase * relPerm0 = &getConstitutiveModel< RelativePermeabilityBase >( *subRegion0, relPermName0 );
+RelativePermeabilityBase * relPerm1 = &getConstitutiveModel< RelativePermeabilityBase >( *subRegion1, relPermName1 );
+
+std::string & cappresName0 = subRegion0->getReference< std::string >( viewKeyStruct::capPressureNamesString());
+std::string & cappresName1 = subRegion1->getReference< std::string >( viewKeyStruct::capPressureNamesString());
+CapillaryPressureBase * capPressure0 = &getConstitutiveModel< CapillaryPressureBase >( *subRegion0, cappresName0 );
+CapillaryPressureBase * capPressure1 = &getConstitutiveModel< CapillaryPressureBase >( *subRegion1, cappresName1 );
+
+std::string & fluidName0 = subRegion0->getReference< std::string >( viewKeyStruct::fluidNamesString() );
+std::string & fluidName1 = subRegion1->getReference< std::string >( viewKeyStruct::fluidNamesString() );
+
+TwoPhaseImmiscibleFluid * fluid0 = &getConstitutiveModel< TwoPhaseImmiscibleFluid >( *subRegion0, fluidName0 );
+TwoPhaseImmiscibleFluid * fluid1 = &getConstitutiveModel< TwoPhaseImmiscibleFluid >( *subRegion1, fluidName1 );
+
+m_interfaceConstitutivePairs[surfaceRegionIndex][0] = std::make_tuple( relPerm0, capPressure0, fluid0 );
+m_interfaceConstitutivePairs[surfaceRegionIndex][1] = std::make_tuple( relPerm1, capPressure1, fluid1 );
+
+ }
+ } );
+
+ }
+
+
+ void ImmiscibleMultiphaseFlow::updateFluidModel( ObjectManagerBase & dataGroup ) const
+ {
+ GEOS_MARK_FUNCTION;
+
+ arrayView1d< real64 const > const pres = dataGroup.getField< fields::flow::pressure >();
+
+ TwoPhaseImmiscibleFluid & fluid = getConstitutiveModel< TwoPhaseImmiscibleFluid >( dataGroup, dataGroup.getReference< string >( viewKeyStruct::fluidNamesString() ) );
+
+ constitutiveUpdatePassThru( fluid, [&] ( auto & castedFluid )
+ {
+ using FluidType = TYPEOFREF( castedFluid );
+ typename FluidType::KernelWrapper fluidWrapper = castedFluid.createKernelWrapper();
+
+ FluidUpdateKernel::launch< parallelDevicePolicy<> >( dataGroup.size(), fluidWrapper, pres );
+ } );
+ }
+
+
+ void ImmiscibleMultiphaseFlow::updateRelPermModel( ObjectManagerBase & dataGroup ) const
+ {
+ GEOS_MARK_FUNCTION;
+
+
+ GEOS_UNUSED_VAR( dataGroup );
+
+ arrayView2d< real64 const, immiscibleFlow::USD_PHASE > const phaseVolFrac =
+ dataGroup.getField< fields::immiscibleMultiphaseFlow::phaseVolumeFraction >();
+
+ string const & relPermName = dataGroup.getReference< string >( viewKeyStruct::relPermNamesString() );
+ RelativePermeabilityBase & relPerm = getConstitutiveModel< RelativePermeabilityBase >( dataGroup, relPermName );
+
+ constitutive::constitutiveUpdatePassThru( relPerm, [&] ( auto & castedRelPerm )
+ {
+ typename TYPEOFREF( castedRelPerm ) ::KernelWrapper relPermWrapper = castedRelPerm.createKernelWrapper();
+
+ isothermalCompositionalMultiphaseBaseKernels::
+ RelativePermeabilityUpdateKernel::
+ launch< parallelDevicePolicy<> >( dataGroup.size(),
+ relPermWrapper,
+ phaseVolFrac );
+ } );
+ }
+
+ void ImmiscibleMultiphaseFlow::updateCapPressureModel( ObjectManagerBase & dataGroup ) const
+ {
+ GEOS_MARK_FUNCTION;
+
+ if( m_hasCapPressure )
+ {
+ arrayView2d< real64 const, immiscibleFlow::USD_PHASE > const phaseVolFrac =
+ dataGroup.getField< fields::immiscibleMultiphaseFlow::phaseVolumeFraction >();
+
+ string const & cappresName = dataGroup.getReference< string >( viewKeyStruct::capPressureNamesString() );
+ CapillaryPressureBase & capPressure = getConstitutiveModel< CapillaryPressureBase >( dataGroup, cappresName );
+
+ constitutive::constitutiveUpdatePassThru( capPressure, [&] ( auto & castedCapPres )
+ {
+ typename TYPEOFREF( castedCapPres ) ::KernelWrapper capPresWrapper = castedCapPres.createKernelWrapper();
+
+ // isothermalCompositionalMultiphaseBaseKernels::
+ immiscibleMultiphaseKernels::
+ CapillaryPressureUpdateKernel::
+ launch< parallelDevicePolicy<> >( dataGroup.size(),
+ capPresWrapper,
+ phaseVolFrac );
+ } );
+ }
+ }
+
+
+ void ImmiscibleMultiphaseFlow::updateFluidState( ElementSubRegionBase & subRegion ) const
+ {
+ GEOS_MARK_FUNCTION;
+
+ updateFluidModel( subRegion );
+ updateVolumeConstraint( subRegion );
+ updatePhaseMass( subRegion );
+ updateRelPermModel( subRegion );
+ updatePhaseMobility( subRegion );
+ updateCapPressureModel( subRegion );
+ }
+
+
+ void ImmiscibleMultiphaseFlow::updatePhaseMass( ElementSubRegionBase & subRegion ) const
+ {
+ GEOS_MARK_FUNCTION;
+
+ string const & solidName = subRegion.getReference< string >( viewKeyStruct::solidNamesString() );
+ string const & fluidName = subRegion.getReference< string >( viewKeyStruct::fluidNamesString() );
+
+ TwoPhaseImmiscibleFluid const & fluid = getConstitutiveModel< TwoPhaseImmiscibleFluid >( subRegion, fluidName );
+ CoupledSolidBase const & solid = getConstitutiveModel< CoupledSolidBase >( subRegion, solidName );
+
+ arrayView1d< real64 const > const volume = subRegion.getElementVolume();
+ arrayView2d< real64 const > const porosity = solid.getPorosity();
+
+ arrayView2d< real64 const, immiscibleFlow::USD_PHASE > const phaseVolFrac= subRegion.getField< fields::immiscibleMultiphaseFlow::phaseVolumeFraction >();
+ arrayView3d< real64 const, multifluid::USD_PHASE > phaseDens = fluid.phaseDensity();
+ arrayView2d< real64, immiscibleFlow::USD_PHASE > const phaseMass = subRegion.getField< fields::immiscibleMultiphaseFlow::phaseMass >();
+
+ // Might be needed for geomechanics????? if so, need to change the accumulation as well?
+ //arrayView1d< real64 > const deltaVolume = subRegion.getField< fields::flow::deltaVolume >();
+
+ forAll< parallelDevicePolicy<> >( subRegion.size(), [=] GEOS_HOST_DEVICE ( localIndex const ei )
+ {
+ real64 const poreVolume = volume[ei] * porosity[ei][0];
+ for( integer ip = 0; ip < 2; ++ip )
+ {
+ phaseMass[ei][ip] = poreVolume * phaseVolFrac[ei][ip] * phaseDens[ei][0][ip];
+ }
+ } );
+ }
+
+
+ void ImmiscibleMultiphaseFlow::updatePhaseMobility( ObjectManagerBase & dataGroup ) const
+ {
+ GEOS_MARK_FUNCTION;
+
+ // note that the phase mobility computed here also includes phase density
+ string const & fluidName = dataGroup.getReference< string >( viewKeyStruct::fluidNamesString() );
+ TwoPhaseImmiscibleFluid const & fluid = getConstitutiveModel< TwoPhaseImmiscibleFluid >( dataGroup, fluidName );
+
+ string const & relpermName = dataGroup.getReference< string >( viewKeyStruct::relPermNamesString() );
+ RelativePermeabilityBase const & relperm = getConstitutiveModel< RelativePermeabilityBase >( dataGroup, relpermName );
+
+ immiscibleMultiphaseKernels::
+ PhaseMobilityKernelFactory::
+ createAndLaunch< parallelDevicePolicy<> >( m_numPhases,
+ dataGroup,
+ fluid,
+ relperm );
+ }
+
+ void ImmiscibleMultiphaseFlow::initializeFluidState( MeshLevel & mesh,
+ string_array const & regionNames )
+ {
+ GEOS_MARK_FUNCTION;
+
+ mesh.getElemManager().forElementSubRegions( regionNames,
+ [&]( localIndex const,
+ ElementSubRegionBase & subRegion )
+ {
+ // 2. Assume global component fractions have been prescribed.
+ // Initialize constitutive state to get fluid density.
+ updateFluidModel( subRegion );
+
+ } );
+
+ // for some reason CUDA does not want the host_device lambda to be defined inside the generic lambda
+ // I need the exact type of the subRegion for updateSolidflowProperties to work well.
+ mesh.getElemManager().forElementSubRegions< CellElementSubRegion,
+ SurfaceElementSubRegion >( regionNames, [&]( localIndex const,
+ auto & subRegion )
+ {
+ // 4. Initialize/update dependent state quantities
+
+ // 4.1 Update the constitutive models that only depend on
+ // - the primary variables
+ // - the fluid constitutive quantities (as they have already been updated)
+ // We postpone the other constitutive models for now
+ // In addition, to avoid multiplying permeability/porosity bay netToGross in the assembly kernel, we do it once and for all here
+ arrayView1d< real64 const > const netToGross = subRegion.template getField< fields::flow::netToGross >();
+ CoupledSolidBase const & porousSolid =
+ getConstitutiveModel< CoupledSolidBase >( subRegion, subRegion.template getReference< string >( viewKeyStruct::solidNamesString() ) );
+ PermeabilityBase const & permeabilityModel =
+ getConstitutiveModel< PermeabilityBase >( subRegion, subRegion.template getReference< string >( viewKeyStruct::permeabilityNamesString() ) );
+ permeabilityModel.scaleHorizontalPermeability( netToGross );
+ porousSolid.scaleReferencePorosity( netToGross );
+ saveConvergedState( subRegion ); // necessary for a meaningful porosity update in sequential schemes
+ updatePorosityAndPermeability( subRegion );
+
+ // Now, we initialize and update each constitutive model one by one
+
+ // 4.2 Save the computed porosity into the old porosity
+ //
+ // Note:
+ // - This must be called after updatePorosityAndPermeability
+ // - This step depends on porosity
+ string const & solidName = subRegion.template getReference< string >( viewKeyStruct::solidNamesString() );
+ CoupledSolidBase const & porousMaterial = getConstitutiveModel< CoupledSolidBase >( subRegion, solidName );
+ porousMaterial.initializeState();
+
+ // 4.3 Initialize/update the relative permeability model using the initial phase volume fraction
+ // This is needed to handle relative permeability hysteresis
+ // Also, initialize the fluid model
+ //
+ // Note:
+ // - This must be called after updateVolumeConstraint
+ // - This step depends on phaseVolFraction
+
+ // initialized phase volume fraction
+ arrayView2d< real64 const, immiscibleFlow::USD_PHASE > const phaseVolFrac =
+ subRegion.template getField< fields::immiscibleMultiphaseFlow::phaseVolumeFraction >();
+
+ string const & relpermName = subRegion.template getReference< string >( viewKeyStruct::relPermNamesString() );
+ RelativePermeabilityBase & relPermMaterial =
+ getConstitutiveModel< RelativePermeabilityBase >( subRegion, relpermName );
+ relPermMaterial.saveConvergedPhaseVolFractionState( phaseVolFrac ); // this needs to happen before calling updateRelPermModel
+ updateRelPermModel( subRegion );
+ relPermMaterial.saveConvergedState(); // this needs to happen after calling updateRelPermModel
+
+ // 4.4 Then, we initialize/update the capillary pressure model
+ //
+ // Note:
+ // - This must be called after updatePorosityAndPermeability
+ // - This step depends on porosity and permeability
+ if( m_hasCapPressure )
+ {
// initialized porosity
arrayView2d< real64 const > const porosity = porousMaterial.getPorosity();
@@ -425,854 +608,939 @@ void ImmiscibleMultiphaseFlow::initializeFluidState( MeshLevel & mesh,
CapillaryPressureBase const & capPressureMaterial =
getConstitutiveModel< CapillaryPressureBase >( subRegion, capPressureName );
capPressureMaterial.initializeRockState( porosity, permeability ); // this needs to happen before calling updateCapPressureModel
+ capPressureMaterial.saveConvergedPhaseVolFractionState( phaseVolFrac );
updateCapPressureModel( subRegion );
- }
-
- // 4.5 Update the phase mobility
- //
- // Note:
- // - This must be called after updateRelPermModel
- // - This step depends phaseRelPerm
- updatePhaseMobility( subRegion );
-
- } );
-
- // 5. Save initial pressure
- mesh.getElemManager().forElementSubRegions( regionNames, [&]( localIndex const,
- ElementSubRegionBase & subRegion )
- {
- arrayView1d< real64 const > const pres = subRegion.getField< fields::flow::pressure >();
- arrayView1d< real64 > const initPres = subRegion.getField< fields::flow::initialPressure >();
- arrayView1d< real64 const > const temp = subRegion.getField< fields::flow::temperature >();
- arrayView1d< real64 > const initTemp = subRegion.template getField< fields::flow::initialTemperature >();
- initPres.setValues< parallelDevicePolicy<> >( pres );
- initTemp.setValues< parallelDevicePolicy<> >( temp );
-
- // TODO: Missing updatePhaseMass?
- } );
-}
-
-
-void ImmiscibleMultiphaseFlow::initializePostInitialConditionsPreSubGroups()
-{
- GEOS_MARK_FUNCTION;
-
- FlowSolverBase::initializePostInitialConditionsPreSubGroups();
-
- DomainPartition & domain = this->getGroupByPath< DomainPartition >( "/Problem/domain" );
-
- forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const &,
- MeshLevel & mesh,
- string_array const & regionNames )
- {
- FieldIdentifiers fieldsToBeSync;
- fieldsToBeSync.addElementFields( { fields::flow::pressure::key(),
- fields::immiscibleMultiphaseFlow::phaseVolumeFraction::key() },
- regionNames );
-
- CommunicationTools::getInstance().synchronizeFields( fieldsToBeSync, mesh, domain.getNeighbors(), false );
- } );
-
- initializeState( domain );
-}
-
-
-void
-ImmiscibleMultiphaseFlow::implicitStepSetup( real64 const & GEOS_UNUSED_PARAM( time_n ),
- real64 const & GEOS_UNUSED_PARAM( dt ),
- DomainPartition & domain )
-{
- forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const &,
- MeshLevel & mesh,
- string_array const & regionNames )
- {
- mesh.getElemManager().forElementSubRegions< CellElementSubRegion,
- SurfaceElementSubRegion >( regionNames,
- [&]( localIndex const,
- auto & subRegion )
- {
- saveConvergedState( subRegion );
-
- // update porosity, permeability
- updatePorosityAndPermeability( subRegion );
- // update all fluid properties
- updateVolumeConstraint( subRegion );
- updateFluidState( subRegion );
-
- // after the update, save the new saturation
- arrayView2d< real64 const, immiscibleFlow::USD_PHASE > const phaseVolFrac =
- subRegion.template getField< fields::immiscibleMultiphaseFlow::phaseVolumeFraction >();
-
- arrayView2d< real64, immiscibleFlow::USD_PHASE > const phaseVolFrac_n =
- subRegion.template getField< fields::immiscibleMultiphaseFlow::phaseVolumeFraction_n >();
- phaseVolFrac_n.setValues< parallelDevicePolicy<> >( phaseVolFrac );
-
- arrayView2d< real64 const, immiscibleFlow::USD_PHASE > const & phaseMass =
- subRegion.template getField< fields::immiscibleMultiphaseFlow::phaseMass >();
-
- arrayView2d< real64, immiscibleFlow::USD_PHASE > const & phaseMass_n =
- subRegion.template getField< fields::immiscibleMultiphaseFlow::phaseMass_n >();
- phaseMass_n.setValues< parallelDevicePolicy<> >( phaseMass );
-
- } );
- } );
-}
-
-void ImmiscibleMultiphaseFlow::assembleSystem( real64 const GEOS_UNUSED_PARAM( time_n ),
- real64 const dt,
- DomainPartition & domain,
- DofManager const & dofManager,
- CRSMatrixView< real64, globalIndex const > const & localMatrix,
- arrayView1d< real64 > const & localRhs )
-{
- GEOS_MARK_FUNCTION;
-
- assembleAccumulationTerm( domain,
- dofManager,
- localMatrix,
- localRhs );
-
-
- assembleFluxTerms( dt,
- domain,
- dofManager,
- localMatrix,
- localRhs );
-}
-
-
-
-void ImmiscibleMultiphaseFlow::assembleAccumulationTerm( DomainPartition & domain,
- DofManager const & dofManager,
- CRSMatrixView< real64, globalIndex const > const & localMatrix,
- arrayView1d< real64 > const & localRhs ) const
-{
- GEOS_MARK_FUNCTION;
-
-
- forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const &,
- MeshLevel const & mesh,
- string_array const & regionNames )
- {
- mesh.getElemManager().forElementSubRegions( regionNames,
- [&]( localIndex const,
- ElementSubRegionBase const & subRegion )
- {
- string const dofKey = dofManager.getKey( viewKeyStruct::elemDofFieldString() );
- string const & solidName = subRegion.getReference< string >( viewKeyStruct::solidNamesString() );
- string const & fluidName = subRegion.getReference< string >( viewKeyStruct::fluidNamesString() );
-
- TwoPhaseImmiscibleFluid const & fluid = getConstitutiveModel< TwoPhaseImmiscibleFluid >( subRegion, fluidName );
- CoupledSolidBase const & solid = getConstitutiveModel< CoupledSolidBase >( subRegion, solidName );
-
- immiscibleMultiphaseKernels::
- AccumulationKernelFactory::
- createAndLaunch< parallelDevicePolicy<> >( m_numPhases,
- dofManager.rankOffset(),
- m_useTotalMassEquation,
- dofKey,
- subRegion,
- fluid,
- solid,
- localMatrix,
- localRhs );
-
- } );
- } );
-}
-
-void ImmiscibleMultiphaseFlow::assembleFluxTerms( real64 const dt,
- DomainPartition const & domain,
- DofManager const & dofManager,
- CRSMatrixView< real64, globalIndex const > const & localMatrix,
- arrayView1d< real64 > const & localRhs ) const
-{
- GEOS_MARK_FUNCTION;
-
- NumericalMethodsManager const & numericalMethodManager = domain.getNumericalMethodManager();
- FiniteVolumeManager const & fvManager = numericalMethodManager.getFiniteVolumeManager();
- FluxApproximationBase const & fluxApprox = fvManager.getFluxApproximation( m_discretizationName );
-
- string const & dofKey = dofManager.getKey( viewKeyStruct::elemDofFieldString() );
-
- forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&] ( string const &,
+ }
+
+ // 4.5 Update the phase mobility
+ //
+ // Note:
+ // - This must be called after updateRelPermModel
+ // - This step depends phaseRelPerm
+ updatePhaseMobility( subRegion );
+
+ } );
+
+ // 5. Save initial pressure
+ mesh.getElemManager().forElementSubRegions( regionNames, [&]( localIndex const,
+ ElementSubRegionBase & subRegion )
+ {
+ arrayView1d< real64 const > const pres = subRegion.getField< fields::flow::pressure >();
+ arrayView1d< real64 > const initPres = subRegion.getField< fields::flow::initialPressure >();
+ arrayView1d< real64 const > const temp = subRegion.getField< fields::flow::temperature >();
+ arrayView1d< real64 > const initTemp = subRegion.template getField< fields::flow::initialTemperature >();
+ initPres.setValues< parallelDevicePolicy<> >( pres );
+ initTemp.setValues< parallelDevicePolicy<> >( temp );
+
+ // TODO: Missing updatePhaseMass?
+ } );
+ }
+
+
+ void ImmiscibleMultiphaseFlow::initializePostInitialConditionsPreSubGroups()
+ {
+ GEOS_MARK_FUNCTION;
+
+ FlowSolverBase::initializePostInitialConditionsPreSubGroups();
+
+ DomainPartition & domain = this->getGroupByPath< DomainPartition >( "/Problem/domain" );
+
+ forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const &,
+ MeshLevel & mesh,
+ string_array const & regionNames )
+ {
+ FieldIdentifiers fieldsToBeSync;
+ fieldsToBeSync.addElementFields( { fields::flow::pressure::key(),
+ fields::immiscibleMultiphaseFlow::phaseVolumeFraction::key() },
+ regionNames );
+
+ CommunicationTools::getInstance().synchronizeFields( fieldsToBeSync, mesh, domain.getNeighbors(), false );
+ } );
+
+ // Retrieve the numerical methods and finite volume manager
+ FiniteVolumeManager const & fvManager = domain.getNumericalMethodManager().getFiniteVolumeManager();
+ FluxApproximationBase const & fluxApprox = fvManager.getFluxApproximation( m_discretizationName );
+ const geos::string flux_approximation_name = fluxApprox.getName();
+
+ // Clear the existing mapping between connector indices and interface region indices
+ m_interfaceRegionByConnector.clear();
+ forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( std::string const &,
+ MeshLevel & meshLevel,
+ string_array const & GEOS_UNUSED_PARAM( regionNames ))
+ {
+ // Access the face manager and retrieve the face set group for the current mesh level
+ FaceManager const & faceManager = meshLevel.getFaceManager();
+ Group const & faceSetGroup = faceManager.sets();
+
+ // Access the connector indices map (face index → connector index)
+ Group & stencilGroup =
+ meshLevel.getGroup( FluxApproximationBase::groupKeyStruct::stencilMeshGroupString())
+ .getGroup( flux_approximation_name );
+ CellElementStencilTPFA & stencil =
+ stencilGroup.getReference< CellElementStencilTPFA >(
+ FluxApproximationBase::viewKeyStruct::cellStencilString());
+ unordered_map< localIndex, localIndex > const & connectorIndices = stencil.getConnectorIndices();
+
+ // for all interface face sets to map connector indices to their corresponding interface region indices
+ for( size_t surfaceRegionIndex = 0; surfaceRegionIndex < m_interfaceFaceSetNames.size(); ++surfaceRegionIndex )
+ {
+ // Iterate over each face and associate its connector index
+ std::string const & faceSetName = m_interfaceFaceSetNames[surfaceRegionIndex];
+ for( localIndex kf : faceSetGroup.getReference< SortedArray< localIndex > >( faceSetName ))
+ {
+ auto it = connectorIndices.find( kf );
+ if( it != connectorIndices.end())
+ {
+ // Map the connector index to the corresponding surface region index
+ m_interfaceRegionByConnector[it->second] = surfaceRegionIndex;
+ }
+ }
+ }
+ } );
+
+
+ initializeState( domain );
+ }
+
+
+ void
+ ImmiscibleMultiphaseFlow::implicitStepSetup( real64 const & GEOS_UNUSED_PARAM( time_n ),
+ real64 const & GEOS_UNUSED_PARAM( dt ),
+ DomainPartition & domain )
+ {
+ forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const &,
+ MeshLevel & mesh,
+ string_array const & regionNames )
+ {
+ mesh.getElemManager().forElementSubRegions< CellElementSubRegion,
+ SurfaceElementSubRegion >( regionNames,
+ [&]( localIndex const,
+ auto & subRegion )
+ {
+ saveConvergedState( subRegion );
+
+ // update porosity, permeability
+ updatePorosityAndPermeability( subRegion );
+ // update all fluid properties
+ updateVolumeConstraint( subRegion );
+ updateFluidState( subRegion );
+
+ // after the update, save the new saturation
+ arrayView2d< real64 const, immiscibleFlow::USD_PHASE > const phaseVolFrac =
+ subRegion.template getField< fields::immiscibleMultiphaseFlow::phaseVolumeFraction >();
+
+ arrayView2d< real64, immiscibleFlow::USD_PHASE > const phaseVolFrac_n =
+ subRegion.template getField< fields::immiscibleMultiphaseFlow::phaseVolumeFraction_n >();
+ phaseVolFrac_n.setValues< parallelDevicePolicy<> >( phaseVolFrac );
+
+ arrayView2d< real64 const, immiscibleFlow::USD_PHASE > const & phaseMass =
+ subRegion.template getField< fields::immiscibleMultiphaseFlow::phaseMass >();
+
+ arrayView2d< real64, immiscibleFlow::USD_PHASE > const & phaseMass_n =
+ subRegion.template getField< fields::immiscibleMultiphaseFlow::phaseMass_n >();
+ phaseMass_n.setValues< parallelDevicePolicy<> >( phaseMass );
+
+ } );
+ } );
+ }
+
+ void ImmiscibleMultiphaseFlow::assembleSystem( real64 const GEOS_UNUSED_PARAM( time_n ),
+ real64 const dt,
+ DomainPartition & domain,
+ DofManager const & dofManager,
+ CRSMatrixView< real64, globalIndex const > const & localMatrix,
+ arrayView1d< real64 > const & localRhs )
+ {
+ GEOS_MARK_FUNCTION;
+
+ assembleAccumulationTerm( domain,
+ dofManager,
+ localMatrix,
+ localRhs );
+
+
+ assembleFluxTerms( dt,
+ domain,
+ dofManager,
+ localMatrix,
+ localRhs );
+ }
+
+
+
+ void ImmiscibleMultiphaseFlow::assembleAccumulationTerm( DomainPartition & domain,
+ DofManager const & dofManager,
+ CRSMatrixView< real64, globalIndex const > const & localMatrix,
+ arrayView1d< real64 > const & localRhs ) const
+ {
+ GEOS_MARK_FUNCTION;
+
+
+ forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const &,
MeshLevel const & mesh,
- string_array const & )
- {
- fluxApprox.forAllStencils( mesh, [&]( auto & stencil )
- {
- typename TYPEOFREF( stencil ) ::KernelWrapper stencilWrapper = stencil.createKernelWrapper();
- immiscibleMultiphaseKernels::
- FluxComputeKernelFactory::createAndLaunch< parallelDevicePolicy<> >( m_numPhases,
- dofManager.rankOffset(),
- dofKey,
- m_hasCapPressure,
- m_useTotalMassEquation,
- m_gravityDensityScheme == GravityDensityScheme::PhasePresence,
- getName(),
- mesh.getElemManager(),
- stencilWrapper,
- dt,
- localMatrix.toViewConstSizes(),
- localRhs.toView() );
- } );
- } );
-}
-
-void ImmiscibleMultiphaseFlow::setupDofs( DomainPartition const & domain,
- DofManager & dofManager ) const
-{
- GEOS_UNUSED_VAR( domain, dofManager );
- // add a field for the cell-centered degrees of freedom
- dofManager.addField( viewKeyStruct::elemDofFieldString(),
- FieldLocation::Elem,
- m_numDofPerCell,
- getMeshTargets() );
-
- //// this call with instruct GEOS to reorder the dof numbers
- //dofManager.setLocalReorderingType( viewKeyStruct::elemDofFieldString(),
- // DofManager::LocalReorderingType::ReverseCutHillMcKee );
-
- NumericalMethodsManager const & numericalMethodManager = domain.getNumericalMethodManager();
- FiniteVolumeManager const & fvManager = numericalMethodManager.getFiniteVolumeManager();
- FluxApproximationBase const & fluxApprox = fvManager.getFluxApproximation( m_discretizationName );
- dofManager.addCoupling( viewKeyStruct::elemDofFieldString(), fluxApprox );
-}
-
-void ImmiscibleMultiphaseFlow::applyBoundaryConditions( real64 const time_n,
- real64 const dt,
- DomainPartition & domain,
- DofManager const & dofManager,
- CRSMatrixView< real64, globalIndex const > const & localMatrix,
- arrayView1d< real64 > const & localRhs )
-{
- GEOS_MARK_FUNCTION;
-
- // apply pressure boundary conditions.
- applyDirichletBC( time_n, dt, dofManager, domain, localMatrix.toViewConstSizes(), localRhs.toView() );
-
- // apply flux boundary conditions
- applySourceFluxBC( time_n, dt, dofManager, domain, localMatrix.toViewConstSizes(), localRhs.toView() );
-}
-
-
-namespace
-{
-char const bcLogMessage[] =
- "ImmiscibleMultiphaseFlow {}: at time {}s, "
- "the <{}> boundary condition '{}' is applied to the element set '{}' in subRegion '{}'. "
- "\nThe scale of this boundary condition is {} and multiplies the value of the provided function (if any). "
- "\nThe total number of target elements (including ghost elements) is {}. "
- "\nNote that if this number is equal to zero for all subRegions, the boundary condition will not be applied on this element set.";
-}
-
-bool ImmiscibleMultiphaseFlow::validateDirichletBC( DomainPartition & domain,
- real64 const time ) const
-{
- constexpr integer MAX_NP = 2;
- FieldSpecificationManager & fsManager = FieldSpecificationManager::getInstance();
-
- bool bcConsistent = true;
-
- forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const &,
- MeshLevel & mesh,
- string_array const & )
- {
- // map: regionName -> subRegionName -> setName -> numPhases to check pressure/phase are present consistent
- map< string, map< string, map< string, ComponentMask< MAX_NP > > > > bcPresCompStatusMap;
-
- // 1. Check pressure Dirichlet BCs
- fsManager.apply< ElementSubRegionBase >( time,
- mesh,
- fields::flow::pressure::key(),
- [&]( FieldSpecificationBase const &,
- string const & setName,
- SortedArrayView< localIndex const > const &,
- ElementSubRegionBase & subRegion,
- string const & )
- {
- // Check whether pressure has already been applied to this set
- string const & subRegionName = subRegion.getName();
- string const & regionName = subRegion.getParent().getParent().getName();
-
- auto & subRegionSetMap = bcPresCompStatusMap[regionName][subRegionName];
- if( subRegionSetMap.count( setName ) > 0 )
- {
- bcConsistent = false;
- GEOS_WARNING( BCMessage::pressureConflict( regionName, subRegionName, setName,
- fields::flow::pressure::key() ) );
- }
- subRegionSetMap[setName].setNumComp( m_numPhases );
- } );
- // 2. Check saturation Dirichlet BCs
- fsManager.apply< ElementSubRegionBase >( time,
- mesh,
- fields::immiscibleMultiphaseFlow::phaseVolumeFraction::key(),
- [&] ( FieldSpecificationBase const & fs,
- string const & setName,
- SortedArrayView< localIndex const > const &,
- ElementSubRegionBase & subRegion,
- string const & )
+ string_array const & regionNames )
+ {
+ mesh.getElemManager().forElementSubRegions( regionNames,
+ [&]( localIndex const,
+ ElementSubRegionBase const & subRegion )
+ {
+ string const dofKey = dofManager.getKey( viewKeyStruct::elemDofFieldString() );
+ string const & solidName = subRegion.getReference< string >( viewKeyStruct::solidNamesString() );
+ string const & fluidName = subRegion.getReference< string >( viewKeyStruct::fluidNamesString() );
+
+ TwoPhaseImmiscibleFluid const & fluid = getConstitutiveModel< TwoPhaseImmiscibleFluid >( subRegion, fluidName );
+ CoupledSolidBase const & solid = getConstitutiveModel< CoupledSolidBase >( subRegion, solidName );
+
+ immiscibleMultiphaseKernels::
+ AccumulationKernelFactory::
+ createAndLaunch< parallelDevicePolicy<> >( m_numPhases,
+ dofManager.rankOffset(),
+ m_useTotalMassEquation,
+ dofKey,
+ subRegion,
+ fluid,
+ solid,
+ localMatrix,
+ localRhs );
+
+ } );
+ } );
+ }
+
+ void ImmiscibleMultiphaseFlow::assembleFluxTerms( real64 const dt,
+ DomainPartition & domain,
+ DofManager const & dofManager,
+ CRSMatrixView< real64, globalIndex const > const & localMatrix,
+ arrayView1d< real64 > const & localRhs ) const
+ {
+ GEOS_MARK_FUNCTION;
+
+ NumericalMethodsManager const & numericalMethodManager = domain.getNumericalMethodManager();
+ FiniteVolumeManager const & fvManager = numericalMethodManager.getFiniteVolumeManager();
+ FluxApproximationBase const & fluxApprox = fvManager.getFluxApproximation( m_discretizationName );
+
+ string const & dofKey = dofManager.getKey( viewKeyStruct::elemDofFieldString() );
+
+
+ forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&] ( string const &,
+ MeshLevel & mesh,
+ string_array const & regionNames )
+ {
+ if( m_hasCapPressure )
{
- string const & subRegionName = subRegion.getName( );
- string const & regionName = subRegion.getParent().getParent().getName();
- integer const comp = fs.getComponent();
-
- auto & subRegionSetMap = bcPresCompStatusMap[regionName][subRegionName];
- if( subRegionSetMap.count( setName ) == 0 )
+ // Get the first subregion to pass to the kernel factory (only used for domainSize, not for filtering connections)
+ ElementSubRegionBase const * firstSubRegion = nullptr;
+ mesh.getElemManager().forElementSubRegions( regionNames,
+ [&]( localIndex const,
+ ElementSubRegionBase const & subRegion )
{
- bcConsistent = false;
- GEOS_WARNING( BCMessage::missingPressure( regionName, subRegionName, setName,
- fields::flow::pressure::key() ) );
- }
- if( comp < 0 || comp >= m_numPhases )
- {
- bcConsistent = false;
- GEOS_WARNING( BCMessage::invalidComponentIndex( comp, fs.getName(),
- fields::immiscibleMultiphaseFlow::phaseVolumeFraction::key() ) );
- return; // can't check next part with invalid component id
- }
-
- ComponentMask< MAX_NP > & compMask = subRegionSetMap[setName];
- if( compMask[comp] )
- {
- bcConsistent = false;
- fsManager.forSubGroups< EquilibriumInitialCondition >( [&] ( EquilibriumInitialCondition const & bc )
+ if( firstSubRegion == nullptr )
{
- string_array const & componentNames = bc.getComponentNames();
- GEOS_WARNING( BCMessage::conflictingComposition( comp, componentNames[comp],
- regionName, subRegionName, setName,
- fields::immiscibleMultiphaseFlow::phaseVolumeFraction::key() ) );
- } );
- }
- compMask.set( comp );
- } );
+ firstSubRegion = &subRegion;
+ }
+ } );
- // 3.2 Check consistency between composition BC applied to sets
- // Note: for a temperature-only boundary condition, this loop does not do anything
- for( auto const & regionEntry : bcPresCompStatusMap )
- {
- for( auto const & subRegionEntry : regionEntry.second )
+ fluxApprox.forAllStencils( mesh, [&]( auto & stencil )
{
- for( auto const & setEntry : subRegionEntry.second )
- {
- ComponentMask< MAX_NP > const & compMask = setEntry.second;
-
- fsManager.forSubGroups< EquilibriumInitialCondition >( [&] ( EquilibriumInitialCondition const & fs )
- {
- string_array const & componentNames = fs.getComponentNames();
- for( size_t ic = 0; ic < componentNames.size(); ic++ )
- {
- if( !compMask[ic] )
- {
- bcConsistent = false;
- GEOS_WARNING( BCMessage::notAppliedOnRegion( ic, componentNames[ic],
- regionEntry.first, subRegionEntry.first, setEntry.first,
- fields::immiscibleMultiphaseFlow::phaseVolumeFraction::key() ) );
- }
- }
- } );
- }
- }
+ typename TYPEOFREF( stencil ) ::KernelWrapper stencilWrapper = stencil.createKernelWrapper();
+ immiscibleMultiphaseKernels::
+ FluxComputeKernelFactory::createAndLaunch< parallelDevicePolicy<> >( m_numPhases,
+ dofManager.rankOffset(),
+ dofKey,
+ m_hasCapPressure,
+ m_useTotalMassEquation,
+ m_gravityDensityScheme == GravityDensityScheme::PhasePresence,
+ getName(),
+ mesh.getElemManager(),
+ stencilWrapper,
+ m_interfaceFaceSetNames,
+ m_interfaceConstitutivePairs,
+ m_interfaceRegionByConnector,
+ *firstSubRegion,
+ dt,
+ localMatrix.toViewConstSizes(),
+ localRhs.toView() );
+ } );
}
- } );
-
- return bcConsistent;
-}
-
-void ImmiscibleMultiphaseFlow::applyDirichletBC( real64 const time_n,
- real64 const dt,
- DofManager const & dofManager,
- DomainPartition & domain,
- CRSMatrixView< real64, globalIndex const > const & localMatrix,
- arrayView1d< real64 > const & localRhs ) const
-{
- GEOS_MARK_FUNCTION;
-
- // Only validate BC at the beginning of Newton loop
- if( m_nonlinearSolverParameters.m_numNewtonIterations == 0 )
- {
- bool const bcConsistent = validateDirichletBC( domain, time_n + dt );
- GEOS_ERROR_IF( !bcConsistent, GEOS_FMT( "ImmiscibleMultiphaseFlow {}: inconsistent boundary conditions", getDataContext() ) );
- }
-
- FieldSpecificationManager & fsManager = FieldSpecificationManager::getInstance();
-
- forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const &,
- MeshLevel & mesh,
- string_array const & )
- {
-
- // 1. Apply pressure Dirichlet BCs, store in a separate field
- applyFieldValue< ElementSubRegionBase >( time_n, dt, mesh, bcLogMessage,
- fields::flow::pressure::key(), fields::flow::bcPressure::key() );
- // 2. Apply saturation BC (phase volume fraction) and store in a separate field
- applyFieldValue< ElementSubRegionBase >( time_n, dt, mesh, bcLogMessage,
- fields::immiscibleMultiphaseFlow::phaseVolumeFraction::key(),
- fields::immiscibleMultiphaseFlow::bcPhaseVolumeFraction::key() );
-
- globalIndex const rankOffset = dofManager.rankOffset();
- string const dofKey = dofManager.getKey( viewKeyStruct::elemDofFieldString() );
-
- // 3. Call constitutive update
- fsManager.apply< ElementSubRegionBase >( time_n + dt,
- mesh,
- fields::flow::pressure::key(),
- [&] ( FieldSpecificationBase const &,
- string const &,
- SortedArrayView< localIndex const > const & targetSet,
+ else
+ {
+ fluxApprox.forAllStencils( mesh, [&]( auto & stencil )
+ {
+ typename TYPEOFREF( stencil ) ::KernelWrapper stencilWrapper = stencil.createKernelWrapper();
+ immiscibleMultiphaseKernels::
+ FluxComputeKernelFactory::createAndLaunch< parallelDevicePolicy<> >( m_numPhases,
+ dofManager.rankOffset(),
+ dofKey,
+ m_hasCapPressure,
+ m_useTotalMassEquation,
+ m_gravityDensityScheme == GravityDensityScheme::PhasePresence,
+ getName(),
+ mesh.getElemManager(),
+ stencilWrapper,
+ dt,
+ localMatrix.toViewConstSizes(),
+ localRhs.toView() );
+ } );
+ }
+
+ } );
+ }
+
+ // Ryan: Looks like this will need to be overwritten as well...
+ // I have left the CompositionalMultiphaseFVM implementation for reference
+ void ImmiscibleMultiphaseFlow::setupDofs( DomainPartition const & domain,
+ DofManager & dofManager ) const
+ {
+ GEOS_UNUSED_VAR( domain, dofManager );
+ // add a field for the cell-centered degrees of freedom
+ dofManager.addField( viewKeyStruct::elemDofFieldString(),
+ FieldLocation::Elem,
+ m_numDofPerCell,
+ getMeshTargets() );
+
+ //// this call with instruct GEOS to reorder the dof numbers
+ //dofManager.setLocalReorderingType( viewKeyStruct::elemDofFieldString(),
+ // DofManager::LocalReorderingType::ReverseCutHillMcKee );
+
+ NumericalMethodsManager const & numericalMethodManager = domain.getNumericalMethodManager();
+ FiniteVolumeManager const & fvManager = numericalMethodManager.getFiniteVolumeManager();
+ FluxApproximationBase const & fluxApprox = fvManager.getFluxApproximation( m_discretizationName );
+ dofManager.addCoupling( viewKeyStruct::elemDofFieldString(), fluxApprox );
+ }
+
+ void ImmiscibleMultiphaseFlow::applyBoundaryConditions( real64 const time_n,
+ real64 const dt,
+ DomainPartition & domain,
+ DofManager const & dofManager,
+ CRSMatrixView< real64, globalIndex const > const & localMatrix,
+ arrayView1d< real64 > const & localRhs )
+ {
+ GEOS_MARK_FUNCTION;
+
+ // apply pressure boundary conditions.
+ applyDirichletBC( time_n, dt, dofManager, domain, localMatrix.toViewConstSizes(), localRhs.toView() );
+
+ // apply flux boundary conditions
+ applySourceFluxBC( time_n, dt, dofManager, domain, localMatrix.toViewConstSizes(), localRhs.toView() );
+ }
+
+
+ namespace
+ {
+ char const bcLogMessage[] =
+ "ImmiscibleMultiphaseFlow {}: at time {}s, "
+ "the <{}> boundary condition '{}' is applied to the element set '{}' in subRegion '{}'. "
+ "\nThe scale of this boundary condition is {} and multiplies the value of the provided function (if any). "
+ "\nThe total number of target elements (including ghost elements) is {}. "
+ "\nNote that if this number is equal to zero for all subRegions, the boundary condition will not be applied on this element set.";
+ }
+
+ bool ImmiscibleMultiphaseFlow::validateDirichletBC( DomainPartition & domain,
+ real64 const time ) const
+ {
+ constexpr integer MAX_NP = 2;
+ FieldSpecificationManager & fsManager = FieldSpecificationManager::getInstance();
+
+ bool bcConsistent = true;
+
+ forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const &,
+ MeshLevel & mesh,
+ string_array const & )
+ {
+ // map: regionName -> subRegionName -> setName -> numPhases to check pressure/phase are present consistent
+ map< string, map< string, map< string, ComponentMask< MAX_NP > > > > bcPresCompStatusMap;
+
+ // 1. Check pressure Dirichlet BCs
+ fsManager.apply< ElementSubRegionBase >( time,
+ mesh,
+ fields::flow::pressure::key(),
+ [&]( FieldSpecificationBase const &,
+ string const & setName,
+ SortedArrayView< localIndex const > const &,
ElementSubRegionBase & subRegion,
string const & )
- {
-
- arrayView1d< real64 const > const bcPres =
- subRegion.getReference< array1d< real64 > >( fields::flow::bcPressure::key() );
- arrayView2d< real64 const, immiscibleFlow::USD_PHASE > const bcPhaseVolFraction =
- subRegion.getReference< array2d< real64, immiscibleFlow::LAYOUT_PHASE > >(
- fields::immiscibleMultiphaseFlow::bcPhaseVolumeFraction::key() );
-
- arrayView1d< integer const > const ghostRank =
- subRegion.getReference< array1d< integer > >( ObjectManagerBase::viewKeyStruct::ghostRankString() );
- arrayView1d< globalIndex const > const dofNumber =
- subRegion.getReference< array1d< globalIndex > >( dofKey );
- arrayView1d< real64 const > const pres =
- subRegion.getReference< array1d< real64 > >( fields::flow::pressure::key() );
- arrayView2d< real64 const, immiscibleFlow::USD_PHASE > const phaseVolFraction =
- subRegion.getReference< array2d< real64, immiscibleFlow::LAYOUT_PHASE > >(
- fields::immiscibleMultiphaseFlow::phaseVolumeFraction::key() );
-
- integer const numPhase = m_numPhases;
-
-
- forAll< parallelDevicePolicy<> >( targetSet.size(), [=] GEOS_HOST_DEVICE ( localIndex const a )
- {
- localIndex const ei = targetSet[a];
- if( ghostRank[ei] >= 0 )
- {
- return;
- }
-
- globalIndex const dofIndex = dofNumber[ei];
- localIndex const localRow = dofIndex - rankOffset;
- real64 rhsValue;
-
- // 3.1. Apply pressure value to the matrix/rhs
- FieldSpecificationEqual::SpecifyFieldValue( dofIndex,
- rankOffset,
- localMatrix,
- rhsValue,
- bcPres[ei],
- pres[ei] );
- localRhs[localRow] = rhsValue;
-
- // 3.2. For each phase, apply target saturation value
- for( integer ip = 0; ip < numPhase-1; ++ip )
- {
- FieldSpecificationEqual::SpecifyFieldValue( dofIndex + ip + 1,
- rankOffset,
- localMatrix,
- rhsValue,
- bcPhaseVolFraction[ei][ip],
- phaseVolFraction[ei][ip] );
- localRhs[localRow + ip + 1] = rhsValue;
- }
- } );
- } );
- } );
-}
-
-void ImmiscibleMultiphaseFlow::applySourceFluxBC( real64 const time,
+ {
+ // Check whether pressure has already been applied to this set
+ string const & subRegionName = subRegion.getName();
+ string const & regionName = subRegion.getParent().getParent().getName();
+
+ auto & subRegionSetMap = bcPresCompStatusMap[regionName][subRegionName];
+ if( subRegionSetMap.count( setName ) > 0 )
+ {
+ bcConsistent = false;
+ GEOS_WARNING( BCMessage::pressureConflict( regionName, subRegionName, setName,
+ fields::flow::pressure::key() ) );
+ }
+ subRegionSetMap[setName].setNumComp( m_numPhases );
+ } );
+ // 2. Check saturation Dirichlet BCs
+ fsManager.apply< ElementSubRegionBase >( time,
+ mesh,
+ fields::immiscibleMultiphaseFlow::phaseVolumeFraction::key(),
+ [&] ( FieldSpecificationBase const & fs,
+ string const & setName,
+ SortedArrayView< localIndex const > const &,
+ ElementSubRegionBase & subRegion,
+ string const & )
+ {
+ string const & subRegionName = subRegion.getName( );
+ string const & regionName = subRegion.getParent().getParent().getName();
+ integer const comp = fs.getComponent();
+
+ auto & subRegionSetMap = bcPresCompStatusMap[regionName][subRegionName];
+ if( subRegionSetMap.count( setName ) == 0 )
+ {
+ bcConsistent = false;
+ GEOS_WARNING( BCMessage::missingPressure( regionName, subRegionName, setName,
+ fields::flow::pressure::key() ) );
+ }
+ if( comp < 0 || comp >= m_numPhases )
+ {
+ bcConsistent = false;
+ GEOS_WARNING( BCMessage::invalidComponentIndex( comp, fs.getName(),
+ fields::immiscibleMultiphaseFlow::phaseVolumeFraction::key() ) );
+ return; // can't check next part with invalid component id
+ }
+
+ ComponentMask< MAX_NP > & compMask = subRegionSetMap[setName];
+ if( compMask[comp] )
+ {
+ bcConsistent = false;
+ fsManager.forSubGroups< EquilibriumInitialCondition >( [&] ( EquilibriumInitialCondition const & bc )
+ {
+ string_array const & componentNames = bc.getComponentNames();
+ GEOS_WARNING( BCMessage::conflictingComposition( comp, componentNames[comp],
+ regionName, subRegionName, setName,
+ fields::immiscibleMultiphaseFlow::phaseVolumeFraction::key() ) );
+ } );
+ }
+ compMask.set( comp );
+ } );
+
+ // 3.2 Check consistency between composition BC applied to sets
+ // Note: for a temperature-only boundary condition, this loop does not do anything
+ for( auto const & regionEntry : bcPresCompStatusMap )
+ {
+ for( auto const & subRegionEntry : regionEntry.second )
+ {
+ for( auto const & setEntry : subRegionEntry.second )
+ {
+ ComponentMask< MAX_NP > const & compMask = setEntry.second;
+
+ fsManager.forSubGroups< EquilibriumInitialCondition >( [&] ( EquilibriumInitialCondition const & fs )
+ {
+ string_array const & componentNames = fs.getComponentNames();
+ for( size_t ic = 0; ic < componentNames.size(); ic++ )
+ {
+ if( !compMask[ic] )
+ {
+ bcConsistent = false;
+ GEOS_WARNING( BCMessage::notAppliedOnRegion( ic, componentNames[ic],
+ regionEntry.first, subRegionEntry.first, setEntry.first,
+ fields::immiscibleMultiphaseFlow::phaseVolumeFraction::key() ) );
+ }
+ }
+ } );
+ }
+ }
+ }
+ } );
+
+ return bcConsistent;
+ }
+
+ void ImmiscibleMultiphaseFlow::applyDirichletBC( real64 const time_n,
real64 const dt,
DofManager const & dofManager,
DomainPartition & domain,
CRSMatrixView< real64, globalIndex const > const & localMatrix,
arrayView1d< real64 > const & localRhs ) const
-{
- GEOS_MARK_FUNCTION;
-
- FieldSpecificationManager & fsManager = FieldSpecificationManager::getInstance();
-
- string const dofKey = dofManager.getKey( viewKeyStruct::elemDofFieldString() );
-
- // Step 1: count individual source flux boundary conditions
-
- stdMap< string, localIndex > bcNameToBcId;
- localIndex bcCounter = 0;
-
+ {
+ GEOS_MARK_FUNCTION;
+
+ // Only validate BC at the beginning of Newton loop
+ if( m_nonlinearSolverParameters.m_numNewtonIterations == 0 )
+ {
+ bool const bcConsistent = validateDirichletBC( domain, time_n + dt );
+ GEOS_ERROR_IF( !bcConsistent, GEOS_FMT( "ImmiscibleMultiphaseFlow {}: inconsistent boundary conditions", getDataContext() ) );
+ }
+
+ FieldSpecificationManager & fsManager = FieldSpecificationManager::getInstance();
+
+ forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const &,
+ MeshLevel & mesh,
+ string_array const & )
+ {
+
+ // 1. Apply pressure Dirichlet BCs, store in a separate field
+ applyFieldValue< ElementSubRegionBase >( time_n, dt, mesh, bcLogMessage,
+ fields::flow::pressure::key(), fields::flow::bcPressure::key() );
+ // 2. Apply saturation BC (phase volume fraction) and store in a separate field
+ applyFieldValue< ElementSubRegionBase >( time_n, dt, mesh, bcLogMessage,
+ fields::immiscibleMultiphaseFlow::phaseVolumeFraction::key(),
+ fields::immiscibleMultiphaseFlow::bcPhaseVolumeFraction::key() );
+
+ globalIndex const rankOffset = dofManager.rankOffset();
+ string const dofKey = dofManager.getKey( viewKeyStruct::elemDofFieldString() );
+
+ // 3. Call constitutive update
+ fsManager.apply< ElementSubRegionBase >( time_n + dt,
+ mesh,
+ fields::flow::pressure::key(),
+ [&] ( FieldSpecificationBase const &,
+ string const &,
+ SortedArrayView< localIndex const > const & targetSet,
+ ElementSubRegionBase & subRegion,
+ string const & )
+ {
+
+ arrayView1d< real64 const > const bcPres =
+ subRegion.getReference< array1d< real64 > >( fields::flow::bcPressure::key() );
+ arrayView2d< real64 const, immiscibleFlow::USD_PHASE > const bcPhaseVolFraction =
+ subRegion.getReference< array2d< real64, immiscibleFlow::LAYOUT_PHASE > >(
+ fields::immiscibleMultiphaseFlow::bcPhaseVolumeFraction::key() );
+
+ arrayView1d< integer const > const ghostRank =
+ subRegion.getReference< array1d< integer > >( ObjectManagerBase::viewKeyStruct::ghostRankString() );
+ arrayView1d< globalIndex const > const dofNumber =
+ subRegion.getReference< array1d< globalIndex > >( dofKey );
+ arrayView1d< real64 const > const pres =
+ subRegion.getReference< array1d< real64 > >( fields::flow::pressure::key() );
+ arrayView2d< real64 const, immiscibleFlow::USD_PHASE > const phaseVolFraction =
+ subRegion.getReference< array2d< real64, immiscibleFlow::LAYOUT_PHASE > >(
+ fields::immiscibleMultiphaseFlow::phaseVolumeFraction::key() );
+
+ integer const numPhase = m_numPhases;
+
+
+ forAll< parallelDevicePolicy<> >( targetSet.size(), [=] GEOS_HOST_DEVICE ( localIndex const a )
+ {
+ localIndex const ei = targetSet[a];
+ if( ghostRank[ei] >= 0 )
+ {
+ return;
+ }
+
+ globalIndex const dofIndex = dofNumber[ei];
+ localIndex const localRow = dofIndex - rankOffset;
+ real64 rhsValue;
+
+ // 3.1. Apply pressure value to the matrix/rhs
+ FieldSpecificationEqual::SpecifyFieldValue( dofIndex,
+ rankOffset,
+ localMatrix,
+ rhsValue,
+ bcPres[ei],
+ pres[ei] );
+ localRhs[localRow] = rhsValue;
+
+ // 3.2. For each phase, apply target saturation value
+ for( integer ip = 0; ip < numPhase-1; ++ip )
+ {
+ FieldSpecificationEqual::SpecifyFieldValue( dofIndex + ip + 1,
+ rankOffset,
+ localMatrix,
+ rhsValue,
+ bcPhaseVolFraction[ei][ip],
+ phaseVolFraction[ei][ip] );
+ localRhs[localRow + ip + 1] = rhsValue;
+ }
+ } );
+ } );
+ } );
+ }
+
+ void ImmiscibleMultiphaseFlow::applySourceFluxBC( real64 const time,
+ real64 const dt,
+ DofManager const & dofManager,
+ DomainPartition & domain,
+ CRSMatrixView< real64, globalIndex const > const & localMatrix,
+ arrayView1d< real64 > const & localRhs ) const
+ {
+ GEOS_MARK_FUNCTION;
+
+ FieldSpecificationManager & fsManager = FieldSpecificationManager::getInstance();
+
+ string const dofKey = dofManager.getKey( viewKeyStruct::elemDofFieldString() );
+
+ // Step 1: count individual source flux boundary conditions
+
+ stdMap< string, localIndex > bcNameToBcId;
+ localIndex bcCounter = 0;
+
fsManager.forSubGroups< SourceFluxBoundaryCondition >( [&] ( SourceFluxBoundaryCondition const & bc )
{
// collect all the bc names to idx
bcNameToBcId.insert( {bc.getName(), bcCounter} );
bcCounter++;
} );
-
- if( bcCounter == 0 )
- {
- return;
- }
-
- // Step 2: count the set size for each source flux (each source flux may have multiple target sets)
-
- array1d< globalIndex > bcAllSetsSize( bcNameToBcId.size() );
-
- computeSourceFluxSizeScalingFactor( time,
- dt,
- domain,
- bcNameToBcId,
- bcAllSetsSize.toView() );
-
- // Step 3: we are ready to impose the boundary condition, normalized by the set size
-
- forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const &,
- MeshLevel & mesh,
- string_array const & )
- {
-
- fsManager.apply< ElementSubRegionBase,
- SourceFluxBoundaryCondition >( time + dt,
- mesh,
- SourceFluxBoundaryCondition::catalogName(),
- [&]( SourceFluxBoundaryCondition const & fs,
- string const & setName,
- SortedArrayView< localIndex const > const & targetSet,
- ElementSubRegionBase & subRegion,
- string const & )
- {
- if( fs.getLogLevel() >= 1 && m_nonlinearSolverParameters.m_numNewtonIterations == 0 )
- {
- globalIndex const numTargetElems = MpiWrapper::sum< globalIndex >( targetSet.size() );
- GEOS_LOG_RANK_0( GEOS_FMT( bcLogMessage,
- getName(), time+dt, fs.getCatalogName(), fs.getName(),
- setName, subRegion.getName(), fs.getScale(), numTargetElems ) );
- }
-
- if( targetSet.size() == 0 )
- {
- return;
- }
- if( !subRegion.hasWrapper( dofKey ) )
- {
- if( fs.getLogLevel() >= 1 )
- {
- GEOS_LOG_RANK( GEOS_FMT( "{}: trying to apply SourceFlux, but its targetSet named '{}' intersects with non-simulated region named '{}'.",
- getDataContext(), setName, subRegion.getName() ) );
- }
- return;
- }
-
- arrayView1d< globalIndex const > const dofNumber = subRegion.getReference< array1d< globalIndex > >( dofKey );
- arrayView1d< integer const > const ghostRank = subRegion.ghostRank();
-
- // Step 3.1: get the values of the source boundary condition that need to be added to the rhs
-
- array1d< globalIndex > dofArray( targetSet.size() );
- array1d< real64 > rhsContributionArray( targetSet.size() );
- arrayView1d< real64 > rhsContributionArrayView = rhsContributionArray.toView();
- localIndex const rankOffset = dofManager.rankOffset();
-
- RAJA::ReduceSum< parallelDeviceReduce, real64 > massProd( 0.0 );
-
- // note that the dofArray will not be used after this step (simpler to use dofNumber instead)
- fs.computeRhsContribution< FieldSpecificationAdd,
- parallelDevicePolicy<> >( targetSet.toViewConst(),
- time + dt,
- dt,
- subRegion,
- dofNumber,
- rankOffset,
- localMatrix,
- dofArray.toView(),
- rhsContributionArrayView,
- [] GEOS_HOST_DEVICE ( localIndex const )
- {
- return 0.0;
- } );
-
- // Step 3.2: we are ready to add the right-hand side contributions, taking into account our equation layout
-
- // get the normalizer
- real64 const sizeScalingFactor = bcAllSetsSize[bcNameToBcId.at( fs.getName())];
- integer const fluidPhaseId = fs.getComponent();
- integer const numFluidPhases = m_numPhases;
- integer useTotalMassEquation = m_useTotalMassEquation;
- forAll< parallelDevicePolicy<> >( targetSet.size(), [sizeScalingFactor,
- targetSet,
- rankOffset,
- ghostRank,
- fluidPhaseId,
- numFluidPhases,
- useTotalMassEquation,
- dofNumber,
- rhsContributionArrayView,
- localRhs,
- massProd] GEOS_HOST_DEVICE ( localIndex const a )
- {
- // we need to filter out ghosts here, because targetSet may contain them
- localIndex const ei = targetSet[a];
- if( ghostRank[ei] >= 0 )
- {
- return;
- }
-
- real64 const rhsValue = rhsContributionArrayView[a] / sizeScalingFactor; // scale the contribution by the sizeScalingFactor here!
- massProd += rhsValue;
- if( useTotalMassEquation > 0 )
- {
- // for all "fluid components", we add the value to the total mass balance equation
- globalIndex const totalMassBalanceRow = dofNumber[ei] - rankOffset;
- localRhs[totalMassBalanceRow] += rhsValue;
- if( fluidPhaseId < numFluidPhases - 1 )
- {
- globalIndex const compMassBalanceRow = totalMassBalanceRow + fluidPhaseId + 1; // component mass bal equations are shifted
- localRhs[compMassBalanceRow] += rhsValue;
- }
- }
- else
- {
- globalIndex const compMassBalanceRow = dofNumber[ei] - rankOffset + fluidPhaseId;
- localRhs[compMassBalanceRow] += rhsValue;
- }
- } );
-
- SourceFluxStatsAggregator::forAllFluxStatWrappers( subRegion, fs.getName(),
- [&]( SourceFluxStatsAggregator::WrappedStats & wrapper )
- {
- // set the new sub-region statistics for this timestep
- array1d< real64 > massProdArr{ m_numPhases };
- massProdArr[fluidPhaseId] = massProd.get();
- wrapper.gatherTimeStepStats( time, dt, massProdArr.toViewConst(), targetSet.size() );
- } );
- } );
- } );
-}
-
-real64 ImmiscibleMultiphaseFlow::calculateResidualNorm( real64 const & GEOS_UNUSED_PARAM( time_n ),
- real64 const & GEOS_UNUSED_PARAM( dt ),
- DomainPartition const & domain,
- DofManager const & dofManager,
- arrayView1d< real64 const > const & localRhs )
-{
- GEOS_MARK_FUNCTION;
- array1d< real64 > localResidualNorm;
- array1d< real64 > localResidualNormalizer;
- localResidualNorm.resize( numNorm );
- localResidualNormalizer.resize( numNorm );
-
- physicsSolverBaseKernels::NormType const normType = getNonlinearSolverParameters().normType();
-
- globalIndex const rankOffset = dofManager.rankOffset();
- string const dofKey = dofManager.getKey( viewKeyStruct::elemDofFieldString() );
-
- forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const &,
- MeshLevel const & mesh,
- string_array const & regionNames )
- {
- mesh.getElemManager().forElementSubRegions( regionNames,
- [&]( localIndex const,
- ElementSubRegionBase const & subRegion )
- {
- real64 subRegionResidualNorm[numNorm]{};
- real64 subRegionResidualNormalizer[numNorm]{};
-
- string const & solidName = subRegion.getReference< string >( viewKeyStruct::solidNamesString() );
- CoupledSolidBase const & solid = getConstitutiveModel< CoupledSolidBase >( subRegion, solidName );
-
- // step 1: compute the norm in the subRegion
-
- real64 subRegionFlowResidualNorm[1]{};
- real64 subRegionFlowResidualNormalizer[1]{};
-
- immiscibleMultiphaseKernels::
- ResidualNormKernelFactory::createAndLaunch< parallelDevicePolicy<> >( normType,
- 2,
- rankOffset,
- dofKey,
- localRhs,
- subRegion,
- solid,
- m_nonlinearSolverParameters.m_minNormalizer,
- subRegionFlowResidualNorm,
- subRegionFlowResidualNormalizer );
- subRegionResidualNorm[0] = subRegionFlowResidualNorm[0];
- subRegionResidualNormalizer[0] = subRegionFlowResidualNormalizer[0];
-
- // step 2: first reduction across meshBodies/regions/subRegions
- if( normType == physicsSolverBaseKernels::NormType::Linf )
- {
- physicsSolverBaseKernels::LinfResidualNormHelper::
- updateLocalNorm< numNorm >( subRegionResidualNorm, localResidualNorm );
- }
- else
- {
- physicsSolverBaseKernels::L2ResidualNormHelper::
- updateLocalNorm< numNorm >( subRegionResidualNorm, subRegionResidualNormalizer, localResidualNorm, localResidualNormalizer );
- }
- } );
- } );
-
- real64 residualNorm = 0.0;
- residualNorm = localResidualNorm[0];
- if( normType == physicsSolverBaseKernels::NormType::Linf )
- {
- physicsSolverBaseKernels::LinfResidualNormHelper::computeGlobalNorm( localResidualNorm[0], residualNorm );
- }
- else
- {
- physicsSolverBaseKernels::L2ResidualNormHelper::computeGlobalNorm( localResidualNorm[0], localResidualNormalizer[0], residualNorm );
- }
-
+
+ if( bcCounter == 0 )
+ {
+ return;
+ }
+
+ // Step 2: count the set size for each source flux (each source flux may have multiple target sets)
+
+ array1d< globalIndex > bcAllSetsSize( bcNameToBcId.size() );
+
+ computeSourceFluxSizeScalingFactor( time,
+ dt,
+ domain,
+ bcNameToBcId,
+ bcAllSetsSize.toView() );
+
+ // Step 3: we are ready to impose the boundary condition, normalized by the set size
+
+ forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const &,
+ MeshLevel & mesh,
+ string_array const & )
+ {
+
+ fsManager.apply< ElementSubRegionBase,
+ SourceFluxBoundaryCondition >( time + dt,
+ mesh,
+ SourceFluxBoundaryCondition::catalogName(),
+ [&]( SourceFluxBoundaryCondition const & fs,
+ string const & setName,
+ SortedArrayView< localIndex const > const & targetSet,
+ ElementSubRegionBase & subRegion,
+ string const & )
+ {
+ if( fs.getLogLevel() >= 1 && m_nonlinearSolverParameters.m_numNewtonIterations == 0 )
+ {
+ globalIndex const numTargetElems = MpiWrapper::sum< globalIndex >( targetSet.size() );
+ GEOS_LOG_RANK_0( GEOS_FMT( bcLogMessage,
+ getName(), time+dt, fs.getCatalogName(), fs.getName(),
+ setName, subRegion.getName(), fs.getScale(), numTargetElems ) );
+ }
+
+ if( targetSet.size() == 0 )
+ {
+ return;
+ }
+ if( !subRegion.hasWrapper( dofKey ) )
+ {
+ if( fs.getLogLevel() >= 1 )
+ {
+ GEOS_LOG_RANK( GEOS_FMT( "{}: trying to apply SourceFlux, but its targetSet named '{}' intersects with non-simulated region named '{}'.",
+ getDataContext(), setName, subRegion.getName() ) );
+ }
+ return;
+ }
+
+ arrayView1d< globalIndex const > const dofNumber = subRegion.getReference< array1d< globalIndex > >( dofKey );
+ arrayView1d< integer const > const ghostRank = subRegion.ghostRank();
+
+ // Step 3.1: get the values of the source boundary condition that need to be added to the rhs
+
+ array1d< globalIndex > dofArray( targetSet.size() );
+ array1d< real64 > rhsContributionArray( targetSet.size() );
+ arrayView1d< real64 > rhsContributionArrayView = rhsContributionArray.toView();
+ localIndex const rankOffset = dofManager.rankOffset();
+
+ RAJA::ReduceSum< parallelDeviceReduce, real64 > massProd( 0.0 );
+
+ // note that the dofArray will not be used after this step (simpler to use dofNumber instead)
+ fs.computeRhsContribution< FieldSpecificationAdd,
+ parallelDevicePolicy<> >( targetSet.toViewConst(),
+ time + dt,
+ dt,
+ subRegion,
+ dofNumber,
+ rankOffset,
+ localMatrix,
+ dofArray.toView(),
+ rhsContributionArrayView,
+ [] GEOS_HOST_DEVICE ( localIndex const )
+ {
+ return 0.0;
+ } );
+
+ // Step 3.2: we are ready to add the right-hand side contributions, taking into account our equation layout
+
+ // get the normalizer
+ real64 const sizeScalingFactor = bcAllSetsSize[bcNameToBcId.at( fs.getName())];
+ integer const fluidPhaseId = fs.getComponent();
+ integer const numFluidPhases = m_numPhases;
+ integer useTotalMassEquation = m_useTotalMassEquation;
+ forAll< parallelDevicePolicy<> >( targetSet.size(), [sizeScalingFactor,
+ targetSet,
+ rankOffset,
+ ghostRank,
+ fluidPhaseId,
+ numFluidPhases,
+ useTotalMassEquation,
+ dofNumber,
+ rhsContributionArrayView,
+ localRhs,
+ massProd] GEOS_HOST_DEVICE ( localIndex const a )
+ {
+ // we need to filter out ghosts here, because targetSet may contain them
+ localIndex const ei = targetSet[a];
+ if( ghostRank[ei] >= 0 )
+ {
+ return;
+ }
+
+ real64 const rhsValue = rhsContributionArrayView[a] / sizeScalingFactor; // scale the contribution by the sizeScalingFactor here!
+ massProd += rhsValue;
+ if( useTotalMassEquation > 0 )
+ {
+ // for all "fluid components", we add the value to the total mass balance equation
+ globalIndex const totalMassBalanceRow = dofNumber[ei] - rankOffset;
+ localRhs[totalMassBalanceRow] += rhsValue;
+ if( fluidPhaseId < numFluidPhases - 1 )
+ {
+ globalIndex const compMassBalanceRow = totalMassBalanceRow + fluidPhaseId + 1; // component mass bal equations are shifted
+ localRhs[compMassBalanceRow] += rhsValue;
+ }
+ }
+ else
+ {
+ globalIndex const compMassBalanceRow = dofNumber[ei] - rankOffset + fluidPhaseId;
+ localRhs[compMassBalanceRow] += rhsValue;
+ }
+ } );
+
+ SourceFluxStatsAggregator::forAllFluxStatWrappers( subRegion, fs.getName(),
+ [&]( SourceFluxStatsAggregator::WrappedStats & wrapper )
+ {
+ // set the new sub-region statistics for this timestep
+ array1d< real64 > massProdArr{ m_numPhases };
+ massProdArr[fluidPhaseId] = massProd.get();
+ wrapper.gatherTimeStepStats( time, dt, massProdArr.toViewConst(), targetSet.size() );
+ } );
+ } );
+ } );
+ }
+
+ real64 ImmiscibleMultiphaseFlow::calculateResidualNorm( real64 const & GEOS_UNUSED_PARAM( time_n ),
+ real64 const & GEOS_UNUSED_PARAM( dt ),
+ DomainPartition const & domain,
+ DofManager const & dofManager,
+ arrayView1d< real64 const > const & localRhs )
+ {
+ GEOS_MARK_FUNCTION;
+ array1d< real64 > localResidualNorm;
+ array1d< real64 > localResidualNormalizer;
+ localResidualNorm.resize( numNorm );
+ localResidualNormalizer.resize( numNorm );
+
+ physicsSolverBaseKernels::NormType const normType = getNonlinearSolverParameters().normType();
+
+ globalIndex const rankOffset = dofManager.rankOffset();
+ string const dofKey = dofManager.getKey( viewKeyStruct::elemDofFieldString() );
+
+ forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const &,
+ MeshLevel const & mesh,
+ string_array const & regionNames )
+ {
+ mesh.getElemManager().forElementSubRegions( regionNames,
+ [&]( localIndex const,
+ ElementSubRegionBase const & subRegion )
+ {
+ real64 subRegionResidualNorm[numNorm]{};
+ real64 subRegionResidualNormalizer[numNorm]{};
+
+ string const & solidName = subRegion.getReference< string >( viewKeyStruct::solidNamesString() );
+ CoupledSolidBase const & solid = getConstitutiveModel< CoupledSolidBase >( subRegion, solidName );
+
+ // step 1: compute the norm in the subRegion
+
+ real64 subRegionFlowResidualNorm[1]{};
+ real64 subRegionFlowResidualNormalizer[1]{};
+
+ immiscibleMultiphaseKernels::
+ ResidualNormKernelFactory::createAndLaunch< parallelDevicePolicy<> >( normType,
+ 2,
+ rankOffset,
+ dofKey,
+ localRhs,
+ subRegion,
+ solid,
+ m_nonlinearSolverParameters.m_minNormalizer,
+ subRegionFlowResidualNorm,
+ subRegionFlowResidualNormalizer );
+ subRegionResidualNorm[0] = subRegionFlowResidualNorm[0];
+ subRegionResidualNormalizer[0] = subRegionFlowResidualNormalizer[0];
+
+ // step 2: first reduction across meshBodies/regions/subRegions
+ if( normType == physicsSolverBaseKernels::NormType::Linf )
+ {
+ physicsSolverBaseKernels::LinfResidualNormHelper::
+ updateLocalNorm< numNorm >( subRegionResidualNorm, localResidualNorm );
+ }
+ else
+ {
+ physicsSolverBaseKernels::L2ResidualNormHelper::
+ updateLocalNorm< numNorm >( subRegionResidualNorm, subRegionResidualNormalizer, localResidualNorm, localResidualNormalizer );
+ }
+ } );
+ } );
+
+ real64 residualNorm = 0.0;
+ residualNorm = localResidualNorm[0];
+ if( normType == physicsSolverBaseKernels::NormType::Linf )
+ {
+ physicsSolverBaseKernels::LinfResidualNormHelper::computeGlobalNorm( localResidualNorm[0], residualNorm );
+ }
+ else
+ {
+ physicsSolverBaseKernels::L2ResidualNormHelper::computeGlobalNorm( localResidualNorm[0], localResidualNormalizer[0], residualNorm );
+ }
+
GEOS_LOG_LEVEL_RANK_0_NLR( logInfo::ResidualNorm, GEOS_FMT( " ( R{} ) = ( {:4.2e} )",
coupledSolverAttributePrefix(), residualNorm ))
-
- getConvergenceStats().setResidualValue( GEOS_FMT( "R{}", coupledSolverAttributePrefix()), residualNorm );
-
- return residualNorm;
-}
-
-void ImmiscibleMultiphaseFlow::applySystemSolution( DofManager const & dofManager,
- arrayView1d< real64 const > const & localSolution,
- real64 const scalingFactor,
- real64 const dt,
- DomainPartition & domain )
-{
- GEOS_UNUSED_VAR( dt );
-
- DofManager::CompMask pressureMask( m_numDofPerCell, 0, 1 );
-
- // 1. apply the pressure update
- dofManager.addVectorToField( localSolution,
- viewKeyStruct::elemDofFieldString(),
- fields::flow::pressure::key(),
- scalingFactor,
- pressureMask );
-
- // 2. apply the phaseVolumeFraction update
- dofManager.addVectorToField( localSolution,
- viewKeyStruct::elemDofFieldString(),
- fields::immiscibleMultiphaseFlow::phaseVolumeFraction::key(),
- scalingFactor,
- ~pressureMask );
-
- // 3. synchronize
- forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&] ( string const &,
+
+ return residualNorm;
+ }
+
+ void ImmiscibleMultiphaseFlow::applySystemSolution( DofManager const & dofManager,
+ arrayView1d< real64 const > const & localSolution,
+ real64 const scalingFactor,
+ real64 const dt,
+ DomainPartition & domain )
+ {
+ GEOS_UNUSED_VAR( dt );
+
+ DofManager::CompMask pressureMask( m_numDofPerCell, 0, 1 );
+
+ // 1. apply the pressure update
+ dofManager.addVectorToField( localSolution,
+ viewKeyStruct::elemDofFieldString(),
+ fields::flow::pressure::key(),
+ scalingFactor,
+ pressureMask );
+
+ // 2. apply the phaseVolumeFraction update
+ dofManager.addVectorToField( localSolution,
+ viewKeyStruct::elemDofFieldString(),
+ fields::immiscibleMultiphaseFlow::phaseVolumeFraction::key(),
+ scalingFactor,
+ ~pressureMask );
+
+ // 3. synchronize
+ forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&] ( string const &,
+ MeshLevel & mesh,
+ string_array const & regionNames )
+ {
+ std::vector< string > fields{ fields::flow::pressure::key(), fields::immiscibleMultiphaseFlow::phaseVolumeFraction::key() };
+
+ FieldIdentifiers fieldsToBeSync;
+ fieldsToBeSync.addElementFields( fields, regionNames );
+
+ CommunicationTools::getInstance().synchronizeFields( fieldsToBeSync, mesh, domain.getNeighbors(), true );
+ } );
+ }
+
+
+ void ImmiscibleMultiphaseFlow::updateVolumeConstraint( ElementSubRegionBase & subRegion ) const
+ {
+ GEOS_MARK_FUNCTION;
+
+ arrayView2d< real64, immiscibleFlow::USD_PHASE > const phaseVolumeFraction = subRegion.getField< fields::immiscibleMultiphaseFlow::phaseVolumeFraction >();
+
+ forAll< parallelDevicePolicy<> >( subRegion.size(), [=] GEOS_HOST_DEVICE ( localIndex const ei )
+ {
+ phaseVolumeFraction[ei][0] = fmin( 1.0, fmax( phaseVolumeFraction[ei][0], 0.0 )); ;
+ phaseVolumeFraction[ei][1] = 1.0 - phaseVolumeFraction[ei][0];
+ } );
+ }
+
+
+ void ImmiscibleMultiphaseFlow::resetStateToBeginningOfStep( DomainPartition & domain )
+ {
+ GEOS_MARK_FUNCTION;
+
+ forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const &,
MeshLevel & mesh,
string_array const & regionNames )
- {
- stdVector< string > fields{ fields::flow::pressure::key(), fields::immiscibleMultiphaseFlow::phaseVolumeFraction::key() };
-
- FieldIdentifiers fieldsToBeSync;
- fieldsToBeSync.addElementFields( fields, regionNames );
-
- CommunicationTools::getInstance().synchronizeFields( fieldsToBeSync, mesh, domain.getNeighbors(), true );
- } );
-}
-
-
-void ImmiscibleMultiphaseFlow::updateVolumeConstraint( ElementSubRegionBase & subRegion ) const
-{
- GEOS_MARK_FUNCTION;
-
- arrayView2d< real64, immiscibleFlow::USD_PHASE > const phaseVolumeFraction = subRegion.getField< fields::immiscibleMultiphaseFlow::phaseVolumeFraction >();
-
- forAll< parallelDevicePolicy<> >( subRegion.size(), [=] GEOS_HOST_DEVICE ( localIndex const ei )
- {
- phaseVolumeFraction[ei][1] = 1.0 - phaseVolumeFraction[ei][0];
- } );
-}
-
-
-void ImmiscibleMultiphaseFlow::resetStateToBeginningOfStep( DomainPartition & domain )
-{
- GEOS_MARK_FUNCTION;
-
- forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const &,
- MeshLevel & mesh,
- string_array const & regionNames )
- {
- mesh.getElemManager().forElementSubRegions< CellElementSubRegion,
- SurfaceElementSubRegion >( regionNames,
- [&]( localIndex const,
- auto & subRegion )
- {
- arrayView1d< real64 > const & pres =
- subRegion.template getField< fields::flow::pressure >();
- arrayView1d< real64 const > const & pres_n =
- subRegion.template getField< fields::flow::pressure_n >();
- pres.setValues< parallelDevicePolicy<> >( pres_n );
-
- // after the update, save the new saturation
- arrayView2d< real64 const, immiscibleFlow::USD_PHASE > const phaseVolFrac_n =
- subRegion.template getField< fields::immiscibleMultiphaseFlow::phaseVolumeFraction_n >();
-
- arrayView2d< real64, immiscibleFlow::USD_PHASE > const phaseVolFrac =
- subRegion.template getField< fields::immiscibleMultiphaseFlow::phaseVolumeFraction >();
- phaseVolFrac.setValues< parallelDevicePolicy<> >( phaseVolFrac_n );
-
- arrayView2d< real64 const, immiscibleFlow::USD_PHASE > const & phaseMass_n =
- subRegion.template getField< fields::immiscibleMultiphaseFlow::phaseMass_n >();
-
- arrayView2d< real64, immiscibleFlow::USD_PHASE > const & phaseMass =
- subRegion.template getField< fields::immiscibleMultiphaseFlow::phaseMass >();
- phaseMass.setValues< parallelDevicePolicy<> >( phaseMass_n );
-
- if( m_isThermal )
- {
- arrayView1d< real64 > const & temp =
- subRegion.template getField< fields::flow::temperature >();
- arrayView1d< real64 const > const & temp_n =
- subRegion.template getField< fields::flow::temperature_n >();
- temp.setValues< parallelDevicePolicy<> >( temp_n );
- }
-
- // update porosity, permeability
- updatePorosityAndPermeability( subRegion );
- // update all fluid properties
- updateFluidState( subRegion );
- } );
- } );
-}
-
-void ImmiscibleMultiphaseFlow::implicitStepComplete( real64 const & time,
- real64 const & dt,
- DomainPartition & domain )
-{
- // Step 1: save the converged aquifer state
- // note: we have to save the aquifer state **before** updating the pressure,
- // otherwise the aquifer flux is saved with the wrong pressure time level
- saveAquiferConvergedState( time, dt, domain );
-
- forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const &,
- MeshLevel & mesh,
- string_array const & regionNames )
- {
- mesh.getElemManager().forElementSubRegions( regionNames,
- [&]( localIndex const,
- ElementSubRegionBase & subRegion )
- {
- // Step 3: save the converged solid state
- string const & solidName = subRegion.getReference< string >( viewKeyStruct::solidNamesString() );
- CoupledSolidBase const & porousMaterial = getConstitutiveModel< CoupledSolidBase >( subRegion, solidName );
- if( m_keepVariablesConstantDuringInitStep )
- {
- porousMaterial.ignoreConvergedState(); // newPorosity <- porosity_n
- }
- else
- {
- porousMaterial.saveConvergedState(); // porosity_n <- porosity
- }
-
- // Step 4: save converged state for the relperm model to handle hysteresis
- arrayView2d< real64 const, immiscibleFlow::USD_PHASE > const phaseVolFrac =
- subRegion.getField< fields::immiscibleMultiphaseFlow::phaseVolumeFraction >();
- string const & relPermName = subRegion.getReference< string >( viewKeyStruct::relPermNamesString() );
- RelativePermeabilityBase const & relPermMaterial =
- getConstitutiveModel< RelativePermeabilityBase >( subRegion, relPermName );
- relPermMaterial.saveConvergedPhaseVolFractionState( phaseVolFrac );
-
- // Step 5: if capillary pressure is supported, send the converged porosity and permeability to the capillary pressure model
- // note: this is needed when the capillary pressure depends on porosity and permeability (Leverett J-function for instance)
- if( m_hasCapPressure )
- {
+ {
+ mesh.getElemManager().forElementSubRegions< CellElementSubRegion,
+ SurfaceElementSubRegion >( regionNames,
+ [&]( localIndex const,
+ auto & subRegion )
+ {
+ arrayView1d< real64 > const & pres =
+ subRegion.template getField< fields::flow::pressure >();
+ arrayView1d< real64 const > const & pres_n =
+ subRegion.template getField< fields::flow::pressure_n >();
+ pres.setValues< parallelDevicePolicy<> >( pres_n );
+
+ // after the update, save the new saturation
+ arrayView2d< real64 const, immiscibleFlow::USD_PHASE > const phaseVolFrac_n =
+ subRegion.template getField< fields::immiscibleMultiphaseFlow::phaseVolumeFraction_n >();
+
+ arrayView2d< real64, immiscibleFlow::USD_PHASE > const phaseVolFrac =
+ subRegion.template getField< fields::immiscibleMultiphaseFlow::phaseVolumeFraction >();
+ phaseVolFrac.setValues< parallelDevicePolicy<> >( phaseVolFrac_n );
+
+ arrayView2d< real64 const, immiscibleFlow::USD_PHASE > const & phaseMass_n =
+ subRegion.template getField< fields::immiscibleMultiphaseFlow::phaseMass_n >();
+
+ arrayView2d< real64, immiscibleFlow::USD_PHASE > const & phaseMass =
+ subRegion.template getField< fields::immiscibleMultiphaseFlow::phaseMass >();
+ phaseMass.setValues< parallelDevicePolicy<> >( phaseMass_n );
+
+ if( m_isThermal )
+ {
+ arrayView1d< real64 > const & temp =
+ subRegion.template getField< fields::flow::temperature >();
+ arrayView1d< real64 const > const & temp_n =
+ subRegion.template getField< fields::flow::temperature_n >();
+ temp.setValues< parallelDevicePolicy<> >( temp_n );
+ }
+
+ // update porosity, permeability
+ updatePorosityAndPermeability( subRegion );
+ // update all fluid properties
+ updateFluidState( subRegion );
+ } );
+ } );
+ }
+
+ void ImmiscibleMultiphaseFlow::implicitStepComplete( real64 const & time,
+ real64 const & dt,
+ DomainPartition & domain )
+ {
+ // Step 1: save the converged aquifer state
+ // note: we have to save the aquifer state **before** updating the pressure,
+ // otherwise the aquifer flux is saved with the wrong pressure time level
+ saveAquiferConvergedState( time, dt, domain );
+
+ forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const &,
+ MeshLevel & mesh,
+ string_array const & regionNames )
+ {
+ mesh.getElemManager().forElementSubRegions( regionNames,
+ [&]( localIndex const,
+ ElementSubRegionBase & subRegion )
+ {
+ // Step 3: save the converged solid state
+ string const & solidName = subRegion.getReference< string >( viewKeyStruct::solidNamesString() );
+ CoupledSolidBase const & porousMaterial = getConstitutiveModel< CoupledSolidBase >( subRegion, solidName );
+ if( m_keepVariablesConstantDuringInitStep )
+ {
+ porousMaterial.ignoreConvergedState(); // newPorosity <- porosity_n
+ }
+ else
+ {
+ porousMaterial.saveConvergedState(); // porosity_n <- porosity
+ }
+
+ // Step 4: save converged state for the relperm model to handle hysteresis
+ arrayView2d< real64 const, immiscibleFlow::USD_PHASE > const phaseVolFrac =
+ subRegion.getField< fields::immiscibleMultiphaseFlow::phaseVolumeFraction >();
+ string const & relPermName = subRegion.getReference< string >( viewKeyStruct::relPermNamesString() );
+ RelativePermeabilityBase const & relPermMaterial =
+ getConstitutiveModel< RelativePermeabilityBase >( subRegion, relPermName );
+ relPermMaterial.saveConvergedPhaseVolFractionState( phaseVolFrac );
+
+ // Step 5: if capillary pressure is supported, send the converged porosity and permeability to the capillary pressure model
+ // note: this is needed when the capillary pressure depends on porosity and permeability (Leverett J-function for instance)
+ if( m_hasCapPressure )
+ {
arrayView2d< real64 const > const porosity = porousMaterial.getPorosity();
string const & permName = subRegion.getReference< string >( viewKeyStruct::permeabilityNamesString() );
@@ -1284,127 +1552,128 @@ void ImmiscibleMultiphaseFlow::implicitStepComplete( real64 const & time,
CapillaryPressureBase const & capPressureMaterial =
getConstitutiveModel< CapillaryPressureBase >( subRegion, capPressName );
capPressureMaterial.saveConvergedRockState( porosity, permeability );
- }
- } );
- } );
-
-}
-
-void ImmiscibleMultiphaseFlow::saveConvergedState( ElementSubRegionBase & subRegion ) const
-{
- FlowSolverBase::saveConvergedState( subRegion );
-
- arrayView2d< real64 const, immiscibleFlow::USD_PHASE > const phaseVolFrac =
- subRegion.getField< fields::immiscibleMultiphaseFlow::phaseVolumeFraction >();
- arrayView2d< real64, immiscibleFlow::USD_PHASE > const phaseVolFrac_n =
- subRegion.getField< fields::immiscibleMultiphaseFlow::phaseVolumeFraction_n >();
- phaseVolFrac_n.setValues< parallelDevicePolicy<> >( phaseVolFrac );
-
- arrayView2d< real64 const, immiscibleFlow::USD_PHASE > const phaseMass =
- subRegion.getField< fields::immiscibleMultiphaseFlow::phaseMass >();
- arrayView2d< real64, immiscibleFlow::USD_PHASE > const phaseMass_n =
- subRegion.getField< fields::immiscibleMultiphaseFlow::phaseMass_n >();
- phaseMass_n.setValues< parallelDevicePolicy<> >( phaseMass );
-
-}
-
-void ImmiscibleMultiphaseFlow::updateState( DomainPartition & domain )
-{
- GEOS_MARK_FUNCTION;
-
- forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const &,
- MeshLevel & mesh,
- string_array const & regionNames )
- {
- mesh.getElemManager().forElementSubRegions< CellElementSubRegion,
- SurfaceElementSubRegion >( regionNames, [&]( localIndex const,
- auto & subRegion )
- {
- // update porosity, permeability, and solid internal energy
- updatePorosityAndPermeability( subRegion );
- // update all fluid properties
- updateVolumeConstraint( subRegion );
- updateFluidState( subRegion );
- } );
- } );
-}
-
-real64 ImmiscibleMultiphaseFlow::setNextDtBasedOnStateChange( real64 const & currentDt,
- DomainPartition & domain )
-{
- if( m_targetRelativePresChange >= 1.0 &&
- m_targetPhaseVolFracChange >= 1.0 )
- {
- return LvArray::NumericLimits< real64 >::max;
- }
-
- real64 maxRelativePresChange = 0.0;
- real64 maxAbsolutePhaseVolFracChange = 0.0;
-
- integer const numPhase = m_numPhases;
-
- forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const &,
- MeshLevel & mesh,
- string_array const & regionNames )
- {
- mesh.getElemManager().forElementSubRegions( regionNames,
- [&]( localIndex const,
- ElementSubRegionBase & subRegion )
- {
- arrayView1d< integer const > const ghostRank = subRegion.ghostRank();
-
- arrayView1d< real64 const > const pres = subRegion.getField< fields::flow::pressure >();
- arrayView1d< real64 const > const pres_n = subRegion.getField< fields::flow::pressure_n >();
- arrayView2d< real64 const, immiscibleFlow::USD_PHASE > const phaseVolFrac =
- subRegion.getField< fields::immiscibleMultiphaseFlow::phaseVolumeFraction >();
- arrayView2d< real64, immiscibleFlow::USD_PHASE > const phaseVolFrac_n =
- subRegion.getField< fields::immiscibleMultiphaseFlow::phaseVolumeFraction_n >();
-
- RAJA::ReduceMax< parallelDeviceReduce, real64 > subRegionMaxPresChange( 0.0 );
- RAJA::ReduceMax< parallelDeviceReduce, real64 > subRegionMaxPhaseVolFracChange( 0.0 );
-
- forAll< parallelDevicePolicy<> >( subRegion.size(), [=] GEOS_HOST_DEVICE ( localIndex const ei )
- {
- if( ghostRank[ei] < 0 )
- {
- // switch from relative to absolute when values less than 1
- subRegionMaxPresChange.max( LvArray::math::abs( pres[ei] - pres_n[ei] ) / LvArray::math::max( LvArray::math::abs( pres_n[ei] ), 1.0 ) );
- for( integer ip = 0; ip < numPhase; ++ip )
- {
- subRegionMaxPhaseVolFracChange.max( LvArray::math::abs( phaseVolFrac[ei][ip] - phaseVolFrac_n[ei][ip] ) );
- }
- }
- } );
-
- maxRelativePresChange = LvArray::math::max( maxRelativePresChange, subRegionMaxPresChange.get() );
- maxAbsolutePhaseVolFracChange = LvArray::math::max( maxAbsolutePhaseVolFracChange, subRegionMaxPhaseVolFracChange.get() );
-
- } );
- } );
-
- maxRelativePresChange = MpiWrapper::max( maxRelativePresChange );
- maxAbsolutePhaseVolFracChange = MpiWrapper::max( maxAbsolutePhaseVolFracChange );
-
- GEOS_LOG_LEVEL_RANK_0( logInfo::TimeStep, GEOS_FMT( "{}: max relative pressure change during time step = {} %",
- getName(), GEOS_FMT( "{:.{}f}", 100*maxRelativePresChange, 3 ) ) );
- GEOS_LOG_LEVEL_RANK_0( logInfo::TimeStep, GEOS_FMT( "{}: max absolute phase volume fraction change during time step = {}",
- getName(), GEOS_FMT( "{:.{}f}", maxAbsolutePhaseVolFracChange, 3 ) ) );
-
- real64 const eps = LvArray::NumericLimits< real64 >::epsilon;
-
- real64 const nextDtPressure = currentDt * ( 1.0 + m_solutionChangeScalingFactor ) * m_targetRelativePresChange
- / std::max( eps, maxRelativePresChange + m_solutionChangeScalingFactor * m_targetRelativePresChange );
- if( m_nonlinearSolverParameters.getLogLevel() > 0 )
- GEOS_LOG_RANK_0( GEOS_FMT( "{}: next time step based on pressure change = {}", getName(), nextDtPressure ));
- real64 const nextDtPhaseVolFrac = currentDt * ( 1.0 + m_solutionChangeScalingFactor ) * m_targetPhaseVolFracChange
- / std::max( eps, maxAbsolutePhaseVolFracChange + m_solutionChangeScalingFactor * m_targetPhaseVolFracChange );
- if( m_nonlinearSolverParameters.getLogLevel() > 0 )
- GEOS_LOG_RANK_0( GEOS_FMT( "{}: next time step based on phase volume fraction change = {}", getName(), nextDtPhaseVolFrac ));
-
- return std::min( nextDtPressure, nextDtPhaseVolFrac );
-
-}
-
-REGISTER_CATALOG_ENTRY( PhysicsSolverBase, ImmiscibleMultiphaseFlow, string const &, Group * const )
-
-} // namespace geos
+ capPressureMaterial.saveConvergedPhaseVolFractionState( phaseVolFrac );
+ }
+ } );
+ } );
+ }
+
+ void ImmiscibleMultiphaseFlow::saveConvergedState( ElementSubRegionBase & subRegion ) const
+ {
+ FlowSolverBase::saveConvergedState( subRegion );
+
+ arrayView2d< real64 const, immiscibleFlow::USD_PHASE > const phaseVolFrac =
+ subRegion.getField< fields::immiscibleMultiphaseFlow::phaseVolumeFraction >();
+ arrayView2d< real64, immiscibleFlow::USD_PHASE > const phaseVolFrac_n =
+ subRegion.getField< fields::immiscibleMultiphaseFlow::phaseVolumeFraction_n >();
+ phaseVolFrac_n.setValues< parallelDevicePolicy<> >( phaseVolFrac );
+
+ arrayView2d< real64 const, immiscibleFlow::USD_PHASE > const phaseMass =
+ subRegion.getField< fields::immiscibleMultiphaseFlow::phaseMass >();
+ arrayView2d< real64, immiscibleFlow::USD_PHASE > const phaseMass_n =
+ subRegion.getField< fields::immiscibleMultiphaseFlow::phaseMass_n >();
+ phaseMass_n.setValues< parallelDevicePolicy<> >( phaseMass );
+
+ }
+
+ void ImmiscibleMultiphaseFlow::updateState( DomainPartition & domain )
+ {
+ GEOS_MARK_FUNCTION;
+
+ forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const &,
+ MeshLevel & mesh,
+ string_array const & regionNames )
+ {
+ mesh.getElemManager().forElementSubRegions< CellElementSubRegion,
+ SurfaceElementSubRegion >( regionNames, [&]( localIndex const,
+ auto & subRegion )
+ {
+ // update porosity, permeability, and solid internal energy
+ updatePorosityAndPermeability( subRegion );
+ // update all fluid properties
+ updateVolumeConstraint( subRegion );
+ updateFluidState( subRegion );
+ } );
+ } );
+ }
+
+ real64 ImmiscibleMultiphaseFlow::setNextDtBasedOnStateChange( real64 const & currentDt,
+ DomainPartition & domain )
+ {
+ if( m_targetRelativePresChange >= 1.0 &&
+ m_targetPhaseVolFracChange >= 1.0 )
+ {
+ return LvArray::NumericLimits< real64 >::max;
+ }
+
+ real64 maxRelativePresChange = 0.0;
+ real64 maxAbsolutePhaseVolFracChange = 0.0;
+
+ integer const numPhase = m_numPhases;
+
+ forDiscretizationOnMeshTargets( domain.getMeshBodies(), [&]( string const &,
+ MeshLevel & mesh,
+ string_array const & regionNames )
+ {
+ mesh.getElemManager().forElementSubRegions( regionNames,
+ [&]( localIndex const,
+ ElementSubRegionBase & subRegion )
+ {
+ arrayView1d< integer const > const ghostRank = subRegion.ghostRank();
+
+ arrayView1d< real64 const > const pres = subRegion.getField< fields::flow::pressure >();
+ arrayView1d< real64 const > const pres_n = subRegion.getField< fields::flow::pressure_n >();
+ arrayView2d< real64 const, immiscibleFlow::USD_PHASE > const phaseVolFrac =
+ subRegion.getField< fields::immiscibleMultiphaseFlow::phaseVolumeFraction >();
+ arrayView2d< real64, immiscibleFlow::USD_PHASE > const phaseVolFrac_n =
+ subRegion.getField< fields::immiscibleMultiphaseFlow::phaseVolumeFraction_n >();
+
+ RAJA::ReduceMax< parallelDeviceReduce, real64 > subRegionMaxPresChange( 0.0 );
+ RAJA::ReduceMax< parallelDeviceReduce, real64 > subRegionMaxPhaseVolFracChange( 0.0 );
+
+ forAll< parallelDevicePolicy<> >( subRegion.size(), [=] GEOS_HOST_DEVICE ( localIndex const ei )
+ {
+ if( ghostRank[ei] < 0 )
+ {
+ // switch from relative to absolute when values less than 1
+ subRegionMaxPresChange.max( LvArray::math::abs( pres[ei] - pres_n[ei] ) / LvArray::math::max( LvArray::math::abs( pres_n[ei] ), 1.0 ) );
+ for( integer ip = 0; ip < numPhase; ++ip )
+ {
+ subRegionMaxPhaseVolFracChange.max( LvArray::math::abs( phaseVolFrac[ei][ip] - phaseVolFrac_n[ei][ip] ) );
+ }
+ }
+ } );
+
+ maxRelativePresChange = LvArray::math::max( maxRelativePresChange, subRegionMaxPresChange.get() );
+ maxAbsolutePhaseVolFracChange = LvArray::math::max( maxAbsolutePhaseVolFracChange, subRegionMaxPhaseVolFracChange.get() );
+
+ } );
+ } );
+
+ maxRelativePresChange = MpiWrapper::max( maxRelativePresChange );
+ maxAbsolutePhaseVolFracChange = MpiWrapper::max( maxAbsolutePhaseVolFracChange );
+
+ GEOS_LOG_LEVEL_RANK_0( logInfo::TimeStep, GEOS_FMT( "{}: max relative pressure change during time step = {} %",
+ getName(), GEOS_FMT( "{:.{}f}", 100*maxRelativePresChange, 3 ) ) );
+ GEOS_LOG_LEVEL_RANK_0( logInfo::TimeStep, GEOS_FMT( "{}: max absolute phase volume fraction change during time step = {}",
+ getName(), GEOS_FMT( "{:.{}f}", maxAbsolutePhaseVolFracChange, 3 ) ) );
+
+ real64 const eps = LvArray::NumericLimits< real64 >::epsilon;
+
+ real64 const nextDtPressure = currentDt * ( 1.0 + m_solutionChangeScalingFactor ) * m_targetRelativePresChange
+ / std::max( eps, maxRelativePresChange + m_solutionChangeScalingFactor * m_targetRelativePresChange );
+ if( m_nonlinearSolverParameters.getLogLevel() > 0 )
+ GEOS_LOG_RANK_0( GEOS_FMT( "{}: next time step based on pressure change = {}", getName(), nextDtPressure ));
+ real64 const nextDtPhaseVolFrac = currentDt * ( 1.0 + m_solutionChangeScalingFactor ) * m_targetPhaseVolFracChange
+ / std::max( eps, maxAbsolutePhaseVolFracChange + m_solutionChangeScalingFactor * m_targetPhaseVolFracChange );
+ if( m_nonlinearSolverParameters.getLogLevel() > 0 )
+ GEOS_LOG_RANK_0( GEOS_FMT( "{}: next time step based on phase volume fraction change = {}", getName(), nextDtPhaseVolFrac ));
+
+ return std::min( nextDtPressure, nextDtPhaseVolFrac );
+
+ }
+
+ REGISTER_CATALOG_ENTRY( PhysicsSolverBase, ImmiscibleMultiphaseFlow, string const &, Group * const )
+
+ } // namespace geos
+
diff --git a/src/coreComponents/physicsSolvers/fluidFlow/ImmiscibleMultiphaseFlow.hpp b/src/coreComponents/physicsSolvers/fluidFlow/ImmiscibleMultiphaseFlow.hpp
index 7526130316c..b3769837600 100644
--- a/src/coreComponents/physicsSolvers/fluidFlow/ImmiscibleMultiphaseFlow.hpp
+++ b/src/coreComponents/physicsSolvers/fluidFlow/ImmiscibleMultiphaseFlow.hpp
@@ -27,6 +27,12 @@
namespace geos
{
+
+namespace constitutive
+{
+class ConstitutiveBase;
+} // namespace constitutive
+
//START_SPHINX_INCLUDE_00
/**
* @class ImmiscibleMultiphaseFlow
@@ -160,12 +166,11 @@ class ImmiscibleMultiphaseFlow : public FlowSolverBase
* @param matrix the system matrix
* @param rhs the system right-hand side vector
*/
- virtual void
- assembleFluxTerms( real64 const dt,
- DomainPartition const & domain,
- DofManager const & dofManager,
- CRSMatrixView< real64, globalIndex const > const & localMatrix,
- arrayView1d< real64 > const & localRhs ) const;
+ void assembleFluxTerms( real64 const dt,
+ DomainPartition & domain,
+ DofManager const & dofManager,
+ CRSMatrixView< real64, globalIndex const > const & localMatrix,
+ arrayView1d< real64 > const & localRhs ) const;
/**
* @brief Function to perform the Application of Dirichlet type BC's
@@ -215,6 +220,10 @@ class ImmiscibleMultiphaseFlow : public FlowSolverBase
struct viewKeyStruct : public FlowSolverBase::viewKeyStruct
{
// inputs
+ static constexpr char const * capPressureNamesString() { return "capPressureNames"; }
+ static constexpr char const * relPermNamesString() { return "relPermNames"; }
+ static constexpr char const * elemDofFieldString() { return "elemDofField"; }
+ static constexpr char const * interfaceFaceSetNamesString() { return "interfaceFaceSetNames"; }
// density averaging scheme
static constexpr char const * gravityDensitySchemeString() { return "gravityDensityScheme"; }
@@ -228,9 +237,9 @@ class ImmiscibleMultiphaseFlow : public FlowSolverBase
static constexpr char const * maxRelativePresChangeString() { return "maxRelativePressureChange"; }
static constexpr char const * useTotalMassEquationString() { return "useTotalMassEquation"; }
- static constexpr char const * capPressureNamesString() { return "capillary_pressure"; }
- static constexpr char const * relPermNamesString() { return "relative_permeability"; }
- static constexpr char const * elemDofFieldString() { return "elemDofField"; }
+// static constexpr char const * capPressureNamesString() { return "capillary_pressure"; }
+// static constexpr char const * relPermNamesString() { return "relative_permeability"; }
+// static constexpr char const * elemDofFieldString() { return "elemDofField"; }
};
@@ -300,6 +309,15 @@ class ImmiscibleMultiphaseFlow : public FlowSolverBase
/// damping factor for solution change targets
real64 m_solutionChangeScalingFactor;
+ string_array m_interfaceFaceSetNames;
+
+ stdVector< std::array< std::tuple< constitutive::RelativePermeabilityBase *,
+ constitutive::CapillaryPressureBase *,
+ constitutive::TwoPhaseImmiscibleFluid * >, 2 > > m_interfaceConstitutivePairs;
+
+ unordered_map< localIndex, localIndex > m_interfaceRegionByConnector;
+ unordered_map< localIndex, localIndex > m_connectorIndicesByInterfaceRegion;
+
private:
diff --git a/src/coreComponents/physicsSolvers/fluidFlow/kernels/immiscibleMultiphase/CapillaryPressureUpdateKernel.hpp b/src/coreComponents/physicsSolvers/fluidFlow/kernels/immiscibleMultiphase/CapillaryPressureUpdateKernel.hpp
new file mode 100644
index 00000000000..2a02f536ea9
--- /dev/null
+++ b/src/coreComponents/physicsSolvers/fluidFlow/kernels/immiscibleMultiphase/CapillaryPressureUpdateKernel.hpp
@@ -0,0 +1,73 @@
+/*
+ * ------------------------------------------------------------------------------------------------------------
+ * SPDX-License-Identifier: LGPL-2.1-only
+ *
+ * Copyright (c) 2016-2024 Lawrence Livermore National Security LLC
+ * Copyright (c) 2018-2024 TotalEnergies
+ * Copyright (c) 2018-2024 The Board of Trustees of the Leland Stanford Junior University
+ * Copyright (c) 2023-2024 Chevron
+ * Copyright (c) 2019- GEOS/GEOSX Contributors
+ * All rights reserved
+ *
+ * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details.
+ * ------------------------------------------------------------------------------------------------------------
+ */
+
+/**
+ * @file CapillaryPressureUpdateKernel.hpp
+ */
+
+#ifndef GEOS_PHYSICSSOLVERS_FLUIDFLOW_IMMISCIBLEMULTIPHASE_CAPILLARYPRESSUREUPDATEKERNEL_HPP
+#define GEOS_PHYSICSSOLVERS_FLUIDFLOW_IMMISCIBLEMULTIPHASE_CAPILLARYPRESSUREUPDATEKERNEL_HPP
+
+#include "common/DataTypes.hpp"
+#include "common/GEOS_RAJA_Interface.hpp"
+
+namespace geos
+{
+
+namespace immiscibleMultiphaseKernels
+{
+
+/******************************** CapillaryPressureUpdateKernel ********************************/
+
+struct CapillaryPressureUpdateKernel
+{
+ template< typename POLICY, typename CAPPRES_WRAPPER >
+ static void
+ launch( localIndex const size,
+ CAPPRES_WRAPPER const & capPresWrapper,
+ arrayView2d< real64 const, immiscibleFlow::USD_PHASE > const & phaseVolFrac )
+ {
+ forAll< POLICY >( size, [=] GEOS_HOST_DEVICE ( localIndex const k )
+ {
+ for( localIndex q = 0; q < capPresWrapper.numGauss(); ++q )
+ {
+ capPresWrapper.update( k, q, phaseVolFrac[k] );
+ }
+ } );
+ }
+
+ template< typename POLICY, typename CAPPRES_WRAPPER >
+ static void
+ launch( SortedArrayView< localIndex const > const & targetSet,
+ CAPPRES_WRAPPER const & capPresWrapper,
+ arrayView2d< real64 const, immiscibleFlow::USD_PHASE > const & phaseVolFrac )
+ {
+ forAll< POLICY >( targetSet.size(), [=] GEOS_HOST_DEVICE ( localIndex const a )
+ {
+ localIndex const k = targetSet[a];
+ for( localIndex q = 0; q < capPresWrapper.numGauss(); ++q )
+ {
+ capPresWrapper.update( k, q, phaseVolFrac[k] );
+ }
+ } );
+ }
+};
+
+} // namespace immiscibleMultiphaseKernels
+
+} // namespace geos
+
+
+#endif //GEOS_PHYSICSSOLVERS_FLUIDFLOW_IMMISCIBLEMULTIPHASE_CAPILLARYPRESSUREUPDATEKERNEL_HPP
diff --git a/src/coreComponents/physicsSolvers/fluidFlow/kernels/immiscibleMultiphase/ImmiscibleMultiphaseKernels.hpp b/src/coreComponents/physicsSolvers/fluidFlow/kernels/immiscibleMultiphase/ImmiscibleMultiphaseKernels.hpp
index ebb78332b43..0881a8b07ee 100644
--- a/src/coreComponents/physicsSolvers/fluidFlow/kernels/immiscibleMultiphase/ImmiscibleMultiphaseKernels.hpp
+++ b/src/coreComponents/physicsSolvers/fluidFlow/kernels/immiscibleMultiphase/ImmiscibleMultiphaseKernels.hpp
@@ -17,39 +17,2201 @@
* @file ImmiscibleMultiphaseKernels.hpp
*/
+
#ifndef GEOS_PHYSICSSOLVERS_FLUIDFLOW_MULTIPHASEKERNELS_HPP
#define GEOS_PHYSICSSOLVERS_FLUIDFLOW_MULTIPHASEKERNELS_HPP
-#include "codingUtilities/Utilities.hpp"
-#include "common/DataLayouts.hpp"
-#include "common/DataTypes.hpp"
-#include "common/GEOS_RAJA_Interface.hpp"
+
+ #include "codingUtilities/Utilities.hpp"
+ #include "common/DataLayouts.hpp"
+ #include "common/DataTypes.hpp"
+ #include "common/GEOS_RAJA_Interface.hpp"
+ #include "constitutive/fluid/twophaseimmisciblefluid/TwoPhaseImmiscibleFluid.hpp"
+ #include "constitutive/solid/CoupledSolidBase.hpp"
+ #include "constitutive/fluid/twophaseimmisciblefluid/TwoPhaseImmiscibleFluidFields.hpp"
+ #include "constitutive/capillaryPressure/CapillaryPressureFields.hpp"
+ #include "constitutive/capillaryPressure/CapillaryPressureBase.hpp"
+ #include "constitutive/capillaryPressure/JFunctionCapillaryPressure.hpp"
+ #include "constitutive/capillaryPressure/TableCapillaryPressure.hpp"
+ #include "constitutive/capillaryPressure/TableCapillaryPressureHysteresis.hpp"
+ #include "constitutive/permeability/PermeabilityBase.hpp"
+ #include "constitutive/permeability/PermeabilityFields.hpp"
+ #include "constitutive/relativePermeability/RelativePermeabilityBase.hpp"
+ #include "constitutive/relativePermeability/RelativePermeabilityFields.hpp"
+ #include "constitutive/ConstitutiveManager.hpp"
+#include "constitutive/capillaryPressure/CapillaryPressureSelector.hpp"
+#include "constitutive/relativePermeability/RelativePermeabilitySelector.hpp"
+
+#include "constitutive/ConstitutivePassThru.hpp"
#include "constitutive/fluid/twophaseimmisciblefluid/TwoPhaseImmiscibleFluid.hpp"
-#include "constitutive/solid/CoupledSolidBase.hpp"
-#include "constitutive/fluid/twophaseimmisciblefluid/TwoPhaseImmiscibleFluidFields.hpp"
-#include "constitutive/capillaryPressure/CapillaryPressureFields.hpp"
-#include "constitutive/capillaryPressure/CapillaryPressureBase.hpp"
-#include "constitutive/permeability/PermeabilityBase.hpp"
-#include "constitutive/permeability/PermeabilityFields.hpp"
-#include "constitutive/relativePermeability/RelativePermeabilityBase.hpp"
-#include "constitutive/relativePermeability/RelativePermeabilityFields.hpp"
-#include "fieldSpecification/AquiferBoundaryCondition.hpp"
-#include "finiteVolume/BoundaryStencil.hpp"
-#include "finiteVolume/FluxApproximationBase.hpp"
-#include "linearAlgebra/interfaces/InterfaceTypes.hpp"
-#include "physicsSolvers/PhysicsSolverBaseKernels.hpp"
-#include "physicsSolvers/fluidFlow/FlowSolverBaseFields.hpp"
-#include "physicsSolvers/fluidFlow/ImmiscibleMultiphaseFlowFields.hpp"
-#include "physicsSolvers/fluidFlow/CompositionalMultiphaseUtilities.hpp"
-#include "physicsSolvers/fluidFlow/StencilAccessors.hpp"
-#include "physicsSolvers/fluidFlow/kernels/immiscibleMultiphase/KernelLaunchSelectors.hpp"
+
+
+ #include "fieldSpecification/AquiferBoundaryCondition.hpp"
+ #include "finiteVolume/BoundaryStencil.hpp"
+ #include "finiteVolume/CellElementStencilTPFA.hpp"
+ #include "finiteVolume/FluxApproximationBase.hpp"
+ #include "linearAlgebra/interfaces/InterfaceTypes.hpp"
+ #include "physicsSolvers/PhysicsSolverBaseKernels.hpp"
+ #include "physicsSolvers/fluidFlow/FlowSolverBaseFields.hpp"
+ #include "physicsSolvers/fluidFlow/ImmiscibleMultiphaseFlowFields.hpp"
+ #include "physicsSolvers/fluidFlow/CompositionalMultiphaseUtilities.hpp"
+ #include "physicsSolvers/fluidFlow/StencilAccessors.hpp"
+ #include "physicsSolvers/fluidFlow/kernels/compositional/RelativePermeabilityUpdateKernel.hpp"
+ #include "physicsSolvers/fluidFlow/kernels/compositional/CapillaryPressureUpdateKernel.hpp"
+ #include "physicsSolvers/PhysicsSolverBaseKernels.hpp"
+ #include "physicsSolvers/fluidFlow/kernels/immiscibleMultiphase/KernelLaunchSelectors.hpp"
+ #include "physicsSolvers/fluidFlow/kernels/immiscibleMultiphase/KernelLaunchSelectors.hpp"
+
namespace geos
{
namespace immiscibleMultiphaseKernels
{
+
using namespace constitutive;
+GEOS_HOST_DEVICE
+inline
+constexpr bool ENABLE_LOCAL_SOLVER_DEBUG = false;
+
+static void local_solver( real64 uT, stdVector< real64 > saturations, stdVector< real64 > pressures, stdVector< real64 > JFMultipliers, stdVector< real64 > trappedSats1,
+ stdVector< real64 > trappedSats2, stdVector< fields::cappres::ModeIndexType> modes, stdVector< real64 > transHat, stdVector< real64 > dTransHat_dP, stdVector< real64 > gravCoefHat, stdVector< real64 > gravCoef,
+ stdVector< real64 > cellCenterDuT, stdVector< real64 > cellCenterDens, stdVector< real64 > cellCenterDens_dP,
+ std::vector< RelativePermeabilityBase * > relPerms, std::vector< CapillaryPressureBase * > capPressures,
+ std::vector< TwoPhaseImmiscibleFluid * > fluids, std::vector< real64 > &phi, std::vector< real64 > &grad_phi_P, std::vector< real64 > &grad_phi_S, bool &converged,
+ stdVector< real64 > const & phaseMaxHistVolFrac1, stdVector< real64 > const & phaseMinHistVolFrac1,
+ stdVector< real64 > const & phaseMaxHistVolFrac2, stdVector< real64 > const & phaseMinHistVolFrac2,
+ real64 & warmStartPc )
+{
+
+ // getting wrappers:
+
+ constitutive::constitutiveUpdatePassThru( *capPressures[0], [&] ( auto & castedCapPres1 )
+ {
+ auto capPresWrapper1 = castedCapPres1.createKernelWrapper();
+
+
+ constitutive::constitutiveUpdatePassThru( *capPressures[1], [&] ( auto & castedCapPres2 )
+ {
+ auto capPresWrapper2 = castedCapPres2.createKernelWrapper();
+
+
+ constitutive::constitutiveUpdatePassThru( *relPerms[0], [&] ( auto & castedRelPerm1 )
+ {
+ auto relPermWrapper1 = castedRelPerm1.createKernelWrapper();
+
+
+ constitutive::constitutiveUpdatePassThru( *relPerms[1], [&] ( auto & castedRelPerm2 )
+ {
+ auto relPermWrapper2 = castedRelPerm2.createKernelWrapper();
+
+ auto fluidWrapper1 = fluids[0]->createKernelWrapper();
+ auto fluidWrapper2 = fluids[1]->createKernelWrapper();
+
+
+ std::ofstream outFile( "iterations2.csv" );
+
+
+ // Write data to the file
+ outFile << "Jacobian";
+ outFile << ",";
+ outFile << "residual";
+ outFile << ",";
+ outFile << "Fw_alpha";
+ outFile << ",";
+ outFile << "Fw_beta";
+ outFile << ",";
+ outFile << "Pc_int";
+ outFile << ",";
+ outFile << "Pc_int1";
+ outFile << ",";
+ outFile << "Pc_int2";
+ outFile << ",";
+ outFile << "Fn_alpha";
+ outFile << ",";
+ outFile << "Fn_beta";
+ outFile << ",";
+ outFile << "Vw_alpha";
+ outFile << ",";
+ outFile << "Vn_alpha";
+ outFile << ",";
+ outFile << "Vw_beta";
+ outFile << ",";
+ outFile << "Vn_beta";
+ outFile << ",";
+ outFile << "Gw_alpha";
+ outFile << ",";
+ outFile << "Gn_alpha";
+ outFile << ",";
+ outFile << "Gw_beta";
+ outFile << ",";
+ outFile << "Gn_beta";
+ outFile << ",";
+ outFile << "Cw_alpha";
+ outFile << ",";
+ outFile << "Cn_alpha";
+ outFile << ",";
+ outFile << "Cw_beta";
+ outFile << ",";
+ outFile << "Cn_beta";
+ outFile << ",";
+ outFile << "transHats0";
+ outFile << ",";
+ outFile << "transHats1";
+ outFile << ",";
+ outFile << "gravCoefHats";
+ outFile << ",";
+ outFile << "gravCoef0";
+ outFile << ",";
+ outFile << "gravCoef1";
+ outFile << ",";
+ outFile << "uT";
+ outFile << ",";
+ outFile << "duT_dP0";
+ outFile << ",";
+ outFile << "duT_dS0";
+ outFile << ",";
+ outFile << "duT_dP1";
+ outFile << ",";
+ outFile << "duT_dS1";
+ outFile << std::endl;
+
+
+
+ // nonlinear solver's parameters
+ real64 tol = 1.0e-9;
+ int max_iter = 50;
+ converged = 0;
+ bool damping = true;
+
+ // Local newton loop:
+
+ // Use of the capillary pressure kernel wrapper
+
+ StackArray< real64, 2, 2, immiscibleFlow::LAYOUT_PHASE > phaseVolFrac1( 1, 2 );
+ StackArray< real64, 3, 2, constitutive::cappres::LAYOUT_CAPPRES > capPres1( 1, 1, 2 );
+ StackArray< real64, 4, 4, constitutive::cappres::LAYOUT_CAPPRES_DS > dPhaseVolFrac_dCapPres1( 1, 1, 2, 2 );
+ StackArray< real64, 4, 4, constitutive::cappres::LAYOUT_CAPPRES_DS > dCapPres1_dPhaseVolFrac( 1, 1, 2, 2 );
+ StackArray< real64, 3, 2, constitutive::relperm::LAYOUT_RELPERM > trappedVolFrac1( 1, 1, 2 );
+ StackArray< real64, 2, 2, immiscibleFlow::LAYOUT_PHASE > facePhaseVolFrac1( 1, 2 );
+ StackArray< real64, 3, 2, constitutive::relperm::LAYOUT_RELPERM > faceTrappedVolFrac1( 1, 1, 2 );
+ StackArray< real64, 3, 2, constitutive::cappres::LAYOUT_CAPPRES > faceCapPres1( 1, 1, 2 );
+ StackArray< real64, 4, 4, constitutive::cappres::LAYOUT_CAPPRES_DS > dfacePhaseVolFrac_dCapPres1( 1, 1, 2, 2 );
+ StackArray< real64, 4, 4, constitutive::cappres::LAYOUT_CAPPRES_DS > dCapPres1_dfacePhaseVolFrac( 1, 1, 2, 2 );
+ StackArray< real64, 2, 2, immiscibleFlow::LAYOUT_PHASE > phaseVolFrac2( 1, 2 );
+ StackArray< real64, 3, 2, constitutive::cappres::LAYOUT_CAPPRES > capPres2( 1, 1, 2 );
+ StackArray< real64, 4, 4, constitutive::cappres::LAYOUT_CAPPRES_DS > dPhaseVolFrac_dCapPres2( 1, 1, 2, 2 );
+ StackArray< real64, 4, 4, constitutive::cappres::LAYOUT_CAPPRES_DS > dCapPres2_dPhaseVolFrac( 1, 1, 2, 2 );
+ StackArray< real64, 3, 2, constitutive::relperm::LAYOUT_RELPERM > trappedVolFrac2( 1, 1, 2 );
+ StackArray< real64, 2, 2, immiscibleFlow::LAYOUT_PHASE > facePhaseVolFrac2( 1, 2 );
+ StackArray< real64, 3, 2, constitutive::relperm::LAYOUT_RELPERM > faceTrappedVolFrac2( 1, 1, 2 );
+ StackArray< real64, 3, 2, constitutive::cappres::LAYOUT_CAPPRES > faceCapPres2( 1, 1, 2 );
+ StackArray< real64, 4, 4, constitutive::cappres::LAYOUT_CAPPRES_DS > dfacePhaseVolFrac_dCapPres2( 1, 1, 2, 2 );
+ StackArray< real64, 4, 4, constitutive::cappres::LAYOUT_CAPPRES_DS > dCapPres2_dfacePhaseVolFrac( 1, 1, 2, 2 );
+ StackArray< real64, 1, 2 > JFunc1( 2 );
+ StackArray< real64, 1, 2 > JFunc2( 2 );
+
+
+ StackArray< real64, 2, 2, immiscibleFlow::LAYOUT_PHASE > phaseMaxHistoricalVolFraction1( 1, 2 );
+ StackArray< real64, 2, 2, immiscibleFlow::LAYOUT_PHASE > phaseMinHistoricalVolFraction1( 1, 2 );
+ StackArray< real64, 2, 2, immiscibleFlow::LAYOUT_PHASE > phaseMaxHistoricalVolFraction2( 1, 2 );
+ StackArray< real64, 2, 2, immiscibleFlow::LAYOUT_PHASE > phaseMinHistoricalVolFraction2( 1, 2 );
+ StackArray< real64, 2, 2, compflow::LAYOUT_PHASE > phaseMode2PeakVolFraction1( 1, 2 );
+ StackArray< real64, 2, 2, compflow::LAYOUT_PHASE > phaseMode2PeakVolFraction2( 1, 2 );
+ phaseMode2PeakVolFraction1[0][0] = 0.0;
+ phaseMode2PeakVolFraction1[0][1] = 0.0;
+ phaseMode2PeakVolFraction2[0][0] = 0.0;
+ phaseMode2PeakVolFraction2[0][1] = 0.0;
+ arraySlice1d