From c7008d329a9318c3ac8f4ab784cd385c6bebe124 Mon Sep 17 00:00:00 2001 From: Joakim Hove Date: Sat, 25 Apr 2020 19:15:17 +0200 Subject: [PATCH] Add class ExtNetwork for extended networks --- CMakeLists_files.cmake | 7 + .../EclipseState/Schedule/Network/Branch.hpp | 73 ++++++ .../Schedule/Network/ExtNetwork.hpp | 66 +++++ .../EclipseState/Schedule/Network/Node.hpp | 64 +++++ .../EclipseState/Schedule/Schedule.hpp | 11 + src/opm/parser/eclipse/Deck/DeckItem.cpp | 2 +- .../EclipseState/Schedule/Network/Branch.cpp | 103 ++++++++ .../Schedule/Network/ExtNetwork.cpp | 136 +++++++++++ .../EclipseState/Schedule/Network/Node.cpp | 72 ++++++ .../EclipseState/Schedule/Schedule.cpp | 74 ++++++ .../share/keywords/000_Eclipse100/N/NODEPROP | 5 + tests/parser/NetworkTests.cpp | 228 ++++++++++++++++++ 12 files changed, 840 insertions(+), 1 deletion(-) create mode 100644 opm/parser/eclipse/EclipseState/Schedule/Network/Branch.hpp create mode 100644 opm/parser/eclipse/EclipseState/Schedule/Network/ExtNetwork.hpp create mode 100644 opm/parser/eclipse/EclipseState/Schedule/Network/Node.hpp create mode 100644 src/opm/parser/eclipse/EclipseState/Schedule/Network/Branch.cpp create mode 100644 src/opm/parser/eclipse/EclipseState/Schedule/Network/ExtNetwork.cpp create mode 100644 src/opm/parser/eclipse/EclipseState/Schedule/Network/Node.cpp create mode 100644 tests/parser/NetworkTests.cpp diff --git a/CMakeLists_files.cmake b/CMakeLists_files.cmake index a34d27b04..d296cb53f 100644 --- a/CMakeLists_files.cmake +++ b/CMakeLists_files.cmake @@ -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 diff --git a/opm/parser/eclipse/EclipseState/Schedule/Network/Branch.hpp b/opm/parser/eclipse/EclipseState/Schedule/Network/Branch.hpp new file mode 100644 index 000000000..dfe4b59cb --- /dev/null +++ b/opm/parser/eclipse/EclipseState/Schedule/Network/Branch.hpp @@ -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 . +*/ + + +#ifndef NETWORK_BRANCH_HPP +#define NETWORK_BRANCH_HPP + +#include + +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 vfp_table() const; + AlqEQ alq_eq() const; + std::optional alq_value() const; + + static Branch serializeObject(); + bool operator==(const Branch& other) const; + + template + 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 m_alq_value; + AlqEQ m_alq_eq; +}; + +} +} +#endif diff --git a/opm/parser/eclipse/EclipseState/Schedule/Network/ExtNetwork.hpp b/opm/parser/eclipse/EclipseState/Schedule/Network/ExtNetwork.hpp new file mode 100644 index 000000000..a1c9c3e28 --- /dev/null +++ b/opm/parser/eclipse/EclipseState/Schedule/Network/ExtNetwork.hpp @@ -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 . +*/ + + +#ifndef EXT_NETWORK_HPP +#define EXT_NETWORK_HPP + +#include +#include +#include +#include + +#include +#include + +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 downtree_branches(const std::string& node) const; + std::optional uptree_branch(const std::string& node) const; + + + bool operator==(const ExtNetwork& other) const; + static ExtNetwork serializeObject(); + + template + void serializeOp(Serializer& serializer) + { + serializer.vector(m_branches); + serializer.map(m_nodes); + } + +private: + std::vector m_branches; + std::map m_nodes; +}; + +} +} +#endif diff --git a/opm/parser/eclipse/EclipseState/Schedule/Network/Node.hpp b/opm/parser/eclipse/EclipseState/Schedule/Network/Node.hpp new file mode 100644 index 000000000..90deb1e65 --- /dev/null +++ b/opm/parser/eclipse/EclipseState/Schedule/Network/Node.hpp @@ -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 . +*/ + + +#ifndef NETWORK_NODE_HPP +#define NETWORK_NODE_HPP + +#include +#include + +namespace Opm { +namespace Network { + +class Node { +public: + Node() = default; + Node(const std::string& name); + + const std::string& name() const; + const std::optional& terminal_pressure() const; + bool as_choke() const; + bool add_gas_lift_gas() const; + const std::optional& 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 + 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 m_terminal_pressure; + std::optional m_choke_target_group; + bool m_add_gas_lift_gas = false; +}; +} +} +#endif diff --git a/opm/parser/eclipse/EclipseState/Schedule/Schedule.hpp b/opm/parser/eclipse/EclipseState/Schedule/Schedule.hpp index f5a05e872..1fa46982a 100644 --- a/opm/parser/eclipse/EclipseState/Schedule/Schedule.hpp +++ b/opm/parser/eclipse/EclipseState/Schedule/Schedule.hpp @@ -40,6 +40,7 @@ #include #include #include +#include #include #include #include @@ -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 python() const; @@ -305,6 +309,7 @@ namespace Opm gconsump.serializeOp(serializer); global_whistctl_mode.template serializeOp(serializer); m_actions.serializeOp(serializer); + m_network.serializeOp(serializer); rft_config.serializeOp(serializer); m_nupcol.template serializeOp(serializer); restart_config.serializeOp(serializer); @@ -340,6 +345,7 @@ namespace Opm DynamicState> gconsump; DynamicState global_whistctl_mode; DynamicState> m_actions; + DynamicState> m_network; RFTConfig rft_config; DynamicState m_nupcol; RestartConfig restart_config; @@ -367,6 +373,8 @@ namespace Opm const UnitSystem& unit_system); DynamicState> rpt_config; + void updateNetwork(std::shared_ptr 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, 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 python, const std::string& input_path, const DeckKeyword& keyword, size_t currentStep); void handleNUPCOL( const DeckKeyword& keyword, size_t currentStep); diff --git a/src/opm/parser/eclipse/Deck/DeckItem.cpp b/src/opm/parser/eclipse/Deck/DeckItem.cpp index fc4b63062..828f90613 100644 --- a/src/opm/parser/eclipse/Deck/DeckItem.cpp +++ b/src/opm/parser/eclipse/Deck/DeckItem.cpp @@ -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]; } diff --git a/src/opm/parser/eclipse/EclipseState/Schedule/Network/Branch.cpp b/src/opm/parser/eclipse/EclipseState/Schedule/Network/Branch.cpp new file mode 100644 index 000000000..7d9133218 --- /dev/null +++ b/src/opm/parser/eclipse/EclipseState/Schedule/Network/Branch.cpp @@ -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 . +*/ + + +#include + +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 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 Branch::alq_value() const { + return this->m_alq_value; +} + +} +} diff --git a/src/opm/parser/eclipse/EclipseState/Schedule/Network/ExtNetwork.cpp b/src/opm/parser/eclipse/EclipseState/Schedule/Network/ExtNetwork.cpp new file mode 100644 index 000000000..539632bcd --- /dev/null +++ b/src/opm/parser/eclipse/EclipseState/Schedule/Network/ExtNetwork.cpp @@ -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 . +*/ +#include +#include + +#include + +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 ExtNetwork::uptree_branch(const std::string& node) const { + if (!this->has_node(node)) + throw std::out_of_range("No such node: " + node); + + std::vector 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 ExtNetwork::downtree_branches(const std::string& node) const { + if (!this->has_node(node)) + throw std::out_of_range("No such node: " + node); + + std::vector 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) }); +} + +} +} diff --git a/src/opm/parser/eclipse/EclipseState/Schedule/Network/Node.cpp b/src/opm/parser/eclipse/EclipseState/Schedule/Network/Node.cpp new file mode 100644 index 000000000..634e45a40 --- /dev/null +++ b/src/opm/parser/eclipse/EclipseState/Schedule/Network/Node.cpp @@ -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 . +*/ +#include + +#include + +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& Node::target_group() const { + return this->m_choke_target_group; +} + +const std::optional& 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; +} + + +} +} diff --git a/src/opm/parser/eclipse/EclipseState/Schedule/Schedule.cpp b/src/opm/parser/eclipse/EclipseState/Schedule/Schedule.cpp index 5f03ada43..704047a89 100644 --- a/src/opm/parser/eclipse/EclipseState/Schedule/Schedule.cpp +++ b/src/opm/parser/eclipse/EclipseState/Schedule/Schedule.cpp @@ -19,6 +19,7 @@ #include #include +#include #include #include #include @@ -35,6 +36,7 @@ #include #include #include +#include #include #include #include @@ -61,6 +63,7 @@ #include #include #include +#include #include #include #include @@ -145,6 +148,7 @@ std::pair restart_info(const RestartIO::RstState * rst gconsump(this->m_timeMap, std::make_shared() ), global_whistctl_mode(this->m_timeMap, Well::ProducerCMode::CMODE_UNDEFINED), m_actions(this->m_timeMap, std::make_shared()), + m_network(this->m_timeMap, std::make_shared()), 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 Schedule::python() const return this->python_handle; } + +void Schedule::updateNetwork(std::shared_ptr 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( this->network(report_step) ); + for (const auto& record : keyword) { + const auto& name = record.getItem().get(0); + const auto& pressure_item = record.getItem(); + bool as_choke = DeckItem::to_bool( record.getItem().get(0)); + bool add_gas_lift_gas = DeckItem::to_bool( record.getItem().get(0)); + Network::Node node{ name }; + + if (pressure_item.hasValue(0) && (pressure_item.get(0) > 0)) + node.terminal_pressure( pressure_item.getSIDouble(0) ); + + if (as_choke) { + std::string target_group = name; + const auto& target_item = record.getItem(); + if (target_item.hasValue(0)) + target_group = target_item.get(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( this->network(report_step) ); + for (const auto& record : keyword) { + const auto& downtree_node = record.getItem().get(0); + const auto& uptree_node = record.getItem().get(0); + int vfp_table = record.getItem().get(0); + auto alq_eq = Network::Branch::AlqEqfromString( record.getItem().get(0)); + if (alq_eq == Network::Branch::AlqEQ::ALQ_INPUT) { + double alq_value = record.getItem().get(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 diff --git a/src/opm/parser/eclipse/share/keywords/000_Eclipse100/N/NODEPROP b/src/opm/parser/eclipse/share/keywords/000_Eclipse100/N/NODEPROP index f4e683154..ab6667c68 100644 --- a/src/opm/parser/eclipse/share/keywords/000_Eclipse100/N/NODEPROP +++ b/src/opm/parser/eclipse/share/keywords/000_Eclipse100/N/NODEPROP @@ -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" diff --git a/tests/parser/NetworkTests.cpp b/tests/parser/NetworkTests.cpp new file mode 100644 index 000000000..ed5476f9e --- /dev/null +++ b/tests/parser/NetworkTests.cpp @@ -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 . +*/ + +#define BOOST_TEST_MODULE ScheduleTests + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace Opm; + +Schedule make_schedule(const std::string& schedule_string) { + Parser parser; + auto python = std::make_shared(); + 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() ); +} +