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