Merge pull request #1746 from joakim-hove/network

Network
This commit is contained in:
Bård Skaflestad 2020-04-29 17:00:54 +02:00 committed by GitHub
commit fc69eb7b72
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 840 additions and 1 deletions

View File

@ -114,6 +114,9 @@ if(ENABLE_ECL_INPUT)
src/opm/parser/eclipse/EclipseState/Schedule/MSW/updatingConnectionsWithSegments.cpp
src/opm/parser/eclipse/EclipseState/Schedule/MSW/SpiralICD.cpp
src/opm/parser/eclipse/EclipseState/Schedule/MSW/Valve.cpp
src/opm/parser/eclipse/EclipseState/Schedule/Network/Branch.cpp
src/opm/parser/eclipse/EclipseState/Schedule/Network/ExtNetwork.cpp
src/opm/parser/eclipse/EclipseState/Schedule/Network/Node.cpp
src/opm/parser/eclipse/EclipseState/Schedule/OilVaporizationProperties.cpp
src/opm/parser/eclipse/EclipseState/Schedule/RFTConfig.cpp
src/opm/parser/eclipse/EclipseState/Schedule/RPTConfig.cpp
@ -332,6 +335,7 @@ if(ENABLE_ECL_INPUT)
tests/parser/MultiRegTests.cpp
tests/parser/MultisegmentWellTests.cpp
tests/parser/MULTREGTScannerTests.cpp
tests/parser/NetworkTests.cpp
tests/parser/OrderedMapTests.cpp
tests/parser/ParseContextTests.cpp
tests/parser/ParseContext_EXIT1.cpp
@ -664,6 +668,9 @@ if(ENABLE_ECL_INPUT)
opm/parser/eclipse/EclipseState/Schedule/Action/ASTNode.hpp
opm/parser/eclipse/EclipseState/Schedule/Action/PyAction.hpp
opm/parser/eclipse/EclipseState/Schedule/ArrayDimChecker.hpp
opm/parser/eclipse/EclipseState/Schedule/Network/Branch.hpp
opm/parser/eclipse/EclipseState/Schedule/Network/ExtNetwork.hpp
opm/parser/eclipse/EclipseState/Schedule/Network/Node.hpp
opm/parser/eclipse/EclipseState/Schedule/TimeMap.hpp
opm/parser/eclipse/EclipseState/Schedule/VFPInjTable.hpp
opm/parser/eclipse/EclipseState/Schedule/VFPProdTable.hpp

View File

