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