Merge pull request #2002 from bska/welpi

Add Support Infrastructure for WELPI Feature
This commit is contained in:
Joakim Hove 2020-10-08 17:48:03 +02:00 committed by GitHub
commit a8b510334e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 559 additions and 95 deletions

View File

@ -35,22 +35,22 @@ namespace Opm
the Schedule object the NEW_WELL event is triggered
every time a WELSPECS keyword is encountered.
*/
NEW_WELL = 1,
NEW_WELL = (1 << 0),
/*
WHen the well data is updated with the WELSPECS keyword
this event is triggered. Only applies to individual
wells, and not the global Schedule object.
*/
WELL_WELSPECS_UPDATE = 2,
WELL_WELSPECS_UPDATE = (1 << 1),
//WELL_POLYMER_UPDATE = 4,
//WELL_POLYMER_UPDATE = (1 << 2),
/*
The NEW_GROUP event is triggered by the WELSPECS and
GRUPTREE keywords.
*/
NEW_GROUP = 8,
NEW_GROUP = (1 << 3),
/*
The PRODUCTION_UPDATE event is triggered by the
@ -59,48 +59,53 @@ namespace Opm
is changed. Quite simlar for INJECTION_UPDATE and
POLYMER_UPDATE.
*/
PRODUCTION_UPDATE = 16,
INJECTION_UPDATE = 32,
//POLYMER_UPDATES = 64,
PRODUCTION_UPDATE = (1 << 4),
INJECTION_UPDATE = (1 << 5),
//POLYMER_UPDATES = (1 << 6),
/*
This event is triggered if the well status is changed
between {OPEN,SHUT,STOP,AUTO}. There are many keywords
which can trigger a well status change.
*/
WELL_STATUS_CHANGE = 128,
WELL_STATUS_CHANGE = (1 << 7),
/*
COMPDAT and WELOPEN
*/
COMPLETION_CHANGE = 256,
COMPLETION_CHANGE = (1 << 8),
/*
The well group topolyg has changed.
*/
GROUP_CHANGE = 512,
GROUP_CHANGE = (1 << 9),
/*
Geology modifier.
*/
GEO_MODIFIER = 1024,
GEO_MODIFIER = (1 << 10),
/*
TUNING has changed
*/
TUNING_CHANGE = 2048,
TUNING_CHANGE = (1 << 11),
/* The VFP tables have changed */
VFPINJ_UPDATE = 4096,
VFPPROD_UPDATE = 8192,
VFPINJ_UPDATE = (1 << 12),
VFPPROD_UPDATE = (1 << 13),
/*
GROUP production or injection targets has changed
*/
GROUP_PRODUCTION_UPDATE = 16384,
GROUP_INJECTION_UPDATE = 32768
GROUP_PRODUCTION_UPDATE = (1 << 14),
GROUP_INJECTION_UPDATE = (1 << 15),
/*
* New explicit well productivity/injectivity assignment.
*/
WELL_PRODUCTIVITY_INDEX = (1 << 16),
};
}

View File

@ -521,6 +521,7 @@ namespace Opm
void handleWECON (const HandlerContext&, const ParseContext&, ErrorGuard&);
void handleWEFAC (const HandlerContext&, const ParseContext&, ErrorGuard&);
void handleWELOPEN (const HandlerContext&, const ParseContext&, ErrorGuard&);
void handleWELPI (const HandlerContext&, const ParseContext&, ErrorGuard&);
void handleWELSEGS (const HandlerContext&, const ParseContext&, ErrorGuard&);
void handleWELSPECS (const HandlerContext&, const ParseContext&, ErrorGuard&);
void handleWELTARG (const HandlerContext&, const ParseContext&, ErrorGuard&);

View File