@ -0,0 +1,73 @@
/*
Copyright 2020 Equinor ASA.
This file is part of the Open Porous Media project (OPM).
OPM is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OPM is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OPM. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef NETWORK_BRANCH_HPP
#define NETWORK_BRANCH_HPP
#include <optional>
namespace Opm {
namespace Network {
class Branch {
public:
enum class AlqEQ {
OIL_DENSITY,
GAS_DENSITY,
ALQ_INPUT
};
static AlqEQ AlqEqfromString(const std::string& input_string);
Branch() = default;
Branch(const std::string& downtree_node, const std::string& uptree_node, int vfp_table, double alq);
Branch(const std::string& downtree_node, const std::string& uptree_node, int vfp_table, AlqEQ alq_eq);
const std::string& downtree_node() const;
const std::string& uptree_node() const;
std::optional<int> vfp_table() const;
AlqEQ alq_eq() const;
std::optional<double> alq_value() const;
static Branch serializeObject();
bool operator==(const Branch& other) const;
template<class Serializer>
void serializeOp(Serializer& serializer)
{
serializer(m_downtree_node);
serializer(m_uptree_node);
serializer(m_vfp_table);
serializer(m_alq_value);
serializer(m_alq_eq);
}
private:
std::string m_downtree_node;
std::string m_uptree_node;
int m_vfp_table;
std::optional<double> m_alq_value;
AlqEQ m_alq_eq;
};
}
}
#endif

View File

@ -0,0 +1,66 @@
/*
Copyright 2020 Equinor ASA.
This file is part of the Open Porous Media project (OPM).
OPM is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OPM is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OPM. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef EXT_NETWORK_HPP
#define EXT_NETWORK_HPP
#include <map>
#include <optional>
#include <string>
#include <vector>
#include <opm/parser/eclipse/EclipseState/Schedule/Network/Branch.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/Network/Node.hpp>
namespace Opm {
namespace Network {
class ExtNetwork {
public:
ExtNetwork() = default;
bool active() const;
bool has_node(const std::string& name) const;
void add_node(Node node);
void add_branch(Branch branch);
const Node& node(const std::string& name) const;
const Node& root() const;
std::vector<Branch> downtree_branches(const std::string& node) const;
std::optional<Branch> uptree_branch(const std::string& node) const;
bool operator==(const ExtNetwork& other) const;
static ExtNetwork serializeObject();
template<class Serializer>
void serializeOp(Serializer& serializer)
{
serializer.vector(m_branches);
serializer.map(m_nodes);
}
private:
std::vector<Branch> m_branches;
std::map<std::string, Node> m_nodes;
};
}
}
#endif

View File

@ -0,0 +1,64 @@
/*
Copyright 2020 Equinor ASA.
This file is part of the Open Porous Media project (OPM).
OPM is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OPM is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OPM. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef NETWORK_NODE_HPP
#define NETWORK_NODE_HPP
#include <optional>
#include <string>
namespace Opm {
namespace Network {
class Node {
public:
Node() = default;
Node(const std::string& name);
const std::string& name() const;
const std::optional<double>& terminal_pressure() const;
bool as_choke() const;
bool add_gas_lift_gas() const;
const std::optional<std::string>& target_group() const;
void terminal_pressure(double pressure);
void add_gas_lift_gas(bool add_gas);
void as_choke(const std::string& target_group);
static Node serializeObject();
bool operator==(const Node& other) const;
template<class Serializer>
void serializeOp(Serializer& serializer)
{
serializer(m_name);
serializer(m_terminal_pressure);
serializer(m_add_gas_lift_gas);
serializer(m_choke_target_group);
}
private:
std::string m_name;
std::optional<double> m_terminal_pressure;
std::optional<std::string> m_choke_target_group;
bool m_add_gas_lift_gas = false;
};
}
}
#endif

View File

@ -40,6 +40,7 @@
#include <opm/parser/eclipse/EclipseState/Schedule/RFTConfig.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/VFPInjTable.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/VFPProdTable.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/Network/ExtNetwork.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/Well/Well.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/Well/WellTestConfig.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/Action/Actions.hpp>
@ -263,6 +264,9 @@ namespace Opm
void applyAction(size_t reportStep, const Action::ActionX& action, const Action::Result& result);
int getNupcol(size_t reportStep) const;
const Network::ExtNetwork& network(std::size_t report_step) const;
bool operator==(const Schedule& data) const;
std::shared_ptr<const Python> python() const;
@ -305,6 +309,7 @@ namespace Opm
gconsump.serializeOp(serializer);
global_whistctl_mode.template serializeOp<Serializer, false>(serializer);
m_actions.serializeOp(serializer);
m_network.serializeOp(serializer);
rft_config.serializeOp(serializer);
m_nupcol.template serializeOp<Serializer, false>(serializer);
restart_config.serializeOp(serializer);
@ -340,6 +345,7 @@ namespace Opm
DynamicState<std::shared_ptr<GConSump>> gconsump;
DynamicState<Well::ProducerCMode> global_whistctl_mode;
DynamicState<std::shared_ptr<Action::Actions>> m_actions;
DynamicState<std::shared_ptr<Network::ExtNetwork>> m_network;
RFTConfig rft_config;
DynamicState<int> m_nupcol;
RestartConfig restart_config;
@ -367,6 +373,8 @@ namespace Opm
const UnitSystem& unit_system);
DynamicState<std::shared_ptr<RPTConfig>> rpt_config;
void updateNetwork(std::shared_ptr<Network::ExtNetwork> network, std::size_t report_step);
GTNode groupTree(const std::string& root_node, std::size_t report_step, const GTNode * parent) const;
void updateGroup(std::shared_ptr<Group> group, size_t reportStep);
@ -419,6 +427,9 @@ namespace Opm
void handleLINCOM( const DeckKeyword& keyword, size_t currentStep);
void handleWEFAC( const DeckKeyword& keyword, size_t currentStep, const ParseContext& parseContext, ErrorGuard& errors);
void handleBRANPROP( const DeckKeyword& keyword, size_t currentStep);
void handleNODEPROP( const DeckKeyword& keyword, size_t currentStep);
void handleTUNING( const DeckKeyword& keyword, size_t currentStep);
void handlePYACTION( std::shared_ptr<const Python> python, const std::string& input_path, const DeckKeyword& keyword, size_t currentStep);
void handleNUPCOL( const DeckKeyword& keyword, size_t currentStep);

View File

@ -160,7 +160,7 @@ T DeckItem::get( size_t index ) const {
throw std::out_of_range("Invalid index");
if (!value::has_value(this->value_status[index]))
throw std::invalid_argument("Invalid arguemnt");
throw std::invalid_argument("Tried to get unitialized value from DeckItem index: " + std::to_string(index));
return this->value_ref< T >()[index];
}

View File

@ -0,0 +1,103 @@
/*
Copyright 2020 Equinor ASA.
This file is part of the Open Porous Media project (OPM).
OPM is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OPM is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OPM. If not, see <http://www.gnu.org/licenses/>.
*/
#include <opm/parser/eclipse/EclipseState/Schedule/Network/Branch.hpp>
namespace Opm {
namespace Network {
namespace {
constexpr int invalid_vfp_table = 9999;
}
Branch Branch::serializeObject() {
Branch object;
return object;
}
Branch::Branch(const std::string& downtree_node, const std::string& uptree_node, int vfp_table, double alq) :
m_downtree_node(downtree_node),
m_uptree_node(uptree_node),
m_vfp_table(vfp_table),
m_alq_value(alq),
m_alq_eq(AlqEQ::ALQ_INPUT)
{
}
Branch::Branch(const std::string& downtree_node, const std::string& uptree_node, int vfp_table, AlqEQ alq_eq):
m_downtree_node(downtree_node),
m_uptree_node(uptree_node),
m_vfp_table(vfp_table),
m_alq_eq(alq_eq)
{
if (alq_eq == AlqEQ::ALQ_INPUT)
throw std::logic_error("Wrong constructor - must supply ALQ value");
}
const std::string& Branch::uptree_node() const {
return this->m_uptree_node;
}
const std::string& Branch::downtree_node() const {
return this->m_downtree_node;
}
bool Branch::operator==(const Branch& other) const {
return this->m_downtree_node == other.m_downtree_node &&
this->m_uptree_node == other.m_uptree_node &&
this->m_vfp_table == other.m_vfp_table &&
this->m_alq_value == other.m_alq_value &&
this->m_alq_eq == other.m_alq_eq;
}
Branch::AlqEQ Branch::AlqEqfromString(const std::string& input_string) {
if (input_string == "NONE")
return AlqEQ::ALQ_INPUT;
if (input_string == "DENO")
return AlqEQ::OIL_DENSITY;
if (input_string == "DENG")
return AlqEQ::GAS_DENSITY;
throw std::invalid_argument("Invalid input for ALQ surface density eq: " + input_string);
}
std::optional<int> Branch::vfp_table() const {
if (this->m_vfp_table == invalid_vfp_table)
return {};
else
return this->m_vfp_table;
}
Branch::AlqEQ Branch::alq_eq() const {
return this->m_alq_eq;
}
std::optional<double> Branch::alq_value() const {
return this->m_alq_value;
}
}
}

