Import the remaining code from opm-core

This commit is contained in:
Arne Morten Kvarving 2018-01-17 15:18:56 +01:00
commit c03a980199
187 changed files with 44698 additions and 0 deletions

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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 */

View 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]);
}
}

View 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 */

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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
View 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;
}

View 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 */

View 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 */

View 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];
}
}
}

View 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 */

View 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: */

View 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

View 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: */

View 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 */

File diff suppressed because it is too large Load Diff

View 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 */

View 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 */

View 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 */

View 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);
}

View 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 */

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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 */

View 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 */

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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,
&regionIdx,
&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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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);
}

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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 )
{
}
}

View 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

View 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