@ -122,6 +122,8 @@ namespace RestartIO {
void setState(State state);
void setComplnum(int compnum);
void scaleWellPi(double wellPi);
bool prepareWellPIScaling();
void applyWellPIScaling(const double scaleFactor);
void updateSegmentRST(int segment_number_arg,
double center_depth_arg);
void updateSegment(int segment_number_arg,
@ -161,6 +163,7 @@ namespace RestartIO {
serializer(m_perf_range);
serializer(m_defaultSatTabId);
serializer(segment_number);
serializer(m_subject_to_welpi);
}
private:
@ -238,6 +241,9 @@ namespace RestartIO {
// 0 means the completion is not related to segment
int segment_number = 0;
// Whether or not this Connection is subject to WELPI scaling.
bool m_subject_to_welpi = false;
static std::string CTFKindToString(const CTFKind);
};
}

View File

@ -21,8 +21,16 @@
#ifndef WELL2_HPP
#define WELL2_HPP
#include <cstddef>
#include <iosfwd>
#include <map>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>
#include <stddef.h>
#include <opm/parser/eclipse/EclipseState/Runspec.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/SummaryState.hpp>
@ -45,8 +53,6 @@ namespace Opm {
class DeckRecord;
class EclipseGrid;
class DeckKeyword;
struct WellInjectionProperties;
class WellProductionProperties;
class UDQActive;
class UDQConfig;
class SICD;
@ -539,6 +545,7 @@ public:
bool updateEconLimits(std::shared_ptr<WellEconProductionLimits> econ_limits);
bool updateProduction(std::shared_ptr<WellProductionProperties> production);
bool updateInjection(std::shared_ptr<WellInjectionProperties> injection);
bool updateWellProductivityIndex(const double prodIndex);
bool updateWSEGSICD(const std::vector<std::pair<int, SICD> >& sicd_pairs);
bool updateWSEGVALV(const std::vector<std::pair<int, Valve> >& valve_pairs);
@ -563,6 +570,7 @@ public:
bool cmp_structure(const Well& other) const;
bool operator==(const Well& data) const;
void setInsertIndex(std::size_t index);
void applyWellProdIndexScaling(const double currentEffectivePI);
template<class Serializer>
void serializeOp(Serializer& serializer)
@ -588,6 +596,7 @@ public:
serializer(solvent_fraction);
serializer(has_produced);
serializer(prediction_mode);
serializer(productivity_index);
serializer(econ_limits);
serializer(foam_properties);
serializer(polymer_properties);
@ -624,21 +633,21 @@ private:
double solvent_fraction;
bool has_produced = false;
bool prediction_mode = true;
std::optional<double> productivity_index{ std::nullopt };
std::shared_ptr<WellEconProductionLimits> econ_limits;
std::shared_ptr<WellFoamProperties> foam_properties;
std::shared_ptr<WellPolymerProperties> polymer_properties;
std::shared_ptr<WellBrineProperties> brine_properties;
std::shared_ptr<WellTracerProperties> tracer_properties;
std::shared_ptr<WellConnections> connections; // The WellConnections object can not be const because of the filterConnections method - would be beneficial to rewrite to enable const
std::shared_ptr<WellConnections> connections; // The WellConnections object cannot be const because of WELPI and the filterConnections method
std::shared_ptr<WellProductionProperties> production;
std::shared_ptr<WellInjectionProperties> injection;
std::shared_ptr<WellSegments> segments;
};
std::ostream& operator<<( std::ostream&, const Well::WellInjectionProperties& );
std::ostream& operator<<( std::ostream&, const WellProductionProperties& );
std::ostream& operator<<( std::ostream&, const Well::WellProductionProperties& );
int eclipseControlMode(const Well::InjectorCMode imode,
const InjectorType itype);

View File

@ -25,7 +25,13 @@
#include <opm/common/utility/ActiveGridCells.hpp>
#include <cstddef>
#include <vector>
#include <stddef.h>
namespace Opm {
class DeckRecord;
class EclipseGrid;
class FieldPropsManager;
class WellConnections {
@ -93,6 +99,21 @@ namespace Opm {
Connection::Order ordering() const { return this->m_ordering; }
std::vector<const Connection *> output(const EclipseGrid& grid) const;
/// Activate or reactivate WELPI scaling for this connection set.
///
/// Following this call, any WELPI-based scaling will apply to all
/// connections whose properties are not reset in COMPDAT.
///
/// Returns whether or not this call to prepareWellPIScaling() is
/// a state change (e.g., no WELPI to active WELPI or WELPI for
/// some connections to WELPI for all connections).
bool prepareWellPIScaling();
/// 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);
template<class Serializer>
void serializeOp(Serializer& serializer)
{

View File

@ -17,6 +17,7 @@
along with OPM. If not, see <http://www.gnu.org/licenses/>.
*/
#include <exception>
#include <fnmatch.h>
#include <functional>
#include <iostream>
@ -50,6 +51,7 @@
#include <opm/parser/eclipse/Parser/ParserKeywords/W.hpp>
#include <opm/parser/eclipse/EclipseState/EclipseState.hpp>
#include <opm/parser/eclipse/EclipseState/Runspec.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/Action/ActionX.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/Action/ActionResult.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/DynamicState.hpp>
@ -1064,6 +1066,48 @@ namespace {
return applyWELOPEN(handlerContext.keyword, handlerContext.currentStep, parseContext, errors);
}
void Schedule::handleWELPI(const HandlerContext& handlerContext, const ParseContext& parseContext, ErrorGuard& errors) {
// Keyword structure
//
// WELPI
// W1 123.45 /
// W2* 456.78 /
// *P 111.222 /
// **X* 333.444 /
// /
//
// Interpretation of productivity index (item 2) depends on well's preferred phase.
using WELL_NAME = ParserKeywords::WELPI::WELL_NAME;
using PI = ParserKeywords::WELPI::STEADY_STATE_PRODUCTIVITY_OR_INJECTIVITY_INDEX_VALUE;
const auto& usys = handlerContext.section.unitSystem();
const auto gasPI = UnitSystem::measure::gas_productivity_index;
const auto liqPI = UnitSystem::measure::liquid_productivity_index;
for (const auto& record : handlerContext.keyword) {
const auto well_names = this->wellNames(record.getItem<WELL_NAME>().getTrimmedString(0),
handlerContext.currentStep);
if (well_names.empty())
this->invalidNamePattern(record.getItem<WELL_NAME>().getTrimmedString(0),
handlerContext.currentStep, parseContext,
errors, handlerContext.keyword);
const auto rawProdIndex = record.getItem<PI>().get<double>(0);
for (const auto& well_name : well_names) {
// All wells in a single record *hopefully* have the same preferred phase...
const auto& well = this->getWell(well_name, handlerContext.currentStep);
const auto unitPI = (well.getPreferredPhase() == Phase::GAS) ? gasPI : liqPI;
auto well2 = std::make_shared<Well>(well);
if (well2->updateWellProductivityIndex(usys.to_si(unitPI, rawProdIndex)))
this->updateWell(std::move(well2), handlerContext.currentStep);
this->addWellGroupEvent(well_name, ScheduleEvents::WELL_PRODUCTIVITY_INDEX, handlerContext.currentStep);
}
}
}
void Schedule::handleWELSEGS(const HandlerContext& handlerContext, const ParseContext&, ErrorGuard&) {
const auto& record1 = handlerContext.keyword.getRecord(0);
const auto& wname = record1.getItem("WELL").getTrimmedString(0);
@ -1680,7 +1724,7 @@ namespace {
bool Schedule::handleNormalKeyword(const HandlerContext& handlerContext, const ParseContext& parseContext, ErrorGuard& errors) {
using handler_function = std::function<void(Schedule*, const HandlerContext&, const ParseContext&, ErrorGuard&)>;
using handler_function = void (Schedule::*)(const HandlerContext&, const ParseContext&, ErrorGuard&);
static const std::unordered_map<std::string,handler_function> handler_functions = {
{ "BRANPROP", &Schedule::handleBRANPROP },
{ "COMPDAT" , &Schedule::handleCOMPDAT },
@ -1734,6 +1778,7 @@ namespace {
{ "WECON" , &Schedule::handleWECON },
{ "WEFAC" , &Schedule::handleWEFAC },
{ "WELOPEN" , &Schedule::handleWELOPEN },
{ "WELPI" , &Schedule::handleWELPI },
{ "WELSEGS" , &Schedule::handleWELSEGS },
{ "WELSPECS", &Schedule::handleWELSPECS },
{ "WELTARG" , &Schedule::handleWELTARG },
@ -1757,27 +1802,24 @@ namespace {
{ "WTRACER" , &Schedule::handleWTRACER },
};
const auto function_iterator = handler_functions.find(handlerContext.keyword.name());
if (function_iterator != handler_functions.end()) {
const auto& handler = function_iterator->second;
try {
handler(this, handlerContext, parseContext, errors);
} catch (const OpmInputError&) {
throw;
} catch (const std::exception& e) {
const OpmInputError opm_error { e, handlerContext.keyword.location() } ;
OpmLog::error(opm_error.what());
std::throw_with_nested(opm_error);
}
return true;
} else {
auto function_iterator = handler_functions.find(handlerContext.keyword.name());
if (function_iterator == handler_functions.end()) {
return false;
}
try {
std::invoke(function_iterator->second, this, handlerContext, parseContext, errors);
} catch (const OpmInputError&) {
throw;
} catch (const std::exception& e) {
const OpmInputError opm_error { e, handlerContext.keyword.location() } ;
OpmLog::error(opm_error.what());
std::throw_with_nested(opm_error);
}
return true;
}
}

View File

@ -1285,22 +1285,6 @@ namespace {
well_pair.second->filterConnections(grid);
}
}
for (auto& dynamic_pair : this->wells_static) {
auto& dynamic_state = dynamic_pair.second;
for (auto& well_pair : dynamic_state.unique()) {
if (well_pair.second)
well_pair.second->filterConnections(grid);
}
}
for (auto& dynamic_pair : this->wells_static) {
auto& dynamic_state = dynamic_pair.second;
for (auto& well_pair : dynamic_state.unique()) {
if (well_pair.second)
well_pair.second->filterConnections(grid);
}
}
}
const VFPProdTable& Schedule::getVFPProdTable(int table_id, std::size_t timeStep) const {

View File

@ -127,6 +127,7 @@ Connection::Connection(const RestartIO::RstConnection& rst_connection, const Ecl
result.m_sort_value = 14;
result.m_defaultSatTabId = true;
result.segment_number = 16;
result.m_subject_to_welpi = true;
return result;
}
@ -247,7 +248,20 @@ const std::optional<std::pair<double, double>>& Connection::perf_range() const {
this->m_CF *= wellPi;
}
bool Connection::prepareWellPIScaling() {
const auto update = !this->m_subject_to_welpi;
this->m_subject_to_welpi = true;
return update;
}
void Connection::applyWellPIScaling(const double scaleFactor) {
if (! this->m_subject_to_welpi)
return;
this->scaleWellPi(scaleFactor);
}
std::string Connection::str() const {
std::stringstream ss;
@ -283,7 +297,8 @@ const std::optional<std::pair<double, double>>& Connection::perf_range() const {
&& this->direction == rhs.direction
&& this->segment_number == rhs.segment_number
&& this->center_depth == rhs.center_depth
&& this->m_sort_value == rhs.m_sort_value;
&& this->m_sort_value == rhs.m_sort_value
&& this->m_subject_to_welpi == rhs.m_subject_to_welpi;
}
bool Connection::operator!=( const Connection& rhs ) const {

View File

@ -375,6 +375,7 @@ Well Well::serializeObject()
result.efficiency_factor = 8.0;
result.solvent_fraction = 9.0;
result.prediction_mode = false;
result.productivity_index = 10.0;
result.econ_limits = std::make_shared<Opm::WellEconProductionLimits>(Opm::WellEconProductionLimits::serializeObject());
result.foam_properties = std::make_shared<WellFoamProperties>(WellFoamProperties::serializeObject());
result.polymer_properties = std::make_shared<WellPolymerProperties>(WellPolymerProperties::serializeObject());
@ -488,6 +489,15 @@ bool Well::updateInjection(std::shared_ptr<WellInjectionProperties> injection_ar
return false;
}
bool Well::updateWellProductivityIndex(const double prodIndex) {
const auto update = this->productivity_index != prodIndex;
if (update)
this->productivity_index = prodIndex;
// Note order here: We must always run prepareWellPIScaling(), but that operation
// *may* not lead to requiring updating the well state, so return 'update' if not.
return this->connections->prepareWellPIScaling() || update;
}
bool Well::updateHasProduced() {
if (this->wtype.producer() && this->status == Status::OPEN) {
@ -801,6 +811,21 @@ void Well::setInsertIndex(std::size_t index) {
this->insert_index = index;
}
void Well::applyWellProdIndexScaling(const double currentEffectivePI) {
if (this->connections->empty())
// No connections for this well. Unexpected.
return;
if (!this->productivity_index)
// WELPI not activated. Nothing to do.
return;
if (this->productivity_index == currentEffectivePI)
// No change in scaling.
return;
this->connections->applyWellPIScaling(*this->productivity_index / currentEffectivePI);
}
const WellConnections& Well::getConnections() const {
return *this->connections;
@ -1486,6 +1511,10 @@ bool Well::operator==(const Well& data) const {
this->getFoamProperties() == data.getFoamProperties() &&
this->getStatus() == data.getStatus() &&
this->guide_rate == data.guide_rate &&
this->solvent_fraction == data.solvent_fraction &&
this->hasProduced() == data.hasProduced() &&
this->predictionMode() == data.predictionMode() &&
this->productivity_index == data.productivity_index &&
this->getTracerProperties() == data.getTracerProperties() &&
this->getProductionProperties() == data.getProductionProperties() &&
this->getInjectionProperties() == data.getInjectionProperties();

View File

@ -17,10 +17,15 @@
along with OPM. If not, see <http://www.gnu.org/licenses/>.
*/
#include <algorithm>
#include <cassert>
#include <cmath>
#include <limits>
#include <cstddef>
#include <iostream>
#include <limits>
#include <stdexcept>
#include <string>
#include <utility>
#include <opm/parser/eclipse/Units/Units.hpp>
#include <opm/io/eclipse/rst/connection.hpp>
@ -181,6 +186,20 @@ inline std::array< size_t, 3> directionIndices(const Opm::Connection::Direction
return out;
}
bool WellConnections::prepareWellPIScaling()
{
auto update = false;
for (auto& conn : this->m_connections)
update = conn.prepareWellPIScaling() || update;
return update;
}
void WellConnections::applyWellPIScaling(const double scaleFactor)
{
for (auto& conn : this->m_connections)
conn.applyWellPIScaling(scaleFactor);
}
void WellConnections::addConnection(int i, int j , int k ,
std::size_t global_index,
@ -528,7 +547,6 @@ inline std::array< size_t, 3> directionIndices(const Opm::Connection::Direction
}
}
size_t WellConnections::findClosestConnection(int oi, int oj, double oz, size_t start_pos)
{
size_t closest = std::numeric_limits<size_t>::max();
@ -568,11 +586,12 @@ inline std::array< size_t, 3> directionIndices(const Opm::Connection::Direction
return !( *this == rhs );
}
void WellConnections::filter(const ActiveGridCells& grid) {
auto new_end = std::remove_if(m_connections.begin(),
m_connections.end(),
[&grid](const Connection& c) { return !grid.cellActive(c.getI(), c.getJ(), c.getK()); });
auto isInactive = [&grid](const Connection& c) {
return !grid.cellActive(c.getI(), c.getJ(), c.getK());
};
auto new_end = std::remove_if(m_connections.begin(), m_connections.end(), isInactive);
m_connections.erase(new_end, m_connections.end());
}

View File

@ -17,6 +17,7 @@
along with OPM. If not, see <http://www.gnu.org/licenses/>.
*/
#include <cstddef>
#include <stdexcept>
#include <iostream>
#include <boost/filesystem.hpp>
@ -28,6 +29,7 @@
#include <opm/parser/eclipse/Python/Python.hpp>
#include <opm/parser/eclipse/Parser/Parser.hpp>
#include <opm/parser/eclipse/Deck/Deck.hpp>
#include <opm/parser/eclipse/Deck/DeckItem.hpp>
#include <opm/parser/eclipse/Deck/DeckRecord.hpp>
#include <opm/parser/eclipse/Deck/DeckKeyword.hpp>
@ -41,6 +43,30 @@
#include <opm/parser/eclipse/EclipseState/Runspec.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/Schedule.hpp>
#include <opm/parser/eclipse/Units/Units.hpp>
namespace {
double cp_rm3_per_db()
{
return Opm::prefix::centi*Opm::unit::Poise * Opm::unit::cubic(Opm::unit::meter)
/ (Opm::unit::day * Opm::unit::barsa);
}
Opm::WellConnections loadCOMPDAT(const std::string& compdat_keyword) {
Opm::EclipseGrid grid(10,10,10);
Opm::TableManager tables;
Opm::Parser parser;
const auto deck = parser.parseString(compdat_keyword);
Opm::FieldPropsManager field_props(deck, Opm::Phases{true, true, true}, grid, Opm::TableManager());
const auto& keyword = deck.getKeyword("COMPDAT", 0);
Opm::WellConnections connections(Opm::Connection::Order::TRACK, 10,10);
for (const auto& rec : keyword)
connections.loadCOMPDAT(rec, grid, field_props);
return connections;
}
}
namespace Opm {
inline std::ostream& operator<<( std::ostream& stream, const Connection& c ) {
@ -150,20 +176,6 @@ BOOST_AUTO_TEST_CASE(ActiveCompletions) {
BOOST_CHECK_EQUAL( completion3, active_completions.get(1));
}
Opm::WellConnections loadCOMPDAT(const std::string& compdat_keyword) {
Opm::EclipseGrid grid(10,10,10);
Opm::TableManager tables;
Opm::Parser parser;
const auto deck = parser.parseString(compdat_keyword);
Opm::FieldPropsManager field_props(deck, Opm::Phases{true, true, true}, grid, Opm::TableManager());
const auto& keyword = deck.getKeyword("COMPDAT", 0);
Opm::WellConnections connections(Opm::Connection::Order::TRACK, 10,10);
for (const auto& rec : keyword)
connections.loadCOMPDAT(rec, grid, field_props);
return connections;
}
BOOST_AUTO_TEST_CASE(loadCOMPDATTEST) {
Opm::UnitSystem units(Opm::UnitSystem::UnitType::UNIT_TYPE_METRIC); // Unit system used in deck FIRST_SIM.DATA.
{
@ -336,3 +348,149 @@ BOOST_AUTO_TEST_CASE(loadCOMPDATTESTSPE9) {
BOOST_CHECK_MESSAGE( !conn.ctfAssignedFromInput(), "Calculated SPE9 CTF values must NOT be assigned from input");
}
}
BOOST_AUTO_TEST_CASE(ApplyWellPI) {
const auto deck = Opm::Parser{}.parseString(R"(RUNSPEC
DIMENS
10 10 3 /
START
5 OCT 2020 /
GRID
DXV
10*100 /
DYV
10*100 /
DZV
3*10 /
DEPTHZ
121*2000 /
ACTNUM
100*1
99*1 0
100*1
/
PERMX
300*100 /
PERMY
300*100 /
PERMZ
300*100 /
PORO
300*0.3 /
SCHEDULE
WELSPECS
'P' 'G' 10 10 2005 'LIQ' /
/
COMPDAT
'P' 0 0 1 3 OPEN 1 100 /
/
TSTEP
10
/
END
)");
const auto es = Opm::EclipseState{ deck };
const auto sched = Opm::Schedule{ deck, es };
const auto expectCF = 100.0*cp_rm3_per_db();
auto connP = sched.getWell("P", 0).getConnections();
for (const auto& conn : connP) {
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);
}
// 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);
}
// Reset CF -- simulating COMPDAT record (inactive cell)
connP.addConnection(9, 9, 1, // 10, 10, 2
199,
2015.0,
Opm::Connection::State::OPEN,
50.0*cp_rm3_per_db(),
0.123,
0.234,
0.157,
0.0,
1);
BOOST_REQUIRE_EQUAL(connP.size(), std::size_t{3});
BOOST_CHECK_CLOSE(connP[0].CF(), 2.0*expectCF , 1.0e-10);
BOOST_CHECK_CLOSE(connP[1].CF(), 2.0*expectCF , 1.0e-10);
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);
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);
// Reset CF -- simulating COMPDAT record (active cell)
connP.addConnection(8, 9, 1, // 10, 10, 2
198,
2015.0,
Opm::Connection::State::OPEN,
50.0*cp_rm3_per_db(),
0.123,
0.234,
0.157,
0.0,
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);
const auto& grid = es.getInputGrid();
const auto actCells = Opm::ActiveGridCells {
std::size_t{10}, std::size_t{10}, std::size_t{3},
grid.getActiveMap().data(),
grid.getNumActive()
};
connP.filter(actCells);
BOOST_REQUIRE_EQUAL(connP.size(), std::size_t{3});
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(), 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);
}

View File

@ -17,19 +17,17 @@
along with OPM. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdexcept>
#include <algorithm>
#include <iostream>
#include <boost/filesystem.hpp>
#include <stdexcept>
#define BOOST_TEST_MODULE ScheduleTests
#include <boost/test/unit_test.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <opm/common/utility/TimeService.hpp>
#include <opm/common/utility/OpmInputError.hpp>
#include <opm/parser/eclipse/Python/Python.hpp>
#include <opm/parser/eclipse/EclipseState/Grid/FieldPropsManager.hpp>
#include <opm/parser/eclipse/EclipseState/Grid/EclipseGrid.hpp>
@ -60,8 +58,20 @@
using namespace Opm;
namespace {
double liquid_PI_unit()
{
return UnitSystem::newMETRIC().to_si(UnitSystem::measure::liquid_productivity_index, 1.0);
}
Schedule make_schedule(const std::string& deck_string) {
double cp_rm3_per_db()
{
return prefix::centi*unit::Poise * unit::cubic(unit::meter)
/ (unit::day * unit::barsa);
}
}
static Schedule make_schedule(const std::string& deck_string) {
const auto& deck = Parser{}.parseString(deck_string);
auto python = std::make_shared<Python>();
EclipseGrid grid(10,10,10);
@ -408,7 +418,7 @@ BOOST_AUTO_TEST_CASE(CreateScheduleDeckWellsOrdered) {
}
bool has_well( const std::vector<Well>& wells, const std::string& well_name) {
static bool has_well( const std::vector<Well>& wells, const std::string& well_name) {
for (const auto& well : wells )
if (well.name( ) == well_name)
return true;
@ -3159,7 +3169,7 @@ BOOST_AUTO_TEST_CASE(WTEST_CONFIG) {
}
bool has(const std::vector<std::string>& l, const std::string& s) {
static bool has(const std::vector<std::string>& l, const std::string& s) {
auto f = std::find(l.begin(), l.end(), s);
return (f != l.end());
}
@ -3704,3 +3714,82 @@ WLIFTOPT
BOOST_CHECK(w3.alloc_extra_gas());
}
BOOST_AUTO_TEST_CASE(WellPI) {
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 /
/
TSTEP
10
/
WELPI
'P' 200.0 /
/
TSTEP
10
/
END
)");
const auto es = EclipseState{ deck };
const auto sched = Schedule{ deck, es };
// Apply WELPI before seeing WELPI data
{
const auto expectCF = 100.0*cp_rm3_per_db();
auto wellP = sched.getWell("P", 0);
wellP.applyWellProdIndexScaling(2.7182818);
for (const auto& conn : wellP.getConnections()) {
BOOST_CHECK_CLOSE(conn.CF(), expectCF, 1.0e-10);
}
}
// Apply WELPI after seeing WELPI data.
{
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());
for (const auto& conn : wellP.getConnections()) {
BOOST_CHECK_CLOSE(conn.CF(), expectCF, 1.0e-10);
}
}
BOOST_CHECK_MESSAGE(sched.hasWellGroupEvent("P", ScheduleEvents::WELL_PRODUCTIVITY_INDEX, 1),
"Must have WELL_PRODUCTIVITY_INDEX event at report step 1");
}

View File

@ -49,17 +49,13 @@
using namespace Opm;
namespace Opm {
inline std::ostream& operator<<( std::ostream& stream, const Connection& c ) {
return stream << "(" << c.getI() << "," << c.getJ() << "," << c.getK() << ")";
namespace {
double cp_rm3_per_db()
{
return prefix::centi*unit::Poise * unit::cubic(unit::meter)
/ (unit::day * unit::barsa);
}
}
inline std::ostream& operator<<( std::ostream& stream, const Well& well ) {
return stream << "(" << well.name() << ")";
}
}
BOOST_AUTO_TEST_CASE(WellCOMPDATtestTRACK) {
Opm::Parser parser;
@ -1161,3 +1157,93 @@ WCONINJE
}
BOOST_AUTO_TEST_CASE(WellPI) {
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 expectCF = 100.0*cp_rm3_per_db();
auto wellP = sched.getWell("P", 0);
for (const auto& conn : wellP.getConnections()) {
BOOST_CHECK_CLOSE(conn.CF(), expectCF, 1.0e-10);
}
// 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);
}
// Simulate applying WELPI after seeing
//
// WELPI
// P 2 /
// /
//
// (ignoring units of measure)
BOOST_CHECK_MESSAGE( wellP.updateWellProductivityIndex(2.0), "First call to updateWellProductivityIndex() must be a state change");
BOOST_CHECK_MESSAGE(!wellP.updateWellProductivityIndex(2.0), "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);
}
// 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);
}
// New WELPI record does not reset the scaling factors
wellP.updateWellProductivityIndex(3.0);
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);
}
}