FlowProblemComp.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 Copyright 2024, 2026 SINTEF Digital
5
6 This file is part of the Open Porous Media project (OPM).
7
8 OPM is free software: you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation, either version 2 of the License, or
11 (at your option) any later version.
12
13 OPM is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with OPM. If not, see <http://www.gnu.org/licenses/>.
20
21 Consult the COPYING file in the top-level source directory of this
22 module for the precise wording of the license and the list of
23 copyright holders.
24*/
30#ifndef OPM_FLOW_PROBLEM_COMP_HPP
31#define OPM_FLOW_PROBLEM_COMP_HPP
32
33
42
43#include <opm/grid/LookUpData.hh>
44
45#include <opm/material/constraintsolvers/PTFlashMethod.hpp>
46#include <opm/material/fluidstates/CompositionalFluidState.hpp>
47
48#include <opm/material/thermal/EclThermalLawManager.hpp>
49
50#include <opm/common/OpmLog/OpmLog.hpp>
51
52#include <opm/input/eclipse/EclipseState/Compositional/CompositionalConfig.hpp>
53
54#include <dune/grid/common/rangegenerators.hh>
55
56#include <fmt/format.h>
57
58#include <algorithm>
59#include <array>
60#include <cstdint>
61#include <functional>
62#include <optional>
63#include <stdexcept>
64#include <string>
65#include <vector>
66
67namespace Opm::Parameters {
68
69// Treat explicit compositional saturations as given before the hydrocarbon
70// flash and rescale SWAT accordingly. By default, keep the input SWAT.
71struct NormalizeExplicitSwat { static constexpr bool value = false; };
72
73} // namespace Opm::Parameters
74
75namespace Opm {
76
83template <class TypeTag>
84class FlowProblemComp : public FlowProblem<TypeTag>
85{
86 // TODO: the naming of the Types will be adjusted
88
89 using typename FlowProblemType::Scalar;
90 using typename FlowProblemType::Simulator;
91 using typename FlowProblemType::GridView;
92 using typename FlowProblemType::FluidSystem;
93 using typename FlowProblemType::Vanguard;
94
95 // might not be needed
98
101
105
106 using typename FlowProblemType::Indices;
109 using typename FlowProblemType::Evaluation;
111 using typename FlowProblemType::MaterialLaw;
112 using typename FlowProblemType::RateVector;
113
114 using InitialFluidState = CompositionalFluidState<Scalar, FluidSystem>;
116
117public:
120
124 static void registerParameters()
125 {
127 Parameters::SetDefault<Parameters::InputSkipMode>("300");
128
130
131 // tighter tolerance is needed for compositional modeling here
132 Parameters::SetDefault<Parameters::NewtonTolerance<Scalar>>(1e-7);
133 // the CPR solves of compositional systems converge far more reliably
134 // when the fine smoother also runs before the pressure correction.
135 // Only ISTLSolver and its runtime proxy register the parameter;
136 // flowexp_comp uses another linear solver.
138 if constexpr (std::is_same_v<Backend, ISTLSolver<TypeTag>>
139 || std::is_same_v<Backend, ISTLSolverRuntimeOptionProxy<TypeTag>>) {
140 Parameters::SetDefault<Parameters::CprPreSmooth>(1);
141 }
142
143 Parameters::Register<Parameters::NormalizeExplicitSwat>(
144 "Treat the explicit initial saturations of compositional runs as "
145 "saturations before the hydrocarbon flash: SGAS and the oil "
146 "saturation 1 - SWAT - SGAS each hold their input composition at "
147 "the vapour or liquid equation-of-state root, and water and the "
148 "flashed phases are then rescaled to fill the pore volume. When "
149 "disabled (the default), the input SWAT is retained.");
150 }
151
152 Opm::CompositionalConfig::EOSType getEosType() const
153 {
154 auto& simulator = this->simulator();
155 const auto& eclState = simulator.vanguard().eclState();
156 return eclState.compositionalConfig().eosType(0);
157 }
158
162 explicit FlowProblemComp(Simulator& simulator)
163 : FlowProblemType(simulator)
164 {
165 eclWriter_ = std::make_unique<EclWriterType>(simulator);
166 enableEclOutput_ = Parameters::Get<Parameters::EnableEclOutput>();
167 }
168
173 {
174 FlowProblemType::finishInit();
175
176 auto& simulator = this->simulator();
177 const bool transmissibilitiesFinished =
178 this->prepareTransmissibilityOutput_(*eclWriter_, enableEclOutput_);
179 if (!transmissibilitiesFinished) {
181 }
182
183 const auto& eclState = simulator.vanguard().eclState();
184 const auto& schedule = simulator.vanguard().schedule();
186
187 this->initFluidSystem_();
188 if (eclState.getSimulationConfig().rock_config().active()) {
189 throw std::runtime_error("Rock compaction (ROCKCOMP) is not supported "
190 "by compositional modeling yet");
191 }
193
194 // write the static output files (EGRID, INIT)
195 if (enableEclOutput_) {
196 eclWriter_->writeInit();
197 }
198
199 const auto& initconfig = eclState.getInitConfig();
200 if (initconfig.restartRequested())
202 else
203 this->readInitialCondition_();
204
206
207 if constexpr (getPropValue<TypeTag, Properties::EnablePolymer>()) {
208 const auto& vanguard = this->simulator().vanguard();
209 const auto& gridView = vanguard.gridView();
210 int numElements = gridView.size(/*codim=*/0);
211 this->polymer_.maxAdsorption.resize(numElements, 0.0);
212 }
213
214 /* readBoundaryConditions_();
215
216 // compute and set eq weights based on initial b values
217 computeAndSetEqWeights_();
218
219 if (enableDriftCompensation_) {
220 drift_.resize(this->model().numGridDof());
221 drift_ = 0.0;
222 } */
223
224 // TODO: check wether the following can work with compostional
225 if (this->enableVtkOutput_() && eclState.getIOConfig().initOnly()) {
226 simulator.setTimeStepSize(0.0);
228 }
229
230 // after finishing the initialization and writing the initial solution, we move
231 // to the first "real" episode/report step
232 // for restart the episode index and start is already set
233 if (!initconfig.restartRequested()) {
234 simulator.startNextEpisode(schedule.seconds(1));
235 simulator.setEpisodeIndex(0);
236 simulator.setTimeStepIndex(0);
237 }
238 }
239
244 {
246
247 // Defaulted threshold pressures are taken from the initial solution.
248 this->thresholdPressures_.finishInit();
249
250 reportTwoPhaseEquilCells_();
251 }
252
256 void endTimeStep() override
257 {
259
260 // after the solution is updated, the values in output module also needs to be updated
261 this->eclWriter_->mutableOutputModule().invalidateLocalData();
262
263 // For CpGrid with LGRs, ecl/vtk output is not supported yet.
264 const auto& grid = this->simulator().vanguard().gridView().grid();
265
266 using GridType = std::remove_cv_t<std::remove_reference_t<decltype(grid)>>;
267 constexpr bool isCpGrid = std::is_same_v<GridType, Dune::CpGrid>;
268 if (!isCpGrid || (grid.maxLevel() == 0)) {
269 this->eclWriter_->evalSummaryState(! this->episodeWillBeOver());
270 }
271 }
272
273 void writeReports(const SimulatorTimer& timer) {
274 if (enableEclOutput_){
275 eclWriter_->writeReports(timer);
276 }
277 }
278
283 void writeOutput(bool verbose) override
284 {
286
287 if (! this->enableEclOutput_) {
288 return;
289 }
290
291 const auto isSubStep = !this->episodeWillBeOver();
292
293 if (!isSubStep || Parameters::Get<Parameters::EnableWriteAllSolutions>()) {
294 auto localCellData = data::Solution {};
295
296 this->eclWriter_->writeOutput(std::move(localCellData), isSubStep,
297 this->simulator().vanguard().schedule()
298 .exitStatus().has_value());
299 }
300 }
301
307 template <class Context>
308 void boundary(BoundaryRateVector& values,
309 const Context& context,
310 unsigned spaceIdx,
311 unsigned /* timeIdx */) const
312 {
313 OPM_TIMEBLOCK_LOCAL(eclProblemBoundary, Subsystem::Assembly);
314 if (!context.intersection(spaceIdx).boundary())
315 return;
316
317 values.setNoFlow();
318
319 if (this->nonTrivialBoundaryConditions()) {
320 throw std::logic_error("boundary condition is not supported by compostional modeling yet");
321 }
322 }
323
330 template <class Context>
331 void initial(PrimaryVariables& values, const Context& context, unsigned spaceIdx, unsigned timeIdx) const
332 {
333 const unsigned globalDofIdx = context.globalSpaceIndex(spaceIdx, timeIdx);
334 const auto& initial_fs = initialFluidStates_[globalDofIdx];
335 Opm::CompositionalFluidState<Scalar, FluidSystem> fs;
336 for (unsigned p = 0; p < numPhases; ++p) { // TODO: assuming the phaseidx continuous
337 // pressure
338 fs.setPressure(p, initial_fs.pressure(p));
339
340 // saturation
341 fs.setSaturation(p, initial_fs.saturation(p));
342
343 // temperature
344 fs.setTemperature(initial_fs.temperature(p));
345 }
346
347
348 if (!zmf_initialization_) {
349 for (unsigned p = 0; p < numPhases; ++p) {
350 for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) {
351 fs.setMoleFraction(p, compIdx, initial_fs.moleFraction(p, compIdx));
352 }
353 }
354
355 {
356 const auto& eos_type = getEosType();
357 typename FluidSystem::template ParameterCache<Scalar> paramCache(eos_type);
358 paramCache.updatePhase(fs, FluidSystem::oilPhaseIdx);
359 paramCache.updatePhase(fs, FluidSystem::gasPhaseIdx);
360 fs.setDensity(FluidSystem::oilPhaseIdx, FluidSystem::density(fs, paramCache, FluidSystem::oilPhaseIdx));
361 fs.setDensity(FluidSystem::gasPhaseIdx, FluidSystem::density(fs, paramCache, FluidSystem::gasPhaseIdx));
362 }
363 // determine the component fractions
364 Dune::FieldVector<Scalar, numComponents> z(0.0);
365 Scalar sumMoles = 0.0;
366 for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) {
367 if (Indices::waterEnabled && phaseIdx == static_cast<unsigned int>(waterPhaseIdx)){
368 continue;
369 }
370 const auto saturation = fs.saturation(phaseIdx);
371 for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) {
372 Scalar tmp = fs.molarity(phaseIdx, compIdx) * saturation;
373 tmp = max(tmp, 1e-8);
374 z[compIdx] += tmp;
375 sumMoles += tmp;
376 }
377 }
378 z /= sumMoles;
379 for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) {
380 fs.setMoleFraction(compIdx, z[compIdx]);
381 }
382 } else {
383 // TODO: should we normalize the input?
384 for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) {
385 fs.setMoleFraction(compIdx, initial_fs.moleFraction(compIdx));
386 }
387 }
388
389 // Set initial K and L
390 for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) {
391 const auto& Ktmp = fs.wilsonK_(compIdx);
392 fs.setKvalue(compIdx, Ktmp);
393 }
394
395 const Scalar& Ltmp = -1.0;
396 fs.setLvalue(Ltmp);
397
398 values.assignNaive(fs);
399 }
400
401 void addToSourceDense(RateVector&, unsigned, unsigned) const override
402 {
403 // we do nothing for now
404 }
405
406 const InitialFluidState& initialFluidState(unsigned globalDofIdx) const
407 { return initialFluidStates_[globalDofIdx]; }
408
409 std::vector<InitialFluidState>& initialFluidStates()
410 { return initialFluidStates_; }
411
412 const std::vector<InitialFluidState>& initialFluidStates() const
413 { return initialFluidStates_; }
414
416 { return *eclWriter_; }
417
419 { return *eclWriter_; }
420
422 { return eclWriter_->setSubStepReport(report); }
423
425 { return eclWriter_->setSimulationReport(report); }
426
428 {
429 OPM_TIMEBLOCK(finalizeOutput);
430 eclWriter_.reset();
431 }
432
433 // TODO: do we need this one?
434 template<class Serializer>
435 void serializeOp(Serializer& serializer)
436 {
437 serializer(static_cast<FlowProblemType&>(*this));
438 serializer(*eclWriter_);
439 }
440protected:
441
442 void updateExplicitQuantities_(int /* episodeIdx*/, int /* timeStepSize */, bool /* first_step_after_restart */) override
443 {
444 // we do nothing here for now
445 }
446
448 {
449 const auto& simulator = this->simulator();
450 const auto& vanguard = simulator.vanguard();
451 const auto& eclState = vanguard.eclState();
452
453 // Zero-based equilibration region of every cell (EQLNUM, or region 0).
454 // EQLNUM is given on the unrefined input grid while the equilibration
455 // works on the leaf grid, so a refined cell takes its parent's region;
456 // without local refinement the mapping is the identity.
457 std::vector<int> eqlnum(this->model().numGridDof(), 0);
458 if (eclState.fieldProps().has_int("EQLNUM")) {
460 lookUpData(vanguard.gridView());
461 eqlnum = lookUpData.template assignFieldPropsIntOnLeaf<int>(
462 eclState.fieldProps(), "EQLNUM", /*needsTranslation=*/true);
463 }
464
465 // The water saturation endpoints are selected by SATNUM and scaled per
466 // cell, so they are read here rather than from the equilibration region.
467 std::vector<Scalar> connateWater, maxWater;
468 if (FluidSystem::phaseIsActive(FluidSystem::waterPhaseIdx)) {
469 const auto numDof = this->model().numGridDof();
470 connateWater.resize(numDof);
471 maxWater.resize(numDof);
472 for (std::size_t dofIdx = 0; dofIdx < numDof; ++dofIdx) {
473 const auto& eps = this->materialLawManager()
474 .oilWaterScaledEpsInfoDrainage(static_cast<int>(dofIdx));
475 connateWater[dofIdx] = eps.Swl;
476 maxWater[dofIdx] = eps.Swu;
477 }
478 }
479
481 eclState,
482 getEosType(),
483 vanguard.cellCenterDepths(),
484 eqlnum,
485 this->pvtnum_,
486 vanguard.gridView().comm(),
487 this->gravity()[dimWorld - 1],
488 this->numPressurePointsEquil(),
489 connateWater,
490 maxWater);
491
492 initialFluidStates_ = std::move(initialState.fluidStates());
493 twoPhaseCheckRegions_ = std::move(initialState.twoPhaseCheckRegions());
494 // The current flash has one pressure for all phases. Adapt the
495 // independent equilibrium columns here, using the water column below
496 // the contact. Once phase-pressure differences are supported by the
497 // flash, the equilibrated phase pressures can be used directly.
498 for (std::size_t cell = 0; cell < initialFluidStates_.size(); ++cell) {
499 for (unsigned phaseIdx = 0; phaseIdx < FluidSystem::numPhases; ++phaseIdx) {
500 initialFluidStates_[cell].setPressure(phaseIdx,
501 initialState.referencePressures()[cell]);
502 }
503 }
504 // The primary variables are formed from the total composition; see initial().
505 zmf_initialization_ = true;
506 }
507
509 {
510 throw std::logic_error("Restarting is not supported by compositional modeling yet");
511 }
512
514 {
516 }
517
519 {
520 const auto& simulator = this->simulator();
521 const auto& vanguard = simulator.vanguard();
522 const auto& eclState = vanguard.eclState();
523 const auto& fp = eclState.fieldProps();
524 const bool has_pressure = fp.has_double("PRESSURE");
525 if (!has_pressure)
526 throw std::runtime_error("The ECL input file requires the presence of the PRESSURE "
527 "keyword if the model is initialized explicitly");
528
529 const bool has_xmf = fp.has_double("XMF");
530 const bool has_ymf = fp.has_double("YMF");
531 const bool has_zmf = fp.has_double("ZMF");
532 if ( !has_zmf && !(has_xmf && has_ymf) ) {
533 throw std::runtime_error("The ECL input file requires the presence of ZMF or XMF and YMF "
534 "keyword if the model is initialized explicitly");
535 }
536
537 if (has_zmf && (has_xmf || has_ymf)) {
538 throw std::runtime_error("The ECL input file can not handle explicit initialization "
539 "with both ZMF and XMF or YMF");
540 }
541
542 if (has_xmf != has_ymf) {
543 throw std::runtime_error("The ECL input file needs XMF and YMF combined to do the explicit "
544 "initializtion when using XMF or YMF");
545 }
546
547 std::size_t numDof = this->model().numGridDof();
548
549 initialFluidStates_.resize(numDof);
550
551 const bool water_active = FluidSystem::phaseIsActive(waterPhaseIdx);
552 const bool gas_active = FluidSystem::phaseIsActive(gasPhaseIdx);
553 const bool oil_active = FluidSystem::phaseIsActive(oilPhaseIdx);
554 const std::array activePhases {water_active, oil_active, gas_active};
555 constexpr std::array saturationKeywords {"SWAT", "SOIL", "SGAS"};
556 std::array<const std::vector<double>*, 3> saturationData {};
557 bool allActiveSaturationsSupplied = true;
558 for (std::size_t phase = 0; phase < activePhases.size(); ++phase) {
559 if (!activePhases[phase]) {
560 continue;
561 }
562 if (fp.has_double(saturationKeywords[phase])) {
563 saturationData[phase] = &fp.get_double(saturationKeywords[phase]);
564 } else {
565 allActiveSaturationsSupplied = false;
566 }
567 }
568
569 const auto& pressureData = fp.get_double("PRESSURE");
570
571 // Without TEMPI in the deck, this is the temperature from RTEMPVD or RTEMP.
572 const auto& tempiData = fp.get_double("TEMPI");
573
574 const auto cellName = [&vanguard](const std::size_t dofIdx) {
575 std::array<int, 3> ijk;
576 vanguard.cartesianCoordinate(dofIdx, ijk);
577 return fmt::format("cell ({}, {}, {})", ijk[0] + 1, ijk[1] + 1, ijk[2] + 1);
578 };
579
580 // Invalid input on one rank must not leave the others entering the
581 // collective operations in normalizeExplicitSwat_().
582 const auto& comm = vanguard.grid().comm();
583 DeferredLogger deferredLogger;
584 std::vector<bool> ownedCells;
585 if (allActiveSaturationsSupplied) {
586 ownedCells.resize(numDof, false);
587 for (const auto& element : elements(vanguard.gridView(), Dune::Partitions::interior)) {
588 ownedCells[this->elementMapper().index(element)] = true;
589 }
590 }
591 std::uint64_t normalizedCellCount = 0;
592 double maxNormalizationDeviation = 0.0;
594 for (std::size_t dofIdx = 0; dofIdx < numDof; ++dofIdx) {
595 auto& dofFluidState = initialFluidStates_[dofIdx];
596 // dofFluidState.setPvtRegionIndex(pvtRegionIndex(dofIdx));
597
598 std::array<std::optional<double>, 3> supplied;
599 for (std::size_t phase = 0; phase < saturationData.size(); ++phase) {
600 if (saturationData[phase]) {
601 supplied[phase] = (*saturationData[phase])[dofIdx];
602 }
603 }
605 try {
606 completed = completeExplicitSaturations(supplied, activePhases);
607 } catch (const std::invalid_argument& error) {
608 throw std::invalid_argument("Invalid explicit saturations in "
609 + cellName(dofIdx) + ": " + error.what());
610 }
611 const auto& saturations = completed.saturations;
612 if (allActiveSaturationsSupplied && ownedCells[dofIdx]
613 && completed.normalizationDeviation > 0.0) {
614 ++normalizedCellCount;
615 maxNormalizationDeviation
616 = std::max(maxNormalizationDeviation, completed.normalizationDeviation);
617 }
618
619 const Scalar temperatureLoc = tempiData[dofIdx];
620 if (!std::isfinite(temperatureLoc) || temperatureLoc <= 0) {
621 throw std::invalid_argument(fmt::format("Invalid temperature in {}: "
622 "expected a finite, positive value, got {} K",
623 cellName(dofIdx), temperatureLoc));
624 }
625 dofFluidState.setTemperature(temperatureLoc);
626
627 if (gas_active) {
628 dofFluidState.setSaturation(FluidSystem::gasPhaseIdx,
629 saturations[2]);
630 }
631 if (oil_active) {
632 dofFluidState.setSaturation(FluidSystem::oilPhaseIdx,
633 saturations[1]);
634 }
635 if (water_active) {
636 dofFluidState.setSaturation(FluidSystem::waterPhaseIdx,
637 saturations[0]);
638 }
639
641 // set phase pressures
643 const Scalar pressure = pressureData[dofIdx]; // oil pressure (or gas pressure for water-gas system or water pressure for single phase)
644
645 // TODO: zero capillary pressure for now
646 const std::array<Scalar, numPhases> pc = {0};
647 for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) {
648 if (!FluidSystem::phaseIsActive(phaseIdx))
649 continue;
650
651 if (Indices::oilEnabled)
652 dofFluidState.setPressure(phaseIdx, pressure + (pc[phaseIdx] - pc[oilPhaseIdx]));
653 else if (Indices::gasEnabled)
654 dofFluidState.setPressure(phaseIdx, pressure + (pc[phaseIdx] - pc[gasPhaseIdx]));
655 else if (Indices::waterEnabled)
656 // single (water) phase
657 dofFluidState.setPressure(phaseIdx, pressure);
658 }
659
660 if (has_xmf && has_ymf) {
661 const auto& xmfData = fp.get_double("XMF");
662 const auto& ymfData = fp.get_double("YMF");
663 for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) {
664 const std::size_t data_idx = compIdx * numDof + dofIdx;
665 const Scalar xmf = xmfData[data_idx];
666 const Scalar ymf = ymfData[data_idx];
667
668 dofFluidState.setMoleFraction(FluidSystem::oilPhaseIdx, compIdx, xmf);
669 dofFluidState.setMoleFraction(FluidSystem::gasPhaseIdx, compIdx, ymf);
670 }
671 }
672
673 if (has_zmf) {
674 zmf_initialization_ = true;
675 const auto& zmfData = fp.get_double("ZMF");
676 for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) {
677 const std::size_t data_idx = compIdx * numDof + dofIdx;
678 const Scalar zmf = zmfData[data_idx];
679 dofFluidState.setMoleFraction(compIdx, zmf);
680
681 if (gas_active) {
682 const auto ymf = (dofFluidState.saturation(FluidSystem::gasPhaseIdx) > 0.) ? zmf : Scalar{0};
683 dofFluidState.setMoleFraction(FluidSystem::gasPhaseIdx, compIdx, ymf);
684 }
685 if (oil_active) {
686 const auto xmf = (dofFluidState.saturation(FluidSystem::oilPhaseIdx) > 0.) ? zmf : Scalar{0};
687 dofFluidState.setMoleFraction(FluidSystem::oilPhaseIdx, compIdx, xmf);
688 }
689 }
690 }
691 }
692 OPM_END_PARALLEL_TRY_CATCH_LOG(deferredLogger,
693 "FlowProblemComp: invalid explicit initial state: ",
694 comm.rank() == 0, comm);
695
696 const auto globalNormalizedCellCount = comm.sum(normalizedCellCount);
697 const auto globalMaxDeviation = comm.max(maxNormalizationDeviation);
698 if (globalNormalizedCellCount > 0 && comm.rank() == 0) {
699 OpmLog::warning(
700 "EXPLICIT_SATURATION_NORMALIZATION",
701 fmt::format("Normalized explicit saturations in {} cells; "
702 "largest absolute deviation of their input sum from 1: {:.6g}",
703 globalNormalizedCellCount,
704 globalMaxDeviation));
705 }
706
707 if (water_active && Parameters::Get<Parameters::NormalizeExplicitSwat>()) {
709 }
710 }
711
712 // The explicit saturations describe the cell before the hydrocarbon
713 // flash. SGAS and the oil saturation So = 1 - SWAT - SGAS each hold
714 // their input composition at the vapour or liquid EOS root, whatever the
715 // stable state, and with volume shift applied. After flashing the total,
716 // water and the flashed phases are rescaled to fill the pore volume:
717 //
718 // Sw = SWAT / (SWAT + n * Vm_flash), n = So / Vm_liquid + SGAS / Vm_vapour
720 {
722 const auto eos_type = getEosType();
723 // Flash as the model does, so that this converges wherever the model's flash does.
724 const Scalar flash_tolerance = Parameters::Get<Parameters::FlashTolerance<Scalar>>();
725 const int flash_verbosity = Parameters::Get<Parameters::FlashVerbosity>();
726 const auto flash_method
727 = ptFlashMethodFromString(Parameters::Get<Parameters::FlashTwoPhaseMethod>());
728 bool zmf_oil_and_gas = false;
729
730 // A failing flash on one rank must not leave the others in the reduction below.
731 const auto& comm = this->simulator().vanguard().grid().comm();
733 for (auto& fs : initialFluidStates_) {
734 const Scalar sw_in = fs.saturation(FluidSystem::waterPhaseIdx);
735 if (sw_in <= 0.0 || sw_in >= 1.0) {
736 continue;
737 }
738 if (!zmf_oil_and_gas && zmf_initialization_
739 && FluidSystem::phaseIsActive(FluidSystem::oilPhaseIdx)
740 && FluidSystem::phaseIsActive(FluidSystem::gasPhaseIdx)
741 && fs.saturation(FluidSystem::oilPhaseIdx) > 0.0
742 && fs.saturation(FluidSystem::gasPhaseIdx) > 0.0) {
743 zmf_oil_and_gas = true;
744 }
745
746 typename FluidSystem::template ParameterCache<Scalar> param_cache(eos_type);
747
748 // Hydrocarbon moles per unit pore volume as given. Each given phase holds its
749 // input composition, ZMF or XMF/YMF, before the flash, so this is the molar
750 // volume of that composition at the phase's own root.
751 Dune::FieldVector<Scalar, numComponents> moles(0.0);
752 Scalar total_moles = 0.0;
753 for (const unsigned phaseIdx : {FluidSystem::oilPhaseIdx, FluidSystem::gasPhaseIdx}) {
754 const Scalar saturation = fs.saturation(phaseIdx);
755 if (!FluidSystem::phaseIsActive(phaseIdx) || saturation <= 0.0) {
756 continue;
757 }
758 param_cache.updatePhase(fs, phaseIdx);
759 const Scalar phase_moles = saturation / param_cache.correctedMolarVolume(phaseIdx);
760 for (unsigned c = 0; c < numComponents; ++c) {
761 moles[c] += phase_moles * fs.moleFraction(phaseIdx, c);
762 }
763 total_moles += phase_moles;
764 }
765 if (total_moles <= 0.0) {
766 continue;
767 }
768
769 // Overall composition of the given phases, which is ZMF itself with ZMF input.
770 // Floor and renormalize it as the model does before its flash.
771 Dune::FieldVector<Scalar, numComponents> z(0.0);
772 Scalar sum_z = 0.0;
773 for (unsigned c = 0; c < numComponents; ++c) {
774 z[c] = std::max(moles[c] / total_moles, IntensiveQuantities::compositionFloor);
775 sum_z += z[c];
776 }
777
778 InitialFluidState flash_fs;
779 flash_fs.setTemperature(fs.temperature(0));
780 flash_fs.setPressure(FluidSystem::oilPhaseIdx, fs.pressure(FluidSystem::oilPhaseIdx));
781 flash_fs.setPressure(FluidSystem::gasPhaseIdx, fs.pressure(FluidSystem::gasPhaseIdx));
782 for (unsigned c = 0; c < numComponents; ++c) {
783 flash_fs.setMoleFraction(c, z[c] / sum_z);
784 flash_fs.setKvalue(c, flash_fs.wilsonK_(c));
785 }
786 flash_fs.setLvalue(-1.0);
787 FlashSolver::flash_solve_scalar(
788 flash_fs, flash_method, flash_tolerance, eos_type, flash_verbosity);
789
790 // A single-phase result has L = 0 or 1, so only one phase counts.
791 const Scalar L = flash_fs.L();
792 Scalar vm_flash = 0.0;
793 if (L > 0.0) {
794 param_cache.updatePhase(flash_fs, FluidSystem::oilPhaseIdx);
795 vm_flash += L * param_cache.correctedMolarVolume(FluidSystem::oilPhaseIdx);
796 }
797 if (L < 1.0) {
798 param_cache.updatePhase(flash_fs, FluidSystem::gasPhaseIdx);
799 vm_flash += (1.0 - L) * param_cache.correctedMolarVolume(FluidSystem::gasPhaseIdx);
800 }
801
802 const Scalar sw = sw_in / (sw_in + total_moles * vm_flash);
803 const Scalar hc_scale = (1.0 - sw) / (1.0 - sw_in);
804
805 fs.setSaturation(FluidSystem::waterPhaseIdx, sw);
806 if (FluidSystem::phaseIsActive(FluidSystem::gasPhaseIdx)) {
807 fs.setSaturation(FluidSystem::gasPhaseIdx,
808 fs.saturation(FluidSystem::gasPhaseIdx) * hc_scale);
809 }
810 if (FluidSystem::phaseIsActive(FluidSystem::oilPhaseIdx)) {
811 fs.setSaturation(FluidSystem::oilPhaseIdx,
812 fs.saturation(FluidSystem::oilPhaseIdx) * hc_scale);
813 }
814 }
815 OPM_END_PARALLEL_TRY_CATCH("FlowProblemComp::normalizeExplicitSwat_() failed: ", comm);
816
817 if (comm.max(zmf_oil_and_gas ? 1 : 0) > 0 && comm.rank() == 0) {
818 OpmLog::warning("ZMF is given with both oil and gas saturations; both phases take "
819 "the ZMF composition when SWAT is normalized.");
820 }
821 }
822
823private:
824
827 void reportTwoPhaseEquilCells_()
828 {
829 if (!twoPhaseCheckRegions_.has_value()) {
830 return;
831 }
832
833 const auto liquidFraction = [this](const std::size_t cell) {
834 const auto& intQuants
835 = this->model().intensiveQuantities(static_cast<unsigned>(cell), /*timeIdx=*/0);
836 return getValue(intQuants.fluidState().L());
837 };
838 const auto& vanguard = this->simulator().vanguard();
839 EQUIL::Comp::reportTwoPhaseCells(*twoPhaseCheckRegions_,
840 vanguard.cellCenterDepths(),
841 liquidFraction,
842 vanguard.eclState().getInitConfig().getEquil().size(),
843 vanguard.gridView().comm());
844 twoPhaseCheckRegions_.reset();
845 }
846
847 void handleSolventBC(const BCState::BCFace& /* bc */, RateVector& /* rate */) const override
848 {
849 throw std::logic_error("solvent is disabled for compositional modeling and you're trying to add solvent to BC");
850 }
851
852 void handlePolymerBC(const BCState::BCFace& /* bc */, RateVector& /* rate */) const override
853 {
854 throw std::logic_error("polymer is disabled for compositional modeling and you're trying to add polymer to BC");
855 }
856
857 void handleMicrBC(const BCState::BCFace& /* bc */, RateVector& /* rate */) const override
858 {
859 throw std::logic_error("MICP is disabled for compositional modeling and you're trying to add microbes to BC");
860 }
861
862 void handleOxygBC(const BCState::BCFace& /* bc */, RateVector& /* rate */) const override
863 {
864 throw std::logic_error("MICP is disabled for compositional modeling and you're trying to add oxygen to BC");
865 }
866
867 void handleUreaBC(const BCState::BCFace& /* bc */, RateVector& /* rate */) const override
868 {
869 throw std::logic_error("MICP is disabled for compositional modeling and you're trying to add urea to BC");
870 }
871
872 std::vector<InitialFluidState> initialFluidStates_;
875 std::optional<std::vector<int>> twoPhaseCheckRegions_;
876
877 bool zmf_initialization_ {false};
878
879 bool enableEclOutput_{false};
880 std::unique_ptr<EclWriterType> eclWriter_;
881};
882
883} // namespace Opm
884
885#endif // OPM_FLOW_PROBLEM_COMP_HPP
#define OPM_END_PARALLEL_TRY_CATCH_LOG(obptc_logger, obptc_prefix, obptc_output, comm)
Catch exception, log, and throw in a parallel try-catch clause.
Definition: DeferredLoggingErrorHelpers.hpp:207
#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
Hydrostatic equilibration for the compositional simulator (EQUIL + ZMFVD/COMPVD).
Definition: DeferredLogger.hpp:57
Computes the initial state of a compositional model from hydrostatic equilibrium (the EQUIL and ZMFVD...
Definition: InitStateEquilComp.hpp:240
std::vector< int > & twoPhaseCheckRegions()
Definition: InitStateEquilComp.hpp:369
const std::vector< Scalar > & referencePressures() const
Definition: InitStateEquilComp.hpp:362
std::vector< FluidState > & fluidStates()
Definition: InitStateEquilComp.hpp:353
Collects necessary output values and pass it to opm-common's ECL output.
Definition: EclWriter.hpp:124
static void registerParameters()
Definition: EclWriter.hpp:152
This problem simulates an input file given in the data format used by the commercial ECLiPSE simulato...
Definition: FlowProblemComp.hpp:85
void writeOutput(bool verbose) override
Write the requested quantities of the current solution into the output files.
Definition: FlowProblemComp.hpp:283
const std::vector< InitialFluidState > & initialFluidStates() const
Definition: FlowProblemComp.hpp:412
void finishInit()
Called by the Opm::Simulator in order to initialize the problem.
Definition: FlowProblemComp.hpp:172
Opm::CompositionalConfig::EOSType getEosType() const
Definition: FlowProblemComp.hpp:152
FlowProblemComp(Simulator &simulator)
Definition: FlowProblemComp.hpp:162
void writeReports(const SimulatorTimer &timer)
Definition: FlowProblemComp.hpp:273
void readExplicitInitialCondition_() override
Definition: FlowProblemComp.hpp:513
void readExplicitInitialConditionCompositional_()
Definition: FlowProblemComp.hpp:518
void endTimeStep() override
Called by the simulator after each time integration.
Definition: FlowProblemComp.hpp:256
const EclWriterType & eclWriter() const
Definition: FlowProblemComp.hpp:415
std::vector< InitialFluidState > & initialFluidStates()
Definition: FlowProblemComp.hpp:409
void readEclRestartSolution_()
Definition: FlowProblemComp.hpp:508
void normalizeExplicitSwat_()
Definition: FlowProblemComp.hpp:719
void finalizeOutput()
Definition: FlowProblemComp.hpp:427
const InitialFluidState & initialFluidState(unsigned globalDofIdx) const
Definition: FlowProblemComp.hpp:406
void boundary(BoundaryRateVector &values, const Context &context, unsigned spaceIdx, unsigned) const
Evaluate the boundary conditions for a boundary segment.
Definition: FlowProblemComp.hpp:308
void initial(PrimaryVariables &values, const Context &context, unsigned spaceIdx, unsigned timeIdx) const
Evaluate the initial value for a control volume.
Definition: FlowProblemComp.hpp:331
void readEquilInitialCondition_() override
Definition: FlowProblemComp.hpp:447
void serializeOp(Serializer &serializer)
Definition: FlowProblemComp.hpp:435
void updateExplicitQuantities_(int, int, bool) override
Definition: FlowProblemComp.hpp:442
void addToSourceDense(RateVector &, unsigned, unsigned) const override
Definition: FlowProblemComp.hpp:401
void setSubStepReport(const SimulatorReportSingle &report)
Definition: FlowProblemComp.hpp:421
void initialSolutionApplied() override
Callback used by the model to indicate that the initial solution has been determined for all degrees ...
Definition: FlowProblemComp.hpp:243
static void registerParameters()
Registers all available parameters for the problem and the model.
Definition: FlowProblemComp.hpp:124
EclWriterType & eclWriter()
Definition: FlowProblemComp.hpp:418
void setSimulationReport(const SimulatorReport &report)
Definition: FlowProblemComp.hpp:424
This problem simulates an input file given in the data format used by the commercial ECLiPSE simulato...
Definition: FlowProblem.hpp:101
virtual bool episodeWillBeOver() const
Definition: FlowProblem.hpp:2196
GetPropType< TypeTag, Properties::Evaluation > Evaluation
Definition: FlowProblem.hpp:166
bool nonTrivialBoundaryConditions() const
Definition: FlowProblem.hpp:1166
virtual void writeOutput(bool verbose)
Write the requested quantities of the current solution into the output files.
Definition: FlowProblem.hpp:544
unsigned pvtRegionIndex(const Context &context, unsigned spaceIdx, unsigned timeIdx) const
Returns the index of the relevant region for thermodynmic properties.
Definition: FlowProblem.hpp:962
Scalar porosity(const Context &context, unsigned spaceIdx, unsigned timeIdx) const
Definition: FlowProblem.hpp:742
GetPropType< TypeTag, Properties::Vanguard > Vanguard
Definition: FlowProblem.hpp:114
@ numComponents
Definition: FlowProblem.hpp:124
GetPropType< TypeTag, Properties::Scalar > Scalar
Definition: FlowProblem.hpp:108
GetPropType< TypeTag, Properties::RateVector > RateVector
Definition: FlowProblem.hpp:155
FlowThresholdPressure< TypeTag > thresholdPressures_
Definition: FlowProblem.hpp:2129
@ waterPhaseIdx
Definition: FlowProblem.hpp:146
GetPropType< TypeTag, Properties::Indices > Indices
Definition: FlowProblem.hpp:115
const EclMaterialLawManager & materialLawManager() const
Returns the ECL material law manager.
Definition: FlowProblem.hpp:914
GetPropType< TypeTag, Properties::Simulator > Simulator
Definition: FlowProblem.hpp:156
@ dimWorld
Definition: FlowProblem.hpp:119
@ gasPhaseIdx
Definition: FlowProblem.hpp:144
@ numPhases
Definition: FlowProblem.hpp:123
void finishTransmissibilities_()
Definition: FlowProblem.hpp:1428
@ dim
Definition: FlowProblem.hpp:118
GetPropType< TypeTag, Properties::IntensiveQuantities > IntensiveQuantities
Definition: FlowProblem.hpp:167
GetPropType< TypeTag, Properties::GridView > GridView
Definition: FlowProblem.hpp:109
bool prepareTransmissibilityOutput_(EclWriterType &eclWriter, const bool enableEclOutput)
Definition: FlowProblem.hpp:1437
void initializeSimulatorTime_()
Definition: FlowProblem.hpp:1496
static void registerParameters()
Registers all available parameters for the problem and the model.
Definition: FlowProblem.hpp:200
void updatePffDofData_()
Definition: FlowProblem.hpp:1890
@ oilPhaseIdx
Definition: FlowProblem.hpp:145
GetPropType< TypeTag, Properties::PrimaryVariables > PrimaryVariables
Definition: FlowProblem.hpp:154
virtual void endTimeStep()
Called by the simulator after each time integration.
Definition: FlowProblem.hpp:462
virtual void readInitialCondition_()
Definition: FlowProblem.hpp:1798
virtual void initialSolutionApplied()
Callback used by the model to indicate that the initial solution has been determined for all degrees ...
Definition: FlowProblem.hpp:1080
GetPropType< TypeTag, Properties::FluidSystem > FluidSystem
Definition: FlowProblem.hpp:111
GetPropType< TypeTag, Properties::MaterialLaw > MaterialLaw
Definition: FlowProblem.hpp:164
void initializeModelProperties_()
Definition: FlowProblem.hpp:1520
Definition: FlowGenericProblem.hpp:52
Definition: SimulatorTimer.hpp:38
void reportTwoPhaseCells(const std::vector< int > &checkRegions, const std::vector< Scalar > &cellCenterDepth, const LiquidFraction &liquidFraction, const std::size_t numRegions, const Parallel::Communication &comm)
Definition: InitStateEquilComp.hpp:1356
Definition: blackoilnewtonmethodparams.hpp:31
Definition: blackoilbioeffectsmodules.hh:45
typename Properties::Detail::GetPropImpl< TypeTag, Property >::type::type GetPropType
get the type alias defined in the property (equivalent to old macro GET_PROP_TYPE(....
Definition: propertysystem.hh:233
ExplicitInitialSaturations completeExplicitSaturations(const std::array< std::optional< double >, 3 > &supplied, const std::array< bool, 3 > &active)
Declares the parameters for the compositional multi-phase model based on flash calculations.
Definition: ExplicitInitialSaturations.hpp:29
double normalizationDeviation
Absolute input closure error for a nontrivial normalization.
Definition: ExplicitInitialSaturations.hpp:32
std::array< double, 3 > saturations
Definition: ExplicitInitialSaturations.hpp:30
Definition: FlowProblemComp.hpp:71
static constexpr bool value
Definition: FlowProblemComp.hpp:71
Definition: SimulatorReport.hpp:202
A struct for returning timing data from a simulator to its caller.
Definition: SimulatorReport.hpp:34