/*
Copyright (c) 2013-2014 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 .
*/
#include "config.h"
#include "EclipseWriter.hpp"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include // WellType
#include
#include
#include
#include
#include
#include
#include // to_upper_copy
#include
#include // path
#include // mktime
#include
#include // unique_ptr
#include // move
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
// namespace start here since we don't want the ERT headers in it
namespace Opm {
namespace EclipseWriterDetails {
/// Names of the saturation property for each phase. The order of these
/// names are critical; they must be the same as the BlackoilPhases enum
static const char* saturationKeywordNames[] = { "SWAT", "SOIL", "SGAS" };
// throw away the data for all non-active cells and reorder to the Cartesian logic of
// eclipse
void restrictAndReorderToActiveCells(std::vector &data,
int numCells,
const int* compressedToCartesianCellIdx)
{
if (!compressedToCartesianCellIdx)
// if there is no active -> global mapping, all cells
// are considered active
return;
std::vector eclData;
eclData.reserve( numCells );
// activate those cells that are actually there
for (int i = 0; i < numCells; ++i) {
eclData.push_back( data[ compressedToCartesianCellIdx[i] ] );
}
data.swap( eclData );
}
// convert the units of an array
void convertFromSiTo(std::vector &siValues, double toSiConversionFactor)
{
for (size_t curIdx = 0; curIdx < siValues.size(); ++curIdx) {
siValues[curIdx] = unit::convert::to(siValues[curIdx], toSiConversionFactor);
}
}
// extract a sub-array of a larger one which represents multiple
// striped ones
void extractFromStripedData(std::vector &data,
int offset,
int stride)
{
size_t tmpIdx = 0;
for (size_t curIdx = offset; curIdx < data.size(); curIdx += stride) {
assert(tmpIdx <= curIdx);
data[tmpIdx] = data[curIdx];
++tmpIdx;
}
// shirk the result
data.resize(tmpIdx);
}
/// Convert OPM phase usage to ERT bitmask
int ertPhaseMask(const PhaseUsage uses)
{
return (uses.phase_used[BlackoilPhases::Liquid] ? ECL_OIL_PHASE : 0)
| (uses.phase_used[BlackoilPhases::Aqua] ? ECL_WATER_PHASE : 0)
| (uses.phase_used[BlackoilPhases::Vapour] ? ECL_GAS_PHASE : 0);
}
/**
* Eclipse "keyword" (i.e. named data) for a vector.
*/
template
class Keyword : private boost::noncopyable
{
public:
// Default constructor
Keyword()
: ertHandle_(0)
{}
/// Initialization from double-precision array.
Keyword(const std::string& name,
const std::vector& data)
: ertHandle_(0)
{ set(name, data); }
/// Initialization from double-precision array.
Keyword(const std::string& name,
const std::vector& data)
: ertHandle_(0)
{ set(name, data); }
Keyword(const std::string& name,
const std::vector& data)
: ertHandle_(0)
{set(name, data); }
~Keyword()
{
if (ertHandle_)
ecl_kw_free(ertHandle_);
}
template
void set(const std::string name, const std::vector& data)
{
if(ertHandle_) {
ecl_kw_free(ertHandle_);
}
ertHandle_ = ecl_kw_alloc(name.c_str(),
data.size(),
ertType_());
// number of elements to take
const int numEntries = data.size();
// fill it with values
T* target = static_cast(ecl_kw_get_ptr(ertHandle()));
for (int i = 0; i < numEntries; ++i) {
target[i] = static_cast(data[i]);
}
}
void set(const std::string name, const std::vector& data)
{
if(ertHandle_) {
ecl_kw_free(ertHandle_);
}
ertHandle_ = ecl_kw_alloc(name.c_str(),
data.size(),
ertType_());
// number of elements to take
const int numEntries = data.size();
for (int i = 0; i < numEntries; ++i) {
ecl_kw_iset_char_ptr( ertHandle_, i, data[i]);
}
}
ecl_kw_type *ertHandle() const
{ return ertHandle_; }
private:
static ecl_type_enum ertType_()
{
if (std::is_same::value)
{ return ECL_FLOAT_TYPE; }
if (std::is_same::value)
{ return ECL_DOUBLE_TYPE; }
if (std::is_same::value)
{ return ECL_INT_TYPE; }
if (std::is_same::value)
{ return ECL_CHAR_TYPE; }
OPM_THROW(std::logic_error,
"Unhandled type for data elements in EclipseWriterDetails::Keyword");
}
ecl_kw_type *ertHandle_;
};
/**
* Pointer to memory that holds the name to an Eclipse output file.
*/
class FileName : private boost::noncopyable
{
public:
FileName(const std::string& outputDir,
const std::string& baseName,
ecl_file_enum type,
int writeStepIdx)
{
ertHandle_ = ecl_util_alloc_filename(outputDir.c_str(),
baseName.c_str(),
type,
false, // formatted?
writeStepIdx);
}
~FileName()
{ std::free(ertHandle_); }
const char *ertHandle() const
{ return ertHandle_; }
private:
char *ertHandle_;
};
class Restart : private boost::noncopyable
{
public:
static const int NIWELZ = 11; //Number of data elements per well in IWEL array in restart file
static const int NZWELZ = 3; //Number of 8-character words per well in ZWEL array restart file
static const int NICONZ = 14; //Number of data elements per completion in ICON array restart file
/**
* The constants NIWELZ and NZWELZ referes to the number of elements per well that we write to
* the IWEL and ZWEL eclipse restart file data arrays. The constant NICONZ refers to the number of
* elements per completion in the eclipse restart file ICON data array.These numbers are written
* to the INTEHEAD header.
* The elements are added in the methods addRestartFileIwelData(...), addRestartFileZwelData(...)
* and addRestartFileIconData(...), respectively.
* We write as many elements that we need to be able to view the restart file in Resinsight.
* The restart file will not be possible to open with Eclipse, we write to little information to
* be able to do this.
*/
Restart(const std::string& outputDir,
const std::string& baseName,
int writeStepIdx)
{
restartFileName_ = ecl_util_alloc_filename(outputDir.c_str(),
baseName.c_str(),
/*type=*/ECL_UNIFIED_RESTART_FILE,
false, // use formatted instead of binary output?
writeStepIdx);
if (writeStepIdx == 0) {
restartFileHandle_ = ecl_rst_file_open_write(restartFileName_);
}
else {
restartFileHandle_ = ecl_rst_file_open_append(restartFileName_);
}
}
template
void add_kw(const Keyword& kw)
{ ecl_rst_file_add_kw(restartFileHandle_, kw.ertHandle()); }
void addRestartFileIwelData(std::vector& iwel_data, size_t currentStep, WellConstPtr well_ptr) const {
iwel_data.reserve(iwel_data.size() + Opm::EclipseWriterDetails::Restart::NIWELZ);
int eclipse_offset = 1;
iwel_data.push_back(well_ptr->getHeadI() + eclipse_offset); // item 1 - gridhead I value
iwel_data.push_back(well_ptr->getHeadJ() + eclipse_offset); // item 2 - gridhead J value
iwel_data.push_back(0); // item 3 - gridhead K value
iwel_data.push_back(0); // item 4 - undefined - 0
CompletionSetConstPtr completions_ptr = well_ptr->getCompletions(currentStep);
int num_completions = completions_ptr->size();
iwel_data.push_back(num_completions); // item 5 - number of completions
iwel_data.push_back(1); // item 6 - for now, set all group indexes to 1
WellType welltype = well_ptr->isProducer(currentStep) ? PRODUCER : INJECTOR;
int ert_welltype = EclipseWriter::eclipseWellTypeMask(welltype, well_ptr->getInjectionProperties(currentStep).injectorType);
iwel_data.push_back(ert_welltype); // item 7 - welltype
iwel_data.insert(iwel_data.end(), 3, 0); //items 8,9,10 - undefined - 0
iwel_data.push_back(EclipseWriter::eclipseWellStatusMask(well_ptr->getStatus(currentStep))); // item 11 - well status
}
void addRestartFileZwelData(std::vector& zwel_data, size_t currentstep, WellConstPtr well_ptr) const {
zwel_data.reserve(zwel_data.size() + Opm::EclipseWriterDetails::Restart::NZWELZ);
zwel_data.push_back(well_ptr->name().c_str());
zwel_data.push_back("");
zwel_data.push_back("");
}
void addRestartFileIconData(std::vector& icon_data, size_t currentstep, int ncwmax, WellConstPtr well_ptr) const {
icon_data.reserve(icon_data.size() + Opm::EclipseWriterDetails::Restart::NICONZ * ncwmax);
CompletionSetConstPtr completions_set_ptr = well_ptr->getCompletions(currentstep);
int zero_pad = ncwmax - completions_set_ptr->size();
for (size_t i = 0; i < completions_set_ptr->size(); ++i) {
CompletionConstPtr completion_ptr = completions_set_ptr->get(i);
icon_data.push_back(1);
int eclipse_offset = 1;
icon_data.push_back(completion_ptr->getI() + eclipse_offset);
icon_data.push_back(completion_ptr->getJ() + eclipse_offset) ;
icon_data.push_back(completion_ptr->getK() + eclipse_offset);
icon_data.push_back(0);
WellCompletion::StateEnum completion_state = completion_ptr->getState();
if (completion_state == WellCompletion::StateEnum::OPEN) {
icon_data.push_back(1);
} else {
icon_data.push_back(0);
}
icon_data.insert(icon_data.end(), 7, 0);
icon_data.push_back((int)completion_ptr->getDirection());
}
for(int i=0;iertHandle()); }
~Solution()
{ ecl_rst_file_end_solution(restartHandle_->ertHandle()); }
template
void add(const Keyword& kw)
{ ecl_rst_file_add_kw(restartHandle_->ertHandle(), kw.ertHandle()); }
ecl_rst_file_type *ertHandle() const
{ return restartHandle_->ertHandle(); }
private:
Restart* restartHandle_;
};
/// Supported well types. Enumeration doesn't let us get all the members,
/// so we must have an explicit array.
static WellType WELL_TYPES[] = { INJECTOR, PRODUCER };
class WellReport;
class Summary : private boost::noncopyable
{
public:
Summary(const std::string& outputDir,
const std::string& baseName,
const SimulatorTimerInterface& timer,
int nx,
int ny,
int nz)
{
boost::filesystem::path casePath(outputDir);
casePath /= boost::to_upper_copy(baseName);
ertHandle_ = ecl_sum_alloc_writer(casePath.string().c_str(),
false, /* formatted */
true, /* unified */
":", /* join string */
timer.simulationTimeElapsed(),
nx,
ny,
nz);
}
~Summary()
{ ecl_sum_free(ertHandle_); }
typedef std::unique_ptr SummaryReportVar;
typedef std::vector SummaryReportVarCollection;
Summary& addWell(SummaryReportVar var)
{
summaryReportVars_.push_back(std::move(var));
return *this;
}
// no inline implementation of these two methods since they depend
// on the classes defined in the following.
// add rate variables for each of the well in the input file
void addAllWells(Opm::EclipseStateConstPtr eclipseState,
const PhaseUsage& uses);
void writeTimeStep(int writeStepIdx,
const SimulatorTimerInterface& timer,
const WellState& wellState);
ecl_sum_type *ertHandle() const
{ return ertHandle_; }
private:
ecl_sum_type *ertHandle_;
Opm::EclipseStateConstPtr eclipseState_;
SummaryReportVarCollection summaryReportVars_;
};
class SummaryTimeStep : private boost::noncopyable
{
public:
SummaryTimeStep(Summary& summaryHandle,
int writeStepIdx,
const SimulatorTimerInterface &timer)
{
ertHandle_ = ecl_sum_add_tstep(summaryHandle.ertHandle(),
writeStepIdx,
Opm::unit::convert::to(timer.simulationTimeElapsed(),
Opm::unit::day));
}
// no destructor in this class as ERT takes care of freeing the
// handle as part of freeing the solution handle!
ecl_sum_tstep_type *ertHandle() const
{ return ertHandle_; };
private:
ecl_sum_tstep_type *ertHandle_;
};
/**
* Initialization file which contains static properties (such as
* porosity and permeability) for the simulation field.
*/
class Init : private boost::noncopyable
{
public:
Init(const std::string& outputDir,
const std::string& baseName,
int writeStepIdx)
: egridFileName_(outputDir,
baseName,
ECL_EGRID_FILE,
writeStepIdx)
{
FileName initFileName(outputDir,
baseName,
ECL_INIT_FILE,
writeStepIdx);
bool isFormatted;
if (!ecl_util_fmt_file(initFileName.ertHandle(), &isFormatted)) {
OPM_THROW(std::runtime_error,
"Could not determine formatted/unformatted status of file:" << initFileName.ertHandle() << " non-standard name?" << std::endl);
}
ertHandle_ = fortio_open_writer(initFileName.ertHandle(),
isFormatted,
ECL_ENDIAN_FLIP);
}
~Init()
{ fortio_fclose(ertHandle_); }
void writeHeader(int numCells,
const int* compressedToCartesianCellIdx,
const SimulatorTimerInterface& timer,
Opm::EclipseStateConstPtr eclipseState,
const PhaseUsage uses)
{
auto dataField = eclipseState->getDoubleGridProperty("PORO")->getData();
restrictAndReorderToActiveCells(dataField, numCells, compressedToCartesianCellIdx);
auto eclGrid = eclipseState->getEclipseGridCopy();
// update the ACTNUM array using the processed cornerpoint grid
std::vector actnumData(eclGrid->getCartesianSize(), 1);
if (compressedToCartesianCellIdx) {
std::fill(actnumData.begin(), actnumData.end(), 0);
for (int cellIdx = 0; cellIdx < numCells; ++cellIdx) {
int cartesianCellIdx = compressedToCartesianCellIdx[cellIdx];
actnumData[cartesianCellIdx] = 1;
}
}
eclGrid->resetACTNUM(&actnumData[0]);
// finally, write the grid to disk
eclGrid->fwriteEGRID(egridFileName_.ertHandle());
Keyword poro_kw("PORO", dataField);
ecl_init_file_fwrite_header(ertHandle(),
eclGrid->c_ptr(),
poro_kw.ertHandle(),
ertPhaseMask(uses),
timer.currentPosixTime());
}
void writeKeyword(const std::string& keywordName, const std::vector &data)
{
Keyword kw(keywordName, data);
ecl_kw_fwrite(kw.ertHandle(), ertHandle());
}
fortio_type *ertHandle() const
{ return ertHandle_; }
private:
fortio_type *ertHandle_;
FileName egridFileName_;
};
/**
* Summary variable that reports a characteristics of a well.
*/
class WellReport : private boost::noncopyable
{
protected:
WellReport(const Summary& summary, /* section to add to */
Opm::EclipseStateConstPtr eclipseState,
Opm::WellConstPtr& well,
PhaseUsage uses, /* phases present */
BlackoilPhases::PhaseIndex phase, /* oil, water or gas */
WellType type, /* prod. or inj. */
char aggregation, /* rate or total */
std::string unit)
// save these for when we update the value in a timestep
: eclipseState_(eclipseState)
, well_(well)
, phaseUses_(uses)
, phaseIdx_(phase)
{
// producers can be seen as negative injectors
if (type == INJECTOR)
sign_ = +1.0;
else
sign_ = -1.0;
ertHandle_ = ecl_sum_add_var(summary.ertHandle(),
varName_(phase,
type,
aggregation).c_str(),
well_->name().c_str(),
/*num=*/ 0,
unit.c_str(),
/*defaultValue=*/ 0.);
}
public:
/// Retrieve the value which the monitor is supposed to write to the summary file
/// according to the state of the well.
virtual double retrieveValue(const int writeStepIdx,
const SimulatorTimerInterface& timer,
const WellState& wellState,
const std::map& nameToIdxMap) = 0;
smspec_node_type *ertHandle() const
{ return ertHandle_; }
protected:
void updateTimeStepWellIndex_(const std::map& nameToIdxMap)
{
const std::string& wellName = well_->name();
const auto wellIdxIt = nameToIdxMap.find(wellName);
if (wellIdxIt == nameToIdxMap.end()) {
timeStepWellIdx_ = -1;
flatIdx_ = -1;
return;
}
timeStepWellIdx_ = wellIdxIt->second;
flatIdx_ = timeStepWellIdx_*phaseUses_.num_phases + phaseUses_.phase_pos[phaseIdx_];
}
// return m^3/s of injected or produced fluid
double rate(const WellState& wellState)
{
double value = 0;
if (wellState.wellRates().size() > 0) {
assert(int(wellState.wellRates().size()) > flatIdx_);
value = sign_ * wellState.wellRates()[flatIdx_];
}
return value;
}
double bhp(const WellState& wellState)
{
if (wellState.bhp().size() > 0) {
// Note that 'flatIdx_' is used here even though it is meant
// to give a (well,phase) pair.
const int numPhases = wellState.wellRates().size() / wellState.bhp().size();
return wellState.bhp()[flatIdx_/numPhases];
}
return 0.0;
}
/// Get the index associated a well name
int wellIndex_(Opm::EclipseStateConstPtr eclipseState)
{
const Opm::ScheduleConstPtr schedule = eclipseState->getSchedule();
const std::string& wellName = well_->name();
const auto& wells = schedule->getWells();
for (size_t wellIdx = 0; wellIdx < wells.size(); ++wellIdx) {
if (wells[wellIdx]->name() == wellName) {
return wellIdx;
}
}
OPM_THROW(std::runtime_error,
"Well '" << wellName << "' is not present in deck");
}
/// 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
if (aggregation == 'B') {
name += "BHP";
} else {
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;
}
smspec_node_type *ertHandle_;
Opm::EclipseStateConstPtr eclipseState_;
Opm::WellConstPtr well_;
PhaseUsage phaseUses_;
BlackoilPhases::PhaseIndex phaseIdx_;
int timeStepWellIdx_;
/// index into a (flattened) wellsOfTimeStep*phases matrix
int flatIdx_;
/// natural sign of the rate
double sign_;
};
/// Monitors the rate given by a well.
class WellRate : public WellReport
{
public:
WellRate(const Summary& summary,
Opm::EclipseStateConstPtr eclipseState,
Opm::WellConstPtr well,
PhaseUsage uses,
BlackoilPhases::PhaseIndex phase,
WellType type)
: WellReport(summary,
eclipseState,
well,
uses,
phase,
type,
'R',
"SM3/DAY" /* surf. cub. m. per day */)
{ }
virtual double retrieveValue(const int writeStepIdx,
const SimulatorTimerInterface& timer,
const WellState& wellState,
const std::map& wellNameToIdxMap)
{
// find the index for the quantity in the wellState
this->updateTimeStepWellIndex_(wellNameToIdxMap);
if (this->flatIdx_ < 0) {
// well not active in current time step
return 0.0;
}
if (well_->getStatus(timer.reportStepNum()) == WellCommon::SHUT) {
// well is shut in the current time step
return 0.0;
}
// TODO: Why only positive rates?
using namespace Opm::unit;
return convert::to(std::max(0., rate(wellState)),
cubic(meter)/day);
}
};
/// Monitors the total production in a well.
class WellTotal : public WellReport
{
public:
WellTotal(const Summary& summary,
Opm::EclipseStateConstPtr eclipseState,
Opm::WellConstPtr well,
PhaseUsage uses,
BlackoilPhases::PhaseIndex phase,
WellType type)
: WellReport(summary,
eclipseState,
well,
uses,
phase,
type,
'T',
"SM3" /* surface cubic meter */ )
// nothing produced when the reporting starts
, total_(0.)
{ }
virtual double retrieveValue(const int writeStepIdx,
const SimulatorTimerInterface& timer,
const WellState& wellState,
const std::map& wellNameToIdxMap)
{
if (writeStepIdx == 0) {
// We are at the initial state.
// No step has been taken yet.
return 0.0;
}
if (well_->getStatus(timer.reportStepNum()) == WellCommon::SHUT) {
// well is shut in the current time step
return 0.0;
}
// find the index for the quantity in the wellState
this->updateTimeStepWellIndex_(wellNameToIdxMap);
if (this->flatIdx_ < 0) {
// well not active in current time step
return 0.0;
}
// due to using an Euler method as time integration scheme, the well rate is the
// average for the time step. For more complicated time stepping schemes, the
// integral of the rate is not simply multiplying two numbers...
const double intg = timer.stepLengthTaken() * 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_;
};
/// Monitors the bottom hole pressure in a well.
class WellBhp : public WellReport
{
public:
WellBhp(const Summary& summary,
Opm::EclipseStateConstPtr eclipseState,
Opm::WellConstPtr well,
PhaseUsage uses,
BlackoilPhases::PhaseIndex phase,
WellType type)
: WellReport(summary,
eclipseState,
well,
uses,
phase,
type,
'B',
"Pascal")
{ }
virtual double retrieveValue(const int writeStepIdx,
const SimulatorTimerInterface& timer,
const WellState& wellState,
const std::map& wellNameToIdxMap)
{
// find the index for the quantity in the wellState
this->updateTimeStepWellIndex_(wellNameToIdxMap);
if (this->flatIdx_ < 0) {
// well not active in current time step
return 0.0;
}
if (well_->getStatus(timer.reportStepNum()) == WellCommon::SHUT) {
// well is shut in the current time step
return 0.0;
}
return bhp(wellState);
}
};
// no inline implementation of this since it depends on the
// WellReport type being completed first
void Summary::writeTimeStep(int writeStepIdx,
const SimulatorTimerInterface& timer,
const WellState& wellState)
{
// create a name -> well index map
const Opm::ScheduleConstPtr schedule = eclipseState_->getSchedule();
const auto& timeStepWells = schedule->getWells(timer.reportStepNum());
std::map wellNameToIdxMap;
int openWellIdx = 0;
for (size_t tsWellIdx = 0; tsWellIdx < timeStepWells.size(); ++tsWellIdx) {
if (timeStepWells[tsWellIdx]->getStatus(timer.reportStepNum()) != WellCommon::SHUT ) {
wellNameToIdxMap[timeStepWells[tsWellIdx]->name()] = openWellIdx;
openWellIdx++;
}
}
// internal view; do not move this code out of Summary!
SummaryTimeStep tstep(*this, writeStepIdx, timer);
// write all the variables
for (auto varIt = summaryReportVars_.begin(); varIt != summaryReportVars_.end(); ++varIt) {
ecl_sum_tstep_iset(tstep.ertHandle(),
smspec_node_get_params_index((*varIt)->ertHandle()),
(*varIt)->retrieveValue(writeStepIdx, timer, wellState, wellNameToIdxMap));
}
// write the summary file to disk
ecl_sum_fwrite(ertHandle());
}
void Summary::addAllWells(Opm::EclipseStateConstPtr eclipseState,
const PhaseUsage& uses)
{
eclipseState_ = eclipseState;
// 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
Opm::ScheduleConstPtr schedule = eclipseState->getSchedule();
const auto& wells = schedule->getWells();
const int numWells = schedule->numWells();
for (int phaseIdx = 0; phaseIdx != BlackoilPhases::MaxNumPhases; ++phaseIdx) {
const BlackoilPhases::PhaseIndex ertPhaseIdx =
static_cast (phaseIdx);
// don't bother with reporting for phases that aren't there
if (!uses.phase_used[phaseIdx]) {
continue;
}
size_t numWellTypes = sizeof(WELL_TYPES) / sizeof(WELL_TYPES[0]);
for (size_t wellTypeIdx = 0; wellTypeIdx < numWellTypes; ++wellTypeIdx) {
const WellType wellType = WELL_TYPES[wellTypeIdx];
for (int wellIdx = 0; wellIdx != numWells; ++wellIdx) {
// W{O,G,W}{I,P}R
addWell(std::unique_ptr (
new WellRate(*this,
eclipseState,
wells[wellIdx],
uses,
ertPhaseIdx,
wellType)));
// W{O,G,W}{I,P}T
addWell(std::unique_ptr (
new WellTotal(*this,
eclipseState,
wells[wellIdx],
uses,
ertPhaseIdx,
wellType)));
}
}
}
// Add BHP monitors
for (int wellIdx = 0; wellIdx != numWells; ++wellIdx) {
// In the call below: uses, phase and the well type arguments
// are not used, except to set up an index that stores the
// well indirectly. For details see the implementation of the
// WellReport constructor, and the method
// WellReport::bhp().
BlackoilPhases::PhaseIndex ertPhaseIdx = BlackoilPhases::Liquid;
if (!uses.phase_used[BlackoilPhases::Liquid]) {
ertPhaseIdx = BlackoilPhases::Vapour;
}
addWell(std::unique_ptr (
new WellBhp(*this,
eclipseState,
wells[wellIdx],
uses,
ertPhaseIdx,
WELL_TYPES[0])));
}
}
} // end namespace EclipseWriterDetails
/**
* Convert opm-core WellType and InjectorType to eclipse welltype
*/
int EclipseWriter::eclipseWellTypeMask(WellType wellType, WellInjector::TypeEnum injectorType)
{
int ert_well_type = IWEL_UNDOCUMENTED_ZERO;
if (PRODUCER == wellType) {
ert_well_type = IWEL_PRODUCER;
} else if (INJECTOR == wellType) {
switch (injectorType) {
case WellInjector::WATER:
ert_well_type = IWEL_WATER_INJECTOR;
break;
case WellInjector::GAS:
ert_well_type = IWEL_GAS_INJECTOR;
break;
case WellInjector::OIL :
ert_well_type = IWEL_OIL_INJECTOR;
break;
default:
ert_well_type = IWEL_UNDOCUMENTED_ZERO;
}
}
return ert_well_type;
}
/**
* Convert opm-core WellStatus to eclipse format: > 0 open, <= 0 shut
*/
int EclipseWriter::eclipseWellStatusMask(WellCommon::StatusEnum wellStatus)
{
int well_status = 0;
if (wellStatus == WellCommon::OPEN) {
well_status = 1;
}
return well_status;
}
void EclipseWriter::writeInit(const SimulatorTimerInterface &timer)
{
// if we don't want to write anything, this method becomes a
// no-op...
if (!enableOutput_) {
return;
}
writeStepIdx_ = 0;
EclipseWriterDetails::Init fortio(outputDir_, baseName_, /*stepIdx=*/0);
fortio.writeHeader(numCells_,
compressedToCartesianCellIdx_,
timer,
eclipseState_,
phaseUsage_);
if (eclipseState_->hasDoubleGridProperty("PERMX")) {
auto data = eclipseState_->getDoubleGridProperty("PERMX")->getData();
EclipseWriterDetails::convertFromSiTo(data, Opm::prefix::milli * Opm::unit::darcy);
fortio.writeKeyword("PERMX", data);
}
if (eclipseState_->hasDoubleGridProperty("PERMY")) {
auto data = eclipseState_->getDoubleGridProperty("PERMY")->getData();
EclipseWriterDetails::convertFromSiTo(data, Opm::prefix::milli * Opm::unit::darcy);
fortio.writeKeyword("PERMY", data);
}
if (eclipseState_->hasDoubleGridProperty("PERMZ")) {
auto data = eclipseState_->getDoubleGridProperty("PERMZ")->getData();
EclipseWriterDetails::convertFromSiTo(data, Opm::prefix::milli * Opm::unit::darcy);
fortio.writeKeyword("PERMZ", data);
}
/* Create summary object (could not do it at construction time,
since it requires knowledge of the start time). */
auto eclGrid = eclipseState_->getEclipseGrid();
summary_.reset(new EclipseWriterDetails::Summary(outputDir_,
baseName_,
timer,
eclGrid->getNX(),
eclGrid->getNY(),
eclGrid->getNZ()));
summary_->addAllWells(eclipseState_, phaseUsage_);
}
// implementation of the writeTimeStep method
void EclipseWriter::writeTimeStep(const SimulatorTimerInterface& timer,
const SimulatorState& reservoirState,
const WellState& wellState)
{
// if we don't want to write anything, this method becomes a
// no-op...
if (!enableOutput_) {
return;
}
// respected the output_interval parameter
if (writeStepIdx_ % outputInterval_ != 0) {
return;
}
std::vector wells_ptr = eclipseState_->getSchedule()->getWells(timer.reportStepNum());
std::vector iwell_data;
std::vector zwell_data;
std::vector icon_data;
ecl_rsthead_type rsthead_data = { 0 };
rsthead_data.sim_time = timer.currentPosixTime();
rsthead_data.nactive = numCells_;
rsthead_data.nx = cartesianSize_[0];
rsthead_data.ny = cartesianSize_[1];
rsthead_data.nz = cartesianSize_[2];
rsthead_data.nwells = eclipseState_->getSchedule()->numWells(timer.reportStepNum());
rsthead_data.niwelz = 0;
rsthead_data.nzwelz = 0;
rsthead_data.niconz = 0;
rsthead_data.ncwmax = 0;
rsthead_data.phase_sum = Opm::EclipseWriterDetails::ertPhaseMask(phaseUsage_);
EclipseWriterDetails::Restart restartHandle(outputDir_, baseName_, writeStepIdx_);
for (std::vector::const_iterator c_iter = wells_ptr.begin(); c_iter != wells_ptr.end(); ++c_iter) {
WellConstPtr well_ptr = *c_iter;
rsthead_data.ncwmax = eclipseState_->getSchedule()->getMaxNumCompletionsForWells(timer.reportStepNum());
restartHandle.addRestartFileIwelData(iwell_data, timer.reportStepNum(), well_ptr);
restartHandle.addRestartFileZwelData(zwell_data, timer.reportStepNum(), well_ptr);
restartHandle.addRestartFileIconData(icon_data, timer.reportStepNum(), rsthead_data.ncwmax, well_ptr);
rsthead_data.niwelz = EclipseWriterDetails::Restart::NIWELZ;
rsthead_data.nzwelz = EclipseWriterDetails::Restart::NZWELZ;
rsthead_data.niconz = EclipseWriterDetails::Restart::NICONZ;
}
rsthead_data.sim_days = Opm::unit::convert::to(timer.simulationTimeElapsed(), Opm::unit::day); //data for doubhead
restartHandle.writeHeader(timer,
writeStepIdx_,
&rsthead_data);
restartHandle.add_kw(EclipseWriterDetails::Keyword(IWEL_KW, iwell_data));
restartHandle.add_kw(EclipseWriterDetails::Keyword(ZWEL_KW, zwell_data));
restartHandle.add_kw(EclipseWriterDetails::Keyword(ICON_KW, icon_data));
EclipseWriterDetails::Solution sol(restartHandle);
// write out the pressure of the reference phase (whatever phase that is...). this is
// not the most performant solution thinkable, but this is also not in the most
// performance critical code path!
//
// Also, we want to use the same units as the deck for pressure output, i.e. we have
// to mutliate our nice SI pressures by the inverse of the conversion factor of deck
// to SI pressure units...
std::vector tmp = reservoirState.pressure();
EclipseWriterDetails::convertFromSiTo(tmp, deckToSiPressure_);
EclipseWriterDetails::restrictAndReorderToActiveCells(tmp, gridToEclipseIdx_.size(), gridToEclipseIdx_.data());
sol.add(EclipseWriterDetails::Keyword("PRESSURE", tmp));
for (int phase = 0; phase != BlackoilPhases::MaxNumPhases; ++phase) {
// Eclipse never writes the oil saturation, so all post-processors
// must calculate this from the other saturations anyway
if (phase == BlackoilPhases::PhaseIndex::Liquid) {
continue;
}
if (phaseUsage_.phase_used[phase]) {
tmp = reservoirState.saturation();
EclipseWriterDetails::extractFromStripedData(tmp,
/*offset=*/phaseUsage_.phase_pos[phase],
/*stride=*/phaseUsage_.num_phases);
EclipseWriterDetails::restrictAndReorderToActiveCells(tmp, gridToEclipseIdx_.size(), gridToEclipseIdx_.data());
sol.add(EclipseWriterDetails::Keyword(EclipseWriterDetails::saturationKeywordNames[phase], tmp));
}
}
/* Summary variables (well reporting) */
// TODO: instead of writing the header (smspec) every time, it should
// only be written when there is a change in the well configuration
// (first timestep, in practice), and reused later. but how to do this
// without keeping the complete summary in memory (which will then
// accumulate all the timesteps)?
//
// Note: The answer to the question above is still not settled, but now we do keep
// the complete summary in memory, as a member variable in the EclipseWriter class,
// instead of creating a temporary EclipseWriterDetails::Summary in this function
// every time it is called. This has been changed so that the final summary file
// will contain data from the whole simulation, instead of just the last step.
summary_->writeTimeStep(writeStepIdx_, timer, wellState);
++writeStepIdx_;
}
EclipseWriter::EclipseWriter(const parameter::ParameterGroup& params,
Opm::EclipseStateConstPtr eclipseState,
const Opm::PhaseUsage &phaseUsage,
int numCells,
const int* compressedToCartesianCellIdx)
: eclipseState_(eclipseState)
, numCells_(numCells)
, compressedToCartesianCellIdx_(compressedToCartesianCellIdx)
, gridToEclipseIdx_(numCells, int(-1) )
, phaseUsage_(phaseUsage)
{
const auto eclGrid = eclipseState->getEclipseGrid();
cartesianSize_[0] = eclGrid->getNX();
cartesianSize_[1] = eclGrid->getNY();
cartesianSize_[2] = eclGrid->getNZ();
if( compressedToCartesianCellIdx ) {
// if compressedToCartesianCellIdx available then
// compute mapping to eclipse order
std::map< int , int > indexMap;
for (int cellIdx = 0; cellIdx < numCells; ++cellIdx) {
int cartesianCellIdx = compressedToCartesianCellIdx[cellIdx];
indexMap[ cartesianCellIdx ] = cellIdx;
}
int idx = 0;
for( auto it = indexMap.begin(), end = indexMap.end(); it != end; ++it ) {
gridToEclipseIdx_[ idx++ ] = (*it).second;
}
}
else {
// if not compressedToCartesianCellIdx was given use identity
for (int cellIdx = 0; cellIdx < numCells; ++cellIdx) {
gridToEclipseIdx_[ cellIdx ] = cellIdx;
}
}
// factor from the pressure values given in the deck to Pascals
deckToSiPressure_ =
eclipseState->getDeckUnitSystem()->parse("Pressure")->getSIScaling();
init(params);
}
void EclipseWriter::init(const parameter::ParameterGroup& params)
{
// get the base name from the name of the deck
using boost::filesystem::path;
path deckPath(params.get ("deck_filename"));
if (boost::to_upper_copy(path(deckPath.extension()).string()) == ".DATA") {
baseName_ = path(deckPath.stem()).string();
}
else {
baseName_ = path(deckPath.filename()).string();
}
// make uppercase of everything (or otherwise we'll get uppercase
// of some of the files (.SMSPEC, .UNSMRY) and not others
baseName_ = boost::to_upper_copy(baseName_);
// retrieve the value of the "output" parameter
enableOutput_ = params.getDefault("output", /*defaultValue=*/true);
// retrieve the interval at which something should get written to
// disk (once every N timesteps)
outputInterval_ = params.getDefault("output_interval", /*defaultValue=*/1);
// store in current directory if not explicitly set
outputDir_ = params.getDefault("output_dir", ".");
// set the index of the first time step written to 0...
writeStepIdx_ = 0;
if (enableOutput_) {
// make sure that the output directory exists, if not try to create it
if (!boost::filesystem::exists(outputDir_)) {
std::cout << "Trying to create directory \"" << outputDir_ << "\" for the simulation output\n";
boost::filesystem::create_directories(outputDir_);
}
if (!boost::filesystem::is_directory(outputDir_)) {
OPM_THROW(std::runtime_error,
"The path specified as output directory '" << outputDir_
<< "' is not a directory");
}
}
}
// default destructor is OK, just need to be defined
EclipseWriter::~EclipseWriter()
{ }
} // namespace Opm