opm-simulators
TemperatureModel.hpp
Go to the documentation of this file.
1 // -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 // vi: set et ts=4 sw=4 sts=4:
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 2 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  Consult the COPYING file in the top-level source directory of this
20  module for the precise wording of the license and the list of
21  copyright holders.
22 */
28 #ifndef OPM_TEMPERATURE_MODEL_HPP
29 #define OPM_TEMPERATURE_MODEL_HPP
30 
31 #include <opm/common/OpmLog/OpmLog.hpp>
32 #include <opm/common/utility/gpuDecorators.hpp>
33 
36 
37 #include <opm/simulators/flow/BlackoilModelParameters.hpp>
39 #include <opm/simulators/linalg/findOverlapRowsAndColumns.hpp>
40 #include <opm/simulators/aquifers/AquiferGridUtils.hpp>
43 
44 #include <algorithm>
45 #include <array>
46 #include <cassert>
47 #include <cmath>
48 #include <cstddef>
49 #include <memory>
50 #include <limits>
51 #include <set>
52 #include <vector>
53 
54 namespace Opm::Properties {
55 
56 template<class TypeTag, class MyTypeTag>
58  using type = UndefinedProperty;
59 };
60 
61 } // namespace Opm::Properties
62 
63 namespace Opm {
64 
65 template<typename Scalar, typename IndexTraits> class WellState;
66 
67 
68 template <class TypeTag>
70 {
74  using ThermalConductionLaw = GetPropType<TypeTag, Properties::ThermalConductionLaw>;
77 
78  static constexpr bool enableBrine = getPropValue<TypeTag, Properties::EnableBrine>();
79  enum { enableVapwat = getPropValue<TypeTag, Properties::EnableVapwat>() };
80  enum { enableDisgasInWater = getPropValue<TypeTag, Properties::EnableDisgasInWater>() };
81  enum { enableSaltPrecipitation = getPropValue<TypeTag, Properties::EnableSaltPrecipitation>() };
82  static constexpr bool enableSolvent = getPropValue<TypeTag, Properties::EnableSolvent>();
83  enum { numPhases = getPropValue<TypeTag, Properties::NumPhases>() };
84  static constexpr bool compositionSwitchEnabled =
85  Indices::compositionSwitchIdx != std::numeric_limits<unsigned>::max();
86 
87  public:
88  using EvaluationTemp = DenseAd::Evaluation<Scalar, 1>;
89  using FluidStateTemp = BlackOilFluidState<EvaluationTemp,
90  FluidSystem,
91  true, //store temperature
92  true, // store enthalpy
93  compositionSwitchEnabled,
94  enableVapwat,
95  enableBrine,
96  enableSaltPrecipitation,
97  enableDisgasInWater,
98  enableSolvent,
99  Indices::numPhases>;
100 
101 
102 
103  OPM_HOST_DEVICE void updateTemperature_(const Problem& problem,
104  unsigned globalDofIdx,
105  unsigned timeIdx)
106  {
107  const EvaluationTemp T = EvaluationTemp::createVariable(problem.temperature(globalDofIdx, timeIdx), 0);
108  fluidState_.setTemperature(T);
109  }
110 
111  OPM_HOST_DEVICE void updateEnergyQuantities_(const Problem& problem,
112  const unsigned globalSpaceIdx,
113  const unsigned timeIdx)
114  {
115  // compute the specific enthalpy of the fluids, the specific enthalpy of the rock
116  // and the thermal conductivity coefficients
117  for (int phaseIdx = 0; phaseIdx < numPhases; ++ phaseIdx) {
118  if (!FluidSystem::phaseIsActive(phaseIdx)) {
119  continue;
120  }
121 
122  const auto& h = FluidSystem::enthalpy(fluidState_, phaseIdx, problem.pvtRegionIndex(globalSpaceIdx));
123  fluidState_.setEnthalpy(phaseIdx, h);
124  }
125 
126  const auto& solidEnergyLawParams = problem.solidEnergyLawParams(globalSpaceIdx, timeIdx);
127  rockInternalEnergy_ = SolidEnergyLaw::solidInternalEnergy(solidEnergyLawParams, fluidState_);
128 
129  const auto& thermalConductionLawParams = problem.thermalConductionLawParams(globalSpaceIdx, timeIdx);
130  totalThermalConductivity_ = ThermalConductionLaw::thermalConductivity(thermalConductionLawParams, fluidState_);
131 
132  // Retrieve the rock fraction from the problem
133  // Usually 1 - porosity, but if pvmult is used to modify porosity
134  // we will apply the same multiplier to the rock fraction
135  // i.e. pvmult*(1 - porosity) and thus interpret multpv as a volume
136  // multiplier. This is to avoid negative rock volume for pvmult*porosity > 1
137  rockFraction_ = problem.rockFraction(globalSpaceIdx, timeIdx);
138  }
139 
140  OPM_HOST_DEVICE const EvaluationTemp& rockInternalEnergy() const
141  { return rockInternalEnergy_; }
142 
143  OPM_HOST_DEVICE const EvaluationTemp& totalThermalConductivity() const
144  { return totalThermalConductivity_; }
145 
146  OPM_HOST_DEVICE const Scalar& rockFraction() const
147  { return rockFraction_; }
148 
149  OPM_HOST_DEVICE const FluidStateTemp& fluidStateTemp() const
150  { return fluidState_; }
151 
152  template <class FluidState>
153  OPM_HOST_DEVICE void setFluidState(const FluidState& fs)
154  {
155  // copy the needed part of the fluid state
156  fluidState_.setPvtRegionIndex(fs.pvtRegionIndex());
157  fluidState_.setRs(getValue(fs.Rs()));
158  fluidState_.setRv(getValue(fs.Rv()));
159  for (int phaseIdx = 0; phaseIdx < numPhases; ++ phaseIdx) {
160  fluidState_.setPressure(phaseIdx, getValue(fs.pressure(phaseIdx)));
161  fluidState_.setDensity(phaseIdx, getValue(fs.density(phaseIdx)));
162  fluidState_.setSaturation(phaseIdx, getValue(fs.saturation(phaseIdx)));
163  fluidState_.setInvB(phaseIdx, getValue(fs.invB(phaseIdx)));
164  }
165  }
166 
167 protected:
168  EvaluationTemp rockInternalEnergy_;
169  EvaluationTemp totalThermalConductivity_;
170  FluidStateTemp fluidState_;
171  Scalar rockFraction_;
172 };
173 
179 template <class TypeTag, bool enableTempV = getPropValue<TypeTag, Properties::EnergyModuleType>() == EnergyModules::SequentialImplicitThermal >
180 class TemperatureModel : public GenericTemperatureModel<GetPropType<TypeTag, Properties::Grid>,
181  GetPropType<TypeTag, Properties::GridView>,
182  GetPropType<TypeTag, Properties::DofMapper>,
183  GetPropType<TypeTag, Properties::Stencil>,
184  GetPropType<TypeTag, Properties::FluidSystem>,
185  GetPropType<TypeTag, Properties::Scalar>>
186 {
202  using IntensiveQuantities = GetPropType<TypeTag, Properties::IntensiveQuantities>;
203  static constexpr EnergyModules energyModuleType = getPropValue<TypeTag, Properties::EnergyModuleType>();
205  using IndexTraits = typename FluidSystem::IndexTraitsType;
207 
209  using FluidStateTemp = typename IntensiveQuantitiesTemp::FluidStateTemp;
210  using Evaluation = typename IntensiveQuantitiesTemp::EvaluationTemp;
211  using EnergyMatrix = typename BaseType::EnergyMatrix;
212  using EnergyVector = typename BaseType::EnergyVector;
213  using MatrixBlockTemp = typename BaseType::MatrixBlockTemp;
214  using SpareMatrixEnergyAdapter = typename Linear::IstlSparseMatrixAdapter<MatrixBlockTemp>;
215 
216 
217  //using ResidualNBInfo = typename LocalResidual::ResidualNBInfo;
218  struct ResidualNBInfo
219  {
220  double faceArea;
221  double inAlpha;
222  double outAlpha;
223  };
224 
226 
227  enum { numEq = getPropValue<TypeTag, Properties::NumEq>() };
228  enum { numPhases = FluidSystem::numPhases };
229  enum { waterPhaseIdx = FluidSystem::waterPhaseIdx };
230  enum { oilPhaseIdx = FluidSystem::oilPhaseIdx };
231  enum { gasPhaseIdx = FluidSystem::gasPhaseIdx };
232  static constexpr unsigned temperatureIdx = 0;
233 
234 public:
235  explicit TemperatureModel(Simulator& simulator)
236  : BaseType(simulator.vanguard().gridView(),
237  simulator.vanguard().eclState(),
238  simulator.vanguard().cartesianIndexMapper(),
239  simulator.model().dofMapper())
240  , simulator_(simulator)
241  {}
242 
243  void init()
244  {
245  const unsigned int numCells = simulator_.model().numTotalDof();
246  this->doInit(numCells);
247 
248  if (!this->doTemp())
249  return;
250 
251  // we need the storage term at start of the iteration (timeIdx = 1)
252  storage1_.resize(numCells);
253 
254  // set the initial temperature
255  for (unsigned globI = 0; globI < numCells; ++globI) {
256  this->temperature_[globI] = simulator_.problem().initialFluidState(globI).temperature(0);
257  }
258  // keep a copy of the intensive quantities to simplify the update during
259  // the newton iterations
260  intQuants_.resize(numCells);
261 
262  // find and store the overlap cells
263  const auto& elemMapper = simulator_.model().elementMapper();
264  detail::findOverlapAndInterior(simulator_.vanguard().grid(), elemMapper, overlapRows_, interiorRows_);
265 
266  // set the scaling factor
267  scalingFactor_ = getPropValue<TypeTag, Properties::BlackOilEnergyScalingFactor>();
268 
269  // find the sparsity pattern of the temperature matrix
270  using NeighborSet = std::set<unsigned>;
271  std::vector<NeighborSet> neighbors(numCells);
272  Stencil stencil(this->gridView_, this->dofMapper_);
273  neighborInfo_.reserve(numCells, 6 * numCells);
274  std::vector<NeighborInfoCPU> loc_nbinfo;
275  for (const auto& elem : elements(this->gridView_)) {
276  stencil.update(elem);
277  for (unsigned primaryDofIdx = 0; primaryDofIdx < stencil.numPrimaryDof(); ++primaryDofIdx) {
278  const unsigned myIdx = stencil.globalSpaceIndex(primaryDofIdx);
279  loc_nbinfo.resize(stencil.numDof() - 1); // Do not include the primary dof in neighborInfo_
280  for (unsigned dofIdx = 0; dofIdx < stencil.numDof(); ++dofIdx) {
281  const unsigned neighborIdx = stencil.globalSpaceIndex(dofIdx);
282  neighbors[myIdx].insert(neighborIdx);
283  if (dofIdx > 0) {
284  const auto scvfIdx = dofIdx - 1;
285  const auto& scvf = stencil.interiorFace(scvfIdx);
286  const Scalar area = scvf.area();
287  Scalar inAlpha = simulator_.problem().thermalHalfTransmissibility(myIdx, neighborIdx);
288  Scalar outAlpha = simulator_.problem().thermalHalfTransmissibility(neighborIdx, myIdx);
289  ResidualNBInfo nbinfo{area, inAlpha, outAlpha};
290  loc_nbinfo[dofIdx - 1] = NeighborInfoCPU{neighborIdx, nbinfo, nullptr};
291  }
292  }
293  neighborInfo_.appendRow(loc_nbinfo.begin(), loc_nbinfo.end());
294  }
295  }
296  // allocate matrix for storing the Jacobian of the temperature residual
297  energyMatrix_ = std::make_unique<SpareMatrixEnergyAdapter>(simulator_);
298  diagMatAddress_.resize(numCells);
299  energyMatrix_->reserve(neighbors);
300  for (unsigned globI = 0; globI < numCells; globI++) {
301  const auto& nbInfos = neighborInfo_[globI];
302  diagMatAddress_[globI] = energyMatrix_->blockAddress(globI, globI);
303  for (auto& nbInfo : nbInfos) {
304  nbInfo.matBlockAddress = energyMatrix_->blockAddress(nbInfo.neighbor, globI);
305  }
306  }
307  }
308 
309  void beginTimeStep()
310  {
311  if (!this->doTemp()) {
312  return;
313  }
314 
315  // We use the specialized intensive quantities here with only the temperature derivative
316  const unsigned int numCells = simulator_.model().numTotalDof();
317  #ifdef _OPENMP
318  #pragma omp parallel for
319  #endif
320  for (unsigned globI = 0; globI < numCells; ++globI) {
321  intQuants_[globI].setFluidState(simulator_.model().intensiveQuantities(globI, /*timeIdx*/ 0).fluidState());
322  intQuants_[globI].updateTemperature_(simulator_.problem(), globI, /*timeIdx*/ 0);
323  intQuants_[globI].updateEnergyQuantities_(simulator_.problem(), globI, /*timeIdx*/ 0);
324  }
325  updateStorageCache();
326 
327  const int nw = simulator_.problem().wellModel().wellState().numWells();
328  this->energy_rates_.resize(nw, 0.0);
329  }
330 
334  void endTimeStep(WellStateType& wellState)
335  {
336  if (!this->doTemp()) {
337  return;
338  }
339  OPM_TIMEBLOCK(TemperatureModel_endTimeStep);
340 
341  // We use the specialized intensive quantities here with only the temperature derivative
342  const unsigned int numCells = simulator_.model().numTotalDof();
343  #ifdef _OPENMP
344  #pragma omp parallel for
345  #endif
346  for (unsigned globI = 0; globI < numCells; ++globI) {
347  intQuants_[globI].setFluidState(simulator_.model().intensiveQuantities(globI, /*timeIdx*/ 0).fluidState());
348  intQuants_[globI].updateTemperature_(simulator_.problem(), globI, /*timeIdx*/ 0);
349  intQuants_[globI].updateEnergyQuantities_(simulator_.problem(), globI, /*timeIdx*/ 0);
350  }
351  advanceTemperatureFields();
352 
353  // update energy_rates
354  const int nw = wellState.numWells();
355  for (auto wellID = 0*nw; wellID < nw; ++wellID) {
356  auto& ws = wellState.well(wellID);
357  ws.energy_rate = this->energy_rates_[wellID];
358  }
359 
360  // Update well temperature
361  const auto& wellPtrs = simulator_.problem().wellModel().localNonshutWells();
362  for (const auto& wellPtr : wellPtrs) {
363  auto& ws = wellState.well(wellPtr->name());
364  this->computeWellTemperature(*wellPtr, ws);
365  }
366  }
367 
372  template <class Restarter>
373  void serialize(Restarter&)
374  { /* not implemented */ }
375 
382  template <class Restarter>
383  void deserialize(Restarter&)
384  { /* not implemented */ }
385 
386 protected:
387  void updateStorageCache()
388  {
389  // we need the storage term at start of the iteration (timeIdx = 1)
390  const unsigned int numCells = simulator_.model().numTotalDof();
391  #ifdef _OPENMP
392  #pragma omp parallel for
393  #endif
394  for (unsigned globI = 0; globI < numCells; ++globI) {
395  Scalar storage = 0.0;
396  computeStorageTerm(globI, storage);
397  storage1_[globI] = storage;
398  }
399  }
400 
401  void advanceTemperatureFields()
402  {
403  OPM_TIMEBLOCK(TemperatureModel_advanceTemperatureFields);
404  const int max_iter = 20;
405  const int min_iter = 1;
406  bool is_converged = false;
407  // solve using Newton
408  for (int iter = 0; iter < max_iter; ++iter) {
409  assembleEquations();
410  if (iter >= min_iter && converged(iter)) {
411  is_converged = true;
412  break;
413  }
414  solveAndUpdate();
415  }
416  if (!is_converged) {
417  const auto msg =
418  fmt::format(fmt::runtime("Temperature model (TEMP): Newton did not converge after {} iterations. \n"
419  "The Simulator will continue to the next step with an unconverged solution."),
420  max_iter);
421  OpmLog::debug(msg);
422  }
423  }
424 
425  void solveAndUpdate()
426  {
427  OPM_TIMEBLOCK(TemperatureModel_solveAndUpdate);
428  const unsigned int numCells = simulator_.model().numTotalDof();
429  EnergyVector dx(numCells);
430  bool conv = this->linearSolve_(this->energyMatrix_->istlMatrix(), dx, this->energyVector_);
431  if (!conv) {
432  if (simulator_.gridView().comm().rank() == 0) {
433  OpmLog::warning("Temp model: Linear solver did not converge. Temperature values not updated.");
434  }
435  }
436  else {
437  OPM_TIMEBLOCK(TemperatureModel_solveAndUpdate_update);
438  #ifdef _OPENMP
439  #pragma omp parallel for
440  #endif
441  for (unsigned globI = 0; globI < numCells; ++globI) {
442  this->temperature_[globI] -= std::clamp(dx[globI][0], -this->maxTempChange_, this->maxTempChange_);
443  intQuants_[globI].updateTemperature_(simulator_.problem(), globI, /*timeIdx*/ 0);
444  intQuants_[globI].updateEnergyQuantities_(simulator_.problem(), globI, /*timeIdx*/ 0);
445  }
446  }
447  }
448 
449  bool converged(const int iter)
450  {
451  OPM_TIMEBLOCK(TemperatureModel_converged);
452  Scalar dt = simulator_.timeStepSize();
453  Scalar maxNorm = 0.0;
454  Scalar sumNorm = 0.0;
455  const auto tolerance_cnv_energy_strict = Parameters::Get<Parameters::ToleranceCnvEnergy<Scalar>>();
456  const auto& elemMapper = simulator_.model().elementMapper();
457  const IsNumericalAquiferCell isNumericalAquiferCell(simulator_.gridView().grid());
458  Scalar sum_pv = 0.0;
459  Scalar sum_pv_not_converged = 0.0;
460  for (const auto& elem : elements(simulator_.gridView(), Dune::Partitions::interior)) {
461  unsigned globI = elemMapper.index(elem);
462  const auto pvValue = simulator_.problem().referencePorosity(globI, /*timeIdx=*/0)
463  * simulator_.model().dofTotalVolume(globI);
464 
465  const Scalar scaled_norm = dt * std::abs(this->energyVector_[globI])/ pvValue;
466  maxNorm = max(maxNorm, scaled_norm);
467  sumNorm += scaled_norm;
468  if (!isNumericalAquiferCell(elem)) {
469  if (scaled_norm > tolerance_cnv_energy_strict) {
470  sum_pv_not_converged += pvValue;
471  }
472  sum_pv += pvValue;
473  }
474  }
475  {
476  OPM_TIMEBLOCK(TemperatureModel_converged_communicate);
477 
478  maxNorm = simulator_.gridView().comm().max(maxNorm);
479  sumNorm = simulator_.gridView().comm().sum(sumNorm);
480  sum_pv = simulator_.gridView().comm().sum(sum_pv);
481  sumNorm /= sum_pv;
482 
483  // Use relaxed tolerance if the fraction of unconverged cells porevolume is less than relaxed_max_pv_fraction
484  sum_pv_not_converged = simulator_.gridView().comm().sum(sum_pv_not_converged);
485  }
486  Scalar relaxed_max_pv_fraction = Parameters::Get<Parameters::RelaxedMaxPvFraction<Scalar>>();
487  const bool relax = (sum_pv_not_converged / sum_pv) < relaxed_max_pv_fraction;
488  const auto tolerance_energy_balance = relax? Parameters::Get<Parameters::ToleranceEnergyBalanceRelaxed<Scalar>>():
489  Parameters::Get<Parameters::ToleranceEnergyBalance<Scalar>>();
490  const bool tolerance_cnv_energy = relax? Parameters::Get<Parameters::ToleranceCnvEnergyRelaxed<Scalar>>():
491  tolerance_cnv_energy_strict;
492 
493  const auto msg = fmt::format(fmt::runtime("Temperature model (TEMP): Newton iter {}: "
494  "CNV(E): {:.1e}, EB: {:.1e}"),
495  iter, maxNorm, sumNorm);
496  OpmLog::debug(msg);
497  if (maxNorm < tolerance_cnv_energy && sumNorm < tolerance_energy_balance) {
498  const auto msg2 =
499  fmt::format(fmt::runtime("Temperature model (TEMP): Newton converged after {} iterations"),
500  iter);
501  OpmLog::debug(msg2);
502  return true;
503  }
504  return false;
505  }
506 
507  template< class LhsEval>
508  void computeStorageTerm(unsigned globI, LhsEval& storage)
509  {
510  const auto& intQuants = intQuants_[globI];
511  const auto& poro = getValue(simulator_.model().intensiveQuantities(globI, /*timeIdx*/ 0).porosity());
512  // accumulate the internal energy of the fluids
513  const auto& fs = intQuants.fluidStateTemp();
514  for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++ phaseIdx) {
515  if (!FluidSystem::phaseIsActive(phaseIdx)) {
516  continue;
517  }
518 
519  const auto& u = decay<LhsEval>(fs.internalEnergy(phaseIdx));
520  const auto& S = decay<LhsEval>(fs.saturation(phaseIdx));
521  const auto& rho = decay<LhsEval>(fs.density(phaseIdx));
522 
523  storage += poro*S*u*rho;
524  }
525 
526  // add the internal energy of the rock
527  const Scalar rockFraction = intQuants.rockFraction();
528  const auto& uRock = decay<LhsEval>(intQuants.rockInternalEnergy());
529  storage += rockFraction*uRock;
530  storage*= scalingFactor_;
531  }
532 
533  template <class RateVector>
534  void computeFluxTerm(const FluidStateTemp& fsIn,
535  const FluidStateTemp& fsEx,
536  const RateVector& darcyFlux,
537  Evaluation& flux)
538  {
539  for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++ phaseIdx) {
540  if (!FluidSystem::phaseIsActive(phaseIdx)) {
541  continue;
542  }
543 
544  const unsigned activeCompIdx =
545  FluidSystem::canonicalToActiveCompIdx(FluidSystem::solventComponentIndex(phaseIdx));
546  bool inIsUp = darcyFlux[activeCompIdx] > 0;
547  const auto& fs = inIsUp ? fsIn : fsEx;
548  if (inIsUp) {
549  flux += fs.enthalpy(phaseIdx)
550  * fs.density(phaseIdx)
551  * getValue(darcyFlux[activeCompIdx]);
552  }
553  else {
554  flux += getValue(fs.enthalpy(phaseIdx))
555  * getValue(fs.density(phaseIdx))
556  * getValue(darcyFlux[activeCompIdx]);
557  }
558  }
559  flux *= scalingFactor_;
560  }
561 
562  template <class ResidualNBInfo>
563  void computeHeatFluxTerm(const IntensiveQuantitiesTemp& intQuantsIn,
564  const IntensiveQuantitiesTemp& intQuantsEx,
565  const ResidualNBInfo& res_nbinfo,
566  Evaluation& heatFlux)
567  {
568  short interiorDofIdx = 0; // NB
569  short exteriorDofIdx = 1; // NB
570  BlackOilEnergyExtensiveQuantities<TypeTag, energyModuleType>::
571  updateEnergy(heatFlux,
572  interiorDofIdx, // focusDofIndex,
573  interiorDofIdx,
574  exteriorDofIdx,
575  intQuantsIn,
576  intQuantsEx,
577  intQuantsIn.fluidStateTemp(),
578  intQuantsEx.fluidStateTemp(),
579  res_nbinfo.inAlpha,
580  res_nbinfo.outAlpha,
581  res_nbinfo.faceArea);
582  heatFlux *= scalingFactor_*res_nbinfo.faceArea;
583  }
584 
585  void assembleEquations()
586  {
587  OPM_TIMEBLOCK(TemperatureModel_assembleEquations);
588 
589  const unsigned int numCells = simulator_.model().numTotalDof();
590  for (unsigned globI = 0; globI < numCells; ++globI) {
591  this->energyVector_[globI] = 0.0;
592  energyMatrix_->clearRow(globI, 0.0);
593  }
594  const Scalar dt = simulator_.timeStepSize();
595 
596  // Storage term
597  {
598  OPM_TIMEBLOCK(TemperatureModel_assembleEquations_storage);
599  #ifdef _OPENMP
600  #pragma omp parallel for
601  #endif
602  for (unsigned globI = 0; globI < numCells; ++globI) {
603  MatrixBlockTemp bMat;
604  Scalar volume = simulator_.model().dofTotalVolume(globI);
605  Scalar storefac = volume / dt;
606  Evaluation storage = 0.0;
607  computeStorageTerm(globI, storage);
608  this->energyVector_[globI] += storefac * ( getValue(storage) - storage1_[globI] );
609  bMat[0][0] = storefac * storage.derivative(temperatureIdx);
610  *diagMatAddress_[globI] += bMat;
611  }
612  }
613 
614  // Flux term
615  {
616  OPM_TIMEBLOCK(TemperatureModel_assembleEquations_flux);
617  const auto& floresInfo = this->simulator_.problem().model().linearizer().getFloresInfo();
618  const bool enableDriftCompensation = Parameters::Get<Parameters::EnableDriftCompensationTemp>();
619  const auto& problem = simulator_.problem();
620 
621  #ifdef _OPENMP
622  #pragma omp parallel for
623  #endif
624  for (unsigned globI = 0; globI < numCells; ++globI) {
625  const auto& nbInfos = neighborInfo_[globI];
626  const auto& floresInfos = floresInfo[globI];
627  int loc = 0;
628  const auto& intQuantsIn = intQuants_[globI];
629  MatrixBlockTemp bMat;
630  for (const auto& nbInfo : nbInfos) {
631  unsigned globJ = nbInfo.neighbor;
632  const auto& intQuantsEx = intQuants_[globJ];
633  assert(globJ != globI);
634  const auto& darcyflux = floresInfos[loc].flow;
635  // compute convective flux
636  Evaluation flux = 0.0;
637  computeFluxTerm(intQuantsIn.fluidStateTemp(), intQuantsEx.fluidStateTemp(), darcyflux, flux);
638  // compute conductive flux
639  Evaluation heatFlux = 0.0;
640  computeHeatFluxTerm(intQuantsIn, intQuantsEx, nbInfo.res_nbinfo, heatFlux);
641  heatFlux += flux;
642  this->energyVector_[globI] += getValue(heatFlux);
643  bMat[0][0] = heatFlux.derivative(temperatureIdx);
644  *diagMatAddress_[globI] += bMat;
645  bMat *= -1.0;
646  //SparseAdapter syntax: jacobian_->addToBlock(globJ, globI, bMat);
647  *nbInfo.matBlockAddress += bMat;
648  loc++;
649  }
650 
651  if (enableDriftCompensation) {
652  auto dofDriftRate = problem.drift()[globI]/dt;
653  const auto& fs = intQuantsIn.fluidStateTemp();
654  for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++ phaseIdx) {
655  const unsigned activeCompIdx =
656  FluidSystem::canonicalToActiveCompIdx(FluidSystem::solventComponentIndex(phaseIdx));
657  auto drift_hrate = dofDriftRate[activeCompIdx]*getValue(fs.enthalpy(phaseIdx)) * getValue(fs.density(phaseIdx)) / getValue(fs.invB(phaseIdx));
658  this->energyVector_[globI] -= drift_hrate*scalingFactor_;
659  }
660  }
661  }
662  }
663 
664  // Well terms
665  {
666  OPM_TIMEBLOCK(TemperatureModel_assembleEquations_wells);
667  const auto& wellPtrs = simulator_.problem().wellModel().localNonshutWells();
668  for (const auto& wellPtr : wellPtrs) {
669  this->assembleEquationWell(*wellPtr);
670  }
671  }
672 
673  // For standard ilu0 we need to set the overlapping cells to identity. But since we use "paroverilu0"
674  // here this is not needed.
675  // if (simulator_.gridView().comm().size() > 1) {
676  // // Set dirichlet conditions for overlapping cells
677  // // loop over precalculated overlap rows and columns
678  // for (const auto row : overlapRows_) {
679  // // Zero out row.
680  // (*this->energyMatrix_)[row] = 0.0;
681  //
682  // //diagonal block set to diag(1.0).
683  // (*this->energyMatrix_)[row][row][0][0] = 1.0;
684  // }
685  //}
686  }
687 
688  template<class Well>
689  void assembleEquationWell(const Well& well)
690  {
691  const auto& eclWell = well.wellEcl();
692  std::size_t well_index = simulator_.problem().wellModel().wellState().index(well.name()).value();
693  const auto& ws = simulator_.problem().wellModel().wellState().well(well_index);
694  this->energy_rates_[well_index] = 0.0;
695  MatrixBlockTemp bMat;
696  for (std::size_t i = 0; i < ws.perf_data.size(); ++i) {
697  const auto globI = ws.perf_data.cell_index[i];
698  auto fs = intQuants_[globI].fluidStateTemp(); //copy to make it possible to change the temp in the injector
699  for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++ phaseIdx) {
700  if (!FluidSystem::phaseIsActive(phaseIdx)) {
701  continue;
702  }
703 
704  Evaluation rate = well.volumetricSurfaceRateForConnection(globI, phaseIdx);
705  if (rate > 0 && eclWell.isInjector()) {
706  fs.setTemperature(eclWell.inj_temperature());
707  const auto& rho = FluidSystem::density(fs, phaseIdx, fs.pvtRegionIndex());
708  fs.setDensity(phaseIdx, rho);
709  const auto& h = FluidSystem::enthalpy(fs, phaseIdx, fs.pvtRegionIndex());
710  fs.setEnthalpy(phaseIdx, h);
711  rate *= getValue(fs.enthalpy(phaseIdx)) * getValue(fs.density(phaseIdx)) / getValue(fs.invB(phaseIdx));
712  } else {
713  const Evaluation d = 1.0 - fs.Rv() * fs.Rs();
714  if (phaseIdx == gasPhaseIdx && d > 0) {
715  const auto& oilrate = well.volumetricSurfaceRateForConnection(globI, oilPhaseIdx);
716  rate -= oilrate * getValue(fs.Rs());
717  rate /= d;
718  }
719  if (phaseIdx == oilPhaseIdx && d > 0) {
720  const auto& gasrate = well.volumetricSurfaceRateForConnection(globI, gasPhaseIdx);
721  rate -= gasrate * getValue(fs.Rv());
722  rate /= d;
723  }
724  rate *= fs.enthalpy(phaseIdx) * getValue(fs.density(phaseIdx)) / getValue(fs.invB(phaseIdx));
725  }
726  this->energy_rates_[well_index] += getValue(rate);
727  rate *= scalingFactor_;
728  this->energyVector_[globI] -= getValue(rate);
729  bMat[0][0] = -rate.derivative(temperatureIdx);
730  *diagMatAddress_[globI] += bMat;
731  }
732  }
733  }
734 
735  template<class Well, class SingleWellState>
736  void computeWellTemperature(const Well& well, SingleWellState& ws)
737  {
738  if (well.isInjector()) {
739  if (ws.status != WellStatus::STOP) {
740  ws.temperature = well.wellEcl().inj_temperature();
741  return;
742  }
743  }
744  const int np = simulator_.problem().wellModel().wellState().numPhases();
745  std::array<Scalar,2> weighted{0.0,0.0};
746  auto& [weighted_temperature, total_weight] = weighted;
747  for (std::size_t i = 0; i < ws.perf_data.size(); ++i) {
748  const auto globI = ws.perf_data.cell_index[i];
749  const auto& fs = intQuants_[globI].fluidStateTemp();
750  Scalar weight_factor = simulator_.problem().wellModel().computeTemperatureWeightFactor(i, np, fs, ws);
751  total_weight += weight_factor;
752  weighted_temperature += weight_factor * fs.temperature(/*phaseIdx*/0).value();
753  }
754  //simulator_.gridView().comm().sum(weighted.data(), 2);
755  ws.temperature = weighted_temperature / total_weight;
756  }
757 
758  const Simulator& simulator_;
759  EnergyVector storage1_;
760  std::vector<IntensiveQuantitiesTemp> intQuants_;
761  SparseTable<NeighborInfoCPU> neighborInfo_{};
762  std::vector<MatrixBlockTemp*> diagMatAddress_{};
763  std::unique_ptr<SpareMatrixEnergyAdapter> energyMatrix_;
764  std::vector<int> overlapRows_;
765  std::vector<int> interiorRows_;
766  Scalar scalingFactor_{1.0};
767 };
768 
769 // need for the old linearizer
770 template <class TypeTag>
771 class TemperatureModel<TypeTag, false>
772 {
776 
777 public:
778  TemperatureModel(Simulator&)
779  { }
780 
785  template <class Restarter>
786  void serialize(Restarter&)
787  { /* not implemented */ }
788 
795  template <class Restarter>
796  void deserialize(Restarter&)
797  { /* not implemented */ }
798 
799  void init() {}
800  void beginTimeStep() {}
801  const Scalar temperature(size_t /*globalIdx*/) const
802  {
803  return 273.15; // return 0C to make the compiler happy
804  }
805 };
806 
807 } // namespace Opm
808 
809 #endif // OPM_TEMPERATURE_MODEL_HPP
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
void serialize(Restarter &)
This method writes the complete state of all temperature to the hard disk.
Definition: TemperatureModel.hpp:786
void deserialize(Restarter &)
This method restores the complete state of the temperature from disk.
Definition: TemperatureModel.hpp:383
Definition: TemperatureModel.hpp:69
Definition: GenericTemperatureModel.hpp:52
a tag to mark properties as undefined
Definition: propertysystem.hh:38
void serialize(Restarter &)
This method writes the complete state of all temperature to the hard disk.
Definition: TemperatureModel.hpp:373
Definition: matrixblock.hh:228
The common code for the linearizers of non-linear systems of equations.
Structs needed for tpfalinearizer and its gpuparams struct extracted to be defined in one place that ...
Definition: blackoilbioeffectsmodules.hh:45
A class which handles sequential implicit solution of the energy equation as specified in by TEMP...
Definition: TemperatureModel.hpp:180
Definition: blackoilmodules.hpp:62
void endTimeStep(WellStateType &wellState)
Informs the temperature model that a time step has just been finished.
Definition: TemperatureModel.hpp:334
Provides data handles for parallel communication which operate on DOFs.
A sparse matrix interface backend for BCRSMatrix from dune-istl.
A class which handles sequential implicit solution of the energy equation as specified in by TEMP...
The Opm property system, traits with inheritance.
Definition: tpfalinearizerstructs.hh:65
A sparse matrix interface backend for BCRSMatrix from dune-istl.
Definition: istlsparsematrixadapter.hh:42
void deserialize(Restarter &)
This method restores the complete state of the temperature from disk.
Definition: TemperatureModel.hpp:796
Definition: blackoilmodel.hh:75
The state of a set of wells, tailored for use by the fully implicit blackoil simulator.
Definition: TemperatureModel.hpp:65
Definition: TemperatureModel.hpp:57