diff --git a/CMakeLists_files.cmake b/CMakeLists_files.cmake index f66858321..e51b422a4 100644 --- a/CMakeLists_files.cmake +++ b/CMakeLists_files.cmake @@ -298,6 +298,7 @@ if(ENABLE_ECL_OUTPUT) src/opm/io/eclipse/rst/connection.cpp src/opm/io/eclipse/rst/group.cpp src/opm/io/eclipse/rst/header.cpp + src/opm/io/eclipse/rst/network.cpp src/opm/io/eclipse/rst/udq.cpp src/opm/io/eclipse/rst/segment.cpp src/opm/io/eclipse/rst/state.cpp @@ -910,6 +911,7 @@ if(ENABLE_ECL_OUTPUT) opm/io/eclipse/rst/connection.hpp opm/io/eclipse/rst/group.hpp opm/io/eclipse/rst/header.hpp + opm/io/eclipse/rst/network.hpp opm/io/eclipse/rst/segment.hpp opm/io/eclipse/rst/state.hpp opm/io/eclipse/rst/udq.hpp diff --git a/opm/io/eclipse/rst/network.hpp b/opm/io/eclipse/rst/network.hpp new file mode 100644 index 000000000..ef961bf2d --- /dev/null +++ b/opm/io/eclipse/rst/network.hpp @@ -0,0 +1,95 @@ +/* + Copyright 2021 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 RST_NETWORK_HPP +#define RST_NETWORK_HPP + +#include +#include +#include +#include + +namespace Opm { + class UnitSystem; +} // namespace Opm + +namespace Opm { namespace EclIO { + class RestartFileView; +}} // namespace Opm::EclIO + +namespace Opm { namespace RestartIO { + + class RstNetwork + { + public: + /// Single branch in extended network model. + struct Branch + { + /// Downtree node. Index into 'nodes' array. + int down{-1}; + + /// Uptree node. Index into 'nodes' array. + int up{-1}; + + /// One-based VFP table ID. + int vfp{-1}; + }; + + /// Single node in extended network model. + struct Node + { + /// Name of network node. + std::string name{}; + + /// Fixed pressure for terminal node. Nullopt if not terminal. + std::optional terminal_pressure{}; + + /// Group whose rate target the choking mechanism attempts to + /// match. Nullopt if this node does not act as a choke or if + /// choking is disabled. + std::optional as_choke{}; + + /// Whether or not to include lift gas of subordinate wells as + /// part of the produced gas entering the network at this node. + bool add_lift_gas{false}; + }; + + explicit RstNetwork(std::shared_ptr rstView, + const UnitSystem& usys); + + bool isActive() const; + + const std::vector& branches() const + { + return this->branches_; + } + + const std::vector& nodes() const + { + return this->nodes_; + } + + private: + std::vector branches_{}; + std::vector nodes_{}; + }; + +}} // namespace Opm::RestartIO + +#endif // RST_NETWORK_HPP diff --git a/opm/io/eclipse/rst/state.hpp b/opm/io/eclipse/rst/state.hpp index bb1528872..0a0d6fe10 100644 --- a/opm/io/eclipse/rst/state.hpp +++ b/opm/io/eclipse/rst/state.hpp @@ -24,22 +24,22 @@ #include #include -#include #include -#include -#include -#include #include +#include +#include +#include +#include +#include + +#include +#include #include -#include -#include - namespace Opm { class EclipseGrid; class Parser; - class Parser; } // namespace Opm namespace Opm { namespace EclIO { @@ -62,6 +62,7 @@ struct RstState { ::Opm::UnitSystem unit_system; RstHeader header; RstAquifer aquifers; + RstNetwork network; std::vector wells; std::vector groups; std::vector udqs; diff --git a/src/opm/io/eclipse/rst/network.cpp b/src/opm/io/eclipse/rst/network.cpp new file mode 100644 index 000000000..1af1b0e59 --- /dev/null +++ b/src/opm/io/eclipse/rst/network.cpp @@ -0,0 +1,250 @@ +/* + Copyright 2021 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 +#include + +#include + +#include +#include +#include +#include +#include + +#include + +namespace VI = ::Opm::RestartIO::Helpers::VectorItems; + +namespace { + template + boost::iterator_range::const_iterator> + getDataWindow(const std::vector& arr, + const std::size_t windowSize, + const std::size_t entity) + { + const auto off = windowSize * entity; + + auto begin = arr.begin() + off; + auto end = begin + windowSize; + + return { begin, end }; + } +} + +// --------------------------------------------------------------------------- + +class BranchVectors +{ +public: + template + using Window = boost::iterator_range< + typename std::vector::const_iterator + >; + + explicit BranchVectors(const std::vector& intehead, + std::shared_ptr rst_view); + + std::size_t numActiveBranches() const + { + return this->numActiveBranches_; + } + + Window ibran(const std::size_t branchID) const; + +private: + std::size_t numActiveBranches_; + std::size_t numIBranElem_; + + std::shared_ptr rstView_; +}; + +BranchVectors::BranchVectors(const std::vector& intehead, + std::shared_ptr rst_view) + : numActiveBranches_(intehead[VI::intehead::NOACTBR]) + , numIBranElem_ (intehead[VI::intehead::NIBRAN]) + , rstView_ (std::move(rst_view)) +{} + +BranchVectors::Window +BranchVectors::ibran(const std::size_t branchID) const +{ + return getDataWindow(this->rstView_->getKeyword("IBRAN"), + this->numIBranElem_, branchID); +} + +// --------------------------------------------------------------------------- + +class NodeVectors +{ +public: + template + using Window = boost::iterator_range< + typename std::vector::const_iterator + >; + + explicit NodeVectors(const std::vector& intehead, + std::shared_ptr rst_view); + + std::size_t numActiveNodes() const + { + return this->numActiveNodes_; + } + + Window inode(const std::size_t nodeID) const; + Window rnode(const std::size_t nodeID) const; + Window znode(const std::size_t nodeID) const; + +private: + std::size_t numActiveNodes_; + std::size_t numINodeElem_; + std::size_t numRNodeElem_; + std::size_t numZNodeElem_; + + std::shared_ptr rstView_; +}; + +NodeVectors::NodeVectors(const std::vector& intehead, + std::shared_ptr rst_view) + : numActiveNodes_(intehead[VI::intehead::NOACTNOD]) + , numINodeElem_ (intehead[VI::intehead::NINODE]) + , numRNodeElem_ (intehead[VI::intehead::NRNODE]) + , numZNodeElem_ (intehead[VI::intehead::NZNODE]) + , rstView_ (std::move(rst_view)) +{} + +NodeVectors::Window +NodeVectors::inode(const std::size_t nodeID) const +{ + return getDataWindow(this->rstView_->getKeyword("INODE"), + this->numINodeElem_, nodeID); +} + +NodeVectors::Window +NodeVectors::rnode(const std::size_t nodeID) const +{ + return getDataWindow(this->rstView_->getKeyword("RNODE"), + this->numRNodeElem_, nodeID); +} + +NodeVectors::Window +NodeVectors::znode(const std::size_t nodeID) const +{ + return getDataWindow(this->rstView_->getKeyword("ZNODE"), + this->numZNodeElem_, nodeID); +} + +// --------------------------------------------------------------------------- + +namespace { + int branch_vfp_no_pressure_loss() + { + return 9999; + } + + template + Opm::RestartIO::RstNetwork::Branch make_branch(const IBranArray ibran) + { + using Ix = VI::IBran::index; + + auto branch = Opm::RestartIO::RstNetwork::Branch{}; + + branch.down = ibran[Ix::DownTreeNode] - 1; // Index into ZNODE + branch.up = ibran[Ix::UpTreeNode] - 1; // Index into ZNODE + branch.vfp = (ibran[Ix::VfpTableNo] > 0) + ? ibran[Ix::VfpTableNo] // One-based VFP table ID + : branch_vfp_no_pressure_loss(); + + return branch; + } + + template + Opm::RestartIO::RstNetwork::Node + make_node(const std::string& name, + const Opm::UnitSystem& usys, + const INodeArray inode, + const RNodeArray rnode) + { + auto node = Opm::RestartIO::RstNetwork::Node{}; + node.name = name; + + if (inode[VI::INode::index::FixedPresNode] != 0) { + node.terminal_pressure = + usys.to_si(Opm::UnitSystem::measure::pressure, + rnode[VI::RNode::index::PressureLimit]); + } + + return node; + } + + std::vector + create_branches(std::shared_ptr rstView) + { + auto branches = std::vector{}; + + const auto branchData = BranchVectors { rstView->intehead(), rstView }; + const auto numBranches = branchData.numActiveBranches(); + + branches.reserve(numBranches); + + for (auto branchID = 0*numBranches; branchID < numBranches; ++branchID) { + branches.push_back(make_branch(branchData.ibran(branchID))); + } + + return branches; + } + + std::vector + create_nodes(std::shared_ptr rstView, + const Opm::UnitSystem& usys) + { + auto nodes = std::vector{}; + + const auto nodeData = NodeVectors { rstView->intehead(), rstView }; + const auto numNodes = nodeData.numActiveNodes(); + + nodes.reserve(numNodes); + + for (auto nodeID = 0*numNodes; nodeID < numNodes; ++nodeID) { + const auto znode = nodeData.znode(nodeID); + + nodes.push_back(make_node(znode[VI::ZNode::index::NodeName], usys, + nodeData.inode(nodeID), + nodeData.rnode(nodeID))); + } + + return nodes; + } +} + +Opm::RestartIO::RstNetwork::RstNetwork(std::shared_ptr rstView, + const UnitSystem& usys) + : branches_{ create_branches(rstView) } + , nodes_ { create_nodes (rstView, usys) } +{} + +bool Opm::RestartIO::RstNetwork::isActive() const +{ + return ! (this->branches_.empty() || + this->nodes_.empty()); +} diff --git a/src/opm/io/eclipse/rst/state.cpp b/src/opm/io/eclipse/rst/state.cpp index 15d28c4d8..1ce511cfe 100644 --- a/src/opm/io/eclipse/rst/state.cpp +++ b/src/opm/io/eclipse/rst/state.cpp @@ -16,33 +16,41 @@ along with OPM. If not, see . */ +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + #include #include #include #include #include -#include - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - - namespace { std::string udq_define(const std::vector& zudl, @@ -78,6 +86,7 @@ RstState::RstState(std::shared_ptr rstView, : unit_system(rstView->intehead()[VI::intehead::UNIT]) , header(unit_system, rstView->intehead(), rstView->logihead(), rstView->doubhead()) , aquifers(rstView, grid, unit_system) + , network(rstView, unit_system) { this->load_tuning(rstView->intehead(), rstView->doubhead()); } diff --git a/src/opm/parser/eclipse/EclipseState/Schedule/Schedule.cpp b/src/opm/parser/eclipse/EclipseState/Schedule/Schedule.cpp index 3edb3f4ac..7e5650d39 100644 --- a/src/opm/parser/eclipse/EclipseState/Schedule/Schedule.cpp +++ b/src/opm/parser/eclipse/EclipseState/Schedule/Schedule.cpp @@ -47,6 +47,7 @@ #include #include #include +#include #include #include #include @@ -1561,6 +1562,45 @@ namespace { if (!rst_state.wlists.empty()) this->snapshots.back().wlist_manager.update( WListManager(rst_state) ); + + if (rst_state.network.isActive()) { + auto network = this->snapshots.back().network(); + + // Note: We presently support only the default value of BRANPROP(4). + const auto alq_value = + ParserKeywords::BRANPROP::ALQ::defaultValue; + + const auto& rst_nodes = rst_state.network.nodes(); + for (const auto& rst_branch : rst_state.network.branches()) { + if ((rst_branch.down < 0) || (rst_branch.up < 0)) { + // Prune branches to non-existent nodes. + continue; + } + + const auto& downtree_node = rst_nodes[rst_branch.down].name; + const auto& uptree_node = rst_nodes[rst_branch.up].name; + + network.add_branch({ downtree_node, uptree_node, rst_branch.vfp, alq_value }); + } + + for (const auto& rst_node : rst_nodes) { + auto node = Network::Node { rst_node.name }; + + if (rst_node.terminal_pressure.has_value()) { + node.terminal_pressure(rst_node.terminal_pressure.value()); + } + + if (rst_node.as_choke.has_value()) { + node.as_choke(rst_node.as_choke.value()); + } + + node.add_gas_lift_gas(rst_node.add_lift_gas); + + network.add_node(std::move(node)); + } + + this->snapshots.back().network.update(std::move(network)); + } } std::shared_ptr Schedule::python() const diff --git a/tests/parser/ScheduleRestartTests.cpp b/tests/parser/ScheduleRestartTests.cpp index 1bea132ce..5cc6afbfd 100644 --- a/tests/parser/ScheduleRestartTests.cpp +++ b/tests/parser/ScheduleRestartTests.cpp @@ -123,7 +123,7 @@ std::tuple load_schedule_pair(const std EclipseState ecl_state_restart(restart_deck); Schedule restart_sched(restart_deck, ecl_state_restart, python, {}, &rst_state); - return {sched, restart_sched, rst_state}; + return {sched, restart_sched, std::move(rst_state)}; }