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