remove files

This commit is contained in:
Arne Morten Kvarving 2018-11-14 14:42:52 +01:00
parent 78158bf44a
commit f027262ec4
259 changed files with 1 additions and 72573 deletions

View File

@ -25,17 +25,10 @@
list (APPEND MAIN_SOURCE_FILES
opm/autodiff/Compat.cpp
opm/autodiff/ExtractParallelGridInformationToISTL.cpp
opm/autodiff/NewtonIterationBlackoilCPR.cpp
opm/autodiff/NewtonIterationBlackoilInterleaved.cpp
opm/autodiff/NewtonIterationBlackoilSimple.cpp
opm/autodiff/NewtonIterationUtilities.cpp
opm/autodiff/GridHelpers.cpp
opm/autodiff/ImpesTPFAAD.cpp
opm/autodiff/moduleVersion.cpp
opm/autodiff/multiPhaseUpwind.cpp
opm/autodiff/SimulatorFullyImplicitBlackoilOutput.cpp
opm/autodiff/SimulatorIncompTwophaseAd.cpp
opm/autodiff/TransportSolverTwophaseAd.cpp
opm/autodiff/BlackoilPropsAdFromDeck.cpp
opm/autodiff/BlackoilModelParameters.cpp
opm/autodiff/WellDensitySegmented.cpp
@ -46,53 +39,12 @@ list (APPEND MAIN_SOURCE_FILES
opm/autodiff/VFPInjProperties.cpp
opm/autodiff/VFPInjPropertiesLegacy.cpp
opm/autodiff/MissingFeatures.cpp
opm/core/flowdiagnostics/AnisotropicEikonal.cpp
opm/core/flowdiagnostics/DGBasis.cpp
opm/core/flowdiagnostics/FlowDiagnostics.cpp
opm/core/flowdiagnostics/TofDiscGalReorder.cpp
opm/core/flowdiagnostics/TofReorder.cpp
opm/core/linalg/LinearSolverFactory.cpp
opm/core/linalg/LinearSolverInterface.cpp
opm/core/linalg/LinearSolverIstl.cpp
opm/core/linalg/LinearSolverUmfpack.cpp
opm/core/linalg/call_umfpack.c
opm/core/linalg/sparse_sys.c
opm/core/pressure/CompressibleTpfa.cpp
opm/core/pressure/FlowBCManager.cpp
opm/core/pressure/IncompTpfa.cpp
opm/core/pressure/IncompTpfaSinglePhase.cpp
opm/core/pressure/flow_bc.c
opm/core/pressure/mimetic/mimetic.c
opm/core/pressure/msmfem/dfs.c
opm/core/pressure/msmfem/partition.c
opm/core/pressure/tpfa/cfs_tpfa_residual.c
opm/core/pressure/tpfa/ifs_tpfa.c
opm/core/props/BlackoilPropertiesBasic.cpp
opm/core/props/BlackoilPropertiesFromDeck.cpp
opm/core/props/IncompPropertiesBasic.cpp
opm/core/props/IncompPropertiesFromDeck.cpp
opm/core/props/IncompPropertiesSinglePhase.cpp
opm/core/props/pvt/PvtPropertiesBasic.cpp
opm/core/props/pvt/PvtPropertiesIncompFromDeck.cpp
opm/core/props/rock/RockBasic.cpp
opm/core/props/rock/RockCompressibility.cpp
opm/core/props/rock/RockFromDeck.cpp
opm/core/props/satfunc/RelpermDiagnostics.cpp
opm/core/props/satfunc/SaturationPropsBasic.cpp
opm/core/props/satfunc/SaturationPropsFromDeck.cpp
opm/core/simulator/BlackoilState.cpp
opm/core/simulator/TwophaseState.cpp
opm/core/simulator/SimulatorReport.cpp
opm/core/transport/TransportSolverTwophaseInterface.cpp
opm/core/transport/reorder/ReorderSolverInterface.cpp
opm/core/transport/reorder/TransportSolverCompressibleTwophaseReorder.cpp
opm/core/transport/reorder/TransportSolverTwophaseReorder.cpp
opm/core/transport/reorder/reordersequence.cpp
opm/core/transport/reorder/tarjan.c
opm/core/utility/Event.cpp
opm/core/utility/miscUtilities.cpp
opm/core/utility/miscUtilitiesBlackoil.cpp
opm/core/utility/NullStream.cpp
opm/core/wells/InjectionSpecification.cpp
opm/core/wells/ProductionSpecification.cpp
opm/core/wells/WellCollection.cpp
@ -100,21 +52,8 @@ list (APPEND MAIN_SOURCE_FILES
opm/core/wells/WellsManager.cpp
opm/core/wells/well_controls.c
opm/core/wells/wells.c
opm/polymer/PolymerState.cpp
opm/polymer/PolymerBlackoilState.cpp
opm/polymer/CompressibleTpfaPolymer.cpp
opm/polymer/IncompTpfaPolymer.cpp
opm/polymer/PolymerInflow.cpp
opm/polymer/PolymerProperties.cpp
opm/polymer/polymerUtilities.cpp
opm/polymer/SimulatorCompressiblePolymer.cpp
opm/polymer/SimulatorPolymer.cpp
opm/polymer/TransportSolverTwophaseCompressiblePolymer.cpp
opm/polymer/TransportSolverTwophasePolymer.cpp
opm/simulators/ensureDirectoryExists.cpp
opm/simulators/SimulatorCompressibleTwophase.cpp
opm/simulators/WellSwitchingLogger.cpp
opm/simulators/vtk/writeVtkData.cpp
opm/simulators/timestepping/TimeStepControl.cpp
opm/simulators/timestepping/AdaptiveSimulatorTimer.cpp
opm/simulators/timestepping/SimulatorTimer.cpp
@ -133,52 +72,37 @@ list (APPEND TEST_SOURCE_FILES
tests/test_autodiffmatrix.cpp
tests/test_blackoil_amg.cpp
tests/test_block.cpp
tests/test_boprops_ad.cpp
tests/test_convergencereport.cpp
tests/test_graphcoloring.cpp
tests/test_rateconverter.cpp
tests/test_span.cpp
tests/test_syntax.cpp
tests/test_scalar_mult.cpp
tests/test_transmissibilitymultipliers.cpp
tests/test_welldensitysegmented.cpp
tests/test_vfpproperties.cpp
tests/test_vfpproperties_legacy.cpp
tests/test_singlecellsolves.cpp
tests/test_milu.cpp
tests/test_multmatrixtransposed.cpp
tests/test_multiphaseupwind.cpp
tests/test_wellmodel.cpp
# tests/test_thresholdpressure.cpp
tests/test_wellswitchlogger.cpp
tests/test_timer.cpp
tests/test_invert.cpp
tests/test_event.cpp
tests/test_dgbasis.cpp
tests/test_flowdiagnostics.cpp
tests/test_wells.cpp
tests/test_linearsolver.cpp
tests/test_satfunc.cpp
tests/test_shadow.cpp
tests/test_equil_legacy.cpp
tests/test_blackoilstate.cpp
tests/test_wellsmanager.cpp
tests/test_wellcontrols.cpp
tests/test_wellsgroup.cpp
tests/test_wellcollection.cpp
tests/test_anisotropiceikonal.cpp
tests/test_stoppedwells.cpp
tests/test_relpermdiagnostics.cpp
tests/test_norne_pvt.cpp
)
if(MPI_FOUND)
list(APPEND TEST_SOURCE_FILES tests/test_parallel_linearsolver.cpp
tests/test_parallelistlinformation.cpp)
list(APPEND TEST_SOURCE_FILES tests/test_parallelistlinformation.cpp)
endif()
list (APPEND TEST_DATA_FILES
tests/fluid.data
tests/VFPPROD1
tests/VFPPROD2
tests/msw.data
@ -193,12 +117,7 @@ list (APPEND TEST_DATA_FILES
tests/equil_liveoil.DATA
tests/equil_rsvd_and_rvvd.DATA
tests/wetgas.DATA
tests/satfuncStandard.DATA
tests/satfuncEPSBase.DATA
tests/satfuncEPS_A.DATA
tests/satfuncEPS_B.DATA
tests/satfuncEPS_C.DATA
tests/satfuncEPS_D.DATA
tests/testBlackoilState1.DATA
tests/testBlackoilState2.DATA
tests/wells_manager_data.data
@ -215,39 +134,6 @@ list (APPEND TEST_DATA_FILES
# find tutorials examples -name '*.c*' -printf '\t%p\n' | sort
list (APPEND EXAMPLE_SOURCE_FILES
examples/find_zero.cpp
examples/flow_legacy.cpp
examples/flow_reorder.cpp
examples/flow_sequential.cpp
examples/sim_2p_incomp_ad.cpp
examples/sim_2p_comp_reorder.cpp
examples/sim_simple.cpp
examples/sim_poly2p_comp_reorder.cpp
examples/sim_poly2p_incomp_reorder.cpp
examples/wells_example.cpp
examples/compute_eikonal_from_files.cpp
examples/compute_initial_state.cpp
examples/compute_tof_from_files.cpp
examples/diagnose_relperm.cpp
tutorials/sim_tutorial1.cpp
)
if(SuiteSparse_FOUND)
list(APPEND EXAMPLE_SOURCE_FILES tutorials/sim_tutorial2.cpp
tutorials/sim_tutorial3.cpp
tutorials/sim_tutorial4.cpp)
endif()
# programs listed here will not only be compiled, but also marked for
# installation
list (APPEND PROGRAM_SOURCE_FILES
examples/sim_2p_incomp.cpp
examples/sim_2p_incomp_ad.cpp
examples/sim_2p_comp_reorder.cpp
examples/flow_legacy.cpp
examples/flow_reorder.cpp
examples/flow_sequential.cpp
examples/sim_poly2p_comp_reorder.cpp
examples/sim_poly2p_incomp_reorder.cpp
)
# originally generated with the command:
@ -287,16 +173,12 @@ list (APPEND PUBLIC_HEADER_FILES
opm/autodiff/GraphColoring.hpp
opm/autodiff/GridHelpers.hpp
opm/autodiff/GridInit.hpp
opm/autodiff/ImpesTPFAAD.hpp
opm/autodiff/ISTLSolver.hpp
opm/autodiff/ISTLSolverEbos.hpp
opm/autodiff/IterationReport.hpp
opm/autodiff/moduleVersion.hpp
opm/autodiff/multiPhaseUpwind.hpp
opm/autodiff/NewtonIterationBlackoilCPR.hpp
opm/autodiff/NewtonIterationBlackoilInterface.hpp
opm/autodiff/NewtonIterationBlackoilInterleaved.hpp
opm/autodiff/NewtonIterationBlackoilSimple.hpp
opm/autodiff/NewtonIterationUtilities.hpp
opm/autodiff/NonlinearSolver.hpp
opm/autodiff/NonlinearSolver_impl.hpp
@ -312,13 +194,10 @@ list (APPEND PUBLIC_HEADER_FILES
opm/autodiff/SimulatorBase_impl.hpp
opm/autodiff/SimulatorFullyImplicitBlackoilEbos.hpp
opm/autodiff/SimulatorFullyImplicitBlackoil.hpp
opm/autodiff/SimulatorIncompTwophaseAd.hpp
opm/autodiff/SimulatorSequentialBlackoil.hpp
opm/autodiff/TransportSolverTwophaseAd.hpp
opm/autodiff/WellConnectionAuxiliaryModule.hpp
opm/autodiff/WellDensitySegmented.hpp
opm/autodiff/WellStateFullyImplicitBlackoil.hpp
opm/autodiff/SimulatorFullyImplicitBlackoilOutput.hpp
opm/autodiff/ThreadHandle.hpp
opm/autodiff/VFPProperties.hpp
opm/autodiff/VFPHelpers.hpp
@ -337,53 +216,22 @@ list (APPEND PUBLIC_HEADER_FILES
opm/autodiff/BlackoilWellModel.hpp
opm/autodiff/BlackoilWellModel_impl.hpp
opm/autodiff/MissingFeatures.hpp
opm/core/flowdiagnostics/AnisotropicEikonal.hpp
opm/core/flowdiagnostics/DGBasis.hpp
opm/core/flowdiagnostics/FlowDiagnostics.hpp
opm/core/flowdiagnostics/TofDiscGalReorder.hpp
opm/core/flowdiagnostics/TofReorder.hpp
opm/core/linalg/LinearSolverFactory.hpp
opm/core/linalg/LinearSolverInterface.hpp
opm/core/linalg/LinearSolverIstl.hpp
opm/core/linalg/LinearSolverPetsc.hpp
opm/core/linalg/LinearSolverUmfpack.hpp
opm/core/linalg/ParallelIstlInformation.hpp
opm/core/linalg/call_umfpack.h
opm/core/linalg/sparse_sys.h
opm/core/pressure/CompressibleTpfa.hpp
opm/core/pressure/FlowBCManager.hpp
opm/core/pressure/IncompTpfa.hpp
opm/core/pressure/flow_bc.h
opm/core/pressure/legacy_well.h
opm/core/pressure/mimetic/mimetic.h
opm/core/pressure/msmfem/dfs.h
opm/core/pressure/msmfem/partition.h
opm/core/pressure/tpfa/cfs_tpfa_residual.h
opm/core/pressure/tpfa/compr_quant_general.h
opm/core/pressure/tpfa/compr_source.h
opm/core/pressure/tpfa/ifs_tpfa.h
opm/core/props/BlackoilPhases.hpp
opm/core/props/BlackoilPropertiesBasic.hpp
opm/core/props/BlackoilPropertiesFromDeck.hpp
opm/core/props/BlackoilPropertiesInterface.hpp
opm/core/props/IncompPropertiesBasic.hpp
opm/core/props/IncompPropertiesFromDeck.hpp
opm/core/props/IncompPropertiesInterface.hpp
opm/core/props/IncompPropertiesShadow.hpp
opm/core/props/IncompPropertiesShadow_impl.hpp
opm/core/props/IncompPropertiesSinglePhase.hpp
opm/core/props/phaseUsageFromDeck.hpp
opm/core/props/pvt/PvtPropertiesBasic.hpp
opm/core/props/pvt/PvtPropertiesIncompFromDeck.hpp
opm/core/props/pvt/ThermalGasPvtWrapper.hpp
opm/core/props/pvt/ThermalOilPvtWrapper.hpp
opm/core/props/pvt/ThermalWaterPvtWrapper.hpp
opm/core/props/rock/RockBasic.hpp
opm/core/props/rock/RockCompressibility.hpp
opm/core/props/rock/RockFromDeck.hpp
opm/core/props/satfunc/RelpermDiagnostics.hpp
opm/core/props/satfunc/SaturationPropsBasic.hpp
opm/core/props/satfunc/SaturationPropsFromDeck.hpp
opm/core/props/satfunc/SaturationPropsInterface.hpp
opm/core/props/satfunc/RelpermDiagnostics_impl.hpp
opm/core/simulator/BlackoilState.hpp
@ -392,26 +240,16 @@ list (APPEND PUBLIC_HEADER_FILES
opm/core/simulator/ExplicitArraysFluidState.hpp
opm/core/simulator/ExplicitArraysSatDerivativesFluidState.hpp
opm/core/simulator/SimulatorReport.hpp
opm/core/simulator/TwophaseState.hpp
opm/core/simulator/WellState.hpp
opm/core/simulator/initState.hpp
opm/core/simulator/initStateEquil.hpp
opm/core/simulator/initStateEquil_impl.hpp
opm/core/simulator/initState_impl.hpp
opm/core/transport/TransportSolverTwophaseInterface.hpp
opm/core/transport/reorder/ReorderSolverInterface.hpp
opm/core/transport/reorder/TransportSolverCompressibleTwophaseReorder.hpp
opm/core/transport/reorder/TransportSolverTwophaseReorder.hpp
opm/core/transport/reorder/reordersequence.h
opm/core/transport/reorder/tarjan.h
opm/core/utility/DataMap.hpp
opm/core/utility/Event.hpp
opm/core/utility/Event_impl.hpp
opm/core/utility/initHydroCarbonState.hpp
opm/core/utility/miscUtilities.hpp
opm/core/utility/miscUtilitiesBlackoil.hpp
opm/core/utility/miscUtilities_impl.hpp
opm/core/utility/NullStream.hpp
opm/core/utility/share_obj.hpp
opm/core/well_controls.h
opm/core/wells.h
@ -422,28 +260,15 @@ list (APPEND PUBLIC_HEADER_FILES
opm/core/wells/WellsManager.hpp
opm/core/wells/DynamicListEconLimited.hpp
opm/core/wells/WellsManager_impl.hpp
opm/polymer/CompressibleTpfaPolymer.hpp
opm/polymer/GravityColumnSolverPolymer.hpp
opm/polymer/GravityColumnSolverPolymer_impl.hpp
opm/polymer/IncompPropertiesDefaultPolymer.hpp
opm/polymer/IncompTpfaPolymer.hpp
opm/polymer/PolymerBlackoilState.hpp
opm/polymer/PolymerInflow.hpp
opm/polymer/PolymerProperties.hpp
opm/polymer/PolymerState.hpp
opm/polymer/polymerUtilities.hpp
opm/polymer/SimulatorCompressiblePolymer.hpp
opm/polymer/SimulatorPolymer.hpp
opm/polymer/SinglePointUpwindTwoPhasePolymer.hpp
opm/polymer/TransportSolverTwophaseCompressiblePolymer.hpp
opm/polymer/Point2D.hpp
opm/polymer/TransportSolverTwophasePolymer.hpp
opm/simulators/ensureDirectoryExists.hpp
opm/simulators/ParallelFileMerger.hpp
opm/simulators/SimulatorCompressibleTwophase.hpp
opm/simulators/thresholdPressures.hpp
opm/simulators/WellSwitchingLogger.hpp
opm/simulators/vtk/writeVtkData.hpp
opm/simulators/timestepping/AdaptiveSimulatorTimer.hpp
opm/simulators/timestepping/AdaptiveTimeStepping.hpp
opm/simulators/timestepping/AdaptiveTimeStepping_impl.hpp

View File

@ -1,131 +0,0 @@
/*
Copyright 2014 SINTEF ICT, Applied Mathematics.
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 3 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 <http://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif // HAVE_CONFIG_H
#include <opm/core/flowdiagnostics/AnisotropicEikonal.hpp>
#include <opm/grid/UnstructuredGrid.h>
#include <opm/grid/GridManager.hpp>
#include <opm/common/ErrorMacros.hpp>
#include <opm/grid/utility/StopWatch.hpp>
#include <opm/core/utility/miscUtilities.hpp>
#include <opm/common/utility/parameters/ParameterGroup.hpp>
#include <boost/filesystem.hpp>
#include <memory>
#include <algorithm>
#include <iostream>
#include <fstream>
#include <vector>
#include <numeric>
#include <iterator>
namespace
{
void warnIfUnusedParams(const Opm::ParameterGroup& param)
{
if (param.anyUnused()) {
std::cout << "-------------------- Warning: unused parameters: --------------------\n";
param.displayUsage();
std::cout << "-------------------------------------------------------------------------" << std::endl;
}
}
} // anon namespace
// ----------------- Main program -----------------
int
main(int argc, char** argv)
try
{
using namespace Opm;
ParameterGroup param(argc, argv);
// Read grid.
GridManager grid_manager(param.get<std::string>("grid_filename"));
const UnstructuredGrid& grid = *grid_manager.c_grid();
// Read metric tensor.
std::vector<double> metric;
{
std::ifstream metric_stream(param.get<std::string>("metric_filename").c_str());
std::istream_iterator<double> beg(metric_stream);
std::istream_iterator<double> end;
metric.assign(beg, end);
if (int(metric.size()) != grid.number_of_cells*grid.dimensions*grid.dimensions) {
OPM_THROW(std::runtime_error, "Size of metric field differs from (dim^2 * number of cells).");
}
}
// Read starting cells.
std::vector<int> startcells;
{
std::ifstream start_stream(param.get<std::string>("startcells_filename").c_str());
std::istream_iterator<int> beg(start_stream);
std::istream_iterator<int> end;
startcells.assign(beg, end);
}
// Write parameters used for later reference.
bool output = param.getDefault("output", true);
std::string output_dir;
if (output) {
output_dir =
param.getDefault("output_dir", std::string("output"));
boost::filesystem::path fpath(output_dir);
try {
create_directories(fpath);
}
catch (...) {
OPM_THROW(std::runtime_error, "Creating directories failed: " << fpath);
}
param.writeParam(output_dir + "/eikonal.param");
}
// Issue a warning if any parameters were unused.
warnIfUnusedParams(param);
// Solve eikonal equation.
Opm::time::StopWatch timer;
timer.start();
std::vector<double> solution;
AnisotropicEikonal2d ae(grid);
ae.solve(metric.data(), startcells, solution);
timer.stop();
double tt = timer.secsSinceStart();
std::cout << "Eikonal solver took: " << tt << " seconds." << std::endl;
// Output.
if (output) {
std::string filename = output_dir + "/solution.txt";
std::ofstream stream(filename.c_str());
stream.precision(16);
std::copy(solution.begin(), solution.end(), std::ostream_iterator<double>(stream, "\n"));
}
}
catch (const std::exception &e) {
std::cerr << "Program threw an exception: " << e.what() << "\n";
throw;
}

View File

@ -1,174 +0,0 @@
/*
Copyright 2014 SINTEF ICT, Applied Mathematics.
Copyright 2017 IRIS
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 3 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 <http://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif // HAVE_CONFIG_H
#include <opm/grid/UnstructuredGrid.h>
#include <opm/grid/GridManager.hpp>
#include <opm/common/ErrorMacros.hpp>
#include <opm/core/simulator/initStateEquil.hpp>
#include <opm/common/utility/parameters/ParameterGroup.hpp>
#include <opm/core/props/BlackoilPhases.hpp>
#include <opm/core/props/phaseUsageFromDeck.hpp>
#include <opm/core/simulator/BlackoilState.hpp>
#include <opm/grid/utility/compressedToCartesian.hpp>
#include <opm/parser/eclipse/Parser/ParseContext.hpp>
#include <opm/parser/eclipse/Parser/Parser.hpp>
#include <opm/parser/eclipse/Deck/Deck.hpp>
#include <opm/material/fluidmatrixinteractions/EclMaterialLawManager.hpp>
#include <boost/filesystem.hpp>
#include <fstream>
namespace
{
void warnIfUnusedParams(const Opm::ParameterGroup& param)
{
if (param.anyUnused()) {
std::cout << "-------------------- Unused parameters: --------------------\n";
param.displayUsage();
std::cout << "----------------------------------------------------------------" << std::endl;
}
}
void outputData(const std::string& output_dir,
const std::string& name,
const std::vector<double>& data)
{
std::ostringstream fname;
fname << output_dir << "/" << name;
boost::filesystem::path fpath = fname.str();
try {
create_directories(fpath);
}
catch (...) {
OPM_THROW(std::runtime_error, "Creating directories failed: " << fpath);
}
fname << "/" << "initial.txt";
std::ofstream file(fname.str().c_str());
if (!file) {
OPM_THROW(std::runtime_error, "Failed to open " << fname.str());
}
std::copy(data.begin(), data.end(), std::ostream_iterator<double>(file, "\n"));
}
/// Convert saturations from a vector of individual phase saturation vectors
/// to an interleaved format where all values for a given cell come before all
/// values for the next cell, all in a single vector.
template <class FluidSystem>
void convertSats(std::vector<double>& sat_interleaved, const std::vector< std::vector<double> >& sat, const Opm::PhaseUsage& pu)
{
assert(sat.size() == 3);
const auto nc = sat[0].size();
const auto np = sat_interleaved.size() / nc;
for (size_t c = 0; c < nc; ++c) {
if ( FluidSystem::phaseIsActive(FluidSystem::oilPhaseIdx)) {
const int opos = pu.phase_pos[Opm::BlackoilPhases::Liquid];
const std::vector<double>& sat_p = sat[ FluidSystem::oilPhaseIdx];
sat_interleaved[np*c + opos] = sat_p[c];
}
if ( FluidSystem::phaseIsActive(FluidSystem::waterPhaseIdx)) {
const int wpos = pu.phase_pos[Opm::BlackoilPhases::Aqua];
const std::vector<double>& sat_p = sat[ FluidSystem::waterPhaseIdx];
sat_interleaved[np*c + wpos] = sat_p[c];
}
if ( FluidSystem::phaseIsActive(FluidSystem::gasPhaseIdx)) {
const int gpos = pu.phase_pos[Opm::BlackoilPhases::Vapour];
const std::vector<double>& sat_p = sat[ FluidSystem::gasPhaseIdx];
sat_interleaved[np*c + gpos] = sat_p[c];
}
}
}
} // anon namespace
// ----------------- Main program -----------------
int
main(int argc, char** argv)
try
{
using namespace Opm;
// Setup.
ParameterGroup param(argc, argv);
std::cout << "--------------- Reading parameters ---------------" << std::endl;
const std::string deck_filename = param.get<std::string>("deck_filename");
Opm::ParseContext parseContext;
Opm::Parser parser;
const Opm::Deck& deck = parser.parseFile(deck_filename , parseContext);
const Opm::EclipseState eclipseState(deck, parseContext);
const double grav = param.getDefault("gravity", unit::gravity);
GridManager gm(eclipseState.getInputGrid());
const UnstructuredGrid& grid = *gm.c_grid();
warnIfUnusedParams(param);
// Create material law manager.
std::vector<int> compressedToCartesianIdx
= Opm::compressedToCartesian(grid.number_of_cells, grid.global_cell);
typedef BlackOilFluidSystem<double> FluidSystem;
// Forward declaring the MaterialLawManager template.
typedef Opm::ThreePhaseMaterialTraits<double,
/*wettingPhaseIdx=*/FluidSystem::waterPhaseIdx,
/*nonWettingPhaseIdx=*/FluidSystem::oilPhaseIdx,
/*gasPhaseIdx=*/FluidSystem::gasPhaseIdx> MaterialTraits;
typedef Opm::EclMaterialLawManager<MaterialTraits> MaterialLawManager;
MaterialLawManager materialLawManager = MaterialLawManager();
materialLawManager.initFromDeck(deck, eclipseState, compressedToCartesianIdx);
// Initialisation.
//initBlackoilSurfvolUsingRSorRV(UgGridHelpers::numCells(grid), props, state);
BlackoilState state( UgGridHelpers::numCells(grid) , UgGridHelpers::numFaces(grid), 3);
FluidSystem::initFromDeck(deck, eclipseState);
PhaseUsage pu = phaseUsageFromDeck(deck);
typedef EQUIL::DeckDependent::InitialStateComputer<FluidSystem> ISC;
ISC isc(materialLawManager, eclipseState, grid, grav);
const bool oil = FluidSystem::phaseIsActive(FluidSystem::oilPhaseIdx);
const int oilpos = FluidSystem::oilPhaseIdx;
const int waterpos = FluidSystem::waterPhaseIdx;
const int ref_phase = oil ? oilpos : waterpos;
state.pressure() = isc.press()[ref_phase];
convertSats<FluidSystem>(state.saturation(), isc.saturation(), pu);
state.gasoilratio() = isc.rs();
state.rv() = isc.rv();
// Output.
const std::string output_dir = param.getDefault<std::string>("output_dir", "output");
outputData(output_dir, "pressure", state.pressure());
outputData(output_dir, "saturation", state.saturation());
outputData(output_dir, "rs", state.gasoilratio());
outputData(output_dir, "rv", state.rv());
}
catch (const std::exception& e) {
std::cerr << "Program threw an exception: " << e.what() << "\n";
throw;
}

View File

@ -1,213 +0,0 @@
/*
Copyright 2012 SINTEF ICT, Applied Mathematics.
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 3 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 <http://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif // HAVE_CONFIG_H
#include <opm/grid/UnstructuredGrid.h>
#include <opm/grid/GridManager.hpp>
#include <opm/core/wells.h>
#include <opm/core/wells/WellsManager.hpp>
#include <opm/common/ErrorMacros.hpp>
#include <opm/grid/utility/SparseTable.hpp>
#include <opm/grid/utility/StopWatch.hpp>
#include <opm/core/utility/miscUtilities.hpp>
#include <opm/common/utility/parameters/ParameterGroup.hpp>
#include <opm/core/props/IncompPropertiesBasic.hpp>
#include <opm/core/props/IncompPropertiesFromDeck.hpp>
#include <opm/core/linalg/LinearSolverFactory.hpp>
#include <opm/core/simulator/TwophaseState.hpp>
#include <opm/core/simulator/WellState.hpp>
#include <opm/core/simulator/initState.hpp>
#include <opm/core/flowdiagnostics/TofReorder.hpp>
#include <opm/core/flowdiagnostics/TofDiscGalReorder.hpp>
#include <memory>
#include <boost/filesystem.hpp>
#include <algorithm>
#include <iostream>
#include <vector>
#include <numeric>
#include <iterator>
#include <fstream>
namespace
{
void warnIfUnusedParams(const Opm::ParameterGroup& param)
{
if (param.anyUnused()) {
std::cout << "-------------------- Warning: unused parameters: --------------------\n";
param.displayUsage();
std::cout << "-------------------------------------------------------------------------" << std::endl;
}
}
} // anon namespace
// ----------------- Main program -----------------
int
main(int argc, char** argv)
try
{
using namespace Opm;
ParameterGroup param(argc, argv);
// Read grid.
GridManager grid_manager(param.get<std::string>("grid_filename"));
const UnstructuredGrid& grid = *grid_manager.c_grid();
// Read porosity, compute pore volume.
std::vector<double> porevol;
{
std::ifstream poro_stream(param.get<std::string>("poro_filename").c_str());
std::istream_iterator<double> beg(poro_stream);
std::istream_iterator<double> end;
porevol.assign(beg, end); // Now contains poro.
if (int(porevol.size()) != grid.number_of_cells) {
OPM_THROW(std::runtime_error, "Size of porosity field differs from number of cells.");
}
for (int i = 0; i < grid.number_of_cells; ++i) {
porevol[i] *= grid.cell_volumes[i];
}
}
// Read flux.
std::vector<double> flux;
{
std::ifstream flux_stream(param.get<std::string>("flux_filename").c_str());
std::istream_iterator<double> beg(flux_stream);
std::istream_iterator<double> end;
flux.assign(beg, end);
if (int(flux.size()) != grid.number_of_faces) {
OPM_THROW(std::runtime_error, "Size of flux field differs from number of faces.");
}
}
// Read source terms.
std::vector<double> src;
{
std::ifstream src_stream(param.get<std::string>("src_filename").c_str());
std::istream_iterator<double> beg(src_stream);
std::istream_iterator<double> end;
src.assign(beg, end);
if (int(src.size()) != grid.number_of_cells) {
OPM_THROW(std::runtime_error, "Size of source term field differs from number of cells.");
}
}
const bool compute_tracer = param.getDefault("compute_tracer", false);
Opm::SparseTable<int> tracerheads;
if (compute_tracer) {
std::ifstream tr_stream(param.get<std::string>("tracerheads_filename").c_str());
int num_rows;
tr_stream >> num_rows;
for (int row = 0; row < num_rows; ++row) {
int row_size;
tr_stream >> row_size;
std::vector<int> rowdata(row_size);
for (int elem = 0; elem < row_size; ++elem) {
tr_stream >> rowdata[elem];
}
tracerheads.appendRow(rowdata.begin(), rowdata.end());
}
}
// Choice of tof solver.
bool use_dg = param.getDefault("use_dg", false);
bool use_multidim_upwind = false;
// Need to initialize dg solver here, since it uses parameters now.
std::unique_ptr<Opm::TofDiscGalReorder> dg_solver;
if (use_dg) {
dg_solver.reset(new Opm::TofDiscGalReorder(grid, param));
} else {
use_multidim_upwind = param.getDefault("use_multidim_upwind", false);
}
// Write parameters used for later reference.
bool output = param.getDefault("output", true);
std::ofstream epoch_os;
std::string output_dir;
if (output) {
output_dir =
param.getDefault("output_dir", std::string("output"));
boost::filesystem::path fpath(output_dir);
try {
create_directories(fpath);
}
catch (...) {
OPM_THROW(std::runtime_error, "Creating directories failed: " << fpath);
}
param.writeParam(output_dir + "/simulation.param");
}
// Issue a warning if any parameters were unused.
warnIfUnusedParams(param);
// Solve time-of-flight.
Opm::time::StopWatch transport_timer;
transport_timer.start();
std::vector<double> tof;
std::vector<double> tracer;
if (use_dg) {
if (compute_tracer) {
dg_solver->solveTofTracer(&flux[0], &porevol[0], &src[0], tracerheads, tof, tracer);
} else {
dg_solver->solveTof(&flux[0], &porevol[0], &src[0], tof);
}
} else {
Opm::TofReorder tofsolver(grid, use_multidim_upwind);
if (compute_tracer) {
tofsolver.solveTofTracer(&flux[0], &porevol[0], &src[0], tracerheads, tof, tracer);
} else {
tofsolver.solveTof(&flux[0], &porevol[0], &src[0], tof);
}
}
transport_timer.stop();
double tt = transport_timer.secsSinceStart();
std::cout << "Transport solver took: " << tt << " seconds." << std::endl;
// Output.
if (output) {
std::string tof_filename = output_dir + "/tof.txt";
std::ofstream tof_stream(tof_filename.c_str());
tof_stream.precision(16);
std::copy(tof.begin(), tof.end(), std::ostream_iterator<double>(tof_stream, "\n"));
if (compute_tracer) {
std::string tracer_filename = output_dir + "/tracer.txt";
std::ofstream tracer_stream(tracer_filename.c_str());
tracer_stream.precision(16);
const int nt = tracer.size()/grid.number_of_cells;
for (int i = 0; i < nt*grid.number_of_cells; ++i) {
tracer_stream << tracer[i] << (((i + 1) % nt == 0) ? '\n' : ' ');
}
}
}
}
catch (const std::exception &e) {
std::cerr << "Program threw an exception: " << e.what() << "\n";
throw;
}

View File

@ -1,105 +0,0 @@
/*
Copyright 2015 Statoil ASA.
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 3 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 <http://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif // HAVE_CONFIG_H
#include <opm/grid/UnstructuredGrid.h>
#include <opm/grid/GridManager.hpp>
#include <opm/core/props/satfunc/RelpermDiagnostics.hpp>
#include <opm/core/props/satfunc/RelpermDiagnostics_impl.hpp>
#include <opm/common/ErrorMacros.hpp>
#include <opm/grid/utility/StopWatch.hpp>
#include <opm/core/utility/miscUtilities.hpp>
#include <opm/common/utility/parameters/ParameterGroup.hpp>
#include <opm/parser/eclipse/Parser/ParseContext.hpp>
#include <opm/parser/eclipse/Parser/Parser.hpp>
#include <opm/parser/eclipse/Deck/Deck.hpp>
#include <opm/common/OpmLog/OpmLog.hpp>
#include <opm/common/OpmLog/EclipsePRTLog.hpp>
#include <boost/filesystem.hpp>
#include <boost/algorithm/string/case_conv.hpp>
#include <memory>
#include <algorithm>
#include <iostream>
#include <vector>
#include <numeric>
#include <iterator>
void usage() {
std::cout << std::endl <<
"Usage: diagnose_relperm <eclipseFile>" << std::endl;
}
// ----------------- Main program -----------------
int
main(int argc, char** argv)
try
{
using namespace Opm;
if (argc <= 1) {
usage();
exit(1);
}
const char* eclipseFilename = argv[1];
Parser parser;
Opm::ParseContext parseContext({{ ParseContext::PARSE_RANDOM_SLASH , InputError::IGNORE },
{ ParseContext::PARSE_UNKNOWN_KEYWORD, InputError::IGNORE},
{ ParseContext::PARSE_RANDOM_TEXT, InputError::IGNORE},
{ ParseContext::UNSUPPORTED_SCHEDULE_GEO_MODIFIER, InputError::IGNORE},
{ ParseContext::UNSUPPORTED_COMPORD_TYPE, InputError::IGNORE},
{ ParseContext::UNSUPPORTED_INITIAL_THPRES, InputError::IGNORE},
{ ParseContext::INTERNAL_ERROR_UNINITIALIZED_THPRES, InputError::IGNORE}
});
Opm::Deck deck = parser.parseFile(eclipseFilename, parseContext);
Opm::EclipseState eclState( deck, parseContext );
GridManager gm(eclState.getInputGrid());
const UnstructuredGrid& grid = *gm.c_grid();
using boost::filesystem::path;
path fpath(eclipseFilename);
std::string baseName;
if (boost::to_upper_copy(path(fpath.extension()).string())== ".DATA") {
baseName = path(fpath.stem()).string();
} else {
baseName = path(fpath.filename()).string();
}
std::string logFile = baseName + ".SATFUNCLOG";
std::shared_ptr<EclipsePRTLog> prtLog = std::make_shared<EclipsePRTLog>(logFile, Log::DefaultMessageTypes);
OpmLog::addBackend( "ECLIPSEPRTLOG" , prtLog );
prtLog->setMessageFormatter(std::make_shared<SimpleMessageFormatter>(true, false));
std::shared_ptr<StreamLog> streamLog = std::make_shared<EclipsePRTLog>(std::cout, Log::DefaultMessageTypes);
OpmLog::addBackend( "STREAMLOG" , streamLog );
streamLog->setMessageLimiter(std::make_shared<MessageLimiter>(10));
streamLog->setMessageFormatter(std::make_shared<SimpleMessageFormatter>(true, true));
RelpermDiagnostics diagnostic;
diagnostic.diagnosis(eclState, deck, grid);
}
catch (const std::exception &e) {
std::cerr << "Program threw an exception: " << e.what() << "\n";
throw;
}

View File

@ -1,110 +0,0 @@
/*===========================================================================
//
// File: find_zero.cpp
//
// Created: 2013-04-29 11:58:29+0200
//
// Authors: Knut-Andreas Lie <Knut-Andreas.Lie@sintef.no>
// Halvor M. Nilsen <HalvorMoll.Nilsen@sintef.no>
// Atgeirr F. Rasmussen <atgeirr@sintef.no>
// Xavier Raynaud <Xavier.Raynaud@sintef.no>
// Bård Skaflestad <Bard.Skaflestad@sintef.no>
//
//==========================================================================*/
/*
Copyright 2013 SINTEF ICT, Applied Mathematics.
Copyright 2013 Statoil ASA.
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 3 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 <http://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <opm/autodiff/AutoDiff.hpp>
#include <iostream>
#include <cmath>
struct Func
{
template <typename T>
T operator()(T x) const
{
#if 1
T r = std::sqrt(std::cos(x * x) + x) - 1.2;
return r;
#else
return x;
// const int n = 6;
// double xv[6] = { 0.0, 0.2, 0.4, 0.6, 0.8, 1.0 };
// double yv[6] = { -0.5, -0.3, -0.1, 0.1, 0.3, 0.5 };
// int interv = -1;
// for (int i = 0; i < n; ++i) {
// if (x < xv[i]) {
// interv = i - 1;
// break;
// }
// }
// T t = (x - xv[interv])/(xv[interv+1] - xv[interv]);
// return (1.0 - t)*yv[interv] + t*yv[interv+1];
#endif
}
};
// template <class ErrorPolicy = ThrowOnError>
class Newton
{
public:
/// Implements a scalar Newton solve.
template <class Functor>
inline static double solve(const Functor& f,
const double initial_guess,
const int max_iter,
const double tolerance,
int& iterations_used)
{
double x = initial_guess;
iterations_used = 0;
typedef Opm::AutoDiff<double> AD;
while (std::abs(f(x)) > tolerance && ++iterations_used < max_iter) {
AD xfad = AD::variable(x);
AD rfad = f(xfad);
x = x - rfad.val()/rfad.der();
}
return x;
}
};
int main()
try
{
int iter = 0;
const double atol = 1.0e-13;
const double soln = Newton::solve(Func(), 0.1, 30, atol, iter);
std::cout.precision(16);
std::cout << "Solution is: " << soln
<< " using " << iter << " iterations." << '\n';
std::cout << " f(x) = " << Func()(soln) << '\n';
}
catch (const std::exception &e) {
std::cerr << "Program threw an exception: " << e.what() << "\n";
throw;
}

View File

@ -1,44 +0,0 @@
/*
Copyright 2013, 2014, 2015 SINTEF ICT, Applied Mathematics.
Copyright 2014 Dr. Blatt - HPC-Simulation-Software & Services
Copyright 2015 IRIS AS
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 3 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 <http://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif // HAVE_CONFIG_H
// Define making clear that the simulator supports AMG
#define FLOW_SUPPORT_AMG !defined(HAVE_UMFPACK)
#include <opm/grid/UnstructuredGrid.h>
#include <opm/autodiff/SimulatorFullyImplicitBlackoil.hpp>
#include <opm/autodiff/FlowMain.hpp>
// ----------------- Main program -----------------
int
main(int argc, char** argv)
{
typedef UnstructuredGrid Grid;
typedef Opm::SimulatorFullyImplicitBlackoil<Grid> Simulator;
Opm::FlowMain<Grid, Simulator> mainfunc;
return mainfunc.execute(argc, argv);
}

View File

@ -1,43 +0,0 @@
/*
Copyright 2016 SINTEF ICT, Applied Mathematics.
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 3 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 <http://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif // HAVE_CONFIG_H
#include <opm/grid/UnstructuredGrid.h>
#include <opm/autodiff/SimulatorSequentialBlackoil.hpp>
#include <opm/autodiff/FlowMainSequential.hpp>
#include <opm/autodiff/BlackoilPressureModel.hpp>
#include <opm/autodiff/BlackoilReorderingTransportModel.hpp>
#include <opm/autodiff/StandardWells.hpp>
// ----------------- Main program -----------------
int
main(int argc, char** argv)
{
typedef UnstructuredGrid Grid;
typedef Opm::StandardWells WellModel;
typedef Opm::SimulatorSequentialBlackoil<Grid, WellModel, Opm::BlackoilPressureModel, Opm::BlackoilReorderingTransportModel> Simulator;
Opm::FlowMainSequential<Grid, Simulator> mainfunc;
return mainfunc.execute(argc, argv);
}

View File

@ -1,43 +0,0 @@
/*
Copyright 2016 SINTEF ICT, Applied Mathematics.
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 3 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 <http://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif // HAVE_CONFIG_H
#include <opm/grid/UnstructuredGrid.h>
#include <opm/autodiff/SimulatorSequentialBlackoil.hpp>
#include <opm/autodiff/FlowMainSequential.hpp>
#include <opm/autodiff/BlackoilPressureModel.hpp>
#include <opm/autodiff/BlackoilTransportModel.hpp>
#include <opm/autodiff/StandardWells.hpp>
// ----------------- Main program -----------------
int
main(int argc, char** argv)
{
typedef UnstructuredGrid Grid;
typedef Opm::StandardWells WellModel;
typedef Opm::SimulatorSequentialBlackoil<Grid, WellModel, Opm::BlackoilPressureModel, Opm::BlackoilTransportModel> Simulator;
Opm::FlowMainSequential<Grid, Simulator> mainfunc;
return mainfunc.execute(argc, argv);
}

View File

@ -1,302 +0,0 @@
/*
Copyright 2012 SINTEF ICT, Applied Mathematics.
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 3 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 <http://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif // HAVE_CONFIG_H
#include <opm/core/pressure/FlowBCManager.hpp>
#include <opm/grid/UnstructuredGrid.h>
#include <opm/grid/GridManager.hpp>
#include <opm/grid/GridHelpers.hpp>
#include <opm/core/wells.h>
#include <opm/core/wells/WellsManager.hpp>
#include <opm/common/ErrorMacros.hpp>
#include <opm/core/simulator/initState.hpp>
#include <opm/core/simulator/SimulatorReport.hpp>
#include <opm/simulators/timestepping/SimulatorTimer.hpp>
#include <opm/core/utility/miscUtilities.hpp>
#include <opm/common/utility/parameters/ParameterGroup.hpp>
#include <opm/core/props/BlackoilPropertiesBasic.hpp>
#include <opm/core/props/BlackoilPropertiesFromDeck.hpp>
#include <opm/core/props/rock/RockCompressibility.hpp>
#include <opm/core/linalg/LinearSolverFactory.hpp>
#include <opm/core/simulator/BlackoilState.hpp>
#include <opm/core/simulator/WellState.hpp>
#include <opm/simulators/SimulatorCompressibleTwophase.hpp>
#include <opm/simulators/ensureDirectoryExists.hpp>
#include <opm/parser/eclipse/Parser/Parser.hpp>
#include <opm/parser/eclipse/Parser/ParseContext.hpp>
#include <opm/parser/eclipse/EclipseState/EclipseState.hpp>
#include <memory>
#include <boost/filesystem.hpp>
#include <algorithm>
#include <iostream>
#include <vector>
#include <numeric>
#include <fstream>
namespace
{
void warnIfUnusedParams(const Opm::ParameterGroup& param)
{
if (param.anyUnused()) {
std::cout << "-------------------- Unused parameters: --------------------\n";
param.displayUsage();
std::cout << "----------------------------------------------------------------" << std::endl;
}
}
} // anon namespace
// ----------------- Main program -----------------
int
main(int argc, char** argv)
try
{
using namespace Opm;
OpmLog::setupSimpleDefaultLogging(false, true, 10);
std::cout << "\n================ Test program for weakly compressible two-phase flow ===============\n\n";
ParameterGroup param(argc, argv, false);
std::cout << "--------------- Reading parameters ---------------" << std::endl;
// If we have a "deck_filename", grid and props will be read from that.
bool use_deck = param.has("deck_filename");
std::shared_ptr< EclipseState > eclipseState;
std::shared_ptr< Schedule > schedule;
std::unique_ptr<GridManager> grid;
std::unique_ptr<BlackoilPropertiesInterface> props;
std::unique_ptr<RockCompressibility> rock_comp;
std::unique_ptr<BlackoilState> state;
Parser parser;
// bool check_well_controls = false;
// int max_well_control_iterations = 0;
double gravity[3] = { 0.0 };
if (use_deck) {
ParseContext parseContext;
std::string deck_filename = param.get<std::string>("deck_filename");
auto deck = parser.parseFile(deck_filename , parseContext);
eclipseState.reset(new EclipseState(deck, parseContext));
schedule.reset( new Schedule(deck,
eclipseState->getInputGrid(),
eclipseState->get3DProperties(),
eclipseState->runspec(),
parseContext));
// Grid init
grid.reset(new GridManager(eclipseState->getInputGrid()));
{
const UnstructuredGrid& ug_grid = *(grid->c_grid());
state.reset( new BlackoilState( UgGridHelpers::numCells( ug_grid ) , UgGridHelpers::numFaces( ug_grid ) ,2));
// Rock and fluid init
props.reset(new BlackoilPropertiesFromDeck(deck, *eclipseState, ug_grid, param));
// check_well_controls = param.getDefault("check_well_controls", false);
// max_well_control_iterations = param.getDefault("max_well_control_iterations", 10);
// Rock compressibility.
rock_comp.reset(new RockCompressibility(*eclipseState));
// Gravity.
gravity[2] = deck.hasKeyword("NOGRAV") ? 0.0 : unit::gravity;
// Init state variables (saturation and pressure).
if (param.has("init_saturation")) {
initStateBasic(ug_grid, *props, param, gravity[2], *state);
} else {
initStateFromDeck(ug_grid, *props, deck, gravity[2], *state);
}
initBlackoilSurfvol(ug_grid, *props, *state);
}
} else {
// Grid init.
const int nx = param.getDefault("nx", 100);
const int ny = param.getDefault("ny", 100);
const int nz = param.getDefault("nz", 1);
const double dx = param.getDefault("dx", 1.0);
const double dy = param.getDefault("dy", 1.0);
const double dz = param.getDefault("dz", 1.0);
grid.reset(new GridManager(nx, ny, nz, dx, dy, dz));
{
const UnstructuredGrid& ug_grid = *(grid->c_grid());
// Rock and fluid init.
props.reset(new BlackoilPropertiesBasic(param, ug_grid.dimensions, UgGridHelpers::numCells( ug_grid )));
// State init
state.reset( new BlackoilState( UgGridHelpers::numCells( ug_grid ) , UgGridHelpers::numFaces( ug_grid ), 3));
// Rock compressibility.
rock_comp.reset(new RockCompressibility(param));
// Gravity.
gravity[2] = param.getDefault("gravity", 0.0);
// Init state variables (saturation and pressure).
initStateBasic(ug_grid, *props, param, gravity[2], *state);
initBlackoilSurfvol(ug_grid, *props, *state);
}
}
bool use_gravity = (gravity[0] != 0.0 || gravity[1] != 0.0 || gravity[2] != 0.0);
const double *grav = use_gravity ? &gravity[0] : 0;
// Initialising src
int num_cells = grid->c_grid()->number_of_cells;
std::vector<double> src(num_cells, 0.0);
if (use_deck) {
// Do nothing, wells will be the driving force, not source terms.
} else {
// Compute pore volumes, in order to enable specifying injection rate
// terms of total pore volume.
std::vector<double> porevol;
if (rock_comp->isActive()) {
computePorevolume(*grid->c_grid(), props->porosity(), *rock_comp, state->pressure(), porevol);
} else {
computePorevolume(*grid->c_grid(), props->porosity(), porevol);
}
const double tot_porevol_init = std::accumulate(porevol.begin(), porevol.end(), 0.0);
const double default_injection = use_gravity ? 0.0 : 0.1;
const double flow_per_sec = param.getDefault<double>("injected_porevolumes_per_day", default_injection)
*tot_porevol_init/unit::day;
src[0] = flow_per_sec;
src[num_cells - 1] = -flow_per_sec;
}
// Boundary conditions.
FlowBCManager bcs;
if (param.getDefault("use_pside", false)) {
int pside = param.get<int>("pside");
double pside_pressure = param.get<double>("pside_pressure");
bcs.pressureSide(*grid->c_grid(), FlowBCManager::Side(pside), pside_pressure);
}
// Linear solver.
LinearSolverFactory linsolver(param);
// Write parameters used for later reference.
bool output = param.getDefault("output", true);
std::ofstream epoch_os;
std::string output_dir;
if (output) {
output_dir =
param.getDefault("output_dir", std::string("output"));
ensureDirectoryExists(output_dir);
std::string filename = output_dir + "/epoch_timing.param";
epoch_os.open(filename.c_str(), std::fstream::trunc | std::fstream::out);
// open file to clean it. The file is appended to in SimulatorTwophase
filename = output_dir + "/step_timing.param";
std::fstream step_os(filename.c_str(), std::fstream::trunc | std::fstream::out);
step_os.close();
param.writeParam(output_dir + "/simulation.param");
}
std::cout << "\n\n================ Starting main simulation loop ===============\n";
SimulatorReport rep;
if (!use_deck) {
// Simple simulation without a deck.
WellsManager wells; // no wells.
SimulatorCompressibleTwophase simulator(param,
*grid->c_grid(),
*props,
rock_comp->isActive() ? rock_comp.get() : 0,
wells,
src,
bcs.c_bcs(),
linsolver,
grav);
SimulatorTimer simtimer;
simtimer.init(param);
warnIfUnusedParams(param);
WellState well_state;
well_state.init(0, *state);
rep = simulator.run(simtimer, *state, well_state);
} else {
// With a deck, we may have more epochs etc.
WellState well_state;
int step = 0;
SimulatorTimer simtimer;
// Use timer for last epoch to obtain total time.
const auto& timeMap = schedule->getTimeMap();
simtimer.init(timeMap);
const double total_time = simtimer.totalTime();
for (size_t reportStepIdx = 0; reportStepIdx < timeMap.numTimesteps(); ++reportStepIdx) {
simtimer.setCurrentStepNum(step);
simtimer.setTotalTime(total_time);
// Report on start of report step.
std::cout << "\n\n-------------- Starting report step " << reportStepIdx << " --------------"
<< "\n (number of steps: "
<< simtimer.numSteps() - step << ")\n\n" << std::flush;
// Create new wells, well_state
WellsManager wells(*eclipseState , *schedule, reportStepIdx , *grid->c_grid());
// @@@ HACK: we should really make a new well state and
// properly transfer old well state to it every report step,
// since number of wells may change etc.
if (reportStepIdx == 0) {
well_state.init(wells.c_wells(), *state);
}
// Create and run simulator.
SimulatorCompressibleTwophase simulator(param,
*grid->c_grid(),
*props,
rock_comp->isActive() ? rock_comp.get() : 0,
wells,
src,
bcs.c_bcs(),
linsolver,
grav);
if (reportStepIdx == 0) {
warnIfUnusedParams(param);
}
SimulatorReport epoch_rep = simulator.run(simtimer, *state, well_state);
if (output) {
epoch_rep.reportParam(epoch_os);
}
// Update total timing report and remember step number.
rep += epoch_rep;
step = simtimer.currentStepNum();
}
}
std::cout << "\n\n================ End of simulation ===============\n\n";
rep.report(std::cout);
if (output) {
std::string filename = output_dir + "/walltime.param";
std::fstream tot_os(filename.c_str(),std::fstream::trunc | std::fstream::out);
rep.reportParam(tot_os);
}
}
catch (const std::exception &e) {
std::cerr << "Program threw an exception: " << e.what() << "\n";
throw;
}

View File

@ -1,309 +0,0 @@
/*
Copyright 2013 SINTEF ICT, Applied Mathematics.
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 3 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 <http://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif // HAVE_CONFIG_H
#include <opm/core/pressure/FlowBCManager.hpp>
#include <opm/grid/UnstructuredGrid.h>
#include <opm/grid/GridManager.hpp>
#include <opm/core/wells.h>
#include <opm/core/wells/WellsManager.hpp>
#include <opm/common/ErrorMacros.hpp>
#include <opm/core/simulator/initState.hpp>
#include <opm/core/simulator/SimulatorReport.hpp>
#include <opm/simulators/timestepping/SimulatorTimer.hpp>
#include <opm/core/utility/miscUtilities.hpp>
#include <opm/common/utility/parameters/ParameterGroup.hpp>
#include <opm/core/props/IncompPropertiesBasic.hpp>
#include <opm/core/props/IncompPropertiesFromDeck.hpp>
#include <opm/core/props/rock/RockCompressibility.hpp>
#include <opm/core/linalg/LinearSolverFactory.hpp>
#include <opm/core/simulator/TwophaseState.hpp>
#include <opm/core/simulator/WellState.hpp>
#include <opm/autodiff/SimulatorIncompTwophaseAd.hpp>
#include <opm/parser/eclipse/Parser/Parser.hpp>
#include <opm/parser/eclipse/Parser/ParseContext.hpp>
#include <opm/parser/eclipse/EclipseState/EclipseState.hpp>
#include <opm/simulators/ensureDirectoryExists.hpp>
#include <boost/filesystem.hpp>
#include <memory>
#include <algorithm>
#include <iostream>
#include <vector>
#include <numeric>
#include <fstream>
namespace
{
void warnIfUnusedParams(const Opm::ParameterGroup& param)
{
if (param.anyUnused()) {
std::cout << "-------------------- Unused parameters: --------------------\n";
param.displayUsage();
std::cout << "----------------------------------------------------------------" << std::endl;
}
}
} // anon namespace
// ----------------- Main program -----------------
int
main(int argc, char** argv)
try
{
using namespace Opm;
OpmLog::setupSimpleDefaultLogging(false, true, 10);
std::cout << "\n================ Test program for incompressible two-phase flow ===============\n\n";
ParameterGroup param(argc, argv, false);
std::cout << "--------------- Reading parameters ---------------" << std::endl;
#if ! HAVE_SUITESPARSE_UMFPACK_H
// This is an extra check to intercept a potentially invalid request for the
// implicit transport solver as early as possible for the user.
{
const std::string transport_solver_type
= param.getDefault<std::string>("transport_solver_type", "ad");
if (transport_solver_type == "implicit") {
OPM_THROW(std::runtime_error, "Cannot use implicit transport solver without UMFPACK. "
"Either reconfigure opm-core with SuiteSparse/UMFPACK support and recompile, "
"or use the reordering solver (transport_solver_type=reorder).");
}
}
#endif
// If we have a "deck_filename", grid and props will be read from that.
bool use_deck = param.has("deck_filename");
std::shared_ptr< EclipseState > eclipseState;
std::shared_ptr< Schedule > schedule;
std::unique_ptr<GridManager> grid;
std::unique_ptr<IncompPropertiesInterface> props;
std::unique_ptr<RockCompressibility> rock_comp;
std::unique_ptr<TwophaseState> state;
double gravity[3] = { 0.0 };
if (use_deck) {
Parser parser;
ParseContext parseContext;
parseContext.update(ParseContext::PARSE_MISSING_DIMS_KEYWORD, InputError::WARN);
std::string deck_filename = param.get<std::string>("deck_filename");
auto deck = parser.parseFile(deck_filename, parseContext);
eclipseState.reset(new EclipseState(deck, parseContext));
schedule.reset( new Schedule(deck,
eclipseState->getInputGrid(),
eclipseState->get3DProperties(),
eclipseState->runspec(),
parseContext));
// Grid init
grid.reset(new GridManager(eclipseState->getInputGrid()));
{
const UnstructuredGrid& ug_grid = *(grid->c_grid());
// Rock and fluid init
props.reset(new IncompPropertiesFromDeck(deck, *eclipseState, ug_grid));
state.reset( new TwophaseState( UgGridHelpers::numCells( ug_grid ) , UgGridHelpers::numFaces( ug_grid )));
// Rock compressibility.
rock_comp.reset(new RockCompressibility(*eclipseState));
// Gravity.
gravity[2] = deck.hasKeyword("NOGRAV") ? 0.0 : unit::gravity;
// Init state variables (saturation and pressure).
if (param.has("init_saturation")) {
initStateBasic(ug_grid, *props, param, gravity[2], *state);
} else {
initStateFromDeck(ug_grid, *props, deck, gravity[2], *state);
}
}
} else {
// Grid init.
const int nx = param.getDefault("nx", 100);
const int ny = param.getDefault("ny", 100);
const int nz = param.getDefault("nz", 1);
const double dx = param.getDefault("dx", 1.0);
const double dy = param.getDefault("dy", 1.0);
const double dz = param.getDefault("dz", 1.0);
grid.reset(new GridManager(nx, ny, nz, dx, dy, dz));
{
const UnstructuredGrid& ug_grid = *(grid->c_grid());
// Rock and fluid init.
props.reset(new IncompPropertiesBasic(param, ug_grid.dimensions, UgGridHelpers::numCells( ug_grid )));
state.reset( new TwophaseState( UgGridHelpers::numCells( ug_grid ) , UgGridHelpers::numFaces( ug_grid )));
// Rock compressibility.
rock_comp.reset(new RockCompressibility(param));
// Gravity.
gravity[2] = param.getDefault("gravity", 0.0);
// Init state variables (saturation and pressure).
initStateBasic(ug_grid, *props, param, gravity[2], *state);
}
}
// Warn if gravity but no density difference.
bool use_gravity = (gravity[0] != 0.0 || gravity[1] != 0.0 || gravity[2] != 0.0);
if (use_gravity) {
if (props->density()[0] == props->density()[1]) {
std::cout << "**** Warning: nonzero gravity, but zero density difference." << std::endl;
}
}
const double *grav = use_gravity ? &gravity[0] : 0;
// Initialising src
int num_cells = grid->c_grid()->number_of_cells;
std::vector<double> src(num_cells, 0.0);
if (use_deck) {
// Do nothing, wells will be the driving force, not source terms.
} else {
// Compute pore volumes, in order to enable specifying injection rate
// terms of total pore volume.
std::vector<double> porevol;
if (rock_comp->isActive()) {
computePorevolume(*grid->c_grid(), props->porosity(), *rock_comp, state->pressure(), porevol);
} else {
computePorevolume(*grid->c_grid(), props->porosity(), porevol);
}
const double tot_porevol_init = std::accumulate(porevol.begin(), porevol.end(), 0.0);
const double default_injection = use_gravity ? 0.0 : 0.1;
const double flow_per_sec = param.getDefault<double>("injected_porevolumes_per_day", default_injection)
*tot_porevol_init/unit::day;
src[0] = flow_per_sec;
src[num_cells - 1] = -flow_per_sec;
}
// Boundary conditions.
FlowBCManager bcs;
if (param.getDefault("use_pside", false)) {
int pside = param.get<int>("pside");
double pside_pressure = param.get<double>("pside_pressure");
bcs.pressureSide(*grid->c_grid(), FlowBCManager::Side(pside), pside_pressure);
}
// Linear solver.
LinearSolverFactory linsolver(param);
// Write parameters used for later reference.
bool output = param.getDefault("output", true);
std::ofstream epoch_os;
std::string output_dir;
if (output) {
output_dir =
param.getDefault("output_dir", std::string("output"));
ensureDirectoryExists(output_dir);
std::string filename = output_dir + "/epoch_timing.param";
epoch_os.open(filename.c_str(), std::fstream::trunc | std::fstream::out);
// open file to clean it. The file is appended to in SimulatorTwophase
filename = output_dir + "/step_timing.param";
std::fstream step_os(filename.c_str(), std::fstream::trunc | std::fstream::out);
step_os.close();
param.writeParam(output_dir + "/simulation.param");
}
std::cout << "\n\n================ Starting main simulation loop ===============\n";
SimulatorReport rep;
if (!use_deck) {
// Simple simulation without a deck.
WellsManager wells; // no wells.
SimulatorIncompTwophaseAd simulator(param,
*grid->c_grid(),
*props,
rock_comp->isActive() ? rock_comp.get() : 0,
wells,
src,
bcs.c_bcs(),
linsolver,
grav);
SimulatorTimer simtimer;
simtimer.init(param);
warnIfUnusedParams(param);
WellState well_state;
well_state.init(0, *state);
rep = simulator.run(simtimer, *state, well_state);
} else {
// With a deck, we may have more report steps etc.
WellState well_state;
const auto& timeMap = schedule->getTimeMap();
SimulatorTimer simtimer;
for (size_t reportStepIdx = 0; reportStepIdx < timeMap.numTimesteps(); ++reportStepIdx) {
// Report on start of report step.
std::cout << "\n\n-------------- Starting report step " << reportStepIdx << " --------------"
<< "\n (number of steps left: "
<< timeMap.numTimesteps() - reportStepIdx << ")\n\n" << std::flush;
// Create new wells, well_state
WellsManager wells(*eclipseState , *schedule, reportStepIdx , *grid->c_grid());
// @@@ HACK: we should really make a new well state and
// properly transfer old well state to it every report step,
// since number of wells may change etc.
if (reportStepIdx == 0) {
well_state.init(wells.c_wells(), *state);
}
simtimer.setCurrentStepNum(reportStepIdx);
// Create and run simulator.
SimulatorIncompTwophaseAd simulator(param,
*grid->c_grid(),
*props,
rock_comp->isActive() ? rock_comp.get() : 0,
wells,
src,
bcs.c_bcs(),
linsolver,
grav);
if (reportStepIdx == 0) {
warnIfUnusedParams(param);
}
SimulatorReport epoch_rep = simulator.run(simtimer, *state, well_state);
if (output) {
epoch_rep.reportParam(epoch_os);
}
// Update total timing report and remember step number.
rep += epoch_rep;
}
}
std::cout << "\n\n================ End of simulation ===============\n\n";
rep.report(std::cout);
if (output) {
std::string filename = output_dir + "/walltime.param";
std::fstream tot_os(filename.c_str(),std::fstream::trunc | std::fstream::out);
rep.reportParam(tot_os);
}
}
catch (const std::exception &e) {
std::cerr << "Program threw an exception: " << e.what() << "\n";
throw;
}

View File

@ -1,335 +0,0 @@
/*
Copyright 2012 SINTEF ICT, Applied Mathematics.
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 3 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 <http://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif // HAVE_CONFIG_H
#include <opm/core/pressure/FlowBCManager.hpp>
#include <opm/grid/UnstructuredGrid.h>
#include <opm/grid/GridManager.hpp>
#include <opm/core/wells.h>
#include <opm/core/wells/WellsManager.hpp>
#include <opm/common/ErrorMacros.hpp>
#include <opm/core/simulator/initState.hpp>
#include <opm/core/simulator/SimulatorReport.hpp>
#include <opm/simulators/timestepping/SimulatorTimer.hpp>
#include <opm/core/utility/miscUtilities.hpp>
#include <opm/common/utility/parameters/ParameterGroup.hpp>
#include <opm/core/props/BlackoilPropertiesBasic.hpp>
#include <opm/core/props/BlackoilPropertiesFromDeck.hpp>
#include <opm/core/props/rock/RockCompressibility.hpp>
#include <opm/core/linalg/LinearSolverFactory.hpp>
#include <opm/polymer/PolymerBlackoilState.hpp>
#include <opm/core/simulator/WellState.hpp>
#include <opm/polymer/SimulatorCompressiblePolymer.hpp>
#include <opm/polymer/PolymerInflow.hpp>
#include <opm/polymer/PolymerProperties.hpp>
#include <opm/parser/eclipse/Parser/Parser.hpp>
#include <opm/parser/eclipse/Parser/ParseContext.hpp>
#include <opm/parser/eclipse/EclipseState/EclipseState.hpp>
#include <opm/simulators/ensureDirectoryExists.hpp>
#include <boost/scoped_ptr.hpp>
#include <boost/filesystem.hpp>
#include <algorithm>
#include <iostream>
#include <vector>
#include <numeric>
namespace
{
void warnIfUnusedParams(const Opm::ParameterGroup& param)
{
if (param.anyUnused()) {
std::cout << "-------------------- Unused parameters: --------------------\n";
param.displayUsage();
std::cout << "----------------------------------------------------------------" << std::endl;
}
}
} // anon namespace
// ----------------- Main program -----------------
int
main(int argc, char** argv)
try
{
using namespace Opm;
OpmLog::setupSimpleDefaultLogging(false, true, 10);
std::cout << "\n================ Test program for weakly compressible two-phase flow with polymer ===============\n\n";
ParameterGroup param(argc, argv, false);
std::cout << "--------------- Reading parameters ---------------" << std::endl;
// If we have a "deck_filename", grid and props will be read from that.
bool use_deck = param.has("deck_filename");
boost::scoped_ptr<GridManager> grid;
boost::scoped_ptr<BlackoilPropertiesInterface> props;
boost::scoped_ptr<RockCompressibility> rock_comp;
std::unique_ptr<PolymerBlackoilState> state;
Opm::PolymerProperties poly_props;
std::unique_ptr<Opm::Deck> deck;
std::unique_ptr< EclipseState > eclipseState;
std::unique_ptr< Schedule> schedule;
// bool check_well_controls = false;
// int max_well_control_iterations = 0;
double gravity[3] = { 0.0 };
if (use_deck) {
std::string deck_filename = param.get<std::string>("deck_filename");
Parser parser;
Opm::ParseContext parseContext({{ ParseContext::PARSE_RANDOM_SLASH , InputError::IGNORE }});
deck.reset(new Deck(parser.parseFile(deck_filename , parseContext)));
eclipseState.reset( new EclipseState(*deck , parseContext) );
schedule.reset( new Schedule(*deck, eclipseState->getInputGrid(), eclipseState->get3DProperties(), eclipseState->runspec(), parseContext ));
// Grid init
grid.reset(new GridManager(eclipseState->getInputGrid()));
{
const UnstructuredGrid& ug_grid = *(grid->c_grid());
// Rock and fluid init
props.reset(new BlackoilPropertiesFromDeck(*deck, *eclipseState, ug_grid));
// check_well_controls = param.getDefault("check_well_controls", false);
// max_well_control_iterations = param.getDefault("max_well_control_iterations", 10);
state.reset( new PolymerBlackoilState( UgGridHelpers::numCells( ug_grid ) , UgGridHelpers::numFaces( ug_grid ), 2));
// Rock compressibility.
rock_comp.reset(new RockCompressibility(*eclipseState));
// Gravity.
gravity[2] = deck->hasKeyword("NOGRAV") ? 0.0 : unit::gravity;
// Init state variables (saturation and pressure).
if (param.has("init_saturation")) {
initStateBasic(ug_grid, *props, param, gravity[2], *state);
} else {
initStateFromDeck(ug_grid, *props, *deck, gravity[2], *state);
}
initBlackoilSurfvol(ug_grid, *props, *state);
// Init polymer properties.
poly_props.readFromDeck(*deck, *eclipseState);
}
} else {
// Grid init.
const int nx = param.getDefault("nx", 100);
const int ny = param.getDefault("ny", 100);
const int nz = param.getDefault("nz", 1);
const double dx = param.getDefault("dx", 1.0);
const double dy = param.getDefault("dy", 1.0);
const double dz = param.getDefault("dz", 1.0);
grid.reset(new GridManager(nx, ny, nz, dx, dy, dz));
{
const UnstructuredGrid& ug_grid = *(grid->c_grid());
// Rock and fluid init.
props.reset(new BlackoilPropertiesBasic(param, ug_grid.dimensions, UgGridHelpers::numCells( ug_grid )));
state.reset( new PolymerBlackoilState( UgGridHelpers::numCells( ug_grid ) , UgGridHelpers::numFaces( ug_grid ) , 2));
// Rock compressibility.
rock_comp.reset(new RockCompressibility(param));
// Gravity.
gravity[2] = param.getDefault("gravity", 0.0);
// Init state variables (saturation and pressure).
initStateBasic(ug_grid, *props, param, gravity[2], *state);
initBlackoilSurfvol(ug_grid, *props, *state);
// Init Polymer state
if (param.has("poly_init")) {
double poly_init = param.getDefault("poly_init", 0.0);
for (int cell = 0; cell < UgGridHelpers::numCells( ug_grid ); ++cell) {
double smin[2], smax[2];
auto& saturation = state->saturation();
auto& concentration = state->getCellData( state->CONCENTRATION );
auto& max_concentration = state->getCellData( state->CMAX );
props->satRange(1, &cell, smin, smax);
if (saturation[2*cell] > 0.5*(smin[0] + smax[0])) {
concentration[cell] = poly_init;
max_concentration[cell] = poly_init;
} else {
saturation[2*cell + 0] = 0.;
saturation[2*cell + 1] = 1.;
concentration[cell] = 0.;
max_concentration[cell] = 0.;
}
}
}
}
// Init polymer properties.
// Setting defaults to provide a simple example case.
double c_max = param.getDefault("c_max_limit", 5.0);
double mix_param = param.getDefault("mix_param", 1.0);
double rock_density = param.getDefault("rock_density", 1000.0);
double dead_pore_vol = param.getDefault("dead_pore_vol", 0.15);
double res_factor = param.getDefault("res_factor", 1.) ; // res_factor = 1 gives no change in permeability
double c_max_ads = param.getDefault("c_max_ads", 1.);
int ads_index = param.getDefault<int>("ads_index", Opm::PolymerProperties::NoDesorption);
std::vector<double> c_vals_visc(2, -1e100);
c_vals_visc[0] = 0.0;
c_vals_visc[1] = 7.0;
std::vector<double> visc_mult_vals(2, -1e100);
visc_mult_vals[0] = 1.0;
// poly_props.visc_mult_vals[1] = param.getDefault("c_max_viscmult", 30.0);
visc_mult_vals[1] = 20.0;
std::vector<double> c_vals_ads(3, -1e100);
c_vals_ads[0] = 0.0;
c_vals_ads[1] = 2.0;
c_vals_ads[2] = 8.0;
std::vector<double> ads_vals(3, -1e100);
ads_vals[0] = 0.0;
ads_vals[1] = 0.0015;
ads_vals[2] = 0.0025;
// ads_vals[1] = 0.0;
// ads_vals[2] = 0.0;
std::vector<double> water_vel_vals(2, -1e100);
water_vel_vals[0] = 0.0;
water_vel_vals[1] = 10.0;
std::vector<double> shear_vrf_vals(2, -1e100);
shear_vrf_vals[0] = 1.0;
shear_vrf_vals[1] = 1.0;
poly_props.set(c_max, mix_param, rock_density, dead_pore_vol, res_factor, c_max_ads,
static_cast<Opm::PolymerProperties::AdsorptionBehaviour>(ads_index),
c_vals_visc, visc_mult_vals, c_vals_ads, ads_vals, water_vel_vals, shear_vrf_vals);
}
bool use_gravity = (gravity[0] != 0.0 || gravity[1] != 0.0 || gravity[2] != 0.0);
const double *grav = use_gravity ? &gravity[0] : 0;
// Linear solver.
LinearSolverFactory linsolver(param);
// Write parameters used for later reference.
bool output = param.getDefault("output", true);
if (output) {
std::string output_dir =
param.getDefault("output_dir", std::string("output"));
ensureDirectoryExists(output_dir);
param.writeParam(output_dir + "/simulation.param");
}
std::cout << "\n\n================ Starting main simulation loop ===============\n"
<< std::flush;
SimulatorReport rep;
if (!use_deck) {
// Simple simulation without a deck.
PolymerInflowBasic polymer_inflow(param.getDefault("poly_start_days", 300.0)*Opm::unit::day,
param.getDefault("poly_end_days", 800.0)*Opm::unit::day,
param.getDefault("poly_amount", poly_props.cMax()));
WellsManager wells;
SimulatorCompressiblePolymer simulator(param,
*grid->c_grid(),
*props,
poly_props,
rock_comp->isActive() ? rock_comp.get() : 0,
wells,
polymer_inflow,
linsolver,
grav);
SimulatorTimer simtimer;
simtimer.init(param);
warnIfUnusedParams(param);
WellState well_state;
well_state.init(0, *state);
rep = simulator.run(simtimer, *state, well_state);
} else {
// With a deck, we may have more epochs etc.
WellState well_state;
int step = 0;
Opm::TimeMap timeMap(*deck);
SimulatorTimer simtimer;
simtimer.init(timeMap);
// Check for WPOLYMER presence in last report step to decide
// polymer injection control type.
const bool use_wpolymer = deck->hasKeyword("WPOLYMER");
if (use_wpolymer) {
if (param.has("poly_start_days")) {
OPM_MESSAGE("Warning: Using WPOLYMER to control injection since it was found in deck. "
"You seem to be trying to control it via parameter poly_start_days (etc.) as well.");
}
}
for (size_t reportStepIdx = 0; reportStepIdx < timeMap.numTimesteps(); ++reportStepIdx) {
simtimer.setCurrentStepNum(reportStepIdx);
// Report on start of report step.
std::cout << "\n\n-------------- Starting report step " << reportStepIdx << " --------------"
<< "\n (number of remaining steps: "
<< simtimer.numSteps() - step << ")\n\n" << std::flush;
// Create new wells, polymer inflow controls.
eclipseState.reset( new EclipseState( *deck ) );
WellsManager wells(*eclipseState , *schedule, reportStepIdx , *grid->c_grid());
boost::scoped_ptr<PolymerInflowInterface> polymer_inflow;
if (use_wpolymer) {
if (wells.c_wells() == 0) {
OPM_THROW(std::runtime_error, "Cannot control polymer injection via WPOLYMER without wells.");
}
polymer_inflow.reset(new PolymerInflowFromDeck( *schedule, *wells.c_wells(), props->numCells(), simtimer.currentStepNum()));
} else {
polymer_inflow.reset(new PolymerInflowBasic(param.getDefault("poly_start_days", 300.0)*Opm::unit::day,
param.getDefault("poly_end_days", 800.0)*Opm::unit::day,
param.getDefault("poly_amount", poly_props.cMax())));
}
// @@@ HACK: we should really make a new well state and
// properly transfer old well state to it every report step,
// since number of wells may change etc.
if (reportStepIdx == 0) {
well_state.init(wells.c_wells(), *state);
}
// Create and run simulator.
SimulatorCompressiblePolymer simulator(param,
*grid->c_grid(),
*props,
poly_props,
rock_comp->isActive() ? rock_comp.get() : 0,
wells,
*polymer_inflow,
linsolver,
grav);
if (reportStepIdx == 0) {
warnIfUnusedParams(param);
}
SimulatorReport epoch_rep = simulator.run(simtimer, *state, well_state);
// Update total timing report and remember step number.
rep += epoch_rep;
step = simtimer.currentStepNum();
}
}
std::cout << "\n\n================ End of simulation ===============\n\n";
rep.report(std::cout);
}
catch (const std::exception &e) {
std::cerr << "Program threw an exception: " << e.what() << "\n";
throw;
}

View File

@ -1,370 +0,0 @@
/*
Copyright 2012 SINTEF ICT, Applied Mathematics.
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 3 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 <http://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif // HAVE_CONFIG_H
#include <opm/core/pressure/FlowBCManager.hpp>
#include <opm/grid/UnstructuredGrid.h>
#include <opm/grid/GridManager.hpp>
#include <opm/core/wells.h>
#include <opm/core/wells/WellsManager.hpp>
#include <opm/common/ErrorMacros.hpp>
#include <opm/core/simulator/initState.hpp>
#include <opm/core/simulator/SimulatorReport.hpp>
#include <opm/simulators/timestepping/SimulatorTimer.hpp>
#include <opm/core/utility/miscUtilities.hpp>
#include <opm/common/utility/parameters/ParameterGroup.hpp>
#include <opm/core/props/IncompPropertiesBasic.hpp>
#include <opm/core/props/IncompPropertiesFromDeck.hpp>
#include <opm/core/props/rock/RockCompressibility.hpp>
#include <opm/core/linalg/LinearSolverFactory.hpp>
#include <opm/polymer/PolymerState.hpp>
#include <opm/core/simulator/WellState.hpp>
#include <opm/polymer/SimulatorPolymer.hpp>
#include <opm/polymer/PolymerInflow.hpp>
#include <opm/polymer/PolymerProperties.hpp>
#include <opm/parser/eclipse/Parser/Parser.hpp>
#include <opm/parser/eclipse/Parser/ParseContext.hpp>
#include <opm/parser/eclipse/EclipseState/EclipseState.hpp>
#include <opm/simulators/ensureDirectoryExists.hpp>
#include <boost/scoped_ptr.hpp>
#include <boost/filesystem.hpp>
#include <algorithm>
#include <iostream>
#include <vector>
#include <numeric>
namespace
{
void warnIfUnusedParams(const Opm::ParameterGroup& param)
{
if (param.anyUnused()) {
std::cout << "-------------------- Unused parameters: --------------------\n";
param.displayUsage();
std::cout << "----------------------------------------------------------------" << std::endl;
}
}
} // anon namespace
// ----------------- Main program -----------------
int
main(int argc, char** argv)
try
{
using namespace Opm;
OpmLog::setupSimpleDefaultLogging(false, true, 10);
std::cout << "\n================ Test program for incompressible two-phase flow with polymer ===============\n\n";
ParameterGroup param(argc, argv, false);
std::cout << "--------------- Reading parameters ---------------" << std::endl;
// If we have a "deck_filename", grid and props will be read from that.
bool use_deck = param.has("deck_filename");
std::unique_ptr<Deck> deck;
boost::scoped_ptr<GridManager> grid;
boost::scoped_ptr<IncompPropertiesInterface> props;
boost::scoped_ptr<RockCompressibility> rock_comp;
std::shared_ptr< EclipseState > eclipseState;
std::shared_ptr<Schedule> schedule;
std::unique_ptr<PolymerState> state;
Opm::PolymerProperties poly_props;
// bool check_well_controls = false;
// int max_well_control_iterations = 0;
double gravity[3] = { 0.0 };
if (use_deck) {
std::string deck_filename = param.get<std::string>("deck_filename");
Opm::ParseContext parseContext({{ ParseContext::PARSE_RANDOM_SLASH , InputError::IGNORE }});
Parser parser;
deck.reset(new Deck(parser.parseFile(deck_filename , parseContext)));
eclipseState.reset(new Opm::EclipseState(*deck , parseContext));
schedule.reset( new Opm::Schedule(*deck, eclipseState->getInputGrid(), eclipseState->get3DProperties(), eclipseState->runspec(), parseContext));
// Grid init
grid.reset(new GridManager(eclipseState->getInputGrid()));
{
const UnstructuredGrid& ug_grid = *(grid->c_grid());
// Rock and fluid init
props.reset(new IncompPropertiesFromDeck(*deck, *eclipseState, ug_grid ));
// check_well_controls = param.getDefault("check_well_controls", false);
// max_well_control_iterations = param.getDefault("max_well_control_iterations", 10);
state.reset( new PolymerState( UgGridHelpers::numCells( ug_grid ) , UgGridHelpers::numFaces( ug_grid ), 2));
// Rock compressibility.
rock_comp.reset(new RockCompressibility(*eclipseState));
// Gravity.
gravity[2] = deck->hasKeyword("NOGRAV") ? 0.0 : unit::gravity;
// Init state variables (saturation and pressure).
if (param.has("init_saturation")) {
initStateBasic(ug_grid, *props, param, gravity[2], *state);
} else {
initStateFromDeck(ug_grid, *props, *deck, gravity[2], *state);
}
// Init polymer properties.
poly_props.readFromDeck(*deck, *eclipseState);
}
} else {
// Grid init.
const int nx = param.getDefault("nx", 100);
const int ny = param.getDefault("ny", 100);
const int nz = param.getDefault("nz", 1);
const double dx = param.getDefault("dx", 1.0);
const double dy = param.getDefault("dy", 1.0);
const double dz = param.getDefault("dz", 1.0);
grid.reset(new GridManager(nx, ny, nz, dx, dy, dz));
{
const UnstructuredGrid& ug_grid = *(grid->c_grid());
// Rock and fluid init.
props.reset(new IncompPropertiesBasic(param, ug_grid.dimensions, UgGridHelpers::numCells( ug_grid )));;
state.reset( new PolymerState( UgGridHelpers::numCells( ug_grid ) , UgGridHelpers::numFaces( ug_grid ) , 2));
// Rock compressibility.
rock_comp.reset(new RockCompressibility(param));
// Gravity.
gravity[2] = param.getDefault("gravity", 0.0);
// Init state variables (saturation and pressure).
initStateBasic(ug_grid, *props, param, gravity[2], *state);
// Init Polymer state
if (param.has("poly_init")) {
double poly_init = param.getDefault("poly_init", 0.0);
for (int cell = 0; cell < UgGridHelpers::numCells( ug_grid ); ++cell) {
double smin[2], smax[2];
auto& saturation = state->saturation();
auto& concentration = state->getCellData( state->CONCENTRATION );
auto& max_concentration = state->getCellData( state->CMAX );
props->satRange(1, &cell, smin, smax);
if (saturation[2*cell] > 0.5*(smin[0] + smax[0])) {
concentration[cell] = poly_init;
max_concentration[cell] = poly_init;
} else {
saturation[2*cell + 0] = 0.;
saturation[2*cell + 1] = 1.;
concentration[cell] = 0.;
max_concentration[cell] = 0.;
}
}
}
}
// Init polymer properties.
// Setting defaults to provide a simple example case.
double c_max = param.getDefault("c_max_limit", 5.0);
double mix_param = param.getDefault("mix_param", 1.0);
double rock_density = param.getDefault("rock_density", 1000.0);
double dead_pore_vol = param.getDefault("dead_pore_vol", 0.15);
double res_factor = param.getDefault("res_factor", 1.) ; // res_factor = 1 gives no change in permeability
double c_max_ads = param.getDefault("c_max_ads", 1.);
int ads_index = param.getDefault<int>("ads_index", Opm::PolymerProperties::NoDesorption);
std::vector<double> c_vals_visc(2, -1e100);
c_vals_visc[0] = 0.0;
c_vals_visc[1] = 7.0;
std::vector<double> visc_mult_vals(2, -1e100);
visc_mult_vals[0] = 1.0;
// poly_props.visc_mult_vals[1] = param.getDefault("c_max_viscmult", 30.0);
visc_mult_vals[1] = 20.0;
std::vector<double> c_vals_ads(3, -1e100);
c_vals_ads[0] = 0.0;
c_vals_ads[1] = 2.0;
c_vals_ads[2] = 8.0;
std::vector<double> ads_vals(3, -1e100);
ads_vals[0] = 0.0;
ads_vals[1] = 0.0015;
ads_vals[2] = 0.0025;
// ads_vals[1] = 0.0;
// ads_vals[2] = 0.0;
std::vector<double> water_vel_vals(2, -1e100);
water_vel_vals[0] = 0.0;
water_vel_vals[1] = 10.0;
std::vector<double> shear_vrf_vals(2, -1e100);
shear_vrf_vals[0] = 1.0;
shear_vrf_vals[1] = 1.0;
poly_props.set(c_max, mix_param, rock_density, dead_pore_vol, res_factor, c_max_ads,
static_cast<Opm::PolymerProperties::AdsorptionBehaviour>(ads_index),
c_vals_visc, visc_mult_vals, c_vals_ads, ads_vals, water_vel_vals, shear_vrf_vals);
}
// Warn if gravity but no density difference.
bool use_gravity = (gravity[0] != 0.0 || gravity[1] != 0.0 || gravity[2] != 0.0);
if (use_gravity) {
if (props->density()[0] == props->density()[1]) {
std::cout << "**** Warning: nonzero gravity, but zero density difference." << std::endl;
}
}
const double *grav = use_gravity ? &gravity[0] : 0;
// Initialising src
int num_cells = grid->c_grid()->number_of_cells;
std::vector<double> src(num_cells, 0.0);
if (use_deck) {
// Do nothing, wells will be the driving force, not source terms.
} else {
// Compute pore volumes, in order to enable specifying injection rate
// terms of total pore volume.
std::vector<double> porevol;
if (rock_comp->isActive()) {
computePorevolume(*grid->c_grid(), props->porosity(), *rock_comp, state->pressure(), porevol);
} else {
computePorevolume(*grid->c_grid(), props->porosity(), porevol);
}
const double tot_porevol_init = std::accumulate(porevol.begin(), porevol.end(), 0.0);
const double default_injection = use_gravity ? 0.0 : 0.1;
const double flow_per_sec = param.getDefault<double>("injected_porevolumes_per_day", default_injection)
*tot_porevol_init/unit::day;
src[0] = flow_per_sec;
src[num_cells - 1] = -flow_per_sec;
}
// Boundary conditions.
FlowBCManager bcs;
if (param.getDefault("use_pside", false)) {
int pside = param.get<int>("pside");
double pside_pressure = param.get<double>("pside_pressure");
bcs.pressureSide(*grid->c_grid(), FlowBCManager::Side(pside), pside_pressure);
}
// Linear solver.
LinearSolverFactory linsolver(param);
// Write parameters used for later reference.
bool output = param.getDefault("output", true);
if (output) {
std::string output_dir =
param.getDefault("output_dir", std::string("output"));
ensureDirectoryExists(output_dir);
param.writeParam(output_dir + "/simulation.param");
}
std::cout << "\n\n================ Starting main simulation loop ===============\n"
<< std::flush;
SimulatorReport rep;
if (!use_deck) {
// Simple simulation without a deck.
PolymerInflowBasic polymer_inflow(param.getDefault("poly_start_days", 300.0)*Opm::unit::day,
param.getDefault("poly_end_days", 800.0)*Opm::unit::day,
param.getDefault("poly_amount", poly_props.cMax()));
WellsManager wells;
SimulatorPolymer simulator(param,
*grid->c_grid(),
*props,
poly_props,
rock_comp->isActive() ? rock_comp.get() : 0,
wells,
polymer_inflow,
src,
bcs.c_bcs(),
linsolver,
grav);
SimulatorTimer simtimer;
simtimer.init(param);
warnIfUnusedParams(param);
WellState well_state;
well_state.init(0, *state);
rep = simulator.run(simtimer, *state, well_state);
} else {
// With a deck, we may have more epochs etc.
WellState well_state;
int step = 0;
const auto& timeMap = schedule->getTimeMap();
SimulatorTimer simtimer;
simtimer.init(timeMap);
// Check for WPOLYMER presence in last epoch to decide
// polymer injection control type.
const bool use_wpolymer = deck->hasKeyword("WPOLYMER");
if (use_wpolymer) {
if (param.has("poly_start_days")) {
OPM_MESSAGE("Warning: Using WPOLYMER to control injection since it was found in deck. "
"You seem to be trying to control it via parameter poly_start_days (etc.) as well.");
}
}
for (size_t reportStepIdx = 0; reportStepIdx < timeMap.numTimesteps(); ++reportStepIdx) {
simtimer.setCurrentStepNum(reportStepIdx);
// Report on start of report step.
std::cout << "\n\n-------------- Starting report step " << reportStepIdx << " --------------"
<< "\n (number of remaining steps: "
<< simtimer.numSteps() - step << ")\n\n" << std::flush;
// Create new wells, polymer inflow controls.
WellsManager wells(*eclipseState , *schedule, reportStepIdx , *grid->c_grid());
boost::scoped_ptr<PolymerInflowInterface> polymer_inflow;
if (use_wpolymer) {
if (wells.c_wells() == 0) {
OPM_THROW(std::runtime_error, "Cannot control polymer injection via WPOLYMER without wells.");
}
polymer_inflow.reset(new PolymerInflowFromDeck(*schedule, *wells.c_wells(), props->numCells(), simtimer.currentStepNum()));
} else {
polymer_inflow.reset(new PolymerInflowBasic(param.getDefault("poly_start_days", 300.0)*Opm::unit::day,
param.getDefault("poly_end_days", 800.0)*Opm::unit::day,
param.getDefault("poly_amount", poly_props.cMax())));
}
// @@@ HACK: we should really make a new well state and
// properly transfer old well state to it every report step,
// since number of wells may change etc.
if (reportStepIdx == 0) {
well_state.init(wells.c_wells(), *state);
}
// Create and run simulator.
SimulatorPolymer simulator(param,
*grid->c_grid(),
*props,
poly_props,
rock_comp->isActive() ? rock_comp.get() : 0,
wells,
*polymer_inflow,
src,
bcs.c_bcs(),
linsolver,
grav);
if (reportStepIdx == 0) {
warnIfUnusedParams(param);
}
SimulatorReport epoch_rep = simulator.run(simtimer, *state, well_state);
// Update total timing report and remember step number.
rep += epoch_rep;
step = simtimer.currentStepNum();
}
}
std::cout << "\n\n================ End of simulation ===============\n\n";
rep.report(std::cout);
}
catch (const std::exception &e) {
std::cerr << "Program threw an exception: " << e.what() << "\n";
throw;
}

View File

@ -1,311 +0,0 @@
/*
Copyright 2013 SINTEF ICT, Applied Mathematics.
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 3 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 <http://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <opm/autodiff/AutoDiffBlock.hpp>
#include <opm/autodiff/AutoDiffHelpers.hpp>
#include <opm/grid/UnstructuredGrid.h>
#include <opm/grid/GridManager.hpp>
#include <opm/core/props/IncompPropertiesBasic.hpp>
#include <opm/parser/eclipse/Units/Units.hpp>
#include <opm/grid/utility/StopWatch.hpp>
#include <opm/grid/transmissibility/trans_tpfa.h>
#include <opm/common/utility/platform_dependent/disable_warnings.h>
#if HAVE_SUITESPARSE_UMFPACK_H
#include <Eigen/UmfPackSupport>
#else
#include <Eigen/IterativeLinearSolvers>
#endif
#include <opm/common/utility/platform_dependent/reenable_warnings.h>
#include <iostream>
#include <cstdlib>
/*
Equations for incompressible two-phase flow.
Using s and p as variables:
PV (s_i - s0_i) / dt + sum_{j \in U(i)} f(s_j) v_{ij} + sum_{j in D(i) f(s_i) v_{ij} = qw_i
where
v_{ij} = totmob_ij T_ij (p_i - p_j)
Pressure equation:
sum_{j \in N(i)} totmob_ij T_ij (p_i - p_j) = q_i
*/
template <class ADB>
std::vector<ADB>
phaseMobility(const Opm::IncompPropertiesInterface& props,
const std::vector<int>& cells,
const typename ADB::V& sw)
{
typedef Eigen::Array<double, Eigen::Dynamic, 2, Eigen::RowMajor> TwoCol;
typedef Eigen::Array<double, Eigen::Dynamic, 4, Eigen::RowMajor> FourCol;
typedef Eigen::SparseMatrix<double> S;
typedef typename ADB::V V;
typedef typename ADB::M M;
const int nc = props.numCells();
TwoCol s(nc, 2);
s.leftCols<1>() = sw;
s.rightCols<1>() = 1.0 - s.leftCols<1>();
TwoCol kr(nc, 2);
FourCol dkr(nc, 4);
props.relperm(nc, s.data(), cells.data(), kr.data(), dkr.data());
V krw = kr.leftCols<1>();
V kro = kr.rightCols<1>();
V dkrw = dkr.leftCols<1>(); // Left column is top-left of dkr/ds 2x2 matrix.
V dkro = -dkr.rightCols<1>(); // Right column is bottom-right of dkr/ds 2x2 matrix.
S krwjac(nc,nc);
S krojac(nc,nc);
auto sizes = Eigen::ArrayXi::Ones(nc);
krwjac.reserve(sizes);
krojac.reserve(sizes);
for (int c = 0; c < nc; ++c) {
krwjac.insert(c,c) = dkrw(c);
krojac.insert(c,c) = dkro(c);
}
const double* mu = props.viscosity();
std::vector<M> dmw = { M(krwjac)/mu[0] };
std::vector<M> dmo = { M(krojac)/mu[1] };
std::vector<ADB> pmobc = { ADB::function(krw / mu[0], std::move(dmw)) ,
ADB::function(kro / mu[1], std::move(dmo)) };
return pmobc;
}
/// Returns fw(sw).
template <class ADB>
ADB
fluxFunc(const std::vector<ADB>& m)
{
assert (m.size() == 2);
ADB f = m[0] / (m[0] + m[1]);
return f;
}
int main()
try
{
typedef Opm::AutoDiffBlock<double> ADB;
typedef ADB::V V;
typedef Eigen::SparseMatrix<double> S;
Opm::time::StopWatch clock;
clock.start();
const Opm::GridManager gm(3,3);//(50, 50, 10);
const UnstructuredGrid& grid = *gm.c_grid();
using namespace Opm::unit;
using namespace Opm::prefix;
// const Opm::IncompPropertiesBasic props(2, Opm::SaturationPropsBasic::Linear,
// { 1000.0, 800.0 },
// { 1.0*centi*Poise, 5.0*centi*Poise },
// 0.2, 100*milli*darcy,
// grid.dimensions, grid.number_of_cells);
// const Opm::IncompPropertiesBasic props(2, Opm::SaturationPropsBasic::Linear,
// { 1000.0, 1000.0 },
// { 1.0, 1.0 },
// 1.0, 1.0,
// grid.dimensions, grid.number_of_cells);
const Opm::IncompPropertiesBasic props(2, Opm::SaturationPropsBasic::Linear,
{ 1000.0, 1000.0 },
{ 1.0, 30.0 },
1.0, 1.0,
grid.dimensions, grid.number_of_cells);
V htrans(grid.cell_facepos[grid.number_of_cells]);
tpfa_htrans_compute(const_cast<UnstructuredGrid*>(&grid), props.permeability(), htrans.data());
V trans_all(grid.number_of_faces);
// tpfa_trans_compute(const_cast<UnstructuredGrid*>(&grid), htrans.data(), trans_all.data());
const int nc = grid.number_of_cells;
std::vector<int> allcells(nc);
for (int i = 0; i < nc; ++i) {
allcells[i] = i;
}
std::cerr << "Opm core " << clock.secsSinceLast() << std::endl;
// Define neighbourhood-derived operator matrices.
const Opm::HelperOps ops(grid);
const int num_internal = ops.internal_faces.size();
std::cerr << "Topology matrices " << clock.secsSinceLast() << std::endl;
typedef Opm::AutoDiffBlock<double> ADB;
typedef ADB::V V;
// q
V q(nc);
q.setZero();
q[0] = 1.0;
q[nc-1] = -1.0;
// s0 - this is explicit now
typedef Eigen::Array<double, Eigen::Dynamic, 2, Eigen::RowMajor> TwoCol;
TwoCol s0(nc, 2);
s0.leftCols<1>().setZero();
s0.rightCols<1>().setOnes();
// totmob - explicit as well
TwoCol kr(nc, 2);
props.relperm(nc, s0.data(), allcells.data(), kr.data(), 0);
const V krw = kr.leftCols<1>();
const V kro = kr.rightCols<1>();
const double* mu = props.viscosity();
const V totmob = krw/mu[0] + kro/mu[1];
// Moved down here because we need total mobility.
tpfa_eff_trans_compute(const_cast<UnstructuredGrid*>(&grid), totmob.data(),
htrans.data(), trans_all.data());
// Still explicit, and no upwinding!
V mobtransf(num_internal);
for (int fi = 0; fi < num_internal; ++fi) {
mobtransf[fi] = trans_all[ops.internal_faces[fi]];
}
std::cerr << "Property arrays " << clock.secsSinceLast() << std::endl;
// Initial pressure.
V p0(nc,1);
p0.fill(200*Opm::unit::barsa);
// First actual AD usage: defining pressure variable.
const std::vector<int> bpat = { nc };
// Could actually write { nc } instead of bpat below,
// but we prefer a named variable since we will repeat it.
const ADB p = ADB::variable(0, p0, bpat);
const ADB ngradp = ops.ngrad*p;
// We want flux = totmob*trans*(p_i - p_j) for the ij-face.
const ADB flux = mobtransf*ngradp;
const ADB residual = ops.div*flux - q;
std::cerr << "Construct AD residual " << clock.secsSinceLast() << std::endl;
// It's the residual we want to be zero. We know it's linear in p,
// so we just need a single linear solve. Since we have formulated
// ourselves with a residual and jacobian we do this with a single
// Newton step (hopefully easy to extend later):
// p = p0 - J(p0) \ R(p0)
// Where R(p0) and J(p0) are contained in residual.value() and
// residual.derived()[0].
#if HAVE_SUITESPARSE_UMFPACK_H
typedef Eigen::UmfPackLU<S> LinSolver;
#else
typedef Eigen::BiCGSTAB<S> LinSolver;
#endif // HAVE_SUITESPARSE_UMFPACK_H
LinSolver solver;
S pmatr;
residual.derivative()[0].toSparse(pmatr);
pmatr.coeffRef(0,0) *= 2.0;
pmatr.makeCompressed();
solver.compute(pmatr);
if (solver.info() != Eigen::Success) {
std::cerr << "Pressure/flow Jacobian decomposition error\n";
return EXIT_FAILURE;
}
// const Eigen::VectorXd dp = solver.solve(residual.value().matrix());
ADB::V residual_v = residual.value();
const V dp = solver.solve(residual_v.matrix()).array();
if (solver.info() != Eigen::Success) {
std::cerr << "Pressure/flow solve failure\n";
return EXIT_FAILURE;
}
const V p1 = p0 - dp;
std::cerr << "Solve " << clock.secsSinceLast() << std::endl;
// std::cout << p1 << std::endl;
// ------ Transport solve ------
// Now we'll try to do a transport step as well.
// Residual formula is
// R_w = s_w - s_w^0 + dt/pv * (div v_w)
// where
// v_w = f_w v
// and f_w is (for now) based on averaged mobilities, not upwind.
double res_norm = 1e100;
const V sw0 = s0.leftCols<1>();
// V sw1 = sw0;
V sw1 = 0.5*V::Ones(nc,1);
const V ndp = (ops.ngrad * p1.matrix()).array();
const V dflux = mobtransf * ndp;
const Opm::UpwindSelector<double> upwind(grid, ops, dflux);
const V pv = Eigen::Map<const V>(props.porosity(), nc, 1)
* Eigen::Map<const V>(grid.cell_volumes, nc, 1);
const double dt = 0.0005;
const V dtpv = dt/pv;
const V qneg = q.min(V::Zero(nc,1));
const V qpos = q.max(V::Zero(nc,1));
std::cout.setf(std::ios::scientific);
std::cout.precision(16);
int it = 0;
do {
const ADB sw = ADB::variable(0, sw1, bpat);
const std::vector<ADB> pmobc = phaseMobility<ADB>(props, allcells, sw.value());
const std::vector<ADB> pmobf = upwind.select(pmobc);
const ADB fw_cell = fluxFunc(pmobc);
const ADB fw_face = fluxFunc(pmobf);
const ADB flux1 = fw_face * dflux;
const ADB qtr_ad = qpos + fw_cell*qneg;
const ADB transport_residual = sw - sw0 + dtpv*(ops.div*flux1 - qtr_ad);
res_norm = transport_residual.value().matrix().norm();
std::cout << "res_norm[" << it << "] = "
<< res_norm << std::endl;
S smatr;
transport_residual.derivative()[0].toSparse(smatr);
smatr.makeCompressed();
solver.compute(smatr);
if (solver.info() != Eigen::Success) {
std::cerr << "Transport Jacobian decomposition error\n";
return EXIT_FAILURE;
}
ADB::V transport_residual_v = transport_residual.value();
const V ds = solver.solve(transport_residual_v.matrix()).array();
if (solver.info() != Eigen::Success) {
std::cerr << "Transport solve failure\n";
return EXIT_FAILURE;
}
sw1 = sw.value() - ds;
std::cerr << "Solve for s[" << it << "]: "
<< clock.secsSinceLast() << '\n';
sw1 = sw1.min(V::Ones(nc,1)).max(V::Zero(nc,1));
it += 1;
} while (res_norm > 1e-7);
std::cout << "Saturation solution:\n"
<< "function s1 = solution\n"
<< "s1 = [\n" << sw1 << "\n];\n";
}
catch (const std::exception &e) {
std::cerr << "Program threw an exception: " << e.what() << "\n";
throw;
}

View File

@ -1,144 +0,0 @@
#include "config.h"
#include <iostream>
#include <opm/common/utility/parameters/ParameterGroup.hpp>
#include <opm/core/simulator/initState.hpp>
#include <opm/simulators/timestepping/SimulatorTimer.hpp>
#include <opm/core/wells/WellsManager.hpp>
#include <opm/grid/GridManager.hpp>
#include <opm/core/pressure/IncompTpfa.hpp>
#include <opm/core/props/IncompPropertiesFromDeck.hpp>
#include <opm/core/wells.h>
#include <opm/grid/UnstructuredGrid.h>
#include <opm/core/utility/miscUtilities.hpp>
#include <opm/core/simulator/TwophaseState.hpp>
#include <opm/core/simulator/WellState.hpp>
#include <opm/core/pressure/FlowBCManager.hpp>
#include <opm/core/linalg/LinearSolverFactory.hpp>
#include <opm/core/props/rock/RockCompressibility.hpp>
#include <opm/parser/eclipse/Parser/ParseContext.hpp>
#include <opm/parser/eclipse/Parser/Parser.hpp>
#include <opm/parser/eclipse/Deck/Deck.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/Schedule.hpp>
int main(int argc, char** argv)
try
{
using namespace Opm;
ParameterGroup parameters(argc, argv, false);
std::string file_name = parameters.getDefault<std::string > ("inputdeck", "data.data");
SimulatorTimer simtimer;
simtimer.init(parameters);
// Read input file
ParseContext parseContext;
Opm::Parser parser;
Opm::Deck deck = parser.parseFile(file_name , parseContext);
Opm::EclipseState eclipseState(deck , parseContext);
Opm::Schedule schedule(deck, eclipseState.getInputGrid(), eclipseState.get3DProperties(), eclipseState.runspec(), parseContext);
std::cout << "Done!" << std::endl;
// Setup grid
GridManager grid(eclipseState.getInputGrid());
// Define rock and fluid properties
IncompPropertiesFromDeck incomp_properties(deck, eclipseState, *grid.c_grid());
RockCompressibility rock_comp(eclipseState);
// Finally handle the wells
WellsManager wells(eclipseState , schedule, 0 , *grid.c_grid());
double gravity[3] = {0.0, 0.0, parameters.getDefault<double>("gravity", 0.0)};
Opm::LinearSolverFactory linsolver(parameters);
double nl_pressure_residual_tolerance = 1e-8;
double nl_pressure_change_tolerance = 0.0;
int nl_pressure_maxiter = 100;
if (rock_comp.isActive()) {
nl_pressure_residual_tolerance = parameters.getDefault("nl_pressure_residual_tolerance", 1e-8);
nl_pressure_change_tolerance = parameters.getDefault("nl_pressure_change_tolerance", 1.0); // in Pascal
nl_pressure_maxiter = parameters.getDefault("nl_pressure_maxiter", 10);
}
std::vector<double> src;
Opm::FlowBCManager bcs;
// EXPERIMENT_ISTL
IncompTpfa pressure_solver(*grid.c_grid(), incomp_properties, &rock_comp, linsolver,
nl_pressure_residual_tolerance, nl_pressure_change_tolerance, nl_pressure_maxiter,
gravity, wells.c_wells(), src, bcs.c_bcs());
std::vector<int> all_cells;
for (int i = 0; i < grid.c_grid()->number_of_cells; i++) {
all_cells.push_back(i);
}
Opm::TwophaseState state( grid.c_grid()->number_of_cells , grid.c_grid()->number_of_faces );
initStateFromDeck(*grid.c_grid(), incomp_properties, deck, gravity[2], state);
Opm::WellState well_state;
well_state.init(wells.c_wells(), state);
pressure_solver.solve(simtimer.currentStepLength(), state, well_state);
const int np = incomp_properties.numPhases();
std::vector<double> fractional_flows(grid.c_grid()->number_of_cells*np, 0.0);
computeFractionalFlow(incomp_properties, all_cells, state.saturation(), fractional_flows);
// This will be refactored into a separate function once done
std::vector<double> well_resflows(wells.c_wells()->number_of_wells*np, 0.0);
computePhaseFlowRatesPerWell(*wells.c_wells(), well_state.perfRates(), fractional_flows, well_resflows);
// We approximate (for _testing_ that resflows = surfaceflows)
for (int wc_iter = 0; wc_iter < 10 && !wells.conditionsMet(well_state.bhp(), well_resflows, well_resflows); ++wc_iter) {
std::cout << "Conditions not met for well, trying again" << std::endl;
pressure_solver.solve(simtimer.currentStepLength(), state, well_state);
std::cout << "Solved" << std::endl;
computePhaseFlowRatesPerWell(*wells.c_wells(), well_state.perfRates(), fractional_flows, well_resflows);
}
#if 0
std::vector<double> porevol;
computePorevolume(*grid->c_grid(), incomp_properties, porevol);
TwophaseFluid fluid(incomp_properties);
TransportContextl model(fluid, *grid->c_grid(), porevol, gravity[2], true);
TransportSolver tsolver(model);
TransportSource* tsrc = create_transport_source(2, 2);
double ssrc[] = {1.0, 0.0};
double ssink[] = {0.0, 1.0};
double zdummy[] = {0.0, 0.0};
{
int well_cell_index = 0;
for (int well = 0; well < wells.c_wells()->number_of_wells; ++well) {
for (int cell = wells.c_wells()->well_connpos[well]; cell < wells.c_wells()->well_connpos[well + 1]; ++cell) {
if (well_rate_per_cell[well_cell_index] > 0.0) {
append_transport_source(well_cell_index, 2, 0,
well_rate_per_cell[well_cell_index], ssrc, zdummy, tsrc);
} else if (well_rate_per_cell[well_cell_index] < 0.0) {
append_transport_source(well_cell_index, 2, 0,
well_rate_per_cell[well_cell_index], ssink, zdummy, tsrc);
}
}
}
}
tsolver.solve(*grid->c_grid(), tsrc, stepsize, ctrl, state, linsolve, rpt);
Opm::computeInjectedProduced(*props, state.saturation(), src, stepsize, injected, produced);
#endif
return 0;
}
catch (const std::exception &e) {
std::cerr << "Program threw an exception: " << e.what() << "\n";
throw;
}

View File

@ -1,302 +0,0 @@
/*
Copyright 2013 SINTEF ICT, Applied Mathematics.
Copyright 2013 Statoil ASA.
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 3 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 <http://www.gnu.org/licenses/>.
*/
#ifndef OPM_AUTODIFF_HPP_HEADER
#define OPM_AUTODIFF_HPP_HEADER
#include <cmath>
namespace Opm
{
/// A simple class for forward-mode automatic differentiation.
///
/// The class represents a single value and a single derivative.
/// Only basic arithmetic operators and a few functions are
/// implemented for it, it is mostly intended for simple
/// experimentation.
template <typename Scalar>
class AutoDiff
{
public:
/// Create an AutoDiff object representing a constant, that
/// is, its derivative is zero.
static AutoDiff
constant(const Scalar x)
{
return function(x, Scalar(0));
}
/// Create an AutoDiff object representing a primary variable,
/// that is, its derivative is one.
static AutoDiff
variable(const Scalar x)
{
return function(x, Scalar(1));
}
/// Create an AutoDiff object representing a function value
/// and its derivative.
static AutoDiff
function(const Scalar x, const Scalar dx)
{
return AutoDiff(x, dx);
}
void
operator +=(const Scalar& rhs)
{
val_ += rhs;
}
void
operator +=(const AutoDiff& rhs)
{
val_ += rhs.val_;
der_ += rhs.der_;
}
void
operator -=(const Scalar& rhs)
{
val_ -= rhs;
}
void
operator -=(const AutoDiff& rhs)
{
val_ -= rhs.val_;
der_ -= rhs.der_;
}
void
operator *=(const Scalar& rhs)
{
val_ *= rhs;
der_ *= rhs;
}
void
operator *=(const AutoDiff& rhs)
{
der_ = der_*rhs.val_ + val_*rhs.der_;
val_ *= rhs.val_;
}
void
operator /=(const Scalar& rhs)
{
val_ /= rhs;
der_ /= rhs;
}
void
operator /=(const AutoDiff& rhs)
{
der_ = (der_*rhs.val_ - val_*rhs.der_) / (rhs.val_ * rhs.val_);
val_ /= rhs.val_;
}
template <class Ostream>
Ostream&
print(Ostream& os) const
{
os << "(x,dx) = (" << val_ << ',' << der_ << ")";
return os;
}
const Scalar val() const { return val_; }
const Scalar der() const { return der_; }
private:
AutoDiff(const Scalar x, const Scalar dx)
: val_(x), der_(dx)
{}
Scalar val_;
Scalar der_;
};
template <class Ostream, typename Scalar>
Ostream&
operator<<(Ostream& os, const AutoDiff<Scalar>& fw)
{
return fw.print(os);
}
template <typename Scalar>
AutoDiff<Scalar>
operator +(const AutoDiff<Scalar>& lhs,
const AutoDiff<Scalar>& rhs)
{
AutoDiff<Scalar> ret = lhs;
ret += rhs;
return ret;
}
template <typename Scalar, typename T>
AutoDiff<Scalar>
operator +(const T lhs,
const AutoDiff<Scalar>& rhs)
{
AutoDiff<Scalar> ret = rhs;
ret += Scalar(lhs);
return ret;
}
template <typename Scalar, typename T>
AutoDiff<Scalar>
operator +(const AutoDiff<Scalar>& lhs,
const T rhs)
{
AutoDiff<Scalar> ret = lhs;
ret += Scalar(rhs);
return ret;
}
template <typename Scalar>
AutoDiff<Scalar>
operator -(const AutoDiff<Scalar>& lhs,
const AutoDiff<Scalar>& rhs)
{
AutoDiff<Scalar> ret = lhs;
ret -= rhs;
return ret;
}
template <typename Scalar, typename T>
AutoDiff<Scalar>
operator -(const T lhs,
const AutoDiff<Scalar>& rhs)
{
return AutoDiff<Scalar>::function(Scalar(lhs) - rhs.val(), -rhs.der());
}
template <typename Scalar, typename T>
AutoDiff<Scalar>
operator -(const AutoDiff<Scalar>& lhs,
const T rhs)
{
AutoDiff<Scalar> ret = lhs;
ret -= Scalar(rhs);
return ret;
}
template <typename Scalar>
AutoDiff<Scalar>
operator *(const AutoDiff<Scalar>& lhs,
const AutoDiff<Scalar>& rhs)
{
AutoDiff<Scalar> ret = lhs;
ret *= rhs;
return ret;
}
template <typename Scalar, typename T>
AutoDiff<Scalar>
operator *(const T lhs,
const AutoDiff<Scalar>& rhs)
{
AutoDiff<Scalar> ret = rhs;
ret *= Scalar(lhs);
return ret;
}
template <typename Scalar, typename T>
AutoDiff<Scalar>
operator *(const AutoDiff<Scalar>& lhs,
const T rhs)
{
AutoDiff<Scalar> ret = lhs;
ret *= Scalar(rhs);
return ret;
}
template <typename Scalar>
AutoDiff<Scalar>
operator /(const AutoDiff<Scalar>& lhs,
const AutoDiff<Scalar>& rhs)
{
AutoDiff<Scalar> ret = lhs;
ret /= rhs;
return ret;
}
template <typename Scalar, typename T>
AutoDiff<Scalar>
operator /(const T lhs,
const AutoDiff<Scalar>& rhs)
{
Scalar a = Scalar(lhs) / rhs.val();
Scalar b = (-Scalar(lhs) / (rhs.val() * rhs.val())) * rhs.der();
return AutoDiff<Scalar>::function(a, b);
}
template <typename Scalar, typename T>
AutoDiff<Scalar>
operator /(const AutoDiff<Scalar>& lhs,
const T rhs)
{
Scalar a = lhs.val() / Scalar(rhs);
Scalar b = lhs.der() / Scalar(rhs);
return AutoDiff<Scalar>::function(a, b);
}
template <typename Scalar>
AutoDiff<Scalar>
cos(const AutoDiff<Scalar>& x)
{
Scalar a = std::cos(x.val());
Scalar b = -std::sin(x.val()) * x.der();
return AutoDiff<Scalar>::function(a, b);
}
template <typename Scalar>
AutoDiff<Scalar>
sqrt(const AutoDiff<Scalar>& x)
{
Scalar a = std::sqrt(x.val());
Scalar b = (Scalar(1.0) / (Scalar(2.0) * a)) * x.der();
return AutoDiff<Scalar>::function(a, b);
}
} // namespace Opm
namespace std {
using Opm::cos;
using Opm::sqrt;
}
#endif /* OPM_AUTODIFF_HPP_HEADER */

View File

@ -1,764 +0,0 @@
/*
Copyright 2013 SINTEF ICT, Applied Mathematics.
Copyright 2016 IRIS AS
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 3 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 <http://www.gnu.org/licenses/>.
*/
#ifndef OPM_AUTODIFFBLOCK_HEADER_INCLUDED
#define OPM_AUTODIFFBLOCK_HEADER_INCLUDED
#include <opm/common/utility/platform_dependent/disable_warnings.h>
#include <Eigen/Eigen>
#include <Eigen/Sparse>
#include <opm/autodiff/fastSparseOperations.hpp>
#include <opm/common/utility/platform_dependent/reenable_warnings.h>
#include <opm/autodiff/AutoDiffMatrix.hpp>
#include <utility>
#include <vector>
#include <cassert>
#include <iostream>
namespace Opm
{
/// A class for forward-mode automatic differentiation with vector
/// values and sparse jacobian matrices.
///
/// The class contains a (column) vector of values and multiple
/// sparse matrices representing its partial derivatives. Each
/// such matrix has a number of rows equal to the number of rows
/// in the value vector, and a number of columns equal to the
/// number of discrete variables we want to compute the
/// derivatives with respect to. The reason to have multiple such
/// jacobians instead of just one is to allow simpler grouping of
/// variables, making it easier to implement various
/// preconditioning schemes. Only basic arithmetic operators are
/// implemented for this class, reflecting our needs so far.
///
/// The class is built on the Eigen library, using an Eigen array
/// type to contain the values and Eigen sparse matrices for the
/// jacobians. The overloaded operators are intended to behave in
/// a similar way to Eigen arrays, meaning for example that the *
/// operator is elementwise multiplication. The only exception is
/// multiplication with a sparse matrix from the left, which is
/// treated as an Eigen matrix operation.
///
/// There are no public constructors, instead we use the Named
/// Constructor pattern. In general, one needs to know which
/// variables one wants to compute the derivatives with respect to
/// before constructing an AutoDiffBlock. Some of the constructors
/// require you to pass a block pattern. This should be a vector
/// containing the number of columns you want for each jacobian
/// matrix.
///
/// For example: you want the derivatives with respect to three
/// different variables p, r and s. Assuming that there are 10
/// elements in p, and 20 in each of r and s, the block pattern is
/// { 10, 20, 20 }. When creating the variables p, r and s in your
/// program you have two options:
/// - Use the variable() constructor three times, passing the
/// index (0 for p, 1 for r and 2 for s), initial value of
/// each variable and the block pattern.
/// - Use the variables() constructor passing only the initial
/// values of each variable. The block pattern will be
/// inferred from the size of the initial value vectors.
/// This is usually the simplest option if you have multiple
/// variables. Note that this constructor returns a vector
/// of all three variables, so you need to use index access
/// (operator[]) to get the individual variables (that is p,
/// r and s).
///
/// After this, the r variable for example will have a size() of
/// 20 and three jacobian matrices. The first is a 20 by 10 zero
/// matrix, the second is a 20 by 20 identity matrix, and the
/// third is a 20 by 20 zero matrix.
template <typename Scalar>
class AutoDiffBlock
{
public:
/// Underlying type for values.
typedef Eigen::Array<Scalar, Eigen::Dynamic, 1> V;
/// Underlying type for jacobians.
typedef AutoDiffMatrix M;
/// Construct an empty AutoDiffBlock.
static AutoDiffBlock null()
{
return AutoDiffBlock(V(), {});
}
/// Create an AutoDiffBlock representing a constant.
/// \param[in] val values
static AutoDiffBlock constant(V&& val)
{
return AutoDiffBlock(std::move(val));
}
/// Create an AutoDiffBlock representing a constant.
/// \param[in] val values
static AutoDiffBlock constant(const V& val)
{
return AutoDiffBlock(val);
}
/// Create an AutoDiffBlock representing a constant.
/// This variant requires specifying the block sizes used
/// for the Jacobians even though the Jacobian matrices
/// themselves will be zero.
/// \param[in] val values
/// \param[in] blocksizes block pattern
static AutoDiffBlock constant(const V& val, const std::vector<int>& blocksizes)
{
std::vector<M> jac;
const int num_elem = val.size();
const int num_blocks = blocksizes.size();
// For constants, all jacobian blocks are zero.
jac.resize(num_blocks);
for (int i = 0; i < num_blocks; ++i) {
jac[i] = M(num_elem, blocksizes[i]);
}
V val_copy(val);
return AutoDiffBlock(std::move(val_copy), std::move(jac));
}
/// Create an AutoDiffBlock representing a single variable block.
/// \param[in] index index of the variable you are constructing
/// \param[in] val values
/// \param[in] blocksizes block pattern
/// The resulting object will have size() equal to block_pattern[index].
/// Its jacobians will all be zero, except for derivative()[index], which
/// will be an identity matrix.
static AutoDiffBlock variable(const int index, V&& val, const std::vector<int>& blocksizes)
{
std::vector<M> jac;
const int num_elem = val.size();
const int num_blocks = blocksizes.size();
// First, set all jacobian blocks to zero...
jac.resize(num_blocks);
for (int i = 0; i < num_blocks; ++i) {
jac[i] = M(num_elem, blocksizes[i]);
}
// ... then set the one corrresponding to this variable to identity.
assert(blocksizes[index] == num_elem);
jac[index] = M::createIdentity(val.size());
return AutoDiffBlock(std::move(val), std::move(jac));
}
/// Create an AutoDiffBlock representing a single variable block.
/// \param[in] index index of the variable you are constructing
/// \param[in] val values
/// \param[in] blocksizes block pattern
/// The resulting object will have size() equal to block_pattern[index].
/// Its jacobians will all be zero, except for derivative()[index], which
/// will be an identity matrix.
static AutoDiffBlock variable(const int index, const V& val, const std::vector<int>& blocksizes)
{
V value = val;
return variable(index, std::move(value), blocksizes);
}
/// Create an AutoDiffBlock by directly specifying values and jacobians.
/// This version of function() moves its arguments and is therefore
/// quite efficient, but leaves the argument variables empty (but valid).
/// \param[in] val values
/// \param[in] jac vector of jacobians
static AutoDiffBlock function(V&& val, std::vector<M>&& jac)
{
return AutoDiffBlock(std::move(val), std::move(jac));
}
/// Create an AutoDiffBlock by directly specifying values and jacobians.
/// This version of function() copies its arguments and is therefore
/// less efficient than the other (moving) overload.
/// \param[in] val values
/// \param[in] jac vector of jacobians
static AutoDiffBlock function(const V& val, const std::vector<M>& jac)
{
V val_copy(val);
std::vector<M> jac_copy(jac);
return AutoDiffBlock(std::move(val_copy), std::move(jac_copy));
}
/// Construct a set of primary variables, each initialized to
/// a given vector.
static std::vector<AutoDiffBlock> variables(const std::vector<V>& initial_values)
{
const int num_vars = initial_values.size();
std::vector<int> bpat;
for (int v = 0; v < num_vars; ++v) {
bpat.push_back(initial_values[v].size());
}
std::vector<AutoDiffBlock> vars;
for (int v = 0; v < num_vars; ++v) {
vars.emplace_back(variable(v, initial_values[v], bpat));
}
return vars;
}
/// Elementwise operator +=
AutoDiffBlock& operator+=(const AutoDiffBlock& rhs)
{
if (jac_.empty()) {
jac_ = rhs.jac_;
} else if (!rhs.jac_.empty()) {
assert (numBlocks() == rhs.numBlocks());
assert (value().size() == rhs.value().size());
const int num_blocks = numBlocks();
#if HAVE_OPENMP
#pragma omp parallel for schedule(static)
#endif // HAVE_OPENMP
for (int block = 0; block < num_blocks; ++block) {
assert(jac_[block].rows() == rhs.jac_[block].rows());
assert(jac_[block].cols() == rhs.jac_[block].cols());
jac_[block] += rhs.jac_[block];
}
}
val_ += rhs.val_;
return *this;
}
/// Elementwise operator -=
AutoDiffBlock& operator-=(const AutoDiffBlock& rhs)
{
if (jac_.empty()) {
const int num_blocks = rhs.numBlocks();
jac_.resize(num_blocks);
#if HAVE_OPENMP
#pragma omp parallel for schedule(static)
#endif // HAVE_OPENMP
for (int block = 0; block < num_blocks; ++block) {
jac_[block] = rhs.jac_[block] * (-1.0);
}
} else if (!rhs.jac_.empty()) {
assert (numBlocks() == rhs.numBlocks());
assert (value().size() == rhs.value().size());
const int num_blocks = numBlocks();
#if HAVE_OPENMP
#pragma omp parallel for schedule(static)
#endif // HAVE_OPENMP
for (int block = 0; block < num_blocks; ++block) {
assert(jac_[block].rows() == rhs.jac_[block].rows());
assert(jac_[block].cols() == rhs.jac_[block].cols());
jac_[block] -= rhs.jac_[block];
}
}
val_ -= rhs.val_;
return *this;
}
/// Elementwise operator +
AutoDiffBlock operator+(const AutoDiffBlock& rhs) const
{
if (jac_.empty() && rhs.jac_.empty()) {
return constant(val_ + rhs.val_);
}
if (jac_.empty()) {
return val_ + rhs;
}
if (rhs.jac_.empty()) {
return *this + rhs.val_;
}
std::vector<M> jac = jac_;
assert(numBlocks() == rhs.numBlocks());
int num_blocks = numBlocks();
#if HAVE_OPENMP
#pragma omp parallel for schedule(static)
#endif // HAVE_OPENMP
for (int block = 0; block < num_blocks; ++block) {
assert(jac[block].rows() == rhs.jac_[block].rows());
assert(jac[block].cols() == rhs.jac_[block].cols());
jac[block] += rhs.jac_[block];
}
return function(val_ + rhs.val_, std::move(jac));
}
/// Elementwise operator -
AutoDiffBlock operator-(const AutoDiffBlock& rhs) const
{
if (jac_.empty() && rhs.jac_.empty()) {
return constant(val_ - rhs.val_);
}
if (jac_.empty()) {
return val_ - rhs;
}
if (rhs.jac_.empty()) {
return *this - rhs.val_;
}
std::vector<M> jac = jac_;
assert(numBlocks() == rhs.numBlocks());
int num_blocks = numBlocks();
#if HAVE_OPENMP
#pragma omp parallel for schedule(static)
#endif // HAVE_OPENMP
for (int block = 0; block < num_blocks; ++block) {
assert(jac[block].rows() == rhs.jac_[block].rows());
assert(jac[block].cols() == rhs.jac_[block].cols());
jac[block] -= rhs.jac_[block];
}
return function(val_ - rhs.val_, std::move(jac));
}
/// Elementwise operator *
AutoDiffBlock operator*(const AutoDiffBlock& rhs) const
{
if (jac_.empty() && rhs.jac_.empty()) {
return constant(val_ * rhs.val_);
}
if (jac_.empty()) {
return val_ * rhs;
}
if (rhs.jac_.empty()) {
return *this * rhs.val_;
}
int num_blocks = numBlocks();
std::vector<M> jac(num_blocks);
assert(numBlocks() == rhs.numBlocks());
M D1(val_.matrix().asDiagonal());
M D2(rhs.val_.matrix().asDiagonal());
#if HAVE_OPENMP
#pragma omp parallel for schedule(dynamic)
#endif // HAVE_OPENMP
for (int block = 0; block < num_blocks; ++block) {
assert(jac_[block].rows() == rhs.jac_[block].rows());
assert(jac_[block].cols() == rhs.jac_[block].cols());
if( jac_[block].nonZeros() == 0 && rhs.jac_[block].nonZeros() == 0 ) {
jac[block] = M( D2.rows(), jac_[block].cols() );
}
else if( jac_[block].nonZeros() == 0 )
jac[block] = D1*rhs.jac_[block];
else if ( rhs.jac_[block].nonZeros() == 0 ) {
jac[block] = D2*jac_[block];
}
else {
jac[block] = D2*jac_[block];
jac[block] += D1*rhs.jac_[block];
}
}
return function(val_ * rhs.val_, std::move(jac));
}
/// Elementwise operator /
AutoDiffBlock operator/(const AutoDiffBlock& rhs) const
{
if (jac_.empty() && rhs.jac_.empty()) {
return constant(val_ / rhs.val_);
}
if (jac_.empty()) {
return val_ / rhs;
}
if (rhs.jac_.empty()) {
return *this / rhs.val_;
}
int num_blocks = numBlocks();
std::vector<M> jac(num_blocks);
assert(numBlocks() == rhs.numBlocks());
M D1(val_.matrix().asDiagonal());
M D2(rhs.val_.matrix().asDiagonal());
M D3((1.0/(rhs.val_*rhs.val_)).matrix().asDiagonal());
#if HAVE_OPENMP
#pragma omp parallel for schedule(dynamic)
#endif // HAVE_OPENMP
for (int block = 0; block < num_blocks; ++block) {
assert(jac_[block].rows() == rhs.jac_[block].rows());
assert(jac_[block].cols() == rhs.jac_[block].cols());
if( jac_[block].nonZeros() == 0 && rhs.jac_[block].nonZeros() == 0 ) {
jac[block] = M( D3.rows(), jac_[block].cols() );
}
else if( jac_[block].nonZeros() == 0 ) {
jac[block] = D3 * ( D1*rhs.jac_[block]) * (-1.0);
}
else if ( rhs.jac_[block].nonZeros() == 0 )
{
jac[block] = D3 * (D2*jac_[block]);
}
else {
jac[block] = D3 * (D2*jac_[block] + (D1*rhs.jac_[block]*(-1.0)));
}
}
return function(val_ / rhs.val_, std::move(jac));
}
/// I/O.
template <class Ostream>
Ostream&
print(Ostream& os) const
{
int num_blocks = jac_.size();
os << "Value =\n" << val_ << "\n\nJacobian =\n";
for (int i = 0; i < num_blocks; ++i) {
Eigen::SparseMatrix<double> m;
jac_[i].toSparse(m);
os << "Sub Jacobian #" << i << '\n' << m << "\n";
}
return os;
}
/// Efficient swap function.
void swap(AutoDiffBlock& other)
{
val_.swap(other.val_);
jac_.swap(other.jac_);
}
/// Number of elements
int size() const
{
return val_.size();
}
/// Number of Jacobian blocks.
int numBlocks() const
{
return jac_.size();
}
/// Sizes (number of columns) of Jacobian blocks.
std::vector<int> blockPattern() const
{
const int nb = numBlocks();
std::vector<int> bp(nb);
for (int block = 0; block < nb; ++block) {
bp[block] = jac_[block].cols();
}
return bp;
}
/// Function value.
const V& value() const
{
return val_;
}
/// Function derivatives.
const std::vector<M>& derivative() const
{
return jac_;
}
private:
AutoDiffBlock(const V& val)
: val_(val)
{
}
AutoDiffBlock(V&& val)
: val_(std::move(val))
{
}
AutoDiffBlock(V&& val, std::vector<M>&& jac)
: val_(std::move(val)), jac_(std::move(jac))
{
#ifndef NDEBUG
const int num_elem = val_.size();
const int num_blocks = jac_.size();
for (int block = 0; block < num_blocks; ++block) {
assert(num_elem == jac_[block].rows());
}
#endif
}
V val_;
std::vector<M> jac_;
};
// --------- Free functions and operators for AutoDiffBlock ---------
/// Stream output.
template <class Ostream, typename Scalar>
Ostream&
operator<<(Ostream& os, const AutoDiffBlock<Scalar>& fw)
{
return fw.print(os);
}
/// Multiply with AutoDiffMatrix from the left.
template <typename Scalar>
AutoDiffBlock<Scalar> operator*(const typename AutoDiffBlock<Scalar>::M& lhs,
const AutoDiffBlock<Scalar>& rhs)
{
int num_blocks = rhs.numBlocks();
std::vector<typename AutoDiffBlock<Scalar>::M> jac(num_blocks);
assert(lhs.cols() == rhs.value().rows());
#if HAVE_OPENMP
#pragma omp parallel for schedule(dynamic)
#endif // HAVE_OPENMP
for (int block = 0; block < num_blocks; ++block) {
fastSparseProduct(lhs, rhs.derivative()[block], jac[block]);
}
typename AutoDiffBlock<Scalar>::V val = lhs*rhs.value().matrix();
return AutoDiffBlock<Scalar>::function(std::move(val), std::move(jac));
}
/// Multiply with Eigen sparse matrix from the left.
template <typename Scalar>
AutoDiffBlock<Scalar> operator*(const Eigen::SparseMatrix<Scalar>& lhs,
const AutoDiffBlock<Scalar>& rhs)
{
int num_blocks = rhs.numBlocks();
std::vector<typename AutoDiffBlock<Scalar>::M> jac(num_blocks);
assert(lhs.cols() == rhs.value().rows());
for (int block = 0; block < num_blocks; ++block) {
fastSparseProduct(lhs, rhs.derivative()[block], jac[block]);
}
typename AutoDiffBlock<Scalar>::V val = lhs*rhs.value().matrix();
return AutoDiffBlock<Scalar>::function(std::move(val), std::move(jac));
}
/// Elementwise multiplication with constant on the left.
template <typename Scalar>
AutoDiffBlock<Scalar> operator*(const typename AutoDiffBlock<Scalar>::V& lhs,
const AutoDiffBlock<Scalar>& rhs)
{
return AutoDiffBlock<Scalar>::constant(lhs, rhs.blockPattern()) * rhs;
}
/// Elementwise multiplication with constant on the right.
template <typename Scalar>
AutoDiffBlock<Scalar> operator*(const AutoDiffBlock<Scalar>& lhs,
const typename AutoDiffBlock<Scalar>::V& rhs)
{
return rhs * lhs; // Commutative operation.
}
/// Elementwise addition with constant on the left.
template <typename Scalar>
AutoDiffBlock<Scalar> operator+(const typename AutoDiffBlock<Scalar>::V& lhs,
const AutoDiffBlock<Scalar>& rhs)
{
return AutoDiffBlock<Scalar>::constant(lhs, rhs.blockPattern()) + rhs;
}
/// Elementwise addition with constant on the right.
template <typename Scalar>
AutoDiffBlock<Scalar> operator+(const AutoDiffBlock<Scalar>& lhs,
const typename AutoDiffBlock<Scalar>::V& rhs)
{
return rhs + lhs; // Commutative operation.
}
/// Elementwise subtraction with constant on the left.
template <typename Scalar>
AutoDiffBlock<Scalar> operator-(const typename AutoDiffBlock<Scalar>::V& lhs,
const AutoDiffBlock<Scalar>& rhs)
{
return AutoDiffBlock<Scalar>::constant(lhs, rhs.blockPattern()) - rhs;
}
/// Elementwise subtraction with constant on the right.
template <typename Scalar>
AutoDiffBlock<Scalar> operator-(const AutoDiffBlock<Scalar>& lhs,
const typename AutoDiffBlock<Scalar>::V& rhs)
{
return lhs - AutoDiffBlock<Scalar>::constant(rhs, lhs.blockPattern());
}
/// Elementwise division with constant on the left.
template <typename Scalar>
AutoDiffBlock<Scalar> operator/(const typename AutoDiffBlock<Scalar>::V& lhs,
const AutoDiffBlock<Scalar>& rhs)
{
return AutoDiffBlock<Scalar>::constant(lhs, rhs.blockPattern()) / rhs;
}
/// Elementwise division with constant on the right.
template <typename Scalar>
AutoDiffBlock<Scalar> operator/(const AutoDiffBlock<Scalar>& lhs,
const typename AutoDiffBlock<Scalar>::V& rhs)
{
return lhs / AutoDiffBlock<Scalar>::constant(rhs, lhs.blockPattern());
}
/**
* @brief Operator for multiplication with a scalar on the right-hand side
*
* @param lhs The left-hand side AD forward block
* @param rhs The scalar to multiply with
* @return The product
*/
template <typename Scalar>
AutoDiffBlock<Scalar> operator*(const AutoDiffBlock<Scalar>& lhs,
const Scalar& rhs)
{
std::vector< typename AutoDiffBlock<Scalar>::M > jac;
jac.reserve( lhs.numBlocks() );
for (int block=0; block<lhs.numBlocks(); block++) {
jac.emplace_back( lhs.derivative()[block] * rhs );
}
return AutoDiffBlock<Scalar>::function( lhs.value() * rhs, std::move(jac) );
}
/**
* @brief Operator for multiplication with a scalar on the left-hand side
*
* @param lhs The scalar to multiply with
* @param rhs The right-hand side AD forward block
* @return The product
*/
template <typename Scalar>
AutoDiffBlock<Scalar> operator*(const Scalar& lhs,
const AutoDiffBlock<Scalar>& rhs)
{
return rhs * lhs; // Commutative operation.
}
/**
* @brief Computes the value of base raised to the power of exponent
*
* @param base The AD forward block
* @param exponent double
* @return The value of base raised to the power of exponent
*/
template <typename Scalar>
AutoDiffBlock<Scalar> pow(const AutoDiffBlock<Scalar>& base,
const double exponent)
{
const typename AutoDiffBlock<Scalar>::V val = base.value().pow(exponent);
const typename AutoDiffBlock<Scalar>::V derivative = exponent * base.value().pow(exponent - 1.0);
const typename AutoDiffBlock<Scalar>::M derivative_diag(derivative.matrix().asDiagonal());
std::vector< typename AutoDiffBlock<Scalar>::M > jac (base.numBlocks());
for (int block = 0; block < base.numBlocks(); block++) {
fastSparseProduct(derivative_diag, base.derivative()[block], jac[block]);
}
return AutoDiffBlock<Scalar>::function( std::move(val), std::move(jac) );
}
/**
* @brief Computes the value of base raised to the power of exponent
*
* @param base The AD forward block
* @param exponent Array of exponents
* @return The value of base raised to the power of exponent elementwise
*/
template <typename Scalar>
AutoDiffBlock<Scalar> pow(const AutoDiffBlock<Scalar>& base,
const typename AutoDiffBlock<Scalar>::V& exponent)
{
// Add trivial derivatives and use the AD pow function
return pow(base,AutoDiffBlock<Scalar>::constant(exponent));
}
/**
* @brief Computes the value of base raised to the power of exponent
*
* @param base Array of base values
* @param exponent The AD forward block
* @return The value of base raised to the power of exponent elementwise
*/
template <typename Scalar>
AutoDiffBlock<Scalar> pow(const typename AutoDiffBlock<Scalar>::V& base,
const AutoDiffBlock<Scalar>& exponent)
{
// Add trivial derivatives and use the AD pow function
return pow(AutoDiffBlock<Scalar>::constant(base),exponent);
}
/**
* @brief Computes the value of base raised to the power of exponent
*
* @param base The base AD forward block
* @param exponent The exponent AD forward block
* @return The value of base raised to the power of exp
*/
template <typename Scalar>
AutoDiffBlock<Scalar> pow(const AutoDiffBlock<Scalar>& base,
const AutoDiffBlock<Scalar>& exponent)
{
const int num_elem = base.value().size();
assert(exponent.size() == num_elem);
typename AutoDiffBlock<Scalar>::V val (num_elem);
for (int i = 0; i < num_elem; ++i) {
val[i] = std::pow(base.value()[i], exponent.value()[i]);
}
// (f^g)' = f^g * ln(f) * g' + g * f^(g-1) * f' = der1 + der2
// if f' is empty only der1 is calculated
// if g' is empty only der2 is calculated
// if f' and g' are non empty they should have the same size
int num_blocks = std::max (base.numBlocks(), exponent.numBlocks());
if (!base.derivative().empty() && !exponent.derivative().empty()) {
assert(exponent.numBlocks() == base.numBlocks());
}
std::vector< typename AutoDiffBlock<Scalar>::M > jac (num_blocks);
if ( !exponent.derivative().empty() ) {
typename AutoDiffBlock<Scalar>::V der1 = val;
for (int i = 0; i < num_elem; ++i) {
der1[i] *= std::log(base.value()[i]);
}
std::vector< typename AutoDiffBlock<Scalar>::M > jac1 (exponent.numBlocks());
const typename AutoDiffBlock<Scalar>::M der1_diag(der1.matrix().asDiagonal());
for (int block = 0; block < exponent.numBlocks(); block++) {
fastSparseProduct(der1_diag, exponent.derivative()[block], jac1[block]);
jac[block] = jac1[block];
}
}
if ( !base.derivative().empty() ) {
typename AutoDiffBlock<Scalar>::V der2 = exponent.value();
for (int i = 0; i < num_elem; ++i) {
der2[i] *= std::pow(base.value()[i], exponent.value()[i] - 1.0);
}
std::vector< typename AutoDiffBlock<Scalar>::M > jac2 (base.numBlocks());
const typename AutoDiffBlock<Scalar>::M der2_diag(der2.matrix().asDiagonal());
for (int block = 0; block < base.numBlocks(); block++) {
fastSparseProduct(der2_diag, base.derivative()[block], jac2[block]);
if (!exponent.derivative().empty()) {
jac[block] += jac2[block];
} else {
jac[block] = jac2[block];
}
}
}
return AutoDiffBlock<Scalar>::function(std::move(val), std::move(jac));
}
} // namespace Opm
#endif // OPM_AUTODIFFBLOCK_HEADER_INCLUDED

View File

@ -1,734 +0,0 @@
/*
Copyright 2013 SINTEF ICT, Applied Mathematics.
Copyright 2015 IRIS
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 3 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 <http://www.gnu.org/licenses/>.
*/
#ifndef OPM_AUTODIFFHELPERS_HEADER_INCLUDED
#define OPM_AUTODIFFHELPERS_HEADER_INCLUDED
#include <opm/autodiff/AutoDiffBlock.hpp>
#include <opm/autodiff/GridHelpers.hpp>
#include <opm/autodiff/GeoProps.hpp>
#include <opm/grid/UnstructuredGrid.h>
#include <opm/grid/PinchProcessor.hpp>
#include <opm/core/props/rock/RockFromDeck.hpp>
#include <opm/common/ErrorMacros.hpp>
#include <opm/parser/eclipse/EclipseState/Grid/NNC.hpp>
#include <opm/parser/eclipse/EclipseState/EclipseState.hpp>
#include <iostream>
#include <vector>
namespace Opm
{
// -------------------- class HelperOps --------------------
/// Contains vectors and sparse matrices that represent subsets or
/// operations on (AD or regular) vectors of data.
struct HelperOps
{
typedef Eigen::SparseMatrix<double> M;
typedef AutoDiffBlock<double>::V V;
/// A list of internal faces.
typedef Eigen::Array<int, Eigen::Dynamic, 1> IFaces;
IFaces internal_faces;
/// Extract for each internal face the difference of its adjacent cells' values (first - second).
M ngrad;
/// Extract for each face the difference of its adjacent cells' values (second - first).
M grad;
/// Extract for each face the average of its adjacent cells' values.
M caver;
/// Extract for each cell the sum of its adjacent interior faces' (signed) values.
M div;
/// Extract for each face the difference of its adjacent cells' values (first - second).
/// For boundary faces, one of the entries per row (corresponding to the outside) is zero.
M fullngrad;
/// Extract for each cell the sum of all its adjacent faces' (signed) values.
M fulldiv;
/// Non-neighboring connections
typedef Eigen::Array<int, Eigen::Dynamic, 2, Eigen::RowMajor> TwoColInt;
TwoColInt nnc_cells;
/// The NNC transmissibilities
V nnc_trans;
/// The set of all connections' cells (face or nnc).
TwoColInt connection_cells;
/// Constructs all helper vectors and matrices.
template<class Grid>
HelperOps(const Grid& grid, const NNC& nnc = NNC())
{
using namespace AutoDiffGrid;
const int nc = numCells(grid);
const int nf = numFaces(grid);
// Define some neighbourhood-derived helper arrays.
TwoColInt nbi;
extractInternalFaces(grid, internal_faces, nbi);
const int num_internal = internal_faces.size();
// handle non-neighboring connections
const bool has_nnc = nnc.hasNNC();
int numNNC = nnc.numNNC();
// num_connections may also include non-neighboring connections
const int num_connections = num_internal + numNNC;
if (has_nnc) {
const int *cartDims = AutoDiffGrid::cartDims(grid);
const int numCartesianCells = cartDims[0] * cartDims[1] * cartDims[2];
// the nnc's acts on global indicies and must be mapped to cell indicies
std::vector<int> global2localIdx(numCartesianCells,0);
for (int i = 0; i< nc; ++i) {
global2localIdx[ globalCell( grid )[i] ] = i;
}
nnc_cells.resize(numNNC,2);
nnc_trans.resize(numNNC);
for (int i = 0; i < numNNC; ++i) {
nnc_cells(i,0) = global2localIdx[nnc.nncdata()[i].cell1];
nnc_cells(i,1) = global2localIdx[nnc.nncdata()[i].cell2];
// store the nnc transmissibilities for later usage.
nnc_trans(i) = nnc.nncdata()[i].trans;
}
}
// std::cout << "nbi = \n" << nbi << std::endl;
// Create matrices.
ngrad.resize(num_connections, nc);
caver.resize(num_connections, nc);
typedef Eigen::Triplet<double> Tri;
std::vector<Tri> ngrad_tri;
std::vector<Tri> caver_tri;
ngrad_tri.reserve(2*num_connections);
caver_tri.reserve(2*num_connections);
for (int i = 0; i < num_internal; ++i) {
ngrad_tri.emplace_back(i, nbi(i,0), 1.0);
ngrad_tri.emplace_back(i, nbi(i,1), -1.0);
caver_tri.emplace_back(i, nbi(i,0), 0.5);
caver_tri.emplace_back(i, nbi(i,1), 0.5);
}
// add contribution from NNC
if (has_nnc) {
for (int i = 0; i < numNNC; ++i) {
ngrad_tri.emplace_back(i+num_internal, nnc_cells(i,0), 1.0);
ngrad_tri.emplace_back(i+num_internal, nnc_cells(i,1), -1.0);
caver_tri.emplace_back(i+num_internal, nnc_cells(i,0), 0.5);
caver_tri.emplace_back(i+num_internal, nnc_cells(i,1), 0.5);
}
}
ngrad.setFromTriplets(ngrad_tri.begin(), ngrad_tri.end());
caver.setFromTriplets(caver_tri.begin(), caver_tri.end());
grad = -ngrad;
div = ngrad.transpose();
std::vector<Tri> fullngrad_tri;
fullngrad_tri.reserve(2*(nf+numNNC));
typename ADFaceCellTraits<Grid>::Type nb = faceCellsToEigen(grid);
for (int i = 0; i < nf; ++i) {
if (nb(i,0) >= 0) {
fullngrad_tri.emplace_back(i, nb(i,0), 1.0);
}
if (nb(i,1) >= 0) {
fullngrad_tri.emplace_back(i, nb(i,1), -1.0);
}
}
// add contribution from NNC
if (has_nnc) {
for (int i = 0; i < numNNC; ++i) {
fullngrad_tri.emplace_back(i+nf, nnc_cells(i,0), 1.0);
fullngrad_tri.emplace_back(i+nf, nnc_cells(i,1), -1.0);
}
}
fullngrad.resize(nf+numNNC, nc);
fullngrad.setFromTriplets(fullngrad_tri.begin(), fullngrad_tri.end());
fulldiv = fullngrad.transpose();
if (has_nnc) {
connection_cells.resize(nbi.rows() + nnc_cells.rows(), 2);
connection_cells << nbi, nnc_cells;
} else {
connection_cells = nbi;
}
}
};
// -------------------- upwinding helper class --------------------
/// Upwind selection in absence of counter-current flow (i.e.,
/// without effects of gravity and/or capillary pressure).
template <typename Scalar>
class UpwindSelector {
public:
typedef AutoDiffBlock<Scalar> ADB;
template<class Grid>
UpwindSelector(const Grid& g,
const HelperOps& h,
const typename ADB::V& ifaceflux)
{
using namespace AutoDiffGrid;
typedef HelperOps::IFaces::Index IFIndex;
const IFIndex nif = h.internal_faces.size();
typename ADFaceCellTraits<Grid>::Type
face_cells = faceCellsToEigen(g);
// num connections may possibly include NNCs
int num_nnc = h.nnc_trans.size();
int num_connections = nif + num_nnc;
assert(num_connections == ifaceflux.size());
// Define selector structure.
typedef typename Eigen::Triplet<Scalar> Triplet;
std::vector<Triplet> s; s.reserve(num_connections);
for (IFIndex iface = 0; iface < nif; ++iface) {
const int f = h.internal_faces[iface];
const int c1 = face_cells(f,0);
const int c2 = face_cells(f,1);
assert ((c1 >= 0) && (c2 >= 0));
// Select upwind cell.
const int c = (ifaceflux[iface] >= 0) ? c1 : c2;
s.push_back(Triplet(iface, c, Scalar(1)));
}
if (num_nnc > 0) {
for (int i = 0; i < num_nnc; ++i) {
const int c = (ifaceflux[i+nif] >= 0) ? h.nnc_cells(i,0) : h.nnc_cells(i,1);
s.push_back(Triplet(i+nif,c,Scalar(1)));
}
}
// Assemble explicit selector operator.
select_.resize(num_connections, numCells(g));
select_.setFromTriplets(s.begin(), s.end());
}
/// Apply selector to multiple per-cell quantities.
std::vector<ADB>
select(const std::vector<ADB>& xc) const
{
// Absence of counter-current flow means that the same
// selector applies to all quantities, 'x', defined per
// cell.
std::vector<ADB> xf; xf.reserve(xc.size());
for (typename std::vector<ADB>::const_iterator
b = xc.begin(), e = xc.end(); b != e; ++b)
{
xf.push_back(select_ * (*b));
}
return xf;
}
/// Apply selector to single per-cell ADB quantity.
ADB select(const ADB& xc) const
{
return select_*xc;
}
/// Apply selector to single per-cell constant quantity.
typename ADB::V select(const typename ADB::V& xc) const
{
return (select_*xc.matrix()).array();
}
private:
Eigen::SparseMatrix<double> select_;
};
namespace {
template <typename Scalar, class IntVec>
typename Eigen::SparseMatrix<Scalar>
constructSupersetSparseMatrix(const int full_size, const IntVec& indices)
{
const int subset_size = indices.size();
if (subset_size == 0) {
Eigen::SparseMatrix<Scalar> mat(full_size, 0);
return mat;
}
Eigen::SparseMatrix<Scalar> mat(full_size, subset_size);
mat.reserve(Eigen::VectorXi::Constant(subset_size, 1));
for (int i = 0; i < subset_size; ++i) {
mat.insert(indices[i], i) = 1;
}
return mat;
}
} // anon namespace
/// Returns x(indices).
template <typename Scalar, class IntVec>
Eigen::Array<Scalar, Eigen::Dynamic, 1>
subset(const Eigen::Array<Scalar, Eigen::Dynamic, 1>& x,
const IntVec& indices)
{
typedef typename Eigen::Array<Scalar, Eigen::Dynamic, 1>::Index Index;
const Index size = indices.size();
Eigen::Array<Scalar, Eigen::Dynamic, 1> ret( size );
for( Index i=0; i<size; ++i )
ret[ i ] = x[ indices[ i ] ];
return std::move(ret);
}
/// Returns x(indices).
template <typename Scalar, class IntVec>
AutoDiffBlock<Scalar>
subset(const AutoDiffBlock<Scalar>& x,
const IntVec& indices)
{
const Eigen::SparseMatrix<Scalar> sub
= constructSupersetSparseMatrix<Scalar>(x.value().size(), indices).transpose().eval();
return sub * x;
}
/// Returns v where v(indices) == x, v(!indices) == 0 and v.size() == n.
template <typename Scalar, class IntVec>
AutoDiffBlock<Scalar>
superset(const AutoDiffBlock<Scalar>& x,
const IntVec& indices,
const int n)
{
return constructSupersetSparseMatrix<Scalar>(n, indices) * x;
}
/// Returns v where v(indices) == x, v(!indices) == 0 and v.size() == n.
template <typename Scalar, class IntVec>
Eigen::Array<Scalar, Eigen::Dynamic, 1>
superset(const Eigen::Array<Scalar, Eigen::Dynamic, 1>& x,
const IntVec& indices,
const int n)
{
return constructSupersetSparseMatrix<Scalar>(n, indices) * x.matrix();
}
/// Construct square sparse matrix with the
/// elements of d on the diagonal.
/// Need to mark this as inline since it is defined in a header and not a template.
inline
Eigen::SparseMatrix<double>
spdiag(const AutoDiffBlock<double>::V& d)
{
typedef Eigen::SparseMatrix<double> M;
const int n = d.size();
M mat(n, n);
mat.reserve(Eigen::ArrayXi::Ones(n, 1));
for (M::Index i = 0; i < n; ++i) {
mat.insert(i, i) = d[i];
}
return mat;
}
/// Selection. Choose first of two elements if selection basis element is nonnegative.
template <typename Scalar>
class Selector {
public:
typedef AutoDiffBlock<Scalar> ADB;
enum CriterionForLeftElement { GreaterEqualZero, GreaterZero, Zero, NotEqualZero, LessZero, LessEqualZero, NotNaN, NotNaNInf };
Selector(const typename ADB::V& selection_basis,
CriterionForLeftElement crit = GreaterEqualZero)
{
using std::isnan;
// Define selector structure.
const int n = selection_basis.size();
// Over-reserving so we do not have to count.
left_elems_.reserve(n);
right_elems_.reserve(n);
for (int i = 0; i < n; ++i) {
bool chooseleft = false;
switch (crit) {
case GreaterEqualZero:
chooseleft = selection_basis[i] >= 0.0;
break;
case GreaterZero:
chooseleft = selection_basis[i] > 0.0;
break;
case Zero:
chooseleft = selection_basis[i] == 0.0;
break;
case NotEqualZero:
chooseleft = selection_basis[i] != 0.0;
break;
case LessZero:
chooseleft = selection_basis[i] < 0.0;
break;
case LessEqualZero:
chooseleft = selection_basis[i] <= 0.0;
break;
case NotNaN:
chooseleft = !isnan(selection_basis[i]);
break;
case NotNaNInf:
chooseleft = !isnan(selection_basis[i]) && !std::isinf(selection_basis[i]);
break;
default:
OPM_THROW(std::logic_error, "No such criterion: " << crit);
}
if (chooseleft) {
left_elems_.push_back(i);
} else {
right_elems_.push_back(i);
}
}
}
/// Apply selector to ADB quantities.
ADB select(const ADB& x1, const ADB& x2) const
{
if (right_elems_.empty()) {
return x1;
} else if (left_elems_.empty()) {
return x2;
} else {
return superset(subset(x1, left_elems_), left_elems_, x1.size())
+ superset(subset(x2, right_elems_), right_elems_, x2.size());
}
}
/// Apply selector to ADB quantities.
typename ADB::V select(const typename ADB::V& x1, const typename ADB::V& x2) const
{
if (right_elems_.empty()) {
return x1;
} else if (left_elems_.empty()) {
return x2;
} else {
return superset(subset(x1, left_elems_), left_elems_, x1.size())
+ superset(subset(x2, right_elems_), right_elems_, x2.size());
}
}
private:
std::vector<int> left_elems_;
std::vector<int> right_elems_;
};
/// Returns the input expression, but with all Jacobians collapsed to one.
template <class Matrix>
inline
void
collapseJacs(const AutoDiffBlock<double>& x, Matrix& jacobian)
{
typedef AutoDiffBlock<double> ADB;
const int nb = x.numBlocks();
typedef Eigen::Triplet<double> Tri;
int nnz = 0;
for (int block = 0; block < nb; ++block) {
nnz += x.derivative()[block].nonZeros();
}
std::vector<Tri> t;
t.reserve(nnz);
int block_col_start = 0;
for (int block = 0; block < nb; ++block) {
const ADB::M& jac1 = x.derivative()[block];
Eigen::SparseMatrix<double> jac;
jac1.toSparse(jac);
for (Eigen::SparseMatrix<double>::Index k = 0; k < jac.outerSize(); ++k) {
for (Eigen::SparseMatrix<double>::InnerIterator i(jac, k); i ; ++i) {
if (i.value() != 0.0) {
t.push_back(Tri(i.row(),
i.col() + block_col_start,
i.value()));
}
}
}
block_col_start += jac.cols();
}
// Build final jacobian.
jacobian = Matrix(x.size(), block_col_start);
jacobian.setFromTriplets(t.begin(), t.end());
}
/// Returns the input expression, but with all Jacobians collapsed to one.
inline
AutoDiffBlock<double>
collapseJacs(const AutoDiffBlock<double>& x)
{
Eigen::SparseMatrix<double> comb_jac;
collapseJacs( x, comb_jac );
// Build final jacobian.
typedef AutoDiffBlock<double> ADB;
std::vector<ADB::M> jacs(1);
jacs[0] = AutoDiffMatrix(std::move(comb_jac));
ADB::V val = x.value();
return ADB::function(std::move(val), std::move(jacs));
}
/// Returns the vertical concatenation [ x; y ] of the inputs.
inline
AutoDiffBlock<double>
vertcat(const AutoDiffBlock<double>& x,
const AutoDiffBlock<double>& y)
{
const int nx = x.size();
const int ny = y.size();
const int n = nx + ny;
std::vector<int> xind(nx);
for (int i = 0; i < nx; ++i) {
xind[i] = i;
}
std::vector<int> yind(ny);
for (int i = 0; i < ny; ++i) {
yind[i] = nx + i;
}
return superset(x, xind, n) + superset(y, yind, n);
}
/// Returns the vertical concatenation [ x[0]; x[1]; ...; x[n-1] ] of the inputs.
/// This function also collapses the Jacobian matrices into one like collapsJacs().
inline
AutoDiffBlock<double>
vertcatCollapseJacs(const std::vector<AutoDiffBlock<double> >& x)
{
typedef AutoDiffBlock<double> ADB;
if (x.empty()) {
return ADB::null();
}
// Count sizes, nonzeros.
const int nx = x.size();
int size = 0;
int nnz = 0;
int elem_with_deriv = -1;
int num_blocks = 0;
for (int elem = 0; elem < nx; ++elem) {
size += x[elem].size();
if (x[elem].derivative().empty()) {
// No nnz contributions from this element.
continue;
} else {
if (elem_with_deriv == -1) {
elem_with_deriv = elem;
num_blocks = x[elem].numBlocks();
}
}
if (x[elem].blockPattern() != x[elem_with_deriv].blockPattern()) {
OPM_THROW(std::runtime_error, "vertcatCollapseJacs(): all arguments must have the same block pattern");
}
for (int block = 0; block < num_blocks; ++block) {
nnz += x[elem].derivative()[block].nonZeros();
}
}
int num_cols = 0;
for (int block = 0; block < num_blocks; ++block) {
num_cols += x[elem_with_deriv].derivative()[block].cols();
}
// Build value for result.
ADB::V val(size);
int pos = 0;
for (int elem = 0; elem < nx; ++elem) {
const int loc_size = x[elem].size();
val.segment(pos, loc_size) = x[elem].value();
pos += loc_size;
}
assert(pos == size);
// Return a constant if we have no derivatives at all.
if (num_blocks == 0) {
return ADB::constant(std::move(val));
}
// Set up for batch insertion of all Jacobian elements.
typedef Eigen::SparseMatrix<double> M;
typedef Eigen::Triplet<double> Tri;
std::vector<Tri> t;
t.reserve(nnz);
{
int block_row_start = 0;
M jac;
for (int elem = 0; elem < nx; ++elem) {
int block_col_start = 0;
if (!x[elem].derivative().empty()) {
for (int block = 0; block < num_blocks; ++block) {
x[elem].derivative()[block].toSparse(jac);
for (M::Index k = 0; k < jac.outerSize(); ++k) {
for (M::InnerIterator i(jac, k); i ; ++i) {
t.push_back(Tri(i.row() + block_row_start,
i.col() + block_col_start,
i.value()));
}
}
block_col_start += jac.cols();
}
}
block_row_start += x[elem].size();
}
}
// Build final jacobian.
M comb_jac = M(size, num_cols);
comb_jac.reserve(nnz);
comb_jac.setFromTriplets(t.begin(), t.end());
std::vector<ADB::M> jac(1);
jac[0] = ADB::M(std::move(comb_jac));
// Use move semantics to return result efficiently.
return ADB::function(std::move(val), std::move(jac));
}
class Span
{
public:
explicit Span(const int num)
: num_(num),
stride_(1),
start_(0)
{
}
Span(const int num, const int stride, const int start)
: num_(num),
stride_(stride),
start_(start)
{
}
int operator[](const int i) const
{
assert(i >= 0 && i < num_);
return start_ + i*stride_;
}
int size() const
{
return num_;
}
class SpanIterator
{
public:
SpanIterator(const Span* span, const int index)
: span_(span),
index_(index)
{
}
SpanIterator operator++()
{
++index_;
return *this;
}
SpanIterator operator++(int)
{
SpanIterator before_increment(*this);
++index_;
return before_increment;
}
bool operator<(const SpanIterator& rhs) const
{
assert(span_ == rhs.span_);
return index_ < rhs.index_;
}
bool operator==(const SpanIterator& rhs) const
{
assert(span_ == rhs.span_);
return index_ == rhs.index_;
}
bool operator!=(const SpanIterator& rhs) const
{
assert(span_ == rhs.span_);
return index_ != rhs.index_;
}
int operator*()
{
return (*span_)[index_];
}
private:
const Span* span_;
int index_;
};
typedef SpanIterator iterator;
typedef SpanIterator const_iterator;
SpanIterator begin() const
{
return SpanIterator(this, 0);
}
SpanIterator end() const
{
return SpanIterator(this, num_);
}
bool operator==(const Span& rhs)
{
return num_ == rhs.num_ && start_ == rhs.start_ && stride_ == rhs.stride_;
}
private:
const int num_;
const int stride_;
const int start_;
};
/// Return a vector of (-1.0, 0.0 or 1.0), depending on sign per element.
inline Eigen::ArrayXd sign (const Eigen::ArrayXd& x)
{
const int n = x.size();
Eigen::ArrayXd retval(n);
for (int i = 0; i < n; ++i) {
retval[i] = x[i] < 0.0 ? -1.0 : (x[i] > 0.0 ? 1.0 : 0.0);
}
return retval;
}
} // namespace Opm
#endif // OPM_AUTODIFFHELPERS_HEADER_INCLUDED

View File

@ -1,736 +0,0 @@
/*
Copyright 2014, 2015 SINTEF ICT, Applied Mathematics.
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 3 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 <http://www.gnu.org/licenses/>.
*/
#ifndef OPM_AUTODIFFMATRIX_HEADER_INCLUDED
#define OPM_AUTODIFFMATRIX_HEADER_INCLUDED
#include <opm/common/utility/platform_dependent/disable_warnings.h>
#include <Eigen/Eigen>
#include <Eigen/Sparse>
#include <opm/common/utility/platform_dependent/reenable_warnings.h>
#include <opm/common/ErrorMacros.hpp>
#include <opm/autodiff/fastSparseOperations.hpp>
#include <vector>
namespace Opm
{
/**
* AutoDiffMatrix is a wrapper class that optimizes matrix operations.
* Internally, an AutoDiffMatrix can be either Zero, Identity, Diagonal,
* or Sparse, and we utilize this to perform faster matrix operations.
*/
class AutoDiffMatrix
{
public:
typedef std::vector<double> DiagRep;
typedef Eigen::SparseMatrix<double> SparseRep;
/**
* Creates an empty zero matrix
*/
AutoDiffMatrix()
: type_(Zero),
rows_(0),
cols_(0),
diag_(),
sparse_()
{
}
/**
* Creates a zero matrix with num_rows x num_cols entries
*/
AutoDiffMatrix(const int num_rows, const int num_cols)
: type_(Zero),
rows_(num_rows),
cols_(num_cols),
diag_(),
sparse_()
{
}
/**
* Creates an identity matrix with num_rows_cols x num_rows_cols entries
*/
static AutoDiffMatrix createIdentity(const int num_rows_cols)
{
return AutoDiffMatrix(Identity, num_rows_cols, num_rows_cols);
}
/**
* Creates a diagonal matrix from an Eigen diagonal matrix
*/
explicit AutoDiffMatrix(const Eigen::DiagonalMatrix<double, Eigen::Dynamic>& d)
: type_(Diagonal),
rows_(d.rows()),
cols_(d.cols()),
diag_(d.diagonal().array().data(), d.diagonal().array().data() + d.rows()),
sparse_()
{
assert(rows_ == cols_);
}
/**
* Creates a sparse matrix from an Eigen sparse matrix
*/
explicit AutoDiffMatrix(const Eigen::SparseMatrix<double>& s)
: type_(Sparse),
rows_(s.rows()),
cols_(s.cols()),
diag_(),
sparse_(s)
{
}
AutoDiffMatrix(const AutoDiffMatrix& other) = default;
AutoDiffMatrix& operator=(const AutoDiffMatrix& other) = default;
AutoDiffMatrix(AutoDiffMatrix&& other)
: type_(Zero),
rows_(0),
cols_(0),
diag_(),
sparse_()
{
swap(other);
}
AutoDiffMatrix& operator=(AutoDiffMatrix&& other)
{
swap(other);
return *this;
}
void swap(AutoDiffMatrix& other)
{
std::swap(type_, other.type_);
std::swap(rows_, other.rows_);
std::swap(cols_, other.cols_);
diag_.swap(other.diag_);
sparse_.swap(other.sparse_);
}
/**
* Adds two AutoDiffMatrices. Internally, this function optimizes
* the addition operation based on the structure of the matrix, e.g.,
* adding two zero matrices will obviously yield a zero matrix, and
* so on.
*/
AutoDiffMatrix operator+(const AutoDiffMatrix& rhs) const
{
assert(rows_ == rhs.rows_);
assert(cols_ == rhs.cols_);
switch (type_) {
case Zero:
return rhs;
case Identity:
switch (rhs.type_) {
case Zero:
return *this;
case Identity:
return addII(*this, rhs);
case Diagonal:
return addDI(rhs, *this);
case Sparse:
return addSI(rhs, *this);
default:
OPM_THROW(std::logic_error, "Invalid AutoDiffMatrix type encountered: " << rhs.type_);
}
case Diagonal:
switch (rhs.type_) {
case Zero:
return *this;
case Identity:
return addDI(*this, rhs);
case Diagonal:
return addDD(*this, rhs);
case Sparse:
return addSD(rhs, *this);
default:
OPM_THROW(std::logic_error, "Invalid AutoDiffMatrix type encountered: " << rhs.type_);
}
case Sparse:
switch (rhs.type_) {
case Zero:
return *this;
case Identity:
return addSI(*this, rhs);
case Diagonal:
return addSD(*this, rhs);
case Sparse:
return addSS(*this, rhs);
default:
OPM_THROW(std::logic_error, "Invalid AutoDiffMatrix type encountered: " << rhs.type_);
}
default:
OPM_THROW(std::logic_error, "Invalid AutoDiffMatrix type encountered: " << rhs.type_);
}
}
/**
* Multiplies two AutoDiffMatrices. Internally, this function optimizes
* the multiplication operation based on the structure of the matrix, e.g.,
* multiplying M with a zero matrix will obviously yield a zero matrix.
*/
AutoDiffMatrix operator*(const AutoDiffMatrix& rhs) const
{
assert(cols_ == rhs.rows_);
switch (type_) {
case Zero:
return AutoDiffMatrix(rows_, rhs.cols_);
case Identity:
return rhs;
case Diagonal:
switch (rhs.type_) {
case Zero:
return AutoDiffMatrix(rows_, rhs.cols_);
case Identity:
return *this;
case Diagonal:
return mulDD(*this, rhs);
case Sparse:
return mulDS(*this, rhs);
default:
OPM_THROW(std::logic_error, "Invalid AutoDiffMatrix type encountered: " << rhs.type_);
}
case Sparse:
switch (rhs.type_) {
case Zero:
return AutoDiffMatrix(rows_, rhs.cols_);
case Identity:
return *this;
case Diagonal:
return mulSD(*this, rhs);
case Sparse:
return mulSS(*this, rhs);
default:
OPM_THROW(std::logic_error, "Invalid AutoDiffMatrix type encountered: " << rhs.type_);
}
default:
OPM_THROW(std::logic_error, "Invalid AutoDiffMatrix type encountered: " << rhs.type_);
}
}
AutoDiffMatrix& operator+=(const AutoDiffMatrix& rhs)
{
if( type_ == Sparse && rhs.type_ == Sparse )
{
fastSparseAdd( sparse_, rhs.sparse_ );
}
else {
*this = *this + rhs;
}
return *this;
}
AutoDiffMatrix& operator-=(const AutoDiffMatrix& rhs)
{
if( type_ == Sparse && rhs.type_ == Sparse )
{
fastSparseSubstract( sparse_, rhs.sparse_ );
}
else {
*this = *this + (rhs * -1.0);
}
return *this;
}
/**
* Multiplies an AutoDiffMatrix with a scalar. Optimizes internally
* by exploiting that e.g., an identity matrix multiplied by a scalar x
* yields a diagonal matrix with x the diagonal.
*/
AutoDiffMatrix operator*(const double rhs) const
{
switch (type_) {
case Zero:
return *this;
case Identity:
{
AutoDiffMatrix retval(*this);
retval.type_ = Diagonal;
retval.diag_.assign(rows_, rhs);
return retval;
}
case Diagonal:
{
AutoDiffMatrix retval(*this);
for (double& elem : retval.diag_) {
elem *= rhs;
}
return retval;
}
case Sparse:
{
AutoDiffMatrix retval(*this);
retval.sparse_ *= rhs;
return retval;
}
default:
OPM_THROW(std::logic_error, "Invalid AutoDiffMatrix type encountered: " << type_);
}
}
/**
* Divides an AutoDiffMatrix by a scalar. Optimizes internally
* by exploiting that e.g., an identity matrix divided by a scalar x
* yields a diagonal matrix with 1/x on the diagonal.
*/
AutoDiffMatrix operator/(const double rhs) const
{
switch (type_) {
case Zero:
return *this;
case Identity:
{
AutoDiffMatrix retval(*this);
retval.type_ = Diagonal;
retval.diag_.assign(rows_, 1.0/rhs);
return retval;
}
case Diagonal:
{
AutoDiffMatrix retval(*this);
for (double& elem : retval.diag_) {
elem /= rhs;
}
return retval;
}
case Sparse:
{
AutoDiffMatrix retval(*this);
retval.sparse_ /= rhs;
return retval;
}
default:
OPM_THROW(std::logic_error, "Invalid AutoDiffMatrix type encountered: " << type_);
}
}
/**
* Multiplies an AutoDiffMatrix with a vector. Optimizes internally
* by exploiting that e.g., an identity matrix multiplied by a vector
* yields the vector itself.
*/
Eigen::VectorXd operator*(const Eigen::VectorXd& rhs) const
{
assert(cols_ == rhs.size());
switch (type_) {
case Zero:
return Eigen::VectorXd::Zero(rows_);
case Identity:
return rhs;
case Diagonal:
{
const Eigen::VectorXd d = Eigen::Map<const Eigen::VectorXd>(diag_.data(), rows_);
return d.cwiseProduct(rhs);
}
case Sparse:
return sparse_ * rhs;
default:
OPM_THROW(std::logic_error, "Invalid AutoDiffMatrix type encountered: " << type_);
}
}
// Add identity to identity
static AutoDiffMatrix addII(const AutoDiffMatrix& lhs, const AutoDiffMatrix& rhs)
{
assert(lhs.type_ == Identity);
assert(rhs.type_ == Identity);
AutoDiffMatrix retval;
retval.type_ = Diagonal;
retval.rows_ = lhs.rows_;
retval.cols_ = rhs.cols_;
retval.diag_.assign(lhs.rows_, 2.0);
return retval;
}
// Add diagonal to identity
static AutoDiffMatrix addDI(const AutoDiffMatrix& lhs, const AutoDiffMatrix& rhs)
{
static_cast<void>(rhs); // Silence release-mode warning.
assert(lhs.type_ == Diagonal);
assert(rhs.type_ == Identity);
AutoDiffMatrix retval = lhs;
for (int r = 0; r < lhs.rows_; ++r) {
retval.diag_[r] += 1.0;
}
return retval;
}
// Add diagonal to diagonal
static AutoDiffMatrix addDD(const AutoDiffMatrix& lhs, const AutoDiffMatrix& rhs)
{
assert(lhs.type_ == Diagonal);
assert(rhs.type_ == Diagonal);
AutoDiffMatrix retval = lhs;
for (int r = 0; r < lhs.rows_; ++r) {
retval.diag_[r] += rhs.diag_[r];
}
return retval;
}
// Add sparse to identity
static AutoDiffMatrix addSI(const AutoDiffMatrix& lhs, const AutoDiffMatrix& rhs)
{
static_cast<void>(rhs); // Silence release-mode warning.
assert(lhs.type_ == Sparse);
assert(rhs.type_ == Identity);
AutoDiffMatrix retval = lhs;
retval.sparse_ += spdiag(Eigen::VectorXd::Ones(lhs.rows_));;
return retval;
}
// Add sparse to diagonal
static AutoDiffMatrix addSD(const AutoDiffMatrix& lhs, const AutoDiffMatrix& rhs)
{
assert(lhs.type_ == Sparse);
assert(rhs.type_ == Diagonal);
AutoDiffMatrix retval = lhs;
retval.sparse_ += spdiag(rhs.diag_);
return retval;
}
// Add sparse to sparse
static AutoDiffMatrix addSS(const AutoDiffMatrix& lhs, const AutoDiffMatrix& rhs)
{
assert(lhs.type_ == Sparse);
assert(rhs.type_ == Sparse);
AutoDiffMatrix retval = lhs;
retval.sparse_ += rhs.sparse_;
return retval;
}
// Multiply diagonal with diagonal
static AutoDiffMatrix mulDD(const AutoDiffMatrix& lhs, const AutoDiffMatrix& rhs)
{
assert(lhs.type_ == Diagonal);
assert(rhs.type_ == Diagonal);
AutoDiffMatrix retval = lhs;
for (int r = 0; r < lhs.rows_; ++r) {
retval.diag_[r] *= rhs.diag_[r];
}
return retval;
}
// Multiply diagonal with sparse
static AutoDiffMatrix mulDS(const AutoDiffMatrix& lhs, const AutoDiffMatrix& rhs)
{
assert(lhs.type_ == Diagonal);
assert(rhs.type_ == Sparse);
AutoDiffMatrix retval;
retval.type_ = Sparse;
retval.rows_ = lhs.rows_;
retval.cols_ = rhs.cols_;
fastDiagSparseProduct(lhs.diag_, rhs.sparse_, retval.sparse_);
return retval;
}
// Multiply sparse with diagonal
static AutoDiffMatrix mulSD(const AutoDiffMatrix& lhs, const AutoDiffMatrix& rhs)
{
assert(lhs.type_ == Sparse);
assert(rhs.type_ == Diagonal);
AutoDiffMatrix retval;
retval.type_ = Sparse;
retval.rows_ = lhs.rows_;
retval.cols_ = rhs.cols_;
fastSparseDiagProduct(lhs.sparse_, rhs.diag_, retval.sparse_);
return retval;
}
// Multiply sparse with sparse
static AutoDiffMatrix mulSS(const AutoDiffMatrix& lhs, const AutoDiffMatrix& rhs)
{
assert(lhs.type_ == Sparse);
assert(rhs.type_ == Sparse);
AutoDiffMatrix retval;
retval.type_ = Sparse;
retval.rows_ = lhs.rows_;
retval.cols_ = rhs.cols_;
fastSparseProduct(lhs.sparse_, rhs.sparse_, retval.sparse_);
return retval;
}
/**
* Converts the AutoDiffMatrix to an Eigen SparseMatrix.This might be
* an expensive operation to perform for e.g., an identity matrix or a
* diagonal matrix.
*/
template<class Scalar, int Options, class Index>
void toSparse(Eigen::SparseMatrix<Scalar, Options, Index>& s) const
{
switch (type_) {
case Zero:
s = Eigen::SparseMatrix<Scalar, Options, Index>(rows_, cols_);
return;
case Identity:
s = spdiag(Eigen::VectorXd::Ones(rows_));
return;
case Diagonal:
s = spdiag(diag_);
return;
case Sparse:
s = sparse_;
return;
}
}
/**
* Returns number of rows in the matrix
*/
int rows() const
{
return rows_;
}
/**
* Returns number of columns in the matrix
*/
int cols() const
{
return cols_;
}
/**
* Returns number of non-zero elements in the matrix. Optimizes internally
* by exploiting that e.g., an n*n identity matrix has n non-zeros.
* Note that an n*n diagonal matrix is defined to have n non-zeros, even though
* several diagonal elements might be 0.0.
*/
int nonZeros() const
{
switch (type_) {
case Zero:
return 0;
case Identity:
return rows_;
case Diagonal:
return rows_;
case Sparse:
return sparse_.nonZeros();
default:
OPM_THROW(std::logic_error, "Invalid AutoDiffMatrix type encountered: " << type_);
}
}
/**
* Returns element (row, col) in the matrix
*/
double coeff(const int row, const int col) const
{
switch (type_) {
case Zero:
return 0.0;
case Identity:
return (row == col) ? 1.0 : 0.0;
case Diagonal:
return (row == col) ? diag_[row] : 0.0;
case Sparse:
return sparse_.coeff(row, col);
default:
OPM_THROW(std::logic_error, "Invalid AutoDiffMatrix type encountered: " << type_);
}
}
/**
* Returns the sparse representation of this matrix. Note that this might
* be an expensive operation to perform if the internal structure is not
* sparse.
*/
const SparseRep& getSparse() const {
if (type_ != Sparse) {
/**
* If we are not a sparse matrix, our internal variable sparse_
* is undefined, and hence changing it so that it happens to be
* a sparse representation of our true data does not change our
* true data, and hence justifies that we do not really violate
* the const qualifier.
*/
SparseRep& mutable_sparse = const_cast<SparseRep&>(sparse_);
toSparse(mutable_sparse);
}
return sparse_;
}
private:
enum AudoDiffMatrixType { Zero, Identity, Diagonal, Sparse };
AudoDiffMatrixType type_; //< Type of matrix
int rows_; //< Number of rows
int cols_; //< Number of columns
DiagRep diag_; //< Diagonal representation (only if type==Diagonal)
SparseRep sparse_; //< Sparse representation (only if type==Sparse)
/**
* Constructor which sets all members
*/
AutoDiffMatrix(AudoDiffMatrixType type, int rows_arg, int cols_arg,
DiagRep diag=DiagRep(), SparseRep sparse=SparseRep())
: type_(type),
rows_(rows_arg),
cols_(cols_arg),
diag_(diag),
sparse_(sparse)
{
}
/**
* Creates a sparse diagonal matrix from d.
* Typical use is to convert a standard vector to an
* Eigen sparse matrix.
*/
template <class V>
static inline
SparseRep
spdiag(const V& d)
{
const int n = d.size();
SparseRep mat(n, n);
mat.reserve(Eigen::ArrayXi::Ones(n, 1));
for (SparseRep::Index i = 0; i < n; ++i) {
if (d[i] != 0.0) {
mat.insert(i, i) = d[i];
}
}
return mat;
}
};
/**
* Utility function to lessen code changes required elsewhere.
*/
inline void fastSparseProduct(const AutoDiffMatrix& lhs, const AutoDiffMatrix& rhs, AutoDiffMatrix& res)
{
res = lhs * rhs;
}
/**
* Utility function to lessen code changes required elsewhere.
*/
inline void fastSparseProduct(const Eigen::SparseMatrix<double>& lhs, const AutoDiffMatrix& rhs, AutoDiffMatrix& res)
{
res = AutoDiffMatrix(lhs) * rhs;
}
/**
* Multiplies an Eigen sparse matrix with an AutoDiffMatrix.
*/
inline AutoDiffMatrix operator*(const Eigen::SparseMatrix<double>& lhs, const AutoDiffMatrix& rhs)
{
AutoDiffMatrix retval;
fastSparseProduct(lhs, rhs, retval);
return retval;
}
} // namespace Opm
#endif // OPM_AUTODIFFMATRIX_HEADER_INCLUDED

View File

@ -1,87 +0,0 @@
/*
Copyright 2013, 2015 SINTEF ICT, Applied Mathematics.
Copyright 2014, 2015 Dr. Blatt - HPC-Simulation-Software & Services
Copyright 2014, 2015 Statoil ASA.
Copyright 2015 NTNU
Copyright 2015 IRIS AS
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 3 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 <http://www.gnu.org/licenses/>.
*/
#ifndef OPM_BLACKOILLEGACYDETAILS_HEADER_INCLUDED
#define OPM_BLACKOILLEGACYDETAILS_HEADER_INCLUDED
#include <opm/core/linalg/ParallelIstlInformation.hpp>
namespace Opm {
namespace detail {
/// \brief Compute the L-infinity norm of a vector
/// \warn This function is not suitable to compute on the well equations.
/// \param a The container to compute the infinity norm on.
/// It has to have one entry for each cell.
/// \param info In a parallel this holds the information about the data distribution.
template <class ADB>
inline
double infinityNorm( const ADB& a, const boost::any& pinfo = boost::any() )
{
static_cast<void>(pinfo); // Suppress warning in non-MPI case.
#if HAVE_MPI
if ( pinfo.type() == typeid(ParallelISTLInformation) )
{
const ParallelISTLInformation& real_info =
boost::any_cast<const ParallelISTLInformation&>(pinfo);
double result=0;
real_info.computeReduction(a.value(), Reduction::makeLInfinityNormFunctor<double>(), result);
return result;
}
else
#endif
{
if( a.value().size() > 0 ) {
return a.value().matrix().template lpNorm<Eigen::Infinity> ();
}
else { // this situation can occur when no wells are present
return 0.0;
}
}
}
/// \brief Compute the L-infinity norm of a vector representing a well equation.
/// \param a The container to compute the infinity norm on.
/// \param info In a parallel this holds the information about the data distribution.
template <class ADB>
inline
double infinityNormWell( const ADB& a, const boost::any& pinfo )
{
static_cast<void>(pinfo); // Suppress warning in non-MPI case.
double result=0;
if( a.value().size() > 0 ) {
result = a.value().matrix().template lpNorm<Eigen::Infinity> ();
}
#if HAVE_MPI
if ( pinfo.type() == typeid(ParallelISTLInformation) )
{
const ParallelISTLInformation& real_info =
boost::any_cast<const ParallelISTLInformation&>(pinfo);
result = real_info.communicator().max(result);
}
#endif
return result;
}
} // namespace detail
} // namespace Opm
#endif // OPM_BLACKOILDETAILS_HEADER_INCLUDED

View File

@ -1,96 +0,0 @@
/*
Copyright 2013, 2015 SINTEF ICT, Applied Mathematics.
Copyright 2014, 2015 Statoil ASA.
Copyright 2014, 2015 Dr. Markus Blatt - HPC-Simulation-Software & Services
Copyright 2015 NTNU
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 3 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 <http://www.gnu.org/licenses/>.
*/
#ifndef OPM_BLACKOILMODEL_HEADER_INCLUDED
#define OPM_BLACKOILMODEL_HEADER_INCLUDED
#include <opm/autodiff/BlackoilModelBase.hpp>
#include <opm/core/simulator/BlackoilState.hpp>
#include <opm/autodiff/WellStateFullyImplicitBlackoil.hpp>
#include <opm/autodiff/StandardWells.hpp>
#include <opm/autodiff/BlackoilModelParameters.hpp>
namespace Opm {
/// A model implementation for three-phase black oil.
///
/// The simulator is capable of handling three-phase problems
/// where gas can be dissolved in oil and vice versa. It
/// uses an industry-standard TPFA discretization with per-phase
/// upwind weighting of mobilities.
///
/// It uses automatic differentiation via the class AutoDiffBlock
/// to simplify assembly of the jacobian matrix.
template<class Grid>
class BlackoilModel : public BlackoilModelBase<Grid, StandardWells, BlackoilModel<Grid> >
{
public:
typedef BlackoilModelBase<Grid, StandardWells, BlackoilModel<Grid> > Base;
/// Construct the model. It will retain references to the
/// arguments of this functions, and they are expected to
/// remain in scope for the lifetime of the solver.
/// \param[in] param parameters
/// \param[in] grid grid data structure
/// \param[in] fluid fluid properties
/// \param[in] geo rock properties
/// \param[in] rock_comp_props if non-null, rock compressibility properties
/// \param[in] wells_arg well structure
/// \param[in] linsolver linear solver
/// \param[in] eclState eclipse state
/// \param[in] has_disgas turn on dissolved gas
/// \param[in] has_vapoil turn on vaporized oil feature
/// \param[in] terminal_output request output to cout/cerr
BlackoilModel(const typename Base::ModelParameters& param,
const Grid& grid,
const BlackoilPropsAdFromDeck& fluid,
const DerivedGeology& geo,
const RockCompressibility* rock_comp_props,
const StandardWells& std_wells,
const NewtonIterationBlackoilInterface& linsolver,
std::shared_ptr< const Opm::EclipseState > eclState,
std::shared_ptr< const Opm::Schedule> schedule,
std::shared_ptr< const Opm::SummaryConfig> summary_config,
const bool has_disgas,
const bool has_vapoil,
const bool terminal_output)
: Base(param, grid, fluid, geo, rock_comp_props, std_wells, linsolver,
eclState, schedule, summary_config, has_disgas, has_vapoil, terminal_output)
{
}
};
/// Providing types by template specialisation of ModelTraits for BlackoilModel.
template <class Grid>
struct ModelTraits< BlackoilModel<Grid> >
{
typedef BlackoilState ReservoirState;
typedef WellStateFullyImplicitBlackoil WellState;
typedef BlackoilModelParameters ModelParameters;
typedef DefaultBlackoilSolutionState SolutionState;
};
} // namespace Opm
#endif // OPM_BLACKOILMODEL_HEADER_INCLUDED

View File

@ -1,583 +0,0 @@
/*
Copyright 2013, 2015 SINTEF ICT, Applied Mathematics.
Copyright 2014, 2015 Statoil ASA.
Copyright 2014, 2015 Dr. Markus Blatt - HPC-Simulation-Software & Services
Copyright 2015 NTNU
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 3 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 <http://www.gnu.org/licenses/>.
*/
#ifndef OPM_BLACKOILMODELBASE_HEADER_INCLUDED
#define OPM_BLACKOILMODELBASE_HEADER_INCLUDED
#include <cassert>
#include <opm/autodiff/AutoDiffBlock.hpp>
#include <opm/autodiff/AutoDiffHelpers.hpp>
#include <opm/autodiff/BlackoilPropsAdFromDeck.hpp>
#include <opm/autodiff/LinearisedBlackoilResidual.hpp>
#include <opm/autodiff/NewtonIterationBlackoilInterface.hpp>
#include <opm/autodiff/BlackoilModelEnums.hpp>
#include <opm/autodiff/VFPInjPropertiesLegacy.hpp>
#include <opm/autodiff/VFPProdPropertiesLegacy.hpp>
#include <opm/autodiff/VFPProperties.hpp>
#include <opm/autodiff/RateConverter.hpp>
#include <opm/autodiff/IterationReport.hpp>
#include <opm/autodiff/DefaultBlackoilSolutionState.hpp>
#include <opm/parser/eclipse/EclipseState/Grid/NNC.hpp>
#include <opm/simulators/timestepping/SimulatorTimerInterface.hpp>
#include <opm/core/simulator/SimulatorReport.hpp>
#include <opm/common/data/SimulationDataContainer.hpp>
#include <array>
struct Wells;
namespace Opm {
class ParameterGroup;
class DerivedGeology;
class RockCompressibility;
class NewtonIterationBlackoilInterface;
/// Traits to encapsulate the types used by classes using or
/// extending this model. Forward declared here, must be
/// specialised for each concrete model class.
template <class ConcreteModel>
struct ModelTraits;
/// A model implementation for three-phase black oil.
///
/// The simulator is capable of handling three-phase problems
/// where gas can be dissolved in oil and vice versa. It
/// uses an industry-standard TPFA discretization with per-phase
/// upwind weighting of mobilities.
///
/// It uses automatic differentiation via the class AutoDiffBlock
/// to simplify assembly of the jacobian matrix.
/// \tparam Grid UnstructuredGrid or CpGrid.
/// \tparam WellModel WellModel employed.
/// \tparam Implementation Provides concrete state types.
template<class Grid, class WellModel, class Implementation>
class BlackoilModelBase
{
public:
// --------- Types and enums ---------
typedef AutoDiffBlock<double> ADB;
typedef ADB::V V;
typedef ADB::M M;
struct ReservoirResidualQuant {
ReservoirResidualQuant();
std::vector<ADB> accum; // Accumulations
ADB mflux; // Mass flux (surface conditions)
ADB b; // Reciprocal FVF
ADB mu; // Viscosities
ADB rho; // Densities
ADB kr; // Permeabilities
ADB dh; // Pressure drop across int. interfaces
ADB mob; // Phase mobility (per cell)
};
struct SimulatorData : public Opm::FIPDataEnums {
SimulatorData(int num_phases);
using Opm::FIPDataEnums :: FipId ;
using Opm::FIPDataEnums :: fipValues ;
std::vector<ReservoirResidualQuant> rq;
ADB rsSat; // Saturated gas-oil ratio
ADB rvSat; // Saturated oil-gas ratio
std::vector<double> soMax; // Maximum oil saturation
std::vector<double> Pb; // Bubble point pressure
std::vector<double> Pd; // Dew point pressure
//Hysteresis parameters
std::vector<double> krnswdc_ow;
std::vector<double> krnswdc_go;
std::vector<double> pcswmdc_ow;
std::vector<double> pcswmdc_go;
std::array<V, fipValues> fip;
};
typedef Opm::FIPData FIPDataType;
typedef typename ModelTraits<Implementation>::ReservoirState ReservoirState;
typedef typename ModelTraits<Implementation>::WellState WellState;
typedef typename ModelTraits<Implementation>::ModelParameters ModelParameters;
typedef typename ModelTraits<Implementation>::SolutionState SolutionState;
// For the conversion between the surface volume rate and resrevoir voidage rate
using RateConverterType = RateConverter::
SurfaceToReservoirVoidage<BlackoilPropsAdFromDeck::FluidSystem, std::vector<int> >;
// --------- Public methods ---------
/// Construct the model. It will retain references to the
/// arguments of this functions, and they are expected to
/// remain in scope for the lifetime of the solver.
/// \param[in] param parameters
/// \param[in] grid grid data structure
/// \param[in] fluid fluid properties
/// \param[in] geo rock properties
/// \param[in] rock_comp_props if non-null, rock compressibility properties
/// \param[in] wells well structure
/// \param[in] vfp_properties Vertical flow performance tables
/// \param[in] linsolver linear solver
/// \param[in] eclState eclipse state
/// \param[in] has_disgas turn on dissolved gas
/// \param[in] has_vapoil turn on vaporized oil feature
/// \param[in] terminal_output request output to cout/cerr
BlackoilModelBase(const ModelParameters& param,
const Grid& grid ,
const BlackoilPropsAdFromDeck& fluid,
const DerivedGeology& geo ,
const RockCompressibility* rock_comp_props,
const WellModel& well_model,
const NewtonIterationBlackoilInterface& linsolver,
std::shared_ptr< const EclipseState > eclState,
std::shared_ptr< const Schedule> schedule,
std::shared_ptr< const SummaryConfig> summary_config,
const bool has_disgas,
const bool has_vapoil,
const bool terminal_output);
/// \brief Set threshold pressures that prevent or reduce flow.
/// This prevents flow across faces if the potential
/// difference is less than the threshold. If the potential
/// difference is greater, the threshold value is subtracted
/// before calculating flow. This is treated symmetrically, so
/// flow is prevented or reduced in both directions equally.
/// \param[in] threshold_pressures_by_face array of size equal to the number of faces
/// of the grid passed in the constructor.
void setThresholdPressures(const std::vector<double>& threshold_pressures_by_face);
/// Called once before each time step.
/// \param[in] timer simulation timer
/// \param[in, out] reservoir_state reservoir state variables
/// \param[in, out] well_state well state variables
void prepareStep(const SimulatorTimerInterface& timer,
const ReservoirState& reservoir_state,
const WellState& well_state);
/// Called once per nonlinear iteration.
/// This model will perform a Newton-Raphson update, changing reservoir_state
/// and well_state. It will also use the nonlinear_solver to do relaxation of
/// updates if necessary.
/// \param[in] iteration should be 0 for the first call of a new timestep
/// \param[in] timer simulation timer
/// \param[in] nonlinear_solver nonlinear solver used (for oscillation/relaxation control)
/// \param[in, out] reservoir_state reservoir state variables
/// \param[in, out] well_state well state variables
template <class NonlinearSolverType>
SimulatorReport nonlinearIteration(const int iteration,
const SimulatorTimerInterface& timer,
NonlinearSolverType& nonlinear_solver,
ReservoirState& reservoir_state,
WellState& well_state);
/// Called once after each time step.
/// In this class, this function does nothing.
/// \param[in] timer simulation timer
/// \param[in, out] reservoir_state reservoir state variables
/// \param[in, out] well_state well state variables
void afterStep(const SimulatorTimerInterface& timer,
ReservoirState& reservoir_state,
WellState& well_state);
/// Assemble the residual and Jacobian of the nonlinear system.
/// \param[in] reservoir_state reservoir state variables
/// \param[in, out] well_state well state variables
/// \param[in] initial_assembly pass true if this is the first call to assemble() in this timestep
SimulatorReport
assemble(const ReservoirState& reservoir_state,
WellState& well_state,
const bool initial_assembly);
/// \brief Compute the residual norms of the mass balance for each phase,
/// the well flux, and the well equation.
/// \return a vector that contains for each phase the norm of the mass balance
/// and afterwards the norm of the residual of the well flux and the well equation.
std::vector<double> computeResidualNorms() const;
/// \brief compute the relative change between to simulation states
// \return || u^n+1 - u^n || / || u^n+1 ||
double relativeChange( const SimulationDataContainer& previous, const SimulationDataContainer& current ) const;
/// The size (number of unknowns) of the nonlinear system of equations.
int sizeNonLinear() const;
/// Number of linear iterations used in last call to solveJacobianSystem().
int linearIterationsLastSolve() const;
/// Solve the Jacobian system Jx = r where J is the Jacobian and
/// r is the residual.
V solveJacobianSystem() const;
/// Apply an update to the primary variables, chopped if appropriate.
/// \param[in] dx updates to apply to primary variables
/// \param[in, out] reservoir_state reservoir state variables
/// \param[in, out] well_state well state variables
void updateState(const V& dx,
ReservoirState& reservoir_state,
WellState& well_state);
/// Return true if this is a parallel run.
bool isParallel() const;
/// Return true if output to cout is wanted.
bool terminalOutputEnabled() const;
/// Compute convergence based on total mass balance (tol_mb) and maximum
/// residual mass balance (tol_cnv).
/// \param[in] timer simulation timer
/// \param[in] iteration current iteration number
bool getConvergence(const SimulatorTimerInterface& timer, const int iteration);
/// The number of active fluid phases in the model.
int numPhases() const;
/// The number of active materials in the model.
/// This should be equal to the number of material balance
/// equations.
int numMaterials() const;
/// The name of an active material in the model.
/// It is required that material_index < numMaterials().
const std::string& materialName(int material_index) const;
/// Update the scaling factors for mass balance equations
void updateEquationsScaling();
/// return the WellModel object
WellModel& wellModel() { return well_model_; }
const WellModel& wellModel() const { return well_model_; }
/// Return reservoir simulation data (for output functionality)
const SimulatorData& getSimulatorData(const SimulationDataContainer&) const {
return sd_;
}
/// Return fluid-in-place data (for output functionality)
FIPDataType getFIPData() const {
return FIPDataType( sd_.fip );
}
/// Compute fluid in place.
/// \param[in] ReservoirState
/// \param[in] FIPNUM for active cells not global cells.
/// \return fluid in place, number of fip regions, each region contains 5 values which are liquid, vapour, water, free gas and dissolved gas.
std::vector<std::vector<double> >
computeFluidInPlace(const ReservoirState& x,
const std::vector<int>& fipnum);
/// Function to compute the resevoir voidage for the production wells.
/// TODO: Probably should go to well model, while we then have duplications there for two Well Models.
/// With time, it looks like probably we will introduce a base class for Well Models.
void computeWellVoidageRates(const ReservoirState& reservoir_state,
const WellState& well_state,
std::vector<double>& well_voidage_rates,
std::vector<double>& voidage_conversion_coeffs);
void applyVREPGroupControl(const ReservoirState& reservoir_state,
WellState& well_state);
/// return the statistics if the nonlinearIteration() method failed.
///
/// NOTE: for the flow_legacy simulator family this method is a stub, i.e. the
/// failure report object will *not* contain any meaningful data.
const SimulatorReport& failureReport() const
{ return failureReport_; }
protected:
// --------- Types and enums ---------
typedef Eigen::Array<double,
Eigen::Dynamic,
Eigen::Dynamic,
Eigen::RowMajor> DataBlock;
// --------- Data members ---------
SimulatorReport failureReport_;
const Grid& grid_;
const BlackoilPropsAdFromDeck& fluid_;
const DerivedGeology& geo_;
const RockCompressibility* rock_comp_props_;
VFPProperties<VFPInjPropertiesLegacy,VFPProdPropertiesLegacy> vfp_properties_;
const NewtonIterationBlackoilInterface& linsolver_;
// For each canonical phase -> true if active
const std::vector<bool> active_;
// Size = # active phases. Maps active -> canonical phase indices.
const std::vector<int> canph_;
const std::vector<int> cells_; // All grid cells
HelperOps ops_;
const bool has_disgas_;
const bool has_vapoil_;
ModelParameters param_;
bool use_threshold_pressure_;
V threshold_pressures_by_connection_;
mutable SimulatorData sd_;
std::vector<PhasePresence> phaseCondition_;
// Well Model
WellModel well_model_;
V isRs_;
V isRv_;
V isSg_;
LinearisedBlackoilResidual residual_;
/// \brief Whether we print something to std::cout
bool terminal_output_;
/// \brief The number of cells of the global grid.
int global_nc_;
V pvdt_;
std::vector<std::string> material_name_;
std::vector<std::vector<double>> residual_norms_history_;
double current_relaxation_;
V dx_old_;
// rate converter between the surface volume rates and reservoir voidage rates
RateConverterType rate_converter_;
// --------- Protected methods ---------
/// Access the most-derived class used for
/// static polymorphism (CRTP).
Implementation& asImpl()
{
return static_cast<Implementation&>(*this);
}
/// Access the most-derived class used for
/// static polymorphism (CRTP).
const Implementation& asImpl() const
{
return static_cast<const Implementation&>(*this);
}
/// return the Well struct in the WellModel
const Wells& wells() const { return well_model_.wells(); }
/// return true if wells are available in the reservoir
bool wellsActive() const { return well_model_.wellsActive(); }
/// return true if wells are available on this process
bool localWellsActive() const { return well_model_.localWellsActive(); }
void
makeConstantState(SolutionState& state) const;
SolutionState
variableState(const ReservoirState& x,
const WellState& xw) const;
std::vector<V>
variableStateInitials(const ReservoirState& x,
const WellState& xw) const;
void
variableReservoirStateInitials(const ReservoirState& x,
std::vector<V>& vars0) const;
std::vector<int>
variableStateIndices() const;
SolutionState
variableStateExtractVars(const ReservoirState& x,
const std::vector<int>& indices,
std::vector<ADB>& vars) const;
void
computeAccum(const SolutionState& state,
const int aix );
void
assembleMassBalanceEq(const SolutionState& state);
SimulatorReport
solveWellEq(const std::vector<ADB>& mob_perfcells,
const std::vector<ADB>& b_perfcells,
const ReservoirState& reservoir_state,
SolutionState& state,
WellState& well_state);
void
addWellContributionToMassBalanceEq(const std::vector<ADB>& cq_s,
const SolutionState& state,
const WellState& xw);
bool getWellConvergence(const int iteration);
bool isVFPActive() const;
std::vector<ADB>
computePressures(const ADB& po,
const ADB& sw,
const ADB& so,
const ADB& sg) const;
V
computeGasPressure(const V& po,
const V& sw,
const V& so,
const V& sg) const;
std::vector<ADB>
computeRelPerm(const SolutionState& state) const;
void
computeMassFlux(const int actph ,
const V& transi,
const ADB& kr ,
const ADB& mu ,
const ADB& rho ,
const ADB& p ,
const SolutionState& state );
void applyThresholdPressures(ADB& dp);
ADB
fluidViscosity(const int phase,
const ADB& p ,
const ADB& temp ,
const ADB& rs ,
const ADB& rv ,
const std::vector<PhasePresence>& cond) const;
ADB
fluidReciprocFVF(const int phase,
const ADB& p ,
const ADB& temp ,
const ADB& rs ,
const ADB& rv ,
const std::vector<PhasePresence>& cond) const;
ADB
fluidDensity(const int phase,
const ADB& b,
const ADB& rs,
const ADB& rv) const;
V
fluidRsSat(const V& p,
const V& so,
const std::vector<int>& cells) const;
ADB
fluidRsSat(const ADB& p,
const ADB& so,
const std::vector<int>& cells) const;
V
fluidRvSat(const V& p,
const V& so,
const std::vector<int>& cells) const;
ADB
fluidRvSat(const ADB& p,
const ADB& so,
const std::vector<int>& cells) const;
ADB
poroMult(const ADB& p) const;
ADB
transMult(const ADB& p) const;
const std::vector<PhasePresence>
phaseCondition() const {return phaseCondition_;}
void
classifyCondition(const ReservoirState& state);
/// update the primal variable for Sg, Rv or Rs. The Gas phase must
/// be active to call this method.
void
updatePrimalVariableFromState(const ReservoirState& state);
/// Update the phaseCondition_ member based on the primalVariable_ member.
/// Also updates isRs_, isRv_ and isSg_;
void
updatePhaseCondFromPrimalVariable(const ReservoirState& state);
// TODO: added since the interfaces of the function are different
// TODO: for StandardWells and MultisegmentWells
void
computeWellConnectionPressures(const SolutionState& state,
const WellState& well_state);
/// \brief Compute the reduction within the convergence check.
/// \param[in] B A matrix with MaxNumPhases columns and the same number rows
/// as the number of cells of the grid. B.col(i) contains the values
/// for phase i.
/// \param[in] tempV A matrix with MaxNumPhases columns and the same number rows
/// as the number of cells of the grid. tempV.col(i) contains the
/// values
/// for phase i.
/// \param[in] R A matrix with MaxNumPhases columns and the same number rows
/// as the number of cells of the grid. B.col(i) contains the values
/// for phase i.
/// \param[out] R_sum An array of size MaxNumPhases where entry i contains the sum
/// of R for the phase i.
/// \param[out] maxCoeff An array of size MaxNumPhases where entry i contains the
/// maximum of tempV for the phase i.
/// \param[out] B_avg An array of size MaxNumPhases where entry i contains the average
/// of B for the phase i.
/// \param[out] maxNormWell The maximum of the well flux equations for each phase.
/// \param[in] nc The number of cells of the local grid.
/// \return The total pore volume over all cells.
double
convergenceReduction(const Eigen::Array<double, Eigen::Dynamic, Eigen::Dynamic>& B,
const Eigen::Array<double, Eigen::Dynamic, Eigen::Dynamic>& tempV,
const Eigen::Array<double, Eigen::Dynamic, Eigen::Dynamic>& R,
std::vector<double>& R_sum,
std::vector<double>& maxCoeff,
std::vector<double>& B_avg,
std::vector<double>& maxNormWell,
int nc) const;
/// Set up the group control related at the beginning of each time step
void
setupGroupControl(const ReservoirState& reservoir_state,
WellState& well_state);
double dpMaxRel() const { return param_.dp_max_rel_; }
double dbhpMaxRel() const {return param_.dbhp_max_rel_; }
double dsMax() const { return param_.ds_max_; }
double drMaxRel() const { return param_.dr_max_rel_; }
double maxResidualAllowed() const { return param_.max_residual_allowed_; }
};
} // namespace Opm
#include "BlackoilModelBase_impl.hpp"
#endif // OPM_BLACKOILMODELBASE_HEADER_INCLUDED

File diff suppressed because it is too large Load Diff

View File

@ -1,91 +0,0 @@
/*
Copyright 2015 SINTEF ICT, Applied Mathematics.
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 3 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 <http://www.gnu.org/licenses/>.
*/
#ifndef OPM_BLACKOILMODELENUMS_HEADER_INCLUDED
#define OPM_BLACKOILMODELENUMS_HEADER_INCLUDED
#include <opm/core/props/BlackoilPhases.hpp>
#include <array>
#include <vector>
namespace Opm
{
constexpr const auto Water = BlackoilPhases::Aqua;
constexpr const auto Oil = BlackoilPhases::Liquid;
constexpr const auto Gas = BlackoilPhases::Vapour;
constexpr const auto MaxNumPhases = BlackoilPhases::MaxNumPhases;
enum PrimalVariables {
Sg = 0,
RS = 1,
RV = 2
};
enum CanonicalVariablePositions {
Pressure = 0,
Sw = 1,
Xvar = 2,
Qs = 3,
Bhp = 4,
Next // For extension.
};
struct FIPDataEnums {
enum FipId {
FIP_AQUA = Opm::Water,
FIP_LIQUID = Opm::Oil,
FIP_VAPOUR = Opm::Gas,
FIP_DISSOLVED_GAS = 3,
FIP_VAPORIZED_OIL = 4,
FIP_PV = 5, //< Pore volume
FIP_WEIGHTED_PRESSURE = 6
};
static const int fipValues = FIP_WEIGHTED_PRESSURE + 1 ;
};
class FIPData : public FIPDataEnums
{
public:
typedef std::vector<double> VectorType;
using FIPDataEnums :: FipId;
using FIPDataEnums :: fipValues ;
std::array< VectorType, fipValues> fip;
// default constructor
FIPData() {}
// initialize from array of Eigen vectors (or std::vectors)
template <class V>
explicit FIPData( const std::array< V, fipValues>& otherFip )
{
// copy fip vector from V to std::vector
for( int i=0; i<fipValues; ++i ) {
fip[ i ] = VectorType(otherFip[ i ].data(), otherFip[ i ].data() + otherFip[ i ].size() );
}
}
};
} // namespace Opm
#endif // OPM_BLACKOILMODELENUMS_HEADER_INCLUDED

View File

@ -1,105 +0,0 @@
/*
Copyright 2015 SINTEF ICT, Applied Mathematics.
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 3 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 <http://www.gnu.org/licenses/>.
*/
#include <opm/autodiff/BlackoilModelParameters.hpp>
#include <opm/common/utility/parameters/ParameterGroup.hpp>
#include <opm/parser/eclipse/Units/Units.hpp>
namespace Opm
{
BlackoilModelParameters::BlackoilModelParameters()
{
// set default values
reset();
}
BlackoilModelParameters::BlackoilModelParameters( const ParameterGroup& param )
{
// set default values
reset();
// overload with given parameters
dp_max_rel_ = param.getDefault("dp_max_rel", dp_max_rel_);
ds_max_ = param.getDefault("ds_max", ds_max_);
dr_max_rel_ = param.getDefault("dr_max_rel", dr_max_rel_);
dbhp_max_rel_= param.getDefault("dbhp_max_rel", dbhp_max_rel_);
dwell_fraction_max_ = param.getDefault("dwell_fraction_max", dwell_fraction_max_);
max_residual_allowed_ = param.getDefault("max_residual_allowed", max_residual_allowed_);
tolerance_mb_ = param.getDefault("tolerance_mb", tolerance_mb_);
tolerance_cnv_ = param.getDefault("tolerance_cnv", tolerance_cnv_);
tolerance_cnv_relaxed_ = param.getDefault("tolerance_cnv_relaxed", tolerance_cnv_relaxed_);
tolerance_wells_ = param.getDefault("tolerance_wells", tolerance_wells_ );
tolerance_well_control_ = param.getDefault("tolerance_well_control", tolerance_well_control_);
max_welleq_iter_ = param.getDefault("max_welleq_iter", max_welleq_iter_);
use_multisegment_well_ = param.getDefault("use_multisegment_well", use_multisegment_well_);
if (use_multisegment_well_) {
tolerance_pressure_ms_wells_ = param.getDefault("tolerance_pressure_ms_wells", tolerance_pressure_ms_wells_);
max_pressure_change_ms_wells_ = param.getDefault("max_pressure_change_ms_wells", max_pressure_change_ms_wells_);
use_inner_iterations_ms_wells_ = param.getDefault("use_inner_iterations_ms_wells", use_inner_iterations_ms_wells_);
max_inner_iter_ms_wells_ = param.getDefault("max_inner_iter_ms_wells", max_inner_iter_ms_wells_);
}
maxSinglePrecisionTimeStep_ = unit::convert::from(
param.getDefault("max_single_precision_days", unit::convert::to( maxSinglePrecisionTimeStep_, unit::day) ), unit::day );
max_strict_iter_ = param.getDefault("max_strict_iter",8);
solve_welleq_initially_ = param.getDefault("solve_welleq_initially",solve_welleq_initially_);
update_equations_scaling_ = param.getDefault("update_equations_scaling", update_equations_scaling_);
use_update_stabilization_ = param.getDefault("use_update_stabilization", use_update_stabilization_);
deck_file_name_ = param.template get<std::string>("deck_filename");
matrix_add_well_contributions_ = param.getDefault("matrix_add_well_contributions", matrix_add_well_contributions_);
preconditioner_add_well_contributions_ = param.getDefault("preconditioner_add_well_contributions", preconditioner_add_well_contributions_);
}
void BlackoilModelParameters::reset()
{
// default values for the solver parameters
dp_max_rel_ = 0.3;
ds_max_ = 0.2;
dr_max_rel_ = 1.0e9;
dbhp_max_rel_ = 1.0;
dwell_fraction_max_ = 0.2;
max_residual_allowed_ = 1e7;
tolerance_mb_ = 1.0e-5;
tolerance_cnv_ = 1.0e-2;
tolerance_cnv_relaxed_ = 1.0e9;
tolerance_wells_ = 1.0e-4;
tolerance_well_control_ = 1.0e-7;
tolerance_pressure_ms_wells_ = unit::convert::from(0.01, unit::barsa); // 0.01 bar
max_welleq_iter_ = 15;
max_pressure_change_ms_wells_ = unit::convert::from(2.0, unit::barsa); // 2.0 bar
use_inner_iterations_ms_wells_ = true;
max_inner_iter_ms_wells_ = 10;
maxSinglePrecisionTimeStep_ = unit::convert::from( 20.0, unit::day );
solve_welleq_initially_ = true;
update_equations_scaling_ = false;
use_update_stabilization_ = true;
use_multisegment_well_ = false;
matrix_add_well_contributions_ = false;
preconditioner_add_well_contributions_ = false;
}
} // namespace Opm

View File

@ -1,114 +0,0 @@
/*
Copyright 2015 SINTEF ICT, Applied Mathematics.
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 3 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 <http://www.gnu.org/licenses/>.
*/
#ifndef OPM_BLACKOILMODELPARAMETERS_HEADER_INCLUDED
#define OPM_BLACKOILMODELPARAMETERS_HEADER_INCLUDED
#include <string>
namespace Opm
{
class ParameterGroup;
/// Solver parameters for the BlackoilModel.
struct BlackoilModelParameters
{
/// Max relative change in pressure in single iteration.
double dp_max_rel_;
/// Max absolute change in saturation in single iteration.
double ds_max_;
/// Max relative change in gas-oil or oil-gas ratio in single iteration.
double dr_max_rel_;
/// Max relative change in bhp in single iteration.
double dbhp_max_rel_;
/// Max absolute change in well volume fraction in single iteration.
double dwell_fraction_max_;
/// Absolute max limit for residuals.
double max_residual_allowed_;
/// Relative mass balance tolerance (total mass balance error).
double tolerance_mb_;
/// Local convergence tolerance (max of local saturation errors).
double tolerance_cnv_;
/// Relaxed local convergence tolerance (used when iter >= max_strict_iter_).
double tolerance_cnv_relaxed_;
/// Well convergence tolerance.
double tolerance_wells_;
/// Tolerance for the well control equations
// TODO: it might need to distinguish between rate control and pressure control later
double tolerance_well_control_;
/// Tolerance for the pressure equations for multisegment wells
double tolerance_pressure_ms_wells_;
/// Maximum pressure change over an iteratio for ms wells
double max_pressure_change_ms_wells_;
/// Whether to use inner iterations for ms wells
bool use_inner_iterations_ms_wells_;
/// Maximum inner iteration number for ms wells
int max_inner_iter_ms_wells_;
/// Maximum iteration number of the well equation solution
int max_welleq_iter_;
/// Tolerance for time step in seconds where single precision can be used
/// for solving for the Jacobian
double maxSinglePrecisionTimeStep_;
/// Maximum number of Newton iterations before we give up on the CNV convergence criterion
int max_strict_iter_;
/// Solve well equation initially
bool solve_welleq_initially_;
/// Update scaling factors for mass balance equations
bool update_equations_scaling_;
/// Try to detect oscillation or stagnation.
bool use_update_stabilization_;
/// Whether to use MultisegmentWell to handle multisegment wells
/// it is something temporary before the multisegment well model is considered to be
/// well developed and tested.
/// if it is false, we will handle multisegment wells as standard wells, which will be
/// the default behavoir for the moment. Later, we might set it to be true by default if necessary
bool use_multisegment_well_;
/// The file name of the deck
std::string deck_file_name_;
// Whether to add influences of wells between cells to the matrix and preconditioner matrix
bool matrix_add_well_contributions_;
// Whether to add influences of wells between cells to the preconditioner matrix only
bool preconditioner_add_well_contributions_;
/// Construct from user parameters or defaults.
explicit BlackoilModelParameters( const ParameterGroup& param );
/// Construct with default parameters.
BlackoilModelParameters();
/// Set default parameters.
void reset();
};
} // namespace Opm
#endif // OPM_BLACKOILMODELPARAMETERS_HEADER_INCLUDED

View File

@ -1,337 +0,0 @@
/*
Copyright 2015, 2016 SINTEF ICT, Applied Mathematics.
Copyright 2016 Statoil AS.
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 3 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 <http://www.gnu.org/licenses/>.
*/
#ifndef OPM_BLACKOILPRESSUREMODEL_HEADER_INCLUDED
#define OPM_BLACKOILPRESSUREMODEL_HEADER_INCLUDED
#include <opm/autodiff/BlackoilModelBase.hpp>
#include <opm/core/simulator/BlackoilState.hpp>
#include <opm/autodiff/WellStateFullyImplicitBlackoil.hpp>
#include <opm/autodiff/BlackoilModelParameters.hpp>
#include <opm/simulators/timestepping/SimulatorTimerInterface.hpp>
#include <algorithm>
namespace Opm {
/// A model implementation for the pressure equation in three-phase black oil.
///
/// The model is based on the normal black oil model.
/// It uses automatic differentiation via the class AutoDiffBlock
/// to simplify assembly of the jacobian matrix.
template<class Grid, class WellModel>
class BlackoilPressureModel : public BlackoilModelBase<Grid, WellModel, BlackoilPressureModel<Grid, WellModel> >
{
public:
typedef BlackoilModelBase<Grid, WellModel, BlackoilPressureModel<Grid, WellModel> > Base;
friend Base;
typedef typename Base::ReservoirState ReservoirState;
typedef typename Base::WellState WellState;
typedef typename Base::SolutionState SolutionState;
typedef typename Base::V V;
/// Construct the model. It will retain references to the
/// arguments of this functions, and they are expected to
/// remain in scope for the lifetime of the solver.
/// \param[in] param parameters
/// \param[in] grid grid data structure
/// \param[in] fluid fluid properties
/// \param[in] geo rock properties
/// \param[in] rock_comp_props if non-null, rock compressibility properties
/// \param[in] wells_arg well structure
/// \param[in] linsolver linear solver
/// \param[in] eclState eclipse state
/// \param[in] has_disgas turn on dissolved gas
/// \param[in] has_vapoil turn on vaporized oil feature
/// \param[in] terminal_output request output to cout/cerr
BlackoilPressureModel(const typename Base::ModelParameters& param,
const Grid& grid,
const BlackoilPropsAdFromDeck& fluid,
const DerivedGeology& geo,
const RockCompressibility* rock_comp_props,
const StandardWells& std_wells,
const NewtonIterationBlackoilInterface& linsolver,
std::shared_ptr< const EclipseState> eclState,
std::shared_ptr< const Schedule> schedule,
std::shared_ptr< const SummaryConfig> summary_config,
const bool has_disgas,
const bool has_vapoil,
const bool terminal_output)
: Base(param, grid, fluid, geo, rock_comp_props, std_wells, linsolver,
eclState, schedule, summary_config, has_disgas, has_vapoil, terminal_output),
state0_(3),
max_dp_rel_(std::numeric_limits<double>::infinity()),
scaling_{ ADB::null(), ADB::null(), ADB::null() }
{
}
/// Called once per timestep.
void prepareStep(const SimulatorTimerInterface& timer,
const ReservoirState& reservoir_state,
const WellState& well_state)
{
asImpl().wellModel().setStoreWellPerforationFluxesFlag(true);
Base::prepareStep(timer, reservoir_state, well_state);
max_dp_rel_ = std::numeric_limits<double>::infinity();
state0_ = asImpl().variableState(reservoir_state, well_state);
asImpl().makeConstantState(state0_);
}
/// Solve the Jacobian system Jx = r where J is the Jacobian and
/// r is the residual.
V solveJacobianSystem() const
{
// We make a reduced residual object which just
// contains the pressure residual.
// TODO: directly contruct that object in residual_ instead.
const int n1 = residual_.material_balance_eq[0].size();
const int n2 = residual_.well_flux_eq.size() + residual_.well_eq.size();
const int n_full = residual_.sizeNonLinear();
const auto& mb = residual_.material_balance_eq;
const auto& fe = residual_.well_flux_eq;
const auto& we = residual_.well_eq;
LinearisedBlackoilResidual pressure_res = {
{
// TODO: handle general 2-phase etc.
ADB::function(mb[0].value(), { mb[0].derivative()[0], mb[0].derivative()[3], mb[0].derivative()[4] })
},
ADB::function(fe.value(), { fe.derivative()[0], fe.derivative()[3], fe.derivative()[4] }),
ADB::function(we.value(), { we.derivative()[0], we.derivative()[3], we.derivative()[4] }),
residual_.matbalscale,
residual_.singlePrecision
};
assert(pressure_res.sizeNonLinear() == n1 + n2);
V dx_pressure = linsolver_.computeNewtonIncrement(pressure_res);
assert(dx_pressure.size() == n1 + n2);
V dx_full = V::Zero(n_full);
dx_full.topRows(n1) = dx_pressure.topRows(n1);
dx_full.bottomRows(n2) = dx_pressure.bottomRows(n2);
return dx_full;
}
using Base::numPhases;
using Base::numMaterials;
using Base::wellModel;
protected:
using Base::asImpl;
using Base::linsolver_;
using Base::residual_;
using Base::sd_;
using Base::grid_;
using Base::ops_;
using Base::has_vapoil_;
using Base::has_disgas_;
SolutionState state0_;
double max_dp_rel_ = std::numeric_limits<double>::infinity();
ADB scaling_[3] = { ADB::null(), ADB::null(), ADB::null() };
SimulatorReport
assemble(const ReservoirState& reservoir_state,
WellState& well_state,
const bool initial_assembly)
{
SimulatorReport report;
report += Base::assemble(reservoir_state, well_state, initial_assembly);
if (initial_assembly) {
}
// Compute pressure residual.
ADB pressure_residual = ADB::constant(V::Zero(residual_.material_balance_eq[0].size()));
for (int phase = 0; phase < numPhases(); ++phase) {
pressure_residual += residual_.material_balance_eq[phase] * scaling_[phase];
}
residual_.material_balance_eq[0] = pressure_residual; // HACK
// Compute total reservoir volume flux.
const int n = sd_.rq[0].mflux.size();
V flux = V::Zero(n);
for (int phase = 0; phase < numPhases(); ++phase) {
UpwindSelector<double> upwind(grid_, ops_, sd_.rq[phase].dh.value());
flux += sd_.rq[phase].mflux.value() / upwind.select(sd_.rq[phase].b.value());
}
// Storing the fluxes in the assemble() method is a bit of
// a hack, but alternatives either require a more
// significant redesign of the base class or are
// inefficient.
ReservoirState& s = const_cast<ReservoirState&>(reservoir_state);
s.faceflux().resize(n);
std::copy_n(flux.data(), n, s.faceflux().begin());
if (asImpl().localWellsActive()) {
const V& wflux = asImpl().wellModel().getStoredWellPerforationFluxes();
assert(int(well_state.perfRates().size()) == wflux.size());
std::copy_n(wflux.data(), wflux.size(), well_state.perfRates().begin());
}
return report;
}
SolutionState
variableState(const ReservoirState& x,
const WellState& xw) const
{
// As Base::variableState(), except making Sw and Xvar constants.
std::vector<V> vars0 = asImpl().variableStateInitials(x, xw);
std::vector<ADB> vars = ADB::variables(vars0);
const std::vector<int> indices = asImpl().variableStateIndices();
vars[indices[Sw]] = ADB::constant(vars[indices[Sw]].value());
vars[indices[Xvar]] = ADB::constant(vars[indices[Xvar]].value());
// OpmLog::debug("Injector pressure: " + std::to_string(vars[indices[Bhp]].value()[1]));
return asImpl().variableStateExtractVars(x, indices, vars);
}
void computeAccum(const SolutionState& state,
const int aix )
{
if (aix == 0) {
Base::computeAccum(state0_, aix);
} else {
Base::computeAccum(state, aix);
}
}
void assembleMassBalanceEq(const SolutionState& state)
{
Base::assembleMassBalanceEq(state);
// Compute the scaling factors.
// Scaling factors are:
// 1/bw, 1/bo - rs/bg, 1/bg - rv/bo
assert(numPhases() == 3);
assert(numMaterials() == 3);
V one = V::Constant(state.pressure.size(), 1.0);
scaling_[Water] = one / sd_.rq[Water].b;
scaling_[Oil] = one / sd_.rq[Oil].b - state.rs / sd_.rq[Gas].b;
scaling_[Gas] = one / sd_.rq[Gas].b - state.rv / sd_.rq[Oil].b;
if (has_disgas_ && has_vapoil_) {
ADB r_factor = one / (one - state.rs * state.rv);
scaling_[Oil] = r_factor * scaling_[Oil];
scaling_[Gas] = r_factor * scaling_[Gas];
}
// @TODO: multiply the oil and gas scale by 1/(1-rs*rv)?
// OpmLog::debug("gas scaling: " + std::to_string(scaling_[2].value()[0]));
}
void updateState(const V& dx,
ReservoirState& reservoir_state,
WellState& well_state)
{
// Naively, rs and rv can get overwritten, so we
// avoid that by storing.
std::vector<double> rs_old = reservoir_state.gasoilratio();
std::vector<double> rv_old = reservoir_state.rv();
auto hs_old = reservoir_state.hydroCarbonState();
auto phasecond_old = Base::phaseCondition_;
auto isRs_old = Base::isRs_;
auto isRv_old = Base::isRv_;
auto isSg_old = Base::isSg_;
// Compute the pressure range.
const auto minmax_iters = std::minmax_element(reservoir_state.pressure().begin(),
reservoir_state.pressure().end());
const double range = *minmax_iters.second - *minmax_iters.first;
// Use the base class' updateState().
Base::updateState(dx, reservoir_state, well_state);
// Compute relative change.
max_dp_rel_ = dx.head(reservoir_state.pressure().size()).abs().maxCoeff() / range;
// Restore rs and rv, also various state flags.
reservoir_state.gasoilratio() = rs_old;
reservoir_state.rv() = rv_old;
reservoir_state.hydroCarbonState() = hs_old;
Base::phaseCondition_ = phasecond_old;
Base::isRs_ = isRs_old;
Base::isRv_ = isRv_old;
Base::isSg_ = isSg_old;
}
bool getConvergence(const SimulatorTimerInterface& /* timer */, const int iteration)
{
const double tol_p = 1e-10;
const double resmax = residual_.material_balance_eq[0].value().abs().maxCoeff();
if (Base::terminalOutputEnabled()) {
// Only rank 0 does print to std::cout
if (iteration == 0) {
OpmLog::info("\nIter Res(p) Delta(p)\n");
}
std::ostringstream os;
os.precision(3);
os.setf(std::ios::scientific);
os << std::setw(4) << iteration;
os << std::setw(11) << resmax;
os << std::setw(11) << max_dp_rel_;
OpmLog::info(os.str());
}
return resmax < tol_p;
}
};
/// Providing types by template specialisation of ModelTraits for BlackoilPressureModel.
template <class Grid, class WellModel>
struct ModelTraits< BlackoilPressureModel<Grid, WellModel> >
{
typedef BlackoilState ReservoirState;
typedef WellStateFullyImplicitBlackoil WellState;
typedef BlackoilModelParameters ModelParameters;
typedef DefaultBlackoilSolutionState SolutionState;
};
} // namespace Opm
#endif // OPM_BLACKOILPRESSUREMODEL_HEADER_INCLUDED

File diff suppressed because it is too large Load Diff

View File

@ -1,504 +0,0 @@
/*
Copyright 2013 SINTEF ICT, Applied Mathematics.
Copyright 2015 Dr. Blatt - HPC-Simulation-Software & Services.
Copyright 2015 NTNU.
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 3 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 <http://www.gnu.org/licenses/>.
*/
#ifndef OPM_BLACKOILPROPSADFROMDECK_HEADER_INCLUDED
#define OPM_BLACKOILPROPSADFROMDECK_HEADER_INCLUDED
#include <opm/autodiff/AutoDiffBlock.hpp>
#include <opm/autodiff/BlackoilModelEnums.hpp>
#include <opm/core/props/satfunc/SaturationPropsFromDeck.hpp>
#include <opm/core/props/rock/RockFromDeck.hpp>
#include <opm/material/fluidsystems/BlackOilFluidSystem.hpp>
#include <opm/material/densead/Math.hpp>
#include <opm/material/densead/Evaluation.hpp>
#include <opm/parser/eclipse/Deck/Deck.hpp>
#include <opm/parser/eclipse/EclipseState/EclipseState.hpp>
#include <memory>
#include <array>
#include <vector>
#ifdef HAVE_OPM_GRID
#include <opm/common/utility/platform_dependent/disable_warnings.h>
#include <opm/grid/CpGrid.hpp>
#include <opm/common/utility/platform_dependent/reenable_warnings.h>
#endif
namespace Opm
{
class PvtInterface;
/// This class implements the AD-adapted fluid interface for
/// three-phase black-oil. It requires an input deck from which it
/// reads all relevant property data.
///
/// Most methods are available in two overloaded versions, one
/// taking a constant vector and returning the same, and one
/// taking an AD type and returning the same. Derivatives are not
/// returned separately by any method, only implicitly with the AD
/// version of the methods.
class BlackoilPropsAdFromDeck
{
friend class BlackoilPropsDataHandle;
public:
typedef BlackOilFluidSystem<double> FluidSystem;
typedef Opm::GasPvtMultiplexer<double> GasPvt;
typedef Opm::OilPvtMultiplexer<double> OilPvt;
typedef Opm::WaterPvtMultiplexer<double> WaterPvt;
typedef typename SaturationPropsFromDeck::MaterialLawManager MaterialLawManager;
/// Constructor to create a blackoil properties from an ECL deck.
///
/// The materialLawManager parameter represents the object from opm-material
/// which handles the creating and updating parameter objects for the capillary
/// pressure/relperm relations for each grid cell. This object is created
/// internally for the constructors below, but if it is already available
/// externally some performance can be gained by creating it only once.
///
/// \param deck The unprocessed ECL deck from opm-parser
/// \param eclState The processed ECL deck from opm-parser
/// \param materialLawManager The container for the material law parameter objects
/// \param grid The grid upon which the simulation is run on.
/// \param init_rock If true the rock properties (rock compressibility and
/// reference pressure) are read from the deck
BlackoilPropsAdFromDeck(const Opm::Deck& deck,
const Opm::EclipseState& eclState,
std::shared_ptr<MaterialLawManager> materialLawManager,
const UnstructuredGrid& grid,
const bool init_rock = true );
#ifdef HAVE_OPM_GRID
/// Constructor to create a blackoil properties from an ECL deck.
///
/// The materialLawManager parameter represents the object from opm-material
/// which handles the creating and updating parameter objects for the capillary
/// pressure/relperm relations for each grid cell. This object is created
/// internally for the constructors below, but if it is already available
/// externally some performance can be gained by creating it only once.
///
/// \param deck The unprocessed ECL deck from opm-parser
/// \param eclState The processed ECL deck from opm-parser
/// \param materialLawManager The container for the material law parameter objects
/// \param grid The grid upon which the simulation is run on.
/// \param init_rock If true the rock properties (rock compressibility and
/// reference pressure) are read from the deck
BlackoilPropsAdFromDeck(const Opm::Deck& deck,
const Opm::EclipseState& eclState,
std::shared_ptr<MaterialLawManager> materialLawManager,
const Dune::CpGrid& grid,
const bool init_rock = true );
#endif
/// Constructor to create a blackoil properties from an ECL deck.
///
/// \param deck The unprocessed ECL deck from opm-parser
/// \param eclState The processed ECL deck from opm-parser
/// \param grid The grid upon which the simulation is run on.
/// \param init_rock If true the rock properties (rock compressibility and
/// reference pressure) are read from the deck
BlackoilPropsAdFromDeck(const Opm::Deck& deck,
const Opm::EclipseState& eclState,
const UnstructuredGrid& grid,
const bool init_rock = true );
#ifdef HAVE_OPM_GRID
/// Constructor to create a blackoil properties from an ECL deck.
///
/// \param deck The unprocessed ECL deck from opm-parser
/// \param eclState The processed ECL deck from opm-parser
/// \param grid The grid upon which the simulation is run on.
/// \param init_rock If true the rock properties (rock compressibility and
/// reference pressure) are read from the deck
BlackoilPropsAdFromDeck(const Opm::Deck& deck,
const Opm::EclipseState& eclState,
const Dune::CpGrid& grid,
const bool init_rock = true );
#endif
/// \brief Constructor to create properties for a subgrid
///
/// This copies all properties that are not dependant on the
/// grid size from an existing properties object
/// and the number of cells. All properties that do not depend
/// on the grid dimension will be copied. For the rest will have
/// the correct size but the values will be undefined.
///
/// \param props The property object to copy from.
/// \param materialLawManager The container for the material law parameter objects.
/// Initialized only for the subgrid
/// \param number_of_cells The number of cells of the subgrid.
BlackoilPropsAdFromDeck(const BlackoilPropsAdFromDeck& props,
std::shared_ptr<MaterialLawManager> materialLawManager,
const int number_of_cells);
////////////////////////////
// Rock interface //
////////////////////////////
/// \return D, the number of spatial dimensions.
int numDimensions() const;
/// \return N, the number of cells.
int numCells() const;
/// Return an array containing the PVT table index for each
/// grid cell
virtual const int* cellPvtRegionIndex() const
{ return &cellPvtRegionIdx_[0]; }
/// \return Array of N porosity values.
const double* porosity() const;
/// \return Array of ND^2 permeability values.
/// The D^2 permeability values for a cell are organized as a matrix,
/// which is symmetric (so ordering does not matter).
const double* permeability() const;
////////////////////////////
// Fluid interface //
////////////////////////////
typedef AutoDiffBlock<double> ADB;
typedef ADB::V V;
typedef std::vector<int> Cells;
/// \return Number of active phases (also the number of components).
int numPhases() const;
/// \return Object describing the active phases.
PhaseUsage phaseUsage() const;
// ------ Density ------
/// Densities of stock components at surface conditions.
/// \param[in] phaseIdx
/// \param[in] cells Array of n cell indices to be associated with the pressure values.
/// \return Array of n density values for phase given by phaseIdx.
V surfaceDensity(const int phaseIdx , const Cells& cells) const;
// ------ Viscosity ------
/// Water viscosity.
/// \param[in] pw Array of n water pressure values.
/// \param[in] T Array of n temperature values.
/// \param[in] cells Array of n cell indices to be associated with the pressure values.
/// \return Array of n viscosity values.
ADB muWat(const ADB& pw,
const ADB& T,
const Cells& cells) const;
/// Oil viscosity.
/// \param[in] po Array of n oil pressure values.
/// \param[in] T Array of n temperature values.
/// \param[in] rs Array of n gas solution factor values.
/// \param[in] cond Array of n objects, each specifying which phases are present with non-zero saturation in a cell.
/// \param[in] cells Array of n cell indices to be associated with the pressure values.
/// \return Array of n viscosity values.
ADB muOil(const ADB& po,
const ADB& T,
const ADB& rs,
const std::vector<PhasePresence>& cond,
const Cells& cells) const;
/// Gas viscosity.
/// \param[in] pg Array of n gas pressure values.
/// \param[in] T Array of n temperature values.
/// \param[in] rv Array of n vapor oil/gas ratios.
/// \param[in] cond Array of n objects, each specifying which phases are present with non-zero saturation in a cell.
/// \param[in] cells Array of n cell indices to be associated with the pressure values.
/// \return Array of n viscosity values.
ADB muGas(const ADB& pg,
const ADB& T,
const ADB& rv,
const std::vector<PhasePresence>& cond,
const Cells& cells) const;
// ------ Formation volume factor (b) ------
/// Water formation volume factor.
/// \param[in] pw Array of n water pressure values.
/// \param[in] T Array of n temperature values.
/// \param[in] cells Array of n cell indices to be associated with the pressure values.
/// \return Array of n formation volume factor values.
ADB bWat(const ADB& pw,
const ADB& T,
const Cells& cells) const;
/// Oil formation volume factor.
/// \param[in] po Array of n oil pressure values.
/// \param[in] T Array of n temperature values.
/// \param[in] rs Array of n gas solution factor values.
/// \param[in] cond Array of n objects, each specifying which phases are present with non-zero saturation in a cell.
/// \param[in] cells Array of n cell indices to be associated with the pressure values.
/// \return Array of n formation volume factor values.
ADB bOil(const ADB& po,
const ADB& T,
const ADB& rs,
const std::vector<PhasePresence>& cond,
const Cells& cells) const;
/// Gas formation volume factor.
/// \param[in] pg Array of n gas pressure values.
/// \param[in] T Array of n temperature values.
/// \param[in] rv Array of n vapor oil/gas ratio
/// \param[in] cond Array of n objects, each specifying which phases are present with non-zero saturation in a cell.
/// \param[in] cells Array of n cell indices to be associated with the pressure values.
/// \return Array of n formation volume factor values.
ADB bGas(const ADB& pg,
const ADB& T,
const ADB& rv,
const std::vector<PhasePresence>& cond,
const Cells& cells) const;
// ------ Rs bubble point curve ------
/// Bubble point curve for Rs as function of oil pressure.
/// \param[in] po Array of n oil pressure values.
/// \param[in] cells Array of n cell indices to be associated with the pressure values.
/// \return Array of n bubble point values for Rs.
V rsSat(const V& po,
const Cells& cells) const;
/// Bubble point curve for Rs as function of oil pressure.
/// \param[in] po Array of n oil pressure values.
/// \param[in] so Array of n oil saturation values.
/// \param[in] cells Array of n cell indices to be associated with the pressure values.
/// \return Array of n bubble point values for Rs.
V rsSat(const V& po,
const V& so,
const Cells& cells) const;
/// Bubble point curve for Rs as function of oil pressure.
/// \param[in] po Array of n oil pressure values.
/// \param[in] cells Array of n cell indices to be associated with the pressure values.
/// \return Array of n bubble point values for Rs.
ADB rsSat(const ADB& po,
const Cells& cells) const;
/// Bubble point curve for Rs as function of oil pressure.
/// \param[in] po Array of n oil pressure values.
/// \param[in] so Array of n oil saturation values.
/// \param[in] cells Array of n cell indices to be associated with the pressure values.
/// \return Array of n bubble point values for Rs.
ADB rsSat(const ADB& po,
const ADB& so,
const Cells& cells) const;
// ------ Rv condensation curve ------
/// Condensation curve for Rv as function of oil pressure.
/// \param[in] po Array of n oil pressure values.
/// \param[in] cells Array of n cell indices to be associated with the pressure values.
/// \return Array of n condensation point values for Rv.
ADB rvSat(const ADB& po,
const Cells& cells) const;
/// Condensation curve for Rv as function of oil pressure.
/// \param[in] po Array of n oil pressure values.
/// \param[in] so Array of n oil saturation values.
/// \param[in] cells Array of n cell indices to be associated with the pressure values.
/// \return Array of n condensation point values for Rv.
ADB rvSat(const ADB& po,
const ADB& so,
const Cells& cells) const;
// ------ Relative permeability ------
/// Relative permeabilities for all phases.
/// \param[in] sw Array of n water saturation values.
/// \param[in] so Array of n oil saturation values.
/// \param[in] sg Array of n gas saturation values.
/// \param[in] cells Array of n cell indices to be associated with the saturation values.
/// \return An std::vector with 3 elements, each an array of n relperm values,
/// containing krw, kro, krg. Use PhaseIndex for indexing into the result.
std::vector<ADB> relperm(const ADB& sw,
const ADB& so,
const ADB& sg,
const Cells& cells) const;
/// Capillary pressure for all phases.
/// \param[in] sw Array of n water saturation values.
/// \param[in] so Array of n oil saturation values.
/// \param[in] sg Array of n gas saturation values.
/// \param[in] cells Array of n cell indices to be associated with the saturation values.
/// \return An std::vector with 3 elements, each an array of n capillary pressure values,
/// containing the offsets for each p_g, p_o, p_w. The capillary pressure between
/// two arbitrary phases alpha and beta is then given as p_alpha - p_beta.
std::vector<ADB> capPress(const ADB& sw,
const ADB& so,
const ADB& sg,
const Cells& cells) const;
/// Saturation update for hysteresis behavior.
/// \param[in] cells Array of n cell indices to be associated with the saturation values.
void updateSatHyst(const std::vector<double>& saturation,
const std::vector<int>& cells);
/// Set gas-oil hysteresis parameters
/// \param[in] pcswmdc Vector of hysteresis parameters (@see EclHysteresisTwoPhaseLawParams::pcSwMdc(...))
/// \param[in] krnswdc Vector of hysteresis parameters (@see EclHysteresisTwoPhaseLawParams::krnSwMdc(...))
void setGasOilHystParams(const std::vector<double>& pcswmdc,
const std::vector<double>& krnswdc,
const std::vector<int>& cells);
/// Get gas-oil hysteresis parameters
/// \param[in] pcswmdc Vector of hysteresis parameters (@see EclHysteresisTwoPhaseLawParams::pcSwMdc(...))
/// \param[in] krnswdc Vector of hysteresis parameters (@see EclHysteresisTwoPhaseLawParams::krnSwMdc(...))
void getGasOilHystParams(std::vector<double>& pcswmdc,
std::vector<double>& krnswdc,
const std::vector<int>& cells) const;
/// Set oil-water hysteresis parameters
/// \param[in] pcswmdc Vector of hysteresis parameters (@see EclHysteresisTwoPhaseLawParams::pcSwMdc(...))
/// \param[in] krnswdc Vector of hysteresis parameters (@see EclHysteresisTwoPhaseLawParams::krnSwMdc(...))
void setOilWaterHystParams(const std::vector<double>& pcswmdc,
const std::vector<double>& krnswdc,
const std::vector<int>& cells);
/// Set oil-water hysteresis parameters
/// \param[in] pcswmdc Vector of hysteresis parameters (@see EclHysteresisTwoPhaseLawParams::pcSwMdc(...))
/// \param[in] krnswdc Vector of hysteresis parameters (@see EclHysteresisTwoPhaseLawParams::krnSwMdc(...))
void getOilWaterHystParams(std::vector<double>& pcswmdc,
std::vector<double>& krnswdc,
const std::vector<int>& cells) const;
/// Update for max oil saturation.
/// \param[in] saturation Saturations for all phases
void updateSatOilMax(const std::vector<double>& saturation);
/// Returns the max oil saturation vector
const std::vector<double>& satOilMax() const;
/// Force set max oil saturation (used for restarting)
/// \param[in] max_sat Max oil saturations.
/// Note that this is a vector of *only* oil saturations (no other phases)
/// @see The similar function updateSatOilMax(const std::vector<double>& saturation)
/// @see satOilMax()
void setSatOilMax(const std::vector<double>& max_sat);
/// Returns the bubble point pressures
std::vector<double> bubblePointPressure(const Cells& cells,
const V& T,
const V& rs) const;
/// Returns the dew point pressures
std::vector<double> dewPointPressure(const Cells& cells,
const V& T,
const V& rv) const;
/// Set capillary pressure scaling according to pressure diff. and initial water saturation.
/// \param[in] saturation Array of n*numPhases saturation values.
/// \param[in] pc Array of n*numPhases capillary pressure values.
void setSwatInitScaling(const std::vector<double>& saturation,
const std::vector<double>& pc);
/// Obtain the scaled critical oil in gas saturation values.
/// \param[in] cells Array of cell indices.
/// \return Array of critical oil in gas saturaion values.
V scaledCriticalOilinGasSaturations(const Cells& cells) const;
/// Obtain the scaled critical gas saturation values.
/// \param[in] cells Array of cell indices.
/// \return Array of scaled critical gas saturaion values.
V scaledCriticalGasSaturations(const Cells& cells) const;
/// Direct access to lower-level water pvt props.
const WaterPvt& waterProps() const
{
return FluidSystem::waterPvt();
}
/// Direct access to lower-level oil pvt props.
const OilPvt& oilProps() const
{
return FluidSystem::oilPvt();
}
/// Direct access to lower-level gas pvt props.
const GasPvt& gasProps() const
{
return FluidSystem::gasPvt();
}
/// Direct access to lower-level saturation functions.
const MaterialLawManager& materialLaws() const
{
return *materialLawManager_;
}
// Direct access to pvt region indices.
const std::vector<int>& pvtRegions() const
{
return cellPvtRegionIdx_;
}
private:
/// Initializes the properties.
void init(const Opm::Deck& deck,
const Opm::EclipseState& eclState,
std::shared_ptr<MaterialLawManager> materialLawManager,
int number_of_cells,
const int* global_cell,
const int* cart_dims,
const bool init_rock);
/// Correction to rs/rv according to kw VAPPARS
void applyVap(V& r,
const V& so,
const std::vector<int>& cells,
const double vap) const;
void applyVap(ADB& r,
const ADB& so,
const std::vector<int>& cells,
const double vap) const;
RockFromDeck rock_;
// This has to be a shared pointer as we must
// be able to make a copy of *this in the parallel case.
std::shared_ptr<MaterialLawManager> materialLawManager_;
std::shared_ptr<SaturationPropsFromDeck> satprops_;
PhaseUsage phase_usage_;
// bool has_vapoil_;
// bool has_disgas_;
// The PVT region which is to be used for each cell
std::vector<int> cellPvtRegionIdx_;
// VAPPARS
double vap1_;
double vap2_;
std::vector<double> satOilMax_;
double vap_satmax_guard_; //Threshold value to promote stability
};
} // namespace Opm
#endif // OPM_BLACKOILPROPSADFROMDECK_HEADER_INCLUDED

View File

@ -1,983 +0,0 @@
/*
Copyright 2016 SINTEF ICT, Applied Mathematics.
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 3 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 <http://www.gnu.org/licenses/>.
*/
#ifndef OPM_BLACKOILREORDERINGTRANSPORTMODEL_HEADER_INCLUDED
#define OPM_BLACKOILREORDERINGTRANSPORTMODEL_HEADER_INCLUDED
#include <opm/autodiff/BlackoilModelBase.hpp>
#include <opm/autodiff/WellStateFullyImplicitBlackoil.hpp>
#include <opm/autodiff/BlackoilModelParameters.hpp>
#include <opm/autodiff/DebugTimeReport.hpp>
#include <opm/autodiff/multiPhaseUpwind.hpp>
#include <opm/grid/UnstructuredGrid.h>
#include <opm/core/transport/reorder/reordersequence.h>
#include <opm/core/simulator/BlackoilState.hpp>
#include <opm/autodiff/BlackoilTransportModel.hpp>
namespace Opm {
namespace detail
{
template <typename Scalar>
struct CreateVariable
{
Scalar operator()(double value, int index)
{
return Scalar::createVariable(value, index);
}
};
template <>
struct CreateVariable<double>
{
double operator()(double value, int)
{
return value;
}
};
template <typename Scalar>
struct CreateConstant
{
Scalar operator()(double value)
{
return Scalar::createConstant(value);
}
};
template <>
struct CreateConstant<double>
{
double operator()(double value)
{
return value;
}
};
struct Connection
{
Connection(const int ind, const double s) : index(ind), sign(s) {}
int index;
double sign;
};
class Connections;
class ConnectivityGraph
{
public:
explicit ConnectivityGraph(const HelperOps& ops)
: grad_(ops.grad)
, div_(ops.div)
{
grad_ia_ = grad_.outerIndexPtr();
grad_ja_ = grad_.innerIndexPtr();
grad_sign_ = grad_.valuePtr();
div_ia_ = div_.outerIndexPtr();
div_ja_ = div_.innerIndexPtr();
div_sign_ = div_.valuePtr();
}
Connections cellConnections(const int cell) const;
std::array<int, 2> connectionCells(const int connection) const
{
const int pos = div_ia_[connection];
assert(div_ia_[connection + 1] == pos + 2);
const double sign1 = div_sign_[pos];
assert(div_sign_[pos + 1] == -sign1);
if (sign1 > 0.0) {
return {{ div_ja_[pos], div_ja_[pos + 1] }};
} else {
return {{ div_ja_[pos + 1], div_ja_[pos] }};
}
}
private:
friend class Connections;
typedef HelperOps::M M;
const M& grad_;
const M& div_;
const int* grad_ia_;
const int* grad_ja_;
const double* grad_sign_;
const int* div_ia_;
const int* div_ja_;
const double* div_sign_;
};
class Connections
{
public:
Connections(const ConnectivityGraph& cg, const int cell) : cg_(cg), cell_(cell) {}
int size() const
{
return cg_.grad_ia_[cell_ + 1] - cg_.grad_ia_[cell_];
}
class Iterator
{
public:
Iterator(const Connections& c, const int index) : c_(c), index_(index) {}
Iterator& operator++()
{
++index_;
return *this;
}
bool operator!=(const Iterator& other)
{
assert(&c_ == &other.c_);
return index_ != other.index_;
}
Connection operator*()
{
assert(index_ >= 0 && index_ < c_.size());
const int pos = c_.cg_.grad_ia_[c_.cell_] + index_;
return Connection(c_.cg_.grad_ja_[pos], -c_.cg_.grad_sign_[pos]); // Note the minus sign!
}
private:
const Connections& c_;
int index_;
};
Iterator begin() const { return Iterator(*this, 0); }
Iterator end() const { return Iterator(*this, size()); }
private:
friend class Iterator;
const ConnectivityGraph& cg_;
const int cell_;
};
inline Connections ConnectivityGraph::cellConnections(const int cell) const
{
return Connections(*this, cell);
}
} // namespace detail
/// A model implementation for the transport equation in three-phase black oil.
template<class Grid, class WellModel>
class BlackoilReorderingTransportModel
: public BlackoilModelBase<Grid, WellModel, BlackoilReorderingTransportModel<Grid, WellModel> >
{
public:
typedef BlackoilModelBase<Grid, WellModel, BlackoilReorderingTransportModel<Grid, WellModel> > Base;
friend Base;
typedef typename Base::ReservoirState ReservoirState;
typedef typename Base::WellState WellState;
typedef typename Base::SolutionState SolutionState;
typedef typename Base::V V;
/// Construct the model. It will retain references to the
/// arguments of this functions, and they are expected to
/// remain in scope for the lifetime of the solver.
/// \param[in] param parameters
/// \param[in] grid grid data structure
/// \param[in] fluid fluid properties
/// \param[in] geo rock properties
/// \param[in] rock_comp_props if non-null, rock compressibility properties
/// \param[in] wells_arg well structure
/// \param[in] linsolver linear solver
/// \param[in] eclState eclipse state
/// \param[in] has_disgas turn on dissolved gas
/// \param[in] has_vapoil turn on vaporized oil feature
/// \param[in] terminal_output request output to cout/cerr
BlackoilReorderingTransportModel(const typename Base::ModelParameters& param,
const Grid& grid,
const BlackoilPropsAdFromDeck& fluid,
const DerivedGeology& geo,
const RockCompressibility* rock_comp_props,
const StandardWells& std_wells,
const NewtonIterationBlackoilInterface& linsolver,
std::shared_ptr<const EclipseState> eclState,
std::shared_ptr<const Schedule> schedule,
std::shared_ptr<const SummaryConfig> summary_config,
const bool has_disgas,
const bool has_vapoil,
const bool terminal_output)
: Base(param, grid, fluid, geo, rock_comp_props, std_wells, linsolver,
eclState, schedule, summary_config, has_disgas, has_vapoil, terminal_output)
, graph_(Base::ops_)
, props_(dynamic_cast<const BlackoilPropsAdFromDeck&>(fluid)) // TODO: remove the need for this cast.
, state0_{ ReservoirState(0, 0, 0), WellState(), V(), V() }
, state_{ ReservoirState(0, 0, 0), WellState(), V(), V() }
, tr_model_(param, grid, fluid, geo, rock_comp_props, std_wells, linsolver,
eclState, schedule, summary_config, has_disgas, has_vapoil, terminal_output)
{
// Set up the common parts of the mass balance equations
// for each active phase.
const V transi = subset(geo_.transmissibility(), ops_.internal_faces);
const V trans_nnc = ops_.nnc_trans;
trans_all_ = V::Zero(transi.size() + trans_nnc.size());
trans_all_ << transi, trans_nnc;
gdz_ = geo_.gravity()[2] * (ops_.grad * geo_.z().matrix());
rhos_ = DataBlock::Zero(ops_.div.rows(), 3);
rhos_.col(Water) = props_.surfaceDensity(Water, Base::cells_);
rhos_.col(Oil) = props_.surfaceDensity(Oil, Base::cells_);
rhos_.col(Gas) = props_.surfaceDensity(Gas, Base::cells_);
}
void prepareStep(const SimulatorTimerInterface& timer,
const ReservoirState& reservoir_state,
const WellState& well_state)
{
tr_model_.prepareStep(timer, reservoir_state, well_state);
Base::prepareStep(timer, reservoir_state, well_state);
Base::param_.solve_welleq_initially_ = false;
state0_.reservoir_state = reservoir_state;
state0_.well_state = well_state;
// Since (reference) pressure is constant, porosity and transmissibility multipliers can
// be computed just once.
const std::vector<double>& p = reservoir_state.pressure();
state0_.tr_mult = Base::transMult(ADB::constant(Eigen::Map<const V>(p.data(), p.size()))).value();
state0_.pv_mult = Base::poroMult(ADB::constant(Eigen::Map<const V>(p.data(), p.size()))).value();
const int num_cells = p.size();
cstate0_.resize(num_cells);
for (int cell = 0; cell < num_cells; ++cell) {
computeCellState(cell, state0_, cstate0_[cell]);
}
cstate_ = cstate0_;
}
template <class NonlinearSolverType>
SimulatorReport nonlinearIteration(const int iteration,
const SimulatorTimerInterface& timer,
NonlinearSolverType& nonlinear_solver,
ReservoirState& reservoir_state,
const WellState& well_state)
{
// Extract reservoir and well fluxes and state.
{
DebugTimeReport tr("Extracting fluxes");
extractFluxes(reservoir_state, well_state);
extractState(reservoir_state, well_state);
}
// Compute cell ordering based on total flux.
{
DebugTimeReport tr("Topological sort");
computeOrdering();
}
// Solve in every component (cell or block of cells), in order.
{
DebugTimeReport tr("Solving all components");
for (int ii = 0; ii < 5; ++ii) {
DebugTimeReport tr2("Solving components single sweep.");
solveComponents();
}
}
// Update states for output.
reservoir_state = state_.reservoir_state;
// Assemble with other model,
{
auto rs = reservoir_state;
auto ws = well_state;
tr_model_.nonlinearIteration(/*iteration*/ 0, timer, nonlinear_solver, rs, ws);
}
// Create report and exit.
SimulatorReport report;
report.converged = true;
return report;
}
void afterStep(const SimulatorTimerInterface& /* timer */,
const ReservoirState& /* reservoir_state */,
const WellState& /* well_state */)
{
// Does nothing in this model.
}
using Base::numPhases;
protected:
// ============ Types ============
using Vec2 = Dune::FieldVector<double, 2>;
using Mat22 = Dune::FieldMatrix<double, 2, 2>;
using Eval = DenseAd::Evaluation<double, 2>;
struct State
{
ReservoirState reservoir_state;
WellState well_state;
V tr_mult;
V pv_mult;
};
template <typename ScalarT>
struct CellState
{
using Scalar = ScalarT;
Scalar s[3];
Scalar rs;
Scalar rv;
Scalar p[3];
Scalar kr[3];
Scalar pc[3];
Scalar temperature;
Scalar mu[3];
Scalar b[3];
Scalar lambda[3];
Scalar rho[3];
Scalar rssat;
Scalar rvsat;
// Implement interface used for opm-material properties.
const Scalar& saturation(int phaseIdx) const
{
return s[phaseIdx];
}
template <typename T>
CellState<T> flatten() const
{
return CellState<T>{
{ s[0].value(), s[1].value(), s[2].value() },
rs.value(),
rv.value(),
{ p[0].value(), p[1].value(), p[2].value() },
{ kr[0].value(), kr[1].value(), kr[2].value() },
{ pc[0].value(), pc[1].value(), pc[2].value() },
temperature.value(),
{ mu[0].value(), mu[1].value(), mu[2].value() },
{ b[0].value(), b[1].value(), b[2].value() },
{ lambda[0].value(), lambda[1].value(), lambda[2].value() },
{ rho[0].value(), rho[1].value(), rho[2].value() },
rssat.value(),
rvsat.value()
};
}
};
// ============ Data members ============
using Base::grid_;
using Base::geo_;
using Base::ops_;
const detail::ConnectivityGraph graph_;
const BlackoilPropsAdFromDeck& props_;
State state0_;
State state_;
std::vector<CellState<double>> cstate0_;
std::vector<CellState<double>> cstate_;
V total_flux_;
V total_wellperf_flux_;
DataBlock comp_wellperf_flux_;
V total_wellflux_cell_;
V oil_wellflux_cell_;
V gas_wellflux_cell_;
std::vector<int> sequence_;
std::vector<int> components_;
V trans_all_;
V gdz_;
DataBlock rhos_;
std::array<double, 2> max_abs_dx_;
std::array<int, 2> max_abs_dx_cell_;
// TODO: remove this, for debug only.
BlackoilTransportModel<Grid, WellModel> tr_model_;
// ============ Member functions ============
template <typename Scalar>
void computeCellState(const int cell, const State& state, CellState<Scalar>& cstate) const
{
assert(numPhases() == 3); // I apologize for this to my future self, that will have to fix it.
// Extract from state and props.
const auto hcstate = state.reservoir_state.hydroCarbonState()[cell];
const bool is_sg = (hcstate == HydroCarbonState::GasAndOil);
const bool is_rs = (hcstate == HydroCarbonState::OilOnly);
const bool is_rv = (hcstate == HydroCarbonState::GasOnly);
const double swval = state.reservoir_state.saturation()[3*cell + Water];
const double sgval = state.reservoir_state.saturation()[3*cell + Gas];
const double rsval = state.reservoir_state.gasoilratio()[cell];
const double rvval = state.reservoir_state.rv()[cell];
const double poval = state.reservoir_state.pressure()[cell];
const int pvt_region = props_.pvtRegions()[cell];
// Property functions.
const auto& waterpvt = props_.waterProps();
const auto& oilpvt = props_.oilProps();
const auto& gaspvt = props_.gasProps();
const auto& satfunc = props_.materialLaws();
// Create saturation and composition variables.
detail::CreateVariable<Scalar> variable;
detail::CreateConstant<Scalar> constant;
cstate.s[Water] = variable(swval, 0);
cstate.s[Gas] = is_sg ? variable(sgval, 1) : constant(sgval);
cstate.s[Oil] = 1.0 - cstate.s[Water] - cstate.s[Gas];
cstate.rs = is_rs ? variable(rsval, 1) : constant(rsval);
cstate.rv = is_rv ? variable(rvval, 1) : constant(rvval);
// Compute relative permeabilities amd capillary pressures.
const auto& params = satfunc.materialLawParams(cell);
typedef BlackoilPropsAdFromDeck::MaterialLawManager::MaterialLaw MaterialLaw;
MaterialLaw::relativePermeabilities(cstate.kr, params, cstate);
MaterialLaw::capillaryPressures(cstate.pc, params, cstate);
// Compute phase pressures.
cstate.p[Oil] = constant(poval);
cstate.p[Water] = cstate.p[Oil] + cstate.pc[Water]; // pcow = pw - po (!) [different from old convention]
cstate.p[Gas] = cstate.p[Oil] + cstate.pc[Gas]; // pcog = pg - po
// Compute PVT properties.
cstate.temperature = constant(0.0); // Temperature is not used.
cstate.mu[Water] = waterpvt.viscosity(pvt_region, cstate.temperature, cstate.p[Water]);
cstate.mu[Oil] = is_sg
? oilpvt.saturatedViscosity(pvt_region, cstate.temperature, cstate.p[Oil])
: oilpvt.viscosity(pvt_region, cstate.temperature, cstate.p[Oil], cstate.rs);
cstate.mu[Gas] = is_sg
? gaspvt.saturatedViscosity(pvt_region, cstate.temperature, cstate.p[Gas])
: gaspvt.viscosity(pvt_region, cstate.temperature, cstate.p[Gas], cstate.rv);
cstate.b[Water] = waterpvt.inverseFormationVolumeFactor(pvt_region, cstate.temperature, cstate.p[Water]);
cstate.b[Oil] = is_sg
? oilpvt.saturatedInverseFormationVolumeFactor(pvt_region, cstate.temperature, cstate.p[Oil])
: oilpvt.inverseFormationVolumeFactor(pvt_region, cstate.temperature, cstate.p[Oil], cstate.rs);
cstate.b[Gas] = is_sg
? gaspvt.saturatedInverseFormationVolumeFactor(pvt_region, cstate.temperature, cstate.p[Gas])
: gaspvt.inverseFormationVolumeFactor(pvt_region, cstate.temperature, cstate.p[Gas], cstate.rv);
// Compute mobilities.
for (int phase = 0; phase < 3; ++phase) {
cstate.lambda[phase] = cstate.kr[phase] / cstate.mu[phase];
}
// Compute densities.
cstate.rho[Water] = rhos_(cell, Water) * cstate.b[Water];
cstate.rho[Oil] = (rhos_(cell, Oil) + cstate.rs*rhos_(cell, Gas)) * cstate.b[Oil]; // TODO: check that this is correct
cstate.rho[Gas] = (rhos_(cell, Gas) + cstate.rv*rhos_(cell, Oil)) * cstate.b[Gas];
// Compute saturated rs and rv factors.
cstate.rssat = oilpvt.saturatedGasDissolutionFactor(pvt_region, cstate.temperature, cstate.p[Oil]);
cstate.rvsat = gaspvt.saturatedOilVaporizationFactor(pvt_region, cstate.temperature, cstate.p[Gas]);
// TODO: add vaporization controls such as in BlackoilPropsAdFromDeck::applyVap().
}
void extractFluxes(const ReservoirState& reservoir_state,
const WellState& well_state)
{
// Input face fluxes are for interior faces + nncs.
total_flux_ = Eigen::Map<const V>(reservoir_state.faceflux().data(),
reservoir_state.faceflux().size());
total_wellperf_flux_ = Eigen::Map<const V>(well_state.perfRates().data(),
well_state.perfRates().size());
comp_wellperf_flux_ = Eigen::Map<const DataBlock>(well_state.perfPhaseRates().data(),
well_state.perfRates().size(),
numPhases());
const int num_cells = reservoir_state.pressure().size();
total_wellflux_cell_ = superset(total_wellperf_flux_, Base::wellModel().wellOps().well_cells, num_cells);
assert(Base::numPhases() == 3);
V oilflux = comp_wellperf_flux_.col(1);
V gasflux = comp_wellperf_flux_.col(2);
oil_wellflux_cell_ = superset(oilflux, Base::wellModel().wellOps().well_cells, num_cells);
gas_wellflux_cell_ = superset(gasflux, Base::wellModel().wellOps().well_cells, num_cells);
assert(numPhases() * well_state.perfRates().size() == well_state.perfPhaseRates().size());
}
void extractState(const ReservoirState& reservoir_state,
const WellState& well_state)
{
state_.reservoir_state = reservoir_state;
state_.well_state = well_state;
const std::vector<double>& p = reservoir_state.pressure();
state_.tr_mult = Base::transMult(ADB::constant(Eigen::Map<const V>(p.data(), p.size()))).value();
state_.pv_mult = Base::poroMult(ADB::constant(Eigen::Map<const V>(p.data(), p.size()))).value();
}
void computeOrdering()
{
assert(!geo_.nnc().hasNNC()); // TODO: support compute_sequence() with grid + nnc.
static_assert(std::is_same<Grid, UnstructuredGrid>::value,
"compute_sequence() is written in C and therefore requires an UnstructuredGrid, "
"it must be rewritten to use other grid classes such as CpGrid");
using namespace Opm::AutoDiffGrid;
const int num_cells = numCells(grid_);
sequence_.resize(num_cells);
components_.resize(num_cells + 1); // max possible size
int num_components = -1;
using namespace Opm::AutoDiffGrid;
const int num_faces = numFaces(grid_);
V flux_on_all_faces = superset(total_flux_, ops_.internal_faces, num_faces);
compute_sequence(&grid_, flux_on_all_faces.data(), sequence_.data(), components_.data(), &num_components);
OpmLog::debug(std::string("Number of components: ") + std::to_string(num_components));
components_.resize(num_components + 1); // resize to fit actually used part
}
void solveComponents()
{
// Zero the max changed.
max_abs_dx_[0] = 0.0;
max_abs_dx_[1] = 0.0;
max_abs_dx_cell_[0] = -1;
max_abs_dx_cell_[1] = -1;
// Solve the equations.
const int num_components = components_.size() - 1;
for (int comp = 0; comp < num_components; ++comp) {
const int comp_size = components_[comp + 1] - components_[comp];
if (comp_size == 1) {
solveSingleCell(sequence_[components_[comp]]);
} else {
solveMultiCell(comp_size, &sequence_[components_[comp]]);
}
}
// Log the max change.
{
std::ostringstream os;
os << "=== Max abs dx[0]: " << max_abs_dx_[0] << " (cell " << max_abs_dx_cell_[0]
<<") dx[1]: " << max_abs_dx_[1] << " (cell " << max_abs_dx_cell_[1] << ")";
OpmLog::debug(os.str());
}
}
void solveSingleCell(const int cell)
{
Vec2 res;
Mat22 jac;
assembleSingleCell(cell, res, jac);
// Newton loop.
int iter = 0;
const int max_iter = 100;
double relaxation = 1.0;
while (!getConvergence(cell, res) && iter < max_iter) {
Vec2 dx;
jac.solve(dx, res);
dx *= relaxation;
// const auto hcstate_old = state_.reservoir_state.hydroCarbonState()[cell];
updateState(cell, -dx);
// const auto hcstate = state_.reservoir_state.hydroCarbonState()[cell];
assembleSingleCell(cell, res, jac);
++iter;
if (iter > 10) {
relaxation = 0.85;
if (iter > 15) {
relaxation = 0.70;
}
if (iter > 20) {
relaxation = 0.55;
}
if (iter > 25) {
relaxation = 0.40;
}
if (iter > 30) {
relaxation = 0.25;
}
// std::ostringstream os;
// os << "Iteration " << iter << " in cell " << cell << ", residual = " << res
// << ", cell values { s = ( " << cstate_[cell].s[Water] << ", " << cstate_[cell].s[Oil] << ", " << cstate_[cell].s[Gas]
// << " ), rs = " << cstate_[cell].rs << ", rv = " << cstate_[cell].rv << "}, dx = " << dx << ", hcstate: " << hcstate_old << " -> " << hcstate;
// OpmLog::debug(os.str());
}
}
if (iter == max_iter) {
std::ostringstream os;
os << "Failed to converge in cell " << cell << ", residual = " << res
<< ", cell values { s = ( " << cstate_[cell].s[Water] << ", " << cstate_[cell].s[Oil] << ", " << cstate_[cell].s[Gas]
<< " ), rs = " << cstate_[cell].rs << ", rv = " << cstate_[cell].rv << " }";
OpmLog::debug(os.str());
}
}
void solveMultiCell(const int comp_size, const int* cell_array)
{
// OpmLog::warning("solveMultiCell", "solveMultiCell() called with component size " + std::to_string(comp_size));
for (int ii = 0; ii < comp_size; ++ii) {
solveSingleCell(cell_array[ii]);
}
}
template <typename Scalar>
Scalar oilAccumulation(const CellState<Scalar>& cs)
{
return cs.b[Oil]*cs.s[Oil] + cs.rv*cs.b[Gas]*cs.s[Gas];
}
template <typename Scalar>
Scalar gasAccumulation(const CellState<Scalar>& cs)
{
return cs.b[Gas]*cs.s[Gas] + cs.rs*cs.b[Oil]*cs.s[Oil];
}
void applyThresholdPressure(const int connection, Eval& dp)
{
const double thres_press = Base::threshold_pressures_by_connection_[connection];
if (std::fabs(dp.value()) < thres_press) {
dp.setValue(0.0);
} else {
dp -= dp.value() > 0.0 ? thres_press : -thres_press;
}
}
void assembleSingleCell(const int cell, Vec2& res, Mat22& jac)
{
assert(numPhases() == 3); // I apologize for this to my future self, that will have to fix it.
CellState<Eval> st;
computeCellState(cell, state_, st);
cstate_[cell] = st.template flatten<double>();
// Accumulation terms.
const double pvm0 = state0_.pv_mult[cell];
const double pvm = state_.pv_mult[cell];
const double ao0 = oilAccumulation(cstate0_[cell]) * pvm0;
const Eval ao = oilAccumulation(st) * pvm;
const double ag0 = gasAccumulation(cstate0_[cell]) * pvm0;
const Eval ag = gasAccumulation(st) * pvm;
// Flux terms.
Eval div_oilflux = Eval::createConstant(0.0);
Eval div_gasflux = Eval::createConstant(0.0);
for (auto conn : graph_.cellConnections(cell)) {
auto conn_cells = graph_.connectionCells(conn.index);
const int from = conn_cells[0];
const int to = conn_cells[1];
if (from < 0 || to < 0) {
continue; // Boundary.
}
assert((from == cell) == (conn.sign > 0.0));
const int other = from == cell ? to : from;
const double vt = conn.sign * total_flux_[conn.index];
const double gdz = conn.sign * gdz_[conn.index];
// From this point, we treat everything about this
// connection as going from 'cell' to 'other'. Since
// we don't want derivatives from the 'other' cell to
// participate in the solution, we use the constant
// values from cstate_[other].
Eval dh[3];
Eval dh_sat[3];
const Eval grad_oil_press = cstate_[other].p[Oil] - st.p[Oil];
for (int phase : { Water, Oil, Gas }) {
const Eval gradp = cstate_[other].p[phase] - st.p[phase];
const Eval rhoavg = 0.5 * (st.rho[phase] + cstate_[other].rho[phase]);
dh[phase] = gradp - rhoavg * gdz;
if (Base::use_threshold_pressure_) {
applyThresholdPressure(conn.index, dh[phase]);
}
dh_sat[phase] = grad_oil_press - dh[phase];
}
const double tran = trans_all_[conn.index]; // TODO: include tr_mult effect.
const auto& m1 = st.lambda;
const auto& m2 = cstate_[other].lambda;
const auto upw = connectionMultiPhaseUpwind({{ dh_sat[Water].value(), dh_sat[Oil].value(), dh_sat[Gas].value() }},
{{ m1[Water].value(), m1[Oil].value(), m1[Gas].value() }},
{{ m2[Water], m2[Oil], m2[Gas] }},
tran, vt);
// if (upw[0] != upw[1] || upw[1] != upw[2]) {
// OpmLog::debug("Detected countercurrent flow between cells " + std::to_string(from) + " and " + std::to_string(to));
// }
Eval b[3];
Eval mob[3];
Eval tot_mob = Eval::createConstant(0.0);
for (int phase : { Water, Oil, Gas }) {
b[phase] = upw[phase] > 0.0 ? st.b[phase] : cstate_[other].b[phase];
mob[phase] = upw[phase] > 0.0 ? m1[phase] : m2[phase];
tot_mob += mob[phase];
}
Eval rs = upw[Oil] > 0.0 ? st.rs : cstate_[other].rs;
Eval rv = upw[Gas] > 0.0 ? st.rv : cstate_[other].rv;
Eval flux[3];
for (int phase : { Oil, Gas }) {
Eval gflux = Eval::createConstant(0.0);
for (int other_phase : { Water, Oil, Gas }) {
if (phase != other_phase) {
gflux += mob[other_phase] * (dh_sat[phase] - dh_sat[other_phase]);
}
}
flux[phase] = b[phase] * (mob[phase] / tot_mob) * (vt + tran*gflux);
}
div_oilflux += flux[Oil] + rv*flux[Gas];
div_gasflux += flux[Gas] + rs*flux[Oil];
}
// Well fluxes.
if (total_wellflux_cell_[cell] > 0.0) {
// Injecting perforation. Use given phase rates.
assert(oil_wellflux_cell_[cell] >= 0.0);
assert(gas_wellflux_cell_[cell] >= 0.0);
div_oilflux -= oil_wellflux_cell_[cell];
div_gasflux -= gas_wellflux_cell_[cell];
} else if (total_wellflux_cell_[cell] < 0.0) {
// Producing perforation. Use total rate and fractional flow.
Eval totmob = st.lambda[Water] + st.lambda[Oil] + st.lambda[Gas];
Eval oilflux = st.b[Oil] * (st.lambda[Oil]/totmob) * total_wellflux_cell_[cell];
Eval gasflux = st.b[Gas] * (st.lambda[Gas]/totmob) * total_wellflux_cell_[cell];
div_oilflux -= (oilflux + st.rv * gasflux);
div_gasflux -= (gasflux + st.rs * oilflux);
}
const Eval oileq = Base::pvdt_[cell]*(ao - ao0) + div_oilflux;
const Eval gaseq = Base::pvdt_[cell]*(ag - ag0) + div_gasflux;
res[0] = oileq.value();
res[1] = gaseq.value();
jac[0][0] = oileq.derivative(0);
jac[0][1] = oileq.derivative(1);
jac[1][0] = gaseq.derivative(0);
jac[1][1] = gaseq.derivative(1);
}
bool getConvergence(const int cell, const Vec2& res)
{
const double tol = 1e-7;
// Compute scaled residuals (scaled like saturations).
double sres[] = { res[0] / (cstate_[cell].b[Oil] * Base::pvdt_[cell]),
res[1] / (cstate_[cell].b[Gas] * Base::pvdt_[cell]) };
return std::fabs(sres[0]) < tol && std::fabs(sres[1]) < tol;
}
void updateState(const int cell,
const Vec2& dx)
{
if (std::fabs(dx[0]) > max_abs_dx_[0]) {
max_abs_dx_cell_[0] = cell;
}
if (std::fabs(dx[1]) > max_abs_dx_[1]) {
max_abs_dx_cell_[1] = cell;
}
max_abs_dx_[0] = std::max(max_abs_dx_[0], std::fabs(dx[0]));
max_abs_dx_[1] = std::max(max_abs_dx_[1], std::fabs(dx[1]));
// Get saturation updates.
const double dsw = dx[0];
double dsg = 0.0;
auto& hcstate = state_.reservoir_state.hydroCarbonState()[cell];
if (hcstate == HydroCarbonState::GasAndOil) {
dsg = dx[1];
} else if (hcstate == HydroCarbonState::GasOnly) {
dsg = -dsw;
}
const double dso = -(dsw + dsg);
// Handle too large saturation changes.
const double maxval = std::max(std::fabs(dsw), std::max(std::fabs(dso), std::fabs(dsg)));
const double sfactor = std::min(1.0, Base::dsMax() / maxval);
double* s = state_.reservoir_state.saturation().data() + 3*cell;
s[Water] += sfactor*dsw;
s[Gas] += sfactor*dsg;
s[Oil] = 1.0 - s[Water] - s[Gas];
// Handle < 0 saturations.
for (int phase : { Gas, Oil, Water }) { // TODO: check if ordering here is significant
if (s[phase] < 0.0) {
for (int other_phase : { Water, Oil, Gas }) {
if (phase != other_phase) {
s[other_phase] /= (1.0 - s[phase]);
}
}
s[phase] = 0.0;
}
}
// Update rs.
double& rs = state_.reservoir_state.gasoilratio()[cell];
const double rs_old = rs;
if (hcstate == HydroCarbonState::OilOnly) {
// const double max_allowed_change = std::fabs(rs_old) * Base::drMaxRel();
const double drs = dx[1];
// const double factor = std::min(1.0, max_allowed_change / std::fabs(drs));
// rs += factor*drs;
rs += drs;
rs = std::max(rs, 0.0);
}
// Update rv.
double& rv = state_.reservoir_state.rv()[cell];
const double rv_old = rv;
if (hcstate == HydroCarbonState::GasOnly) {
// const double max_allowed_change = std::fabs(rv_old) * Base::drMaxRel();
const double drv = dx[1];
// const double factor = std::min(1.0, max_allowed_change / std::fabs(drv));
// rv += factor*drv;
rv += drv;
rv = std::max(rv, 0.0);
}
const double epsilon = std::sqrt(std::numeric_limits<double>::epsilon());
const bool water_only = s[Water] > (1 - epsilon);
const auto old_hcstate = hcstate;
hcstate = HydroCarbonState::GasAndOil;
// sg <-> rs transition.
{
const double rssat_old = cstate_[cell].rssat;
const double rssat = rssat_old; // TODO: This is no longer true with vaporization controls
const bool is_rs = old_hcstate == HydroCarbonState::OilOnly;
const bool has_gas = (s[Gas] > 0.0 && !is_rs);
const bool gas_vaporized = ( (rs > rssat * (1+epsilon) && is_rs ) && (rs_old > rssat_old * (1-epsilon)) );
if (water_only || has_gas || gas_vaporized) {
rs = rssat;
} else {
hcstate = HydroCarbonState::OilOnly;
}
}
// sg <-> rv transition.
{
const double rvsat_old = cstate_[cell].rvsat;
const double rvsat = rvsat_old; // TODO: This is no longer true with vaporization controls
const bool is_rv = old_hcstate == HydroCarbonState::GasOnly;
const bool has_oil = (s[Oil] > 0.0 && !is_rv);
const bool oil_condensed = ( (rv > rvsat * (1+epsilon) && is_rv) && (rv_old > rvsat_old * (1-epsilon)) );
if (water_only || has_oil || oil_condensed) {
rv = rvsat;
} else {
hcstate = HydroCarbonState::GasOnly;
}
}
}
};
/// Providing types by template specialisation of ModelTraits for BlackoilReorderingTransportModel.
template <class Grid, class WellModel>
struct ModelTraits< BlackoilReorderingTransportModel<Grid, WellModel> >
{
typedef BlackoilState ReservoirState;
typedef WellStateFullyImplicitBlackoil WellState;
typedef BlackoilModelParameters ModelParameters;
typedef DefaultBlackoilSolutionState SolutionState;
};
} // namespace Opm
#endif // OPM_BLACKOILREORDERINGTRANSPORTMODEL_HEADER_INCLUDED

View File

@ -1,335 +0,0 @@
/*
Copyright 2015, 2016 SINTEF ICT, Applied Mathematics.
Copyright 2016 Statoil AS.
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 3 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 <http://www.gnu.org/licenses/>.
*/
#ifndef OPM_BLACKOILSEQUENTIALMODEL_HEADER_INCLUDED
#define OPM_BLACKOILSEQUENTIALMODEL_HEADER_INCLUDED
#include <opm/autodiff/BlackoilModelBase.hpp>
#include <opm/core/simulator/BlackoilState.hpp>
#include <opm/autodiff/WellStateFullyImplicitBlackoil.hpp>
#include <opm/autodiff/BlackoilModelParameters.hpp>
#include <opm/simulators/timestepping/SimulatorTimerInterface.hpp>
namespace Opm {
struct BlackoilSequentialModelParameters : public BlackoilModelParameters
{
bool iterate_to_fully_implicit;
explicit BlackoilSequentialModelParameters( const ParameterGroup& param )
: BlackoilModelParameters(param),
iterate_to_fully_implicit(param.getDefault("iterate_to_fully_implicit", false))
{
}
};
/// A sequential splitting model implementation for three-phase black oil.
template<class Grid, class WellModel,
template <class G, class W> class PressureModelT,
template <class G, class W> class TransportModelT>
class BlackoilSequentialModel
{
public:
typedef BlackoilState ReservoirState;
typedef WellStateFullyImplicitBlackoil WellState;
typedef BlackoilSequentialModelParameters ModelParameters;
typedef DefaultBlackoilSolutionState SolutionState;
typedef PressureModelT<Grid, WellModel> PressureModel;
typedef TransportModelT<Grid, WellModel> TransportModel;
typedef NonlinearSolver<PressureModel> PressureSolver;
typedef NonlinearSolver<TransportModel> TransportSolver;
typedef typename TransportModel::SimulatorData SimulatorData;
typedef typename TransportModel::FIPDataType FIPDataType;
/// Construct the model. It will retain references to the
/// arguments of this functions, and they are expected to
/// remain in scope for the lifetime of the solver.
/// \param[in] param parameters
/// \param[in] grid grid data structure
/// \param[in] fluid fluid properties
/// \param[in] geo rock properties
/// \param[in] rock_comp_props if non-null, rock compressibility properties
/// \param[in] wells well structure
/// \param[in] vfp_properties Vertical flow performance tables
/// \param[in] linsolver linear solver
/// \param[in] eclState eclipse state
/// \param[in] has_disgas turn on dissolved gas
/// \param[in] has_vapoil turn on vaporized oil feature
/// \param[in] terminal_output request output to cout/cerr
BlackoilSequentialModel(const ModelParameters& param,
const Grid& grid ,
const BlackoilPropsAdFromDeck& fluid,
const DerivedGeology& geo ,
const RockCompressibility* rock_comp_props,
const WellModel well_model,
const NewtonIterationBlackoilInterface& linsolver,
std::shared_ptr< const EclipseState > eclState,
std::shared_ptr< const Schedule> schedule,
std::shared_ptr< const SummaryConfig> summary_config,
const bool has_disgas,
const bool has_vapoil,
const bool terminal_output)
: pressure_model_(new PressureModel(param, grid, fluid, geo, rock_comp_props, well_model,
linsolver, eclState, schedule, summary_config, has_disgas, has_vapoil, terminal_output)),
transport_model_(new TransportModel(param, grid, fluid, geo, rock_comp_props, well_model,
linsolver, eclState, schedule, summary_config, has_disgas, has_vapoil, terminal_output)),
// TODO: fix solver parameters for pressure and transport solver.
pressure_solver_(typename PressureSolver::SolverParameters(), std::move(pressure_model_)),
transport_solver_(typename TransportSolver::SolverParameters(), std::move(transport_model_)),
initial_reservoir_state_(0, 0, 0), // will be overwritten
iterate_to_fully_implicit_(param.iterate_to_fully_implicit)
{
typename PressureSolver::SolverParameters pp;
pp.min_iter_ = 0;
pressure_solver_.setParameters(pp);
typename TransportSolver::SolverParameters tp;
tp.min_iter_ = 0;
transport_solver_.setParameters(tp);
}
/// Called once before each time step.
/// \param[in] timer simulation timer
/// \param[in] reservoir_state reservoir state variables
/// \param[in] well_state well state variables
void prepareStep(const SimulatorTimerInterface& /*timer*/,
const ReservoirState& reservoir_state,
const WellState& well_state)
{
initial_reservoir_state_ = reservoir_state;
initial_well_state_ = well_state;
}
/// Called once per nonlinear iteration.
/// This model will first solve the pressure model to convergence, then the
/// transport model.
/// \param[in] iteration should be 0 for the first call of a new timestep
/// \param[in] timer simulation timer
/// \param[in] nonlinear_solver nonlinear solver used (for oscillation/relaxation control)
/// \param[in, out] reservoir_state reservoir state variables
/// \param[in, out] well_state well state variables
template <class NonlinearSolverType>
SimulatorReport nonlinearIteration(const int iteration,
const SimulatorTimerInterface& timer,
NonlinearSolverType& /* nonlinear_solver */,
ReservoirState& reservoir_state,
WellState& well_state)
{
if (!iterate_to_fully_implicit_) {
// Do a single pressure solve, followed by a single transport solve.
if (terminalOutputEnabled()) {
OpmLog::info("Using sequential model.");
}
// Pressure solve.
if (terminalOutputEnabled()) {
OpmLog::info("Solving the pressure equation.");
}
ReservoirState initial_state = reservoir_state;
const SimulatorReport pressure_report = pressure_solver_.step(timer, reservoir_state, well_state);
const int pressure_liniter = pressure_report.total_linear_iterations;
if (pressure_liniter == -1) {
OPM_THROW(std::runtime_error, "Pressure solver failed to converge.");
}
// Transport solve.
if (terminalOutputEnabled()) {
OpmLog::info("Solving the transport equations.");
}
const SimulatorReport transport_report = transport_solver_.step(timer, initial_state, well_state, reservoir_state, well_state);
const int transport_liniter = transport_report.total_linear_iterations;
if (transport_liniter == -1) {
OPM_THROW(std::runtime_error, "Transport solver failed to converge.");
}
// Report and return.
SimulatorReport report;
report.converged = true;
report.total_linear_iterations = pressure_liniter + transport_liniter;
return report;
} else {
// Iterate to fully implicit solution.
// This call is just for a single iteration (one pressure and one transport solve),
// we return a 'false' converged status if more are needed
if (terminalOutputEnabled()) {
OpmLog::info("Using sequential model in iterative mode, outer iteration " + std::to_string(iteration));
}
// Pressure solve.
if (terminalOutputEnabled()) {
OpmLog::info("Solving the pressure equation.");
}
const SimulatorReport pressure_report = pressure_solver_.step(timer, initial_reservoir_state_, initial_well_state_, reservoir_state, well_state);
const int pressure_liniter = pressure_report.total_linear_iterations;
if (pressure_liniter == -1) {
OPM_THROW(std::runtime_error, "Pressure solver failed to converge.");
}
// Transport solve.
if (terminalOutputEnabled()) {
OpmLog::info("Solving the transport equations.");
}
const SimulatorReport transport_report = transport_solver_.step(timer, initial_reservoir_state_, initial_well_state_, reservoir_state, well_state);
const int transport_liniter = transport_report.total_linear_iterations;
if (transport_liniter == -1) {
OPM_THROW(std::runtime_error, "Transport solver failed to converge.");
}
// Revisit pressure equation to check if it is still converged.
bool done = false;
{
auto rstate = reservoir_state;
auto wstate = well_state;
pressure_solver_.model().prepareStep(timer, initial_reservoir_state_, initial_well_state_);
SimulatorReport rep = pressure_solver_.model().nonlinearIteration(0, timer, pressure_solver_, rstate, wstate);
if (rep.converged && rep.total_newton_iterations == 0) {
done = true;
}
}
SimulatorReport report;
report.converged = done;
report.total_linear_iterations = pressure_liniter + transport_liniter;
return report;
}
}
/// Called once after each time step.
/// In this class, this function does nothing.
/// \param[in] timer simulation timer
/// \param[in, out] reservoir_state reservoir state variables
/// \param[in, out] well_state well state variables
void afterStep(const SimulatorTimerInterface& /* timer */,
ReservoirState& /* reservoir_state */,
WellState& /* well_state */)
{
}
/// \brief Set threshold pressures that prevent or reduce flow.
/// This prevents flow across faces if the potential
/// difference is less than the threshold. If the potential
/// difference is greater, the threshold value is subtracted
/// before calculating flow. This is treated symmetrically, so
/// flow is prevented or reduced in both directions equally.
/// \param[in] threshold_pressures_by_face array of size equal to the number of faces
/// of the grid passed in the constructor.
void setThresholdPressures(const std::vector<double>& threshold_pressures_by_face)
{
pressure_solver_.model().setThresholdPressures(threshold_pressures_by_face);
transport_solver_.model().setThresholdPressures(threshold_pressures_by_face);
}
/// Return true if output to cout is wanted.
bool terminalOutputEnabled() const
{
return pressure_solver_.model().terminalOutputEnabled();
}
/// Return the relative change in variables relevant to this model.
double relativeChange(const SimulationDataContainer& previous,
const SimulationDataContainer& current ) const
{
// TODO: this is a quick stopgap implementation, and should be evaluated more carefully.
return std::max(pressure_solver_.model().relativeChange(previous, current),
transport_solver_.model().relativeChange(previous, current));
}
/// Return the well model
const WellModel& wellModel() const
{
return pressure_solver_.model().wellModel();
}
/// Compute fluid in place.
/// \param[in] ReservoirState
/// \param[in] FIPNUM for active cells not global cells.
/// \return fluid in place, number of fip regions, each region contains 5 values which are liquid, vapour, water, free gas and dissolved gas.
std::vector<std::vector<double> >
computeFluidInPlace(const ReservoirState& x,
const std::vector<int>& fipnum) const
{
return transport_solver_.computeFluidInPlace(x, fipnum);
}
/// Return reservoir simulation data (for output functionality)
const SimulatorData& getSimulatorData(const SimulationDataContainer& localState) const {
return transport_solver_.model().getSimulatorData(localState);
}
/// Return fluid-in-place data (for output functionality)
FIPDataType getFIPData() const {
return transport_solver_.model().getFIPData();
}
/// return the statistics if the nonlinearIteration() method failed.
///
/// NOTE: for the flow_legacy simulator family this method is a stub, i.e. the
/// failure report object will *not* contain any meaningful data.
const SimulatorReport& failureReport() const
{ return failureReport_; }
protected:
SimulatorReport failureReport_;
std::unique_ptr<PressureModel> pressure_model_;
std::unique_ptr<TransportModel> transport_model_;
PressureSolver pressure_solver_;
TransportSolver transport_solver_;
ReservoirState initial_reservoir_state_;
WellState initial_well_state_;
bool iterate_to_fully_implicit_;
};
} // namespace Opm
#endif // OPM_BLACKOILSEQUENTIALMODEL_HEADER_INCLUDED

View File

@ -1,585 +0,0 @@
/*
Copyright 2015, 2016 SINTEF ICT, Applied Mathematics.
Copyright 2016 Statoil AS.
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 3 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 <http://www.gnu.org/licenses/>.
*/
#ifndef OPM_BLACKOILTRANSPORTMODEL_HEADER_INCLUDED
#define OPM_BLACKOILTRANSPORTMODEL_HEADER_INCLUDED
#include <opm/autodiff/BlackoilModelBase.hpp>
#include <opm/core/simulator/BlackoilState.hpp>
#include <opm/autodiff/WellStateFullyImplicitBlackoil.hpp>
#include <opm/autodiff/BlackoilModelParameters.hpp>
#include <opm/simulators/timestepping/SimulatorTimerInterface.hpp>
#include <opm/autodiff/multiPhaseUpwind.hpp>
namespace Opm {
/// A model implementation for the transport equation in three-phase black oil.
template<class Grid, class WellModel>
class BlackoilTransportModel : public BlackoilModelBase<Grid, WellModel, BlackoilTransportModel<Grid, WellModel> >
{
public:
typedef BlackoilModelBase<Grid, WellModel, BlackoilTransportModel<Grid, WellModel> > Base;
friend Base;
typedef typename Base::ReservoirState ReservoirState;
typedef typename Base::WellState WellState;
typedef typename Base::SolutionState SolutionState;
typedef typename Base::V V;
/// Construct the model. It will retain references to the
/// arguments of this functions, and they are expected to
/// remain in scope for the lifetime of the solver.
/// \param[in] param parameters
/// \param[in] grid grid data structure
/// \param[in] fluid fluid properties
/// \param[in] geo rock properties
/// \param[in] rock_comp_props if non-null, rock compressibility properties
/// \param[in] wells_arg well structure
/// \param[in] linsolver linear solver
/// \param[in] eclState eclipse state
/// \param[in] has_disgas turn on dissolved gas
/// \param[in] has_vapoil turn on vaporized oil feature
/// \param[in] terminal_output request output to cout/cerr
BlackoilTransportModel(const typename Base::ModelParameters& param,
const Grid& grid,
const BlackoilPropsAdFromDeck& fluid,
const DerivedGeology& geo,
const RockCompressibility* rock_comp_props,
const StandardWells& std_wells,
const NewtonIterationBlackoilInterface& linsolver,
std::shared_ptr<const EclipseState> eclState,
std::shared_ptr<const Schedule> schedule,
std::shared_ptr<const SummaryConfig> summary_config,
const bool has_disgas,
const bool has_vapoil,
const bool terminal_output)
: Base(param, grid, fluid, geo, rock_comp_props, std_wells, linsolver,
eclState, schedule, summary_config, has_disgas, has_vapoil, terminal_output)
{
}
void prepareStep(const SimulatorTimerInterface& timer,
const ReservoirState& reservoir_state,
const WellState& well_state)
{
Base::prepareStep(timer, reservoir_state, well_state);
Base::param_.solve_welleq_initially_ = false;
SolutionState state0 = variableState(reservoir_state, well_state);
asImpl().makeConstantState(state0);
asImpl().computeAccum(state0, 0);
}
SimulatorReport
assemble(const ReservoirState& reservoir_state,
WellState& well_state,
const bool initial_assembly)
{
using namespace Opm::AutoDiffGrid;
SimulatorReport report;
// If we have VFP tables, we need the well connection
// pressures for the "simple" hydrostatic correction
// between well depth and vfp table depth.
if (isVFPActive()) {
SolutionState state = asImpl().variableState(reservoir_state, well_state);
SolutionState state0 = state;
asImpl().makeConstantState(state0);
asImpl().wellModel().computeWellConnectionPressures(state0, well_state);
}
// Possibly switch well controls and updating well state to
// get reasonable initial conditions for the wells
asImpl().wellModel().updateWellControls(well_state);
if (initial_assembly) {
// HACK
const_cast<V&>(total_flux_)
= Eigen::Map<const V>(reservoir_state.faceflux().data(), reservoir_state.faceflux().size());
const_cast<V&>(total_wellperf_flux_)
= Eigen::Map<const V>(well_state.perfRates().data(), well_state.perfRates().size());
const_cast<DataBlock&>(comp_wellperf_flux_)
= Eigen::Map<const DataBlock>(well_state.perfPhaseRates().data(), well_state.perfRates().size(), numPhases());
assert(numPhases() * well_state.perfRates().size() == well_state.perfPhaseRates().size());
asImpl().updatePhaseCondFromPrimalVariable(reservoir_state);
}
// Create the primary variables.
SolutionState state = asImpl().variableState(reservoir_state, well_state);
if (initial_assembly) {
SolutionState state0 = state;
asImpl().makeConstantState(state0);
asImpl().wellModel().computeWellConnectionPressures(state0, well_state);
}
// -------- Mass balance equations --------
asImpl().assembleMassBalanceEq(state);
// -------- Well equations ----------
if ( ! wellsActive() ) {
return report;
}
std::vector<ADB> mob_perfcells;
std::vector<ADB> b_perfcells;
asImpl().wellModel().extractWellPerfProperties(state, sd_.rq, mob_perfcells, b_perfcells);
if (param_.solve_welleq_initially_ && initial_assembly) {
// solve the well equations as a pre-processing step
report += asImpl().solveWellEq(mob_perfcells, b_perfcells, reservoir_state, state, well_state);
}
V aliveWells;
std::vector<ADB> cq_s;
// @afr changed
// asImpl().wellModel().computeWellFlux(state, mob_perfcells, b_perfcells, aliveWells, cq_s);
asImpl().computeWellFlux(state, mob_perfcells, b_perfcells, aliveWells, cq_s);
// end of changed
asImpl().wellModel().updatePerfPhaseRatesAndPressures(cq_s, state, well_state);
asImpl().wellModel().addWellFluxEq(cq_s, state, residual_);
asImpl().addWellContributionToMassBalanceEq(cq_s, state, well_state);
asImpl().wellModel().addWellControlEq(state, well_state, aliveWells, residual_);
return report;
}
/// Solve the Jacobian system Jx = r where J is the Jacobian and
/// r is the residual.
V solveJacobianSystem() const
{
const int n_transport = residual_.material_balance_eq[1].size();
const int n_full = residual_.sizeNonLinear();
const auto& mb = residual_.material_balance_eq;
LinearisedBlackoilResidual transport_res = {
{
// TODO: handle general 2-phase etc.
ADB::function(mb[1].value(), { mb[1].derivative()[1], mb[1].derivative()[2] }),
ADB::function(mb[2].value(), { mb[2].derivative()[1], mb[2].derivative()[2] })
},
ADB::null(),
ADB::null(),
residual_.matbalscale,
residual_.singlePrecision
};
assert(transport_res.sizeNonLinear() == 2*n_transport);
V dx_transport = linsolver_.computeNewtonIncrement(transport_res);
assert(dx_transport.size() == 2*n_transport);
V dx_full = V::Zero(n_full);
for (int i = 0; i < 2*n_transport; ++i) {
dx_full(n_transport + i) = dx_transport(i);
}
return dx_full;
}
using Base::numPhases;
using Base::numMaterials;
protected:
using Base::asImpl;
using Base::materialName;
using Base::convergenceReduction;
using Base::maxResidualAllowed;
using Base::linsolver_;
using Base::residual_;
using Base::sd_;
using Base::geo_;
using Base::ops_;
using Base::grid_;
using Base::use_threshold_pressure_;
using Base::canph_;
using Base::active_;
using Base::pvdt_;
using Base::fluid_;
using Base::param_;
using Base::terminal_output_;
using Base::isVFPActive;
using Base::phaseCondition;
using Base::vfp_properties_;
using Base::wellsActive;
V total_flux_; // HACK, should be part of a revised (transport-specific) SolutionState.
V total_wellperf_flux_;
DataBlock comp_wellperf_flux_;
Eigen::Array<double, Eigen::Dynamic, Eigen::Dynamic> upwind_flags_;
SolutionState
variableState(const ReservoirState& x,
const WellState& xw) const
{
// As Base::variableState(), except making Pressure, Qs and Bhp constants.
std::vector<V> vars0 = asImpl().variableStateInitials(x, xw);
std::vector<ADB> vars = ADB::variables(vars0);
const std::vector<int> indices = asImpl().variableStateIndices();
vars[indices[Pressure]] = ADB::constant(vars[indices[Pressure]].value());
vars[indices[Qs]] = ADB::constant(vars[indices[Qs]].value());
vars[indices[Bhp]] = ADB::constant(vars[indices[Bhp]].value());
return asImpl().variableStateExtractVars(x, indices, vars);
}
void assembleMassBalanceEq(const SolutionState& state)
{
// Compute b_p and the accumulation term b_p*s_p for each phase,
// except gas. For gas, we compute b_g*s_g + Rs*b_o*s_o.
// These quantities are stored in sd_.rq[phase].accum[1].
// The corresponding accumulation terms from the start of
// the timestep (b^0_p*s^0_p etc.) were already computed
// on the initial call to assemble() and stored in sd_.rq[phase].accum[0].
asImpl().computeAccum(state, 1);
// Set up the common parts of the mass balance equations
// for each active phase.
const V transi = subset(geo_.transmissibility(), ops_.internal_faces);
const V trans_nnc = ops_.nnc_trans;
V trans_all(transi.size() + trans_nnc.size());
trans_all << transi, trans_nnc;
const ADB tr_mult = asImpl().transMult(state.pressure);
const V gdz = geo_.gravity()[2] * (ops_.grad * geo_.z().matrix());
// Compute mobilities and heads
const std::vector<PhasePresence>& cond = asImpl().phaseCondition();
const std::vector<ADB> kr = asImpl().computeRelPerm(state);
#pragma omp parallel for schedule(static)
for (int phase_idx = 0; phase_idx < numPhases(); ++phase_idx) {
// Compute and store mobilities.
const int canonical_phase_idx = canph_[ phase_idx ];
const ADB& phase_pressure = state.canonical_phase_pressures[canonical_phase_idx];
sd_.rq[phase_idx].mu = asImpl().fluidViscosity(canonical_phase_idx, phase_pressure, state.temperature, state.rs, state.rv, cond);
sd_.rq[phase_idx].kr = kr[canonical_phase_idx];
// Note that the pressure-dependent transmissibility multipliers are considered
// part of the mobility here.
sd_.rq[ phase_idx ].mob = tr_mult * sd_.rq[phase_idx].kr / sd_.rq[phase_idx].mu;
// Compute head differentials. Gravity potential is done using the face average as in eclipse and MRST.
sd_.rq[phase_idx].rho = asImpl().fluidDensity(canonical_phase_idx, sd_.rq[phase_idx].b, state.rs, state.rv);
const ADB rhoavg = ops_.caver * sd_.rq[phase_idx].rho;
sd_.rq[ phase_idx ].dh = ops_.grad * phase_pressure - rhoavg * gdz;
if (use_threshold_pressure_) {
asImpl().applyThresholdPressures(sd_.rq[ phase_idx ].dh);
}
}
// Extract saturation-dependent part of head differences.
const ADB gradp = ops_.grad * state.pressure;
std::vector<ADB> dh_sat(numPhases(), ADB::null());
for (int phase_idx = 0; phase_idx < numPhases(); ++phase_idx) {
dh_sat[phase_idx] = gradp - sd_.rq[phase_idx].dh;
}
// Find upstream directions for each phase.
upwind_flags_ = multiPhaseUpwind(dh_sat, trans_all);
// Compute (upstream) phase and total mobilities for connections.
// Also get upstream b, rs, and rv values to avoid recreating the UpwindSelector.
std::vector<ADB> mob(numPhases(), ADB::null());
std::vector<ADB> b(numPhases(), ADB::null());
ADB rs = ADB::null();
ADB rv = ADB::null();
ADB tot_mob = ADB::constant(V::Zero(gdz.size()));
for (int phase_idx = 0; phase_idx < numPhases(); ++phase_idx) {
UpwindSelector<double> upwind(grid_, ops_, upwind_flags_.col(phase_idx));
mob[phase_idx] = upwind.select(sd_.rq[phase_idx].mob);
tot_mob += mob[phase_idx];
b[phase_idx] = upwind.select(sd_.rq[phase_idx].b);
if (canph_[phase_idx] == Oil) {
rs = upwind.select(state.rs);
}
if (canph_[phase_idx] == Gas) {
rv = upwind.select(state.rv);
}
}
// Compute phase fluxes.
for (int phase_idx = 0; phase_idx < numPhases(); ++phase_idx) {
ADB gflux = ADB::constant(V::Zero(gdz.size()));
for (int other_phase = 0; other_phase < numPhases(); ++other_phase) {
if (phase_idx != other_phase) {
gflux += mob[other_phase] * (dh_sat[phase_idx] - dh_sat[other_phase]);
}
}
sd_.rq[phase_idx].mflux = b[phase_idx] * (mob[phase_idx] / tot_mob) * (total_flux_ + trans_all * gflux);
}
#pragma omp parallel for schedule(static)
for (int phase_idx = 0; phase_idx < numPhases(); ++phase_idx) {
// const int canonical_phase_idx = canph_[ phase_idx ];
// const ADB& phase_pressure = state.canonical_phase_pressures[canonical_phase_idx];
// asImpl().computeMassFlux(phase_idx, trans_all, kr[canonical_phase_idx], phase_pressure, state);
// Material balance equation for this phase.
residual_.material_balance_eq[ phase_idx ] =
pvdt_ * (sd_.rq[phase_idx].accum[1] - sd_.rq[phase_idx].accum[0])
+ ops_.div*sd_.rq[phase_idx].mflux;
}
// -------- Extra (optional) rs and rv contributions to the mass balance equations --------
// Add the extra (flux) terms to the mass balance equations
// From gas dissolved in the oil phase (rs) and oil vaporized in the gas phase (rv)
// The extra terms in the accumulation part of the equation are already handled.
if (active_[ Oil ] && active_[ Gas ]) {
const int po = fluid_.phaseUsage().phase_pos[ Oil ];
const int pg = fluid_.phaseUsage().phase_pos[ Gas ];
residual_.material_balance_eq[ pg ] += ops_.div * (rs * sd_.rq[po].mflux);
residual_.material_balance_eq[ po ] += ops_.div * (rv * sd_.rq[pg].mflux);
}
if (param_.update_equations_scaling_) {
asImpl().updateEquationsScaling();
}
}
Eigen::Array<double, Eigen::Dynamic, Eigen::Dynamic> multiPhaseUpwind(const std::vector<ADB>& head_diff,
const V& transmissibility)
{
assert(numPhases() == 3);
const int num_connections = head_diff[0].size();
Eigen::Array<double, Eigen::Dynamic, Eigen::Dynamic> upwind(num_connections, numPhases());
for (int conn = 0; conn < num_connections; ++conn) {
const double q = total_flux_[conn];
const double t = transmissibility[conn];
const int a = ops_.connection_cells(conn, 0); // first cell of connection
const int b = ops_.connection_cells(conn, 1); // second cell of connection
auto up = connectionMultiPhaseUpwind(
{{ head_diff[0].value()[conn], head_diff[1].value()[conn], head_diff[2].value()[conn] }},
{{ sd_.rq[0].mob.value()[a], sd_.rq[1].mob.value()[a], sd_.rq[2].mob.value()[a]}},
{{ sd_.rq[0].mob.value()[b], sd_.rq[1].mob.value()[b], sd_.rq[2].mob.value()[b]}},
t,
q);
for (int ii = 0; ii < numPhases(); ++ii) {
upwind(conn, ii) = up[ii];
}
}
return upwind;
}
void computeWellFlux(const SolutionState& state,
const std::vector<ADB>& mob_perfcells,
const std::vector<ADB>& b_perfcells,
V& /* aliveWells */,
std::vector<ADB>& cq_s) const
{
// Note that use of this function replaces using the well models'
// function of the same name.
if( ! asImpl().localWellsActive() ) return ;
const int np = asImpl().wells().number_of_phases;
const int nw = asImpl().wells().number_of_wells;
const int nperf = asImpl().wells().well_connpos[nw];
const Opm::PhaseUsage& pu = asImpl().fluid_.phaseUsage();
// Compute total mobilities for perforations.
ADB totmob_perfcells = ADB::constant(V::Zero(nperf));
for (int phase = 0; phase < numPhases(); ++phase) {
totmob_perfcells += mob_perfcells[phase];
}
// Compute fractional flow.
std::vector<ADB> frac_flow(np, ADB::null());
for (int phase = 0; phase < np; ++phase) {
frac_flow[phase] = mob_perfcells[phase] / totmob_perfcells;
}
// Identify injecting and producing perforations.
V is_inj = V::Zero(nperf);
V is_prod = V::Zero(nperf);
for (int c = 0; c < nperf; ++c){
if (total_wellperf_flux_[c] > 0.0) {
is_inj[c] = 1;
} else {
is_prod[c] = 1;
}
}
// Compute fluxes for producing perforations.
std::vector<ADB> cq_s_prod(3, ADB::null());
for (int phase = 0; phase < np; ++phase) {
// For producers, we use the total reservoir flux from the pressure solver.
cq_s_prod[phase] = b_perfcells[phase] * frac_flow[phase] * total_wellperf_flux_;
}
if (asImpl().has_disgas_ || asImpl().has_vapoil_) {
const int oilpos = pu.phase_pos[Oil];
const int gaspos = pu.phase_pos[Gas];
const ADB cq_s_prod_oil = cq_s_prod[oilpos];
const ADB cq_s_prod_gas = cq_s_prod[gaspos];
cq_s_prod[gaspos] += subset(state.rs, Base::well_model_.wellOps().well_cells) * cq_s_prod_oil;
cq_s_prod[oilpos] += subset(state.rv, Base::well_model_.wellOps().well_cells) * cq_s_prod_gas;
}
// Compute well perforation surface volume fluxes.
cq_s.resize(np, ADB::null());
for (int phase = 0; phase < np; ++phase) {
const int pos = pu.phase_pos[phase];
// For injectors, we use the component fluxes computed by the pressure solver.
const V cq_s_inj = comp_wellperf_flux_.col(pos);
cq_s[phase] = is_prod * cq_s_prod[phase] + is_inj * cq_s_inj;
}
}
bool getConvergence(const SimulatorTimerInterface& timer, const int iteration)
{
const double dt = timer.currentStepLength();
const double tol_mb = param_.tolerance_mb_;
const double tol_cnv = param_.tolerance_cnv_;
const int nc = Opm::AutoDiffGrid::numCells(grid_);
const int np = asImpl().numPhases();
const int nm = asImpl().numMaterials();
assert(int(sd_.rq.size()) == nm);
const V& pv = geo_.poreVolume();
std::vector<double> R_sum(nm);
std::vector<double> B_avg(nm);
std::vector<double> maxCoeff(nm);
std::vector<double> maxNormWell(np);
Eigen::Array<typename V::Scalar, Eigen::Dynamic, Eigen::Dynamic> B(nc, nm);
Eigen::Array<typename V::Scalar, Eigen::Dynamic, Eigen::Dynamic> R(nc, nm);
Eigen::Array<typename V::Scalar, Eigen::Dynamic, Eigen::Dynamic> tempV(nc, nm);
for ( int idx = 0; idx < nm; ++idx )
{
const ADB& tempB = sd_.rq[idx].b;
B.col(idx) = 1./tempB.value();
R.col(idx) = residual_.material_balance_eq[idx].value();
tempV.col(idx) = R.col(idx).abs()/pv;
}
const double pvSum = convergenceReduction(B, tempV, R,
R_sum, maxCoeff, B_avg, maxNormWell,
nc);
std::vector<double> CNV(nm);
std::vector<double> mass_balance_residual(nm);
std::vector<double> well_flux_residual(np);
bool converged_MB = true;
bool converged_CNV = true;
// Finish computation
for ( int idx = 1; idx < nm; ++idx ) {
CNV[idx] = B_avg[idx] * dt * maxCoeff[idx];
mass_balance_residual[idx] = std::abs(B_avg[idx]*R_sum[idx]) * dt / pvSum;
converged_MB = converged_MB && (mass_balance_residual[idx] < tol_mb);
converged_CNV = converged_CNV && (CNV[idx] < tol_cnv);
assert(nm >= np);
}
const bool converged = converged_MB && converged_CNV;
for (int idx = 0; idx < nm; ++idx) {
if (std::isnan(mass_balance_residual[idx])
|| std::isnan(CNV[idx])
|| (idx < np && std::isnan(well_flux_residual[idx]))) {
OPM_THROW(Opm::NumericalIssue, "NaN residual for phase " << materialName(idx));
}
if (mass_balance_residual[idx] > maxResidualAllowed()
|| CNV[idx] > maxResidualAllowed()
|| (idx < np && well_flux_residual[idx] > maxResidualAllowed())) {
OPM_THROW(Opm::NumericalIssue, "Too large residual for phase " << materialName(idx));
}
}
if ( terminal_output_ ) {
// Only rank 0 does print to std::cout
std::ostringstream os;
if (iteration == 0) {
os << "\nIter";
for (int idx = 1; idx < nm; ++idx) {
os << " MB(" << materialName(idx).substr(0, 3) << ") ";
}
for (int idx = 1; idx < nm; ++idx) {
os << " CNV(" << materialName(idx).substr(0, 1) << ") ";
}
os << '\n';
}
os.precision(3);
os.setf(std::ios::scientific);
os << std::setw(4) << iteration;
for (int idx = 1; idx < nm; ++idx) {
os << std::setw(11) << mass_balance_residual[idx];
}
for (int idx = 1; idx < nm; ++idx) {
os << std::setw(11) << CNV[idx];
}
OpmLog::info(os.str());
}
return converged;
}
};
/// Providing types by template specialisation of ModelTraits for BlackoilTransportModel.
template <class Grid, class WellModel>
struct ModelTraits< BlackoilTransportModel<Grid, WellModel> >
{
typedef BlackoilState ReservoirState;
typedef WellStateFullyImplicitBlackoil WellState;
typedef BlackoilModelParameters ModelParameters;
typedef DefaultBlackoilSolutionState SolutionState;
};
} // namespace Opm
#endif // OPM_BLACKOILTRANSPORTMODEL_HEADER_INCLUDED

View File

@ -1,54 +0,0 @@
/*
Copyright 2016 SINTEF ICT, Applied Mathematics.
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 3 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 <http://www.gnu.org/licenses/>.
*/
#ifndef OPM_DEBUGTIMEREPORT_HEADER_INCLUDED
#define OPM_DEBUGTIMEREPORT_HEADER_INCLUDED
#include <opm/common/OpmLog/OpmLog.hpp>
#include <opm/grid/utility/StopWatch.hpp>
#include <string>
namespace Opm
{
class DebugTimeReport
{
public:
explicit DebugTimeReport(const std::string& report_name)
: report_name_(report_name)
{
clock_.start();
}
~DebugTimeReport()
{
clock_.stop();
std::ostringstream msg;
msg << report_name_ << " took: " << clock_.secsSinceStart() << " seconds.";
OpmLog::debug(msg.str());
}
private:
std::string report_name_;
time::StopWatch clock_;
};
} // namespace Opm
#endif // OPM_DEBUGTIMEREPORT_HEADER_INCLUDED

View File

@ -1,57 +0,0 @@
/*
Copyright 2013, 2015 SINTEF ICT, Applied Mathematics.
Copyright 2014, 2015 Statoil ASA.
Copyright 2014, 2015 Dr. Markus Blatt - HPC-Simulation-Software & Services
Copyright 2015 NTNU
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 3 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 <http://www.gnu.org/licenses/>.
*/
#ifndef OPM_DEFAULTBLACKOILSOLUTIONSTATE_HEADER_INCLUDED
#define OPM_DEFAULTBLACKOILSOLUTIONSTATE_HEADER_INCLUDED
#include <opm/autodiff/AutoDiffBlock.hpp>
namespace Opm {
/// Struct for containing iteration variables.
struct DefaultBlackoilSolutionState
{
typedef AutoDiffBlock<double> ADB;
explicit DefaultBlackoilSolutionState(const int np)
: pressure ( ADB::null())
, temperature( ADB::null())
, saturation(np, ADB::null())
, rs ( ADB::null())
, rv ( ADB::null())
, qs ( ADB::null())
, bhp ( ADB::null())
, wellVariables ( ADB::null())
, canonical_phase_pressures(3, ADB::null())
{
}
ADB pressure;
ADB temperature;
std::vector<ADB> saturation;
ADB rs;
ADB rv;
ADB qs;
ADB bhp;
ADB wellVariables;
// Below are quantities stored in the state for optimization purposes.
std::vector<ADB> canonical_phase_pressures; // Always has 3 elements, even if only 2 phases active.
};
} // namespace Opm
#endif // OPM_DEFAULTBLACKOILSOLUTIONSTATE_HEADER_INCLUDED

View File

@ -1,110 +0,0 @@
/*
Copyright 2014 SINTEF ICT, Applied Mathematics.
Copyright 2014 IRIS AS
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 3 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 <http://www.gnu.org/licenses/>.
*/
#ifndef OPM_DUNEMATRIX_HEADER_INCLUDED
#define OPM_DUNEMATRIX_HEADER_INCLUDED
#ifdef DUNE_BCRSMATRIX_HH
#error This header must be included before any bcrsmatrix.hh is included (directly or indirectly)
#endif
#include <opm/common/utility/platform_dependent/disable_warnings.h>
#include <Eigen/Eigen>
#include <Eigen/Sparse>
#include <dune/common/fmatrix.hh>
#include <dune/common/version.hh>
#include <dune/istl/bcrsmatrix.hh>
#include <opm/common/utility/platform_dependent/reenable_warnings.h>
namespace Opm
{
class DuneMatrix : public Dune::BCRSMatrix< Dune::FieldMatrix<double, 1, 1> >
{
public:
DuneMatrix(const int rows, const int cols, const int* ia, const int* ja, const double* sa)
{
// create BCRSMatrix from given CSR storage
init( rows, cols, ia, ja, sa );
}
/// \brief create an ISTL BCRSMatrix from a Eigen::SparseMatrix
DuneMatrix( const Eigen::SparseMatrix<double, Eigen::RowMajor>& matrix )
{
// Create ISTL matrix.
const int rows = matrix.rows();
const int cols = matrix.cols();
const int* ia = matrix.outerIndexPtr();
const int* ja = matrix.innerIndexPtr();
const double* sa = matrix.valuePtr();
// create BCRSMatrix from Eigen matrix
init( rows, cols, ia, ja, sa );
}
protected:
void init(const int rows, const int cols, const int* ia, const int* ja, const double* sa)
{
typedef Dune::BCRSMatrix< Dune::FieldMatrix<double, 1, 1> > Super;
typedef Super::block_type block_type;
this->build_mode = Super::unknown;
this->ready = Super::built;
this->n = rows;
this->m = cols;
typedef Super::size_type size_type ;
#if DUNE_VERSION_NEWER_REV(DUNE_ISTL, 2, 4, 1)
size_type& nnz = this->nnz_;
std::shared_ptr<size_type>& j = this->j_;
#else
size_type& nnz = this->nnz;
std::shared_ptr<size_type>& j = this->j;
#endif
nnz = ia[rows];
#if DUNE_VERSION_NEWER(DUNE_ISTL, 2, 5)
this->allocationSize_ = nnz;
#else
this->allocationSize = nnz;
#endif
this->avg = 0;
this->overflowsize = -1.0;
// make sure to use the allocators of this matrix
// because the same allocators are used to deallocate the data
this->a = this->allocator_.allocate(nnz);
static_assert(sizeof(block_type) == sizeof(double), "This constructor requires a block type that is the same as a double.");
std::copy(sa, sa + nnz, reinterpret_cast<double*>(this->a));
j.reset(this->sizeAllocator_.allocate(nnz));
std::copy(ja, ja +nnz, j.get());
this->r = rowAllocator_.allocate(rows);
for (int row = 0; row < rows; ++row) {
this->r[row].set(ia[row+1] - ia[row], this->a + ia[row], j.get() + ia[row]);
}
}
};
} // namespace Opm
#endif // OPM_DUNEMATRIX_HEADER_INCLUDED

View File

@ -1,975 +0,0 @@
/*
Copyright 2013, 2014, 2015 SINTEF ICT, Applied Mathematics.
Copyright 2014 Dr. Blatt - HPC-Simulation-Software & Services
Copyright 2015 IRIS AS
Copyright 2014 STATOIL ASA.
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 3 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 <http://www.gnu.org/licenses/>.
*/
#ifndef OPM_FLOWMAIN_HEADER_INCLUDED
#define OPM_FLOWMAIN_HEADER_INCLUDED
#include <opm/common/utility/platform_dependent/disable_warnings.h>
#include <dune/common/version.hh>
#include <dune/common/parallel/mpihelper.hh>
#include <opm/common/utility/platform_dependent/reenable_warnings.h>
#include <opm/grid/GridManager.hpp>
#include <opm/autodiff/GridHelpers.hpp>
#include <opm/autodiff/createGlobalCellArray.hpp>
#include <opm/autodiff/GridInit.hpp>
#include <opm/simulators/ParallelFileMerger.hpp>
#include <opm/simulators/ensureDirectoryExists.hpp>
#include <opm/core/wells.h>
#include <opm/core/wells/WellsManager.hpp>
#include <opm/common/ErrorMacros.hpp>
#include <opm/core/simulator/initState.hpp>
#include <opm/core/simulator/initStateEquil.hpp>
#include <opm/core/simulator/SimulatorReport.hpp>
#include <opm/simulators/timestepping/SimulatorTimer.hpp>
#include <opm/core/utility/miscUtilities.hpp>
#include <opm/common/utility/parameters/ParameterGroup.hpp>
#include <opm/simulators/thresholdPressures.hpp> // Note: the GridHelpers must be included before this (to make overloads available). \TODO: Fix.
#include <opm/material/fluidmatrixinteractions/EclMaterialLawManager.hpp>
#include <opm/core/props/BlackoilPropertiesBasic.hpp>
#include <opm/core/props/BlackoilPropertiesFromDeck.hpp>
#include <opm/core/props/rock/RockCompressibility.hpp>
#include <opm/core/props/satfunc/RelpermDiagnostics.hpp>
#include <opm/core/linalg/LinearSolverFactory.hpp>
#include <opm/autodiff/NewtonIterationBlackoilSimple.hpp>
#include <opm/autodiff/NewtonIterationBlackoilCPR.hpp>
#include <opm/autodiff/NewtonIterationBlackoilInterleaved.hpp>
#include <opm/autodiff/WellStateFullyImplicitBlackoil.hpp>
#include <opm/autodiff/BlackoilPropsAdFromDeck.hpp>
#include <opm/autodiff/RedistributeDataHandles.hpp>
#include <opm/autodiff/moduleVersion.hpp>
#include <opm/autodiff/MissingFeatures.hpp>
#include <opm/core/utility/share_obj.hpp>
#include <opm/core/utility/initHydroCarbonState.hpp>
#include <opm/common/OpmLog/OpmLog.hpp>
#include <opm/common/OpmLog/EclipsePRTLog.hpp>
#include <opm/common/OpmLog/LogUtil.hpp>
#include <opm/parser/eclipse/Deck/Deck.hpp>
#include <opm/parser/eclipse/Parser/Parser.hpp>
#include <opm/parser/eclipse/Parser/ParseContext.hpp>
#include <opm/parser/eclipse/EclipseState/EclipseState.hpp>
#include <opm/parser/eclipse/EclipseState/IOConfig/IOConfig.hpp>
#include <opm/parser/eclipse/EclipseState/InitConfig/InitConfig.hpp>
#include <opm/parser/eclipse/EclipseState/checkDeck.hpp>
#include <opm/material/common/ResetLocale.hpp>
#include <boost/filesystem.hpp>
#include <boost/algorithm/string.hpp>
#ifdef _OPENMP
#include <omp.h>
#endif
#include <memory>
#include <algorithm>
#include <cstdlib>
#include <iostream>
#include <vector>
#include <numeric>
#include <cstdlib>
#include <stdexcept>
namespace Opm
{
namespace detail
{
boost::filesystem::path simulationCaseName( const std::string& casename );
}
/// This class encapsulates the setup and running of
/// a simulator based on an input deck.
template <class Implementation, class Grid, class Simulator>
class FlowMainBase
{
public:
/// This is the main function of Flow.
/// It runs a complete simulation, with the given grid and
/// simulator classes, based on user command-line input. The
/// content of this function used to be in the main() function of
/// flow.cpp.
int execute(int argc, char** argv)
try {
// we always want to use the default locale, and thus spare us the trouble
// with incorrect locale settings.
resetLocale();
// Setup.
asImpl().setupParallelism(argc, argv);
asImpl().printStartupMessage();
const bool ok = asImpl().setupParameters(argc, argv);
if (!ok) {
return EXIT_FAILURE;
}
asImpl().readDeckInput();
asImpl().setupOutput();
asImpl().setupLogging();
asImpl().setupGridAndProps();
asImpl().runDiagnostics();
asImpl().setupState();
asImpl().writeInit();
asImpl().distributeData();
asImpl().setupOutputWriter();
asImpl().setupLinearSolver();
asImpl().createSimulator();
// Run.
auto ret = asImpl().runSimulator();
asImpl().mergeParallelLogFiles();
return ret;
}
catch (const std::exception &e) {
std::ostringstream message;
message << "Program threw an exception: " << e.what();
if( output_cout_ )
{
// in some cases exceptions are thrown before the logging system is set
// up.
if (OpmLog::hasBackend("STREAMLOG")) {
OpmLog::error(message.str());
}
else {
std::cout << message.str() << "\n";
}
}
return EXIT_FAILURE;
}
protected:
// ------------ Types ------------
typedef BlackoilPropsAdFromDeck FluidProps;
typedef FluidProps::MaterialLawManager MaterialLawManager;
typedef typename Simulator::ReservoirState ReservoirState;
typedef typename Simulator::OutputWriter OutputWriter;
// ------------ Data members ------------
// The comments indicate in which method the
// members first occur.
// setupParallelism()
int mpi_rank_ = 0;
bool output_cout_ = false;
bool must_distribute_ = false;
// setupParameters()
ParameterGroup param_;
// setupOutput()
bool output_to_files_ = false;
std::string output_dir_ = std::string(".");
// readDeckInput()
std::shared_ptr<Deck> deck_;
std::shared_ptr<EclipseState> eclipse_state_;
std::shared_ptr<Schedule> schedule_;
std::shared_ptr<SummaryConfig> summary_config_;
// setupGridAndProps()
std::unique_ptr<GridInit<Grid>> grid_init_;
std::shared_ptr<MaterialLawManager> material_law_manager_;
std::unique_ptr<FluidProps> fluidprops_;
std::unique_ptr<RockCompressibility> rock_comp_;
std::array<double, 3> gravity_;
bool use_local_perm_ = true;
std::unique_ptr<DerivedGeology> geoprops_;
// setupState()
std::unique_ptr<ReservoirState> state_;
std::vector<double> threshold_pressures_;
// distributeData()
boost::any parallel_information_;
// setupOutputWriter()
std::unique_ptr<EclipseIO> eclipse_writer_;
std::unique_ptr<OutputWriter> output_writer_;
// setupLinearSolver
std::unique_ptr<NewtonIterationBlackoilInterface> fis_solver_;
// createSimulator()
std::unique_ptr<Simulator> simulator_;
// create log file
std::string logFile_;
// The names of wells that are artifically defunct in parallel runs.
// Those wells are handled on a another process.
std::unordered_set<std::string> defunct_well_names_;
// ------------ Methods ------------
// Set up MPI and OpenMP.
// Writes to:
// output_cout_
// must_distribute_
void setupParallelism(int argc, char** argv)
{
// MPI setup.
// Must ensure an instance of the helper is created to initialise MPI.
// For a build without MPI the Dune::FakeMPIHelper is used, so rank will
// be 0 and size 1.
const Dune::MPIHelper& mpi_helper = Dune::MPIHelper::instance(argc, argv);
mpi_rank_ = mpi_helper.rank();
const int mpi_size = mpi_helper.size();
output_cout_ = ( mpi_rank_ == 0 );
must_distribute_ = ( mpi_size > 1 );
#ifdef _OPENMP
// OpenMP setup.
if (!getenv("OMP_NUM_THREADS")) {
// Default to at most 4 threads, regardless of
// number of cores (unless ENV(OMP_NUM_THREADS) is defined)
int num_cores = omp_get_num_procs();
int num_threads = std::min(4, num_cores);
omp_set_num_threads(num_threads);
}
#pragma omp parallel
if (omp_get_thread_num() == 0) {
// omp_get_num_threads() only works as expected within a parallel region.
const int num_omp_threads = omp_get_num_threads();
if (mpi_size == 1) {
std::cout << "OpenMP using " << num_omp_threads << " threads." << std::endl;
} else {
std::cout << "OpenMP using " << num_omp_threads << " threads on MPI rank " << mpi_rank_ << "." << std::endl;
}
}
#endif
}
/// checks cartesian adjacency of global indices g1 and g2
bool cartesianAdjacent(const Grid& grid, int g1, int g2) {
// we need cartDims from UgGridHelpers
using namespace UgGridHelpers;
int diff = std::abs(g1 - g2);
const int * dimens = cartDims(grid);
if (diff == 1)
return true;
if (diff == dimens[0])
return true;
if (diff == dimens[0] * dimens[1])
return true;
return false;
}
// Print startup message if on output rank.
void printStartupMessage()
{
if (output_cout_) {
const std::string version = moduleVersionName();
std::cout << "**********************************************************************\n";
std::cout << "* *\n";
std::cout << "* This is flow_legacy (version " << version << ")"
<< std::string(26 - version.size(), ' ') << "*\n";
std::cout << "* *\n";
std::cout << "* Flow is a simulator for fully implicit three-phase black-oil flow, *\n";
std::cout << "* and is part of OPM. For more information see: *\n";
std::cout << "* https://opm-project.org *\n";
std::cout << "* *\n";
std::cout << "**********************************************************************\n\n";
}
}
// Read parameters, see if a deck was specified on the command line, and if
// it was, insert it into parameters.
// Writes to:
// param_
// Returns true if ok, false if not.
bool setupParameters(int argc, char** argv)
{
param_ = ParameterGroup(argc, argv, false, output_cout_);
// See if a deck was specified on the command line.
if (!param_.unhandledArguments().empty()) {
if (param_.unhandledArguments().size() != 1) {
std::cerr << "You can only specify a single input deck on the command line.\n";
return false;
} else {
const auto casename = detail::simulationCaseName( param_.unhandledArguments()[ 0 ] );
param_.insertParameter("deck_filename", casename.string() );
}
}
// We must have an input deck. Grid and props will be read from that.
if (!param_.has("deck_filename")) {
std::cerr << "This program must be run with an input deck.\n"
"Specify the deck filename either\n"
" a) as a command line argument by itself\n"
" b) as a command line parameter with the syntax deck_filename=<path to your deck>, or\n"
" c) as a parameter in a parameter file (.param or .xml) passed to the program.\n";
return false;
}
return true;
}
// Set output_to_files_ and set/create output dir. Write parameter file.
// Writes to:
// output_to_files_
// output_dir_
// Throws std::runtime_error if failed to create (if requested) output dir.
void setupOutput()
{
output_to_files_ = output_cout_ && param_.getDefault("output", true);
// Setup output directory.
auto& ioConfig = eclipse_state_->getIOConfig();
// Default output directory is the directory where the deck is found.
const std::string default_output_dir = ioConfig.getOutputDir();
output_dir_ = param_.getDefault("output_dir", default_output_dir);
// Override output directory if user specified.
ioConfig.setOutputDir(output_dir_);
bool opm_rst_file = param_.getDefault("enable-opm-rst-file", false);
ioConfig.setEclCompatibleRST(!opm_rst_file);
// Write parameters used for later reference. (only if rank is zero)
if (output_to_files_) {
// Create output directory if needed.
ensureDirectoryExists(output_dir_);
// Write simulation parameters.
param_.writeParam(output_dir_ + "/simulation.param");
}
}
// Setup OpmLog backend with output_dir.
void setupLogging()
{
std::string deck_filename = param_.get<std::string>("deck_filename");
// create logFile
using boost::filesystem::path;
path fpath(deck_filename);
std::string baseName;
std::ostringstream debugFileStream;
std::ostringstream logFileStream;
if (boost::to_upper_copy(path(fpath.extension()).string()) == ".DATA") {
baseName = path(fpath.stem()).string();
} else {
baseName = path(fpath.filename()).string();
}
logFileStream << output_dir_ << "/" << baseName;
debugFileStream << output_dir_ << "/" << "." << baseName;
if ( must_distribute_ && mpi_rank_ != 0 )
{
// Added rank to log file for non-zero ranks.
// This prevents message loss.
debugFileStream << "."<< mpi_rank_;
// If the following file appears then there is a bug.
logFileStream << "." << mpi_rank_;
}
logFileStream << ".PRT";
debugFileStream << ".DEBUG";
std::string debugFile = debugFileStream.str();
logFile_ = logFileStream.str();
std::shared_ptr<EclipsePRTLog> prtLog = std::make_shared<EclipsePRTLog>(logFile_ , Log::NoDebugMessageTypes, false, output_cout_);
const bool all_to_terminal = param_.getDefault("all_messages_to_terminal", false);
const auto terminal_msg_types = all_to_terminal ? Log::DefaultMessageTypes : Log::StdoutMessageTypes;
std::shared_ptr<StreamLog> streamLog = std::make_shared<StreamLog>(std::cout, terminal_msg_types);
OpmLog::addBackend( "ECLIPSEPRTLOG" , prtLog );
OpmLog::addBackend( "STREAMLOG", streamLog);
std::shared_ptr<StreamLog> debugLog = std::make_shared<EclipsePRTLog>(debugFile, Log::DefaultMessageTypes, false, output_cout_);
OpmLog::addBackend( "DEBUGLOG" , debugLog);
const auto& msgLimits = schedule_->getMessageLimits();
const std::map<int64_t, int> limits = {{Log::MessageType::Note, msgLimits.getCommentPrintLimit(0)},
{Log::MessageType::Info, msgLimits.getMessagePrintLimit(0)},
{Log::MessageType::Warning, msgLimits.getWarningPrintLimit(0)},
{Log::MessageType::Error, msgLimits.getErrorPrintLimit(0)},
{Log::MessageType::Problem, msgLimits.getProblemPrintLimit(0)},
{Log::MessageType::Bug, msgLimits.getBugPrintLimit(0)}};
prtLog->setMessageLimiter(std::make_shared<MessageLimiter>());
prtLog->setMessageFormatter(std::make_shared<SimpleMessageFormatter>(false));
streamLog->setMessageLimiter(std::make_shared<MessageLimiter>(10, limits));
streamLog->setMessageFormatter(std::make_shared<SimpleMessageFormatter>(true));
// Read parameters.
if ( output_cout_ )
{
OpmLog::debug("\n--------------- Reading parameters ---------------\n");
}
}
void mergeParallelLogFiles()
{
// force closing of all log files.
OpmLog::removeAllBackends();
if( mpi_rank_ != 0 || !must_distribute_ || !output_to_files_ )
{
return;
}
namespace fs = boost::filesystem;
fs::path output_path(".");
if ( param_.has("output_dir") )
{
output_path = fs::path(output_dir_);
}
fs::path deck_filename(param_.get<std::string>("deck_filename"));
std::for_each(fs::directory_iterator(output_path),
fs::directory_iterator(),
detail::ParallelFileMerger(output_path, deck_filename.stem().string()));
}
// Parser the input and creates the Deck and EclipseState objects.
// Writes to:
// deck_
// eclipse_state_
// May throw if errors are encountered, here configured to be somewhat tolerant.
void readDeckInput()
{
std::string deck_filename = param_.get<std::string>("deck_filename");
// Create Parser
Parser parser;
// Create Deck and EclipseState.
try {
ParseContext parseContext({ { ParseContext::PARSE_RANDOM_SLASH , InputError::IGNORE },
{ ParseContext::PARSE_MISSING_DIMS_KEYWORD, InputError::WARN },
{ ParseContext::SUMMARY_UNKNOWN_WELL, InputError::WARN },
{ ParseContext::SUMMARY_UNKNOWN_GROUP, InputError::WARN }});
deck_ = std::make_shared< Deck >( parser.parseFile(deck_filename, parseContext) );
checkDeck(*deck_, parser);
if ( output_cout_)
{
MissingFeatures::checkKeywords(*deck_);
}
eclipse_state_.reset(new EclipseState(*deck_, parseContext));
schedule_.reset(new Schedule(*deck_,
eclipse_state_->getInputGrid(),
eclipse_state_->get3DProperties(),
eclipse_state_->runspec(),
parseContext));
summary_config_.reset(new SummaryConfig(*deck_,
*schedule_,
eclipse_state_->getTableManager(),
parseContext));
}
catch (const std::invalid_argument& e) {
std::cerr << "Failed to create valid EclipseState object. See logfile: " << logFile_ << std::endl;
std::cerr << "Exception caught: " << e.what() << std::endl;
throw;
}
// Possibly override IOConfig setting (from deck) for how often RESTART files should get written to disk (every N report step)
if (param_.has("output_interval")) {
const int output_interval = param_.get<int>("output_interval");
eclipse_state_->getRestartConfig().overrideRestartWriteInterval( size_t( output_interval ) );
}
// Possible to force initialization only behavior (NOSIM).
if (param_.has("nosim")) {
const bool nosim = param_.get<bool>("nosim");
auto& ioConfig = eclipse_state_->getIOConfig();
ioConfig.overrideNOSIM( nosim );
}
}
// Create grid and property objects.
// Writes to:
// grid_init_
// material_law_manager_
// fluidprops_
// rock_comp_
// gravity_
// use_local_perm_
// geoprops_
void setupGridAndProps()
{
// Create grid.
const std::vector<double>& porv =
eclipse_state_->get3DProperties().getDoubleGridProperty("PORV").getData();
grid_init_.reset(new GridInit<Grid>(*eclipse_state_, porv));
const Grid& grid = grid_init_->grid();
// Create material law manager.
std::vector<int> compressedToCartesianIdx;
Opm::createGlobalCellArray(grid, compressedToCartesianIdx);
material_law_manager_.reset(new MaterialLawManager());
material_law_manager_->initFromDeck(*deck_, *eclipse_state_, compressedToCartesianIdx);
// Rock and fluid properties.
fluidprops_.reset(new BlackoilPropsAdFromDeck(*deck_, *eclipse_state_, material_law_manager_, grid));
// Rock compressibility.
rock_comp_.reset(new RockCompressibility(*eclipse_state_, output_cout_));
// Gravity.
assert(UgGridHelpers::dimensions(grid) == 3);
gravity_.fill(0.0);
gravity_[2] = deck_->hasKeyword("NOGRAV")
? param_.getDefault("gravity", 0.0)
: param_.getDefault("gravity", unit::gravity);
// Geological properties
use_local_perm_ = param_.getDefault("use_local_perm", use_local_perm_);
geoprops_.reset(new DerivedGeology(grid, *fluidprops_, *eclipse_state_, use_local_perm_, gravity_.data()));
}
// Initialise the reservoir state. Updated fluid props for SWATINIT.
// Writes to:
// state_
// threshold_pressures_
// fluidprops_ (if SWATINIT is used)
void setupState()
{
const PhaseUsage pu = Opm::phaseUsageFromDeck(*deck_);
const Grid& grid = grid_init_->grid();
// Need old-style fluid object for init purposes (only).
BlackoilPropertiesFromDeck props( *deck_, *eclipse_state_, material_law_manager_,
Opm::UgGridHelpers::numCells(grid),
Opm::UgGridHelpers::globalCell(grid),
Opm::UgGridHelpers::cartDims(grid),
param_);
// Init state variables (saturation and pressure).
if (param_.has("init_saturation")) {
state_.reset( new ReservoirState( Opm::UgGridHelpers::numCells(grid),
Opm::UgGridHelpers::numFaces(grid),
props.numPhases() ));
initStateBasic(Opm::UgGridHelpers::numCells(grid),
Opm::UgGridHelpers::globalCell(grid),
Opm::UgGridHelpers::cartDims(grid),
Opm::UgGridHelpers::numFaces(grid),
Opm::UgGridHelpers::faceCells(grid),
Opm::UgGridHelpers::beginFaceCentroids(grid),
Opm::UgGridHelpers::beginCellCentroids(grid),
Opm::UgGridHelpers::dimensions(grid),
props, param_, gravity_[2], *state_);
initBlackoilSurfvol(Opm::UgGridHelpers::numCells(grid), props, *state_);
enum { Oil = BlackoilPhases::Liquid, Gas = BlackoilPhases::Vapour };
if (pu.phase_used[Oil] && pu.phase_used[Gas]) {
const int numPhases = props.numPhases();
const int numCells = Opm::UgGridHelpers::numCells(grid);
// Uglyness 1: The state is a templated type, here we however make explicit use BlackoilState.
auto& gor = state_->getCellData( BlackoilState::GASOILRATIO );
const auto& surface_vol = state_->getCellData( BlackoilState::SURFACEVOL );
for (int c = 0; c < numCells; ++c) {
// Uglyness 2: Here we explicitly use the layout of the saturation in the surface_vol field.
gor[c] = surface_vol[ c * numPhases + pu.phase_pos[Gas]] / surface_vol[ c * numPhases + pu.phase_pos[Oil]];
}
}
} else if (deck_->hasKeyword("EQUIL")) {
// Which state class are we really using - what a f... mess?
state_.reset( new ReservoirState( Opm::UgGridHelpers::numCells(grid),
Opm::UgGridHelpers::numFaces(grid),
props.numPhases()));
typedef Opm::BlackOilFluidSystem<double> FluidSystem;
FluidSystem::initFromDeck(*deck_ , *eclipse_state_);
typedef EQUIL::DeckDependent::InitialStateComputer<FluidSystem> ISC;
ISC isc(*material_law_manager_, *eclipse_state_, grid, gravity_[2]);
const bool oil = FluidSystem::phaseIsActive(FluidSystem::oilPhaseIdx);
const int oilpos = FluidSystem::oilPhaseIdx;
const int waterpos = FluidSystem::waterPhaseIdx;
const int ref_phase = oil ? oilpos : waterpos;
state_->pressure() = isc.press()[ref_phase];
convertSats<FluidSystem>(state_->saturation(), isc.saturation(), pu);
state_->gasoilratio() = isc.rs();
state_->rv() = isc.rv();
} else {
state_.reset( new ReservoirState( Opm::UgGridHelpers::numCells(grid),
Opm::UgGridHelpers::numFaces(grid),
props.numPhases()));
initBlackoilStateFromDeck(Opm::UgGridHelpers::numCells(grid),
Opm::UgGridHelpers::globalCell(grid),
Opm::UgGridHelpers::numFaces(grid),
Opm::UgGridHelpers::faceCells(grid),
Opm::UgGridHelpers::beginFaceCentroids(grid),
Opm::UgGridHelpers::beginCellCentroids(grid),
Opm::UgGridHelpers::dimensions(grid),
props, *deck_, gravity_[2], *state_);
}
// Threshold pressures.
std::map<std::pair<int, int>, double> maxDp;
computeMaxDp(maxDp, *deck_, *eclipse_state_, grid_init_->grid(), *state_, props, gravity_[2]);
threshold_pressures_ = thresholdPressures(*deck_, *eclipse_state_, grid, maxDp);
std::vector<double> threshold_pressures_nnc = thresholdPressuresNNC(*eclipse_state_, geoprops_->nnc(), maxDp);
threshold_pressures_.insert(threshold_pressures_.end(), threshold_pressures_nnc.begin(), threshold_pressures_nnc.end());
// The capillary pressure is scaled in fluidprops_ to match the scaled capillary pressure in props.
if (deck_->hasKeyword("SWATINIT")) {
const int numCells = Opm::UgGridHelpers::numCells(grid);
std::vector<int> cells(numCells);
for (int c = 0; c < numCells; ++c) { cells[c] = c; }
std::vector<double> pc = state_->saturation();
props.capPress(numCells, state_->saturation().data(), cells.data(), pc.data(), nullptr);
fluidprops_->setSwatInitScaling(state_->saturation(), pc);
}
initHydroCarbonState(*state_, pu, Opm::UgGridHelpers::numCells(grid), deck_->hasKeyword("DISGAS"), deck_->hasKeyword("VAPOIL"));
}
template <class FluidSystem>
void convertSats(std::vector<double>& sat_interleaved, const std::vector< std::vector<double> >& sat, const PhaseUsage& pu)
{
assert(sat.size() == 3);
const auto nc = sat[0].size();
const auto np = sat_interleaved.size() / nc;
for (size_t c = 0; c < nc; ++c) {
if ( FluidSystem::phaseIsActive(FluidSystem::oilPhaseIdx)) {
const int opos = pu.phase_pos[BlackoilPhases::Liquid];
const std::vector<double>& sat_p = sat[ FluidSystem::oilPhaseIdx];
sat_interleaved[np*c + opos] = sat_p[c];
}
if ( FluidSystem::phaseIsActive(FluidSystem::waterPhaseIdx)) {
const int wpos = pu.phase_pos[BlackoilPhases::Aqua];
const std::vector<double>& sat_p = sat[ FluidSystem::waterPhaseIdx];
sat_interleaved[np*c + wpos] = sat_p[c];
}
if ( FluidSystem::phaseIsActive(FluidSystem::gasPhaseIdx)) {
const int gpos = pu.phase_pos[BlackoilPhases::Vapour];
const std::vector<double>& sat_p = sat[ FluidSystem::gasPhaseIdx];
sat_interleaved[np*c + gpos] = sat_p[c];
}
}
}
// Distribute the grid, properties and state.
// Writes to:
// grid_init_->grid()
// state_
// fluidprops_
// geoprops_
// material_law_manager_
// parallel_information_
void distributeData()
{
// At this point all properties and state variables are correctly initialized
// If there are more than one processors involved, we now repartition the grid
// and initilialize new properties and states for it.
if (must_distribute_) {
defunct_well_names_ =
distributeGridAndData(grid_init_->grid(), *deck_, *eclipse_state_, *schedule_,
*state_, *fluidprops_, *geoprops_,
material_law_manager_, threshold_pressures_,
parallel_information_, use_local_perm_);
}
}
// Run diagnostics.
// Writes to:
// OpmLog singleton.
void runDiagnostics()
{
if( ! output_cout_ )
{
return;
}
// Run relperm diagnostics
RelpermDiagnostics diagnostic;
diagnostic.diagnosis(*eclipse_state_, *deck_, grid_init_->grid());
}
void writeInit()
{
bool output = param_.getDefault("output", true);
bool output_ecl = param_.getDefault("output_ecl", true);
const Grid& grid = grid_init_->grid();
if( output && output_ecl && output_cout_)
{
const EclipseGrid& inputGrid = eclipse_state_->getInputGrid();
eclipse_writer_.reset(new EclipseIO(*eclipse_state_,
UgGridHelpers::createEclipseGrid( grid , inputGrid ),
*schedule_,
*summary_config_ ));
eclipse_writer_->writeInitial(geoprops_->simProps(grid), {},
geoprops_->nonCartesianConnections());
}
}
// Setup output writer.
// Writes to:
// output_writer_
void setupOutputWriter()
{
// create output writer after grid is distributed, otherwise the parallel output
// won't work correctly since we need to create a mapping from the distributed to
// the global view
output_writer_.reset(new OutputWriter(grid_init_->grid(),
param_,
*eclipse_state_,
*schedule_,
*summary_config_,
std::move(eclipse_writer_),
Opm::phaseUsageFromDeck(*deck_)));
}
// Setup linear solver.
// Writes to:
// fis_solver_
void setupLinearSolver()
{
const std::string cprSolver = "cpr";
const std::string interleavedSolver = "interleaved";
const std::string directSolver = "direct";
std::string flowDefaultSolver = interleavedSolver;
if (!param_.has("solver_approach")) {
if (eclipse_state_->getSimulationConfig().useCPR()) {
flowDefaultSolver = cprSolver;
}
}
const std::string solver_approach = param_.getDefault("solver_approach", flowDefaultSolver);
if (solver_approach == cprSolver) {
fis_solver_.reset(new NewtonIterationBlackoilCPR(param_, parallel_information_));
} else if (solver_approach == interleavedSolver) {
fis_solver_.reset(new NewtonIterationBlackoilInterleaved(param_, parallel_information_));
} else if (solver_approach == directSolver) {
fis_solver_.reset(new NewtonIterationBlackoilSimple(param_, parallel_information_));
} else {
OPM_THROW( std::runtime_error , "Internal error - solver approach " << solver_approach << " not recognized.");
}
}
// Run the simulator.
// Returns EXIT_SUCCESS if it does not throw.
int runSimulator()
{
const auto& timeMap = schedule_->getTimeMap();
auto& ioConfig = eclipse_state_->getIOConfig();
SimulatorTimer simtimer;
// initialize variables
const auto& initConfig = eclipse_state_->getInitConfig();
simtimer.init(timeMap, (size_t)initConfig.getRestartStep());
if (!ioConfig.initOnly()) {
if (output_cout_) {
std::string msg;
msg = "\n\n================ Starting main simulation loop ===============\n";
OpmLog::info(msg);
}
SimulatorReport fullReport = simulator_->run(simtimer, *state_);
if (output_cout_) {
std::ostringstream ss;
ss << "\n\n================ End of simulation ===============\n\n";
fullReport.reportFullyImplicit(ss);
OpmLog::info(ss.str());
if (param_.anyUnused()) {
// This allows a user to catch typos and misunderstandings in the
// use of simulator parameters.
std::cout << "-------------------- Unused parameters: --------------------\n";
param_.displayUsage();
std::cout << "----------------------------------------------------------------" << std::endl;
}
}
if (output_to_files_) {
std::string filename = output_dir_ + "/walltime.txt";
std::fstream tot_os(filename.c_str(), std::fstream::trunc | std::fstream::out);
fullReport.reportParam(tot_os);
}
} else {
if (output_cout_) {
std::cout << "\n\n================ Simulation turned off ===============\n" << std::flush;
}
}
return EXIT_SUCCESS;
}
// Access the most-derived class used for
// static polymorphism (CRTP).
Implementation& asImpl()
{
return static_cast<Implementation&>(*this);
}
}; // class FlowMainBase
// The FlowMain class is the basic black-oil simulator case.
template <class Grid, class Simulator>
class FlowMain : public FlowMainBase<FlowMain<Grid, Simulator>, Grid, Simulator>
{
protected:
using Base = FlowMainBase<FlowMain<Grid, Simulator>, Grid, Simulator>;
friend Base;
// Create simulator instance.
// Writes to:
// simulator_
void createSimulator()
{
// Create the simulator instance.
Base::simulator_.reset(new Simulator(Base::param_,
Base::grid_init_->grid(),
*Base::geoprops_,
*Base::fluidprops_,
Base::rock_comp_->isActive() ? Base::rock_comp_.get() : nullptr,
*Base::fis_solver_,
Base::gravity_.data(),
Base::deck_->hasKeyword("DISGAS"),
Base::deck_->hasKeyword("VAPOIL"),
Base::eclipse_state_,
Base::schedule_,
Base::summary_config_,
*Base::output_writer_,
Base::threshold_pressures_,
Base::defunct_well_names_));
}
};
namespace detail
{
boost::filesystem::path simulationCaseName( const std::string& casename ) {
namespace fs = boost::filesystem;
const auto exists = []( const fs::path& f ) -> bool {
if( !fs::exists( f ) ) return false;
if( fs::is_regular_file( f ) ) return true;
return fs::is_symlink( f )
&& fs::is_regular_file( fs::read_symlink( f ) );
};
auto simcase = fs::path( casename );
if( exists( simcase ) ) {
return simcase;
}
for( const auto& ext : { std::string("data"), std::string("DATA") } ) {
if( exists( simcase.replace_extension( ext ) ) ) {
return simcase;
}
}
throw std::invalid_argument( "Cannot find input case " + casename );
}
} // namespace detail
} // namespace Opm
#endif // OPM_FLOWMAIN_HEADER_INCLUDED

View File

@ -1,145 +0,0 @@
/*
Copyright 2016 SINTEF ICT, Applied Mathematics.
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 3 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 <http://www.gnu.org/licenses/>.
*/
#ifndef OPM_FLOWMAINSEQUENTIAL_HEADER_INCLUDED
#define OPM_FLOWMAINSEQUENTIAL_HEADER_INCLUDED
#include <opm/autodiff/FlowMain.hpp>
namespace Opm
{
// The FlowMainSequential class is for a black-oil simulator using the sequential models.
template <class Grid, class Simulator>
class FlowMainSequential : public FlowMainBase<FlowMainSequential<Grid, Simulator>, Grid, Simulator>
{
protected:
using Base = FlowMainBase<FlowMainSequential<Grid, Simulator>, Grid, Simulator>;
using Base::eclipse_state_;
using Base::schedule_;
using Base::summary_config_;
using Base::param_;
using Base::fis_solver_;
using Base::parallel_information_;
friend Base;
// ------------ Methods ------------
// Print startup message if on output rank.
void printStartupMessage()
{
if (Base::output_cout_) {
const std::string version = moduleVersionName();
std::cout << "**********************************************************************\n";
std::cout << "* *\n";
std::cout << "* This is Flow-Sequential (version " << version << ")"
<< std::string(17 - version.size(), ' ') << "*\n";
std::cout << "* *\n";
std::cout << "* Flow-Sequential is a simulator for fully implicit three-phase, *\n";
std::cout << "* black-oil flow, and is part of OPM. *\n";
std::cout << "* For more information see https://opm-project.org *\n";
std::cout << "* *\n";
std::cout << "**********************************************************************\n\n";
}
}
// Setup linear solver.
// Writes to:
// fis_solver_
// param_ (conditionally)
// The CPR solver cannot be used with the sequential model.
// Also, the interleaved solver requires the full sparsity pattern option.
void setupLinearSolver()
{
const std::string cprSolver = "cpr";
const std::string interleavedSolver = "interleaved";
const std::string directSolver = "direct";
std::string flowDefaultSolver = interleavedSolver;
if (!param_.has("solver_approach")) {
if (eclipse_state_->getSimulationConfig().useCPR()) {
flowDefaultSolver = cprSolver;
}
}
const std::string solver_approach = param_.getDefault("solver_approach", flowDefaultSolver);
if (solver_approach == cprSolver) {
OPM_THROW( std::runtime_error , "CPR solver is not ready for use with sequential simulator.");
} else if (solver_approach == interleavedSolver) {
if (!param_.has("require_full_sparsity_pattern")) {
param_.insertParameter("require_full_sparsity_pattern", "true");
}
fis_solver_.reset(new NewtonIterationBlackoilInterleaved(param_, parallel_information_));
} else if (solver_approach == directSolver) {
fis_solver_.reset(new NewtonIterationBlackoilSimple(param_, parallel_information_));
} else {
OPM_THROW( std::runtime_error , "Internal error - solver approach " << solver_approach << " not recognized.");
}
}
// Create simulator instance.
// Writes to:
// simulator_
void createSimulator()
{
// We must override the min_iter argument unless it was already supplied, to avoid requiring iteration.
if (!param_.has("min_iter")) {
param_.insertParameter("min_iter", "0");
}
// Create the simulator instance.
Base::simulator_.reset(new Simulator(Base::param_,
Base::grid_init_->grid(),
*Base::geoprops_,
*Base::fluidprops_,
Base::rock_comp_->isActive() ? Base::rock_comp_.get() : nullptr,
*Base::fis_solver_,
Base::gravity_.data(),
Base::deck_->hasKeyword("DISGAS"),
Base::deck_->hasKeyword("VAPOIL"),
Base::eclipse_state_,
Base::schedule_,
Base::summary_config_,
*Base::output_writer_,
Base::threshold_pressures_));
}
};
} // namespace Opm
#endif // OPM_FLOWMAINSEQUENTIAL_HEADER_INCLUDED

View File

@ -1,631 +0,0 @@
/*
Copyright 2013 SINTEF ICT, Applied Mathematics.
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 3 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 <http://www.gnu.org/licenses/>.
*/
#ifndef OPM_GEOPROPS_HEADER_INCLUDED
#define OPM_GEOPROPS_HEADER_INCLUDED
#include <opm/grid/UnstructuredGrid.h>
#include <opm/autodiff/GridHelpers.hpp>
#include <opm/common/ErrorMacros.hpp>
#include <opm/grid/transmissibility/TransTpfa.hpp>
#include <opm/parser/eclipse/EclipseState/EclipseState.hpp>
#include <opm/parser/eclipse/EclipseState/Grid/EclipseGrid.hpp>
#include <opm/parser/eclipse/EclipseState/Grid/GridProperty.hpp>
#include <opm/parser/eclipse/EclipseState/Grid/NNC.hpp>
#include <opm/parser/eclipse/EclipseState/Grid/TransMult.hpp>
#include <opm/grid/PinchProcessor.hpp>
#include <opm/common/utility/platform_dependent/disable_warnings.h>
#include <opm/output/data/Cells.hpp>
#include <opm/output/data/Solution.hpp>
#include <Eigen/Eigen>
#ifdef HAVE_OPM_GRID
#include <dune/common/version.hh>
#include <opm/grid/CpGrid.hpp>
#include <dune/grid/common/mcmgmapper.hh>
#endif
#include <opm/common/utility/platform_dependent/reenable_warnings.h>
#include <cstddef>
namespace Opm
{
/// Class containing static geological properties that are
/// derived from grid and petrophysical properties:
/// - pore volume
/// - transmissibilities
/// - gravity potentials
class DerivedGeology
{
public:
typedef Eigen::ArrayXd Vector;
/// Construct contained derived geological properties
/// from grid and property information.
template <class Props, class Grid>
DerivedGeology(const Grid& grid,
const Props& props ,
const EclipseState& eclState,
const bool use_local_perm,
const double* grav = 0
)
: pvol_ (Opm::AutoDiffGrid::numCells(grid))
, trans_(Opm::AutoDiffGrid::numFaces(grid))
, gpot_ (Vector::Zero(Opm::AutoDiffGrid::cell2Faces(grid).noEntries(), 1))
, z_(Opm::AutoDiffGrid::numCells(grid))
, use_local_perm_(use_local_perm)
{
update(grid, props, eclState, grav);
}
/// compute the all geological properties at a given report step
template <class Props, class Grid>
void update(const Grid& grid,
const Props& props ,
const EclipseState& eclState,
const double* grav)
{
int numCells = AutoDiffGrid::numCells(grid);
int numFaces = AutoDiffGrid::numFaces(grid);
const int *cartDims = AutoDiffGrid::cartDims(grid);
int numCartesianCells =
cartDims[0]
* cartDims[1]
* cartDims[2];
// get the pore volume multipliers from the EclipseState
std::vector<double> multpv(numCartesianCells, 1.0);
const auto& eclProps = eclState.get3DProperties();
if (eclProps.hasDeckDoubleGridProperty("MULTPV")) {
multpv = eclProps.getDoubleGridProperty("MULTPV").getData();
}
// get the net-to-gross cell thickness from the EclipseState
std::vector<double> ntg(numCartesianCells, 1.0);
if (eclProps.hasDeckDoubleGridProperty("NTG")) {
ntg = eclProps.getDoubleGridProperty("NTG").getData();
}
// Get grid from parser.
const auto& eclgrid = eclState.getInputGrid();
// update the pore volume of all active cells in the grid
computePoreVolume_(grid, eclState);
// Non-neighbour connections.
nnc_ = eclState.getInputNNC();
// Transmissibility
Vector htrans(AutoDiffGrid::numCellFaces(grid));
Grid* ug = const_cast<Grid*>(& grid);
if (! use_local_perm_) {
tpfa_htrans_compute(ug, props.permeability(), htrans.data());
}
else {
tpfa_loc_trans_compute_(grid,eclgrid, props.permeability(),htrans);
}
// Use volume weighted arithmetic average of the NTG values for
// the cells effected by the current OPM cpgrid process algorithm
// for MINPV. Note that the change does not effect the pore volume calculations
// as the pore volume is currently defaulted to be comparable to ECLIPSE, but
// only the transmissibility calculations.
bool opmfil = eclgrid.getMinpvMode() == MinpvMode::ModeEnum::OpmFIL;
// opmfil is hardcoded to be true. i.e the volume weighting is always used
opmfil = true;
if (opmfil) {
minPvFillProps_(grid, eclState, ntg);
}
std::vector<double> mult;
multiplyHalfIntersections_(grid, eclState, ntg, htrans, mult);
if (!opmfil && eclgrid.isPinchActive()) {
// opmfil is hardcoded to be true. i.e the pinch processor is never used
pinchProcess_(grid, eclState, htrans, numCells);
}
// combine the half-face transmissibilites into the final face
// transmissibilites.
tpfa_trans_compute(ug, htrans.data(), trans_.data());
// multiply the face transmissibilities with their appropriate
// transmissibility multipliers
for (int faceIdx = 0; faceIdx < numFaces; faceIdx++) {
trans_[faceIdx] *= mult[faceIdx];
}
// Create the set of noncartesian connections.
noncartesian_ = nnc_;
exportNncStructure(grid);
// Compute z coordinates
for (int c = 0; c<numCells; ++c){
z_[c] = Opm::UgGridHelpers::cellCenterDepth(grid, c);
}
// Gravity potential
std::fill(gravity_, gravity_ + 3, 0.0);
if (grav != 0) {
const typename Vector::Index nd = AutoDiffGrid::dimensions(grid);
typedef typename AutoDiffGrid::ADCell2FacesTraits<Grid>::Type Cell2Faces;
Cell2Faces c2f=AutoDiffGrid::cell2Faces(grid);
std::size_t i = 0;
for (typename Vector::Index c = 0; c < numCells; ++c) {
const double* const cc = AutoDiffGrid::cellCentroid(grid, c);
typename Cell2Faces::row_type faces=c2f[c];
typedef typename Cell2Faces::row_type::iterator Iter;
for (Iter f=faces.begin(), end=faces.end(); f!=end; ++f, ++i) {
auto fc = AutoDiffGrid::faceCentroid(grid, *f);
for (typename Vector::Index d = 0; d < nd; ++d) {
gpot_[i] += grav[d] * (fc[d] - cc[d]);
}
}
}
std::copy(grav, grav + nd, gravity_);
}
}
const Vector& poreVolume() const { return pvol_ ;}
const Vector& transmissibility() const { return trans_ ;}
const Vector& gravityPotential() const { return gpot_ ;}
const Vector& z() const { return z_ ;}
const double* gravity() const { return gravity_;}
Vector& poreVolume() { return pvol_ ;}
Vector& transmissibility() { return trans_ ;}
const NNC& nnc() const { return nnc_;}
const NNC& nonCartesianConnections() const { return noncartesian_;}
/// Most properties are loaded by the parser, and managed by
/// the EclipseState class in the opm-parser. However - some
/// properties must be calculated by the simulator, the
/// purpose of this method is to calculate these properties in
/// a form suitable for output. Currently the transmissibility
/// is the only property calculated this way:
///
/// The grid properties TRANX, TRANY and TRANZ are initialized
/// in a form suitable for writing to the INIT file. These
/// properties should be interpreted with a
/// 'the-grid-is-nearly-cartesian' mindset:
///
/// TRANX[i,j,k] = T on face between cells (i,j,k) and (i+1,j ,k )
/// TRANY[i,j,k] = T on face between cells (i,j,k) and (i ,j+1,k )
/// TRANZ[i,j,k] = T on face between cells (i,j,k) and (i ,j ,k+1)
///
/// If the grid structure has no resemblance to a cartesian
/// grid the whole TRAN keyword is quite meaningless.
template <class Grid>
data::Solution simProps( const Grid& grid ) const {
using namespace UgGridHelpers;
const int* dims = cartDims( grid );
const int globalSize = dims[0] * dims[1] * dims[2];
const auto& trans = this->transmissibility( );
data::CellData tranx = {UnitSystem::measure::transmissibility, std::vector<double>( globalSize ), data::TargetType::INIT};
data::CellData trany = {UnitSystem::measure::transmissibility, std::vector<double>( globalSize ), data::TargetType::INIT};
data::CellData tranz = {UnitSystem::measure::transmissibility, std::vector<double>( globalSize ), data::TargetType::INIT};
size_t num_faces = numFaces(grid);
auto fc = faceCells(grid);
for (size_t i = 0; i < num_faces; ++i) {
auto c1 = std::min( fc(i,0) , fc(i,1));
auto c2 = std::max( fc(i,0) , fc(i,1));
if (c1 == -1 || c2 == -1)
continue;
c1 = globalCell(grid) ? globalCell(grid)[c1] : c1;
c2 = globalCell(grid) ? globalCell(grid)[c2] : c2;
if ((c2 - c1) == 1) {
tranx.data[c1] = trans[i];
}
if ((c2 - c1) == dims[0]) {
trany.data[c1] = trans[i];
}
if ((c2 - c1) == dims[0]*dims[1]) {
tranz.data[c1] = trans[i];
}
}
return { {"TRANX" , tranx},
{"TRANY" , trany} ,
{"TRANZ" , tranz } };
}
private:
template <class Grid>
void multiplyHalfIntersections_(const Grid &grid,
const EclipseState& eclState,
const std::vector<double> &ntg,
Vector &halfIntersectTransmissibility,
std::vector<double> &intersectionTransMult);
template <class Grid>
void tpfa_loc_trans_compute_(const Grid &grid,
const EclipseGrid& eclGrid,
const double* perm,
Vector &hTrans);
template <class Grid>
void minPvFillProps_(const Grid &grid,
const EclipseState& eclState,
std::vector<double> &ntg);
template <class GridType>
void computePoreVolume_(const GridType &grid,
const EclipseState& eclState)
{
int numCells = Opm::AutoDiffGrid::numCells(grid);
const int* globalCell = Opm::UgGridHelpers::globalCell(grid);
const auto& eclGrid = eclState.getInputGrid();
const int nx = eclGrid.getNX();
const int ny = eclGrid.getNY();
// the "raw" pore volume.
const std::vector<double>& porvData =
eclState.get3DProperties().getDoubleGridProperty("PORV").getData();
pvol_.resize(numCells);
// the "activation number" grid property
const std::vector<int>& actnumData =
eclState.get3DProperties().getIntGridProperty("ACTNUM").getData();
for (int cellIdx = 0; cellIdx < numCells; ++cellIdx) {
const int cellCartIdx = globalCell[cellIdx];
double cellPoreVolume = porvData[cellCartIdx];
if (eclGrid.getMinpvMode() == MinpvMode::ModeEnum::OpmFIL) {
// Sum the pore volumes of the cells above which have been deactivated
// because their volume less is less than the MINPV threshold
for (int aboveCellCartIdx = cellCartIdx - nx*ny;
aboveCellCartIdx >= 0;
aboveCellCartIdx -= nx*ny)
{
if (porvData[aboveCellCartIdx] >= eclGrid.getMinpvVector()[aboveCellCartIdx]) {
// stop if we encounter a cell which has a pore volume which is
// at least as large as the minimum one
break;
}
const double aboveCellVolume = eclGrid.getCellVolume(aboveCellCartIdx);
if (actnumData[aboveCellCartIdx] == 0 && aboveCellVolume > 1e-6) {
// stop at explicitly disabled cells, but only if their volume is
// greater than 10^-6 m^3
break;
}
cellPoreVolume += porvData[aboveCellCartIdx];
}
}
pvol_[cellIdx] = cellPoreVolume;
}
}
template <class Grid>
void pinchProcess_(const Grid& grid,
const Opm::EclipseState& eclState,
const Vector& htrans,
int numCells);
/// checks cartesian adjacency of global indices g1 and g2
template <typename Grid>
bool cartesianAdjacent(const Grid& grid, int g1, int g2) {
int diff = std::abs(g1 - g2);
const int * dimens = UgGridHelpers::cartDims(grid);
if (diff == 1)
return true;
if (diff == dimens[0])
return true;
if (diff == dimens[0] * dimens[1])
return true;
return false;
}
/// Write the NNC structure of the given grid to NNC.
///
/// Write cell adjacencies beyond Cartesian neighborhoods to NNC.
///
/// The trans vector is indexed by face number as it is in grid.
template <typename Grid>
void exportNncStructure(const Grid& grid) {
// we use numFaces, numCells, cell2Faces, globalCell from UgGridHelpers
using namespace UgGridHelpers;
size_t num_faces = numFaces(grid);
auto fc = faceCells(grid);
for (size_t i = 0; i < num_faces; ++i) {
auto c1 = fc(i, 0);
auto c2 = fc(i, 1);
if (c1 == -1 || c2 == -1)
continue; // face on grid boundary
// translate from active cell idx (ac1,ac2) to global cell idx
c1 = globalCell(grid) ? globalCell(grid)[c1] : c1;
c2 = globalCell(grid) ? globalCell(grid)[c2] : c2;
if (!cartesianAdjacent(grid, c1, c2)) {
// suppose c1,c2 is specified in ECLIPSE input
// we here overwrite its trans by grid's
noncartesian_.addNNC(c1, c2, trans_[i]);
}
}
}
Vector pvol_ ;
Vector trans_;
Vector gpot_ ;
Vector z_;
double gravity_[3]; // Size 3 even if grid is 2-dim.
bool use_local_perm_;
// Non-neighboring connections
NNC nnc_;
// Non-cartesian connections
NNC noncartesian_;
};
template <class GridType>
inline void DerivedGeology::minPvFillProps_(const GridType &grid,
const EclipseState& eclState,
std::vector<double> &ntg)
{
int numCells = Opm::AutoDiffGrid::numCells(grid);
const int* global_cell = Opm::UgGridHelpers::globalCell(grid);
const int* cartdims = Opm::UgGridHelpers::cartDims(grid);
const auto& eclgrid = eclState.getInputGrid();
const auto& porv = eclState.get3DProperties().getDoubleGridProperty("PORV").getData();
const auto& actnum = eclState.get3DProperties().getIntGridProperty("ACTNUM").getData();
for (int cellIdx = 0; cellIdx < numCells; ++cellIdx) {
const int nx = cartdims[0];
const int ny = cartdims[1];
const int cartesianCellIdx = global_cell[cellIdx];
const double cellVolume = eclgrid.getCellVolume(cartesianCellIdx);
ntg[cartesianCellIdx] *= cellVolume;
double totalCellVolume = cellVolume;
// Average properties as long as there exist cells above
// that has pore volume less than the MINPV threshold
int cartesianCellIdxAbove = cartesianCellIdx - nx*ny;
while ( cartesianCellIdxAbove >= 0 &&
actnum[cartesianCellIdxAbove] > 0 &&
porv[cartesianCellIdxAbove] < eclgrid.getMinpvVector()[cartesianCellIdxAbove] ) {
// Volume weighted arithmetic average of NTG
const double cellAboveVolume = eclgrid.getCellVolume(cartesianCellIdxAbove);
totalCellVolume += cellAboveVolume;
ntg[cartesianCellIdx] += ntg[cartesianCellIdxAbove]*cellAboveVolume;
cartesianCellIdxAbove -= nx*ny;
}
ntg[cartesianCellIdx] /= totalCellVolume;
}
}
template <class GridType>
inline void DerivedGeology::pinchProcess_(const GridType& grid,
const Opm::EclipseState& eclState,
const Vector& htrans,
int numCells)
{
// this is not used
OPM_THROW(std::runtime_error, "This method is not implemented");
}
template <class GridType>
inline void DerivedGeology::multiplyHalfIntersections_(const GridType &grid,
const EclipseState& eclState,
const std::vector<double> &ntg,
Vector &halfIntersectTransmissibility,
std::vector<double> &intersectionTransMult)
{
int numCells = Opm::AutoDiffGrid::numCells(grid);
int numIntersections = Opm::AutoDiffGrid::numFaces(grid);
intersectionTransMult.resize(numIntersections);
std::fill(intersectionTransMult.begin(), intersectionTransMult.end(), 1.0);
const TransMult& multipliers = eclState.getTransMult();
auto cell2Faces = Opm::UgGridHelpers::cell2Faces(grid);
auto faceCells = Opm::AutoDiffGrid::faceCells(grid);
const int* global_cell = Opm::UgGridHelpers::globalCell(grid);
int cellFaceIdx = 0;
for (int cellIdx = 0; cellIdx < numCells; ++cellIdx) {
// loop over all logically-Cartesian faces of the current cell
auto cellFacesRange = cell2Faces[cellIdx];
for(auto cellFaceIter = cellFacesRange.begin(), cellFaceEnd = cellFacesRange.end();
cellFaceIter != cellFaceEnd; ++cellFaceIter, ++cellFaceIdx)
{
// the index of the current cell in arrays for the logically-Cartesian grid
int cartesianCellIdx = global_cell[cellIdx];
// The index of the face in the compressed grid
int faceIdx = *cellFaceIter;
// the logically-Cartesian direction of the face
int faceTag = Opm::UgGridHelpers::faceTag(grid, cellFaceIter);
// Translate the C face tag into the enum used by opm-parser's TransMult class
Opm::FaceDir::DirEnum faceDirection;
if (faceTag == 0) // left
faceDirection = Opm::FaceDir::XMinus;
else if (faceTag == 1) // right
faceDirection = Opm::FaceDir::XPlus;
else if (faceTag == 2) // back
faceDirection = Opm::FaceDir::YMinus;
else if (faceTag == 3) // front
faceDirection = Opm::FaceDir::YPlus;
else if (faceTag == 4) // bottom
faceDirection = Opm::FaceDir::ZMinus;
else if (faceTag == 5) // top
faceDirection = Opm::FaceDir::ZPlus;
else
OPM_THROW(std::logic_error, "Unhandled face direction: " << faceTag);
// Account for NTG in horizontal one-sided transmissibilities
switch (faceDirection) {
case Opm::FaceDir::XMinus:
case Opm::FaceDir::XPlus:
case Opm::FaceDir::YMinus:
case Opm::FaceDir::YPlus:
halfIntersectTransmissibility[cellFaceIdx] *= ntg[cartesianCellIdx];
break;
default:
// do nothing for the top and bottom faces
break;
}
// Multiplier contribution on this face for MULT[XYZ] logical cartesian multipliers
intersectionTransMult[faceIdx] *=
multipliers.getMultiplier(cartesianCellIdx, faceDirection);
// Multiplier contribution on this fase for region multipliers
const int cellIdxInside = faceCells(faceIdx, 0);
const int cellIdxOutside = faceCells(faceIdx, 1);
// Do not apply region multipliers in the case of boundary connections
if (cellIdxInside < 0 || cellIdxOutside < 0) {
continue;
}
const int cartesianCellIdxInside = global_cell[cellIdxInside];
const int cartesianCellIdxOutside = global_cell[cellIdxOutside];
// Only apply the region multipliers from the inside
if (cartesianCellIdx == cartesianCellIdxInside) {
intersectionTransMult[faceIdx] *= multipliers.getRegionMultiplier(cartesianCellIdxInside,cartesianCellIdxOutside,faceDirection);
}
}
}
}
template <class GridType>
inline void DerivedGeology::tpfa_loc_trans_compute_(const GridType& grid,
const EclipseGrid& eclGrid,
const double* perm,
Vector& hTrans){
// Using Local coordinate system for the transmissibility calculations
// hTrans(cellFaceIdx) = K(cellNo,j) * sum( C(:,i) .* N(:,j), 2) / sum(C.*C, 2)
// where K is a diagonal permeability tensor, C is the distance from cell centroid
// to face centroid and N is the normal vector pointing outwards with norm equal to the face area.
// Off-diagonal permeability values are ignored without warning
int numCells = AutoDiffGrid::numCells(grid);
int cellFaceIdx = 0;
auto cell2Faces = Opm::UgGridHelpers::cell2Faces(grid);
auto faceCells = Opm::UgGridHelpers::faceCells(grid);
for (int cellIdx = 0; cellIdx < numCells; ++cellIdx) {
// loop over all logically-Cartesian faces of the current cell
auto cellFacesRange = cell2Faces[cellIdx];
for(auto cellFaceIter = cellFacesRange.begin(), cellFaceEnd = cellFacesRange.end();
cellFaceIter != cellFaceEnd; ++cellFaceIter, ++cellFaceIdx)
{
// The index of the face in the compressed grid
const int faceIdx = *cellFaceIter;
// the logically-Cartesian direction of the face
const int faceTag = Opm::UgGridHelpers::faceTag(grid, cellFaceIter);
// d = 0: XPERM d = 4: YPERM d = 8: ZPERM ignores off-diagonal permeability values.
const int d = std::floor(faceTag/2) * 4;
// compute the half transmissibility
double dist = 0.0;
double cn = 0.0;
double sgn = 2.0 * (faceCells(faceIdx, 0) == cellIdx) - 1;
const int dim = Opm::UgGridHelpers::dimensions(grid);
int cartesianCellIdx = AutoDiffGrid::globalCell(grid)[cellIdx];
auto cellCenter = eclGrid.getCellCenter(cartesianCellIdx);
const auto& faceCenter = Opm::UgGridHelpers::faceCenterEcl(grid, cellIdx, faceTag);
const auto& faceAreaNormalEcl = Opm::UgGridHelpers::faceAreaNormalEcl(grid, faceIdx);
for (int indx = 0; indx < dim; ++indx) {
const double Ci = faceCenter[indx] - cellCenter[indx];
dist += Ci*Ci;
cn += sgn * Ci * faceAreaNormalEcl[ indx ];
}
if (cn < 0){
switch (d) {
case 0:
OPM_MESSAGE("Warning: negative X-transmissibility value in cell: " << cellIdx << " replace by absolute value") ;
break;
case 4:
OPM_MESSAGE("Warning: negative Y-transmissibility value in cell: " << cellIdx << " replace by absolute value") ;
break;
case 8:
OPM_MESSAGE("Warning: negative Z-transmissibility value in cell: " << cellIdx << " replace by absolute value") ;
break;
default:
OPM_THROW(std::logic_error, "Inconsistency in the faceTag in cell: " << cellIdx);
}
cn = -cn;
}
hTrans[cellFaceIdx] = perm[cellIdx*dim*dim + d] * cn / dist;
}
}
}
}
#endif // OPM_GEOPROPS_HEADER_INCLUDED

View File

@ -1,135 +0,0 @@
/*
Copyright 2014, 2015 Dr. Markus Blatt - HPC-Simulation-Software & Services.
Copyright 2014 Statoil AS
Copyright 2015 NTNU
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 3 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 <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <opm/autodiff/GridHelpers.hpp>
namespace Opm
{
namespace AutoDiffGrid
{
Eigen::Array<int, Eigen::Dynamic, 2, Eigen::RowMajor>
faceCellsToEigen(const UnstructuredGrid& grid)
{
typedef Eigen::Array<int, Eigen::Dynamic, 2, Eigen::RowMajor> TwoColInt;
return Eigen::Map<TwoColInt>(grid.face_cells, grid.number_of_faces, 2);
}
Eigen::Array<double, Eigen::Dynamic, 1>
cellCentroidsZToEigen(const UnstructuredGrid& grid)
{
return Eigen::Map<Eigen::Array<double, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor> >
(grid.cell_centroids, grid.number_of_cells, grid.dimensions).rightCols<1>();
}
/*
SparseTableView cell2Faces(const UnstructuredGrid& grid)
{
return SparseTableView(grid.cell_faces, grid.cell_facepos, numCells(grid));
}
*/
void extractInternalFaces(const UnstructuredGrid& grid,
Eigen::Array<int, Eigen::Dynamic, 1>& internal_faces,
Eigen::Array<int, Eigen::Dynamic, 2, Eigen::RowMajor>& nbi)
{
typedef Eigen::Array<bool, Eigen::Dynamic, 1> OneColBool;
typedef Eigen::Array<int, Eigen::Dynamic, 2, Eigen::RowMajor> TwoColInt;
typedef Eigen::Array<bool, Eigen::Dynamic, 2, Eigen::RowMajor> TwoColBool;
TwoColInt nb = faceCellsToEigen(grid);
// std::cout << "nb = \n" << nb << std::endl;
// Extracts the internal faces of the grid.
// These are stored in internal_faces.
TwoColBool nbib = nb >= 0;
OneColBool ifaces = nbib.rowwise().all();
const int num_internal = ifaces.cast<int>().sum();
// std::cout << num_internal << " internal faces." << std::endl;
nbi.resize(num_internal, 2);
internal_faces.resize(num_internal);
int fi = 0;
int nf = numFaces(grid);
for (int f = 0; f < nf; ++f) {
if (ifaces[f]) {
internal_faces[fi] = f;
nbi.row(fi) = nb.row(f);
++fi;
}
}
}
} // end namespace AutoDiffGrid
#ifdef HAVE_OPM_GRID
namespace AutoDiffGrid
{
ADFaceCellTraits<Dune::CpGrid>::Type
faceCellsToEigen(const Dune::CpGrid& grid)
{
return Dune::cpgrid::FaceCellsContainerProxy(&grid);
}
Eigen::Array<double, Eigen::Dynamic, 1>
cellCentroidsZToEigen(const Dune::CpGrid& grid)
{
// Create an Eigen array of appropriate size
int rows=numCells(grid);
Eigen::Array<double, Eigen::Dynamic, 1> array(rows);
// Fill it with the z coordinate of the cell centroids.
for (int i=0; i<rows; ++i)
array[i]=Opm::UgGridHelpers::cellCentroid(grid, i)[2];
return array;
}
void extractInternalFaces(const Dune::CpGrid& grid,
Eigen::Array<int, Eigen::Dynamic, 1>& internal_faces,
Eigen::Array<int, Eigen::Dynamic, 2, Eigen::RowMajor>& nbi)
{
// Extracts the internal faces of the grid.
// These are stored in internal_faces.
int nf=numFaces(grid);
int num_internal=0;
for(int f=0; f<nf; ++f)
{
if(grid.faceCell(f, 0)<0 || grid.faceCell(f, 1)<0)
continue;
++num_internal;
}
// std::cout << num_internal << " internal faces." << std::endl;
nbi.resize(num_internal, 2);
internal_faces.resize(num_internal);
int fi = 0;
for (int f = 0; f < nf; ++f) {
if(grid.faceCell(f, 0)>=0 && grid.faceCell(f, 1)>=0) {
internal_faces[fi] = f;
nbi(fi,0) = grid.faceCell(f, 0);
nbi(fi,1) = grid.faceCell(f, 1);
++fi;
}
}
}
} // end namespace AutoDiffGrid
#endif // HAVE_OPM_GRID
} // end namespace Opm

View File

@ -1,164 +0,0 @@
/*
Copyright 2014, 2015 Dr. Markus Blatt - HPC-Simulation-Software & Services.
Copyright 2014 Statoil AS
Copyright 2015 NTNU
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 3 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 <http://www.gnu.org/licenses/>.
*/
#ifndef OPM_AUTODIFF_GRIDHELPERS_HEADER_INCLUDED
#define OPM_AUTODIFF_GRIDHELPERS_HEADER_INCLUDED
#include <functional>
#include <opm/grid/UnstructuredGrid.h>
#include <opm/grid/GridHelpers.hpp>
#include <opm/common/utility/platform_dependent/disable_warnings.h>
#include <Eigen/Eigen>
#include <Eigen/Sparse>
#include <boost/range/iterator_range.hpp>
#ifdef HAVE_OPM_GRID
#include <opm/grid/CpGrid.hpp>
#include <opm/grid/cpgrid/GridHelpers.hpp>
#include <opm/grid/polyhedralgrid.hh>
#endif
#include <opm/common/utility/platform_dependent/reenable_warnings.h>
namespace Opm
{
namespace AutoDiffGrid
{
/// \brief Mapps a grid type to the corresponding face to cell mapping.
///
/// The value of the mapping is provided by the type Type.
template<class T>
struct ADFaceCellTraits
{
};
/// \brief Get the z coordinates of the cell centroids of a grid.
Eigen::Array<double, Eigen::Dynamic, 1>
cellCentroidsZToEigen(const UnstructuredGrid& grid);
/// \brief Mapping of the grid type to the type of the cell to faces mapping.
template<class T>
struct ADCell2FacesTraits
: public Opm::UgGridHelpers::Cell2FacesTraits<T>
{
};
/// \brief extracts the internal faces of a grid.
/// \param[in] The grid whose internal faces we query.
/// \param[out] internal_faces The internal faces.
/// \param[out] nbi
void extractInternalFaces(const UnstructuredGrid& grid,
Eigen::Array<int, Eigen::Dynamic, 1>& internal_faces,
Eigen::Array<int, Eigen::Dynamic, 2, Eigen::RowMajor>& nbi);
} // end namespace AutoDiffGrid
} // end namespace Opm
#ifdef HAVE_OPM_GRID
namespace Opm
{
namespace AutoDiffGrid
{
/// \brief Get the z coordinates of the cell centroids of a grid.
/// \return The z coordinates of the cell centroids in an Eigen array
Eigen::Array<double, Eigen::Dynamic, 1>
cellCentroidsZToEigen(const Dune::CpGrid& grid);
template<>
struct ADCell2FacesTraits<Dune::CpGrid>
{
typedef Dune::cpgrid::Cell2FacesContainer Type;
};
/// \brief extracts the internal faces of a grid.
/// \param[in] The grid whose internal faces we query.
/// \param[out] internal_faces The internal faces.
/// \param[out] nbi
void extractInternalFaces(const Dune::CpGrid& grid,
Eigen::Array<int, Eigen::Dynamic, 1>& internal_faces,
Eigen::Array<int, Eigen::Dynamic, 2, Eigen::RowMajor>& nbi);
template<>
struct ADFaceCellTraits<Dune::CpGrid>
: public Opm::UgGridHelpers::FaceCellTraits<Dune::CpGrid>
{};
/// \brief Get the face to cell mapping of a grid.
ADFaceCellTraits<Dune::CpGrid>::Type
faceCellsToEigen(const Dune::CpGrid& grid);
} // end namespace AutoDiffGrid
} //end namespace OPM
#endif
namespace Opm
{
namespace AutoDiffGrid
{
using Opm::UgGridHelpers::SparseTableView;
using Opm::UgGridHelpers::numCells;
using Opm::UgGridHelpers::faceCells;
using Opm::UgGridHelpers::numFaces;
using Opm::UgGridHelpers::dimensions;
using Opm::UgGridHelpers::cartDims;
using Opm::UgGridHelpers::globalCell;
using Opm::UgGridHelpers::cell2Faces;
using Opm::UgGridHelpers::increment;
using Opm::UgGridHelpers::getCoordinate;
using Opm::UgGridHelpers::numCellFaces;
using Opm::UgGridHelpers::beginFaceCentroids;
using Opm::UgGridHelpers::beginCellCentroids;
using Opm::UgGridHelpers::cellCentroid;
using Opm::UgGridHelpers::faceCentroid;
using Opm::UgGridHelpers::beginCellVolumes;
using Opm::UgGridHelpers::cellVolume;
template<>
struct ADFaceCellTraits<UnstructuredGrid>
{
typedef Eigen::Array<int, Eigen::Dynamic, 2, Eigen::RowMajor> Type;
};
#ifdef HAVE_OPM_GRID
// specialization for PolyhedralGrid as a fallback to UnstructuredGrid
template< int dim, int dimworld >
struct ADFaceCellTraits< Dune::PolyhedralGrid< dim, dimworld > >
: public ADFaceCellTraits<UnstructuredGrid>
{
};
#endif
/// \brief Get the face to cell mapping of a grid.
ADFaceCellTraits<UnstructuredGrid>::Type
faceCellsToEigen(const UnstructuredGrid& grid);
} // end namespace AutoDiffGrid
} //end namespace OPM
#endif

View File

@ -1,378 +0,0 @@
/*
Copyright 2016 IRIS AS
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 3 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 <http://www.gnu.org/licenses/>.
*/
#ifndef OPM_ISTLSOLVER_HEADER_INCLUDED
#define OPM_ISTLSOLVER_HEADER_INCLUDED
#include <opm/autodiff/BlackoilAmg.hpp>
#include <opm/autodiff/CPRPreconditioner.hpp>
#include <opm/autodiff/NewtonIterationBlackoilInterleaved.hpp>
#include <opm/autodiff/NewtonIterationUtilities.hpp>
#include <opm/autodiff/ParallelRestrictedAdditiveSchwarz.hpp>
#include <opm/autodiff/ParallelOverlappingILU0.hpp>
#include <opm/autodiff/AutoDiffHelpers.hpp>
#include <opm/autodiff/MatrixBlock.hpp>
#include <opm/autodiff/MPIUtilities.hpp>
#include <opm/common/Exceptions.hpp>
#include <opm/core/linalg/ParallelIstlInformation.hpp>
#include <opm/common/utility/platform_dependent/disable_warnings.h>
#include <dune/istl/scalarproducts.hh>
#include <dune/istl/operators.hh>
#include <dune/istl/preconditioners.hh>
#include <dune/istl/solvers.hh>
#include <dune/istl/owneroverlapcopy.hh>
#include <dune/istl/paamg/amg.hh>
#include <opm/common/utility/platform_dependent/reenable_warnings.h>
namespace Opm
{
/// This class solves the fully implicit black-oil system by
/// solving the reduced system (after eliminating well variables)
/// as a block-structured matrix (one block for all cell variables) for a fixed
/// number of cell variables np .
/// \tparam MatrixBlockType The type of the matrix block used.
/// \tparam VectorBlockType The type of the vector block used.
/// \tparam pressureIndex The index of the pressure component in the vector
/// vector block. It is used to guide the AMG coarsening.
/// Default is zero.
template < class MatrixBlockType, class VectorBlockType, int pressureIndex=0 >
class ISTLSolver : public NewtonIterationBlackoilInterface
{
typedef typename MatrixBlockType :: field_type Scalar;
typedef Dune::BCRSMatrix <MatrixBlockType> Matrix;
typedef Dune::BlockVector<VectorBlockType> Vector;
public:
typedef Dune::AssembledLinearOperator< Matrix, Vector, Vector > AssembledLinearOperatorType;
typedef NewtonIterationBlackoilInterface :: SolutionVector SolutionVector;
/// Construct a system solver.
/// \param[in] param parameters controlling the behaviour of the linear solvers
/// \param[in] parallelInformation In the case of a parallel run
/// with dune-istl the information about the parallelization.
ISTLSolver(const NewtonIterationBlackoilInterleavedParameters& param,
const boost::any& parallelInformation_arg=boost::any())
: iterations_( 0 ),
parallelInformation_(parallelInformation_arg),
isIORank_(isIORank(parallelInformation_arg)),
parameters_( param )
{
}
/// Construct a system solver.
/// \param[in] param ParameterGroup controlling the behaviour of the linear solvers
/// \param[in] parallelInformation In the case of a parallel run
/// with dune-istl the information about the parallelization.
ISTLSolver(const ParameterGroup& param,
const boost::any& parallelInformation_arg=boost::any())
: iterations_( 0 ),
parallelInformation_(parallelInformation_arg),
isIORank_(isIORank(parallelInformation_arg)),
parameters_( param )
{
}
// dummy method that is not implemented for this class
SolutionVector computeNewtonIncrement(const LinearisedBlackoilResidual&) const
{
OPM_THROW(std::logic_error,"This method is not implemented");
return SolutionVector();
}
/// Solve the system of linear equations Ax = b, with A being the
/// combined derivative matrix of the residual and b
/// being the residual itself.
/// \param[in] residual residual object containing A and b.
/// \return the solution x
/// \copydoc NewtonIterationBlackoilInterface::iterations
int iterations () const { return iterations_; }
/// \copydoc NewtonIterationBlackoilInterface::parallelInformation
const boost::any& parallelInformation() const { return parallelInformation_; }
public:
/// \brief construct the CPR preconditioner and the solver.
/// \tparam P The type of the parallel information.
/// \param parallelInformation the information about the parallelization.
#if DUNE_VERSION_NEWER(DUNE_ISTL, 2, 6)
template<Dune::SolverCategory::Category category=Dune::SolverCategory::sequential,
class LinearOperator, class POrComm>
#else
template<int category=Dune::SolverCategory::sequential, class LinearOperator, class POrComm>
#endif
void constructPreconditionerAndSolve(LinearOperator& linearOperator,
Vector& x, Vector& istlb,
const POrComm& parallelInformation_arg,
Dune::InverseOperatorResult& result) const
{
// Construct scalar product.
#if DUNE_VERSION_NEWER(DUNE_ISTL, 2, 6)
auto sp = Dune::createScalarProduct<Vector,POrComm>(parallelInformation_arg, category);
#else
typedef Dune::ScalarProductChooser<Vector, POrComm, category> ScalarProductChooser;
typedef std::unique_ptr<typename ScalarProductChooser::ScalarProduct> SPPointer;
SPPointer sp(ScalarProductChooser::construct(parallelInformation_arg));
#endif
// Communicate if parallel.
parallelInformation_arg.copyOwnerToAll(istlb, istlb);
#if FLOW_SUPPORT_AMG // activate AMG if either flow_ebos is used or UMFPack is not available
if( parameters_.linear_solver_use_amg_ || parameters_.use_cpr_)
{
typedef ISTLUtility::CPRSelector< Matrix, Vector, Vector, POrComm> CPRSelectorType;
typedef typename CPRSelectorType::Operator MatrixOperator;
std::unique_ptr< MatrixOperator > opA;
if( ! std::is_same< LinearOperator, MatrixOperator > :: value )
{
// create new operator in case linear operator and matrix operator differ
opA.reset( CPRSelectorType::makeOperator( linearOperator.getmat(), parallelInformation_arg ) );
}
const double relax = parameters_.ilu_relaxation_;
const MILU_VARIANT ilu_milu = parameters_.ilu_milu_;
if ( parameters_.use_cpr_ )
{
// We should never end up here as this code is
// only part of flow_legacy and if use_cpr_ is
// true for flow_legacy then solver_approach=cpr
// was specified and NewtonIterationBlackoilCPR
// is used as a solve and not ISTLSolver.
OPM_THROW(std::logic_error,
"This code path should bever be exectuded for parameters_.use_cpr_="
<<parameters_.use_cpr_<<" in flow_legacy.");
}
else
{
typedef typename CPRSelectorType::AMG AMG;
std::unique_ptr< AMG > amg;
// Construct preconditioner.
constructAMGPrecond( linearOperator, parallelInformation_arg, amg, opA, relax, ilu_milu );
// Solve.
solve(linearOperator, x, istlb, *sp, *amg, result);
}
}
else
#endif
{
// Construct preconditioner.
auto precond = constructPrecond(linearOperator, parallelInformation_arg);
// Solve.
solve(linearOperator, x, istlb, *sp, *precond, result);
}
}
// 3x3 matrix block inversion was unstable at least 2.3 until and including
// 2.5.0. There may still be some issue with the 4x4 matrix block inversion
// we therefore still use the block inversion in OPM
typedef ParallelOverlappingILU0<Dune::BCRSMatrix<Dune::MatrixBlock<typename Matrix::field_type,
Matrix::block_type::rows,
Matrix::block_type::cols> >,
Vector, Vector> SeqPreconditioner;
template <class Operator>
std::unique_ptr<SeqPreconditioner> constructPrecond(Operator& opA, const Dune::Amg::SequentialInformation&) const
{
const double relax = parameters_.ilu_relaxation_;
const int ilu_fillin = parameters_.ilu_fillin_level_;
const MILU_VARIANT ilu_milu = parameters_.ilu_milu_;
const bool ilu_redblack = parameters_.ilu_redblack_;
const bool ilu_reorder_spheres = parameters_.ilu_reorder_sphere_;
std::unique_ptr<SeqPreconditioner> precond(new SeqPreconditioner(opA.getmat(), ilu_fillin, relax, ilu_milu, ilu_redblack, ilu_reorder_spheres));
return precond;
}
#if HAVE_MPI
typedef Dune::OwnerOverlapCopyCommunication<int, int> Comm;
#if DUNE_VERSION_NEWER_REV(DUNE_ISTL, 2 , 5, 1)
// 3x3 matrix block inversion was unstable from at least 2.3 until and
// including 2.5.0
typedef ParallelOverlappingILU0<Matrix,Vector,Vector,Comm> ParPreconditioner;
#else
typedef ParallelOverlappingILU0<Dune::BCRSMatrix<Dune::MatrixBlock<typename Matrix::field_type,
Matrix::block_type::rows,
Matrix::block_type::cols> >,
Vector, Vector, Comm> ParPreconditioner;
#endif
template <class Operator>
std::unique_ptr<ParPreconditioner>
constructPrecond(Operator& opA, const Comm& comm) const
{
typedef std::unique_ptr<ParPreconditioner> Pointer;
const double relax = parameters_.ilu_relaxation_;
const MILU_VARIANT ilu_milu = parameters_.ilu_milu_;
const bool ilu_redblack = parameters_.ilu_redblack_;
const bool ilu_reorder_spheres = parameters_.ilu_reorder_sphere_;
return Pointer(new ParPreconditioner(opA.getmat(), comm, relax, ilu_milu, ilu_redblack, ilu_reorder_spheres));
}
#endif
template <class MatrixOperator, class POrComm, class AMG >
void
constructAMGPrecond(MatrixOperator& opA, const POrComm& comm, std::unique_ptr< AMG >& amg, std::unique_ptr< MatrixOperator >&, const double relax,
const MILU_VARIANT milu) const
{
ISTLUtility::template createAMGPreconditionerPointer<pressureIndex>( opA, relax,
milu, comm, amg );
}
/// \brief Solve the system using the given preconditioner and scalar product.
template <class Operator, class ScalarProd, class Precond>
void solve(Operator& opA, Vector& x, Vector& istlb, ScalarProd& sp, Precond& precond, Dune::InverseOperatorResult& result) const
{
// TODO: Revise when linear solvers interface opm-core is done
// Construct linear solver.
// GMRes solver
int verbosity = ( isIORank_ ) ? parameters_.linear_solver_verbosity_ : 0;
if ( parameters_.newton_use_gmres_ ) {
Dune::RestartedGMResSolver<Vector> linsolve(opA, sp, precond,
parameters_.linear_solver_reduction_,
parameters_.linear_solver_restart_,
parameters_.linear_solver_maxiter_,
verbosity);
// Solve system.
linsolve.apply(x, istlb, result);
}
else { // BiCGstab solver
Dune::BiCGSTABSolver<Vector> linsolve(opA, sp, precond,
parameters_.linear_solver_reduction_,
parameters_.linear_solver_maxiter_,
verbosity);
// Solve system.
linsolve.apply(x, istlb, result);
}
}
/// Solve the linear system Ax = b, with A being the
/// combined derivative matrix of the residual and b
/// being the residual itself.
/// \param[in] A matrix A
/// \param[inout] x solution to be computed x
/// \param[in] b right hand side b
void solve(Matrix& A, Vector& x, Vector& b ) const
{
// Parallel version is deactivated until we figure out how to do it properly.
#if HAVE_MPI
if (parallelInformation_.type() == typeid(ParallelISTLInformation))
{
typedef Dune::OwnerOverlapCopyCommunication<int,int> Comm;
const ParallelISTLInformation& info =
boost::any_cast<const ParallelISTLInformation&>( parallelInformation_);
Comm istlComm(info.communicator());
// Construct operator, scalar product and vectors needed.
typedef Dune::OverlappingSchwarzOperator<Matrix, Vector, Vector,Comm> Operator;
Operator opA(A, istlComm);
solve( opA, x, b, istlComm );
}
else
#endif
{
// Construct operator, scalar product and vectors needed.
Dune::MatrixAdapter< Matrix, Vector, Vector> opA( A );
solve( opA, x, b );
}
}
/// Solve the linear system Ax = b, with A being the
/// combined derivative matrix of the residual and b
/// being the residual itself.
/// \param[in] A matrix A
/// \param[inout] x solution to be computed x
/// \param[in] b right hand side b
template <class Operator, class Comm >
void solve(Operator& opA, Vector& x, Vector& b, Comm& comm) const
{
Dune::InverseOperatorResult result;
// Parallel version is deactivated until we figure out how to do it properly.
#if HAVE_MPI
if (parallelInformation_.type() == typeid(ParallelISTLInformation))
{
const size_t size = opA.getmat().N();
const ParallelISTLInformation& info =
boost::any_cast<const ParallelISTLInformation&>( parallelInformation_);
// As we use a dune-istl with block size np the number of components
// per parallel is only one.
info.copyValuesTo(comm.indexSet(), comm.remoteIndices(),
size, 1);
// Construct operator, scalar product and vectors needed.
constructPreconditionerAndSolve<Dune::SolverCategory::overlapping>(opA, x, b, comm, result);
}
else
#endif
{
OPM_THROW(std::logic_error,"this method if for parallel solve only");
}
checkConvergence( result );
}
/// Solve the linear system Ax = b, with A being the
/// combined derivative matrix of the residual and b
/// being the residual itself.
/// \param[in] A matrix A
/// \param[inout] x solution to be computed x
/// \param[in] b right hand side b
template <class Operator>
void solve(Operator& opA, Vector& x, Vector& b ) const
{
Dune::InverseOperatorResult result;
// Construct operator, scalar product and vectors needed.
Dune::Amg::SequentialInformation info;
constructPreconditionerAndSolve(opA, x, b, info, result);
checkConvergence( result );
}
void checkConvergence( const Dune::InverseOperatorResult& result ) const
{
// store number of iterations
iterations_ = result.iterations;
// Check for failure of linear solver.
if (!parameters_.ignoreConvergenceFailure_ && !result.converged) {
const std::string msg("Convergence failure for linear solver.");
OPM_THROW_NOLOG(LinearSolverProblem, msg);
}
}
protected:
mutable int iterations_;
boost::any parallelInformation_;
bool isIORank_;
NewtonIterationBlackoilInterleavedParameters parameters_;
}; // end ISTLSolver
} // namespace Opm
#endif

View File

@ -1,677 +0,0 @@
/*
Copyright 2013 SINTEF ICT, Applied Mathematics.
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 3 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 <http://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <opm/autodiff/ImpesTPFAAD.hpp>
#include <opm/autodiff/GeoProps.hpp>
#include <opm/autodiff/GridHelpers.hpp>
#include <opm/core/simulator/BlackoilState.hpp>
#include <opm/core/simulator/WellState.hpp>
#include <opm/common/ErrorMacros.hpp>
#include <opm/common/Exceptions.hpp>
#include <opm/core/linalg/LinearSolverInterface.hpp>
#include <opm/core/wells.h>
#include <iostream>
#include <iomanip>
namespace Opm {
// Repeated from inside ImpesTPFAAD for convenience.
typedef AutoDiffBlock<double> ADB;
typedef ADB::V V;
typedef ADB::M M;
typedef Eigen::SparseMatrix<double> S;
namespace {
std::vector<int>
buildAllCells(const int nc)
{
std::vector<int> all_cells(nc);
for (int c = 0; c < nc; ++c) { all_cells[c] = c; }
return all_cells;
}
template <class GeoProps>
AutoDiffBlock<double>::M
gravityOperator(const UnstructuredGrid& grid,
const HelperOps& ops ,
const GeoProps& geo )
{
using namespace Opm::AutoDiffGrid;
const int nc = numCells(grid);
std::vector<int> f2hf(2 * numFaces(grid), -1);
Eigen::Array<int, Eigen::Dynamic, 2, Eigen::RowMajor>
face_cells = faceCellsToEigen(grid);
typedef typename Opm::UgGridHelpers::Cell2FacesTraits<UnstructuredGrid>::Type
Cell2Faces;
Cell2Faces c2f=cell2Faces(grid);
for (int c = 0; c < nc; ++c) {
typename Cell2Faces::row_type
cell_faces = c2f[c];
typedef typename Cell2Faces::row_type::iterator Iter;
for (Iter f=cell_faces.begin(), end=cell_faces.end();
f!=end; ++f) {
const int p = 0 + (face_cells(*f,0) != c);
f2hf[2*(*f) + p] = f-c2f[0].begin();
}
}
typedef AutoDiffBlock<double>::V V;
typedef AutoDiffBlock<double>::M M;
const V& gpot = geo.gravityPotential();
const V& trans = geo.transmissibility();
const HelperOps::IFaces::Index ni = ops.internal_faces.size();
typedef Eigen::Triplet<double> Tri;
std::vector<Tri> grav; grav.reserve(2 * ni);
for (HelperOps::IFaces::Index i = 0; i < ni; ++i) {
const int f = ops.internal_faces[ i ];
const int c1 = face_cells(f,0);
const int c2 = face_cells(f,1);
assert ((c1 >= 0) && (c2 >= 0));
const double dG1 = gpot[ f2hf[2*f + 0] ];
const double dG2 = gpot[ f2hf[2*f + 1] ];
const double t = trans[ f ];
grav.push_back(Tri(i, c1, t * dG1));
grav.push_back(Tri(i, c2, - t * dG2));
}
S G_s(ni, nc);
G_s.setFromTriplets(grav.begin(), grav.end());
M G(G_s);
return G;
}
V computePerfPress(const UnstructuredGrid& grid, const Wells& wells, const V& rho, const double grav)
{
using namespace Opm::AutoDiffGrid;
const int nw = wells.number_of_wells;
const int nperf = wells.well_connpos[nw];
const int dim = dimensions(grid);
V wdp = V::Zero(nperf,1);
assert(wdp.size() == rho.size());
// Main loop, iterate over all perforations,
// using the following formula:
// wdp(perf) = g*(perf_z - well_ref_z)*rho(perf)
// where the total density rho(perf) is taken to be
// sum_p (rho_p*saturation_p) in the perforation cell.
// [although this is computed on the outside of this function].
for (int w = 0; w < nw; ++w) {
const double ref_depth = wells.depth_ref[w];
for (int j = wells.well_connpos[w]; j < wells.well_connpos[w + 1]; ++j) {
const int cell = wells.well_cells[j];
const double cell_depth = grid.cell_centroids[dim * cell + dim - 1];
wdp[j] = rho[j]*grav*(cell_depth - ref_depth);
}
}
return wdp;
}
} // anonymous namespace
ImpesTPFAAD::ImpesTPFAAD(const UnstructuredGrid& grid,
const BlackoilPropsAdFromDeck& fluid,
const DerivedGeology& geo,
const Wells& wells,
const LinearSolverInterface& linsolver)
: grid_ (grid)
, fluid_ (fluid)
, geo_ (geo)
, wells_ (wells)
, linsolver_(linsolver)
// , pdepfdata_(grid.number_of_cells, fluid)
, ops_ (grid)
, grav_ (gravityOperator(grid_, ops_, geo_))
, cell_residual_ (ADB::null())
, well_flow_residual_ ()
, well_residual_ (ADB::null())
, total_residual_ (ADB::null())
, qs_ (ADB::null())
{
}
void
ImpesTPFAAD::solve(const double dt,
BlackoilState& state,
WellState& well_state)
{
using namespace Opm::AutoDiffGrid;
const int nc = numCells(grid_);
const int np = state.numPhases();
well_flow_residual_.resize(np, ADB::null());
// Compute dynamic data that are treated explicitly.
computeExplicitData(dt, state, well_state);
// Compute relperms once and for all (since saturations are explicit).
DataBlock s = Eigen::Map<const DataBlock>(state.saturation().data(), nc, np);
assert(np == 2);
kr_ = fluidRelperm(s.col(0), s.col(1), V::Zero(nc,1), buildAllCells(nc));
// Compute relperms for wells. This must be revisited for crossflow.
const int nw = wells_.number_of_wells;
const int nperf = wells_.well_connpos[nw];
DataBlock well_s(nperf, np);
for (int w = 0; w < nw; ++w) {
const double* comp_frac = &wells_.comp_frac[np*w];
for (int j = wells_.well_connpos[w]; j < wells_.well_connpos[w+1]; ++j) {
well_s.row(j) = Eigen::Map<const DataBlock>(comp_frac, 1, np);
}
}
const std::vector<int> well_cells(wells_.well_cells,
wells_.well_cells + nperf);
well_kr_ = fluidRelperm(well_s.col(0), well_s.col(1), V::Zero(nperf,1), well_cells);
const double atol = 1.0e-10;
const double rtol = 5.0e-6;
const int maxit = 15;
assemble(dt, state, well_state);
const double r0 = residualNorm();
int it = 0;
std::cout << "\nIteration Residual\n"
<< std::setw(9) << it << std::setprecision(9)
<< std::setw(18) << r0 << std::endl;
bool resTooLarge = r0 > atol;
while (resTooLarge && (it < maxit)) {
solveJacobianSystem(state, well_state);
assemble(dt, state, well_state);
const double r = residualNorm();
resTooLarge = (r > atol) && (r > rtol*r0);
it += 1;
std::cout << std::setw(9) << it << std::setprecision(9)
<< std::setw(18) << r << std::endl;
}
if (resTooLarge) {
OPM_THROW(std::runtime_error, "Failed to compute converged pressure solution");
}
else {
computeFluxes(state, well_state);
}
}
void
ImpesTPFAAD::computeExplicitData(const double dt,
const BlackoilState& state,
const WellState& well_state)
{
using namespace Opm::AutoDiffGrid;
// Suppress warnings about "unused parameters".
static_cast<void>(dt);
static_cast<void>(well_state);
const int nc = numCells(grid_);
const int np = state.numPhases();
const int nw = wells_.number_of_wells;
const int nperf = wells_.well_connpos[nw];
const int dim = dimensions(grid_);
const std::vector<int> cells = buildAllCells(nc);
// Compute relperms.
DataBlock s = Eigen::Map<const DataBlock>(state.saturation().data(), nc, np);
assert(np == 2);
kr_ = fluidRelperm(s.col(0), s.col(1), V::Zero(nc,1), buildAllCells(nc));
// Compute relperms for wells. This must be revisited for crossflow.
DataBlock well_s(nperf, np);
for (int w = 0; w < nw; ++w) {
const double* comp_frac = &wells_.comp_frac[np*w];
for (int j = wells_.well_connpos[w]; j < wells_.well_connpos[w+1]; ++j) {
well_s.row(j) = Eigen::Map<const DataBlock>(comp_frac, 1, np);
}
}
const std::vector<int> well_cells(wells_.well_cells,
wells_.well_cells + nperf);
well_kr_ = fluidRelperm(well_s.col(0), well_s.col(1), V::Zero(nperf,1), well_cells);
// Compute well pressure differentials.
// Construct pressure difference vector for wells.
const double* g = geo_.gravity();
if (g) {
// Guard against gravity in anything but last dimension.
for (int dd = 0; dd < dim - 1; ++dd) {
assert(g[dd] == 0.0);
}
}
V cell_rho_total = V::Zero(nc,1);
const Eigen::Map<const V> p(state.pressure().data(), nc, 1);
const Eigen::Map<const V> T(state.temperature().data(), nc, 1);
for (int phase = 0; phase < np; ++phase) {
const V cell_rho = fluidRho(phase, p, T, cells);
const V cell_s = s.col(phase);
cell_rho_total += cell_s * cell_rho;
}
V rho_perf = subset(cell_rho_total, well_cells);
well_perf_dp_ = computePerfPress(grid_, wells_, rho_perf, g ? g[dim-1] : 0.0);
}
void
ImpesTPFAAD::assemble(const double dt,
const BlackoilState& state,
const WellState& well_state)
{
using namespace Opm::AutoDiffGrid;
const V& pv = geo_.poreVolume();
const int nc = numCells(grid_); ;
const int np = state.numPhases();
const int nw = wells_.number_of_wells;
const int nperf = wells_.well_connpos[nw];
const std::vector<int> cells = buildAllCells(nc);
const Eigen::Map<const DataBlock> z0all(&state.surfacevol()[0], nc, np);
const DataBlock qall = DataBlock::Zero(nc, np);
const V delta_t = dt * V::Ones(nc, 1);
const V transi = subset(geo_.transmissibility(),
ops_.internal_faces);
const std::vector<int> well_cells(wells_.well_cells,
wells_.well_cells + nperf);
const V transw = Eigen::Map<const V>(wells_.WI, nperf, 1);
// Initialize AD variables: p (cell pressures) and bhp (well bhp).
const V p0 = Eigen::Map<const V>(&state.pressure()[0], nc, 1);
const V T0 = Eigen::Map<const V>(&state.temperature()[0], nc, 1);
const V bhp0 = Eigen::Map<const V>(&well_state.bhp()[0], nw, 1);
std::vector<V> vars0 = { p0, bhp0 };
std::vector<ADB> vars = ADB::variables(vars0);
const ADB& p = vars[0];
const ADB T = ADB::constant(T0);
const ADB& bhp = vars[1];
// Compute T_ij * (p_i - p_j).
const ADB nkgradp = transi * (ops_.ngrad * p);
// Extract variables for perforation cell pressures
// and corresponding perforation well pressures.
const ADB p_perfcell = subset(p, well_cells);
const ADB T_perfcell = subset(T, well_cells);
// Construct matrix to map wells->perforations.
S well_to_perf(well_cells.size(), nw);
typedef Eigen::Triplet<double> Tri;
std::vector<Tri> w2p;
for (int w = 0; w < nw; ++w) {
for (int perf = wells_.well_connpos[w]; perf < wells_.well_connpos[w+1]; ++perf) {
w2p.emplace_back(perf, w, 1.0);
}
}
well_to_perf.setFromTriplets(w2p.begin(), w2p.end());
const S perf_to_well = well_to_perf.transpose();
// Finally construct well perforation pressures and well flows.
const ADB p_perfwell = well_to_perf*bhp + well_perf_dp_;
const ADB nkgradp_well = transw * (p_perfcell - p_perfwell);
const Selector<double> cell_to_well_selector(nkgradp_well.value());
cell_residual_ = ADB::constant(pv);
well_residual_ = ADB::constant(V::Zero(nw,1));
ADB divcontrib_sum = ADB::constant(V::Zero(nc,1));
qs_ = ADB::constant(V::Zero(nw*np, 1));
for (int phase = 0; phase < np; ++phase) {
const ADB cell_b = fluidFvf(phase, p, T, cells);
const ADB cell_rho = fluidRho(phase, p, T, cells);
const ADB well_b = fluidFvf(phase, p_perfwell, T_perfcell, well_cells);
const V kr = fluidKr(phase);
// Explicitly not asking for derivatives of viscosity,
// since they are not available yet.
const V mu = fluidMu(phase, p.value(), T.value(), cells);
const V cell_mob = kr / mu;
const ADB head_diff_grav = (grav_ * cell_rho);
const ADB head = nkgradp + (grav_ * cell_rho);
const UpwindSelector<double> upwind(grid_, ops_, head.value());
const V face_mob = upwind.select(cell_mob);
const V well_kr = fluidKrWell(phase);
const V well_mu = fluidMu(phase, p_perfwell.value(), T_perfcell.value(), well_cells);
const V well_mob = well_kr / well_mu;
const V perf_mob = cell_to_well_selector.select(subset(cell_mob, well_cells), well_mob);
const ADB flux = face_mob * head;
const ADB perf_flux = perf_mob * (nkgradp_well); // No gravity term for perforations.
const ADB face_b = upwind.select(cell_b);
const ADB perf_b = cell_to_well_selector.select(subset(cell_b, well_cells), well_b);
const V z0 = z0all.block(0, phase, nc, 1);
const V q = qall .block(0, phase, nc, 1);
const ADB well_contrib = superset(perf_flux*perf_b, well_cells, nc);
const ADB divcontrib = delta_t * (ops_.div * (flux * face_b) + well_contrib);
const V qcontrib = delta_t * q;
const ADB pvcontrib = ADB::constant(pv*z0);
const ADB component_contrib = pvcontrib + qcontrib;
divcontrib_sum = divcontrib_sum - divcontrib/cell_b;
cell_residual_ = cell_residual_ - (component_contrib/cell_b);
const ADB well_rates = perf_to_well * (perf_flux*perf_b);
qs_ = qs_ + superset(well_rates, Span(nw, 1, phase*nw), nw*np);
}
cell_residual_ = cell_residual_ + divcontrib_sum;
// Handling BHP and SURFACE_RATE wells.
V bhp_targets(nw,1);
V rate_targets(nw,1);
S rate_distr(nw, np*nw);
for (int w = 0; w < nw; ++w) {
const WellControls* wc = wells_.ctrls[w];
if (well_controls_get_current_type(wc) == BHP) {
bhp_targets[w] = well_controls_get_current_target( wc );
rate_targets[w] = -1e100;
} else if (well_controls_get_current_type(wc) == SURFACE_RATE) {
bhp_targets[w] = -1e100;
rate_targets[w] = well_controls_get_current_target( wc );
{
const double * distr = well_controls_get_current_distr( wc );
for (int phase = 0; phase < np; ++phase) {
rate_distr.insert(w, phase*nw + w) = distr[phase];
}
}
} else {
OPM_THROW(std::runtime_error, "Can only handle BHP and SURFACE_RATE type controls.");
}
}
const ADB bhp_residual = bhp - bhp_targets;
const ADB rate_residual = rate_distr * qs_ - rate_targets;
// Choose bhp residual for positive bhp targets.
Selector<double> bhp_selector(bhp_targets);
well_residual_ = bhp_selector.select(bhp_residual, rate_residual);
// Build full residual by concatenation of residual arrays and
// jacobian matrices.
total_residual_ = collapseJacs(vertcat(cell_residual_, well_residual_));
// std::cout.precision(16);
// std::cout << total_residual_;
}
void
ImpesTPFAAD::solveJacobianSystem(BlackoilState& state,
WellState& well_state) const
{
using namespace Opm::AutoDiffGrid;
const int nc = numCells(grid_);
const int nw = wells_.number_of_wells;
// const int np = state.numPhases();
Eigen::SparseMatrix<double, Eigen::RowMajor> matr;
total_residual_.derivative()[0].toSparse(matr);
V dx(V::Zero(total_residual_.size()));
Opm::LinearSolverInterface::LinearSolverReport rep
= linsolver_.solve(matr.rows(), matr.nonZeros(),
matr.outerIndexPtr(), matr.innerIndexPtr(), matr.valuePtr(),
total_residual_.value().data(), dx.data());
if (!rep.converged) {
OPM_THROW(LinearSolverProblem, "ImpesTPFAAD::solve(): Linear solver convergence failure.");
}
const V p0 = Eigen::Map<const V>(&state.pressure()[0], nc, 1);
const V dp = subset(dx, Span(nc));
const V p = p0 - dp;
std::copy(&p[0], &p[0] + nc, state.pressure().begin());
const V bhp0 = Eigen::Map<const V>(&well_state.bhp()[0], nw, 1);
Span bhp_dofs(nw, 1, nc);
const V dbhp = subset(dx, bhp_dofs);
const V bhp = bhp0 - dbhp;
std::copy(&bhp[0], &bhp[0] + nw, well_state.bhp().begin());
}
double
ImpesTPFAAD::residualNorm() const
{
return total_residual_.value().matrix().norm();
}
void
ImpesTPFAAD::computeFluxes(BlackoilState& state,
WellState& well_state) const
{
using namespace Opm::AutoDiffGrid;
// This method computes state.faceflux(),
// well_state.perfRates() and well_state.perfPress().
const int nc = numCells(grid_);
const int np = state.numPhases();
const int nw = wells_.number_of_wells;
const int nperf = wells_.well_connpos[nw];
// Build cell sets.
const std::vector<int> cells = buildAllCells(nc);
const std::vector<int> well_cells(wells_.well_cells,
wells_.well_cells + nperf);
// Construct matrix to map wells->perforations.
S well_to_perf(well_cells.size(), nw);
typedef Eigen::Triplet<double> Tri;
std::vector<Tri> w2p;
for (int w = 0; w < nw; ++w) {
for (int perf = wells_.well_connpos[w]; perf < wells_.well_connpos[w+1]; ++perf) {
w2p.emplace_back(perf, w, 1.0);
}
}
well_to_perf.setFromTriplets(w2p.begin(), w2p.end());
const S perf_to_well = well_to_perf.transpose();
const V transw = Eigen::Map<const V>(wells_.WI, nperf, 1);
const V p = Eigen::Map<const V>(&state.pressure()[0], nc, 1);
const V T = Eigen::Map<const V>(&state.temperature()[0], nc, 1);
const V bhp = Eigen::Map<const V>(&well_state.bhp()[0], nw, 1);
const V p_perfcell = subset(p, well_cells);
const V T_perfcell = subset(T, well_cells);
const V transi = subset(geo_.transmissibility(),
ops_.internal_faces);
const V nkgradp = transi * (ops_.ngrad * p.matrix()).array();
const V p_perfwell = (well_to_perf*bhp.matrix()).array() + well_perf_dp_;
const V nkgradp_well = transw * (p_perfcell - p_perfwell);
const Selector<double> cell_to_well_selector(nkgradp_well);
V flux = V::Zero(ops_.internal_faces.size(), 1);
V perf_flux = V::Zero(nperf, 1);
for (int phase = 0; phase < np; ++phase) {
const V cell_rho = fluidRho(phase, p, T, cells);
const V head = nkgradp + (grav_ * cell_rho.matrix()).array();
const UpwindSelector<double> upwind(grid_, ops_, head);
const V kr = fluidKr(phase);
const V mu = fluidMu(phase, p, T, cells);
const V cell_mob = kr / mu;
const V face_mob = upwind.select(cell_mob);
const V well_kr = fluidKrWell(phase);
const V well_mu = fluidMu(phase, p_perfwell, T_perfcell, well_cells);
const V well_mob = well_kr / well_mu;
const V perf_mob = cell_to_well_selector.select(subset(cell_mob, well_cells), well_mob);
perf_flux += perf_mob * (nkgradp_well); // No gravity term for perforations.
flux += face_mob * head;
}
V all_flux = superset(flux, ops_.internal_faces, numFaces(grid_));
std::copy(all_flux.data(), all_flux.data() + numFaces(grid_), state.faceflux().begin());
perf_flux = -perf_flux; // well_state.perfRates() assumed to be inflows.
std::copy(perf_flux.data(), perf_flux.data() + nperf, well_state.perfRates().begin());
std::copy(p_perfwell.data(), p_perfwell.data() + nperf, well_state.perfPress().begin());
std::copy(qs_.value().data(), qs_.value().data() + np*nw, &well_state.wellRates()[0]);
}
V ImpesTPFAAD::fluidMu(const int phase, const V& p, const V& T, const std::vector<int>& cells) const
{
return fluidMu(phase, ADB::constant(p), ADB::constant(T), cells).value();
}
ADB ImpesTPFAAD::fluidMu(const int phase, const ADB& p, const ADB& T, const std::vector<int>& cells) const
{
switch (phase) {
case Water:
return fluid_.muWat(p, T, cells);
case Oil: {
ADB dummy_rs = V::Zero(p.size(), 1) * p;
std::vector<PhasePresence> cond(dummy_rs.size());
return fluid_.muOil(p, T, dummy_rs, cond, cells);
}
case Gas: {
ADB dummy_rv = V::Zero(p.size(), 1) * p;
std::vector<PhasePresence> cond(dummy_rv.size());
return fluid_.muGas(p, T, dummy_rv, cond, cells);
}
default:
OPM_THROW(std::runtime_error, "Unknown phase index " << phase);
}
}
V ImpesTPFAAD::fluidFvf(const int phase, const V& p, const V& T, const std::vector<int>& cells) const
{
return fluidFvf(phase, ADB::constant(p), ADB::constant(T), cells).value();
}
ADB ImpesTPFAAD::fluidFvf(const int phase, const ADB& p, const ADB& T, const std::vector<int>& cells) const
{
switch (phase) {
case Water:
return fluid_.bWat(p, T, cells);
case Oil: {
ADB dummy_rs = V::Zero(p.size(), 1) * p;
std::vector<PhasePresence> cond(dummy_rs.size());
return fluid_.bOil(p, T, dummy_rs, cond, cells);
}
case Gas: {
ADB dummy_rv = V::Zero(p.size(), 1) * p;
std::vector<PhasePresence> cond(dummy_rv.size());
return fluid_.bGas(p, T, dummy_rv, cond, cells);
}
default:
OPM_THROW(std::runtime_error, "Unknown phase index " << phase);
}
}
V ImpesTPFAAD::fluidRho(const int phase, const V& p, const V& T, const std::vector<int>& cells) const
{
V rho = fluid_.surfaceDensity(phase, cells);
V b = fluidFvf(phase, p, T, cells);
rho = rho * b;
return rho;
}
ADB ImpesTPFAAD::fluidRho(const int phase, const ADB& p, const ADB& T, const std::vector<int>& cells) const
{
const V& rhos = fluid_.surfaceDensity(phase, cells);
ADB b = fluidFvf(phase, p, T, cells);
ADB rho = rhos * b;
return rho;
}
std::vector<V> ImpesTPFAAD::fluidRelperm(const V& sw,
const V& so,
const V& sg,
const std::vector<int>& cells) const
{
std::vector<ADB> kr_ad = fluid_.relperm(ADB::constant(sw), ADB::constant(so), ADB::constant(sg), cells);
std::vector<V> kr = { kr_ad[0].value(), kr_ad[1].value(), kr_ad[2].value() };
return kr;
}
V ImpesTPFAAD::fluidKr(const int phase) const
{
return kr_[phase];
}
V ImpesTPFAAD::fluidKrWell(const int phase) const
{
return well_kr_[phase];
}
} // namespace Opm

View File

@ -1,122 +0,0 @@
/*
Copyright 2013 SINTEF ICT, Applied Mathematics.
Copyright 2013 Statoil ASA.
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 3 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 <http://www.gnu.org/licenses/>.
*/
#ifndef OPM_IMPESTPFAAD_HEADER_INCLUDED
#define OPM_IMPESTPFAAD_HEADER_INCLUDED
#include <opm/autodiff/AutoDiffBlock.hpp>
#include <opm/autodiff/AutoDiffHelpers.hpp>
#include <opm/autodiff/BlackoilModelEnums.hpp>
#include <opm/autodiff/BlackoilPropsAdFromDeck.hpp>
struct UnstructuredGrid;
struct Wells;
namespace Opm {
class DerivedGeology;
class LinearSolverInterface;
class BlackoilState;
class WellState;
/// Class for solving black-oil impes problems.
/// Current known limitations:
/// - pressure solve only
/// - no miscibility
/// - no gravity in wells or crossflow
class ImpesTPFAAD
{
public:
/// Construct impes solver.
ImpesTPFAAD(const UnstructuredGrid& grid,
const BlackoilPropsAdFromDeck& fluid,
const DerivedGeology& geo,
const Wells& wells,
const LinearSolverInterface& linsolver);
/// Solve forward in time.
/// Currently this will only modify
/// state.pressure(), state.faceflux(), well_state.bhp()
/// and well_state.wellRates().
void solve(const double dt,
BlackoilState& state,
WellState& well_state);
private:
// Disallow copying and assignment
ImpesTPFAAD(const ImpesTPFAAD& rhs);
ImpesTPFAAD& operator=(const ImpesTPFAAD& rhs);
// Types
typedef AutoDiffBlock<double> ADB;
typedef ADB::V V;
typedef ADB::M M;
typedef Eigen::Array<double,
Eigen::Dynamic,
Eigen::Dynamic,
Eigen::RowMajor> DataBlock;
enum { Water = Opm::Water,
Oil = Opm::Oil,
Gas = Opm::Gas };
// Data
const UnstructuredGrid& grid_;
const BlackoilPropsAdFromDeck& fluid_;
const DerivedGeology& geo_ ;
const Wells& wells_;
const LinearSolverInterface& linsolver_;
HelperOps ops_;
const M grav_;
ADB cell_residual_;
std::vector<ADB> well_flow_residual_;
ADB well_residual_;
ADB total_residual_;
std::vector<V> kr_;
std::vector<V> well_kr_;
ADB qs_;
V well_perf_dp_;
// Methods for assembling and solving.
void computeExplicitData(const double dt,
const BlackoilState& state,
const WellState& well_state);
void assemble(const double dt,
const BlackoilState& state,
const WellState& well_state);
void solveJacobianSystem(BlackoilState& state,
WellState& well_state) const;
double residualNorm() const;
void computeFluxes(BlackoilState& state, WellState& well_state) const;
// Fluid interface forwarding calls to correct methods of fluid_.
V fluidMu(const int phase, const V& p, const V& T, const std::vector<int>& cells) const;
ADB fluidMu(const int phase, const ADB& p, const ADB& T, const std::vector<int>& cells) const;
V fluidFvf(const int phase, const V& p, const V& T, const std::vector<int>& cells) const;
ADB fluidFvf(const int phase, const ADB& p, const ADB& T, const std::vector<int>& cells) const;
V fluidRho(const int phase, const V& p, const V& T, const std::vector<int>& cells) const;
ADB fluidRho(const int phase, const ADB& p, const ADB& T, const std::vector<int>& cells) const;
std::vector<V> fluidRelperm(const V& sw, const V& so, const V& sg, const std::vector<int>& cells) const;
V fluidKr(const int phase) const;
V fluidKrWell(const int phase) const;
};
} // namespace Opm
#endif /* OPM_IMPESTPFAAD_HEADER_INCLUDED */

View File

@ -1,36 +0,0 @@
/*
Copyright 2013 SINTEF ICT, Applied Mathematics.
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 3 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 <http://www.gnu.org/licenses/>.
*/
#include <opm/autodiff/LinearisedBlackoilResidual.hpp>
int Opm::LinearisedBlackoilResidual::sizeNonLinear() const
{
int size = 0;
std::vector<ADB>::const_iterator massBalanceIt = material_balance_eq.begin();
const std::vector<ADB>::const_iterator endMassBalanceIt = material_balance_eq.end();
for (; massBalanceIt != endMassBalanceIt; ++massBalanceIt) {
size += (*massBalanceIt).size();
}
size += well_flux_eq.size();
size += well_eq.size();
return size;
}

View File

@ -1,77 +0,0 @@
/*
Copyright 2014 SINTEF ICT, Applied Mathematics.
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 3 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 <http://www.gnu.org/licenses/>.
*/
#ifndef OPM_LINEARISEDBLACKOILRESIDUAL_HEADER_INCLUDED
#define OPM_LINEARISEDBLACKOILRESIDUAL_HEADER_INCLUDED
#include <opm/autodiff/AutoDiffBlock.hpp>
namespace Opm
{
/// Residual structure of the fully implicit solver.
/// All equations are given as AD types, with multiple
/// jacobian blocks corresponding to the primary unknowns. The
/// primary unknowns are for a three-phase simulation, in order:
/// p (pressure)
/// sw (water saturation)
/// xvar (gas saturation, gas-oil ratio or oil-gas ratio)
/// qs (well outflows by well and phase)
/// bhp (bottom hole pressures)
/// In the above, the xvar variable will have a different
/// meaning from cell to cell, corresponding to the state in
/// that cell (saturated, undersaturated oil or undersaturated
/// gas). In a two-phase simulation, either sw or xvar is not
/// used, depending on which phase is missing.
///
/// Note: this class is strongly coupled to the class
/// FullyImplicitBlackoilSolver, and is separated from that
/// class to facilitate the development of linear solver
/// strategies outside that class.
struct LinearisedBlackoilResidual {
/// A type alias for the automatic differentiation type.
typedef AutoDiffBlock<double> ADB;
/// The material_balance_eq vector has one element for each
/// active phase, each of which has size equal to the number
/// of cells. Each material balance equation is given in terms
/// of surface volumes (in SI units, that is standard m^3).
std::vector<ADB> material_balance_eq;
/// The well_flux_eq has size equal to the number of wells
/// times the number of phases. It contains the well flow
/// equations, relating the total well flows to
/// bottom-hole pressures and reservoir conditions.
ADB well_flux_eq;
/// The well_eq has size equal to the number of wells. It
/// contains the well control equations, that is for each
/// well either a rate specification or bottom hole
/// pressure specification.
ADB well_eq;
std::vector<double> matbalscale;
bool singlePrecision ;
/// The size of the non-linear system.
int sizeNonLinear() const;
};
} // namespace Opm
#endif // OPM_LINEARISEDBLACKOILRESIDUAL_HEADER_INCLUDED

View File

@ -1,201 +0,0 @@
/*
Copyright 2014 SINTEF ICT, Applied Mathematics.
Copyright 2015 Dr. Blatt - HPC-Simulation-Software & Services
Copyright 2015 NTNU
Copyright 2015 Statoil AS
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 3 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 <http://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <opm/autodiff/DuneMatrix.hpp>
#include <opm/autodiff/NewtonIterationBlackoilCPR.hpp>
#include <opm/autodiff/NewtonIterationUtilities.hpp>
#include <opm/autodiff/AutoDiffHelpers.hpp>
#include <opm/parser/eclipse/Units/Units.hpp>
#include <opm/common/Exceptions.hpp>
#include <opm/core/linalg/ParallelIstlInformation.hpp>
#include <opm/common/utility/platform_dependent/disable_warnings.h>
#if HAVE_UMFPACK
#include <Eigen/UmfPackSupport>
#else
#include <Eigen/SparseLU>
#endif
#include <opm/common/utility/platform_dependent/reenable_warnings.h>
namespace Opm
{
typedef AutoDiffBlock<double> ADB;
typedef ADB::V V;
typedef ADB::M M;
/// Construct a system solver.
NewtonIterationBlackoilCPR::NewtonIterationBlackoilCPR(const ParameterGroup& param,
const boost::any& parallelInformation_arg)
: cpr_param_( param ),
iterations_( 0 ),
parallelInformation_(parallelInformation_arg),
newton_use_gmres_( param.getDefault("newton_use_gmres", false ) ),
linear_solver_reduction_( param.getDefault("linear_solver_reduction", 1e-2 ) ),
linear_solver_maxiter_( param.getDefault("linear_solver_maxiter", 50 ) ),
linear_solver_restart_( param.getDefault("linear_solver_restart", 40 ) ),
linear_solver_verbosity_( param.getDefault("linear_solver_verbosity", 0 )),
linear_solver_ignoreconvergencefailure_(param.getDefault("linear_solver_ignoreconvergencefailure", false))
{
}
/// Solve the linear system Ax = b, with A being the
/// combined derivative matrix of the residual and b
/// being the residual itself.
/// \param[in] residual residual object containing A and b.
/// \return the solution x
NewtonIterationBlackoilCPR::SolutionVector
NewtonIterationBlackoilCPR::computeNewtonIncrement(const LinearisedBlackoilResidual& residual) const
{
// Build the vector of equations.
const int np = residual.material_balance_eq.size();
std::vector<ADB> eqs;
eqs.reserve(np + 2);
for (int phase = 0; phase < np; ++phase) {
eqs.push_back(residual.material_balance_eq[phase]);
}
// check if wells are present
const bool hasWells = residual.well_flux_eq.size() > 0 ;
std::vector<ADB> elim_eqs;
if( hasWells )
{
eqs.push_back(residual.well_flux_eq);
eqs.push_back(residual.well_eq);
// Eliminate the well-related unknowns, and corresponding equations.
elim_eqs.reserve(2);
elim_eqs.push_back(eqs[np]);
eqs = eliminateVariable(eqs, np); // Eliminate well flux unknowns.
elim_eqs.push_back(eqs[np]);
eqs = eliminateVariable(eqs, np); // Eliminate well bhp unknowns.
assert(int(eqs.size()) == np);
}
// Scale material balance equations.
for (int phase = 0; phase < np; ++phase) {
eqs[phase] = eqs[phase] * residual.matbalscale[phase];
}
// Add material balance equations (or other manipulations) to
// form pressure equation in top left of full system.
Eigen::SparseMatrix<double, Eigen::RowMajor> A;
V b;
formEllipticSystem(np, eqs, A, b);
// Scale pressure equation.
const double pscale = 200*unit::barsa;
const int nc = residual.material_balance_eq[0].size();
A.topRows(nc) *= pscale;
b.topRows(nc) *= pscale;
// Solve reduced system.
SolutionVector dx(SolutionVector::Zero(b.size()));
// Create ISTL matrix.
DuneMatrix istlA( A );
// Create ISTL matrix for elliptic part.
DuneMatrix istlAe( A.topLeftCorner(nc, nc) );
// Right hand side.
Vector istlb(istlA.N());
std::copy_n(b.data(), istlb.size(), istlb.begin());
// System solution
Vector x(istlA.M());
x = 0.0;
Dune::InverseOperatorResult result;
#if HAVE_MPI
if(parallelInformation_.type()==typeid(ParallelISTLInformation))
{
typedef Dune::OwnerOverlapCopyCommunication<int,int> Comm;
const ParallelISTLInformation& info =
boost::any_cast<const ParallelISTLInformation&>( parallelInformation_);
Comm istlComm(info.communicator());
Comm istlAeComm(info.communicator());
info.copyValuesTo(istlAeComm.indexSet(), istlAeComm.remoteIndices());
info.copyValuesTo(istlComm.indexSet(), istlComm.remoteIndices(),
istlAe.N(), istlA.N()/istlAe.N());
// Construct operator, scalar product and vectors needed.
typedef Dune::OverlappingSchwarzOperator<Mat,Vector,Vector,Comm> Operator;
Operator opA(istlA, istlComm);
constructPreconditionerAndSolve<Dune::SolverCategory::overlapping>(opA, istlAe, x, istlb, istlComm, istlAeComm, result);
}
else
#endif
{
// Construct operator, scalar product and vectors needed.
typedef Dune::MatrixAdapter<Mat,Vector,Vector> Operator;
Operator opA(istlA);
Dune::Amg::SequentialInformation info;
constructPreconditionerAndSolve(opA, istlAe, x, istlb, info, info, result);
}
// store number of iterations
iterations_ = result.iterations;
// Check for failure of linear solver.
if (!result.converged && !linear_solver_ignoreconvergencefailure_) {
OPM_THROW(LinearSolverProblem, "Convergence failure for linear solver.");
}
// Copy solver output to dx.
std::copy(x.begin(), x.end(), dx.data());
if ( hasWells ) {
// Compute full solution using the eliminated equations.
// Recovery in inverse order of elimination.
dx = recoverVariable(elim_eqs[1], dx, np);
dx = recoverVariable(elim_eqs[0], dx, np);
}
return dx;
}
const boost::any& NewtonIterationBlackoilCPR::parallelInformation() const
{
return parallelInformation_;
}
} // namespace Opm

View File

@ -1,145 +0,0 @@
/*
Copyright 2014 SINTEF ICT, Applied Mathematics.
Copyright 2015 IRIS AS
Copyright 2015 Dr. Blatt - HPC-Simulation-Software & Services
Copyright 2015 NTNU
Copyright 2015 Statoil AS
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 3 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 <http://www.gnu.org/licenses/>.
*/
#ifndef OPM_NEWTONITERATIONBLACKOILCPR_HEADER_INCLUDED
#define OPM_NEWTONITERATIONBLACKOILCPR_HEADER_INCLUDED
#include <opm/autodiff/DuneMatrix.hpp>
#include <opm/autodiff/NewtonIterationBlackoilInterface.hpp>
#include <opm/autodiff/CPRPreconditioner.hpp>
#include <opm/common/utility/parameters/ParameterGroup.hpp>
#include <opm/core/linalg/LinearSolverInterface.hpp>
#include <dune/istl/scalarproducts.hh>
#include <dune/istl/operators.hh>
#include <dune/istl/bvector.hh>
#include <memory>
namespace Opm
{
/// This class solves the fully implicit black-oil system by
/// applying a Constrained Pressure Residual preconditioning
/// strategy.
/// The approach is similar to the one described in
/// "Preconditioning for Efficiently Applying Algebraic Multigrid
/// in Fully Implicit Reservoir Simulations" by Gries et al (SPE 163608).
class NewtonIterationBlackoilCPR : public NewtonIterationBlackoilInterface
{
typedef Dune::FieldVector<double, 1 > VectorBlockType;
typedef Dune::FieldMatrix<double, 1, 1> MatrixBlockType;
typedef Dune::BCRSMatrix <MatrixBlockType> Mat;
typedef Dune::BlockVector<VectorBlockType> Vector;
public:
/// Construct a system solver.
/// \param[in] param parameters controlling the behaviour of
/// the preconditioning and choice of
/// linear solvers.
/// Parameters:
/// cpr_relax (default 1.0) relaxation for the preconditioner
/// cpr_ilu_n (default 0) use ILU(n) for preconditioning of the linear system
/// cpr_use_amg (default false) if true, use AMG preconditioner for elliptic part
/// cpr_use_bicgstab (default true) if true, use BiCGStab (else use CG) for elliptic part
/// \param[in] parallelInformation In the case of a parallel run
/// with dune-istl the information about the parallelization.
NewtonIterationBlackoilCPR(const ParameterGroup& param,
const boost::any& parallelInformation=boost::any());
/// Solve the system of linear equations Ax = b, with A being the
/// combined derivative matrix of the residual and b
/// being the residual itself.
/// \param[in] residual residual object containing A and b.
/// \return the solution x
virtual SolutionVector computeNewtonIncrement(const LinearisedBlackoilResidual& residual) const;
/// \copydoc NewtonIterationBlackoilInterface::iterations
virtual int iterations () const { return iterations_; }
/// \copydoc NewtonIterationBlackoilInterface::parallelInformation
virtual const boost::any& parallelInformation() const;
private:
/// \brief construct the CPR preconditioner and the solver.
/// \tparam P The type of the parallel information.
/// \param parallelInformation the information about the parallelization.
#if DUNE_VERSION_NEWER(DUNE_ISTL, 2, 6)
template<Dune::SolverCategory::Category category=Dune::SolverCategory::sequential,
class O, class P>
#else
template<int category=Dune::SolverCategory::sequential, class O, class P>
#endif
void constructPreconditionerAndSolve(O& opA, DuneMatrix& istlAe,
Vector& x, Vector& istlb,
const P& parallelInformation_arg,
const P& parallelInformationAe,
Dune::InverseOperatorResult& result) const
{
#if DUNE_VERSION_NEWER(DUNE_ISTL, 2, 6)
auto sp = Dune::createScalarProduct<Vector,P>(parallelInformation_arg, category);
#else
typedef Dune::ScalarProductChooser<Vector,P,category> ScalarProductChooser;
std::unique_ptr<typename ScalarProductChooser::ScalarProduct>
sp(ScalarProductChooser::construct(parallelInformation_arg));
#endif
// Construct preconditioner.
// typedef Dune::SeqILU0<Mat,Vector,Vector> Preconditioner;
typedef Opm::CPRPreconditioner<Mat,Vector,Vector,P> Preconditioner;
parallelInformation_arg.copyOwnerToAll(istlb, istlb);
Preconditioner precond(cpr_param_, opA.getmat(), istlAe, parallelInformation_arg,
parallelInformationAe);
// TODO: Revise when linear solvers interface opm-core is done
// Construct linear solver.
// GMRes solver
if ( newton_use_gmres_ ) {
Dune::RestartedGMResSolver<Vector> linsolve(opA, *sp, precond,
linear_solver_reduction_, linear_solver_restart_, linear_solver_maxiter_, linear_solver_verbosity_);
// Solve system.
linsolve.apply(x, istlb, result);
}
else { // BiCGstab solver
Dune::BiCGSTABSolver<Vector> linsolve(opA, *sp, precond,
linear_solver_reduction_, linear_solver_maxiter_, linear_solver_verbosity_);
// Solve system.
linsolve.apply(x, istlb, result);
}
}
CPRParameter cpr_param_;
mutable int iterations_;
boost::any parallelInformation_;
const bool newton_use_gmres_;
const double linear_solver_reduction_;
const int linear_solver_maxiter_;
const int linear_solver_restart_;
const int linear_solver_verbosity_;
const bool linear_solver_ignoreconvergencefailure_;
};
} // namespace Opm
#endif // OPM_NEWTONITERATIONBLACKOILCPR_HEADER_INCLUDED

View File

@ -1,57 +0,0 @@
/*
Copyright 2014 SINTEF ICT, Applied Mathematics.
Copyright 2014 IRIS AS
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 3 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 <http://www.gnu.org/licenses/>.
*/
#ifndef OPM_NEWTONITERATIONBLACKOILINTERFACE_HEADER_INCLUDED
#define OPM_NEWTONITERATIONBLACKOILINTERFACE_HEADER_INCLUDED
#include <opm/autodiff/LinearisedBlackoilResidual.hpp>
#include <boost/any.hpp>
namespace Opm
{
/// Interface class for (linear) solvers for the fully implicit black-oil system.
class NewtonIterationBlackoilInterface
{
public:
/// Return type for linearSolve(). A simple, non-ad vector type.
typedef LinearisedBlackoilResidual::ADB::V SolutionVector;
virtual ~NewtonIterationBlackoilInterface() {}
/// Solve the linear system Ax = b, with A being the
/// combined derivative matrix of the residual and b
/// being the residual itself.
/// \param[in] residual residual object containing A and b.
/// \return the solution x
virtual SolutionVector computeNewtonIncrement(const LinearisedBlackoilResidual& residual) const = 0;
/// \return number of linear iterations used during last call of computeNewtonIncrement
virtual int iterations () const = 0;
/// \brief Get the information about the parallelization of the grid.
virtual const boost::any& parallelInformation() const = 0;
};
} // namespace Opm
#endif // OPM_NEWTONITERATIONBLACKOILINTERFACE_HEADER_INCLUDED

View File

@ -1,497 +0,0 @@
/*
Copyright 2015 SINTEF ICT, Applied Mathematics.
Copyright 2015 Dr. Blatt - HPC-Simulation-Software & Services
Copyright 2015 NTNU
Copyright 2015 Statoil AS
Copyright 2015 IRIS AS
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 3 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 <http://www.gnu.org/licenses/>.
*/
#include <config.h>
// Define making clear that the simulator supports AMG
#define FLOW_SUPPORT_AMG !defined(HAVE_UMFPACK)
#include <opm/autodiff/CPRPreconditioner.hpp>
#include <opm/autodiff/NewtonIterationBlackoilInterleaved.hpp>
#include <opm/autodiff/NewtonIterationUtilities.hpp>
#include <opm/autodiff/ParallelRestrictedAdditiveSchwarz.hpp>
#include <opm/autodiff/ParallelOverlappingILU0.hpp>
#include <opm/autodiff/AutoDiffHelpers.hpp>
#include <opm/autodiff/DuneMatrix.hpp>
#include <opm/common/Exceptions.hpp>
#include <opm/core/linalg/ParallelIstlInformation.hpp>
#include <opm/autodiff/ISTLSolver.hpp>
#include <opm/common/utility/platform_dependent/disable_warnings.h>
#if HAVE_UMFPACK
#include <Eigen/UmfPackSupport>
#else
#include <Eigen/SparseLU>
#endif
#include <opm/common/utility/platform_dependent/reenable_warnings.h>
namespace Opm
{
namespace detail {
/**
* Simple binary operator that always returns 0.1
* It is used to get the sparsity pattern for our
* interleaved system, and is marginally faster than using
* operator+=.
*/
template<typename Scalar> struct PointOneOp {
EIGEN_EMPTY_STRUCT_CTOR(PointOneOp)
Scalar operator()(const Scalar&, const Scalar&) const { return 0.1; }
};
}
/// This class solves the fully implicit black-oil system by
/// solving the reduced system (after eliminating well variables)
/// as a block-structured matrix (one block for all cell variables) for a fixed
/// number of cell variables np .
template <int np, class ScalarT = double >
class NewtonIterationBlackoilInterleavedImpl : public NewtonIterationBlackoilInterface
{
typedef ScalarT Scalar;
typedef Dune::FieldVector<Scalar, np > VectorBlockType;
typedef Dune::MatrixBlock<Scalar, np, np > MatrixBlockType;
typedef Dune::BCRSMatrix <MatrixBlockType> Mat;
typedef Dune::BlockVector<VectorBlockType> Vector;
typedef Opm::ISTLSolver< MatrixBlockType, VectorBlockType > ISTLSolverType;
public:
typedef NewtonIterationBlackoilInterface :: SolutionVector SolutionVector;
/// Construct a system solver.
/// \param[in] param parameters controlling the behaviour of the linear solvers
/// \param[in] parallelInformation In the case of a parallel run
/// with dune-istl the information about the parallelization.
NewtonIterationBlackoilInterleavedImpl(const NewtonIterationBlackoilInterleavedParameters& param,
const boost::any& parallelInformation_arg=boost::any())
: istlSolver_( param, parallelInformation_arg ),
parameters_( param )
{
}
/// Solve the system of linear equations Ax = b, with A being the
/// combined derivative matrix of the residual and b
/// being the residual itself.
/// \param[in] residual residual object containing A and b.
/// \return the solution x
/// \copydoc NewtonIterationBlackoilInterface::iterations
int iterations () const { return istlSolver_.iterations(); }
/// \copydoc NewtonIterationBlackoilInterface::parallelInformation
const boost::any& parallelInformation() const { return istlSolver_.parallelInformation(); }
public:
void formInterleavedSystem(const std::vector<LinearisedBlackoilResidual::ADB>& eqs,
Mat& istlA) const
{
assert( np == int(eqs.size()) );
// Find sparsity structure as union of basic block sparsity structures,
// corresponding to the jacobians with respect to pressure.
// Use our custom PointOneOp to get to the union structure.
// As default we only iterate over the pressure derivatives.
Eigen::SparseMatrix<double, Eigen::ColMajor> col_major = eqs[0].derivative()[0].getSparse();
detail::PointOneOp<double> point_one;
for (int phase = 1; phase < np; ++phase) {
const AutoDiffMatrix::SparseRep& mat = eqs[phase].derivative()[0].getSparse();
col_major = col_major.binaryExpr(mat, point_one);
}
// For some cases (for instance involving Solvent flow) the reasoning for only adding
// the pressure derivatives fails. As getting the sparsity pattern is non-trivial, in terms
// of work, the full sparsity pattern is only added when required.
if (parameters_.require_full_sparsity_pattern_) {
for (int p1 = 0; p1 < np; ++p1) {
for (int p2 = 1; p2 < np; ++p2) { // pressure is already added
const AutoDiffMatrix::SparseRep& mat = eqs[p1].derivative()[p2].getSparse();
col_major = col_major.binaryExpr(mat, point_one);
}
}
}
// Automatically convert the column major structure to a row-major structure
Eigen::SparseMatrix<double, Eigen::RowMajor> row_major = col_major;
const int size = row_major.rows();
assert(size == row_major.cols());
{
// Create ISTL matrix with interleaved rows and columns (block structured).
istlA.setSize(row_major.rows(), row_major.cols(), row_major.nonZeros());
istlA.setBuildMode(Mat::row_wise);
const int* ia = row_major.outerIndexPtr();
const int* ja = row_major.innerIndexPtr();
const typename Mat::CreateIterator endrow = istlA.createend();
for (typename Mat::CreateIterator row = istlA.createbegin(); row != endrow; ++row) {
const int ri = row.index();
for (int i = ia[ri]; i < ia[ri + 1]; ++i) {
row.insert(ja[i]);
}
}
}
/*
// not neeeded since MatrixBlock initially zeros all elements during construction
// Set all blocks to zero.
for (auto row = istlA.begin(), rowend = istlA.end(); row != rowend; ++row ) {
for (auto col = row->begin(), colend = row->end(); col != colend; ++col ) {
*col = 0.0;
}
}
*/
/**
* Go through all jacobians, and insert in correct spot
*
* The straight forward way to do this would be to run through each
* element in the output matrix, and set all block entries by gathering
* from all "input matrices" (derivatives).
*
* A faster alternative is to instead run through each "input matrix" and
* insert its elements in the correct spot in the output matrix.
*
*/
for (int p1 = 0; p1 < np; ++p1) {
for (int p2 = 0; p2 < np; ++p2) {
// Note that that since these are CSC and not CSR matrices,
// ja contains row numbers instead of column numbers.
const AutoDiffMatrix::SparseRep& s = eqs[p1].derivative()[p2].getSparse();
const int* ia = s.outerIndexPtr();
const int* ja = s.innerIndexPtr();
const double* sa = s.valuePtr();
for (int col = 0; col < size; ++col) {
for (int elem_ix = ia[col]; elem_ix < ia[col + 1]; ++elem_ix) {
const int row = ja[elem_ix];
istlA[row][col][p1][p2] = sa[elem_ix];
}
}
}
}
}
/// Solve the linear system Ax = b, with A being the
/// combined derivative matrix of the residual and b
/// being the residual itself.
/// \param[in] residual residual object containing A and b.
/// \return the solution x
SolutionVector computeNewtonIncrement(const LinearisedBlackoilResidual& residual) const
{
typedef LinearisedBlackoilResidual::ADB ADB;
typedef ADB::V V;
// Build the vector of equations.
//const int np = residual.material_balance_eq.size();
assert( np == int(residual.material_balance_eq.size()) );
std::vector<ADB> eqs;
eqs.reserve(np + 2);
for (int phase = 0; phase < np; ++phase) {
eqs.push_back(residual.material_balance_eq[phase]);
}
// check if wells are present
const bool hasWells = residual.well_flux_eq.size() > 0 ;
std::vector<ADB> elim_eqs;
if( hasWells )
{
eqs.push_back(residual.well_flux_eq);
eqs.push_back(residual.well_eq);
// Eliminate the well-related unknowns, and corresponding equations.
elim_eqs.reserve(2);
elim_eqs.push_back(eqs[np]);
eqs = eliminateVariable(eqs, np); // Eliminate well flux unknowns.
elim_eqs.push_back(eqs[np]);
eqs = eliminateVariable(eqs, np); // Eliminate well bhp unknowns.
assert(int(eqs.size()) == np);
}
// Scale material balance equations.
for (int phase = 0; phase < np; ++phase) {
eqs[phase] = eqs[phase] * residual.matbalscale[phase];
}
// calculating the size for b
int size_b = 0;
for (int elem = 0; elem < np; ++elem) {
const int loc_size = eqs[elem].size();
size_b += loc_size;
}
V b(size_b);
int pos = 0;
for (int elem = 0; elem < np; ++elem) {
const int loc_size = eqs[elem].size();
b.segment(pos, loc_size) = eqs[elem].value();
pos += loc_size;
}
assert(pos == size_b);
// Create ISTL matrix with interleaved rows and columns (block structured).
Mat istlA;
formInterleavedSystem(eqs, istlA);
// Solve reduced system.
SolutionVector dx(SolutionVector::Zero(b.size()));
// Right hand side.
const int size = istlA.N();
Vector istlb(size);
for (int i = 0; i < size; ++i) {
for( int p = 0, idx = i; p<np; ++p, idx += size ) {
istlb[i][p] = b(idx);
}
}
// System solution
Vector x(istlA.M());
x = 0.0;
// solve linear system using ISTL methods
istlSolver_.solve( istlA, x, istlb );
// Copy solver output to dx.
for (int i = 0; i < size; ++i) {
for( int p=0, idx = i; p<np; ++p, idx += size ) {
dx(idx) = x[i][p];
}
}
if ( hasWells ) {
// Compute full solution using the eliminated equations.
// Recovery in inverse order of elimination.
dx = recoverVariable(elim_eqs[1], dx, np);
dx = recoverVariable(elim_eqs[0], dx, np);
}
return dx;
}
protected:
ISTLSolverType istlSolver_;
NewtonIterationBlackoilInterleavedParameters parameters_;
}; // end NewtonIterationBlackoilInterleavedImpl
/// Construct a system solver.
NewtonIterationBlackoilInterleaved::NewtonIterationBlackoilInterleaved(const ParameterGroup& param,
const boost::any& parallelInformation_arg)
: newtonIncrementDoublePrecision_(),
newtonIncrementSinglePrecision_(),
parameters_( param ),
parallelInformation_(parallelInformation_arg),
iterations_( 0 )
{
}
namespace detail {
template< int NP, class Scalar >
struct NewtonIncrement
{
template <class NewtonIncVector>
static const NewtonIterationBlackoilInterface&
get( NewtonIncVector& newtonIncrements,
const NewtonIterationBlackoilInterleavedParameters& param,
const boost::any& parallelInformation,
const int np )
{
if( np == NP )
{
assert( np < int(newtonIncrements.size()) );
// create NewtonIncrement with fixed np
if( ! newtonIncrements[ NP ] )
newtonIncrements[ NP ].reset( new NewtonIterationBlackoilInterleavedImpl< NP, Scalar >( param, parallelInformation ) );
return *(newtonIncrements[ NP ]);
}
else
{
return NewtonIncrement< NP-1, Scalar >::get(newtonIncrements, param, parallelInformation, np );
}
}
};
template<class Scalar>
struct NewtonIncrement< 0, Scalar >
{
template <class NewtonIncVector>
static const NewtonIterationBlackoilInterface&
get( NewtonIncVector&,
const NewtonIterationBlackoilInterleavedParameters&,
const boost::any&,
const int np )
{
OPM_THROW(std::runtime_error,"NewtonIncrement::get: number of variables not supported yet. Adjust maxNumberEquations appropriately to cover np = " << np);
}
};
std::pair<NewtonIterationBlackoilInterleaved::SolutionVector, Dune::InverseOperatorResult>
computePressureIncrement(const LinearisedBlackoilResidual& residual)
{
typedef LinearisedBlackoilResidual::ADB ADB;
// Build the vector of equations (should be just a single material balance equation
// in which the pressure equation is stored).
const int np = residual.material_balance_eq.size();
assert(np == 1);
std::vector<ADB> eqs;
eqs.reserve(np + 2);
for (int phase = 0; phase < np; ++phase) {
eqs.push_back(residual.material_balance_eq[phase]);
}
// Check if wells are present.
const bool hasWells = residual.well_flux_eq.size() > 0 ;
std::vector<ADB> elim_eqs;
if (hasWells) {
// Eliminate the well-related unknowns, and corresponding equations.
eqs.push_back(residual.well_flux_eq);
eqs.push_back(residual.well_eq);
elim_eqs.reserve(2);
elim_eqs.push_back(eqs[np]);
eqs = eliminateVariable(eqs, np); // Eliminate well flux unknowns.
elim_eqs.push_back(eqs[np]);
eqs = eliminateVariable(eqs, np); // Eliminate well bhp unknowns.
assert(int(eqs.size()) == np);
}
// Solve the linearised oil equation.
Eigen::SparseMatrix<double, Eigen::RowMajor> eigenA = eqs[0].derivative()[0].getSparse();
DuneMatrix opA(eigenA);
const int size = eqs[0].size();
typedef Dune::BlockVector<Dune::FieldVector<double, 1> > Vector1;
Vector1 x;
x.resize(size);
x = 0.0;
Vector1 b;
b.resize(size);
b = 0.0;
std::copy_n(eqs[0].value().data(), size, b.begin());
// Solve with AMG solver.
typedef Dune::BCRSMatrix<Dune::FieldMatrix<double, 1, 1> > Mat;
typedef Dune::MatrixAdapter<Mat, Vector1, Vector1> Operator;
Operator sOpA(opA);
typedef Dune::Amg::SequentialInformation ParallelInformation;
typedef Dune::SeqILU0<Mat,Vector1,Vector1> EllipticPreconditioner;
typedef EllipticPreconditioner Smoother;
typedef Dune::Amg::AMG<Operator, Vector1, Smoother, ParallelInformation> AMG;
typedef Dune::Amg::FirstDiagonal CouplingMetric;
typedef Dune::Amg::SymmetricCriterion<Mat, CouplingMetric> CritBase;
typedef Dune::Amg::CoarsenCriterion<CritBase> Criterion;
// TODO: revise choice of parameters
const int coarsenTarget = 1200;
Criterion criterion(15, coarsenTarget);
criterion.setDebugLevel(0); // no debug information, 1 for printing hierarchy information
criterion.setDefaultValuesIsotropic(2);
criterion.setNoPostSmoothSteps(1);
criterion.setNoPreSmoothSteps(1);
// for DUNE 2.2 we also need to pass the smoother args
typedef typename AMG::Smoother Smoother;
typedef typename Dune::Amg::SmootherTraits<Smoother>::Arguments SmootherArgs;
SmootherArgs smootherArgs;
smootherArgs.iterations = 1;
smootherArgs.relaxationFactor = 1.0;
AMG precond(sOpA, criterion, smootherArgs);
const int verbosity = 0;
const int maxit = 30;
const double tolerance = 1e-5;
// Construct linear solver.
Dune::BiCGSTABSolver<Vector1> linsolve(sOpA, precond, tolerance, maxit, verbosity);
// Solve system.
Dune::InverseOperatorResult result;
linsolve.apply(x, b, result);
// Check for failure of linear solver.
if (!result.converged) {
const std::string msg("Convergence failure for linear solver in computePressureIncrement().");
OpmLog::problem(msg);
OPM_THROW_NOLOG(LinearSolverProblem, msg);
}
// Copy solver output to dx.
NewtonIterationBlackoilInterleaved::SolutionVector dx(size);
for (int i = 0; i < size; ++i) {
dx(i) = x[i];
}
if (hasWells) {
// Compute full solution using the eliminated equations.
// Recovery in inverse order of elimination.
dx = recoverVariable(elim_eqs[1], dx, np);
dx = recoverVariable(elim_eqs[0], dx, np);
}
return std::make_pair(dx, result);
}
} // end namespace detail
NewtonIterationBlackoilInterleaved::SolutionVector
NewtonIterationBlackoilInterleaved::computeNewtonIncrement(const LinearisedBlackoilResidual& residual) const
{
// get np and call appropriate template method
const int np = residual.material_balance_eq.size();
if (np == 1) {
auto result = detail::computePressureIncrement(residual);
iterations_ = result.second.iterations;
return result.first;
}
const NewtonIterationBlackoilInterface& newtonIncrement = residual.singlePrecision ?
detail::NewtonIncrement< maxNumberEquations_, float > :: get( newtonIncrementSinglePrecision_, parameters_, parallelInformation_, np ) :
detail::NewtonIncrement< maxNumberEquations_, double > :: get( newtonIncrementDoublePrecision_, parameters_, parallelInformation_, np );
// compute newton increment
SolutionVector dx = newtonIncrement.computeNewtonIncrement( residual );
// get number of linear iterations
iterations_ = newtonIncrement.iterations();
return dx;
}
const boost::any& NewtonIterationBlackoilInterleaved::parallelInformation() const
{
return parallelInformation_;
}
} // namespace Opm

View File

@ -1,85 +0,0 @@
/*
Copyright 2015 SINTEF ICT, Applied Mathematics.
Copyright 2015 IRIS AS
Copyright 2015 Dr. Blatt - HPC-Simulation-Software & Services
Copyright 2015 NTNU
Copyright 2015 Statoil AS
Copyright 2015 IRIS AS
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 3 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 <http://www.gnu.org/licenses/>.
*/
#ifndef OPM_NEWTONITERATIONBLACKOILINTERLEAVED_HEADER_INCLUDED
#define OPM_NEWTONITERATIONBLACKOILINTERLEAVED_HEADER_INCLUDED
#include <opm/autodiff/CPRPreconditioner.hpp>
#include <opm/autodiff/NewtonIterationBlackoilInterface.hpp>
#include <opm/common/utility/parameters/ParameterGroup.hpp>
#include <opm/autodiff/ParallelOverlappingILU0.hpp>
#include <opm/autodiff/FlowLinearSolverParameters.hpp>
#include <ewoms/common/parametersystem.hh>
#include <array>
#include <memory>
namespace Opm
{
using NewtonIterationBlackoilInterleavedParameters = FlowLinearSolverParameters;
/// This class solves the fully implicit black-oil system by
/// solving the reduced system (after eliminating well variables)
/// as a block-structured matrix (one block for all cell variables).
class NewtonIterationBlackoilInterleaved : public NewtonIterationBlackoilInterface
{
public:
/// Construct a system solver.
/// \param[in] param parameters controlling the behaviour of the linear solvers
/// \param[in] parallelInformation In the case of a parallel run
/// with dune-istl the information about the parallelization.
NewtonIterationBlackoilInterleaved(const ParameterGroup& param,
const boost::any& parallelInformation=boost::any());
/// Solve the system of linear equations Ax = b, with A being the
/// combined derivative matrix of the residual and b
/// being the residual itself.
/// \param[in] residual residual object containing A and b.
/// \return the solution x
virtual SolutionVector computeNewtonIncrement(const LinearisedBlackoilResidual& residual) const;
/// \copydoc NewtonIterationBlackoilInterface::iterations
virtual int iterations () const { return iterations_; }
/// \copydoc NewtonIterationBlackoilInterface::parallelInformation
virtual const boost::any& parallelInformation() const;
private:
// max number of equations supported, increase if necessary
static const int maxNumberEquations_ = 6 ;
mutable std::array< std::unique_ptr< NewtonIterationBlackoilInterface >, maxNumberEquations_+1 > newtonIncrementDoublePrecision_;
mutable std::array< std::unique_ptr< NewtonIterationBlackoilInterface >, maxNumberEquations_+1 > newtonIncrementSinglePrecision_;
NewtonIterationBlackoilInterleavedParameters parameters_;
boost::any parallelInformation_;
mutable int iterations_;
};
} // namespace Opm
#endif // OPM_NEWTONITERATIONBLACKOILINTERLEAVED_HEADER_INCLUDED

View File

@ -1,84 +0,0 @@
/*
Copyright 2014 SINTEF ICT, Applied Mathematics.
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 3 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 <http://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <opm/autodiff/NewtonIterationBlackoilSimple.hpp>
#include <opm/autodiff/AutoDiffHelpers.hpp>
#include <opm/common/ErrorMacros.hpp>
#include <opm/common/Exceptions.hpp>
#include <opm/core/linalg/LinearSolverFactory.hpp>
namespace Opm
{
/// Construct a system solver.
/// \param[in] linsolver linear solver to use
/// \param[in] parallelInformation In the case of a parallel run
/// with dune-istl the information about the parallelization.
NewtonIterationBlackoilSimple::NewtonIterationBlackoilSimple(const ParameterGroup& param,
const boost::any& parallelInformation_arg)
: iterations_( 0 ), parallelInformation_(parallelInformation_arg)
{
linsolver_.reset(new LinearSolverFactory(param));
}
/// Solve the linear system Ax = b, with A being the
/// combined derivative matrix of the residual and b
/// being the residual itself.
/// \param[in] residual residual object containing A and b.
/// \return the solution x
NewtonIterationBlackoilSimple::SolutionVector
NewtonIterationBlackoilSimple::computeNewtonIncrement(const LinearisedBlackoilResidual& residual) const
{
typedef LinearisedBlackoilResidual::ADB ADB;
const int np = residual.material_balance_eq.size();
ADB mass_res = residual.material_balance_eq[0];
for (int phase = 1; phase < np; ++phase) {
mass_res = vertcat(mass_res, residual.material_balance_eq[phase]);
}
const ADB well_res = vertcat(residual.well_flux_eq, residual.well_eq);
const ADB total_residual = collapseJacs(vertcat(mass_res, well_res));
Eigen::SparseMatrix<double, Eigen::RowMajor> matr;
total_residual.derivative()[0].toSparse(matr);
SolutionVector dx(SolutionVector::Zero(total_residual.size()));
Opm::LinearSolverInterface::LinearSolverReport rep
= linsolver_->solve(matr.rows(), matr.nonZeros(),
matr.outerIndexPtr(), matr.innerIndexPtr(), matr.valuePtr(),
total_residual.value().data(), dx.data(), parallelInformation_);
// store iterations
iterations_ = rep.iterations;
if (!rep.converged) {
OPM_THROW(LinearSolverProblem,
"FullyImplicitBlackoilSolver::solveJacobianSystem(): "
"Linear solver convergence failure.");
}
return dx;
}
const boost::any& NewtonIterationBlackoilSimple::parallelInformation() const
{
return parallelInformation_;
}
} // namespace Opm

View File

@ -1,70 +0,0 @@
/*
Copyright 2014 SINTEF ICT, Applied Mathematics.
Copyright 2014 IRIS AS
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 3 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 <http://www.gnu.org/licenses/>.
*/
#ifndef OPM_NEWTONITERATIONBLACKOILSIMPLE_HEADER_INCLUDED
#define OPM_NEWTONITERATIONBLACKOILSIMPLE_HEADER_INCLUDED
#include <opm/autodiff/NewtonIterationBlackoilInterface.hpp>
#include <opm/common/utility/parameters/ParameterGroup.hpp>
#include <opm/core/linalg/LinearSolverInterface.hpp>
#include <memory>
namespace Opm
{
/// This class solves the fully implicit black-oil system by
/// simply concatenating the Jacobian matrices and passing the
/// resulting system to a linear solver. The linear solver used
/// can be passed in as a constructor argument.
class NewtonIterationBlackoilSimple : public NewtonIterationBlackoilInterface
{
public:
/// Construct a system solver.
/// \param[in] param parameters controlling the behaviour and
/// choice of linear solver.
/// \param[in] parallelInformation In the case of a parallel run
/// with dune-istl the information about the parallelization.
NewtonIterationBlackoilSimple(const ParameterGroup& param,
const boost::any& parallelInformation=boost::any());
/// Solve the system of linear equations Ax = b, with A being the
/// combined derivative matrix of the residual and b
/// being the residual itself.
/// \param[in] residual residual object containing A and b.
/// \return the solution x
virtual SolutionVector computeNewtonIncrement(const LinearisedBlackoilResidual& residual) const;
/// \copydoc NewtonIterationBlackoilInterface::iterations
virtual int iterations () const { return iterations_; }
/// \copydoc NewtonIterationBlackoilInterface::parallelInformation
virtual const boost::any& parallelInformation() const;
private:
std::unique_ptr<LinearSolverInterface> linsolver_;
mutable int iterations_;
boost::any parallelInformation_;
};
} // namespace Opm
#endif // OPM_NEWTONITERATIONBLACKOILSIMPLE_HEADER_INCLUDED

View File

@ -1,291 +0,0 @@
/*
Copyright 2014 SINTEF ICT, Applied Mathematics.
Copyright 2014 IRIS AS
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 3 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 <http://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <opm/autodiff/NewtonIterationUtilities.hpp>
#include <opm/autodiff/AutoDiffHelpers.hpp>
#include <opm/common/ErrorMacros.hpp>
#include <opm/common/utility/platform_dependent/disable_warnings.h>
#if HAVE_UMFPACK
#include <Eigen/UmfPackSupport>
#else
#include <Eigen/SparseLU>
#endif
#include <opm/common/utility/platform_dependent/reenable_warnings.h>
namespace Opm
{
typedef AutoDiffBlock<double> ADB;
typedef ADB::V V;
typedef ADB::M M;
typedef Eigen::SparseMatrix<double> S;
std::vector<ADB> eliminateVariable(const std::vector<ADB>& eqs, const int n)
{
// Check that the variable index to eliminate is within bounds.
const int num_eq = eqs.size();
const int num_vars = eqs[0].derivative().size();
if (num_eq != num_vars) {
OPM_THROW(std::logic_error, "eliminateVariable() requires the same number of variables and equations.");
}
if (n >= num_eq) {
OPM_THROW(std::logic_error, "Trying to eliminate variable from too small set of equations.");
}
// Schur complement of (A B ; C D) wrt. D is A - B*inv(D)*C.
// This is applied to all 2x2 block submatrices
// The right hand side is modified accordingly. bi = bi - B * inv(D)* bn;
// We do not explicitly compute inv(D) instead Du = C is solved
// Extract the submatrix
const std::vector<M>& Jn = eqs[n].derivative();
// Use sparse LU to solve the block submatrices i.e compute inv(D)
typedef Eigen::SparseMatrix<double> Sp;
Sp Jnn;
Jn[n].toSparse(Jnn);
#if HAVE_UMFPACK
const Eigen::UmfPackLU<Sp> solver(Jnn);
#else
const Eigen::SparseLU<Sp> solver(Jnn);
#endif
Sp id(Jn[n].rows(), Jn[n].cols());
id.setIdentity();
const Sp Di = solver.solve(id);
// compute inv(D)*bn for the update of the right hand side
// Note: Eigen version > 3.2 requires a non-const reference to solve.
ADB::V eqs_n_v = eqs[n].value();
const Eigen::VectorXd& Dibn = solver.solve(eqs_n_v.matrix());
std::vector<V> vals(num_eq); // Number n will remain empty.
std::vector<std::vector<M>> jacs(num_eq); // Number n will remain empty.
for (int eq = 0; eq < num_eq; ++eq) {
jacs[eq].reserve(num_eq - 1);
const std::vector<M>& Je = eqs[eq].derivative();
const M& B = Je[n];
// Update right hand side.
vals[eq] = eqs[eq].value().matrix() - B * Dibn;
}
for (int var = 0; var < num_eq; ++var) {
if (var == n) {
continue;
}
// solve Du = C
// const M u = Di * Jn[var]; // solver.solve(Jn[var]);
M u;
fastSparseProduct(Di, Jn[var], u); // solver.solve(Jn[var]);
for (int eq = 0; eq < num_eq; ++eq) {
if (eq == n) {
continue;
}
const std::vector<M>& Je = eqs[eq].derivative();
const M& B = Je[n];
// Create new jacobians.
// Add A
jacs[eq].push_back(Je[var]);
M& J = jacs[eq].back();
// Subtract Bu (B*inv(D)*C)
M Bu;
fastSparseProduct(B, u, Bu);
J = J + (Bu * -1.0);
}
}
// Create return value.
std::vector<ADB> retval;
retval.reserve(num_eq - 1);
for (int eq = 0; eq < num_eq; ++eq) {
if (eq == n) {
continue;
}
retval.push_back(ADB::function(std::move(vals[eq]), std::move(jacs[eq])));
}
return retval;
}
V recoverVariable(const ADB& equation, const V& partial_solution, const int n)
{
// The equation to solve for the unknown y (to be recovered) is
// Cx + Dy = b
// Dy = (b - Cx)
// where D is the eliminated block, C is the jacobian of
// the eliminated equation with respect to the
// non-eliminated unknowms, b is the right-hand side of
// the eliminated equation, and x is the partial solution
// of the non-eliminated unknowns.
const M& D1 = equation.derivative()[n];
// Build C.
std::vector<M> C_jacs = equation.derivative();
C_jacs.erase(C_jacs.begin() + n);
V equation_value = equation.value();
ADB eq_coll = collapseJacs(ADB::function(std::move(equation_value), std::move(C_jacs)));
const M& C = eq_coll.derivative()[0];
// Use sparse LU to solve the block submatrices
typedef Eigen::SparseMatrix<double> Sp;
Sp D;
D1.toSparse(D);
#if HAVE_UMFPACK
const Eigen::UmfPackLU<Sp> solver(D);
#else
const Eigen::SparseLU<Sp> solver(D);
#endif
// Compute value of eliminated variable.
const Eigen::VectorXd b = (equation.value().matrix() - C * partial_solution.matrix());
const Eigen::VectorXd elim_var = solver.solve(b);
// Find the relevant sizes to use when reconstructing the full solution.
const int nelim = equation.size();
const int npart = partial_solution.size();
assert(C.cols() == npart);
const int full_size = nelim + npart;
int start = 0;
for (int i = 0; i < n; ++i) {
start += equation.derivative()[i].cols();
}
assert(start < full_size);
// Reconstruct complete solution vector.
V sol(full_size);
std::copy_n(partial_solution.data(), start, sol.data());
std::copy_n(elim_var.data(), nelim, sol.data() + start);
std::copy_n(partial_solution.data() + start, npart - start, sol.data() + start + nelim);
return sol;
}
/// Form an elliptic system of equations.
/// \param[in] num_phases the number of fluid phases
/// \param[in] eqs the equations
/// \param[out] A the resulting full system matrix
/// \param[out] b the right hand side
/// This function will deal with the first num_phases
/// equations in eqs, and return a matrix A for the full
/// system that has a elliptic upper left corner, if possible.
void formEllipticSystem(const int num_phases,
const std::vector<ADB>& eqs_in,
Eigen::SparseMatrix<double, Eigen::RowMajor>& A,
V& b)
{
if (num_phases != 3) {
OPM_THROW(std::logic_error, "formEllipticSystem() requires 3 phases.");
}
// A concession to MRST, to obtain more similar behaviour:
// swap the first two equations, so that oil is first, then water.
auto eqs = eqs_in;
eqs[0].swap(eqs[1]);
// Characterize the material balance equations.
const int n = eqs[0].size();
const double ratio_limit = 0.01;
typedef Eigen::Array<double, Eigen::Dynamic, Eigen::Dynamic> Block;
// The l1 block indicates if the equation for a given cell and phase is
// sufficiently strong on the diagonal.
Block l1 = Block::Zero(n, num_phases);
{
S J;
for (int phase = 0; phase < num_phases; ++phase) {
eqs[phase].derivative()[0].toSparse(J);
V dj = J.diagonal().cwiseAbs();
V sod = V::Zero(n);
for (int elem = 0; elem < n; ++elem) {
sod(elem) = J.col(elem).cwiseAbs().sum() - dj(elem);
}
l1.col(phase) = (dj/sod > ratio_limit).cast<double>();
}
}
// By default, replace first equation with sum of all phase equations.
// Build helper vectors.
V l21 = V::Zero(n);
V l22 = V::Ones(n);
V l31 = V::Zero(n);
V l33 = V::Ones(n);
// If the first phase diagonal is not strong enough, we need further treatment.
// Then the first equation will be the sum of the remaining equations,
// and we swap the first equation into one of their slots.
for (int elem = 0; elem < n; ++elem) {
if (!l1(elem, 0)) {
const double l12x = l1(elem, 1);
const double l13x = l1(elem, 2);
const bool allzero = (l12x + l13x == 0);
if (allzero) {
l1(elem, 0) = 1;
} else {
if (l12x >= l13x) {
l21(elem) = 1;
l22(elem) = 0;
} else {
l31(elem) = 1;
l33(elem) = 0;
}
}
}
}
// Construct the sparse matrix L that does the swaps and sums.
Span i1(n, 1, 0);
Span i2(n, 1, n);
Span i3(n, 1, 2*n);
std::vector< Eigen::Triplet<double> > t;
t.reserve(7*n);
for (int ii = 0; ii < n; ++ii) {
t.emplace_back(i1[ii], i1[ii], l1(ii));
t.emplace_back(i1[ii], i2[ii], l1(ii+n));
t.emplace_back(i1[ii], i3[ii], l1(ii+2*n));
t.emplace_back(i2[ii], i1[ii], l21(ii));
t.emplace_back(i2[ii], i2[ii], l22(ii));
t.emplace_back(i3[ii], i1[ii], l31(ii));
t.emplace_back(i3[ii], i3[ii], l33(ii));
}
S L(3*n, 3*n);
L.setFromTriplets(t.begin(), t.end());
// Combine in single block.
ADB total_residual = vertcatCollapseJacs(eqs);
S derivative;
total_residual.derivative()[0].toSparse(derivative);
// Create output as product of L with equations.
A = L * derivative;
b = L * total_residual.value().matrix();
}
} // namespace Opm

View File

@ -1,62 +0,0 @@
/*
Copyright 2015 SINTEF ICT, Applied Mathematics.
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 3 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 <http://www.gnu.org/licenses/>.
*/
#ifndef OPM_NEWTONITERATIONUTILITIES_HEADER_INCLUDED
#define OPM_NEWTONITERATIONUTILITIES_HEADER_INCLUDED
#include <opm/autodiff/AutoDiffBlock.hpp>
#include <vector>
namespace Opm
{
/// Eliminate a variable via Schur complement.
/// \param[in] eqs set of equations with Jacobians
/// \param[in] n index of equation/variable to eliminate.
/// \return new set of equations, one smaller than eqs.
/// Note: this method requires the eliminated variable to have the same size
/// as the equation in the corresponding position (that also will be eliminated).
std::vector< AutoDiffBlock<double> >
eliminateVariable(const std::vector< AutoDiffBlock<double> >& eqs,
const int n);
/// Recover that value of a variable previously eliminated.
/// \param[in] equation previously eliminated equation.
/// \param[in] partial_solution solution to the remainder system after elimination.
/// \param[in] n index of equation/variable that was eliminated.
/// \return solution to complete system.
AutoDiffBlock<double>::V recoverVariable(const AutoDiffBlock<double>& equation,
const AutoDiffBlock<double>::V& partial_solution,
const int n);
/// Form an elliptic system of equations.
/// \param[in] num_phases the number of fluid phases
/// \param[in] eqs the equations
/// \param[out] A the resulting full system matrix
/// \param[out] b the right hand side
/// This function will deal with the first num_phases
/// equations in eqs, and return a matrix A for the full
/// system that has a elliptic upper left corner, if possible.
void formEllipticSystem(const int num_phases,
const std::vector< AutoDiffBlock<double> >& eqs,
Eigen::SparseMatrix<double, Eigen::RowMajor>& A,
AutoDiffBlock<double>::V& b);
} // namespace Opm
#endif // OPM_NEWTONITERATIONUTILITIES_HEADER_INCLUDED

View File

@ -1,197 +0,0 @@
/*
Copyright 2015 SINTEF ICT, Applied Mathematics.
Copyright 2015 Statoil ASA.
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 3 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 <http://www.gnu.org/licenses/>.
*/
#ifndef OPM_NONLINEARSOLVER_HEADER_INCLUDED
#define OPM_NONLINEARSOLVER_HEADER_INCLUDED
#include <opm/core/simulator/SimulatorReport.hpp>
#include <opm/common/utility/parameters/ParameterGroup.hpp>
#include <opm/simulators/timestepping/SimulatorTimerInterface.hpp>
#include <dune/common/fmatrix.hh>
#include <dune/istl/bcrsmatrix.hh>
#include <memory>
namespace Opm {
/// A nonlinear solver class suitable for general fully-implicit models,
/// as well as pressure, transport and sequential models.
template <class PhysicalModel>
class NonlinearSolver
{
public:
// Available relaxation scheme types.
enum RelaxType { DAMPEN, SOR };
// Solver parameters controlling nonlinear process.
struct SolverParameters
{
enum RelaxType relax_type_;
double relax_max_;
double relax_increment_;
double relax_rel_tol_;
int max_iter_; // max nonlinear iterations
int min_iter_; // min nonlinear iterations
explicit SolverParameters( const ParameterGroup& param );
SolverParameters();
void reset();
};
// Forwarding types from PhysicalModel.
typedef typename PhysicalModel::ReservoirState ReservoirState;
typedef typename PhysicalModel::WellState WellState;
// --------- Public methods ---------
/// Construct solver for a given model.
///
/// The model is a std::unique_ptr because the object to which model points to is
/// not allowed to be deleted as long as the NonlinearSolver object exists.
///
/// \param[in] param parameters controlling nonlinear process
/// \param[in, out] model physical simulation model.
explicit NonlinearSolver(const SolverParameters& param,
std::unique_ptr<PhysicalModel> model);
/// Take a single forward step, after which the states will be modified
/// according to the physical model.
/// \param[in] timer simulation timer
/// \param[in, out] reservoir_state reservoir state variables
/// \param[in, out] well_state well state variables
SimulatorReport
step(const SimulatorTimerInterface& timer,
ReservoirState& reservoir_state,
WellState& well_state);
/// Take a single forward step, after which the states will be modified
/// according to the physical model. This version allows for the
/// states passed as in/out arguments to be different from the initial
/// states.
/// \param[in] timer simulation timer
/// \param[in] initial_reservoir_state reservoir state variables at start of timestep
/// \param[in] initial_well_state well state variables at start of timestep
/// \param[in, out] reservoir_state reservoir state variables
/// \param[in, out] well_state well state variables
/// \return number of linear iterations used
SimulatorReport
step(const SimulatorTimerInterface& timer,
const ReservoirState& initial_reservoir_state,
const WellState& initial_well_state,
ReservoirState& reservoir_state,
WellState& well_state);
/// return the statistics if the step() method failed
const SimulatorReport& failureReport() const
{ return failureReport_; }
/// Number of linearizations used in all calls to step().
int linearizations() const;
/// Number of full nonlinear solver iterations used in all calls to step().
int nonlinearIterations() const;
/// Number of linear solver iterations used in all calls to step().
int linearIterations() const;
/// Number of well iterations used in all calls to step().
int wellIterations() const;
/// Number of nonlinear solver iterations used in the last call to step().
int nonlinearIterationsLastStep() const;
/// Number of linear solver iterations used in the last call to step().
int linearIterationsLastStep() const;
/// Number of well iterations used in all calls to step().
int wellIterationsLastStep() const;
/// Compute fluid in place.
/// \param[in] ReservoirState
/// \param[in] FIPNUM for active cells not global cells.
/// \return fluid in place, number of fip regions, each region contains 5 values which are liquid, vapour, water, free gas and dissolved gas.
std::vector<std::vector<double> >
computeFluidInPlace(const ReservoirState& x, const std::vector<int>& fipnum) const
{
return model_->computeFluidInPlace(x, fipnum);
}
std::vector<std::vector<double> >
computeFluidInPlace(const std::vector<int>& fipnum) const
{
return model_->computeFluidInPlace(fipnum);
}
/// Reference to physical model.
const PhysicalModel& model() const;
/// Mutable reference to physical model.
PhysicalModel& model();
/// Detect oscillation or stagnation in a given residual history.
void detectOscillations(const std::vector<std::vector<double>>& residual_history,
const int it, bool& oscillate, bool& stagnate) const;
/// Apply a stabilization to dx, depending on dxOld and relaxation parameters.
/// Implemention for Dune block vectors.
template <class BVector>
void stabilizeNonlinearUpdate(BVector& dx, BVector& dxOld, const double omega) const;
/// The greatest relaxation factor (i.e. smallest factor) allowed.
double relaxMax() const { return param_.relax_max_; }
/// The step-change size for the relaxation factor.
double relaxIncrement() const { return param_.relax_increment_; }
/// The relaxation type (DAMPEN or SOR).
enum RelaxType relaxType() const { return param_.relax_type_; }
/// The relaxation relative tolerance.
double relaxRelTol() const { return param_.relax_rel_tol_; }
/// The maximum number of nonlinear iterations allowed.
int maxIter() const { return param_.max_iter_; }
/// The minimum number of nonlinear iterations allowed.
int minIter() const { return param_.min_iter_; }
/// Set parameters to override those given at construction time.
void setParameters(const SolverParameters& param) { param_ = param; }
private:
// --------- Data members ---------
SimulatorReport failureReport_;
SolverParameters param_;
std::unique_ptr<PhysicalModel> model_;
int linearizations_;
int nonlinearIterations_;
int linearIterations_;
int wellIterations_;
int nonlinearIterationsLast_;
int linearIterationsLast_;
int wellIterationsLast_;
};
} // namespace Opm
#include "NonlinearSolver_impl.hpp"
#endif // OPM_NONLINEARSOLVER_HEADER_INCLUDED

View File

@ -1,301 +0,0 @@
/*
Copyright 2013, 2015 SINTEF ICT, Applied Mathematics.
Copyright 2015 Dr. Blatt - HPC-Simulation-Software & Services
Copyright 2015 NTNU
Copyright 2015 IRIS AS
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 3 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 <http://www.gnu.org/licenses/>.
*/
#ifndef OPM_NONLINEARSOLVER_IMPL_HEADER_INCLUDED
#define OPM_NONLINEARSOLVER_IMPL_HEADER_INCLUDED
#include <opm/autodiff/NonlinearSolver.hpp>
#include <opm/common/Exceptions.hpp>
#include <opm/common/ErrorMacros.hpp>
namespace Opm
{
template <class PhysicalModel>
NonlinearSolver<PhysicalModel>::NonlinearSolver(const SolverParameters& param,
std::unique_ptr<PhysicalModel> model_arg)
: param_(param),
model_(std::move(model_arg)),
linearizations_(0),
nonlinearIterations_(0),
linearIterations_(0),
wellIterations_(0),
nonlinearIterationsLast_(0),
linearIterationsLast_(0),
wellIterationsLast_(0)
{
if (!model_) {
OPM_THROW(std::logic_error, "Must provide a non-null model argument for NonlinearSolver.");
}
}
template <class PhysicalModel>
int NonlinearSolver<PhysicalModel>::linearizations() const
{
return linearizations_;
}
template <class PhysicalModel>
int NonlinearSolver<PhysicalModel>::nonlinearIterations() const
{
return nonlinearIterations_;
}
template <class PhysicalModel>
int NonlinearSolver<PhysicalModel>::linearIterations() const
{
return linearIterations_;
}
template <class PhysicalModel>
int NonlinearSolver<PhysicalModel>::wellIterations() const
{
return wellIterations_;
}
template <class PhysicalModel>
const PhysicalModel& NonlinearSolver<PhysicalModel>::model() const
{
return *model_;
}
template <class PhysicalModel>
PhysicalModel& NonlinearSolver<PhysicalModel>::model()
{
return *model_;
}
template <class PhysicalModel>
int NonlinearSolver<PhysicalModel>::nonlinearIterationsLastStep() const
{
return nonlinearIterationsLast_;
}
template <class PhysicalModel>
int NonlinearSolver<PhysicalModel>::linearIterationsLastStep() const
{
return linearIterationsLast_;
}
template <class PhysicalModel>
int NonlinearSolver<PhysicalModel>::wellIterationsLastStep() const
{
return wellIterationsLast_;
}
template <class PhysicalModel>
SimulatorReport
NonlinearSolver<PhysicalModel>::
step(const SimulatorTimerInterface& timer,
ReservoirState& reservoir_state,
WellState& well_state)
{
return step(timer, reservoir_state, well_state, reservoir_state, well_state);
}
template <class PhysicalModel>
SimulatorReport
NonlinearSolver<PhysicalModel>::
step(const SimulatorTimerInterface& timer,
const ReservoirState& initial_reservoir_state,
const WellState& initial_well_state,
ReservoirState& reservoir_state,
WellState& well_state)
{
SimulatorReport iterReport;
SimulatorReport report;
failureReport_ = SimulatorReport();
// Do model-specific once-per-step calculations.
model_->prepareStep(timer, initial_reservoir_state, initial_well_state);
int iteration = 0;
// Let the model do one nonlinear iteration.
// Set up for main solver loop.
bool converged = false;
// ---------- Main nonlinear solver loop ----------
do {
try {
// Do the nonlinear step. If we are in a converged state, the
// model will usually do an early return without an expensive
// solve, unless the minIter() count has not been reached yet.
iterReport = model_->nonlinearIteration(iteration, timer, *this, reservoir_state, well_state);
report += iterReport;
report.converged = iterReport.converged;
converged = report.converged;
iteration += 1;
}
catch (...) {
// if an iteration fails during a time step, all previous iterations
// count as a failure as well
failureReport_ += report;
failureReport_ += model_->failureReport();
throw;
}
} while ( (!converged && (iteration <= maxIter())) || (iteration <= minIter()));
if (!converged) {
failureReport_ += report;
std::string msg = "Solver convergence failure - Failed to complete a time step within " + std::to_string(maxIter()) + " iterations.";
OPM_THROW_NOLOG(Opm::TooManyIterations, msg);
}
// Do model-specific post-step actions.
model_->afterStep(timer, reservoir_state, well_state);
report.converged = true;
return report;
}
template <class PhysicalModel>
void NonlinearSolver<PhysicalModel>::SolverParameters::
reset()
{
// default values for the solver parameters
relax_type_ = DAMPEN;
relax_max_ = 0.5;
relax_increment_ = 0.1;
relax_rel_tol_ = 0.2;
max_iter_ = 10;
min_iter_ = 1;
}
template <class PhysicalModel>
NonlinearSolver<PhysicalModel>::SolverParameters::
SolverParameters()
{
// set default values
reset();
}
template <class PhysicalModel>
NonlinearSolver<PhysicalModel>::SolverParameters::
SolverParameters( const ParameterGroup& param )
{
// set default values
reset();
// overload with given parameters
relax_max_ = param.getDefault("relax_max", relax_max_);
max_iter_ = param.getDefault("max_iter", max_iter_);
min_iter_ = param.getDefault("min_iter", min_iter_);
std::string relaxation_type = param.getDefault("relax_type", std::string("dampen"));
if (relaxation_type == "dampen") {
relax_type_ = DAMPEN;
} else if (relaxation_type == "sor") {
relax_type_ = SOR;
} else {
OPM_THROW(std::runtime_error, "Unknown Relaxtion Type " << relaxation_type);
}
}
template <class PhysicalModel>
void
NonlinearSolver<PhysicalModel>::detectOscillations(const std::vector<std::vector<double>>& residual_history,
const int it,
bool& oscillate, bool& stagnate) const
{
// The detection of oscillation in two primary variable results in the report of the detection
// of oscillation for the solver.
// Only the saturations are used for oscillation detection for the black oil model.
// Stagnate is not used for any treatment here.
if ( it < 2 ) {
oscillate = false;
stagnate = false;
return;
}
stagnate = true;
int oscillatePhase = 0;
const std::vector<double>& F0 = residual_history[it];
const std::vector<double>& F1 = residual_history[it - 1];
const std::vector<double>& F2 = residual_history[it - 2];
for (int p= 0; p < model_->numPhases(); ++p){
const double d1 = std::abs((F0[p] - F2[p]) / F0[p]);
const double d2 = std::abs((F0[p] - F1[p]) / F0[p]);
oscillatePhase += (d1 < relaxRelTol()) && (relaxRelTol() < d2);
// Process is 'stagnate' unless at least one phase
// exhibits significant residual change.
stagnate = (stagnate && !(std::abs((F1[p] - F2[p]) / F2[p]) > 1.0e-3));
}
oscillate = (oscillatePhase > 1);
}
template <class PhysicalModel>
template <class BVector>
void
NonlinearSolver<PhysicalModel>::stabilizeNonlinearUpdate(BVector& dx, BVector& dxOld, const double omega) const
{
// The dxOld is updated with dx.
// If omega is equal to 1., no relaxtion will be appiled.
BVector tempDxOld = dxOld;
dxOld = dx;
switch (relaxType()) {
case DAMPEN: {
if (omega == 1.) {
return;
}
auto i = dx.size();
for (i = 0; i < dx.size(); ++i) {
dx[i] *= omega;
}
return;
}
case SOR: {
if (omega == 1.) {
return;
}
auto i = dx.size();
for (i = 0; i < dx.size(); ++i) {
dx[i] *= omega;
tempDxOld[i] *= (1.-omega);
dx[i] += tempDxOld[i];
}
return;
}
default:
OPM_THROW(std::runtime_error, "Can only handle DAMPEN and SOR relaxation type.");
}
return;
}
} // namespace Opm
#endif // OPM_FULLYIMPLICITSOLVER_IMPL_HEADER_INCLUDED

View File

@ -1,702 +0,0 @@
/*
Copyright 2015 IRIS AS
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 3 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 <http://www.gnu.org/licenses/>.
*/
#ifndef OPM_PARALLELDEBUGOUTPUT_HEADER_INCLUDED
#define OPM_PARALLELDEBUGOUTPUT_HEADER_INCLUDED
#include <unordered_set>
#include <opm/common/data/SimulationDataContainer.hpp>
#include <opm/output/eclipse/RestartValue.hpp>
#include <opm/grid/UnstructuredGrid.h>
#include <opm/core/simulator/WellState.hpp>
#include <opm/core/wells/WellsManager.hpp>
#include <opm/autodiff/WellStateFullyImplicitBlackoil.hpp>
#include <opm/autodiff/WellStateFullyImplicitBlackoil.hpp>
#include <opm/output/eclipse/RestartValue.hpp>
#include <opm/autodiff/Compat.hpp>
#include <opm/core/wells/DynamicListEconLimited.hpp>
#if HAVE_OPM_GRID
#include <opm/grid/common/p2pcommunicator.hh>
#endif
namespace Opm
{
class ParallelDebugOutputInterface
{
protected:
ParallelDebugOutputInterface () {}
public:
virtual ~ParallelDebugOutputInterface() {}
//! \brief gather solution to rank 0 for EclipseWriter
//! \param localReservoirState The reservoir state
//! \param localWellState The well state
//! \param localCellData The cell data used for eclipse output
//! (needs to include the cell data of
//! localReservoirState)
//! \param wellStateStepNumber The step number of the well state.
virtual bool collectToIORank( const SimulationDataContainer& localReservoirState,
const WellStateFullyImplicitBlackoil& localWellState,
const data::Solution& localCellData,
const int wellStateStepNumber ) = 0;
virtual const SimulationDataContainer& globalReservoirState() const = 0 ;
virtual const data::Solution& globalCellData() const = 0 ;
virtual const WellStateFullyImplicitBlackoil& globalWellState() const = 0 ;
virtual bool isIORank() const = 0;
virtual bool isParallel() const = 0;
virtual int numCells() const = 0 ;
virtual const int* globalCell() const = 0;
};
template <class GridImpl>
class ParallelDebugOutput : public ParallelDebugOutputInterface
{
protected:
const GridImpl& grid_;
const SimulationDataContainer* globalState_;
const WellStateFullyImplicitBlackoil* wellState_;
const data::Solution* globalCellData_;
public:
ParallelDebugOutput ( const GridImpl& grid,
const EclipseState& /* eclipseState */,
const Schedule&,
const int,
const Opm::PhaseUsage& )
: grid_( grid ) {}
// gather solution to rank 0 for EclipseWriter
virtual bool collectToIORank( const SimulationDataContainer& localReservoirState,
const WellStateFullyImplicitBlackoil& localWellState,
const data::Solution& localCellData,
const int /* wellStateStepNumber */)
{
globalState_ = &localReservoirState;
wellState_ = &localWellState;
globalCellData_ = &localCellData;
return true ;
}
virtual const SimulationDataContainer& globalReservoirState() const { return *globalState_; }
virtual const data::Solution& globalCellData() const
{
return *globalCellData_;
}
virtual const WellStateFullyImplicitBlackoil& globalWellState() const { return *wellState_; }
virtual bool isIORank () const { return true; }
virtual bool isParallel () const { return false; }
virtual int numCells() const { return Opm::AutoDiffGrid::numCells(grid_); }
virtual const int* globalCell() const { return Opm::AutoDiffGrid::globalCell(grid_); }
};
#if HAVE_OPM_GRID
template <>
class ParallelDebugOutput< Dune::CpGrid> : public ParallelDebugOutputInterface
{
public:
typedef Dune::CpGrid Grid;
typedef typename Grid :: CollectiveCommunication CollectiveCommunication;
// global id
class GlobalCellIndex
{
int globalId_;
int localIndex_;
bool isInterior_;
public:
GlobalCellIndex() : globalId_(-1), localIndex_(-1), isInterior_(true) {}
void setGhost() { isInterior_ = false; }
void setId( const int globalId ) { globalId_ = globalId; }
void setIndex( const int localIndex ) { localIndex_ = localIndex; }
int localIndex () const { return localIndex_; }
int id () const { return globalId_; }
bool isInterior() const { return isInterior_; }
};
typedef typename Dune::PersistentContainer< Grid, GlobalCellIndex > GlobalIndexContainer;
static const int dimension = Grid :: dimension ;
typedef typename Grid :: LeafGridView GridView;
typedef GridView AllGridView;
typedef Dune :: Point2PointCommunicator< Dune :: SimpleMessageBuffer > P2PCommunicatorType;
typedef typename P2PCommunicatorType :: MessageBufferType MessageBufferType;
typedef std::vector< GlobalCellIndex > LocalIndexMapType;
typedef std::vector<int> IndexMapType;
typedef std::vector< IndexMapType > IndexMapStorageType;
class DistributeIndexMapping : public P2PCommunicatorType::DataHandleInterface
{
protected:
const std::vector<int>& distributedGlobalIndex_;
IndexMapType& localIndexMap_;
IndexMapStorageType& indexMaps_;
std::map< const int, const int > globalPosition_;
#ifndef NDEBUG
std::set< int > checkPosition_;
#endif
public:
DistributeIndexMapping( const std::vector<int>& globalIndex,
const std::vector<int>& distributedGlobalIndex,
IndexMapType& localIndexMap,
IndexMapStorageType& indexMaps )
: distributedGlobalIndex_( distributedGlobalIndex ),
localIndexMap_( localIndexMap ),
indexMaps_( indexMaps ),
globalPosition_()
{
const size_t size = globalIndex.size();
// create mapping globalIndex --> localIndex
for ( size_t index = 0; index < size; ++index )
{
globalPosition_.insert( std::make_pair( globalIndex[ index ], index ) );
}
// on I/O rank we need to create a mapping from local to global
if( ! indexMaps_.empty() )
{
// for the ioRank create a localIndex to index in global state map
IndexMapType& indexMap = indexMaps_.back();
const size_t localSize = localIndexMap_.size();
indexMap.resize( localSize );
for( size_t i=0; i<localSize; ++i )
{
const int id = distributedGlobalIndex_[ localIndexMap_[ i ] ];
indexMap[ i ] = globalPosition_[ id ] ;
#ifndef NDEBUG
assert( checkPosition_.find( id ) == checkPosition_.end() );
checkPosition_.insert( id );
#endif
}
}
}
void pack( const int link, MessageBufferType& buffer )
{
// we should only get one link
if( link != 0 ) {
OPM_THROW(std::logic_error,"link in method pack is not 0 as execpted");
}
// pack all interior global cell id's
const int size = localIndexMap_.size();
buffer.write( size );
for( int index = 0; index < size; ++index )
{
const int globalIdx = distributedGlobalIndex_[ localIndexMap_[ index ] ];
buffer.write( globalIdx );
}
}
void unpack( const int link, MessageBufferType& buffer )
{
// get index map for current link
IndexMapType& indexMap = indexMaps_[ link ];
assert( ! globalPosition_.empty() );
// unpack all interior global cell id's
int numCells = 0;
buffer.read( numCells );
indexMap.resize( numCells );
for( int index = 0; index < numCells; ++index )
{
int globalId = -1;
buffer.read( globalId );
assert( globalPosition_.find( globalId ) != globalPosition_.end() );
indexMap[ index ] = globalPosition_[ globalId ];
#ifndef NDEBUG
assert( checkPosition_.find( globalId ) == checkPosition_.end() );
checkPosition_.insert( globalId );
#endif
}
}
};
enum { ioRank = 0 };
/// \brief Constructor
/// \param otherGrid The grid after loadbalance was run.
/// \param eclipseState The eclipse file parser output
/// \param numPhases The number of active phases.
/// \param permeability The permeabilities for the global(!) view.
ParallelDebugOutput( const Dune::CpGrid& otherGrid,
const EclipseState& eclipseState,
const Schedule& schedule,
const int numPhases,
const Opm::PhaseUsage& phaseUsage)
: grid_(),
eclipseState_( eclipseState ),
schedule_(schedule),
globalCellData_(new data::Solution),
isIORank_(true),
phaseUsage_(phaseUsage)
{
// Switch to distributed view unconditionally for safety.
Dune::CpGrid distributed_grid = otherGrid;
const CollectiveCommunication& comm = otherGrid.comm();
if( comm.size() > 1 )
{
std::set< int > send, recv;
distributed_grid.switchToDistributedView();
toIORankComm_ = distributed_grid.comm();
isIORank_ = (distributed_grid.comm().rank() == ioRank);
// the I/O rank receives from all other ranks
if( isIORank() )
{
// copy grid
grid_.reset( new Dune::CpGrid(otherGrid ) );
grid_->switchToGlobalView();
Dune::CpGrid& globalGrid = *grid_;
// initialize global state with correct sizes
globalReservoirState_.reset( new SimulationDataContainer( globalGrid.numCells(), globalGrid.numFaces(), numPhases ));
// copy global cartesian index
globalIndex_ = globalGrid.globalCell();
unsigned int count = 0;
auto gridView = globalGrid.leafGridView();
for( auto it = gridView.begin< 0 >(),
end = gridView.end< 0 >(); it != end; ++it, ++count )
{
}
assert( count == globalIndex_.size() );
for(int i=0; i<comm.size(); ++i)
{
if( i != ioRank )
{
recv.insert( i );
}
}
}
else // all other simply send to the I/O rank
{
// globalReservoirState will be deferenced even if this rank is not outputting anything
// To prevent dereferencing a nullptr we create an empty container
globalReservoirState_.reset( new SimulationDataContainer( 0, 0, 0));
send.insert( ioRank );
}
localIndexMap_.clear();
localIndexMap_.reserve( distributed_grid.size( 0 ) );
unsigned int index = 0;
auto localView = distributed_grid.leafGridView();
for( auto it = localView.begin< 0 >(),
end = localView.end< 0 >(); it != end; ++it, ++index )
{
const auto element = *it ;
// only store interior element for collection
if( element.partitionType() == Dune :: InteriorEntity )
{
localIndexMap_.push_back( index );
}
}
// insert send and recv linkage to communicator
toIORankComm_.insertRequest( send, recv );
if( isIORank() )
{
// need an index map for each rank
indexMaps_.clear();
indexMaps_.resize( comm.size() );
}
// distribute global id's to io rank for later association of dof's
DistributeIndexMapping distIndexMapping( globalIndex_, distributed_grid.globalCell(), localIndexMap_, indexMaps_ );
toIORankComm_.exchange( distIndexMapping );
}
else // serial run
{
// copy global cartesian index
globalIndex_ = distributed_grid.globalCell();
}
}
class PackUnPackSimulationDataContainer : public P2PCommunicatorType::DataHandleInterface
{
const data::Solution& localCellData_;
data::Solution& globalCellData_;
const WellStateFullyImplicitBlackoil& localWellState_;
WellStateFullyImplicitBlackoil& globalWellState_;
const IndexMapType& localIndexMap_;
const IndexMapStorageType& indexMaps_;
public:
PackUnPackSimulationDataContainer( std::size_t numGlobalCells,
const data::Solution& localCellData,
data::Solution& globalCellData,
const WellStateFullyImplicitBlackoil& localWellState,
WellStateFullyImplicitBlackoil& globalWellState,
const IndexMapType& localIndexMap,
const IndexMapStorageType& indexMaps,
const bool isIORank )
: localCellData_( localCellData ),
globalCellData_( globalCellData ),
localWellState_( localWellState ),
globalWellState_( globalWellState ),
localIndexMap_( localIndexMap ),
indexMaps_( indexMaps )
{
if( isIORank )
{
// add missing data to global cell data
for (const auto& pair : localCellData_) {
const std::string& key = pair.first;
std::size_t container_size = numGlobalCells;
auto ret = globalCellData_.insert(key, pair.second.dim,
std::vector<double>(container_size),
pair.second.target);
assert(ret.second);
DUNE_UNUSED_PARAMETER(ret.second); //dummy op to prevent warning with -DNDEBUG
}
MessageBufferType buffer;
pack( 0, buffer );
// the last index map is the local one
doUnpack( indexMaps.back(), buffer );
}
}
// pack all data associated with link
void pack( const int link, MessageBufferType& buffer )
{
// we should only get one link
if( link != 0 ) {
OPM_THROW(std::logic_error,"link in method pack is not 0 as execpted");
}
// write all cell data registered in local state
for (const auto& pair : localCellData_) {
const auto& data = pair.second.data;
// write all data from local data to buffer
write( buffer, localIndexMap_, data);
}
// write all data from local well state to buffer
writeWells( buffer );
}
void doUnpack( const IndexMapType& indexMap, MessageBufferType& buffer )
{
// we loop over the data as
// its order governs the order the data got received.
for (auto& pair : localCellData_) {
const std::string& key = pair.first;
auto& data = globalCellData_.data(key);
//write all data from local cell data to buffer
read( buffer, indexMap, data);
}
// read well data from buffer
readWells( buffer );
}
// unpack all data associated with link
void unpack( const int link, MessageBufferType& buffer )
{
doUnpack( indexMaps_[ link ], buffer );
}
protected:
template <class Vector>
void write( MessageBufferType& buffer, const IndexMapType& localIndexMap,
const Vector& vector,
const unsigned int offset = 0, const unsigned int stride = 1 ) const
{
unsigned int size = localIndexMap.size();
buffer.write( size );
assert( vector.size() >= stride * size );
for( unsigned int i=0; i<size; ++i )
{
const unsigned int index = localIndexMap[ i ] * stride + offset;
assert( index < vector.size() );
buffer.write( vector[ index ] );
}
}
template <class Vector>
void read( MessageBufferType& buffer,
const IndexMapType& indexMap,
Vector& vector,
const unsigned int offset = 0, const unsigned int stride = 1 ) const
{
unsigned int size = 0;
buffer.read( size );
assert( size == indexMap.size() );
for( unsigned int i=0; i<size; ++i )
{
const unsigned int index = indexMap[ i ] * stride + offset;
assert( index < vector.size() );
buffer.read( vector[ index ] );
}
}
void writeString( MessageBufferType& buffer, const std::string& s) const
{
const int size = s.size();
buffer.write( size );
for( int i=0; i<size; ++i )
{
buffer.write( s[ i ] );
}
}
void readString( MessageBufferType& buffer, std::string& s) const
{
int size = -1;
buffer.read( size );
s.resize( size );
for( int i=0; i<size; ++i )
{
buffer.read( s[ i ] );
}
}
void writeWells( MessageBufferType& buffer ) const
{
int nWells = localWellState_.wellMap().size();
buffer.write( nWells );
auto end = localWellState_.wellMap().end();
for( auto it = localWellState_.wellMap().begin(); it != end; ++it )
{
const std::string& name = it->first;
const int wellIdx = it->second[ 0 ];
// write well name
writeString( buffer, name );
// write well data
buffer.write( localWellState_.bhp()[ wellIdx ] );
buffer.write( localWellState_.thp()[ wellIdx ] );
const int wellRateIdx = wellIdx * localWellState_.numPhases();
for( int np=0; np<localWellState_.numPhases(); ++np )
buffer.write( localWellState_.wellRates()[ wellRateIdx + np ] );
// Write well control
buffer.write(localWellState_.currentControls()[ wellIdx ]);
// Write perfRates and perfPress. No need to figure out the index
// mapping there as the ordering of the perforations should
// be the same for global and local state.
const int end_con = it->second[1] + it->second[2];
for( int con = it->second[1]; con < end_con; ++con )
{
buffer.write( localWellState_.perfRates()[ con ] );
}
for( int con = it->second[1]; con < end_con; ++con )
{
buffer.write( localWellState_.perfPress()[ con ] );
}
// Write perfPhaseRate
const int np = localWellState_.perfPhaseRates().size() /
localWellState_.perfRates().size();
for( int con = it->second[1]*np; con < end_con*np; ++con )
{
buffer.write( localWellState_.perfPhaseRates()[ con ] );
}
}
}
void readWells( MessageBufferType& buffer )
{
int nWells = -1;
buffer.read( nWells );
// unpack all wells that have been sent
std::string name ;
for( int well = 0; well < nWells ; ++well )
{
// read well name for local identification
readString( buffer, name );
// unpack values
auto it = globalWellState_.wellMap().find( name );
if( it == globalWellState_.wellMap().end() )
{
OPM_THROW(std::logic_error,"global state does not contain well " << name );
}
const int wellIdx = it->second[ 0 ];
buffer.read( globalWellState_.bhp()[ wellIdx ] );
buffer.read( globalWellState_.thp()[ wellIdx ] );
const int wellRateIdx = wellIdx * globalWellState_.numPhases();
for( int np=0; np<globalWellState_.numPhases(); ++np )
buffer.read( globalWellState_.wellRates()[ wellRateIdx + np ] );
// Write well control
buffer.read(globalWellState_.currentControls()[ wellIdx ]);
// Read perfRates and perfPress. No need to figure out the index
// mapping there as the ordering of the perforations should
// be the same for global and local state.
const int end_con = it->second[1] + it->second[2];
for( int con = it->second[1]; con < end_con; ++con )
{
buffer.read( globalWellState_.perfRates()[ con ] );
}
for( int con = it->second[1]; con < end_con; ++con )
{
buffer.read( globalWellState_.perfPress()[ con ] );
}
// Read perfPhaseRate
const int np = globalWellState_.perfPhaseRates().size() /
globalWellState_.perfRates().size();
for( int con = it->second[1]*np; con < end_con*np; ++con )
{
buffer.read( globalWellState_.perfPhaseRates()[ con ] );
}
}
}
};
// gather solution to rank 0 for EclipseWriter
template <class WellState>
bool collectToIORank( const SimulationDataContainer& /*localReservoirState*/,
const WellState& localWellState,
const data::Solution& localCellData,
const int wellStateStepNumber )
{
if( isIORank() )
{
Dune::CpGrid& globalGrid = *grid_;
// TODO: make a dummy DynamicListEconLimited here for NOW for compilation and development
// TODO: NOT SURE whether it will cause problem for parallel running
// TODO: TO BE TESTED AND IMPROVED
const DynamicListEconLimited dynamic_list_econ_limited;
// Create wells and well state.
WellsManager wells_manager(eclipseState_,
schedule_,
wellStateStepNumber,
Opm::UgGridHelpers::numCells( globalGrid ),
Opm::UgGridHelpers::globalCell( globalGrid ),
Opm::UgGridHelpers::cartDims( globalGrid ),
Opm::UgGridHelpers::dimensions( globalGrid ),
Opm::UgGridHelpers::cell2Faces( globalGrid ),
Opm::UgGridHelpers::beginFaceCentroids( globalGrid ),
dynamic_list_econ_limited,
false,
// We need to pass the optionaly arguments
// as we get the following error otherwise
// with c++ (Debian 4.9.2-10) 4.9.2 and -std=c++11
// converting to const std::unordered_set<std::basic_string<char> > from initializer list would use explicit constructor
std::unordered_set<std::string>());
const Wells* wells = wells_manager.c_wells();
globalWellState_.initLegacy(wells, *globalReservoirState_, globalWellState_, phaseUsage_ );
globalCellData_->clear();
}
PackUnPackSimulationDataContainer packUnpack( numCells(),
localCellData, *globalCellData_,
localWellState, globalWellState_,
localIndexMap_, indexMaps_,
isIORank() );
//toIORankComm_.exchangeCached( packUnpack );
toIORankComm_.exchange( packUnpack );
#ifndef NDEBUG
// make sure every process is on the same page
toIORankComm_.barrier();
#endif
if( isIORank() )
{
// copy values from globalCellData to globalReservoirState
RestartValue restart_value(*globalCellData_, {});
solutionToSim(restart_value, phaseUsage_, *globalReservoirState_);
}
return isIORank();
}
const SimulationDataContainer& globalReservoirState() const { return *globalReservoirState_; }
const data::Solution& globalCellData() const
{
return *globalCellData_;
}
const WellStateFullyImplicitBlackoil& globalWellState() const { return globalWellState_; }
bool isIORank() const
{
return isIORank_;
}
bool isParallel() const
{
return toIORankComm_.size() > 1;
}
int numCells () const { return globalIndex_.size(); }
const int* globalCell () const
{
assert( ! globalIndex_.empty() );
return globalIndex_.data();
}
protected:
std::unique_ptr< Dune::CpGrid > grid_;
const EclipseState& eclipseState_;
const Schedule& schedule_;
P2PCommunicatorType toIORankComm_;
IndexMapType globalIndex_;
IndexMapType localIndexMap_;
IndexMapStorageType indexMaps_;
std::unique_ptr<SimulationDataContainer> globalReservoirState_;
std::unique_ptr<data::Solution> globalCellData_;
// this needs to be revised
WellStateFullyImplicitBlackoil globalWellState_;
// true if we are on I/O rank
bool isIORank_;
// Phase usage needed to convert solution to simulation data container
Opm::PhaseUsage phaseUsage_;
};
#endif // #if HAVE_OPM_GRID
} // end namespace Opm
#endif

View File

@ -1,631 +0,0 @@
/*
Copyright 2015-2016 Dr. Blatt - HPC-Simulation-Software & Services.
Coypright 2015 NTNU
Copyright 2015-2016 Statoil AS
Copyright 2015 IRIS AS
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 3 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 <http://www.gnu.org/licenses/>.
*/
#ifndef OPM_REDISTRIBUTEDATAHANDLES_HEADER
#define OPM_REDISTRIBUTEDATAHANDLES_HEADER
#include <unordered_set>
#include <string>
#include <type_traits>
#include <iterator>
#include <opm/core/simulator/BlackoilState.hpp>
#include <opm/autodiff/BlackoilPropsAdFromDeck.hpp>
#include <opm/autodiff/ExtractParallelGridInformationToISTL.hpp>
#include <opm/autodiff/createGlobalCellArray.hpp>
#include<boost/any.hpp>
namespace Opm
{
template <class Grid>
inline std::unordered_set<std::string>
distributeGridAndData( Grid& ,
const Opm::Deck& ,
const EclipseState& ,
const Schedule&,
BlackoilState&,
BlackoilPropsAdFromDeck& ,
DerivedGeology&,
std::shared_ptr<BlackoilPropsAdFromDeck::MaterialLawManager>&,
std::vector<double>&,
boost::any& ,
const bool )
{
return std::unordered_set<std::string>();
}
/// \brief A handle that copies a fixed number data per index.
///
/// It works on Iterators to allow for communicating C arrays.
/// \tparam Iter1 Constant random access iterator type.
/// \tparam Iter1 Mutable random access iterator type.
template<class Iter1, class Iter2=Iter1>
class FixedSizeIterCopyHandle
{
typedef typename std::iterator_traits<Iter1>::value_type DataType2;
public:
typedef typename std::iterator_traits<Iter1>::value_type DataType;
/// \brief Constructor.
/// \param send_begin The begin iterator for sending.
/// \param receive_begin The begin iterator for receiving.
FixedSizeIterCopyHandle(const Iter1& send_begin,
const Iter2& receive_begin,
std::size_t size = 1)
: send_(send_begin), receive_(receive_begin), size_(size)
{
static_assert(std::is_same<DataType,DataType2>::value,
"Iter1 and Iter2 have to have the same value_type!");
}
template<class Buffer>
void gather(Buffer& buffer, std::size_t i)
{
for(auto index = i*size(i), end = (i+1)*size(i);
index < end; ++index)
{
buffer.write(send_[index]);
}
}
template<class Buffer>
void scatter(Buffer& buffer, std::size_t i, std::size_t s OPM_OPTIM_UNUSED)
{
assert(s==size(i));
static_cast<void>(s);
for(auto index = i*size(i), end = (i+1)*size(i);
index < end; ++index)
{
buffer.read(receive_[index]);
}
}
bool fixedsize()
{
return true;
}
std::size_t size(std::size_t)
{
return size_;
}
private:
Iter1 send_;
Iter2 receive_;
std::size_t size_;
};
/// \brief Data handle for gathering the rank that owns a cell
template<class Mapper>
class CellOwnerDataHandle
{
public:
using DataType = int;
CellOwnerDataHandle(const Mapper& globalMapper, std::vector<int>& globalData)
: globalMapper_(globalMapper), globalData_(globalData)
{
int argc = 0;
char** argv = nullptr;
my_rank_ = Dune::MPIHelper::instance(argc,argv).rank();
}
bool fixedsize(int /*dim*/, int /*codim*/)
{
return true;
}
template<class T>
std::size_t size(const T& e)
{
if ( T::codimension == 0)
{
return 1;
}
else
{
OPM_THROW(std::logic_error, "Data handle can only be used for elements");
}
}
template<class B, class T>
void gather(B& buffer, const T& e)
{
buffer.write(my_rank_);
}
template<class B, class T>
void scatter(B& buffer, const T& e, std::size_t /* size */)
{
const auto& index = globalMapper_.index(e);
buffer.read(globalData_[index]);
}
bool contains(int dim, int codim)
{
return codim==0;
}
private:
int my_rank_;
const Mapper& globalMapper_;
std::vector<int>& globalData_;
};
#if HAVE_OPM_GRID && HAVE_MPI
/// \brief a data handle to distribute the threshold pressures
class ThresholdPressureDataHandle
{
public:
/// \brief type of the data we send
typedef double DataType;
/// \brief Constructor
/// \param sendGrid The grid that the data is attached to when sending.
/// \param recvGrid The grid that the data is attached to when receiving.
/// \param sendPressures The container where we will retrieve the values to be sent.
/// \param numFaces Number of faces of the distributed grid.
ThresholdPressureDataHandle(const Dune::CpGrid& sendGrid,
const Dune::CpGrid& recvGrid,
const std::vector<double>& sendPressures,
std::vector<double>& recvPressures)
: sendGrid_(sendGrid), recvGrid_(recvGrid), sendPressures_(sendPressures),
recvPressures_(recvPressures)
{}
bool fixedsize(int /*dim*/, int /*codim*/)
{
return false;
}
template<class T>
std::size_t size(const T& e)
{
if ( T::codimension == 0)
{
return sendGrid_.numCellFaces(e.index());
}
else
{
OPM_THROW(std::logic_error, "Data handle can only be used for elements");
}
}
template<class B, class T>
void gather(B& buffer, const T& e)
{
assert( T::codimension == 0);
for ( int i=0; i< sendGrid_.numCellFaces(e.index()); ++i )
{
buffer.write(sendPressures_[sendGrid_.cellFace(e.index(), i)]);
}
}
template<class B, class T>
void scatter(B& buffer, const T& e, std::size_t /* size */)
{
assert( T::codimension == 0);
for ( int i=0; i< recvGrid_.numCellFaces(e.index()); ++i )
{
double val;
buffer.read(val);
recvPressures_[recvGrid_.cellFace(e.index(), i)]=val;
}
}
bool contains(int dim, int codim)
{
return dim==3 && codim==0;
}
private:
/// \brief The grid that the data we send is associated with.
const Dune::CpGrid& sendGrid_;
/// \brief The grid that the data we receive is associated with.
const Dune::CpGrid& recvGrid_;
/// \brief The data to send.
const std::vector<double>& sendPressures_;
/// \brief The data to receive.
std::vector<double>& recvPressures_;
};
/// \brief a data handle to distribute Derived Geology
class GeologyDataHandle
{
public:
/// \brief type of the data we send
typedef double DataType;
/// \brief Constructor
/// \param sendGrid The grid that the data is attached to when sending.
/// \param recvGrid The grid that the data is attached to when receiving.
/// \param sendGeology The state where we will retieve the values to be sent.
/// \param recvGeology The state where we will store the received values.
GeologyDataHandle(const Dune::CpGrid& sendGrid,
const Dune::CpGrid& recvGrid,
const DerivedGeology& sendGeology,
DerivedGeology& recvGeology)
: sendGrid_(sendGrid), recvGrid_(recvGrid), sendGeology_(sendGeology),
recvGeology_(recvGeology)
{}
bool fixedsize(int /*dim*/, int /*codim*/)
{
return false;
}
template<class T>
std::size_t size(const T& e)
{
if ( T::codimension == 0)
{
return 1 + sendGrid_.numCellFaces(e.index());
}
else
{
OPM_THROW(std::logic_error, "Data handle can only be used for elements");
}
}
template<class B, class T>
void gather(B& buffer, const T& e)
{
assert( T::codimension == 0);
buffer.write(sendGeology_.poreVolume()[e.index()]);
for ( int i=0; i< sendGrid_.numCellFaces(e.index()); ++i )
{
buffer.write(sendGeology_.transmissibility()[sendGrid_.cellFace(e.index(), i)]);
}
}
template<class B, class T>
void scatter(B& buffer, const T& e, std::size_t /* size */)
{
assert( T::codimension == 0);
double val;
buffer.read(val);
recvGeology_.poreVolume()[e.index()]=val;
for ( int i=0; i< recvGrid_.numCellFaces(e.index()); ++i )
{
buffer.read(val);
recvGeology_.transmissibility()[recvGrid_.cellFace(e.index(), i)]=val;
}
}
bool contains(int dim, int codim)
{
return dim==3 && codim==0;
}
private:
/// \brief The grid that the data we send is associated with.
const Dune::CpGrid& sendGrid_;
/// \brief The grid that the data we receive is associated with.
const Dune::CpGrid& recvGrid_;
/// \brief The data to send.
const DerivedGeology& sendGeology_;
/// \brief The data to receive.
DerivedGeology& recvGeology_;
};
/// \brief a data handle to distribute the BlackoilState
class BlackoilStateDataHandle
{
public:
/// \brief The data that we send.
typedef double DataType;
/// \brief Constructor.
/// \param sendGrid The grid that the data is attached to when sending.
/// \param recvGrid The grid that the data is attached to when receiving.
/// \param sendState The state where we will retieve the values to be sent.
/// \param recvState The state where we will store the received values.
BlackoilStateDataHandle(const Dune::CpGrid& sendGrid,
const Dune::CpGrid& recvGrid,
const BlackoilState& sendState,
BlackoilState& recvState)
: sendGrid_(sendGrid), recvGrid_(recvGrid), sendState_(sendState), recvState_(recvState)
{
// construction does not resize surfacevol and hydroCarbonState. Do it manually.
recvState.surfacevol().resize(recvGrid.numCells()*sendState.numPhases(),
std::numeric_limits<double>::max());
recvState.hydroCarbonState().resize(recvGrid.numCells());
}
bool fixedsize(int /*dim*/, int /*codim*/)
{
return false;
}
template<class T>
std::size_t size(const T& e)
{
if ( T::codimension == 0)
{
return 2 * sendState_.numPhases() + 5 + 2*sendGrid_.numCellFaces(e.index());
}
else
{
OPM_THROW(std::logic_error, "Data handle can only be used for elements");
}
}
template<class B, class T>
void gather(B& buffer, const T& e)
{
assert( T::codimension == 0);
for ( size_t i=0; i<sendState_.numPhases(); ++i )
{
buffer.write(sendState_.surfacevol()[e.index()*sendState_.numPhases()+i]);
}
buffer.write(sendState_.gasoilratio()[e.index()]);
buffer.write(sendState_.rv()[e.index()]);
buffer.write(sendState_.pressure()[e.index()]);
buffer.write(sendState_.temperature()[e.index()]);
//We can only send one type with this buffer. Ergo we convert the enum to a double.
double hydroCarbonState_ = sendState_.hydroCarbonState()[e.index()];
buffer.write(hydroCarbonState_);
for ( size_t i=0; i<sendState_.numPhases(); ++i )
{
buffer.write(sendState_.saturation()[e.index()*sendState_.numPhases()+i]);
}
for ( int i=0; i<sendGrid_.numCellFaces(e.index()); ++i )
{
buffer.write(sendState_.facepressure()[sendGrid_.cellFace(e.index(), i)]);
}
for ( int i=0; i<sendGrid_.numCellFaces(e.index()); ++i )
{
buffer.write(sendState_.faceflux()[sendGrid_.cellFace(e.index(), i)]);
}
}
template<class B, class T>
void scatter(B& buffer, const T& e, std::size_t size_arg)
{
assert( T::codimension == 0);
assert( size_arg == 2 * recvState_.numPhases() + 5 +2*recvGrid_.numCellFaces(e.index()));
static_cast<void>(size_arg);
double val;
for ( size_t i=0; i<recvState_.numPhases(); ++i )
{
buffer.read(val);
recvState_.surfacevol()[e.index()*sendState_.numPhases()+i]=val;
}
buffer.read(val);
recvState_.gasoilratio()[e.index()]=val;
buffer.read(val);
recvState_.rv()[e.index()]=val;
buffer.read(val);
recvState_.pressure()[e.index()]=val;
buffer.read(val);
recvState_.temperature()[e.index()]=val;
//We can only send one type with this buffer. Ergo we convert the enum to a double.
buffer.read(val);
recvState_.hydroCarbonState()[e.index()]=static_cast<HydroCarbonState>(val);
for ( size_t i=0; i<recvState_.numPhases(); ++i )
{
buffer.read(val);
recvState_.saturation()[e.index()*sendState_.numPhases()+i]=val;
}
for ( int i=0; i<recvGrid_.numCellFaces(e.index()); ++i )
{
buffer.read(val);
recvState_.facepressure()[recvGrid_.cellFace(e.index(), i)]=val;
}
for ( int i=0; i<recvGrid_.numCellFaces(e.index()); ++i )
{
buffer.read(val);
recvState_.faceflux()[recvGrid_.cellFace(e.index(), i)]=val;
}
}
bool contains(int dim, int codim)
{
return dim==3 && codim==0;
}
private:
/// \brief The grid that the data is attached to when sending
const Dune::CpGrid& sendGrid_;
/// \brief The grid that the data is attached to when receiving
const Dune::CpGrid& recvGrid_;
/// \brief The state where we will retieve the values to be sent.
const BlackoilState& sendState_;
// \brief The state where we will store the received values.
BlackoilState& recvState_;
};
/// \brief A DUNE data handle for sending the blackoil properties
class BlackoilPropsDataHandle
{
public:
/// \brief The data that we send.
typedef double DataType;
/// \brief Constructor.
/// \param sendProps The properties where we will retieve the values to be sent.
/// \parame recvProps The properties where we will store the received values.
BlackoilPropsDataHandle(const BlackoilPropsAdFromDeck& sendProps,
BlackoilPropsAdFromDeck& recvProps)
: sendProps_(sendProps), recvProps_(recvProps),
size_(11) // full permeability tensor 9 + porosity 1 + pvt region index
{
// satOilMax might be non empty. In this case we will need to send it, too.
if ( sendProps.satOilMax_.size()>0 )
{
// satOilMax has to have the same size as the cellPvtRegionIdx_
recvProps_.satOilMax_.resize(recvProps_.cellPvtRegionIdx_.size(),
-std::numeric_limits<double>::max());
++size_;
}
}
bool fixedsize(int /*dim*/, int /*codim*/)
{
return true;
}
template<class T>
std::size_t size(const T&)
{
if ( T::codimension == 0)
{
return size_;
}
else
{
OPM_THROW(std::logic_error, "Data handle can only be used for elements");
}
}
template<class B, class T>
void gather(B& buffer, const T& e)
{
assert( T::codimension == 0);
buffer.write(sendProps_.cellPvtRegionIndex()[e.index()]);
for( std::size_t i = 0; i < 9; ++i )
{
buffer.write(sendProps_.rock_.permeability_[e.index()*9+i]);
}
buffer.write(sendProps_.rock_.porosity_[e.index()]);
if ( size_ > 11 ) {
buffer.write(sendProps_.satOilMax_[e.index()]);
}
}
template<class B, class T>
void scatter(B& buffer, const T& e, std::size_t size_arg)
{
assert( T::codimension == 0);
assert( size_arg==size_ ); (void) size_arg;
double val;
buffer.read(val);
recvProps_.cellPvtRegionIdx_[e.index()]=val;
for( std::size_t i = 0; i < 9; ++i )
{
buffer.read(val);
recvProps_.rock_.permeability_[e.index()*9+i]
= val;
}
buffer.read(val);
recvProps_.rock_.porosity_[e.index()]=val;
if ( size_ > 11 ) {
buffer.read(val);
recvProps_.satOilMax_[e.index()]=val;
}
}
bool contains(int dim, int codim)
{
return dim==3 && codim==0;
}
private:
/// \brief The properties where we will retieve the values to be sent.
const BlackoilPropsAdFromDeck& sendProps_;
/// \brief The properties where we will store the received values.
BlackoilPropsAdFromDeck& recvProps_;
/// \brief The number of entries to send.
///
/// full permeability tensor 9 + porosity 1 + pvt region index and
/// in some case satOilMax
std::size_t size_;
};
inline
std::unordered_set<std::string>
distributeGridAndData( Dune::CpGrid& grid,
const Opm::Deck& deck,
const EclipseState& eclipseState,
const Schedule& schedule,
BlackoilState& state,
BlackoilPropsAdFromDeck& properties,
DerivedGeology& geology,
std::shared_ptr<BlackoilPropsAdFromDeck::MaterialLawManager>& material_law_manager,
std::vector<double>& threshold_pressures,
boost::any& parallelInformation,
const bool useLocalPerm)
{
Dune::CpGrid global_grid ( grid );
global_grid.switchToGlobalView();
// distribute the grid and switch to the distributed view
using std::get;
auto wells = schedule.getWells();
auto my_defunct_wells = get<1>(grid.loadBalance(&wells, geology.transmissibility().data()));
grid.switchToDistributedView();
std::vector<int> compressedToCartesianIdx;
Opm::createGlobalCellArray(grid, compressedToCartesianIdx);
typedef BlackoilPropsAdFromDeck::MaterialLawManager MaterialLawManager;
auto distributed_material_law_manager = std::make_shared<MaterialLawManager>();
distributed_material_law_manager->initFromDeck(deck, eclipseState, compressedToCartesianIdx);
// copy the values from the global to the local MaterialLawManager
// We should actually communicate these to be future proof. But that is
// really, really cumbersome for the underlying vector<shared_ptr>
// where the classes pointed to even have more shared_ptr stored in them.
typedef Dune::CpGrid::ParallelIndexSet IndexSet;
const IndexSet& local_indices = grid.getCellIndexSet();
for ( auto index : local_indices )
{
distributed_material_law_manager->materialLawParamsPointerReferenceHack(index.local()) =
material_law_manager->materialLawParamsPointerReferenceHack(index.global());
distributed_material_law_manager->oilWaterScaledEpsInfoDrainagePointerReferenceHack(index.local()) =
material_law_manager->oilWaterScaledEpsInfoDrainagePointerReferenceHack(index.global());
}
BlackoilPropsAdFromDeck distributed_props(properties,
distributed_material_law_manager,
grid.numCells());
BlackoilState distributed_state(grid.numCells(), grid.numFaces(), state.numPhases());
BlackoilStateDataHandle state_handle(global_grid, grid,
state, distributed_state);
BlackoilPropsDataHandle props_handle(properties,
distributed_props);
grid.scatterData(state_handle);
grid.scatterData(props_handle);
// Create a distributed Geology. Some values will be updated using communication
// below
DerivedGeology distributed_geology(grid,
distributed_props, eclipseState,
useLocalPerm, geology.gravity());
GeologyDataHandle geo_handle(global_grid, grid,
geology, distributed_geology);
grid.scatterData(geo_handle);
std::vector<double> distributed_pressures;
if( !threshold_pressures.empty() ) // Might be empty if not specified
{
if( threshold_pressures.size() !=
static_cast<std::size_t>(UgGridHelpers::numFaces(global_grid)) )
{
OPM_THROW(std::runtime_error, "NNCs not yet supported for parallel runs. "
<< UgGridHelpers::numFaces(grid) << " faces but " <<
threshold_pressures.size()<<" threshold pressure values");
}
distributed_pressures.resize(UgGridHelpers::numFaces(grid));
ThresholdPressureDataHandle press_handle(global_grid, grid,
threshold_pressures,
distributed_pressures);
grid.scatterData(press_handle);
}
// copy states
properties = distributed_props;
geology = distributed_geology;
state = distributed_state;
material_law_manager = distributed_material_law_manager;
threshold_pressures = distributed_pressures;
extractParallelGridInformationToISTL(grid, parallelInformation);
return my_defunct_wells;
}
#endif
} // end namespace Opm
#endif

View File

@ -1,235 +0,0 @@
/*
Copyright 2013 SINTEF ICT, Applied Mathematics.
Copyright 2015 Andreas Lauser
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 3 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 <http://www.gnu.org/licenses/>.
*/
#ifndef OPM_SIMULATORBASE_HEADER_INCLUDED
#define OPM_SIMULATORBASE_HEADER_INCLUDED
#include <opm/material/densead/Math.hpp>
#include <opm/autodiff/DuneMatrix.hpp>
#include <opm/autodiff/SimulatorFullyImplicitBlackoilOutput.hpp>
#include <opm/common/utility/parameters/ParameterGroup.hpp>
#include <opm/common/ErrorMacros.hpp>
#include <opm/autodiff/GeoProps.hpp>
#include <opm/autodiff/BlackoilModel.hpp>
#include <opm/autodiff/BlackoilPropsAdFromDeck.hpp>
#include <opm/autodiff/WellStateFullyImplicitBlackoil.hpp>
#include <opm/autodiff/RateConverter.hpp>
#include <opm/grid/UnstructuredGrid.h>
#include <opm/core/wells.h>
#include <opm/core/well_controls.h>
#include <opm/core/pressure/flow_bc.h>
#include <opm/core/simulator/SimulatorReport.hpp>
#include <opm/simulators/timestepping/SimulatorTimer.hpp>
#include <opm/simulators/timestepping/AdaptiveSimulatorTimer.hpp>
#include <opm/grid/utility/StopWatch.hpp>
#include <opm/core/utility/miscUtilities.hpp>
#include <opm/core/utility/miscUtilitiesBlackoil.hpp>
#include <opm/core/props/rock/RockCompressibility.hpp>
#include <opm/core/simulator/BlackoilState.hpp>
#include <opm/simulators/timestepping/AdaptiveTimeStepping.hpp>
#include <opm/core/transport/reorder/TransportSolverCompressibleTwophaseReorder.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/Schedule.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/ScheduleEnums.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/Well.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/WellProductionProperties.hpp>
#include <boost/filesystem.hpp>
#include <boost/lexical_cast.hpp>
#include <algorithm>
#include <cstddef>
#include <cassert>
#include <functional>
#include <memory>
#include <numeric>
#include <fstream>
#include <iostream>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
namespace Opm
{
template <class Simulator>
struct SimulatorTraits;
/// Class collecting all necessary components for a two-phase simulation.
template <class Implementation>
class SimulatorBase
{
typedef SimulatorTraits<Implementation> Traits;
public:
typedef typename Traits::ReservoirState ReservoirState;
typedef typename Traits::WellState WellState;
typedef typename Traits::OutputWriter OutputWriter;
typedef typename Traits::Grid Grid;
typedef typename Traits::Solver Solver;
typedef typename Traits::WellModel WellModel;
/// Initialise from parameters and objects to observe.
/// \param[in] param parameters, this class accepts the following:
/// parameter (default) effect
/// -----------------------------------------------------------
/// output (true) write output to files?
/// output_dir ("output") output directoty
/// output_interval (1) output every nth step
/// nl_pressure_residual_tolerance (0.0) pressure solver residual tolerance (in Pascal)
/// nl_pressure_change_tolerance (1.0) pressure solver change tolerance (in Pascal)
/// nl_pressure_maxiter (10) max nonlinear iterations in pressure
/// nl_maxiter (30) max nonlinear iterations in transport
/// nl_tolerance (1e-9) transport solver absolute residual tolerance
/// num_transport_substeps (1) number of transport steps per pressure step
/// use_segregation_split (false) solve for gravity segregation (if false,
/// segregation is ignored).
///
/// \param[in] grid grid data structure
/// \param[in] geo derived geological properties
/// \param[in] props fluid and rock properties
/// \param[in] rock_comp_props if non-null, rock compressibility properties
/// \param[in] linsolver linear solver
/// \param[in] gravity if non-null, gravity vector
/// \param[in] disgas true for dissolved gas option
/// \param[in] vapoil true for vaporized oil option
/// \param[in] eclipse_state the object which represents an internalized ECL deck
/// \param[in] output_writer
/// \param[in] threshold_pressures_by_face if nonempty, threshold pressures that inhibit flow
SimulatorBase(const ParameterGroup& param,
const Grid& grid,
DerivedGeology& geo,
BlackoilPropsAdFromDeck& props,
const RockCompressibility* rock_comp_props,
NewtonIterationBlackoilInterface& linsolver,
const double* gravity,
const bool disgas,
const bool vapoil,
std::shared_ptr<EclipseState> eclipse_state,
std::shared_ptr<Schedule> schedule,
std::shared_ptr<SummaryConfig> summary_config,
OutputWriter& output_writer,
const std::vector<double>& threshold_pressures_by_face,
const std::unordered_set<std::string>& defunct_well_names);
/// Run the simulation.
/// This will run succesive timesteps until timer.done() is true. It will
/// modify the reservoir and well states.
/// \param[in,out] timer governs the requested reporting timesteps
/// \param[in,out] state state of reservoir: pressure, fluxes
/// \return simulation report, with timing data
SimulatorReport run(SimulatorTimer& timer,
ReservoirState& state);
protected:
Implementation& asImpl() { return *static_cast<Implementation*>(this); }
const Implementation& asImpl() const { return *static_cast<const Implementation*>(this); }
void handleAdditionalWellInflow(SimulatorTimer& timer,
WellsManager& wells_manager,
WellState& well_state,
const Wells* wells);
std::unique_ptr<Solver> createSolver(const WellModel& well_model);
void
computeRESV(const std::size_t step,
const Wells* wells,
const BlackoilState& x,
WellState& xw);
void
FIPUnitConvert(const UnitSystem& units,
std::vector<std::vector<double> >& fip);
void
FIPUnitConvert(const UnitSystem& units,
std::vector<double>& fip);
std::vector<double>
FIPTotals(const std::vector<std::vector<double> >& fip, const ReservoirState& state);
void
outputFluidInPlace(const std::vector<double>& oip, const std::vector<double>& cip, const UnitSystem& units, const int reg);
void updateListEconLimited(const std::unique_ptr<Solver>& solver,
const Schedule& schedule,
const int current_step,
const Wells* wells,
const WellState& well_state,
DynamicListEconLimited& list_econ_limited) const;
void initHysteresisParams(ReservoirState& state);
// Data.
typedef RateConverter::
SurfaceToReservoirVoidage< BlackoilPropsAdFromDeck::FluidSystem,
std::vector<int> > RateConverterType;
typedef typename Traits::Model Model;
typedef typename Model::ModelParameters ModelParameters;
typedef typename Solver::SolverParameters SolverParameters;
const ParameterGroup param_;
ModelParameters model_param_;
SolverParameters solver_param_;
// Observed objects.
const Grid& grid_;
BlackoilPropsAdFromDeck& props_;
const RockCompressibility* rock_comp_props_;
const double* gravity_;
// Solvers
DerivedGeology& geo_;
NewtonIterationBlackoilInterface& solver_;
// Misc. data
std::vector<int> allcells_;
const bool has_disgas_;
const bool has_vapoil_;
bool terminal_output_;
// eclipse_state
std::shared_ptr<EclipseState> eclipse_state_;
std::shared_ptr<Schedule> schedule_;
std::shared_ptr<SummaryConfig> summary_config_;
// output_writer
OutputWriter& output_writer_;
RateConverterType rateConverter_;
// Threshold pressures.
std::vector<double> threshold_pressures_by_face_;
// Whether this a parallel simulation or not
bool is_parallel_run_;
// The names of wells that should be defunct
// (e.g. in a parallel run when they are handeled by
// a different process)
std::unordered_set<std::string> defunct_well_names_;
};
} // namespace Opm
#include "SimulatorBase_impl.hpp"
#endif // OPM_SIMULATORBASE_HEADER_INCLUDED

View File

@ -1,847 +0,0 @@
/*
Copyright 2013 SINTEF ICT, Applied Mathematics.
Copyright 2014-2016 IRIS AS
Copyright 2015 Andreas Lauser
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 3 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 <http://www.gnu.org/licenses/>.
*/
#include <utility>
#include <functional>
#include <algorithm>
#include <locale>
#include <opm/parser/eclipse/EclipseState/Schedule/Events.hpp>
#include <opm/core/utility/initHydroCarbonState.hpp>
#include <opm/core/well_controls.h>
#include <opm/core/wells/DynamicListEconLimited.hpp>
#include <opm/autodiff/BlackoilModel.hpp>
namespace Opm
{
template <class Implementation>
SimulatorBase<Implementation>::SimulatorBase(const ParameterGroup& param,
const Grid& grid,
DerivedGeology& geo,
BlackoilPropsAdFromDeck& props,
const RockCompressibility* rock_comp_props,
NewtonIterationBlackoilInterface& linsolver,
const double* gravity,
const bool has_disgas,
const bool has_vapoil,
std::shared_ptr<EclipseState> eclipse_state,
std::shared_ptr<Schedule> schedule,
std::shared_ptr<SummaryConfig> summary_config,
OutputWriter& output_writer,
const std::vector<double>& threshold_pressures_by_face,
const std::unordered_set<std::string>& defunct_well_names)
: param_(param),
model_param_(param),
solver_param_(param),
grid_(grid),
props_(props),
rock_comp_props_(rock_comp_props),
gravity_(gravity),
geo_(geo),
solver_(linsolver),
has_disgas_(has_disgas),
has_vapoil_(has_vapoil),
terminal_output_(param.getDefault("output_terminal", true)),
eclipse_state_(eclipse_state),
schedule_(schedule),
summary_config_(summary_config),
output_writer_(output_writer),
rateConverter_(props_.phaseUsage(), std::vector<int>(AutoDiffGrid::numCells(grid_), 0)),
threshold_pressures_by_face_(threshold_pressures_by_face),
is_parallel_run_( false ),
defunct_well_names_(defunct_well_names)
{
// Misc init.
const int num_cells = AutoDiffGrid::numCells(grid);
allcells_.resize(num_cells);
for (int cell = 0; cell < num_cells; ++cell) {
allcells_[cell] = cell;
}
#if HAVE_MPI
if ( solver_.parallelInformation().type() == typeid(ParallelISTLInformation) )
{
const ParallelISTLInformation& info =
boost::any_cast<const ParallelISTLInformation&>(solver_.parallelInformation());
// Only rank 0 does print to std::cout
terminal_output_ = terminal_output_ && ( info.communicator().rank() == 0 );
is_parallel_run_ = ( info.communicator().size() > 1 );
}
#endif
}
template <class Implementation>
SimulatorReport SimulatorBase<Implementation>::run(SimulatorTimer& timer,
ReservoirState& state)
{
WellState prev_well_state;
ExtraData extra;
if (output_writer_.isRestart()) {
// This is a restart, populate WellState and ReservoirState state objects from restart file
output_writer_.initFromRestartFile(props_.phaseUsage(), grid_, state, prev_well_state, extra);
initHydroCarbonState(state, props_.phaseUsage(), Opm::UgGridHelpers::numCells(grid_), has_disgas_, has_vapoil_);
initHysteresisParams(state);
}
// Create timers and file for writing timing info.
Opm::time::StopWatch solver_timer;
Opm::time::StopWatch step_timer;
Opm::time::StopWatch total_timer;
total_timer.start();
std::string tstep_filename = output_writer_.outputDirectory() + "/step_timing.txt";
std::ofstream tstep_os;
if ( output_writer_.output() ) {
if ( output_writer_.isIORank() )
tstep_os.open(tstep_filename.c_str());
}
// adaptive time stepping
const auto& events = schedule_->getEvents();
std::unique_ptr< AdaptiveTimeStepping > adaptiveTimeStepping;
if( param_.getDefault("timestep.adaptive", true ) )
{
if (param_.getDefault("use_TUNING", false)) {
adaptiveTimeStepping.reset( new AdaptiveTimeStepping( schedule_->getTuning(), timer.currentStepNum(), param_, terminal_output_ ) );
} else {
adaptiveTimeStepping.reset( new AdaptiveTimeStepping( param_, terminal_output_ ) );
}
if (output_writer_.isRestart()) {
if (extra.suggested_step > 0.0) {
adaptiveTimeStepping->setSuggestedNextStep(extra.suggested_step);
}
}
}
DynamicListEconLimited dynamic_list_econ_limited;
SimulatorReport report;
SimulatorReport stepReport;
bool ooip_computed = false;
std::vector<int> fipnum_global = eclipse_state_->get3DProperties().getIntGridProperty("FIPNUM").getData();
//Get compressed cell fipnum.
std::vector<int> fipnum(AutoDiffGrid::numCells(grid_));
if (fipnum_global.empty()) {
std::fill(fipnum.begin(), fipnum.end(), 0);
} else {
for (size_t c = 0; c < fipnum.size(); ++c) {
fipnum[c] = fipnum_global[AutoDiffGrid::globalCell(grid_)[c]];
}
}
std::vector<std::vector<double> > OOIP;
// Main simulation loop.
while (!timer.done()) {
// Report timestep.
step_timer.start();
if ( terminal_output_ )
{
std::ostringstream ss;
timer.report(ss);
OpmLog::note(ss.str());
}
// Create wells and well state.
WellsManager wells_manager(*eclipse_state_,
*schedule_,
timer.currentStepNum(),
Opm::UgGridHelpers::numCells(grid_),
Opm::UgGridHelpers::globalCell(grid_),
Opm::UgGridHelpers::cartDims(grid_),
Opm::UgGridHelpers::dimensions(grid_),
Opm::UgGridHelpers::cell2Faces(grid_),
Opm::UgGridHelpers::beginFaceCentroids(grid_),
dynamic_list_econ_limited,
is_parallel_run_,
defunct_well_names_);
const Wells* wells = wells_manager.c_wells();
WellState well_state;
well_state.initLegacy(wells, state, prev_well_state, props_.phaseUsage());
// give the polymer and surfactant simulators the chance to do their stuff
asImpl().handleAdditionalWellInflow(timer, wells_manager, well_state, wells);
// write the inital state at the report stage
if (timer.initialStep()) {
Dune::Timer perfTimer;
perfTimer.start();
// No per cell data is written for initial step, but will be
// for subsequent steps, when we have started simulating
output_writer_.writeTimeStepWithoutCellProperties( timer, state, well_state, {}, {} );
report.output_write_time += perfTimer.stop();
}
// Max oil saturation (for VPPARS), hysteresis update.
props_.updateSatOilMax(state.saturation());
props_.updateSatHyst(state.saturation(), allcells_);
// Compute reservoir volumes for RESV controls.
asImpl().computeRESV(timer.currentStepNum(), wells, state, well_state);
// Run a multiple steps of the solver depending on the time step control.
solver_timer.start();
const WellModel well_model(wells, &(wells_manager.wellCollection()), timer.currentStepNum());
std::unique_ptr<Solver> solver = asImpl().createSolver(well_model);
// Compute orignal FIP;
if (!ooip_computed) {
OOIP = solver->computeFluidInPlace(state, fipnum);
FIPUnitConvert(eclipse_state_->getUnits(), OOIP);
ooip_computed = true;
}
if( terminal_output_ )
{
std::ostringstream step_msg;
boost::posix_time::time_facet* facet = new boost::posix_time::time_facet("%d-%b-%Y");
step_msg.imbue(std::locale(std::locale::classic(), facet));
step_msg << "\nTime step " << std::setw(4) <<timer.currentStepNum()
<< " at day " << (double)unit::convert::to(timer.simulationTimeElapsed(), unit::day)
<< "/" << (double)unit::convert::to(timer.totalTime(), unit::day)
<< ", date = " << timer.currentDateTime();
OpmLog::info(step_msg.str());
}
// If sub stepping is enabled allow the solver to sub cycle
// in case the report steps are too large for the solver to converge
//
// \Note: The report steps are met in any case
// \Note: The sub stepping will require a copy of the state variables
if( adaptiveTimeStepping ) {
bool event = events.hasEvent(ScheduleEvents::NEW_WELL, timer.currentStepNum()) ||
events.hasEvent(ScheduleEvents::PRODUCTION_UPDATE, timer.currentStepNum()) ||
events.hasEvent(ScheduleEvents::INJECTION_UPDATE, timer.currentStepNum()) ||
events.hasEvent(ScheduleEvents::WELL_STATUS_CHANGE, timer.currentStepNum());
report += adaptiveTimeStepping->step( timer, *solver, state, well_state, event, output_writer_,
output_writer_.requireFIPNUM() ? &fipnum : nullptr );
}
else {
// solve for complete report step
stepReport = solver->step(timer, state, well_state);
report += stepReport;
if( terminal_output_ )
{
std::ostringstream iter_msg;
iter_msg << "Stepsize " << (double)unit::convert::to(timer.currentStepLength(), unit::day);
if (solver->wellIterations() != 0) {
iter_msg << " days well iterations = " << solver->wellIterations() << ", ";
}
iter_msg << "non-linear iterations = " << solver->nonlinearIterations()
<< ", total linear iterations = " << solver->linearIterations()
<< "\n";
OpmLog::info(iter_msg.str());
}
}
// update the derived geology (transmissibilities, pore volumes, etc) if the
// has geology changed for the next report step
const int nextTimeStepIdx = timer.currentStepNum() + 1;
if (nextTimeStepIdx < timer.numSteps()
&& events.hasEvent(ScheduleEvents::GEO_MODIFIER, nextTimeStepIdx)) {
// bring the contents of the keywords to the current state of the SCHEDULE
// section
//
// TODO (?): handle the parallel case (maybe this works out of the box)
const auto& miniDeck = schedule_->getModifierDeck(nextTimeStepIdx);
eclipse_state_->applyModifierDeck(miniDeck);
geo_.update(grid_, props_, *eclipse_state_, gravity_);
}
// take time that was used to solve system for this reportStep
solver_timer.stop();
// update timing.
report.solver_time += solver_timer.secsSinceStart();
// Compute current FIP.
std::vector<std::vector<double> > COIP;
COIP = solver->computeFluidInPlace(state, fipnum);
std::vector<double> OOIP_totals = FIPTotals(OOIP, state);
std::vector<double> COIP_totals = FIPTotals(COIP, state);
//Convert to correct units
FIPUnitConvert(eclipse_state_->getUnits(), COIP);
FIPUnitConvert(eclipse_state_->getUnits(), OOIP_totals);
FIPUnitConvert(eclipse_state_->getUnits(), COIP_totals);
if ( terminal_output_ )
{
outputFluidInPlace(OOIP_totals, COIP_totals,eclipse_state_->getUnits(), 0);
for (size_t reg = 0; reg < OOIP.size(); ++reg) {
outputFluidInPlace(OOIP[reg], COIP[reg], eclipse_state_->getUnits(), reg+1);
}
}
if ( terminal_output_ )
{
std::string msg;
msg = "Fully implicit solver took: " + std::to_string(stepReport.solver_time) + " seconds. Total solver time taken: " + std::to_string(report.solver_time) + " seconds.";
OpmLog::note(msg);
}
if ( tstep_os.is_open() ) {
stepReport.reportParam(tstep_os);
}
// Increment timer, remember well state.
++timer;
// write simulation state at the report stage
Dune::Timer perfTimer;
perfTimer.start();
const auto& physicalModel = solver->model();
output_writer_.writeTimeStep( timer, state, well_state, physicalModel );
report.output_write_time += perfTimer.stop();
prev_well_state = well_state;
asImpl().updateListEconLimited(solver, *schedule_, timer.currentStepNum(), wells,
well_state, dynamic_list_econ_limited);
}
// Stop timer and create timing report
total_timer.stop();
report.total_time = total_timer.secsSinceStart();
report.converged = true;
return report;
}
namespace SimFIBODetails {
typedef std::unordered_map<std::string, const Well* > WellMap;
inline WellMap
mapWells(const std::vector< const Well* >& wells)
{
WellMap wmap;
for (std::vector< const Well* >::const_iterator
w = wells.begin(), e = wells.end();
w != e; ++w)
{
wmap.insert(std::make_pair((*w)->name(), *w));
}
return wmap;
}
inline int
resv_control(const WellControls* ctrl)
{
int i, n = well_controls_get_num(ctrl);
bool match = false;
for (i = 0; (! match) && (i < n); ++i) {
match = well_controls_iget_type(ctrl, i) == RESERVOIR_RATE;
}
if (! match) { i = 0; }
return i - 1; // -1 if no match, undo final "++" otherwise
}
inline bool
is_resv(const Wells& wells,
const int w)
{
return (0 <= resv_control(wells.ctrls[w]));
}
inline bool
is_resv(const WellMap& wmap,
const std::string& name,
const std::size_t step)
{
bool match = false;
WellMap::const_iterator i = wmap.find(name);
if (i != wmap.end()) {
const Well* wp = i->second;
match = (wp->isProducer(step) &&
wp->getProductionProperties(step)
.hasProductionControl(WellProducer::RESV))
|| (wp->isInjector(step) &&
wp->getInjectionProperties(step)
.hasInjectionControl(WellInjector::RESV));
}
return match;
}
inline std::vector<int>
resvWells(const Wells* wells,
const std::size_t step,
const WellMap& wmap)
{
std::vector<int> resv_wells;
if( wells )
{
for (int w = 0, nw = wells->number_of_wells; w < nw; ++w) {
if (is_resv(*wells, w) ||
((wells->name[w] != 0) &&
is_resv(wmap, wells->name[w], step)))
{
resv_wells.push_back(w);
}
}
}
return resv_wells;
}
inline void
historyRates(const PhaseUsage& pu,
const WellProductionProperties& p,
std::vector<double>& rates)
{
assert (! p.predictionMode);
assert (rates.size() ==
std::vector<double>::size_type(pu.num_phases));
if (pu.phase_used[ BlackoilPhases::Aqua ]) {
const std::vector<double>::size_type
i = pu.phase_pos[ BlackoilPhases::Aqua ];
rates[i] = p.WaterRate;
}
if (pu.phase_used[ BlackoilPhases::Liquid ]) {
const std::vector<double>::size_type
i = pu.phase_pos[ BlackoilPhases::Liquid ];
rates[i] = p.OilRate;
}
if (pu.phase_used[ BlackoilPhases::Vapour ]) {
const std::vector<double>::size_type
i = pu.phase_pos[ BlackoilPhases::Vapour ];
rates[i] = p.GasRate;
}
}
} // namespace SimFIBODetails
template <class Implementation>
void SimulatorBase<Implementation>::handleAdditionalWellInflow(SimulatorTimer& /* timer */,
WellsManager& /* wells_manager */,
WellState& /* well_state */,
const Wells* /* wells */)
{ }
template <class Implementation>
auto SimulatorBase<Implementation>::createSolver(const WellModel& well_model)
-> std::unique_ptr<Solver>
{
auto model = std::unique_ptr<Model>(new Model(model_param_,
grid_,
props_,
geo_,
rock_comp_props_,
well_model,
solver_,
eclipse_state_,
schedule_,
summary_config_,
has_disgas_,
has_vapoil_,
terminal_output_));
if (!threshold_pressures_by_face_.empty()) {
model->setThresholdPressures(threshold_pressures_by_face_);
}
return std::unique_ptr<Solver>(new Solver(solver_param_, std::move(model)));
}
template <class Implementation>
void SimulatorBase<Implementation>::computeRESV(const std::size_t step,
const Wells* wells,
const BlackoilState& x,
WellState& xw)
{
typedef SimFIBODetails::WellMap WellMap;
const auto w_ecl = schedule_->getWells(step);
const WellMap& wmap = SimFIBODetails::mapWells(w_ecl);
const std::vector<int>& resv_wells = SimFIBODetails::resvWells(wells, step, wmap);
const std::size_t number_resv_wells = resv_wells.size();
std::size_t global_number_resv_wells = number_resv_wells;
#if HAVE_MPI
if ( solver_.parallelInformation().type() == typeid(ParallelISTLInformation) )
{
const auto& info =
boost::any_cast<const ParallelISTLInformation&>(solver_.parallelInformation());
global_number_resv_wells = info.communicator().sum(global_number_resv_wells);
if ( global_number_resv_wells )
{
// At least one process has resv wells. Therefore rate converter needs
// to calculate averages over regions that might cross process
// borders. This needs to be done by all processes and therefore
// outside of the next if statement.
rateConverter_.defineState(x, boost::any_cast<const ParallelISTLInformation&>(solver_.parallelInformation()));
}
}
else
#endif
{
if ( global_number_resv_wells )
{
rateConverter_.defineState(x);
}
}
if (! resv_wells.empty()) {
const PhaseUsage& pu = props_.phaseUsage();
const std::vector<double>::size_type np = props_.numPhases();
std::vector<double> distr (np);
std::vector<double> hrates(np);
std::vector<double> prates(np);
for (std::vector<int>::const_iterator
rp = resv_wells.begin(), e = resv_wells.end();
rp != e; ++rp)
{
WellControls* ctrl = wells->ctrls[*rp];
const bool is_producer = wells->type[*rp] == PRODUCER;
// RESV control mode, all wells
{
const int rctrl = SimFIBODetails::resv_control(ctrl);
if (0 <= rctrl) {
const std::vector<double>::size_type off = (*rp) * np;
if (is_producer) {
// Convert to positive rates to avoid issues
// in coefficient calculations.
std::transform(xw.wellRates().begin() + (off + 0*np),
xw.wellRates().begin() + (off + 1*np),
prates.begin(), std::negate<double>());
} else {
std::copy(xw.wellRates().begin() + (off + 0*np),
xw.wellRates().begin() + (off + 1*np),
prates.begin());
}
const int fipreg = 0; // Hack. Ignore FIP regions.
const int well_cell_top = wells->well_cells[wells->well_connpos[*rp]];
const int pvtreg = props_.cellPvtRegionIndex()[well_cell_top];
rateConverter_.calcCoeff(fipreg, pvtreg, distr);
well_controls_iset_distr(ctrl, rctrl, & distr[0]);
}
}
// RESV control, WCONHIST wells. A bit of duplicate
// work, regrettably.
if (is_producer && wells->name[*rp] != 0) {
WellMap::const_iterator i = wmap.find(wells->name[*rp]);
if (i != wmap.end()) {
const auto* wp = i->second;
const WellProductionProperties& p =
wp->getProductionProperties(step);
if (! p.predictionMode) {
// History matching (WCONHIST/RESV)
SimFIBODetails::historyRates(pu, p, hrates);
const int fipreg = 0; // Hack. Ignore FIP regions.
const int well_cell_top = wells->well_cells[wells->well_connpos[*rp]];
const int pvtreg = props_.cellPvtRegionIndex()[well_cell_top];
rateConverter_.calcCoeff(fipreg, pvtreg, distr);
// WCONHIST/RESV target is sum of all
// observed phase rates translated to
// reservoir conditions. Recall sign
// convention: Negative for producers.
const double target =
- std::inner_product(distr.begin(), distr.end(),
hrates.begin(), 0.0);
well_controls_clear(ctrl);
well_controls_assert_number_of_phases(ctrl, int(np));
static const double invalid_alq = -std::numeric_limits<double>::max();
static const int invalid_vfp = -std::numeric_limits<int>::max();
const int ok_resv =
well_controls_add_new(RESERVOIR_RATE, target,
invalid_alq, invalid_vfp,
& distr[0], ctrl);
// For WCONHIST the BHP limit is set to 1 atm.
// or a value specified using WELTARG
double bhp_limit = (p.BHPLimit > 0) ? p.BHPLimit : unit::convert::from(1.0, unit::atm);
const int ok_bhp =
well_controls_add_new(BHP, bhp_limit,
invalid_alq, invalid_vfp,
NULL, ctrl);
if (ok_resv != 0 && ok_bhp != 0) {
xw.currentControls()[*rp] = 0;
well_controls_set_current(ctrl, 0);
}
}
}
}
}
}
if( wells )
{
for (int w = 0, nw = wells->number_of_wells; w < nw; ++w) {
WellControls* ctrl = wells->ctrls[w];
const bool is_producer = wells->type[w] == PRODUCER;
if (!is_producer && wells->name[w] != 0) {
WellMap::const_iterator i = wmap.find(wells->name[w]);
if (i != wmap.end()) {
const auto* wp = i->second;
const WellInjectionProperties& injector = wp->getInjectionProperties(step);
if (!injector.predictionMode) {
//History matching WCONINJEH
static const double invalid_alq = -std::numeric_limits<double>::max();
static const int invalid_vfp = -std::numeric_limits<int>::max();
// For WCONINJEH the BHP limit is set to a large number
// or a value specified using WELTARG
double bhp_limit = (injector.BHPLimit > 0) ? injector.BHPLimit : std::numeric_limits<double>::max();
const int ok_bhp =
well_controls_add_new(BHP, bhp_limit,
invalid_alq, invalid_vfp,
NULL, ctrl);
if (!ok_bhp) {
OPM_THROW(std::runtime_error, "Failed to add well control.");
}
}
}
}
}
}
}
template <class Implementation>
void
SimulatorBase<Implementation>::FIPUnitConvert(const UnitSystem& units,
std::vector<std::vector<double> >& fip)
{
for (size_t i = 0; i < fip.size(); ++i) {
FIPUnitConvert(units, fip[i]);
}
}
template <class Implementation>
void
SimulatorBase<Implementation>::FIPUnitConvert(const UnitSystem& units, std::vector<double>& fip)
{
if (units.getType() == UnitSystem::UnitType::UNIT_TYPE_FIELD) {
fip[0] = unit::convert::to(fip[0], unit::stb);
fip[1] = unit::convert::to(fip[1], unit::stb);
fip[2] = unit::convert::to(fip[2], 1000*unit::cubic(unit::feet));
fip[3] = unit::convert::to(fip[3], 1000*unit::cubic(unit::feet));
fip[4] = unit::convert::to(fip[4], unit::stb);
fip[5] = unit::convert::to(fip[5], unit::stb);
fip[6] = unit::convert::to(fip[6], unit::psia);
}
else if (units.getType() == UnitSystem::UnitType::UNIT_TYPE_METRIC) {
fip[6] = unit::convert::to(fip[6], unit::barsa);
}
else {
OPM_THROW(std::runtime_error, "Unsupported unit type for fluid in place output.");
}
}
template <class Implementation>
std::vector<double>
SimulatorBase<Implementation>::FIPTotals(const std::vector<std::vector<double> >& fip, const ReservoirState& state)
{
std::vector<double> totals(7, 0.0);
for (int i = 0; i < 5; ++i) {
for (size_t reg = 0; reg < fip.size(); ++reg) {
totals[i] += fip[reg][i];
}
}
const int nc = Opm::AutoDiffGrid::numCells(grid_);
const int np = state.numPhases();
const PhaseUsage& pu = props_.phaseUsage();
const DataBlock s = Eigen::Map<const DataBlock>(& state.saturation()[0], nc, np);
std::vector<double> so(nc);
std::vector<double> sg(nc);
std::vector<double> hydrocarbon(nc);
// Using dummy indices if phase not used, the columns will not be accessed below if unused.
const int oilpos = pu.phase_used[BlackoilPhases::Liquid] ? pu.phase_pos[BlackoilPhases::Liquid] : 0;
const int gaspos = pu.phase_used[BlackoilPhases::Vapour] ? pu.phase_pos[BlackoilPhases::Vapour] : 0;
const auto& soCol = s.col(oilpos);
const auto& sgCol = s.col(gaspos);
for (unsigned c = 0; c < so.size(); ++ c) {
double mySo = 0.0;
if (pu.phase_used[BlackoilPhases::Liquid]) {
mySo = soCol[c];
}
double mySg = 0.0;
if (pu.phase_used[BlackoilPhases::Vapour]) {
mySg = sgCol[c];
}
so[c] = mySo;
sg[c] = mySg;
hydrocarbon[c] = mySo + mySg;
}
const std::vector<double> p = state.pressure();
if ( ! is_parallel_run_ )
{
double tmp = 0.0;
double tmp2 = 0.0;
for (unsigned i = 0; i < p.size(); ++i) {
tmp += p[i] * geo_.poreVolume()[i] * hydrocarbon[i];
tmp2 += geo_.poreVolume()[i] * hydrocarbon[i];
}
totals[5] = geo_.poreVolume().sum();
totals[6] = tmp/tmp2;
}
else
{
#if HAVE_MPI
const auto & pinfo =
boost::any_cast<const ParallelISTLInformation&>(solver_.parallelInformation());
auto operators = std::make_tuple(Opm::Reduction::makeGlobalSumFunctor<double>(),
Opm::Reduction::makeGlobalSumFunctor<double>(),
Opm::Reduction::makeGlobalSumFunctor<double>());
std::vector<double> pav_nom(p.size());
std::vector<double> pav_denom(pav_nom.size());
for (unsigned i = 0; i < p.size(); ++i) {
pav_nom[i] = p[i] * geo_.poreVolume()[i] * hydrocarbon[i];
pav_denom[i] = geo_.poreVolume()[i] * hydrocarbon[i];
}
// using ref cref to prevent copying
auto inputs = std::make_tuple(std::cref(geo_.poreVolume()),
std::cref(pav_nom), std::cref(pav_denom));
std::tuple<double, double, double> results(0.0, 0.0, 0.0);
pinfo.computeReduction(inputs, operators, results);
using std::get;
totals[5] = get<0>(results);
totals[6] = get<1>(results)/get<2>(results);
#else
// This should never happen!
OPM_THROW(std::logic_error, "HAVE_MPI should be defined if we are running in parallel");
#endif
}
return totals;
}
template <class Implementation>
void
SimulatorBase<Implementation>::outputFluidInPlace(const std::vector<double>& oip, const std::vector<double>& cip, const UnitSystem& units, const int reg)
{
std::ostringstream ss;
if (!reg) {
ss << " ===================================================\n"
<< " : Field Totals :\n";
} else {
ss << " ===================================================\n"
<< " : FIPNUM report region "
<< std::setw(2) << reg << " :\n";
}
if (units.getType() == UnitSystem::UnitType::UNIT_TYPE_METRIC) {
ss << " : PAV =" << std::setw(14) << cip[6] << " BARSA :\n"
<< std::fixed << std::setprecision(0)
<< " : PORV =" << std::setw(14) << cip[5] << " RM3 :\n";
if (!reg) {
ss << " : Pressure is weighted by hydrocarbon pore volume :\n"
<< " : Porv volumes are taken at reference conditions :\n";
}
ss << " :--------------- Oil SM3 ---------------:-- Wat SM3 --:--------------- Gas SM3 ---------------:\n";
}
if (units.getType() == UnitSystem::UnitType::UNIT_TYPE_FIELD) {
ss << " : PAV =" << std::setw(14) << cip[6] << " PSIA :\n"
<< std::fixed << std::setprecision(0)
<< " : PORV =" << std::setw(14) << cip[5] << " RB :\n";
if (!reg) {
ss << " : Pressure is weighted by hydrocarbon pore voulme :\n"
<< " : Pore volumes are taken at reference conditions :\n";
}
ss << " :--------------- Oil STB ---------------:-- Wat STB --:--------------- Gas MSCF ---------------:\n";
}
ss << " : Liquid Vapour Total : Total : Free Dissolved Total :" << "\n"
<< ":------------------------:------------------------------------------:----------------:------------------------------------------:" << "\n"
<< ":Currently in place :" << std::setw(14) << cip[1] << std::setw(14) << cip[4] << std::setw(14) << (cip[1]+cip[4]) << ":"
<< std::setw(13) << cip[0] << " :" << std::setw(14) << (cip[2]) << std::setw(14) << cip[3] << std::setw(14) << (cip[2] + cip[3]) << ":\n"
<< ":------------------------:------------------------------------------:----------------:------------------------------------------:\n"
<< ":Originally in place :" << std::setw(14) << oip[1] << std::setw(14) << oip[4] << std::setw(14) << (oip[1]+oip[4]) << ":"
<< std::setw(13) << oip[0] << " :" << std::setw(14) << oip[2] << std::setw(14) << oip[3] << std::setw(14) << (oip[2] + oip[3]) << ":\n"
<< ":========================:==========================================:================:==========================================:\n";
OpmLog::note(ss.str());
}
template <class Implementation>
void
SimulatorBase<Implementation>::
updateListEconLimited(const std::unique_ptr<Solver>& solver,
const Schedule& schedule,
const int current_step,
const Wells* wells,
const WellState& well_state,
DynamicListEconLimited& list_econ_limited) const
{
solver->model().wellModel().updateListEconLimited(schedule, current_step, wells,
well_state, list_econ_limited);
}
template <class Implementation>
void
SimulatorBase<Implementation>::
initHysteresisParams(ReservoirState& state)
{
typedef std::vector<double> VectorType;
const VectorType& somax = state.getCellData( "SOMAX" );
VectorType& pcSwMdc_ow = state.getCellData( "PCSWMDC_OW" );
VectorType& krnSwMdc_ow = state.getCellData( "KRNSWMDC_OW" );
VectorType& pcSwMdc_go = state.getCellData( "PCSWMDC_GO" );
VectorType& krnSwMdc_go = state.getCellData( "KRNSWMDC_GO" );
props_.setSatOilMax(somax);
props_.setOilWaterHystParams(pcSwMdc_ow, krnSwMdc_ow, allcells_);
props_.setGasOilHystParams(pcSwMdc_go, krnSwMdc_go, allcells_);
}
} // namespace Opm

View File

@ -1,76 +0,0 @@
/*
Copyright 2013, 2015 SINTEF ICT, Applied Mathematics.
Copyright 2015 Andreas Lauser
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 3 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 <http://www.gnu.org/licenses/>.
*/
#ifndef OPM_SIMULATORFULLYIMPLICITBLACKOIL_HEADER_INCLUDED
#define OPM_SIMULATORFULLYIMPLICITBLACKOIL_HEADER_INCLUDED
#include <opm/autodiff/SimulatorBase.hpp>
#include <opm/autodiff/BlackoilModel.hpp>
#include <opm/autodiff/NonlinearSolver.hpp>
namespace Opm {
template <class GridT>
class SimulatorFullyImplicitBlackoil;
class StandardWells;
template <class GridT>
struct SimulatorTraits<SimulatorFullyImplicitBlackoil<GridT> >
{
typedef WellStateFullyImplicitBlackoil WellState;
typedef BlackoilState ReservoirState;
typedef BlackoilOutputWriter OutputWriter;
typedef GridT Grid;
typedef BlackoilModel<Grid> Model;
typedef NonlinearSolver<Model> Solver;
typedef StandardWells WellModel;
};
/// a simulator for the blackoil model
template <class GridT>
class SimulatorFullyImplicitBlackoil
: public SimulatorBase<SimulatorFullyImplicitBlackoil<GridT> >
{
typedef SimulatorBase<SimulatorFullyImplicitBlackoil<GridT> > Base;
public:
// forward the constructor to the base class
SimulatorFullyImplicitBlackoil(const ParameterGroup& param,
const typename Base::Grid& grid,
DerivedGeology& geo,
BlackoilPropsAdFromDeck& props,
const RockCompressibility* rock_comp_props,
NewtonIterationBlackoilInterface& linsolver,
const double* gravity,
const bool disgas,
const bool vapoil,
std::shared_ptr<EclipseState> eclipse_state,
std::shared_ptr<Schedule> schedule,
std::shared_ptr<SummaryConfig> summaryConfig,
BlackoilOutputWriter& output_writer,
const std::vector<double>& threshold_pressures_by_face,
const std::unordered_set<std::string>& defunct_well_names)
: Base(param, grid, geo, props, rock_comp_props, linsolver, gravity, disgas, vapoil,
eclipse_state, schedule, summaryConfig, output_writer, threshold_pressures_by_face, defunct_well_names)
{}
};
} // namespace Opm
#endif // OPM_SIMULATORFULLYIMPLICITBLACKOIL_HEADER_INCLUDED

View File

@ -1,370 +0,0 @@
/*
Copyright (c) 2014 SINTEF ICT, Applied Mathematics.
Copyright (c) 2015-2016 IRIS AS
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 3 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 <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include "SimulatorFullyImplicitBlackoilOutput.hpp"
#include <opm/common/data/SimulationDataContainer.hpp>
#include <opm/parser/eclipse/EclipseState/InitConfig/InitConfig.hpp>
#include <opm/output/eclipse/RestartValue.hpp>
#include <opm/output/data/Cells.hpp>
#include <opm/output/eclipse/RestartValue.hpp>
#include <opm/core/simulator/BlackoilState.hpp>
#include <opm/core/utility/DataMap.hpp>
#include <opm/autodiff/Compat.hpp>
#include <opm/simulators/vtk/writeVtkData.hpp>
#include <opm/common/ErrorMacros.hpp>
#include <opm/core/utility/miscUtilities.hpp>
#include <opm/parser/eclipse/Units/Units.hpp>
#include <opm/autodiff/GridHelpers.hpp>
#include <sstream>
#include <iomanip>
#include <fstream>
#include <boost/filesystem.hpp>
//For OutputWriterHelper
#include <map>
#include <opm/parser/eclipse/Units/UnitSystem.hpp>
#ifdef HAVE_OPM_GRID
#include <opm/common/utility/platform_dependent/disable_warnings.h>
#include <dune/common/version.hh>
#include <dune/grid/io/file/vtk/vtkwriter.hh>
#include <opm/common/utility/platform_dependent/reenable_warnings.h>
#endif
namespace Opm
{
void outputStateVtk(const UnstructuredGrid& grid,
const SimulationDataContainer& state,
const int step,
const std::string& output_dir)
{
// Write data in VTK format.
std::ostringstream vtkfilename;
vtkfilename << output_dir << "/vtk_files";
ensureDirectoryExists(vtkfilename.str());
vtkfilename << "/output-" << std::setw(3) << std::setfill('0') << step << ".vtu";
std::ofstream vtkfile(vtkfilename.str().c_str());
if (!vtkfile) {
OPM_THROW(std::runtime_error, "Failed to open " << vtkfilename.str());
}
Opm::DataMap dm;
dm["saturation"] = &state.saturation();
dm["pressure"] = &state.pressure();
std::vector<double> cell_velocity;
Opm::estimateCellVelocity(AutoDiffGrid::numCells(grid),
AutoDiffGrid::numFaces(grid),
AutoDiffGrid::beginFaceCentroids(grid),
AutoDiffGrid::faceCells(grid),
AutoDiffGrid::beginCellCentroids(grid),
AutoDiffGrid::beginCellVolumes(grid),
AutoDiffGrid::dimensions(grid),
state.faceflux(), cell_velocity);
dm["velocity"] = &cell_velocity;
Opm::writeVtkData(grid, dm, vtkfile);
}
void outputWellStateMatlab(const Opm::WellState& well_state,
const int step,
const std::string& output_dir)
{
Opm::DataMap dm;
dm["bhp"] = &well_state.bhp();
dm["wellrates"] = &well_state.wellRates();
// Write data (not grid) in Matlab format
for (Opm::DataMap::const_iterator it = dm.begin(); it != dm.end(); ++it) {
std::ostringstream fname;
fname << output_dir << "/" << it->first;
ensureDirectoryExists(fname.str());
fname << "/" << std::setw(3) << std::setfill('0') << step << ".txt";
std::ofstream file(fname.str().c_str());
if (!file) {
OPM_THROW(std::runtime_error,"Failed to open " << fname.str());
}
file.precision(15);
const std::vector<double>& d = *(it->second);
std::copy(d.begin(), d.end(), std::ostream_iterator<double>(file, "\n"));
}
}
#if 0
void outputWaterCut(const Opm::Watercut& watercut,
const std::string& output_dir)
{
// Write water cut curve.
std::string fname = output_dir + "/watercut.txt";
std::ofstream os(fname.c_str());
if (!os) {
OPM_THROW(std::runtime_error, "Failed to open " << fname);
}
watercut.write(os);
}
void outputWellReport(const Opm::WellReport& wellreport,
const std::string& output_dir)
{
// Write well report.
std::string fname = output_dir + "/wellreport.txt";
std::ofstream os(fname.c_str());
if (!os) {
OPM_THROW(std::runtime_error, "Failed to open " << fname);
}
wellreport.write(os);
}
#endif
#ifdef HAVE_OPM_GRID
void outputStateVtk(const Dune::CpGrid& grid,
const Opm::SimulationDataContainer& state,
const int step,
const std::string& output_dir)
{
// Write data in VTK format.
std::ostringstream vtkfilename;
std::ostringstream vtkpath;
vtkpath << output_dir << "/vtk_files";
vtkpath << "/output-" << std::setw(3) << std::setfill('0') << step;
ensureDirectoryExists(vtkpath.str());
vtkfilename << "output-" << std::setw(3) << std::setfill('0') << step;
Dune::VTKWriter<Dune::CpGrid::LeafGridView> writer(grid.leafGridView(), Dune::VTK::nonconforming);
writer.addCellData(state.saturation(), "saturation", state.numPhases());
writer.addCellData(state.pressure(), "pressure", 1);
std::vector<double> cell_velocity;
Opm::estimateCellVelocity(AutoDiffGrid::numCells(grid),
AutoDiffGrid::numFaces(grid),
AutoDiffGrid::beginFaceCentroids(grid),
AutoDiffGrid::faceCells(grid),
AutoDiffGrid::beginCellCentroids(grid),
AutoDiffGrid::beginCellVolumes(grid),
AutoDiffGrid::dimensions(grid),
state.faceflux(), cell_velocity);
writer.addCellData(cell_velocity, "velocity", Dune::CpGrid::dimension);
writer.pwrite(vtkfilename.str(), vtkpath.str(), std::string("."), Dune::VTK::ascii);
}
#endif
namespace detail {
struct WriterCall : public ThreadHandle :: ObjectInterface
{
BlackoilOutputWriter& writer_;
std::unique_ptr< SimulatorTimerInterface > timer_;
const SimulationDataContainer state_;
const WellStateFullyImplicitBlackoil wellState_;
data::Solution simProps_;
std::map<std::string, double> miscSummaryData_;
RestartValue::ExtraVector extraRestartData_;
const bool substep_;
explicit WriterCall( BlackoilOutputWriter& writer,
const SimulatorTimerInterface& timer,
const SimulationDataContainer& state,
const WellStateFullyImplicitBlackoil& wellState,
const data::Solution& simProps,
const std::map<std::string, double>& miscSummaryData,
const RestartValue::ExtraVector& extraRestartData,
bool substep)
: writer_( writer ),
timer_( timer.clone() ),
state_( state ),
wellState_( wellState ),
simProps_( simProps ),
miscSummaryData_( miscSummaryData ),
extraRestartData_( extraRestartData ),
substep_( substep )
{
}
// callback to writer's serial writeTimeStep method
void run ()
{
// write data
writer_.writeTimeStepSerial( *timer_, state_, wellState_, simProps_, miscSummaryData_, extraRestartData_, substep_ );
}
};
}
void
BlackoilOutputWriter::
writeTimeStepWithoutCellProperties(
const SimulatorTimerInterface& timer,
const SimulationDataContainer& localState,
const WellStateFullyImplicitBlackoil& localWellState,
const std::map<std::string, double>& miscSummaryData,
const RestartValue::ExtraVector& extraRestartData,
bool substep)
{
data::Solution localCellData{};
if( output_ )
{
localCellData = simToSolution(localState, restart_double_si_, phaseUsage_); // Get "normal" data (SWAT, PRESSURE, ...);
}
writeTimeStepWithCellProperties(timer, localState, localCellData ,
localWellState, miscSummaryData, extraRestartData, substep);
}
void
BlackoilOutputWriter::
writeTimeStepWithCellProperties(
const SimulatorTimerInterface& timer,
const SimulationDataContainer& localState,
const data::Solution& localCellData,
const WellStateFullyImplicitBlackoil& localWellState,
const std::map<std::string, double>& miscSummaryData,
const RestartValue::ExtraVector& extraRestartData,
bool substep)
{
// VTK output (is parallel if grid is parallel)
if( vtkWriter_ ) {
vtkWriter_->writeTimeStep( timer, localState, localWellState, false );
}
bool isIORank = output_ ;
if( parallelOutput_ && parallelOutput_->isParallel() )
{
// If this is not the initial write and no substep, then the well
// state used in the computation is actually the one of the last
// step. We need that well state for the gathering. Otherwise
// It an exception with a message like "global state does not
// contain well ..." might be thrown.
int wellStateStepNumber = ( ! substep && timer.reportStepNum() > 0) ?
(timer.reportStepNum() - 1) : timer.reportStepNum();
// collect all solutions to I/O rank
isIORank = parallelOutput_->collectToIORank( localState, localWellState,
localCellData,
wellStateStepNumber );
// Note that at this point the extraData are assumed to be global, i.e. identical across all processes.
}
const data::Solution& cellData = ( parallelOutput_ && parallelOutput_->isParallel() ) ? parallelOutput_->globalCellData() : localCellData;
const SimulationDataContainer& state = (parallelOutput_ && parallelOutput_->isParallel() ) ? parallelOutput_->globalReservoirState() : localState;
const WellStateFullyImplicitBlackoil& wellState = (parallelOutput_ && parallelOutput_->isParallel() ) ? parallelOutput_->globalWellState() : localWellState;
// serial output is only done on I/O rank
int err = 0;
std::string emsg;
if( isIORank )
{
if( asyncOutput_ ) {
// dispatch the write call to the extra thread
asyncOutput_->dispatch( detail::WriterCall( *this, timer, state, wellState, cellData, miscSummaryData, extraRestartData, substep ) );
}
else {
// just write the data to disk
try {
writeTimeStepSerial( timer, state, wellState, cellData, miscSummaryData, extraRestartData, substep );
} catch (std::runtime_error& msg) {
err = 1;
emsg = msg.what();
}
}
}
if (!asyncOutput_) {
#if HAVE_MPI
MPI_Bcast(&err, 1, MPI_INT, 0, MPI_COMM_WORLD);
#endif
if (err) {
if (isIORank) {
throw std::runtime_error(emsg);
} else {
throw std::runtime_error("I/O process encountered problems.");
}
}
}
}
void
BlackoilOutputWriter::
writeTimeStepSerial(const SimulatorTimerInterface& timer,
const SimulationDataContainer& state,
const WellStateFullyImplicitBlackoil& wellState,
const data::Solution& simProps,
const std::map<std::string, double>& miscSummaryData,
const RestartValue::ExtraVector& extraRestartData,
bool substep)
{
// Matlab output
if( matlabWriter_ ) {
matlabWriter_->writeTimeStep( timer, state, wellState, substep );
}
// ECL output
if ( eclIO_ )
{
const auto& initConfig = eclipseState_.getInitConfig();
if (initConfig.restartRequested() && ((initConfig.getRestartStep()) == (timer.currentStepNum()))) {
std::cout << "Skipping restart write in start of step " << timer.currentStepNum() << std::endl;
} else {
// ... insert "extra" data (KR, VISC, ...)
const int reportStepForOutput = substep ? timer.reportStepNum() + 1 : timer.reportStepNum();
RestartValue restart_value(simProps, wellState.report(phaseUsage_, globalCellIdxMap_));
for (const auto& extra_pair : extraRestartData) {
const RestartKey& restart_key = extra_pair.first;
const std::vector<double>& data = extra_pair.second;
restart_value.addExtra(restart_key.key, restart_key.dim, data);
}
// Here we should check if the THPRES option is active, and in that case
// add the THPRES values to the extra values object.
eclIO_->writeTimeStep(reportStepForOutput,
substep,
timer.simulationTimeElapsed(),
restart_value,
miscSummaryData,
{}, //regionData
{}, //blockData
restart_double_si_);
}
}
}
bool BlackoilOutputWriter::isRestart() const {
const auto& initconfig = eclipseState_.getInitConfig();
return initconfig.restartRequested();
}
bool BlackoilOutputWriter::requireFIPNUM() const {
return summaryConfig_.requireFIPNUM();
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,606 +0,0 @@
/*
Copyright 2013 SINTEF ICT, Applied Mathematics.
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 3 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 <http://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif // HAVE_CONFIG_H
#include <opm/autodiff/SimulatorIncompTwophaseAd.hpp>
#include <opm/autodiff/GridHelpers.hpp>
#include <opm/common/utility/parameters/ParameterGroup.hpp>
#include <opm/common/ErrorMacros.hpp>
#include <opm/core/pressure/IncompTpfa.hpp>
#include <opm/grid/UnstructuredGrid.h>
#include <opm/core/wells.h>
#include <opm/core/well_controls.h>
#include <opm/core/pressure/flow_bc.h>
#include <opm/core/simulator/SimulatorReport.hpp>
#include <opm/simulators/timestepping/SimulatorTimer.hpp>
#include <opm/grid/utility/StopWatch.hpp>
#include <opm/core/utility/DataMap.hpp>
#include <opm/simulators/vtk/writeVtkData.hpp>
#include <opm/core/utility/miscUtilities.hpp>
#include <opm/core/wells/WellsManager.hpp>
#include <opm/core/props/IncompPropertiesInterface.hpp>
#include <opm/core/props/rock/RockCompressibility.hpp>
#include <opm/core/simulator/TwophaseState.hpp>
#include <opm/core/simulator/WellState.hpp>
#include <opm/core/transport/reorder/TransportSolverTwophaseReorder.hpp>
#include <opm/autodiff/TransportSolverTwophaseAd.hpp>
#include <opm/simulators/ensureDirectoryExists.hpp>
#include <boost/filesystem.hpp>
#include <boost/lexical_cast.hpp>
#include <memory>
#include <numeric>
#include <fstream>
#include <iostream>
namespace Opm
{
class SimulatorIncompTwophaseAd::Impl
{
public:
Impl(const ParameterGroup& param,
const UnstructuredGrid& grid,
const IncompPropertiesInterface& props,
const RockCompressibility* rock_comp_props,
WellsManager& wells_manager,
const std::vector<double>& src,
const FlowBoundaryConditions* bcs,
LinearSolverInterface& linsolver,
const double* gravity);
SimulatorReport run(SimulatorTimer& timer,
TwophaseState& state,
WellState& well_state);
private:
// Data.
// Parameters for output.
bool output_;
bool output_vtk_;
std::string output_dir_;
int output_interval_;
// Parameters for well control
bool check_well_controls_;
int max_well_control_iterations_;
// Parameters for transport solver.
int num_transport_substeps_;
std::string transport_solver_type_;
bool use_segregation_split_;
// Observed objects.
const UnstructuredGrid& grid_;
const IncompPropertiesInterface& props_;
const RockCompressibility* rock_comp_props_;
WellsManager& wells_manager_;
const Wells* wells_;
const std::vector<double>& src_;
const FlowBoundaryConditions* bcs_;
// Solvers
IncompTpfa psolver_;
std::unique_ptr<TransportSolverTwophaseInterface> tsolver_;
// Misc. data
std::vector<int> allcells_;
};
SimulatorIncompTwophaseAd::SimulatorIncompTwophaseAd(const ParameterGroup& param,
const UnstructuredGrid& grid,
const IncompPropertiesInterface& props,
const RockCompressibility* rock_comp_props,
WellsManager& wells_manager,
const std::vector<double>& src,
const FlowBoundaryConditions* bcs,
LinearSolverInterface& linsolver,
const double* gravity)
{
pimpl_.reset(new Impl(param, grid, props, rock_comp_props, wells_manager, src, bcs, linsolver, gravity));
}
SimulatorReport SimulatorIncompTwophaseAd::run(SimulatorTimer& timer,
TwophaseState& state,
WellState& well_state)
{
return pimpl_->run(timer, state, well_state);
}
static void reportVolumes(std::ostream &os, double satvol[2], double tot_porevol_init,
double tot_injected[2], double tot_produced[2],
double injected[2], double produced[2],
double init_satvol[2])
{
os.precision(5);
const int width = 18;
os << "\nVolume balance report (all numbers relative to total pore volume).\n";
os << " Saturated volumes: "
<< std::setw(width) << satvol[0]/tot_porevol_init
<< std::setw(width) << satvol[1]/tot_porevol_init << std::endl;
os << " Injected volumes: "
<< std::setw(width) << injected[0]/tot_porevol_init
<< std::setw(width) << injected[1]/tot_porevol_init << std::endl;
os << " Produced volumes: "
<< std::setw(width) << produced[0]/tot_porevol_init
<< std::setw(width) << produced[1]/tot_porevol_init << std::endl;
os << " Total inj volumes: "
<< std::setw(width) << tot_injected[0]/tot_porevol_init
<< std::setw(width) << tot_injected[1]/tot_porevol_init << std::endl;
os << " Total prod volumes: "
<< std::setw(width) << tot_produced[0]/tot_porevol_init
<< std::setw(width) << tot_produced[1]/tot_porevol_init << std::endl;
os << " In-place + prod - inj: "
<< std::setw(width) << (satvol[0] + tot_produced[0] - tot_injected[0])/tot_porevol_init
<< std::setw(width) << (satvol[1] + tot_produced[1] - tot_injected[1])/tot_porevol_init << std::endl;
os << " Init - now - pr + inj: "
<< std::setw(width) << (init_satvol[0] - satvol[0] - tot_produced[0] + tot_injected[0])/tot_porevol_init
<< std::setw(width) << (init_satvol[1] - satvol[1] - tot_produced[1] + tot_injected[1])/tot_porevol_init
<< std::endl;
os.precision(8);
}
static void outputStateVtk(const UnstructuredGrid& grid,
const Opm::TwophaseState& state,
const int step,
const std::string& output_dir)
{
// Write data in VTK format.
std::ostringstream vtkfilename;
vtkfilename << output_dir << "/vtk_files";
ensureDirectoryExists(vtkfilename.str());
vtkfilename << "/output-" << std::setw(3) << std::setfill('0') << step << ".vtu";
std::ofstream vtkfile(vtkfilename.str().c_str());
if (!vtkfile) {
OPM_THROW(std::runtime_error, "Failed to open " << vtkfilename.str());
}
Opm::DataMap dm;
dm["saturation"] = &state.saturation();
dm["pressure"] = &state.pressure();
std::vector<double> cell_velocity;
Opm::estimateCellVelocity(grid, state.faceflux(), cell_velocity);
dm["velocity"] = &cell_velocity;
Opm::writeVtkData(grid, dm, vtkfile);
}
static void outputVectorMatlab(const std::string& name,
const std::vector<int>& vec,
const int step,
const std::string& output_dir)
{
std::ostringstream fname;
fname << output_dir << "/" << name;
ensureDirectoryExists(fname.str());
fname << "/" << std::setw(3) << std::setfill('0') << step << ".txt";
std::ofstream file(fname.str().c_str());
if (!file) {
OPM_THROW(std::runtime_error, "Failed to open " << fname.str());
}
std::copy(vec.begin(), vec.end(), std::ostream_iterator<double>(file, "\n"));
}
static void outputStateMatlab(const UnstructuredGrid& grid,
const Opm::TwophaseState& state,
const int step,
const std::string& output_dir)
{
Opm::DataMap dm;
dm["saturation"] = &state.saturation();
dm["pressure"] = &state.pressure();
std::vector<double> cell_velocity;
Opm::estimateCellVelocity(grid, state.faceflux(), cell_velocity);
dm["velocity"] = &cell_velocity;
// Write data (not grid) in Matlab format
for (Opm::DataMap::const_iterator it = dm.begin(); it != dm.end(); ++it) {
std::ostringstream fname;
fname << output_dir << "/" << it->first;
ensureDirectoryExists(fname.str());
fname << "/" << std::setw(3) << std::setfill('0') << step << ".txt";
std::ofstream file(fname.str().c_str());
if (!file) {
OPM_THROW(std::runtime_error, "Failed to open " << fname.str());
}
file.precision(15);
const std::vector<double>& d = *(it->second);
std::copy(d.begin(), d.end(), std::ostream_iterator<double>(file, "\n"));
}
}
static void outputWaterCut(const Opm::Watercut& watercut,
const std::string& output_dir)
{
// Write water cut curve.
std::string fname = output_dir + "/watercut.txt";
std::ofstream os(fname.c_str());
if (!os) {
OPM_THROW(std::runtime_error, "Failed to open " << fname);
}
watercut.write(os);
}
static void outputWellReport(const Opm::WellReport& wellreport,
const std::string& output_dir)
{
// Write well report.
std::string fname = output_dir + "/wellreport.txt";
std::ofstream os(fname.c_str());
if (!os) {
OPM_THROW(std::runtime_error, "Failed to open " << fname);
}
wellreport.write(os);
}
static bool allNeumannBCs(const FlowBoundaryConditions* bcs)
{
if (bcs == NULL) {
return true;
} else {
return std::find(bcs->type, bcs->type + bcs->nbc, BC_PRESSURE)
== bcs->type + bcs->nbc;
}
}
static bool allRateWells(const Wells* wells)
{
if (wells == NULL) {
return true;
}
const int nw = wells->number_of_wells;
for (int w = 0; w < nw; ++w) {
const WellControls* wc = wells->ctrls[w];
if (well_controls_well_is_open( wc )) {
if (well_controls_get_current_type(wc) == BHP ) {
return false;
}
}
}
return true;
}
SimulatorIncompTwophaseAd::Impl::Impl(const ParameterGroup& param,
const UnstructuredGrid& grid,
const IncompPropertiesInterface& props,
const RockCompressibility* rock_comp_props,
WellsManager& wells_manager,
const std::vector<double>& src,
const FlowBoundaryConditions* bcs,
LinearSolverInterface& linsolver,
const double* gravity)
: transport_solver_type_(param.getDefault<std::string>("transport_solver_type", "ad")),
use_segregation_split_(param.getDefault("use_segregation_split", false)),
grid_(grid),
props_(props),
rock_comp_props_(rock_comp_props),
wells_manager_(wells_manager),
wells_(wells_manager.c_wells()),
src_(src),
bcs_(bcs),
psolver_(grid, props, rock_comp_props, linsolver,
param.getDefault("nl_pressure_residual_tolerance", 0.0),
param.getDefault("nl_pressure_change_tolerance", 1.0),
param.getDefault("nl_pressure_maxiter", 10),
gravity, wells_manager.c_wells(), src, bcs)
{
// Initialize transport solver.
if (transport_solver_type_ == "reorder") {
tsolver_.reset(new Opm::TransportSolverTwophaseReorder(grid,
props,
use_segregation_split_ ? gravity : NULL,
param.getDefault("nl_tolerance", 1e-9),
param.getDefault("nl_maxiter", 30)));
} else if (transport_solver_type_ == "ad") {
if (rock_comp_props && rock_comp_props->isActive()) {
OPM_THROW(std::runtime_error, "The implicit ad transport solver cannot handle rock compressibility.");
}
if (use_segregation_split_) {
OPM_THROW(std::runtime_error, "The implicit ad transport solver is not set up to use segregation splitting.");
}
std::vector<double> porevol;
computePorevolume(grid, props.porosity(), porevol);
tsolver_.reset(new Opm::TransportSolverTwophaseAd(grid,
props,
linsolver,
gravity,
param));
} else {
OPM_THROW(std::runtime_error, "Unknown transport solver type: " << transport_solver_type_);
}
// For output.
output_ = param.getDefault("output", true);
if (output_) {
output_vtk_ = param.getDefault("output_vtk", true);
output_dir_ = param.getDefault("output_dir", std::string("output"));
// Ensure that output dir exists
ensureDirectoryExists(output_dir_);
output_interval_ = param.getDefault("output_interval", 1);
}
// Well control related init.
check_well_controls_ = param.getDefault("check_well_controls", false);
max_well_control_iterations_ = param.getDefault("max_well_control_iterations", 10);
// Transport related init.
num_transport_substeps_ = param.getDefault("num_transport_substeps", 1);
// Misc init.
const int num_cells = Opm::AutoDiffGrid::numCells(grid);
allcells_.resize(num_cells);
for (int cell = 0; cell < num_cells; ++cell) {
allcells_[cell] = cell;
}
}
SimulatorReport SimulatorIncompTwophaseAd::Impl::run(SimulatorTimer& timer,
TwophaseState& state,
WellState& well_state)
{
std::vector<double> transport_src;
// Initialisation.
std::vector<double> porevol;
if (rock_comp_props_ && rock_comp_props_->isActive()) {
computePorevolume(grid_, props_.porosity(), *rock_comp_props_, state.pressure(), porevol);
} else {
computePorevolume(grid_, props_.porosity(), porevol);
}
const double tot_porevol_init = std::accumulate(porevol.begin(), porevol.end(), 0.0);
std::vector<double> initial_porevol = porevol;
// Main simulation loop.
Opm::time::StopWatch pressure_timer;
double ptime = 0.0;
Opm::time::StopWatch transport_timer;
double ttime = 0.0;
Opm::time::StopWatch step_timer;
Opm::time::StopWatch total_timer;
total_timer.start();
double init_satvol[2] = { 0.0 };
double satvol[2] = { 0.0 };
double tot_injected[2] = { 0.0 };
double tot_produced[2] = { 0.0 };
Opm::computeSaturatedVol(porevol, state.saturation(), init_satvol);
std::cout << "\nInitial saturations are " << init_satvol[0]/tot_porevol_init
<< " " << init_satvol[1]/tot_porevol_init << std::endl;
Opm::Watercut watercut;
watercut.push(0.0, 0.0, 0.0);
Opm::WellReport wellreport;
std::vector<double> fractional_flows;
std::vector<double> well_resflows_phase;
if (wells_) {
well_resflows_phase.resize((wells_->number_of_phases)*(wells_->number_of_wells), 0.0);
wellreport.push(props_, *wells_, state.saturation(), 0.0, well_state.bhp(), well_state.perfRates());
}
std::fstream tstep_os;
if (output_) {
std::string filename = output_dir_ + "/step_timing.param";
tstep_os.open(filename.c_str(), std::fstream::out | std::fstream::app);
}
for (; !timer.done(); ++timer) {
// Report timestep and (optionally) write state to disk.
step_timer.start();
timer.report(std::cout);
if (output_ && (timer.currentStepNum() % output_interval_ == 0)) {
if (output_vtk_) {
outputStateVtk(grid_, state, timer.currentStepNum(), output_dir_);
}
outputStateMatlab(grid_, state, timer.currentStepNum(), output_dir_);
if (transport_solver_type_ == "reorder") {
// This use of dynamic_cast is not ideal, but should be safe.
outputVectorMatlab(std::string("reorder_it"),
dynamic_cast<const TransportSolverTwophaseReorder&>(*tsolver_).getReorderIterations(),
timer.currentStepNum(), output_dir_);
}
}
SimulatorReport sreport;
// Solve pressure equation.
if (check_well_controls_) {
computeFractionalFlow(props_, allcells_, state.saturation(), fractional_flows);
wells_manager_.applyExplicitReinjectionControls(well_resflows_phase, well_resflows_phase);
}
bool well_control_passed = !check_well_controls_;
int well_control_iteration = 0;
do {
// Run solver.
pressure_timer.start();
std::vector<double> initial_pressure = state.pressure();
psolver_.solve(timer.currentStepLength(), state, well_state);
// Renormalize pressure if rock is incompressible, and
// there are no pressure conditions (bcs or wells).
// It is deemed sufficient for now to renormalize
// using geometric volume instead of pore volume.
if ((rock_comp_props_ == NULL || !rock_comp_props_->isActive())
&& allNeumannBCs(bcs_) && allRateWells(wells_)) {
// Compute average pressures of previous and last
// step, and total volume.
double av_prev_press = 0.0;
double av_press = 0.0;
double tot_vol = 0.0;
const int num_cells = Opm::AutoDiffGrid::numCells(grid_);
for (int cell = 0; cell < num_cells; ++cell) {
av_prev_press += initial_pressure[cell]*
Opm::AutoDiffGrid::cellVolume(grid_, cell);
av_press += state.pressure()[cell]*
Opm::AutoDiffGrid::cellVolume(grid_, cell);
tot_vol += Opm::AutoDiffGrid::cellVolume(grid_, cell);
}
// Renormalization constant
const double ren_const = (av_prev_press - av_press)/tot_vol;
for (int cell = 0; cell < num_cells; ++cell) {
state.pressure()[cell] += ren_const;
}
const int num_wells = (wells_ == NULL) ? 0 : wells_->number_of_wells;
for (int well = 0; well < num_wells; ++well) {
well_state.bhp()[well] += ren_const;
}
}
// Stop timer and report.
pressure_timer.stop();
double pt = pressure_timer.secsSinceStart();
std::cout << "Pressure solver took: " << pt << " seconds." << std::endl;
ptime += pt;
sreport.pressure_time = pt;
// Optionally, check if well controls are satisfied.
if (check_well_controls_) {
Opm::computePhaseFlowRatesPerWell(*wells_,
well_state.perfRates(),
fractional_flows,
well_resflows_phase);
std::cout << "Checking well conditions." << std::endl;
// For testing we set surface := reservoir
well_control_passed = wells_manager_.conditionsMet(well_state.bhp(), well_resflows_phase, well_resflows_phase);
++well_control_iteration;
if (!well_control_passed && well_control_iteration > max_well_control_iterations_) {
OPM_THROW(std::runtime_error, "Could not satisfy well conditions in " << max_well_control_iterations_ << " tries.");
}
if (!well_control_passed) {
std::cout << "Well controls not passed, solving again." << std::endl;
} else {
std::cout << "Well conditions met." << std::endl;
}
}
} while (!well_control_passed);
// Update pore volumes if rock is compressible.
if (rock_comp_props_ && rock_comp_props_->isActive()) {
initial_porevol = porevol;
computePorevolume(grid_, props_.porosity(), *rock_comp_props_, state.pressure(), porevol);
}
// Process transport sources (to include bdy terms and well flows).
Opm::computeTransportSource(grid_, src_, state.faceflux(), 1.0,
wells_, well_state.perfRates(), transport_src);
// Solve transport.
transport_timer.start();
double stepsize = timer.currentStepLength();
if (num_transport_substeps_ != 1) {
stepsize /= double(num_transport_substeps_);
std::cout << "Making " << num_transport_substeps_ << " transport substeps." << std::endl;
}
double injected[2] = { 0.0 };
double produced[2] = { 0.0 };
for (int tr_substep = 0; tr_substep < num_transport_substeps_; ++tr_substep) {
tsolver_->solve(&initial_porevol[0], &transport_src[0], stepsize, state);
double substep_injected[2] = { 0.0 };
double substep_produced[2] = { 0.0 };
Opm::computeInjectedProduced(props_, state.saturation(), transport_src, stepsize,
substep_injected, substep_produced);
injected[0] += substep_injected[0];
injected[1] += substep_injected[1];
produced[0] += substep_produced[0];
produced[1] += substep_produced[1];
if (transport_solver_type_ == "reorder" && use_segregation_split_) {
// Again, unfortunate but safe use of dynamic_cast.
// Possible solution: refactor gravity solver to its own class.
dynamic_cast<TransportSolverTwophaseReorder&>(*tsolver_)
.solveGravity(&initial_porevol[0], stepsize, state);
}
watercut.push(timer.simulationTimeElapsed() + timer.currentStepLength(),
produced[0]/(produced[0] + produced[1]),
tot_produced[0]/tot_porevol_init);
if (wells_) {
wellreport.push(props_, *wells_, state.saturation(),
timer.simulationTimeElapsed() + timer.currentStepLength(),
well_state.bhp(), well_state.perfRates());
}
}
transport_timer.stop();
double tt = transport_timer.secsSinceStart();
sreport.transport_time = tt;
std::cout << "Transport solver took: " << tt << " seconds." << std::endl;
ttime += tt;
// Report volume balances.
Opm::computeSaturatedVol(porevol, state.saturation(), satvol);
tot_injected[0] += injected[0];
tot_injected[1] += injected[1];
tot_produced[0] += produced[0];
tot_produced[1] += produced[1];
reportVolumes(std::cout,satvol, tot_porevol_init,
tot_injected, tot_produced,
injected, produced,
init_satvol);
sreport.total_time = step_timer.secsSinceStart();
if (output_) {
sreport.reportParam(tstep_os);
}
}
if (output_) {
if (output_vtk_) {
outputStateVtk(grid_, state, timer.currentStepNum(), output_dir_);
}
outputStateMatlab(grid_, state, timer.currentStepNum(), output_dir_);
if (transport_solver_type_ == "reorder") {
// This use of dynamic_cast is not ideal, but should be safe.
outputVectorMatlab(std::string("reorder_it"),
dynamic_cast<const TransportSolverTwophaseReorder&>(*tsolver_).getReorderIterations(),
timer.currentStepNum(), output_dir_);
}
outputWaterCut(watercut, output_dir_);
if (wells_) {
outputWellReport(wellreport, output_dir_);
}
tstep_os.close();
}
total_timer.stop();
SimulatorReport report;
report.pressure_time = ptime;
report.transport_time = ttime;
report.total_time = total_timer.secsSinceStart();
return report;
}
} // namespace Opm

View File

@ -1,99 +0,0 @@
/*
Copyright 2013 SINTEF ICT, Applied Mathematics.
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 3 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 <http://www.gnu.org/licenses/>.
*/
#ifndef OPM_SIMULATORINCOMPTWOPHASEAD_HEADER_INCLUDED
#define OPM_SIMULATORINCOMPTWOPHASEAD_HEADER_INCLUDED
#include <memory>
#include <vector>
struct UnstructuredGrid;
struct Wells;
struct FlowBoundaryConditions;
namespace Opm
{
class ParameterGroup;
class IncompPropertiesInterface;
class RockCompressibility;
class WellsManager;
class LinearSolverInterface;
class SimulatorTimer;
class TwophaseState;
class WellState;
struct SimulatorReport;
/// Class collecting all necessary components for a two-phase simulation.
class SimulatorIncompTwophaseAd
{
public:
/// Initialise from parameters and objects to observe.
/// \param[in] param parameters, this class accepts the following:
/// parameter (default) effect
/// -----------------------------------------------------------
/// output (true) write output to files?
/// output_dir ("output") output directoty
/// output_interval (1) output every nth step
/// nl_pressure_residual_tolerance (0.0) pressure solver residual tolerance (in Pascal)
/// nl_pressure_change_tolerance (1.0) pressure solver change tolerance (in Pascal)
/// nl_pressure_maxiter (10) max nonlinear iterations in pressure
/// nl_maxiter (30) max nonlinear iterations in transport
/// nl_tolerance (1e-9) transport solver absolute residual tolerance
/// num_transport_substeps (1) number of transport steps per pressure step
/// use_segregation_split (false) solve for gravity segregation (if false,
/// segregation is ignored).
///
/// \param[in] grid grid data structure
/// \param[in] props fluid and rock properties
/// \param[in] rock_comp_props if non-null, rock compressibility properties
/// \param[in] well_manager well manager, may manage no (null) wells
/// \param[in] src source terms
/// \param[in] bcs boundary conditions, treat as all noflow if null
/// \param[in] linsolver linear solver
/// \param[in] gravity if non-null, gravity vector
SimulatorIncompTwophaseAd(const ParameterGroup& param,
const UnstructuredGrid& grid,
const IncompPropertiesInterface& props,
const RockCompressibility* rock_comp_props,
WellsManager& wells_manager,
const std::vector<double>& src,
const FlowBoundaryConditions* bcs,
LinearSolverInterface& linsolver,
const double* gravity);
/// Run the simulation.
/// This will run succesive timesteps until timer.done() is true. It will
/// modify the reservoir and well states.
/// \param[in,out] timer governs the requested reporting timesteps
/// \param[in,out] state state of reservoir: pressure, fluxes
/// \param[in,out] well_state state of wells: bhp, perforation rates
/// \return simulation report, with timing data
SimulatorReport run(SimulatorTimer& timer,
TwophaseState& state,
WellState& well_state);
private:
class Impl;
// Using shared_ptr instead of scoped_ptr since scoped_ptr requires complete type for Impl.
std::shared_ptr<Impl> pimpl_;
};
} // namespace Opm
#endif // OPM_SIMULATORINCOMPTWOPHASEAD_HEADER_INCLUDED

View File

@ -1,82 +0,0 @@
/*
Copyright 2013, 2015, 2016 SINTEF ICT, Applied Mathematics.
Copyright 2015 Andreas Lauser
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 3 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 <http://www.gnu.org/licenses/>.
*/
#ifndef OPM_SIMULATORSEQUENTIALBLACKOIL_HEADER_INCLUDED
#define OPM_SIMULATORSEQUENTIALBLACKOIL_HEADER_INCLUDED
#include <opm/autodiff/SimulatorBase.hpp>
#include <opm/autodiff/NonlinearSolver.hpp>
#include <opm/autodiff/BlackoilSequentialModel.hpp>
namespace Opm {
template <class GridT, class WellModelT,
template <class G, class W> class PressureModel,
template <class G, class W> class TransportModel>
class SimulatorSequentialBlackoil;
template <class GridT, class WellModelT,
template <class G, class W> class PressureModel,
template <class G, class W> class TransportModel>
struct SimulatorTraits<SimulatorSequentialBlackoil<GridT, WellModelT, PressureModel, TransportModel> >
{
typedef WellStateFullyImplicitBlackoil WellState;
typedef BlackoilState ReservoirState;
typedef BlackoilOutputWriter OutputWriter;
typedef GridT Grid;
typedef BlackoilSequentialModel<Grid, StandardWells, PressureModel, TransportModel> Model;
typedef NonlinearSolver<Model> Solver;
typedef WellModelT WellModel;
};
/// a simulator for the blackoil model
template <class GridT, class WellModelT,
template <class G, class W> class PressureModel,
template <class G, class W> class TransportModel>
class SimulatorSequentialBlackoil
: public SimulatorBase<SimulatorSequentialBlackoil<GridT, WellModelT, PressureModel, TransportModel> >
{
typedef SimulatorBase<SimulatorSequentialBlackoil<GridT, WellModelT, PressureModel, TransportModel> > Base;
public:
// forward the constructor to the base class
SimulatorSequentialBlackoil(const ParameterGroup& param,
const typename Base::Grid& grid,
DerivedGeology& geo,
BlackoilPropsAdFromDeck& props,
const RockCompressibility* rock_comp_props,
NewtonIterationBlackoilInterface& linsolver,
const double* gravity,
const bool disgas,
const bool vapoil,
std::shared_ptr<EclipseState> eclipse_state,
std::shared_ptr<Schedule> schedule,
std::shared_ptr<SummaryConfig> summary_config,
BlackoilOutputWriter& output_writer,
const std::vector<double>& threshold_pressures_by_face)
: Base(param, grid, geo, props, rock_comp_props, linsolver, gravity, disgas, vapoil,
eclipse_state, schedule, summary_config, output_writer, threshold_pressures_by_face,
// names of deactivated wells in parallel run
std::unordered_set<std::string>())
{}
};
} // namespace Opm
#endif // OPM_SIMULATORSEQUENTIALBLACKOIL_HEADER_INCLUDED

View File

@ -1,302 +0,0 @@
/*
Copyright 2016 SINTEF ICT, Applied Mathematics.
Copyright 2016 Statoil ASA.
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 3 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 <http://www.gnu.org/licenses/>.
*/
#ifndef OPM_STANDARDWELLS_HEADER_INCLUDED
#define OPM_STANDARDWELLS_HEADER_INCLUDED
#include <dune/common/parallel/mpihelper.hh>
#include <opm/common/OpmLog/OpmLog.hpp>
#include <opm/common/utility/platform_dependent/disable_warnings.h>
#include <Eigen/Eigen>
#include <Eigen/Sparse>
#include <opm/common/utility/platform_dependent/reenable_warnings.h>
#include <cassert>
#include <tuple>
#include <opm/parser/eclipse/EclipseState/Schedule/Schedule.hpp>
#include <opm/core/wells.h>
#include <opm/core/wells/DynamicListEconLimited.hpp>
#include <opm/core/wells/WellCollection.hpp>
#include <opm/autodiff/AutoDiffBlock.hpp>
#include <opm/autodiff/AutoDiffHelpers.hpp>
#include <opm/autodiff/BlackoilPropsAdFromDeck.hpp>
#include <opm/simulators/WellSwitchingLogger.hpp>
namespace Opm {
/// Class for handling the standard well model.
class StandardWells {
public:
struct WellOps {
explicit WellOps(const Wells* wells);
Eigen::SparseMatrix<double> w2p; // well -> perf (scatter)
Eigen::SparseMatrix<double> p2w; // perf -> well (gather)
std::vector<int> well_cells; // the set of perforated cells
};
// --------- Types ---------
using ADB = AutoDiffBlock<double>;
using Vector = ADB::V;
using Communication =
Dune::CollectiveCommunication<typename Dune::MPIHelper
::MPICommunicator>;
// copied from BlackoilModelBase
// should put to somewhere better
using DataBlock = Eigen::Array<double,
Eigen::Dynamic,
Eigen::Dynamic,
Eigen::RowMajor>;
// --------- Public methods ---------
StandardWells(const Wells* wells_arg, WellCollection* well_collection, const int current_step);
void init(const BlackoilPropsAdFromDeck* fluid_arg,
const std::vector<bool>* active_arg,
const std::vector<PhasePresence>* pc_arg,
const VFPProperties<VFPInjPropertiesLegacy,VFPProdPropertiesLegacy>* vfp_properties_arg,
const double gravity_arg,
const Vector& depth_arg);
const WellOps& wellOps() const;
int numPhases() const { return wells().number_of_phases; };
const Wells& wells() const;
const Wells* wellsPointer() const;
/// return true if wells are available in the reservoir
bool wellsActive() const;
void setWellsActive(const bool wells_active);
/// return true if wells are available on this process
bool localWellsActive() const;
int numWellVars() const;
/// Density of each well perforation
Vector& wellPerforationDensities(); // mutable version kept for BlackoilMultisegmentModel
const Vector& wellPerforationDensities() const;
/// Diff to bhp for each well perforation.
Vector& wellPerforationPressureDiffs(); // mutable version kept for BlackoilMultisegmentModel
const Vector& wellPerforationPressureDiffs() const;
template <class ReservoirResidualQuant, class SolutionState>
void extractWellPerfProperties(const SolutionState& state,
const std::vector<ReservoirResidualQuant>& rq,
std::vector<ADB>& mob_perfcells,
std::vector<ADB>& b_perfcells) const;
template <class SolutionState>
void computeWellFlux(const SolutionState& state,
const std::vector<ADB>& mob_perfcells,
const std::vector<ADB>& b_perfcells,
Vector& aliveWells,
std::vector<ADB>& cq_s) const;
template <class SolutionState, class WellState>
void updatePerfPhaseRatesAndPressures(const std::vector<ADB>& cq_s,
const SolutionState& state,
WellState& xw) const;
template <class WellState>
void updateWellState(const Vector& dwells,
const double dpmaxrel,
WellState& well_state);
template <class WellState>
void updateWellControls(WellState& xw) const;
// TODO: should LinearisedBlackoilResidual also be a template class?
template <class SolutionState>
void addWellFluxEq(const std::vector<ADB>& cq_s,
const SolutionState& state,
LinearisedBlackoilResidual& residual);
// TODO: some parameters, like gravity, maybe it is better to put in the member list
template <class SolutionState, class WellState>
void addWellControlEq(const SolutionState& state,
const WellState& xw,
const Vector& aliveWells,
LinearisedBlackoilResidual& residual);
template <class SolutionState, class WellState>
void computeWellConnectionPressures(const SolutionState& state,
const WellState& xw);
// state0 is non-constant, while it will not be used outside of the function
template <class SolutionState, class WellState>
void
computeWellPotentials(const std::vector<ADB>& mob_perfcells,
const std::vector<ADB>& b_perfcells,
const WellState& well_state,
SolutionState& state0,
std::vector<double>& well_potentials) const;
template <class SolutionState>
void
variableStateExtractWellsVars(const std::vector<int>& indices,
std::vector<ADB>& vars,
SolutionState& state) const;
void
variableStateWellIndices(std::vector<int>& indices,
int& next) const;
std::vector<int>
variableWellStateIndices() const;
template <class WellState>
void
variableWellStateInitials(const WellState& xw,
std::vector<Vector>& vars0) const;
/// If set, computeWellFlux() will additionally store the
/// total reservoir volume perforation fluxes.
void setStoreWellPerforationFluxesFlag(const bool store_fluxes);
/// Retrieves the stored fluxes. It is an error to call this
/// unless setStoreWellPerforationFluxesFlag(true) has been
/// called.
const Vector& getStoredWellPerforationFluxes() const;
/// upate the dynamic lists related to economic limits
template<class WellState>
void
updateListEconLimited(const Schedule& schedule,
const int current_step,
const Wells* wells,
const WellState& well_state,
DynamicListEconLimited& list_econ_limited) const;
WellCollection* wellCollection() const;
void calculateEfficiencyFactors();
const Vector& wellPerfEfficiencyFactors() const;
// just return the passed well state
template<class WellState>
const WellState& wellState(const WellState& well_state) const { return well_state; }
int currentStep() const;
protected:
bool wells_active_;
const Wells* wells_;
const WellOps wops_;
// It will probably need to be updated during running time.
WellCollection* well_collection_;
const int current_step_;
// The efficiency factor for each connection
// It is specified based on wells and groups
// By default, they should all be one.
Vector well_perforation_efficiency_factors_;
const BlackoilPropsAdFromDeck* fluid_;
const std::vector<bool>* active_;
const std::vector<PhasePresence>* phase_condition_;
const VFPProperties<VFPInjPropertiesLegacy,VFPProdPropertiesLegacy>* vfp_properties_;
double gravity_;
// the depth of the all the cell centers
// for standard Wells, it the same with the perforation depth
Vector perf_cell_depth_;
Vector well_perforation_densities_;
Vector well_perforation_pressure_diffs_;
bool store_well_perforation_fluxes_;
Vector well_perforation_fluxes_;
// protected methods
template <class SolutionState, class WellState>
void computePropertiesForWellConnectionPressures(const SolutionState& state,
const WellState& xw,
std::vector<double>& b_perf,
std::vector<double>& rsmax_perf,
std::vector<double>& rvmax_perf,
std::vector<double>& surf_dens_perf);
template <class WellState>
void computeWellConnectionDensitesPressures(const WellState& xw,
const std::vector<double>& b_perf,
const std::vector<double>& rsmax_perf,
const std::vector<double>& rvmax_perf,
const std::vector<double>& surf_dens_perf,
const std::vector<double>& depth_perf,
const double grav);
template <class WellState>
bool checkRateEconLimits(const WellEconProductionLimits& econ_production_limits,
const WellState& well_state,
const int well_number) const;
using WellMapType = typename WellState::WellMapType;
using WellMapEntryType = typename WellState::mapentry_t;
// a tuple type for ratio limit check.
// first value indicates whether ratio limit is violated, when the ratio limit is not violated, the following three
// values should not be used.
// second value indicates whehter there is only one connection left.
// third value indicates the indx of the worst-offending connection.
// the last value indicates the extent of the violation for the worst-offending connection, which is defined by
// the ratio of the actual value to the value of the violated limit.
using RatioCheckTuple = std::tuple<bool, bool, int, double>;
enum ConnectionIndex {
INVALIDCONNECTION = -10000
};
template <class WellState>
RatioCheckTuple checkRatioEconLimits(const WellEconProductionLimits& econ_production_limits,
const WellState& well_state,
const WellMapEntryType& map_entry) const;
template <class WellState>
RatioCheckTuple checkMaxWaterCutLimit(const WellEconProductionLimits& econ_production_limits,
const WellState& well_state,
const WellMapEntryType& map_entry) const;
template <class WellState>
void updateWellStateWithTarget(const WellControls* wc,
const int current,
const int well_index,
WellState& xw) const;
};
} // namespace Opm
#include "StandardWells_impl.hpp"
#endif

File diff suppressed because it is too large Load Diff

View File

@ -1,220 +0,0 @@
// -*- 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 <http://www.gnu.org/licenses/>.
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.
*/
#ifndef OPM_THREADHANDLE_HPP
#define OPM_THREADHANDLE_HPP
#include <cassert>
#include <dune/common/exceptions.hh>
#include <thread>
#include <mutex>
#include <queue>
namespace Opm
{
class ThreadHandle
{
public:
/// \brief ObjectInterface class
/// Virtual interface for code to be run in a seperate thread.
class ObjectInterface
{
protected:
ObjectInterface() {}
public:
virtual ~ObjectInterface() {}
virtual void run() = 0;
virtual bool isEndMarker () const { return false; }
};
/// \brief ObjectWrapper class
/// Implementation of virtualization of template argument fullfilling
/// the virtual object interface.
template <class Object>
class ObjectWrapper : public ObjectInterface
{
Object obj_;
public:
ObjectWrapper( Object&& obj ) : obj_( std::move( obj ) ) {}
void run() { obj_.run(); }
};
protected:
/// \brief EndObject class
/// Empthy object marking thread termination.
class EndObject : public ObjectInterface
{
public:
void run () { }
bool isEndMarker () const { return true; }
};
/// \brief The ThreadHandleQueue class
/// Queue of objects to be handled by this thread.
class ThreadHandleQueue
{
public:
//! constructor creating object that is executed by thread
ThreadHandleQueue()
: objQueue_(), mutex_()
{
}
~ThreadHandleQueue()
{
// wait until all objects have been written.
while( ! objQueue_.empty() )
{
wait();
}
}
//! insert object into threads queue
void push_back( std::unique_ptr< ObjectInterface >&& obj )
{
// lock mutex to make sure objPtr is not used
mutex_.lock();
objQueue_.emplace( std::move(obj) );
mutex_.unlock();
}
//! do the work until the queue received an end object
void run()
{
// wait until objects have been pushed to the queue
while( objQueue_.empty() )
{
// sleep one second
wait();
}
{
// lock mutex for access to objQueue_
mutex_.lock();
// get next object from queue
std::unique_ptr< ObjectInterface > obj( objQueue_.front().release() );
// remove object from queue
objQueue_.pop();
// unlock mutex for access to objQueue_
mutex_.unlock();
// if object is end marker terminate thread
if( obj->isEndMarker() ){
if( ! objQueue_.empty() ) {
throw std::logic_error("ThreadHandleQueue: not all queued objects were executed");
}
return;
}
// execute object action
obj->run();
}
// keep thread running
run();
}
protected:
std::queue< std::unique_ptr< ObjectInterface > > objQueue_;
std::mutex mutex_;
// no copying
ThreadHandleQueue( const ThreadHandleQueue& ) = delete;
// wait duration of 10 milli seconds
void wait() const
{
std::this_thread::sleep_for( std::chrono::milliseconds(10) );
}
}; // end ThreadHandleQueue
////////////////////////////////////////////////////
// end ThreadHandleQueue
////////////////////////////////////////////////////
// start the thread by calling method run
static void startThread( ThreadHandleQueue* obj )
{
obj->run();
}
ThreadHandleQueue threadObjectQueue_;
std::unique_ptr< std::thread > thread_;
private:
// prohibit copying
ThreadHandle( const ThreadHandle& ) = delete;
public:
//! constructor creating ThreadHandle
//! \param isIORank if true thread is created
ThreadHandle( const bool createThread )
: threadObjectQueue_(),
thread_()
{
if( createThread )
{
thread_.reset( new std::thread( startThread, &threadObjectQueue_ ) );
// detach thread into nirvana
thread_->detach();
}
} // end constructor
//! dispatch object to queue of separate thread
template <class Object>
void dispatch( Object&& obj )
{
if( thread_ )
{
typedef ObjectWrapper< Object > ObjectPointer;
ObjectInterface* objPtr = new ObjectPointer( std::move(obj) );
// add object to queue of objects
threadObjectQueue_.push_back( std::unique_ptr< ObjectInterface > (objPtr) );
}
else
{
throw std::logic_error("ThreadHandle::dispatch called without thread being initialized (i.e. on non-ioRank)");
}
}
//! destructor terminating the thread
~ThreadHandle()
{
if( thread_ )
{
// dispatch end object which will terminate the thread
threadObjectQueue_.push_back( std::unique_ptr< ObjectInterface > (new EndObject()) ) ;
}
}
};
} // end namespace Opm
#endif

View File

@ -1,259 +0,0 @@
/*
Copyright 2013 SINTEF ICT, Applied Mathematics.
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 3 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 <http://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <opm/autodiff/TransportSolverTwophaseAd.hpp>
#include <opm/grid/UnstructuredGrid.h>
#include <opm/core/linalg/LinearSolverInterface.hpp>
#include <opm/core/props/IncompPropertiesInterface.hpp>
#include <opm/grid/transmissibility/trans_tpfa.h>
#include <opm/common/utility/parameters/ParameterGroup.hpp>
#include <opm/common/ErrorMacros.hpp>
#include <opm/common/Exceptions.hpp>
#include <iostream>
namespace Opm
{
/// Construct solver.
/// \param[in] grid A 2d or 3d grid.
/// \param[in] props Rock and fluid properties.
/// \param[in] linsolver Linear solver for Newton-Raphson scheme.
/// \param[in] gravity Gravity vector (null for no gravity).
/// \param[in] param Parameters for the solver.
TransportSolverTwophaseAd::TransportSolverTwophaseAd(const UnstructuredGrid& grid,
const IncompPropertiesInterface& props,
const LinearSolverInterface& linsolver,
const double* gravity,
const ParameterGroup& param)
: grid_(grid),
props_(props),
linsolver_(linsolver),
ops_(grid),
gravity_(0.0),
tol_(param.getDefault("nl_tolerance", 1e-9)),
maxit_(param.getDefault("nl_maxiter", 30))
{
using namespace Opm::AutoDiffGrid;
const int nc = numCells(grid_);
allcells_.resize(nc);
for (int i = 0; i < nc; ++i) {
allcells_[i] = i;
}
if (gravity && gravity[dimensions(grid_) - 1] != 0.0) {
gravity_ = gravity[dimensions(grid_) - 1];
for (int dd = 0; dd < dimensions(grid_) - 1; ++dd) {
if (gravity[dd] != 0.0) {
OPM_THROW(std::runtime_error, "TransportSolverTwophaseAd: can only handle gravity aligned with last dimension");
}
}
V htrans(grid.cell_facepos[grid.number_of_cells]);
tpfa_htrans_compute(const_cast<UnstructuredGrid*>(&grid), props.permeability(), htrans.data());
V trans(numFaces(grid_));
tpfa_trans_compute(const_cast<UnstructuredGrid*>(&grid), htrans.data(), trans.data());
transi_ = subset(trans, ops_.internal_faces);
}
}
// Virtual destructor.
TransportSolverTwophaseAd::~TransportSolverTwophaseAd()
{
}
namespace
{
template <class ADB>
std::vector<ADB>
phaseMobility(const Opm::IncompPropertiesInterface& props,
const std::vector<int>& cells,
const typename ADB::V& sw)
{
typedef Eigen::Array<double, Eigen::Dynamic, 2, Eigen::RowMajor> TwoCol;
typedef Eigen::Array<double, Eigen::Dynamic, 4, Eigen::RowMajor> FourCol;
typedef Eigen::SparseMatrix<double> S;
typedef typename ADB::V V;
typedef typename ADB::M M;
const int nc = props.numCells();
TwoCol s(nc, 2);
s.leftCols<1>() = sw;
s.rightCols<1>() = 1.0 - s.leftCols<1>();
TwoCol kr(nc, 2);
FourCol dkr(nc, 4);
props.relperm(nc, s.data(), cells.data(), kr.data(), dkr.data());
V krw = kr.leftCols<1>();
V kro = kr.rightCols<1>();
// In dkr, columns col(0..3) are:
// dkrw/dsw dkro/dsw dkrw/dso dkrw/dso <-- partial derivatives, really.
// If we want the derivatives with respect to some variable x,
// we must apply the chain rule:
// dkrw/dx = dkrw/dsw*dsw/dx + dkrw/dso*dso/dx.
// If x is sw as in our case we are left with.
// dkrw/dsw = col(0) - col(2)
// dkro/dsw = col(1) - col(3)
V dkrw = dkr.leftCols<1>() - dkr.rightCols<2>().leftCols<1>();
V dkro = dkr.leftCols<2>().rightCols<1>() - dkr.rightCols<1>();
S krwjac(nc,nc);
S krojac(nc,nc);
auto sizes = Eigen::ArrayXi::Ones(nc);
krwjac.reserve(sizes);
krojac.reserve(sizes);
for (int c = 0; c < nc; ++c) {
krwjac.insert(c,c) = dkrw(c);
krojac.insert(c,c) = dkro(c);
}
const double* mu = props.viscosity();
std::vector<M> dmw = { M(krwjac)/mu[0] };
std::vector<M> dmo = { M(krojac)/mu[1] };
std::vector<ADB> pmobc = { ADB::function(krw / mu[0], std::move(dmw)) ,
ADB::function(kro / mu[1], std::move(dmo)) };
return pmobc;
}
/// Returns fw(sw).
template <class ADB>
ADB
fluxFunc(const std::vector<ADB>& m)
{
assert (m.size() == 2);
ADB f = m[0] / (m[0] + m[1]);
return f;
}
} // anonymous namespace
/// Solve for saturation at next timestep.
/// Note that this only performs advection by total velocity, and
/// no gravity segregation.
/// \param[in] porevolume Array of pore volumes.
/// \param[in] source Transport source term. For interpretation see Opm::computeTransportSource().
/// \param[in] dt Time step.
/// \param[in, out] state Reservoir state. Calling solve() will read state.faceflux() and
/// read and write state.saturation().
void TransportSolverTwophaseAd::solve(const double* porevolume,
const double* source,
const double dt,
TwophaseState& state)
{
using namespace Opm::AutoDiffGrid;
typedef Eigen::Array<double, Eigen::Dynamic, 2, Eigen::RowMajor> TwoCol;
typedef Eigen::Map<const V> Vec;
const int nc = numCells(grid_);
const TwoCol s0 = Eigen::Map<const TwoCol>(state.saturation().data(), nc, 2);
double res_norm = 1e100;
const V sw0 = s0.leftCols<1>();
// sw1 is the object that will be changed every Newton iteration.
// V sw1 = sw0;
V sw1 = 0.5*V::Ones(nc,1);
const V dflux_all = Vec(state.faceflux().data(), numFaces(grid_), 1);
const int num_internal = ops_.internal_faces.size();
V dflux = subset(dflux_all, ops_.internal_faces);
// Upwind selection of mobilities by phase.
// We have that for a phase P
// v_P = lambda_P K (-grad p + rho_P g grad z)
// and we assume that this has the same direction as
// dh_P = -grad p + rho_P g grad z.
// This may not be true for arbitrary anisotropic situations,
// but for scalar lambda and using TPFA it holds.
const V p1 = Vec(state.pressure().data(), nc, 1);
const V ndp = (ops_.ngrad * p1.matrix()).array();
const V z = cellCentroidsZToEigen(grid_);
const V ndz = (ops_.ngrad * z.matrix()).array();
assert(num_internal == ndp.size());
const double* density = props_.density();
const V dhw = ndp - ndz*(gravity_*density[0]);
const V dho = ndp - ndz*(gravity_*density[1]);
const UpwindSelector<double> upwind_w(grid_, ops_, dhw);
const UpwindSelector<double> upwind_o(grid_, ops_, dho);
// Compute more explicit and constant terms used in the equations.
const V pv = Vec(porevolume, nc, 1);
const V dtpv = dt/pv;
const V q = Vec(source, nc, 1);
const V qneg = q.min(V::Zero(nc,1));
const V qpos = q.max(V::Zero(nc,1));
const double gfactor = gravity_*(density[0] - density[1]);
const V gravflux = (gravity_ == 0.0) ? V(V::Zero(num_internal, 1))
: ndz*transi_*gfactor;
// Block pattern for variables.
// Primary variables:
// sw : one per cell
std::vector<int> bpat = { nc };
// Newton-Raphson loop.
int it = 0;
do {
// Assemble linear system.
const ADB sw = ADB::variable(0, sw1, bpat);
const std::vector<ADB> pmobc = phaseMobility<ADB>(props_, allcells_, sw.value());
const ADB fw_cell = fluxFunc(pmobc);
const std::vector<ADB> pmobf = { upwind_w.select(pmobc[0]),
upwind_o.select(pmobc[1]) };
const ADB fw_face = fluxFunc(pmobf);
const ADB flux = fw_face * (dflux - pmobf[1]*gravflux);
// const ADB fw_face = upwind_w.select(fw_cell);
// const ADB flux = fw_face * dflux;
const ADB qtr_ad = qpos + fw_cell*qneg;
const ADB transport_residual = sw - sw0 + dtpv*(ops_.div*flux - qtr_ad);
res_norm = transport_residual.value().matrix().norm();
std::cout << "Residual l2-norm = " << res_norm << std::endl;
// Solve linear system.
Eigen::SparseMatrix<double, Eigen::RowMajor> smatr;
transport_residual.derivative()[0].toSparse(smatr);
assert(smatr.isCompressed());
V ds(nc);
LinearSolverInterface::LinearSolverReport rep
= linsolver_.solve(nc, smatr.nonZeros(),
smatr.outerIndexPtr(), smatr.innerIndexPtr(), smatr.valuePtr(),
transport_residual.value().data(), ds.data());
if (!rep.converged) {
OPM_THROW(LinearSolverProblem, "Linear solver convergence error in TransportSolverTwophaseAd::solve()");
}
// Update (possible clamp) sw1.
sw1 = sw.value() - ds;
sw1 = sw1.min(V::Ones(nc,1)).max(V::Zero(nc,1));
it += 1;
} while (res_norm > tol_ && it < maxit_);
// Write to output data structure.
Eigen::Map<TwoCol> sref(state.saturation().data(), nc, 2);
sref.leftCols<1>() = sw1;
sref.rightCols<1>() = 1.0 - sw1;
}
} // namespace Opm

View File

@ -1,89 +0,0 @@
/*
Copyright 2013 SINTEF ICT, Applied Mathematics.
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 3 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 <http://www.gnu.org/licenses/>.
*/
#ifndef OPM_TRANSPORTSOLVERTWOPHASEAD_HEADER_INCLUDED
#define OPM_TRANSPORTSOLVERTWOPHASEAD_HEADER_INCLUDED
#include <opm/autodiff/AutoDiffBlock.hpp>
#include <opm/autodiff/AutoDiffHelpers.hpp>
#include <opm/core/transport/TransportSolverTwophaseInterface.hpp>
#include <vector>
struct UnstructuredGrid;
namespace Opm
{
class IncompPropertiesInterface;
class LinearSolverInterface;
class ParameterGroup;
/// Implements an implicit transport solver for incompressible two-phase flow,
/// using automatic differentiation.
class TransportSolverTwophaseAd : public TransportSolverTwophaseInterface
{
public:
/// Construct solver.
/// \param[in] grid A 2d or 3d grid.
/// \param[in] props Rock and fluid properties.
/// \param[in] linsolver Linear solver for Newton-Raphson scheme.
/// \param[in] gravity Gravity vector (null for no gravity).
/// \param[in] param Parameters for the solver.
TransportSolverTwophaseAd(const UnstructuredGrid& grid,
const IncompPropertiesInterface& props,
const LinearSolverInterface& linsolver,
const double* gravity,
const ParameterGroup& param);
// Virtual destructor.
virtual ~TransportSolverTwophaseAd();
/// Solve for saturation at next timestep.
/// Note that this only performs advection by total velocity, and
/// no gravity segregation.
/// \param[in] porevolume Array of pore volumes.
/// \param[in] source Transport source term. For interpretation see Opm::computeTransportSource().
/// \param[in] dt Time step.
/// \param[in, out] state Reservoir state. Calling solve() will read state.faceflux() and
/// read and write state.saturation().
virtual void solve(const double* porevolume,
const double* source,
const double dt,
TwophaseState& state);
private:
typedef AutoDiffBlock<double> ADB;
typedef ADB::V V;
typedef ADB::M M;
private:
const UnstructuredGrid& grid_;
const IncompPropertiesInterface& props_;
const LinearSolverInterface& linsolver_;
const HelperOps ops_;
double gravity_;
double tol_;
int maxit_;
std::vector<int> allcells_;
V transi_;
};
} // namespace Opm
#endif // OPM_TRANSPORTSOLVERTWOPHASEAD_HEADER_INCLUDED

View File

@ -1,238 +0,0 @@
/*
Copyright 2015 SINTEF ICT, Applied Mathematics.
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 3 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 <http://www.gnu.org/licenses/>.
*/
#ifndef OPM_AUTODIFF_VFPHELPERSLEGACY_HPP_
#define OPM_AUTODIFF_VFPHELPERSLEGACY_HPP_
#include <opm/parser/eclipse/EclipseState/Schedule/VFPProdTable.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/VFPInjTable.hpp>
#include <opm/autodiff/AutoDiffHelpers.hpp>
/**
* This file contains a set of helper functions used by VFPProd / VFPInj.
*/
namespace Opm {
namespace detail {
typedef AutoDiffBlock<double> ADB;
/**
* * Returns zero for every entry in the ADB which is NaN or INF
* */
inline ADB zeroIfNanInf(const ADB& values) {
Selector<ADB::V::Scalar> not_nan_inf_selector(values.value(), Selector<ADB::V::Scalar>::NotNaNInf);
const ADB::V z = ADB::V::Zero(values.size());
const ADB zero = ADB::constant(z, values.blockPattern());
ADB retval = not_nan_inf_selector.select(values, zero);
return retval;
}
/**
* Sets block_pattern to be the "union of x.blockPattern() and block_pattern".
*/
inline void extendBlockPattern(const ADB& x, std::vector<int>& block_pattern) {
std::vector<int> x_block_pattern = x.blockPattern();
if (x_block_pattern.empty()) {
return;
}
else {
if (block_pattern.empty()) {
block_pattern = x_block_pattern;
return;
}
else {
if (x_block_pattern != block_pattern) {
OPM_THROW(std::logic_error, "Block patterns do not match");
}
}
}
}
}
}
#include "VFPHelpers.hpp"
namespace Opm {
namespace detail {
/**
* Finds the common block pattern for all inputs
*/
inline std::vector<int> commonBlockPattern(
const ADB& x1,
const ADB& x2,
const ADB& x3,
const ADB& x4) {
std::vector<int> block_pattern;
extendBlockPattern(x1, block_pattern);
extendBlockPattern(x2, block_pattern);
extendBlockPattern(x3, block_pattern);
extendBlockPattern(x4, block_pattern);
return block_pattern;
}
inline std::vector<int> commonBlockPattern(
const ADB& x1,
const ADB& x2,
const ADB& x3,
const ADB& x4,
const ADB& x5) {
std::vector<int> block_pattern = commonBlockPattern(x1, x2, x3, x4);
extendBlockPattern(x5, block_pattern);
return block_pattern;
}
/**
* Returns the actual ADB for the type of FLO/GFR/WFR type
*/
template <typename TYPE>
ADB getValue(
const ADB& aqua,
const ADB& liquid,
const ADB& vapour, TYPE type);
template <>
inline
ADB getValue(
const ADB& aqua,
const ADB& liquid,
const ADB& vapour,
VFPProdTable::FLO_TYPE type) {
return detail::getFlo(aqua, liquid, vapour, type);
}
template <>
inline
ADB getValue(
const ADB& aqua,
const ADB& liquid,
const ADB& vapour,
VFPProdTable::WFR_TYPE type) {
return detail::getWFR(aqua, liquid, vapour, type);
}
template <>
inline
ADB getValue(
const ADB& aqua,
const ADB& liquid,
const ADB& vapour,
VFPProdTable::GFR_TYPE type) {
return detail::getGFR(aqua, liquid, vapour, type);
}
template <>
inline
ADB getValue(
const ADB& aqua,
const ADB& liquid,
const ADB& vapour,
VFPInjTable::FLO_TYPE type) {
return detail::getFlo(aqua, liquid, vapour, type);
}
/**
* Given m wells and n types of VFP variables (e.g., FLO = {FLO_OIL, FLO_LIQ}
* this function combines the n types of ADB objects, so that each of the
* m wells gets the right ADB.
* @param TYPE Type of variable to return, e.g., FLO_TYPE, WFR_TYPE, GFR_TYPE
* @param TABLE Type of table to use, e.g., VFPInjTable, VFPProdTable.
*/
template <typename TYPE, typename TABLE>
ADB combineADBVars(const std::vector<const TABLE*>& well_tables,
const ADB& aqua,
const ADB& liquid,
const ADB& vapour) {
const int num_wells = static_cast<int>(well_tables.size());
assert(aqua.size() == num_wells);
assert(liquid.size() == num_wells);
assert(vapour.size() == num_wells);
//Caching variable for flo/wfr/gfr
std::map<TYPE, ADB> map;
//Indexing variable used when combining the different ADB types
std::map<TYPE, std::vector<int> > elems;
//Compute all of the different ADB types,
//and record which wells use which types
for (int i=0; i<num_wells; ++i) {
const TABLE* table = well_tables[i];
//Only do something if this well is under THP control
if (table != NULL) {
TYPE type = getType<TYPE>(table);
//"Caching" of flo_type etc: Only calculate used types
//Create type if it does not exist
if (map.find(type) == map.end()) {
map.insert(std::pair<TYPE, ADB>(
type,
detail::getValue<TYPE>(aqua, liquid, vapour, type)
));
}
//Add the index for assembly later in gather_vars
elems[type].push_back(i);
}
}
//Loop over all types of ADB variables, and combine them
//so that each well gets the proper variable
ADB retval = ADB::constant(ADB::V::Zero(num_wells));
for (const auto& entry : elems) {
const auto& key = entry.first;
const auto& value = entry.second;
//Get the ADB for this type of variable
assert(map.find(key) != map.end());
const ADB& values = map.find(key)->second;
//Get indices to all elements that should use this ADB
const std::vector<int>& current = value;
//Add these elements to retval
retval = retval + superset(subset(values, current), current, values.size());
}
return retval;
}
} // namespace detail
} // namespace
#endif /* OPM_AUTODIFF_VFPHELPERSLEGACY_HPP_ */

View File

@ -1,130 +0,0 @@
/*
Copyright 2015 SINTEF ICT, Applied Mathematics.
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 3 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 <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <opm/autodiff/VFPHelpersLegacy.hpp>
#include <opm/autodiff/VFPInjPropertiesLegacy.hpp>
#include <opm/core/props/BlackoilPhases.hpp>
#include <opm/common/ErrorMacros.hpp>
#include <opm/autodiff/AutoDiffBlock.hpp>
#include <opm/autodiff/AutoDiffHelpers.hpp>
namespace Opm {
VFPInjPropertiesLegacy::ADB VFPInjPropertiesLegacy::bhp(const std::vector<int>& table_id,
const Wells& wells,
const ADB& qs,
const ADB& thp_val) const {
const int nw = wells.number_of_wells;
//Short-hands for water / oil / gas phases
//TODO enable support for two-phase.
assert(wells.number_of_phases == 3);
const ADB& w = subset(qs, Span(nw, 1, BlackoilPhases::Aqua*nw));
const ADB& o = subset(qs, Span(nw, 1, BlackoilPhases::Liquid*nw));
const ADB& g = subset(qs, Span(nw, 1, BlackoilPhases::Vapour*nw));
return bhp(table_id, w, o, g, thp_val);
}
VFPInjPropertiesLegacy::ADB VFPInjPropertiesLegacy::bhp(const std::vector<int>& table_id,
const ADB& aqua,
const ADB& liquid,
const ADB& vapour,
const ADB& thp_arg) const {
const int nw = thp_arg.size();
std::vector<int> block_pattern = detail::commonBlockPattern(aqua, liquid, vapour, thp_arg);
assert(static_cast<int>(table_id.size()) == nw);
assert(aqua.size() == nw);
assert(liquid.size() == nw);
assert(vapour.size() == nw);
assert(thp_arg.size() == nw);
//Allocate data for bhp's and partial derivatives
ADB::V value = ADB::V::Zero(nw);
ADB::V dthp = ADB::V::Zero(nw);
ADB::V dflo = ADB::V::Zero(nw);
//Get the table for each well
std::vector<const VFPInjTable*> well_tables(nw, nullptr);
for (int i=0; i<nw; ++i) {
if (table_id[i] > 0) {
well_tables[i] = detail::getTable(m_tables, table_id[i]);
}
}
//Get the right FLO variable for each well as a single ADB
const ADB flo = detail::combineADBVars<VFPInjTable::FLO_TYPE>(well_tables, aqua, liquid, vapour);
//Compute the BHP for each well independently
for (int i=0; i<nw; ++i) {
const VFPInjTable* table = well_tables[i];
if (table != nullptr) {
//First, find the values to interpolate between
auto flo_i = detail::findInterpData(flo.value()[i], table->getFloAxis());
auto thp_i = detail::findInterpData(thp_arg.value()[i], table->getTHPAxis());
detail::VFPEvaluation bhp_val = detail::interpolate(table->getTable(), flo_i, thp_i);
value[i] = bhp_val.value;
dthp[i] = bhp_val.dthp;
dflo[i] = bhp_val.dflo;
}
else {
value[i] = -1e100; //Signal that this value has not been calculated properly, due to "missing" table
}
}
//Create diagonal matrices from ADB::Vs
ADB::M dthp_diag(dthp.matrix().asDiagonal());
ADB::M dflo_diag(dflo.matrix().asDiagonal());
//Calculate the Jacobians
const int num_blocks = block_pattern.size();
std::vector<ADB::M> jacs(num_blocks);
for (int block = 0; block < num_blocks; ++block) {
//Could have used fastSparseProduct and temporary variables
//but may not save too much on that.
jacs[block] = ADB::M(nw, block_pattern[block]);
if (!thp_arg.derivative().empty()) {
jacs[block] += dthp_diag * thp_arg.derivative()[block];
}
if (!flo.derivative().empty()) {
jacs[block] += dflo_diag * flo.derivative()[block];
}
}
ADB retval = ADB::function(std::move(value), std::move(jacs));
return retval;
}
} //Namespace Opm

View File

@ -1,104 +0,0 @@
/*
Copyright 2015 SINTEF ICT, Applied Mathematics.
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 3 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 <http://www.gnu.org/licenses/>.
*/
#ifndef OPM_AUTODIFF_VFPINJPROPERTIESLEGACY_HPP_
#define OPM_AUTODIFF_VFPINJPROPERTIESLEGACY_HPP_
#include <opm/autodiff/VFPHelpersLegacy.hpp>
#include <opm/autodiff/VFPInjProperties.hpp>
#include <vector>
#include <map>
namespace Opm {
template <class Scalar>
class AutoDiffBlock;
class VFPInjPropertiesLegacy : public VFPInjProperties {
public:
typedef AutoDiffBlock<double> ADB;
/**
* Empty constructor
*/
VFPInjPropertiesLegacy() {}
/**
* Constructor
* Takes *no* ownership of data.
* @param inj_table A *single* VFPINJ table
*/
explicit VFPInjPropertiesLegacy(const VFPInjTable* inj_table) :
VFPInjProperties(inj_table) {}
/**
* Constructor
* Takes *no* ownership of data.
* @param inj_tables A map of different VFPINJ tables.
*/
explicit VFPInjPropertiesLegacy(const InjTable& inj_tables) :
VFPInjProperties(inj_tables) {}
using VFPInjProperties::bhp;
/**
* Linear interpolation of bhp as function of the input parameters.
* @param table_id Table number to use
* @param wells Wells structure with information about wells in qs
* @param qs Flow quantities
* @param thp Tubing head pressure
*
* @return The bottom hole pressure, interpolated/extrapolated linearly using
* the above parameters from the values in the input table.
*/
ADB bhp(const std::vector<int>& table_id,
const Wells& wells,
const ADB& qs,
const ADB& thp) const;
/**
* Linear interpolation of bhp as a function of the input parameters given as ADBs
* Each entry corresponds typically to one well.
* @param table_id Table number to use. A negative entry (e.g., -1)
* will indicate that no table is used, and the corresponding
* BHP will be calculated as a constant -1e100.
* @param aqua Water phase
* @param liquid Oil phase
* @param vapour Gas phase
* @param thp Tubing head pressure
*
* @return The bottom hole pressure, interpolated/extrapolated linearly using
* the above parameters from the values in the input table, for each entry in the
* input ADB objects.
*/
ADB bhp(const std::vector<int>& table_id,
const ADB& aqua,
const ADB& liquid,
const ADB& vapour,
const ADB& thp) const;
};
} //namespace
#endif /* OPM_AUTODIFF_VFPINJPROPERTIES_HPP_ */

View File

@ -1,154 +0,0 @@
/*
Copyright 2015 SINTEF ICT, Applied Mathematics.
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 3 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 <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <opm/autodiff/VFPHelpersLegacy.hpp>
#include <opm/autodiff/VFPProdPropertiesLegacy.hpp>
#include <opm/core/props/BlackoilPhases.hpp>
#include <opm/common/ErrorMacros.hpp>
#include <opm/autodiff/AutoDiffBlock.hpp>
#include <opm/autodiff/AutoDiffHelpers.hpp>
#include <opm/material/densead/Math.hpp>
#include <opm/material/densead/Evaluation.hpp>
namespace Opm {
VFPProdPropertiesLegacy::ADB VFPProdPropertiesLegacy::bhp(const std::vector<int>& table_id,
const Wells& wells,
const ADB& qs,
const ADB& thp_arg,
const ADB& alq) const {
const int nw = wells.number_of_wells;
//Short-hands for water / oil / gas phases
//TODO enable support for two-phase.
assert(wells.number_of_phases == 3);
const ADB& w = subset(qs, Span(nw, 1, BlackoilPhases::Aqua*nw));
const ADB& o = subset(qs, Span(nw, 1, BlackoilPhases::Liquid*nw));
const ADB& g = subset(qs, Span(nw, 1, BlackoilPhases::Vapour*nw));
return bhp(table_id, w, o, g, thp_arg, alq);
}
VFPProdPropertiesLegacy::ADB VFPProdPropertiesLegacy::bhp(const std::vector<int>& table_id,
const ADB& aqua,
const ADB& liquid,
const ADB& vapour,
const ADB& thp_arg,
const ADB& alq) const {
const int nw = thp_arg.size();
std::vector<int> block_pattern = detail::commonBlockPattern(aqua, liquid, vapour, thp_arg, alq);
assert(static_cast<int>(table_id.size()) == nw);
assert(aqua.size() == nw);
assert(liquid.size() == nw);
assert(vapour.size() == nw);
assert(thp_arg.size() == nw);
assert(alq.size() == nw);
//Allocate data for bhp's and partial derivatives
ADB::V value = ADB::V::Zero(nw);
ADB::V dthp = ADB::V::Zero(nw);
ADB::V dwfr = ADB::V::Zero(nw);
ADB::V dgfr = ADB::V::Zero(nw);
ADB::V dalq = ADB::V::Zero(nw);
ADB::V dflo = ADB::V::Zero(nw);
//Get the table for each well
std::vector<const VFPProdTable*> well_tables(nw, nullptr);
for (int i=0; i<nw; ++i) {
if (table_id[i] >= 0) {
well_tables[i] = detail::getTable(m_tables, table_id[i]);
}
}
//Get the right FLO/GFR/WFR variable for each well as a single ADB
const ADB flo = detail::combineADBVars<VFPProdTable::FLO_TYPE>(well_tables, aqua, liquid, vapour);
const ADB wfr = detail::combineADBVars<VFPProdTable::WFR_TYPE>(well_tables, aqua, liquid, vapour);
const ADB gfr = detail::combineADBVars<VFPProdTable::GFR_TYPE>(well_tables, aqua, liquid, vapour);
//Compute the BHP for each well independently
for (int i=0; i<nw; ++i) {
const VFPProdTable* table = well_tables[i];
if (table != nullptr) {
//First, find the values to interpolate between
//Value of FLO is negative in OPM for producers, but positive in VFP table
auto flo_i = detail::findInterpData(-flo.value()[i], table->getFloAxis());
auto thp_i = detail::findInterpData( thp_arg.value()[i], table->getTHPAxis());
auto wfr_i = detail::findInterpData( wfr.value()[i], table->getWFRAxis());
auto gfr_i = detail::findInterpData( gfr.value()[i], table->getGFRAxis());
auto alq_i = detail::findInterpData( alq.value()[i], table->getALQAxis());
detail::VFPEvaluation bhp_val = detail::interpolate(table->getTable(), flo_i, thp_i, wfr_i, gfr_i, alq_i);
value[i] = bhp_val.value;
dthp[i] = bhp_val.dthp;
dwfr[i] = bhp_val.dwfr;
dgfr[i] = bhp_val.dgfr;
dalq[i] = bhp_val.dalq;
dflo[i] = bhp_val.dflo;
}
else {
value[i] = -1e100; //Signal that this value has not been calculated properly, due to "missing" table
}
}
//Create diagonal matrices from ADB::Vs
ADB::M dthp_diag(dthp.matrix().asDiagonal());
ADB::M dwfr_diag(dwfr.matrix().asDiagonal());
ADB::M dgfr_diag(dgfr.matrix().asDiagonal());
ADB::M dalq_diag(dalq.matrix().asDiagonal());
ADB::M dflo_diag(dflo.matrix().asDiagonal());
//Calculate the Jacobians
const int num_blocks = block_pattern.size();
std::vector<ADB::M> jacs(num_blocks);
for (int block = 0; block < num_blocks; ++block) {
//Could have used fastSparseProduct and temporary variables
//but may not save too much on that.
jacs[block] = ADB::M(nw, block_pattern[block]);
if (!thp_arg.derivative().empty()) {
jacs[block] += dthp_diag * thp_arg.derivative()[block];
}
if (!wfr.derivative().empty()) {
jacs[block] += dwfr_diag * wfr.derivative()[block];
}
if (!gfr.derivative().empty()) {
jacs[block] += dgfr_diag * gfr.derivative()[block];
}
if (!alq.derivative().empty()) {
jacs[block] += dalq_diag * alq.derivative()[block];
}
if (!flo.derivative().empty()) {
jacs[block] -= dflo_diag * flo.derivative()[block];
}
}
ADB retval = ADB::function(std::move(value), std::move(jacs));
return retval;
}
}

View File

@ -1,110 +0,0 @@
/*
Copyright 2015 SINTEF ICT, Applied Mathematics.
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 3 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 <http://www.gnu.org/licenses/>.
*/
#ifndef OPM_AUTODIFF_VFPPRODPROPERTIESLEGACY_HPP_
#define OPM_AUTODIFF_VFPPRODPROPERTIESLEGACY_HPP_
#include <opm/autodiff/VFPHelpersLegacy.hpp>
#include <opm/autodiff/VFPProdProperties.hpp>
#include <vector>
#include <map>
namespace Opm {
template <class Scalar>
class AutoDiffBlock;
/**
* Class which linearly interpolates BHP as a function of rate, tubing head pressure,
* water fraction, gas fraction, and artificial lift for production VFP tables, and similarly
* the BHP as a function of the rate and tubing head pressure.
*/
class VFPProdPropertiesLegacy : public VFPProdProperties {
public:
typedef AutoDiffBlock<double> ADB;
/**
* Empty constructor
*/
VFPProdPropertiesLegacy() {}
/**
* Constructor
* Takes *no* ownership of data.
* @param prod_table A *single* VFPPROD table
*/
explicit VFPProdPropertiesLegacy(const VFPProdTable* prod_table) :
VFPProdProperties(prod_table) {}
/**
* Constructor
* Takes *no* ownership of data.
* @param prod_tables A map of different VFPPROD tables.
*/
explicit VFPProdPropertiesLegacy(const ProdTable& prod_tables) :
VFPProdProperties(prod_tables) {}
using VFPProdProperties::bhp;
/**
* Linear interpolation of bhp as function of the input parameters.
* @param table_id Table number to use
* @param wells Wells structure with information about wells in qs
* @param qs Flow quantities
* @param thp Tubing head pressure
* @param alq Artificial lift or other parameter
*
* @return The bottom hole pressure, interpolated/extrapolated linearly using
* the above parameters from the values in the input table.
*/
ADB bhp(const std::vector<int>& table_id,
const Wells& wells,
const ADB& qs,
const ADB& thp,
const ADB& alq) const;
/**
* Linear interpolation of bhp as a function of the input parameters given as ADBs
* Each entry corresponds typically to one well.
* @param table_id Table number to use. A negative entry (e.g., -1)
* will indicate that no table is used, and the corresponding
* BHP will be calculated as a constant -1e100.
* @param aqua Water phase
* @param liquid Oil phase
* @param vapour Gas phase
* @param thp Tubing head pressure
* @param alq Artificial lift or other parameter
*
* @return The bottom hole pressure, interpolated/extrapolated linearly using
* the above parameters from the values in the input table, for each entry in the
* input ADB objects.
*/
ADB bhp(const std::vector<int>& table_id,
const ADB& aqua,
const ADB& liquid,
const ADB& vapour,
const ADB& thp,
const ADB& alq) const;
};
} //namespace
#endif /* OPM_AUTODIFF_VFPPRODPROPERTIESLEGACY_HPP_ */

View File

@ -1,196 +0,0 @@
/*
Copyright 2014 SINTEF ICT, Applied Mathematics.
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 3 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 <http://www.gnu.org/licenses/>.
*/
#include <opm/autodiff/WellDensitySegmented.hpp>
#include <opm/core/wells.h>
#include <opm/autodiff/WellStateFullyImplicitBlackoil.hpp>
#include <opm/common/ErrorMacros.hpp>
#include <opm/core/props/BlackoilPhases.hpp>
#include <numeric>
#include <cmath>
std::vector<double>
Opm::WellDensitySegmented::computeConnectionDensities(const Wells& wells,
const PhaseUsage& phase_usage,
const std::vector<double>& perfComponentRates,
const std::vector<double>& b_perf,
const std::vector<double>& rsmax_perf,
const std::vector<double>& rvmax_perf,
const std::vector<double>& surf_dens_perf)
{
// Verify that we have consistent input.
const int np = wells.number_of_phases;
const int nw = wells.number_of_wells;
const int nperf = wells.well_connpos[nw];
const int numComponents = perfComponentRates.size() / nperf;
if (wells.number_of_phases != phase_usage.num_phases) {
OPM_THROW(std::logic_error, "Inconsistent input: wells vs. phase_usage.");
}
if (nperf*numComponents != int(surf_dens_perf.size())) {
OPM_THROW(std::logic_error, "Inconsistent input: wells vs. surf_dens.");
}
if (nperf*numComponents != int(perfComponentRates.size())) {
OPM_THROW(std::logic_error, "Inconsistent input: wells vs. wstate.");
}
if (nperf*numComponents != int(b_perf.size())) {
OPM_THROW(std::logic_error, "Inconsistent input: wells vs. b_perf.");
}
if ((!rsmax_perf.empty()) || (!rvmax_perf.empty())) {
// Need both oil and gas phases.
if (!phase_usage.phase_used[BlackoilPhases::Liquid]) {
OPM_THROW(std::logic_error, "Oil phase inactive, but non-empty rsmax_perf or rvmax_perf.");
}
if (!phase_usage.phase_used[BlackoilPhases::Vapour]) {
OPM_THROW(std::logic_error, "Gas phase inactive, but non-empty rsmax_perf or rvmax_perf.");
}
}
// 1. Compute the flow (in surface volume units for each
// component) exiting up the wellbore from each perforation,
// taking into account flow from lower in the well, and
// in/out-flow at each perforation.
std::vector<double> q_out_perf(nperf*numComponents);
for (int w = 0; w < nw; ++w) {
// Iterate over well perforations from bottom to top.
for (int perf = wells.well_connpos[w+1] - 1; perf >= wells.well_connpos[w]; --perf) {
for (int component = 0; component < numComponents; ++component) {
if (perf == wells.well_connpos[w+1] - 1) {
// This is the bottom perforation. No flow from below.
q_out_perf[perf*numComponents + component] = 0.0;
} else {
// Set equal to flow from below.
q_out_perf[perf*numComponents + component] = q_out_perf[(perf+1)*numComponents + component];
}
// Subtract outflow through perforation.
q_out_perf[perf*numComponents + component] -= perfComponentRates[perf*numComponents + component];
}
}
}
// 2. Compute the component mix at each perforation as the
// absolute values of the surface rates divided by their sum.
// Then compute volume ratios (formation factors) for each perforation.
// Finally compute densities for the segments associated with each perforation.
const int gaspos = phase_usage.phase_pos[BlackoilPhases::Vapour];
const int oilpos = phase_usage.phase_pos[BlackoilPhases::Liquid];
std::vector<double> mix(numComponents,0.0);
std::vector<double> x(numComponents);
std::vector<double> surf_dens(numComponents);
std::vector<double> dens(nperf);
for (int w = 0; w < nw; ++w) {
for (int perf = wells.well_connpos[w]; perf < wells.well_connpos[w+1]; ++perf) {
// Find component mix.
const double tot_surf_rate = std::accumulate(q_out_perf.begin() + numComponents*perf,
q_out_perf.begin() + numComponents*(perf+1), 0.0);
if (tot_surf_rate != 0.0) {
for (int component = 0; component < numComponents; ++component) {
mix[component] = std::fabs(q_out_perf[perf*numComponents + component]/tot_surf_rate);
}
} else {
// No flow => use well specified fractions for mix.
for (int phase = 0; phase < np; ++phase) {
mix[phase] = wells.comp_frac[w*np + phase];
}
// intialize 0.0 for comIdx >= np;
}
// Compute volume ratio.
x = mix;
double rs = 0.0;
double rv = 0.0;
if (!rsmax_perf.empty() && mix[oilpos] > 0.0) {
rs = std::min(mix[gaspos]/mix[oilpos], rsmax_perf[perf]);
}
if (!rvmax_perf.empty() && mix[gaspos] > 0.0) {
rv = std::min(mix[oilpos]/mix[gaspos], rvmax_perf[perf]);
}
if (rs != 0.0) {
// Subtract gas in oil from gas mixture
x[gaspos] = (mix[gaspos] - mix[oilpos]*rs)/(1.0 - rs*rv);
}
if (rv != 0.0) {
// Subtract oil in gas from oil mixture
x[oilpos] = (mix[oilpos] - mix[gaspos]*rv)/(1.0 - rs*rv);;
}
double volrat = 0.0;
for (int component = 0; component < numComponents; ++component) {
volrat += x[component] / b_perf[perf*numComponents + component];
}
for (int component = 0; component < numComponents; ++component) {
surf_dens[component] = surf_dens_perf[perf*numComponents + component];
}
// Compute segment density.
dens[perf] = std::inner_product(surf_dens.begin(), surf_dens.end(), mix.begin(), 0.0) / volrat;
}
}
return dens;
}
std::vector<double>
Opm::WellDensitySegmented::computeConnectionPressureDelta(const Wells& wells,
const std::vector<double>& z_perf,
const std::vector<double>& dens_perf,
const double gravity) {
const int nw = wells.number_of_wells;
const int nperf = wells.well_connpos[nw];
if (nperf != int(z_perf.size())) {
OPM_THROW(std::logic_error, "Inconsistent input: wells vs. z_perf.");
}
if (nperf != int(dens_perf.size())) {
OPM_THROW(std::logic_error, "Inconsistent input: wells vs. dens_perf.");
}
// Algorithm:
// We'll assume the perforations are given in order from top to
// bottom for each well. By top and bottom we do not necessarily
// mean in a geometric sense (depth), but in a topological sense:
// the 'top' perforation is nearest to the surface topologically.
// Our goal is to compute a pressure delta for each perforation.
// 1. Compute pressure differences between perforations.
// dp_perf will contain the pressure difference between a
// perforation and the one above it, except for the first
// perforation for each well, for which it will be the
// difference to the reference (bhp) depth.
std::vector<double> dp_perf(nperf);
for (int w = 0; w < nw; ++w) {
for (int perf = wells.well_connpos[w]; perf < wells.well_connpos[w+1]; ++perf) {
const double z_above = perf == wells.well_connpos[w] ? wells.depth_ref[w] : z_perf[perf - 1];
const double dz = z_perf[perf] - z_above;
dp_perf[perf] = dz * dens_perf[perf] * gravity;
}
}
// 2. Compute pressure differences to the reference point (bhp) by
// accumulating the already computed adjacent pressure
// differences, storing the result in dp_perf.
// This accumulation must be done per well.
for (int w = 0; w < nw; ++w) {
const auto beg = dp_perf.begin() + wells.well_connpos[w];
const auto end = dp_perf.begin() + wells.well_connpos[w + 1];
std::partial_sum(beg, end, beg);
}
return dp_perf;
}

View File

@ -1,77 +0,0 @@
/*
Copyright 2014 SINTEF ICT, Applied Mathematics.
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 3 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 <http://www.gnu.org/licenses/>.
*/
#ifndef OPM_WELLDENSITYSEGMENTED_HEADER_INCLUDED
#define OPM_WELLDENSITYSEGMENTED_HEADER_INCLUDED
#include <vector>
struct Wells;
namespace Opm
{
class WellStateFullyImplicitBlackoil;
class WellStateFullyImplicitBlackoilSolvent;
struct PhaseUsage;
/// A class giving a well model, by which we mean a way to compute
/// the pressure deltas of each perforation and the bottom-hole
/// pressure. This class contains an explicit model, that uses a
/// different density for each well segment, that is between each
/// pair of perforations.
class WellDensitySegmented
{
public:
/// Compute well segment densities
/// Notation: N = number of perforations, C = number of components.
/// \param[in] wells struct with static well info
/// \param[in] well_rates well rates for actiev components, size NC, P values per perforation
/// \param[in] phase_usage specifies which phases are active and not
/// \param[in] b_perf inverse ('little b') formation volume factor, size NC, P values per perforation
/// \param[in] rsmax_perf saturation point for rs (gas in oil) at each perforation, size N
/// \param[in] rvmax_perf saturation point for rv (oil in gas) at each perforation, size N
/// \param[in] surf_dens surface densities for active components, size NC, C values per perforation
static std::vector<double> computeConnectionDensities(const Wells& wells,
const PhaseUsage& phase_usage,
const std::vector<double>& perfComponentRates,
const std::vector<double>& b_perf,
const std::vector<double>& rsmax_perf,
const std::vector<double>& rvmax_perf,
const std::vector<double>& surf_dens_perf);
/// Compute pressure deltas.
/// Notation: N = number of perforations
/// \param[in] wells struct with static well info
/// \param[in] z_perf depth values for each perforation, size N
/// \param[in] dens_perf densities for each perforation, size N (typically computed using computeConnectionDensities)
/// \param[in] gravity gravity acceleration constant
static std::vector<double> computeConnectionPressureDelta(const Wells& wells,
const std::vector<double>& z_perf,
const std::vector<double>& dens_perf,
const double gravity);
};
} // namespace Opm
#endif // OPM_WELLDENSITYSEGMENTED_HEADER_INCLUDED

View File

@ -1,284 +0,0 @@
// This file is part of Eigen, a lightweight C++ template library
// for linear algebra.
//
// Copyright (C) 2008-2011 Gael Guennebaud <gael.guennebaud@inria.fr>
//
// This Source Code Form is subject to the terms of the Mozilla
// Public License v. 2.0. If a copy of the MPL was not distributed
// with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// This file has been modified for use in the OPM project codebase.
#ifndef OPM_FASTSPARSEPRODUCT_HEADER_INCLUDED
#define OPM_FASTSPARSEPRODUCT_HEADER_INCLUDED
#include <Eigen/Sparse>
#include <algorithm>
#include <iterator>
#include <functional>
#include <limits>
#include <vector>
#include <Eigen/Core>
namespace Opm {
template < unsigned int depth >
struct QuickSort
{
template <typename T>
static inline void sort(T begin, T end)
{
if (begin != end)
{
T middle = std::partition (begin, end,
std::bind2nd(std::less<typename std::iterator_traits<T>::value_type>(), *begin)
);
QuickSort< depth-1 >::sort(begin, middle);
// std::sort (max(begin + 1, middle), end);
T new_middle = begin;
QuickSort< depth-1 >::sort(++new_middle, end);
}
}
};
template <>
struct QuickSort< 0 >
{
template <typename T>
static inline void sort(T begin, T end)
{
// fall back to standard insertion sort
std::sort( begin, end );
}
};
template<typename Lhs, typename Rhs, typename ResultType>
void fastSparseProduct(const Lhs& lhs, const Rhs& rhs, ResultType& res)
{
// initialize result
res = ResultType(lhs.rows(), rhs.cols());
// if one of the matrices does not contain non zero elements
// the result will only contain an empty matrix
if( lhs.nonZeros() == 0 || rhs.nonZeros() == 0 )
return;
typedef typename Eigen::internal::remove_all<Lhs>::type::Scalar Scalar;
typedef typename Eigen::internal::remove_all<Lhs>::type::Index Index;
// make sure to call innerSize/outerSize since we fake the storage order.
Index rows = lhs.innerSize();
Index cols = rhs.outerSize();
eigen_assert(lhs.outerSize() == rhs.innerSize());
std::vector<bool> mask(rows,false);
Eigen::Matrix<Scalar,Eigen::Dynamic,1> values(rows);
Eigen::Matrix<Index, Eigen::Dynamic,1> indices(rows);
// estimate the number of non zero entries
// given a rhs column containing Y non zeros, we assume that the respective Y columns
// of the lhs differs in average of one non zeros, thus the number of non zeros for
// the product of a rhs column with the lhs is X+Y where X is the average number of non zero
// per column of the lhs.
// Therefore, we have nnz(lhs*rhs) = nnz(lhs) + nnz(rhs)
Index estimated_nnz_prod = lhs.nonZeros() + rhs.nonZeros();
res.setZero();
res.reserve(Index(estimated_nnz_prod));
//const Scalar epsilon = std::numeric_limits< Scalar >::epsilon();
const Scalar epsilon = 0.0;
// we compute each column of the result, one after the other
for (Index j=0; j<cols; ++j)
{
Index nnz = 0;
for (typename Rhs::InnerIterator rhsIt(rhs, j); rhsIt; ++rhsIt)
{
const Scalar y = rhsIt.value();
for (typename Lhs::InnerIterator lhsIt(lhs, rhsIt.index()); lhsIt; ++lhsIt)
{
const Scalar val = lhsIt.value() * y;
if( std::abs( val ) > epsilon )
{
const Index i = lhsIt.index();
if(!mask[i])
{
mask[i] = true;
values[i] = val;
indices[nnz] = i;
++nnz;
}
else
values[i] += val;
}
}
}
if( nnz > 1 )
{
// sort indices for sorted insertion to avoid later copying
QuickSort< 1 >::sort( indices.data(), indices.data()+nnz );
}
res.startVec(j);
// ordered insertion
// still using insertBackByOuterInnerUnordered since we know what we are doing
for(Index k=0; k<nnz; ++k)
{
const Index i = indices[k];
res.insertBackByOuterInnerUnordered(j,i) = values[i];
mask[i] = false;
}
}
res.finalize();
}
inline void fastDiagSparseProduct(const std::vector<double>& lhs,
const Eigen::SparseMatrix<double>& rhs,
Eigen::SparseMatrix<double>& res)
{
res = rhs;
// Multiply rows by diagonal lhs.
int n = res.cols();
for (int col = 0; col < n; ++col) {
typedef Eigen::SparseMatrix<double>::InnerIterator It;
for (It it(res, col); it; ++it) {
it.valueRef() *= lhs[it.row()];
}
}
}
inline void fastSparseDiagProduct(const Eigen::SparseMatrix<double>& lhs,
const std::vector<double>& rhs,
Eigen::SparseMatrix<double>& res)
{
res = lhs;
// Multiply columns by diagonal rhs.
int n = res.cols();
for (int col = 0; col < n; ++col) {
typedef Eigen::SparseMatrix<double>::InnerIterator It;
for (It it(res, col); it; ++it) {
it.valueRef() *= rhs[col];
}
}
}
template<typename Lhs, typename Rhs>
inline bool
equalSparsityPattern(const Lhs& lhs, const Rhs& rhs)
{
// if both matrices have equal storage and non zeros match, we can check sparsity pattern
bool equal = (Lhs::IsRowMajor == Rhs::IsRowMajor) && (lhs.nonZeros() == rhs.nonZeros());
// check complete sparsity pattern
if( equal )
{
typedef std::size_t Index;
const Index outerSize = lhs.outerSize();
const Index rhsOuterSize = rhs.outerSize();
if( outerSize != rhsOuterSize )
{
return false;
}
// outer indices
const auto rhsOuter = rhs.outerIndexPtr();
const auto lhsOuter = lhs.outerIndexPtr();
for(Index i=0; i<=outerSize; ++i )
{
if( lhsOuter[ i ] != rhsOuter[ i ] ) {
return false ;
}
}
// inner indices
const auto rhsInner = rhs.innerIndexPtr();
const auto lhsInner = lhs.innerIndexPtr();
const Index nnz = lhs.nonZeros();
for( Index i=0; i<nnz; ++i)
{
if( lhsInner[ i ] != rhsInner[ i ] ) {
return false;
}
}
}
return equal;
}
// this function substracts two sparse matrices
// if the sparsity pattern is the same a faster add/substract is performed
template<typename Lhs, typename Rhs>
inline void
fastSparseAdd(Lhs& lhs, const Rhs& rhs)
{
if( equalSparsityPattern( lhs, rhs ) )
{
typedef typename Eigen::internal::remove_all<Lhs>::type::Scalar Scalar;
typedef std::size_t Index;
const Index nnz = lhs.nonZeros();
// fast add using only the data pointers
const Scalar* rhsV = rhs.valuePtr();
Scalar* lhsV = lhs.valuePtr();
for(Index i=0; i<nnz; ++i )
{
lhsV[ i ] += rhsV[ i ];
}
}
else
{
// default Eigen operator+=
lhs += rhs;
}
}
// this function substracts two sparse matrices
// if the sparsity pattern is the same a faster add/substract is performed
template<typename Lhs, typename Rhs>
inline void
fastSparseSubstract(Lhs& lhs, const Rhs& rhs)
{
if( equalSparsityPattern( lhs, rhs ) )
{
typedef typename Eigen::internal::remove_all<Lhs>::type::Scalar Scalar;
typedef std::size_t Index;
const Index nnz = lhs.nonZeros();
// fast add using only the data pointers
const Scalar* rhsV = rhs.valuePtr();
Scalar* lhsV = lhs.valuePtr();
for(Index i=0; i<nnz; ++i )
{
lhsV[ i ] -= rhsV[ i ];
}
}
else
{
// default Eigen operator-=
lhs -= rhs;
}
}
} // end namespace Opm
#endif // OPM_FASTSPARSEPRODUCT_HEADER_INCLUDED

View File

@ -1,88 +0,0 @@
/*
Copyright 2015, 2016 SINTEF ICT, Applied Mathematics.
Copyright 2016 Statoil AS.
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 3 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 <http://www.gnu.org/licenses/>.
*/
#include <opm/autodiff/multiPhaseUpwind.hpp>
#include <algorithm>
#include <utility>
namespace Opm
{
std::array<double, 3> connectionMultiPhaseUpwind(const std::array<double, 3>& head_diff,
const std::array<double, 3>& mob1,
const std::array<double, 3>& mob2,
const double transmissibility,
const double flux)
{
// Based on the paper "Upstream Differencing for Multiphase Flow in Reservoir Simulation",
// by Yann Brenier and Jérôme Jaffré,
// SIAM J. Numer. Anal., 28(3), 685696.
// DOI:10.1137/0728036
//
// Notation is based on this paper, except q -> flux, t -> transmissibility.
enum { NumPhases = 3 }; // TODO: remove this restriction.
// Get and sort the g values (also called "weights" in the paper) for this connection.
using ValueAndIndex = std::pair<double, int>;
std::array<ValueAndIndex, NumPhases> g;
for (int phase_idx = 0; phase_idx < NumPhases; ++phase_idx) {
g[phase_idx] = ValueAndIndex(head_diff[phase_idx], phase_idx);
}
std::sort(g.begin(), g.end());
// Compute theta and r.
// Paper notation: subscript l -> ell (for read/searchability)
// Note that since we index phases from 0, r is one less than in the paper.
std::array<double, NumPhases> theta;
int r = -1;
for (int ell = 0; ell < NumPhases; ++ell) {
theta[ell] = flux;
for (int j = 0; j < NumPhases; ++j) {
if (j < ell) {
theta[ell] += transmissibility * (g[ell].first - g[j].first) * mob2[g[j].second];
}
if (j > ell) {
theta[ell] += transmissibility * (g[ell].first - g[j].first) * mob1[g[j].second];
}
}
if (theta[ell] <= 0.0) {
r = ell;
} else {
break; // r is correct, no need to continue
}
}
// Set upwind array and return.
std::array<double, NumPhases> upwind;
for (int ell = 0; ell < NumPhases; ++ell) {
const int phase_idx = g[ell].second;
upwind[phase_idx] = ell > r ? 1.0 : -1.0;
}
return upwind;
}
} // namespace Opm

View File

@ -1,45 +0,0 @@
/*
Copyright 2015, 2016 SINTEF ICT, Applied Mathematics.
Copyright 2016 Statoil AS.
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 3 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 <http://www.gnu.org/licenses/>.
*/
#ifndef OPM_MULTIPHASEUPWIND_HEADER_INCLUDED
#define OPM_MULTIPHASEUPWIND_HEADER_INCLUDED
#include <array>
namespace Opm
{
/// Compute upwind directions for three-phase flow across a connection.
///
/// @param[in] head_diff head differences by phase
/// @param[in] mob1 phase mobilities for first cell
/// @param[in] mob2 phase mobilities for second cell
/// @param[in] transmissibility tranmissibility of connection
/// @param[in] flux total volume flux across connection
/// @return array containing, for each phase, 1.0 if flow in the
/// direction of the connection, -1.0 if flow in the opposite
/// direction.
std::array<double, 3> connectionMultiPhaseUpwind(const std::array<double, 3>& head_diff,
const std::array<double, 3>& mob1,
const std::array<double, 3>& mob2,
const double transmissibility,
const double flux);
} // namespace Opm
#endif // OPM_MULTIPHASEUPWIND_HEADER_INCLUDED

View File

@ -1,462 +0,0 @@
/*
Copyright 2014 SINTEF ICT, Applied Mathematics.
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 3 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 <http://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <opm/core/flowdiagnostics/AnisotropicEikonal.hpp>
#include <opm/grid/GridUtilities.hpp>
#include <opm/grid/UnstructuredGrid.h>
#include <opm/common/utility/numeric/RootFinders.hpp>
#if BOOST_HEAP_AVAILABLE
namespace Opm
{
namespace
{
/// Euclidean (isotropic) distance.
double distanceIso(const double v1[2],
const double v2[2])
{
const double d[2] = { v2[0] - v1[0], v2[1] - v1[1] };
const double dist = std::sqrt(d[0]*d[0] + d[1]*d[1]);
return dist;
}
/// Anisotropic distance with respect to a metric g.
/// If d = v2 - v1, the distance is sqrt(d^T g d).
double distanceAniso(const double v1[2],
const double v2[2],
const double g[4])
{
const double d[2] = { v2[0] - v1[0], v2[1] - v1[1] };
const double dist = std::sqrt(+ g[0] * d[0] * d[0]
+ g[1] * d[0] * d[1]
+ g[2] * d[1] * d[0]
+ g[3] * d[1] * d[1]);
return dist;
}
} // anonymous namespace
/// Construct solver.
/// \param[in] grid A 2d grid.
AnisotropicEikonal2d::AnisotropicEikonal2d(const UnstructuredGrid& grid)
: grid_(grid),
safety_factor_(1.2)
{
if (grid.dimensions != 2) {
OPM_THROW(std::logic_error, "Grid for AnisotropicEikonal2d must be 2d.");
}
cell_neighbours_ = cellNeighboursAcrossVertices(grid);
orderCounterClockwise(grid, cell_neighbours_);
computeGridRadius();
}
/// Solve the eikonal equation.
/// \param[in] metric Array of metric tensors, M, for each cell.
/// \param[in] startcells Array of cells where u = 0 at the centroid.
/// \param[out] solution Array of solution to the eikonal equation.
void AnisotropicEikonal2d::solve(const double* metric,
const std::vector<int>& startcells,
std::vector<double>& solution)
{
// Compute anisotropy ratios to be used by isClose().
computeAnisoRatio(metric);
// The algorithm used is described in J.A. Sethian and A. Vladimirsky,
// "Ordered Upwind Methods for Static Hamilton-Jacobi Equations".
// Notation in comments is as used in that paper: U is the solution,
// and q is the boundary condition. One difference is that we talk about
// grid cells instead of mesh points.
//
// Algorithm summary:
// 1. Put all cells in Far. U_i = \inf.
// 2. Move the startcells to Accepted. U_i = q(x_i)
// 3. Move cells adjacent to startcells to Considered, evaluate
// U_i = min_{(x_j,x_k) \in NF(x_i)} G_{j,k}
// 4. Find the Considered cell with the smallest value: r.
// 5. Move cell r to Accepted. Update AcceptedFront.
// 6. Recompute the value for all Considered cells within
// distance h * F_2/F1 from x_r. Use min of previous and new.
// 7. Move cells adjacent to r from Far to Considered.
// 8. If Considered is not empty, go to step 4.
// 1. Put all cells in Far. U_i = \inf.
const int num_cells = grid_.number_of_cells;
const double inf = 1e100;
solution.clear();
solution.resize(num_cells, inf);
is_accepted_.clear();
is_accepted_.resize(num_cells, false);
accepted_front_.clear();
considered_.clear();
considered_handles_.clear();
is_considered_.clear();
is_considered_.resize(num_cells, false);
// 2. Move the startcells to Accepted. U_i = q(x_i)
const int num_startcells = startcells.size();
for (int ii = 0; ii < num_startcells; ++ii) {
is_accepted_[startcells[ii]] = true;
solution[startcells[ii]] = 0.0;
}
accepted_front_.insert(startcells.begin(), startcells.end());
// 3. Move cells adjacent to startcells to Considered, evaluate
// U_i = min_{(x_j,x_k) \in NF(x_i)} G_{j,k}
for (int ii = 0; ii < num_startcells; ++ii) {
const int scell = startcells[ii];
const int num_nb = cell_neighbours_[scell].size();
for (int nb = 0; nb < num_nb; ++nb) {
const int nb_cell = cell_neighbours_[scell][nb];
if (!is_accepted_[nb_cell] && !is_considered_[nb_cell]) {
const double value = computeValue(nb_cell, metric, solution.data());
pushConsidered(std::make_pair(value, nb_cell));
}
}
}
while (!considered_.empty()) {
// 4. Find the Considered cell with the smallest value: r.
const ValueAndCell r = topConsidered();
// std::cout << "Accepting cell " << r.second << std::endl;
// 5. Move cell r to Accepted. Update AcceptedFront.
const int rcell = r.second;
is_accepted_[rcell] = true;
solution[rcell] = r.first;
popConsidered();
accepted_front_.insert(rcell);
for (auto it = accepted_front_.begin(); it != accepted_front_.end();) {
// Note that loop increment happens in the body of this loop.
const int cell = *it;
bool on_front = false;
for (auto it2 = cell_neighbours_[cell].begin(); it2 != cell_neighbours_[cell].end(); ++it2) {
if (!is_accepted_[*it2]) {
on_front = true;
break;
}
}
if (!on_front) {
accepted_front_.erase(it++);
} else {
++it;
}
}
// 6. Recompute the value for all Considered cells within
// distance h * F_2/F1 from x_r. Use min of previous and new.
for (auto it = considered_.begin(); it != considered_.end(); ++it) {
const int ccell = it->second;
if (isClose(rcell, ccell)) {
const double value = computeValueUpdate(ccell, metric, solution.data(), rcell);
if (value < it->first) {
// Update value for considered cell.
// Note that as solution values decrease, their
// goodness w.r.t. the heap comparator increase,
// therefore we may safely call the increase()
// modificator below.
considered_.increase(considered_handles_[ccell], std::make_pair(value, ccell));
}
}
}
// 7. Move cells adjacent to r from Far to Considered.
for (auto it = cell_neighbours_[rcell].begin(); it != cell_neighbours_[rcell].end(); ++it) {
const int nb_cell = *it;
if (!is_accepted_[nb_cell] && !is_considered_[nb_cell]) {
assert(solution[nb_cell] == inf);
const double value = computeValue(nb_cell, metric, solution.data());
pushConsidered(std::make_pair(value, nb_cell));
}
}
// 8. If Considered is not empty, go to step 4.
}
}
bool AnisotropicEikonal2d::isClose(const int c1,
const int c2) const
{
const double* v[] = { grid_.cell_centroids + 2*c1,
grid_.cell_centroids + 2*c2 };
return distanceIso(v[0], v[1]) < safety_factor_ * aniso_ratio_[c1] * grid_radius_[c1];
}
double AnisotropicEikonal2d::computeValue(const int cell,
const double* metric,
const double* solution) const
{
// std::cout << "++++ computeValue(), cell = " << cell << std::endl;
const auto& nbs = cell_neighbours_[cell];
const int num_nbs = nbs.size();
const double inf = 1e100;
double val = inf;
for (int ii = 0; ii < num_nbs; ++ii) {
const int n[2] = { nbs[ii], nbs[(ii+1) % num_nbs] };
if (accepted_front_.count(n[0]) && accepted_front_.count(n[1])) {
const double cand_val = computeFromTri(cell, n[0], n[1], metric, solution);
val = std::min(val, cand_val);
}
}
if (val == inf) {
// Failed to find two accepted front nodes adjacent to this,
// so we go for a single-neighbour update.
for (int ii = 0; ii < num_nbs; ++ii) {
if (accepted_front_.count(nbs[ii])) {
const double cand_val = computeFromLine(cell, nbs[ii], metric, solution);
val = std::min(val, cand_val);
}
}
}
assert(val != inf);
// std::cout << "---> " << val << std::endl;
return val;
}
double AnisotropicEikonal2d::computeValueUpdate(const int cell,
const double* metric,
const double* solution,
const int new_cell) const
{
// std::cout << "++++ computeValueUpdate(), cell = " << cell << std::endl;
const auto& nbs = cell_neighbours_[cell];
const int num_nbs = nbs.size();
const double inf = 1e100;
double val = inf;
for (int ii = 0; ii < num_nbs; ++ii) {
const int n[2] = { nbs[ii], nbs[(ii+1) % num_nbs] };
if ((n[0] == new_cell || n[1] == new_cell)
&& accepted_front_.count(n[0]) && accepted_front_.count(n[1])) {
const double cand_val = computeFromTri(cell, n[0], n[1], metric, solution);
val = std::min(val, cand_val);
}
}
if (val == inf) {
// Failed to find two accepted front nodes adjacent to this,
// so we go for a single-neighbour update.
for (int ii = 0; ii < num_nbs; ++ii) {
if (nbs[ii] == new_cell && accepted_front_.count(nbs[ii])) {
const double cand_val = computeFromLine(cell, nbs[ii], metric, solution);
val = std::min(val, cand_val);
}
}
}
// std::cout << "---> " << val << std::endl;
return val;
}
double AnisotropicEikonal2d::computeFromLine(const int cell,
const int from,
const double* metric,
const double* solution) const
{
assert(!is_accepted_[cell]);
assert(is_accepted_[from]);
// Applying the first fundamental form to compute geodesic distance.
// Using the metric of 'cell', not 'from'.
const double dist = distanceAniso(grid_.cell_centroids + 2 * cell,
grid_.cell_centroids + 2 * from,
metric + 4 * cell);
return solution[from] + dist;
}
struct DistanceDerivative
{
const double* x1;
const double* x2;
const double* x;
double u1;
double u2;
const double* g;
double operator()(const double theta) const
{
const double xt[2] = { (1-theta)*x1[0] + theta*x2[0], (1-theta)*x1[1] + theta*x2[1] };
const double a[2] = { x[0] - xt[0], x[1] - xt[1] };
const double b[2] = { x1[0] - x2[0], x1[1] - x2[1] };
const double dQdtheta = 2*(a[0]*b[0]*g[0] + a[0]*b[1]*g[1] + a[1]*b[0]*g[2] + a[1]*b[1]*g[3]);
const double val = u2 - u1 + dQdtheta/(2*distanceAniso(x, xt, g));
// std::cout << theta << " " << val << std::endl;
return val;
}
};
double AnisotropicEikonal2d::computeFromTri(const int cell,
const int n0,
const int n1,
const double* metric,
const double* solution) const
{
// std::cout << "==== cell = " << cell << " n0 = " << n0 << " n1 = " << n1 << std::endl;
assert(!is_accepted_[cell]);
assert(is_accepted_[n0]);
assert(is_accepted_[n1]);
DistanceDerivative dd;
dd.x1 = grid_.cell_centroids + 2 * n0;
dd.x2 = grid_.cell_centroids + 2 * n1;
dd.x = grid_.cell_centroids + 2 * cell;
dd.u1 = solution[n0];
dd.u2 = solution[n1];
dd.g = metric + 4 * cell;
int iter = 0;
const double theta = RegulaFalsi<ContinueOnError>::solve(dd, 0.0, 1.0, 15, 1e-8, iter);
const double xt[2] = { (1-theta)*dd.x1[0] + theta*dd.x2[0],
(1-theta)*dd.x1[1] + theta*dd.x2[1] };
const double d1 = distanceAniso(dd.x1, dd.x, dd.g) + solution[n0];
const double d2 = distanceAniso(dd.x2, dd.x, dd.g) + solution[n1];
const double dt = distanceAniso(xt, dd.x, dd.g) + (1-theta)*solution[n0] + theta*solution[n1];
return std::min(d1, std::min(d2, dt));
}
const AnisotropicEikonal2d::ValueAndCell& AnisotropicEikonal2d::topConsidered() const
{
return considered_.top();
}
void AnisotropicEikonal2d::pushConsidered(const ValueAndCell& vc)
{
HeapHandle h = considered_.push(vc);
considered_handles_[vc.second] = h;
is_considered_[vc.second] = true;
}
void AnisotropicEikonal2d::popConsidered()
{
is_considered_[considered_.top().second] = false;
considered_handles_.erase(considered_.top().second);
considered_.pop();
}
void AnisotropicEikonal2d::computeGridRadius()
{
const int num_cells = cell_neighbours_.size();
grid_radius_.resize(num_cells);
for (int cell = 0; cell < num_cells; ++cell) {
double radius = 0.0;
const double* v1 = grid_.cell_centroids + 2*cell;
const auto& nb = cell_neighbours_[cell];
for (auto it = nb.begin(); it != nb.end(); ++it) {
const double* v2 = grid_.cell_centroids + 2*(*it);
radius = std::max(radius, distanceIso(v1, v2));
}
grid_radius_[cell] = radius;
}
}
void AnisotropicEikonal2d::computeAnisoRatio(const double* metric)
{
const int num_cells = cell_neighbours_.size();
aniso_ratio_.resize(num_cells);
for (int cell = 0; cell < num_cells; ++cell) {
const double* m = metric + 4*cell;
// Find the two eigenvalues from trace and determinant.
const double t = m[0] + m[3];
const double d = m[0]*m[3] - m[1]*m[2];
const double sd = std::sqrt(t*t/4.0 - d);
const double eig[2] = { t/2.0 - sd, t/2.0 + sd };
// Anisotropy ratio is the max ratio of the eigenvalues.
aniso_ratio_[cell] = std::max(eig[0]/eig[1], eig[1]/eig[0]);
}
}
} // namespace Opm
#else // BOOST_HEAP_AVAILABLE is false
namespace {
const char* AnisotropicEikonal2derrmsg =
"\n********************************************************************************\n"
"This library has not been compiled with support for the AnisotropicEikonal2d\n"
"class, due to too old version of the boost libraries (Boost.Heap from boost\n"
"version 1.49 or newer is required.\n"
"To use this class you must recompile opm-core on a system with sufficiently new\n"
"version of the boost libraries."
"\n********************************************************************************\n";
}
namespace Opm
{
AnisotropicEikonal2d::AnisotropicEikonal2d(const UnstructuredGrid&)
{
OPM_THROW(std::logic_error, AnisotropicEikonal2derrmsg);
}
void AnisotropicEikonal2d::solve(const double*,
const std::vector<int>&,
std::vector<double>&)
{
OPM_THROW(std::logic_error, AnisotropicEikonal2derrmsg);
}
}
#endif // BOOST_HEAP_AVAILABLE

View File

@ -1,106 +0,0 @@
/*
Copyright 2014 SINTEF ICT, Applied Mathematics.
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 3 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 <http://www.gnu.org/licenses/>.
*/
#ifndef OPM_ANISOTROPICEIKONAL_HEADER_INCLUDED
#define OPM_ANISOTROPICEIKONAL_HEADER_INCLUDED
#include <opm/grid/utility/SparseTable.hpp>
#include <vector>
#include <set>
#include <map>
#include <opm/common/utility/platform_dependent/disable_warnings.h>
#include <boost/version.hpp>
#define BOOST_HEAP_AVAILABLE ((BOOST_VERSION / 100 % 1000) >= 49)
#if BOOST_HEAP_AVAILABLE
#include <boost/heap/fibonacci_heap.hpp>
#endif
#include <opm/common/utility/platform_dependent/reenable_warnings.h>
struct UnstructuredGrid;
namespace Opm
{
/// A solver for the anisotropic eikonal equation:
/// \f[ || \nabla u^T M^{-1}(x) \nabla u || = 1 \qquad x \in \Omega \f]
/// where M(x) is a symmetric positive definite matrix.
/// The boundary conditions are assumed to be
/// \f[ u(x) = 0 \qquad x \in \partial\Omega \f].
class AnisotropicEikonal2d
{
public:
/// Construct solver.
/// \param[in] grid A 2d grid.
explicit AnisotropicEikonal2d(const UnstructuredGrid& grid);
/// Solve the eikonal equation.
/// \param[in] metric Array of metric tensors, M, for each cell.
/// \param[in] startcells Array of cells where u = 0 at the centroid.
/// \param[out] solution Array of solution to the eikonal equation.
void solve(const double* metric,
const std::vector<int>& startcells,
std::vector<double>& solution);
private:
#if BOOST_HEAP_AVAILABLE
// Grid and topology.
const UnstructuredGrid& grid_;
SparseTable<int> cell_neighbours_;
// Keep track of accepted cells.
std::vector<char> is_accepted_;
std::set<int> accepted_front_;
// Quantities relating to anisotropy.
std::vector<double> grid_radius_;
std::vector<double> aniso_ratio_;
const double safety_factor_;
// Keep track of considered cells.
typedef std::pair<double, int> ValueAndCell;
typedef boost::heap::compare<std::greater<ValueAndCell>> Comparator;
typedef boost::heap::fibonacci_heap<ValueAndCell, Comparator> Heap;
Heap considered_;
typedef Heap::handle_type HeapHandle;
std::map<int, HeapHandle> considered_handles_;
std::vector<char> is_considered_;
bool isClose(const int c1, const int c2) const;
double computeValue(const int cell, const double* metric, const double* solution) const;
double computeValueUpdate(const int cell, const double* metric, const double* solution, const int new_cell) const;
double computeFromLine(const int cell, const int from, const double* metric, const double* solution) const;
double computeFromTri(const int cell, const int n0, const int n1, const double* metric, const double* solution) const;
const ValueAndCell& topConsidered() const;
void pushConsidered(const ValueAndCell& vc);
void popConsidered();
void computeGridRadius();
void computeAnisoRatio(const double* metric);
#endif // BOOST_HEAP_AVAILABLE
};
} // namespace Opm
#endif // OPM_ANISOTROPICEIKONAL_HEADER_INCLUDED

View File

@ -1,342 +0,0 @@
/*
Copyright 2013 SINTEF ICT, Applied Mathematics.
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 3 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 <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <opm/core/flowdiagnostics/DGBasis.hpp>
#include <opm/grid/UnstructuredGrid.h>
#include <opm/common/ErrorMacros.hpp>
#include <numeric>
namespace Opm
{
// ---------------- Methods for class DGBasisInterface ----------------
/// Virtual destructor.
DGBasisInterface::~DGBasisInterface()
{
}
/// Evaluate function f = sum_i c_i b_i at the point x.
/// Note that this function is not virtual, but implemented in
/// terms of the virtual functions of the class.
/// \param[in] cell Cell index
/// \param[in] coefficients Coefficients {c_i} for a single cell.
/// \param[in] x Point at which to compute f(x).
double DGBasisInterface::evalFunc(const int cell,
const double* coefficients,
const double* x) const
{
bvals_.resize(numBasisFunc());
eval(cell, x, &bvals_[0]);
return std::inner_product(bvals_.begin(), bvals_.end(), coefficients, 0.0);
}
// ---------------- Methods for class DGBasisBoundedTotalDegree ----------------
/// Constructor.
/// \param[in] grid grid on which basis is used (cell-wise)
/// \param[in] degree polynomial degree of basis
DGBasisBoundedTotalDegree::DGBasisBoundedTotalDegree(const UnstructuredGrid& grid,
const int degree_arg)
: grid_(grid),
degree_(degree_arg)
{
if (grid_.dimensions > 3) {
OPM_THROW(std::runtime_error, "Grid dimension must be 1, 2 or 3.");
}
if (degree_ > 1 || degree_ < 0) {
OPM_THROW(std::runtime_error, "Degree must be 0 or 1.");
}
}
/// Destructor.
DGBasisBoundedTotalDegree::~DGBasisBoundedTotalDegree()
{
}
/// The number of basis functions per cell.
int DGBasisBoundedTotalDegree::numBasisFunc() const
{
switch (dimensions()) {
case 1:
return degree_ + 1;
case 2:
return (degree_ + 2)*(degree_ + 1)/2;
case 3:
return (degree_ + 3)*(degree_ + 2)*(degree_ + 1)/6;
default:
OPM_THROW(std::runtime_error, "Dimensions must be 1, 2 or 3.");
}
}
/// The number of space dimensions.
int DGBasisBoundedTotalDegree::dimensions() const
{
return grid_.dimensions;
}
/// The polynomial degree of the basis functions.
int DGBasisBoundedTotalDegree::degree() const
{
return degree_;
}
/// Evaluate all basis functions associated with cell at x,
/// writing to f_x. The array f_x must have size equal to
/// numBasisFunc().
void DGBasisBoundedTotalDegree::eval(const int cell,
const double* x,
double* f_x) const
{
const int dim = dimensions();
const double* cc = grid_.cell_centroids + dim*cell;
// Note intentional fallthrough in this switch statement!
switch (degree_) {
case 1:
for (int ix = 0; ix < dim; ++ix) {
f_x[1 + ix] = x[ix] - cc[ix];
}
case 0:
f_x[0] = 1;
break;
default:
OPM_THROW(std::runtime_error, "Maximum degree is 1 for now.");
}
}
/// Evaluate gradients of all basis functions associated with
/// cell at x, writing to grad_f_x. The array grad_f_x must
/// have size numBasisFunc() * dimensions(). The dimensions()
/// components of the first basis function gradient come
/// before the components of the second etc.
void DGBasisBoundedTotalDegree::evalGrad(const int /*cell*/,
const double* /*x*/,
double* grad_f_x) const
{
const int dim = dimensions();
const int num_basis = numBasisFunc();
std::fill(grad_f_x, grad_f_x + num_basis*dim, 0.0);
if (degree_ == 1) {
for (int ix = 0; ix < dim; ++ix) {
grad_f_x[dim*(ix + 1) + ix] = 1.0;
}
}
}
/// Modify basis coefficients to add to the function value.
/// A function f = sum_i c_i b_i is assumed, and we change
/// it to (f + increment) by modifying the c_i. This is done without
/// modifying its gradient.
/// \param[in] increment Add this value to the function.
/// \param[out] coefficients Coefficients {c_i} for a single cell.
void DGBasisBoundedTotalDegree::addConstant(const double increment,
double* coefficients) const
{
coefficients[0] += increment;
}
/// Modify basis coefficients to change the function's slope.
/// A function f = sum_i c_i b_i is assumed, and we change
/// it to a function g with the property that grad g = factor * grad f
/// by modifying the c_i. This is done without modifying the average,
/// i.e. the integrals of g and f over the cell are the same.
/// \param[in] factor Multiply gradient by this factor.
/// \param[out] coefficients Coefficients {c_i} for a single cell.
void DGBasisBoundedTotalDegree::multiplyGradient(const double factor,
double* coefficients) const
{
const int nb = numBasisFunc();
for (int ix = 1; ix < nb; ++ix) {
coefficients[ix] *= factor;
}
}
/// Compute the average of the function f = sum_i c_i b_i.
/// \param[in] coefficients Coefficients {c_i} for a single cell.
double DGBasisBoundedTotalDegree::functionAverage(const double* coefficients) const
{
return coefficients[0];
}
// ---------------- Methods for class DGBasisMultilin ----------------
/// Constructor.
/// \param[in] grid grid on which basis is used (cell-wise)
/// \param[in] degree polynomial degree of basis
DGBasisMultilin::DGBasisMultilin(const UnstructuredGrid& grid,
const int degree_arg)
: grid_(grid),
degree_(degree_arg)
{
if (grid_.dimensions > 3) {
OPM_THROW(std::runtime_error, "Grid dimension must be 1, 2 or 3.");
}
if (degree_ > 1 || degree_ < 0) {
OPM_THROW(std::runtime_error, "Degree must be 0 or 1.");
}
}
/// Destructor.
DGBasisMultilin::~DGBasisMultilin()
{
}
/// The number of basis functions per cell.
int DGBasisMultilin::numBasisFunc() const
{
switch (dimensions()) {
case 1:
return degree_ + 1;
case 2:
return (degree_ + 1)*(degree_ + 1);
case 3:
return (degree_ + 1)*(degree_ + 1)*(degree_ + 1);
default:
OPM_THROW(std::runtime_error, "Dimensions must be 1, 2 or 3.");
}
}
/// The number of space dimensions.
int DGBasisMultilin::dimensions() const
{
return grid_.dimensions;
}
/// The polynomial degree of the basis functions.
int DGBasisMultilin::degree() const
{
return degree_;
}
/// Evaluate all basis functions associated with cell at x,
/// writing to f_x. The array f_x must have size equal to
/// numBasisFunc().
void DGBasisMultilin::eval(const int cell,
const double* x,
double* f_x) const
{
const int dim = dimensions();
const int num_basis = numBasisFunc();
const double* cc = grid_.cell_centroids + dim*cell;
switch (degree_) {
case 0:
f_x[0] = 1;
break;
case 1:
std::fill(f_x, f_x + num_basis, 1.0);
for (int dd = 0; dd < dim; ++dd) {
const double f[2] = { 0.5 - x[dd] + cc[dd], 0.5 + x[dd] - cc[dd] };
const int divi = 1 << (dim - dd - 1); // { 4, 2, 1 } for 3d, for example.
for (int ix = 0; ix < num_basis; ++ix) {
f_x[ix] *= f[(ix/divi) % 2];
}
}
break;
default:
OPM_THROW(std::runtime_error, "Maximum degree is 1 for now.");
}
}
/// Evaluate gradients of all basis functions associated with
/// cell at x, writing to grad_f_x. The array grad_f_x must
/// have size numBasisFunc() * dimensions(). The dimensions()
/// components of the first basis function gradient come
/// before the components of the second etc.
void DGBasisMultilin::evalGrad(const int cell,
const double* x,
double* grad_f_x) const
{
const int dim = dimensions();
const int num_basis = numBasisFunc();
const double* cc = grid_.cell_centroids + dim*cell;
switch (degree_) {
case 0:
std::fill(grad_f_x, grad_f_x + num_basis*dim, 0.0);
break;
case 1:
std::fill(grad_f_x, grad_f_x + num_basis*dim, 1.0);
for (int dd = 0; dd < dim; ++dd) {
const double f[2] = { 0.5 - x[dd] + cc[dd], 0.5 + x[dd] - cc[dd] };
const double fder[2] = { -1.0, 1.0 };
const int divi = 1 << (dim - dd - 1); // { 4, 2, 1 } for 3d, for example.
for (int ix = 0; ix < num_basis; ++ix) {
const int ind = (ix/divi) % 2;
for (int dder = 0; dder < dim; ++dder) {
grad_f_x[ix*dim + dder] *= (dder == dd ? fder[ind] : f[ind]);
}
}
}
break;
default:
OPM_THROW(std::runtime_error, "Maximum degree is 1 for now.");
}
}
/// Modify basis coefficients to add to the function value.
/// A function f = sum_i c_i b_i is assumed, and we change
/// it to (f + increment) by modifying the c_i. This is done without
/// modifying its gradient.
/// \param[in] increment Add this value to the function.
/// \param[out] coefficients Coefficients {c_i} for a single cell.
void DGBasisMultilin::addConstant(const double increment,
double* coefficients) const
{
const int nb = numBasisFunc();
const double term = increment/double(nb);
for (int ix = 0; ix < nb; ++ix) {
coefficients[ix] += term;
}
}
/// Modify basis coefficients to change the function's slope.
/// A function f = sum_i c_i b_i is assumed, and we change
/// it to a function g with the property that grad g = factor * grad f
/// by modifying the c_i. This is done without modifying the average,
/// i.e. the integrals of g and f over the cell are the same.
/// \param[in] factor Multiply gradient by this factor.
/// \param[out] coefficients Coefficients {c_i} for a single cell.
void DGBasisMultilin::multiplyGradient(const double factor,
double* coefficients) const
{
const int nb = numBasisFunc();
const double aver = functionAverage(coefficients);
for (int ix = 0; ix < nb; ++ix) {
coefficients[ix] = factor*(coefficients[ix] - aver) + aver;
}
}
/// Compute the average of the function f = sum_i c_i b_i.
/// \param[in] coefficients Coefficients {c_i} for a single cell.
double DGBasisMultilin::functionAverage(const double* coefficients) const
{
const int nb = numBasisFunc();
return std::accumulate(coefficients, coefficients + nb, 0.0)/double(nb);
}
} // namespace Opm

View File

@ -1,259 +0,0 @@
/*
Copyright 2013 SINTEF ICT, Applied Mathematics.
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 3 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 <http://www.gnu.org/licenses/>.
*/
#ifndef OPM_DGBASIS_HEADER_INCLUDED
#define OPM_DGBASIS_HEADER_INCLUDED
#include <vector>
struct UnstructuredGrid;
namespace Opm
{
/// Base class for Discontinuous Galerkin bases, intended for time-of-flight computations.
class DGBasisInterface
{
public:
/// Virtual destructor.
virtual ~DGBasisInterface();
/// The number of basis functions per cell.
virtual int numBasisFunc() const = 0;
/// The number of space dimensions.
virtual int dimensions() const = 0;
/// The polynomial degree of the basis functions.
virtual int degree() const = 0;
/// Evaluate all basis functions associated with cell at x,
/// writing to f_x. The array f_x must have size equal to
/// numBasisFunc().
virtual void eval(const int cell,
const double* x,
double* f_x) const = 0;
/// Evaluate gradients of all basis functions associated with
/// cell at x, writing to grad_f_x. The array grad_f_x must
/// have size numBasisFunc() * dimensions(). The dimensions()
/// components of the first basis function gradient come
/// before the components of the second etc.
virtual void evalGrad(const int cell,
const double* x,
double* grad_f_x) const = 0;
/// Modify basis coefficients to add to the function value.
/// A function f = sum_i c_i b_i is assumed, and we change
/// it to (f + increment) by modifying the c_i. This is done without
/// modifying its gradient.
/// \param[in] increment Add this value to the function.
/// \param[out] coefficients Coefficients {c_i} for a single cell.
virtual void addConstant(const double increment,
double* coefficients) const = 0;
/// Modify basis coefficients to change the function's slope.
/// A function f = sum_i c_i b_i is assumed, and we change
/// it to a function g with the property that grad g = factor * grad f
/// by modifying the c_i. This is done without modifying the average,
/// i.e. the integrals of g and f over the cell are the same.
/// \param[in] factor Multiply gradient by this factor.
/// \param[out] coefficients Coefficients {c_i} for a single cell.
virtual void multiplyGradient(const double factor,
double* coefficients) const = 0;
/// Evaluate function f = sum_i c_i b_i at the point x.
/// Note that this function is not virtual, but implemented in
/// terms of the virtual functions of the class.
/// \param[in] cell Cell index
/// \param[in] coefficients Coefficients {c_i} for a single cell.
/// \param[in] x Point at which to compute f(x).
double evalFunc(const int cell,
const double* coefficients,
const double* x) const;
/// Compute the average of the function f = sum_i c_i b_i.
/// \param[in] coefficients Coefficients {c_i} for a single cell.
virtual double functionAverage(const double* coefficients) const = 0;
private:
mutable std::vector<double> bvals_; // For evalFunc().
};
/// A class providing discontinuous Galerkin basis functions
/// of bounded total degree.
///
/// The basis functions are the following for each cell (example for 3d):
/// Degree 0: 1.
/// Degree 1: 1, x - xc, y - yc, z - zc
/// where (xc, yc, zc) are the coordinates of the cell centroid.
/// Further degrees await development.
class DGBasisBoundedTotalDegree : public DGBasisInterface
{
public:
/// Constructor.
/// \param[in] grid grid on which basis is used (cell-wise)
/// \param[in] degree polynomial degree of basis
DGBasisBoundedTotalDegree(const UnstructuredGrid& grid, const int degree);
/// Destructor.
virtual ~DGBasisBoundedTotalDegree();
/// The number of basis functions per cell.
virtual int numBasisFunc() const;
/// The number of space dimensions.
virtual int dimensions() const;
/// The polynomial degree of the basis functions.
virtual int degree() const;
/// Evaluate all basis functions associated with cell at x,
/// writing to f_x. The array f_x must have size equal to
/// numBasisFunc().
virtual void eval(const int cell,
const double* x,
double* f_x) const;
/// Evaluate gradients of all basis functions associated with
/// cell at x, writing to grad_f_x. The array grad_f_x must
/// have size numBasisFunc() * dimensions(). The dimensions()
/// components of the first basis function gradient come
/// before the components of the second etc.
virtual void evalGrad(const int cell,
const double* x,
double* grad_f_x) const;
/// Modify basis coefficients to add to the function value.
/// A function f = sum_i c_i b_i is assumed, and we change
/// it to (f + increment) by modifying the c_i. This is done without
/// modifying its gradient.
/// \param[in] increment Add this value to the function.
/// \param[out] coefficients Coefficients {c_i} for a single cell.
virtual void addConstant(const double increment,
double* coefficients) const;
/// Modify basis coefficients to change the function's slope.
/// A function f = sum_i c_i b_i is assumed, and we change
/// it to a function g with the property that grad g = factor * grad f
/// by modifying the c_i. This is done without modifying the average,
/// i.e. the integrals of g and f over the cell are the same.
/// \param[in] factor Multiply gradient by this factor.
/// \param[out] coefficients Coefficients {c_i} for a single cell.
virtual void multiplyGradient(const double factor,
double* coefficients) const;
/// Compute the average of the function f = sum_i c_i b_i.
/// \param[in] coefficients Coefficients {c_i} for a single cell.
virtual double functionAverage(const double* coefficients) const;
private:
const UnstructuredGrid& grid_;
const int degree_;
};
/// A class providing discontinuous Galerkin basis functions of
/// multi-degree 1 (bilinear or trilinear functions).
///
/// The basis functions for a cell are the following
/// Degree 0: 1.
/// (for 2 dims:)
/// (Bi)degree 1: (x-)(y-), (x-)(y+), (x+)(y-), (x+)(y+)
/// where (x-) = (1/2 - x + xc), (x+) = (1/2 + x - xc)
/// and xc is the x-coordinate of the cell centroid.
/// Similar for (y-), (y+).
class DGBasisMultilin : public DGBasisInterface
{
public:
/// Constructor.
/// \param[in] grid grid on which basis is used (cell-wise)
/// \param[in] degree polynomial degree of basis (in each coordinate)
DGBasisMultilin(const UnstructuredGrid& grid, const int degree);
/// Destructor.
virtual ~DGBasisMultilin();
/// The number of basis functions per cell.
virtual int numBasisFunc() const;
/// The number of space dimensions.
virtual int dimensions() const;
/// The polynomial degree of the basis functions.
virtual int degree() const;
/// Evaluate all basis functions associated with cell at x,
/// writing to f_x. The array f_x must have size equal to
/// numBasisFunc().
virtual void eval(const int cell,
const double* x,
double* f_x) const;
/// Evaluate gradients of all basis functions associated with
/// cell at x, writing to grad_f_x. The array grad_f_x must
/// have size numBasisFunc() * dimensions(). The dimensions()
/// components of the first basis function gradient come
/// before the components of the second etc.
virtual void evalGrad(const int cell,
const double* x,
double* grad_f_x) const;
/// Modify basis coefficients to add to the function value.
/// A function f = sum_i c_i b_i is assumed, and we change
/// it to (f + increment) by modifying the c_i. This is done without
/// modifying its gradient.
/// \param[in] increment Add this value to the function.
/// \param[out] coefficients Coefficients {c_i} for a single cell.
virtual void addConstant(const double increment,
double* coefficients) const;
/// Modify basis coefficients to change the function's slope.
/// A function f = sum_i c_i b_i is assumed, and we change
/// it to a function g with the property that grad g = factor * grad f
/// by modifying the c_i. This is done without modifying the average,
/// i.e. the integrals of g and f over the cell are the same.
/// \param[in] factor Multiply gradient by this factor.
/// \param[out] coefficients Coefficients {c_i} for a single cell.
virtual void multiplyGradient(const double factor,
double* coefficients) const;
/// Compute the average of the function f = sum_i c_i b_i.
/// \param[in] coefficients Coefficients {c_i} for a single cell.
virtual double functionAverage(const double* coefficients) const;
private:
const UnstructuredGrid& grid_;
const int degree_;
};
} // namespace Opm
#endif // OPM_DGBASIS_HEADER_INCLUDED

View File

@ -1,226 +0,0 @@
/*
Copyright 2015 SINTEF ICT, Applied Mathematics.
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 3 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 <http://www.gnu.org/licenses/>.
*/
#include <opm/core/flowdiagnostics/FlowDiagnostics.hpp>
#include <opm/core/wells.h>
#include <opm/common/ErrorMacros.hpp>
#include <algorithm>
#include <numeric>
namespace Opm
{
/// \brief Compute flow-capacity/storage-capacity based on time-of-flight.
///
/// The F-Phi curve is an analogue to the fractional flow curve in a 1D
/// displacement. It can be used to compute other interesting diagnostic
/// quantities such as the Lorenz coefficient. For a technical description
/// see Shavali et al. (SPE 146446), Shook and Mitchell (SPE 124625).
///
/// \param[in] pv pore volumes of each cell
/// \param[in] ftof forward (time from injector) time-of-flight values for each cell
/// \param[in] rtof reverse (time to producer) time-of-flight values for each cell
/// \return a pair of vectors, the first containing F (flow capacity) the second
/// containing Phi (storage capacity).
std::pair<std::vector<double>, std::vector<double>> computeFandPhi(const std::vector<double>& pv,
const std::vector<double>& ftof,
const std::vector<double>& rtof)
{
if (pv.size() != ftof.size() || pv.size() != rtof.size()) {
OPM_THROW(std::runtime_error, "computeFandPhi(): Input vectors must have same size.");
}
// Sort according to total travel time.
const int n = pv.size();
typedef std::pair<double, double> D2;
std::vector<D2> time_and_pv(n);
for (int ii = 0; ii < n; ++ii) {
time_and_pv[ii].first = ftof[ii] + rtof[ii]; // Total travel time.
time_and_pv[ii].second = pv[ii];
}
std::sort(time_and_pv.begin(), time_and_pv.end());
// Compute Phi.
std::vector<double> Phi(n + 1);
Phi[0] = 0.0;
for (int ii = 0; ii < n; ++ii) {
Phi[ii+1] = time_and_pv[ii].second;
}
std::partial_sum(Phi.begin(), Phi.end(), Phi.begin());
const double vt = Phi.back(); // Total pore volume.
for (int ii = 1; ii < n+1; ++ii) { // Note limits of loop.
Phi[ii] /= vt; // Normalize Phi.
}
// Compute F.
std::vector<double> F(n + 1);
F[0] = 0.0;
for (int ii = 0; ii < n; ++ii) {
F[ii+1] = time_and_pv[ii].second / time_and_pv[ii].first;
}
std::partial_sum(F.begin(), F.end(), F.begin());
const double ft = F.back(); // Total flux.
for (int ii = 1; ii < n+1; ++ii) { // Note limits of loop.
F[ii] /= ft; // Normalize Phi.
}
return std::make_pair(F, Phi);
}
/// \brief Compute the Lorenz coefficient based on the F-Phi curve.
///
/// The Lorenz coefficient is a measure of heterogeneity. It is equal
/// to twice the area between the F-Phi curve and the F = Phi line.
/// The coefficient can vary from zero to one. If the coefficient is
/// zero (so the F-Phi curve is a straight line) we have perfect
/// piston-like displacement while a coefficient of one indicates
/// infinitely heterogenous displacement (essentially no sweep).
///
/// Note: The coefficient is analogous to the Gini coefficient of
/// economic theory, where the name Lorenz curve is applied to
/// what we call the F-Phi curve.
///
/// \param[in] flowcap flow capacity (F) as from computeFandPhi()
/// \param[in] storagecap storage capacity (Phi) as from computeFandPhi()
/// \return the Lorenz coefficient
double computeLorenz(const std::vector<double>& flowcap,
const std::vector<double>& storagecap)
{
if (flowcap.size() != storagecap.size()) {
OPM_THROW(std::runtime_error, "computeLorenz(): Input vectors must have same size.");
}
double integral = 0.0;
// Trapezoid quadrature of the curve F(Phi).
const int num_intervals = flowcap.size() - 1;
for (int ii = 0; ii < num_intervals; ++ii) {
const double len = storagecap[ii+1] - storagecap[ii];
integral += (flowcap[ii] + flowcap[ii+1]) * len / 2.0;
}
return 2.0 * (integral - 0.5);
}
/// \brief Compute sweep efficiency versus dimensionless time (PVI).
///
/// The sweep efficiency is analogue to 1D displacement using the
/// F-Phi curve as flux function.
///
/// \param[in] flowcap flow capacity (F) as from computeFandPhi()
/// \param[in] storagecap storage capacity (Phi) as from computeFandPhi()
/// \return a pair of vectors, the first containing Ev (sweep efficiency)
/// the second containing tD (dimensionless time).
std::pair<std::vector<double>, std::vector<double>> computeSweep(const std::vector<double>& flowcap,
const std::vector<double>& storagecap)
{
if (flowcap.size() != storagecap.size()) {
OPM_THROW(std::runtime_error, "computeSweep(): Input vectors must have same size.");
}
// Compute tD and Ev simultaneously,
// skipping identical Phi data points.
const int n = flowcap.size();
std::vector<double> Ev;
std::vector<double> tD;
tD.reserve(n);
Ev.reserve(n);
tD.push_back(0.0);
Ev.push_back(0.0);
for (int ii = 1; ii < n; ++ii) { // Note loop limits.
const double fd = flowcap[ii] - flowcap[ii-1];
const double sd = storagecap[ii] - storagecap[ii-1];
if (fd != 0.0) {
tD.push_back(sd/fd);
Ev.push_back(storagecap[ii] + (1.0 - flowcap[ii]) * tD.back());
}
}
return std::make_pair(Ev, tD);
}
/// \brief Compute volumes associated with injector-producer pairs.
///
/// \param[in] wells wells structure, containing NI injector wells and NP producer wells.
/// \param[in] porevol pore volume of each grid cell
/// \param[in] ftracer array of forward (injector) tracer values, NI per cell
/// \param[in] btracer array of backward (producer) tracer values, NP per cell
/// \return a vector of tuples, one tuple for each injector-producer pair,
/// where the first and second elements are well indices for the
/// injector and producer, and the third element is the pore volume
/// associated with that pair.
std::vector<std::tuple<int, int, double> >
computeWellPairs(const Wells& wells,
const std::vector<double>& porevol,
const std::vector<double>& ftracer,
const std::vector<double>& btracer)
{
// Identify injectors and producers.
std::vector<int> inj;
std::vector<int> prod;
const int nw = wells.number_of_wells;
for (int w = 0; w < nw; ++w) {
if (wells.type[w] == INJECTOR) {
inj.push_back(w);
} else {
prod.push_back(w);
}
}
// Check sizes of input arrays.
const int nc = porevol.size();
if (nc * inj.size() != ftracer.size()) {
OPM_THROW(std::runtime_error, "computeWellPairs(): wrong size of input array ftracer.");
}
if (nc * prod.size() != btracer.size()) {
OPM_THROW(std::runtime_error, "computeWellPairs(): wrong size of input array btracer.");
}
// Compute associated pore volumes.
std::vector<std::tuple<int, int, double> > result;
const int num_inj = inj.size();
const int num_prod = prod.size();
for (int inj_ix = 0; inj_ix < num_inj; ++inj_ix) {
for (int prod_ix = 0; prod_ix < num_prod; ++prod_ix) {
double assoc_porevol = 0.0;
for (int c = 0; c < nc; ++c) {
assoc_porevol += porevol[c]
* ftracer[num_inj * c + inj_ix]
* btracer[num_prod * c + prod_ix];
}
result.push_back(std::make_tuple(inj[inj_ix], prod[prod_ix], assoc_porevol));
}
}
return result;
}
} // namespace Opm

View File

@ -1,103 +0,0 @@
/*
Copyright 2015 SINTEF ICT, Applied Mathematics.
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 3 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 <http://www.gnu.org/licenses/>.
*/
#ifndef OPM_FLOWDIAGNOSTICS_HEADER_INCLUDED
#define OPM_FLOWDIAGNOSTICS_HEADER_INCLUDED
#include <vector>
#include <utility>
#include <tuple>
struct Wells;
namespace Opm
{
/// \brief Compute flow-capacity/storage-capacity based on time-of-flight.
///
/// The F-Phi curve is an analogue to the fractional flow curve in a 1D
/// displacement. It can be used to compute other interesting diagnostic
/// quantities such as the Lorenz coefficient. For a technical description
/// see Shavali et al. (SPE 146446), Shook and Mitchell (SPE 124625).
///
/// \param[in] pv pore volumes of each cell
/// \param[in] ftof forward (time from injector) time-of-flight values for each cell
/// \param[in] rtof reverse (time to producer) time-of-flight values for each cell
/// \return a pair of vectors, the first containing F (flow capacity) the second
/// containing Phi (storage capacity).
std::pair<std::vector<double>, std::vector<double>>
computeFandPhi(const std::vector<double>& pv,
const std::vector<double>& ftof,
const std::vector<double>& rtof);
/// \brief Compute the Lorenz coefficient based on the F-Phi curve.
///
/// The Lorenz coefficient is a measure of heterogeneity. It is equal
/// to twice the area between the F-Phi curve and the F = Phi line.
/// The coefficient can vary from zero to one. If the coefficient is
/// zero (so the F-Phi curve is a straight line) we have perfect
/// piston-like displacement while a coefficient of one indicates
/// infinitely heterogenous displacement (essentially no sweep).
///
/// Note: The coefficient is analogous to the Gini coefficient of
/// economic theory, where the name Lorenz curve is applied to
/// what we call the F-Phi curve.
///
/// \param[in] flowcap flow capacity (F) as from computeFandPhi()
/// \param[in] storagecap storage capacity (Phi) as from computeFandPhi()
/// \return the Lorenz coefficient
double computeLorenz(const std::vector<double>& flowcap,
const std::vector<double>& storagecap);
/// \brief Compute sweep efficiency versus dimensionless time (PVI).
///
/// The sweep efficiency is analogue to 1D displacement using the
/// F-Phi curve as flux function.
///
/// \param[in] flowcap flow capacity (F) as from computeFandPhi()
/// \param[in] storagecap storage capacity (Phi) as from computeFandPhi()
/// \return a pair of vectors, the first containing Ev (sweep efficiency)
/// the second containing tD (dimensionless time).
std::pair<std::vector<double>, std::vector<double>>
computeSweep(const std::vector<double>& flowcap,
const std::vector<double>& storagecap);
/// \brief Compute volumes associated with injector-producer pairs.
///
/// \param[in] wells wells structure, containing NI injector wells and NP producer wells.
/// \param[in] porevol pore volume of each grid cell
/// \param[in] ftracer array of forward (injector) tracer values, NI per cell
/// \param[in] btracer array of backward (producer) tracer values, NP per cell
/// \return a vector of tuples, one tuple for each injector-producer pair,
/// where the first and second elements are well indices for the
/// injector and producer, and the third element is the pore volume
/// associated with that pair.
std::vector<std::tuple<int, int, double>>
computeWellPairs(const Wells& wells,
const std::vector<double>& porevol,
const std::vector<double>& ftracer,
const std::vector<double>& btracer);
} // namespace Opm
#endif // OPM_FLOWDIAGNOSTICS_HEADER_INCLUDED

View File

@ -1,806 +0,0 @@
/*
Copyright 2012 SINTEF ICT, Applied Mathematics.
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 3 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 <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <opm/grid/CellQuadrature.hpp>
#include <opm/grid/FaceQuadrature.hpp>
#include <opm/core/flowdiagnostics/TofDiscGalReorder.hpp>
#include <opm/core/flowdiagnostics/DGBasis.hpp>
#include <opm/grid/UnstructuredGrid.h>
#include <opm/common/ErrorMacros.hpp>
#include <opm/grid/utility/SparseTable.hpp>
#include <opm/grid/utility/VelocityInterpolation.hpp>
#include <opm/common/utility/parameters/ParameterGroup.hpp>
#include <opm/common/utility/numeric/blas_lapack.h>
#include <algorithm>
#include <cmath>
#include <numeric>
#include <iostream>
namespace Opm
{
/// Construct solver.
TofDiscGalReorder::TofDiscGalReorder(const UnstructuredGrid& grid,
const ParameterGroup& param)
: grid_(grid),
use_cvi_(false),
use_limiter_(false),
limiter_relative_flux_threshold_(1e-3),
limiter_method_(MinUpwindAverage),
limiter_usage_(DuringComputations),
coord_(grid.dimensions),
velocity_(grid.dimensions),
gauss_seidel_tol_(1e-3)
{
const int dg_degree = param.getDefault("dg_degree", 0);
const bool use_tensorial_basis = param.getDefault("use_tensorial_basis", false);
if (use_tensorial_basis) {
basis_func_.reset(new DGBasisMultilin(grid_, dg_degree));
} else {
basis_func_.reset(new DGBasisBoundedTotalDegree(grid_, dg_degree));
}
tracers_ensure_unity_ = param.getDefault("tracers_ensure_unity", true);
use_cvi_ = param.getDefault("use_cvi", use_cvi_);
use_limiter_ = param.getDefault("use_limiter", use_limiter_);
if (use_limiter_) {
limiter_relative_flux_threshold_ = param.getDefault("limiter_relative_flux_threshold",
limiter_relative_flux_threshold_);
const std::string limiter_method_str = param.getDefault<std::string>("limiter_method", "MinUpwindAverage");
if (limiter_method_str == "MinUpwindFace") {
limiter_method_ = MinUpwindFace;
} else if (limiter_method_str == "MinUpwindAverage") {
limiter_method_ = MinUpwindAverage;
} else {
OPM_THROW(std::runtime_error, "Unknown limiter method: " << limiter_method_str);
}
const std::string limiter_usage_str = param.getDefault<std::string>("limiter_usage", "DuringComputations");
if (limiter_usage_str == "DuringComputations") {
limiter_usage_ = DuringComputations;
} else if (limiter_usage_str == "AsPostProcess") {
limiter_usage_ = AsPostProcess;
} else if (limiter_usage_str == "AsSimultaneousPostProcess") {
limiter_usage_ = AsSimultaneousPostProcess;
} else {
OPM_THROW(std::runtime_error, "Unknown limiter usage spec: " << limiter_usage_str);
}
}
// A note about the use_cvi_ member variable:
// In principle, we should not need it, since the choice of velocity
// interpolation is made below, but we may need to use higher order
// quadrature to exploit CVI, so we store the choice.
// An alternative would be to add a virtual method isConstant() to
// the VelocityInterpolationInterface.
if (use_cvi_) {
velocity_interpolation_.reset(new VelocityInterpolationECVI(grid_));
} else {
velocity_interpolation_.reset(new VelocityInterpolationConstant(grid_));
}
}
/// Solve for time-of-flight.
void TofDiscGalReorder::solveTof(const double* darcyflux,
const double* porevolume,
const double* source,
std::vector<double>& tof_coeff)
{
darcyflux_ = darcyflux;
porevolume_ = porevolume;
source_ = source;
#ifndef NDEBUG
// Sanity check for sources.
const double cum_src = std::accumulate(source, source + grid_.number_of_cells, 0.0);
if (std::fabs(cum_src) > *std::max_element(source, source + grid_.number_of_cells)*1e-2) {
// OPM_THROW(std::runtime_error, "Sources do not sum to zero: " << cum_src);
OPM_MESSAGE("Warning: sources do not sum to zero: " << cum_src);
}
#endif
const int num_basis = basis_func_->numBasisFunc();
tof_coeff.resize(num_basis*grid_.number_of_cells);
std::fill(tof_coeff.begin(), tof_coeff.end(), 0.0);
tof_coeff_ = &tof_coeff[0];
rhs_.resize(num_basis);
jac_.resize(num_basis*num_basis);
orig_jac_.resize(num_basis*num_basis);
basis_.resize(num_basis);
basis_nb_.resize(num_basis);
grad_basis_.resize(num_basis*grid_.dimensions);
velocity_interpolation_->setupFluxes(darcyflux);
num_tracers_ = 0;
num_multicell_ = 0;
max_size_multicell_ = 0;
max_iter_multicell_ = 0;
num_singlesolves_ = 0;
reorderAndTransport(grid_, darcyflux);
switch (limiter_usage_) {
case AsPostProcess:
applyLimiterAsPostProcess();
break;
case AsSimultaneousPostProcess:
applyLimiterAsSimultaneousPostProcess();
break;
case DuringComputations:
// Do nothing.
break;
default:
OPM_THROW(std::runtime_error, "Unknown limiter usage choice: " << limiter_usage_);
}
if (num_multicell_ > 0) {
std::cout << num_multicell_ << " multicell blocks with max size "
<< max_size_multicell_ << " cells in upto "
<< max_iter_multicell_ << " iterations." << std::endl;
std::cout << "Average solves per cell (for all cells) was "
<< double(num_singlesolves_)/double(grid_.number_of_cells) << std::endl;
}
}
/// Solve for time-of-flight and a number of tracers.
/// \param[in] darcyflux Array of signed face fluxes.
/// \param[in] porevolume Array of pore volumes.
/// \param[in] source Source term. Sign convention is:
/// (+) inflow flux,
/// (-) outflow flux.
/// \param[in] tracerheads Table containing one row per tracer, and each
/// row contains the source cells for that tracer.
/// \param[out] tof_coeff Array of time-of-flight solution coefficients.
/// The values are ordered by cell, meaning that
/// the K coefficients corresponding to the first
/// cell comes before the K coefficients corresponding
/// to the second cell etc.
/// K depends on degree and grid dimension.
/// \param[out] tracer_coeff Array of tracer solution coefficients. N*K per cell,
/// where N is equal to tracerheads.size(). All K coefs
/// for a tracer are consecutive, and all tracers' coefs
/// for a cell come before those for the next cell.
void TofDiscGalReorder::solveTofTracer(const double* darcyflux,
const double* porevolume,
const double* source,
const SparseTable<int>& tracerheads,
std::vector<double>& tof_coeff,
std::vector<double>& tracer_coeff)
{
darcyflux_ = darcyflux;
porevolume_ = porevolume;
source_ = source;
#ifndef NDEBUG
// Sanity check for sources.
const double cum_src = std::accumulate(source, source + grid_.number_of_cells, 0.0);
if (std::fabs(cum_src) > *std::max_element(source, source + grid_.number_of_cells)*1e-2) {
// OPM_THROW(std::runtime_error, "Sources do not sum to zero: " << cum_src);
OPM_MESSAGE("Warning: sources do not sum to zero: " << cum_src);
}
#endif
const int num_basis = basis_func_->numBasisFunc();
num_tracers_ = tracerheads.size();
tof_coeff.resize(num_basis*grid_.number_of_cells);
std::fill(tof_coeff.begin(), tof_coeff.end(), 0.0);
tof_coeff_ = &tof_coeff[0];
rhs_.resize(num_basis*(num_tracers_ + 1));
jac_.resize(num_basis*num_basis);
orig_jac_.resize(num_basis*num_basis);
basis_.resize(num_basis);
basis_nb_.resize(num_basis);
grad_basis_.resize(num_basis*grid_.dimensions);
velocity_interpolation_->setupFluxes(darcyflux);
// Set up tracer
tracer_coeff.resize(grid_.number_of_cells*num_tracers_*num_basis);
std::fill(tracer_coeff.begin(), tracer_coeff.end(), 0.0);
if (num_tracers_ > 0) {
tracerhead_by_cell_.clear();
tracerhead_by_cell_.resize(grid_.number_of_cells, NoTracerHead);
}
for (int tr = 0; tr < num_tracers_; ++tr) {
const unsigned int tracerheadsSize = tracerheads[tr].size();
for (unsigned int i = 0; i < tracerheadsSize; ++i) {
const int cell = tracerheads[tr][i];
basis_func_->addConstant(1.0, &tracer_coeff[cell*num_tracers_*num_basis + tr*num_basis]);
tracer_coeff[cell*num_tracers_ + tr] = 1.0;
tracerhead_by_cell_[cell] = tr;
}
}
tracer_coeff_ = &tracer_coeff[0];
num_multicell_ = 0;
max_size_multicell_ = 0;
max_iter_multicell_ = 0;
num_singlesolves_ = 0;
reorderAndTransport(grid_, darcyflux);
switch (limiter_usage_) {
case AsPostProcess:
applyLimiterAsPostProcess();
break;
case AsSimultaneousPostProcess:
applyLimiterAsSimultaneousPostProcess();
break;
case DuringComputations:
// Do nothing.
break;
default:
OPM_THROW(std::runtime_error, "Unknown limiter usage choice: " << limiter_usage_);
}
if (num_multicell_ > 0) {
std::cout << num_multicell_ << " multicell blocks with max size "
<< max_size_multicell_ << " cells in upto "
<< max_iter_multicell_ << " iterations." << std::endl;
std::cout << "Average solves per cell (for all cells) was "
<< double(num_singlesolves_)/double(grid_.number_of_cells) << std::endl;
}
}
void TofDiscGalReorder::solveSingleCell(const int cell)
{
// Residual:
// For each cell K, basis function b_j (spanning V_h),
// writing the solution u_h|K = \sum_i c_i b_i
// Res = - \int_K \sum_i c_i b_i v(x) \cdot \grad b_j dx
// + \int_{\partial K} F(u_h, u_h^{ext}, v(x) \cdot n) b_j ds
// - \int_K \phi b_j
// This is linear in c_i, so we do not need any nonlinear iterations.
// We assemble the jacobian and the right-hand side. The residual is
// equal to Res = Jac*c - rhs, and we compute rhs directly.
//
// For tracers, the equation is the same, except for the last
// term being zero (the one with \phi).
//
// The rhs_ vector contains a (Fortran ordering) matrix of all
// right-hand-sides, first for tof and then (optionally) for
// all tracers.
const int num_basis = basis_func_->numBasisFunc();
++num_singlesolves_;
std::fill(rhs_.begin(), rhs_.end(), 0.0);
std::fill(jac_.begin(), jac_.end(), 0.0);
// Add cell contributions to res_ and jac_.
cellContribs(cell);
// Add face contributions to res_ and jac_.
faceContribs(cell);
// Solve linear equation.
solveLinearSystem(cell);
// The solution ends up in rhs_, so we must copy it.
std::copy(rhs_.begin(), rhs_.begin() + num_basis, tof_coeff_ + num_basis*cell);
if (num_tracers_ && tracerhead_by_cell_[cell] == NoTracerHead) {
std::copy(rhs_.begin() + num_basis, rhs_.end(), tracer_coeff_ + num_tracers_*num_basis*cell);
}
// Apply limiter.
if (basis_func_->degree() > 0 && use_limiter_ && limiter_usage_ == DuringComputations) {
applyLimiter(cell, tof_coeff_);
if (num_tracers_ && tracerhead_by_cell_[cell] == NoTracerHead) {
for (int tr = 0; tr < num_tracers_; ++tr) {
applyTracerLimiter(cell, tracer_coeff_ + cell*num_tracers_*num_basis + tr*num_basis);
}
}
}
// Ensure that tracer averages sum to 1.
if (num_tracers_ && tracers_ensure_unity_ && tracerhead_by_cell_[cell] == NoTracerHead) {
std::vector<double> tr_aver(num_tracers_);
double tr_sum = 0.0;
for (int tr = 0; tr < num_tracers_; ++tr) {
const double* local_basis = tracer_coeff_ + cell*num_tracers_*num_basis + tr*num_basis;
tr_aver[tr] = basis_func_->functionAverage(local_basis);
tr_sum += tr_aver[tr];
}
if (tr_sum == 0.0) {
std::cout << "Tracer sum is zero in cell " << cell << std::endl;
} else {
for (int tr = 0; tr < num_tracers_; ++tr) {
const double increment = tr_aver[tr]/tr_sum - tr_aver[tr];
double* local_basis = tracer_coeff_ + cell*num_tracers_*num_basis + tr*num_basis;
basis_func_->addConstant(increment, local_basis);
}
}
}
}
void TofDiscGalReorder::cellContribs(const int cell)
{
const int num_basis = basis_func_->numBasisFunc();
const int dim = grid_.dimensions;
// Compute cell residual contribution.
{
const int deg_needed = basis_func_->degree();
CellQuadrature quad(grid_, cell, deg_needed);
for (int quad_pt = 0; quad_pt < quad.numQuadPts(); ++quad_pt) {
// Integral of: b_i \phi
quad.quadPtCoord(quad_pt, &coord_[0]);
basis_func_->eval(cell, &coord_[0], &basis_[0]);
const double w = quad.quadPtWeight(quad_pt);
for (int j = 0; j < num_basis; ++j) {
// Only adding to the tof rhs.
rhs_[j] += w * basis_[j] * porevolume_[cell] / grid_.cell_volumes[cell];
}
}
}
// Compute cell jacobian contribution. We use Fortran ordering
// for jac_, i.e. rows cycling fastest.
{
// Even with ECVI velocity interpolation, degree of precision 1
// is sufficient for optimal convergence order for DG1 when we
// use linear (total degree 1) basis functions.
// With bi(tri)-linear basis functions, it still seems sufficient
// for convergence order 2, but the solution looks much better and
// has significantly lower error with degree of precision 2.
// For now, we err on the side of caution, and use 2*degree, even
// though this is wasteful for the pure linear basis functions.
// const int deg_needed = 2*basis_func_->degree() - 1;
const int deg_needed = 2*basis_func_->degree();
CellQuadrature quad(grid_, cell, deg_needed);
for (int quad_pt = 0; quad_pt < quad.numQuadPts(); ++quad_pt) {
// b_i (v \cdot \grad b_j)
quad.quadPtCoord(quad_pt, &coord_[0]);
basis_func_->eval(cell, &coord_[0], &basis_[0]);
basis_func_->evalGrad(cell, &coord_[0], &grad_basis_[0]);
velocity_interpolation_->interpolate(cell, &coord_[0], &velocity_[0]);
const double w = quad.quadPtWeight(quad_pt);
for (int j = 0; j < num_basis; ++j) {
for (int i = 0; i < num_basis; ++i) {
for (int dd = 0; dd < dim; ++dd) {
jac_[j*num_basis + i] -= w * basis_[j] * grad_basis_[dim*i + dd] * velocity_[dd];
}
}
}
}
}
// Compute downstream jacobian contribution from sink terms.
// Contribution from inflow sources would be
// similar to the contribution from upstream faces, but
// it is zero since we let all external inflow be associated
// with a zero tof.
if (source_[cell] < 0.0) {
// A sink.
const double flux = -source_[cell]; // Sign convention for flux: outflux > 0.
const double flux_density = flux / grid_.cell_volumes[cell];
// Do quadrature over the cell to compute
// \int_{K} b_i flux b_j dx
CellQuadrature quad(grid_, cell, 2*basis_func_->degree());
for (int quad_pt = 0; quad_pt < quad.numQuadPts(); ++quad_pt) {
quad.quadPtCoord(quad_pt, &coord_[0]);
basis_func_->eval(cell, &coord_[0], &basis_[0]);
const double w = quad.quadPtWeight(quad_pt);
for (int j = 0; j < num_basis; ++j) {
for (int i = 0; i < num_basis; ++i) {
jac_[j*num_basis + i] += w * basis_[i] * flux_density * basis_[j];
}
}
}
}
}
void TofDiscGalReorder::faceContribs(const int cell)
{
const int num_basis = basis_func_->numBasisFunc();
// Compute upstream residual contribution from faces.
for (int hface = grid_.cell_facepos[cell]; hface < grid_.cell_facepos[cell+1]; ++hface) {
const int face = grid_.cell_faces[hface];
double flux = 0.0;
int upstream_cell = -1;
if (cell == grid_.face_cells[2*face]) {
flux = darcyflux_[face];
upstream_cell = grid_.face_cells[2*face+1];
} else {
flux = -darcyflux_[face];
upstream_cell = grid_.face_cells[2*face];
}
if (flux >= 0.0) {
// This is an outflow boundary.
continue;
}
if (upstream_cell < 0) {
// This is an outer boundary. Assumed tof = 0 on inflow, so no contribution.
// For tracers, a cell with inflow should be marked as a tracer head cell,
// and not be modified.
continue;
}
// Do quadrature over the face to compute
// \int_{\partial K} u_h^{ext} (v(x) \cdot n) b_j ds
// (where u_h^{ext} is the upstream unknown (tof)).
// Quadrature degree set to 2*D, since u_h^{ext} varies
// with degree D, and b_j too. We assume that the normal
// velocity is constant (this assumption may have to go
// for higher order than DG1).
const double normal_velocity = flux / grid_.face_areas[face];
const int deg_needed = 2*basis_func_->degree();
FaceQuadrature quad(grid_, face, deg_needed);
for (int quad_pt = 0; quad_pt < quad.numQuadPts(); ++quad_pt) {
quad.quadPtCoord(quad_pt, &coord_[0]);
basis_func_->eval(cell, &coord_[0], &basis_[0]);
basis_func_->eval(upstream_cell, &coord_[0], &basis_nb_[0]);
const double w = quad.quadPtWeight(quad_pt);
// Modify tof rhs
const double tof_upstream = std::inner_product(basis_nb_.begin(), basis_nb_.end(),
tof_coeff_ + num_basis*upstream_cell, 0.0);
for (int j = 0; j < num_basis; ++j) {
rhs_[j] -= w * tof_upstream * normal_velocity * basis_[j];
}
// Modify tracer rhs
if (num_tracers_ && tracerhead_by_cell_[cell] == NoTracerHead) {
for (int tr = 0; tr < num_tracers_; ++tr) {
const double* up_tr_co = tracer_coeff_ + num_tracers_*num_basis*upstream_cell + num_basis*tr;
const double tracer_up = std::inner_product(basis_nb_.begin(), basis_nb_.end(), up_tr_co, 0.0);
for (int j = 0; j < num_basis; ++j) {
rhs_[num_basis*(tr + 1) + j] -= w * tracer_up * normal_velocity * basis_[j];
}
}
}
}
}
// Compute downstream jacobian contribution from faces.
for (int hface = grid_.cell_facepos[cell]; hface < grid_.cell_facepos[cell+1]; ++hface) {
const int face = grid_.cell_faces[hface];
double flux = 0.0;
if (cell == grid_.face_cells[2*face]) {
flux = darcyflux_[face];
} else {
flux = -darcyflux_[face];
}
if (flux <= 0.0) {
// This is an inflow boundary.
continue;
}
// Do quadrature over the face to compute
// \int_{\partial K} b_i (v(x) \cdot n) b_j ds
const double normal_velocity = flux / grid_.face_areas[face];
FaceQuadrature quad(grid_, face, 2*basis_func_->degree());
for (int quad_pt = 0; quad_pt < quad.numQuadPts(); ++quad_pt) {
// u^ext flux B (B = {b_j})
quad.quadPtCoord(quad_pt, &coord_[0]);
basis_func_->eval(cell, &coord_[0], &basis_[0]);
const double w = quad.quadPtWeight(quad_pt);
for (int j = 0; j < num_basis; ++j) {
for (int i = 0; i < num_basis; ++i) {
jac_[j*num_basis + i] += w * basis_[i] * normal_velocity * basis_[j];
}
}
}
}
}
// This function assumes that jac_ and rhs_ contain the
// linear system to be solved. They are stored in orig_jac_
// and orig_rhs_, then the system is solved via LAPACK,
// overwriting the input data (jac_ and rhs_).
void TofDiscGalReorder::solveLinearSystem(const int cell)
{
MAT_SIZE_T n = basis_func_->numBasisFunc();
int num_tracer_to_compute = num_tracers_;
if (num_tracers_) {
if (tracerhead_by_cell_[cell] != NoTracerHead) {
num_tracer_to_compute = 0;
}
}
MAT_SIZE_T nrhs = 1 + num_tracer_to_compute;
MAT_SIZE_T lda = n;
std::vector<MAT_SIZE_T> piv(n);
MAT_SIZE_T ldb = n;
MAT_SIZE_T info = 0;
orig_jac_ = jac_;
orig_rhs_ = rhs_;
dgesv_(&n, &nrhs, &jac_[0], &lda, &piv[0], &rhs_[0], &ldb, &info);
if (info != 0) {
// Print the local matrix and rhs.
std::cerr << "Failed solving single-cell system Ax = b in cell " << cell
<< " with A = \n";
for (int row = 0; row < n; ++row) {
for (int col = 0; col < n; ++col) {
std::cerr << " " << orig_jac_[row + n*col];
}
std::cerr << '\n';
}
std::cerr << "and b = \n";
for (int row = 0; row < n; ++row) {
std::cerr << " " << orig_rhs_[row] << '\n';
}
OPM_THROW(std::runtime_error, "Lapack error: " << info << " encountered in cell " << cell);
}
}
void TofDiscGalReorder::solveMultiCell(const int num_cells, const int* cells)
{
++num_multicell_;
max_size_multicell_ = std::max(max_size_multicell_, num_cells);
// std::cout << "Multiblock solve with " << num_cells << " cells." << std::endl;
// Using a Gauss-Seidel approach.
const int nb = basis_func_->numBasisFunc();
double max_delta = 1e100;
int num_iter = 0;
while (max_delta > gauss_seidel_tol_) {
max_delta = 0.0;
++num_iter;
for (int ci = 0; ci < num_cells; ++ci) {
const int cell = cells[ci];
const double tof_before = basis_func_->functionAverage(&tof_coeff_[nb*cell]);
solveSingleCell(cell);
const double tof_after = basis_func_->functionAverage(&tof_coeff_[nb*cell]);
max_delta = std::max(max_delta, std::fabs(tof_after - tof_before));
}
// std::cout << "Max delta = " << max_delta << std::endl;
}
max_iter_multicell_ = std::max(max_iter_multicell_, num_iter);
}
void TofDiscGalReorder::applyLimiter(const int cell, double* tof)
{
switch (limiter_method_) {
case MinUpwindFace:
applyMinUpwindLimiter(cell, true, tof);
break;
case MinUpwindAverage:
applyMinUpwindLimiter(cell, false, tof);
break;
default:
OPM_THROW(std::runtime_error, "Limiter type not implemented: " << limiter_method_);
}
}
void TofDiscGalReorder::applyMinUpwindLimiter(const int cell, const bool face_min, double* tof)
{
if (basis_func_->degree() != 1) {
OPM_THROW(std::runtime_error, "This limiter only makes sense for our DG1 implementation.");
}
// Limiter principles:
// 1. Let M be either:
// - the minimum TOF value of all upstream faces,
// evaluated in the upstream cells
// (chosen if face_min is true).
// or:
// - the minimum average TOF value of all upstream cells
// (chosen if face_min is false).
// Then the value at all points in this cell shall be at
// least M. Upstream faces whose flux does not exceed the
// relative flux threshold are not considered for this
// minimum.
// 2. The TOF shall not be below zero in any point.
// Find minimum tof on upstream faces/cells and for this cell.
const int num_basis = basis_func_->numBasisFunc();
double min_upstream_tof = 1e100;
double min_here_tof = 1e100;
int num_upstream_faces = 0;
const double total_flux = totalFlux(cell);
for (int hface = grid_.cell_facepos[cell]; hface < grid_.cell_facepos[cell+1]; ++hface) {
const int face = grid_.cell_faces[hface];
double flux = 0.0;
int upstream_cell = -1;
if (cell == grid_.face_cells[2*face]) {
flux = darcyflux_[face];
upstream_cell = grid_.face_cells[2*face+1];
} else {
flux = -darcyflux_[face];
upstream_cell = grid_.face_cells[2*face];
}
const bool upstream = (flux < -total_flux*limiter_relative_flux_threshold_);
const bool interior = (upstream_cell >= 0);
// Find minimum tof in this cell and upstream.
// The meaning of minimum upstream tof depends on method.
min_here_tof = std::min(min_here_tof, minCornerVal(cell, face));
if (upstream) {
++num_upstream_faces;
double upstream_tof = 0.0;
if (interior) {
if (face_min) {
upstream_tof = minCornerVal(upstream_cell, face);
} else {
upstream_tof = basis_func_->functionAverage(tof_coeff_ + num_basis*upstream_cell);
}
}
min_upstream_tof = std::min(min_upstream_tof, upstream_tof);
}
}
// Compute slope multiplier (limiter).
if (num_upstream_faces == 0) {
min_upstream_tof = 0.0;
min_here_tof = 0.0;
}
if (min_upstream_tof < 0.0) {
min_upstream_tof = 0.0;
}
const double tof_c = basis_func_->functionAverage(tof_coeff_ + num_basis*cell);
double limiter = (tof_c - min_upstream_tof)/(tof_c - min_here_tof);
if (tof_c < min_upstream_tof) {
// Handle by setting a flat solution.
// std::cout << "Trouble in cell " << cell << std::endl;
limiter = 0.0;
basis_func_->addConstant(min_upstream_tof - tof_c, tof + num_basis*cell);
}
assert(limiter >= 0.0);
// Actually do the limiting (if applicable).
if (limiter < 1.0) {
// std::cout << "Applying limiter in cell " << cell << ", limiter = " << limiter << std::endl;
basis_func_->multiplyGradient(limiter, tof + num_basis*cell);
} else {
// std::cout << "Not applying limiter in cell " << cell << "!" << std::endl;
}
}
void TofDiscGalReorder::applyLimiterAsPostProcess()
{
// Apply the limiter sequentially to all cells.
// This means that a cell's limiting behaviour may be affected by
// any limiting applied to its upstream cells.
const std::vector<int>& seq = ReorderSolverInterface::sequence();
const int nc = seq.size();
assert(nc == grid_.number_of_cells);
for (int i = 0; i < nc; ++i) {
const int cell = seq[i];
applyLimiter(cell, tof_coeff_);
}
}
void TofDiscGalReorder::applyLimiterAsSimultaneousPostProcess()
{
// Apply the limiter simultaneously to all cells.
// This means that each cell is limited independently from all other cells,
// we write the resulting dofs to a new array instead of writing to tof_coeff_.
// Afterwards we copy the results back to tof_coeff_.
const int num_basis = basis_func_->numBasisFunc();
std::vector<double> tof_coeffs_new(tof_coeff_, tof_coeff_ + num_basis*grid_.number_of_cells);
for (int c = 0; c < grid_.number_of_cells; ++c) {
applyLimiter(c, &tof_coeffs_new[0]);
}
std::copy(tof_coeffs_new.begin(), tof_coeffs_new.end(), tof_coeff_);
}
double TofDiscGalReorder::totalFlux(const int cell) const
{
// Find total upstream/downstream fluxes.
double upstream_flux = 0.0;
double downstream_flux = 0.0;
for (int hface = grid_.cell_facepos[cell]; hface < grid_.cell_facepos[cell+1]; ++hface) {
const int face = grid_.cell_faces[hface];
double flux = 0.0;
if (cell == grid_.face_cells[2*face]) {
flux = darcyflux_[face];
} else {
flux = -darcyflux_[face];
}
if (flux < 0.0) {
upstream_flux += flux;
} else {
downstream_flux += flux;
}
}
// In the presence of sources, significant fluxes may be missing from the computed fluxes,
// setting the total flux to the (positive) maximum avoids this: since source is either
// inflow or outflow, not both, either upstream_flux or downstream_flux must be correct.
return std::max(-upstream_flux, downstream_flux);
}
double TofDiscGalReorder::minCornerVal(const int cell, const int face) const
{
// Evaluate the solution in all corners.
const int dim = grid_.dimensions;
const int num_basis = basis_func_->numBasisFunc();
double min_cornerval = 1e100;
for (int fnode = grid_.face_nodepos[face]; fnode < grid_.face_nodepos[face+1]; ++fnode) {
const double* nc = grid_.node_coordinates + dim*grid_.face_nodes[fnode];
basis_func_->eval(cell, nc, &basis_[0]);
const double tof_corner = std::inner_product(basis_.begin(), basis_.end(),
tof_coeff_ + num_basis*cell, 0.0);
min_cornerval = std::min(min_cornerval, tof_corner);
}
return min_cornerval;
}
void TofDiscGalReorder::applyTracerLimiter(const int cell, double* local_coeff)
{
// Evaluate the solution in all corners of all faces. Extract max and min.
const int dim = grid_.dimensions;
const int num_basis = basis_func_->numBasisFunc();
double min_cornerval = 1e100;
double max_cornerval = -1e100;
for (int hface = grid_.cell_facepos[cell]; hface < grid_.cell_facepos[cell+1]; ++hface) {
const int face = grid_.cell_faces[hface];
for (int fnode = grid_.face_nodepos[face]; fnode < grid_.face_nodepos[face+1]; ++fnode) {
const double* nc = grid_.node_coordinates + dim*grid_.face_nodes[fnode];
basis_func_->eval(cell, nc, &basis_[0]);
const double tracer_corner = std::inner_product(basis_.begin(), basis_.end(),
local_coeff, 0.0);
min_cornerval = std::min(min_cornerval, tracer_corner);
max_cornerval = std::max(min_cornerval, tracer_corner);
}
}
const double average = basis_func_->functionAverage(local_coeff);
if (average < 0.0 || average > 1.0) {
// Adjust average. Flatten gradient.
std::fill(local_coeff, local_coeff + num_basis, 0.0);
if (average > 1.0) {
basis_func_->addConstant(1.0, local_coeff);
}
} else {
// Possibly adjust gradient.
double factor = 1.0;
if (min_cornerval < 0.0) {
factor = average/(average - min_cornerval);
}
if (max_cornerval > 1.0) {
factor = std::min(factor, (1.0 - average)/(max_cornerval - average));
}
if (factor != 1.0) {
basis_func_->multiplyGradient(factor, local_coeff);
}
}
}
} // namespace Opm

View File

@ -1,190 +0,0 @@
/*
Copyright 2012 SINTEF ICT, Applied Mathematics.
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 3 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 <http://www.gnu.org/licenses/>.
*/
#ifndef OPM_TOFDISCGALREORDER_HEADER_INCLUDED
#define OPM_TOFDISCGALREORDER_HEADER_INCLUDED
#include <opm/core/transport/reorder/ReorderSolverInterface.hpp>
#include <memory>
#include <vector>
#include <map>
#include <ostream>
struct UnstructuredGrid;
namespace Opm
{
class IncompPropertiesInterface;
class ParameterGroup;
class VelocityInterpolationInterface;
class DGBasisInterface;
template <typename T> class SparseTable;
/// Implements a discontinuous Galerkin solver for
/// (single-phase) time-of-flight using reordering.
/// The equation solved is:
/// \f[v \cdot \nabla\tau = \phi\f]
/// in which \f$ v \f$ is the fluid velocity, \f$ \tau \f$ is time-of-flight and
/// \f$ \phi \f$ is the porosity. This is a boundary value problem, and
/// \f$ \tau \f$ is specified to be zero on all inflow boundaries.
/// The user may specify the polynomial degree of the basis function space
/// used, but only degrees 0 and 1 are supported so far.
class TofDiscGalReorder : public ReorderSolverInterface
{
public:
/// Construct solver.
/// \param[in] grid A 2d or 3d grid.
/// \param[in] param Parameters for the solver.
/// The following parameters are accepted (defaults):\n
/// - \c dg_degree (0) -- Polynomial degree of basis functions.
/// - \c use_tensorial_basis (false) -- Use tensor-product basis, interpreting dg_degree as
/// bi/tri-degree not total degree.
/// - \c use_cvi (false) -- Use ECVI velocity interpolation.
/// - \c use_limiter (false) -- Use a slope limiter. If true, the next three parameters are used.
/// - \c limiter_relative_flux_threshold (1e-3) -- Ignore upstream fluxes below this threshold,
/// relative to total cell flux.
/// - \c limiter_method ("MinUpwindFace") -- Limiter method used. Accepted methods are:
/// - MinUpwindFace -- Limit cell tof to >= inflow face tofs.
/// - MinUpwindAverage -- Limit cell tof to >= inflow cell average tofs.
/// - \c limiter_usage ("DuringComputations") -- Usage pattern for limiter. Accepted choices are:
/// - DuringComputations -- Apply limiter to cells as they are computed,
/// so downstream cells' solutions may be affected
/// by limiting in upstream cells.
/// - AsPostProcess -- Apply in dependency order, but only after
/// computing (unlimited) solution.
/// - AsSimultaneousPostProcess -- Apply to each cell independently, using un-
/// limited solution in neighbouring cells.
TofDiscGalReorder(const UnstructuredGrid& grid,
const ParameterGroup& param);
/// Solve for time-of-flight.
/// \param[in] darcyflux Array of signed face fluxes.
/// \param[in] porevolume Array of pore volumes.
/// \param[in] source Source term. Sign convention is:
/// (+) inflow flux,
/// (-) outflow flux.
/// \param[out] tof_coeff Array of time-of-flight solution coefficients.
/// The values are ordered by cell, meaning that
/// the K coefficients corresponding to the first
/// cell come before the K coefficients corresponding
/// to the second cell etc.
/// K depends on degree and grid dimension.
void solveTof(const double* darcyflux,
const double* porevolume,
const double* source,
std::vector<double>& tof_coeff);
/// Solve for time-of-flight and a number of tracers.
/// \param[in] darcyflux Array of signed face fluxes.
/// \param[in] porevolume Array of pore volumes.
/// \param[in] source Source term. Sign convention is:
/// (+) inflow flux,
/// (-) outflow flux.
/// \param[in] tracerheads Table containing one row per tracer, and each
/// row contains the source cells for that tracer.
/// \param[out] tof_coeff Array of time-of-flight solution coefficients.
/// The values are ordered by cell, meaning that
/// the K coefficients corresponding to the first
/// cell comes before the K coefficients corresponding
/// to the second cell etc.
/// K depends on degree and grid dimension.
/// \param[out] tracer_coeff Array of tracer solution coefficients. N*K per cell,
/// where N is equal to tracerheads.size(). All K coefs
/// for a tracer are consecutive, and all tracers' coefs
/// for a cell come before those for the next cell.
void solveTofTracer(const double* darcyflux,
const double* porevolume,
const double* source,
const SparseTable<int>& tracerheads,
std::vector<double>& tof_coeff,
std::vector<double>& tracer_coeff);
private:
virtual void solveSingleCell(const int cell);
virtual void solveMultiCell(const int num_cells, const int* cells);
void cellContribs(const int cell);
void faceContribs(const int cell);
void solveLinearSystem(const int cell);
private:
// Disable copying and assignment.
TofDiscGalReorder(const TofDiscGalReorder&);
TofDiscGalReorder& operator=(const TofDiscGalReorder&);
// Data members
const UnstructuredGrid& grid_;
std::shared_ptr<VelocityInterpolationInterface> velocity_interpolation_;
bool use_cvi_;
bool use_limiter_;
double limiter_relative_flux_threshold_;
enum LimiterMethod { MinUpwindFace, MinUpwindAverage };
LimiterMethod limiter_method_;
enum LimiterUsage { DuringComputations, AsPostProcess, AsSimultaneousPostProcess };
LimiterUsage limiter_usage_;
const double* darcyflux_; // one flux per grid face
const double* porevolume_; // one volume per cell
const double* source_; // one volumetric source term per cell
std::shared_ptr<DGBasisInterface> basis_func_;
double* tof_coeff_;
// For tracers.
double* tracer_coeff_;
int num_tracers_;
enum { NoTracerHead = -1 };
std::vector<int> tracerhead_by_cell_;
bool tracers_ensure_unity_;
// Used by solveSingleCell().
std::vector<double> rhs_; // single-cell right-hand-sides
std::vector<double> jac_; // single-cell jacobian
std::vector<double> orig_rhs_; // single-cell right-hand-sides (copy)
std::vector<double> orig_jac_; // single-cell jacobian (copy)
std::vector<double> coord_;
mutable std::vector<double> basis_;
mutable std::vector<double> basis_nb_;
std::vector<double> grad_basis_;
std::vector<double> velocity_;
int num_singlesolves_;
// Used by solveMultiCell():
double gauss_seidel_tol_;
int num_multicell_;
int max_size_multicell_;
int max_iter_multicell_;
// Private methods
// Apply some limiter, writing to array tof
// (will read data from tof_coeff_, it is ok to call
// with tof_coeff as tof argument.
void applyLimiter(const int cell, double* tof);
void applyMinUpwindLimiter(const int cell, const bool face_min, double* tof);
void applyLimiterAsPostProcess();
void applyLimiterAsSimultaneousPostProcess();
double totalFlux(const int cell) const;
double minCornerVal(const int cell, const int face) const;
// Apply a simple (restrict to [0,1]) limiter.
// Intended for tracers.
void applyTracerLimiter(const int cell, double* local_coeff);
};
} // namespace Opm
#endif // OPM_TRANSPORTMODELTRACERTOFDISCGAL_HEADER_INCLUDED

View File

@ -1,453 +0,0 @@
/*
Copyright 2012 SINTEF ICT, Applied Mathematics.
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 3 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 <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <opm/core/flowdiagnostics/TofReorder.hpp>
#include <opm/grid/UnstructuredGrid.h>
#include <opm/common/ErrorMacros.hpp>
#include <opm/grid/utility/SparseTable.hpp>
#include <algorithm>
#include <numeric>
#include <cmath>
#include <iostream>
namespace Opm
{
/// Construct solver.
/// \param[in] grid A 2d or 3d grid.
/// \param[in] use_multidim_upwind If true, use multidimensional tof upwinding.
TofReorder::TofReorder(const UnstructuredGrid& grid,
const bool use_multidim_upwind)
: grid_(grid),
darcyflux_(0),
porevolume_(0),
source_(0),
tof_(0),
gauss_seidel_tol_(1e-3),
use_multidim_upwind_(use_multidim_upwind)
{
}
/// Solve for time-of-flight.
/// \param[in] darcyflux Array of signed face fluxes.
/// \param[in] porevolume Array of pore volumes.
/// \param[in] source Source term. Sign convention is:
/// (+) inflow flux,
/// (-) outflow flux.
/// \param[out] tof Array of time-of-flight values.
void TofReorder::solveTof(const double* darcyflux,
const double* porevolume,
const double* source,
std::vector<double>& tof)
{
darcyflux_ = darcyflux;
porevolume_ = porevolume;
source_ = source;
#ifndef NDEBUG
// Sanity check for sources.
const double cum_src = std::accumulate(source, source + grid_.number_of_cells, 0.0);
if (std::fabs(cum_src) > *std::max_element(source, source + grid_.number_of_cells)*1e-2) {
// OPM_THROW(std::runtime_error, "Sources do not sum to zero: " << cum_src);
OPM_MESSAGE("Warning: sources do not sum to zero: " << cum_src);
}
#endif
tof.resize(grid_.number_of_cells);
std::fill(tof.begin(), tof.end(), 0.0);
tof_ = &tof[0];
if (use_multidim_upwind_) {
face_tof_.resize(grid_.number_of_faces);
std::fill(face_tof_.begin(), face_tof_.end(), 0.0);
face_part_tof_.resize(grid_.face_nodepos[grid_.number_of_faces]);
std::fill(face_part_tof_.begin(), face_part_tof_.end(), 0.0);
}
compute_tracer_ = false;
executeSolve();
}
/// Solve for time-of-flight and a number of tracers.
/// \param[in] darcyflux Array of signed face fluxes.
/// \param[in] porevolume Array of pore volumes.
/// \param[in] source Source term. Sign convention is:
/// (+) inflow flux,
/// (-) outflow flux.
/// \param[in] tracerheads Table containing one row per tracer, and each
/// row contains the source cells for that tracer.
/// \param[out] tof Array of time-of-flight values (1 per cell).
/// \param[out] tracer Array of tracer values. N per cell, where N is
/// equalt to tracerheads.size().
void TofReorder::solveTofTracer(const double* darcyflux,
const double* porevolume,
const double* source,
const SparseTable<int>& tracerheads,
std::vector<double>& tof,
std::vector<double>& tracer)
{
darcyflux_ = darcyflux;
porevolume_ = porevolume;
source_ = source;
const int num_cells = grid_.number_of_cells;
#ifndef NDEBUG
// Sanity check for sources.
const double cum_src = std::accumulate(source, source + num_cells, 0.0);
if (std::fabs(cum_src) > *std::max_element(source, source + num_cells)*1e-2) {
OPM_THROW(std::runtime_error, "Sources do not sum to zero: " << cum_src);
}
#endif
tof.resize(num_cells);
std::fill(tof.begin(), tof.end(), 0.0);
tof_ = &tof[0];
if (use_multidim_upwind_) {
face_tof_.resize(grid_.number_of_faces);
std::fill(face_tof_.begin(), face_tof_.end(), 0.0);
face_part_tof_.resize(grid_.face_nodepos[grid_.number_of_faces]);
std::fill(face_part_tof_.begin(), face_part_tof_.end(), 0.0);
}
// Execute solve for tof
compute_tracer_ = false;
executeSolve();
// Find the tracer heads (injectors).
const int num_tracers = tracerheads.size();
tracer.resize(num_cells*num_tracers);
std::fill(tracer.begin(), tracer.end(), 0.0);
if (num_tracers > 0) {
tracerhead_by_cell_.clear();
tracerhead_by_cell_.resize(num_cells, NoTracerHead);
}
for (int tr = 0; tr < num_tracers; ++tr) {
const unsigned int tracerheadsSize = tracerheads[tr].size();
for (unsigned int i = 0; i < tracerheadsSize; ++i) {
const int cell = tracerheads[tr][i];
tracer[num_cells * tr + cell] = 1.0;
tracerhead_by_cell_[cell] = tr;
}
}
// Execute solve for tracers.
std::vector<double> fake_pv(num_cells, 0.0);
porevolume_ = fake_pv.data();
for (int tr = 0; tr < num_tracers; ++tr) {
tof_ = tracer.data() + tr * num_cells;
compute_tracer_ = true;
executeSolve();
}
// Write output tracer data (transposing the computed data).
std::vector<double> computed = tracer;
for (int cell = 0; cell < num_cells; ++cell) {
for (int tr = 0; tr < num_tracers; ++tr) {
tracer[num_tracers * cell + tr] = computed[num_cells * tr + cell];
}
}
}
void TofReorder::executeSolve()
{
num_multicell_ = 0;
max_size_multicell_ = 0;
max_iter_multicell_ = 0;
reorderAndTransport(grid_, darcyflux_);
if (num_multicell_ > 0) {
std::cout << num_multicell_ << " multicell blocks with max size "
<< max_size_multicell_ << " cells in upto "
<< max_iter_multicell_ << " iterations." << std::endl;
}
}
void TofReorder::solveSingleCell(const int cell)
{
if (use_multidim_upwind_) {
solveSingleCellMultidimUpwind(cell);
return;
}
// Compute flux terms.
// Sources have zero tof, and therefore do not contribute
// to upwind_term. Sinks on the other hand, must be added
// to the downwind_flux (note sign change resulting from
// different sign conventions: pos. source is injection,
// pos. flux is outflow).
if (compute_tracer_ && tracerhead_by_cell_[cell] != NoTracerHead) {
// This is a tracer head cell, already has solution.
return;
}
double upwind_term = 0.0;
double downwind_flux = std::max(-source_[cell], 0.0);
for (int i = grid_.cell_facepos[cell]; i < grid_.cell_facepos[cell+1]; ++i) {
int f = grid_.cell_faces[i];
double flux;
int other;
// Compute cell flux
if (cell == grid_.face_cells[2*f]) {
flux = darcyflux_[f];
other = grid_.face_cells[2*f+1];
} else {
flux =-darcyflux_[f];
other = grid_.face_cells[2*f];
}
// Add flux to upwind_term or downwind_flux
if (flux < 0.0) {
// Using tof == 0 on inflow, so we only add a
// nonzero contribution if we are on an internal
// face.
if (other != -1) {
upwind_term += flux*tof_[other];
}
} else {
downwind_flux += flux;
}
}
// Compute tof.
tof_[cell] = (porevolume_[cell] - upwind_term)/downwind_flux;
}
void TofReorder::solveSingleCellMultidimUpwind(const int cell)
{
// Compute flux terms.
// Sources have zero tof, and therefore do not contribute
// to upwind_term. Sinks on the other hand, must be added
// to the downwind terms (note sign change resulting from
// different sign conventions: pos. source is injection,
// pos. flux is outflow).
double upwind_term = 0.0;
double downwind_term_cell_factor = std::max(-source_[cell], 0.0);
double downwind_term_face = 0.0;
for (int i = grid_.cell_facepos[cell]; i < grid_.cell_facepos[cell+1]; ++i) {
int f = grid_.cell_faces[i];
double flux;
// Compute cell flux
if (cell == grid_.face_cells[2*f]) {
flux = darcyflux_[f];
} else {
flux =-darcyflux_[f];
}
// Add flux to upwind_term or downwind_term_[face|cell_factor].
if (flux < 0.0) {
upwind_term += flux*face_tof_[f];
} else if (flux > 0.0) {
double fterm, cterm_factor;
multidimUpwindTerms(f, cell, fterm, cterm_factor);
downwind_term_face += fterm*flux;
downwind_term_cell_factor += cterm_factor*flux;
}
}
// Compute tof for cell.
if (compute_tracer_ && tracerhead_by_cell_[cell] != NoTracerHead) {
// Do nothing to the value in this cell, since we are at a tracer head.
} else {
tof_[cell] = (porevolume_[cell] - upwind_term - downwind_term_face)/downwind_term_cell_factor;
}
// Compute tof for downwind faces.
for (int i = grid_.cell_facepos[cell]; i < grid_.cell_facepos[cell+1]; ++i) {
int f = grid_.cell_faces[i];
const double outflux_f = (grid_.face_cells[2*f] == cell) ? darcyflux_[f] : -darcyflux_[f];
if (outflux_f > 0.0) {
double fterm, cterm_factor;
multidimUpwindTerms(f, cell, fterm, cterm_factor);
face_tof_[f] = fterm + cterm_factor*tof_[cell];
// Combine locally computed (for each adjacent vertex) terms, with uniform weighting.
const int* face_nodes_beg = grid_.face_nodes + grid_.face_nodepos[f];
const int* face_nodes_end = grid_.face_nodes + grid_.face_nodepos[f + 1];
assert((face_nodes_end - face_nodes_beg) == 2 || grid_.dimensions != 2);
for (const int* fn_iter = face_nodes_beg; fn_iter < face_nodes_end; ++fn_iter) {
double loc_face_term = 0.0;
double loc_cell_term_factor = 0.0;
const int node_pos = fn_iter - grid_.face_nodes;
localMultidimUpwindTerms(f, cell, node_pos,
loc_face_term, loc_cell_term_factor);
face_part_tof_[node_pos] = loc_face_term + loc_cell_term_factor * tof_[cell];
}
}
}
}
void TofReorder::solveMultiCell(const int num_cells, const int* cells)
{
++num_multicell_;
max_size_multicell_ = std::max(max_size_multicell_, num_cells);
// std::cout << "Multiblock solve with " << num_cells << " cells." << std::endl;
// Using a Gauss-Seidel approach.
double max_delta = 1e100;
int num_iter = 0;
while (max_delta > gauss_seidel_tol_) {
max_delta = 0.0;
++num_iter;
for (int ci = 0; ci < num_cells; ++ci) {
const int cell = cells[ci];
const double tof_before = tof_[cell];
solveSingleCell(cell);
max_delta = std::max(max_delta, std::fabs(tof_[cell] - tof_before));
}
// std::cout << "Max delta = " << max_delta << std::endl;
}
max_iter_multicell_ = std::max(max_iter_multicell_, num_iter);
}
// Assumes that face_part_tof_[node_pos] is known for all inflow
// faces to 'upwind_cell' sharing vertices with 'face'. The index
// 'node_pos' is the same as the one used for the grid face-node
// connectivity.
// Assumes that darcyflux_[face] is != 0.0.
// This function returns factors to compute the tof for 'face':
// tof(face) = face_term + cell_term_factor*tof(upwind_cell).
// It is not computed here, since these factors are needed to
// compute the tof(upwind_cell) itself.
void TofReorder::multidimUpwindTerms(const int face,
const int upwind_cell,
double& face_term,
double& cell_term_factor) const
{
// Implements multidim upwind inspired by
// "Multidimensional upstream weighting for multiphase transport on general grids"
// by Keilegavlen, Kozdon, Mallison.
// However, that article does not give a 3d extension other than noting that using
// multidimensional upwinding in the XY-plane and not in the Z-direction may be
// a good idea. We have here attempted some generalization, by treating each face-part
// (association of a face and a vertex) as possibly influencing all downwind face-parts
// of the neighbouring cell that share the same vertex.
// The current implementation aims to reproduce 2d results for extruded 3d grids.
// Combine locally computed (for each adjacent vertex) terms, with uniform weighting.
const int* face_nodes_beg = grid_.face_nodes + grid_.face_nodepos[face];
const int* face_nodes_end = grid_.face_nodes + grid_.face_nodepos[face + 1];
const int num_terms = face_nodes_end - face_nodes_beg;
assert(num_terms == 2 || grid_.dimensions != 2);
face_term = 0.0;
cell_term_factor = 0.0;
for (const int* fn_iter = face_nodes_beg; fn_iter < face_nodes_end; ++fn_iter) {
double loc_face_term = 0.0;
double loc_cell_term_factor = 0.0;
localMultidimUpwindTerms(face, upwind_cell, fn_iter - grid_.face_nodes,
loc_face_term, loc_cell_term_factor);
face_term += loc_face_term;
cell_term_factor += loc_cell_term_factor;
}
face_term /= double(num_terms);
cell_term_factor /= double(num_terms);
}
namespace {
double weightFunc(const double w)
{
// SPU
// return 0.0;
// TMU
return w > 0.0 ? std::min(w, 1.0) : 0.0;
// SMU
// return w > 0.0 ? w/(1.0 + w) : 0.0;
}
}
void TofReorder::localMultidimUpwindTerms(const int face,
const int upwind_cell,
const int node_pos,
double& face_term,
double& cell_term_factor) const
{
// Loop over all faces adjacent to the given cell and the
// vertex in position node_pos.
// If that part's influx is positive, we store it, and also its associated
// node position.
std::vector<double> influx;
std::vector<int> node_pos_influx;
influx.reserve(5);
node_pos_influx.reserve(5);
const int node = grid_.face_nodes[node_pos];
for (int hf = grid_.cell_facepos[upwind_cell]; hf < grid_.cell_facepos[upwind_cell + 1]; ++hf) {
const int f = grid_.cell_faces[hf];
if (f != face) {
// Find out if the face 'f' is adjacent to vertex 'node'.
const int* f_nodes_beg = grid_.face_nodes + grid_.face_nodepos[f];
const int* f_nodes_end = grid_.face_nodes + grid_.face_nodepos[f + 1];
const int* pos = std::find(f_nodes_beg, f_nodes_end, node);
const int node_pos2 = pos - grid_.face_nodes;
const bool is_adj = (pos != f_nodes_end);
if (is_adj) {
const int num_parts = f_nodes_end - f_nodes_beg;
const double influx_sign = (grid_.face_cells[2*f] == upwind_cell) ? -1.0 : 1.0;
const double part_influx = influx_sign * darcyflux_[f] / double(num_parts);
if (part_influx > 0.0) {
influx.push_back(part_influx);
node_pos_influx.push_back(node_pos2);
}
}
}
}
// Now we may compute the weighting of the upwind terms.
const int num_parts = grid_.face_nodepos[face + 1] - grid_.face_nodepos[face];
const double outflux_sign = (grid_.face_cells[2*face] == upwind_cell) ? 1.0 : -1.0;
const double part_outflux = outflux_sign * darcyflux_[face] / double(num_parts);
const double sum_influx = std::accumulate(influx.begin(), influx.end(), 0.0);
const double w_factor = weightFunc(sum_influx / part_outflux);
const int num_influx = influx.size();
std::vector<double> w(num_influx);
face_term = 0.0;
for (int ii = 0; ii < num_influx; ++ii) {
w[ii] = (influx[ii] / sum_influx) * w_factor;
face_term += w[ii] * face_part_tof_[node_pos_influx[ii]];
}
const double sum_w = std::accumulate(w.begin(), w.end(), 0.0);
cell_term_factor = 1.0 - sum_w;
const double tol = 1e-5;
if (cell_term_factor < -tol && cell_term_factor > 1.0 + tol) {
OPM_THROW(std::logic_error, "cell_term_factor outside [0,1]: " << cell_term_factor);
}
cell_term_factor = std::min(std::max(cell_term_factor, 0.0), 1.0);
assert(cell_term_factor >= 0.0);
assert(cell_term_factor <= 1.0);
}
} // namespace Opm

View File

@ -1,119 +0,0 @@
/*
Copyright 2012 SINTEF ICT, Applied Mathematics.
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 3 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 <http://www.gnu.org/licenses/>.
*/
#ifndef OPM_TOFREORDER_HEADER_INCLUDED
#define OPM_TOFREORDER_HEADER_INCLUDED
#include <opm/core/transport/reorder/ReorderSolverInterface.hpp>
#include <vector>
#include <map>
#include <ostream>
struct UnstructuredGrid;
namespace Opm
{
class IncompPropertiesInterface;
template <typename T> class SparseTable;
/// Implements a first-order finite volume solver for
/// (single-phase) time-of-flight using reordering.
/// The equation solved is:
/// \f[v \cdot \nabla\tau = \phi\f]
/// in which \f$ v \f$ is the fluid velocity, \f$ \tau \f$ is time-of-flight and
/// \f$ \phi \f$ is the porosity. This is a boundary value problem, and
/// \f$ \tau \f$ is specified to be zero on all inflow boundaries.
class TofReorder : public ReorderSolverInterface
{
public:
/// Construct solver.
/// \param[in] grid A 2d or 3d grid.
/// \param[in] use_multidim_upwind If true, use multidimensional tof upwinding.
TofReorder(const UnstructuredGrid& grid,
const bool use_multidim_upwind = false);
/// Solve for time-of-flight.
/// \param[in] darcyflux Array of signed face fluxes.
/// \param[in] porevolume Array of pore volumes.
/// \param[in] source Source term. Sign convention is:
/// (+) inflow flux,
/// (-) outflow flux.
/// \param[out] tof Array of time-of-flight values.
void solveTof(const double* darcyflux,
const double* porevolume,
const double* source,
std::vector<double>& tof);
/// Solve for time-of-flight and a number of tracers.
/// \param[in] darcyflux Array of signed face fluxes.
/// \param[in] porevolume Array of pore volumes.
/// \param[in] source Source term. Sign convention is:
/// (+) inflow flux,
/// (-) outflow flux.
/// \param[in] tracerheads Table containing one row per tracer, and each
/// row contains the source cells for that tracer.
/// \param[out] tof Array of time-of-flight values (1 per cell).
/// \param[out] tracer Array of tracer values. N per cell, where N is
/// equalt to tracerheads.size().
void solveTofTracer(const double* darcyflux,
const double* porevolume,
const double* source,
const SparseTable<int>& tracerheads,
std::vector<double>& tof,
std::vector<double>& tracer);
private:
void executeSolve();
virtual void solveSingleCell(const int cell);
void solveSingleCellMultidimUpwind(const int cell);
void assembleSingleCell(const int cell,
std::vector<int>& local_column,
std::vector<double>& local_coefficient,
double& rhs);
virtual void solveMultiCell(const int num_cells, const int* cells);
void multidimUpwindTerms(const int face, const int upwind_cell,
double& face_term, double& cell_term_factor) const;
void localMultidimUpwindTerms(const int face, const int upwind_cell, const int node_pos,
double& face_term, double& cell_term_factor) const;
private:
const UnstructuredGrid& grid_;
const double* darcyflux_; // one flux per grid face
const double* porevolume_; // one volume per cell
const double* source_; // one volumetric source term per cell
double* tof_;
bool compute_tracer_;
enum { NoTracerHead = -1 };
std::vector<int> tracerhead_by_cell_;
// For solveMultiCell():
double gauss_seidel_tol_;
int num_multicell_;
int max_size_multicell_;
int max_iter_multicell_;
// For multidim upwinding:
bool use_multidim_upwind_;
std::vector<double> face_tof_; // For multidim upwind face tofs.
std::vector<double> face_part_tof_; // For multidim upwind face tofs.
};
} // namespace Opm
#endif // OPM_TRANSPORTMODELTRACERTOF_HEADER_INCLUDED

View File

@ -1,141 +0,0 @@
/*
Copyright 2012 SINTEF ICT, Applied Mathematics.
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 3 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 <http://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <opm/core/linalg/LinearSolverFactory.hpp>
#if HAVE_SUITESPARSE_UMFPACK_H
#include <opm/core/linalg/LinearSolverUmfpack.hpp>
#endif
#if HAVE_DUNE_ISTL
#include <opm/core/linalg/LinearSolverIstl.hpp>
#endif
#if HAVE_PETSC
#include <opm/core/linalg/LinearSolverPetsc.hpp>
#endif
#include <opm/common/utility/parameters/ParameterGroup.hpp>
#include <opm/common/ErrorMacros.hpp>
#include <string>
namespace Opm
{
LinearSolverFactory::LinearSolverFactory()
{
#if HAVE_SUITESPARSE_UMFPACK_H
solver_.reset(new LinearSolverUmfpack);
#elif HAVE_DUNE_ISTL
solver_.reset(new LinearSolverIstl);
#elif HAVE_PETSC
solver_.reset(new LinearSolverPetsc);
#else
OPM_THROW(std::runtime_error, "No linear solver available, you must have UMFPACK , dune-istl or Petsc installed to use LinearSolverFactory.");
#endif
}
LinearSolverFactory::LinearSolverFactory(const ParameterGroup& param)
{
#if HAVE_SUITESPARSE_UMFPACK_H
std::string default_solver = "umfpack";
#elif HAVE_DUNE_ISTL
std::string default_solver = "istl";
#elif HAVE_PETSC
std::string default_solver = "petsc";
#else
std::string default_solver = "no_solver_available";
OPM_THROW(std::runtime_error, "No linear solver available, you must have UMFPACK , dune-istl or Petsc installed to use LinearSolverFactory.");
#endif
const std::string ls =
param.getDefault("linsolver", default_solver);
if (ls == "umfpack") {
#if HAVE_SUITESPARSE_UMFPACK_H
solver_.reset(new LinearSolverUmfpack);
#endif
}
else if (ls == "istl") {
#if HAVE_DUNE_ISTL
solver_.reset(new LinearSolverIstl(param));
#endif
}
else if (ls == "petsc"){
#if HAVE_PETSC
solver_.reset(new LinearSolverPetsc(param));
#endif
}
else {
OPM_THROW(std::runtime_error, "Linear solver " << ls << " is unknown.");
}
if (! solver_) {
OPM_THROW(std::runtime_error, "Linear solver " << ls << " is not enabled in "
"this configuration.");
}
}
LinearSolverFactory::~LinearSolverFactory()
{
}
LinearSolverInterface::LinearSolverReport
LinearSolverFactory::solve(const int size,
const int nonzeros,
const int* ia,
const int* ja,
const double* sa,
const double* rhs,
double* solution,
const boost::any& add) const
{
return solver_->solve(size, nonzeros, ia, ja, sa, rhs, solution, add);
}
void LinearSolverFactory::setTolerance(const double tol)
{
solver_->setTolerance(tol);
}
double LinearSolverFactory::getTolerance() const
{
return solver_->getTolerance();
}
} // namespace Opm

View File

@ -1,101 +0,0 @@
/*
Copyright 2012 SINTEF ICT, Applied Mathematics.
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 3 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 <http://www.gnu.org/licenses/>.
*/
#ifndef OPM_LINEARSOLVERFACTORY_HEADER_INCLUDED
#define OPM_LINEARSOLVERFACTORY_HEADER_INCLUDED
#include <opm/core/linalg/LinearSolverInterface.hpp>
#include <memory>
namespace Opm
{
class ParameterGroup;
/// Concrete class encapsulating any available linear solver.
/// For the moment, this means UMFPACK and dune-istl.
/// Since both are optional dependencies, either or both
/// may be unavailable, depending on configuration.
class LinearSolverFactory : public LinearSolverInterface
{
public:
/// Default constructor.
LinearSolverFactory();
/// Construct from parameters.
/// The accepted parameters are (default) (allowed values):
/// linsolver ("umfpack") ("umfpack", "istl", "petsc")
/// For the umfpack solver to be available, this class must be
/// compiled with UMFPACK support, as indicated by the
/// variable HAVE_SUITESPARSE_UMFPACK_H in config.h.
/// For the istl solver to be available, this class must be
/// compiled with dune-istl support, as indicated by the
/// variable HAVE_DUNE_ISTL in config.h.
/// For the petsc solver to be available, this class must be
/// compiled with petsc support, as indicated by the
/// variable HAVE_PETSC in config.h.
/// Any further parameters are passed on to the constructors
/// of the actual solver used, see LinearSolverUmfpack,
/// LinearSolverIstl and LinearSolverPetsc for details.
LinearSolverFactory(const ParameterGroup& param);
/// Destructor.
virtual ~LinearSolverFactory();
using LinearSolverInterface::solve;
/// Solve a linear system, with a matrix given in compressed sparse row format.
/// \param[in] size # of rows in matrix
/// \param[in] nonzeros # of nonzeros elements in matrix
/// \param[in] ia array of length (size + 1) containing start and end indices for each row
/// \param[in] ja array of length nonzeros containing column numbers for the nonzero elements
/// \param[in] sa array of length nonzeros containing the values of the nonzero elements
/// \param[in] rhs array of length size containing the right hand side
/// \param[inout] solution array of length size to which the solution will be written, may also be used
/// as initial guess by iterative solvers.
virtual LinearSolverReport solve(const int size,
const int nonzeros,
const int* ia,
const int* ja,
const double* sa,
const double* rhs,
double* solution,
const boost::any& add=boost::any()) const;
/// Set tolerance for the linear solver.
/// \param[in] tol tolerance value
/// Not used for LinearSolverFactory
virtual void setTolerance(const double tol);
/// Get tolerance for the linear solver.
/// \param[out] tolerance value
/// Not used for LinearSolverFactory. Returns -1.
virtual double getTolerance() const;
private:
std::shared_ptr<LinearSolverInterface> solver_;
};
} // namespace Opm
#endif // OPM_LINEARSOLVERFACTORY_HEADER_INCLUDED

View File

@ -1,45 +0,0 @@
/*
Copyright 2012 SINTEF ICT, Applied Mathematics.
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 3 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 <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <opm/core/linalg/LinearSolverInterface.hpp>
#include <opm/core/linalg/sparse_sys.h>
#include <opm/core/linalg/call_umfpack.h>
namespace Opm
{
LinearSolverInterface::~LinearSolverInterface()
{
}
LinearSolverInterface::LinearSolverReport
LinearSolverInterface::solve(const CSRMatrix* A,
const double* rhs,
double* solution) const
{
return solve(A->m, A->nnz, A->ia, A->ja, A->sa, rhs, solution);
}
} // namespace Opm

View File

@ -1,91 +0,0 @@
/*
Copyright 2012 SINTEF ICT, Applied Mathematics.
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 3 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 <http://www.gnu.org/licenses/>.
*/
#ifndef OPM_LINEARSOLVERINTERFACE_HEADER_INCLUDED
#define OPM_LINEARSOLVERINTERFACE_HEADER_INCLUDED
#include<boost/any.hpp>
struct CSRMatrix;
namespace Opm
{
/// Abstract interface for linear solvers.
class LinearSolverInterface
{
public:
/// Virtual destructor.
virtual ~LinearSolverInterface();
/// Struct for reporting data about the solution process back
/// to the caller. The only field that is mandatory to set is
/// 'converged' (even for direct solvers) to indicate success.
struct LinearSolverReport
{
bool converged;
int iterations;
double residual_reduction;
};
/// Solve a linear system, with a matrix given in compressed sparse row format.
/// \param[in] A matrix in CSR format
/// \param[in] rhs array of length A->m containing the right hand side
/// \param[inout] solution array of length A->m to which the solution will be written, may also be used
/// as initial guess by iterative solvers.
/// Note: this method is a convenience method that calls the virtual solve() method.
LinearSolverReport solve(const CSRMatrix* A,
const double* rhs,
double* solution) const;
/// Solve a linear system, with a matrix given in compressed sparse row format.
/// \param[in] size # of rows in matrix
/// \param[in] nonzeros # of nonzeros elements in matrix
/// \param[in] ia array of length (size + 1) containing start and end indices for each row
/// \param[in] ja array of length nonzeros containing column numbers for the nonzero elements
/// \param[in] sa array of length nonzeros containing the values of the nonzero elements
/// \param[in] rhs array of length size containing the right hand side
/// \param[inout] solution array of length size to which the solution will be written, may also be used
/// as initial guess by iterative solvers.
virtual LinearSolverReport solve(const int size,
const int nonzeros,
const int* ia,
const int* ja,
const double* sa,
const double* rhs,
double* solution,
const boost::any& add=boost::any()) const = 0;
/// Set tolerance for the linear solver.
/// \param[in] tol tolerance value
virtual void setTolerance(const double tol) = 0;
/// Get tolerance for the linear solver.
/// \param[out] tolerance value
virtual double getTolerance() const = 0;
};
} // namespace Opm
#endif // OPM_LINEARSOLVERINTERFACE_HEADER_INCLUDED

View File

@ -1,526 +0,0 @@
/*
Copyright 2012 SINTEF ICT, Applied Mathematics.
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 3 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 <http://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <opm/core/linalg/LinearSolverIstl.hpp>
#include <opm/core/linalg/ParallelIstlInformation.hpp>
#include <opm/common/ErrorMacros.hpp>
// Silence compatibility warning from DUNE headers since we don't use
// the deprecated member anyway (in this compilation unit)
#define DUNE_COMMON_FIELDVECTOR_SIZE_IS_METHOD 1
#include <opm/common/utility/platform_dependent/disable_warnings.h>
// TODO: clean up includes.
#include <dune/common/deprecated.hh>
#include <dune/common/version.hh>
#include <dune/istl/bvector.hh>
#include <dune/istl/bcrsmatrix.hh>
#include <dune/istl/operators.hh>
#include <dune/istl/io.hh>
#include <dune/istl/owneroverlapcopy.hh>
#include <dune/istl/preconditioners.hh>
#include <dune/istl/schwarz.hh>
#include <dune/istl/solvers.hh>
#include <dune/istl/paamg/amg.hh>
#include <dune/istl/paamg/kamg.hh>
#include <dune/istl/paamg/pinfo.hh>
#include <dune/istl/paamg/fastamg.hh>
#include <opm/common/utility/platform_dependent/reenable_warnings.h>
#include <stdexcept>
#include <iostream>
#include <type_traits>
namespace Opm
{
namespace {
typedef Dune::FieldVector<double, 1 > VectorBlockType;
typedef Dune::FieldMatrix<double, 1, 1> MatrixBlockType;
typedef Dune::BCRSMatrix <MatrixBlockType> Mat;
typedef Dune::BlockVector<VectorBlockType> Vector;
typedef Dune::MatrixAdapter<Mat,Vector,Vector> Operator;
template<class O, class S, class C>
LinearSolverInterface::LinearSolverReport
solveCG_ILU0(O& A, Vector& x, Vector& b, S& sp, const C& comm, double tolerance, int maxit, int verbosity);
template<class O, class S, class C>
LinearSolverInterface::LinearSolverReport
solveCG_AMG(O& A, Vector& x, Vector& b, S& sp, const C& comm, double tolerance, int maxit, int verbosity,
double prolongateFactor, int smoothsteps);
template<class O, class S, class C>
LinearSolverInterface::LinearSolverReport
solveKAMG(O& A, Vector& x, Vector& b, S& sp, const C& comm, double tolerance, int maxit, int verbosity,
double prolongateFactor, int smoothsteps);
template<class O, class S, class C>
LinearSolverInterface::LinearSolverReport
solveFastAMG(O& A, Vector& x, Vector& b, S& sp, const C& comm, double tolerance, int maxit, int verbosity,
double prolongateFactor);
template<class O, class S, class C>
LinearSolverInterface::LinearSolverReport
solveBiCGStab_ILU0(O& A, Vector& x, Vector& b, S& sp, const C& comm, double tolerance, int maxit, int verbosity);
} // anonymous namespace
LinearSolverIstl::LinearSolverIstl()
: linsolver_residual_tolerance_(1e-8),
linsolver_verbosity_(0),
linsolver_type_(CG_AMG),
linsolver_save_system_(false),
linsolver_max_iterations_(0),
linsolver_smooth_steps_(2),
linsolver_prolongate_factor_(1.6)
{
}
LinearSolverIstl::LinearSolverIstl(const ParameterGroup& param)
: linsolver_residual_tolerance_(1e-8),
linsolver_verbosity_(0),
linsolver_type_(CG_AMG),
linsolver_save_system_(false),
linsolver_max_iterations_(0),
linsolver_smooth_steps_(2),
linsolver_prolongate_factor_(1.6)
{
linsolver_residual_tolerance_ = param.getDefault("linsolver_residual_tolerance", linsolver_residual_tolerance_);
linsolver_verbosity_ = param.getDefault("linsolver_verbosity", linsolver_verbosity_);
linsolver_type_ = LinsolverType(param.getDefault("linsolver_type", int(linsolver_type_)));
linsolver_save_system_ = param.getDefault("linsolver_save_system", linsolver_save_system_);
if (linsolver_save_system_) {
linsolver_save_filename_ = param.getDefault("linsolver_save_filename", std::string("linsys"));
}
linsolver_max_iterations_ = param.getDefault("linsolver_max_iterations", linsolver_max_iterations_);
linsolver_smooth_steps_ = param.getDefault("linsolver_smooth_steps", linsolver_smooth_steps_);
linsolver_prolongate_factor_ = param.getDefault("linsolver_prolongate_factor", linsolver_prolongate_factor_);
}
LinearSolverIstl::~LinearSolverIstl()
{}
LinearSolverInterface::LinearSolverReport
LinearSolverIstl::solve(const int size,
const int nonzeros,
const int* ia,
const int* ja,
const double* sa,
const double* rhs,
double* solution,
const boost::any& comm) const
{
// Build Istl structures from input.
// System matrix
Mat A(size, size, nonzeros, Mat::row_wise);
for (Mat::CreateIterator row = A.createbegin(); row != A.createend(); ++row) {
int ri = row.index();
for (int i = ia[ri]; i < ia[ri + 1]; ++i) {
row.insert(ja[i]);
}
}
for (int ri = 0; ri < size; ++ri) {
for (int i = ia[ri]; i < ia[ri + 1]; ++i) {
A[ri][ja[i]] = sa[i];
}
}
int maxit = linsolver_max_iterations_;
if (maxit == 0) {
maxit = 5000;
}
#if HAVE_MPI
if(comm.type()==typeid(ParallelISTLInformation))
{
typedef Dune::OwnerOverlapCopyCommunication<int,int> Comm;
const ParallelISTLInformation& info = boost::any_cast<const ParallelISTLInformation&>(comm);
Comm istlComm(info.communicator());
info.copyValuesTo(istlComm.indexSet(), istlComm.remoteIndices());
Dune::OverlappingSchwarzOperator<Mat,Vector,Vector, Comm>
opA(A, istlComm);
Dune::OverlappingSchwarzScalarProduct<Vector,Comm> sp(istlComm);
return solveSystem(opA, solution, rhs, sp, istlComm, maxit);
}
else
#endif
{
(void) comm; // Avoid warning for unused argument if no MPI.
Dune::SeqScalarProduct<Vector> sp;
Dune::Amg::SequentialInformation seq_comm;
Operator opA(A);
return solveSystem(opA, solution, rhs, sp, seq_comm, maxit);
}
}
template<class O, class S, class C>
LinearSolverInterface::LinearSolverReport
LinearSolverIstl::solveSystem (O& opA, double* solution, const double* rhs,
S& sp, const C& comm, int maxit) const
{
// System RHS
Vector b(opA.getmat().N());
std::copy(rhs, rhs+b.size(), b.begin());
// Make rhs consistent in the parallel case
comm.copyOwnerToAll(b,b);
// System solution
Vector x(opA.getmat().M());
x = 0.0;
if (linsolver_save_system_)
{
// Save system to files.
writeMatrixToMatlab(opA.getmat(), linsolver_save_filename_ + "-mat");
std::string rhsfile(linsolver_save_filename_ + "-rhs");
std::ofstream rhsf(rhsfile.c_str());
rhsf.precision(15);
rhsf.setf(std::ios::scientific | std::ios::showpos);
std::copy(b.begin(), b.end(),
std::ostream_iterator<VectorBlockType>(rhsf, "\n"));
}
LinearSolverReport res;
switch (linsolver_type_) {
case CG_ILU0:
res = solveCG_ILU0(opA, x, b, sp, comm, linsolver_residual_tolerance_, maxit, linsolver_verbosity_);
break;
case CG_AMG:
res = solveCG_AMG(opA, x, b, sp, comm, linsolver_residual_tolerance_, maxit, linsolver_verbosity_,
linsolver_prolongate_factor_, linsolver_smooth_steps_);
break;
case KAMG:
res = solveKAMG(opA, x, b, sp, comm, linsolver_residual_tolerance_, maxit, linsolver_verbosity_,
linsolver_prolongate_factor_, linsolver_smooth_steps_);
break;
case FastAMG:
#if HAVE_MPI
if(std::is_same<C,Dune::OwnerOverlapCopyCommunication<int,int> >::value)
{
OPM_THROW(std::runtime_error, "Trying to use sequential FastAMG solver for a parallel problem!");
}
#endif // HAVE_MPI
res = solveFastAMG(opA, x, b, sp, comm, linsolver_residual_tolerance_, maxit, linsolver_verbosity_,
linsolver_prolongate_factor_);
break;
case BiCGStab_ILU0:
res = solveBiCGStab_ILU0(opA, x, b, sp, comm, linsolver_residual_tolerance_, maxit, linsolver_verbosity_);
break;
default:
std::cerr << "Unknown linsolver_type: " << int(linsolver_type_) << '\n';
throw std::runtime_error("Unknown linsolver_type");
}
std::copy(x.begin(), x.end(), solution);
return res;
}
void LinearSolverIstl::setTolerance(const double tol)
{
linsolver_residual_tolerance_ = tol;
}
double LinearSolverIstl::getTolerance() const
{
return linsolver_residual_tolerance_;
}
namespace
{
template<class P, class O, class C>
struct SmootherChooser
{
typedef P Type;
};
#if HAVE_MPI
template<class P, class O>
struct SmootherChooser<P, O, Dune::OwnerOverlapCopyCommunication<int,int> >
{
typedef Dune::OwnerOverlapCopyCommunication<int,int> Comm;
typedef Dune::BlockPreconditioner<typename O::domain_type, typename O::range_type,
Comm, P>
Type;
};
#endif
template<class P, class O, class C>
struct PreconditionerTraits
{
typedef typename SmootherChooser<P,O,C>::Type SmootherType;
typedef std::shared_ptr<SmootherType> PointerType;
};
template<class P, class O, class C>
typename PreconditionerTraits<P,O,C>::PointerType
makePreconditioner(O& opA, double relax, const C& comm, int iterations=1)
{
typedef typename SmootherChooser<P,O,C>::Type SmootherType;
typedef typename PreconditionerTraits<P,O,C>::PointerType PointerType;
typename Dune::Amg::SmootherTraits<SmootherType>::Arguments args;
typename Dune::Amg::ConstructionTraits<SmootherType>::Arguments cargs;
cargs.setMatrix(opA.getmat());
args.iterations=iterations;
args.relaxationFactor=relax;
cargs.setArgs(args);
cargs.setComm(comm);
return PointerType(Dune::Amg::ConstructionTraits<SmootherType>::construct(cargs));
}
template<class O, class S, class C>
LinearSolverInterface::LinearSolverReport
solveCG_ILU0(O& opA, Vector& x, Vector& b, S& sp, const C& comm, double tolerance, int maxit, int verbosity)
{
// Construct preconditioner.
typedef Dune::SeqILU0<Mat,Vector,Vector> Preconditioner;
auto precond = makePreconditioner<Preconditioner>(opA, 1.0, comm);
// Construct linear solver.
Dune::CGSolver<Vector> linsolve(opA, sp, *precond, tolerance, maxit, verbosity);
// Solve system.
Dune::InverseOperatorResult result;
linsolve.apply(x, b, result);
// Output results.
LinearSolverInterface::LinearSolverReport res;
res.converged = result.converged;
res.iterations = result.iterations;
res.residual_reduction = result.reduction;
return res;
}
#define FIRST_DIAGONAL 1
#define SYMMETRIC 1
#define SMOOTHER_ILU 0
#define ANISOTROPIC_3D 0
template<typename C>
void setUpCriterion(C& criterion, double linsolver_prolongate_factor,
int verbosity, std::size_t linsolver_smooth_steps)
{
criterion.setDebugLevel(verbosity);
#if ANISOTROPIC_3D
criterion.setDefaultValuesAnisotropic(3, 2);
#endif
criterion.setProlongationDampingFactor(linsolver_prolongate_factor);
criterion.setNoPreSmoothSteps(linsolver_smooth_steps);
criterion.setNoPostSmoothSteps(linsolver_smooth_steps);
criterion.setGamma(1); // V-cycle; this is the default
}
template<class O, class S, class C>
LinearSolverInterface::LinearSolverReport
solveCG_AMG(O& opA, Vector& x, Vector& b, S& sp, const C& comm, double tolerance, int maxit, int verbosity,
double linsolver_prolongate_factor, int linsolver_smooth_steps)
{
// Solve with AMG solver.
#if FIRST_DIAGONAL
typedef Dune::Amg::FirstDiagonal CouplingMetric;
#else
typedef Dune::Amg::RowSum CouplingMetric;
#endif
#if SYMMETRIC
typedef Dune::Amg::SymmetricCriterion<Mat,CouplingMetric> CriterionBase;
#else
typedef Dune::Amg::UnSymmetricCriterion<Mat,CouplingMetric> CriterionBase;
#endif
#if SMOOTHER_ILU
typedef Dune::SeqILU0<Mat,Vector,Vector> SeqSmoother;
#else
typedef Dune::SeqSOR<Mat,Vector,Vector> SeqSmoother;
#endif
typedef typename SmootherChooser<SeqSmoother, O, C>::Type Smoother;
typedef Dune::Amg::CoarsenCriterion<CriterionBase> Criterion;
typedef Dune::Amg::AMG<O,Vector,Smoother,C> Precond;
// Construct preconditioner.
Criterion criterion;
typename Precond::SmootherArgs smootherArgs;
setUpCriterion(criterion, linsolver_prolongate_factor, verbosity,
linsolver_smooth_steps);
Precond precond(opA, criterion, smootherArgs, comm);
// Construct linear solver.
Dune::CGSolver<Vector> linsolve(opA, sp, precond, tolerance, maxit, verbosity);
// Solve system.
Dune::InverseOperatorResult result;
linsolve.apply(x, b, result);
// Output results.
LinearSolverInterface::LinearSolverReport res;
res.converged = result.converged;
res.iterations = result.iterations;
res.residual_reduction = result.reduction;
return res;
}
template<class O, class S, class C>
LinearSolverInterface::LinearSolverReport
solveKAMG(O& opA, Vector& x, Vector& b, S& /* sp */, const C& /* comm */, double tolerance, int maxit, int verbosity,
double linsolver_prolongate_factor, int linsolver_smooth_steps)
{
// Solve with AMG solver.
Dune::MatrixAdapter<typename O::matrix_type,Vector,Vector> sOpA(opA.getmat());
#if FIRST_DIAGONAL
typedef Dune::Amg::FirstDiagonal CouplingMetric;
#else
typedef Dune::Amg::RowSum CouplingMetric;
#endif
#if SYMMETRIC
typedef Dune::Amg::SymmetricCriterion<Mat,CouplingMetric> CriterionBase;
#else
typedef Dune::Amg::UnSymmetricCriterion<Mat,CouplingMetric> CriterionBase;
#endif
#if SMOOTHER_ILU
typedef Dune::SeqILU0<Mat,Vector,Vector> Smoother;
#else
typedef Dune::SeqSOR<Mat,Vector,Vector> Smoother;
#endif
typedef Dune::Amg::CoarsenCriterion<CriterionBase> Criterion;
typedef Dune::Amg::KAMG<Operator,Vector,Smoother,Dune::Amg::SequentialInformation> Precond;
// Construct preconditioner.
Precond::SmootherArgs smootherArgs;
Criterion criterion;
setUpCriterion(criterion, linsolver_prolongate_factor, verbosity,
linsolver_smooth_steps);
Precond precond(sOpA, criterion, smootherArgs);
// Construct linear solver.
Dune::GeneralizedPCGSolver<Vector> linsolve(sOpA, precond, tolerance, maxit, verbosity);
// Solve system.
Dune::InverseOperatorResult result;
linsolve.apply(x, b, result);
// Output results.
LinearSolverInterface::LinearSolverReport res;
res.converged = result.converged;
res.iterations = result.iterations;
res.residual_reduction = result.reduction;
return res;
}
template<class O, class S, class C>
LinearSolverInterface::LinearSolverReport
solveFastAMG(O& opA, Vector& x, Vector& b, S& /* sp */, const C& /* comm */, double tolerance, int maxit, int verbosity,
double linsolver_prolongate_factor)
{
// Solve with AMG solver.
typedef Dune::MatrixAdapter<typename O::matrix_type, Vector, Vector> AMGOperator;
AMGOperator sOpA(opA.getmat());
#if FIRST_DIAGONAL
typedef Dune::Amg::FirstDiagonal CouplingMetric;
#else
typedef Dune::Amg::RowSum CouplingMetric;
#endif
#if SYMMETRIC
typedef Dune::Amg::AggregationCriterion<Dune::Amg::SymmetricMatrixDependency<Mat,CouplingMetric> > CriterionBase;
#else
typedef Dune::Amg::AggregationCriterion<Dune::Amg::SymmetricMatrixDependency<Mat,CouplingMetric> > CriterionBase;
#endif
typedef Dune::Amg::CoarsenCriterion<CriterionBase> Criterion;
typedef Dune::Amg::FastAMG<AMGOperator, Vector> Precond;
// Construct preconditioner.
Criterion criterion;
const int smooth_steps = 1;
setUpCriterion(criterion, linsolver_prolongate_factor, verbosity, smooth_steps);
Dune::Amg::Parameters parms;
parms.setDebugLevel(verbosity);
parms.setNoPreSmoothSteps(smooth_steps);
parms.setNoPostSmoothSteps(smooth_steps);
parms.setProlongationDampingFactor(linsolver_prolongate_factor);
Precond precond(sOpA, criterion, parms);
// Construct linear solver.
Dune::GeneralizedPCGSolver<Vector> linsolve(sOpA, precond, tolerance, maxit, verbosity);
// Solve system.
Dune::InverseOperatorResult result;
linsolve.apply(x, b, result);
// Output results.
LinearSolverInterface::LinearSolverReport res;
res.converged = result.converged;
res.iterations = result.iterations;
res.residual_reduction = result.reduction;
return res;
}
template<class O, class S, class C>
LinearSolverInterface::LinearSolverReport
solveBiCGStab_ILU0(O& opA, Vector& x, Vector& b, S& sp, const C& comm, double tolerance, int maxit, int verbosity)
{
// Construct preconditioner.
typedef Dune::SeqILU0<Mat,Vector,Vector> Preconditioner;
auto precond = makePreconditioner<Preconditioner>(opA, 1.0, comm);
// Construct linear solver.
Dune::BiCGSTABSolver<Vector> linsolve(opA, sp, *precond, tolerance, maxit, verbosity);
// Solve system.
Dune::InverseOperatorResult result;
linsolve.apply(x, b, result);
// Output results.
LinearSolverInterface::LinearSolverReport res;
res.converged = result.converged;
res.iterations = result.iterations;
res.residual_reduction = result.reduction;
return res;
}
} // anonymous namespace
} // namespace Opm

View File

@ -1,119 +0,0 @@
/*
Copyright 2012 SINTEF ICT, Applied Mathematics.
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 3 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 <http://www.gnu.org/licenses/>.
*/
#ifndef OPM_LINEARSOLVERISTL_HEADER_INCLUDED
#define OPM_LINEARSOLVERISTL_HEADER_INCLUDED
#include <opm/core/linalg/LinearSolverInterface.hpp>
#include <opm/common/utility/parameters/ParameterGroup.hpp>
#include <string>
#include <boost/any.hpp>
namespace Opm
{
/// Concrete class encapsulating some dune-istl linear solvers.
class LinearSolverIstl : public LinearSolverInterface
{
public:
/// Default constructor.
/// All parameters controlling the solver are defaulted:
/// linsolver_residual_tolerance 1e-8
/// linsolver_verbosity 0
/// linsolver_type 1 ( = CG_AMG), alternatives are:
/// CG_ILU0 = 0, CG_AMG = 1, BiCGStab_ILU0 = 2
/// FastAMG=3, KAMG=4 };
/// linsolver_save_system false
/// linsolver_save_filename <empty string>
/// linsolver_max_iterations 0 (unlimited=5000)
/// linsolver_residual_tolerance 1e-8
/// linsolver_smooth_steps 2
/// linsolver_prolongate_factor 1.6
/// linsolver_verbosity 0
LinearSolverIstl();
/// Construct from parameters
/// Accepted parameters are, with defaults, listed in the
/// default constructor.
LinearSolverIstl(const ParameterGroup& param);
/// Destructor.
virtual ~LinearSolverIstl();
using LinearSolverInterface::solve;
/// Solve a linear system, with a matrix given in compressed sparse row format.
/// \param[in] size # of rows in matrix
/// \param[in] nonzeros # of nonzeros elements in matrix
/// \param[in] ia array of length (size + 1) containing start and end indices for each row
/// \param[in] ja array of length nonzeros containing column numbers for the nonzero elements
/// \param[in] sa array of length nonzeros containing the values of the nonzero elements
/// \param[in] rhs array of length size containing the right hand side
/// \param[inout] solution array of length size to which the solution will be written, may also be used
/// as initial guess by iterative solvers.
virtual LinearSolverReport solve(const int size,
const int nonzeros,
const int* ia,
const int* ja,
const double* sa,
const double* rhs,
double* solution,
const boost::any& comm=boost::any()) const;
/// Set tolerance for the residual in dune istl linear solver.
/// \param[in] tol tolerance value
virtual void setTolerance(const double tol);
/// Get tolerance ofthe linear solver.
/// \param[out] tolerance value
virtual double getTolerance() const;
private:
/// \brief Solve the linear system using ISTL
/// \param[in] opA The linear operator of the system to solve.
/// \param[out] solution C array for storing the solution vector.
/// \param[in] rhs C array containing the right hand side.
/// \param[in] sp The scalar product to use.
/// \param[in] comm The information about the parallel domain decomposition.
/// \param[in] maxit The maximum number of iterations allowed.
template<class O, class S, class C>
LinearSolverReport solveSystem(O& opA, double* solution, const double *rhs,
S& sp, const C& comm, int maxit) const;
double linsolver_residual_tolerance_;
int linsolver_verbosity_;
enum LinsolverType { CG_ILU0 = 0, CG_AMG = 1, BiCGStab_ILU0 = 2, FastAMG=3, KAMG=4 };
LinsolverType linsolver_type_;
bool linsolver_save_system_;
std::string linsolver_save_filename_;
int linsolver_max_iterations_;
/** \brief The number smoothing steps to apply in AMG. */
int linsolver_smooth_steps_;
/** \brief The factor to scale the coarse grid correction with. */
double linsolver_prolongate_factor_;
};
} // namespace Opm
#endif // OPM_LINEARSOLVERISTL_HEADER_INCLUDED

View File

@ -1,289 +0,0 @@
/*
Copyright 2014 SINTEF ICT, Applied Mathematics.
Copyright 2014 STATOIL ASA.
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 3 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 <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <cstring>
#include <opm/core/linalg/LinearSolverPetsc.hpp>
#include <unordered_map>
#define PETSC_CLANGUAGE_CXX 1 //enable CHKERRXX macro.
#include <opm/common/utility/platform_dependent/disable_warnings.h>
#include <petsc.h>
#include <opm/common/utility/platform_dependent/reenable_warnings.h>
#include <opm/common/ErrorMacros.hpp>
namespace Opm
{
namespace{
class KSPTypeMap {
public:
explicit
KSPTypeMap(const std::string& default_type = "gmres")
: default_type_(default_type)
{
// g++-4.4 has problems converting const char* to char*
// The problem is caused by the mapped type being PCType
// which (at least in PETSc 3.2) is char* because of C
// (in the header there is "#define PCType character*(80)").
// and the KSP... defines being const char* (because of C++).
type_map_["richardson"] = KSPRICHARDSON;
// Not available in PETSC 3.2 on Debian
//type_map_["chebyshev"] = KSPCHEBYSHEV;
type_map_["cg"] = KSPCG;
type_map_["bicgs"] = KSPBICG;
type_map_["gmres"] = KSPGMRES;
type_map_["fgmres"] = KSPFGMRES;
type_map_["dgmres"] = KSPDGMRES;
type_map_["gcr"] = KSPGCR;
type_map_["bcgs"] = KSPBCGS;
type_map_["cgs"] = KSPCGS;
type_map_["tfqmr"] = KSPTFQMR;
type_map_["tcqmr"] = KSPTCQMR;
type_map_["cr"] = KSPCR;
type_map_["preonly"] = KSPPREONLY;
}
KSPType
find(const std::string& type) const
{
Map::const_iterator it = type_map_.find(type);
if (it == type_map_.end()) {
it = type_map_.find(default_type_);
}
if (it == type_map_.end()) {
OPM_THROW(std::runtime_error, "Unknown KSPType: '" << type << "'");
}
return it->second;
}
private:
typedef std::unordered_map<std::string, KSPType> Map;
std::string default_type_;
Map type_map_;
};
class PCTypeMap {
public:
explicit
PCTypeMap(const std::string& default_type = "jacobi")
: default_type_(default_type)
{
type_map_["jacobi"] = PCJACOBI;
type_map_["bjacobi"] = PCBJACOBI;
type_map_["sor"] = PCSOR;
type_map_["eisenstat"] = PCEISENSTAT;
type_map_["icc"] = PCICC;
type_map_["ilu"] = PCILU;
type_map_["asm"] = PCASM;
type_map_["gamg"] = PCGAMG;
type_map_["ksp"] = PCKSP;
type_map_["composite"] = PCCOMPOSITE;
type_map_["lu"] = PCLU;
type_map_["cholesky"] = PCCHOLESKY;
type_map_["none"] = PCNONE;
}
PCType
find(const std::string& type) const
{
Map::const_iterator it = type_map_.find(type);
if (it == type_map_.end()) {
it = type_map_.find(default_type_);
}
if (it == type_map_.end()) {
OPM_THROW(std::runtime_error, "Unknown PCType: '" << type << "'");
}
return it->second;
}
private:
typedef std::unordered_map<std::string, PCType> Map;
std::string default_type_;
Map type_map_;
};
struct OEM_DATA {
/* Convenience struct to handle automatic (de)allocation of some useful
* variables, as well as group them up for easier parameter passing
*/
Vec x;
Vec b;
Mat A;
KSP ksp;
PC preconditioner;
OEM_DATA( const int size ) {
VecCreate( PETSC_COMM_WORLD, &b );
auto err = VecSetSizes( b, PETSC_DECIDE, size );
CHKERRXX( err );
VecSetFromOptions( b );
KSPCreate( PETSC_COMM_WORLD, &ksp );
}
~OEM_DATA() {
VecDestroy( &x );
VecDestroy( &b );
MatDestroy( &A );
KSPDestroy( &ksp );
}
};
Vec to_petsc_vec( const double* x, int size ) {
PetscScalar* vec;
Vec v;
VecCreate( PETSC_COMM_WORLD, &v );
VecSetSizes( v, PETSC_DECIDE, size );
VecSetFromOptions( v );
VecGetArray( v, &vec );
std::memcpy( vec, x, size * sizeof( double ) );
VecRestoreArray( v, &vec );
return v;
}
void from_petsc_vec( double* x, Vec v ) {
if( !v ) OPM_THROW( std::runtime_error,
"PETSc CopySolution: Invalid PETSc vector." );
PetscScalar* vec;
PetscInt size;
VecGetLocalSize( v, &size );
VecGetArray( v, &vec );
std::memcpy( x, vec, size * sizeof( double ) );
VecRestoreArray( v, &vec );
}
Mat to_petsc_mat( const int size, const int /* nonzeros */,
const int* ia, const int* ja, const double* sa ) {
Mat A;
auto err = MatCreateSeqAIJWithArrays( PETSC_COMM_WORLD, size, size, const_cast<int*>(ia), const_cast<int*>(ja), (double*)sa, &A );
CHKERRXX( err );
return A;
}
void solve_system( OEM_DATA& t, KSPType method, PCType pcname,
double rtol, double atol, double dtol, int maxits, int ksp_view ) {
PetscInt its;
PetscReal residual;
KSPConvergedReason reason;
#if PETSC_VERSION_MAJOR <= 3 && PETSC_VERSION_MINOR < 5
KSPSetOperators( t.ksp, t.A, t.A, DIFFERENT_NONZERO_PATTERN );
#else
KSPSetOperators( t.ksp, t.A, t.A );
KSPSetReusePreconditioner(t.ksp, PETSC_FALSE);
#endif
KSPGetPC( t.ksp, &t.preconditioner );
auto err = KSPSetType( t.ksp, method );
CHKERRXX( err );
err = PCSetType( t.preconditioner, pcname );
CHKERRXX( err );
err = KSPSetTolerances( t.ksp, rtol, atol, dtol, maxits );
CHKERRXX( err );
err = KSPSetFromOptions( t.ksp );
CHKERRXX( err );
KSPSetInitialGuessNonzero( t.ksp, PETSC_FALSE );
KSPSolve( t.ksp, t.x, t.b );
KSPGetConvergedReason( t.ksp, &reason );
KSPGetIterationNumber( t.ksp, &its );
KSPGetResidualNorm( t.ksp, &residual );
if( ksp_view )
KSPView( t.ksp, PETSC_VIEWER_STDOUT_WORLD );
err = PetscPrintf( PETSC_COMM_WORLD, "KSP Iterations %D, Final Residual %g\n", its, (double)residual );
CHKERRXX( err );
}
} // anonymous namespace.
LinearSolverPetsc::LinearSolverPetsc(const ParameterGroup& param)
: ksp_type_( param.getDefault( std::string( "ksp_type" ), std::string( "gmres" ) ) )
, pc_type_( param.getDefault( std::string( "pc_type" ), std::string( "sor" ) ) )
, ksp_view_( param.getDefault( std::string( "ksp_view" ), int( false ) ) )
, rtol_( param.getDefault( std::string( "ksp_rtol" ), 1e-5 ) )
, atol_( param.getDefault( std::string( "ksp_atol" ), 1e-50 ) )
, dtol_( param.getDefault( std::string( "ksp_dtol" ), 1e5 ) )
, maxits_( param.getDefault( std::string( "ksp_max_it" ), 1e5 ) )
{
int argc = 0;
char** argv = NULL;
PetscInitialize(&argc, &argv, (char*)0, "Petsc interface for OPM!\n");
}
LinearSolverPetsc::~LinearSolverPetsc()
{
PetscFinalize();
}
LinearSolverInterface::LinearSolverReport
LinearSolverPetsc::solve(const int size,
const int nonzeros,
const int* ia,
const int* ja,
const double* sa,
const double* rhs,
double* solution,
const boost::any&) const
{
KSPTypeMap ksp(ksp_type_);
KSPType ksp_type = ksp.find(ksp_type_);
PCTypeMap pc(pc_type_);
PCType pc_type = pc.find(pc_type_);
OEM_DATA t( size );
t.A = to_petsc_mat( size, nonzeros, ia, ja, sa );
t.x = to_petsc_vec( rhs, size );
solve_system( t, ksp_type, pc_type, rtol_, atol_, dtol_, maxits_, ksp_view_ );
from_petsc_vec( solution, t.b );
LinearSolverReport rep = {};
rep.converged = true;
return rep;
}
void LinearSolverPetsc::setTolerance(const double /*tol*/)
{
}
double LinearSolverPetsc::getTolerance() const
{
return -1.;
}
} // namespace Opm

View File

@ -1,97 +0,0 @@
/*
Copyright 2014 SINTEF ICT, Applied Mathematics.
Copyright 2014 STATOIL ASA.
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 3 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 <http://www.gnu.org/licenses/>.
*/
#ifndef OPM_LINEARSOLVERPETSC_HEADER_INCLUDED
#define OPM_LINEARSOLVERPETSC_HEADER_INCLUDED
#if !HAVE_PETSC
#error "LinearSolverPetsc.hpp included, but the PETSc libraries are not available!"
#endif
#include <opm/core/linalg/LinearSolverInterface.hpp>
#include <opm/common/utility/parameters/ParameterGroup.hpp>
#include <string>
namespace Opm
{
/// Concrete class encapsulating some Petsc linear solvers.
class LinearSolverPetsc : public LinearSolverInterface
{
public:
/// Default constructor.
/// Declared, but not implemented. Petsc can only be created through
/// the ParameterGroup constructor, everything else is an error. This way
/// the error is caught compile time and not rune time, which is nice as
/// it is a static error.
LinearSolverPetsc();
/// Construct from parameters
/// Accepted parameters are, with defaults, listed in the
/// default constructor.
LinearSolverPetsc(const ParameterGroup& param);
/// Destructor.
virtual ~LinearSolverPetsc();
using LinearSolverInterface::solve;
/// Solve a linear system, with a matrix given in compressed sparse row format.
/// \param[in] size # of rows in matrix
/// \param[in] nonzeros # of nonzeros elements in matrix
/// \param[in] ia array of length (size + 1) containing start and end indices for each row
/// \param[in] ja array of length nonzeros containing column numbers for the nonzero elements
/// \param[in] sa array of length nonzeros containing the values of the nonzero elements
/// \param[in] rhs array of length size containing the right hand side
/// \param[inout] solution array of length size to which the solution will be written, may also be used
/// as initial guess by iterative solvers.
virtual LinearSolverReport solve(const int size,
const int nonzeros,
const int* ia,
const int* ja,
const double* sa,
const double* rhs,
double* solution,
const boost::any&) const;
/// Set tolerance for the residual in dune istl linear solver.
/// \param[in] tol tolerance value
virtual void setTolerance(const double tol);
/// Get tolerance ofthe linear solver.
/// \param[out] tolerance value
virtual double getTolerance() const;
private:
std::string ksp_type_;
std::string pc_type_;
int ksp_view_;
double rtol_;
double atol_;
double dtol_;
int maxits_;
};
} // namespace Opm
#endif // OPM_LINEARSOLVERPETSC_HEADER_INCLUDED

View File

@ -1,76 +0,0 @@
/*
Copyright 2012 SINTEF ICT, Applied Mathematics.
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 3 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 <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <opm/core/linalg/LinearSolverUmfpack.hpp>
#include <opm/core/linalg/sparse_sys.h>
#include <opm/core/linalg/call_umfpack.h>
namespace Opm
{
LinearSolverUmfpack::LinearSolverUmfpack()
{
}
LinearSolverUmfpack::~LinearSolverUmfpack()
{
}
LinearSolverInterface::LinearSolverReport
LinearSolverUmfpack::solve(const int size,
const int nonzeros,
const int* ia,
const int* ja,
const double* sa,
const double* rhs,
double* solution,
const boost::any&) const
{
CSRMatrix A = {
(size_t)size,
(size_t)nonzeros,
const_cast<int*>(ia),
const_cast<int*>(ja),
const_cast<double*>(sa)
};
call_UMFPACK(&A, rhs, solution);
LinearSolverReport rep = {};
rep.converged = true;
return rep;
}
void LinearSolverUmfpack::setTolerance(const double /*tol*/)
{
}
double LinearSolverUmfpack::getTolerance() const
{
return -1.;
}
} // namespace Opm

Some files were not shown because too many files have changed in this diff Show More