Merge pull request #1446 from bska/currctrl-restart-support

Add Restart Infrastructure for Well's Active Control
This commit is contained in:
Joakim Hove
2020-02-06 16:35:46 +01:00
committed by GitHub
5 changed files with 272 additions and 50 deletions

View File

@@ -30,6 +30,8 @@
#include <unordered_map>
#include <vector>
#include <opm/parser/eclipse/EclipseState/Schedule/Well/Well.hpp>
namespace Opm {
namespace data {
@@ -167,6 +169,31 @@ namespace Opm {
void read(MessageBufferType& buffer);
};
struct CurrentControl {
bool isProducer{true};
::Opm::Well::ProducerCMode prod {
::Opm::Well::ProducerCMode::CMODE_UNDEFINED
};
::Opm::Well::InjectorCMode inj {
::Opm::Well::InjectorCMode::CMODE_UNDEFINED
};
bool operator==(const CurrentControl& rhs) const
{
return (this->isProducer == rhs.isProducer)
&& ((this->isProducer && (this->prod == rhs.prod)) ||
(!this->isProducer && (this->inj == rhs.inj)));
}
template <class MessageBufferType>
void write(MessageBufferType& buffer) const;
template <class MessageBufferType>
void read(MessageBufferType& buffer);
};
struct Well {
Rates rates;
double bhp;
@@ -175,6 +202,8 @@ namespace Opm {
int control;
std::vector< Connection > connections;
std::unordered_map<std::size_t, Segment> segments;
CurrentControl current_control;
inline bool flowing() const noexcept;
template <class MessageBufferType>
void write(MessageBufferType& buffer) const;
@@ -189,7 +218,8 @@ namespace Opm {
temperature == well2.temperature &&
control == well2.control &&
connections == well2.connections &&
segments == well2.segments;
segments == well2.segments &&
current_control == well2.current_control;
}
};
@@ -401,6 +431,18 @@ namespace Opm {
buffer.write(this->pressure);
}
template <class MessageBufferType>
void CurrentControl::write(MessageBufferType& buffer) const
{
buffer.write(this->isProducer);
if (this->isProducer) {
buffer.write(this->prod);
}
else {
buffer.write(this->inj);
}
}
template <class MessageBufferType>
void Well::write(MessageBufferType& buffer) const {
this->rates.write(buffer);
@@ -422,6 +464,8 @@ namespace Opm {
seg.second.write(buffer);
}
}
this->current_control.write(buffer);
}
template <class MessageBufferType>
@@ -465,6 +509,18 @@ namespace Opm {
buffer.read(this->pressure);
}
template <class MessageBufferType>
void CurrentControl::read(MessageBufferType& buffer)
{
buffer.read(this->isProducer);
if (this->isProducer) {
buffer.read(this->prod);
}
else {
buffer.read(this->inj);
}
}
template <class MessageBufferType>
void Well::read(MessageBufferType& buffer) {
this->rates.read(buffer);
@@ -499,6 +555,8 @@ namespace Opm {
const auto segNumber = seg.segNumber;
this->segments.emplace(segNumber, std::move(seg));
}
this->current_control.read(buffer);
}
}} // Opm::data

View File

