EclGenericWriter_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_ECL_GENERIC_WRITER_IMPL_HPP
24#define OPM_ECL_GENERIC_WRITER_IMPL_HPP
25
26#include <dune/grid/common/mcmgmapper.hh>
27
28#include <opm/grid/cpgrid/LgrOutputHelpers.hpp>
29#include <opm/grid/GridHelpers.hpp>
30#include <opm/grid/utility/cartesianToCompressed.hpp>
31
32#include <opm/input/eclipse/EclipseState/EclipseState.hpp>
33#include <opm/input/eclipse/EclipseState/Grid/RegionSetMatcher.hpp>
34#include <opm/input/eclipse/EclipseState/Grid/NNC.hpp>
35#include <opm/input/eclipse/EclipseState/SummaryConfig/SummaryConfig.hpp>
36
37#include <opm/input/eclipse/Schedule/Action/State.hpp>
38#include <opm/input/eclipse/Schedule/RPTConfig.hpp>
39#include <opm/input/eclipse/Schedule/Schedule.hpp>
40#include <opm/input/eclipse/Schedule/SummaryState.hpp>
41#include <opm/input/eclipse/Schedule/UDQ/UDQConfig.hpp>
42#include <opm/input/eclipse/Schedule/UDQ/UDQState.hpp>
43#include <opm/input/eclipse/Schedule/Well/WellConnections.hpp>
44#include <opm/input/eclipse/Schedule/Well/WellMatcher.hpp>
45
46#include <opm/input/eclipse/Units/UnitSystem.hpp>
47
48#include <opm/output/data/RegionVariableMapping.hpp>
49
50#include <opm/output/eclipse/EclipseIO.hpp>
51#include <opm/output/eclipse/RegionVariableCollection.hpp>
52#include <opm/output/eclipse/RestartValue.hpp>
53#include <opm/output/eclipse/Summary.hpp>
54
56
58
59#if HAVE_MPI
61#endif
62
63#if HAVE_MPI
64#include <mpi.h>
65#endif
66
67#include <algorithm>
68#include <array>
69#include <cassert>
70#include <cmath>
71#include <functional>
72#include <map>
73#include <memory>
74#include <stdexcept>
75#include <string>
76#include <unordered_map>
77#include <utility>
78#include <vector>
79
80namespace {
81
96bool directVerticalNeighbors(const std::array<int, 3>& cartDims,
97 const std::unordered_map<int,int>& cartesianToActive,
98 int smallGlobalIndex, int largeGlobalIndex)
99{
100 assert(smallGlobalIndex <= largeGlobalIndex);
101 std::array<int, 3> ijk1, ijk2;
102 auto globalToIjk = [cartDims](int gc) {
103 std::array<int, 3> ijk;
104 ijk[0] = gc % cartDims[0];
105 gc /= cartDims[0];
106 ijk[1] = gc % cartDims[1];
107 ijk[2] = gc / cartDims[1];
108 return ijk;
109 };
110 ijk1 = globalToIjk(smallGlobalIndex);
111 ijk2 = globalToIjk(largeGlobalIndex);
112 assert(ijk2[2]>=ijk1[2]);
113
114 if ( ijk1[0] == ijk2[0] && ijk1[1] == ijk2[1] && (ijk2[2] - ijk1[2]) > 1)
115 {
116 assert((largeGlobalIndex-smallGlobalIndex)%(cartDims[0]*cartDims[1])==0);
117 for ( int gi = smallGlobalIndex + cartDims[0] * cartDims[1]; gi < largeGlobalIndex;
118 gi += cartDims[0] * cartDims[1] )
119 {
120 if ( cartesianToActive.find( gi ) != cartesianToActive.end() )
121 {
122 return false;
123 }
124 }
125 return true;
126 } else
127 return false;
128}
129
130std::unordered_map<std::string, Opm::data::InterRegFlowMap>
131getInterRegFlowsAsMap(const Opm::InterRegFlowMap& map)
132{
133 auto maps = std::unordered_map<std::string, Opm::data::InterRegFlowMap>{};
135 const auto& regionNames = map.names();
136 auto flows = map.getInterRegFlows();
137 const auto nmap = regionNames.size();
138
139 maps.reserve(nmap);
140 for (auto mapID = 0*nmap; mapID < nmap; ++mapID) {
141 maps.emplace(regionNames[mapID], std::move(flows[mapID]));
142 }
143
144 return maps;
145}
146
147struct EclWriteTasklet : public Opm::TaskletInterface
149 Opm::Action::State actionState_;
150 Opm::WellTestState wtestState_;
151 Opm::SummaryState summaryState_;
152 Opm::UDQState udqState_;
153 Opm::EclipseIO& eclIO_;
154 int reportStepNum_;
155 std::optional<int> timeStepNum_;
156 bool isSubStep_;
157 double secondsElapsed_;
158 std::vector<Opm::RestartValue> restartValue_;
159 bool writeDoublePrecision_;
161 bool forcedSimulationFinished_;
162
163 explicit EclWriteTasklet(const Opm::Action::State& actionState,
164 const Opm::WellTestState& wtestState,
165 const Opm::SummaryState& summaryState,
166 const Opm::UDQState& udqState,
167 Opm::EclipseIO& eclIO,
168 int reportStepNum,
169 std::optional<int> timeStepNum,
170 bool isSubStep,
171 double secondsElapsed,
172 std::vector<Opm::RestartValue> restartValue,
173 bool writeDoublePrecision,
174 bool forcedSimulationFinished)
175 : actionState_(actionState)
176 , wtestState_(wtestState)
177 , summaryState_(summaryState)
178 , udqState_(udqState)
179 , eclIO_(eclIO)
180 , reportStepNum_(reportStepNum)
181 , timeStepNum_(timeStepNum)
182 , isSubStep_(isSubStep)
183 , secondsElapsed_(secondsElapsed)
184 , restartValue_(std::move(restartValue))
185 , writeDoublePrecision_(writeDoublePrecision)
186 , forcedSimulationFinished_(forcedSimulationFinished)
187 {}
188
189 // callback to eclIO serial writeTimeStep method
190 void run() override
191 {
192 if (this->restartValue_.size() == 1) {
193 this->eclIO_.writeTimeStep(this->actionState_,
194 this->wtestState_,
195 this->summaryState_,
196 this->udqState_,
197 this->reportStepNum_,
198 this->isSubStep_,
199 this->secondsElapsed_,
200 std::move(this->restartValue_.back()),
201 this->writeDoublePrecision_,
202 this->timeStepNum_,
203 forcedSimulationFinished_);
204 }
205 else{
206 this->eclIO_.writeTimeStep(this->actionState_,
207 this->wtestState_,
208 this->summaryState_,
209 this->udqState_,
210 this->reportStepNum_,
211 this->isSubStep_,
212 this->secondsElapsed_,
213 std::move(this->restartValue_),
214 this->writeDoublePrecision_,
215 this->timeStepNum_,
216 forcedSimulationFinished_);
217 }
218 }
219};
220
221}
222
223namespace Opm {
224
225template<class Grid, class EquilGrid, class GridView, class ElementMapper, class Scalar>
227EclGenericWriter(const Schedule& schedule,
228 const EclipseState& eclState,
229 const SummaryConfig& summaryConfig,
230 const Grid& grid,
231 const EquilGrid* equilGrid,
232 const GridView& gridView,
233 const Dune::CartesianIndexMapper<Grid>& cartMapper,
234 const Dune::CartesianIndexMapper<EquilGrid>* equilCartMapper,
235 bool enableAsyncOutput,
236 bool enableEsmry )
237 : collectOnIORank_(grid,
238 equilGrid,
239 gridView,
240 cartMapper,
241 equilCartMapper,
242 summaryConfig.fip_regions_interreg_flow())
243 , grid_ (grid)
244 , gridView_ (gridView)
245 , schedule_ (schedule)
246 , eclState_ (eclState)
247 , cartMapper_ (cartMapper)
248 , equilCartMapper_(equilCartMapper)
249 , equilGrid_ (equilGrid)
250{
251 // Make sure outputNnc_ vector has at least 1 entry in all ranks.
252 outputNnc_.resize(1);
253
254 if (this->collectOnIORank_.isIORank()) {
255 this->eclIO_ = std::make_unique<EclipseIO>
256 (this->eclState_,
257 UgGridHelpers::createEclipseGrid(*equilGrid, eclState_.getInputGrid()),
258 this->schedule_, summaryConfig, "", enableEsmry);
259 }
260
261 // create output thread if enabled and rank is I/O rank
262 // async output is enabled by default if pthread are enabled
263 int numWorkerThreads = 0;
264 if (enableAsyncOutput && collectOnIORank_.isIORank()) {
265 numWorkerThreads = 1;
266 }
267
268 this->taskletRunner_.reset(new TaskletRunner(numWorkerThreads));
269}
270
271template<class Grid, class EquilGrid, class GridView, class ElementMapper, class Scalar>
273eclIO() const
274{
275 assert(eclIO_);
276 return *eclIO_;
277}
278
279template<class Grid, class EquilGrid, class GridView, class ElementMapper, class Scalar>
281writeInit()
282{
283 if (collectOnIORank_.isIORank()) {
284 std::map<std::string, std::vector<int>> integerVectors;
285 // globalRanks() is empty when the I/O-rank cell collection is not set up
286 // (parallel runs with LGRs). Passing it on would write a zero-length
287 // MPI_RANK, which the per-LGR INIT sections then index by father cell.
288 if (collectOnIORank_.isParallel() && !collectOnIORank_.globalRanks().empty()) {
289 integerVectors.emplace("MPI_RANK", collectOnIORank_.globalRanks());
290 }
291
292 if (const auto& lgrs = this->eclState_.getLgrs(); lgrs.size() > 0) {
293
294 const auto nncCollection = Opm::NNCCollection::fromLGROutputContainers(this->outputNnc_,
295 this->outputNncGlobalLocal_,
296 this->outputAmalgamatedNnc_);
297 eclIO_->writeInitial(*this->outputTrans_,
298 integerVectors,
299 nncCollection);
300 } else {
301 eclIO_->writeInitial(*this->outputTrans_,
302 integerVectors,
303 this->outputNnc_.front());
304 }
305 this->outputTrans_.reset();
306 }
307 this->gatheredLgrTrans_.reset();
308}
309
310template<class Grid, class EquilGrid, class GridView, class ElementMapper, class Scalar>
311void
313extractOutputTransAndNNC(const std::function<unsigned int(unsigned int)>& map)
314{
315 // The work below runs on the I/O rank only, but an exception there (e.g. a
316 // missing gathered LGR transmissibility) must not leave the other ranks
317 // blocked in the broadcast that follows -- so failure is agreed on
318 // collectively and every rank throws together.
320
321 if (collectOnIORank_.isIORank()) {
322 constexpr bool equilGridIsCpGrid = std::is_same_v<EquilGrid, Dune::CpGrid>;
323
324 const auto levelCartMapp = this->createLevelCartMapp_<equilGridIsCpGrid>();
325 const auto levelCartToLevelCompressed = this->createCartesianToActiveMaps_<equilGridIsCpGrid>(levelCartMapp);
326 auto computeLevelIndices = this->computeLevelIndices_<equilGridIsCpGrid>();
327 auto computeLevelCartIdx = this->computeLevelCartIdx_<equilGridIsCpGrid>(levelCartMapp, *(this->equilCartMapper_));
328 auto computeLevelCartDimensions = this->computeLevelCartDimensions_<equilGridIsCpGrid>(levelCartMapp, *(this->equilCartMapper_));
329 auto computeOriginIndices = this->computeOriginIndices_<equilGridIsCpGrid>();
330
331 computeTrans_(levelCartToLevelCompressed, map, computeLevelIndices,
332 computeLevelCartIdx, computeLevelCartDimensions, computeOriginIndices);
333 exportNncStructure_(levelCartToLevelCompressed, map, computeLevelIndices, computeLevelCartIdx,
334 computeLevelCartDimensions, computeOriginIndices);
335 }
336
337 OPM_END_PARALLEL_TRY_CATCH("EclGenericWriter::extractOutputTransAndNNC() failed: ",
338 grid_.comm());
339
340#if HAVE_MPI
341 if (collectOnIORank_.isParallel()) {
342 const auto& comm = grid_.comm();
343 Parallel::MpiSerializer ser(comm);
344 ser.broadcast(Parallel::RootRank{0}, outputNnc_);
345 }
346#endif
347}
348
349template<class Grid, class EquilGrid, class GridView, class ElementMapper, class Scalar>
350bool
352isNumAquCell_(const std::size_t cartIdx) const
353{
354 const auto& numAquCell = this->eclState_.aquifer().hasNumericalAquifer()
355 ? this->eclState_.aquifer().numericalAquifers().allAquiferCellIds()
356 : std::vector<std::size_t>{};
357
358 return std::ranges::binary_search(numAquCell.begin(), numAquCell.end(), cartIdx);
359}
360
361template<class Grid, class EquilGrid, class GridView, class ElementMapper, class Scalar>
362bool
363EclGenericWriter<Grid,EquilGrid,GridView,ElementMapper,Scalar>::
364isNumAquConn_(const std::size_t cartIdx1,
365 const std::size_t cartIdx2) const
366{
367 return isNumAquCell_(cartIdx1) || isNumAquCell_(cartIdx2);
368}
369
370template<class Grid, class EquilGrid, class GridView, class ElementMapper, class Scalar>
371template<bool equilGridIsCpGrid>
373EclGenericWriter<Grid,EquilGrid,GridView,ElementMapper,Scalar>::
374createLevelCartMapp_() const
375{
376 if constexpr (equilGridIsCpGrid) {
377 return Opm::LevelCartesianIndexMapper<EquilGrid>(*this->equilGrid_);
378 } else {
379 return Opm::LevelCartesianIndexMapper<EquilGrid>(*equilCartMapper_); }
380}
381
382template<class Grid, class EquilGrid, class GridView, class ElementMapper, class Scalar>
383template<bool equilGridIsCpGrid>
384std::vector<std::unordered_map<int,int>>
385EclGenericWriter<Grid,EquilGrid,GridView,ElementMapper,Scalar>::
386createCartesianToActiveMaps_(const Opm::LevelCartesianIndexMapper<EquilGrid>& levelCartMapp) const
387{
388 if constexpr (equilGridIsCpGrid) {
389 if (this->equilGrid_->maxLevel()) {
390 return Opm::Lgr::levelCartesianToLevelCompressedMaps(*this->equilGrid_, levelCartMapp); }
391 else {
392 return std::vector<std::unordered_map<int,int>>{ cartesianToCompressed(equilGrid_->size(0), UgGridHelpers::globalCell(*equilGrid_)) };
393 }
394 }
395 return std::vector<std::unordered_map<int,int>>{ cartesianToCompressed(equilGrid_->size(0), UgGridHelpers::globalCell(*equilGrid_)) };
396}
397
398template<class Grid, class EquilGrid, class GridView, class ElementMapper, class Scalar>
399template<bool equilGridIsCpGrid>
400std::function<std::array<int,3>(int)>
401EclGenericWriter<Grid,EquilGrid,GridView,ElementMapper,Scalar>::
402computeLevelCartDimensions_(const Opm::LevelCartesianIndexMapper<EquilGrid>& levelCartMapp,
403 const Dune::CartesianIndexMapper<EquilGrid>& equilCartMapp) const
404{
405 if constexpr (equilGridIsCpGrid) {
406 return [&](int level)
407 {
408 return levelCartMapp.cartesianDimensions(level);
409 };
410 }
411 else {
412 return [&]([[maybe_unused]] int level)
413 {
414 assert(level == 0); // refinement only supported for CpGrid for now
415 return equilCartMapp.cartesianDimensions();
416 };
417 }
418}
419
420template<class Grid, class EquilGrid, class GridView, class ElementMapper, class Scalar>
421template<bool equilGridIsCpGrid>
422std::function<int(int, int)>
423EclGenericWriter<Grid,EquilGrid,GridView,ElementMapper,Scalar>::
424computeLevelCartIdx_(const Opm::LevelCartesianIndexMapper<EquilGrid>& levelCartMapp,
425 const Dune::CartesianIndexMapper<EquilGrid>& equilCartMapp) const
426{
427 if constexpr (equilGridIsCpGrid) {
428 return [&](int levelCompressedIdx,
429 int level)
430 {
431 return levelCartMapp.cartesianIndex(levelCompressedIdx, level);
432 };
433 }
434 else {
435 return [&](int levelCompressedIdx,
436 [[maybe_unused]] int level)
437 {
438 assert(level == 0); // refinement only supported for CpGrid for now
439 return equilCartMapp.cartesianIndex(levelCompressedIdx);
440 };
441 }
442}
443
444template<class Grid, class EquilGrid, class GridView, class ElementMapper, class Scalar>
445template <bool equilGridIsCpGrid>
446auto
447EclGenericWriter<Grid,EquilGrid,GridView,ElementMapper,Scalar>::
448computeLevelIndices_() const
449{
450 if constexpr (equilGridIsCpGrid) {
451 return [](const auto& intersection,
452 const auto&,
453 const auto&)
454 {
455 return std::pair{intersection.inside().getLevelElem().index(), intersection.outside().getLevelElem().index()};
456 };
457 }
458 else {
459 return [](const auto&,
460 const auto& intersectionInsideLeafIdx,
461 const auto& intersectionOutsideLeafIdx)
462 {
463 return std::pair{intersectionInsideLeafIdx, intersectionOutsideLeafIdx};
464 };
465 }
466}
467
468template<class Grid, class EquilGrid, class GridView, class ElementMapper, class Scalar>
469template <bool equilGridIsCpGrid>
470auto
471EclGenericWriter<Grid,EquilGrid,GridView,ElementMapper,Scalar>::
472computeOriginIndices_() const
473{
474 if constexpr (equilGridIsCpGrid) {
475 return [](const auto& intersection,
476 const auto&,
477 const auto&)
478 {
479 return std::pair{intersection.inside().getOrigin().index(), intersection.outside().getOrigin().index()};
480 };
481 }
482 else {
483 return [](const auto&,
484 const auto& intersectionInsideLeafIdx,
485 const auto& intersectionOutsideLeafIdx)
486 {
487 return std::pair{intersectionInsideLeafIdx, intersectionOutsideLeafIdx};
488 };
489 }
490}
491
492template<class Grid, class EquilGrid, class GridView, class ElementMapper, class Scalar>
493void
494EclGenericWriter<Grid,EquilGrid,GridView,ElementMapper,Scalar>::
495allocateLevelTrans_(const std::array<int,3>& levelCartDims,
496 data::Solution& levelTrans) const
497{
498 auto createLevelCellData = [&levelCartDims]() {
499 return Opm::data::CellData{
500 Opm::UnitSystem::measure::transmissibility,
501 std::vector<double>(levelCartDims[0] * levelCartDims[1] * levelCartDims[2], 0.0),
502 Opm::data::TargetType::INIT
503 };
504 };
505
506 levelTrans.clear();
507 levelTrans.emplace("TRANX", createLevelCellData());
508 levelTrans.emplace("TRANY", createLevelCellData());
509 levelTrans.emplace("TRANZ", createLevelCellData());
510}
511
512template<class Grid, class EquilGrid, class GridView, class ElementMapper, class Scalar>
513template<typename LevelIndicesFunction, typename OriginIndicesFunction>
514void
515EclGenericWriter<Grid,EquilGrid,GridView,ElementMapper,Scalar>::
516computeTrans_(const std::vector<std::unordered_map<int,int>>& levelCartToLevelCompressed,
517 const std::function<unsigned int(unsigned int)>& map,
518 const LevelIndicesFunction& computeLevelIndices,
519 const std::function<int(int, int)>& computeLevelCartIdx,
520 const std::function<std::array<int,3>(int)>& computeLevelCartDims,
521 const OriginIndicesFunction& computeOriginIndices) const
522{
523 if (!outputTrans_) {
524 outputTrans_ = std::make_unique<std::vector<data::Solution>>(std::vector<data::Solution>{});
525 }
526
527 using GlobalGridView = typename EquilGrid::LeafGridView;
528 using GlobElementMapper = Dune::MultipleCodimMultipleGeomTypeMapper<GlobalGridView>;
529 const GlobalGridView& globalGridView = this->equilGrid_->leafGridView();
530 const GlobElementMapper globalElemMapper { globalGridView, Dune::mcmgElementLayout() };
531
532 // Refinement supported only for CpGrid for now.
533 int maxLevel = this->equilGrid_->maxLevel();
534
535 outputTrans_->resize(maxLevel+1); // including level zero grid
536
537 for (int level = 0; level <= maxLevel; ++level) {
538 allocateLevelTrans_(computeLevelCartDims(level), this->outputTrans_->at(level));
539 }
540
541 for (const auto& elem : elements(globalGridView)) {
542 for (const auto& is : intersections(globalGridView, elem)) {
543 if (!is.neighbor())
544 continue; // intersection is on the domain boundary
545
546 if ( is.inside().level() != is.outside().level() ) // Those are treated as NNCs
547 continue;
548
549 // Not 'const' because remapped if 'map' is non-null.
550 unsigned c1 = globalElemMapper.index(is.inside());
551 unsigned c2 = globalElemMapper.index(is.outside());
552
553 if (c1 > c2)
554 continue; // we only need to handle each connection once, thank you.
555
556 int level = is.inside().level();
557
558 // For CpGrid with LGRs, level*Idx and c* do not coincide.
559 const auto& [levelInIdx, levelOutIdx] = computeLevelIndices(is, c1, c2);
560
561 const int levelCartIdxIn = computeLevelCartIdx(levelInIdx, level);
562 const int levelCartIdxOut = computeLevelCartIdx(levelOutIdx, level);
563
564 // For CpGrid with LGRs, the origin cell index refers to the coarsest
565 // ancestor cell when the cell is refined. For cells not involved in
566 // any refinement, it corresponds to the geometrically equivalent
567 // cell in the level-zero grid.
568 const auto [originInIdx, originOutIdx] = computeOriginIndices(is, c1, c2);
569
570 const auto originCartIdxIn = computeLevelCartIdx(originInIdx, /* level = */ 0);
571 const auto originCartIdxOut = computeLevelCartIdx(originOutIdx, /* level = */ 0);
572
573 // For level-zero grid, level Cartesian indices coincide with the grid Cartesian indices.
574 if (isNumAquCell_(originCartIdxIn) || isNumAquCell_(originCartIdxOut)) {
575 // Check there are no refined aquifer cells.
576 assert(level == 0);
577 // Connections involving numerical aquifers are always NNCs
578 // for the purpose of file output. This holds even for
579 // connections between cells like (I,J,K) and (I+1,J,K)
580 // which are nominally neighbours in the Cartesian grid.
581 continue;
582 }
583
584 const auto minLevelCartIdx = std::min(levelCartIdxIn, levelCartIdxOut);
585 const auto maxLevelCartIdx = std::max(levelCartIdxIn, levelCartIdxOut);
586
587 const auto& levelCartDims = computeLevelCartDims(level);
588
589 // Re-ordering in case of non-empty mapping between equilGrid to grid
590 if (map) {
591 c1 = map(c1); // equilGridToGrid map
592 c2 = map(c2);
593 }
594
595 if (maxLevelCartIdx - minLevelCartIdx == 1 && levelCartDims[0] > 1 ) {
596 outputTrans_->at(level).at("TRANX").template data<double>()[minLevelCartIdx] =
597 gatheredOrGlobalTrans_(std::array{level, minLevelCartIdx, maxLevelCartIdx}, c1, c2);
598 continue; // skip other if clauses as they are false, last one needs some computation
599 }
600
601 if (maxLevelCartIdx - minLevelCartIdx == levelCartDims[0] && levelCartDims[1] > 1) {
602 outputTrans_->at(level).at("TRANY").template data<double>()[minLevelCartIdx] =
603 gatheredOrGlobalTrans_(std::array{level, minLevelCartIdx, maxLevelCartIdx}, c1, c2);
604 continue; // skipt next if clause as it needs some computation
605 }
606
607 if ( maxLevelCartIdx - minLevelCartIdx == levelCartDims[0]*levelCartDims[1] ||
608 directVerticalNeighbors(levelCartDims,
609 levelCartToLevelCompressed[level],
610 minLevelCartIdx,
611 maxLevelCartIdx)) {
612 outputTrans_->at(level).at("TRANZ").template data<double>()[minLevelCartIdx] =
613 gatheredOrGlobalTrans_(std::array{level, minLevelCartIdx, maxLevelCartIdx}, c1, c2);
614 }
615 }
616 }
617}
618
619template<class Grid, class EquilGrid, class GridView, class ElementMapper, class Scalar>
620bool
621EclGenericWriter<Grid,EquilGrid,GridView,ElementMapper,Scalar>::
622isCartesianNeighbour_(const std::array<int,3>& levelCartDims,
623 const std::size_t levelCartIdx1,
624 const std::size_t levelCartIdx2) const
625{
626 const int diff = levelCartIdx2 - levelCartIdx1;
627
628 return (diff == 1)
629 || (diff == levelCartDims[0])
630 || (diff == (levelCartDims[0] * levelCartDims[1]));
631}
632
633template<class Grid, class EquilGrid, class GridView, class ElementMapper, class Scalar>
634bool
635EclGenericWriter<Grid,EquilGrid,GridView,ElementMapper,Scalar>::
636isDirectNeighbours_(const std::unordered_map<int,int>& levelCartesianToActive,
637 const std::array<int,3>& levelCartDims,
638 const std::size_t levelCartIdx1,
639 const std::size_t levelCartIdx2) const
640{
641 return isCartesianNeighbour_(levelCartDims, levelCartIdx1, levelCartIdx2)
642 || directVerticalNeighbors(levelCartDims, levelCartesianToActive, levelCartIdx1, levelCartIdx2);
643}
644
645template<class Grid, class EquilGrid, class GridView, class ElementMapper, class Scalar>
646auto
647EclGenericWriter<Grid,EquilGrid,GridView,ElementMapper,Scalar>::
648activeCell_(const std::unordered_map<int,int>& levelCartToLevelCompressed,
649 const std::size_t levelCartIdx) const
650{
651 auto pos = levelCartToLevelCompressed.find(levelCartIdx);
652 return (pos == levelCartToLevelCompressed.end()) ? -1 : pos->second;
653}
654
655template<class Grid, class EquilGrid, class GridView, class ElementMapper, class Scalar>
656void
657EclGenericWriter<Grid,EquilGrid,GridView,ElementMapper,Scalar>::
658allocateAllNncs_(int maxLevel) const
659{
660 this->outputNnc_.resize(maxLevel+1); // level 0,1,..., maxLevel
661
662 if (maxLevel) {
663 // NNCs between main (level zero) grid and LGRs: level 1, ...., maxLevel.
664 // Example: grid with maxLevel == 3, outputNncGlobalLocal_.size() is maxLevel = 3
665 // outputNncGlobalLocal_[0] -> NNCs between level 0 and level 1
666 // outputNncGlobalLocal_[1] -> NNCs between level 0 and level 2
667 // outputAmalgamatedNnc_[2] -> NNCs between level 0 and level 3
668 this->outputNncGlobalLocal_.resize(maxLevel);
669
670 // NNCs between different refined level grids: (level1, level2)
671 // with 0 < level1 < level2 <= maxLevel
672 // Example: grid with maxLevel == 3, outputAmalgamatedNnc_.size() is maxLevel-1 = 2
673 // outputAmalgamatedNnc_[0][0] -> NNCs between level 1 and level 2
674 // outputAmalgamatedNnc_[0][1] -> NNCs between level 1 and level 3
675 // outputAmalgamatedNnc_[1][2] -> NNCs between level 2 and level 3
676 this->outputAmalgamatedNnc_.resize(maxLevel-1);
677 for (int i = 0; i < maxLevel-1; ++i) {
678 this->outputAmalgamatedNnc_[i].resize(maxLevel-1-i);
679 }
680 }
681}
682
683template<class Grid, class EquilGrid, class GridView, class ElementMapper, class Scalar>
684template<typename LevelIndicesFunction, typename OriginIndicesFunction>
685std::vector<std::vector<NNCdata>>
686EclGenericWriter<Grid,EquilGrid,GridView,ElementMapper,Scalar>::
687exportNncStructure_(const std::vector<std::unordered_map<int,int>>& levelCartToLevelCompressed,
688 const std::function<unsigned int(unsigned int)>& map,
689 const LevelIndicesFunction& computeLevelIndices,
690 const std::function<int(int, int)>& computeLevelCartIdx,
691 const std::function<std::array<int,3>(int)>& computeLevelCartDims,
692 const OriginIndicesFunction& computeOriginIndices) const
693{
694 const auto& nncData = this->eclState_.getInputNNC().input();
695 const auto& nncEdit = this->eclState_.getInputNNC().edit();
696 const auto& nncEditr = this->eclState_.getInputNNC().editr();
697 const auto& unitSystem = this->eclState_.getDeckUnitSystem();
698 const auto& transMult = this->eclState_.getTransMult();
699
700 // Cartesian index mapper for the serial I/O grid
701 const auto& equilCartMapper = *equilCartMapper_;
702
703 const auto& level0CartDims = equilCartMapper.cartesianDimensions();
704
705 int maxLevel = this->equilGrid_->maxLevel();
706 allocateAllNncs_(maxLevel);
707
708 using GlobalGridView = typename EquilGrid::LeafGridView;
709 using GlobElementMapper = Dune::MultipleCodimMultipleGeomTypeMapper<GlobalGridView>;
710 const GlobalGridView& globalGridView = this->equilGrid_->leafGridView();
711 const GlobElementMapper globalElemMapper { globalGridView, Dune::mcmgElementLayout() };
712
713 for (const auto& elem : elements(globalGridView)) {
714 for (const auto& is : intersections(globalGridView, elem)) {
715 if (!is.neighbor())
716 continue; // intersection is on the domain boundary
717
718 // Not 'const' because remapped if 'map' is non-null.
719 unsigned c1 = globalElemMapper.index(is.inside());
720 unsigned c2 = globalElemMapper.index(is.outside());
721
722 if (c1 > c2)
723 continue; // we only need to handle each connection once, thank you.
724
725 if ( is.inside().level() != is.outside().level() ) { // TRANGL and TRANLL
726 // For CpGrid with LGRs, level*Idx and c* do not coincide.
727 const auto& [levelInIdx, levelOutIdx] = computeLevelIndices(is, c1, c2);
728
729 const int levelIn = is.inside().level();
730 const int levelOut = is.outside().level();
731
732 auto levelCartIdxIn = computeLevelCartIdx(levelInIdx, levelIn);
733 auto levelCartIdxOut = computeLevelCartIdx(levelOutIdx, levelOut);
734
735 // To store correctly and only once the corresponding NNC
736 std::pair<int,int> smallerPair = {levelIn, levelCartIdxIn},
737 largerPair = {levelOut, levelCartIdxOut};
738 if (smallerPair.first > largerPair.first) {
739 std::swap(smallerPair, largerPair);
740 }
741
742 const auto& [smallerLevel, smallerLevelCartIdx] = smallerPair;
743 const auto& [largerLevel, largerLevelCartIdx] = largerPair;
744
745 auto t = this->gatheredOrGlobalTrans_(std::array{smallerLevel, smallerLevelCartIdx,
746 largerLevel, largerLevelCartIdx},
747 c1, c2);
748
749 // ECLIPSE ignores NNCs with zero transmissibility
750 // (different threshold than for NNC with corresponding
751 // EDITNNC above). In addition we do set small
752 // transmissibilities to zero when setting up the simulator.
753 // These will be ignored here, too.
754 const auto tt = unitSystem
755 .from_si(UnitSystem::measure::transmissibility, t);
756
757 if (std::isnormal(tt) && (tt > 1.0e-12)) {
758 // Store always FIRST the level Cartesian index of the cell belonging to the smaller level grid involved.
759 if (smallerLevel == 0) { // NNC between main (level zero) grid and a refined level/local grid
760 this->outputNncGlobalLocal_[largerLevel-1].emplace_back(smallerLevelCartIdx, largerLevelCartIdx, t);
761 }
762 else { // NNC between different refined level/local grids -> amlgamated NNC
763 assert(smallerLevel >= 1);
764 this->outputAmalgamatedNnc_[smallerLevel-1][largerLevel-smallerLevel-1].emplace_back(smallerLevelCartIdx, largerLevelCartIdx, t);
765 }
766 }
767 }
768 else {
769 // the cells sharing the intersection belong to the same level
770 assert(is.inside().level() == is.outside().level());
771 const int level = is.inside().level();
772
773 // For CpGrid with LGRs, the origin cell index refers to the coarsest
774 // ancestor cell when the cell is refined. For cells not involved in
775 // any refinement, it corresponds to the geometrically equivalent
776 // cell in the level-zero grid.
777 const auto [originInIdx, originOutIdx] = computeOriginIndices(is, c1, c2);
778
779 const std::size_t originCartIdxIn = computeLevelCartIdx(originInIdx, /* level = */ 0);
780 const std::size_t originCartIdxOut = computeLevelCartIdx(originOutIdx, /* level = */ 0);
781
782 // For CpGrid with LGRs, level*Idx and c* do not coincide.
783 const auto& [levelInIdx, levelOutIdx] = computeLevelIndices(is, c1, c2);
784
785 auto levelCartIdxIn = computeLevelCartIdx(levelInIdx, level);
786 auto levelCartIdxOut = computeLevelCartIdx(levelOutIdx, level);
787
788 if ( levelCartIdxOut < levelCartIdxIn )
789 std::swap(levelCartIdxIn, levelCartIdxOut);
790
791 // Re-ordering in case of non-empty mapping between equilGrid to grid
792 if (map) {
793 c1 = map(c1); // equilGridToGrid map
794 c2 = map(c2);
795 }
796
797 const auto& levelCartDims = computeLevelCartDims(level);
798
799 // Check there are no refined aquifer connections
800 assert(!isNumAquConn_(originCartIdxIn, originCartIdxOut) || level == 0);
801
802 if (isNumAquConn_(originCartIdxIn, originCartIdxOut) ||
803 ! isDirectNeighbours_(levelCartToLevelCompressed[level],
804 levelCartDims,
805 levelCartIdxIn, levelCartIdxOut)) {
806 // We need to check whether an NNC for this face was also
807 // specified via the NNC keyword in the deck.
808 // (levelCartIdxIn/Out are already swapped into min/max order above.)
809 auto t = this->gatheredOrGlobalTrans_(std::array{level, levelCartIdxIn, levelCartIdxOut},
810 c1, c2);
811
812 if (level == 0) {
813 auto candidate = std::lower_bound(nncData.begin(), nncData.end(),
814 NNCdata { originCartIdxIn, originCartIdxOut, 0.0 });
815 const auto transMlt = transMult.getRegionMultiplierNNC(originCartIdxIn, originCartIdxOut);
816 bool foundNncEditr = false;
817
818 while ((candidate != nncData.end()) &&
819 (candidate->cell1 == originCartIdxIn) &&
820 (candidate->cell2 == originCartIdxOut))
821 {
822 auto trans = candidate->trans;
823 trans *= transMlt;
824 if (! nncEditr.empty()) {
825 auto it = std::lower_bound(nncEditr.begin(), nncEditr.end(),
826 NNCdata { originCartIdxIn, originCartIdxOut, 0.0 });
827 foundNncEditr = it != nncEditr.end() && it->cell1 == originCartIdxIn && it->cell2 == originCartIdxOut;
828 }
829 if (foundNncEditr) {
830 // Only write one value for EDITNNCR, then skip it here and add it on the second loop below
831 break;
832 }
833 if (! nncEdit.empty()) {
834 auto it = std::lower_bound(nncEdit.begin(), nncEdit.end(),
835 NNCdata { originCartIdxIn, originCartIdxOut, 0.0 });
836 if (it != nncEdit.end() && it->cell1 == originCartIdxIn && it->cell2 == originCartIdxOut) {
837 trans *= it->trans;
838 }
839 }
840 t -= trans;
841 ++candidate;
842 }
843 if (foundNncEditr) {
844 // Only write one value for EDITNNCR, then skip it here and add it on the second loop below
845 continue;
846 }
847 }
848
849 // ECLIPSE ignores NNCs with zero transmissibility
850 // (different threshold than for NNC with corresponding
851 // EDITNNC above). In addition we do set small
852 // transmissibilities to zero when setting up the simulator.
853 // These will be ignored here, too.
854 const auto tt = unitSystem
855 .from_si(UnitSystem::measure::transmissibility, t);
856
857 if (std::isnormal(tt) && (tt > 1.0e-12)) {
858 this->outputNnc_[level].emplace_back(levelCartIdxIn, levelCartIdxOut, t);
859 }
860 }
861 }
862 }
863 }
864
865 // Do not include the generated NNCs transsmisibilities in the input NNCs
866 std::vector<NNCdata> inputedNnc{};
867 const auto generatedNnc = outputNnc_[0];
868
869 // The NNC keyword in the deck is defined only for faces in the level-0 grid.
870 // The same limitation applies to aquifer data.
871 for (const auto& entry : nncData) {
872 // Ignore most explicit NNCs between otherwise neighbouring cells.
873 // We keep NNCs that involve cells with numerical aquifers even if
874 // these might be between neighbouring cells in the Cartesian
875 // grid--e.g., between cells (I,J,K) and (I+1,J,K). All such
876 // connections should be written to NNC output arrays provided the
877 // transmissibility value is sufficiently large.
878 //
879 // The condition cell2 >= cell1 holds by construction of nncData.
880 assert (entry.cell2 >= entry.cell1);
881
882 if (! isCartesianNeighbour_(level0CartDims, entry.cell1, entry.cell2) ||
883 isNumAquConn_(entry.cell1, entry.cell2))
884 {
885 bool foundNncEdit = false;
886 auto trans = entry.trans;
887 if (! nncEdit.empty()) {
888 auto it = std::lower_bound(nncEdit.begin(), nncEdit.end(),
889 NNCdata {entry.cell1, entry.cell2, 0.0 });
890 if (it != nncEdit.end() && it->cell1 == entry.cell1 && it->cell2 == entry.cell2) {
891 trans *= it->trans;
892 foundNncEdit = true;
893 }
894 }
895 if (! foundNncEdit) {
896 // Pick up transmissibility value from 'globalTrans()' since
897 // multiplier keywords like MULTREGT might have impacted the
898 // values entered in primary sources like NNC/EDITNNC/EDITNNCR.
899 const auto c1 = activeCell_(levelCartToLevelCompressed[/* level */0], entry.cell1);
900 const auto c2 = activeCell_(levelCartToLevelCompressed[/* level */0], entry.cell2);
901
902 if ((c1 < 0) || (c2 < 0)) {
903 // Connection between inactive cells? Unexpected at this
904 // level. Might consider 'throw'ing if this happens...
905 continue;
906 }
907
908 // A deck NNC names a pair of level-zero cells. When one of them is
909 // refined by an LGR the pair is no longer a leaf connection, so the
910 // gathered records (correctly) hold no value for it -- keep the
911 // deck-specified transmissibility in that case.
912 const auto key = std::array{0, static_cast<int>(entry.cell1),
913 static_cast<int>(entry.cell2)};
914 const double* gathered = this->findGatheredTrans_(key);
915 if (!this->gatheredLgrTrans_.has_value() || gathered != nullptr) {
916 trans = (gathered != nullptr)
917 ? *gathered
918 : this->globalTrans().transmissibility(c1, c2);
919
920 if (! generatedNnc.empty()) {
921 for (const auto& generated : generatedNnc) {
922 if (entry.cell1 == generated.cell1 && entry.cell2 == generated.cell2) {
923 trans -= generated.trans;
924 break;
925 }
926 }
927 }
928 }
929 }
930 const auto tt = unitSystem
931 .from_si(UnitSystem::measure::transmissibility, trans);
932
933 // ECLIPSE ignores NNCs (with EDITNNC/EDITNNCR applied) with
934 // small transmissibility values. Seems like the threshold is
935 // 1.0e-6 in output units.
936 if (std::isnormal(tt) && ! (tt < 1.0e-6)) {
937 inputedNnc.emplace_back(entry.cell1, entry.cell2, trans);
938 }
939 }
940 }
941 // Write first the inputed NNCs and after the internally computed NNCs
942 this->outputNnc_[0].insert(this->outputNnc_[0].begin(), inputedNnc.begin(), inputedNnc.end());
943 return this->outputNnc_;
944}
945
946template<class Grid, class EquilGrid, class GridView, class ElementMapper, class Scalar>
948doWriteOutput(const int reportStepNum,
949 const std::optional<int> timeStepNum,
950 const bool isSubStep,
951 const bool isForcedFinalOutput,
952 data::Solution&& localCellData,
953 data::Wells&& localWellData,
954 data::GroupAndNetworkValues&& localGroupAndNetworkData,
955 data::Aquifers&& localAquiferData,
956 WellTestState&& localWTestState,
957 const Action::State& actionState,
958 const UDQState& udqState,
959 const SummaryState& summaryState,
960 const std::vector<Scalar>& thresholdPressure,
961 Scalar curTime,
962 Scalar nextStepSize,
963 bool doublePrecision,
964 bool isFlowsn,
965 std::array<FlowsData<double>, 3>&& flowsn,
966 bool isFloresn,
967 std::array<FlowsData<double>, 3>&& floresn)
968{
969 const auto isParallel = this->collectOnIORank_.isParallel();
970 const bool needsReordering = this->collectOnIORank_.doesNeedReordering();
971
972 RestartValue restartValue {
973 (isParallel || needsReordering)
974 ? this->collectOnIORank_.globalCellData()
975 : std::move(localCellData),
976
977 isParallel ? this->collectOnIORank_.globalWellData()
978 : std::move(localWellData),
979
980 isParallel ? this->collectOnIORank_.globalGroupAndNetworkData()
981 : std::move(localGroupAndNetworkData),
982
983 isParallel ? this->collectOnIORank_.globalAquiferData()
984 : std::move(localAquiferData)
985 };
986
987 if (eclState_.getSimulationConfig().useThresholdPressure()) {
988 restartValue.addExtra("THRESHPR", UnitSystem::measure::pressure,
989 thresholdPressure);
990 }
991
992 // Add suggested next timestep to extra data.
993 if (! isSubStep) {
994 restartValue.addExtra("OPMEXTRA", std::vector<double>(1, nextStepSize));
995 }
996
997 // Add nnc flows and flores.
998 if (isFlowsn) {
999 const auto flowsn_global = isParallel ? this->collectOnIORank_.globalFlowsn() : std::move(flowsn);
1000 for (const auto& flows : flowsn_global) {
1001 if (flows.name.empty())
1002 continue;
1003 if (flows.name == "FLOGASN+") {
1004 restartValue.addExtra(flows.name, UnitSystem::measure::gas_surface_rate, flows.values);
1005 } else {
1006 restartValue.addExtra(flows.name, UnitSystem::measure::liquid_surface_rate, flows.values);
1007 }
1008 }
1009 }
1010 if (isFloresn) {
1011 const auto floresn_global = isParallel ? this->collectOnIORank_.globalFloresn() : std::move(floresn);
1012 for (const auto& flores : floresn_global) {
1013 if (flores.name.empty()) {
1014 continue;
1015 }
1016 restartValue.addExtra(flores.name, UnitSystem::measure::rate, flores.values);
1017 }
1018 }
1019
1020 std::vector<Opm::RestartValue> restartValues{};
1021 // only serial, only CpGrid (for now)
1022 if ( !isParallel && !needsReordering && (this->eclState_.getLgrs().size()>0) && (this->grid_.maxLevel()>0) ) {
1023 // Level cells that appear on the leaf grid view get the data::Solution values from there.
1024 // Other cells (i.e., parent cells that vanished due to refinement) get rubbish values for now.
1025 // Only data::Solution is restricted to the level grids. Well, GroupAndNetwork, Aquifer are
1026 // not modified in this method.
1027 Opm::Lgr::extractRestartValueLevelGrids<Grid>(this->grid_, restartValue, restartValues);
1028 }
1029 else {
1030 restartValues.reserve(1); // minimum size
1031 restartValues.push_back(std::move(restartValue)); // no LGRs-> only one restart value
1032 }
1033
1034 // make sure that the previous I/O request has been completed
1035 // and the number of incomplete tasklets does not increase between
1036 // time steps
1037 this->taskletRunner_->barrier();
1038
1039 // check if there might have been a failure in the TaskletRunner
1040 if (this->taskletRunner_->failure()) {
1041 throw std::runtime_error("Failure in the TaskletRunner while writing output.");
1042 }
1043
1044 // create a tasklet to write the data for the current time step to disk
1045 auto eclWriteTasklet = std::make_shared<EclWriteTasklet>(
1046 actionState,
1047 isParallel ? this->collectOnIORank_.globalWellTestState() : std::move(localWTestState),
1048 summaryState, udqState, *this->eclIO_,
1049 reportStepNum, timeStepNum, isSubStep, curTime, std::move(restartValues), doublePrecision,
1050 isForcedFinalOutput);
1051
1052 // finally, start a new output writing job
1053 this->taskletRunner_->dispatch(std::move(eclWriteTasklet));
1054}
1055
1056template<class Grid, class EquilGrid, class GridView, class ElementMapper, class Scalar>
1058evalSummary(const int reportStepNum,
1059 const Scalar curTime,
1060 const data::Wells& localWellData,
1061 const data::WellBlockAveragePressures& localWBPData,
1062 const data::GroupAndNetworkValues& localGroupAndNetworkData,
1063 const std::map<int,data::AquiferData>& localAquiferData,
1064 const std::map<std::pair<std::string, int>, double>& blockData,
1065 const std::map<std::tuple<std::string, int, int>, double>& lgrBlockData,
1066 const std::map<std::string, double>& miscSummaryData,
1067 const std::map<std::string, std::vector<double>>& regionData,
1068 const data::RegionVariableMapping& regVarMap,
1069 const RegionVariableCollection& regVars,
1070 const Inplace& inplace,
1071 const Inplace* initialInPlace,
1072 const InterRegFlowMap& interRegFlows,
1073 SummaryState& summaryState,
1074 UDQState& udqState,
1075 const data::ReservoirCouplingGroupRates* rcGroupRates)
1076{
1077 if (collectOnIORank_.isIORank()) {
1078 const auto& wellData = this->collectOnIORank_.isParallel()
1079 ? this->collectOnIORank_.globalWellData()
1080 : localWellData;
1081
1082 const auto& wbpData = this->collectOnIORank_.isParallel()
1083 ? this->collectOnIORank_.globalWBPData()
1084 : localWBPData;
1085
1086 const auto& groupAndNetworkData = this->collectOnIORank_.isParallel()
1087 ? this->collectOnIORank_.globalGroupAndNetworkData()
1088 : localGroupAndNetworkData;
1089
1090 const auto& aquiferData = this->collectOnIORank_.isParallel()
1091 ? this->collectOnIORank_.globalAquiferData()
1092 : localAquiferData;
1093
1094 const auto interreg_flows = getInterRegFlowsAsMap(interRegFlows);
1095
1096 const auto values = out::Summary::DynamicSimulatorState {
1097 .well_solution = &wellData,
1098 .wbp = &wbpData,
1099 .group_and_nwrk_solution = &groupAndNetworkData,
1100 .single_values = &miscSummaryData,
1101 .region_values = &regionData,
1102 .reg_var_map = &regVarMap,
1103 .reg_var_coll = &regVars,
1104 .block_values = &blockData,
1105 .aquifer_values = &aquiferData,
1106 .interreg_flows = &interreg_flows,
1107 .rc_group_rates = rcGroupRates,
1108 .inplace = {
1109 .current = &inplace,
1110 .initial = initialInPlace
1111 },
1112 .lgr_block_values = &lgrBlockData
1113 };
1114
1115 this->eclIO_->summary()
1116 .eval(reportStepNum, curTime, values, summaryState);
1117
1118 // Off-by-one-fun: The reportStepNum argument corresponds to the
1119 // report step these results will be written to, whereas the
1120 // argument to UDQ function evaluation corresponds to the report
1121 // step we are currently on.
1122 const auto udq_step = reportStepNum - 1;
1123
1124 this->schedule_[udq_step].udq()
1125 .eval(udq_step,
1126 this->schedule_.wellMatcher(udq_step),
1127 this->schedule_[udq_step].group_order(),
1128 this->schedule_.segmentMatcherFactory(udq_step),
1129 [es = std::cref(this->eclState_)]() {
1130 return std::make_unique<RegionSetMatcher>
1131 (es.get().fipRegionStatistics());
1132 },
1133 summaryState, udqState);
1134 }
1135
1136#if HAVE_MPI
1137 if (collectOnIORank_.isParallel()) {
1138 Parallel::MpiSerializer ser(grid_.comm());
1139 ser.append(summaryState);
1140 }
1141#endif
1142}
1143
1144template<class Grid, class EquilGrid, class GridView, class ElementMapper, class Scalar>
1145template<std::size_t N>
1146const double*
1148findGatheredTrans_(const std::array<int,N>& key) const
1149{
1150 static_assert(N == 3 || N == 4, "unknown gathered LGR record shape");
1151
1152 if (!gatheredLgrTrans_.has_value()) {
1153 return nullptr;
1154 }
1155
1156 if constexpr (N == 3) {
1157 return gatheredLgrTrans_->sameLevel.find(key);
1158 } else {
1159 return gatheredLgrTrans_->crossLevel.find(key);
1160 }
1161}
1162
1163template<class Grid, class EquilGrid, class GridView, class ElementMapper, class Scalar>
1164template<std::size_t N>
1165double
1167gatheredOrGlobalTrans_(const std::array<int,N>& key,
1168 unsigned c1,
1169 unsigned c2) const
1170{
1171 if (!gatheredLgrTrans_.has_value()) {
1172 return this->globalTrans().transmissibility(c1, c2);
1173 }
1174
1175 if (const double* value = this->findGatheredTrans_(key); value != nullptr) {
1176 return *value;
1177 }
1178
1179 std::string msg = "Gathered LGR transmissibilities: no value for connection key (";
1180 for (std::size_t j = 0; j < N; ++j) {
1181 msg += std::to_string(key[j]);
1182 msg += (j + 1 < N) ? ", " : ")";
1183 }
1184 throw std::logic_error { msg };
1185}
1186
1187template<class Grid, class EquilGrid, class GridView, class ElementMapper, class Scalar>
1190globalTrans() const
1191{
1192 assert (globalTrans_);
1193 return *globalTrans_;
1194}
1195
1196} // namespace Opm
1197
1198#endif // OPM_ECL_GENERIC_WRITER_IMPL_HPP
#define OPM_END_PARALLEL_TRY_CATCH(prefix, comm)
Catch exception and throw in a parallel try-catch clause.
Definition: DeferredLoggingErrorHelpers.hpp:197
#define OPM_BEGIN_PARALLEL_TRY_CATCH()
Macro to setup the try of a parallel try-catch.
Definition: DeferredLoggingErrorHelpers.hpp:160
Definition: CollectDataOnIORank.hpp:50
bool isIORank() const
Definition: CollectDataOnIORank.hpp:131
Definition: EclGenericWriter.hpp:76
std::vector< std::vector< NNCdata > > outputNnc_
Definition: EclGenericWriter.hpp:213
const EclipseState & eclState_
Definition: EclGenericWriter.hpp:196
double gatheredOrGlobalTrans_(const std::array< int, N > &key, unsigned c1, unsigned c2) const
Definition: EclGenericWriter_impl.hpp:1167
void evalSummary(int reportStepNum, Scalar curTime, const data::Wells &localWellData, const data::WellBlockAveragePressures &localWBPData, const data::GroupAndNetworkValues &localGroupAndNetworkData, const std::map< int, data::AquiferData > &localAquiferData, const std::map< std::pair< std::string, int >, double > &blockData, const std::map< std::tuple< std::string, int, int >, double > &lgrBlockData, const std::map< std::string, double > &miscSummaryData, const std::map< std::string, std::vector< double > > &regionData, const data::RegionVariableMapping &regVarMap, const RegionVariableCollection &regVars, const Inplace &inplace, const Inplace *initialInPlace, const InterRegFlowMap &interRegFlows, SummaryState &summaryState, UDQState &udqState, const data::ReservoirCouplingGroupRates *rcGroupRates=nullptr)
Definition: EclGenericWriter_impl.hpp:1058
CollectDataOnIORankType collectOnIORank_
Definition: EclGenericWriter.hpp:192
void extractOutputTransAndNNC(const std::function< unsigned int(unsigned int)> &map)
Definition: EclGenericWriter_impl.hpp:313
std::unique_ptr< TaskletRunner > taskletRunner_
Definition: EclGenericWriter.hpp:198
void writeInit()
Definition: EclGenericWriter_impl.hpp:281
const double * findGatheredTrans_(const std::array< int, N > &key) const
Definition: EclGenericWriter_impl.hpp:1148
EclGenericWriter(const Schedule &schedule, const EclipseState &eclState, const SummaryConfig &summaryConfig, const Grid &grid, const EquilGrid *equilGrid, const GridView &gridView, const Dune::CartesianIndexMapper< Grid > &cartMapper, const Dune::CartesianIndexMapper< EquilGrid > *equilCartMapper, bool enableAsyncOutput, bool enableEsmry)
Definition: EclGenericWriter_impl.hpp:227
const EclipseIO & eclIO() const
Definition: EclGenericWriter_impl.hpp:273
const TransmissibilityType & globalTrans() const
Definition: EclGenericWriter_impl.hpp:1190
void doWriteOutput(const int reportStepNum, const std::optional< int > timeStepNum, const bool isSubStep, const bool forcedSimulationFinished, data::Solution &&localCellData, data::Wells &&localWellData, data::GroupAndNetworkValues &&localGroupAndNetworkData, data::Aquifers &&localAquiferData, WellTestState &&localWTestState, const Action::State &actionState, const UDQState &udqState, const SummaryState &summaryState, const std::vector< Scalar > &thresholdPressure, Scalar curTime, Scalar nextStepSize, bool doublePrecision, bool isFlowsn, std::array< FlowsData< double >, 3 > &&flowsn, bool isFloresn, std::array< FlowsData< double >, 3 > &&floresn)
Definition: EclGenericWriter_impl.hpp:948
Inter-region flow accumulation maps for all region definition arrays.
Definition: InterRegFlows.hpp:179
std::vector< data::InterRegFlowMap > getInterRegFlows() const
const std::vector< std::string > & names() const
Definition: EclGenericWriter.hpp:54
Class for serializing and broadcasting data using MPI.
Definition: MPISerializer.hpp:38
void append(T &data, int root=0)
Serialize and broadcast on root process, de-serialize and append on others.
Definition: MPISerializer.hpp:82
void broadcast(RootRank rootrank, Args &&... args)
Definition: MPISerializer.hpp:47
The base class for tasklets.
Definition: tasklets.hpp:45
virtual void run()=0
Handles where a given tasklet is run.
Definition: tasklets.hpp:93
Definition: Transmissibility.hpp:55
Definition: blackoilbioeffectsmodules.hh:45
std::string to_string(const ConvergenceReport::ReservoirFailure::Type t)
Avoid mistakes in calls to broadcast() by wrapping the root argument in an explicit type.
Definition: MPISerializer.hpp:33