opm-simulators
AquiferAnalytical.hpp
1 /*
2  Copyright 2017 SINTEF Digital, Mathematics and Cybernetics.
3  Copyright 2017 Statoil ASA.
4  Copyright 2017 IRIS
5 
6  This file is part of the Open Porous Media project (OPM).
7 
8  OPM is free software: you can redistribute it and/or modify
9  it under the terms of the GNU General Public License as published by
10  the Free Software Foundation, either version 3 of the License, or
11  (at your option) any later version.
12 
13  OPM is distributed in the hope that it will be useful,
14  but WITHOUT ANY WARRANTY; without even the implied warranty of
15  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16  GNU General Public License for more details.
17 
18  You should have received a copy of the GNU General Public License
19  along with OPM. If not, see <http://www.gnu.org/licenses/>.
20 */
21 
22 #ifndef OPM_AQUIFERANALYTICAL_HEADER_INCLUDED
23 #define OPM_AQUIFERANALYTICAL_HEADER_INCLUDED
24 
25 #include <dune/grid/common/partitionset.hh>
26 
27 #include <opm/common/ErrorMacros.hpp>
28 
29 #include <opm/input/eclipse/EclipseState/Aquifer/Aquancon.hpp>
30 
31 #include <opm/material/common/MathToolbox.hpp>
32 #include <opm/material/densead/Evaluation.hpp>
33 #include <opm/material/densead/Math.hpp>
34 #include <opm/material/fluidstates/BlackOilFluidState.hpp>
35 
39 
40 #include <opm/output/data/Aquifer.hpp>
41 
42 #include <opm/simulators/aquifers/AquiferInterface.hpp>
43 #include <opm/simulators/utils/DeferredLoggingErrorHelpers.hpp>
44 
45 #include <algorithm>
46 #include <cmath>
47 #include <cstddef>
48 #include <limits>
49 #include <numeric>
50 #include <optional>
51 #include <vector>
52 
53 namespace Opm
54 {
55 template <typename TypeTag>
56 class AquiferAnalytical : public AquiferInterface<TypeTag>
57 {
58 public:
63  using BlackoilIndices = GetPropType<TypeTag, Properties::Indices>;
67 
68  static constexpr EnergyModules energyModuleType = getPropValue<TypeTag, Properties::EnergyModuleType>();
69  enum { enableBrine = getPropValue<TypeTag, Properties::EnableBrine>() };
70  enum { enableVapwat = getPropValue<TypeTag, Properties::EnableVapwat>() };
71  enum { has_disgas_in_water = getPropValue<TypeTag, Properties::EnableDisgasInWater>() };
72  enum { enableSolvent = getPropValue<TypeTag, Properties::EnableSolvent>() };
73 
74  enum { enableSaltPrecipitation = getPropValue<TypeTag, Properties::EnableSaltPrecipitation>() };
75 
77 
78  static constexpr int numEq = BlackoilIndices::numEq;
79 
80  using FluidState = BlackOilFluidState<Eval,
81  FluidSystem,
82  energyModuleType != EnergyModules::NoTemperature,
83  energyModuleType == EnergyModules::FullyImplicitThermal,
84  BlackoilIndices::gasEnabled,
85  enableVapwat,
86  enableBrine,
87  enableSaltPrecipitation,
88  has_disgas_in_water,
89  enableSolvent,
90  BlackoilIndices::numPhases>;
91 
92  // Constructor
93  AquiferAnalytical(const int aqID,
94  const std::vector<Aquancon::AquancCell>& connections,
95  const Simulator& simulator)
96  : AquiferInterface<TypeTag>(aqID, simulator)
97  , connections_(connections)
98  {
99  this->initializeConnectionMappings();
100  }
101 
102  void computeFaceAreaFraction(const std::vector<Scalar>& total_face_area) override
103  {
104  assert (total_face_area.size() >= static_cast<typename std::vector<Scalar>::size_type>(this->aquiferID()));
105 
106  const auto tfa = total_face_area[this->aquiferID() - 1];
107  const auto eps_sqrt = std::sqrt(std::numeric_limits<Scalar>::epsilon());
108 
109  if (tfa < eps_sqrt) {
110  this->alphai_.assign(this->size(), Scalar{0});
111  }
112  else {
113  std::ranges::transform(this->faceArea_connected_, this->alphai_.begin(),
114  [tfa](const Scalar area)
115  { return area / tfa; });
116  }
117 
118  this->area_fraction_ = this->totalFaceArea() / tfa;
119  }
120 
121  Scalar totalFaceArea() const override
122  {
123  return this->total_face_area_;
124  }
125 
126  void initFromRestart(const data::Aquifers& aquiferSoln) override
127  {
128  auto xaqPos = aquiferSoln.find(this->aquiferID());
129  if (xaqPos == aquiferSoln.end())
130  return;
131 
132  this->assignRestartData(xaqPos->second);
133 
134  this->W_flux_ = xaqPos->second.volume * this->area_fraction_;
135  this->pa0_ = xaqPos->second.initPressure;
136 
137  this->solution_set_from_restart_ = true;
138  }
139 
140  void initialSolutionApplied() override
141  {
142  initQuantities();
143  }
144 
145  void beginTimeStep() override
146  {
147  ElementContext elemCtx(this->simulator_);
148  OPM_BEGIN_PARALLEL_TRY_CATCH();
149 
150  for (const auto& elem : elements(this->simulator_.gridView())) {
151  elemCtx.updatePrimaryStencil(elem);
152 
153  const int cellIdx = elemCtx.globalSpaceIndex(0, 0);
154  const int idx = cellToConnectionIdx_[cellIdx];
155  if (idx < 0)
156  continue;
157 
158  elemCtx.updateIntensiveQuantities(0);
159  const auto& iq = elemCtx.intensiveQuantities(0, 0);
160  pressure_previous_[idx] = getValue(iq.fluidState().pressure(this->phaseIdx_()));
161  }
162 
163  OPM_END_PARALLEL_TRY_CATCH("AquiferAnalytical::beginTimeStep() failed: ",
164  this->simulator_.vanguard().grid().comm());
165  }
166 
167  void addToSource(RateVector& rates,
168  const unsigned cellIdx,
169  const unsigned timeIdx) override
170  {
171  const auto& model = this->simulator_.model();
172 
173  const int idx = this->cellToConnectionIdx_[cellIdx];
174  if (idx < 0)
175  return;
176 
177  const auto& intQuants = model.intensiveQuantities(cellIdx, timeIdx);
178 
179  // This is the pressure at td + dt
180  this->updateCellPressure(this->pressure_current_, idx, intQuants);
181  this->calculateInflowRate(idx, this->simulator_);
182 
183  rates[BlackoilIndices::conti0EqIdx + compIdx_()]
184  += this->Qai_[idx] / model.dofTotalVolume(cellIdx);
185 
186  if constexpr (energyModuleType == EnergyModules::FullyImplicitThermal) {
187  auto fs = intQuants.fluidState();
188  if (this->Ta0_.has_value() && this->Qai_[idx] > 0)
189  {
190  fs.setTemperature(this->Ta0_.value());
191  typedef typename std::decay<decltype(fs)>::type::ValueType FsValueType;
192  typename FluidSystem::template ParameterCache<FsValueType> paramCache;
193  const unsigned pvtRegionIdx = intQuants.pvtRegionIndex();
194  paramCache.setRegionIndex(pvtRegionIdx);
195  paramCache.updatePhase(fs, this->phaseIdx_());
196  const auto& h = FluidSystem::enthalpy(fs, paramCache, this->phaseIdx_());
197  fs.setEnthalpy(this->phaseIdx_(), h);
198  }
199  rates[BlackoilIndices::contiEnergyEqIdx]
200  += this->Qai_[idx] *fs.enthalpy(this->phaseIdx_()) * FluidSystem::referenceDensity( this->phaseIdx_(), intQuants.pvtRegionIndex()) / model.dofTotalVolume(cellIdx);
201 
202  }
203  }
204 
205  std::size_t size() const
206  {
207  return this->connections_.size();
208  }
209 
210  template<class Serializer>
211  void serializeOp(Serializer& serializer)
212  {
213  serializer(pressure_previous_);
214  serializer(pressure_current_);
215  serializer(Qai_);
216  serializer(rhow_);
217  serializer(W_flux_);
218  }
219 
220  bool operator==(const AquiferAnalytical& rhs) const
221  {
222  return this->pressure_previous_ == rhs.pressure_previous_ &&
223  this->pressure_current_ == rhs.pressure_current_ &&
224  this->Qai_ == rhs.Qai_ &&
225  this->rhow_ == rhs.rhow_ &&
226  this->W_flux_ == rhs.W_flux_;
227  }
228 
229 protected:
230  virtual void assignRestartData(const data::AquiferData& xaq) = 0;
231  virtual void calculateInflowRate(int idx, const Simulator& simulator) = 0;
232  virtual void calculateAquiferCondition() = 0;
233  virtual void calculateAquiferConstants() = 0;
234  virtual Scalar aquiferDepth() const = 0;
235 
236  Scalar gravity_() const
237  {
238  return this->simulator_.problem().gravity()[2];
239  }
240 
241  int compIdx_() const
242  {
243  if (this->co2store_or_h2store_())
244  return FluidSystem::oilCompIdx;
245 
246  return FluidSystem::waterCompIdx;
247  }
248 
249  void initQuantities()
250  {
251  // We reset the cumulative flux at the start of any simulation, so, W_flux = 0
252  if (! this->solution_set_from_restart_) {
253  W_flux_ = Scalar{0};
254  }
255 
256  this->initializeConnectionDepths();
257  this->calculateAquiferCondition();
258  this->calculateAquiferConstants();
259 
260  this->pressure_previous_.resize(this->size(), Scalar{0});
261  this->pressure_current_.resize(this->size(), Scalar{0});
262  this->Qai_.resize(this->size(), Scalar{0});
263  }
264 
265  void updateCellPressure(std::vector<Eval>& pressure_water,
266  const int idx,
267  const IntensiveQuantities& intQuants)
268  {
269  const auto& fs = intQuants.fluidState();
270  pressure_water.at(idx) = fs.pressure(this->phaseIdx_());
271  }
272 
273  void updateCellPressure(std::vector<Scalar>& pressure_water,
274  const int idx,
275  const IntensiveQuantities& intQuants)
276  {
277  const auto& fs = intQuants.fluidState();
278  pressure_water.at(idx) = fs.pressure(this->phaseIdx_()).value();
279  }
280 
281  void initializeConnectionMappings()
282  {
283  this->alphai_.resize(this->size(), 1.0);
284  this->faceArea_connected_.resize(this->size(), Scalar{0});
285 
286  // total_face_area_ is the sum of the areas connected to an aquifer
287  this->total_face_area_ = Scalar{0};
288  this->cellToConnectionIdx_.resize(this->simulator_.gridView().size(/*codim=*/0), -1);
289  const auto& gridView = this->simulator_.vanguard().gridView();
290  for (std::size_t idx = 0; idx < this->size(); ++idx) {
291  const auto global_index = this->connections_[idx].global_index;
292  const int cell_index = this->simulator_.vanguard().compressedIndex(global_index);
293  if (cell_index < 0) {
294  continue;
295  }
296 
297  auto elemIt = gridView.template begin</*codim=*/ 0>();
298  std::advance(elemIt, cell_index);
299 
300  // The global_index is not part of this grid
301  if (elemIt->partitionType() != Dune::InteriorEntity) {
302  continue;
303  }
304 
305  this->cellToConnectionIdx_[cell_index] = idx;
306  }
307 
308  // Translate the C face tag into the enum used by opm-parser's TransMult class
309  FaceDir::DirEnum faceDirection;
310 
311  // Get areas for all connections
312  const auto& elemMapper = this->simulator_.model().dofMapper();
313  for (const auto& elem : elements(gridView)) {
314  const unsigned cell_index = elemMapper.index(elem);
315  const int idx = this->cellToConnectionIdx_[cell_index];
316 
317  // Only deal with connections given by the aquifer
318  if (idx < 0) {
319  continue;
320  }
321 
322  for (const auto& intersection : intersections(gridView, elem)) {
323  // Only deal with grid boundaries
324  if (! intersection.boundary()) {
325  continue;
326  }
327 
328  switch (intersection.indexInInside()) {
329  case 0:
330  faceDirection = FaceDir::XMinus;
331  break;
332  case 1:
333  faceDirection = FaceDir::XPlus;
334  break;
335  case 2:
336  faceDirection = FaceDir::YMinus;
337  break;
338  case 3:
339  faceDirection = FaceDir::YPlus;
340  break;
341  case 4:
342  faceDirection = FaceDir::ZMinus;
343  break;
344  case 5:
345  faceDirection = FaceDir::ZPlus;
346  break;
347  default:
348  OPM_THROW(std::logic_error,
349  "Internal error in initialization of aquifer.");
350  }
351 
352  if (faceDirection == this->connections_[idx].face_dir) {
353  this->faceArea_connected_[idx] = this->connections_[idx].influx_coeff;
354  break;
355  }
356  }
357 
358  this->total_face_area_ += this->faceArea_connected_.at(idx);
359  }
360  }
361 
362  void initializeConnectionDepths()
363  {
364  this->cell_depth_.resize(this->size(), this->aquiferDepth());
365 
366  const auto& gridView = this->simulator_.vanguard().gridView();
367  for (std::size_t idx = 0; idx < this->size(); ++idx) {
368  const int cell_index = this->simulator_.vanguard()
369  .compressedIndex(this->connections_[idx].global_index);
370  if (cell_index < 0) {
371  continue;
372  }
373 
374  auto elemIt = gridView.template begin</*codim=*/ 0>();
375  std::advance(elemIt, cell_index);
376 
377  // The global_index is not part of this grid
378  if (elemIt->partitionType() != Dune::InteriorEntity) {
379  continue;
380  }
381 
382  this->cell_depth_.at(idx) = this->simulator_.vanguard().cellCenterDepth(cell_index);
383  }
384  }
385 
386  // This function is for calculating the aquifer properties from equilibrium state with the reservoir
387  Scalar calculateReservoirEquilibrium()
388  {
389  // Since the global_indices are the reservoir index, we just need to extract the fluidstate at those indices
390  std::vector<Scalar> pw_aquifer;
391  Scalar water_pressure_reservoir;
392 
393  ElementContext elemCtx(this->simulator_);
394  const auto& gridView = this->simulator_.gridView();
395  for (const auto& elem : elements(gridView)) {
396  elemCtx.updatePrimaryStencil(elem);
397 
398  const auto cellIdx = elemCtx.globalSpaceIndex(/*spaceIdx=*/0, /*timeIdx=*/0);
399  const auto idx = this->cellToConnectionIdx_[cellIdx];
400  if (idx < 0)
401  continue;
402 
403  elemCtx.updatePrimaryIntensiveQuantities(/*timeIdx=*/0);
404  const auto& iq0 = elemCtx.intensiveQuantities(/*spaceIdx=*/0, /*timeIdx=*/0);
405  const auto& fs = iq0.fluidState();
406 
407  water_pressure_reservoir = fs.pressure(this->phaseIdx_()).value();
408  const auto water_density = fs.density(this->phaseIdx_());
409 
410  const auto gdz =
411  this->gravity_() * (this->cell_depth_[idx] - this->aquiferDepth());
412 
413  pw_aquifer.push_back(this->alphai_[idx] *
414  (water_pressure_reservoir - water_density.value()*gdz));
415  }
416 
417  // We take the average of the calculated equilibrium pressures.
418  const auto& comm = this->simulator_.vanguard().grid().comm();
419 
420  Scalar vals[2];
421  vals[0] = std::accumulate(this->alphai_.begin(), this->alphai_.end(), Scalar{0});
422  vals[1] = std::accumulate(pw_aquifer.begin(), pw_aquifer.end(), Scalar{0});
423 
424  comm.sum(vals, 2);
425 
426  return vals[1] / vals[0];
427  }
428 
429  const std::vector<Aquancon::AquancCell> connections_;
430 
431  // Grid variables
432  std::vector<Scalar> faceArea_connected_;
433  std::vector<int> cellToConnectionIdx_;
434 
435  // Quantities at each grid id
436  std::vector<Scalar> cell_depth_;
437  std::vector<Scalar> pressure_previous_;
438  std::vector<Eval> pressure_current_;
439  std::vector<Eval> Qai_;
440  std::vector<Scalar> alphai_;
441 
442  Scalar Tc_{}; // Time constant
443  Scalar pa0_{}; // initial aquifer pressure
444  std::optional<Scalar> Ta0_{}; // initial aquifer temperature
445  Scalar rhow_{};
446 
447  Scalar total_face_area_{};
448  Scalar area_fraction_{Scalar{1}};
449 
450  Eval W_flux_;
451 
452  bool solution_set_from_restart_ {false};
453  bool has_active_connection_on_proc_{false};
454 };
455 
456 } // namespace Opm
457 
458 #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
Declares the properties required by the black oil model.
Definition: AquiferInterface.hpp:34
Definition: AquiferAnalytical.hpp:56
Contains the classes required to extend the black-oil model by energy.
Defines a type tags and some fundamental properties all models.