rst_deck: optionally interpret restart argument as proper filename

This commit is contained in:
Joakim Hove 2021-11-08 16:10:44 +01:00
parent 01a7149b36
commit d950e3ceb1
6 changed files with 1907 additions and 39 deletions

View File

@ -531,7 +531,9 @@ if(ENABLE_ECL_OUTPUT)
tests/action_missing_run.py tests/action_missing_run.py
tests/EMBEDDED_PYTHON.DATA tests/EMBEDDED_PYTHON.DATA
tests/ACTIONX_M1.DATA tests/ACTIONX_M1.DATA
tests/ACTIONX_M1_MULTIPLE.DATA
tests/ACTIONX_M1_RESTART.DATA tests/ACTIONX_M1_RESTART.DATA
tests/ACTIONX_M1.UNRST
tests/ACTIONX_M1.X0010 tests/ACTIONX_M1.X0010
tests/wclose.py tests/wclose.py
tests/msim/MSIM_PYACTION.DATA tests/msim/MSIM_PYACTION.DATA

View File

@ -74,6 +74,15 @@ add_test( NAME rst_deck_test
${PROJECT_SOURCE_DIR}/tests/SPE1CASE2_INCLUDE.DATA) ${PROJECT_SOURCE_DIR}/tests/SPE1CASE2_INCLUDE.DATA)
add_test( NAME rst_deck_test2
COMMAND ${PROJECT_SOURCE_DIR}/tests/rst_test_driver2.sh
${PROJECT_BINARY_DIR}/bin/rst_deck
${PROJECT_BINARY_DIR}/bin/opmi
${PROJECT_SOURCE_DIR}/tests/ACTIONX_M1.DATA
${PROJECT_SOURCE_DIR}/tests/ACTIONX_M1_MULTIPLE.DATA
${PROJECT_SOURCE_DIR}/tests/ACTIONX_M1.UNRST
${PROJECT_SOURCE_DIR}/tests/ACTIONX_M1.X0010 )
# opm-tests dependent tests # opm-tests dependent tests
if(HAVE_OPM_TESTS) if(HAVE_OPM_TESTS)
opm_add_test(parse_write ONLY_COMPILE opm_add_test(parse_write ONLY_COMPILE

View File

@ -24,6 +24,7 @@
#include <getopt.h> #include <getopt.h>
#include <fmt/format.h> #include <fmt/format.h>
#include <unordered_set> #include <unordered_set>
#include <utility>
#include <opm/parser/eclipse/Deck/DeckKeyword.hpp> #include <opm/parser/eclipse/Deck/DeckKeyword.hpp>
#include <opm/parser/eclipse/Units/UnitSystem.hpp> #include <opm/parser/eclipse/Units/UnitSystem.hpp>
@ -36,6 +37,7 @@
#include <opm/parser/eclipse/Parser/ErrorGuard.hpp> #include <opm/parser/eclipse/Parser/ErrorGuard.hpp>
#include <opm/parser/eclipse/Parser/ParseContext.hpp> #include <opm/parser/eclipse/Parser/ParseContext.hpp>
#include <opm/parser/eclipse/Parser/InputErrorAction.hpp> #include <opm/parser/eclipse/Parser/InputErrorAction.hpp>
#include <opm/parser/eclipse/EclipseState/IOConfig/IOConfig.hpp>
#include <opm/parser/eclipse/Deck/Deck.hpp> #include <opm/parser/eclipse/Deck/Deck.hpp>
#include <opm/parser/eclipse/Deck/FileDeck.hpp> #include <opm/parser/eclipse/Deck/FileDeck.hpp>
@ -76,9 +78,35 @@ Arguments:
1. The data file we are starting with. 1. The data file we are starting with.
2. The basename of the restart file - with an optional path prefix and a :N to 2. The restart source; this can either be a basename with an optional path
restart from step N(2). A restart step value of 0 is interpreted as a dry run prefix and a :N to restart from step N; alternatively you can point to an
- a deck which has not been set up for restart will be written out. existing restart file. If you point to an existing restart file the input
will be validated in several ways:
a) Unified/multiple files will be checked against the UNIFIN setting of
the deck.
b) Formatted/unformatted will be checked against the FMTIn setting of the
deck.
c) If a single file like /path/to/case/HISTORY.X0067 is given as argument the
:N notation to denote report step should not be used.
If the restart argument is given as the path to an existing file the content
of the RESTART keyword will be updated to contain the correct path from the
location of the restart deck to the location of the restart file. This path
awareness will be fooled if the restart deck is redirected from stdout to a
path different from cwd. If the restart argument is given as an absolute
filename the RESTART keyword will have an absolute path, if the restart
argument is a relative path the RESTART keyword will get a relative path -
although an absolute path will be used if the restart file and the output
deck have different roots. If the restart argument is given as a string not
pointing to an existing file it will be inserted verbatim in the restart
deck.
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; 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 the path will be created if it does not already exist. This argument is
@ -111,11 +139,6 @@ Example:
1: The program has a compiled list of keywords which will be retained in the 1: The program has a compiled list of keywords which will be retained in the
SOLUTION section. The current value of that list is: {} SOLUTION section. The current value of that list is: {}
2: 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); )", keep_keywords);
std::cerr << help_text << std::endl; std::cerr << help_text << std::endl;
@ -129,15 +152,17 @@ Example:
struct options { struct options {
std::string input_deck; std::string input_deck;
std::pair<std::string, int> restart; std::string restart_base;
std::optional<std::string> target; int restart_step;
std::optional<std::string> target_path;
std::optional<std::string> target_fname;
Opm::FileDeck::OutputMode mode{Opm::FileDeck::OutputMode::SHARE}; Opm::FileDeck::OutputMode mode{Opm::FileDeck::OutputMode::SHARE};
bool skiprest{false}; bool skiprest{false};
}; };
Opm::FileDeck load_deck(const options& opt) { Opm::Deck load_deck(const options& opt) {
Opm::ParseContext parseContext(Opm::InputError::WARN); Opm::ParseContext parseContext(Opm::InputError::WARN);
Opm::ErrorGuard errors; Opm::ErrorGuard errors;
Opm::Parser parser; Opm::Parser parser;
@ -147,8 +172,7 @@ Opm::FileDeck load_deck(const options& opt) {
parseContext.update(Opm::ParseContext::PARSE_MISSING_DIMS_KEYWORD, Opm::InputError::WARN); 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_WELL, Opm::InputError::WARN);
parseContext.update(Opm::ParseContext::SUMMARY_UNKNOWN_GROUP, Opm::InputError::WARN); parseContext.update(Opm::ParseContext::SUMMARY_UNKNOWN_GROUP, Opm::InputError::WARN);
auto deck = parser.parseFile(opt.input_deck, parseContext, errors); return parser.parseFile(opt.input_deck, parseContext, errors);
return Opm::FileDeck{ deck };
} }
@ -166,16 +190,85 @@ Opm::FileDeck::OutputMode mode(const std::string& mode_arg) {
return Opm::FileDeck::OutputMode::INLINE; return Opm::FileDeck::OutputMode::INLINE;
} }
std::pair<std::string, std::size_t> split_restart(const std::string& restart_base) { std::optional<std::size_t> verify_extension(const std::string& extension, bool unified, bool formatted) {
auto sep_pos = restart_base.rfind(':'); if (unified) {
if (sep_pos == std::string::npos) if (formatted) {
print_help_and_exit(fmt::format("Expected restart argument on the form: BASE:NUMBER - e.g. HISTORY:60")); if (extension == ".FUNRST")
return std::nullopt;
print_help_and_exit("Deck has specified formatted unified input - expected restart extension: .FUNRST");
return std::make_pair(restart_base.substr(0, sep_pos), std::stoi(restart_base.substr(sep_pos + 1))); }
if (extension == ".UNRST")
return std::nullopt;
print_help_and_exit("Deck has expected unformatted unified input - expected restart extension: .UNRST");
}
std::size_t report_step;
if ((formatted && (extension[1] == 'F')) || (!formatted && (extension[1] == 'X'))) {
try {
report_step = std::stoi(extension.substr(2));
return report_step;
}
catch (...) {}
}
print_help_and_exit("Deck has specified multiple input files - expected restart extension: .Xnnnn / .Fnnnn");
return std::nullopt;
} }
options load_options(int argc, char **argv) { bool same_mount(const fs::path& p1, const fs::path& p2) {
auto abs1 = fs::absolute(p1);
auto abs2 = fs::absolute(p2);
auto iter1 = abs1.begin(); iter1++;
auto iter2 = abs2.begin(); iter2++;
auto mnt1 = *iter1;
auto mnt2 = *iter2;
return (mnt1 == mnt2);
}
void update_restart_path(options& opt, const std::string& restart_arg, const Opm::IOConfig& io_config) {
std::string base;
std::optional<std::size_t> rst_step;
auto sep_pos = restart_arg.rfind(':');
auto base_arg = restart_arg.substr(0, sep_pos);
if (fs::exists(base_arg)) {
auto unif = io_config.getUNIFIN();
auto fmt = io_config.getFMTIN();
auto path = fs::path(base_arg);
auto extension = path.extension();
rst_step = verify_extension(extension, unif, fmt);
if (path.is_absolute()) {
path.replace_extension();
base = path;
} else {
auto target_path = fs::current_path();
if (opt.target_path.has_value())
target_path = fs::path(opt.target_path.value());
if (same_mount(path, target_path))
base = fs::relative(path, target_path).replace_extension();
else
base = fs::canonical(fs::absolute(path)).replace_extension();
}
} else
base = base_arg;
if (!rst_step.has_value()) {
if (sep_pos == std::string::npos)
print_help_and_exit(fmt::format("Expected restart argument on the form: BASE:NUMBER - e.g. HISTORY:60"));
rst_step = std::stoi(restart_arg.substr(sep_pos + 1));
}
opt.restart_step = rst_step.value();
opt.restart_base = base;
}
std::pair<options, std::string> load_options(int argc, char **argv) {
options opt; options opt;
while (true) { while (true) {
int c; int c;
@ -201,11 +294,21 @@ options load_options(int argc, char **argv) {
print_help_and_exit(); print_help_and_exit();
opt.input_deck = argv[arg_offset]; opt.input_deck = argv[arg_offset];
opt.restart = split_restart(argv[arg_offset + 1]); std::string restart_arg = argv[arg_offset + 1];
if ((argc - arg_offset) >= 3) { if ((argc - arg_offset) >= 3) {
opt.target = argv[arg_offset + 2]; auto target_arg = argv[arg_offset + 2];
if (fs::is_directory(target_arg)) {
opt.target_path = target_arg;
opt.target_fname = fs::path(opt.input_deck).filename();
} else {
auto target_path = fs::path( fs::absolute(target_arg) );
opt.target_path = fs::absolute(target_path.parent_path());
opt.target_fname = target_path.filename();
}
if (opt.mode == Opm::FileDeck::OutputMode::COPY) { if (opt.mode == Opm::FileDeck::OutputMode::COPY) {
auto target = fs::path(opt.target.value()).parent_path(); auto target = fs::path(target_arg).parent_path();
if (fs::exists(target)) { if (fs::exists(target)) {
auto input = fs::path(opt.input_deck).parent_path(); auto input = fs::path(opt.input_deck).parent_path();
if (fs::equivalent(target, input)) if (fs::equivalent(target, input))
@ -217,13 +320,13 @@ options load_options(int argc, char **argv) {
print_help_and_exit("When writing output to stdout you must use inline|share mode"); print_help_and_exit("When writing output to stdout you must use inline|share mode");
} }
return opt; return {opt, restart_arg};
} }
void update_solution(const options& opt, Opm::FileDeck& file_deck) void update_solution(const options& opt, Opm::FileDeck& file_deck)
{ {
if (opt.restart.second == 0) if (opt.restart_step == 0)
return; return;
const auto solution = file_deck.find("SOLUTION"); const auto solution = file_deck.find("SOLUTION");
@ -234,37 +337,33 @@ void update_solution(const options& opt, Opm::FileDeck& file_deck)
if (!summary.has_value()) if (!summary.has_value())
print_help_and_exit(fmt::format("Could not find SUMMARY section in input deck: {}", opt.input_deck)); print_help_and_exit(fmt::format("Could not find SUMMARY section in input deck: {}", opt.input_deck));
file_deck.rst_solution(opt.restart.first, opt.restart.second); file_deck.rst_solution(opt.restart_base, opt.restart_step);
} }
void update_schedule(const options& opt, Opm::FileDeck& file_deck) void update_schedule(const options& opt, Opm::FileDeck& file_deck)
{ {
if (opt.restart.second == 0) if (opt.restart_step == 0)
return; return;
if (opt.skiprest) if (opt.skiprest)
file_deck.insert_skiprest(); file_deck.insert_skiprest();
else else
file_deck.skip(opt.restart.second); file_deck.skip(opt.restart_step);
} }
int main(int argc, char** argv) { int main(int argc, char** argv) {
auto options = load_options(argc, argv); auto [options, restart_arg] = load_options(argc, argv);
auto file_deck = load_deck(options); auto deck = load_deck(options);
Opm::FileDeck file_deck(deck);
update_restart_path(options, restart_arg, Opm::IOConfig(deck));
update_solution(options, file_deck); update_solution(options, file_deck);
update_schedule(options, file_deck); update_schedule(options, file_deck);
if (!options.target.has_value()) if (!options.target_path.has_value())
file_deck.dump_stdout(fs::current_path(), options.mode); file_deck.dump_stdout(fs::current_path(), options.mode);
else { else
std::string target = options.target.value(); file_deck.dump( options.target_path.value(), options.target_fname.value(), options.mode);
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 );
}
}
} }

BIN
tests/ACTIONX_M1.UNRST Normal file

Binary file not shown.

File diff suppressed because it is too large Load Diff

51
tests/rst_test_driver2.sh Executable file
View File

@ -0,0 +1,51 @@
#!/bin/bash
set -e
rst_deck=$1
opmi=$2
unif_deck=$3
single_deck=$4
unified_rst_file=$5
single_rst_file=$6
pushd $(mktemp -d)
# Happy path using unified restart file from source location
${rst_deck} ${unif_deck} ${unified_rst_file}:10 output/RESTART_UNIF.DATA -s
${opmi} output/RESTART_UNIF.DATA
# Happy path using single restart file with absolute path
${rst_deck} ${single_deck} ${single_rst_file} output/RESTART_SINGLE_ABS.DATA -s
${opmi} output/RESTART_SINGLE_ABS.DATA
mkdir rst
cp ${single_rst_file} rst/.
single_rst_file="$(basename -- ${single_rst_file})"
# Happy path using single restart file wth relative path from rst/
${rst_deck} ${single_deck} rst/${single_rst_file} output/RESTART_SINGLE_RELATIVE.DATA -s
${opmi} output/RESTART_SINGLE_RELATIVE.DATA
assert_error() {
retVal=$?
if [ $retVal -eq 0 ]; then
echo "Error"
exit 1
fi
}
set +e
# Test two error conditions
# Deck with UNIFIN and pass a single .X0010 restart file
${rst_deck} ${unif_deck} rst/${single_rst_file} output/RESTART_SINGLE_RELATIVE.DATA -s
assert_error
# Ask for wrong restart number from unified restart file
${rst_deck} ${unif_deck} ${unified_rst_file}:20 output/RESTART_ERROR.DATA -s
${opmi} output/RESTART_ERROR.DATA
assert_error
popd