Repository: OPM/opm-common
Pull request #4535: New application for viewing a production network
Author: login: tskille
Created: 2025-03-21T08:07:44Z | Merged: 2025-04-11T12:19:13Z

The PR is introducing a new application networkgraph. This can be used to visualize a production network (extended network model). This visualization is also including wells. 

Input for this program can be both a data deck and an unified restart file.  The user can specify the report step to be visualized with option -r. Default report step is the final report step.

Output from this application run with model 

https://github.com/OPM/opm-tests/blob/master/model5/5_NETWORK_MODEL5_STDW.DATA

output to the terminal will be like this.

```
Report step : 01 'JUL' 2020 00:00:00


  o (PLAT-A) --[5]-- +(B1) :  B-1H B-2H B-3H
  \
   --[4]-- +(C1) :  C-1H C-2H


Fixed pressure nodes: 

  PLAT-A = 21.00

```





Diff:
diff --git a/CMakeLists_files.cmake b/CMakeLists_files.cmake
index 63ae83d6947..83c171b2eed 100644
--- a/CMakeLists_files.cmake
+++ b/CMakeLists_files.cmake
@@ -817,6 +817,7 @@ if(ENABLE_ECL_INPUT)
     examples/opmhash.cpp
     examples/rst_deck.cpp
     examples/wellgraph.cpp
+    examples/networkgraph.cpp
     examples/make_ext_smry.cpp
     examples/co2brinepvt.cpp
     examples/hysteresis.cpp
