// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- // vi: set et ts=4 sw=4 sts=4: /* This file is part of the Open Porous Media project (OPM). OPM is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. OPM is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OPM. If not, see . Consult the COPYING file in the top-level source directory of this module for the precise wording of the license and the list of copyright holders. */ #include #include #include #include #include #include #include #include #include #include #if HAVE_DUNE_FEM #include #include #include #endif #include #include #include #include namespace Opm { template EclGenericProblem:: EclGenericProblem(const EclipseState& eclState, const Schedule& schedule, const GridView& gridView) : eclState_(eclState) , schedule_(schedule) , gridView_(gridView) { } template std::string EclGenericProblem:: helpPreamble(int, const char **argv) { std::string desc = EclGenericProblem::briefDescription(); if (!desc.empty()) desc = desc + "\n"; return "Usage: "+std::string(argv[0]) + " [OPTIONS] [ECL_DECK_FILENAME]\n" + desc; } template std::string EclGenericProblem:: briefDescription() { if (briefDescription_.empty()) return "The Ecl-deck Black-Oil reservoir Simulator (ebos); a hydrocarbon " "reservoir simulation program that processes ECL-formatted input " "files that is part of the Open Porous Media project " "(https://opm-project.org).\n" "\n" "THE GOAL OF THE `ebos` SIMULATOR IS TO CATER FOR THE NEEDS OF " "DEVELOPMENT AND RESEARCH. No guarantees are made for production use!"; else return briefDescription_; } template void EclGenericProblem:: readRockParameters_(const std::vector& cellCenterDepths) { const auto& rock_config = eclState_.getSimulationConfig().rock_config(); // read the rock compressibility parameters { const auto& comp = rock_config.comp(); rockParams_.clear(); for (const auto& c : comp) rockParams_.push_back( { c.pref, c.compressibility } ); } // read the parameters for water-induced rock compaction readRockCompactionParameters_(); unsigned numElem = gridView_.size(0); if (eclState_.fieldProps().has_int(rock_config.rocknum_property())) { rockTableIdx_.resize(numElem); const auto& num = eclState_.fieldProps().get_int(rock_config.rocknum_property()); for (size_t elemIdx = 0; elemIdx < numElem; ++ elemIdx) { rockTableIdx_[elemIdx] = num[elemIdx] - 1; } } // Store overburden pressure pr element const auto& overburdTables = eclState_.getTableManager().getOverburdTables(); if (!overburdTables.empty()) { overburdenPressure_.resize(numElem,0.0); size_t numRocktabTables = rock_config.num_rock_tables(); if (overburdTables.size() != numRocktabTables) throw std::runtime_error(std::to_string(numRocktabTables) +" OVERBURD tables is expected, but " + std::to_string(overburdTables.size()) +" is provided"); std::vector> overburdenTables(numRocktabTables); for (size_t regionIdx = 0; regionIdx < numRocktabTables; ++regionIdx) { const OverburdTable& overburdTable = overburdTables.template getTable(regionIdx); overburdenTables[regionIdx].setXYContainers(overburdTable.getDepthColumn(),overburdTable.getOverburdenPressureColumn()); } for (size_t elemIdx = 0; elemIdx < numElem; ++ elemIdx) { unsigned tableIdx = 0; if (!rockTableIdx_.empty()) { tableIdx = rockTableIdx_[elemIdx]; } overburdenPressure_[elemIdx] = overburdenTables[tableIdx].eval(cellCenterDepths[elemIdx], /*extrapolation=*/true); } } } template void EclGenericProblem:: readRockCompactionParameters_() { const auto& rock_config = eclState_.getSimulationConfig().rock_config(); if (!rock_config.active()) return; // deck does not enable rock compaction unsigned numElem = gridView_.size(0); switch (rock_config.hysteresis_mode()) { case RockConfig::Hysteresis::REVERS: break; case RockConfig::Hysteresis::IRREVERS: // interpolate the porv volume multiplier using the minimum pressure in the cell // i.e. don't allow re-inflation. minOilPressure_.resize(numElem, 1e99); break; default: throw std::runtime_error("Not support ROCKOMP hysteresis option "); } size_t numRocktabTables = rock_config.num_rock_tables(); bool waterCompaction = rock_config.water_compaction(); if (!waterCompaction) { const auto& rocktabTables = eclState_.getTableManager().getRocktabTables(); if (rocktabTables.size() != numRocktabTables) throw std::runtime_error("ROCKCOMP is activated." + std::to_string(numRocktabTables) +" ROCKTAB tables is expected, but " + std::to_string(rocktabTables.size()) +" is provided"); rockCompPoroMult_.resize(numRocktabTables); rockCompTransMult_.resize(numRocktabTables); for (size_t regionIdx = 0; regionIdx < numRocktabTables; ++regionIdx) { const auto& rocktabTable = rocktabTables.template getTable(regionIdx); const auto& pressureColumn = rocktabTable.getPressureColumn(); const auto& poroColumn = rocktabTable.getPoreVolumeMultiplierColumn(); const auto& transColumn = rocktabTable.getTransmissibilityMultiplierColumn(); rockCompPoroMult_[regionIdx].setXYContainers(pressureColumn, poroColumn); rockCompTransMult_[regionIdx].setXYContainers(pressureColumn, transColumn); } } else { const auto& rock2dTables = eclState_.getTableManager().getRock2dTables(); const auto& rock2dtrTables = eclState_.getTableManager().getRock2dtrTables(); const auto& rockwnodTables = eclState_.getTableManager().getRockwnodTables(); maxWaterSaturation_.resize(numElem, 0.0); if (rock2dTables.size() != numRocktabTables) throw std::runtime_error("Water compation option is selected in ROCKCOMP." + std::to_string(numRocktabTables) +" ROCK2D tables is expected, but " + std::to_string(rock2dTables.size()) +" is provided"); if (rockwnodTables.size() != numRocktabTables) throw std::runtime_error("Water compation option is selected in ROCKCOMP." + std::to_string(numRocktabTables) +" ROCKWNOD tables is expected, but " + std::to_string(rockwnodTables.size()) +" is provided"); //TODO check size match rockCompPoroMultWc_.resize(numRocktabTables, TabulatedTwoDFunction(TabulatedTwoDFunction::InterpolationPolicy::Vertical)); for (size_t regionIdx = 0; regionIdx < numRocktabTables; ++regionIdx) { const RockwnodTable& rockwnodTable = rockwnodTables.template getTable(regionIdx); const auto& rock2dTable = rock2dTables[regionIdx]; if (rockwnodTable.getSaturationColumn().size() != rock2dTable.sizeMultValues()) throw std::runtime_error("Number of entries in ROCKWNOD and ROCK2D needs to match."); for (size_t xIdx = 0; xIdx < rock2dTable.size(); ++xIdx) { rockCompPoroMultWc_[regionIdx].appendXPos(rock2dTable.getPressureValue(xIdx)); for (size_t yIdx = 0; yIdx < rockwnodTable.getSaturationColumn().size(); ++yIdx) rockCompPoroMultWc_[regionIdx].appendSamplePoint(xIdx, rockwnodTable.getSaturationColumn()[yIdx], rock2dTable.getPvmultValue(xIdx, yIdx)); } } if (!rock2dtrTables.empty()) { rockCompTransMultWc_.resize(numRocktabTables, TabulatedTwoDFunction(TabulatedTwoDFunction::InterpolationPolicy::Vertical)); for (size_t regionIdx = 0; regionIdx < numRocktabTables; ++regionIdx) { const RockwnodTable& rockwnodTable = rockwnodTables.template getTable(regionIdx); const auto& rock2dtrTable = rock2dtrTables[regionIdx]; if (rockwnodTable.getSaturationColumn().size() != rock2dtrTable.sizeMultValues()) throw std::runtime_error("Number of entries in ROCKWNOD and ROCK2DTR needs to match."); for (size_t xIdx = 0; xIdx < rock2dtrTable.size(); ++xIdx) { rockCompTransMultWc_[regionIdx].appendXPos(rock2dtrTable.getPressureValue(xIdx)); for (size_t yIdx = 0; yIdx < rockwnodTable.getSaturationColumn().size(); ++yIdx) rockCompTransMultWc_[regionIdx].appendSamplePoint(xIdx, rockwnodTable.getSaturationColumn()[yIdx], rock2dtrTable.getTransMultValue(xIdx, yIdx)); } } } } } template template void EclGenericProblem:: updateNum(const std::string& name, std::vector& numbers) { if (!eclState_.fieldProps().has_int(name)) return; const auto& numData = eclState_.fieldProps().get_int(name); unsigned numElems = gridView_.size(/*codim=*/0); numbers.resize(numElems); for (unsigned elemIdx = 0; elemIdx < numElems; ++elemIdx) { numbers[elemIdx] = static_cast(numData[elemIdx]) - 1; } } template void EclGenericProblem:: updatePvtnum_() { updateNum("PVTNUM", pvtnum_); } template void EclGenericProblem:: updateSatnum_() { updateNum("SATNUM", satnum_); } template void EclGenericProblem:: updateMiscnum_() { updateNum("MISCNUM", miscnum_); } template void EclGenericProblem:: updatePlmixnum_() { updateNum("PLMIXNUM", plmixnum_); } template bool EclGenericProblem:: vapparsActive(int episodeIdx) const { const auto& oilVaporizationControl = schedule_[episodeIdx].oilvap(); return (oilVaporizationControl.getType() == OilVaporizationProperties::OilVaporization::VAPPARS); } template bool EclGenericProblem:: drsdtActive_(int episodeIdx) const { const auto& oilVaporizationControl = schedule_[episodeIdx].oilvap(); const bool bothOilGasActive = FluidSystem::phaseIsActive(FluidSystem::oilPhaseIdx) && FluidSystem::phaseIsActive(FluidSystem::gasPhaseIdx); return (oilVaporizationControl.drsdtActive() && bothOilGasActive); } template bool EclGenericProblem:: drvdtActive_(int episodeIdx) const { const auto& oilVaporizationControl = schedule_[episodeIdx].oilvap(); const bool bothOilGasActive = FluidSystem::phaseIsActive(FluidSystem::oilPhaseIdx) && FluidSystem::phaseIsActive(FluidSystem::gasPhaseIdx); return (oilVaporizationControl.drvdtActive() && bothOilGasActive); } template bool EclGenericProblem:: drsdtConvective_(int episodeIdx) const { const auto& oilVaporizationControl = schedule_[episodeIdx].oilvap(); const bool bothOilGasActive = FluidSystem::phaseIsActive(FluidSystem::oilPhaseIdx) && FluidSystem::phaseIsActive(FluidSystem::gasPhaseIdx); return (oilVaporizationControl.drsdtConvective() && bothOilGasActive); } template bool EclGenericProblem:: beginEpisode_(bool enableExperiments, int episodeIdx) { if (enableExperiments && gridView_.comm().rank() == 0 && episodeIdx >= 0) { // print some useful information in experimental mode. (the production // simulator does this externally.) std::ostringstream ss; boost::posix_time::time_facet* facet = new boost::posix_time::time_facet("%d-%b-%Y"); boost::posix_time::ptime curDateTime = boost::posix_time::from_time_t(schedule_.simTime(episodeIdx)); ss.imbue(std::locale(std::locale::classic(), facet)); ss << "Report step " << episodeIdx + 1 << "/" << schedule_.size() - 1 << " at day " << schedule_.seconds(episodeIdx)/(24*3600) << "/" << schedule_.seconds(schedule_.size() - 1)/(24*3600) << ", date = " << curDateTime.date() << "\n "; OpmLog::info(ss.str()); } const auto& events = schedule_[episodeIdx].events(); // react to TUNING changes if (episodeIdx > 0 && enableTuning_ && events.hasEvent(ScheduleEvents::TUNING_CHANGE)) { const auto& sched_state = schedule_[episodeIdx]; const auto& tuning = sched_state.tuning(); initialTimeStepSize_ = sched_state.max_next_tstep(); maxTimeStepAfterWellEvent_ = tuning.TMAXWC; maxTimeStepSize_ = tuning.TSMAXZ; restartShrinkFactor_ = 1./tuning.TSFCNV; minTimeStepSize_ = tuning.TSMINZ; return true; } return false; } template void EclGenericProblem:: beginTimeStep_(bool enableExperiments, int episodeIdx, int timeStepIndex, Scalar startTime, Scalar time, Scalar timeStepSize, Scalar endTime) { if (enableExperiments && gridView_.comm().rank() == 0 && episodeIdx >= 0) { std::ostringstream ss; boost::posix_time::time_facet* facet = new boost::posix_time::time_facet("%d-%b-%Y"); boost::posix_time::ptime date = boost::posix_time::from_time_t(startTime) + boost::posix_time::milliseconds(static_cast(time / prefix::milli)); ss.imbue(std::locale(std::locale::classic(), facet)); ss <<"\nTime step " << timeStepIndex << ", stepsize " << unit::convert::to(timeStepSize, unit::day) << " days," << " at day " << (double)unit::convert::to(time, unit::day) << "/" << (double)unit::convert::to(endTime, unit::day) << ", date = " << date; OpmLog::info(ss.str()); } // update explicit quantities between timesteps. const auto& oilVaporizationControl = schedule_[episodeIdx].oilvap(); if (drsdtActive_(episodeIdx)) // DRSDT is enabled for (size_t pvtRegionIdx = 0; pvtRegionIdx < maxDRs_.size(); ++pvtRegionIdx) maxDRs_[pvtRegionIdx] = oilVaporizationControl.getMaxDRSDT(pvtRegionIdx)*timeStepSize; if (drvdtActive_(episodeIdx)) // DRVDT is enabled for (size_t pvtRegionIdx = 0; pvtRegionIdx < maxDRv_.size(); ++pvtRegionIdx) maxDRv_[pvtRegionIdx] = oilVaporizationControl.getMaxDRVDT(pvtRegionIdx)*timeStepSize; } template void EclGenericProblem:: checkDeckCompatibility_(const Deck& deck, bool enableApiTracking, bool enableSolvent, bool enablePolymer, bool enableExtbo, bool enableEnergy, int numPhases, bool indicesGasEnabled, bool indicesOilEnabled, bool indicesWaterEnabled, bool enableMICP) const { if (enableApiTracking) throw std::logic_error("API tracking is not yet implemented but requested at compile time."); if (!enableApiTracking && deck.hasKeyword("API")) throw std::logic_error("The simulator is build with API tracking disabled, but API tracking is requested by the deck."); if (enableSolvent && !deck.hasKeyword("SOLVENT")) throw std::runtime_error("The simulator requires the solvent option to be enabled, but the deck does not."); else if (!enableSolvent && deck.hasKeyword("SOLVENT")) throw std::runtime_error("The deck enables the solvent option, but the simulator is compiled without it."); if (enablePolymer && !deck.hasKeyword("POLYMER")) throw std::runtime_error("The simulator requires the polymer option to be enabled, but the deck does not."); else if (!enablePolymer && deck.hasKeyword("POLYMER")) throw std::runtime_error("The deck enables the polymer option, but the simulator is compiled without it."); if (enableMICP && !deck.hasKeyword("MICP")) throw std::runtime_error("The simulator requires the MICP option to be enabled, but the deck does not."); else if (!enableMICP && deck.hasKeyword("MICP")) throw std::runtime_error("The deck enables the MICP option, but the simulator is compiled without it."); if (enableExtbo && !deck.hasKeyword("PVTSOL")) throw std::runtime_error("The simulator requires the extendedBO option to be enabled, but the deck does not."); else if (!enableExtbo && deck.hasKeyword("PVTSOL")) throw std::runtime_error("The deck enables the extendedBO option, but the simulator is compiled without it."); if (deck.hasKeyword("TEMP") && deck.hasKeyword("THERMAL")) throw std::runtime_error("The deck enables both, the TEMP and the THERMAL options, but they are mutually exclusive."); bool deckEnergyEnabled = (deck.hasKeyword("TEMP") || deck.hasKeyword("THERMAL")); if (enableEnergy && !deckEnergyEnabled) throw std::runtime_error("The simulator requires the TEMP or the THERMAL option to be enabled, but the deck activates neither."); else if (!enableEnergy && deckEnergyEnabled) throw std::runtime_error("The deck enables the TEMP or the THERMAL option, but the simulator is not compiled to support either."); if (deckEnergyEnabled && deck.hasKeyword("TEMP")) std::cerr << "WARNING: The deck requests the TEMP option, i.e., treating energy " << "conservation as a post processing step. This is currently unsupported, " << "i.e., energy conservation is always handled fully implicitly." << std::endl; int numDeckPhases = FluidSystem::numActivePhases(); if (numDeckPhases < numPhases) std::cerr << "WARNING: The number of active phases specified by the deck (" << numDeckPhases << ") is smaller than the number of compiled-in phases (" << numPhases << "). This usually results in a significant " << "performance degradation compared to using a specialized simulator." << std::endl; else if (numDeckPhases < numPhases) throw std::runtime_error("The deck enables "+std::to_string(numDeckPhases)+" phases " "while this simulator can only handle "+ std::to_string(numPhases)+"."); // make sure that the correct phases are active if (FluidSystem::phaseIsActive(FluidSystem::oilPhaseIdx) && !indicesOilEnabled) throw std::runtime_error("The deck enables oil, but this simulator cannot handle it."); if (FluidSystem::phaseIsActive(FluidSystem::gasPhaseIdx) && !indicesGasEnabled) throw std::runtime_error("The deck enables gas, but this simulator cannot handle it."); if (FluidSystem::phaseIsActive(FluidSystem::waterPhaseIdx) && !indicesWaterEnabled) throw std::runtime_error("The deck enables water, but this simulator cannot handle it."); // the opposite cases should be fine (albeit a bit slower than what's possible) } template void EclGenericProblem:: initFluidSystem_() { FluidSystem::initFromState(eclState_, schedule_); } template void EclGenericProblem:: readBlackoilExtentionsInitialConditions_(size_t numDof, bool enableSolvent, bool enablePolymer, bool enablePolymerMolarWeight, bool enableMICP) { if (enableSolvent) { if (eclState_.fieldProps().has_double("SSOL")) solventSaturation_ = eclState_.fieldProps().get_double("SSOL"); else solventSaturation_.resize(numDof, 0.0); } if (enablePolymer) { if (eclState_.fieldProps().has_double("SPOLY")) polymerConcentration_ = eclState_.fieldProps().get_double("SPOLY"); else polymerConcentration_.resize(numDof, 0.0); } if (enablePolymerMolarWeight) { if (eclState_.fieldProps().has_double("SPOLYMW")) polymerMoleWeight_ = eclState_.fieldProps().get_double("SPOLYMW"); else polymerMoleWeight_.resize(numDof, 0.0); } if (enableMICP) { if (eclState_.fieldProps().has_double("SMICR")) microbialConcentration_ = eclState_.fieldProps().get_double("SMICR"); else microbialConcentration_.resize(numDof, 0.0); if (eclState_.fieldProps().has_double("SOXYG")) oxygenConcentration_ = eclState_.fieldProps().get_double("SOXYG"); else oxygenConcentration_.resize(numDof, 0.0); if (eclState_.fieldProps().has_double("SUREA")) ureaConcentration_ = eclState_.fieldProps().get_double("SUREA"); else ureaConcentration_.resize(numDof, 0.0); if (eclState_.fieldProps().has_double("SBIOF")) biofilmConcentration_ = eclState_.fieldProps().get_double("SBIOF"); else biofilmConcentration_.resize(numDof, 0.0); if (eclState_.fieldProps().has_double("SCALC")) calciteConcentration_ = eclState_.fieldProps().get_double("SCALC"); else calciteConcentration_.resize(numDof, 0.0); } } template Scalar EclGenericProblem:: maxWaterSaturation(unsigned globalDofIdx) const { if (maxWaterSaturation_.empty()) return 0.0; return maxWaterSaturation_[globalDofIdx]; } template Scalar EclGenericProblem:: minOilPressure(unsigned globalDofIdx) const { if (minOilPressure_.empty()) return 0.0; return minOilPressure_[globalDofIdx]; } template Scalar EclGenericProblem:: overburdenPressure(unsigned elementIdx) const { if (overburdenPressure_.empty()) return 0.0; return overburdenPressure_[elementIdx]; } template Scalar EclGenericProblem:: solventSaturation(unsigned elemIdx) const { if (solventSaturation_.empty()) return 0; return solventSaturation_[elemIdx]; } template Scalar EclGenericProblem:: polymerConcentration(unsigned elemIdx) const { if (polymerConcentration_.empty()) return 0; return polymerConcentration_[elemIdx]; } template Scalar EclGenericProblem:: polymerMolecularWeight(const unsigned elemIdx) const { if (polymerMoleWeight_.empty()) return 0.0; return polymerMoleWeight_[elemIdx]; } template Scalar EclGenericProblem:: microbialConcentration(unsigned elemIdx) const { if (microbialConcentration_.empty()) return 0; return microbialConcentration_[elemIdx]; } template Scalar EclGenericProblem:: oxygenConcentration(unsigned elemIdx) const { if (oxygenConcentration_.empty()) return 0; return oxygenConcentration_[elemIdx]; } template Scalar EclGenericProblem:: ureaConcentration(unsigned elemIdx) const { if (ureaConcentration_.empty()) return 0; return ureaConcentration_[elemIdx]; } template Scalar EclGenericProblem:: biofilmConcentration(unsigned elemIdx) const { if (biofilmConcentration_.empty()) return 0; return biofilmConcentration_[elemIdx]; } template Scalar EclGenericProblem:: calciteConcentration(unsigned elemIdx) const { if (calciteConcentration_.empty()) return 0; return calciteConcentration_[elemIdx]; } template unsigned EclGenericProblem:: pvtRegionIndex(unsigned elemIdx) const { if (pvtnum_.empty()) return 0; return pvtnum_[elemIdx]; } template unsigned EclGenericProblem:: satnumRegionIndex(unsigned elemIdx) const { if (satnum_.empty()) return 0; return satnum_[elemIdx]; } template unsigned EclGenericProblem:: miscnumRegionIndex(unsigned elemIdx) const { if (miscnum_.empty()) return 0; return miscnum_[elemIdx]; } template unsigned EclGenericProblem:: plmixnumRegionIndex(unsigned elemIdx) const { if (plmixnum_.empty()) return 0; return plmixnum_[elemIdx]; } template Scalar EclGenericProblem:: maxPolymerAdsorption(unsigned elemIdx) const { if (maxPolymerAdsorption_.empty()) return 0; return maxPolymerAdsorption_[elemIdx]; } template void EclGenericProblem:: initDRSDT_(size_t numDof, int episodeIdx) { // deal with DRSDT unsigned ntpvt = eclState_.runspec().tabdims().getNumPVTTables(); //TODO We may want to only allocate these properties only if active. //But since they may be activated at later time we need some more //intrastructure to handle it if (FluidSystem::phaseIsActive(FluidSystem::oilPhaseIdx) && FluidSystem::phaseIsActive(FluidSystem::gasPhaseIdx)) { maxDRv_.resize(ntpvt, 1e30); lastRv_.resize(numDof, 0.0); maxDRs_.resize(ntpvt, 1e30); dRsDtOnlyFreeGas_.resize(ntpvt, false); lastRs_.resize(numDof, 0.0); maxDRv_.resize(ntpvt, 1e30); lastRv_.resize(numDof, 0.0); maxOilSaturation_.resize(numDof, 0.0); if (drsdtConvective_(episodeIdx)) { convectiveDrs_.resize(numDof, 1.0); } } } #if HAVE_DUNE_FEM template class EclGenericProblem>>, BlackOilFluidSystem, double>; template class EclGenericProblem>, Opm::BlackOilFluidSystem< double, Opm::BlackOilDefaultIndexTraits>, double>; #else template class EclGenericProblem>, BlackOilFluidSystem, double>; #endif template class EclGenericProblem>, BlackOilFluidSystem, double>; } // namespace Opm