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
39
40#include <algorithm>
41#include <cassert>
42#include <cmath>
43#include <cstddef>
44#include <string>
45#include <vector>
46
47namespace Opm::Properties {
48
49template<class TypeTag, class MyTypeTag>
52};
53
54} // namespace Opm::Properties
55
56namespace Opm {
57
58template<typename Scalar, typename IndexTraits> class WellState;
59
65template <class TypeTag, bool enableTempV = getPropValue<TypeTag, Properties::EnergyModuleType>() == EnergyModules::SequentialImplicitThermal >
66class TemperatureModel : public GenericTemperatureModel<GetPropType<TypeTag, Properties::Grid>,
67 GetPropType<TypeTag, Properties::GridView>,
68 GetPropType<TypeTag, Properties::DofMapper>,
69 GetPropType<TypeTag, Properties::Stencil>,
70 GetPropType<TypeTag, Properties::FluidSystem>,
71 GetPropType<TypeTag, Properties::Scalar>>
72{
88 using TemperatureEvaluation = DenseAd::Evaluation<Scalar,1>;
93 using IndexTraits = typename FluidSystem::IndexTraitsType;
95
96 using EnergyMatrix = typename BaseType::EnergyMatrix;
97 using EnergyVector = typename BaseType::EnergyVector;
98
99 enum { numEq = getPropValue<TypeTag, Properties::NumEq>() };
100 enum { numPhases = FluidSystem::numPhases };
101 enum { waterPhaseIdx = FluidSystem::waterPhaseIdx };
102 enum { oilPhaseIdx = FluidSystem::oilPhaseIdx };
103 enum { gasPhaseIdx = FluidSystem::gasPhaseIdx };
104
105public:
106 explicit TemperatureModel(Simulator& simulator)
107 : BaseType(simulator.vanguard().gridView(),
108 simulator.vanguard().eclState(),
109 simulator.vanguard().cartesianIndexMapper(),
110 simulator.model().dofMapper())
111 , simulator_(simulator)
112 {}
113
114 void init()
115 {
116 const unsigned int numCells = simulator_.model().numTotalDof();
117 this->doInit(numCells);
118
119 if (!this->doTemp())
120 return;
121
122 // we need the storage term at start of the iteration (timeIdx = 1)
123 storage1_.resize(numCells);
124
125 // set the initial temperature
126 for (unsigned globI = 0; globI < numCells; ++globI) {
127 this->temperature_[globI] = simulator_.problem().initialFluidState(globI).temperature(0);
128 }
129 // keep a copy of the intensive quantities to simplify the update during
130 // the newton iterations
131 intQuants_.resize(numCells);
132
133 // find and store the overlap cells
134 const auto& elemMapper = simulator_.model().elementMapper();
136 }
137
139 {
140 if (!this->doTemp()) {
141 return;
142 }
143
144 // We copy the intensive quantities here to make it possible to update them
145 const unsigned int numCells = simulator_.model().numTotalDof();
146 for (unsigned globI = 0; globI < numCells; ++globI) {
147 intQuants_[globI] = simulator_.model().intensiveQuantities(globI, /*timeIdx*/ 0);
148 intQuants_[globI].updateTemperature_(simulator_.problem(), globI, /*timeIdx*/ 0);
149 intQuants_[globI].updateEnergyQuantities_(simulator_.problem(), globI, /*timeIdx*/ 0);
150 }
152
153 const int nw = simulator_.problem().wellModel().wellState().numWells();
154 this->energy_rates_.resize(nw, 0.0);
155 }
156
160 void endTimeStep(WellStateType& wellState)
161 {
162 if (!this->doTemp()) {
163 return;
164 }
165
166 // We copy the intensive quantities here to make it possible to update them
167 const unsigned int numCells = simulator_.model().numTotalDof();
168 for (unsigned globI = 0; globI < numCells; ++globI) {
169 intQuants_[globI] = simulator_.model().intensiveQuantities(globI, /*timeIdx*/ 0);
170 intQuants_[globI].updateTemperature_(simulator_.problem(), globI, /*timeIdx*/ 0);
171 intQuants_[globI].updateEnergyQuantities_(simulator_.problem(), globI, /*timeIdx*/ 0);
172 }
174
175 // update energy_rates
176 const int nw = wellState.numWells();
177 for (auto wellID = 0*nw; wellID < nw; ++wellID) {
178 auto& ws = wellState.well(wellID);
179 ws.energy_rate = this->energy_rates_[wellID];
180 }
181 }
182
187 template <class Restarter>
188 void serialize(Restarter&)
189 { /* not implemented */ }
190
197 template <class Restarter>
198 void deserialize(Restarter&)
199 { /* not implemented */ }
200
201protected:
203 {
204 // we need the storage term at start of the iteration (timeIdx = 1)
205 const unsigned int numCells = simulator_.model().numTotalDof();
206 #ifdef _OPENMP
207 #pragma omp parallel for
208 #endif
209 for (unsigned globI = 0; globI < numCells; ++globI) {
210 Scalar storage = 0.0;
211 computeStorageTerm(globI, storage);
212 storage1_[globI] = storage;
213 }
214 }
215
217 {
218 const int max_iter = 20;
219 const int min_iter = 1;
220 // solve using Newton
221 for (int iter = 0; iter < max_iter; ++iter) {
223 if (iter >= min_iter && converged(iter)) {
224 break;
225 }
227 }
228 }
229
231 {
232 const unsigned int numCells = simulator_.model().numTotalDof();
233 EnergyVector dx(numCells);
234 bool conv = this->linearSolve_(*this->energyMatrix_, dx, this->energyVector_);
235 if (!conv) {
236 if (simulator_.gridView().comm().rank() == 0) {
237 OpmLog::warning("Temp model: Linear solver did not converge. Temperature values not updated.");
238 }
239 }
240 else {
241 for (unsigned globI = 0; globI < numCells; ++globI) {
242 this->temperature_[globI] -= std::clamp(dx[globI][0], -this->maxTempChange_, this->maxTempChange_);
243 intQuants_[globI].updateTemperature_(simulator_.problem(), globI, /*timeIdx*/ 0);
244 intQuants_[globI].updateEnergyQuantities_(simulator_.problem(), globI, /*timeIdx*/ 0);
245 }
246 }
247 }
248
249 bool converged(const int iter)
250 {
251 const unsigned int numCells = simulator_.model().numTotalDof();
252 Scalar maxNorm = 0.0;
253 Scalar sumNorm = 0.0;
254 const auto& elemMapper = simulator_.model().elementMapper();
255 for (const auto& elem : elements(simulator_.gridView(), Dune::Partitions::interior)) {
256 unsigned globI = elemMapper.index(elem);
257 maxNorm = max(maxNorm, std::abs(this->energyVector_[globI]));
258 sumNorm += std::abs(this->energyVector_[globI]);
259 }
260 maxNorm = simulator_.gridView().comm().max(maxNorm);
261 sumNorm = simulator_.gridView().comm().sum(sumNorm);
262 const int globalNumCells = simulator_.gridView().comm().sum(numCells);
263 sumNorm /= globalNumCells;
264 const auto tolerance_cnv_energy = Parameters::Get<Parameters::ToleranceCnvEnergy<Scalar>>();
265 const auto tolerance_energy_balance = Parameters::Get<Parameters::ToleranceEnergyBalance<Scalar>>();
266 if (maxNorm < tolerance_cnv_energy || sumNorm < tolerance_energy_balance) {
267 const auto msg = fmt::format("Temperature model (TEMP): Newton converged after {} iterations", iter);
268 OpmLog::debug(msg);
269 return true;
270 }
271 return false;
272 }
273
274 template< class LhsEval>
275 void computeStorageTerm(unsigned globI, LhsEval& storage)
276 {
277 const auto& intQuants = intQuants_[globI];
278 const auto& poro = decay<LhsEval>(intQuants.porosity());
279 // accumulate the internal energy of the fluids
280 const auto& fs = intQuants.fluidState();
281 for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++ phaseIdx) {
282 if (!FluidSystem::phaseIsActive(phaseIdx)) {
283 continue;
284 }
285
286 const auto& u = decay<LhsEval>(fs.internalEnergy(phaseIdx));
287 const auto& S = decay<LhsEval>(fs.saturation(phaseIdx));
288 const auto& rho = decay<LhsEval>(fs.density(phaseIdx));
289
290 storage += poro*S*u*rho;
291 }
292
293 // add the internal energy of the rock
294 const Scalar rockFraction = intQuants.rockFraction();
295 const auto& uRock = decay<LhsEval>(intQuants.rockInternalEnergy());
296 storage += rockFraction*uRock;
297 storage*= getPropValue<TypeTag, Properties::BlackOilEnergyScalingFactor>();
298 }
299
300 template < class ResidualNBInfo>
301 void computeFluxTerm(unsigned globI, unsigned globJ,
302 const ResidualNBInfo& res_nbinfo,
303 Evaluation& flux)
304 {
305 const IntensiveQuantities& intQuantsIn = intQuants_[globI];
306 const IntensiveQuantities& intQuantsEx = intQuants_[globJ];
307 RateVector tmp(0.0); //not used
308 RateVector darcyFlux(0.0);
309 LocalResidual::computeFlux(tmp, darcyFlux, globI, globJ, intQuantsIn, intQuantsEx,
310 res_nbinfo, simulator_.problem().moduleParams());
311 for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++ phaseIdx) {
312 if (!FluidSystem::phaseIsActive(phaseIdx)) {
313 continue;
314 }
315
316 const unsigned activeCompIdx =
317 FluidSystem::canonicalToActiveCompIdx(FluidSystem::solventComponentIndex(phaseIdx));
318
319 bool inIsUp = darcyFlux[activeCompIdx] > 0;
320 const IntensiveQuantities& up = inIsUp ? intQuantsIn : intQuantsEx;
321 const auto& fs = up.fluidState();
322 if (inIsUp) {
323 flux += fs.enthalpy(phaseIdx)
324 * fs.density(phaseIdx)
325 * darcyFlux[activeCompIdx];
326 }
327 else {
328 flux += getValue(fs.enthalpy(phaseIdx))
329 * getValue(fs.density(phaseIdx))
330 * getValue(darcyFlux[activeCompIdx]);
331 }
332 }
333 flux *= getPropValue<TypeTag, Properties::BlackOilEnergyScalingFactor>();
334 }
335
336 template < class ResidualNBInfo>
337 void computeHeatFluxTerm(unsigned globI, unsigned globJ,
338 const ResidualNBInfo& res_nbinfo,
339 Evaluation& heatFlux)
340 {
341 const IntensiveQuantities& intQuantsIn = intQuants_[globI];
342 const IntensiveQuantities& intQuantsEx = intQuants_[globJ];
343 const Scalar inAlpha = simulator_.problem().thermalHalfTransmissibility(globI, globJ);
344 const Scalar outAlpha = simulator_.problem().thermalHalfTransmissibility(globJ, globI);
345 short interiorDofIdx = 0; // NB
346 short exteriorDofIdx = 1; // NB
347 EnergyModule::ExtensiveQuantities::updateEnergy(heatFlux,
348 interiorDofIdx, // focusDofIndex,
349 interiorDofIdx,
350 exteriorDofIdx,
351 intQuantsIn,
352 intQuantsEx,
353 intQuantsIn.fluidState(),
354 intQuantsEx.fluidState(),
355 inAlpha,
356 outAlpha,
357 res_nbinfo.faceArea);
358 heatFlux *= getPropValue<TypeTag, Properties::BlackOilEnergyScalingFactor>()*res_nbinfo.faceArea;
359 }
360
362 {
363 this->energyVector_ = 0.0;
364 (*this->energyMatrix_) = 0.0;
365 Scalar dt = simulator_.timeStepSize();
366 const unsigned int numCells = simulator_.model().numTotalDof();
367#ifdef _OPENMP
368#pragma omp parallel for
369#endif
370 for (unsigned globI = 0; globI < numCells; ++globI) {
371 Scalar volume = simulator_.model().dofTotalVolume(globI);
372 Scalar storefac = volume / dt;
373 Evaluation storage = 0.0;
374 computeStorageTerm(globI, storage);
375 this->energyVector_[globI] += storefac * ( getValue(storage) - storage1_[globI] );
376 (*this->energyMatrix_)[globI][globI][0][0] += storefac * storage.derivative(Indices::temperatureIdx);
377 }
378
379 const auto& neighborInfo = simulator_.model().linearizer().getNeighborInfo();
380#ifdef _OPENMP
381#pragma omp parallel for
382#endif
383 for (unsigned globI = 0; globI < numCells; ++globI) {
384 const auto& nbInfos = neighborInfo[globI];
385 for (const auto& nbInfo : nbInfos) {
386 unsigned globJ = nbInfo.neighbor;
387 assert(globJ != globI);
388
389 // compute convective flux
390 Evaluation flux = 0.0;
391 computeFluxTerm(globI, globJ, nbInfo.res_nbinfo, flux);
392 this->energyVector_[globI] += getValue(flux);
393 (*this->energyMatrix_)[globI][globI][0][0] += flux.derivative(Indices::temperatureIdx);
394 (*this->energyMatrix_)[globJ][globI][0][0] -= flux.derivative(Indices::temperatureIdx);
395
396 // compute conductive flux
397 Evaluation heatFlux = 0.0;
398 computeHeatFluxTerm(globI, globJ, nbInfo.res_nbinfo, heatFlux);
399 this->energyVector_[globI] += getValue(heatFlux);
400 (*this->energyMatrix_)[globI][globI][0][0] += heatFlux.derivative(Indices::temperatureIdx);
401 (*this->energyMatrix_)[globJ][globI][0][0] -= heatFlux.derivative(Indices::temperatureIdx);
402 }
403 }
404
405 // Well terms
406 const auto& wellPtrs = simulator_.problem().wellModel().localNonshutWells();
407 for (const auto& wellPtr : wellPtrs) {
408 this->assembleEquationWell(*wellPtr);
409 }
410
411 if (simulator_.gridView().comm().size() > 1) {
412 // Set dirichlet conditions for overlapping cells
413 // loop over precalculated overlap rows and columns
414 for (const auto row : overlapRows_) {
415 // Zero out row.
416 (*this->energyMatrix_)[row] = 0.0;
417
418 //diagonal block set to diag(1.0).
419 (*this->energyMatrix_)[row][row][0][0] = 1.0;
420 }
421 }
422 }
423
424 template<class Well>
425 void assembleEquationWell(const Well& well)
426 {
427 const auto& eclWell = well.wellEcl();
428 std::size_t well_index = simulator_.problem().wellModel().wellState().index(well.name()).value();
429 const auto& ws = simulator_.problem().wellModel().wellState().well(well_index);
430 this->energy_rates_[well_index] = 0.0;
431 for (std::size_t i = 0; i < ws.perf_data.size(); ++i) {
432 const auto globI = ws.perf_data.cell_index[i];
433 auto fs = intQuants_[globI].fluidState(); //copy to make it possible to change the temp in the injector
434 for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++ phaseIdx) {
435 if (!FluidSystem::phaseIsActive(phaseIdx)) {
436 continue;
437 }
438
439 Evaluation rate = well.volumetricSurfaceRateForConnection(globI, phaseIdx);
440 if (rate > 0 && eclWell.isInjector()) {
441 fs.setTemperature(eclWell.inj_temperature());
442 const auto& rho = FluidSystem::density(fs, phaseIdx, fs.pvtRegionIndex());
443 fs.setDensity(phaseIdx, rho);
444 const auto& h = FluidSystem::enthalpy(fs, phaseIdx, fs.pvtRegionIndex());
445 fs.setEnthalpy(phaseIdx, h);
446 rate *= getValue(fs.enthalpy(phaseIdx)) * getValue(fs.density(phaseIdx)) / getValue(fs.invB(phaseIdx));
447 } else {
448 const Evaluation d = 1.0 - fs.Rv() * fs.Rs();
449 if (phaseIdx == gasPhaseIdx && d > 0) {
450 const auto& oilrate = well.volumetricSurfaceRateForConnection(globI, oilPhaseIdx);
451 rate -= oilrate * getValue(fs.Rs());
452 rate /= d;
453 }
454 if (phaseIdx == oilPhaseIdx && d > 0) {
455 const auto& gasrate = well.volumetricSurfaceRateForConnection(globI, gasPhaseIdx);
456 rate -= gasrate * getValue(fs.Rv());
457 rate /= d;
458 }
459 rate *= fs.enthalpy(phaseIdx) * getValue(fs.density(phaseIdx)) / getValue(fs.invB(phaseIdx));
460 }
461 this->energy_rates_[well_index] += getValue(rate);
462 rate *= getPropValue<TypeTag, Properties::BlackOilEnergyScalingFactor>();
463 this->energyVector_[globI] -= getValue(rate);
464 (*this->energyMatrix_)[globI][globI][0][0] -= rate.derivative(Indices::temperatureIdx);
465 }
466 }
467 }
468
469 const Simulator& simulator_;
470 EnergyVector storage1_;
471 std::vector<IntensiveQuantities> intQuants_;
472 std::vector<int> overlapRows_;
473 std::vector<int> interiorRows_;
474};
475
476// need for the old linearizer
477template <class TypeTag>
478class TemperatureModel<TypeTag, false>
479{
483
484public:
486 { }
487
492 template <class Restarter>
493 void serialize(Restarter&)
494 { /* not implemented */ }
495
502 template <class Restarter>
503 void deserialize(Restarter&)
504 { /* not implemented */ }
505
506 void init() {}
508 const Scalar temperature(size_t /*globalIdx*/) const
509 {
510 return 273.15; // return 0C to make the compiler happy
511 }
512};
513
514} // namespace Opm
515
516#endif // OPM_TEMPERATURE_MODEL_HPP
Contains the high level supplements required to extend the black oil model by energy.
Definition: blackoilenergymodules.hh:60
Definition: GenericTemperatureModel.hpp:52
void beginTimeStep()
Definition: TemperatureModel.hpp:507
void init()
Definition: TemperatureModel.hpp:506
const Scalar temperature(size_t) const
Definition: TemperatureModel.hpp:508
void deserialize(Restarter &)
This method restores the complete state of the temperature from disk.
Definition: TemperatureModel.hpp:503
void serialize(Restarter &)
This method writes the complete state of all temperature to the hard disk.
Definition: TemperatureModel.hpp:493
TemperatureModel(Simulator &)
Definition: TemperatureModel.hpp:485
A class which handles sequential implicit solution of the energy equation as specified in by TEMP.
Definition: TemperatureModel.hpp:72
void serialize(Restarter &)
This method writes the complete state of all temperature to the hard disk.
Definition: TemperatureModel.hpp:188
void assembleEquationWell(const Well &well)
Definition: TemperatureModel.hpp:425
std::vector< int > interiorRows_
Definition: TemperatureModel.hpp:473
EnergyVector storage1_
Definition: TemperatureModel.hpp:470
bool converged(const int iter)
Definition: TemperatureModel.hpp:249
void computeHeatFluxTerm(unsigned globI, unsigned globJ, const ResidualNBInfo &res_nbinfo, Evaluation &heatFlux)
Definition: TemperatureModel.hpp:337
void computeStorageTerm(unsigned globI, LhsEval &storage)
Definition: TemperatureModel.hpp:275
void deserialize(Restarter &)
This method restores the complete state of the temperature from disk.
Definition: TemperatureModel.hpp:198
void endTimeStep(WellStateType &wellState)
Informs the temperature model that a time step has just been finished.
Definition: TemperatureModel.hpp:160
const Simulator & simulator_
Definition: TemperatureModel.hpp:469
void beginTimeStep()
Definition: TemperatureModel.hpp:138
void advanceTemperatureFields()
Definition: TemperatureModel.hpp:216
void updateStorageCache()
Definition: TemperatureModel.hpp:202
void assembleEquations()
Definition: TemperatureModel.hpp:361
void solveAndUpdate()
Definition: TemperatureModel.hpp:230
std::vector< IntensiveQuantities > intQuants_
Definition: TemperatureModel.hpp:471
void computeFluxTerm(unsigned globI, unsigned globJ, const ResidualNBInfo &res_nbinfo, Evaluation &flux)
Definition: TemperatureModel.hpp:301
std::vector< int > overlapRows_
Definition: TemperatureModel.hpp:472
TemperatureModel(Simulator &simulator)
Definition: TemperatureModel.hpp:106
void init()
Definition: TemperatureModel.hpp:114
Definition: WellState.hpp:66
int numWells() const
Definition: WellState.hpp:99
const SingleWellState< Scalar, IndexTraits > & well(std::size_t well_index) const
Definition: WellState.hpp:290
Provides data handles for parallel communication which operate on DOFs.
Definition: blackoilmodel.hh:80
void findOverlapAndInterior(const Grid &grid, const Mapper &mapper, std::vector< int > &overlapRows, std::vector< int > &interiorRows)
Find the rows corresponding to overlap cells.
Definition: findOverlapRowsAndColumns.hpp:92
Definition: blackoilbioeffectsmodules.hh:43
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
The Opm property system, traits with inheritance.
Definition: TemperatureModel.hpp:50
a tag to mark properties as undefined
Definition: propertysystem.hh:38