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/BCState.hpp>
36#include <opm/input/eclipse/Schedule/Schedule.hpp>
37#include <opm/input/eclipse/Schedule/Well/Well.hpp>
38#include <opm/input/eclipse/Schedule/Well/WellConnections.hpp>
39
40#include <opm/grid/utility/ElementChunks.hpp>
41
44
49
50#include <array>
51#include <cstddef>
52#include <memory>
53#include <set>
54#include <stdexcept>
55#include <string>
56#include <unordered_map>
57#include <utility>
58#include <vector>
59
60#include <fmt/format.h>
61
62namespace Opm::Properties {
63
64template<class TypeTag, class MyTypeTag>
67};
68
69} // namespace Opm::Properties
70
71namespace Opm {
72
78template <class TypeTag>
79class TracerModel : public GenericTracerModel<GetPropType<TypeTag, Properties::Grid>,
80 GetPropType<TypeTag, Properties::GridView>,
81 GetPropType<TypeTag, Properties::DofMapper>,
82 GetPropType<TypeTag, Properties::Stencil>,
83 GetPropType<TypeTag, Properties::FluidSystem>,
84 GetPropType<TypeTag, Properties::Scalar>>
85{
101
102 using TracerEvaluation = DenseAd::Evaluation<Scalar,1>;
103
104 using TracerMatrix = typename BaseType::TracerMatrix;
105 using TracerVector = typename BaseType::TracerVector;
106 using TracerVectorSingle = typename BaseType::TracerVectorSingle;
107
108 enum { numEq = getPropValue<TypeTag, Properties::NumEq>() };
109 enum { numPhases = FluidSystem::numPhases };
110 enum { waterPhaseIdx = FluidSystem::waterPhaseIdx };
111 enum { oilPhaseIdx = FluidSystem::oilPhaseIdx };
112 enum { gasPhaseIdx = FluidSystem::gasPhaseIdx };
113
114public:
115 explicit TracerModel(Simulator& simulator)
116 : BaseType(simulator.vanguard().gridView(),
117 simulator.vanguard().eclState(),
118 simulator.vanguard().cartesianIndexMapper(),
119 simulator.model().dofMapper(),
120 simulator.vanguard().cellCentroids())
121 , simulator_(simulator)
122 , tbatch({waterPhaseIdx, oilPhaseIdx, gasPhaseIdx})
123 , wat_(tbatch[0])
124 , oil_(tbatch[1])
125 , gas_(tbatch[2])
126 , element_chunks_(simulator.gridView(), Dune::Partitions::all, ThreadManager::maxThreads())
127 { }
128
129
130 /*
131 The initialization of the tracer model is a three step process:
132
133 1. The init() method is called. This will allocate buffers and initialize
134 some phase index stuff. If this is a normal run the initial tracer
135 concentrations will be assigned from the TBLK or TVDPF keywords.
136
137 2. [Restart only:] The tracer concentration are read from the restart
138 file and the concentrations are applied with repeated calls to the
139 setTracerConcentration() method. This is currently done in the
140 eclwriter::beginRestart() method.
141
142 3. Internally the tracer model manages the concentrations in "batches" for
143 the oil, water and gas tracers respectively. The batches should be
144 initialized with the initial concentration, that must be performed
145 after the concentration values have been assigned. This is done in
146 method prepareTracerBatches() called from eclproblem::finishInit().
147 */
148 void init(bool rst)
149 {
150 this->doInit(rst, simulator_.model().numGridDof(),
151 gasPhaseIdx, oilPhaseIdx, waterPhaseIdx);
152 }
153
155 {
156 DeferredLogger local_deferredLogger;
157
158 for (std::size_t tracerIdx = 0; tracerIdx < this->tracerPhaseIdx_.size(); ++tracerIdx) {
159 if (this->tracerPhaseIdx_[tracerIdx] == FluidSystem::waterPhaseIdx) {
160 if (! FluidSystem::phaseIsActive(FluidSystem::waterPhaseIdx)){
161 throw std::runtime_error("Water tracer specified for non-water fluid system: " +
162 this->name(tracerIdx));
163 }
164
165 wat_.addTracer(tracerIdx, this->tracerConcentration_[tracerIdx]);
166 }
167 else if (this->tracerPhaseIdx_[tracerIdx] == FluidSystem::oilPhaseIdx) {
168 if (! FluidSystem::phaseIsActive(FluidSystem::oilPhaseIdx)){
169 throw std::runtime_error("Oil tracer specified for non-oil fluid system: " +
170 this->name(tracerIdx));
171 }
172
173 oil_.addTracer(tracerIdx, this->tracerConcentration_[tracerIdx]);
174 }
175 else if (this->tracerPhaseIdx_[tracerIdx] == FluidSystem::gasPhaseIdx) {
176 if (! FluidSystem::phaseIsActive(FluidSystem::gasPhaseIdx)){
177 throw std::runtime_error("Gas tracer specified for non-gas fluid system: " +
178 this->name(tracerIdx));
179 }
180
181 gas_.addTracer(tracerIdx, this->tracerConcentration_[tracerIdx]);
182 }
183
184 // resize free and solution volume storages
185 vol1_[0][this->tracerPhaseIdx_[tracerIdx]].
186 resize(this->freeTracerConcentration_[tracerIdx].size());
187 vol1_[1][this->tracerPhaseIdx_[tracerIdx]].
188 resize(this->freeTracerConcentration_[tracerIdx].size());
189 dVol_[0][this->tracerPhaseIdx_[tracerIdx]].
190 resize(this->solTracerConcentration_[tracerIdx].size());
191 dVol_[1][this->tracerPhaseIdx_[tracerIdx]].
192 resize(this->solTracerConcentration_[tracerIdx].size());
193 }
194
195 // will be valid after we move out of tracerMatrix_
196 TracerMatrix* base = this->tracerMatrix_.get();
197 for (auto& tr : this->tbatch) {
198 if (tr.numTracer() != 0) {
199 if (this->tracerMatrix_) {
200 tr.mat = std::move(this->tracerMatrix_);
201 }
202 else {
203 tr.mat = std::make_unique<TracerMatrix>(*base);
204 }
205 }
206 }
207
208 this->buildAquiferTracerConnections_(local_deferredLogger);
209 this->checkBoundaryTracers_(local_deferredLogger);
210
211 const auto& comm = simulator_.vanguard().grid().comm();
212 auto global_logger = gatherDeferredLogger(local_deferredLogger, comm);
213 if (comm.rank() == 0) {
214 global_logger.logMessages();
215 }
216 }
217
219 {
220 if (this->numTracers() == 0) {
221 return;
222 }
223
224 OPM_TIMEBLOCK(tracerUpdateCache);
226 }
227
232 {
233 if (this->numTracers() == 0) {
234 return;
235 }
236
237 OPM_TIMEBLOCK(tracerAdvance);
239 }
240
245 template <class Restarter>
246 void serialize(Restarter&)
247 { /* not implemented */ }
248
255 template <class Restarter>
256 void deserialize(Restarter&)
257 { /* not implemented */ }
258
259 template<class Serializer>
260 void serializeOp(Serializer& serializer)
261 {
262 serializer(static_cast<BaseType&>(*this));
263 serializer(tbatch);
264 }
265
266protected:
268 using BaseType::Free;
269 using BaseType::Solution;
270
271 // compute volume associated with free/solution concentration
272 template<TracerTypeIdx Index>
273 Scalar computeVolume_(const int tracerPhaseIdx,
274 const unsigned globalDofIdx,
275 const unsigned timeIdx) const
276 {
277 const auto& intQuants = simulator_.model().intensiveQuantities(globalDofIdx, timeIdx);
278 const auto& fs = intQuants.fluidState();
279 constexpr Scalar min_volume = 1e-10;
280
281 if constexpr (Index == Free) {
282 return std::max(decay<Scalar>(fs.saturation(tracerPhaseIdx)) *
283 decay<Scalar>(fs.invB(tracerPhaseIdx)) *
284 decay<Scalar>(intQuants.porosity()),
285 min_volume);
286 } else {
287 // vaporized oil
288 if (tracerPhaseIdx == FluidSystem::oilPhaseIdx && FluidSystem::enableVaporizedOil()) {
289 return std::max(decay<Scalar>(fs.saturation(FluidSystem::gasPhaseIdx)) *
290 decay<Scalar>(fs.invB(FluidSystem::gasPhaseIdx)) *
291 decay<Scalar>(fs.Rv()) *
292 decay<Scalar>(intQuants.porosity()),
293 min_volume);
294 }
295
296 // dissolved gas
297 else if (tracerPhaseIdx == FluidSystem::gasPhaseIdx && FluidSystem::enableDissolvedGas()) {
298 return std::max(decay<Scalar>(fs.saturation(FluidSystem::oilPhaseIdx)) *
299 decay<Scalar>(fs.invB(FluidSystem::oilPhaseIdx)) *
300 decay<Scalar>(fs.Rs()) *
301 decay<Scalar>(intQuants.porosity()),
302 min_volume);
303 }
304
305 return min_volume;
306 }
307 }
308
309 template<TracerTypeIdx Index>
310 std::pair<TracerEvaluation, bool>
311 computeFlux_(const int tracerPhaseIdx,
312 const ElementContext& elemCtx,
313 const unsigned scvfIdx,
314 const unsigned timeIdx) const
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 const unsigned inIdx = extQuants.interiorIndex();
321
322 Scalar v;
323 unsigned upIdx;
324
325 if constexpr (Index == Free) {
326 upIdx = extQuants.upstreamIndex(tracerPhaseIdx);
327 const auto& intQuants = elemCtx.intensiveQuantities(upIdx, timeIdx);
328 const auto& fs = intQuants.fluidState();
329 v = decay<Scalar>(extQuants.volumeFlux(tracerPhaseIdx)) *
330 decay<Scalar>(fs.invB(tracerPhaseIdx));
331 } else {
332 if (tracerPhaseIdx == FluidSystem::oilPhaseIdx && FluidSystem::enableVaporizedOil()) {
333 upIdx = extQuants.upstreamIndex(FluidSystem::gasPhaseIdx);
334
335 const auto& intQuants = elemCtx.intensiveQuantities(upIdx, timeIdx);
336 const auto& fs = intQuants.fluidState();
337 v = decay<Scalar>(fs.invB(FluidSystem::gasPhaseIdx)) *
338 decay<Scalar>(extQuants.volumeFlux(FluidSystem::gasPhaseIdx)) *
339 decay<Scalar>(fs.Rv());
340 }
341 // dissolved gas
342 else if (tracerPhaseIdx == FluidSystem::gasPhaseIdx && FluidSystem::enableDissolvedGas()) {
343 upIdx = extQuants.upstreamIndex(FluidSystem::oilPhaseIdx);
344
345 const auto& intQuants = elemCtx.intensiveQuantities(upIdx, timeIdx);
346 const auto& fs = intQuants.fluidState();
347 v = decay<Scalar>(fs.invB(FluidSystem::oilPhaseIdx)) *
348 decay<Scalar>(extQuants.volumeFlux(FluidSystem::oilPhaseIdx)) *
349 decay<Scalar>(fs.Rs());
350 }
351 else {
352 upIdx = 0;
353 v = 0.0;
354 }
355 }
356
357 const Scalar A = scvf.area();
358 return inIdx == upIdx
359 ? std::pair{A * v * variable<TracerEvaluation>(1.0, 0), true}
360 : std::pair{A * v, false};
361 }
362
363 template<TracerTypeIdx Index, class TrRe>
364 Scalar storage1_(const TrRe& tr,
365 const unsigned tIdx,
366 const unsigned I,
367 const unsigned I1,
368 const bool cache)
369 {
370 if (cache) {
371 return tr.storageOfTimeIndex1_[tIdx][I][Index];
372 } else {
373 return computeVolume_<Index>(tr.phaseIdx_, I1, 1) *
374 tr.concentration_[tIdx][I1][Index];
375 }
376 }
377
378 template<class TrRe>
380 const ElementContext& elemCtx,
381 const Scalar scvVolume,
382 const Scalar dt,
383 unsigned I,
384 unsigned I1)
385
386 {
387 if (tr.numTracer() == 0) {
388 return;
389 }
390
391 const TracerEvaluation fVol = computeVolume_<Free>(tr.phaseIdx_, I, 0) * variable<TracerEvaluation>(1.0, 0);
392 const TracerEvaluation sVol = computeVolume_<Solution>(tr.phaseIdx_, I, 0) * variable<TracerEvaluation>(1.0, 0);
393 dVol_[Solution][tr.phaseIdx_][I] += sVol.value() * scvVolume - vol1_[1][tr.phaseIdx_][I];
394 dVol_[Free][tr.phaseIdx_][I] += fVol.value() * scvVolume - vol1_[0][tr.phaseIdx_][I];
395 for (int tIdx = 0; tIdx < tr.numTracer(); ++tIdx) {
396 // Free part
397 const Scalar fStorageOfTimeIndex0 = fVol.value() * tr.concentration_[tIdx][I][Free];
398 const Scalar fLocalStorage = (fStorageOfTimeIndex0 - storage1_<Free>(tr, tIdx, I, I1,
399 elemCtx.enableStorageCache())) * scvVolume / dt;
400 tr.residual_[tIdx][I][Free] += fLocalStorage; // residual + flux
401
402 // Solution part
403 const Scalar sStorageOfTimeIndex0 = sVol.value() * tr.concentration_[tIdx][I][Solution];
404 const Scalar sLocalStorage = (sStorageOfTimeIndex0 - storage1_<Solution>(tr, tIdx, I, I1,
405 elemCtx.enableStorageCache())) * scvVolume / dt;
406 tr.residual_[tIdx][I][Solution] += sLocalStorage; // residual + flux
407 }
408
409 // Derivative matrix
410 (*tr.mat)[I][I][Free][Free] += fVol.derivative(0) * scvVolume/dt;
411 (*tr.mat)[I][I][Solution][Solution] += sVol.derivative(0) * scvVolume/dt;
412 }
413
414 template<class TrRe>
416 const ElementContext& elemCtx,
417 unsigned scvfIdx,
418 unsigned I,
419 unsigned J,
420 const Scalar dt)
421 {
422 if (tr.numTracer() == 0) {
423 return;
424 }
425
426 const auto& [fFlux, isUpF] = computeFlux_<Free>(tr.phaseIdx_, elemCtx, scvfIdx, 0);
427 const auto& [sFlux, isUpS] = computeFlux_<Solution>(tr.phaseIdx_, elemCtx, scvfIdx, 0);
428 dVol_[Solution][tr.phaseIdx_][I] += sFlux.value() * dt;
429 dVol_[Free][tr.phaseIdx_][I] += fFlux.value() * dt;
430 const int fGlobalUpIdx = isUpF ? I : J;
431 const int sGlobalUpIdx = isUpS ? I : J;
432 for (int tIdx = 0; tIdx < tr.numTracer(); ++tIdx) {
433 // Free and solution fluxes
434 tr.residual_[tIdx][I][Free] += fFlux.value()*tr.concentration_[tIdx][fGlobalUpIdx][Free]; // residual + flux
435 tr.residual_[tIdx][I][Solution] += sFlux.value()*tr.concentration_[tIdx][sGlobalUpIdx][Solution]; // residual + flux
436 }
437
438 // Derivative matrix
439 if (isUpF){
440 (*tr.mat)[J][I][Free][Free] = -fFlux.derivative(0);
441 (*tr.mat)[I][I][Free][Free] += fFlux.derivative(0);
442 }
443 if (isUpS) {
444 (*tr.mat)[J][I][Solution][Solution] = -sFlux.derivative(0);
445 (*tr.mat)[I][I][Solution][Solution] += sFlux.derivative(0);
446 }
447 }
448
449 template<class TrRe, class Well>
451 const Well& well)
452 {
453 if (tr.numTracer() == 0) {
454 return;
455 }
456
457 const auto& eclWell = well.wellEcl();
458
459 // Init. well output to zero
460 auto& tracerRate = this->wellTracerRate_[eclWell.seqIndex()];
461 auto& solTracerRate = this->wellSolTracerRate_[eclWell.seqIndex()];
462 auto& freeTracerRate = this->wellFreeTracerRate_[eclWell.seqIndex()];
463 auto* mswTracerRate = eclWell.isMultiSegment()
464 ? &this->mSwTracerRate_[eclWell.seqIndex()]
465 : nullptr;
466 for (int tIdx = 0; tIdx < tr.numTracer(); ++tIdx) {
467 tracerRate[tr.idx_[tIdx]] = {this->name(tr.idx_[tIdx]), 0.0};
468 freeTracerRate[tr.idx_[tIdx]] = {this->wellfname(tr.idx_[tIdx]), 0.0};
469 solTracerRate[tr.idx_[tIdx]] = {this->wellsname(tr.idx_[tIdx]), 0.0};
470 if (eclWell.isMultiSegment()) {
471 auto& wtr = mswTracerRate->at(tr.idx_[tIdx]) = {this->name(tr.idx_[tIdx])};;
472 wtr.rate.reserve(eclWell.getConnections().size());
473 for (std::size_t i = 0; i < eclWell.getConnections().size(); ++i) {
474 wtr.rate.emplace(eclWell.getConnections().get(i).segment(), 0.0);
475 }
476 }
477 }
478
479 std::vector<Scalar> wtracer(tr.numTracer());
480 for (int tIdx = 0; tIdx < tr.numTracer(); ++tIdx) {
481 wtracer[tIdx] = this->currentConcentration_(eclWell, this->name(tr.idx_[tIdx]),
482 simulator_.problem().wellModel().summaryState());
483 }
484
485 const Scalar dt = simulator_.timeStepSize();
486 const auto& ws = simulator_.problem().wellModel().wellState().well(well.name());
487 const auto well_eff = well.wellEfficiencyFactor(); // Needed to convert ws.perf_data.phase_mixing_rates to effective rates
488 for (std::size_t i = 0; i < ws.perf_data.size(); ++i) {
489 const auto I = ws.perf_data.cell_index[i];
490 const Scalar rate = well.volumetricSurfaceRateForConnection(I, tr.phaseIdx_); // Includes (accumulated) well efficiency factor
491 Scalar rate_s;
492
493 if (tr.phaseIdx_ == FluidSystem::oilPhaseIdx && FluidSystem::enableVaporizedOil()) {
494 rate_s = ws.perf_data.phase_mixing_rates[i][ws.vaporized_oil] * well_eff;
495 }
496 else if (tr.phaseIdx_ == FluidSystem::gasPhaseIdx && FluidSystem::enableDissolvedGas()) {
497 rate_s = ws.perf_data.phase_mixing_rates[i][ws.dissolved_gas] * well_eff;
498 }
499 else {
500 rate_s = 0.0;
501 }
502
503 const Scalar rate_f = rate - rate_s;
504 if (rate_f > 0) {
505 for (int tIdx = 0; tIdx < tr.numTracer(); ++tIdx) {
506 const Scalar delta = rate_f * wtracer[tIdx];
507 // Injection of free tracer only
508 tr.residual_[tIdx][I][Free] -= delta;
509
510 // Store _injector_ tracer rate for reporting
511 // (can be done here since WTRACER is constant)
512 tracerRate[tr.idx_[tIdx]].rate += delta;
513 freeTracerRate[tr.idx_[tIdx]].rate += delta;
514 if (eclWell.isMultiSegment()) {
515 (*mswTracerRate)[tr.idx_[tIdx]].rate[eclWell.getConnections().get(i).segment()] += delta;
516 }
517 }
518 dVol_[Free][tr.phaseIdx_][I] -= rate_f * dt;
519 }
520 else if (rate_f < 0) {
521 for (int tIdx = 0; tIdx < tr.numTracer(); ++tIdx) {
522 const Scalar delta = rate_f * wtracer[tIdx];
523 // Store _injector_ tracer rate for cross-flowing well connections
524 // (can be done here since WTRACER is constant)
525 tracerRate[tr.idx_[tIdx]].rate += delta;
526 freeTracerRate[tr.idx_[tIdx]].rate += delta;
527
528 // Production of free tracer
529 tr.residual_[tIdx][I][Free] -= rate_f * tr.concentration_[tIdx][I][Free];
530 }
531 dVol_[Free][tr.phaseIdx_][I] -= rate_f * dt;
532
533 // Derivative matrix for free tracer producer
534 (*tr.mat)[I][I][Free][Free] -= rate_f * variable<TracerEvaluation>(1.0, 0).derivative(0);
535 }
536 if (rate_s < 0) {
537 for (int tIdx = 0; tIdx < tr.numTracer(); ++tIdx) {
538 // Production of solution tracer
539 tr.residual_[tIdx][I][Solution] -= rate_s * tr.concentration_[tIdx][I][Solution];
540 }
541 dVol_[Solution][tr.phaseIdx_][I] -= rate_s * dt;
542
543 // Derivative matrix for solution tracer producer
544 (*tr.mat)[I][I][Solution][Solution] -= rate_s * variable<TracerEvaluation>(1.0, 0).derivative(0);
545 }
546 }
547 }
548
563 template<class TrRe>
565 const ElementContext& elemCtx,
566 const unsigned I,
567 const Scalar dt)
568 {
569 if (tr.numTracer() == 0) {
570 return;
571 }
572
573 const auto& problem = simulator_.problem();
574 if (!problem.nonTrivialBoundaryConditions()) {
575 return;
576 }
577
578 if (!elemCtx.onBoundary()) {
579 return;
580 }
581
582 // The boundary flux of the flow equations, per unit area, is obtained from the
583 // problem for each boundary face, as done when assembling the flow equations.
586 BoundaryContext boundaryCtx(elemCtx);
587 if (boundaryCtx.intersection(0).neighbor()) {
588 // move the iterator to the first boundary
589 boundaryCtx.increment();
590 }
591
592 const auto& stencil = elemCtx.stencil(/*timeIdx=*/0);
593 const auto& bcstate = simulator_.vanguard().schedule()[problem.episodeIndex()].bcstate;
594 const unsigned numBoundaryFaces = boundaryCtx.numBoundaryFaces(/*timeIdx=*/0);
595 for (unsigned bfIdx = 0; bfIdx < numBoundaryFaces; ++bfIdx, boundaryCtx.increment()) {
596 const auto& bf = stencil.boundaryFace(bfIdx);
597 const int dirId = bf.dirId();
598 if (dirId < 0) { // not for NNCs
599 continue;
600 }
601
602 const int bcIndex = problem.boundaryConditionIndex(I, dirId);
603 if (bcIndex == 0) {
604 continue;
605 }
606
607 const BCType type = problem.boundaryCondition(I, dirId).first;
608 if (type != BCType::RATE && type != BCType::FREE && type != BCType::DIRICHLET) {
609 continue;
610 }
611
612 BoundaryRateVector bdyFlux;
613 problem.boundary(bdyFlux, boundaryCtx, bfIdx, /*timeIdx=*/0);
614 const unsigned pvtRegionIdx = problem.pvtRegionIndex(I);
615 const Scalar boundaryArea = bf.area() * decay<Scalar>(elemCtx.intensiveQuantities(0, 0).extrusionFactor());
616
617 // Surface volume rate out of the cell of the main component of a phase (water in the
618 // water phase etc.), converted with the reference density of that phase if the flux
619 // is a mass flux. The rate of the tracer phase is zero if the BCPROP component is
620 // not the phase of the tracer.
621 const auto phaseFlux = [&](const int phaseIdx)
622 {
623 const int compIdx = phaseIdx == FluidSystem::waterPhaseIdx ? FluidSystem::waterCompIdx
624 : phaseIdx == FluidSystem::oilPhaseIdx ? FluidSystem::oilCompIdx
625 : FluidSystem::gasCompIdx;
626 Scalar flux = decay<Scalar>(bdyFlux[FluidSystem::canonicalToActiveCompIdx(compIdx)]) * boundaryArea;
627 if constexpr (!getPropValue<TypeTag, Properties::BlackoilConserveSurfaceVolume>()) {
628 flux /= FluidSystem::referenceDensity(phaseIdx, pvtRegionIdx);
629 }
630 return flux;
631 };
632 const Scalar rateOut = phaseFlux(tr.phaseIdx_);
633 if (rateOut == Scalar{0}) {
634 continue;
635 }
636
637 // Outflow is split in a free part and a solution part (vaporized oil in the gas
638 // phase for oil tracers, dissolved gas in the oil phase for gas tracers), as for
639 // producer wells. The component fluxes are F_o + Rv F_g (oil) and F_g + Rs F_o
640 // (gas) in terms of the free phase fluxes F_o and F_g. Inflow is free only.
641 Scalar rateFree = rateOut;
642 Scalar rateSol = 0.0;
643 const bool oilSolution = tr.phaseIdx_ == FluidSystem::oilPhaseIdx && FluidSystem::enableVaporizedOil();
644 const bool gasSolution = tr.phaseIdx_ == FluidSystem::gasPhaseIdx && FluidSystem::enableDissolvedGas();
645 if (rateOut > 0 && (oilSolution || gasSolution)) {
646 // Bind the intensive quantities, as the model may return them by value.
647 const auto& intQuants = simulator_.model().intensiveQuantities(I, /*timeIdx=*/0);
648 const auto& fs = intQuants.fluidState();
649 const Scalar Rs = FluidSystem::enableDissolvedGas() ? decay<Scalar>(fs.Rs()) : Scalar{0};
650 const Scalar Rv = FluidSystem::enableVaporizedOil() ? decay<Scalar>(fs.Rv()) : Scalar{0};
651 const Scalar Qo = phaseFlux(FluidSystem::oilPhaseIdx);
652 const Scalar Qg = phaseFlux(FluidSystem::gasPhaseIdx);
653 const Scalar det = 1.0 - Rs * Rv;
654 const Scalar Fo = (Qo - Rv * Qg) / det;
655 const Scalar Fg = (Qg - Rs * Qo) / det;
656 if (oilSolution) {
657 rateFree = Fo;
658 rateSol = Rv * Fg;
659 }
660 else {
661 rateFree = Fg;
662 rateSol = Rs * Fo;
663 }
664 }
665
666 for (int tIdx = 0; tIdx < tr.numTracer(); ++tIdx) {
667 if (rateOut < 0) {
668 // Inflow: concentration from BCTRACER, no tracer if not specified
669 const auto bcConc = bcstate.tracerConcentration(bcIndex,
670 this->name(tr.idx_[tIdx]));
671 tr.residual_[tIdx][I][Free] += rateOut * bcConc.value_or(0.0);
672 }
673 else {
674 // Outflow: tracer leaves with the concentrations of the cell
675 tr.residual_[tIdx][I][Free] += rateFree * tr.concentration_[tIdx][I][Free];
676 tr.residual_[tIdx][I][Solution] += rateSol * tr.concentration_[tIdx][I][Solution];
677 }
678 }
679 dVol_[Free][tr.phaseIdx_][I] += rateFree * dt;
680 dVol_[Solution][tr.phaseIdx_][I] += rateSol * dt;
681
682 if (rateOut > 0) {
683 // Derivative matrix for outflow
684 (*tr.mat)[I][I][Free][Free] += rateFree * variable<TracerEvaluation>(1.0, 0).derivative(0);
685 (*tr.mat)[I][I][Solution][Solution] += rateSol * variable<TracerEvaluation>(1.0, 0).derivative(0);
686 }
687 }
688 }
689
690 template<class TrRe>
692 const Scalar dt,
693 unsigned I)
694 {
695 if (tr.numTracer() == 0) {
696 return;
697 }
698
699 // Skip if solution tracers do not exist
700 if (tr.phaseIdx_ == FluidSystem::waterPhaseIdx ||
701 (tr.phaseIdx_ == FluidSystem::gasPhaseIdx && !FluidSystem::enableDissolvedGas()) ||
702 (tr.phaseIdx_ == FluidSystem::oilPhaseIdx && !FluidSystem::enableVaporizedOil()))
703 {
704 return;
705 }
706
707 const Scalar& dsVol = dVol_[Solution][tr.phaseIdx_][I];
708 const Scalar& dfVol = dVol_[Free][tr.phaseIdx_][I];
709
710 // Source term determined by sign of dsVol: if dsVol > 0 then ms -> mf, else mf -> ms
711 for (int tIdx = 0; tIdx < tr.numTracer(); ++tIdx) {
712 if (dsVol >= 0) {
713 const auto delta = (dfVol / dt) * tr.concentration_[tIdx][I][Free];
714 tr.residual_[tIdx][I][Free] -= delta;
715 tr.residual_[tIdx][I][Solution] += delta;
716 }
717 else {
718 const auto delta = (dsVol / dt) * tr.concentration_[tIdx][I][Solution];
719 tr.residual_[tIdx][I][Free] += delta;
720 tr.residual_[tIdx][I][Solution] -= delta;
721 }
722 }
723
724 // Derivative matrix
725 if (dsVol >= 0) {
726 const auto delta = (dfVol / dt) * variable<TracerEvaluation>(1.0, 0).derivative(0);
727 (*tr.mat)[I][I][Free][Free] -= delta;
728 (*tr.mat)[I][I][Solution][Free] += delta;
729 }
730 else {
731 const auto delta = (dsVol / dt) * variable<TracerEvaluation>(1.0, 0).derivative(0);
732 (*tr.mat)[I][I][Free][Solution] += delta;
733 (*tr.mat)[I][I][Solution][Solution] -= delta;
734 }
735 }
736
738 {
739 // Note that we formulate the equations in terms of a concentration update
740 // (compared to previous time step) and not absolute concentration.
741 // This implies that current concentration (tr.concentration_[][]) contributes
742 // to the rhs both through storage and flux terms.
743 // Compare also advanceTracerFields(...) below.
744
745 DeferredLogger local_deferredLogger{};
747 {
748 OPM_TIMEBLOCK(tracerAssemble);
749 for (auto& tr : tbatch) {
750 if (tr.numTracer() != 0) {
751 (*tr.mat) = 0.0;
752 for (int tIdx = 0; tIdx < tr.numTracer(); ++tIdx) {
753 tr.residual_[tIdx] = 0.0;
754 }
755 }
756 }
757
758 this->wellTracerRate_.clear();
759 this->wellFreeTracerRate_.clear();
760 this->wellSolTracerRate_.clear();
761
762 // educated guess for new container size
763 const auto num_msw = this->mSwTracerRate_.size();
764 this->mSwTracerRate_.clear();
765
766 // Well terms
767 const auto& wellPtrs = simulator_.problem().wellModel().localNonshutWells();
768 this->wellTracerRate_.reserve(wellPtrs.size());
769 this->wellFreeTracerRate_.reserve(wellPtrs.size());
770 this->wellSolTracerRate_.reserve(wellPtrs.size());
771 this->mSwTracerRate_.reserve(num_msw);
772 for (const auto& wellPtr : wellPtrs) {
773 // Resize vectors of well tracer rates to total number of tracers
774 const auto& eclWell = wellPtr->wellEcl();
775 this->wellTracerRate_[eclWell.seqIndex()].resize(this->numTracers());
776 this->wellFreeTracerRate_[eclWell.seqIndex()].resize(this->numTracers());
777 this->wellSolTracerRate_[eclWell.seqIndex()].resize(this->numTracers());
778 auto* mswTracerRate = eclWell.isMultiSegment()
779 ? &this->mSwTracerRate_[eclWell.seqIndex()]
780 : nullptr;
781 if (mswTracerRate) {
782 mswTracerRate->resize(this->numTracers());
783 }
784 for (auto& tr : tbatch) {
785 this->assembleTracerEquationWell(tr, *wellPtr);
786 }
787 }
788
789 for (auto& tr : tbatch) {
791 }
792
793 // Parallel loop over element chunks
794 #ifdef _OPENMP
795 #pragma omp parallel for
796 #endif
797 for (const auto& chunk : element_chunks_) {
798 ElementContext elemCtx(simulator_);
799 const Scalar dt = elemCtx.simulator().timeStepSize();
800
801 for (const auto& elem : chunk) {
802 elemCtx.updateStencil(elem);
803 const std::size_t I = elemCtx.globalSpaceIndex(/*dofIdx=*/ 0, /*timeIdx=*/0);
804
805 if (elem.partitionType() != Dune::InteriorEntity) {
806 // Dirichlet boundary conditions for parallel matrix
807 // This is safe as each element has a unique I. So each thread
808 // always writes to different memory locations in the shared arrays.
809 for (const auto& tr : tbatch) {
810 if (tr.numTracer() != 0) {
811 (*tr.mat)[I][I][0][0] = 1.;
812 (*tr.mat)[I][I][1][1] = 1.;
813 }
814 }
815 continue;
816 }
817 elemCtx.updateAllIntensiveQuantities();
818 elemCtx.updateAllExtensiveQuantities();
819
820 const Scalar extrusionFactor =
821 elemCtx.intensiveQuantities(/*dofIdx=*/ 0, /*timeIdx=*/0).extrusionFactor();
822 Valgrind::CheckDefined(extrusionFactor);
823 assert(isfinite(extrusionFactor));
824 assert(extrusionFactor > 0.0);
825 const Scalar scvVolume =
826 elemCtx.stencil(/*timeIdx=*/0).subControlVolume(/*dofIdx=*/ 0).volume() * extrusionFactor;
827 const std::size_t I1 = elemCtx.globalSpaceIndex(/*dofIdx=*/ 0, /*timeIdx=*/1);
828
829 // This is safe as each element has a unique I. So each thread
830 // always writes to different memory locations in the shared arrays.
831 for (auto& tr : tbatch) {
832 if (tr.numTracer() == 0) {
833 continue;
834 }
835 this->assembleTracerEquationVolume(tr, elemCtx, scvVolume, dt, I, I1);
836 }
837
838 const std::size_t numInteriorFaces = elemCtx.numInteriorFaces(/*timIdx=*/0);
839 for (unsigned scvfIdx = 0; scvfIdx < numInteriorFaces; scvfIdx++) {
840 const auto& face = elemCtx.stencil(0).interiorFace(scvfIdx);
841 const unsigned j = face.exteriorIndex();
842 const unsigned J = elemCtx.globalSpaceIndex(/*dofIdx=*/ j, /*timIdx=*/0);
843 for (auto& tr : tbatch) {
844 if (tr.numTracer() == 0) {
845 continue;
846 }
847 this->assembleTracerEquationFlux(tr, elemCtx, scvfIdx, I, J, dt);
848 }
849 }
850
851 // Boundary conditions (BCCON + BCPROP + BCTRACER)
852 for (auto& tr : tbatch) {
853 this->assembleTracerEquationBoundary(tr, elemCtx, I, dt);
854 }
855
856 // Source terms (mass transfer between free and solution tracer)
857 for (auto& tr : tbatch) {
858 if (tr.numTracer() == 0) {
859 continue;
860 }
861 this->assembleTracerEquationSource(tr, dt, I);
862 }
863 }
864 }
865 }
866 OPM_END_PARALLEL_TRY_CATCH_LOG(local_deferredLogger,
867 "assembleTracerEquations() failed: ",
868 true, simulator_.gridView().comm())
869
870 // Communicate overlap using grid Communication
871 for (auto& tr : tbatch) {
872 if (tr.numTracer() == 0) {
873 continue;
874 }
876 simulator_.gridView());
877 simulator_.gridView().communicate(handle, Dune::InteriorBorder_All_Interface,
878 Dune::ForwardCommunication);
879 }
880 }
881
882 template<TracerTypeIdx Index, class TrRe>
883 void updateElem(TrRe& tr,
884 const Scalar scvVolume,
885 const unsigned globalDofIdx)
886 {
887 const Scalar vol1 = computeVolume_<Index>(tr.phaseIdx_, globalDofIdx, 0);
888 vol1_[Index][tr.phaseIdx_][globalDofIdx] = vol1 * scvVolume;
889 dVol_[Index][tr.phaseIdx_][globalDofIdx] = 0.0;
890 for (int tIdx = 0; tIdx < tr.numTracer(); ++tIdx) {
891 tr.storageOfTimeIndex1_[tIdx][globalDofIdx][Index] =
892 vol1 * tr.concentrationInitial_[tIdx][globalDofIdx][Index];
893 }
894 }
895
897 {
898 for (auto& tr : tbatch) {
899 if (tr.numTracer() != 0) {
900 tr.concentrationInitial_ = tr.concentration_;
901 }
902 }
903
904 // Parallel loop over element chunks
905 #ifdef _OPENMP
906 #pragma omp parallel for
907 #endif
908 for (const auto& chunk : element_chunks_) {
909 ElementContext elemCtx(simulator_);
910
911 for (const auto& elem : chunk) {
912 elemCtx.updatePrimaryStencil(elem);
913 elemCtx.updatePrimaryIntensiveQuantities(/*timeIdx=*/0);
914 const Scalar extrusionFactor = elemCtx.intensiveQuantities(/*dofIdx=*/ 0, /*timeIdx=*/0).extrusionFactor();
915 const Scalar scvVolume = elemCtx.stencil(/*timeIdx=*/0).subControlVolume(/*dofIdx=*/ 0).volume() * extrusionFactor;
916 const unsigned globalDofIdx = elemCtx.globalSpaceIndex(0, /*timeIdx=*/0);
917
918 for (auto& tr : tbatch) {
919 if (tr.numTracer() == 0) {
920 continue;
921 }
922 // This is safe as each element has a unique globalDofIdx. So each thread
923 // always writes to different memory locations in the shared arrays.
924 updateElem<Free>(tr, scvVolume, globalDofIdx);
925 updateElem<Solution>(tr, scvVolume, globalDofIdx);
926 }
927 }
928 }
929 }
930
931 template<TracerTypeIdx Index, class TrRe>
932 void copyForOutput(TrRe& tr,
933 const std::vector<TracerVector>& dx,
934 const Scalar S,
935 const unsigned tIdx,
936 const unsigned globalDofIdx,
937 std::vector<TracerVectorSingle>& sc)
938 {
939 constexpr Scalar tol_gas_sat = 1e-6;
940 tr.concentration_[tIdx][globalDofIdx][Index] -= dx[tIdx][globalDofIdx][Index];
941 if (tr.concentration_[tIdx][globalDofIdx][Index] < 0.0 || S < tol_gas_sat) {
942 tr.concentration_[tIdx][globalDofIdx][Index] = 0.0;
943 }
944 sc[tr.idx_[tIdx]][globalDofIdx] = tr.concentration_[tIdx][globalDofIdx][Index];
945 }
946
947 template<TracerTypeIdx Index, class TrRe>
948 void assignRates(const TrRe& tr,
949 const Well& eclWell,
950 const std::size_t i,
951 const std::size_t I,
952 const Scalar rate,
953 std::vector<WellTracerRate<Scalar>>& tracerRate,
954 std::vector<MSWellTracerRate<Scalar>>* mswTracerRate,
955 std::vector<WellTracerRate<Scalar>>& splitRate)
956 {
957 if (rate < 0) {
958 for (int tIdx = 0; tIdx < tr.numTracer(); ++tIdx) {
959 // Store _producer_ free tracer rate for reporting
960 const Scalar delta = rate * tr.concentration_[tIdx][I][Index];
961 tracerRate[tr.idx_[tIdx]].rate += delta;
962 splitRate[tr.idx_[tIdx]].rate += delta;
963 if (eclWell.isMultiSegment()) {
964 (*mswTracerRate)[tr.idx_[tIdx]].rate[eclWell.getConnections().get(i).segment()] += delta;
965 }
966 }
967 }
968 }
969
971 const auto& wellPtrs = simulator_.problem().wellModel().localNonshutWells();
972 for (const auto& wellPtr : wellPtrs) {
973 const auto& eclWell = wellPtr->wellEcl();
974 const auto well_seq_index = eclWell.seqIndex();
975 const auto inv_well_eff_factor = 1.0 / std::max(Scalar{1.0e-10}, wellPtr->wellEfficiencyFactor());
976
977 std::ranges::for_each(this->wellTracerRate_[well_seq_index], [&](WellTracerRate<Scalar>& wtr) {
978 wtr.rate *= inv_well_eff_factor;
979 });
980 std::ranges::for_each(this->wellFreeTracerRate_[well_seq_index], [&](WellTracerRate<Scalar>& wtr) {
981 wtr.rate *= inv_well_eff_factor;
982 });
983 std::ranges::for_each(this->wellSolTracerRate_[well_seq_index], [&](WellTracerRate<Scalar>& wtr) {
984 wtr.rate *= inv_well_eff_factor;
985 });
986 if (eclWell.isMultiSegment()) {
987 std::ranges::for_each(this->mSwTracerRate_[well_seq_index], [&](MSWellTracerRate<Scalar>& wtr) {
988 std::ranges::for_each(wtr.rate, [&](auto& item) {
989 item.second *= inv_well_eff_factor;
990 });
991 });
992 }
993 }
994 }
995
1008 void checkBoundaryTracers_(DeferredLogger& deferredLogger) const
1009 {
1010 std::unordered_map<std::string, int> tracer_name_to_idx;
1011 for (int tracerIdx = 0; tracerIdx < this->numTracers(); ++tracerIdx) {
1012 tracer_name_to_idx.emplace(this->name(tracerIdx), tracerIdx);
1013 }
1014
1015 const bool logging = simulator_.vanguard().grid().comm().rank() == 0;
1016 const auto& schedule = simulator_.vanguard().schedule();
1017 std::set<std::pair<int, std::string>> warned;
1018 std::set<int> flowIndices; // RATE, FREE or DIRICHLET
1019 std::set<int> tracerIndices;
1020 for (std::size_t step = 0; step < schedule.size(); ++step) {
1021 for (const auto& face : schedule[step].bcstate) {
1022 if (face.bctype == BCType::RATE || face.bctype == BCType::FREE ||
1023 face.bctype == BCType::DIRICHLET) {
1024 flowIndices.insert(face.index);
1025 }
1026
1027 for (const auto& tracerbc : face.tracerbcvalues) {
1028 tracerIndices.insert(face.index);
1029 const auto pos = tracer_name_to_idx.find(tracerbc.tracer);
1030 if (pos == tracer_name_to_idx.end()) {
1031 throw std::runtime_error(fmt::format("BCTRACER: tracer {} (INDEX {}) is not declared in TRACER",
1032 tracerbc.tracer, face.index));
1033 }
1034
1035 const int phaseIdx = this->tracerPhaseIdx_[pos->second];
1036 const bool samePhase =
1037 (face.component == BCComponent::WATER && phaseIdx == FluidSystem::waterPhaseIdx) ||
1038 (face.component == BCComponent::OIL && phaseIdx == FluidSystem::oilPhaseIdx) ||
1039 (face.component == BCComponent::GAS && phaseIdx == FluidSystem::gasPhaseIdx);
1040 // Faces without a BCPROP component (yet), or with a non-phase component, are not checked
1041 const bool isPhaseComponent = face.component == BCComponent::WATER ||
1042 face.component == BCComponent::OIL ||
1043 face.component == BCComponent::GAS;
1044 if (isPhaseComponent && !samePhase &&
1045 warned.emplace(face.index, tracerbc.tracer).second && logging)
1046 {
1047 deferredLogger.warning(fmt::format("BCTRACER: the BCPROP component of INDEX {} is not the phase "
1048 "of tracer {}, so the tracer will not enter through this "
1049 "boundary", face.index, tracerbc.tracer));
1050 }
1051 }
1052 }
1053 }
1054
1055 for (const int index : tracerIndices) {
1056 if (!flowIndices.contains(index) && logging) {
1057 deferredLogger.warning(fmt::format("BCTRACER: INDEX {} has no BCPROP of type RATE, FREE or DIRICHLET. "
1058 "BCTRACER is only used for boundaries with a flow and is ignored.",
1059 index));
1060 }
1061 }
1062 }
1063
1065 {
1066 aquifer_tracer_cells_.clear();
1067
1068 const auto& aquifer_cfg = this->eclState_.aquifer();
1069 if (!aquifer_cfg.active()) {
1070 return;
1071 }
1072
1073 const auto& specs = aquifer_cfg.aquiferTracers();
1074 if (specs.empty()) {
1075 return;
1076 }
1077
1078 std::unordered_map<std::string, int> tracer_name_to_idx;
1079 for (int tracerIdx = 0; tracerIdx < this->numTracers(); ++tracerIdx) {
1080 tracer_name_to_idx.emplace(this->name(tracerIdx), tracerIdx);
1081 }
1082
1083 const auto& vanguard = simulator_.vanguard();
1084 for (const auto& spec : specs) {
1085 if (!aquifer_cfg.hasAnalyticalAquifer(spec.aquifer_id)) {
1086 continue;
1087 }
1088
1089 const auto tracer_pos = tracer_name_to_idx.find(spec.tracer_name);
1090 if (tracer_pos == tracer_name_to_idx.end()) {
1091 if (simulator_.vanguard().grid().comm().rank() == 0) {
1092 deferredLogger.warning(fmt::format("AQANTRC tracer '{}' is not declared in TRACER",
1093 spec.tracer_name));
1094 }
1095 continue;
1096 }
1097
1098 if (!aquifer_cfg.connections().hasAquiferConnections(spec.aquifer_id)) {
1099 continue;
1100 }
1101
1102 const int tracerIdx = tracer_pos->second;
1103 const int phaseIdx = this->tracerPhaseIdx_[tracerIdx];
1104
1105 for (const auto& conn : aquifer_cfg.connections().getConnections(spec.aquifer_id)) {
1106 const int cellIdx = vanguard.compressedIndex(conn.global_index);
1107 if (cellIdx < 0) {
1108 continue;
1109 }
1110
1111 aquifer_tracer_cells_[cellIdx].push_back(
1112 AquiferTracerCellSpec { tracerIdx, phaseIdx, static_cast<Scalar>(spec.concentration) });
1113 }
1114 }
1115 }
1116
1117 template<class TrRe>
1119 {
1120 if (tr.numTracer() == 0 || aquifer_tracer_cells_.empty()) {
1121 return;
1122 }
1123
1124 const auto& aquiferModel = simulator_.problem().aquiferModel();
1125 const Scalar dt = simulator_.timeStepSize();
1126
1127 for (const auto& [cellIdx, specs] : aquifer_tracer_cells_) {
1128 // Scalar influx from converged Qai_; do not recalculate or use getValue()
1129 // in the flow Newton path (see AquiferAnalytical::addToSource).
1130 const Scalar rate = aquiferModel.cachedConnectionInfluxRate(cellIdx);
1131 if (rate == Scalar{0}) {
1132 continue;
1133 }
1134
1135 const unsigned I = cellIdx;
1136 const Scalar rate_f = rate;
1137
1138 for (const auto& spec : specs) {
1139 if (spec.phaseIdx != tr.phaseIdx_) {
1140 continue;
1141 }
1142
1143 int localTIdx = -1;
1144 for (int tIdx = 0; tIdx < tr.numTracer(); ++tIdx) {
1145 if (tr.idx_[tIdx] == spec.tracerIdx) {
1146 localTIdx = tIdx;
1147 break;
1148 }
1149 }
1150 if (localTIdx < 0) {
1151 continue;
1152 }
1153
1154 if (rate_f > 0) {
1155 const Scalar delta = rate_f * spec.concentration;
1156 tr.residual_[localTIdx][I][Free] -= delta;
1157 dVol_[Free][tr.phaseIdx_][I] -= rate_f * dt;
1158 }
1159 else if (rate_f < 0) {
1160 tr.residual_[localTIdx][I][Free] -= rate_f * tr.concentration_[localTIdx][I][Free];
1161 dVol_[Free][tr.phaseIdx_][I] -= rate_f * dt;
1162 (*tr.mat)[I][I][Free][Free] -= rate_f * variable<TracerEvaluation>(1.0, 0).derivative(0);
1163 }
1164 }
1165 }
1166 }
1167
1169 {
1171
1172 for (auto& tr : tbatch) {
1173 if (tr.numTracer() == 0) {
1174 continue;
1175 }
1176
1177 // Note that we solve for a concentration update (compared to previous time step)
1178 // Confer also assembleTracerEquations_(...) above.
1179 std::vector<TracerVector> dx(tr.concentration_);
1180 for (int tIdx = 0; tIdx < tr.numTracer(); ++tIdx) {
1181 dx[tIdx] = 0.0;
1182 }
1183
1184 const bool converged = this->linearSolveBatchwise_(*tr.mat, dx, tr.residual_);
1185 if (!converged) {
1186 OpmLog::warning("### Tracer model: Linear solver did not converge. ###");
1187 }
1188
1189 OPM_TIMEBLOCK(tracerPost);
1190
1191 for (int tIdx = 0; tIdx < tr.numTracer(); ++tIdx) {
1192 for (std::size_t globalDofIdx = 0; globalDofIdx < tr.concentration_[tIdx].size(); ++globalDofIdx) {
1193 // New concetration. Concentrations that are negative or where free/solution phase is not
1194 // present are set to zero
1195 const auto& intQuants = simulator_.model().intensiveQuantities(globalDofIdx, 0);
1196 const auto& fs = intQuants.fluidState();
1197 const Scalar Sf = decay<Scalar>(fs.saturation(tr.phaseIdx_));
1198 Scalar Ss = 0.0;
1199
1200 if (tr.phaseIdx_ == FluidSystem::gasPhaseIdx && FluidSystem::enableDissolvedGas()) {
1201 Ss = decay<Scalar>(fs.saturation(FluidSystem::oilPhaseIdx));
1202 }
1203 else if (tr.phaseIdx_ == FluidSystem::oilPhaseIdx && FluidSystem::enableVaporizedOil()) {
1204 Ss = decay<Scalar>(fs.saturation(FluidSystem::gasPhaseIdx));
1205 }
1206
1207 copyForOutput<Free>(tr, dx, Sf, tIdx, globalDofIdx, this->freeTracerConcentration_);
1208 copyForOutput<Solution>(tr, dx, Ss, tIdx, globalDofIdx, this->solTracerConcentration_);
1209 }
1210 }
1211
1212 // Store _producer_ tracer rate for reporting
1213 const auto& wellPtrs = simulator_.problem().wellModel().localNonshutWells();
1214 for (const auto& wellPtr : wellPtrs) {
1215 const auto& eclWell = wellPtr->wellEcl();
1216
1217 // Injection rates already reported during assembly
1218 if (!eclWell.isProducer()) {
1219 continue;
1220 }
1221
1222 Scalar rateWellPos = 0.0;
1223 Scalar rateWellNeg = 0.0;
1224 const std::size_t well_index = simulator_.problem().wellModel().wellState().index(eclWell.name()).value();
1225 const auto& ws = simulator_.problem().wellModel().wellState().well(well_index);
1226 auto& tracerRate = this->wellTracerRate_[eclWell.seqIndex()];
1227 auto& freeTracerRate = this->wellFreeTracerRate_[eclWell.seqIndex()];
1228 auto& solTracerRate = this->wellSolTracerRate_[eclWell.seqIndex()];
1229 auto* mswTracerRate = eclWell.isMultiSegment() ? &this->mSwTracerRate_[eclWell.seqIndex()] : nullptr;
1230 const auto well_eff = wellPtr->wellEfficiencyFactor(); // Needed to convert ws.perf_data.phase_mixing_rates to effective rates
1231 for (std::size_t i = 0; i < ws.perf_data.size(); ++i) {
1232 const auto I = ws.perf_data.cell_index[i];
1233 const Scalar rate = wellPtr->volumetricSurfaceRateForConnection(I, tr.phaseIdx_); // Includes (accumulated) well efficiency factor
1234
1235 Scalar rate_s;
1236 if (tr.phaseIdx_ == FluidSystem::oilPhaseIdx && FluidSystem::enableVaporizedOil()) {
1237 rate_s = ws.perf_data.phase_mixing_rates[i][ws.vaporized_oil]*well_eff;
1238 }
1239 else if (tr.phaseIdx_ == FluidSystem::gasPhaseIdx && FluidSystem::enableDissolvedGas()) {
1240 rate_s = ws.perf_data.phase_mixing_rates[i][ws.dissolved_gas]*well_eff;
1241 }
1242 else {
1243 rate_s = 0.0;
1244 }
1245
1246 const Scalar rate_f = rate - rate_s;
1247 assignRates<Free>(tr, eclWell, i, I, rate_f,
1248 tracerRate, mswTracerRate, freeTracerRate);
1249 assignRates<Solution>(tr, eclWell, i, I, rate_s,
1250 tracerRate, mswTracerRate, solTracerRate);
1251
1252 if (rate < 0) {
1253 rateWellNeg += rate;
1254 }
1255 else {
1256 rateWellPos += rate;
1257 }
1258 }
1259
1260 // TODO: Some inconsistencies here that perhaps should be clarified.
1261 // The "offical" rate as reported below is occasionally significant
1262 // different from the sum over connections (as calculated above). Only observed
1263 // for small values, neglible for the rate itself, but matters when used to
1264 // calculate tracer concentrations.
1265 const Scalar official_well_rate_total =
1266 simulator_.problem().wellModel().wellState().well(well_index).surface_rates[tr.phaseIdx_];
1267
1268 const Scalar rateWellTotal = official_well_rate_total;
1269
1270 if (rateWellTotal > rateWellNeg) { // Cross flow
1271 constexpr Scalar bucketPrDay = 10.0 / (1000. * 3600. * 24.); // ... keeps (some) trouble away
1272 const Scalar factor = (rateWellTotal < -bucketPrDay) ? rateWellTotal / rateWellNeg : 0.0;
1273 for (int tIdx = 0; tIdx < tr.numTracer(); ++tIdx) {
1274 tracerRate[tIdx].rate *= factor;
1275 }
1276 }
1277 }
1278 }
1280 }
1281
1282 Simulator& simulator_;
1283
1284 // This struct collects tracers of the same type (i.e, transported in same phase).
1285 // The idea being that, under the assumption of linearity, tracers of same type can
1286 // be solved in concert, having a common system matrix but separate right-hand-sides.
1287
1288 // Since oil or gas tracers appears in dual compositions when VAPOIL respectively DISGAS
1289 // is active, the template argument is intended to support future extension to these
1290 // scenarios by supplying an extended vector type.
1291
1292 template <typename TV>
1294 {
1295 std::vector<int> idx_;
1296 const int phaseIdx_;
1297 std::vector<TV> concentrationInitial_;
1298 std::vector<TV> concentration_;
1299 std::vector<TV> storageOfTimeIndex1_;
1300 std::vector<TV> residual_;
1301 std::unique_ptr<TracerMatrix> mat;
1302
1303 bool operator==(const TracerBatch& rhs) const
1304 {
1305 return this->concentrationInitial_ == rhs.concentrationInitial_ &&
1306 this->concentration_ == rhs.concentration_;
1307 }
1308
1310 {
1311 TracerBatch<TV> result(4);
1312 result.idx_ = {1,2,3};
1313 result.concentrationInitial_ = {5.0, 6.0};
1314 result.concentration_ = {7.0, 8.0};
1315 result.storageOfTimeIndex1_ = {9.0, 10.0, 11.0};
1316 result.residual_ = {12.0, 13.0};
1317
1318 return result;
1319 }
1320
1321 template<class Serializer>
1322 void serializeOp(Serializer& serializer)
1323 {
1324 serializer(concentrationInitial_);
1325 serializer(concentration_);
1326 }
1327
1328 TracerBatch(int phaseIdx = 0) : phaseIdx_(phaseIdx) {}
1329
1330 int numTracer() const
1331 { return idx_.size(); }
1332
1333 void addTracer(const int idx, const TV& concentration)
1334 {
1335 const int numGridDof = concentration.size();
1336 idx_.emplace_back(idx);
1337 concentrationInitial_.emplace_back(concentration);
1338 concentration_.emplace_back(concentration);
1339 residual_.emplace_back(numGridDof);
1340 storageOfTimeIndex1_.emplace_back(numGridDof);
1341 }
1342 };
1343
1344 std::array<TracerBatch<TracerVector>,numPhases> tbatch;
1348 std::array<std::array<std::vector<Scalar>,numPhases>,2> vol1_;
1349 std::array<std::array<std::vector<Scalar>,numPhases>,2> dVol_;
1350 ElementChunks<GridView, Dune::Partitions::All> element_chunks_;
1351
1356 };
1357
1358 std::unordered_map<unsigned, std::vector<AquiferTracerCellSpec>> aquifer_tracer_cells_;
1359};
1360
1361} // namespace Opm
1362
1363#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:207
#define OPM_BEGIN_PARALLEL_TRY_CATCH()
Macro to setup the try of a parallel try-catch.
Definition: DeferredLoggingErrorHelpers.hpp:160
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:85
void updateStorageCache()
Definition: TracerModel.hpp:896
std::array< std::array< std::vector< Scalar >, numPhases >, 2 > vol1_
Definition: TracerModel.hpp:1348
void convertEffectiveRatesToRawRates()
Definition: TracerModel.hpp:970
TracerBatch< TracerVector > & oil_
Definition: TracerModel.hpp:1346
void assembleTracerEquationBoundary(TrRe &tr, const ElementContext &elemCtx, const unsigned I, const Scalar dt)
Boundary conditions for the tracer equations.
Definition: TracerModel.hpp:564
void advanceTracerFields()
Definition: TracerModel.hpp:1168
void init(bool rst)
Definition: TracerModel.hpp:148
void checkBoundaryTracers_(DeferredLogger &deferredLogger) const
Cross-reference the boundary conditions of the schedule with the tracers.
Definition: TracerModel.hpp:1008
void assembleTracerEquationSource(TrRe &tr, const Scalar dt, unsigned I)
Definition: TracerModel.hpp:691
TracerModel(Simulator &simulator)
Definition: TracerModel.hpp:115
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:948
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:932
void assembleTracerEquationFlux(TrRe &tr, const ElementContext &elemCtx, unsigned scvfIdx, unsigned I, unsigned J, const Scalar dt)
Definition: TracerModel.hpp:415
void assembleTracerEquationWell(TrRe &tr, const Well &well)
Definition: TracerModel.hpp:450
void updateElem(TrRe &tr, const Scalar scvVolume, const unsigned globalDofIdx)
Definition: TracerModel.hpp:883
Scalar computeVolume_(const int tracerPhaseIdx, const unsigned globalDofIdx, const unsigned timeIdx) const
Definition: TracerModel.hpp:273
void assembleTracerEquationAquifer_(TrRe &tr)
Definition: TracerModel.hpp:1118
typename BaseType::TracerTypeIdx TracerTypeIdx
Definition: TracerModel.hpp:267
void assembleTracerEquationVolume(TrRe &tr, const ElementContext &elemCtx, const Scalar scvVolume, const Scalar dt, unsigned I, unsigned I1)
Definition: TracerModel.hpp:379
Scalar storage1_(const TrRe &tr, const unsigned tIdx, const unsigned I, const unsigned I1, const bool cache)
Definition: TracerModel.hpp:364
void serialize(Restarter &)
This method writes the complete state of all tracer to the hard disk.
Definition: TracerModel.hpp:246
TracerBatch< TracerVector > & gas_
Definition: TracerModel.hpp:1347
void assembleTracerEquations_()
Definition: TracerModel.hpp:737
std::array< TracerBatch< TracerVector >, numPhases > tbatch
Definition: TracerModel.hpp:1344
void buildAquiferTracerConnections_(DeferredLogger &deferredLogger)
Definition: TracerModel.hpp:1064
std::pair< TracerEvaluation, bool > computeFlux_(const int tracerPhaseIdx, const ElementContext &elemCtx, const unsigned scvfIdx, const unsigned timeIdx) const
Definition: TracerModel.hpp:311
void beginTimeStep()
Definition: TracerModel.hpp:218
void serializeOp(Serializer &serializer)
Definition: TracerModel.hpp:260
void prepareTracerBatches()
Definition: TracerModel.hpp:154
std::unordered_map< unsigned, std::vector< AquiferTracerCellSpec > > aquifer_tracer_cells_
Definition: TracerModel.hpp:1358
std::array< std::array< std::vector< Scalar >, numPhases >, 2 > dVol_
Definition: TracerModel.hpp:1349
ElementChunks< GridView, Dune::Partitions::All > element_chunks_
Definition: TracerModel.hpp:1350
TracerBatch< TracerVector > & wat_
Definition: TracerModel.hpp:1345
void endTimeStep()
Informs the tracer model that a time step has just been finished.
Definition: TracerModel.hpp:231
Simulator & simulator_
Definition: TracerModel.hpp:1282
void deserialize(Restarter &)
This method restores the complete state of the tracer from disk.
Definition: TracerModel.hpp:256
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:74
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:65
a tag to mark properties as undefined
Definition: propertysystem.hh:38
Definition: TracerModel.hpp:1352
int phaseIdx
Definition: TracerModel.hpp:1354
Scalar concentration
Definition: TracerModel.hpp:1355
int tracerIdx
Definition: TracerModel.hpp:1353
Definition: TracerModel.hpp:1294
const int phaseIdx_
Definition: TracerModel.hpp:1296
void addTracer(const int idx, const TV &concentration)
Definition: TracerModel.hpp:1333
bool operator==(const TracerBatch &rhs) const
Definition: TracerModel.hpp:1303
static TracerBatch serializationTestObject()
Definition: TracerModel.hpp:1309
std::vector< TV > concentrationInitial_
Definition: TracerModel.hpp:1297
void serializeOp(Serializer &serializer)
Definition: TracerModel.hpp:1322
TracerBatch(int phaseIdx=0)
Definition: TracerModel.hpp:1328
int numTracer() const
Definition: TracerModel.hpp:1330
std::vector< int > idx_
Definition: TracerModel.hpp:1295
std::unique_ptr< TracerMatrix > mat
Definition: TracerModel.hpp:1301
std::vector< TV > storageOfTimeIndex1_
Definition: TracerModel.hpp:1299
std::vector< TV > residual_
Definition: TracerModel.hpp:1300
std::vector< TV > concentration_
Definition: TracerModel.hpp:1298
Definition: WellTracerRate.hpp:33
Scalar rate
Definition: WellTracerRate.hpp:35