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