mirror of
https://github.com/OPM/opm-upscaling.git
synced 2025-02-25 18:45:23 -06:00
Add new program for doing water saturation upscaling
Represents a subset of the functionality in upscale_relperm that can upscale capillary pressure as a function of global water saturation in model. Also prints out water saturation pr. rocktype alongside global capillary pressure
This commit is contained in:
parent
a6f7372de6
commit
8182c85b49
@ -6,6 +6,7 @@ noinst_PROGRAMS = upscale_perm \
|
|||||||
upscale_relperm \
|
upscale_relperm \
|
||||||
upscale_avg \
|
upscale_avg \
|
||||||
upscale_cond \
|
upscale_cond \
|
||||||
|
upscale_cap \
|
||||||
cpchop \
|
cpchop \
|
||||||
exp_variogram \
|
exp_variogram \
|
||||||
cpregularize
|
cpregularize
|
||||||
@ -18,6 +19,8 @@ upscale_cond_SOURCES = upscale_cond.cpp
|
|||||||
|
|
||||||
upscale_avg_SOURCES = upscale_avg.cpp
|
upscale_avg_SOURCES = upscale_avg.cpp
|
||||||
|
|
||||||
|
upscale_cap_SOURCES = upscale_cap.cpp
|
||||||
|
|
||||||
cpchop_SOURCES = cpchop.cpp
|
cpchop_SOURCES = cpchop.cpp
|
||||||
|
|
||||||
exp_variogram_SOURCES = exp_variogram.cpp
|
exp_variogram_SOURCES = exp_variogram.cpp
|
||||||
|
715
examples/upscale_cap.cpp
Normal file
715
examples/upscale_cap.cpp
Normal file
@ -0,0 +1,715 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2008-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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
|
||||||
|
@file upscale_cap.C
|
||||||
|
@brief Upscales capillary pressure function, water saturation, and water saturation pr. rocktype.
|
||||||
|
|
||||||
|
Description:
|
||||||
|
|
||||||
|
Reads in a lithofacies geometry in Eclipse format, reads in J(S_w)
|
||||||
|
permeability and porosity, and upscale the capillary pressure function
|
||||||
|
and water saturation, both for the whole geometry, and on a per rocktype basis
|
||||||
|
|
||||||
|
Assumption for capillary pressure function
|
||||||
|
- Capillary equilibrium, p_c is spatially invariant.
|
||||||
|
Units handling:
|
||||||
|
- Assumes cornerpoint file reports lengths in cm.
|
||||||
|
- Input surface tension is in dynes/cm
|
||||||
|
- Input density is in g/cm^3
|
||||||
|
- The denominator \sigma * cos(\phi) in J-function scaling
|
||||||
|
is what we call "surface tension". If angle dependency is to be
|
||||||
|
included, calculate the "surface tension" yourself.
|
||||||
|
- Outputted capillary pressure is in Pascals.
|
||||||
|
|
||||||
|
|
||||||
|
Steps in the code:
|
||||||
|
|
||||||
|
1: Process command line options.
|
||||||
|
2: Read Eclipse file
|
||||||
|
3: Read and J-function for each stone-type.
|
||||||
|
4: Find minimum and maximum capillary pressure from the
|
||||||
|
J-functions in each cell.
|
||||||
|
5: Upscale water saturation as a function of capillary pressure
|
||||||
|
6: Print output to screen and optionally to file.
|
||||||
|
|
||||||
|
@author: Håvard Berland <havb <at> statoil.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <fstream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <ctime>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cfloat> // for DB_MAX/DBL_MIN
|
||||||
|
#include <map>
|
||||||
|
#include <sys/utsname.h>
|
||||||
|
|
||||||
|
#include <dune/common/MonotCubicInterpolator.hpp>
|
||||||
|
#include <dune/upscaling/SinglePhaseUpscaler.hpp>
|
||||||
|
|
||||||
|
using namespace Dune;
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Explains how to use the file. Shows possible option parameters.
|
||||||
|
|
||||||
|
*/
|
||||||
|
void usage()
|
||||||
|
{
|
||||||
|
cout << "Usage: upscale_cap <options> <eclipsefile> stoneA.txt stoneB.txt ..." << endl <<
|
||||||
|
"where the options are:" << endl <<
|
||||||
|
" -points <integer> -- Number of saturation points to report for." << endl <<
|
||||||
|
" Uniformly distributed within saturation endpoints." << endl <<
|
||||||
|
" Default 50." << endl <<
|
||||||
|
" -jFunctionCurve <integer> -- the column number in the stone-files that" << endl <<
|
||||||
|
" represent the Leverett J-function. Default 4." << 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 <<
|
||||||
|
" -surfaceTension <float> -- Surface tension to use in J-function/Pc conversion." << endl <<
|
||||||
|
" Default 11 dynes/cm (oil-water systems). In absence of" << endl <<
|
||||||
|
" a correct value, the surface tension for gas-oil systems " << endl <<
|
||||||
|
" could be 22.5 dynes/cm." << endl <<
|
||||||
|
" -maxPermContrast <float> -- maximal permeability contrast in model." << endl <<
|
||||||
|
" Default 10^7" << endl <<
|
||||||
|
" -minPerm <float> -- Minimum floating point value allowed for" << endl <<
|
||||||
|
" phase permeability in computations. If set to zero," << endl <<
|
||||||
|
" some models can end up singular for permeability" << endl <<
|
||||||
|
" upscaling. Default 10^-12" << endl <<
|
||||||
|
"" << endl <<
|
||||||
|
"If only one stone-file is supplied, it is used for all stone-types defined" << endl <<
|
||||||
|
"in the geometry. If more than one, it corresponds to the SATNUM-values." << endl;
|
||||||
|
// "minPoro" intentionally left undocumented
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void usageandexit() {
|
||||||
|
usage();
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int varnum, char** vararg)
|
||||||
|
{
|
||||||
|
|
||||||
|
/******************************************************************************
|
||||||
|
* Step 1:
|
||||||
|
* Process command line options
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (varnum == 1) { /* If no arguments supplied ("upscale_cap" is the first ('zero') "argument") */
|
||||||
|
usage();
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Populate options-map with default values
|
||||||
|
*/
|
||||||
|
map<string,string> options;
|
||||||
|
options.insert(make_pair("points", "50" )); // Number of saturation points (uniformly distributed within saturation endpoints)
|
||||||
|
options.insert(make_pair("jFunctionCurve", "4")); // Which column in the rock type file is the J-function curve
|
||||||
|
options.insert(make_pair("output", "")); // If this is set, output goes to screen and to this file.
|
||||||
|
options.insert(make_pair("outputprecision", "8")); // number of decimals to print
|
||||||
|
options.insert(make_pair("surfaceTension", "11")); // Surface tension given in dynes/cm
|
||||||
|
options.insert(make_pair("maxPermContrast", "1e7")); // maximum allowed contrast in each single-phase computation
|
||||||
|
options.insert(make_pair("minPerm", "1e-12")); // absoluted minimum allowed minimal cell permeability
|
||||||
|
options.insert(make_pair("minPoro", "0.0001")); // this limit is necessary for pcmin/max computation
|
||||||
|
|
||||||
|
// Conversion factor, multiply mD numbers with this to get m² numbers
|
||||||
|
const double milliDarcyToSqMetre = 9.869233e-16;
|
||||||
|
// Reference: http://www.spe.org/spe-site/spe/spe/papers/authors/Metric_Standard.pdf
|
||||||
|
|
||||||
|
|
||||||
|
/* Check first if there is anything on the command line to look for */
|
||||||
|
if (varnum == 1) {
|
||||||
|
cout << "Error: No Eclipsefile or stonefiles found on command line." << endl;
|
||||||
|
usageandexit();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Loop over all command line options in order to look
|
||||||
|
for options.
|
||||||
|
|
||||||
|
argidx loops over all the arguments here, and updates the
|
||||||
|
variable 'argeclindex' *if* it finds any legal options,
|
||||||
|
'argeclindex' is so that vararg[argeclindex] = the eclipse
|
||||||
|
filename. If options are illegal, argeclindex will be wrong,
|
||||||
|
|
||||||
|
*/
|
||||||
|
int argeclindex = 0;
|
||||||
|
for (int argidx = 1; argidx < varnum; argidx += 2) {
|
||||||
|
if (string(vararg[argidx]).substr(0,1) == "-") {
|
||||||
|
string searchfor = string(vararg[argidx]).substr(1); // Chop off leading '-'
|
||||||
|
/* 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;
|
||||||
|
argeclindex = argidx + 2;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
cout << "Option -" << searchfor << " unrecognized." << endl;
|
||||||
|
usageandexit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// if vararg[argidx] does not start in '-',
|
||||||
|
// assume we have found the position of the Eclipse-file.
|
||||||
|
argeclindex = argidx;
|
||||||
|
break; // out of for-loop,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// argeclindex should now point to the eclipse file
|
||||||
|
static char* ECLIPSEFILENAME(vararg[argeclindex]);
|
||||||
|
argeclindex += 1; // argeclindex jumps to next input argument, now it points to the stone files.
|
||||||
|
|
||||||
|
// argeclindex now points to the first J-function. This index is not
|
||||||
|
// to be touched now.
|
||||||
|
static int JFindex = argeclindex;
|
||||||
|
|
||||||
|
|
||||||
|
/* Check if at least one J-function is supplied on command line */
|
||||||
|
if (varnum <= JFindex) {
|
||||||
|
cerr << "Error: No J-functions found on command line." << endl;
|
||||||
|
usageandexit();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/***********************************************************************
|
||||||
|
* Step 2:
|
||||||
|
* Load geometry and data from Eclipse file
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Read data from the Eclipse file and
|
||||||
|
// populate our vectors with data from the file
|
||||||
|
|
||||||
|
const double emptycellvolumecutoff = 1e-10;
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
usageandexit();
|
||||||
|
}
|
||||||
|
eclipsefile.close();
|
||||||
|
|
||||||
|
cout << "Parsing Eclipse file <" << ECLIPSEFILENAME << "> ... " << endl;
|
||||||
|
EclipseGridParser eclParser(ECLIPSEFILENAME);
|
||||||
|
|
||||||
|
// Check that we have the information we need from the eclipse file:
|
||||||
|
if (! (eclParser.hasField("SPECGRID") && eclParser.hasField("COORD") && eclParser.hasField("ZCORN")
|
||||||
|
&& eclParser.hasField("PORO") && eclParser.hasField("PERMX") && eclParser.hasField("SATNUM"))) {
|
||||||
|
cerr << "Error: Did not find SPECGRID, COORD and ZCORN in Eclipse file " << ECLIPSEFILENAME << endl;
|
||||||
|
usageandexit();
|
||||||
|
}
|
||||||
|
|
||||||
|
vector<int> satnums = eclParser.getIntegerValue("SATNUM");
|
||||||
|
vector<double> poros = eclParser.getFloatingPointValue("PORO");
|
||||||
|
vector<double> permxs = eclParser.getFloatingPointValue("PERMX");
|
||||||
|
vector<int> griddims = eclParser.getSPECGRID().dimensions;
|
||||||
|
int x_res = griddims[0];
|
||||||
|
int y_res = griddims[1];
|
||||||
|
int z_res = griddims[2];
|
||||||
|
|
||||||
|
int maxSatnum = 0;
|
||||||
|
const double maxPermContrast = atof(options["maxPermContrast"].c_str());
|
||||||
|
const double minPerm = atof(options["minPerm"].c_str());
|
||||||
|
const double minPoro = atof(options["minPoro"].c_str());
|
||||||
|
bool zerosatnumcells = false; // This is set true if there are some cells with rocktype zero.
|
||||||
|
|
||||||
|
/* Sanity check/fix on input for each cell:
|
||||||
|
- Check that SATNUM are set sensibly, that is => 0 and < 1000, error if not.
|
||||||
|
- Check that porosity is between 0 and 1, error if not.
|
||||||
|
Set to minPoro if zero or less than minPoro (due to pcmin/max computation)
|
||||||
|
- Check that permeability is zero or positive. Error if negative.
|
||||||
|
Set to minPerm if zero or less than minPerm.
|
||||||
|
- Check maximum number of SATNUM values (can be number of rock types present)
|
||||||
|
*/
|
||||||
|
for (uint i = 0; i < satnums.size(); ++i) {
|
||||||
|
if (satnums[i] < 0 || satnums[i] > 1000) {
|
||||||
|
cerr << "satnums[" << i << "] = " << satnums[i] << ", not sane, quitting." << endl;
|
||||||
|
usageandexit();
|
||||||
|
}
|
||||||
|
if (satnums[i] > maxSatnum) {
|
||||||
|
maxSatnum = satnums[i];
|
||||||
|
}
|
||||||
|
if ((poros[i] >= 0) && (poros[i] < minPoro)) { // Truncate porosity from below
|
||||||
|
poros[i] = minPoro;
|
||||||
|
}
|
||||||
|
if (poros[i] < 0 || poros[i] > 1) {
|
||||||
|
cerr << "poros[" << i <<"] = " << poros[i] << ", not sane, quitting." << endl;
|
||||||
|
usageandexit();
|
||||||
|
}
|
||||||
|
if ((permxs[i] >= 0) && (permxs[i] < minPerm)) { // Truncate permeability from below
|
||||||
|
permxs[i] = minPerm;
|
||||||
|
}
|
||||||
|
if (permxs[i] < 0) {
|
||||||
|
cerr << "permx[" << i <<"] = " << permxs[i] << ", not sane, quitting." << endl;
|
||||||
|
usageandexit();
|
||||||
|
}
|
||||||
|
// Explicitly handle "no rock" cells, set them to minimum perm and zero porosity.
|
||||||
|
if (satnums[i] == 0) {
|
||||||
|
zerosatnumcells = true;
|
||||||
|
permxs[i] = minPerm;
|
||||||
|
poros[i] = 0; // zero poro is fine for these cells, as they are not
|
||||||
|
// used in pcmin/max computation.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
* Step 3:
|
||||||
|
* Load relperm- and J-function-curves for the stone types.
|
||||||
|
* We read columns from text-files, syntax allowed is determined
|
||||||
|
* by MonotCubicInterpolator which actually opens and parses the
|
||||||
|
* text files.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Number of stone-types is max(satnums):
|
||||||
|
|
||||||
|
// If there is only one J-function supplied on the command line,
|
||||||
|
// use that for all stone types.
|
||||||
|
|
||||||
|
int stone_types = int(*(max_element(satnums.begin(), satnums.end())));
|
||||||
|
std::vector<MonotCubicInterpolator> InvJfunctions; // Holds the inverse of the loaded J-functions.
|
||||||
|
|
||||||
|
std::vector<string> JfunctionNames; // Placeholder for the names of the loaded J-functions.
|
||||||
|
|
||||||
|
// This decides whether we are upscaling water or oil relative permeability
|
||||||
|
const int relPermCurve = atoi(options["relPermCurve"].c_str());
|
||||||
|
|
||||||
|
// Input for surfaceTension is dynes/cm
|
||||||
|
// SI units are Joules/square metre
|
||||||
|
const double surfaceTension = atof(options["surfaceTension"].c_str()) * 1e-3; // multiply with 10^-3 to obtain SI units
|
||||||
|
const int jFunctionCurve = atoi(options["jFunctionCurve"].c_str());
|
||||||
|
const int interpolationPoints = atoi(options["points"].c_str());
|
||||||
|
const int outputprecision = atoi(options["outputprecision"].c_str());
|
||||||
|
|
||||||
|
// Handle two command line input formats, either one J-function for all stone types
|
||||||
|
// or one each. If there is only one stone type, both code blocks below are equivalent.
|
||||||
|
|
||||||
|
if (varnum == JFindex + stone_types) {
|
||||||
|
for (int i=0 ; i < stone_types; ++i) {
|
||||||
|
const char* ROCKFILENAME = vararg[JFindex+i];
|
||||||
|
// Check if rock file exists and is readable:
|
||||||
|
ifstream rockfile(ROCKFILENAME, ios::in);
|
||||||
|
if (rockfile.fail()) {
|
||||||
|
cerr << "Error: Filename " << ROCKFILENAME << " not found or not readable." << endl;
|
||||||
|
usageandexit();
|
||||||
|
}
|
||||||
|
rockfile.close();
|
||||||
|
MonotCubicInterpolator Jtmp;
|
||||||
|
try {
|
||||||
|
Jtmp = MonotCubicInterpolator(ROCKFILENAME, 1, jFunctionCurve);
|
||||||
|
}
|
||||||
|
catch (const char * errormessage) {
|
||||||
|
cerr << "Error: " << errormessage << endl;
|
||||||
|
cerr << "Check filename and -jFunctionCurve" << endl;
|
||||||
|
usageandexit();
|
||||||
|
}
|
||||||
|
// Invert J-function, now we get saturation as a function of pressure:
|
||||||
|
if (Jtmp.isStrictlyMonotone()) {
|
||||||
|
InvJfunctions.push_back(MonotCubicInterpolator(Jtmp.get_fVector(), Jtmp.get_xVector()));
|
||||||
|
JfunctionNames.push_back(vararg[JFindex + i]);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
cerr << "Error: Jfunction " << i+1 << " in rock file " << ROCKFILENAME << " was not invertible." << endl;
|
||||||
|
usageandexit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (varnum == JFindex + 1) {
|
||||||
|
for (int i=0; i < stone_types; ++i) {
|
||||||
|
const char* ROCKFILENAME = vararg[JFindex];
|
||||||
|
// Check if rock file exists and is readable:
|
||||||
|
ifstream rockfile(ROCKFILENAME, ios::in);
|
||||||
|
if (rockfile.fail()) {
|
||||||
|
cerr << "Error: Filename " << ROCKFILENAME << " not found or not readable." << endl;
|
||||||
|
usageandexit();
|
||||||
|
}
|
||||||
|
rockfile.close();
|
||||||
|
MonotCubicInterpolator Jtmp;
|
||||||
|
try {
|
||||||
|
Jtmp = MonotCubicInterpolator(ROCKFILENAME, 1, jFunctionCurve);
|
||||||
|
}
|
||||||
|
catch(const char * errormessage) {
|
||||||
|
cerr << "Error: " << errormessage << endl;
|
||||||
|
cerr << "Check filename and -jFunctionCurve" << endl;
|
||||||
|
usageandexit();
|
||||||
|
}
|
||||||
|
// Invert J-function, now we get saturation as a function of pressure:
|
||||||
|
if (Jtmp.isStrictlyMonotone()) {
|
||||||
|
InvJfunctions.push_back(MonotCubicInterpolator(Jtmp.get_fVector(), Jtmp.get_xVector()));
|
||||||
|
JfunctionNames.push_back(vararg[JFindex]);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
cerr << "Error: Jfunction " << i+1 << " in rock file " << ROCKFILENAME << " was not invertible." << endl;
|
||||||
|
usageandexit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
cerr << "Error: Wrong number of stone-functions provided. " << endl;
|
||||||
|
usageandexit();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/******************************************************************************
|
||||||
|
* Step 5:
|
||||||
|
* Go through each cell and calculate the minimum and
|
||||||
|
* maximum capillary pressure possible in the cell, given poro,
|
||||||
|
* perm and the J-function for the cell. This depends on the
|
||||||
|
* J-function in that they represent all possible saturations,
|
||||||
|
* ie. we do not want to extrapolate the J-functions (but we might
|
||||||
|
* have to do that later in the computations).
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (maxPermContrast == 0) {
|
||||||
|
cout << "Illegal contrast value" << endl;
|
||||||
|
usageandexit();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Construct an object for single-phase upscaling, since we need to get some
|
||||||
|
// information from the grid.
|
||||||
|
SinglePhaseUpscaler upscaler;
|
||||||
|
eclParser.convertToSI();
|
||||||
|
upscaler.init(eclParser, SinglePhaseUpscaler::Fixed,
|
||||||
|
unit::convert::from(minPerm, prefix::milli*unit::darcy),
|
||||||
|
0.0, 1e-8, 0, 1, false); // options on this line are noops for upscale_cap
|
||||||
|
|
||||||
|
vector<double> cellVolumes, cellPoreVolumes;
|
||||||
|
cellVolumes.resize(satnums.size(), 0.0);
|
||||||
|
cellPoreVolumes.resize(satnums.size(), 0.0);
|
||||||
|
|
||||||
|
/* Volumes/saturations pr. rocktype */
|
||||||
|
vector<double> cellporevolume_rocktype;
|
||||||
|
cellporevolume_rocktype.resize(maxSatnum + 1, 0.0);
|
||||||
|
|
||||||
|
vector<double> watervolume_rocktype;
|
||||||
|
watervolume_rocktype.resize(maxSatnum + 1, 0.0);
|
||||||
|
|
||||||
|
|
||||||
|
/* Find minimium and maximum capillary pressure values in each
|
||||||
|
cell, and use the global min/max as the two initial pressure
|
||||||
|
points for computations.
|
||||||
|
|
||||||
|
Also find max single-phase permeability, used to obey the
|
||||||
|
maxPermContrast option.
|
||||||
|
|
||||||
|
Also find properly upscaled saturation endpoints, these are
|
||||||
|
printed out to stdout for reference during computations, but will
|
||||||
|
automatically appear as the lowest and highest saturation points
|
||||||
|
in finished output.
|
||||||
|
*/
|
||||||
|
int tesselatedCells = 0; // for counting "active" cells (Sintef interpretation of "active")
|
||||||
|
double Pcmax = -DBL_MAX, Pcmin = DBL_MAX;
|
||||||
|
double maxSinglePhasePerm = 0;
|
||||||
|
double Swirvolume = 0;
|
||||||
|
double Sworvolume = 0;
|
||||||
|
|
||||||
|
const std::vector<int>& ecl_idx = upscaler.grid().globalCell();
|
||||||
|
CpGrid::Codim<0>::LeafIterator c = upscaler.grid().leafbegin<0>();
|
||||||
|
for (; c != upscaler.grid().leafend<0>(); ++c) {
|
||||||
|
uint cell_idx = ecl_idx[c->index()];
|
||||||
|
if (satnums[cell_idx] > 0) { // Satnum zero is "no rock"
|
||||||
|
|
||||||
|
cellVolumes[cell_idx] = c->geometry().volume();
|
||||||
|
cellPoreVolumes[cell_idx] = cellVolumes[cell_idx] * poros[cell_idx];
|
||||||
|
|
||||||
|
|
||||||
|
double Pcmincandidate = InvJfunctions[int(satnums[cell_idx])-1].getMinimumX().first
|
||||||
|
/ sqrt(permxs[cell_idx] * milliDarcyToSqMetre/poros[cell_idx]) * surfaceTension;
|
||||||
|
Pcmin = min(Pcmincandidate, Pcmin);
|
||||||
|
|
||||||
|
double Pcmaxcandidate = InvJfunctions[int(satnums[cell_idx])-1].getMaximumX().first
|
||||||
|
/ sqrt(permxs[cell_idx] * milliDarcyToSqMetre/poros[cell_idx]) * surfaceTension;
|
||||||
|
Pcmax = max(Pcmaxcandidate, Pcmax);
|
||||||
|
|
||||||
|
maxSinglePhasePerm = max( maxSinglePhasePerm, permxs[cell_idx]);
|
||||||
|
|
||||||
|
cellporevolume_rocktype[satnums[cell_idx]] += cellPoreVolumes[cell_idx];
|
||||||
|
double minSw = InvJfunctions[int(satnums[cell_idx])-1].getMinimumF().second;
|
||||||
|
double maxSw = InvJfunctions[int(satnums[cell_idx])-1].getMaximumF().second;
|
||||||
|
|
||||||
|
// cout << "minSwc: " << minSw << endl;
|
||||||
|
// cout << "maxSwc: " << maxSw << endl;
|
||||||
|
|
||||||
|
// Add irreducible water saturation volume
|
||||||
|
Swirvolume += minSw * cellPoreVolumes[cell_idx];
|
||||||
|
Sworvolume += maxSw * cellPoreVolumes[cell_idx];
|
||||||
|
|
||||||
|
}
|
||||||
|
++tesselatedCells; // keep count.
|
||||||
|
}
|
||||||
|
|
||||||
|
double minSinglePhasePerm = max(maxSinglePhasePerm/maxPermContrast, minPerm);
|
||||||
|
|
||||||
|
cout << "Pcmin: " << Pcmin << endl;
|
||||||
|
cout << "Pcmax: " << Pcmax << endl;
|
||||||
|
|
||||||
|
if (Pcmin > Pcmax) {
|
||||||
|
cerr << "ERROR: No legal capillary pressures found for this system. Exiting..." << endl;
|
||||||
|
usageandexit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Total porevolume and total volume -> upscaled porosity:
|
||||||
|
double poreVolume = std::accumulate(cellPoreVolumes.begin(),
|
||||||
|
cellPoreVolumes.end(),
|
||||||
|
0.0);
|
||||||
|
double volume = std::accumulate(cellVolumes.begin(),
|
||||||
|
cellVolumes.end(),
|
||||||
|
0.0);
|
||||||
|
|
||||||
|
double Swir = Swirvolume/poreVolume;
|
||||||
|
double Swor = Sworvolume/poreVolume;
|
||||||
|
|
||||||
|
cout << "LF Pore volume: " << poreVolume << endl;
|
||||||
|
cout << "LF Volume: " << volume << endl;
|
||||||
|
cout << "Upscaled porosity: " << poreVolume/volume << endl;
|
||||||
|
cout << "Upscaled Swir: " << Swir << endl;
|
||||||
|
cout << "Upscaled Swor: " << Swor << endl;
|
||||||
|
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
* Step 6:
|
||||||
|
* Upscale capillary pressure function.
|
||||||
|
*
|
||||||
|
* This is upscaled in advance in order to be able to have uniformly distributed
|
||||||
|
* saturation points for which upscaling is performed.
|
||||||
|
*
|
||||||
|
* Capillary pressure points are chosen heuristically in order to
|
||||||
|
* ensure largest saturation interval between two saturation points
|
||||||
|
* is 1/1000 of the saturation interval. Monotone cubic interpolation
|
||||||
|
* will be used afterwards for accessing the tabulated values.
|
||||||
|
*/
|
||||||
|
|
||||||
|
MonotCubicInterpolator WaterSaturationVsCapPressure;
|
||||||
|
|
||||||
|
int saturationpoints = 0; // for counting
|
||||||
|
double largestSaturationInterval = Swor-Swir;
|
||||||
|
|
||||||
|
double Ptestvalue = Pcmax;
|
||||||
|
|
||||||
|
vector<MonotCubicInterpolator> watersaturation_rocktype;
|
||||||
|
for (uint satidx=0; satidx <= maxSatnum; ++satidx) {
|
||||||
|
MonotCubicInterpolator tmp;
|
||||||
|
watersaturation_rocktype.push_back(tmp);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (largestSaturationInterval > (Swor-Swir)/200.0) {
|
||||||
|
if (Pcmax == Pcmin) {
|
||||||
|
// This is a dummy situation, we go through once and then
|
||||||
|
// we are finished (this will be triggered by zero permeability)
|
||||||
|
Ptestvalue = Pcmin;
|
||||||
|
largestSaturationInterval = 0;
|
||||||
|
}
|
||||||
|
else if (WaterSaturationVsCapPressure.getSize() == 0) {
|
||||||
|
/* No data values previously computed */
|
||||||
|
Ptestvalue = Pcmax;
|
||||||
|
}
|
||||||
|
else if (WaterSaturationVsCapPressure.getSize() == 1) {
|
||||||
|
/* If only one point has been computed, it was for Pcmax. So now
|
||||||
|
do Pcmin */
|
||||||
|
Ptestvalue = Pcmin;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
/* Search for largest saturation interval in which there are no
|
||||||
|
computed saturation points (and estimate the capillary pressure
|
||||||
|
that will fall in the center of this saturation interval)
|
||||||
|
*/
|
||||||
|
pair<double,double> SatDiff = WaterSaturationVsCapPressure.getMissingX();
|
||||||
|
Ptestvalue = SatDiff.first;
|
||||||
|
largestSaturationInterval = SatDiff.second;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for saneness of Ptestvalue:
|
||||||
|
if (isnan(Ptestvalue) | isinf(Ptestvalue)) {
|
||||||
|
cerr << "ERROR: Ptestvalue was inf or nan" << endl;
|
||||||
|
break; // Jump out of while-loop, just print out the results
|
||||||
|
// up to now and exit the program
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset array to zero.
|
||||||
|
for (uint satidx = 0; satidx <= maxSatnum; ++satidx) {
|
||||||
|
watervolume_rocktype[satidx] = 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
double waterVolume = 0.0;
|
||||||
|
for (uint cell_idx = 0; cell_idx < satnums.size(); ++cell_idx) {
|
||||||
|
if (cellVolumes[cell_idx] > emptycellvolumecutoff) {
|
||||||
|
double waterSaturationCell = 0.0;
|
||||||
|
if (satnums[cell_idx] > 0) { // handle "no rock" cells with satnum zero
|
||||||
|
double PtestvalueCell;
|
||||||
|
PtestvalueCell = Ptestvalue;
|
||||||
|
double Jvalue = sqrt(permxs[cell_idx] * milliDarcyToSqMetre/poros[cell_idx])
|
||||||
|
* PtestvalueCell / surfaceTension;
|
||||||
|
//cout << "JvalueCell: " << Jvalue << endl;
|
||||||
|
waterSaturationCell
|
||||||
|
= InvJfunctions[int(satnums[cell_idx])-1].evaluate(Jvalue);
|
||||||
|
}
|
||||||
|
waterVolume += waterSaturationCell * cellPoreVolumes[cell_idx];
|
||||||
|
watervolume_rocktype[satnums[cell_idx]] += waterSaturationCell * cellPoreVolumes[cell_idx];
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
WaterSaturationVsCapPressure.addPair(Ptestvalue, waterVolume/poreVolume);
|
||||||
|
// cout << "Ptestvalue " << Ptestvalue << " sat: " << waterVolume/poreVolume << endl;
|
||||||
|
for (uint satidx = 1; satidx <= maxSatnum; ++satidx) {
|
||||||
|
// cout << "satidx "<< satidx << " " << watervolume_rocktype[satidx]/cellporevolume_rocktype[satidx] << endl;
|
||||||
|
//cout << "watvol: " << watervolume_rocktype[satidx] << " porevol " << cellporevolume_rocktype[satidx] << endl;
|
||||||
|
watersaturation_rocktype[satidx].addPair(Ptestvalue, watervolume_rocktype[satidx]/cellporevolume_rocktype[satidx]);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
//cout << WaterSaturationVsCapPressure.toString() << endl;
|
||||||
|
|
||||||
|
// Now we can also invert the upscaled water saturation
|
||||||
|
// (it should be monotonic)
|
||||||
|
if (!WaterSaturationVsCapPressure.isStrictlyMonotone()) {
|
||||||
|
{
|
||||||
|
cerr << "Error: Upscaled water saturation not strictly monotone in capillary pressure." << endl;
|
||||||
|
cerr << " Unphysical input data, exiting." << endl;
|
||||||
|
}
|
||||||
|
usageandexit();
|
||||||
|
}
|
||||||
|
MonotCubicInterpolator CapPressureVsWaterSaturation(WaterSaturationVsCapPressure.get_fVector(),
|
||||||
|
WaterSaturationVsCapPressure.get_xVector());
|
||||||
|
|
||||||
|
|
||||||
|
/*********************************************************************************
|
||||||
|
* Step 9
|
||||||
|
*
|
||||||
|
* Output results to stdout and optionally to file. Note, we only output to
|
||||||
|
* file if the '-outputWater'-option and/or '-outputOil' has been set, as this option is an
|
||||||
|
* empty string by default.
|
||||||
|
*/
|
||||||
|
vector<double> Pvalues = WaterSaturationVsCapPressure.get_xVector();
|
||||||
|
vector<double> Satvalues = WaterSaturationVsCapPressure.get_fVector();
|
||||||
|
|
||||||
|
vector<vector<double> > watersaturation_rocktype_values;
|
||||||
|
vector<double> tmp;
|
||||||
|
watersaturation_rocktype_values.push_back(tmp); // dummy zero index element
|
||||||
|
for (uint satidx=1; satidx <= maxSatnum; ++satidx) {
|
||||||
|
watersaturation_rocktype_values.push_back(watersaturation_rocktype[satidx].get_fVector());
|
||||||
|
}
|
||||||
|
|
||||||
|
stringstream outputtmp;
|
||||||
|
|
||||||
|
// Print a table of all computed values:
|
||||||
|
outputtmp << "######################################################################" << endl;
|
||||||
|
outputtmp << "# Results from upscaling capillary pressure and water saturations."<< endl;
|
||||||
|
outputtmp << "#" << endl;
|
||||||
|
outputtmp << "# Version: $Id: upscale_cap.C 388 2008-10-24 08:14:21Z havb $" << endl;
|
||||||
|
time_t now = std::time(NULL);
|
||||||
|
outputtmp << "# Finished: " << asctime(localtime(&now));
|
||||||
|
|
||||||
|
utsname hostname; uname(&hostname);
|
||||||
|
outputtmp << "# Hostname: " << hostname.nodename << endl;
|
||||||
|
|
||||||
|
outputtmp << "#" << endl;
|
||||||
|
outputtmp << "# Eclipse file: " << ECLIPSEFILENAME << endl;
|
||||||
|
outputtmp << "# cells: " << tesselatedCells << endl;
|
||||||
|
outputtmp << "# Pore volume: " << poreVolume << endl;
|
||||||
|
outputtmp << "# volume: " << volume << endl;
|
||||||
|
outputtmp << "# Porosity: " << poreVolume/volume << endl;
|
||||||
|
outputtmp << "#" << endl;
|
||||||
|
for (int i=0; i < stone_types ; ++i) {
|
||||||
|
outputtmp << "# Stone " << i+1 << ": " << JfunctionNames[i] << " (" << InvJfunctions[i].getSize() << " points)" << endl;
|
||||||
|
}
|
||||||
|
outputtmp << "# " << endl;
|
||||||
|
outputtmp << "# Options used:" << endl;
|
||||||
|
outputtmp << "# jFunctionCurve: " << options["jFunctionCurve"] << endl;
|
||||||
|
outputtmp << "# points: " << options["points"] << endl;
|
||||||
|
outputtmp << "# maxPermContrast: " << options["maxPermContrast"] << endl;
|
||||||
|
outputtmp << "# surfaceTension: " << options["surfaceTension"] << endl;
|
||||||
|
outputtmp << "######################################################################" << endl;
|
||||||
|
outputtmp << "# Pc (Pa) Sw Sw1 Sw2 Sw3 etc.." << endl;
|
||||||
|
|
||||||
|
|
||||||
|
// If user wants interpolated output, do monotone cubic interpolation
|
||||||
|
// by modifying the data vectors that are to be printed
|
||||||
|
if (interpolationPoints > 0) {
|
||||||
|
// Find min and max for saturation values
|
||||||
|
double xmin = +DBL_MAX;
|
||||||
|
double xmax = -DBL_MAX;
|
||||||
|
for (uint i = 0; i < Satvalues.size(); ++i) {
|
||||||
|
if (Satvalues[i] < xmin) {
|
||||||
|
xmin = Satvalues[i];
|
||||||
|
}
|
||||||
|
if (Satvalues[i] > xmax) {
|
||||||
|
xmax = Satvalues[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Make uniform grid in saturation axis
|
||||||
|
vector<double> SatvaluesInterp;
|
||||||
|
for (int i = 0; i < interpolationPoints; ++i) {
|
||||||
|
SatvaluesInterp.push_back(xmin + ((double)i)/((double)interpolationPoints-1)*(xmax-xmin));
|
||||||
|
}
|
||||||
|
// Now capillary pressure and computed relperm-values must be viewed as functions
|
||||||
|
// of saturation, and then interpolated on the uniform saturation grid.
|
||||||
|
|
||||||
|
// Now overwrite existing Pvalues and relperm-data with interpolated data:
|
||||||
|
MonotCubicInterpolator PvaluesVsSaturation(Satvalues, Pvalues);
|
||||||
|
Pvalues.clear();
|
||||||
|
for (int i = 0; i < interpolationPoints; ++i) {
|
||||||
|
Pvalues.push_back(PvaluesVsSaturation.evaluate(SatvaluesInterp[i]));
|
||||||
|
}
|
||||||
|
for (int satidx = 1; satidx <= maxSatnum; ++satidx) {
|
||||||
|
MonotCubicInterpolator WaterSaturationRocktypeVsSaturation(Satvalues, watersaturation_rocktype_values[satidx]);
|
||||||
|
watersaturation_rocktype_values[satidx].clear();
|
||||||
|
for (int i=0; i < interpolationPoints; ++i) {
|
||||||
|
watersaturation_rocktype_values[satidx].push_back(WaterSaturationRocktypeVsSaturation.evaluate(SatvaluesInterp[i]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Now also overwrite Satvalues
|
||||||
|
Satvalues.clear();
|
||||||
|
Satvalues = SatvaluesInterp;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const int fieldwidth = outputprecision + 8;
|
||||||
|
for (uint i=0; i < Satvalues.size(); ++i) {
|
||||||
|
outputtmp << showpoint << setw(fieldwidth) << setprecision(outputprecision) << Pvalues[i];
|
||||||
|
outputtmp << showpoint << setw(fieldwidth) << setprecision(outputprecision) << Satvalues[i];
|
||||||
|
|
||||||
|
for (int satidx = 1; satidx <= maxSatnum; ++satidx) {
|
||||||
|
outputtmp << showpoint << setw(fieldwidth) << setprecision(outputprecision)
|
||||||
|
<< watersaturation_rocktype_values[satidx][i];
|
||||||
|
}
|
||||||
|
outputtmp << endl;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
cout << 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;
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user