diff --git a/examples/networkgraph.cpp b/examples/networkgraph.cpp
new file mode 100644
index 00000000000..fac4cddc2f2
--- /dev/null
+++ b/examples/networkgraph.cpp
@@ -0,0 +1,989 @@
+/*
+  Copyright 2025 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 <getopt.h>
+#include <iostream>
+#include <sstream>
+
+#include <opm/io/eclipse/ERst.hpp>
+
+#include <opm/input/eclipse/Deck/Deck.hpp>
+
+#include <opm/input/eclipse/Parser/ErrorGuard.hpp>
+#include <opm/input/eclipse/Parser/InputErrorAction.hpp>
+#include <opm/input/eclipse/Parser/ParseContext.hpp>
+#include <opm/input/eclipse/Parser/Parser.hpp>
+
+
+class Node
+{
+public:
+    explicit Node(const std::string& name);
+
+    std::string name() const
+    {
+        return m_name;
+    }
+    void set_outlet(std::shared_ptr<Node> outlet)
+    {
+        m_outlet = outlet;
+    };
+    std::shared_ptr<Node> get_outlet()
+    {
+        return m_outlet;
+    };
+
+    void add_inlet_node(std::shared_ptr<Node> n);
+
+    void set_vfp(int vfp)
+    {
+        m_vfp = vfp;
+    };
+    int get_vfp()
+    {
+        return m_vfp;
+    };
+
+    void set_fixed_pres(double pres)
+    {
+        m_fixed_pres = pres;
+    };
+    double get_fixed_pres()
+    {
+        return m_fixed_pres;
+    };
+
+    int get_xpos()
+    {
+        return m_xpos;
+    };
+
+    void print(std::stringstream& netw_str);
+
+    bool delete_from_inlet_list(const std::string& name);
+
+    bool add_well(const std::string& name);
+
+    void reset_outlet()
+    {
+        m_outlet = nullptr;
+    };
+
+private:
+    int m_vfp;
+    int m_xpos = -1;
+    double m_fixed_pres = -1.0;
+    int m_ind = -1;
+
+    std::string m_name;
+    std::shared_ptr<Node> m_outlet;
+    std::vector<std::shared_ptr<Node>> m_inlet_list;
+    std::vector<std::string> m_well_list;
+
+    std::shared_ptr<Node> next_branch();
+};
+
+
+Node::Node(const std::string& name)
+    : m_name(name)
+{
+    m_vfp = -1;
+}
+
+std::shared_ptr<Node>
+Node::next_branch()
+{
+
+    std::shared_ptr<Node> p1 = m_outlet;
+
+    while (p1 != nullptr) {
+        for (size_t n = 0; n < p1->m_inlet_list.size(); n++) {
+            if (p1->m_inlet_list[n]->m_xpos == -1) {
+                return p1;
+            }
+        }
+        p1 = p1->m_outlet;
+    }
+
+    return p1;
+}
+
+bool
+Node::delete_from_inlet_list(const std::string& name)
+{
+
+    int ind = -1;
+
+    for (size_t n = 0; n < m_inlet_list.size(); n++)
+        if (m_inlet_list[n]->name() == name)
+            ind = n;
+
+    if (ind < 0) {
+        return false;
+    } else {
+        m_inlet_list.erase(m_inlet_list.begin() + ind);
+        return true;
+    }
+}
+
+bool
+Node::add_well(const std::string& name)
+{
+    if (m_inlet_list.size() > 0)
+        return false;
+
+    m_well_list.push_back(name);
+
+    return true;
+}
+
+
+void
+Node::print(std::stringstream& netw_str)
+{
+
+    netw_str.seekg(0, std::ios::end);
+    int init_length = netw_str.tellg();
+
+    int pos_lineshift = 0;
+
+    auto p = netw_str.str().find_last_of("\n");
+
+    if (p != std::string::npos)
+        pos_lineshift = p + 1;
+
+    if (m_outlet == nullptr) {
+        netw_str << "  o (" << m_name << ")";
+        m_xpos = netw_str.str().find_first_of("o", init_length) - pos_lineshift;
+    } else if (m_vfp == 9999) {
+        netw_str << " --- +(" << m_name << ")";
+        m_xpos = netw_str.str().find_first_of("+", init_length) - pos_lineshift;
+    } else {
+        netw_str << " --[" << m_vfp << "]-- +(" << m_name << ")";
+        m_xpos = netw_str.str().find_first_of("+", init_length) - pos_lineshift;
+    }
+
+    for (size_t m = 0; m < m_inlet_list.size(); m++)
+        m_inlet_list[m]->print(netw_str);
+
+    if (m_inlet_list.size() == 0) {
+        auto next_br = next_branch();
+
+        netw_str << " : ";
+
+        for (auto& well : m_well_list)
+            netw_str << " " << well;
+
+        if (next_br != nullptr) {
+            std::string xpos_str(next_br->m_xpos, ' ');
+            netw_str << "\n" << xpos_str << "\\\n" << xpos_str;
+        }
+    }
+}
+
+
+void
+Node::add_inlet_node(std::shared_ptr<Node> node)
+{
+    std::vector<std::shared_ptr<Node>>::iterator exist;
+
+    exist = std::find_if(
+        m_inlet_list.begin(), m_inlet_list.end(), [&](const auto& val) { return val->name() == node->name(); });
+
+    if (exist != m_inlet_list.end()) {
+        m_inlet_list.erase(exist);
+    }
+
+    m_inlet_list.push_back(node);
+}
+
+
+class NetWork
+{
+public:
+    // upptree, downtree, vfp
+    using bran_input_type = std::tuple<std::string, std::string, int>;
+
+    // name and fixed pressure
+    using node_input_type = std::tuple<std::string, int>;
+
+    // well and group name
+    using well_input_type = std::tuple<std::string, std::string>;
+
+    explicit NetWork(const std::string& filename);
+
+    void print_report_steps();
+    int number_report_steps()
+    {
+        return m_report_time_list.size();
+    }
+
+    void build_network(int rstep);
+    void print_network(int rstep);
+
+private:
+    time_t time_from_rec(const Opm::DeckRecord& rec);
+    time_t time_from_rst(const std::string& rstfile, int rstep);
+
+    bool node_exist(const std::string& name);
+
+    void add_node(const std::string& name);
+    void add_branch(const std::string& downtree, const std::string& uptree, int vfp);
+    void delete_branch(const std::string& downtree, const std::string& uptree);
+
+    void br_input_from_rst(const std::string& rstfile, std::vector<int> rstep_vect);
+
+    void print_network_input();
+    void parse_data_deck(const std::filesystem::path& inputFileName);
+    void parse_unrst(const std::filesystem::path& inputFileName);
+
+    std::stringstream m_netw_str;
+
+    time_t m_start_date;
+    time_t m_rst_time;
+
+    bool m_from_unrst = false;
+
+    std::vector<time_t> m_report_time_list;
+    std::vector<std::vector<bran_input_type>> m_bran_input_list;
+    std::vector<std::vector<node_input_type>> m_node_input_list;
+    std::vector<std::vector<well_input_type>> m_well_input_list;
+
+    std::string time_str(time_t t1);
+
+    std::vector<std::shared_ptr<Node>> m_node_list;
+    std::vector<std::shared_ptr<Node>> m_top_node_list;
+};
+
+
+NetWork::NetWork(const std::string& filename)
+{
+    std::filesystem::path inputFileName {filename};
+
+    if (inputFileName.extension() == ".DATA") {
+
+        parse_data_deck(inputFileName);
+
+    } else if (inputFileName.extension() == ".UNRST") {
+
+        parse_unrst(inputFileName);
+        m_from_unrst = true;
+
+    } else {
+        std::cout << "\n!Error, unsupported file type " << filename << "\n\n";
+        exit(1);
+    }
+}
+
+
+void
+NetWork::parse_data_deck(const std::filesystem::path& inputFileName)
+{
+
+    Opm::ParseContext parseContext;
+    parseContext.update(Opm::ParseContext::PARSE_UNKNOWN_KEYWORD, Opm::InputErrorAction::IGNORE);
+    parseContext.update(Opm::ParseContext::PARSE_RANDOM_TEXT, Opm::InputErrorAction::IGNORE);
+    parseContext.update(Opm::ParseContext::PARSE_EXTRA_RECORDS, Opm::InputErrorAction::IGNORE);
+    parseContext.update(Opm::ParseContext::PARSE_RANDOM_SLASH, Opm::InputErrorAction::IGNORE);
+
+    std::vector<Opm::Ecl::SectionType> sections = {Opm::Ecl::RUNSPEC, Opm::Ecl::SOLUTION, Opm::Ecl::SCHEDULE};
+
+    Opm::Parser parser;
+
+    Opm::Deck deck_schecule;
+
+    try {
+        deck_schecule = parser.parseFile(inputFileName, parseContext, sections);
+
+    } catch (const std::exception& e) {
+
+        std::cout << "\n!Error parsing data deck " << inputFileName << "\n\n";
+        std::cout << e.what() << "\n\n";
+
+        exit(1);
+    }
+
+    bool restart = false;
+    bool skiprest = false;
+
+    time_t last_time;
+
+    auto network_keyw = deck_schecule["NETWORK"];
+
+    if (network_keyw.size() == 0) {
+        std::cout << "\n > !Error, data deck " << inputFileName << " doesn't include a production network \n\n";
+        exit(1);
+    }
+
+    for (auto keyw : deck_schecule) {
+
+        if (keyw.name() == "START") {
+            m_node_input_list.push_back({});
+            m_bran_input_list.push_back({});
+            m_well_input_list.push_back({});
+            m_start_date = time_from_rec(keyw[0]);
+
+            last_time = m_start_date;
+
+        } else if (keyw.name() == "TSTEP") {
+
+            for (size_t n = 0; n < keyw[0].getItem(0).data_size(); n++) {
+                auto dt = keyw[0].getItem(0).get<double>(n);
+                last_time = last_time + static_cast<int>(dt * 24.0 * 3600.0);
+
+                if (!skiprest) {
+                    m_report_time_list.push_back(last_time);
+                    m_node_input_list.push_back({});
+                    m_bran_input_list.push_back({});
+                    m_well_input_list.push_back({});
+                }
+
+                if ((skiprest) && (last_time >= m_rst_time))
+                    skiprest = false;
+            }
+
+        } else if (keyw.name() == "DATES") {
+            for (auto& rec : keyw) {
+                last_time = time_from_rec(rec);
+
+                if (m_report_time_list.size() == 0) {
+                    if ((last_time <= m_start_date) && (!skiprest)) {
+                        std::cout << "\n!Error, next report step '" << time_str(last_time)
+                                  << "' has already passed \n\n";
+                        exit(1);
+                    }
+                } else {
+                    if ((last_time <= m_report_time_list.back()) && (!skiprest)) {
+                        std::cout << "\n!Error, next report step '" << time_str(last_time)
+                                  << "' has already passed \n\n";
+                        exit(1);
+                    }
+                }
+
+                if (!skiprest) {
+                    m_report_time_list.push_back(last_time);
+                    m_node_input_list.push_back({});
+                    m_bran_input_list.push_back({});
+                    m_well_input_list.push_back({});
+                }
+
+                if ((skiprest) && (last_time >= m_rst_time))
+                    skiprest = false;
+            }
+
+        } else if (keyw.name() == "RESTART") {
+
+            auto rst_file = keyw[0].getItem(0).get<std::string>(0) + ".UNRST";
+            auto rst_rstep = keyw[0].getItem(1).get<int>(0);
+
+            br_input_from_rst(rst_file, {rst_rstep});
+
+            restart = true;
+
+        } else if (keyw.name() == "SKIPREST") {
+
+            if (restart)
+                skiprest = true;
+
+        } else if ((keyw.name() == "BRANPROP") && (!skiprest)) {
+            for (auto& rec : keyw) {
+
+                auto downtree = rec.getItem(0).get<std::string>(0);
+                auto uptree = rec.getItem(1).get<std::string>(0);
+                auto vfp = rec.getItem(2).get<int>(0);
+
+                auto br = std::make_tuple(downtree, uptree, vfp);
+
+                m_bran_input_list.back().push_back(br);
+            }
+
+        } else if ((keyw.name() == "NODEPROP") && (!skiprest)) {
+            for (auto& rec : keyw) {
+                if (rec.getItem(1).hasValue(0)) {
+                    auto node_name = rec.getItem(0).get<std::string>(0);
+                    auto node_pres = rec.getItem(1).get<double>(0);
+                    auto node = std::make_tuple(node_name, node_pres);
+                    m_node_input_list.back().push_back(node);
+                }
+            }
+
+        } else if ((keyw.name() == "WELSPECS") && (!skiprest)) {
+            for (auto& rec : keyw) {
+                auto wname = rec.getItem(0).get<std::string>(0);
+                auto gname = rec.getItem(1).get<std::string>(0);
+
+                auto well = std::make_tuple(wname, gname);
+
+                m_well_input_list.back().push_back(well);
+            }
+        }
+    }
+}
+
+void
+NetWork::parse_unrst(const std::filesystem::path& inputFileName)
+{
+
+    Opm::EclIO::ERst rst1(inputFileName);
+
+    auto all_reports = rst1.listOfReportStepNumbers();
+
+    std::vector<int> rstep_vect;
+
+    for (auto r : all_reports)
+        if (r > 0)
+            rstep_vect.push_back(r);
+
+    m_node_input_list.push_back({});
+    m_bran_input_list.push_back({});
+    m_well_input_list.push_back({});
+
+    br_input_from_rst(inputFileName, rstep_vect);
+}
+
+std::string
+NetWork::time_str(time_t t1)
+{
+
+    std::vector<std::string> mndStr {
+        "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"};
+
+    std::tm* date = std::localtime(&t1);
+
+    std::stringstream date_str;
+
+    // ignoring daylight saving time
+
+    int hr = date->tm_hour;
+    int day = date->tm_mday;
+
+    if (date->tm_isdst == 1) {
+        hr = hr - 1;
+        if (hr < 0) {
+            hr = hr + 24;
+            day = day - 1;
+        }
+    }
+
+    date_str << std::setw(2) << std::setfill('0') << day;
+    date_str << " '" << mndStr[date->tm_mon] << "' " << date->tm_year + 1900;
+    date_str << " " << std::setw(2) << std::setfill('0') << hr;
+    date_str << ":" << std::setw(2) << std::setfill('0') << date->tm_min;
+    date_str << ":" << std::setw(2) << std::setfill('0') << date->tm_sec;
+
+    return date_str.str();
+}
+
+
+void
+NetWork::print_network_input()
+{
+
+    std::cout << "m_report_time_list: " << m_report_time_list.size() << "\n";
+    std::cout << "m_well_input_list : " << m_well_input_list.size() << "\n";
+
+    std::cout << "\nm_bran_input_list: " << m_bran_input_list.size() << "\n\n";
+
+    for (size_t r = 0; r < m_bran_input_list.size(); r++) {
+        for (size_t n = 0; n < m_bran_input_list[r].size(); n++) {
+            auto downtree = std::get<0>(m_bran_input_list[r][n]);
+            auto uptree = std::get<1>(m_bran_input_list[r][n]);
+            auto vfp = std::get<2>(m_bran_input_list[r][n]);
+
+            std::cout << "r= " << r << ", n= " << n << " " << downtree;
+            std::cout << " " << uptree << " " << vfp << "\n";
+        }
+    }
+
+    std::cout << "\n\nm_node_input_list: " << m_node_input_list.size() << "\n\n";
+
+    for (size_t r = 0; r < m_node_input_list.size(); r++) {
+        for (size_t n = 0; n < m_node_input_list[r].size(); n++) {
+            auto node = std::get<0>(m_node_input_list[r][n]);
+            auto pres = std::get<1>(m_node_input_list[r][n]);
+
+            std::cout << "r= " << r << ", n= " << n << " " << node;
+            std::cout << " " << pres << "\n";
+        }
+    }
+
+    std::cout << "\n\n";
+}
+
+
+bool
+NetWork::node_exist(const std::string& name)
+{
+    for (size_t n = 0; n < m_node_list.size(); n++)
+        if (name == m_node_list[n]->name())
+            return true;
+
+    return false;
+}
+
+
+void
+NetWork::add_node(const std::string& name)
+{
+    if (!this->node_exist(name))
+        m_node_list.push_back(std::make_shared<Node>(name));
+    else {
+        std::cout << "in function add_node: Node " << name << " already exists \n\n";
+        exit(1);
+    }
+}
+
+
+void
+NetWork::add_branch(const std::string& downtree, const std::string& uptree, int vfp)
+{
+    if (vfp == 0) {
+        std::cout << "\n!Error, vfp = 0, use function remove_branch to remove a branch \n\n";
+        exit(1);
+    }
+
+    std::shared_ptr<Node> pUptree;
+    std::shared_ptr<Node> pDowntree;
+
+    // handle uptree node
+
+    for (size_t n = 0; n < m_node_list.size(); n++)
+        if (m_node_list[n]->name() == uptree)
+            pUptree = m_node_list[n];
+
+    if (pUptree == nullptr) {
+        add_node(uptree);
+        pUptree = m_node_list.back();
+    }
+
+    // handle down tree node
+
+    for (size_t n = 0; n < m_node_list.size(); n++)
+        if (m_node_list[n]->name() == downtree)
+            pDowntree = m_node_list[n];
+
+    if (pDowntree == nullptr) {
+        add_node(downtree);
+        pDowntree = m_node_list.back();
+    }
+
+    pDowntree->set_outlet(pUptree);
+    pDowntree->set_vfp(vfp);
+
+
+    pUptree->add_inlet_node(pDowntree);
+}
+
+void
+NetWork::delete_branch(const std::string& downtree, const std::string& uptree)
+{
+    std::shared_ptr<Node> pUptree;
+    std::shared_ptr<Node> pDowntree;
+
+    for (size_t n = 0; n < m_node_list.size(); n++)
+        if (m_node_list[n]->name() == uptree)
+            pUptree = m_node_list[n];
+
+    for (size_t n = 0; n < m_node_list.size(); n++)
+        if (m_node_list[n]->name() == downtree)
+            pDowntree = m_node_list[n];
+
+    if ((pUptree == nullptr) || (pDowntree == nullptr)) {
+        std::cout << "\n!Error, pointer to downtree and/or uptree not found \n\n";
+        exit(1);
+    }
+
+    if (pUptree->delete_from_inlet_list(downtree) == false) {
+        std::cout << "\n!Error, problem with deleteing branch, needs to be checked  \n\n";
+        exit(1);
+    }
+
+    pDowntree->reset_outlet();
+
+    bool found_in_list = false;
+
+    for (size_t n = 0; n < m_top_node_list.size(); n++)
+        if (m_top_node_list[n]->name() == downtree)
+            found_in_list = true;
+
+    if (!found_in_list)
+        m_top_node_list.push_back(pDowntree);
+}
+
+
+void
+NetWork::build_network(int rstep)
+{
+    std::vector<int> input_step_vect;
+
+    // input from restart file -> each report step describes a complete state of network
+    // input from data deck -> network build from all steps up to report step (branprop increments)
+
+    if (m_from_unrst)
+        input_step_vect.push_back(rstep - 1);
+    else
+        for (int n = 0; n < rstep; n++)
+            input_step_vect.push_back(n);
+
+    for (auto& n : input_step_vect) {
+
+        for (size_t b = 0; b < m_bran_input_list[n].size(); b++) {
+            auto downtree = std::get<0>(m_bran_input_list[n][b]);
+            auto uptree = std::get<1>(m_bran_input_list[n][b]);
+            auto vfp = std::get<2>(m_bran_input_list[n][b]);
+
+            if (vfp == 0)
+                delete_branch(downtree, uptree);
+            else
+                add_branch(downtree, uptree, vfp);
+        }
+    }
+
+    m_top_node_list.clear();
+
+    for (size_t n = 0; n < m_node_list.size(); n++)
+        if (m_node_list[n]->get_outlet() == nullptr)
+            m_top_node_list.push_back(m_node_list[n]);
+
+    std::map<std::string, std::string> well_map;
+
+    for (auto n : input_step_vect) {
+        for (size_t b = 0; b < m_well_input_list[n].size(); b++) {
+            auto wname = std::get<0>(m_well_input_list[n][b]);
+            auto gname = std::get<1>(m_well_input_list[n][b]);
+            well_map[wname] = gname;
+        }
+    }
+
+    for (auto m : well_map) {
+        auto gname = m.second;
+        auto wname = m.first;
+
+        for (size_t n = 0; n < m_node_list.size(); n++)
+            if (m_node_list[n]->name() == gname)
+                m_node_list[n]->add_well(wname);
+    }
+
+    for (auto n : input_step_vect) {
+        for (size_t b = 0; b < m_node_input_list[n].size(); b++) {
+            auto node = std::get<0>(m_node_input_list[n][b]);
+            auto pressure = std::get<1>(m_node_input_list[n][b]);
+
+            for (size_t m = 0; m < m_node_list.size(); m++)
+                if (m_node_list[m]->name() == node)
+                    m_node_list[m]->set_fixed_pres(pressure);
+        }
+    }
+}
+
+void
+NetWork::print_network(int rstep)
+{
+
+    std::cout << "\n\n";
+
+    time_t t = m_report_time_list[rstep - 1];
+
+    std::cout << "Report step : " << time_str(t) << "\n\n";
+
+    for (size_t n = 0; n < m_top_node_list.size(); n++) {
+
+        m_top_node_list[n]->print(m_netw_str);
+        m_netw_str << "\n\n";
+    }
+
+    std::cout << "\n" << m_netw_str.str() << "";
+
+    std::cout << "\nFixed pressure nodes: \n\n";
+
+    for (size_t n = 0; n < m_node_list.size(); n++) {
+        auto fixed_pres = m_node_list[n]->get_fixed_pres();
+
+        if (fixed_pres > -1.0) {
+            std::cout << "  " << m_node_list[n]->name() << " = ";
+            std::cout << std::fixed << std::setprecision(2) << fixed_pres;
+        }
+    }
+
+    std::cout << "\n\n\n";
+}
+
+void
+NetWork::print_report_steps()
+{
+
+    if (!m_from_unrst)
+        std::cout << "\n\nStart date  " << time_str(m_start_date) << "\n";
+
+    std::cout << "\nList of all report steps \n\n";
+
+    for (size_t n = 0; n < m_report_time_list.size(); n++)
+        std::cout << "Report step " << n + 1 << "  | " << time_str(m_report_time_list[n]) << "\n";
+}
+
+time_t
+NetWork::time_from_rec(const Opm::DeckRecord& rec)
+{
+
+    std::map<std::string, int> mnd_map {{"JAN", 0},
+                                        {"FEB", 1},
+                                        {"MAR", 2},
+                                        {"APR", 3},
+                                        {"MAY", 4},
+                                        {"JUN", 5},
+                                        {"JUL", 6},
+                                        {"JLY", 6},
+                                        {"AUG", 7},
+                                        {"SEP", 8},
+                                        {"OCT", 9},
+                                        {"NOV", 10},
+                                        {"DEC", 11}};
+
+    auto day = rec.getItem(0).get<int>(0);
+    auto mndStr = rec.getItem(1).get<std::string>(0);
+    auto year = rec.getItem(2).get<int>(0);
+    auto time = rec.getItem(3).get<std::string>(0);
+
+    int sec_frac = 0;
+
+    auto p1 = time.find(".");
+
+    if (p1 != std::string::npos) {
+        auto sec_frac_str = time.substr(p1 + 1);
+        sec_frac = std::stoi(sec_frac_str);
+        time = time.substr(0, p1);
+    }
+
+    if (sec_frac > 0) {
+        std::cout << "\n!Error, fraction of section not supported \n";
+        exit(1);
+    }
+
+    p1 = time.find(":");
+
+    if (p1 == std::string::npos) {
+        std::cout << "\n!Error, invalied format for time " << time << "\n";
+        exit(1);
+    }
+
+    auto p2 = time.find(":", p1 + 1);
+    if (p2 == std::string::npos) {
+        std::cout << "\n!Error, Second invalied format for time " << time << "\n";
+        exit(1);
+    }
+
+    int h = std::stoi(time.substr(0, p1));
+    int min = std::stoi(time.substr(p1 + 1, p2 - p1 - 1));
+    int sec = std::stoi(time.substr(p2 + 1));
+
+    int mnd = mnd_map.at(mndStr);
+
+    // std::tm date1 = {sec, min, h, day, mnd, year - 1900 };
+
+    std::tm date1 = {};
+
+    date1.tm_sec = sec;
+    date1.tm_min = min;
+    date1.tm_hour = h;
+
+    date1.tm_mday = day;
+    date1.tm_mon = mnd;
+    date1.tm_year = year - 1900;
+
+    return std::mktime(&date1);
+}
+
+
+time_t
+NetWork::time_from_rst(const std::string& rstfile, int rstep)
+{
+
+    Opm::EclIO::ERst rst1(rstfile);
+
+    auto inteh = rst1.getRestartData<int>("INTEHEAD", rstep);
+
+    int year = inteh[66];
+    int mnd = inteh[65];
+    int day = inteh[64];
+
+    int h = inteh[206];
+    int min = inteh[207];
+    int sec = static_cast<int>(inteh[410] / 1000000);
+
+    std::tm date1 = {};
+
+    date1.tm_sec = sec;
+    date1.tm_min = min;
+    date1.tm_hour = h;
+
+    date1.tm_mday = day;
+    date1.tm_mon = mnd - 1;
+    date1.tm_year = year - 1900;
+
+    return std::mktime(&date1);
+}
+
+void
+NetWork::br_input_from_rst(const std::string& rstfile, std::vector<int> rstep_vect)
+{
+
+    Opm::EclIO::ERst rst1(rstfile);
+
+    for (int& rstep : rstep_vect) {
+
+        m_rst_time = time_from_rst(rstfile, rstep);
+        m_report_time_list.push_back(m_rst_time);
+
+        auto intehead = rst1.getRestartData<int>("INTEHEAD", rstep);
+
+        if (rst1.hasArray("ZNODE", rstep)) {
+
+            std::vector<std::string> nodelist;
+
+            auto noactnod = intehead[129]; // Number of active/defined nodes in the network
+            auto nibran = intehead[133]; // number of entries per branch in the IBRAN array
+            auto noactbr = intehead[130]; // Number of active/defined branches in the network
+            auto nrnode = intehead[136]; // number of entries per node in the RNODE array
+
+            auto znode = rst1.getRestartData<std::string>("ZNODE", rstep);
+            auto ibran = rst1.getRestartData<int>("IBRAN", rstep);
+            auto rnode = rst1.getRestartData<double>("RNODE", rstep);
+
+
+            for (int n = 0; n < noactnod; n++)
+                nodelist.push_back(znode[2 * n]);
+
+
+            for (int b = 0; b < noactbr; b++) {
+                int ind = b * nibran;
+                std::string downtree = nodelist[ibran[ind] - 1];
+                std::string uptree = nodelist[ibran[ind + 1] - 1];
+                int vfp = ibran[ind + 2];
+
+                auto br = std::make_tuple(downtree, uptree, vfp);
+                m_bran_input_list.back().push_back(br);
+            }
+
+            for (int n = 0; n < noactnod; n++) {
+                int ind = n * nrnode;
+                if (rnode[ind + 1] == 0.0) {
+                    auto node = std::make_tuple(nodelist[n], rnode[ind + 2]);
+                    m_node_input_list.back().push_back(node);
+                }
+            }
+        }
+
+        std::vector<std::string> grouplist;
+
+        auto nzwelz = intehead[27]; // Number of 8-character words per well in ZWEL array
+        auto nswells = intehead[16]; // Number of wells
+        auto nzgrpz = intehead[39]; // Number of data elements per group in ZGRP array
+        auto ngmaxz = intehead[20]; // Maximum number of groups in field
+        auto niwelz = intehead[24]; // Number of data elements per well in IWEL array
+
+        auto zwel = rst1.getRestartData<std::string>("ZWEL", rstep);
+        auto iwel = rst1.getRestartData<int>("IWEL", rstep);
+        auto zgrp = rst1.getRestartData<std::string>("ZGRP", rstep);
+
+        for (int g = 0; g < ngmaxz; g++)
+            grouplist.push_back(zgrp[g * nzgrpz]);
+
+        for (int n = 0; n < nswells; n++) {
+            std::string wname = zwel[n * nzwelz];
+            int grp_ind = iwel[n * niwelz + 5] - 1;
+            std::string gname = grouplist[grp_ind];
+            auto well = std::make_tuple(wname, gname);
+            m_well_input_list.back().push_back(well);
+        }
+
+        m_node_input_list.push_back({});
+        m_bran_input_list.push_back({});
+        m_well_input_list.push_back({});
+    }
+}
+
+
+static void
+printHelp()
+{
+
+    std::cout << "\n This program visualizes a production network with terminal output."
+              << " Input to this program should be a valid data deck (.DATA) \n or a unified"
+              << " restart file (.UNRST).\n\n The program takes these options"
+              << " (which must be given before the arguments):\n\n"
+              << " -l lists all available report steps and exit.\n"
+              << " -r selects report step to be visualized. Default is the last report step \n"
+              << " -h Print help and exit.\n\n";
+}
+
+
+int
+main(int argc, char** argv)
+{
+
+    int c = 0;
+    bool list_report_steps = false;
+    int rstep = -1;
+
+    while ((c = getopt(argc, argv, "lr:h")) != -1) {
+        switch (c) {
+        case 'l':
+            list_report_steps = true;
+            break;
+        case 'h':
+            printHelp();
+            return 0;
+        case 'r':
+            rstep = atoi(optarg);
+            break;
+        default:
+            return EXIT_FAILURE;
+        }
+    }
+
+    int argOffset = optind;
+
+    NetWork netw(argv[argOffset]);
+
+    if (list_report_steps) {
+        netw.print_report_steps();
+        std::cout << "\n";
+
+        return 0;
+    }
+
+    if (rstep == -1) {
+        rstep = netw.number_report_steps();
+    } else {
+
+        if ((rstep < 1) || (rstep > netw.number_report_steps())) {
+            std::cout << "\n!Error, invalid report step " << rstep;
+            std::cout << " should be > 0 and less than " << netw.number_report_steps() << "\n";
+            std::cout << "        use option -l to list all report steps. " << "\n\n";
+            exit(1);
+        }
+    }
+
+    netw.build_network(rstep);
+    netw.print_network(rstep);
+
+    std::cout << "\n\n";
+
+    return EXIT_SUCCESS;
+}


Reviews:
user: login: atgeirr
body: I have done a somewhat superficial review, not having checked all the logic. But as an auxiliary utility I think we can merge it once the minor issues pointed out are fixed, it does not influence any other code.
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-10T13:24:56Z
user: login: tskille
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-10T18:43:19Z
user: login: tskille
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-10T18:43:31Z
user: login: tskille
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-10T18:47:32Z
user: login: tskille
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-10T18:50:41Z
user: login: tskille
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-10T18:50:50Z
user: login: tskille
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-10T18:50:54Z
user: login: tskille
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-10T18:51:01Z

Review comments:
diff_hunk: @@ -0,0 +1,909 @@
+/*
+  Copyright 2025 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 <iostream>
+#include <getopt.h>
+#include <sstream>
+
+#include <opm/io/eclipse/ERst.hpp>
+
+#include <opm/input/eclipse/Deck/Deck.hpp>
+
+#include <opm/input/eclipse/Parser/ErrorGuard.hpp>
+#include <opm/input/eclipse/Parser/InputErrorAction.hpp>
+#include <opm/input/eclipse/Parser/Parser.hpp>
+#include <opm/input/eclipse/Parser/ParseContext.hpp>
+
+
+class Node
+{
+public:
+    Node(const std::string& name);
+
+    std::string name() const { return m_name; }
+    void set_outlet(std::shared_ptr<Node> outlet) { m_outlet = outlet; };
+    std::shared_ptr<Node> get_outlet() { return m_outlet; };
+
+    void add_inlet_node(std::shared_ptr<Node> n);
+
+    void set_vfp(int vfp){m_vfp = vfp;};
+    int get_vfp(){ return m_vfp;};
+
+    void set_fixed_pres(double pres){m_fixed_pres = pres; };
+    double get_fixed_pres(){return m_fixed_pres; };
+
+    int get_xpos(){return m_xpos;};
+
+    void print(std::stringstream& netw_str);
+
+    bool detete_from_inlet_list(const std::string& name);
path: examples/networkgraph.cpp
user: login: atgeirr
body: Typo: delete
created_at: 2025-04-10T13:05:03Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,909 @@
+/*
+  Copyright 2025 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 <iostream>
+#include <getopt.h>
+#include <sstream>
+
+#include <opm/io/eclipse/ERst.hpp>
+
+#include <opm/input/eclipse/Deck/Deck.hpp>
+
+#include <opm/input/eclipse/Parser/ErrorGuard.hpp>
+#include <opm/input/eclipse/Parser/InputErrorAction.hpp>
+#include <opm/input/eclipse/Parser/Parser.hpp>
+#include <opm/input/eclipse/Parser/ParseContext.hpp>
+
+
+class Node
+{
+public:
+    Node(const std::string& name);
path: examples/networkgraph.cpp
user: login: atgeirr
body: Always make single-argument constructors **explicit**.
created_at: 2025-04-10T13:05:28Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,909 @@
+/*
+  Copyright 2025 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 <iostream>
+#include <getopt.h>
+#include <sstream>
+
+#include <opm/io/eclipse/ERst.hpp>
+
+#include <opm/input/eclipse/Deck/Deck.hpp>
+
+#include <opm/input/eclipse/Parser/ErrorGuard.hpp>
+#include <opm/input/eclipse/Parser/InputErrorAction.hpp>
+#include <opm/input/eclipse/Parser/Parser.hpp>
+#include <opm/input/eclipse/Parser/ParseContext.hpp>
+
+
+class Node
+{
+public:
+    Node(const std::string& name);
+
+    std::string name() const { return m_name; }
+    void set_outlet(std::shared_ptr<Node> outlet) { m_outlet = outlet; };
+    std::shared_ptr<Node> get_outlet() { return m_outlet; };
+
+    void add_inlet_node(std::shared_ptr<Node> n);
+
+    void set_vfp(int vfp){m_vfp = vfp;};
path: examples/networkgraph.cpp
user: login: atgeirr
body: Lots of inconsistent formatting in the file, such as missing whitespace here. I suggest running clang-format on the file for a quick and easy fix. (There is a .clang-format file in the root of the repo that should apply automatically, I hope, but you can tweak it if you are unhappy with the results).
created_at: 2025-04-10T13:08:28Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,909 @@
+/*
+  Copyright 2025 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 <iostream>
+#include <getopt.h>
+#include <sstream>
+
+#include <opm/io/eclipse/ERst.hpp>
+
+#include <opm/input/eclipse/Deck/Deck.hpp>
+
+#include <opm/input/eclipse/Parser/ErrorGuard.hpp>
+#include <opm/input/eclipse/Parser/InputErrorAction.hpp>
+#include <opm/input/eclipse/Parser/Parser.hpp>
+#include <opm/input/eclipse/Parser/ParseContext.hpp>
+
+
+class Node
+{
+public:
+    Node(const std::string& name);
+
+    std::string name() const { return m_name; }
+    void set_outlet(std::shared_ptr<Node> outlet) { m_outlet = outlet; };
+    std::shared_ptr<Node> get_outlet() { return m_outlet; };
+
+    void add_inlet_node(std::shared_ptr<Node> n);
+
+    void set_vfp(int vfp){m_vfp = vfp;};
+    int get_vfp(){ return m_vfp;};
+
+    void set_fixed_pres(double pres){m_fixed_pres = pres; };
+    double get_fixed_pres(){return m_fixed_pres; };
+
+    int get_xpos(){return m_xpos;};
+
+    void print(std::stringstream& netw_str);
+
+    bool detete_from_inlet_list(const std::string& name);
+
+    bool add_well(const std::string& name);
+
+    void reset_outlet(){m_outlet = nullptr;};
+
+private:
+    int m_vfp;
+    int m_xpos = -1;
+    double m_fixed_pres = -1.0;
+    int m_ind = -1;
+
+    std::string m_name;
+    std::shared_ptr<Node> m_outlet;
+    std::vector<std::shared_ptr<Node>> m_inlet_list;
+    std::vector<std::string> m_well_list;
+
+    std::shared_ptr<Node> next_branch();
+};
+
+
+Node::Node(const std::string& name)
+   : m_name(name)
+{
+    m_vfp = -1;
+}
+
+std::shared_ptr<Node> Node::next_branch(){
+
+    std::shared_ptr<Node> p1 = m_outlet;
+
+    while (p1 != nullptr){
+        for (size_t n = 0; n < p1->m_inlet_list.size(); n ++){
+            if (p1->m_inlet_list[n]->m_xpos == -1){
+                return p1;
+            }
+        }
+        p1 = p1->m_outlet;
+    }
+
+    return p1;
+}
+
+bool Node::detete_from_inlet_list(const std::string& name){
+
+    int ind = -1;
+
+    for (size_t n = 0; n < m_inlet_list.size(); n ++)
+        if (m_inlet_list[n]->name() == name)
+            ind = n;
+
+    if (ind < 0){
+        return false;
+    } else {
+        m_inlet_list.erase (m_inlet_list.begin() + ind);
+        return true;
+    }
+}
+
+bool Node::add_well(const std::string& name){
+   if (m_inlet_list.size() > 0)
+       return false;
+
+   m_well_list.push_back(name);
+
+   return true;
+}
+
+
+void Node::print(std::stringstream& netw_str){
+
+    netw_str.seekg(0, std::ios::end);
+    int init_length = netw_str.tellg();
+
+    int pos_lineshift = 0;
+
+    auto p = netw_str.str().find_last_of("\n");
path: examples/networkgraph.cpp
user: login: atgeirr
body: I think the str() method may be quite expensive, as it probably constructs a complete string every time. So invoking it multiple times could be a performance killer, although it might not matter for the typical use case for this app.

I am fine with leaving it in if you do not want to change it.
created_at: 2025-04-10T13:12:26Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,909 @@
+/*
+  Copyright 2025 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 <iostream>
+#include <getopt.h>
+#include <sstream>
+
+#include <opm/io/eclipse/ERst.hpp>
+
+#include <opm/input/eclipse/Deck/Deck.hpp>
+
+#include <opm/input/eclipse/Parser/ErrorGuard.hpp>
+#include <opm/input/eclipse/Parser/InputErrorAction.hpp>
+#include <opm/input/eclipse/Parser/Parser.hpp>
+#include <opm/input/eclipse/Parser/ParseContext.hpp>
+
+
+class Node
+{
+public:
+    Node(const std::string& name);
+
+    std::string name() const { return m_name; }
+    void set_outlet(std::shared_ptr<Node> outlet) { m_outlet = outlet; };
+    std::shared_ptr<Node> get_outlet() { return m_outlet; };
+
+    void add_inlet_node(std::shared_ptr<Node> n);
+
+    void set_vfp(int vfp){m_vfp = vfp;};
+    int get_vfp(){ return m_vfp;};
+
+    void set_fixed_pres(double pres){m_fixed_pres = pres; };
+    double get_fixed_pres(){return m_fixed_pres; };
+
+    int get_xpos(){return m_xpos;};
+
+    void print(std::stringstream& netw_str);
+
+    bool detete_from_inlet_list(const std::string& name);
+
+    bool add_well(const std::string& name);
+
+    void reset_outlet(){m_outlet = nullptr;};
+
+private:
+    int m_vfp;
+    int m_xpos = -1;
+    double m_fixed_pres = -1.0;
+    int m_ind = -1;
+
+    std::string m_name;
+    std::shared_ptr<Node> m_outlet;
+    std::vector<std::shared_ptr<Node>> m_inlet_list;
+    std::vector<std::string> m_well_list;
+
+    std::shared_ptr<Node> next_branch();
+};
+
+
+Node::Node(const std::string& name)
+   : m_name(name)
+{
+    m_vfp = -1;
+}
+
+std::shared_ptr<Node> Node::next_branch(){
+
+    std::shared_ptr<Node> p1 = m_outlet;
+
+    while (p1 != nullptr){
+        for (size_t n = 0; n < p1->m_inlet_list.size(); n ++){
+            if (p1->m_inlet_list[n]->m_xpos == -1){
+                return p1;
+            }
+        }
+        p1 = p1->m_outlet;
+    }
+
+    return p1;
+}
+
+bool Node::detete_from_inlet_list(const std::string& name){
+
+    int ind = -1;
+
+    for (size_t n = 0; n < m_inlet_list.size(); n ++)
+        if (m_inlet_list[n]->name() == name)
+            ind = n;
+
+    if (ind < 0){
+        return false;
+    } else {
+        m_inlet_list.erase (m_inlet_list.begin() + ind);
+        return true;
+    }
+}
+
+bool Node::add_well(const std::string& name){
+   if (m_inlet_list.size() > 0)
+       return false;
+
+   m_well_list.push_back(name);
+
+   return true;
+}
+
+
+void Node::print(std::stringstream& netw_str){
+
+    netw_str.seekg(0, std::ios::end);
+    int init_length = netw_str.tellg();
+
+    int pos_lineshift = 0;
+
+    auto p = netw_str.str().find_last_of("\n");
+
+    if (p != std::string::npos)
+        pos_lineshift = p + 1;
+
+    if (m_outlet == nullptr){
+        netw_str << "  o (" << m_name << ")";
+        m_xpos = netw_str.str().find_first_of("o", init_length) - pos_lineshift;
+    } else if (m_vfp == 9999){
+        netw_str << " --- +(" << m_name << ")";
+        m_xpos = netw_str.str().find_first_of("+", init_length) - pos_lineshift;
+    } else {
+        netw_str << " --[" << m_vfp << "]-- +(" << m_name << ")";
+        m_xpos = netw_str.str().find_first_of("+", init_length) - pos_lineshift;
+    }
+
+
+    for (size_t m = 0; m < m_inlet_list.size(); m ++)
+        m_inlet_list[m]->print(netw_str);
+
+    if (m_inlet_list.size() == 0){
+        auto next_br = next_branch();
+
+        netw_str << " : ";
+
+        for (auto& well: m_well_list)
+            netw_str << " " << well;
+
+        if (next_br != nullptr){
+            std::string xpos_str(next_br->m_xpos, ' ');
+            netw_str << "\n" << xpos_str << "\\\n" << xpos_str;
+        }
+    }
+}
+
+void Node::add_inlet_node(std::shared_ptr<Node> node)
+{
+    for (size_t n = 0; n < m_inlet_list.size(); n ++ ){
path: examples/networkgraph.cpp
user: login: atgeirr
body: I do not understand this code. Why do you erase and then push?

If that is indeed what you need to do, the code is also a bit hard to maintain, for example continuing to loop and not doing the break would skip an element. It is better to use std::find() to find the matching node and erase that (if not at the end).
created_at: 2025-04-10T13:18:26Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,909 @@
+/*
+  Copyright 2025 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 <iostream>
+#include <getopt.h>
+#include <sstream>
+
+#include <opm/io/eclipse/ERst.hpp>
+
+#include <opm/input/eclipse/Deck/Deck.hpp>
+
+#include <opm/input/eclipse/Parser/ErrorGuard.hpp>
+#include <opm/input/eclipse/Parser/InputErrorAction.hpp>
+#include <opm/input/eclipse/Parser/Parser.hpp>
+#include <opm/input/eclipse/Parser/ParseContext.hpp>
+
+
+class Node
+{
+public:
+    Node(const std::string& name);
+
+    std::string name() const { return m_name; }
+    void set_outlet(std::shared_ptr<Node> outlet) { m_outlet = outlet; };
+    std::shared_ptr<Node> get_outlet() { return m_outlet; };
+
+    void add_inlet_node(std::shared_ptr<Node> n);
+
+    void set_vfp(int vfp){m_vfp = vfp;};
+    int get_vfp(){ return m_vfp;};
+
+    void set_fixed_pres(double pres){m_fixed_pres = pres; };
+    double get_fixed_pres(){return m_fixed_pres; };
+
+    int get_xpos(){return m_xpos;};
+
+    void print(std::stringstream& netw_str);
+
+    bool detete_from_inlet_list(const std::string& name);
+
+    bool add_well(const std::string& name);
+
+    void reset_outlet(){m_outlet = nullptr;};
+
+private:
+    int m_vfp;
+    int m_xpos = -1;
+    double m_fixed_pres = -1.0;
+    int m_ind = -1;
+
+    std::string m_name;
+    std::shared_ptr<Node> m_outlet;
+    std::vector<std::shared_ptr<Node>> m_inlet_list;
+    std::vector<std::string> m_well_list;
+
+    std::shared_ptr<Node> next_branch();
+};
+
+
+Node::Node(const std::string& name)
+   : m_name(name)
+{
+    m_vfp = -1;
+}
+
+std::shared_ptr<Node> Node::next_branch(){
+
+    std::shared_ptr<Node> p1 = m_outlet;
+
+    while (p1 != nullptr){
+        for (size_t n = 0; n < p1->m_inlet_list.size(); n ++){
+            if (p1->m_inlet_list[n]->m_xpos == -1){
+                return p1;
+            }
+        }
+        p1 = p1->m_outlet;
+    }
+
+    return p1;
+}
+
+bool Node::detete_from_inlet_list(const std::string& name){
+
+    int ind = -1;
+
+    for (size_t n = 0; n < m_inlet_list.size(); n ++)
+        if (m_inlet_list[n]->name() == name)
+            ind = n;
+
+    if (ind < 0){
+        return false;
+    } else {
+        m_inlet_list.erase (m_inlet_list.begin() + ind);
+        return true;
+    }
+}
+
+bool Node::add_well(const std::string& name){
+   if (m_inlet_list.size() > 0)
+       return false;
+
+   m_well_list.push_back(name);
+
+   return true;
+}
+
+
+void Node::print(std::stringstream& netw_str){
+
+    netw_str.seekg(0, std::ios::end);
+    int init_length = netw_str.tellg();
+
+    int pos_lineshift = 0;
+
+    auto p = netw_str.str().find_last_of("\n");
+
+    if (p != std::string::npos)
+        pos_lineshift = p + 1;
+
+    if (m_outlet == nullptr){
+        netw_str << "  o (" << m_name << ")";
+        m_xpos = netw_str.str().find_first_of("o", init_length) - pos_lineshift;
+    } else if (m_vfp == 9999){
+        netw_str << " --- +(" << m_name << ")";
+        m_xpos = netw_str.str().find_first_of("+", init_length) - pos_lineshift;
+    } else {
+        netw_str << " --[" << m_vfp << "]-- +(" << m_name << ")";
+        m_xpos = netw_str.str().find_first_of("+", init_length) - pos_lineshift;
+    }
+
+
+    for (size_t m = 0; m < m_inlet_list.size(); m ++)
+        m_inlet_list[m]->print(netw_str);
+
+    if (m_inlet_list.size() == 0){
+        auto next_br = next_branch();
+
+        netw_str << " : ";
+
+        for (auto& well: m_well_list)
+            netw_str << " " << well;
+
+        if (next_br != nullptr){
+            std::string xpos_str(next_br->m_xpos, ' ');
+            netw_str << "\n" << xpos_str << "\\\n" << xpos_str;
+        }
+    }
+}
+
+void Node::add_inlet_node(std::shared_ptr<Node> node)
+{
+    for (size_t n = 0; n < m_inlet_list.size(); n ++ ){
+        if (m_inlet_list[n]->name() == node->name()){
+            m_inlet_list.erase (m_inlet_list.begin() + n);
+            break;
+        }
+    }
+
+    m_inlet_list.push_back(node);
+}
+
+
+class NetWork
+{
+public:
+    // upptree, downtree, vfp
+    using bran_input_type = std::tuple<std::string, std::string, int>;
+
+    // name and fixed pressure
+    using node_input_type = std::tuple<std::string, int>;
+
+    // well and group name
+    using well_input_type = std::tuple<std::string, std::string>;
+
+    NetWork(const std::string& filename);
path: examples/networkgraph.cpp
user: login: atgeirr
body: Make explicit.
created_at: 2025-04-10T13:21:18Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,909 @@
+/*
+  Copyright 2025 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 <iostream>
+#include <getopt.h>
+#include <sstream>
+
+#include <opm/io/eclipse/ERst.hpp>
+
+#include <opm/input/eclipse/Deck/Deck.hpp>
+
+#include <opm/input/eclipse/Parser/ErrorGuard.hpp>
+#include <opm/input/eclipse/Parser/InputErrorAction.hpp>
+#include <opm/input/eclipse/Parser/Parser.hpp>
+#include <opm/input/eclipse/Parser/ParseContext.hpp>
+
+
+class Node
+{
+public:
+    Node(const std::string& name);
+
+    std::string name() const { return m_name; }
+    void set_outlet(std::shared_ptr<Node> outlet) { m_outlet = outlet; };
+    std::shared_ptr<Node> get_outlet() { return m_outlet; };
+
+    void add_inlet_node(std::shared_ptr<Node> n);
+
+    void set_vfp(int vfp){m_vfp = vfp;};
+    int get_vfp(){ return m_vfp;};
+
+    void set_fixed_pres(double pres){m_fixed_pres = pres; };
+    double get_fixed_pres(){return m_fixed_pres; };
+
+    int get_xpos(){return m_xpos;};
+
+    void print(std::stringstream& netw_str);
+
+    bool detete_from_inlet_list(const std::string& name);
+
+    bool add_well(const std::string& name);
+
+    void reset_outlet(){m_outlet = nullptr;};
+
+private:
+    int m_vfp;
+    int m_xpos = -1;
+    double m_fixed_pres = -1.0;
+    int m_ind = -1;
+
+    std::string m_name;
+    std::shared_ptr<Node> m_outlet;
+    std::vector<std::shared_ptr<Node>> m_inlet_list;
+    std::vector<std::string> m_well_list;
+
+    std::shared_ptr<Node> next_branch();
+};
+
+
+Node::Node(const std::string& name)
+   : m_name(name)
+{
+    m_vfp = -1;
+}
+
+std::shared_ptr<Node> Node::next_branch(){
+
+    std::shared_ptr<Node> p1 = m_outlet;
+
+    while (p1 != nullptr){
+        for (size_t n = 0; n < p1->m_inlet_list.size(); n ++){
+            if (p1->m_inlet_list[n]->m_xpos == -1){
+                return p1;
+            }
+        }
+        p1 = p1->m_outlet;
+    }
+
+    return p1;
+}
+
+bool Node::detete_from_inlet_list(const std::string& name){
+
+    int ind = -1;
+
+    for (size_t n = 0; n < m_inlet_list.size(); n ++)
+        if (m_inlet_list[n]->name() == name)
+            ind = n;
+
+    if (ind < 0){
+        return false;
+    } else {
+        m_inlet_list.erase (m_inlet_list.begin() + ind);
+        return true;
+    }
+}
+
+bool Node::add_well(const std::string& name){
+   if (m_inlet_list.size() > 0)
+       return false;
+
+   m_well_list.push_back(name);
+
+   return true;
+}
+
+
+void Node::print(std::stringstream& netw_str){
+
+    netw_str.seekg(0, std::ios::end);
+    int init_length = netw_str.tellg();
+
+    int pos_lineshift = 0;
+
+    auto p = netw_str.str().find_last_of("\n");
+
+    if (p != std::string::npos)
+        pos_lineshift = p + 1;
+
+    if (m_outlet == nullptr){
+        netw_str << "  o (" << m_name << ")";
+        m_xpos = netw_str.str().find_first_of("o", init_length) - pos_lineshift;
+    } else if (m_vfp == 9999){
+        netw_str << " --- +(" << m_name << ")";
+        m_xpos = netw_str.str().find_first_of("+", init_length) - pos_lineshift;
+    } else {
+        netw_str << " --[" << m_vfp << "]-- +(" << m_name << ")";
+        m_xpos = netw_str.str().find_first_of("+", init_length) - pos_lineshift;
+    }
+
+
+    for (size_t m = 0; m < m_inlet_list.size(); m ++)
+        m_inlet_list[m]->print(netw_str);
+
+    if (m_inlet_list.size() == 0){
+        auto next_br = next_branch();
+
+        netw_str << " : ";
+
+        for (auto& well: m_well_list)
+            netw_str << " " << well;
+
+        if (next_br != nullptr){
+            std::string xpos_str(next_br->m_xpos, ' ');
+            netw_str << "\n" << xpos_str << "\\\n" << xpos_str;
+        }
+    }
+}
+
+void Node::add_inlet_node(std::shared_ptr<Node> node)
+{
+    for (size_t n = 0; n < m_inlet_list.size(); n ++ ){
+        if (m_inlet_list[n]->name() == node->name()){
+            m_inlet_list.erase (m_inlet_list.begin() + n);
+            break;
+        }
+    }
+
+    m_inlet_list.push_back(node);
+}
+
+
+class NetWork
+{
+public:
+    // upptree, downtree, vfp
+    using bran_input_type = std::tuple<std::string, std::string, int>;
+
+    // name and fixed pressure
+    using node_input_type = std::tuple<std::string, int>;
+
+    // well and group name
+    using well_input_type = std::tuple<std::string, std::string>;
+
+    NetWork(const std::string& filename);
+
+    void print_report_steps();
+    int number_report_steps() {return m_report_time_list.size();}
+
+    void build_network(int rstep);
+    void print_network(int rstep);
+
+private:
+    time_t time_from_rec(const Opm::DeckRecord& rec);
+    time_t time_from_rst(const std::string& rstfile, int rstep);
+
+    bool node_exist(const std::string& name);
+
+    void add_node(const std::string& name);
+    void add_branch(const std::string& downtree, const std::string& uptree, int vfp);
+    void delete_branch(const std::string& downtree, const std::string& uptree);
+
+    void br_input_from_rst(const std::string& rstfile, std::vector<int> rstep_vect);
+
+    void print_network_input();
+    void parse_data_deck(const std::filesystem::path& inputFileName);
+    void parse_unrst(const std::filesystem::path& inputFileName);
+
+    std::stringstream m_netw_str;
+
+    time_t m_start_date;
+    time_t m_rst_time;
+
+    bool m_from_unrst = false;
+
+    std::vector<time_t> m_report_time_list;
+    std::vector<std::vector<bran_input_type>> m_bran_input_list;
+    std::vector<std::vector<node_input_type>> m_node_input_list;
+    std::vector<std::vector<well_input_type>> m_well_input_list;
+
+    std::string time_str(time_t t1);
+
+    std::vector<std::shared_ptr<Node>> m_node_list;
+    std::vector<std::shared_ptr<Node>> m_top_node_list;
+};
+
+
+NetWork::NetWork(const std::string& filename)
+{
+    std::filesystem::path inputFileName { filename } ;
+
+    if (inputFileName.extension()== ".DATA"){
+
+        parse_data_deck(inputFileName);
+
+    } else if (inputFileName.extension()== ".UNRST"){
+
+        parse_unrst(inputFileName);
+        m_from_unrst = true;
+
+    } else {
+        std::cout << "\n!Error, unsupported file type " << filename << "\n\n";
+        exit(1);
+    }
+}
+
+
+void NetWork::parse_data_deck(const std::filesystem::path& inputFileName){
+
+    Opm::ParseContext parseContext;
+    parseContext.update(Opm::ParseContext::PARSE_UNKNOWN_KEYWORD , Opm::InputErrorAction::IGNORE );
+    parseContext.update(Opm::ParseContext::PARSE_RANDOM_TEXT , Opm::InputErrorAction::IGNORE );
+    parseContext.update(Opm::ParseContext::PARSE_EXTRA_RECORDS , Opm::InputErrorAction::IGNORE );
+    parseContext.update(Opm::ParseContext::PARSE_RANDOM_SLASH , Opm::InputErrorAction::IGNORE );
+
+    std::vector<Opm::Ecl::SectionType> sections = {Opm::Ecl::RUNSPEC, Opm::Ecl::SOLUTION, Opm::Ecl::SCHEDULE};
+
+    Opm::Parser parser;
+
+    Opm::Deck deck_schecule;
+
+    try {
+        deck_schecule = parser.parseFile(inputFileName, parseContext, sections);
+
+    } catch (const std::exception& e){
+
+        std::cout << "\n!Error parsing data deck " << inputFileName << "\n\n";
+        std::cout << e.what() << "\n\n";
+
+        exit(1);
+    }
+
+    bool restart = false;
+    bool skiprest = false;
+
+    time_t last_time;
+
+    auto network_keyw =  deck_schecule["NETWORK"];
+
+    if (network_keyw.size() == 0){
+        std::cout << "\n > !Error, data deck "  << inputFileName << " doesn't include a production network \n\n";
+        exit(1);
+    }
+
+    for (auto keyw : deck_schecule){
+
+        if (keyw.name() == "START"){
+            m_node_input_list.push_back({});
+            m_bran_input_list.push_back({});
+            m_well_input_list.push_back({});
+            m_start_date = time_from_rec(keyw[0]);
+
+            last_time = m_start_date;
+
+        } else if (keyw.name() == "TSTEP"){
+
+            for (size_t n = 0; n < keyw[0].getItem(0).data_size(); n++){
+                auto dt = keyw[0].getItem(0).get<double>(n);
+                last_time = last_time + static_cast<int>(dt * 24.0 * 3600.0);
+
+                if (!skiprest){
+                    m_report_time_list.push_back(last_time);
+                    m_node_input_list.push_back({});
+                    m_bran_input_list.push_back({});
+                    m_well_input_list.push_back({});
+                }
+
+                if ((skiprest) && (last_time >= m_rst_time))
+                    skiprest = false;
+            }
+
+        } else if (keyw.name() == "DATES"){
+            for (auto& rec : keyw ){
+                last_time = time_from_rec(rec);
+
+                if (m_report_time_list.size() == 0){
+                    if ((last_time <=  m_start_date) && (!skiprest)) {
+                        std::cout << "\n!Error, next report step '" << time_str(last_time) << "' has already passed \n\n";
+                        exit(1);
+                    }
+                } else {
+                    if ((last_time <=  m_report_time_list.back()) && (!skiprest)) {
+                        std::cout << "\n!Error, next report step '" << time_str(last_time) << "' has already passed \n\n";
+                        exit(1);
+                    }
+                }
+
+                if (!skiprest){
+                    m_report_time_list.push_back(last_time);
+                    m_node_input_list.push_back({});
+                    m_bran_input_list.push_back({});
+                    m_well_input_list.push_back({});
+                }
+
+                if ((skiprest) && (last_time >= m_rst_time))
+                    skiprest = false;
+            }
+
+        } else if (keyw.name() == "RESTART"){
+
+            auto rst_file = keyw[0].getItem(0).get<std::string>(0) + ".UNRST";
+            auto rst_rstep = keyw[0].getItem(1).get<int>(0);
+
+            br_input_from_rst(rst_file, {rst_rstep} );
+
+            restart = true;
+
+        } else if (keyw.name() == "SKIPREST"){
+
+            if (restart)
+                skiprest = true;
+
+        } else if ((keyw.name() == "BRANPROP") && (!skiprest)){
+            for (auto& rec : keyw ){
+
+                auto downtree = rec.getItem(0).get<std::string>(0);
+                auto uptree = rec.getItem(1).get<std::string>(0);
+                auto vfp = rec.getItem(2).get<int>(0);
+
+                auto br = std::make_tuple(downtree, uptree, vfp);
+
+                m_bran_input_list.back().push_back(br);
+            }
+
+        } else if ((keyw.name() == "NODEPROP") && (!skiprest)){
+            for (auto& rec : keyw ){
+                if (rec.getItem(1).hasValue(0)){
+                    auto node_name = rec.getItem(0).get<std::string>(0);
+                    auto node_pres = rec.getItem(1).get<double>(0);
+                    auto node = std::make_tuple(node_name, node_pres);
+                    m_node_input_list.back().push_back(node);
+                }
+            }
+
+        } else if ((keyw.name() == "WELSPECS") && (!skiprest)){
+            for (auto& rec : keyw ){
+                auto wname = rec.getItem(0).get<std::string>(0);
+                auto gname = rec.getItem(1).get<std::string>(0);
+
+                auto well = std::make_tuple(wname, gname);
+
+                m_well_input_list.back().push_back(well);
+            }
+        }
+    }
+}
+
+void NetWork::parse_unrst(const std::filesystem::path& inputFileName){
+
+    Opm::EclIO::ERst rst1(inputFileName);
+
+    auto all_reports = rst1.listOfReportStepNumbers();
+
+    std::vector<int> rstep_vect;
+
+    for (auto r : all_reports)
+        if (r > 0)
+            rstep_vect.push_back(r);
+
+    m_node_input_list.push_back({});
+    m_bran_input_list.push_back({});
+    m_well_input_list.push_back({});
+
+    br_input_from_rst(inputFileName, rstep_vect);
+}
+
+std::string NetWork::time_str(time_t t1){
+
+    std::vector<std::string> mndStr{"JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"};
+
+    std::tm* date = std::localtime(&t1);
+
+    std::stringstream date_str;
+
+    // ignoring daylight saving time
+
+    int hr = date->tm_hour;
+    int day = date->tm_mday;
+
+    if (date->tm_isdst == 1){
+        hr = hr - 1;
+        if (hr < 0){
+            hr = hr + 24;
+            day = day - 1;
+        }
+    }
+
+    date_str << std::setw(2) << std::setfill('0') << day;
+    date_str <<  " '" << mndStr[date->tm_mon] << "' " << date->tm_year + 1900;
+    date_str << " " << std::setw(2) << std::setfill('0') << hr;
+    date_str << ":" << std::setw(2) << std::setfill('0') << date->tm_min;
+    date_str << ":" << std::setw(2) << std::setfill('0') << date->tm_sec;
+
+    return date_str.str();
+}
+
+
+void NetWork::print_network_input(){
+
+    std::cout << "m_report_time_list: " << m_report_time_list.size() << "\n";
+    std::cout << "m_well_input_list : " << m_well_input_list.size() << "\n";
+
+    std::cout << "\nm_bran_input_list: " <<  m_bran_input_list.size() << "\n\n";
+
+    for (size_t r = 0; r <  m_bran_input_list.size(); r++){
+        for (size_t n = 0; n <  m_bran_input_list[r].size(); n++){
+            auto downtree = std::get<0>(m_bran_input_list[r][n]);
+            auto uptree = std::get<1>(m_bran_input_list[r][n]);
+            auto vfp = std::get<2>(m_bran_input_list[r][n]);
+
+            std::cout << "r= " << r << ", n= " << n << " " << downtree;
+            std::cout << " " << uptree << " " << vfp << "\n";
+        }
+    }
+
+    std::cout << "\n\nm_node_input_list: " <<  m_node_input_list.size() << "\n\n";
+
+    for (size_t r = 0; r <  m_node_input_list.size(); r++){
+        for (size_t n = 0; n <  m_node_input_list[r].size(); n++){
+            auto node = std::get<0>(m_node_input_list[r][n]);
+            auto pres = std::get<1>(m_node_input_list[r][n]);
+
+            std::cout << "r= " << r << ", n= " << n << " " << node;
+            std::cout << " " << pres << "\n";
+        }
+    }
+
+    std::cout << "\n\n";
+}
+
+
+bool NetWork::node_exist(const std::string& name)
+{
+   for (size_t n = 0; n < m_node_list.size(); n ++)
+       if (name == m_node_list[n]->name())
+           return true;
+
+   return false;
+}
+
+
+void NetWork::add_node(const std::string& name)
+{
+    if (!this->node_exist(name))
+        m_node_list.push_back(std::make_shared<Node>(name));
+    else {
+        std::cout << "in function add_node: Node " << name << " already exists \n\n";
+        exit(1);
+    }
+}
+
+
+void  NetWork::add_branch(const std::string& downtree, const std::string& uptree, int vfp)
+{
+    if (vfp == 0){
+        std::cout << "\n!Error, vfp = 0, use function remove_branch to remove a branch \n\n";
+        exit(1);
+    }
+
+    std::shared_ptr<Node> pUptree;
+    std::shared_ptr<Node> pDowntree;
+
+    // handle uptree node
+
+    for (size_t n = 0; n < m_node_list.size(); n ++)
+        if (m_node_list[n]->name() == uptree)
+            pUptree = m_node_list[n];
+
+    if (pUptree == nullptr){
+        add_node(uptree);
+        pUptree = m_node_list.back();
+    }
+
+    // handle down tree node
+
+    for (size_t n = 0; n < m_node_list.size(); n ++)
+        if (m_node_list[n]->name() == downtree)
+            pDowntree = m_node_list[n];
+
+    if (pDowntree == nullptr){
+        add_node(downtree);
+        pDowntree = m_node_list.back();
+    }
+
+    pDowntree->set_outlet(pUptree);
+    pDowntree->set_vfp(vfp);
+
+
+    pUptree->add_inlet_node(pDowntree);
+}
+
+void  NetWork::delete_branch(const std::string& downtree, const std::string& uptree)
+{
+    std::shared_ptr<Node> pUptree;
+    std::shared_ptr<Node> pDowntree;
+
+    for (size_t n = 0; n < m_node_list.size(); n ++)
+        if (m_node_list[n]->name() == uptree)
+            pUptree = m_node_list[n];
+
+    for (size_t n = 0; n < m_node_list.size(); n ++)
+        if (m_node_list[n]->name() == downtree)
+            pDowntree = m_node_list[n];
+
+    if ((pUptree == nullptr) || (pDowntree == nullptr)) {
+        std::cout << "\n!Error, pointer to downtree and/or uptree not found \n\n";
+        exit(1);
+    }
+
+    if (pUptree->detete_from_inlet_list(downtree) == false)  {
+        std::cout << "\n!Error, problem with deleteing branch, needs to be checked  \n\n";
+        exit(1);
+    }
+
+    pDowntree->reset_outlet();
+
+    bool found_in_list = false;
+
+    for (size_t n = 0; n < m_top_node_list.size(); n++)
+        if (m_top_node_list[n]->name() == downtree)
+            found_in_list = true;
+
+    if (!found_in_list)
+        m_top_node_list.push_back(pDowntree);
+}
+
+
+void  NetWork::build_network(int rstep)
+{
+    std::vector<int> input_step_vect;
+
+    // input from restart file -> each report step describes a complete state of network
+    // input from data deck -> network build from all steps up to report step (branprop increments)
+
+    if (m_from_unrst)
+        input_step_vect.push_back(rstep - 1);
+    else
+        for (int n = 0; n < rstep; n++)
+            input_step_vect.push_back(n);
+
+    for (auto& n : input_step_vect){
+
+        for (size_t b = 0; b < m_bran_input_list[n].size(); b ++){
+            auto downtree = std::get<0>(m_bran_input_list[n][b]);
+            auto uptree = std::get<1>(m_bran_input_list[n][b]);
+            auto vfp = std::get<2>(m_bran_input_list[n][b]);
+
+            if (vfp == 0)
+                delete_branch(downtree, uptree);
+            else
+                add_branch(downtree, uptree, vfp);
+        }
+    }
+
+    m_top_node_list.clear();
+
+    for (size_t n = 0; n < m_node_list.size(); n++)
+        if (m_node_list[n]->get_outlet() == nullptr)
+            m_top_node_list.push_back(m_node_list[n]);
+
+    std::map<std::string, std::string> well_map;
+
+    for (auto n : input_step_vect){
+        for (size_t b = 0; b < m_well_input_list[n].size(); b ++){
+            auto wname = std::get<0>(m_well_input_list[n][b]);
+            auto gname = std::get<1>(m_well_input_list[n][b]);
+            well_map[wname] = gname;
+        }
+    }
+
+    for (auto m : well_map){
+        auto gname = m.second;
+        auto wname = m.first;
+
+        for (size_t n = 0; n < m_node_list.size(); n++)
+            if (m_node_list[n]->name() == gname)
+                m_node_list[n]->add_well(wname);
+    }
+
+    for (auto n : input_step_vect){
+        for (size_t b = 0; b < m_node_input_list[n].size(); b ++){
+            auto node = std::get<0>(m_node_input_list[n][b]);
+            auto pressure = std::get<1>(m_node_input_list[n][b]);
+
+            for (size_t m = 0; m < m_node_list.size(); m ++ )
+                if (m_node_list[m]->name() == node)
+                    m_node_list[m]->set_fixed_pres(pressure);
+        }
+    }
+}
+
+void NetWork::print_network(int rstep){
+
+    std::cout << "\n\n";
+
+    time_t t = m_report_time_list[rstep - 1];
+
+    std::cout << "Report step : " << time_str(t) << "\n\n";
+
+    for (size_t n = 0; n < m_top_node_list.size(); n++){
+
+        m_top_node_list[n]->print(m_netw_str);
+        m_netw_str << "\n\n";
+    }
+
+    std::cout << "\n" << m_netw_str.str() << "";
+
+    std::cout << "\nFixed pressure nodes: \n\n";
+
+    for (size_t n = 0; n < m_node_list.size(); n++){
+        auto fixed_pres = m_node_list[n]->get_fixed_pres();
+
+        if (fixed_pres > -1.0){
+            std::cout << "  " << m_node_list[n]->name() << " = ";
+            std::cout << std::fixed << std::setprecision(2) << fixed_pres;
+        }
+    }
+
+    std::cout << "\n\n\n";
+}
+
+void NetWork::print_report_steps(){
+
+    if (!m_from_unrst)
+        std::cout << "\n\nStart date  " << time_str(m_start_date) << "\n";
+
+    std::cout << "\nList of all report steps \n\n";
+
+    for (size_t n = 0; n < m_report_time_list.size(); n ++)
+        std::cout << "Report step " << n + 1 << "  | " << time_str(m_report_time_list[n]) << "\n";
+}
+
+time_t NetWork::time_from_rec(const Opm::DeckRecord& rec){
+
+    std::map<std::string, int> mnd_map{{"JAN", 0}, {"FEB", 1}, {"MAR", 2}, {"APR", 3}, {"MAY", 4}, {"JUN", 5},
+                                       {"JUL", 6}, {"JLY", 6}, {"AUG", 7}, {"SEP", 8}, {"OCT", 9}, {"NOV", 10},
+                                       {"DEC", 11}};
+
+    auto day = rec.getItem(0).get<int>(0);
+    auto mndStr = rec.getItem(1).get<std::string>(0);
+    auto year = rec.getItem(2).get<int>(0);
+    auto time = rec.getItem(3).get<std::string>(0);
+
+    int sec_frac = 0;
+
+    auto p1 = time.find(".");
+
+    if (p1 != std::string::npos){
+        auto sec_frac_str = time.substr (p1 + 1);
+        sec_frac = std::stoi(sec_frac_str);
+        time = time.substr (0, p1);
+    }
+
+    if (sec_frac > 0){
+        std::cout << "\n!Error, fraction of section not supported \n";
+        exit(1);
+    }
+
+    p1 = time.find(":");
+
+    if (p1 == std::string::npos){
+        std::cout << "\n!Error, invalied format for time " << time << "\n";
+        exit(1);
+    }
+
+    auto p2 = time.find(":", p1 + 1);
+    if (p2 == std::string::npos){
+        std::cout << "\n!Error, Second invalied format for time " << time << "\n";
+        exit(1);
+    }
+
+    int h = std::stoi(time.substr (0, p1));
+    int min = std::stoi(time.substr (p1+1, p2-p1-1));
+    int sec = std::stoi(time.substr (p2+1));
+
+    int mnd = mnd_map.at(mndStr);
+
+    //std::tm date1 = {sec, min, h, day, mnd, year - 1900 };
+
+    std::tm date1 = {};
+
+    date1.tm_sec = sec;
+    date1.tm_min = min;
+    date1.tm_hour = h;
+
+    date1.tm_mday  = day;
+    date1.tm_mon = mnd;
+    date1.tm_year = year - 1900;
+
+    return std::mktime(&date1);
+}
+
+
+time_t NetWork::time_from_rst(const std::string& rstfile, int rstep){
+
+    Opm::EclIO::ERst rst1(rstfile);
+
+    auto inteh = rst1.getRestartData<int>("INTEHEAD", rstep);
+
+    int year = inteh[66];
+    int mnd = inteh[65];
+    int day = inteh[64];
+
+    int h = inteh[206];
+    int min = inteh[207];
+    int sec = static_cast<int>(inteh[410] / 1000000);
+
+    std::tm date1 = {};
+
+    date1.tm_sec = sec;
+    date1.tm_min = min;
+    date1.tm_hour = h;
+
+    date1.tm_mday  = day;
+    date1.tm_mon = mnd - 1;
+    date1.tm_year = year - 1900;
+
+    return std::mktime(&date1);
+}
+
+void NetWork::br_input_from_rst(const std::string& rstfile, std::vector<int> rstep_vect){
+
+    Opm::EclIO::ERst rst1(rstfile);
+
+    for (int& rstep : rstep_vect){
+
+        m_rst_time = time_from_rst(rstfile, rstep);
+        m_report_time_list.push_back(m_rst_time);
+
+        auto intehead = rst1.getRestartData<int>("INTEHEAD", rstep);
+
+        if (rst1.hasArray("ZNODE", rstep)){
+
+            std::vector<std::string> nodelist;
+
+            auto noactnod = intehead[129];  // Number of active/defined nodes in the network
+            auto nibran = intehead[133];    // number of entries per branch in the IBRAN array
+            auto noactbr = intehead[130];   // Number of active/defined branches in the network
+            auto nrnode = intehead[136];    // number of entries per node in the RNODE array
+
+            auto znode = rst1.getRestartData<std::string>("ZNODE", rstep);
+            auto ibran = rst1.getRestartData<int>("IBRAN", rstep);
+            auto rnode = rst1.getRestartData<double>("RNODE", rstep);
+
+
+            for (int n = 0; n < noactnod; n++)
+                nodelist.push_back(znode[2*n]);
+
+
+            for (int b = 0; b < noactbr; b++){
+                int ind = b*nibran;
+                std::string downtree = nodelist[ibran[ind] - 1];
+                std::string uptree = nodelist[ibran[ind + 1] - 1];
+                int vfp = ibran[ind + 2];
+
+                auto br = std::make_tuple(downtree, uptree, vfp);
+                m_bran_input_list.back().push_back(br);
+            }
+
+            for (int n = 0; n < noactnod; n++){
+                int ind = n*nrnode;
+                if (rnode[ind + 1] == 0.0){
+                    auto node = std::make_tuple(nodelist[n], rnode[ind + 2]);
+                    m_node_input_list.back().push_back(node);
+                }
+            }
+        }
+
+        std::vector<std::string> grouplist;
+
+        auto nzwelz = intehead[27];     // Number of 8-character words per well in ZWEL array
+        auto nswells = intehead[16];    // Number of wells
+        auto nzgrpz = intehead[39];     // Number of data elements per group in ZGRP array
+        auto ngmaxz = intehead[20];     // Maximum number of groups in field
+        auto niwelz = intehead[24];     // Number of data elements per well in IWEL array
+
+        auto zwel = rst1.getRestartData<std::string>("ZWEL", rstep);
+        auto iwel = rst1.getRestartData<int>("IWEL", rstep);
+        auto zgrp = rst1.getRestartData<std::string>("ZGRP", rstep);
+
+        for (int g = 0; g < ngmaxz; g ++)
+            grouplist.push_back(zgrp[g*nzgrpz]);
+
+        for (int n = 0; n < nswells; n++){
+            std::string wname = zwel[n*nzwelz];
+            int grp_ind = iwel[n*niwelz + 5] - 1;
+            std::string gname = grouplist[grp_ind];
+            auto well = std::make_tuple(wname, gname);
+            m_well_input_list.back().push_back(well);
+        }
+
+        m_node_input_list.push_back({});
+        m_bran_input_list.push_back({});
+        m_well_input_list.push_back({});
+    }
+}
+
+
+static void printHelp() {
+
+    std::cout << "\n This program visualizes a production network with termial output."
path: examples/networkgraph.cpp
user: login: atgeirr
body: Typos: termial -> terminal, valied -> valid, selets->selects.
created_at: 2025-04-10T13:22:54Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,909 @@
+/*
+  Copyright 2025 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 <iostream>
+#include <getopt.h>
+#include <sstream>
+
+#include <opm/io/eclipse/ERst.hpp>
+
+#include <opm/input/eclipse/Deck/Deck.hpp>
+
+#include <opm/input/eclipse/Parser/ErrorGuard.hpp>
+#include <opm/input/eclipse/Parser/InputErrorAction.hpp>
+#include <opm/input/eclipse/Parser/Parser.hpp>
+#include <opm/input/eclipse/Parser/ParseContext.hpp>
+
+
+class Node
+{
+public:
+    Node(const std::string& name);
+
+    std::string name() const { return m_name; }
+    void set_outlet(std::shared_ptr<Node> outlet) { m_outlet = outlet; };
+    std::shared_ptr<Node> get_outlet() { return m_outlet; };
+
+    void add_inlet_node(std::shared_ptr<Node> n);
+
+    void set_vfp(int vfp){m_vfp = vfp;};
+    int get_vfp(){ return m_vfp;};
+
+    void set_fixed_pres(double pres){m_fixed_pres = pres; };
+    double get_fixed_pres(){return m_fixed_pres; };
+
+    int get_xpos(){return m_xpos;};
+
+    void print(std::stringstream& netw_str);
+
+    bool detete_from_inlet_list(const std::string& name);
+
+    bool add_well(const std::string& name);
+
+    void reset_outlet(){m_outlet = nullptr;};
+
+private:
+    int m_vfp;
+    int m_xpos = -1;
+    double m_fixed_pres = -1.0;
+    int m_ind = -1;
+
+    std::string m_name;
+    std::shared_ptr<Node> m_outlet;
+    std::vector<std::shared_ptr<Node>> m_inlet_list;
+    std::vector<std::string> m_well_list;
+
+    std::shared_ptr<Node> next_branch();
+};
+
+
+Node::Node(const std::string& name)
+   : m_name(name)
+{
+    m_vfp = -1;
+}
+
+std::shared_ptr<Node> Node::next_branch(){
+
+    std::shared_ptr<Node> p1 = m_outlet;
+
+    while (p1 != nullptr){
+        for (size_t n = 0; n < p1->m_inlet_list.size(); n ++){
+            if (p1->m_inlet_list[n]->m_xpos == -1){
+                return p1;
+            }
+        }
+        p1 = p1->m_outlet;
+    }
+
+    return p1;
+}
+
+bool Node::detete_from_inlet_list(const std::string& name){
+
+    int ind = -1;
+
+    for (size_t n = 0; n < m_inlet_list.size(); n ++)
+        if (m_inlet_list[n]->name() == name)
+            ind = n;
+
+    if (ind < 0){
+        return false;
+    } else {
+        m_inlet_list.erase (m_inlet_list.begin() + ind);
+        return true;
+    }
+}
+
+bool Node::add_well(const std::string& name){
+   if (m_inlet_list.size() > 0)
+       return false;
+
+   m_well_list.push_back(name);
+
+   return true;
+}
+
+
+void Node::print(std::stringstream& netw_str){
+
+    netw_str.seekg(0, std::ios::end);
+    int init_length = netw_str.tellg();
+
+    int pos_lineshift = 0;
+
+    auto p = netw_str.str().find_last_of("\n");
+
+    if (p != std::string::npos)
+        pos_lineshift = p + 1;
+
+    if (m_outlet == nullptr){
+        netw_str << "  o (" << m_name << ")";
+        m_xpos = netw_str.str().find_first_of("o", init_length) - pos_lineshift;
+    } else if (m_vfp == 9999){
+        netw_str << " --- +(" << m_name << ")";
+        m_xpos = netw_str.str().find_first_of("+", init_length) - pos_lineshift;
+    } else {
+        netw_str << " --[" << m_vfp << "]-- +(" << m_name << ")";
+        m_xpos = netw_str.str().find_first_of("+", init_length) - pos_lineshift;
+    }
+
+
+    for (size_t m = 0; m < m_inlet_list.size(); m ++)
+        m_inlet_list[m]->print(netw_str);
+
+    if (m_inlet_list.size() == 0){
+        auto next_br = next_branch();
+
+        netw_str << " : ";
+
+        for (auto& well: m_well_list)
+            netw_str << " " << well;
+
+        if (next_br != nullptr){
+            std::string xpos_str(next_br->m_xpos, ' ');
+            netw_str << "\n" << xpos_str << "\\\n" << xpos_str;
+        }
+    }
+}
+
+void Node::add_inlet_node(std::shared_ptr<Node> node)
+{
+    for (size_t n = 0; n < m_inlet_list.size(); n ++ ){
+        if (m_inlet_list[n]->name() == node->name()){
+            m_inlet_list.erase (m_inlet_list.begin() + n);
+            break;
+        }
+    }
+
+    m_inlet_list.push_back(node);
+}
+
+
+class NetWork
+{
+public:
+    // upptree, downtree, vfp
+    using bran_input_type = std::tuple<std::string, std::string, int>;
+
+    // name and fixed pressure
+    using node_input_type = std::tuple<std::string, int>;
+
+    // well and group name
+    using well_input_type = std::tuple<std::string, std::string>;
+
+    NetWork(const std::string& filename);
+
+    void print_report_steps();
+    int number_report_steps() {return m_report_time_list.size();}
+
+    void build_network(int rstep);
+    void print_network(int rstep);
+
+private:
+    time_t time_from_rec(const Opm::DeckRecord& rec);
+    time_t time_from_rst(const std::string& rstfile, int rstep);
+
+    bool node_exist(const std::string& name);
+
+    void add_node(const std::string& name);
+    void add_branch(const std::string& downtree, const std::string& uptree, int vfp);
+    void delete_branch(const std::string& downtree, const std::string& uptree);
+
+    void br_input_from_rst(const std::string& rstfile, std::vector<int> rstep_vect);
+
+    void print_network_input();
+    void parse_data_deck(const std::filesystem::path& inputFileName);
+    void parse_unrst(const std::filesystem::path& inputFileName);
+
+    std::stringstream m_netw_str;
+
+    time_t m_start_date;
+    time_t m_rst_time;
+
+    bool m_from_unrst = false;
+
+    std::vector<time_t> m_report_time_list;
+    std::vector<std::vector<bran_input_type>> m_bran_input_list;
+    std::vector<std::vector<node_input_type>> m_node_input_list;
+    std::vector<std::vector<well_input_type>> m_well_input_list;
+
+    std::string time_str(time_t t1);
+
+    std::vector<std::shared_ptr<Node>> m_node_list;
+    std::vector<std::shared_ptr<Node>> m_top_node_list;
+};
+
+
+NetWork::NetWork(const std::string& filename)
+{
+    std::filesystem::path inputFileName { filename } ;
+
+    if (inputFileName.extension()== ".DATA"){
+
+        parse_data_deck(inputFileName);
+
+    } else if (inputFileName.extension()== ".UNRST"){
+
+        parse_unrst(inputFileName);
+        m_from_unrst = true;
+
+    } else {
+        std::cout << "\n!Error, unsupported file type " << filename << "\n\n";
+        exit(1);
+    }
+}
+
+
+void NetWork::parse_data_deck(const std::filesystem::path& inputFileName){
+
+    Opm::ParseContext parseContext;
+    parseContext.update(Opm::ParseContext::PARSE_UNKNOWN_KEYWORD , Opm::InputErrorAction::IGNORE );
+    parseContext.update(Opm::ParseContext::PARSE_RANDOM_TEXT , Opm::InputErrorAction::IGNORE );
+    parseContext.update(Opm::ParseContext::PARSE_EXTRA_RECORDS , Opm::InputErrorAction::IGNORE );
+    parseContext.update(Opm::ParseContext::PARSE_RANDOM_SLASH , Opm::InputErrorAction::IGNORE );
+
+    std::vector<Opm::Ecl::SectionType> sections = {Opm::Ecl::RUNSPEC, Opm::Ecl::SOLUTION, Opm::Ecl::SCHEDULE};
+
+    Opm::Parser parser;
+
+    Opm::Deck deck_schecule;
+
+    try {
+        deck_schecule = parser.parseFile(inputFileName, parseContext, sections);
+
+    } catch (const std::exception& e){
+
+        std::cout << "\n!Error parsing data deck " << inputFileName << "\n\n";
+        std::cout << e.what() << "\n\n";
+
+        exit(1);
+    }
+
+    bool restart = false;
+    bool skiprest = false;
+
+    time_t last_time;
+
+    auto network_keyw =  deck_schecule["NETWORK"];
+
+    if (network_keyw.size() == 0){
+        std::cout << "\n > !Error, data deck "  << inputFileName << " doesn't include a production network \n\n";
+        exit(1);
+    }
+
+    for (auto keyw : deck_schecule){
+
+        if (keyw.name() == "START"){
+            m_node_input_list.push_back({});
+            m_bran_input_list.push_back({});
+            m_well_input_list.push_back({});
+            m_start_date = time_from_rec(keyw[0]);
+
+            last_time = m_start_date;
+
+        } else if (keyw.name() == "TSTEP"){
+
+            for (size_t n = 0; n < keyw[0].getItem(0).data_size(); n++){
+                auto dt = keyw[0].getItem(0).get<double>(n);
+                last_time = last_time + static_cast<int>(dt * 24.0 * 3600.0);
+
+                if (!skiprest){
+                    m_report_time_list.push_back(last_time);
+                    m_node_input_list.push_back({});
+                    m_bran_input_list.push_back({});
+                    m_well_input_list.push_back({});
+                }
+
+                if ((skiprest) && (last_time >= m_rst_time))
+                    skiprest = false;
+            }
+
+        } else if (keyw.name() == "DATES"){
+            for (auto& rec : keyw ){
+                last_time = time_from_rec(rec);
+
+                if (m_report_time_list.size() == 0){
+                    if ((last_time <=  m_start_date) && (!skiprest)) {
+                        std::cout << "\n!Error, next report step '" << time_str(last_time) << "' has already passed \n\n";
+                        exit(1);
+                    }
+                } else {
+                    if ((last_time <=  m_report_time_list.back()) && (!skiprest)) {
+                        std::cout << "\n!Error, next report step '" << time_str(last_time) << "' has already passed \n\n";
+                        exit(1);
+                    }
+                }
+
+                if (!skiprest){
+                    m_report_time_list.push_back(last_time);
+                    m_node_input_list.push_back({});
+                    m_bran_input_list.push_back({});
+                    m_well_input_list.push_back({});
+                }
+
+                if ((skiprest) && (last_time >= m_rst_time))
+                    skiprest = false;
+            }
+
+        } else if (keyw.name() == "RESTART"){
+
+            auto rst_file = keyw[0].getItem(0).get<std::string>(0) + ".UNRST";
+            auto rst_rstep = keyw[0].getItem(1).get<int>(0);
+
+            br_input_from_rst(rst_file, {rst_rstep} );
+
+            restart = true;
+
+        } else if (keyw.name() == "SKIPREST"){
+
+            if (restart)
+                skiprest = true;
+
+        } else if ((keyw.name() == "BRANPROP") && (!skiprest)){
+            for (auto& rec : keyw ){
+
+                auto downtree = rec.getItem(0).get<std::string>(0);
+                auto uptree = rec.getItem(1).get<std::string>(0);
+                auto vfp = rec.getItem(2).get<int>(0);
+
+                auto br = std::make_tuple(downtree, uptree, vfp);
+
+                m_bran_input_list.back().push_back(br);
+            }
+
+        } else if ((keyw.name() == "NODEPROP") && (!skiprest)){
+            for (auto& rec : keyw ){
+                if (rec.getItem(1).hasValue(0)){
+                    auto node_name = rec.getItem(0).get<std::string>(0);
+                    auto node_pres = rec.getItem(1).get<double>(0);
+                    auto node = std::make_tuple(node_name, node_pres);
+                    m_node_input_list.back().push_back(node);
+                }
+            }
+
+        } else if ((keyw.name() == "WELSPECS") && (!skiprest)){
+            for (auto& rec : keyw ){
+                auto wname = rec.getItem(0).get<std::string>(0);
+                auto gname = rec.getItem(1).get<std::string>(0);
+
+                auto well = std::make_tuple(wname, gname);
+
+                m_well_input_list.back().push_back(well);
+            }
+        }
+    }
+}
+
+void NetWork::parse_unrst(const std::filesystem::path& inputFileName){
+
+    Opm::EclIO::ERst rst1(inputFileName);
+
+    auto all_reports = rst1.listOfReportStepNumbers();
+
+    std::vector<int> rstep_vect;
+
+    for (auto r : all_reports)
+        if (r > 0)
+            rstep_vect.push_back(r);
+
+    m_node_input_list.push_back({});
+    m_bran_input_list.push_back({});
+    m_well_input_list.push_back({});
+
+    br_input_from_rst(inputFileName, rstep_vect);
+}
+
+std::string NetWork::time_str(time_t t1){
+
+    std::vector<std::string> mndStr{"JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"};
+
+    std::tm* date = std::localtime(&t1);
+
+    std::stringstream date_str;
+
+    // ignoring daylight saving time
+
+    int hr = date->tm_hour;
+    int day = date->tm_mday;
+
+    if (date->tm_isdst == 1){
+        hr = hr - 1;
+        if (hr < 0){
+            hr = hr + 24;
+            day = day - 1;
+        }
+    }
+
+    date_str << std::setw(2) << std::setfill('0') << day;
+    date_str <<  " '" << mndStr[date->tm_mon] << "' " << date->tm_year + 1900;
+    date_str << " " << std::setw(2) << std::setfill('0') << hr;
+    date_str << ":" << std::setw(2) << std::setfill('0') << date->tm_min;
+    date_str << ":" << std::setw(2) << std::setfill('0') << date->tm_sec;
+
+    return date_str.str();
+}
+
+
+void NetWork::print_network_input(){
+
+    std::cout << "m_report_time_list: " << m_report_time_list.size() << "\n";
+    std::cout << "m_well_input_list : " << m_well_input_list.size() << "\n";
+
+    std::cout << "\nm_bran_input_list: " <<  m_bran_input_list.size() << "\n\n";
+
+    for (size_t r = 0; r <  m_bran_input_list.size(); r++){
+        for (size_t n = 0; n <  m_bran_input_list[r].size(); n++){
+            auto downtree = std::get<0>(m_bran_input_list[r][n]);
+            auto uptree = std::get<1>(m_bran_input_list[r][n]);
+            auto vfp = std::get<2>(m_bran_input_list[r][n]);
+
+            std::cout << "r= " << r << ", n= " << n << " " << downtree;
+            std::cout << " " << uptree << " " << vfp << "\n";
+        }
+    }
+
+    std::cout << "\n\nm_node_input_list: " <<  m_node_input_list.size() << "\n\n";
+
+    for (size_t r = 0; r <  m_node_input_list.size(); r++){
+        for (size_t n = 0; n <  m_node_input_list[r].size(); n++){
+            auto node = std::get<0>(m_node_input_list[r][n]);
+            auto pres = std::get<1>(m_node_input_list[r][n]);
+
+            std::cout << "r= " << r << ", n= " << n << " " << node;
+            std::cout << " " << pres << "\n";
+        }
+    }
+
+    std::cout << "\n\n";
+}
+
+
+bool NetWork::node_exist(const std::string& name)
+{
+   for (size_t n = 0; n < m_node_list.size(); n ++)
+       if (name == m_node_list[n]->name())
+           return true;
+
+   return false;
+}
+
+
+void NetWork::add_node(const std::string& name)
+{
+    if (!this->node_exist(name))
+        m_node_list.push_back(std::make_shared<Node>(name));
+    else {
+        std::cout << "in function add_node: Node " << name << " already exists \n\n";
+        exit(1);
+    }
+}
+
+
+void  NetWork::add_branch(const std::string& downtree, const std::string& uptree, int vfp)
+{
+    if (vfp == 0){
+        std::cout << "\n!Error, vfp = 0, use function remove_branch to remove a branch \n\n";
+        exit(1);
+    }
+
+    std::shared_ptr<Node> pUptree;
+    std::shared_ptr<Node> pDowntree;
+
+    // handle uptree node
+
+    for (size_t n = 0; n < m_node_list.size(); n ++)
+        if (m_node_list[n]->name() == uptree)
+            pUptree = m_node_list[n];
+
+    if (pUptree == nullptr){
+        add_node(uptree);
+        pUptree = m_node_list.back();
+    }
+
+    // handle down tree node
+
+    for (size_t n = 0; n < m_node_list.size(); n ++)
+        if (m_node_list[n]->name() == downtree)
+            pDowntree = m_node_list[n];
+
+    if (pDowntree == nullptr){
+        add_node(downtree);
+        pDowntree = m_node_list.back();
+    }
+
+    pDowntree->set_outlet(pUptree);
+    pDowntree->set_vfp(vfp);
+
+
+    pUptree->add_inlet_node(pDowntree);
+}
+
+void  NetWork::delete_branch(const std::string& downtree, const std::string& uptree)
+{
+    std::shared_ptr<Node> pUptree;
+    std::shared_ptr<Node> pDowntree;
+
+    for (size_t n = 0; n < m_node_list.size(); n ++)
+        if (m_node_list[n]->name() == uptree)
+            pUptree = m_node_list[n];
+
+    for (size_t n = 0; n < m_node_list.size(); n ++)
+        if (m_node_list[n]->name() == downtree)
+            pDowntree = m_node_list[n];
+
+    if ((pUptree == nullptr) || (pDowntree == nullptr)) {
+        std::cout << "\n!Error, pointer to downtree and/or uptree not found \n\n";
+        exit(1);
+    }
+
+    if (pUptree->detete_from_inlet_list(downtree) == false)  {
+        std::cout << "\n!Error, problem with deleteing branch, needs to be checked  \n\n";
+        exit(1);
+    }
+
+    pDowntree->reset_outlet();
+
+    bool found_in_list = false;
+
+    for (size_t n = 0; n < m_top_node_list.size(); n++)
+        if (m_top_node_list[n]->name() == downtree)
+            found_in_list = true;
+
+    if (!found_in_list)
+        m_top_node_list.push_back(pDowntree);
+}
+
+
+void  NetWork::build_network(int rstep)
+{
+    std::vector<int> input_step_vect;
+
+    // input from restart file -> each report step describes a complete state of network
+    // input from data deck -> network build from all steps up to report step (branprop increments)
+
+    if (m_from_unrst)
+        input_step_vect.push_back(rstep - 1);
+    else
+        for (int n = 0; n < rstep; n++)
+            input_step_vect.push_back(n);
+
+    for (auto& n : input_step_vect){
+
+        for (size_t b = 0; b < m_bran_input_list[n].size(); b ++){
+            auto downtree = std::get<0>(m_bran_input_list[n][b]);
+            auto uptree = std::get<1>(m_bran_input_list[n][b]);
+            auto vfp = std::get<2>(m_bran_input_list[n][b]);
+
+            if (vfp == 0)
+                delete_branch(downtree, uptree);
+            else
+                add_branch(downtree, uptree, vfp);
+        }
+    }
+
+    m_top_node_list.clear();
+
+    for (size_t n = 0; n < m_node_list.size(); n++)
+        if (m_node_list[n]->get_outlet() == nullptr)
+            m_top_node_list.push_back(m_node_list[n]);
+
+    std::map<std::string, std::string> well_map;
+
+    for (auto n : input_step_vect){
+        for (size_t b = 0; b < m_well_input_list[n].size(); b ++){
+            auto wname = std::get<0>(m_well_input_list[n][b]);
+            auto gname = std::get<1>(m_well_input_list[n][b]);
+            well_map[wname] = gname;
+        }
+    }
+
+    for (auto m : well_map){
+        auto gname = m.second;
+        auto wname = m.first;
+
+        for (size_t n = 0; n < m_node_list.size(); n++)
+            if (m_node_list[n]->name() == gname)
+                m_node_list[n]->add_well(wname);
+    }
+
+    for (auto n : input_step_vect){
+        for (size_t b = 0; b < m_node_input_list[n].size(); b ++){
+            auto node = std::get<0>(m_node_input_list[n][b]);
+            auto pressure = std::get<1>(m_node_input_list[n][b]);
+
+            for (size_t m = 0; m < m_node_list.size(); m ++ )
+                if (m_node_list[m]->name() == node)
+                    m_node_list[m]->set_fixed_pres(pressure);
+        }
+    }
+}
+
+void NetWork::print_network(int rstep){
+
+    std::cout << "\n\n";
+
+    time_t t = m_report_time_list[rstep - 1];
+
+    std::cout << "Report step : " << time_str(t) << "\n\n";
+
+    for (size_t n = 0; n < m_top_node_list.size(); n++){
+
+        m_top_node_list[n]->print(m_netw_str);
+        m_netw_str << "\n\n";
+    }
+
+    std::cout << "\n" << m_netw_str.str() << "";
+
+    std::cout << "\nFixed pressure nodes: \n\n";
+
+    for (size_t n = 0; n < m_node_list.size(); n++){
+        auto fixed_pres = m_node_list[n]->get_fixed_pres();
+
+        if (fixed_pres > -1.0){
+            std::cout << "  " << m_node_list[n]->name() << " = ";
+            std::cout << std::fixed << std::setprecision(2) << fixed_pres;
+        }
+    }
+
+    std::cout << "\n\n\n";
+}
+
+void NetWork::print_report_steps(){
+
+    if (!m_from_unrst)
+        std::cout << "\n\nStart date  " << time_str(m_start_date) << "\n";
+
+    std::cout << "\nList of all report steps \n\n";
+
+    for (size_t n = 0; n < m_report_time_list.size(); n ++)
+        std::cout << "Report step " << n + 1 << "  | " << time_str(m_report_time_list[n]) << "\n";
+}
+
+time_t NetWork::time_from_rec(const Opm::DeckRecord& rec){
+
+    std::map<std::string, int> mnd_map{{"JAN", 0}, {"FEB", 1}, {"MAR", 2}, {"APR", 3}, {"MAY", 4}, {"JUN", 5},
+                                       {"JUL", 6}, {"JLY", 6}, {"AUG", 7}, {"SEP", 8}, {"OCT", 9}, {"NOV", 10},
+                                       {"DEC", 11}};
+
+    auto day = rec.getItem(0).get<int>(0);
+    auto mndStr = rec.getItem(1).get<std::string>(0);
+    auto year = rec.getItem(2).get<int>(0);
+    auto time = rec.getItem(3).get<std::string>(0);
+
+    int sec_frac = 0;
+
+    auto p1 = time.find(".");
+
+    if (p1 != std::string::npos){
+        auto sec_frac_str = time.substr (p1 + 1);
+        sec_frac = std::stoi(sec_frac_str);
+        time = time.substr (0, p1);
+    }
+
+    if (sec_frac > 0){
+        std::cout << "\n!Error, fraction of section not supported \n";
+        exit(1);
+    }
+
+    p1 = time.find(":");
+
+    if (p1 == std::string::npos){
+        std::cout << "\n!Error, invalied format for time " << time << "\n";
+        exit(1);
+    }
+
+    auto p2 = time.find(":", p1 + 1);
+    if (p2 == std::string::npos){
+        std::cout << "\n!Error, Second invalied format for time " << time << "\n";
+        exit(1);
+    }
+
+    int h = std::stoi(time.substr (0, p1));
+    int min = std::stoi(time.substr (p1+1, p2-p1-1));
+    int sec = std::stoi(time.substr (p2+1));
+
+    int mnd = mnd_map.at(mndStr);
+
+    //std::tm date1 = {sec, min, h, day, mnd, year - 1900 };
+
+    std::tm date1 = {};
+
+    date1.tm_sec = sec;
+    date1.tm_min = min;
+    date1.tm_hour = h;
+
+    date1.tm_mday  = day;
+    date1.tm_mon = mnd;
+    date1.tm_year = year - 1900;
+
+    return std::mktime(&date1);
+}
+
+
+time_t NetWork::time_from_rst(const std::string& rstfile, int rstep){
+
+    Opm::EclIO::ERst rst1(rstfile);
+
+    auto inteh = rst1.getRestartData<int>("INTEHEAD", rstep);
+
+    int year = inteh[66];
+    int mnd = inteh[65];
+    int day = inteh[64];
+
+    int h = inteh[206];
+    int min = inteh[207];
+    int sec = static_cast<int>(inteh[410] / 1000000);
+
+    std::tm date1 = {};
+
+    date1.tm_sec = sec;
+    date1.tm_min = min;
+    date1.tm_hour = h;
+
+    date1.tm_mday  = day;
+    date1.tm_mon = mnd - 1;
+    date1.tm_year = year - 1900;
+
+    return std::mktime(&date1);
+}
+
+void NetWork::br_input_from_rst(const std::string& rstfile, std::vector<int> rstep_vect){
+
+    Opm::EclIO::ERst rst1(rstfile);
+
+    for (int& rstep : rstep_vect){
+
+        m_rst_time = time_from_rst(rstfile, rstep);
+        m_report_time_list.push_back(m_rst_time);
+
+        auto intehead = rst1.getRestartData<int>("INTEHEAD", rstep);
+
+        if (rst1.hasArray("ZNODE", rstep)){
+
+            std::vector<std::string> nodelist;
+
+            auto noactnod = intehead[129];  // Number of active/defined nodes in the network
+            auto nibran = intehead[133];    // number of entries per branch in the IBRAN array
+            auto noactbr = intehead[130];   // Number of active/defined branches in the network
+            auto nrnode = intehead[136];    // number of entries per node in the RNODE array
+
+            auto znode = rst1.getRestartData<std::string>("ZNODE", rstep);
+            auto ibran = rst1.getRestartData<int>("IBRAN", rstep);
+            auto rnode = rst1.getRestartData<double>("RNODE", rstep);
+
+
+            for (int n = 0; n < noactnod; n++)
+                nodelist.push_back(znode[2*n]);
+
+
+            for (int b = 0; b < noactbr; b++){
+                int ind = b*nibran;
+                std::string downtree = nodelist[ibran[ind] - 1];
+                std::string uptree = nodelist[ibran[ind + 1] - 1];
+                int vfp = ibran[ind + 2];
+
+                auto br = std::make_tuple(downtree, uptree, vfp);
+                m_bran_input_list.back().push_back(br);
+            }
+
+            for (int n = 0; n < noactnod; n++){
+                int ind = n*nrnode;
+                if (rnode[ind + 1] == 0.0){
+                    auto node = std::make_tuple(nodelist[n], rnode[ind + 2]);
+                    m_node_input_list.back().push_back(node);
+                }
+            }
+        }
+
+        std::vector<std::string> grouplist;
+
+        auto nzwelz = intehead[27];     // Number of 8-character words per well in ZWEL array
+        auto nswells = intehead[16];    // Number of wells
+        auto nzgrpz = intehead[39];     // Number of data elements per group in ZGRP array
+        auto ngmaxz = intehead[20];     // Maximum number of groups in field
+        auto niwelz = intehead[24];     // Number of data elements per well in IWEL array
+
+        auto zwel = rst1.getRestartData<std::string>("ZWEL", rstep);
+        auto iwel = rst1.getRestartData<int>("IWEL", rstep);
+        auto zgrp = rst1.getRestartData<std::string>("ZGRP", rstep);
+
+        for (int g = 0; g < ngmaxz; g ++)
+            grouplist.push_back(zgrp[g*nzgrpz]);
+
+        for (int n = 0; n < nswells; n++){
+            std::string wname = zwel[n*nzwelz];
+            int grp_ind = iwel[n*niwelz + 5] - 1;
+            std::string gname = grouplist[grp_ind];
+            auto well = std::make_tuple(wname, gname);
+            m_well_input_list.back().push_back(well);
+        }
+
+        m_node_input_list.push_back({});
+        m_bran_input_list.push_back({});
+        m_well_input_list.push_back({});
+    }
+}
+
+
+static void printHelp() {
+
+    std::cout << "\n This program visualizes a production network with termial output."
+              << " Input to this program should be a valied data deck (.DATA) \n or a unified"
+              << " restart file (.UNRST).\n\n The program takes these options"
+              << " (which must be given before the arguments):\n\n"
+              << " -l lists all available report steps and exit.\n"
+              << " -r selets report step to be visualized. Default is the last report step \n"
+              << " -h Print help and exit.\n\n";
+
+}
+
+
+int main(int argc, char **argv) {
+
+    int c                          = 0;
+    bool list_report_steps         = false;
+    int rstep                      = -1;
+
+    while ((c = getopt(argc, argv, "lr:h")) != -1) {
+        switch (c) {
+        case 'l':
+            list_report_steps = true;
+            break;
+        case 'h':
+            printHelp();
+            return 0;
+        case 'r':
+            rstep = atoi(optarg);
+            break;
+        default:
+            return EXIT_FAILURE;
+        }
+    }
+
+    int argOffset = optind;
+
+    NetWork netw(argv[argOffset]);
+
+    if (list_report_steps) {
+        netw.print_report_steps();
+        std::cout << "\n";
+
+        return 0;
+    }
+
+    if (rstep == -1){
+        rstep = netw.number_report_steps();
+    } else {
+
+        if ((rstep < 1) || (rstep > netw.number_report_steps())){
+            std::cout << "\n!Error, invalid report step " << rstep;
+            std::cout << " should be > 0 and less than " << netw.number_report_steps() << "\n";
+            std::cout << "        use option -l to list all report steps. "<< "\n\n";
+            exit(1);
+        }
+    }
+
+    netw.build_network(rstep);
+    netw.print_network(rstep);
+
+    std::cout << "\n\n";
+
+    return 0;
path: examples/networkgraph.cpp
user: login: atgeirr
body: Can use EXIT_SUCCESS instead of 0.
created_at: 2025-04-10T13:23:41Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,909 @@
+/*
+  Copyright 2025 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 <iostream>
+#include <getopt.h>
+#include <sstream>
+
+#include <opm/io/eclipse/ERst.hpp>
+
+#include <opm/input/eclipse/Deck/Deck.hpp>
+
+#include <opm/input/eclipse/Parser/ErrorGuard.hpp>
+#include <opm/input/eclipse/Parser/InputErrorAction.hpp>
+#include <opm/input/eclipse/Parser/Parser.hpp>
+#include <opm/input/eclipse/Parser/ParseContext.hpp>
+
+
+class Node
+{
+public:
+    Node(const std::string& name);
+
+    std::string name() const { return m_name; }
+    void set_outlet(std::shared_ptr<Node> outlet) { m_outlet = outlet; };
+    std::shared_ptr<Node> get_outlet() { return m_outlet; };
+
+    void add_inlet_node(std::shared_ptr<Node> n);
+
+    void set_vfp(int vfp){m_vfp = vfp;};
+    int get_vfp(){ return m_vfp;};
+
+    void set_fixed_pres(double pres){m_fixed_pres = pres; };
+    double get_fixed_pres(){return m_fixed_pres; };
+
+    int get_xpos(){return m_xpos;};
+
+    void print(std::stringstream& netw_str);
+
+    bool detete_from_inlet_list(const std::string& name);
path: examples/networkgraph.cpp
user: login: tskille
body: fixed
created_at: 2025-04-10T18:43:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,909 @@
+/*
+  Copyright 2025 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 <iostream>
+#include <getopt.h>
+#include <sstream>
+
+#include <opm/io/eclipse/ERst.hpp>
+
+#include <opm/input/eclipse/Deck/Deck.hpp>
+
+#include <opm/input/eclipse/Parser/ErrorGuard.hpp>
+#include <opm/input/eclipse/Parser/InputErrorAction.hpp>
+#include <opm/input/eclipse/Parser/Parser.hpp>
+#include <opm/input/eclipse/Parser/ParseContext.hpp>
+
+
+class Node
+{
+public:
+    Node(const std::string& name);
path: examples/networkgraph.cpp
user: login: tskille
body: fixed
created_at: 2025-04-10T18:43:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,909 @@
+/*
+  Copyright 2025 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 <iostream>
+#include <getopt.h>
+#include <sstream>
+
+#include <opm/io/eclipse/ERst.hpp>
+
+#include <opm/input/eclipse/Deck/Deck.hpp>
+
+#include <opm/input/eclipse/Parser/ErrorGuard.hpp>
+#include <opm/input/eclipse/Parser/InputErrorAction.hpp>
+#include <opm/input/eclipse/Parser/Parser.hpp>
+#include <opm/input/eclipse/Parser/ParseContext.hpp>
+
+
+class Node
+{
+public:
+    Node(const std::string& name);
+
+    std::string name() const { return m_name; }
+    void set_outlet(std::shared_ptr<Node> outlet) { m_outlet = outlet; };
+    std::shared_ptr<Node> get_outlet() { return m_outlet; };
+
+    void add_inlet_node(std::shared_ptr<Node> n);
+
+    void set_vfp(int vfp){m_vfp = vfp;};
+    int get_vfp(){ return m_vfp;};
+
+    void set_fixed_pres(double pres){m_fixed_pres = pres; };
+    double get_fixed_pres(){return m_fixed_pres; };
+
+    int get_xpos(){return m_xpos;};
+
+    void print(std::stringstream& netw_str);
+
+    bool detete_from_inlet_list(const std::string& name);
+
+    bool add_well(const std::string& name);
+
+    void reset_outlet(){m_outlet = nullptr;};
+
+private:
+    int m_vfp;
+    int m_xpos = -1;
+    double m_fixed_pres = -1.0;
+    int m_ind = -1;
+
+    std::string m_name;
+    std::shared_ptr<Node> m_outlet;
+    std::vector<std::shared_ptr<Node>> m_inlet_list;
+    std::vector<std::string> m_well_list;
+
+    std::shared_ptr<Node> next_branch();
+};
+
+
+Node::Node(const std::string& name)
+   : m_name(name)
+{
+    m_vfp = -1;
+}
+
+std::shared_ptr<Node> Node::next_branch(){
+
+    std::shared_ptr<Node> p1 = m_outlet;
+
+    while (p1 != nullptr){
+        for (size_t n = 0; n < p1->m_inlet_list.size(); n ++){
+            if (p1->m_inlet_list[n]->m_xpos == -1){
+                return p1;
+            }
+        }
+        p1 = p1->m_outlet;
+    }
+
+    return p1;
+}
+
+bool Node::detete_from_inlet_list(const std::string& name){
+
+    int ind = -1;
+
+    for (size_t n = 0; n < m_inlet_list.size(); n ++)
+        if (m_inlet_list[n]->name() == name)
+            ind = n;
+
+    if (ind < 0){
+        return false;
+    } else {
+        m_inlet_list.erase (m_inlet_list.begin() + ind);
+        return true;
+    }
+}
+
+bool Node::add_well(const std::string& name){
+   if (m_inlet_list.size() > 0)
+       return false;
+
+   m_well_list.push_back(name);
+
+   return true;
+}
+
+
+void Node::print(std::stringstream& netw_str){
+
+    netw_str.seekg(0, std::ios::end);
+    int init_length = netw_str.tellg();
+
+    int pos_lineshift = 0;
+
+    auto p = netw_str.str().find_last_of("\n");
path: examples/networkgraph.cpp
user: login: tskille
body: The str() method is called twice for each node. First I need to find position of end of line, then I'm adding more characters to the string stream before I again need to find the new position.  I would like to keep this as is until we see that this is a performance issue.

created_at: 2025-04-10T18:47:32Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,909 @@
+/*
+  Copyright 2025 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 <iostream>
+#include <getopt.h>
+#include <sstream>
+
+#include <opm/io/eclipse/ERst.hpp>
+
+#include <opm/input/eclipse/Deck/Deck.hpp>
+
+#include <opm/input/eclipse/Parser/ErrorGuard.hpp>
+#include <opm/input/eclipse/Parser/InputErrorAction.hpp>
+#include <opm/input/eclipse/Parser/Parser.hpp>
+#include <opm/input/eclipse/Parser/ParseContext.hpp>
+
+
+class Node
+{
+public:
+    Node(const std::string& name);
+
+    std::string name() const { return m_name; }
+    void set_outlet(std::shared_ptr<Node> outlet) { m_outlet = outlet; };
+    std::shared_ptr<Node> get_outlet() { return m_outlet; };
+
+    void add_inlet_node(std::shared_ptr<Node> n);
+
+    void set_vfp(int vfp){m_vfp = vfp;};
+    int get_vfp(){ return m_vfp;};
+
+    void set_fixed_pres(double pres){m_fixed_pres = pres; };
+    double get_fixed_pres(){return m_fixed_pres; };
+
+    int get_xpos(){return m_xpos;};
+
+    void print(std::stringstream& netw_str);
+
+    bool detete_from_inlet_list(const std::string& name);
+
+    bool add_well(const std::string& name);
+
+    void reset_outlet(){m_outlet = nullptr;};
+
+private:
+    int m_vfp;
+    int m_xpos = -1;
+    double m_fixed_pres = -1.0;
+    int m_ind = -1;
+
+    std::string m_name;
+    std::shared_ptr<Node> m_outlet;
+    std::vector<std::shared_ptr<Node>> m_inlet_list;
+    std::vector<std::string> m_well_list;
+
+    std::shared_ptr<Node> next_branch();
+};
+
+
+Node::Node(const std::string& name)
+   : m_name(name)
+{
+    m_vfp = -1;
+}
+
+std::shared_ptr<Node> Node::next_branch(){
+
+    std::shared_ptr<Node> p1 = m_outlet;
+
+    while (p1 != nullptr){
+        for (size_t n = 0; n < p1->m_inlet_list.size(); n ++){
+            if (p1->m_inlet_list[n]->m_xpos == -1){
+                return p1;
+            }
+        }
+        p1 = p1->m_outlet;
+    }
+
+    return p1;
+}
+
+bool Node::detete_from_inlet_list(const std::string& name){
+
+    int ind = -1;
+
+    for (size_t n = 0; n < m_inlet_list.size(); n ++)
+        if (m_inlet_list[n]->name() == name)
+            ind = n;
+
+    if (ind < 0){
+        return false;
+    } else {
+        m_inlet_list.erase (m_inlet_list.begin() + ind);
+        return true;
+    }
+}
+
+bool Node::add_well(const std::string& name){
+   if (m_inlet_list.size() > 0)
+       return false;
+
+   m_well_list.push_back(name);
+
+   return true;
+}
+
+
+void Node::print(std::stringstream& netw_str){
+
+    netw_str.seekg(0, std::ios::end);
+    int init_length = netw_str.tellg();
+
+    int pos_lineshift = 0;
+
+    auto p = netw_str.str().find_last_of("\n");
+
+    if (p != std::string::npos)
+        pos_lineshift = p + 1;
+
+    if (m_outlet == nullptr){
+        netw_str << "  o (" << m_name << ")";
+        m_xpos = netw_str.str().find_first_of("o", init_length) - pos_lineshift;
+    } else if (m_vfp == 9999){
+        netw_str << " --- +(" << m_name << ")";
+        m_xpos = netw_str.str().find_first_of("+", init_length) - pos_lineshift;
+    } else {
+        netw_str << " --[" << m_vfp << "]-- +(" << m_name << ")";
+        m_xpos = netw_str.str().find_first_of("+", init_length) - pos_lineshift;
+    }
+
+
+    for (size_t m = 0; m < m_inlet_list.size(); m ++)
+        m_inlet_list[m]->print(netw_str);
+
+    if (m_inlet_list.size() == 0){
+        auto next_br = next_branch();
+
+        netw_str << " : ";
+
+        for (auto& well: m_well_list)
+            netw_str << " " << well;
+
+        if (next_br != nullptr){
+            std::string xpos_str(next_br->m_xpos, ' ');
+            netw_str << "\n" << xpos_str << "\\\n" << xpos_str;
+        }
+    }
+}
+
+void Node::add_inlet_node(std::shared_ptr<Node> node)
+{
+    for (size_t n = 0; n < m_inlet_list.size(); n ++ ){
path: examples/networkgraph.cpp
user: login: tskille
body: updated with std::find_if. Yes this is needed. Consider following example 

```
BRANPROP
-- downtree   uptree
-- node       node
  'PD1'      'PROD1'  8   /
  'PD1'      'PROD1'  9   /
  'TEST1'    'PD1'  9999 /
/
```

The second record is replacing the the first and changing the lift curve.

created_at: 2025-04-10T18:50:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,909 @@
+/*
+  Copyright 2025 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 <iostream>
+#include <getopt.h>
+#include <sstream>
+
+#include <opm/io/eclipse/ERst.hpp>
+
+#include <opm/input/eclipse/Deck/Deck.hpp>
+
+#include <opm/input/eclipse/Parser/ErrorGuard.hpp>
+#include <opm/input/eclipse/Parser/InputErrorAction.hpp>
+#include <opm/input/eclipse/Parser/Parser.hpp>
+#include <opm/input/eclipse/Parser/ParseContext.hpp>
+
+
+class Node
+{
+public:
+    Node(const std::string& name);
+
+    std::string name() const { return m_name; }
+    void set_outlet(std::shared_ptr<Node> outlet) { m_outlet = outlet; };
+    std::shared_ptr<Node> get_outlet() { return m_outlet; };
+
+    void add_inlet_node(std::shared_ptr<Node> n);
+
+    void set_vfp(int vfp){m_vfp = vfp;};
+    int get_vfp(){ return m_vfp;};
+
+    void set_fixed_pres(double pres){m_fixed_pres = pres; };
+    double get_fixed_pres(){return m_fixed_pres; };
+
+    int get_xpos(){return m_xpos;};
+
+    void print(std::stringstream& netw_str);
+
+    bool detete_from_inlet_list(const std::string& name);
+
+    bool add_well(const std::string& name);
+
+    void reset_outlet(){m_outlet = nullptr;};
+
+private:
+    int m_vfp;
+    int m_xpos = -1;
+    double m_fixed_pres = -1.0;
+    int m_ind = -1;
+
+    std::string m_name;
+    std::shared_ptr<Node> m_outlet;
+    std::vector<std::shared_ptr<Node>> m_inlet_list;
+    std::vector<std::string> m_well_list;
+
+    std::shared_ptr<Node> next_branch();
+};
+
+
+Node::Node(const std::string& name)
+   : m_name(name)
+{
+    m_vfp = -1;
+}
+
+std::shared_ptr<Node> Node::next_branch(){
+
+    std::shared_ptr<Node> p1 = m_outlet;
+
+    while (p1 != nullptr){
+        for (size_t n = 0; n < p1->m_inlet_list.size(); n ++){
+            if (p1->m_inlet_list[n]->m_xpos == -1){
+                return p1;
+            }
+        }
+        p1 = p1->m_outlet;
+    }
+
+    return p1;
+}
+
+bool Node::detete_from_inlet_list(const std::string& name){
+
+    int ind = -1;
+
+    for (size_t n = 0; n < m_inlet_list.size(); n ++)
+        if (m_inlet_list[n]->name() == name)
+            ind = n;
+
+    if (ind < 0){
+        return false;
+    } else {
+        m_inlet_list.erase (m_inlet_list.begin() + ind);
+        return true;
+    }
+}
+
+bool Node::add_well(const std::string& name){
+   if (m_inlet_list.size() > 0)
+       return false;
+
+   m_well_list.push_back(name);
+
+   return true;
+}
+
+
+void Node::print(std::stringstream& netw_str){
+
+    netw_str.seekg(0, std::ios::end);
+    int init_length = netw_str.tellg();
+
+    int pos_lineshift = 0;
+
+    auto p = netw_str.str().find_last_of("\n");
+
+    if (p != std::string::npos)
+        pos_lineshift = p + 1;
+
+    if (m_outlet == nullptr){
+        netw_str << "  o (" << m_name << ")";
+        m_xpos = netw_str.str().find_first_of("o", init_length) - pos_lineshift;
+    } else if (m_vfp == 9999){
+        netw_str << " --- +(" << m_name << ")";
+        m_xpos = netw_str.str().find_first_of("+", init_length) - pos_lineshift;
+    } else {
+        netw_str << " --[" << m_vfp << "]-- +(" << m_name << ")";
+        m_xpos = netw_str.str().find_first_of("+", init_length) - pos_lineshift;
+    }
+
+
+    for (size_t m = 0; m < m_inlet_list.size(); m ++)
+        m_inlet_list[m]->print(netw_str);
+
+    if (m_inlet_list.size() == 0){
+        auto next_br = next_branch();
+
+        netw_str << " : ";
+
+        for (auto& well: m_well_list)
+            netw_str << " " << well;
+
+        if (next_br != nullptr){
+            std::string xpos_str(next_br->m_xpos, ' ');
+            netw_str << "\n" << xpos_str << "\\\n" << xpos_str;
+        }
+    }
+}
+
+void Node::add_inlet_node(std::shared_ptr<Node> node)
+{
+    for (size_t n = 0; n < m_inlet_list.size(); n ++ ){
+        if (m_inlet_list[n]->name() == node->name()){
+            m_inlet_list.erase (m_inlet_list.begin() + n);
+            break;
+        }
+    }
+
+    m_inlet_list.push_back(node);
+}
+
+
+class NetWork
+{
+public:
+    // upptree, downtree, vfp
+    using bran_input_type = std::tuple<std::string, std::string, int>;
+
+    // name and fixed pressure
+    using node_input_type = std::tuple<std::string, int>;
+
+    // well and group name
+    using well_input_type = std::tuple<std::string, std::string>;
+
+    NetWork(const std::string& filename);
+
+    void print_report_steps();
+    int number_report_steps() {return m_report_time_list.size();}
+
+    void build_network(int rstep);
+    void print_network(int rstep);
+
+private:
+    time_t time_from_rec(const Opm::DeckRecord& rec);
+    time_t time_from_rst(const std::string& rstfile, int rstep);
+
+    bool node_exist(const std::string& name);
+
+    void add_node(const std::string& name);
+    void add_branch(const std::string& downtree, const std::string& uptree, int vfp);
+    void delete_branch(const std::string& downtree, const std::string& uptree);
+
+    void br_input_from_rst(const std::string& rstfile, std::vector<int> rstep_vect);
+
+    void print_network_input();
+    void parse_data_deck(const std::filesystem::path& inputFileName);
+    void parse_unrst(const std::filesystem::path& inputFileName);
+
+    std::stringstream m_netw_str;
+
+    time_t m_start_date;
+    time_t m_rst_time;
+
+    bool m_from_unrst = false;
+
+    std::vector<time_t> m_report_time_list;
+    std::vector<std::vector<bran_input_type>> m_bran_input_list;
+    std::vector<std::vector<node_input_type>> m_node_input_list;
+    std::vector<std::vector<well_input_type>> m_well_input_list;
+
+    std::string time_str(time_t t1);
+
+    std::vector<std::shared_ptr<Node>> m_node_list;
+    std::vector<std::shared_ptr<Node>> m_top_node_list;
+};
+
+
+NetWork::NetWork(const std::string& filename)
+{
+    std::filesystem::path inputFileName { filename } ;
+
+    if (inputFileName.extension()== ".DATA"){
+
+        parse_data_deck(inputFileName);
+
+    } else if (inputFileName.extension()== ".UNRST"){
+
+        parse_unrst(inputFileName);
+        m_from_unrst = true;
+
+    } else {
+        std::cout << "\n!Error, unsupported file type " << filename << "\n\n";
+        exit(1);
+    }
+}
+
+
+void NetWork::parse_data_deck(const std::filesystem::path& inputFileName){
+
+    Opm::ParseContext parseContext;
+    parseContext.update(Opm::ParseContext::PARSE_UNKNOWN_KEYWORD , Opm::InputErrorAction::IGNORE );
+    parseContext.update(Opm::ParseContext::PARSE_RANDOM_TEXT , Opm::InputErrorAction::IGNORE );
+    parseContext.update(Opm::ParseContext::PARSE_EXTRA_RECORDS , Opm::InputErrorAction::IGNORE );
+    parseContext.update(Opm::ParseContext::PARSE_RANDOM_SLASH , Opm::InputErrorAction::IGNORE );
+
+    std::vector<Opm::Ecl::SectionType> sections = {Opm::Ecl::RUNSPEC, Opm::Ecl::SOLUTION, Opm::Ecl::SCHEDULE};
+
+    Opm::Parser parser;
+
+    Opm::Deck deck_schecule;
+
+    try {
+        deck_schecule = parser.parseFile(inputFileName, parseContext, sections);
+
+    } catch (const std::exception& e){
+
+        std::cout << "\n!Error parsing data deck " << inputFileName << "\n\n";
+        std::cout << e.what() << "\n\n";
+
+        exit(1);
+    }
+
+    bool restart = false;
+    bool skiprest = false;
+
+    time_t last_time;
+
+    auto network_keyw =  deck_schecule["NETWORK"];
+
+    if (network_keyw.size() == 0){
+        std::cout << "\n > !Error, data deck "  << inputFileName << " doesn't include a production network \n\n";
+        exit(1);
+    }
+
+    for (auto keyw : deck_schecule){
+
+        if (keyw.name() == "START"){
+            m_node_input_list.push_back({});
+            m_bran_input_list.push_back({});
+            m_well_input_list.push_back({});
+            m_start_date = time_from_rec(keyw[0]);
+
+            last_time = m_start_date;
+
+        } else if (keyw.name() == "TSTEP"){
+
+            for (size_t n = 0; n < keyw[0].getItem(0).data_size(); n++){
+                auto dt = keyw[0].getItem(0).get<double>(n);
+                last_time = last_time + static_cast<int>(dt * 24.0 * 3600.0);
+
+                if (!skiprest){
+                    m_report_time_list.push_back(last_time);
+                    m_node_input_list.push_back({});
+                    m_bran_input_list.push_back({});
+                    m_well_input_list.push_back({});
+                }
+
+                if ((skiprest) && (last_time >= m_rst_time))
+                    skiprest = false;
+            }
+
+        } else if (keyw.name() == "DATES"){
+            for (auto& rec : keyw ){
+                last_time = time_from_rec(rec);
+
+                if (m_report_time_list.size() == 0){
+                    if ((last_time <=  m_start_date) && (!skiprest)) {
+                        std::cout << "\n!Error, next report step '" << time_str(last_time) << "' has already passed \n\n";
+                        exit(1);
+                    }
+                } else {
+                    if ((last_time <=  m_report_time_list.back()) && (!skiprest)) {
+                        std::cout << "\n!Error, next report step '" << time_str(last_time) << "' has already passed \n\n";
+                        exit(1);
+                    }
+                }
+
+                if (!skiprest){
+                    m_report_time_list.push_back(last_time);
+                    m_node_input_list.push_back({});
+                    m_bran_input_list.push_back({});
+                    m_well_input_list.push_back({});
+                }
+
+                if ((skiprest) && (last_time >= m_rst_time))
+                    skiprest = false;
+            }
+
+        } else if (keyw.name() == "RESTART"){
+
+            auto rst_file = keyw[0].getItem(0).get<std::string>(0) + ".UNRST";
+            auto rst_rstep = keyw[0].getItem(1).get<int>(0);
+
+            br_input_from_rst(rst_file, {rst_rstep} );
+
+            restart = true;
+
+        } else if (keyw.name() == "SKIPREST"){
+
+            if (restart)
+                skiprest = true;
+
+        } else if ((keyw.name() == "BRANPROP") && (!skiprest)){
+            for (auto& rec : keyw ){
+
+                auto downtree = rec.getItem(0).get<std::string>(0);
+                auto uptree = rec.getItem(1).get<std::string>(0);
+                auto vfp = rec.getItem(2).get<int>(0);
+
+                auto br = std::make_tuple(downtree, uptree, vfp);
+
+                m_bran_input_list.back().push_back(br);
+            }
+
+        } else if ((keyw.name() == "NODEPROP") && (!skiprest)){
+            for (auto& rec : keyw ){
+                if (rec.getItem(1).hasValue(0)){
+                    auto node_name = rec.getItem(0).get<std::string>(0);
+                    auto node_pres = rec.getItem(1).get<double>(0);
+                    auto node = std::make_tuple(node_name, node_pres);
+                    m_node_input_list.back().push_back(node);
+                }
+            }
+
+        } else if ((keyw.name() == "WELSPECS") && (!skiprest)){
+            for (auto& rec : keyw ){
+                auto wname = rec.getItem(0).get<std::string>(0);
+                auto gname = rec.getItem(1).get<std::string>(0);
+
+                auto well = std::make_tuple(wname, gname);
+
+                m_well_input_list.back().push_back(well);
+            }
+        }
+    }
+}
+
+void NetWork::parse_unrst(const std::filesystem::path& inputFileName){
+
+    Opm::EclIO::ERst rst1(inputFileName);
+
+    auto all_reports = rst1.listOfReportStepNumbers();
+
+    std::vector<int> rstep_vect;
+
+    for (auto r : all_reports)
+        if (r > 0)
+            rstep_vect.push_back(r);
+
+    m_node_input_list.push_back({});
+    m_bran_input_list.push_back({});
+    m_well_input_list.push_back({});
+
+    br_input_from_rst(inputFileName, rstep_vect);
+}
+
+std::string NetWork::time_str(time_t t1){
+
+    std::vector<std::string> mndStr{"JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"};
+
+    std::tm* date = std::localtime(&t1);
+
+    std::stringstream date_str;
+
+    // ignoring daylight saving time
+
+    int hr = date->tm_hour;
+    int day = date->tm_mday;
+
+    if (date->tm_isdst == 1){
+        hr = hr - 1;
+        if (hr < 0){
+            hr = hr + 24;
+            day = day - 1;
+        }
+    }
+
+    date_str << std::setw(2) << std::setfill('0') << day;
+    date_str <<  " '" << mndStr[date->tm_mon] << "' " << date->tm_year + 1900;
+    date_str << " " << std::setw(2) << std::setfill('0') << hr;
+    date_str << ":" << std::setw(2) << std::setfill('0') << date->tm_min;
+    date_str << ":" << std::setw(2) << std::setfill('0') << date->tm_sec;
+
+    return date_str.str();
+}
+
+
+void NetWork::print_network_input(){
+
+    std::cout << "m_report_time_list: " << m_report_time_list.size() << "\n";
+    std::cout << "m_well_input_list : " << m_well_input_list.size() << "\n";
+
+    std::cout << "\nm_bran_input_list: " <<  m_bran_input_list.size() << "\n\n";
+
+    for (size_t r = 0; r <  m_bran_input_list.size(); r++){
+        for (size_t n = 0; n <  m_bran_input_list[r].size(); n++){
+            auto downtree = std::get<0>(m_bran_input_list[r][n]);
+            auto uptree = std::get<1>(m_bran_input_list[r][n]);
+            auto vfp = std::get<2>(m_bran_input_list[r][n]);
+
+            std::cout << "r= " << r << ", n= " << n << " " << downtree;
+            std::cout << " " << uptree << " " << vfp << "\n";
+        }
+    }
+
+    std::cout << "\n\nm_node_input_list: " <<  m_node_input_list.size() << "\n\n";
+
+    for (size_t r = 0; r <  m_node_input_list.size(); r++){
+        for (size_t n = 0; n <  m_node_input_list[r].size(); n++){
+            auto node = std::get<0>(m_node_input_list[r][n]);
+            auto pres = std::get<1>(m_node_input_list[r][n]);
+
+            std::cout << "r= " << r << ", n= " << n << " " << node;
+            std::cout << " " << pres << "\n";
+        }
+    }
+
+    std::cout << "\n\n";
+}
+
+
+bool NetWork::node_exist(const std::string& name)
+{
+   for (size_t n = 0; n < m_node_list.size(); n ++)
+       if (name == m_node_list[n]->name())
+           return true;
+
+   return false;
+}
+
+
+void NetWork::add_node(const std::string& name)
+{
+    if (!this->node_exist(name))
+        m_node_list.push_back(std::make_shared<Node>(name));
+    else {
+        std::cout << "in function add_node: Node " << name << " already exists \n\n";
+        exit(1);
+    }
+}
+
+
+void  NetWork::add_branch(const std::string& downtree, const std::string& uptree, int vfp)
+{
+    if (vfp == 0){
+        std::cout << "\n!Error, vfp = 0, use function remove_branch to remove a branch \n\n";
+        exit(1);
+    }
+
+    std::shared_ptr<Node> pUptree;
+    std::shared_ptr<Node> pDowntree;
+
+    // handle uptree node
+
+    for (size_t n = 0; n < m_node_list.size(); n ++)
+        if (m_node_list[n]->name() == uptree)
+            pUptree = m_node_list[n];
+
+    if (pUptree == nullptr){
+        add_node(uptree);
+        pUptree = m_node_list.back();
+    }
+
+    // handle down tree node
+
+    for (size_t n = 0; n < m_node_list.size(); n ++)
+        if (m_node_list[n]->name() == downtree)
+            pDowntree = m_node_list[n];
+
+    if (pDowntree == nullptr){
+        add_node(downtree);
+        pDowntree = m_node_list.back();
+    }
+
+    pDowntree->set_outlet(pUptree);
+    pDowntree->set_vfp(vfp);
+
+
+    pUptree->add_inlet_node(pDowntree);
+}
+
+void  NetWork::delete_branch(const std::string& downtree, const std::string& uptree)
+{
+    std::shared_ptr<Node> pUptree;
+    std::shared_ptr<Node> pDowntree;
+
+    for (size_t n = 0; n < m_node_list.size(); n ++)
+        if (m_node_list[n]->name() == uptree)
+            pUptree = m_node_list[n];
+
+    for (size_t n = 0; n < m_node_list.size(); n ++)
+        if (m_node_list[n]->name() == downtree)
+            pDowntree = m_node_list[n];
+
+    if ((pUptree == nullptr) || (pDowntree == nullptr)) {
+        std::cout << "\n!Error, pointer to downtree and/or uptree not found \n\n";
+        exit(1);
+    }
+
+    if (pUptree->detete_from_inlet_list(downtree) == false)  {
+        std::cout << "\n!Error, problem with deleteing branch, needs to be checked  \n\n";
+        exit(1);
+    }
+
+    pDowntree->reset_outlet();
+
+    bool found_in_list = false;
+
+    for (size_t n = 0; n < m_top_node_list.size(); n++)
+        if (m_top_node_list[n]->name() == downtree)
+            found_in_list = true;
+
+    if (!found_in_list)
+        m_top_node_list.push_back(pDowntree);
+}
+
+
+void  NetWork::build_network(int rstep)
+{
+    std::vector<int> input_step_vect;
+
+    // input from restart file -> each report step describes a complete state of network
+    // input from data deck -> network build from all steps up to report step (branprop increments)
+
+    if (m_from_unrst)
+        input_step_vect.push_back(rstep - 1);
+    else
+        for (int n = 0; n < rstep; n++)
+            input_step_vect.push_back(n);
+
+    for (auto& n : input_step_vect){
+
+        for (size_t b = 0; b < m_bran_input_list[n].size(); b ++){
+            auto downtree = std::get<0>(m_bran_input_list[n][b]);
+            auto uptree = std::get<1>(m_bran_input_list[n][b]);
+            auto vfp = std::get<2>(m_bran_input_list[n][b]);
+
+            if (vfp == 0)
+                delete_branch(downtree, uptree);
+            else
+                add_branch(downtree, uptree, vfp);
+        }
+    }
+
+    m_top_node_list.clear();
+
+    for (size_t n = 0; n < m_node_list.size(); n++)
+        if (m_node_list[n]->get_outlet() == nullptr)
+            m_top_node_list.push_back(m_node_list[n]);
+
+    std::map<std::string, std::string> well_map;
+
+    for (auto n : input_step_vect){
+        for (size_t b = 0; b < m_well_input_list[n].size(); b ++){
+            auto wname = std::get<0>(m_well_input_list[n][b]);
+            auto gname = std::get<1>(m_well_input_list[n][b]);
+            well_map[wname] = gname;
+        }
+    }
+
+    for (auto m : well_map){
+        auto gname = m.second;
+        auto wname = m.first;
+
+        for (size_t n = 0; n < m_node_list.size(); n++)
+            if (m_node_list[n]->name() == gname)
+                m_node_list[n]->add_well(wname);
+    }
+
+    for (auto n : input_step_vect){
+        for (size_t b = 0; b < m_node_input_list[n].size(); b ++){
+            auto node = std::get<0>(m_node_input_list[n][b]);
+            auto pressure = std::get<1>(m_node_input_list[n][b]);
+
+            for (size_t m = 0; m < m_node_list.size(); m ++ )
+                if (m_node_list[m]->name() == node)
+                    m_node_list[m]->set_fixed_pres(pressure);
+        }
+    }
+}
+
+void NetWork::print_network(int rstep){
+
+    std::cout << "\n\n";
+
+    time_t t = m_report_time_list[rstep - 1];
+
+    std::cout << "Report step : " << time_str(t) << "\n\n";
+
+    for (size_t n = 0; n < m_top_node_list.size(); n++){
+
+        m_top_node_list[n]->print(m_netw_str);
+        m_netw_str << "\n\n";
+    }
+
+    std::cout << "\n" << m_netw_str.str() << "";
+
+    std::cout << "\nFixed pressure nodes: \n\n";
+
+    for (size_t n = 0; n < m_node_list.size(); n++){
+        auto fixed_pres = m_node_list[n]->get_fixed_pres();
+
+        if (fixed_pres > -1.0){
+            std::cout << "  " << m_node_list[n]->name() << " = ";
+            std::cout << std::fixed << std::setprecision(2) << fixed_pres;
+        }
+    }
+
+    std::cout << "\n\n\n";
+}
+
+void NetWork::print_report_steps(){
+
+    if (!m_from_unrst)
+        std::cout << "\n\nStart date  " << time_str(m_start_date) << "\n";
+
+    std::cout << "\nList of all report steps \n\n";
+
+    for (size_t n = 0; n < m_report_time_list.size(); n ++)
+        std::cout << "Report step " << n + 1 << "  | " << time_str(m_report_time_list[n]) << "\n";
+}
+
+time_t NetWork::time_from_rec(const Opm::DeckRecord& rec){
+
+    std::map<std::string, int> mnd_map{{"JAN", 0}, {"FEB", 1}, {"MAR", 2}, {"APR", 3}, {"MAY", 4}, {"JUN", 5},
+                                       {"JUL", 6}, {"JLY", 6}, {"AUG", 7}, {"SEP", 8}, {"OCT", 9}, {"NOV", 10},
+                                       {"DEC", 11}};
+
+    auto day = rec.getItem(0).get<int>(0);
+    auto mndStr = rec.getItem(1).get<std::string>(0);
+    auto year = rec.getItem(2).get<int>(0);
+    auto time = rec.getItem(3).get<std::string>(0);
+
+    int sec_frac = 0;
+
+    auto p1 = time.find(".");
+
+    if (p1 != std::string::npos){
+        auto sec_frac_str = time.substr (p1 + 1);
+        sec_frac = std::stoi(sec_frac_str);
+        time = time.substr (0, p1);
+    }
+
+    if (sec_frac > 0){
+        std::cout << "\n!Error, fraction of section not supported \n";
+        exit(1);
+    }
+
+    p1 = time.find(":");
+
+    if (p1 == std::string::npos){
+        std::cout << "\n!Error, invalied format for time " << time << "\n";
+        exit(1);
+    }
+
+    auto p2 = time.find(":", p1 + 1);
+    if (p2 == std::string::npos){
+        std::cout << "\n!Error, Second invalied format for time " << time << "\n";
+        exit(1);
+    }
+
+    int h = std::stoi(time.substr (0, p1));
+    int min = std::stoi(time.substr (p1+1, p2-p1-1));
+    int sec = std::stoi(time.substr (p2+1));
+
+    int mnd = mnd_map.at(mndStr);
+
+    //std::tm date1 = {sec, min, h, day, mnd, year - 1900 };
+
+    std::tm date1 = {};
+
+    date1.tm_sec = sec;
+    date1.tm_min = min;
+    date1.tm_hour = h;
+
+    date1.tm_mday  = day;
+    date1.tm_mon = mnd;
+    date1.tm_year = year - 1900;
+
+    return std::mktime(&date1);
+}
+
+
+time_t NetWork::time_from_rst(const std::string& rstfile, int rstep){
+
+    Opm::EclIO::ERst rst1(rstfile);
+
+    auto inteh = rst1.getRestartData<int>("INTEHEAD", rstep);
+
+    int year = inteh[66];
+    int mnd = inteh[65];
+    int day = inteh[64];
+
+    int h = inteh[206];
+    int min = inteh[207];
+    int sec = static_cast<int>(inteh[410] / 1000000);
+
+    std::tm date1 = {};
+
+    date1.tm_sec = sec;
+    date1.tm_min = min;
+    date1.tm_hour = h;
+
+    date1.tm_mday  = day;
+    date1.tm_mon = mnd - 1;
+    date1.tm_year = year - 1900;
+
+    return std::mktime(&date1);
+}
+
+void NetWork::br_input_from_rst(const std::string& rstfile, std::vector<int> rstep_vect){
+
+    Opm::EclIO::ERst rst1(rstfile);
+
+    for (int& rstep : rstep_vect){
+
+        m_rst_time = time_from_rst(rstfile, rstep);
+        m_report_time_list.push_back(m_rst_time);
+
+        auto intehead = rst1.getRestartData<int>("INTEHEAD", rstep);
+
+        if (rst1.hasArray("ZNODE", rstep)){
+
+            std::vector<std::string> nodelist;
+
+            auto noactnod = intehead[129];  // Number of active/defined nodes in the network
+            auto nibran = intehead[133];    // number of entries per branch in the IBRAN array
+            auto noactbr = intehead[130];   // Number of active/defined branches in the network
+            auto nrnode = intehead[136];    // number of entries per node in the RNODE array
+
+            auto znode = rst1.getRestartData<std::string>("ZNODE", rstep);
+            auto ibran = rst1.getRestartData<int>("IBRAN", rstep);
+            auto rnode = rst1.getRestartData<double>("RNODE", rstep);
+
+
+            for (int n = 0; n < noactnod; n++)
+                nodelist.push_back(znode[2*n]);
+
+
+            for (int b = 0; b < noactbr; b++){
+                int ind = b*nibran;
+                std::string downtree = nodelist[ibran[ind] - 1];
+                std::string uptree = nodelist[ibran[ind + 1] - 1];
+                int vfp = ibran[ind + 2];
+
+                auto br = std::make_tuple(downtree, uptree, vfp);
+                m_bran_input_list.back().push_back(br);
+            }
+
+            for (int n = 0; n < noactnod; n++){
+                int ind = n*nrnode;
+                if (rnode[ind + 1] == 0.0){
+                    auto node = std::make_tuple(nodelist[n], rnode[ind + 2]);
+                    m_node_input_list.back().push_back(node);
+                }
+            }
+        }
+
+        std::vector<std::string> grouplist;
+
+        auto nzwelz = intehead[27];     // Number of 8-character words per well in ZWEL array
+        auto nswells = intehead[16];    // Number of wells
+        auto nzgrpz = intehead[39];     // Number of data elements per group in ZGRP array
+        auto ngmaxz = intehead[20];     // Maximum number of groups in field
+        auto niwelz = intehead[24];     // Number of data elements per well in IWEL array
+
+        auto zwel = rst1.getRestartData<std::string>("ZWEL", rstep);
+        auto iwel = rst1.getRestartData<int>("IWEL", rstep);
+        auto zgrp = rst1.getRestartData<std::string>("ZGRP", rstep);
+
+        for (int g = 0; g < ngmaxz; g ++)
+            grouplist.push_back(zgrp[g*nzgrpz]);
+
+        for (int n = 0; n < nswells; n++){
+            std::string wname = zwel[n*nzwelz];
+            int grp_ind = iwel[n*niwelz + 5] - 1;
+            std::string gname = grouplist[grp_ind];
+            auto well = std::make_tuple(wname, gname);
+            m_well_input_list.back().push_back(well);
+        }
+
+        m_node_input_list.push_back({});
+        m_bran_input_list.push_back({});
+        m_well_input_list.push_back({});
+    }
+}
+
+
+static void printHelp() {
+
+    std::cout << "\n This program visualizes a production network with termial output."
path: examples/networkgraph.cpp
user: login: tskille
body: fixed
created_at: 2025-04-10T18:50:50Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,909 @@
+/*
+  Copyright 2025 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 <iostream>
+#include <getopt.h>
+#include <sstream>
+
+#include <opm/io/eclipse/ERst.hpp>
+
+#include <opm/input/eclipse/Deck/Deck.hpp>
+
+#include <opm/input/eclipse/Parser/ErrorGuard.hpp>
+#include <opm/input/eclipse/Parser/InputErrorAction.hpp>
+#include <opm/input/eclipse/Parser/Parser.hpp>
+#include <opm/input/eclipse/Parser/ParseContext.hpp>
+
+
+class Node
+{
+public:
+    Node(const std::string& name);
+
+    std::string name() const { return m_name; }
+    void set_outlet(std::shared_ptr<Node> outlet) { m_outlet = outlet; };
+    std::shared_ptr<Node> get_outlet() { return m_outlet; };
+
+    void add_inlet_node(std::shared_ptr<Node> n);
+
+    void set_vfp(int vfp){m_vfp = vfp;};
+    int get_vfp(){ return m_vfp;};
+
+    void set_fixed_pres(double pres){m_fixed_pres = pres; };
+    double get_fixed_pres(){return m_fixed_pres; };
+
+    int get_xpos(){return m_xpos;};
+
+    void print(std::stringstream& netw_str);
+
+    bool detete_from_inlet_list(const std::string& name);
+
+    bool add_well(const std::string& name);
+
+    void reset_outlet(){m_outlet = nullptr;};
+
+private:
+    int m_vfp;
+    int m_xpos = -1;
+    double m_fixed_pres = -1.0;
+    int m_ind = -1;
+
+    std::string m_name;
+    std::shared_ptr<Node> m_outlet;
+    std::vector<std::shared_ptr<Node>> m_inlet_list;
+    std::vector<std::string> m_well_list;
+
+    std::shared_ptr<Node> next_branch();
+};
+
+
+Node::Node(const std::string& name)
+   : m_name(name)
+{
+    m_vfp = -1;
+}
+
+std::shared_ptr<Node> Node::next_branch(){
+
+    std::shared_ptr<Node> p1 = m_outlet;
+
+    while (p1 != nullptr){
+        for (size_t n = 0; n < p1->m_inlet_list.size(); n ++){
+            if (p1->m_inlet_list[n]->m_xpos == -1){
+                return p1;
+            }
+        }
+        p1 = p1->m_outlet;
+    }
+
+    return p1;
+}
+
+bool Node::detete_from_inlet_list(const std::string& name){
+
+    int ind = -1;
+
+    for (size_t n = 0; n < m_inlet_list.size(); n ++)
+        if (m_inlet_list[n]->name() == name)
+            ind = n;
+
+    if (ind < 0){
+        return false;
+    } else {
+        m_inlet_list.erase (m_inlet_list.begin() + ind);
+        return true;
+    }
+}
+
+bool Node::add_well(const std::string& name){
+   if (m_inlet_list.size() > 0)
+       return false;
+
+   m_well_list.push_back(name);
+
+   return true;
+}
+
+
+void Node::print(std::stringstream& netw_str){
+
+    netw_str.seekg(0, std::ios::end);
+    int init_length = netw_str.tellg();
+
+    int pos_lineshift = 0;
+
+    auto p = netw_str.str().find_last_of("\n");
+
+    if (p != std::string::npos)
+        pos_lineshift = p + 1;
+
+    if (m_outlet == nullptr){
+        netw_str << "  o (" << m_name << ")";
+        m_xpos = netw_str.str().find_first_of("o", init_length) - pos_lineshift;
+    } else if (m_vfp == 9999){
+        netw_str << " --- +(" << m_name << ")";
+        m_xpos = netw_str.str().find_first_of("+", init_length) - pos_lineshift;
+    } else {
+        netw_str << " --[" << m_vfp << "]-- +(" << m_name << ")";
+        m_xpos = netw_str.str().find_first_of("+", init_length) - pos_lineshift;
+    }
+
+
+    for (size_t m = 0; m < m_inlet_list.size(); m ++)
+        m_inlet_list[m]->print(netw_str);
+
+    if (m_inlet_list.size() == 0){
+        auto next_br = next_branch();
+
+        netw_str << " : ";
+
+        for (auto& well: m_well_list)
+            netw_str << " " << well;
+
+        if (next_br != nullptr){
+            std::string xpos_str(next_br->m_xpos, ' ');
+            netw_str << "\n" << xpos_str << "\\\n" << xpos_str;
+        }
+    }
+}
+
+void Node::add_inlet_node(std::shared_ptr<Node> node)
+{
+    for (size_t n = 0; n < m_inlet_list.size(); n ++ ){
+        if (m_inlet_list[n]->name() == node->name()){
+            m_inlet_list.erase (m_inlet_list.begin() + n);
+            break;
+        }
+    }
+
+    m_inlet_list.push_back(node);
+}
+
+
+class NetWork
+{
+public:
+    // upptree, downtree, vfp
+    using bran_input_type = std::tuple<std::string, std::string, int>;
+
+    // name and fixed pressure
+    using node_input_type = std::tuple<std::string, int>;
+
+    // well and group name
+    using well_input_type = std::tuple<std::string, std::string>;
+
+    NetWork(const std::string& filename);
path: examples/networkgraph.cpp
user: login: tskille
body: fixed
created_at: 2025-04-10T18:50:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,909 @@
+/*
+  Copyright 2025 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 <iostream>
+#include <getopt.h>
+#include <sstream>
+
+#include <opm/io/eclipse/ERst.hpp>
+
+#include <opm/input/eclipse/Deck/Deck.hpp>
+
+#include <opm/input/eclipse/Parser/ErrorGuard.hpp>
+#include <opm/input/eclipse/Parser/InputErrorAction.hpp>
+#include <opm/input/eclipse/Parser/Parser.hpp>
+#include <opm/input/eclipse/Parser/ParseContext.hpp>
+
+
+class Node
+{
+public:
+    Node(const std::string& name);
+
+    std::string name() const { return m_name; }
+    void set_outlet(std::shared_ptr<Node> outlet) { m_outlet = outlet; };
+    std::shared_ptr<Node> get_outlet() { return m_outlet; };
+
+    void add_inlet_node(std::shared_ptr<Node> n);
+
+    void set_vfp(int vfp){m_vfp = vfp;};
+    int get_vfp(){ return m_vfp;};
+
+    void set_fixed_pres(double pres){m_fixed_pres = pres; };
+    double get_fixed_pres(){return m_fixed_pres; };
+
+    int get_xpos(){return m_xpos;};
+
+    void print(std::stringstream& netw_str);
+
+    bool detete_from_inlet_list(const std::string& name);
+
+    bool add_well(const std::string& name);
+
+    void reset_outlet(){m_outlet = nullptr;};
+
+private:
+    int m_vfp;
+    int m_xpos = -1;
+    double m_fixed_pres = -1.0;
+    int m_ind = -1;
+
+    std::string m_name;
+    std::shared_ptr<Node> m_outlet;
+    std::vector<std::shared_ptr<Node>> m_inlet_list;
+    std::vector<std::string> m_well_list;
+
+    std::shared_ptr<Node> next_branch();
+};
+
+
+Node::Node(const std::string& name)
+   : m_name(name)
+{
+    m_vfp = -1;
+}
+
+std::shared_ptr<Node> Node::next_branch(){
+
+    std::shared_ptr<Node> p1 = m_outlet;
+
+    while (p1 != nullptr){
+        for (size_t n = 0; n < p1->m_inlet_list.size(); n ++){
+            if (p1->m_inlet_list[n]->m_xpos == -1){
+                return p1;
+            }
+        }
+        p1 = p1->m_outlet;
+    }
+
+    return p1;
+}
+
+bool Node::detete_from_inlet_list(const std::string& name){
+
+    int ind = -1;
+
+    for (size_t n = 0; n < m_inlet_list.size(); n ++)
+        if (m_inlet_list[n]->name() == name)
+            ind = n;
+
+    if (ind < 0){
+        return false;
+    } else {
+        m_inlet_list.erase (m_inlet_list.begin() + ind);
+        return true;
+    }
+}
+
+bool Node::add_well(const std::string& name){
+   if (m_inlet_list.size() > 0)
+       return false;
+
+   m_well_list.push_back(name);
+
+   return true;
+}
+
+
+void Node::print(std::stringstream& netw_str){
+
+    netw_str.seekg(0, std::ios::end);
+    int init_length = netw_str.tellg();
+
+    int pos_lineshift = 0;
+
+    auto p = netw_str.str().find_last_of("\n");
+
+    if (p != std::string::npos)
+        pos_lineshift = p + 1;
+
+    if (m_outlet == nullptr){
+        netw_str << "  o (" << m_name << ")";
+        m_xpos = netw_str.str().find_first_of("o", init_length) - pos_lineshift;
+    } else if (m_vfp == 9999){
+        netw_str << " --- +(" << m_name << ")";
+        m_xpos = netw_str.str().find_first_of("+", init_length) - pos_lineshift;
+    } else {
+        netw_str << " --[" << m_vfp << "]-- +(" << m_name << ")";
+        m_xpos = netw_str.str().find_first_of("+", init_length) - pos_lineshift;
+    }
+
+
+    for (size_t m = 0; m < m_inlet_list.size(); m ++)
+        m_inlet_list[m]->print(netw_str);
+
+    if (m_inlet_list.size() == 0){
+        auto next_br = next_branch();
+
+        netw_str << " : ";
+
+        for (auto& well: m_well_list)
+            netw_str << " " << well;
+
+        if (next_br != nullptr){
+            std::string xpos_str(next_br->m_xpos, ' ');
+            netw_str << "\n" << xpos_str << "\\\n" << xpos_str;
+        }
+    }
+}
+
+void Node::add_inlet_node(std::shared_ptr<Node> node)
+{
+    for (size_t n = 0; n < m_inlet_list.size(); n ++ ){
+        if (m_inlet_list[n]->name() == node->name()){
+            m_inlet_list.erase (m_inlet_list.begin() + n);
+            break;
+        }
+    }
+
+    m_inlet_list.push_back(node);
+}
+
+
+class NetWork
+{
+public:
+    // upptree, downtree, vfp
+    using bran_input_type = std::tuple<std::string, std::string, int>;
+
+    // name and fixed pressure
+    using node_input_type = std::tuple<std::string, int>;
+
+    // well and group name
+    using well_input_type = std::tuple<std::string, std::string>;
+
+    NetWork(const std::string& filename);
+
+    void print_report_steps();
+    int number_report_steps() {return m_report_time_list.size();}
+
+    void build_network(int rstep);
+    void print_network(int rstep);
+
+private:
+    time_t time_from_rec(const Opm::DeckRecord& rec);
+    time_t time_from_rst(const std::string& rstfile, int rstep);
+
+    bool node_exist(const std::string& name);
+
+    void add_node(const std::string& name);
+    void add_branch(const std::string& downtree, const std::string& uptree, int vfp);
+    void delete_branch(const std::string& downtree, const std::string& uptree);
+
+    void br_input_from_rst(const std::string& rstfile, std::vector<int> rstep_vect);
+
+    void print_network_input();
+    void parse_data_deck(const std::filesystem::path& inputFileName);
+    void parse_unrst(const std::filesystem::path& inputFileName);
+
+    std::stringstream m_netw_str;
+
+    time_t m_start_date;
+    time_t m_rst_time;
+
+    bool m_from_unrst = false;
+
+    std::vector<time_t> m_report_time_list;
+    std::vector<std::vector<bran_input_type>> m_bran_input_list;
+    std::vector<std::vector<node_input_type>> m_node_input_list;
+    std::vector<std::vector<well_input_type>> m_well_input_list;
+
+    std::string time_str(time_t t1);
+
+    std::vector<std::shared_ptr<Node>> m_node_list;
+    std::vector<std::shared_ptr<Node>> m_top_node_list;
+};
+
+
+NetWork::NetWork(const std::string& filename)
+{
+    std::filesystem::path inputFileName { filename } ;
+
+    if (inputFileName.extension()== ".DATA"){
+
+        parse_data_deck(inputFileName);
+
+    } else if (inputFileName.extension()== ".UNRST"){
+
+        parse_unrst(inputFileName);
+        m_from_unrst = true;
+
+    } else {
+        std::cout << "\n!Error, unsupported file type " << filename << "\n\n";
+        exit(1);
+    }
+}
+
+
+void NetWork::parse_data_deck(const std::filesystem::path& inputFileName){
+
+    Opm::ParseContext parseContext;
+    parseContext.update(Opm::ParseContext::PARSE_UNKNOWN_KEYWORD , Opm::InputErrorAction::IGNORE );
+    parseContext.update(Opm::ParseContext::PARSE_RANDOM_TEXT , Opm::InputErrorAction::IGNORE );
+    parseContext.update(Opm::ParseContext::PARSE_EXTRA_RECORDS , Opm::InputErrorAction::IGNORE );
+    parseContext.update(Opm::ParseContext::PARSE_RANDOM_SLASH , Opm::InputErrorAction::IGNORE );
+
+    std::vector<Opm::Ecl::SectionType> sections = {Opm::Ecl::RUNSPEC, Opm::Ecl::SOLUTION, Opm::Ecl::SCHEDULE};
+
+    Opm::Parser parser;
+
+    Opm::Deck deck_schecule;
+
+    try {
+        deck_schecule = parser.parseFile(inputFileName, parseContext, sections);
+
+    } catch (const std::exception& e){
+
+        std::cout << "\n!Error parsing data deck " << inputFileName << "\n\n";
+        std::cout << e.what() << "\n\n";
+
+        exit(1);
+    }
+
+    bool restart = false;
+    bool skiprest = false;
+
+    time_t last_time;
+
+    auto network_keyw =  deck_schecule["NETWORK"];
+
+    if (network_keyw.size() == 0){
+        std::cout << "\n > !Error, data deck "  << inputFileName << " doesn't include a production network \n\n";
+        exit(1);
+    }
+
+    for (auto keyw : deck_schecule){
+
+        if (keyw.name() == "START"){
+            m_node_input_list.push_back({});
+            m_bran_input_list.push_back({});
+            m_well_input_list.push_back({});
+            m_start_date = time_from_rec(keyw[0]);
+
+            last_time = m_start_date;
+
+        } else if (keyw.name() == "TSTEP"){
+
+            for (size_t n = 0; n < keyw[0].getItem(0).data_size(); n++){
+                auto dt = keyw[0].getItem(0).get<double>(n);
+                last_time = last_time + static_cast<int>(dt * 24.0 * 3600.0);
+
+                if (!skiprest){
+                    m_report_time_list.push_back(last_time);
+                    m_node_input_list.push_back({});
+                    m_bran_input_list.push_back({});
+                    m_well_input_list.push_back({});
+                }
+
+                if ((skiprest) && (last_time >= m_rst_time))
+                    skiprest = false;
+            }
+
+        } else if (keyw.name() == "DATES"){
+            for (auto& rec : keyw ){
+                last_time = time_from_rec(rec);
+
+                if (m_report_time_list.size() == 0){
+                    if ((last_time <=  m_start_date) && (!skiprest)) {
+                        std::cout << "\n!Error, next report step '" << time_str(last_time) << "' has already passed \n\n";
+                        exit(1);
+                    }
+                } else {
+                    if ((last_time <=  m_report_time_list.back()) && (!skiprest)) {
+                        std::cout << "\n!Error, next report step '" << time_str(last_time) << "' has already passed \n\n";
+                        exit(1);
+                    }
+                }
+
+                if (!skiprest){
+                    m_report_time_list.push_back(last_time);
+                    m_node_input_list.push_back({});
+                    m_bran_input_list.push_back({});
+                    m_well_input_list.push_back({});
+                }
+
+                if ((skiprest) && (last_time >= m_rst_time))
+                    skiprest = false;
+            }
+
+        } else if (keyw.name() == "RESTART"){
+
+            auto rst_file = keyw[0].getItem(0).get<std::string>(0) + ".UNRST";
+            auto rst_rstep = keyw[0].getItem(1).get<int>(0);
+
+            br_input_from_rst(rst_file, {rst_rstep} );
+
+            restart = true;
+
+        } else if (keyw.name() == "SKIPREST"){
+
+            if (restart)
+                skiprest = true;
+
+        } else if ((keyw.name() == "BRANPROP") && (!skiprest)){
+            for (auto& rec : keyw ){
+
+                auto downtree = rec.getItem(0).get<std::string>(0);
+                auto uptree = rec.getItem(1).get<std::string>(0);
+                auto vfp = rec.getItem(2).get<int>(0);
+
+                auto br = std::make_tuple(downtree, uptree, vfp);
+
+                m_bran_input_list.back().push_back(br);
+            }
+
+        } else if ((keyw.name() == "NODEPROP") && (!skiprest)){
+            for (auto& rec : keyw ){
+                if (rec.getItem(1).hasValue(0)){
+                    auto node_name = rec.getItem(0).get<std::string>(0);
+                    auto node_pres = rec.getItem(1).get<double>(0);
+                    auto node = std::make_tuple(node_name, node_pres);
+                    m_node_input_list.back().push_back(node);
+                }
+            }
+
+        } else if ((keyw.name() == "WELSPECS") && (!skiprest)){
+            for (auto& rec : keyw ){
+                auto wname = rec.getItem(0).get<std::string>(0);
+                auto gname = rec.getItem(1).get<std::string>(0);
+
+                auto well = std::make_tuple(wname, gname);
+
+                m_well_input_list.back().push_back(well);
+            }
+        }
+    }
+}
+
+void NetWork::parse_unrst(const std::filesystem::path& inputFileName){
+
+    Opm::EclIO::ERst rst1(inputFileName);
+
+    auto all_reports = rst1.listOfReportStepNumbers();
+
+    std::vector<int> rstep_vect;
+
+    for (auto r : all_reports)
+        if (r > 0)
+            rstep_vect.push_back(r);
+
+    m_node_input_list.push_back({});
+    m_bran_input_list.push_back({});
+    m_well_input_list.push_back({});
+
+    br_input_from_rst(inputFileName, rstep_vect);
+}
+
+std::string NetWork::time_str(time_t t1){
+
+    std::vector<std::string> mndStr{"JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"};
+
+    std::tm* date = std::localtime(&t1);
+
+    std::stringstream date_str;
+
+    // ignoring daylight saving time
+
+    int hr = date->tm_hour;
+    int day = date->tm_mday;
+
+    if (date->tm_isdst == 1){
+        hr = hr - 1;
+        if (hr < 0){
+            hr = hr + 24;
+            day = day - 1;
+        }
+    }
+
+    date_str << std::setw(2) << std::setfill('0') << day;
+    date_str <<  " '" << mndStr[date->tm_mon] << "' " << date->tm_year + 1900;
+    date_str << " " << std::setw(2) << std::setfill('0') << hr;
+    date_str << ":" << std::setw(2) << std::setfill('0') << date->tm_min;
+    date_str << ":" << std::setw(2) << std::setfill('0') << date->tm_sec;
+
+    return date_str.str();
+}
+
+
+void NetWork::print_network_input(){
+
+    std::cout << "m_report_time_list: " << m_report_time_list.size() << "\n";
+    std::cout << "m_well_input_list : " << m_well_input_list.size() << "\n";
+
+    std::cout << "\nm_bran_input_list: " <<  m_bran_input_list.size() << "\n\n";
+
+    for (size_t r = 0; r <  m_bran_input_list.size(); r++){
+        for (size_t n = 0; n <  m_bran_input_list[r].size(); n++){
+            auto downtree = std::get<0>(m_bran_input_list[r][n]);
+            auto uptree = std::get<1>(m_bran_input_list[r][n]);
+            auto vfp = std::get<2>(m_bran_input_list[r][n]);
+
+            std::cout << "r= " << r << ", n= " << n << " " << downtree;
+            std::cout << " " << uptree << " " << vfp << "\n";
+        }
+    }
+
+    std::cout << "\n\nm_node_input_list: " <<  m_node_input_list.size() << "\n\n";
+
+    for (size_t r = 0; r <  m_node_input_list.size(); r++){
+        for (size_t n = 0; n <  m_node_input_list[r].size(); n++){
+            auto node = std::get<0>(m_node_input_list[r][n]);
+            auto pres = std::get<1>(m_node_input_list[r][n]);
+
+            std::cout << "r= " << r << ", n= " << n << " " << node;
+            std::cout << " " << pres << "\n";
+        }
+    }
+
+    std::cout << "\n\n";
+}
+
+
+bool NetWork::node_exist(const std::string& name)
+{
+   for (size_t n = 0; n < m_node_list.size(); n ++)
+       if (name == m_node_list[n]->name())
+           return true;
+
+   return false;
+}
+
+
+void NetWork::add_node(const std::string& name)
+{
+    if (!this->node_exist(name))
+        m_node_list.push_back(std::make_shared<Node>(name));
+    else {
+        std::cout << "in function add_node: Node " << name << " already exists \n\n";
+        exit(1);
+    }
+}
+
+
+void  NetWork::add_branch(const std::string& downtree, const std::string& uptree, int vfp)
+{
+    if (vfp == 0){
+        std::cout << "\n!Error, vfp = 0, use function remove_branch to remove a branch \n\n";
+        exit(1);
+    }
+
+    std::shared_ptr<Node> pUptree;
+    std::shared_ptr<Node> pDowntree;
+
+    // handle uptree node
+
+    for (size_t n = 0; n < m_node_list.size(); n ++)
+        if (m_node_list[n]->name() == uptree)
+            pUptree = m_node_list[n];
+
+    if (pUptree == nullptr){
+        add_node(uptree);
+        pUptree = m_node_list.back();
+    }
+
+    // handle down tree node
+
+    for (size_t n = 0; n < m_node_list.size(); n ++)
+        if (m_node_list[n]->name() == downtree)
+            pDowntree = m_node_list[n];
+
+    if (pDowntree == nullptr){
+        add_node(downtree);
+        pDowntree = m_node_list.back();
+    }
+
+    pDowntree->set_outlet(pUptree);
+    pDowntree->set_vfp(vfp);
+
+
+    pUptree->add_inlet_node(pDowntree);
+}
+
+void  NetWork::delete_branch(const std::string& downtree, const std::string& uptree)
+{
+    std::shared_ptr<Node> pUptree;
+    std::shared_ptr<Node> pDowntree;
+
+    for (size_t n = 0; n < m_node_list.size(); n ++)
+        if (m_node_list[n]->name() == uptree)
+            pUptree = m_node_list[n];
+
+    for (size_t n = 0; n < m_node_list.size(); n ++)
+        if (m_node_list[n]->name() == downtree)
+            pDowntree = m_node_list[n];
+
+    if ((pUptree == nullptr) || (pDowntree == nullptr)) {
+        std::cout << "\n!Error, pointer to downtree and/or uptree not found \n\n";
+        exit(1);
+    }
+
+    if (pUptree->detete_from_inlet_list(downtree) == false)  {
+        std::cout << "\n!Error, problem with deleteing branch, needs to be checked  \n\n";
+        exit(1);
+    }
+
+    pDowntree->reset_outlet();
+
+    bool found_in_list = false;
+
+    for (size_t n = 0; n < m_top_node_list.size(); n++)
+        if (m_top_node_list[n]->name() == downtree)
+            found_in_list = true;
+
+    if (!found_in_list)
+        m_top_node_list.push_back(pDowntree);
+}
+
+
+void  NetWork::build_network(int rstep)
+{
+    std::vector<int> input_step_vect;
+
+    // input from restart file -> each report step describes a complete state of network
+    // input from data deck -> network build from all steps up to report step (branprop increments)
+
+    if (m_from_unrst)
+        input_step_vect.push_back(rstep - 1);
+    else
+        for (int n = 0; n < rstep; n++)
+            input_step_vect.push_back(n);
+
+    for (auto& n : input_step_vect){
+
+        for (size_t b = 0; b < m_bran_input_list[n].size(); b ++){
+            auto downtree = std::get<0>(m_bran_input_list[n][b]);
+            auto uptree = std::get<1>(m_bran_input_list[n][b]);
+            auto vfp = std::get<2>(m_bran_input_list[n][b]);
+
+            if (vfp == 0)
+                delete_branch(downtree, uptree);
+            else
+                add_branch(downtree, uptree, vfp);
+        }
+    }
+
+    m_top_node_list.clear();
+
+    for (size_t n = 0; n < m_node_list.size(); n++)
+        if (m_node_list[n]->get_outlet() == nullptr)
+            m_top_node_list.push_back(m_node_list[n]);
+
+    std::map<std::string, std::string> well_map;
+
+    for (auto n : input_step_vect){
+        for (size_t b = 0; b < m_well_input_list[n].size(); b ++){
+            auto wname = std::get<0>(m_well_input_list[n][b]);
+            auto gname = std::get<1>(m_well_input_list[n][b]);
+            well_map[wname] = gname;
+        }
+    }
+
+    for (auto m : well_map){
+        auto gname = m.second;
+        auto wname = m.first;
+
+        for (size_t n = 0; n < m_node_list.size(); n++)
+            if (m_node_list[n]->name() == gname)
+                m_node_list[n]->add_well(wname);
+    }
+
+    for (auto n : input_step_vect){
+        for (size_t b = 0; b < m_node_input_list[n].size(); b ++){
+            auto node = std::get<0>(m_node_input_list[n][b]);
+            auto pressure = std::get<1>(m_node_input_list[n][b]);
+
+            for (size_t m = 0; m < m_node_list.size(); m ++ )
+                if (m_node_list[m]->name() == node)
+                    m_node_list[m]->set_fixed_pres(pressure);
+        }
+    }
+}
+
+void NetWork::print_network(int rstep){
+
+    std::cout << "\n\n";
+
+    time_t t = m_report_time_list[rstep - 1];
+
+    std::cout << "Report step : " << time_str(t) << "\n\n";
+
+    for (size_t n = 0; n < m_top_node_list.size(); n++){
+
+        m_top_node_list[n]->print(m_netw_str);
+        m_netw_str << "\n\n";
+    }
+
+    std::cout << "\n" << m_netw_str.str() << "";
+
+    std::cout << "\nFixed pressure nodes: \n\n";
+
+    for (size_t n = 0; n < m_node_list.size(); n++){
+        auto fixed_pres = m_node_list[n]->get_fixed_pres();
+
+        if (fixed_pres > -1.0){
+            std::cout << "  " << m_node_list[n]->name() << " = ";
+            std::cout << std::fixed << std::setprecision(2) << fixed_pres;
+        }
+    }
+
+    std::cout << "\n\n\n";
+}
+
+void NetWork::print_report_steps(){
+
+    if (!m_from_unrst)
+        std::cout << "\n\nStart date  " << time_str(m_start_date) << "\n";
+
+    std::cout << "\nList of all report steps \n\n";
+
+    for (size_t n = 0; n < m_report_time_list.size(); n ++)
+        std::cout << "Report step " << n + 1 << "  | " << time_str(m_report_time_list[n]) << "\n";
+}
+
+time_t NetWork::time_from_rec(const Opm::DeckRecord& rec){
+
+    std::map<std::string, int> mnd_map{{"JAN", 0}, {"FEB", 1}, {"MAR", 2}, {"APR", 3}, {"MAY", 4}, {"JUN", 5},
+                                       {"JUL", 6}, {"JLY", 6}, {"AUG", 7}, {"SEP", 8}, {"OCT", 9}, {"NOV", 10},
+                                       {"DEC", 11}};
+
+    auto day = rec.getItem(0).get<int>(0);
+    auto mndStr = rec.getItem(1).get<std::string>(0);
+    auto year = rec.getItem(2).get<int>(0);
+    auto time = rec.getItem(3).get<std::string>(0);
+
+    int sec_frac = 0;
+
+    auto p1 = time.find(".");
+
+    if (p1 != std::string::npos){
+        auto sec_frac_str = time.substr (p1 + 1);
+        sec_frac = std::stoi(sec_frac_str);
+        time = time.substr (0, p1);
+    }
+
+    if (sec_frac > 0){
+        std::cout << "\n!Error, fraction of section not supported \n";
+        exit(1);
+    }
+
+    p1 = time.find(":");
+
+    if (p1 == std::string::npos){
+        std::cout << "\n!Error, invalied format for time " << time << "\n";
+        exit(1);
+    }
+
+    auto p2 = time.find(":", p1 + 1);
+    if (p2 == std::string::npos){
+        std::cout << "\n!Error, Second invalied format for time " << time << "\n";
+        exit(1);
+    }
+
+    int h = std::stoi(time.substr (0, p1));
+    int min = std::stoi(time.substr (p1+1, p2-p1-1));
+    int sec = std::stoi(time.substr (p2+1));
+
+    int mnd = mnd_map.at(mndStr);
+
+    //std::tm date1 = {sec, min, h, day, mnd, year - 1900 };
+
+    std::tm date1 = {};
+
+    date1.tm_sec = sec;
+    date1.tm_min = min;
+    date1.tm_hour = h;
+
+    date1.tm_mday  = day;
+    date1.tm_mon = mnd;
+    date1.tm_year = year - 1900;
+
+    return std::mktime(&date1);
+}
+
+
+time_t NetWork::time_from_rst(const std::string& rstfile, int rstep){
+
+    Opm::EclIO::ERst rst1(rstfile);
+
+    auto inteh = rst1.getRestartData<int>("INTEHEAD", rstep);
+
+    int year = inteh[66];
+    int mnd = inteh[65];
+    int day = inteh[64];
+
+    int h = inteh[206];
+    int min = inteh[207];
+    int sec = static_cast<int>(inteh[410] / 1000000);
+
+    std::tm date1 = {};
+
+    date1.tm_sec = sec;
+    date1.tm_min = min;
+    date1.tm_hour = h;
+
+    date1.tm_mday  = day;
+    date1.tm_mon = mnd - 1;
+    date1.tm_year = year - 1900;
+
+    return std::mktime(&date1);
+}
+
+void NetWork::br_input_from_rst(const std::string& rstfile, std::vector<int> rstep_vect){
+
+    Opm::EclIO::ERst rst1(rstfile);
+
+    for (int& rstep : rstep_vect){
+
+        m_rst_time = time_from_rst(rstfile, rstep);
+        m_report_time_list.push_back(m_rst_time);
+
+        auto intehead = rst1.getRestartData<int>("INTEHEAD", rstep);
+
+        if (rst1.hasArray("ZNODE", rstep)){
+
+            std::vector<std::string> nodelist;
+
+            auto noactnod = intehead[129];  // Number of active/defined nodes in the network
+            auto nibran = intehead[133];    // number of entries per branch in the IBRAN array
+            auto noactbr = intehead[130];   // Number of active/defined branches in the network
+            auto nrnode = intehead[136];    // number of entries per node in the RNODE array
+
+            auto znode = rst1.getRestartData<std::string>("ZNODE", rstep);
+            auto ibran = rst1.getRestartData<int>("IBRAN", rstep);
+            auto rnode = rst1.getRestartData<double>("RNODE", rstep);
+
+
+            for (int n = 0; n < noactnod; n++)
+                nodelist.push_back(znode[2*n]);
+
+
+            for (int b = 0; b < noactbr; b++){
+                int ind = b*nibran;
+                std::string downtree = nodelist[ibran[ind] - 1];
+                std::string uptree = nodelist[ibran[ind + 1] - 1];
+                int vfp = ibran[ind + 2];
+
+                auto br = std::make_tuple(downtree, uptree, vfp);
+                m_bran_input_list.back().push_back(br);
+            }
+
+            for (int n = 0; n < noactnod; n++){
+                int ind = n*nrnode;
+                if (rnode[ind + 1] == 0.0){
+                    auto node = std::make_tuple(nodelist[n], rnode[ind + 2]);
+                    m_node_input_list.back().push_back(node);
+                }
+            }
+        }
+
+        std::vector<std::string> grouplist;
+
+        auto nzwelz = intehead[27];     // Number of 8-character words per well in ZWEL array
+        auto nswells = intehead[16];    // Number of wells
+        auto nzgrpz = intehead[39];     // Number of data elements per group in ZGRP array
+        auto ngmaxz = intehead[20];     // Maximum number of groups in field
+        auto niwelz = intehead[24];     // Number of data elements per well in IWEL array
+
+        auto zwel = rst1.getRestartData<std::string>("ZWEL", rstep);
+        auto iwel = rst1.getRestartData<int>("IWEL", rstep);
+        auto zgrp = rst1.getRestartData<std::string>("ZGRP", rstep);
+
+        for (int g = 0; g < ngmaxz; g ++)
+            grouplist.push_back(zgrp[g*nzgrpz]);
+
+        for (int n = 0; n < nswells; n++){
+            std::string wname = zwel[n*nzwelz];
+            int grp_ind = iwel[n*niwelz + 5] - 1;
+            std::string gname = grouplist[grp_ind];
+            auto well = std::make_tuple(wname, gname);
+            m_well_input_list.back().push_back(well);
+        }
+
+        m_node_input_list.push_back({});
+        m_bran_input_list.push_back({});
+        m_well_input_list.push_back({});
+    }
+}
+
+
+static void printHelp() {
+
+    std::cout << "\n This program visualizes a production network with termial output."
+              << " Input to this program should be a valied data deck (.DATA) \n or a unified"
+              << " restart file (.UNRST).\n\n The program takes these options"
+              << " (which must be given before the arguments):\n\n"
+              << " -l lists all available report steps and exit.\n"
+              << " -r selets report step to be visualized. Default is the last report step \n"
+              << " -h Print help and exit.\n\n";
+
+}
+
+
+int main(int argc, char **argv) {
+
+    int c                          = 0;
+    bool list_report_steps         = false;
+    int rstep                      = -1;
+
+    while ((c = getopt(argc, argv, "lr:h")) != -1) {
+        switch (c) {
+        case 'l':
+            list_report_steps = true;
+            break;
+        case 'h':
+            printHelp();
+            return 0;
+        case 'r':
+            rstep = atoi(optarg);
+            break;
+        default:
+            return EXIT_FAILURE;
+        }
+    }
+
+    int argOffset = optind;
+
+    NetWork netw(argv[argOffset]);
+
+    if (list_report_steps) {
+        netw.print_report_steps();
+        std::cout << "\n";
+
+        return 0;
+    }
+
+    if (rstep == -1){
+        rstep = netw.number_report_steps();
+    } else {
+
+        if ((rstep < 1) || (rstep > netw.number_report_steps())){
+            std::cout << "\n!Error, invalid report step " << rstep;
+            std::cout << " should be > 0 and less than " << netw.number_report_steps() << "\n";
+            std::cout << "        use option -l to list all report steps. "<< "\n\n";
+            exit(1);
+        }
+    }
+
+    netw.build_network(rstep);
+    netw.print_network(rstep);
+
+    std::cout << "\n\n";
+
+    return 0;
path: examples/networkgraph.cpp
user: login: tskille
body: fixed
created_at: 2025-04-10T18:51:01Z
author_association: CONTRIBUTOR

PR comments:
user: login: GitPaean
created_at: 2025-04-08T13:10:40Z
body: jenkins build this please
author_association: MEMBER
user: login: GitPaean
created_at: 2025-04-08T13:16:07Z
body: I think it is helpful,  for example with the case `NETWORK-01-MULTIROOT.DATA`, 

```
Report step : 05 'JAN' 2019 00:00:00


  o (FIELD) --- +(GRPA) --- +(PRODA) :  PROD1 PROD3
                \
                 --- +(GRPB) --[2]-- +(PRODB) :  PROD2


Fixed pressure nodes: 

  GRPB = 82.00  FIELD = 80.00
```
author_association: MEMBER
user: login: GitPaean
created_at: 2025-04-08T19:56:10Z
body: There are some warnings remain to be addressed. 

https://ci.opm-project.org/job/opm-common-PR-builder/8298/gcc/folder.1937579081/

author_association: MEMBER
user: login: GitPaean
created_at: 2025-04-09T20:42:36Z
body: jenkins build this please
author_association: MEMBER
user: login: tskille
created_at: 2025-04-10T18:55:47Z
body: Thanks @atgeirr . All issues addressed, except for the clang formatting.  I don't know how to do this. May be you can help me with this ?
author_association: CONTRIBUTOR
user: login: GitPaean
created_at: 2025-04-11T09:37:52Z
body: > Thanks @atgeirr . All issues addressed, except for the clang formatting. I don't know how to do this. May be you can help me with this ?

Hei, you can use clang-format as follows, it was also my first time to use it, assuming you are in the opm-common folder already.

```
sudo apt install clang-format  // this is for ubuntu, for other Linux distros, you can do different commands to install clang-format.

cd examples

clang-format -i networkgraph.cpp 
```

Then you commit and push the formatted networkgraph.cpp . 

author_association: MEMBER
user: login: GitPaean
created_at: 2025-04-11T11:25:35Z
body: jenkins build this please
author_association: MEMBER
user: login: GitPaean
created_at: 2025-04-11T12:19:04Z
body: Thanks for the contribution. I think it is very helpful to get a quick overview regarding the network setup on terminal.  I am getting it in now. 

At also, inspired by this PR, and also the welgraph program, I am thinking to add an application to plot the network setup as a pdf file at a later stage, in case someone want to print out for record. 
author_association: MEMBER