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