@@ -1,4 +1,5 @@
/*
Copyright 2019-2020 Equinor ASA
Copyright 2018 Statoil ASA
This file is part of the Open Porous Media project (OPM).
@@ -77,20 +78,14 @@ namespace {
// Remove leading/trailing blanks.
return s.substr(b, e - b + 1);
}
template <typename WellOp>
void wellLoop(const std::vector<Opm::Well>& wells,
WellOp&& wellOp)
WellOp&& wellOp)
{
for (auto nWell = wells.size(), wellID = 0*nWell;
wellID < nWell; ++wellID)
{
const auto& well = wells[wellID];
wellOp(well, wellID);
auto wellID = 0*wells.size();
for (const auto& well : wells) {
wellOp(well, wellID++);
}
}
@@ -142,7 +137,7 @@ namespace {
int wellType(const Opm::Well& well, const Opm::SummaryState& st)
{
using WTypeVal = ::Opm::RestartIO::Helpers::VectorItems::IWell::Value::WellType;
using WTypeVal = VI::IWell::Value::WellType;
if (well.isProducer()) {
return WTypeVal::Producer;
@@ -170,7 +165,7 @@ namespace {
int ctrlMode(const Opm::Well& well, const Opm::SummaryState& st)
{
using WMCtrlVal = ::Opm::RestartIO::Helpers::VectorItems::IWell::Value::WellCtrlMode;
using WMCtrlVal = VI::IWell::Value::WellCtrlMode;
if (well.isInjector()) {
const auto& controls = well.injectionControls(st);
@@ -243,6 +238,71 @@ namespace {
return WMCtrlVal::WMCtlUnk;
}
bool wellControlDefined(const Opm::data::Well& xw)
{
using PMode = ::Opm::Well::ProducerCMode;
using IMode = ::Opm::Well::InjectorCMode;
const auto& curr = xw.current_control;
return (curr.isProducer && (curr.prod != PMode::CMODE_UNDEFINED))
|| (!curr.isProducer && (curr.inj != IMode::CMODE_UNDEFINED));
}
int ctrlMode(const Opm::Well& well, const Opm::data::Well& xw)
{
using PMode = ::Opm::Well::ProducerCMode;
using IMode = ::Opm::Well::InjectorCMode;
using Val = VI::IWell::Value::WellCtrlMode;
const auto& curr = xw.current_control;
if (curr.isProducer) {
switch (curr.prod) {
case PMode::ORAT: return Val::OilRate;
case PMode::WRAT: return Val::WatRate;
case PMode::GRAT: return Val::GasRate;
case PMode::LRAT: return Val::LiqRate;
case PMode::RESV: return Val::ResVRate;
case PMode::THP: return Val::THP;
case PMode::BHP: return Val::BHP;
case PMode::CRAT: return Val::CombRate;
case PMode::GRUP: return Val::Group;
default:
if (well.getStatus() == ::Opm::Well::Status::SHUT) {
return Val::Shut;
}
}
}
else { // injector
using IType = ::Opm::Well::InjectorType;
switch (curr.inj) {
case IMode::RATE: {
switch (well.injectorType()) {
case IType::OIL: return Val::OilRate;
case IType::WATER: return Val::WatRate;
case IType::GAS: return Val::GasRate;
case IType::MULTI: return Val::WMCtlUnk;
}}
break;
case IMode::RESV: return Val::ResVRate;
case IMode::THP: return Val::THP;
case IMode::BHP: return Val::BHP;
case IMode::GRUP: return Val::Group;
default:
if (well.getStatus() == ::Opm::Well::Status::SHUT) {
return Val::Shut;
}
}
}
return Val::WMCtlUnk;
}
int compOrder(const Opm::Well& well)
{
using WCO = ::Opm::Connection::Order;
@@ -258,14 +318,37 @@ namespace {
return 0;
}
template <typename IWellArray>
void setCurrentControl(const Opm::Well& well,
const int curr,
IWellArray& iWell)
{
using Ix = VI::IWell::index;
iWell[Ix::ActWCtrl] = curr;
if (well.predictionMode()) {
// Well in prediction mode (WCONPROD, WCONINJE). Assign
// requested control mode for prediction.
iWell[Ix::PredReqWCtrl] = curr;
iWell[Ix::HistReqWCtrl] = 0;
}
else {
// Well controlled by observed rates/BHP (WCONHIST,
// WCONINJH). Assign requested control mode for history.
iWell[Ix::PredReqWCtrl] = 0; // Possibly =1 instead.
iWell[Ix::HistReqWCtrl] = curr;
}
}
template <class IWellArray>
void staticContrib(const Opm::Well& well,
void staticContrib(const Opm::Well& well,
const Opm::SummaryState& st,
const std::size_t msWellID,
const std::map <const std::string, size_t>& GroupMapNameInd,
IWellArray& iWell)
{
using Ix = ::Opm::RestartIO::Helpers::VectorItems::IWell::index;
using Ix = VI::IWell::index;
iWell[Ix::IHead] = well.getHeadI() + 1;
iWell[Ix::JHead] = well.getHeadJ() + 1;
@@ -309,28 +392,14 @@ namespace {
// the target control mode requested in the simulation deck.
// This item is supposed to be the well's actual, active target
// control mode in the simulator.
iWell[Ix::ActWCtrl] = ctrlMode(well, st);
if (well.predictionMode()) {
// Well in prediction mode (WCONPROD, WCONINJE). Assign
// requested control mode for prediction.
iWell[Ix::PredReqWCtrl] = iWell[Ix::ActWCtrl];
iWell[Ix::HistReqWCtrl] = 0;
}
else {
// Well controlled by observed rates/BHP (WCONHIST,
// WCONINJH). Assign requested control mode for history.
iWell[Ix::PredReqWCtrl] = 0; // Possibly =1 instead.
iWell[Ix::HistReqWCtrl] = iWell[Ix::ActWCtrl];
}
setCurrentControl(well, ctrlMode(well, st), iWell);
// Multi-segmented well information
iWell[Ix::MsWID] = 0; // MS Well ID (0 or 1..#MS wells)
iWell[Ix::NWseg] = 0; // Number of well segments
if (well.isMultiSegment()) {
iWell[Ix::MsWID] = static_cast<int>(msWellID);
iWell[Ix::NWseg] =
well.getSegments().size();
iWell[Ix::NWseg] = well.getSegments().size();
}
iWell[Ix::CompOrd] = compOrder(well);
@@ -339,17 +408,22 @@ namespace {
template <class IWellArray>
void dynamicContribShut(IWellArray& iWell)
{
using Ix = ::Opm::RestartIO::Helpers::VectorItems::IWell::index;
using Ix = VI::IWell::index;
iWell[Ix::item9 ] = -1000;
iWell[Ix::item11] = -1000;
}
template <class IWellArray>
void dynamicContribOpen(const Opm::data::Well& xw,
void dynamicContribOpen(const Opm::Well& well,
const Opm::data::Well& xw,
IWellArray& iWell)
{
using Ix = ::Opm::RestartIO::Helpers::VectorItems::IWell::index;
using Ix = VI::IWell::index;
if (wellControlDefined(xw)) {
setCurrentControl(well, ctrlMode(well, xw), iWell);
}
const auto any_flowing_conn =
std::any_of(std::begin(xw.connections),
@@ -415,11 +489,11 @@ namespace {
zero , zero , infty, infty, zero , dflt , // 12.. 17 ( 2)
infty, infty, infty, infty, infty, zero , // 18.. 23 ( 3)
one , zero , zero , zero , zero , zero , // 24.. 29 ( 4)
zero , one , zero , infty, zero , zero , // 30.. 35 ( 5)
zero , one , zero , infty, zero , zero , // 30.. 35 ( 5)
zero , zero , zero , zero , zero , zero , // 36.. 41 ( 6)
zero , zero , zero , zero , zero , zero , // 42.. 47 ( 7)
zero , zero , zero , zero , zero , zero , // 48.. 53 ( 8)
infty, zero , zero , zero , zero , zero , // 54.. 59 ( 9)
infty, zero , zero , zero , zero , zero , // 54.. 59 ( 9)
zero , zero , zero , zero , zero , zero , // 60.. 65 (10)
zero , zero , zero , zero , zero , zero , // 66.. 71 (11)
zero , zero , zero , zero , zero , zero , // 72.. 77 (12)
@@ -856,7 +930,7 @@ captureDeclaredWellData(const Schedule& sched,
});
{
const auto actResStat = ZWell::act_res_stat(sched, smry, sim_step);
const auto actResStat = ZWell::act_res_stat(sched, smry, sim_step);
// Static contributions to ZWEL array.
wellLoop(wells,
[&actResStat, this](const Well& well, const std::size_t wellID) -> void
@@ -889,7 +963,7 @@ captureDynamicWellData(const Schedule& sched,
IWell::dynamicContribShut(iWell);
}
else {
IWell::dynamicContribOpen(i->second, iWell);
IWell::dynamicContribOpen(well, i->second, iWell);
}
});

View File

@@ -67,6 +67,8 @@
#include <utility>
#include <vector>
#include <boost/range.hpp>
namespace VI = ::Opm::RestartIO::Helpers::VectorItems;
namespace {
@@ -1026,6 +1028,75 @@ namespace {
}
}
::Opm::Well::ProducerCMode producerControlMode(const int curr)
{
using PMode = ::Opm::Well::ProducerCMode;
using Ctrl = VI::IWell::Value::WellCtrlMode;
switch (curr) {
case Ctrl::OilRate: return PMode::ORAT;
case Ctrl::WatRate: return PMode::WRAT;
case Ctrl::GasRate: return PMode::GRAT;
case Ctrl::LiqRate: return PMode::LRAT;
case Ctrl::ResVRate: return PMode::RESV;
case Ctrl::THP: return PMode::THP;
case Ctrl::BHP: return PMode::BHP;
case Ctrl::CombRate: return PMode::CRAT;
case Ctrl::Group: return PMode::GRUP;
default:
return PMode::CMODE_UNDEFINED;
}
}
::Opm::Well::InjectorCMode
injectorControlMode(const int curr, const int itype)
{
using IMode = ::Opm::Well::InjectorCMode;
using WType = VI::IWell::Value::WellType;
using Ctrl = VI::IWell::Value::WellCtrlMode;
switch (curr) {
case Ctrl::OilRate:
return (itype == WType::OilInj)
? IMode::RATE : IMode::CMODE_UNDEFINED;
case Ctrl::WatRate:
return (itype == WType::WatInj)
? IMode::RATE : IMode::CMODE_UNDEFINED;
case Ctrl::GasRate:
return (itype == WType::GasInj)
? IMode::RATE : IMode::CMODE_UNDEFINED;
case Ctrl::ResVRate: return IMode::RESV;
case Ctrl::THP: return IMode::THP;
case Ctrl::BHP: return IMode::BHP;
case Ctrl::Group: return IMode::GRUP;
}
return IMode::CMODE_UNDEFINED;
}
void restoreCurrentControl(const std::size_t wellID,
const WellVectors& wellData,
Opm::data::Well& xw)
{
const auto iwel = wellData.iwel(wellID);
const auto act = iwel[VI::IWell::index::ActWCtrl];
const auto wtyp = iwel[VI::IWell::index::WType];
auto& curr = xw.current_control;
curr.isProducer = wtyp == VI::IWell::Value::WellType::Producer;
if (curr.isProducer) {
curr.prod = producerControlMode(act);
}
else { // Assume injector
curr.inj = injectorControlMode(act, wtyp);
}
}
void restoreSegmentQuantities(const std::size_t mswID,
const Opm::WellSegments& segSet,
const Opm::UnitSystem& usys,
@@ -1085,7 +1156,7 @@ namespace {
}
Opm::data::Well
restore_well(const Opm::Well& well,
restore_well(const Opm::Well& well,
const std::size_t wellID,
const Opm::EclipseGrid& grid,
const Opm::UnitSystem& usys,
@@ -1136,9 +1207,11 @@ namespace {
// and pressure values (xw.connections[i].pressure).
restoreConnResults(well, wellID, grid, usys, phases, wellData, xw);
// 4) Restore segment quantities if applicable.
if (well.isMultiSegment() &&
segData.hasDefinedValues())
// 4) Restore well's active/current control
restoreCurrentControl(wellID, wellData, xw);
// 5) Restore segment quantities if applicable.
if (well.isMultiSegment() && segData.hasDefinedValues())
{
const auto iwel = wellData.iwel(wellID);
const auto mswID = iwel[VI::IWell::index::MsWID]; // One-based

View File

@@ -297,9 +297,10 @@ BOOST_AUTO_TEST_CASE(test_RFT)
Opm::data::Wells wells;
using SegRes = decltype(wells["w"].segments);
using Ctrl = decltype(wells["w"].current_control);
wells["OP_1"] = { std::move(r1), 1.0, 1.1, 3.1, 1, std::move(well1_comps), SegRes{} };
wells["OP_2"] = { std::move(r2), 1.0, 1.1, 3.2, 1, std::move(well2_comps), SegRes{} };
wells["OP_1"] = { std::move(r1), 1.0, 1.1, 3.1, 1, std::move(well1_comps), SegRes{}, Ctrl{} };
wells["OP_2"] = { std::move(r2), 1.0, 1.1, 3.2, 1, std::move(well2_comps), SegRes{}, Ctrl{} };
RestartValue restart_value(std::move(solution), std::move(wells));
@@ -419,9 +420,10 @@ BOOST_AUTO_TEST_CASE(test_RFT2)
Opm::data::Solution solution = createBlackoilState(2, numCells);
using SegRes = decltype(wells["w"].segments);
using Ctrl = decltype(wells["w"].current_control);
wells["OP_1"] = { std::move(r1), 1.0, 1.1, 3.1, 1, std::move(well1_comps), SegRes{} };
wells["OP_2"] = { std::move(r2), 1.0, 1.1, 3.2, 1, std::move(well2_comps), SegRes{} };
wells["OP_1"] = { std::move(r1), 1.0, 1.1, 3.1, 1, std::move(well1_comps), SegRes{}, Ctrl{} };
wells["OP_2"] = { std::move(r2), 1.0, 1.1, 3.2, 1, std::move(well2_comps), SegRes{}, Ctrl{} };
RestartValue restart_value(std::move(solution), std::move(wells));

View File

@@ -240,13 +240,23 @@ static data::Wells result_wells() {
rates1, 0.1 * ps, 0.2 * ps, 0.3 * ps, 1,
{ {well1_comp1} },
{ { segment.segNumber, segment } },
data::CurrentControl{}
};
well1.current_control.isProducer = false;
well1.current_control.inj =::Opm::Well::InjectorCMode::BHP;
using SegRes = decltype(well1.segments);
using Ctrl = data::CurrentControl;
data::Well well2 { rates2, 1.1 * ps, 1.2 * ps, 1.3 * ps, 2, { {well2_comp1 , well2_comp2} }, SegRes{} };
data::Well well3 { rates3, 2.1 * ps, 2.2 * ps, 2.3 * ps, 3, { {well3_comp1} }, SegRes{} };
data::Well well6 { rates6, 2.1 * ps, 2.2 * ps, 2.3 * ps, 3, { {well6_comp1} }, SegRes{} };
data::Well well2 { rates2, 1.1 * ps, 1.2 * ps, 1.3 * ps, 2, { {well2_comp1 , well2_comp2} }, SegRes{}, Ctrl{} };
well2.current_control.prod = ::Opm::Well::ProducerCMode::ORAT;
data::Well well3 { rates3, 2.1 * ps, 2.2 * ps, 2.3 * ps, 3, { {well3_comp1} }, SegRes{}, Ctrl{} };
well2.current_control.prod = ::Opm::Well::ProducerCMode::RESV;
data::Well well6 { rates6, 2.1 * ps, 2.2 * ps, 2.3 * ps, 3, { {well6_comp1} }, SegRes{}, Ctrl{} };
well6.current_control.isProducer = false;
well6.current_control.inj = ::Opm::Well::InjectorCMode::GRUP;
data::Wells wellrates;
@@ -1490,6 +1500,12 @@ BOOST_AUTO_TEST_CASE(READ_WRITE_WELLDATA) {
// No data for segment 10 of well W_2 (or no such segment).
const auto& W2 = wellRatesCopy.at("W_2");
BOOST_CHECK_THROW(W2.segments.at(10), std::out_of_range);
const auto& W6 = wellRatesCopy.at("W_6");
const auto& curr = W6.current_control;
BOOST_CHECK_MESSAGE(!curr.isProducer, "W_6 must be an injector");
BOOST_CHECK_MESSAGE(curr.prod == ::Opm::Well::ProducerCMode::CMODE_UNDEFINED, "W_6 must have an undefined producer control");
BOOST_CHECK_MESSAGE(curr.inj == ::Opm::Well::InjectorCMode::GRUP, "W_6 must be on GRUP control");
}
BOOST_AUTO_TEST_CASE(efficiency_factor) {
@@ -3123,7 +3139,6 @@ BOOST_AUTO_TEST_SUITE_END()
BOOST_AUTO_TEST_SUITE(Reset_Cumulative_Vectors)
BOOST_AUTO_TEST_CASE(SummaryState_TOTAL) {
SummaryState st(std::chrono::system_clock::now());
st.update("FOPR", 100);