Merge pull request #2031 from bska/schedule-apply-wellpi

Apply WELPI CTF Scaling at Schedule Level
This commit is contained in:
Joakim Hove 2020-10-20 12:05:05 +02:00 committed by GitHub
commit bf2f049a4b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 586 additions and 88 deletions

View File

@ -106,12 +106,6 @@ namespace Opm
* New explicit well productivity/injectivity assignment.
*/
WELL_PRODUCTIVITY_INDEX = (1 << 16),
/*
* Well's internal WellConnections structure changed.
* Rerun WELPI scaling if applicable.
*/
WELL_CONNECTIONS_UPDATED = (1 << 17),
};
}

View File

@ -272,6 +272,7 @@ namespace Opm
RestartConfig& restart();
void applyAction(std::size_t reportStep, const Action::ActionX& action, const Action::Result& result);
void applyWellProdIndexScaling(const std::string& well_name, const std::size_t reportStep, const double scalingFactor);
int getNupcol(std::size_t reportStep) const;

View File

@ -123,7 +123,7 @@ namespace RestartIO {
void setComplnum(int compnum);
void scaleWellPi(double wellPi);
bool prepareWellPIScaling();
void applyWellPIScaling(const double scaleFactor);
bool applyWellPIScaling(const double scaleFactor);
void updateSegmentRST(int segment_number_arg,
double center_depth_arg);
void updateSegment(int segment_number_arg,

View File

@ -598,8 +598,11 @@ public:
bool updateHasProduced();
bool cmp_structure(const Well& other) const;
bool operator==(const Well& data) const;
bool hasSameConnectionsPointers(const Well& other) const;
void setInsertIndex(std::size_t index);
void applyWellProdIndexScaling(const double currentEffectivePI);
double getWellPIScalingFactor(const double currentEffectivePI) const;
void applyWellProdIndexScaling(const double scalingFactor,
std::vector<bool>& scalingApplicable);
template<class Serializer>
void serializeOp(Serializer& serializer)

View File

@ -111,8 +111,13 @@ namespace Opm {
/// Scale pertinent connections' CF value by supplied value. Scaling
/// factor typically derived from 'WELPI' input keyword and a dynamic
/// productivity index calculation.
void applyWellPIScaling(const double scaleFactor);
/// productivity index calculation. Applicability array specifies
/// whether or not a particular connection is exempt from scaling.
/// Empty array means "apply scaling to all eligible connections".
/// This array is updated on return (entries set to 'false' if
/// corresponding connection is not eligible).
void applyWellPIScaling(const double scaleFactor,
std::vector<bool>& scalingApplicable);
template<class Serializer>
void serializeOp(Serializer& serializer)

View File

@ -154,10 +154,8 @@ namespace {
auto connections = std::shared_ptr<WellConnections>( new WellConnections( well2->getConnections()));
connections->loadCOMPDAT(record, handlerContext.grid, handlerContext.fieldPropsManager);
if (well2->updateConnections(connections, handlerContext.grid, handlerContext.fieldPropsManager.get_int("PVTNUM"))) {
this->updateWell(well2, handlerContext.currentStep);
this->addWellGroupEvent(name, ScheduleEvents::WELL_CONNECTIONS_UPDATED, handlerContext.currentStep);
}
if (well2->updateConnections(connections, handlerContext.grid, handlerContext.fieldPropsManager.get_int("PVTNUM")))
this->updateWell(std::move(well2), handlerContext.currentStep);
this->addWellGroupEvent(name, ScheduleEvents::COMPLETION_CHANGE, handlerContext.currentStep);
}

View File

@ -17,16 +17,17 @@
along with OPM. If not, see <http://www.gnu.org/licenses/>.
*/
#include <algorithm>
#include <ctime>
#include <fnmatch.h>
#include <functional>
#include <iostream>
#include <optional>
#include <stdexcept>
#include <string>
#include <unordered_set>
#include <utility>
#include <vector>
#include <functional>
#include <fmt/format.h>
@ -666,10 +667,9 @@ private:
{
auto& dynamic_state = this->wells_static.at(wname);
auto well_ptr = std::make_shared<Well>( *dynamic_state[currentStep] );
if (well_ptr->handleWELOPEN(record, comp_status, action_mode)) {
if (well_ptr->handleWELOPEN(record, comp_status, action_mode))
// The updateWell call breaks test at line 825 and 831 in ScheduleTests
this->updateWell(well_ptr, currentStep);
}
this->updateWell(std::move(well_ptr), currentStep);
}
m_events.addEvent( ScheduleEvents::COMPLETION_CHANGE, currentStep );
@ -1499,6 +1499,38 @@ private:
}
}
void Schedule::applyWellProdIndexScaling(const std::string& well_name, const std::size_t reportStep, const double scalingFactor) {
auto wstat = this->wells_static.find(well_name);
if (wstat == this->wells_static.end())
return;
auto unique_well_instances = wstat->second.unique();
auto end = unique_well_instances.end();
auto start = std::lower_bound(unique_well_instances.begin(), end, reportStep,
[](const auto& time_well_pair, const auto lookup) -> bool
{
// time < reportStep
return time_well_pair.first < lookup;
});
if (start == end)
// Report step after last?
return;
// Relies on wells_static being OrderedMap<string, DynamicState<shared_ptr<>>>
// which means unique_well_instances is a vector<pair<report_step, shared_ptr<>>>
std::vector<bool> scalingApplicable;
auto wellPtr = start->second;
wellPtr->applyWellProdIndexScaling(scalingFactor, scalingApplicable);
for (; start != end; ++start)
if (! wellPtr->hasSameConnectionsPointers(*start->second)) {
wellPtr = start->second;
wellPtr->applyWellProdIndexScaling(scalingFactor, scalingApplicable);
}
}
RestartConfig& Schedule::restart() {
return this->restart_config;
}

View File

@ -256,11 +256,12 @@ const std::optional<std::pair<double, double>>& Connection::perf_range() const {
return update;
}
void Connection::applyWellPIScaling(const double scaleFactor) {
bool Connection::applyWellPIScaling(const double scaleFactor) {
if (! this->m_subject_to_welpi)
return;
return false;
this->scaleWellPi(scaleFactor);
return true;
}
std::string Connection::str() const {

View File

@ -821,12 +821,35 @@ const std::string& Well::name() const {
return this->wname;
}
bool Well::hasSameConnectionsPointers(const Well& other) const
{
// Note: This is *supposed* to be a pointer comparison. We need to know
// if the two connection structures represent the exact same object, not
// just if they have the same value.
return this->connections == other.connections;
}
void Well::setInsertIndex(std::size_t index) {
this->insert_index = index;
}
void Well::applyWellProdIndexScaling(const double currentEffectivePI) {
double Well::getWellPIScalingFactor(const double currentEffectivePI) const {
if (this->connections->empty())
// No connections for this well. Unexpected.
return 1.0;
if (!this->productivity_index)
// WELPI not activated. Nothing to do.
return 1.0;
if (this->productivity_index->pi_value == currentEffectivePI)
// No change in scaling.
return 1.0;
return this->productivity_index->pi_value / currentEffectivePI;
}
void Well::applyWellProdIndexScaling(const double scalingFactor, std::vector<bool>& scalingApplicable) {
if (this->connections->empty())
// No connections for this well. Unexpected.
return;
@ -835,11 +858,11 @@ void Well::applyWellProdIndexScaling(const double currentEffectivePI) {
// WELPI not activated. Nothing to do.
return;
if (this->productivity_index->pi_value == currentEffectivePI)
if (scalingFactor == 1.0)
// No change in scaling.
return;
this->connections->applyWellPIScaling(this->productivity_index->pi_value / currentEffectivePI);
this->connections->applyWellPIScaling(scalingFactor, scalingApplicable);
}
const WellConnections& Well::getConnections() const {

View File

@ -195,10 +195,17 @@ inline std::array< size_t, 3> directionIndices(const Opm::Connection::Direction
return update;
}
void WellConnections::applyWellPIScaling(const double scaleFactor)
void WellConnections::applyWellPIScaling(const double scaleFactor,
std::vector<bool>& scalingApplicable)
{
for (auto& conn : this->m_connections)
conn.applyWellPIScaling(scaleFactor);
scalingApplicable.resize(std::max(scalingApplicable.size(), this->m_connections.size()), true);
auto i = std::size_t{0};
for (auto& conn : this->m_connections) {
if (scalingApplicable[i])
scalingApplicable[i] = conn.applyWellPIScaling(scaleFactor);
++i;
}
}
void WellConnections::addConnection(int i, int j , int k ,

View File

@ -408,17 +408,26 @@ END
BOOST_CHECK_CLOSE(conn.CF(), expectCF, 1.0e-10);
}
connP.applyWellPIScaling(2.0); // No "prepare" -> no change.
for (const auto& conn : connP) {
BOOST_CHECK_CLOSE(conn.CF(), expectCF, 1.0e-10);
{
std::vector<bool> scalingApplicable;
connP.applyWellPIScaling(2.0, scalingApplicable); // No "prepare" -> no change.
for (const auto& conn : connP) {
BOOST_CHECK_CLOSE(conn.CF(), expectCF, 1.0e-10);
}
}
// All CFs scaled by factor 2.
BOOST_CHECK_MESSAGE( connP.prepareWellPIScaling(), "First call to prepareWellPIScaling must be a state change");
BOOST_CHECK_MESSAGE(!connP.prepareWellPIScaling(), "Second call to prepareWellPIScaling must NOT be a state change");
connP.applyWellPIScaling(2.0);
for (const auto& conn : connP) {
BOOST_CHECK_CLOSE(conn.CF(), 2.0*expectCF, 1.0e-10);
{
std::vector<bool> scalingApplicable;
connP.applyWellPIScaling(2.0, scalingApplicable); // No "prepare" -> no change.
for (const auto& conn : connP) {
BOOST_CHECK_CLOSE(conn.CF(), 2.0*expectCF, 1.0e-10);
}
}
// Reset CF -- simulating COMPDAT record (inactive cell)
@ -440,20 +449,27 @@ END
BOOST_CHECK_CLOSE(connP[2].CF(), 50.0*cp_rm3_per_db(), 1.0e-10);
// Should not apply to connection whose CF was manually specified
connP.applyWellPIScaling(2.0);
{
std::vector<bool> scalingApplicable;
connP.applyWellPIScaling(2.0, scalingApplicable);
BOOST_CHECK_CLOSE(connP[0].CF(), 4.0*expectCF , 1.0e-10);
BOOST_CHECK_CLOSE(connP[1].CF(), 4.0*expectCF , 1.0e-10);
BOOST_CHECK_CLOSE(connP[2].CF(), 50.0*cp_rm3_per_db(), 1.0e-10);
BOOST_CHECK_CLOSE(connP[0].CF(), 4.0*expectCF , 1.0e-10);
BOOST_CHECK_CLOSE(connP[1].CF(), 4.0*expectCF , 1.0e-10);
BOOST_CHECK_CLOSE(connP[2].CF(), 50.0*cp_rm3_per_db(), 1.0e-10);
}
// Prepare new scaling. Simulating new WELPI record.
// New scaling applies to all connections.
BOOST_CHECK_MESSAGE(connP.prepareWellPIScaling(), "Third call to prepareWellPIScaling must be a state change");
connP.applyWellPIScaling(2.0);
BOOST_CHECK_CLOSE(connP[0].CF(), 8.0*expectCF , 1.0e-10);
BOOST_CHECK_CLOSE(connP[1].CF(), 8.0*expectCF , 1.0e-10);
BOOST_CHECK_CLOSE(connP[2].CF(), 100.0*cp_rm3_per_db(), 1.0e-10);
{
std::vector<bool> scalingApplicable;
connP.applyWellPIScaling(2.0, scalingApplicable);
BOOST_CHECK_CLOSE(connP[0].CF(), 8.0*expectCF , 1.0e-10);
BOOST_CHECK_CLOSE(connP[1].CF(), 8.0*expectCF , 1.0e-10);
BOOST_CHECK_CLOSE(connP[2].CF(), 100.0*cp_rm3_per_db(), 1.0e-10);
}
// Reset CF -- simulating COMPDAT record (active cell)
connP.addConnection(8, 9, 1, // 10, 10, 2
@ -468,12 +484,16 @@ END
1);
BOOST_REQUIRE_EQUAL(connP.size(), std::size_t{4});
connP.applyWellPIScaling(2.0);
BOOST_CHECK_CLOSE(connP[0].CF(), 16.0*expectCF , 1.0e-10);
BOOST_CHECK_CLOSE(connP[1].CF(), 16.0*expectCF , 1.0e-10);
BOOST_CHECK_CLOSE(connP[2].CF(), 200.0*cp_rm3_per_db(), 1.0e-10);
BOOST_CHECK_CLOSE(connP[3].CF(), 50.0*cp_rm3_per_db(), 1.0e-10);
{
std::vector<bool> scalingApplicable;
connP.applyWellPIScaling(2.0, scalingApplicable);
BOOST_CHECK_CLOSE(connP[0].CF(), 16.0*expectCF , 1.0e-10);
BOOST_CHECK_CLOSE(connP[1].CF(), 16.0*expectCF , 1.0e-10);
BOOST_CHECK_CLOSE(connP[2].CF(), 200.0*cp_rm3_per_db(), 1.0e-10);
BOOST_CHECK_CLOSE(connP[3].CF(), 50.0*cp_rm3_per_db(), 1.0e-10);
}
const auto& grid = es.getInputGrid();
const auto actCells = Opm::ActiveGridCells {
@ -489,8 +509,12 @@ END
BOOST_CHECK_CLOSE(connP[1].CF(), 16.0*expectCF , 1.0e-10);
BOOST_CHECK_CLOSE(connP[2].CF(), 50.0*cp_rm3_per_db(), 1.0e-10);
connP.applyWellPIScaling(2.0);
BOOST_CHECK_CLOSE(connP[0].CF(), 32.0*expectCF , 1.0e-10);
BOOST_CHECK_CLOSE(connP[1].CF(), 32.0*expectCF , 1.0e-10);
BOOST_CHECK_CLOSE(connP[2].CF(), 50.0*cp_rm3_per_db(), 1.0e-10);
{
std::vector<bool> scalingApplicable;
connP.applyWellPIScaling(2.0, scalingApplicable);
BOOST_CHECK_CLOSE(connP[0].CF(), 32.0*expectCF , 1.0e-10);
BOOST_CHECK_CLOSE(connP[1].CF(), 32.0*expectCF , 1.0e-10);
BOOST_CHECK_CLOSE(connP[2].CF(), 50.0*cp_rm3_per_db(), 1.0e-10);
}
}

View File

@ -18,6 +18,7 @@
*/
#include <algorithm>
#include <cstddef>
#include <iostream>
#include <stdexcept>
@ -3779,10 +3780,15 @@ END
const auto expectCF = 100.0*cp_rm3_per_db();
auto wellP = sched.getWell("P", 0);
wellP.applyWellProdIndexScaling(2.7182818);
std::vector<bool> scalingApplicable;
wellP.applyWellProdIndexScaling(2.7182818, scalingApplicable);
for (const auto& conn : wellP.getConnections()) {
BOOST_CHECK_CLOSE(conn.CF(), expectCF, 1.0e-10);
}
for (const bool applicable : scalingApplicable) {
BOOST_CHECK_MESSAGE(! applicable, "No connection must be eligible for WELPI scaling");
}
}
// Apply WELPI after seeing WELPI data.
@ -3790,10 +3796,18 @@ END
const auto expectCF = (200.0 / 100.0) * 100.0*cp_rm3_per_db();
auto wellP = sched.getWell("P", 1);
wellP.applyWellProdIndexScaling(100.0*liquid_PI_unit());
const auto scalingFactor = wellP.getWellPIScalingFactor(100.0*liquid_PI_unit());
BOOST_CHECK_CLOSE(scalingFactor, 2.0, 1.0e-10);
std::vector<bool> scalingApplicable;
wellP.applyWellProdIndexScaling(scalingFactor, scalingApplicable);
for (const auto& conn : wellP.getConnections()) {
BOOST_CHECK_CLOSE(conn.CF(), expectCF, 1.0e-10);
}
for (const bool applicable : scalingApplicable) {
BOOST_CHECK_MESSAGE(applicable, "All connections must be eligible for WELPI scaling");
}
}
// Apply WELPI after new COMPDAT.
@ -3801,29 +3815,329 @@ END
const auto expectCF = (200.0 / 100.0) * 100.0*cp_rm3_per_db();
auto wellP = sched.getWell("P", 2);
wellP.applyWellProdIndexScaling(100.0*liquid_PI_unit());
const auto scalingFactor = wellP.getWellPIScalingFactor(100.0*liquid_PI_unit());
BOOST_CHECK_CLOSE(scalingFactor, 2.0, 1.0e-10);
std::vector<bool> scalingApplicable;
wellP.applyWellProdIndexScaling(scalingFactor, scalingApplicable);
const auto& connP = wellP.getConnections();
BOOST_CHECK_CLOSE(connP[0].CF(), expectCF , 1.0e-10);
BOOST_CHECK_CLOSE(connP[1].CF(), 50*cp_rm3_per_db(), 1.0e-10);
BOOST_CHECK_CLOSE(connP[2].CF(), expectCF , 1.0e-10);
BOOST_CHECK_MESSAGE(bool(scalingApplicable[0]), "Connection[0] must be eligible for WELPI scaling");
BOOST_CHECK_MESSAGE(! scalingApplicable[1] , "Connection[1] must NOT be eligible for WELPI scaling");
BOOST_CHECK_MESSAGE(bool(scalingApplicable[0]), "Connection[2] must be eligible for WELPI scaling");
}
BOOST_CHECK_MESSAGE(sched.hasWellGroupEvent("P", ScheduleEvents::WELL_CONNECTIONS_UPDATED, 0),
"Well P must have WELL_CONNECTIONS_UPDATED event at report step 0");
BOOST_CHECK_MESSAGE(!sched.hasWellGroupEvent("P", ScheduleEvents::WELL_CONNECTIONS_UPDATED, 1),
"Well P must NOT have WELL_CONNECTIONS_UPDATED event at report step 1");
BOOST_CHECK_MESSAGE(sched.hasWellGroupEvent("P", ScheduleEvents::WELL_CONNECTIONS_UPDATED, 2),
"Well P must have WELL_CONNECTIONS_UPDATED event at report step 2");
BOOST_CHECK_MESSAGE(!sched.hasWellGroupEvent("P", ScheduleEvents::WELL_CONNECTIONS_UPDATED, 3),
"Well P must NOT have WELL_CONNECTIONS_UPDATED event at report step 3");
BOOST_CHECK_MESSAGE(sched.hasWellGroupEvent("P", ScheduleEvents::WELL_PRODUCTIVITY_INDEX, 1),
"Must have WELL_PRODUCTIVITY_INDEX event at report step 1");
}
BOOST_AUTO_TEST_CASE(Schedule_ApplyWellProdIndexScaling) {
const auto deck = Parser{}.parseString(R"(RUNSPEC
START
7 OCT 2020 /
DIMENS
10 10 3 /
GRID
DXV
10*100.0 /
DYV
10*100.0 /
DZV
3*10.0 /
DEPTHZ
121*2000.0 /
PERMX
300*100.0 /
PERMY
300*100.0 /
PERMZ
300*10.0 /
PORO
300*0.3 /
SCHEDULE
WELSPECS -- 0
'P' 'G' 10 10 2005 'LIQ' /
/
COMPDAT
'P' 0 0 1 3 OPEN 1 100 /
/
TSTEP -- 1
10
/
WELPI -- 1
'P' 200.0 /
/
TSTEP -- 2
10
/
COMPDAT -- 2
'P' 0 0 2 2 OPEN 1 50 /
/
TSTEP -- 3
10
/
WELPI --3
'P' 50.0 /
/
TSTEP -- 4
10
/
COMPDAT -- 4
'P' 10 9 2 2 OPEN 1 100 1.0 3* 'Y' /
'P' 10 8 2 2 OPEN 1 75 1.0 3* 'Y' /
'P' 10 7 2 2 OPEN 1 25 1.0 3* 'Y' /
/
TSTEP -- 5
10
/
END
)");
const auto es = EclipseState{ deck };
auto sched = Schedule{ deck, es };
BOOST_REQUIRE_EQUAL(sched.getTimeMap().size(), std::size_t{6});
BOOST_REQUIRE_EQUAL(sched.getTimeMap().numTimesteps(), std::size_t{5});
BOOST_REQUIRE_EQUAL(sched.getTimeMap().last(), std::size_t{5});
BOOST_REQUIRE_MESSAGE(sched.hasWellGroupEvent("P", ScheduleEvents::Events::WELL_PRODUCTIVITY_INDEX, 1),
"Schedule must have WELL_PRODUCTIVITY_INDEX Event at report step 1");
BOOST_REQUIRE_MESSAGE(sched.hasWellGroupEvent("P", ScheduleEvents::Events::WELL_PRODUCTIVITY_INDEX, 3),
"Schedule must have WELL_PRODUCTIVITY_INDEX Event at report step 3");
auto getScalingFactor = [&sched](const std::size_t report_step, const double wellPI) -> double
{
return sched.getWell("P", report_step).getWellPIScalingFactor(wellPI);
};
auto applyWellPIScaling = [&sched](const std::size_t report_step, const double scalingFactor)
{
sched.applyWellProdIndexScaling("P", report_step, scalingFactor);
};
auto getConnections = [&sched](const std::size_t report_step)
{
return sched.getWell("P", report_step).getConnections();
};
// Apply WELPI scaling after end of time series => no change to CTFs
{
const auto report_step = std::size_t{1};
const auto scalingFactor = getScalingFactor(report_step, 100.0*liquid_PI_unit());
BOOST_CHECK_CLOSE(scalingFactor, 2.0, 1.0e-10);
applyWellPIScaling(1729, scalingFactor);
{
const auto expectCF = 100.0*cp_rm3_per_db();
const auto& conns = getConnections(0);
BOOST_REQUIRE_EQUAL(conns.size(), 3);
BOOST_CHECK_CLOSE(conns[0].CF(), expectCF, 1.0e-10);
BOOST_CHECK_CLOSE(conns[1].CF(), expectCF, 1.0e-10);
BOOST_CHECK_CLOSE(conns[2].CF(), expectCF, 1.0e-10);
}
{
const auto expectCF = 100.0*cp_rm3_per_db();
const auto& conns = getConnections(1);
BOOST_REQUIRE_EQUAL(conns.size(), 3);
BOOST_CHECK_CLOSE(conns[0].CF(), expectCF, 1.0e-10);
BOOST_CHECK_CLOSE(conns[1].CF(), expectCF, 1.0e-10);
BOOST_CHECK_CLOSE(conns[2].CF(), expectCF, 1.0e-10);
}
{
const auto expectCF = 100.0*cp_rm3_per_db();
const auto& conns = getConnections(2);
BOOST_REQUIRE_EQUAL(conns.size(), 3);
BOOST_CHECK_CLOSE(conns[0].CF(), expectCF, 1.0e-10);
BOOST_CHECK_CLOSE(conns[1].CF(), 50.0*cp_rm3_per_db(), 1.0e-10);
BOOST_CHECK_CLOSE(conns[2].CF(), expectCF, 1.0e-10);
}
{
const auto expectCF = 100.0*cp_rm3_per_db();
const auto& conns = getConnections(3);
BOOST_REQUIRE_EQUAL(conns.size(), 3);
BOOST_CHECK_CLOSE(conns[0].CF(), expectCF, 1.0e-10);
BOOST_CHECK_CLOSE(conns[1].CF(), 50.0*cp_rm3_per_db(), 1.0e-10);
BOOST_CHECK_CLOSE(conns[2].CF(), expectCF, 1.0e-10);
}
{
const auto& conns = getConnections(4);
BOOST_REQUIRE_EQUAL(conns.size(), 6);
BOOST_CHECK_CLOSE(conns[0].CF(), 100.0*cp_rm3_per_db(), 1.0e-10);
BOOST_CHECK_CLOSE(conns[1].CF(), 50.0*cp_rm3_per_db(), 1.0e-10);
BOOST_CHECK_CLOSE(conns[2].CF(), 100.0*cp_rm3_per_db(), 1.0e-10);
BOOST_CHECK_CLOSE(conns[3].CF(), 100.0*cp_rm3_per_db(), 1.0e-10);
BOOST_CHECK_CLOSE(conns[4].CF(), 75.0*cp_rm3_per_db(), 1.0e-10);
BOOST_CHECK_CLOSE(conns[5].CF(), 25.0*cp_rm3_per_db(), 1.0e-10);
}
}
// Apply WELPI scaling after first WELPI specification
{
const auto report_step = std::size_t{1};
const auto scalingFactor = getScalingFactor(report_step, 100.0*liquid_PI_unit());
BOOST_CHECK_CLOSE(scalingFactor, 2.0, 1.0e-10);
applyWellPIScaling(report_step, scalingFactor);
{
const auto expectCF = 100.0*cp_rm3_per_db();
const auto& conns = getConnections(0);
BOOST_REQUIRE_EQUAL(conns.size(), 3);
BOOST_CHECK_CLOSE(conns[0].CF(), expectCF, 1.0e-10);
BOOST_CHECK_CLOSE(conns[1].CF(), expectCF, 1.0e-10);
BOOST_CHECK_CLOSE(conns[2].CF(), expectCF, 1.0e-10);
}
{
const auto expectCF = 200.0*cp_rm3_per_db();
const auto& conns = getConnections(1);
BOOST_REQUIRE_EQUAL(conns.size(), 3);
BOOST_CHECK_CLOSE(conns[0].CF(), expectCF, 1.0e-10);
BOOST_CHECK_CLOSE(conns[1].CF(), expectCF, 1.0e-10);
BOOST_CHECK_CLOSE(conns[2].CF(), expectCF, 1.0e-10);
}
{
const auto expectCF = 200.0*cp_rm3_per_db();
const auto& conns = getConnections(2);
BOOST_REQUIRE_EQUAL(conns.size(), 3);
BOOST_CHECK_CLOSE(conns[0].CF(), expectCF, 1.0e-10);
BOOST_CHECK_CLOSE(conns[1].CF(), 50.0*cp_rm3_per_db(), 1.0e-10);
BOOST_CHECK_CLOSE(conns[2].CF(), expectCF, 1.0e-10);
}
{
const auto expectCF = 200.0*cp_rm3_per_db();
const auto& conns = getConnections(3);
BOOST_REQUIRE_EQUAL(conns.size(), 3);
BOOST_CHECK_CLOSE(conns[0].CF(), expectCF, 1.0e-10);
BOOST_CHECK_CLOSE(conns[1].CF(), 50.0*cp_rm3_per_db(), 1.0e-10);
BOOST_CHECK_CLOSE(conns[2].CF(), expectCF, 1.0e-10);
}
{
const auto expectCF = 200.0*cp_rm3_per_db();
const auto& conns = getConnections(4);
BOOST_REQUIRE_EQUAL(conns.size(), 6);
BOOST_CHECK_CLOSE(conns[0].CF(), expectCF, 1.0e-10);
BOOST_CHECK_CLOSE(conns[1].CF(), 50.0*cp_rm3_per_db(), 1.0e-10);
BOOST_CHECK_CLOSE(conns[2].CF(), expectCF, 1.0e-10);
BOOST_CHECK_CLOSE(conns[3].CF(), 100.0*cp_rm3_per_db(), 1.0e-10);
BOOST_CHECK_CLOSE(conns[4].CF(), 75.0*cp_rm3_per_db(), 1.0e-10);
BOOST_CHECK_CLOSE(conns[5].CF(), 25.0*cp_rm3_per_db(), 1.0e-10);
}
}
// Apply WELPI scaling after second WELPI specification
{
const auto report_step = std::size_t{3};
const auto scalingFactor = getScalingFactor(report_step, 200.0*liquid_PI_unit());
BOOST_CHECK_CLOSE(scalingFactor, 0.25, 1.0e-10);
applyWellPIScaling(report_step, scalingFactor);
{
const auto expectCF = 100.0*cp_rm3_per_db();
const auto& conns = getConnections(0);
BOOST_REQUIRE_EQUAL(conns.size(), 3);
BOOST_CHECK_CLOSE(conns[0].CF(), expectCF, 1.0e-10);
BOOST_CHECK_CLOSE(conns[1].CF(), expectCF, 1.0e-10);
BOOST_CHECK_CLOSE(conns[2].CF(), expectCF, 1.0e-10);
}
{
const auto expectCF = 200.0*cp_rm3_per_db();
const auto& conns = getConnections(1);
BOOST_REQUIRE_EQUAL(conns.size(), 3);
BOOST_CHECK_CLOSE(conns[0].CF(), expectCF, 1.0e-10);
BOOST_CHECK_CLOSE(conns[1].CF(), expectCF, 1.0e-10);
BOOST_CHECK_CLOSE(conns[2].CF(), expectCF, 1.0e-10);
}
{
const auto expectCF = 200.0*cp_rm3_per_db();
const auto& conns = getConnections(2);
BOOST_REQUIRE_EQUAL(conns.size(), 3);
BOOST_CHECK_CLOSE(conns[0].CF(), expectCF, 1.0e-10);
BOOST_CHECK_CLOSE(conns[1].CF(), 50.0*cp_rm3_per_db(), 1.0e-10);
BOOST_CHECK_CLOSE(conns[2].CF(), expectCF, 1.0e-10);
}
{
const auto expectCF = 50.0*cp_rm3_per_db();
const auto& conns = getConnections(3);
BOOST_REQUIRE_EQUAL(conns.size(), 3);
BOOST_CHECK_CLOSE(conns[0].CF(), expectCF, 1.0e-10);
BOOST_CHECK_CLOSE(conns[1].CF(), 0.25*expectCF, 1.0e-10);
BOOST_CHECK_CLOSE(conns[2].CF(), expectCF, 1.0e-10);
}
{
const auto expectCF = 50.0*cp_rm3_per_db();
const auto& conns = getConnections(4);
BOOST_REQUIRE_EQUAL(conns.size(), 6);
BOOST_CHECK_CLOSE(conns[0].CF(), expectCF, 1.0e-10);
BOOST_CHECK_CLOSE(conns[1].CF(), 0.25*expectCF, 1.0e-10);
BOOST_CHECK_CLOSE(conns[2].CF(), expectCF, 1.0e-10);
BOOST_CHECK_CLOSE(conns[3].CF(), 100.0*cp_rm3_per_db(), 1.0e-10);
BOOST_CHECK_CLOSE(conns[4].CF(), 75.0*cp_rm3_per_db(), 1.0e-10);
BOOST_CHECK_CLOSE(conns[5].CF(), 25.0*cp_rm3_per_db(), 1.0e-10);
}
}
}
void cmp_vector(const std::vector<double>&v1, const std::vector<double>& v2) {
BOOST_CHECK_EQUAL(v1.size(), v2.size());
@ -3848,4 +4162,3 @@ BOOST_AUTO_TEST_CASE(VFPPROD_SCALING) {
cmp_vector(gfr, vfp_table.getGFRAxis());
cmp_vector(alq, vfp_table.getALQAxis());
}

View File

@ -17,9 +17,11 @@
along with OPM. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdexcept>
#include <iostream>
#include <memory>
#include <optional>
#include <stdexcept>
#include <utility>
#define BOOST_TEST_MODULE WellTest
#include <boost/test/unit_test.hpp>
@ -1197,7 +1199,7 @@ COMPDAT
END
)");
using WellPI = Well::WellProductivityIndex;
using WellPIType = Well::WellProductivityIndex;
const auto es = EclipseState{ deck };
const auto sched = Schedule{ deck, es };
@ -1211,9 +1213,16 @@ END
}
// Simulate applying WELPI before WELPI keyword. No effect.
wellP.applyWellProdIndexScaling(2.7182818);
for (const auto& conn : wellP.getConnections()) {
BOOST_CHECK_CLOSE(conn.CF(), expectCF, 1.0e-10);
{
std::vector<bool> scalingApplicable;
wellP.applyWellProdIndexScaling(2.7182818, scalingApplicable);
for (const auto& conn : wellP.getConnections()) {
BOOST_CHECK_CLOSE(conn.CF(), expectCF, 1.0e-10);
}
for (const bool applicable : scalingApplicable) {
BOOST_CHECK_MESSAGE(! applicable, "No connection must be eligible for WELPI scaling");
}
}
// Simulate applying WELPI after seeing
@ -1223,37 +1232,125 @@ END
// /
//
// (ignoring units of measure)
BOOST_CHECK_MESSAGE(wellP.updateWellProductivityIndex(WellPI{ 2.0, Phase::GAS }),
BOOST_CHECK_MESSAGE(wellP.updateWellProductivityIndex(WellPIType{ 2.0, Phase::GAS }),
"First call to updateWellProductivityIndex() must be a state change");
BOOST_CHECK_MESSAGE(!wellP.updateWellProductivityIndex(WellPI{ 2.0, Phase::GAS }),
BOOST_CHECK_MESSAGE(!wellP.updateWellProductivityIndex(WellPIType{ 2.0, Phase::GAS }),
"Second call to updateWellProductivityIndex() must NOT be a state change");
// Want PI=2, but actual/effective PI=1 => scale CF by 2.0/1.0.
wellP.applyWellProdIndexScaling(1.0);
for (const auto& conn : wellP.getConnections()) {
BOOST_CHECK_CLOSE(conn.CF(), 2.0*expectCF, 1.0e-10);
{
const auto scalingFactor = wellP.getWellPIScalingFactor(1.0);
BOOST_CHECK_CLOSE(scalingFactor, 2.0, 1.0e-10);
std::vector<bool> scalingApplicable;
wellP.applyWellProdIndexScaling(scalingFactor, scalingApplicable);
for (const auto& conn : wellP.getConnections()) {
BOOST_CHECK_CLOSE(conn.CF(), 2.0*expectCF, 1.0e-10);
}
for (const bool applicable : scalingApplicable) {
BOOST_CHECK_MESSAGE(applicable, "All connections must be eligible for WELPI scaling");
}
}
// Repeated application of WELPI multiplies scaling factors.
wellP.applyWellProdIndexScaling(1.0);
for (const auto& conn : wellP.getConnections()) {
BOOST_CHECK_CLOSE(conn.CF(), 4.0*expectCF, 1.0e-10);
{
const auto scalingFactor = wellP.getWellPIScalingFactor(1.0);
BOOST_CHECK_CLOSE(scalingFactor, 2.0, 1.0e-10);
std::vector<bool> scalingApplicable;
wellP.applyWellProdIndexScaling(scalingFactor, scalingApplicable);
for (const auto& conn : wellP.getConnections()) {
BOOST_CHECK_CLOSE(conn.CF(), 4.0*expectCF, 1.0e-10);
}
for (const bool applicable : scalingApplicable) {
BOOST_CHECK_MESSAGE(applicable, "All connections must be eligible for WELPI scaling");
}
}
// New WELPI record does not reset the scaling factors
wellP.updateWellProductivityIndex(WellPI{ 3.0, Phase::GAS });
wellP.updateWellProductivityIndex(WellPIType{ 3.0, Phase::GAS });
for (const auto& conn : wellP.getConnections()) {
BOOST_CHECK_CLOSE(conn.CF(), 4.0*expectCF, 1.0e-10);
}
// Effective PI=desired PI => no scaling change
wellP.applyWellProdIndexScaling(3.0);
for (const auto& conn : wellP.getConnections()) {
BOOST_CHECK_CLOSE(conn.CF(), 4.0*expectCF, 1.0e-10);
{
const auto scalingFactor = wellP.getWellPIScalingFactor(3.0);
BOOST_CHECK_CLOSE(scalingFactor, 1.0, 1.0e-10);
std::vector<bool> scalingApplicable;
wellP.applyWellProdIndexScaling(scalingFactor, scalingApplicable);
for (const auto& conn : wellP.getConnections()) {
BOOST_CHECK_CLOSE(conn.CF(), 4.0*expectCF, 1.0e-10);
}
for (const bool applicable : scalingApplicable) {
BOOST_CHECK_MESSAGE(applicable, "All connections must be eligible for WELPI scaling");
}
}
BOOST_CHECK_MESSAGE(wellP.updateWellProductivityIndex(WellPI{ 3.0, Phase::OIL }),
BOOST_CHECK_MESSAGE(wellP.updateWellProductivityIndex(WellPIType{ 3.0, Phase::OIL }),
"Fourth call to updateWellProductivityIndex() must be a state change");
BOOST_CHECK_MESSAGE(!wellP.updateWellProductivityIndex(WellPI{ 3.0, Phase::OIL }),
BOOST_CHECK_MESSAGE(!wellP.updateWellProductivityIndex(WellPIType{ 3.0, Phase::OIL }),
"Fifth call to updateWellProductivityIndex() must NOT be a state change");
}
BOOST_AUTO_TEST_CASE(Has_Same_Connections_Pointers) {
const auto deck = Parser{}.parseString(R"(RUNSPEC
START
7 OCT 2020 /
DIMENS
10 10 3 /
GRID
DXV
10*100.0 /
DYV
10*100.0 /
DZV
3*10.0 /
DEPTHZ
121*2000.0 /
PERMX
300*100.0 /
PERMY
300*100.0 /
PERMZ
300*10.0 /
PORO
300*0.3 /
SCHEDULE
WELSPECS
'P' 'G' 10 10 2005 'LIQ' /
/
COMPDAT
'P' 0 0 1 3 OPEN 1 100 /
/
END
)");
const auto es = EclipseState{ deck };
const auto sched = Schedule{ deck, es };
const auto wellP = sched.getWell("P", 0);
auto wellQ = wellP;
BOOST_CHECK_MESSAGE(wellP.hasSameConnectionsPointers(wellQ),
"P and Q must have the same internal connections pointers");
auto connQ = std::make_shared<WellConnections>(wellP.getConnections());
wellQ.forceUpdateConnections(std::move(connQ));
BOOST_CHECK_MESSAGE(! wellP.hasSameConnectionsPointers(wellQ),
"P and Q must NOT have the same internal connections pointers "
"after forcibly updating the connections structure");
BOOST_CHECK_MESSAGE(wellP.getConnections() == wellQ.getConnections(),
"P and Q must have same WellConnections VALUE");
}