Merge pull request #2002 from bska/welpi
Add Support Infrastructure for WELPI Feature
This commit is contained in:
commit
a8b510334e
@ -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),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -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&);
|
||||
|
@ -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);
|
||||
};
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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();
|
||||
|
@ -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());
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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");
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user