View File

@ -0,0 +1,136 @@
/*
Copyright 2020 Equinor ASA.
This file is part of the Open Porous Media project (OPM).
OPM is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OPM is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OPM. If not, see <http://www.gnu.org/licenses/>.
*/
#include <algorithm>
#include <stdexcept>
#include <opm/parser/eclipse/EclipseState/Schedule/Network/ExtNetwork.hpp>
namespace Opm {
namespace Network {
ExtNetwork ExtNetwork::serializeObject() {
ExtNetwork object;
return object;
}
bool ExtNetwork::active() const {
return !this->m_branches.empty() && !this->m_nodes.empty();
}
bool ExtNetwork::operator==(const ExtNetwork&) const {
return true;
}
bool ExtNetwork::has_node(const std::string& name) const {
return (this->m_nodes.count(name) > 0);
}
const Node& ExtNetwork::node(const std::string& name) const {
const auto node_iter = this->m_nodes.find( name );
if (node_iter == this->m_nodes.end())
throw std::out_of_range("No such node: " + name);
return node_iter->second;
}
const Node& ExtNetwork::root() const {
if (this->m_nodes.empty())
throw std::invalid_argument("No root defined for empty network");
auto node_ptr = &(this->m_nodes.begin()->second);
while (true) {
auto next_branch = this->uptree_branch(node_ptr->name());
if (!next_branch)
break;
node_ptr = &(this->node( next_branch->uptree_node() ));
}
return *node_ptr;
}
void ExtNetwork::add_branch(Branch branch)
{
this->m_branches.push_back( std::move(branch) );
}
std::optional<Branch> ExtNetwork::uptree_branch(const std::string& node) const {
if (!this->has_node(node))
throw std::out_of_range("No such node: " + node);
std::vector<Branch> branches;
std::copy_if(this->m_branches.begin(), this->m_branches.end(), std::back_inserter(branches), [&node](const Branch& b) { return b.downtree_node() == node; });
if (branches.empty())
return {};
if (branches.size() == 1)
return std::move(branches[0]);
throw std::logic_error("Bug - more than upstree branch for node: " + node);
}
std::vector<Branch> ExtNetwork::downtree_branches(const std::string& node) const {
if (!this->has_node(node))
throw std::out_of_range("No such node: " + node);
std::vector<Branch> branches;
std::copy_if(this->m_branches.begin(), this->m_branches.end(), std::back_inserter(branches), [&node](const Branch& b) { return b.uptree_node() == node; });
return branches;
}
/*
The validation of the network structure is very weak. The current validation
goes as follows:
1. A branch is defined with and uptree and downtree node; the node names used
in the Branch definition is totally unchecked.
2. When a node is added we check that the name of the node corresponds to a
node name referred to in one of the previous branch definitions.
The algorithm feels quite illogical, but from the documentation it seems to be
the only possibility.
*/
void ExtNetwork::add_node(Node node)
{
std::string name = node.name();
auto branch = std::find_if(this->m_branches.begin(), this->m_branches.end(),
[&name](const Branch& b) { return b.uptree_node() == name || b.downtree_node() == name;});
if (branch == this->m_branches.end())
throw std::invalid_argument("Node: " + name + " is not referenced by any branch and would be dangling.");
if (branch->downtree_node() == name) {
if (node.as_choke() && branch->vfp_table().has_value())
throw std::invalid_argument("Node: " + name + " should serve as a choke => upstream branch can not have VFP table");
}
this->m_nodes.insert({ name, std::move(node) });
}
}
}

