mirror of
https://github.com/OPM/opm-upscaling.git
synced 2025-02-25 18:45:23 -06:00
400 lines
16 KiB
C++
400 lines
16 KiB
C++
// -*- tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
|
// vi: set ts=8 sw=4 et sts=4:
|
|
|
|
/*
|
|
Copyright 2010 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/>.
|
|
*/
|
|
|
|
/** @file upscale_perm.C
|
|
* @brief Upscales permeability
|
|
*
|
|
* Upscales permeability for a given Eclipse-model.
|
|
* Upscaling is performed for periodic, linear and
|
|
* fixed boundary conditions and output is ASCII to standard out.
|
|
*
|
|
* Input is Eclipse grid format specifying the corner-point
|
|
* grid (must be of shoebox-shape, but this condition is slightly relaxed
|
|
* on top and bottom surfaces).
|
|
*
|
|
* The input eclipse file must specify the permeability properties
|
|
* for each cell.
|
|
*
|
|
* If only PERMX is supplied, isotropic permeability is assumed in each cell
|
|
*
|
|
* If PERMX, PERMY and PERMZ are supplied, diagonal anisotropic permeability
|
|
* is assumed in each cell.
|
|
*
|
|
* If PERMXX, PERMYY, PERMZZ, PERMXY, PERMYZ and PERMZX are supplied,
|
|
* full-tensor (symmetric) anisotropic permeability is assumed in each cell.
|
|
*
|
|
*/
|
|
#include <config.h>
|
|
|
|
#include <opm/common/utility/platform_dependent/disable_warnings.h>
|
|
|
|
#include <dune/common/version.hh>
|
|
|
|
#include <dune/common/parallel/mpihelper.hh>
|
|
|
|
#include <opm/common/utility/platform_dependent/reenable_warnings.h>
|
|
|
|
#include <opm/upscaling/RelPermUtils.hpp>
|
|
#include <opm/upscaling/SinglePhaseUpscaler.hpp>
|
|
|
|
#include <ctime>
|
|
#include <fstream>
|
|
#include <iomanip>
|
|
#include <iostream>
|
|
#include <memory>
|
|
#include <sstream>
|
|
|
|
#include <sys/utsname.h>
|
|
|
|
using namespace std;
|
|
|
|
namespace {
|
|
|
|
void usage() {
|
|
cout << endl <<
|
|
"Usage: upscale_perm <options> <eclipsefile>" << endl <<
|
|
"where the options are:" << endl <<
|
|
"-output <string> -- filename for where to write upscaled values." << endl <<
|
|
" If not supplied, output will only go to " << endl <<
|
|
" the terminal (standard out)." << endl <<
|
|
"-bc <string> -- which boundary conditions to compute for. " << endl <<
|
|
" <string> may contain a combination of the" << endl <<
|
|
" letters lfp to compute linear (l), fixed (f) " << endl <<
|
|
" and periodic (p) boundary conditions." << endl <<
|
|
" Default: f (fixed boundary conditions)" << endl <<
|
|
"-minPerm <float> -- Minimum floating point value allowed for" << endl <<
|
|
" permeability. If zero, the problem is singular" << endl <<
|
|
" Default 1e-9. Unit Millidarcy." << endl;
|
|
}
|
|
|
|
/**
|
|
@brief Upscales permeability
|
|
|
|
@param varnum Number of input arguments
|
|
@param vararg Input arguments
|
|
@return int
|
|
*/
|
|
int upscale(int varnum, char** vararg) {
|
|
Dune::MPIHelper::instance(varnum, vararg);
|
|
if (varnum == 1) { // If no arguments supplied ("upscale_perm" is the first argument)
|
|
cout << "Error: No eclipsefile provided" << endl;
|
|
usage();
|
|
exit(1);
|
|
}
|
|
map<string,string> options;
|
|
options.insert(make_pair("output", "")); // If this is set, output goes to screen and to this file
|
|
options.insert(make_pair("bc", "f")); // Fixed boundary conditions are default
|
|
options.insert(make_pair("minPerm", "1e-9")); // Minimum allowable permeability value (for diagonal tensor entries)
|
|
|
|
options.insert(make_pair("linsolver_tolerance", "1e-8")); // residual tolerance for linear solver
|
|
options.insert(make_pair("linsolver_verbosity", "0")); // verbosity level for linear solver
|
|
options.insert(make_pair("linsolver_max_iterations", "0")); // Maximum number of iterations allow, specify 0 for default
|
|
options.insert(make_pair("linsolver_prolongate_factor", "1.0")); // Factor to scale the prolongate coarse grid correction
|
|
options.insert(make_pair("linsolver_type", "3")); // type of linear solver: 0 = ILU/BiCGStab, 1 = AMG/CG, 2 = KAMG/CG, 3 = FastAMG/CG
|
|
options.insert(make_pair("linsolver_smooth_steps", "1")); // Number of pre and postsmoothing steps for AMG
|
|
|
|
// Parse options from command line
|
|
int eclipseindex = 1; // Index for the eclipsefile in the command line options
|
|
|
|
for (int argidx = 1; argidx < varnum - 1; argidx += 2) {
|
|
string searchfor = string(vararg[argidx]).substr(1); //Chop off leading '-'
|
|
if (string(vararg[argidx]).substr(0,1) != "-") {
|
|
cout << "Error: Options must come before eclipse file" << endl;
|
|
usage();
|
|
exit(1);
|
|
}
|
|
// Check if it is a match
|
|
if (options.count(searchfor) == 1) {
|
|
options[searchfor] = string(vararg[argidx+1]);
|
|
cout << "Parsed command line option: " << searchfor << " := " << vararg[argidx+1] << endl;
|
|
eclipseindex += 2;
|
|
}
|
|
}
|
|
|
|
const char* ECLIPSEFILENAME(vararg[eclipseindex]);
|
|
|
|
|
|
// Test if filename exists and is readable
|
|
ifstream eclipsefile(ECLIPSEFILENAME, ios::in);
|
|
if (eclipsefile.fail()) {
|
|
cerr << "Error: Filename " << ECLIPSEFILENAME << " not found or not readable." << endl;
|
|
usage();
|
|
exit(1);
|
|
}
|
|
eclipsefile.close();
|
|
|
|
// Check validity of boundary conditions chosen, and make booleans
|
|
// for boundary conditions, this allows more readable code later
|
|
bool isFixed, isLinear, isPeriodic;
|
|
isFixed = isLinear = isPeriodic = false;
|
|
|
|
// Read in default or user-specified boundary conditions:
|
|
string boundcond(options["bc"]);
|
|
|
|
// Length of string must be between 1 and 3:
|
|
if (boundcond.length()>= 1 && boundcond.length() <= 3) {
|
|
if (boundcond.find(string("p")) < 3) {
|
|
isPeriodic = true;
|
|
}
|
|
if (boundcond.find(string("f")) < 3) {
|
|
isFixed = true;
|
|
}
|
|
if (boundcond.find(string("l")) < 3) {
|
|
isLinear = true;
|
|
}
|
|
|
|
// If no boundary conditions are set now, issue error:
|
|
if (!isFixed && !isLinear && !isPeriodic) {
|
|
cerr << "Error: No boundary conditions specified: " << boundcond << endl;
|
|
usage();
|
|
exit(1);
|
|
}
|
|
}
|
|
else {
|
|
cerr << "Error: Syntax error in specifying boundary conditions: " << boundcond << endl;
|
|
usage();
|
|
exit(1);
|
|
}
|
|
|
|
|
|
// Variables for timing/profiling
|
|
clock_t start, finish;
|
|
double timeused = 0; // reusable variable
|
|
double timeused_periodic_tesselation = 0, timeused_nonperiodic_tesselation = 0;
|
|
double timeused_periodic = 0, timeused_fixed = 0, timeused_linear = 0;
|
|
|
|
cout << endl;
|
|
|
|
// Storage for upscaled results:
|
|
using Opm::SinglePhaseUpscaler;
|
|
typedef SinglePhaseUpscaler::permtensor_t Matrix;
|
|
Matrix Kfixed, Klinear, Kperiodic;
|
|
|
|
|
|
/***********************************************************************
|
|
* Load geometry and data from Eclipse file
|
|
*/
|
|
cout << "Parsing Eclipse file <" << ECLIPSEFILENAME << "> ... ";
|
|
flush(cout); start = clock();
|
|
|
|
auto deck = Opm::RelPermUpscaleHelper::parseEclipseFile(ECLIPSEFILENAME);
|
|
|
|
finish = clock(); timeused = (double(finish)-double(start))/CLOCKS_PER_SEC;
|
|
cout << " (" << timeused <<" secs)" << endl;
|
|
|
|
// Check that we have the information we need from the eclipse file, we will check PERM-fields later
|
|
if (! (deck.hasKeyword("SPECGRID") && deck.hasKeyword("COORD") && deck.hasKeyword("ZCORN"))) {
|
|
cerr << "Error: Did not find SPECGRID, COORD and ZCORN in Eclipse file " << ECLIPSEFILENAME << endl;
|
|
usage();
|
|
exit(1);
|
|
}
|
|
|
|
|
|
/*****************************************************************
|
|
* Tesselate grid
|
|
*
|
|
* Possibly twice because, the grid must be massaged slightly
|
|
* (crop top and bottom) for periodic boundary conditions. These
|
|
* modifications ruin the computations for linear and fixed
|
|
* boundary conditions, so we must tesselate twice, thus use more
|
|
* memory and more time for processing.
|
|
*/
|
|
|
|
|
|
double linsolver_tolerance = atof(options["linsolver_tolerance"].c_str());
|
|
int linsolver_verbosity = atoi(options["linsolver_verbosity"].c_str());
|
|
int linsolver_type = atoi(options["linsolver_type"].c_str());
|
|
int linsolver_maxit = atoi(options["linsolver_max_iterations"].c_str());
|
|
int smooth_steps = atoi(options["linsolver_smooth_steps"].c_str());
|
|
double linsolver_prolongate_factor = atof(options["linsolver_prolongate_factor"].c_str());
|
|
bool twodim_hack = false;
|
|
|
|
SinglePhaseUpscaler upscaler_nonperiodic;
|
|
SinglePhaseUpscaler upscaler_periodic;
|
|
|
|
const double minPerm = Opm::unit::convert::from(atof(options["minPerm"].c_str()),
|
|
Opm::prefix::milli*Opm::unit::darcy);
|
|
|
|
if (isFixed || isLinear) {
|
|
cout << "Tesselating non-periodic grid ...";
|
|
start = clock();
|
|
upscaler_nonperiodic.init(deck,
|
|
isFixed ? SinglePhaseUpscaler::Fixed : SinglePhaseUpscaler::Linear,
|
|
minPerm, linsolver_tolerance, linsolver_verbosity, linsolver_type,
|
|
twodim_hack, linsolver_maxit, linsolver_prolongate_factor, smooth_steps);
|
|
finish = clock();
|
|
timeused_nonperiodic_tesselation = (double(finish)-double(start))/CLOCKS_PER_SEC;
|
|
cout << " (" << timeused_nonperiodic_tesselation << " secs)" << endl << endl;
|
|
}
|
|
if (isPeriodic) {
|
|
cout << "Tesselating periodic grid ... ";
|
|
start = clock();
|
|
upscaler_periodic.init(deck, SinglePhaseUpscaler::Periodic, minPerm,
|
|
linsolver_tolerance, linsolver_verbosity, linsolver_type, twodim_hack,
|
|
linsolver_maxit, linsolver_prolongate_factor, smooth_steps);
|
|
finish = clock();
|
|
timeused_periodic_tesselation = (double(finish)-double(start))/CLOCKS_PER_SEC;
|
|
cout << " (" << timeused_periodic_tesselation << " secs)" << endl << endl;
|
|
}
|
|
|
|
|
|
|
|
|
|
/*********************************************************************
|
|
* Do porosity upscaling
|
|
*
|
|
* This is an added feature. It is done since does not cost anything
|
|
* in terms of cpu-resources (compared to permeability upscaling).
|
|
*/
|
|
double upscaledPorosity = 0.0;
|
|
if (deck.hasKeyword("PORO")) {
|
|
if (isPeriodic) {
|
|
upscaledPorosity = upscaler_periodic.upscalePorosity();
|
|
} else {
|
|
upscaledPorosity = upscaler_nonperiodic.upscalePorosity();
|
|
}
|
|
}
|
|
|
|
/*********************************************************************
|
|
* Do single-phase permeability upscaling
|
|
*/
|
|
|
|
if (isFixed) {
|
|
cout << "Compute for fixed boundary conditions: ... ";
|
|
start = clock();
|
|
upscaler_nonperiodic.setBoundaryConditionType(SinglePhaseUpscaler::Fixed);
|
|
Kfixed = upscaler_nonperiodic.upscaleSinglePhase();
|
|
Kfixed *= 1.0/(Opm::prefix::milli*Opm::unit::darcy);
|
|
finish = clock();
|
|
timeused_fixed = (double(finish)-double(start))/CLOCKS_PER_SEC;
|
|
cout << " ( " << timeused_fixed << " secs)" << endl;
|
|
cout << Kfixed << endl;
|
|
cout << endl;
|
|
}
|
|
|
|
if (isLinear) {
|
|
cout << "Compute for linear boundary conditions: ... " << endl;
|
|
start = clock();
|
|
upscaler_nonperiodic.setBoundaryConditionType(SinglePhaseUpscaler::Linear);
|
|
Klinear = upscaler_nonperiodic.upscaleSinglePhase();
|
|
Klinear *= 1.0/(Opm::prefix::milli*Opm::unit::darcy);
|
|
finish = clock();
|
|
timeused_linear = (double(finish)-double(start))/CLOCKS_PER_SEC;
|
|
cout << Klinear << endl;
|
|
cout << " ( " << timeused_linear << " secs)" << endl;
|
|
cout << endl << endl;
|
|
}
|
|
|
|
if (isPeriodic) {
|
|
cout << "Compute for periodic boundary conditions: ... ";
|
|
start = clock();
|
|
upscaler_periodic.setBoundaryConditionType(SinglePhaseUpscaler::Periodic);
|
|
Kperiodic = upscaler_periodic.upscaleSinglePhase();
|
|
Kperiodic *= 1.0/(Opm::prefix::milli*Opm::unit::darcy);
|
|
finish = clock();
|
|
timeused_periodic = (double(finish)-double(start))/CLOCKS_PER_SEC;
|
|
cout << " (" << timeused_periodic << " secs)" << endl;
|
|
cout << Kperiodic << endl;
|
|
cout << endl;
|
|
}
|
|
|
|
/***********************************************************************
|
|
* Output results to stdout or optionally to file
|
|
*/
|
|
|
|
stringstream outputtmp;
|
|
|
|
// Print a table of all computed values:
|
|
outputtmp << "###############################################################################" << endl;
|
|
outputtmp << "# Results from upscaling permeability."<< endl;
|
|
outputtmp << "#" << endl;
|
|
time_t now = time(NULL);
|
|
outputtmp << "# Finished: " << asctime(localtime(&now));
|
|
utsname hostname; uname(&hostname);
|
|
outputtmp << "# Hostname: " << hostname.nodename << endl;
|
|
|
|
outputtmp << "#" << endl;
|
|
outputtmp << "# Eclipse file: " << ECLIPSEFILENAME << endl;
|
|
outputtmp << "# Porosity : " << upscaledPorosity << endl;
|
|
outputtmp << "#" << endl;
|
|
outputtmp << "# Options used:" << endl;
|
|
outputtmp << "# Boundary conditions: ";
|
|
if (isFixed) outputtmp << "Fixed (no-flow) ";
|
|
if (isPeriodic) outputtmp << "Periodic ";
|
|
if (isLinear) outputtmp << "Linear ";
|
|
outputtmp << endl;
|
|
outputtmp << "# minPerm: " << options["minPerm"] << endl;
|
|
outputtmp << "#" << endl;
|
|
outputtmp << "# If both linear and fixed boundary conditions are calculated, " << endl <<
|
|
"# the nonperiodic tesselation is done only once" << endl << "# " << endl << "#" << endl;
|
|
|
|
if (isFixed) {
|
|
outputtmp << "# Upscaled permeability for fixed boundary conditions:" << endl;
|
|
outputtmp << "# Tesselation time: " << timeused_nonperiodic_tesselation << " s" << endl;
|
|
outputtmp << "# Computation time: " << timeused_fixed << " s" << endl;
|
|
outputtmp << Kfixed;
|
|
}
|
|
if (isLinear) {
|
|
outputtmp << "# Upscaled permeability for linear boundary conditions:" << endl;
|
|
if (!isFixed) {
|
|
// Only display this if fixed BC was not used, as the tesselation time is shared.
|
|
outputtmp << "# Tesselation time: " << timeused_nonperiodic_tesselation << " s" << endl;
|
|
}
|
|
outputtmp << "# Computation time: " << timeused_linear << " s" << endl;
|
|
outputtmp << Klinear;
|
|
}
|
|
if (isPeriodic) {
|
|
outputtmp << "# Upscaled permeability for periodic boundary conditions:" << endl;
|
|
outputtmp << "# Tesselation time: " << timeused_periodic_tesselation << " s" << endl;
|
|
outputtmp << "# Computation time: " << timeused_periodic << " s" << endl;
|
|
outputtmp << Kperiodic;
|
|
}
|
|
cout << endl << outputtmp.str();
|
|
|
|
if (options["output"] != "") {
|
|
cout << "Writing results to " << options["output"] << endl;
|
|
ofstream outfile;
|
|
outfile.open(options["output"].c_str(), ios::out | ios::trunc);
|
|
outfile << outputtmp.str();
|
|
outfile.close();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
} // namespace anonymous
|
|
|
|
/**
|
|
@brief Upscales permeability
|
|
|
|
@param varnum Number of input arguments
|
|
@param vararg Input arguments
|
|
@return int
|
|
*/
|
|
int main(int varnum, char** vararg) try {
|
|
return upscale(varnum, vararg);
|
|
}
|
|
catch (const std::exception &e) {
|
|
std::cerr << "Program threw an exception: " << e.what() << "\n";
|
|
throw;
|
|
}
|
|
|