diff --git a/CMakeLists.txt b/CMakeLists.txt index 061d8c505..3c74c4d0b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -273,20 +273,6 @@ if (OPM_ENABLE_PYTHON) add_custom_target(opmcommon_python ALL DEPENDS python/python/opm/${python_lib_target}) add_dependencies(opmcommon_python opmcommon) - - add_custom_command(OUTPUT python/python/opm/python${PYTHON_VER}/libopmioecl_python.so - DEPENDS - python/cxx/eclipse_io.cpp - COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/python/ ${CMAKE_BINARY_DIR}/python - COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_BINARY_DIR}/python/setup.py - build - build_ext - --build-lib=${CMAKE_BINARY_DIR}/python/python/opm/python${PYTHON_VER} - WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/python - COMMENT "Building more python bindings") - - - # The install target is based on manually copying the python file tree to the # installation area with a small installation script 'install.py'. Would have # preferred to use standard setup.py install, but the setup.py based solution diff --git a/CMakeLists_files.cmake b/CMakeLists_files.cmake index 2f79ecb1d..d36629620 100644 --- a/CMakeLists_files.cmake +++ b/CMakeLists_files.cmake @@ -183,6 +183,7 @@ if(ENABLE_ECL_INPUT) python/cxx/converters.cpp python/cxx/deck.cpp python/cxx/deck_keyword.cpp + python/cxx/eclipse_io.cpp python/cxx/eclipse_3d_properties.cpp python/cxx/eclipse_config.cpp python/cxx/eclipse_grid.cpp diff --git a/python/cxx/eclipse_io.cpp b/python/cxx/eclipse_io.cpp index f77afc03c..380865547 100644 --- a/python/cxx/eclipse_io.cpp +++ b/python/cxx/eclipse_io.cpp @@ -5,32 +5,39 @@ #include #include +#include "export.hpp" +#include "converters.hpp" namespace py = pybind11; -class EclFileTmp : public Opm::EclIO::EclFile { - -public: - EclFileTmp(const std::string& filename): Opm::EclIO::EclFile(filename) {}; - py::array_t getFloatNumpy(int arrIndex) { - std::vector tmp=get(arrIndex); - return py::array(py::dtype("f"), {tmp.size()}, {}, &tmp[0]); - }; +namespace { - py::array_t getDoubleNumpy(int arrIndex) { - std::vector tmp=get(arrIndex); - return py::array(py::dtype("d"), {tmp.size()}, {}, &tmp[0]); - }; +py::array get_vector(Opm::EclIO::EclFile * file_ptr, std::size_t array_index) { + auto array_type = std::get<1>(file_ptr->getList()[array_index]); + if (array_type == Opm::EclIO::INTE) + return convert::numpy_array( file_ptr->get(array_index) ); - py::array_t getIntegerNumpy(int arrIndex) { - std::vector tmp=get(arrIndex); - return py::array(py::dtype("i"), {tmp.size()}, {}, &tmp[0]); - }; -}; + if (array_type == Opm::EclIO::REAL) + return convert::numpy_array( file_ptr->get(array_index) ); + + if (array_type == Opm::EclIO::DOUB) + return convert::numpy_array( file_ptr->get(array_index) ); + + if (array_type == Opm::EclIO::LOGI) + return convert::numpy_array( file_ptr->get(array_index) ); + + if (array_type == Opm::EclIO::CHAR) + return convert::numpy_string_array( file_ptr->get(array_index) ); + + throw std::logic_error("Data type not supported"); +} + +} -PYBIND11_MODULE(libopmioecl_python, m) { + +void python::common::export_IO(py::module& m) { py::enum_(m, "eclArrType", py::arithmetic()) .value("INTE", Opm::EclIO::INTE) @@ -41,26 +48,13 @@ PYBIND11_MODULE(libopmioecl_python, m) { .value("MESS", Opm::EclIO::MESS) .export_values(); - py::class_>>(m, "EclEntry") - .def(py::init<>()) - .def("__len__", [](const std::vector> &v) { return v.size(); }) - .def("__getitem__", [](std::vector> &v, int item) { return v[item];}) - .def("__iter__", [](std::vector> &v) { - return py::make_iterator(v.begin(), v.end()); - }, py::keep_alive<0, 1>()); - - py::class_(m, "EclFileBind") - .def(py::init()) - .def("getList", &EclFileTmp::getList) - .def("hasKey", &EclFileTmp::hasKey) - - .def("loadAllData", (void (EclFileTmp::*)(void)) &EclFileTmp::loadData) - .def("loadDataByIndex", (void (EclFileTmp::*)(int)) &EclFileTmp::loadData) - - .def("getRealFromIndexNumpy", &EclFileTmp::getFloatNumpy) - .def("getDoubFromIndexNumpy", &EclFileTmp::getDoubleNumpy) - .def("getInteFromIndexNumpy", &EclFileTmp::getIntegerNumpy) - .def("getLogiFromIndex", (const std::vector& (EclFileTmp::*)(int)) &EclFileTmp::get) - .def("getCharFromIndex", (const std::vector& (EclFileTmp::*)(int)) &EclFileTmp::get); + py::class_(m, "EclFile") + .def(py::init(), py::arg("filename"), py::arg("preload") = false) + .def_property_readonly("arrays", &Opm::EclIO::EclFile::getList) + .def("getListOfArrays", &Opm::EclIO::EclFile::getList) + .def("__contains__", &Opm::EclIO::EclFile::hasKey) + .def("__len__", &Opm::EclIO::EclFile::size) + .def("__getitem__", &get_vector) + .def("get", &get_vector); } diff --git a/python/cxx/export.cpp b/python/cxx/export.cpp index ce966930a..46054e227 100644 --- a/python/cxx/export.cpp +++ b/python/cxx/export.cpp @@ -18,6 +18,7 @@ void python::common::export_all(py::module& module) { export_EclipseGrid(module); export_UnitSystem(module); export_Log(module); + export_IO(module); } diff --git a/python/cxx/export.hpp b/python/cxx/export.hpp index 18833ec2e..0dc60a142 100644 --- a/python/cxx/export.hpp +++ b/python/cxx/export.hpp @@ -29,6 +29,7 @@ void export_Schedule(py::module& module); void export_TableManager(py::module& module); void export_Well(py::module& module); void export_Log(py::module& module); +void export_IO(py::module& module); } #endif //SUNBEAM_HPP diff --git a/python/python/opm/_common.py b/python/python/opm/_common.py index a07f38252..df593dfb3 100644 --- a/python/python/opm/_common.py +++ b/python/python/opm/_common.py @@ -18,8 +18,7 @@ from .libopmcommon_python import EclipseState from .libopmcommon_python import Schedule from .libopmcommon_python import OpmLog from .libopmcommon_python import SummaryConfig - -from .libopmioecl_python import EclFileBind, eclArrType +from .libopmcommon_python import EclFile, eclArrType #from .schedule import Well, Connection, Schedule #from .config import EclipseConfig diff --git a/python/python/opm/io/ecl/__init__.py b/python/python/opm/io/ecl/__init__.py index f17a242a7..cd80ef140 100644 --- a/python/python/opm/io/ecl/__init__.py +++ b/python/python/opm/io/ecl/__init__.py @@ -1,71 +1,21 @@ - -from opm._common import EclFileBind, eclArrType +from opm._common import eclArrType +from opm._common import EclFile -class EclFile: +# When extracting the strings from CHAR keywords we get a character array, in +# Python this becomes a list of bytes. This desperate monkey-patching is to +# ensure the EclFile class returns normal Python strings in the case of CHAR +# arrays. The return value is normal Python list of strings. - def __init__(self, fileName): - self.name = fileName - self.arrayNameList=[] - self.arrayTypeList=[] - - self.eclfile = EclFileBind(fileName) +__getitem__ = EclFile.__getitem__ - arrayList = self.eclfile.getList() +def getitem(self, index): + data = __getitem__(self, index) + array_type = self.arrays[index][1] + if array_type == eclArrType.CHAR: + return [ x.decode("utf-8") for x in data ] - for name, type, size in arrayList: - self.arrayNameList.append(name) + return data - if type==eclArrType.INTE: - self.arrayTypeList.append(eclArrType.INTE) - elif type==eclArrType.REAL: - self.arrayTypeList.append(eclArrType.REAL) - elif type==eclArrType.DOUB: - self.arrayTypeList.append(eclArrType.DOUB) - elif type==eclArrType.CHAR: - self.arrayTypeList.append(eclArrType.CHAR) - elif type==eclArrType.LOGI: - self.arrayTypeList.append(eclArrType.LOGI) - elif type==eclArrType.MESS: - self.arrayTypeList.append(eclArrType.MESS) - else: - message = "Unknown array type: %s for array: '%s' in file %s" % (repr(type),name, fileName) - raise NameError(message) - - def getListOfArrays(self): - return self.arrayNameList - - def getNumArrays(self): - return len(self.arrayNameList) - - def hasArray(self, name): - return self.eclfile.hasKey(name) - - def get(self, arg): - - try: - ind = int(arg) - except: - if not arg in self.arrayNameList: - message = "Array '%s' not found in file %s" % (arg,self.name) - raise NameError(message) - - ind = self.arrayNameList.index(arg) - - self.eclfile.loadDataByIndex(ind) - - if self.arrayTypeList[ind]==eclArrType.INTE: - array=self.eclfile.getInteFromIndexNumpy(ind) - elif self.arrayTypeList[ind]==eclArrType.REAL: - array=self.eclfile.getRealFromIndexNumpy(ind) - elif self.arrayTypeList[ind]==eclArrType.DOUB: - array=self.eclfile.getDoubFromIndexNumpy(ind) - elif self.arrayTypeList[ind]==eclArrType.LOGI: - array=self.eclfile.getLogiFromIndex(ind) - elif self.arrayTypeList[ind]==eclArrType.CHAR: - array=self.eclfile.getCharFromIndex(ind) - elif self.arrayTypeList[ind]==eclArrType.MESS: - array=[] - - return array +setattr(EclFile, "__getitem__", getitem) diff --git a/python/setup.py b/python/setup.py index 74d97857f..e651b5a35 100644 --- a/python/setup.py +++ b/python/setup.py @@ -39,6 +39,7 @@ ext_modules = [ 'cxx/converters.cpp', 'cxx/deck.cpp', 'cxx/deck_keyword.cpp', + 'cxx/eclipse_io.cpp', 'cxx/eclipse_3d_properties.cpp', 'cxx/eclipse_config.cpp', 'cxx/eclipse_grid.cpp', diff --git a/python/tests/test_eclfile.py b/python/tests/test_eclfile.py index 59a2b26ba..7aa600eaf 100755 --- a/python/tests/test_eclfile.py +++ b/python/tests/test_eclfile.py @@ -10,37 +10,45 @@ except ImportError: +def array_index(ecl_file, target_kw): + index_list = [] + for index,kw in enumerate(ecl_file.arrays): + if kw[0] == target_kw: + index_list.append(index) + + return index_list class TestEclFile(unittest.TestCase): - - def test_getListOfArrays(self): + + def test_arrays(self): refList=["INTEHEAD","LOGIHEAD","DOUBHEAD","PORV","DEPTH","DX","DY","DZ","PORO", "PERMX","PERMY", "PERMZ","NTG","TRANX","TRANY","TRANZ","TABDIMS","TAB", "ACTNUM","EQLNUM","FIPNUM","PVTNUM","SATNUM","TRANNNC"] - + self.assertRaises(ValueError, EclFile, "/file/that/does_not_exists") - self.assertEqual(file2uf.getNumArrays(), 24) - file2uf = EclFile(test_path("data/SPE9.INIT")) + file2uf = EclFile(test_path("data/SPE9.INIT"), preload=False) + self.assertEqual(len(file2uf), 24) - arrList = file2uf.getListOfArrays() + arrList = [ x[0] for x in file2uf.arrays ] self.assertEqual(arrList, refList) - self.assertEqual(file2f.getNumArrays(), 24) file2f = EclFile(test_path("data/SPE9.FINIT")) + self.assertEqual(len(file2f), 24) def test_get_function(self): - file1 = EclFile(test_path("data/SPE9.INIT")) + file1 = EclFile(test_path("data/SPE9.INIT"), preload=True) - first = file1.get(0) + first = file1[0] self.assertEqual(len(first), 95) - # get fourth array in file SPE9.INIT which is PORV - test1 = file1.get(3) - test2 = file1.get("PORV") + #fourth array in file SPE9.INIT which is PORV + test1 = file1[3] + porv_index = array_index(file1, "PORV")[0] + test2 = file1[porv_index] for val1, val2 in zip(test1, test2): self.assertEqual(val1, val2) @@ -52,49 +60,52 @@ class TestEclFile(unittest.TestCase): dzList=[20.0, 15.0, 26.0, 15.0, 16.0, 14.0, 8.0, 8.0, 18.0, 12.0, 19.0, 18.0, 20.0, 50.0, 100.0] poroList = [0.087, 0.097, 0.111, 0.16, 0.13, 0.17, 0.17, 0.08, 0.14, 0.13, 0.12, 0.105, 0.12, 0.116, 0.157] ft3_to_bbl = 0.1781076 - + refporv = [] - + for poro, dz in zip(dzList, poroList): for i in range(0,600): refporv.append(300.0*300.0*dz*poro*ft3_to_bbl) - - self.assertTrue(file1.hasArray("PORV")) - porv_np = file1.get("PORV") - + + self.assertTrue("PORV" in file1) + porv_index = array_index(file1, "PORV")[0] + porv_np = file1[porv_index] + self.assertEqual(len(porv_np), 9000) self.assertTrue(isinstance(porv_np, np.ndarray)) self.assertEqual(porv_np.dtype, "float32") - porv_list = file1.get("PORV") - + porv_list = file1[porv_index] + for val1, val2 in zip(porv_np, refporv): self.assertLess(abs(1.0 - val1/val2), 1e-6) - + def test_get_function_double(self): refTabData=[0.147E+02, 0.2E+21, 0.4E+03, 0.2E+21, 0.8E+03, 0.2E+21, 0.12E+04, 0.2E+21, 0.16E+04, 0.2E+21, 0.2E+04, 0.2E+21, 0.24E+04, 0.2E+21, 0.28E+04, 0.2E+21, 0.32E+04, 0.2E+21, 0.36E+04, 0.2E+21, 0.4E+04, 0.5E+04, 0.1E+01, 0.2E+21, 0.98814229249012E+00, 0.2E+21, 0.97513408093613E+00] - - tab = file1.get("TAB") file1 = EclFile(test_path("data/SPE9.INIT")) + tab_index = array_index(file1, "TAB")[0] + + tab = file1[tab_index] self.assertTrue(isinstance(tab, np.ndarray)) self.assertEqual(tab.dtype, "float64") - + for i in range(0, len(refTabData)): self.assertLess(abs(1.0 - refTabData[i]/tab[i]), 1e-12 ) def test_get_function_integer(self): - refTabdims = [ 885, 1, 1, 1, 1, 1, 1, 67, 11, 2, 1, 78, 1, 78, 78, 0, 0, 0, 83, 1, 686, 40, 1, 86, 40, 1, + refTabdims = [ 885, 1, 1, 1, 1, 1, 1, 67, 11, 2, 1, 78, 1, 78, 78, 0, 0, 0, 83, 1, 686, 40, 1, 86, 40, 1, 286, 1, 80, 1 ] - - tabdims = file1.get("TABDIMS") + file1 = EclFile(test_path("data/SPE9.INIT")) + tabdims_index = array_index(file1, "TABDIMS")[0] + tabdims = file1[tabdims_index] self.assertTrue(isinstance(tabdims, np.ndarray)) self.assertEqual(tabdims.dtype, "int32") @@ -107,25 +118,28 @@ class TestEclFile(unittest.TestCase): file1 = EclFile(test_path("data/9_EDITNNC.INIT")) - self.assertTrue(file1.hasArray("LOGIHEAD")) - logih = file1.get("LOGIHEAD") + self.assertTrue("LOGIHEAD" in file1) + logih_index = array_index(file1, "LOGIHEAD")[0] + logih = file1[logih_index] self.assertEqual(len(logih), 121) self.assertEqual(logih[0], True) self.assertEqual(logih[2], False) self.assertEqual(logih[8], True) - + def test_get_function_char(self): - self.assertTrue(file1.hasArray("KEYWORDS")) - keyw = file1.get("KEYWORDS") - file1 = EclFile(test_path("data/9_EDITNNC.SMSPEC")) + + self.assertTrue("KEYWORDS" in file1) + kw_index = array_index(file1, "KEYWORDS")[0] + keyw = file1[kw_index] + self.assertEqual(len(keyw), 312) self.assertEqual(keyw[0], "TIME") self.assertEqual(keyw[16], "FWCT") - + if __name__ == "__main__": unittest.main() - +