Transmissibility_impl.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*/
23#ifndef OPM_TRANSMISSIBILITY_IMPL_HPP
24#define OPM_TRANSMISSIBILITY_IMPL_HPP
25
26#ifndef OPM_TRANSMISSIBILITY_HPP
27#include <config.h>
29#endif
30
31#include <dune/common/version.hh>
32#include <dune/grid/common/mcmgmapper.hh>
33
34#include <opm/common/OpmLog/KeywordLocation.hpp>
35#include <opm/common/utility/ThreadSafeMapBuilder.hpp>
36
37#include <opm/grid/CpGrid.hpp>
38#include <opm/grid/utility/ElementChunks.hpp>
39
40#include <opm/input/eclipse/EclipseState/EclipseState.hpp>
41#include <opm/input/eclipse/EclipseState/Grid/FaceDir.hpp>
42#include <opm/input/eclipse/EclipseState/Grid/FieldPropsManager.hpp>
43#include <opm/input/eclipse/EclipseState/Grid/TransMult.hpp>
44#include <opm/input/eclipse/Units/Units.hpp>
45
47
48#include <algorithm>
49#include <array>
50#include <cassert>
51#include <cmath>
52#include <cstddef>
53#include <cstdint>
54#include <functional>
55#include <initializer_list>
56#include <sstream>
57#include <stdexcept>
58#include <type_traits>
59#include <utility>
60#include <vector>
61
62#include <fmt/format.h>
63
64namespace Opm {
65
66namespace details {
67
68 constexpr unsigned elemIdxShift = 32; // bits
69
70 std::uint64_t isId(std::uint32_t elemIdx1, std::uint32_t elemIdx2)
71 {
72 const std::uint32_t elemAIdx = std::min(elemIdx1, elemIdx2);
73 const std::uint64_t elemBIdx = std::max(elemIdx1, elemIdx2);
74
75 return (elemBIdx << elemIdxShift) + elemAIdx;
76 }
77
78 std::pair<std::uint32_t, std::uint32_t> isIdReverse(const std::uint64_t& id)
79 {
80 // Assigning an unsigned integer to a narrower type discards the most significant bits.
81 // See "The C programming language", section A.6.2.
82 // NOTE that the ordering of element A and B may have changed
83 const std::uint32_t elemAIdx = static_cast<uint32_t>(id);
84 const std::uint32_t elemBIdx = (id - elemAIdx) >> elemIdxShift;
85
86 return std::make_pair(elemAIdx, elemBIdx);
87 }
88
89 std::uint64_t directionalIsId(std::uint32_t elemIdx1, std::uint32_t elemIdx2)
90 {
91 return (std::uint64_t(elemIdx1) << elemIdxShift) + elemIdx2;
92 }
93}
94
95template<class Grid, class GridView, class ElementMapper, class CartesianIndexMapper, class Scalar>
97Transmissibility(const EclipseState& eclState,
98 const GridView& gridView,
99 const CartesianIndexMapper& cartMapper,
100 const Grid& grid,
101 std::function<std::array<double,dimWorld>(int)> centroids,
102 bool enableEnergy,
103 bool enableDiffusivity,
104 bool enableDispersivity)
105 : eclState_(eclState)
106 , gridView_(gridView)
107 , cartMapper_(cartMapper)
108 , grid_(grid)
109 , centroids_(centroids)
110 , enableEnergy_(enableEnergy)
111 , enableDiffusivity_(enableDiffusivity)
112 , enableDispersivity_(enableDispersivity)
113 , lookUpData_(gridView)
114 , lookUpCartesianData_(gridView, cartMapper)
115{
116 const UnitSystem& unitSystem = eclState_.getDeckUnitSystem();
117 transmissibilityThreshold_ = unitSystem.parse("Transmissibility").getSIScaling() * 1e-6;
118}
119
120template<class Grid, class GridView, class ElementMapper, class CartesianIndexMapper, class Scalar>
122transmissibility(unsigned elemIdx1, unsigned elemIdx2) const
123{
124 return trans_.at(details::isId(elemIdx1, elemIdx2));
125}
126
127template<class Grid, class GridView, class ElementMapper, class CartesianIndexMapper, class Scalar>
129transmissibilityBoundary(unsigned elemIdx, unsigned boundaryFaceIdx) const
130{
131 return transBoundary_.at(std::make_pair(elemIdx, boundaryFaceIdx));
132}
133
134template<class Grid, class GridView, class ElementMapper, class CartesianIndexMapper, class Scalar>
136thermalHalfTrans(unsigned insideElemIdx, unsigned outsideElemIdx) const
137{
138 return thermalHalfTrans_.at(details::directionalIsId(insideElemIdx, outsideElemIdx));
139}
140
141template<class Grid, class GridView, class ElementMapper, class CartesianIndexMapper, class Scalar>
143halfTransmissibility(unsigned insideElemIdx, unsigned outsideElemIdx) const
144{
145 if (!storeHalfTrans_) {
146 // Without this the caller gets a bare std::out_of_range from the empty
147 // map. One branch on a bool, and this is not a hot path: only the
148 // adjoint code asks for these today.
149 OPM_THROW(std::logic_error,
150 "One-sided half transmissibilities were not stored. "
151 "Call setStoreHalfTrans(true) before update() to have them "
152 "computed; they are off by default because only the adjoint "
153 "code needs them.");
154 }
155
156 return halfTrans_.at(details::directionalIsId(insideElemIdx, outsideElemIdx));
157}
158
159template<class Grid, class GridView, class ElementMapper, class CartesianIndexMapper, class Scalar>
161thermalHalfTransBoundary(unsigned insideElemIdx, unsigned boundaryFaceIdx) const
162{
163 return thermalHalfTransBoundary_.at(std::make_pair(insideElemIdx, boundaryFaceIdx));
164}
165
166template<class Grid, class GridView, class ElementMapper, class CartesianIndexMapper, class Scalar>
167const std::map<std::pair<unsigned, unsigned>, Scalar>& Transmissibility<Grid,GridView,ElementMapper,CartesianIndexMapper,Scalar>::
169{
170 return thermalHalfTransBoundary_;
171}
172
173template<class Grid, class GridView, class ElementMapper, class CartesianIndexMapper, class Scalar>
175diffusivity(unsigned elemIdx1, unsigned elemIdx2) const
176{
177 if (diffusivity_.empty())
178 return 0.0;
179
180 return diffusivity_.at(details::isId(elemIdx1, elemIdx2));
181}
182
183template<class Grid, class GridView, class ElementMapper, class CartesianIndexMapper, class Scalar>
185dispersivity(unsigned elemIdx1, unsigned elemIdx2) const
186{
187 if (dispersivity_.empty())
188 return 0.0;
189
190 return dispersivity_.at(details::isId(elemIdx1, elemIdx2));
191}
192
193template<class Grid, class GridView, class ElementMapper, class CartesianIndexMapper, class Scalar>
195update(bool global, const TransUpdateQuantities update_quantities,
196 const std::function<unsigned int(unsigned int)>& map, const bool applyNncMultregT)
197{
198 // whether only update the permeability related transmissibility
199 const bool onlyTrans = (update_quantities == TransUpdateQuantities::Trans);
200 const auto& cartDims = cartMapper_.cartesianDimensions();
201 const auto& transMult = eclState_.getTransMult();
202 const auto& comm = gridView_.comm();
203 ElementMapper elemMapper(gridView_, Dune::mcmgElementLayout());
204
205 unsigned numElements = elemMapper.size();
206 // get the ntg values, the ntg values are modified for the cells merged with minpv
207 const std::vector<double>& ntg = this->lookUpData_.assignFieldPropsDoubleOnLeaf(eclState_.fieldProps(), "NTG");
208 const bool updateDiffusivity = eclState_.getSimulationConfig().isDiffusive();
209 const bool updateDispersivity = eclState_.getSimulationConfig().rock_config().dispersion();
210
211 const bool disableNNC = eclState_.getSimulationConfig().useNONNC();
212
213 if (map) {
214 extractPermeability_(map);
215 }
216 else {
217 extractPermeability_();
218 }
219
220 const int num_threads = ThreadManager::maxThreads();
221
222 // reserving some space in the hashmap upfront saves quite a bit of time because
223 // resizes are costly for hashmaps and there would be quite a few of them if we
224 // would not have a rough idea of how large the final map will be (the rough idea
225 // is a conforming Cartesian grid).
226 trans_.clear();
227 if (num_threads == 1) {
228 trans_.reserve(numElements*3*1.05);
229 }
230
231 transBoundary_.clear();
232
233 if (storeHalfTrans_) {
234 halfTrans_.clear();
235 if (num_threads == 1) {
236 halfTrans_.reserve(numElements*6*1.05);
237 }
238 }
239
240 // if energy is enabled, let's do the same for the "thermal half transmissibilities"
241 if ( enableEnergy_ && !onlyTrans) {
242 thermalHalfTrans_.clear();
243 if (num_threads == 1) {
244 thermalHalfTrans_.reserve(numElements*6*1.05);
245 }
246
247 thermalHalfTransBoundary_.clear();
248 }
249
250 // if diffusion is enabled, let's do the same for the "diffusivity"
251 if (updateDiffusivity && !onlyTrans) {
252 diffusivity_.clear();
253 if (num_threads == 1) {
254 diffusivity_.reserve(numElements*3*1.05);
255 }
256 extractPorosity_();
257 }
258
259 // if dispersion is enabled, let's do the same for the "dispersivity"
260 if (updateDispersivity && !onlyTrans) {
261 dispersivity_.clear();
262 if (num_threads == 1) {
263 dispersivity_.reserve(numElements*3*1.05);
264 }
265 extractDispersion_();
266 }
267
268 // The MULTZ needs special case if the option is ALL
269 // Then the smallest multiplier is applied.
270 // Default is to apply the top and bottom multiplier
271 bool useSmallestMultiplier;
272 bool pinchOption4ALL;
273 bool pinchActive;
274 if (comm.rank() == 0) {
275 const auto& eclGrid = eclState_.getInputGrid();
276 pinchActive = eclGrid.isPinchActive();
277 auto pinchTransCalcMode = eclGrid.getPinchOption();
278 useSmallestMultiplier = eclGrid.getMultzOption() == PinchMode::ALL;
279 pinchOption4ALL = (pinchTransCalcMode == PinchMode::ALL);
280 if (pinchOption4ALL) {
281 useSmallestMultiplier = false;
282 }
283 }
284 if (global && comm.size() > 1) {
285 comm.broadcast(&useSmallestMultiplier, 1, 0);
286 comm.broadcast(&pinchOption4ALL, 1, 0);
287 comm.broadcast(&pinchActive, 1, 0);
288 }
289
290 // fill the centroids cache to avoid repeated calculations in loops below
291 centroids_cache_.resize(gridView_.size(0));
292 for (const auto& elem : elements(gridView_)) {
293 const unsigned elemIdx = elemMapper.index(elem);
294 centroids_cache_[elemIdx] = centroids_(elemIdx);
295 }
296
297 auto harmonicMean = [](const Scalar x1, const Scalar x2)
298 {
299 return (std::abs(x1) < 1e-30 || std::abs(x2) < 1e-30)
300 ? 0.0
301 : 1.0 / (1.0 / x1 + 1.0 / x2);
302 };
303
304 auto faceIdToDir = [](int insideFaceIdx)
305 {
306 switch (insideFaceIdx) {
307 case 0:
308 case 1:
309 return FaceDir::XPlus;
310 case 2:
311 case 3:
312 return FaceDir::YPlus;
313 break;
314 case 4:
315 case 5:
316 return FaceDir::ZPlus;
317 default:
318 throw std::logic_error("Could not determine a face direction");
319 }
320 };
321
322 auto halfDiff = [](const DimVector& faceAreaNormal,
323 const unsigned,
324 const DimVector& distVector,
325 const Scalar prop)
326 {
327 return computeHalfDiffusivity_(faceAreaNormal,
328 distVector,
329 prop);
330 };
331
332 ThreadSafeMapBuilder transBoundary(transBoundary_, num_threads,
333 MapBuilderInsertionMode::Insert_Or_Assign);
334 ThreadSafeMapBuilder transMap(trans_, num_threads,
335 MapBuilderInsertionMode::Insert_Or_Assign);
336 ThreadSafeMapBuilder thermalHalfTransBoundary(thermalHalfTransBoundary_, num_threads,
337 MapBuilderInsertionMode::Insert_Or_Assign);
338 ThreadSafeMapBuilder thermalHalfTrans(thermalHalfTrans_, num_threads,
339 MapBuilderInsertionMode::Insert_Or_Assign);
340 ThreadSafeMapBuilder halfTransMap(halfTrans_, num_threads,
341 MapBuilderInsertionMode::Insert_Or_Assign);
342 ThreadSafeMapBuilder diffusivity(diffusivity_, num_threads,
343 MapBuilderInsertionMode::Insert_Or_Assign);
344 ThreadSafeMapBuilder dispersivity(dispersivity_, num_threads,
345 MapBuilderInsertionMode::Insert_Or_Assign);
346
347 const auto& nnc_input = eclState_.getInputNNC().input();
348
349#ifdef _OPENMP
350#pragma omp parallel for
351#endif
352 for (const auto& chunk : ElementChunks(gridView_, Dune::Partitions::all, num_threads)) {
353 for (const auto& elem : chunk) {
354 FaceInfo inside;
355 FaceInfo outside;
356 DimVector faceAreaNormal;
357
358 inside.elemIdx = elemMapper.index(elem);
359 // Get the Cartesian index of the origin cells (parent or equivalent cell on level zero),
360 // for CpGrid with LGRs. For general grids and no LGRs, get the usual Cartesian Index.
361 inside.cartElemIdx = this->lookUpCartesianData_.
362 template getFieldPropCartesianIdx<Grid>(inside.elemIdx);
363
364 auto computeHalf = [this, &faceAreaNormal, &inside, &outside]
365 (const auto& halfComputer,
366 const auto& prop1, const auto& prop2) -> std::array<Scalar,2>
367 {
368 return {
369 halfComputer(faceAreaNormal,
370 inside.faceIdx,
371 distanceVector_(inside.faceCenter, inside.elemIdx),
372 prop1),
373 halfComputer(faceAreaNormal,
374 outside.faceIdx,
375 distanceVector_(outside.faceCenter, outside.elemIdx),
376 prop2)
377 };
378 };
379
380 auto computeHalfMean = [&inside, &outside, &computeHalf, &ntg, &harmonicMean]
381 (const auto& halfComputer, const auto& prop)
382 {
383 auto onesided = computeHalf(halfComputer, prop[inside.elemIdx], prop[outside.elemIdx]);
384 applyNtg_(onesided[0], inside, ntg);
385 applyNtg_(onesided[1], outside, ntg);
386
387 //TODO Add support for multipliers
388 return harmonicMean(onesided[0], onesided[1]);
389 };
390
391 unsigned boundaryIsIdx = 0;
392 for (const auto& intersection : intersections(gridView_, elem)) {
393 // deal with grid boundaries
394 if (intersection.boundary()) {
395 // compute the transmissibilty for the boundary intersection
396 const auto& geometry = intersection.geometry();
397 inside.faceCenter = geometry.center();
398
399 faceAreaNormal = intersection.centerUnitOuterNormal();
400 faceAreaNormal *= geometry.volume();
401
402 Scalar transBoundaryIs =
403 computeHalfTrans_(faceAreaNormal,
404 intersection.indexInInside(),
405 distanceVector_(inside.faceCenter, inside.elemIdx),
406 permeability_[inside.elemIdx]);
407
408 // normally there would be two half-transmissibilities that would be
409 // averaged. on the grid boundary there only is the half
410 // transmissibility of the interior element.
411 applyMultipliers_(transBoundaryIs, intersection.indexInInside(), inside.cartElemIdx, transMult);
412 transBoundary.insert_or_assign(std::make_pair(inside.elemIdx, boundaryIsIdx), transBoundaryIs);
413
414 // for boundary intersections we also need to compute the thermal
415 // half transmissibilities
416 if (enableEnergy_ && !onlyTrans) {
417 Scalar transBoundaryEnergyIs =
418 computeHalfDiffusivity_(faceAreaNormal,
419 distanceVector_(inside.faceCenter, inside.elemIdx),
420 1.0);
421 thermalHalfTransBoundary.insert_or_assign(std::make_pair(inside.elemIdx, boundaryIsIdx),
422 transBoundaryEnergyIs);
423 }
424
425 ++boundaryIsIdx;
426 continue;
427 }
428
429 if (!intersection.neighbor()) {
430 // elements can be on process boundaries, i.e. they are not on the
431 // domain boundary yet they don't have neighbors.
432 ++boundaryIsIdx;
433 continue;
434 }
435
436 const auto& outsideElem = intersection.outside();
437 outside.elemIdx = elemMapper.index(outsideElem);
438
439 // Get the Cartesian index of the origin cells (parent or equivalent cell on level zero),
440 // for CpGrid with LGRs. For general grids and no LGRs, get the usual Cartesian Index.
441 outside.cartElemIdx = this->lookUpCartesianData_.
442 template getFieldPropCartesianIdx<Grid>(outside.elemIdx);
443
444 // we only need to calculate a face's transmissibility
445 // once...
446 // In a parallel run inside.cartElemIdx > outside.cartElemIdx does not imply inside.elemIdx > outside.elemIdx for
447 // ghost cells and we need to use the cartesian index as this will be used when applying Z multipliers
448 // To cover the case where both cells are part of an LGR and as a consequence might have
449 // the same cartesian index, we tie their Cartesian indices and the ones on the leaf grid view.
450 if (std::tie(inside.cartElemIdx, inside.elemIdx) > std::tie(outside.cartElemIdx, outside.elemIdx)) {
451 continue;
452 }
453
454 // local indices of the faces of the inside and
455 // outside elements which contain the intersection
456 inside.faceIdx = intersection.indexInInside();
457 outside.faceIdx = intersection.indexInOutside();
458
459 if (inside.faceIdx == -1) {
460 // NNC. Set zero transmissibility, as it will be
461 // *added to* by applyNncToGridTrans_() later.
462 assert(outside.faceIdx == -1);
463 transMap.insert_or_assign(details::isId(inside.elemIdx, outside.elemIdx), 0.0);
464 if (enableEnergy_ && !onlyTrans) {
465 thermalHalfTrans.insert_or_assign(details::directionalIsId(inside.elemIdx, outside.elemIdx), 0.0);
466 thermalHalfTrans.insert_or_assign(details::directionalIsId(outside.elemIdx, inside.elemIdx), 0.0);
467 }
468
469 if (updateDiffusivity && !onlyTrans) {
470 diffusivity.insert_or_assign(details::isId(inside.elemIdx, outside.elemIdx), 0.0);
471 }
472 if (updateDispersivity && !onlyTrans) {
473 dispersivity.insert_or_assign(details::isId(inside.elemIdx, outside.elemIdx), 0.0);
474 }
475 continue;
476 }
477
478 typename std::is_same<Grid, Dune::CpGrid>::type isCpGrid;
479 computeFaceProperties(intersection,
480 inside,
481 outside,
482 faceAreaNormal,
483 isCpGrid);
484
485 Scalar trans = computeHalfMean(computeHalfTrans_, permeability_);
486
487 if (storeHalfTrans_) {
488 // one-sided half transmissibilities (NTG applied),
489 // for the adjoint permeability chain rule
490 auto onesided = computeHalf(computeHalfTrans_,
491 permeability_[inside.elemIdx],
492 permeability_[outside.elemIdx]);
493 applyNtg_(onesided[0], inside, ntg);
494 applyNtg_(onesided[1], outside, ntg);
495 halfTransMap.insert_or_assign(
497 onesided[0]);
498 halfTransMap.insert_or_assign(
500 onesided[1]);
501 }
502
503 // apply the full face transmissibility multipliers
504 // for the inside ...
505 if (!pinchActive) {
506 if (inside.faceIdx > 3) { // top or bottom
507 auto find_layer = [&cartDims](std::size_t cell) {
508 cell /= cartDims[0];
509 auto k = cell / cartDims[1];
510 return k;
511 };
512 int kup = find_layer(inside.cartElemIdx);
513 int kdown = find_layer(outside.cartElemIdx);
514 // When a grid is a CpGrid with LGRs, insideCartElemIdx coincides with outsideCartElemIdx
515 // for cells on the leaf with the same parent cell on level zero.
516 assert((kup != kdown) || (inside.cartElemIdx == outside.cartElemIdx));
517 if (std::abs(kup -kdown) > 1) {
518 trans = 0.0;
519 }
520 }
521 }
522
523 if (useSmallestMultiplier) {
524 // PINCH(4) == TOPBOT is assumed here as we set useSmallestMultipliers
525 // to false if PINCH(4) == ALL holds
526 // In contrast to the name this will also apply
527 applyAllZMultipliers_(trans, inside, outside, transMult, cartDims);
528 }
529 else {
530 applyMultipliers_(trans, inside.faceIdx, inside.cartElemIdx, transMult);
531 // ... and outside elements
532 applyMultipliers_(trans, outside.faceIdx, outside.cartElemIdx, transMult);
533 }
534
535 bool foundInputNNC = false;
536 if (! nnc_input.empty()) {
537 // Skip region multipliers for overlapping input NNCs (they are handled later)
538 auto it = std::lower_bound(nnc_input.begin(), nnc_input.end(),
539 NNCdata { inside.cartElemIdx, outside.cartElemIdx, 0.0 });
540 foundInputNNC = it != nnc_input.end() && it->cell1 == inside.cartElemIdx && it->cell2 == outside.cartElemIdx;
541 }
542 if (! foundInputNNC) {
543 // apply the region multipliers (cf. the MULTREGT keyword)
544 trans *= transMult.getRegionMultiplier(inside.cartElemIdx,
545 outside.cartElemIdx,
546 faceIdToDir(inside.faceIdx));
547 }
548
549 transMap.insert_or_assign(details::isId(inside.elemIdx, outside.elemIdx), trans);
550
551 // update the "thermal half transmissibility" for the intersection
552 if (enableEnergy_ && !onlyTrans) {
553 const auto half = computeHalf(halfDiff, 1.0, 1.0);
554 // TODO Add support for multipliers
555 thermalHalfTrans.insert_or_assign(details::directionalIsId(inside.elemIdx, outside.elemIdx),
556 half[0]);
557 thermalHalfTrans.insert_or_assign(details::directionalIsId(outside.elemIdx, inside.elemIdx),
558 half[1]);
559 }
560
561 // update the "diffusive half transmissibility" for the intersection
562 if (updateDiffusivity && !onlyTrans) {
563 diffusivity.insert_or_assign(details::isId(inside.elemIdx, outside.elemIdx),
564 computeHalfMean(halfDiff, porosity_));
565 }
566
567 // update the "dispersivity half transmissibility" for the intersection
568 if (updateDispersivity && !onlyTrans) {
569 dispersivity.insert_or_assign(details::isId(inside.elemIdx, outside.elemIdx),
570 computeHalfMean(halfDiff, dispersion_));
571 }
572 }
573 }
574 }
575 centroids_cache_.clear();
576
577#ifdef _OPENMP
578#pragma omp parallel sections
579#endif
580 {
581#ifdef _OPENMP
582#pragma omp section
583#endif
584 transMap.finalize();
585#ifdef _OPENMP
586#pragma omp section
587#endif
588 transBoundary.finalize();
589#ifdef _OPENMP
590#pragma omp section
591#endif
592 thermalHalfTransBoundary.finalize();
593#ifdef _OPENMP
594#pragma omp section
595#endif
596 thermalHalfTrans.finalize();
597#ifdef _OPENMP
598#pragma omp section
599#endif
600 diffusivity.finalize();
601#ifdef _OPENMP
602#pragma omp section
603#endif
604 dispersivity.finalize();
605#ifdef _OPENMP
606#pragma omp section
607#endif
608 halfTransMap.finalize();
609 }
610
611 // Potentially overwrite and/or modify transmissibilities based on input from deck
612 this->updateFromEclState_(global);
613
614 // Create mapping from global to local index
615 std::unordered_map<std::size_t,int> globalToLocal;
616
617 // Loop over all elements (global grid) and store Cartesian index
618 for (const auto& elem : elements(grid_.leafGridView())) {
619 int elemIdx = elemMapper.index(elem);
620 int cartElemIdx = cartMapper_.cartesianIndex(elemIdx);
621 globalToLocal[cartElemIdx] = elemIdx;
622 }
623
624 if (!disableNNC) {
625 // For EDITNNC and EDITNNCR we warn only once
626 // If transmissibility is used for load balancing this will be done
627 // when computing the gobal transmissibilities and all warnings will
628 // be seen in a parallel. Unfortunately, when we do not use transmissibilities
629 // we will only see warnings for the partition of process 0 and also false positives.
630 this->applyPinchNncToGridTrans_(globalToLocal, applyNncMultregT);
631 this->applyNncToGridTrans_(globalToLocal);
632 this->applyEditNncToGridTrans_(globalToLocal);
633 this->applyEditNncrToGridTrans_(globalToLocal);
634 if (applyNncMultregT) {
635 this->applyNncMultreg_(globalToLocal);
636 }
637 warnEditNNC_ = false;
638 }
639
640 // If disableNNC == true, remove all non-neighbouring transmissibilities.
641 // If disableNNC == false, remove very small non-neighbouring transmissibilities.
642 this->removeNonCartesianTransmissibilities_(disableNNC);
643}
644
645template<class Grid, class GridView, class ElementMapper, class CartesianIndexMapper, class Scalar>
648{
649 unsigned numElem = gridView_.size(/*codim=*/0);
650 permeability_.resize(numElem);
651
652 // read the intrinsic permeabilities from the eclState. Note that all arrays
653 // provided by eclState are one-per-cell of "uncompressed" grid, whereas the
654 // simulation grid might remove a few elements. (e.g. because it is distributed
655 // over several processes.)
656 const auto& fp = eclState_.fieldProps();
657 if (fp.has_double("PERMX")) {
658 const std::vector<double>& permxData = this-> lookUpData_.assignFieldPropsDoubleOnLeaf(fp, "PERMX");
659
660 std::vector<double> permyData;
661 if (fp.has_double("PERMY"))
662 permyData = this-> lookUpData_.assignFieldPropsDoubleOnLeaf(fp,"PERMY");
663 else
664 permyData = permxData;
665
666 std::vector<double> permzData;
667 if (fp.has_double("PERMZ"))
668 permzData = this-> lookUpData_.assignFieldPropsDoubleOnLeaf(fp,"PERMZ");
669 else
670 permzData = permxData;
671
672 for (std::size_t dofIdx = 0; dofIdx < numElem; ++ dofIdx) {
673 permeability_[dofIdx] = 0.0;
674 permeability_[dofIdx][0][0] = permxData[dofIdx];
675 permeability_[dofIdx][1][1] = permyData[dofIdx];
676 permeability_[dofIdx][2][2] = permzData[dofIdx];
677 }
678
679 // for now we don't care about non-diagonal entries
680
681 }
682 else
683 throw std::logic_error("Can't read the intrinsic permeability from the ecl state. "
684 "(The PERM{X,Y,Z} keywords are missing)");
685}
686
687template<class Grid, class GridView, class ElementMapper, class CartesianIndexMapper, class Scalar>
689extractPermeability_(const std::function<unsigned int(unsigned int)>& map)
690{
691 unsigned numElem = gridView_.size(/*codim=*/0);
692 permeability_.resize(numElem);
693
694 // read the intrinsic permeabilities from the eclState. Note that all arrays
695 // provided by eclState are one-per-cell of "uncompressed" grid, whereas the
696 // simulation grid might remove a few elements. (e.g. because it is distributed
697 // over several processes.)
698 const auto& fp = eclState_.fieldProps();
699 if (fp.has_double("PERMX")) {
700 const std::vector<double>& permxData =
701 this->lookUpData_.assignFieldPropsDoubleOnLeaf(fp,"PERMX");
702
703 std::vector<double> permyData;
704 if (fp.has_double("PERMY")){
705 permyData = this->lookUpData_.assignFieldPropsDoubleOnLeaf(fp,"PERMY");
706 }
707 else {
708 permyData = permxData;
709 }
710
711 std::vector<double> permzData;
712 if (fp.has_double("PERMZ")) {
713 permzData = this->lookUpData_.assignFieldPropsDoubleOnLeaf(fp,"PERMZ");
714 }
715 else {
716 permzData = permxData;
717 }
718
719 for (std::size_t dofIdx = 0; dofIdx < numElem; ++ dofIdx) {
720 permeability_[dofIdx] = 0.0;
721 std::size_t inputDofIdx = map(dofIdx);
722 permeability_[dofIdx][0][0] = permxData[inputDofIdx];
723 permeability_[dofIdx][1][1] = permyData[inputDofIdx];
724 permeability_[dofIdx][2][2] = permzData[inputDofIdx];
725 }
726
727 // for now we don't care about non-diagonal entries
728 }
729 else {
730 throw std::logic_error("Can't read the intrinsic permeability from the ecl state. "
731 "(The PERM{X,Y,Z} keywords are missing)");
732 }
733}
734
735template<class Grid, class GridView, class ElementMapper, class CartesianIndexMapper, class Scalar>
738{
739 // read the intrinsic porosity from the eclState. Note that all arrays
740 // provided by eclState are one-per-cell of "uncompressed" grid, whereas the
741 // simulation grid might remove a few elements. (e.g. because it is distributed
742 // over several processes.)
743 const auto& fp = eclState_.fieldProps();
744 if (fp.has_double("PORO")) {
745 if constexpr (std::is_same_v<Scalar,double>) {
746 porosity_ = this->lookUpData_.assignFieldPropsDoubleOnLeaf(fp,"PORO");
747 }
748 else {
749 const auto por = this->lookUpData_.assignFieldPropsDoubleOnLeaf(fp,"PORO");
750 porosity_.resize(por.size());
751 std::ranges::copy(por, porosity_.begin());
752 }
753 }
754 else {
755 throw std::logic_error("Can't read the porosity from the ecl state. "
756 "(The PORO keywords are missing)");
757 }
758}
759
760template<class Grid, class GridView, class ElementMapper, class CartesianIndexMapper, class Scalar>
763{
764 if (!enableDispersivity_) {
765 throw std::runtime_error("Dispersion disabled at compile time, but the deck "
766 "contains the DISPERC keyword.");
767 }
768 const auto& fp = eclState_.fieldProps();
769 if constexpr (std::is_same_v<Scalar,double>) {
770 dispersion_ = this->lookUpData_.assignFieldPropsDoubleOnLeaf(fp,"DISPERC");
771 }
772 else {
773 const auto disp = this->lookUpData_.assignFieldPropsDoubleOnLeaf(fp,"DISPERC");
774 dispersion_.resize(disp.size());
775 std::ranges::copy(disp, dispersion_.begin());
776 }
777}
778
779template<class Grid, class GridView, class ElementMapper, class CartesianIndexMapper, class Scalar>
782{
783 const auto& cartDims = cartMapper_.cartesianDimensions();
784 for (auto&& trans: trans_) {
785 //either remove all NNC transmissibilities or those less than the threshold (by default 1e-6 in the deck's unit system)
786 if (removeAll || trans.second < transmissibilityThreshold_) {
787 const auto& id = trans.first;
788 const auto& elements = details::isIdReverse(id);
789 int gc1 = std::min(cartMapper_.cartesianIndex(elements.first), cartMapper_.cartesianIndex(elements.second));
790 int gc2 = std::max(cartMapper_.cartesianIndex(elements.first), cartMapper_.cartesianIndex(elements.second));
791
792 // only adjust the NNCs
793 // When LGRs, all neighbors in the LGR are cartesian neighbours on the level grid representing the LGR.
794 // When elements on the leaf grid view have the same parent cell, gc1 and gc2 coincide.
795 if (gc2 - gc1 == 1 || gc2 - gc1 == cartDims[0] || gc2 - gc1 == cartDims[0]*cartDims[1] || gc2 - gc1 == 0) {
796 continue;
797 }
798
799 trans.second = 0.0;
800 }
801 }
802}
803
804template<class Grid, class GridView, class ElementMapper, class CartesianIndexMapper, class Scalar>
806applyAllZMultipliers_(Scalar& trans,
807 const FaceInfo& inside,
808 const FaceInfo& outside,
809 const TransMult& transMult,
810 const std::array<int, dimWorld>& cartDims)
811{
812 if (grid_.maxLevel() > 0) {
813 OPM_THROW(std::invalid_argument, "MULTZ not support with LGRS, yet.");
814 }
815 if (inside.faceIdx > 3) { // top or or bottom
816 assert(inside.faceIdx == 5); // as insideCartElemIdx < outsideCartElemIdx holds for the Z column
817 // For CpGrid with LGRs, insideCartElemIdx == outsideCartElemIdx when cells on the leaf have the same parent cell on level zero.
818 assert(outside.cartElemIdx >= inside.cartElemIdx);
819 unsigned lastCartElemIdx;
820 if (outside.cartElemIdx == inside.cartElemIdx) {
821 lastCartElemIdx = outside.cartElemIdx;
822 }
823 else {
824 lastCartElemIdx = outside.cartElemIdx - cartDims[0]*cartDims[1];
825 }
826 // Last multiplier using (Z+)*(Z-)
827 Scalar mult = transMult.getMultiplier(lastCartElemIdx , FaceDir::ZPlus) *
828 transMult.getMultiplier(outside.cartElemIdx , FaceDir::ZMinus);
829
830 // pick the smallest multiplier using (Z+)*(Z-) while looking down
831 // the pillar until reaching the other end of the connection
832 for (auto cartElemIdx = inside.cartElemIdx; cartElemIdx < lastCartElemIdx;) {
833 auto multiplier = transMult.getMultiplier(cartElemIdx, FaceDir::ZPlus);
834 cartElemIdx += cartDims[0]*cartDims[1];
835 multiplier *= transMult.getMultiplier(cartElemIdx, FaceDir::ZMinus);
836 mult = std::min(mult, static_cast<Scalar>(multiplier));
837 }
838
839 trans *= mult;
840 }
841 else {
842 applyMultipliers_(trans, inside.faceIdx, inside.cartElemIdx, transMult);
843 applyMultipliers_(trans, outside.faceIdx, outside.cartElemIdx, transMult);
844 }
845}
846
847template<class Grid, class GridView, class ElementMapper, class CartesianIndexMapper, class Scalar>
849updateFromEclState_(bool global)
850{
851 const FieldPropsManager* fp =
852 (global) ? &(eclState_.fieldProps()) :
853 &(eclState_.globalFieldProps());
854
855 std::array<bool,3> is_tran {fp->tran_active("TRANX"),
856 fp->tran_active("TRANY"),
857 fp->tran_active("TRANZ")};
858
859 if (!(is_tran[0] || is_tran[1] || is_tran[2])) {
860 // Skip unneeded expensive traversals
861 return;
862 }
863
864 std::array<std::string, 3> keywords {"TRANX", "TRANY", "TRANZ"};
865 std::array<std::vector<double>,3> trans = createTransmissibilityArrays_(is_tran);
866 auto key = keywords.begin();
867 auto perform = is_tran.begin();
868
869 for (auto it = trans.begin(); it != trans.end(); ++it, ++key, ++perform) {
870 if (*perform) {
871 if (grid_.maxLevel() > 0) {
872 OPM_THROW(std::invalid_argument, "Calculations on TRANX/TRANY/TRANZ arrays are not support with LGRS, yet.");
873 }
874 fp->apply_tran(*key, *it);
875 }
876 }
877
878 resetTransmissibilityFromArrays_(is_tran, trans);
879}
880
881template<class Grid, class GridView, class ElementMapper, class CartesianIndexMapper, class Scalar>
882std::array<std::vector<double>,3>
884createTransmissibilityArrays_(const std::array<bool,3>& is_tran)
885{
886 const auto& cartDims = cartMapper_.cartesianDimensions();
887 ElementMapper elemMapper(gridView_, Dune::mcmgElementLayout());
888
889 auto numElem = gridView_.size(/*codim=*/0);
890 std::array<std::vector<double>,3> trans = {
891 std::vector<double>(is_tran[0] ? numElem : 0, 0),
892 std::vector<double>(is_tran[1] ? numElem : 0, 0),
893 std::vector<double>(is_tran[2] ? numElem : 0, 0)
894 };
895
896 // compute the transmissibilities for all intersections
897 for (const auto& elem : elements(gridView_)) {
898 for (const auto& intersection : intersections(gridView_, elem)) {
899 // store intersection, this might be costly
900 if (!intersection.neighbor()) {
901 continue; // intersection is on the domain boundary
902 }
903
904 // In the EclState TRANX[c1] is transmissibility in X+
905 // direction. we only store transmissibilities in the +
906 // direction. Same for Y and Z. Ordering of compressed (c1,c2) and cartesian index
907 // (gc1, gc2) is coherent (c1 < c2 <=> gc1 < gc2) only in a serial run.
908 // In a parallel run this only holds in the interior as elements in the
909 // ghost overlap region might be ordered after the others. Hence we need
910 // to use the cartesian index to select the compressed index where to store
911 // the transmissibility value.
912 // c1 < c2 <=> gc1 < gc2 is no longer true (even in serial) when the grid is a
913 // CpGrid with LGRs. When cells c1 and c2 have the same parent
914 // cell on level zero, then gc1 == gc2.
915 unsigned c1 = elemMapper.index(intersection.inside());
916 unsigned c2 = elemMapper.index(intersection.outside());
917 int gc1 = cartMapper_.cartesianIndex(c1);
918 int gc2 = cartMapper_.cartesianIndex(c2);
919 if (std::tie(gc1, c1) > std::tie(gc2, c2)) {
920 // we only need to handle each connection once, thank you.
921 // We do this when gc1 is smaller than the other to find the
922 // correct place to store in parallel when ghost/overlap elements
923 // are ordered last
924 continue;
925 }
926
927 auto isID = details::isId(c1, c2);
928
929 // For CpGrid with LGRs, when leaf grid view cells with indices c1 and c2
930 // have the same parent cell on level zero, then gc2 - gc1 == 0. In that case,
931 // 'intersection.indexInSIde()' needed to be checked to determine the direction, i.e.
932 // add in the if/else-if 'gc2 == gc1 && intersection.indexInInside() == ... '
933 if ((gc2 - gc1 == 1 || (gc2 == gc1 && (intersection.indexInInside() == 0 || intersection.indexInInside() == 1)))
934 && cartDims[0] > 1)
935 {
936 if (is_tran[0]) {
937 // set simulator internal transmissibilities to values from inputTranx
938 trans[0][c1] = trans_[isID];
939 }
940 }
941 else if ((gc2 - gc1 == cartDims[0] || (gc2 == gc1 && (intersection.indexInInside() == 2 || intersection.indexInInside() == 3)))
942 && cartDims[1] > 1)
943 {
944 if (is_tran[1]) {
945 // set simulator internal transmissibilities to values from inputTrany
946 trans[1][c1] = trans_[isID];
947 }
948 }
949 else if (gc2 - gc1 == cartDims[0]*cartDims[1] ||
950 (gc2 == gc1 && (intersection.indexInInside() == 4 || intersection.indexInInside() == 5)))
951 {
952 if (is_tran[2]) {
953 // set simulator internal transmissibilities to values from inputTranz
954 trans[2][c1] = trans_[isID];
955 }
956 }
957 // else.. We don't support modification of NNC at the moment.
958 }
959 }
960
961 return trans;
962}
963
964template<class Grid, class GridView, class ElementMapper, class CartesianIndexMapper, class Scalar>
966resetTransmissibilityFromArrays_(const std::array<bool,3>& is_tran,
967 const std::array<std::vector<double>,3>& trans)
968{
969 const auto& cartDims = cartMapper_.cartesianDimensions();
970 ElementMapper elemMapper(gridView_, Dune::mcmgElementLayout());
971
972 // compute the transmissibilities for all intersections
973 for (const auto& elem : elements(gridView_)) {
974 for (const auto& intersection : intersections(gridView_, elem)) {
975 if (!intersection.neighbor()) {
976 continue; // intersection is on the domain boundary
977 }
978
979 // In the EclState TRANX[c1] is transmissibility in X+
980 // direction. we only store transmissibilities in the +
981 // direction. Same for Y and Z. Ordering of compressed (c1,c2) and cartesian index
982 // (gc1, gc2) is coherent (c1 < c2 <=> gc1 < gc2) only in a serial run.
983 // In a parallel run this only holds in the interior as elements in the
984 // ghost overlap region might be ordered after the others. Hence we need
985 // to use the cartesian index to select the compressed index where to store
986 // the transmissibility value.
987 // c1 < c2 <=> gc1 < gc2 is no longer true (even in serial) when the grid is a
988 // CpGrid with LGRs. When cells c1 and c2 have the same parent
989 // cell on level zero, then gc1 == gc2.
990 unsigned c1 = elemMapper.index(intersection.inside());
991 unsigned c2 = elemMapper.index(intersection.outside());
992 int gc1 = cartMapper_.cartesianIndex(c1);
993 int gc2 = cartMapper_.cartesianIndex(c2);
994 if (std::tie(gc1, c1) > std::tie(gc2, c2)) {
995 // we only need to handle each connection once, thank you.
996 // We do this when gc1 is smaller than the other to find the
997 // correct place to read in parallel when ghost/overlap elements
998 // are ordered last
999 continue;
1000 }
1001
1002 auto isID = details::isId(c1, c2);
1003
1004 // For CpGrid with LGRs, when leaf grid view cells with indices c1 and c2
1005 // have the same parent cell on level zero, then gc2 - gc1 == 0. In that case,
1006 // 'intersection.indexInSIde()' needed to be checked to determine the direction, i.e.
1007 // add in the if/else-if 'gc2 == gc1 && intersection.indexInInside() == ... '
1008 if ((gc2 - gc1 == 1 || (gc2 == gc1 && (intersection.indexInInside() == 0 || intersection.indexInInside() == 1)))
1009 && cartDims[0] > 1)
1010 {
1011 if (is_tran[0]) {
1012 // set simulator internal transmissibilities to values from inputTranx
1013 trans_[isID] = trans[0][c1];
1014 }
1015 }
1016 else if ((gc2 - gc1 == cartDims[0] || (gc2 == gc1 && (intersection.indexInInside() == 2|| intersection.indexInInside() == 3)))
1017 && cartDims[1] > 1)
1018 {
1019 if (is_tran[1]) {
1020 // set simulator internal transmissibilities to values from inputTrany
1021 trans_[isID] = trans[1][c1];
1022 }
1023 }
1024 else if (gc2 - gc1 == cartDims[0]*cartDims[1] ||
1025 (gc2 == gc1 && (intersection.indexInInside() == 4 || intersection.indexInInside() == 5)))
1026 {
1027 if (is_tran[2]) {
1028 // set simulator internal transmissibilities to values from inputTranz
1029 trans_[isID] = trans[2][c1];
1030 }
1031 }
1032
1033 // else.. We don't support modification of NNC at the moment.
1034 }
1035 }
1036}
1037
1038template<class Grid, class GridView, class ElementMapper, class CartesianIndexMapper, class Scalar>
1039template<class Intersection>
1041computeFaceProperties(const Intersection& intersection,
1042 FaceInfo& inside,
1043 FaceInfo& outside,
1044 DimVector& faceAreaNormal,
1045 /*isCpGrid=*/std::false_type) const
1046{
1047 // default implementation for DUNE grids
1048 const auto& geometry = intersection.geometry();
1049 outside.faceCenter = inside.faceCenter = geometry.center();
1050
1051 faceAreaNormal = intersection.centerUnitOuterNormal();
1052 faceAreaNormal *= geometry.volume();
1053}
1054
1055template<class Grid, class GridView, class ElementMapper, class CartesianIndexMapper, class Scalar>
1056template<class Intersection>
1058computeFaceProperties(const Intersection& intersection,
1059 FaceInfo& inside,
1060 FaceInfo& outside,
1061 DimVector& faceAreaNormal,
1062 /*isCpGrid=*/std::true_type) const
1063{
1064 int faceIdx = intersection.id();
1065
1066 if (grid_.maxLevel() == 0) {
1067 inside.faceCenter = grid_.faceCenterEcl(inside.elemIdx, inside.faceIdx, intersection);
1068 outside.faceCenter = grid_.faceCenterEcl(outside.elemIdx, outside.faceIdx, intersection);
1069 faceAreaNormal = grid_.faceAreaNormalEcl(faceIdx);
1070 }
1071 else {
1072 if ((intersection.inside().level() != intersection.outside().level())) {
1073 // For CpGrid with LGRs, intersection laying on the boundary of an LGR, having two neighboring cells:
1074 // one coarse neighboring cell and one refined neighboring cell, we get the corresponding parent
1075 // intersection (from level 0), and use the center of the parent intersection for the coarse
1076 // neighboring cell.
1077
1078 // Get parent intersection and its geometry
1079 const auto& parentIntersection =
1080 grid_.getParentIntersectionFromLgrBoundaryFace(intersection);
1081 const auto& parentIntersectionGeometry = parentIntersection.geometry();
1082
1083 // For the coarse neighboring cell, take the center of the parent intersection.
1084 // For the refined neighboring cell, take the 'usual' center.
1085 inside.faceCenter = (intersection.inside().level() == 0)
1086 ? parentIntersectionGeometry.center()
1087 : grid_.faceCenterEcl(inside.elemIdx, inside.faceIdx, intersection);
1088 outside.faceCenter = (intersection.outside().level() == 0)
1089 ? parentIntersectionGeometry.center()
1090 : grid_.faceCenterEcl(outside.elemIdx, outside.faceIdx, intersection);
1091
1092 // For some computations, it seems to be benefitial to replace the actual area of the refined face, by
1093 // the area of its parent face.
1094 // faceAreaNormal = parentIntersection.centerUnitOuterNormal();
1095 // faceAreaNormal *= parentIntersectionGeometry.volume();
1096
1098 faceAreaNormal = intersection.centerUnitOuterNormal();
1099 faceAreaNormal *= intersection.geometry().volume();
1100 }
1101 else {
1102 assert(intersection.inside().level() == intersection.outside().level());
1103
1104 inside.faceCenter = grid_.faceCenterEcl(inside.elemIdx, inside.faceIdx, intersection);
1105 outside.faceCenter = grid_.faceCenterEcl(outside.elemIdx, outside.faceIdx, intersection);
1106
1107 // When the CpGrid has LGRs, we compute the face area normal differently.
1108 if (intersection.inside().level() > 0) { // remove intersection.inside().level() > 0
1109 faceAreaNormal = intersection.centerUnitOuterNormal();
1110 faceAreaNormal *= intersection.geometry().volume();
1111 }
1112 else {
1113 faceAreaNormal = grid_.faceAreaNormalEcl(faceIdx);
1114 }
1115 }
1116 }
1117}
1118
1119template<class Grid, class GridView, class ElementMapper, class CartesianIndexMapper, class Scalar>
1120void
1122applyPinchNncToGridTrans_(const std::unordered_map<std::size_t,int>& cartesianToCompressed,
1123 const bool applyNncMultregT)
1124{
1125 const auto& pinchNnc = eclState_.getPinchNNC();
1126 const auto& transMult = this->eclState_.getTransMult();
1127
1128 for (const auto& nncEntry : pinchNnc) {
1129 auto c1 = nncEntry.cell1;
1130 auto c2 = nncEntry.cell2;
1131 auto lowIt = cartesianToCompressed.find(c1);
1132 auto highIt = cartesianToCompressed.find(c2);
1133 int low = (lowIt == cartesianToCompressed.end())? -1 : lowIt->second;
1134 int high = (highIt == cartesianToCompressed.end())? -1 : highIt->second;
1135
1136 if (low > high) {
1137 std::swap(low, high);
1138 }
1139
1140 if (low == -1 && high == -1) {
1141 // Silently discard as it is not between active cells
1142 continue;
1143 }
1144
1145 if (low == -1 || high == -1) {
1146 // We can end up here if one of the cells is overlap/ghost, because those
1147 // are lacking connections to other cells in the ghost/overlap.
1148 // Hence discard the NNC if it is between active cell and inactive cell
1149 continue;
1150 }
1151
1152 {
1153 auto candidate = trans_.find(details::isId(low, high));
1154 if (candidate != trans_.end()) {
1155 // the correctly calculated transmissibility is stored in
1156 // the NNC. Overwrite previous value with it.
1157 // taking the region multiplier into account.
1158 candidate->second = nncEntry.trans;
1159 if (applyNncMultregT) {
1160 const auto mult = transMult.getRegionMultiplierNNC(c1, c2);
1161 candidate->second *= mult;
1162 }
1163 }
1164 }
1165 }
1166}
1167
1168template<class Grid, class GridView, class ElementMapper, class CartesianIndexMapper, class Scalar>
1169void
1171applyNncToGridTrans_(const std::unordered_map<std::size_t,int>& cartesianToCompressed)
1172{
1173 // First scale NNCs with EDITNNC.
1174 const auto& nnc_input = eclState_.getInputNNC().input();
1175
1176 for (const auto& nncEntry : nnc_input) {
1177 auto c1 = nncEntry.cell1;
1178 auto c2 = nncEntry.cell2;
1179 auto lowIt = cartesianToCompressed.find(c1);
1180 auto highIt = cartesianToCompressed.find(c2);
1181 int low = (lowIt == cartesianToCompressed.end())? -1 : lowIt->second;
1182 int high = (highIt == cartesianToCompressed.end())? -1 : highIt->second;
1183
1184 if (low > high) {
1185 std::swap(low, high);
1186 }
1187
1188 if (low == -1 && high == -1) {
1189 // Silently discard as it is not between active cells
1190 continue;
1191 }
1192
1193 if (low == -1 || high == -1) {
1194 // Discard the NNC if it is between active cell and inactive cell
1195 std::ostringstream sstr;
1196 sstr << "NNC between active and inactive cells ("
1197 << low << " -> " << high << ") with globalcell is (" << c1 << "->" << c2 <<")";
1198 OpmLog::warning(sstr.str());
1199 continue;
1200 }
1201
1202 if (auto candidate = trans_.find(details::isId(low, high)); candidate != trans_.end()) {
1203 // NNC is represented by the grid and might be a neighboring connection
1204 // In this case the transmissibilty is added to the value already
1205 // set or computed.
1206 candidate->second += nncEntry.trans;
1207 }
1208 // if (enableEnergy_) {
1209 // auto candidate = thermalHalfTrans_.find(details::directionalIsId(low, high));
1210 // if (candidate != trans_.end()) {
1211 // // NNC is represented by the grid and might be a neighboring connection
1212 // // In this case the transmissibilty is added to the value already
1213 // // set or computed.
1214 // candidate->second += nncEntry.transEnergy1;
1215 // }
1216 // auto candidate = thermalHalfTrans_.find(details::directionalIsId(high, low));
1217 // if (candidate != trans_.end()) {
1218 // // NNC is represented by the grid and might be a neighboring connection
1219 // // In this case the transmissibilty is added to the value already
1220 // // set or computed.
1221 // candidate->second += nncEntry.transEnergy2;
1222 // }
1223 // }
1224 // if (enableDiffusivity_) {
1225 // auto candidate = diffusivity_.find(details::isId(low, high));
1226 // if (candidate != trans_.end()) {
1227 // // NNC is represented by the grid and might be a neighboring connection
1228 // // In this case the transmissibilty is added to the value already
1229 // // set or computed.
1230 // candidate->second += nncEntry.transDiffusion;
1231 // }
1232 // }
1233 }
1234}
1235
1236template<class Grid, class GridView, class ElementMapper, class CartesianIndexMapper, class Scalar>
1238applyEditNncToGridTrans_(const std::unordered_map<std::size_t,int>& globalToLocal)
1239{
1240 const auto& input = eclState_.getInputNNC();
1241 applyEditNncToGridTransHelper_(globalToLocal, "EDITNNC",
1242 input.edit(),
1243 [&input](const NNCdata& nnc){
1244 return input.edit_location(nnc);},
1245 // Multiply transmissibility with EDITNNC value
1246 [](Scalar& trans, const Scalar& rhs){ trans *= rhs;});
1247}
1248
1249template<class Grid, class GridView, class ElementMapper, class CartesianIndexMapper, class Scalar>
1251applyEditNncrToGridTrans_(const std::unordered_map<std::size_t,int>& globalToLocal)
1252{
1253 const auto& input = eclState_.getInputNNC();
1254 applyEditNncToGridTransHelper_(globalToLocal, "EDITNNCR",
1255 input.editr(),
1256 [&input](const NNCdata& nnc){
1257 return input.editr_location(nnc);},
1258 // Replace Transmissibility with EDITNNCR value
1259 [](Scalar& trans, const Scalar& rhs){ trans = rhs;});
1260}
1261
1262template<class Grid, class GridView, class ElementMapper, class CartesianIndexMapper, class Scalar>
1264applyEditNncToGridTransHelper_(const std::unordered_map<std::size_t,int>& globalToLocal,
1265 const std::string& keyword,
1266 const std::vector<NNCdata>& nncs,
1267 const std::function<KeywordLocation(const NNCdata&)>& getLocation,
1268 const std::function<void(Scalar&, const Scalar&)>& apply)
1269{
1270 if (nncs.empty()) {
1271 return;
1272 }
1273 const auto& cartDims = cartMapper_.cartesianDimensions();
1274
1275 auto format_ijk = [&cartDims](std::size_t cell) -> std::string
1276 {
1277 auto i = cell % cartDims[0]; cell /= cartDims[0];
1278 auto j = cell % cartDims[1];
1279 auto k = cell / cartDims[1];
1280
1281 return fmt::format("({},{},{})", i + 1,j + 1,k + 1);
1282 };
1283
1284 auto print_warning = [&format_ijk, &getLocation, &keyword] (const NNCdata& nnc)
1285 {
1286 const auto& location = getLocation( nnc );
1287 auto warning = fmt::format("Problem with {} keyword\n"
1288 "In {} line {} \n"
1289 "No NNC defined for connection {} -> {}", keyword, location.filename,
1290 location.lineno, format_ijk(nnc.cell1), format_ijk(nnc.cell2));
1291 OpmLog::warning(keyword, warning);
1292 };
1293
1294 // editNnc is supposed to only reference non-neighboring connections and not
1295 // neighboring connections. Use all entries for scaling if there is an NNC.
1296 // variable nnc incremented in loop body.
1297 auto nnc = nncs.begin();
1298 auto end = nncs.end();
1299 std::size_t warning_count = 0;
1300 while (nnc != end) {
1301 auto c1 = nnc->cell1;
1302 auto c2 = nnc->cell2;
1303 auto lowIt = globalToLocal.find(c1);
1304 auto highIt = globalToLocal.find(c2);
1305
1306 if (lowIt == globalToLocal.end() || highIt == globalToLocal.end()) {
1307 // Prevent warnings for NNCs stored on other processes in parallel (both cells inactive)
1308 if (lowIt != highIt && warnEditNNC_) {
1309 print_warning(*nnc);
1310 warning_count++;
1311 }
1312 ++nnc;
1313 continue;
1314 }
1315
1316 auto low = lowIt->second, high = highIt->second;
1317
1318 if (low > high) {
1319 std::swap(low, high);
1320 }
1321
1322 auto candidate = trans_.find(details::isId(low, high));
1323 if (candidate == trans_.end() && warnEditNNC_) {
1324 print_warning(*nnc);
1325 ++nnc;
1326 warning_count++;
1327 }
1328 else {
1329 // NNC exists
1330 while (nnc != end && c1 == nnc->cell1 && c2 == nnc->cell2) {
1331 apply(candidate->second, nnc->trans);
1332 ++nnc;
1333 }
1334 }
1335 }
1336
1337 if (warning_count > 0) {
1338 auto warning = fmt::format("Problems with {} keyword\n"
1339 "A total of {} connections not defined in grid", keyword, warning_count);
1340 OpmLog::warning(warning);
1341 }
1342}
1343
1344template<class Grid, class GridView, class ElementMapper, class CartesianIndexMapper, class Scalar>
1345void
1347applyNncMultreg_(const std::unordered_map<std::size_t,int>& cartesianToCompressed)
1348{
1349 const auto& inputNNC = this->eclState_.getInputNNC();
1350 const auto& transMult = this->eclState_.getTransMult();
1351
1352 auto compressedIdx = [&cartesianToCompressed](const std::size_t globIdx)
1353 {
1354 auto ixPos = cartesianToCompressed.find(globIdx);
1355 return (ixPos == cartesianToCompressed.end()) ? -1 : ixPos->second;
1356 };
1357
1358 // Apply region-based transmissibility multipliers (i.e., the MULTREGT
1359 // keyword) to those transmissibilities that are directly assigned from
1360 // the input.
1361 //
1362 // * NNC::input() covers the NNC keyword and any numerical aquifers
1363 // * NNC::editr() covers the EDITNNCR keyword
1364 //
1365 // Note: We do not apply MULTREGT to the entries in NNC::edit() since
1366 // those act as regular multipliers and have already been fully
1367 // accounted for in the multiplier part of the main loop of update() and
1368 // the applyEditNncToGridTrans_() member function.
1369 for (const auto& nncList : {&NNC::input, &NNC::editr}) {
1370 for (const auto& nncEntry : (inputNNC.*nncList)()) {
1371 const auto c1 = nncEntry.cell1;
1372 const auto c2 = nncEntry.cell2;
1373
1374 auto low = compressedIdx(c1);
1375 auto high = compressedIdx(c2);
1376
1377 if ((low == -1) || (high == -1)) {
1378 continue;
1379 }
1380
1381 if (low > high) {
1382 std::swap(low, high);
1383 }
1384
1385 auto candidate = this->trans_.find(details::isId(low, high));
1386 if (candidate != this->trans_.end()) {
1387 candidate->second *= transMult.getRegionMultiplierNNC(c1, c2);
1388 }
1389 }
1390 }
1391}
1392
1393template<class Grid, class GridView, class ElementMapper, class CartesianIndexMapper, class Scalar>
1394Scalar
1396computeHalfTrans_(const DimVector& areaNormal,
1397 int faceIdx, // in the reference element that contains the intersection
1398 const DimVector& distance,
1399 const DimMatrix& perm)
1400{
1401 assert(faceIdx >= 0);
1402 unsigned dimIdx = faceIdx / 2;
1403 assert(dimIdx < dimWorld);
1404 Scalar halfTrans = perm[dimIdx][dimIdx];
1405 halfTrans *= std::abs(Dune::dot(areaNormal, distance));
1406 halfTrans /= distance.two_norm2();
1407
1408 return halfTrans;
1409}
1410
1411template<class Grid, class GridView, class ElementMapper, class CartesianIndexMapper, class Scalar>
1412Scalar
1414computeHalfDiffusivity_(const DimVector& areaNormal,
1415 const DimVector& distance,
1416 const Scalar poro)
1417{
1418 Scalar halfDiff = poro;
1419 halfDiff *= std::abs(Dune::dot(areaNormal, distance));
1420 halfDiff /= distance.two_norm2();
1421
1422 return halfDiff;
1423}
1424
1425template<class Grid, class GridView, class ElementMapper, class CartesianIndexMapper, class Scalar>
1428distanceVector_(const DimVector& faceCenter,
1429 const unsigned& cellIdx) const
1430{
1431 const auto& cellCenter = centroids_cache_.empty() ? centroids_(cellIdx)
1432 : centroids_cache_[cellIdx];
1433 DimVector x = faceCenter;
1434 for (unsigned dimIdx = 0; dimIdx < dimWorld; ++dimIdx) {
1435 x[dimIdx] -= cellCenter[dimIdx];
1436 }
1437
1438 return x;
1439}
1440
1441template<class Grid, class GridView, class ElementMapper, class CartesianIndexMapper, class Scalar>
1443applyMultipliers_(Scalar& trans,
1444 unsigned faceIdx,
1445 unsigned cartElemIdx,
1446 const TransMult& transMult) const
1447{
1448 // apply multiplier for the transmissibility of the face. (the
1449 // face index is the index of the reference-element face which
1450 // contains the intersection of interest.)
1451 trans *= transMult.getMultiplier(cartElemIdx,
1452 FaceDir::FromIntersectionIndex(faceIdx));
1453}
1454
1455template<class Grid, class GridView, class ElementMapper, class CartesianIndexMapper, class Scalar>
1457applyNtg_(Scalar& trans,
1458 const FaceInfo& face,
1459 const std::vector<double>& ntg)
1460{
1461 // apply multiplier for the transmissibility of the face. (the
1462 // face index is the index of the reference-element face which
1463 // contains the intersection of interest.)
1464 // NTG does not apply to top and bottom faces
1465 if (face.faceIdx >= 0 && face.faceIdx <= 3) {
1466 trans *= ntg[face.elemIdx];
1467 }
1468}
1469
1470} // namespace Opm
1471
1472#endif // OPM_TRANSMISSIBILITY_IMPL_HPP
static unsigned maxThreads()
Return the maximum number of threads of the current process.
Definition: threadmanager.hpp:66
void applyMultipliers_(Scalar &trans, unsigned faceIdx, unsigned cartElemIdx, const TransMult &transMult) const
Definition: Transmissibility_impl.hpp:1443
Scalar diffusivity(unsigned elemIdx1, unsigned elemIdx2) const
Return the diffusivity for the intersection between two elements.
Definition: Transmissibility_impl.hpp:175
void applyEditNncToGridTrans_(const std::unordered_map< std::size_t, int > &globalToLocal)
Multiplies the grid transmissibilities according to EDITNNC.
Definition: Transmissibility_impl.hpp:1238
void computeFaceProperties(const Intersection &intersection, FaceInfo &inside, FaceInfo &outside, DimVector &faceAreaNormal, std::false_type) const
Definition: Transmissibility_impl.hpp:1041
Dune::FieldMatrix< Scalar, dimWorld, dimWorld > DimMatrix
Definition: Transmissibility.hpp:59
void update(bool global, TransUpdateQuantities update_quantities=TransUpdateQuantities::All, const std::function< unsigned int(unsigned int)> &map={}, bool applyNncMultRegT=false)
Definition: Transmissibility_impl.hpp:195
DimVector distanceVector_(const DimVector &faceCenter, const unsigned &cellIdx) const
Definition: Transmissibility_impl.hpp:1428
void applyNncMultreg_(const std::unordered_map< std::size_t, int > &globalToLocal)
Definition: Transmissibility_impl.hpp:1347
void applyEditNncrToGridTrans_(const std::unordered_map< std::size_t, int > &globalToLocal)
Resets the grid transmissibilities according to EDITNNCR.
Definition: Transmissibility_impl.hpp:1251
void extractDispersion_()
Definition: Transmissibility_impl.hpp:762
const std::map< std::pair< unsigned, unsigned >, Scalar > & getThermalHalfTransBoundary() const
Definition: Transmissibility_impl.hpp:168
Scalar thermalHalfTrans(unsigned insideElemIdx, unsigned outsideElemIdx) const
Return the thermal "half transmissibility" for the intersection between two elements.
Definition: Transmissibility_impl.hpp:136
Scalar transmissibilityBoundary(unsigned elemIdx, unsigned boundaryFaceIdx) const
Return the transmissibility for a given boundary segment.
Definition: Transmissibility_impl.hpp:129
std::array< std::vector< double >, 3 > createTransmissibilityArrays_(const std::array< bool, 3 > &is_tran)
Creates TRANS{XYZ} arrays for modification by FieldProps data.
Definition: Transmissibility_impl.hpp:884
void applyEditNncToGridTransHelper_(const std::unordered_map< std::size_t, int > &globalToLocal, const std::string &keyword, const std::vector< NNCdata > &nncs, const std::function< KeywordLocation(const NNCdata &)> &getLocation, const std::function< void(Scalar &, const Scalar &)> &apply)
Definition: Transmissibility_impl.hpp:1264
void updateFromEclState_(bool global)
Definition: Transmissibility_impl.hpp:849
void extractPermeability_()
Definition: Transmissibility_impl.hpp:647
void applyAllZMultipliers_(Scalar &trans, const FaceInfo &inside, const FaceInfo &outside, const TransMult &transMult, const std::array< int, dimWorld > &cartDims)
Apply the Multipliers for the case PINCH(4)==TOPBOT.
Definition: Transmissibility_impl.hpp:806
TransUpdateQuantities
Compute all transmissibilities.
Definition: Transmissibility.hpp:177
void resetTransmissibilityFromArrays_(const std::array< bool, 3 > &is_tran, const std::array< std::vector< double >, 3 > &trans)
overwrites calculated transmissibilities
Definition: Transmissibility_impl.hpp:966
Dune::FieldVector< Scalar, dimWorld > DimVector
Definition: Transmissibility.hpp:60
static Scalar computeHalfDiffusivity_(const DimVector &areaNormal, const DimVector &distance, const Scalar poro)
Definition: Transmissibility_impl.hpp:1414
Scalar transmissibilityThreshold_
Definition: Transmissibility.hpp:309
Scalar transmissibility(unsigned elemIdx1, unsigned elemIdx2) const
Return the transmissibility for the intersection between two elements.
Definition: Transmissibility_impl.hpp:122
static void applyNtg_(Scalar &trans, const FaceInfo &face, const std::vector< double > &ntg)
Definition: Transmissibility_impl.hpp:1457
const EclipseState & eclState_
Definition: Transmissibility.hpp:303
Scalar dispersivity(unsigned elemIdx1, unsigned elemIdx2) const
Return the dispersivity for the intersection between two elements.
Definition: Transmissibility_impl.hpp:185
Transmissibility(const EclipseState &eclState, const GridView &gridView, const CartesianIndexMapper &cartMapper, const Grid &grid, std::function< std::array< double, dimWorld >(int)> centroids, bool enableEnergy, bool enableDiffusivity, bool enableDispersivity)
Definition: Transmissibility_impl.hpp:97
void applyNncToGridTrans_(const std::unordered_map< std::size_t, int > &cartesianToCompressed)
Definition: Transmissibility_impl.hpp:1171
Scalar halfTransmissibility(unsigned insideElemIdx, unsigned outsideElemIdx) const
One-sided (half) transmissibility of the face between two cells, seen from the inside cell (NTG appli...
Definition: Transmissibility_impl.hpp:143
void applyPinchNncToGridTrans_(const std::unordered_map< std::size_t, int > &cartesianToCompressed, bool applyNncMultregT)
Applies the previous calculate transmissibilities to the NNCs created via PINCH.
Definition: Transmissibility_impl.hpp:1122
void extractPorosity_()
Definition: Transmissibility_impl.hpp:737
void removeNonCartesianTransmissibilities_(bool removeAll)
Definition: Transmissibility_impl.hpp:781
static Scalar computeHalfTrans_(const DimVector &areaNormal, int faceIdx, const DimVector &distance, const DimMatrix &perm)
Definition: Transmissibility_impl.hpp:1396
Scalar thermalHalfTransBoundary(unsigned insideElemIdx, unsigned boundaryFaceIdx) const
Definition: Transmissibility_impl.hpp:161
if(!linsolver_)
Definition: FlexibleSolver_impl.hpp:326
constexpr unsigned elemIdxShift
Definition: FacePropertiesTPSA_impl.hpp:52
std::pair< std::uint32_t, std::uint32_t > isIdReverse(const std::uint64_t &id)
Definition: Transmissibility_impl.hpp:78
std::uint64_t directionalIsId(std::uint32_t elemIdx1, std::uint32_t elemIdx2)
Definition: Transmissibility_impl.hpp:89
std::uint64_t isId(std::uint32_t elemIdx1, std::uint32_t elemIdx2)
Definition: Transmissibility_impl.hpp:70
Definition: blackoilbioeffectsmodules.hh:45
Definition: Transmissibility.hpp:187
unsigned cartElemIdx
Definition: Transmissibility.hpp:191
DimVector faceCenter
Definition: Transmissibility.hpp:188
unsigned elemIdx
Definition: Transmissibility.hpp:190
int faceIdx
Definition: Transmissibility.hpp:189