View File

@ -0,0 +1,72 @@
/*
Copyright 2020 Equinor ASA.
This file is part of the Open Porous Media project (OPM).
OPM is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OPM is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OPM. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdexcept>
#include <opm/parser/eclipse/EclipseState/Schedule/Network/Node.hpp>
namespace Opm {
namespace Network {
Node::Node(const std::string& name) :
m_name(name)
{}
const std::string& Node::name() const {
return this->m_name;
}
const std::optional<std::string>& Node::target_group() const {
return this->m_choke_target_group;
}
const std::optional<double>& Node::terminal_pressure() const {
return this->m_terminal_pressure;
}
bool Node::add_gas_lift_gas() const {
return this->m_add_gas_lift_gas;
}
bool Node::as_choke() const {
return this->m_choke_target_group.has_value();
}
void Node::terminal_pressure(double pressure) {
this->m_terminal_pressure = pressure;
}
void Node::add_gas_lift_gas(bool add_gas) {
this->m_add_gas_lift_gas = add_gas;
}
void Node::as_choke(const std::string& target_group) {
this->m_choke_target_group = target_group;
}
bool Node::operator==(const Node& other) const {
return this->m_name == other.m_name &&
this->m_terminal_pressure == other.m_terminal_pressure &&
this->m_add_gas_lift_gas == other.m_add_gas_lift_gas &&
this->m_choke_target_group == other.m_choke_target_group;
}
}
}

View File

@ -19,6 +19,7 @@
#include <fnmatch.h>
#include <iostream>
#include <optional>
#include <stdexcept>
#include <string>
#include <unordered_set>
@ -35,6 +36,7 @@
#include <opm/parser/eclipse/Deck/DeckSection.hpp>
#include <opm/parser/eclipse/Parser/ErrorGuard.hpp>
#include <opm/parser/eclipse/Parser/ParseContext.hpp>
#include <opm/parser/eclipse/Parser/ParserKeywords/B.hpp>
#include <opm/parser/eclipse/Parser/ParserKeywords/C.hpp>
#include <opm/parser/eclipse/Parser/ParserKeywords/G.hpp>
#include <opm/parser/eclipse/Parser/ParserKeywords/L.hpp>
@ -61,6 +63,7 @@
#include <opm/parser/eclipse/EclipseState/Schedule/Schedule.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/TimeMap.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/Tuning.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/Network/Node.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/Well/WList.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/Well/WListManager.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/Well/WellFoamProperties.hpp>
@ -145,6 +148,7 @@ std::pair<std::time_t, std::size_t> restart_info(const RestartIO::RstState * rst
gconsump(this->m_timeMap, std::make_shared<GConSump>() ),
global_whistctl_mode(this->m_timeMap, Well::ProducerCMode::CMODE_UNDEFINED),
m_actions(this->m_timeMap, std::make_shared<Action::Actions>()),
m_network(this->m_timeMap, std::make_shared<Network::ExtNetwork>()),
rft_config(this->m_timeMap),
m_nupcol(this->m_timeMap, ParserKeywords::NUPCOL::NUM_ITER::defaultValue),
restart_config(m_timeMap, deck, parseContext, errors),
@ -481,6 +485,12 @@ Schedule::Schedule(const Deck& deck, const EclipseState& es, const ParseContext&
else if (keyword.name() == "NUPCOL")
handleNUPCOL(keyword, currentStep);
else if (keyword.name() == "NODEPROP")
handleNODEPROP(keyword, currentStep);
else if (keyword.name() == "BRANPROP")
handleBRANPROP(keyword, currentStep);
else if (keyword.name() == "PYACTION")
handlePYACTION(python, input_path, keyword, currentStep);
@ -3116,6 +3126,70 @@ std::shared_ptr<const Python> Schedule::python() const
return this->python_handle;
}
void Schedule::updateNetwork(std::shared_ptr<Network::ExtNetwork> network, std::size_t report_step) {
this->m_network.update(report_step, std::move(network));
}
const Network::ExtNetwork& Schedule::network(std::size_t report_step) const {
return *this->m_network[report_step];
}
void Schedule::handleNODEPROP(const DeckKeyword& keyword, std::size_t report_step) {
using NP = ParserKeywords::NODEPROP;
auto ext_network = std::make_shared<Network::ExtNetwork>( this->network(report_step) );
for (const auto& record : keyword) {
const auto& name = record.getItem<NP::NAME>().get<std::string>(0);
const auto& pressure_item = record.getItem<NP::PRESSURE>();
bool as_choke = DeckItem::to_bool( record.getItem<NP::AS_CHOKE>().get<std::string>(0));
bool add_gas_lift_gas = DeckItem::to_bool( record.getItem<NP::ADD_GAS_LIFT_GAS>().get<std::string>(0));
Network::Node node{ name };
if (pressure_item.hasValue(0) && (pressure_item.get<double>(0) > 0))
node.terminal_pressure( pressure_item.getSIDouble(0) );
if (as_choke) {
std::string target_group = name;
const auto& target_item = record.getItem<NP::CHOKE_GROUP>();
if (target_item.hasValue(0))
target_group = target_item.get<std::string>(0);
if (target_group != name) {
if (this->hasGroup(name, report_step)) {
const auto& group = this->getGroup(name, report_step);
if (group.numWells() > 0)
throw std::invalid_argument("A manifold group must respond to its own target");
}
}
node.as_choke( target_group );
}
node.add_gas_lift_gas( add_gas_lift_gas );
ext_network->add_node( node );
}
this->updateNetwork(ext_network, report_step);
}
void Schedule::handleBRANPROP(const DeckKeyword& keyword, std::size_t report_step) {
using BP = ParserKeywords::BRANPROP;
auto ext_network = std::make_shared<Network::ExtNetwork>( this->network(report_step) );
for (const auto& record : keyword) {
const auto& downtree_node = record.getItem<BP::DOWNTREE_NODE>().get<std::string>(0);
const auto& uptree_node = record.getItem<BP::UPTREE_NODE>().get<std::string>(0);
int vfp_table = record.getItem<BP::VFP_TABLE>().get<int>(0);
auto alq_eq = Network::Branch::AlqEqfromString( record.getItem<BP::ALQ_SURFACE_DENSITY>().get<std::string>(0));
if (alq_eq == Network::Branch::AlqEQ::ALQ_INPUT) {
double alq_value = record.getItem<BP::ALQ>().get<double>(0);
ext_network->add_branch( Network::Branch(downtree_node, uptree_node, vfp_table, alq_value));
} else
ext_network->add_branch( Network::Branch(downtree_node, uptree_node, vfp_table, alq_eq));
}
this->updateNetwork(ext_network, report_step);
}
namespace {
/*
The insane trickery here (thank you Stackoverflow!) is to be able to provide a

View File

@ -18,6 +18,11 @@
"value_type": "STRING",
"default": "NO"
},
{
"name": "ADD_GAS_LIFT_GAS",
"value_type": "STRING",
"default": "NO"
},
{
"name": "CHOKE_GROUP",
"value_type": "STRING"

View File

@ -0,0 +1,228 @@
/*
Copyright 2020 Equinor ASA.
This file is part of the Open Porous Media project (OPM).
OPM is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OPM is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OPM. If not, see <http://www.gnu.org/licenses/>.
*/
#define BOOST_TEST_MODULE ScheduleTests
#include <boost/test/unit_test.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/Network/ExtNetwork.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/Network/Node.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/Network/Branch.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/Schedule.hpp>
#include <opm/parser/eclipse/EclipseState/Grid/EclipseGrid.hpp>
#include <opm/parser/eclipse/EclipseState/Grid/FieldPropsManager.hpp>
#include <opm/parser/eclipse/Parser/Parser.hpp>
#include <opm/parser/eclipse/Python/Python.hpp>
using namespace Opm;
Schedule make_schedule(const std::string& schedule_string) {
Parser parser;
auto python = std::make_shared<Python>();
Deck deck = parser.parseString(schedule_string);
EclipseGrid grid(10,10,10);
TableManager table ( deck );
FieldPropsManager fp( deck, Phases{true, true, true}, grid, table);
Runspec runspec (deck);
Schedule schedule(deck, grid , fp, runspec, python);
return schedule;
}
BOOST_AUTO_TEST_CASE(CreateNetwork) {
Network::ExtNetwork network;
BOOST_CHECK( !network.active() );
auto schedule = make_schedule("SCHEDULE\n");
auto network2 = schedule.network(0);
BOOST_CHECK( !network2.active() );
}
BOOST_AUTO_TEST_CASE(Branch) {
BOOST_CHECK_THROW( Network::Branch("down", "up", 100, Network::Branch::AlqEQ::ALQ_INPUT), std::logic_error);
}
BOOST_AUTO_TEST_CASE(INVALID_DOWNTREE_NODE) {
std::string deck_string = R"(
SCHEDULE
GRUPTREE
'PROD' 'FIELD' /
'M5S' 'PLAT-A' /
'M5N' 'PLAT-A' /
'C1' 'M5N' /
'F1' 'M5N' /
'B1' 'M5S' /
'G1' 'M5S' /
/
BRANPROP
-- Downtree Uptree #VFP ALQ
B1X PLAT-A 5 1* /
C1 PLAT-A 4 1* /
/
NODEPROP
-- Node_name Pr autoChock? addGasLift? Group_name
PLAT-A 21.0 NO NO 1* /
B1 1* NO NO 1* /
C1 1* NO NO 1* /
/
)";
BOOST_CHECK_THROW( make_schedule(deck_string), std::invalid_argument);
}
BOOST_AUTO_TEST_CASE(INVALID_UPTREE_NODE) {
std::string deck_string = R"(
SCHEDULE
GRUPTREE
'PROD' 'FIELD' /
'M5S' 'PLAT-A' /
'M5N' 'PLAT-A' /
'C1' 'M5N' /
'F1' 'M5N' /
'B1' 'M5S' /
'G1' 'M5S' /
/
BRANPROP
-- Downtree Uptree #VFP ALQ
B1 PLAT-AX 5 1* /
C1 PLAT-AX 4 1* /
/
NODEPROP
-- Node_name Pr autoChock? addGasLift? Group_name
PLAT-A 21.0 NO NO 1* /
B1 1* NO NO 1* /
C1 1* NO NO 1* /
/
)";
BOOST_CHECK_THROW( make_schedule(deck_string), std::invalid_argument);
}
BOOST_AUTO_TEST_CASE(INVALID_VFP_NODE) {
std::string deck_string = R"(
SCHEDULE
GRUPTREE
'PROD' 'FIELD' /
'M5S' 'PLAT-A' /
'M5N' 'PLAT-A' /
'C1' 'M5N' /
'F1' 'M5N' /
'B1' 'M5S' /
'G1' 'M5S' /
/
BRANPROP
-- Downtree Uptree #VFP ALQ
B1 PLAT-A 5 1* /
C1 PLAT-A 4 1* / --This is a choke branch - must have VFP=9999
/
NODEPROP
-- Node_name Pr autoChock? addGasLift? Group_name
PLAT-A 21.0 NO NO 1* /
B1 1* NO NO 1* /
C1 1* YES NO 1* /
/
)";
BOOST_CHECK_THROW( make_schedule(deck_string), std::invalid_argument);
}
BOOST_AUTO_TEST_CASE(OK) {
std::string deck_string = R"(
SCHEDULE
GRUPTREE
'PROD' 'FIELD' /
'M5S' 'PLAT-A' /
'M5N' 'PLAT-A' /
'C1' 'M5N' /
'F1' 'M5N' /
'B1' 'M5S' /
'G1' 'M5S' /
/
BRANPROP
-- Downtree Uptree #VFP ALQ
B1 PLAT-A 9999 1* /
C1 PLAT-A 9999 1* /
/
NODEPROP
-- Node_name Pr autoChock? addGasLift? Group_name
PLAT-A 21.0 NO NO 1* /
B1 1* YES NO 1* /
C1 1* YES NO 'GROUP' /
/
)";
auto sched = make_schedule(deck_string);
const auto& network = sched.network(0);
const auto& b1 = network.node("B1");
BOOST_CHECK( b1.as_choke() );
BOOST_CHECK(!b1.add_gas_lift_gas());
BOOST_CHECK( b1.name() == b1.target_group());
BOOST_CHECK(!b1.terminal_pressure());
const auto& p = network.node("PLAT-A");
BOOST_CHECK( p.terminal_pressure() );
BOOST_CHECK_EQUAL( p.terminal_pressure().value() , 21 * 100000 );
BOOST_CHECK( p == network.root() );
BOOST_CHECK_THROW( network.node("NO_SUCH_NODE"), std::out_of_range);
BOOST_CHECK_EQUAL( network.downtree_branches("PLAT-A").size(), 2);
for (const auto& b : network.downtree_branches("PLAT-A")) {
BOOST_CHECK_EQUAL(b.uptree_node(), "PLAT-A");
BOOST_CHECK(b.downtree_node() == "B1" || b.downtree_node() == "C1");
}
const auto& platform_uptree = network.uptree_branch("PLAT-A");
BOOST_CHECK( !platform_uptree.has_value() );
const auto& B1_uptree = network.uptree_branch("B1");
BOOST_CHECK( B1_uptree.has_value() );
BOOST_CHECK_EQUAL( B1_uptree->downtree_node(), "B1" );
BOOST_CHECK_EQUAL( B1_uptree->uptree_node(), "PLAT-A" );
BOOST_CHECK( network.active() );
}