mirror of
https://github.com/OPM/opm-simulators.git
synced 2024-07-07 04:53:03 -05:00
remove files
This commit is contained in:
parent
78158bf44a
commit
f027262ec4
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
|
@ -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 */
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
@ -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 */
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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
|
||||
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
@ -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
|
|
@ -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
|
||||
|
|
@ -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
|
|
@ -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
|
||||
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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_ */
|
|
@ -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
|
|
@ -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_ */
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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_ */
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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), 685–696.
|
||||
// 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
|
||||
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
@ -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
|
|
@ -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
|
||||
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
Loading…
Reference in New Issue
Block a user