Rewrite EclFile wrapping

- Include the eclipse_io.cpp file in existing extension library - do not create
   a new library.

 - Remove Python class EclFile - just expose the C++ through Pybind1, include
   some monkey-patching of the class to ensure proper Python strings for CHAR
   keywords.

 - Remove string based keyword lookup.

 - Use standard Python methods __len__, __getitem__ and __contains__; get() is
   an equivalent alternative to __getitem__().

 - Add property arrays in addition to getListOfArrays()
This commit is contained in:
Joakim Hove
2019-10-30 22:38:23 +01:00
parent 629606c5d8
commit d66852d8de
9 changed files with 101 additions and 154 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -5,32 +5,39 @@
#include <src/opm/io/eclipse/EclFile.cpp>
#include <opm/io/eclipse/EclIOdata.hpp>
#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<float> getFloatNumpy(int arrIndex) {
std::vector<float> tmp=get<float>(arrIndex);
return py::array(py::dtype("f"), {tmp.size()}, {}, &tmp[0]);
};
namespace {
py::array_t<double> getDoubleNumpy(int arrIndex) {
std::vector<double> tmp=get<double>(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<int>(array_index) );
py::array_t<int> getIntegerNumpy(int arrIndex) {
std::vector<int> tmp=get<int>(arrIndex);
return py::array(py::dtype("i"), {tmp.size()}, {}, &tmp[0]);
};
};
if (array_type == Opm::EclIO::REAL)
return convert::numpy_array( file_ptr->get<float>(array_index) );
if (array_type == Opm::EclIO::DOUB)
return convert::numpy_array( file_ptr->get<double>(array_index) );
if (array_type == Opm::EclIO::LOGI)
return convert::numpy_array( file_ptr->get<bool>(array_index) );
if (array_type == Opm::EclIO::CHAR)
return convert::numpy_string_array( file_ptr->get<std::string>(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_<Opm::EclIO::eclArrType>(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_<std::vector<std::tuple<std::string,Opm::EclIO::eclArrType,int>>>(m, "EclEntry")
.def(py::init<>())
.def("__len__", [](const std::vector<std::tuple<std::string,Opm::EclIO::eclArrType,int>> &v) { return v.size(); })
.def("__getitem__", [](std::vector<std::tuple<std::string,Opm::EclIO::eclArrType,int>> &v, int item) { return v[item];})
.def("__iter__", [](std::vector<std::tuple<std::string,Opm::EclIO::eclArrType,int>> &v) {
return py::make_iterator(v.begin(), v.end());
}, py::keep_alive<0, 1>());
py::class_<EclFileTmp>(m, "EclFileBind")
.def(py::init<const std::string &>())
.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<bool>& (EclFileTmp::*)(int)) &EclFileTmp::get<bool>)
.def("getCharFromIndex", (const std::vector<std::string>& (EclFileTmp::*)(int)) &EclFileTmp::get<std::string>);
py::class_<Opm::EclIO::EclFile>(m, "EclFile")
.def(py::init<const std::string &, bool>(), 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);
}

View File

@@ -18,6 +18,7 @@ void python::common::export_all(py::module& module) {
export_EclipseGrid(module);
export_UnitSystem(module);
export_Log(module);
export_IO(module);
}

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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',

View File

@@ -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()