Restart Output: Add File Management Facility
This commit introduces a new class, Opm::ecl::OutputStream::Restart that handles the details of opening restart output streams (all combinations of formatted/unformatted and unified/separate), as well as correctly positioning the stream 'put' indicator if we're opening an existing unified restart file. In most cases, this will be a simple append operation (std::ios_base::app), but there are some subtleties that require more precise control. The new class is befriended by EclOutput and ERst in order to access private member functions and data members of these classes. That is needed in order to handle file resize operations since some of the requisite information is only available in those two classes. We use Boost.Filesystem to implement file resize, as if by POSIX function ::truncate(), and also to simplify filename generation. These calls can be switched to std::filesystem once the project requires C++17. Intended use of the Restart class is Restart rst(resultSet, formatted, unified); rst.prepareStep(17); // Report step/SEQNUM 17. rst.write("INTEHEAD", ihead); // ... The 'prepareStep' operation opens the file stream (if needed) and also outputs a SEQNUM record in the case of a unified output stream. Moreover, the 'resultSet' is a pair of output directory and case's base name (e.g., "./mpi.np4/2019.05.14.01" and "NORNE_ATW2013") and the formatted/unified flags are bools wrapped in structures. The Restart class is intended to take over the role of the 'filename' string parameter of RestartIO::save(). Add a set of unit tests to demontrate usage and abilities of class Restart.
This commit is contained in:
parent
c71f57f635
commit
992d3b0ce5
@ -171,6 +171,7 @@ if(ENABLE_ECL_OUTPUT)
|
||||
src/opm/io/eclipse/ERft.cpp
|
||||
src/opm/io/eclipse/ERst.cpp
|
||||
src/opm/io/eclipse/ESmry.cpp
|
||||
src/opm/io/eclipse/OutputStream.cpp
|
||||
src/opm/output/eclipse/AggregateConnectionData.cpp
|
||||
src/opm/output/eclipse/AggregateGroupData.cpp
|
||||
src/opm/output/eclipse/AggregateMSWData.cpp
|
||||
@ -286,6 +287,7 @@ if(ENABLE_ECL_OUTPUT)
|
||||
tests/test_InteHEAD.cpp
|
||||
tests/test_LinearisedOutputTable.cpp
|
||||
tests/test_LogiHEAD.cpp
|
||||
tests/test_OutputStream.cpp
|
||||
tests/test_regionCache.cpp
|
||||
tests/test_Restart.cpp
|
||||
tests/test_RFT.cpp
|
||||
@ -577,6 +579,7 @@ if(ENABLE_ECL_OUTPUT)
|
||||
opm/io/eclipse/ERft.hpp
|
||||
opm/io/eclipse/ERst.hpp
|
||||
opm/io/eclipse/ESmry.hpp
|
||||
opm/io/eclipse/OutputStream.hpp
|
||||
opm/output/data/Cells.hpp
|
||||
opm/output/data/Solution.hpp
|
||||
opm/output/data/Wells.hpp
|
||||
|
@ -27,6 +27,10 @@
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace Opm { namespace ecl { namespace OutputStream {
|
||||
class Restart;
|
||||
}}}
|
||||
|
||||
namespace Opm { namespace ecl {
|
||||
|
||||
class ERst : public EclFile
|
||||
@ -44,6 +48,8 @@ public:
|
||||
|
||||
std::vector<EclEntry> listOfRstArrays(int reportStepNumber);
|
||||
|
||||
friend class OutputStream::Restart;
|
||||
|
||||
private:
|
||||
int nReports;
|
||||
std::vector<int> seqnum; // report step numbers, from SEQNUM array in restart file
|
||||
|
@ -27,6 +27,10 @@
|
||||
|
||||
#include <opm/io/eclipse/EclIOdata.hpp>
|
||||
|
||||
namespace Opm { namespace ecl { namespace OutputStream {
|
||||
class Restart;
|
||||
}}}
|
||||
|
||||
namespace Opm { namespace ecl {
|
||||
|
||||
class EclOutput
|
||||
@ -68,6 +72,8 @@ public:
|
||||
|
||||
void message(const std::string& msg);
|
||||
|
||||
friend class OutputStream::Restart;
|
||||
|
||||
private:
|
||||
void writeBinaryHeader(const std::string& arrName, int size, eclArrType arrType);
|
||||
|
||||
|
175
opm/io/eclipse/OutputStream.hpp
Normal file
175
opm/io/eclipse/OutputStream.hpp
Normal file
@ -0,0 +1,175 @@
|
||||
/*
|
||||
Copyright (c) 2019 Equinor ASA
|
||||
|
||||
This file is part of the Open Porous Media project (OPM).
|
||||
|
||||
OPM is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
OPM is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with OPM. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef OPM_IO_OUTPUTSTREAM_HPP_INCLUDED
|
||||
#define OPM_IO_OUTPUTSTREAM_HPP_INCLUDED
|
||||
|
||||
#include <ios>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace Opm { namespace ecl {
|
||||
|
||||
class EclOutput;
|
||||
|
||||
}} // namespace Opm::ecl
|
||||
|
||||
namespace Opm { namespace ecl { namespace OutputStream {
|
||||
|
||||
struct Formatted { bool set; };
|
||||
struct Unified { bool set; };
|
||||
|
||||
/// Abstract representation of an ECLIPSE-style result set.
|
||||
struct ResultSet
|
||||
{
|
||||
/// Output directory. Commonly "." or location of run's .DATA file.
|
||||
std::string outputDir;
|
||||
|
||||
/// Base name of simulation run.
|
||||
std::string baseName;
|
||||
};
|
||||
|
||||
/// File manager for restart output streams.
|
||||
class Restart
|
||||
{
|
||||
public:
|
||||
/// Constructor.
|
||||
///
|
||||
/// \param[in] rset Output directory and base name of output stream.
|
||||
///
|
||||
/// \param[in] fmt Whether or not to create formatted output files.
|
||||
///
|
||||
/// \param[in] unif Whether or not to create unified output files.
|
||||
explicit Restart(ResultSet rset,
|
||||
const Formatted& fmt,
|
||||
const Unified& unif);
|
||||
|
||||
~Restart();
|
||||
|
||||
Restart(const Restart& rhs) = delete;
|
||||
Restart(Restart&& rhs);
|
||||
|
||||
Restart& operator=(const Restart& rhs) = delete;
|
||||
Restart& operator=(Restart&& rhs);
|
||||
|
||||
/// Opens file stream pertaining to restart of particular report
|
||||
/// step and also outputs a SEQNUM record in the case of a unified
|
||||
/// output stream.
|
||||
///
|
||||
/// Must be called before accessing the stream object through the
|
||||
/// stream() member function.
|
||||
///
|
||||
/// \param[in] seqnum Sequence number of new report. One-based
|
||||
/// report step ID.
|
||||
void prepareStep(const int seqnum);
|
||||
|
||||
const ResultSet& resultSetDescriptor() const
|
||||
{
|
||||
return this->rset_;
|
||||
}
|
||||
|
||||
bool formatted() const
|
||||
{
|
||||
return this->formatted_;
|
||||
}
|
||||
|
||||
bool unified() const
|
||||
{
|
||||
return this->unified_;
|
||||
}
|
||||
|
||||
/// Access writable output stream.
|
||||
///
|
||||
/// Must not be called prior to \c prepareStep.
|
||||
EclOutput& stream();
|
||||
|
||||
private:
|
||||
ResultSet rset_;
|
||||
bool formatted_;
|
||||
bool unified_;
|
||||
|
||||
/// Restart output stream.
|
||||
std::unique_ptr<EclOutput> stream_;
|
||||
|
||||
/// Open unified output file and place stream's output indicator
|
||||
/// in appropriate location.
|
||||
///
|
||||
/// Writes to \c stream_.
|
||||
///
|
||||
/// \param[in] fname Filename of output stream.
|
||||
///
|
||||
/// \param[in] seqnum Sequence number of new report. One-based
|
||||
/// report step ID.
|
||||
void openUnified(const std::string& fname,
|
||||
const int seqnum);
|
||||
|
||||
/// Open new output stream.
|
||||
///
|
||||
/// Handles the case of separate output files or unified output file
|
||||
/// that does not already exist. Writes to \c stream_.
|
||||
///
|
||||
/// \param[in] fname Filename of new output stream.
|
||||
void openNew(const std::string& fname);
|
||||
|
||||
/// Open existing output file and place stream's output indicator
|
||||
/// in appropriate location.
|
||||
///
|
||||
/// Writes to \c stream_.
|
||||
///
|
||||
/// \param[in] fname Filename of output stream.
|
||||
///
|
||||
/// \param[in] writePos Position at which to place stream's output
|
||||
/// indicator. Use \code streampos{ streamoff{-1} } \endcode to
|
||||
/// place output indicator at end of file (i.e, simple append).
|
||||
void openExisting(const std::string& fname,
|
||||
const std::streampos writePos);
|
||||
};
|
||||
|
||||
/// Derive filename corresponding to output stream of particular result
|
||||
/// set, with user-specified file extension.
|
||||
///
|
||||
/// Low-level string concatenation routine that does not know specific
|
||||
/// relations between base names and file extensions. Handles details
|
||||
/// of base name ending in a period (full stop) or having a name that
|
||||
/// might otherwise appear to contain a file extension (e.g., CASE.01).
|
||||
///
|
||||
/// \param[in] rsetDescriptor Output directory and base name of result set.
|
||||
///
|
||||
/// \param[in] ext Filename extension.
|
||||
///
|
||||
/// \return outputDir/baseName.ext
|
||||
std::string outputFileName(const ResultSet& rsetDescriptor,
|
||||
const std::string& ext);
|
||||
|
||||
/// Derive filename of restart output stream.
|
||||
///
|
||||
/// Knows the rules for formatted vs. unformatted, and separate vs.
|
||||
/// unified output files.
|
||||
///
|
||||
/// \param[in] rst Previously configured restart stream.
|
||||
///
|
||||
/// \param[in] seqnum Sequence number (1-based report step ID).
|
||||
///
|
||||
/// \return Filename corresponding to stream's configuration.
|
||||
std::string outputFileName(const Restart& rst,
|
||||
const int seqnum);
|
||||
|
||||
}}} // namespace Opm::ecl::OutputStream
|
||||
|
||||
#endif // OPM_IO_OUTPUTSTREAM_HPP_INCLUDED
|
267
src/opm/io/eclipse/OutputStream.cpp
Normal file
267
src/opm/io/eclipse/OutputStream.cpp
Normal file
@ -0,0 +1,267 @@
|
||||
/*
|
||||
Copyright (c) 2019 Equinor ASA
|
||||
|
||||
This file is part of the Open Porous Media project (OPM).
|
||||
|
||||
OPM is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
OPM is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with OPM. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <opm/io/eclipse/OutputStream.hpp>
|
||||
|
||||
#include <opm/io/eclipse/EclOutput.hpp>
|
||||
#include <opm/io/eclipse/ERst.hpp>
|
||||
|
||||
#include <exception>
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <ios>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
namespace {
|
||||
namespace FileExtension
|
||||
{
|
||||
std::string
|
||||
restart(const int rptStep,
|
||||
const bool formatted,
|
||||
const bool unified)
|
||||
{
|
||||
if (unified) {
|
||||
return formatted ? "FUNRST" : "UNRST";
|
||||
}
|
||||
|
||||
std::ostringstream ext;
|
||||
|
||||
ext << (formatted ? 'F' : 'X')
|
||||
<< std::setw(4) << std::setfill('0')
|
||||
<< rptStep;
|
||||
|
||||
return ext.str();
|
||||
}
|
||||
} // namespace FileExtension
|
||||
|
||||
namespace Open
|
||||
{
|
||||
namespace Restart
|
||||
{
|
||||
std::unique_ptr<Opm::ecl::ERst>
|
||||
read(const std::string& filename)
|
||||
{
|
||||
// Bypass some of the internal logic of the ERst constructor.
|
||||
//
|
||||
// Specifically, the base class EclFile's constructor throws
|
||||
// and outputs a highly visible diagnostic message if it is
|
||||
// unable to open the file. The diagnostic message is very
|
||||
// confusing to users if they are running a simulation case
|
||||
// for the first time and will likely provoke a reaction along
|
||||
// the lines of "well of course the restart file doesn't exist".
|
||||
{
|
||||
std::ifstream is(filename);
|
||||
|
||||
if (! is) {
|
||||
// Unable to open (does not exist?). Return null.
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
// File exists and can (could?) be opened. Attempt to form
|
||||
// an ERst object on top of it.
|
||||
return std::unique_ptr<Opm::ecl::ERst> {
|
||||
new Opm::ecl::ERst{filename}
|
||||
};
|
||||
}
|
||||
|
||||
std::unique_ptr<Opm::ecl::EclOutput>
|
||||
writeNew(const std::string& filename,
|
||||
const bool isFmt)
|
||||
{
|
||||
return std::unique_ptr<Opm::ecl::EclOutput> {
|
||||
new Opm::ecl::EclOutput {
|
||||
filename, isFmt, std::ios_base::out
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
std::unique_ptr<Opm::ecl::EclOutput>
|
||||
writeExisting(const std::string& filename,
|
||||
const bool isFmt)
|
||||
{
|
||||
return std::unique_ptr<Opm::ecl::EclOutput> {
|
||||
new Opm::ecl::EclOutput {
|
||||
filename, isFmt, std::ios_base::app
|
||||
}
|
||||
};
|
||||
}
|
||||
} // namespace Restart
|
||||
} // namespace Open
|
||||
} // Anonymous namespace
|
||||
|
||||
Opm::ecl::OutputStream::Restart::
|
||||
Restart(ResultSet rset,
|
||||
const Formatted& fmt,
|
||||
const Unified& unif)
|
||||
: rset_ (std::move(rset))
|
||||
, formatted_{fmt.set}
|
||||
, unified_ {unif.set}
|
||||
{}
|
||||
|
||||
Opm::ecl::OutputStream::Restart::~Restart()
|
||||
{}
|
||||
|
||||
Opm::ecl::OutputStream::Restart::Restart(Restart&& rhs)
|
||||
: rset_ (std::move(rhs.rset_))
|
||||
, formatted_{rhs.formatted_}
|
||||
, unified_ {rhs.unified_}
|
||||
{}
|
||||
|
||||
Opm::ecl::OutputStream::Restart&
|
||||
Opm::ecl::OutputStream::Restart::operator=(Restart&& rhs)
|
||||
{
|
||||
this->rset_ = std::move(rhs.rset_);
|
||||
this->formatted_ = rhs.formatted_;
|
||||
this->unified_ = rhs.unified_;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
void Opm::ecl::OutputStream::Restart::prepareStep(const int seqnum)
|
||||
{
|
||||
const auto fname = outputFileName(*this, seqnum);
|
||||
|
||||
if (this->unified_) {
|
||||
// Run uses unified restart files.
|
||||
this->openUnified(fname, seqnum);
|
||||
|
||||
// Write SEQNUM value to stream to start new output sequence.
|
||||
this->stream_->write("SEQNUM", std::vector<int>{ seqnum });
|
||||
}
|
||||
else {
|
||||
// Run uses separate, not unified, restart files. Create a
|
||||
// new output file and open an output stream on it.
|
||||
this->openNew(fname);
|
||||
}
|
||||
}
|
||||
|
||||
Opm::ecl::EclOutput&
|
||||
Opm::ecl::OutputStream::Restart::stream()
|
||||
{
|
||||
return *this->stream_;
|
||||
}
|
||||
|
||||
void
|
||||
Opm::ecl::OutputStream::Restart::
|
||||
openUnified(const std::string& fname,
|
||||
const int seqnum)
|
||||
{
|
||||
// Determine if we're creating a new output/restart file or
|
||||
// if we're opening an existing one, possibly at a specific
|
||||
// write position.
|
||||
auto rst = Open::Restart::read(fname);
|
||||
|
||||
if (rst == nullptr) {
|
||||
// No such unified restart file exists. Create new file.
|
||||
this->openNew(fname);
|
||||
}
|
||||
else if (! rst->hasKey("SEQNUM")) {
|
||||
// File with correct filename exists but does not appear
|
||||
// to be an actual unified restart file.
|
||||
throw std::invalid_argument {
|
||||
"Purported existing unified restart file '"
|
||||
+ boost::filesystem::path{fname}.filename().string()
|
||||
+ "' does not appear to be a unified restart file"
|
||||
};
|
||||
}
|
||||
else {
|
||||
// Restart file exists and appears to be a unified restart
|
||||
// resource. Open writable restart stream backed by the
|
||||
// specific file.
|
||||
this->openExisting(fname, rst->restartStepWritePosition(seqnum));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Opm::ecl::OutputStream::Restart::openNew(const std::string& fname)
|
||||
{
|
||||
this->stream_ = Open::Restart::writeNew(fname, this->formatted_);
|
||||
}
|
||||
|
||||
void
|
||||
Opm::ecl::OutputStream::Restart::
|
||||
openExisting(const std::string& fname,
|
||||
const std::streampos writePos)
|
||||
{
|
||||
this->stream_ = Open::Restart::
|
||||
writeExisting(fname, this->formatted_);
|
||||
|
||||
if (writePos == std::streampos(-1)) {
|
||||
// No specified initial write position. Typically the case if
|
||||
// requested SEQNUM value exceeds all existing SEQNUM values in
|
||||
// 'fname'. This is effectively a simple append operation so
|
||||
// no further actions required.
|
||||
return;
|
||||
}
|
||||
|
||||
// The user specified an initial write position. Resize existing
|
||||
// file (as if by POSIX function ::truncate()) to requested size,
|
||||
// and place output position at that position (i.e., new EOF). This
|
||||
// case typically corresponds to reopening a unified restart file at
|
||||
// the start of a particular SEQNUM keyword.
|
||||
//
|
||||
// Note that this intentionally operates on the file/path backing the
|
||||
// already opened 'stream_'. In other words, 'open' followed by
|
||||
// resize_file() followed by seekp() is the intended and expected
|
||||
// order of operations.
|
||||
|
||||
boost::filesystem::resize_file(fname, writePos);
|
||||
|
||||
if (! this->stream_->ofileH.seekp(0, std::ios_base::end)) {
|
||||
throw std::invalid_argument {
|
||||
"Unable to Seek to Write Position " +
|
||||
std::to_string(writePos) + " of File '"
|
||||
+ fname + "'"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
std::string
|
||||
Opm::ecl::OutputStream::outputFileName(const ResultSet& rsetDescriptor,
|
||||
const std::string& ext)
|
||||
{
|
||||
namespace fs = boost::filesystem;
|
||||
|
||||
// Allow baseName = "CASE", "CASE.", "CASE.N", or "CASE.N.".
|
||||
auto fname = fs::path {
|
||||
rsetDescriptor.baseName
|
||||
+ (rsetDescriptor.baseName.back() == '.' ? "" : ".")
|
||||
+ "REPLACE"
|
||||
}.replace_extension(ext);
|
||||
|
||||
return (fs::path { rsetDescriptor.outputDir } / fname)
|
||||
.generic().string();
|
||||
}
|
||||
|
||||
std::string
|
||||
Opm::ecl::OutputStream::outputFileName(const Restart& rst,
|
||||
const int seqnum)
|
||||
{
|
||||
const auto ext = FileExtension::
|
||||
restart(seqnum, rst.formatted(), rst.unified());
|
||||
|
||||
return outputFileName(rst.resultSetDescriptor(), ext);
|
||||
}
|
1017
tests/test_OutputStream.cpp
Normal file
1017
tests/test_OutputStream.cpp
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user