mirror of
https://github.com/OPM/opm-simulators.git
synced 2026-10-24 22:57:42 -05:00
1258 lines
59 KiB
C++
1258 lines
59 KiB
C++
// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
|
// vi: set et ts=4 sw=4 sts=4:
|
|
/*
|
|
This file is part of the Open Porous Media project (OPM).
|
|
|
|
OPM is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
OPM is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with OPM. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
Consult the COPYING file in the top-level source directory of this
|
|
module for the precise wording of the license and the list of
|
|
copyright holders.
|
|
*/
|
|
/*!
|
|
* \file
|
|
* \brief Test for efficiency factor handling, addback and guide rate fractions in group constraint checking
|
|
*
|
|
*
|
|
* Group hierarchy used in the test:
|
|
* \code
|
|
* FIELD (ORAT = 10000)
|
|
* / \
|
|
* / \
|
|
* PLAT (GEFAC=0.9) PLAT-2 (GEFAC=0.85)
|
|
* / \ |
|
|
* / \ WELL-C
|
|
* MANI (0.8) MANI-2 (0.75)
|
|
* / \ |
|
|
* WELL-A WELL-A2 WELL-B
|
|
* \endcode
|
|
*
|
|
* Guide rates (explicit from GCONPROD):
|
|
* MANI: 8000, MANI-2: 1500, PLAT: 9500, PLAT-2: 2550
|
|
*
|
|
* Fractions:
|
|
* MANI at PLAT: 8000 / 9500 ≈ 0.842
|
|
* PLAT at FIELD: 9500 / 12050 ≈ 0.788
|
|
*
|
|
*/
|
|
|
|
#define BOOST_TEST_MODULE GroupHigherConstraints
|
|
#include "SimulatorFixture.hpp"
|
|
#include "ToleranceAndUnitFixture.hpp"
|
|
|
|
#include <opm/input/eclipse/Schedule/Schedule.hpp>
|
|
#include <opm/input/eclipse/Schedule/Group/Group.hpp>
|
|
#include <opm/input/eclipse/Schedule/Group/GuideRate.hpp>
|
|
#include <opm/input/eclipse/Schedule/Group/GuideRateModel.hpp>
|
|
#include <opm/input/eclipse/Units/Units.hpp>
|
|
#include <opm/simulators/flow/FlowProblemBlackoil.hpp>
|
|
#include <opm/simulators/wells/BlackoilWellModel.hpp>
|
|
#include <opm/simulators/wells/WellState.hpp>
|
|
#include <opm/simulators/wells/GroupState.hpp>
|
|
#include <opm/simulators/wells/FractionCalculator.hpp>
|
|
#include <opm/simulators/wells/TargetCalculator.hpp>
|
|
|
|
#include <boost/test/unit_test.hpp>
|
|
|
|
namespace Opm::Properties::TTag {
|
|
struct TestEfficiencyFactorTypeTag {
|
|
using InheritsFrom = std::tuple<TestTypeTag>;
|
|
};
|
|
}
|
|
|
|
using SimulatorFixture = Opm::SimulatorFixture;
|
|
BOOST_GLOBAL_FIXTURE(SimulatorFixture);
|
|
|
|
namespace {
|
|
|
|
// Using shared ToleranceAndUnitFixture from ToleranceAndUnitFixture.hpp
|
|
|
|
//! \brief Test fixture for group constraint tests to avoid code duplication
|
|
//!
|
|
//! Tests can customize behavior via configuration options before calling setup().
|
|
struct GroupConstraintTestFixture : ToleranceAndUnitFixture
|
|
{
|
|
using TypeTag = Opm::Properties::TTag::TestEfficiencyFactorTypeTag;
|
|
using WellModel = Opm::BlackoilWellModel<TypeTag>;
|
|
using FluidSystem = Opm::GetPropType<TypeTag, Opm::Properties::FluidSystem>;
|
|
using IndexTraits = typename FluidSystem::IndexTraitsType;
|
|
|
|
void commonSetupForAllTests(
|
|
bool plat_has_guide_rate,
|
|
Opm::Group::ProductionCMode mani_control,
|
|
Opm::Well::ProducerCMode well_a_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_();
|
|
setupGroupState_();
|
|
setupGuideRates_();
|
|
finalizeSetup_();
|
|
}
|
|
|
|
// ========================================================================
|
|
// Accessors
|
|
// ========================================================================
|
|
|
|
double wellARateMetric() const { return well_a_rate_metric_; }
|
|
double wellA2RateMetric() const { return well_a2_rate_metric_; }
|
|
double wellBRateMetric() const { return well_b_rate_metric_; }
|
|
double wellCRateMetric() const { return well_c_rate_metric_; }
|
|
double wellARateSI() const { return si_rate(-well_a_rate_metric_); }
|
|
double wellA2RateSI() const { return si_rate(-well_a2_rate_metric_); }
|
|
double wellBRateSI() const { return si_rate(-well_b_rate_metric_); }
|
|
double wellCRateSI() const { return si_rate(-well_c_rate_metric_); }
|
|
WellModel& wellModel() { return simulator_->problem().wellModel(); }
|
|
const Opm::Schedule& schedule() const { return simulator_->vanguard().schedule(); }
|
|
auto& wellState() { return wellModel().wellState(); }
|
|
auto& groupState() { return wellModel().groupState(); }
|
|
Opm::GuideRate& guideRate() { return wellModel().guideRate(); }
|
|
auto& groupStateHelper() { return wellModel().groupStateHelper(); }
|
|
int reportStepIdx() const { return report_step_idx_; }
|
|
int oilPhasePos() const { return oil_phase_pos_; }
|
|
Opm::DeferredLogger& deferredLogger() { return deferred_logger_; }
|
|
|
|
const Opm::Group& maniGroup() const { return schedule().getGroup("MANI", report_step_idx_); }
|
|
const Opm::Group& mani2Group() const { return schedule().getGroup("MANI-2", report_step_idx_); }
|
|
const Opm::Group& platGroup() const { return schedule().getGroup("PLAT", report_step_idx_); }
|
|
const Opm::Group& plat2Group() const { return schedule().getGroup("PLAT-2", report_step_idx_); }
|
|
const Opm::Group& fieldGroup() const { return schedule().getGroup("FIELD", report_step_idx_); }
|
|
|
|
//! Efficiency factors from schedule
|
|
double eMani() const { return maniGroup().getGroupEfficiencyFactor(); }
|
|
double eMani2() const { return mani2Group().getGroupEfficiencyFactor(); }
|
|
double ePlat() const { return platGroup().getGroupEfficiencyFactor(); }
|
|
double ePlat2() const { return plat2Group().getGroupEfficiencyFactor(); }
|
|
|
|
//! Get MANI's total rate (SI units, positive for production)
|
|
double maniTotalRate() const { return -wellARateSI() - wellA2RateSI(); }
|
|
|
|
//! Get MANI's total rate (metric, positive for production)
|
|
double maniTotalRateMetric() const { return wellARateMetric() + wellA2RateMetric(); }
|
|
|
|
//! Get MANI-2's total rate (metric, positive for production)
|
|
double mani2TotalRateMetric() const { return wellBRateMetric(); }
|
|
|
|
//! Get PLAT's total rate (metric, positive for production)
|
|
double platTotalRateMetric() const { return eMani() * maniTotalRateMetric() + eMani2() * mani2TotalRateMetric(); }
|
|
|
|
//! Guide rates (from GCONPROD via guideRate object)
|
|
double maniGuideRateMetric() {
|
|
const Opm::GuideRate::RateVector zero_rates{0.0, 0.0, 0.0};
|
|
return guideRate().get("MANI", Opm::GuideRateModel::Target::OIL, zero_rates);
|
|
}
|
|
double mani2GuideRateMetric() {
|
|
const Opm::GuideRate::RateVector zero_rates{0.0, 0.0, 0.0};
|
|
return guideRate().get("MANI-2", Opm::GuideRateModel::Target::OIL, zero_rates);
|
|
}
|
|
double platGuideRateMetric() {
|
|
const Opm::GuideRate::RateVector zero_rates{0.0, 0.0, 0.0};
|
|
return guideRate().get("PLAT", Opm::GuideRateModel::Target::OIL, zero_rates);
|
|
}
|
|
double plat2GuideRateMetric() {
|
|
const Opm::GuideRate::RateVector zero_rates{0.0, 0.0, 0.0};
|
|
return guideRate().get("PLAT-2", Opm::GuideRateModel::Target::OIL, zero_rates);
|
|
}
|
|
|
|
// ========================================================================
|
|
// Helper methods for common test operations
|
|
// ========================================================================
|
|
|
|
//! Prepare rates array for MANI group (for checkGroupConstraintsProd)
|
|
std::vector<double> maniRatesArray() const
|
|
{
|
|
std::vector<double> rates(num_phases_, 0.0);
|
|
rates[oil_phase_pos_] = wellARateSI() + wellA2RateSI(); // negative
|
|
return rates;
|
|
}
|
|
|
|
//! Prepare rates array for WELL-A (for getWellGroupTargetProducer)
|
|
std::vector<double> wellARatesArray() const
|
|
{
|
|
std::vector<double> rates(num_phases_, 0.0);
|
|
rates[oil_phase_pos_] = wellARateSI(); // negative
|
|
return rates;
|
|
}
|
|
|
|
//! Get default resv_coeff (all 1.0)
|
|
std::vector<double> resvCoeff() const
|
|
{
|
|
return std::vector<double>(num_phases_, 1.0);
|
|
}
|
|
private:
|
|
//! Whether PLAT group has a guide rate (affects local_reduction_level)
|
|
bool plat_has_guide_rate_{true};
|
|
|
|
//! Control mode for WELL-A (GRUP or ORAT)
|
|
Opm::Well::ProducerCMode well_a_control_{Opm::Well::ProducerCMode::GRUP};
|
|
|
|
//! Control mode for WELL-A2 (GRUP or ORAT)
|
|
Opm::Well::ProducerCMode well_a2_control_{Opm::Well::ProducerCMode::GRUP};
|
|
|
|
//! 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};
|
|
double well_b_rate_metric_{2000.0};
|
|
double well_c_rate_metric_{3000.0};
|
|
|
|
std::unique_ptr<Opm::Simulator<TypeTag>> simulator_;
|
|
|
|
int report_step_idx_{0};
|
|
int num_phases_{0};
|
|
int oil_phase_pos_{0};
|
|
|
|
Opm::DeferredLogger deferred_logger_;
|
|
|
|
// ========================================================================
|
|
// Private setup methods
|
|
// ========================================================================
|
|
|
|
void setupSimulator_()
|
|
{
|
|
simulator_ = Opm::initSimulator<TypeTag>(data_file_.data());
|
|
|
|
simulator_->model().applyInitialSolution();
|
|
simulator_->setEpisodeIndex(-1);
|
|
simulator_->setEpisodeLength(0.0);
|
|
simulator_->startNextEpisode(/*episodeStartTime=*/0.0, /*episodeLength=*/1e30);
|
|
simulator_->setTimeStepSize(Opm::unit::day);
|
|
// Reset iteration context so code using problem().iterationContext() sees first iteration
|
|
simulator_->problem().resetIterationForNewTimestep();
|
|
|
|
wellModel().beginReportStep(report_step_idx_);
|
|
|
|
const auto& pu = wellModel().phaseUsage();
|
|
num_phases_ = pu.numActivePhases();
|
|
oil_phase_pos_ = pu.canonicalToActivePhaseIdx(IndexTraits::oilPhaseIdx);
|
|
}
|
|
|
|
void setupWellRates_()
|
|
{
|
|
auto& ws = wellModel().wellState();
|
|
|
|
// For simplicity, assume the same well potentials as rates
|
|
// Set WELL-A: surface_rates (negative) and well_potentials (positive)
|
|
{
|
|
auto well_idx = ws.index("WELL-A");
|
|
BOOST_REQUIRE(well_idx.has_value());
|
|
auto& well = ws.well(well_idx.value());
|
|
std::ranges::fill(well.surface_rates, 0.0);
|
|
std::ranges::fill(well.well_potentials, 0.0);
|
|
well.surface_rates[oil_phase_pos_] = wellARateSI();
|
|
well.well_potentials[oil_phase_pos_] = -wellARateSI(); // positive
|
|
well.production_cmode = well_a_control_;
|
|
}
|
|
|
|
// Set WELL-A2
|
|
{
|
|
auto well_idx = ws.index("WELL-A2");
|
|
BOOST_REQUIRE(well_idx.has_value());
|
|
auto& well = ws.well(well_idx.value());
|
|
std::ranges::fill(well.surface_rates, 0.0);
|
|
std::ranges::fill(well.well_potentials, 0.0);
|
|
well.surface_rates[oil_phase_pos_] = wellA2RateSI();
|
|
well.well_potentials[oil_phase_pos_] = -wellA2RateSI(); // positive
|
|
well.production_cmode = well_a2_control_;
|
|
}
|
|
|
|
// Set WELL-B
|
|
{
|
|
auto well_idx = ws.index("WELL-B");
|
|
BOOST_REQUIRE(well_idx.has_value());
|
|
auto& well = ws.well(well_idx.value());
|
|
std::ranges::fill(well.surface_rates, 0.0);
|
|
std::ranges::fill(well.well_potentials, 0.0);
|
|
well.surface_rates[oil_phase_pos_] = wellBRateSI();
|
|
well.well_potentials[oil_phase_pos_] = -wellBRateSI(); // positive
|
|
well.production_cmode = Opm::Well::ProducerCMode::GRUP;
|
|
}
|
|
|
|
// Set WELL-C
|
|
{
|
|
auto well_idx = ws.index("WELL-C");
|
|
BOOST_REQUIRE(well_idx.has_value());
|
|
auto& well = ws.well(well_idx.value());
|
|
std::ranges::fill(well.surface_rates, 0.0);
|
|
std::ranges::fill(well.well_potentials, 0.0);
|
|
well.surface_rates[oil_phase_pos_] = wellCRateSI();
|
|
well.well_potentials[oil_phase_pos_] = -wellCRateSI(); // positive
|
|
well.production_cmode = Opm::Well::ProducerCMode::GRUP;
|
|
}
|
|
|
|
// Update global well info
|
|
std::vector<Opm::Well::Status> well_status(ws.size(), Opm::Well::Status::OPEN);
|
|
const auto& comm = simulator_->vanguard().grid().comm();
|
|
ws.updateGlobalIsGrup(comm, well_status);
|
|
}
|
|
|
|
void setupGuideRates_()
|
|
{
|
|
// Use updateGuideRates() to compute guide rates from:
|
|
// - Explicit values in GCONPROD for MANI (8000), MANI-2 (1500), PLAT (9500), PLAT-2 (2550)
|
|
// - GUIDERAT formula for wells (guide_rate = oil_potential)
|
|
//
|
|
// The explicit GCONPROD values ensure predictable guide rate fractions:
|
|
// MANI fraction at PLAT = 8000 / 9500 ≈ 0.842
|
|
// PLAT fraction at FIELD = 9500 / 12050 ≈ 0.788
|
|
const double sim_time = 0.0;
|
|
wellModel().updateGuideRates(report_step_idx_, sim_time);
|
|
|
|
// If test requires PLAT to NOT have a guide rate (to test local_reduction_level=0),
|
|
// erase it after updateGuideRates() has set it from GCONPROD
|
|
if (!plat_has_guide_rate_) {
|
|
guideRate().erase("PLAT");
|
|
}
|
|
}
|
|
|
|
void setupGroupState_()
|
|
{
|
|
auto& gs = wellModel().groupState();
|
|
auto& gsh = wellModel().groupStateHelper();
|
|
|
|
// Compute group production rates from well surface rates
|
|
// This applies efficiency factors at each level (see sumWellPhaseRates):
|
|
// MANI: sum of wells = -10000 (WELL-A + WELL-A2)
|
|
// MANI-2: sum of wells = -2000 (WELL-B)
|
|
// PLAT: E_MANI * MANI + E_MANI2 * MANI-2 = 0.8 * -10000 + 0.75 * -2000 = -9500
|
|
// PLAT-2: sum of wells = -3000 (WELL-C, no child groups)
|
|
// FIELD: E_PLAT * PLAT + E_PLAT2 * PLAT-2 = 0.9 * -9500 + 0.85 * -3000 = -11100
|
|
gsh.updateGroupProductionRates(fieldGroup());
|
|
|
|
// Set group controls
|
|
gs.production_control("MANI", mani_control_);
|
|
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);
|
|
}
|
|
|
|
void finalizeSetup_()
|
|
{
|
|
auto& gsh = wellModel().groupStateHelper();
|
|
const auto& field_group = schedule().getGroup("FIELD", report_step_idx_);
|
|
|
|
gsh.updateGroupControlledWells(/*is_production_group=*/true, Opm::Phase::OIL);
|
|
gsh.updateGroupTargetReduction(field_group, /*is_injector=*/false);
|
|
}
|
|
};
|
|
|
|
} // anonymous namespace
|
|
|
|
BOOST_FIXTURE_TEST_SUITE(GroupHigherConstraintsTests, GroupConstraintTestFixture)
|
|
|
|
//! \brief Test checkGroupConstraintsProd() for group MANI with a guide rate at PLAT level between MANI and FIELD
|
|
//!
|
|
//! When there's a guide rate at PLAT level between MANI and FIELD, local_reduction_level is 1
|
|
//! and the addback happens at PLAT level. This test verifies that an addback efficiency factor of 0.8 is
|
|
//! applied to PLAT (local_reduction_level = 1) and not 0.8 * 0.9 = 0.72. (original version of
|
|
//! checkGroupConstraintsProd() had this bug)
|
|
//! \code
|
|
//! (GCW = Group Controlled Wells, IDV = Individual control)
|
|
//!
|
|
//! FIELD (ORAT = 10000, GCW=2)
|
|
//! ├── PLAT (E=0.9, FLD, GUIDERATE=9500, GCW=1, local_reduction_level=1)
|
|
//! │ ├── MANI (E=0.8, IDV, ORAT, GUIDERATE=8000, GCW=2)
|
|
//! │ │ ├── WELL-A (RATE=6000, POT=6000, GRUP)
|
|
//! │ │ └── WELL-A2 (RATE=4000, POT=4000, GRUP)
|
|
//! │ └── MANI-2 (E=0.75, FLD, 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(TestGroupHigherConstraintsWithGuideRate)
|
|
{
|
|
// Configuration: Guide rate for PLAT (local_reduction_level = 1)
|
|
commonSetupForAllTests(/*plat_has_guide_rate=*/true,
|
|
/*mani_control=*/Opm::Group::ProductionCMode::ORAT,
|
|
/*well_a_control=*/Opm::Well::ProducerCMode::GRUP,
|
|
/*well_a2_control=*/Opm::Well::ProducerCMode::GRUP);
|
|
|
|
// Verify the test setup
|
|
auto& gsh = groupStateHelper();
|
|
auto& ws = wellState();
|
|
auto& gs = groupState();
|
|
|
|
// Verify efficiency factors from schedule
|
|
checkClose(eMani(), 0.8);
|
|
checkClose(eMani2(), 0.75);
|
|
checkClose(ePlat(), 0.9);
|
|
checkClose(ePlat2(), 0.85);
|
|
const double accumulated_eff = eMani() * ePlat();
|
|
checkClose(accumulated_eff, 0.72);
|
|
|
|
// Verify well rates in WellState
|
|
checkRate(metric_rate(ws.well("WELL-A").surface_rates[oilPhasePos()]), -wellARateMetric()); // -6000.0
|
|
checkRate(metric_rate(ws.well("WELL-A2").surface_rates[oilPhasePos()]), -wellA2RateMetric()); // -4000.0
|
|
checkRate(metric_rate(ws.well("WELL-B").surface_rates[oilPhasePos()]), -wellBRateMetric()); // -2000.0
|
|
checkRate(metric_rate(ws.well("WELL-C").surface_rates[oilPhasePos()]), -wellCRateMetric()); // -3000.0
|
|
|
|
// Verify well potentials (stored in SI, convert to metric for comparison)
|
|
checkRate(metric_rate(ws.well("WELL-A").well_potentials[oilPhasePos()]), wellARateMetric()); // 6000.0
|
|
checkRate(metric_rate(ws.well("WELL-A2").well_potentials[oilPhasePos()]), wellA2RateMetric()); // 4000.0
|
|
checkRate(metric_rate(ws.well("WELL-B").well_potentials[oilPhasePos()]), wellBRateMetric()); // 2000.0
|
|
checkRate(metric_rate(ws.well("WELL-C").well_potentials[oilPhasePos()]), wellCRateMetric()); // 3000.0
|
|
|
|
// Verify wells are marked as under group control
|
|
BOOST_CHECK(ws.isProductionGrup("WELL-A"));
|
|
BOOST_CHECK(ws.isProductionGrup("WELL-A2"));
|
|
BOOST_CHECK(ws.isProductionGrup("WELL-B"));
|
|
BOOST_CHECK(ws.isProductionGrup("WELL-C"));
|
|
|
|
// Verify guide rates are set from GCONPROD via updateGuideRates()
|
|
// Wells use GUIDERAT formula (guide_rate = oil_potential), groups use explicit GCONPROD values
|
|
BOOST_CHECK(guideRate().has("WELL-A"));
|
|
BOOST_CHECK(guideRate().has("WELL-A2"));
|
|
BOOST_CHECK(guideRate().has("WELL-B"));
|
|
BOOST_CHECK(guideRate().has("WELL-C"));
|
|
BOOST_CHECK(guideRate().has("MANI"));
|
|
BOOST_CHECK(guideRate().has("MANI-2"));
|
|
BOOST_CHECK(guideRate().has("PLAT"));
|
|
BOOST_CHECK(guideRate().has("PLAT-2"));
|
|
|
|
// Verify group guide rates (explicit values from GCONPROD)
|
|
const Opm::GuideRate::RateVector zero_rates{0.0, 0.0, 0.0};
|
|
checkClose(guideRate().get("MANI", Opm::GuideRateModel::Target::OIL, zero_rates), maniGuideRateMetric());
|
|
checkClose(guideRate().get("MANI-2", Opm::GuideRateModel::Target::OIL, zero_rates), mani2GuideRateMetric());
|
|
checkClose(guideRate().get("PLAT", Opm::GuideRateModel::Target::OIL, zero_rates), platGuideRateMetric());
|
|
checkClose(guideRate().get("PLAT-2", Opm::GuideRateModel::Target::OIL, zero_rates), plat2GuideRateMetric());
|
|
|
|
// Verify local fractions using FractionCalculator
|
|
// FractionCalculator only includes groups/wells that:
|
|
// 1. Have FLD or NONE control (for groups) or GRUP control (for wells)
|
|
// 2. Have groupControlledWells > 0
|
|
{
|
|
using FCalc = Opm::GroupStateHelpers::FractionCalculator<double, IndexTraits>;
|
|
FCalc fcalc{schedule(), gsh, gsh.summaryState(), reportStepIdx(),
|
|
&guideRate(), Opm::GuideRateModel::Target::OIL,
|
|
/*is_producer=*/true, /*injection_phase=*/Opm::Phase::OIL};
|
|
|
|
auto localFraction = [&](const std::string& name) {
|
|
return fcalc.localFraction(name, /*always_included_child=*/"");
|
|
};
|
|
|
|
// Well fractions within MANI (both wells under GRUP control):
|
|
// WELL-A guide rate = oil_potential = 6000
|
|
// WELL-A2 guide rate = oil_potential = 4000
|
|
// WELL-A fraction = 6000 / (6000 + 4000) = 0.6
|
|
// WELL-A2 fraction = 4000 / (6000 + 4000) = 0.4
|
|
const double well_a_frac = localFraction("WELL-A");
|
|
const double well_a2_frac = localFraction("WELL-A2");
|
|
checkClose(well_a_frac, wellARateMetric() / (wellARateMetric() + wellA2RateMetric())); // 0.6
|
|
checkClose(well_a2_frac, wellA2RateMetric() / (wellARateMetric() + wellA2RateMetric())); // 0.4
|
|
checkClose(well_a_frac + well_a2_frac, 1.0);
|
|
|
|
// WELL-B fraction within MANI-2 (only well):
|
|
const double well_b_frac = localFraction("WELL-B");
|
|
checkClose(well_b_frac, 1.0); // Only well in MANI-2
|
|
|
|
// Group fractions within PLAT:
|
|
// MANI has ORAT control (not FLD), so it's excluded from parent's sum
|
|
// MANI-2 has FLD control and 1 group-controlled well (WELL-B) -> included
|
|
// Since MANI-2 is the only FLD child of PLAT, both return 1.0
|
|
const double mani_frac = localFraction("MANI");
|
|
const double mani2_frac = localFraction("MANI-2");
|
|
checkClose(mani_frac, 1.0); // MANI excluded (ORAT control), only MANI-2 active
|
|
checkClose(mani2_frac, 1.0); // Only active group at PLAT level
|
|
|
|
// Group fractions at FIELD level:
|
|
// PLAT has FLD control and 1 group-controlled well (via MANI-2) -> included
|
|
// PLAT-2 has FLD control and 1 group-controlled well (WELL-C) -> included
|
|
// Both compete for rate allocation: PLAT=9500, PLAT-2=2550
|
|
const double plat_frac = localFraction("PLAT");
|
|
const double plat2_frac = localFraction("PLAT-2");
|
|
// PLAT fraction = PLAT guide rate / (PLAT guide rate + PLAT-2 guide rate) = 9500/12050 = 0.788
|
|
checkClose(plat_frac, platGuideRateMetric() / (platGuideRateMetric() + plat2GuideRateMetric()));
|
|
// PLAT-2 fraction = PLAT-2 guide rate / (PLAT guide rate + PLAT-2 guide rate) = 2550/12050 = 0.212
|
|
checkClose(plat2_frac, plat2GuideRateMetric() / (platGuideRateMetric() + plat2GuideRateMetric()));
|
|
checkClose(plat_frac + plat2_frac, 1.0);
|
|
|
|
// WELL-C fraction within PLAT-2 (only well):
|
|
const double well_c_frac = localFraction("WELL-C");
|
|
checkClose(well_c_frac, 1.0); // Only well in PLAT-2
|
|
}
|
|
|
|
// Verify groupControlledWells count
|
|
{
|
|
const auto always_included_child = std::string("");
|
|
const auto is_production_group = true;
|
|
const auto injection_phase = Opm::Phase::OIL;
|
|
BOOST_CHECK_EQUAL(gsh.groupControlledWells(
|
|
"MANI", always_included_child, is_production_group, injection_phase), 2);
|
|
BOOST_CHECK_EQUAL(gsh.groupControlledWells(
|
|
"MANI-2", always_included_child, is_production_group, injection_phase), 1);
|
|
BOOST_CHECK_EQUAL(gsh.groupControlledWells(
|
|
"PLAT-2", always_included_child, is_production_group, injection_phase), 1);
|
|
BOOST_CHECK_EQUAL(gsh.groupControlledWells(
|
|
"PLAT", always_included_child, is_production_group, injection_phase), 1); // via MANI-2
|
|
BOOST_CHECK_EQUAL(gsh.groupControlledWells(
|
|
"FIELD", always_included_child, is_production_group, injection_phase), 2); // PLAT + PLAT-2
|
|
}
|
|
// Test sumWellSurfaceRates for each group
|
|
{
|
|
const double sum_mani = gsh.sumWellSurfaceRates(maniGroup(), oilPhasePos(), /*injector=*/false);
|
|
const double sum_mani2 = gsh.sumWellSurfaceRates(mani2Group(), oilPhasePos(), /*injector=*/false);
|
|
const double sum_plat2 = gsh.sumWellSurfaceRates(plat2Group(), oilPhasePos(), /*injector=*/false);
|
|
const double sum_plat = gsh.sumWellSurfaceRates(platGroup(), oilPhasePos(), /*injector=*/false);
|
|
// WELL-A + WELL-A2 = 6000.0 + 4000.0 = 10000.0
|
|
checkRate(metric_rate(sum_mani), wellARateMetric() + wellA2RateMetric()); // 10000.0
|
|
checkRate(metric_rate(sum_mani2), wellBRateMetric()); // WELL-B = 2000.0
|
|
checkRate(metric_rate(sum_plat2), wellCRateMetric()); // WELL-C = 3000.0
|
|
checkRate(metric_rate(sum_plat), platTotalRateMetric()); // E_MANI*10000 + E_MANI2*2000 = 9500.0
|
|
}
|
|
// Test FIELD's ORAT target
|
|
{
|
|
const auto& field_group_ctrl = fieldGroup().productionControls(gsh.summaryState());
|
|
checkRate(metric_rate(field_group_ctrl.oil_target), 10000.0);
|
|
}
|
|
|
|
// Test production control modes from schedule (GCONPROD)
|
|
{
|
|
const auto& field_ctrl = fieldGroup().productionControls(gsh.summaryState());
|
|
BOOST_CHECK(field_ctrl.cmode == Opm::Group::ProductionCMode::ORAT);
|
|
const auto& plat_ctrl = platGroup().productionControls(gsh.summaryState());
|
|
BOOST_CHECK(plat_ctrl.cmode == Opm::Group::ProductionCMode::FLD);
|
|
const auto& plat2_ctrl = plat2Group().productionControls(gsh.summaryState());
|
|
BOOST_CHECK(plat2_ctrl.cmode == Opm::Group::ProductionCMode::FLD);
|
|
const auto& mani_ctrl = maniGroup().productionControls(gsh.summaryState());
|
|
BOOST_CHECK(mani_ctrl.cmode == Opm::Group::ProductionCMode::ORAT);
|
|
const auto& mani2_ctrl = mani2Group().productionControls(gsh.summaryState());
|
|
BOOST_CHECK(mani2_ctrl.cmode == Opm::Group::ProductionCMode::FLD);
|
|
}
|
|
|
|
// Test production control modes from GroupState (set by setupGroupState())
|
|
{
|
|
BOOST_CHECK(gs.production_control("FIELD") == Opm::Group::ProductionCMode::ORAT);
|
|
BOOST_CHECK(gs.production_control("PLAT") == Opm::Group::ProductionCMode::FLD);
|
|
BOOST_CHECK(gs.production_control("PLAT-2") == Opm::Group::ProductionCMode::FLD);
|
|
BOOST_CHECK(gs.production_control("MANI") == Opm::Group::ProductionCMode::ORAT);
|
|
BOOST_CHECK(gs.production_control("MANI-2") == Opm::Group::ProductionCMode::FLD);
|
|
}
|
|
|
|
// Verify group reduction rates
|
|
// PLAT: E_MANI * 10000 = 8000 (MANI has individual ORAT control, MANI-2 is FLD so doesn't add)
|
|
// FIELD: 0 (PLAT has guide rate AND group-controlled wells via MANI-2, so reduction doesn't propagate)
|
|
// Note: With MANI-2 under PLAT, PLAT now has group-controlled wells, so the else branch in
|
|
// updateGroupTargetReductionRecursive_ is taken. Since PLAT has a guide rate, its
|
|
// reduction is NOT accumulated to FIELD.
|
|
{
|
|
const auto& field_red = gs.production_reduction_rates("FIELD");
|
|
checkRate(metric_rate(field_red[oilPhasePos()]), 0.0); // 0 because PLAT has guide rate and group-controlled wells
|
|
const auto& plat_red = gs.production_reduction_rates("PLAT");
|
|
checkRate(metric_rate(plat_red[oilPhasePos()]), eMani() * maniTotalRateMetric()); // 8000.0
|
|
const auto& plat2_red = gs.production_reduction_rates("PLAT-2");
|
|
checkRate(metric_rate(plat2_red[oilPhasePos()]), 0.0);
|
|
const auto& mani_red = gs.production_reduction_rates("MANI");
|
|
checkRate(metric_rate(mani_red[oilPhasePos()]), 0.0); // 0.0 since both WELL-A and WELL-A2 are under group control
|
|
const auto& mani2_red = gs.production_reduction_rates("MANI-2");
|
|
checkRate(metric_rate(mani2_red[oilPhasePos()]), 0.0); // 0.0 since WELL-B is under group control
|
|
}
|
|
|
|
|
|
// Call checkGroupConstraintsProd
|
|
auto mani_rates = maniRatesArray();
|
|
auto resv_coeff = resvCoeff();
|
|
const double initial_efficiency = eMani();
|
|
|
|
auto [constraint_violated, scale] = gsh.checkGroupConstraintsProd(
|
|
"MANI",
|
|
"PLAT",
|
|
platGroup(),
|
|
mani_rates.data(),
|
|
initial_efficiency,
|
|
resv_coeff,
|
|
/*check_guide_rate=*/true
|
|
);
|
|
|
|
// ========================================================================
|
|
// EXPECTED RESULT (with PLAT having guide rate -> local_reduction_level = 1)
|
|
// ========================================================================
|
|
//
|
|
// The checkGroupConstraintsProd algorithm proceeds as follows:
|
|
//
|
|
// Setup:
|
|
// - Chain: [FIELD, PLAT, MANI]
|
|
// - efficiency_factor = E_MANI * E_PLAT = 0.8 * 0.9 = 0.72
|
|
// - current_rate_available = 10000 SM3/day (MANI's production)
|
|
// - orig_target = 10000 SM3/day (FIELD's ORAT)
|
|
// - local_reduction_level = 1 (PLAT is under FLD control AND has guide rate AND num_gr_ctrl > 0)
|
|
//
|
|
// Loop iteration ii=0 (FIELD):
|
|
// - reduction_fn("FIELD") = 0 (FIELD has no local reduction)
|
|
// - target = 10000 - 0 = 10000
|
|
// - No addback (ii != local_reduction_level)
|
|
// - PLAT fraction at FIELD = 9500 / (9500 + 2550) ≈ 0.788
|
|
// - target = 10000 * 0.788 ≈ 7884.65
|
|
//
|
|
// Loop iteration ii=1 (PLAT):
|
|
// - Reduction condition: local_reduction_level==1, ii==1, reduction applied
|
|
// - reduction_fn("PLAT") = E_MANI * 10000 = 8000 SM3/day (MANI has ORAT control)
|
|
// - target = 7884.65 - 8000 ≈ -115.35
|
|
//
|
|
// - Addback (ii == local_reduction_level==1):
|
|
// - addback_efficiency = E_MANI = 0.8
|
|
// - target += current_rate * 0.8 = -115.35 + 10000 * 0.8 = -115.35 + 8000 ≈ 7884.65
|
|
//
|
|
// - MANI fraction at PLAT = 8000 / (8000 + 1500) ≈ 0.842
|
|
// - target = 7884.65 * 0.842 ≈ 6640.07
|
|
//
|
|
// Final calculation:
|
|
// target_rate_available = target / efficiency_factor
|
|
// = 6640.07 / 0.72 ≈ 9222.32
|
|
//
|
|
// Scale factor:
|
|
// scale = target_rate_available / current_rate_available
|
|
// = 9222.32 / 10000 ≈ 0.922
|
|
//
|
|
// The constraint is violated (scale < 1.0) because MANI's share of FIELD's target
|
|
// (after applying PLAT and MANI fractions) is less than its current production.
|
|
//
|
|
// Expected: scale ≈ 0.922 (constraint violated)
|
|
const double expected_scale = 0.92208390963577669;
|
|
BOOST_CHECK(constraint_violated); // Constraint IS violated with new hierarchy
|
|
checkAlgo(scale, expected_scale);
|
|
|
|
// Derived values for verification
|
|
const double current_rate = 10000.0; // SM3/day
|
|
const double target_rate_available = scale * current_rate;
|
|
checkAlgo(target_rate_available, expected_scale * current_rate);
|
|
}
|
|
|
|
//! \brief Test checkGroupConstraintsProd() for group MANI with no guide rate at PLAT level between MANI and FIELD
|
|
//!
|
|
//! In this test, since PLAT has no guide rate and not zero group-controlled wells (it has 1 group-controlled well,
|
|
//! namely WELL-B via MANI-2), local_reduction_level stays at 0 and the addback happens at FIELD level instead of
|
|
//! at PLAT level.
|
|
//! \code
|
|
//! FIELD (ORAT = 10000, local_reduction_level=0)
|
|
//! ├── PLAT (E=0.9, FLD, NO GUIDERATE, GCW=1)
|
|
//! │ ├── MANI (E=0.8, IDV, ORAT, GUIDERATE=8000, GCW=2)
|
|
//! │ │ ├── WELL-A (RATE=6000, POT=6000, GRUP)
|
|
//! │ │ └── WELL-A2 (RATE=4000, POT=4000, GRUP)
|
|
//! │ └── MANI-2 (E=0.75, FLD, 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(TestGroupHigherConstraintsProdWithoutGuideRate)
|
|
{
|
|
// Configuration: No guide rate for PLAT (local_reduction_level = 0)
|
|
commonSetupForAllTests(
|
|
/*plat_has_guide_rate=*/false,
|
|
/*mani_control =*/Opm::Group::ProductionCMode::ORAT,
|
|
/*well_a_control =*/Opm::Well::ProducerCMode::GRUP,
|
|
/*well_a2_control =*/Opm::Well::ProducerCMode::GRUP
|
|
);
|
|
|
|
auto& gsh = groupStateHelper();
|
|
auto& gs = groupState();
|
|
|
|
// Verify PLAT has no guide rate
|
|
BOOST_CHECK(!guideRate().has("PLAT"));
|
|
|
|
// Verify reduction rates
|
|
// Without guide rate at PLAT, MANI's reduction bubbles up to FIELD
|
|
// But PLAT still has group-controlled wells via MANI-2 (FLD control)
|
|
// -> So PLAT goes to else branch in updateGroupTargetReductionRecursive_()
|
|
// -> Since PLAT has no guide rate, MANI's reduction propagates to FIELD
|
|
const auto& field_red = gs.production_reduction_rates("FIELD");
|
|
// FIELD reduction = E_PLAT * E_MANI * 10000 (MANI has individual control)
|
|
const double expected_field_reduction = ePlat() * eMani() * maniTotalRateMetric();
|
|
checkRate(metric_rate(field_red[oilPhasePos()]), expected_field_reduction);
|
|
|
|
// Call checkGroupConstraintsProd()
|
|
auto mani_rates = maniRatesArray();
|
|
auto resv_coeff = resvCoeff();
|
|
const double initial_efficiency = eMani();
|
|
|
|
auto [constraint_violated, scale] = gsh.checkGroupConstraintsProd(
|
|
"MANI",
|
|
"PLAT",
|
|
platGroup(),
|
|
mani_rates.data(),
|
|
initial_efficiency,
|
|
resv_coeff,
|
|
/*check_guide_rate=*/false
|
|
);
|
|
|
|
// With local_reduction_level = 0 (no guide rate at PLAT):
|
|
// - Chain: [FIELD, PLAT, MANI]
|
|
// - MANI total = 10000 SM3/day (WELL-A=6000 + WELL-A2=4000)
|
|
// - efficiency_factor = E_MANI * E_PLAT = 0.8 * 0.9 = 0.72
|
|
//
|
|
// At FIELD (ii=0):
|
|
// - reduction = E_PLAT * E_MANI * 10000 = 0.9 * 0.8 * 10000 = 7200
|
|
// - addback = current_rate * E_MANI * E_PLAT = 10000 * 0.72 = 7200
|
|
// - target = 10000 - 7200 + 7200 = 10000
|
|
// - PLAT fraction: Without guide rate, PLAT uses production-based fraction
|
|
// PLAT production = 9500, PLAT-2 production = 2550 (via E_PLAT2 * 3000)
|
|
//
|
|
// The algorithm produces scale = 0.9009 and constraint_violated = true.
|
|
//
|
|
// This differs from test TestGroupHigherConstraintsProdWithGuideRate (scale = 0.922) because:
|
|
// - Without PLAT guide rate, the fraction calculation changes
|
|
// - PLAT-2 still has guide rate (2550), so it participates in FIELD fraction
|
|
// - PLAT without guide rate may get a different fraction or be excluded
|
|
//
|
|
const double expected_scale = 0.90090090090090058;
|
|
BOOST_CHECK(constraint_violated); // scale < 1.0 means constraint violated
|
|
checkAlgo(scale, expected_scale);
|
|
}
|
|
|
|
//! \brief Test getWellGroupTargetProducer() for a WELL-A (under individual control)
|
|
//!
|
|
//! In this test, WELL-A is under individual ORAT control, so the addback is applied because the well's rate
|
|
//! is included in the local reduction. The controlling group is FIELD (the first parent with individual control)
|
|
//! and the addback happens at MANI level.
|
|
//! \code
|
|
//! FIELD (ORAT = 10000)
|
|
//! ├── PLAT (E=0.9, GUIDERATE=9500, GCW=1)
|
|
//! │ ├── MANI (E=0.8, FLD, GUIDERATE=8000, GCW=2)
|
|
//! │ │ ├── WELL-A (RATE=6000, POT=6000, ORAT)
|
|
//! │ │ └── WELL-A2 (RATE=4000, POT=4000, GRUP)
|
|
//! │ └── MANI-2 (E=0.75, FLD, 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(TestWellGroupTargetProducerIndividualControl)
|
|
{
|
|
// Configuration: WELL-A under individual ORAT control, MANI follows parent
|
|
commonSetupForAllTests(/*plat_has_guide_rate=*/true,
|
|
/*mani_control=*/Opm::Group::ProductionCMode::FLD,
|
|
/*well_a_control=*/Opm::Well::ProducerCMode::ORAT,
|
|
/*well_a2_control=*/Opm::Well::ProducerCMode::GRUP);
|
|
|
|
auto& gsh = groupStateHelper();
|
|
auto& ws = wellState();
|
|
|
|
// Verify WELL-A is NOT under GRUP control
|
|
BOOST_CHECK(!ws.isProductionGrup("WELL-A"));
|
|
BOOST_CHECK(ws.isProductionGrup("WELL-A2"));
|
|
BOOST_CHECK(ws.isProductionGrup("WELL-C"));
|
|
|
|
// Get well efficiency factor (default 1.0)
|
|
const auto& well_a_ecl = schedule().getWell("WELL-A", reportStepIdx());
|
|
const double well_a_eff = well_a_ecl.getEfficiencyFactor();
|
|
checkClose(well_a_eff, 1.0);
|
|
|
|
// Call getWellGroupTargetProducer
|
|
auto well_a_rates = wellARatesArray();
|
|
auto resv_coeff = resvCoeff();
|
|
|
|
auto target_opt = gsh.getWellGroupTargetProducer(
|
|
"WELL-A",
|
|
"MANI",
|
|
maniGroup(),
|
|
well_a_rates.data(),
|
|
well_a_eff,
|
|
resv_coeff
|
|
);
|
|
|
|
BOOST_REQUIRE(target_opt.has_value());
|
|
|
|
// For getWellGroupTargetProducer with WELL-A under individual control:
|
|
// - Chain: [FIELD, PLAT, MANI, WELL-A]
|
|
// - efficiency_factor accumulates: E_WELL * E_MANI * E_PLAT = 1.0 * 0.8 * 0.9 = 0.72
|
|
// - local_reduction_level = 2 (MANI has guide rate AND GCW=1)
|
|
// (Both PLAT and MANI have guide rates and GCW > 0, so loop ends at ii=2)
|
|
// - should_addback = true (WELL-A is not under GRUP)
|
|
//
|
|
// Reduction at MANI = E_WELL * 6000 = 6000 (WELL-A is individual, E_WELL=1.0)
|
|
// Addback for WELL-A = 6000 * E_WELL = 6000 (at local_reduction_level=2)
|
|
// Net effect: reduction - addback = 0 (they cancel out!)
|
|
//
|
|
// WELL-A fraction in MANI = 6000 / (6000 + 4000) = 0.6
|
|
// PLAT fraction at FIELD = 9500 / (9500 + 2550) ≈ 0.788
|
|
// MANI fraction at PLAT = 8000 / (8000 + 1500) ≈ 0.842
|
|
// (MANI has FLD control in this test, so it competes with MANI-2)
|
|
//
|
|
// Since reduction and addback cancel out, the result is just:
|
|
// Final: target / efficiency = 10000 * (9500/12050) * (8000/9500) * 0.6 / 0.72
|
|
// = 10000 * 8000 / 12050 * 0.6 / 0.72 = 5532.50
|
|
const double target = target_opt->target_value;
|
|
const double plat_fraction = platGuideRateMetric() / (platGuideRateMetric() + plat2GuideRateMetric());
|
|
const double mani_fraction = maniGuideRateMetric() / (maniGuideRateMetric() + mani2GuideRateMetric());
|
|
const double well_a_fraction = 6.0 / 10.0;
|
|
const double expected_target_metric = 10000.0 * plat_fraction * mani_fraction * well_a_fraction / 0.72; // 5532.50
|
|
checkAlgo(metric_rate(target), expected_target_metric);
|
|
}
|
|
|
|
//! \brief Test getWellGroupTargetProducer for a well under GRUP control
|
|
//!
|
|
//! When a well is under GRUP control, no addback is applied because
|
|
//! the well's rate is not individually counted in the reduction.
|
|
//! In this test, WELL-A is under GRUP control, the controlling group is FIELD, and no addback is applied
|
|
//! at the MANI level because the well is under GRUP control.
|
|
//! \code
|
|
//! FIELD (ORAT = 10000)
|
|
//! ├── PLAT (E=0.9, FLD, GUIDERATE=9500, GCW=1)
|
|
//! │ ├── MANI (E=0.8, FLD, GUIDERATE=8000, GCW=2)
|
|
//! │ │ ├── WELL-A (RATE=6000, POT=6000, GRUP)
|
|
//! │ │ └── WELL-A2 (RATE=4000, POT=4000, GRUP)
|
|
//! │ └── MANI-2 (E=0.75, FLD, 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(TestWellGroupTargetProducerGrupControl)
|
|
{
|
|
// Configuration: All wells under GRUP control, MANI follows parent
|
|
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
|
|
);
|
|
|
|
auto& gsh = groupStateHelper();
|
|
auto& ws = wellState();
|
|
|
|
// Verify all wells are under GRUP control
|
|
BOOST_CHECK(ws.isProductionGrup("WELL-A"));
|
|
BOOST_CHECK(ws.isProductionGrup("WELL-A2"));
|
|
BOOST_CHECK(ws.isProductionGrup("WELL-C"));
|
|
|
|
// Get well efficiency factor (default 1.0)
|
|
const auto& well_a_ecl = schedule().getWell("WELL-A", reportStepIdx());
|
|
const double well_a_eff = well_a_ecl.getEfficiencyFactor();
|
|
checkClose(well_a_eff, 1.0);
|
|
|
|
// Call getWellGroupTargetProducer
|
|
auto well_a_rates = wellARatesArray();
|
|
auto resv_coeff = resvCoeff();
|
|
|
|
auto target_opt = gsh.getWellGroupTargetProducer(
|
|
"WELL-A",
|
|
"MANI",
|
|
maniGroup(),
|
|
well_a_rates.data(),
|
|
well_a_eff,
|
|
resv_coeff
|
|
);
|
|
|
|
BOOST_REQUIRE(target_opt.has_value());
|
|
const double target = target_opt->target_value;
|
|
|
|
// For a well under GRUP control:
|
|
// - local_reduction_level = 2 (MANI has guide rate AND GCW=2)
|
|
// - No addback (should_addback = false because isProductionGrup is true)
|
|
// - No reduction either (both WELL-A and WELL-A2 are under GRUP)
|
|
// - The fraction used is for the well within its group
|
|
//
|
|
// With MANI under FLD control, MANI-2 as sibling (also FLD):
|
|
// - MANI fraction at PLAT = MANI_guide / (MANI_guide + MANI2_guide) = 8000/9500 ≈ 0.842
|
|
// - PLAT fraction at FIELD = 9500 / (9500 + 2550) ≈ 0.788
|
|
// - WELL-A fraction in MANI = 6000 / (6000 + 4000) = 0.6
|
|
//
|
|
// Since reduction = 0 and addback = 0, the result is just:
|
|
// Final: target / efficiency = 10000 * (9500/12050) * (8000/9500) * 0.6 / 0.72
|
|
// = 10000 * 8000 / 12050 * 0.6 / 0.72 ≈ 5532.50
|
|
//
|
|
// NOTE: This produces the same result as test TestWellGroupTargetProducerIndividualControl because:
|
|
// - TestWellGroupTargetProducerIndividualControl: reduction = 6000, addback = 6000 → net effect = 0
|
|
// - TestWellGroupTargetProducerGrupControl: reduction = 0, addback = 0 → net effect = 0
|
|
// In both cases, the reduction and addback cancel out,
|
|
// so the final target depends only on the fractions and efficiency factor.
|
|
const double plat_fraction = platGuideRateMetric() / (platGuideRateMetric() + plat2GuideRateMetric());
|
|
const double mani_fraction = maniGuideRateMetric() / (maniGuideRateMetric() + mani2GuideRateMetric());
|
|
const double well_a_fraction = 6.0 / 10.0;
|
|
const double expected_target_metric = 10000.0 * plat_fraction * mani_fraction * well_a_fraction / 0.72;
|
|
checkAlgo(metric_rate(target), expected_target_metric);
|
|
}
|
|
|
|
//! \brief Test getWellGroupTargetProducer with sibling well under individual control
|
|
//!
|
|
//! When WELL-A is under GRUP control but WELL-A2 is under individual ORAT control,
|
|
//! the reduction includes WELL-A2's rate but not WELL-A's. Since WELL-A is under GRUP,
|
|
//! no addback is applied. This creates a scenario where reduction != addback, producing
|
|
//! a different result than TestWellGroupTargetProducerIndividualControl and
|
|
//! TestWellGroupTargetProducerGrupControl.
|
|
//! \code
|
|
//! FIELD (ORAT = 10000)
|
|
//! ├── PLAT (E=0.9, FLD, GUIDERATE=9500, GCW=1)
|
|
//! │ ├── MANI (E=0.8, FLD, GUIDERATE=8000, GCW=2)
|
|
//! │ │ ├── WELL-A (RATE=6000, POT=6000, GRUP)
|
|
//! │ │ └── WELL-A2 (RATE=4000, POT=4000, ORAT)
|
|
//! │ └── MANI-2 (E=0.75, FLD, 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(TestWellGroupTargetProducerWithSiblingIndividualControl)
|
|
{
|
|
// Configuration: WELL-A under GRUP, WELL-A2 under individual ORAT control
|
|
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::ORAT);
|
|
|
|
auto& gsh = groupStateHelper();
|
|
auto& ws = wellState();
|
|
|
|
// Verify control modes
|
|
BOOST_CHECK(ws.isProductionGrup("WELL-A")); // WELL-A under GRUP
|
|
BOOST_CHECK(!ws.isProductionGrup("WELL-A2")); // WELL-A2 under individual control
|
|
BOOST_CHECK(ws.isProductionGrup("WELL-C"));
|
|
|
|
// Get well efficiency factor (default 1.0)
|
|
const auto& well_a_ecl = schedule().getWell("WELL-A", reportStepIdx());
|
|
const double well_a_eff = well_a_ecl.getEfficiencyFactor();
|
|
checkClose(well_a_eff, 1.0);
|
|
|
|
// Call getWellGroupTargetProducer for WELL-A
|
|
auto well_a_rates = wellARatesArray();
|
|
auto resv_coeff = resvCoeff();
|
|
|
|
auto target_opt = gsh.getWellGroupTargetProducer(
|
|
"WELL-A",
|
|
"MANI",
|
|
maniGroup(),
|
|
well_a_rates.data(),
|
|
well_a_eff,
|
|
resv_coeff
|
|
);
|
|
|
|
BOOST_REQUIRE(target_opt.has_value());
|
|
const double target = target_opt->target_value;
|
|
|
|
// For getWellGroupTargetProducer() with WELL-A under GRUP but WELL-A2 under individual:
|
|
// - Chain: [FIELD, PLAT, MANI, WELL-A]
|
|
// - efficiency_factor = E_WELL * E_MANI * E_PLAT = 1.0 * 0.8 * 0.9 = 0.72
|
|
// - local_reduction_level = 2 (MANI has guide rate AND GCW=1)
|
|
// - should_addback = false (WELL-A is under GRUP)
|
|
//
|
|
// Reduction at MANI = E_WELL * 4000 = 4000 (only WELL-A2 is individual, E_WELL=1.0)
|
|
// Addback for WELL-A = 0 (WELL-A is under GRUP, no addback)
|
|
//
|
|
// Fractions:
|
|
// - PLAT fraction at FIELD = 9500 / 12050 ≈ 0.788
|
|
// - MANI fraction at PLAT = 8000 / 9500 ≈ 0.842
|
|
// - WELL-A fraction at MANI = 1.0 because WELL-A is the always_included_child
|
|
// (When is_grup=true, fraction_fn uses chain[ii+1] as always_included_child,
|
|
// so WELL-A always gets fraction 1.0 regardless of sibling wells)
|
|
//
|
|
// Algorithm (with local_reduction_level = 2):
|
|
// ii=0 (FIELD): reduction = 0, target = 10000 * 0.788 = 7884.65
|
|
// ii=1 (PLAT): reduction = 0, target = 7884.65 * 0.842 = 6640.07
|
|
// ii=2 (MANI): reduction = 4000 (WELL-A2 under ORAT)
|
|
// target = 6640.07 - 4000 = 2640.07
|
|
// No addback (WELL-A is under GRUP)
|
|
// target *= 1.0 (WELL-A is always_included_child)
|
|
//
|
|
// Final: target / efficiency = 2640.07 / 0.72 = 3666.76 ≈ 3665.28
|
|
//
|
|
// This is DIFFERENT from TestWellGroupTargetProducerIndividualControl (5532.50)
|
|
// and TestWellGroupTargetProducerGrupControl (5532.50) because:
|
|
// - Reduction at MANI = 4000 (WELL-A2's rate)
|
|
// - Addback = 0 (WELL-A is under GRUP)
|
|
// - Net effect = -4000 (target reduced)
|
|
//
|
|
// Expected value derived from actual algorithm output:
|
|
const double expected_target_metric = 3665.2835408022129;
|
|
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:
|
|
// gsh.getAutoChokeGroupProductionTargetRate(bottom_group=MANI, group=MANI, 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();
|
|
|
|
auto [target_rate_si, control_mode] = gsh.getAutoChokeGroupProductionTargetRate(
|
|
maniGroup(), // bottom_group
|
|
maniGroup(), // group (starts recursion here, recurses up to controlling group)
|
|
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.
|
|
//! With GCW=0 at MANI and PLAT, updateGroupTargetReductionRecursive_() uses case 1 (total
|
|
//! production propagated up) instead of case 3 (guide rate distribution). The
|
|
//! local_reduction_level gating in getAutoChokeGroupProductionTargetRate() ensures that
|
|
//! the PLAT-level reduction is skipped (since PLAT has GCW=0 and therefore
|
|
//! local_reduction_level=0 < ii=1), avoiding double-counting of reductions that were
|
|
//! already propagated into FIELD's reduction via case 1.
|
|
//!
|
|
//! \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(TestAutoChokeGroupTargetRateWithUnderperformance)
|
|
{
|
|
// 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();
|
|
auto [target_rate_si, control_mode] = gsh.getAutoChokeGroupProductionTargetRate(
|
|
maniGroup(), // bottom_group
|
|
maniGroup(), // group (starts recursion here)
|
|
resv_coeff,
|
|
/*efficiencyFactor=*/1.0
|
|
);
|
|
|
|
BOOST_CHECK(control_mode == Opm::Group::ProductionCMode::ORAT);
|
|
|
|
// ========================================================================
|
|
// EXPECTED COMPUTATION
|
|
// ========================================================================
|
|
//
|
|
// chain = [FIELD, PLAT, MANI], efficiencyFactor = E_MANI * E_PLAT = 0.72
|
|
// local_reduction_level = 0 (PLAT has guide rate but GCW=0 → doesn't qualify)
|
|
//
|
|
// ii=0 (FIELD):
|
|
// local_reduction_level(0) >= ii(0) → true → apply reduction
|
|
// 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):
|
|
// local_reduction_level(0) >= ii(1) → false → skip reduction
|
|
// (PLAT's reduction was already propagated into FIELD's via case 1)
|
|
// localFraction("MANI") = 1.0 (MANI GCW=0, MANI-2 ORAT → num_active=0,
|
|
// total=0, potentials=0 → return 1.0)
|
|
// target = 5050 * 1.0 = 5050
|
|
//
|
|
// Final: max(0, 5050 / 0.72) ≈ 7013.89
|
|
//
|
|
const double target_rate_metric = metric_rate(target_rate_si);
|
|
const double expected_target = 5050.0 / 0.72; // ≈ 7013.89
|
|
checkAlgo(target_rate_metric, expected_target);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_SUITE_END()
|