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