diff --git a/CMakeLists.txt b/CMakeLists.txt index d81da8c60..aa224cb8a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,6 +9,8 @@ option(ENABLE_ECL_INPUT "Enable eclipse input support?" ON) option(ENABLE_ECL_OUTPUT "Enable eclipse output support?" ON) option(ENABLE_MOCKSIM "Build the mock simulator for io testing" ON) option(OPM_ENABLE_PYTHON "Enable python bindings?" OFF) +option(OPM_ENABLE_EMBEDDED_PYTHON "Enable python bindings?" OFF) + # Output implies input if(ENABLE_ECL_OUTPUT) @@ -211,7 +213,8 @@ install(DIRECTORY cmake DESTINATION share/opm) install(FILES etc/opm_bash_completion.sh.in DESTINATION share/opm/etc) if (OPM_ENABLE_PYTHON) - + # ------------------------------------------------------------------------- + # 1: Wrap C++ functionality in Python find_package(PythonInterp REQUIRED) include(FindPythonInterp) @@ -272,6 +275,9 @@ if (OPM_ENABLE_PYTHON) set_target_properties(opmcommon PROPERTIES POSITION_INDEPENDENT_CODE ON) set_directory_properties(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES ${CMAKE_BINARY_DIR}/python/python) + # ------------------------------------------------------------------------- + # Let cmake configure some small shell scripts which can be used to simplify + # building and testing of the Python extensions. configure_file(python/setup-build.sh.in tmp/setup-build.sh) file( COPY ${PROJECT_BINARY_DIR}/tmp/setup-build.sh DESTINATION ${PROJECT_BINARY_DIR} @@ -283,6 +289,15 @@ if (OPM_ENABLE_PYTHON) FILE_PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE ) configure_file(python/enable-python.sh.in enable-python.sh) + + # ------------------------------------------------------------------------- + # 2: Embed the Python interpreter for keywords like PYACTION and PYINPUT + if (OPM_ENABLE_EMBEDDED_PYTHON) + add_subdirectory(python/pybind11) + #target_link_libraries(opmcommon PRIVATE pybind11::embed) + include_directories("python/pybind11/include;${PYTHON_INCLUDE_DIRS}") + target_link_libraries(opmcommon PRIVATE ${PYTHON_LIBRARY}) + + add_definitions(-DEMBEDDED_PYTHON) + endif() endif() - - diff --git a/CMakeLists_files.cmake b/CMakeLists_files.cmake index d593a2c80..709bfffca 100644 --- a/CMakeLists_files.cmake +++ b/CMakeLists_files.cmake @@ -46,6 +46,8 @@ if(ENABLE_ECL_INPUT) src/opm/parser/eclipse/Deck/DeckOutput.cpp src/opm/parser/eclipse/Deck/Section.cpp src/opm/parser/eclipse/Deck/UDAValue.cpp + src/opm/parser/eclipse/Python/Python.cpp + src/opm/parser/eclipse/Python/PythonInterp.cpp src/opm/parser/eclipse/EclipseState/AquiferCT.cpp src/opm/parser/eclipse/EclipseState/Aquifetp.cpp src/opm/parser/eclipse/EclipseState/Aquancon.cpp @@ -234,6 +236,7 @@ if(ENABLE_ECL_INPUT) tests/parser/DynamicVectorTests.cpp tests/parser/Eclipse3DPropertiesTests.cpp tests/parser/EclipseGridTests.cpp + tests/parser/EmbeddedPython.cpp tests/parser/EqualRegTests.cpp tests/parser/EventTests.cpp tests/parser/FaceDirTests.cpp @@ -591,7 +594,8 @@ if(ENABLE_ECL_INPUT) opm/parser/eclipse/Deck/DeckOutput.hpp opm/parser/eclipse/Deck/DeckKeyword.hpp opm/parser/eclipse/Deck/DeckRecord.hpp - opm/parser/eclipse/Deck/UDAValue.hpp) + opm/parser/eclipse/Deck/UDAValue.hpp + opm/parser/eclipse/Python/Python.hpp) endif() if(ENABLE_ECL_OUTPUT) list(APPEND PUBLIC_HEADER_FILES diff --git a/opm/parser/eclipse/Python/Python.hpp b/opm/parser/eclipse/Python/Python.hpp new file mode 100644 index 000000000..3f6545389 --- /dev/null +++ b/opm/parser/eclipse/Python/Python.hpp @@ -0,0 +1,65 @@ +/* + 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 . +*/ + +#ifndef OPM_EMBEDDED_PYTHON +#define OPM_EMBEDDED_PYTHON + +#include +#include + + +namespace Opm { +class PythonInterp; + + + +/* + This class is a thin wrapper around the PythonInterp class. The Python class + can always be safely instantiated, but the actual PythonInterp implementation + depends on whether Python support was enabled when this library instance was + compiled. + + If one the methods actually invoking the Python interpreter is invoked without + proper Python support a dummy PythinInterp instance will be used; and that + will just throw std::logic_error. The operator bool can be used to check if + this Python manager indeed has a valid Python runtime: + + + Python python; + + if (python) + python.exec("print('Hello world')") + else + OpmLog::Error("This version of opmcommon has been built with support for embedded Python"); + +*/ + +class Python { +public: + Python(); + bool exec(const std::string& python_code); + explicit operator bool() const; +private: + std::shared_ptr interp; +}; +} + + +#endif + diff --git a/src/opm/parser/eclipse/Python/Python.cpp b/src/opm/parser/eclipse/Python/Python.cpp new file mode 100644 index 000000000..2e2a3d73c --- /dev/null +++ b/src/opm/parser/eclipse/Python/Python.cpp @@ -0,0 +1,45 @@ +/* + 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 "PythonInterp.hpp" + +namespace Opm { + +Python::Python(): + interp(std::make_shared()) +{ +} + + +bool Python::exec(const std::string& python_code) { + this->interp->exec(python_code); + return true; +} + + +Python::operator bool() const { + if (this->interp) + return bool( *this->interp ); + else + return false; +} + +} diff --git a/src/opm/parser/eclipse/Python/PythonInterp.cpp b/src/opm/parser/eclipse/Python/PythonInterp.cpp new file mode 100644 index 000000000..1f9bfdaa9 --- /dev/null +++ b/src/opm/parser/eclipse/Python/PythonInterp.cpp @@ -0,0 +1,38 @@ +/* + 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 "PythonInterp.hpp" + +#ifdef EMBEDDED_PYTHON + +#include +namespace py = pybind11; + +namespace Opm { + + +bool PythonInterp::exec(const std::string& python_code) { + py::object scope = py::module::import("__main__").attr("__dict__"); + py::exec(python_code, scope); + return true; +} + +} + +#endif diff --git a/src/opm/parser/eclipse/Python/PythonInterp.hpp b/src/opm/parser/eclipse/Python/PythonInterp.hpp new file mode 100644 index 000000000..3ce2d4d78 --- /dev/null +++ b/src/opm/parser/eclipse/Python/PythonInterp.hpp @@ -0,0 +1,66 @@ +/* + 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 . +*/ + + +#ifndef EMBEDDED_PYTHON_INTERP +#define EMBEDDED_PYTHON_INTERP + +#include +#include + +#ifdef EMBEDDED_PYTHON + +#include +namespace py = pybind11; + +#endif + + + +namespace Opm { + +#ifdef EMBEDDED_PYTHON + +class PythonInterp { +public: + bool exec(const std::string& python_code); + explicit operator bool() const { return true; } +private: + py::scoped_interpreter guard = {}; +}; + + +#else + +class PythonInterp { +public: + bool exec(const std::string&) { + return this->fail(); + }; + + explicit operator bool() const { return false; } +private: + bool fail() { throw std::logic_error("The current opm code has been built without Python support;"); } +}; + +#endif + +} + +#endif diff --git a/tests/parser/EmbeddedPython.cpp b/tests/parser/EmbeddedPython.cpp new file mode 100644 index 000000000..e7e31a58a --- /dev/null +++ b/tests/parser/EmbeddedPython.cpp @@ -0,0 +1,47 @@ +/* + 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 + +#define BOOST_TEST_MODULE EMBEDDED_PYTHON + +#include + +#include + +#ifndef EMBEDDED_PYTHON + +BOOST_AUTO_TEST_CASE(INSTANTIATE) { + Opm::Python python; + BOOST_CHECK(!python); + BOOST_CHECK_THROW(python.exec("print('Hello world')"), std::logic_error); +} + +#else + +BOOST_AUTO_TEST_CASE(INSTANTIATE) { + Opm::Python python; + BOOST_CHECK(python); + BOOST_CHECK_NO_THROW(python.exec("print('Hello world')")); +} + +#endif +