69class TracerModel :
public GenericTracerModel<GetPropType<TypeTag, Properties::Grid>,
70 GetPropType<TypeTag, Properties::GridView>,
71 GetPropType<TypeTag, Properties::DofMapper>,
72 GetPropType<TypeTag, Properties::Stencil>,
73 GetPropType<TypeTag, Properties::FluidSystem>,
74 GetPropType<TypeTag, Properties::Scalar>>
76 using BaseType = GenericTracerModel<GetPropType<TypeTag, Properties::Grid>,
92 using TracerEvaluation = DenseAd::Evaluation<Scalar,1>;
94 using TracerMatrix =
typename BaseType::TracerMatrix;
95 using TracerVector =
typename BaseType::TracerVector;
96 using TracerVectorSingle =
typename BaseType::TracerVectorSingle;
99 enum { numPhases = FluidSystem::numPhases };
100 enum { waterPhaseIdx = FluidSystem::waterPhaseIdx };
101 enum { oilPhaseIdx = FluidSystem::oilPhaseIdx };
102 enum { gasPhaseIdx = FluidSystem::gasPhaseIdx };
105 explicit TracerModel(Simulator& simulator)
106 : BaseType(simulator.vanguard().gridView(),
107 simulator.vanguard().eclState(),
108 simulator.vanguard().cartesianIndexMapper(),
109 simulator.model().dofMapper(),
110 simulator.vanguard().cellCentroids())
111 , simulator_(simulator)
112 , tbatch({waterPhaseIdx, oilPhaseIdx, gasPhaseIdx})
140 this->
doInit(rst, simulator_.model().numGridDof(),
141 gasPhaseIdx, oilPhaseIdx, waterPhaseIdx);
144 void prepareTracerBatches()
146 for (std::size_t tracerIdx = 0; tracerIdx < this->tracerPhaseIdx_.size(); ++tracerIdx) {
147 if (this->tracerPhaseIdx_[tracerIdx] == FluidSystem::waterPhaseIdx) {
148 if (! FluidSystem::phaseIsActive(FluidSystem::waterPhaseIdx)){
149 throw std::runtime_error(
"Water tracer specified for non-water fluid system: " +
150 this->
name(tracerIdx));
153 wat_.addTracer(tracerIdx, this->tracerConcentration_[tracerIdx]);
155 else if (this->tracerPhaseIdx_[tracerIdx] == FluidSystem::oilPhaseIdx) {
156 if (! FluidSystem::phaseIsActive(FluidSystem::oilPhaseIdx)){
157 throw std::runtime_error(
"Oil tracer specified for non-oil fluid system: " +
158 this->
name(tracerIdx));
161 oil_.addTracer(tracerIdx, this->tracerConcentration_[tracerIdx]);
163 else if (this->tracerPhaseIdx_[tracerIdx] == FluidSystem::gasPhaseIdx) {
164 if (! FluidSystem::phaseIsActive(FluidSystem::gasPhaseIdx)){
165 throw std::runtime_error(
"Gas tracer specified for non-gas fluid system: " +
166 this->
name(tracerIdx));
169 gas_.addTracer(tracerIdx, this->tracerConcentration_[tracerIdx]);
173 vol1_[0][this->tracerPhaseIdx_[tracerIdx]].
174 resize(this->freeTracerConcentration_[tracerIdx].size());
175 vol1_[1][this->tracerPhaseIdx_[tracerIdx]].
176 resize(this->freeTracerConcentration_[tracerIdx].size());
177 dVol_[0][this->tracerPhaseIdx_[tracerIdx]].
178 resize(this->solTracerConcentration_[tracerIdx].size());
179 dVol_[1][this->tracerPhaseIdx_[tracerIdx]].
180 resize(this->solTracerConcentration_[tracerIdx].size());
184 TracerMatrix* base = this->tracerMatrix_.get();
185 for (
auto& tr : this->tbatch) {
186 if (tr.numTracer() != 0) {
187 if (this->tracerMatrix_) {
188 tr.mat = std::move(this->tracerMatrix_);
191 tr.mat = std::make_unique<TracerMatrix>(*base);
203 OPM_TIMEBLOCK(tracerUpdateCache);
204 updateStorageCache();
216 OPM_TIMEBLOCK(tracerAdvance);
217 advanceTracerFields();
224 template <
class Restarter>
234 template <
class Restarter>
238 template<
class Serializer>
239 void serializeOp(Serializer& serializer)
241 serializer(
static_cast<BaseType&
>(*
this));
247 using BaseType::Free;
248 using BaseType::Solution;
251 template<TracerTypeIdx Index>
252 Scalar computeVolume_(
const int tracerPhaseIdx,
253 const unsigned globalDofIdx,
254 const unsigned timeIdx)
const
256 const auto& intQuants = simulator_.model().intensiveQuantities(globalDofIdx, timeIdx);
257 const auto& fs = intQuants.fluidState();
258 constexpr Scalar min_volume = 1e-10;
260 if constexpr (Index == Free) {
261 return std::max(decay<Scalar>(fs.saturation(tracerPhaseIdx)) *
262 decay<Scalar>(fs.invB(tracerPhaseIdx)) *
263 decay<Scalar>(intQuants.porosity()),
267 if (tracerPhaseIdx == FluidSystem::oilPhaseIdx && FluidSystem::enableVaporizedOil()) {
268 return std::max(decay<Scalar>(fs.saturation(FluidSystem::gasPhaseIdx)) *
269 decay<Scalar>(fs.invB(FluidSystem::gasPhaseIdx)) *
270 decay<Scalar>(fs.Rv()) *
271 decay<Scalar>(intQuants.porosity()),
276 else if (tracerPhaseIdx == FluidSystem::gasPhaseIdx && FluidSystem::enableDissolvedGas()) {
277 return std::max(decay<Scalar>(fs.saturation(FluidSystem::oilPhaseIdx)) *
278 decay<Scalar>(fs.invB(FluidSystem::oilPhaseIdx)) *
279 decay<Scalar>(fs.Rs()) *
280 decay<Scalar>(intQuants.porosity()),
288 template<TracerTypeIdx Index>
289 std::pair<TracerEvaluation, bool>
290 computeFlux_(
const int tracerPhaseIdx,
291 const ElementContext& elemCtx,
292 const unsigned scvfIdx,
293 const unsigned timeIdx)
const
295 const auto& stencil = elemCtx.stencil(timeIdx);
296 const auto& scvf = stencil.interiorFace(scvfIdx);
298 const auto& extQuants = elemCtx.extensiveQuantities(scvfIdx, timeIdx);
299 const unsigned inIdx = extQuants.interiorIndex();
304 if constexpr (
Index == Free) {
305 upIdx = extQuants.upstreamIndex(tracerPhaseIdx);
306 const auto& intQuants = elemCtx.intensiveQuantities(upIdx, timeIdx);
307 const auto& fs = intQuants.fluidState();
308 v = decay<Scalar>(extQuants.volumeFlux(tracerPhaseIdx)) *
309 decay<Scalar>(fs.invB(tracerPhaseIdx));
311 if (tracerPhaseIdx == FluidSystem::oilPhaseIdx && FluidSystem::enableVaporizedOil()) {
312 upIdx = extQuants.upstreamIndex(FluidSystem::gasPhaseIdx);
314 const auto& intQuants = elemCtx.intensiveQuantities(upIdx, timeIdx);
315 const auto& fs = intQuants.fluidState();
316 v = decay<Scalar>(fs.invB(FluidSystem::gasPhaseIdx)) *
317 decay<Scalar>(extQuants.volumeFlux(FluidSystem::gasPhaseIdx)) *
318 decay<Scalar>(fs.Rv());
321 else if (tracerPhaseIdx == FluidSystem::gasPhaseIdx && FluidSystem::enableDissolvedGas()) {
322 upIdx = extQuants.upstreamIndex(FluidSystem::oilPhaseIdx);
324 const auto& intQuants = elemCtx.intensiveQuantities(upIdx, timeIdx);
325 const auto& fs = intQuants.fluidState();
326 v = decay<Scalar>(fs.invB(FluidSystem::oilPhaseIdx)) *
327 decay<Scalar>(extQuants.volumeFlux(FluidSystem::oilPhaseIdx)) *
328 decay<Scalar>(fs.Rs());
336 const Scalar A = scvf.area();
337 return inIdx == upIdx
338 ? std::pair{A * v * variable<TracerEvaluation>(1.0, 0),
true}
339 : std::pair{A * v,
false};
342 template<TracerTypeIdx Index,
class TrRe>
343 Scalar storage1_(
const TrRe& tr,
350 return tr.storageOfTimeIndex1_[tIdx][I][
Index];
352 return computeVolume_<Index>(tr.phaseIdx_, I1, 1) *
353 tr.concentration_[tIdx][I1][
Index];
358 void assembleTracerEquationVolume(TrRe& tr,
359 const ElementContext& elemCtx,
360 const Scalar scvVolume,
366 if (tr.numTracer() == 0) {
370 const TracerEvaluation fVol = computeVolume_<Free>(tr.phaseIdx_, I, 0) * variable<TracerEvaluation>(1.0, 0);
371 const TracerEvaluation sVol = computeVolume_<Solution>(tr.phaseIdx_, I, 0) * variable<TracerEvaluation>(1.0, 0);
372 dVol_[Solution][tr.phaseIdx_][I] += sVol.value() * scvVolume - vol1_[1][tr.phaseIdx_][I];
373 dVol_[Free][tr.phaseIdx_][I] += fVol.value() * scvVolume - vol1_[0][tr.phaseIdx_][I];
374 for (
int tIdx = 0; tIdx < tr.numTracer(); ++tIdx) {
376 const Scalar fStorageOfTimeIndex0 = fVol.value() * tr.concentration_[tIdx][I][Free];
377 const Scalar fLocalStorage = (fStorageOfTimeIndex0 - storage1_<Free>(tr, tIdx, I, I1,
378 elemCtx.enableStorageCache())) * scvVolume / dt;
379 tr.residual_[tIdx][I][Free] += fLocalStorage;
382 const Scalar sStorageOfTimeIndex0 = sVol.value() * tr.concentration_[tIdx][I][Solution];
383 const Scalar sLocalStorage = (sStorageOfTimeIndex0 - storage1_<Solution>(tr, tIdx, I, I1,
384 elemCtx.enableStorageCache())) * scvVolume / dt;
385 tr.residual_[tIdx][I][Solution] += sLocalStorage;
389 (*tr.mat)[I][I][Free][Free] += fVol.derivative(0) * scvVolume/dt;
390 (*tr.mat)[I][I][Solution][Solution] += sVol.derivative(0) * scvVolume/dt;
394 void assembleTracerEquationFlux(TrRe& tr,
395 const ElementContext& elemCtx,
401 if (tr.numTracer() == 0) {
405 const auto& [fFlux, isUpF] = computeFlux_<Free>(tr.phaseIdx_, elemCtx, scvfIdx, 0);
406 const auto& [sFlux, isUpS] = computeFlux_<Solution>(tr.phaseIdx_, elemCtx, scvfIdx, 0);
407 dVol_[Solution][tr.phaseIdx_][I] += sFlux.value() * dt;
408 dVol_[Free][tr.phaseIdx_][I] += fFlux.value() * dt;
409 const int fGlobalUpIdx = isUpF ? I : J;
410 const int sGlobalUpIdx = isUpS ? I : J;
411 for (
int tIdx = 0; tIdx < tr.numTracer(); ++tIdx) {
413 tr.residual_[tIdx][I][Free] += fFlux.value()*tr.concentration_[tIdx][fGlobalUpIdx][Free];
414 tr.residual_[tIdx][I][Solution] += sFlux.value()*tr.concentration_[tIdx][sGlobalUpIdx][Solution];
419 (*tr.mat)[J][I][Free][Free] = -fFlux.derivative(0);
420 (*tr.mat)[I][I][Free][Free] += fFlux.derivative(0);
423 (*tr.mat)[J][I][Solution][Solution] = -sFlux.derivative(0);
424 (*tr.mat)[I][I][Solution][Solution] += sFlux.derivative(0);
428 template<
class TrRe,
class Well>
429 void assembleTracerEquationWell(TrRe& tr,
432 if (tr.numTracer() == 0) {
436 const auto& eclWell = well.wellEcl();
439 auto& tracerRate = this->wellTracerRate_[eclWell.seqIndex()];
440 auto& solTracerRate = this->wellSolTracerRate_[eclWell.seqIndex()];
441 auto& freeTracerRate = this->wellFreeTracerRate_[eclWell.seqIndex()];
442 auto* mswTracerRate = eclWell.isMultiSegment()
443 ? &this->mSwTracerRate_[eclWell.seqIndex()]
445 for (
int tIdx = 0; tIdx < tr.numTracer(); ++tIdx) {
446 tracerRate[tr.idx_[tIdx]] = {this->
name(tr.idx_[tIdx]), 0.0};
447 freeTracerRate[tr.idx_[tIdx]] = {this->wellfname(tr.idx_[tIdx]), 0.0};
448 solTracerRate[tr.idx_[tIdx]] = {this->wellsname(tr.idx_[tIdx]), 0.0};
449 if (eclWell.isMultiSegment()) {
450 auto& wtr = mswTracerRate->at(tr.idx_[tIdx]) = {this->
name(tr.idx_[tIdx])};;
451 wtr.rate.reserve(eclWell.getConnections().size());
452 for (std::size_t i = 0; i < eclWell.getConnections().size(); ++i) {
453 wtr.rate.emplace(eclWell.getConnections().get(i).segment(), 0.0);
458 std::vector<Scalar> wtracer(tr.numTracer());
459 for (
int tIdx = 0; tIdx < tr.numTracer(); ++tIdx) {
460 wtracer[tIdx] = this->currentConcentration_(eclWell, this->
name(tr.idx_[tIdx]),
461 simulator_.problem().wellModel().summaryState());
464 const Scalar dt = simulator_.timeStepSize();
465 const auto& ws = simulator_.problem().wellModel().wellState().well(well.name());
466 for (std::size_t i = 0; i < ws.perf_data.size(); ++i) {
467 const auto I = ws.perf_data.cell_index[i];
468 const Scalar rate = well.volumetricSurfaceRateForConnection(I, tr.phaseIdx_);
470 if (tr.phaseIdx_ == FluidSystem::oilPhaseIdx && FluidSystem::enableVaporizedOil()) {
471 rate_s = ws.perf_data.phase_mixing_rates[i][ws.vaporized_oil];
473 else if (tr.phaseIdx_ == FluidSystem::gasPhaseIdx && FluidSystem::enableDissolvedGas()) {
474 rate_s = ws.perf_data.phase_mixing_rates[i][ws.dissolved_gas];
480 const Scalar rate_f = rate - rate_s;
482 for (
int tIdx = 0; tIdx < tr.numTracer(); ++tIdx) {
483 const Scalar delta = rate_f * wtracer[tIdx];
485 tr.residual_[tIdx][I][Free] -= delta;
489 tracerRate[tr.idx_[tIdx]].rate += delta;
490 freeTracerRate[tr.idx_[tIdx]].rate += delta;
491 if (eclWell.isMultiSegment()) {
492 (*mswTracerRate)[tr.idx_[tIdx]].rate[eclWell.getConnections().get(i).segment()] += delta;
495 dVol_[Free][tr.phaseIdx_][I] -= rate_f * dt;
497 else if (rate_f < 0) {
498 for (
int tIdx = 0; tIdx < tr.numTracer(); ++tIdx) {
499 const Scalar delta = rate_f * wtracer[tIdx];
502 tracerRate[tr.idx_[tIdx]].rate += delta;
503 freeTracerRate[tr.idx_[tIdx]].rate += delta;
506 tr.residual_[tIdx][I][Free] -= rate_f * tr.concentration_[tIdx][I][Free];
508 dVol_[Free][tr.phaseIdx_][I] -= rate_f * dt;
511 (*tr.mat)[I][I][Free][Free] -= rate_f * variable<TracerEvaluation>(1.0, 0).derivative(0);
514 for (
int tIdx = 0; tIdx < tr.numTracer(); ++tIdx) {
516 tr.residual_[tIdx][I][Solution] -= rate_s * tr.concentration_[tIdx][I][Solution];
518 dVol_[Solution][tr.phaseIdx_][I] -= rate_s * dt;
521 (*tr.mat)[I][I][Solution][Solution] -= rate_s * variable<TracerEvaluation>(1.0, 0).derivative(0);
527 void assembleTracerEquationSource(TrRe& tr,
531 if (tr.numTracer() == 0) {
536 if (tr.phaseIdx_ == FluidSystem::waterPhaseIdx ||
537 (tr.phaseIdx_ == FluidSystem::gasPhaseIdx && !FluidSystem::enableDissolvedGas()) ||
538 (tr.phaseIdx_ == FluidSystem::oilPhaseIdx && !FluidSystem::enableVaporizedOil()))
543 const Scalar& dsVol = dVol_[Solution][tr.phaseIdx_][I];
544 const Scalar& dfVol = dVol_[Free][tr.phaseIdx_][I];
547 for (
int tIdx = 0; tIdx < tr.numTracer(); ++tIdx) {
549 const auto delta = (dfVol / dt) * tr.concentration_[tIdx][I][Free];
550 tr.residual_[tIdx][I][Free] -= delta;
551 tr.residual_[tIdx][I][Solution] += delta;
554 const auto delta = (dsVol / dt) * tr.concentration_[tIdx][I][Solution];
555 tr.residual_[tIdx][I][Free] += delta;
556 tr.residual_[tIdx][I][Solution] -= delta;
562 const auto delta = (dfVol / dt) * variable<TracerEvaluation>(1.0, 0).derivative(0);
563 (*tr.mat)[I][I][Free][Free] -= delta;
564 (*tr.mat)[I][I][Solution][Free] += delta;
567 const auto delta = (dsVol / dt) * variable<TracerEvaluation>(1.0, 0).derivative(0);
568 (*tr.mat)[I][I][Free][Solution] += delta;
569 (*tr.mat)[I][I][Solution][Solution] -= delta;
573 void assembleTracerEquations_()
581 DeferredLogger local_deferredLogger{};
582 OPM_BEGIN_PARALLEL_TRY_CATCH()
584 OPM_TIMEBLOCK(tracerAssemble);
585 for (
auto& tr : tbatch) {
586 if (tr.numTracer() != 0) {
588 for (
int tIdx = 0; tIdx < tr.numTracer(); ++tIdx) {
589 tr.residual_[tIdx] = 0.0;
594 this->wellTracerRate_.clear();
595 this->wellFreeTracerRate_.clear();
596 this->wellSolTracerRate_.clear();
599 const auto num_msw = this->mSwTracerRate_.size();
600 this->mSwTracerRate_.clear();
603 const auto& wellPtrs = simulator_.problem().wellModel().localNonshutWells();
604 this->wellTracerRate_.reserve(wellPtrs.size());
605 this->wellFreeTracerRate_.reserve(wellPtrs.size());
606 this->wellSolTracerRate_.reserve(wellPtrs.size());
607 this->mSwTracerRate_.reserve(num_msw);
608 for (
const auto& wellPtr : wellPtrs) {
610 const auto& eclWell = wellPtr->wellEcl();
611 this->wellTracerRate_[eclWell.seqIndex()].resize(this->
numTracers());
612 this->wellFreeTracerRate_[eclWell.seqIndex()].resize(this->
numTracers());
613 this->wellSolTracerRate_[eclWell.seqIndex()].resize(this->
numTracers());
614 auto* mswTracerRate = eclWell.isMultiSegment()
615 ? &this->mSwTracerRate_[eclWell.seqIndex()]
620 for (
auto& tr : tbatch) {
621 this->assembleTracerEquationWell(tr, *wellPtr);
627 #pragma omp parallel for
629 for (
const auto& chunk : element_chunks_) {
630 ElementContext elemCtx(simulator_);
631 const Scalar dt = elemCtx.simulator().timeStepSize();
633 for (
const auto& elem : chunk) {
634 elemCtx.updateStencil(elem);
635 const std::size_t I = elemCtx.globalSpaceIndex( 0, 0);
637 if (elem.partitionType() != Dune::InteriorEntity) {
641 for (
const auto& tr : tbatch) {
642 if (tr.numTracer() != 0) {
643 (*tr.mat)[I][I][0][0] = 1.;
644 (*tr.mat)[I][I][1][1] = 1.;
649 elemCtx.updateAllIntensiveQuantities();
650 elemCtx.updateAllExtensiveQuantities();
652 const Scalar extrusionFactor =
653 elemCtx.intensiveQuantities( 0, 0).extrusionFactor();
654 Valgrind::CheckDefined(extrusionFactor);
655 assert(isfinite(extrusionFactor));
656 assert(extrusionFactor > 0.0);
657 const Scalar scvVolume =
658 elemCtx.stencil(0).subControlVolume( 0).volume() * extrusionFactor;
659 const std::size_t I1 = elemCtx.globalSpaceIndex( 0, 1);
663 for (
auto& tr : tbatch) {
664 if (tr.numTracer() == 0) {
667 this->assembleTracerEquationVolume(tr, elemCtx, scvVolume, dt, I, I1);
670 const std::size_t numInteriorFaces = elemCtx.numInteriorFaces(0);
671 for (
unsigned scvfIdx = 0; scvfIdx < numInteriorFaces; scvfIdx++) {
672 const auto& face = elemCtx.stencil(0).interiorFace(scvfIdx);
673 const unsigned j = face.exteriorIndex();
674 const unsigned J = elemCtx.globalSpaceIndex( j, 0);
675 for (
auto& tr : tbatch) {
676 if (tr.numTracer() == 0) {
679 this->assembleTracerEquationFlux(tr, elemCtx, scvfIdx, I, J, dt);
684 for (
auto& tr : tbatch) {
685 if (tr.numTracer() == 0) {
688 this->assembleTracerEquationSource(tr, dt, I);
693 OPM_END_PARALLEL_TRY_CATCH_LOG(local_deferredLogger,
694 "assembleTracerEquations() failed: ",
695 true, simulator_.gridView().comm())
698 for (auto& tr : tbatch) {
699 if (tr.numTracer() == 0) {
702 auto handle = VectorVectorDataHandle<GridView, std::vector<TracerVector>>(tr.residual_,
703 simulator_.gridView());
704 simulator_.gridView().communicate(handle, Dune::InteriorBorder_All_Interface,
705 Dune::ForwardCommunication);
709 template<TracerTypeIdx Index,
class TrRe>
710 void updateElem(TrRe& tr,
711 const Scalar scvVolume,
712 const unsigned globalDofIdx)
714 const Scalar vol1 = computeVolume_<Index>(tr.phaseIdx_, globalDofIdx, 0);
715 vol1_[
Index][tr.phaseIdx_][globalDofIdx] = vol1 * scvVolume;
716 dVol_[
Index][tr.phaseIdx_][globalDofIdx] = 0.0;
717 for (
int tIdx = 0; tIdx < tr.numTracer(); ++tIdx) {
718 tr.storageOfTimeIndex1_[tIdx][globalDofIdx][
Index] =
719 vol1 * tr.concentrationInitial_[tIdx][globalDofIdx][
Index];
723 void updateStorageCache()
725 for (
auto& tr : tbatch) {
726 if (tr.numTracer() != 0) {
727 tr.concentrationInitial_ = tr.concentration_;
733 #pragma omp parallel for
735 for (
const auto& chunk : element_chunks_) {
736 ElementContext elemCtx(simulator_);
738 for (
const auto& elem : chunk) {
739 elemCtx.updatePrimaryStencil(elem);
740 elemCtx.updatePrimaryIntensiveQuantities(0);
741 const Scalar extrusionFactor = elemCtx.intensiveQuantities( 0, 0).extrusionFactor();
742 const Scalar scvVolume = elemCtx.stencil(0).subControlVolume( 0).volume() * extrusionFactor;
743 const unsigned globalDofIdx = elemCtx.globalSpaceIndex(0, 0);
745 for (
auto& tr : tbatch) {
746 if (tr.numTracer() == 0) {
751 updateElem<Free>(tr, scvVolume, globalDofIdx);
752 updateElem<Solution>(tr, scvVolume, globalDofIdx);
758 template<TracerTypeIdx Index,
class TrRe>
759 void copyForOutput(TrRe& tr,
760 const std::vector<TracerVector>& dx,
763 const unsigned globalDofIdx,
764 std::vector<TracerVectorSingle>& sc)
766 constexpr Scalar tol_gas_sat = 1e-6;
767 tr.concentration_[tIdx][globalDofIdx][
Index] -= dx[tIdx][globalDofIdx][
Index];
768 if (tr.concentration_[tIdx][globalDofIdx][Index] < 0.0 || S < tol_gas_sat) {
769 tr.concentration_[tIdx][globalDofIdx][
Index] = 0.0;
771 sc[tr.idx_[tIdx]][globalDofIdx] = tr.concentration_[tIdx][globalDofIdx][
Index];
774 template<TracerTypeIdx Index,
class TrRe>
775 void assignRates(
const TrRe& tr,
780 std::vector<WellTracerRate<Scalar>>& tracerRate,
781 std::vector<MSWellTracerRate<Scalar>>* mswTracerRate,
782 std::vector<WellTracerRate<Scalar>>& splitRate)
785 for (
int tIdx = 0; tIdx < tr.numTracer(); ++tIdx) {
787 const Scalar delta = rate * tr.concentration_[tIdx][I][
Index];
788 tracerRate[tr.idx_[tIdx]].rate += delta;
789 splitRate[tr.idx_[tIdx]].rate += delta;
790 if (eclWell.isMultiSegment()) {
791 (*mswTracerRate)[tr.idx_[tIdx]].rate[eclWell.getConnections().get(i).segment()] += delta;
797 void advanceTracerFields()
799 assembleTracerEquations_();
801 for (
auto& tr : tbatch) {
802 if (tr.numTracer() == 0) {
808 std::vector<TracerVector> dx(tr.concentration_);
809 for (
int tIdx = 0; tIdx < tr.numTracer(); ++tIdx) {
813 const bool converged = this->linearSolveBatchwise_(*tr.mat, dx, tr.residual_);
815 OpmLog::warning(
"### Tracer model: Linear solver did not converge. ###");
818 OPM_TIMEBLOCK(tracerPost);
820 for (
int tIdx = 0; tIdx < tr.numTracer(); ++tIdx) {
821 for (std::size_t globalDofIdx = 0; globalDofIdx < tr.concentration_[tIdx].size(); ++globalDofIdx) {
824 const auto& intQuants = simulator_.model().intensiveQuantities(globalDofIdx, 0);
825 const auto& fs = intQuants.fluidState();
826 const Scalar Sf = decay<Scalar>(fs.saturation(tr.phaseIdx_));
829 if (tr.phaseIdx_ == FluidSystem::gasPhaseIdx && FluidSystem::enableDissolvedGas()) {
830 Ss = decay<Scalar>(fs.saturation(FluidSystem::oilPhaseIdx));
832 else if (tr.phaseIdx_ == FluidSystem::oilPhaseIdx && FluidSystem::enableVaporizedOil()) {
833 Ss = decay<Scalar>(fs.saturation(FluidSystem::gasPhaseIdx));
836 copyForOutput<Free>(tr, dx, Sf, tIdx, globalDofIdx, this->freeTracerConcentration_);
837 copyForOutput<Solution>(tr, dx, Ss, tIdx, globalDofIdx, this->solTracerConcentration_);
842 const auto& wellPtrs = simulator_.problem().wellModel().localNonshutWells();
843 for (
const auto& wellPtr : wellPtrs) {
844 const auto& eclWell = wellPtr->wellEcl();
847 if (!eclWell.isProducer()) {
851 Scalar rateWellPos = 0.0;
852 Scalar rateWellNeg = 0.0;
853 const std::size_t well_index = simulator_.problem().wellModel().wellState().index(eclWell.name()).value();
854 const auto& ws = simulator_.problem().wellModel().wellState().well(well_index);
855 auto& tracerRate = this->wellTracerRate_[eclWell.seqIndex()];
856 auto& freeTracerRate = this->wellFreeTracerRate_[eclWell.seqIndex()];
857 auto& solTracerRate = this->wellSolTracerRate_[eclWell.seqIndex()];
858 auto* mswTracerRate = eclWell.isMultiSegment() ? &this->mSwTracerRate_[eclWell.seqIndex()] :
nullptr;
860 for (std::size_t i = 0; i < ws.perf_data.size(); ++i) {
861 const auto I = ws.perf_data.cell_index[i];
862 const Scalar rate = wellPtr->volumetricSurfaceRateForConnection(I, tr.phaseIdx_);
865 if (tr.phaseIdx_ == FluidSystem::oilPhaseIdx && FluidSystem::enableVaporizedOil()) {
866 rate_s = ws.perf_data.phase_mixing_rates[i][ws.vaporized_oil];
868 else if (tr.phaseIdx_ == FluidSystem::gasPhaseIdx && FluidSystem::enableDissolvedGas()) {
869 rate_s = ws.perf_data.phase_mixing_rates[i][ws.dissolved_gas];
875 const Scalar rate_f = rate - rate_s;
876 assignRates<Free>(tr, eclWell, i, I, rate_f,
877 tracerRate, mswTracerRate, freeTracerRate);
878 assignRates<Solution>(tr, eclWell, i, I, rate_s,
879 tracerRate, mswTracerRate, solTracerRate);
894 const Scalar official_well_rate_total =
895 simulator_.problem().wellModel().wellState().well(well_index).surface_rates[tr.phaseIdx_];
897 const Scalar rateWellTotal = official_well_rate_total;
899 if (rateWellTotal > rateWellNeg) {
900 constexpr Scalar bucketPrDay = 10.0 / (1000. * 3600. * 24.);
901 const Scalar factor = (rateWellTotal < -bucketPrDay) ? rateWellTotal / rateWellNeg : 0.0;
902 for (
int tIdx = 0; tIdx < tr.numTracer(); ++tIdx) {
903 tracerRate[tIdx].rate *= factor;
910 Simulator& simulator_;
920 template <
typename TV>
923 std::vector<int> idx_;
925 std::vector<TV> concentrationInitial_;
926 std::vector<TV> concentration_;
927 std::vector<TV> storageOfTimeIndex1_;
928 std::vector<TV> residual_;
929 std::unique_ptr<TracerMatrix> mat;
931 bool operator==(
const TracerBatch& rhs)
const
933 return this->concentrationInitial_ == rhs.concentrationInitial_ &&
934 this->concentration_ == rhs.concentration_;
937 static TracerBatch serializationTestObject()
939 TracerBatch<TV> result(4);
940 result.idx_ = {1,2,3};
941 result.concentrationInitial_ = {5.0, 6.0};
942 result.concentration_ = {7.0, 8.0};
943 result.storageOfTimeIndex1_ = {9.0, 10.0, 11.0};
944 result.residual_ = {12.0, 13.0};
949 template<
class Serializer>
950 void serializeOp(Serializer& serializer)
952 serializer(concentrationInitial_);
953 serializer(concentration_);
956 TracerBatch(
int phaseIdx = 0) : phaseIdx_(phaseIdx) {}
958 int numTracer()
const
959 {
return idx_.size(); }
961 void addTracer(
const int idx,
const TV& concentration)
963 const int numGridDof = concentration.size();
964 idx_.emplace_back(idx);
965 concentrationInitial_.emplace_back(concentration);
966 concentration_.emplace_back(concentration);
967 residual_.emplace_back(numGridDof);
968 storageOfTimeIndex1_.emplace_back(numGridDof);
972 std::array<TracerBatch<TracerVector>,numPhases> tbatch;
976 std::array<std::array<std::vector<Scalar>,numPhases>,2> vol1_;
977 std::array<std::array<std::vector<Scalar>,numPhases>,2> dVol_;
978 ElementChunks<GridView, Dune::Partitions::All> element_chunks_;