From 9fa90b26879744260e684984aa34e43bc60c3b98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torbj=C3=B8rn=20Skille?= Date: Mon, 2 Nov 2020 22:14:49 +0100 Subject: [PATCH] Update of EclIO classes. - Enables reading of output files generated by simulator IX - Adding support for EclFile data type C0nn (string with length > 8 characters) - Update of program summary, now supporting well names with more that 8 characters - Updates of program convertECL, possible to write output files with IX "format" - updates of python bindings (EclOutput and EclFile) --- CMakeLists.txt | 6 +- CMakeLists_files.cmake | 4 + opm/io/eclipse/EclFile.hpp | 11 +- opm/io/eclipse/EclIOdata.hpp | 38 +++--- opm/io/eclipse/EclOutput.hpp | 34 +++-- opm/io/eclipse/EclUtil.hpp | 19 ++- python/cxx/converters.cpp | 4 +- python/cxx/eclipse_io.cpp | 11 +- python/opm/io/ecl/__init__.py | 11 +- python/tests/test_ecloutput.py | 131 ++++++++++++++++++ src/opm/io/eclipse/ESmry.cpp | 54 +++++--- src/opm/io/eclipse/EclFile.cpp | 145 ++++++++++++++++++-- src/opm/io/eclipse/EclOutput.cpp | 226 +++++++++++++++++++++++++++---- src/opm/io/eclipse/EclUtil.cpp | 141 +++++++++++++++---- test_util/convertECL.cpp | 39 +++++- test_util/rewriteEclFile.cpp | 102 ++++++++++++++ test_util/summary.cpp | 36 +++-- tests/MODEL1_IX.INIT | Bin 0 -> 107028 bytes tests/MODEL1_IX.SMSPEC | Bin 0 -> 50036 bytes tests/MODEL1_IX.UNSMRY | Bin 0 -> 53680 bytes tests/test_ESmry.cpp | 29 ++++ tests/test_EclIO.cpp | 163 +++++++++++++++++++--- 22 files changed, 1037 insertions(+), 167 deletions(-) create mode 100644 test_util/rewriteEclFile.cpp create mode 100644 tests/MODEL1_IX.INIT create mode 100644 tests/MODEL1_IX.SMSPEC create mode 100644 tests/MODEL1_IX.UNSMRY diff --git a/CMakeLists.txt b/CMakeLists.txt index af029aab2..85a0d606a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -197,7 +197,11 @@ if(ENABLE_ECL_INPUT) test_util/summary.cpp ) - foreach(target compareECL convertECL summary) + add_executable(rewriteEclFile + test_util/rewriteEclFile.cpp + ) + + foreach(target compareECL convertECL summary rewriteEclFile) target_link_libraries(${target} opmcommon) install(TARGETS ${target} DESTINATION bin) endforeach() diff --git a/CMakeLists_files.cmake b/CMakeLists_files.cmake index 9821b0730..d8a3b08b9 100644 --- a/CMakeLists_files.cmake +++ b/CMakeLists_files.cmake @@ -472,6 +472,10 @@ if(ENABLE_ECL_INPUT) list (APPEND TEST_DATA_FILES tests/ECLFILE.INIT tests/ECLFILE.FINIT + tests/ECLFILE.FINIT + tests/MODEL1_IX.INIT + tests/MODEL1_IX.SMSPEC + tests/MODEL1_IX.UNSMRY tests/SPE1CASE1.EGRID tests/SPE1CASE1.RFT tests/SPE1_TESTCASE.UNRST diff --git a/opm/io/eclipse/EclFile.hpp b/opm/io/eclipse/EclFile.hpp index 0f5d6f628..0d5136410 100644 --- a/opm/io/eclipse/EclFile.hpp +++ b/opm/io/eclipse/EclFile.hpp @@ -36,7 +36,7 @@ class EclFile { public: explicit EclFile(const std::string& filename, bool preload = false); - bool formattedInput() { return formatted; } + bool formattedInput() const { return formatted; } void loadData(); // load all data void loadData(const std::string& arrName); // load all arrays with array name equal to arrName @@ -55,6 +55,8 @@ public: using EclEntry = std::tuple; std::vector getList() const; + const std::vector& getElementSizeList() const { return array_element_size; } + template const std::vector& get(int arrIndex); @@ -66,6 +68,7 @@ public: const std::vector& arrayNames() const { return array_name; } std::size_t size() const; + bool is_ix() const; protected: bool formatted; @@ -80,6 +83,7 @@ protected: std::vector array_name; std::vector array_type; std::vector array_size; + std::vector array_element_size; std::vector ifStreamPos; @@ -110,7 +114,10 @@ private: void loadBinaryArray(std::fstream& fileH, std::size_t arrIndex); void loadFormattedArray(const std::string& fileStr, std::size_t arrIndex, int64_t fromPos); - + + std::vector get_bin_logi_raw_values(int arrIndex) const; + std::vector get_fmt_real_raw_str_values(int arrIndex) const; + }; }} // namespace Opm::EclIO diff --git a/opm/io/eclipse/EclIOdata.hpp b/opm/io/eclipse/EclIOdata.hpp index 7fdcbc034..eb349f5ee 100644 --- a/opm/io/eclipse/EclIOdata.hpp +++ b/opm/io/eclipse/EclIOdata.hpp @@ -24,26 +24,28 @@ namespace Opm { namespace EclIO { - // type MESS have no assisiated data + // type MESS have no assisiated data enum eclArrType { - INTE, REAL, DOUB, CHAR, LOGI, MESS + INTE, REAL, DOUB, CHAR, LOGI, MESS, C0NN }; // named constants related to binary file format - const unsigned int true_value = 0xffffffff; + const unsigned int true_value_ecl = 0xffffffff; + const unsigned int true_value_ix = 0x1000000; const unsigned int false_value = 0x00000000; + const int sizeOfInte = 4; // number of bytes pr integer (inte) element const int sizeOfReal = 4; // number of bytes pr float (real) element const int sizeOfDoub = 8; // number of bytes pr double (doub) element const int sizeOfLogi = 4; // number of bytes pr bool (logi) element const int sizeOfChar = 8; // number of bytes pr string (char) element - const int MaxBlockSizeInte = 4000; // Maximum block size for INTE arrays in binary files - const int MaxBlockSizeReal = 4000; // Maximum block size for REAL arrays in binary files - const int MaxBlockSizeDoub = 8000; // Maximum block size for DOUB arrays in binary files - const int MaxBlockSizeLogi = 4000; // Maximum block size for LOGI arrays in binary files - const int MaxBlockSizeChar = 840; // Maximum block size for CHAR arrays in binary files + const int MaxBlockSizeInte = 4000; // Maximum block size for INTE arrays in binary files + const int MaxBlockSizeReal = 4000; // Maximum block size for REAL arrays in binary files + const int MaxBlockSizeDoub = 8000; // Maximum block size for DOUB arrays in binary files + const int MaxBlockSizeLogi = 4000; // Maximum block size for LOGI arrays in binary files + const int MaxBlockSizeChar = 840; // Maximum block size for CHAR arrays in binary files // named constants related to formatted file file format const int MaxNumBlockInte = 1000; // maximum number of Inte values in block => hard line shift @@ -52,17 +54,17 @@ namespace Opm { namespace EclIO { const int MaxNumBlockLogi = 1000; // maximum number of Logi values in block => hard line shift const int MaxNumBlockChar = 105; // maximum number of Char values in block => hard line shift - const int numColumnsInte = 6; // number of columns for Inte values - const int numColumnsReal = 4; // number of columns for Real values - const int numColumnsDoub = 3; // number of columns for Doub values - const int numColumnsLogi = 25; // number of columns for Logi values - const int numColumnsChar = 7; // number of columns for Char values + const int numColumnsInte = 6; // number of columns for Inte values + const int numColumnsReal = 4; // number of columns for Real values + const int numColumnsDoub = 3; // number of columns for Doub values + const int numColumnsLogi = 25; // number of columns for Logi values + const int numColumnsChar = 7; // number of columns for Char values - const int columnWidthInte = 12; // number of characters fore each Inte Element - const int columnWidthReal = 17; // number of characters fore each Inte Element - const int columnWidthDoub = 23; // number of characters fore each Inte Element - const int columnWidthLogi = 3; // number of characters fore each Inte Element - const int columnWidthChar = 11; // number of characters fore each Inte Element + const int columnWidthInte = 12; // number of characters fore each Inte Element + const int columnWidthReal = 17; // number of characters fore each Inte Element + const int columnWidthDoub = 23; // number of characters fore each Inte Element + const int columnWidthLogi = 3; // number of characters fore each Inte Element + const int columnWidthChar = 11; // number of characters fore each Inte Element }} // namespace Opm::EclIO diff --git a/opm/io/eclipse/EclOutput.hpp b/opm/io/eclipse/EclOutput.hpp index edfa383a4..a20c4d0dd 100644 --- a/opm/io/eclipse/EclOutput.hpp +++ b/opm/io/eclipse/EclOutput.hpp @@ -47,59 +47,71 @@ public: const std::vector& data) { eclArrType arrType = MESS; + int element_size = 4; + if (typeid(T) == typeid(int)) arrType = INTE; else if (typeid(T) == typeid(float)) arrType = REAL; - else if (typeid(T) == typeid(double)) + else if (typeid(T) == typeid(double)){ arrType = DOUB; - else if (typeid(T) == typeid(bool)) + element_size = 8; + } else if (typeid(T) == typeid(bool)) arrType = LOGI; else if (typeid(T) == typeid(char)) arrType = MESS; if (isFormatted) { - writeFormattedHeader(name, data.size(), arrType); + writeFormattedHeader(name, data.size(), arrType, element_size); if (arrType != MESS) writeFormattedArray(data); } else { - writeBinaryHeader(name, data.size(), arrType); + writeBinaryHeader(name, data.size(), arrType, element_size); if (arrType != MESS) writeBinaryArray(data); } } + // when this function is used array type will be assumed C0NN (not CHAR). + // Also in cases where element size is 8 or less, element size will be 8. + + void write(const std::string& name, const std::vector& data, int element_size); + void message(const std::string& msg); void flushStream(); + void set_ix() { ix_standard = true; } + friend class OutputStream::Restart; friend class OutputStream::SummarySpecification; private: - void writeBinaryHeader(const std::string& arrName, int64_t size, eclArrType arrType); + void writeBinaryHeader(const std::string& arrName, int64_t size, eclArrType arrType, int element_size); template void writeBinaryArray(const std::vector& data); - void writeBinaryCharArray(const std::vector& data); + void writeBinaryCharArray(const std::vector& data, int element_size); void writeBinaryCharArray(const std::vector>& data); - void writeFormattedHeader(const std::string& arrName, int size, eclArrType arrType); + void writeFormattedHeader(const std::string& arrName, int size, eclArrType arrType, int element_size); template void writeFormattedArray(const std::vector& data); - void writeFormattedCharArray(const std::vector& data); + void writeFormattedCharArray(const std::vector& data, int element_size); void writeFormattedCharArray(const std::vector>& data); void writeArrayType(const eclArrType arrType); - std::string make_real_string(float value) const; - std::string make_doub_string(double value) const; + std::string make_real_string_ecl(float value) const; + std::string make_real_string_ix(float value) const; + std::string make_doub_string_ecl(double value) const; + std::string make_doub_string_ix(double value) const; - bool isFormatted; + bool isFormatted, ix_standard; std::ofstream ofileH; }; diff --git a/opm/io/eclipse/EclUtil.hpp b/opm/io/eclipse/EclUtil.hpp index 7e0501f96..68cca1625 100644 --- a/opm/io/eclipse/EclUtil.hpp +++ b/opm/io/eclipse/EclUtil.hpp @@ -41,35 +41,42 @@ namespace Opm { namespace EclIO { std::string trimr(const std::string &str1); - uint64_t sizeOnDiskBinary(int64_t num, Opm::EclIO::eclArrType arrType); - uint64_t sizeOnDiskFormatted(const int64_t num, Opm::EclIO::eclArrType arrType); + uint64_t sizeOnDiskBinary(int64_t num, Opm::EclIO::eclArrType arrType, int elementSize); + uint64_t sizeOnDiskFormatted(const int64_t num, Opm::EclIO::eclArrType arrType, int elementSize); void readBinaryHeader(std::fstream& fileH, std::string& tmpStrName, int& tmpSize, std::string& tmpStrType); void readBinaryHeader(std::fstream& fileH, std::string& arrName, - int64_t& size, Opm::EclIO::eclArrType &arrType); + int64_t& size, Opm::EclIO::eclArrType &arrType, int& elementSize); void readFormattedHeader(std::fstream& fileH, std::string& arrName, - int64_t &num, Opm::EclIO::eclArrType &arrType); + int64_t &num, Opm::EclIO::eclArrType &arrType, int& elementSize); template std::vector readBinaryArray(std::fstream& fileH, const int64_t size, Opm::EclIO::eclArrType type, - std::function& flip); + std::function& flip, int elementSize); std::vector readBinaryInteArray(std::fstream &fileH, const int64_t size); std::vector readBinaryRealArray(std::fstream& fileH, const int64_t size); std::vector readBinaryDoubArray(std::fstream& fileH, const int64_t size); std::vector readBinaryLogiArray(std::fstream &fileH, const int64_t size); + std::vector readBinaryRawLogiArray(std::fstream &fileH, const int64_t size); std::vector readBinaryCharArray(std::fstream& fileH, const int64_t size); + std::vector readBinaryC0nnArray(std::fstream& fileH, const int64_t size, int elementSize); template std::vector readFormattedArray(const std::string& file_str, const int size, int64_t fromPos, std::function& process); std::vector readFormattedInteArray(const std::string& file_str, const int64_t size, int64_t fromPos); - std::vector readFormattedCharArray(const std::string& file_str, const int64_t size, int64_t fromPos); + + std::vector readFormattedCharArray(const std::string& file_str, const int64_t size, + int64_t fromPos, int elementSize); + std::vector readFormattedRealArray(const std::string& file_str, const int64_t size, int64_t fromPos); + std::vector readFormattedRealRawStrings(const std::string& file_str, const int64_t size, int64_t fromPos); + std::vector readFormattedLogiArray(const std::string& file_str, const int64_t size, int64_t fromPos); std::vector readFormattedDoubArray(const std::string& file_str, const int64_t size, int64_t fromPos); diff --git a/python/cxx/converters.cpp b/python/cxx/converters.cpp index f2a617aae..b4f195a76 100644 --- a/python/cxx/converters.cpp +++ b/python/cxx/converters.cpp @@ -3,13 +3,13 @@ namespace convert { py::array numpy_string_array(const std::vector& input) { - const std::size_t target_length = 8; + const std::size_t target_length = 99; using numpy_string_t = char[target_length]; auto output = py::array_t(input.size()); auto output_ptr = reinterpret_cast(output.request().ptr); for (std::size_t index = 0; index < input.size(); index++) { if (input[index].size() > target_length) - throw std::invalid_argument("Current implementation only works with 8 character strings"); + throw std::invalid_argument("Current implementation only works with 99 character strings"); std::size_t length = input[index].size(); std::strncpy(output_ptr[index], input[index].c_str(), length); diff --git a/python/cxx/eclipse_io.cpp b/python/cxx/eclipse_io.cpp index d8e33d1ac..58f09476b 100644 --- a/python/cxx/eclipse_io.cpp +++ b/python/cxx/eclipse_io.cpp @@ -43,6 +43,11 @@ public: m_output->flushStream(); } + void writeC0nnArray(const std::string& name, const std::vector& data, int element_size){ + m_output->write(name, data, element_size); + m_output->flushStream(); + } + void writeMessage(const std::string& name) { m_output->message(name); @@ -70,7 +75,7 @@ npArray get_vector_index(Opm::EclIO::EclFile * file_ptr, std::size_t array_index if (array_type == Opm::EclIO::LOGI) return std::make_tuple (convert::numpy_array( file_ptr->get(array_index)), array_type); - if (array_type == Opm::EclIO::CHAR) + if ((array_type == Opm::EclIO::CHAR) || (array_type == Opm::EclIO::C0NN)) return std::make_tuple (convert::numpy_string_array( file_ptr->get(array_index)), array_type); throw std::logic_error("Data type not supported"); @@ -305,6 +310,7 @@ void python::common::export_IO(py::module& m) { .value("REAL", Opm::EclIO::REAL) .value("DOUB", Opm::EclIO::DOUB) .value("CHAR", Opm::EclIO::CHAR) + .value("C0nn", Opm::EclIO::C0NN) .value("LOGI", Opm::EclIO::LOGI) .value("MESS", Opm::EclIO::MESS) .export_values(); @@ -384,6 +390,9 @@ void python::common::export_IO(py::module& m) { .def("write_message", &EclOutputBind::writeMessage) .def("__write_char_array", (void (EclOutputBind::*)(const std::string&, const std::vector&)) &EclOutputBind::writeArray) + + .def("__write_c0nn_array", &EclOutputBind::writeC0nnArray) + .def("__write_logi_array", (void (EclOutputBind::*)(const std::string&, const std::vector&)) &EclOutputBind::writeArray) .def("__write_inte_array", (void (EclOutputBind::*)(const std::string&, diff --git a/python/opm/io/ecl/__init__.py b/python/opm/io/ecl/__init__.py index 85d2b0801..a11710cac 100644 --- a/python/opm/io/ecl/__init__.py +++ b/python/opm/io/ecl/__init__.py @@ -33,7 +33,7 @@ def getitem_eclfile(self, arg): else: data, array_type = self.__get_data(arg) - if array_type == eclArrType.CHAR: + if array_type == eclArrType.CHAR or array_type == eclArrType.C0nn: return [ x.decode("utf-8") for x in data ] return data @@ -63,7 +63,7 @@ def getitem_erst(self, arg): else: raise ValueError("expecting tuple argument with 2 or 3 argumens: (index, rstep), (name, rstep) or (name, rstep, occurrence) ") - if array_type == eclArrType.CHAR: + if array_type == eclArrType.CHAR or array_type == eclArrType.C0nn: return [ x.decode("utf-8") for x in data ] return data @@ -169,7 +169,7 @@ def getitem_erft(self, arg): (CHAR, LOGI, INTE) ''' -def ecloutput_write(self, name, array): +def ecloutput_write(self, name, array, C0nn=False): if isinstance(array, list): if all(isinstance(element, str) for element in array): @@ -197,8 +197,11 @@ def ecloutput_write(self, name, array): self.__write_doub_array(name, array) elif array.dtype == "bool": self.__write_logi_array(name, array) - elif array.dtype.kind in {'U', 'S'}: + elif array.dtype.kind in {'U', 'S'} and not C0nn: self.__write_char_array(name, array) + elif array.dtype.kind in {'U', 'S'} and C0nn: + maxStrLength = max([len(x) for x in array]) + self.__write_c0nn_array(name, array, max([maxStrLength, 8])) else: raise ValueError("unknown array type for array {}".format(name)) diff --git a/python/tests/test_ecloutput.py b/python/tests/test_ecloutput.py index f3019f71a..50b66fa8b 100755 --- a/python/tests/test_ecloutput.py +++ b/python/tests/test_ecloutput.py @@ -271,6 +271,137 @@ class TestEclOutput(unittest.TestCase): os.remove(testFile) + def test_write_strings_binary(self): + + testFile = test_path("data/TMP1.INIT") + + shortWelNames = ["PROD-1", "PROD-2", "PROD-3", "PROD-4", "PROD-5"] + longWelNames = ["PRODUCER-1", "PRODUCER-2", "PRODUCER-13", "PRODUCER-4"] + + out1 = EclOutput(testFile) + + out1.write("WGNAME", shortWelNames) + + file1 = EclFile(testFile) + array = file1["WGNAME"] + + self.assertEqual(len(array), len(shortWelNames)) + + for v1,v2 in zip (array, shortWelNames): + self.assertEqual(v1, v2) + + arrayList = file1.arrays + name, arr_type, num = arrayList[0] + + self.assertEqual(name, "WGNAME") + self.assertEqual(arr_type, eclArrType.CHAR) + self.assertEqual(num, len(shortWelNames)) + + out2 = EclOutput(testFile) + out2.write("NAMES", longWelNames) + + file2 = EclFile(testFile) + array = file2["NAMES"] + + self.assertEqual(len(array), len(longWelNames)) + + for v1,v2 in zip (array, longWelNames): + self.assertEqual(v1, v2) + + arrayList = file2.arrays + name, arr_type, num = arrayList[0] + + self.assertEqual(name, "NAMES") + self.assertEqual(arr_type, eclArrType.C0nn) + self.assertEqual(num, len(longWelNames)) + + out3 = EclOutput(testFile, formatted=False) + out3.write("NAMES", shortWelNames, C0nn=True) + + file3 = EclFile(testFile) + array = file3["NAMES"] + + self.assertEqual(len(array), len(shortWelNames)) + + for v1,v2 in zip (array, shortWelNames): + self.assertEqual(v1, v2) + + arrayList = file3.arrays + name, arr_type, num = arrayList[0] + + self.assertEqual(name, "NAMES") + self.assertEqual(arr_type, eclArrType.C0nn) + self.assertEqual(num, len(shortWelNames)) + + if os.path.isfile(testFile): + os.remove(testFile) + + + def test_write_strings_formattede(self): + + testFile = test_path("data/TMP1.FINIT") + + shortWelNames = ["PROD-1", "PROD-2", "PROD-3", "PROD-4", "PROD-5"] + longWelNames = ["PRODUCER-1", "PRODUCER-2", "PRODUCER-13", "PRODUCER-4"] + + out1 = EclOutput(testFile, formatted=True) + + out1.write("WGNAME", shortWelNames) + + file1 = EclFile(testFile) + array = file1["WGNAME"] + + self.assertEqual(len(array), len(shortWelNames)) + + for v1,v2 in zip (array, shortWelNames): + self.assertEqual(v1, v2) + + arrayList = file1.arrays + name, arr_type, num = arrayList[0] + + self.assertEqual(name, "WGNAME") + self.assertEqual(arr_type, eclArrType.CHAR) + self.assertEqual(num, len(shortWelNames)) + + out2 = EclOutput(testFile, formatted=True) + out2.write("NAMES", longWelNames) + + file2 = EclFile(testFile) + array = file2["NAMES"] + + self.assertEqual(len(array), len(longWelNames)) + + for v1,v2 in zip (array, longWelNames): + self.assertEqual(v1, v2) + + arrayList = file2.arrays + name, arr_type, num = arrayList[0] + + self.assertEqual(name, "NAMES") + self.assertEqual(arr_type, eclArrType.C0nn) + self.assertEqual(num, len(longWelNames)) + + out3 = EclOutput(testFile, formatted=True) + out3.write("NAMES", shortWelNames, C0nn=True) + + file3 = EclFile(testFile) + array = file3["NAMES"] + + self.assertEqual(len(array), len(shortWelNames)) + + for v1,v2 in zip (array, shortWelNames): + self.assertEqual(v1, v2) + + arrayList = file3.arrays + name, arr_type, num = arrayList[0] + + self.assertEqual(name, "NAMES") + self.assertEqual(arr_type, eclArrType.C0nn) + self.assertEqual(num, len(shortWelNames)) + + if os.path.isfile(testFile): + os.remove(testFile) + if __name__ == "__main__": diff --git a/src/opm/io/eclipse/ESmry.cpp b/src/opm/io/eclipse/ESmry.cpp index 9b775c475..e96ab56db 100644 --- a/src/opm/io/eclipse/ESmry.cpp +++ b/src/opm/io/eclipse/ESmry.cpp @@ -158,7 +158,13 @@ ESmry::ESmry(const std::string &filename, bool loadBaseRunData) : const std::vector restartArray = smspecList.back().get("RESTART"); const std::vector keywords = smspecList.back().get("KEYWORDS"); - const std::vector wgnames = smspecList.back().get("WGNAMES"); + std::vector wgnames; + + if (smspecList.back().hasKey("WGNAMES")) + wgnames = smspecList.back().get("WGNAMES"); + else + wgnames = smspecList.back().get("NAMES"); + const std::vector nums = smspecList.back().get("NUMS"); const std::vector units = smspecList.back().get("UNITS"); @@ -223,7 +229,13 @@ ESmry::ESmry(const std::string &filename, bool loadBaseRunData) : const std::vector dimens = smspecList.back().get("DIMENS"); const std::vector restartArray = smspecList.back().get("RESTART"); const std::vector keywords = smspecList.back().get("KEYWORDS"); - const std::vector wgnames = smspecList.back().get("WGNAMES"); + std::vector wgnames; + + if (smspecList.back().hasKey("WGNAMES")) + wgnames = smspecList.back().get("WGNAMES"); + else + wgnames = smspecList.back().get("NAMES"); + const std::vector nums = smspecList.back().get("NUMS"); const std::vector units = smspecList.back().get("UNITS"); @@ -285,7 +297,13 @@ ESmry::ESmry(const std::string &filename, bool loadBaseRunData) : nParamsSpecFile[specInd] = dimens[0]; const std::vector keywords = smspecList[specInd].get("KEYWORDS"); - const std::vector wgnames = smspecList[specInd].get("WGNAMES"); + std::vector wgnames; + + if (smspecList.back().hasKey("WGNAMES")) + wgnames = smspecList.back().get("WGNAMES"); + else + wgnames = smspecList.back().get("NAMES"); + const std::vector nums = smspecList[specInd].get("NUMS"); for (size_t i=0; i < keywords.size(); i++) { @@ -457,15 +475,16 @@ void ESmry::inspect_lodsmry() std::string arrName; int64_t arr_size; Opm::EclIO::eclArrType arrType; + int sizeOfElement; std::fstream fileH; if (formattedFiles[0]) { fileH.open(lodFileName, std::ios::in); - Opm::EclIO::readFormattedHeader(fileH, arrName, arr_size, arrType); + Opm::EclIO::readFormattedHeader(fileH, arrName, arr_size, arrType, sizeOfElement); } else { fileH.open(lodFileName, std::ios::in | std::ios::binary); - Opm::EclIO::readBinaryHeader(fileH, arrName, arr_size, arrType); + Opm::EclIO::readBinaryHeader(fileH, arrName, arr_size, arrType, sizeOfElement); } if ((arrName != "KEYCHECK") or (arrType != Opm::EclIO::CHAR)) @@ -474,10 +493,10 @@ void ESmry::inspect_lodsmry() std::vector keycheck; if (formattedFiles[0]) { - uint64_t size = Opm::EclIO::sizeOnDiskFormatted(arr_size, Opm::EclIO::CHAR) + 1; + uint64_t size = Opm::EclIO::sizeOnDiskFormatted(arr_size, Opm::EclIO::CHAR, sizeOfChar) + 1; std::string fileStr = read_string_from_disk(fileH, size); - keycheck = Opm::EclIO::readFormattedCharArray(fileStr, arr_size, 0); + keycheck = Opm::EclIO::readFormattedCharArray(fileStr, arr_size, 0, sizeOfChar); } else { keycheck = Opm::EclIO::readBinaryCharArray(fileH, arr_size); } @@ -501,9 +520,9 @@ void ESmry::inspect_lodsmry() } if (formattedFiles[0]) - Opm::EclIO::readFormattedHeader(fileH, arrName, arr_size, arrType); + Opm::EclIO::readFormattedHeader(fileH, arrName, arr_size, arrType, sizeOfElement); else - Opm::EclIO::readBinaryHeader(fileH, arrName, arr_size, arrType); + Opm::EclIO::readBinaryHeader(fileH, arrName, arr_size, arrType, sizeOfElement); if ((arrName != "RSTEP ") or (arrType != Opm::EclIO::LOGI)) OPM_THROW(std::invalid_argument, "reading rstep, invalid lod file"); @@ -511,7 +530,7 @@ void ESmry::inspect_lodsmry() std::vector rstep; if (formattedFiles[0]) { - uint64_t size = Opm::EclIO::sizeOnDiskFormatted(arr_size, Opm::EclIO::LOGI) + 1; + uint64_t size = Opm::EclIO::sizeOnDiskFormatted(arr_size, Opm::EclIO::LOGI, sizeOfLogi) + 1; std::string fileStr = read_string_from_disk(fileH, size); rstep = Opm::EclIO::readFormattedLogiArray(fileStr, arr_size, 0); @@ -527,9 +546,9 @@ void ESmry::inspect_lodsmry() nTstep = rstep.size(); if (formattedFiles[0]) - lod_arr_size = sizeOnDiskFormatted(nTstep, Opm::EclIO::REAL); + lod_arr_size = sizeOnDiskFormatted(nTstep, Opm::EclIO::REAL, sizeOfReal); else - lod_arr_size = sizeOnDiskBinary(nTstep, Opm::EclIO::REAL); + lod_arr_size = sizeOnDiskBinary(nTstep, Opm::EclIO::REAL, sizeOfReal); fileH.close(); } @@ -558,6 +577,7 @@ void ESmry::Load_from_lodsmry(const std::vector& keywIndVect) const std::string arrName; int64_t size; Opm::EclIO::eclArrType arrType; + int sizeOfElement; uint64_t pos = lod_offset + lod_arr_size*static_cast(ind); @@ -569,9 +589,9 @@ void ESmry::Load_from_lodsmry(const std::vector& keywIndVect) const fileH.seekg (pos, fileH.beg); if (formattedFiles[0]) - readFormattedHeader(fileH, arrName, size, arrType); + readFormattedHeader(fileH, arrName, size, arrType, sizeOfElement); else - readBinaryHeader(fileH, arrName, size, arrType); + readBinaryHeader(fileH, arrName, size, arrType, sizeOfElement); arrName = Opm::EclIO::trimr(arrName); @@ -777,7 +797,7 @@ void ESmry::LoadData() const if (formattedFiles[specInd]) { char* buffer; - size_t size = sizeOnDiskFormatted(nParamsSpecFile[specInd], Opm::EclIO::REAL)+1; + size_t size = sizeOnDiskFormatted(nParamsSpecFile[specInd], Opm::EclIO::REAL, sizeOfReal) + 1; buffer = new char [size]; fileH.read (buffer, size); @@ -932,10 +952,10 @@ ESmry::getListOfArrays(std::string filename, bool formatted) if (num > 0) { if (formatted) { - uint64_t sizeOfNextArray = sizeOnDiskFormatted(num, arrType); + uint64_t sizeOfNextArray = sizeOnDiskFormatted(num, arrType, 4); fseek(ptr, static_cast(sizeOfNextArray), SEEK_CUR); } else { - uint64_t sizeOfNextArray = sizeOnDiskBinary(num, arrType); + uint64_t sizeOfNextArray = sizeOnDiskBinary(num, arrType, 4); fseek(ptr, static_cast(sizeOfNextArray), SEEK_CUR); } } diff --git a/src/opm/io/eclipse/EclFile.cpp b/src/opm/io/eclipse/EclFile.cpp index e906e0a71..44b951703 100644 --- a/src/opm/io/eclipse/EclFile.cpp +++ b/src/opm/io/eclipse/EclFile.cpp @@ -64,17 +64,19 @@ EclFile::EclFile(const std::string& filename, bool preload) : inputFilename(file std::string arrName(8,' '); eclArrType arrType; int64_t num; + int sizeOfElement; if (formatted) { - readFormattedHeader(fileH,arrName,num,arrType); + readFormattedHeader(fileH,arrName,num,arrType, sizeOfElement); } else { - readBinaryHeader(fileH,arrName,num,arrType); + readBinaryHeader(fileH,arrName,num, arrType, sizeOfElement); } array_size.push_back(num); array_type.push_back(arrType); - array_name.push_back(trimr(arrName)); + array_element_size.push_back(sizeOfElement); + array_index[array_name[n]] = n; uint64_t pos = fileH.tellg(); @@ -84,15 +86,14 @@ EclFile::EclFile(const std::string& filename, bool preload) : inputFilename(file if (num > 0){ if (formatted) { - uint64_t sizeOfNextArray = sizeOnDiskFormatted(num, arrType); + uint64_t sizeOfNextArray = sizeOnDiskFormatted(num, arrType, sizeOfElement); fileH.seekg(static_cast(sizeOfNextArray), std::ios_base::cur); } else { - uint64_t sizeOfNextArray = sizeOnDiskBinary(num, arrType); + uint64_t sizeOfNextArray = sizeOnDiskBinary(num, arrType, sizeOfElement); fileH.seekg(static_cast(sizeOfNextArray), std::ios_base::cur); } } - n++; }; @@ -125,6 +126,9 @@ void EclFile::loadBinaryArray(std::fstream& fileH, std::size_t arrIndex) case CHAR: char_array[arrIndex] = readBinaryCharArray(fileH, array_size[arrIndex]); break; + case C0NN: + char_array[arrIndex] = readBinaryC0nnArray(fileH, array_size[arrIndex], array_element_size[arrIndex]); + break; case MESS: break; default: @@ -152,7 +156,10 @@ void EclFile::loadFormattedArray(const std::string& fileStr, std::size_t arrInde logi_array[arrIndex] = readFormattedLogiArray(fileStr, array_size[arrIndex], fromPos); break; case CHAR: - char_array[arrIndex] = readFormattedCharArray(fileStr, array_size[arrIndex], fromPos); + char_array[arrIndex] = readFormattedCharArray(fileStr, array_size[arrIndex], fromPos, sizeOfChar); + break; + case C0NN: + char_array[arrIndex] = readFormattedCharArray(fileStr, array_size[arrIndex], fromPos, array_element_size[arrIndex]); break; case MESS: break; @@ -208,7 +215,7 @@ void EclFile::loadData(const std::string& name) inFile.seekg(ifStreamPos[arrIndex]); char* buffer; - size_t size = sizeOnDiskFormatted(array_size[arrIndex], array_type[arrIndex])+1; + size_t size = sizeOnDiskFormatted(array_size[arrIndex], array_type[arrIndex], array_element_size[arrIndex])+1; buffer = new char [size]; inFile.read (buffer, size); @@ -253,7 +260,7 @@ void EclFile::loadData(const std::vector& arrIndex) inFile.seekg(ifStreamPos[ind]); char* buffer; - size_t size = sizeOnDiskFormatted(array_size[ind], array_type[ind])+1; + size_t size = sizeOnDiskFormatted(array_size[ind], array_type[ind], array_element_size[ind])+1; buffer = new char [size]; inFile.read (buffer, size); @@ -284,7 +291,6 @@ void EclFile::loadData(const std::vector& arrIndex) void EclFile::loadData(int arrIndex) { - if (formatted) { std::ifstream inFile(inputFilename); @@ -292,7 +298,7 @@ void EclFile::loadData(int arrIndex) inFile.seekg(ifStreamPos[arrIndex]); char* buffer; - size_t size = sizeOnDiskFormatted(array_size[arrIndex], array_type[arrIndex])+1; + size_t size = sizeOnDiskFormatted(array_size[arrIndex], array_type[arrIndex], array_element_size[arrIndex])+1; buffer = new char [size]; inFile.read (buffer, size); @@ -318,6 +324,109 @@ void EclFile::loadData(int arrIndex) } } +bool EclFile::is_ix() const +{ + // assuming that array data type C0nn only are used in IX. This may change in future. + + // Formatted files, + // >> use real arrays. Example Ecl = '0.70000000E-01', IX = '7.0000000E-02' + // Binary files, + // >> if logi array exists in file, look for IX spes binary representation of true value + + if (formatted) { + for (size_t n=0; n < array_type.size(); n++) { + if (array_type[n] == Opm::EclIO::C0NN) { + return true; + } else if (array_type[n] == Opm::EclIO::REAL) { + auto realStr = get_fmt_real_raw_str_values(n); + int p, first; + + for (auto val : realStr) { + double dtmpv = abs(std::stod(val)); + + if (dtmpv > 0.0) { + p = val.find_first_of("."); + first = abs(std::stoi(val.substr(0, p))); + + if (first > 0) + return true; + else + return false; + } + } + } + } + } else { + for (size_t n=0; n < array_type.size(); n++) { + if (array_type[n] == Opm::EclIO::C0NN) { + return true; + } else if (array_type[n] == Opm::EclIO::LOGI) { + auto raw_logi_values = get_bin_logi_raw_values(n); + for (unsigned int val : raw_logi_values) { + if (val == Opm::EclIO::true_value_ix) + return true; + } + } + } + + return false; + } + + return false; +} + +std::vector EclFile::get_bin_logi_raw_values(int arrIndex) const +{ + if (array_type[arrIndex] != Opm::EclIO::LOGI) + OPM_THROW(std::runtime_error, "Error, selected array is not of type LOGI"); + + std::fstream fileH; + fileH.open(inputFilename, std::ios::in | std::ios::binary); + + if (!fileH) { + std::string message="Could not open file: '" + inputFilename +"'"; + OPM_THROW(std::runtime_error, message); + } + + fileH.seekg (ifStreamPos[arrIndex], fileH.beg); + + std::vector raw_logi = readBinaryRawLogiArray(fileH, array_size[arrIndex]); + + return raw_logi; +} + +std::vector EclFile::get_fmt_real_raw_str_values(int arrIndex) const +{ + std::vector real_vect; + + if (array_type[arrIndex] != Opm::EclIO::REAL) + OPM_THROW(std::runtime_error, "Error, selected array is not of type REAL"); + + std::ifstream inFile(inputFilename); + + if (!inFile) { + std::string message="Could not open file: '" + inputFilename +"'"; + OPM_THROW(std::runtime_error, message); + } + + inFile.seekg(ifStreamPos[arrIndex]); + + char* buffer; + size_t size = sizeOnDiskFormatted(array_size[arrIndex], array_type[arrIndex], array_element_size[arrIndex])+1; + + buffer = new char [size]; + inFile.read (buffer, size); + + std::string fileStr = std::string(buffer, size); + + std::vector real_vect_str; + real_vect_str = readFormattedRealRawStrings(fileStr, array_size[arrIndex], 0); + delete buffer; + + return real_vect_str; +} + + std::vector EclFile::getList() const { std::vector list; @@ -362,7 +471,12 @@ const std::vector& EclFile::get(int arrIndex) template<> const std::vector& EclFile::get(int arrIndex) { - return getImpl(arrIndex, CHAR, char_array, "string"); + if ((array_type[arrIndex] != Opm::EclIO::C0NN) && (array_type[arrIndex] != Opm::EclIO::CHAR)){ + std::string message = "Array with index " + std::to_string(arrIndex) + " is not of type " + "std::string"; + OPM_THROW(std::runtime_error, message); + } + + return getImpl(arrIndex, array_type[arrIndex], char_array, "string"); } @@ -498,7 +612,12 @@ const std::vector& EclFile::get(const std::string &nam OPM_THROW(std::invalid_argument, message); } - return getImpl(search->second, CHAR, char_array, "string"); + if ((array_type[search->second] != Opm::EclIO::C0NN) && (array_type[search->second] != Opm::EclIO::CHAR)){ + std::string message = "Array with index " + std::to_string(search->second) + " is not of type " + "std::string"; + OPM_THROW(std::runtime_error, message); + } + + return getImpl(search->second, array_type[search->second], char_array, "string"); } diff --git a/src/opm/io/eclipse/EclOutput.cpp b/src/opm/io/eclipse/EclOutput.cpp index f112cf9ea..3431824f6 100644 --- a/src/opm/io/eclipse/EclOutput.cpp +++ b/src/opm/io/eclipse/EclOutput.cpp @@ -41,6 +41,7 @@ EclOutput::EclOutput(const std::string& filename, : isFormatted{formatted} { const auto binmode = mode | std::ios_base::binary; + ix_standard = false; this->ofileH.open(filename, this->isFormatted ? mode : binmode); } @@ -50,15 +51,79 @@ template<> void EclOutput::write(const std::string& name, const std::vector& data) { + // array type will be assumed CHAR if maximum string length is 8 or less + // If maximum string length is > 8, C0nn will be used with element size equal to + // maximum string length + + int maximum_length = 8; + + if (data.size() > 0) { + auto it = std::max_element(data.begin(), data.end(), [] + (const std::string& str1, const std::string& str2) + { + return str2.size() > str1.size(); + }); + + maximum_length = it->size(); + } + + if (isFormatted) { - writeFormattedHeader(name, data.size(), CHAR); - writeFormattedCharArray(data); + if (maximum_length > sizeOfChar){ + writeFormattedHeader(name, data.size(), C0NN, maximum_length); + writeFormattedCharArray(data, maximum_length); + } else { + writeFormattedHeader(name, data.size(), CHAR, sizeOfChar); + writeFormattedCharArray(data, sizeOfChar); + } } else { - writeBinaryHeader(name, data.size(), CHAR); - writeBinaryCharArray(data); + if (maximum_length > sizeOfChar){ + writeBinaryHeader(name, data.size(), C0NN, maximum_length); + writeBinaryCharArray(data, maximum_length); + } else { + writeBinaryHeader(name, data.size(), CHAR, sizeOfChar); + writeBinaryCharArray(data, sizeOfChar); + } + } +} + +void EclOutput::write(const std::string& name, const std::vector& data, int element_size) +{ + // array type will be assumed C0NN (not CHAR). Also in cases where element size is 8 or less + + if (data.size() > 0) { + auto it = std::max_element(data.begin(), data.end(), [] + (const std::string& str1, const std::string& str2) + { + return str2.size() > str1.size(); + }); + + if (it->size() > static_cast(element_size)) + OPM_THROW(std::runtime_error, "specified element size for type C0NN less than maximum string length in ouput data"); + } + + if (isFormatted) + { + if (element_size > sizeOfChar){ + writeFormattedHeader(name, data.size(), C0NN, element_size); + writeFormattedCharArray(data, element_size); + } else { + writeFormattedHeader(name, data.size(), C0NN, sizeOfChar); + writeFormattedCharArray(data, sizeOfChar); + } + } + else + { + if (element_size > sizeOfChar){ + writeBinaryHeader(name, data.size(), C0NN, element_size); + writeBinaryCharArray(data, element_size); + } else { + writeBinaryHeader(name, data.size(), C0NN, sizeOfChar); + writeBinaryCharArray(data, sizeOfChar); + } } } @@ -68,11 +133,11 @@ void EclOutput::write> const std::vector>& data) { if (this->isFormatted) { - writeFormattedHeader(name, data.size(), CHAR); + writeFormattedHeader(name, data.size(), CHAR, sizeOfChar); writeFormattedCharArray(data); } else { - writeBinaryHeader(name, data.size(), CHAR); + writeBinaryHeader(name, data.size(), CHAR, sizeOfChar); writeBinaryCharArray(data); } } @@ -91,7 +156,7 @@ void EclOutput::flushStream() this->ofileH.flush(); } -void EclOutput::writeBinaryHeader(const std::string&arrName, int64_t size, eclArrType arrType) +void EclOutput::writeBinaryHeader(const std::string&arrName, int64_t size, eclArrType arrType, int element_size) { int bhead = flipEndianInt(16); std::string name = arrName + std::string(8 - arrName.size(),' '); @@ -102,13 +167,13 @@ void EclOutput::writeBinaryHeader(const std::string&arrName, int64_t size, eclAr int64_t x231 = size / val231; int flippedx231 = flipEndianInt(static_cast( (-1)*x231 )); - + ofileH.write(reinterpret_cast(&bhead), sizeof(bhead)); ofileH.write(name.c_str(), 8); ofileH.write(reinterpret_cast(&flippedx231), sizeof(flippedx231)); ofileH.write("X231", 4); ofileH.write(reinterpret_cast(&bhead), sizeof(bhead)); - + size = size - (x231 * val231); } @@ -119,6 +184,14 @@ void EclOutput::writeBinaryHeader(const std::string&arrName, int64_t size, eclAr ofileH.write(name.c_str(), 8); ofileH.write(reinterpret_cast(&flippedSize), sizeof(flippedSize)); + std::string c0nn_str; + + if (arrType == C0NN){ + std::ostringstream ss; + ss << "C" << std::setw(3) << std::setfill('0') << element_size; + c0nn_str = ss.str(); + } + switch(arrType) { case INTE: ofileH.write("INTE", 4); @@ -135,6 +208,9 @@ void EclOutput::writeBinaryHeader(const std::string&arrName, int64_t size, eclAr case CHAR: ofileH.write("CHAR", 4); break; + case C0NN: + ofileH.write(c0nn_str.c_str(), 4); + break; case MESS: ofileH.write("MESS", 4); break; @@ -178,6 +254,8 @@ void EclOutput::writeBinaryArray(const std::vector& data) OPM_THROW(std::runtime_error, "fstream fileH not open for writing"); } + int logi_true_val = ix_standard ? true_value_ix : true_value_ecl; + rest = size * static_cast(sizeOfElement); while (rest > 0) { if (rest > maxBlockSize) { @@ -203,7 +281,7 @@ void EclOutput::writeBinaryArray(const std::vector& data) value_d = flipEndianDouble(data[n]); ofileH.write(reinterpret_cast(&value_d), sizeof(value_d)); } else if (arrType == LOGI) { - intVal = data[n] ? true_value : false_value; + intVal = data[n] ? logi_true_val : false_value; ofileH.write(reinterpret_cast(&intVal), sizeOfElement); } else { std::cerr << "type not supported in write binaryarray\n"; @@ -225,7 +303,7 @@ template void EclOutput::writeBinaryArray(const std::vector& data); template void EclOutput::writeBinaryArray(const std::vector& data); -void EclOutput::writeBinaryCharArray(const std::vector& data) +void EclOutput::writeBinaryCharArray(const std::vector& data, int element_size) { int num,dhead; @@ -234,6 +312,11 @@ void EclOutput::writeBinaryCharArray(const std::vector& data) auto sizeData = block_size_data_binary(CHAR); + if (element_size > sizeOfChar){ + std::get<1>(sizeData)= std::get<1>(sizeData) / std::get<0>(sizeData) * element_size; + std::get<0>(sizeData) = element_size; + } + int sizeOfElement = std::get<0>(sizeData); int maxBlockSize = std::get<1>(sizeData); int maxNumberOfElements = maxBlockSize / sizeOfElement; @@ -258,7 +341,7 @@ void EclOutput::writeBinaryCharArray(const std::vector& data) ofileH.write(reinterpret_cast(&dhead), sizeof(dhead)); for (int i = 0; i < num; i++) { - std::string tmpStr = data[n] + std::string(8 - data[n].size(),' '); + std::string tmpStr = data[n] + std::string(sizeOfElement - data[n].size(),' '); ofileH.write(tmpStr.c_str(), sizeOfElement); n++; } @@ -303,12 +386,21 @@ void EclOutput::writeBinaryCharArray(const std::vector>& d } } -void EclOutput::writeFormattedHeader(const std::string& arrName, int size, eclArrType arrType) +void EclOutput::writeFormattedHeader(const std::string& arrName, int size, eclArrType arrType, int element_size) { std::string name = arrName + std::string(8 - arrName.size(),' '); ofileH << " '" << name << "' " << std::setw(11) << size; + std::string c0nn_str; + + if (arrType == C0NN){ + std::ostringstream ss; + ss << "C" << std::setw(3) << std::setfill('0') << element_size; + c0nn_str = ss.str(); + } + + switch (arrType) { case INTE: ofileH << " 'INTE'" << std::endl; @@ -325,6 +417,9 @@ void EclOutput::writeFormattedHeader(const std::string& arrName, int size, eclAr case CHAR: ofileH << " 'CHAR'" << std::endl; break; + case C0NN: + ofileH << " '" << c0nn_str << "'" << std::endl; + break; case MESS: ofileH << " 'MESS'" << std::endl; break; @@ -332,7 +427,7 @@ void EclOutput::writeFormattedHeader(const std::string& arrName, int size, eclAr } -std::string EclOutput::make_real_string(float value) const +std::string EclOutput::make_real_string_ecl(float value) const { char buffer [15]; std::sprintf (buffer, "%10.7E", value); @@ -367,8 +462,32 @@ std::string EclOutput::make_real_string(float value) const } } +std::string EclOutput::make_real_string_ix(float value) const +{ + char buffer [15]; + std::sprintf (buffer, "%10.7E", value); -std::string EclOutput::make_doub_string(double value) const + if (value == 0.0) { + return " 0.0000000E+00"; + } else { + if (std::isnan(value)) + return "NAN"; + + if (std::isinf(value)) { + if (value > 0) + return "INF"; + else + return "-INF"; + } + + std::string tmpstr(buffer); + + return tmpstr; + } +} + + +std::string EclOutput::make_doub_string_ecl(double value) const { char buffer [21]; std::sprintf (buffer, "%19.13E", value); @@ -409,6 +528,30 @@ std::string EclOutput::make_doub_string(double value) const } } +std::string EclOutput::make_doub_string_ix(double value) const +{ + char buffer [21]; + std::sprintf (buffer, "%19.13E", value); + + if (value == 0.0) { + return " 0.0000000000000E+00"; + } else { + if (std::isnan(value)) + return "NAN"; + + if (std::isinf(value)) { + if (value > 0) + return "INF"; + else + return "-INF"; + } + + std::string tmpstr(buffer); + + return tmpstr; + } +} + template void EclOutput::writeFormattedArray(const std::vector& data) @@ -427,6 +570,7 @@ void EclOutput::writeFormattedArray(const std::vector& data) arrType = LOGI; } + auto sizeData = block_size_data_formatted(arrType); int maxBlockSize = std::get<0>(sizeData); @@ -441,10 +585,16 @@ void EclOutput::writeFormattedArray(const std::vector& data) ofileH << std::setw(columnWidth) << data[i]; break; case REAL: - ofileH << std::setw(columnWidth) << make_real_string(data[i]); + if (ix_standard) + ofileH << std::setw(columnWidth) << make_real_string_ix(data[i]); + else + ofileH << std::setw(columnWidth) << make_real_string_ecl(data[i]); break; case DOUB: - ofileH << std::setw(columnWidth) << make_doub_string(data[i]); + if (ix_standard) + ofileH << std::setw(columnWidth) << make_doub_string_ix(data[i]); + else + ofileH << std::setw(columnWidth) << make_doub_string_ecl(data[i]); break; case LOGI: if (data[i]) { @@ -479,30 +629,50 @@ template void EclOutput::writeFormattedArray(const std::vector& data template void EclOutput::writeFormattedArray(const std::vector& data); -void EclOutput::writeFormattedCharArray(const std::vector& data) +void EclOutput::writeFormattedCharArray(const std::vector& data, int element_size) { auto sizeData = block_size_data_formatted(CHAR); + int maxBlockSize = std::get<0>(sizeData); - int nColumns = std::get<1>(sizeData); + int nColumns; - int size = data.size(); + if (element_size < 9) + { + element_size = 8; + nColumns = std::get<1>(sizeData); + } else + nColumns = 80 / (element_size + 3); - for (int i = 0; i < size; i++) { - std::string str1(8,' '); - str1 = data[i] + std::string(8 - data[i].size(),' '); + int rest = data.size(); + int n = 0; - ofileH << " '" << str1 << "'"; + while (rest > 0) { + int size = rest; - if ((i+1) % nColumns == 0) { + if (size > maxBlockSize) + size = maxBlockSize; + + for (int i = 0; i < size; i++) { + std::string str1(element_size,' '); + str1 = data[n] + std::string(element_size - data[n].size(),' '); + + n++; + ofileH << " '" << str1 << "'"; + + if ((i+1) % nColumns == 0) { + ofileH << std::endl; + } + } + + if ((size % nColumns) != 0) { ofileH << std::endl; } - } - if ((size % nColumns) != 0) { - ofileH << std::endl; + rest = (rest > maxBlockSize) ? rest - maxBlockSize : 0; } } + void EclOutput::writeFormattedCharArray(const std::vector>& data) { const auto sizeData = block_size_data_formatted(CHAR); diff --git a/src/opm/io/eclipse/EclUtil.cpp b/src/opm/io/eclipse/EclUtil.cpp index dfa548dfc..f349f3996 100644 --- a/src/opm/io/eclipse/EclUtil.cpp +++ b/src/opm/io/eclipse/EclUtil.cpp @@ -27,6 +27,8 @@ #include #include +//temporary +#include int Opm::EclIO::flipEndianInt(int num) { @@ -111,6 +113,9 @@ std::tuple Opm::EclIO::block_size_data_binary(eclArrType arrType) case CHAR: return BlockSizeTuple{sizeOfChar, MaxBlockSizeChar}; break; + case C0NN: + return BlockSizeTuple{sizeOfChar, MaxBlockSizeChar}; + break; case MESS: OPM_THROW(std::invalid_argument, "Type 'MESS' have no associated data"); break; @@ -141,6 +146,9 @@ std::tuple Opm::EclIO::block_size_data_formatted(eclArrType arrTy case CHAR: return BlockSizeTuple{MaxNumBlockChar,numColumnsChar, columnWidthChar}; break; + case C0NN: + return BlockSizeTuple{MaxNumBlockChar,numColumnsChar, columnWidthChar}; + break; case MESS: OPM_THROW(std::invalid_argument, "Type 'MESS' have no associated data") ; break; @@ -162,7 +170,7 @@ std::string Opm::EclIO::trimr(const std::string &str1) } } -uint64_t Opm::EclIO::sizeOnDiskBinary(int64_t num, Opm::EclIO::eclArrType arrType) +uint64_t Opm::EclIO::sizeOnDiskBinary(int64_t num, Opm::EclIO::eclArrType arrType, int elementSize) { uint64_t size = 0; @@ -175,6 +183,11 @@ uint64_t Opm::EclIO::sizeOnDiskBinary(int64_t num, Opm::EclIO::eclArrType arrTyp if (num > 0) { auto sizeData = Opm::EclIO::block_size_data_binary(arrType); + if (arrType == Opm::EclIO::C0NN){ + std::get<1>(sizeData)= std::get<1>(sizeData) / std::get<0>(sizeData) * elementSize; + std::get<0>(sizeData) = elementSize; + } + int sizeOfElement = std::get<0>(sizeData); int maxBlockSize = std::get<1>(sizeData); int maxNumberOfElements = maxBlockSize / sizeOfElement; @@ -197,7 +210,7 @@ uint64_t Opm::EclIO::sizeOnDiskBinary(int64_t num, Opm::EclIO::eclArrType arrTyp return size; } -uint64_t Opm::EclIO::sizeOnDiskFormatted(const int64_t num, Opm::EclIO::eclArrType arrType) +uint64_t Opm::EclIO::sizeOnDiskFormatted(const int64_t num, Opm::EclIO::eclArrType arrType, int elementSize) { uint64_t size = 0; @@ -208,6 +221,11 @@ uint64_t Opm::EclIO::sizeOnDiskFormatted(const int64_t num, Opm::EclIO::eclArrTy } else { auto sizeData = block_size_data_formatted(arrType); + if (arrType == Opm::EclIO::C0NN){ + std::get<2>(sizeData) = elementSize + 3; + std::get<1>(sizeData) = 80 / std::get<2>(sizeData); + } + int maxBlockSize = std::get<0>(sizeData); int nColumns = std::get<1>(sizeData); int columnWidth = std::get<2>(sizeData); @@ -272,7 +290,7 @@ void Opm::EclIO::readBinaryHeader(std::fstream& fileH, std::string& tmpStrName, } void Opm::EclIO::readBinaryHeader(std::fstream& fileH, std::string& arrName, - int64_t& size, Opm::EclIO::eclArrType &arrType) + int64_t& size, Opm::EclIO::eclArrType &arrType, int& elementSize) { std::string tmpStrName(8,' '); std::string tmpStrType(4,' '); @@ -297,15 +315,25 @@ void Opm::EclIO::readBinaryHeader(std::fstream& fileH, std::string& arrName, size = static_cast(tmpSize); } + elementSize = 4; + arrName = tmpStrName; if (tmpStrType == "INTE") arrType = Opm::EclIO::INTE; else if (tmpStrType == "REAL") arrType = Opm::EclIO::REAL; - else if (tmpStrType == "DOUB") + else if (tmpStrType == "DOUB"){ arrType = Opm::EclIO::DOUB; - else if (tmpStrType == "CHAR") + elementSize = 8; + } + else if (tmpStrType == "CHAR"){ arrType = Opm::EclIO::CHAR; + elementSize = 8; + } + else if (tmpStrType.substr(0,1)=="C"){ + arrType = Opm::EclIO::C0NN; + elementSize = std::stoi(tmpStrType.substr(1,3)); + } else if (tmpStrType =="LOGI") arrType = Opm::EclIO::LOGI; else if (tmpStrType == "MESS") @@ -316,7 +344,7 @@ void Opm::EclIO::readBinaryHeader(std::fstream& fileH, std::string& arrName, void Opm::EclIO::readFormattedHeader(std::fstream& fileH, std::string& arrName, - int64_t &num, Opm::EclIO::eclArrType &arrType) + int64_t &num, Opm::EclIO::eclArrType &arrType, int& elementSize) { std::string line; std::getline(fileH,line); @@ -336,14 +364,24 @@ void Opm::EclIO::readFormattedHeader(std::fstream& fileH, std::string& arrName, num = std::stol(antStr); + elementSize = 4; + if (arrTypeStr == "INTE") arrType = Opm::EclIO::INTE; else if (arrTypeStr == "REAL") arrType = Opm::EclIO::REAL; - else if (arrTypeStr == "DOUB") + else if (arrTypeStr == "DOUB"){ arrType = Opm::EclIO::DOUB; - else if (arrTypeStr == "CHAR") + elementSize = 8; + } + else if (arrTypeStr == "CHAR"){ arrType = Opm::EclIO::CHAR; + elementSize = 8; + } + else if (arrTypeStr.substr(0,1)=="C"){ + arrType = Opm::EclIO::C0NN; + elementSize = std::stoi(arrTypeStr.substr(1,3)); + } else if (arrTypeStr == "LOGI") arrType = Opm::EclIO::LOGI; else if (arrTypeStr == "MESS") @@ -358,11 +396,17 @@ void Opm::EclIO::readFormattedHeader(std::fstream& fileH, std::string& arrName, template std::vector Opm::EclIO::readBinaryArray(std::fstream& fileH, const int64_t size, Opm::EclIO::eclArrType type, - std::function& flip) + std::function& flip, int elementSize) { std::vector arr; auto sizeData = block_size_data_binary(type); + + if (type == Opm::EclIO::C0NN){ + std::get<1>(sizeData)= std::get<1>(sizeData) / std::get<0>(sizeData) * elementSize; + std::get<0>(sizeData) = elementSize; + } + int sizeOfElement = std::get<0>(sizeData); int maxBlockSize = std::get<1>(sizeData); int maxNumberOfElements = maxBlockSize / sizeOfElement; @@ -370,11 +414,11 @@ std::vector Opm::EclIO::readBinaryArray(std::fstream& fileH, const int64_t si arr.reserve(size); int64_t rest = size; + while (rest > 0) { int dhead; fileH.read(reinterpret_cast(&dhead), sizeof(dhead)); dhead = Opm::EclIO::flipEndianInt(dhead); - int num = dhead / sizeOfElement; if ((num > maxNumberOfElements) || (num < 0)) { @@ -383,7 +427,13 @@ std::vector Opm::EclIO::readBinaryArray(std::fstream& fileH, const int64_t si for (int i = 0; i < num; i++) { T2 value; - fileH.read(reinterpret_cast(&value), sizeOfElement); + + if constexpr (std::is_same_v) { + value.resize(sizeOfElement) ; + fileH.read(&value[0], sizeOfElement); + } else + fileH.read(reinterpret_cast(&value), sizeOfElement); + arr.push_back(flip(value)); } @@ -411,21 +461,21 @@ std::vector Opm::EclIO::readBinaryArray(std::fstream& fileH, const int64_t si std::vector Opm::EclIO::readBinaryInteArray(std::fstream &fileH, const int64_t size) { std::function f = Opm::EclIO::flipEndianInt; - return readBinaryArray(fileH, size, Opm::EclIO::INTE, f); + return readBinaryArray(fileH, size, Opm::EclIO::INTE, f, sizeOfInte); } std::vector Opm::EclIO::readBinaryRealArray(std::fstream& fileH, const int64_t size) { std::function f = Opm::EclIO::flipEndianFloat; - return readBinaryArray(fileH, size, Opm::EclIO::REAL, f); + return readBinaryArray(fileH, size, Opm::EclIO::REAL, f, sizeOfReal); } std::vector Opm::EclIO::readBinaryDoubArray(std::fstream& fileH, const int64_t size) { std::function f = Opm::EclIO::flipEndianDouble; - return readBinaryArray(fileH, size, Opm::EclIO::DOUB, f); + return readBinaryArray(fileH, size, Opm::EclIO::DOUB, f, sizeOfDoub); } std::vector Opm::EclIO::readBinaryLogiArray(std::fstream &fileH, const int64_t size) @@ -433,17 +483,28 @@ std::vector Opm::EclIO::readBinaryLogiArray(std::fstream &fileH, const int std::function f = [](unsigned int intVal) { bool value; - if (intVal == Opm::EclIO::true_value) { + if (intVal == Opm::EclIO::true_value_ecl) { value = true; } else if (intVal == Opm::EclIO::false_value) { value = false; + } else if (intVal == Opm::EclIO::true_value_ix) { + value = true; } else { OPM_THROW(std::runtime_error, "Error reading logi value"); } return value; }; - return readBinaryArray(fileH, size, Opm::EclIO::LOGI, f); + return readBinaryArray(fileH, size, Opm::EclIO::LOGI, f, sizeOfLogi); +} + +std::vector Opm::EclIO::readBinaryRawLogiArray(std::fstream &fileH, const int64_t size) +{ + std::function f = [](unsigned int intVal) + { + return intVal; + }; + return readBinaryArray(fileH, size, Opm::EclIO::LOGI, f, sizeOfLogi); } @@ -455,7 +516,18 @@ std::vector Opm::EclIO::readBinaryCharArray(std::fstream& fileH, co std::string res(val.begin(), val.end()); return Opm::EclIO::trimr(res); }; - return readBinaryArray(fileH, size, Opm::EclIO::CHAR, f); + return readBinaryArray(fileH, size, Opm::EclIO::CHAR, f, sizeOfChar); +} + + +std::vector Opm::EclIO::readBinaryC0nnArray(std::fstream& fileH, const int64_t size, int elementSize) +{ + std::function f = [](const std::string& val) + { + return Opm::EclIO::trimr(val); + }; + + return readBinaryArray(fileH, size, Opm::EclIO::C0NN, f, elementSize); } @@ -479,7 +551,6 @@ std::vector Opm::EclIO::readFormattedArray(const std::string& file_str, const } return arr; - } @@ -495,7 +566,8 @@ std::vector Opm::EclIO::readFormattedInteArray(const std::string& file_str, } -std::vector Opm::EclIO::readFormattedCharArray(const std::string& file_str, const int64_t size, int64_t fromPos) +std::vector Opm::EclIO::readFormattedCharArray(const std::string& file_str, const int64_t size, + int64_t fromPos, int elementSize) { std::vector arr; arr.reserve(size); @@ -504,7 +576,7 @@ std::vector Opm::EclIO::readFormattedCharArray(const std::string& f for (int i=0; i< size; i++) { p1 = file_str.find_first_of('\'',p1); - std::string value = file_str.substr(p1 + 1, 8); + std::string value = file_str.substr(p1 + 1, elementSize); if (value == " ") { arr.push_back(""); @@ -512,7 +584,7 @@ std::vector Opm::EclIO::readFormattedCharArray(const std::string& f arr.push_back(Opm::EclIO::trimr(value)); } - p1 = p1+10; + p1 = p1 + elementSize + 2; } return arr; @@ -533,6 +605,18 @@ std::vector Opm::EclIO::readFormattedRealArray(const std::string& file_st return readFormattedArray(file_str, size, fromPos, f); } +std::vector Opm::EclIO::readFormattedRealRawStrings(const std::string& file_str, const int64_t size, int64_t fromPos) +{ + + + std::function f = [](const std::string& val) + { + return val; + }; + + return readFormattedArray(file_str, size, fromPos, f); +} + std::vector Opm::EclIO::readFormattedLogiArray(const std::string& file_str, const int64_t size, int64_t fromPos) { @@ -559,15 +643,18 @@ std::vector Opm::EclIO::readFormattedDoubArray(const std::string& file_s { auto p1 = val.find_first_of("D"); - if (p1 == std::string::npos) { - auto p2 = val.find_first_of("-+", 1); - if (p2 != std::string::npos) { - val = val.insert(p2,"E"); - } - } else { + if (p1 != std::string::npos) { val.replace(p1,1,"E"); } + p1 = val.find_first_of("E"); + + if (p1 == std::string::npos) { + auto p2 = val.find_first_of("-+", 1); + + if (p2 != std::string::npos) + val = val.insert(p2,"E"); + } return std::stod(val); }; diff --git a/test_util/convertECL.cpp b/test_util/convertECL.cpp index 2e944ae3d..979028913 100644 --- a/test_util/convertECL.cpp +++ b/test_util/convertECL.cpp @@ -50,6 +50,8 @@ void writeArray(std::string name, eclArrType arrType, T& file1, int index, EclOu write(outFile, file1, name, index); } else if (arrType == CHAR) { write(outFile, file1, name, index); + } else if (arrType == C0NN) { + write(outFile, file1, name, index); } else if (arrType == MESS) { outFile.message(name); } else { @@ -58,6 +60,7 @@ void writeArray(std::string name, eclArrType arrType, T& file1, int index, EclOu } } + template void writeArray(std::string name, eclArrType arrType, T& file1, int index, int reportStepNumber, EclOutput& outFile) { @@ -79,12 +82,24 @@ void writeArray(std::string name, eclArrType arrType, T& file1, int index, int r } } -void writeArrayList(std::vector& arrayList, EclFile file1, EclOutput& outFile) { + +void writeC0nnArray(std::string name, int elementSize, EclFile& file1, int index, EclOutput& outFile) +{ + auto vect = file1.get(index); + outFile.write(name, vect, elementSize); +} + + +void writeArrayList(std::vector& arrayList, std::vector& elementSizeList, EclFile file1, EclOutput& outFile) { for (size_t index = 0; index < arrayList.size(); index++) { std::string name = std::get<0>(arrayList[index]); eclArrType arrType = std::get<1>(arrayList[index]); - writeArray(name, arrType, file1, index, outFile); + + if (arrType == Opm::EclIO::C0NN){ + writeC0nnArray(name, elementSizeList[index], file1, index, outFile); + } else + writeArray(name, arrType, file1, index, outFile); } } @@ -102,8 +117,9 @@ static void printHelp() { std::cout << "\nconvertECL needs one argument which is the input file to be converted. If this is a binary file the output file will be formatted. If the input file is formatted the output will be binary. \n" << "\nIn addition, the program takes these options (which must be given before the arguments):\n\n" << "-h Print help and exit.\n" - << "-l list report step numbers in the selected restart file.\n" - << "-r extract and convert a spesific report time step number from a unified restart file. \n\n"; + << "-l List report step numbers in the selected restart file.\n" + << "-i Enforce IX standard on output file.\n" + << "-r Extract and convert a spesific report time step number from a unified restart file. \n\n"; } int main(int argc, char **argv) { @@ -112,8 +128,9 @@ int main(int argc, char **argv) { int reportStepNumber = -1; bool specificReportStepNumber = false; bool listProperties = false; + bool enforce_ix_output = false; - while ((c = getopt(argc, argv, "hr:l")) != -1) { + while ((c = getopt(argc, argv, "hr:li")) != -1) { switch (c) { case 'h': printHelp(); @@ -121,6 +138,9 @@ int main(int argc, char **argv) { case 'l': listProperties=true; break; + case 'i': + enforce_ix_output=true; + break; case 'r': specificReportStepNumber=true; reportStepNumber = atoi(optarg); @@ -215,6 +235,11 @@ int main(int argc, char **argv) { EclOutput outFile(resFile, formattedOutput); + if ((file1.is_ix()) || (enforce_ix_output)) { + std::cout << "setting IX flag on output file \n"; + outFile.set_ix(); + } + if (specificReportStepNumber) { if (extension!=".UNRST") { @@ -239,8 +264,8 @@ int main(int argc, char **argv) { file1.loadData(); auto arrayList = file1.getList(); - - writeArrayList(arrayList, file1, outFile); + std::vector elementSizeList = file1.getElementSizeList(); + writeArrayList(arrayList, elementSizeList, file1, outFile); } auto end = std::chrono::system_clock::now(); diff --git a/test_util/rewriteEclFile.cpp b/test_util/rewriteEclFile.cpp new file mode 100644 index 000000000..2dbd58fd9 --- /dev/null +++ b/test_util/rewriteEclFile.cpp @@ -0,0 +1,102 @@ +/* + Copyright 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 . +*/ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + + +static void printHelp() { + + std::cout << "\rewriteEclFile needs a minimum of one arguments which is the input file name. \n" + << "\nIn addition, the program takes these options (which must be given before the arguments):\n\n" + << "-h Print help and exit.\n\n"; +} + +int main(int argc, char **argv) { + + int c = 0; + + while ((c = getopt(argc, argv, "h")) != -1) { + switch (c) { + case 'h': + printHelp(); + return 0; + default: + return EXIT_FAILURE; + } + } + + int argOffset = optind; + + Opm::EclIO::EclFile reffile(argv[argOffset]); + auto arrayList = reffile.getList(); + + std::string outputFile=std::string(argv[argOffset]); + + int p1 = outputFile.find_last_of("."); + std::string ext = outputFile.substr(p1+1); + + outputFile = outputFile.substr(0,p1) + "_REWRITE." + ext; + Opm::EclIO::EclOutput outFile(outputFile, reffile.formattedInput()); + + if (reffile.is_ix()) + outFile.set_ix(); + + reffile.loadData(); + + std::vector elementSizeList = reffile.getElementSizeList(); + + for (size_t n = 0; n < arrayList.size(); n++){ + + std::string name = std::get<0>(arrayList[n]); + auto arrType = std::get<1>(arrayList[n]); + + if (arrType == Opm::EclIO::INTE) { + auto data = reffile.get(n); + outFile.write(name, data); + } else if (arrType == Opm::EclIO::CHAR) { + auto data = reffile.get(n); + outFile.write(name, data); + } else if (arrType == Opm::EclIO::C0NN) { + auto data = reffile.get(n); + outFile.write(name, data, elementSizeList[n]); + } else if (arrType == Opm::EclIO::REAL) { + auto data = reffile.get(n); + outFile.write(name, data); + } else if (arrType == Opm::EclIO::DOUB) { + auto data = reffile.get(n); + outFile.write(name, data); + } else if (arrType == Opm::EclIO::LOGI) { + auto data = reffile.get(n); + outFile.write(name, data); + } else if (arrType == Opm::EclIO::MESS) { + outFile.message(name); + } + } + + return 0; +} diff --git a/test_util/summary.cpp b/test_util/summary.cpp index 57fea83f0..c4f19264e 100644 --- a/test_util/summary.cpp +++ b/test_util/summary.cpp @@ -26,7 +26,6 @@ #include - static void printHelp() { std::cout << "\nsummary needs a minimum of two arguments. First is smspec filename and then list of vectors \n" @@ -36,26 +35,32 @@ static void printHelp() { << "-r extract data only for report steps. \n\n"; } -void printHeader(const std::vector& keyList){ +void printHeader(const std::vector& keyList, const std::vector& width){ - std::cout << "--" << std::setw(14) << keyList[0]; + std::cout << std::endl; - for (size_t n= 1; n < keyList.size(); n++){ - std::cout << std::setw(16) << keyList[n]; + for (size_t n= 0; n < keyList.size(); n++){ + if (width[n] < 14) + std::cout << std::setw(16) << keyList[n]; + else + std::cout << std::setw(width[n] + 2) << keyList[n]; } std::cout << std::endl; } -std::string formatString(float data){ +std::string formatString(float data, int width){ std::stringstream stream; - if (std::fabs(data) < 1e6){ - stream << std::fixed << std::setw(16) << std::setprecision(6) << data; - } else { + if (std::fabs(data) < 1e6) + if (width < 14) + stream << std::fixed << std::setw(16) << std::setprecision(6) << data; + else + stream << std::fixed << std::setw(width + 2) << std::setprecision(6) << data; + + else stream << std::scientific << std::setw(16) << std::setprecision(6) << data; - } return stream.str(); } @@ -128,20 +133,27 @@ int main(int argc, char **argv) { } std::vector> smryData; + std::vector width; + + for (auto name : smryList) + width.push_back(name.size()); + for (auto key : smryList) { std::vector vect = reportStepsOnly ? smryFile.get_at_rstep(key) : smryFile.get(key); smryData.push_back(vect); } - printHeader(smryList); + printHeader(smryList, width); for (size_t s=0; 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 J-@+2r_&?|z-z5M5 literal 0 HcmV?d00001 diff --git a/tests/MODEL1_IX.SMSPEC b/tests/MODEL1_IX.SMSPEC new file mode 100644 index 0000000000000000000000000000000000000000..cae5cf591ac40d6de7736f464b33d10d5a1eeec1 GIT binary patch literal 50036 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 Oo?;zb$X(UftN#P-R95K# literal 0 HcmV?d00001 diff --git a/tests/MODEL1_IX.UNSMRY b/tests/MODEL1_IX.UNSMRY new file mode 100644 index 0000000000000000000000000000000000000000..9729ed49fd3f2aa74045a18fc42d79edc3bfcedd GIT binary patch literal 53680 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 literal 0 HcmV?d00001 diff --git a/tests/test_ESmry.cpp b/tests/test_ESmry.cpp index d6dce5c61..d71425635 100644 --- a/tests/test_ESmry.cpp +++ b/tests/test_ESmry.cpp @@ -366,6 +366,35 @@ BOOST_AUTO_TEST_CASE(TestESmry_4) { } +BOOST_AUTO_TEST_CASE(TestESmry_5) { + + // file MODEL1_IX.SMSPEC and MODEL1_IX.UNSMRY are output from comercial simulator ix with + // BASE_MODEL_1.DATA in opm-tests + + // array WGNAME (of type CHAR) in Eclipse and Flow smspec files, is replaced with + // array NAMES (of type C0NN) in IX smspec files + + ESmry smry1("MODEL1_IX.SMSPEC"); + smry1.LoadData(); + + std::vector report_timesteps = {31.0, 60.0, 91.0, 121.0, 152.0, 182.0, 213.0, 244.0, 274.0, 305.0, 335.0, 364.0}; + + std::vector qoil_p2 = { 1160.149902, 1199.301147, 1199.304932, 1199.147583, 1199.120239, 1199.040405, 1198.917725, + 1198.765381, 1198.627930, 1198.406616, 1198.143555, 1197.853760 }; + + + std::vector timeVect = smry1.get_at_rstep("TIME"); + std::vector wopr_prod2 = smry1.get_at_rstep("WOPR:PROD-2"); + + for (size_t n = 0; n < timeVect.size() ; n ++) + BOOST_CHECK_CLOSE(timeVect[n], report_timesteps[n], 1e-6); + + for (size_t n = 0; n < wopr_prod2.size() ; n ++) + BOOST_CHECK_CLOSE(wopr_prod2[n], qoil_p2[n], 1e-6); +} + + + namespace fs = Opm::filesystem; BOOST_AUTO_TEST_CASE(TestCreateRSM) { ESmry smry1("SPE1CASE1.SMSPEC"); diff --git a/tests/test_EclIO.cpp b/tests/test_EclIO.cpp index 3067bc13f..493382fa7 100644 --- a/tests/test_EclIO.cpp +++ b/tests/test_EclIO.cpp @@ -87,41 +87,40 @@ void write_header(std::ofstream& ofileH, std::string& arrName, int size, std::st ofileH.write(reinterpret_cast(&bhead), sizeof(bhead)); } - BOOST_AUTO_TEST_CASE(TestEclFile_X231) { std::string filename = "TEST.DAT"; std::string arrName = "TESTX231"; std::vector ivect(10); - std::iota(ivect.begin(), ivect.end(), -4); + std::iota(ivect.begin(), ivect.end(), -4); { std::ofstream ofileH; - ofileH.open(filename, std::ios_base::binary); + ofileH.open(filename, std::ios_base::binary); - int size = static_cast((-1) * std::pow(2,31) + 10); + int size = static_cast((-1) * std::pow(2,31) + 10); write_header(ofileH, arrName, -1, std::string("X231")); write_header(ofileH, arrName, size, std::string("INTE")); int sizeData = ivect.size()*sizeof(int); sizeData = flipEndianInt(sizeData); - + ofileH.write(reinterpret_cast(&sizeData), sizeof(sizeData)); for (auto v : ivect){ - int fval = flipEndianInt(v); + int fval = flipEndianInt(v); ofileH.write(reinterpret_cast(&fval), sizeof(fval)); } - + ofileH.write(reinterpret_cast(&sizeData), sizeof(sizeData)); ofileH.close(); } - + EclFile test1(filename); auto array = test1.get(arrName); - + for (size_t n = 0; n < 10; n++){ BOOST_CHECK_EQUAL(array[n], ivect[n]); } @@ -193,6 +192,7 @@ BOOST_AUTO_TEST_CASE(TestEclFile_BINARY) { BOOST_CHECK_EQUAL(vect5b.size(), 312U); } + BOOST_AUTO_TEST_CASE(TestEclFile_FORMATTED) { std::string testFile1="ECLFILE.INIT"; @@ -238,6 +238,31 @@ BOOST_AUTO_TEST_CASE(TestEclFile_FORMATTED) { } + +BOOST_AUTO_TEST_CASE(TestEclFile_IX) { + + // file MODEL1_IX.INIT is output from comercial simulator ix with + // BASE_MODEL_1.DATA in opm-tests + + // binary representation of true value for data type LOGI is different + // compared with output from Eclipse and OPM-Flow + + std::string testInitFile="MODEL1_IX.INIT"; + + EclFile file1(testInitFile); + file1.loadData(); + + std::vector refLogihead = {true, true, false, false, true, false, false, false, false, false, false, + false, false, false, false, false, true, false, true, false }; + + auto logih = file1.get("LOGIHEAD"); + + for (size_t n = 0 ; n < refLogihead.size(); n++) + BOOST_CHECK_EQUAL(refLogihead[n], logih[n]); +} + + + BOOST_AUTO_TEST_CASE(TestEcl_Write_binary) { std::string inputFile="ECLFILE.INIT"; @@ -391,26 +416,128 @@ BOOST_AUTO_TEST_CASE(TestEcl_getList) { BOOST_AUTO_TEST_CASE(TestEcl_Write_CHAR) { - std::string testFile="TEST.FDAT"; - std::vector refStrList = {"This", "is", "a test.", "", "charact", "er >'<", "can be", "part of", "a string"}; + std::string testFile1="TEST.FDAT"; + std::string testFile2="TEST2.DAT"; + + std::vector refStrList1 = {"This", "is", "a test.", "", "charact", "er >'<", "can be", "part of", "a string"}; + std::vector refStrList2 = {"strings with length", "beyone 8 character","is also possible", "will use type C0nn"}; { - EclOutput eclTest(testFile, true); - eclTest.write("TEST",refStrList); + EclOutput eclTest(testFile1, true); + eclTest.write("TEST1",refStrList1); } { - EclFile file1(testFile); - std::vector strList=file1.get("TEST"); + EclFile file1(testFile1); + std::vector strList=file1.get("TEST1"); - for (size_t n = 0; n < refStrList.size(); n++) { - BOOST_CHECK(refStrList[n] == strList[n]); + for (size_t n = 0; n < refStrList1.size(); n++) { + BOOST_CHECK(refStrList1[n] == strList[n]); } + + auto arrayList =file1.getList(); + BOOST_CHECK(std::get<1>(arrayList[0]) == Opm::EclIO::CHAR); } - if (remove(testFile.c_str())==-1) { + // the next should automatically use C019 + { + EclOutput eclTest(testFile1, true); + eclTest.write("TEST2",refStrList2); + } + + { + EclFile file1(testFile1); + std::vector strList=file1.get("TEST2"); + + for (size_t n = 0; n < refStrList2.size(); n++) { + BOOST_CHECK(refStrList2[n] == strList[n]); + } + + auto arrayList =file1.getList(); + BOOST_CHECK(std::get<1>(arrayList[0]) == Opm::EclIO::C0NN); + } + + // the next should automatically use C008 (and not CHAR) + // will use C0nn since element size is specified in function call + { + EclOutput eclTest(testFile1, true); + eclTest.write("TEST3",refStrList1, 8); + } + + { + EclFile file1(testFile1); + std::vector strList=file1.get("TEST3"); + + for (size_t n = 0; n < refStrList1.size(); n++) { + BOOST_CHECK(refStrList1[n] == strList[n]); + } + + auto arrayList =file1.getList(); + BOOST_CHECK(std::get<1>(arrayList[0]) == Opm::EclIO::C0NN); + } + + // testing binary CHAR/C0nn format + + { + EclOutput eclTest(testFile2, false); + eclTest.write("TEST4",refStrList1); + } + + { + EclFile file1(testFile2); + std::vector strList=file1.get("TEST4"); + + for (size_t n = 0; n < refStrList1.size(); n++) { + BOOST_CHECK(refStrList1[n] == strList[n]); + } + + auto arrayList =file1.getList(); + BOOST_CHECK(std::get<1>(arrayList[0]) == Opm::EclIO::CHAR); + } + + // the next should automatically use C019 + { + EclOutput eclTest(testFile2, false); + eclTest.write("TEST5",refStrList2); + } + + { + EclFile file1(testFile2); + std::vector strList=file1.get("TEST5"); + + for (size_t n = 0; n < refStrList2.size(); n++) { + BOOST_CHECK(refStrList2[n] == strList[n]); + } + + auto arrayList =file1.getList(); + BOOST_CHECK(std::get<1>(arrayList[0]) == Opm::EclIO::C0NN); + } + + // the next should automatically use C008 (and not CHAR) + // will use C0nn since element size is specified in function call + { + EclOutput eclTest(testFile2, false); + eclTest.write("TEST6",refStrList1, 8); + } + + { + EclFile file1(testFile2); + std::vector strList=file1.get("TEST6"); + + for (size_t n = 0; n < refStrList1.size(); n++) { + BOOST_CHECK(refStrList1[n] == strList[n]); + } + + auto arrayList =file1.getList(); + BOOST_CHECK(std::get<1>(arrayList[0]) == Opm::EclIO::C0NN); + } + + if (remove(testFile1.c_str())==-1) { std::cout << " > Warning! temporary file was not deleted" << std::endl; }; + if (remove(testFile2.c_str())==-1) { + std::cout << " > Warning! temporary file was not deleted" << std::endl; + }; }