TracerModel.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_TRACER_MODEL_HPP
29#define OPM_TRACER_MODEL_HPP
30
31#include <opm/common/OpmLog/OpmLog.hpp>
32
34
37
38#include <array>
39#include <cstddef>
40#include <memory>
41#include <stdexcept>
42#include <string>
43#include <vector>
44
45namespace Opm::Properties {
46
47template<class TypeTag, class MyTypeTag>
50};
51
52} // namespace Opm::Properties
53
54namespace Opm {
55
61template <class TypeTag>
62class TracerModel : public GenericTracerModel<GetPropType<TypeTag, Properties::Grid>,
63 GetPropType<TypeTag, Properties::GridView>,
64 GetPropType<TypeTag, Properties::DofMapper>,
65 GetPropType<TypeTag, Properties::Stencil>,
66 GetPropType<TypeTag, Properties::FluidSystem>,
67 GetPropType<TypeTag, Properties::Scalar>>
68{
84
85 using TracerEvaluation = DenseAd::Evaluation<Scalar,1>;
86
87 using TracerMatrix = typename BaseType::TracerMatrix;
88 using TracerVector = typename BaseType::TracerVector;
89
90 enum { numEq = getPropValue<TypeTag, Properties::NumEq>() };
91 enum { numPhases = FluidSystem::numPhases };
92 enum { waterPhaseIdx = FluidSystem::waterPhaseIdx };
93 enum { oilPhaseIdx = FluidSystem::oilPhaseIdx };
94 enum { gasPhaseIdx = FluidSystem::gasPhaseIdx };
95
96public:
97 TracerModel(Simulator& simulator)
98 : BaseType(simulator.vanguard().gridView(),
99 simulator.vanguard().eclState(),
100 simulator.vanguard().cartesianIndexMapper(),
101 simulator.model().dofMapper(),
102 simulator.vanguard().cellCentroids())
103 , simulator_(simulator)
104 , tbatch({waterPhaseIdx, oilPhaseIdx, gasPhaseIdx})
105 , wat_(tbatch[0])
106 , oil_(tbatch[1])
107 , gas_(tbatch[2])
108 { }
109
110
111 /*
112 The initialization of the tracer model is a three step process:
113
114 1. The init() method is called. This will allocate buffers and initialize
115 some phase index stuff. If this is a normal run the initial tracer
116 concentrations will be assigned from the TBLK or TVDPF keywords.
117
118 2. [Restart only:] The tracer concentration are read from the restart
119 file and the concentrations are applied with repeated calls to the
120 setTracerConcentration() method. This is currently done in the
121 eclwriter::beginRestart() method.
122
123 3. Internally the tracer model manages the concentrations in "batches" for
124 the oil, water and gas tracers respectively. The batches should be
125 initialized with the initial concentration, that must be performed
126 after the concentration values have been assigned. This is done in
127 method prepareTracerBatches() called from eclproblem::finishInit().
128 */
129 void init(bool rst)
130 {
131 this->doInit(rst, simulator_.model().numGridDof(),
132 gasPhaseIdx, oilPhaseIdx, waterPhaseIdx);
133 }
134
136 {
137 for (std::size_t tracerIdx = 0; tracerIdx < this->tracerPhaseIdx_.size(); ++tracerIdx) {
138 if (this->tracerPhaseIdx_[tracerIdx] == FluidSystem::waterPhaseIdx) {
139 if (! FluidSystem::phaseIsActive(FluidSystem::waterPhaseIdx)){
140 throw std::runtime_error("Water tracer specified for non-water fluid system:" + this->name(tracerIdx));
141 }
142
143 wat_.addTracer(tracerIdx, this->tracerConcentration_[tracerIdx]);
144 }
145 else if (this->tracerPhaseIdx_[tracerIdx] == FluidSystem::oilPhaseIdx) {
146 if (! FluidSystem::phaseIsActive(FluidSystem::oilPhaseIdx)){
147 throw std::runtime_error("Oil tracer specified for non-oil fluid system:" + this->name(tracerIdx));
148 }
149
150 oil_.addTracer(tracerIdx, this->tracerConcentration_[tracerIdx]);
151 }
152 else if (this->tracerPhaseIdx_[tracerIdx] == FluidSystem::gasPhaseIdx) {
153 if (! FluidSystem::phaseIsActive(FluidSystem::gasPhaseIdx)){
154 throw std::runtime_error("Gas tracer specified for non-gas fluid system:" + this->name(tracerIdx));
155 }
156
157 gas_.addTracer(tracerIdx, this->tracerConcentration_[tracerIdx]);
158 }
159
160 // resize free and solution volume storages
161 fVol1_[this->tracerPhaseIdx_[tracerIdx]].resize(this->freeTracerConcentration_[tracerIdx].size());
162 sVol1_[this->tracerPhaseIdx_[tracerIdx]].resize(this->solTracerConcentration_[tracerIdx].size());
163 dsVol_[this->tracerPhaseIdx_[tracerIdx]].resize(this->solTracerConcentration_[tracerIdx].size());
164 dfVol_[this->tracerPhaseIdx_[tracerIdx]].resize(this->solTracerConcentration_[tracerIdx].size());
165 }
166
167 // will be valid after we move out of tracerMatrix_
168 TracerMatrix* base = this->tracerMatrix_.get();
169 for (auto& tr : this->tbatch) {
170 if (tr.numTracer() != 0) {
171 if (this->tracerMatrix_)
172 tr.mat = std::move(this->tracerMatrix_);
173 else
174 tr.mat = std::make_unique<TracerMatrix>(*base);
175 }
176 }
177 }
178
180 {
181 if (this->numTracers() == 0)
182 return;
183
185 }
186
191 {
192 if (this->numTracers() == 0)
193 return;
194
196 }
197
202 template <class Restarter>
203 void serialize(Restarter&)
204 { /* not implemented */ }
205
212 template <class Restarter>
213 void deserialize(Restarter&)
214 { /* not implemented */ }
215
216 template<class Serializer>
217 void serializeOp(Serializer& serializer)
218 {
219 serializer(static_cast<BaseType&>(*this));
220 serializer(tbatch);
221 }
222
223protected:
224
225 // compute volume associated with free concentration
226 Scalar computeFreeVolume_(const int tracerPhaseIdx,
227 unsigned globalDofIdx,
228 unsigned timeIdx)
229 {
230 const auto& intQuants = simulator_.model().intensiveQuantities(globalDofIdx, timeIdx);
231 const auto& fs = intQuants.fluidState();
232
233 Scalar phaseVolume =
234 decay<Scalar>(fs.saturation(tracerPhaseIdx))
235 *decay<Scalar>(fs.invB(tracerPhaseIdx))
236 *decay<Scalar>(intQuants.porosity());
237
238 return max(phaseVolume, 1e-10);
239 }
240
241 // compute volume associated with solution concentration
242 Scalar computeSolutionVolume_(const int tracerPhaseIdx,
243 unsigned globalDofIdx,
244 unsigned timeIdx)
245 {
246 const auto& intQuants = simulator_.model().intensiveQuantities(globalDofIdx, timeIdx);
247 const auto& fs = intQuants.fluidState();
248
249 Scalar phaseVolume;
250
251 // vaporized oil
252 if (tracerPhaseIdx == FluidSystem::oilPhaseIdx && FluidSystem::enableVaporizedOil()) {
253 phaseVolume =
254 decay<Scalar>(fs.saturation(FluidSystem::gasPhaseIdx))
255 * decay<Scalar>(fs.invB(FluidSystem::gasPhaseIdx))
256 * decay<Scalar>(fs.Rv())
257 * decay<Scalar>(intQuants.porosity());
258 }
259
260 // dissolved gas
261 else if (tracerPhaseIdx == FluidSystem::gasPhaseIdx && FluidSystem::enableDissolvedGas()) {
262 phaseVolume =
263 decay<Scalar>(fs.saturation(FluidSystem::oilPhaseIdx))
264 * decay<Scalar>(fs.invB(FluidSystem::oilPhaseIdx))
265 * decay<Scalar>(fs.Rs())
266 * decay<Scalar>(intQuants.porosity());
267 }
268 else {
269 phaseVolume = 0.0;
270 }
271
272 return max(phaseVolume, 1e-10);
273
274 }
275
276 void computeFreeFlux_(TracerEvaluation & freeFlux,
277 bool & isUp,
278 const int tracerPhaseIdx,
279 const ElementContext& elemCtx,
280 unsigned scvfIdx,
281 unsigned timeIdx)
282 {
283 const auto& stencil = elemCtx.stencil(timeIdx);
284 const auto& scvf = stencil.interiorFace(scvfIdx);
285
286 const auto& extQuants = elemCtx.extensiveQuantities(scvfIdx, timeIdx);
287 unsigned inIdx = extQuants.interiorIndex();
288
289 unsigned upIdx = extQuants.upstreamIndex(tracerPhaseIdx);
290
291 const auto& intQuants = elemCtx.intensiveQuantities(upIdx, timeIdx);
292 const auto& fs = intQuants.fluidState();
293
294 Scalar v =
295 decay<Scalar>(extQuants.volumeFlux(tracerPhaseIdx))
296 * decay<Scalar>(fs.invB(tracerPhaseIdx));
297
298 Scalar A = scvf.area();
299 if (inIdx == upIdx) {
300 freeFlux = A*v*variable<TracerEvaluation>(1.0, 0);
301 isUp = true;
302 }
303 else {
304 freeFlux = A*v;
305 isUp = false;
306 }
307 }
308
309 void computeSolFlux_(TracerEvaluation & solFlux,
310 bool & isUp,
311 const int tracerPhaseIdx,
312 const ElementContext& elemCtx,
313 unsigned scvfIdx,
314 unsigned timeIdx)
315 {
316 const auto& stencil = elemCtx.stencil(timeIdx);
317 const auto& scvf = stencil.interiorFace(scvfIdx);
318
319 const auto& extQuants = elemCtx.extensiveQuantities(scvfIdx, timeIdx);
320 unsigned inIdx = extQuants.interiorIndex();
321
322 Scalar v;
323 unsigned upIdx;
324
325 // vaporized oil
326 if (tracerPhaseIdx == FluidSystem::oilPhaseIdx && FluidSystem::enableVaporizedOil()) {
327 upIdx = extQuants.upstreamIndex(FluidSystem::gasPhaseIdx);
328
329 const auto& intQuants = elemCtx.intensiveQuantities(upIdx, timeIdx);
330 const auto& fs = intQuants.fluidState();
331 v =
332 decay<Scalar>(fs.invB(FluidSystem::gasPhaseIdx))
333 * decay<Scalar>(extQuants.volumeFlux(FluidSystem::gasPhaseIdx))
334 * decay<Scalar>(fs.Rv());
335 }
336 // dissolved gas
337 else if (tracerPhaseIdx == FluidSystem::gasPhaseIdx && FluidSystem::enableDissolvedGas()) {
338 upIdx = extQuants.upstreamIndex(FluidSystem::oilPhaseIdx);
339
340 const auto& intQuants = elemCtx.intensiveQuantities(upIdx, timeIdx);
341 const auto& fs = intQuants.fluidState();
342 v =
343 decay<Scalar>(fs.invB(FluidSystem::oilPhaseIdx))
344 * decay<Scalar>(extQuants.volumeFlux(FluidSystem::oilPhaseIdx))
345 * decay<Scalar>(fs.Rs());
346 }
347 else {
348 upIdx = 0;
349 v = 0.0;
350 }
351
352 Scalar A = scvf.area();
353 if (inIdx == upIdx) {
354 solFlux = A*v*variable<TracerEvaluation>(1.0, 0);
355 isUp = true;
356 }
357 else {
358 solFlux = A*v;
359 isUp = false;
360 }
361 }
362
363 template<class TrRe>
365 const ElementContext& elemCtx,
366 const Scalar scvVolume,
367 const Scalar dt,
368 unsigned I,
369 unsigned I1)
370
371 {
372 if (tr.numTracer() == 0)
373 return;
374
375 // Storage terms at previous time step (timeIdx = 1)
376 std::vector<Scalar> storageOfTimeIndex1(tr.numTracer());
377 std::vector<Scalar> fStorageOfTimeIndex1(tr.numTracer());
378 std::vector<Scalar> sStorageOfTimeIndex1(tr.numTracer());
379 if (elemCtx.enableStorageCache()) {
380 for (int tIdx = 0; tIdx < tr.numTracer(); ++tIdx) {
381 fStorageOfTimeIndex1[tIdx] = tr.storageOfTimeIndex1_[tIdx][I][0];
382 sStorageOfTimeIndex1[tIdx] = tr.storageOfTimeIndex1_[tIdx][I][1];
383 }
384 }
385 else {
386 Scalar fVolume1 = computeFreeVolume_(tr.phaseIdx_, I1, 1);
387 Scalar sVolume1 = computeSolutionVolume_(tr.phaseIdx_, I1, 1);
388 for (int tIdx = 0; tIdx < tr.numTracer(); ++tIdx) {
389 fStorageOfTimeIndex1[tIdx] = fVolume1 * tr.concentration_[tIdx][I1][0];
390 sStorageOfTimeIndex1[tIdx] = sVolume1 * tr.concentration_[tIdx][I1][1];
391 }
392 }
393
394 TracerEvaluation fVol = computeFreeVolume_(tr.phaseIdx_, I, 0) * variable<TracerEvaluation>(1.0, 0);
395 TracerEvaluation sVol = computeSolutionVolume_(tr.phaseIdx_, I, 0) * variable<TracerEvaluation>(1.0, 0);
396 dsVol_[tr.phaseIdx_][I] += sVol.value() * scvVolume - sVol1_[tr.phaseIdx_][I];
397 dfVol_[tr.phaseIdx_][I] += fVol.value() * scvVolume - fVol1_[tr.phaseIdx_][I];
398 for (int tIdx = 0; tIdx < tr.numTracer(); ++tIdx) {
399 // Free part
400 Scalar fStorageOfTimeIndex0 = fVol.value() * tr.concentration_[tIdx][I][0];
401 Scalar fLocalStorage = (fStorageOfTimeIndex0 - fStorageOfTimeIndex1[tIdx]) * scvVolume/dt;
402 tr.residual_[tIdx][I][0] += fLocalStorage; //residual + flux
403
404 // Solution part
405 Scalar sStorageOfTimeIndex0 = sVol.value() * tr.concentration_[tIdx][I][1];
406 Scalar sLocalStorage = (sStorageOfTimeIndex0 - sStorageOfTimeIndex1[tIdx]) * scvVolume/dt;
407 tr.residual_[tIdx][I][1] += sLocalStorage; //residual + flux
408 }
409
410 // Derivative matrix
411 (*tr.mat)[I][I][0][0] += fVol.derivative(0) * scvVolume/dt;
412 (*tr.mat)[I][I][1][1] += sVol.derivative(0) * scvVolume/dt;
413
414 }
415
416 template<class TrRe>
418 const ElementContext& elemCtx,
419 unsigned scvfIdx,
420 unsigned I,
421 unsigned J,
422 const Scalar dt)
423 {
424 if (tr.numTracer() == 0)
425 return;
426
427 TracerEvaluation fFlux;
428 TracerEvaluation sFlux;
429 bool isUpF;
430 bool isUpS;
431 computeFreeFlux_(fFlux, isUpF, tr.phaseIdx_, elemCtx, scvfIdx, 0);
432 computeSolFlux_(sFlux, isUpS, tr.phaseIdx_, elemCtx, scvfIdx, 0);
433 dsVol_[tr.phaseIdx_][I] += sFlux.value() * dt;
434 dfVol_[tr.phaseIdx_][I] += fFlux.value() * dt;
435 int fGlobalUpIdx = isUpF ? I : J;
436 int sGlobalUpIdx = isUpS ? I : J;
437 for (int tIdx =0; tIdx < tr.numTracer(); ++tIdx) {
438 // Free and solution fluxes
439 tr.residual_[tIdx][I][0] += fFlux.value()*tr.concentration_[tIdx][fGlobalUpIdx][0]; //residual + flux
440 tr.residual_[tIdx][I][1] += sFlux.value()*tr.concentration_[tIdx][sGlobalUpIdx][1]; //residual + flux
441 }
442
443 // Derivative matrix
444 if (isUpF){
445 (*tr.mat)[J][I][0][0] = -fFlux.derivative(0);
446 (*tr.mat)[I][I][0][0] += fFlux.derivative(0);
447 }
448 if (isUpS) {
449 (*tr.mat)[J][I][1][1] = -sFlux.derivative(0);
450 (*tr.mat)[I][I][1][1] += sFlux.derivative(0);
451 }
452 }
453
454 template<class TrRe, class Well>
456 const Well& well)
457 {
458 if (tr.numTracer() == 0)
459 return;
460
461 const auto& eclWell = well.wellEcl();
462
463 // Init. well output to zero
464 for (int tIdx = 0; tIdx < tr.numTracer(); ++tIdx) {
465 this->wellTracerRate_[std::make_pair(eclWell.name(), this->name(tr.idx_[tIdx]))] = 0.0;
466 this->wellFreeTracerRate_[std::make_pair(eclWell.name(), this->wellfname(tr.idx_[tIdx]))] = 0.0;
467 this->wellSolTracerRate_[std::make_pair(eclWell.name(), this->wellsname(tr.idx_[tIdx]))] = 0.0;
468 if (eclWell.isMultiSegment()) {
469 for (std::size_t i = 0; i < eclWell.getConnections().size(); ++i) {
470 this->mSwTracerRate_[std::make_tuple(eclWell.name(),
471 this->name(tr.idx_[tIdx]),
472 eclWell.getConnections().get(i).segment())] = 0.0;
473 }
474 }
475 }
476
477 std::vector<Scalar> wtracer(tr.numTracer());
478 for (int tIdx = 0; tIdx < tr.numTracer(); ++tIdx) {
479 wtracer[tIdx] = this->currentConcentration_(eclWell, this->name(tr.idx_[tIdx]));
480 }
481
482 Scalar dt = simulator_.timeStepSize();
483 std::size_t well_index = simulator_.problem().wellModel().wellState().index(well.name()).value();
484 const auto& ws = simulator_.problem().wellModel().wellState().well(well_index);
485 for (std::size_t i = 0; i < ws.perf_data.size(); ++i) {
486 const auto I = ws.perf_data.cell_index[i];
487 Scalar rate = well.volumetricSurfaceRateForConnection(I, tr.phaseIdx_);
488 Scalar rate_s;
489 if (tr.phaseIdx_ == FluidSystem::oilPhaseIdx && FluidSystem::enableVaporizedOil()) {
490 rate_s = ws.perf_data.phase_mixing_rates[i][ws.vaporized_oil];
491 }
492 else if (tr.phaseIdx_ == FluidSystem::gasPhaseIdx && FluidSystem::enableDissolvedGas()) {
493 rate_s = ws.perf_data.phase_mixing_rates[i][ws.dissolved_gas];
494 }
495 else {
496 rate_s = 0.0;
497 }
498
499 Scalar rate_f = rate - rate_s;
500 if (rate_f > 0) {
501 for (int tIdx = 0; tIdx < tr.numTracer(); ++tIdx) {
502 // Injection of free tracer only
503 tr.residual_[tIdx][I][0] -= rate_f*wtracer[tIdx];
504
505 // Store _injector_ tracer rate for reporting (can be done here since WTRACER is constant)
506 this->wellTracerRate_.at(std::make_pair(eclWell.name(),this->name(tr.idx_[tIdx]))) += rate_f*wtracer[tIdx];
507 this->wellFreeTracerRate_.at(std::make_pair(eclWell.name(),this->wellfname(tr.idx_[tIdx]))) += rate_f*wtracer[tIdx];
508 if (eclWell.isMultiSegment()) {
509 this->mSwTracerRate_[std::make_tuple(eclWell.name(),
510 this->name(tr.idx_[tIdx]),
511 eclWell.getConnections().get(i).segment())] += rate_f*wtracer[tIdx];
512 }
513 }
514 dfVol_[tr.phaseIdx_][I] -= rate_f * dt;
515 }
516 else if (rate_f < 0) {
517 for (int tIdx = 0; tIdx < tr.numTracer(); ++tIdx) {
518 // Production of free tracer
519 tr.residual_[tIdx][I][0] -= rate_f * tr.concentration_[tIdx][I][0];
520
521 }
522 dfVol_[tr.phaseIdx_][I] -= rate_f * dt;
523
524 // Derivative matrix for free tracer producer
525 (*tr.mat)[I][I][0][0] -= rate_f * variable<TracerEvaluation>(1.0, 0).derivative(0);
526 }
527 if (rate_s < 0) {
528 for (int tIdx = 0; tIdx < tr.numTracer(); ++tIdx) {
529 // Production of solution tracer
530 tr.residual_[tIdx][I][1] -= rate_s * tr.concentration_[tIdx][I][1];
531 }
532 dsVol_[tr.phaseIdx_][I] -= rate_s * dt;
533
534 // Derivative matrix for solution tracer producer
535 (*tr.mat)[I][I][1][1] -= rate_s * variable<TracerEvaluation>(1.0, 0).derivative(0);
536 }
537 }
538 }
539
540 template<class TrRe>
542 const Scalar dt,
543 unsigned I)
544 {
545 if (tr.numTracer() == 0)
546 return;
547
548 // Skip if solution tracers do not exist
549 if (tr.phaseIdx_ == FluidSystem::waterPhaseIdx ||
550 (tr.phaseIdx_ == FluidSystem::gasPhaseIdx && !FluidSystem::enableDissolvedGas()) ||
551 (tr.phaseIdx_ == FluidSystem::oilPhaseIdx && !FluidSystem::enableVaporizedOil())) {
552 return;
553 }
554
555 const Scalar& dsVol = dsVol_[tr.phaseIdx_][I];
556 const Scalar& dfVol = dfVol_[tr.phaseIdx_][I];
557
558 // Source term determined by sign of dsVol: if dsVol > 0 then ms -> mf, else mf -> ms
559 for (int tIdx = 0; tIdx < tr.numTracer(); ++tIdx) {
560 if (dsVol >= 0) {
561 tr.residual_[tIdx][I][0] -= (dfVol / dt) * tr.concentration_[tIdx][I][0];
562 tr.residual_[tIdx][I][1] += (dfVol / dt) * tr.concentration_[tIdx][I][0];
563 }
564 else {
565 tr.residual_[tIdx][I][0] += (dsVol / dt) * tr.concentration_[tIdx][I][1];
566 tr.residual_[tIdx][I][1] -= (dsVol / dt) * tr.concentration_[tIdx][I][1];
567 }
568 }
569
570 // Derivative matrix
571 if (dsVol >= 0) {
572 (*tr.mat)[I][I][0][0] -= (dfVol / dt) * variable<TracerEvaluation>(1.0, 0).derivative(0);
573 (*tr.mat)[I][I][1][0] += (dfVol / dt) * variable<TracerEvaluation>(1.0, 0).derivative(0);
574 }
575 else {
576 (*tr.mat)[I][I][0][1] += (dsVol / dt) * variable<TracerEvaluation>(1.0, 0).derivative(0);
577 (*tr.mat)[I][I][1][1] -= (dsVol / dt) * variable<TracerEvaluation>(1.0, 0).derivative(0);
578 }
579 }
580
582 {
583 // Note that we formulate the equations in terms of a concentration update
584 // (compared to previous time step) and not absolute concentration.
585 // This implies that current concentration (tr.concentration_[][]) contributes
586 // to the rhs both through storrage and flux terms.
587 // Compare also advanceTracerFields(...) below.
588
589 for (auto& tr : tbatch) {
590 if (tr.numTracer() != 0) {
591 (*tr.mat) = 0.0;
592 for (int tIdx = 0; tIdx < tr.numTracer(); ++tIdx) {
593 tr.residual_[tIdx] = 0.0;
594 }
595 }
596 }
597
598 // Well terms
599 const auto& wellPtrs = simulator_.problem().wellModel().localNonshutWells();
600 for (const auto& wellPtr : wellPtrs) {
601 for (auto& tr : tbatch) {
602 this->assembleTracerEquationWell(tr, *wellPtr);
603 }
604 }
605
606 ElementContext elemCtx(simulator_);
607 for (const auto& elem : elements(simulator_.gridView())) {
608 elemCtx.updateStencil(elem);
609
610 std::size_t I = elemCtx.globalSpaceIndex(/*dofIdx=*/ 0, /*timeIdx=*/0);
611
612 if (elem.partitionType() != Dune::InteriorEntity)
613 {
614 // Dirichlet boundary conditions needed for the parallel matrix
615 for (auto& tr : tbatch) {
616 if (tr.numTracer() != 0) {
617 (*tr.mat)[I][I][0][0] = 1.;
618 (*tr.mat)[I][I][1][1] = 1.;
619 }
620 }
621 continue;
622 }
623 elemCtx.updateAllIntensiveQuantities();
624 elemCtx.updateAllExtensiveQuantities();
625
626 Scalar extrusionFactor =
627 elemCtx.intensiveQuantities(/*dofIdx=*/ 0, /*timeIdx=*/0).extrusionFactor();
628 Valgrind::CheckDefined(extrusionFactor);
629 assert(isfinite(extrusionFactor));
630 assert(extrusionFactor > 0.0);
631 Scalar scvVolume =
632 elemCtx.stencil(/*timeIdx=*/0).subControlVolume(/*dofIdx=*/ 0).volume()
633 * extrusionFactor;
634 Scalar dt = elemCtx.simulator().timeStepSize();
635
636 std::size_t I1 = elemCtx.globalSpaceIndex(/*dofIdx=*/ 0, /*timeIdx=*/1);
637
638 for (auto& tr : tbatch) {
639 this->assembleTracerEquationVolume(tr, elemCtx, scvVolume, dt, I, I1);
640 }
641
642 std::size_t numInteriorFaces = elemCtx.numInteriorFaces(/*timIdx=*/0);
643 for (unsigned scvfIdx = 0; scvfIdx < numInteriorFaces; scvfIdx++) {
644 const auto& face = elemCtx.stencil(0).interiorFace(scvfIdx);
645 unsigned j = face.exteriorIndex();
646 unsigned J = elemCtx.globalSpaceIndex(/*dofIdx=*/ j, /*timIdx=*/0);
647 for (auto& tr : tbatch) {
648 this->assembleTracerEquationFlux(tr, elemCtx, scvfIdx, I, J, dt);
649 }
650 }
651
652 // Source terms (mass transfer between free and solution tracer)
653 for (auto& tr : tbatch) {
654 this->assembleTracerEquationSource(tr, dt, I);
655 }
656
657 }
658
659 // Communicate overlap using grid Communication
660 for (auto& tr : tbatch) {
661 if (tr.numTracer() == 0)
662 continue;
664 simulator_.gridView());
665 simulator_.gridView().communicate(handle, Dune::InteriorBorder_All_Interface,
666 Dune::ForwardCommunication);
667 }
668 }
669
671 {
672 for (auto& tr : tbatch) {
673 if (tr.numTracer() != 0) {
674 tr.concentrationInitial_ = tr.concentration_;
675 }
676 }
677
678 ElementContext elemCtx(simulator_);
679 for (const auto& elem : elements(simulator_.gridView())) {
680 elemCtx.updatePrimaryStencil(elem);
681 elemCtx.updatePrimaryIntensiveQuantities(/*timeIdx=*/0);
682 Scalar extrusionFactor = elemCtx.intensiveQuantities(/*dofIdx=*/ 0, /*timeIdx=*/0).extrusionFactor();
683 Scalar scvVolume = elemCtx.stencil(/*timeIdx=*/0).subControlVolume(/*dofIdx=*/ 0).volume() * extrusionFactor;
684 int globalDofIdx = elemCtx.globalSpaceIndex(0, /*timeIdx=*/0);
685 for (auto& tr : tbatch) {
686 if (tr.numTracer() == 0)
687 continue;
688
689 Scalar fVol1 = computeFreeVolume_(tr.phaseIdx_, globalDofIdx, 0);
690 Scalar sVol1 = computeSolutionVolume_(tr.phaseIdx_, globalDofIdx, 0);
691 fVol1_[tr.phaseIdx_][globalDofIdx] = fVol1 * scvVolume;
692 sVol1_[tr.phaseIdx_][globalDofIdx] = sVol1 * scvVolume;
693 dsVol_[tr.phaseIdx_][globalDofIdx] = 0.0;
694 dfVol_[tr.phaseIdx_][globalDofIdx] = 0.0;
695 for (int tIdx = 0; tIdx < tr.numTracer(); ++tIdx) {
696 tr.storageOfTimeIndex1_[tIdx][globalDofIdx][0] = fVol1 * tr.concentrationInitial_[tIdx][globalDofIdx][0];
697 tr.storageOfTimeIndex1_[tIdx][globalDofIdx][1] = sVol1 * tr.concentrationInitial_[tIdx][globalDofIdx][1];
698 }
699 }
700 }
701 }
702
704 {
706
707 for (auto& tr : tbatch) {
708 if (tr.numTracer() == 0)
709 continue;
710
711 // Note that we solve for a concentration update (compared to previous time step)
712 // Confer also assembleTracerEquations_(...) above.
713 std::vector<TracerVector> dx(tr.concentration_);
714 for (int tIdx = 0; tIdx < tr.numTracer(); ++tIdx) {
715 dx[tIdx] = 0.0;
716 }
717
718 bool converged = this->linearSolveBatchwise_(*tr.mat, dx, tr.residual_);
719 if (!converged) {
720 OpmLog::warning("### Tracer model: Linear solver did not converge. ###");
721 }
722
723 for (int tIdx = 0; tIdx < tr.numTracer(); ++tIdx) {
724 for (std::size_t globalDofIdx = 0; globalDofIdx < tr.concentration_[tIdx].size(); ++globalDofIdx) {
725 // New concetration. Concentrations that are negative or where free/solution phase is not
726 // present are set to zero
727 const auto& intQuants = simulator_.model().intensiveQuantities(globalDofIdx, 0);
728 const auto& fs = intQuants.fluidState();
729 Scalar Sf = decay<Scalar>(fs.saturation(tr.phaseIdx_));
730 Scalar Ss = 0.0;
731 if (tr.phaseIdx_ == FluidSystem::gasPhaseIdx && FluidSystem::enableDissolvedGas())
732 Ss = decay<Scalar>(fs.saturation(FluidSystem::oilPhaseIdx));
733 else if (tr.phaseIdx_ == FluidSystem::oilPhaseIdx && FluidSystem::enableVaporizedOil())
734 Ss = decay<Scalar>(fs.saturation(FluidSystem::gasPhaseIdx));
735
736 const Scalar tol_gas_sat = 1e-6;
737 if ((tr.concentration_[tIdx][globalDofIdx][0] - dx[tIdx][globalDofIdx][0] < 0.0)
738 || Sf < tol_gas_sat)
739 tr.concentration_[tIdx][globalDofIdx][0] = 0.0;
740 else
741 tr.concentration_[tIdx][globalDofIdx][0] -= dx[tIdx][globalDofIdx][0];
742 if (tr.concentration_[tIdx][globalDofIdx][1] - dx[tIdx][globalDofIdx][1] < 0.0
743 || Ss < tol_gas_sat)
744 tr.concentration_[tIdx][globalDofIdx][1] = 0.0;
745 else
746 tr.concentration_[tIdx][globalDofIdx][1] -= dx[tIdx][globalDofIdx][1];
747
748 // Partition concentration into free and solution tracers for output
749 this->freeTracerConcentration_[tr.idx_[tIdx]][globalDofIdx] = tr.concentration_[tIdx][globalDofIdx][0];
750 this->solTracerConcentration_[tr.idx_[tIdx]][globalDofIdx] = tr.concentration_[tIdx][globalDofIdx][1];
751 }
752 }
753
754 // Store _producer_ tracer rate for reporting
755 const auto& wellPtrs = simulator_.problem().wellModel().localNonshutWells();
756 for (const auto& wellPtr : wellPtrs) {
757 const auto& eclWell = wellPtr->wellEcl();
758
759 // Injection rates already reported during assembly
760 if (!eclWell.isProducer())
761 continue;
762
763 Scalar rateWellPos = 0.0;
764 Scalar rateWellNeg = 0.0;
765 std::size_t well_index = simulator_.problem().wellModel().wellState().index(eclWell.name()).value();
766 const auto& ws = simulator_.problem().wellModel().wellState().well(well_index);
767 for (std::size_t i = 0; i < ws.perf_data.size(); ++i) {
768 const auto I = ws.perf_data.cell_index[i];
769 Scalar rate = wellPtr->volumetricSurfaceRateForConnection(I, tr.phaseIdx_);
770
771 Scalar rate_s;
772 if (tr.phaseIdx_ == FluidSystem::oilPhaseIdx && FluidSystem::enableVaporizedOil()) {
773 rate_s = ws.perf_data.phase_mixing_rates[i][ws.vaporized_oil];
774 }
775 else if (tr.phaseIdx_ == FluidSystem::gasPhaseIdx && FluidSystem::enableDissolvedGas()) {
776 rate_s = ws.perf_data.phase_mixing_rates[i][ws.dissolved_gas];
777 }
778 else {
779 rate_s = 0.0;
780 }
781
782 Scalar rate_f = rate - rate_s;
783 if (rate_f < 0) {
784 for (int tIdx = 0; tIdx < tr.numTracer(); ++tIdx) {
785 // Store _producer_ free tracer rate for reporting
786 this->wellTracerRate_.at(std::make_pair(eclWell.name(),this->name(tr.idx_[tIdx]))) +=
787 rate_f * tr.concentration_[tIdx][I][0];
788 this->wellFreeTracerRate_.at(std::make_pair(eclWell.name(),this->wellfname(tr.idx_[tIdx]))) +=
789 rate_f * tr.concentration_[tIdx][I][0];
790 if (eclWell.isMultiSegment()) {
791 this->mSwTracerRate_[std::make_tuple(eclWell.name(),
792 this->name(tr.idx_[tIdx]),
793 eclWell.getConnections().get(i).segment())] +=
794 rate_f * tr.concentration_[tIdx][I][0];
795 }
796 }
797 }
798 if (rate_s < 0) {
799 for (int tIdx = 0; tIdx < tr.numTracer(); ++tIdx) {
800 // Store _producer_ solution tracer rate for reporting
801 this->wellTracerRate_.at(std::make_pair(eclWell.name(),this->name(tr.idx_[tIdx]))) +=
802 rate_s * tr.concentration_[tIdx][I][1];
803 this->wellSolTracerRate_.at(std::make_pair(eclWell.name(),this->wellsname(tr.idx_[tIdx]))) +=
804 rate_s * tr.concentration_[tIdx][I][1];
805 if (eclWell.isMultiSegment()) {
806 this->mSwTracerRate_[std::make_tuple(eclWell.name(),
807 this->name(tr.idx_[tIdx]),
808 eclWell.getConnections().get(i).segment())] +=
809 rate_s * tr.concentration_[tIdx][I][1];
810 }
811 }
812 }
813
814 if (rate < 0) {
815 rateWellNeg += rate;
816 }
817 else {
818 rateWellPos += rate;
819 }
820 }
821
822 Scalar rateWellTotal = rateWellNeg + rateWellPos;
823
824 // TODO: Some inconsistencies here that perhaps should be clarified. The "offical" rate as reported below is
825 // occasionally significant different from the sum over connections (as calculated above). Only observed
826 // for small values, neglible for the rate itself, but matters when used to calculate tracer concentrations.
827 Scalar official_well_rate_total = simulator_.problem().wellModel().wellState().well(well_index).surface_rates[tr.phaseIdx_];
828
829 rateWellTotal = official_well_rate_total;
830
831 if (rateWellTotal > rateWellNeg) { // Cross flow
832 const Scalar bucketPrDay = 10.0/(1000.*3600.*24.); // ... keeps (some) trouble away
833 const Scalar factor = (rateWellTotal < -bucketPrDay) ? rateWellTotal/rateWellNeg : 0.0;
834 for (int tIdx = 0; tIdx < tr.numTracer(); ++tIdx) {
835 this->wellTracerRate_.at(std::make_pair(eclWell.name(),this->name(tr.idx_[tIdx]))) *= factor;
836 }
837 }
838 }
839 }
840 }
841
842
843
844 Simulator& simulator_;
845
846 // This struct collects tracers of the same type (i.e, transported in same phase).
847 // The idea being that, under the assumption of linearity, tracers of same type can
848 // be solved in concert, having a common system matrix but separate right-hand-sides.
849
850 // Since oil or gas tracers appears in dual compositions when VAPOIL respectively DISGAS
851 // is active, the template argument is intended to support future extension to these
852 // scenarios by supplying an extended vector type.
853
854 template <typename TV>
855 struct TracerBatch {
856 std::vector<int> idx_;
857 const int phaseIdx_;
858 std::vector<TV> concentrationInitial_;
859 std::vector<TV> concentration_;
860 std::vector<TV> storageOfTimeIndex1_;
861 std::vector<TV> residual_;
862 std::unique_ptr<TracerMatrix> mat;
863
864 bool operator==(const TracerBatch& rhs) const
865 {
866 return this->concentrationInitial_ == rhs.concentrationInitial_ &&
867 this->concentration_ == rhs.concentration_;
868 }
869
871 {
872 TracerBatch<TV> result(4);
873 result.idx_ = {1,2,3};
874 result.concentrationInitial_ = {5.0, 6.0};
875 result.concentration_ = {7.0, 8.0};
876 result.storageOfTimeIndex1_ = {9.0, 10.0, 11.0};
877 result.residual_ = {12.0, 13.0};
878
879 return result;
880 }
881
882 template<class Serializer>
883 void serializeOp(Serializer& serializer)
884 {
885 serializer(concentrationInitial_);
886 serializer(concentration_);
887 }
888
889 TracerBatch(int phaseIdx = 0) : phaseIdx_(phaseIdx) {}
890
891 int numTracer() const {return idx_.size(); }
892
893 void addTracer(const int idx, const TV & concentration)
894 {
895 int numGridDof = concentration.size();
896 idx_.emplace_back(idx);
897 concentrationInitial_.emplace_back(concentration);
898 concentration_.emplace_back(concentration);
899 residual_.emplace_back(numGridDof);
900 storageOfTimeIndex1_.emplace_back(numGridDof);
901 }
902 };
903
904 std::array<TracerBatch<TracerVector>,3> tbatch;
908 std::array<std::vector<Scalar>, 3> fVol1_;
909 std::array<std::vector<Scalar>, 3> sVol1_;
910 std::array<std::vector<Scalar>, 3> dsVol_;
911 std::array<std::vector<Scalar>, 3> dfVol_;
912};
913
914} // namespace Opm
915
916#endif // OPM_TRACER_MODEL_HPP
A datahandle sending data located in multiple vectors.
Definition: GenericTracerModel.hpp:55
void doInit(bool rst, std::size_t numGridDof, std::size_t gasPhaseIdx, std::size_t oilPhaseIdx, std::size_t waterPhaseIdx)
Initialize all internal data structures needed by the tracer module.
Definition: GenericTracerModel_impl.hpp:222
A class which handles tracers as specified in by ECL.
Definition: TracerModel.hpp:68
std::array< TracerBatch< TracerVector >, 3 > tbatch
Definition: TracerModel.hpp:904
Scalar computeSolutionVolume_(const int tracerPhaseIdx, unsigned globalDofIdx, unsigned timeIdx)
Definition: TracerModel.hpp:242
void updateStorageCache()
Definition: TracerModel.hpp:670
void computeFreeFlux_(TracerEvaluation &freeFlux, bool &isUp, const int tracerPhaseIdx, const ElementContext &elemCtx, unsigned scvfIdx, unsigned timeIdx)
Definition: TracerModel.hpp:276
TracerBatch< TracerVector > & oil_
Definition: TracerModel.hpp:906
void advanceTracerFields()
Definition: TracerModel.hpp:703
std::array< std::vector< Scalar >, 3 > fVol1_
Definition: TracerModel.hpp:908
void init(bool rst)
Definition: TracerModel.hpp:129
void assembleTracerEquationSource(TrRe &tr, const Scalar dt, unsigned I)
Definition: TracerModel.hpp:541
TracerModel(Simulator &simulator)
Definition: TracerModel.hpp:97
void assembleTracerEquationFlux(TrRe &tr, const ElementContext &elemCtx, unsigned scvfIdx, unsigned I, unsigned J, const Scalar dt)
Definition: TracerModel.hpp:417
Scalar computeFreeVolume_(const int tracerPhaseIdx, unsigned globalDofIdx, unsigned timeIdx)
Definition: TracerModel.hpp:226
void assembleTracerEquationWell(TrRe &tr, const Well &well)
Definition: TracerModel.hpp:455
void computeSolFlux_(TracerEvaluation &solFlux, bool &isUp, const int tracerPhaseIdx, const ElementContext &elemCtx, unsigned scvfIdx, unsigned timeIdx)
Definition: TracerModel.hpp:309
void assembleTracerEquationVolume(TrRe &tr, const ElementContext &elemCtx, const Scalar scvVolume, const Scalar dt, unsigned I, unsigned I1)
Definition: TracerModel.hpp:364
void serialize(Restarter &)
This method writes the complete state of all tracer to the hard disk.
Definition: TracerModel.hpp:203
TracerBatch< TracerVector > & gas_
Definition: TracerModel.hpp:907
void assembleTracerEquations_()
Definition: TracerModel.hpp:581
std::array< std::vector< Scalar >, 3 > sVol1_
Definition: TracerModel.hpp:909
std::array< std::vector< Scalar >, 3 > dfVol_
Definition: TracerModel.hpp:911
void beginTimeStep()
Definition: TracerModel.hpp:179
void serializeOp(Serializer &serializer)
Definition: TracerModel.hpp:217
void prepareTracerBatches()
Definition: TracerModel.hpp:135
std::array< std::vector< Scalar >, 3 > dsVol_
Definition: TracerModel.hpp:910
TracerBatch< TracerVector > & wat_
Definition: TracerModel.hpp:905
void endTimeStep()
Informs the tracer model that a time step has just been finished.
Definition: TracerModel.hpp:190
Simulator & simulator_
Definition: TracerModel.hpp:844
void deserialize(Restarter &)
This method restores the complete state of the tracer from disk.
Definition: TracerModel.hpp:213
A data handle sending multiple data store in vectors attached to cells.
Definition: VectorVectorDataHandle.hpp:49
Definition: blackoilmodel.hh:72
Definition: blackoilboundaryratevector.hh:37
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:235
The Opm property system, traits with inheritance.
Definition: TracerModel.hpp:48
a tag to mark properties as undefined
Definition: propertysystem.hh:40
Definition: TracerModel.hpp:855
const int phaseIdx_
Definition: TracerModel.hpp:857
void addTracer(const int idx, const TV &concentration)
Definition: TracerModel.hpp:893
bool operator==(const TracerBatch &rhs) const
Definition: TracerModel.hpp:864
static TracerBatch serializationTestObject()
Definition: TracerModel.hpp:870
std::vector< TV > concentrationInitial_
Definition: TracerModel.hpp:858
void serializeOp(Serializer &serializer)
Definition: TracerModel.hpp:883
TracerBatch(int phaseIdx=0)
Definition: TracerModel.hpp:889
int numTracer() const
Definition: TracerModel.hpp:891
std::vector< int > idx_
Definition: TracerModel.hpp:856
std::unique_ptr< TracerMatrix > mat
Definition: TracerModel.hpp:862
std::vector< TV > storageOfTimeIndex1_
Definition: TracerModel.hpp:860
std::vector< TV > residual_
Definition: TracerModel.hpp:861
std::vector< TV > concentration_
Definition: TracerModel.hpp:859