opm-core/opm/core/io/eclipse/BlackoilEclipseOutputWriter.cpp

699 lines
27 KiB
C++
Raw Normal View History

/*
2013-11-06 05:56:19 -06:00
Copyright (c) 2013 Andreas Lauser
Copyright (c) 2013 SINTEF ICT, Applied Mathematics.
Copyright (c) 2013 Uni Research AS
This file is part of the Open Porous Media project (OPM).
OPM is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OPM is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OPM. If not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include "BlackoilEclipseOutputWriter.hpp"
#include <boost/algorithm/string/case_conv.hpp> // to_upper_copy
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/filesystem.hpp> // path
#include <boost/format.hpp>
#include <opm/core/simulator/BlackoilState.hpp>
#include <opm/core/io/eclipse/EclipseGridParser.hpp>
#include <opm/core/props/BlackoilPhases.hpp>
#include <opm/core/utility/Units.hpp>
#include <opm/core/utility/ErrorMacros.hpp>
#include <opm/core/utility/DataMap.hpp>
#include <opm/core/wells.h> // WellType
#ifdef HAVE_ERT
#include <ert/ecl/fortio.h>
2013-11-04 07:27:29 -06:00
#include <ert/ecl/ecl_endian_flip.h>
#include <ert/ecl/ecl_grid.h>
#include <ert/ecl/ecl_kw_magic.h>
#include <ert/ecl/ecl_kw.h>
#include <ert/ecl/ecl_sum.h>
#include <ert/ecl/ecl_util.h>
#include <ert/ecl/ecl_init_file.h>
#include <ert/ecl/ecl_file.h>
#include <ert/ecl/ecl_rst_file.h>
#endif
#include <memory> // unique_ptr
#include <utility> // move
using namespace Opm;
/// Smart pointer/handle class for ERT opaque types, such as ecl_kw_type*.
///
/// \tparam T Type of handle being wrapper
template <typename T>
struct EclipseHandle : private std::unique_ptr <T, void (*)(T*) throw()> {
// to save ourselves from some typing we introduce this alias
typedef std::unique_ptr <T, void (*)(T*) throw()> base;
/// Construct a new smart handle based on the returned value of
/// an allocation function and the corresponding destroyer function.
EclipseHandle (T* t, void (*destroy)(T*))
: base (t, destroy) { }
/// Convenience operator that lets us use this type as if
/// it was a handle directly.
operator T* () const { return base::get (); }
};
/**
* Eclipse "keyword" (i.e. named data) for a vector. (This class is
* different from EclKW in the constructors it provide).
*/
template <typename T>
struct EclipseKeyword : public EclipseHandle <ecl_kw_type> {
EclipseKeyword (const std::string& name, /// identification
const std::vector<T>& data, /// array holding values
const int num, /// actual number to take
const int offset, /// distance to first
const int stride) /// distance between each
// allocate handle and put in smart pointer base class
: EclipseHandle (ecl_kw_alloc (name.c_str(), num, type ()),
ecl_kw_free) {
// range cannot start outside of data set
assert(offset >= 0 && offset < data.size());
// don't jump out of the set when trying to
assert(stride > 0 && stride < data.size() - offset);
// fill it with values
for (int i = 0; i < num; ++i) {
// access from data store
const float value = data[i * stride + offset];
// write into memory represented by handle
ecl_kw_iset_float(*this, i, value);
}
}
/// Convenience constructor that takes the entire array
EclipseKeyword (const std::string& name,
const std::vector<T>& data)
: EclipseKeyword (name, data, data.size(), 0, 1) { }
/// Convenience constructor that gets the set of data
/// from the samely named item in the parser
EclipseKeyword (const std::string& name,
const EclipseGridParser& parser)
: EclipseKeyword (name, parser.getValue<T> (name)) { }
/// Constructor for optional fields
EclipseKeyword (const std::string& name)
: EclipseHandle (0, ecl_kw_free) {
static_cast<void> (name);
}
private:
/// Map the C++ data type (given by T) to an Eclipse type enum
static ecl_type_enum type ();
};
// specializations for known keyword types
template <> ecl_type_enum EclipseKeyword<int >::type () { return ECL_INT_TYPE ; }
template <> ecl_type_enum EclipseKeyword<double>::type () { return ECL_FLOAT_TYPE; }
/**
* Extract the current time from a timer object into the C type used by ERT.
*/
time_t current (const SimulatorTimer& timer) {
tm t = boost::posix_time::to_tm (timer.currentDateTime());
return mktime(&t);
}
namespace Opm {
void BlackoilEclipseOutputWriter::writeInitFile(const SimulatorTimer &timer)
{
#if HAVE_ERT
writeGridInitFile_(timer);
writeSummaryHeaderFile_(timer);
#else
OPM_THROW(std::runtime_error,
"The ERT libraries are required to write ECLIPSE output files.");
#endif // HAVE_ERT
}
/**
* Pointer to memory that holds the name to an Eclipse output file.
*/
struct EclipseFileName : public EclipseHandle <const char> {
EclipseFileName (const std::string& outputDir,
const std::string& baseName,
ecl_file_enum type,
const SimulatorTimer& timer)
// filename formatting function returns a pointer to allocated
// memory that must be released with the free() function
: EclipseHandle (ecl_util_alloc_filename (outputDir.c_str(),
baseName.c_str(),
type,
false, // formatted?
timer.currentStepNum ()),
EclipseFileName::freestr) { }
private:
/// Facade which allows us to free a const char*
static void freestr (const char* ptr) {
::free (const_cast<char*>(ptr));
}
};
struct EclipseRestart : public EclipseHandle <ecl_rst_file_type> {
EclipseRestart (const std::string& outputDir,
const std::string& baseName,
const SimulatorTimer& timer)
// notice the poor man's polymorphism of the allocation function
: EclipseHandle ((timer.currentStepNum () > 0 ? ecl_rst_file_open_append
: ecl_rst_file_open_write)(
EclipseFileName (outputDir,
baseName,
ECL_UNIFIED_RESTART_FILE,
timer)),
ecl_rst_file_close) { }
void writeHeader (const UnstructuredGrid& grid,
const SimulatorTimer& timer,
const int phases) {
ecl_rst_file_fwrite_header (*this,
timer.currentStepNum (),
current (timer),
Opm::unit::convert::to (timer.currentTime (),
Opm::unit::day),
grid.cartdims[0],
grid.cartdims[1],
grid.cartdims[2],
grid.number_of_cells,
phases);
}
};
/**
* The EclipseSolution class wraps the actions that must be done to the
* restart file while writing solution variables; it is not a handle on
* its own.
*/
struct EclipseSolution : public EclipseHandle <ecl_rst_file_type> {
EclipseSolution (EclipseRestart& rst_file)
: EclipseHandle (start_solution (rst_file),
ecl_rst_file_end_solution) { }
template <typename T>
void add (const EclipseKeyword<T>& kw) {
ecl_rst_file_add_kw (*this, kw);
}
private:
/// Helper method to call function *and* return the handle
static ecl_rst_file_type* start_solution (EclipseRestart& rst_file) {
ecl_rst_file_start_solution (rst_file);
return rst_file;
}
};
static double pasToBar (double pressureInPascal) {
return Opm::unit::convert::to (pressureInPascal, Opm::unit::barsa);
}
void BlackoilEclipseOutputWriter::writeReservoirState(const BlackoilState& reservoirState, const SimulatorTimer& timer)
{
#if HAVE_ERT
EclipseRestart rst (outputDir_,
baseName_,
timer);
rst.writeHeader (grid_,
timer,
ECL_OIL_PHASE | ECL_GAS_PHASE | ECL_WATER_PHASE);
EclipseSolution sol (rst);
// convert the pressures from Pascals to bar because eclipse
// seems to write bars
const std::vector<double>& pas = reservoirState.pressure ();
std::vector<double> bar (pas.size (), 0.);
std::transform (pas.begin(), pas.end(), bar.begin(), pasToBar);
sol.add (EclipseKeyword<double> ("PRESSURE", bar));
sol.add (EclipseKeyword<double> ("SWAT",
reservoirState.saturation(),
grid_.number_of_cells,
BlackoilPhases::Aqua,
BlackoilPhases::MaxNumPhases));
sol.add (EclipseKeyword<double> ("SOIL",
reservoirState.saturation(),
grid_.number_of_cells,
BlackoilPhases::Liquid,
BlackoilPhases::MaxNumPhases));
sol.add (EclipseKeyword<double> ("SGAS",
reservoirState.saturation(),
grid_.number_of_cells,
BlackoilPhases::Vapour,
BlackoilPhases::MaxNumPhases));
#else
OPM_THROW(std::runtime_error,
"The ERT libraries are required to write ECLIPSE output files.");
#endif // HAVE_ERT
}
/**
* Representation of an Eclipse grid.
*/
struct EclipseGrid : public EclipseHandle <ecl_grid_type> {
/// Create a grid based on the keywords available in input file
static EclipseGrid make (const EclipseGridParser& parser) {
if (parser.hasField("DXV")) {
// make sure that the DYV and DZV keywords are present if the
// DXV keyword is used in the deck...
assert(parser.hasField("DYV"));
assert(parser.hasField("DZV"));
const auto& dxv = parser.getFloatingPointValue("DXV");
const auto& dyv = parser.getFloatingPointValue("DYV");
const auto& dzv = parser.getFloatingPointValue("DZV");
return EclipseGrid (dxv, dyv, dzv);
}
else if (parser.hasField("ZCORN")) {
struct grdecl g = parser.get_grdecl ();
EclipseKeyword<double> coord_kw (COORD_KW, parser);
EclipseKeyword<double> zcorn_kw (ZCORN_KW, parser);
EclipseKeyword<double> actnum_kw (ACTNUM_KW, parser);
EclipseKeyword<double> mapaxes_kw (MAPAXES_KW);
if (g.mapaxes) {
mapaxes_kw = std::move (EclipseKeyword<double> (MAPAXES_KW, parser));
}
return EclipseGrid (g.dims, zcorn_kw, coord_kw, actnum_kw, mapaxes_kw);
}
else {
OPM_THROW(std::runtime_error,
"Can't create an ERT grid (no supported keywords found in deck)");
}
}
/**
* Save the grid in an .EGRID file.
*/
void write (const std::string& outputDir,
const std::string& baseName,
const SimulatorTimer& timer) {
ecl_grid_fwrite_EGRID (*this,
EclipseFileName (outputDir,
baseName,
ECL_EGRID_FILE,
timer));
}
private:
// each of these cases could have been their respective subclass,
// but there is not any polymorphism on each of these grid types
// once we have the handle
// setup smart pointer for Cartesian grid
EclipseGrid (const std::vector<double>& dxv,
const std::vector<double>& dyv,
const std::vector<double>& dzv)
: EclipseHandle (ecl_grid_alloc_dxv_dyv_dzv (dxv.size (),
dyv.size (),
dzv.size (),
&dxv[0],
&dyv[0],
&dzv[0],
NULL),
ecl_grid_free) { }
// setup smart pointer for cornerpoint grid
EclipseGrid (const int dims[],
const EclipseKeyword<double>& coord,
const EclipseKeyword<double>& zcorn,
const EclipseKeyword<double>& actnum,
const EclipseKeyword<double>& mapaxes)
: EclipseHandle (ecl_grid_alloc_GRDECL_kw(dims[0],
dims[1],
dims[2],
zcorn,
coord,
actnum,
mapaxes),
ecl_grid_free) { }
};
2013-11-04 07:27:29 -06:00
/**
* Initialization file which contains static properties (such as
* porosity and permeability) for the simulation field.
*/
struct EclipseInit : public EclipseHandle <fortio_type> {
// contrary to the grid, the location of the file goes here because
// there is only one construction method but several write methods
// (but we need to do a bit of logic before we can call the actual
// constructor, so we'll have to do with a static wrapper)
static EclipseInit make (const std::string& outputDir,
const std::string& baseName,
const SimulatorTimer& timer) {
EclipseFileName initFileName (outputDir,
baseName,
ECL_INIT_FILE,
timer);
bool fmt_file;
if (!ecl_util_fmt_file(initFileName, &fmt_file)) {
OPM_THROW(std::runtime_error,
"Could not determine formatted/unformatted status of file:" << initFileName << " non-standard name?" << std::endl);
}
return EclipseInit (initFileName, fmt_file);
}
void writeHeader (const EclipseGrid& grid,
const SimulatorTimer& timer,
const EclipseGridParser& parser,
const int phases) {
EclipseKeyword<double> poro (PORO_KW, parser);
ecl_init_file_fwrite_header (*this,
grid,
poro,
phases,
current (timer));
}
template <typename T>
void writeKeyword (const std::string& keyword,
const EclipseGridParser& parser) {
EclipseKeyword <T> kw (keyword, parser);
ecl_kw_fwrite (kw, *this);
}
private:
EclipseInit (const EclipseFileName& fname, const bool formatted)
: EclipseHandle (fortio_open_writer (fname, formatted, ECL_ENDIAN_FLIP),
fortio_fclose) { }
};
#if HAVE_ERT
void BlackoilEclipseOutputWriter::writeGridInitFile_(const SimulatorTimer &timer)
{
EclipseGrid ecl_grid = EclipseGrid::make (eclipseParser_);
ecl_grid.write (outputDir_, baseName_, timer);
2013-11-04 07:27:29 -06:00
EclipseInit fortio = EclipseInit::make (outputDir_, baseName_, timer);
fortio.writeHeader (ecl_grid,
timer,
eclipseParser_,
ECL_OIL_PHASE | ECL_GAS_PHASE | ECL_WATER_PHASE);
/* This collection of keywords is somewhat arbitrary and random. */
2013-11-04 07:27:29 -06:00
fortio.writeKeyword<double> ("PERMX", eclipseParser_);
fortio.writeKeyword<double> ("PERMY", eclipseParser_);
fortio.writeKeyword<double> ("PERMZ", eclipseParser_);
}
// forward decl. of mutually dependent type
struct EclipseWellReport;
struct EclipseSummary : public EclipseHandle <ecl_sum_type> {
EclipseSummary (const std::string& outputDir,
const std::string& baseName,
const SimulatorTimer& timer,
const UnstructuredGrid& grid)
: EclipseHandle (ecl_sum_alloc_writer (caseName (outputDir,
baseName).c_str (),
false, /* formatted */
true, /* unified */
":", /* join string */
current (timer),
grid.cartdims[0],
grid.cartdims[1],
grid.cartdims[2]),
ecl_sum_free) { }
typedef std::unique_ptr <EclipseWellReport> var_t;
typedef std::vector <var_t> vars_t;
EclipseSummary& add (var_t var) {
vars_.push_back (std::move (var));
return *this;
}
// no inline implementation of this since it depends on the
// EclipseWellReport type being completed first
void writeTimeStep (const SimulatorTimer& timer,
const WellState& wellState);
private:
vars_t vars_;
/// Make sure a new timestep is flushed to the summary section
struct EclipseTimeStep : public EclipseHandle <ecl_sum_tstep_type> {
EclipseTimeStep (const EclipseSummary& sum,
const SimulatorTimer& timer)
: EclipseHandle (ecl_sum_add_tstep (
sum,
timer.currentStepNum () + 1,
// currentTime is always relative to start
Opm::unit::convert::to (timer.currentTime (),
Opm::unit::day)),
ecl_sum_tstep_free)
, sum_ (sum) { }
~EclipseTimeStep () {
ecl_sum_fwrite (sum_);
}
private:
ecl_sum_type* sum_;
};
/// Helper function for getting the case name
std::string caseName (const std::string& outputDir,
const std::string& baseName) {
boost::filesystem::path casePath (outputDir);
casePath /= boost::to_upper_copy (baseName);
return casePath.string ();
}
};
/**
* Summary variable that reports a characteristics of a well.
*/
struct EclipseWellReport : public EclipseHandle <smspec_node_type> {
protected:
EclipseWellReport (const EclipseSummary& summary, /* section to add to */
const EclipseGridParser& parser, /* well names */
int whichWell, /* index of well line */
BlackoilPhases::PhaseIndex phase, /* oil, water or gas */
WellType type, /* prod. or inj. */
char aggregation, /* rate or total */
std::string unit)
: EclipseHandle (ecl_sum_add_var (summary,
varName (phase,
type,
aggregation).c_str (),
wellName (parser, whichWell).c_str (),
/* num = */ 0,
unit.c_str(),
/* defaultValue = */ 0.),
smspec_node_free)
// save these for when we update the value in a timestep
, index_ (whichWell * BlackoilPhases::MaxNumPhases + phase)
// injectors can be seen as negative producers
, sign_ (type == INJECTOR ? -1. : +1.) { }
public:
/// Allows us to pass this type to ecl_sum_tstep_iset
operator int () {
return smspec_node_get_params_index (*this);
}
/// Update the monitor according to the new state of the well, and
/// get the reported value. Note: Only call this once for each timestep.
virtual double update (const SimulatorTimer& timer,
const WellState& wellState) = 0;
private:
/// index into a (flattened) wells*phases matrix
const int index_;
/// natural sign of the rate
const double sign_;
/// Get the name associated with this well
std::string wellName (const EclipseGridParser& parser,
int whichWell) {
return parser.getWELSPECS().welspecs[whichWell].name_;
}
/// Compose the name of the summary variable, e.g. "WOPR" for
/// well oil production rate.
std::string varName (BlackoilPhases::PhaseIndex phase,
WellType type,
char aggregation) {
std::string name;
name += 'W'; // well
switch (phase) {
case BlackoilPhases::Aqua: name += 'W'; break; /* water */
case BlackoilPhases::Vapour: name += 'G'; break; /* gas */
case BlackoilPhases::Liquid: name += 'O'; break; /* oil */
default:
OPM_THROW(std::runtime_error,
"Unknown phase used in blackoil reporting");
}
switch (type) {
case WellType::INJECTOR: name += 'I'; break;
case WellType::PRODUCER: name += 'P'; break;
default:
OPM_THROW(std::runtime_error,
"Unknown well type used in blackoil reporting");
}
name += aggregation; /* rate ('R') or total ('T') */
return name;
}
protected:
double rate (const WellState& wellState) {
// convert m^3/s of injected fluid to m^3/d of produced fluid
const double convFactor = Opm::unit::convert::to (1., Opm::unit::day);
// TODO: Correct to flip sign to get positive values?
const double value = sign_ * wellState.wellRates () [index_] * convFactor;
return value;
}
};
/// Monitors the rate given by a well.
struct EclipseWellRate : public EclipseWellReport {
EclipseWellRate (const EclipseSummary& summary,
const EclipseGridParser& parser,
int whichWell,
BlackoilPhases::PhaseIndex phase,
WellType type)
: EclipseWellReport (summary,
parser,
whichWell,
phase,
type,
'R',
"SM3/DAY" /* surf. cub. m. per day */ ) { }
virtual double update (const SimulatorTimer& timer,
const WellState& wellState) {
// TODO: Why only positive rates?
return std::max (0., rate (wellState));
}
};
/// Monitors the total production in a well.
struct EclipseWellTotal : public EclipseWellReport {
EclipseWellTotal (const EclipseSummary& summary,
const EclipseGridParser& parser,
int whichWell,
BlackoilPhases::PhaseIndex phase,
WellType type)
: EclipseWellReport (summary,
parser,
whichWell,
phase,
type,
'T',
"SM3" /* surface cubic meter */ )
// nothing produced when the reporting starts
, total_ (0.) { }
virtual double update (const SimulatorTimer& timer,
const WellState& wellState) {
// TODO: Is the rate average for the timestep, or is in
// instantaneous (in which case trapezoidal or Simpson integration
// would probably be better)
const double intg = timer.currentStepLength () * rate (wellState);
// add this timesteps production to the total
total_ += intg;
// report the new production total
return total_;
}
private:
/// Aggregated value of the course of the simulation
double total_;
};
inline void
EclipseSummary::writeTimeStep (const SimulatorTimer& timer,
const WellState& wellState) {
EclipseTimeStep tstep (*this, timer);
// write all the variables
for (vars_t::iterator v = vars_.begin(); v != vars_.end(); ++v) {
const double value = (*v)->update (timer, wellState);
ecl_sum_tstep_iset(tstep, *(*v).get (), value);
}
}
void BlackoilEclipseOutputWriter::writeWellState(const WellState& wellState, const SimulatorTimer& timer)
{
#if HAVE_ERT
sum_->writeTimeStep (timer, wellState);
#else
OPM_THROW(std::runtime_error,
"The ERT libraries are required to write ECLIPSE output files.");
#endif // HAVE_ERT
}
static WellType WELL_TYPES[] = { INJECTOR, PRODUCER };
void BlackoilEclipseOutputWriter::writeSummaryHeaderFile_(const SimulatorTimer &timer)
{
sum_ = std::move (std::unique_ptr <EclipseSummary> (
new EclipseSummary (outputDir_,
baseName_,
timer,
grid_)));
// TODO: Only create report variables that are requested with keywords
// (e.g. "WOPR") in the input files, and only for those wells that are
// mentioned in those keywords
const int numWells = eclipseParser_.getWELSPECS().welspecs.size();
for (int whichWell = 0; whichWell != numWells; ++whichWell) {
for (BlackoilPhases::PhaseIndex phase = static_cast<BlackoilPhases::PhaseIndex>(0);
phase != BlackoilPhases::MaxNumPhases;
++phase) {
for (int typeIndex = 0;
typeIndex < sizeof (WELL_TYPES) / sizeof (WELL_TYPES[0]);
++typeIndex) {
const WellType type = WELL_TYPES[typeIndex];
// W{O,G,W}{I,P}R
sum_->add (std::unique_ptr <EclipseWellReport> (
new EclipseWellRate (*sum_,
eclipseParser_,
whichWell,
phase,
type)));
// W{O,G,W}{I,P}T
sum_->add (std::unique_ptr <EclipseWellReport> (
new EclipseWellTotal (*sum_,
eclipseParser_,
whichWell,
phase,
type)));
}
}
}
// flush after all variables are allocated
ecl_sum_fwrite(*sum_);
}
#endif // HAVE_ERT
} // namespace Opm