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