opm-simulators
AquiferNumerical.hpp
1 /*
2  Copyright (C) 2020 Equinor ASA
3  Copyright (C) 2020 SINTEF Digital
4 
5  This file is part of the Open Porous Media project (OPM).
6 
7  OPM is free software: you can redistribute it and/or modify
8  it under the terms of the GNU General Public License as published by
9  the Free Software Foundation, either version 3 of the License, or
10  (at your option) any later version.
11 
12  OPM is distributed in the hope that it will be useful,
13  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  GNU General Public License for more details.
16 
17  You should have received a copy of the GNU General Public License
18  along with OPM. If not, see <http://www.gnu.org/licenses/>.
19 */
20 
21 #ifndef OPM_AQUIFERNUMERICAL_HEADER_INCLUDED
22 #define OPM_AQUIFERNUMERICAL_HEADER_INCLUDED
23 
24 #include <dune/grid/common/partitionset.hh>
25 
26 #include <opm/input/eclipse/EclipseState/Aquifer/NumericalAquifer/SingleNumericalAquifer.hpp>
27 
28 #include <opm/material/common/MathToolbox.hpp>
29 #include <opm/material/densead/Evaluation.hpp>
30 
31 #include <opm/output/data/Aquifer.hpp>
32 
33 #include <opm/simulators/aquifers/AquiferInterface.hpp>
34 #include <opm/simulators/utils/DeferredLoggingErrorHelpers.hpp>
35 
36 #include <algorithm>
37 #include <cassert>
38 #include <cstddef>
39 #include <vector>
40 
41 namespace Opm
42 {
43 template <typename TypeTag>
44 class AquiferNumerical : public AquiferInterface<TypeTag>
45 {
46 public:
47  using BlackoilIndices = GetPropType<TypeTag, Properties::Indices>;
56 
57  enum { dimWorld = GridView::dimensionworld };
58  enum { numPhases = FluidSystem::numPhases };
59  static constexpr int numEq = BlackoilIndices::numEq;
60 
62  using Toolbox = MathToolbox<Eval>;
63 
64  using typename AquiferInterface<TypeTag>::RateVector;
65 
66  // Constructor
67  AquiferNumerical(const SingleNumericalAquifer& aquifer,
68  const Simulator& simulator)
69  : AquiferInterface<TypeTag>(aquifer.id(), simulator)
70  , flux_rate_ (0.0)
71  , cumulative_flux_(0.0)
72  , init_pressure_ (aquifer.numCells(), 0.0)
73  {
74  this->cell_to_aquifer_cell_idx_.resize(this->simulator_.gridView().size(/*codim=*/0), -1);
75 
76  auto aquifer_on_process = false;
77  for (std::size_t idx = 0; idx < aquifer.numCells(); ++idx) {
78  const auto* cell = aquifer.getCellPrt(idx);
79 
80  // Due to parallelisation, the cell might not exist in the current process
81  const int compressed_idx = simulator.vanguard().compressedIndexForInterior(cell->global_index);
82  if (compressed_idx >= 0) {
83  this->cell_to_aquifer_cell_idx_[compressed_idx] = idx;
84  aquifer_on_process = true;
85  }
86  }
87 
88  if (aquifer_on_process) {
89  this->checkConnectsToReservoir();
90  }
91  }
92 
93  static AquiferNumerical serializationTestObject(const Simulator& simulator)
94  {
95  AquiferNumerical result({}, simulator);
96  result.flux_rate_ = 1.0;
97  result.cumulative_flux_ = 2.0;
98  result.init_pressure_ = {3.0, 4.0};
99  result.pressure_ = 5.0;
100 
101  return result;
102  }
103 
104  void initFromRestart(const data::Aquifers& aquiferSoln) override
105  {
106  auto xaqPos = aquiferSoln.find(this->aquiferID());
107  if (xaqPos == aquiferSoln.end())
108  return;
109 
110  if (this->connects_to_reservoir_) {
111  this->cumulative_flux_ = xaqPos->second.volume;
112  }
113 
114  if (const auto* aqData = xaqPos->second.typeData.template get<data::AquiferType::Numerical>();
115  aqData != nullptr)
116  {
117  this->init_pressure_.resize(aqData->initPressure.size());
118  std::ranges::copy(aqData->initPressure, this->init_pressure_.begin());
119  }
120 
121  this->solution_set_from_restart_ = true;
122  }
123 
124  void beginTimeStep() override {}
125  void addToSource(RateVector&, const unsigned, const unsigned) override {}
126 
127  void endTimeStep() override
128  {
129  this->pressure_ = this->calculateAquiferPressure();
130  this->flux_rate_ = this->calculateAquiferFluxRate();
131  this->cumulative_flux_ += this->flux_rate_ * this->simulator_.timeStepSize();
132  }
133 
134  data::AquiferData aquiferData() const override
135  {
136  data::AquiferData data;
137  data.aquiferID = this->aquiferID();
138  data.pressure = this->pressure_;
139  data.fluxRate = this->flux_rate_;
140  data.volume = this->cumulative_flux_;
141 
142  auto* aquNum = data.typeData.template create<data::AquiferType::Numerical>();
143  aquNum->initPressure.resize(this->init_pressure_.size());
144  std::ranges::copy(this->init_pressure_, aquNum->initPressure.begin());
145 
146  return data;
147  }
148 
149  void initialSolutionApplied() override
150  {
151  if (this->solution_set_from_restart_) {
152  return;
153  }
154 
155  this->pressure_ = this->calculateAquiferPressure(this->init_pressure_);
156  this->flux_rate_ = 0.;
157  this->cumulative_flux_ = 0.;
158  }
159 
160  void computeFaceAreaFraction(const std::vector<Scalar>& /*total_face_area*/) override
161  {}
162 
163  Scalar totalFaceArea() const override
164  {
165  return 1.0;
166  }
167 
168  template<class Serializer>
169  void serializeOp(Serializer& serializer)
170  {
171  serializer(flux_rate_);
172  serializer(cumulative_flux_);
173  serializer(init_pressure_);
174  serializer(pressure_);
175  }
176 
177  bool operator==(const AquiferNumerical& rhs) const
178  {
179  return this->flux_rate_ == rhs.flux_rate_ &&
180  this->cumulative_flux_ == rhs.cumulative_flux_ &&
181  this->init_pressure_ == rhs.init_pressure_ &&
182  this->pressure_ == rhs.pressure_;
183  }
184 
185  Scalar cumulativeFlux() const
186  {
187  return this->cumulative_flux_;
188  }
189 
190 private:
191  void checkConnectsToReservoir()
192  {
193  ElementContext elem_ctx(this->simulator_);
194  auto elemIt = std::find_if(this->simulator_.gridView().template begin</*codim=*/0>(),
195  this->simulator_.gridView().template end</*codim=*/0>(),
196  [&elem_ctx, this](const auto& elem) -> bool
197  {
198  elem_ctx.updateStencil(elem);
199 
200  const auto cell_index = elem_ctx
201  .globalSpaceIndex(/*spaceIdx=*/0, /*timeIdx=*/0);
202 
203  return this->cell_to_aquifer_cell_idx_[cell_index] == 0;
204  });
205 
206  assert ((elemIt != this->simulator_.gridView().template end</*codim=*/0>())
207  && "Internal error locating numerical aquifer's connecting cell");
208 
209  this->connects_to_reservoir_ =
210  elemIt->partitionType() == Dune::InteriorEntity;
211  }
212 
213  Scalar calculateAquiferPressure() const
214  {
215  auto capture = std::vector<Scalar>(this->init_pressure_.size(), 0.0);
216  return this->calculateAquiferPressure(capture);
217  }
218 
219  Scalar calculateAquiferPressure(std::vector<Scalar>& cell_pressure) const
220  {
221  Scalar sum_pressure_watervolume = 0.;
222  Scalar sum_watervolume = 0.;
223 
224  ElementContext elem_ctx(this->simulator_);
225  const auto& gridView = this->simulator_.gridView();
226  OPM_BEGIN_PARALLEL_TRY_CATCH();
227 
228  for (const auto& elem : elements(gridView, Dune::Partitions::interior)) {
229  elem_ctx.updatePrimaryStencil(elem);
230 
231  const std::size_t cell_index = elem_ctx.globalSpaceIndex(/*spaceIdx=*/0, /*timeIdx=*/0);
232  const int idx = this->cell_to_aquifer_cell_idx_[cell_index];
233  if (idx < 0) {
234  continue;
235  }
236 
237  elem_ctx.updatePrimaryIntensiveQuantities(/*timeIdx=*/0);
238  const auto& iq0 = elem_ctx.intensiveQuantities(/*spaceIdx=*/0, /*timeIdx=*/0);
239  const auto& fs = iq0.fluidState();
240 
241  // TODO: the porosity of the cells are still wrong for numerical aquifer cells
242  // Because the dofVolume still based on the grid information.
243  // The pore volume is correct. Extra efforts will be done to get sensible porosity value here later.
244  const Scalar water_saturation = fs.saturation(this->phaseIdx_()).value();
245  const Scalar porosity = iq0.porosity().value();
246  const Scalar volume = elem_ctx.dofTotalVolume(0, 0);
247  // TODO: not sure we should use water pressure here
248  const Scalar water_pressure_reservoir = fs.pressure(this->phaseIdx_()).value();
249  const Scalar water_volume = volume * porosity * water_saturation;
250  sum_pressure_watervolume += water_volume * water_pressure_reservoir;
251  sum_watervolume += water_volume;
252 
253  cell_pressure[idx] = water_pressure_reservoir;
254  }
255  OPM_END_PARALLEL_TRY_CATCH("AquiferNumerical::calculateAquiferPressure() failed: ",
256  this->simulator_.vanguard().grid().comm());
257  const auto& comm = this->simulator_.vanguard().grid().comm();
258  comm.sum(&sum_pressure_watervolume, 1);
259  comm.sum(&sum_watervolume, 1);
260 
261  // Ensure all processes have same notion of the aquifer cells' pressure values.
262  comm.sum(cell_pressure.data(), cell_pressure.size());
263 
264  return sum_pressure_watervolume / sum_watervolume;
265  }
266 
267  template <class ElemCtx>
268  Scalar getWaterFlux(const ElemCtx& elem_ctx, unsigned face_idx) const
269  {
270  const auto& exQuants = elem_ctx.extensiveQuantities(face_idx, /*timeIdx*/ 0);
271  const Scalar water_flux = Toolbox::value(exQuants.volumeFlux(this->phaseIdx_()));
272  return water_flux;
273  }
274 
275  Scalar calculateAquiferFluxRate() const
276  {
277  Scalar aquifer_flux = 0.0;
278 
279  if (! this->connects_to_reservoir_) {
280  return aquifer_flux;
281  }
282 
283  ElementContext elem_ctx(this->simulator_);
284  const auto& gridView = this->simulator_.gridView();
285  for (const auto& elem : elements(gridView, Dune::Partitions::interior)) {
286  elem_ctx.updatePrimaryStencil(elem);
287  const std::size_t cell_index = elem_ctx.globalSpaceIndex(/*spaceIdx=*/0, /*timeIdx=*/0);
288  const int idx = this->cell_to_aquifer_cell_idx_[cell_index];
289  // we only need the first aquifer cell
290  if (idx != 0) {
291  continue;
292  }
293 
294  elem_ctx.updateStencil(elem);
295  const std::size_t num_interior_faces = elem_ctx.numInteriorFaces(/*timeIdx*/ 0);
296  const auto& stencil = elem_ctx.stencil(0);
297  elem_ctx.updateAllIntensiveQuantities();
298  elem_ctx.updateAllExtensiveQuantities();
299 
300  for (std::size_t face_idx = 0; face_idx < num_interior_faces; ++face_idx) {
301  const auto& face = stencil.interiorFace(face_idx);
302  // dof index
303  const std::size_t i = face.interiorIndex();
304  const std::size_t j = face.exteriorIndex();
305  // compressed index
306  // const std::size_t I = stencil.globalSpaceIndex(i);
307  const std::size_t J = stencil.globalSpaceIndex(j);
308 
309  assert(stencil.globalSpaceIndex(i) == cell_index);
310 
311  // we do not consider the flux within aquifer cells
312  // we only need the flux to the connections
313  if (this->cell_to_aquifer_cell_idx_[J] > 0) {
314  continue;
315  }
316 
317  const Scalar water_flux = getWaterFlux(elem_ctx,face_idx);
318  const std::size_t up_id = water_flux >= 0.0 ? i : j;
319  const auto& intQuantsIn = elem_ctx.intensiveQuantities(up_id, 0);
320  const Scalar invB = Toolbox::value(intQuantsIn.fluidState().invB(this->phaseIdx_()));
321  const Scalar face_area = face.area();
322  aquifer_flux += water_flux * invB * face_area;
323  }
324 
325  // we only need to handle the first aquifer cell, we can exit loop here
326  break;
327  }
328 
329  return aquifer_flux;
330  }
331 
332  Scalar flux_rate_; // aquifer influx rate
333  Scalar cumulative_flux_; // cumulative aquifer influx
334  std::vector<Scalar> init_pressure_{};
335  Scalar pressure_; // aquifer pressure
336  bool solution_set_from_restart_ {false};
337  bool connects_to_reservoir_ {false};
338 
339  // TODO: maybe unordered_map can also do the work to save memory?
340  std::vector<int> cell_to_aquifer_cell_idx_;
341 };
342 
343 } // namespace Opm
344 
345 #endif
typename Properties::Detail::GetPropImpl< TypeTag, Property >::type::type GetPropType
get the type alias defined in the property (equivalent to old macro GET_PROP_TYPE(...))
Definition: propertysystem.hh:233
This file contains a set of helper functions used by VFPProd / VFPInj.
Definition: blackoilbioeffectsmodules.hh:45
Definition: AquiferInterface.hpp:34
Definition: AquiferNumerical.hpp:44