Add tests for autochoke group target rate

Add two test cases for getAutoChokeGroupProductionTargetRate():

- TestAutoChokeGroupProductionTargetRate: normal case with
  MANI (autochoke, FLD) under intermediate group PLAT that
  has a guide rate. Verifies the autochoke reduction loop
  produces the same result as applyReductionsAndFractions_()
  when both are applicable.

- TestAutoChokeGroupTargetRateDoubleCountingBug: uses a
  NETWORK DATA file with NODEPROP (as_choke=YES) and lower
  well rates to naturally trigger underperformance detection
  (GCW=0). Documents the double-counting bug where missing
  local_reduction_level gating causes reductions to be
  subtracted at both FIELD and PLAT, producing a clamped-
  to-zero target instead of the correct value.

Also adds GROUP_HIGHER_CONSTRAINTS_NETWORK.DATA with
BRANPROP/NODEPROP keywords and parameterizes the test
fixture with mani2_control, data_file, and well rates.
This commit is contained in:
Håkon Hægland committed 2026-02-17 15:41:08 +01:00
1 parent 1f7b248016
commit 504c8f62e5
3 files changed
+589 -4

No files matched your search

+1
View File
@@ -679,6 +679,7 @@ list (APPEND TEST_DATA_FILES
tests/GCONSUMP.DATA
tests/GCONSUMP_COMPLEX.DATA
tests/GROUP_HIGHER_CONSTRAINTS.DATA
tests/GROUP_HIGHER_CONSTRAINTS_NETWORK.DATA
tests/GLIFT1.DATA
tests/RC-01_MAST_PRED.DATA
tests/include/flowl_b_vfp.ecl
+278
View File
@@ -0,0 +1,278 @@
-- This reservoir simulation deck is made available under the Open Database
-- License: http://opendatacommons.org/licenses/odbl/1.0/. Any rights in
-- individual contents of the database are licensed under the Database Contents
-- License: http://opendatacommons.org/licenses/dbcl/1.0/
-- Extended network variant of GROUP_HIGHER_CONSTRAINTS.DATA
--
-- Adds NETWORK, BRANPROP and NODEPROP keywords to enable the extended network
-- model. MANI is designated as a sub-sea manifold (autochoke group) via
-- NODEPROP, which sets as_choke()=true on the MANI group.
--
-- Group hierarchy:
--
-- FIELD (ORAT = 10000, terminal pressure = 200 barsa)
-- / \
-- / \
-- PLAT (GEFAC=0.9) PLAT-2 (GEFAC=0.85)
-- / \ |
-- / \ WELL-C
-- MANI (0.8,SSM) MANI-2 (0.75)
-- / \ |
-- WELL-A WELL-A2 WELL-B
--
-- Network branches (all VFP=9999, no pressure drop):
-- MANI -> PLAT (autochoke branch)
-- MANI-2 -> PLAT
-- PLAT -> FIELD
-- PLAT-2 -> FIELD
--
-- NODEPROP effects on MANI (as_choke=YES):
-- - group.as_choke() = true
-- - Wells switched to THP control (ProducerCMode::THP)
-- - Wells' guide rate availability disabled
--
RUNSPEC
TITLE
EFFICIENCY_FACTOR_NETWORK_TEST
DIMENS
5 5 3 /
OIL
WATER
GAS
DISGAS
METRIC
START
1 'JAN' 2020 /
WELLDIMS
-- max wells, max cons/well, max groups, max wells/group
6 5 6 4 /
TABDIMS
/
EQLDIMS
/
UNIFIN
UNIFOUT
NETWORK
-- max_nodes max_branches
5 4 /
-- =============================================================================
GRID
-- =============================================================================
DXV
5*100.0 /
DYV
5*100.0 /
DZV
3*10.0 /
TOPS
25*2000 /
PORO
75*0.25 /
PERMX
75*500 /
COPY
PERMX PERMY /
PERMX PERMZ /
/
MULTIPLY
PERMZ 0.1 /
/
-- =============================================================================
PROPS
-- =============================================================================
PVTW
-- ref.pres Bw Cw Vw Vw.visc
250.0 1.02 4.0E-05 0.5 0.0 /
ROCK
-- ref.pres compressibility
250.0 4.0E-05 /
DENSITY
-- oil water gas
850 1014 1.0 /
PVDO
-- P Bo Vo
100.0 1.05 2.0
250.0 1.02 2.5
400.0 1.00 3.0 /
PVDG
-- P Bg Vg
100.0 0.010 0.015
250.0 0.005 0.020
400.0 0.003 0.025 /
SWOF
-- Sw Krw Kro Pcow
0.2 0.0 1.0 0.0
0.3 0.05 0.6 0.0
0.5 0.15 0.3 0.0
0.7 0.35 0.1 0.0
0.8 0.5 0.0 0.0 /
SGOF
-- Sg Krg Kro Pcog
0.0 0.0 1.0 0.0
0.1 0.02 0.6 0.0
0.3 0.10 0.3 0.0
0.5 0.25 0.1 0.0
0.7 0.5 0.0 0.0 /
-- =============================================================================
SOLUTION
-- =============================================================================
EQUIL
-- datum pressure OWC Pc@OWC GOC Pc@GOC RSVD RVVD SOLN
2000 250.0 2100 0.0 1900 0.0 1 0 0 /
RSVD
1900 100.0
2100 100.0 /
-- =============================================================================
SUMMARY
-- =============================================================================
WOPR
/
WGPR
/
WWPR
/
WBHP
/
GOPR
/
FOPR
FGPR
FWPR
-- =============================================================================
SCHEDULE
-- =============================================================================
GRUPTREE
'PLAT' 'FIELD' /
'PLAT-2' 'FIELD' /
'MANI' 'PLAT' /
'MANI-2' 'PLAT' /
/
-- Well specifications
WELSPECS
'WELL-A' 'MANI' 3 3 2010 'OIL' /
'WELL-A2' 'MANI' 4 4 2010 'OIL' /
'WELL-B' 'MANI-2' 5 5 2010 'OIL' /
'WELL-C' 'PLAT-2' 2 2 2010 'OIL' /
/
COMPDAT
'WELL-A' 3 3 1 3 'OPEN' 1* 1* 0.2 /
'WELL-A2' 4 4 1 3 'OPEN' 1* 1* 0.2 /
'WELL-B' 5 5 1 3 'OPEN' 1* 1* 0.2 /
'WELL-C' 2 2 1 3 'OPEN' 1* 1* 0.2 /
/
-- Group efficiency factors
GEFAC
'PLAT' 0.9 /
'PLAT-2' 0.85 /
'MANI' 0.8 /
'MANI-2' 0.75 /
/
-- Well efficiency factors
WEFAC
'WELL-A' 1.0 /
'WELL-A2' 1.0 /
'WELL-B' 1.0 /
'WELL-C' 1.0 /
/
-- Group controls
-- FIELD has overall oil rate target
GCONPROD
'FIELD' 'ORAT' 10000 1* 1* 1* 'RATE' /
/
-- PLAT is under FLD control with explicit guide rate
GCONPROD
'PLAT' 'FLD' 1* 1* 1* 1* 'RATE' 'YES' 9500 'OIL' /
/
-- PLAT-2 is under FLD control with explicit guide rate
GCONPROD
'PLAT-2' 'FLD' 1* 1* 1* 1* 'RATE' 'YES' 2550 'OIL' /
/
-- MANI is under FLD control with explicit guide rate (autochoke group)
-- Guide rate = E_MANI * (WELL-A + WELL-A2) = 0.8 * 10000 = 8000
GCONPROD
'MANI' 'FLD' 1* 1* 1* 1* 'RATE' 'YES' 8000 'OIL' /
/
-- MANI-2 is under FLD control with explicit guide rate
GCONPROD
'MANI-2' 'FLD' 1* 1* 1* 1* 'RATE' 'YES' 1500 'OIL' /
/
-- Guide rate formula for wells (guide_rate = oil_potential)
GUIDERAT
-- min.int type A B C D E F allow.inc damp.factor use.free.gas
0 'OIL' 1.0 1.0 0.0 0.0 0.0 0.0 'YES' 1.0 'NO' /
-- Wells under group control (NODEPROP will switch MANI wells to THP)
WCONPROD
'WELL-A' 'OPEN' 'GRUP' 1* 1* 1* 1* 1* 100.0 /
'WELL-A2' 'OPEN' 'GRUP' 1* 1* 1* 1* 1* 100.0 /
'WELL-B' 'OPEN' 'GRUP' 1* 1* 1* 1* 1* 100.0 /
'WELL-C' 'OPEN' 'GRUP' 1* 1* 1* 1* 1* 100.0 /
/
-- Extended network branches (all VFP=9999: no pressure drop)
BRANPROP
-- Downtree Uptree VFP ALQ
'MANI' 'PLAT' 9999 1* /
'MANI-2' 'PLAT' 9999 1* /
'PLAT' 'FIELD' 9999 1* /
'PLAT-2' 'FIELD' 9999 1* /
/
-- Extended network node properties
-- FIELD: terminal node with fixed pressure (200 barsa)
-- MANI: sub-sea manifold (autochoke group, as_choke=YES)
NODEPROP
-- Node Pressure autoChoke addGasLift
'FIELD' 200 'NO' 'NO' /
'MANI' 1* 'YES' 'NO' /
/
TSTEP
1 /
END
+310 -4
View File
@@ -62,6 +62,7 @@
#include <opm/simulators/wells/GroupState.hpp>
#include <opm/simulators/wells/FractionCalculator.hpp>
#include <opm/simulators/wells/TargetCalculator.hpp>
#include <opm/simulators/wells/WellGroupControls.hpp>
#include <boost/test/unit_test.hpp>
@@ -92,12 +93,20 @@ struct GroupConstraintTestFixture : ToleranceAndUnitFixture
bool plat_has_guide_rate,
Opm::Group::ProductionCMode mani_control,
Opm::Well::ProducerCMode well_a_control,
Opm::Well::ProducerCMode well_a2_control)
Opm::Well::ProducerCMode well_a2_control,
Opm::Group::ProductionCMode mani2_control = Opm::Group::ProductionCMode::FLD,
const std::string& data_file = "GROUP_HIGHER_CONSTRAINTS.DATA",
double well_a_rate_metric = 6000.0,
double well_a2_rate_metric = 4000.0)
{
plat_has_guide_rate_ = plat_has_guide_rate;
mani_control_ = mani_control;
mani2_control_ = mani2_control;
well_a_control_ = well_a_control;
well_a2_control_ = well_a2_control;
data_file_ = data_file;
well_a_rate_metric_ = well_a_rate_metric;
well_a2_rate_metric_ = well_a2_rate_metric;
setupSimulator_();
setupWellRates_();
@@ -208,6 +217,12 @@ private:
//! Control mode for MANI group (ORAT or FLD)
Opm::Group::ProductionCMode mani_control_{Opm::Group::ProductionCMode::ORAT};
//! Control mode for MANI-2 group (FLD or ORAT)
Opm::Group::ProductionCMode mani2_control_{Opm::Group::ProductionCMode::FLD};
//! DATA file to load
std::string data_file_{"GROUP_HIGHER_CONSTRAINTS.DATA"};
// Well rates (SM3/day, metric units for readability)
double well_a_rate_metric_{6000.0};
double well_a2_rate_metric_{4000.0};
@@ -228,8 +243,7 @@ private:
void setupSimulator_()
{
const std::string filename = "GROUP_HIGHER_CONSTRAINTS.DATA";
simulator_ = Opm::initSimulator<TypeTag>(filename.data());
simulator_ = Opm::initSimulator<TypeTag>(data_file_.data());
simulator_->model().applyInitialSolution();
simulator_->setEpisodeIndex(-1);
@@ -339,7 +353,7 @@ private:
// Set group controls
gs.production_control("MANI", mani_control_);
gs.production_control("MANI-2", Opm::Group::ProductionCMode::FLD);
gs.production_control("MANI-2", mani2_control_);
gs.production_control("PLAT", Opm::Group::ProductionCMode::FLD);
gs.production_control("PLAT-2", Opm::Group::ProductionCMode::FLD);
gs.production_control("FIELD", Opm::Group::ProductionCMode::ORAT);
@@ -971,4 +985,296 @@ BOOST_AUTO_TEST_CASE(TestWellGroupTargetProducerWithSiblingIndividualControl)
checkAlgo(metric_rate(target), expected_target_metric);
}
//! \brief Test getAutoChokeGroupProductionTargetRate() for autochoke group MANI
//!
//! This test exercises the autochoke target calculation which uses a simpler reduction loop
//! than applyReductionsAndFractions_(): it omits both local_reduction_level gating and addback.
//! With MANI-2 under individual ORAT control, a non-zero reduction exists at the PLAT level,
//! exposing the code-path difference.
//!
//! Code-path difference vs applyReductionsAndFractions_():
//! - Standard algorithm: applies reduction only at ii >= local_reduction_level, plus addback
//! - Autochoke loop: applies reduction at ii==0 OR where guideRate->has(chain[ii]) is true
//! In this scenario both produce the same result because:
//! (a) The autochoke group (MANI) has no individual wells, so no addback is needed
//! (b) PLAT has a guide rate, so both algorithms apply the PLAT-level reduction
//!
//! \code
//! FIELD (ORAT = 10000)
//! ├── PLAT (E=0.9, FLD, GUIDERATE=9500, GCW=2)
//! │ ├── MANI (E=0.8, FLD, GUIDERATE=8000, GCW=2, autochoke group)
//! │ │ ├── WELL-A (RATE=6000, POT=6000, GRUP)
//! │ │ └── WELL-A2 (RATE=4000, POT=4000, GRUP)
//! │ └── MANI-2 (E=0.75, IDV, ORAT, GUIDERATE=1500)
//! │ └── WELL-B (RATE=2000, POT=2000, GRUP)
//! └── PLAT-2 (E=0.85, FLD, GUIDERATE=2550, GCW=1)
//! └── WELL-C (RATE=3000, POT=3000, GRUP)
//! \endcode
BOOST_AUTO_TEST_CASE(TestAutoChokeGroupProductionTargetRate)
{
// Configuration: MANI under FLD control (autochoke group follows parent),
// both wells under GRUP control, MANI-2 under individual ORAT control.
// MANI-2 under ORAT creates a non-zero reduction at PLAT level (MANI-2's
// wells contribute) which is the key ingredient that differentiates the
// autochoke loop from applyReductionsAndFractions_().
commonSetupForAllTests(
/*plat_has_guide_rate=*/true,
/*mani_control =*/Opm::Group::ProductionCMode::FLD,
/*well_a_control =*/Opm::Well::ProducerCMode::GRUP,
/*well_a2_control =*/Opm::Well::ProducerCMode::GRUP,
/*mani2_control =*/Opm::Group::ProductionCMode::ORAT
);
auto& gs = groupState();
auto& gsh = groupStateHelper();
// Verify MANI-2 is under individual ORAT control
BOOST_CHECK(gs.production_control("MANI-2") == Opm::Group::ProductionCMode::ORAT);
BOOST_CHECK(gs.production_control("MANI") == Opm::Group::ProductionCMode::FLD);
// Verify reduction rates:
// - PLAT reduction: MANI is FLD with guide rate → skip.
// MANI-2 is ORAT (individual) → add E_MANI2 * wellB = 0.75 * 2000 = 1500
// - FIELD reduction: PLAT has guide rate and GCW > 0 → skip. PLAT-2 likewise → 0
{
const auto& plat_red = gs.production_reduction_rates("PLAT");
checkRate(metric_rate(plat_red[oilPhasePos()]), eMani2() * mani2TotalRateMetric()); // 1500
const auto& field_red = gs.production_reduction_rates("FIELD");
checkRate(metric_rate(field_red[oilPhasePos()]), 0.0);
}
// Verify groupControlledWells:
// MANI-2 is under individual ORAT → its wells don't count as group-controlled
// for the parent, but MANI-2 itself is excluded from PLAT's FLD children
{
const auto always_included = std::string("");
const auto is_prod = true;
const auto phase = Opm::Phase::OIL;
BOOST_CHECK_EQUAL(gsh.groupControlledWells("MANI", always_included, is_prod, phase), 2);
BOOST_CHECK_EQUAL(gsh.groupControlledWells("PLAT", always_included, is_prod, phase), 2);
BOOST_CHECK_EQUAL(gsh.groupControlledWells("FIELD", always_included, is_prod, phase), 3);
}
// Call getAutoChokeGroupProductionTargetRate()
// This is how it's called in BlackoilWellModelNetwork:
// getAutoChokeGroupProductionTargetRate(bottom_group=MANI, group=MANI, gsh, resv_coeff, 1.0)
// The function recurses upward from MANI (FLD) → PLAT (FLD) → FIELD (ORAT=controlling).
// During recursion: efficiencyFactor accumulates E_MANI * E_PLAT = 0.8 * 0.9 = 0.72.
auto resv_coeff = resvCoeff();
using WGC = Opm::WellGroupControls<double, IndexTraits>;
auto [target_rate_si, control_mode] = WGC::getAutoChokeGroupProductionTargetRate(
maniGroup(), // bottom_group
maniGroup(), // group (starts recursion here, recurses up to controlling group)
gsh,
resv_coeff,
/*efficiencyFactor=*/1.0
);
// Verify control mode is ORAT (from FIELD, the controlling group)
BOOST_CHECK(control_mode == Opm::Group::ProductionCMode::ORAT);
// ========================================================================
// EXPECTED COMPUTATION
// ========================================================================
//
// The function recurses from MANI (FLD) → PLAT (FLD) → FIELD (ORAT).
// During recursion, efficiencyFactor accumulates:
// 1.0 * E_MANI * E_PLAT = 0.8 * 0.9 = 0.72
// (Both MANI and PLAT multiply into efficiencyFactor before reaching FIELD.)
//
// chain = groupChainTopBot("MANI", "FIELD") = ["FIELD", "PLAT", "MANI"]
// num_ancestors = 2
// orig_target = 10000 SM3/day (FIELD's ORAT target)
//
// Loop ii=0 (FIELD):
// (ii==0) → apply reduction
// localReduction("FIELD") = 0 (no reduction at FIELD level)
// target = 10000 - 0 = 10000
// localFraction("PLAT"): PLAT=9500, PLAT-2=2550, both FLD → 9500/12050
// target = 10000 * 9500/12050 ≈ 7884.65
//
// Loop ii=1 (PLAT):
// guideRate->has("PLAT") = true → apply reduction
// localReduction("PLAT") = E_MANI2 * wellB_rate = 0.75 * 2000 = 1500
// target = 7884.65 - 1500 = 6384.65
// localFraction("MANI"): MANI is FLD with GCW=2, MANI-2 is ORAT (excluded) → 1.0
// target = 6384.65 * 1.0 = 6384.65
//
// Final: target / efficiencyFactor = 6384.65 / 0.72 ≈ 8867.57 SM3/day
//
// Code-path difference: in applyReductionsAndFractions_(), local_reduction_level
// gating and addback would apply at the intermediate PLAT level. Here, the autochoke
// loop applies reduction wherever guideRate->has() is true (PLAT), without addback.
// Both algorithms produce the same result here because MANI has no individual wells
// (reduction and addback would cancel if present).
//
const double eff = eMani() * ePlat(); // 0.72
const double plat_fraction = platGuideRateMetric()
/ (platGuideRateMetric() + plat2GuideRateMetric()); // 9500/12050
const double plat_reduction_metric = eMani2() * mani2TotalRateMetric(); // 1500
const double field_target_metric = 10000.0;
const double target_after_field = field_target_metric * plat_fraction; // ≈ 7884.65
const double target_after_plat = target_after_field - plat_reduction_metric; // ≈ 6384.65
const double mani_fraction = 1.0; // MANI is only FLD child at PLAT level
const double expected_target_metric = (target_after_plat * mani_fraction)
/ eff; // ≈ 8867.57
const double target_rate_metric = metric_rate(target_rate_si);
checkAlgo(target_rate_metric, expected_target_metric);
}
//! \brief Test getAutoChokeGroupProductionTargetRate() with GCW=0 (underperformance scenario)
//!
//! Uses the NETWORK DATA file where MANI is a sub-sea manifold (as_choke=true via NODEPROP).
//! NODEPROP switches MANI's wells to THP control and disables their guide rate availability.
//!
//! MANI's well rates are set low enough (total=5000) that isAutoChokeGroupUnderperforming_()
//! detects underperformance and updateGroupControlledWells() naturally sets MANI GCW=0.
//!
//! This test demonstrates the double-counting bug in the autochoke loop when an intermediate
//! group has a guide rate but GCW=0. The autochoke loop subtracts reductions unconditionally
//! at every level where guideRate->has() is true, without local_reduction_level gating.
//! When GCW=0 at MANI and PLAT, updateGroupTargetReductionRecursive_() propagates MANI's
//! total production into PLAT's reduction (case 1), which then propagates into FIELD's
//! reduction (case 1 again). The autochoke loop subtracts reduction(FIELD) at ii=0 and
//! reduction(PLAT) at ii=1, double-counting MANI's production.
//!
//! BUG: The buggy code gives target=0 (double-counted reductions make the target negative,
//! clamped to 0 by std::max at WellGroupControls.cpp:406).
//! FIX: With local_reduction_level gating, the correct target is ~7013.89 SM3/day.
//!
//! \code
//! FIELD (ORAT = 10000, GCW=1)
//! ├── PLAT (E=0.9, FLD, GUIDERATE=9500, GCW=0)
//! │ ├── MANI (E=0.8, FLD, GUIDERATE=8000, GCW=0, as_choke, underperforming)
//! │ │ ├── WELL-A (RATE=3000, POT=3000, THP)
//! │ │ └── WELL-A2 (RATE=2000, POT=2000, THP)
//! │ └── MANI-2 (E=0.75, IDV, ORAT, GUIDERATE=1500, GCW=1)
//! │ └── WELL-B (RATE=2000, POT=2000, GRUP)
//! └── PLAT-2 (E=0.85, FLD, GUIDERATE=2550, GCW=1)
//! └── WELL-C (RATE=3000, POT=3000, GRUP)
//! \endcode
BOOST_AUTO_TEST_CASE(TestAutoChokeGroupTargetRateDoubleCountingBug)
{
// Uses NETWORK DATA file: MANI has as_choke()=true from NODEPROP,
// wells under MANI are switched to THP control by the NODEPROP handler.
// Lower MANI well rates (3000+2000=5000) trigger underperformance detection
// in isAutoChokeGroupUnderperforming_(), which naturally sets MANI GCW=0.
commonSetupForAllTests(
/*plat_has_guide_rate=*/true,
/*mani_control =*/Opm::Group::ProductionCMode::FLD,
/*well_a_control =*/Opm::Well::ProducerCMode::THP,
/*well_a2_control =*/Opm::Well::ProducerCMode::THP,
/*mani2_control =*/Opm::Group::ProductionCMode::ORAT,
/*data_file =*/"GROUP_HIGHER_CONSTRAINTS_NETWORK.DATA",
/*well_a_rate_metric =*/3000.0,
/*well_a2_rate_metric=*/2000.0
);
// Verify NODEPROP correctly set as_choke() on MANI
BOOST_CHECK(maniGroup().as_choke());
BOOST_CHECK(!platGroup().as_choke());
auto& gs = groupState();
auto& gsh = groupStateHelper();
// finalizeSetup_() called updateGroupControlledWells() which runs
// isAutoChokeGroupUnderperforming_(): MANI (total=5000) is below its target
// share, so all wells are excluded → MANI GCW=0.
// With MANI GCW=0 and MANI-2 under ORAT (not FLD), PLAT GCW also becomes 0.
//
// Verify GCW values (naturally 0 from underperformance detection)
{
const auto always_included = std::string("");
const auto is_prod = true;
const auto phase = Opm::Phase::OIL;
BOOST_CHECK_EQUAL(gsh.groupControlledWells("MANI", always_included, is_prod, phase), 0);
BOOST_CHECK_EQUAL(gsh.groupControlledWells("PLAT", always_included, is_prod, phase), 0);
BOOST_CHECK_EQUAL(gsh.groupControlledWells("FIELD", always_included, is_prod, phase), 1);
}
// finalizeSetup_() called updateGroupTargetReduction() with
// GCW=0 at MANI and PLAT. updateGroupTargetReductionRecursive_() uses
// case 1 (total production propagated up) instead of case 3 (nothing):
//
// reduction(MANI) = 0 (wells are THP, and !group.as_choke() is false)
//
// reduction(PLAT):
// MANI (FLD, GCW=0): case 1 → E_MANI * sumWellSurfaceRates(MANI) = 0.8 * 5000 = 4000
// MANI-2 (ORAT): case 1 → E_MANI2 * sumWellSurfaceRates(MANI-2) = 0.75 * 2000 = 1500
// reduction(PLAT) = 5500
//
// reduction(FIELD):
// PLAT (FLD, GCW=0): case 1 → E_PLAT * sumWellSurfaceRates(PLAT) = 0.9 * 5500 = 4950
// PLAT-2 (FLD, GCW>0, guide rate): case 3 → nothing
// reduction(FIELD) = 4950
//
// Verify reduction rates
{
const auto& plat_red = gs.production_reduction_rates("PLAT");
const double plat_red_metric = eMani() * maniTotalRateMetric()
+ eMani2() * mani2TotalRateMetric(); // 4000 + 1500 = 5500
checkRate(metric_rate(plat_red[oilPhasePos()]), plat_red_metric);
const auto& field_red = gs.production_reduction_rates("FIELD");
const double field_red_metric = ePlat() * platTotalRateMetric(); // 0.9 * 5500 = 4950
checkRate(metric_rate(field_red[oilPhasePos()]), field_red_metric);
}
// Call getAutoChokeGroupProductionTargetRate
auto resv_coeff = resvCoeff();
using WGC = Opm::WellGroupControls<double, IndexTraits>;
auto [target_rate_si, control_mode] = WGC::getAutoChokeGroupProductionTargetRate(
maniGroup(), // bottom_group
maniGroup(), // group (starts recursion here)
gsh,
resv_coeff,
/*efficiencyFactor=*/1.0
);
BOOST_CHECK(control_mode == Opm::Group::ProductionCMode::ORAT);
// ========================================================================
// EXPECTED COMPUTATION (buggy code, no local_reduction_level gating)
// ========================================================================
//
// chain = [FIELD, PLAT, MANI], efficiencyFactor = E_MANI * E_PLAT = 0.72
//
// ii=0 (FIELD):
// reduction(FIELD) = 4950
// target = 10000 - 4950 = 5050
// localFraction("PLAT") = 1.0 (PLAT GCW=0 → excluded from sum,
// but PLAT-2 is the only active sibling → num_active=1 → return 1.0)
// target = 5050 * 1.0 = 5050
//
// ii=1 (PLAT):
// guideRate->has("PLAT") = true → apply reduction [BUG: should be gated]
// reduction(PLAT) = 5500
// target = 5050 - 5500 = -450 ← DOUBLE-COUNTED!
// localFraction("MANI") = 1.0 (MANI GCW=0, MANI-2 ORAT → num_active=0,
// total=0, potentials=0 → return 1.0)
// target = -450 * 1.0 = -450
//
// Final: max(0, -450 / 0.72) = 0.0
//
// The double-counting: reduction(FIELD) = 4950 already includes
// E_PLAT * sumWellSurfaceRates(PLAT) = 0.9 * 5500 = 4950
// which accounts for ALL of PLAT's production. Then reduction(PLAT) = 5500
// subtracts MANI's production AGAIN (via E_MANI * 5000 = 4000).
//
// With local_reduction_level gating (commit 2 fix):
// local_reduction_level = 0 (PLAT has GR but GCW=0 → doesn't qualify)
// ii=0: 0 >= 0 → true → target = 5050
// ii=1: 0 >= 1 → false → skip reduction → target stays 5050
// Final: max(0, 5050 / 0.72) ≈ 7013.89
//
const double target_rate_metric = metric_rate(target_rate_si);
// BUG: double-counting makes target negative, clamped to 0 by std::max
// After fix in commit 2, this should be updated to expect ~7013.89
const double expected_buggy_target = 0.0;
BOOST_CHECK_EQUAL(target_rate_metric, expected_buggy_target);
}
BOOST_AUTO_TEST_SUITE_END()