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