Merge pull request #2684 from joakim-hove/file-deck

rst_deck: application to create a deck prepared for restart
This commit is contained in:
Joakim Hove 2021-09-24 18:46:45 +02:00 committed by GitHub
commit 5a3eca0fe5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 1639 additions and 13 deletions

View File

@ -42,11 +42,13 @@ set(OPM_PROJECT_EXTRA_CODE_INTREE "#ENABLE_ECL_INPUT is needed by opm-common-pre
if(ENABLE_ECL_OUTPUT)
set(OPM_PROJECT_EXTRA_CODE_INSTALLED "${OPM_PROJECT_EXTRA_CODE_INSTALLED}
set(COMPARE_ECL_COMMAND ${CMAKE_INSTALL_PREFIX}/bin${${name}_VER_DIR}/compareECL)
set(OPM_PACK_COMMAND ${CMAKE_INSTALL_PREFIX}/bin${${name}_VER_DIR}/opmpack)")
set(OPM_PACK_COMMAND ${CMAKE_INSTALL_PREFIX}/bin${${name}_VER_DIR}/opmpack)
set(RST_DECK_COMMAND ${CMAKE_INSTALL_PREFIX}/bin${${name}_VER_DIR}/rst_deck)")
set(OPM_PROJECT_EXTRA_CODE_INTREE "${OPM_PROJECT_EXTRA_CODE_INTREE}
set(COMPARE_ECL_COMMAND ${PROJECT_BINARY_DIR}/bin/compareECL)
set(OPM_PACK_COMMAND ${PROJECT_BINARY_DIR}/bin/opmpack)")
set(OPM_PACK_COMMAND ${PROJECT_BINARY_DIR}/bin/opmpack)
set(RST_DECK_COMMAND ${PROJECT_BINARY_DIR}/bin/rst_deck)")
endif()
# project information is in dune.module. Read this file and set variables.

View File

@ -46,6 +46,8 @@ if(ENABLE_ECL_INPUT)
src/opm/io/eclipse/SummaryNode.cpp
src/opm/json/JsonObject.cpp
src/opm/parser/eclipse/Deck/Deck.cpp
src/opm/parser/eclipse/Deck/DeckTree.cpp
src/opm/parser/eclipse/Deck/FileDeck.cpp
src/opm/parser/eclipse/Deck/DeckItem.cpp
src/opm/parser/eclipse/Deck/DeckValue.cpp
src/opm/parser/eclipse/Deck/DeckKeyword.cpp
@ -563,6 +565,7 @@ if(ENABLE_ECL_INPUT)
examples/opmi.cpp
examples/opmpack.cpp
examples/opmhash.cpp
examples/rst_deck.cpp
examples/wellgraph.cpp
examples/make_ext_smry.cpp
)
@ -577,6 +580,7 @@ if(ENABLE_ECL_INPUT)
examples/opmi.cpp
examples/opmpack.cpp
examples/opmhash.cpp
examples/rst_deck.cpp
examples/make_esmry.cpp
)
endif()
@ -851,7 +855,9 @@ if(ENABLE_ECL_INPUT)
opm/parser/eclipse/EclipseState/Schedule/UDQ/UDQFunctionTable.hpp
opm/parser/eclipse/Deck/DeckItem.hpp
opm/parser/eclipse/Deck/Deck.hpp
opm/parser/eclipse/Deck/FileDeck.hpp
opm/parser/eclipse/Deck/DeckSection.hpp
opm/parser/eclipse/Deck/DeckTree.hpp
opm/parser/eclipse/Deck/DeckOutput.hpp
opm/parser/eclipse/Deck/DeckValue.hpp
opm/parser/eclipse/Deck/DeckKeyword.hpp

View File

@ -69,6 +69,11 @@ opm_add_test( rst_msw
LIBRARIES ${TEST_LIBS}
TEST_ARGS tests/MSW.DATA tests/MSW_RESTART.DATA )
add_test( NAME rst_deck_test
COMMAND ${PROJECT_SOURCE_DIR}/tests/rst_test_driver.sh ${PROJECT_BINARY_DIR}/bin/rst_deck ${PROJECT_BINARY_DIR}/bin/opmhash
${PROJECT_SOURCE_DIR}/tests/SPE1CASE2_INCLUDE.DATA)
# opm-tests dependent tests
if(HAVE_OPM_TESTS)
opm_add_test(parse_write ONLY_COMPILE
@ -113,8 +118,17 @@ if(HAVE_OPM_TESTS)
opm_add_test("SPE9_CP_GROUP2" NO_COMPILE EXE_NAME parse_write TEST_ARGS "${OPM_TESTS_ROOT}/spe9group/SPE9_CP_GROUP.DATA")
set_property(TEST NORNE_ATW2013
PROPERTY ENVIRONMENT "OPM_ERRORS_IGNORE=PARSE_RANDOM_SLASH")
add_test( NAME rst_deck_test_norne
COMMAND ${PROJECT_SOURCE_DIR}/tests/rst_test_driver.sh ${CMAKE_BINARY_DIR}/bin/rst_deck ${CMAKE_BINARY_DIR}/bin/opmhash
${OPM_TESTS_ROOT}/norne/NORNE_ATW2013.DATA)
set_property(TEST rst_deck_test_norne
PROPERTY ENVIRONMENT "OPM_ERRORS_IGNORE=PARSE_RANDOM_SLASH")
endif()
# JSON tests
opm_add_test(jsonTests
SOURCES tests/json/jsonTests.cpp

View File

@ -1,6 +1,7 @@
set(genkw_SOURCES src/opm/json/JsonObject.cpp
src/opm/parser/eclipse/Parser/createDefaultKeywordList.cpp
src/opm/parser/eclipse/Deck/UDAValue.cpp
src/opm/parser/eclipse/Deck/DeckTree.cpp
src/opm/parser/eclipse/Deck/DeckValue.cpp
src/opm/parser/eclipse/Deck/Deck.cpp
src/opm/parser/eclipse/Deck/DeckItem.cpp

7
docs/man1/rst_deck.1 Normal file
View File

@ -0,0 +1,7 @@
.TH RST_DECK
.SH NAME
rst_deck \- Create a version of deck ready for restart

297
examples/rst_deck.cpp Normal file
View File

@ -0,0 +1,297 @@
/*
Copyright 2021 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/>.
*/
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <getopt.h>
#include <fmt/format.h>
#include <unordered_set>
#include <opm/parser/eclipse/Deck/DeckValue.hpp>
#include <opm/parser/eclipse/Deck/DeckKeyword.hpp>
#include <opm/common/utility/FileSystem.hpp>
#include <opm/parser/eclipse/Units/UnitSystem.hpp>
#include <opm/parser/eclipse/Parser/ParserKeywords/I.hpp>
#include <opm/parser/eclipse/Parser/ParserKeywords/P.hpp>
#include <opm/parser/eclipse/Parser/ParserKeywords/G.hpp>
#include <opm/parser/eclipse/Parser/ParserKeywords/R.hpp>
#include <opm/parser/eclipse/Parser/ParserKeywords/S.hpp>
#include <opm/parser/eclipse/Parser/Parser.hpp>
#include <opm/parser/eclipse/Parser/ErrorGuard.hpp>
#include <opm/parser/eclipse/Parser/ParseContext.hpp>
#include <opm/parser/eclipse/Parser/InputErrorAction.hpp>
#include <opm/parser/eclipse/Deck/Deck.hpp>
#include <opm/parser/eclipse/Deck/FileDeck.hpp>
namespace fs = Opm::filesystem;
const std::unordered_set<std::string> remove_from_solution = {"EQUIL", "PRESSURE", "SWAT", "SGAS"};
const std::unordered_set<std::string> keep_in_solution = {"ABC"};
void print_help_and_exit(const std::optional<std::string> error_msg = {}) {
if (error_msg.has_value()) {
std::cerr << "Error:" << std::endl;
std::cerr << error_msg.value() << std::endl;
std::cerr << "------------------------------------------------------" << std::endl;
}
std::string keep_keywords;
for (const auto& kw : keep_in_solution)
keep_keywords += kw + " ";
const std::string help_text = fmt::format(R"(
The rst_deck program will load a simulation deck and parameters for a restart
and reformat the deck to become a restart deck. Before the updated deck is
output the program will update the SOLUTION and SCHEDULE sections. All keywords
from the SOLUTION section will be cleared out(1) and a RESTART keyword will be
inserted. In the SCHEDULE section the program can either remove all keywords up
until the restart date, or alternatively insert SKIPREST immediately following
the SCHEDULE keyword(2).
When creating the updated restart deck the program can either link to unmodified
include files with INCLUDE statements, create a copy of deck structure in an
alternative location or create one large file with all keywords in the same
file. Apart from the alterations to support restart the output deck will be
equivalent to the input deck, but formatting is not retained and comments have
been stripped away.
Arguments:
1. The data file we are starting with.
2. The basename of the restart file - with an optional path prefix and a :N to
restart from step N(3). A restart step value of 0 is interpreted as a dry run
- a deck which has not been set up for restart will be written out.
3. Basename of the restart deck we create, can optionally contain a path prefix;
the path will be created if it does not already exist. This argument is
optional, if it is not provided the program will dump a restart deck on
stdout. If the argument corresponds to an existing directory the restart case
will get the same name as the base case.
Options:
-s: Manipulate the SCHEDULE section by inserting a SKIPREST keyword immediately
following the SCHEDULE keyword. If the -s option is not used the SCHEDULE
section will be modified by removing all keywords until we reach the restart
date. NB: Currently the -s option is required
-m: [share|inline|copy] The restart deck can reuse the unmodified include files
from the base case, this is mode 'share' and is the default. With mode
'inline' the restart deck will be one long file and with mode 'copy' the
file structure of the base case will be retained. The default if no -m
option is given is the 'share' mode.
In the case of 'share' and 'copy' the correct path to include files will be
negotiated based on the path given to the output case in the third argument.
If the restart deck is passed to stdout the include files will be resolved
based on output in cwd.
Example:
rst_deck /path/to/history/HISTORY.DATA rst/HISTORY:30 /path/to/rst/RESTART -s
1: The program has a compiled list of keywords which will be retained in the
SOLUTION section. The current value of that list is: {}
2: Current version of the program *only* supports the SKIPREST option, and the
-s option is required.
3: The second argument is treated purely as a string and inserted verbatim into
the updated restart deck. In a future version we might interpret the second
argument as a file path and check the content and also do filesystem
manipulations from it.
)", keep_keywords);
std::cerr << help_text << std::endl;
if (error_msg.has_value())
std::exit(EXIT_FAILURE);
std::exit(EXIT_SUCCESS);
}
struct options {
std::string input_deck;
std::pair<std::string, int> restart;
std::optional<std::string> target;
Opm::FileDeck::OutputMode mode{Opm::FileDeck::OutputMode::SHARE};
bool skiprest{false};
};
Opm::FileDeck load_deck(const options& opt) {
Opm::ParseContext parseContext(Opm::InputError::WARN);
Opm::ErrorGuard errors;
Opm::Parser parser;
/* Use the same default ParseContext as flow. */
parseContext.update(Opm::ParseContext::PARSE_RANDOM_SLASH, Opm::InputError::IGNORE);
parseContext.update(Opm::ParseContext::PARSE_MISSING_DIMS_KEYWORD, Opm::InputError::WARN);
parseContext.update(Opm::ParseContext::SUMMARY_UNKNOWN_WELL, Opm::InputError::WARN);
parseContext.update(Opm::ParseContext::SUMMARY_UNKNOWN_GROUP, Opm::InputError::WARN);
auto deck = parser.parseFile(opt.input_deck, parseContext, errors);
return Opm::FileDeck{ deck };
}
Opm::FileDeck::OutputMode mode(const std::string& mode_arg) {
if (mode_arg == "inline")
return Opm::FileDeck::OutputMode::INLINE;
if (mode_arg == "share")
return Opm::FileDeck::OutputMode::SHARE;
if (mode_arg == "copy")
return Opm::FileDeck::OutputMode::COPY;
print_help_and_exit(fmt::format("Mode argument: \'{}\' not recognized. Valid options are inline|share|copy", mode_arg));
return Opm::FileDeck::OutputMode::INLINE;
}
std::pair<std::string, std::size_t> split_restart(const std::string& restart_base) {
auto sep_pos = restart_base.rfind(':');
if (sep_pos == std::string::npos)
print_help_and_exit(fmt::format("Expected restart argument on the form: BASE:NUMBER - e.g. HISTORY:60"));
return std::make_pair(restart_base.substr(0, sep_pos), std::stoi(restart_base.substr(sep_pos + 1)));
}
options load_options(int argc, char **argv) {
options opt;
while (true) {
int c;
c = getopt(argc, argv, "hm:s");
if (c == -1)
break;
switch(c) {
case 'm':
opt.mode = mode(optarg);
break;
case 's':
opt.skiprest = true;
break;
case 'h':
print_help_and_exit();
break;
}
}
auto arg_offset = optind;
if (arg_offset >= argc)
print_help_and_exit();
opt.input_deck = argv[arg_offset];
opt.restart = split_restart(argv[arg_offset + 1]);
if ((argc - arg_offset) >= 3) {
opt.target = argv[arg_offset + 2];
if (opt.mode == Opm::FileDeck::OutputMode::COPY) {
auto target = fs::path(opt.target.value()).parent_path();
if (fs::exists(target)) {
auto input = fs::path(opt.input_deck).parent_path();
if (fs::equivalent(target, input))
opt.mode = Opm::FileDeck::OutputMode::SHARE;
}
}
} else {
if (opt.mode == Opm::FileDeck::OutputMode::COPY)
print_help_and_exit("When writing output to stdout you must use inline|share mode");
}
return opt;
}
void update_solution(const options& opt, Opm::FileDeck& file_deck)
{
if (opt.restart.second == 0)
return;
const auto solution = file_deck.find("SOLUTION");
if (!solution.has_value())
print_help_and_exit(fmt::format("Could not find SOLUTION section in input deck: {}", opt.input_deck));
const auto summary = file_deck.find("SUMMARY");
if (!summary.has_value())
print_help_and_exit(fmt::format("Could not find SUMMARY section in input deck: {}", opt.input_deck));
auto index = solution.value();
{
while (true) {
const auto& keyword = file_deck[++index];
if (keep_in_solution.count(keyword.name()) == 0)
file_deck.erase(index);
if (index == summary)
break;
}
}
{
Opm::UnitSystem units;
std::vector<Opm::DeckValue> values{ Opm::DeckValue{opt.restart.first}, Opm::DeckValue{opt.restart.second} };
Opm::DeckKeyword restart(Opm::ParserKeywords::RESTART(), std::vector<std::vector<Opm::DeckValue>>{ values }, units, units);
auto schedule = file_deck.find("SCHEDULE");
file_deck.insert(++schedule.value(), restart);
}
}
void update_schedule(const options& opt, Opm::FileDeck& file_deck)
{
if (opt.restart.second == 0)
return;
const auto schedule = file_deck.find("SCHEDULE");
if (opt.skiprest) {
Opm::DeckKeyword skiprest( Opm::ParserKeywords::SKIPREST{} );
auto index = schedule.value();
file_deck.insert(++index, skiprest);
}
}
int main(int argc, char** argv) {
auto options = load_options(argc, argv);
auto file_deck = load_deck(options);
update_solution(options, file_deck);
update_schedule(options, file_deck);
if (!options.target.has_value())
file_deck.dump_stdout(fs::current_path(), options.mode);
else {
std::string target = options.target.value();
if (fs::is_directory(target))
file_deck.dump( fs::absolute(target), fs::path(options.input_deck).filename(), options.mode );
else {
auto target_path = fs::path( fs::absolute(options.target.value()) );
file_deck.dump( fs::absolute(target_path.parent_path()), target_path.filename(), options.mode );
}
}
}

View File

@ -27,6 +27,7 @@
#include <vector>
#include <string>
#include <opm/parser/eclipse/Deck/DeckTree.hpp>
#include <opm/parser/eclipse/Deck/DeckKeyword.hpp>
#include <opm/parser/eclipse/Units/UnitSystem.hpp>
@ -153,6 +154,8 @@ namespace Opm {
std::string getDataFile() const;
void setDataFile(const std::string& dataFile);
std::string makeDeckPath(const std::string& path) const;
DeckTree& tree();
DeckTree tree() const;
std::size_t size() const;
bool empty() const;
@ -183,6 +186,7 @@ namespace Opm {
std::optional<std::string> m_dataFile;
std::string input_path;
DeckTree file_tree;
mutable std::size_t unit_system_access_count = 0;
};
}

View File

@ -0,0 +1,71 @@
/*
Copyright 2021 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/>.
*/
#ifndef DECK_TREE_HPP
#define DECK_TREE_HPP
#include <string>
#include <unordered_map>
#include <unordered_set>
namespace Opm {
/*
The purpose of the DeckTree class is to maintain a minimal relationship
between the include files in the deck; the sole purpose of this class is to
support writing of decks with the keywords in the correct files.
*/
class DeckTree {
public:
DeckTree() = default;
DeckTree(const std::string&);
const std::string& parent(const std::string& fname) const;
bool includes(const std::string& parent_file, const std::string& include_file) const;
void add_include(std::string parent_file, std::string include_file);
void add_root(const std::string& fname);
bool has_include(const std::string& fname) const;
const std::string& root() const;
private:
class TreeNode {
public:
explicit TreeNode(const std::string& fn);
TreeNode(const std::string& pn, const std::string& fn);
void add_include(const std::string& include_file);
bool includes(const std::string& include_file) const;
std::string fname;
std::optional<std::string> parent;
std::unordered_set<std::string> include_files;
};
std::string add_node(const std::string& fname);
std::optional<std::string> root_file;
std::unordered_map<std::string, TreeNode> nodes;
};
}
#endif /* DECKRECORD_HPP */

View File

@ -0,0 +1,155 @@
/*
Copyright 2021 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/>.
*/
#ifndef FILE_DECK_HPP
#define FILE_DECK_HPP
#include <optional>
#include <string>
#include <unordered_set>
#include <fstream>
#include <iostream>
#include <fmt/format.h>
#include <opm/parser/eclipse/Deck/DeckKeyword.hpp>
#include <opm/parser/eclipse/Deck/FileDeck.hpp>
#include <opm/common/utility/FileSystem.hpp>
namespace fs = Opm::filesystem;
namespace Opm {
class Deck;
class FileDeck {
public:
enum class OutputMode {
INLINE = 1,
SHARE = 2,
COPY = 3
};
struct Index {
std::size_t file_index;
std::size_t keyword_index;
Index(std::size_t file_index_arg, std::size_t keyword_index_arg, const FileDeck* deck_arg)
: file_index(file_index_arg)
, keyword_index(keyword_index_arg)
, deck(deck_arg)
{}
Index& operator--();
Index operator--(int);
Index& operator++();
Index operator++(int);
bool operator==(const Index& other) const;
bool operator!=(const Index& other) const;
private:
const FileDeck * deck;
};
class Block {
public:
explicit Block(const std::string& filename);
std::size_t size() const;
void load(const Deck& deck, std::size_t deck_index);
std::optional<std::size_t> find(const std::string& keyword) const;
bool empty() const;
void erase(const FileDeck::Index& index);
void insert(std::size_t keyword_index, const DeckKeyword& keyword);
void dump(DeckOutput& out) const;
private:
std::string fname;
std::vector<DeckKeyword> keywords;
friend FileDeck;
};
explicit FileDeck(const Deck& deck);
std::optional<Index> find(const std::string& keyword) const;
void erase(const Index& index);
void erase(const Index& begin, const Index& end);
void insert(const Index& index, const DeckKeyword& keyword);
void dump_stdout(const std::string& output_dir, OutputMode mode) const;
void dump(const std::string& dir, const std::string& fname, OutputMode mode) const;
const DeckKeyword& operator[](const Index& index) const;
const Index start() const;
const Index stop() const;
private:
std::vector<Block> blocks;
std::string input_directory;
std::unordered_set<std::string> modified_files;
DeckTree deck_tree;
struct DumpContext {
std::unordered_map<std::string, std::ofstream> stream_map;
std::unordered_map<std::string, std::string> file_map;
bool has_file(const std::string& fname) const {
return this->file_map.count(fname) > 0;
}
std::optional<std::ofstream *> get_stream(const std::string& deck_name) {
auto name_iter = this->file_map.find(deck_name);
if (name_iter == this->file_map.end())
return {};
return &this->stream_map.at(name_iter->second);
}
std::ofstream& open_file(const std::string& deck_name, const fs::path& output_file)
{
if (this->stream_map.count(output_file.string()) == 0) {
this->file_map.insert(std::make_pair( deck_name, output_file.string() ));
if (!fs::is_directory(output_file.parent_path()))
fs::create_directories(output_file.parent_path());
std::ofstream stream{output_file};
if (!stream)
throw std::logic_error(fmt::format("Opening {} for writing failed", output_file.string()));
this->stream_map.insert(std::make_pair(output_file.string(), std::move(stream)));
}
return this->stream_map.at(output_file.string());
}
};
void dump(std::ostream& os) const;
void dump_shared(std::ostream& stream, const std::string& output_dir) const;
void dump_inline() const;
std::string dump_block(const Block& block, const std::string& dir, const std::optional<std::string>& fname, DumpContext& context) const;
void include_block(const std::string& source_file, const std::string& target_file, const std::string& dir, DumpContext& context) const;
};
}
#endif

View File

@ -19,12 +19,15 @@
#include <algorithm>
#include <vector>
#include <opm/common/utility/FileSystem.hpp>
#include <opm/parser/eclipse/Deck/Deck.hpp>
#include <opm/parser/eclipse/Deck/DeckOutput.hpp>
#include <opm/parser/eclipse/Deck/DeckKeyword.hpp>
#include <opm/parser/eclipse/Deck/DeckSection.hpp>
#include <opm/parser/eclipse/Units/UnitSystem.hpp>
namespace fs = Opm::filesystem;
namespace Opm {
bool DeckView::hasKeyword( const DeckKeyword& keyword ) const {
@ -156,7 +159,8 @@ namespace Opm {
keywordList( d.keywordList ),
defaultUnits( d.defaultUnits ),
m_dataFile( d.m_dataFile ),
input_path( d.input_path )
input_path( d.input_path ),
file_tree( d.file_tree )
{
this->init(this->keywordList.begin(), this->keywordList.end());
if (d.activeUnits)
@ -169,7 +173,8 @@ namespace Opm {
keywordList( std::move(d.keywordList) ),
defaultUnits( d.defaultUnits ),
m_dataFile( d.m_dataFile ),
input_path( d.input_path )
input_path( d.input_path ),
file_tree( std::move(d.file_tree) )
{
this->init(this->keywordList.begin(), this->keywordList.end());
if (d.activeUnits)
@ -280,8 +285,20 @@ namespace Opm {
this->input_path = "";
else
this->input_path = dataFile.substr(0, slash_pos);
this->file_tree.add_root(dataFile);
}
DeckTree& Deck::tree() {
return this->file_tree;
}
DeckTree Deck::tree() const {
return this->file_tree;
}
Deck::iterator Deck::begin() {
return this->keywordList.begin();
}

View File

@ -0,0 +1,99 @@
/*
Copyright 2021 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.
along with OPM. If not, see <http://www.gnu.org/licenses/>.
*/
#include <opm/common/utility/FileSystem.hpp>
#include <opm/parser/eclipse/Deck/DeckTree.hpp>
namespace fs = Opm::filesystem;
namespace Opm {
DeckTree::TreeNode::TreeNode(const std::string& fn)
: fname(fn)
{}
DeckTree::TreeNode::TreeNode(const std::string& pn, const std::string& fn)
: fname(fn)
, parent(pn)
{}
void DeckTree::TreeNode::add_include(const std::string& include_file) {
this->include_files.emplace(include_file);
}
bool DeckTree::TreeNode::includes(const std::string& include_file) const {
return this->include_files.count(include_file) > 0;
}
std::string DeckTree::add_node(const std::string& fname) {
auto abs_path = fs::canonical(fname);
this->nodes.emplace( abs_path, TreeNode(abs_path) );
return abs_path;
}
DeckTree::DeckTree(const std::string& fname)
{
this->add_root(fname);
}
void DeckTree::add_root(const std::string& fname)
{
if (this->root_file.has_value())
throw std::logic_error("Root already assigned");
this->root_file = this->add_node(fname);
}
bool DeckTree::includes(const std::string& parent_file, const std::string& include_file) const {
if (!this->root_file.has_value())
return false;
const auto& parent_node = this->nodes.at(fs::canonical(parent_file));
return parent_node.includes(fs::canonical(include_file));
}
const std::string& DeckTree::parent(const std::string& fname) const {
const auto& node = this->nodes.at(fs::canonical(fname));
const auto& parent_node = this->nodes.at( node.parent.value() );
return parent_node.fname;
}
const std::string& DeckTree::root() const {
return this->root_file.value();
}
void DeckTree::add_include(std::string parent_file, std::string include_file) {
if (!this->root_file.has_value())
return;
parent_file = fs::canonical(parent_file);
include_file = fs::canonical(include_file);
this->nodes.emplace(include_file, TreeNode(parent_file, include_file));
auto& parent_node = this->nodes.at(parent_file);
parent_node.add_include( include_file );
}
bool DeckTree::has_include(const std::string& fname) const {
const auto& node = this->nodes.at(fname);
return !node.include_files.empty();
}
}

View File

@ -0,0 +1,350 @@
/*
Copyright 2021 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.
along with OPM. If not, see <http://www.gnu.org/licenses/>.
*/
#include <fmt/format.h>
#include <opm/common/utility/FileSystem.hpp>
#include <opm/parser/eclipse/Deck/Deck.hpp>
#include <opm/parser/eclipse/Deck/DeckOutput.hpp>
#include <opm/parser/eclipse/Deck/FileDeck.hpp>
namespace fs = Opm::filesystem;
namespace Opm {
namespace {
void INCLUDE(std::ostream& stream , const std::string& fname)
{
auto include_string = fmt::format(R"(
INCLUDE
'{}' /
)", fname);
stream << include_string;
}
void touch_file(const fs::path& file) {
if (!fs::exists(file)) {
const auto& parent_path = file.parent_path();
if (!fs::is_directory(parent_path))
fs::create_directories(parent_path);
std::ofstream{file};
}
}
}
FileDeck::Index& FileDeck::Index::operator--() {
if (this->keyword_index > 0)
this->keyword_index--;
else {
if (this->file_index == 0)
throw std::logic_error("Going beyond start of container");
this->file_index--;
this->keyword_index = this->deck->blocks[this->file_index].size() - 1;
}
return *this;
}
FileDeck::Index FileDeck::Index::operator--(int) {
auto current = *this;
this->operator--();
return current;
}
FileDeck::Index& FileDeck::Index::operator++() {
if (this->deck->blocks.empty())
throw std::logic_error("Trying to iterate empty container");
const auto& block = this->deck->blocks[this->file_index];
if (this->keyword_index < block.size() - 1)
this->keyword_index++;
else {
this->file_index++;
this->keyword_index = 0;
}
return *this;
}
FileDeck::Index FileDeck::Index::operator++(int) {
auto current = *this;
this->operator++();
return current;
}
bool FileDeck::Index::operator==(const Index& other) const {
return this->file_index == other.file_index &&
this->keyword_index == other.keyword_index;
}
bool FileDeck::Index::operator!=(const Index& other) const {
return !(*this == other);
}
FileDeck::FileDeck(const Deck& deck)
: input_directory(deck.getInputPath())
, deck_tree(deck.tree())
{
if (deck.empty())
return;
std::size_t deck_index = 0;
while (true) {
const auto& current_file = deck[deck_index].location().filename;
FileDeck::Block block(current_file);
block.load(deck, deck_index);
deck_index += block.size();
this->blocks.push_back(std::move(block));
if (deck_index >= deck.size())
break;
}
this->modified_files.insert(this->blocks[0].fname);
}
const DeckKeyword& FileDeck::operator[](const Index& index) const {
const auto& file_block = this->blocks.at(index.file_index);
return file_block.keywords.at(index.keyword_index);
}
void FileDeck::FileDeck::Block::load(const Deck& deck, std::size_t deck_index) {
const auto& current_file = deck[deck_index].location().filename;
while (true) {
this->keywords.push_back(deck[deck_index]);
deck_index += 1;
if (deck_index >= deck.size())
break;
if (deck[deck_index].location().filename != current_file)
break;
}
}
std::size_t FileDeck::FileDeck::Block::size() const {
return this->keywords.size();
}
std::optional<std::size_t> FileDeck::Block::find(const std::string& keyword) const {
auto iter = std::find_if(this->keywords.begin(), this->keywords.end(), [&keyword](const DeckKeyword& kw) { return kw.name() == keyword;});
if (iter == this->keywords.end())
return {};
return std::distance(this->keywords.begin(), iter);
}
void FileDeck::erase(const FileDeck::Index& index) {
auto& block = this->blocks.at(index.file_index);
this->modified_files.insert(block.fname);
block.erase(index);
}
void FileDeck::erase(const Index& begin, const Index& end) {
auto current = end;
while (current != begin) {
current--;
this->erase(current);
}
}
bool FileDeck::Block::empty() const {
return this->keywords.empty();
}
void FileDeck::Block::erase(const FileDeck::Index& index) {
if (index.keyword_index >= this->keywords.size())
throw std::logic_error("Invalid keyword index in block");
this->keywords.erase(this->keywords.begin() + index.keyword_index);
}
void FileDeck::Block::insert(std::size_t keyword_index, const DeckKeyword& keyword) {
this->keywords.insert(this->keywords.begin() + keyword_index, keyword);
}
void FileDeck::Block::dump(DeckOutput& out) const {
for (const auto& kw : this->keywords) {
kw.write( out );
out.write_string( out.fmt.keyword_sep );
}
}
FileDeck::FileDeck::Block::Block(const std::string& filename)
: fname(fs::canonical(filename))
{}
std::optional<FileDeck::Index> FileDeck::find(const std::string& keyword) const {
std::size_t file_index = 0;
while (true) {
if (file_index >= this->blocks.size())
break;
const auto& file_block = this->blocks[file_index];
const auto& block_index = file_block.find(keyword);
if (block_index.has_value())
return std::make_optional<FileDeck::Index>(file_index, block_index.value(), this);
file_index++;
}
return {};
}
void FileDeck::insert(const Index& index, const DeckKeyword& keyword)
{
auto& block = this->blocks.at(index.file_index);
block.insert(index.keyword_index, keyword);
this->modified_files.insert(block.fname);
}
const FileDeck::Index FileDeck::start() const {
return FileDeck::Index{0,0, this};
}
const FileDeck::Index FileDeck::stop() const {
return FileDeck::Index{this->blocks.size(), 0 , this};
}
void FileDeck::dump(std::ostream& os) const {
DeckOutput out( os , 10 );
for (const auto& block : this->blocks)
block.dump(out);
}
void FileDeck::dump_inline() const {
this->dump(std::cout);
}
std::string FileDeck::dump_block(const FileDeck::Block& block, const std::string& output_dir, const std::optional<std::string>& data_file , FileDeck::DumpContext& context) const {
const auto& deck_name = block.fname;
auto old_stream = context.get_stream(deck_name);
if (old_stream.has_value()) {
DeckOutput out(*old_stream.value(), 10);
block.dump( out );
return "";
}
fs::path output_file;
if (data_file.has_value())
output_file = fs::path(output_dir) / data_file.value();
else {
// Should ideally use fs::relative()
auto rel_path = fs::proximate(block.fname, this->input_directory);
output_file = output_dir / rel_path;
}
touch_file(output_file);
output_file = fs::canonical(output_file);
auto& stream = context.open_file(deck_name, output_file);
DeckOutput out(stream, 10);
block.dump( out );
return output_file.string();
}
void FileDeck::include_block(const std::string& input_file, const std::string& output_file, const std::string& output_dir, FileDeck::DumpContext& context) const {
auto current_file = input_file;
while (true) {
const auto& parent = this->deck_tree.parent(current_file);
auto stream = context.get_stream(parent);
if (stream.has_value()) {
// Should ideally use fs::relative()
std::string include_file = fs::proximate(output_file, output_dir);
INCLUDE(*stream.value(), include_file);
break;
}
current_file = parent;
}
}
void FileDeck::dump(const std::string& output_dir, const std::string& fname, OutputMode mode) const {
if (!fs::is_directory(output_dir))
fs::create_directories(output_dir);
auto output_cwd = fs::path(output_dir);
if (mode == OutputMode::INLINE) {
std::ofstream os(output_cwd / fname);
this->dump(os);
return;
}
if (mode == OutputMode::COPY) {
DumpContext context;
this->dump_block(this->blocks[0], output_dir, fname, context);
for (std::size_t block_index = 1; block_index < this->blocks.size(); block_index++) {
const auto& block = this->blocks[block_index];
const auto& include_file = this->dump_block(block, output_dir, {}, context);
if (block.fname != this->deck_tree.root())
this->include_block(block.fname, include_file, output_dir, context);
}
}
if (mode == OutputMode::SHARE) {
std::ofstream stream{output_cwd / fname};
this->dump_shared(stream, output_dir);
}
}
void FileDeck::dump_shared(std::ostream& stream, const std::string& output_dir) const {
for (std::size_t block_index = 0; block_index < this->blocks.size(); block_index++) {
const auto& block = this->blocks[block_index];
if (block_index == 0 || this->modified_files.count(block.fname) > 0 || this->deck_tree.has_include(block.fname)) {
DeckOutput out(stream, 10);
block.dump( out );
} else {
// Should ideally use fs::relative()
std::string include_file = fs::proximate(block.fname, output_dir);
if (include_file.find(block.fname) == std::string::npos)
INCLUDE(stream, include_file);
else
INCLUDE(stream, block.fname);
}
}
}
void FileDeck::dump_stdout(const std::string& output_dir, OutputMode mode) const {
if (mode == OutputMode::COPY)
throw std::logic_error("dump to stdout can not be combined outputmode COPY");
if (mode == OutputMode::INLINE)
this->dump_inline();
else if (mode == OutputMode::SHARE)
this->dump_shared(std::cout, output_dir);
}
}

View File

@ -1011,8 +1011,11 @@ bool parseState( ParserState& parserState, const Parser& parser ) {
std::string includeFileAsString = readValueToken<std::string>(firstRecord.getItem(0));
const auto& includeFile = parserState.getIncludeFilePath( includeFileAsString );
if (includeFile.has_value())
if (includeFile.has_value()) {
auto& deck_tree = parserState.deck.tree();
deck_tree.add_include(filesystem::absolute(parserState.current_path()), includeFile.value() );
parserState.loadFile( includeFile.value() );
}
continue;
}

View File

@ -0,0 +1,418 @@
-- This reservoir simulation deck is made available under the Open Database
-- License: http://opendatacommons.org/licenses/odbl/1.0/. Any rights in
-- individual contents of the database are licensed under the Database Contents
-- License: http://opendatacommons.org/licenses/dbcl/1.0/
-- Copyright (C) 2015 Statoil
-- This simulation is based on the data given in
-- 'Comparison of Solutions to a Three-Dimensional
-- Black-Oil Reservoir Simulation Problem' by Aziz S. Odeh,
-- Journal of Petroleum Technology, January 1981
---------------------------------------------------------------------------
------------------------ SPE1 - CASE 2 ------------------------------------
---------------------------------------------------------------------------
RUNSPEC
-- -------------------------------------------------------------------------
TITLE
SPE1 - CASE 2
DIMENS
10 10 3 /
-- The number of equilibration regions is inferred from the EQLDIMS
-- keyword.
EQLDIMS
/
-- The number of PVTW tables is inferred from the TABDIMS keyword;
-- when no data is included in the keyword the default values are used.
TABDIMS
/
OIL
GAS
WATER
DISGAS
-- As seen from figure 4 in Odeh, GOR is increasing with time,
-- which means that dissolved gas is present
FIELD
START
1 'JAN' 2015 /
WELLDIMS
-- Item 1: maximum number of wells in the model
-- - there are two wells in the problem; injector and producer
-- Item 2: maximum number of grid blocks connected to any one well
-- - must be one as the wells are located at specific grid blocks
-- Item 3: maximum number of groups in the model
-- - we are dealing with only one 'group'
-- Item 4: maximum number of wells in any one group
-- - there must be two wells in a group as there are two wells in total
2 1 1 2 /
UNIFOUT
GRID
-- The INIT keyword is used to request an .INIT file. The .INIT file
-- is written before the simulation actually starts, and contains grid
-- properties and saturation tables as inferred from the input
-- deck. There are no other keywords which can be used to configure
-- exactly what is written to the .INIT file.
INIT
-- -------------------------------------------------------------------------
INCLUDE
'include/grid.grdecl' /
TOPS
-- The depth of the top of each grid block
100*8325 /
INCLUDE
'include/props.grdecl' /
PROPS
-- -------------------------------------------------------------------------
PVTW
-- Item 1: pressure reference (psia)
-- Item 2: water FVF (rb per bbl or rb per stb)
-- Item 3: water compressibility (psi^{-1})
-- Item 4: water viscosity (cp)
-- Item 5: water 'viscosibility' (psi^{-1})
-- Using values from Norne:
-- In METRIC units:
-- 277.0 1.038 4.67E-5 0.318 0.0 /
-- In FIELD units:
4017.55 1.038 3.22E-6 0.318 0.0 /
ROCK
-- Item 1: reference pressure (psia)
-- Item 2: rock compressibility (psi^{-1})
-- Using values from table 1 in Odeh:
14.7 3E-6 /
SWOF
-- Column 1: water saturation
-- - this has been set to (almost) equally spaced values from 0.12 to 1
-- Column 2: water relative permeability
-- - generated from the Corey-type approx. formula
-- the coeffisient is set to 10e-5, S_{orw}=0 and S_{wi}=0.12
-- Column 3: oil relative permeability when only oil and water are present
-- - we will use the same values as in column 3 in SGOF.
-- This is not really correct, but since only the first
-- two values are of importance, this does not really matter
-- Column 4: corresponding water-oil capillary pressure (psi)
0.12 0 1 0
0.18 4.64876033057851E-008 1 0
0.24 0.000000186 0.997 0
0.3 4.18388429752066E-007 0.98 0
0.36 7.43801652892562E-007 0.7 0
0.42 1.16219008264463E-006 0.35 0
0.48 1.67355371900826E-006 0.2 0
0.54 2.27789256198347E-006 0.09 0
0.6 2.97520661157025E-006 0.021 0
0.66 3.7654958677686E-006 0.01 0
0.72 4.64876033057851E-006 0.001 0
0.78 0.000005625 0.0001 0
0.84 6.69421487603306E-006 0 0
0.91 8.05914256198347E-006 0 0
1 0.00001 0 0 /
SGOF
-- Column 1: gas saturation
-- Column 2: gas relative permeability
-- Column 3: oil relative permeability when oil, gas and connate water are present
-- Column 4: oil-gas capillary pressure (psi)
-- - stated to be zero in Odeh's paper
-- Values in column 1-3 are taken from table 3 in Odeh's paper:
0 0 1 0
0.001 0 1 0
0.02 0 0.997 0
0.05 0.005 0.980 0
0.12 0.025 0.700 0
0.2 0.075 0.350 0
0.25 0.125 0.200 0
0.3 0.190 0.090 0
0.4 0.410 0.021 0
0.45 0.60 0.010 0
0.5 0.72 0.001 0
0.6 0.87 0.0001 0
0.7 0.94 0.000 0
0.85 0.98 0.000 0
0.88 0.984 0.000 0 /
--1.00 1.0 0.000 0 /
-- Warning from Eclipse: first sat. value in SWOF + last sat. value in SGOF
-- must not be greater than 1, but Eclipse still runs
-- Flow needs the sum to be excactly 1 so I added a row with gas sat. = 0.88
-- The corresponding krg value was estimated by assuming linear rel. between
-- gas sat. and krw. between gas sat. 0.85 and 1.00 (the last two values given)
DENSITY
-- Density (lb per ft³) at surface cond. of
-- oil, water and gas, respectively (in that order)
-- Using values from Norne:
-- In METRIC units:
-- 859.5 1033.0 0.854 /
-- In FIELD units:
53.66 64.49 0.0533 /
PVDG
-- Column 1: gas phase pressure (psia)
-- Column 2: gas formation volume factor (rb per Mscf)
-- - in Odeh's paper the units are said to be given in rb per bbl,
-- but this is assumed to be a mistake: FVF-values in Odeh's paper
-- are given in rb per scf, not rb per bbl. This will be in
-- agreement with conventions
-- Column 3: gas viscosity (cP)
-- Using values from lower right table in Odeh's table 2:
14.700 166.666 0.008000
264.70 12.0930 0.009600
514.70 6.27400 0.011200
1014.7 3.19700 0.014000
2014.7 1.61400 0.018900
2514.7 1.29400 0.020800
3014.7 1.08000 0.022800
4014.7 0.81100 0.026800
5014.7 0.64900 0.030900
9014.7 0.38600 0.047000 /
PVTO
-- Column 1: dissolved gas-oil ratio (Mscf per stb)
-- Column 2: bubble point pressure (psia)
-- Column 3: oil FVF for saturated oil (rb per stb)
-- Column 4: oil viscosity for saturated oil (cP)
-- Use values from top left table in Odeh's table 2:
0.0010 14.7 1.0620 1.0400 /
0.0905 264.7 1.1500 0.9750 /
0.1800 514.7 1.2070 0.9100 /
0.3710 1014.7 1.2950 0.8300 /
0.6360 2014.7 1.4350 0.6950 /
0.7750 2514.7 1.5000 0.6410 /
0.9300 3014.7 1.5650 0.5940 /
1.2700 4014.7 1.6950 0.5100
9014.7 1.5790 0.7400 /
1.6180 5014.7 1.8270 0.4490
9014.7 1.7370 0.6310 /
-- It is required to enter data for undersaturated oil for the highest GOR
-- (i.e. the last row) in the PVTO table.
-- In order to fulfill this requirement, values for oil FVF and viscosity
-- at 9014.7psia and GOR=1.618 for undersaturated oil have been approximated:
-- It has been assumed that there is a linear relation between the GOR
-- and the FVF when keeping the pressure constant at 9014.7psia.
-- From Odeh we know that (at 9014.7psia) the FVF is 2.357 at GOR=2.984
-- for saturated oil and that the FVF is 1.579 at GOR=1.27 for undersaturated oil,
-- so it is possible to use the assumption described above.
-- An equivalent approximation for the viscosity has been used.
/
SOLUTION
-- -------------------------------------------------------------------------
EQUIL
-- Item 1: datum depth (ft)
-- Item 2: pressure at datum depth (psia)
-- - Odeh's table 1 says that initial reservoir pressure is
-- 4800 psi at 8400ft, which explains choice of item 1 and 2
-- Item 3: depth of water-oil contact (ft)
-- - chosen to be directly under the reservoir
-- Item 4: oil-water capillary pressure at the water oil contact (psi)
-- - given to be 0 in Odeh's paper
-- Item 5: depth of gas-oil contact (ft)
-- - chosen to be directly above the reservoir
-- Item 6: gas-oil capillary pressure at gas-oil contact (psi)
-- - given to be 0 in Odeh's paper
-- Item 7: RSVD-table
-- Item 8: RVVD-table
-- Item 9: Set to 0 as this is the only value supported by OPM
-- Item #: 1 2 3 4 5 6 7 8 9
8400 4800 8450 0 8300 0 1 0 0 /
RSVD
-- Dissolved GOR is initially constant with depth through the reservoir.
-- The reason is that the initial reservoir pressure given is higher
---than the bubble point presssure of 4014.7psia, meaning that there is no
-- free gas initially present.
8300 1.270
8450 1.270 /
SUMMARY
-- -------------------------------------------------------------------------
-- 1a) Oil rate vs time
FOPR
-- Field Oil Production Rate
-- 1b) GOR vs time
WGOR
-- Well Gas-Oil Ratio
'PROD'
/
-- Using FGOR instead of WGOR:PROD results in the same graph
FGOR
-- 2a) Pressures of the cell where the injector and producer are located
BPR
1 1 1 /
10 10 3 /
/
-- 2b) Gas saturation at grid points given in Odeh's paper
BGSAT
1 1 1 /
1 1 2 /
1 1 3 /
10 1 1 /
10 1 2 /
10 1 3 /
10 10 1 /
10 10 2 /
10 10 3 /
/
-- In order to compare Eclipse with Flow:
WBHP
'INJ'
'PROD'
/
WGIR
'INJ'
'PROD'
/
WGIT
'INJ'
'PROD'
/
WGPR
'INJ'
'PROD'
/
WGPT
'INJ'
'PROD'
/
WOIR
'INJ'
'PROD'
/
WOIT
'INJ'
'PROD'
/
WOPR
'INJ'
'PROD'
/
WOPT
'INJ'
'PROD'
/
WWIR
'INJ'
'PROD'
/
WWIT
'INJ'
'PROD'
/
WWPR
'INJ'
'PROD'
/
WWPT
'INJ'
'PROD'
/
SCHEDULE
-- -------------------------------------------------------------------------
RPTSCHED
'PRES' 'SGAS' 'RS' 'WELLS' /
RPTRST
'BASIC=1' /
-- If no resolution (i.e. case 1), the two following lines must be added:
--DRSDT
-- 0 /
-- Since this is Case 2, the two lines above have been commented out.
-- if DRSDT is set to 0, GOR cannot rise and free gas does not
-- dissolve in undersaturated oil -> constant bubble point pressure
WELSPECS
-- Item #: 1 2 3 4 5 6
'PROD' 'G1' 10 10 8400 'OIL' /
'INJ' 'G1' 1 1 8335 'GAS' /
/
-- Coordinates in item 3-4 are retrieved from Odeh's figure 1 and 2
-- Note that the depth at the midpoint of the well grid blocks
-- has been used as reference depth for bottom hole pressure in item 5
COMPDAT
-- Item #: 1 2 3 4 5 6 7 8 9
'PROD' 10 10 3 3 'OPEN' 1* 1* 0.5 /
'INJ' 1 1 1 1 'OPEN' 1* 1* 0.5 /
/
-- Coordinates in item 2-5 are retreived from Odeh's figure 1 and 2
-- Item 9 is the well bore internal diameter,
-- the radius is given to be 0.25ft in Odeh's paper
WCONPROD
-- Item #:1 2 3 4 5 9
'PROD' 'OPEN' 'ORAT' 20000 4* 1000 /
/
-- It is stated in Odeh's paper that the maximum oil prod. rate
-- is 20 000stb per day which explains the choice of value in item 4.
-- The items > 4 are defaulted with the exception of item 9,
-- the BHP lower limit, which is given to be 1000psia in Odeh's paper
WCONINJE
-- Item #:1 2 3 4 5 6 7
'INJ' 'GAS' 'OPEN' 'RATE' 100000 1* 9014 /
/
-- Stated in Odeh that gas inj. rate (item 5) is 100MMscf per day
-- BHP upper limit (item 7) should not be exceeding the highest
-- pressure in the PVT table=9014.7psia (default is 100 000psia)
TSTEP
--Advance the simulater once a month for TEN years:
31 28 31 30 31 30 31 31 30 31 30 31
31 28 31 30 31 30 31 31 30 31 30 31
31 28 31 30 31 30 31 31 30 31 30 31
31 28 31 30 31 30 31 31 30 31 30 31
31 28 31 30 31 30 31 31 30 31 30 /
DATES
31 'DEC' 2019 /
/
TSTEP
31 28 31 30 31 30 31 31 30 31 30 31
31 28 31 30 31 30 31 31 30 31 30 31
31 28 31 30 31 30 31 31 30 31 30 31
31 28 31 30 31 30 31 31 30 31 30 31
31 28 31 30 31 30 31 31 30 31 30 31 /
--Advance the simulator once a year for TEN years:
--10*365 /
END

2
tests/include/dx.grdecl Normal file
View File

@ -0,0 +1,2 @@
DX
300*1000 /

2
tests/include/dy.grdecl Normal file
View File

@ -0,0 +1,2 @@
DY
300*1000 /

2
tests/include/dz.grdecl Normal file
View File

@ -0,0 +1,2 @@
DZ
100*20 100*30 100*50 /

View File

@ -0,0 +1,8 @@
INCLUDE
'include/dx.grdecl' /
INCLUDE
'include/dy.grdecl' /
INCLUDE
'include/dz.grdecl' /

View File

@ -0,0 +1,3 @@
PORO
300*0.3 /

View File

@ -0,0 +1,12 @@
PERMX
100*500 100*50 100*200 /
PERMY
100*500 100*50 100*200 /
PERMZ
100*500 100*50 100*200 /
INCLUDE
'include/poro.grdecl' /

View File

@ -26,6 +26,7 @@
#include <boost/test/unit_test.hpp>
#include <opm/parser/eclipse/Units/UnitSystem.hpp>
#include <opm/parser/eclipse/Deck/DeckTree.hpp>
#include <opm/parser/eclipse/Deck/DeckOutput.hpp>
#include <opm/parser/eclipse/Deck/Deck.hpp>
#include <opm/parser/eclipse/Deck/DeckKeyword.hpp>
@ -149,13 +150,6 @@ BOOST_AUTO_TEST_CASE(set_and_get_data_file) {
BOOST_CHECK_EQUAL("", deck.getInputPath());
BOOST_CHECK_EQUAL("some/path", deck.makeDeckPath("some/path"));
BOOST_CHECK_EQUAL("/abs/path", deck.makeDeckPath("/abs/path"));
std::string file("/path/to/file.DATA");
deck.setDataFile( file );
BOOST_CHECK_EQUAL(file, deck.getDataFile());
BOOST_CHECK_EQUAL("/path/to", deck.getInputPath());
BOOST_CHECK_EQUAL("/path/to/some/path", deck.makeDeckPath("some/path"));
BOOST_CHECK_EQUAL("/abs/path", deck.makeDeckPath("/abs/path"));
}
BOOST_AUTO_TEST_CASE(DummyDefaultsString) {
@ -686,3 +680,5 @@ BOOST_AUTO_TEST_CASE(STRING_TO_BOOL) {
BOOST_CHECK_THROW(DeckItem::to_bool("YE"), std::invalid_argument);
BOOST_CHECK_THROW(DeckItem::to_bool("YE"), std::invalid_argument);
}

View File

@ -23,11 +23,16 @@
#include <boost/test/unit_test.hpp>
#include <fstream>
#include <iostream>
#include <opm/common/utility/FileSystem.hpp>
#include <opm/parser/eclipse/Deck/Deck.hpp>
#include <opm/parser/eclipse/Parser/Parser.hpp>
#include <opm/parser/eclipse/EclipseState/IOConfig/IOConfig.hpp>
#include "tests/WorkArea.cpp"
namespace fs = Opm::filesystem;
using namespace Opm;
const std::string& deckStr = R"(
@ -258,12 +263,25 @@ GRIDFILE
BOOST_CHECK( !ioConfig.getWriteEGRIDFile() );
}
void touch_file(const fs::path& file) {
if (!fs::exists(file)) {
if (file.has_parent_path()) {
const auto& parent_path = file.parent_path();
if (!fs::is_directory(parent_path))
fs::create_directories(parent_path);
}
std::ofstream os{file};
}
}
BOOST_AUTO_TEST_CASE(OutputPaths) {
WorkArea wa;
IOConfig config1( "" );
BOOST_CHECK_EQUAL("", config1.getBaseName() );
Deck deck2;
touch_file("testString.DATA");
deck2.setDataFile( "testString.DATA" );
IOConfig config2( deck2 );
std::string output_dir2 = ".";
@ -273,7 +291,8 @@ BOOST_AUTO_TEST_CASE(OutputPaths) {
namespace fs = Opm::filesystem;
Deck deck3;
deck3.setDataFile( "/path/to/testString.DATA" );
touch_file("path/to/testString.DATA");
deck3.setDataFile( "path/to/testString.DATA" );
IOConfig config3( deck3 );
std::string output_dir3 = "/path/to";
config3.setOutputDir( output_dir3 );

View File

@ -21,6 +21,9 @@
#include <boost/test/unit_test.hpp>
#include <opm/common/utility/TimeService.hpp>
#include <opm/parser/eclipse/Units/UnitSystem.hpp>
#include <opm/common/utility/FileSystem.hpp>
#include <opm/parser/eclipse/Python/Python.hpp>
#include <opm/parser/eclipse/EclipseState/Grid/FieldPropsManager.hpp>
#include <opm/parser/eclipse/EclipseState/Grid/EclipseGrid.hpp>
@ -33,7 +36,9 @@
#include <opm/parser/eclipse/EclipseState/Schedule/UDQ/UDQState.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/Action/State.hpp>
#include <opm/parser/eclipse/Deck/DeckValue.hpp>
#include <opm/parser/eclipse/Deck/Deck.hpp>
#include <opm/parser/eclipse/Deck/FileDeck.hpp>
#include <opm/parser/eclipse/Parser/Parser.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/Schedule.hpp>
#include <opm/io/eclipse/rst/state.hpp>
@ -44,6 +49,9 @@
#include <iterator>
#include <memory>
#include <utility>
#include <vector>
namespace fs = Opm::filesystem;
using namespace Opm;
@ -204,3 +212,111 @@ BOOST_AUTO_TEST_CASE(LoadActionRestartSim) {
Action::State action_state;
action_state.load_rst(rst_actions, rst_state);
}
BOOST_AUTO_TEST_CASE(TestFileDeck)
{
Parser parser;
auto python = std::make_shared<Python>();
auto deck = parser.parseFile("UDQ_WCONPROD.DATA");
FileDeck fd(deck);
{
auto index = fd.start();
for (const auto& kw : deck) {
BOOST_CHECK(kw == fd[index]);
index++;
}
}
{
auto index = fd.stop();
for (std::size_t deck_index = deck.size(); deck_index > 0; deck_index--)
BOOST_CHECK(deck[deck_index - 1] == fd[--index]);
}
const auto& index1 = fd.find("RADIAL");
BOOST_CHECK( !index1.has_value() );
const auto& index2 = fd.find("DIMENS");
BOOST_CHECK( index2.has_value());
BOOST_CHECK_EQUAL( index2.value().file_index , 0);
BOOST_CHECK_EQUAL( index2.value().keyword_index, 3);
const auto& index3 = fd.find("COORD");
BOOST_CHECK_EQUAL( index3.value().file_index , 1);
BOOST_CHECK_EQUAL( index3.value().keyword_index, 3);
const auto& index4 = fd.find("SGOF");
BOOST_CHECK_EQUAL( index4.value().file_index , 2);
BOOST_CHECK_EQUAL( index4.value().keyword_index, 2);
fd.erase(index4.value());
const auto& index5 = fd.find("SGOF");
BOOST_CHECK( !index5.has_value() );
const auto& index6 = fd.find("SWOF");
const auto& index7 = fd.find("SOLUTION");
fd.erase(index6.value(), index7.value());
}
BOOST_AUTO_TEST_CASE(RestartTest)
{
Parser parser;
auto python = std::make_shared<Python>();
auto deck = parser.parseFile("UDQ_WCONPROD.DATA");
FileDeck fd(deck);
const auto solution = fd.find("SOLUTION");
const auto schedule = fd.find("SCHEDULE");
auto index = solution.value();
while (true) {
const auto& keyword = fd[++index];
if (keyword.name() == "EQUIL")
fd.erase(index);
if (index == schedule)
break;
}
UnitSystem units;
std::vector<DeckValue> values{ DeckValue{"RESTART_BASE"}, DeckValue{100} };
DeckKeyword restart(parser.getKeyword("RESTART"), std::vector<std::vector<DeckValue>>{ values }, units, units);
index = solution.value();
fd.insert(++index, restart);
BOOST_CHECK( fd[index] == restart );
// time_point tp = asTimePoint( TimeStampUTC(2018, 1, 11) );
// auto find_date = [tp](const DeckKeyword& keyword)
// {
// if (keyword.name() == "DATES") {
// const auto& record = keyword[0];
// const auto &dayItem = record.getItem(0);
// const auto &monthItem = record.getItem(1);
// const auto &yearItem = record.getItem(2);
// // Accept lower- and mixed-case month names.
// std::string monthname = uppercase(monthItem.get<std::string>(0));
// auto date = asTimePoint( TimeStampUTC( yearItem.get<int>(0), TimeService::eclipseMonthIndices().at(monthname), dayItem.get<int>(0)));
// return date == tp;
// }
// };
}

22
tests/rst_test_driver.sh Executable file
View File

@ -0,0 +1,22 @@
#!/bin/bash
set -e
rst_deck=$1
opmhash=$2
deck=$3
pushd $(mktemp -d)
${rst_deck} ${deck} HISTORY:0 output/CASE_COPY.DATA -m copy -s
${opmhash} ${deck} output/CASE_COPY.DATA -S
${rst_deck} ${deck} HISTORY:0 -m share -s > CASE_STDOUT.DATA
${opmhash} ${deck} CASE_STDOUT.DATA -S
pushd output
chmod -R a-w *
popd
${rst_deck} output/CASE_COPY.DATA HISTORY:0 output/CASE_SHARE.DATA -m copy -s
${opmhash} ${deck} output/CASE_SHARE.DATA -S
popd