mirror of
https://github.com/OPM/opm-simulators.git
synced 2025-02-25 18:55:30 -06:00
Import the remaining code from opm-core
This commit is contained in:
commit
c03a980199
131
examples/compute_eikonal_from_files.cpp
Normal file
131
examples/compute_eikonal_from_files.cpp
Normal file
@ -0,0 +1,131 @@
|
||||
/*
|
||||
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/core/grid.h>
|
||||
#include <opm/core/grid/GridManager.hpp>
|
||||
#include <opm/common/ErrorMacros.hpp>
|
||||
#include <opm/core/utility/StopWatch.hpp>
|
||||
#include <opm/core/utility/miscUtilities.hpp>
|
||||
#include <opm/core/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;
|
||||
}
|
174
examples/compute_initial_state.cpp
Normal file
174
examples/compute_initial_state.cpp
Normal file
@ -0,0 +1,174 @@
|
||||
/*
|
||||
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/core/grid.h>
|
||||
#include <opm/core/grid/GridManager.hpp>
|
||||
#include <opm/common/ErrorMacros.hpp>
|
||||
#include <opm/core/simulator/initStateEquil.hpp>
|
||||
#include <opm/core/utility/parameters/ParameterGroup.hpp>
|
||||
#include <opm/core/props/BlackoilPhases.hpp>
|
||||
#include <opm/core/props/phaseUsageFromDeck.hpp>
|
||||
#include <opm/core/simulator/BlackoilState.hpp>
|
||||
#include <opm/core/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 FluidSystems::BlackOil<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;
|
||||
}
|
213
examples/compute_tof_from_files.cpp
Normal file
213
examples/compute_tof_from_files.cpp
Normal file
@ -0,0 +1,213 @@
|
||||
/*
|
||||
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/grid.h>
|
||||
#include <opm/core/grid/GridManager.hpp>
|
||||
#include <opm/core/wells.h>
|
||||
#include <opm/core/wells/WellsManager.hpp>
|
||||
#include <opm/common/ErrorMacros.hpp>
|
||||
#include <opm/core/utility/SparseTable.hpp>
|
||||
#include <opm/core/utility/StopWatch.hpp>
|
||||
#include <opm/core/utility/miscUtilities.hpp>
|
||||
#include <opm/core/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;
|
||||
}
|
105
examples/diagnose_relperm.cpp
Normal file
105
examples/diagnose_relperm.cpp
Normal file
@ -0,0 +1,105 @@
|
||||
/*
|
||||
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/core/grid.h>
|
||||
#include <opm/core/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/core/utility/StopWatch.hpp>
|
||||
#include <opm/core/utility/miscUtilities.hpp>
|
||||
#include <opm/core/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;
|
||||
}
|
462
opm/core/flowdiagnostics/AnisotropicEikonal.cpp
Normal file
462
opm/core/flowdiagnostics/AnisotropicEikonal.cpp
Normal file
@ -0,0 +1,462 @@
|
||||
/*
|
||||
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/core/grid/GridUtilities.hpp>
|
||||
#include <opm/core/grid.h>
|
||||
#include <opm/core/utility/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
|
106
opm/core/flowdiagnostics/AnisotropicEikonal.hpp
Normal file
106
opm/core/flowdiagnostics/AnisotropicEikonal.hpp
Normal file
@ -0,0 +1,106 @@
|
||||
/*
|
||||
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/core/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
|
342
opm/core/flowdiagnostics/DGBasis.cpp
Normal file
342
opm/core/flowdiagnostics/DGBasis.cpp
Normal file
@ -0,0 +1,342 @@
|
||||
/*
|
||||
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/core/grid.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
|
259
opm/core/flowdiagnostics/DGBasis.hpp
Normal file
259
opm/core/flowdiagnostics/DGBasis.hpp
Normal file
@ -0,0 +1,259 @@
|
||||
/*
|
||||
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
|
226
opm/core/flowdiagnostics/FlowDiagnostics.cpp
Normal file
226
opm/core/flowdiagnostics/FlowDiagnostics.cpp
Normal file
@ -0,0 +1,226 @@
|
||||
/*
|
||||
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
|
103
opm/core/flowdiagnostics/FlowDiagnostics.hpp
Normal file
103
opm/core/flowdiagnostics/FlowDiagnostics.hpp
Normal file
@ -0,0 +1,103 @@
|
||||
/*
|
||||
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
|
806
opm/core/flowdiagnostics/TofDiscGalReorder.cpp
Normal file
806
opm/core/flowdiagnostics/TofDiscGalReorder.cpp
Normal file
@ -0,0 +1,806 @@
|
||||
/*
|
||||
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/grid/CellQuadrature.hpp>
|
||||
#include <opm/core/grid/FaceQuadrature.hpp>
|
||||
#include <opm/core/flowdiagnostics/TofDiscGalReorder.hpp>
|
||||
#include <opm/core/flowdiagnostics/DGBasis.hpp>
|
||||
#include <opm/core/grid.h>
|
||||
#include <opm/common/ErrorMacros.hpp>
|
||||
#include <opm/core/utility/SparseTable.hpp>
|
||||
#include <opm/core/utility/VelocityInterpolation.hpp>
|
||||
#include <opm/core/utility/parameters/ParameterGroup.hpp>
|
||||
#include <opm/core/linalg/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
|
190
opm/core/flowdiagnostics/TofDiscGalReorder.hpp
Normal file
190
opm/core/flowdiagnostics/TofDiscGalReorder.hpp
Normal file
@ -0,0 +1,190 @@
|
||||
/*
|
||||
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
|
453
opm/core/flowdiagnostics/TofReorder.cpp
Normal file
453
opm/core/flowdiagnostics/TofReorder.cpp
Normal file
@ -0,0 +1,453 @@
|
||||
/*
|
||||
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/core/grid.h>
|
||||
#include <opm/common/ErrorMacros.hpp>
|
||||
#include <opm/core/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
|
119
opm/core/flowdiagnostics/TofReorder.hpp
Normal file
119
opm/core/flowdiagnostics/TofReorder.hpp
Normal file
@ -0,0 +1,119 @@
|
||||
/*
|
||||
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
|
141
opm/core/linalg/LinearSolverFactory.cpp
Normal file
141
opm/core/linalg/LinearSolverFactory.cpp
Normal file
@ -0,0 +1,141 @@
|
||||
/*
|
||||
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/core/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
|
||||
|
101
opm/core/linalg/LinearSolverFactory.hpp
Normal file
101
opm/core/linalg/LinearSolverFactory.hpp
Normal file
@ -0,0 +1,101 @@
|
||||
/*
|
||||
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
|
45
opm/core/linalg/LinearSolverInterface.cpp
Normal file
45
opm/core/linalg/LinearSolverInterface.cpp
Normal file
@ -0,0 +1,45 @@
|
||||
/*
|
||||
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
|
||||
|
91
opm/core/linalg/LinearSolverInterface.hpp
Normal file
91
opm/core/linalg/LinearSolverInterface.hpp
Normal file
@ -0,0 +1,91 @@
|
||||
/*
|
||||
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
|
526
opm/core/linalg/LinearSolverIstl.cpp
Normal file
526
opm/core/linalg/LinearSolverIstl.cpp
Normal file
@ -0,0 +1,526 @@
|
||||
/*
|
||||
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
|
119
opm/core/linalg/LinearSolverIstl.hpp
Normal file
119
opm/core/linalg/LinearSolverIstl.hpp
Normal file
@ -0,0 +1,119 @@
|
||||
/*
|
||||
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/core/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
|
293
opm/core/linalg/LinearSolverPetsc.cpp
Normal file
293
opm/core/linalg/LinearSolverPetsc.cpp
Normal file
@ -0,0 +1,293 @@
|
||||
/*
|
||||
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"
|
||||
|
||||
#if HAVE_PETSC
|
||||
|
||||
#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
|
||||
|
||||
#endif // HAVE_PETSC
|
97
opm/core/linalg/LinearSolverPetsc.hpp
Normal file
97
opm/core/linalg/LinearSolverPetsc.hpp
Normal file
@ -0,0 +1,97 @@
|
||||
/*
|
||||
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/core/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
|
76
opm/core/linalg/LinearSolverUmfpack.cpp
Normal file
76
opm/core/linalg/LinearSolverUmfpack.cpp
Normal file
@ -0,0 +1,76 @@
|
||||
/*
|
||||
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
|
||||
|
79
opm/core/linalg/LinearSolverUmfpack.hpp
Normal file
79
opm/core/linalg/LinearSolverUmfpack.hpp
Normal file
@ -0,0 +1,79 @@
|
||||
/*
|
||||
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_LINEARSOLVERUMFPACK_HEADER_INCLUDED
|
||||
#define OPM_LINEARSOLVERUMFPACK_HEADER_INCLUDED
|
||||
|
||||
|
||||
#include <opm/core/linalg/LinearSolverInterface.hpp>
|
||||
|
||||
|
||||
namespace Opm
|
||||
{
|
||||
|
||||
|
||||
/// Concrete class encapsulating the UMFPACK direct linear solver.
|
||||
class LinearSolverUmfpack : public LinearSolverInterface
|
||||
{
|
||||
public:
|
||||
/// Default constructor.
|
||||
LinearSolverUmfpack();
|
||||
|
||||
/// Destructor.
|
||||
virtual ~LinearSolverUmfpack();
|
||||
|
||||
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 UMFPACK solver.
|
||||
virtual void setTolerance(const double /*tol*/);
|
||||
|
||||
/// Get tolerance for the linear solver.
|
||||
/// \param[out] tolerance value
|
||||
/// Not used for UMFPACK solver. Returns -1.
|
||||
virtual double getTolerance() const;
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
} // namespace Opm
|
||||
|
||||
|
||||
|
||||
#endif // OPM_LINEARSOLVERUMFPACK_HEADER_INCLUDED
|
691
opm/core/linalg/ParallelIstlInformation.hpp
Normal file
691
opm/core/linalg/ParallelIstlInformation.hpp
Normal file
@ -0,0 +1,691 @@
|
||||
/*
|
||||
Copyright 2014, 2015 Dr. Markus Blatt - HPC-Simulation-Software & Services
|
||||
Copyright 2014, 2015 Statoil ASA
|
||||
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_PARALLELISTLINFORMTION_HEADER_INCLUDED
|
||||
#define OPM_PARALLELISTLINFORMTION_HEADER_INCLUDED
|
||||
|
||||
|
||||
#include <opm/core/grid.h>
|
||||
#include <opm/common/ErrorMacros.hpp>
|
||||
#include <boost/any.hpp>
|
||||
#include <exception>
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <numeric>
|
||||
#include <type_traits>
|
||||
|
||||
#if HAVE_MPI && HAVE_DUNE_ISTL
|
||||
|
||||
#include <opm/common/utility/platform_dependent/disable_warnings.h>
|
||||
#include <mpi.h>
|
||||
#include <dune/istl/owneroverlapcopy.hh>
|
||||
#include <dune/common/parallel/interface.hh>
|
||||
#include <dune/common/parallel/communicator.hh>
|
||||
#include <dune/common/enumset.hh>
|
||||
#include <opm/common/utility/platform_dependent/reenable_warnings.h>
|
||||
|
||||
namespace Opm
|
||||
{
|
||||
namespace
|
||||
{
|
||||
|
||||
template<class T>
|
||||
struct is_tuple
|
||||
: std::integral_constant<bool, false>
|
||||
{};
|
||||
template<typename... T>
|
||||
struct is_tuple<std::tuple<T...> >
|
||||
: std::integral_constant<bool, true>
|
||||
{};
|
||||
}
|
||||
|
||||
/// \brief Class that encapsulates the parallelization information needed by the
|
||||
/// ISTL solvers.
|
||||
class ParallelISTLInformation
|
||||
{
|
||||
public:
|
||||
/// \brief The type of the parallel index set used.
|
||||
typedef Dune::OwnerOverlapCopyCommunication<int, int>::ParallelIndexSet ParallelIndexSet;
|
||||
/// \brief The type of the remote indices information used.
|
||||
typedef Dune::OwnerOverlapCopyCommunication<int, int>::RemoteIndices RemoteIndices;
|
||||
|
||||
/// \brief Constructs an empty parallel information object using MPI_COMM_WORLD
|
||||
ParallelISTLInformation()
|
||||
: indexSet_(new ParallelIndexSet),
|
||||
remoteIndices_(new RemoteIndices(*indexSet_, *indexSet_, MPI_COMM_WORLD)),
|
||||
communicator_(MPI_COMM_WORLD)
|
||||
{}
|
||||
/// \brief Constructs an empty parallel information object using a communicator.
|
||||
/// \param communicator The communicator to use.
|
||||
ParallelISTLInformation(MPI_Comm communicator)
|
||||
: indexSet_(new ParallelIndexSet),
|
||||
remoteIndices_(new RemoteIndices(*indexSet_, *indexSet_, communicator)),
|
||||
communicator_(communicator)
|
||||
{}
|
||||
/// \brief Constructs a parallel information object from the specified information.
|
||||
/// \param indexSet The parallel index set to use.
|
||||
/// \param remoteIndices The remote indices information to use.
|
||||
/// \param communicator The communicator to use.
|
||||
ParallelISTLInformation(const std::shared_ptr<ParallelIndexSet>& indexSet,
|
||||
const std::shared_ptr<RemoteIndices>& remoteIndices,
|
||||
MPI_Comm communicator)
|
||||
: indexSet_(indexSet), remoteIndices_(remoteIndices), communicator_(communicator)
|
||||
{}
|
||||
/// \brief Copy constructor.
|
||||
///
|
||||
/// The information will be shared by the the two objects.
|
||||
ParallelISTLInformation(const ParallelISTLInformation& other)
|
||||
: indexSet_(other.indexSet_), remoteIndices_(other.remoteIndices_),
|
||||
communicator_(other.communicator_)
|
||||
{}
|
||||
/// \brief Get a pointer to the underlying index set.
|
||||
std::shared_ptr<ParallelIndexSet> indexSet() const
|
||||
{
|
||||
return indexSet_;
|
||||
}
|
||||
/// \brief Get a pointer to the remote indices information.
|
||||
std::shared_ptr<RemoteIndices> remoteIndices() const
|
||||
{
|
||||
return remoteIndices_;
|
||||
}
|
||||
/// \brief Get the Collective MPI communicator that we use.
|
||||
Dune::CollectiveCommunication<MPI_Comm> communicator() const
|
||||
{
|
||||
return communicator_;
|
||||
}
|
||||
/// \brief Copy the information stored to the specified objects.
|
||||
/// \param[out] indexSet The object to store the index set in.
|
||||
/// \param[out] remoteIndices The object to store the remote indices information in.
|
||||
void copyValuesTo(ParallelIndexSet& indexSet, RemoteIndices& remoteIndices,
|
||||
std::size_t local_component_size = 0, std::size_t num_components = 1) const
|
||||
{
|
||||
ParallelIndexSet::GlobalIndex global_component_size = local_component_size;
|
||||
if ( num_components > 1 )
|
||||
{
|
||||
ParallelIndexSet::GlobalIndex max_gi = 0;
|
||||
// component the max global index
|
||||
for( auto i = indexSet_->begin(), end = indexSet_->end(); i != end; ++i )
|
||||
{
|
||||
max_gi = std::max(max_gi, i->global());
|
||||
}
|
||||
global_component_size = max_gi+1;
|
||||
global_component_size = communicator_.max(global_component_size);
|
||||
}
|
||||
indexSet.beginResize();
|
||||
IndexSetInserter<ParallelIndexSet> inserter(indexSet, global_component_size,
|
||||
local_component_size, num_components);
|
||||
std::for_each(indexSet_->begin(), indexSet_->end(), inserter);
|
||||
indexSet.endResize();
|
||||
remoteIndices.rebuild<false>();
|
||||
}
|
||||
/// \brief Communcate the dofs owned by us to the other process.
|
||||
///
|
||||
/// Afterwards all associated dofs will contain the same data.
|
||||
template<class T>
|
||||
void copyOwnerToAll (const T& source, T& dest) const
|
||||
{
|
||||
typedef Dune::Combine<Dune::EnumItem<Dune::OwnerOverlapCopyAttributeSet::AttributeSet,Dune::OwnerOverlapCopyAttributeSet::owner>,Dune::EnumItem<Dune::OwnerOverlapCopyAttributeSet::AttributeSet,Dune::OwnerOverlapCopyAttributeSet::overlap>,Dune::OwnerOverlapCopyAttributeSet::AttributeSet> OwnerOverlapSet;
|
||||
typedef Dune::EnumItem<Dune::OwnerOverlapCopyAttributeSet::AttributeSet,Dune::OwnerOverlapCopyAttributeSet::owner> OwnerSet;
|
||||
typedef Dune::Combine<OwnerOverlapSet, Dune::EnumItem<Dune::OwnerOverlapCopyAttributeSet::AttributeSet,Dune::OwnerOverlapCopyAttributeSet::copy>,Dune::OwnerOverlapCopyAttributeSet::AttributeSet> AllSet;
|
||||
OwnerSet sourceFlags;
|
||||
AllSet destFlags;
|
||||
Dune::Interface interface(communicator_);
|
||||
if( !remoteIndices_->isSynced() )
|
||||
{
|
||||
remoteIndices_->rebuild<false>();
|
||||
}
|
||||
interface.build(*remoteIndices_,sourceFlags,destFlags);
|
||||
Dune::BufferedCommunicator communicator;
|
||||
communicator.template build<T>(interface);
|
||||
communicator.template forward<CopyGatherScatter<T> >(source,dest);
|
||||
communicator.free();
|
||||
}
|
||||
template<class T>
|
||||
const std::vector<double>& updateOwnerMask(const T& container) const
|
||||
{
|
||||
if( ! indexSet_ )
|
||||
{
|
||||
OPM_THROW(std::runtime_error, "Trying to update owner mask without parallel information!");
|
||||
}
|
||||
if( static_cast<std::size_t>(container.size())!= ownerMask_.size() )
|
||||
{
|
||||
ownerMask_.resize(container.size(), 1.);
|
||||
for( auto i=indexSet_->begin(), end=indexSet_->end(); i!=end; ++i )
|
||||
{
|
||||
if (i->local().attribute()!=Dune::OwnerOverlapCopyAttributeSet::owner)
|
||||
{
|
||||
ownerMask_[i->local().local()] = 0.;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ownerMask_;
|
||||
}
|
||||
|
||||
/// \brief Get the owner Mask.
|
||||
///
|
||||
/// \return A vector with entries 0, and 1. 0 marks an index that we cannot
|
||||
/// compute correct results for. 1 marks an index that this process
|
||||
/// is responsible for and computes correct results in parallel.
|
||||
const std::vector<double>& getOwnerMask() const
|
||||
{
|
||||
return ownerMask_;
|
||||
}
|
||||
|
||||
/// \brief Compute one or more global reductions.
|
||||
///
|
||||
/// This function can either be used with a container, an operator, and an initial value
|
||||
/// to compute a reduction. Or with tuples of them to compute multiple reductions with only
|
||||
/// one global communication.
|
||||
/// The possible functors needed can be constructed with Opm::Reduction::makeGlobalMaxFunctor(),
|
||||
/// Opm::Reduction::makeLInfinityNormFunctor(),
|
||||
/// Opm::Reduction::makeGlobalMinFunctor(), and
|
||||
/// Opm::Reduction::makeGlobalSumFunctor().
|
||||
/// \tparam type of the container or the tuple of containers.
|
||||
/// \tparam tyoe of the operator or a tuple of operators, examples are e.g.
|
||||
/// Reduction::MaskIDOperator, Reduction::MaskToMinOperator,
|
||||
/// and Reduction::MaskToMaxOperator. Has to provide an operator() that takes three
|
||||
/// arguments (the last one is the mask value: 1 for a dof that we own, 0 otherwise),
|
||||
/// a method maskValue that takes a value and mask value, and localOperator that
|
||||
/// returns the underlying binary operator.
|
||||
/// \param container A container or tuple of containers.
|
||||
/// \param binaryOperator An operator doing the reduction of two values.
|
||||
/// \param value The initial value or a tuple of them.
|
||||
template<typename Container, typename BinaryOperator, typename T>
|
||||
void computeReduction(const Container& container, BinaryOperator binaryOperator,
|
||||
T& value) const
|
||||
{
|
||||
computeReduction(container, binaryOperator, value, is_tuple<Container>());
|
||||
}
|
||||
private:
|
||||
/// \brief compute the reductions for tuples.
|
||||
///
|
||||
/// This is a helper function to prepare for calling computeTupleReduction.
|
||||
template<typename Container, typename BinaryOperator, typename T>
|
||||
void computeReduction(const Container& container, BinaryOperator binaryOperator,
|
||||
T& value, std::integral_constant<bool,true>) const
|
||||
{
|
||||
computeTupleReduction(container, binaryOperator, value);
|
||||
}
|
||||
/// \brief compute the reductions for non-tuples.
|
||||
///
|
||||
/// This is a helper function to prepare for calling computeTupleReduction.
|
||||
template<typename Container, typename BinaryOperator, typename T>
|
||||
void computeReduction(const Container& container, BinaryOperator binaryOperator,
|
||||
T& value, std::integral_constant<bool,false>) const
|
||||
{
|
||||
std::tuple<const Container&> containers=std::tuple<const Container&>(container);
|
||||
auto values=std::make_tuple(value);
|
||||
auto operators=std::make_tuple(binaryOperator);
|
||||
computeTupleReduction(containers, operators, values);
|
||||
value=std::get<0>(values);
|
||||
}
|
||||
/// \brief Compute the reductions for tuples.
|
||||
template<typename... Containers, typename... BinaryOperators, typename... ReturnValues>
|
||||
void computeTupleReduction(const std::tuple<Containers...>& containers,
|
||||
std::tuple<BinaryOperators...>& operators,
|
||||
std::tuple<ReturnValues...>& values) const
|
||||
{
|
||||
static_assert(std::tuple_size<std::tuple<Containers...> >::value==
|
||||
std::tuple_size<std::tuple<BinaryOperators...> >::value,
|
||||
"We need the same number of containers and binary operators");
|
||||
static_assert(std::tuple_size<std::tuple<Containers...> >::value==
|
||||
std::tuple_size<std::tuple<ReturnValues...> >::value,
|
||||
"We need the same number of containers and return values");
|
||||
if( std::tuple_size<std::tuple<Containers...> >::value==0 )
|
||||
{
|
||||
return;
|
||||
}
|
||||
// Copy the initial values.
|
||||
std::tuple<ReturnValues...> init=values;
|
||||
updateOwnerMask(std::get<0>(containers));
|
||||
computeLocalReduction(containers, operators, values);
|
||||
std::vector<std::tuple<ReturnValues...> > receivedValues(communicator_.size());
|
||||
communicator_.allgather(&values, 1, &(receivedValues[0]));
|
||||
values=init;
|
||||
for( auto rvals=receivedValues.begin(), endvals=receivedValues.end(); rvals!=endvals;
|
||||
++rvals )
|
||||
{
|
||||
computeGlobalReduction(*rvals, operators, values);
|
||||
}
|
||||
}
|
||||
/// \brief TMP for computing the the global reduction after receiving the local ones.
|
||||
///
|
||||
/// End of recursion.
|
||||
template<int I=0, typename... BinaryOperators, typename... ReturnValues>
|
||||
typename std::enable_if<I == sizeof...(BinaryOperators), void>::type
|
||||
computeGlobalReduction(const std::tuple<ReturnValues...>&,
|
||||
std::tuple<BinaryOperators...>&,
|
||||
std::tuple<ReturnValues...>&) const
|
||||
{}
|
||||
/// \brief TMP for computing the the global reduction after receiving the local ones.
|
||||
template<int I=0, typename... BinaryOperators, typename... ReturnValues>
|
||||
typename std::enable_if<I !=sizeof...(BinaryOperators), void>::type
|
||||
computeGlobalReduction(const std::tuple<ReturnValues...>& receivedValues,
|
||||
std::tuple<BinaryOperators...>& operators,
|
||||
std::tuple<ReturnValues...>& values) const
|
||||
{
|
||||
auto& val=std::get<I>(values);
|
||||
val = std::get<I>(operators).localOperator()(val, std::get<I>(receivedValues));
|
||||
computeGlobalReduction<I+1>(receivedValues, operators, values);
|
||||
}
|
||||
/// \brief TMP for computing the the local reduction on the DOF that the process owns.
|
||||
///
|
||||
/// End of recursion.
|
||||
template<int I=0, typename... Containers, typename... BinaryOperators, typename... ReturnValues>
|
||||
typename std::enable_if<I==sizeof...(Containers), void>::type
|
||||
computeLocalReduction(const std::tuple<Containers...>&,
|
||||
std::tuple<BinaryOperators...>&,
|
||||
std::tuple<ReturnValues...>&) const
|
||||
{}
|
||||
/// \brief TMP for computing the the local reduction on the DOF that the process owns.
|
||||
template<int I=0, typename... Containers, typename... BinaryOperators, typename... ReturnValues>
|
||||
typename std::enable_if<I!=sizeof...(Containers), void>::type
|
||||
computeLocalReduction(const std::tuple<Containers...>& containers,
|
||||
std::tuple<BinaryOperators...>& operators,
|
||||
std::tuple<ReturnValues...>& values) const
|
||||
{
|
||||
const auto& container = std::get<I>(containers);
|
||||
if( container.size() )
|
||||
{
|
||||
auto& reduceOperator = std::get<I>(operators);
|
||||
// Eigen:Block does not support STL iterators!!!!
|
||||
// Therefore we need to rely on the harder random-access
|
||||
// property of the containers. But this should be save, too.
|
||||
// Just commenting out code in the hope that Eigen might improve
|
||||
// in this regard in the future.
|
||||
//auto newVal = container.begin();
|
||||
auto mask = ownerMask_.begin();
|
||||
auto& value = std::get<I>(values);
|
||||
value = reduceOperator.getInitialValue();
|
||||
|
||||
for( auto endVal=ownerMask_.end(); mask!=endVal;
|
||||
/*++newVal,*/ ++mask )
|
||||
{
|
||||
value = reduceOperator(value, container[mask-ownerMask_.begin()], *mask);
|
||||
}
|
||||
}
|
||||
computeLocalReduction<I+1>(containers, operators, values);
|
||||
}
|
||||
/** \brief gather/scatter callback for communcation */
|
||||
template<typename T>
|
||||
struct CopyGatherScatter
|
||||
{
|
||||
typedef typename Dune::CommPolicy<T>::IndexedType V;
|
||||
|
||||
static V gather(const T& a, std::size_t i)
|
||||
{
|
||||
return a[i];
|
||||
}
|
||||
|
||||
static void scatter(T& a, V v, std::size_t i)
|
||||
{
|
||||
a[i] = v;
|
||||
}
|
||||
};
|
||||
template<class T>
|
||||
class IndexSetInserter
|
||||
{
|
||||
public:
|
||||
typedef T ParallelIndexSet;
|
||||
typedef typename ParallelIndexSet::LocalIndex LocalIndex;
|
||||
typedef typename ParallelIndexSet::GlobalIndex GlobalIndex;
|
||||
|
||||
IndexSetInserter(ParallelIndexSet& indexSet, const GlobalIndex& component_size,
|
||||
std::size_t local_component_size, std::size_t num_components)
|
||||
: indexSet_(&indexSet), component_size_(component_size),
|
||||
local_component_size_(local_component_size),
|
||||
num_components_(num_components)
|
||||
{}
|
||||
void operator()(const typename ParallelIndexSet::IndexPair& pair)
|
||||
{
|
||||
for(std::size_t i = 0; i < num_components_; i++)
|
||||
indexSet_->add(i * component_size_ + pair.global(),
|
||||
LocalIndex(i * local_component_size_ + pair.local(),
|
||||
pair.local().attribute()));
|
||||
}
|
||||
private:
|
||||
ParallelIndexSet* indexSet_;
|
||||
/// \brief The global number of unknowns per component/equation.
|
||||
GlobalIndex component_size_;
|
||||
/// \brief The local number of unknowns per component/equation.
|
||||
std::size_t local_component_size_;
|
||||
/// \brief The number of components/equations.
|
||||
std::size_t num_components_;
|
||||
};
|
||||
std::shared_ptr<ParallelIndexSet> indexSet_;
|
||||
std::shared_ptr<RemoteIndices> remoteIndices_;
|
||||
Dune::CollectiveCommunication<MPI_Comm> communicator_;
|
||||
mutable std::vector<double> ownerMask_;
|
||||
};
|
||||
|
||||
namespace Reduction
|
||||
{
|
||||
/// \brief An operator that only uses values where mask is 1.
|
||||
///
|
||||
/// Could be used to compute a global sum
|
||||
/// \tparam BinaryOperator The wrapped binary operator that specifies
|
||||
// the reduction operation.
|
||||
template<typename BinaryOperator>
|
||||
struct MaskIDOperator
|
||||
{
|
||||
// This is a real nice one: numeric limits needs a type without const
|
||||
// or reference qualifier. Otherwise we get complete nonesense.
|
||||
typedef typename std::remove_cv<
|
||||
typename std::remove_reference<typename BinaryOperator::result_type>::type
|
||||
>::type Result;
|
||||
/// \brief Apply the underlying binary operator according to the mask.
|
||||
///
|
||||
/// The BinaryOperator will be called with t1, and mask*t2.
|
||||
/// \param t1 first value
|
||||
/// \param t2 second value (might be modified).
|
||||
/// \param mask The mask (0 or 1).
|
||||
template<class T, class T1>
|
||||
T operator()(const T& t1, const T& t2, const T1& mask)
|
||||
{
|
||||
return b_(t1, maskValue(t2, mask));
|
||||
}
|
||||
template<class T, class T1>
|
||||
T maskValue(const T& t, const T1& mask)
|
||||
{
|
||||
return t*mask;
|
||||
}
|
||||
BinaryOperator& localOperator()
|
||||
{
|
||||
return b_;
|
||||
}
|
||||
Result getInitialValue()
|
||||
{
|
||||
return Result();
|
||||
}
|
||||
private:
|
||||
BinaryOperator b_;
|
||||
};
|
||||
|
||||
/// \brief An operator for computing a parallel inner product.
|
||||
template<class T>
|
||||
struct InnerProductFunctor
|
||||
{
|
||||
/// \brief Apply the underlying binary operator according to the mask.
|
||||
///
|
||||
/// The BinaryOperator will be called with t1, and mask*t2.
|
||||
/// \param t1 first value
|
||||
/// \param t2 second value (might be modified).
|
||||
/// \param mask The mask (0 or 1).
|
||||
template<class T1>
|
||||
T operator()(const T& t1, const T& t2, const T1& mask)
|
||||
{
|
||||
T masked = maskValue(t2, mask);
|
||||
return t1 + masked * masked;
|
||||
}
|
||||
template<class T1>
|
||||
T maskValue(const T& t, const T1& mask)
|
||||
{
|
||||
return t*mask;
|
||||
}
|
||||
std::plus<T> localOperator()
|
||||
{
|
||||
return std::plus<T>();
|
||||
}
|
||||
T getInitialValue()
|
||||
{
|
||||
return T();
|
||||
}
|
||||
};
|
||||
|
||||
/// \brief An operator that converts the values where mask is 0 to the minimum value
|
||||
///
|
||||
/// Could be used to compute a global maximum.
|
||||
/// \tparam BinaryOperator The wrapped binary operator that specifies
|
||||
// the reduction operation.
|
||||
template<typename BinaryOperator>
|
||||
struct MaskToMinOperator
|
||||
{
|
||||
// This is a real nice one: numeric limits has to a type without const
|
||||
// or reference. Otherwise we get complete nonesense.
|
||||
typedef typename std::remove_reference<
|
||||
typename std::remove_const<typename BinaryOperator::result_type>::type
|
||||
>::type Result;
|
||||
|
||||
MaskToMinOperator(BinaryOperator b)
|
||||
: b_(b)
|
||||
{}
|
||||
/// \brief Apply the underlying binary operator according to the mask.
|
||||
///
|
||||
/// If mask is 0 then t2 will be substituted by the lowest value,
|
||||
/// else t2 will be used.
|
||||
/// \param t1 first value
|
||||
/// \param t2 second value (might be modified).
|
||||
template<class T, class T1>
|
||||
T operator()(const T& t1, const T& t2, const T1& mask)
|
||||
{
|
||||
return b_(t1, maskValue(t2, mask));
|
||||
}
|
||||
template<class T, class T1>
|
||||
T maskValue(const T& t, const T1& mask)
|
||||
{
|
||||
if( mask )
|
||||
{
|
||||
return t;
|
||||
}
|
||||
else
|
||||
{
|
||||
return getInitialValue();
|
||||
}
|
||||
}
|
||||
Result getInitialValue()
|
||||
{
|
||||
//g++-4.4 does not support std::numeric_limits<T>::lowest();
|
||||
// we rely on IEE 754 for floating point values and use min()
|
||||
// for integral types.
|
||||
if( std::is_integral<Result>::value )
|
||||
{
|
||||
return std::numeric_limits<Result>::min();
|
||||
}
|
||||
else
|
||||
{
|
||||
return -std::numeric_limits<Result>::max();
|
||||
}
|
||||
}
|
||||
/// \brief Get the underlying binary operator.
|
||||
///
|
||||
/// This might be needed to compute the reduction after each processor
|
||||
/// has computed its local one.
|
||||
BinaryOperator& localOperator()
|
||||
{
|
||||
return b_;
|
||||
}
|
||||
private:
|
||||
BinaryOperator b_;
|
||||
};
|
||||
|
||||
/// \brief An operator that converts the values where mask is 0 to the maximum value
|
||||
///
|
||||
/// Could be used to compute a global minimum.
|
||||
template<typename BinaryOperator>
|
||||
struct MaskToMaxOperator
|
||||
{
|
||||
// This is a real nice one: numeric limits has to a type without const
|
||||
// or reference. Otherwise we get complete nonesense.
|
||||
typedef typename std::remove_cv<
|
||||
typename std::remove_reference<typename BinaryOperator::result_type>::type
|
||||
>::type Result;
|
||||
|
||||
MaskToMaxOperator(BinaryOperator b)
|
||||
: b_(b)
|
||||
{}
|
||||
/// \brief Apply the underlying binary operator according to the mask.
|
||||
///
|
||||
/// If mask is 0 then t2 will be substituted by the maximum value,
|
||||
/// else t2 will be used.
|
||||
/// \param t1 first value
|
||||
/// \param t2 second value (might be modified).
|
||||
template<class T, class T1>
|
||||
T operator()(const T& t1, const T& t2, const T1& mask)
|
||||
{
|
||||
return b_(t1, maskValue(t2, mask));
|
||||
}
|
||||
template<class T, class T1>
|
||||
T maskValue(const T& t, const T1& mask)
|
||||
{
|
||||
if( mask )
|
||||
{
|
||||
return t;
|
||||
}
|
||||
else
|
||||
{
|
||||
return std::numeric_limits<T>::max();
|
||||
}
|
||||
}
|
||||
BinaryOperator& localOperator()
|
||||
{
|
||||
return b_;
|
||||
}
|
||||
Result getInitialValue()
|
||||
{
|
||||
return std::numeric_limits<Result>::max();
|
||||
}
|
||||
private:
|
||||
BinaryOperator b_;
|
||||
};
|
||||
/// \brief Create a functor for computing a global sum.
|
||||
///
|
||||
/// To be used with ParallelISTLInformation::computeReduction.
|
||||
template<class T>
|
||||
MaskIDOperator<std::plus<T> >
|
||||
makeGlobalSumFunctor()
|
||||
{
|
||||
return MaskIDOperator<std::plus<T> >();
|
||||
}
|
||||
/// \brief Create a functor for computing a global maximum.
|
||||
///
|
||||
/// To be used with ParallelISTLInformation::computeReduction.
|
||||
template<class T>
|
||||
MaskToMinOperator<std::pointer_to_binary_function<const T&,const T&,const T&> >
|
||||
makeGlobalMaxFunctor()
|
||||
{
|
||||
return MaskToMinOperator<std::pointer_to_binary_function<const T&,const T&,const T&> >
|
||||
(std::pointer_to_binary_function<const T&,const T&,const T&>
|
||||
((const T&(*)(const T&, const T&))std::max<T>));
|
||||
}
|
||||
|
||||
namespace detail
|
||||
{
|
||||
/// \brief Computes the maximum of the absolute values of two values.
|
||||
template<typename T, typename Enable = void>
|
||||
struct MaxAbsFunctor
|
||||
{
|
||||
using result_type = T;
|
||||
result_type operator()(const T& t1,
|
||||
const T& t2)
|
||||
{
|
||||
return std::max(std::abs(t1), std::abs(t2));
|
||||
}
|
||||
};
|
||||
|
||||
// Specialization for unsigned integers. They need their own
|
||||
// version since abs(x) is ambiguous (as well as somewhat
|
||||
// meaningless).
|
||||
template<typename T>
|
||||
struct MaxAbsFunctor<T, typename std::enable_if<std::is_unsigned<T>::value>::type>
|
||||
{
|
||||
using result_type = T;
|
||||
result_type operator()(const T& t1,
|
||||
const T& t2)
|
||||
{
|
||||
return std::max(t1, t2);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// \brief Create a functor for computing a global L infinity norm
|
||||
///
|
||||
/// To be used with ParallelISTLInformation::computeReduction.
|
||||
template<class T>
|
||||
MaskIDOperator<detail::MaxAbsFunctor<T> >
|
||||
makeLInfinityNormFunctor()
|
||||
{
|
||||
return MaskIDOperator<detail::MaxAbsFunctor<T> >();
|
||||
}
|
||||
/// \brief Create a functor for computing a global minimum.
|
||||
///
|
||||
/// To be used with ParallelISTLInformation::computeReduction.
|
||||
template<class T>
|
||||
MaskToMaxOperator<std::pointer_to_binary_function<const T&,const T&,const T&> >
|
||||
makeGlobalMinFunctor()
|
||||
{
|
||||
return MaskToMaxOperator<std::pointer_to_binary_function<const T&,const T&,const T&> >
|
||||
(std::pointer_to_binary_function<const T&,const T&,const T&>
|
||||
((const T&(*)(const T&, const T&))std::min<T>));
|
||||
}
|
||||
template<class T>
|
||||
InnerProductFunctor<T>
|
||||
makeInnerProductFunctor()
|
||||
{
|
||||
return InnerProductFunctor<T>();
|
||||
}
|
||||
} // end namespace Reduction
|
||||
} // end namespace Opm
|
||||
|
||||
#endif
|
||||
|
||||
namespace Opm
|
||||
{
|
||||
/// \brief Extracts the information about the data decomposition from the grid for dune-istl
|
||||
///
|
||||
/// In the case that grid is a parallel grid this method will query it to get the information
|
||||
/// about the data decompoisition and convert it to the format expected by the linear algebra
|
||||
/// of dune-istl.
|
||||
/// \warn for UnstructuredGrid this function doesn't do anything.
|
||||
/// \param anyComm The handle to store the information in. If grid is a parallel grid
|
||||
/// then this will ecapsulate an instance of ParallelISTLInformation.
|
||||
/// \param grid The grid to inspect.
|
||||
|
||||
inline void extractParallelGridInformationToISTL(boost::any& anyComm, const UnstructuredGrid& grid)
|
||||
{
|
||||
(void)anyComm; (void)grid;
|
||||
}
|
||||
|
||||
/// \brief Accumulates entries masked with 1.
|
||||
/// \param container The container whose values to accumulate.
|
||||
/// \param maskContainer null pointer or a pointer to a container
|
||||
/// with entries 0 and 1. Only values at indices with a 1 stored
|
||||
/// will be accumulated. If null then all values will be accumulated
|
||||
/// \return the summ of all entries that should be represented.
|
||||
template<class T1>
|
||||
auto
|
||||
accumulateMaskedValues(const T1& container, const std::vector<double>* maskContainer)
|
||||
-> decltype(container[0]*(*maskContainer)[0])
|
||||
{
|
||||
decltype(container[0]*(*maskContainer)[0]) initial = 0;
|
||||
|
||||
if( maskContainer )
|
||||
{
|
||||
return std::inner_product(container.begin(), container.end(), maskContainer->begin(),
|
||||
initial);
|
||||
}else
|
||||
{
|
||||
return std::accumulate(container.begin(), container.end(), initial);
|
||||
}
|
||||
}
|
||||
} // end namespace Opm
|
||||
|
||||
#endif
|
199
opm/core/linalg/call_umfpack.c
Normal file
199
opm/core/linalg/call_umfpack.c
Normal file
@ -0,0 +1,199 @@
|
||||
/*===========================================================================
|
||||
//
|
||||
// File: call_umfpack.c
|
||||
//
|
||||
// Created: 2011-10-05 19:55:34+0200
|
||||
//
|
||||
// Authors: Ingeborg S. Ligaarden <Ingeborg.Ligaarden@sintef.no>
|
||||
// Jostein R. Natvig <Jostein.R.Natvig@sintef.no>
|
||||
// Halvor M. Nilsen <HalvorMoll.Nilsen@sintef.no>
|
||||
// Atgeirr F. Rasmussen <atgeirr@sintef.no>
|
||||
// Bård Skaflestad <Bard.Skaflestad@sintef.no>
|
||||
//
|
||||
//==========================================================================*/
|
||||
|
||||
|
||||
/*
|
||||
Copyright 2011 SINTEF ICT, Applied Mathematics.
|
||||
Copyright 2011 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"
|
||||
|
||||
#if HAVE_UMFPACK
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <umfpack.h>
|
||||
|
||||
#include <opm/core/linalg/sparse_sys.h>
|
||||
#include <opm/core/linalg/call_umfpack.h>
|
||||
|
||||
/* To handle new versions of SuiteSparse. */
|
||||
#ifndef UF_long
|
||||
#define UF_long SuiteSparse_long
|
||||
#endif
|
||||
|
||||
struct CSCMatrix {
|
||||
UF_long n;
|
||||
UF_long nnz;
|
||||
|
||||
UF_long *p;
|
||||
UF_long *i;
|
||||
double *x;
|
||||
};
|
||||
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
static void
|
||||
csc_deallocate(struct CSCMatrix *csc)
|
||||
/* ---------------------------------------------------------------------- */
|
||||
{
|
||||
if (csc != NULL) {
|
||||
free(csc->x);
|
||||
free(csc->i);
|
||||
free(csc->p);
|
||||
}
|
||||
|
||||
free(csc);
|
||||
}
|
||||
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
static struct CSCMatrix *
|
||||
csc_allocate(UF_long n, UF_long nnz)
|
||||
/* ---------------------------------------------------------------------- */
|
||||
{
|
||||
struct CSCMatrix *new;
|
||||
|
||||
new = malloc(1 * sizeof *new);
|
||||
|
||||
if (new != NULL) {
|
||||
new->p = malloc((n + 1) * sizeof *new->p);
|
||||
new->i = malloc(nnz * sizeof *new->i);
|
||||
new->x = malloc(nnz * sizeof *new->x);
|
||||
|
||||
if ((new->p == NULL) || (new->i == NULL) || (new->x == NULL)) {
|
||||
csc_deallocate(new);
|
||||
new = NULL;
|
||||
} else {
|
||||
new->n = n;
|
||||
new->nnz = nnz;
|
||||
}
|
||||
}
|
||||
|
||||
return new;
|
||||
}
|
||||
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
static void
|
||||
csr_to_csc(const int *ia,
|
||||
const int *ja,
|
||||
const double *sa,
|
||||
struct CSCMatrix *csc)
|
||||
/* ---------------------------------------------------------------------- */
|
||||
{
|
||||
UF_long i, nz;
|
||||
|
||||
/* Clear garbage, prepare for counting */
|
||||
for (i = 0; i <= csc->n; i++) { csc->p[i] = 0; }
|
||||
|
||||
/* Count column connections */
|
||||
for (nz = 0; nz < csc->nnz; nz++) {
|
||||
csc->p[ ja[nz] + 1 ] += 1;
|
||||
}
|
||||
|
||||
/* Define column start pointers */
|
||||
for (i = 1; i <= csc->n; i++) {
|
||||
csc->p[0] += csc->p[i];
|
||||
csc->p[i] = csc->p[0] - csc->p[i];
|
||||
}
|
||||
|
||||
assert (csc->p[0] == csc->nnz);
|
||||
|
||||
/* Fill matrix whilst defining column end pointers */
|
||||
for (i = nz = 0; i < csc->n; i++) {
|
||||
for (; nz < ia[i + 1]; nz++) {
|
||||
csc->i[ csc->p[ ja[nz] + 1 ] ] = i; /* Insertion sort */
|
||||
csc->x[ csc->p[ ja[nz] + 1 ] ] = sa[nz]; /* Insert mat elem */
|
||||
|
||||
csc->p [ ja[nz] + 1 ] += 1; /* Advance col ptr */
|
||||
}
|
||||
}
|
||||
|
||||
assert (csc->p[csc->n] == csc->nnz);
|
||||
|
||||
csc->p[0] = 0;
|
||||
}
|
||||
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
static void
|
||||
solve_umfpack(struct CSCMatrix *csc, const double *b, double *x)
|
||||
/* ---------------------------------------------------------------------- */
|
||||
{
|
||||
void *Symbolic, *Numeric;
|
||||
double Info[UMFPACK_INFO], Control[UMFPACK_CONTROL];
|
||||
|
||||
umfpack_dl_defaults(Control);
|
||||
|
||||
umfpack_dl_symbolic(csc->n, csc->n, csc->p, csc->i, csc->x,
|
||||
&Symbolic, Control, Info);
|
||||
umfpack_dl_numeric (csc->p, csc->i, csc->x,
|
||||
Symbolic, &Numeric, Control, Info);
|
||||
|
||||
umfpack_dl_free_symbolic(&Symbolic);
|
||||
|
||||
umfpack_dl_solve(UMFPACK_A, csc->p, csc->i, csc->x, x, b,
|
||||
Numeric, Control, Info);
|
||||
|
||||
umfpack_dl_free_numeric(&Numeric);
|
||||
}
|
||||
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
void
|
||||
call_UMFPACK(struct CSRMatrix *A, const double *b, double *x)
|
||||
/*---------------------------------------------------------------------------*/
|
||||
{
|
||||
struct CSCMatrix *csc;
|
||||
|
||||
csc = csc_allocate(A->m, A->ia[A->m]);
|
||||
|
||||
if (csc != NULL) {
|
||||
csr_to_csc(A->ia, A->ja, A->sa, csc);
|
||||
|
||||
solve_umfpack(csc, b, x);
|
||||
}
|
||||
|
||||
csc_deallocate(csc);
|
||||
}
|
||||
|
||||
#else
|
||||
#include <stdlib.h>
|
||||
#include <opm/core/linalg/call_umfpack.h>
|
||||
|
||||
void
|
||||
call_UMFPACK(struct CSRMatrix *A, const double *b, double *x)
|
||||
{
|
||||
/* UMFPACK is not available */
|
||||
abort();
|
||||
}
|
||||
|
||||
#endif
|
49
opm/core/linalg/call_umfpack.h
Normal file
49
opm/core/linalg/call_umfpack.h
Normal file
@ -0,0 +1,49 @@
|
||||
/*===========================================================================
|
||||
//
|
||||
// File: call_umfpack.h
|
||||
//
|
||||
// Created: 2011-10-05 19:55:58+0200
|
||||
//
|
||||
// Authors: Ingeborg S. Ligaarden <Ingeborg.Ligaarden@sintef.no>
|
||||
// Jostein R. Natvig <Jostein.R.Natvig@sintef.no>
|
||||
// Halvor M. Nilsen <HalvorMoll.Nilsen@sintef.no>
|
||||
// Atgeirr F. Rasmussen <atgeirr@sintef.no>
|
||||
// Bård Skaflestad <Bard.Skaflestad@sintef.no>
|
||||
//
|
||||
//==========================================================================*/
|
||||
|
||||
|
||||
/*
|
||||
Copyright 2011 SINTEF ICT, Applied Mathematics.
|
||||
Copyright 2011 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_CALL_UMFPACK_H_HEADER
|
||||
#define OPM_CALL_UMFPACK_H_HEADER
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct CSRMatrix;
|
||||
|
||||
void call_UMFPACK(struct CSRMatrix *A, const double *b, double *x);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif /* OPM_CALL_UMFPACK_H_HEADER */
|
264
opm/core/linalg/sparse_sys.c
Normal file
264
opm/core/linalg/sparse_sys.c
Normal file
@ -0,0 +1,264 @@
|
||||
/*
|
||||
Copyright 2010 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 <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <opm/core/linalg/sparse_sys.h>
|
||||
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
struct CSRMatrix *
|
||||
csrmatrix_new_count_nnz(size_t m)
|
||||
/* ---------------------------------------------------------------------- */
|
||||
{
|
||||
size_t i;
|
||||
struct CSRMatrix *new;
|
||||
|
||||
assert (m > 0);
|
||||
|
||||
new = malloc(1 * sizeof *new);
|
||||
if (new != NULL) {
|
||||
new->ia = malloc((m + 1) * sizeof *new->ia);
|
||||
|
||||
if (new->ia != NULL) {
|
||||
for (i = 0; i < m + 1; i++) { new->ia[i] = 0; }
|
||||
|
||||
new->m = m;
|
||||
new->nnz = 0;
|
||||
|
||||
new->ja = NULL;
|
||||
new->sa = NULL;
|
||||
} else {
|
||||
csrmatrix_delete(new);
|
||||
new = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return new;
|
||||
}
|
||||
|
||||
|
||||
/* Allocate CSR matrix, known nnz. Allocation only. Caller must
|
||||
* build sparsity structure before using in global assembly.
|
||||
*
|
||||
* Returns fully allocated structure if successful and NULL otherwise. */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
struct CSRMatrix *
|
||||
csrmatrix_new_known_nnz(size_t m, size_t nnz)
|
||||
/* ---------------------------------------------------------------------- */
|
||||
{
|
||||
struct CSRMatrix *new;
|
||||
|
||||
new = malloc(1 * sizeof *new);
|
||||
|
||||
if (new != NULL) {
|
||||
new->ia = malloc((m + 1) * sizeof *new->ia);
|
||||
new->ja = malloc(nnz * sizeof *new->ja);
|
||||
new->sa = malloc(nnz * sizeof *new->sa);
|
||||
|
||||
if ((new->ia == NULL) || (new->ja == NULL) || (new->sa == NULL)) {
|
||||
csrmatrix_delete(new);
|
||||
new = NULL;
|
||||
} else {
|
||||
new->m = m;
|
||||
new->nnz = nnz;
|
||||
}
|
||||
}
|
||||
|
||||
return new;
|
||||
}
|
||||
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
size_t
|
||||
csrmatrix_new_elms_pushback(struct CSRMatrix *A)
|
||||
/* ---------------------------------------------------------------------- */
|
||||
{
|
||||
size_t i;
|
||||
|
||||
assert (A->ia[0] == 0); /* Elems for row 'i' in bin i+1 ... */
|
||||
|
||||
for (i = 1; i <= A->m; i++) {
|
||||
A->ia[0] += A->ia[i];
|
||||
A->ia[i] = A->ia[0] - A->ia[i];
|
||||
}
|
||||
|
||||
A->nnz = A->ia[0];
|
||||
assert (A->nnz > 0); /* Else not a real system. */
|
||||
|
||||
A->ia[0] = 0;
|
||||
|
||||
A->ja = malloc(A->nnz * sizeof *A->ja);
|
||||
A->sa = malloc(A->nnz * sizeof *A->sa);
|
||||
|
||||
if ((A->ja == NULL) || (A->sa == NULL)) {
|
||||
free(A->sa); A->sa = NULL;
|
||||
free(A->ja); A->ja = NULL;
|
||||
|
||||
A->nnz = 0;
|
||||
}
|
||||
|
||||
return A->nnz;
|
||||
}
|
||||
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
static int
|
||||
cmp_row_elems(const void *a0, const void *b0)
|
||||
/* ---------------------------------------------------------------------- */
|
||||
{
|
||||
return *(const int * const)a0 - *(const int * const)b0;
|
||||
}
|
||||
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
void
|
||||
csrmatrix_sortrows(struct CSRMatrix *A)
|
||||
/* ---------------------------------------------------------------------- */
|
||||
{
|
||||
size_t i;
|
||||
|
||||
/* O(A->nnz * log(average nnz per row)) \approx O(A->nnz) */
|
||||
for (i = 0; i < A->m; i++) {
|
||||
qsort(A->ja + A->ia[i] ,
|
||||
A->ia[i + 1] - A->ia[i] ,
|
||||
sizeof A->ja [A->ia[i]],
|
||||
cmp_row_elems);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
size_t
|
||||
csrmatrix_elm_index(int i, int j, const struct CSRMatrix *A)
|
||||
/* ---------------------------------------------------------------------- */
|
||||
{
|
||||
int *p;
|
||||
|
||||
p = bsearch(&j, A->ja + A->ia[i], A->ia[i + 1] - A->ia[i],
|
||||
sizeof A->ja[A->ia[i]], cmp_row_elems);
|
||||
|
||||
assert (p != NULL);
|
||||
|
||||
return p - A->ja;
|
||||
}
|
||||
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
void
|
||||
csrmatrix_delete(struct CSRMatrix *A)
|
||||
/* ---------------------------------------------------------------------- */
|
||||
{
|
||||
if (A != NULL) {
|
||||
free(A->sa);
|
||||
free(A->ja);
|
||||
free(A->ia);
|
||||
}
|
||||
|
||||
free(A);
|
||||
}
|
||||
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
void
|
||||
csrmatrix_zero(struct CSRMatrix *A)
|
||||
/* ---------------------------------------------------------------------- */
|
||||
{
|
||||
vector_zero(A->nnz, A->sa);
|
||||
}
|
||||
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
/* v = zeros([n, 1]) */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
void
|
||||
vector_zero(size_t n, double *v)
|
||||
/* ---------------------------------------------------------------------- */
|
||||
{
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < n; i++) { v[i] = 0.0; }
|
||||
}
|
||||
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
void
|
||||
csrmatrix_write(const struct CSRMatrix *A, const char *fn)
|
||||
/* ---------------------------------------------------------------------- */
|
||||
{
|
||||
FILE *fp;
|
||||
|
||||
fp = fopen(fn, "wt");
|
||||
|
||||
if (fp != NULL) {
|
||||
csrmatrix_write_stream(A, fp);
|
||||
}
|
||||
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
void
|
||||
csrmatrix_write_stream(const struct CSRMatrix *A, FILE *fp)
|
||||
/* ---------------------------------------------------------------------- */
|
||||
{
|
||||
size_t i, j;
|
||||
for (i = j = 0; i < A->m; i++) {
|
||||
for (; j < (size_t) (A->ia[i + 1]); j++) {
|
||||
fprintf(fp, "%lu %lu %26.18e\n",
|
||||
(unsigned long) (i + 1),
|
||||
(unsigned long) (A->ja[j] + 1),
|
||||
A->sa[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
void
|
||||
vector_write(size_t n, const double *v, const char *fn)
|
||||
/* ---------------------------------------------------------------------- */
|
||||
{
|
||||
FILE *fp;
|
||||
|
||||
fp = fopen(fn, "wt");
|
||||
|
||||
if (fp != NULL) {
|
||||
vector_write_stream(n, v, fp);
|
||||
}
|
||||
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
void
|
||||
vector_write_stream(size_t n, const double *v, FILE *fp)
|
||||
/* ---------------------------------------------------------------------- */
|
||||
{
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < n; i++) {
|
||||
fprintf(fp, "%26.18e\n", v[i]);
|
||||
}
|
||||
}
|
252
opm/core/linalg/sparse_sys.h
Normal file
252
opm/core/linalg/sparse_sys.h
Normal file
@ -0,0 +1,252 @@
|
||||
/*
|
||||
Copyright 2010 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_SPARSE_SYS_HEADER_INCLUDED
|
||||
#define OPM_SPARSE_SYS_HEADER_INCLUDED
|
||||
|
||||
/**
|
||||
* \file
|
||||
* Data structure and operations to manage sparse matrices in CSR formats.
|
||||
*/
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Basic compressed-sparse row (CSR) matrix data structure.
|
||||
*/
|
||||
struct CSRMatrix
|
||||
{
|
||||
size_t m; /**< Number of rows */
|
||||
size_t nnz; /**< Number of structurally non-zero elements */
|
||||
|
||||
int *ia; /**< Row pointers */
|
||||
int *ja; /**< Column indices */
|
||||
|
||||
double *sa; /**< Matrix elements */
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Allocate a matrix structure and corresponding row pointers, @c ia,
|
||||
* sufficiently initialised to support "count and push-back"
|
||||
* construction scheme.
|
||||
*
|
||||
* The matrix will be fully formed in csrmatrix_new_elms_pushback().
|
||||
*
|
||||
* \param[in] m Number of matrix rows.
|
||||
*
|
||||
* \return Allocated matrix structure with allocated row pointers and
|
||||
* valid @c m field. The row pointer elements are initialised all
|
||||
* zero to simplify the non-zero element counting procedure. The
|
||||
* @c ja and @c sa fields are @c NULL. This function returns @c NULL
|
||||
* in case of allocation failure.
|
||||
*/
|
||||
struct CSRMatrix *
|
||||
csrmatrix_new_count_nnz(size_t m);
|
||||
|
||||
|
||||
/**
|
||||
* Allocate a matrix structure and all constituent fields to hold a
|
||||
* sparse matrix with a specified number of (structural) non-zero
|
||||
* elements.
|
||||
*
|
||||
* The contents of the individual matrix arrays is undefined. In
|
||||
* particular, the sparsity pattern must be constructed through some
|
||||
* other, external, means prior to using the matrix in (e.g.,) a
|
||||
* global system assembly process.
|
||||
*
|
||||
* The memory resources should be released through the
|
||||
* csrmatrix_delete() function.
|
||||
*
|
||||
* \param[in] m Number of matrix rows.
|
||||
* \param[in] nnz Number of structural non-zeros.
|
||||
*
|
||||
* \return Allocated matrix structure and constituent element arrays.
|
||||
* @c NULL in case of allocation failure.
|
||||
*/
|
||||
struct CSRMatrix *
|
||||
csrmatrix_new_known_nnz(size_t m, size_t nnz);
|
||||
|
||||
|
||||
/**
|
||||
* Set row pointers and allocate column index and matrix element
|
||||
* arrays of a matrix previous obtained from
|
||||
* csrmatrix_new_count_nnz().
|
||||
*
|
||||
* The memory resources should be released through the
|
||||
* csrmatrix_delete() function.
|
||||
*
|
||||
* This function assumes that, on input, the total number of
|
||||
* structurally non-zero elements of row @c i are stored in
|
||||
* <CODE>A->ia[i+1]</CODE> for all <CODE>i = 0, ..., A->m - 1</CODE>
|
||||
* and that <CODE>A->ia[0] == 0</CODE>. If successful, then on output
|
||||
* the row \em end pointers <CODE>A->ia[i+1]</CODE> are positioned at
|
||||
* the \em start of the corresponding rows. If not, then the
|
||||
* <CODE>A->ja</CODE> and <CODE>A->sa</CODE> arrays remain unallocated.
|
||||
*
|
||||
* \param[in,out] A Matrix.
|
||||
*
|
||||
* \return Total number of allocated non-zeros, <CODE>A->nnz ==
|
||||
* A->ia[A->m]</CODE> if successful and zero in case of allocation
|
||||
* failure.
|
||||
*/
|
||||
size_t
|
||||
csrmatrix_new_elms_pushback(struct CSRMatrix *A);
|
||||
|
||||
|
||||
/**
|
||||
* Compute non-zero index of specified matrix element.
|
||||
*
|
||||
* \param[in] i Row index.
|
||||
* \param[in] j Column index. Must be in the structural non-zero
|
||||
* element set of row @c i.
|
||||
* \param[in] A Matrix.
|
||||
*
|
||||
* \return Non-zero index, into @c A->ja and @c A->sa, of the
|
||||
* <CODE>(i,j)</CODE> matrix element.
|
||||
*/
|
||||
size_t
|
||||
csrmatrix_elm_index(int i, int j, const struct CSRMatrix *A);
|
||||
|
||||
|
||||
/**
|
||||
* Sort column indices within each matrix row in ascending order.
|
||||
*
|
||||
* The corresponding matrix elements (i.e., @c sa) are not referenced.
|
||||
* Consequently, following a call to csrmatrix_sortrows(), all
|
||||
* relations to any pre-existing matrix elements are lost and must be
|
||||
* rebuilt.
|
||||
*
|
||||
* After a call to csrmatrix_sortrows(), the following relation holds
|
||||
* <CODE>A->ja[k] < A->ja[k+1]</CODE> for all <CODE>k = A->ia[i], ...,
|
||||
* A->ia[i+1]-2</CODE> in each row <CODE>i = 0, ..., A->m - 1</CODE>.
|
||||
*
|
||||
* \param[in,out] A Matrix.
|
||||
*/
|
||||
void
|
||||
csrmatrix_sortrows(struct CSRMatrix *A);
|
||||
|
||||
|
||||
/**
|
||||
* Dispose of memory resources obtained through prior calls to
|
||||
* allocation routines.
|
||||
*
|
||||
* \param[in,out] A Matrix obtained from csrmatrix_new_count_nnz() +
|
||||
* csrmatrix_new_elms_pushback() or
|
||||
* csrmatrix_new_known_nnz().
|
||||
*
|
||||
* The pointer @c A is invalid following a call to csrmatrix_delete().
|
||||
*/
|
||||
void
|
||||
csrmatrix_delete(struct CSRMatrix *A);
|
||||
|
||||
|
||||
/**
|
||||
* Zero all matrix elements, typically in preparation of elemental
|
||||
* assembly.
|
||||
*
|
||||
* \param[in,out] A Matrix for which to zero the elements.
|
||||
*/
|
||||
void
|
||||
csrmatrix_zero(struct CSRMatrix *A);
|
||||
|
||||
|
||||
/**
|
||||
* Zero all vector elements.
|
||||
*
|
||||
* \param[in] n Number of vector elements.
|
||||
* \param[out] v Vector for which to zero the elements.
|
||||
*/
|
||||
void
|
||||
vector_zero(size_t n, double *v);
|
||||
|
||||
|
||||
/**
|
||||
* Print matrix to file.
|
||||
*
|
||||
* The matrix content is printed in coordinate format with row and
|
||||
* column indices ranging from @c 1 to @c A->m. This output format
|
||||
* facilitates simple processing through the @c spconvert function in
|
||||
* MATLAB© or Octave.
|
||||
*
|
||||
* This function is implemented in terms of csrmatrix_write_stream().
|
||||
*
|
||||
* \param[in] A Matrix.
|
||||
* \param[in] fn Name of file to which matrix contents will be output.
|
||||
*/
|
||||
void
|
||||
csrmatrix_write(const struct CSRMatrix *A, const char *fn);
|
||||
|
||||
|
||||
/**
|
||||
* Print matrix to stream.
|
||||
*
|
||||
* The matrix content is printed in coordinate format with row and
|
||||
* column indices ranging from @c 1 to @c A->m. This output format
|
||||
* facilitates simple processing through the @c spconvert function in
|
||||
* MATLAB© or Octave.
|
||||
*
|
||||
* \param[in] A Matrix.
|
||||
* \param[in,out] fp Open (text) stream to which matrix contents
|
||||
* will be output.
|
||||
*/
|
||||
void
|
||||
csrmatrix_write_stream(const struct CSRMatrix *A, FILE *fp);
|
||||
|
||||
|
||||
/**
|
||||
* Print vector to file.
|
||||
*
|
||||
* Elements are printed with one line (separated by <CODE>'\n'</CODE>)
|
||||
* per vector element.
|
||||
*
|
||||
* This function is implemented in terms of vector_write_stream().
|
||||
*
|
||||
* \param[in] n Number of vector elements.
|
||||
* \param[in] v Vector.
|
||||
* \param[in] fn Name of file to which vector contents will be output.
|
||||
*/
|
||||
void
|
||||
vector_write(size_t n, const double *v, const char *fn);
|
||||
|
||||
|
||||
/**
|
||||
* Print vector to stream.
|
||||
*
|
||||
* Elements are printed with one line (separated by <CODE>'\n'</CODE>)
|
||||
* per vector element.
|
||||
*
|
||||
* \param[in] n Number of vector elements.
|
||||
* \param[in] v Vector.
|
||||
* \param[in,out] fp Open (text) stream to which vector contents will be
|
||||
* output.
|
||||
*/
|
||||
void
|
||||
vector_write_stream(size_t n, const double *v, FILE *fp);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* OPM_SPARSE_SYS_HEADER_INCLUDED */
|
649
opm/core/pressure/CompressibleTpfa.cpp
Normal file
649
opm/core/pressure/CompressibleTpfa.cpp
Normal file
@ -0,0 +1,649 @@
|
||||
/*
|
||||
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/pressure/CompressibleTpfa.hpp>
|
||||
#include <opm/core/pressure/tpfa/cfs_tpfa_residual.h>
|
||||
#include <opm/core/pressure/tpfa/compr_quant_general.h>
|
||||
#include <opm/core/pressure/tpfa/compr_source.h>
|
||||
#include <opm/core/pressure/tpfa/trans_tpfa.h>
|
||||
#include <opm/core/linalg/LinearSolverInterface.hpp>
|
||||
#include <opm/core/linalg/sparse_sys.h>
|
||||
#include <opm/common/ErrorMacros.hpp>
|
||||
#include <opm/core/utility/miscUtilities.hpp>
|
||||
#include <opm/core/wells.h>
|
||||
#include <opm/core/simulator/BlackoilState.hpp>
|
||||
#include <opm/core/simulator/WellState.hpp>
|
||||
#include <opm/core/props/rock/RockCompressibility.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include <numeric>
|
||||
|
||||
namespace Opm
|
||||
{
|
||||
|
||||
|
||||
/// Construct solver.
|
||||
/// \param[in] grid A 2d or 3d grid.
|
||||
/// \param[in] props Rock and fluid properties.
|
||||
/// \param[in] linsolver Linear solver to use.
|
||||
/// \param[in] residual_tol Solution accepted if inf-norm of residual is smaller.
|
||||
/// \param[in] change_tol Solution accepted if inf-norm of change in pressure is smaller.
|
||||
/// \param[in] maxiter Maximum acceptable number of iterations.
|
||||
/// \param[in] gravity Gravity vector. If non-null, the array should
|
||||
/// have D elements.
|
||||
/// \param[in] wells The wells argument. Will be used in solution,
|
||||
/// is ignored if NULL.
|
||||
/// Note: this class observes the well object, and
|
||||
/// makes the assumption that the well topology
|
||||
/// and completions does not change during the
|
||||
/// run. However, controls (only) are allowed
|
||||
/// to change.
|
||||
CompressibleTpfa::CompressibleTpfa(const UnstructuredGrid& grid,
|
||||
const BlackoilPropertiesInterface& props,
|
||||
const RockCompressibility* rock_comp_props,
|
||||
const LinearSolverInterface& linsolver,
|
||||
const double residual_tol,
|
||||
const double change_tol,
|
||||
const int maxiter,
|
||||
const double* gravity,
|
||||
const struct Wells* wells)
|
||||
: grid_(grid),
|
||||
props_(props),
|
||||
rock_comp_props_(rock_comp_props),
|
||||
linsolver_(linsolver),
|
||||
residual_tol_(residual_tol),
|
||||
change_tol_(change_tol),
|
||||
maxiter_(maxiter),
|
||||
gravity_(gravity),
|
||||
wells_(wells),
|
||||
htrans_(grid.cell_facepos[ grid.number_of_cells ]),
|
||||
trans_ (grid.number_of_faces),
|
||||
allcells_(grid.number_of_cells),
|
||||
singular_(false)
|
||||
{
|
||||
if (wells_ && (wells_->number_of_phases != props.numPhases())) {
|
||||
OPM_THROW(std::runtime_error, "Inconsistent number of phases specified (wells vs. props): "
|
||||
<< wells_->number_of_phases << " != " << props.numPhases());
|
||||
}
|
||||
const int num_dofs = grid.number_of_cells + (wells ? wells->number_of_wells : 0);
|
||||
pressure_increment_.resize(num_dofs);
|
||||
UnstructuredGrid* gg = const_cast<UnstructuredGrid*>(&grid_);
|
||||
tpfa_htrans_compute(gg, props.permeability(), &htrans_[0]);
|
||||
tpfa_trans_compute(gg, &htrans_[0], &trans_[0]);
|
||||
// If we have rock compressibility, pore volumes are updated
|
||||
// in the compute*() methods, otherwise they are constant and
|
||||
// hence may be computed here.
|
||||
if (rock_comp_props_ == NULL || !rock_comp_props_->isActive()) {
|
||||
computePorevolume(grid_, props.porosity(), porevol_);
|
||||
}
|
||||
for (int c = 0; c < grid.number_of_cells; ++c) {
|
||||
allcells_[c] = c;
|
||||
}
|
||||
cfs_tpfa_res_wells w;
|
||||
w.W = const_cast<struct Wells*>(wells_);
|
||||
w.data = NULL;
|
||||
h_ = cfs_tpfa_res_construct(gg, &w, props.numPhases());
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// Destructor.
|
||||
CompressibleTpfa::~CompressibleTpfa()
|
||||
{
|
||||
cfs_tpfa_res_destroy(h_);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// Solve pressure equation, by Newton iterations.
|
||||
void CompressibleTpfa::solve(const double dt,
|
||||
BlackoilState& state,
|
||||
WellState& well_state)
|
||||
{
|
||||
const int nc = grid_.number_of_cells;
|
||||
const int nw = (wells_ != 0) ? wells_->number_of_wells : 0;
|
||||
|
||||
// Set up dynamic data.
|
||||
computePerSolveDynamicData(dt, state, well_state);
|
||||
computePerIterationDynamicData(dt, state, well_state);
|
||||
|
||||
// Assemble J and F.
|
||||
assemble(dt, state, well_state);
|
||||
|
||||
double inc_norm = 0.0;
|
||||
int iter = 0;
|
||||
double res_norm = residualNorm();
|
||||
std::cout << "\nIteration Residual Change in p\n"
|
||||
<< std::setw(9) << iter
|
||||
<< std::setw(18) << res_norm
|
||||
<< std::setw(18) << '*' << std::endl;
|
||||
while ((iter < maxiter_) && (res_norm > residual_tol_)) {
|
||||
// Solve for increment in Newton method:
|
||||
// incr = x_{n+1} - x_{n} = -J^{-1}F
|
||||
// (J is Jacobian matrix, F is residual)
|
||||
solveIncrement();
|
||||
++iter;
|
||||
|
||||
// Update pressure vars with increment.
|
||||
for (int c = 0; c < nc; ++c) {
|
||||
state.pressure()[c] += pressure_increment_[c];
|
||||
}
|
||||
for (int w = 0; w < nw; ++w) {
|
||||
well_state.bhp()[w] += pressure_increment_[nc + w];
|
||||
}
|
||||
|
||||
// Stop iterating if increment is small.
|
||||
inc_norm = incrementNorm();
|
||||
if (inc_norm <= change_tol_) {
|
||||
std::cout << std::setw(9) << iter
|
||||
<< std::setw(18) << '*'
|
||||
<< std::setw(18) << inc_norm << std::endl;
|
||||
break;
|
||||
}
|
||||
|
||||
// Set up dynamic data.
|
||||
computePerIterationDynamicData(dt, state, well_state);
|
||||
|
||||
// Assemble J and F.
|
||||
assemble(dt, state, well_state);
|
||||
|
||||
// Update residual norm.
|
||||
res_norm = residualNorm();
|
||||
|
||||
std::cout << std::setw(9) << iter
|
||||
<< std::setw(18) << res_norm
|
||||
<< std::setw(18) << inc_norm << std::endl;
|
||||
}
|
||||
|
||||
if ((iter == maxiter_) && (res_norm > residual_tol_) && (inc_norm > change_tol_)) {
|
||||
OPM_THROW(std::runtime_error, "CompressibleTpfa::solve() failed to converge in " << maxiter_ << " iterations.");
|
||||
}
|
||||
|
||||
std::cout << "Solved pressure in " << iter << " iterations." << std::endl;
|
||||
|
||||
// Compute fluxes and face pressures.
|
||||
computeResults(state, well_state);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/// @brief After solve(), was the resulting pressure singular.
|
||||
/// Returns true if the pressure is singular in the following
|
||||
/// sense: if everything is incompressible and there are no
|
||||
/// pressure conditions, the absolute values of the pressure
|
||||
/// solution are arbitrary. (But the differences in pressure
|
||||
/// are significant.)
|
||||
bool CompressibleTpfa::singularPressure() const
|
||||
{
|
||||
return singular_;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/// Compute well potentials.
|
||||
void CompressibleTpfa::computeWellPotentials(const BlackoilState& state)
|
||||
{
|
||||
if (wells_ == NULL) return;
|
||||
|
||||
const int nw = wells_->number_of_wells;
|
||||
const int np = props_.numPhases();
|
||||
const int nperf = wells_->well_connpos[nw];
|
||||
const int dim = grid_.dimensions;
|
||||
const double grav = gravity_ ? gravity_[dim - 1] : 0.0;
|
||||
wellperf_wdp_.clear();
|
||||
wellperf_wdp_.resize(nperf, 0.0);
|
||||
if (not (std::abs(grav) > 0.0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Temporary storage for perforation A matrices and densities.
|
||||
std::vector<double> A(np*np, 0.0);
|
||||
std::vector<double> rho(np, 0.0);
|
||||
|
||||
// Main loop, iterate over all perforations,
|
||||
// using the following formula (by phase):
|
||||
// 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.
|
||||
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];
|
||||
props_.matrix(1, &state.pressure()[cell], &state.temperature()[cell], &state.surfacevol()[np*cell], &cell, &A[0], 0);
|
||||
props_.density(1, &A[0], &cell, &rho[0]);
|
||||
for (int phase = 0; phase < np; ++phase) {
|
||||
const double s_phase = state.saturation()[np*cell + phase];
|
||||
wellperf_wdp_[j] += s_phase*rho[phase]*grav*(cell_depth - ref_depth);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// Compute per-solve dynamic properties.
|
||||
void CompressibleTpfa::computePerSolveDynamicData(const double /*dt*/,
|
||||
const BlackoilState& state,
|
||||
const WellState& /*well_state*/)
|
||||
{
|
||||
computeWellPotentials(state);
|
||||
if (rock_comp_props_ && rock_comp_props_->isActive()) {
|
||||
computePorevolume(grid_, props_.porosity(), *rock_comp_props_, state.pressure(), initial_porevol_);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// Compute per-iteration dynamic properties.
|
||||
void CompressibleTpfa::computePerIterationDynamicData(const double dt,
|
||||
const BlackoilState& state,
|
||||
const WellState& well_state)
|
||||
{
|
||||
// These are the variables that get computed by this function:
|
||||
//
|
||||
// std::vector<double> cell_A_;
|
||||
// std::vector<double> cell_dA_;
|
||||
// std::vector<double> cell_viscosity_;
|
||||
// std::vector<double> cell_phasemob_;
|
||||
// std::vector<double> cell_voldisc_;
|
||||
// std::vector<double> face_A_;
|
||||
// std::vector<double> face_phasemob_;
|
||||
// std::vector<double> face_gravcap_;
|
||||
// std::vector<double> wellperf_A_;
|
||||
// std::vector<double> wellperf_phasemob_;
|
||||
// std::vector<double> porevol_; // Only modified if rock_comp_props_ is non-null.
|
||||
// std::vector<double> rock_comp_; // Empty unless rock_comp_props_ is non-null.
|
||||
computeCellDynamicData(dt, state, well_state);
|
||||
computeFaceDynamicData(dt, state, well_state);
|
||||
computeWellDynamicData(dt, state, well_state);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/// Compute per-iteration dynamic properties for cells.
|
||||
void CompressibleTpfa::computeCellDynamicData(const double /*dt*/,
|
||||
const BlackoilState& state,
|
||||
const WellState& /*well_state*/)
|
||||
{
|
||||
// These are the variables that get computed by this function:
|
||||
//
|
||||
// std::vector<double> cell_A_;
|
||||
// std::vector<double> cell_dA_;
|
||||
// std::vector<double> cell_viscosity_;
|
||||
// std::vector<double> cell_phasemob_;
|
||||
// std::vector<double> cell_voldisc_;
|
||||
// std::vector<double> porevol_; // Only modified if rock_comp_props_ is non-null.
|
||||
// std::vector<double> rock_comp_; // Empty unless rock_comp_props_ is non-null.
|
||||
const int nc = grid_.number_of_cells;
|
||||
const int np = props_.numPhases();
|
||||
const double* cell_p = &state.pressure()[0];
|
||||
const double* cell_T = &state.temperature()[0];
|
||||
const double* cell_z = &state.surfacevol()[0];
|
||||
const double* cell_s = &state.saturation()[0];
|
||||
cell_A_.resize(nc*np*np);
|
||||
cell_dA_.resize(nc*np*np);
|
||||
props_.matrix(nc, cell_p, cell_T, cell_z, &allcells_[0], &cell_A_[0], &cell_dA_[0]);
|
||||
cell_viscosity_.resize(nc*np);
|
||||
props_.viscosity(nc, cell_p, cell_T, cell_z, &allcells_[0], &cell_viscosity_[0], 0);
|
||||
cell_phasemob_.resize(nc*np);
|
||||
props_.relperm(nc, cell_s, &allcells_[0], &cell_phasemob_[0], 0);
|
||||
std::transform(cell_phasemob_.begin(), cell_phasemob_.end(),
|
||||
cell_viscosity_.begin(),
|
||||
cell_phasemob_.begin(),
|
||||
std::divides<double>());
|
||||
// Volume discrepancy: we have that
|
||||
// z = Au, voldiscr = sum(u) - 1,
|
||||
// but I am not sure it is actually needed.
|
||||
// Use zero for now.
|
||||
// TODO: Check this!
|
||||
cell_voldisc_.clear();
|
||||
cell_voldisc_.resize(nc, 0.0);
|
||||
|
||||
if (rock_comp_props_ && rock_comp_props_->isActive()) {
|
||||
computePorevolume(grid_, props_.porosity(), *rock_comp_props_, state.pressure(), porevol_);
|
||||
rock_comp_.resize(nc);
|
||||
for (int cell = 0; cell < nc; ++cell) {
|
||||
rock_comp_[cell] = rock_comp_props_->rockComp(state.pressure()[cell]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// Compute per-iteration dynamic properties for faces.
|
||||
void CompressibleTpfa::computeFaceDynamicData(const double /*dt*/,
|
||||
const BlackoilState& state,
|
||||
const WellState& /*well_state*/)
|
||||
{
|
||||
// These are the variables that get computed by this function:
|
||||
//
|
||||
// std::vector<double> face_A_;
|
||||
// std::vector<double> face_phasemob_;
|
||||
// std::vector<double> face_gravcap_;
|
||||
const int np = props_.numPhases();
|
||||
const int nf = grid_.number_of_faces;
|
||||
const int dim = grid_.dimensions;
|
||||
const double grav = gravity_ ? gravity_[dim - 1] : 0.0;
|
||||
std::vector<double> gravcontrib[2];
|
||||
std::vector<double> pot[2];
|
||||
gravcontrib[0].resize(np);
|
||||
gravcontrib[1].resize(np);
|
||||
pot[0].resize(np);
|
||||
pot[1].resize(np);
|
||||
face_A_.resize(nf*np*np);
|
||||
face_phasemob_.resize(nf*np);
|
||||
face_gravcap_.resize(nf*np);
|
||||
for (int face = 0; face < nf; ++face) {
|
||||
// Obtain properties from both sides of the face.
|
||||
const double face_depth = grid_.face_centroids[face*dim + dim - 1];
|
||||
const int* c = &grid_.face_cells[2*face];
|
||||
|
||||
// Get pressures and compute gravity contributions,
|
||||
// to decide upwind directions.
|
||||
double c_press[2];
|
||||
for (int j = 0; j < 2; ++j) {
|
||||
if (c[j] >= 0) {
|
||||
// Pressure
|
||||
c_press[j] = state.pressure()[c[j]];
|
||||
// Gravity contribution, gravcontrib = rho*(face_z - cell_z) [per phase].
|
||||
if (grav != 0.0) {
|
||||
const double depth_diff = face_depth - grid_.cell_centroids[c[j]*dim + dim - 1];
|
||||
props_.density(1, &cell_A_[np*np*c[j]], &c[j], &gravcontrib[j][0]);
|
||||
for (int p = 0; p < np; ++p) {
|
||||
gravcontrib[j][p] *= depth_diff*grav;
|
||||
}
|
||||
} else {
|
||||
std::fill(gravcontrib[j].begin(), gravcontrib[j].end(), 0.0);
|
||||
}
|
||||
} else {
|
||||
// Pressures
|
||||
c_press[j] = state.facepressure()[face];
|
||||
// Gravity contribution.
|
||||
std::fill(gravcontrib[j].begin(), gravcontrib[j].end(), 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
// Gravity contribution:
|
||||
// gravcapf = rho_1*g*(z_12 - z_1) - rho_2*g*(z_12 - z_2)
|
||||
// where _1 and _2 refers to two neigbour cells, z is the
|
||||
// z coordinate of the centroid, and z_12 is the face centroid.
|
||||
// Also compute the potentials.
|
||||
for (int phase = 0; phase < np; ++phase) {
|
||||
face_gravcap_[np*face + phase] = gravcontrib[0][phase] - gravcontrib[1][phase];
|
||||
pot[0][phase] = c_press[0] + face_gravcap_[np*face + phase];
|
||||
pot[1][phase] = c_press[1];
|
||||
}
|
||||
|
||||
// Now we can easily find the upwind direction for every phase,
|
||||
// we can also tell which boundary faces are inflow bdys.
|
||||
|
||||
// Get upwind mobilities by phase.
|
||||
// Get upwind A matrix rows by phase.
|
||||
// NOTE:
|
||||
// We should be careful to upwind the R factors,
|
||||
// the B factors are not that vital.
|
||||
// z = Au = RB^{-1}u,
|
||||
// where (this example is for gas-oil)
|
||||
// R = [1 RgL; RoV 1], B = [BL 0 ; 0 BV]
|
||||
// (RgL is gas in Liquid phase, RoV is oil in Vapour phase.)
|
||||
// A = [1/BL RgL/BV; RoV/BL 1/BV]
|
||||
// This presents us with a dilemma, as V factors should be
|
||||
// upwinded according to V phase flow, same for L. What then
|
||||
// about the RgL/BV and RoV/BL numbers?
|
||||
// We give priority to R, and therefore upwind the rows of A
|
||||
// by phase (but remember, Fortran matrix ordering).
|
||||
// This prompts the question if we should split the matrix()
|
||||
// property method into formation volume and R-factor methods.
|
||||
for (int phase = 0; phase < np; ++phase) {
|
||||
int upwindc = -1;
|
||||
if (c[0] >=0 && c[1] >= 0) {
|
||||
upwindc = (pot[0][phase] < pot[1][phase]) ? c[1] : c[0];
|
||||
} else {
|
||||
upwindc = (c[0] >= 0) ? c[0] : c[1];
|
||||
}
|
||||
face_phasemob_[np*face + phase] = cell_phasemob_[np*upwindc + phase];
|
||||
for (int p2 = 0; p2 < np; ++p2) {
|
||||
// Recall: column-major ordering.
|
||||
face_A_[np*np*face + phase + np*p2]
|
||||
= cell_A_[np*np*upwindc + phase + np*p2];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// Compute per-iteration dynamic properties for wells.
|
||||
void CompressibleTpfa::computeWellDynamicData(const double /*dt*/,
|
||||
const BlackoilState& /*state*/,
|
||||
const WellState& well_state)
|
||||
{
|
||||
// These are the variables that get computed by this function:
|
||||
//
|
||||
// std::vector<double> wellperf_A_;
|
||||
// std::vector<double> wellperf_phasemob_;
|
||||
const int np = props_.numPhases();
|
||||
const int nw = (wells_ != 0) ? wells_->number_of_wells : 0;
|
||||
const int nperf = (wells_ != 0) ? wells_->well_connpos[nw] : 0;
|
||||
wellperf_A_.resize(nperf*np*np);
|
||||
wellperf_phasemob_.resize(nperf*np);
|
||||
// The A matrix is set equal to the perforation grid cells'
|
||||
// matrix for producers, computed from bhp and injection
|
||||
// component fractions from
|
||||
// The mobilities are set equal to the perforation grid cells'
|
||||
// mobilities for producers.
|
||||
std::vector<double> mu(np);
|
||||
for (int w = 0; w < nw; ++w) {
|
||||
bool producer = (wells_->type[w] == PRODUCER);
|
||||
const double* comp_frac = &wells_->comp_frac[np*w];
|
||||
for (int j = wells_->well_connpos[w]; j < wells_->well_connpos[w+1]; ++j) {
|
||||
const int c = wells_->well_cells[j];
|
||||
double* wpA = &wellperf_A_[np*np*j];
|
||||
double* wpM = &wellperf_phasemob_[np*j];
|
||||
if (producer) {
|
||||
const double* cA = &cell_A_[np*np*c];
|
||||
std::copy(cA, cA + np*np, wpA);
|
||||
const double* cM = &cell_phasemob_[np*c];
|
||||
std::copy(cM, cM + np, wpM);
|
||||
} else {
|
||||
const double bhp = well_state.bhp()[w];
|
||||
double perf_p = bhp + wellperf_wdp_[j];
|
||||
const double perf_T = well_state.temperature()[w];
|
||||
// Hack warning: comp_frac is used as a component
|
||||
// surface-volume variable in calls to matrix() and
|
||||
// viscosity(), but as a saturation in the call to
|
||||
// relperm(). This is probably ok as long as injectors
|
||||
// only inject pure fluids.
|
||||
props_.matrix(1, &perf_p, &perf_T, comp_frac, &c, wpA, NULL);
|
||||
props_.viscosity(1, &perf_p, &perf_T, comp_frac, &c, &mu[0], NULL);
|
||||
assert(std::fabs(std::accumulate(comp_frac, comp_frac + np, 0.0) - 1.0) < 1e-6);
|
||||
props_.relperm (1, comp_frac, &c, wpM , NULL);
|
||||
for (int phase = 0; phase < np; ++phase) {
|
||||
wpM[phase] /= mu[phase];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// Compute the residual and Jacobian.
|
||||
void CompressibleTpfa::assemble(const double dt,
|
||||
const BlackoilState& state,
|
||||
const WellState& well_state)
|
||||
{
|
||||
const double* cell_press = &state.pressure()[0];
|
||||
const double* well_bhp = well_state.bhp().empty() ? NULL : &well_state.bhp()[0];
|
||||
const double* z = &state.surfacevol()[0];
|
||||
UnstructuredGrid* gg = const_cast<UnstructuredGrid*>(&grid_);
|
||||
CompletionData completion_data;
|
||||
completion_data.wdp = ! wellperf_wdp_.empty() ? &wellperf_wdp_[0] : 0;
|
||||
completion_data.A = ! wellperf_A_.empty() ? &wellperf_A_[0] : 0;
|
||||
completion_data.phasemob = ! wellperf_phasemob_.empty() ? &wellperf_phasemob_[0] : 0;
|
||||
cfs_tpfa_res_wells wells_tmp;
|
||||
wells_tmp.W = const_cast<Wells*>(wells_);
|
||||
wells_tmp.data = &completion_data;
|
||||
cfs_tpfa_res_forces forces;
|
||||
forces.wells = &wells_tmp;
|
||||
forces.src = NULL; // Check if it is legal to leave it as NULL.
|
||||
compr_quantities_gen cq;
|
||||
cq.nphases = props_.numPhases();
|
||||
cq.Ac = &cell_A_[0];
|
||||
cq.dAc = &cell_dA_[0];
|
||||
cq.Af = &face_A_[0];
|
||||
cq.phasemobf = &face_phasemob_[0];
|
||||
cq.voldiscr = &cell_voldisc_[0];
|
||||
int was_adjusted = 0;
|
||||
if (! (rock_comp_props_ && rock_comp_props_->isActive())) {
|
||||
was_adjusted =
|
||||
cfs_tpfa_res_assemble(gg, dt, &forces, z, &cq, &trans_[0],
|
||||
&face_gravcap_[0], cell_press, well_bhp,
|
||||
&porevol_[0], h_);
|
||||
} else {
|
||||
was_adjusted =
|
||||
cfs_tpfa_res_comprock_assemble(gg, dt, &forces, z, &cq, &trans_[0],
|
||||
&face_gravcap_[0], cell_press, well_bhp,
|
||||
&porevol_[0], &initial_porevol_[0],
|
||||
&rock_comp_[0], h_);
|
||||
}
|
||||
singular_ = (was_adjusted == 1);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// Computes pressure_increment_.
|
||||
void CompressibleTpfa::solveIncrement()
|
||||
{
|
||||
// Increment is equal to -J^{-1}F
|
||||
linsolver_.solve(h_->J, h_->F, &pressure_increment_[0]);
|
||||
std::transform(pressure_increment_.begin(), pressure_increment_.end(),
|
||||
pressure_increment_.begin(), std::negate<double>());
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
namespace {
|
||||
template <class FI>
|
||||
double infnorm(FI beg, FI end)
|
||||
{
|
||||
double norm = 0.0;
|
||||
for (; beg != end; ++beg) {
|
||||
norm = std::max(norm, std::fabs(*beg));
|
||||
}
|
||||
return norm;
|
||||
}
|
||||
} // anonymous namespace
|
||||
|
||||
|
||||
|
||||
|
||||
/// Computes the inf-norm of the residual.
|
||||
double CompressibleTpfa::residualNorm() const
|
||||
{
|
||||
const int ndof = pressure_increment_.size();
|
||||
return infnorm(h_->F, h_->F + ndof);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// Computes the inf-norm of pressure_increment_.
|
||||
double CompressibleTpfa::incrementNorm() const
|
||||
{
|
||||
return infnorm(pressure_increment_.begin(), pressure_increment_.end());
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// Compute the output.
|
||||
void CompressibleTpfa::computeResults(BlackoilState& state,
|
||||
WellState& well_state) const
|
||||
{
|
||||
UnstructuredGrid* gg = const_cast<UnstructuredGrid*>(&grid_);
|
||||
CompletionData completion_data;
|
||||
completion_data.wdp = ! wellperf_wdp_.empty() ? const_cast<double*>(&wellperf_wdp_[0]) : 0;
|
||||
completion_data.A = ! wellperf_A_.empty() ? const_cast<double*>(&wellperf_A_[0]) : 0;
|
||||
completion_data.phasemob = ! wellperf_phasemob_.empty() ? const_cast<double*>(&wellperf_phasemob_[0]) : 0;
|
||||
cfs_tpfa_res_wells wells_tmp;
|
||||
wells_tmp.W = const_cast<Wells*>(wells_);
|
||||
wells_tmp.data = &completion_data;
|
||||
cfs_tpfa_res_forces forces;
|
||||
forces.wells = &wells_tmp;
|
||||
forces.src = NULL;
|
||||
|
||||
double* wpress = ! well_state.bhp ().empty() ? & well_state.bhp ()[0] : 0;
|
||||
double* wflux = ! well_state.perfRates().empty() ? & well_state.perfRates()[0] : 0;
|
||||
|
||||
cfs_tpfa_res_flux(gg,
|
||||
&forces,
|
||||
props_.numPhases(),
|
||||
&trans_[0],
|
||||
&cell_phasemob_[0],
|
||||
&face_phasemob_[0],
|
||||
&face_gravcap_[0],
|
||||
&state.pressure()[0],
|
||||
wpress,
|
||||
&state.faceflux()[0],
|
||||
wflux);
|
||||
cfs_tpfa_res_fpress(gg,
|
||||
props_.numPhases(),
|
||||
&htrans_[0],
|
||||
&face_phasemob_[0],
|
||||
&face_gravcap_[0],
|
||||
h_,
|
||||
&state.pressure()[0],
|
||||
&state.faceflux()[0],
|
||||
&state.facepressure()[0]);
|
||||
|
||||
// Compute well perforation pressures (not done by the C code).
|
||||
if (wells_ != 0) {
|
||||
const int nw = wells_->number_of_wells;
|
||||
for (int w = 0; w < nw; ++w) {
|
||||
for (int j = wells_->well_connpos[w]; j < wells_->well_connpos[w+1]; ++j) {
|
||||
const double bhp = well_state.bhp()[w];
|
||||
well_state.perfPress()[j] = bhp + wellperf_wdp_[j];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Opm
|
165
opm/core/pressure/CompressibleTpfa.hpp
Normal file
165
opm/core/pressure/CompressibleTpfa.hpp
Normal file
@ -0,0 +1,165 @@
|
||||
/*
|
||||
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_COMPRESSIBLETPFA_HEADER_INCLUDED
|
||||
#define OPM_COMPRESSIBLETPFA_HEADER_INCLUDED
|
||||
|
||||
|
||||
#include <vector>
|
||||
|
||||
struct UnstructuredGrid;
|
||||
struct cfs_tpfa_res_data;
|
||||
struct Wells;
|
||||
struct FlowBoundaryConditions;
|
||||
|
||||
namespace Opm
|
||||
{
|
||||
|
||||
class BlackoilState;
|
||||
class BlackoilPropertiesInterface;
|
||||
class RockCompressibility;
|
||||
class LinearSolverInterface;
|
||||
class WellState;
|
||||
|
||||
/// Encapsulating a tpfa pressure solver for the compressible-fluid case.
|
||||
/// Supports gravity, wells and simple sources as driving forces.
|
||||
/// Below we use the shortcuts D for the number of dimensions, N
|
||||
/// for the number of cells and F for the number of faces.
|
||||
class CompressibleTpfa
|
||||
{
|
||||
public:
|
||||
/// Construct solver.
|
||||
/// \param[in] grid A 2d or 3d grid.
|
||||
/// \param[in] props Rock and fluid properties.
|
||||
/// \param[in] rock_comp_props Rock compressibility properties. May be null.
|
||||
/// \param[in] linsolver Linear solver to use.
|
||||
/// \param[in] residual_tol Solution accepted if inf-norm of residual is smaller.
|
||||
/// \param[in] change_tol Solution accepted if inf-norm of change in pressure is smaller.
|
||||
/// \param[in] maxiter Maximum acceptable number of iterations.
|
||||
/// \param[in] gravity Gravity vector. If non-null, the array should
|
||||
/// have D elements.
|
||||
/// \param[in] wells The wells argument. Will be used in solution,
|
||||
/// is ignored if NULL.
|
||||
/// Note: this class observes the well object, and
|
||||
/// makes the assumption that the well topology
|
||||
/// and completions does not change during the
|
||||
/// run. However, controls (only) are allowed
|
||||
/// to change.
|
||||
CompressibleTpfa(const UnstructuredGrid& grid,
|
||||
const BlackoilPropertiesInterface& props,
|
||||
const RockCompressibility* rock_comp_props,
|
||||
const LinearSolverInterface& linsolver,
|
||||
const double residual_tol,
|
||||
const double change_tol,
|
||||
const int maxiter,
|
||||
const double* gravity,
|
||||
const Wells* wells);
|
||||
|
||||
/// Destructor.
|
||||
virtual ~CompressibleTpfa();
|
||||
|
||||
/// Solve the pressure equation by Newton-Raphson scheme.
|
||||
/// May throw an exception if the number of iterations
|
||||
/// exceed maxiter (set in constructor).
|
||||
void solve(const double dt,
|
||||
BlackoilState& state,
|
||||
WellState& well_state);
|
||||
|
||||
/// @brief After solve(), was the resulting pressure singular.
|
||||
/// Returns true if the pressure is singular in the following
|
||||
/// sense: if everything is incompressible and there are no
|
||||
/// pressure conditions, the absolute values of the pressure
|
||||
/// solution are arbitrary. (But the differences in pressure
|
||||
/// are significant.)
|
||||
bool singularPressure() const;
|
||||
|
||||
private:
|
||||
virtual void computePerSolveDynamicData(const double dt,
|
||||
const BlackoilState& state,
|
||||
const WellState& well_state);
|
||||
void computePerIterationDynamicData(const double dt,
|
||||
const BlackoilState& state,
|
||||
const WellState& well_state);
|
||||
virtual void computeCellDynamicData(const double dt,
|
||||
const BlackoilState& state,
|
||||
const WellState& well_state);
|
||||
void computeFaceDynamicData(const double dt,
|
||||
const BlackoilState& state,
|
||||
const WellState& well_state);
|
||||
void computeWellDynamicData(const double dt,
|
||||
const BlackoilState& state,
|
||||
const WellState& well_state);
|
||||
void assemble(const double dt,
|
||||
const BlackoilState& state,
|
||||
const WellState& well_state);
|
||||
void solveIncrement();
|
||||
double residualNorm() const;
|
||||
double incrementNorm() const;
|
||||
void computeResults(BlackoilState& state,
|
||||
WellState& well_state) const;
|
||||
protected:
|
||||
void computeWellPotentials(const BlackoilState& state);
|
||||
|
||||
// ------ Data that will remain unmodified after construction. ------
|
||||
const UnstructuredGrid& grid_;
|
||||
const BlackoilPropertiesInterface& props_;
|
||||
const RockCompressibility* rock_comp_props_;
|
||||
const LinearSolverInterface& linsolver_;
|
||||
const double residual_tol_;
|
||||
const double change_tol_;
|
||||
const int maxiter_;
|
||||
const double* gravity_; // May be NULL
|
||||
const Wells* wells_; // May be NULL, outside may modify controls (only) between calls to solve().
|
||||
std::vector<double> htrans_;
|
||||
std::vector<double> trans_ ;
|
||||
std::vector<int> allcells_;
|
||||
|
||||
// ------ Internal data for the cfs_tpfa_res solver. ------
|
||||
struct cfs_tpfa_res_data* h_;
|
||||
|
||||
// ------ Data that will be modified for every solve. ------
|
||||
std::vector<double> wellperf_wdp_;
|
||||
std::vector<double> initial_porevol_;
|
||||
|
||||
// ------ Data that will be modified for every solver iteration. ------
|
||||
std::vector<double> cell_A_;
|
||||
std::vector<double> cell_dA_;
|
||||
std::vector<double> cell_viscosity_;
|
||||
std::vector<double> cell_phasemob_;
|
||||
std::vector<double> cell_voldisc_;
|
||||
std::vector<double> face_A_;
|
||||
std::vector<double> face_phasemob_;
|
||||
std::vector<double> face_gravcap_;
|
||||
std::vector<double> wellperf_A_;
|
||||
std::vector<double> wellperf_phasemob_;
|
||||
std::vector<double> porevol_; // Only modified if rock_comp_props_ is non-null.
|
||||
std::vector<double> rock_comp_; // Empty unless rock_comp_props_ is non-null.
|
||||
// The update to be applied to the pressures (cell and bhp).
|
||||
std::vector<double> pressure_increment_;
|
||||
// True if the matrix assembled would be singular but for the
|
||||
// adjustment made in the cfs_*_assemble() calls. This happens
|
||||
// if everything is incompressible and there are no pressure
|
||||
// conditions.
|
||||
bool singular_;
|
||||
};
|
||||
|
||||
} // namespace Opm
|
||||
|
||||
|
||||
#endif // OPM_COMPRESSIBLETPFA_HEADER_INCLUDED
|
239
opm/core/pressure/FlowBCManager.cpp
Normal file
239
opm/core/pressure/FlowBCManager.cpp
Normal file
@ -0,0 +1,239 @@
|
||||
/*
|
||||
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/pressure/FlowBCManager.hpp>
|
||||
#include <opm/common/ErrorMacros.hpp>
|
||||
#include <opm/core/grid.h>
|
||||
#include <vector>
|
||||
|
||||
namespace Opm
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
std::string sideString(FlowBCManager::Side s);
|
||||
void findSideFaces(const UnstructuredGrid& grid,
|
||||
const FlowBCManager::Side side,
|
||||
std::vector<int>& faces);
|
||||
} // anon namespace
|
||||
|
||||
|
||||
/// Default constructor sets up empty boundary conditions.
|
||||
/// By convention, this is equivalent to all-noflow conditions.
|
||||
FlowBCManager::FlowBCManager()
|
||||
: bc_(0)
|
||||
{
|
||||
bc_ = flow_conditions_construct(0);
|
||||
if (!bc_) {
|
||||
OPM_THROW(std::runtime_error, "Failed to construct FlowBoundaryConditions struct.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Destructor.
|
||||
FlowBCManager::~FlowBCManager()
|
||||
{
|
||||
flow_conditions_destroy(bc_);
|
||||
}
|
||||
|
||||
|
||||
/// Remove all appended BCs.
|
||||
/// By convention, BCs are now equivalent to all-noflow conditions.
|
||||
void FlowBCManager::clear()
|
||||
{
|
||||
flow_conditions_clear(bc_);
|
||||
}
|
||||
|
||||
|
||||
/// Append a single boundary condition.
|
||||
/// If the type is BC_NOFLOW the value argument is not used.
|
||||
/// If the type is BC_PRESSURE the value argument is a pressure value.
|
||||
/// If the type is BC_FLUX_TOTVOL the value argument is a total flux value (m^3/s).
|
||||
/// Note: unset boundary conditions are noflow by convention,
|
||||
/// so it is normally not necessary to explicitly append
|
||||
/// BC_NOFLOW conditions. However, it may make sense to do so
|
||||
/// if the bc will change during a simulation run.
|
||||
/// Note: if normal velocity bcs are desired, convert to
|
||||
/// fluxes by multiplying with face area.
|
||||
void FlowBCManager::append(const FlowBCType type,
|
||||
const int face,
|
||||
const double value)
|
||||
{
|
||||
int ok = flow_conditions_append(type, face, value, bc_);
|
||||
if (!ok) {
|
||||
OPM_THROW(std::runtime_error, "Failed to append boundary condition for face " << face);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Add BC_PRESSURE boundary conditions to all faces on a given side.
|
||||
/// The grid must have a logical cartesian structure, and grid
|
||||
/// faces must be tagged (i.e. grid.cell_facetag must be
|
||||
/// non-null). Only the set of faces adjacent to cells with
|
||||
/// minimum/maximum I/J/K coordinate (depending on side) are
|
||||
/// considered.
|
||||
void FlowBCManager::pressureSide(const UnstructuredGrid& grid,
|
||||
const Side side,
|
||||
const double pressure)
|
||||
{
|
||||
std::vector<int> faces;
|
||||
findSideFaces(grid, side, faces);
|
||||
int ok = flow_conditions_append_multi(BC_PRESSURE, faces.size(), &faces[0], pressure, bc_);
|
||||
if (!ok) {
|
||||
OPM_THROW(std::runtime_error, "Failed to append pressure boundary conditions for side " << sideString(side));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Add BC_FLUX_TOTVOL boundary conditions to all faces on a given side.
|
||||
/// The grid must have a logical cartesian structure, and grid
|
||||
/// faces must be tagged (i.e. grid.cell_facetag must be
|
||||
/// non-null). Only the set of faces adjacent to cells with
|
||||
/// minimum/maximum I/J/K coordinate (depending on side) are
|
||||
/// considered.
|
||||
/// The flux specified is taken to be the total flux through
|
||||
/// the side, each individual face receiving a part of the
|
||||
/// total flux in proportion to its area, so that all faces
|
||||
/// will have identical normal velocities.
|
||||
void FlowBCManager::fluxSide(const UnstructuredGrid& grid,
|
||||
const Side side,
|
||||
const double flux)
|
||||
{
|
||||
// Find side faces.
|
||||
std::vector<int> faces;
|
||||
findSideFaces(grid, side, faces);
|
||||
|
||||
// Compute total area of faces.
|
||||
double tot_area = 0.0;
|
||||
for (int fi = 0; fi < int(faces.size()); ++fi) {
|
||||
tot_area += grid.face_areas[faces[fi]];
|
||||
}
|
||||
|
||||
// Append flux conditions for all the faces individually.
|
||||
for (int fi = 0; fi < int(faces.size()); ++fi) {
|
||||
const double face_flux = flux * grid.face_areas[faces[fi]] / tot_area;
|
||||
int ok = flow_conditions_append(BC_FLUX_TOTVOL, faces[fi], face_flux, bc_);
|
||||
if (!ok) {
|
||||
OPM_THROW(std::runtime_error, "Failed to append flux boundary conditions for face " << faces[fi] << " on side " << sideString(side));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// Access the managed boundary conditions.
|
||||
/// The method is named similarly to c_str() in std::string,
|
||||
/// to make it clear that we are returning a C-compatible struct.
|
||||
const FlowBoundaryConditions* FlowBCManager::c_bcs() const
|
||||
{
|
||||
return bc_;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// ------ Utility functions ------
|
||||
|
||||
|
||||
namespace
|
||||
{
|
||||
std::string sideString(FlowBCManager::Side s)
|
||||
{
|
||||
switch (s) {
|
||||
case FlowBCManager::Xmin: return "Xmin";
|
||||
case FlowBCManager::Xmax: return "Xmax";
|
||||
case FlowBCManager::Ymin: return "Ymin";
|
||||
case FlowBCManager::Ymax: return "Ymax";
|
||||
case FlowBCManager::Zmin: return "Zmin";
|
||||
case FlowBCManager::Zmax: return "Zmax";
|
||||
default: OPM_THROW(std::runtime_error, "Unknown side tag " << s);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void cartCoord(const int ndims,
|
||||
const int log_cart_coord,
|
||||
const int* dims,
|
||||
int* ijk)
|
||||
{
|
||||
int ix = log_cart_coord;
|
||||
|
||||
for (int dim = 0; dim < ndims; ++dim) {
|
||||
ijk[dim] = ix % dims[dim];
|
||||
ix /= dims[dim];
|
||||
}
|
||||
|
||||
// Make sure that lexicographic index is consistent with
|
||||
// grid dimensions.
|
||||
assert(ix == 0);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// The grid must have a logical cartesian structure, and grid
|
||||
/// faces must be tagged (i.e. grid.cell_facetag must be
|
||||
/// non-null). Only the set of faces adjacent to cells with
|
||||
/// minimum/maximum I/J/K coordinate (depending on side) are
|
||||
/// considered.
|
||||
void findSideFaces(const UnstructuredGrid& grid,
|
||||
const FlowBCManager::Side side,
|
||||
std::vector<int>& faces)
|
||||
{
|
||||
if (grid.cell_facetag == 0) {
|
||||
OPM_THROW(std::runtime_error, "Faces not tagged - cannot extract " << sideString(side) << " faces.");
|
||||
}
|
||||
|
||||
// make sure that grid has three dimensions or less.
|
||||
assert(grid.dimensions <= 3);
|
||||
|
||||
// Make sure boundary condition side is consistent with
|
||||
// number of physical grid dimensions.
|
||||
assert(side < 2 * grid.dimensions);
|
||||
|
||||
// Get all boundary faces with the correct tag and with
|
||||
// min/max i/j/k (depending on side).
|
||||
const int correct_ijk = (side % 2) ? grid.cartdims[side/2] - 1 : 0;
|
||||
for (int c = 0; c < grid.number_of_cells; ++c) {
|
||||
int ijk[3] = { -1, -1, -1 };
|
||||
int gc = (grid.global_cell != 0) ? grid.global_cell[c] : c;
|
||||
cartCoord(grid.dimensions, gc, grid.cartdims, ijk);
|
||||
if (ijk[side/2] != correct_ijk) {
|
||||
continue;
|
||||
}
|
||||
for (int hf = grid.cell_facepos[c]; hf < grid.cell_facepos[c + 1]; ++hf) {
|
||||
if (grid.cell_facetag[hf] == side) {
|
||||
// Tag is correct.
|
||||
const int f = grid.cell_faces[hf];
|
||||
if (grid.face_cells[2*f] == -1 || grid.face_cells[2*f + 1] == -1) {
|
||||
// Face is on boundary.
|
||||
faces.push_back(f);
|
||||
} else {
|
||||
OPM_THROW(std::runtime_error, "Face not on boundary, even with correct tag and boundary cell. This should not occur.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // anon namespace
|
||||
|
||||
} // namespace Opm
|
104
opm/core/pressure/FlowBCManager.hpp
Normal file
104
opm/core/pressure/FlowBCManager.hpp
Normal file
@ -0,0 +1,104 @@
|
||||
/*
|
||||
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_FLOWBCMANAGER_HEADER_INCLUDED
|
||||
#define OPM_FLOWBCMANAGER_HEADER_INCLUDED
|
||||
|
||||
#include <opm/core/pressure/flow_bc.h>
|
||||
|
||||
struct UnstructuredGrid;
|
||||
|
||||
namespace Opm
|
||||
{
|
||||
|
||||
/// This class manages a FlowBoundaryConditions struct in the
|
||||
/// sense that it encapsulates creation and destruction of the
|
||||
/// data structure.
|
||||
/// The resulting struct is available through the c_bcs() method.
|
||||
class FlowBCManager
|
||||
{
|
||||
public:
|
||||
/// Default constructor sets up empty boundary conditions.
|
||||
/// By convention, this is equivalent to all-noflow conditions.
|
||||
FlowBCManager();
|
||||
|
||||
/// Destructor.
|
||||
~FlowBCManager();
|
||||
|
||||
/// Remove all appended BCs.
|
||||
/// By convention, BCs are now equivalent to all-noflow conditions.
|
||||
void clear();
|
||||
|
||||
/// Append a single boundary condition.
|
||||
/// If the type is BC_NOFLOW the value argument is not used.
|
||||
/// If the type is BC_PRESSURE the value argument is a pressure value.
|
||||
/// If the type is BC_FLUX_TOTVOL the value argument is a total flux value (m^3/s).
|
||||
/// Note: unset boundary conditions are noflow by convention,
|
||||
/// so it is normally not necessary to explicitly append
|
||||
/// BC_NOFLOW conditions. However, it may make sense to do so
|
||||
/// if the bc will change during a simulation run.
|
||||
/// Note: if normal velocity bcs are desired, convert to
|
||||
/// fluxes by multiplying with face area.
|
||||
void append(const FlowBCType type,
|
||||
const int face,
|
||||
const double value);
|
||||
|
||||
/// Defines the canonical sides for logical cartesian grids.
|
||||
enum Side { Xmin, Xmax, Ymin, Ymax, Zmin, Zmax };
|
||||
|
||||
/// Add BC_PRESSURE boundary conditions to all faces on a given side.
|
||||
/// The grid must have a logical cartesian structure, and grid
|
||||
/// faces must be tagged (i.e. grid.cell_facetag must be
|
||||
/// non-null). Only the set of faces adjacent to cells with
|
||||
/// minimum/maximum I/J/K coordinate (depending on side) are
|
||||
/// considered.
|
||||
void pressureSide(const UnstructuredGrid& grid,
|
||||
const Side side,
|
||||
const double pressure);
|
||||
|
||||
/// Add BC_FLUX_TOTVOL boundary conditions to all faces on a given side.
|
||||
/// The grid must have a logical cartesian structure, and grid
|
||||
/// faces must be tagged (i.e. grid.cell_facetag must be
|
||||
/// non-null). Only the set of faces adjacent to cells with
|
||||
/// minimum/maximum I/J/K coordinate (depending on side) are
|
||||
/// considered.
|
||||
/// The flux specified is taken to be the total flux through
|
||||
/// the side, each individual face receiving a part of the
|
||||
/// total flux in proportion to its area, so that all faces
|
||||
/// will have identical normal velocities.
|
||||
void fluxSide(const UnstructuredGrid& grid,
|
||||
const Side side,
|
||||
const double flux);
|
||||
|
||||
/// Access the managed boundary conditions.
|
||||
/// The method is named similarly to c_str() in std::string,
|
||||
/// to make it clear that we are returning a C-compatible struct.
|
||||
const FlowBoundaryConditions* c_bcs() const;
|
||||
|
||||
private:
|
||||
// Disable copying and assignment.
|
||||
FlowBCManager(const FlowBCManager& other);
|
||||
FlowBCManager& operator=(const FlowBCManager& other);
|
||||
// The managed struct.
|
||||
FlowBoundaryConditions* bc_;
|
||||
};
|
||||
|
||||
} // namespace Opm
|
||||
|
||||
#endif // OPM_FLOWBCMANAGER_HEADER_INCLUDED
|
496
opm/core/pressure/IncompTpfa.cpp
Normal file
496
opm/core/pressure/IncompTpfa.cpp
Normal file
@ -0,0 +1,496 @@
|
||||
/*
|
||||
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/common/data/SimulationDataContainer.hpp>
|
||||
|
||||
#include <opm/core/pressure/IncompTpfa.hpp>
|
||||
#include <opm/core/props/IncompPropertiesInterface.hpp>
|
||||
#include <opm/core/props/rock/RockCompressibility.hpp>
|
||||
#include <opm/core/pressure/tpfa/ifs_tpfa.h>
|
||||
#include <opm/core/pressure/tpfa/trans_tpfa.h>
|
||||
#include <opm/core/pressure/mimetic/mimetic.h>
|
||||
#include <opm/core/pressure/flow_bc.h>
|
||||
#include <opm/core/linalg/LinearSolverInterface.hpp>
|
||||
#include <opm/core/linalg/sparse_sys.h>
|
||||
#include <opm/core/simulator/WellState.hpp>
|
||||
#include <opm/common/ErrorMacros.hpp>
|
||||
#include <opm/core/utility/miscUtilities.hpp>
|
||||
#include <opm/core/wells.h>
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
|
||||
namespace Opm
|
||||
{
|
||||
|
||||
|
||||
|
||||
/// Construct solver for incompressible case.
|
||||
/// \param[in] grid A 2d or 3d grid.
|
||||
/// \param[in] props Rock and fluid properties.
|
||||
/// \param[in] linsolver Linear solver to use.
|
||||
/// \param[in] gravity Gravity vector. If non-null, the array should
|
||||
/// have D elements.
|
||||
/// \param[in] wells The wells argument. Will be used in solution,
|
||||
/// is ignored if NULL.
|
||||
/// Note: this class observes the well object, and
|
||||
/// makes the assumption that the well topology
|
||||
/// and completions does not change during the
|
||||
/// run. However, controls (only) are allowed
|
||||
/// to change.
|
||||
/// \param[in] src Source terms. May be empty().
|
||||
/// \param[in] bcs Boundary conditions, treat as all noflow if null.
|
||||
IncompTpfa::IncompTpfa(const UnstructuredGrid& grid,
|
||||
const IncompPropertiesInterface& props,
|
||||
LinearSolverInterface& linsolver,
|
||||
const double* gravity,
|
||||
const Wells* wells,
|
||||
const std::vector<double>& src,
|
||||
const FlowBoundaryConditions* bcs)
|
||||
: grid_(grid),
|
||||
props_(props),
|
||||
rock_comp_props_(NULL),
|
||||
linsolver_(linsolver),
|
||||
residual_tol_(0.0),
|
||||
change_tol_(0.0),
|
||||
maxiter_(0),
|
||||
gravity_(gravity),
|
||||
wells_(wells),
|
||||
src_(src),
|
||||
bcs_(bcs),
|
||||
htrans_(grid.cell_facepos[ grid.number_of_cells ]),
|
||||
allcells_(grid.number_of_cells),
|
||||
trans_ (grid.number_of_faces)
|
||||
{
|
||||
computeStaticData();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// Construct solver, possibly with rock compressibility.
|
||||
/// \param[in] grid A 2d or 3d grid.
|
||||
/// \param[in] props Rock and fluid properties.
|
||||
/// \param[in] rock_comp_props Rock compressibility properties. May be null.
|
||||
/// \param[in] linsolver Linear solver to use.
|
||||
/// \param[in] residual_tol Solution accepted if inf-norm of residual is smaller.
|
||||
/// \param[in] change_tol Solution accepted if inf-norm of change in pressure is smaller.
|
||||
/// \param[in] maxiter Maximum acceptable number of iterations.
|
||||
/// \param[in] gravity Gravity vector. If non-null, the array should
|
||||
/// have D elements.
|
||||
/// \param[in] wells The wells argument. Will be used in solution,
|
||||
/// is ignored if NULL.
|
||||
/// Note: this class observes the well object, and
|
||||
/// makes the assumption that the well topology
|
||||
/// and completions does not change during the
|
||||
/// run. However, controls (only) are allowed
|
||||
/// to change.
|
||||
/// \param[in] src Source terms. May be empty().
|
||||
/// \param[in] bcs Boundary conditions, treat as all noflow if null.
|
||||
IncompTpfa::IncompTpfa(const UnstructuredGrid& grid,
|
||||
const IncompPropertiesInterface& props,
|
||||
const RockCompressibility* rock_comp_props,
|
||||
LinearSolverInterface& linsolver,
|
||||
const double residual_tol,
|
||||
const double change_tol,
|
||||
const int maxiter,
|
||||
const double* gravity,
|
||||
const Wells* wells,
|
||||
const std::vector<double>& src,
|
||||
const FlowBoundaryConditions* bcs)
|
||||
: grid_(grid),
|
||||
props_(props),
|
||||
rock_comp_props_(rock_comp_props),
|
||||
linsolver_(linsolver),
|
||||
residual_tol_(residual_tol),
|
||||
change_tol_(change_tol),
|
||||
maxiter_(maxiter),
|
||||
gravity_(gravity),
|
||||
wells_(wells),
|
||||
src_(src),
|
||||
bcs_(bcs),
|
||||
htrans_(grid.cell_facepos[ grid.number_of_cells ]),
|
||||
allcells_(grid.number_of_cells),
|
||||
trans_ (grid.number_of_faces)
|
||||
{
|
||||
computeStaticData();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/// Destructor.
|
||||
IncompTpfa::~IncompTpfa()
|
||||
{
|
||||
ifs_tpfa_destroy(h_);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// Solve the pressure equation. If there is no pressure
|
||||
/// dependency introduced by rock compressibility effects,
|
||||
/// the equation is linear, and it is solved directly.
|
||||
/// Otherwise, the nonlinear equations ares solved by a
|
||||
/// Newton-Raphson scheme.
|
||||
/// May throw an exception if the number of iterations
|
||||
/// exceed maxiter (set in constructor).
|
||||
void IncompTpfa::solve(const double dt,
|
||||
SimulationDataContainer& state,
|
||||
WellState& well_state)
|
||||
{
|
||||
if (rock_comp_props_ != 0 && rock_comp_props_->isActive()) {
|
||||
solveRockComp(dt, state, well_state);
|
||||
} else {
|
||||
solveIncomp(dt, state, well_state);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Solve with no rock compressibility (linear eqn).
|
||||
void IncompTpfa::solveIncomp(const double dt,
|
||||
SimulationDataContainer& state,
|
||||
WellState& well_state)
|
||||
{
|
||||
// Set up properties.
|
||||
computePerSolveDynamicData(dt, state, well_state);
|
||||
|
||||
// Assemble.
|
||||
UnstructuredGrid* gg = const_cast<UnstructuredGrid*>(&grid_);
|
||||
int ok = ifs_tpfa_assemble(gg, &forces_, &trans_[0], &gpress_omegaweighted_[0], h_);
|
||||
if (!ok) {
|
||||
OPM_THROW(std::runtime_error, "Failed assembling pressure system.");
|
||||
}
|
||||
|
||||
// Solve.
|
||||
linsolver_.solve(h_->A, h_->b, h_->x);
|
||||
|
||||
// Obtain solution.
|
||||
assert(int(state.pressure().size()) == grid_.number_of_cells);
|
||||
assert(int(state.faceflux().size()) == grid_.number_of_faces);
|
||||
ifs_tpfa_solution soln = { NULL, NULL, NULL, NULL };
|
||||
soln.cell_press = &state.pressure()[0];
|
||||
soln.face_flux = &state.faceflux()[0];
|
||||
if (wells_ != NULL) {
|
||||
assert(int(well_state.bhp().size()) == wells_->number_of_wells);
|
||||
assert(int(well_state.perfRates().size()) == wells_->well_connpos[ wells_->number_of_wells ]);
|
||||
soln.well_flux = &well_state.perfRates()[0];
|
||||
soln.well_press = &well_state.bhp()[0];
|
||||
}
|
||||
ifs_tpfa_press_flux(gg, &forces_, &trans_[0], h_, &soln);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// Solve with rock compressibility (nonlinear eqn).
|
||||
void IncompTpfa::solveRockComp(const double dt,
|
||||
SimulationDataContainer& state,
|
||||
WellState& well_state)
|
||||
{
|
||||
// This function is identical to CompressibleTpfa::solve().
|
||||
// \TODO refactor?
|
||||
|
||||
const int nc = grid_.number_of_cells;
|
||||
const int nw = (wells_) ? wells_->number_of_wells : 0;
|
||||
|
||||
// Set up dynamic data.
|
||||
computePerSolveDynamicData(dt, state, well_state);
|
||||
computePerIterationDynamicData(dt, state, well_state);
|
||||
|
||||
// Assemble J and F.
|
||||
assemble(dt, state, well_state);
|
||||
|
||||
double inc_norm = 0.0;
|
||||
int iter = 0;
|
||||
double res_norm = residualNorm();
|
||||
std::cout << "\nIteration Residual Change in p\n"
|
||||
<< std::setw(9) << iter
|
||||
<< std::setw(18) << res_norm
|
||||
<< std::setw(18) << '*' << std::endl;
|
||||
while ((iter < maxiter_) && (res_norm > residual_tol_)) {
|
||||
// Solve for increment in Newton method:
|
||||
// incr = x_{n+1} - x_{n} = -J^{-1}F
|
||||
// (J is Jacobian matrix, F is residual)
|
||||
solveIncrement();
|
||||
++iter;
|
||||
|
||||
// Update pressure vars with increment.
|
||||
for (int c = 0; c < nc; ++c) {
|
||||
state.pressure()[c] += h_->x[c];
|
||||
}
|
||||
for (int w = 0; w < nw; ++w) {
|
||||
well_state.bhp()[w] += h_->x[nc + w];
|
||||
}
|
||||
|
||||
// Stop iterating if increment is small.
|
||||
inc_norm = incrementNorm();
|
||||
if (inc_norm <= change_tol_) {
|
||||
std::cout << std::setw(9) << iter
|
||||
<< std::setw(18) << '*'
|
||||
<< std::setw(18) << inc_norm << std::endl;
|
||||
break;
|
||||
}
|
||||
|
||||
// Set up dynamic data.
|
||||
computePerIterationDynamicData(dt, state, well_state);
|
||||
|
||||
// Assemble J and F.
|
||||
assemble(dt, state, well_state);
|
||||
|
||||
// Update residual norm.
|
||||
res_norm = residualNorm();
|
||||
|
||||
std::cout << std::setw(9) << iter
|
||||
<< std::setw(18) << res_norm
|
||||
<< std::setw(18) << inc_norm << std::endl;
|
||||
}
|
||||
|
||||
if ((iter == maxiter_) && (res_norm > residual_tol_) && (inc_norm > change_tol_)) {
|
||||
OPM_THROW(std::runtime_error, "IncompTpfa::solve() failed to converge in " << maxiter_ << " iterations.");
|
||||
}
|
||||
|
||||
std::cout << "Solved pressure in " << iter << " iterations." << std::endl;
|
||||
|
||||
// Compute fluxes and face pressures.
|
||||
computeResults(state, well_state);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/// Compute data that never changes (after construction).
|
||||
void IncompTpfa::computeStaticData()
|
||||
{
|
||||
if (wells_ && (wells_->number_of_phases != props_.numPhases())) {
|
||||
OPM_THROW(std::runtime_error, "Inconsistent number of phases specified (wells vs. props): "
|
||||
<< wells_->number_of_phases << " != " << props_.numPhases());
|
||||
}
|
||||
const int num_dofs = grid_.number_of_cells + (wells_ ? wells_->number_of_wells : 0);
|
||||
pressures_.resize(num_dofs);
|
||||
UnstructuredGrid* gg = const_cast<UnstructuredGrid*>(&grid_);
|
||||
tpfa_htrans_compute(gg, props_.permeability(), &htrans_[0]);
|
||||
if (gravity_) {
|
||||
gpress_.resize(gg->cell_facepos[ gg->number_of_cells ], 0.0);
|
||||
|
||||
mim_ip_compute_gpress(gg->number_of_cells, gg->dimensions, gravity_,
|
||||
gg->cell_facepos, gg->cell_faces,
|
||||
gg->face_centroids, gg->cell_centroids,
|
||||
&gpress_[0]);
|
||||
}
|
||||
// gpress_omegaweighted_ is sent to assembler always, and it dislikes
|
||||
// getting a zero pointer.
|
||||
gpress_omegaweighted_.resize(gg->cell_facepos[ gg->number_of_cells ], 0.0);
|
||||
if (rock_comp_props_) {
|
||||
rock_comp_.resize(grid_.number_of_cells);
|
||||
}
|
||||
for (int c = 0; c < grid_.number_of_cells; ++c) {
|
||||
allcells_[c] = c;
|
||||
}
|
||||
h_ = ifs_tpfa_construct(gg, const_cast<struct Wells*>(wells_));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/// Compute per-solve dynamic properties.
|
||||
void IncompTpfa::computePerSolveDynamicData(const double /*dt*/,
|
||||
const SimulationDataContainer& state,
|
||||
const WellState& /*well_state*/)
|
||||
{
|
||||
// Computed here:
|
||||
//
|
||||
// std::vector<double> wdp_;
|
||||
// std::vector<double> totmob_;
|
||||
// std::vector<double> omega_;
|
||||
// std::vector<double> trans_;
|
||||
// std::vector<double> gpress_omegaweighted_;
|
||||
// std::vector<double> initial_porevol_;
|
||||
// ifs_tpfa_forces forces_;
|
||||
|
||||
// wdp_
|
||||
if (wells_) {
|
||||
Opm::computeWDP(*wells_, grid_, state.saturation(), props_.density(),
|
||||
gravity_ ? gravity_[2] : 0.0, true, wdp_);
|
||||
}
|
||||
// totmob_, omega_, gpress_omegaweighted_
|
||||
if (gravity_) {
|
||||
computeTotalMobilityOmega(props_, allcells_, state.saturation(), totmob_, omega_);
|
||||
mim_ip_density_update(grid_.number_of_cells, grid_.cell_facepos,
|
||||
&omega_[0],
|
||||
&gpress_[0], &gpress_omegaweighted_[0]);
|
||||
} else {
|
||||
computeTotalMobility(props_, allcells_, state.saturation(), totmob_);
|
||||
}
|
||||
// trans_
|
||||
tpfa_eff_trans_compute(const_cast<UnstructuredGrid*>(&grid_), &totmob_[0], &htrans_[0], &trans_[0]);
|
||||
// initial_porevol_
|
||||
if (rock_comp_props_ && rock_comp_props_->isActive()) {
|
||||
computePorevolume(grid_, props_.porosity(), *rock_comp_props_, state.pressure(), initial_porevol_);
|
||||
}
|
||||
// forces_
|
||||
forces_.src = src_.empty() ? NULL : &src_[0];
|
||||
forces_.bc = bcs_;
|
||||
forces_.W = wells_;
|
||||
forces_.totmob = &totmob_[0];
|
||||
forces_.wdp = wdp_.empty() ? NULL : &wdp_[0];
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/// Compute per-iteration dynamic properties.
|
||||
void IncompTpfa::computePerIterationDynamicData(const double /*dt*/,
|
||||
const SimulationDataContainer& state,
|
||||
const WellState& well_state)
|
||||
{
|
||||
// These are the variables that get computed by this function:
|
||||
//
|
||||
// std::vector<double> porevol_
|
||||
// std::vector<double> rock_comp_
|
||||
// std::vector<double> pressures_
|
||||
|
||||
computePorevolume(grid_, props_.porosity(), *rock_comp_props_, state.pressure(), porevol_);
|
||||
if (rock_comp_props_ && rock_comp_props_->isActive()) {
|
||||
for (int cell = 0; cell < grid_.number_of_cells; ++cell) {
|
||||
rock_comp_[cell] = rock_comp_props_->rockComp(state.pressure()[cell]);
|
||||
}
|
||||
}
|
||||
if (wells_) {
|
||||
std::copy(state.pressure().begin(), state.pressure().end(), pressures_.begin());
|
||||
std::copy(well_state.bhp().begin(), well_state.bhp().end(), pressures_.begin() + grid_.number_of_cells);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/// Compute the residual in h_->b and Jacobian in h_->A.
|
||||
void IncompTpfa::assemble(const double dt,
|
||||
const SimulationDataContainer& state,
|
||||
const WellState& /*well_state*/)
|
||||
{
|
||||
const double* pressures = wells_ ? &pressures_[0] : &state.pressure()[0];
|
||||
|
||||
bool ok = ifs_tpfa_assemble_comprock_increment(const_cast<UnstructuredGrid*>(&grid_),
|
||||
&forces_, &trans_[0], &gpress_omegaweighted_[0],
|
||||
&porevol_[0], &rock_comp_[0], dt, pressures,
|
||||
&initial_porevol_[0], h_);
|
||||
if (!ok) {
|
||||
OPM_THROW(std::runtime_error, "Failed assembling pressure system.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/// Computes pressure increment, puts it in h_->x
|
||||
void IncompTpfa::solveIncrement()
|
||||
{
|
||||
// Increment is equal to -J^{-1}R.
|
||||
// The Jacobian is in h_->A, residual in h_->b.
|
||||
linsolver_.solve(h_->A, h_->b, h_->x);
|
||||
// It is not necessary to negate the increment,
|
||||
// apparently the system for the increment is generated,
|
||||
// not the Jacobian and residual as such.
|
||||
// std::transform(h_->x, h_->x + h_->A->m, h_->x, std::negate<double>());
|
||||
}
|
||||
|
||||
|
||||
namespace {
|
||||
template <class FI>
|
||||
double infnorm(FI beg, FI end)
|
||||
{
|
||||
double norm = 0.0;
|
||||
for (; beg != end; ++beg) {
|
||||
norm = std::max(norm, std::fabs(*beg));
|
||||
}
|
||||
return norm;
|
||||
}
|
||||
} // anonymous namespace
|
||||
|
||||
|
||||
|
||||
|
||||
/// Computes the inf-norm of the residual.
|
||||
double IncompTpfa::residualNorm() const
|
||||
{
|
||||
return infnorm(h_->b, h_->b + h_->A->m);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// Computes the inf-norm of pressure_increment_.
|
||||
double IncompTpfa::incrementNorm() const
|
||||
{
|
||||
return infnorm(h_->x, h_->x + h_->A->m);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// Compute the output.
|
||||
void IncompTpfa::computeResults(SimulationDataContainer& state,
|
||||
WellState& well_state) const
|
||||
{
|
||||
// Make sure h_ contains the direct-solution matrix
|
||||
// and right hand side (not jacobian and residual).
|
||||
// TODO: optimize by only adjusting b and diagonal of A.
|
||||
UnstructuredGrid* gg = const_cast<UnstructuredGrid*>(&grid_);
|
||||
ifs_tpfa_assemble(gg, &forces_, &trans_[0], &gpress_omegaweighted_[0], h_);
|
||||
|
||||
|
||||
// Make sure h_->x contains the direct solution vector.
|
||||
assert(int(state.pressure().size()) == grid_.number_of_cells);
|
||||
assert(int(state.faceflux().size()) == grid_.number_of_faces);
|
||||
std::copy(state.pressure().begin(), state.pressure().end(), h_->x);
|
||||
std::copy(well_state.bhp().begin(), well_state.bhp().end(), h_->x + grid_.number_of_cells);
|
||||
|
||||
// Obtain solution.
|
||||
ifs_tpfa_solution soln = { NULL, NULL, NULL, NULL };
|
||||
soln.cell_press = &state.pressure()[0];
|
||||
soln.face_flux = &state.faceflux()[0];
|
||||
if (wells_ != NULL) {
|
||||
assert(int(well_state.bhp().size()) == wells_->number_of_wells);
|
||||
assert(int(well_state.perfRates().size()) == wells_->well_connpos[ wells_->number_of_wells ]);
|
||||
soln.well_flux = &well_state.perfRates()[0];
|
||||
soln.well_press = &well_state.bhp()[0];
|
||||
}
|
||||
ifs_tpfa_press_flux(gg, &forces_, &trans_[0], h_, &soln); // TODO: Check what parts of h_ are used here.
|
||||
}
|
||||
|
||||
|
||||
} // namespace Opm
|
186
opm/core/pressure/IncompTpfa.hpp
Normal file
186
opm/core/pressure/IncompTpfa.hpp
Normal file
@ -0,0 +1,186 @@
|
||||
/*
|
||||
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_INCOMPTPFA_HEADER_INCLUDED
|
||||
#define OPM_INCOMPTPFA_HEADER_INCLUDED
|
||||
|
||||
#include <opm/core/pressure/tpfa/ifs_tpfa.h>
|
||||
#include <vector>
|
||||
|
||||
struct UnstructuredGrid;
|
||||
struct Wells;
|
||||
struct FlowBoundaryConditions;
|
||||
|
||||
namespace Opm
|
||||
{
|
||||
|
||||
class IncompPropertiesInterface;
|
||||
class RockCompressibility;
|
||||
class LinearSolverInterface;
|
||||
class WellState;
|
||||
class SimulationDataContainer;
|
||||
|
||||
|
||||
/// Encapsulating a tpfa pressure solver for the incompressible-fluid case.
|
||||
/// Supports gravity, wells controlled by bhp or reservoir rates,
|
||||
/// boundary conditions and simple sources as driving forces.
|
||||
/// Rock compressibility can be included, and necessary nonlinear
|
||||
/// iterations are handled.
|
||||
/// Below we use the shortcuts D for the number of dimensions, N
|
||||
/// for the number of cells and F for the number of faces.
|
||||
class IncompTpfa
|
||||
{
|
||||
public:
|
||||
/// Construct solver for incompressible case.
|
||||
/// \param[in] grid A 2d or 3d grid.
|
||||
/// \param[in] props Rock and fluid properties.
|
||||
/// \param[in] linsolver Linear solver to use.
|
||||
/// \param[in] gravity Gravity vector. If non-null, the array should
|
||||
/// have D elements.
|
||||
/// \param[in] wells The wells argument. Will be used in solution,
|
||||
/// is ignored if NULL.
|
||||
/// Note: this class observes the well object, and
|
||||
/// makes the assumption that the well topology
|
||||
/// and completions does not change during the
|
||||
/// run. However, controls (only) are allowed
|
||||
/// to change.
|
||||
/// \param[in] src Source terms. May be empty().
|
||||
/// \param[in] bcs Boundary conditions, treat as all noflow if null.
|
||||
IncompTpfa(const UnstructuredGrid& grid,
|
||||
const IncompPropertiesInterface& props,
|
||||
LinearSolverInterface& linsolver,
|
||||
const double* gravity,
|
||||
const Wells* wells,
|
||||
const std::vector<double>& src,
|
||||
const FlowBoundaryConditions* bcs);
|
||||
|
||||
/// Construct solver, possibly with rock compressibility.
|
||||
/// \param[in] grid A 2d or 3d grid.
|
||||
/// \param[in] props Rock and fluid properties.
|
||||
/// \param[in] rock_comp_props Rock compressibility properties. May be null.
|
||||
/// \param[in] linsolver Linear solver to use.
|
||||
/// \param[in] residual_tol Solution accepted if inf-norm of residual is smaller.
|
||||
/// \param[in] change_tol Solution accepted if inf-norm of change in pressure is smaller.
|
||||
/// \param[in] maxiter Maximum acceptable number of iterations.
|
||||
/// \param[in] gravity Gravity vector. If non-null, the array should
|
||||
/// have D elements.
|
||||
/// \param[in] wells The wells argument. Will be used in solution,
|
||||
/// is ignored if NULL.
|
||||
/// Note: this class observes the well object, and
|
||||
/// makes the assumption that the well topology
|
||||
/// and completions does not change during the
|
||||
/// run. However, controls (only) are allowed
|
||||
/// to change.
|
||||
/// \param[in] src Source terms. May be empty().
|
||||
/// \param[in] bcs Boundary conditions, treat as all noflow if null.
|
||||
IncompTpfa(const UnstructuredGrid& grid,
|
||||
const IncompPropertiesInterface& props,
|
||||
const RockCompressibility* rock_comp_props,
|
||||
LinearSolverInterface& linsolver,
|
||||
const double residual_tol,
|
||||
const double change_tol,
|
||||
const int maxiter,
|
||||
const double* gravity,
|
||||
const Wells* wells,
|
||||
const std::vector<double>& src,
|
||||
const FlowBoundaryConditions* bcs);
|
||||
|
||||
/// Destructor.
|
||||
virtual ~IncompTpfa();
|
||||
|
||||
/// Solve the pressure equation. If there is no pressure
|
||||
/// dependency introduced by rock compressibility effects,
|
||||
/// the equation is linear, and it is solved directly.
|
||||
/// Otherwise, the nonlinear equations ares solved by a
|
||||
/// Newton-Raphson scheme.
|
||||
/// May throw an exception if the number of iterations
|
||||
/// exceed maxiter (set in constructor).
|
||||
void solve(const double dt,
|
||||
SimulationDataContainer& state,
|
||||
WellState& well_state);
|
||||
|
||||
|
||||
/// Expose read-only reference to internal half-transmissibility.
|
||||
const std::vector<double>& getHalfTrans() const { return htrans_; }
|
||||
|
||||
protected:
|
||||
// Solve with no rock compressibility (linear eqn).
|
||||
void solveIncomp(const double dt,
|
||||
SimulationDataContainer& state,
|
||||
WellState& well_state);
|
||||
// Solve with rock compressibility (nonlinear eqn).
|
||||
void solveRockComp(const double dt,
|
||||
SimulationDataContainer& state,
|
||||
WellState& well_state);
|
||||
private:
|
||||
// Helper functions.
|
||||
void computeStaticData();
|
||||
virtual void computePerSolveDynamicData(const double dt,
|
||||
const SimulationDataContainer& state,
|
||||
const WellState& well_state);
|
||||
void computePerIterationDynamicData(const double dt,
|
||||
const SimulationDataContainer& state,
|
||||
const WellState& well_state);
|
||||
void assemble(const double dt,
|
||||
const SimulationDataContainer& state,
|
||||
const WellState& well_state);
|
||||
void solveIncrement();
|
||||
double residualNorm() const;
|
||||
double incrementNorm() const;
|
||||
void computeResults(SimulationDataContainer& state,
|
||||
WellState& well_state) const;
|
||||
|
||||
protected:
|
||||
// ------ Data that will remain unmodified after construction. ------
|
||||
const UnstructuredGrid& grid_;
|
||||
const IncompPropertiesInterface& props_;
|
||||
const RockCompressibility* rock_comp_props_;
|
||||
const LinearSolverInterface& linsolver_;
|
||||
const double residual_tol_;
|
||||
const double change_tol_;
|
||||
const int maxiter_;
|
||||
const double* gravity_; // May be NULL
|
||||
const Wells* wells_; // May be NULL, outside may modify controls (only) between calls to solve().
|
||||
const std::vector<double>& src_;
|
||||
const FlowBoundaryConditions* bcs_;
|
||||
std::vector<double> htrans_;
|
||||
std::vector<double> gpress_;
|
||||
std::vector<int> allcells_;
|
||||
|
||||
// ------ Data that will be modified for every solve. ------
|
||||
std::vector<double> trans_ ;
|
||||
std::vector<double> wdp_;
|
||||
std::vector<double> totmob_;
|
||||
std::vector<double> omega_;
|
||||
std::vector<double> gpress_omegaweighted_;
|
||||
std::vector<double> initial_porevol_;
|
||||
struct ifs_tpfa_forces forces_;
|
||||
|
||||
// ------ Data that will be modified for every solver iteration. ------
|
||||
std::vector<double> porevol_;
|
||||
std::vector<double> rock_comp_;
|
||||
std::vector<double> pressures_;
|
||||
|
||||
// ------ Internal data for the ifs_tpfa solver. ------
|
||||
struct ifs_tpfa_data* h_;
|
||||
};
|
||||
|
||||
} // namespace Opm
|
||||
|
||||
#endif // OPM_INCOMPTPFA_HEADER_INCLUDED
|
155
opm/core/pressure/IncompTpfaSinglePhase.cpp
Normal file
155
opm/core/pressure/IncompTpfaSinglePhase.cpp
Normal file
@ -0,0 +1,155 @@
|
||||
/*
|
||||
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/core/pressure/IncompTpfaSinglePhase.hpp>
|
||||
|
||||
#include <opm/core/props/IncompPropertiesSinglePhase.hpp>
|
||||
#include <opm/core/pressure/tpfa/ifs_tpfa.h>
|
||||
#include <opm/core/pressure/tpfa/trans_tpfa.h>
|
||||
// #include <opm/core/pressure/mimetic/mimetic.h>
|
||||
// #include <opm/core/pressure/flow_bc.h>
|
||||
#include <opm/core/linalg/LinearSolverInterface.hpp>
|
||||
#include <opm/core/linalg/sparse_sys.h>
|
||||
// #include <opm/core/simulator/TwophaseState.hpp>
|
||||
// #include <opm/core/simulator/WellState.hpp>
|
||||
#include <opm/common/ErrorMacros.hpp>
|
||||
// #include <opm/core/utility/miscUtilities.hpp>
|
||||
#include <opm/core/wells.h>
|
||||
// #include <iostream>
|
||||
// #include <iomanip>
|
||||
// #include <cmath>
|
||||
// #include <algorithm>
|
||||
|
||||
namespace Opm
|
||||
{
|
||||
|
||||
|
||||
|
||||
/// Construct solver for incompressible case.
|
||||
/// \param[in] grid A 2d or 3d grid.
|
||||
/// \param[in] props Rock and fluid properties.
|
||||
/// \param[in] linsolver Linear solver to use.
|
||||
/// \param[in] wells The wells used as driving forces.
|
||||
IncompTpfaSinglePhase::IncompTpfaSinglePhase(const UnstructuredGrid& grid,
|
||||
const IncompPropertiesSinglePhase& props,
|
||||
const LinearSolverInterface& linsolver,
|
||||
const Wells& wells)
|
||||
: grid_(grid),
|
||||
props_(props),
|
||||
linsolver_(linsolver),
|
||||
wells_(wells),
|
||||
htrans_(grid.cell_facepos[ grid.number_of_cells ]),
|
||||
trans_ (grid.number_of_faces),
|
||||
zeros_(grid.cell_facepos[ grid.number_of_cells ])
|
||||
{
|
||||
computeStaticData();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/// Destructor.
|
||||
IncompTpfaSinglePhase::~IncompTpfaSinglePhase()
|
||||
{
|
||||
ifs_tpfa_destroy(h_);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/// Solve the pressure equation.
|
||||
void IncompTpfaSinglePhase::solve(std::vector<double>& press,
|
||||
std::vector<double>& flux,
|
||||
std::vector<double>& bhp,
|
||||
std::vector<double>& wellrates)
|
||||
{
|
||||
// Set up properties.
|
||||
computePerSolveDynamicData();
|
||||
|
||||
// Assemble.
|
||||
UnstructuredGrid* gg = const_cast<UnstructuredGrid*>(&grid_);
|
||||
int ok = ifs_tpfa_assemble(gg, &forces_, trans_.data(), zeros_.data(), h_);
|
||||
if (!ok) {
|
||||
OPM_THROW(std::runtime_error, "Failed assembling pressure system.");
|
||||
}
|
||||
|
||||
// Solve.
|
||||
linsolver_.solve(h_->A, h_->b, h_->x);
|
||||
|
||||
// Obtain solution.
|
||||
press.resize(grid_.number_of_cells);
|
||||
flux.resize(grid_.number_of_faces);
|
||||
wellrates.resize(wells_.well_connpos[ wells_.number_of_wells ]);
|
||||
bhp.resize(wells_.number_of_wells);
|
||||
ifs_tpfa_solution soln = { NULL, NULL, NULL, NULL };
|
||||
soln.cell_press = press.data();
|
||||
soln.face_flux = flux.data();
|
||||
soln.well_press = bhp.data();
|
||||
soln.well_flux = wellrates.data();
|
||||
ifs_tpfa_press_flux(gg, &forces_, &trans_[0], h_, &soln);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/// Compute data that never changes (after construction).
|
||||
void IncompTpfaSinglePhase::computeStaticData()
|
||||
{
|
||||
UnstructuredGrid* gg = const_cast<UnstructuredGrid*>(&grid_);
|
||||
tpfa_htrans_compute(gg, props_.permeability(), &htrans_[0]);
|
||||
h_ = ifs_tpfa_construct(gg, const_cast<struct Wells*>(&wells_));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/// Compute per-solve dynamic properties.
|
||||
void IncompTpfaSinglePhase::computePerSolveDynamicData()
|
||||
{
|
||||
// Computed here:
|
||||
//
|
||||
// std::vector<double> totmob_;
|
||||
// std::vector<double> trans_;
|
||||
// ifs_tpfa_forces forces_;
|
||||
|
||||
// totmob_
|
||||
totmob_.clear();
|
||||
totmob_.resize(grid_.number_of_cells, 1.0/(*props_.viscosity()));
|
||||
// trans_
|
||||
tpfa_eff_trans_compute(const_cast<UnstructuredGrid*>(&grid_), totmob_.data(), htrans_.data(), trans_.data());
|
||||
// forces_
|
||||
forces_.src = NULL;
|
||||
forces_.bc = NULL;
|
||||
forces_.W = &wells_;
|
||||
forces_.totmob = totmob_.data();
|
||||
forces_.wdp = zeros_.data();
|
||||
}
|
||||
|
||||
|
||||
} // namespace Opm
|
88
opm/core/pressure/IncompTpfaSinglePhase.hpp
Normal file
88
opm/core/pressure/IncompTpfaSinglePhase.hpp
Normal file
@ -0,0 +1,88 @@
|
||||
/*
|
||||
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_INCOMPTPFASINGLEPHASE_HEADER_INCLUDED
|
||||
#define OPM_INCOMPTPFASINGLEPHASE_HEADER_INCLUDED
|
||||
|
||||
|
||||
#include <opm/core/pressure/tpfa/ifs_tpfa.h>
|
||||
#include <vector>
|
||||
|
||||
struct UnstructuredGrid;
|
||||
struct Wells;
|
||||
|
||||
namespace Opm
|
||||
{
|
||||
|
||||
class IncompPropertiesSinglePhase;
|
||||
class LinearSolverInterface;
|
||||
|
||||
/// Encapsulating a tpfa pressure solver for the incompressible-fluid case.
|
||||
/// Supports gravity, wells controlled by bhp or reservoir rates,
|
||||
/// boundary conditions and simple sources as driving forces.
|
||||
/// Rock compressibility can be included, and necessary nonlinear
|
||||
/// iterations are handled.
|
||||
/// Below we use the shortcuts D for the number of dimensions, N
|
||||
/// for the number of cells and F for the number of faces.
|
||||
class IncompTpfaSinglePhase
|
||||
{
|
||||
public:
|
||||
/// Construct solver for incompressible case.
|
||||
/// \param[in] grid A 2d or 3d grid.
|
||||
/// \param[in] props Rock and fluid properties.
|
||||
/// \param[in] linsolver Linear solver to use.
|
||||
/// \param[in] wells The wells used as driving forces.
|
||||
IncompTpfaSinglePhase(const UnstructuredGrid& grid,
|
||||
const IncompPropertiesSinglePhase& props,
|
||||
const LinearSolverInterface& linsolver,
|
||||
const Wells& wells);
|
||||
|
||||
/// Destructor.
|
||||
~IncompTpfaSinglePhase();
|
||||
|
||||
/// Solve the pressure equation.
|
||||
void solve(std::vector<double>& press,
|
||||
std::vector<double>& flux,
|
||||
std::vector<double>& bhp,
|
||||
std::vector<double>& wellrates);
|
||||
|
||||
private:
|
||||
// Helper functions.
|
||||
void computeStaticData();
|
||||
void computePerSolveDynamicData();
|
||||
|
||||
protected:
|
||||
// ------ Data that will remain unmodified after construction. ------
|
||||
const UnstructuredGrid& grid_;
|
||||
const IncompPropertiesSinglePhase& props_;
|
||||
const LinearSolverInterface& linsolver_;
|
||||
const Wells& wells_;
|
||||
std::vector<double> htrans_;
|
||||
std::vector<double> trans_ ;
|
||||
std::vector<double> zeros_;
|
||||
std::vector<double> totmob_;
|
||||
struct ifs_tpfa_forces forces_;
|
||||
|
||||
// ------ Internal data for the ifs_tpfa solver. ------
|
||||
struct ifs_tpfa_data* h_;
|
||||
};
|
||||
|
||||
} // namespace Opm
|
||||
|
||||
#endif // OPM_INCOMPTPFASINGLEPHASE_HEADER_INCLUDED
|
234
opm/core/pressure/flow_bc.c
Normal file
234
opm/core/pressure/flow_bc.c
Normal file
@ -0,0 +1,234 @@
|
||||
/*
|
||||
Copyright 2010, 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 <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <opm/core/pressure/flow_bc.h>
|
||||
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
/* Compute an appropriate array dimension to minimise total number of
|
||||
* (re-)allocations. */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
static size_t
|
||||
alloc_size(size_t n, size_t c)
|
||||
/* ---------------------------------------------------------------------- */
|
||||
{
|
||||
if (c < n) {
|
||||
c *= 2; /* log_2(n) allocations */
|
||||
|
||||
if (c < n) {
|
||||
c = n; /* Typically for the first few allocs */
|
||||
}
|
||||
}
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
/* Put structure in a well-defined, initial state */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
static int
|
||||
initialise_structure(struct FlowBoundaryConditions *fbc)
|
||||
/* ---------------------------------------------------------------------- */
|
||||
{
|
||||
fbc->nbc = 0;
|
||||
fbc->cond_cpty = 0;
|
||||
fbc->face_cpty = 0;
|
||||
|
||||
fbc->cond_pos = malloc(1 * sizeof *fbc->cond_pos);
|
||||
fbc->type = NULL;
|
||||
fbc->value = NULL;
|
||||
fbc->face = NULL;
|
||||
|
||||
return fbc->cond_pos != NULL;
|
||||
}
|
||||
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
static int
|
||||
expand_tables(size_t nbc,
|
||||
size_t nf ,
|
||||
struct FlowBoundaryConditions *fbc)
|
||||
/* ---------------------------------------------------------------------- */
|
||||
{
|
||||
int ok_cond, ok_face;
|
||||
size_t alloc_sz;
|
||||
void *p1, *p2, *p3, *p4;
|
||||
|
||||
ok_cond = nbc <= fbc->cond_cpty;
|
||||
ok_face = nf <= fbc->face_cpty;
|
||||
|
||||
if (! ok_cond) {
|
||||
alloc_sz = alloc_size(nbc, fbc->cond_cpty);
|
||||
|
||||
p1 = realloc(fbc->type , (alloc_sz + 0) * sizeof *fbc->type );
|
||||
p2 = realloc(fbc->value , (alloc_sz + 0) * sizeof *fbc->value );
|
||||
p3 = realloc(fbc->cond_pos, (alloc_sz + 1) * sizeof *fbc->cond_pos);
|
||||
|
||||
ok_cond = (p1 != NULL) && (p2 != NULL) && (p3 != NULL);
|
||||
|
||||
if (p1 != NULL) { fbc->type = p1; }
|
||||
if (p2 != NULL) { fbc->value = p2; }
|
||||
if (p3 != NULL) { fbc->cond_pos = p3; }
|
||||
|
||||
if (ok_cond) {
|
||||
fbc->cond_cpty = alloc_sz;
|
||||
}
|
||||
}
|
||||
|
||||
if (! ok_face) {
|
||||
alloc_sz = alloc_size(nf, fbc->face_cpty);
|
||||
|
||||
p4 = realloc(fbc->face, alloc_sz * sizeof *fbc->face);
|
||||
|
||||
ok_face = p4 != NULL;
|
||||
|
||||
if (ok_face) {
|
||||
fbc->face = p4;
|
||||
fbc->face_cpty = alloc_sz;
|
||||
}
|
||||
}
|
||||
|
||||
return ok_cond && ok_face;
|
||||
}
|
||||
|
||||
|
||||
/* ======================================================================
|
||||
* Public interface below separator
|
||||
* ====================================================================== */
|
||||
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
/* Allocate a 'FlowBoundaryConditions' structure, initially capable of
|
||||
* managing 'nbc' individual boundary conditions. */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
struct FlowBoundaryConditions *
|
||||
flow_conditions_construct(size_t nbc)
|
||||
/* ---------------------------------------------------------------------- */
|
||||
{
|
||||
int ok;
|
||||
struct FlowBoundaryConditions *fbc;
|
||||
|
||||
fbc = malloc(1 * sizeof *fbc);
|
||||
|
||||
if (fbc != NULL) {
|
||||
ok = initialise_structure(fbc);
|
||||
|
||||
ok = ok && expand_tables(nbc, nbc, fbc);
|
||||
|
||||
if (! ok) {
|
||||
flow_conditions_destroy(fbc);
|
||||
|
||||
fbc = NULL;
|
||||
} else {
|
||||
fbc->cond_pos[0] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return fbc;
|
||||
}
|
||||
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
/* Release memory resources managed by 'fbc', including the containing
|
||||
* 'struct' pointer, 'fbc'. */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
void
|
||||
flow_conditions_destroy(struct FlowBoundaryConditions *fbc)
|
||||
/* ---------------------------------------------------------------------- */
|
||||
{
|
||||
if (fbc != NULL) {
|
||||
free(fbc->face );
|
||||
free(fbc->cond_pos);
|
||||
free(fbc->value );
|
||||
free(fbc->type );
|
||||
}
|
||||
|
||||
free(fbc);
|
||||
}
|
||||
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
/* Append a new boundary condition to existing set.
|
||||
*
|
||||
* Return one (1) if successful, and zero (0) otherwise. */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
int
|
||||
flow_conditions_append(enum FlowBCType type ,
|
||||
int face ,
|
||||
double value,
|
||||
struct FlowBoundaryConditions *fbc )
|
||||
/* ---------------------------------------------------------------------- */
|
||||
{
|
||||
return flow_conditions_append_multi(type, 1, &face, value, fbc);
|
||||
}
|
||||
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
/* Append a new boundary condition that affects multiple interfaces.
|
||||
*
|
||||
* Return one (1) if successful, and zero (0) otherwise. */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
int
|
||||
flow_conditions_append_multi(enum FlowBCType type ,
|
||||
size_t nfaces,
|
||||
const int *faces ,
|
||||
double value ,
|
||||
struct FlowBoundaryConditions *fbc )
|
||||
/* ---------------------------------------------------------------------- */
|
||||
{
|
||||
int ok;
|
||||
size_t nbc;
|
||||
|
||||
nbc = fbc->nbc;
|
||||
|
||||
ok = expand_tables(nbc + 1, fbc->cond_pos[ nbc ] + nfaces, fbc);
|
||||
|
||||
if (ok) {
|
||||
memcpy(fbc->face + fbc->cond_pos[ nbc ],
|
||||
faces, nfaces * sizeof *faces);
|
||||
|
||||
fbc->type [ nbc ] = type;
|
||||
fbc->value[ nbc ] = value;
|
||||
|
||||
fbc->cond_pos[ nbc + 1 ] = fbc->cond_pos[ nbc ] + nfaces;
|
||||
|
||||
fbc->nbc += 1;
|
||||
}
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
/* Clear existing set of boundary conditions */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
void
|
||||
flow_conditions_clear(struct FlowBoundaryConditions *fbc)
|
||||
/* ---------------------------------------------------------------------- */
|
||||
{
|
||||
assert (fbc != NULL);
|
||||
|
||||
fbc->nbc = 0;
|
||||
}
|
93
opm/core/pressure/flow_bc.h
Normal file
93
opm/core/pressure/flow_bc.h
Normal file
@ -0,0 +1,93 @@
|
||||
/*
|
||||
Copyright 2010 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_FLOW_BC_HEADER_INCLUDED
|
||||
#define OPM_FLOW_BC_HEADER_INCLUDED
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
enum FlowBCType { BC_NOFLOW ,
|
||||
BC_PRESSURE ,
|
||||
BC_FLUX_TOTVOL };
|
||||
|
||||
/* Boundary condition structure.
|
||||
*
|
||||
* Condition i (in [0 .. nbc-1]) affects (outer) interface face[i], is
|
||||
* of type type[i], and specifies a target value of value[i].
|
||||
*
|
||||
* The field 'cpty' is for internal use by the implementation. */
|
||||
struct FlowBoundaryConditions {
|
||||
size_t nbc; /* Current number of bdry. conditions */
|
||||
|
||||
size_t face_cpty; /* Internal management. Do not touch */
|
||||
size_t cond_cpty; /* Internal management. Do not touch */
|
||||
|
||||
size_t *cond_pos; /* Indirection pointer into '.face' */
|
||||
|
||||
enum FlowBCType *type; /* Condition type */
|
||||
double *value; /* Condition value (target) */
|
||||
int *face; /* Outer faces affected by ind. target */
|
||||
};
|
||||
|
||||
|
||||
/* Allocate a 'FlowBoundaryConditions' structure, initially capable of
|
||||
* managing 'nbc' individual boundary conditions. */
|
||||
struct FlowBoundaryConditions *
|
||||
flow_conditions_construct(size_t nbc);
|
||||
|
||||
|
||||
/* Release memory resources managed by 'fbc', including the containing
|
||||
* 'struct' pointer, 'fbc'. */
|
||||
void
|
||||
flow_conditions_destroy(struct FlowBoundaryConditions *fbc);
|
||||
|
||||
|
||||
/* Append a new boundary condition to existing set.
|
||||
*
|
||||
* Return one (1) if successful, and zero (0) otherwise. */
|
||||
int
|
||||
flow_conditions_append(enum FlowBCType type ,
|
||||
int face ,
|
||||
double value,
|
||||
struct FlowBoundaryConditions *fbc );
|
||||
|
||||
/* Append a new boundary condition that affects multiple interfaces.
|
||||
*
|
||||
* Return one (1) if successful, and zero (0) otherwise. */
|
||||
int
|
||||
flow_conditions_append_multi(enum FlowBCType type ,
|
||||
size_t nfaces,
|
||||
const int *faces ,
|
||||
double value ,
|
||||
struct FlowBoundaryConditions *fbc );
|
||||
|
||||
|
||||
/* Clear existing set of boundary conditions */
|
||||
void
|
||||
flow_conditions_clear(struct FlowBoundaryConditions *fbc);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* OPM_FLOW_BC_HEADER_INCLUDED */
|
94
opm/core/pressure/legacy_well.h
Normal file
94
opm/core/pressure/legacy_well.h
Normal file
@ -0,0 +1,94 @@
|
||||
/*
|
||||
Copyright 2010 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_LEGACY_WELL_HEADER_INCLUDED
|
||||
#define OPM_LEGACY_WELL_HEADER_INCLUDED
|
||||
|
||||
/**
|
||||
* \file
|
||||
* Deprecated (and obsolescent) well definition. Still in use by
|
||||
* the hybridized pressure solvers.
|
||||
*/
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
/**
|
||||
* Well taxonomy.
|
||||
*/
|
||||
enum well_type { INJECTOR, PRODUCER };
|
||||
|
||||
/**
|
||||
* Control types recognised in system.
|
||||
*/
|
||||
enum well_control { BHP , RATE };
|
||||
|
||||
/**
|
||||
* Compositions recognised in injection wells.
|
||||
*/
|
||||
enum surface_component { WATER = 0, OIL = 1, GAS = 2 };
|
||||
|
||||
/**
|
||||
* Basic representation of well topology.
|
||||
*/
|
||||
struct LegacyWellCompletions {
|
||||
int number_of_wells; /**< Number of wells. */
|
||||
int *well_connpos; /**< Well topology start pointers. */
|
||||
int *well_cells; /**< Well connections */
|
||||
};
|
||||
|
||||
/**
|
||||
* Basic representation of well controls.
|
||||
*/
|
||||
struct LegacyWellControls {
|
||||
enum well_type *type; /**< Individual well taxonomy */
|
||||
enum well_control *ctrl; /**< Individual well controls */
|
||||
double *target; /**< Control target */
|
||||
double *zfrac; /**< Surface injection composition */
|
||||
};
|
||||
|
||||
/**
|
||||
* Dynamic discretisation data relating well to flow in reservoir.
|
||||
*/
|
||||
struct completion_data {
|
||||
double *WI; /**< Well indices */
|
||||
double *gpot; /**< Gravity potential */
|
||||
double *A; /**< \f$RB^{-1}\f$ for compressible flows. */
|
||||
double *phasemob; /**< Phase mobility, per connection. */
|
||||
};
|
||||
|
||||
/**
|
||||
* Convenience type alias to preserve backwards compatibility in
|
||||
* well topology definitions used by hybridised pressure solver.
|
||||
*/
|
||||
typedef struct LegacyWellCompletions well_t;
|
||||
|
||||
/**
|
||||
* Convenience type alias to preserve backwards compatiblity in
|
||||
* well control definitions used by hybridised pressure solver.
|
||||
*/
|
||||
typedef struct LegacyWellControls well_control_t;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* OPM_LEGACY_WELL_HEADER_INCLUDED */
|
253
opm/core/pressure/mimetic/mimetic.c
Normal file
253
opm/core/pressure/mimetic/mimetic.c
Normal file
@ -0,0 +1,253 @@
|
||||
/*
|
||||
Copyright 2010 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 <assert.h>
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <opm/core/linalg/blas_lapack.h>
|
||||
#include <opm/core/pressure/mimetic/mimetic.h>
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
void
|
||||
mim_ip_simple_all(int ncells, int d, int max_nconn,
|
||||
int *pconn, int *conn,
|
||||
int *fneighbour, double *fcentroid, double *fnormal,
|
||||
double *farea, double *ccentroid, double *cvol,
|
||||
double *perm, double *Binv)
|
||||
/* ------------------------------------------------------------------ */
|
||||
{
|
||||
int i, j, c, f, nconn, fpos2, lwork;
|
||||
|
||||
double *C, *N, *A, *work, s;
|
||||
|
||||
double cc[3] = { 0.0 }; /* No more than 3 space dimensions */
|
||||
|
||||
lwork = 64 * (max_nconn * d); /* 64 from ILAENV() */
|
||||
C = malloc((max_nconn * d) * sizeof *C);
|
||||
N = malloc((max_nconn * d) * sizeof *N);
|
||||
A = malloc(max_nconn * sizeof *A);
|
||||
work = malloc(lwork * sizeof *work);
|
||||
|
||||
if ((C != NULL) && (N != NULL) && (A != NULL) && (work != NULL)) {
|
||||
fpos2 = 0;
|
||||
|
||||
for (c = 0; c < ncells; c++) {
|
||||
for (j = 0; j < d; j++) {
|
||||
cc[j] = ccentroid[j + c*d];
|
||||
}
|
||||
|
||||
nconn = pconn[c + 1] - pconn[c];
|
||||
|
||||
for (i = 0; i < nconn; i++) {
|
||||
f = conn[pconn[c] + i];
|
||||
s = 2.0*(fneighbour[2 * f] == c) - 1.0;
|
||||
|
||||
A[i] = farea[f];
|
||||
|
||||
for (j = 0; j < d; j++) {
|
||||
C[i + j*nconn] = fcentroid [j + f*d] - cc[j];
|
||||
N[i + j*nconn] = s * fnormal[j + f*d];
|
||||
}
|
||||
}
|
||||
|
||||
mim_ip_simple(nconn, nconn, d, cvol[c], &perm[c * d * d],
|
||||
C, A, N, &Binv[fpos2], work, lwork);
|
||||
|
||||
fpos2 += nconn * nconn;
|
||||
}
|
||||
}
|
||||
|
||||
free(work); free(A); free(N); free(C);
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
void
|
||||
mim_ip_simple(int nf, int nconn, int d,
|
||||
double v, double *K, double *C,
|
||||
double *A, double *N,
|
||||
double *Binv,
|
||||
double *work, int lwork)
|
||||
/* ------------------------------------------------------------------ */
|
||||
{
|
||||
mim_ip_span_nullspace(nf, nconn, d, C, A, Binv, work, lwork);
|
||||
mim_ip_linpress_exact(nf, nconn, d, v, K, N, Binv, work, lwork);
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
void
|
||||
mim_ip_span_nullspace(int nf, int nconn, int d,
|
||||
double *C,
|
||||
double *A,
|
||||
double *X,
|
||||
double *work, int nwork)
|
||||
/* ------------------------------------------------------------------ */
|
||||
{
|
||||
MAT_SIZE_T m, n, k, ldC, ldX, info, lwork;
|
||||
|
||||
int i, j;
|
||||
double a1, a2;
|
||||
|
||||
double tau[3] = { 0.0 }; /* No more than 3 spatial dimensions */
|
||||
|
||||
/* Step 1) X(1:nf, 1:nf) <- I_{nf} */
|
||||
for (j = 0; j < nf; j++) {
|
||||
for (i = 0; i < nf; i++) {
|
||||
X[i + j*nconn] = 0.0;
|
||||
}
|
||||
X[j * (nconn + 1)] = 1.0;
|
||||
}
|
||||
|
||||
/* Step 2) C <- orth(A * C) */
|
||||
for (j = 0; j < d; j++) {
|
||||
for (i = 0; i < nf; i++) {
|
||||
C[i + j*nf] *= A[i];
|
||||
}
|
||||
}
|
||||
|
||||
m = nf; n = d; ldC = nf; k = d; lwork = nwork;
|
||||
dgeqrf_(&m, &n, C, &ldC, tau, work, &lwork, &info);
|
||||
dorgqr_(&m, &n, &k, C, &ldC, tau, work, &lwork, &info);
|
||||
|
||||
/* Step 3) X <- A * (X - C*C') * A */
|
||||
ldX = nconn;
|
||||
a1 = -1.0; a2 = 1.0;
|
||||
dsyrk_("Upper Triangular", "No Transpose",
|
||||
&m, &n, &a1, C, &ldC, &a2, X, &ldX);
|
||||
for (j = 0; j < nf; j++) {
|
||||
for (i = 0; i <= j; i++) {
|
||||
X[i + j*nconn] *= A[i] * A[j];
|
||||
}
|
||||
}
|
||||
|
||||
/* Account for DSYRK only assigning upper triangular part. */
|
||||
for (j = 0; j < nf; j++) {
|
||||
for (i = j + 1; i < nf; i++) {
|
||||
X[i + j*nconn] = X[j + i*nconn];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
void
|
||||
mim_ip_linpress_exact(int nf, int nconn, int d,
|
||||
double vol, double *K,
|
||||
double *N,
|
||||
double *Binv,
|
||||
double *work, int lwork)
|
||||
/* ------------------------------------------------------------------ */
|
||||
{
|
||||
MAT_SIZE_T m, n, k, ld1, ld2, ldBinv;
|
||||
int i;
|
||||
double a1, a2, t;
|
||||
|
||||
assert (lwork >= d * nf);
|
||||
#if defined(NDEBUG)
|
||||
/* Suppress warning about unused parameter. */
|
||||
(void) lwork;
|
||||
#endif
|
||||
|
||||
t = 0.0;
|
||||
for (i = 0; i < d; i++) {
|
||||
t += K[i + i*d];
|
||||
}
|
||||
|
||||
/* Step 4) T <- N*K */
|
||||
m = nf ; n = d ; k = d;
|
||||
ld1 = nf ; ld2 = d ;
|
||||
a1 = 1.0; a2 = 0.0;
|
||||
dgemm_("No Transpose", "No Transpose", &m, &n, &k,
|
||||
&a1, N, &ld1, K, &ld2, &a2, work, &ld1);
|
||||
|
||||
/* Step 5) Binv <- (N*K*N' + t*X) / vol */
|
||||
a1 = 1.0 / vol ;
|
||||
a2 = 6.0 * t / (d * vol);
|
||||
ldBinv = nconn;
|
||||
dgemm_("No Transpose", "Transpose", &m, &m, &n,
|
||||
&a1, work, &ld1, N, &ld1, &a2, Binv, &ldBinv);
|
||||
}
|
||||
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
void
|
||||
mim_ip_compute_gpress(int nc, int d, const double *grav,
|
||||
const int *pconn, const int *conn,
|
||||
const double *fcentroid, const double *ccentroid,
|
||||
double *gpress)
|
||||
/* ---------------------------------------------------------------------- */
|
||||
{
|
||||
int c, i, j;
|
||||
|
||||
const double *cc, *fc;
|
||||
|
||||
for (c = i = 0; c < nc; c++) {
|
||||
cc = ccentroid + (c * d);
|
||||
|
||||
for (; i < pconn[c + 1]; i++) {
|
||||
fc = fcentroid + (conn[i] * d);
|
||||
|
||||
gpress[i] = 0.0;
|
||||
for (j = 0; j < d; j++) {
|
||||
gpress[i] += grav[j] * (fc[j] - cc[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* inv(B) <- \lambda_t(s)*inv(B)_0 */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
void
|
||||
mim_ip_mobility_update(int nc, const int *pconn, const double *totmob,
|
||||
const double *Binv0, double *Binv)
|
||||
/* ---------------------------------------------------------------------- */
|
||||
{
|
||||
int c, i, n, p2;
|
||||
|
||||
for (c = p2 = 0; c < nc; c++) {
|
||||
n = pconn[c + 1] - pconn[c];
|
||||
|
||||
for (i = 0; i < n * n; i++) {
|
||||
Binv[p2 + i] = totmob[c] * Binv0[p2 + i];
|
||||
}
|
||||
|
||||
p2 += n * n;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* G <- \sum_i \rho_i f_i(s) * G_0 */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
void
|
||||
mim_ip_density_update(int nc, const int *pconn, const double *omega,
|
||||
const double *gpress0, double *gpress)
|
||||
/* ---------------------------------------------------------------------- */
|
||||
{
|
||||
int c, i;
|
||||
|
||||
for (c = i = 0; c < nc; c++) {
|
||||
for (; i < pconn[c + 1]; i++) {
|
||||
gpress[i] = omega[c] * gpress0[i];
|
||||
}
|
||||
}
|
||||
}
|
275
opm/core/pressure/mimetic/mimetic.h
Normal file
275
opm/core/pressure/mimetic/mimetic.h
Normal file
@ -0,0 +1,275 @@
|
||||
/*
|
||||
Copyright 2010 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_MIMETIC_HEADER_INCLUDED
|
||||
#define OPM_MIMETIC_HEADER_INCLUDED
|
||||
|
||||
/**
|
||||
* \file
|
||||
* Routines to assist mimetic discretisations of the flow equation.
|
||||
*/
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Form linear operator to span the null space of the normal vectors
|
||||
* of a grid cell.
|
||||
*
|
||||
* Specifically,
|
||||
* \f[
|
||||
* \begin{aligned}
|
||||
* X &= \operatorname{diag}(A) (I - QQ^\mathsf{T})
|
||||
* \operatorname{diag}(A), \\
|
||||
* Q &= \operatorname{orth}(\operatorname{diag}(A) C)
|
||||
* \end{aligned}
|
||||
* \f]
|
||||
* in which \f$\operatorname{orth}(M)\f$ denotes an orthonormal
|
||||
* basis for the colum space (range) of the matrix \f$M\f$,
|
||||
* represented as a matrix.
|
||||
*
|
||||
* @param[in] nf Number of faces connected to single grid cell.
|
||||
* @param[in] nconn Total number of grid cell connections.
|
||||
* Typically equal to @c nf.
|
||||
* @param[in] d Number of physical dimensions.
|
||||
* Assumed less than four.
|
||||
* @param[in,out] C Centroid vectors. Specifically,
|
||||
* \f$c_{ij} = \Bar{x}_{ij} - \Bar{x}_{cj}\f$.
|
||||
* Array of size \f$\mathit{nf}\times d\f$
|
||||
* in column major (Fortran) order.
|
||||
* Contents destroyed on output.
|
||||
* @param[in] A Interface areas.
|
||||
* @param[out] X Null space linear operator. Array of size
|
||||
* \f$\mathit{nconn}\times\mathit{nconn}\f$
|
||||
* in column major (Fortran) order. On output,
|
||||
* the upper left \f$\mathit{nf}\times\mathit{nf}\f$
|
||||
* sub-matrix contains the required null space
|
||||
* linear operator.
|
||||
* @param[out] work Scratch array of size at least @c nconn.
|
||||
* @param[in] lwork Actual size of scratch array.
|
||||
*/
|
||||
void
|
||||
mim_ip_span_nullspace(int nf, int nconn, int d,
|
||||
double *C,
|
||||
double *A,
|
||||
double *X,
|
||||
double *work, int lwork);
|
||||
|
||||
/**
|
||||
* Form (inverse) mimetic inner product that reproduces linear
|
||||
* pressure drops (constant velocity) on general polyhedral cells.
|
||||
*
|
||||
* Specifically
|
||||
* \f[
|
||||
* B^{-1} = \frac{1}{v} \big(NKN^\mathsf{T} + \frac{6t}{d}\,X\big)
|
||||
* \f]
|
||||
* in which \f$t = \operatorname{tr}(K)\f$ is the trace of \f$K\f$
|
||||
* and \f$X\f$ is the result of function mim_ip_span_nullspace().
|
||||
*
|
||||
* @param[in] nf Number of faces connected to single grid cell.
|
||||
* @param[in] nconn Total number of grid cell connections.
|
||||
* Typically equal to @c nf.
|
||||
* @param[in] d Number of physical dimensions.
|
||||
* Assumed less than four.
|
||||
* @param[in] vol Cell volume.
|
||||
* @param[in] K Permeability. A \f$d\times d\f$ matrix in
|
||||
* column major (Fortran) order.
|
||||
* @param[in] N Normal vectors. An \f$\mathit{nf}\times d\f$
|
||||
* matrix in column major (Fortran) order.
|
||||
* @param[in,out] Binv Inverse inner product result. An
|
||||
* \f$\mathit{nconn}\times\mathit{nconn}\f$
|
||||
* matrix in column major format. On input,
|
||||
* the result of mim_ip_span_nullspace(). On
|
||||
* output, the upper left
|
||||
* \f$\mathit{nf}\times\mathit{nf}\f$ sub-matrix
|
||||
* will be overwritten with \f$B^{-1}\f$.
|
||||
* @param[in,out] work Scratch array of size at least <CODE>nf * d</CODE>.
|
||||
* @param[in] lwork Actual size of scratch array.
|
||||
*/
|
||||
void
|
||||
mim_ip_linpress_exact(int nf, int nconn, int d,
|
||||
double vol, double *K,
|
||||
double *N,
|
||||
double *Binv,
|
||||
double *work, int lwork);
|
||||
|
||||
|
||||
/**
|
||||
* Convenience wrapper around the function pair mim_ip_span_nullspace()
|
||||
* and mim_ip_linpress_exact().
|
||||
*
|
||||
* @param[in] nf Number of faces connected to single grid cell.
|
||||
* @param[in] nconn Total number of grid cell connections.
|
||||
* Typically equal to @c nf.
|
||||
* @param[in] d Number of physical dimensions.
|
||||
* Assumed less than four.
|
||||
* @param[in] v Cell volume.
|
||||
* @param[in] K Permeability. A \f$d\times d\f$ matrix in
|
||||
* column major (Fortran) order.
|
||||
* @param[in,out] C Centroid vectors. Specifically,
|
||||
* \f$c_{ij} = \Bar{x}_{ij} - \Bar{x}_{cj}\f$.
|
||||
* Array of size \f$\mathit{nf}\times d\f$
|
||||
* in column major (Fortran) order.
|
||||
* Contents destroyed on output.
|
||||
* @param[in] A Interface areas.
|
||||
* @param[in] N Outward normal vectors.
|
||||
* An \f$\mathit{nf}\times d\f$ matrix in
|
||||
* column major (Fortran) order.
|
||||
* @param[out] Binv Inverse inner product result. An
|
||||
* \f$\mathit{nconn}\times\mathit{nconn}\f$
|
||||
* matrix in column major format. On
|
||||
* output, the upper left
|
||||
* \f$\mathit{nf}\times\mathit{nf}\f$ sub-matrix
|
||||
* will be overwritten with \f$B^{-1}\f$
|
||||
* defined by function mim_ip_linpress_exact().
|
||||
* @param[in,out] work Scratch array of size at least <CODE>nf * d</CODE>.
|
||||
* @param[in] lwork Actual size of scratch array.
|
||||
*/
|
||||
void
|
||||
mim_ip_simple(int nf, int nconn, int d,
|
||||
double v, double *K, double *C,
|
||||
double *A, double *N,
|
||||
double *Binv,
|
||||
double *work, int lwork);
|
||||
|
||||
|
||||
/**
|
||||
* Compute the mimetic inner products given a grid and cell-wise
|
||||
* permeability tensors.
|
||||
*
|
||||
* This function applies mim_ip_simple() to all specified cells.
|
||||
*
|
||||
* @param[in] ncells Number of cells.
|
||||
* @param[in] d Number of physical dimensions.
|
||||
* @param[in] max_ncf Maximum number of connections (faces)
|
||||
* of any individual cell.
|
||||
* @param[in] pconn Start pointers of cell-to-face topology
|
||||
* mapping.
|
||||
* @param[in] conn Actual cell-to-face topology mapping.
|
||||
* @param[in] fneighbour Face-to-cell mapping.
|
||||
* @param[in] fcentroid Face centroids.
|
||||
* @param[in] fnormal Face normals.
|
||||
* @param[in] farea Face areas.
|
||||
* @param[in] ccentroid Cell centroids.
|
||||
* @param[in] cvol Cell volumes.
|
||||
* @param[in] perm Cell permeability.
|
||||
* @param[out] Binv Inverse inner product result. Must point
|
||||
* to an array of size at least
|
||||
* \f$\sum_c n_c^2\f$ when \f$n_c\f$ denotes
|
||||
* the number of connections (faces) of
|
||||
* cell \f$c\f$.
|
||||
*/
|
||||
void
|
||||
mim_ip_simple_all(int ncells, int d, int max_ncf,
|
||||
int *pconn, int *conn,
|
||||
int *fneighbour, double *fcentroid, double *fnormal,
|
||||
double *farea, double *ccentroid, double *cvol,
|
||||
double *perm, double *Binv);
|
||||
|
||||
/**
|
||||
* Compute local, static gravity pressure contributions to Darcy
|
||||
* flow equation discretised using a mimetic finite-difference method.
|
||||
*
|
||||
* The pressure contribution of local face \f$i\f$ in cell \f$c\f$ is
|
||||
* \f[
|
||||
* \mathit{gpress}_{\mathit{pconn}_c + i} =
|
||||
* \vec{g}\cdot (\Bar{x}_{\mathit{conn}_{\mathit{pconn}_c + i}}
|
||||
* - \Bar{x}_c)
|
||||
* \f]
|
||||
*
|
||||
* @param[in] nc Number of cells.
|
||||
* @param[in] d Number of physcial dimensions.
|
||||
* @param[in] grav Gravity vector. Array of size @c d.
|
||||
* @param[in] pconn Start pointers of cell-to-face topology
|
||||
* mapping.
|
||||
* @param[in] conn Actual cell-to-face topology mapping.
|
||||
* @param[in] fcentroid Face centroids.
|
||||
* @param[in] ccentroid Cell centroids.
|
||||
* @param[out] gpress Gravity pressure result. Array of size
|
||||
* at least <CODE>pconn[nc]</CODE>.
|
||||
*/
|
||||
void
|
||||
mim_ip_compute_gpress(int nc, int d, const double *grav,
|
||||
const int *pconn, const int *conn,
|
||||
const double *fcentroid, const double *ccentroid,
|
||||
double *gpress);
|
||||
|
||||
|
||||
/**
|
||||
* Incorporate effects of multiple phases in mimetic discretisation of
|
||||
* flow equations.
|
||||
*
|
||||
* Specifically, update the (inverse) inner products \f$B^{-1}\f$
|
||||
* previously computed using function mim_ip_linpress_exact() according
|
||||
* to the rule
|
||||
* \f[
|
||||
* \Tilde{B}_c^{-1} = \frac{1}{\lambda_{T,c}} B_c^{-1},
|
||||
* \quad i=0,\dots,\mathit{nc}-1
|
||||
* \f]
|
||||
* in which \f$B_c^{-1}\f$ denotes the result of mim_ip_linpress_exact()
|
||||
* for cell \f$c\f$ and \f$\lambda_{T,c}\f$ denotes the total mobility
|
||||
* of cell \f$c\f$.
|
||||
*
|
||||
* @param[in] nc Number of cells.
|
||||
* @param[in] pconn Start pointers of cell-to-face topology
|
||||
* mapping.
|
||||
* @param[in] totmob Total mobility for all cells. Array of size @c nc.
|
||||
* @param[in] Binv0 Inverse inner product results for all cells.
|
||||
* @param[out] Binv Inverse inner product results incorporating
|
||||
* effects of multiple fluid phases.
|
||||
*/
|
||||
void
|
||||
mim_ip_mobility_update(int nc, const int *pconn, const double *totmob,
|
||||
const double *Binv0, double *Binv);
|
||||
|
||||
|
||||
/**
|
||||
* Incorporate effects of multiple fluid phases into existing, local,
|
||||
* static mimetic discretisations of gravity pressure.
|
||||
*
|
||||
* Specifically, update the result of mim_ip_compute_gpress()
|
||||
* according to the rule
|
||||
* \f[
|
||||
* \Tilde{G}_{\mathit{pconn}_c + i} = \omega_c\cdot
|
||||
* G_{\mathit{pconn}_c + i}, \quad i=\mathit{pconn}_c, \dots,
|
||||
* \mathit{pconn}_{c+1}-1, \quad c=0,\dots,\mathit{nc}-1
|
||||
* \f]
|
||||
* in which \f$\omega_c = (\sum_\alpha \lambda_{\alpha,c}
|
||||
* \rho_\alpha)/\lambda_{T,c}\f$ and \f$\Tilde{G}\f$ denotes the result
|
||||
* of function mim_ip_compute_gpress().
|
||||
*
|
||||
* @param[in] nc Number of cells.
|
||||
* @param[in] pconn Start pointers of cell-to-face topology
|
||||
* mapping.
|
||||
* @param[in] omega Sum of phase densities weighted by
|
||||
* fractional flow.
|
||||
* @param[in] gpress0 Result of mim_ip_compute_gpress().
|
||||
* @param[out] gpress Gravity pressure incorporating effects
|
||||
* of multiple fluid phases.
|
||||
*/
|
||||
void
|
||||
mim_ip_density_update(int nc, const int *pconn, const double *omega,
|
||||
const double *gpress0, double *gpress);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* OPM_MIMETIC_HEADER_INCLUDED */
|
119
opm/core/pressure/msmfem/dfs.c
Normal file
119
opm/core/pressure/msmfem/dfs.c
Normal file
@ -0,0 +1,119 @@
|
||||
/*
|
||||
Copyright 2010 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 <assert.h>
|
||||
|
||||
#include <opm/core/pressure/msmfem/dfs.h>
|
||||
|
||||
/*
|
||||
* Assign color (nonnegative number) to each connected component of graph
|
||||
*/
|
||||
void dfs (int size, int *ia, int *ja, int *ncolors, int *color, int* work)
|
||||
{
|
||||
int i, c;
|
||||
enum {UNVISITED = -1, VISITED = -2};
|
||||
int *stack = work;
|
||||
int *count = work + size;
|
||||
int *bottom = stack;
|
||||
|
||||
*ncolors = 0; /* colors are nonnegative */
|
||||
|
||||
for (i=0; i<size; ++i) {
|
||||
color [i] = UNVISITED;
|
||||
count [i] = ia[i+1]-ia[i];
|
||||
}
|
||||
|
||||
/* Push seeds on stack */
|
||||
for (i=0; i<size; ++i) {
|
||||
if(color[i] >= 0) { /* FINISHED */
|
||||
continue;
|
||||
}
|
||||
|
||||
*stack++ = i; /* push i */
|
||||
color[i] = VISITED;
|
||||
|
||||
while ( stack != bottom ) {
|
||||
c = *(stack-1); /* peek */
|
||||
|
||||
if (count[c] > 0){
|
||||
int child = ja[ia[c] + count[c]-1];
|
||||
count[c]--;
|
||||
|
||||
if (color[child] == UNVISITED) {
|
||||
*stack++ = child;
|
||||
color[c] = VISITED;
|
||||
}
|
||||
|
||||
} else {
|
||||
color[c] = *ncolors;
|
||||
--stack; /* pop c */
|
||||
}
|
||||
}
|
||||
++*ncolors;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
#if defined(TEST) && TEST
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
|
||||
/* Test code. Reads a sparse matrix from a file specified on the */
|
||||
/* command line with file format "m n\n i1 j1 v1\n i2 j2 v2\n ...". */
|
||||
/* Computes reorder sequence */
|
||||
int main (int argc, char *argv [])
|
||||
{
|
||||
int *color, *work;
|
||||
int j, ncolors;
|
||||
#if 0
|
||||
int size = 8;
|
||||
int ia[] = {0, 1, 2, 4, 5, 5, 7, 7, 8};
|
||||
int ja[] = {1, 2, 0, 3, 4, 5, 6, 6};
|
||||
#else
|
||||
int size = 3;
|
||||
int ia[] = {0,2,5,7};
|
||||
int ja[] = {0,1,1,0,2,2,1};
|
||||
#endif
|
||||
|
||||
|
||||
color = malloc (size * sizeof *color);
|
||||
work = malloc (2*size * sizeof *work);
|
||||
dfs(size, ia, ja, &ncolors, color, work);
|
||||
|
||||
|
||||
fprintf(stderr, "ncolors = %d\n", ncolors);
|
||||
for (j=0; j<size; ++j) {
|
||||
fprintf(stderr, "%d\n", color[j]);
|
||||
}
|
||||
|
||||
|
||||
free (color);
|
||||
free (work);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Local Variables: */
|
||||
/* c-basic-offset:4 */
|
||||
/* End: */
|
35
opm/core/pressure/msmfem/dfs.h
Normal file
35
opm/core/pressure/msmfem/dfs.h
Normal file
@ -0,0 +1,35 @@
|
||||
/*
|
||||
Copyright 2010 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_DFS_HEADER_INCLUDED
|
||||
#define OPM_DFS_HEADER_INCLUDED
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
void dfs (int size, int *ia, int *ja, int *ncolors, int *color, int* work);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
586
opm/core/pressure/msmfem/partition.c
Normal file
586
opm/core/pressure/msmfem/partition.c
Normal file
@ -0,0 +1,586 @@
|
||||
/*
|
||||
Copyright 2010 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 <assert.h>
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <opm/core/pressure/msmfem/dfs.h>
|
||||
#include <opm/core/pressure/msmfem/partition.h>
|
||||
|
||||
|
||||
#define MAX(a,b) (((a) > (b)) ? (a) : (b))
|
||||
|
||||
|
||||
/* [cidx{1:ndims}] = ind2sub(size, idx) */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
static void
|
||||
partition_coord_idx(int ndims, int idx, const int *size, int *cidx)
|
||||
/* ---------------------------------------------------------------------- */
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ndims; i++) {
|
||||
cidx[i] = idx % size[i];
|
||||
idx /= size[i];
|
||||
}
|
||||
|
||||
assert (idx == 0);
|
||||
}
|
||||
|
||||
|
||||
/* sub2ind(size, cidx{1:ndims}) */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
static int
|
||||
partition_lin_idx(int ndims, const int *size, const int *cidx)
|
||||
/* ---------------------------------------------------------------------- */
|
||||
{
|
||||
int i, idx;
|
||||
|
||||
idx = cidx[ndims - 1];
|
||||
for (i = ndims - 2; i >= 0; i--) {
|
||||
idx = cidx[i] + size[i]*idx;
|
||||
}
|
||||
|
||||
return idx;
|
||||
}
|
||||
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
/* Load-balanced linear distribution.
|
||||
*
|
||||
* See Eric F. Van de Velde, Concurrent Scientific Computing,
|
||||
* 1994, Springer Verlag, p. 54 (Sect. 2.3) for details. */
|
||||
static void
|
||||
partition_loadbal_lin_dist(int ndims, const int *size, const int *nbins,
|
||||
int *idx)
|
||||
/* ---------------------------------------------------------------------- */
|
||||
{
|
||||
int i, L, R, b1, b2;
|
||||
|
||||
for (i = 0; i < ndims; i++) {
|
||||
L = size[i] / nbins[i]; /* # entities per bin */
|
||||
R = size[i] % nbins[i]; /* # bins containing one extra entity */
|
||||
|
||||
b1 = idx[i] / (L + 1);
|
||||
b2 = (idx[i] - R) / L ;
|
||||
|
||||
idx[i] = MAX(b1, b2);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Partition 'nc' fine-scale Cartesian indices 'idx' from a box of
|
||||
* dimension 'fine_d' into a coarse-scale box of dimension 'coarse_d'.
|
||||
*
|
||||
* Store partition in vector 'p' (assumed to hold at least 'nc'
|
||||
* slots).
|
||||
*
|
||||
* Allocates a tiny work array to hold 'ndims' ints. Returns 'nc' if
|
||||
* successful and -1 if unable to allocate the work array. */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
int
|
||||
partition_unif_idx(int ndims, int nc,
|
||||
const int *fine_d, const int *coarse_d, const int *idx,
|
||||
int *p)
|
||||
/* ---------------------------------------------------------------------- */
|
||||
{
|
||||
int c, ret, *ix;
|
||||
|
||||
ix = malloc(ndims * sizeof *ix);
|
||||
|
||||
if (ix != NULL) {
|
||||
for (c = 0; c < nc; c++) {
|
||||
partition_coord_idx(ndims, idx[c], fine_d, ix);
|
||||
|
||||
partition_loadbal_lin_dist(ndims, fine_d, coarse_d, ix);
|
||||
|
||||
p[c] = partition_lin_idx(ndims, coarse_d, ix);
|
||||
}
|
||||
|
||||
ret = nc;
|
||||
} else {
|
||||
ret = -1;
|
||||
}
|
||||
|
||||
free(ix);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/* Renumber blocks to create contiguous block numbers from 0..n-1
|
||||
* (in other words: remove empty coarse blocks).
|
||||
*
|
||||
* Returns maximum new block number if successful and -1 if not. */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
int
|
||||
partition_compress(int n, int *p)
|
||||
/* ---------------------------------------------------------------------- */
|
||||
{
|
||||
int ret, i, max, *compr;
|
||||
|
||||
max = -1;
|
||||
assert(n > 0);
|
||||
for (i = 0; i < n; i++) {
|
||||
assert (0 <= p[i]); /* Only non-neg partitions (for now?). */
|
||||
max = MAX(max, p[i]);
|
||||
}
|
||||
|
||||
compr = malloc((max + 1) * sizeof *compr);
|
||||
|
||||
if (compr != NULL) {
|
||||
for (i = 0; i < max + 1; i++) { compr[i] = 0; }
|
||||
for (i = 0; i < n; i++) { compr[p[i]]++; }
|
||||
|
||||
compr[0] = -1 + (compr[0] > 0);
|
||||
for (i = 1; i <= max; i++) {
|
||||
compr[i] = compr[i - 1] + (compr[i] > 0);
|
||||
}
|
||||
|
||||
for (i = 0; i < n; i++) { p[i] = compr[p[i]]; }
|
||||
|
||||
ret = compr[max];
|
||||
} else {
|
||||
ret = -1;
|
||||
}
|
||||
|
||||
free(compr);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/* Free memory resources for block->cell map. */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
void
|
||||
partition_deallocate_inverse(int *pi, int *inverse)
|
||||
/* ---------------------------------------------------------------------- */
|
||||
{
|
||||
free(inverse);
|
||||
free(pi);
|
||||
}
|
||||
|
||||
|
||||
/* Allocate memory for block->cell map (CSR representation). Highest
|
||||
* block number is 'max_bin'. Grid contains 'nc' cells.
|
||||
*
|
||||
* Returns 'nc' (and sets CSR pointer pair (*pi, *inverse)) if
|
||||
* successful, -1 and pointers to NULL if not. */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
int
|
||||
partition_allocate_inverse(int nc, int max_bin,
|
||||
int **pi, int **inverse)
|
||||
/* ---------------------------------------------------------------------- */
|
||||
{
|
||||
int nbin, ret, *ptr, *i;
|
||||
|
||||
nbin = max_bin + 1;
|
||||
|
||||
ptr = malloc((nbin + 1) * sizeof *ptr);
|
||||
i = malloc(nc * sizeof *i );
|
||||
|
||||
if ((ptr == NULL) || (i == NULL)) {
|
||||
partition_deallocate_inverse(ptr, i);
|
||||
|
||||
*pi = NULL;
|
||||
*inverse = NULL;
|
||||
|
||||
ret = 0;
|
||||
} else {
|
||||
*pi = ptr;
|
||||
*inverse = i;
|
||||
|
||||
ret = nc;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
static int
|
||||
max_block(int nc, const int *p)
|
||||
/* ---------------------------------------------------------------------- */
|
||||
{
|
||||
int m, i;
|
||||
|
||||
m = -1;
|
||||
|
||||
for (i = 0; i < nc; i++) {
|
||||
m = MAX(m, p[i]);
|
||||
}
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
|
||||
/* Invert cell->block mapping 'p' (partition vector) to create
|
||||
* block->cell mapping (CSR representation, pointer pair (pi,inverse)). */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
void
|
||||
partition_invert(int nc, const int *p, int *pi, int *inverse)
|
||||
/* ---------------------------------------------------------------------- */
|
||||
{
|
||||
int nbin, b, i;
|
||||
|
||||
nbin = max_block(nc, p) + 1; /* Adjust for bin 0 */
|
||||
|
||||
/* Zero start pointers */
|
||||
for (i = 0; i < nbin + 1; i++) {
|
||||
pi[i] = 0;
|
||||
}
|
||||
|
||||
/* Count elements per bin */
|
||||
for (i = 0; i < nc; i++) { pi[ p[i] + 1 ]++; }
|
||||
|
||||
for (b = 1; b <= nbin; b++) {
|
||||
pi[0] += pi[b];
|
||||
pi[b] = pi[0] - pi[b];
|
||||
}
|
||||
|
||||
/* Insert bin elements whilst deriving start pointers */
|
||||
for (i = 0; i < nc; i++) {
|
||||
inverse[ pi[ p[i] + 1 ] ++ ] = i;
|
||||
}
|
||||
|
||||
/* Assert basic sanity */
|
||||
assert (pi[nbin] == nc);
|
||||
pi[0] = 0;
|
||||
}
|
||||
|
||||
|
||||
/* Create local cell numbering, within the cell's block, for each
|
||||
* global cell. */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
void
|
||||
partition_localidx(int nbin, const int *pi, const int *inverse,
|
||||
int *localidx)
|
||||
/* ---------------------------------------------------------------------- */
|
||||
{
|
||||
int b, i;
|
||||
|
||||
for (b = 0; b < nbin; b++) {
|
||||
for (i = pi[b]; i < pi[b + 1]; i++) {
|
||||
localidx[ inverse[i] ] = i - pi[b];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Release memory resources for internal cell-to-cell connectivity
|
||||
* (CSR representation). */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
static void
|
||||
partition_destroy_c2c(int *pc2c, int *c2c)
|
||||
/* ---------------------------------------------------------------------- */
|
||||
{
|
||||
free(c2c); free(pc2c);
|
||||
}
|
||||
|
||||
|
||||
/* Create symmetric cell-to-cell (internal) connectivity for domain
|
||||
* containing 'nc' cells. CSR representation (*pc2c,*c2c).
|
||||
*
|
||||
* Neighbourship 'neigh' is 2*nneigh array such that cell neigh[2*i+0]
|
||||
* is connected to cell neigh[2*i+1] for all i=0:nneigh-1.
|
||||
*
|
||||
* Negative 'neigh' entries represent invalid cells (outside domain).
|
||||
*
|
||||
* Returns 'nc' (and sets pointer pair) if successful, 0 (and pointer
|
||||
* pair to NULL) if not. */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
static int
|
||||
partition_create_c2c(int nc, int nneigh, const int *neigh,
|
||||
int **pc2c, int **c2c)
|
||||
/* ---------------------------------------------------------------------- */
|
||||
{
|
||||
int i, ret, c1, c2;
|
||||
|
||||
assert(nc > 0);
|
||||
assert(nneigh > 0);
|
||||
|
||||
*pc2c = malloc((nc + 1) * sizeof **pc2c);
|
||||
|
||||
if (*pc2c != NULL) {
|
||||
for (i = 0; i < nc + 1; i++) {
|
||||
(*pc2c)[i] = 0;
|
||||
}
|
||||
|
||||
for (i = 0; i < nneigh; i++) {
|
||||
c1 = neigh[2*i + 0];
|
||||
c2 = neigh[2*i + 1];
|
||||
|
||||
if ((c1 >= 0) && (c2 >= 0)) {
|
||||
/* Symmetric Laplace matrix (undirected graph) */
|
||||
(*pc2c)[ c1 + 1 ] ++;
|
||||
(*pc2c)[ c2 + 1 ] ++;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 1; i <= nc; i++) {
|
||||
(*pc2c)[i] += 1; /* Self connection */
|
||||
|
||||
(*pc2c)[0] += (*pc2c)[i];
|
||||
(*pc2c)[i] = (*pc2c)[0] - (*pc2c)[i];
|
||||
}
|
||||
|
||||
*c2c = malloc((*pc2c)[0] * sizeof **c2c);
|
||||
|
||||
if (*c2c != NULL) {
|
||||
/* Self connections */
|
||||
for (i = 0; i < nc; i++) {
|
||||
(*c2c)[ (*pc2c)[i + 1] ++ ] = i;
|
||||
}
|
||||
|
||||
for (i = 0; i < nneigh; i++) {
|
||||
c1 = neigh[2*i + 0];
|
||||
c2 = neigh[2*i + 1];
|
||||
|
||||
if ((c1 >= 0) && (c2 >= 0)) {
|
||||
/* Symmetric Laplace matrix (undirected graph) */
|
||||
(*c2c)[ (*pc2c)[ c1 + 1 ] ++ ] = c2;
|
||||
(*c2c)[ (*pc2c)[ c2 + 1 ] ++ ] = c1;
|
||||
}
|
||||
}
|
||||
|
||||
ret = nc;
|
||||
} else {
|
||||
free(*pc2c);
|
||||
*pc2c = NULL;
|
||||
|
||||
ret = 0;
|
||||
}
|
||||
} else {
|
||||
*c2c = NULL;
|
||||
|
||||
ret = 0;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/* Release dfs() memory resources. */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
static void
|
||||
deallocate_dfs_arrays(int *ia, int *ja, int *colour, int *work)
|
||||
/* ---------------------------------------------------------------------- */
|
||||
{
|
||||
free(work); free(colour); free(ja); free(ia);
|
||||
}
|
||||
|
||||
|
||||
/* Allocate dfs() memory resources to support graph containing 'n'
|
||||
* nodes and (at most) 'nnz' total connections. Return 'n' if
|
||||
* successful (and set pointers) and 0 (and set pointers to NULL) if
|
||||
* not. */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
static int
|
||||
allocate_dfs_arrays(int n, int nnz,
|
||||
int **ia, int **ja, int **colour, int **work)
|
||||
/* ---------------------------------------------------------------------- */
|
||||
{
|
||||
int ret;
|
||||
|
||||
*ia = malloc((n + 1) * sizeof **ia );
|
||||
*ja = malloc(nnz * sizeof **ja );
|
||||
*colour = malloc(n * sizeof **colour);
|
||||
*work = malloc(2 * n * sizeof **work );
|
||||
|
||||
if ((*ia == NULL) || (*ja == NULL) ||
|
||||
(*colour == NULL) || (*work == NULL)) {
|
||||
deallocate_dfs_arrays(*ia, *ja, *colour, *work);
|
||||
|
||||
*ia = NULL;
|
||||
*ja = NULL;
|
||||
*colour = NULL;
|
||||
*work = NULL;
|
||||
|
||||
ret = 0;
|
||||
} else {
|
||||
ret = n;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/* Compute maximum number of cells (*max_blk_cells) and cell-to-cell
|
||||
* connections (*max_blk_conn) over all blocks. */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
static void
|
||||
count_block_conns(int nblk,
|
||||
const int *pb2c, const int *b2c, const int *pc2c,
|
||||
int *max_blk_cells, int *max_blk_conn)
|
||||
/* ---------------------------------------------------------------------- */
|
||||
{
|
||||
int b, i, n_blk_conn;
|
||||
|
||||
*max_blk_cells = 0;
|
||||
*max_blk_conn = 0;
|
||||
|
||||
i = 0; /* == pb2c[0] */
|
||||
for (b = 0; b < nblk; b++) {
|
||||
n_blk_conn = 0;
|
||||
|
||||
for (; i < pb2c[b + 1]; i++) {
|
||||
n_blk_conn += pc2c[b2c[i] + 1] - pc2c[b2c[i]];
|
||||
}
|
||||
|
||||
*max_blk_cells = MAX(*max_blk_cells, pb2c[b + 1] - pb2c[b]);
|
||||
*max_blk_conn = MAX(*max_blk_conn , n_blk_conn);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Create block-internal (symmetric) connectivity graph (CSR
|
||||
* representation ia,ja) for connected component labelling (used in
|
||||
* splitting disconnected blocks). */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
static void
|
||||
create_block_conns(int b ,
|
||||
const int *p , const int *loc,
|
||||
const int *pb2c, const int *b2c,
|
||||
const int *pc2c, const int *c2c,
|
||||
int *ia , int *ja )
|
||||
/* ---------------------------------------------------------------------- */
|
||||
{
|
||||
int nc, c, i, j;
|
||||
|
||||
nc = pb2c[b + 1] - pb2c[b];
|
||||
|
||||
/* Clear start pointers */
|
||||
for (i = 0; i < nc + 1; i++) {
|
||||
ia[i] = 0;
|
||||
}
|
||||
|
||||
for (i = pb2c[b]; i < pb2c[b + 1]; i++) {
|
||||
c = b2c[i]; assert (loc[c] == i - pb2c[b]);
|
||||
|
||||
/* Self connections inserted in partition_create_c2c()) */
|
||||
for (j = pc2c[c]; j < pc2c[c + 1]; j++) {
|
||||
if (p[c2c[j]] == b) {
|
||||
/* Connection internal to block 'b'. Add */
|
||||
ia[loc[c] + 1] ++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert (ia[0] == 0);
|
||||
|
||||
for (i = 1; i <= nc; i++) {
|
||||
ia[0] += ia[i];
|
||||
ia[i] = ia[0] - ia[i];
|
||||
}
|
||||
|
||||
for (i = pb2c[b]; i < pb2c[b + 1]; i++) {
|
||||
c = b2c[i];
|
||||
|
||||
/* Create connections (self conn automatic) */
|
||||
for (j = pc2c[c]; j < pc2c[c + 1]; j++) {
|
||||
if (p[c2c[j]] == b) {
|
||||
ja[ ia[loc[c] + 1] ++ ] = loc[c2c[j]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ia[0] = 0;
|
||||
}
|
||||
|
||||
|
||||
/* Split disconnected coarse blocks. Preserve block numbering where
|
||||
* possible.
|
||||
*
|
||||
* Neighbourship definition 'neigh' is pointer to 2*nneigh array such
|
||||
* that cell neigh[2*i+0] is connected to cell neigh[2*i+1] for all
|
||||
* i=0:nneigh-1. Negative entries in 'neigh' represent invalid cells
|
||||
* (outside domain).
|
||||
*
|
||||
* Returns number of new blocks (0 if all blocks internally connected)
|
||||
* if successful and -1 otherwise. */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
int
|
||||
partition_split_disconnected(int nc, int nneigh, const int *neigh, int *p)
|
||||
/* ---------------------------------------------------------------------- */
|
||||
{
|
||||
int inv_ok, c2c_ok, dfs_ok;
|
||||
int i, b, ret, maxblk, ncolour, max_blk_cells, max_blk_conn;
|
||||
|
||||
int *pb2c, *b2c, *loc, *pc2c, *c2c;
|
||||
int *ia, *ja, *colour, *work;
|
||||
|
||||
maxblk = max_block(nc, p);
|
||||
|
||||
inv_ok = partition_allocate_inverse(nc, maxblk, &pb2c, &b2c);
|
||||
c2c_ok = partition_create_c2c(nc, nneigh, neigh, &pc2c, &c2c);
|
||||
loc = malloc(nc * sizeof *loc);
|
||||
|
||||
if (inv_ok && c2c_ok && (loc != NULL)) {
|
||||
partition_invert(nc, p, pb2c, b2c);
|
||||
partition_localidx(maxblk + 1, pb2c, b2c, loc);
|
||||
|
||||
count_block_conns(maxblk + 1, pb2c, b2c, pc2c,
|
||||
&max_blk_cells, &max_blk_conn);
|
||||
|
||||
dfs_ok = allocate_dfs_arrays(max_blk_cells, max_blk_conn,
|
||||
&ia, &ja, &colour, &work);
|
||||
if (dfs_ok) {
|
||||
/* Target acquired. Fire. */
|
||||
ret = 0;
|
||||
|
||||
for (b = 0; b < maxblk + 1; b++) {
|
||||
create_block_conns(b, p, loc, pb2c, b2c, pc2c, c2c, ia, ja);
|
||||
|
||||
dfs(pb2c[b + 1] - pb2c[b], ia, ja, &ncolour, colour, work);
|
||||
|
||||
if (ncolour > 1) {
|
||||
/* Block contains more than one component. Assign
|
||||
* new block numbers for cells in components
|
||||
* 1:ncomp-1. */
|
||||
for (i = pb2c[b]; i < pb2c[b + 1]; i++) {
|
||||
if (colour[i - pb2c[b]] > 0) {
|
||||
p[b2c[i]] = maxblk + ret + colour[i - pb2c[b]];
|
||||
}
|
||||
}
|
||||
|
||||
ret += ncolour - 1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ret = -1;
|
||||
}
|
||||
|
||||
deallocate_dfs_arrays(ia, ja, colour, work);
|
||||
} else {
|
||||
ret = -1;
|
||||
}
|
||||
|
||||
free(loc);
|
||||
partition_destroy_c2c(pc2c, c2c);
|
||||
partition_deallocate_inverse(pb2c, b2c);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Local Variables: */
|
||||
/* c-basic-offset:4 */
|
||||
/* End: */
|
62
opm/core/pressure/msmfem/partition.h
Normal file
62
opm/core/pressure/msmfem/partition.h
Normal file
@ -0,0 +1,62 @@
|
||||
/*
|
||||
Copyright 2010 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_PARTITION_HEADER_INCLUDED
|
||||
#define OPM_PARTITION_HEADER_INCLUDED
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
int
|
||||
partition_unif_idx(int ndims, int nc,
|
||||
const int *fine_d,
|
||||
const int *coarse_d,
|
||||
const int *idx,
|
||||
int *p);
|
||||
|
||||
int
|
||||
partition_compress(int n, int *p);
|
||||
|
||||
|
||||
int
|
||||
partition_allocate_inverse(int nc, int max_blk,
|
||||
int **pi, int **inverse);
|
||||
|
||||
void
|
||||
partition_deallocate_inverse(int *pi, int *inverse);
|
||||
|
||||
void
|
||||
partition_invert(int nc, const int *p,
|
||||
int *pi, int *inverse);
|
||||
|
||||
void
|
||||
partition_localidx(int nblk, const int *pi, const int *inverse,
|
||||
int *localidx);
|
||||
|
||||
|
||||
int
|
||||
partition_split_disconnected(int nc, int nneigh, const int *neigh,
|
||||
int *p);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* PARTITION_H_INLCUDED */
|
1631
opm/core/pressure/tpfa/cfs_tpfa_residual.c
Normal file
1631
opm/core/pressure/tpfa/cfs_tpfa_residual.c
Normal file
File diff suppressed because it is too large
Load Diff
373
opm/core/pressure/tpfa/cfs_tpfa_residual.h
Normal file
373
opm/core/pressure/tpfa/cfs_tpfa_residual.h
Normal file
@ -0,0 +1,373 @@
|
||||
/*
|
||||
Copyright 2010 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_CFS_TPFA_HEADER_INCLUDED
|
||||
#define OPM_CFS_TPFA_HEADER_INCLUDED
|
||||
|
||||
#include <opm/core/grid.h>
|
||||
#include <opm/core/wells.h>
|
||||
|
||||
#include <opm/core/pressure/tpfa/compr_source.h>
|
||||
|
||||
/**
|
||||
* \file
|
||||
* Public interface to assembler for (compressible) discrete pressure system
|
||||
* based on two-point flux approximation method. The assembler implements a
|
||||
* residual formulation that for a single cell \f$i\f$ reads
|
||||
* \f[
|
||||
* \mathit{pv}_i\cdot (1 - \sum_\alpha A_i^{-1}(p^{n+1},z^n)z^n) +
|
||||
* \Delta t\sum_\alpha A_i^{-1}(p^{n+1},z^n) \Big(\sum_j A_{ij}v_{ij}^{n+1} -
|
||||
* q_i^{n+1}\Big) = 0
|
||||
* \f]
|
||||
* in which \f$\mathit{pv}_i\f$ is the (constant or pressure-dependent)
|
||||
* pore-volume of cell \f$i\f$. Moreover, \f$\Delta t\f$ is the time step size,
|
||||
* \f$n\f$ denotes the time level, \f$A\f$ is the pressure and mass-dependent
|
||||
* fluid matrix that converts phase volumes at reservoir conditions into
|
||||
* component volumes at surface conditions and \f$v_{ij}\f$ is the vector of
|
||||
* outward (with respect to cell \f$i\f$) phase fluxes across the \f$ij\f$
|
||||
* cell-interface.
|
||||
*
|
||||
* This module's usage model is intended to be
|
||||
* -# Construct assembler
|
||||
* -# for (each time step)
|
||||
* -# while (pressure not converged)
|
||||
* -# Assemble appropriate (Jacobian) system of linear equations
|
||||
* -# Solve Jacobian system to derive pressure increments
|
||||
* -# Include increments into current state
|
||||
* -# Check convergence
|
||||
* -# Derive fluxes and, optionally, interface pressures
|
||||
* -# Solve transport by some means
|
||||
* -# Destroy assembler
|
||||
*/
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct cfs_tpfa_res_impl;
|
||||
struct CSRMatrix;
|
||||
struct compr_quantities_gen;
|
||||
|
||||
/**
|
||||
* Type encapsulating well topology and completion data (e.g., phase mobilities
|
||||
* per connection (perforation)).
|
||||
*/
|
||||
struct cfs_tpfa_res_wells {
|
||||
/**
|
||||
* All wells pertaining to a particular linear system assembly.
|
||||
* Must include current controls/targets and complete well topology.
|
||||
*/
|
||||
struct Wells *W ;
|
||||
|
||||
/**
|
||||
* Completion data describing the fluid state at the current time level.
|
||||
*/
|
||||
struct CompletionData *data;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Type encapsulating all driving forces affecting the discrete pressure system.
|
||||
*/
|
||||
struct cfs_tpfa_res_forces {
|
||||
struct cfs_tpfa_res_wells *wells; /**< Wells */
|
||||
struct compr_src *src ; /**< Explicit source terms */
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Result structure that presents the fully assembled system of linear
|
||||
* equations, linearised around the current pressure point.
|
||||
*/
|
||||
struct cfs_tpfa_res_data {
|
||||
struct CSRMatrix *J; /**< Jacobian matrix */
|
||||
double *F; /**< Residual vector (right-hand side) */
|
||||
|
||||
struct cfs_tpfa_res_impl *pimpl; /**< Internal management structure */
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Construct assembler for system of linear equations.
|
||||
*
|
||||
* @param[in] G Grid
|
||||
* @param[in] wells Well description. @c NULL in case of no wells.
|
||||
* For backwards compatibility, the constructor also
|
||||
* interprets <CODE>(wells != NULL) &&
|
||||
* (wells->W == NULL)</CODE> as "no wells present", but new
|
||||
* code should use <CODE>wells == NULL</CODE> to signify
|
||||
* "no wells".
|
||||
* @param[in] nphases Number of active fluid phases in this simulation run.
|
||||
* Needed to correctly size various internal work arrays.
|
||||
* @return Fully formed assembler structure suitable for forming systems of
|
||||
* linear equations using, e.g., function cfs_tpfa_res_assemble(). @c NULL in
|
||||
* case of allocation failure. Must be destroyed using function
|
||||
* cfs_tpfa_res_destroy().
|
||||
*/
|
||||
struct cfs_tpfa_res_data *
|
||||
cfs_tpfa_res_construct(struct UnstructuredGrid *G ,
|
||||
struct cfs_tpfa_res_wells *wells ,
|
||||
int nphases);
|
||||
|
||||
|
||||
/**
|
||||
* Destroy assembler for system of linear equations.
|
||||
*
|
||||
* Disposes of all resources acquired in a previous call to construction
|
||||
* function cfs_tpfa_res_construct(). Note that the statement
|
||||
* <CODE>
|
||||
* cfs_tpfa_res_destroy(NULL)
|
||||
* </CODE>
|
||||
* is supported and benign (i.e., behaves like <CODE>free(NULL)</CODE>).
|
||||
*
|
||||
* @param[in,out] h On input - assembler obtained through a previous call to
|
||||
* construction function cfs_tpfa_res_construct(). On output - invalid pointer.
|
||||
*/
|
||||
void
|
||||
cfs_tpfa_res_destroy(struct cfs_tpfa_res_data *h);
|
||||
|
||||
|
||||
/**
|
||||
* Assemble system of linear equations by linearising the residual around the
|
||||
* current pressure point. Assume incompressible rock (i.e., that the
|
||||
* pore-volume is independent of pressure).
|
||||
*
|
||||
* The fully assembled system is presented in <CODE>h->J</CODE> and
|
||||
* <CODE>h->F</CODE> and must be solved separately using external software.
|
||||
*
|
||||
* @param[in] G Grid.
|
||||
* @param[in] dt Time step size \f$\Delta t\f$.
|
||||
* @param[in] forces Driving forces.
|
||||
* @param[in] zc Component volumes, per pore-volume, at surface
|
||||
* conditions for all components in all cells stored
|
||||
* consecutively per cell. Array of size
|
||||
* <CODE>G->number_of_cells * cq->nphases</CODE>.
|
||||
* @param[in] cq Compressible quantities describing the current fluid
|
||||
* state. Fields @c Ac, @c dAc, @c Af, and
|
||||
* @c phasemobf must be valid.
|
||||
* @param[in] trans Background transmissibilities as defined by function
|
||||
* tpfa_trans_compute().
|
||||
* @param[in] gravcap_f Discrete gravity and capillary forces.
|
||||
* @param[in] cpress Cell pressures. One scalar value per grid cell.
|
||||
* @param[in] wpress Well (bottom-hole) pressures. One scalar value per
|
||||
* well. @c NULL in case of no wells.
|
||||
* @param[in] porevol Pore-volumes. One (positive) scalar value for each
|
||||
* grid cell.
|
||||
* @param[in,out] h On input-a valid (non-@c NULL) assembler obtained
|
||||
* from a previous call to constructor function
|
||||
* cfs_tpfa_res_construct(). On output-valid assembler
|
||||
* that includes the new system of linear equations in
|
||||
* its @c J and @c F fields.
|
||||
*
|
||||
* @return 1 if the assembled matrix was adjusted to remove a singularity. This
|
||||
* happens if all fluids are incompressible and there are no pressure conditions
|
||||
* on wells or boundaries. Otherwise return 0.
|
||||
*/
|
||||
int
|
||||
cfs_tpfa_res_assemble(struct UnstructuredGrid *G,
|
||||
double dt,
|
||||
struct cfs_tpfa_res_forces *forces,
|
||||
const double *zc,
|
||||
struct compr_quantities_gen *cq,
|
||||
const double *trans,
|
||||
const double *gravcap_f,
|
||||
const double *cpress,
|
||||
const double *wpress,
|
||||
const double *porevol,
|
||||
struct cfs_tpfa_res_data *h);
|
||||
|
||||
|
||||
/**
|
||||
* Assemble system of linear equations by linearising the residual around the
|
||||
* current pressure point. Assume compressible rock (i.e., that the pore-volume
|
||||
* depends on pressure).
|
||||
*
|
||||
* The fully assembled system is presented in <CODE>h->J</CODE> and
|
||||
* <CODE>h->F</CODE> and must be solved separately using external software.
|
||||
*
|
||||
* @param[in] G Grid.
|
||||
* @param[in] dt Time step size \f$\Delta t\f$.
|
||||
* @param[in] forces Driving forces.
|
||||
* @param[in] zc Component volumes, per pore-volume, at surface
|
||||
* conditions for all components in all cells stored
|
||||
* consecutively per cell. Array of size
|
||||
* <CODE>G->number_of_cells * cq->nphases</CODE>.
|
||||
* @param[in] cq Compressible quantities describing the current fluid
|
||||
* state. Fields @c Ac, @c dAc, @c Af, and
|
||||
* @c phasemobf must be valid.
|
||||
* @param[in] trans Background transmissibilities as defined by function
|
||||
* tpfa_trans_compute().
|
||||
* @param[in] gravcap_f Discrete gravity and capillary forces.
|
||||
* @param[in] cpress Cell pressures. One scalar value per grid cell.
|
||||
* @param[in] wpress Well (bottom-hole) pressures. One scalar value per
|
||||
* well. @c NULL in case of no wells.
|
||||
* @param[in] porevol Pore-volumes. One (positive) scalar value for each
|
||||
* grid cell.
|
||||
* @param[in] porevol0 Pore-volumes at start of time step (i.e., at time
|
||||
* level \f$n\f$). One (positive) scalar value for
|
||||
* each grid cell.
|
||||
* @param[in] rock_comp Rock compressibility. One non-negative scalar for
|
||||
* each grid cell.
|
||||
* @param[in,out] h On input-a valid (non-@c NULL) assembler obtained
|
||||
* from a previous call to constructor function
|
||||
* cfs_tpfa_res_construct(). On output-valid assembler
|
||||
* that includes the new system of linear equations in
|
||||
* its @c J and @c F fields.
|
||||
*
|
||||
* @return 1 if the assembled matrix was adjusted to remove a singularity. This
|
||||
* happens if all fluids are incompressible, the rock is incompressible, and
|
||||
* there are no pressure conditions on wells or boundaries. Otherwise return 0.
|
||||
*/
|
||||
int
|
||||
cfs_tpfa_res_comprock_assemble(
|
||||
struct UnstructuredGrid *G,
|
||||
double dt,
|
||||
struct cfs_tpfa_res_forces *forces,
|
||||
const double *zc,
|
||||
struct compr_quantities_gen *cq,
|
||||
const double *trans,
|
||||
const double *gravcap_f,
|
||||
const double *cpress,
|
||||
const double *wpress,
|
||||
const double *porevol,
|
||||
const double *porevol0,
|
||||
const double *rock_comp,
|
||||
struct cfs_tpfa_res_data *h);
|
||||
|
||||
|
||||
/**
|
||||
* Derive interface (total) Darcy fluxes from (converged) pressure solution.
|
||||
*
|
||||
* @param[in] G Grid
|
||||
* @param[in] forces Driving forces. Must correspond to those used when
|
||||
* forming the system of linear equations, e.g., in the
|
||||
* call to function cfs_tpfa_res_assemble().
|
||||
* @param[in] np Number of fluid phases (and components).
|
||||
* @param[in] trans Background transmissibilities as defined by function
|
||||
* tpfa_trans_compute(). Must correspond to equally
|
||||
* named parameter of the assembly functions.
|
||||
* @param[in] pmobc Phase mobilities stored consecutively per cell with
|
||||
* phase index cycling the most rapidly. Array of size
|
||||
* <CODE>G->number_of_cells * np</CODE>.
|
||||
* @param[in] pmobf Phase mobilities stored consecutively per interface
|
||||
* with phase index cycling the most rapidly. Array of
|
||||
* size <CODE>G->number_of_faces * np</CODE>.
|
||||
* @param[in] gravcap_f Discrete gravity and capillary forces.
|
||||
* @param[in] cpress Cell pressure values. One (positive) scalar for each
|
||||
* grid cell.
|
||||
* @param[in] wpress Well (bottom-hole) pressure values. One (positive)
|
||||
* scalar value for each well. @c NULL in case of no
|
||||
* wells.
|
||||
* @param[out] fflux Total Darcy interface fluxes. One scalar value for
|
||||
* each interface (inter-cell connection). Array of size
|
||||
* <CODE>G->number_of_faces</CODE>.
|
||||
* @param[out] wflux Total Darcy well connection fluxes. One scalar value
|
||||
* for each well connection (perforation). Array of size
|
||||
* <CODE>forces->wells->W->well_connpos
|
||||
* [forces->wells->W->number_of_wells]</CODE>.
|
||||
*/
|
||||
void
|
||||
cfs_tpfa_res_flux(struct UnstructuredGrid *G ,
|
||||
struct cfs_tpfa_res_forces *forces ,
|
||||
int np ,
|
||||
const double *trans ,
|
||||
const double *pmobc ,
|
||||
const double *pmobf ,
|
||||
const double *gravcap_f,
|
||||
const double *cpress ,
|
||||
const double *wpress ,
|
||||
double *fflux ,
|
||||
double *wflux );
|
||||
|
||||
|
||||
/**
|
||||
* Derive interface pressures from (converged) pressure solution.
|
||||
*
|
||||
* @param[in] G Grid
|
||||
* @param[in] np Number of fluid phases (and components).
|
||||
* @param[in] htrans Background one-sided ("half") transmissibilities as
|
||||
* defined by function tpfa_htrans_compute().
|
||||
* @param[in] pmobf Phase mobilities stored consecutively per interface
|
||||
* with phase index cycling the most rapidly. Array of
|
||||
* size <CODE>G->number_of_faces * np</CODE>.
|
||||
* @param[in] gravcap_f Discrete gravity and capillary forces.
|
||||
* @param[in] h System assembler. Must correspond to the assembler
|
||||
* state used to form the final system of linear equations
|
||||
* from which the converged pressure solution was derived.
|
||||
* @param[in] cpress Cell pressure values. One (positive) scalar for each
|
||||
* grid cell.
|
||||
* @param[in] fflux Total Darcy interface fluxes. One scalar value for
|
||||
* each interface (inter-cell connection). Array of size
|
||||
* <CODE>G->number_of_faces</CODE>. Typically computed
|
||||
* using function cfs_tpfa_res_flux().
|
||||
* @param[out] fpress Interface pressure values. One (positive) scalar for
|
||||
* each interface. Array of size
|
||||
* <CODE>G->number_of_faces</CODE>.
|
||||
*/
|
||||
void
|
||||
cfs_tpfa_res_fpress(struct UnstructuredGrid *G,
|
||||
int np,
|
||||
const double *htrans,
|
||||
const double *pmobf,
|
||||
const double *gravcap_f,
|
||||
struct cfs_tpfa_res_data *h,
|
||||
const double *cpress,
|
||||
const double *fflux,
|
||||
double *fpress);
|
||||
|
||||
#if 0
|
||||
void
|
||||
cfs_tpfa_retrieve_masstrans(struct UnstructuredGrid *G,
|
||||
int np,
|
||||
struct cfs_tpfa_data *h,
|
||||
double *masstrans_f);
|
||||
|
||||
void
|
||||
cfs_tpfa_retrieve_gravtrans(struct UnstructuredGrid *G,
|
||||
int np,
|
||||
struct cfs_tpfa_data *h,
|
||||
double *gravtrans_f);
|
||||
|
||||
double
|
||||
cfs_tpfa_impes_maxtime(struct UnstructuredGrid *G,
|
||||
struct compr_quantities *cq,
|
||||
const double *trans,
|
||||
const double *porevol,
|
||||
struct cfs_tpfa_data *h,
|
||||
const double *dpmobf,
|
||||
const double *surf_dens,
|
||||
const double *gravity);
|
||||
|
||||
void
|
||||
cfs_tpfa_expl_mass_transport(struct UnstructuredGrid *G,
|
||||
well_t *W,
|
||||
struct completion_data *wdata,
|
||||
int np,
|
||||
double dt,
|
||||
const double *porevol,
|
||||
struct cfs_tpfa_data *h,
|
||||
double *surf_vol);
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* OPM_CFS_TPFA_HEADER_INCLUDED */
|
98
opm/core/pressure/tpfa/compr_quant_general.h
Normal file
98
opm/core/pressure/tpfa/compr_quant_general.h
Normal file
@ -0,0 +1,98 @@
|
||||
/*
|
||||
Copyright 2010 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_COMPR_QUANT_HEADER_INCLUDED
|
||||
#define OPM_COMPR_QUANT_HEADER_INCLUDED
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
/**
|
||||
* \file
|
||||
* Module defining derived fluid quantities needed to discretise compressible
|
||||
* and miscible pressure (flow) problems.
|
||||
*/
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Aggregate structure that represents an atomic view of the current fluid
|
||||
* state. These quantities are used directly in the cfs_tpfa_residual module to
|
||||
* discretise a particular, linearised flow problem.
|
||||
*/
|
||||
struct compr_quantities_gen {
|
||||
/**
|
||||
* Number of fluid phases. The pressure solvers also assume that the number
|
||||
* of fluid components (at surface conditions) equals the number of fluid
|
||||
* phases.
|
||||
*/
|
||||
int nphases;
|
||||
|
||||
/**
|
||||
* Pressure and mass-dependent fluid matrix that converts phase volumes at
|
||||
* reservoir conditions into component volumes at surface conditions. Obeys
|
||||
* the defining formula
|
||||
* \f[
|
||||
* A = RB^{-1}
|
||||
* \f]
|
||||
* in which \f$R\f$ denotes the miscibility ratios (i.e., the dissolved
|
||||
* gas-oil ratio, \f$R_s\f$ and the evaporated oil-gas ratio, \f$R_v\f$)
|
||||
* collected as a matrix and \f$B\f$ is the diagonal matrix of
|
||||
* formation-volume expansion factors. The function is sampled in each grid
|
||||
* cell. Array of size <CODE>nphases * nphases * nc</CODE>.
|
||||
*/
|
||||
double *Ac;
|
||||
|
||||
/**
|
||||
* Derivative of \f$A\f$ with respect to pressure,
|
||||
* \f[
|
||||
* \frac{\partial A}{\partial p} = \frac{\partial R}{\partial p}B^{-1} +
|
||||
* R\frac{\partial B^{-1}}{\partial p} = (\frac{\partial R}{\partial p} -
|
||||
* A\frac{\partial B}{\partial p})B^{-1}
|
||||
* \f]
|
||||
* sampled in each grid cell. Array of size
|
||||
* <CODE>nphases * nphases * nc</CODE>.
|
||||
*/
|
||||
double *dAc;
|
||||
|
||||
/**
|
||||
* Fluid matrix sampled at each interface. Possibly as a result of an
|
||||
* upwind calculation. Array of size <CODE>nphases * nphases * nf</CODE>.
|
||||
*/
|
||||
double *Af;
|
||||
|
||||
/**
|
||||
* Phase mobility per interface. Possibly defined through an upwind
|
||||
* calculation. Array of size <CODE>nphases * nf</CODE>.
|
||||
*/
|
||||
double *phasemobf;
|
||||
|
||||
/**
|
||||
* Deceptively named "volume-discrepancy" term. Array of size @c nc.
|
||||
* Unused by the cfs_tpfa_residual module.
|
||||
*/
|
||||
double *voldiscr;
|
||||
};
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* OPM_COMPR_QUANT_HEADER_INCLUDED */
|
97
opm/core/pressure/tpfa/compr_source.h
Normal file
97
opm/core/pressure/tpfa/compr_source.h
Normal file
@ -0,0 +1,97 @@
|
||||
/*===========================================================================
|
||||
//
|
||||
// File: compr_source.h
|
||||
//
|
||||
// Created: 2011-10-19 19:14:30+0200
|
||||
//
|
||||
// Authors: Ingeborg S. Ligaarden <Ingeborg.Ligaarden@sintef.no>
|
||||
// Jostein R. Natvig <Jostein.R.Natvig@sintef.no>
|
||||
// Halvor M. Nilsen <HalvorMoll.Nilsen@sintef.no>
|
||||
// Atgeirr F. Rasmussen <atgeirr@sintef.no>
|
||||
// Bård Skaflestad <Bard.Skaflestad@sintef.no>
|
||||
//
|
||||
//==========================================================================*/
|
||||
|
||||
|
||||
/*
|
||||
Copyright 2011 SINTEF ICT, Applied Mathematics.
|
||||
Copyright 2011 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_COMPR_SOURCE_H_HEADER
|
||||
#define OPM_COMPR_SOURCE_H_HEADER
|
||||
|
||||
/**
|
||||
* \file
|
||||
* Data structures and support routines needed to represent explicit,
|
||||
* compressible source terms.
|
||||
*/
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Collection of explicit, compressible source terms.
|
||||
*/
|
||||
struct compr_src {
|
||||
/**
|
||||
* Number of source terms.
|
||||
*/
|
||||
int nsrc;
|
||||
|
||||
/**
|
||||
* Source term capacity. Client code should treat this member as read-only.
|
||||
* The field is used in internal memory management.
|
||||
*/
|
||||
int cpty;
|
||||
|
||||
/**
|
||||
* Number of fluid phases.
|
||||
*/
|
||||
int nphases;
|
||||
|
||||
/**
|
||||
* Cells influenced by explicit source terms. Array of size @c cpty, the
|
||||
* @c nsrc first elements (only) of which are valid.
|
||||
*/
|
||||
int *cell;
|
||||
|
||||
/**
|
||||
* Total Darcy rate of inflow (measured at reservoir conditions) of each
|
||||
* individual source term. Sign convention: Positive rate into reservoir
|
||||
* (i.e., injection) and negative rate out of reservoir (production).
|
||||
* Array of size @c cpty, the @c nsrc first elements (only) of which are
|
||||
* valid.
|
||||
*/
|
||||
double *flux;
|
||||
|
||||
/**
|
||||
* Injection composition for all explicit source terms. Not referenced for
|
||||
* production sources (i.e., those terms for which <CODE>->flux[]</CODE> is
|
||||
* negative). Array of size <CODE>nphases * cpty</CODE>, the
|
||||
* <CODE>nphases * nsrc</CODE> of which (only) are valid.
|
||||
*/
|
||||
double *saturation;
|
||||
};
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* OPM_COMPR_SOURCE_H_HEADER */
|
875
opm/core/pressure/tpfa/ifs_tpfa.c
Normal file
875
opm/core/pressure/tpfa/ifs_tpfa.c
Normal file
@ -0,0 +1,875 @@
|
||||
#include "config.h"
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <opm/core/linalg/sparse_sys.h>
|
||||
|
||||
#include <opm/core/wells.h>
|
||||
#include <opm/core/well_controls.h>
|
||||
#include <opm/core/pressure/flow_bc.h>
|
||||
#include <opm/core/pressure/tpfa/ifs_tpfa.h>
|
||||
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
static void
|
||||
mult_csr_matrix(const struct CSRMatrix *A,
|
||||
const double *u,
|
||||
double *v)
|
||||
/* ---------------------------------------------------------------------- */
|
||||
{
|
||||
int j;
|
||||
size_t i;
|
||||
|
||||
for (i = 0, j = 0; i < A->m; i++) {
|
||||
v[i] = 0.0;
|
||||
|
||||
for (; j < A->ia[i + 1]; j++) {
|
||||
v[i] += A->sa[j] * u[ A->ja[j] ];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct ifs_tpfa_impl {
|
||||
double *fgrav; /* Accumulated grav contrib/face */
|
||||
double *work;
|
||||
|
||||
/* Linear storage */
|
||||
double *ddata;
|
||||
};
|
||||
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
static void
|
||||
impl_deallocate(struct ifs_tpfa_impl *pimpl)
|
||||
/* ---------------------------------------------------------------------- */
|
||||
{
|
||||
if (pimpl != NULL) {
|
||||
free(pimpl->ddata);
|
||||
}
|
||||
|
||||
free(pimpl);
|
||||
}
|
||||
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
static struct ifs_tpfa_impl *
|
||||
impl_allocate(struct UnstructuredGrid *G,
|
||||
struct Wells *W)
|
||||
/* ---------------------------------------------------------------------- */
|
||||
{
|
||||
struct ifs_tpfa_impl *new;
|
||||
|
||||
size_t nnu;
|
||||
size_t ddata_sz;
|
||||
|
||||
nnu = G->number_of_cells;
|
||||
if (W != NULL) {
|
||||
nnu += W->number_of_wells;
|
||||
}
|
||||
|
||||
ddata_sz = 2 * nnu; /* b, x */
|
||||
ddata_sz += 1 * G->number_of_faces; /* fgrav */
|
||||
ddata_sz += 1 * nnu; /* work */
|
||||
|
||||
new = malloc(1 * sizeof *new);
|
||||
|
||||
if (new != NULL) {
|
||||
new->ddata = malloc(ddata_sz * sizeof *new->ddata);
|
||||
|
||||
if (new->ddata == NULL) {
|
||||
impl_deallocate(new);
|
||||
new = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return new;
|
||||
}
|
||||
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
static struct CSRMatrix *
|
||||
ifs_tpfa_construct_matrix(struct UnstructuredGrid *G,
|
||||
struct Wells *W)
|
||||
/* ---------------------------------------------------------------------- */
|
||||
{
|
||||
int f, c1, c2, w, i, nc, nnu;
|
||||
size_t nnz;
|
||||
|
||||
struct CSRMatrix *A;
|
||||
|
||||
nc = nnu = G->number_of_cells;
|
||||
if (W != NULL) {
|
||||
nnu += W->number_of_wells;
|
||||
}
|
||||
|
||||
A = csrmatrix_new_count_nnz(nnu);
|
||||
|
||||
if (A != NULL) {
|
||||
/* Self connections */
|
||||
for (c1 = 0; c1 < nnu; c1++) {
|
||||
A->ia[ c1 + 1 ] = 1;
|
||||
}
|
||||
|
||||
/* Other connections */
|
||||
for (f = 0; f < G->number_of_faces; f++) {
|
||||
c1 = G->face_cells[2*f + 0];
|
||||
c2 = G->face_cells[2*f + 1];
|
||||
|
||||
if ((c1 >= 0) && (c2 >= 0)) {
|
||||
A->ia[ c1 + 1 ] += 1;
|
||||
A->ia[ c2 + 1 ] += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (W != NULL) {
|
||||
/* Well <-> cell connections */
|
||||
for (w = i = 0; w < W->number_of_wells; w++) {
|
||||
for (; i < W->well_connpos[w + 1]; i++) {
|
||||
c1 = W->well_cells[i];
|
||||
|
||||
A->ia[ 0 + c1 + 1 ] += 1; /* c -> w */
|
||||
A->ia[ nc + w + 1 ] += 1; /* w -> c */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nnz = csrmatrix_new_elms_pushback(A);
|
||||
if (nnz == 0) {
|
||||
csrmatrix_delete(A);
|
||||
A = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (A != NULL) {
|
||||
/* Fill self connections */
|
||||
for (i = 0; i < nnu; i++) {
|
||||
A->ja[ A->ia[ i + 1 ] ++ ] = i;
|
||||
}
|
||||
|
||||
/* Fill other connections */
|
||||
for (f = 0; f < G->number_of_faces; f++) {
|
||||
c1 = G->face_cells[2*f + 0];
|
||||
c2 = G->face_cells[2*f + 1];
|
||||
|
||||
if ((c1 >= 0) && (c2 >= 0)) {
|
||||
A->ja[ A->ia[ c1 + 1 ] ++ ] = c2;
|
||||
A->ja[ A->ia[ c2 + 1 ] ++ ] = c1;
|
||||
}
|
||||
}
|
||||
|
||||
if (W != NULL) {
|
||||
/* Fill well <-> cell connections */
|
||||
for (w = i = 0; w < W->number_of_wells; w++) {
|
||||
for (; i < W->well_connpos[w + 1]; i++) {
|
||||
c1 = W->well_cells[i];
|
||||
|
||||
A->ja[ A->ia[ 0 + c1 + 1 ] ++ ] = nc + w;
|
||||
A->ja[ A->ia[ nc + w + 1 ] ++ ] = c1 ;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert ((size_t) A->ia[ nnu ] == nnz);
|
||||
|
||||
/* Guarantee sorted rows */
|
||||
csrmatrix_sortrows(A);
|
||||
}
|
||||
|
||||
return A;
|
||||
}
|
||||
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
/* fgrav = accumarray(cf(j), grav(j).*sgn(j), [nf, 1]) */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
static void
|
||||
compute_grav_term(struct UnstructuredGrid *G, const double *gpress,
|
||||
double *fgrav)
|
||||
/* ---------------------------------------------------------------------- */
|
||||
{
|
||||
int c, i, f, c1, c2;
|
||||
double s;
|
||||
|
||||
vector_zero(G->number_of_faces, fgrav);
|
||||
|
||||
for (c = i = 0; c < G->number_of_cells; c++) {
|
||||
for (; i < G->cell_facepos[c + 1]; i++) {
|
||||
f = G->cell_faces[i];
|
||||
|
||||
c1 = G->face_cells[2*f + 0];
|
||||
c2 = G->face_cells[2*f + 1];
|
||||
|
||||
s = 2.0*(c1 == c) - 1.0;
|
||||
|
||||
if ((c1 >= 0) && (c2 >= 0)) {
|
||||
fgrav[f] += s * gpress[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
static void
|
||||
assemble_bhp_well(int nc, int w,
|
||||
const struct Wells *W ,
|
||||
const double *mt ,
|
||||
const double *wdp,
|
||||
struct ifs_tpfa_data *h )
|
||||
/* ---------------------------------------------------------------------- */
|
||||
{
|
||||
int c, i, wdof;
|
||||
size_t jc, jw;
|
||||
double trans, bhp;
|
||||
|
||||
struct WellControls *ctrls;
|
||||
|
||||
ctrls = W->ctrls[ w ];
|
||||
wdof = nc + w;
|
||||
bhp = well_controls_get_current_target(ctrls);
|
||||
|
||||
jw = csrmatrix_elm_index(wdof, wdof, h->A);
|
||||
|
||||
for (i = W->well_connpos[w]; i < W->well_connpos[w + 1]; i++) {
|
||||
|
||||
c = W->well_cells [ i ];
|
||||
trans = mt[ c ] * W->WI[ i ];
|
||||
|
||||
jc = csrmatrix_elm_index(c, c, h->A);
|
||||
|
||||
/* c<->c diagonal contribution from well */
|
||||
h->A->sa[ jc ] += trans;
|
||||
h->b [ c ] += trans * (bhp + wdp[ i ]);
|
||||
|
||||
/* w<->w diagonal contribution from well, trivial eqn. */
|
||||
h->A->sa[ jw ] += trans;
|
||||
h->b [ wdof ] += trans * bhp;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
static void
|
||||
assemble_rate_well(int nc, int w,
|
||||
const struct Wells *W ,
|
||||
const double *mt ,
|
||||
const double *wdp,
|
||||
struct ifs_tpfa_data *h )
|
||||
/* ---------------------------------------------------------------------- */
|
||||
{
|
||||
int c, i, wdof;
|
||||
size_t jcc, jcw, jwc, jww;
|
||||
double trans, resv;
|
||||
|
||||
struct WellControls *ctrls;
|
||||
|
||||
ctrls = W->ctrls[ w ];
|
||||
wdof = nc + w;
|
||||
resv = well_controls_get_current_target(ctrls);
|
||||
|
||||
jww = csrmatrix_elm_index(wdof, wdof, h->A);
|
||||
|
||||
for (i = W->well_connpos[w]; i < W->well_connpos[w + 1]; i++) {
|
||||
|
||||
c = W->well_cells[ i ];
|
||||
|
||||
jcc = csrmatrix_elm_index(c , c , h->A);
|
||||
jcw = csrmatrix_elm_index(c , wdof, h->A);
|
||||
jwc = csrmatrix_elm_index(wdof, c , h->A);
|
||||
|
||||
/* Connection transmissibility */
|
||||
trans = mt[ c ] * W->WI[ i ];
|
||||
|
||||
/* c->w connection */
|
||||
h->A->sa[ jcc ] += trans;
|
||||
h->A->sa[ jcw ] -= trans;
|
||||
h->b [ c ] += trans * wdp[ i ];
|
||||
|
||||
/* w->c connection */
|
||||
h->A->sa[ jwc ] -= trans;
|
||||
h->A->sa[ jww ] += trans;
|
||||
h->b [ wdof ] -= trans * wdp[ i ];
|
||||
}
|
||||
|
||||
h->b[ wdof ] += resv;
|
||||
}
|
||||
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
static void
|
||||
assemble_shut_well(int nc, int w,
|
||||
const struct Wells *W ,
|
||||
const double *mt ,
|
||||
struct ifs_tpfa_data *h )
|
||||
/* ---------------------------------------------------------------------- */
|
||||
{
|
||||
int c, i, wdof;
|
||||
size_t jw;
|
||||
double trans;
|
||||
|
||||
/* The equation added for a shut well w is
|
||||
* \sum_{c~w} T_{w,c} * mt_c p_w = 0.0
|
||||
* where c~w indicates the cell perforated by w, T are production
|
||||
* indices, mt is total mobility and p_w is the bottom-hole
|
||||
* pressure.
|
||||
* Note: post-processing in ifs_tpfa_press_flux() may change the
|
||||
* well bhp away from zero, the equation used here is intended
|
||||
* just to
|
||||
* 1) give the same solution as if the well was not there for
|
||||
* all other degrees of freedom,
|
||||
* 2) not disturb the conditioning of the matrix.
|
||||
*/
|
||||
|
||||
wdof = nc + w;
|
||||
|
||||
jw = csrmatrix_elm_index(wdof, wdof, h->A);
|
||||
|
||||
for (i = W->well_connpos[w]; i < W->well_connpos[w + 1]; i++) {
|
||||
|
||||
c = W->well_cells [ i ];
|
||||
trans = mt[ c ] * W->WI[ i ];
|
||||
|
||||
/* w<->w diagonal contribution from well, trivial eqn. */
|
||||
h->A->sa[ jw ] += trans;
|
||||
}
|
||||
h->b[ wdof ] = 0.0;
|
||||
}
|
||||
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
static void
|
||||
assemble_well_contrib(int nc ,
|
||||
const struct Wells *W ,
|
||||
const double *mt ,
|
||||
const double *wdp,
|
||||
struct ifs_tpfa_data *h ,
|
||||
int *all_rate,
|
||||
int *ok)
|
||||
/* ---------------------------------------------------------------------- */
|
||||
{
|
||||
int w, p, np;
|
||||
|
||||
struct WellControls *ctrls;
|
||||
|
||||
np = W->number_of_phases;
|
||||
|
||||
*all_rate = 1;
|
||||
*ok = 1;
|
||||
|
||||
for (w = 0; w < W->number_of_wells; w++) {
|
||||
ctrls = W->ctrls[ w ];
|
||||
|
||||
if (well_controls_well_is_stopped(ctrls) ) {
|
||||
fprintf(stderr, "Stopped well detected: will be treated as completely shut\n");
|
||||
/* Treat this well as a shut well, isolated from the domain. */
|
||||
assemble_shut_well(nc, w, W, mt, h);
|
||||
|
||||
} else {
|
||||
|
||||
assert (well_controls_get_current(ctrls) < well_controls_get_num(ctrls));
|
||||
|
||||
switch (well_controls_get_current_type(ctrls)) {
|
||||
case BHP:
|
||||
case THP : // THP is implemented as a BHP target
|
||||
*all_rate = 0;
|
||||
assemble_bhp_well (nc, w, W, mt, wdp, h);
|
||||
break;
|
||||
|
||||
case RESERVOIR_RATE:
|
||||
if (W->type[w] == PRODUCER) {
|
||||
/* Ensure minimal consistency. A PRODUCER should
|
||||
* specify a phase distribution of all ones in the
|
||||
* case of RESV controls. */
|
||||
const double * distr = well_controls_get_current_distr( ctrls );
|
||||
for (p = 0; p < np; p++) {
|
||||
if (distr[p] != 1.0) {
|
||||
*ok = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* Ignore phase distribution for non-PRODUCERs */
|
||||
*ok = 1;
|
||||
}
|
||||
|
||||
if (*ok) {
|
||||
assemble_rate_well(nc, w, W, mt, wdp, h);
|
||||
}
|
||||
break;
|
||||
|
||||
case SURFACE_RATE:
|
||||
/* We cannot handle this case, since we do
|
||||
* not have access to formation volume factors,
|
||||
* needed to convert between reservoir and
|
||||
* surface rates
|
||||
*/
|
||||
fprintf(stderr, "ifs_tpfa cannot handle SURFACE_RATE well controls.\n");
|
||||
*ok = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
static int
|
||||
assemble_bc_contrib(struct UnstructuredGrid *G ,
|
||||
const struct FlowBoundaryConditions *bc ,
|
||||
const double *trans,
|
||||
struct ifs_tpfa_data *h )
|
||||
/* ---------------------------------------------------------------------- */
|
||||
{
|
||||
int is_neumann, is_outflow;
|
||||
int f, c1, c2;
|
||||
|
||||
size_t i, j, ix;
|
||||
double s, t;
|
||||
|
||||
is_neumann = 1;
|
||||
|
||||
for (i = 0; i < bc->nbc; i++) {
|
||||
if (bc->type[ i ] == BC_PRESSURE) {
|
||||
is_neumann = 0;
|
||||
|
||||
for (j = bc->cond_pos[ i ]; j < bc->cond_pos[i + 1]; j++) {
|
||||
f = bc->face[ j ];
|
||||
c1 = G->face_cells[2*f + 0];
|
||||
c2 = G->face_cells[2*f + 1];
|
||||
|
||||
assert ((c1 < 0) ^ (c2 < 0)); /* BCs on ext. faces only */
|
||||
|
||||
is_outflow = c1 >= 0;
|
||||
|
||||
t = trans[ f ];
|
||||
s = 2.0*is_outflow - 1.0;
|
||||
c1 = is_outflow ? c1 : c2;
|
||||
ix = csrmatrix_elm_index(c1, c1, h->A);
|
||||
|
||||
h->A->sa[ ix ] += t;
|
||||
h->b [ c1 ] += t * bc->value[ i ];
|
||||
h->b [ c1 ] -= s * t * h->pimpl->fgrav[ f ];
|
||||
}
|
||||
}
|
||||
|
||||
else if (bc->type[ i ] == BC_FLUX_TOTVOL) {
|
||||
/* We currently support individual flux faces only. */
|
||||
assert (bc->cond_pos[i + 1] - bc->cond_pos[i] == 1);
|
||||
|
||||
for (j = bc->cond_pos[ i ]; j < bc->cond_pos[i + 1]; j++) {
|
||||
f = bc->face[ j ];
|
||||
c1 = G->face_cells[2*f + 0];
|
||||
c2 = G->face_cells[2*f + 1];
|
||||
|
||||
assert ((c1 < 0) ^ (c2 < 0)); /* BCs on ext. faces only */
|
||||
|
||||
c1 = (c1 >= 0) ? c1 : c2;
|
||||
|
||||
/* Interpret BC as flow *INTO* cell */
|
||||
h->b[ c1 ] += bc->value[ i ];
|
||||
}
|
||||
}
|
||||
|
||||
/* Other types currently not handled */
|
||||
}
|
||||
|
||||
return is_neumann;
|
||||
}
|
||||
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
static void
|
||||
boundary_fluxes(struct UnstructuredGrid *G ,
|
||||
const struct FlowBoundaryConditions *bc ,
|
||||
const double *trans ,
|
||||
const double *cpress,
|
||||
const struct ifs_tpfa_data *h ,
|
||||
double *fflux )
|
||||
/* ---------------------------------------------------------------------- */
|
||||
{
|
||||
int f, c1, c2;
|
||||
size_t i, j;
|
||||
double s, dh;
|
||||
|
||||
for (i = 0; i < bc->nbc; i++) {
|
||||
if (bc->type[ i ] == BC_PRESSURE) {
|
||||
for (j = bc->cond_pos[ i ]; j < bc->cond_pos[ i + 1 ]; j++) {
|
||||
|
||||
f = bc->face[ j ];
|
||||
c1 = G->face_cells[2*f + 0];
|
||||
c2 = G->face_cells[2*f + 1];
|
||||
|
||||
assert ((c1 < 0) ^ (c2 < 0));
|
||||
|
||||
if (c1 < 0) { /* Environment -> c2 */
|
||||
dh = bc->value[ i ] - cpress[c2];
|
||||
}
|
||||
else { /* c1 -> environment */
|
||||
dh = cpress[c1] - bc->value[ i ];
|
||||
}
|
||||
|
||||
fflux[f] = trans[f] * (dh + h->pimpl->fgrav[f]);
|
||||
}
|
||||
}
|
||||
|
||||
else if (bc->type[ i ] == BC_FLUX_TOTVOL) {
|
||||
assert (bc->cond_pos[i+1] - bc->cond_pos[i] == 1);
|
||||
|
||||
for (j = bc->cond_pos[ i ]; j < bc->cond_pos[ i + 1 ]; j++) {
|
||||
|
||||
f = bc->face[ j ];
|
||||
c1 = G->face_cells[2*f + 0];
|
||||
c2 = G->face_cells[2*f + 1];
|
||||
|
||||
assert ((c1 < 0) ^ (c2 < 0));
|
||||
|
||||
/* BC flux is positive into reservoir. */
|
||||
s = 2.0*(c1 < 0) - 1.0;
|
||||
|
||||
fflux[f] = s * bc->value[ i ];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
static void
|
||||
well_solution(const struct UnstructuredGrid *G ,
|
||||
const struct ifs_tpfa_forces *F ,
|
||||
const struct ifs_tpfa_data *h ,
|
||||
struct ifs_tpfa_solution *soln)
|
||||
/* ---------------------------------------------------------------------- */
|
||||
{
|
||||
int c, w, i, shut;
|
||||
double bhp, dp, trans;
|
||||
const double *mt, *WI, *wdp;
|
||||
|
||||
if (soln->well_press != NULL) {
|
||||
/* Extract BHP directly from solution vector for non-shut wells */
|
||||
for (w = 0; w < F->W->number_of_wells; w++) {
|
||||
if (well_controls_get_current(F->W->ctrls[w]) >= 0) {
|
||||
soln->well_press[w] = h->x[G->number_of_cells + w];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (soln->well_flux != NULL) {
|
||||
WI = F->W->WI;
|
||||
mt = F->totmob;
|
||||
wdp = F->wdp;
|
||||
|
||||
for (w = i = 0; w < F->W->number_of_wells; w++) {
|
||||
bhp = h->x[G->number_of_cells + w];
|
||||
shut = (well_controls_get_current(F->W->ctrls[w]) < 0);
|
||||
|
||||
for (; i < F->W->well_connpos[ w + 1 ]; i++) {
|
||||
|
||||
c = F->W->well_cells[ i ];
|
||||
|
||||
trans = mt[ c ] * WI[ i ];
|
||||
dp = bhp + wdp[ i ] - soln->cell_press[ c ];
|
||||
|
||||
soln->well_flux[i] = shut ? 0.0 : trans * dp;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
static void
|
||||
assemble_incompressible(struct UnstructuredGrid *G ,
|
||||
const struct ifs_tpfa_forces *F ,
|
||||
const double *trans ,
|
||||
const double *gpress,
|
||||
struct ifs_tpfa_data *h ,
|
||||
int *singular,
|
||||
int *ok )
|
||||
/* ---------------------------------------------------------------------- */
|
||||
{
|
||||
int c1, c2, c, i, f, j1, j2;
|
||||
|
||||
int res_is_neumann, wells_are_rate;
|
||||
|
||||
double s;
|
||||
|
||||
*ok = 1;
|
||||
csrmatrix_zero( h->A);
|
||||
vector_zero (h->A->m, h->b);
|
||||
|
||||
compute_grav_term(G, gpress, h->pimpl->fgrav);
|
||||
|
||||
for (c = i = 0; c < G->number_of_cells; c++) {
|
||||
j1 = csrmatrix_elm_index(c, c, h->A);
|
||||
|
||||
for (; i < G->cell_facepos[c + 1]; i++) {
|
||||
f = G->cell_faces[i];
|
||||
|
||||
c1 = G->face_cells[2*f + 0];
|
||||
c2 = G->face_cells[2*f + 1];
|
||||
|
||||
s = 2.0*(c1 == c) - 1.0;
|
||||
c2 = (c1 == c) ? c2 : c1;
|
||||
|
||||
h->b[c] -= trans[f] * (s * h->pimpl->fgrav[f]);
|
||||
|
||||
if (c2 >= 0) {
|
||||
j2 = csrmatrix_elm_index(c, c2, h->A);
|
||||
|
||||
h->A->sa[j1] += trans[f];
|
||||
h->A->sa[j2] -= trans[f];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Assemble contributions from driving forces other than gravity */
|
||||
res_is_neumann = 1;
|
||||
wells_are_rate = 1;
|
||||
if (F != NULL) {
|
||||
if ((F->W != NULL) && (F->totmob != NULL) && (F->wdp != NULL)) {
|
||||
/* Contributions from wells */
|
||||
|
||||
/* Ensure modicum of internal consistency. */
|
||||
assert (h->A->m ==
|
||||
(size_t) G->number_of_cells +
|
||||
(size_t) F->W->number_of_wells);
|
||||
|
||||
assemble_well_contrib(G->number_of_cells, F->W,
|
||||
F->totmob, F->wdp, h,
|
||||
&wells_are_rate, ok);
|
||||
}
|
||||
|
||||
if (F->bc != NULL) {
|
||||
/* Contributions from boundary conditions */
|
||||
res_is_neumann = assemble_bc_contrib(G, F->bc, trans, h);
|
||||
}
|
||||
|
||||
if (F->src != NULL) {
|
||||
/* Contributions from explicit source terms. */
|
||||
for (c = 0; c < G->number_of_cells; c++) {
|
||||
h->b[c] += F->src[c];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*singular = res_is_neumann && wells_are_rate;
|
||||
}
|
||||
|
||||
/* ======================================================================
|
||||
* Public interface below separator.
|
||||
* ====================================================================== */
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
struct ifs_tpfa_data *
|
||||
ifs_tpfa_construct(struct UnstructuredGrid *G,
|
||||
struct Wells *W)
|
||||
/* ---------------------------------------------------------------------- */
|
||||
{
|
||||
struct ifs_tpfa_data *new;
|
||||
|
||||
new = malloc(1 * sizeof *new);
|
||||
|
||||
if (new != NULL) {
|
||||
new->pimpl = impl_allocate(G, W);
|
||||
new->A = ifs_tpfa_construct_matrix(G, W);
|
||||
|
||||
if ((new->pimpl == NULL) || (new->A == NULL)) {
|
||||
ifs_tpfa_destroy(new);
|
||||
new = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (new != NULL) {
|
||||
new->b = new->pimpl->ddata;
|
||||
new->x = new->b + new->A->m;
|
||||
|
||||
new->pimpl->fgrav = new->x + new->A->m;
|
||||
new->pimpl->work = new->pimpl->fgrav + G->number_of_faces;
|
||||
}
|
||||
|
||||
return new;
|
||||
}
|
||||
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
int
|
||||
ifs_tpfa_assemble(struct UnstructuredGrid *G ,
|
||||
const struct ifs_tpfa_forces *F ,
|
||||
const double *trans ,
|
||||
const double *gpress,
|
||||
struct ifs_tpfa_data *h )
|
||||
/* ---------------------------------------------------------------------- */
|
||||
{
|
||||
int system_singular, ok;
|
||||
|
||||
assemble_incompressible(G, F, trans, gpress, h, &system_singular, &ok);
|
||||
|
||||
if (ok && system_singular) {
|
||||
/* Remove zero eigenvalue associated to constant pressure */
|
||||
h->A->sa[0] *= 2.0;
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
int
|
||||
ifs_tpfa_assemble_comprock(struct UnstructuredGrid *G ,
|
||||
const struct ifs_tpfa_forces *F ,
|
||||
const double *trans ,
|
||||
const double *gpress ,
|
||||
const double *porevol ,
|
||||
const double *rock_comp,
|
||||
const double dt ,
|
||||
const double *pressure ,
|
||||
struct ifs_tpfa_data *h )
|
||||
/* ---------------------------------------------------------------------- */
|
||||
{
|
||||
int c, system_singular, ok;
|
||||
size_t j;
|
||||
double d;
|
||||
|
||||
assemble_incompressible(G, F, trans, gpress, h, &system_singular, &ok);
|
||||
|
||||
/*
|
||||
* The extra term of the equation is
|
||||
*
|
||||
* porevol*rock_comp*(p - p0)/dt.
|
||||
*
|
||||
* The p part goes on the diagonal, the p0 on the rhs.
|
||||
* We don't care if the system was singular before this modification,
|
||||
* after it will always be nonsingular.
|
||||
*/
|
||||
if (ok) {
|
||||
for (c = 0; c < G->number_of_cells; c++) {
|
||||
j = csrmatrix_elm_index(c, c, h->A);
|
||||
|
||||
d = porevol[c] * rock_comp[c] / dt;
|
||||
|
||||
h->A->sa[j] += d;
|
||||
h->b[c] += d * pressure[c];
|
||||
}
|
||||
}
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
int
|
||||
ifs_tpfa_assemble_comprock_increment(struct UnstructuredGrid *G ,
|
||||
const struct ifs_tpfa_forces *F ,
|
||||
const double *trans ,
|
||||
const double *gpress ,
|
||||
const double *porevol ,
|
||||
const double *rock_comp,
|
||||
const double dt ,
|
||||
const double *prev_pressure,
|
||||
const double *initial_porevolume,
|
||||
struct ifs_tpfa_data *h )
|
||||
/* ---------------------------------------------------------------------- */
|
||||
{
|
||||
int c, w, wdof, system_singular, ok;
|
||||
size_t j;
|
||||
double *v, dpvdt;
|
||||
|
||||
ok = 1;
|
||||
assemble_incompressible(G, F, trans, gpress, h, &system_singular, &ok);
|
||||
|
||||
/* We want to solve a Newton step for the residual
|
||||
* (porevol(pressure)-porevol(initial_pressure))/dt + residual_for_incompressible
|
||||
*
|
||||
*/
|
||||
|
||||
if (ok) {
|
||||
v = h->pimpl->work;
|
||||
mult_csr_matrix(h->A, prev_pressure, v);
|
||||
|
||||
for (c = 0; c < G->number_of_cells; c++) {
|
||||
j = csrmatrix_elm_index(c, c, h->A);
|
||||
|
||||
dpvdt = (porevol[c] - initial_porevolume[c]) / dt;
|
||||
|
||||
h->A->sa[j] += porevol[c] * rock_comp[c] / dt;
|
||||
h->b[c] -= dpvdt + v[c];
|
||||
}
|
||||
|
||||
if (F->W != NULL) {
|
||||
wdof = G->number_of_cells;
|
||||
|
||||
for (w = 0; w < F->W->number_of_wells; w++, wdof++) {
|
||||
h->b[wdof] -= v[wdof];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
void
|
||||
ifs_tpfa_press_flux(struct UnstructuredGrid *G ,
|
||||
const struct ifs_tpfa_forces *F ,
|
||||
const double *trans,
|
||||
struct ifs_tpfa_data *h ,
|
||||
struct ifs_tpfa_solution *soln )
|
||||
/* ---------------------------------------------------------------------- */
|
||||
{
|
||||
int c1, c2, f;
|
||||
double dh;
|
||||
|
||||
double *cpress, *fflux;
|
||||
|
||||
assert (soln != NULL);
|
||||
assert (soln->cell_press != NULL);
|
||||
assert (soln->face_flux != NULL);
|
||||
|
||||
cpress = soln->cell_press;
|
||||
fflux = soln->face_flux ;
|
||||
|
||||
/* Assign cell pressure directly from solution vector */
|
||||
memcpy(cpress, h->x, G->number_of_cells * sizeof *cpress);
|
||||
|
||||
for (f = 0; f < G->number_of_faces; f++) {
|
||||
c1 = G->face_cells[2*f + 0];
|
||||
c2 = G->face_cells[2*f + 1];
|
||||
|
||||
if ((c1 >= 0) && (c2 >= 0)) {
|
||||
dh = cpress[c1] - cpress[c2] + h->pimpl->fgrav[f];
|
||||
fflux[f] = trans[f] * dh;
|
||||
} else {
|
||||
fflux[f] = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
if (F != NULL) {
|
||||
if (F->bc != NULL) {
|
||||
boundary_fluxes(G, F->bc, trans, cpress, h, fflux);
|
||||
}
|
||||
|
||||
if (F->W != NULL) {
|
||||
well_solution(G, F, h, soln);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
void
|
||||
ifs_tpfa_destroy(struct ifs_tpfa_data *h)
|
||||
/* ---------------------------------------------------------------------- */
|
||||
{
|
||||
if (h != NULL) {
|
||||
csrmatrix_delete(h->A);
|
||||
impl_deallocate (h->pimpl);
|
||||
}
|
||||
|
||||
free(h);
|
||||
}
|
148
opm/core/pressure/tpfa/ifs_tpfa.h
Normal file
148
opm/core/pressure/tpfa/ifs_tpfa.h
Normal file
@ -0,0 +1,148 @@
|
||||
/*
|
||||
Copyright 2010 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_IFS_TPFA_HEADER_INCLUDED
|
||||
#define OPM_IFS_TPFA_HEADER_INCLUDED
|
||||
|
||||
/**
|
||||
* \file
|
||||
* Interfaces and data structures to assemble a system of simultaneous linear
|
||||
* equations discretising a flow problem that is either incompressible or
|
||||
* features rock compressibility using the two-point flux approximation method.
|
||||
*
|
||||
* Includes support for reconstructing the Darcy flux field as well as well
|
||||
* connection fluxes.
|
||||
*/
|
||||
|
||||
#include <opm/core/grid.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct ifs_tpfa_impl;
|
||||
struct CSRMatrix;
|
||||
struct FlowBoundaryConditions;
|
||||
struct Wells;
|
||||
|
||||
/**
|
||||
* Main data structure presenting a view of an assembled system of simultaneous
|
||||
* linear equations which may be solved using external software.
|
||||
*/
|
||||
struct ifs_tpfa_data {
|
||||
struct CSRMatrix *A; /**< Coefficient matrix */
|
||||
double *b; /**< Right-hand side */
|
||||
double *x; /**< Solution */
|
||||
|
||||
struct ifs_tpfa_impl *pimpl; /**< Internal management structure */
|
||||
};
|
||||
|
||||
/**
|
||||
* Solution variables.
|
||||
*/
|
||||
struct ifs_tpfa_solution {
|
||||
double *cell_press; /**< Cell pressures */
|
||||
double *face_flux ; /**< Interface fluxes */
|
||||
|
||||
double *well_press; /**< Bottom-hole pressures for each well */
|
||||
double *well_flux ; /**< Well connection total fluxes */
|
||||
};
|
||||
|
||||
/**
|
||||
* Driving forces pertaining to a particular model setup.
|
||||
*/
|
||||
struct ifs_tpfa_forces {
|
||||
const double *src; /**< Explicit source terms */
|
||||
const struct FlowBoundaryConditions *bc ; /**< Boundary conditions */
|
||||
|
||||
const struct Wells *W ; /**< Well topology */
|
||||
const double *totmob; /**< Total mobility in each cell */
|
||||
const double *wdp ; /**< Gravity adjustment at each perforation */
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Allocate TPFA management structure capable of assembling a system of
|
||||
* simultaneous linear equations corresponding to a particular grid and well
|
||||
* configuration.
|
||||
*
|
||||
* @param[in] G Grid.
|
||||
* @param[in] W Well topology.
|
||||
* @return Fully formed TPFA management structure if successful, @c NULL in case
|
||||
* of allocation failure.
|
||||
*/
|
||||
struct ifs_tpfa_data *
|
||||
ifs_tpfa_construct(struct UnstructuredGrid *G,
|
||||
struct Wells *W);
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @param[in] G
|
||||
* @param[in] F
|
||||
* @param[in] trans
|
||||
* @param[in] gpress
|
||||
* @param[in,out] h
|
||||
* @return
|
||||
*/
|
||||
int
|
||||
ifs_tpfa_assemble(struct UnstructuredGrid *G ,
|
||||
const struct ifs_tpfa_forces *F ,
|
||||
const double *trans ,
|
||||
const double *gpress,
|
||||
struct ifs_tpfa_data *h );
|
||||
|
||||
int
|
||||
ifs_tpfa_assemble_comprock(struct UnstructuredGrid *G ,
|
||||
const struct ifs_tpfa_forces *F ,
|
||||
const double *trans ,
|
||||
const double *gpress ,
|
||||
const double *porevol ,
|
||||
const double *rock_comp,
|
||||
const double dt ,
|
||||
const double *pressure ,
|
||||
struct ifs_tpfa_data *h );
|
||||
int
|
||||
ifs_tpfa_assemble_comprock_increment(struct UnstructuredGrid *G ,
|
||||
const struct ifs_tpfa_forces *F ,
|
||||
const double *trans ,
|
||||
const double *gpress ,
|
||||
const double *porevol ,
|
||||
const double *rock_comp,
|
||||
const double dt ,
|
||||
const double *prev_pressure ,
|
||||
const double *initial_porevolume,
|
||||
struct ifs_tpfa_data *h );
|
||||
|
||||
|
||||
void
|
||||
ifs_tpfa_press_flux(struct UnstructuredGrid *G ,
|
||||
const struct ifs_tpfa_forces *F ,
|
||||
const double *trans,
|
||||
struct ifs_tpfa_data *h ,
|
||||
struct ifs_tpfa_solution *soln );
|
||||
|
||||
void
|
||||
ifs_tpfa_destroy(struct ifs_tpfa_data *h);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* OPM_IFS_TPFA_HEADER_INCLUDED */
|
92
opm/core/props/BlackoilPhases.hpp
Normal file
92
opm/core/props/BlackoilPhases.hpp
Normal file
@ -0,0 +1,92 @@
|
||||
/*
|
||||
Copyright 2010, 2011, 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_BLACKOILPHASES_HEADER_INCLUDED
|
||||
#define OPM_BLACKOILPHASES_HEADER_INCLUDED
|
||||
|
||||
|
||||
namespace Opm
|
||||
{
|
||||
|
||||
class BlackoilPhases
|
||||
{
|
||||
public:
|
||||
static const int MaxNumPhases = 3;
|
||||
|
||||
// "Crypto phases" are "phases" (or rather "conservation quantities") in the
|
||||
// sense that they can be active or not and canonical indices can be translated
|
||||
// to and from active ones. That said, they are not considered by num_phases or
|
||||
// MaxNumPhases. The crypto phases which are currently implemented are solvent,
|
||||
// polymer and energy.
|
||||
static const int NumCryptoPhases = 3;
|
||||
|
||||
// enum ComponentIndex { Water = 0, Oil = 1, Gas = 2 };
|
||||
enum PhaseIndex { Aqua = 0, Liquid = 1, Vapour = 2, Solvent = 3, Polymer = 4, Energy = 5 };
|
||||
};
|
||||
|
||||
struct PhaseUsage : public BlackoilPhases
|
||||
{
|
||||
int num_phases;
|
||||
int phase_used[MaxNumPhases + NumCryptoPhases];
|
||||
int phase_pos[MaxNumPhases + NumCryptoPhases];
|
||||
bool has_solvent;
|
||||
bool has_polymer;
|
||||
bool has_energy;
|
||||
};
|
||||
|
||||
/// Check or assign presence of a formed, free phase. Limited to
|
||||
/// the 'BlackoilPhases'.
|
||||
///
|
||||
/// Use a std::vector<PhasePresence> to represent the conditions
|
||||
/// in an entire model.
|
||||
class PhasePresence
|
||||
{
|
||||
public:
|
||||
PhasePresence()
|
||||
: present_(0)
|
||||
{}
|
||||
|
||||
bool hasFreeWater() const { return present(BlackoilPhases::Aqua ); }
|
||||
bool hasFreeOil () const { return present(BlackoilPhases::Liquid); }
|
||||
bool hasFreeGas () const { return present(BlackoilPhases::Vapour); }
|
||||
|
||||
void setFreeWater() { insert(BlackoilPhases::Aqua ); }
|
||||
void setFreeOil () { insert(BlackoilPhases::Liquid); }
|
||||
void setFreeGas () { insert(BlackoilPhases::Vapour); }
|
||||
|
||||
bool operator==(const PhasePresence& other) const { return present_ == other.present_; }
|
||||
bool operator!=(const PhasePresence& other) const { return !this->operator==(other); }
|
||||
|
||||
private:
|
||||
unsigned char present_;
|
||||
|
||||
bool present(const BlackoilPhases::PhaseIndex i) const
|
||||
{
|
||||
return present_ & (1 << i);
|
||||
}
|
||||
|
||||
void insert(const BlackoilPhases::PhaseIndex i)
|
||||
{
|
||||
present_ |= (1 << i);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Opm
|
||||
|
||||
#endif // OPM_BLACKOILPHASES_HEADER_INCLUDED
|
258
opm/core/props/BlackoilPropertiesBasic.cpp
Normal file
258
opm/core/props/BlackoilPropertiesBasic.cpp
Normal file
@ -0,0 +1,258 @@
|
||||
/*
|
||||
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/props/BlackoilPropertiesBasic.hpp>
|
||||
#include <opm/parser/eclipse/Units/Units.hpp>
|
||||
#include <opm/common/ErrorMacros.hpp>
|
||||
#include <iostream>
|
||||
|
||||
namespace Opm
|
||||
{
|
||||
|
||||
BlackoilPropertiesBasic::BlackoilPropertiesBasic(const ParameterGroup& param,
|
||||
const int dim,
|
||||
const int num_cells)
|
||||
{
|
||||
double poro = param.getDefault("porosity", 1.0);
|
||||
using namespace Opm::unit;
|
||||
using namespace Opm::prefix;
|
||||
double perm = param.getDefault("permeability", 100.0)*milli*darcy;
|
||||
rock_.init(dim, num_cells, poro, perm);
|
||||
pvt_.init(param);
|
||||
satprops_.init(param);
|
||||
if (pvt_.numPhases() != satprops_.numPhases()) {
|
||||
OPM_THROW(std::runtime_error, "BlackoilPropertiesBasic::BlackoilPropertiesBasic() - Inconsistent number of phases in pvt data ("
|
||||
<< pvt_.numPhases() << ") and saturation-dependent function data (" << satprops_.numPhases() << ").");
|
||||
}
|
||||
}
|
||||
|
||||
BlackoilPropertiesBasic::~BlackoilPropertiesBasic()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/// \return D, the number of spatial dimensions.
|
||||
int BlackoilPropertiesBasic::numDimensions() const
|
||||
{
|
||||
return rock_.numDimensions();
|
||||
}
|
||||
|
||||
/// \return N, the number of cells.
|
||||
int BlackoilPropertiesBasic::numCells() const
|
||||
{
|
||||
return rock_.numCells();
|
||||
}
|
||||
|
||||
/// \return Array of N porosity values.
|
||||
const double* BlackoilPropertiesBasic::porosity() const
|
||||
{
|
||||
return rock_.porosity();
|
||||
}
|
||||
|
||||
/// \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* BlackoilPropertiesBasic::permeability() const
|
||||
{
|
||||
return rock_.permeability();
|
||||
}
|
||||
|
||||
|
||||
// ---- Fluid interface ----
|
||||
|
||||
/// \return P, the number of phases (also the number of components).
|
||||
int BlackoilPropertiesBasic::numPhases() const
|
||||
{
|
||||
return pvt_.numPhases();
|
||||
}
|
||||
|
||||
/// \return Object describing the active phases.
|
||||
PhaseUsage BlackoilPropertiesBasic::phaseUsage() const
|
||||
{
|
||||
return pvt_.phaseUsage();
|
||||
}
|
||||
|
||||
/// \param[in] n Number of data points.
|
||||
/// \param[in] p Array of n pressure values.
|
||||
/// \param[in] T Array of n temperature values.
|
||||
/// \param[in] z Array of nP surface volume values.
|
||||
/// \param[in] cells Array of n cell indices to be associated with the p and z values.
|
||||
/// \param[out] mu Array of nP viscosity values, array must be valid before calling.
|
||||
/// \param[out] dmudp If non-null: array of nP viscosity derivative values,
|
||||
/// array must be valid before calling.
|
||||
void BlackoilPropertiesBasic::viscosity(const int n,
|
||||
const double* p,
|
||||
const double* T,
|
||||
const double* z,
|
||||
const int* /*cells*/,
|
||||
double* mu,
|
||||
double* dmudp) const
|
||||
{
|
||||
if (dmudp) {
|
||||
OPM_THROW(std::runtime_error, "BlackoilPropertiesBasic::viscosity() -- derivatives of viscosity not yet implemented.");
|
||||
} else {
|
||||
pvt_.mu(n, p, T, z, mu);
|
||||
}
|
||||
}
|
||||
|
||||
/// \param[in] n Number of data points.
|
||||
/// \param[in] p Array of n pressure values.
|
||||
/// \param[in] T Array of n temperature values.
|
||||
/// \param[in] z Array of nP surface volume values.
|
||||
/// \param[in] cells Array of n cell indices to be associated with the p and z values.
|
||||
/// \param[out] A Array of nP^2 values, array must be valid before calling.
|
||||
/// The P^2 values for a cell give the matrix A = RB^{-1} which
|
||||
/// relates z to u by z = Au. The matrices are output in Fortran order.
|
||||
/// \param[out] dAdp If non-null: array of nP^2 matrix derivative values,
|
||||
/// array must be valid before calling. The matrices are output
|
||||
/// in Fortran order.
|
||||
void BlackoilPropertiesBasic::matrix(const int n,
|
||||
const double* p,
|
||||
const double* T,
|
||||
const double* /*z*/,
|
||||
const int* /*cells*/,
|
||||
double* A,
|
||||
double* dAdp) const
|
||||
{
|
||||
const int np = numPhases();
|
||||
assert(np <= 2);
|
||||
double B[2]; // Must be enough since component classes do not handle more than 2.
|
||||
pvt_.B(1, p, T, 0, B);
|
||||
// Compute A matrix
|
||||
// #pragma omp parallel for
|
||||
for (int i = 0; i < n; ++i) {
|
||||
double* m = A + i*np*np;
|
||||
std::fill(m, m + np*np, 0.0);
|
||||
// Diagonal entries only.
|
||||
for (int phase = 0; phase < np; ++phase) {
|
||||
m[phase + phase*np] = 1.0/B[phase];
|
||||
}
|
||||
}
|
||||
|
||||
// Derivative of A matrix.
|
||||
if (dAdp) {
|
||||
// #pragma omp parallel for
|
||||
for (int i = 0; i < n; ++i) {
|
||||
double* m = dAdp + i*np*np;
|
||||
std::fill(m, m + np*np, 0.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// \param[in] n Number of data points.
|
||||
/// \param[in] A Array of nP^2 values, where the P^2 values for a cell give the
|
||||
/// matrix A = RB^{-1} which relates z to u by z = Au. The matrices
|
||||
/// are assumed to be in Fortran order, and are typically the result
|
||||
/// of a call to the method matrix().
|
||||
/// \param[in] cells The index of the grid cell of each data point.
|
||||
/// \param[out] rho Array of nP density values, array must be valid before calling.
|
||||
void BlackoilPropertiesBasic::density(const int n,
|
||||
const double* A,
|
||||
const int* /*cells*/,
|
||||
double* rho) const
|
||||
{
|
||||
const int np = numPhases();
|
||||
const double* sdens = pvt_.surfaceDensities();
|
||||
// #pragma omp parallel for
|
||||
for (int i = 0; i < n; ++i) {
|
||||
for (int phase = 0; phase < np; ++phase) {
|
||||
rho[np*i + phase] = 0.0;
|
||||
for (int comp = 0; comp < np; ++comp) {
|
||||
rho[np*i + phase] += A[i*np*np + np*phase + comp]*sdens[comp];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Densities of stock components at surface conditions.
|
||||
/// \return Array of P density values.
|
||||
const double* BlackoilPropertiesBasic::surfaceDensity(int /*cellIdx*/) const
|
||||
{
|
||||
return pvt_.surfaceDensities();
|
||||
}
|
||||
|
||||
|
||||
/// \param[in] n Number of data points.
|
||||
/// \param[in] s Array of nP saturation values.
|
||||
/// \param[in] cells Array of n cell indices to be associated with the s values.
|
||||
/// \param[out] kr Array of nP relperm values, array must be valid before calling.
|
||||
/// \param[out] dkrds If non-null: array of nP^2 relperm derivative values,
|
||||
/// array must be valid before calling.
|
||||
/// The P^2 derivative matrix is
|
||||
/// m_{ij} = \frac{dkr_i}{ds^j},
|
||||
/// and is output in Fortran order (m_00 m_10 m_20 m01 ...)
|
||||
void BlackoilPropertiesBasic::relperm(const int n,
|
||||
const double* s,
|
||||
const int* /*cells*/,
|
||||
double* kr,
|
||||
double* dkrds) const
|
||||
{
|
||||
satprops_.relperm(n, s, kr, dkrds);
|
||||
}
|
||||
|
||||
|
||||
/// \param[in] n Number of data points.
|
||||
/// \param[in] s Array of nP saturation values.
|
||||
/// \param[in] cells Array of n cell indices to be associated with the s values.
|
||||
/// \param[out] pc Array of nP capillary pressure values, array must be valid before calling.
|
||||
/// \param[out] dpcds If non-null: array of nP^2 derivative values,
|
||||
/// array must be valid before calling.
|
||||
/// The P^2 derivative matrix is
|
||||
/// m_{ij} = \frac{dpc_i}{ds^j},
|
||||
/// and is output in Fortran order (m_00 m_10 m_20 m01 ...)
|
||||
void BlackoilPropertiesBasic::capPress(const int n,
|
||||
const double* s,
|
||||
const int* /*cells*/,
|
||||
double* pc,
|
||||
double* dpcds) const
|
||||
{
|
||||
satprops_.capPress(n, s, pc, dpcds);
|
||||
}
|
||||
|
||||
|
||||
/// Obtain the range of allowable saturation values.
|
||||
/// In cell cells[i], saturation of phase p is allowed to be
|
||||
/// in the interval [smin[i*P + p], smax[i*P + p]].
|
||||
/// \param[in] n Number of data points.
|
||||
/// \param[in] cells Array of n cell indices.
|
||||
/// \param[out] smin Array of nP minimum s values, array must be valid before calling.
|
||||
/// \param[out] smax Array of nP maximum s values, array must be valid before calling.
|
||||
void BlackoilPropertiesBasic::satRange(const int n,
|
||||
const int* /*cells*/,
|
||||
double* smin,
|
||||
double* smax) const
|
||||
{
|
||||
satprops_.satRange(n, smin, smax);
|
||||
}
|
||||
|
||||
|
||||
/// Update capillary pressure scaling according to pressure diff. and initial water saturation.
|
||||
/// \param[in] cell Cell index.
|
||||
/// \param[in] pcow P_oil - P_water.
|
||||
/// \param[in/out] swat Water saturation. / Possibly modified Water saturation.
|
||||
void BlackoilPropertiesBasic::swatInitScaling(const int /*cell*/,
|
||||
const double /*pcow*/,
|
||||
double& /*swat*/)
|
||||
{
|
||||
OPM_THROW(std::runtime_error, "BlackoilPropertiesBasic::swatInitScaling() -- not implemented.");
|
||||
}
|
||||
|
||||
} // namespace Opm
|
||||
|
203
opm/core/props/BlackoilPropertiesBasic.hpp
Normal file
203
opm/core/props/BlackoilPropertiesBasic.hpp
Normal file
@ -0,0 +1,203 @@
|
||||
/*
|
||||
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_BLACKOILPROPERTIESBASIC_HEADER_INCLUDED
|
||||
#define OPM_BLACKOILPROPERTIESBASIC_HEADER_INCLUDED
|
||||
|
||||
|
||||
#include <opm/core/props/BlackoilPropertiesInterface.hpp>
|
||||
#include <opm/core/props/rock/RockBasic.hpp>
|
||||
#include <opm/core/props/pvt/PvtPropertiesBasic.hpp>
|
||||
#include <opm/core/props/satfunc/SaturationPropsBasic.hpp>
|
||||
#include <opm/core/utility/parameters/ParameterGroup.hpp>
|
||||
|
||||
namespace Opm
|
||||
{
|
||||
|
||||
/// Concrete class implementing the blackoil property interface,
|
||||
/// reading all necessary input from parameters.
|
||||
class BlackoilPropertiesBasic : public BlackoilPropertiesInterface
|
||||
{
|
||||
public:
|
||||
/// Construct from parameters.
|
||||
/// The following parameters are accepted (defaults):
|
||||
/// - num_phases (2) -- Must be 1 or 2.
|
||||
/// - relperm_func ("Linear") -- Must be "Constant", "Linear" or "Quadratic".
|
||||
/// - rho1, rho2, rho3 (1.0e3) -- Density in kg/m^3
|
||||
/// - mu1, mu2, mu3 (1.0) -- Viscosity in cP
|
||||
/// - porosity (1.0) -- Porosity
|
||||
/// - permeability (100.0) -- Permeability in mD
|
||||
BlackoilPropertiesBasic(const ParameterGroup& param,
|
||||
const int dim,
|
||||
const int num_cells);
|
||||
|
||||
/// Destructor.
|
||||
virtual ~BlackoilPropertiesBasic();
|
||||
|
||||
|
||||
// ---- Rock interface ----
|
||||
|
||||
/// \return D, the number of spatial dimensions.
|
||||
virtual int numDimensions() const;
|
||||
|
||||
/// \return N, the number of cells.
|
||||
virtual int numCells() const;
|
||||
|
||||
/// Return an array containing the PVT table index for each
|
||||
/// grid cell
|
||||
virtual const int* cellPvtRegionIndex() const
|
||||
{ return NULL; }
|
||||
|
||||
/// \return Array of N porosity values.
|
||||
virtual 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).
|
||||
virtual const double* permeability() const;
|
||||
|
||||
|
||||
// ---- Fluid interface ----
|
||||
|
||||
/// \return P, the number of phases (also the number of components).
|
||||
virtual int numPhases() const;
|
||||
|
||||
/// \return Object describing the active phases.
|
||||
virtual PhaseUsage phaseUsage() const;
|
||||
|
||||
/// \param[in] n Number of data points.
|
||||
/// \param[in] p Array of n pressure values.
|
||||
/// \param[in] T Array of n temperature values.
|
||||
/// \param[in] z Array of nP surface volume values.
|
||||
/// \param[in] cells Array of n cell indices to be associated with the p and z values.
|
||||
/// \param[out] mu Array of nP viscosity values, array must be valid before calling.
|
||||
/// \param[out] dmudp If non-null: array of nP viscosity derivative values,
|
||||
/// array must be valid before calling.
|
||||
virtual void viscosity(const int n,
|
||||
const double* p,
|
||||
const double* T,
|
||||
const double* z,
|
||||
const int* cells,
|
||||
double* mu,
|
||||
double* dmudp) const;
|
||||
|
||||
/// \param[in] n Number of data points.
|
||||
/// \param[in] p Array of n pressure values.
|
||||
/// \param[in] T Array of n temperature values.
|
||||
/// \param[in] z Array of nP surface volume values.
|
||||
/// \param[in] cells Array of n cell indices to be associated with the p and z values.
|
||||
/// \param[out] A Array of nP^2 values, array must be valid before calling.
|
||||
/// The P^2 values for a cell give the matrix A = RB^{-1} which
|
||||
/// relates z to u by z = Au. The matrices are output in Fortran order.
|
||||
/// \param[out] dAdp If non-null: array of nP^2 matrix derivative values,
|
||||
/// array must be valid before calling. The matrices are output
|
||||
/// in Fortran order.
|
||||
virtual void matrix(const int n,
|
||||
const double* p,
|
||||
const double* T,
|
||||
const double* z,
|
||||
const int* cells,
|
||||
double* A,
|
||||
double* dAdp) const;
|
||||
|
||||
|
||||
/// Densities of stock components at reservoir conditions.
|
||||
/// \param[in] n Number of data points.
|
||||
/// \param[in] A Array of nP^2 values, where the P^2 values for a cell give the
|
||||
/// matrix A = RB^{-1} which relates z to u by z = Au. The matrices
|
||||
/// are assumed to be in Fortran order, and are typically the result
|
||||
/// of a call to the method matrix().
|
||||
/// \param[in] cells The index of the grid cell of each data point.
|
||||
/// \param[out] rho Array of nP density values, array must be valid before calling.
|
||||
virtual void density(const int n,
|
||||
const double* A,
|
||||
const int* cells,
|
||||
double* rho) const;
|
||||
|
||||
/// Densities of stock components at surface conditions.
|
||||
/// \param[in] cellIdx The index of the cell for which the surface density ought to be calculated
|
||||
/// \return Array of P density values.
|
||||
virtual const double* surfaceDensity(int cellIdx = 0) const;
|
||||
|
||||
/// \param[in] n Number of data points.
|
||||
/// \param[in] s Array of nP saturation values.
|
||||
/// \param[in] cells Array of n cell indices to be associated with the s values.
|
||||
/// \param[out] kr Array of nP relperm values, array must be valid before calling.
|
||||
/// \param[out] dkrds If non-null: array of nP^2 relperm derivative values,
|
||||
/// array must be valid before calling.
|
||||
/// The P^2 derivative matrix is
|
||||
/// m_{ij} = \frac{dkr_i}{ds^j},
|
||||
/// and is output in Fortran order (m_00 m_10 m_20 m_01 ...)
|
||||
virtual void relperm(const int n,
|
||||
const double* s,
|
||||
const int* cells,
|
||||
double* kr,
|
||||
double* dkrds) const;
|
||||
|
||||
|
||||
/// \param[in] n Number of data points.
|
||||
/// \param[in] s Array of nP saturation values.
|
||||
/// \param[in] cells Array of n cell indices to be associated with the s values.
|
||||
/// \param[out] pc Array of nP capillary pressure values, array must be valid before calling.
|
||||
/// \param[out] dpcds If non-null: array of nP^2 derivative values,
|
||||
/// array must be valid before calling.
|
||||
/// The P^2 derivative matrix is
|
||||
/// m_{ij} = \frac{dpc_i}{ds^j},
|
||||
/// and is output in Fortran order (m_00 m_10 m_20 m_01 ...)
|
||||
virtual void capPress(const int n,
|
||||
const double* s,
|
||||
const int* cells,
|
||||
double* pc,
|
||||
double* dpcds) const;
|
||||
|
||||
|
||||
/// Obtain the range of allowable saturation values.
|
||||
/// In cell cells[i], saturation of phase p is allowed to be
|
||||
/// in the interval [smin[i*P + p], smax[i*P + p]].
|
||||
/// \param[in] n Number of data points.
|
||||
/// \param[in] cells Array of n cell indices.
|
||||
/// \param[out] smin Array of nP minimum s values, array must be valid before calling.
|
||||
/// \param[out] smax Array of nP maximum s values, array must be valid before calling.
|
||||
virtual void satRange(const int n,
|
||||
const int* cells,
|
||||
double* smin,
|
||||
double* smax) const;
|
||||
|
||||
|
||||
/// Update capillary pressure scaling according to pressure diff. and initial water saturation.
|
||||
/// \param[in] cell Cell index.
|
||||
/// \param[in] pcow P_oil - P_water.
|
||||
/// \param[in/out] swat Water saturation. / Possibly modified Water saturation.
|
||||
virtual void swatInitScaling(const int cell,
|
||||
const double pcow,
|
||||
double & swat);
|
||||
|
||||
|
||||
private:
|
||||
RockBasic rock_;
|
||||
PvtPropertiesBasic pvt_;
|
||||
SaturationPropsBasic satprops_;
|
||||
};
|
||||
|
||||
|
||||
|
||||
} // namespace Opm
|
||||
|
||||
|
||||
#endif // OPM_BLACKOILPROPERTIESBASIC_HEADER_INCLUDED
|
787
opm/core/props/BlackoilPropertiesFromDeck.cpp
Normal file
787
opm/core/props/BlackoilPropertiesFromDeck.cpp
Normal file
@ -0,0 +1,787 @@
|
||||
/*
|
||||
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/props/BlackoilPropertiesFromDeck.hpp>
|
||||
#include <opm/material/fluidmatrixinteractions/EclMaterialLawManager.hpp>
|
||||
#include <opm/core/props/phaseUsageFromDeck.hpp>
|
||||
#include <opm/core/utility/parameters/ParameterGroup.hpp>
|
||||
#include <opm/core/utility/compressedToCartesian.hpp>
|
||||
#include <opm/core/utility/extractPvtTableIndex.hpp>
|
||||
#include <vector>
|
||||
#include <numeric>
|
||||
|
||||
namespace Opm
|
||||
{
|
||||
BlackoilPropertiesFromDeck::BlackoilPropertiesFromDeck(const Opm::Deck& deck,
|
||||
const Opm::EclipseState& eclState,
|
||||
const UnstructuredGrid& grid,
|
||||
bool init_rock)
|
||||
{
|
||||
std::vector<int> compressedToCartesianIdx
|
||||
= compressedToCartesian(grid.number_of_cells, grid.global_cell);
|
||||
|
||||
auto materialLawManager = std::make_shared<MaterialLawManager>();
|
||||
materialLawManager->initFromDeck(deck, eclState, compressedToCartesianIdx);
|
||||
|
||||
init(deck, eclState, materialLawManager, grid.number_of_cells, grid.global_cell, grid.cartdims,
|
||||
init_rock);
|
||||
}
|
||||
|
||||
BlackoilPropertiesFromDeck::BlackoilPropertiesFromDeck(const Opm::Deck& deck,
|
||||
const Opm::EclipseState& eclState,
|
||||
const UnstructuredGrid& grid,
|
||||
const ParameterGroup& param,
|
||||
bool init_rock)
|
||||
{
|
||||
std::vector<int> compressedToCartesianIdx
|
||||
= compressedToCartesian(grid.number_of_cells, grid.global_cell);
|
||||
|
||||
auto materialLawManager = std::make_shared<MaterialLawManager>();
|
||||
materialLawManager->initFromDeck(deck, eclState, compressedToCartesianIdx);
|
||||
|
||||
init(deck, eclState, materialLawManager, grid.number_of_cells, grid.global_cell, grid.cartdims, param, init_rock);
|
||||
}
|
||||
|
||||
BlackoilPropertiesFromDeck::BlackoilPropertiesFromDeck(const Opm::Deck& deck,
|
||||
const Opm::EclipseState& eclState,
|
||||
int number_of_cells,
|
||||
const int* global_cell,
|
||||
const int* cart_dims,
|
||||
bool init_rock)
|
||||
{
|
||||
std::vector<int> compressedToCartesianIdx
|
||||
= compressedToCartesian(number_of_cells, global_cell);
|
||||
|
||||
auto materialLawManager = std::make_shared<MaterialLawManager>();
|
||||
materialLawManager->initFromDeck(deck, eclState, compressedToCartesianIdx);
|
||||
|
||||
init(deck, eclState, materialLawManager, number_of_cells, global_cell, cart_dims,
|
||||
init_rock);
|
||||
}
|
||||
|
||||
BlackoilPropertiesFromDeck::BlackoilPropertiesFromDeck(const Opm::Deck& deck,
|
||||
const Opm::EclipseState& eclState,
|
||||
int number_of_cells,
|
||||
const int* global_cell,
|
||||
const int* cart_dims,
|
||||
const ParameterGroup& param,
|
||||
bool init_rock)
|
||||
{
|
||||
std::vector<int> compressedToCartesianIdx
|
||||
= compressedToCartesian(number_of_cells, global_cell);
|
||||
|
||||
auto materialLawManager = std::make_shared<MaterialLawManager>();
|
||||
materialLawManager->initFromDeck(deck, eclState, compressedToCartesianIdx);
|
||||
|
||||
init(deck,
|
||||
eclState,
|
||||
materialLawManager,
|
||||
number_of_cells,
|
||||
global_cell,
|
||||
cart_dims,
|
||||
param,
|
||||
init_rock);
|
||||
}
|
||||
|
||||
BlackoilPropertiesFromDeck::BlackoilPropertiesFromDeck(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 ParameterGroup& param,
|
||||
bool init_rock)
|
||||
{
|
||||
init(deck,
|
||||
eclState,
|
||||
materialLawManager,
|
||||
number_of_cells,
|
||||
global_cell,
|
||||
cart_dims,
|
||||
param,
|
||||
init_rock);
|
||||
}
|
||||
|
||||
inline void BlackoilPropertiesFromDeck::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,
|
||||
bool init_rock)
|
||||
{
|
||||
// retrieve the cell specific PVT table index from the deck
|
||||
// and using the grid...
|
||||
extractPvtTableIndex(cellPvtRegionIdx_, eclState, number_of_cells, global_cell);
|
||||
|
||||
if (init_rock){
|
||||
rock_.init(eclState, number_of_cells, global_cell, cart_dims);
|
||||
}
|
||||
phaseUsage_ = phaseUsageFromDeck(deck);
|
||||
initSurfaceDensities_(deck);
|
||||
oilPvt_.initFromDeck(deck, eclState);
|
||||
gasPvt_.initFromDeck(deck, eclState);
|
||||
waterPvt_.initFromDeck(deck, eclState);
|
||||
SaturationPropsFromDeck* ptr
|
||||
= new SaturationPropsFromDeck();
|
||||
ptr->init(phaseUsageFromDeck(deck), materialLawManager);
|
||||
satprops_.reset(ptr);
|
||||
}
|
||||
|
||||
inline void BlackoilPropertiesFromDeck::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 ParameterGroup& param,
|
||||
bool init_rock)
|
||||
{
|
||||
// retrieve the cell specific PVT table index from the deck
|
||||
// and using the grid...
|
||||
extractPvtTableIndex(cellPvtRegionIdx_, eclState, number_of_cells, global_cell);
|
||||
|
||||
if(init_rock){
|
||||
rock_.init(eclState, number_of_cells, global_cell, cart_dims);
|
||||
}
|
||||
|
||||
phaseUsage_ = phaseUsageFromDeck(deck);
|
||||
initSurfaceDensities_(deck);
|
||||
oilPvt_.initFromDeck(deck, eclState);
|
||||
gasPvt_.initFromDeck(deck, eclState);
|
||||
waterPvt_.initFromDeck(deck, eclState);
|
||||
|
||||
// Unfortunate lack of pointer smartness here...
|
||||
std::string threephase_model = param.getDefault<std::string>("threephase_model", "gwseg");
|
||||
if (deck.hasKeyword("ENDSCALE") && threephase_model != "gwseg") {
|
||||
OPM_THROW(std::runtime_error, "Sorry, end point scaling currently available for the 'gwseg' model only.");
|
||||
}
|
||||
|
||||
SaturationPropsFromDeck* ptr
|
||||
= new SaturationPropsFromDeck();
|
||||
ptr->init(phaseUsageFromDeck(deck), materialLawManager);
|
||||
satprops_.reset(ptr);
|
||||
}
|
||||
|
||||
BlackoilPropertiesFromDeck::~BlackoilPropertiesFromDeck()
|
||||
{
|
||||
}
|
||||
|
||||
/// \return D, the number of spatial dimensions.
|
||||
int BlackoilPropertiesFromDeck::numDimensions() const
|
||||
{
|
||||
return rock_.numDimensions();
|
||||
}
|
||||
|
||||
/// \return N, the number of cells.
|
||||
int BlackoilPropertiesFromDeck::numCells() const
|
||||
{
|
||||
return rock_.numCells();
|
||||
}
|
||||
|
||||
/// \return Array of N porosity values.
|
||||
const double* BlackoilPropertiesFromDeck::porosity() const
|
||||
{
|
||||
return rock_.porosity();
|
||||
}
|
||||
|
||||
/// \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* BlackoilPropertiesFromDeck::permeability() const
|
||||
{
|
||||
return rock_.permeability();
|
||||
}
|
||||
|
||||
|
||||
// ---- Fluid interface ----
|
||||
|
||||
/// \return P, the number of phases (also the number of components).
|
||||
int BlackoilPropertiesFromDeck::numPhases() const
|
||||
{
|
||||
return phaseUsage_.num_phases;
|
||||
}
|
||||
|
||||
/// \return Object describing the active phases.
|
||||
PhaseUsage BlackoilPropertiesFromDeck::phaseUsage() const
|
||||
{
|
||||
return phaseUsage_;
|
||||
}
|
||||
|
||||
/// \param[in] n Number of data points.
|
||||
/// \param[in] p Array of n pressure values.
|
||||
/// \param[in] T Array of n temperature values.
|
||||
/// \param[in] z Array of nP surface volume values.
|
||||
/// \param[in] cells Array of n cell indices to be associated with the p and z values.
|
||||
/// \param[out] mu Array of nP viscosity values, array must be valid before calling.
|
||||
/// \param[out] dmudp If non-null: array of nP viscosity derivative values,
|
||||
/// array must be valid before calling.
|
||||
void BlackoilPropertiesFromDeck::viscosity(const int n,
|
||||
const double* p,
|
||||
const double* T,
|
||||
const double* z,
|
||||
const int* cells,
|
||||
double* mu,
|
||||
double* dmudp) const
|
||||
{
|
||||
const auto& pu = phaseUsage();
|
||||
const int np = numPhases();
|
||||
|
||||
typedef Opm::DenseAd::Evaluation<double, /*size=*/1> Eval;
|
||||
|
||||
Eval pEval = 0.0;
|
||||
Eval TEval = 0.0;
|
||||
Eval RsEval = 0.0;
|
||||
Eval RvEval = 0.0;
|
||||
Eval muEval = 0.0;
|
||||
|
||||
pEval.setDerivative(0, 1.0);
|
||||
|
||||
R_.resize(n*np);
|
||||
this->compute_R_(n, p, T, z, cells, &R_[0]);
|
||||
|
||||
for (int i = 0; i < n; ++ i) {
|
||||
int cellIdx = cells[i];
|
||||
int pvtRegionIdx = cellPvtRegionIdx_[cellIdx];
|
||||
pEval.setValue(p[i]);
|
||||
TEval.setValue(T[i]);
|
||||
|
||||
if (pu.phase_used[BlackoilPhases::Aqua]) {
|
||||
muEval = waterPvt_.viscosity(pvtRegionIdx, TEval, pEval);
|
||||
int offset = pu.num_phases*cellIdx + pu.phase_pos[BlackoilPhases::Aqua];
|
||||
mu[offset] = muEval.value();
|
||||
dmudp[offset] = muEval.derivative(0);
|
||||
}
|
||||
|
||||
if (pu.phase_used[BlackoilPhases::Liquid]) {
|
||||
RsEval.setValue(R_[i*np + pu.phase_pos[BlackoilPhases::Liquid]]);
|
||||
muEval = oilPvt_.viscosity(pvtRegionIdx, TEval, pEval, RsEval);
|
||||
int offset = pu.num_phases*cellIdx + pu.phase_pos[BlackoilPhases::Liquid];
|
||||
mu[offset] = muEval.value();
|
||||
dmudp[offset] = muEval.derivative(0);
|
||||
}
|
||||
|
||||
if (pu.phase_used[BlackoilPhases::Vapour]) {
|
||||
RvEval.setValue(R_[i*np + pu.phase_pos[BlackoilPhases::Vapour]]);
|
||||
muEval = gasPvt_.viscosity(pvtRegionIdx, TEval, pEval, RvEval);
|
||||
int offset = pu.num_phases*cellIdx + pu.phase_pos[BlackoilPhases::Vapour];
|
||||
mu[offset] = muEval.value();
|
||||
dmudp[offset] = muEval.derivative(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// \param[in] n Number of data points.
|
||||
/// \param[in] p Array of n pressure values.
|
||||
/// \param[in] T Array of n temperature values.
|
||||
/// \param[in] z Array of nP surface volume values.
|
||||
/// \param[in] cells Array of n cell indices to be associated with the p and z values.
|
||||
/// \param[out] A Array of nP^2 values, array must be valid before calling.
|
||||
/// The P^2 values for a cell give the matrix A = RB^{-1} which
|
||||
/// relates z to u by z = Au. The matrices are output in Fortran order.
|
||||
/// \param[out] dAdp If non-null: array of nP^2 matrix derivative values,
|
||||
/// array must be valid before calling. The matrices are output
|
||||
/// in Fortran order.
|
||||
void BlackoilPropertiesFromDeck::matrix(const int n,
|
||||
const double* p,
|
||||
const double* T,
|
||||
const double* z,
|
||||
const int* cells,
|
||||
double* A,
|
||||
double* dAdp) const
|
||||
{
|
||||
const int np = numPhases();
|
||||
|
||||
B_.resize(n*np);
|
||||
R_.resize(n*np);
|
||||
if (dAdp) {
|
||||
dB_.resize(n*np);
|
||||
dR_.resize(n*np);
|
||||
|
||||
this->compute_dBdp_(n, p, T, z, cells, &B_[0], &dB_[0]);
|
||||
this->compute_dRdp_(n, p, T, z, cells, &R_[0], &dR_[0]);
|
||||
} else {
|
||||
this->compute_B_(n, p, T, z, cells, &B_[0]);
|
||||
this->compute_R_(n, p, T, z, cells, &R_[0]);
|
||||
}
|
||||
const auto& pu = phaseUsage();
|
||||
bool oil_and_gas = pu.phase_used[BlackoilPhases::Liquid] &&
|
||||
pu.phase_used[BlackoilPhases::Vapour];
|
||||
const int o = pu.phase_pos[BlackoilPhases::Liquid];
|
||||
const int g = pu.phase_pos[BlackoilPhases::Vapour];
|
||||
|
||||
// Compute A matrix
|
||||
// #pragma omp parallel for
|
||||
for (int i = 0; i < n; ++i) {
|
||||
double* m = A + i*np*np;
|
||||
std::fill(m, m + np*np, 0.0);
|
||||
// Diagonal entries.
|
||||
for (int phase = 0; phase < np; ++phase) {
|
||||
m[phase + phase*np] = 1.0/B_[i*np + phase];
|
||||
}
|
||||
// Off-diagonal entries.
|
||||
if (oil_and_gas) {
|
||||
m[o + g*np] = R_[i*np + g]/B_[i*np + g];
|
||||
m[g + o*np] = R_[i*np + o]/B_[i*np + o];
|
||||
}
|
||||
}
|
||||
|
||||
// Derivative of A matrix.
|
||||
// A = R*inv(B) whence
|
||||
//
|
||||
// dA/dp = (dR/dp*inv(B) + R*d(inv(B))/dp)
|
||||
// = (dR/dp*inv(B) - R*inv(B)*(dB/dp)*inv(B))
|
||||
// = (dR/dp - A*(dB/dp)) * inv(B)
|
||||
//
|
||||
// The B matrix is diagonal and that fact is exploited in the
|
||||
// following implementation.
|
||||
if (dAdp) {
|
||||
// #pragma omp parallel for
|
||||
// (1): dA/dp <- A
|
||||
std::copy(A, A + n*np*np, dAdp);
|
||||
|
||||
for (int i = 0; i < n; ++i) {
|
||||
double* m = dAdp + i*np*np;
|
||||
|
||||
// (2): dA/dp <- -dA/dp*(dB/dp) == -A*(dB/dp)
|
||||
const double* dB = & dB_[i * np];
|
||||
for (int col = 0; col < np; ++col) {
|
||||
for (int row = 0; row < np; ++row) {
|
||||
m[col*np + row] *= - dB[ col ]; // Note sign.
|
||||
}
|
||||
}
|
||||
|
||||
if (oil_and_gas) {
|
||||
// (2b): dA/dp += dR/dp (== dR/dp - A*(dB/dp))
|
||||
const double* dR = & dR_[i * np];
|
||||
|
||||
m[o*np + g] += dR[ o ];
|
||||
m[g*np + o] += dR[ g ];
|
||||
}
|
||||
|
||||
// (3): dA/dp *= inv(B) (== final result)
|
||||
const double* B = & B_[i * np];
|
||||
for (int col = 0; col < np; ++col) {
|
||||
for (int row = 0; row < np; ++row) {
|
||||
m[col*np + row] /= B[ col ];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BlackoilPropertiesFromDeck::compute_B_(const int n,
|
||||
const double* p,
|
||||
const double* T,
|
||||
const double* z,
|
||||
const int* cells,
|
||||
double* B) const
|
||||
{
|
||||
const auto& pu = phaseUsage();
|
||||
|
||||
typedef double Eval;
|
||||
|
||||
Eval pEval = 0.0;
|
||||
Eval TEval = 0.0;
|
||||
Eval RsEval = 0.0;
|
||||
Eval RvEval = 0.0;
|
||||
|
||||
for (int i = 0; i < n; ++ i) {
|
||||
int cellIdx = cells[i];
|
||||
int pvtRegionIdx = cellPvtRegionIdx_[cellIdx];
|
||||
pEval = p[i];
|
||||
TEval = T[i];
|
||||
|
||||
int oilOffset = pu.num_phases*i + pu.phase_pos[BlackoilPhases::Liquid];
|
||||
int gasOffset = pu.num_phases*i + pu.phase_pos[BlackoilPhases::Vapour];
|
||||
int waterOffset = pu.num_phases*i + pu.phase_pos[BlackoilPhases::Aqua];
|
||||
|
||||
if (pu.phase_used[BlackoilPhases::Aqua]) {
|
||||
Eval BEval = 1.0/waterPvt_.inverseFormationVolumeFactor(pvtRegionIdx, TEval, pEval);
|
||||
|
||||
B[waterOffset] = BEval;
|
||||
}
|
||||
|
||||
if (pu.phase_used[BlackoilPhases::Liquid]) {
|
||||
double currentRs = 0.0;
|
||||
double maxRs = 0.0;
|
||||
if (pu.phase_used[BlackoilPhases::Vapour]) {
|
||||
currentRs = (z[oilOffset] == 0.0) ? 0.0 : z[gasOffset]/z[oilOffset];
|
||||
maxRs = oilPvt_.saturatedGasDissolutionFactor(pvtRegionIdx, TEval, pEval);
|
||||
}
|
||||
Eval BEval;
|
||||
if (currentRs >= maxRs) {
|
||||
BEval = 1.0/oilPvt_.saturatedInverseFormationVolumeFactor(pvtRegionIdx, TEval, pEval);
|
||||
}
|
||||
else {
|
||||
RsEval = currentRs;
|
||||
BEval = 1.0/oilPvt_.inverseFormationVolumeFactor(pvtRegionIdx, TEval, pEval, RsEval);
|
||||
}
|
||||
|
||||
B[oilOffset] = BEval;
|
||||
}
|
||||
|
||||
if (pu.phase_used[BlackoilPhases::Vapour]) {
|
||||
double currentRv = 0.0;
|
||||
double maxRv = 0.0;
|
||||
if (pu.phase_used[BlackoilPhases::Liquid]) {
|
||||
currentRv = (z[gasOffset] == 0.0) ? 0.0 : z[oilOffset]/z[gasOffset];
|
||||
maxRv = gasPvt_.saturatedOilVaporizationFactor(pvtRegionIdx, TEval, pEval);
|
||||
}
|
||||
Eval BEval;
|
||||
if (currentRv >= maxRv) {
|
||||
BEval = 1.0/gasPvt_.saturatedInverseFormationVolumeFactor(pvtRegionIdx, TEval, pEval);
|
||||
}
|
||||
else {
|
||||
RvEval = currentRv;
|
||||
BEval = 1.0/gasPvt_.inverseFormationVolumeFactor(pvtRegionIdx, TEval, pEval, RvEval);
|
||||
}
|
||||
|
||||
B[gasOffset] = BEval;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BlackoilPropertiesFromDeck::compute_dBdp_(const int n,
|
||||
const double* p,
|
||||
const double* T,
|
||||
const double* z,
|
||||
const int* cells,
|
||||
double* B,
|
||||
double* dBdp) const
|
||||
{
|
||||
const auto& pu = phaseUsage();
|
||||
|
||||
typedef Opm::DenseAd::Evaluation<double, /*size=*/1> Eval;
|
||||
|
||||
Eval pEval = 0.0;
|
||||
Eval TEval = 0.0;
|
||||
Eval RsEval = 0.0;
|
||||
Eval RvEval = 0.0;
|
||||
|
||||
pEval.setDerivative(0, 1.0);
|
||||
|
||||
for (int i = 0; i < n; ++ i) {
|
||||
int cellIdx = cells[i];
|
||||
int pvtRegionIdx = cellPvtRegionIdx_[cellIdx];
|
||||
pEval.setValue(p[i]);
|
||||
TEval.setValue(T[i]);
|
||||
|
||||
int oilOffset = pu.num_phases*i + pu.phase_pos[BlackoilPhases::Liquid];
|
||||
int gasOffset = pu.num_phases*i + pu.phase_pos[BlackoilPhases::Vapour];
|
||||
int waterOffset = pu.num_phases*i + pu.phase_pos[BlackoilPhases::Aqua];
|
||||
|
||||
if (pu.phase_used[BlackoilPhases::Aqua]) {
|
||||
Eval BEval = 1.0/waterPvt_.inverseFormationVolumeFactor(pvtRegionIdx, TEval, pEval);
|
||||
|
||||
B[waterOffset] = BEval.value();
|
||||
dBdp[waterOffset] = BEval.derivative(0);
|
||||
}
|
||||
|
||||
if (pu.phase_used[BlackoilPhases::Liquid]) {
|
||||
double currentRs = 0.0;
|
||||
double maxRs = 0.0;
|
||||
if (pu.phase_used[BlackoilPhases::Vapour]) {
|
||||
currentRs = (z[oilOffset] == 0.0) ? 0.0 : z[gasOffset]/z[oilOffset];
|
||||
maxRs = oilPvt_.saturatedGasDissolutionFactor(pvtRegionIdx, TEval.value(), pEval.value());
|
||||
}
|
||||
Eval BEval;
|
||||
if (currentRs >= maxRs) {
|
||||
BEval = 1.0/oilPvt_.saturatedInverseFormationVolumeFactor(pvtRegionIdx, TEval, pEval);
|
||||
}
|
||||
else {
|
||||
RsEval.setValue(currentRs);
|
||||
BEval = 1.0/oilPvt_.inverseFormationVolumeFactor(pvtRegionIdx, TEval, pEval, RsEval);
|
||||
}
|
||||
|
||||
B[oilOffset] = BEval.value();
|
||||
dBdp[oilOffset] = BEval.derivative(0);
|
||||
}
|
||||
|
||||
if (pu.phase_used[BlackoilPhases::Vapour]) {
|
||||
double currentRv = 0.0;
|
||||
double maxRv = 0.0;
|
||||
if (pu.phase_used[BlackoilPhases::Liquid]) {
|
||||
currentRv = (z[gasOffset] == 0.0) ? 0.0 : z[oilOffset]/z[gasOffset];
|
||||
maxRv = gasPvt_.saturatedOilVaporizationFactor(pvtRegionIdx, TEval.value(), pEval.value());
|
||||
}
|
||||
Eval BEval;
|
||||
if (currentRv >= maxRv) {
|
||||
BEval = 1.0/gasPvt_.saturatedInverseFormationVolumeFactor(pvtRegionIdx, TEval, pEval);
|
||||
}
|
||||
else {
|
||||
RvEval.setValue(currentRv);
|
||||
BEval = 1.0/gasPvt_.inverseFormationVolumeFactor(pvtRegionIdx, TEval, pEval, RvEval);
|
||||
}
|
||||
|
||||
B[gasOffset] = BEval.value();
|
||||
dBdp[gasOffset] = BEval.derivative(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BlackoilPropertiesFromDeck::compute_R_(const int n,
|
||||
const double* p,
|
||||
const double* T,
|
||||
const double* z,
|
||||
const int* cells,
|
||||
double* R) const
|
||||
{
|
||||
const auto& pu = phaseUsage();
|
||||
|
||||
typedef double Eval;
|
||||
|
||||
Eval pEval = 0.0;
|
||||
Eval TEval = 0.0;
|
||||
|
||||
for (int i = 0; i < n; ++ i) {
|
||||
int cellIdx = cells[i];
|
||||
int pvtRegionIdx = cellPvtRegionIdx_[cellIdx];
|
||||
pEval = p[i];
|
||||
TEval = T[i];
|
||||
|
||||
int oilOffset = pu.num_phases*i + pu.phase_pos[BlackoilPhases::Liquid];
|
||||
int gasOffset = pu.num_phases*i + pu.phase_pos[BlackoilPhases::Vapour];
|
||||
int waterOffset = pu.num_phases*i + pu.phase_pos[BlackoilPhases::Aqua];
|
||||
|
||||
if (pu.phase_used[BlackoilPhases::Aqua]) {
|
||||
R[waterOffset] = 0.0; // water is always immiscible!
|
||||
}
|
||||
|
||||
if (pu.phase_used[BlackoilPhases::Liquid]) {
|
||||
Eval RsSatEval = oilPvt_.saturatedGasDissolutionFactor(pvtRegionIdx, TEval, pEval);
|
||||
|
||||
double currentRs = 0.0;
|
||||
if (pu.phase_used[BlackoilPhases::Vapour]) {
|
||||
currentRs = (z[oilOffset] == 0.0) ? 0.0 : z[gasOffset]/z[oilOffset];
|
||||
}
|
||||
|
||||
RsSatEval = std::min(RsSatEval, currentRs);
|
||||
|
||||
R[oilOffset] = RsSatEval;
|
||||
}
|
||||
|
||||
if (pu.phase_used[BlackoilPhases::Vapour]) {
|
||||
Eval RvSatEval = gasPvt_.saturatedOilVaporizationFactor(pvtRegionIdx, TEval, pEval);
|
||||
|
||||
double currentRv = 0.0;
|
||||
if (pu.phase_used[BlackoilPhases::Liquid]) {
|
||||
currentRv = (z[gasOffset] == 0.0) ? 0.0 : z[oilOffset]/z[gasOffset];
|
||||
}
|
||||
|
||||
RvSatEval = std::min(RvSatEval, currentRv);
|
||||
|
||||
R[gasOffset] = RvSatEval;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BlackoilPropertiesFromDeck::compute_dRdp_(const int n,
|
||||
const double* p,
|
||||
const double* T,
|
||||
const double* z,
|
||||
const int* cells,
|
||||
double* R,
|
||||
double* dRdp) const
|
||||
{
|
||||
const auto& pu = phaseUsage();
|
||||
|
||||
typedef Opm::DenseAd::Evaluation<double, /*size=*/1> Eval;
|
||||
typedef Opm::MathToolbox<Eval> Toolbox;
|
||||
|
||||
Eval pEval = 0.0;
|
||||
Eval TEval = 0.0;
|
||||
|
||||
pEval.setDerivative(0, 1.0);
|
||||
|
||||
for (int i = 0; i < n; ++ i) {
|
||||
int cellIdx = cells[i];
|
||||
int pvtRegionIdx = cellPvtRegionIdx_[cellIdx];
|
||||
pEval.setValue(p[i]);
|
||||
TEval.setValue(T[i]);
|
||||
|
||||
int oilOffset = pu.num_phases*i + pu.phase_pos[BlackoilPhases::Liquid];
|
||||
int gasOffset = pu.num_phases*i + pu.phase_pos[BlackoilPhases::Vapour];
|
||||
int waterOffset = pu.num_phases*i + pu.phase_pos[BlackoilPhases::Aqua];
|
||||
|
||||
if (pu.phase_used[BlackoilPhases::Aqua]) {
|
||||
R[waterOffset] = 0.0; // water is always immiscible!
|
||||
}
|
||||
|
||||
if (pu.phase_used[BlackoilPhases::Liquid]) {
|
||||
Eval RsSatEval = oilPvt_.saturatedGasDissolutionFactor(pvtRegionIdx, TEval, pEval);
|
||||
|
||||
Eval currentRs = 0.0;
|
||||
if (pu.phase_used[BlackoilPhases::Vapour]) {
|
||||
currentRs = (z[oilOffset] == 0.0) ? 0.0 : z[gasOffset]/z[oilOffset];
|
||||
}
|
||||
|
||||
RsSatEval = Toolbox::min(RsSatEval, currentRs);
|
||||
|
||||
R[oilOffset] = RsSatEval.value();
|
||||
dRdp[oilOffset] = RsSatEval.derivative(0);
|
||||
}
|
||||
|
||||
if (pu.phase_used[BlackoilPhases::Vapour]) {
|
||||
Eval RvSatEval = gasPvt_.saturatedOilVaporizationFactor(pvtRegionIdx, TEval, pEval);
|
||||
|
||||
Eval currentRv = 0.0;
|
||||
if (pu.phase_used[BlackoilPhases::Liquid]) {
|
||||
currentRv = (z[gasOffset] == 0.0) ? 0.0 : z[oilOffset]/z[gasOffset];
|
||||
}
|
||||
|
||||
RvSatEval = Toolbox::min(RvSatEval, currentRv);
|
||||
|
||||
R[gasOffset] = RvSatEval.value();
|
||||
dRdp[gasOffset] = RvSatEval.derivative(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// \param[in] n Number of data points.
|
||||
/// \param[in] A Array of nP^2 values, where the P^2 values for a cell give the
|
||||
/// matrix A = RB^{-1} which relates z to u by z = Au. The matrices
|
||||
/// are assumed to be in Fortran order, and are typically the result
|
||||
/// of a call to the method matrix().
|
||||
/// \param[in] cells The index of the grid cell of each data point.
|
||||
/// \param[out] rho Array of nP density values, array must be valid before calling.
|
||||
void BlackoilPropertiesFromDeck::density(const int n,
|
||||
const double* A,
|
||||
const int* cells,
|
||||
double* rho) const
|
||||
{
|
||||
const int np = numPhases();
|
||||
// #pragma omp parallel for
|
||||
for (int i = 0; i < n; ++i) {
|
||||
int cellIdx = cells?cells[i]:i;
|
||||
const double *sdens = surfaceDensity(cellIdx);
|
||||
for (int phase = 0; phase < np; ++phase) {
|
||||
rho[np*i + phase] = 0.0;
|
||||
for (int comp = 0; comp < np; ++comp) {
|
||||
rho[np*i + phase] += A[i*np*np + np*phase + comp]*sdens[comp];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Densities of stock components at surface conditions.
|
||||
/// \return Array of P density values.
|
||||
const double* BlackoilPropertiesFromDeck::surfaceDensity(int cellIdx) const
|
||||
{
|
||||
const auto& pu = phaseUsage();
|
||||
int pvtRegionIdx = getTableIndex_(cellPvtRegionIndex(), cellIdx);
|
||||
return &surfaceDensities_[pvtRegionIdx*pu.num_phases];
|
||||
}
|
||||
|
||||
void BlackoilPropertiesFromDeck::initSurfaceDensities_(const Opm::Deck& deck)
|
||||
{
|
||||
const auto& pu = phaseUsage();
|
||||
int np = pu.num_phases;
|
||||
int numPvtRegions = 1;
|
||||
if (deck.hasKeyword("TABDIMS")) {
|
||||
const auto& tabdimsKeyword = deck.getKeyword("TABDIMS");
|
||||
numPvtRegions = tabdimsKeyword.getRecord(0).getItem("NTPVT").template get<int>(0);
|
||||
}
|
||||
|
||||
const auto& densityKeyword = deck.getKeyword("DENSITY");
|
||||
|
||||
surfaceDensities_.resize(np*numPvtRegions);
|
||||
for (int pvtRegionIdx = 0; pvtRegionIdx < numPvtRegions; ++pvtRegionIdx) {
|
||||
if (pu.phase_used[BlackoilPhases::Aqua])
|
||||
surfaceDensities_[np*pvtRegionIdx + pu.phase_pos[BlackoilPhases::Aqua]] =
|
||||
densityKeyword.getRecord(pvtRegionIdx).getItem("WATER").getSIDouble(0);
|
||||
|
||||
if (pu.phase_used[BlackoilPhases::Liquid])
|
||||
surfaceDensities_[np*pvtRegionIdx + pu.phase_pos[BlackoilPhases::Liquid]] =
|
||||
densityKeyword.getRecord(pvtRegionIdx).getItem("OIL").getSIDouble(0);
|
||||
|
||||
if (pu.phase_used[BlackoilPhases::Vapour])
|
||||
surfaceDensities_[np*pvtRegionIdx + pu.phase_pos[BlackoilPhases::Vapour]] =
|
||||
densityKeyword.getRecord(pvtRegionIdx).getItem("GAS").getSIDouble(0);
|
||||
}
|
||||
}
|
||||
|
||||
/// \param[in] n Number of data points.
|
||||
/// \param[in] s Array of nP saturation values.
|
||||
/// \param[in] cells Array of n cell indices to be associated with the s values.
|
||||
/// \param[out] kr Array of nP relperm values, array must be valid before calling.
|
||||
/// \param[out] dkrds If non-null: array of nP^2 relperm derivative values,
|
||||
/// array must be valid before calling.
|
||||
/// The P^2 derivative matrix is
|
||||
/// m_{ij} = \frac{dkr_i}{ds^j},
|
||||
/// and is output in Fortran order (m_00 m_10 m_20 m01 ...)
|
||||
void BlackoilPropertiesFromDeck::relperm(const int n,
|
||||
const double* s,
|
||||
const int* cells,
|
||||
double* kr,
|
||||
double* dkrds) const
|
||||
{
|
||||
satprops_->relperm(n, s, cells, kr, dkrds);
|
||||
}
|
||||
|
||||
|
||||
/// \param[in] n Number of data points.
|
||||
/// \param[in] s Array of nP saturation values.
|
||||
/// \param[in] cells Array of n cell indices to be associated with the s values.
|
||||
/// \param[out] pc Array of nP capillary pressure values, array must be valid before calling.
|
||||
/// \param[out] dpcds If non-null: array of nP^2 derivative values,
|
||||
/// array must be valid before calling.
|
||||
/// The P^2 derivative matrix is
|
||||
/// m_{ij} = \frac{dpc_i}{ds^j},
|
||||
/// and is output in Fortran order (m_00 m_10 m_20 m01 ...)
|
||||
void BlackoilPropertiesFromDeck::capPress(const int n,
|
||||
const double* s,
|
||||
const int* cells,
|
||||
double* pc,
|
||||
double* dpcds) const
|
||||
{
|
||||
satprops_->capPress(n, s, cells, pc, dpcds);
|
||||
}
|
||||
|
||||
|
||||
/// Obtain the range of allowable saturation values.
|
||||
/// In cell cells[i], saturation of phase p is allowed to be
|
||||
/// in the interval [smin[i*P + p], smax[i*P + p]].
|
||||
/// \param[in] n Number of data points.
|
||||
/// \param[in] cells Array of n cell indices.
|
||||
/// \param[out] smin Array of nP minimum s values, array must be valid before calling.
|
||||
/// \param[out] smax Array of nP maximum s values, array must be valid before calling.
|
||||
void BlackoilPropertiesFromDeck::satRange(const int n,
|
||||
const int* cells,
|
||||
double* smin,
|
||||
double* smax) const
|
||||
{
|
||||
satprops_->satRange(n, cells, smin, smax);
|
||||
}
|
||||
|
||||
|
||||
/// Update capillary pressure scaling according to pressure diff. and initial water saturation.
|
||||
/// \param[in] cell Cell index.
|
||||
/// \param[in] pcow P_oil - P_water.
|
||||
/// \param[in/out] swat Water saturation. / Possibly modified Water saturation.
|
||||
void BlackoilPropertiesFromDeck::swatInitScaling(const int cell,
|
||||
const double pcow,
|
||||
double & swat)
|
||||
{
|
||||
satprops_->swatInitScaling(cell, pcow, swat);
|
||||
}
|
||||
|
||||
} // namespace Opm
|
||||
|
329
opm/core/props/BlackoilPropertiesFromDeck.hpp
Normal file
329
opm/core/props/BlackoilPropertiesFromDeck.hpp
Normal file
@ -0,0 +1,329 @@
|
||||
/*
|
||||
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_BLACKOILPROPERTIESFROMDECK_HEADER_INCLUDED
|
||||
#define OPM_BLACKOILPROPERTIESFROMDECK_HEADER_INCLUDED
|
||||
|
||||
|
||||
#include <opm/core/props/BlackoilPropertiesInterface.hpp>
|
||||
#include <opm/core/props/rock/RockFromDeck.hpp>
|
||||
#include <opm/core/props/satfunc/SaturationPropsFromDeck.hpp>
|
||||
#include <opm/core/utility/parameters/ParameterGroup.hpp>
|
||||
#include <opm/material/fluidsystems/blackoilpvt/OilPvtMultiplexer.hpp>
|
||||
#include <opm/material/fluidsystems/blackoilpvt/GasPvtMultiplexer.hpp>
|
||||
#include <opm/material/fluidsystems/blackoilpvt/WaterPvtMultiplexer.hpp>
|
||||
|
||||
#include <opm/parser/eclipse/Deck/Deck.hpp>
|
||||
|
||||
#include <memory>
|
||||
|
||||
struct UnstructuredGrid;
|
||||
|
||||
namespace Opm
|
||||
{
|
||||
|
||||
/// Concrete class implementing the blackoil property interface,
|
||||
/// reading all data and properties from eclipse deck input.
|
||||
class BlackoilPropertiesFromDeck : public BlackoilPropertiesInterface
|
||||
{
|
||||
public:
|
||||
typedef typename SaturationPropsFromDeck::MaterialLawManager MaterialLawManager;
|
||||
|
||||
/// Initialize from deck and grid.
|
||||
/// \param[in] deck Deck input parser
|
||||
/// \param[in] grid Grid to which property object applies, needed for the
|
||||
/// mapping from cell indices (typically from a processed grid)
|
||||
/// to logical cartesian indices consistent with the deck.
|
||||
BlackoilPropertiesFromDeck(const Opm::Deck& deck,
|
||||
const Opm::EclipseState& eclState,
|
||||
const UnstructuredGrid& grid, bool init_rock=true );
|
||||
|
||||
/// Initialize from deck, grid and parameters.
|
||||
/// \param[in] deck Deck input parser
|
||||
/// \param[in] grid Grid to which property object applies, needed for the
|
||||
/// mapping from cell indices (typically from a processed grid)
|
||||
/// to logical cartesian indices consistent with the deck.
|
||||
/// \param[in] param Parameters. Accepted parameters include:
|
||||
/// pvt_tab_size (200) number of uniform sample points for dead-oil pvt tables.
|
||||
/// sat_tab_size (200) number of uniform sample points for saturation tables.
|
||||
/// threephase_model("simple") three-phase relperm model (accepts "simple" and "stone2").
|
||||
/// For both size parameters, a 0 or negative value indicates that no spline fitting is to
|
||||
/// be done, and the input fluid data used directly for linear interpolation.
|
||||
BlackoilPropertiesFromDeck(const Opm::Deck& deck,
|
||||
const Opm::EclipseState& eclState,
|
||||
const UnstructuredGrid& grid,
|
||||
const ParameterGroup& param,
|
||||
bool init_rock=true);
|
||||
|
||||
BlackoilPropertiesFromDeck(const Opm::Deck& deck,
|
||||
const Opm::EclipseState& eclState,
|
||||
int number_of_cells,
|
||||
const int* global_cell,
|
||||
const int* cart_dims,
|
||||
bool init_rock=true);
|
||||
|
||||
BlackoilPropertiesFromDeck(const Opm::Deck& deck,
|
||||
const Opm::EclipseState& eclState,
|
||||
int number_of_cells,
|
||||
const int* global_cell,
|
||||
const int* cart_dims,
|
||||
const ParameterGroup& param,
|
||||
bool init_rock=true);
|
||||
|
||||
BlackoilPropertiesFromDeck(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 ParameterGroup& param,
|
||||
bool init_rock=true);
|
||||
|
||||
/// Destructor.
|
||||
virtual ~BlackoilPropertiesFromDeck();
|
||||
|
||||
|
||||
// ---- Rock interface ----
|
||||
|
||||
/// \return D, the number of spatial dimensions.
|
||||
virtual int numDimensions() const;
|
||||
|
||||
/// \return N, the number of cells.
|
||||
virtual 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.
|
||||
virtual 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).
|
||||
virtual const double* permeability() const;
|
||||
|
||||
|
||||
// ---- Fluid interface ----
|
||||
|
||||
/// \return P, the number of phases (also the number of components).
|
||||
virtual int numPhases() const;
|
||||
|
||||
/// \return Object describing the active phases.
|
||||
virtual PhaseUsage phaseUsage() const;
|
||||
|
||||
/// \param[in] n Number of data points.
|
||||
/// \param[in] p Array of n pressure values.
|
||||
/// \param[in] T Array of n temperature values.
|
||||
/// \param[in] z Array of nP surface volume values.
|
||||
/// \param[in] cells Array of n cell indices to be associated with the p and z values.
|
||||
/// \param[out] mu Array of nP viscosity values, array must be valid before calling.
|
||||
/// \param[out] dmudp If non-null: array of nP viscosity derivative values,
|
||||
/// array must be valid before calling.
|
||||
virtual void viscosity(const int n,
|
||||
const double* p,
|
||||
const double* T,
|
||||
const double* z,
|
||||
const int* cells,
|
||||
double* mu,
|
||||
double* dmudp) const;
|
||||
|
||||
/// \param[in] n Number of data points.
|
||||
/// \param[in] p Array of n pressure values.
|
||||
/// \param[in] T Array of n temperature values.
|
||||
/// \param[in] z Array of nP surface volume values.
|
||||
/// \param[in] cells Array of n cell indices to be associated with the p and z values.
|
||||
/// \param[out] A Array of nP^2 values, array must be valid before calling.
|
||||
/// The P^2 values for a cell give the matrix A = RB^{-1} which
|
||||
/// relates z to u by z = Au. The matrices are output in Fortran order.
|
||||
/// \param[out] dAdp If non-null: array of nP^2 matrix derivative values,
|
||||
/// array must be valid before calling. The matrices are output
|
||||
/// in Fortran order.
|
||||
virtual void matrix(const int n,
|
||||
const double* p,
|
||||
const double* T,
|
||||
const double* z,
|
||||
const int* cells,
|
||||
double* A,
|
||||
double* dAdp) const;
|
||||
|
||||
|
||||
/// Densities of stock components at reservoir conditions.
|
||||
/// \param[in] n Number of data points.
|
||||
/// \param[in] A Array of nP^2 values, where the P^2 values for a cell give the
|
||||
/// matrix A = RB^{-1} which relates z to u by z = Au. The matrices
|
||||
/// are assumed to be in Fortran order, and are typically the result
|
||||
/// of a call to the method matrix().
|
||||
/// \param[in] cells The index of the grid cell of each data point.
|
||||
/// \param[out] rho Array of nP density values, array must be valid before calling.
|
||||
virtual void density(const int n,
|
||||
const double* A,
|
||||
const int* cells,
|
||||
double* rho) const;
|
||||
|
||||
/// Densities of stock components at surface conditions.
|
||||
/// \return Array of P density values.
|
||||
virtual const double* surfaceDensity(int cellIdx = 0) const;
|
||||
|
||||
/// \param[in] n Number of data points.
|
||||
/// \param[in] s Array of nP saturation values.
|
||||
/// \param[in] cells Array of n cell indices to be associated with the s values.
|
||||
/// \param[out] kr Array of nP relperm values, array must be valid before calling.
|
||||
/// \param[out] dkrds If non-null: array of nP^2 relperm derivative values,
|
||||
/// array must be valid before calling.
|
||||
/// The P^2 derivative matrix is
|
||||
/// m_{ij} = \frac{dkr_i}{ds^j},
|
||||
/// and is output in Fortran order (m_00 m_10 m_20 m_01 ...)
|
||||
virtual void relperm(const int n,
|
||||
const double* s,
|
||||
const int* cells,
|
||||
double* kr,
|
||||
double* dkrds) const;
|
||||
|
||||
|
||||
/// \param[in] n Number of data points.
|
||||
/// \param[in] s Array of nP saturation values.
|
||||
/// \param[in] cells Array of n cell indices to be associated with the s values.
|
||||
/// \param[out] pc Array of nP capillary pressure values, array must be valid before calling.
|
||||
/// \param[out] dpcds If non-null: array of nP^2 derivative values,
|
||||
/// array must be valid before calling.
|
||||
/// The P^2 derivative matrix is
|
||||
/// m_{ij} = \frac{dpc_i}{ds^j},
|
||||
/// and is output in Fortran order (m_00 m_10 m_20 m_01 ...)
|
||||
virtual void capPress(const int n,
|
||||
const double* s,
|
||||
const int* cells,
|
||||
double* pc,
|
||||
double* dpcds) const;
|
||||
|
||||
|
||||
/// Obtain the range of allowable saturation values.
|
||||
/// In cell cells[i], saturation of phase p is allowed to be
|
||||
/// in the interval [smin[i*P + p], smax[i*P + p]].
|
||||
/// \param[in] n Number of data points.
|
||||
/// \param[in] cells Array of n cell indices.
|
||||
/// \param[out] smin Array of nP minimum s values, array must be valid before calling.
|
||||
/// \param[out] smax Array of nP maximum s values, array must be valid before calling.
|
||||
virtual void satRange(const int n,
|
||||
const int* cells,
|
||||
double* smin,
|
||||
double* smax) const;
|
||||
|
||||
|
||||
/// Update capillary pressure scaling according to pressure diff. and initial water saturation.
|
||||
/// \param[in] cell Cell index.
|
||||
/// \param[in] pcow P_oil - P_water.
|
||||
/// \param[in/out] swat Water saturation. / Possibly modified Water saturation.
|
||||
virtual void swatInitScaling(const int cell,
|
||||
const double pcow,
|
||||
double & swat);
|
||||
|
||||
const OilPvtMultiplexer<double>& oilPvt() const
|
||||
{
|
||||
return oilPvt_;
|
||||
}
|
||||
|
||||
const GasPvtMultiplexer<double>& gasPvt() const
|
||||
{
|
||||
return gasPvt_;
|
||||
}
|
||||
|
||||
const WaterPvtMultiplexer<double>& waterPvt() const
|
||||
{
|
||||
return waterPvt_;
|
||||
}
|
||||
|
||||
private:
|
||||
int getTableIndex_(const int* pvtTableIdx, int cellIdx) const
|
||||
{
|
||||
if (!pvtTableIdx)
|
||||
return 0;
|
||||
return pvtTableIdx[cellIdx];
|
||||
}
|
||||
|
||||
void initSurfaceDensities_(const Opm::Deck& deck);
|
||||
|
||||
void compute_B_(const int n,
|
||||
const double* p,
|
||||
const double* T,
|
||||
const double* z,
|
||||
const int* cells,
|
||||
double* B) const;
|
||||
|
||||
void compute_dBdp_(const int n,
|
||||
const double* p,
|
||||
const double* T,
|
||||
const double* z,
|
||||
const int* cells,
|
||||
double* B,
|
||||
double* dBdp) const;
|
||||
|
||||
void compute_R_(const int n,
|
||||
const double* p,
|
||||
const double* T,
|
||||
const double* z,
|
||||
const int* cells,
|
||||
double* R) const;
|
||||
|
||||
void compute_dRdp_(const int n,
|
||||
const double* p,
|
||||
const double* T,
|
||||
const double* z,
|
||||
const int* cells,
|
||||
double* R,
|
||||
double* dRdp) const;
|
||||
|
||||
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,
|
||||
bool init_rock);
|
||||
|
||||
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 ParameterGroup& param,
|
||||
bool init_rock);
|
||||
|
||||
RockFromDeck rock_;
|
||||
PhaseUsage phaseUsage_;
|
||||
std::vector<int> cellPvtRegionIdx_;
|
||||
OilPvtMultiplexer<double> oilPvt_;
|
||||
GasPvtMultiplexer<double> gasPvt_;
|
||||
WaterPvtMultiplexer<double> waterPvt_;
|
||||
std::shared_ptr<MaterialLawManager> materialLawManager_;
|
||||
std::shared_ptr<SaturationPropsInterface> satprops_;
|
||||
std::vector<double> surfaceDensities_;
|
||||
mutable std::vector<double> B_;
|
||||
mutable std::vector<double> dB_;
|
||||
mutable std::vector<double> R_;
|
||||
mutable std::vector<double> dR_;
|
||||
};
|
||||
|
||||
|
||||
|
||||
} // namespace Opm
|
||||
|
||||
#endif // OPM_BLACKOILPROPERTIESFROMDECK_HEADER_INCLUDED
|
184
opm/core/props/BlackoilPropertiesInterface.hpp
Normal file
184
opm/core/props/BlackoilPropertiesInterface.hpp
Normal file
@ -0,0 +1,184 @@
|
||||
/*
|
||||
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_BLACKOILPROPERTIESINTERFACE_HEADER_INCLUDED
|
||||
#define OPM_BLACKOILPROPERTIESINTERFACE_HEADER_INCLUDED
|
||||
|
||||
namespace Opm
|
||||
{
|
||||
|
||||
struct PhaseUsage;
|
||||
|
||||
/// Abstract base class for blackoil fluid and reservoir properties.
|
||||
/// Supports variable number of spatial dimensions, called D.
|
||||
/// Supports variable number of phases, but assumes that
|
||||
/// the number of components is equal to the number of phases, called P.
|
||||
/// In general, when arguments call for n values of some vector or
|
||||
/// matrix property, such as saturation, they shall always be
|
||||
/// ordered cellwise:
|
||||
/// [s^1_0 s^2_0 s^3_0 s^1_1 s^2_2 ... ]
|
||||
/// in which s^i_j denotes saturation of phase i in cell j.
|
||||
class BlackoilPropertiesInterface
|
||||
{
|
||||
public:
|
||||
virtual ~BlackoilPropertiesInterface() {}
|
||||
|
||||
// ---- Rock interface ----
|
||||
|
||||
/// \return D, the number of spatial dimensions.
|
||||
virtual int numDimensions() const = 0;
|
||||
|
||||
/// \return N, the number of cells.
|
||||
virtual int numCells() const = 0;
|
||||
|
||||
/// Return an array containing the PVT table index for each
|
||||
/// grid cell
|
||||
virtual const int* cellPvtRegionIndex() const = 0;
|
||||
|
||||
/// \return Array of N porosity values.
|
||||
virtual const double* porosity() const = 0;
|
||||
|
||||
/// \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).
|
||||
virtual const double* permeability() const = 0;
|
||||
|
||||
|
||||
// ---- Fluid interface ----
|
||||
|
||||
/// \return P, the number of phases (also the number of components).
|
||||
virtual int numPhases() const = 0;
|
||||
|
||||
/// \return Object describing the active phases.
|
||||
virtual PhaseUsage phaseUsage() const = 0;
|
||||
|
||||
/// \param[in] n Number of data points.
|
||||
/// \param[in] p Array of n pressure values.
|
||||
/// \param[in] T Array of n temperature values.
|
||||
/// \param[in] z Array of nP surface volume values.
|
||||
/// \param[in] cells Array of n cell indices to be associated with the p and z values.
|
||||
/// \param[out] mu Array of nP viscosity values, array must be valid before calling.
|
||||
/// \param[out] dmudp If non-null: array of nP viscosity derivative values,
|
||||
/// array must be valid before calling.
|
||||
virtual void viscosity(const int n,
|
||||
const double* p,
|
||||
const double* T,
|
||||
const double* z,
|
||||
const int* cells,
|
||||
double* mu,
|
||||
double* dmudp) const = 0;
|
||||
|
||||
/// \param[in] n Number of data points.
|
||||
/// \param[in] p Array of n pressure values.
|
||||
/// \param[in] T Array of n temperature values.
|
||||
/// \param[in] z Array of nP surface volume values.
|
||||
/// \param[in] cells Array of n cell indices to be associated with the p and z values.
|
||||
/// \param[out] A Array of nP^2 values, array must be valid before calling.
|
||||
/// The P^2 values for a cell give the matrix A = RB^{-1} which
|
||||
/// relates z to u by z = Au. The matrices are output in Fortran order.
|
||||
/// \param[out] dAdp If non-null: array of nP^2 matrix derivative values,
|
||||
/// array must be valid before calling. The matrices are output
|
||||
/// in Fortran order.
|
||||
virtual void matrix(const int n,
|
||||
const double* p,
|
||||
const double* T,
|
||||
const double* z,
|
||||
const int* cells,
|
||||
double* A,
|
||||
double* dAdp) const = 0;
|
||||
|
||||
|
||||
/// Densities of stock components at reservoir conditions.
|
||||
/// \param[in] n Number of data points.
|
||||
/// \param[in] A Array of nP^2 values, where the P^2 values for a cell give the
|
||||
/// matrix A = RB^{-1} which relates z to u by z = Au. The matrices
|
||||
/// are assumed to be in Fortran order, and are typically the result
|
||||
/// of a call to the method matrix().
|
||||
/// \param[in] cells The index of the grid cell of each data point.
|
||||
/// \param[out] rho Array of nP density values, array must be valid before calling.
|
||||
virtual void density(const int n,
|
||||
const double* A,
|
||||
const int* cells,
|
||||
double* rho) const = 0;
|
||||
|
||||
/// Densities of stock components at surface conditions.
|
||||
/// \return Array of P density values.
|
||||
virtual const double* surfaceDensity(int regionIdx = 0) const = 0;
|
||||
|
||||
/// \param[in] n Number of data points.
|
||||
/// \param[in] s Array of nP saturation values.
|
||||
/// \param[in] cells Array of n cell indices to be associated with the s values.
|
||||
/// \param[out] kr Array of nP relperm values, array must be valid before calling.
|
||||
/// \param[out] dkrds If non-null: array of nP^2 relperm derivative values,
|
||||
/// array must be valid before calling.
|
||||
/// The P^2 derivative matrix is
|
||||
/// m_{ij} = \frac{dkr_i}{ds^j},
|
||||
/// and is output in Fortran order (m_00 m_10 m_20 m_01 ...)
|
||||
virtual void relperm(const int n,
|
||||
const double* s,
|
||||
const int* cells,
|
||||
double* kr,
|
||||
double* dkrds) const = 0;
|
||||
|
||||
|
||||
/// \param[in] n Number of data points.
|
||||
/// \param[in] s Array of nP saturation values.
|
||||
/// \param[in] cells Array of n cell indices to be associated with the s values.
|
||||
/// \param[out] pc Array of nP capillary pressure values, array must be valid before calling.
|
||||
/// \param[out] dpcds If non-null: array of nP^2 derivative values,
|
||||
/// array must be valid before calling.
|
||||
/// The P^2 derivative matrix is
|
||||
/// m_{ij} = \frac{dpc_i}{ds^j},
|
||||
/// and is output in Fortran order (m_00 m_10 m_20 m_01 ...)
|
||||
virtual void capPress(const int n,
|
||||
const double* s,
|
||||
const int* cells,
|
||||
double* pc,
|
||||
double* dpcds) const = 0;
|
||||
|
||||
|
||||
/// Obtain the range of allowable saturation values.
|
||||
/// In cell cells[i], saturation of phase p is allowed to be
|
||||
/// in the interval [smin[i*P + p], smax[i*P + p]].
|
||||
/// \param[in] n Number of data points.
|
||||
/// \param[in] cells Array of n cell indices.
|
||||
/// \param[out] smin Array of nP minimum s values, array must be valid before calling.
|
||||
/// \param[out] smax Array of nP maximum s values, array must be valid before calling.
|
||||
virtual void satRange(const int n,
|
||||
const int* cells,
|
||||
double* smin,
|
||||
double* smax) const = 0;
|
||||
|
||||
|
||||
/// Update capillary pressure scaling according to pressure diff. and initial water saturation.
|
||||
/// \param[in] cell Cell index.
|
||||
/// \param[in] pcow P_oil - P_water.
|
||||
/// \param[in/out] swat Water saturation. / Possibly modified Water saturation.
|
||||
virtual void swatInitScaling(const int cell,
|
||||
const double pcow,
|
||||
double & swat) = 0;
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
} // namespace Opm
|
||||
|
||||
|
||||
#endif // OPM_BLACKOILPROPERTIESINTERFACE_HEADER_INCLUDED
|
186
opm/core/props/IncompPropertiesBasic.cpp
Normal file
186
opm/core/props/IncompPropertiesBasic.cpp
Normal file
@ -0,0 +1,186 @@
|
||||
/*
|
||||
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/props/IncompPropertiesBasic.hpp>
|
||||
#include <opm/parser/eclipse/Units/Units.hpp>
|
||||
#include <opm/common/ErrorMacros.hpp>
|
||||
#include <iostream>
|
||||
|
||||
namespace Opm
|
||||
{
|
||||
|
||||
IncompPropertiesBasic::IncompPropertiesBasic(const ParameterGroup& param,
|
||||
const int dim,
|
||||
const int num_cells)
|
||||
{
|
||||
double poro = param.getDefault("porosity", 1.0);
|
||||
using namespace Opm::unit;
|
||||
using namespace Opm::prefix;
|
||||
double perm = param.getDefault("permeability", 100.0)*milli*darcy;
|
||||
rock_.init(dim, num_cells, poro, perm);
|
||||
pvt_.init(param);
|
||||
satprops_.init(param);
|
||||
if (pvt_.numPhases() != satprops_.numPhases()) {
|
||||
OPM_THROW(std::runtime_error, "IncompPropertiesBasic::IncompPropertiesBasic() - Inconsistent number of phases in pvt data ("
|
||||
<< pvt_.numPhases() << ") and saturation-dependent function data (" << satprops_.numPhases() << ").");
|
||||
}
|
||||
viscosity_.resize(pvt_.numPhases());
|
||||
pvt_.mu(1, 0, 0, 0, &viscosity_[0]);
|
||||
}
|
||||
|
||||
IncompPropertiesBasic::IncompPropertiesBasic(const int num_phases,
|
||||
const SaturationPropsBasic::RelPermFunc& relpermfunc,
|
||||
const std::vector<double>& rho,
|
||||
const std::vector<double>& mu,
|
||||
const double por, //porosity
|
||||
const double perm,
|
||||
const int dim,
|
||||
const int num_cells)
|
||||
{
|
||||
rock_.init(dim, num_cells, por, perm);
|
||||
pvt_.init(num_phases, rho, mu);
|
||||
satprops_.init(num_phases, relpermfunc);
|
||||
if (pvt_.numPhases() != satprops_.numPhases()) {
|
||||
OPM_THROW(std::runtime_error, "IncompPropertiesBasic::IncompPropertiesBasic() - Inconsistent number of phases in pvt data ("
|
||||
<< pvt_.numPhases() << ") and saturation-dependent function data (" << satprops_.numPhases() << ").");
|
||||
}
|
||||
viscosity_.resize(pvt_.numPhases());
|
||||
pvt_.mu(1, 0, 0, 0, &viscosity_[0]);
|
||||
}
|
||||
|
||||
IncompPropertiesBasic::~IncompPropertiesBasic()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/// \return D, the number of spatial dimensions.
|
||||
int IncompPropertiesBasic::numDimensions() const
|
||||
{
|
||||
return rock_.numDimensions();
|
||||
}
|
||||
|
||||
/// \return N, the number of cells.
|
||||
int IncompPropertiesBasic::numCells() const
|
||||
{
|
||||
return rock_.numCells();
|
||||
}
|
||||
|
||||
/// \return Array of N porosity values.
|
||||
const double* IncompPropertiesBasic::porosity() const
|
||||
{
|
||||
return rock_.porosity();
|
||||
}
|
||||
|
||||
/// \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* IncompPropertiesBasic::permeability() const
|
||||
{
|
||||
return rock_.permeability();
|
||||
}
|
||||
|
||||
|
||||
// ---- Fluid interface ----
|
||||
|
||||
/// \return P, the number of phases (also the number of components).
|
||||
int IncompPropertiesBasic::numPhases() const
|
||||
{
|
||||
return pvt_.numPhases();
|
||||
}
|
||||
|
||||
/// \return Array of P viscosity values.
|
||||
const double* IncompPropertiesBasic::viscosity() const
|
||||
{
|
||||
return &viscosity_[0];
|
||||
}
|
||||
|
||||
/// \return Array of P density values.
|
||||
const double* IncompPropertiesBasic::density() const
|
||||
{
|
||||
// No difference between reservoir and surface densities
|
||||
// modelled by this class.
|
||||
return pvt_.surfaceDensities();
|
||||
}
|
||||
|
||||
/// \return Array of P density values.
|
||||
const double* IncompPropertiesBasic::surfaceDensity() const
|
||||
{
|
||||
// No difference between reservoir and surface densities
|
||||
// modelled by this class.
|
||||
return pvt_.surfaceDensities();
|
||||
}
|
||||
|
||||
/// \param[in] n Number of data points.
|
||||
/// \param[in] s Array of nP saturation values.
|
||||
/// \param[in] cells Array of n cell indices to be associated with the s values.
|
||||
/// \param[out] kr Array of nP relperm values, array must be valid before calling.
|
||||
/// \param[out] dkrds If non-null: array of nP^2 relperm derivative values,
|
||||
/// array must be valid before calling.
|
||||
/// The P^2 derivative matrix is
|
||||
/// m_{ij} = \frac{dkr_i}{ds^j},
|
||||
/// and is output in Fortran order (m_00 m_10 m_20 m_01 ...)
|
||||
void IncompPropertiesBasic::relperm(const int n,
|
||||
const double* s,
|
||||
const int* /*cells*/,
|
||||
double* kr,
|
||||
double* dkrds) const
|
||||
{
|
||||
satprops_.relperm(n, s, kr, dkrds);
|
||||
}
|
||||
|
||||
|
||||
/// \param[in] n Number of data points.
|
||||
/// \param[in] s Array of nP saturation values.
|
||||
/// \param[in] cells Array of n cell indices to be associated with the s values.
|
||||
/// \param[out] pc Array of nP capillary pressure values, array must be valid before calling.
|
||||
/// \param[out] dpcds If non-null: array of nP^2 derivative values,
|
||||
/// array must be valid before calling.
|
||||
/// The P^2 derivative matrix is
|
||||
/// m_{ij} = \frac{dpc_i}{ds^j},
|
||||
/// and is output in Fortran order (m_00 m_10 m_20 m_01 ...)
|
||||
void IncompPropertiesBasic::capPress(const int n,
|
||||
const double* s,
|
||||
const int* /*cells*/,
|
||||
double* pc,
|
||||
double* dpcds) const
|
||||
{
|
||||
satprops_.capPress(n, s, pc, dpcds);
|
||||
}
|
||||
|
||||
|
||||
/// Obtain the range of allowable saturation values.
|
||||
/// In cell cells[i], saturation of phase p is allowed to be
|
||||
/// in the interval [smin[i*P + p], smax[i*P + p]].
|
||||
/// \param[in] n Number of data points.
|
||||
/// \param[in] cells Array of n cell indices.
|
||||
/// \param[out] smin Array of nP minimum s values, array must be valid before calling.
|
||||
/// \param[out] smax Array of nP maximum s values, array must be valid before calling.
|
||||
void IncompPropertiesBasic::satRange(const int n,
|
||||
const int* /*cells*/,
|
||||
double* smin,
|
||||
double* smax) const
|
||||
{
|
||||
satprops_.satRange(n, smin, smax);
|
||||
}
|
||||
|
||||
} // namespace Opm
|
||||
|
169
opm/core/props/IncompPropertiesBasic.hpp
Normal file
169
opm/core/props/IncompPropertiesBasic.hpp
Normal file
@ -0,0 +1,169 @@
|
||||
/*
|
||||
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_INCOMPPROPERTIESBASIC_HEADER_INCLUDED
|
||||
#define OPM_INCOMPPROPERTIESBASIC_HEADER_INCLUDED
|
||||
|
||||
#include <opm/core/props/IncompPropertiesInterface.hpp>
|
||||
#include <opm/core/props/rock/RockBasic.hpp>
|
||||
#include <opm/core/props/pvt/PvtPropertiesBasic.hpp>
|
||||
#include <opm/core/props/satfunc/SaturationPropsBasic.hpp>
|
||||
|
||||
namespace Opm
|
||||
{
|
||||
|
||||
/// Concrete class implementing the incompressible property
|
||||
/// interface, reading all necessary input from parameters.
|
||||
///
|
||||
/// Supports variable number of spatial dimensions, called D.
|
||||
/// Supports variable number of phases, called P.
|
||||
/// In general, when arguments call for n values of some vector or
|
||||
/// matrix property, such as saturation, they shall always be
|
||||
/// ordered cellwise:
|
||||
/// \f[ [s^1_0, s^2_0, s^3_0, s^1_1, s^2_2, \ldots ] \f]
|
||||
/// in which \f$ s^i_j \f$ denotes saturation of phase i in cell j.
|
||||
class IncompPropertiesBasic : public IncompPropertiesInterface
|
||||
{
|
||||
public:
|
||||
/// Construct from parameters.
|
||||
/// Note that all values passed through param should be in convenient units,
|
||||
/// as documented below.
|
||||
/// The following parameters are accepted (defaults):
|
||||
/// - \c num_phases (2) -- Must be 1 or 2.
|
||||
/// - \c relperm_func ("Linear") -- Must be "Constant", "Linear" or "Quadratic".
|
||||
/// - \c rho1 \c rho2, \c rho3 (1.0e3) -- Density in kg/m^3.
|
||||
/// - \c mu1 \c mu2, \c mu3 (1.0) -- Viscosity in cP.
|
||||
/// - \c porosity (1.0) -- Porosity.
|
||||
/// - \c permeability (100.0) -- Permeability in mD.
|
||||
IncompPropertiesBasic(const ParameterGroup& param,
|
||||
const int dim,
|
||||
const int num_cells);
|
||||
|
||||
|
||||
/// Construct properties from arguments.
|
||||
/// Note that all values should be given in SI units
|
||||
/// for this constructor.
|
||||
/// \param[in] num_phases Must be 1 or 2.
|
||||
/// \param[in] rho Phase densities in kg/m^3.
|
||||
/// \param[in] mu Phase viscosities in Pa*s.
|
||||
/// \param[in] porosity Must be in [0,1].
|
||||
/// \param[in] permeability Permeability in m^2.
|
||||
/// \param[in] dim Must be 2 or 3.
|
||||
/// \param[in] num_cells The number of grid cells.
|
||||
IncompPropertiesBasic(const int num_phases,
|
||||
const SaturationPropsBasic::RelPermFunc& relpermfunc,
|
||||
const std::vector<double>& rho,
|
||||
const std::vector<double>& mu,
|
||||
const double porosity,
|
||||
const double permeability,
|
||||
const int dim,
|
||||
const int num_cells);
|
||||
|
||||
/// Destructor.
|
||||
virtual ~IncompPropertiesBasic();
|
||||
|
||||
// ---- Rock interface ----
|
||||
|
||||
/// \return D, the number of spatial dimensions.
|
||||
virtual int numDimensions() const;
|
||||
|
||||
/// \return N, the number of cells.
|
||||
virtual int numCells() const;
|
||||
|
||||
/// \return Array of N porosity values.
|
||||
virtual 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).
|
||||
virtual const double* permeability() const;
|
||||
|
||||
|
||||
// ---- Fluid interface ----
|
||||
|
||||
/// \return P, the number of phases (also the number of components).
|
||||
virtual int numPhases() const;
|
||||
|
||||
/// \return Array of P viscosity values.
|
||||
virtual const double* viscosity() const;
|
||||
|
||||
/// Densities of fluid phases at reservoir conditions.
|
||||
/// \return Array of P density values.
|
||||
virtual const double* density() const;
|
||||
|
||||
/// Densities of fluid phases at surface conditions.
|
||||
/// \return Array of P density values.
|
||||
virtual const double* surfaceDensity() const;
|
||||
|
||||
/// \param[in] n Number of data points.
|
||||
/// \param[in] s Array of nP saturation values.
|
||||
/// \param[in] cells Array of n cell indices to be associated with the s values.
|
||||
/// \param[out] kr Array of nP relperm values, array must be valid before calling.
|
||||
/// \param[out] dkrds If non-null: array of nP^2 relperm derivative values,
|
||||
/// array must be valid before calling.
|
||||
/// The P^2 derivative matrix is
|
||||
/// m_{ij} = \frac{dkr_i}{ds^j},
|
||||
/// and is output in Fortran order (m_00 m_10 m_20 m_01 ...)
|
||||
virtual void relperm(const int n,
|
||||
const double* s,
|
||||
const int* cells,
|
||||
double* kr,
|
||||
double* dkrds) const;
|
||||
|
||||
|
||||
/// \param[in] n Number of data points.
|
||||
/// \param[in] s Array of nP saturation values.
|
||||
/// \param[in] cells Array of n cell indices to be associated with the s values.
|
||||
/// \param[out] pc Array of nP capillary pressure values, array must be valid before calling.
|
||||
/// \param[out] dpcds If non-null: array of nP^2 derivative values,
|
||||
/// array must be valid before calling.
|
||||
/// The P^2 derivative matrix is
|
||||
/// m_{ij} = \frac{dpc_i}{ds^j},
|
||||
/// and is output in Fortran order (m_00 m_10 m_20 m_01 ...)
|
||||
virtual void capPress(const int n,
|
||||
const double* s,
|
||||
const int* cells,
|
||||
double* pc,
|
||||
double* dpcds) const;
|
||||
|
||||
|
||||
/// Obtain the range of allowable saturation values.
|
||||
/// In cell cells[i], saturation of phase p is allowed to be
|
||||
/// in the interval [smin[i*P + p], smax[i*P + p]].
|
||||
/// \param[in] n Number of data points.
|
||||
/// \param[in] cells Array of n cell indices.
|
||||
/// \param[out] smin Array of nP minimum s values, array must be valid before calling.
|
||||
/// \param[out] smax Array of nP maximum s values, array must be valid before calling.
|
||||
virtual void satRange(const int n,
|
||||
const int* cells,
|
||||
double* smin,
|
||||
double* smax) const;
|
||||
private:
|
||||
RockBasic rock_;
|
||||
PvtPropertiesBasic pvt_;
|
||||
SaturationPropsBasic satprops_;
|
||||
std::vector<double> viscosity_;
|
||||
};
|
||||
|
||||
|
||||
|
||||
} // namespace Opm
|
||||
|
||||
|
||||
#endif // OPM_INCOMPPROPERTIESBASIC_HEADER_INCLUDED
|
168
opm/core/props/IncompPropertiesFromDeck.cpp
Normal file
168
opm/core/props/IncompPropertiesFromDeck.cpp
Normal file
@ -0,0 +1,168 @@
|
||||
/*
|
||||
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/props/IncompPropertiesFromDeck.hpp>
|
||||
#include <opm/material/fluidmatrixinteractions/EclMaterialLawManager.hpp>
|
||||
#include <opm/parser/eclipse/Units/Units.hpp>
|
||||
#include <opm/common/ErrorMacros.hpp>
|
||||
#include <iostream>
|
||||
|
||||
namespace Opm
|
||||
{
|
||||
IncompPropertiesFromDeck::IncompPropertiesFromDeck(const Opm::Deck& deck,
|
||||
const Opm::EclipseState& eclState,
|
||||
const UnstructuredGrid& grid)
|
||||
{
|
||||
rock_.init(eclState, grid.number_of_cells, grid.global_cell, grid.cartdims);
|
||||
pvt_.init(eclState, deck);
|
||||
auto materialLawManager = std::make_shared<typename SaturationPropsFromDeck::MaterialLawManager>();
|
||||
|
||||
std::vector<int> compressedToCartesianIdx(grid.number_of_cells);
|
||||
for (int cellIdx = 0; cellIdx < grid.number_of_cells; ++cellIdx) {
|
||||
if (grid.global_cell) {
|
||||
compressedToCartesianIdx[cellIdx] = grid.global_cell[cellIdx];
|
||||
}
|
||||
else {
|
||||
compressedToCartesianIdx[cellIdx] = cellIdx;
|
||||
}
|
||||
}
|
||||
materialLawManager->initFromDeck(deck, eclState, compressedToCartesianIdx);
|
||||
|
||||
satprops_.init(deck, materialLawManager);
|
||||
if (pvt_.numPhases() != satprops_.numPhases()) {
|
||||
OPM_THROW(std::runtime_error, "IncompPropertiesFromDeck::IncompPropertiesFromDeck() - Inconsistent number of phases in pvt data ("
|
||||
<< pvt_.numPhases() << ") and saturation-dependent function data (" << satprops_.numPhases() << ").");
|
||||
}
|
||||
}
|
||||
|
||||
IncompPropertiesFromDeck::~IncompPropertiesFromDeck()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/// \return D, the number of spatial dimensions.
|
||||
int IncompPropertiesFromDeck::numDimensions() const
|
||||
{
|
||||
return rock_.numDimensions();
|
||||
}
|
||||
|
||||
/// \return N, the number of cells.
|
||||
int IncompPropertiesFromDeck::numCells() const
|
||||
{
|
||||
return rock_.numCells();
|
||||
}
|
||||
|
||||
/// \return Array of N porosity values.
|
||||
const double* IncompPropertiesFromDeck::porosity() const
|
||||
{
|
||||
return rock_.porosity();
|
||||
}
|
||||
|
||||
/// \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* IncompPropertiesFromDeck::permeability() const
|
||||
{
|
||||
return rock_.permeability();
|
||||
}
|
||||
|
||||
|
||||
// ---- Fluid interface ----
|
||||
|
||||
/// \return P, the number of phases (also the number of components).
|
||||
int IncompPropertiesFromDeck::numPhases() const
|
||||
{
|
||||
return pvt_.numPhases();
|
||||
}
|
||||
|
||||
/// \return Array of P viscosity values.
|
||||
const double* IncompPropertiesFromDeck::viscosity() const
|
||||
{
|
||||
return pvt_.viscosity();
|
||||
}
|
||||
|
||||
/// \return Array of P density values.
|
||||
const double* IncompPropertiesFromDeck::density() const
|
||||
{
|
||||
return pvt_.reservoirDensities();
|
||||
}
|
||||
|
||||
/// \return Array of P density values.
|
||||
const double* IncompPropertiesFromDeck::surfaceDensity() const
|
||||
{
|
||||
return pvt_.surfaceDensities();
|
||||
}
|
||||
|
||||
/// \param[in] n Number of data points.
|
||||
/// \param[in] s Array of nP saturation values.
|
||||
/// \param[in] cells Array of n cell indices to be associated with the s values.
|
||||
/// \param[out] kr Array of nP relperm values, array must be valid before calling.
|
||||
/// \param[out] dkrds If non-null: array of nP^2 relperm derivative values,
|
||||
/// array must be valid before calling.
|
||||
/// The P^2 derivative matrix is
|
||||
/// m_{ij} = \frac{dkr_i}{ds^j},
|
||||
/// and is output in Fortran order (m_00 m_10 m_20 m_01 ...)
|
||||
void IncompPropertiesFromDeck::relperm(const int n,
|
||||
const double* s,
|
||||
const int* cells,
|
||||
double* kr,
|
||||
double* dkrds) const
|
||||
{
|
||||
satprops_.relperm(n, s, cells, kr, dkrds);
|
||||
}
|
||||
|
||||
|
||||
/// \param[in] n Number of data points.
|
||||
/// \param[in] s Array of nP saturation values.
|
||||
/// \param[in] cells Array of n cell indices to be associated with the s values.
|
||||
/// \param[out] pc Array of nP capillary pressure values, array must be valid before calling.
|
||||
/// \param[out] dpcds If non-null: array of nP^2 derivative values,
|
||||
/// array must be valid before calling.
|
||||
/// The P^2 derivative matrix is
|
||||
/// m_{ij} = \frac{dpc_i}{ds^j},
|
||||
/// and is output in Fortran order (m_00 m_10 m_20 m_01 ...)
|
||||
void IncompPropertiesFromDeck::capPress(const int n,
|
||||
const double* s,
|
||||
const int* cells,
|
||||
double* pc,
|
||||
double* dpcds) const
|
||||
{
|
||||
satprops_.capPress(n, s, cells, pc, dpcds);
|
||||
}
|
||||
|
||||
|
||||
/// Obtain the range of allowable saturation values.
|
||||
/// In cell cells[i], saturation of phase p is allowed to be
|
||||
/// in the interval [smin[i*P + p], smax[i*P + p]].
|
||||
/// \param[in] n Number of data points.
|
||||
/// \param[in] cells Array of n cell indices.
|
||||
/// \param[out] smin Array of nP minimum s values, array must be valid before calling.
|
||||
/// \param[out] smax Array of nP maximum s values, array must be valid before calling.
|
||||
void IncompPropertiesFromDeck::satRange(const int n,
|
||||
const int* cells,
|
||||
double* smin,
|
||||
double* smax) const
|
||||
{
|
||||
satprops_.satRange(n, cells, smin, smax);
|
||||
}
|
||||
|
||||
} // namespace Opm
|
||||
|
149
opm/core/props/IncompPropertiesFromDeck.hpp
Normal file
149
opm/core/props/IncompPropertiesFromDeck.hpp
Normal file
@ -0,0 +1,149 @@
|
||||
/*
|
||||
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_INCOMPPROPERTIESFROMDECK_HEADER_INCLUDED
|
||||
#define OPM_INCOMPPROPERTIESFROMDECK_HEADER_INCLUDED
|
||||
|
||||
#include <opm/parser/eclipse/Deck/Deck.hpp>
|
||||
#include <opm/parser/eclipse/EclipseState/EclipseState.hpp>
|
||||
#include <opm/core/props/IncompPropertiesInterface.hpp>
|
||||
#include <opm/core/props/pvt/PvtPropertiesIncompFromDeck.hpp>
|
||||
#include <opm/core/props/rock/RockFromDeck.hpp>
|
||||
#include <opm/core/props/satfunc/SaturationPropsFromDeck.hpp>
|
||||
|
||||
struct UnstructuredGrid;
|
||||
|
||||
namespace Opm
|
||||
{
|
||||
|
||||
/// Concrete class implementing the incompressible property
|
||||
/// interface, reading all data and properties from eclipse deck
|
||||
/// input.
|
||||
///
|
||||
/// Supports variable number of spatial dimensions, called D.
|
||||
/// Supports variable number of phases, called P.
|
||||
/// In general, when arguments call for n values of some vector or
|
||||
/// matrix property, such as saturation, they shall always be
|
||||
/// ordered cellwise:
|
||||
/// [s^1_0 s^2_0 s^3_0 s^1_1 s^2_2 ... ]
|
||||
/// in which s^i_j denotes saturation of phase i in cell j.
|
||||
class IncompPropertiesFromDeck : public IncompPropertiesInterface
|
||||
{
|
||||
public:
|
||||
/// Initialize from deck and grid.
|
||||
/// \param deck Deck input parser
|
||||
/// \param eclState The EclipseState (processed deck) produced by the opm-parser code
|
||||
/// \param grid Grid to which property object applies, needed for the
|
||||
/// mapping from cell indices (typically from a processed grid)
|
||||
/// to logical cartesian indices consistent with the deck.
|
||||
IncompPropertiesFromDeck(const Opm::Deck& deck,
|
||||
const Opm::EclipseState& eclState,
|
||||
const UnstructuredGrid& grid);
|
||||
|
||||
/// Destructor.
|
||||
virtual ~IncompPropertiesFromDeck();
|
||||
|
||||
// ---- Rock interface ----
|
||||
|
||||
/// \return D, the number of spatial dimensions.
|
||||
virtual int numDimensions() const;
|
||||
|
||||
/// \return N, the number of cells.
|
||||
virtual int numCells() const;
|
||||
|
||||
/// \return Array of N porosity values.
|
||||
virtual 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).
|
||||
virtual const double* permeability() const;
|
||||
|
||||
|
||||
// ---- Fluid interface ----
|
||||
|
||||
/// \return P, the number of phases (also the number of components).
|
||||
virtual int numPhases() const;
|
||||
|
||||
/// \return Array of P viscosity values.
|
||||
virtual const double* viscosity() const;
|
||||
|
||||
/// Densities of fluid phases at reservoir conditions.
|
||||
/// \return Array of P density values.
|
||||
virtual const double* density() const;
|
||||
|
||||
/// Densities of fluid phases at surface conditions.
|
||||
/// \return Array of P density values.
|
||||
virtual const double* surfaceDensity() const;
|
||||
|
||||
/// \param[in] n Number of data points.
|
||||
/// \param[in] s Array of nP saturation values.
|
||||
/// \param[in] cells Array of n cell indices to be associated with the s values.
|
||||
/// \param[out] kr Array of nP relperm values, array must be valid before calling.
|
||||
/// \param[out] dkrds If non-null: array of nP^2 relperm derivative values,
|
||||
/// array must be valid before calling.
|
||||
/// The P^2 derivative matrix is
|
||||
/// m_{ij} = \frac{dkr_i}{ds^j},
|
||||
/// and is output in Fortran order (m_00 m_10 m_20 m_01 ...)
|
||||
virtual void relperm(const int n,
|
||||
const double* s,
|
||||
const int* cells,
|
||||
double* kr,
|
||||
double* dkrds) const;
|
||||
|
||||
|
||||
/// \param[in] n Number of data points.
|
||||
/// \param[in] s Array of nP saturation values.
|
||||
/// \param[in] cells Array of n cell indices to be associated with the s values.
|
||||
/// \param[out] pc Array of nP capillary pressure values, array must be valid before calling.
|
||||
/// \param[out] dpcds If non-null: array of nP^2 derivative values,
|
||||
/// array must be valid before calling.
|
||||
/// The P^2 derivative matrix is
|
||||
/// m_{ij} = \frac{dpc_i}{ds^j},
|
||||
/// and is output in Fortran order (m_00 m_10 m_20 m_01 ...)
|
||||
virtual void capPress(const int n,
|
||||
const double* s,
|
||||
const int* cells,
|
||||
double* pc,
|
||||
double* dpcds) const;
|
||||
|
||||
|
||||
/// Obtain the range of allowable saturation values.
|
||||
/// In cell cells[i], saturation of phase p is allowed to be
|
||||
/// in the interval [smin[i*P + p], smax[i*P + p]].
|
||||
/// \param[in] n Number of data points.
|
||||
/// \param[in] cells Array of n cell indices.
|
||||
/// \param[out] smin Array of nP minimum s values, array must be valid before calling.
|
||||
/// \param[out] smax Array of nP maximum s values, array must be valid before calling.
|
||||
virtual void satRange(const int n,
|
||||
const int* cells,
|
||||
double* smin,
|
||||
double* smax) const;
|
||||
private:
|
||||
RockFromDeck rock_;
|
||||
PvtPropertiesIncompFromDeck pvt_;
|
||||
SaturationPropsFromDeck satprops_;
|
||||
};
|
||||
|
||||
|
||||
|
||||
} // namespace Opm
|
||||
|
||||
|
||||
#endif // OPM_INCOMPPROPERTIESFROMDECK_HEADER_INCLUDED
|
130
opm/core/props/IncompPropertiesInterface.hpp
Normal file
130
opm/core/props/IncompPropertiesInterface.hpp
Normal file
@ -0,0 +1,130 @@
|
||||
/*
|
||||
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_INCOMPPROPERTIESINTERFACE_HEADER_INCLUDED
|
||||
#define OPM_INCOMPPROPERTIESINTERFACE_HEADER_INCLUDED
|
||||
|
||||
namespace Opm
|
||||
{
|
||||
|
||||
/// Abstract base class for incompressible fluid and reservoir properties.
|
||||
///
|
||||
/// Supports variable number of spatial dimensions, called D.
|
||||
/// Supports variable number of phases, called P.
|
||||
/// In general, when arguments call for n values of some vector or
|
||||
/// matrix property, such as saturation, they shall always be
|
||||
/// ordered cellwise:
|
||||
/// [s^1_0 s^2_0 s^3_0 s^1_1 s^2_2 ... ]
|
||||
/// in which s^i_j denotes saturation of phase i in cell j.
|
||||
class IncompPropertiesInterface
|
||||
{
|
||||
public:
|
||||
virtual ~IncompPropertiesInterface() {}
|
||||
|
||||
// ---- Rock interface ----
|
||||
|
||||
/// \return D, the number of spatial dimensions.
|
||||
virtual int numDimensions() const = 0;
|
||||
|
||||
/// \return N, the number of cells.
|
||||
virtual int numCells() const = 0;
|
||||
|
||||
/// \return Array of N porosity values.
|
||||
virtual const double* porosity() const = 0;
|
||||
|
||||
/// \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).
|
||||
virtual const double* permeability() const = 0;
|
||||
|
||||
|
||||
// ---- Fluid interface ----
|
||||
|
||||
/// \return P, the number of phases (also the number of components).
|
||||
virtual int numPhases() const = 0;
|
||||
|
||||
/// \return Array of P viscosity values.
|
||||
virtual const double* viscosity() const = 0;
|
||||
|
||||
/// Densities of fluid phases at reservoir conditions.
|
||||
/// \return Array of P density values.
|
||||
virtual const double* density() const = 0;
|
||||
|
||||
/// Densities of fluid phases at surface conditions.
|
||||
/// Note: a reasonable question to ask is why there can be
|
||||
/// different densities at surface and reservoir conditions,
|
||||
/// when the phases are assumed incompressible. The answer is
|
||||
/// that even if we approximate the phases as being
|
||||
/// incompressible during simulation, the density difference
|
||||
/// between surface and reservoir may be larger. For accurate
|
||||
/// reporting and using data given in terms of surface values,
|
||||
/// we need to handle this difference.
|
||||
/// \return Array of P density values.
|
||||
virtual const double* surfaceDensity() const = 0;
|
||||
|
||||
/// \param[in] n Number of data points.
|
||||
/// \param[in] s Array of nP saturation values.
|
||||
/// \param[in] cells Array of n cell indices to be associated with the s values.
|
||||
/// \param[out] kr Array of nP relperm values, array must be valid before calling.
|
||||
/// \param[out] dkrds If non-null: array of nP^2 relperm derivative values,
|
||||
/// array must be valid before calling.
|
||||
/// The P^2 derivative matrix is
|
||||
/// m_{ij} = \frac{dkr_i}{ds^j},
|
||||
/// and is output in Fortran order (m_00 m_10 m_20 m01 ...)
|
||||
virtual void relperm(const int n,
|
||||
const double* s,
|
||||
const int* cells,
|
||||
double* kr,
|
||||
double* dkrds) const = 0;
|
||||
|
||||
|
||||
/// \param[in] n Number of data points.
|
||||
/// \param[in] s Array of nP saturation values.
|
||||
/// \param[in] cells Array of n cell indices to be associated with the s values.
|
||||
/// \param[out] pc Array of nP capillary pressure values, array must be valid before calling.
|
||||
/// \param[out] dpcds If non-null: array of nP^2 derivative values,
|
||||
/// array must be valid before calling.
|
||||
/// The P^2 derivative matrix is
|
||||
/// m_{ij} = \frac{dpc_i}{ds^j},
|
||||
/// and is output in Fortran order (m_00 m_10 m_20 m01 ...)
|
||||
virtual void capPress(const int n,
|
||||
const double* s,
|
||||
const int* cells,
|
||||
double* pc,
|
||||
double* dpcds) const = 0;
|
||||
|
||||
/// Obtain the range of allowable saturation values.
|
||||
/// In cell cells[i], saturation of phase p is allowed to be
|
||||
/// in the interval [smin[i*P + p], smax[i*P + p]].
|
||||
/// \param[in] n Number of data points.
|
||||
/// \param[in] cells Array of n cell indices.
|
||||
/// \param[out] smin Array of nP minimum s values, array must be valid before calling.
|
||||
/// \param[out] smax Array of nP maximum s values, array must be valid before calling.
|
||||
virtual void satRange(const int n,
|
||||
const int* cells,
|
||||
double* smin,
|
||||
double* smax) const = 0;
|
||||
};
|
||||
|
||||
|
||||
|
||||
} // namespace Opm
|
||||
|
||||
|
||||
#endif // OPM_INCOMPPROPERTIESINTERFACE_HEADER_INCLUDED
|
207
opm/core/props/IncompPropertiesShadow.hpp
Normal file
207
opm/core/props/IncompPropertiesShadow.hpp
Normal file
@ -0,0 +1,207 @@
|
||||
/* Copyright (c) 2013 Uni Research AS.
|
||||
This file is licensed under the GNU General Public License v3.0 or later. */
|
||||
#ifndef OPM_INCOMPPROPERTIESSHADOW_HEADER_INCLUDED
|
||||
#define OPM_INCOMPPROPERTIESSHADOW_HEADER_INCLUDED
|
||||
|
||||
#ifndef OPM_INCOMPPROPERTIESINTERFACE_HEADER_INCLUDED
|
||||
#include <opm/core/props/IncompPropertiesInterface.hpp>
|
||||
#endif /* OPM_INCOMPPROPERTIESINTERFACE_HEADER_INCLUDED */
|
||||
|
||||
namespace Opm
|
||||
{
|
||||
/**
|
||||
* Override certain properties with values from elsewhere.
|
||||
*
|
||||
* This allows mixing of property objects from several sources,
|
||||
* such as rock and fluid properties from a file but unsaturated
|
||||
* properties from a function. Care must be taken to setup the
|
||||
* shadowing so no inconsistencies arise.
|
||||
*
|
||||
* @remark
|
||||
* This object is mutable; if you change some properties
|
||||
* it will affect all clients that have references to it.
|
||||
* It is thus recommended to only use the mutable portion
|
||||
* when constructing the object, before passing it to clients.
|
||||
*
|
||||
* @example
|
||||
* @code{.cpp}
|
||||
* std::vector<double> poro;
|
||||
* IncompPropertiesFromDeck fromDeck(deck, grid);
|
||||
* simulate (IncompPropertiesShadow(fromDeck).usePorosity(poro));
|
||||
* @endcode
|
||||
*/
|
||||
struct IncompPropertiesShadow : public IncompPropertiesInterface
|
||||
{
|
||||
/**
|
||||
* Shadow another set of properties. If no properties are
|
||||
* overridden, the values from the original will be used.
|
||||
*/
|
||||
IncompPropertiesShadow (const IncompPropertiesInterface& original);
|
||||
|
||||
/**
|
||||
* Implement all methods from the IncompPropertiesInterface.
|
||||
*/
|
||||
virtual int numDimensions () const;
|
||||
virtual int numCells () const;
|
||||
virtual const double* porosity () const;
|
||||
virtual const double* permeability () const;
|
||||
virtual int numPhases () const;
|
||||
virtual const double* viscosity () const;
|
||||
virtual const double* density () const;
|
||||
virtual const double* surfaceDensity () const;
|
||||
virtual void relperm (const int n,
|
||||
const double* s,
|
||||
const int* cells,
|
||||
double* kr,
|
||||
double* dkrds) const;
|
||||
virtual void capPress (const int n,
|
||||
const double* s,
|
||||
const int* cells,
|
||||
double* pc,
|
||||
double* dpcds) const;
|
||||
virtual void satRange (const int n,
|
||||
const int* cells,
|
||||
double* smin,
|
||||
double* smax) const;
|
||||
|
||||
/**
|
||||
* Use a different set of porosities.
|
||||
*
|
||||
* @param poro
|
||||
* Iterator containing new porosity values. It must contain
|
||||
* numCells() values.
|
||||
* @return
|
||||
* A reference to this object, so it can be used for chaining.
|
||||
* @remark
|
||||
* This object does *not* assume ownership of the underlaying
|
||||
* memory nor makes any copies of it. Hence, the calling code
|
||||
* must manage the array so that it points to valid memory for
|
||||
* the lifetime of this object.
|
||||
*/
|
||||
IncompPropertiesShadow& usePorosity (const double* poro);
|
||||
IncompPropertiesShadow& usePorosity (const IncompPropertiesInterface& other);
|
||||
|
||||
/**
|
||||
* Use a different set of permeabilities.
|
||||
*
|
||||
* @param perm
|
||||
* Iterator containing new permeability values. It must contain
|
||||
* numCells()*numDimensions()*numDimensions() values.
|
||||
* @return
|
||||
* A reference to this object, so it can be used for chaining.
|
||||
* @remark
|
||||
* This object does *not* assume ownership of the underlaying
|
||||
* memory nor makes any copies of it. Hence, the calling code
|
||||
* must manage the array so that it points to valid memory for
|
||||
* the lifetime of this object.
|
||||
*/
|
||||
IncompPropertiesShadow& usePermeability (const double* perm);
|
||||
IncompPropertiesShadow& usePermeability (const IncompPropertiesInterface& other);
|
||||
|
||||
/**
|
||||
* Use a different set of viscosities.
|
||||
*
|
||||
* @param visc
|
||||
* Iterator containing new viscosity values. It must contain
|
||||
* numPhases() values.
|
||||
* @return
|
||||
* A reference to this object, so it can be used for chaining.
|
||||
* @remark
|
||||
* This object does *not* assume ownership of the underlaying
|
||||
* memory nor makes any copies of it. Hence, the calling code
|
||||
* must manage the array so that it points to valid memory for
|
||||
* the lifetime of this object.
|
||||
*/
|
||||
IncompPropertiesShadow& useViscosity (const double* visc);
|
||||
IncompPropertiesShadow& useViscosity (const IncompPropertiesInterface& other);
|
||||
|
||||
/**
|
||||
* Use a different set of densities.
|
||||
*
|
||||
* @param dens
|
||||
* Iterator containing new density values. It must contain
|
||||
* numPhases() values.
|
||||
* @return
|
||||
* A reference to this object, so it can be used for chaining.
|
||||
* @remark
|
||||
* This object does *not* assume ownership of the underlaying
|
||||
* memory nor makes any copies of it. Hence, the calling code
|
||||
* must manage the array so that it points to valid memory for
|
||||
* the lifetime of this object.
|
||||
*/
|
||||
IncompPropertiesShadow& useDensity (const double* dens);
|
||||
IncompPropertiesShadow& useDensity (const IncompPropertiesInterface& other);
|
||||
|
||||
/**
|
||||
* Use a different set of surface densities.
|
||||
*
|
||||
* @param surf
|
||||
* Iterator containing new surface density values. It must
|
||||
* contain numPhases() values.
|
||||
* @return
|
||||
* A reference to this object, so it can be used for chaining.
|
||||
* @remark
|
||||
* This object does *not* assume ownership of the underlaying
|
||||
* memory nor makes any copies of it. Hence, the calling code
|
||||
* must manage the array so that it points to valid memory for
|
||||
* the lifetime of this object.
|
||||
*/
|
||||
IncompPropertiesShadow& useSurfaceDensity (const double* surf);
|
||||
IncompPropertiesShadow& useSurfaceDensity (const IncompPropertiesInterface& other);
|
||||
|
||||
/**
|
||||
* Convenience method to set both porosity and permeability.
|
||||
*/
|
||||
IncompPropertiesShadow& useRockProps (const IncompPropertiesInterface& other);
|
||||
|
||||
/**
|
||||
* Convenience method to set both viscosity and density.
|
||||
*/
|
||||
IncompPropertiesShadow& useFluidProps (const IncompPropertiesInterface& other);
|
||||
|
||||
/**
|
||||
* Convenience method to set both rock and fluid properties.
|
||||
*/
|
||||
IncompPropertiesShadow& useRockAndFluidProps (const IncompPropertiesInterface& other);
|
||||
|
||||
private:
|
||||
/**
|
||||
* If we haven't set a property explicitly, then retrieve
|
||||
* them from this. This is a kind of prototype inheritance,
|
||||
* hence the name of this field.
|
||||
*/
|
||||
const IncompPropertiesInterface& prototype_;
|
||||
|
||||
/**
|
||||
* Bitfield which tells us which properties that has been
|
||||
* shadowed. The others are retrieved from the original
|
||||
* interface.
|
||||
*/
|
||||
int shadowed_;
|
||||
|
||||
/**
|
||||
* Bits that indicates which fields that has been overridden.
|
||||
*/
|
||||
static const int POROSITY = 1 << 1;
|
||||
static const int PERMEABILITY = 1 << 2;
|
||||
static const int VISCOSITY = 1 << 3;
|
||||
static const int DENSITY = 1 << 4;
|
||||
static const int SURFACE_DENSITY = 1 << 5;
|
||||
|
||||
/**
|
||||
* Pointers to alternative values. These pointers should only
|
||||
* be assumed to be valid if the corresponding bit in the mask
|
||||
* is set. No management is done for the memory this points to!
|
||||
*/
|
||||
const double* poro_;
|
||||
const double* perm_;
|
||||
const double* visc_;
|
||||
const double* dens_;
|
||||
const double* surf_;
|
||||
};
|
||||
} /* namespace Opm */
|
||||
|
||||
// body of inline methods are defined here:
|
||||
#include <opm/core/props/IncompPropertiesShadow_impl.hpp>
|
||||
|
||||
#endif /* OPM_INCOMPPROPERTIESSHADOW_HEADER_INCLUDED */
|
194
opm/core/props/IncompPropertiesShadow_impl.hpp
Normal file
194
opm/core/props/IncompPropertiesShadow_impl.hpp
Normal file
@ -0,0 +1,194 @@
|
||||
/* Copyright (c) 2013 Uni Research AS.
|
||||
This file is licensed under the GNU General Public License v3.0 or later. */
|
||||
#ifndef OPM_INCOMPPROPERTIESSHADOW_HEADER_INCLUDED
|
||||
#error Do not include IncompPropertiesShadow_impl.hpp directly!
|
||||
#endif /* OPM_INCOMPPROPERTIESSHADOW_HEADER_INCLUDED */
|
||||
|
||||
#include <opm/common/ErrorMacros.hpp>
|
||||
|
||||
namespace Opm
|
||||
{
|
||||
/**
|
||||
* Initialize so that all properties are retrieved from original.
|
||||
*/
|
||||
inline IncompPropertiesShadow::IncompPropertiesShadow (const IncompPropertiesInterface& original)
|
||||
: prototype_ (original)
|
||||
, shadowed_ (0)
|
||||
, poro_ (0)
|
||||
, perm_ (0)
|
||||
, visc_ (0)
|
||||
, dens_ (0)
|
||||
, surf_ (0)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* The format of the prototype and the shadow must be the same,
|
||||
* so these methods should always be forwarded directly.
|
||||
*/
|
||||
inline int IncompPropertiesShadow::numDimensions () const
|
||||
{
|
||||
return prototype_.numDimensions();
|
||||
}
|
||||
|
||||
inline int IncompPropertiesShadow::numCells () const
|
||||
{
|
||||
return prototype_.numCells();
|
||||
}
|
||||
|
||||
inline int IncompPropertiesShadow::numPhases () const
|
||||
{
|
||||
return prototype_.numPhases();
|
||||
}
|
||||
|
||||
/**
|
||||
* These methods are sufficiently advanced (the s parameter is a
|
||||
* non-integral index) for there not to be a trivial implementation,
|
||||
* so they are not overridden yet.
|
||||
*/
|
||||
inline void IncompPropertiesShadow::relperm (const int n,
|
||||
const double* s,
|
||||
const int* cells,
|
||||
double* kr,
|
||||
double* dkrds) const
|
||||
{
|
||||
prototype_.relperm (n, s, cells, kr, dkrds);
|
||||
}
|
||||
|
||||
inline void IncompPropertiesShadow::capPress (const int n,
|
||||
const double* s,
|
||||
const int* cells,
|
||||
double* pc,
|
||||
double* dpcds) const
|
||||
{
|
||||
prototype_.capPress (n, s, cells, pc, dpcds);
|
||||
}
|
||||
|
||||
inline void IncompPropertiesShadow::satRange (const int n,
|
||||
const int* cells,
|
||||
double* smin,
|
||||
double* smax) const
|
||||
{
|
||||
prototype_.satRange (n, cells, smin, smax);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the new value if indicated in the bitfield, otherwise
|
||||
* use the original value from the other object.
|
||||
*/
|
||||
inline const double* IncompPropertiesShadow::porosity () const
|
||||
{
|
||||
return (shadowed_ & POROSITY) ? poro_ : prototype_.porosity ();
|
||||
}
|
||||
|
||||
inline const double* IncompPropertiesShadow::permeability () const
|
||||
{
|
||||
return (shadowed_ & PERMEABILITY) ? perm_ : prototype_.permeability ();
|
||||
}
|
||||
|
||||
inline const double* IncompPropertiesShadow::viscosity () const
|
||||
{
|
||||
return (shadowed_ & VISCOSITY) ? visc_ : prototype_.viscosity ();
|
||||
}
|
||||
|
||||
inline const double* IncompPropertiesShadow::density () const
|
||||
{
|
||||
return (shadowed_ & DENSITY) ? dens_ : prototype_.density ();
|
||||
}
|
||||
|
||||
inline const double* IncompPropertiesShadow::surfaceDensity () const
|
||||
{
|
||||
return (shadowed_ & SURFACE_DENSITY) ? surf_ : prototype_.surfaceDensity ();
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the pointer and indicate that the new value should be used.
|
||||
*/
|
||||
inline IncompPropertiesShadow& IncompPropertiesShadow::usePorosity (const double* poro)
|
||||
{
|
||||
this->poro_ = poro;
|
||||
shadowed_ |= POROSITY;
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline IncompPropertiesShadow& IncompPropertiesShadow::usePermeability (const double* perm)
|
||||
{
|
||||
this->perm_ = perm;
|
||||
shadowed_ |= PERMEABILITY;
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline IncompPropertiesShadow& IncompPropertiesShadow::useViscosity (const double* visc)
|
||||
{
|
||||
this->visc_ = visc;
|
||||
shadowed_ |= VISCOSITY;
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline IncompPropertiesShadow& IncompPropertiesShadow::useDensity (const double* dens)
|
||||
{
|
||||
this->dens_ = dens;
|
||||
shadowed_ |= DENSITY;
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline IncompPropertiesShadow& IncompPropertiesShadow::useSurfaceDensity (const double* surf)
|
||||
{
|
||||
this->surf_ = surf;
|
||||
shadowed_ |= SURFACE_DENSITY;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy the pointer from another property interface, after checking
|
||||
* that they are compatible.
|
||||
*/
|
||||
inline IncompPropertiesShadow& IncompPropertiesShadow::usePorosity (const IncompPropertiesInterface& other)
|
||||
{
|
||||
assert (prototype_.numCells() == other.numCells());
|
||||
return usePorosity (other.porosity());
|
||||
}
|
||||
|
||||
inline IncompPropertiesShadow& IncompPropertiesShadow::usePermeability (const IncompPropertiesInterface& other)
|
||||
{
|
||||
assert (prototype_.numCells() == other.numCells());
|
||||
assert (prototype_.numDimensions() == other.numDimensions());
|
||||
return usePermeability (other.permeability());
|
||||
}
|
||||
|
||||
inline IncompPropertiesShadow& IncompPropertiesShadow::useViscosity (const IncompPropertiesInterface& other)
|
||||
{
|
||||
assert (prototype_.numPhases() == other.numPhases());
|
||||
return useViscosity (other.viscosity());
|
||||
}
|
||||
|
||||
inline IncompPropertiesShadow& IncompPropertiesShadow::useDensity (const IncompPropertiesInterface& other)
|
||||
{
|
||||
assert (prototype_.numPhases() == other.numPhases());
|
||||
return useDensity (other.density());
|
||||
}
|
||||
|
||||
inline IncompPropertiesShadow& IncompPropertiesShadow::useSurfaceDensity (const IncompPropertiesInterface& other)
|
||||
{
|
||||
assert (prototype_.numPhases() == other.numPhases());
|
||||
return useSurfaceDensity (other.surfaceDensity());
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience methods to set several set of properties at once.
|
||||
*/
|
||||
inline IncompPropertiesShadow& IncompPropertiesShadow::useRockProps (const IncompPropertiesInterface& other)
|
||||
{
|
||||
return usePorosity (other).usePermeability (other);
|
||||
}
|
||||
|
||||
inline IncompPropertiesShadow& IncompPropertiesShadow::useFluidProps (const IncompPropertiesInterface& other)
|
||||
{
|
||||
return useViscosity (other).useDensity (other).useSurfaceDensity (other);
|
||||
}
|
||||
|
||||
inline IncompPropertiesShadow& IncompPropertiesShadow::useRockAndFluidProps (const IncompPropertiesInterface& other)
|
||||
{
|
||||
return useRockProps (other).useFluidProps (other);
|
||||
}
|
||||
} /* namespace Opm */
|
182
opm/core/props/IncompPropertiesSinglePhase.cpp
Normal file
182
opm/core/props/IncompPropertiesSinglePhase.cpp
Normal file
@ -0,0 +1,182 @@
|
||||
/*
|
||||
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/core/props/IncompPropertiesSinglePhase.hpp>
|
||||
#include <opm/core/grid.h>
|
||||
#include <opm/parser/eclipse/Units/Units.hpp>
|
||||
#include <opm/common/ErrorMacros.hpp>
|
||||
|
||||
#include <opm/parser/eclipse/EclipseState/EclipseState.hpp>
|
||||
#include <opm/parser/eclipse/Deck/DeckItem.hpp>
|
||||
#include <opm/parser/eclipse/Deck/DeckKeyword.hpp>
|
||||
#include <opm/parser/eclipse/Deck/DeckRecord.hpp>
|
||||
|
||||
namespace Opm
|
||||
{
|
||||
IncompPropertiesSinglePhase::IncompPropertiesSinglePhase(const Opm::Deck& deck,
|
||||
const Opm::EclipseState& eclState,
|
||||
const UnstructuredGrid& grid)
|
||||
{
|
||||
rock_.init(eclState, grid.number_of_cells, grid.global_cell, grid.cartdims);
|
||||
|
||||
const auto& densities = eclState.getTableManager().getDensityTable();
|
||||
if( !densities.empty() ) {
|
||||
surface_density_ = densities[0].oil;
|
||||
} else {
|
||||
surface_density_ = 1000.0;
|
||||
OPM_MESSAGE("Input is missing DENSITY -- using a standard density of "
|
||||
<< surface_density_ << ".\n");
|
||||
}
|
||||
|
||||
// This will be modified if we have a PVCDO specification.
|
||||
reservoir_density_ = surface_density_;
|
||||
|
||||
if (deck.hasKeyword("PVCDO")) {
|
||||
const auto& pvcdoRecord = deck.getKeyword("PVCDO").getRecord(0);
|
||||
if (pvcdoRecord.getItem("OIL_COMPRESSIBILITY").getSIDouble(0) != 0.0 ||
|
||||
pvcdoRecord.getItem("OIL_VISCOSIBILITY").getSIDouble(0) != 0.0) {
|
||||
OPM_MESSAGE("Compressibility effects in PVCDO are ignored.");
|
||||
}
|
||||
reservoir_density_ /= pvcdoRecord.getItem("OIL_VOL_FACTOR").getSIDouble(0);
|
||||
viscosity_ = pvcdoRecord.getItem("OIL_VISCOSITY").getSIDouble(0);
|
||||
} else {
|
||||
viscosity_ = 1.0 * prefix::centi*unit::Poise;
|
||||
OPM_MESSAGE("Input is missing PVCDO -- using a standard viscosity of "
|
||||
<< viscosity_ << " and reservoir density equal to surface density.\n");
|
||||
}
|
||||
}
|
||||
|
||||
IncompPropertiesSinglePhase::~IncompPropertiesSinglePhase()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/// \return D, the number of spatial dimensions.
|
||||
int IncompPropertiesSinglePhase::numDimensions() const
|
||||
{
|
||||
return rock_.numDimensions();
|
||||
}
|
||||
|
||||
/// \return N, the number of cells.
|
||||
int IncompPropertiesSinglePhase::numCells() const
|
||||
{
|
||||
return rock_.numCells();
|
||||
}
|
||||
|
||||
/// \return Array of N porosity values.
|
||||
const double* IncompPropertiesSinglePhase::porosity() const
|
||||
{
|
||||
return rock_.porosity();
|
||||
}
|
||||
|
||||
/// \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* IncompPropertiesSinglePhase::permeability() const
|
||||
{
|
||||
return rock_.permeability();
|
||||
}
|
||||
|
||||
|
||||
// ---- Fluid interface ----
|
||||
|
||||
/// \return P, the number of phases (also the number of components).
|
||||
int IncompPropertiesSinglePhase::numPhases() const
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
/// \return Array of P viscosity values.
|
||||
const double* IncompPropertiesSinglePhase::viscosity() const
|
||||
{
|
||||
return &viscosity_;
|
||||
}
|
||||
|
||||
/// \return Array of P density values.
|
||||
const double* IncompPropertiesSinglePhase::density() const
|
||||
{
|
||||
return &reservoir_density_;
|
||||
}
|
||||
|
||||
/// \return Array of P density values.
|
||||
const double* IncompPropertiesSinglePhase::surfaceDensity() const
|
||||
{
|
||||
return &surface_density_;
|
||||
}
|
||||
|
||||
/// Relative permeability. Always returns 1 (and 0 for derivatives).
|
||||
/// \param[in] n Number of data points.
|
||||
/// \param[in] s Array of n saturation values.
|
||||
/// \param[in] cells Array of n cell indices to be associated with the s values.
|
||||
/// \param[out] kr Array of n relperm values, array must be valid before calling.
|
||||
/// \param[out] dkrds If non-null: array of n relperm derivative values,
|
||||
/// array must be valid before calling.
|
||||
void IncompPropertiesSinglePhase::relperm(const int n,
|
||||
const double* /* s */,
|
||||
const int* /* cells */,
|
||||
double* kr,
|
||||
double* dkrds) const
|
||||
{
|
||||
std::fill(kr, kr + n, 1.0);
|
||||
if (dkrds) {
|
||||
std::fill(dkrds, dkrds + n, 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Capillary pressure. Always returns zero.
|
||||
/// \param[in] n Number of data points.
|
||||
/// \param[in] s Array of n saturation values.
|
||||
/// \param[in] cells Array of n cell indices to be associated with the s values.
|
||||
/// \param[out] pc Array of n capillary pressure values, array must be valid before calling.
|
||||
/// \param[out] dpcds If non-null: array of n derivative values,
|
||||
/// array must be valid before calling.
|
||||
void IncompPropertiesSinglePhase::capPress(const int n,
|
||||
const double* /* s */,
|
||||
const int* /* cells */,
|
||||
double* pc,
|
||||
double* dpcds) const
|
||||
{
|
||||
std::fill(pc, pc + n, 0.0);
|
||||
if (dpcds) {
|
||||
std::fill(dpcds, dpcds + n, 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Obtain the range of allowable saturation values.
|
||||
/// Saturation range is just the point 1 for this class
|
||||
/// \param[in] n Number of data points.
|
||||
/// \param[in] cells Array of n cell indices.
|
||||
/// \param[out] smin Array of n minimum s values, array must be valid before calling.
|
||||
/// \param[out] smax Array of n maximum s values, array must be valid before calling.
|
||||
void IncompPropertiesSinglePhase::satRange(const int n,
|
||||
const int* /* cells */,
|
||||
double* smin,
|
||||
double* smax) const
|
||||
{
|
||||
std::fill(smin, smin + n, 1.0);
|
||||
std::fill(smax, smax + n, 1.0);
|
||||
}
|
||||
|
||||
} // namespace Opm
|
||||
|
146
opm/core/props/IncompPropertiesSinglePhase.hpp
Normal file
146
opm/core/props/IncompPropertiesSinglePhase.hpp
Normal file
@ -0,0 +1,146 @@
|
||||
/*
|
||||
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_INCOMPPROPERTIESSINGLEPHASE_HEADER_INCLUDED
|
||||
#define OPM_INCOMPPROPERTIESSINGLEPHASE_HEADER_INCLUDED
|
||||
|
||||
|
||||
|
||||
#include <opm/parser/eclipse/Deck/Deck.hpp>
|
||||
#include <opm/parser/eclipse/EclipseState/EclipseState.hpp>
|
||||
#include <opm/core/props/IncompPropertiesInterface.hpp>
|
||||
#include <opm/core/props/rock/RockFromDeck.hpp>
|
||||
|
||||
struct UnstructuredGrid;
|
||||
|
||||
namespace Opm
|
||||
{
|
||||
|
||||
/// Concrete class implementing the incompressible property
|
||||
/// interface for a simplified single-phase setting, reading all
|
||||
/// data and properties from eclipse deck input. The oil phase
|
||||
/// properties are used where applicable and available.
|
||||
///
|
||||
/// Supports variable number of spatial dimensions, called D.
|
||||
/// Supports a single phase only.
|
||||
/// In general, when arguments call for n values of some vector or
|
||||
/// matrix property, such as saturation, they shall always be
|
||||
/// ordered cellwise:
|
||||
/// [s^1_0 s^2_0 s^3_0 s^1_1 s^2_2 ... ]
|
||||
/// in which s^i_j denotes saturation of phase i in cell j.
|
||||
class IncompPropertiesSinglePhase : public IncompPropertiesInterface
|
||||
{
|
||||
public:
|
||||
/// Initialize from deck and grid.
|
||||
/// \param deck Deck input parser
|
||||
/// \param eclState The EclipseState (processed deck) produced by the opm-parser code
|
||||
/// \param grid Grid to which property object applies, needed for the
|
||||
/// mapping from cell indices (typically from a processed grid)
|
||||
/// to logical cartesian indices consistent with the deck.
|
||||
IncompPropertiesSinglePhase(const Opm::Deck& deck,
|
||||
const Opm::EclipseState& eclState,
|
||||
const UnstructuredGrid& grid);
|
||||
|
||||
/// Destructor.
|
||||
virtual ~IncompPropertiesSinglePhase();
|
||||
|
||||
// ---- Rock interface ----
|
||||
|
||||
/// \return D, the number of spatial dimensions.
|
||||
virtual int numDimensions() const;
|
||||
|
||||
/// \return N, the number of cells.
|
||||
virtual int numCells() const;
|
||||
|
||||
/// \return Array of N porosity values.
|
||||
virtual 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).
|
||||
virtual const double* permeability() const;
|
||||
|
||||
|
||||
// ---- Fluid interface ----
|
||||
|
||||
/// \return P, the number of phases (= 1).
|
||||
virtual int numPhases() const;
|
||||
|
||||
/// \return Array of P (= 1) viscosity values.
|
||||
virtual const double* viscosity() const;
|
||||
|
||||
/// Densities of fluid at reservoir conditions.
|
||||
/// \return Array of P (= 1) density values.
|
||||
virtual const double* density() const;
|
||||
|
||||
/// Densities of fluid phases at surface conditions.
|
||||
/// \return Array of P (= 1) density values.
|
||||
virtual const double* surfaceDensity() const;
|
||||
|
||||
/// Relative permeability. Always returns 1 (and 0 for derivatives).
|
||||
/// \param[in] n Number of data points.
|
||||
/// \param[in] s Array of n saturation values.
|
||||
/// \param[in] cells Array of n cell indices to be associated with the s values.
|
||||
/// \param[out] kr Array of n relperm values, array must be valid before calling.
|
||||
/// \param[out] dkrds If non-null: array of n relperm derivative values,
|
||||
/// array must be valid before calling.
|
||||
virtual void relperm(const int n,
|
||||
const double* s,
|
||||
const int* cells,
|
||||
double* kr,
|
||||
double* dkrds) const;
|
||||
|
||||
/// Capillary pressure. Always returns zero.
|
||||
/// \param[in] n Number of data points.
|
||||
/// \param[in] s Array of n saturation values.
|
||||
/// \param[in] cells Array of n cell indices to be associated with the s values.
|
||||
/// \param[out] pc Array of n capillary pressure values, array must be valid before calling.
|
||||
/// \param[out] dpcds If non-null: array of n derivative values,
|
||||
/// array must be valid before calling.
|
||||
virtual void capPress(const int n,
|
||||
const double* s,
|
||||
const int* cells,
|
||||
double* pc,
|
||||
double* dpcds) const;
|
||||
|
||||
|
||||
/// Obtain the range of allowable saturation values.
|
||||
/// Saturation range is just the point 1 for this class
|
||||
/// \param[in] n Number of data points.
|
||||
/// \param[in] cells Array of n cell indices.
|
||||
/// \param[out] smin Array of n minimum s values, array must be valid before calling.
|
||||
/// \param[out] smax Array of n maximum s values, array must be valid before calling.
|
||||
virtual void satRange(const int n,
|
||||
const int* cells,
|
||||
double* smin,
|
||||
double* smax) const;
|
||||
private:
|
||||
RockFromDeck rock_;
|
||||
double surface_density_;
|
||||
double reservoir_density_;
|
||||
double viscosity_;
|
||||
};
|
||||
|
||||
|
||||
|
||||
} // namespace Opm
|
||||
|
||||
|
||||
|
||||
#endif // OPM_INCOMPPROPERTIESSINGLEPHASE_HEADER_INCLUDED
|
203
opm/core/props/phaseUsageFromDeck.hpp
Normal file
203
opm/core/props/phaseUsageFromDeck.hpp
Normal file
@ -0,0 +1,203 @@
|
||||
|
||||
/*
|
||||
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_PHASEUSAGEFROMDECK_HEADER_INCLUDED
|
||||
#define OPM_PHASEUSAGEFROMDECK_HEADER_INCLUDED
|
||||
|
||||
#include <opm/core/props/BlackoilPhases.hpp>
|
||||
#include <opm/common/ErrorMacros.hpp>
|
||||
|
||||
#include <opm/parser/eclipse/Deck/Deck.hpp>
|
||||
#include <opm/parser/eclipse/EclipseState/EclipseState.hpp>
|
||||
#include <opm/parser/eclipse/EclipseState/Runspec.hpp>
|
||||
|
||||
|
||||
namespace Opm
|
||||
{
|
||||
|
||||
/// Looks at presence of WATER, OIL and GAS keywords in state object
|
||||
/// to determine active phases.
|
||||
inline PhaseUsage phaseUsageFromDeck(const Opm::EclipseState& eclipseState)
|
||||
{
|
||||
PhaseUsage pu;
|
||||
std::fill(pu.phase_used, pu.phase_used + BlackoilPhases::MaxNumPhases + BlackoilPhases::NumCryptoPhases, 0);
|
||||
|
||||
const auto& phase = eclipseState.runspec().phases();
|
||||
// Discover phase usage.
|
||||
pu.phase_used[BlackoilPhases::Aqua] = phase.active(Phase::WATER);
|
||||
pu.phase_used[BlackoilPhases::Liquid] = phase.active(Phase::OIL);
|
||||
pu.phase_used[BlackoilPhases::Vapour] = phase.active(Phase::GAS);
|
||||
|
||||
pu.num_phases = 0;
|
||||
int numActivePhases = 0;
|
||||
for (int phaseIdx = 0; phaseIdx < BlackoilPhases::MaxNumPhases; ++phaseIdx) {
|
||||
if (!pu.phase_used[phaseIdx]) {
|
||||
pu.phase_pos[phaseIdx] = -1;
|
||||
}
|
||||
else {
|
||||
pu.phase_pos[phaseIdx] = numActivePhases;
|
||||
++ numActivePhases;
|
||||
pu.num_phases = numActivePhases;
|
||||
}
|
||||
}
|
||||
|
||||
// Only 2 or 3 phase systems handled.
|
||||
if (pu.num_phases < 2 || pu.num_phases > 3) {
|
||||
OPM_THROW(std::runtime_error, "Cannot handle cases with " << pu.num_phases << " phases.");
|
||||
}
|
||||
|
||||
// We need oil systems, since we do not support the keywords needed for
|
||||
// water-gas systems.
|
||||
if (!pu.phase_used[BlackoilPhases::Liquid]) {
|
||||
OPM_THROW(std::runtime_error, "Cannot handle cases with no OIL, i.e. water-gas systems.");
|
||||
}
|
||||
|
||||
// Add solvent info
|
||||
pu.has_solvent = phase.active(Phase::SOLVENT);
|
||||
if (pu.has_solvent) {
|
||||
// this is quite a hack: even though solvent is not considered as in
|
||||
// MaxNumPhases and pu.num_phases because this would break a lot of
|
||||
// assumptions in old code, it is nevertheless an index to be translated
|
||||
// to. solvent and solvent are even larger hacks because not even this can be
|
||||
// done for them.
|
||||
pu.phase_pos[BlackoilPhases::Solvent] = numActivePhases;
|
||||
++ numActivePhases;
|
||||
}
|
||||
else
|
||||
pu.phase_pos[BlackoilPhases::Solvent] = -1;
|
||||
|
||||
// Add polymer info
|
||||
pu.has_polymer = phase.active(Phase::POLYMER);
|
||||
if (pu.has_polymer) {
|
||||
// this is quite a hack: even though polymer is not considered as in
|
||||
// MaxNumPhases and pu.num_phases because this would break a lot of
|
||||
// assumptions in old code, it is nevertheless an index to be translated
|
||||
// to. polymer and solvent are even larger hacks because not even this can be
|
||||
// done for them.
|
||||
pu.phase_pos[BlackoilPhases::Polymer] = numActivePhases;
|
||||
++ numActivePhases;
|
||||
}
|
||||
else
|
||||
pu.phase_pos[BlackoilPhases::Polymer] = -1;
|
||||
|
||||
// Add energy info
|
||||
pu.has_energy = phase.active(Phase::ENERGY);
|
||||
if (pu.has_energy) {
|
||||
// this is quite a hack: even though energy is not considered as in
|
||||
// MaxNumPhases and pu.num_phases because this would break a lot of
|
||||
// assumptions in old code, it is nevertheless an index to be translated
|
||||
// to. polymer and solvent are even larger hacks because not even this can be
|
||||
// done for them.
|
||||
pu.phase_pos[BlackoilPhases::Energy] = numActivePhases;
|
||||
++ numActivePhases;
|
||||
}
|
||||
else
|
||||
pu.phase_pos[BlackoilPhases::Energy] = -1;
|
||||
|
||||
return pu;
|
||||
}
|
||||
|
||||
/// Looks at presence of WATER, OIL and GAS keywords in deck
|
||||
/// to determine active phases.
|
||||
inline PhaseUsage phaseUsageFromDeck(const Opm::Deck& deck)
|
||||
{
|
||||
PhaseUsage pu;
|
||||
std::fill(pu.phase_used, pu.phase_used + BlackoilPhases::MaxNumPhases + BlackoilPhases::NumCryptoPhases, 0);
|
||||
|
||||
Runspec runspec( deck );
|
||||
const auto& phase = runspec.phases();
|
||||
|
||||
// Discover phase usage.
|
||||
pu.phase_used[BlackoilPhases::Aqua] = phase.active(Phase::WATER);
|
||||
pu.phase_used[BlackoilPhases::Liquid] = phase.active(Phase::OIL);
|
||||
pu.phase_used[BlackoilPhases::Vapour] = phase.active(Phase::GAS);
|
||||
|
||||
pu.num_phases = 0;
|
||||
int numActivePhases = 0;
|
||||
for (int phaseIdx = 0; phaseIdx < BlackoilPhases::MaxNumPhases; ++phaseIdx) {
|
||||
if (!pu.phase_used[phaseIdx]) {
|
||||
pu.phase_pos[phaseIdx] = -1;
|
||||
}
|
||||
else {
|
||||
pu.phase_pos[phaseIdx] = numActivePhases;
|
||||
++ numActivePhases;
|
||||
pu.num_phases = numActivePhases;
|
||||
}
|
||||
}
|
||||
|
||||
// Only 2 or 3 phase systems handled.
|
||||
if (pu.num_phases < 2 || pu.num_phases > 3) {
|
||||
OPM_THROW(std::runtime_error, "Cannot handle cases with " << pu.num_phases << " phases.");
|
||||
}
|
||||
|
||||
// We need oil systems, since we do not support the keywords needed for
|
||||
// water-gas systems.
|
||||
if (!pu.phase_used[BlackoilPhases::Liquid]) {
|
||||
OPM_THROW(std::runtime_error, "Cannot handle cases with no OIL, i.e. water-gas systems.");
|
||||
}
|
||||
|
||||
// Add solvent info
|
||||
pu.has_solvent = phase.active(Phase::SOLVENT);
|
||||
if (pu.has_solvent) {
|
||||
// this is quite a hack: even though solvent is not considered as in
|
||||
// MaxNumPhases and pu.num_phases because this would break a lot of
|
||||
// assumptions in old code, it is nevertheless an index to be translated
|
||||
// to. solvent and solvent are even larger hacks because not even this can be
|
||||
// done for them.
|
||||
pu.phase_pos[BlackoilPhases::Solvent] = numActivePhases;
|
||||
++ numActivePhases;
|
||||
}
|
||||
else
|
||||
pu.phase_pos[BlackoilPhases::Solvent] = -1;
|
||||
|
||||
// Add polymer info
|
||||
pu.has_polymer = phase.active(Phase::POLYMER);
|
||||
if (pu.has_polymer) {
|
||||
// this is quite a hack: even though polymer is not considered as in
|
||||
// MaxNumPhases and pu.num_phases because this would break a lot of
|
||||
// assumptions in old code, it is nevertheless an index to be translated
|
||||
// to. polymer and solvent are even larger hacks because not even this can be
|
||||
// done for them.
|
||||
pu.phase_pos[BlackoilPhases::Polymer] = numActivePhases;
|
||||
++ numActivePhases;
|
||||
}
|
||||
else
|
||||
pu.phase_pos[BlackoilPhases::Polymer] = -1;
|
||||
|
||||
// Add energy info
|
||||
pu.has_energy = phase.active(Phase::ENERGY);
|
||||
if (pu.has_energy) {
|
||||
// this is quite a hack: even though energy is not considered as in
|
||||
// MaxNumPhases and pu.num_phases because this would break a lot of
|
||||
// assumptions in old code, it is nevertheless an index to be translated
|
||||
// to. polymer and solvent are even larger hacks because not even this can be
|
||||
// done for them.
|
||||
pu.phase_pos[BlackoilPhases::Energy] = numActivePhases;
|
||||
++ numActivePhases;
|
||||
}
|
||||
else
|
||||
pu.phase_pos[BlackoilPhases::Energy] = -1;
|
||||
|
||||
return pu;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // OPM_PHASEUSAGEFROMDECK_HEADER_INCLUDED
|
183
opm/core/props/pvt/PvtPropertiesBasic.cpp
Normal file
183
opm/core/props/pvt/PvtPropertiesBasic.cpp
Normal file
@ -0,0 +1,183 @@
|
||||
/*
|
||||
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/props/pvt/PvtPropertiesBasic.hpp>
|
||||
#include <opm/parser/eclipse/Units/Units.hpp>
|
||||
#include <opm/common/ErrorMacros.hpp>
|
||||
|
||||
|
||||
namespace Opm
|
||||
{
|
||||
|
||||
PvtPropertiesBasic::PvtPropertiesBasic()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
void PvtPropertiesBasic::init(const ParameterGroup& param)
|
||||
{
|
||||
int num_phases = param.getDefault("num_phases", 2);
|
||||
if (num_phases > 3 || num_phases < 1) {
|
||||
OPM_THROW(std::runtime_error, "PvtPropertiesBasic::init() illegal num_phases: " << num_phases);
|
||||
}
|
||||
density_.resize(num_phases);
|
||||
viscosity_.resize(num_phases);
|
||||
// We currently do not allow the user to set B.
|
||||
formation_volume_factor_.clear();
|
||||
formation_volume_factor_.resize(num_phases, 1.0);
|
||||
|
||||
// Setting mu and rho from parameters
|
||||
using namespace Opm::prefix;
|
||||
using namespace Opm::unit;
|
||||
const double kgpm3 = kilogram/cubic(meter);
|
||||
const double cP = centi*Poise;
|
||||
std::string rname[3] = { "rho1", "rho2", "rho3" };
|
||||
double rdefault[3] = { 1.0e3, 1.0e3, 1.0e3 };
|
||||
std::string vname[3] = { "mu1", "mu2", "mu3" };
|
||||
double vdefault[3] = { 1.0, 1.0, 1.0 };
|
||||
for (int phase = 0; phase < num_phases; ++phase) {
|
||||
density_[phase] = kgpm3*param.getDefault(rname[phase], rdefault[phase]);
|
||||
viscosity_[phase] = cP*param.getDefault(vname[phase], vdefault[phase]);
|
||||
}
|
||||
}
|
||||
|
||||
void PvtPropertiesBasic::init(const int num_phases,
|
||||
const std::vector<double>& rho,
|
||||
const std::vector<double>& visc)
|
||||
{
|
||||
if (num_phases > 3 || num_phases < 1) {
|
||||
OPM_THROW(std::runtime_error, "PvtPropertiesBasic::init() illegal num_phases: " << num_phases);
|
||||
}
|
||||
// We currently do not allow the user to set B.
|
||||
formation_volume_factor_.clear();
|
||||
formation_volume_factor_.resize(num_phases, 1.0);
|
||||
density_ = rho;
|
||||
viscosity_ = visc;
|
||||
}
|
||||
|
||||
const double* PvtPropertiesBasic::surfaceDensities() const
|
||||
{
|
||||
return &density_[0];
|
||||
}
|
||||
|
||||
|
||||
int PvtPropertiesBasic::numPhases() const
|
||||
{
|
||||
return density_.size();
|
||||
}
|
||||
|
||||
PhaseUsage PvtPropertiesBasic::phaseUsage() const
|
||||
{
|
||||
PhaseUsage pu;
|
||||
pu.num_phases = numPhases();
|
||||
if (pu.num_phases == 2) {
|
||||
// Might just as well assume water-oil.
|
||||
pu.phase_used[BlackoilPhases::Aqua] = true;
|
||||
pu.phase_used[BlackoilPhases::Liquid] = true;
|
||||
pu.phase_used[BlackoilPhases::Vapour] = false;
|
||||
pu.phase_pos[BlackoilPhases::Aqua] = 0;
|
||||
pu.phase_pos[BlackoilPhases::Liquid] = 1;
|
||||
pu.phase_pos[BlackoilPhases::Vapour] = 1; // Unused.
|
||||
} else {
|
||||
assert(pu.num_phases == 3);
|
||||
pu.phase_used[BlackoilPhases::Aqua] = true;
|
||||
pu.phase_used[BlackoilPhases::Liquid] = true;
|
||||
pu.phase_used[BlackoilPhases::Vapour] = true;
|
||||
pu.phase_pos[BlackoilPhases::Aqua] = 0;
|
||||
pu.phase_pos[BlackoilPhases::Liquid] = 1;
|
||||
pu.phase_pos[BlackoilPhases::Vapour] = 2;
|
||||
}
|
||||
return pu;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void PvtPropertiesBasic::mu(const int n,
|
||||
const double* /*p*/,
|
||||
const double* /*T*/,
|
||||
const double* /*z*/,
|
||||
double* output_mu) const
|
||||
{
|
||||
const int np = numPhases();
|
||||
for (int phase = 0; phase < np; ++phase) {
|
||||
// #pragma omp parallel for
|
||||
for (int i = 0; i < n; ++i) {
|
||||
output_mu[np*i + phase] = viscosity_[phase];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PvtPropertiesBasic::B(const int n,
|
||||
const double* /*p*/,
|
||||
const double* /*T*/,
|
||||
const double* /*z*/,
|
||||
double* output_B) const
|
||||
{
|
||||
const int np = numPhases();
|
||||
for (int phase = 0; phase < np; ++phase) {
|
||||
// #pragma omp parallel for
|
||||
for (int i = 0; i < n; ++i) {
|
||||
output_B[np*i + phase] = formation_volume_factor_[phase];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PvtPropertiesBasic::dBdp(const int n,
|
||||
const double* /*p*/,
|
||||
const double* /*T*/,
|
||||
const double* /*z*/,
|
||||
double* output_B,
|
||||
double* output_dBdp) const
|
||||
{
|
||||
const int np = numPhases();
|
||||
for (int phase = 0; phase < np; ++phase) {
|
||||
// #pragma omp parallel for
|
||||
for (int i = 0; i < n; ++i) {
|
||||
output_B[np*i + phase] = formation_volume_factor_[phase];
|
||||
output_dBdp[np*i + phase] = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
void PvtPropertiesBasic::R(const int n,
|
||||
const double* /*p*/,
|
||||
const double* /*z*/,
|
||||
double* output_R) const
|
||||
{
|
||||
const int np = numPhases();
|
||||
std::fill(output_R, output_R + n*np, 0.0);
|
||||
}
|
||||
|
||||
void PvtPropertiesBasic::dRdp(const int n,
|
||||
const double* /*p*/,
|
||||
const double* /*z*/,
|
||||
double* output_R,
|
||||
double* output_dRdp) const
|
||||
{
|
||||
const int np = numPhases();
|
||||
std::fill(output_R, output_R + n*np, 0.0);
|
||||
std::fill(output_dRdp, output_dRdp + n*np, 0.0);
|
||||
}
|
||||
|
||||
} // namespace Opm
|
112
opm/core/props/pvt/PvtPropertiesBasic.hpp
Normal file
112
opm/core/props/pvt/PvtPropertiesBasic.hpp
Normal file
@ -0,0 +1,112 @@
|
||||
/*
|
||||
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_PVTPROPERTIESBASIC_HEADER_INCLUDED
|
||||
#define OPM_PVTPROPERTIESBASIC_HEADER_INCLUDED
|
||||
|
||||
#include <opm/core/utility/parameters/ParameterGroup.hpp>
|
||||
#include <opm/core/props/BlackoilPhases.hpp>
|
||||
#include <vector>
|
||||
|
||||
namespace Opm
|
||||
{
|
||||
|
||||
/// Class collecting simple pvt properties for 1-3 phases.
|
||||
/// All phases are incompressible and have constant viscosities.
|
||||
/// For all the methods, the following apply: p, T and z are unused.
|
||||
/// Output arrays shall be of size n*numPhases(), and must be valid
|
||||
/// before calling the method.
|
||||
/// NOTE: This class is intentionally similar to BlackoilPvtProperties.
|
||||
class PvtPropertiesBasic
|
||||
{
|
||||
public:
|
||||
/// Default constructor.
|
||||
PvtPropertiesBasic();
|
||||
|
||||
/// Initialize from parameters.
|
||||
/// The following parameters are accepted (defaults):
|
||||
/// - num_phases (2) -- Must be 1, 2 or 3.
|
||||
/// - rho1, rho2, rho3 (1.0e3) -- Density in kg/m^3
|
||||
/// - mu1, mu2, mu3 (1.0) -- Viscosity in cP
|
||||
void init(const ParameterGroup& param);
|
||||
|
||||
/// Initialize from arguments.
|
||||
/// Basic multi phase fluid pvt properties.
|
||||
void init(const int num_phases,
|
||||
const std::vector<double>& rho,
|
||||
const std::vector<double>& visc);
|
||||
|
||||
/// Number of active phases.
|
||||
int numPhases() const;
|
||||
|
||||
/// \return Object describing the active phases.
|
||||
PhaseUsage phaseUsage() const;
|
||||
|
||||
/// Densities of stock components at surface conditions.
|
||||
/// \return Array of size numPhases().
|
||||
const double* surfaceDensities() const;
|
||||
|
||||
/// Viscosity as a function of p, T and z.
|
||||
void mu(const int n,
|
||||
const double* p,
|
||||
const double* T,
|
||||
const double* z,
|
||||
double* output_mu) const;
|
||||
|
||||
/// Formation volume factor as a function of p, T and z.
|
||||
void B(const int n,
|
||||
const double* p,
|
||||
const double* T,
|
||||
const double* z,
|
||||
double* output_B) const;
|
||||
|
||||
/// Formation volume factor and p-derivative as functions of p, T and z.
|
||||
void dBdp(const int n,
|
||||
const double* p,
|
||||
const double* T,
|
||||
const double* z,
|
||||
double* output_B,
|
||||
double* output_dBdp) const;
|
||||
|
||||
/// Solution factor as a function of p and z.
|
||||
void R(const int n,
|
||||
const double* p,
|
||||
const double* z,
|
||||
double* output_R) const;
|
||||
|
||||
/// Solution factor and p-derivative as functions of p and z.
|
||||
void dRdp(const int n,
|
||||
const double* p,
|
||||
const double* z,
|
||||
double* output_R,
|
||||
double* output_dRdp) const;
|
||||
|
||||
private:
|
||||
// The PVT properties. We need to store one value per PVT
|
||||
// region.
|
||||
std::vector<double> density_;
|
||||
std::vector<double> viscosity_;
|
||||
std::vector<double> formation_volume_factor_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
#endif // OPM_PVTPROPERTIESBASIC_HEADER_INCLUDED
|
110
opm/core/props/pvt/PvtPropertiesIncompFromDeck.cpp
Normal file
110
opm/core/props/pvt/PvtPropertiesIncompFromDeck.cpp
Normal file
@ -0,0 +1,110 @@
|
||||
/*
|
||||
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/props/pvt/PvtPropertiesIncompFromDeck.hpp>
|
||||
#include <opm/core/props/phaseUsageFromDeck.hpp>
|
||||
#include <opm/parser/eclipse/EclipseState/EclipseState.hpp>
|
||||
#include <opm/parser/eclipse/Units/Units.hpp>
|
||||
#include <opm/common/ErrorMacros.hpp>
|
||||
#include <opm/core/props/BlackoilPhases.hpp>
|
||||
|
||||
|
||||
namespace Opm
|
||||
{
|
||||
|
||||
PvtPropertiesIncompFromDeck::PvtPropertiesIncompFromDeck()
|
||||
{
|
||||
}
|
||||
|
||||
void PvtPropertiesIncompFromDeck::init(const EclipseState& es, const Opm::Deck& deck)
|
||||
{
|
||||
// So far, this class only supports a single PVT region. TODO?
|
||||
int region_number = 0;
|
||||
|
||||
PhaseUsage phase_usage = phaseUsageFromDeck(deck);
|
||||
if (phase_usage.phase_used[PhaseUsage::Vapour] ||
|
||||
!phase_usage.phase_used[PhaseUsage::Aqua] ||
|
||||
!phase_usage.phase_used[PhaseUsage::Liquid]) {
|
||||
OPM_THROW(std::runtime_error, "PvtPropertiesIncompFromDeck::init() -- must have gas and oil phases (only) in deck input.\n");
|
||||
}
|
||||
|
||||
// Surface densities. Accounting for different orders in eclipse and our code.
|
||||
const auto& densities = es.getTableManager().getDensityTable();
|
||||
if (!densities.empty()) {
|
||||
surface_density_[phase_usage.phase_pos[PhaseUsage::Aqua]] = densities[region_number].water;
|
||||
surface_density_[phase_usage.phase_pos[PhaseUsage::Liquid]] = densities[region_number].oil;
|
||||
} else {
|
||||
OPM_THROW(std::runtime_error, "Input is missing DENSITY\n");
|
||||
}
|
||||
|
||||
// Make reservoir densities the same as surface densities initially.
|
||||
// We will modify them with formation volume factors if found.
|
||||
reservoir_density_ = surface_density_;
|
||||
|
||||
const auto& pvtw = es.getTableManager().getPvtwTable().at( region_number );
|
||||
|
||||
if (pvtw.compressibility != 0.0 || pvtw.viscosibility != 0.0) {
|
||||
OPM_MESSAGE("Compressibility effects in PVTW are ignored.");
|
||||
}
|
||||
|
||||
reservoir_density_[phase_usage.phase_pos[PhaseUsage::Aqua]] /= pvtw.volume_factor;
|
||||
viscosity_[phase_usage.phase_pos[PhaseUsage::Aqua]] = pvtw.viscosity;
|
||||
|
||||
// Oil viscosity.
|
||||
if (deck.hasKeyword("PVCDO")) {
|
||||
const auto& pvcdoRecord = deck.getKeyword("PVCDO").getRecord(region_number);
|
||||
|
||||
if (pvcdoRecord.getItem("OIL_COMPRESSIBILITY").getSIDouble(0) != 0.0 ||
|
||||
pvcdoRecord.getItem("OIL_VISCOSIBILITY").getSIDouble(0) != 0.0) {
|
||||
OPM_MESSAGE("Compressibility effects in PVCDO are ignored.");
|
||||
}
|
||||
reservoir_density_[phase_usage.phase_pos[PhaseUsage::Liquid]] /= pvcdoRecord.getItem("OIL_VOL_FACTOR").getSIDouble(0);
|
||||
viscosity_[phase_usage.phase_pos[PhaseUsage::Liquid]] = pvcdoRecord.getItem("OIL_VISCOSITY").getSIDouble(0);
|
||||
} else {
|
||||
OPM_THROW(std::runtime_error, "Input is missing PVCDO\n");
|
||||
}
|
||||
}
|
||||
|
||||
const double* PvtPropertiesIncompFromDeck::surfaceDensities() const
|
||||
{
|
||||
return surface_density_.data();
|
||||
}
|
||||
|
||||
|
||||
const double* PvtPropertiesIncompFromDeck::reservoirDensities() const
|
||||
{
|
||||
return reservoir_density_.data();
|
||||
}
|
||||
|
||||
|
||||
const double* PvtPropertiesIncompFromDeck::viscosity() const
|
||||
{
|
||||
return viscosity_.data();
|
||||
}
|
||||
|
||||
|
||||
int PvtPropertiesIncompFromDeck::numPhases() const
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
|
||||
|
||||
} // namespace Opm
|
77
opm/core/props/pvt/PvtPropertiesIncompFromDeck.hpp
Normal file
77
opm/core/props/pvt/PvtPropertiesIncompFromDeck.hpp
Normal file
@ -0,0 +1,77 @@
|
||||
/*
|
||||
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_PVTPROPERTIESINCOMPFROMDECK_HEADER_INCLUDED
|
||||
#define OPM_PVTPROPERTIESINCOMPFROMDECK_HEADER_INCLUDED
|
||||
|
||||
#include <opm/parser/eclipse/Deck/Deck.hpp>
|
||||
#include <opm/parser/eclipse/EclipseState/EclipseState.hpp>
|
||||
|
||||
#include <array>
|
||||
|
||||
namespace Opm
|
||||
{
|
||||
|
||||
/// Class collecting pvt properties for 2 phases, reading from
|
||||
/// eclipse input (keywords DENSITY, PVTW, PVCDO).
|
||||
///
|
||||
/// All phases are incompressible and have constant viscosities.
|
||||
/// NOTE: This class is intentionally similar to BlackoilPvtProperties.
|
||||
class PvtPropertiesIncompFromDeck
|
||||
{
|
||||
public:
|
||||
/// Default constructor.
|
||||
PvtPropertiesIncompFromDeck();
|
||||
|
||||
/// Initialize from deck.
|
||||
void init(const EclipseState&, const Opm::Deck& deck);
|
||||
|
||||
/// Number of active phases.
|
||||
int numPhases() const;
|
||||
|
||||
/// Densities of stock components at surface conditions.
|
||||
/// \return Array of size numPhases().
|
||||
const double* surfaceDensities() const;
|
||||
|
||||
/// Densities of stock components at reservoir conditions.
|
||||
/// Note: a reasonable question to ask is why there can be
|
||||
/// different densities at surface and reservoir conditions,
|
||||
/// when the phases are assumed incompressible. The answer is
|
||||
/// that even if we approximate the phases as being
|
||||
/// incompressible during simulation, the density difference
|
||||
/// between surface and reservoir may be larger. For accurate
|
||||
/// reporting and using data given in terms of surface values,
|
||||
/// we need to handle this difference.
|
||||
/// \return Array of size numPhases().
|
||||
const double* reservoirDensities() const;
|
||||
|
||||
/// Viscosities.
|
||||
const double* viscosity() const;
|
||||
|
||||
private:
|
||||
std::array<double, 2> surface_density_;
|
||||
std::array<double, 2> reservoir_density_;
|
||||
std::array<double, 2> viscosity_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
#endif // OPM_PVTPROPERTIESINCOMPFROMDECK_HEADER_INCLUDED
|
322
opm/core/props/pvt/ThermalGasPvtWrapper.hpp
Normal file
322
opm/core/props/pvt/ThermalGasPvtWrapper.hpp
Normal file
@ -0,0 +1,322 @@
|
||||
/*
|
||||
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_THERMAL_GAS_PVT_WRAPPER_HPP
|
||||
#define OPM_THERMAL_GAS_PVT_WRAPPER_HPP
|
||||
|
||||
#include <opm/core/props/pvt/PvtInterface.hpp>
|
||||
#include <opm/common/ErrorMacros.hpp>
|
||||
|
||||
#include <opm/parser/eclipse/EclipseState/EclipseState.hpp>
|
||||
#include <opm/parser/eclipse/EclipseState/Tables/GasvisctTable.hpp>
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace Opm
|
||||
{
|
||||
/// Class which wraps another (i.e., isothermal) PVT object into one which adds
|
||||
/// temperature dependence of gas
|
||||
class ThermalGasPvtWrapper : public PvtInterface
|
||||
{
|
||||
public:
|
||||
ThermalGasPvtWrapper()
|
||||
{}
|
||||
|
||||
|
||||
/// extract the quantities needed specify the temperature dependence of the gas
|
||||
/// viscosity and density from the deck
|
||||
void initFromDeck(std::shared_ptr<const PvtInterface> isothermalPvt,
|
||||
const Opm::Deck& deck,
|
||||
const Opm::EclipseState& eclipseState)
|
||||
{
|
||||
isothermalPvt_ = isothermalPvt;
|
||||
auto tables = eclipseState->getTableManager();
|
||||
int numRegions;
|
||||
if (deck->hasKeyword("PVTG"))
|
||||
numRegions = tables->getPvtgTables().size();
|
||||
else if (deck->hasKeyword("PVDG"))
|
||||
numRegions = tables->getPvdgTables().size();
|
||||
else
|
||||
OPM_THROW(std::runtime_error, "Gas phase was not initialized using a known way");
|
||||
|
||||
// viscosity
|
||||
if (deck->hasKeyword("GASVISCT")) {
|
||||
gasvisctTables_ = &tables->getGasvisctTables();
|
||||
assert(int(gasvisctTables_->size()) == numRegions);
|
||||
static_cast<void>(numRegions); //Silence compiler warning
|
||||
|
||||
gasCompIdx_ = deck->getKeyword("GCOMPIDX").getRecord(0).getItem("GAS_COMPONENT_INDEX").get< int >(0) - 1;
|
||||
gasvisctColumnName_ = "Viscosity"+std::to_string(static_cast<long long>(gasCompIdx_));
|
||||
}
|
||||
|
||||
// density
|
||||
if (deck->hasKeyword("TREF")) {
|
||||
tref_ = deck->getKeyword("TREF").getRecord(0).getItem("TEMPERATURE").getSIDouble(0);
|
||||
}
|
||||
}
|
||||
|
||||
virtual void mu(const int n,
|
||||
const int* pvtRegionIdx,
|
||||
const double* p,
|
||||
const double* T,
|
||||
const double* z,
|
||||
double* output_mu) const
|
||||
{
|
||||
if (gasvisctTables_)
|
||||
// TODO: temperature dependence for viscosity depending on z
|
||||
OPM_THROW(std::runtime_error,
|
||||
"temperature dependent viscosity as a function of z "
|
||||
"is not yet implemented!");
|
||||
|
||||
// compute the isothermal viscosity
|
||||
isothermalPvt_->mu(n, pvtRegionIdx, p, T, z, output_mu);
|
||||
}
|
||||
|
||||
virtual void mu(const int n,
|
||||
const int* pvtRegionIdx,
|
||||
const double* p,
|
||||
const double* T,
|
||||
const double* r,
|
||||
double* output_mu,
|
||||
double* output_dmudp,
|
||||
double* output_dmudr) const
|
||||
{
|
||||
if (gasvisctTables_ != 0) {
|
||||
for (int i = 0; i < n; ++i) {
|
||||
// temperature dependence of the gas phase. this assumes that the gas
|
||||
// component index has been set properly, and it also looses the
|
||||
// pressure dependence of gas. (This does not make much sense, but it
|
||||
// seems to be what the documentation for the GASVISCT keyword in the
|
||||
// RM says.)
|
||||
|
||||
|
||||
int regionIdx = getPvtRegionIndex_(pvtRegionIdx, i);
|
||||
double muGasvisct;
|
||||
{
|
||||
const GasvisctTable& gasvisctTable = gasvisctTables_->getTable<GasvisctTable>(regionIdx);
|
||||
muGasvisct = gasvisctTable.evaluate(gasvisctColumnName_, T[i]);
|
||||
}
|
||||
|
||||
output_mu[i] = muGasvisct;
|
||||
output_dmudp[i] = 0.0;
|
||||
output_dmudr[i] = 0.0;
|
||||
|
||||
// TODO (?): derivative of gas viscosity w.r.t. temperature.
|
||||
}
|
||||
}
|
||||
else {
|
||||
// compute the isothermal viscosity and its derivatives
|
||||
isothermalPvt_->mu(n, pvtRegionIdx, p, T, r, output_mu, output_dmudp, output_dmudr);
|
||||
}
|
||||
}
|
||||
|
||||
virtual void mu(const int n,
|
||||
const int* pvtRegionIdx,
|
||||
const double* p,
|
||||
const double* T,
|
||||
const double* r,
|
||||
const PhasePresence* cond,
|
||||
double* output_mu,
|
||||
double* output_dmudp,
|
||||
double* output_dmudr) const
|
||||
{
|
||||
if (gasvisctTables_ != 0) {
|
||||
for (int i = 0; i < n; ++i) {
|
||||
// temperature dependence of the gas phase. this assumes that the gas
|
||||
// component index has been set properly, and it also looses the
|
||||
// pressure dependence of gas. (This does not make much sense, but it
|
||||
// seems to be what the documentation for the GASVISCT keyword in the
|
||||
// RM says.)
|
||||
int regionIdx = getPvtRegionIndex_(pvtRegionIdx, i);
|
||||
double muGasvisct;
|
||||
{
|
||||
const GasvisctTable& gasvisctTable = gasvisctTables_->getTable<GasvisctTable>(regionIdx);
|
||||
muGasvisct = gasvisctTable.evaluate(gasvisctColumnName_, T[i]);
|
||||
}
|
||||
|
||||
output_mu[i] = muGasvisct;
|
||||
output_dmudp[i] = 0.0;
|
||||
output_dmudr[i] = 0.0;
|
||||
|
||||
// TODO (?): derivative of gas viscosity w.r.t. temperature.
|
||||
}
|
||||
}
|
||||
else {
|
||||
// compute the isothermal viscosity and its derivatives
|
||||
isothermalPvt_->mu(n, pvtRegionIdx, p, T, r, cond, output_mu, output_dmudp, output_dmudr);
|
||||
}
|
||||
}
|
||||
|
||||
virtual void B(const int n,
|
||||
const int* pvtRegionIdx,
|
||||
const double* p,
|
||||
const double* T,
|
||||
const double* z,
|
||||
double* output_B) const
|
||||
{
|
||||
// isothermal case
|
||||
isothermalPvt_->B(n, pvtRegionIdx, p, T, z, output_B);
|
||||
|
||||
if (tref_ > 0.0) {
|
||||
// the Eclipse TD/RM do not explicitly specify the relation of the gas
|
||||
// density and the temperature, but equation (69.49) (for Eclipse 2011.1)
|
||||
// implies that the temperature dependence of the gas phase is rho(T, p) =
|
||||
// rho(tref_, p)/tref_*T ...
|
||||
for (int i = 0; i < n; ++i) {
|
||||
double alpha = tref_/T[i];
|
||||
output_B[i] *= alpha;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
virtual void dBdp(const int n,
|
||||
const int* pvtRegionIdx,
|
||||
const double* p,
|
||||
const double* T,
|
||||
const double* z,
|
||||
double* output_B,
|
||||
double* output_dBdp) const
|
||||
{
|
||||
isothermalPvt_->dBdp(n, pvtRegionIdx, p, T, z, output_B, output_dBdp);
|
||||
|
||||
if (tref_ > 0.0) {
|
||||
// the Eclipse TD/RM do not explicitly specify the relation of the gas
|
||||
// density and the temperature, but equation (69.49) (for Eclipse 2011.1)
|
||||
// implies that the temperature dependence of the gas phase is rho(T, p) =
|
||||
// rho(tref_, p)/tref_*T ...
|
||||
for (int i = 0; i < n; ++i) {
|
||||
double alpha = tref_/T[i];
|
||||
output_B[i] *= alpha;
|
||||
output_dBdp[i] *= alpha;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
virtual void b(const int n,
|
||||
const int* pvtRegionIdx,
|
||||
const double* p,
|
||||
const double* T,
|
||||
const double* r,
|
||||
double* output_b,
|
||||
double* output_dbdp,
|
||||
double* output_dbdr) const
|
||||
{
|
||||
isothermalPvt_->b(n, pvtRegionIdx, p, T, r, output_b, output_dbdp, output_dbdr);
|
||||
|
||||
if (tref_ > 0.0) {
|
||||
// the Eclipse TD/RM do not explicitly specify the relation of the gas
|
||||
// density and the temperature, but equation (69.49) (for Eclipse 2011.1)
|
||||
// implies that the temperature dependence of the gas phase is rho(T, p) =
|
||||
// rho(tref_, p)/tref_*T ...
|
||||
for (int i = 0; i < n; ++i) {
|
||||
double alpha = T[i]/tref_;
|
||||
output_b[i] *= alpha;
|
||||
output_dbdp[i] *= alpha;
|
||||
output_dbdr[i] *= alpha;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
virtual void b(const int n,
|
||||
const int* pvtRegionIdx,
|
||||
const double* p,
|
||||
const double* T,
|
||||
const double* r,
|
||||
const PhasePresence* cond,
|
||||
double* output_b,
|
||||
double* output_dbdp,
|
||||
double* output_dbdr) const
|
||||
{
|
||||
isothermalPvt_->b(n, pvtRegionIdx, p, T, r, cond, output_b, output_dbdp, output_dbdr);
|
||||
|
||||
if (tref_ > 0.0) {
|
||||
// the Eclipse TD/RM do not explicitly specify the relation of the gas
|
||||
// density and the temperature, but equation (69.49) (for Eclipse 2011.1)
|
||||
// implies that the temperature dependence of the gas phase is rho(T, p) =
|
||||
// rho(tref_, p)/tref_*T ...
|
||||
for (int i = 0; i < n; ++i) {
|
||||
double alpha = T[i]/tref_;
|
||||
output_b[i] *= alpha;
|
||||
output_dbdp[i] *= alpha;
|
||||
output_dbdr[i] *= alpha;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
virtual void rsSat(const int n,
|
||||
const int* pvtRegionIdx,
|
||||
const double* p,
|
||||
double* output_rsSat,
|
||||
double* output_drsSatdp) const
|
||||
{
|
||||
isothermalPvt_->rsSat(n, pvtRegionIdx, p, output_rsSat, output_drsSatdp);
|
||||
}
|
||||
|
||||
virtual void rvSat(const int n,
|
||||
const int* pvtRegionIdx,
|
||||
const double* p,
|
||||
double* output_rvSat,
|
||||
double* output_drvSatdp) const
|
||||
{
|
||||
isothermalPvt_->rvSat(n, pvtRegionIdx, p, output_rvSat, output_drvSatdp);
|
||||
}
|
||||
|
||||
virtual void R(const int n,
|
||||
const int* pvtRegionIdx,
|
||||
const double* p,
|
||||
const double* z,
|
||||
double* output_R) const
|
||||
{
|
||||
isothermalPvt_->R(n, pvtRegionIdx, p, z, output_R);
|
||||
}
|
||||
|
||||
virtual void dRdp(const int n,
|
||||
const int* pvtRegionIdx,
|
||||
const double* p,
|
||||
const double* z,
|
||||
double* output_R,
|
||||
double* output_dRdp) const
|
||||
{
|
||||
isothermalPvt_->dRdp(n, pvtRegionIdx, p, z, output_R, output_dRdp);
|
||||
}
|
||||
|
||||
private:
|
||||
int getPvtRegionIndex_(const int* pvtRegionIdx, int cellIdx) const
|
||||
{
|
||||
if (!pvtRegionIdx)
|
||||
return 0;
|
||||
return pvtRegionIdx[cellIdx];
|
||||
}
|
||||
|
||||
// the PVT propertied for the isothermal case
|
||||
std::shared_ptr<const PvtInterface> isothermalPvt_;
|
||||
|
||||
// The PVT properties needed for temperature dependence of the viscosity. We need
|
||||
// to store one value per PVT region.
|
||||
const TableContainer* gasvisctTables_;
|
||||
std::string gasvisctColumnName_;
|
||||
int gasCompIdx_;
|
||||
|
||||
// The PVT properties needed for temperature dependence of the density.
|
||||
double tref_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
393
opm/core/props/pvt/ThermalOilPvtWrapper.hpp
Normal file
393
opm/core/props/pvt/ThermalOilPvtWrapper.hpp
Normal file
@ -0,0 +1,393 @@
|
||||
/*
|
||||
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_THERMAL_OIL_PVT_WRAPPER_HPP
|
||||
#define OPM_THERMAL_OIL_PVT_WRAPPER_HPP
|
||||
|
||||
#include <opm/core/props/pvt/PvtInterface.hpp>
|
||||
#include <opm/common/ErrorMacros.hpp>
|
||||
|
||||
#include <opm/parser/eclipse/EclipseState/EclipseState.hpp>
|
||||
#include <opm/parser/eclipse/EclipseState/Tables/OilvisctTable.hpp>
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace Opm
|
||||
{
|
||||
/// Class which wraps another (i.e., isothermal) PVT object into one which adds
|
||||
/// temperature dependence of oil
|
||||
class ThermalOilPvtWrapper : public PvtInterface
|
||||
{
|
||||
public:
|
||||
ThermalOilPvtWrapper()
|
||||
{}
|
||||
|
||||
|
||||
/// set the tables which specify the temperature dependence of the oil viscosity
|
||||
void initFromDeck(std::shared_ptr<const PvtInterface> isothermalPvt,
|
||||
const Opm::Deck& deck,
|
||||
const Opm::EclipseState& eclipseState)
|
||||
{
|
||||
isothermalPvt_ = isothermalPvt;
|
||||
|
||||
int numRegions;
|
||||
auto tables = eclipseState->getTableManager();
|
||||
|
||||
if (deck->hasKeyword("PVTO"))
|
||||
numRegions = tables->getPvtoTables().size();
|
||||
else if (deck->hasKeyword("PVDO"))
|
||||
numRegions = tables->getPvdoTables().size();
|
||||
else if (deck->hasKeyword("PVCDO"))
|
||||
numRegions = deck->getKeyword("PVCDO").size();
|
||||
else
|
||||
OPM_THROW(std::runtime_error, "Oil phase was not initialized using a known way");
|
||||
|
||||
// viscosity
|
||||
if (deck->hasKeyword("VISCREF")) {
|
||||
oilvisctTables_ = &tables->getOilvisctTables();
|
||||
const auto& viscrefKeyword = deck->getKeyword("VISCREF");
|
||||
|
||||
assert(int(oilvisctTables_->size()) == numRegions);
|
||||
assert(int(viscrefKeyword.size()) == numRegions);
|
||||
|
||||
viscrefPress_.resize(numRegions);
|
||||
viscrefRs_.resize(numRegions);
|
||||
muRef_.resize(numRegions);
|
||||
for (int regionIdx = 0; regionIdx < numRegions; ++regionIdx) {
|
||||
const auto& viscrefRecord = viscrefKeyword.getRecord(regionIdx);
|
||||
viscrefPress_[regionIdx] = viscrefRecord.getItem("REFERENCE_PRESSURE").getSIDouble(0);
|
||||
viscrefRs_[regionIdx] = viscrefRecord.getItem("REFERENCE_RS").getSIDouble(0);
|
||||
|
||||
// temperature used to calculate the reference viscosity [K]. the
|
||||
// value does not really matter if the underlying PVT object really
|
||||
// is isothermal...
|
||||
double Tref = 273.15 + 20;
|
||||
|
||||
// compute the reference viscosity using the isothermal PVT object.
|
||||
double tmp1, tmp2;
|
||||
isothermalPvt_->mu(1,
|
||||
®ionIdx,
|
||||
&viscrefPress_[regionIdx],
|
||||
&Tref,
|
||||
&viscrefRs_[regionIdx],
|
||||
&muRef_[regionIdx],
|
||||
&tmp1,
|
||||
&tmp2);
|
||||
}
|
||||
}
|
||||
|
||||
// quantities required for density. note that we just always use the values
|
||||
// for the first EOS. (since EOS != PVT region.)
|
||||
tref_ = 0.0;
|
||||
if (deck->hasKeyword("THERMEX1")) {
|
||||
oilCompIdx_ = deck->getKeyword("OCOMPIDX").getRecord(0).getItem("OIL_COMPONENT_INDEX").get< int >(0) - 1;
|
||||
|
||||
// always use the values of the first EOS
|
||||
tref_ = deck->getKeyword("TREF").getRecord(0).getItem("TEMPERATURE").getSIDouble(oilCompIdx_);
|
||||
pref_ = deck->getKeyword("PREF").getRecord(0).getItem("PRESSURE").getSIDouble(oilCompIdx_);
|
||||
cref_ = deck->getKeyword("CREF").getRecord(0).getItem("COMPRESSIBILITY").getSIDouble(oilCompIdx_);
|
||||
thermex1_ = deck->getKeyword("THERMEX1").getRecord(0).getItem("EXPANSION_COEFF").getSIDouble(oilCompIdx_);
|
||||
}
|
||||
}
|
||||
|
||||
virtual void mu(const int n,
|
||||
const int* pvtRegionIdx,
|
||||
const double* p,
|
||||
const double* T,
|
||||
const double* z,
|
||||
double* output_mu) const
|
||||
{
|
||||
if (oilvisctTables_)
|
||||
// TODO: temperature dependence for viscosity depending on z
|
||||
OPM_THROW(std::runtime_error,
|
||||
"temperature dependent viscosity as a function of z "
|
||||
"is not yet implemented!");
|
||||
|
||||
// compute the isothermal viscosity
|
||||
isothermalPvt_->mu(n, pvtRegionIdx, p, T, z, output_mu);
|
||||
}
|
||||
|
||||
virtual void mu(const int n,
|
||||
const int* pvtRegionIdx,
|
||||
const double* p,
|
||||
const double* T,
|
||||
const double* r,
|
||||
double* output_mu,
|
||||
double* output_dmudp,
|
||||
double* output_dmudr) const
|
||||
{
|
||||
// compute the isothermal viscosity and its derivatives
|
||||
isothermalPvt_->mu(n, pvtRegionIdx, p, T, r, output_mu, output_dmudp, output_dmudr);
|
||||
|
||||
if (!oilvisctTables_)
|
||||
// isothermal case
|
||||
return;
|
||||
|
||||
// temperature dependence
|
||||
for (int i = 0; i < n; ++i) {
|
||||
int regionIdx = getPvtRegionIndex_(pvtRegionIdx, i);
|
||||
|
||||
// calculate the viscosity of the isothermal keyword for the reference
|
||||
// pressure given by the VISCREF keyword.
|
||||
double muRef = muRef_[regionIdx];
|
||||
|
||||
// compute the viscosity deviation due to temperature
|
||||
double alpha;
|
||||
{
|
||||
const OilvisctTable& oilvisctTable = oilvisctTables_->getTable<OilvisctTable>(regionIdx);
|
||||
double muOilvisct = oilvisctTable.evaluate("Viscosity", T[i]);
|
||||
alpha = muOilvisct/muRef;
|
||||
}
|
||||
|
||||
output_mu[i] *= alpha;
|
||||
output_dmudp[i] *= alpha;
|
||||
output_dmudr[i] *= alpha;
|
||||
// TODO (?): derivative of viscosity w.r.t. temperature.
|
||||
}
|
||||
}
|
||||
|
||||
virtual void mu(const int n,
|
||||
const int* pvtRegionIdx,
|
||||
const double* p,
|
||||
const double* T,
|
||||
const double* r,
|
||||
const PhasePresence* cond,
|
||||
double* output_mu,
|
||||
double* output_dmudp,
|
||||
double* output_dmudr) const
|
||||
{
|
||||
// compute the isothermal viscosity and its derivatives
|
||||
isothermalPvt_->mu(n, pvtRegionIdx, p, T, r, cond, output_mu, output_dmudp, output_dmudr);
|
||||
|
||||
if (!oilvisctTables_)
|
||||
// isothermal case
|
||||
return;
|
||||
|
||||
// temperature dependence
|
||||
for (int i = 0; i < n; ++i) {
|
||||
int regionIdx = getPvtRegionIndex_(pvtRegionIdx, i);
|
||||
|
||||
// calculate the viscosity of the isothermal keyword for the reference
|
||||
// pressure given by the VISCREF keyword.
|
||||
double muRef = muRef_[regionIdx];
|
||||
|
||||
// compute the viscosity deviation due to temperature
|
||||
double alpha;
|
||||
{
|
||||
const OilvisctTable& oilvisctTable = oilvisctTables_->getTable<OilvisctTable>(regionIdx);
|
||||
double muOilvisct = oilvisctTable.evaluate("Viscosity", T[i]);
|
||||
alpha = muOilvisct/muRef;
|
||||
}
|
||||
output_mu[i] *= alpha;
|
||||
output_dmudp[i] *= alpha;
|
||||
output_dmudr[i] *= alpha;
|
||||
// TODO (?): derivative of viscosity w.r.t. temperature.
|
||||
}
|
||||
}
|
||||
|
||||
virtual void B(const int n,
|
||||
const int* pvtRegionIdx,
|
||||
const double* p,
|
||||
const double* T,
|
||||
const double* z,
|
||||
double* output_B) const
|
||||
{
|
||||
// isothermal case
|
||||
isothermalPvt_->B(n, pvtRegionIdx, p, T, z, output_B);
|
||||
|
||||
if (thermex1_ <= 0.0)
|
||||
// isothermal case
|
||||
return;
|
||||
|
||||
// deal with the temperature dependence of the oil phase. we use equation
|
||||
// (3.208) from the Eclipse 2011.1 Reference Manual, but we calculate rho_ref
|
||||
// using the isothermal keyword instead of using the value for the
|
||||
// components, so the oil compressibility is already dealt with there. Note
|
||||
// that we only do the part for the oil component here, the part for
|
||||
// dissolved gas is ignored so far.
|
||||
double cT1 = thermex1_;
|
||||
double TRef = tref_;
|
||||
for (int i = 0; i < n; ++i) {
|
||||
double alpha = (1 + cT1*(T[i] - TRef));
|
||||
output_B[i] *= alpha;
|
||||
}
|
||||
}
|
||||
|
||||
virtual void dBdp(const int n,
|
||||
const int* pvtRegionIdx,
|
||||
const double* p,
|
||||
const double* T,
|
||||
const double* z,
|
||||
double* output_B,
|
||||
double* output_dBdp) const
|
||||
{
|
||||
isothermalPvt_->dBdp(n, pvtRegionIdx, p, T, z, output_B, output_dBdp);
|
||||
|
||||
if (thermex1_ <= 0.0)
|
||||
// isothermal case
|
||||
return;
|
||||
|
||||
// deal with the temperature dependence of the oil phase. we use equation
|
||||
// (3.208) from the Eclipse 2011.1 Reference Manual, but we calculate rho_ref
|
||||
// using the isothermal keyword instead of using the value for the
|
||||
// components, so the oil compressibility is already dealt with there. Note
|
||||
// that we only do the part for the oil component here, the part for
|
||||
// dissolved gas is ignored so far.
|
||||
double cT1 = thermex1_;
|
||||
double TRef = tref_;
|
||||
for (int i = 0; i < n; ++i) {
|
||||
double alpha = (1 + cT1*(T[i] - TRef));
|
||||
output_B[i] *= alpha;
|
||||
output_dBdp[i] *= alpha;
|
||||
}
|
||||
}
|
||||
|
||||
virtual void b(const int n,
|
||||
const int* pvtRegionIdx,
|
||||
const double* p,
|
||||
const double* T,
|
||||
const double* r,
|
||||
double* output_b,
|
||||
double* output_dbdp,
|
||||
double* output_dbdr) const
|
||||
{
|
||||
isothermalPvt_->b(n, pvtRegionIdx, p, T, r, output_b, output_dbdp, output_dbdr);
|
||||
|
||||
if (thermex1_ <= 0.0)
|
||||
// isothermal case
|
||||
return;
|
||||
|
||||
// deal with the temperature dependence of the oil phase. we use equation
|
||||
// (3.208) from the Eclipse 2011.1 Reference Manual, but we calculate rho_ref
|
||||
// using the isothermal keyword instead of using the value for the
|
||||
// components, so the oil compressibility is already dealt with there. Note
|
||||
// that we only do the part for the oil component here, the part for
|
||||
// dissolved gas is ignored so far.
|
||||
double cT1 = thermex1_;
|
||||
double TRef = tref_;
|
||||
for (int i = 0; i < n; ++i) {
|
||||
double alpha = 1.0/(1 + cT1*(T[i] - TRef));
|
||||
output_b[i] *= alpha;
|
||||
output_dbdp[i] *= alpha;
|
||||
output_dbdr[i] *= alpha;
|
||||
}
|
||||
}
|
||||
|
||||
virtual void b(const int n,
|
||||
const int* pvtRegionIdx,
|
||||
const double* p,
|
||||
const double* T,
|
||||
const double* r,
|
||||
const PhasePresence* cond,
|
||||
double* output_b,
|
||||
double* output_dbdp,
|
||||
double* output_dbdr) const
|
||||
{
|
||||
isothermalPvt_->b(n, pvtRegionIdx, p, T, r, cond, output_b, output_dbdp, output_dbdr);
|
||||
|
||||
if (thermex1_ <= 0.0)
|
||||
// isothermal case
|
||||
return;
|
||||
|
||||
// deal with the temperature dependence of the oil phase. we use equation
|
||||
// (3.208) from the Eclipse 2011.1 Reference Manual, but we calculate rho_ref
|
||||
// using the isothermal keyword instead of using the value for the
|
||||
// components, so the oil compressibility is already dealt with there. Note
|
||||
// that we only do the part for the oil component here, the part for
|
||||
// dissolved gas is ignored so far.
|
||||
double cT1 = thermex1_;
|
||||
double TRef = tref_;
|
||||
for (int i = 0; i < n; ++i) {
|
||||
double alpha = 1.0/(1 + cT1*(T[i] - TRef));
|
||||
output_b[i] *= alpha;
|
||||
output_dbdp[i] *= alpha;
|
||||
output_dbdr[i] *= alpha;
|
||||
}
|
||||
}
|
||||
|
||||
virtual void rsSat(const int n,
|
||||
const int* pvtRegionIdx,
|
||||
const double* p,
|
||||
double* output_rsSat,
|
||||
double* output_drsSatdp) const
|
||||
{
|
||||
isothermalPvt_->rsSat(n, pvtRegionIdx, p, output_rsSat, output_drsSatdp);
|
||||
}
|
||||
|
||||
virtual void rvSat(const int n,
|
||||
const int* pvtRegionIdx,
|
||||
const double* p,
|
||||
double* output_rvSat,
|
||||
double* output_drvSatdp) const
|
||||
{
|
||||
isothermalPvt_->rvSat(n, pvtRegionIdx, p, output_rvSat, output_drvSatdp);
|
||||
}
|
||||
|
||||
virtual void R(const int n,
|
||||
const int* pvtRegionIdx,
|
||||
const double* p,
|
||||
const double* z,
|
||||
double* output_R) const
|
||||
{
|
||||
isothermalPvt_->R(n, pvtRegionIdx, p, z, output_R);
|
||||
}
|
||||
|
||||
virtual void dRdp(const int n,
|
||||
const int* pvtRegionIdx,
|
||||
const double* p,
|
||||
const double* z,
|
||||
double* output_R,
|
||||
double* output_dRdp) const
|
||||
{
|
||||
isothermalPvt_->dRdp(n, pvtRegionIdx, p, z, output_R, output_dRdp);
|
||||
}
|
||||
|
||||
private:
|
||||
int getPvtRegionIndex_(const int* pvtRegionIdx, int cellIdx) const
|
||||
{
|
||||
if (!pvtRegionIdx)
|
||||
return 0;
|
||||
return pvtRegionIdx[cellIdx];
|
||||
}
|
||||
|
||||
// the PVT propertied for the isothermal case
|
||||
std::shared_ptr<const PvtInterface> isothermalPvt_;
|
||||
|
||||
// The PVT properties needed for temperature dependence of the viscosity. We need
|
||||
// to store one value per PVT region.
|
||||
std::vector<double> viscrefPress_;
|
||||
std::vector<double> viscrefRs_;
|
||||
std::vector<double> muRef_;
|
||||
|
||||
const TableContainer* oilvisctTables_;
|
||||
|
||||
// The PVT properties needed for temperature dependence of the density. This is
|
||||
// specified as one value per EOS in the manual, but we unconditionally use the
|
||||
// expansion coefficient of the first EOS...
|
||||
int oilCompIdx_;
|
||||
double tref_;
|
||||
double pref_;
|
||||
double cref_;
|
||||
double thermex1_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
399
opm/core/props/pvt/ThermalWaterPvtWrapper.hpp
Normal file
399
opm/core/props/pvt/ThermalWaterPvtWrapper.hpp
Normal file
@ -0,0 +1,399 @@
|
||||
/*
|
||||
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_THERMAL_WATER_PVT_WRAPPER_HPP
|
||||
#define OPM_THERMAL_WATER_PVT_WRAPPER_HPP
|
||||
|
||||
#include <opm/core/props/pvt/PvtInterface.hpp>
|
||||
#include <opm/common/ErrorMacros.hpp>
|
||||
|
||||
#include <opm/parser/eclipse/EclipseState/EclipseState.hpp>
|
||||
#include <opm/parser/eclipse/EclipseState/Tables/WatvisctTable.hpp>
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace Opm
|
||||
{
|
||||
/// Class which wraps another (i.e., isothermal) PVT object into one which adds
|
||||
/// temperature dependence of water
|
||||
class ThermalWaterPvtWrapper : public PvtInterface
|
||||
{
|
||||
public:
|
||||
ThermalWaterPvtWrapper()
|
||||
{}
|
||||
|
||||
|
||||
/// set the tables which specify the temperature dependence of the water viscosity
|
||||
void initFromDeck(std::shared_ptr<const PvtInterface> isothermalPvt,
|
||||
const Opm::Deck& deck,
|
||||
const Opm::EclipseState& eclipseState)
|
||||
{
|
||||
isothermalPvt_ = isothermalPvt;
|
||||
watvisctTables_ = 0;
|
||||
|
||||
// stuff which we need to get from the PVTW keyword
|
||||
const auto& pvtwKeyword = deck->getKeyword("PVTW");
|
||||
int numRegions = pvtwKeyword.size();
|
||||
pvtwRefPress_.resize(numRegions);
|
||||
pvtwRefB_.resize(numRegions);
|
||||
pvtwCompressibility_.resize(numRegions);
|
||||
pvtwViscosity_.resize(numRegions);
|
||||
pvtwViscosibility_.resize(numRegions);
|
||||
for (int regionIdx = 0; regionIdx < numRegions; ++ regionIdx) {
|
||||
const auto& pvtwRecord = pvtwKeyword.getRecord(regionIdx);
|
||||
pvtwRefPress_[regionIdx] = pvtwRecord.getItem("P_REF").getSIDouble(0);
|
||||
pvtwRefB_[regionIdx] = pvtwRecord.getItem("WATER_VOL_FACTOR").getSIDouble(0);
|
||||
pvtwViscosity_[regionIdx] = pvtwRecord.getItem("WATER_VISCOSITY").getSIDouble(0);
|
||||
pvtwViscosibility_[regionIdx] = pvtwRecord.getItem("WATER_VISCOSIBILITY").getSIDouble(0);
|
||||
}
|
||||
|
||||
// quantities required for the temperature dependence of the viscosity
|
||||
// (basically we expect well-behaved VISCREF and WATVISCT keywords.)
|
||||
if (deck->hasKeyword("VISCREF")) {
|
||||
auto tables = eclipseState->getTableManager();
|
||||
watvisctTables_ = &tables->getWatvisctTables();
|
||||
const auto& viscrefKeyword = deck->getKeyword("VISCREF");
|
||||
|
||||
assert(int(watvisctTables_->size()) == numRegions);
|
||||
assert(int(viscrefKeyword.size()) == numRegions);
|
||||
|
||||
viscrefPress_.resize(numRegions);
|
||||
for (int regionIdx = 0; regionIdx < numRegions; ++ regionIdx) {
|
||||
const auto& viscrefRecord = viscrefKeyword.getRecord(regionIdx);
|
||||
|
||||
viscrefPress_[regionIdx] = viscrefRecord.getItem("REFERENCE_PRESSURE").getSIDouble(0);
|
||||
}
|
||||
}
|
||||
|
||||
// quantities required for the temperature dependence of the density
|
||||
if (deck->hasKeyword("WATDENT")) {
|
||||
const auto& watdentKeyword = deck->getKeyword("WATDENT");
|
||||
|
||||
assert(int(watdentKeyword.size()) == numRegions);
|
||||
|
||||
watdentRefTemp_.resize(numRegions);
|
||||
watdentCT1_.resize(numRegions);
|
||||
watdentCT2_.resize(numRegions);
|
||||
for (int regionIdx = 0; regionIdx < numRegions; ++regionIdx) {
|
||||
const auto& watdentRecord = watdentKeyword.getRecord(regionIdx);
|
||||
|
||||
watdentRefTemp_[regionIdx] = watdentRecord.getItem("REFERENCE_TEMPERATURE").getSIDouble(0);
|
||||
watdentCT1_[regionIdx] = watdentRecord.getItem("EXPANSION_COEFF_LINEAR").getSIDouble(0);
|
||||
watdentCT2_[regionIdx] = watdentRecord.getItem("EXPANSION_COEFF_QUADRATIC").getSIDouble(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
virtual void mu(const int n,
|
||||
const int* pvtRegionIdx,
|
||||
const double* p,
|
||||
const double* T,
|
||||
const double* z,
|
||||
double* output_mu) const
|
||||
{
|
||||
if (watvisctTables_)
|
||||
// TODO: temperature dependence for viscosity depending on z
|
||||
OPM_THROW(std::runtime_error,
|
||||
"temperature dependent viscosity as a function of z "
|
||||
"is not yet implemented!");
|
||||
|
||||
// compute the isothermal viscosity
|
||||
isothermalPvt_->mu(n, pvtRegionIdx, p, T, z, output_mu);
|
||||
}
|
||||
|
||||
virtual void mu(const int n,
|
||||
const int* pvtRegionIdx,
|
||||
const double* p,
|
||||
const double* T,
|
||||
const double* r,
|
||||
double* output_mu,
|
||||
double* output_dmudp,
|
||||
double* output_dmudr) const
|
||||
{
|
||||
// compute the isothermal viscosity and its derivatives
|
||||
isothermalPvt_->mu(n, pvtRegionIdx, p, T, r, output_mu, output_dmudp, output_dmudr);
|
||||
|
||||
if (!watvisctTables_)
|
||||
// isothermal case
|
||||
return;
|
||||
|
||||
// temperature dependence
|
||||
for (int i = 0; i < n; ++i) {
|
||||
int tableIdx = getTableIndex_(pvtRegionIdx, i);
|
||||
|
||||
// calculate the viscosity of the isothermal keyword for the reference
|
||||
// pressure given by the VISCREF keyword.
|
||||
double x = -pvtwViscosibility_[tableIdx]*(viscrefPress_[tableIdx] - pvtwRefPress_[tableIdx]);
|
||||
double muRef = pvtwViscosity_[tableIdx]/(1.0 + x + 0.5*x*x);
|
||||
|
||||
// compute the viscosity deviation due to temperature
|
||||
double alpha;
|
||||
{
|
||||
const WatvisctTable& watVisctTable = watvisctTables_->getTable<WatvisctTable>(tableIdx);
|
||||
double muWatvisct = watVisctTable.evaluate("Viscosity", T[i]);
|
||||
alpha = muWatvisct/muRef;
|
||||
}
|
||||
|
||||
output_mu[i] *= alpha;
|
||||
output_dmudp[i] *= alpha;
|
||||
output_dmudr[i] *= alpha;
|
||||
// TODO (?): derivative of viscosity w.r.t. temperature.
|
||||
}
|
||||
}
|
||||
|
||||
virtual void mu(const int n,
|
||||
const int* pvtRegionIdx,
|
||||
const double* p,
|
||||
const double* T,
|
||||
const double* r,
|
||||
const PhasePresence* cond,
|
||||
double* output_mu,
|
||||
double* output_dmudp,
|
||||
double* output_dmudr) const
|
||||
{
|
||||
// compute the isothermal viscosity and its derivatives
|
||||
isothermalPvt_->mu(n, pvtRegionIdx, p, T, r, cond, output_mu, output_dmudp, output_dmudr);
|
||||
|
||||
if (!watvisctTables_)
|
||||
// isothermal case
|
||||
return;
|
||||
|
||||
// temperature dependence
|
||||
for (int i = 0; i < n; ++i) {
|
||||
int tableIdx = getTableIndex_(pvtRegionIdx, i);
|
||||
|
||||
// calculate the viscosity of the isothermal keyword for the reference
|
||||
// pressure given by the VISCREF keyword.
|
||||
double x = -pvtwViscosibility_[tableIdx]*(viscrefPress_[tableIdx] - pvtwRefPress_[tableIdx]);
|
||||
double muRef = pvtwViscosity_[tableIdx]/(1.0 + x + 0.5*x*x);
|
||||
|
||||
// compute the viscosity deviation due to temperature
|
||||
double alpha;
|
||||
{
|
||||
const WatvisctTable& watVisctTable = watvisctTables_->getTable<WatvisctTable>(tableIdx);
|
||||
double muWatvisct = watVisctTable.evaluate("Viscosity", T[i]);
|
||||
alpha = muWatvisct/muRef;
|
||||
}
|
||||
output_mu[i] *= alpha;
|
||||
output_dmudp[i] *= alpha;
|
||||
output_dmudr[i] *= alpha;
|
||||
// TODO (?): derivative of viscosity w.r.t. temperature.
|
||||
}
|
||||
}
|
||||
|
||||
virtual void B(const int n,
|
||||
const int* pvtRegionIdx,
|
||||
const double* p,
|
||||
const double* T,
|
||||
const double* z,
|
||||
double* output_B) const
|
||||
{
|
||||
if (watdentRefTemp_.empty()) {
|
||||
// isothermal case
|
||||
isothermalPvt_->B(n, pvtRegionIdx, p, T, z, output_B);
|
||||
return;
|
||||
}
|
||||
|
||||
// This changes how the water density depends on pressure compared to what's
|
||||
// used for the PVTW keyword, but it seems to be what Eclipse does. For
|
||||
// details, see the documentation for the WATDENT keyword in the Eclipse RM.
|
||||
for (int i = 0; i < n; ++i) {
|
||||
int tableIdx = getTableIndex_(pvtRegionIdx, i);
|
||||
double BwRef = pvtwRefB_[tableIdx];
|
||||
double TRef = watdentRefTemp_[tableIdx];
|
||||
double X = pvtwCompressibility_[tableIdx]*(p[i] - pvtwRefPress_[tableIdx]);
|
||||
double cT1 = watdentCT1_[tableIdx];
|
||||
double cT2 = watdentCT2_[tableIdx];
|
||||
double Y = T[i] - TRef;
|
||||
double Bw = BwRef*(1 - X)*(1 + cT1*Y + cT2*Y*Y);
|
||||
output_B[i] = Bw;
|
||||
}
|
||||
}
|
||||
|
||||
virtual void dBdp(const int n,
|
||||
const int* pvtRegionIdx,
|
||||
const double* p,
|
||||
const double* T,
|
||||
const double* z,
|
||||
double* output_B,
|
||||
double* output_dBdp) const
|
||||
{
|
||||
if (watdentRefTemp_.empty()) {
|
||||
// isothermal case
|
||||
isothermalPvt_->dBdp(n, pvtRegionIdx, p, T, z, output_B, output_dBdp);
|
||||
return;
|
||||
}
|
||||
|
||||
// This changes how the water density depends on pressure. This is awkward,
|
||||
// but it seems to be what Eclipse does. See the documentation for the
|
||||
// WATDENT keyword in the Eclipse RM
|
||||
for (int i = 0; i < n; ++i) {
|
||||
int tableIdx = getTableIndex_(pvtRegionIdx, i);
|
||||
double BwRef = pvtwRefB_[tableIdx];
|
||||
double TRef = watdentRefTemp_[tableIdx];
|
||||
double X = pvtwCompressibility_[tableIdx]*(p[i] - pvtwRefPress_[tableIdx]);
|
||||
double cT1 = watdentCT1_[tableIdx];
|
||||
double cT2 = watdentCT2_[tableIdx];
|
||||
double Y = T[i] - TRef;
|
||||
double Bw = BwRef*(1 - X)*(1 + cT1*Y + cT2*Y*Y);
|
||||
output_B[i] = Bw;
|
||||
}
|
||||
|
||||
std::fill(output_dBdp, output_dBdp + n, 0.0);
|
||||
}
|
||||
|
||||
virtual void b(const int n,
|
||||
const int* pvtRegionIdx,
|
||||
const double* p,
|
||||
const double* T,
|
||||
const double* r,
|
||||
double* output_b,
|
||||
double* output_dbdp,
|
||||
double* output_dbdr) const
|
||||
{
|
||||
if (watdentRefTemp_.empty()) {
|
||||
// isothermal case
|
||||
isothermalPvt_->b(n, pvtRegionIdx, p, T, r, output_b, output_dbdp, output_dbdr);
|
||||
return;
|
||||
}
|
||||
|
||||
// This changes how the water density depends on pressure. This is awkward,
|
||||
// but it seems to be what Eclipse does. See the documentation for the
|
||||
// WATDENT keyword in the Eclipse RM
|
||||
for (int i = 0; i < n; ++i) {
|
||||
int tableIdx = getTableIndex_(pvtRegionIdx, i);
|
||||
double BwRef = pvtwRefB_[tableIdx];
|
||||
double TRef = watdentRefTemp_[tableIdx];
|
||||
double X = pvtwCompressibility_[tableIdx]*(p[i] - pvtwRefPress_[tableIdx]);
|
||||
double cT1 = watdentCT1_[tableIdx];
|
||||
double cT2 = watdentCT2_[tableIdx];
|
||||
double Y = T[i] - TRef;
|
||||
double Bw = BwRef*(1 - X)*(1 + cT1*Y + cT2*Y*Y);
|
||||
output_b[i] = 1.0/Bw;
|
||||
}
|
||||
|
||||
std::fill(output_dbdp, output_dbdp + n, 0.0);
|
||||
std::fill(output_dbdr, output_dbdr + n, 0.0);
|
||||
}
|
||||
|
||||
virtual void b(const int n,
|
||||
const int* pvtRegionIdx,
|
||||
const double* p,
|
||||
const double* T,
|
||||
const double* r,
|
||||
const PhasePresence* cond,
|
||||
double* output_b,
|
||||
double* output_dbdp,
|
||||
double* output_dbdr) const
|
||||
{
|
||||
if (watdentRefTemp_.empty()) {
|
||||
// isothermal case
|
||||
isothermalPvt_->b(n, pvtRegionIdx, p, T, r, cond, output_b, output_dbdp, output_dbdr);
|
||||
return;
|
||||
}
|
||||
|
||||
// This changes pressure dependence of the water density, but it seems to be
|
||||
// what Eclipse does. See the documentation for the WATDENT keyword in the
|
||||
// Eclipse RM
|
||||
for (int i = 0; i < n; ++i) {
|
||||
int tableIdx = getTableIndex_(pvtRegionIdx, i);
|
||||
double BwRef = pvtwRefB_[tableIdx];
|
||||
double TRef = watdentRefTemp_[tableIdx];
|
||||
double X = pvtwCompressibility_[tableIdx]*(p[i] - pvtwRefPress_[tableIdx]);
|
||||
double cT1 = watdentCT1_[tableIdx];
|
||||
double cT2 = watdentCT2_[tableIdx];
|
||||
double Y = T[i] - TRef;
|
||||
double Bw = BwRef*(1 - X)*(1 + cT1*Y + cT2*Y*Y);
|
||||
output_b[i] = 1.0/Bw;
|
||||
}
|
||||
|
||||
std::fill(output_dbdp, output_dbdp + n, 0.0);
|
||||
std::fill(output_dbdr, output_dbdr + n, 0.0);
|
||||
|
||||
}
|
||||
|
||||
virtual void rsSat(const int n,
|
||||
const int* pvtRegionIdx,
|
||||
const double* p,
|
||||
double* output_rsSat,
|
||||
double* output_drsSatdp) const
|
||||
{
|
||||
isothermalPvt_->rsSat(n, pvtRegionIdx, p, output_rsSat, output_drsSatdp);
|
||||
}
|
||||
|
||||
virtual void rvSat(const int n,
|
||||
const int* pvtRegionIdx,
|
||||
const double* p,
|
||||
double* output_rvSat,
|
||||
double* output_drvSatdp) const
|
||||
{
|
||||
isothermalPvt_->rvSat(n, pvtRegionIdx, p, output_rvSat, output_drvSatdp);
|
||||
}
|
||||
|
||||
virtual void R(const int n,
|
||||
const int* pvtRegionIdx,
|
||||
const double* p,
|
||||
const double* z,
|
||||
double* output_R) const
|
||||
{
|
||||
isothermalPvt_->R(n, pvtRegionIdx, p, z, output_R);
|
||||
}
|
||||
|
||||
virtual void dRdp(const int n,
|
||||
const int* pvtRegionIdx,
|
||||
const double* p,
|
||||
const double* z,
|
||||
double* output_R,
|
||||
double* output_dRdp) const
|
||||
{
|
||||
isothermalPvt_->dRdp(n, pvtRegionIdx, p, z, output_R, output_dRdp);
|
||||
}
|
||||
|
||||
private:
|
||||
int getTableIndex_(const int* pvtTableIdx, int cellIdx) const
|
||||
{
|
||||
if (!pvtTableIdx)
|
||||
return 0;
|
||||
return pvtTableIdx[cellIdx];
|
||||
}
|
||||
|
||||
// the PVT propertied for the isothermal case
|
||||
std::shared_ptr<const PvtInterface> isothermalPvt_;
|
||||
|
||||
// The PVT properties needed for temperature dependence. We need to store one
|
||||
// value per PVT region.
|
||||
std::vector<double> viscrefPress_;
|
||||
|
||||
std::vector<double> watdentRefTemp_;
|
||||
std::vector<double> watdentCT1_;
|
||||
std::vector<double> watdentCT2_;
|
||||
|
||||
std::vector<double> pvtwRefPress_;
|
||||
std::vector<double> pvtwRefB_;
|
||||
std::vector<double> pvtwCompressibility_;
|
||||
std::vector<double> pvtwViscosity_;
|
||||
std::vector<double> pvtwViscosibility_;
|
||||
|
||||
const TableContainer* watvisctTables_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
55
opm/core/props/rock/RockBasic.cpp
Normal file
55
opm/core/props/rock/RockBasic.cpp
Normal file
@ -0,0 +1,55 @@
|
||||
/*
|
||||
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/props/rock/RockBasic.hpp>
|
||||
|
||||
namespace Opm
|
||||
{
|
||||
|
||||
|
||||
/// Default constructor.
|
||||
RockBasic::RockBasic()
|
||||
: dimensions_(-1)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/// Initialize with homogenous porosity and permeability.
|
||||
void RockBasic::init(const int dimensions,
|
||||
const int num_cells,
|
||||
const double poro,
|
||||
const double perm)
|
||||
{
|
||||
dimensions_ = dimensions;
|
||||
porosity_.clear();
|
||||
porosity_.resize(num_cells, poro);
|
||||
permeability_.clear();
|
||||
const int dsq = dimensions*dimensions;
|
||||
permeability_.resize(num_cells*dsq, 0.0);
|
||||
// #pragma omp parallel for
|
||||
for (int i = 0; i < num_cells; ++i) {
|
||||
for (int d = 0; d < dimensions; ++d) {
|
||||
permeability_[dsq*i + dimensions*d + d] = perm;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // namespace Opm
|
79
opm/core/props/rock/RockBasic.hpp
Normal file
79
opm/core/props/rock/RockBasic.hpp
Normal file
@ -0,0 +1,79 @@
|
||||
/*
|
||||
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_ROCKBASIC_HEADER_INCLUDED
|
||||
#define OPM_ROCKBASIC_HEADER_INCLUDED
|
||||
|
||||
|
||||
#include <vector>
|
||||
|
||||
|
||||
namespace Opm
|
||||
{
|
||||
|
||||
class RockBasic
|
||||
{
|
||||
public:
|
||||
/// Default constructor.
|
||||
RockBasic();
|
||||
|
||||
/// Initialize with homogenous porosity and permeability.
|
||||
void init(const int dimensions,
|
||||
const int num_cells,
|
||||
const double poro,
|
||||
const double perm);
|
||||
|
||||
/// \return D, the number of spatial dimensions.
|
||||
int numDimensions() const
|
||||
{
|
||||
return dimensions_;
|
||||
}
|
||||
|
||||
/// \return N, the number of cells.
|
||||
int numCells() const
|
||||
{
|
||||
return porosity_.size();
|
||||
}
|
||||
|
||||
/// \return Array of N porosity values.
|
||||
const double* porosity() const
|
||||
{
|
||||
return &porosity_[0];
|
||||
}
|
||||
|
||||
/// \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
|
||||
{
|
||||
return &permeability_[0];
|
||||
}
|
||||
|
||||
private:
|
||||
int dimensions_;
|
||||
std::vector<double> porosity_;
|
||||
std::vector<double> permeability_;
|
||||
};
|
||||
|
||||
|
||||
|
||||
} // namespace Opm
|
||||
|
||||
|
||||
#endif // OPM_ROCKBASIC_HEADER_INCLUDED
|
142
opm/core/props/rock/RockCompressibility.cpp
Normal file
142
opm/core/props/rock/RockCompressibility.cpp
Normal file
@ -0,0 +1,142 @@
|
||||
/*
|
||||
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/props/rock/RockCompressibility.hpp>
|
||||
#include <opm/core/utility/parameters/ParameterGroup.hpp>
|
||||
#include <opm/parser/eclipse/Units/Units.hpp>
|
||||
#include <opm/common/ErrorMacros.hpp>
|
||||
#include <opm/common/OpmLog/OpmLog.hpp>
|
||||
#include <opm/core/utility/linearInterpolation.hpp>
|
||||
|
||||
#include <opm/parser/eclipse/EclipseState/Tables/RocktabTable.hpp>
|
||||
#include <opm/parser/eclipse/EclipseState/Tables/TableManager.hpp>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
namespace Opm
|
||||
{
|
||||
|
||||
RockCompressibility::RockCompressibility(const ParameterGroup& param)
|
||||
: pref_(0.0),
|
||||
rock_comp_(0.0)
|
||||
{
|
||||
pref_ = param.getDefault("rock_compressibility_pref", 100.0)*unit::barsa;
|
||||
rock_comp_ = param.getDefault("rock_compressibility", 0.0)/unit::barsa;
|
||||
}
|
||||
|
||||
RockCompressibility::RockCompressibility(const Opm::EclipseState& eclipseState,
|
||||
const bool is_io_rank)
|
||||
: pref_(0.0),
|
||||
rock_comp_(0.0)
|
||||
{
|
||||
const auto& tables = eclipseState.getTableManager();
|
||||
const auto& rocktabTables = tables.getRocktabTables();
|
||||
if (rocktabTables.size() > 0) {
|
||||
const auto& rocktabTable = rocktabTables.getTable<RocktabTable>(0);
|
||||
if (rocktabTables.size() != 1)
|
||||
OPM_THROW(std::runtime_error, "Can only handle a single region in ROCKTAB.");
|
||||
|
||||
p_ = rocktabTable.getColumn("PO").vectorCopy( );
|
||||
poromult_ = rocktabTable.getColumn("PV_MULT").vectorCopy();
|
||||
if (rocktabTable.hasColumn("PV_MULT_TRAN")) {
|
||||
transmult_ = rocktabTable.getColumn("PV_MULT_TRAN").vectorCopy();
|
||||
} else {
|
||||
transmult_ = rocktabTable.getColumn("PV_MULT_TRANX").vectorCopy();
|
||||
}
|
||||
} else if (!tables.getRockTable().empty()) {
|
||||
const auto& rockKeyword = tables.getRockTable();
|
||||
if (rockKeyword.size() != 1) {
|
||||
if (is_io_rank) {
|
||||
OpmLog::warning("Can only handle a single region in ROCK ("
|
||||
+ std::to_string(rockKeyword.size())
|
||||
+ " regions specified)."
|
||||
+ " Ignoring all except for the first.\n");
|
||||
}
|
||||
}
|
||||
|
||||
pref_ = rockKeyword[0].reference_pressure;
|
||||
rock_comp_ = rockKeyword[0].compressibility;
|
||||
} else {
|
||||
OpmLog::warning("No rock compressibility data found in deck (ROCK or ROCKTAB).");
|
||||
}
|
||||
}
|
||||
|
||||
bool RockCompressibility::isActive() const
|
||||
{
|
||||
return !p_.empty() || (rock_comp_ != 0.0);
|
||||
}
|
||||
|
||||
double RockCompressibility::poroMult(double pressure) const
|
||||
{
|
||||
if (p_.empty()) {
|
||||
// Approximating with a quadratic curve.
|
||||
const double cpnorm = rock_comp_*(pressure - pref_);
|
||||
return (1.0 + cpnorm + 0.5*cpnorm*cpnorm);
|
||||
} else {
|
||||
return Opm::linearInterpolation(p_, poromult_, pressure);
|
||||
}
|
||||
}
|
||||
|
||||
double RockCompressibility::poroMultDeriv(double pressure) const
|
||||
{
|
||||
if (p_.empty()) {
|
||||
// Approximating poro multiplier with a quadratic curve,
|
||||
// we must use its derivative.
|
||||
const double cpnorm = rock_comp_*(pressure - pref_);
|
||||
return rock_comp_ + cpnorm*rock_comp_;
|
||||
} else {
|
||||
return Opm::linearInterpolationDerivative(p_, poromult_, pressure);
|
||||
}
|
||||
}
|
||||
|
||||
double RockCompressibility::transMult(double pressure) const
|
||||
{
|
||||
if (p_.empty()) {
|
||||
return 1.0;
|
||||
} else {
|
||||
return Opm::linearInterpolation(p_, transmult_, pressure);
|
||||
}
|
||||
}
|
||||
|
||||
double RockCompressibility::transMultDeriv(double pressure) const
|
||||
{
|
||||
if (p_.empty()) {
|
||||
return 0.0;
|
||||
} else {
|
||||
return Opm::linearInterpolationDerivative(p_, transmult_, pressure);
|
||||
}
|
||||
}
|
||||
|
||||
double RockCompressibility::rockComp(double pressure) const
|
||||
{
|
||||
if (p_.empty()) {
|
||||
return rock_comp_;
|
||||
} else {
|
||||
//const double poromult = Opm::linearInterpolation(p_, poromult_, pressure);
|
||||
//const double dporomultdp = Opm::linearInterpolationDerivative(p_, poromult_, pressure);
|
||||
const double poromult = Opm::linearInterpolation(p_, poromult_, pressure);
|
||||
const double dporomultdp = Opm::linearInterpolationDerivative(p_, poromult_, pressure);
|
||||
|
||||
return dporomultdp/poromult;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Opm
|
||||
|
75
opm/core/props/rock/RockCompressibility.hpp
Normal file
75
opm/core/props/rock/RockCompressibility.hpp
Normal file
@ -0,0 +1,75 @@
|
||||
/*
|
||||
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_ROCKCOMPRESSIBILITY_HEADER_INCLUDED
|
||||
#define OPM_ROCKCOMPRESSIBILITY_HEADER_INCLUDED
|
||||
|
||||
#include <opm/parser/eclipse/EclipseState/EclipseState.hpp>
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace Opm
|
||||
{
|
||||
|
||||
class ParameterGroup;
|
||||
|
||||
class RockCompressibility
|
||||
{
|
||||
public:
|
||||
/// Construct from input deck.
|
||||
/// Looks for the keywords ROCK and ROCKTAB.
|
||||
RockCompressibility(const Opm::EclipseState& eclipseState,
|
||||
const bool is_io_rank = true);
|
||||
|
||||
/// Construct from parameters.
|
||||
/// Accepts the following parameters (with defaults).
|
||||
/// rock_compressibility_pref (100.0) [given in bar]
|
||||
/// rock_compressibility (0.0) [given in bar^{-1}]
|
||||
RockCompressibility(const ParameterGroup& param);
|
||||
|
||||
/// Returns true if there are compressibility effects.
|
||||
bool isActive() const;
|
||||
|
||||
/// Porosity multiplier.
|
||||
double poroMult(double pressure) const;
|
||||
|
||||
/// Derivative of porosity multiplier with respect to pressure.
|
||||
double poroMultDeriv(double pressure) const;
|
||||
|
||||
/// Transmissibility multiplier.
|
||||
double transMult(double pressure) const;
|
||||
|
||||
/// Derivative of transmissibility multiplier with respect to pressure.
|
||||
double transMultDeriv(double pressure) const;
|
||||
|
||||
/// Rock compressibility = (d poro / d p)*(1 / poro).
|
||||
double rockComp(double pressure) const;
|
||||
|
||||
private:
|
||||
std::vector<double> p_;
|
||||
std::vector<double> poromult_;
|
||||
std::vector<double> transmult_;
|
||||
double pref_;
|
||||
double rock_comp_;
|
||||
};
|
||||
|
||||
} // namespace Opm
|
||||
|
||||
|
||||
#endif // OPM_ROCKCOMPRESSIBILITY_HEADER_INCLUDED
|
363
opm/core/props/rock/RockFromDeck.cpp
Normal file
363
opm/core/props/rock/RockFromDeck.cpp
Normal file
@ -0,0 +1,363 @@
|
||||
/*
|
||||
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/props/rock/RockFromDeck.hpp>
|
||||
#include <opm/core/grid.h>
|
||||
#include <opm/common/ErrorMacros.hpp>
|
||||
|
||||
#include <opm/parser/eclipse/EclipseState/EclipseState.hpp>
|
||||
|
||||
#include <opm/core/utility/CompressedPropertyAccess.hpp>
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace Opm
|
||||
{
|
||||
|
||||
// Helper functions
|
||||
namespace
|
||||
{
|
||||
enum PermeabilityKind { ScalarPerm, DiagonalPerm, TensorPerm, None, Invalid };
|
||||
|
||||
void setScalarPermIfNeeded(std::array<int,9>& kmap,
|
||||
int i, int j, int k);
|
||||
|
||||
typedef GridPropertyAccess::ArrayPolicy::ExtractFromDeck<double> PermArray;
|
||||
|
||||
struct PermTag {};
|
||||
|
||||
typedef GridPropertyAccess::Compressed<PermArray, PermTag> PermComponent;
|
||||
|
||||
PermComponent
|
||||
extractPermComponent(const EclipseState& ecl,
|
||||
const std::string& kw,
|
||||
const int* global_cell);
|
||||
|
||||
PermeabilityKind
|
||||
fillTensor(const EclipseState& eclState,
|
||||
const int* global_cell,
|
||||
std::vector<PermComponent>& tensor,
|
||||
std::array<int,9>& kmap);
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
|
||||
|
||||
// ---- RockFromDeck methods ----
|
||||
|
||||
|
||||
/// Default constructor.
|
||||
RockFromDeck::RockFromDeck()
|
||||
{
|
||||
}
|
||||
|
||||
RockFromDeck::RockFromDeck(std::size_t number_of_cells)
|
||||
: porosity_(number_of_cells, 0),
|
||||
// full permeability tensor in 3D stores 9 scalars
|
||||
permeability_(number_of_cells*9, 0.0)
|
||||
{
|
||||
}
|
||||
|
||||
void RockFromDeck::init(const Opm::EclipseState& eclState,
|
||||
int number_of_cells, const int* global_cell,
|
||||
const int* cart_dims)
|
||||
{
|
||||
assignPorosity(eclState, number_of_cells, global_cell);
|
||||
const double perm_threshold = 0.0; // Maybe turn into parameter?
|
||||
extractInterleavedPermeability(eclState,
|
||||
number_of_cells,
|
||||
global_cell,
|
||||
cart_dims,
|
||||
perm_threshold,
|
||||
permeability_);
|
||||
}
|
||||
|
||||
void RockFromDeck::assignPorosity(const Opm::EclipseState& eclState,
|
||||
int number_of_cells, const int* global_cell)
|
||||
{
|
||||
typedef GridPropertyAccess::ArrayPolicy
|
||||
::ExtractFromDeck<double> Array;
|
||||
|
||||
Array poro_glob(eclState, "PORO", 1.0);
|
||||
GridPropertyAccess::Compressed<Array> poro(poro_glob, global_cell);
|
||||
|
||||
porosity_.clear(); porosity_.reserve(number_of_cells);
|
||||
for (int c = 0; c < number_of_cells; ++c) {
|
||||
porosity_.push_back(poro[c]);
|
||||
}
|
||||
}
|
||||
|
||||
void RockFromDeck::extractInterleavedPermeability(const Opm::EclipseState& eclState,
|
||||
const int number_of_cells,
|
||||
const int* global_cell,
|
||||
const int* cartdims,
|
||||
const double perm_threshold,
|
||||
std::vector<double>& permeability)
|
||||
{
|
||||
const int dim = 3;
|
||||
const int nc = number_of_cells;
|
||||
|
||||
assert(cartdims[0]*cartdims[1]*cartdims[2] > 0);
|
||||
static_cast<void>(cartdims); // Squash warning in release mode.
|
||||
|
||||
permeability.assign(dim * dim * nc, 0.0);
|
||||
|
||||
std::vector<PermComponent> tensor;
|
||||
tensor.reserve(6);
|
||||
|
||||
std::array<int,9> kmap;
|
||||
PermeabilityKind pkind = fillTensor(eclState, global_cell,
|
||||
tensor, kmap);
|
||||
if (pkind == Invalid) {
|
||||
OPM_THROW(std::runtime_error, "Invalid permeability field.");
|
||||
}
|
||||
|
||||
assert (! tensor.empty());
|
||||
{
|
||||
int off = 0;
|
||||
for (int c = 0; c < nc; ++c, off += dim*dim) {
|
||||
// SharedPermTensor K(dim, dim, &permeability_[off]);
|
||||
int kix = 0;
|
||||
|
||||
for (int i = 0; i < dim; ++i) {
|
||||
for (int j = 0; j < dim; ++j, ++kix) {
|
||||
// Clients expect column-major (Fortran) order
|
||||
// in "permeability_" so honour that
|
||||
// requirement despite "tensor" being created
|
||||
// row-major. Note: The actual numerical
|
||||
// values in the resulting array are the same
|
||||
// in either order when viewed contiguously
|
||||
// because fillTensor() enforces symmetry.
|
||||
permeability[off + (i + dim*j)] =
|
||||
tensor[kmap[kix]][c];
|
||||
}
|
||||
|
||||
// K(i,i) = std::max(K(i,i), perm_threshold);
|
||||
double& kii = permeability[off + i*(dim + 1)];
|
||||
kii = std::max(kii, perm_threshold);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
/// @brief
|
||||
/// Classify and verify a given permeability specification
|
||||
/// from a structural point of view. In particular, we
|
||||
/// verify that there are no off-diagonal permeability
|
||||
/// components such as @f$k_{xy}@f$ unless the
|
||||
/// corresponding diagonal components are known as well.
|
||||
///
|
||||
/// @param eclState [in]
|
||||
/// An internalized Eclipse deck from opm-parser which is
|
||||
/// capable of answering which permeability components are
|
||||
/// present in a given input deck.
|
||||
///
|
||||
/// @return
|
||||
/// An enum value with the following possible values:
|
||||
/// ScalarPerm only one component was given.
|
||||
/// DiagonalPerm more than one component given.
|
||||
/// TensorPerm at least one cross-component given.
|
||||
/// None no components given.
|
||||
/// Invalid invalid set of components given.
|
||||
PermeabilityKind classifyPermeability(const Opm::EclipseState& eclState)
|
||||
{
|
||||
auto& props = eclState.get3DProperties();
|
||||
const bool xx = props.hasDeckDoubleGridProperty("PERMX" );
|
||||
const bool xy = props.hasDeckDoubleGridProperty("PERMXY");
|
||||
const bool yx = xy;
|
||||
|
||||
const bool yy = props.hasDeckDoubleGridProperty("PERMY" );
|
||||
const bool yz = props.hasDeckDoubleGridProperty("PERMYZ");
|
||||
const bool zy = yz;
|
||||
|
||||
const bool zz = props.hasDeckDoubleGridProperty("PERMZ" );
|
||||
const bool zx = props.hasDeckDoubleGridProperty("PERMZX");
|
||||
const bool xz = zx;
|
||||
|
||||
int num_cross_comp = xy + xz + yx + yz + zx + zy;
|
||||
int num_comp = xx + yy + zz + num_cross_comp;
|
||||
PermeabilityKind retval = None;
|
||||
if (num_cross_comp > 0) {
|
||||
retval = TensorPerm;
|
||||
} else {
|
||||
if (num_comp == 1) {
|
||||
retval = ScalarPerm;
|
||||
} else if (num_comp >= 2) {
|
||||
retval = DiagonalPerm;
|
||||
}
|
||||
}
|
||||
|
||||
bool ok = true;
|
||||
if (num_comp > 0) {
|
||||
// At least one tensor component specified on input.
|
||||
// Verify that any remaining components are OK from a
|
||||
// structural point of view. In particular, there
|
||||
// must not be any cross-components (e.g., k_{xy})
|
||||
// unless the corresponding diagonal component (e.g.,
|
||||
// k_{xx}) is present as well...
|
||||
//
|
||||
ok = xx || !(xy || xz || yx || zx) ;
|
||||
ok = ok && (yy || !(yx || yz || xy || zy));
|
||||
ok = ok && (zz || !(zx || zy || xz || yz));
|
||||
}
|
||||
if (!ok) {
|
||||
retval = Invalid;
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
||||
/// @brief
|
||||
/// Copy isotropic (scalar) permeability to other diagonal
|
||||
/// components if the latter have not (yet) been assigned a
|
||||
/// separate value. Specifically, this function assigns
|
||||
/// copies of the @f$i@f$ permeability component (e.g.,
|
||||
/// 'PERMX') to the @f$j@f$ and @f$k@f$ permeability (e.g.,
|
||||
/// 'PERMY' and 'PERMZ') components if these have not
|
||||
/// previously been assigned.
|
||||
///
|
||||
/// @param kmap
|
||||
/// Permeability indirection map. In particular @code
|
||||
/// kmap[i] @endcode is the index (an integral number in
|
||||
/// the set [1..9]) into the permeability tensor
|
||||
/// representation of function @code fillTensor @endcode
|
||||
/// which represents permeability component @code i
|
||||
/// @endcode.
|
||||
///
|
||||
/// @param [in] i
|
||||
/// @param [in] j
|
||||
/// @param [in] k
|
||||
void setScalarPermIfNeeded(std::array<int,9>& kmap,
|
||||
int i, int j, int k)
|
||||
{
|
||||
if (kmap[j] < 0) { kmap[j] = kmap[i]; }
|
||||
if (kmap[k] < 0) { kmap[k] = kmap[i]; }
|
||||
}
|
||||
|
||||
/// @brief
|
||||
/// Extract pointers to appropriate tensor components from
|
||||
/// input deck. The permeability tensor is, generally,
|
||||
/// @code
|
||||
/// [ kxx kxy kxz ]
|
||||
/// K = [ kyx kyy kyz ]
|
||||
/// [ kzx kzy kzz ]
|
||||
/// @endcode
|
||||
/// We store these values in a linear array using natural
|
||||
/// ordering with the column index cycling the most rapidly.
|
||||
/// In particular we use the representation
|
||||
/// @code
|
||||
/// [ 0 1 2 3 4 5 6 7 8 ]
|
||||
/// K = [ kxx, kxy, kxz, kyx, kyy, kyz, kzx, kzy, kzz ]
|
||||
/// @endcode
|
||||
/// Moreover, we explicitly enforce symmetric tensors by
|
||||
/// assigning
|
||||
/// @code
|
||||
/// 3 1 6 2 7 5
|
||||
/// kyx = kxy, kzx = kxz, kzy = kyz
|
||||
/// @endcode
|
||||
/// However, we make no attempt at enforcing positive
|
||||
/// definite tensors.
|
||||
///
|
||||
/// @param [in] eclState
|
||||
/// An internalized Eclipse deck object which capable of
|
||||
/// answering which permeability components are present in
|
||||
/// a given input deck as well as retrieving the numerical
|
||||
/// value of each permeability component in each grid cell.
|
||||
///
|
||||
/// @param [out] tensor
|
||||
/// @param [out] kmap
|
||||
PermeabilityKind
|
||||
fillTensor(const EclipseState& eclState,
|
||||
const int* global_cell,
|
||||
std::vector<PermComponent>& tensor,
|
||||
std::array<int,9>& kmap)
|
||||
{
|
||||
PermeabilityKind kind = classifyPermeability(eclState);
|
||||
if (kind == Invalid) {
|
||||
OPM_THROW(std::runtime_error, "Invalid set of permeability fields given.");
|
||||
}
|
||||
|
||||
assert (tensor.empty());
|
||||
|
||||
for (int i = 0; i < 9; ++i) { kmap[i] = -1; }
|
||||
|
||||
enum { xx, xy, xz, // 0, 1, 2
|
||||
yx, yy, yz, // 3, 4, 5
|
||||
zx, zy, zz }; // 6, 7, 8
|
||||
|
||||
// -----------------------------------------------------------
|
||||
// 1st row: [ kxx, kxy ], kxz handled in kzx
|
||||
if (eclState.get3DProperties().hasDeckDoubleGridProperty("PERMX" )) {
|
||||
kmap[xx] = tensor.size();
|
||||
tensor.push_back(extractPermComponent(eclState, "PERMX", global_cell));
|
||||
|
||||
setScalarPermIfNeeded(kmap, xx, yy, zz);
|
||||
}
|
||||
{
|
||||
kmap[xy] = kmap[yx] = tensor.size(); // Enforce symmetry.
|
||||
tensor.push_back(extractPermComponent(eclState, "PERMXY", global_cell));
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------
|
||||
// 2nd row: [ kyy, kyz ], kyx handled in kxy
|
||||
if (eclState.get3DProperties().hasDeckDoubleGridProperty("PERMY" )) {
|
||||
kmap[yy] = tensor.size();
|
||||
tensor.push_back(extractPermComponent(eclState, "PERMY", global_cell));
|
||||
|
||||
setScalarPermIfNeeded(kmap, yy, zz, xx);
|
||||
}
|
||||
{
|
||||
kmap[yz] = kmap[zy] = tensor.size(); // Enforce symmetry.
|
||||
tensor.push_back(extractPermComponent(eclState, "PERMYZ", global_cell));
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------
|
||||
// 3rd row: [ kzx, kzz ], kzy handled in kyz
|
||||
{
|
||||
kmap[zx] = kmap[xz] = tensor.size(); // Enforce symmetry.
|
||||
tensor.push_back(extractPermComponent(eclState, "PERMZX", global_cell));
|
||||
}
|
||||
if (eclState.get3DProperties().hasDeckDoubleGridProperty("PERMZ" )) {
|
||||
kmap[zz] = tensor.size();
|
||||
tensor.push_back(extractPermComponent(eclState, "PERMZ", global_cell));
|
||||
|
||||
setScalarPermIfNeeded(kmap, zz, xx, yy);
|
||||
}
|
||||
|
||||
return kind;
|
||||
}
|
||||
|
||||
PermComponent
|
||||
extractPermComponent(const EclipseState& ecl,
|
||||
const std::string& kw,
|
||||
const int* global_cell)
|
||||
{
|
||||
PermArray k(ecl, kw, 0.0); // return 0.0 if not present.
|
||||
|
||||
return PermComponent(k, global_cell);
|
||||
}
|
||||
} // anonymous namespace
|
||||
|
||||
} // namespace Opm
|
112
opm/core/props/rock/RockFromDeck.hpp
Normal file
112
opm/core/props/rock/RockFromDeck.hpp
Normal file
@ -0,0 +1,112 @@
|
||||
/*
|
||||
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_ROCKFROMDECK_HEADER_INCLUDED
|
||||
#define OPM_ROCKFROMDECK_HEADER_INCLUDED
|
||||
|
||||
#include <opm/parser/eclipse/EclipseState/EclipseState.hpp>
|
||||
|
||||
#include <vector>
|
||||
|
||||
struct UnstructuredGrid;
|
||||
|
||||
namespace Opm
|
||||
{
|
||||
|
||||
class RockFromDeck
|
||||
{
|
||||
// BlackoilPropsDataHandle needs mutable
|
||||
// access to porosity and permeability
|
||||
friend class BlackoilPropsDataHandle;
|
||||
|
||||
public:
|
||||
/// Default constructor.
|
||||
RockFromDeck();
|
||||
/// Creates rock properties with zero porosity and permeability
|
||||
/// \param number_of_cells The number of cells
|
||||
explicit RockFromDeck(std::size_t number_of_cells);
|
||||
/// Initialize from deck and cell mapping.
|
||||
/// \param eclState The EclipseState (processed deck) produced by the opm-parser code
|
||||
/// \param number_of_cells The number of cells in the grid.
|
||||
/// \param global_cell The mapping fom local to global cell indices.
|
||||
/// global_cell[i] is the corresponding global index of i.
|
||||
/// \param cart_dims The size of the underlying cartesian grid.
|
||||
void init(const Opm::EclipseState& eclState,
|
||||
int number_of_cells, const int* global_cell,
|
||||
const int* cart_dims);
|
||||
|
||||
/// \return D, the number of spatial dimensions. Always 3 for deck input.
|
||||
int numDimensions() const
|
||||
{
|
||||
return 3;
|
||||
}
|
||||
|
||||
/// \return N, the number of cells.
|
||||
int numCells() const
|
||||
{
|
||||
return porosity_.size();
|
||||
}
|
||||
|
||||
/// \return Array of N porosity values.
|
||||
const double* porosity() const
|
||||
{
|
||||
return &porosity_[0];
|
||||
}
|
||||
|
||||
/// \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
|
||||
{
|
||||
return &permeability_[0];
|
||||
}
|
||||
|
||||
/// Convert the permeabilites for the logically Cartesian grid in EclipseState to
|
||||
/// an array of size number_of_cells*dim*dim for the compressed array.
|
||||
/// \param eclState The EclipseState (processed deck) produced by the opm-parser code
|
||||
/// \param number_of_cells The number of cells in the grid.
|
||||
/// \param global_cell The mapping fom local to global cell indices.
|
||||
/// global_cell[i] is the corresponding global index of i.
|
||||
/// \param cart_dims The size of the underlying cartesian grid.
|
||||
/// \param perm_threshold The threshold for permeability
|
||||
/// \param permeability The result array
|
||||
static
|
||||
void extractInterleavedPermeability(const Opm::EclipseState& eclState,
|
||||
const int number_of_cells,
|
||||
const int* global_cell,
|
||||
const int* cart_dims,
|
||||
const double perm_threshold,
|
||||
std::vector<double>& permeability);
|
||||
|
||||
private:
|
||||
void assignPorosity(const Opm::EclipseState& eclState,
|
||||
int number_of_cells,
|
||||
const int* global_cell);
|
||||
|
||||
std::vector<double> porosity_;
|
||||
std::vector<double> permeability_;
|
||||
std::vector<unsigned char> permfield_valid_;
|
||||
};
|
||||
|
||||
|
||||
|
||||
} // namespace Opm
|
||||
|
||||
|
||||
#endif // OPM_ROCKFROMDECK_HEADER_INCLUDED
|
686
opm/core/props/satfunc/RelpermDiagnostics.cpp
Normal file
686
opm/core/props/satfunc/RelpermDiagnostics.cpp
Normal file
@ -0,0 +1,686 @@
|
||||
/*
|
||||
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/>.
|
||||
*/
|
||||
|
||||
#include <opm/core/props/satfunc/RelpermDiagnostics.hpp>
|
||||
#include <opm/core/props/phaseUsageFromDeck.hpp>
|
||||
#include <opm/material/fluidmatrixinteractions/EclEpsScalingPoints.hpp>
|
||||
#include <opm/parser/eclipse/EclipseState/Tables/Sof2Table.hpp>
|
||||
#include <opm/parser/eclipse/EclipseState/Tables/SgwfnTable.hpp>
|
||||
|
||||
namespace Opm{
|
||||
|
||||
void RelpermDiagnostics::phaseCheck_(const EclipseState& es)
|
||||
{
|
||||
const auto& phases = es.runspec().phases();
|
||||
bool hasWater = phases.active( Phase::WATER );
|
||||
bool hasGas = phases.active( Phase::GAS );
|
||||
bool hasOil = phases.active( Phase::OIL );
|
||||
bool hasSolvent = phases.active( Phase::SOLVENT );
|
||||
|
||||
if (hasWater && hasGas && !hasOil && !hasSolvent) {
|
||||
const std::string msg = "System: Water-Gas system.";
|
||||
OpmLog::info(msg);
|
||||
fluidSystem_ = FluidSystem::WaterGas;
|
||||
}
|
||||
if (hasWater && hasOil && !hasGas && !hasSolvent) {
|
||||
const std::string msg = "System: Oil-Water system.";
|
||||
OpmLog::info(msg);
|
||||
fluidSystem_ = FluidSystem::OilWater;
|
||||
}
|
||||
if (hasOil && hasGas && !hasWater && !hasSolvent) {
|
||||
const std::string msg = "System: Oil-Gas system.";
|
||||
OpmLog::info(msg);
|
||||
fluidSystem_ = FluidSystem::OilGas;
|
||||
}
|
||||
if (hasOil && hasWater && hasGas && !hasSolvent) {
|
||||
const std::string msg = "System: Black-oil system.";
|
||||
OpmLog::info(msg);
|
||||
fluidSystem_ = FluidSystem::BlackOil;
|
||||
}
|
||||
if (hasSolvent) {
|
||||
const std::string msg = "System: Solvent model.";
|
||||
OpmLog::info(msg);
|
||||
fluidSystem_ = FluidSystem::Solvent;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void RelpermDiagnostics::satFamilyCheck_(const Opm::EclipseState& eclState)
|
||||
{
|
||||
const PhaseUsage pu = phaseUsageFromDeck(eclState);
|
||||
|
||||
const auto& tableManager = eclState.getTableManager();
|
||||
const TableContainer& swofTables = tableManager.getSwofTables();
|
||||
const TableContainer& slgofTables= tableManager.getSlgofTables();
|
||||
const TableContainer& sgofTables = tableManager.getSgofTables();
|
||||
const TableContainer& swfnTables = tableManager.getSwfnTables();
|
||||
const TableContainer& sgfnTables = tableManager.getSgfnTables();
|
||||
const TableContainer& sof3Tables = tableManager.getSof3Tables();
|
||||
const TableContainer& sof2Tables = tableManager.getSof2Tables();
|
||||
const TableContainer& sgwfnTables= tableManager.getSgwfnTables();
|
||||
|
||||
// Family I test.
|
||||
bool family1 = pu.phase_used[BlackoilPhases::Liquid];
|
||||
if (pu.phase_used[BlackoilPhases::Aqua]) {
|
||||
family1 = family1 && !swofTables.empty();
|
||||
}
|
||||
if (pu.phase_used[BlackoilPhases::Vapour]) {
|
||||
family1 = family1 && (!sgofTables.empty() || !slgofTables.empty());
|
||||
}
|
||||
|
||||
// Family II test.
|
||||
bool family2 = true;
|
||||
if (pu.phase_used[BlackoilPhases::Aqua]) {
|
||||
family2 = family2 && (!swfnTables.empty() || !sgwfnTables.empty());
|
||||
}
|
||||
if (pu.phase_used[BlackoilPhases::Liquid]) {
|
||||
family2 = family2 && (!sof3Tables.empty() || !sof2Tables.empty());
|
||||
}
|
||||
if (pu.phase_used[BlackoilPhases::Vapour]) {
|
||||
family2 = family2 && (!sgfnTables.empty() || !sgwfnTables.empty());
|
||||
}
|
||||
|
||||
if (family1 && family2) {
|
||||
const std::string msg = "Saturation families should not be mixed.\n Use either SGOF and SWOF or SGFN, SWFN and SOF3.";
|
||||
OpmLog::error(msg);
|
||||
}
|
||||
|
||||
if (!family1 && !family2) {
|
||||
const std::string msg = "Saturations function must be specified using either \n \
|
||||
family 1 or family 2 keywords \n \
|
||||
Use either SGOF and SWOF or SGFN, SWFN and SOF3.";
|
||||
OpmLog::error(msg);
|
||||
}
|
||||
|
||||
if (family1 && !family2) {
|
||||
satFamily_ = SaturationFunctionFamily::FamilyI;
|
||||
const std::string msg = "Relative permeability input format: Saturation Family I.";
|
||||
OpmLog::info(msg);
|
||||
}
|
||||
if (!family1 && family2) {
|
||||
satFamily_ = SaturationFunctionFamily::FamilyII;
|
||||
const std::string msg = "Relative permeability input format: Saturation Family II.";
|
||||
OpmLog::info(msg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void RelpermDiagnostics::tableCheck_(const EclipseState& eclState)
|
||||
{
|
||||
const int numSatRegions = eclState.runspec().tabdims().getNumSatTables();
|
||||
{
|
||||
const std::string msg = "Number of saturation regions: " + std::to_string(numSatRegions) + "\n";
|
||||
OpmLog::info(msg);
|
||||
}
|
||||
const auto& tableManager = eclState.getTableManager();
|
||||
const TableContainer& swofTables = tableManager.getSwofTables();
|
||||
const TableContainer& slgofTables = tableManager.getSlgofTables();
|
||||
const TableContainer& sgofTables = tableManager.getSgofTables();
|
||||
const TableContainer& swfnTables = tableManager.getSwfnTables();
|
||||
const TableContainer& sgfnTables = tableManager.getSgfnTables();
|
||||
const TableContainer& sof3Tables = tableManager.getSof3Tables();
|
||||
const TableContainer& sof2Tables = tableManager.getSof2Tables();
|
||||
const TableContainer& sgwfnTables = tableManager.getSgwfnTables();
|
||||
const TableContainer& sgcwmisTables = tableManager.getSgcwmisTables();
|
||||
const TableContainer& sorwmisTables = tableManager.getSorwmisTables();
|
||||
const TableContainer& ssfnTables = tableManager.getSsfnTables();
|
||||
const TableContainer& miscTables = tableManager.getMiscTables();
|
||||
const TableContainer& msfnTables = tableManager.getMsfnTables();
|
||||
|
||||
for (int satnumIdx = 0; satnumIdx < numSatRegions; ++satnumIdx) {
|
||||
if (tableManager.hasTables("SWOF")) {
|
||||
swofTableCheck_(swofTables.getTable<SwofTable>(satnumIdx), satnumIdx+1);
|
||||
}
|
||||
if (tableManager.hasTables("SGOF")) {
|
||||
sgofTableCheck_(sgofTables.getTable<SgofTable>(satnumIdx), satnumIdx+1);
|
||||
}
|
||||
if (tableManager.hasTables("SLGOF")) {
|
||||
slgofTableCheck_(slgofTables.getTable<SlgofTable>(satnumIdx), satnumIdx+1);
|
||||
}
|
||||
if (tableManager.hasTables("SWFN")) {
|
||||
swfnTableCheck_(swfnTables.getTable<SwfnTable>(satnumIdx), satnumIdx+1);
|
||||
}
|
||||
if (tableManager.hasTables("SGFN")) {
|
||||
sgfnTableCheck_(sgfnTables.getTable<SgfnTable>(satnumIdx), satnumIdx+1);
|
||||
}
|
||||
if (tableManager.hasTables("SOF3")) {
|
||||
sof3TableCheck_(sof3Tables.getTable<Sof3Table>(satnumIdx), satnumIdx+1);
|
||||
}
|
||||
if (tableManager.hasTables("SOF2")) {
|
||||
sof2TableCheck_(sof2Tables.getTable<Sof2Table>(satnumIdx), satnumIdx+1);
|
||||
}
|
||||
if (tableManager.hasTables("SGWFN")) {
|
||||
sgwfnTableCheck_(sgwfnTables.getTable<SgwfnTable>(satnumIdx), satnumIdx+1);
|
||||
}
|
||||
if (tableManager.hasTables("SGCWMIS")) {
|
||||
sgcwmisTableCheck_(sgcwmisTables.getTable<SgcwmisTable>(satnumIdx), satnumIdx+1);
|
||||
}
|
||||
if (tableManager.hasTables("SORWMIS")) {
|
||||
sorwmisTableCheck_(sorwmisTables.getTable<SorwmisTable>(satnumIdx), satnumIdx+1);
|
||||
}
|
||||
if (tableManager.hasTables("SSFN")) {
|
||||
ssfnTableCheck_(ssfnTables.getTable<SsfnTable>(satnumIdx), satnumIdx+1);
|
||||
}
|
||||
if (tableManager.hasTables("MSFN")) {
|
||||
msfnTableCheck_(msfnTables.getTable<MsfnTable>(satnumIdx), satnumIdx+1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (tableManager.hasTables("MISC")) {
|
||||
const int numMiscNumIdx = miscTables.size();
|
||||
const std::string msg = "Number of misc regions: " + std::to_string(numMiscNumIdx) + "\n";
|
||||
OpmLog::info(msg);
|
||||
for (int miscNumIdx = 0; miscNumIdx < numMiscNumIdx; ++miscNumIdx) {
|
||||
miscTableCheck_(miscTables.getTable<MiscTable>(miscNumIdx), miscNumIdx+1);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void RelpermDiagnostics::swofTableCheck_(const Opm::SwofTable& swofTables,
|
||||
const int satnumIdx)
|
||||
{
|
||||
const auto& sw = swofTables.getSwColumn();
|
||||
const auto& krw = swofTables.getKrwColumn();
|
||||
const auto& krow = swofTables.getKrowColumn();
|
||||
const std::string regionIdx = std::to_string(satnumIdx);
|
||||
//Check sw column.
|
||||
if (sw.front() < 0.0 || sw.back() > 1.0) {
|
||||
const std::string msg = "In SWOF table SATNUM = "+ regionIdx + ", saturation should be in range [0,1].";
|
||||
OpmLog::error(msg);
|
||||
}
|
||||
//TODO check endpoint sw.back() == 1. - Sor.
|
||||
//Check krw column.
|
||||
if (krw.front() != 0.0) {
|
||||
const std::string msg = "In SWOF table SATNUM = " + regionIdx + ", first value of krw should be 0.";
|
||||
OpmLog::error(msg);
|
||||
}
|
||||
if (krw.front() < 0.0 || krw.back() > 1.0) {
|
||||
const std::string msg = "In SWOF table SATNUM = " + regionIdx + ", krw should be in range [0,1].";
|
||||
OpmLog::error(msg);
|
||||
}
|
||||
|
||||
///Check krow column.
|
||||
if (krow.front() > 1.0 || krow.back() < 0.0) {
|
||||
const std::string msg = "In SWOF table SATNUM = "+ regionIdx + ", krow should be in range [0, 1].";
|
||||
OpmLog::error(msg);
|
||||
}
|
||||
///TODO check if run with gas.
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void RelpermDiagnostics::sgofTableCheck_(const Opm::SgofTable& sgofTables,
|
||||
const int satnumIdx)
|
||||
{
|
||||
const auto& sg = sgofTables.getSgColumn();
|
||||
const auto& krg = sgofTables.getKrgColumn();
|
||||
const auto& krog = sgofTables.getKrogColumn();
|
||||
const std::string regionIdx = std::to_string(satnumIdx);
|
||||
//Check sw column.
|
||||
if (sg.front() < 0.0 || sg.back() > 1.0) {
|
||||
const std::string msg = "In SGOF table SATNUM = " + regionIdx + ", saturation should be in range [0,1].";
|
||||
OpmLog::error(msg);
|
||||
}
|
||||
if (sg.front() != 0.0) {
|
||||
const std::string msg = "In SGOF table SATNUM = " + regionIdx + ", first value of sg should be 0.";
|
||||
OpmLog::error(msg);
|
||||
}
|
||||
//TODO check endpoint sw.back() == 1. - Sor.
|
||||
//Check krw column.
|
||||
if (krg.front() != 0.0) {
|
||||
const std::string msg = "In SGOF table SATNUM = " + regionIdx + ", first value of krg should be 0.";
|
||||
OpmLog::error(msg);
|
||||
}
|
||||
if (krg.front() < 0.0 || krg.back() > 1.0) {
|
||||
const std::string msg = "In SGOF table SATNUM = " + regionIdx + ", krg should be in range [0,1].";
|
||||
OpmLog::error(msg);
|
||||
}
|
||||
|
||||
//Check krow column.
|
||||
if (krog.front() > 1.0 || krog.back() < 0.0) {
|
||||
const std::string msg = "In SGOF table SATNUM = " + regionIdx + ", krog should be in range [0, 1].";
|
||||
OpmLog::error(msg);
|
||||
}
|
||||
//TODO check if run with water.
|
||||
}
|
||||
|
||||
void RelpermDiagnostics::slgofTableCheck_(const Opm::SlgofTable& slgofTables,
|
||||
const int satnumIdx)
|
||||
{
|
||||
const auto& sl = slgofTables.getSlColumn();
|
||||
const auto& krg = slgofTables.getKrgColumn();
|
||||
const auto& krog = slgofTables.getKrogColumn();
|
||||
const std::string regionIdx = std::to_string(satnumIdx);
|
||||
//Check sl column.
|
||||
//TODO first value means sl = swco + sor
|
||||
if (sl.front() < 0.0 || sl.back() > 1.0) {
|
||||
const std::string msg = "In SLGOF table SATNUM = " + regionIdx + ", saturation should be in range [0,1].";
|
||||
OpmLog::error(msg);
|
||||
}
|
||||
if (sl.back() != 1.0) {
|
||||
const std::string msg = "In SLGOF table SATNUM = " + regionIdx + ", last value of sl should be 1.";
|
||||
OpmLog::error(msg);
|
||||
}
|
||||
|
||||
if (krg.front() > 1.0 || krg.back() < 0) {
|
||||
const std::string msg = "In SLGOF table SATNUM = " + regionIdx + ", krg shoule be in range [0, 1].";
|
||||
OpmLog::error(msg);
|
||||
}
|
||||
if (krg.back() != 0.0) {
|
||||
const std::string msg = "In SLGOF table SATNUM = " + regionIdx + ", last value of krg hould be 0.";
|
||||
OpmLog::error(msg);
|
||||
}
|
||||
|
||||
if (krog.front() < 0.0 || krog.back() > 1.0) {
|
||||
const std::string msg = "In SLGOF table SATNUM = " + regionIdx + ", krog shoule be in range [0, 1].";
|
||||
OpmLog::error(msg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void RelpermDiagnostics::swfnTableCheck_(const Opm::SwfnTable& swfnTables,
|
||||
const int satnumIdx)
|
||||
{
|
||||
const auto& sw = swfnTables.getSwColumn();
|
||||
const auto& krw = swfnTables.getKrwColumn();
|
||||
const std::string regionIdx = std::to_string(satnumIdx);
|
||||
//Check sw column.
|
||||
if (sw.front() < 0.0 || sw.back() > 1.0) {
|
||||
const std::string msg = "In SWFN table SATNUM = " + regionIdx + ", saturation should be in range [0,1].";
|
||||
OpmLog::error(msg);
|
||||
}
|
||||
|
||||
//Check krw column.
|
||||
if (krw.front() < 0.0 || krw.back() > 1.0) {
|
||||
const std::string msg = "In SWFN table SATNUM = " + regionIdx + ", krw should be in range [0,1].";
|
||||
OpmLog::error(msg);
|
||||
}
|
||||
|
||||
if (krw.front() != 0.0) {
|
||||
const std::string msg = "In SWFN table SATNUM = " + regionIdx + ", first value of krw should be 0.";
|
||||
OpmLog::error(msg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void RelpermDiagnostics::sgfnTableCheck_(const Opm::SgfnTable& sgfnTables,
|
||||
const int satnumIdx)
|
||||
{
|
||||
const auto& sg = sgfnTables.getSgColumn();
|
||||
const auto& krg = sgfnTables.getKrgColumn();
|
||||
const std::string regionIdx = std::to_string(satnumIdx);
|
||||
//Check sg column.
|
||||
if (sg.front() < 0.0 || sg.back() > 1.0) {
|
||||
const std::string msg = "In SGFN table SATNUM = " + regionIdx + ", saturation should be in range [0,1].";
|
||||
OpmLog::error(msg);
|
||||
}
|
||||
|
||||
//Check krg column.
|
||||
if (krg.front() < 0.0 || krg.back() > 1.0) {
|
||||
const std::string msg = "In SGFN table SATNUM = " + regionIdx + ", krg should be in range [0,1].";
|
||||
OpmLog::error(msg);
|
||||
}
|
||||
if (krg.front() != 0.0) {
|
||||
const std::string msg = "In SGFN table SATNUM = " + regionIdx + ", first value of krg should be 0.";
|
||||
OpmLog::error(msg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void RelpermDiagnostics::sof3TableCheck_(const Opm::Sof3Table& sof3Tables,
|
||||
const int satnumIdx)
|
||||
{
|
||||
const auto& so = sof3Tables.getSoColumn();
|
||||
const auto& krow = sof3Tables.getKrowColumn();
|
||||
const auto& krog = sof3Tables.getKrogColumn();
|
||||
const std::string regionIdx = std::to_string(satnumIdx);
|
||||
//Check so column.
|
||||
//TODO: The max so = 1 - Swco
|
||||
if (so.front() < 0.0 || so.back() > 1.0) {
|
||||
const std::string msg = "In SOF3 table SATNUM = " + regionIdx + ", saturation should be in range [0,1].";
|
||||
OpmLog::error(msg);
|
||||
}
|
||||
|
||||
//Check krow column.
|
||||
if (krow.front() < 0.0 || krow.back() > 1.0) {
|
||||
const std::string msg = "In SOF3 table SATNUM = " + regionIdx + ", krow should be in range [0,1].";
|
||||
OpmLog::error(msg);
|
||||
}
|
||||
if (krow.front() != 0.0) {
|
||||
const std::string msg = "In SOF3 table SATNUM = " + regionIdx + ", first value of krow should be 0.";
|
||||
OpmLog::error(msg);
|
||||
}
|
||||
|
||||
//Check krog column.
|
||||
if (krog.front() < 0.0 || krog.back() > 1.0) {
|
||||
const std::string msg = "In SOF3 table SATNUM = " + regionIdx + ", krog should be in range [0,1].";
|
||||
OpmLog::error(msg);
|
||||
}
|
||||
|
||||
if (krog.front() != 0.0) {
|
||||
const std::string msg = "In SOF3 table SATNUM = " + regionIdx + ", first value of krog should be 0.";
|
||||
OpmLog::error(msg);
|
||||
}
|
||||
|
||||
if (krog.back() != krow.back()) {
|
||||
const std::string msg = "In SOF3 table SATNUM = " + regionIdx + ", max value of krog and krow should be the same.";
|
||||
OpmLog::error(msg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void RelpermDiagnostics::sof2TableCheck_(const Opm::Sof2Table& sof2Tables,
|
||||
const int satnumIdx)
|
||||
{
|
||||
const auto& so = sof2Tables.getSoColumn();
|
||||
const auto& kro = sof2Tables.getKroColumn();
|
||||
const std::string regionIdx = std::to_string(satnumIdx);
|
||||
//Check so column.
|
||||
//TODO: The max so = 1 - Swco
|
||||
if (so.front() < 0.0 || so.back() > 1.0) {
|
||||
const std::string msg = "In SOF2 table SATNUM = " + regionIdx + ", saturation should be in range [0,1].";
|
||||
OpmLog::error(msg);
|
||||
}
|
||||
|
||||
//Check krow column.
|
||||
if (kro.front() < 0.0 || kro.back() > 1.0) {
|
||||
const std::string msg = "In SOF2 table SATNUM = " + regionIdx + ", krow should be in range [0,1].";
|
||||
OpmLog::error(msg);
|
||||
}
|
||||
if (kro.front() != 0.0) {
|
||||
const std::string msg = "In SOF2 table SATNUM = " + regionIdx + ", first value of krow should be 0.";
|
||||
OpmLog::error(msg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void RelpermDiagnostics::sgwfnTableCheck_(const Opm::SgwfnTable& sgwfnTables,
|
||||
const int satnumIdx)
|
||||
{
|
||||
const auto& sg = sgwfnTables.getSgColumn();
|
||||
const auto& krg = sgwfnTables.getKrgColumn();
|
||||
const auto& krgw = sgwfnTables.getKrgwColumn();
|
||||
const std::string regionIdx = std::to_string(satnumIdx);
|
||||
//Check sg column.
|
||||
if (sg.front() < 0.0 || sg.back() > 1.0) {
|
||||
const std::string msg = "In SGWFN table SATNUM = " + regionIdx + ", saturation should be in range [0,1].";
|
||||
OpmLog::error(msg);
|
||||
}
|
||||
|
||||
//Check krg column.
|
||||
if (krg.front() < 0.0 || krg.back() > 1.0) {
|
||||
const std::string msg = "In SGWFN table SATNUM = " + regionIdx + ", krg should be in range [0,1].";
|
||||
OpmLog::error(msg);
|
||||
}
|
||||
if (krg.front() != 0.0) {
|
||||
const std::string msg = "In SGWFN table SATNUM = " + regionIdx + ", first value of krg should be 0.";
|
||||
OpmLog::error(msg);
|
||||
}
|
||||
|
||||
//Check krgw column.
|
||||
//TODO check saturation sw = 1. - sg
|
||||
if (krgw.front() > 1.0 || krgw.back() < 0.0) {
|
||||
const std::string msg = "In SGWFN table SATNUM = " + regionIdx + ", krgw should be in range [0,1].";
|
||||
OpmLog::error(msg);
|
||||
}
|
||||
if (krgw.back() != 0.0) {
|
||||
const std::string msg = "In SGWFN table SATNUM = " + regionIdx + ", last value of krgw should be 0.";
|
||||
OpmLog::error(msg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void RelpermDiagnostics::sgcwmisTableCheck_(const Opm::SgcwmisTable& sgcwmisTables,
|
||||
const int satnumIdx)
|
||||
{
|
||||
const auto& sw = sgcwmisTables.getWaterSaturationColumn();
|
||||
const auto& sgc = sgcwmisTables.getMiscibleResidualGasColumn();
|
||||
const std::string regionIdx = std::to_string(satnumIdx);
|
||||
//Check sw column.
|
||||
if (sw.front() < 0.0 || sw.back() > 1.0) {
|
||||
const std::string msg = "In SGCWMIS table SATNUM = " + regionIdx + ", saturation should be in range [0,1].";
|
||||
OpmLog::error(msg);
|
||||
}
|
||||
|
||||
//Check critical gas column.
|
||||
if (sgc.front() < 0.0 || sgc.back() > 1.0) {
|
||||
const std::string msg = "In SGCWMIS table SATNUM = " + regionIdx + ", critical gas saturation should be in range [0,1].";
|
||||
OpmLog::error(msg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void RelpermDiagnostics::sorwmisTableCheck_(const Opm::SorwmisTable& sorwmisTables,
|
||||
const int satnumIdx)
|
||||
{
|
||||
const auto& sw = sorwmisTables.getWaterSaturationColumn();
|
||||
const auto& sor = sorwmisTables.getMiscibleResidualOilColumn();
|
||||
const std::string regionIdx = std::to_string(satnumIdx);
|
||||
//Check sw column.
|
||||
if (sw.front() < 0.0 || sw.back() > 1.0) {
|
||||
const std::string msg = "In SORWMIS table SATNUM = " + regionIdx + ", saturation should be in range [0,1].";
|
||||
OpmLog::error(msg);
|
||||
}
|
||||
|
||||
//Check critical oil column.
|
||||
if (sor.front() < 0.0 || sor.back() > 1.0) {
|
||||
const std::string msg = "In SORWMIS table SATNUM = " + regionIdx + ", critical oil saturation should be in range [0,1].";
|
||||
OpmLog::error(msg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void RelpermDiagnostics::ssfnTableCheck_(const Opm::SsfnTable& ssfnTables,
|
||||
const int satnumIdx)
|
||||
{
|
||||
const auto& frac = ssfnTables.getSolventFractionColumn();
|
||||
const auto& krgm = ssfnTables.getGasRelPermMultiplierColumn();
|
||||
const auto& krsm = ssfnTables.getSolventRelPermMultiplierColumn();
|
||||
const std::string regionIdx = std::to_string(satnumIdx);
|
||||
//Check phase fraction column.
|
||||
if (frac.front() < 0.0 || frac.back() > 1.0) {
|
||||
const std::string msg = "In SSFN table SATNUM = " + regionIdx + ", phase fraction should be in range [0,1].";
|
||||
OpmLog::error(msg);
|
||||
}
|
||||
|
||||
//Check gas relperm multiplier column.
|
||||
if (krgm.front() < 0.0 || krgm.back() > 1.0) {
|
||||
const std::string msg = "In SSFN table SATNUM = " + regionIdx + ", gas relative permeability multiplier should be in range [0,1].";
|
||||
OpmLog::error(msg);
|
||||
}
|
||||
|
||||
//Check solvent relperm multiplier column.
|
||||
if (krsm.front() < 0.0 || krsm.back() > 1.0) {
|
||||
const std::string msg = "In SSFN table SATNUM = " + regionIdx + ", solvent relative permeability multiplier should be in range [0,1].";
|
||||
OpmLog::error(msg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void RelpermDiagnostics::miscTableCheck_(const Opm::MiscTable& miscTables,
|
||||
const int miscnumIdx)
|
||||
{
|
||||
const auto& frac = miscTables.getSolventFractionColumn();
|
||||
const auto& misc = miscTables.getMiscibilityColumn();
|
||||
|
||||
const std::string regionIdx = std::to_string(miscnumIdx);
|
||||
//Check phase fraction column.
|
||||
if (frac.front() < 0.0 || frac.back() > 1.0) {
|
||||
const std::string msg = "In MISC table MISCNUM = " + regionIdx + ", phase fraction should be in range [0,1].";
|
||||
OpmLog::error(msg);
|
||||
}
|
||||
|
||||
//Check miscibility column.
|
||||
if (misc.front() < 0.0 || misc.back() > 1.0) {
|
||||
const std::string msg = "In MISC table MISCNUM = " + regionIdx + ", miscibility should be in range [0,1].";
|
||||
OpmLog::error(msg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void RelpermDiagnostics::msfnTableCheck_(const Opm::MsfnTable& msfnTables,
|
||||
const int satnumIdx)
|
||||
{
|
||||
const auto& frac = msfnTables.getGasPhaseFractionColumn();
|
||||
const auto& krgsm = msfnTables.getGasSolventRelpermMultiplierColumn();
|
||||
const auto& krom = msfnTables.getOilRelpermMultiplierColumn();
|
||||
|
||||
const std::string regionIdx = std::to_string(satnumIdx);
|
||||
//Check phase fraction column.
|
||||
if (frac.front() < 0.0 || frac.back() > 1.0) {
|
||||
const std::string msg = "In MSFN table SATNUM = " + regionIdx + ", total gas fraction should be in range [0,1].";
|
||||
OpmLog::error(msg);
|
||||
}
|
||||
|
||||
//Check gas_solvent relperm multiplier column.
|
||||
if (krgsm.front() < 0.0 || krgsm.back() > 1.0) {
|
||||
const std::string msg = "In MSFN table SATNUM = " + regionIdx + ", gas+solvent relative permeability multiplier should be in range [0,1].";
|
||||
OpmLog::error(msg);
|
||||
}
|
||||
|
||||
//Check oil relperm multiplier column.
|
||||
if (krom.front() > 1.0 || krom.back() < 0.0) {
|
||||
const std::string msg = "In MSFN table SATNUM = " + regionIdx + ", oil relative permeability multiplier should be in range [0,1].";
|
||||
OpmLog::error(msg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void RelpermDiagnostics::unscaledEndPointsCheck_(const Deck& deck,
|
||||
const EclipseState& eclState)
|
||||
{
|
||||
// get the number of saturation regions and the number of cells in the deck
|
||||
const int numSatRegions = eclState.runspec().tabdims().getNumSatTables();
|
||||
unscaledEpsInfo_.resize(numSatRegions);
|
||||
|
||||
const auto& tables = eclState.getTableManager();
|
||||
const TableContainer& swofTables = tables.getSwofTables();
|
||||
const TableContainer& sgofTables = tables.getSgofTables();
|
||||
const TableContainer& slgofTables = tables.getSlgofTables();
|
||||
const TableContainer& sof3Tables = tables.getSof3Tables();
|
||||
|
||||
// std::cout << "***************\nEnd-Points In all the Tables\n";
|
||||
for (int satnumIdx = 0; satnumIdx < numSatRegions; ++satnumIdx) {
|
||||
unscaledEpsInfo_[satnumIdx].extractUnscaled(deck, eclState, satnumIdx);
|
||||
const std::string regionIdx = std::to_string(satnumIdx + 1);
|
||||
///Consistency check.
|
||||
if (unscaledEpsInfo_[satnumIdx].Sgu > (1. - unscaledEpsInfo_[satnumIdx].Swl)) {
|
||||
const std::string msg = "In saturation table SATNUM = " + regionIdx + ", Sgmax should not exceed 1-Swco.";
|
||||
OpmLog::warning(msg);
|
||||
}
|
||||
if (unscaledEpsInfo_[satnumIdx].Sgl > (1. - unscaledEpsInfo_[satnumIdx].Swu)) {
|
||||
const std::string msg = "In saturation table SATNUM = " + regionIdx + ", Sgco should not exceed 1-Swmax.";
|
||||
OpmLog::warning(msg);
|
||||
}
|
||||
|
||||
//Krow(Sou) == Krog(Sou) for three-phase
|
||||
// means Krow(Swco) == Krog(Sgco)
|
||||
double krow_value = 1e20;
|
||||
double krog_value = 1e-20;
|
||||
if (fluidSystem_ == FluidSystem::BlackOil) {
|
||||
if (satFamily_ == SaturationFunctionFamily::FamilyI) {
|
||||
if (!sgofTables.empty()) {
|
||||
const auto& table = sgofTables.getTable<SgofTable>(satnumIdx);
|
||||
krog_value = table.evaluate( "KROG" , unscaledEpsInfo_[satnumIdx].Sgl );
|
||||
} else {
|
||||
assert(!slgofTables.empty());
|
||||
const auto& table = slgofTables.getTable<SlgofTable>(satnumIdx);
|
||||
krog_value = table.evaluate( "KROG" , unscaledEpsInfo_[satnumIdx].Sgl );
|
||||
}
|
||||
{
|
||||
const auto& table = swofTables.getTable<SwofTable>(satnumIdx);
|
||||
krow_value = table.evaluate("KROW" , unscaledEpsInfo_[satnumIdx].Swl);
|
||||
}
|
||||
}
|
||||
if (satFamily_ == SaturationFunctionFamily::FamilyII) {
|
||||
assert(!sof3Tables.empty());
|
||||
const auto& table = sof3Tables.getTable<Sof3Table>(satnumIdx);
|
||||
const double Sou = 1.- unscaledEpsInfo_[satnumIdx].Swl - unscaledEpsInfo_[satnumIdx].Sgl;
|
||||
|
||||
krow_value = table.evaluate("KROW" , Sou);
|
||||
krog_value = table.evaluate("KROG" , Sou);
|
||||
}
|
||||
if (krow_value != krog_value) {
|
||||
const std::string msg = "In saturation table SATNUM = " + regionIdx + ", Krow(Somax) should be equal to Krog(Somax).";
|
||||
OpmLog::warning(msg);
|
||||
}
|
||||
}
|
||||
//Krw(Sw=0)=Krg(Sg=0)=Krow(So=0)=Krog(So=0)=0.
|
||||
//Mobile fluid requirements
|
||||
if (((unscaledEpsInfo_[satnumIdx].Sowcr + unscaledEpsInfo_[satnumIdx].Swcr)-1) >= 0) {
|
||||
const std::string msg = "In saturation table SATNUM = " + regionIdx + ", Sowcr + Swcr should be less than 1.";
|
||||
OpmLog::warning(msg);
|
||||
}
|
||||
if (((unscaledEpsInfo_[satnumIdx].Sogcr + unscaledEpsInfo_[satnumIdx].Sgcr + unscaledEpsInfo_[satnumIdx].Swl) - 1 ) > 0) {
|
||||
const std::string msg = "In saturation table SATNUM = " + regionIdx + ", Sogcr + Sgcr + Swco should be less than 1.";
|
||||
OpmLog::warning(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
} //namespace Opm
|
140
opm/core/props/satfunc/RelpermDiagnostics.hpp
Normal file
140
opm/core/props/satfunc/RelpermDiagnostics.hpp
Normal file
@ -0,0 +1,140 @@
|
||||
/*
|
||||
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_RELPERMDIAGNOSTICS_HEADER_INCLUDED
|
||||
#define OPM_RELPERMDIAGNOSTICS_HEADER_INCLUDED
|
||||
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
|
||||
#if HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif // HAVE_CONFIG_H
|
||||
|
||||
#include <opm/core/grid.h>
|
||||
#include <opm/core/grid/GridManager.hpp>
|
||||
#include <opm/core/grid/GridHelpers.hpp>
|
||||
#include <opm/core/utility/linearInterpolation.hpp>
|
||||
#include <opm/parser/eclipse/EclipseState/EclipseState.hpp>
|
||||
#include <opm/parser/eclipse/Deck/Deck.hpp>
|
||||
#include <opm/common/OpmLog/OpmLog.hpp>
|
||||
#include <opm/parser/eclipse/EclipseState/Tables/SsfnTable.hpp>
|
||||
#include <opm/parser/eclipse/EclipseState/Tables/MiscTable.hpp>
|
||||
#include <opm/parser/eclipse/EclipseState/Tables/MsfnTable.hpp>
|
||||
#include <opm/parser/eclipse/EclipseState/Tables/SgcwmisTable.hpp>
|
||||
#include <opm/parser/eclipse/EclipseState/Tables/SorwmisTable.hpp>
|
||||
#include <opm/material/fluidmatrixinteractions/EclEpsScalingPoints.hpp>
|
||||
|
||||
namespace Opm {
|
||||
|
||||
class Sof2Table;
|
||||
class SgwfnTable;
|
||||
|
||||
///This class is intend to be a relpmer diganostics, to detect
|
||||
///wrong input of relperm table and endpoints.
|
||||
class RelpermDiagnostics
|
||||
{
|
||||
public:
|
||||
///This function is used to diagnosis relperm in
|
||||
///eclipse data file. Errors and warings will be
|
||||
///output if they're found.
|
||||
///\param[in] eclState eclipse state.
|
||||
///\param[in] deck ecliplise data file.
|
||||
///\param[in] grid unstructured grid.
|
||||
template <class GridT>
|
||||
void diagnosis(const EclipseState& eclState,
|
||||
const Deck& deck,
|
||||
const GridT& grid);
|
||||
|
||||
private:
|
||||
enum FluidSystem {
|
||||
OilWater,
|
||||
OilGas,
|
||||
WaterGas,
|
||||
BlackOil,
|
||||
Solvent
|
||||
};
|
||||
|
||||
FluidSystem fluidSystem_;
|
||||
|
||||
enum SaturationFunctionFamily {
|
||||
FamilyI,
|
||||
FamilyII,
|
||||
NoFamily
|
||||
};
|
||||
|
||||
SaturationFunctionFamily satFamily_;
|
||||
|
||||
std::vector<Opm::EclEpsScalingPointsInfo<double> > unscaledEpsInfo_;
|
||||
std::vector<Opm::EclEpsScalingPointsInfo<double> > scaledEpsInfo_;
|
||||
|
||||
|
||||
///Check the phase that used.
|
||||
void phaseCheck_(const EclipseState& es);
|
||||
|
||||
///Check saturation family I and II.
|
||||
void satFamilyCheck_(const EclipseState& eclState);
|
||||
|
||||
///Check saturation tables.
|
||||
void tableCheck_(const EclipseState& eclState);
|
||||
|
||||
///Check endpoints in the saturation tables.
|
||||
void unscaledEndPointsCheck_(const Deck& deck,
|
||||
const EclipseState& eclState);
|
||||
|
||||
template <class GridT>
|
||||
void scaledEndPointsCheck_(const Deck& deck,
|
||||
const EclipseState& eclState,
|
||||
const GridT& grid);
|
||||
|
||||
///For every table, need to deal with case by case.
|
||||
void swofTableCheck_(const Opm::SwofTable& swofTables,
|
||||
const int satnumIdx);
|
||||
void sgofTableCheck_(const Opm::SgofTable& sgofTables,
|
||||
const int satnumIdx);
|
||||
void slgofTableCheck_(const Opm::SlgofTable& slgofTables,
|
||||
const int satnumIdx);
|
||||
void swfnTableCheck_(const Opm::SwfnTable& swfnTables,
|
||||
const int satnumIdx);
|
||||
void sgfnTableCheck_(const Opm::SgfnTable& sgfnTables,
|
||||
const int satnumIdx);
|
||||
void sof3TableCheck_(const Opm::Sof3Table& sof3Tables,
|
||||
const int satnumIdx);
|
||||
void sof2TableCheck_(const Opm::Sof2Table& sof2Tables,
|
||||
const int satnumIdx);
|
||||
void sgwfnTableCheck_(const Opm::SgwfnTable& sgwfnTables,
|
||||
const int satnumIdx);
|
||||
///Tables for solvent model
|
||||
void sgcwmisTableCheck_(const Opm::SgcwmisTable& sgcwmisTables,
|
||||
const int satnumIdx);
|
||||
void sorwmisTableCheck_(const Opm::SorwmisTable& sorwmisTables,
|
||||
const int satnumIdx);
|
||||
void ssfnTableCheck_(const Opm::SsfnTable& ssfnTables,
|
||||
const int satnumIdx);
|
||||
void miscTableCheck_(const Opm::MiscTable& miscTables,
|
||||
const int miscnumIdx);
|
||||
void msfnTableCheck_(const Opm::MsfnTable& msfnTables,
|
||||
const int satnumIdx);
|
||||
};
|
||||
|
||||
} //namespace Opm
|
||||
|
||||
#include <opm/core/props/satfunc/RelpermDiagnostics_impl.hpp>
|
||||
|
||||
#endif // OPM_RELPERMDIAGNOSTICS_HEADER_INCLUDED
|
102
opm/core/props/satfunc/RelpermDiagnostics_impl.hpp
Normal file
102
opm/core/props/satfunc/RelpermDiagnostics_impl.hpp
Normal file
@ -0,0 +1,102 @@
|
||||
/*
|
||||
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_RELPERMDIAGNOSTICS_IMPL_HEADER_INCLUDED
|
||||
#define OPM_RELPERMDIAGNOSTICS_IMPL_HEADER_INCLUDED
|
||||
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
|
||||
#include <opm/core/props/satfunc/RelpermDiagnostics.hpp>
|
||||
#include <opm/core/utility/compressedToCartesian.hpp>
|
||||
|
||||
namespace Opm {
|
||||
|
||||
template <class GridT>
|
||||
void RelpermDiagnostics::diagnosis(const Opm::EclipseState& eclState,
|
||||
const Opm::Deck& deck,
|
||||
const GridT& grid)
|
||||
{
|
||||
OpmLog::info("\n===============Saturation Functions Diagnostics===============\n");
|
||||
phaseCheck_(eclState);
|
||||
satFamilyCheck_(eclState);
|
||||
tableCheck_(eclState);
|
||||
unscaledEndPointsCheck_(deck, eclState);
|
||||
scaledEndPointsCheck_(deck, eclState, grid);
|
||||
}
|
||||
|
||||
template <class GridT>
|
||||
void RelpermDiagnostics::scaledEndPointsCheck_(const Deck& deck,
|
||||
const EclipseState& eclState,
|
||||
const GridT& grid)
|
||||
{
|
||||
// All end points are subject to round-off errors, checks should account for it
|
||||
const float tolerance = 1e-6;
|
||||
const int nc = Opm::UgGridHelpers::numCells(grid);
|
||||
const auto& global_cell = Opm::UgGridHelpers::globalCell(grid);
|
||||
const auto dims = Opm::UgGridHelpers::cartDims(grid);
|
||||
const auto& compressedToCartesianIdx = Opm::compressedToCartesian(nc, global_cell);
|
||||
scaledEpsInfo_.resize(nc);
|
||||
EclEpsGridProperties epsGridProperties;
|
||||
epsGridProperties.initFromDeck(deck, eclState, /*imbibition=*/false);
|
||||
const auto& satnum = eclState.get3DProperties().getIntGridProperty("SATNUM");
|
||||
|
||||
const std::string tag = "Scaled endpoints";
|
||||
for (int c = 0; c < nc; ++c) {
|
||||
const int cartIdx = compressedToCartesianIdx[c];
|
||||
const std::string satnumIdx = std::to_string(satnum.iget(cartIdx));
|
||||
std::array<int, 3> ijk;
|
||||
ijk[0] = cartIdx % dims[0];
|
||||
ijk[1] = (cartIdx / dims[0]) % dims[1];
|
||||
ijk[2] = cartIdx / dims[0] / dims[1];
|
||||
const std::string cellIdx = "(" + std::to_string(ijk[0]) + ", " +
|
||||
std::to_string(ijk[1]) + ", " +
|
||||
std::to_string(ijk[2]) + ")";
|
||||
scaledEpsInfo_[c].extractScaled(eclState, epsGridProperties, cartIdx);
|
||||
|
||||
// SGU <= 1.0 - SWL
|
||||
if (scaledEpsInfo_[c].Sgu > (1.0 - scaledEpsInfo_[c].Swl + tolerance)) {
|
||||
const std::string msg = "For scaled endpoints input, cell" + cellIdx + " SATNUM = " + satnumIdx + ", SGU exceed 1.0 - SWL";
|
||||
OpmLog::warning(tag, msg);
|
||||
}
|
||||
|
||||
// SGL <= 1.0 - SWU
|
||||
if (scaledEpsInfo_[c].Sgl > (1.0 - scaledEpsInfo_[c].Swu + tolerance)) {
|
||||
const std::string msg = "For scaled endpoints input, cell" + cellIdx + " SATNUM = " + satnumIdx + ", SGL exceed 1.0 - SWU";
|
||||
OpmLog::warning(tag, msg);
|
||||
}
|
||||
|
||||
if (deck.hasKeyword("SCALECRS") && fluidSystem_ == FluidSystem::BlackOil) {
|
||||
// Mobilility check.
|
||||
if ((scaledEpsInfo_[c].Sowcr + scaledEpsInfo_[c].Swcr) >= (1.0 + tolerance)) {
|
||||
const std::string msg = "For scaled endpoints input, cell" + cellIdx + " SATNUM = " + satnumIdx + ", SOWCR + SWCR exceed 1.0";
|
||||
OpmLog::warning(tag, msg);
|
||||
}
|
||||
|
||||
if ((scaledEpsInfo_[c].Sogcr + scaledEpsInfo_[c].Sgcr + scaledEpsInfo_[c].Swl) >= (1.0 + tolerance)) {
|
||||
const std::string msg = "For scaled endpoints input, cell" + cellIdx + " SATNUM = " + satnumIdx + ", SOGCR + SGCR + SWL exceed 1.0";
|
||||
OpmLog::warning(tag, msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} //namespace Opm
|
||||
|
||||
#endif // OPM_RELPERMDIAGNOSTICS_IMPL_HEADER_INCLUDED
|
222
opm/core/props/satfunc/SaturationPropsBasic.cpp
Normal file
222
opm/core/props/satfunc/SaturationPropsBasic.cpp
Normal file
@ -0,0 +1,222 @@
|
||||
/*
|
||||
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/props/satfunc/SaturationPropsBasic.hpp>
|
||||
#include <opm/common/ErrorMacros.hpp>
|
||||
#include <iostream>
|
||||
|
||||
namespace Opm
|
||||
{
|
||||
|
||||
|
||||
// ---------- Helper functions ----------
|
||||
|
||||
namespace {
|
||||
|
||||
struct KrFunConstant
|
||||
{
|
||||
double kr(double)
|
||||
{
|
||||
return 1.0;
|
||||
}
|
||||
double dkrds(double)
|
||||
{
|
||||
return 0.0;
|
||||
}
|
||||
};
|
||||
|
||||
struct KrFunLinear
|
||||
{
|
||||
double kr(double s)
|
||||
{
|
||||
return s;
|
||||
}
|
||||
double dkrds(double)
|
||||
{
|
||||
return 1.0;
|
||||
}
|
||||
};
|
||||
|
||||
struct KrFunQuadratic
|
||||
{
|
||||
double kr(double s)
|
||||
{
|
||||
return s*s;
|
||||
}
|
||||
double dkrds(double s)
|
||||
{
|
||||
return 2.0*s;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template <class Fun>
|
||||
static inline void evalAllKrDeriv(const int n, const int np,
|
||||
const double* s, double* kr, double* dkrds, Fun fun)
|
||||
{
|
||||
if (dkrds == 0) {
|
||||
// #pragma omp parallel for
|
||||
for (int i = 0; i < n*np; ++i) {
|
||||
kr[i] = fun.kr(s[i]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// #pragma omp parallel for
|
||||
for (int i = 0; i < n; ++i) {
|
||||
std::fill(dkrds + i*np*np, dkrds + (i+1)*np*np, 0.0);
|
||||
for (int phase = 0; phase < np; ++phase) {
|
||||
kr[i*np + phase] = fun.kr(s[i*np + phase]);
|
||||
// Only diagonal elements in derivative.
|
||||
dkrds[i*np*np + phase*np + phase] = fun.dkrds(s[i*np + phase]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // anon namespace
|
||||
|
||||
|
||||
|
||||
// ---------- Class methods ----------
|
||||
|
||||
|
||||
|
||||
/// Default constructor.
|
||||
SaturationPropsBasic::SaturationPropsBasic()
|
||||
: num_phases_(0), relperm_func_(Constant)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// Initialize from parameters.
|
||||
void SaturationPropsBasic::init(const ParameterGroup& param)
|
||||
{
|
||||
int num_phases = param.getDefault("num_phases", 2);
|
||||
if (num_phases > 2 || num_phases < 1) {
|
||||
OPM_THROW(std::runtime_error, "SaturationPropsBasic::init() illegal num_phases: " << num_phases);
|
||||
}
|
||||
num_phases_ = num_phases;
|
||||
//std::string rpf = param.getDefault("relperm_func", std::string("Unset"));
|
||||
std::string rpf = param.getDefault("relperm_func", std::string("Linear"));
|
||||
if (rpf == "Constant") {
|
||||
relperm_func_ = Constant;
|
||||
if(num_phases!=1){
|
||||
OPM_THROW(std::runtime_error, "Constant relperm with more than one phase???");
|
||||
}
|
||||
} else if (rpf == "Linear") {
|
||||
relperm_func_ = Linear;
|
||||
} else if (rpf == "Quadratic") {
|
||||
relperm_func_ = Quadratic;
|
||||
} else {
|
||||
OPM_THROW(std::runtime_error, "SaturationPropsBasic::init() illegal relperm_func: " << rpf);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// \return P, the number of phases.
|
||||
int SaturationPropsBasic::numPhases() const
|
||||
{
|
||||
return num_phases_;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// Relative permeability.
|
||||
/// \param[in] n Number of data points.
|
||||
/// \param[in] s Array of nP saturation values.
|
||||
/// \param[out] kr Array of nP relperm values, array must be valid before calling.
|
||||
/// \param[out] dkrds If non-null: array of nP^2 relperm derivative values,
|
||||
/// array must be valid before calling.
|
||||
/// The P^2 derivative matrix is
|
||||
/// m_{ij} = \frac{dkr_i}{ds^j},
|
||||
/// and is output in Fortran order (m_00 m_10 m_20 m01 ...)
|
||||
void SaturationPropsBasic::relperm(const int n,
|
||||
const double* s,
|
||||
double* kr,
|
||||
double* dkrds) const
|
||||
{
|
||||
switch (relperm_func_) {
|
||||
case Constant:
|
||||
{
|
||||
evalAllKrDeriv(n, num_phases_, s, kr, dkrds, KrFunConstant());
|
||||
break;
|
||||
}
|
||||
case Linear:
|
||||
{
|
||||
evalAllKrDeriv(n, num_phases_, s, kr, dkrds, KrFunLinear());
|
||||
break;
|
||||
}
|
||||
case Quadratic:
|
||||
{
|
||||
evalAllKrDeriv(n, num_phases_, s, kr, dkrds, KrFunQuadratic());
|
||||
break;
|
||||
}
|
||||
default:
|
||||
OPM_THROW(std::runtime_error, "SaturationPropsBasic::relperm() unhandled relperm func type: " << relperm_func_);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// Capillary pressure.
|
||||
/// \param[in] n Number of data points.
|
||||
/// \param[in] s Array of nP saturation values.
|
||||
/// \param[out] pc Array of nP capillary pressure values, array must be valid before calling.
|
||||
/// \param[out] dpcds If non-null: array of nP^2 derivative values,
|
||||
/// array must be valid before calling.
|
||||
/// The P^2 derivative matrix is
|
||||
/// m_{ij} = \frac{dpc_i}{ds^j},
|
||||
/// and is output in Fortran order (m_00 m_10 m_20 m01 ...)
|
||||
void SaturationPropsBasic::capPress(const int n,
|
||||
const double* /*s*/,
|
||||
double* pc,
|
||||
double* dpcds) const
|
||||
{
|
||||
std::fill(pc, pc + num_phases_*n, 0.0);
|
||||
if (dpcds) {
|
||||
std::fill(dpcds, dpcds + num_phases_*num_phases_*n, 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// Obtain the range of allowable saturation values.
|
||||
/// \param[in] n Number of data points.
|
||||
/// \param[out] smin Array of nP minimum s values, array must be valid before calling.
|
||||
/// \param[out] smax Array of nP maximum s values, array must be valid before calling.
|
||||
void SaturationPropsBasic::satRange(const int n,
|
||||
double* smin,
|
||||
double* smax) const
|
||||
{
|
||||
std::fill(smin, smin + num_phases_*n, 0.0);
|
||||
std::fill(smax, smax + num_phases_*n, 1.0);
|
||||
}
|
||||
|
||||
|
||||
|
||||
} // namespace Opm
|
||||
|
||||
|
110
opm/core/props/satfunc/SaturationPropsBasic.hpp
Normal file
110
opm/core/props/satfunc/SaturationPropsBasic.hpp
Normal file
@ -0,0 +1,110 @@
|
||||
/*
|
||||
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_SATURATIONPROPSBASIC_HEADER_INCLUDED
|
||||
#define OPM_SATURATIONPROPSBASIC_HEADER_INCLUDED
|
||||
|
||||
#include <opm/core/utility/parameters/ParameterGroup.hpp>
|
||||
|
||||
namespace Opm
|
||||
{
|
||||
|
||||
|
||||
/// Class encapsulating basic saturation function behaviour,
|
||||
/// by which we mean constant, linear or quadratic relative
|
||||
/// permeability functions for a maximum of two phases,
|
||||
/// and zero capillary pressure.
|
||||
///
|
||||
/// TODO: This class can easily be extended to three phases,
|
||||
/// by adding three-phase relperm behaviour.
|
||||
class SaturationPropsBasic
|
||||
{
|
||||
public:
|
||||
/// Default constructor.
|
||||
SaturationPropsBasic();
|
||||
|
||||
/// Initialize from parameters.
|
||||
/// The following parameters are accepted (defaults):
|
||||
/// - num_phases (2) -- Must be 1 or 2.
|
||||
/// - relperm_func ("Linear") -- Must be "Constant", "Linear" or "Quadratic".
|
||||
void init(const ParameterGroup& param);
|
||||
|
||||
enum RelPermFunc { Constant, Linear, Quadratic };
|
||||
|
||||
/// Initialize from arguments a basic Saturation property.
|
||||
void init(const int num_phases,
|
||||
const RelPermFunc& relperm_func)
|
||||
{
|
||||
num_phases_ = num_phases;
|
||||
relperm_func_ = relperm_func;
|
||||
}
|
||||
|
||||
/// \return P, the number of phases.
|
||||
int numPhases() const;
|
||||
|
||||
/// Relative permeability.
|
||||
/// \param[in] n Number of data points.
|
||||
/// \param[in] s Array of nP saturation values.
|
||||
/// \param[out] kr Array of nP relperm values, array must be valid before calling.
|
||||
/// \param[out] dkrds If non-null: array of nP^2 relperm derivative values,
|
||||
/// array must be valid before calling.
|
||||
/// The P^2 derivative matrix is
|
||||
/// m_{ij} = \frac{dkr_i}{ds^j},
|
||||
/// and is output in Fortran order (m_00 m_10 m_20 m01 ...)
|
||||
void relperm(const int n,
|
||||
const double* s,
|
||||
double* kr,
|
||||
double* dkrds) const;
|
||||
|
||||
/// Capillary pressure.
|
||||
/// \param[in] n Number of data points.
|
||||
/// \param[in] s Array of nP saturation values.
|
||||
/// \param[out] pc Array of nP capillary pressure values, array must be valid before calling.
|
||||
/// \param[out] dpcds If non-null: array of nP^2 derivative values,
|
||||
/// array must be valid before calling.
|
||||
/// The P^2 derivative matrix is
|
||||
/// m_{ij} = \frac{dpc_i}{ds^j},
|
||||
/// and is output in Fortran order (m_00 m_10 m_20 m01 ...)
|
||||
void capPress(const int n,
|
||||
const double* s,
|
||||
double* pc,
|
||||
double* dpcds) const;
|
||||
|
||||
/// Obtain the range of allowable saturation values.
|
||||
/// \param[in] n Number of data points.
|
||||
/// \param[out] smin Array of nP minimum s values, array must be valid before calling.
|
||||
/// \param[out] smax Array of nP maximum s values, array must be valid before calling.
|
||||
void satRange(const int n,
|
||||
double* smin,
|
||||
double* smax) const;
|
||||
|
||||
|
||||
private:
|
||||
int num_phases_;
|
||||
RelPermFunc relperm_func_;
|
||||
};
|
||||
|
||||
|
||||
|
||||
} // namespace Opm
|
||||
|
||||
|
||||
|
||||
|
||||
#endif // OPM_SATURATIONPROPSBASIC_HEADER_INCLUDED
|
371
opm/core/props/satfunc/SaturationPropsFromDeck.cpp
Normal file
371
opm/core/props/satfunc/SaturationPropsFromDeck.cpp
Normal file
@ -0,0 +1,371 @@
|
||||
/*
|
||||
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/props/satfunc/SaturationPropsFromDeck.hpp>
|
||||
|
||||
#include <opm/material/fluidmatrixinteractions/EclMaterialLawManager.hpp>
|
||||
|
||||
#include <opm/core/utility/UniformTableLinear.hpp>
|
||||
#include <opm/core/utility/NonuniformTableLinear.hpp>
|
||||
#include <opm/core/grid/GridHelpers.hpp>
|
||||
#include <opm/core/simulator/ExplicitArraysFluidState.hpp>
|
||||
#include <opm/core/simulator/ExplicitArraysSatDerivativesFluidState.hpp>
|
||||
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
|
||||
namespace Opm
|
||||
{
|
||||
|
||||
typedef SaturationPropsFromDeck::MaterialLawManager::MaterialLaw MaterialLaw;
|
||||
|
||||
// ----------- Methods of SaturationPropsFromDeck ---------
|
||||
|
||||
|
||||
/// Default constructor.
|
||||
SaturationPropsFromDeck::SaturationPropsFromDeck()
|
||||
{
|
||||
}
|
||||
|
||||
/// Initialize from deck.
|
||||
void SaturationPropsFromDeck::init(const PhaseUsage &phaseUsage,
|
||||
std::shared_ptr<MaterialLawManager> materialLawManager)
|
||||
{
|
||||
phaseUsage_ = phaseUsage;
|
||||
materialLawManager_ = materialLawManager;
|
||||
}
|
||||
|
||||
/// \return P, the number of phases.
|
||||
int SaturationPropsFromDeck::numPhases() const
|
||||
{
|
||||
return phaseUsage_.num_phases;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// Relative permeability.
|
||||
/// \param[in] n Number of data points.
|
||||
/// \param[in] s Array of nP saturation values.
|
||||
/// \param[in] cells Array of n cell indices to be associated with the s values.
|
||||
/// \param[out] kr Array of nP relperm values, array must be valid before calling.
|
||||
/// \param[out] dkrds If non-null: array of nP^2 relperm derivative values,
|
||||
/// array must be valid before calling.
|
||||
/// The P^2 derivative matrix is
|
||||
/// m_{ij} = \frac{dkr_i}{ds^j},
|
||||
/// and is output in Fortran order (m_00 m_10 m_20 m01 ...)
|
||||
void SaturationPropsFromDeck::relperm(const int n,
|
||||
const double* s,
|
||||
const int* cells,
|
||||
double* kr,
|
||||
double* dkrds) const
|
||||
{
|
||||
assert(cells != 0);
|
||||
|
||||
const int np = numPhases();
|
||||
if (dkrds) {
|
||||
ExplicitArraysSatDerivativesFluidState fluidState(phaseUsage_);
|
||||
fluidState.setSaturationArray(s);
|
||||
|
||||
typedef ExplicitArraysSatDerivativesFluidState::Evaluation Evaluation;
|
||||
Evaluation relativePerms[BlackoilPhases::MaxNumPhases];
|
||||
for (int i = 0; i < n; ++i) {
|
||||
fluidState.setIndex(i);
|
||||
const auto& params = materialLawManager_->materialLawParams(cells[i]);
|
||||
MaterialLaw::relativePermeabilities(relativePerms, params, fluidState);
|
||||
|
||||
// copy the values calculated using opm-material to the target arrays
|
||||
for (int krPhaseIdx = 0; krPhaseIdx < np; ++krPhaseIdx) {
|
||||
kr[np*i + krPhaseIdx] = relativePerms[krPhaseIdx].value();
|
||||
|
||||
for (int satPhaseIdx = 0; satPhaseIdx < np; ++satPhaseIdx)
|
||||
dkrds[np*np*i + satPhaseIdx*np + krPhaseIdx] = relativePerms[krPhaseIdx].derivative(satPhaseIdx);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ExplicitArraysFluidState fluidState(phaseUsage_);
|
||||
fluidState.setSaturationArray(s);
|
||||
|
||||
double relativePerms[BlackoilPhases::MaxNumPhases] = { 0 };
|
||||
for (int i = 0; i < n; ++i) {
|
||||
fluidState.setIndex(i);
|
||||
const auto& params = materialLawManager_->materialLawParams(cells[i]);
|
||||
MaterialLaw::relativePermeabilities(relativePerms, params, fluidState);
|
||||
|
||||
// copy the values calculated using opm-material to the target arrays
|
||||
for (int krPhaseIdx = 0; krPhaseIdx < np; ++krPhaseIdx) {
|
||||
kr[np*i + krPhaseIdx] = relativePerms[krPhaseIdx];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// Capillary pressure.
|
||||
/// \param[in] n Number of data points.
|
||||
/// \param[in] s Array of nP saturation values.
|
||||
/// \param[in] cells Array of n cell indices to be associated with the s values.
|
||||
/// \param[out] pc Array of nP capillary pressure values, array must be valid before calling.
|
||||
/// \param[out] dpcds If non-null: array of nP^2 derivative values,
|
||||
/// array must be valid before calling.
|
||||
/// The P^2 derivative matrix is
|
||||
/// m_{ij} = \frac{dpc_i}{ds^j},
|
||||
/// and is output in Fortran order (m_00 m_10 m_20 m01 ...)
|
||||
void SaturationPropsFromDeck::capPress(const int n,
|
||||
const double* s,
|
||||
const int* cells,
|
||||
double* pc,
|
||||
double* dpcds) const
|
||||
{
|
||||
assert(cells != 0);
|
||||
assert(phaseUsage_.phase_used[BlackoilPhases::Liquid]);
|
||||
|
||||
const int np = numPhases();
|
||||
|
||||
if (dpcds) {
|
||||
ExplicitArraysSatDerivativesFluidState fluidState(phaseUsage_);
|
||||
typedef ExplicitArraysSatDerivativesFluidState::Evaluation Evaluation;
|
||||
fluidState.setSaturationArray(s);
|
||||
|
||||
Evaluation capillaryPressures[BlackoilPhases::MaxNumPhases];
|
||||
for (int i = 0; i < n; ++i) {
|
||||
fluidState.setIndex(i);
|
||||
const auto& params = materialLawManager_->materialLawParams(cells[i]);
|
||||
MaterialLaw::capillaryPressures(capillaryPressures, params, fluidState);
|
||||
|
||||
// copy the values calculated using opm-material to the target arrays
|
||||
for (int canonicalPhaseIdx = 0; canonicalPhaseIdx < BlackoilPhases::MaxNumPhases; ++canonicalPhaseIdx) {
|
||||
// skip unused phases
|
||||
if ( ! phaseUsage_.phase_used[canonicalPhaseIdx]) {
|
||||
continue;
|
||||
}
|
||||
const int pcPhaseIdx = phaseUsage_.phase_pos[canonicalPhaseIdx];
|
||||
|
||||
const double sign = (canonicalPhaseIdx == BlackoilPhases::Aqua)? -1.0 : 1.0;
|
||||
// in opm-material the wetting phase is the reference phase
|
||||
// for two-phase problems i.e water for oil-water system,
|
||||
// but for flow it is always oil. Add oil (liquid) capillary pressure value
|
||||
// to shift the reference phase to oil
|
||||
pc[np*i + pcPhaseIdx] = capillaryPressures[BlackoilPhases::Liquid].value() + sign * capillaryPressures[canonicalPhaseIdx].value();
|
||||
for (int canonicalSatPhaseIdx = 0; canonicalSatPhaseIdx < BlackoilPhases::MaxNumPhases; ++canonicalSatPhaseIdx) {
|
||||
if ( ! phaseUsage_.phase_used[canonicalSatPhaseIdx])
|
||||
continue;
|
||||
|
||||
const int satPhaseIdx = phaseUsage_.phase_pos[canonicalSatPhaseIdx];
|
||||
dpcds[np*np*i + satPhaseIdx*np + pcPhaseIdx] = capillaryPressures[BlackoilPhases::Liquid].derivative(canonicalSatPhaseIdx) + sign * capillaryPressures[canonicalPhaseIdx].derivative(canonicalSatPhaseIdx);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ExplicitArraysFluidState fluidState(phaseUsage_);
|
||||
fluidState.setSaturationArray(s);
|
||||
|
||||
double capillaryPressures[BlackoilPhases::MaxNumPhases] = { 0 };
|
||||
for (int i = 0; i < n; ++i) {
|
||||
fluidState.setIndex(i);
|
||||
const auto& params = materialLawManager_->materialLawParams(cells[i]);
|
||||
MaterialLaw::capillaryPressures(capillaryPressures, params, fluidState);
|
||||
|
||||
// copy the values calculated using opm-material to the target arrays
|
||||
for (int canonicalPhaseIdx = 0; canonicalPhaseIdx < BlackoilPhases::MaxNumPhases; ++canonicalPhaseIdx) {
|
||||
// skip unused phases
|
||||
if ( ! phaseUsage_.phase_used[canonicalPhaseIdx])
|
||||
continue;
|
||||
|
||||
const int pcPhaseIdx = phaseUsage_.phase_pos[canonicalPhaseIdx];
|
||||
double sign = (canonicalPhaseIdx == BlackoilPhases::Aqua)? -1.0 : 1.0;
|
||||
// in opm-material the wetting phase is the reference phase
|
||||
// for two-phase problems i.e water for oil-water system,
|
||||
// but for flow it is always oil. Add oil (liquid) capillary pressure value
|
||||
// to shift the reference phase to oil
|
||||
pc[np*i + pcPhaseIdx] = capillaryPressures[BlackoilPhases::Liquid] + sign * capillaryPressures[canonicalPhaseIdx];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Obtain the range of allowable saturation values.
|
||||
/// \param[in] n Number of data points.
|
||||
/// \param[in] cells Array of n cell indices.
|
||||
/// \param[out] smin Array of nP minimum s values, array must be valid before calling.
|
||||
/// \param[out] smax Array of nP maximum s values, array must be valid before calling.
|
||||
void SaturationPropsFromDeck::satRange(const int n,
|
||||
const int* cells,
|
||||
double* smin,
|
||||
double* smax) const
|
||||
{
|
||||
int wpos = phaseUsage_.phase_pos[BlackoilPhases::Aqua];
|
||||
int gpos = phaseUsage_.phase_pos[BlackoilPhases::Vapour];
|
||||
int opos = phaseUsage_.phase_pos[BlackoilPhases::Liquid];
|
||||
|
||||
const int np = numPhases();
|
||||
for (int i = 0; i < n; ++i) {
|
||||
const auto& scaledDrainageInfo =
|
||||
materialLawManager_->oilWaterScaledEpsInfoDrainage(cells[i]);
|
||||
|
||||
if (phaseUsage_.phase_used[BlackoilPhases::Aqua]) {
|
||||
smin[np*i + wpos] = scaledDrainageInfo.Swl;
|
||||
smax[np*i + wpos] = scaledDrainageInfo.Swu;
|
||||
}
|
||||
|
||||
if (phaseUsage_.phase_used[BlackoilPhases::Vapour]) {
|
||||
smin[np*i + gpos] = scaledDrainageInfo.Sgl;
|
||||
smax[np*i + gpos] = scaledDrainageInfo.Sgu;
|
||||
}
|
||||
|
||||
if (phaseUsage_.phase_used[BlackoilPhases::Liquid]) {
|
||||
smin[np*i + opos] = 1.0;
|
||||
smax[np*i + opos] = 1.0;
|
||||
if (phaseUsage_.phase_used[BlackoilPhases::Aqua]) {
|
||||
smin[np*i + opos] -= smax[np*i + wpos];
|
||||
smax[np*i + opos] -= smin[np*i + wpos];
|
||||
}
|
||||
if (phaseUsage_.phase_used[BlackoilPhases::Vapour]) {
|
||||
smin[np*i + opos] -= smax[np*i + gpos];
|
||||
smax[np*i + opos] -= smin[np*i + gpos];
|
||||
}
|
||||
smin[np*i + opos] = std::max(0.0, smin[np*i + opos]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Update saturation state for the hysteresis tracking
|
||||
/// \param[in] n Number of data points.
|
||||
/// \param[in] s Array of nP saturation values.
|
||||
void SaturationPropsFromDeck::updateSatHyst(const int n,
|
||||
const int* cells,
|
||||
const double* s)
|
||||
{
|
||||
assert(cells != 0);
|
||||
|
||||
if (materialLawManager_->enableHysteresis()) {
|
||||
ExplicitArraysFluidState fluidState(phaseUsage_);
|
||||
fluidState.setSaturationArray(s);
|
||||
|
||||
for (int i = 0; i < n; ++i) {
|
||||
fluidState.setIndex(i);
|
||||
materialLawManager_->updateHysteresis(fluidState, cells[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// Set hysteresis parameters for gas-oil
|
||||
/// \param[in] n Number of data points.
|
||||
/// \param[in] pcswmdc Array of hysteresis parameters (@see EclHysteresisTwoPhaseLawParams::pcSwMdc(...))
|
||||
/// \param[in] krnswdc Array of hysteresis parameters (@see EclHysteresisTwoPhaseLawParams::krnSwMdc(...))
|
||||
void SaturationPropsFromDeck::setGasOilHystParams(const int n,
|
||||
const int* cells,
|
||||
const double* pcswmdc,
|
||||
const double* krnswdc)
|
||||
{
|
||||
assert(cells != 0);
|
||||
|
||||
if (materialLawManager_->enableHysteresis()) {
|
||||
for (int i = 0; i < n; ++i) {
|
||||
materialLawManager_->setGasOilHysteresisParams(pcswmdc[i], krnswdc[i], cells[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get hysteresis parameters for gas-oil
|
||||
/// \param[in] n Number of data points.
|
||||
/// \param[in] pcswmdc Array of hysteresis parameters (@see EclHysteresisTwoPhaseLawParams::pcSwMdc(...))
|
||||
/// \param[in] krnswdc Array of hysteresis parameters (@see EclHysteresisTwoPhaseLawParams::krnSwMdc(...))
|
||||
void SaturationPropsFromDeck::getGasOilHystParams(const int n,
|
||||
const int* cells,
|
||||
double* pcswmdc,
|
||||
double* krnswdc) const
|
||||
{
|
||||
assert(cells != 0);
|
||||
|
||||
if (materialLawManager_->enableHysteresis()) {
|
||||
for (int i = 0; i < n; ++i) {
|
||||
materialLawManager_->gasOilHysteresisParams(pcswmdc[i], krnswdc[i], cells[i]);
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (int i = 0; i < n; ++i) {
|
||||
//Signify non-physical values since not enabled
|
||||
pcswmdc[i] = 2.0;
|
||||
krnswdc[i] = 2.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Set hysteresis parameters for oil-water
|
||||
/// \param[in] n Number of data points.
|
||||
/// \param[in] pcswmdc Array of hysteresis parameters (@see EclHysteresisTwoPhaseLawParams::pcSwMdc(...))
|
||||
/// \param[in] krnswdc Array of hysteresis parameters (@see EclHysteresisTwoPhaseLawParams::krnSwMdc(...))
|
||||
void SaturationPropsFromDeck::setOilWaterHystParams(const int n,
|
||||
const int* cells,
|
||||
const double* pcswmdc,
|
||||
const double* krnswdc)
|
||||
{
|
||||
assert(cells != 0);
|
||||
|
||||
if (materialLawManager_->enableHysteresis()) {
|
||||
for (int i = 0; i < n; ++i) {
|
||||
materialLawManager_->setOilWaterHysteresisParams(pcswmdc[i], krnswdc[i], cells[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get hysteresis parameters for oil-water
|
||||
/// \param[in] n Number of data points.
|
||||
/// \param[in] pcswmdc Array of hysteresis parameters (@see EclHysteresisTwoPhaseLawParams::pcSwMdc(...))
|
||||
/// \param[in] krnswdc Array of hysteresis parameters (@see EclHysteresisTwoPhaseLawParams::krnSwMdc(...))
|
||||
void SaturationPropsFromDeck::getOilWaterHystParams(const int n,
|
||||
const int* cells,
|
||||
double* pcswmdc,
|
||||
double* krnswdc) const
|
||||
{
|
||||
assert(cells != 0);
|
||||
|
||||
if (materialLawManager_->enableHysteresis()) {
|
||||
for (int i = 0; i < n; ++i) {
|
||||
materialLawManager_->oilWaterHysteresisParams(pcswmdc[i], krnswdc[i], cells[i]);
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (int i = 0; i < n; ++i) {
|
||||
//Signify non-physical values since not enabled
|
||||
pcswmdc[i] = 2.0;
|
||||
krnswdc[i] = 2.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Update capillary pressure scaling according to pressure diff. and initial water saturation.
|
||||
/// \param[in] cell Cell index.
|
||||
/// \param[in] pcow P_oil - P_water.
|
||||
/// \param[in/out] swat Water saturation. / Possibly modified Water saturation.
|
||||
void SaturationPropsFromDeck::swatInitScaling(const int cell,
|
||||
const double pcow,
|
||||
double& swat)
|
||||
{
|
||||
swat = materialLawManager_->applySwatinit(cell, pcow, swat);
|
||||
}
|
||||
} // namespace Opm
|
181
opm/core/props/satfunc/SaturationPropsFromDeck.hpp
Normal file
181
opm/core/props/satfunc/SaturationPropsFromDeck.hpp
Normal file
@ -0,0 +1,181 @@
|
||||
/*
|
||||
Copyright 2010, 2011, 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_SATURATIONPROPSFROMDECK_HEADER_INCLUDED
|
||||
#define OPM_SATURATIONPROPSFROMDECK_HEADER_INCLUDED
|
||||
|
||||
#include <opm/core/props/satfunc/SaturationPropsInterface.hpp>
|
||||
#include <opm/core/utility/parameters/ParameterGroup.hpp>
|
||||
#include <opm/core/props/BlackoilPhases.hpp>
|
||||
#include <opm/core/props/phaseUsageFromDeck.hpp>
|
||||
#include <opm/core/grid.h>
|
||||
|
||||
#include <opm/parser/eclipse/Deck/Deck.hpp>
|
||||
#include <opm/parser/eclipse/EclipseState/EclipseState.hpp>
|
||||
|
||||
#include <vector>
|
||||
|
||||
struct UnstructuredGrid;
|
||||
|
||||
namespace Opm
|
||||
{
|
||||
|
||||
// Forward declaring the EclMaterialLawManager template.
|
||||
template <class ScalarT, int wettingPhaseIdxV, int nonWettingasPhaseIdxV, int gasPhaseIdxV>
|
||||
class ThreePhaseMaterialTraits;
|
||||
template <class Traits>
|
||||
class EclMaterialLawManager;
|
||||
|
||||
|
||||
/// Interface to saturation functions from deck.
|
||||
class SaturationPropsFromDeck : public SaturationPropsInterface
|
||||
{
|
||||
public:
|
||||
typedef Opm::ThreePhaseMaterialTraits<double,
|
||||
/*wettingPhaseIdx=*/BlackoilPhases::Aqua,
|
||||
/*nonWettingPhaseIdx=*/BlackoilPhases::Liquid,
|
||||
/*gasPhaseIdx=*/BlackoilPhases::Vapour> MaterialTraits;
|
||||
typedef Opm::EclMaterialLawManager<MaterialTraits> MaterialLawManager;
|
||||
|
||||
/// Default constructor.
|
||||
SaturationPropsFromDeck();
|
||||
|
||||
/// Initialize from a MaterialLawManager object.
|
||||
/// \param[in] phaseUsage Phase configuration
|
||||
/// \param[in] materialLawManager An initialized MaterialLawManager object
|
||||
void init(const PhaseUsage& phaseUsage,
|
||||
std::shared_ptr<MaterialLawManager> materialLawManager);
|
||||
|
||||
|
||||
/// Initialize from deck and MaterialLawManager.
|
||||
/// \param[in] deck Input deck
|
||||
/// \param[in] materialLawManager An initialized MaterialLawManager object
|
||||
void init(const Opm::Deck& deck,
|
||||
std::shared_ptr<MaterialLawManager> materialLawManager)
|
||||
{
|
||||
init(Opm::phaseUsageFromDeck(deck), materialLawManager);
|
||||
}
|
||||
|
||||
/// \return P, the number of phases.
|
||||
int numPhases() const;
|
||||
|
||||
/// Relative permeability.
|
||||
/// \param[in] n Number of data points.
|
||||
/// \param[in] s Array of nP saturation values.
|
||||
/// \param[out] kr Array of nP relperm values, array must be valid before calling.
|
||||
/// \param[out] dkrds If non-null: array of nP^2 relperm derivative values,
|
||||
/// array must be valid before calling.
|
||||
/// The P^2 derivative matrix is
|
||||
/// m_{ij} = \frac{dkr_i}{ds^j},
|
||||
/// and is output in Fortran order (m_00 m_10 m_20 m01 ...)
|
||||
void relperm(const int n,
|
||||
const double* s,
|
||||
const int* cells,
|
||||
double* kr,
|
||||
double* dkrds) const;
|
||||
|
||||
/// Capillary pressure.
|
||||
/// \param[in] n Number of data points.
|
||||
/// \param[in] s Array of nP saturation values.
|
||||
/// \param[out] pc Array of nP capillary pressure values, array must be valid before calling.
|
||||
/// \param[out] dpcds If non-null: array of nP^2 derivative values,
|
||||
/// array must be valid before calling.
|
||||
/// The P^2 derivative matrix is
|
||||
/// m_{ij} = \frac{dpc_i}{ds^j},
|
||||
/// and is output in Fortran order (m_00 m_10 m_20 m01 ...)
|
||||
void capPress(const int n,
|
||||
const double* s,
|
||||
const int* cells,
|
||||
double* pc,
|
||||
double* dpcds) const;
|
||||
|
||||
/// Obtain the range of allowable saturation values.
|
||||
/// \param[in] n Number of data points.
|
||||
/// \param[out] smin Array of nP minimum s values, array must be valid before calling.
|
||||
/// \param[out] smax Array of nP maximum s values, array must be valid before calling.
|
||||
void satRange(const int n,
|
||||
const int* cells,
|
||||
double* smin,
|
||||
double* smax) const;
|
||||
|
||||
/// Update saturation state for the hysteresis tracking
|
||||
/// \param[in] n Number of data points.
|
||||
/// \param[in] s Array of nP saturation values.
|
||||
void updateSatHyst(const int n,
|
||||
const int* cells,
|
||||
const double* s);
|
||||
|
||||
/// Set hysteresis parameters for gas-oil
|
||||
/// \param[in] n Number of data points.
|
||||
/// \param[in] pcswmdc Array of hysteresis parameters (@see EclHysteresisTwoPhaseLawParams::pcSwMdc(...))
|
||||
/// \param[in] krnswdc Array of hysteresis parameters (@see EclHysteresisTwoPhaseLawParams::krnSwMdc(...))
|
||||
void setGasOilHystParams(const int n,
|
||||
const int* cells,
|
||||
const double* pcswmdc,
|
||||
const double* krnswdc);
|
||||
|
||||
/// Get hysteresis parameters for gas-oil
|
||||
/// \param[in] n Number of data points.
|
||||
/// \param[in] pcswmdc Array of hysteresis parameters (@see EclHysteresisTwoPhaseLawParams::pcSwMdc(...))
|
||||
/// \param[in] krnswdc Array of hysteresis parameters (@see EclHysteresisTwoPhaseLawParams::krnSwMdc(...))
|
||||
void getGasOilHystParams(const int n,
|
||||
const int* cells,
|
||||
double* pcswmdc,
|
||||
double* krnswdc) const;
|
||||
|
||||
/// Set hysteresis parameters for oil-water
|
||||
/// \param[in] n Number of data points.
|
||||
/// \param[in] pcswmdc Array of hysteresis parameters (@see EclHysteresisTwoPhaseLawParams::pcSwMdc(...))
|
||||
/// \param[in] krnswdc Array of hysteresis parameters (@see EclHysteresisTwoPhaseLawParams::krnSwMdc(...))
|
||||
void setOilWaterHystParams(const int n,
|
||||
const int* cells,
|
||||
const double* pcswmdc,
|
||||
const double* krnswdc);
|
||||
|
||||
/// Get hysteresis parameters for oil-water
|
||||
/// \param[in] n Number of data points.
|
||||
/// \param[in] pcswmdc Array of hysteresis parameters (@see EclHysteresisTwoPhaseLawParams::pcSwMdc(...))
|
||||
/// \param[in] krnswdc Array of hysteresis parameters (@see EclHysteresisTwoPhaseLawParams::krnSwMdc(...))
|
||||
void getOilWaterHystParams(const int n,
|
||||
const int* cells,
|
||||
double* pcswmdc,
|
||||
double* krnswdc) const;
|
||||
|
||||
/// Update capillary pressure scaling according to pressure diff. and initial water saturation.
|
||||
/// \param[in] cell Cell index.
|
||||
/// \param[in] pcow P_oil - P_water.
|
||||
/// \param[in/out] swat Water saturation. / Possibly modified Water saturation.
|
||||
void swatInitScaling(const int cell,
|
||||
const double pcow,
|
||||
double & swat);
|
||||
|
||||
/// Returns a reference to the MaterialLawManager
|
||||
const MaterialLawManager& materialLawManager() const { return *materialLawManager_; }
|
||||
|
||||
|
||||
private:
|
||||
std::shared_ptr<MaterialLawManager> materialLawManager_;
|
||||
PhaseUsage phaseUsage_;
|
||||
};
|
||||
|
||||
|
||||
|
||||
} // namespace Opm
|
||||
|
||||
#endif // OPM_SATURATIONPROPSFROMDECK_HEADER_INCLUDED
|
100
opm/core/props/satfunc/SaturationPropsInterface.hpp
Normal file
100
opm/core/props/satfunc/SaturationPropsInterface.hpp
Normal file
@ -0,0 +1,100 @@
|
||||
/*
|
||||
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_SATURATIONPROPSINTERFACE_HEADER_INCLUDED
|
||||
#define OPM_SATURATIONPROPSINTERFACE_HEADER_INCLUDED
|
||||
|
||||
#include <opm/core/props/BlackoilPhases.hpp>
|
||||
|
||||
|
||||
namespace Opm
|
||||
{
|
||||
|
||||
class SaturationPropsInterface : public BlackoilPhases
|
||||
{
|
||||
public:
|
||||
/// Virtual destructor.
|
||||
virtual ~SaturationPropsInterface() {};
|
||||
|
||||
/// \return P, the number of phases.
|
||||
virtual int numPhases() const = 0;
|
||||
|
||||
/// Relative permeability.
|
||||
/// \param[in] n Number of data points.
|
||||
/// \param[in] s Array of nP saturation values.
|
||||
/// \param[out] kr Array of nP relperm values, array must be valid before calling.
|
||||
/// \param[out] dkrds If non-null: array of nP^2 relperm derivative values,
|
||||
/// array must be valid before calling.
|
||||
/// The P^2 derivative matrix is
|
||||
/// m_{ij} = \frac{dkr_i}{ds^j},
|
||||
/// and is output in Fortran order (m_00 m_10 m_20 m01 ...)
|
||||
virtual void relperm(const int n,
|
||||
const double* s,
|
||||
const int* cells,
|
||||
double* kr,
|
||||
double* dkrds) const = 0;
|
||||
|
||||
/// Capillary pressure.
|
||||
/// \param[in] n Number of data points.
|
||||
/// \param[in] s Array of nP saturation values.
|
||||
/// \param[out] pc Array of nP capillary pressure values, array must be valid before calling.
|
||||
/// \param[out] dpcds If non-null: array of nP^2 derivative values,
|
||||
/// array must be valid before calling.
|
||||
/// The P^2 derivative matrix is
|
||||
/// m_{ij} = \frac{dpc_i}{ds^j},
|
||||
/// and is output in Fortran order (m_00 m_10 m_20 m01 ...)
|
||||
virtual void capPress(const int n,
|
||||
const double* s,
|
||||
const int* cells,
|
||||
double* pc,
|
||||
double* dpcds) const = 0;
|
||||
|
||||
/// Obtain the range of allowable saturation values.
|
||||
/// \param[in] n Number of data points.
|
||||
/// \param[out] smin Array of nP minimum s values, array must be valid before calling.
|
||||
/// \param[out] smax Array of nP maximum s values, array must be valid before calling.
|
||||
virtual void satRange(const int n,
|
||||
const int* cells,
|
||||
double* smin,
|
||||
double* smax) const = 0;
|
||||
|
||||
/// Update saturation state for the hysteresis tracking
|
||||
/// \param[in] n Number of data points.
|
||||
/// \param[in] s Array of nP saturation values.
|
||||
virtual void updateSatHyst(const int n,
|
||||
const int* cells,
|
||||
const double* s) = 0;
|
||||
|
||||
/// Update capillary pressure scaling according to pressure diff. and initial water saturation.
|
||||
/// \param[in] cell Cell index.
|
||||
/// \param[in] pcow P_oil - P_water.
|
||||
/// \param[in/out] swat Water saturation. / Possibly modified Water saturation.
|
||||
virtual void swatInitScaling(const int cell,
|
||||
const double pcow,
|
||||
double & swat) = 0;
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
} // namespace Opm
|
||||
|
||||
|
||||
|
||||
#endif // OPM_SATURATIONPROPSINTERFACE_HEADER_INCLUDED
|
52
opm/core/simulator/BlackoilState.cpp
Normal file
52
opm/core/simulator/BlackoilState.cpp
Normal file
@ -0,0 +1,52 @@
|
||||
#include "BlackoilState.hpp"
|
||||
#include <opm/common/util/numeric/cmp.hpp>
|
||||
#include <opm/core/props/BlackoilPropertiesInterface.hpp>
|
||||
#include <opm/core/simulator/WellState.hpp>
|
||||
#include <opm/output/data/Wells.hpp>
|
||||
|
||||
|
||||
using namespace Opm;
|
||||
|
||||
|
||||
const std::string BlackoilState::GASOILRATIO = "GASOILRATIO";
|
||||
const std::string BlackoilState::RV = "RV";
|
||||
const std::string BlackoilState::SURFACEVOL = "SURFACEVOL";
|
||||
const std::string BlackoilState::SSOL = "SSOL";
|
||||
const std::string BlackoilState::POLYMER = "POLYMER";
|
||||
|
||||
|
||||
BlackoilState::BlackoilState( size_t num_cells , size_t num_faces , size_t num_phases)
|
||||
: SimulationDataContainer( num_cells , num_faces , num_phases)
|
||||
{
|
||||
registerCellData( GASOILRATIO , 1 );
|
||||
registerCellData( RV, 1 );
|
||||
registerCellData( SURFACEVOL, num_phases );
|
||||
registerCellData( SSOL , 1 );
|
||||
registerCellData( POLYMER , 1 );
|
||||
setBlackoilStateReferencePointers();
|
||||
}
|
||||
|
||||
BlackoilState::BlackoilState( const BlackoilState& other )
|
||||
: SimulationDataContainer(other),
|
||||
hydrocarbonstate_(other.hydroCarbonState())
|
||||
{
|
||||
setBlackoilStateReferencePointers();
|
||||
|
||||
}
|
||||
|
||||
BlackoilState& BlackoilState::operator=( const BlackoilState& other )
|
||||
{
|
||||
SimulationDataContainer::operator=(other);
|
||||
setBlackoilStateReferencePointers();
|
||||
hydrocarbonstate_ = other.hydroCarbonState();
|
||||
return *this;
|
||||
}
|
||||
|
||||
void BlackoilState::setBlackoilStateReferencePointers()
|
||||
{
|
||||
// This sets the reference pointers for the fast
|
||||
// accessors, the fields must have been created first.
|
||||
gasoilratio_ref_ = &getCellData(GASOILRATIO);
|
||||
rv_ref_ = &getCellData(RV);
|
||||
surfacevol_ref_ = &getCellData(SURFACEVOL);
|
||||
}
|
94
opm/core/simulator/BlackoilState.hpp
Normal file
94
opm/core/simulator/BlackoilState.hpp
Normal file
@ -0,0 +1,94 @@
|
||||
/*
|
||||
Copyright 2012 SINTEF ICT, Applied Mathematics.
|
||||
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_BLACKOILSTATE_HEADER_INCLUDED
|
||||
#define OPM_BLACKOILSTATE_HEADER_INCLUDED
|
||||
|
||||
#include <opm/common/data/SimulationDataContainer.hpp>
|
||||
|
||||
#include <opm/core/grid.h>
|
||||
#include <opm/core/props/BlackoilPropertiesInterface.hpp>
|
||||
#include <vector>
|
||||
|
||||
namespace Opm
|
||||
{
|
||||
|
||||
enum HydroCarbonState {
|
||||
GasOnly = 0,
|
||||
GasAndOil = 1,
|
||||
OilOnly = 2
|
||||
};
|
||||
|
||||
/// Simulator state for a blackoil simulator.
|
||||
class BlackoilState : public SimulationDataContainer
|
||||
{
|
||||
public:
|
||||
static const std::string GASOILRATIO;
|
||||
static const std::string RV;
|
||||
static const std::string SURFACEVOL;
|
||||
static const std::string SSOL;
|
||||
static const std::string POLYMER;
|
||||
|
||||
/// Main constructor setting the sizes for the contained data
|
||||
/// types.
|
||||
/// \param num_cells number of elements in cell data vectors
|
||||
/// \param num_faces number of elements in face data vectors
|
||||
/// \param num_phases number of phases, the number of components
|
||||
/// in any data vector must equal 1 or this
|
||||
/// number (this behaviour and argument is deprecated).
|
||||
BlackoilState(size_t num_cells, size_t num_faces, size_t num_phases);
|
||||
|
||||
/// Copy constructor.
|
||||
/// Must be defined explicitly because class contains non-value objects
|
||||
/// (the reference pointers rv_ref_ etc.) that should not simply
|
||||
/// be copied.
|
||||
BlackoilState(const BlackoilState& other);
|
||||
|
||||
/// Copy assignment operator.
|
||||
/// Must be defined explicitly because class contains non-value objects
|
||||
/// (the reference pointers rv_ref_ etc.) that should not simply
|
||||
/// be copied.
|
||||
BlackoilState& operator=(const BlackoilState& other);
|
||||
|
||||
std::vector<double>& surfacevol () { return *surfacevol_ref_; }
|
||||
std::vector<double>& gasoilratio () { return *gasoilratio_ref_; }
|
||||
std::vector<double>& rv () { return *rv_ref_; }
|
||||
std::vector<HydroCarbonState>& hydroCarbonState() { return hydrocarbonstate_; }
|
||||
|
||||
const std::vector<double>& surfacevol () const { return *surfacevol_ref_; }
|
||||
const std::vector<double>& gasoilratio () const { return *gasoilratio_ref_; }
|
||||
const std::vector<double>& rv () const { return *rv_ref_; }
|
||||
const std::vector<HydroCarbonState>& hydroCarbonState() const { return hydrocarbonstate_; }
|
||||
|
||||
private:
|
||||
void setBlackoilStateReferencePointers();
|
||||
std::vector<double>* surfacevol_ref_;
|
||||
std::vector<double>* gasoilratio_ref_;
|
||||
std::vector<double>* rv_ref_;
|
||||
|
||||
// A vector storing the hydro carbon state.
|
||||
std::vector<HydroCarbonState> hydrocarbonstate_;
|
||||
|
||||
|
||||
};
|
||||
} // namespace Opm
|
||||
|
||||
|
||||
#endif // OPM_BLACKOILSTATE_HEADER_INCLUDED
|
94
opm/core/simulator/BlackoilStateToFluidState.hpp
Normal file
94
opm/core/simulator/BlackoilStateToFluidState.hpp
Normal file
@ -0,0 +1,94 @@
|
||||
/*
|
||||
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_BLACKOIL_STATE_TO_FLUID_STATE_HEADER_INCLUDED
|
||||
#define OPM_BLACKOIL_STATE_TO_FLUID_STATE_HEADER_INCLUDED
|
||||
|
||||
#include <opm/core/simulator/BlackoilState.hpp>
|
||||
|
||||
namespace Opm
|
||||
{
|
||||
|
||||
/*!
|
||||
* \brief This is an light weight "impedance adaption" class with a well defined API for
|
||||
* saturation and PVT functions.
|
||||
*
|
||||
* It uses a stripped down version of opm-material's FluidState API and takes an
|
||||
* Opm::BlackoilState plus a cell index.
|
||||
*
|
||||
* Note that this class requires that is underlying BlackoilState must valid for at least
|
||||
* as long as an object of BlackoilStateToFluidState is used.
|
||||
*/
|
||||
class BlackoilStateToFluidState
|
||||
{
|
||||
public:
|
||||
typedef double Scalar;
|
||||
|
||||
enum { numPhases = 3 };
|
||||
enum { numComponents = 3 };
|
||||
|
||||
/*!
|
||||
* \brief Create a BlackoilState to Fluid state wrapper object.
|
||||
*
|
||||
* Note that this class requires that is underlying BlackoilState must valid for at least
|
||||
* as long as an object of BlackoilStateToFluidState is used.
|
||||
*/
|
||||
BlackoilStateToFluidState(const BlackoilState& blackoilState)
|
||||
: blackoilState_(blackoilState)
|
||||
{
|
||||
if (blackoilState_.numPhases() != numPhases) {
|
||||
OPM_THROW(std::runtime_error,
|
||||
"Only " << numPhases << " are supported, but the deck specifies " << blackoilState_.numPhases());
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Sets the index of the currently used cell.
|
||||
*
|
||||
* After calling this, the values returned by the other methods are specific for this
|
||||
* cell.
|
||||
*/
|
||||
void setCurrentCellIndex(unsigned cellIdx)
|
||||
{ cellIdx_ = cellIdx; }
|
||||
|
||||
/*!
|
||||
* \brief Returns the saturation of a phase for the current cell index.
|
||||
*/
|
||||
Scalar saturation(int phaseIdx) const
|
||||
{ return blackoilState_.saturation()[numPhases*cellIdx_ + phaseIdx]; }
|
||||
|
||||
/*!
|
||||
* \brief Returns the temperature [K] of a phase for the current cell index.
|
||||
*/
|
||||
Scalar temperature(int phaseIdx) const
|
||||
{ return blackoilState_.temperature()[cellIdx_]; }
|
||||
|
||||
// TODO (?) pressure, composition, etc
|
||||
|
||||
private:
|
||||
size_t numCells() const
|
||||
{ return blackoilState_.pressure().size(); }
|
||||
|
||||
const BlackoilState& blackoilState_;
|
||||
unsigned cellIdx_;
|
||||
}
|
||||
|
||||
} // namespace Opm
|
||||
|
||||
#endif // OPM_SIMULATORTIMER_HEADER_INCLUDED
|
799
opm/core/simulator/EquilibrationHelpers.hpp
Normal file
799
opm/core/simulator/EquilibrationHelpers.hpp
Normal file
@ -0,0 +1,799 @@
|
||||
/*
|
||||
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/>.
|
||||
*/
|
||||
|
||||
#ifndef OPM_EQUILIBRATIONHELPERS_HEADER_INCLUDED
|
||||
#define OPM_EQUILIBRATIONHELPERS_HEADER_INCLUDED
|
||||
|
||||
#include <opm/core/utility/linearInterpolation.hpp>
|
||||
#include <opm/core/utility/RegionMapping.hpp>
|
||||
#include <opm/core/utility/RootFinders.hpp>
|
||||
|
||||
#include <opm/parser/eclipse/EclipseState/InitConfig/Equil.hpp>
|
||||
|
||||
#include <opm/material/fluidsystems/BlackOilFluidSystem.hpp>
|
||||
#include <opm/material/fluidstates/SimpleModularFluidState.hpp>
|
||||
#include <opm/material/fluidmatrixinteractions/EclMaterialLawManager.hpp>
|
||||
|
||||
#include <memory>
|
||||
|
||||
|
||||
/*
|
||||
---- synopsis of EquilibrationHelpers.hpp ----
|
||||
|
||||
namespace Opm
|
||||
{
|
||||
namespace EQUIL {
|
||||
|
||||
namespace Miscibility {
|
||||
class RsFunction;
|
||||
class NoMixing;
|
||||
template <class FluidSystem>
|
||||
class RsVD;
|
||||
template <class FluidSystem>
|
||||
class RsSatAtContact;
|
||||
}
|
||||
|
||||
class EquilReg;
|
||||
|
||||
|
||||
template <class FluidSystem, class MaterialLaw, class MaterialLawManager>
|
||||
struct PcEq;
|
||||
|
||||
template <class FluidSystem, class MaterialLaw, class MaterialLawManager >
|
||||
inline double satFromPc(const MaterialLawManager& materialLawManager,
|
||||
const int phase,
|
||||
const int cell,
|
||||
const double target_pc,
|
||||
const bool increasing = false)
|
||||
template <class FluidSystem, class MaterialLaw, class MaterialLawManager>
|
||||
inline double satFromSumOfPcs(const MaterialLawManager& materialLawManager,
|
||||
const int phase1,
|
||||
const int phase2,
|
||||
const int cell,
|
||||
const double target_pc)
|
||||
} // namespace Equil
|
||||
} // namespace Opm
|
||||
|
||||
---- end of synopsis of EquilibrationHelpers.hpp ----
|
||||
*/
|
||||
|
||||
|
||||
namespace Opm
|
||||
{
|
||||
/**
|
||||
* Types and routines that collectively implement a basic
|
||||
* ECLIPSE-style equilibration-based initialisation scheme.
|
||||
*
|
||||
* This namespace is intentionally nested to avoid name clashes
|
||||
* with other parts of OPM.
|
||||
*/
|
||||
namespace EQUIL {
|
||||
|
||||
|
||||
typedef Opm::FluidSystems::BlackOil<double> FluidSystemSimple;
|
||||
|
||||
// Adjust oil pressure according to gas saturation and cap pressure
|
||||
typedef Opm::SimpleModularFluidState<double,
|
||||
/*numPhases=*/3,
|
||||
/*numComponents=*/3,
|
||||
FluidSystemSimple,
|
||||
/*storePressure=*/false,
|
||||
/*storeTemperature=*/false,
|
||||
/*storeComposition=*/false,
|
||||
/*storeFugacity=*/false,
|
||||
/*storeSaturation=*/true,
|
||||
/*storeDensity=*/false,
|
||||
/*storeViscosity=*/false,
|
||||
/*storeEnthalpy=*/false> SatOnlyFluidState;
|
||||
|
||||
/**
|
||||
* Types and routines relating to phase mixing in
|
||||
* equilibration calculations.
|
||||
*/
|
||||
namespace Miscibility {
|
||||
|
||||
/**
|
||||
* Base class for phase mixing functions.
|
||||
*/
|
||||
class RsFunction
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Function call operator.
|
||||
*
|
||||
* \param[in] depth Depth at which to calculate RS
|
||||
* value.
|
||||
*
|
||||
* \param[in] press Pressure at which to calculate RS
|
||||
* value.
|
||||
*
|
||||
* \param[in] temp Temperature at which to calculate RS
|
||||
* value.
|
||||
*
|
||||
* \return Dissolved gas-oil ratio (RS) at depth @c
|
||||
* depth and pressure @c press.
|
||||
*/
|
||||
virtual double operator()(const double depth,
|
||||
const double press,
|
||||
const double temp,
|
||||
const double sat = 0.0) const = 0;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Type that implements "no phase mixing" policy.
|
||||
*/
|
||||
class NoMixing : public RsFunction {
|
||||
public:
|
||||
/**
|
||||
* Function call.
|
||||
*
|
||||
* \param[in] depth Depth at which to calculate RS
|
||||
* value.
|
||||
*
|
||||
* \param[in] press Pressure at which to calculate RS
|
||||
* value.
|
||||
*
|
||||
* \param[in] temp Temperature at which to calculate RS
|
||||
* value.
|
||||
*
|
||||
* \return Dissolved gas-oil ratio (RS) at depth @c
|
||||
* depth and pressure @c press. In "no mixing
|
||||
* policy", this is identically zero.
|
||||
*/
|
||||
double
|
||||
operator()(const double /* depth */,
|
||||
const double /* press */,
|
||||
const double /* temp */,
|
||||
const double /* sat */ = 0.0) const
|
||||
{
|
||||
return 0.0;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Type that implements "dissolved gas-oil ratio"
|
||||
* tabulated as a function of depth policy. Data
|
||||
* typically taken from keyword 'RSVD'.
|
||||
*/
|
||||
template <class FluidSystem>
|
||||
class RsVD : public RsFunction {
|
||||
public:
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* \param[in] pvtRegionIdx The pvt region index
|
||||
* \param[in] depth Depth nodes.
|
||||
* \param[in] rs Dissolved gas-oil ratio at @c depth.
|
||||
*/
|
||||
RsVD(const int pvtRegionIdx,
|
||||
const std::vector<double>& depth,
|
||||
const std::vector<double>& rs)
|
||||
: pvtRegionIdx_(pvtRegionIdx)
|
||||
, depth_(depth)
|
||||
, rs_(rs)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Function call.
|
||||
*
|
||||
* \param[in] depth Depth at which to calculate RS
|
||||
* value.
|
||||
*
|
||||
* \param[in] press Pressure at which to calculate RS
|
||||
* value.
|
||||
*
|
||||
* \param[in] temp Temperature at which to calculate RS
|
||||
* value.
|
||||
*
|
||||
* \return Dissolved gas-oil ratio (RS) at depth @c
|
||||
* depth and pressure @c press.
|
||||
*/
|
||||
double
|
||||
operator()(const double depth,
|
||||
const double press,
|
||||
const double temp,
|
||||
const double sat_gas = 0.0) const
|
||||
{
|
||||
if (sat_gas > 0.0) {
|
||||
return satRs(press, temp);
|
||||
} else {
|
||||
return std::min(satRs(press, temp), linearInterpolationNoExtrapolation(depth_, rs_, depth));
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
const int pvtRegionIdx_;
|
||||
std::vector<double> depth_; /**< Depth nodes */
|
||||
std::vector<double> rs_; /**< Dissolved gas-oil ratio */
|
||||
|
||||
double satRs(const double press, const double temp) const
|
||||
{
|
||||
return FluidSystem::oilPvt().saturatedGasDissolutionFactor(pvtRegionIdx_, temp, press);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Type that implements "vaporized oil-gas ratio"
|
||||
* tabulated as a function of depth policy. Data
|
||||
* typically taken from keyword 'RVVD'.
|
||||
*/
|
||||
template <class FluidSystem>
|
||||
class RvVD : public RsFunction {
|
||||
public:
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* \param[in] pvtRegionIdx The pvt region index
|
||||
* \param[in] depth Depth nodes.
|
||||
* \param[in] rv Dissolved gas-oil ratio at @c depth.
|
||||
*/
|
||||
RvVD(const int pvtRegionIdx,
|
||||
const std::vector<double>& depth,
|
||||
const std::vector<double>& rv)
|
||||
: pvtRegionIdx_(pvtRegionIdx)
|
||||
, depth_(depth)
|
||||
, rv_(rv)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Function call.
|
||||
*
|
||||
* \param[in] depth Depth at which to calculate RV
|
||||
* value.
|
||||
*
|
||||
* \param[in] press Pressure at which to calculate RV
|
||||
* value.
|
||||
*
|
||||
* \param[in] temp Temperature at which to calculate RV
|
||||
* value.
|
||||
*
|
||||
* \return Vaporized oil-gas ratio (RV) at depth @c
|
||||
* depth and pressure @c press.
|
||||
*/
|
||||
double
|
||||
operator()(const double depth,
|
||||
const double press,
|
||||
const double temp,
|
||||
const double sat_oil = 0.0 ) const
|
||||
{
|
||||
if (std::abs(sat_oil) > 1e-16) {
|
||||
return satRv(press, temp);
|
||||
} else {
|
||||
return std::min(satRv(press, temp), linearInterpolationNoExtrapolation(depth_, rv_, depth));
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
const int pvtRegionIdx_;
|
||||
std::vector<double> depth_; /**< Depth nodes */
|
||||
std::vector<double> rv_; /**< Vaporized oil-gas ratio */
|
||||
|
||||
double satRv(const double press, const double temp) const
|
||||
{
|
||||
return FluidSystem::gasPvt().saturatedOilVaporizationFactor(pvtRegionIdx_, temp, press);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Class that implements "dissolved gas-oil ratio" (Rs)
|
||||
* as function of depth and pressure as follows:
|
||||
*
|
||||
* 1. The Rs at the gas-oil contact is equal to the
|
||||
* saturated Rs value, Rs_sat_contact.
|
||||
*
|
||||
* 2. The Rs elsewhere is equal to Rs_sat_contact, but
|
||||
* constrained to the saturated value as given by the
|
||||
* local pressure.
|
||||
*
|
||||
* This should yield Rs-values that are constant below the
|
||||
* contact, and decreasing above the contact.
|
||||
*/
|
||||
template <class FluidSystem>
|
||||
class RsSatAtContact : public RsFunction {
|
||||
public:
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* \param[in] pvtRegionIdx The pvt region index
|
||||
* \param[in] p_contact oil pressure at the contact
|
||||
* \param[in] T_contact temperature at the contact
|
||||
*/
|
||||
RsSatAtContact(const int pvtRegionIdx, const double p_contact, const double T_contact)
|
||||
: pvtRegionIdx_(pvtRegionIdx)
|
||||
{
|
||||
rs_sat_contact_ = satRs(p_contact, T_contact);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function call.
|
||||
*
|
||||
* \param[in] depth Depth at which to calculate RS
|
||||
* value.
|
||||
*
|
||||
* \param[in] press Pressure at which to calculate RS
|
||||
* value.
|
||||
*
|
||||
* \param[in] temp Temperature at which to calculate RS
|
||||
* value.
|
||||
*
|
||||
* \return Dissolved gas-oil ratio (RS) at depth @c
|
||||
* depth and pressure @c press.
|
||||
*/
|
||||
double
|
||||
operator()(const double /* depth */,
|
||||
const double press,
|
||||
const double temp,
|
||||
const double sat_gas = 0.0) const
|
||||
{
|
||||
if (sat_gas > 0.0) {
|
||||
return satRs(press, temp);
|
||||
} else {
|
||||
return std::min(satRs(press, temp), rs_sat_contact_);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
const int pvtRegionIdx_;
|
||||
double rs_sat_contact_;
|
||||
|
||||
double satRs(const double press, const double temp) const
|
||||
{
|
||||
return FluidSystem::oilPvt().saturatedGasDissolutionFactor(pvtRegionIdx_, temp, press);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Class that implements "vaporized oil-gas ratio" (Rv)
|
||||
* as function of depth and pressure as follows:
|
||||
*
|
||||
* 1. The Rv at the gas-oil contact is equal to the
|
||||
* saturated Rv value, Rv_sat_contact.
|
||||
*
|
||||
* 2. The Rv elsewhere is equal to Rv_sat_contact, but
|
||||
* constrained to the saturated value as given by the
|
||||
* local pressure.
|
||||
*
|
||||
* This should yield Rv-values that are constant below the
|
||||
* contact, and decreasing above the contact.
|
||||
*/
|
||||
template <class FluidSystem>
|
||||
class RvSatAtContact : public RsFunction {
|
||||
public:
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* \param[in] pvtRegionIdx The pvt region index
|
||||
* \param[in] p_contact oil pressure at the contact
|
||||
* \param[in] T_contact temperature at the contact
|
||||
*/
|
||||
RvSatAtContact(const int pvtRegionIdx, const double p_contact, const double T_contact)
|
||||
:pvtRegionIdx_(pvtRegionIdx)
|
||||
{
|
||||
rv_sat_contact_ = satRv(p_contact, T_contact);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function call.
|
||||
*
|
||||
* \param[in] depth Depth at which to calculate RV
|
||||
* value.
|
||||
*
|
||||
* \param[in] press Pressure at which to calculate RV
|
||||
* value.
|
||||
*
|
||||
* \param[in] temp Temperature at which to calculate RV
|
||||
* value.
|
||||
*
|
||||
* \return Dissolved oil-gas ratio (RV) at depth @c
|
||||
* depth and pressure @c press.
|
||||
*/
|
||||
double
|
||||
operator()(const double /*depth*/,
|
||||
const double press,
|
||||
const double temp,
|
||||
const double sat_oil = 0.0) const
|
||||
{
|
||||
if (sat_oil > 0.0) {
|
||||
return satRv(press, temp);
|
||||
} else {
|
||||
return std::min(satRv(press, temp), rv_sat_contact_);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
const int pvtRegionIdx_;
|
||||
double rv_sat_contact_;
|
||||
|
||||
double satRv(const double press, const double temp) const
|
||||
{
|
||||
return FluidSystem::gasPvt().saturatedOilVaporizationFactor(pvtRegionIdx_, temp, press);;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Miscibility
|
||||
|
||||
/**
|
||||
* Aggregate information base of an equilibration region.
|
||||
*
|
||||
* Provides inquiry methods for retrieving depths of contacs
|
||||
* and pressure values as well as a means of calculating fluid
|
||||
* densities, dissolved gas-oil ratio and vapourised oil-gas
|
||||
* ratios.
|
||||
*
|
||||
* \tparam DensCalc Type that provides access to a phase
|
||||
* density calculation facility. Must implement an operator()
|
||||
* declared as
|
||||
* <CODE>
|
||||
* std::vector<double>
|
||||
* operator()(const double press,
|
||||
* const std::vector<double>& svol )
|
||||
* </CODE>
|
||||
* that calculates the phase densities of all phases in @c
|
||||
* svol at fluid pressure @c press.
|
||||
*/
|
||||
class EquilReg {
|
||||
public:
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* \param[in] rec Equilibration data of current region.
|
||||
* \param[in] rs Calculator of dissolved gas-oil ratio.
|
||||
* \param[in] rv Calculator of vapourised oil-gas ratio.
|
||||
* \param[in] pvtRegionIdx The pvt region index
|
||||
*/
|
||||
EquilReg(const EquilRecord& rec,
|
||||
std::shared_ptr<Miscibility::RsFunction> rs,
|
||||
std::shared_ptr<Miscibility::RsFunction> rv,
|
||||
const int pvtIdx)
|
||||
: rec_ (rec)
|
||||
, rs_ (rs)
|
||||
, rv_ (rv)
|
||||
, pvtIdx_ (pvtIdx)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Type of dissolved gas-oil ratio calculator.
|
||||
*/
|
||||
typedef Miscibility::RsFunction CalcDissolution;
|
||||
|
||||
/**
|
||||
* Type of vapourised oil-gas ratio calculator.
|
||||
*/
|
||||
typedef Miscibility::RsFunction CalcEvaporation;
|
||||
|
||||
/**
|
||||
* Datum depth in current region
|
||||
*/
|
||||
double datum() const { return this->rec_.datumDepth(); }
|
||||
|
||||
/**
|
||||
* Pressure at datum depth in current region.
|
||||
*/
|
||||
double pressure() const { return this->rec_.datumDepthPressure(); }
|
||||
|
||||
/**
|
||||
* Depth of water-oil contact.
|
||||
*/
|
||||
double zwoc() const { return this->rec_.waterOilContactDepth(); }
|
||||
|
||||
/**
|
||||
* water-oil capillary pressure at water-oil contact.
|
||||
*
|
||||
* \return P_o - P_w at WOC.
|
||||
*/
|
||||
double pcow_woc() const { return this->rec_.waterOilContactCapillaryPressure(); }
|
||||
|
||||
/**
|
||||
* Depth of gas-oil contact.
|
||||
*/
|
||||
double zgoc() const { return this->rec_.gasOilContactDepth(); }
|
||||
|
||||
/**
|
||||
* Gas-oil capillary pressure at gas-oil contact.
|
||||
*
|
||||
* \return P_g - P_o at GOC.
|
||||
*/
|
||||
double pcgo_goc() const { return this->rec_.gasOilContactCapillaryPressure(); }
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve dissolved gas-oil ratio calculator of current
|
||||
* region.
|
||||
*/
|
||||
const CalcDissolution&
|
||||
dissolutionCalculator() const { return *this->rs_; }
|
||||
|
||||
/**
|
||||
* Retrieve vapourised oil-gas ratio calculator of current
|
||||
* region.
|
||||
*/
|
||||
const CalcEvaporation&
|
||||
evaporationCalculator() const { return *this->rv_; }
|
||||
|
||||
/**
|
||||
* Retrieve pvtIdx of the region.
|
||||
*/
|
||||
int pvtIdx() const { return this->pvtIdx_; }
|
||||
|
||||
|
||||
private:
|
||||
EquilRecord rec_; /**< Equilibration data */
|
||||
std::shared_ptr<Miscibility::RsFunction> rs_; /**< RS calculator */
|
||||
std::shared_ptr<Miscibility::RsFunction> rv_; /**< RV calculator */
|
||||
const int pvtIdx_;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/// Functor for inverting capillary pressure function.
|
||||
/// Function represented is
|
||||
/// f(s) = pc(s) - target_pc
|
||||
template <class FluidSystem, class MaterialLaw, class MaterialLawManager>
|
||||
struct PcEq
|
||||
{
|
||||
PcEq(const MaterialLawManager& materialLawManager,
|
||||
const int phase,
|
||||
const int cell,
|
||||
const double target_pc)
|
||||
: materialLawManager_(materialLawManager),
|
||||
phase_(phase),
|
||||
cell_(cell),
|
||||
target_pc_(target_pc)
|
||||
{
|
||||
|
||||
}
|
||||
double operator()(double s) const
|
||||
{
|
||||
const auto& matParams = materialLawManager_.materialLawParams(cell_);
|
||||
SatOnlyFluidState fluidState;
|
||||
fluidState.setSaturation(FluidSystem::waterPhaseIdx, 0.0);
|
||||
fluidState.setSaturation(FluidSystem::oilPhaseIdx, 0.0);
|
||||
fluidState.setSaturation(FluidSystem::gasPhaseIdx, 0.0);
|
||||
fluidState.setSaturation(phase_, s);
|
||||
|
||||
double pc[FluidSystem::numPhases];
|
||||
std::fill(pc, pc + FluidSystem::numPhases, 0.0);
|
||||
MaterialLaw::capillaryPressures(pc, matParams, fluidState);
|
||||
double sign = (phase_ == FluidSystem::waterPhaseIdx)? -1.0 : 1.0;
|
||||
double pcPhase = pc[FluidSystem::oilPhaseIdx] + sign * pc[phase_];
|
||||
return pcPhase - target_pc_;
|
||||
}
|
||||
private:
|
||||
const MaterialLawManager& materialLawManager_;
|
||||
const int phase_;
|
||||
const int cell_;
|
||||
const double target_pc_;
|
||||
};
|
||||
|
||||
template <class FluidSystem, class MaterialLawManager>
|
||||
double minSaturations(const MaterialLawManager& materialLawManager, const int phase, const int cell) {
|
||||
const auto& scaledDrainageInfo =
|
||||
materialLawManager.oilWaterScaledEpsInfoDrainage(cell);
|
||||
|
||||
// Find minimum and maximum saturations.
|
||||
switch(phase) {
|
||||
case FluidSystem::waterPhaseIdx :
|
||||
{
|
||||
return scaledDrainageInfo.Swl;
|
||||
break;
|
||||
}
|
||||
case FluidSystem::gasPhaseIdx :
|
||||
{
|
||||
return scaledDrainageInfo.Sgl;
|
||||
break;
|
||||
}
|
||||
case FluidSystem::oilPhaseIdx :
|
||||
{
|
||||
OPM_THROW(std::runtime_error, "Min saturation not implemented for oil phase.");
|
||||
break;
|
||||
}
|
||||
default: OPM_THROW(std::runtime_error, "Unknown phaseIdx .");
|
||||
}
|
||||
return -1.0;
|
||||
}
|
||||
|
||||
template <class FluidSystem, class MaterialLawManager>
|
||||
double maxSaturations(const MaterialLawManager& materialLawManager, const int phase, const int cell) {
|
||||
const auto& scaledDrainageInfo =
|
||||
materialLawManager.oilWaterScaledEpsInfoDrainage(cell);
|
||||
|
||||
// Find minimum and maximum saturations.
|
||||
switch(phase) {
|
||||
case FluidSystem::waterPhaseIdx :
|
||||
{
|
||||
return scaledDrainageInfo.Swu;
|
||||
break;
|
||||
}
|
||||
case FluidSystem::gasPhaseIdx :
|
||||
{
|
||||
return scaledDrainageInfo.Sgu;
|
||||
break;
|
||||
}
|
||||
case FluidSystem::oilPhaseIdx :
|
||||
{
|
||||
OPM_THROW(std::runtime_error, "Max saturation not implemented for oil phase.");
|
||||
break;
|
||||
}
|
||||
default: OPM_THROW(std::runtime_error, "Unknown phaseIdx .");
|
||||
}
|
||||
return -1.0;
|
||||
}
|
||||
|
||||
|
||||
/// Compute saturation of some phase corresponding to a given
|
||||
/// capillary pressure.
|
||||
template <class FluidSystem, class MaterialLaw, class MaterialLawManager >
|
||||
inline double satFromPc(const MaterialLawManager& materialLawManager,
|
||||
const int phase,
|
||||
const int cell,
|
||||
const double target_pc,
|
||||
const bool increasing = false)
|
||||
{
|
||||
// Find minimum and maximum saturations.
|
||||
const double s0 = increasing ? maxSaturations<FluidSystem>(materialLawManager, phase, cell) : minSaturations<FluidSystem>(materialLawManager, phase, cell);
|
||||
const double s1 = increasing ? minSaturations<FluidSystem>(materialLawManager, phase, cell) : maxSaturations<FluidSystem>(materialLawManager, phase, cell);
|
||||
|
||||
// Create the equation f(s) = pc(s) - target_pc
|
||||
const PcEq<FluidSystem, MaterialLaw, MaterialLawManager> f(materialLawManager, phase, cell, target_pc);
|
||||
const double f0 = f(s0);
|
||||
const double f1 = f(s1);
|
||||
|
||||
if (f0 <= 0.0) {
|
||||
return s0;
|
||||
} else if (f1 > 0.0) {
|
||||
return s1;
|
||||
} else {
|
||||
const int max_iter = 60;
|
||||
const double tol = 1e-6;
|
||||
int iter_used = -1;
|
||||
typedef RegulaFalsi<ThrowOnError> ScalarSolver;
|
||||
const double sol = ScalarSolver::solve(f, std::min(s0, s1), std::max(s0, s1), max_iter, tol, iter_used);
|
||||
return sol;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Functor for inverting a sum of capillary pressure functions.
|
||||
/// Function represented is
|
||||
/// f(s) = pc1(s) + pc2(1 - s) - target_pc
|
||||
template <class FluidSystem, class MaterialLaw, class MaterialLawManager>
|
||||
struct PcEqSum
|
||||
{
|
||||
PcEqSum(const MaterialLawManager& materialLawManager,
|
||||
const int phase1,
|
||||
const int phase2,
|
||||
const int cell,
|
||||
const double target_pc)
|
||||
: materialLawManager_(materialLawManager),
|
||||
phase1_(phase1),
|
||||
phase2_(phase2),
|
||||
cell_(cell),
|
||||
target_pc_(target_pc)
|
||||
{
|
||||
}
|
||||
double operator()(double s) const
|
||||
{
|
||||
const auto& matParams = materialLawManager_.materialLawParams(cell_);
|
||||
SatOnlyFluidState fluidState;
|
||||
fluidState.setSaturation(FluidSystem::waterPhaseIdx, 0.0);
|
||||
fluidState.setSaturation(FluidSystem::oilPhaseIdx, 0.0);
|
||||
fluidState.setSaturation(FluidSystem::gasPhaseIdx, 0.0);
|
||||
fluidState.setSaturation(phase1_, s);
|
||||
fluidState.setSaturation(phase2_, 1.0 - s);
|
||||
|
||||
double pc[FluidSystem::numPhases];
|
||||
std::fill(pc, pc + FluidSystem::numPhases, 0.0);
|
||||
|
||||
MaterialLaw::capillaryPressures(pc, matParams, fluidState);
|
||||
double sign1 = (phase1_ == FluidSystem::waterPhaseIdx)? -1.0 : 1.0;
|
||||
double pc1 = pc[FluidSystem::oilPhaseIdx] + sign1 * pc[phase1_];
|
||||
double sign2 = (phase2_ == FluidSystem::waterPhaseIdx)? -1.0 : 1.0;
|
||||
double pc2 = pc[FluidSystem::oilPhaseIdx] + sign2 * pc[phase2_];
|
||||
return pc1 + pc2 - target_pc_;
|
||||
}
|
||||
private:
|
||||
const MaterialLawManager& materialLawManager_;
|
||||
const int phase1_;
|
||||
const int phase2_;
|
||||
const int cell_;
|
||||
const double target_pc_;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
/// Compute saturation of some phase corresponding to a given
|
||||
/// capillary pressure, where the capillary pressure function
|
||||
/// is given as a sum of two other functions.
|
||||
template <class FluidSystem, class MaterialLaw, class MaterialLawManager>
|
||||
inline double satFromSumOfPcs(const MaterialLawManager& materialLawManager,
|
||||
const int phase1,
|
||||
const int phase2,
|
||||
const int cell,
|
||||
const double target_pc)
|
||||
{
|
||||
// Find minimum and maximum saturations.
|
||||
const double smin = minSaturations<FluidSystem>(materialLawManager, phase1, cell);
|
||||
const double smax = maxSaturations<FluidSystem>(materialLawManager, phase1, cell);
|
||||
|
||||
// Create the equation f(s) = pc1(s) + pc2(1-s) - target_pc
|
||||
const PcEqSum<FluidSystem, MaterialLaw, MaterialLawManager> f(materialLawManager, phase1, phase2, cell, target_pc);
|
||||
const double f0 = f(smin);
|
||||
const double f1 = f(smax);
|
||||
if (f0 <= 0.0) {
|
||||
return smin;
|
||||
} else if (f1 > 0.0) {
|
||||
return smax;
|
||||
} else {
|
||||
const int max_iter = 30;
|
||||
const double tol = 1e-6;
|
||||
int iter_used = -1;
|
||||
typedef RegulaFalsi<ThrowOnError> ScalarSolver;
|
||||
const double sol = ScalarSolver::solve(f, smin, smax, max_iter, tol, iter_used);
|
||||
return sol;
|
||||
}
|
||||
}
|
||||
|
||||
/// Compute saturation from depth. Used for constant capillary pressure function
|
||||
template <class FluidSystem, class MaterialLaw, class MaterialLawManager>
|
||||
inline double satFromDepth(const MaterialLawManager& materialLawManager,
|
||||
const double cellDepth,
|
||||
const double contactDepth,
|
||||
const int phase,
|
||||
const int cell,
|
||||
const bool increasing = false)
|
||||
{
|
||||
const double s0 = increasing ? maxSaturations<FluidSystem>(materialLawManager, phase, cell) : minSaturations<FluidSystem>(materialLawManager, phase, cell);
|
||||
const double s1 = increasing ? minSaturations<FluidSystem>(materialLawManager, phase, cell) : maxSaturations<FluidSystem>(materialLawManager, phase, cell);
|
||||
|
||||
if (cellDepth < contactDepth){
|
||||
return s0;
|
||||
} else {
|
||||
return s1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// Return true if capillary pressure function is constant
|
||||
template <class FluidSystem, class MaterialLaw, class MaterialLawManager>
|
||||
inline bool isConstPc(const MaterialLawManager& materialLawManager,
|
||||
const int phase,
|
||||
const int cell)
|
||||
{
|
||||
// Create the equation f(s) = pc(s);
|
||||
const PcEq<FluidSystem, MaterialLaw, MaterialLawManager> f(materialLawManager, phase, cell, 0);
|
||||
const double f0 = f(minSaturations<FluidSystem>(materialLawManager, phase, cell));
|
||||
const double f1 = f(maxSaturations<FluidSystem>(materialLawManager, phase, cell));
|
||||
return std::abs(f0 - f1) < std::numeric_limits<double>::epsilon();
|
||||
}
|
||||
|
||||
} // namespace Equil
|
||||
} // namespace Opm
|
||||
|
||||
|
||||
#endif // OPM_EQUILIBRATIONHELPERS_HEADER_INCLUDED
|
92
opm/core/simulator/ExplicitArraysFluidState.hpp
Normal file
92
opm/core/simulator/ExplicitArraysFluidState.hpp
Normal file
@ -0,0 +1,92 @@
|
||||
/*
|
||||
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_EXPLICIT_ARRAYS_FLUID_STATE_HEADER_INCLUDED
|
||||
#define OPM_EXPLICIT_ARRAYS_FLUID_STATE_HEADER_INCLUDED
|
||||
|
||||
#include <opm/core/props/BlackoilPhases.hpp>
|
||||
|
||||
#include <array>
|
||||
|
||||
namespace Opm
|
||||
{
|
||||
|
||||
/*!
|
||||
* \brief This is a fluid state which translates global arrays and translates them to a
|
||||
* subset of the fluid state API.
|
||||
*
|
||||
* This class is similar to Opm::BlackoilStateToFluidState.
|
||||
*/
|
||||
class ExplicitArraysFluidState
|
||||
{
|
||||
public:
|
||||
typedef double Scalar;
|
||||
enum { numPhases = BlackoilPhases::MaxNumPhases };
|
||||
|
||||
explicit ExplicitArraysFluidState(const PhaseUsage& phaseUsage)
|
||||
: phaseUsage_(phaseUsage)
|
||||
{}
|
||||
|
||||
/*!
|
||||
* \brief Sets the currently used array index.
|
||||
*
|
||||
* After calling this, the values returned by the other methods are specific for this
|
||||
* index.
|
||||
*/
|
||||
void setIndex(unsigned arrayIdx)
|
||||
{
|
||||
int np = phaseUsage_.num_phases;
|
||||
for (int phaseIdx = 0; phaseIdx < BlackoilPhases::MaxNumPhases; ++phaseIdx) {
|
||||
if (!phaseUsage_.phase_used[phaseIdx]) {
|
||||
sats_[phaseIdx] = 0.0;
|
||||
}
|
||||
else {
|
||||
sats_[phaseIdx] = saturations_[np*arrayIdx + phaseUsage_.phase_pos[phaseIdx]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Set the array containing the phase saturations.
|
||||
*
|
||||
* This array is supposed to be of size numPhases*size and is not allowed to be
|
||||
* deleted while the ExplicitArraysFluidState object is alive. This class assumes
|
||||
* that the saturations of all phase saturations for a point are consequtive, i.e.,
|
||||
* in the array the saturations cycle fastest.
|
||||
*/
|
||||
void setSaturationArray(const double* saturations)
|
||||
{ saturations_ = saturations; }
|
||||
|
||||
/*!
|
||||
* \brief Returns the saturation of a phase for the current cell index.
|
||||
*/
|
||||
Scalar saturation(int phaseIdx) const
|
||||
{ return sats_[phaseIdx]; }
|
||||
|
||||
// TODO (?) temperature, pressure, composition, etc
|
||||
|
||||
private:
|
||||
const PhaseUsage phaseUsage_;
|
||||
const double* saturations_;
|
||||
std::array<Scalar, BlackoilPhases::MaxNumPhases> sats_;
|
||||
};
|
||||
|
||||
} // namespace Opm
|
||||
|
||||
#endif // OPM_SIMULATORTIMER_HEADER_INCLUDED
|
111
opm/core/simulator/ExplicitArraysSatDerivativesFluidState.hpp
Normal file
111
opm/core/simulator/ExplicitArraysSatDerivativesFluidState.hpp
Normal file
@ -0,0 +1,111 @@
|
||||
/*
|
||||
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_EXPLICIT_ARRAYS_SAT_DERIVATIVES_FLUID_STATE_HEADER_INCLUDED
|
||||
#define OPM_EXPLICIT_ARRAYS_SAT_DERIVATIVES_FLUID_STATE_HEADER_INCLUDED
|
||||
|
||||
#include <opm/material/densead/Evaluation.hpp>
|
||||
#include <opm/material/densead/Math.hpp>
|
||||
|
||||
#include <opm/core/props/BlackoilPhases.hpp>
|
||||
|
||||
#include <array>
|
||||
|
||||
namespace Opm
|
||||
{
|
||||
|
||||
/*!
|
||||
* \brief This is a fluid state which translates global arrays and translates them to a
|
||||
* subset of the fluid state API.
|
||||
*
|
||||
* This class is similar to Opm::ExplicitArraysFluidState except for the fact that it
|
||||
* uses opm-material's local automatic differentiation framework to allow implicit
|
||||
* treatment of saturation derivatives.
|
||||
*/
|
||||
class ExplicitArraysSatDerivativesFluidState
|
||||
{
|
||||
public:
|
||||
enum { numPhases = BlackoilPhases::MaxNumPhases };
|
||||
enum { numComponents = 3 };
|
||||
|
||||
typedef Opm::DenseAd::Evaluation<double, numPhases> Evaluation;
|
||||
typedef Evaluation Scalar;
|
||||
|
||||
ExplicitArraysSatDerivativesFluidState(const PhaseUsage& phaseUsage)
|
||||
: phaseUsage_(phaseUsage)
|
||||
{
|
||||
globalSaturationArray_ = 0;
|
||||
|
||||
// initialize the evaluation objects for the saturations
|
||||
for (int phaseIdx = 0; phaseIdx < numPhases; ++ phaseIdx) {
|
||||
saturation_[phaseIdx] = Evaluation::createVariable(0.0, phaseIdx);
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Sets the currently used array index.
|
||||
*
|
||||
* After calling this, the values returned by the other methods are specific for this
|
||||
* index.
|
||||
*/
|
||||
void setIndex(unsigned arrayIdx)
|
||||
{
|
||||
int np = phaseUsage_.num_phases;
|
||||
|
||||
// copy the saturations values from the global value. the derivatives do not need
|
||||
// to be modified for these...
|
||||
for (int phaseIdx = 0; phaseIdx < numPhases; ++ phaseIdx) {
|
||||
if (!phaseUsage_.phase_used[phaseIdx]) {
|
||||
saturation_[phaseIdx].setValue(0.0);
|
||||
}
|
||||
else {
|
||||
saturation_[phaseIdx].setValue(globalSaturationArray_[np*arrayIdx + phaseUsage_.phase_pos[phaseIdx]]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Set the array containing the phase saturations.
|
||||
*
|
||||
* This array is supposed to be of size numPhases*size and is not allowed to be
|
||||
* deleted while the ExplicitArraysFluidState object is alive. This class assumes
|
||||
* that the saturations of all phase saturations for a point are consequtive, i.e.,
|
||||
* in the array the saturations cycle fastest.
|
||||
*/
|
||||
void setSaturationArray(const double* saturations)
|
||||
{ globalSaturationArray_ = saturations; }
|
||||
|
||||
/*!
|
||||
* \brief Returns the saturation of a phase for the current cell index.
|
||||
*/
|
||||
const Evaluation& saturation(int phaseIdx) const
|
||||
{ return saturation_[phaseIdx]; }
|
||||
|
||||
// TODO (?) temperature, pressure, composition, etc
|
||||
|
||||
private:
|
||||
const PhaseUsage phaseUsage_;
|
||||
const double* globalSaturationArray_;
|
||||
|
||||
std::array<Evaluation, numPhases> saturation_;
|
||||
};
|
||||
|
||||
} // namespace Opm
|
||||
|
||||
#endif
|
164
opm/core/simulator/SimulatorReport.cpp
Normal file
164
opm/core/simulator/SimulatorReport.cpp
Normal file
@ -0,0 +1,164 @@
|
||||
/*
|
||||
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/simulator/SimulatorReport.hpp>
|
||||
#include <ostream>
|
||||
|
||||
namespace Opm
|
||||
{
|
||||
SimulatorReport::SimulatorReport(bool verbose)
|
||||
: pressure_time(0.0),
|
||||
transport_time(0.0),
|
||||
total_time(0.0),
|
||||
solver_time(0.0),
|
||||
assemble_time(0.0),
|
||||
linear_solve_time(0.0),
|
||||
update_time(0.0),
|
||||
output_write_time(0.0),
|
||||
total_well_iterations(0),
|
||||
total_linearizations( 0 ),
|
||||
total_newton_iterations( 0 ),
|
||||
total_linear_iterations( 0 ),
|
||||
converged(false),
|
||||
verbose_(verbose)
|
||||
{
|
||||
}
|
||||
|
||||
void SimulatorReport::operator+=(const SimulatorReport& sr)
|
||||
{
|
||||
pressure_time += sr.pressure_time;
|
||||
transport_time += sr.transport_time;
|
||||
linear_solve_time += sr.linear_solve_time;
|
||||
solver_time += sr.solver_time;
|
||||
assemble_time += sr.assemble_time;
|
||||
update_time += sr.update_time;
|
||||
output_write_time += sr.output_write_time;
|
||||
total_time += sr.total_time;
|
||||
total_well_iterations += sr.total_well_iterations;
|
||||
total_linearizations += sr.total_linearizations;
|
||||
total_newton_iterations += sr.total_newton_iterations;
|
||||
total_linear_iterations += sr.total_linear_iterations;
|
||||
}
|
||||
|
||||
void SimulatorReport::report(std::ostream& os)
|
||||
{
|
||||
if ( verbose_ )
|
||||
{
|
||||
os << "Total time taken: " << total_time
|
||||
<< "\n Pressure time: " << pressure_time
|
||||
<< "\n Transport time: " << transport_time
|
||||
<< "\n Overall Newton Iterations: " << total_newton_iterations
|
||||
<< "\n Overall Linear Iterations: " << total_linear_iterations
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void SimulatorReport::reportFullyImplicit(std::ostream& os, const SimulatorReport* failureReport)
|
||||
{
|
||||
if ( verbose_ )
|
||||
{
|
||||
double t = total_time;
|
||||
os << "Total time (seconds): " << t;
|
||||
os << std::endl;
|
||||
|
||||
t = solver_time + (failureReport ? failureReport->solver_time : 0.0);
|
||||
os << "Solver time (seconds): " << t;
|
||||
os << std::endl;
|
||||
|
||||
if (assemble_time > 0.0 || linear_solve_time > 0.0) {
|
||||
t = assemble_time + (failureReport ? failureReport->assemble_time : 0.0);
|
||||
os << " Assembly time (seconds): " << t;
|
||||
if (failureReport) {
|
||||
os << " (Failed: " << failureReport->assemble_time << "; "
|
||||
<< 100*failureReport->assemble_time/t << "%)";
|
||||
}
|
||||
os << std::endl;
|
||||
|
||||
t = linear_solve_time + (failureReport ? failureReport->linear_solve_time : 0.0);
|
||||
os << " Linear solve time (seconds): " << t;
|
||||
if (failureReport) {
|
||||
os << " (Failed: " << failureReport->linear_solve_time << "; "
|
||||
<< 100*failureReport->linear_solve_time/t << "%)";
|
||||
}
|
||||
os << std::endl;
|
||||
|
||||
t = update_time + (failureReport ? failureReport->update_time : 0.0);
|
||||
os << " Update time (seconds): " << t;
|
||||
if (failureReport) {
|
||||
os << " (Failed: " << failureReport->update_time << "; "
|
||||
<< 100*failureReport->update_time/t << "%)";
|
||||
}
|
||||
os << std::endl;
|
||||
|
||||
t = output_write_time + (failureReport ? failureReport->output_write_time : 0.0);
|
||||
os << " Output write time (seconds): " << t;
|
||||
os << std::endl;
|
||||
|
||||
}
|
||||
|
||||
int n = total_well_iterations + (failureReport ? failureReport->total_well_iterations : 0);
|
||||
os << "Overall Well Iterations: " << n;
|
||||
if (failureReport) {
|
||||
os << " (Failed: " << failureReport->total_well_iterations << "; "
|
||||
<< 100.0*failureReport->total_well_iterations/n << "%)";
|
||||
}
|
||||
os << std::endl;
|
||||
|
||||
n = total_linearizations + (failureReport ? failureReport->total_linearizations : 0);
|
||||
os << "Overall Linearizations: " << n;
|
||||
if (failureReport) {
|
||||
os << " (Failed: " << failureReport->total_linearizations << "; "
|
||||
<< 100.0*failureReport->total_linearizations/n << "%)";
|
||||
}
|
||||
os << std::endl;
|
||||
|
||||
n = total_newton_iterations + (failureReport ? failureReport->total_newton_iterations : 0);
|
||||
os << "Overall Newton Iterations: " << n;
|
||||
if (failureReport) {
|
||||
os << " (Failed: " << failureReport->total_newton_iterations << "; "
|
||||
<< 100.0*failureReport->total_newton_iterations/n << "%)";
|
||||
}
|
||||
os << std::endl;
|
||||
|
||||
n = total_linear_iterations + (failureReport ? failureReport->total_linear_iterations : 0);
|
||||
os << "Overall Linear Iterations: " << n;
|
||||
if (failureReport) {
|
||||
os << " (Failed: " << failureReport->total_linear_iterations << "; "
|
||||
<< 100.0*failureReport->total_linear_iterations/n << "%)";
|
||||
}
|
||||
os << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void SimulatorReport::reportParam(std::ostream& os)
|
||||
{
|
||||
if ( verbose_ )
|
||||
{
|
||||
os << "/timing/total_time=" << total_time
|
||||
<< "\n/timing/pressure/total_time=" << pressure_time
|
||||
<< "\n/timing/transport/total_time=" << transport_time
|
||||
<< "\n/timing/newton/iterations=" << total_newton_iterations
|
||||
<< "\n/timing/linear/iterations=" << total_linear_iterations
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // namespace Opm
|
65
opm/core/simulator/SimulatorReport.hpp
Normal file
65
opm/core/simulator/SimulatorReport.hpp
Normal file
@ -0,0 +1,65 @@
|
||||
/*
|
||||
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_SIMULATORREPORT_HEADER_INCLUDED
|
||||
#define OPM_SIMULATORREPORT_HEADER_INCLUDED
|
||||
|
||||
#include <iosfwd>
|
||||
|
||||
namespace Opm
|
||||
{
|
||||
|
||||
/// A struct for returning timing data from a simulator to its caller.
|
||||
struct SimulatorReport
|
||||
{
|
||||
double pressure_time;
|
||||
double transport_time;
|
||||
double total_time;
|
||||
double solver_time;
|
||||
double assemble_time;
|
||||
double linear_solve_time;
|
||||
double update_time;
|
||||
double output_write_time;
|
||||
|
||||
unsigned int total_well_iterations;
|
||||
unsigned int total_linearizations;
|
||||
unsigned int total_newton_iterations;
|
||||
unsigned int total_linear_iterations;
|
||||
|
||||
bool converged;
|
||||
|
||||
/// Default constructor initializing all times to 0.0.
|
||||
SimulatorReport(bool verbose=true);
|
||||
/// Copy constructor
|
||||
SimulatorReport(const SimulatorReport&) = default;
|
||||
/// Increment this report's times by those in sr.
|
||||
void operator+=(const SimulatorReport& sr);
|
||||
/// Print a report to the given stream.
|
||||
void report(std::ostream& os);
|
||||
/// Print a report, leaving out the transport time.
|
||||
void reportFullyImplicit(std::ostream& os, const SimulatorReport* failedReport = nullptr);
|
||||
void reportParam(std::ostream& os);
|
||||
private:
|
||||
// Whether to print statistics to std::cout
|
||||
bool verbose_;
|
||||
};
|
||||
|
||||
} // namespace Opm
|
||||
|
||||
#endif // OPM_SIMULATORREPORT_HEADER_INCLUDED
|
33
opm/core/simulator/TwophaseState.cpp
Normal file
33
opm/core/simulator/TwophaseState.cpp
Normal file
@ -0,0 +1,33 @@
|
||||
/*
|
||||
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 <opm/core/simulator/TwophaseState.hpp>
|
||||
|
||||
namespace Opm
|
||||
{
|
||||
|
||||
TwophaseState::TwophaseState(size_t num_cells , size_t num_faces) :
|
||||
SimulationDataContainer( num_cells , num_faces , 2 )
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
35
opm/core/simulator/TwophaseState.hpp
Normal file
35
opm/core/simulator/TwophaseState.hpp
Normal file
@ -0,0 +1,35 @@
|
||||
/*
|
||||
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_TWOPHASESTATE_HEADER_INCLUDED
|
||||
#define OPM_TWOPHASESTATE_HEADER_INCLUDED
|
||||
|
||||
#include <opm/common/data/SimulationDataContainer.hpp>
|
||||
|
||||
namespace Opm
|
||||
{
|
||||
class TwophaseState : public SimulationDataContainer
|
||||
{
|
||||
public:
|
||||
TwophaseState(size_t num_cells , size_t num_faces);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
15
opm/core/simulator/TwophaseState_impl.hpp
Normal file
15
opm/core/simulator/TwophaseState_impl.hpp
Normal file
@ -0,0 +1,15 @@
|
||||
#ifndef OPM_TWOPHASESTATE_HEADER_INCLUDED
|
||||
#error Do not include this file directly!
|
||||
#endif
|
||||
|
||||
namespace Opm {
|
||||
|
||||
inline bool
|
||||
TwophaseState::equals (const SimulatorState& other,
|
||||
double epsilon) const {
|
||||
return dynamic_cast <const TwophaseState*>(&other)
|
||||
? SimulatorState::equals (other, epsilon)
|
||||
: false;
|
||||
}
|
||||
|
||||
} // 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