opm-simulators
BlackoilWellModelNetworkPressureComputation.hpp
1 /*
2  Copyright 2020-2026 Equinor ASA
3 
4  This file is part of the Open Porous Media project (OPM).
5 
6  OPM is free software: you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation, either version 3 of the License, or
9  (at your option) any later version.
10 
11  OPM is distributed in the hope that it will be useful,
12  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  GNU General Public License for more details.
15 
16  You should have received a copy of the GNU General Public License
17  along with OPM. If not, see <http://www.gnu.org/licenses/>.
18 */
19 
20 #ifndef OPM_BLACKOIL_WELL_MODEL_NETWORK_PRESSURE_COMPUTATION_HPP
21 #define OPM_BLACKOIL_WELL_MODEL_NETWORK_PRESSURE_COMPUTATION_HPP
22 
23 #include <opm/common/TimingMacros.hpp>
24 
25 #include <opm/input/eclipse/Schedule/Network/ExtNetwork.hpp>
26 #include <opm/input/eclipse/Schedule/Schedule.hpp>
27 #include <opm/input/eclipse/Schedule/VFPProdTable.hpp>
28 
29 #include <opm/output/data/Groups.hpp>
30 
31 #include <opm/simulators/wells/BlackoilWellModelGeneric.hpp>
32 #include <opm/simulators/wells/VFPInjProperties.hpp>
33 #include <opm/simulators/wells/VFPProdProperties.hpp>
34 
35 #include <algorithm>
36 #include <cassert>
37 #include <map>
38 #include <ranges>
39 #include <set>
40 #include <stack>
41 #include <string>
42 #include <vector>
43 
44 namespace Opm {
45 
48 template<typename Scalar, typename IndexTraits, typename VfpProperties>
50 
51 // Production specialization.
52 template<typename Scalar, typename IndexTraits>
53 struct NetworkVfpPressureCalculator<Scalar, IndexTraits, VFPProdProperties<Scalar>>
54 {
55  static void prepareRates(std::vector<Scalar>& rates)
56  {
57  // Network rates are positive, while production VFP expects negative rates.
58  std::ranges::transform(rates, rates.begin(), [](const auto r) { return -r; });
59  }
60 
61  template <class GroupState>
62  static const std::vector<Scalar>
63  leafNodeRate(const GroupState& group_state, const std::string& node)
64  {
65  return group_state.network_leaf_node_production_rates(node);
66  }
67 
68  template<typename Branch>
69  static Scalar compute(const VFPProdProperties<Scalar>& vfp_props,
70  const int table_id,
71  const std::vector<Scalar>& rates,
72  const Scalar up_press,
73  const Branch& upbranch,
74  const UnitSystem& unit_system)
75  {
76  // NB! ALQ in extended network is never implicitly the gas lift rate (GRAT), i.e., the
77  // gas lift rates only enters the network pressure calculations through the rates
78  // (e.g., in GOR calculations) unless a branch ALQ is set in BRANPROP.
79  const auto alq_type = vfp_props.getTable(table_id).getALQType();
80  const auto dimension = VFPProdTable::ALQDimension(alq_type, unit_system);
81  const Scalar alq = upbranch.alq_value(dimension).value_or(0.0);
82 
83  return vfp_props.bhp(table_id,
84  rates[IndexTraits::waterPhaseIdx],
85  rates[IndexTraits::oilPhaseIdx],
86  rates[IndexTraits::gasPhaseIdx],
87  up_press,
88  alq,
89  0.0, // explicit_wfr
90  0.0, // explicit_gfr
91  false); // use_expvfp we dont support explicit lookup
92  }
93 };
94 
95 // Injection specialization.
96 template<typename Scalar, typename IndexTraits>
97 struct NetworkVfpPressureCalculator<Scalar, IndexTraits, VFPInjProperties<Scalar>>
98 {
99  static void prepareRates(std::vector<Scalar>&)
100  {
101  }
102 
103  template <class GroupState>
104  static const std::vector<Scalar>
105  leafNodeRate(const GroupState& group_state, const std::string& node)
106  {
107  return group_state.network_leaf_node_injection_rates(node);
108  }
109 
110  template<typename Branch>
111  static Scalar compute(const VFPInjProperties<Scalar>& vfp_props,
112  const int table_id,
113  const std::vector<Scalar>& rates,
114  const Scalar up_press,
115  const Branch&,
116  const UnitSystem&)
117  {
118  return vfp_props.bhp(table_id,
119  rates[IndexTraits::waterPhaseIdx],
120  rates[IndexTraits::oilPhaseIdx],
121  rates[IndexTraits::gasPhaseIdx],
122  up_press);
123  }
124 };
125 
129 template<typename GenericWellModel, typename VfpProperties, typename Communication = Parallel::Communication>
131 {
132 public:
133  NetworkPressureComputation(const GenericWellModel& well_model,
134  const Network::ExtNetwork& network,
135  const VfpProperties& vfp_props,
136  const UnitSystem& unit_system,
137  const int report_step_idx,
138  const Communication& comm)
139  : well_model_(well_model)
140  , network_(network)
141  , vfp_props_(vfp_props)
142  , unit_system_(unit_system)
143  , report_step_idx_(report_step_idx)
144  , comm_(comm)
145  {
146  }
147 
148  using Scalar = typename GenericWellModel::Scalar;
149  using IndexTraits = GenericWellModel::IndexTraits;
150  std::pair<std::map<std::string, Scalar>, std::map<std::string, data::BranchData>> run()
151  {
152  const auto roots = network_.roots();
153  for (const auto& root : roots) {
154  // Fixed pressure nodes of the network are the roots of trees.
155  // Leaf nodes must correspond to groups in the group structure.
156  // Let us first find all leaf nodes of the network. We also
157  // create a vector of all nodes, ordered so that a child is
158  // always after its parent.
159  const auto [root_to_child_nodes, leaf_nodes] = collectTreeNodes(root.get().name());
160 
161  // Starting with the leaf nodes of the network, get the flow rates
162  // from the corresponding groups.
163  auto node_inflows = initializeLeafInflows(leaf_nodes);
164 
165  // Accumulate flow rates in the network, towards the roots.
166  // Note that a root (i.e. fixed pressure node) can still be
167  // contributing flow towards other nodes in the network, i.e.
168  // a node can be the root of a subtree.
169  accumulateInflows(root_to_child_nodes, node_inflows);
170 
171  // Going the other way (from roots to leafs), calculate the pressure
172  // at each node using VFP tables and rates.
173  computeNodePressures(root_to_child_nodes, node_inflows);
174  }
175 
176  return {node_pressures_, branch_data_};
177  }
178 
179 private:
180  std::pair<std::vector<std::string>, std::set<std::string>>
181  collectTreeNodes(const std::string& root) const
182  {
183  std::stack<std::string> children;
184  std::set<std::string> leaf_nodes;
185  std::vector<std::string> root_to_child_nodes;
186  children.push(root);
187  while (!children.empty()) {
188  const auto node = children.top();
189  children.pop();
190  root_to_child_nodes.push_back(node);
191  auto branches = network_.downtree_branches(node);
192  if (branches.empty()) {
193  leaf_nodes.insert(node);
194  }
195  for (const auto& branch : branches) {
196  children.push(branch.downtree_node());
197  }
198  }
199 
200  assert(children.empty());
201  return {root_to_child_nodes, leaf_nodes};
202  }
203 
204  std::map<std::string, std::vector<Scalar>>
205  initializeLeafInflows(const std::set<std::string>& leaf_nodes) const
206  {
207  std::map<std::string, std::vector<Scalar>> node_inflows;
208  const std::vector<Scalar> zero_rates(3, 0.0);
209 
210  for (const auto& node : leaf_nodes) {
211  // Guard against empty leaf nodes (may not be present in GRUPTREE)
212  if (!well_model_.groupStateHelper().groupState().has_production_rates(node)) {
213  node_inflows[node] = zero_rates;
214  continue;
215  }
216 
218  node_inflows[node] = Calc::leafNodeRate(well_model_.groupStateHelper().groupState(), node);
219  if (network_.node(node).add_gas_lift_gas()) {
220  addGasLiftGas(node, node_inflows[node]);
221  }
222  }
223 
224  return node_inflows;
225  }
226 
227  void addGasLiftGas(const std::string& node,
228  std::vector<Scalar>& rates) const
229  {
230  const auto& group = well_model_.schedule().getGroup(node, report_step_idx_);
231  const auto& well_state = well_model_.groupStateHelper().wellState();
232  Scalar alq = 0.0;
233  // Add gas lift from all wells on this process
234  for (const std::string& wellname : group.wells()) {
235  const Well& well = well_model_.schedule().getWell(wellname, report_step_idx_);
236  if (well.isInjector() || !well_state.isOpen(wellname)) {
237  continue;
238  }
239 
240  const Scalar efficiency = well.getEfficiencyFactor(/*network*/ true)
241  * well_state.getGlobalEfficiencyScalingFactor(wellname);
242  const auto& well_index = well_state.index(wellname);
243  if (well_index.has_value() &&
244  well_state.wellIsOwned(well_index.value(), wellname))
245  {
246  alq += well_state.well(wellname).alq_state.get() * efficiency;
247  }
248  }
249  // Sum ALQ across all processes to get total ALQ for the node.
250  // Note that communication is required here since each
251  // process has different wells, and the loop above therefore
252  // only considers local wells.
253  // However, all processes have all groups and their rates available,
254  // so we do not need to communicate those.
255  alq = comm_.sum(alq);
256  // Only add satellite production once for parallel runs
257  // (i.e. add after communication)
258  if (group.hasSatelliteProduction()) {
259  const auto& gsat_prod = well_model_.schedule()[report_step_idx_].gsatprod().get(node, well_model_.summaryState());
260  alq += gsat_prod.rate[GSatProd::GSatProdGroupProp::Rate::GLift];
261  }
262 
263  rates[IndexTraits::gasPhaseIdx] += alq;
264  }
265 
266  void accumulateInflows(const std::vector<std::string>& root_to_child_nodes,
267  std::map<std::string, std::vector<Scalar>>& node_inflows) const
268  {
269  const auto child_to_root_nodes = std::ranges::reverse_view(root_to_child_nodes);
270 
271  for (const auto& node : child_to_root_nodes) {
272  const auto upbranch = network_.uptree_branch(node);
273  if (!upbranch) {
274  continue;
275  }
276  std::vector<Scalar>& up = node_inflows[(*upbranch).uptree_node()];
277  const std::vector<Scalar>& down = node_inflows[node];
278  // NEFAC support
279  const Scalar efficiency = network_.node(node).efficiency();
280  if (up.empty()) {
281  up = std::vector<Scalar>(down.size(), 0.0);
282  }
283  assert(up.size() == down.size());
284  for (std::size_t ii = 0; ii < up.size(); ++ii) {
285  up[ii] += efficiency * down[ii];
286  }
287  }
288  }
289 
290  void computeNodePressures(const std::vector<std::string>& root_to_child_nodes,
291  const std::map<std::string, std::vector<Scalar>>& node_inflows)
292  {
293  for (const auto& node : root_to_child_nodes) {
294  // Do not traverse subtree more than once
295  if (node_pressures_.find(node) != node_pressures_.end()) {
296  continue;
297  }
298 
299  const auto terminal_pressure = network_.node(node).terminal_pressure();
300  const auto upbranch = network_.uptree_branch(node);
301  assert(upbranch || terminal_pressure); // If not root, must have uptree branch, and if root, must have terminal pressure.
303 
304  if (terminal_pressure) {
305  node_pressures_[node] = *terminal_pressure;
306  if (upbranch) {
307  // If terminal pressure is specified on a non-root node, we still want to calculate the branch data for the uptree branch.
308  const Scalar up_press = node_pressures_[(*upbranch).uptree_node()];
309  auto rates = node_inflows.at(node);
310  branch_data_.try_emplace(node,
311  *terminal_pressure - up_press,
312  rates[IndexTraits::oilPhaseIdx],
313  rates[IndexTraits::waterPhaseIdx],
314  rates[IndexTraits::gasPhaseIdx]);
315  } else {
316  // Root node with terminal pressure and no uptree branch, inserting a zero-valued placeholder.
317  branch_data_.emplace(node, data::BranchData{0.0, 0.0, 0.0, 0.0});
318  }
319  continue;
320  }
321 
322  const Scalar up_press = node_pressures_[(*upbranch).uptree_node()];
323  const auto vfp_table = (*upbranch).vfp_table();
324  if (!vfp_table) {
325  // Table number specified as 9999 in the deck, no pressure loss.
326  if (network_.node(node).as_choke()) {
327  // Node pressure is set to the group THP.
328  node_pressures_[node] = well_model_.groupStateHelper().groupState().well_group_thp(node);
329  } else {
330  node_pressures_[node] = up_press;
331  }
332  auto rates = node_inflows.at(node);
333  branch_data_.try_emplace(node,
334  node_pressures_[node] - up_press,
335  rates[IndexTraits::oilPhaseIdx],
336  rates[IndexTraits::waterPhaseIdx],
337  rates[IndexTraits::gasPhaseIdx]);
338  continue;
339  }
340 
341  OPM_TIMEBLOCK(NetworkVfpCalculations);
342  auto rates = node_inflows.at(node);
343  assert(rates.size() == 3);
344  Calc::prepareRates(rates);
345  auto node_pressure = Calc::compute(vfp_props_, *vfp_table, rates, up_press, *upbranch, unit_system_);
346  node_pressures_[node] = node_pressure;
347  // Prefer inserting after computing the pressure, hence negating rates
348  branch_data_.try_emplace(node,
349  node_pressure - up_press,
350  -rates[IndexTraits::oilPhaseIdx],
351  -rates[IndexTraits::waterPhaseIdx],
352  -rates[IndexTraits::gasPhaseIdx]);
353  }
354  }
355 
356  const GenericWellModel& well_model_;
357  const Network::ExtNetwork& network_;
358  const VfpProperties& vfp_props_;
359  const UnitSystem& unit_system_;
360  const int report_step_idx_;
361  const Communication& comm_;
362  std::map<std::string, Scalar> node_pressures_;
363  std::map<std::string, data::BranchData> branch_data_;
364 };
365 
366 } // namespace Opm
367 
368 #endif // OPM_BLACKOIL_WELL_MODEL_NETWORK_PRESSURE_COMPUTATION_HPP
Helper class to insulate the NetworkPressureComputation class from the differences between production...
Definition: BlackoilWellModelNetworkPressureComputation.hpp:49
EvalWell bhp(const int table_id, const EvalWell &aqua, const EvalWell &liquid, const EvalWell &vapour, const Scalar thp, const Scalar alq, const Scalar explicit_wfr, const Scalar explicit_gfr, const bool use_expvfp) const
Linear interpolation of bhp as a function of the input parameters given as Evalutions Each entry corr...
Definition: VFPProdProperties.cpp:165
Class which linearly interpolates BHP as a function of rate, tubing head pressure, water fraction, gas fraction, and artificial lift for production VFP tables, and similarly the BHP as a function of the rate and tubing head pressure.
Definition: BlackoilWellModelNetworkGeneric.hpp:42
Definition: VFPInjProperties.hpp:34
Definition: BlackoilWellModelConstraints.hpp:34
Structs needed for tpfalinearizer and its gpuparams struct extracted to be defined in one place that ...
Definition: blackoilbioeffectsmodules.hh:45
const VFPProdTable & getTable(const int table_id) const
Returns the table associated with the ID, or throws an exception if the table does not exist...
Definition: VFPProdProperties.cpp:99
Class to compute network pressures using VFP tables, given flow rates for each group and fixed pressu...
Definition: BlackoilWellModelNetworkPressureComputation.hpp:130
EvalWell bhp(const int table_id, const EvalWell &aqua, const EvalWell &liquid, const EvalWell &vapour, const Scalar thp) const
Linear interpolation of bhp as a function of the input parameters given as Evaluation Each entry corr...
Definition: VFPInjProperties.cpp:103